feat(weibo): 拆分账号管理与个人工作台

This commit is contained in:
yml2213
2026-11-08 16:26:53 +08:00
parent 5d6325d74c
commit d566518478
13 changed files with 1307 additions and 93 deletions
@@ -0,0 +1,97 @@
---
type: 设计
description: 微博外部 CK 账号池与客服个人工作台的拆分设计,覆盖数据归属、权限、接口和 UI。
tags: [微博, CK, 账号池, 工作台, 权限, 协作]
generated:
by: ai/codex
at: 2026-11-08T16:20:00+08:00
status: implemented
---
# 微博账号池与个人工作台
## 1. 目标
把原来的「账号与绑定」拆成两个入口:
- **账号管理**:全局微博账号池,负责 CK 导入、生命周期、标签、批量操作和资料维护。
- **微博工作台**:每个用户自己的账号子集,只展示自己导入的账号,用于绑定、充值、兑换和日常操作。
工作台采用斗鱼模式:**账号实体仍然全局唯一,工作台只保存用户与账号的映射**。同一个账号可以被多人分别加入自己的工作台,工作台增删互不影响;底层 CK 刷新后,所有引用该账号的工作台看到同一份最新资料。
## 2. 数据模型
`weibo_accounts` 继续作为全局账号池,不允许因为工作台拆出第二份 CK。
新增:
```text
weibo_workbench_accounts
id
user_id -> users.id
account_id -> weibo_accounts.id
created_at
UNIQUE(user_id, account_id)
```
删除账号或用户时,对应工作台映射级联删除,不保留孤儿记录。
## 3. 权限
- `weibo:view`:查看全局账号管理。
- `weibo:workbench`:访问个人微博工作台。
- `weibo:import`:导入外部 CK。
- `weibo:manage`:编辑账号标签/备注、启停账号、批量标签。
- `weibo:account:ck_export`:复制账号 CK。
- `weibo:bind`:生成绑定二维码。
- `weibo:recharge`:创建充值订单。
- `weibo:exchange`:执行兑换。
客服和运营默认拥有个人工作台及日常操作权限;删除账号仍由 `weibo:delete` 控制,客服默认没有。
## 4. 接口
| 方法 | 路径 | 权限 | 说明 |
| --- | --- | --- | --- |
| GET | `/api/weibo/accounts` | `weibo:view` | 全局账号池分页列表 |
| GET | `/api/weibo/workbench/accounts` | `weibo:workbench` | 当前用户工作台账号分页列表 |
| GET | `/api/weibo/workbench/pool` | `weibo:workbench` | 可加入当前工作台且尚未加入的账号 |
| POST | `/api/weibo/workbench/accounts` | `weibo:workbench` | 批量加入工作台,幂等 |
| POST | `/api/weibo/workbench/accounts/remove` | `weibo:workbench` | 批量移出工作台,不影响账号池和其他用户 |
| POST | `/api/weibo/accounts/batch-tag` | `weibo:manage` | 账号管理页批量设置标签 |
工作台接口始终按 `current.id` 过滤,不接受客户端传入用户 ID。
## 5. UI
菜单:
```text
微博
-> 账号管理
-> 微博工作台
-> 外部单协作
```
账号管理页:
- 标题「微博账号管理」。
- 保留全量账号列表、生命周期、批量刷新、批量标签、启停和删除。
- 用于维护账号池,不作为客服的日常入口。
工作台页:
- 标题「微博工作台」。
- 复用账号能力,但列表只显示当前用户工作台映射。
- 「导入账号」从账号池选择账号加入。
- 「导入外部 CK」会先写入全局账号池,再自动加入当前工作台。
- 支持单个/批量移出工作台,不删除账号池数据。
- 绑定二维码、充值、兑换、功能查询继续复用现有账号接口。
## 6. 数据隔离边界
- 工作台列表、账号池候选和增删接口都按当前登录用户过滤。
- 加入/移出只操作 `weibo_workbench_accounts`,不修改账号归属或 CK。
- 账号池仍由全局权限控制;客服可以看到账号管理入口,但不能删除账号。
- 外部单协作继续引用全局账号 ID,不受个人工作台选择影响。
+1 -1
View File
@@ -26,7 +26,7 @@ TESTS_DIR = Path(__file__).resolve().parent
PROJECT_ROOT = TESTS_DIR.parent
VERSIONS_DIR = PROJECT_ROOT / "web" / "backend" / "migrations" / "versions"
HEAD_REVISION = "20261108_0071"
HEAD_REVISION = "20261108_0072"
# 迁移新增、但不声明在模型里的复合查询索引。
EXTRA_INDEXES = (
+106 -1
View File
@@ -18,10 +18,12 @@ from core.weibo import (
WeiboRechargeError,
WeiboRechargeOrder,
)
from web.backend.models import AuditLog, User, WeiboAccount
from web.backend.models import AuditLog, User, WeiboAccount, WeiboWorkbenchAccount
from web.backend.permissions import ROLE_PERMISSIONS
from web.backend.routers import weibo as weibo_router
from web.backend.routers.weibo import (
add_workbench_accounts,
batch_tag_accounts,
copy_account_ck,
create_bind_qrcodes,
disable_account,
@@ -29,16 +31,21 @@ from web.backend.routers.weibo import (
get_account,
import_cookies,
list_accounts,
list_workbench_accounts,
list_workbench_pool,
refresh_account,
remove_workbench_accounts,
update_account,
)
from web.backend.schemas import (
WeiboAccountBatchTagRequest,
WeiboAccountDisableRequest,
WeiboAccountUpdate,
WeiboCookieImport,
WeiboExchangeRequest,
WeiboPaymentStatusRequest,
WeiboRechargeRequest,
WeiboWorkbenchAccountsRequest,
)
from web.backend.services import weibo_service
@@ -291,6 +298,7 @@ def test_support_role_has_weibo_operation_permissions_without_delete():
assert {
"weibo:view",
"weibo:workbench",
"weibo:import",
"weibo:manage",
"weibo:account:ck_export",
@@ -304,6 +312,103 @@ def test_support_role_has_weibo_operation_permissions_without_delete():
assert "weibo:collab:manage" not in support
def test_weibo_workbench_is_isolated_per_user(db, current_user):
first = WeiboAccount(
uid="4014000101",
nickname="工作台账号A",
cookie="SCF=a; SUB=first",
tag="A",
)
second = WeiboAccount(
uid="4014000102",
nickname="工作台账号B",
cookie="SCF=b; SUB=second",
tag="B",
)
db.add_all([first, second])
db.commit()
user_a = User(
username="weibo-workbench-a",
password_hash="hash",
role="support",
is_active=True,
)
user_b = User(
username="weibo-workbench-b",
password_hash="hash",
role="support",
is_active=True,
)
db.add_all([user_a, user_b])
db.commit()
add_workbench_accounts(
WeiboWorkbenchAccountsRequest(account_ids=[first.id]),
db=db,
current=user_a,
)
add_workbench_accounts(
WeiboWorkbenchAccountsRequest(account_ids=[second.id]),
db=db,
current=user_b,
)
page_a = list_workbench_accounts(page=1, page_size=20, db=db, current=user_a)
page_b = list_workbench_accounts(page=1, page_size=20, db=db, current=user_b)
assert [item.uid for item in page_a["items"]] == [first.uid]
assert [item.uid for item in page_b["items"]] == [second.uid]
assert db.query(WeiboWorkbenchAccount).count() == 2
pool_a = list_workbench_pool(page=1, page_size=20, db=db, current=user_a)
assert [item.uid for item in pool_a["items"]] == [second.uid]
remove_workbench_accounts(
WeiboWorkbenchAccountsRequest(account_ids=[first.id]),
db=db,
current=user_a,
)
assert (
list_workbench_accounts(page=1, page_size=20, db=db, current=user_a)["total"]
== 0
)
assert (
list_workbench_accounts(page=1, page_size=20, db=db, current=user_b)["total"]
== 1
)
def test_batch_tag_updates_selected_accounts(db, current_user):
first = WeiboAccount(
uid="4014000201",
nickname="批量标签A",
cookie="SCF=a; SUB=first",
tag="旧A",
)
second = WeiboAccount(
uid="4014000202",
nickname="批量标签B",
cookie="SCF=b; SUB=second",
tag="旧B",
)
db.add_all([first, second])
db.commit()
result = batch_tag_accounts(
WeiboAccountBatchTagRequest(ids=[first.id, second.id], tag="客服组"),
db=db,
current=current_user,
)
assert result["updated"] == 2
tags = {
account.id: account.tag
for account in db.query(WeiboAccount)
.filter(WeiboAccount.id.in_([first.id, second.id]))
.all()
}
assert tags == {first.id: "客服组", second.id: "客服组"}
def test_list_accounts_returns_parallel_page_shape(db, current_user):
assert (
import_cookies(
@@ -0,0 +1,64 @@
"""增加微博个人工作台账号映射。"""
from __future__ import annotations
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
revision: str = "20261108_0072"
down_revision: str | None = "20261108_0071"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
inspector = sa.inspect(op.get_bind())
if not inspector.has_table("weibo_workbench_accounts"):
op.create_table(
"weibo_workbench_accounts",
sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True),
sa.Column("user_id", sa.Integer(), nullable=False),
sa.Column("account_id", sa.Integer(), nullable=False),
sa.Column("created_at", sa.DateTime(), nullable=False),
sa.ForeignKeyConstraint(
["account_id"],
["weibo_accounts.id"],
ondelete="CASCADE",
),
sa.ForeignKeyConstraint(
["user_id"],
["users.id"],
ondelete="CASCADE",
),
sa.UniqueConstraint(
"user_id",
"account_id",
name="uq_weibo_workbench_account",
),
)
inspector = sa.inspect(op.get_bind())
existing_indexes = {
index["name"] for index in inspector.get_indexes("weibo_workbench_accounts")
}
if "ix_weibo_workbench_accounts_user_id" not in existing_indexes:
op.create_index(
"ix_weibo_workbench_accounts_user_id",
"weibo_workbench_accounts",
["user_id"],
)
if "ix_weibo_workbench_accounts_account_id" not in existing_indexes:
op.create_index(
"ix_weibo_workbench_accounts_account_id",
"weibo_workbench_accounts",
["account_id"],
)
def downgrade() -> None:
inspector = sa.inspect(op.get_bind())
if not inspector.has_table("weibo_workbench_accounts"):
return
op.drop_table("weibo_workbench_accounts")
+25
View File
@@ -961,6 +961,31 @@ class WeiboAccount(Base):
)
class WeiboWorkbenchAccount(Base):
"""用户个人微博工作台中的账号选择。"""
__tablename__ = "weibo_workbench_accounts"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
user_id: Mapped[int] = mapped_column(
ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True
)
account_id: Mapped[int] = mapped_column(
ForeignKey("weibo_accounts.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
created_at: Mapped[datetime] = mapped_column(UTCDateTime, default=_utcnow)
__table_args__ = (
UniqueConstraint(
"user_id",
"account_id",
name="uq_weibo_workbench_account",
),
)
class WeiboCollabShop(Base):
"""微博外部单协作的店铺字典。"""
+3
View File
@@ -43,6 +43,7 @@ PERMISSIONS = {
"yyb:history": "查看应用宝充值历史",
# 微博微豆
"weibo:view": "查看微博账号",
"weibo:workbench": "使用个人微博工作台",
"weibo:import": "导入/刷新微博 CK",
"weibo:manage": "编辑、停用和启用微博账号",
"weibo:account:ck_export": "复制微博外部 CK 账号的 CK(写剪贴板)",
@@ -114,6 +115,7 @@ ROLE_PERMISSIONS = {
"yyb:manage",
"yyb:history",
"weibo:view",
"weibo:workbench",
"weibo:import",
"weibo:manage",
"weibo:account:ck_export",
@@ -160,6 +162,7 @@ ROLE_PERMISSIONS = {
"yyb:session",
"yyb:history",
"weibo:view",
"weibo:workbench",
"weibo:import",
"weibo:manage",
"weibo:account:ck_export",
+227 -39
View File
@@ -13,9 +13,10 @@ from core.weibo import WeiboAuthError, WeiboError, WeiboExchangeUncertainError
from ..database import get_db
from ..deps import get_current_user
from ..models import User, WeiboAccount
from ..models import User, WeiboAccount, WeiboWorkbenchAccount
from ..permissions import user_has_permission
from ..schemas import (
WeiboAccountBatchTagRequest,
WeiboAccountCkOut,
WeiboAccountDisableRequest,
WeiboAccountOut,
@@ -37,6 +38,7 @@ from ..schemas import (
WeiboPointRecordItem,
WeiboRechargeOrderOut,
WeiboRechargeRequest,
WeiboWorkbenchAccountsRequest,
)
from ..services.audit_service import record_audit
from ..services.weibo_service import (
@@ -88,6 +90,57 @@ def _account_out(account: WeiboAccount) -> WeiboAccountOut:
return WeiboAccountOut.model_validate(account)
def _apply_account_filters(
query,
*,
search: str,
bind_status: str,
enabled: bool | None,
):
search_text = _query_str(search)
if search_text:
pattern = f"%{search_text}%"
query = query.filter(
or_(
WeiboAccount.uid.ilike(pattern),
WeiboAccount.nickname.ilike(pattern),
WeiboAccount.game_role_name.ilike(pattern),
WeiboAccount.tag.ilike(pattern),
WeiboAccount.remark.ilike(pattern),
)
)
if isinstance(enabled, bool):
query = query.filter(WeiboAccount.is_enabled.is_(enabled))
bind_value = _query_str(bind_status)
if bind_value == "unbound":
query = query.filter(WeiboAccount.is_bound.is_(False))
elif bind_value == "bound":
query = query.filter(WeiboAccount.is_bound.is_(True))
elif bind_value == "rebindable":
query = query.filter(
WeiboAccount.is_bound.is_(True),
WeiboAccount.can_rebind.is_(True),
)
return query
def _paginated_accounts(query, *, page: int, page_size: int) -> dict:
total = query.order_by(None).count()
rows = (
query.options(defer(WeiboAccount.cookie))
.order_by(WeiboAccount.id.desc())
.offset((page - 1) * page_size)
.limit(page_size)
.all()
)
return {
"items": [_account_out(account) for account in rows],
"total": total,
"page": page,
"page_size": page_size,
}
def _ensure_enabled(account: WeiboAccount) -> None:
if not account.is_enabled:
raise HTTPException(status_code=409, detail="微博账号已停用,请先启用")
@@ -149,46 +202,181 @@ def list_accounts(
"""查看已导入的微博账号。"""
_require_weibo_perm(current, "weibo:view")
query = db.query(WeiboAccount)
search_text = _query_str(search)
if search_text:
pattern = f"%{search_text}%"
query = query.filter(
or_(
WeiboAccount.uid.ilike(pattern),
WeiboAccount.nickname.ilike(pattern),
WeiboAccount.game_role_name.ilike(pattern),
WeiboAccount.tag.ilike(pattern),
WeiboAccount.remark.ilike(pattern),
)
)
if isinstance(enabled, bool):
query = query.filter(WeiboAccount.is_enabled.is_(enabled))
bind_value = _query_str(bind_status)
if bind_value == "unbound":
query = query.filter(WeiboAccount.is_bound.is_(False))
elif bind_value == "bound":
query = query.filter(WeiboAccount.is_bound.is_(True))
elif bind_value == "rebindable":
query = query.filter(
WeiboAccount.is_bound.is_(True),
WeiboAccount.can_rebind.is_(True),
)
total = query.count()
rows = (
query.options(defer(WeiboAccount.cookie))
.order_by(WeiboAccount.id.desc())
.offset((page - 1) * page_size)
.limit(page_size)
.all()
query = _apply_account_filters(
db.query(WeiboAccount),
search=search,
bind_status=bind_status,
enabled=enabled,
)
return {
"items": [_account_out(account) for account in rows],
"total": total,
"page": page,
"page_size": page_size,
return _paginated_accounts(query, page=page, page_size=page_size)
@router.get("/workbench/accounts")
def list_workbench_accounts(
search: str = Query(""),
bind_status: str = Query(""),
enabled: bool | None = Query(None),
page: int = Query(1, ge=1),
page_size: int = Query(20, ge=1, le=200),
db: Session = Depends(get_db),
current: User = Depends(get_current_user),
):
"""查看当前用户个人工作台中的微博账号。"""
_require_weibo_perm(current, "weibo:workbench")
query = (
db.query(WeiboAccount)
.join(
WeiboWorkbenchAccount,
WeiboWorkbenchAccount.account_id == WeiboAccount.id,
)
.filter(WeiboWorkbenchAccount.user_id == current.id)
)
query = _apply_account_filters(
query,
search=search,
bind_status=bind_status,
enabled=enabled,
)
return _paginated_accounts(query, page=page, page_size=page_size)
@router.get("/workbench/pool")
def list_workbench_pool(
search: str = Query(""),
page: int = Query(1, ge=1),
page_size: int = Query(20, ge=1, le=200),
db: Session = Depends(get_db),
current: User = Depends(get_current_user),
):
"""查看可加入当前用户工作台、且尚未加入的账号池账号。"""
_require_weibo_perm(current, "weibo:workbench")
selected = db.query(WeiboWorkbenchAccount.account_id).filter(
WeiboWorkbenchAccount.user_id == current.id
)
query = _apply_account_filters(
db.query(WeiboAccount).filter(WeiboAccount.id.not_in(selected)),
search=search,
bind_status="",
enabled=True,
)
return _paginated_accounts(query, page=page, page_size=page_size)
@router.post("/workbench/accounts")
def add_workbench_accounts(
req: WeiboWorkbenchAccountsRequest,
db: Session = Depends(get_db),
current: User = Depends(get_current_user),
):
"""把账号池账号加入当前用户的工作台。"""
_require_weibo_perm(current, "weibo:workbench")
account_ids = sorted(
{account_id for account_id in req.account_ids if account_id > 0}
)
if not account_ids:
raise HTTPException(status_code=400, detail="请先选择账号")
existing_ids = {
account_id
for (account_id,) in db.query(WeiboAccount.id)
.filter(WeiboAccount.id.in_(account_ids))
.all()
}
missing = sorted(set(account_ids) - existing_ids)
if missing:
raise HTTPException(status_code=404, detail=f"账号不存在: {missing}")
selected_ids = {
account_id
for (account_id,) in db.query(WeiboWorkbenchAccount.account_id)
.filter(
WeiboWorkbenchAccount.user_id == current.id,
WeiboWorkbenchAccount.account_id.in_(account_ids),
)
.all()
}
db.add_all(
[
WeiboWorkbenchAccount(user_id=current.id, account_id=account_id)
for account_id in account_ids
if account_id not in selected_ids
]
)
db.commit()
_record_audit_safely(
db,
current,
action="weibo:workbench:add",
target=f"weibo_user:{current.id}",
detail={"account_ids": account_ids},
)
return {"account_ids": account_ids, "success": True}
@router.post("/workbench/accounts/remove")
def remove_workbench_accounts(
req: WeiboWorkbenchAccountsRequest,
db: Session = Depends(get_db),
current: User = Depends(get_current_user),
):
"""从当前用户工作台移出账号,不影响账号池和其他用户。"""
_require_weibo_perm(current, "weibo:workbench")
account_ids = sorted(
{account_id for account_id in req.account_ids if account_id > 0}
)
if not account_ids:
raise HTTPException(status_code=400, detail="请先选择账号")
db.query(WeiboWorkbenchAccount).filter(
WeiboWorkbenchAccount.user_id == current.id,
WeiboWorkbenchAccount.account_id.in_(account_ids),
).delete(synchronize_session=False)
db.commit()
_record_audit_safely(
db,
current,
action="weibo:workbench:remove",
target=f"weibo_user:{current.id}",
detail={"account_ids": account_ids},
)
return {"account_ids": account_ids, "success": True}
@router.post("/accounts/batch-tag")
def batch_tag_accounts(
req: WeiboAccountBatchTagRequest,
db: Session = Depends(get_db),
current: User = Depends(get_current_user),
):
"""批量设置微博账号标签。"""
_require_weibo_perm(current, "weibo:manage")
account_ids = sorted(set(req.ids))
if not account_ids:
raise HTTPException(status_code=400, detail="请先选择账号")
accounts = db.query(WeiboAccount).filter(WeiboAccount.id.in_(account_ids)).all()
actual_ids = {account.id for account in accounts}
missing = sorted(set(account_ids) - actual_ids)
if missing:
raise HTTPException(status_code=404, detail=f"账号不存在: {missing}")
tag = req.tag.strip()
previous = {account.id: account.tag or "" for account in accounts}
for account in accounts:
account.tag = tag
db.commit()
_record_audit_safely(
db,
current,
action="weibo:account:batch_tag",
target=f"weibo_accounts:{len(account_ids)}",
detail={
"account_ids": account_ids,
"tag": tag,
"previous_tags": previous,
},
)
return {"updated": len(accounts), "tag": tag, "success": True}
@router.get("/accounts/{account_id}", response_model=WeiboAccountOut)
+9
View File
@@ -505,6 +505,15 @@ class WeiboAccountUpdate(BaseModel):
remark: str | None = Field(None, max_length=256)
class WeiboAccountBatchTagRequest(BaseModel):
ids: list[int] = Field(min_length=1)
tag: str = Field("", max_length=64)
class WeiboWorkbenchAccountsRequest(BaseModel):
account_ids: list[int] = Field(default_factory=list)
class WeiboAccountDisableRequest(BaseModel):
reason: str = Field("", max_length=256)
+2 -1
View File
@@ -107,7 +107,8 @@ function AppContent() {
<Route path="huya/elite" element={lazyRoute(<HuyaElitePage />)} />
<Route path="huya/collab" element={lazyRoute(<HuyaCollabPage />)} />
<Route path="huya/browser-exchange" element={lazyRoute(<HuyaBrowserExchangePage />)} />
<Route path="weibo/accounts" element={lazyRoute(<WeiboAccountsPage />)} />
<Route path="weibo/accounts" element={lazyRoute(<WeiboAccountsPage mode="management" />)} />
<Route path="weibo/workbench" element={lazyRoute(<WeiboAccountsPage mode="workbench" />)} />
<Route path="weibo/collab" element={lazyRoute(<WeiboCollabPage />)} />
<Route path="yyb/recharge" element={lazyRoute(<YybRechargePage />)} />
<Route path="proxy" element={lazyRoute(<ProxyPage />)} />
+11
View File
@@ -807,6 +807,17 @@ export interface WeiboAccountCkResult {
ck_text: string;
}
export interface WeiboWorkbenchMutationResult {
account_ids: number[];
success: boolean;
}
export interface WeiboBatchTagResult {
updated: number;
tag: string;
success: boolean;
}
export interface WeiboGoodsItem {
id: number;
goods_name: string;
+37
View File
@@ -4,6 +4,7 @@ import type {
PaginatedResponse,
WeiboAccountItem,
WeiboAccountCkResult,
WeiboBatchTagResult,
WeiboBindQrResult,
WeiboExchangeRecordsResult,
WeiboExchangeResult,
@@ -12,6 +13,7 @@ import type {
WeiboPointHistoryResult,
WeiboRechargeOrderResult,
WeiboImportResult,
WeiboWorkbenchMutationResult,
} from './types';
export const weiboApi = {
@@ -60,6 +62,41 @@ export const weiboApi = {
api.post<WeiboAccountCkResult, WeiboAccountCkResult>(
`/weibo/accounts/${accountId}/ck/copy`,
),
batchTagAccounts: (ids: number[], tag: string) =>
api.post<WeiboBatchTagResult, WeiboBatchTagResult>(
'/weibo/accounts/batch-tag',
{ ids, tag },
),
listWorkbenchAccounts: (params: {
search?: string;
bind_status?: string;
enabled?: boolean;
page: number;
page_size: number;
}) =>
api.get<PaginatedResponse<WeiboAccountItem>, PaginatedResponse<WeiboAccountItem>>(
'/weibo/workbench/accounts',
{ params },
),
listWorkbenchPool: (params: {
search?: string;
page: number;
page_size: number;
}) =>
api.get<PaginatedResponse<WeiboAccountItem>, PaginatedResponse<WeiboAccountItem>>(
'/weibo/workbench/pool',
{ params },
),
addWorkbenchAccounts: (accountIds: number[]) =>
api.post<WeiboWorkbenchMutationResult, WeiboWorkbenchMutationResult>(
'/weibo/workbench/accounts',
{ account_ids: accountIds },
),
removeWorkbenchAccounts: (accountIds: number[]) =>
api.post<WeiboWorkbenchMutationResult, WeiboWorkbenchMutationResult>(
'/weibo/workbench/accounts/remove',
{ account_ids: accountIds },
),
bindQrcodes: (accountId: number, channel: 'wx' | 'qq') =>
api.post<WeiboBindQrResult, WeiboBindQrResult>(
`/weibo/accounts/${accountId}/bind-qrcodes`,
+4 -2
View File
@@ -8,7 +8,6 @@ import {
SunOutlined, MoonOutlined, DesktopOutlined, GiftOutlined, ApartmentOutlined, MobileOutlined, ShoppingCartOutlined,
BookOutlined, TrophyOutlined, ShopOutlined, GlobalOutlined,
SafetyCertificateOutlined, FileSearchOutlined, RobotOutlined, FileExcelOutlined,
MessageOutlined,
} from '@ant-design/icons';
import { useNavigate, useLocation, Outlet } from 'react-router-dom';
import { getUser, clearAuth, type AuthUser } from '../store/auth';
@@ -127,7 +126,10 @@ export default function MainLayout({ onLogout }: { onLogout?: () => void }) {
}
if (can('weibo:view')) {
weiboItems.push({ key: '/weibo/accounts', label: '账号与绑定', icon: <MessageOutlined /> });
weiboItems.push({ key: '/weibo/accounts', label: '账号管理', icon: <UserOutlined /> });
}
if (can('weibo:workbench')) {
weiboItems.push({ key: '/weibo/workbench', label: '微博工作台', icon: <DesktopOutlined /> });
}
if (can('weibo:collab:view')) {
weiboItems.push({ key: '/weibo/collab', label: '外部单协作', icon: <FileExcelOutlined /> });
File diff suppressed because it is too large Load Diff