完善虎牙外部单展示、状态恢复与礼包配置
This commit is contained in:
@@ -1187,6 +1187,16 @@ export interface HuyaConfig {
|
||||
bind_gid: string;
|
||||
bind_act_id: string;
|
||||
pay_channel: string;
|
||||
/** 积分中心 queryAllocation 页面码 */
|
||||
points_page_code: string;
|
||||
/** 积分中心 sourceId */
|
||||
points_source_id: string;
|
||||
/** 积分中心 scene,默认 7 */
|
||||
points_scene: string;
|
||||
/** 可选覆盖:页签 pageId;留空自动从响应发现 */
|
||||
points_page_id: string;
|
||||
/** 可选覆盖:页签 ID,逗号分隔;留空自动拉全部页签 */
|
||||
points_tab_ids: string;
|
||||
updated_at: string | null;
|
||||
}
|
||||
|
||||
@@ -1471,7 +1481,7 @@ export interface HuyaCollabOrder {
|
||||
uid: string;
|
||||
yyuid: string;
|
||||
hy_username: string;
|
||||
/** 虎牙号昵称(探针给不出,暂时为空) */
|
||||
/** 虎牙平台账号昵称(绑定探针通过后由用户资料接口补读) */
|
||||
nickname: string;
|
||||
has_cookie: boolean;
|
||||
has_raw_cookie: boolean;
|
||||
@@ -1662,6 +1672,8 @@ export interface HuyaCollabStatusBatchRequest {
|
||||
reason?: string;
|
||||
/** 目标不是「向前」时必须显式 true */
|
||||
rollback?: boolean;
|
||||
/** 仅超级管理员:批次里的 void 行强制恢复,必须填写 reason */
|
||||
force?: boolean;
|
||||
}
|
||||
|
||||
/** 批量改状态里的一行结论 */
|
||||
@@ -1861,6 +1873,8 @@ export interface HuyaCollabStatusRequest {
|
||||
status: HuyaCollabStatus;
|
||||
reason?: string;
|
||||
rollback?: boolean;
|
||||
/** 仅超级管理员:从「已作废」强制恢复,必须填写 reason */
|
||||
force?: boolean;
|
||||
expected_version?: number;
|
||||
}
|
||||
|
||||
@@ -1954,6 +1968,8 @@ export interface HuyaCollabCkPreview {
|
||||
uid: string;
|
||||
yyuid: string;
|
||||
hy_username: string;
|
||||
/** 虎牙平台账号昵称(CK 验证时补读;不是绑定游戏账号昵称) */
|
||||
nickname: string;
|
||||
ck_status: HuyaCollabCkStatus;
|
||||
bind_game_account: string;
|
||||
bind_game_role: string;
|
||||
|
||||
@@ -17,7 +17,7 @@ import type { TableProps } from 'antd';
|
||||
import {
|
||||
AuditOutlined, CheckCircleOutlined, CloudUploadOutlined, CopyOutlined, DeleteOutlined, EditOutlined,
|
||||
FileAddOutlined, FileExcelOutlined, GiftOutlined, InboxOutlined, LinkOutlined, PlusOutlined, ReloadOutlined,
|
||||
SafetyCertificateOutlined, ShopOutlined, StopOutlined, SwapOutlined, SyncOutlined, UndoOutlined,
|
||||
SafetyCertificateOutlined, SettingOutlined, ShopOutlined, StopOutlined, SwapOutlined, SyncOutlined, UndoOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import {
|
||||
fileApi,
|
||||
@@ -38,6 +38,7 @@ import {
|
||||
type HuyaCollabShop,
|
||||
type HuyaCollabStatus,
|
||||
type HuyaCollabSummary,
|
||||
type HuyaConfig,
|
||||
} from '../api/modules';
|
||||
import HuyaCollabShopModal from '../components/HuyaCollabShopModal';
|
||||
import HuyaCollabShopPicker from '../components/HuyaCollabShopPicker';
|
||||
@@ -165,8 +166,8 @@ const SLOT_META: Record<HuyaCollabImageSlot, { label: string; keysKey: 'bind_ima
|
||||
|
||||
const IMPORT_PREVIEW_ROWS = 20;
|
||||
// 「礼包列表」的本地缓存键:一个活动期间就那一批礼包,没必要每贴一份 CK 都去虎牙拉一次;
|
||||
// v2 = 2026-10-19 切到积分中心 queryAllocation 后换键,避免继续用旧 getActPrizeList 的缓存。
|
||||
const GIFTS_CACHE_KEY = 'huya.collab.gifts.v2';
|
||||
// v3 = 2026-11-11 起自动合并积分中心全部页签,换键避免继续用只含「精英专享」的旧缓存。
|
||||
const GIFTS_CACHE_KEY = 'huya.collab.gifts.v3';
|
||||
const RECHARGE_THRESHOLD_KEY = 'huya.collab.rechargeThreshold';
|
||||
const RECHARGE_THRESHOLD_DEFAULT = 100;
|
||||
|
||||
@@ -504,6 +505,7 @@ export default function HuyaCollabPage() {
|
||||
const navigate = useNavigate();
|
||||
const canEdit = can('huya:collab:edit');
|
||||
const canManage = can('huya:collab:manage');
|
||||
const isAdmin = user?.role === 'super_admin';
|
||||
// 单行复制与批量导出共用这个权限点(默认运营和客服都有)
|
||||
const canExportCk = can('huya:collab:ck_export');
|
||||
|
||||
@@ -522,6 +524,10 @@ export default function HuyaCollabPage() {
|
||||
const [frequentGifts, setFrequentGifts] = useState<HuyaCollabFrequentGift[]>([]);
|
||||
const [shopModalOpen, setShopModalOpen] = useState(false);
|
||||
const [giftModalOpen, setGiftModalOpen] = useState(false);
|
||||
const [pointsConfigOpen, setPointsConfigOpen] = useState(false);
|
||||
const [pointsConfigLoading, setPointsConfigLoading] = useState(false);
|
||||
const [pointsConfigSaving, setPointsConfigSaving] = useState(false);
|
||||
const [pointsConfigForm] = Form.useForm<HuyaConfig>();
|
||||
const [filters, setFilters] = useState<HuyaCollabOrderQuery>({
|
||||
include_void: false,
|
||||
archive_scope: 'active',
|
||||
@@ -532,6 +538,8 @@ export default function HuyaCollabPage() {
|
||||
const [realtimePending, setRealtimePending] = useState(false);
|
||||
const [realtimePaused, setRealtimePaused] = useState(readRealtimePaused);
|
||||
const [selectedIds, setSelectedIds] = useState<number[]>([]);
|
||||
const selectedHasVoid = selectedIds.length > 0
|
||||
&& rows.some((row) => selectedIds.includes(row.id) && row.status === 'void');
|
||||
// 点顶部统计卡筛选后滚到列表,避免用户还要自己往下找
|
||||
const listAnchorRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
@@ -581,7 +589,7 @@ export default function HuyaCollabPage() {
|
||||
const [ckTarget, setCkTarget] = useState<{ order: HuyaCollabOrder; text: string } | null>(null);
|
||||
// 单行状态弹窗:set=人工往前切(可跳步)/ rollback=回退 / void=作废
|
||||
const [statusAction, setStatusAction] = useState<{
|
||||
order: HuyaCollabOrder; mode: 'void' | 'rollback' | 'set'; target?: HuyaCollabStatus;
|
||||
order: HuyaCollabOrder; mode: 'void' | 'rollback' | 'set' | 'force'; target?: HuyaCollabStatus;
|
||||
} | null>(null);
|
||||
const [statusReason, setStatusReason] = useState('');
|
||||
const [rollbackTarget, setRollbackTarget] = useState<HuyaCollabStatus>('to_recharge');
|
||||
@@ -590,6 +598,7 @@ export default function HuyaCollabPage() {
|
||||
const [batchStatusTarget, setBatchStatusTarget] = useState<HuyaCollabStatus>('to_recharge');
|
||||
const [batchStatusReason, setBatchStatusReason] = useState('');
|
||||
const [batchStatusRollback, setBatchStatusRollback] = useState(false);
|
||||
const [batchStatusForce, setBatchStatusForce] = useState(false);
|
||||
// 充值核账弹窗:按「绑定账号 + 绑定角色」整组核账,阈值默认 100
|
||||
const [rechargeOpen, setRechargeOpen] = useState(false);
|
||||
const [rechargeIds, setRechargeIds] = useState<number[]>([]);
|
||||
@@ -661,11 +670,43 @@ export default function HuyaCollabPage() {
|
||||
|
||||
useEffect(() => { void load(); }, [load]);
|
||||
|
||||
const openPointsConfig = async () => {
|
||||
setPointsConfigOpen(true);
|
||||
setPointsConfigLoading(true);
|
||||
try {
|
||||
pointsConfigForm.setFieldsValue(await huyaApi.getConfig());
|
||||
} catch (e: unknown) {
|
||||
message.error(getErrorMessage(e));
|
||||
} finally {
|
||||
setPointsConfigLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const savePointsConfig = async () => {
|
||||
setPointsConfigSaving(true);
|
||||
try {
|
||||
const values = await pointsConfigForm.validateFields();
|
||||
await huyaApi.updateConfig({
|
||||
points_page_code: values.points_page_code,
|
||||
points_source_id: values.points_source_id,
|
||||
points_scene: values.points_scene,
|
||||
points_page_id: values.points_page_id,
|
||||
points_tab_ids: values.points_tab_ids,
|
||||
});
|
||||
message.success('积分中心配置已保存,下次刷新礼包时生效');
|
||||
setPointsConfigOpen(false);
|
||||
} catch (e: unknown) {
|
||||
message.error(getErrorMessage(e));
|
||||
} finally {
|
||||
setPointsConfigSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
const realtimeEnabled = can('huya:collab:view');
|
||||
// 暂停后连 WebSocket 也不建:既不自动重拉,也不做断线兜底轮询。
|
||||
const realtimeSyncEnabled = realtimeEnabled && !realtimePaused;
|
||||
const collabModalOpen = Boolean(
|
||||
createOpen || supplementTarget || detail || statusAction || batchStatusOpen || rechargeOpen || archiveOpen || ckTarget || ckExportOpen || shopModalOpen || giftModalOpen,
|
||||
createOpen || supplementTarget || detail || statusAction || batchStatusOpen || rechargeOpen || archiveOpen || ckTarget || ckExportOpen || shopModalOpen || giftModalOpen || pointsConfigOpen,
|
||||
);
|
||||
|
||||
const toggleRealtimeSync = (nextEnabled: boolean) => {
|
||||
@@ -746,7 +787,7 @@ export default function HuyaCollabPage() {
|
||||
|
||||
const emptyPreview = useCallback(
|
||||
(message: string): HuyaCollabCkPreview => ({
|
||||
ok: false, message, uid: '', yyuid: '', hy_username: '', ck_status: 'invalid',
|
||||
ok: false, message, uid: '', yyuid: '', hy_username: '', nickname: '', ck_status: 'invalid',
|
||||
bind_game_account: '', bind_game_role: '', bind_game_channel: '',
|
||||
has_role: false, gifts: [], gifts_message: '', uid_used_by: [],
|
||||
}),
|
||||
@@ -768,7 +809,9 @@ export default function HuyaCollabPage() {
|
||||
writeGiftCache(preview.gifts);
|
||||
}
|
||||
if (preview.ok && preview.has_role) {
|
||||
message.success(`CK 有效:绑定角色 ${preview.bind_game_role}`);
|
||||
message.success(
|
||||
`CK 有效:${preview.nickname || '虎牙昵称未读到'} · 角色 ${preview.bind_game_role}`,
|
||||
);
|
||||
// 「记住的礼包」如果不在实时列表、也不在标准字典里就清掉重选。
|
||||
const options = preview.gifts.length > 0 ? preview.gifts : giftsRef.current;
|
||||
const available = new Set([
|
||||
@@ -962,13 +1005,18 @@ export default function HuyaCollabPage() {
|
||||
openRecharge(selectedIds);
|
||||
return;
|
||||
}
|
||||
if (batchStatusForce && !batchStatusReason.trim()) {
|
||||
message.warning('强制恢复已作废订单必须填写原因');
|
||||
return;
|
||||
}
|
||||
setBusy(true);
|
||||
try {
|
||||
const result = await huyaApi.batchUpdateCollabOrderStatus({
|
||||
ids: selectedIds,
|
||||
status: batchStatusTarget,
|
||||
reason: batchStatusReason,
|
||||
rollback: batchStatusRollback,
|
||||
rollback: batchStatusForce ? false : batchStatusRollback,
|
||||
force: batchStatusForce,
|
||||
});
|
||||
const failures = result.results.filter((item) => !item.ok);
|
||||
if (failures.length === 0) {
|
||||
@@ -1320,6 +1368,7 @@ export default function HuyaCollabPage() {
|
||||
* - `void`:作废(必须填原因);
|
||||
* - `rollback`:回退到选定状态(`rollback=true`,会清掉后面阶段的时间戳);
|
||||
* - `set`:人工往前切,允许跳步(如 待补资料 → 已上脚本),不用勾回退。
|
||||
* - `force`:超级管理员从「已作废」强制恢复(必须填原因)。
|
||||
*/
|
||||
const submitStatusAction = async () => {
|
||||
if (!statusAction) return;
|
||||
@@ -1336,17 +1385,24 @@ export default function HuyaCollabPage() {
|
||||
message.warning('作废必须填原因');
|
||||
return;
|
||||
}
|
||||
if (mode === 'force' && !statusReason.trim()) {
|
||||
message.warning('强制恢复已作废订单必须填写原因');
|
||||
return;
|
||||
}
|
||||
setBusy(true);
|
||||
try {
|
||||
applyUpdated(await huyaApi.updateCollabOrderStatus(order.id, {
|
||||
status: nextStatus,
|
||||
reason: statusReason,
|
||||
rollback: mode === 'rollback',
|
||||
force: mode === 'force',
|
||||
expected_version: order.version,
|
||||
}));
|
||||
message.success(
|
||||
mode === 'void'
|
||||
? '已作废'
|
||||
: mode === 'force'
|
||||
? '已强制恢复'
|
||||
: mode === 'rollback'
|
||||
? '已回退'
|
||||
: `已改为「${STATUS_META[nextStatus].label}」`,
|
||||
@@ -2043,14 +2099,29 @@ export default function HuyaCollabPage() {
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '绑定账号 / 角色', width: 210,
|
||||
title: '游戏账号 / 角色 / 虎牙昵称', width: 280,
|
||||
...sortProps('bind'),
|
||||
render: (_, row) => (
|
||||
<Space direction="vertical" size={0}>
|
||||
<Text strong>{row.bind_game_account || <Text type="secondary">未读到</Text>}</Text>
|
||||
{row.bind_game_role
|
||||
? <Tag color="green" style={{ marginTop: 2 }}>角色 {row.bind_game_role}</Tag>
|
||||
: <Text type="secondary" style={{ fontSize: 12 }}>未读到绑定角色</Text>}
|
||||
<Space direction="vertical" size={1} style={{ width: '100%' }}>
|
||||
<Space size={4} wrap={false} style={{ maxWidth: '100%' }}>
|
||||
<Text strong ellipsis style={{ maxWidth: 112 }}>
|
||||
{row.bind_game_account || '游戏账号未读到'}
|
||||
</Text>
|
||||
<Text type="secondary">/</Text>
|
||||
<Text
|
||||
type={row.bind_game_role ? undefined : 'secondary'}
|
||||
ellipsis
|
||||
style={{
|
||||
color: row.bind_game_role ? '#389e0d' : undefined,
|
||||
maxWidth: 140,
|
||||
}}
|
||||
>
|
||||
{row.bind_game_role ? `角色 ${row.bind_game_role}` : '角色未读到'}
|
||||
</Text>
|
||||
</Space>
|
||||
<Text type="secondary" style={{ fontSize: 12 }} ellipsis>
|
||||
虎牙昵称:{row.nickname || '未读取(重新验证 CK 后补全)'}
|
||||
</Text>
|
||||
{row.bind_game_channel && (
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>{row.bind_game_channel}</Text>
|
||||
)}
|
||||
@@ -2293,9 +2364,15 @@ export default function HuyaCollabPage() {
|
||||
{ key: 'rebuild', label: '从 App 原始凭据重建', icon: <ReloadOutlined />, disabled: !row.has_raw_cookie },
|
||||
{ key: 'disable', label: row.ck_status === 'disabled' ? '恢复这份 CK' : '停用这份 CK', icon: <StopOutlined /> },
|
||||
{ type: 'divider' },
|
||||
{ key: 'set', label: '改状态…', icon: <CheckCircleOutlined /> },
|
||||
{ key: 'rollback', label: '回退状态…', icon: <UndoOutlined /> },
|
||||
{ key: 'void', label: '作废…', icon: <StopOutlined />, danger: true },
|
||||
...(row.status === 'void'
|
||||
? (isAdmin
|
||||
? [{ key: 'force', label: '管理员强制恢复…', icon: <UndoOutlined />, danger: true }]
|
||||
: [])
|
||||
: [
|
||||
{ key: 'set', label: '改状态…', icon: <CheckCircleOutlined /> },
|
||||
{ key: 'rollback', label: '回退状态…', icon: <UndoOutlined /> },
|
||||
{ key: 'void', label: '作废…', icon: <StopOutlined />, danger: true },
|
||||
]),
|
||||
...(row.status === 'done'
|
||||
? [
|
||||
{ type: 'divider' as const },
|
||||
@@ -2323,6 +2400,11 @@ export default function HuyaCollabPage() {
|
||||
setStatusReason('');
|
||||
setStatusAction({ order: row, mode: 'rollback' });
|
||||
}
|
||||
if (key === 'force') {
|
||||
setRollbackTarget('to_recharge');
|
||||
setStatusReason('');
|
||||
setStatusAction({ order: row, mode: 'force' });
|
||||
}
|
||||
if (key === 'void') {
|
||||
setStatusReason('');
|
||||
setStatusAction({ order: row, mode: 'void' });
|
||||
@@ -2463,7 +2545,7 @@ export default function HuyaCollabPage() {
|
||||
value={filters.gift_name || undefined}
|
||||
onChange={(value?: string) => { setPage(1); setFilters((prev) => ({ ...prev, gift_name: value })); }} />
|
||||
<Input.Search allowClear style={{ width: 340 }}
|
||||
placeholder="搜订单号 / 店铺 / 绑定账号 / 角色 / 虎牙号 / 礼包 / 备注"
|
||||
placeholder="搜订单号 / 店铺 / 游戏账号 / 角色 / 虎牙昵称 / 礼包 / 备注"
|
||||
onSearch={(value) => { setPage(1); setFilters((prev) => ({ ...prev, keyword: value })); }} />
|
||||
<Space size={4}>
|
||||
<Text type="secondary">显示已作废</Text>
|
||||
@@ -2526,6 +2608,7 @@ export default function HuyaCollabPage() {
|
||||
setBatchStatusTarget(key as HuyaCollabStatus);
|
||||
setBatchStatusReason('');
|
||||
setBatchStatusRollback(false);
|
||||
setBatchStatusForce(false);
|
||||
setBatchStatusOpen(true);
|
||||
},
|
||||
}}
|
||||
@@ -2579,6 +2662,11 @@ export default function HuyaCollabPage() {
|
||||
{canManage && !isArchiveView && (
|
||||
<Button icon={<GiftOutlined />} onClick={() => setGiftModalOpen(true)}>礼包管理</Button>
|
||||
)}
|
||||
{canManage && !isArchiveView && (
|
||||
<Button icon={<SettingOutlined />} onClick={() => void openPointsConfig()}>
|
||||
积分中心配置
|
||||
</Button>
|
||||
)}
|
||||
{canEdit && !isArchiveView && (
|
||||
<Button
|
||||
type="primary"
|
||||
@@ -2723,7 +2811,7 @@ export default function HuyaCollabPage() {
|
||||
<Descriptions size="small" column={2} bordered
|
||||
items={[
|
||||
{ key: 'uid', label: '虎牙号', children: detail.uid || '-' },
|
||||
{ key: 'username', label: '登录名 / 昵称', children: `${detail.hy_username || '-'} / ${detail.nickname || '-'}` },
|
||||
{ key: 'username', label: '登录名 / 虎牙昵称', children: `${detail.hy_username || '-'} / ${detail.nickname || '-'}` },
|
||||
{ key: 'ck', label: 'CK 状态',
|
||||
children: (
|
||||
<Space>
|
||||
@@ -2906,30 +2994,43 @@ export default function HuyaCollabPage() {
|
||||
|
||||
{canEdit && !detail.is_archived && (
|
||||
<Space wrap>
|
||||
{detail.next_status && (
|
||||
<Button icon={<CheckCircleOutlined />} disabled={busy}
|
||||
onClick={() => void advance(detail)}>
|
||||
标记为「{STATUS_META[detail.next_status].label}」
|
||||
</Button>
|
||||
)}
|
||||
<Button icon={<SwapOutlined />} disabled={busy}
|
||||
onClick={() => {
|
||||
// 默认给「下一步」,终点态(抢购成功 / 已作废 / 抢购失败)退回当前状态,让人自己挑
|
||||
setRollbackTarget(detail.next_status ?? detail.status);
|
||||
setStatusReason('');
|
||||
setStatusAction({ order: detail, mode: 'set' });
|
||||
}}>
|
||||
改状态…
|
||||
</Button>
|
||||
<Button icon={<UndoOutlined />} disabled={busy}
|
||||
onClick={() => { setRollbackTarget('to_recharge'); setStatusReason(''); setStatusAction({ order: detail, mode: 'rollback' }); }}>
|
||||
回退状态
|
||||
</Button>
|
||||
{detail.status !== 'void' && (
|
||||
<Button danger icon={<StopOutlined />} disabled={busy}
|
||||
onClick={() => { setStatusReason(''); setStatusAction({ order: detail, mode: 'void' }); }}>
|
||||
作废
|
||||
</Button>
|
||||
{detail.status === 'void' ? (
|
||||
isAdmin && (
|
||||
<Button danger icon={<UndoOutlined />} disabled={busy}
|
||||
onClick={() => {
|
||||
setRollbackTarget('to_recharge');
|
||||
setStatusReason('');
|
||||
setStatusAction({ order: detail, mode: 'force' });
|
||||
}}>
|
||||
管理员强制恢复…
|
||||
</Button>
|
||||
)
|
||||
) : (
|
||||
<>
|
||||
{detail.next_status && (
|
||||
<Button icon={<CheckCircleOutlined />} disabled={busy}
|
||||
onClick={() => void advance(detail)}>
|
||||
标记为「{STATUS_META[detail.next_status].label}」
|
||||
</Button>
|
||||
)}
|
||||
<Button icon={<SwapOutlined />} disabled={busy}
|
||||
onClick={() => {
|
||||
// 默认给「下一步」,终点态退回当前状态,让人自己挑
|
||||
setRollbackTarget(detail.next_status ?? detail.status);
|
||||
setStatusReason('');
|
||||
setStatusAction({ order: detail, mode: 'set' });
|
||||
}}>
|
||||
改状态…
|
||||
</Button>
|
||||
<Button icon={<UndoOutlined />} disabled={busy}
|
||||
onClick={() => { setRollbackTarget('to_recharge'); setStatusReason(''); setStatusAction({ order: detail, mode: 'rollback' }); }}>
|
||||
回退状态
|
||||
</Button>
|
||||
<Button danger icon={<StopOutlined />} disabled={busy}
|
||||
onClick={() => { setStatusReason(''); setStatusAction({ order: detail, mode: 'void' }); }}>
|
||||
作废
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</Space>
|
||||
)}
|
||||
@@ -3126,13 +3227,14 @@ export default function HuyaCollabPage() {
|
||||
<Alert
|
||||
type="success"
|
||||
showIcon
|
||||
message={`CK 有效 · 绑定角色 ${ckPreview.bind_game_role}`}
|
||||
message={`CK 有效 · ${ckPreview.nickname || '虎牙昵称未读到'} · 角色 ${ckPreview.bind_game_role}`}
|
||||
description={
|
||||
<>
|
||||
<div>
|
||||
{`订单编号里的账号:${ckPreview.bind_game_account || '-'}`
|
||||
{`游戏账号:${ckPreview.bind_game_account || '-'}`
|
||||
+ `(${ckPreview.bind_game_channel || '-'})`
|
||||
+ `;虎牙号 ${ckPreview.uid}`
|
||||
+ `;虎牙昵称:${ckPreview.nickname || '-'}`
|
||||
+ `;虎牙号:${ckPreview.uid}`
|
||||
+ (giftSourceText ? `;${giftSourceText}` : '')}
|
||||
</div>
|
||||
{ckPreview.uid_used_by.length > 0 && (
|
||||
@@ -3482,18 +3584,32 @@ export default function HuyaCollabPage() {
|
||||
<Modal
|
||||
title={statusAction?.mode === 'void'
|
||||
? `作废订单 ${statusAction.order.order_no}`
|
||||
: statusAction?.mode === 'set'
|
||||
? `改状态:订单 ${statusAction.order.order_no}`
|
||||
: `回退订单 ${statusAction?.order.order_no ?? ''}`}
|
||||
: statusAction?.mode === 'force'
|
||||
? `管理员强制恢复订单 ${statusAction.order.order_no}`
|
||||
: statusAction?.mode === 'set'
|
||||
? `改状态:订单 ${statusAction.order.order_no}`
|
||||
: `回退订单 ${statusAction?.order.order_no ?? ''}`}
|
||||
open={!!statusAction}
|
||||
onCancel={() => setStatusAction(null)}
|
||||
onOk={() => void submitStatusAction()}
|
||||
confirmLoading={busy}
|
||||
okButtonProps={{ danger: statusAction?.mode === 'void' }}
|
||||
okText={statusAction?.mode === 'void' ? '作废' : statusAction?.mode === 'set' ? '改状态' : '回退'}
|
||||
okButtonProps={{ danger: statusAction?.mode === 'void' || statusAction?.mode === 'force' }}
|
||||
okText={statusAction?.mode === 'void'
|
||||
? '作废'
|
||||
: statusAction?.mode === 'force'
|
||||
? '强制恢复'
|
||||
: statusAction?.mode === 'set' ? '改状态' : '回退'}
|
||||
destroyOnHidden
|
||||
>
|
||||
<Space direction="vertical" size={8} style={{ width: '100%' }}>
|
||||
{statusAction?.mode === 'force' && (
|
||||
<Alert
|
||||
type="warning"
|
||||
showIcon
|
||||
message="仅在确认属于误作废时使用"
|
||||
description="强制恢复只跳过「已作废不可逆」限制;资料完整性、待充值条件等校验仍会执行。操作人和原因都会写入审计。"
|
||||
/>
|
||||
)}
|
||||
{statusAction && statusAction.mode !== 'void' && (
|
||||
<Select
|
||||
style={{ width: '100%' }}
|
||||
@@ -3506,6 +3622,8 @@ export default function HuyaCollabPage() {
|
||||
placeholder={
|
||||
statusAction?.mode === 'void'
|
||||
? '作废原因(必填)'
|
||||
: statusAction?.mode === 'force'
|
||||
? '强制恢复原因(必填)'
|
||||
: rollbackTarget === 'void'
|
||||
? '作废原因(必填)'
|
||||
: statusAction?.mode === 'rollback'
|
||||
@@ -3521,6 +3639,11 @@ export default function HuyaCollabPage() {
|
||||
{statusAction?.mode === 'rollback' && (
|
||||
<Text type="secondary">回退会清掉后面阶段的时间戳,并在审计里留痕。</Text>
|
||||
)}
|
||||
{statusAction?.mode === 'force' && (
|
||||
<Text type="secondary">
|
||||
恢复后原「作废原因」会保留在审计时间线里;该操作仅限超级管理员。
|
||||
</Text>
|
||||
)}
|
||||
</Space>
|
||||
</Modal>
|
||||
|
||||
@@ -3574,10 +3697,17 @@ export default function HuyaCollabPage() {
|
||||
/>
|
||||
<Input.TextArea rows={2} value={batchStatusReason}
|
||||
onChange={(event) => setBatchStatusReason(event.target.value)}
|
||||
placeholder={batchStatusTarget === 'void' ? '作废原因(必填)' : '改状态说明(可选)'} />
|
||||
placeholder={batchStatusTarget === 'void'
|
||||
? '作废原因(必填)'
|
||||
: batchStatusForce ? '强制恢复原因(必填)' : '改状态说明(可选)'} />
|
||||
<Checkbox checked={batchStatusRollback} onChange={(event) => setBatchStatusRollback(event.target.checked)}>
|
||||
回退(目标不是「往前」时必须勾选,比如 已上脚本 → 待充值)
|
||||
</Checkbox>
|
||||
{isAdmin && selectedHasVoid && (
|
||||
<Checkbox checked={batchStatusForce} onChange={(event) => setBatchStatusForce(event.target.checked)}>
|
||||
强制恢复所选批次中的「已作废」订单(仅超级管理员,必须填写原因)
|
||||
</Checkbox>
|
||||
)}
|
||||
<Text type="secondary">
|
||||
逐行独立提交:某一行前置校验没过(例如资料不全进不了「待充值」)只跳过那一行,
|
||||
结果会在弹窗里逐条列出来。一次最多 50 行。
|
||||
@@ -3637,6 +3767,55 @@ export default function HuyaCollabPage() {
|
||||
</Space>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
title="积分中心配置"
|
||||
open={pointsConfigOpen}
|
||||
onCancel={() => setPointsConfigOpen(false)}
|
||||
onOk={() => void savePointsConfig()}
|
||||
confirmLoading={pointsConfigSaving}
|
||||
okText="保存"
|
||||
width={680}
|
||||
destroyOnHidden
|
||||
>
|
||||
<Space direction="vertical" size={10} style={{ width: '100%' }}>
|
||||
<Alert
|
||||
type="info"
|
||||
showIcon
|
||||
message="页签默认自动发现,通常只需在活动入口变化时改前三个参数"
|
||||
description="保存后回到新建/补资料里点一次「强制刷新礼包」即可拉取最新商品;pageId 和页签 IDs 留空表示自动。"
|
||||
/>
|
||||
<Form form={pointsConfigForm} layout="vertical" disabled={pointsConfigLoading}>
|
||||
<Row gutter={12}>
|
||||
<Col span={12}>
|
||||
<Form.Item label="pageCode" name="points_page_code">
|
||||
<Input placeholder="heping_baodian_credit_center_page" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item label="sourceId" name="points_source_id">
|
||||
<Input placeholder="yellowcarlist" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Form.Item label="scene" name="points_scene">
|
||||
<Input placeholder="7" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Form.Item label="pageId(可选)" name="points_page_id">
|
||||
<Input placeholder="自动发现,如 343" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Form.Item label="页签 IDs(可选)" name="points_tab_ids">
|
||||
<Input placeholder="自动拉全部,如 431,433" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form>
|
||||
</Space>
|
||||
</Modal>
|
||||
|
||||
<HuyaCollabShopModal
|
||||
open={shopModalOpen}
|
||||
shops={shops}
|
||||
|
||||
@@ -627,7 +627,7 @@ export default function HuyaElitePage() {
|
||||
</div>}
|
||||
</Modal>
|
||||
<Modal open={importOpen} title="导入精英宝典账号" onCancel={() => setImportOpen(false)} onOk={() => { void saveAccounts([...new Set([...accounts.map((item) => item.id), ...importSelected])]).then(() => { setImportSelected([]); setImportOpen(false); }).catch((error) => message.error(getErrorMessage(error))); }} okText="导入" cancelText="取消"><Input.Search allowClear placeholder="搜索账号" value={importSearch} onChange={(event) => setImportSearch(event.target.value)} style={{ marginBottom: 8 }} /><Select allowClear placeholder="标签" value={importTag || undefined} onChange={(value) => setImportTag(value || '')} options={importTags.map((tag) => ({ value: tag, label: tag }))} style={{ width: '100%', marginBottom: 8 }} /><Table rowKey="id" size="small" dataSource={pool.filter((item) => !accounts.some((current) => current.id === item.id) && (!importSearch || [item.uid, item.nickname, item.username].some((value) => String(value || '').includes(importSearch))) && (!importTag || item.tag === importTag))} columns={[{ title: '账号', render: (_value, row) => row.nickname || row.username || row.uid }, { title: '标签', dataIndex: 'tag' }]} pagination={{ pageSize: 8 }} rowSelection={{ selectedRowKeys: importSelected, onChange: (keys) => setImportSelected(keys.map(Number)) }} /></Modal>
|
||||
<Modal open={configOpen} title="精英宝典设置" onCancel={() => setConfigOpen(false)} onOk={() => void saveConfig()} confirmLoading={configSaving} okText="保存" cancelText="取消" width={500}>{configLoading ? <div style={{ padding: 24, textAlign: 'center' }}>加载中...</div> : configDraft && <Space direction="vertical" size={10} style={{ width: '100%' }}>{configField('sid', '活动 SID')}{configField('bind_act_id', '绑定活动 ID')}{configField('outer_act_id', '外部活动 ID')}{configField('bind_gid', '绑定 gid')}{configField('room_pid', '直播间 PID')}{configField('pay_channel', '支付渠道')}<Space style={{ width: '100%', justifyContent: 'space-between' }}><Text>空闲轮询间隔(秒)</Text><InputNumber min={5} max={60} value={refreshInterval} onChange={(value) => { const next = Math.min(60, Math.max(5, value || 15)); setRefreshInterval(next); localStorage.setItem('huya_elite_refresh_interval', String(next)); }} style={{ width: 300 }} /></Space></Space>}</Modal>
|
||||
<Modal open={configOpen} title="精英宝典设置" onCancel={() => setConfigOpen(false)} onOk={() => void saveConfig()} confirmLoading={configSaving} okText="保存" cancelText="取消" width={560}>{configLoading ? <div style={{ padding: 24, textAlign: 'center' }}>加载中...</div> : configDraft && <Space direction="vertical" size={10} style={{ width: '100%' }}>{configField('sid', '活动 SID')}{configField('bind_act_id', '绑定活动 ID')}{configField('outer_act_id', '外部活动 ID')}{configField('bind_gid', '绑定 gid')}{configField('room_pid', '直播间 PID')}{configField('pay_channel', '支付渠道')}{configField('points_page_code', '积分中心 pageCode')}{configField('points_source_id', '积分中心 sourceId')}{configField('points_scene', '积分中心 scene')}{configField('points_page_id', '页签 pageId(可选)')}{configField('points_tab_ids', '页签 IDs(可选)')}<Space style={{ width: '100%', justifyContent: 'space-between' }}><Text>空闲轮询间隔(秒)</Text><InputNumber min={5} max={60} value={refreshInterval} onChange={(value) => { const next = Math.min(60, Math.max(5, value || 15)); setRefreshInterval(next); localStorage.setItem('huya_elite_refresh_interval', String(next)); }} style={{ width: 300 }} /></Space></Space>}</Modal>
|
||||
<HuyaExternalCkImportModal open={ckImportOpen} onClose={() => setCkImportOpen(false)} tags={importTags} scope={SCOPE} onImported={(result) => { localStorage.setItem('huya_elite_workbench_account_ids', JSON.stringify(result.account_ids)); void load(); }} />
|
||||
</div>;
|
||||
}
|
||||
|
||||
@@ -1846,6 +1846,31 @@ export default function HuyaTasksPage() {
|
||||
<Input placeholder="9271" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item label="积分中心 pageCode" name="points_page_code">
|
||||
<Input placeholder="heping_baodian_credit_center_page" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item label="积分中心 sourceId" name="points_source_id">
|
||||
<Input placeholder="yellowcarlist" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item label="积分中心 scene" name="points_scene">
|
||||
<Input placeholder="7" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item label="页签 pageId(可选)" name="points_page_id">
|
||||
<Input placeholder="留空自动发现,如 343" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Form.Item label="页签 IDs(可选,逗号分隔)" name="points_tab_ids">
|
||||
<Input placeholder="留空自动拉全部,如 431,433" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Form.Item label="支付渠道" name="pay_channel">
|
||||
<Select
|
||||
|
||||
Reference in New Issue
Block a user