feat(weibo): 支持工作台账号转入外部单协作
This commit is contained in:
@@ -45,6 +45,8 @@ from ..schemas import (
|
||||
WeiboWorkbenchBatchImportItem,
|
||||
WeiboWorkbenchBatchImportOut,
|
||||
WeiboWorkbenchBatchImportRequest,
|
||||
WeiboWorkbenchTransferToCollabOut,
|
||||
WeiboWorkbenchTransferToCollabRequest,
|
||||
)
|
||||
from ..services.audit_service import record_audit
|
||||
from ..services.weibo_service import (
|
||||
@@ -607,6 +609,57 @@ def add_workbench_accounts(
|
||||
return {"account_ids": account_ids, "success": True}
|
||||
|
||||
|
||||
@router.post(
|
||||
"/workbench/accounts/transfer-to-collab",
|
||||
response_model=WeiboWorkbenchTransferToCollabOut,
|
||||
)
|
||||
def transfer_workbench_to_collab(
|
||||
req: WeiboWorkbenchTransferToCollabRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current: User = Depends(get_current_user),
|
||||
):
|
||||
"""把当前工作台账号移出,并引导到外部单协作新建订单。"""
|
||||
|
||||
_require_weibo_perm(current, "weibo:workbench")
|
||||
membership = (
|
||||
db.query(WeiboWorkbenchAccount)
|
||||
.filter(
|
||||
WeiboWorkbenchAccount.user_id == current.id,
|
||||
WeiboWorkbenchAccount.account_id == req.account_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if membership is None:
|
||||
raise HTTPException(status_code=404, detail="该账号不在当前微博工作台")
|
||||
|
||||
account = _get_account(db, req.account_id, current)
|
||||
_ensure_enabled(account)
|
||||
if not str(account.cookie or "").strip():
|
||||
raise HTTPException(
|
||||
status_code=400, detail="该账号没有可用 Cookie,无法转入外部单协作"
|
||||
)
|
||||
|
||||
db.delete(membership)
|
||||
db.commit()
|
||||
_record_audit_safely(
|
||||
db,
|
||||
current,
|
||||
action="weibo:workbench:transfer_collab",
|
||||
target=f"weibo_account:{account.id}",
|
||||
detail={
|
||||
"account_id": account.id,
|
||||
"uid": account.uid,
|
||||
"nickname": account.nickname,
|
||||
},
|
||||
)
|
||||
return WeiboWorkbenchTransferToCollabOut(
|
||||
account_id=account.id,
|
||||
uid=account.uid,
|
||||
nickname=account.nickname,
|
||||
game_role_name=account.game_role_name,
|
||||
)
|
||||
|
||||
|
||||
@router.post("/workbench/accounts/remove")
|
||||
def remove_workbench_accounts(
|
||||
req: WeiboWorkbenchAccountsRequest,
|
||||
|
||||
@@ -521,6 +521,18 @@ class WeiboWorkbenchAccountsRequest(BaseModel):
|
||||
account_ids: list[int] = Field(default_factory=list)
|
||||
|
||||
|
||||
class WeiboWorkbenchTransferToCollabRequest(BaseModel):
|
||||
account_id: int = Field(..., gt=0)
|
||||
|
||||
|
||||
class WeiboWorkbenchTransferToCollabOut(BaseModel):
|
||||
success: bool = True
|
||||
account_id: int
|
||||
uid: str = ""
|
||||
nickname: str = ""
|
||||
game_role_name: str = ""
|
||||
|
||||
|
||||
class WeiboWorkbenchBatchImportRequest(BaseModel):
|
||||
text: str = Field(..., min_length=1, max_length=100_000)
|
||||
|
||||
|
||||
@@ -839,6 +839,14 @@ export interface WeiboWorkbenchBatchImportResult {
|
||||
items: WeiboWorkbenchBatchImportItem[];
|
||||
}
|
||||
|
||||
export interface WeiboWorkbenchTransferToCollabResult {
|
||||
success: boolean;
|
||||
account_id: number;
|
||||
uid: string;
|
||||
nickname: string;
|
||||
game_role_name: string;
|
||||
}
|
||||
|
||||
export interface WeiboBatchTagResult {
|
||||
updated: number;
|
||||
tag: string;
|
||||
|
||||
@@ -16,6 +16,7 @@ import type {
|
||||
WeiboImportResult,
|
||||
WeiboWorkbenchMutationResult,
|
||||
WeiboWorkbenchBatchImportResult,
|
||||
WeiboWorkbenchTransferToCollabResult,
|
||||
} from './types';
|
||||
|
||||
export const weiboApi = {
|
||||
@@ -128,6 +129,13 @@ export const weiboApi = {
|
||||
'/weibo/workbench/accounts/batch-import',
|
||||
{ text },
|
||||
),
|
||||
transferWorkbenchToCollab: (accountId: number) =>
|
||||
api.post<
|
||||
WeiboWorkbenchTransferToCollabResult,
|
||||
WeiboWorkbenchTransferToCollabResult
|
||||
>('/weibo/workbench/accounts/transfer-to-collab', {
|
||||
account_id: accountId,
|
||||
}),
|
||||
removeWorkbenchAccounts: (accountIds: number[]) =>
|
||||
api.post<WeiboWorkbenchMutationResult, WeiboWorkbenchMutationResult>(
|
||||
'/weibo/workbench/accounts/remove',
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
Alert,
|
||||
AutoComplete,
|
||||
@@ -37,6 +38,7 @@ import {
|
||||
ReloadOutlined,
|
||||
SearchOutlined,
|
||||
StopOutlined,
|
||||
SwapOutlined,
|
||||
ZoomInOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import {
|
||||
@@ -159,6 +161,7 @@ export default function WeiboAccountsPage({
|
||||
}) {
|
||||
const { token } = theme.useToken();
|
||||
const { can } = usePermissions();
|
||||
const navigate = useNavigate();
|
||||
const isWorkbench = mode === 'workbench';
|
||||
const canImport = can('weibo:import');
|
||||
const canManage = can('weibo:manage');
|
||||
@@ -168,6 +171,7 @@ export default function WeiboAccountsPage({
|
||||
const canExchange = can('weibo:exchange');
|
||||
const canDelete = can('weibo:delete');
|
||||
const canAssign = can('weibo:assign');
|
||||
const canTransferCollab = can('weibo:workbench');
|
||||
|
||||
const [items, setItems] = useState<WeiboAccountItem[]>([]);
|
||||
const [total, setTotal] = useState(0);
|
||||
@@ -184,6 +188,7 @@ export default function WeiboAccountsPage({
|
||||
const [mutatingId, setMutatingId] = useState<number | null>(null);
|
||||
const [selectedIds, setSelectedIds] = useState<number[]>([]);
|
||||
const [batchBusy, setBatchBusy] = useState(false);
|
||||
const [transferringId, setTransferringId] = useState<number | null>(null);
|
||||
const [batchTagOpen, setBatchTagOpen] = useState(false);
|
||||
const [batchTagValue, setBatchTagValue] = useState('');
|
||||
const [batchTagging, setBatchTagging] = useState(false);
|
||||
@@ -463,6 +468,28 @@ export default function WeiboAccountsPage({
|
||||
}
|
||||
};
|
||||
|
||||
const transferToCollab = async () => {
|
||||
if (!isWorkbench || selectedIds.length !== 1) {
|
||||
message.warning('请选择一个微博工作台账号');
|
||||
return;
|
||||
}
|
||||
const accountId = selectedIds[0];
|
||||
setTransferringId(accountId);
|
||||
try {
|
||||
const result = await weiboApi.transferWorkbenchToCollab(accountId);
|
||||
message.success(
|
||||
`${result.nickname || result.uid} 已移出工作台,正在打开外部单协作`,
|
||||
);
|
||||
navigate('/weibo/collab', {
|
||||
state: { transferAccountId: result.account_id },
|
||||
});
|
||||
} catch (error) {
|
||||
message.error(getErrorMessage(error));
|
||||
} finally {
|
||||
setTransferringId(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleBatchTag = async () => {
|
||||
if (selectedIds.length === 0) return;
|
||||
setBatchTagging(true);
|
||||
@@ -1735,6 +1762,16 @@ export default function WeiboAccountsPage({
|
||||
批量充值
|
||||
</Button>
|
||||
)}
|
||||
{isWorkbench && canTransferCollab && selectedIds.length === 1 && (
|
||||
<Button
|
||||
size="small"
|
||||
icon={<SwapOutlined />}
|
||||
loading={transferringId === selectedIds[0]}
|
||||
onClick={() => void transferToCollab()}
|
||||
>
|
||||
转入外部单协作
|
||||
</Button>
|
||||
)}
|
||||
{!isWorkbench && canManage && (
|
||||
<Button
|
||||
size="small"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
Alert,
|
||||
AutoComplete,
|
||||
@@ -128,6 +129,8 @@ interface OrderFormValues {
|
||||
export default function WeiboCollabPage() {
|
||||
const { token } = theme.useToken();
|
||||
const { can } = usePermissions();
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const canEdit = can('weibo:collab:edit');
|
||||
const canManage = can('weibo:collab:manage');
|
||||
|
||||
@@ -163,6 +166,7 @@ export default function WeiboCollabPage() {
|
||||
const [detailMetrics, setDetailMetrics] = useState<WeiboCollabMetric[]>([]);
|
||||
const [detailTimeline, setDetailTimeline] = useState<WeiboCollabTimeline[]>([]);
|
||||
const [detailLoading, setDetailLoading] = useState(false);
|
||||
const transferHandledRef = useRef<number | null>(null);
|
||||
|
||||
const [statusOpen, setStatusOpen] = useState(false);
|
||||
const [statusTarget, setStatusTarget] = useState<WeiboCollabStatus>('to_recharge');
|
||||
@@ -297,7 +301,7 @@ export default function WeiboCollabPage() {
|
||||
if (!goodsOptions.some((item) => item.value === option.value)) goodsOptions.push(option);
|
||||
}
|
||||
|
||||
const lookupAccount = async (accountId?: number | null) => {
|
||||
const lookupAccount = useCallback(async (accountId?: number | null) => {
|
||||
setLookup(null);
|
||||
if (!accountId) return;
|
||||
setLookupLoading(true);
|
||||
@@ -310,7 +314,7 @@ export default function WeiboCollabPage() {
|
||||
} finally {
|
||||
setLookupLoading(false);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const openCreate = () => {
|
||||
setEditing(null);
|
||||
@@ -325,6 +329,37 @@ export default function WeiboCollabPage() {
|
||||
setEditOpen(true);
|
||||
};
|
||||
|
||||
const transferAccountId = (
|
||||
location.state as { transferAccountId?: number } | null
|
||||
)?.transferAccountId;
|
||||
|
||||
useEffect(() => {
|
||||
if (!transferAccountId || transferHandledRef.current === transferAccountId) {
|
||||
return;
|
||||
}
|
||||
transferHandledRef.current = transferAccountId;
|
||||
setEditing(null);
|
||||
setLookup(null);
|
||||
form.resetFields();
|
||||
form.setFieldsValue({
|
||||
owner_id: owners[0]?.id,
|
||||
account_id: transferAccountId,
|
||||
required_weidou: 0,
|
||||
home_images: [],
|
||||
draft: false,
|
||||
});
|
||||
setEditOpen(true);
|
||||
void lookupAccount(transferAccountId);
|
||||
navigate(location.pathname, { replace: true, state: null });
|
||||
}, [
|
||||
form,
|
||||
location.pathname,
|
||||
lookupAccount,
|
||||
navigate,
|
||||
owners,
|
||||
transferAccountId,
|
||||
]);
|
||||
|
||||
const openEdit = (order: WeiboCollabOrder) => {
|
||||
setEditing(order);
|
||||
form.setFieldsValue({
|
||||
|
||||
Reference in New Issue
Block a user