修改金币流水
This commit is contained in:
parent
4f54e460bb
commit
6de7cbbff2
@ -5,6 +5,8 @@ import { requestClient } from '#/api/request';
|
||||
const SPECIFIED_GIFT_WEEKLY_RANK_BASE =
|
||||
'/go/resident-activity/specified-gift-weekly-rank';
|
||||
const SIGN_IN_REWARD_BASE = '/go/resident-activity/sign-in-reward';
|
||||
const ROOM_TURNOVER_REWARD_BASE =
|
||||
'/go/resident-activity/room-turnover-reward';
|
||||
|
||||
export async function getResidentInviteConfig(sysOrigin: string) {
|
||||
return requestClient.get<Record<string, any>>('/resident-activity/invite/config', {
|
||||
@ -135,3 +137,30 @@ export async function pageWeekStarRewardRecords(params: Record<string, any>) {
|
||||
export async function retryWeekStarRewardRecord(id: number | string) {
|
||||
return retrySpecifiedGiftWeeklyRankRewardRecord(id);
|
||||
}
|
||||
|
||||
export async function getRoomTurnoverRewardConfig(sysOrigin: string) {
|
||||
return requestClient.get<Record<string, any>>(`${ROOM_TURNOVER_REWARD_BASE}/config`, {
|
||||
params: { sysOrigin },
|
||||
});
|
||||
}
|
||||
|
||||
export async function saveRoomTurnoverRewardConfig(data: Record<string, any>) {
|
||||
return requestClient.post<Record<string, any>>(
|
||||
`${ROOM_TURNOVER_REWARD_BASE}/config/save`,
|
||||
data,
|
||||
);
|
||||
}
|
||||
|
||||
export async function getRoomTurnoverRewardCurrentRank(params: Record<string, any>) {
|
||||
return requestClient.get<Record<string, any>>(
|
||||
`${ROOM_TURNOVER_REWARD_BASE}/current-rank`,
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
export async function pageRoomTurnoverRewardRecords(params: Record<string, any>) {
|
||||
return requestClient.get<LegacyPageResult<Record<string, any>>>(
|
||||
`${ROOM_TURNOVER_REWARD_BASE}/reward-record/page`,
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
@ -85,20 +85,6 @@ const routes: RouteRecordRaw[] = [
|
||||
component: () => import('#/views/operate/friendship-card.vue'),
|
||||
meta: { title: '用户友谊卡配置' },
|
||||
},
|
||||
{
|
||||
name: 'LotfunInviteUserRule',
|
||||
path: 'sys/invite-user-rule',
|
||||
alias: '/sys/invite-user-rule',
|
||||
component: () => import('#/views/operate/invite-user-rule.vue'),
|
||||
meta: { title: '邀请用户配置' },
|
||||
},
|
||||
{
|
||||
name: 'AswatUserAdvertising',
|
||||
path: 'sys/user-advertising',
|
||||
alias: '/sys/user-advertising',
|
||||
component: () => import('#/views/operate/user-advertising.vue'),
|
||||
meta: { title: '用户观看广告配置' },
|
||||
},
|
||||
{
|
||||
name: 'OperateUserFreight',
|
||||
path: 'user_freight',
|
||||
@ -120,13 +106,6 @@ const routes: RouteRecordRaw[] = [
|
||||
component: () => import('#/views/operate/im-account-manager.vue'),
|
||||
meta: { title: 'IM账号管理' },
|
||||
},
|
||||
{
|
||||
name: 'CheckInLog',
|
||||
path: 'check/in/log',
|
||||
alias: '/operate/manager/check/in/log',
|
||||
component: () => import('#/views/operate/check-in-log.vue'),
|
||||
meta: { title: '用户签到' },
|
||||
},
|
||||
{
|
||||
name: 'OperateUserManage',
|
||||
path: 'user',
|
||||
@ -169,13 +148,6 @@ const routes: RouteRecordRaw[] = [
|
||||
component: () => import('#/views/operate/inside-operation-gold.vue'),
|
||||
meta: { title: '内部操作金币' },
|
||||
},
|
||||
{
|
||||
name: 'GameCouponRunningWater',
|
||||
path: 'game/coupon/running/water',
|
||||
alias: '/game/coupon/running/water',
|
||||
component: () => import('#/views/operate/game-coupon-running-water.vue'),
|
||||
meta: { title: '游戏券收支记录' },
|
||||
},
|
||||
{
|
||||
name: 'OperateIntegralStream',
|
||||
path: 'integral/stream',
|
||||
|
||||
@ -1,29 +1,5 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
meta: {
|
||||
icon: 'lucide:house',
|
||||
order: 15.5,
|
||||
title: '家族管理',
|
||||
},
|
||||
name: 'FamilyManager',
|
||||
path: '/family/manager',
|
||||
children: [
|
||||
{
|
||||
name: 'FamilyConfig',
|
||||
path: 'family_config',
|
||||
component: () => import('#/views/family/family-config.vue'),
|
||||
meta: { title: '家族配置' },
|
||||
},
|
||||
{
|
||||
name: 'FamilyList',
|
||||
path: 'family_list',
|
||||
component: () => import('#/views/family/family-list.vue'),
|
||||
meta: { title: '家族列表' },
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
const routes: RouteRecordRaw[] = [];
|
||||
|
||||
export default routes;
|
||||
|
||||
@ -29,12 +29,6 @@ const routes: RouteRecordRaw[] = [
|
||||
component: () => import('#/views/operate/cp-apply.vue'),
|
||||
meta: { title: 'CP申请' },
|
||||
},
|
||||
{
|
||||
name: 'GamePK',
|
||||
path: '/game/pk',
|
||||
component: () => import('#/views/operate/game-pk.vue'),
|
||||
meta: { title: 'PK记录' },
|
||||
},
|
||||
{
|
||||
name: 'OperateActivityConfig',
|
||||
path: 'activity/cnf',
|
||||
@ -47,24 +41,12 @@ const routes: RouteRecordRaw[] = [
|
||||
component: () => import('#/views/operate/activity-template.vue'),
|
||||
meta: { title: '活动模版' },
|
||||
},
|
||||
{
|
||||
name: 'GameLudo',
|
||||
path: '/game-ludo',
|
||||
component: () => import('#/views/operate/ludo-game.vue'),
|
||||
meta: { title: 'Ludo飞行棋' },
|
||||
},
|
||||
{
|
||||
name: 'RefundDeductTagLog',
|
||||
path: 'refund-deduct-tag-log',
|
||||
component: () => import('#/views/operate/refund-deduct-tag-log.vue'),
|
||||
meta: { title: '退款扣除目标' },
|
||||
},
|
||||
{
|
||||
name: 'OperateCnfLottery',
|
||||
path: 'cnf/lottery',
|
||||
component: () => import('#/views/operate/lottery-wheel-config.vue'),
|
||||
meta: { title: '抽奖品配置' },
|
||||
},
|
||||
{
|
||||
name: 'OperateAdministrator',
|
||||
path: 'administrator',
|
||||
@ -107,12 +89,6 @@ const routes: RouteRecordRaw[] = [
|
||||
component: () => import('#/views/operate/gift-config.vue'),
|
||||
meta: { title: '礼物管理' },
|
||||
},
|
||||
{
|
||||
name: 'OperateGameLotteryConfig',
|
||||
path: '/game/lottery-config',
|
||||
component: () => import('#/views/operate/game-lottery-config.vue'),
|
||||
meta: { title: '抽奖概率配置' },
|
||||
},
|
||||
{
|
||||
name: 'GameRedPacketRecord',
|
||||
path: '/game/red/packet/record',
|
||||
@ -137,30 +113,6 @@ const routes: RouteRecordRaw[] = [
|
||||
component: () => import('#/views/operate/lucky-gift-standard-config.vue'),
|
||||
meta: { title: '幸运礼物规格配置' },
|
||||
},
|
||||
{
|
||||
name: 'GameLuckyBox',
|
||||
path: '/game/lucky-box',
|
||||
component: () => import('#/views/operate/lucky-box.vue'),
|
||||
meta: { title: 'LuckyBox抽奖记录' },
|
||||
},
|
||||
{
|
||||
name: 'GameFruitDaysLog',
|
||||
path: '/game/fruit/log/fruit-days-log',
|
||||
component: () => import('#/views/operate/fruit-days-log.vue'),
|
||||
meta: { title: '摩天轮游戏每日数据' },
|
||||
},
|
||||
{
|
||||
name: 'OperateFruitTaskConfig',
|
||||
path: 'game/fruit/task-config',
|
||||
component: () => import('#/views/operate/fruit-task-config.vue'),
|
||||
meta: { title: '摩天轮任务配置' },
|
||||
},
|
||||
{
|
||||
name: 'GameTeenPatti',
|
||||
path: '/game/teen-patti',
|
||||
component: () => import('#/views/operate/teen-patti.vue'),
|
||||
meta: { title: '炸金花' },
|
||||
},
|
||||
{
|
||||
name: 'OperateSysCountry',
|
||||
path: 'sys/country',
|
||||
@ -203,12 +155,6 @@ const routes: RouteRecordRaw[] = [
|
||||
component: () => import('#/views/operate/trumpet-list.vue'),
|
||||
meta: { title: '喇叭列表' },
|
||||
},
|
||||
{
|
||||
name: 'OperateGameEgg',
|
||||
path: '/game/egg',
|
||||
component: () => import('#/views/operate/game-egg.vue'),
|
||||
meta: { title: '砸金蛋' },
|
||||
},
|
||||
{
|
||||
name: 'ActivityHallFame',
|
||||
path: '/activity/hall-fame',
|
||||
|
||||
@ -1,41 +1,5 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
meta: {
|
||||
icon: 'lucide:paw-print',
|
||||
order: 16,
|
||||
title: '宠物管理',
|
||||
},
|
||||
name: 'PetManager',
|
||||
path: '/pet/manager',
|
||||
children: [
|
||||
{
|
||||
name: 'PetFeeding',
|
||||
path: 'feeding',
|
||||
component: () => import('#/views/pet/pet-feeding.vue'),
|
||||
meta: { title: '宠物喂养记录' },
|
||||
},
|
||||
{
|
||||
name: 'PetBeans',
|
||||
path: 'beans',
|
||||
component: () => import('#/views/pet/pet-beans.vue'),
|
||||
meta: { title: '豆子账户' },
|
||||
},
|
||||
{
|
||||
name: 'PetBeansRunningWater',
|
||||
path: 'beans/running-water',
|
||||
component: () => import('#/views/pet/pet-beans-running-water.vue'),
|
||||
meta: { title: '豆子流水' },
|
||||
},
|
||||
{
|
||||
name: 'PetPool',
|
||||
path: 'pool',
|
||||
component: () => import('#/views/pet/pet-pool.vue'),
|
||||
meta: { title: '宠物池' },
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
const routes: RouteRecordRaw[] = [];
|
||||
|
||||
export default routes;
|
||||
|
||||
@ -42,6 +42,12 @@ const routes: RouteRecordRaw[] = [
|
||||
component: () => import('#/views/resident-activity/week-star.vue'),
|
||||
meta: { title: '指定礼物周榜' },
|
||||
},
|
||||
{
|
||||
name: 'ResidentRoomTurnoverReward',
|
||||
path: 'room-turnover-reward',
|
||||
component: () => import('#/views/resident-activity/room-turnover-reward.vue'),
|
||||
meta: { title: '房间流水奖励' },
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@ -1,35 +1,31 @@
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
computed,
|
||||
reactive,
|
||||
ref,
|
||||
watch } from 'vue';
|
||||
import { useRoute,
|
||||
useRouter } from 'vue-router';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
import { computed, reactive, ref, watch } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
import { Page } from '@vben/common-ui';
|
||||
import { useAccessStore } from '@vben/stores';
|
||||
|
||||
import { getClsGoldRunningWater } from '#/api/legacy/operate';
|
||||
import AccountInput from '#/components/account-input.vue';
|
||||
import { formatDate,
|
||||
getAllowedSysOrigins,
|
||||
getDisplaySysOriginText } from '#/views/system/shared';
|
||||
|
||||
import {
|
||||
AutoComplete,
|
||||
Button,
|
||||
Card,
|
||||
DatePicker,
|
||||
Input,
|
||||
message,
|
||||
Select,
|
||||
Space,
|
||||
Table,
|
||||
Tag,
|
||||
message
|
||||
} from 'antdv-next';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import { getClsGoldRunningWater } from '#/api/legacy/operate';
|
||||
import AccountInput from '#/components/account-input.vue';
|
||||
import {
|
||||
formatDate,
|
||||
getAllowedSysOrigins,
|
||||
getDisplaySysOriginText,
|
||||
} from '#/views/system/shared';
|
||||
|
||||
import UserProfileLink from './components/user-profile-link.vue';
|
||||
import {
|
||||
@ -153,6 +149,22 @@ function createDefaultRange() {
|
||||
return [String(start.valueOf()), String(end.valueOf())] as [string, string];
|
||||
}
|
||||
|
||||
function applyRangeDate(value: [string, string] | null) {
|
||||
rangeDate.value = value;
|
||||
query.startTime = value?.[0] || '';
|
||||
query.endTime = value?.[1] || '';
|
||||
}
|
||||
|
||||
function formatRunningWaterTime(value: any) {
|
||||
if (typeof value === 'string') {
|
||||
const trimmed = value.trim();
|
||||
if (/^\d+$/.test(trimmed)) {
|
||||
return formatDate(Number(trimmed));
|
||||
}
|
||||
}
|
||||
return formatDate(value);
|
||||
}
|
||||
|
||||
async function loadData(reset = false) {
|
||||
if (loading.value || loadMoreLoading.value) {
|
||||
return;
|
||||
@ -213,7 +225,7 @@ function openUserDetails(record: Record<string, any>) {
|
||||
}
|
||||
|
||||
if (!rangeDate.value) {
|
||||
rangeDate.value = createDefaultRange();
|
||||
applyRangeDate(createDefaultRange());
|
||||
}
|
||||
|
||||
if (route.query.userId) {
|
||||
@ -229,8 +241,11 @@ void loadData(true);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page v-if="!queryBackOperation" title="金币收支记录">
|
||||
<Card>
|
||||
<component
|
||||
:is="queryBackOperation ? 'div' : Page"
|
||||
:title="queryBackOperation ? undefined : '金币收支记录'"
|
||||
>
|
||||
<component :is="queryBackOperation ? 'div' : Card">
|
||||
<div class="toolbar">
|
||||
<SysOriginSelect
|
||||
v-model:value="sysOrigins"
|
||||
@ -284,7 +299,7 @@ void loadData(true);
|
||||
:pagination="false"
|
||||
row-key="id"
|
||||
:scroll="{ x: 1280 }"
|
||||
@rowClick="openUserDetails"
|
||||
@row-click="openUserDetails"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'sysOrigin'">
|
||||
@ -324,7 +339,7 @@ void loadData(true);
|
||||
</Space>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'createTime'">
|
||||
{{ formatDate(record.runningWater?.createTime || record.createTime) }}
|
||||
{{ formatRunningWaterTime(record.runningWater?.createTime || record.createTime) }}
|
||||
</template>
|
||||
</template>
|
||||
</Table>
|
||||
@ -340,8 +355,8 @@ void loadData(true);
|
||||
</Button>
|
||||
<span v-else>已加载全部</span>
|
||||
</div>
|
||||
</Card>
|
||||
</Page>
|
||||
</component>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@ -94,7 +94,7 @@ const summaryCards = computed(() => [
|
||||
},
|
||||
{
|
||||
label: '被邀请方注册礼包',
|
||||
value: formatRewardSummary(form.inviteeBindReward),
|
||||
value: `${formatRewardSummary(form.inviteeBindReward)} / H5 固定展示 +1000 金币`,
|
||||
},
|
||||
{
|
||||
label: '被邀请人充值阶梯',
|
||||
@ -404,7 +404,7 @@ const rewardSections = [
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card :bordered="false" class="config-card" title="被邀请方注册礼包">
|
||||
<Card :bordered="false" class="config-card" title="被邀请方注册礼包(可不填,H5 固定展示 +1000 金币)">
|
||||
<div class="reward-row">
|
||||
<div class="field amount-field">
|
||||
<div class="field-label">金币</div>
|
||||
|
||||
625
apps/src/views/resident-activity/room-turnover-reward.vue
Normal file
625
apps/src/views/resident-activity/room-turnover-reward.vue
Normal file
@ -0,0 +1,625 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, reactive, ref, watch } from 'vue';
|
||||
|
||||
import { Page } from '@vben/common-ui';
|
||||
import { useAccessStore } from '@vben/stores';
|
||||
|
||||
import {
|
||||
getRoomTurnoverRewardCurrentRank,
|
||||
getRoomTurnoverRewardConfig,
|
||||
pageRoomTurnoverRewardRecords,
|
||||
saveRoomTurnoverRewardConfig,
|
||||
} from '#/api/legacy/resident-activity';
|
||||
import RoomDetailsDrawer from '#/views/app-system/components/room-details-drawer.vue';
|
||||
import { getAllowedSysOrigins } from '#/views/system/shared';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Image,
|
||||
Input,
|
||||
InputNumber,
|
||||
Modal,
|
||||
Pagination,
|
||||
Select,
|
||||
Space,
|
||||
Switch,
|
||||
Table,
|
||||
Tag,
|
||||
message,
|
||||
} from 'antdv-next';
|
||||
|
||||
defineOptions({ name: 'ResidentRoomTurnoverRewardPage' });
|
||||
|
||||
function createLevel(level = 1, threshold = 100_000, rewardGold = 2_000) {
|
||||
return {
|
||||
enabled: true,
|
||||
id: null,
|
||||
level,
|
||||
rewardGold,
|
||||
turnoverThreshold: threshold,
|
||||
};
|
||||
}
|
||||
|
||||
function defaultLevels() {
|
||||
return [
|
||||
createLevel(1, 100_000, 2_000),
|
||||
createLevel(2, 500_000, 15_000),
|
||||
createLevel(3, 2_000_000, 80_000),
|
||||
createLevel(4, 5_000_000, 250_000),
|
||||
];
|
||||
}
|
||||
|
||||
const accessStore = useAccessStore();
|
||||
const sysOriginOptions = computed(() => {
|
||||
const options = getAllowedSysOrigins(accessStore.accessCodes || []);
|
||||
return options.length > 0 ? options : getAllowedSysOrigins([]);
|
||||
});
|
||||
|
||||
const selectedSysOrigin = ref('');
|
||||
const loading = ref(false);
|
||||
const saving = ref(false);
|
||||
|
||||
const recordLoading = ref(false);
|
||||
const recordTotal = ref(0);
|
||||
const recordList = ref<Array<Record<string, any>>>([]);
|
||||
const rankLoading = ref(false);
|
||||
const rankList = ref<Array<Record<string, any>>>([]);
|
||||
const rankMeta = reactive({
|
||||
cycleKey: '',
|
||||
dateNumber: '',
|
||||
periodEndAt: '',
|
||||
periodStartAt: '',
|
||||
timezone: 'Asia/Riyadh',
|
||||
});
|
||||
const roomDetailsOpen = ref(false);
|
||||
const activeRoomId = ref<number | string>('');
|
||||
|
||||
const form = reactive<Record<string, any>>({
|
||||
configured: false,
|
||||
enabled: false,
|
||||
id: 0,
|
||||
levelConfigs: defaultLevels(),
|
||||
sysOrigin: '',
|
||||
timezone: 'Asia/Riyadh',
|
||||
updateTime: '',
|
||||
});
|
||||
|
||||
const recordQuery = reactive({
|
||||
cursor: 1,
|
||||
cycleKey: '',
|
||||
limit: 20,
|
||||
status: '',
|
||||
});
|
||||
|
||||
const rankQuery = reactive({
|
||||
limit: 20,
|
||||
});
|
||||
|
||||
const statusOptions = [
|
||||
{ label: '全部状态', value: '' },
|
||||
{ label: '成功', value: 'SUCCESS' },
|
||||
{ label: '失败', value: 'FAILED' },
|
||||
{ label: '待发放', value: 'PENDING' },
|
||||
];
|
||||
|
||||
const levelColumns = [
|
||||
{ dataIndex: 'enabled', key: 'enabled', title: '启用', width: 90 },
|
||||
{ dataIndex: 'level', key: 'level', title: '等级', width: 110 },
|
||||
{ dataIndex: 'turnoverThreshold', key: 'turnoverThreshold', title: '达到流水', width: 220 },
|
||||
{ dataIndex: 'rewardGold', key: 'rewardGold', title: '奖励房主金币', width: 220 },
|
||||
{ dataIndex: 'actions', key: 'actions', title: '操作', width: 100 },
|
||||
];
|
||||
|
||||
const recordColumns = [
|
||||
{ dataIndex: 'cycleKey', key: 'cycleKey', title: '周期', width: 130 },
|
||||
{ dataIndex: 'room', key: 'room', title: '房间', width: 280 },
|
||||
{ dataIndex: 'ownerUserId', key: 'ownerUserId', title: '房主ID', width: 150 },
|
||||
{ dataIndex: 'turnoverAmount', key: 'turnoverAmount', title: '流水', width: 140 },
|
||||
{ dataIndex: 'level', key: 'level', title: '等级', width: 100 },
|
||||
{ dataIndex: 'rewardGold', key: 'rewardGold', title: '奖励金币', width: 140 },
|
||||
{ dataIndex: 'status', key: 'status', title: '状态', width: 110 },
|
||||
{ dataIndex: 'retryCount', key: 'retryCount', title: '重试次数', width: 110 },
|
||||
{ dataIndex: 'sentAt', key: 'sentAt', title: '发放时间', width: 180 },
|
||||
{ dataIndex: 'lastError', key: 'lastError', title: '失败原因', width: 260 },
|
||||
];
|
||||
|
||||
const rankColumns = [
|
||||
{ dataIndex: 'rank', key: 'rank', title: '排名', width: 90 },
|
||||
{ dataIndex: 'room', key: 'room', title: '房间', width: 280 },
|
||||
{ dataIndex: 'ownerUserId', key: 'ownerUserId', title: '房主ID', width: 150 },
|
||||
{ dataIndex: 'countryName', key: 'countryName', title: '国家', width: 120 },
|
||||
{ dataIndex: 'turnoverAmount', key: 'turnoverAmount', title: '当前流水', width: 150 },
|
||||
{ dataIndex: 'matchedLevel', key: 'matchedLevel', title: '命中等级', width: 120 },
|
||||
{ dataIndex: 'turnoverThreshold', key: 'turnoverThreshold', title: '命中门槛', width: 150 },
|
||||
{ dataIndex: 'expectedRewardGold', key: 'expectedRewardGold', title: '预计奖励金币', width: 150 },
|
||||
];
|
||||
|
||||
const enabledLevelCount = computed(() =>
|
||||
(form.levelConfigs || []).filter((item: Record<string, any>) => item.enabled !== false).length,
|
||||
);
|
||||
|
||||
function normalizeConfig(result: Record<string, any> | null | 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.timezone = String(value.timezone || 'Asia/Riyadh');
|
||||
form.updateTime = String(value.updateTime || '');
|
||||
const levels = Array.isArray(value.levelConfigs) && value.levelConfigs.length > 0
|
||||
? value.levelConfigs
|
||||
: defaultLevels();
|
||||
form.levelConfigs = levels
|
||||
.map((item: Record<string, any>) => ({
|
||||
enabled: item.enabled !== false,
|
||||
id: item.id ?? null,
|
||||
level: Number(item.level || 0),
|
||||
rewardGold: Number(item.rewardGold || 0),
|
||||
turnoverThreshold: Number(item.turnoverThreshold || 0),
|
||||
}))
|
||||
.sort((left: Record<string, any>, right: Record<string, any>) =>
|
||||
Number(left.turnoverThreshold || 0) - Number(right.turnoverThreshold || 0),
|
||||
);
|
||||
}
|
||||
|
||||
async function loadConfig() {
|
||||
if (!selectedSysOrigin.value) {
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
try {
|
||||
const result = await getRoomTurnoverRewardConfig(selectedSysOrigin.value);
|
||||
normalizeConfig(result);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadRecords(reset = false) {
|
||||
if (!selectedSysOrigin.value) {
|
||||
recordList.value = [];
|
||||
recordTotal.value = 0;
|
||||
return;
|
||||
}
|
||||
if (reset) {
|
||||
recordQuery.cursor = 1;
|
||||
}
|
||||
recordLoading.value = true;
|
||||
try {
|
||||
const result = await pageRoomTurnoverRewardRecords({
|
||||
...recordQuery,
|
||||
sysOrigin: selectedSysOrigin.value,
|
||||
});
|
||||
recordList.value = Array.isArray(result.records) ? result.records : [];
|
||||
recordTotal.value = Number(result.total || 0);
|
||||
} finally {
|
||||
recordLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadCurrentRank() {
|
||||
if (!selectedSysOrigin.value) {
|
||||
rankList.value = [];
|
||||
Object.assign(rankMeta, {
|
||||
cycleKey: '',
|
||||
dateNumber: '',
|
||||
periodEndAt: '',
|
||||
periodStartAt: '',
|
||||
timezone: 'Asia/Riyadh',
|
||||
});
|
||||
return;
|
||||
}
|
||||
rankLoading.value = true;
|
||||
try {
|
||||
const result = await getRoomTurnoverRewardCurrentRank({
|
||||
limit: rankQuery.limit,
|
||||
sysOrigin: selectedSysOrigin.value,
|
||||
});
|
||||
rankList.value = Array.isArray(result.records) ? result.records : [];
|
||||
rankMeta.cycleKey = String(result.cycleKey || '');
|
||||
rankMeta.dateNumber = String(result.dateNumber || '');
|
||||
rankMeta.periodEndAt = String(result.periodEndAt || '');
|
||||
rankMeta.periodStartAt = String(result.periodStartAt || '');
|
||||
rankMeta.timezone = String(result.timezone || 'Asia/Riyadh');
|
||||
} finally {
|
||||
rankLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function reloadAll() {
|
||||
await Promise.all([loadConfig(), loadCurrentRank(), loadRecords(true)]);
|
||||
}
|
||||
|
||||
function addLevel() {
|
||||
const levels = form.levelConfigs as Array<Record<string, any>>;
|
||||
const maxLevel = Math.max(0, ...levels.map((item) => Number(item.level || 0)));
|
||||
const maxThreshold = Math.max(0, ...levels.map((item) => Number(item.turnoverThreshold || 0)));
|
||||
levels.push(createLevel(maxLevel + 1, maxThreshold + 100_000, 0));
|
||||
}
|
||||
|
||||
function removeLevel(index: number) {
|
||||
(form.levelConfigs as Array<Record<string, any>>).splice(index, 1);
|
||||
}
|
||||
|
||||
function validateBeforeSave() {
|
||||
const levels = form.levelConfigs as Array<Record<string, any>>;
|
||||
if (levels.length === 0) {
|
||||
message.warning('请至少配置一个奖励等级');
|
||||
return false;
|
||||
}
|
||||
const seenLevels = new Set<string>();
|
||||
const seenThresholds = new Set<string>();
|
||||
for (const item of levels) {
|
||||
const level = Number(item.level || 0);
|
||||
const threshold = Number(item.turnoverThreshold || 0);
|
||||
const rewardGold = Number(item.rewardGold || 0);
|
||||
if (level <= 0 || threshold <= 0 || rewardGold <= 0) {
|
||||
message.warning('等级、流水门槛和奖励金币都必须大于 0');
|
||||
return false;
|
||||
}
|
||||
if (seenLevels.has(String(level))) {
|
||||
message.warning('奖励等级不能重复');
|
||||
return false;
|
||||
}
|
||||
if (seenThresholds.has(String(threshold))) {
|
||||
message.warning('流水门槛不能重复');
|
||||
return false;
|
||||
}
|
||||
seenLevels.add(String(level));
|
||||
seenThresholds.add(String(threshold));
|
||||
}
|
||||
if (form.enabled && enabledLevelCount.value === 0) {
|
||||
message.warning('启用配置时至少要启用一个奖励等级');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async function submitForm() {
|
||||
if (!validateBeforeSave()) {
|
||||
return;
|
||||
}
|
||||
saving.value = true;
|
||||
try {
|
||||
await saveRoomTurnoverRewardConfig({
|
||||
enabled: Boolean(form.enabled),
|
||||
id: form.id || 0,
|
||||
levelConfigs: (form.levelConfigs || []).map((item: Record<string, any>) => ({
|
||||
enabled: item.enabled !== false,
|
||||
id: item.id || 0,
|
||||
level: Number(item.level || 0),
|
||||
rewardGold: Number(item.rewardGold || 0),
|
||||
turnoverThreshold: Number(item.turnoverThreshold || 0),
|
||||
})),
|
||||
sysOrigin: selectedSysOrigin.value,
|
||||
timezone: String(form.timezone || '').trim(),
|
||||
});
|
||||
message.success('保存成功');
|
||||
await reloadAll();
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function statusColor(status: string) {
|
||||
switch (status) {
|
||||
case 'SUCCESS':
|
||||
return 'success';
|
||||
case 'FAILED':
|
||||
return 'error';
|
||||
case 'PENDING':
|
||||
return 'processing';
|
||||
default:
|
||||
return 'default';
|
||||
}
|
||||
}
|
||||
|
||||
function formatNumber(value: number | string | undefined) {
|
||||
const numeric = Number(value || 0);
|
||||
return Number.isFinite(numeric) ? numeric.toLocaleString() : '0';
|
||||
}
|
||||
|
||||
function handleRecordPageChange(page: number, pageSize: number) {
|
||||
recordQuery.cursor = page;
|
||||
recordQuery.limit = pageSize;
|
||||
void loadRecords();
|
||||
}
|
||||
|
||||
function handleRankLimitChange() {
|
||||
void loadCurrentRank();
|
||||
}
|
||||
|
||||
function openRoomDetails(roomId?: number | string) {
|
||||
if (!roomId) {
|
||||
return;
|
||||
}
|
||||
activeRoomId.value = roomId;
|
||||
roomDetailsOpen.value = true;
|
||||
}
|
||||
|
||||
function confirmRemoveLevel(index: number) {
|
||||
Modal.confirm({
|
||||
onOk: () => removeLevel(index),
|
||||
title: '确认删除该奖励等级?',
|
||||
});
|
||||
}
|
||||
|
||||
watch(
|
||||
sysOriginOptions,
|
||||
(options) => {
|
||||
if (!selectedSysOrigin.value) {
|
||||
selectedSysOrigin.value = String(options[0]?.value || '');
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
watch(
|
||||
selectedSysOrigin,
|
||||
(value) => {
|
||||
if (value) {
|
||||
void reloadAll();
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page auto-content-height>
|
||||
<Card :bordered="false" title="房间流水奖励配置">
|
||||
<div class="toolbar">
|
||||
<Select
|
||||
v-model:value="selectedSysOrigin"
|
||||
:options="sysOriginOptions"
|
||||
option-filter-prop="label"
|
||||
option-label-prop="label"
|
||||
placeholder="请选择系统"
|
||||
show-search
|
||||
style="width: 180px"
|
||||
/>
|
||||
<Tag :color="form.enabled ? 'success' : 'default'">
|
||||
{{ form.enabled ? '已启用' : '已关闭' }}
|
||||
</Tag>
|
||||
<Tag color="processing">按达到的最高档发放一次</Tag>
|
||||
<Button :loading="loading" @click="reloadAll">刷新</Button>
|
||||
</div>
|
||||
|
||||
<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="levelColumns"
|
||||
:data-source="form.levelConfigs"
|
||||
:loading="loading"
|
||||
:pagination="false"
|
||||
row-key="level"
|
||||
:scroll="{ x: 760 }"
|
||||
>
|
||||
<template #bodyCell="{ column, index, record }">
|
||||
<template v-if="column.key === 'enabled'">
|
||||
<Switch v-model:checked="record.enabled" />
|
||||
</template>
|
||||
<template v-else-if="column.key === 'level'">
|
||||
<InputNumber v-model:value="record.level" :min="1" :precision="0" style="width: 90px" />
|
||||
</template>
|
||||
<template v-else-if="column.key === 'turnoverThreshold'">
|
||||
<InputNumber
|
||||
v-model:value="record.turnoverThreshold"
|
||||
:min="1"
|
||||
:precision="0"
|
||||
style="width: 180px"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'rewardGold'">
|
||||
<InputNumber
|
||||
v-model:value="record.rewardGold"
|
||||
:min="1"
|
||||
:precision="0"
|
||||
style="width: 180px"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'actions'">
|
||||
<Button danger type="link" @click="confirmRemoveLevel(index)">删除</Button>
|
||||
</template>
|
||||
</template>
|
||||
</Table>
|
||||
|
||||
<div class="actions">
|
||||
<Button @click="addLevel">新增等级</Button>
|
||||
<Button :loading="saving" type="primary" @click="submitForm">保存配置</Button>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card :bordered="false" class="record-card" title="当前房间排行榜">
|
||||
<div class="toolbar">
|
||||
<Tag color="processing">{{ rankMeta.cycleKey || '-' }}</Tag>
|
||||
<Tag>{{ rankMeta.timezone || '-' }}</Tag>
|
||||
<span class="sub-text">
|
||||
{{ rankMeta.periodStartAt || '-' }} - {{ rankMeta.periodEndAt || '-' }}
|
||||
</span>
|
||||
<span class="sub-text">统计周:{{ rankMeta.dateNumber || '-' }}</span>
|
||||
<Select
|
||||
v-model:value="rankQuery.limit"
|
||||
:options="[
|
||||
{ label: 'Top 20', value: 20 },
|
||||
{ label: 'Top 50', value: 50 },
|
||||
{ label: 'Top 100', value: 100 },
|
||||
]"
|
||||
style="width: 120px"
|
||||
@change="handleRankLimitChange"
|
||||
/>
|
||||
<Button :loading="rankLoading" type="primary" @click="loadCurrentRank">刷新排行</Button>
|
||||
</div>
|
||||
|
||||
<Table
|
||||
:columns="rankColumns"
|
||||
:data-source="rankList"
|
||||
:loading="rankLoading"
|
||||
:pagination="false"
|
||||
row-key="roomId"
|
||||
:scroll="{ x: 1410 }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'room'">
|
||||
<div class="room-cell">
|
||||
<Image :preview="false" :src="record.roomCover" class="room-cover" />
|
||||
<div class="room-copy">
|
||||
<Button type="link" @click="openRoomDetails(record.roomId)">
|
||||
{{ record.roomName || '-' }}
|
||||
</Button>
|
||||
<div class="sub-text">{{ record.roomAccount || record.roomId || '-' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'turnoverAmount'">
|
||||
{{ formatNumber(record.turnoverAmount) }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'matchedLevel'">
|
||||
<Tag v-if="record.matchedLevel" color="success">Lv {{ record.matchedLevel }}</Tag>
|
||||
<Tag v-else>未命中</Tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'turnoverThreshold'">
|
||||
{{ record.turnoverThreshold ? formatNumber(record.turnoverThreshold) : '-' }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'expectedRewardGold'">
|
||||
{{ record.expectedRewardGold ? formatNumber(record.expectedRewardGold) : '-' }}
|
||||
</template>
|
||||
</template>
|
||||
</Table>
|
||||
</Card>
|
||||
|
||||
<Card :bordered="false" class="record-card" title="发奖记录">
|
||||
<div class="toolbar">
|
||||
<Input
|
||||
v-model:value="recordQuery.cycleKey"
|
||||
allow-clear
|
||||
placeholder="周期 key"
|
||||
style="width: 180px"
|
||||
@press-enter="loadRecords(true)"
|
||||
/>
|
||||
<Select
|
||||
v-model:value="recordQuery.status"
|
||||
:options="statusOptions"
|
||||
style="width: 140px"
|
||||
@change="loadRecords(true)"
|
||||
/>
|
||||
<Button :loading="recordLoading" type="primary" @click="loadRecords(true)">查询</Button>
|
||||
</div>
|
||||
|
||||
<Table
|
||||
:columns="recordColumns"
|
||||
:data-source="recordList"
|
||||
:loading="recordLoading"
|
||||
:pagination="false"
|
||||
row-key="id"
|
||||
:scroll="{ x: 1560 }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'room'">
|
||||
<div class="room-cell">
|
||||
<Image :preview="false" :src="record.roomCover" class="room-cover" />
|
||||
<div class="room-copy">
|
||||
<Button type="link" @click="openRoomDetails(record.roomId)">
|
||||
{{ record.roomName || '-' }}
|
||||
</Button>
|
||||
<div class="sub-text">{{ record.roomAccount || record.roomId || '-' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'turnoverAmount'">
|
||||
{{ formatNumber(record.turnoverAmount) }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'rewardGold'">
|
||||
{{ formatNumber(record.rewardGold) }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'status'">
|
||||
<Tag :color="statusColor(record.status)">{{ record.status || '-' }}</Tag>
|
||||
</template>
|
||||
</template>
|
||||
</Table>
|
||||
|
||||
<div v-if="recordTotal > 0" class="pager">
|
||||
<Pagination
|
||||
:current="recordQuery.cursor"
|
||||
:page-size="recordQuery.limit"
|
||||
:total="recordTotal"
|
||||
show-size-changer
|
||||
@change="handleRecordPageChange"
|
||||
@showSizeChange="handleRecordPageChange"
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<RoomDetailsDrawer
|
||||
:open="roomDetailsOpen"
|
||||
:room-id="activeRoomId"
|
||||
@close="roomDetailsOpen = false"
|
||||
/>
|
||||
</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;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.record-card {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.room-cell {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.room-cover {
|
||||
border-radius: 8px;
|
||||
height: 48px;
|
||||
width: 48px;
|
||||
}
|
||||
|
||||
.room-copy {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.pager {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
@ -88,7 +88,7 @@ const editorLoading = ref(false);
|
||||
const saving = ref(false);
|
||||
const editorGiftLoading = ref(false);
|
||||
const editorReadonly = ref(false);
|
||||
const editorTitle = ref('新建未来周配置');
|
||||
const editorTitle = ref('新建周配置');
|
||||
const groupPickerOpen = ref(false);
|
||||
const rangeDate = ref<[string, string] | null>(null);
|
||||
|
||||
@ -111,14 +111,14 @@ const configQuery = reactive({
|
||||
});
|
||||
|
||||
const snapshotQuery = reactive({
|
||||
configId: undefined as number | undefined,
|
||||
configId: undefined as number | string | undefined,
|
||||
cursor: 1,
|
||||
cycleKey: '',
|
||||
limit: 10,
|
||||
});
|
||||
|
||||
const rewardQuery = reactive({
|
||||
configId: undefined as number | undefined,
|
||||
configId: undefined as number | string | undefined,
|
||||
cursor: 1,
|
||||
cycleKey: '',
|
||||
limit: 10,
|
||||
@ -246,7 +246,7 @@ function resetEditorForCreate() {
|
||||
assignForm(createEmptyForm(selectedSysOrigin.value));
|
||||
rangeDate.value = null;
|
||||
editorReadonly.value = false;
|
||||
editorTitle.value = '新建未来周配置';
|
||||
editorTitle.value = '新建周配置';
|
||||
}
|
||||
|
||||
async function loadGiftOptions(sysOrigin: string) {
|
||||
@ -284,7 +284,7 @@ async function loadConfigList(reset = false) {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadConfigDetail(id: number) {
|
||||
async function loadConfigDetail(id: number | string) {
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
@ -461,15 +461,15 @@ async function submitForm() {
|
||||
endAt: form.endAt,
|
||||
giftConfigs: (form.giftConfigs || []).map((item: Record<string, any>) => ({
|
||||
giftCandy: Number(item.giftCandy || 0),
|
||||
giftId: Number(item.giftId || 0),
|
||||
giftId: item.giftId || 0,
|
||||
giftName: String(item.giftName || ''),
|
||||
giftPhoto: String(item.giftPhoto || ''),
|
||||
sort: Number(item.sort || 0),
|
||||
})),
|
||||
id: Number(form.id || 0),
|
||||
id: form.id || 0,
|
||||
rewardConfigs: (form.rewardConfigs || []).map((item: Record<string, any>) => ({
|
||||
rank: Number(item.rank || 0),
|
||||
rewardGroupId: Number(item.rewardGroupId || 0),
|
||||
rewardGroupId: item.rewardGroupId || 0,
|
||||
rewardGroupName: String(item.rewardGroupName || ''),
|
||||
})),
|
||||
startAt: form.startAt,
|
||||
@ -491,10 +491,10 @@ function openCreateModal() {
|
||||
}
|
||||
|
||||
async function openConfigModal(record: Record<string, any>, readOnly = !record.editable) {
|
||||
editorTitle.value = readOnly ? '查看周配置' : '编辑未来周配置';
|
||||
editorTitle.value = readOnly ? '查看周配置' : '编辑周配置';
|
||||
editorReadonly.value = readOnly;
|
||||
editorOpen.value = true;
|
||||
await loadConfigDetail(Number(record.id || 0));
|
||||
await loadConfigDetail(record.id || 0);
|
||||
}
|
||||
|
||||
function handleConfigPageChange(page: number, pageSize: number) {
|
||||
@ -560,11 +560,11 @@ watch(
|
||||
watch(
|
||||
configList,
|
||||
(list) => {
|
||||
const configIds = new Set(list.map((item) => Number(item.id || 0)));
|
||||
if (snapshotQuery.configId && !configIds.has(Number(snapshotQuery.configId))) {
|
||||
const configIds = new Set(list.map((item) => String(item.id || '')));
|
||||
if (snapshotQuery.configId && !configIds.has(String(snapshotQuery.configId))) {
|
||||
snapshotQuery.configId = undefined;
|
||||
}
|
||||
if (rewardQuery.configId && !configIds.has(Number(rewardQuery.configId))) {
|
||||
if (rewardQuery.configId && !configIds.has(String(rewardQuery.configId))) {
|
||||
rewardQuery.configId = undefined;
|
||||
}
|
||||
},
|
||||
@ -585,8 +585,8 @@ watch(
|
||||
show-search
|
||||
style="width: 180px"
|
||||
/>
|
||||
<Tag color="processing">仅未来周可编辑</Tag>
|
||||
<Button type="primary" @click="openCreateModal">新建未来周配置</Button>
|
||||
<Tag color="processing">当前周无配置可补建,已开始配置只读</Tag>
|
||||
<Button type="primary" @click="openCreateModal">新建周配置</Button>
|
||||
</Space>
|
||||
|
||||
<Table
|
||||
|
||||
@ -66,12 +66,41 @@ function handleCheck(keys: any, event: Record<string, any>) {
|
||||
halfCheckedKeys.value = event?.halfCheckedKeys || keys?.halfChecked || [];
|
||||
}
|
||||
|
||||
function collectMenuIdsWithAncestors(keys: Array<number | string>) {
|
||||
const idMap = new Map<string, number | string>();
|
||||
const parentMap = new Map<string, string>();
|
||||
|
||||
for (const item of menusList.value) {
|
||||
const id = String(item.id);
|
||||
const parentId = String(item.parentId || '');
|
||||
idMap.set(id, item.id);
|
||||
if (parentId && parentId !== '0') {
|
||||
parentMap.set(id, parentId);
|
||||
}
|
||||
}
|
||||
|
||||
const result = new Set<string>();
|
||||
const append = (key: number | string) => {
|
||||
let current = String(key);
|
||||
while (current) {
|
||||
result.add(current);
|
||||
current = parentMap.get(current) || '';
|
||||
}
|
||||
};
|
||||
|
||||
keys.forEach(append);
|
||||
return [...result].map((key) => idMap.get(key) ?? key);
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
loading.value = true;
|
||||
try {
|
||||
await updateRoleMenus({
|
||||
id: props.roleId,
|
||||
menuIds: [...new Set([...checkedKeys.value, ...halfCheckedKeys.value])],
|
||||
menuIds: collectMenuIdsWithAncestors([
|
||||
...checkedKeys.value,
|
||||
...halfCheckedKeys.value,
|
||||
]),
|
||||
});
|
||||
message.success('保存成功');
|
||||
emit('success');
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user