diff --git a/tests/test_douyu_gold_recharge_channel.py b/tests/test_douyu_gold_recharge_channel.py index adf5985..e13b9be 100644 --- a/tests/test_douyu_gold_recharge_channel.py +++ b/tests/test_douyu_gold_recharge_channel.py @@ -13,8 +13,9 @@ from sqlalchemy.orm import sessionmaker from web.backend.database import Base from web.backend.models import Account, AuditLog, DouyuTask, User -from web.backend.routers.douyu import supplier_recharge_callback +from web.backend.routers.douyu import get_recharge_channel, supplier_recharge_callback from web.backend.services.douyu_runner import DouyuBatchRunner +from web.backend.services.douyu_service import ensure_douyu_config from web.backend.services.audit_service import record_audit from core.douyu import FishFinRechargeClient, FishFinRechargeConfig @@ -124,6 +125,18 @@ class DouyuGoldRechargeChannelTests(unittest.TestCase): "DYGFHISTORICAL-001", ) + def test_recharge_channel_can_be_read_without_full_config(self): + config = ensure_douyu_config(self.session) + config.gold_recharge_channel = "supplier_api" + self.session.commit() + + channel = get_recharge_channel( + db=self.session, + current=self.session.get(User, self.task.created_by), + ) + + self.assertEqual(channel, {"gold_recharge_channel": "supplier_api"}) + def test_recharge_audit_detail_includes_safe_account_identity(self): record_audit( self.session, diff --git a/web/backend/routers/douyu.py b/web/backend/routers/douyu.py index 0b5feed..8cd7e9c 100644 --- a/web/backend/routers/douyu.py +++ b/web/backend/routers/douyu.py @@ -406,6 +406,20 @@ def get_config( return _config_out(ensure_douyu_config(db)) +@router.get("/recharge-channel") +def get_recharge_channel( + db: Session = Depends(get_db), + current: User = Depends(require_permission("douyu:task")), +): + """返回当前鱼翅充值渠道,不暴露供应商商品等配置细节。""" + config = ensure_douyu_config(db) + return { + "gold_recharge_channel": douyu_config_value( + "gold_recharge_channel", config.gold_recharge_channel, + ), + } + + @router.put("/config", response_model=DouyuConfigOut) def update_config( req: DouyuConfigUpdate, diff --git a/web/frontend/src/api/douyu.ts b/web/frontend/src/api/douyu.ts index 5809d6e..33617d3 100644 --- a/web/frontend/src/api/douyu.ts +++ b/web/frontend/src/api/douyu.ts @@ -2,6 +2,7 @@ import api from './client'; import type { DouyuConfig, DouyuGoodsItem, + DouyuRechargeChannel, DouyuTaskAccountItem, DouyuTaskBatchRequest, DouyuTaskBatchResult, @@ -21,6 +22,7 @@ export const douyuApi = { listAccountIds: (params?: { search?: string; tag?: string }) => api.get<{ account_ids: number[]; total: number }, { account_ids: number[]; total: number }>('/douyu/accounts/ids', { params }), getConfig: () => api.get('/douyu/config'), + getRechargeChannel: () => api.get('/douyu/recharge-channel'), updateConfig: (data: Partial) => api.put('/douyu/config', data), listGoods: () => api.get('/douyu/goods'), listEsportsGoods: () => api.get('/douyu/esports-goods'), diff --git a/web/frontend/src/api/types.ts b/web/frontend/src/api/types.ts index 651ce5d..2e7a2cd 100644 --- a/web/frontend/src/api/types.ts +++ b/web/frontend/src/api/types.ts @@ -382,6 +382,10 @@ export interface DouyuConfig { updated_at: string | null; } +export interface DouyuRechargeChannel { + gold_recharge_channel: 'wechat_qr' | 'supplier_api'; +} + export interface DouyuTaskBatchRequest { account_ids: number[]; task_type: string; diff --git a/web/frontend/src/pages/DouyuTasksPage.tsx b/web/frontend/src/pages/DouyuTasksPage.tsx index 677d80a..4ea794a 100644 --- a/web/frontend/src/pages/DouyuTasksPage.tsx +++ b/web/frontend/src/pages/DouyuTasksPage.tsx @@ -366,6 +366,7 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind }) const [tasks, setTasks] = useState([]); const [taskTypes, setTaskTypes] = useState>({}); const [config, setConfig] = useState(null); + const [goldRechargeChannel, setGoldRechargeChannel] = useState(null); const [loading, setLoading] = useState(false); const [runningBatchIds, setRunningBatchIds] = useState>(new Set()); const [configOpen, setConfigOpen] = useState(false); @@ -580,7 +581,7 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind }) const loadData = useCallback(async () => { setLoading(true); try { - const [accResult, goodsResult, taskResult, cfgResult, typeResult] = await Promise.allSettled([ + const [accResult, goodsResult, taskResult, cfgResult, typeResult, rechargeChannelResult] = await Promise.allSettled([ workbenchIds.length > 0 ? douyuApi.listAccountsPaged({ ids: workbenchIds.join(','), @@ -595,6 +596,7 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind }) douyuApi.listTasks(handbook), canConfig ? douyuApi.getConfig() : Promise.resolve(null), douyuApi.taskTypes(), + douyuApi.getRechargeChannel(), ]); if (accResult.status === 'fulfilled') { setAccounts(accResult.value.items); @@ -627,6 +629,9 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind }) } if (cfgResult.status === 'fulfilled' && cfgResult.value) setConfig(cfgResult.value); if (typeResult.status === 'fulfilled') setTaskTypes(typeResult.value); + if (rechargeChannelResult.status === 'fulfilled') { + setGoldRechargeChannel(rechargeChannelResult.value.gold_recharge_channel); + } } finally { setLoading(false); } @@ -1031,6 +1036,7 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind }) try { const saved = await douyuApi.updateConfig(values); setConfig(saved); + setGoldRechargeChannel(saved.gold_recharge_channel); setConfigOpen(false); message.success('配置已保存'); } catch (error) { @@ -1627,9 +1633,14 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind }) const payUrl = resultText(payTask?.result, 'pay_url'); const goldAmountLabel = '充值金额'; - const isSupplierGoldRecharge = config?.gold_recharge_channel === 'supplier_api'; - const goldRechargeChannelLabel = isSupplierGoldRecharge ? '供应商 API 直充' : '微信扫码'; - const goldRechargeButtonLabel = isSupplierGoldRecharge ? 'API直充鱼翅' : '扫码充值鱼翅'; + const isRechargeChannelLoading = goldRechargeChannel === null; + const isSupplierGoldRecharge = goldRechargeChannel === 'supplier_api'; + const goldRechargeChannelLabel = isRechargeChannelLoading + ? '加载中' + : (isSupplierGoldRecharge ? '供应商 API 直充' : '微信扫码'); + const goldRechargeButtonLabel = isRechargeChannelLoading + ? '加载中' + : (isSupplierGoldRecharge ? 'API直充鱼翅' : '扫码充值鱼翅'); const payAccountName = payTask ? (payTask.account_nickname || payTask.account_username || payTask.account_uid || `#${payTask.account_id}`) : ''; @@ -1803,7 +1814,7 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind }) size="small" icon={} onClick={() => startTask('create_gold_qr')} - disabled={actionDisabled('create_gold_qr')} + disabled={isRechargeChannelLoading || actionDisabled('create_gold_qr')} > {goldRechargeButtonLabel} @@ -1869,7 +1880,7 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind }) size="small" icon={} onClick={() => startTask('create_gold_qr')} - disabled={actionDisabled('create_gold_qr')} + disabled={isRechargeChannelLoading || actionDisabled('create_gold_qr')} > {goldRechargeButtonLabel}