yumi-admin/apps/src/views/resident-activity/daily-task-config.vue
2026-05-15 16:31:56 +08:00

941 lines
30 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script lang="ts" setup>
import type { Dayjs } from 'dayjs';
import { computed, reactive, ref, watch } from 'vue';
import { Page } from '@vben/common-ui';
import { useAccessStore } from '@vben/stores';
import {
Button,
Card,
DatePicker,
Input,
InputNumber,
message,
Modal,
Select,
Space,
Spin,
Switch,
Table,
TabPane,
Tabs,
Tag,
} from 'antdv-next';
import dayjs from 'dayjs';
import {
getResidentDailyTaskConfig,
pageResidentDailyTaskClaimRecords,
saveResidentDailyTaskConfig,
} from '#/api/legacy/resident-activity';
import { getAllowedSysOrigins } from '#/views/system/shared';
defineOptions({ name: 'ResidentDailyTaskConfigPage' });
let localRowId = 0;
let localConditionJumpRowId = 0;
const rechargeJumpPage = '/main/me/wallet/recharge';
function createRowKey(seed?: number | string) {
localRowId += 1;
return `daily-task-${seed || localRowId}-${localRowId}`;
}
function createConditionJumpRowKey(seed?: number | string) {
localConditionJumpRowId += 1;
return `condition-jump-${seed || localConditionJumpRowId}-${localConditionJumpRowId}`;
}
function createTask(sortOrder = 10) {
return {
conditionType: 'MIC_DURATION_SECONDS',
cover: '',
enabled: true,
id: null,
rewardGold: 0,
rowKey: createRowKey(),
sortOrder,
targetUnit: 'second',
targetValue: 60,
taskCode: '',
taskDesc: '进度 / 目标',
taskIcon: '',
taskName: '',
};
}
function defaultTasks() {
return [createTask(10)];
}
function defaultJumpType(conditionType: string) {
return conditionType === 'RECHARGE_GOLD' ? 'RECHARGE_IN_APP' : 'ROOM_RANDOM_WITH_MIC';
}
function createConditionJump(sortOrder = 10, conditionType = 'MIC_DURATION_SECONDS') {
return {
conditionType,
id: null,
jumpPage: '',
jumpType: defaultJumpType(conditionType),
rowKey: createConditionJumpRowKey(conditionType),
sortOrder,
taskCategory: '',
};
}
const conditionTypeOptions = [
{ label: '麦克风时长(秒)', value: 'MIC_DURATION_SECONDS' },
{ label: '游戏消耗金币', value: 'GAME_CONSUME_GOLD' },
{ label: '礼物消耗金币', value: 'GIFT_CONSUME_GOLD' },
{ label: '充值金币', value: 'RECHARGE_GOLD' },
];
const taskCategoryOptions = [
{ label: '每日任务', value: 'DAILY' },
{ label: '专属任务', value: 'NEWBIE' },
];
const jumpTypeOptions = [
{ label: '随机上麦房间', value: 'ROOM_RANDOM_WITH_MIC' },
{ label: '随机房间', value: 'ROOM_RANDOM' },
{ label: '充值页', value: 'RECHARGE_IN_APP' },
{ label: '端内路由', value: 'APP_ROUTE' },
{ label: 'H5 链接', value: 'H5_URL' },
{ label: '无跳转', value: 'NONE' },
];
const targetUnitOptions = [
{ label: '秒', value: 'second' },
{ label: '次', value: 'count' },
{ label: '金币', value: 'gold' },
];
const taskColumns = [
{ dataIndex: 'enabled', key: 'enabled', title: '启用', width: 80 },
{ dataIndex: 'taskCode', key: 'taskCode', title: '任务编码', width: 210 },
{ dataIndex: 'taskName', key: 'taskName', title: '任务名称', width: 220 },
{ dataIndex: 'taskDesc', key: 'taskDesc', title: '说明', width: 160 },
{ dataIndex: 'conditionType', key: 'conditionType', title: '条件', width: 190 },
{ dataIndex: 'targetValue', key: 'targetValue', title: '目标', width: 130 },
{ dataIndex: 'targetUnit', key: 'targetUnit', title: '单位', width: 110 },
{ dataIndex: 'rewardGold', key: 'rewardGold', title: '奖励金币', width: 140 },
{ dataIndex: 'sortOrder', key: 'sortOrder', title: '排序', width: 110 },
{ dataIndex: 'actions', key: 'actions', title: '操作', width: 90 },
];
const conditionJumpColumns = [
{ dataIndex: 'conditionType', key: 'conditionType', title: '条件', width: 220 },
{ dataIndex: 'jumpType', key: 'jumpType', title: '跳转', width: 190 },
{ dataIndex: 'jumpPage', key: 'jumpPage', title: '跳转参数', width: 320 },
{ dataIndex: 'sortOrder', key: 'sortOrder', title: '排序', width: 120 },
{ dataIndex: 'actions', key: 'actions', title: '操作', width: 90 },
];
const claimColumns = [
{ dataIndex: 'userId', key: 'userId', title: '用户ID', width: 140 },
{ dataIndex: 'taskCode', key: 'taskCode', title: '任务编码', width: 220 },
{ dataIndex: 'taskName', key: 'taskName', title: '任务名称', width: 220 },
{ dataIndex: 'cycleKey', key: 'cycleKey', title: '周期', width: 130 },
{ dataIndex: 'rewardGold', key: 'rewardGold', title: '金币', width: 110 },
{ dataIndex: 'status', key: 'status', title: '状态', width: 110 },
{ dataIndex: 'walletEventId', key: 'walletEventId', title: '钱包事件ID', width: 260 },
{ dataIndex: 'failureReason', key: 'failureReason', title: '失败原因', width: 220 },
{ dataIndex: 'createTime', key: 'createTime', title: '创建时间', width: 180 },
];
const accessStore = useAccessStore();
const sysOriginOptions = computed(() => {
const options = getAllowedSysOrigins(accessStore.accessCodes || []);
return options.length > 0 ? options : getAllowedSysOrigins([]);
});
const selectedSysOrigin = ref('');
const activeTaskCategory = ref('DAILY');
const activeTab = ref('config');
const loading = ref(false);
const saving = ref(false);
const claimLoading = ref(false);
const form = reactive<Record<string, any>>({
conditionJumps: [],
configured: false,
enabled: false,
id: 0,
sysOrigin: '',
taskCategory: 'DAILY',
tasks: defaultTasks(),
timezone: 'Asia/Riyadh',
updateTime: '',
});
const claimQuery = reactive<Record<string, any>>({
cursor: 1,
limit: 20,
status: '',
taskCode: '',
userId: '',
});
function defaultClaimDateRange(): [Dayjs, Dayjs] {
return [dayjs().startOf('day'), dayjs().endOf('day')];
}
const claimDateRange = ref<[Dayjs, Dayjs] | null>(defaultClaimDateRange());
const claimPage = reactive<Record<string, any>>({
current: 1,
records: [],
size: 20,
total: 0,
});
const claimSummary = reactive<Record<string, any>>({
selectedTaskRewardGold: 0,
taskCode: '',
totalRewardGold: 0,
});
const statusMeta = computed(() => {
if (!form.configured) {
return { color: 'processing', text: '未配置' };
}
if (form.enabled) {
return { color: 'success', text: '已启用' };
}
return { color: 'default', text: '已关闭' };
});
const activeTaskCategoryLabel = computed(() => {
return taskCategoryOptions.find((item) => item.value === activeTaskCategory.value)?.label || '每日任务';
});
const taskCodeOptions = computed(() => {
const seen = new Set<string>();
return ((form.tasks || []) as Array<Record<string, any>>)
.map((item) => {
const value = String(item.taskCode || '').trim();
if (!value || seen.has(value)) {
return null;
}
seen.add(value);
const taskName = String(item.taskName || '').trim();
return {
label: taskName ? `${value} / ${taskName}` : value,
value,
};
})
.filter(Boolean) as Array<{ label: string; value: string }>;
});
function normalizeConditionJumpRows(
rows: Array<Record<string, any>>,
tasks: Array<Record<string, any>>,
) {
const result = rows.map((item: Record<string, any>, index: number) => {
const conditionType = String(item.conditionType || 'MIC_DURATION_SECONDS');
return {
conditionType,
id: item.id ?? null,
jumpPage: String(item.jumpPage || ''),
jumpType: String(item.jumpType || defaultJumpType(conditionType)),
rowKey: createConditionJumpRowKey(item.id || conditionType || index),
sortOrder: Number(item.sortOrder || (index + 1) * 10),
taskCategory: String(item.taskCategory || activeTaskCategory.value),
};
});
const seenConditions = new Set(result.map((item) => String(item.conditionType || '')));
for (const task of tasks) {
const conditionType = String(task.conditionType || '').trim();
if (!conditionType || seenConditions.has(conditionType)) {
continue;
}
result.push(createConditionJump((result.length + 1) * 10, conditionType));
seenConditions.add(conditionType);
}
if (result.length === 0) {
result.push(createConditionJump(10));
}
return result.toSorted((left: Record<string, any>, right: Record<string, any>) =>
Number(left.sortOrder || 0) - Number(right.sortOrder || 0),
);
}
function normalizeConfig(result: null | Record<string, any> | undefined) {
const value = result || {};
form.configured = Boolean(value.configured);
form.enabled = Boolean(value.enabled);
form.id = value.id || 0;
form.sysOrigin = String(value.sysOrigin || selectedSysOrigin.value || '');
form.taskCategory = activeTaskCategory.value;
form.timezone = String(value.timezone || 'Asia/Riyadh');
form.updateTime = String(value.updateTime || '');
const rows = Array.isArray(value.tasks) && value.tasks.length > 0
? value.tasks
: defaultTasks();
const normalizedTasks = rows
.map((item: Record<string, any>, index: number) => ({
conditionType: String(item.conditionType || 'MIC_DURATION_SECONDS'),
cover: String(item.cover || ''),
enabled: item.enabled !== false,
id: item.id ?? null,
rewardGold: Number(item.rewardGold || 0),
rowKey: createRowKey(item.id || index),
sortOrder: Number(item.sortOrder || (index + 1) * 10),
targetUnit: String(item.targetUnit || 'second'),
targetValue: Number(item.targetValue || 0),
taskCode: String(item.taskCode || ''),
taskDesc: String(item.taskDesc || '进度 / 目标'),
taskIcon: String(item.taskIcon || ''),
taskName: String(item.taskName || ''),
}))
.toSorted((left: Record<string, any>, right: Record<string, any>) =>
Number(left.sortOrder || 0) - Number(right.sortOrder || 0),
);
form.tasks = normalizedTasks;
form.conditionJumps = normalizeConditionJumpRows(
Array.isArray(value.conditionJumps) ? value.conditionJumps : [],
normalizedTasks,
);
}
async function loadConfig() {
if (!selectedSysOrigin.value) {
return;
}
loading.value = true;
try {
const result = await getResidentDailyTaskConfig(
selectedSysOrigin.value,
activeTaskCategory.value,
);
normalizeConfig(result);
} finally {
loading.value = false;
}
}
function addTask() {
const tasks = form.tasks as Array<Record<string, any>>;
const maxSort = Math.max(0, ...tasks.map((item) => Number(item.sortOrder || 0)));
tasks.push(createTask(maxSort + 10));
}
function removeTask(index: number) {
(form.tasks as Array<Record<string, any>>).splice(index, 1);
}
function addConditionJump() {
const rows = form.conditionJumps as Array<Record<string, any>>;
const usedConditions = new Set(rows.map((item) => String(item.conditionType || '')));
const option = conditionTypeOptions.find((item) => !usedConditions.has(item.value));
if (!option) {
message.warning('可配置的条件都已添加');
return;
}
const maxSort = Math.max(0, ...rows.map((item) => Number(item.sortOrder || 0)));
rows.push(createConditionJump(maxSort + 10, option.value));
}
function removeConditionJump(index: number) {
(form.conditionJumps as Array<Record<string, any>>).splice(index, 1);
}
function shouldReplaceJumpPage(value: unknown) {
const jumpPage = String(value || '').trim();
return !jumpPage || jumpPage === '/room' || jumpPage === rechargeJumpPage;
}
function jumpTypeRequiresPage(jumpType: string) {
return jumpType === 'APP_ROUTE' || jumpType === 'H5_URL';
}
function taskAt(index: number) {
return (form.tasks as Array<Record<string, any>>)[index];
}
function conditionJumpAt(index: number) {
return (form.conditionJumps as Array<Record<string, any>>)[index];
}
function jumpPagePlaceholder(record: Record<string, any>) {
const jumpType = String(record.jumpType || '').trim();
if (jumpType === 'RECHARGE_IN_APP') {
return rechargeJumpPage;
}
if (jumpType === 'H5_URL') {
return 'https://example.com';
}
if (jumpType === 'APP_ROUTE') {
return '/main/me/wallet/recharge';
}
if (jumpType === 'ROOM_RANDOM' || jumpType === 'ROOM_RANDOM_WITH_MIC') {
return '/room';
}
return '';
}
function ensureConditionJumpForCondition(conditionType: string) {
const normalizedCondition = String(conditionType || '').trim();
if (!normalizedCondition) {
return;
}
const rows = form.conditionJumps as Array<Record<string, any>>;
if (rows.some((item) => String(item.conditionType || '') === normalizedCondition)) {
return;
}
const maxSort = Math.max(0, ...rows.map((item) => Number(item.sortOrder || 0)));
rows.push(createConditionJump(maxSort + 10, normalizedCondition));
}
function handleConditionTypeChange(index: number, value: unknown) {
const record = taskAt(index);
if (!record) {
return;
}
record.conditionType = String(value || record.conditionType || '').trim();
ensureConditionJumpForCondition(record.conditionType);
if (
record.conditionType === 'RECHARGE_GOLD' &&
(!record.targetUnit || record.targetUnit === 'second')
) {
record.targetUnit = 'gold';
}
}
function handleConditionJumpTypeChange(index: number, value: unknown) {
const record = conditionJumpAt(index);
if (!record) {
return;
}
record.jumpType = String(value || record.jumpType || '').trim();
if (shouldReplaceJumpPage(record.jumpPage)) {
record.jumpPage = '';
}
}
function handleConditionJumpConditionChange(index: number, value: unknown) {
const record = conditionJumpAt(index);
if (!record) {
return;
}
const conditionType = String(value || record.conditionType || '').trim();
record.conditionType = conditionType;
record.jumpType = defaultJumpType(conditionType);
if (shouldReplaceJumpPage(record.jumpPage)) {
record.jumpPage = '';
}
}
function confirmRemoveTask(index: number) {
Modal.confirm({
onOk: () => removeTask(index),
title: `确认删除该${activeTaskCategoryLabel.value}`,
});
}
function validateBeforeSave() {
const tasks = form.tasks as Array<Record<string, any>>;
if (tasks.length === 0) {
message.warning(`请至少配置一个${activeTaskCategoryLabel.value}`);
return false;
}
const seenCodes = new Set<string>();
for (const item of tasks) {
const taskCode = String(item.taskCode || '').trim();
const taskName = String(item.taskName || '').trim();
const conditionType = String(item.conditionType || '').trim();
if (!taskCode) {
message.warning('任务编码不能为空');
return false;
}
if (!taskName) {
message.warning('任务名称不能为空');
return false;
}
if (!conditionType) {
message.warning('任务条件不能为空');
return false;
}
if (Number(item.targetValue || 0) <= 0) {
message.warning('任务目标必须大于 0');
return false;
}
if (Number(item.rewardGold || 0) <= 0) {
message.warning('奖励金币必须大于 0');
return false;
}
if (seenCodes.has(taskCode)) {
message.warning('任务编码不能重复');
return false;
}
seenCodes.add(taskCode);
}
const conditionJumps = form.conditionJumps as Array<Record<string, any>>;
const seenConditions = new Set<string>();
for (const item of conditionJumps) {
const conditionType = String(item.conditionType || '').trim();
const jumpType = String(item.jumpType || '').trim();
const jumpPage = String(item.jumpPage || '').trim();
if (!conditionType) {
message.warning('条件跳转的条件不能为空');
return false;
}
if (seenConditions.has(conditionType)) {
message.warning('条件跳转的条件不能重复');
return false;
}
if (!jumpType) {
message.warning('条件跳转的跳转类型不能为空');
return false;
}
if (jumpTypeRequiresPage(jumpType) && !jumpPage) {
message.warning('端内路由和 H5 链接需要填写跳转参数');
return false;
}
seenConditions.add(conditionType);
}
for (const item of tasks) {
const conditionType = String(item.conditionType || '').trim();
if (!seenConditions.has(conditionType)) {
message.warning('请先配置任务条件对应的跳转类型');
return false;
}
}
return true;
}
async function submitForm() {
if (!validateBeforeSave()) {
return;
}
saving.value = true;
try {
const conditionJumpPayloads: Array<Record<string, any>> = (
form.conditionJumps || []
).map((item: Record<string, any>) => {
const jumpType = String(item.jumpType || '').trim();
return {
conditionType: String(item.conditionType || '').trim(),
id: item.id || 0,
jumpPage: String(item.jumpPage || '').trim(),
jumpType,
sortOrder: Number(item.sortOrder || 0),
taskCategory: activeTaskCategory.value,
};
});
const jumpByCondition = new Map<string, Record<string, any>>(
conditionJumpPayloads.map((item: Record<string, any>) => [
String(item.conditionType || ''),
item,
]),
);
await saveResidentDailyTaskConfig({
enabled: Boolean(form.enabled),
id: form.id || 0,
sysOrigin: selectedSysOrigin.value,
taskCategory: activeTaskCategory.value,
tasks: (form.tasks || []).map((item: Record<string, any>) => {
const conditionType = String(item.conditionType || '').trim();
const conditionJump = jumpByCondition.get(conditionType);
return {
conditionType,
cover: String(item.cover || '').trim(),
enabled: item.enabled !== false,
id: item.id || 0,
jumpPage: conditionJump?.jumpPage || '',
jumpType: conditionJump?.jumpType || '',
rewardGold: Number(item.rewardGold || 0),
sortOrder: Number(item.sortOrder || 0),
targetUnit: String(item.targetUnit || '').trim(),
targetValue: Number(item.targetValue || 0),
taskCode: String(item.taskCode || '').trim(),
taskDesc: String(item.taskDesc || '').trim(),
taskIcon: String(item.taskIcon || '').trim(),
taskName: String(item.taskName || '').trim(),
};
}),
conditionJumps: conditionJumpPayloads,
timezone: String(form.timezone || '').trim(),
});
message.success('保存成功');
await loadConfig();
} finally {
saving.value = false;
}
}
function normalizePage(target: Record<string, any>, result: Record<string, any>) {
target.records = Array.isArray(result?.records) ? result.records : [];
target.total = Number(result?.total || 0);
target.current = Number(result?.current || 1);
target.size = Number(result?.size || 20);
const summary = result?.summary || {};
claimSummary.totalRewardGold = Number(summary.totalRewardGold || 0);
claimSummary.selectedTaskRewardGold = Number(summary.selectedTaskRewardGold || 0);
claimSummary.taskCode = String(summary.taskCode || '');
}
function claimTimeParams() {
if (!claimDateRange.value?.[0] || !claimDateRange.value?.[1]) {
return {};
}
return {
endTime: String(claimDateRange.value[1].valueOf()),
startTime: String(claimDateRange.value[0].valueOf()),
};
}
async function loadClaimRecords() {
if (!selectedSysOrigin.value) {
return;
}
claimLoading.value = true;
try {
const result = await pageResidentDailyTaskClaimRecords({
...claimQuery,
...claimTimeParams(),
sysOrigin: selectedSysOrigin.value,
taskCategory: activeTaskCategory.value,
});
normalizePage(claimPage, result || {});
} finally {
claimLoading.value = false;
}
}
function searchClaimRecords() {
claimQuery.cursor = 1;
void loadClaimRecords();
}
function resetClaimDateRange() {
claimDateRange.value = defaultClaimDateRange();
searchClaimRecords();
}
function handleClaimTableChange(pagination: Record<string, any>) {
claimQuery.cursor = Number(pagination.current || 1);
claimQuery.limit = Number(pagination.pageSize || 20);
void loadClaimRecords();
}
watch(
sysOriginOptions,
(options) => {
if (!selectedSysOrigin.value) {
selectedSysOrigin.value = String(options[0]?.value || '');
}
},
{ immediate: true },
);
watch(
selectedSysOrigin,
(value) => {
if (value) {
claimQuery.cursor = 1;
claimQuery.taskCode = '';
void loadConfig();
void loadClaimRecords();
}
},
{ immediate: true },
);
watch(activeTaskCategory, () => {
if (!selectedSysOrigin.value) {
return;
}
claimQuery.cursor = 1;
claimQuery.taskCode = '';
void loadConfig();
void loadClaimRecords();
});
</script>
<template>
<Page auto-content-height>
<Spin :spinning="loading">
<Card :bordered="false">
<template #title>
<Space align="center" wrap>
<span>每日任务</span>
<Tag :color="statusMeta.color">{{ statusMeta.text }}</Tag>
</Space>
</template>
<div class="toolbar">
<Select
v-model:value="selectedSysOrigin"
:options="sysOriginOptions"
option-filter-prop="label"
option-label-prop="label"
placeholder="请选择系统"
show-search
style="width: 180px"
/>
<Button :loading="loading" @click="loadConfig">刷新配置</Button>
</div>
<Tabs v-model:active-key="activeTaskCategory" class="category-tabs">
<TabPane
v-for="item in taskCategoryOptions"
:key="item.value"
:tab="item.label"
/>
</Tabs>
<Tabs v-model:active-key="activeTab">
<TabPane key="config" tab="任务配置">
<div class="config-row">
<Space align="center" wrap>
<span class="field-label">启用</span>
<Switch v-model:checked="form.enabled" />
<span class="field-label">任务时区</span>
<Input v-model:value="form.timezone" style="width: 180px" />
<span class="sub-text">更新时间{{ form.updateTime || '-' }}</span>
</Space>
</div>
<Table
:columns="taskColumns"
:data-source="form.tasks"
:pagination="false"
row-key="rowKey"
:scroll="{ x: 1350 }"
>
<template #bodyCell="{ column, index, record }">
<template v-if="column.key === 'enabled'">
<Switch v-model:checked="record.enabled" />
</template>
<template v-else-if="column.key === 'taskCode'">
<Input
v-model:value="record.taskCode"
:placeholder="activeTaskCategory === 'DAILY' ? 'DAILY_MIC_DURATION' : 'EXCLUSIVE_RECHARGE'"
/>
</template>
<template v-else-if="column.key === 'taskName'">
<Input v-model:value="record.taskName" placeholder="使用麦克风 %d 分钟。" />
</template>
<template v-else-if="column.key === 'taskDesc'">
<Input v-model:value="record.taskDesc" placeholder="进度 / 目标" />
</template>
<template v-else-if="column.key === 'conditionType'">
<Select
v-model:value="record.conditionType"
:options="conditionTypeOptions"
style="width: 170px"
@change="(value) => handleConditionTypeChange(index, value)"
/>
</template>
<template v-else-if="column.key === 'targetValue'">
<InputNumber
v-model:value="record.targetValue"
:min="1"
:precision="0"
style="width: 100px"
/>
</template>
<template v-else-if="column.key === 'targetUnit'">
<Select
v-model:value="record.targetUnit"
:options="targetUnitOptions"
style="width: 90px"
/>
</template>
<template v-else-if="column.key === 'rewardGold'">
<InputNumber
v-model:value="record.rewardGold"
:min="1"
:precision="0"
style="width: 110px"
/>
</template>
<template v-else-if="column.key === 'sortOrder'">
<InputNumber
v-model:value="record.sortOrder"
:precision="0"
style="width: 80px"
/>
</template>
<template v-else-if="column.key === 'actions'">
<Button danger type="link" @click="confirmRemoveTask(index)">删除</Button>
</template>
</template>
</Table>
<div class="actions">
<Button @click="addTask">新增任务</Button>
<Button :loading="saving" type="primary" @click="submitForm">
保存配置
</Button>
</div>
</TabPane>
<TabPane key="conditionJumps" tab="条件跳转类型">
<Table
:columns="conditionJumpColumns"
:data-source="form.conditionJumps"
:pagination="false"
row-key="rowKey"
:scroll="{ x: 940 }"
>
<template #bodyCell="{ column, index, record }">
<template v-if="column.key === 'conditionType'">
<Select
v-model:value="record.conditionType"
:options="conditionTypeOptions"
style="width: 190px"
@change="(value) => handleConditionJumpConditionChange(index, value)"
/>
</template>
<template v-else-if="column.key === 'jumpType'">
<Select
v-model:value="record.jumpType"
:options="jumpTypeOptions"
style="width: 160px"
@change="(value) => handleConditionJumpTypeChange(index, value)"
/>
</template>
<template v-else-if="column.key === 'jumpPage'">
<Input
v-model:value="record.jumpPage"
:placeholder="jumpPagePlaceholder(record)"
/>
</template>
<template v-else-if="column.key === 'sortOrder'">
<InputNumber
v-model:value="record.sortOrder"
:precision="0"
style="width: 80px"
/>
</template>
<template v-else-if="column.key === 'actions'">
<Button danger type="link" @click="removeConditionJump(index)">
删除
</Button>
</template>
</template>
</Table>
<div class="actions">
<Button @click="addConditionJump">新增条件</Button>
<Button :loading="saving" type="primary" @click="submitForm">
保存配置
</Button>
</div>
</TabPane>
<TabPane key="claims" tab="领取记录">
<div class="toolbar">
<Input
v-model:value="claimQuery.userId"
allow-clear
placeholder="用户ID"
style="width: 160px"
/>
<Select
v-model:value="claimQuery.taskCode"
allow-clear
:options="taskCodeOptions"
option-filter-prop="label"
placeholder="任务编码"
show-search
style="width: 280px"
/>
<DatePicker.RangePicker
v-model:value="claimDateRange"
allow-clear
format="YYYY-MM-DD HH:mm:ss"
show-time
style="width: 390px"
/>
<Select
v-model:value="claimQuery.status"
allow-clear
:options="[
{ label: '成功', value: 'SUCCESS' },
{ label: '失败', value: 'FAILED' },
]"
placeholder="状态"
style="width: 140px"
/>
<Button :loading="claimLoading" type="primary" @click="searchClaimRecords">
查询
</Button>
<Button @click="resetClaimDateRange">今天</Button>
</div>
<div class="claim-summary">
<Tag color="blue">
当前时间总发放金币{{ claimSummary.totalRewardGold }}
</Tag>
<Tag v-if="claimQuery.taskCode" color="green">
{{ claimQuery.taskCode }} 发放金币{{
claimSummary.selectedTaskRewardGold
}}
</Tag>
</div>
<Table
:columns="claimColumns"
:data-source="claimPage.records"
:loading="claimLoading"
:pagination="{
current: claimPage.current,
pageSize: claimPage.size,
showSizeChanger: true,
total: claimPage.total,
}"
row-key="id"
:scroll="{ x: 1580 }"
@change="handleClaimTableChange"
/>
</TabPane>
</Tabs>
</Card>
</Spin>
</Page>
</template>
<style scoped>
.toolbar {
align-items: center;
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-bottom: 16px;
}
.config-row {
margin-bottom: 16px;
}
.field-label {
color: rgb(71 85 105);
font-size: 13px;
}
.sub-text {
color: rgb(100 116 139);
font-size: 12px;
}
.claim-summary {
margin-bottom: 12px;
}
.actions {
display: flex;
gap: 12px;
justify-content: flex-end;
margin-top: 16px;
}
</style>