任务tab
This commit is contained in:
parent
720ddc36ad
commit
1d67b3aa97
@ -16,8 +16,8 @@ import {
|
||||
Spin,
|
||||
Switch,
|
||||
Table,
|
||||
Tabs,
|
||||
TabPane,
|
||||
Tabs,
|
||||
Tag,
|
||||
} from 'antdv-next';
|
||||
|
||||
@ -32,20 +32,25 @@ 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,
|
||||
jumpPage: '',
|
||||
jumpType: 'ROOM_RANDOM_WITH_MIC',
|
||||
rewardGold: 0,
|
||||
rowKey: createRowKey(),
|
||||
sortOrder,
|
||||
@ -62,6 +67,22 @@ 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' },
|
||||
@ -77,6 +98,7 @@ const taskCategoryOptions = [
|
||||
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' },
|
||||
@ -97,12 +119,18 @@ const taskColumns = [
|
||||
{ dataIndex: 'targetValue', key: 'targetValue', title: '目标', width: 130 },
|
||||
{ dataIndex: 'targetUnit', key: 'targetUnit', title: '单位', width: 110 },
|
||||
{ dataIndex: 'rewardGold', key: 'rewardGold', title: '奖励金币', width: 140 },
|
||||
{ dataIndex: 'jumpType', key: 'jumpType', title: '跳转', width: 180 },
|
||||
{ dataIndex: 'jumpPage', key: 'jumpPage', title: '跳转参数', width: 220 },
|
||||
{ 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 },
|
||||
@ -141,6 +169,7 @@ const claimLoading = ref(false);
|
||||
const eventLoading = ref(false);
|
||||
|
||||
const form = reactive<Record<string, any>>({
|
||||
conditionJumps: [],
|
||||
configured: false,
|
||||
enabled: false,
|
||||
id: 0,
|
||||
@ -195,6 +224,42 @@ const activeTaskCategoryLabel = computed(() => {
|
||||
return taskCategoryOptions.find((item) => item.value === activeTaskCategory.value)?.label || '每日任务';
|
||||
});
|
||||
|
||||
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);
|
||||
@ -207,14 +272,12 @@ function normalizeConfig(result: null | Record<string, any> | undefined) {
|
||||
const rows = Array.isArray(value.tasks) && value.tasks.length > 0
|
||||
? value.tasks
|
||||
: defaultTasks();
|
||||
form.tasks = rows
|
||||
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,
|
||||
jumpPage: String(item.jumpPage || ''),
|
||||
jumpType: String(item.jumpType || 'ROOM_RANDOM_WITH_MIC'),
|
||||
rewardGold: Number(item.rewardGold || 0),
|
||||
rowKey: createRowKey(item.id || index),
|
||||
sortOrder: Number(item.sortOrder || (index + 1) * 10),
|
||||
@ -225,9 +288,14 @@ function normalizeConfig(result: null | Record<string, any> | undefined) {
|
||||
taskIcon: String(item.taskIcon || ''),
|
||||
taskName: String(item.taskName || ''),
|
||||
}))
|
||||
.sort((left: Record<string, any>, right: Record<string, any>) =>
|
||||
.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() {
|
||||
@ -256,6 +324,108 @@ 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),
|
||||
@ -273,6 +443,7 @@ function validateBeforeSave() {
|
||||
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;
|
||||
@ -281,6 +452,10 @@ function validateBeforeSave() {
|
||||
message.warning('任务名称不能为空');
|
||||
return false;
|
||||
}
|
||||
if (!conditionType) {
|
||||
message.warning('任务条件不能为空');
|
||||
return false;
|
||||
}
|
||||
if (Number(item.targetValue || 0) <= 0) {
|
||||
message.warning('任务目标必须大于 0');
|
||||
return false;
|
||||
@ -295,6 +470,39 @@ function validateBeforeSave() {
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
@ -314,8 +522,6 @@ async function submitForm() {
|
||||
cover: String(item.cover || '').trim(),
|
||||
enabled: item.enabled !== false,
|
||||
id: item.id || 0,
|
||||
jumpPage: String(item.jumpPage || '').trim(),
|
||||
jumpType: String(item.jumpType || '').trim(),
|
||||
rewardGold: Number(item.rewardGold || 0),
|
||||
sortOrder: Number(item.sortOrder || 0),
|
||||
targetUnit: String(item.targetUnit || '').trim(),
|
||||
@ -325,6 +531,17 @@ async function submitForm() {
|
||||
taskIcon: String(item.taskIcon || '').trim(),
|
||||
taskName: String(item.taskName || '').trim(),
|
||||
})),
|
||||
conditionJumps: (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,
|
||||
};
|
||||
}),
|
||||
timezone: String(form.timezone || '').trim(),
|
||||
});
|
||||
message.success('保存成功');
|
||||
@ -457,7 +674,7 @@ watch(activeTaskCategory, () => {
|
||||
<Button :loading="loading" @click="loadConfig">刷新配置</Button>
|
||||
</div>
|
||||
|
||||
<Tabs v-model:activeKey="activeTaskCategory" class="category-tabs">
|
||||
<Tabs v-model:active-key="activeTaskCategory" class="category-tabs">
|
||||
<TabPane
|
||||
v-for="item in taskCategoryOptions"
|
||||
:key="item.value"
|
||||
@ -465,7 +682,7 @@ watch(activeTaskCategory, () => {
|
||||
/>
|
||||
</Tabs>
|
||||
|
||||
<Tabs v-model:activeKey="activeTab">
|
||||
<Tabs v-model:active-key="activeTab">
|
||||
<TabPane key="config" tab="任务配置">
|
||||
<div class="config-row">
|
||||
<Space align="center" wrap>
|
||||
@ -482,7 +699,7 @@ watch(activeTaskCategory, () => {
|
||||
:data-source="form.tasks"
|
||||
:pagination="false"
|
||||
row-key="rowKey"
|
||||
:scroll="{ x: 1850 }"
|
||||
:scroll="{ x: 1350 }"
|
||||
>
|
||||
<template #bodyCell="{ column, index, record }">
|
||||
<template v-if="column.key === 'enabled'">
|
||||
@ -505,6 +722,7 @@ watch(activeTaskCategory, () => {
|
||||
v-model:value="record.conditionType"
|
||||
:options="conditionTypeOptions"
|
||||
style="width: 170px"
|
||||
@change="(value) => handleConditionTypeChange(index, value)"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'targetValue'">
|
||||
@ -530,16 +748,6 @@ watch(activeTaskCategory, () => {
|
||||
style="width: 110px"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'jumpType'">
|
||||
<Select
|
||||
v-model:value="record.jumpType"
|
||||
:options="jumpTypeOptions"
|
||||
style="width: 160px"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'jumpPage'">
|
||||
<Input v-model:value="record.jumpPage" placeholder="/room" />
|
||||
</template>
|
||||
<template v-else-if="column.key === 'sortOrder'">
|
||||
<InputNumber
|
||||
v-model:value="record.sortOrder"
|
||||
@ -561,6 +769,60 @@ watch(activeTaskCategory, () => {
|
||||
</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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user