vip
This commit is contained in:
parent
c35af900cd
commit
1f453c3f0b
@ -7,6 +7,8 @@ const SPECIFIED_GIFT_WEEKLY_RANK_BASE =
|
||||
const SIGN_IN_REWARD_BASE = '/go/resident-activity/sign-in-reward';
|
||||
const ROOM_TURNOVER_REWARD_BASE =
|
||||
'/go/resident-activity/room-turnover-reward';
|
||||
const VIP_BASE = '/go/resident-activity/vip';
|
||||
const RECHARGE_REWARD_BASE = '/go/resident-activity/recharge-reward';
|
||||
|
||||
export async function getResidentInviteConfig(sysOrigin: string) {
|
||||
return requestClient.get<Record<string, any>>('/resident-activity/invite/config', {
|
||||
@ -73,6 +75,41 @@ export async function pageResidentSignInRewardRecords(params: Record<string, any
|
||||
});
|
||||
}
|
||||
|
||||
export async function getResidentVipConfig(sysOrigin: string) {
|
||||
return requestClient.get<Record<string, any>>(`${VIP_BASE}/config`, {
|
||||
params: { sysOrigin },
|
||||
});
|
||||
}
|
||||
|
||||
export async function saveResidentVipConfig(data: Record<string, any>) {
|
||||
return requestClient.post<Record<string, any>>(`${VIP_BASE}/config/save`, data);
|
||||
}
|
||||
|
||||
export async function pageResidentVipOrders(params: Record<string, any>) {
|
||||
return requestClient.get<Record<string, any>>(`${VIP_BASE}/order/page`, {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
export async function pageResidentVipUserStates(params: Record<string, any>) {
|
||||
return requestClient.get<Record<string, any>>(`${VIP_BASE}/user-state/page`, {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
export async function getResidentRechargeRewardConfig(sysOrigin: string) {
|
||||
return requestClient.get<Record<string, any>>(`${RECHARGE_REWARD_BASE}/config`, {
|
||||
params: { sysOrigin },
|
||||
});
|
||||
}
|
||||
|
||||
export async function saveResidentRechargeRewardConfig(data: Record<string, any>) {
|
||||
return requestClient.post<Record<string, any>>(
|
||||
`${RECHARGE_REWARD_BASE}/config/save`,
|
||||
data,
|
||||
);
|
||||
}
|
||||
|
||||
export async function pageSpecifiedGiftWeeklyRankConfigs(params: Record<string, any>) {
|
||||
return requestClient.get<LegacyPageResult<Record<string, any>>>(
|
||||
`${SPECIFIED_GIFT_WEEKLY_RANK_BASE}/config/page`,
|
||||
|
||||
@ -17,6 +17,18 @@ const routes: RouteRecordRaw[] = [
|
||||
component: () => import('#/views/resident-activity/sign-in-reward-config.vue'),
|
||||
meta: { title: '签到奖励配置' },
|
||||
},
|
||||
{
|
||||
name: 'ResidentVipConfig',
|
||||
path: 'vip/config',
|
||||
component: () => import('#/views/resident-activity/vip-config.vue'),
|
||||
meta: { title: 'VIP' },
|
||||
},
|
||||
{
|
||||
name: 'ResidentRechargeRewardConfig',
|
||||
path: 'recharge-reward/config',
|
||||
component: () => import('#/views/resident-activity/recharge-reward-config.vue'),
|
||||
meta: { title: '充值奖励配置' },
|
||||
},
|
||||
{
|
||||
name: 'ResidentRegisterRewardConfig',
|
||||
path: 'register-reward/config',
|
||||
|
||||
451
apps/src/views/resident-activity/recharge-reward-config.vue
Normal file
451
apps/src/views/resident-activity/recharge-reward-config.vue
Normal file
@ -0,0 +1,451 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, reactive, ref, watch } from 'vue';
|
||||
|
||||
import { Page } from '@vben/common-ui';
|
||||
import { useAccessStore } from '@vben/stores';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Input,
|
||||
InputNumber,
|
||||
message,
|
||||
Modal,
|
||||
Select,
|
||||
Space,
|
||||
Spin,
|
||||
Switch,
|
||||
Table,
|
||||
Tag,
|
||||
} from 'antdv-next';
|
||||
|
||||
import {
|
||||
getResidentRechargeRewardConfig,
|
||||
saveResidentRechargeRewardConfig,
|
||||
} from '#/api/legacy/resident-activity';
|
||||
import ActivityResourceGroupSelectDrawer from '#/views/props/components/activity-resource-group-select-drawer.vue';
|
||||
import RewardRow from '#/views/props/components/reward-row.vue';
|
||||
import { getAllowedSysOrigins } from '#/views/system/shared';
|
||||
|
||||
defineOptions({ name: 'ResidentRechargeRewardConfigPage' });
|
||||
|
||||
let localRowId = 0;
|
||||
|
||||
function createRowKey(seed?: number | string) {
|
||||
localRowId += 1;
|
||||
return `recharge-reward-${seed || localRowId}-${localRowId}`;
|
||||
}
|
||||
|
||||
function createLevel(level = 1, rechargeAmount = 100, rewardGold = 0) {
|
||||
return {
|
||||
enabled: true,
|
||||
id: null,
|
||||
level,
|
||||
rechargeAmount,
|
||||
rechargeAmountCents: 0,
|
||||
rewardGold,
|
||||
rewardGroupId: null,
|
||||
rewardGroupName: '',
|
||||
rewardItems: [] as Array<Record<string, any>>,
|
||||
rowKey: createRowKey(),
|
||||
};
|
||||
}
|
||||
|
||||
function defaultLevels() {
|
||||
return [
|
||||
createLevel(1, 100, 0),
|
||||
createLevel(2, 500, 0),
|
||||
createLevel(3, 1000, 0),
|
||||
];
|
||||
}
|
||||
|
||||
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 groupPickerOpen = ref(false);
|
||||
const pickerLevelIndex = ref(-1);
|
||||
|
||||
const form = reactive<Record<string, any>>({
|
||||
configured: false,
|
||||
enabled: false,
|
||||
id: 0,
|
||||
levelConfigs: defaultLevels(),
|
||||
sysOrigin: '',
|
||||
timezone: 'Asia/Riyadh',
|
||||
updateTime: '',
|
||||
});
|
||||
|
||||
const levelColumns = [
|
||||
{ dataIndex: 'enabled', key: 'enabled', title: '启用', width: 90 },
|
||||
{ dataIndex: 'level', key: 'level', title: '档位', width: 110 },
|
||||
{ dataIndex: 'rechargeAmount', key: 'rechargeAmount', title: '充值金额', width: 180 },
|
||||
{ dataIndex: 'rewardGold', key: 'rewardGold', title: '奖励金币', width: 180 },
|
||||
{ dataIndex: 'rewardGroup', key: 'rewardGroup', title: '奖励组', width: 360 },
|
||||
{ dataIndex: 'actions', key: 'actions', title: '操作', width: 100 },
|
||||
];
|
||||
|
||||
const enabledLevelCount = computed(() =>
|
||||
(form.levelConfigs || []).filter((item: Record<string, any>) => item.enabled !== false).length,
|
||||
);
|
||||
|
||||
const statusMeta = computed(() => {
|
||||
if (!form.configured) {
|
||||
return { color: 'processing', text: '未配置' };
|
||||
}
|
||||
if (form.enabled) {
|
||||
return { color: 'success', text: '已启用' };
|
||||
}
|
||||
return { color: 'default', text: '已关闭' };
|
||||
});
|
||||
|
||||
function toAmountCents(value: number | string | undefined) {
|
||||
const numeric = Number(value || 0);
|
||||
if (!Number.isFinite(numeric)) {
|
||||
return 0;
|
||||
}
|
||||
return Math.round(numeric * 100);
|
||||
}
|
||||
|
||||
function formatAmount(value: number | string | undefined) {
|
||||
const numeric = Number(value || 0);
|
||||
return Number.isFinite(numeric) ? numeric.toFixed(2) : '0.00';
|
||||
}
|
||||
|
||||
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.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>, index: number) => {
|
||||
const rechargeAmount = Number(
|
||||
item.rechargeAmount ?? Number(item.rechargeAmountCents || 0) / 100,
|
||||
);
|
||||
return {
|
||||
enabled: item.enabled !== false,
|
||||
id: item.id ?? null,
|
||||
level: Number(item.level || index + 1),
|
||||
rechargeAmount: Number.isFinite(rechargeAmount) ? rechargeAmount : 0,
|
||||
rechargeAmountCents: Number(item.rechargeAmountCents || 0),
|
||||
rewardGold: Number(item.rewardGold || 0),
|
||||
rewardGroupId: item.rewardGroupId ?? null,
|
||||
rewardGroupName: String(item.rewardGroupName || ''),
|
||||
rewardItems: Array.isArray(item.rewardItems) ? item.rewardItems : [],
|
||||
rowKey: createRowKey(item.id || index),
|
||||
};
|
||||
})
|
||||
.toSorted((left: Record<string, any>, right: Record<string, any>) =>
|
||||
toAmountCents(left.rechargeAmount) - toAmountCents(right.rechargeAmount),
|
||||
);
|
||||
}
|
||||
|
||||
async function loadConfig() {
|
||||
if (!selectedSysOrigin.value) {
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
try {
|
||||
const result = await getResidentRechargeRewardConfig(selectedSysOrigin.value);
|
||||
normalizeConfig(result);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function addLevel() {
|
||||
const levels = form.levelConfigs as Array<Record<string, any>>;
|
||||
const maxLevel = Math.max(0, ...levels.map((item) => Number(item.level || 0)));
|
||||
const maxAmount = Math.max(0, ...levels.map((item) => Number(item.rechargeAmount || 0)));
|
||||
levels.push(createLevel(maxLevel + 1, maxAmount + 100, 0));
|
||||
}
|
||||
|
||||
function removeLevel(index: number) {
|
||||
(form.levelConfigs as Array<Record<string, any>>).splice(index, 1);
|
||||
}
|
||||
|
||||
function confirmRemoveLevel(index: number) {
|
||||
Modal.confirm({
|
||||
onOk: () => removeLevel(index),
|
||||
title: '确认删除该充值奖励档位?',
|
||||
});
|
||||
}
|
||||
|
||||
function openRewardPicker(index: number) {
|
||||
pickerLevelIndex.value = index;
|
||||
groupPickerOpen.value = true;
|
||||
}
|
||||
|
||||
function clearRewardGroup(record: Record<string, any>) {
|
||||
record.rewardGroupId = null;
|
||||
record.rewardGroupName = '';
|
||||
record.rewardItems = [];
|
||||
}
|
||||
|
||||
function handleRewardGroupSelect(row: Record<string, any>) {
|
||||
const levels = form.levelConfigs as Array<Record<string, any>>;
|
||||
const target = levels[pickerLevelIndex.value];
|
||||
if (!target) {
|
||||
groupPickerOpen.value = false;
|
||||
return;
|
||||
}
|
||||
target.rewardGroupId = row.id;
|
||||
target.rewardGroupName = row.name;
|
||||
target.rewardItems = Array.isArray(row.rewardConfigList) ? row.rewardConfigList : [];
|
||||
groupPickerOpen.value = false;
|
||||
}
|
||||
|
||||
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 seenAmounts = new Set<string>();
|
||||
for (const item of levels) {
|
||||
const level = Number(item.level || 0);
|
||||
const amountCents = toAmountCents(item.rechargeAmount);
|
||||
const rewardGold = Number(item.rewardGold || 0);
|
||||
const rewardGroupId = Number(item.rewardGroupId || 0);
|
||||
if (level <= 0) {
|
||||
message.warning('档位必须大于 0');
|
||||
return false;
|
||||
}
|
||||
if (amountCents <= 0) {
|
||||
message.warning('充值金额必须大于 0');
|
||||
return false;
|
||||
}
|
||||
if (rewardGold <= 0 && rewardGroupId <= 0) {
|
||||
message.warning('每个档位至少要配置金币或奖励组');
|
||||
return false;
|
||||
}
|
||||
if (seenLevels.has(String(level))) {
|
||||
message.warning('档位不能重复');
|
||||
return false;
|
||||
}
|
||||
if (seenAmounts.has(String(amountCents))) {
|
||||
message.warning('充值金额不能重复');
|
||||
return false;
|
||||
}
|
||||
seenLevels.add(String(level));
|
||||
seenAmounts.add(String(amountCents));
|
||||
}
|
||||
if (form.enabled && enabledLevelCount.value === 0) {
|
||||
message.warning('启用配置时至少要启用一个充值奖励档位');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async function submitForm() {
|
||||
if (!validateBeforeSave()) {
|
||||
return;
|
||||
}
|
||||
saving.value = true;
|
||||
try {
|
||||
await saveResidentRechargeRewardConfig({
|
||||
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),
|
||||
rechargeAmount: formatAmount(item.rechargeAmount),
|
||||
rechargeAmountCents: toAmountCents(item.rechargeAmount),
|
||||
rewardGold: Number(item.rewardGold || 0),
|
||||
rewardGroupId: item.rewardGroupId ?? null,
|
||||
rewardGroupName: String(item.rewardGroupName || ''),
|
||||
})),
|
||||
sysOrigin: selectedSysOrigin.value,
|
||||
timezone: String(form.timezone || '').trim(),
|
||||
});
|
||||
message.success('保存成功');
|
||||
await loadConfig();
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
sysOriginOptions,
|
||||
(options) => {
|
||||
if (!selectedSysOrigin.value) {
|
||||
selectedSysOrigin.value = String(options[0]?.value || '');
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
watch(
|
||||
selectedSysOrigin,
|
||||
(value) => {
|
||||
if (value) {
|
||||
void loadConfig();
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page auto-content-height>
|
||||
<Spin :spinning="loading">
|
||||
<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="statusMeta.color">{{ statusMeta.text }}</Tag>
|
||||
<Tag color="processing">按自然月累计充值</Tag>
|
||||
<Button :loading="loading" @click="loadConfig">刷新</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="rowKey"
|
||||
:scroll="{ x: 1020 }"
|
||||
>
|
||||
<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 === 'rechargeAmount'">
|
||||
<InputNumber
|
||||
v-model:value="record.rechargeAmount"
|
||||
:min="0.01"
|
||||
:precision="2"
|
||||
style="width: 140px"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'rewardGold'">
|
||||
<InputNumber
|
||||
v-model:value="record.rewardGold"
|
||||
:min="0"
|
||||
:precision="0"
|
||||
style="width: 140px"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'rewardGroup'">
|
||||
<div class="reward-group-cell">
|
||||
<Space wrap>
|
||||
<Button size="small" @click="openRewardPicker(index)">
|
||||
{{ record.rewardGroupId ? '更换奖励组' : '选择奖励组' }}
|
||||
</Button>
|
||||
<Button
|
||||
v-if="record.rewardGroupId"
|
||||
danger
|
||||
size="small"
|
||||
type="link"
|
||||
@click="clearRewardGroup(record)"
|
||||
>
|
||||
清空
|
||||
</Button>
|
||||
</Space>
|
||||
<div v-if="record.rewardGroupId" class="reward-group-name">
|
||||
{{ record.rewardGroupName || '-' }} (ID: {{ record.rewardGroupId }})
|
||||
</div>
|
||||
<div v-else class="sub-text">未选择奖励组</div>
|
||||
<RewardRow v-if="record.rewardItems?.length" :list="record.rewardItems" />
|
||||
</div>
|
||||
</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>
|
||||
|
||||
<ActivityResourceGroupSelectDrawer
|
||||
:open="groupPickerOpen"
|
||||
:sys-origin="selectedSysOrigin"
|
||||
@close="groupPickerOpen = false"
|
||||
@select="handleRewardGroupSelect"
|
||||
/>
|
||||
</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;
|
||||
}
|
||||
|
||||
.reward-group-cell {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.reward-group-name {
|
||||
color: rgb(15 23 42);
|
||||
font-size: 13px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
758
apps/src/views/resident-activity/vip-config.vue
Normal file
758
apps/src/views/resident-activity/vip-config.vue
Normal file
@ -0,0 +1,758 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, reactive, ref, watch } from 'vue';
|
||||
|
||||
import { Page } from '@vben/common-ui';
|
||||
import { useAccessStore } from '@vben/stores';
|
||||
|
||||
import {
|
||||
getResidentVipConfig,
|
||||
pageResidentVipOrders,
|
||||
pageResidentVipUserStates,
|
||||
saveResidentVipConfig,
|
||||
} from '#/api/legacy/resident-activity';
|
||||
import { listSysOriginTypeList } from '#/api/legacy/props';
|
||||
import { getAllowedSysOrigins } from '#/views/system/shared';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Input,
|
||||
InputNumber,
|
||||
Modal,
|
||||
Pagination,
|
||||
Select,
|
||||
Space,
|
||||
Spin,
|
||||
Switch,
|
||||
Table,
|
||||
Tag,
|
||||
message,
|
||||
} from 'antdv-next';
|
||||
|
||||
defineOptions({ name: 'ResidentVipConfigPage' });
|
||||
|
||||
const resourceFields = [
|
||||
{ key: 'badge', title: '徽章', type: 'BADGE' },
|
||||
{ key: 'avatarFrame', title: '头像框', type: 'AVATAR_FRAME' },
|
||||
{ key: 'entryEffect', title: '进场特效', type: 'RIDE' },
|
||||
{ key: 'chatBubble', title: '聊天气泡', type: 'CHAT_BUBBLE' },
|
||||
{ key: 'floatPicture', title: '飘窗', type: 'FLOAT_PICTURE' },
|
||||
] as const;
|
||||
|
||||
type ResourceField = (typeof resourceFields)[number];
|
||||
|
||||
const statusOptions = [
|
||||
{ label: '生效中', value: 'ACTIVE' },
|
||||
{ label: '已过期', value: 'EXPIRED' },
|
||||
];
|
||||
|
||||
const orderStatusOptions = [
|
||||
{ label: '成功', value: 'SUCCESS' },
|
||||
{ label: '失败', value: 'FAILED' },
|
||||
{ label: '处理中', value: 'INIT' },
|
||||
];
|
||||
|
||||
const orderTypeOptions = [
|
||||
{ label: '购买', value: 'PURCHASE' },
|
||||
{ label: '续费', value: 'RENEW' },
|
||||
{ label: '升级', value: 'UPGRADE' },
|
||||
];
|
||||
|
||||
const levelColumns = [
|
||||
{ dataIndex: 'level', key: 'level', title: '等级', width: 90 },
|
||||
{ dataIndex: 'enabled', key: 'enabled', title: '启用', width: 90 },
|
||||
{ dataIndex: 'displayName', key: 'displayName', title: '名称', width: 160 },
|
||||
{ dataIndex: 'priceGold', key: 'priceGold', title: '30天价格', width: 150 },
|
||||
...resourceFields.map((item) => ({
|
||||
dataIndex: item.key,
|
||||
key: item.key,
|
||||
title: item.title,
|
||||
width: 260,
|
||||
})),
|
||||
];
|
||||
|
||||
const stateColumns = [
|
||||
{ dataIndex: 'userId', key: 'userId', title: '用户ID', width: 160 },
|
||||
{ dataIndex: 'status', key: 'status', title: '状态', width: 100 },
|
||||
{ dataIndex: 'level', key: 'level', title: '等级', width: 100 },
|
||||
{ dataIndex: 'displayName', key: 'displayName', title: '名称', width: 140 },
|
||||
{ dataIndex: 'resources', key: 'resources', title: '当前资源', width: 360 },
|
||||
{ dataIndex: 'expireAt', key: 'expireAt', title: '到期时间', width: 180 },
|
||||
{ dataIndex: 'updateTime', key: 'updateTime', title: '更新时间', width: 180 },
|
||||
];
|
||||
|
||||
const orderColumns = [
|
||||
{ dataIndex: 'userId', key: 'userId', title: '用户ID', width: 150 },
|
||||
{ dataIndex: 'orderType', key: 'orderType', title: '订单类型', width: 110 },
|
||||
{ dataIndex: 'levelChange', key: 'levelChange', title: '等级变化', width: 130 },
|
||||
{ dataIndex: 'paidGold', key: 'paidGold', title: '支付金币', width: 120 },
|
||||
{ dataIndex: 'originalPriceGold', key: 'originalPriceGold', title: '目标原价', width: 120 },
|
||||
{ dataIndex: 'status', key: 'status', title: '状态', width: 100 },
|
||||
{ dataIndex: 'expireAt', key: 'expireAt', title: '权益到期', width: 180 },
|
||||
{ dataIndex: 'createTime', key: 'createTime', title: '创建时间', width: 180 },
|
||||
{ dataIndex: 'errorMessage', key: 'errorMessage', title: '失败原因', width: 280 },
|
||||
];
|
||||
|
||||
const accessStore = useAccessStore();
|
||||
const sysOriginOptions = computed(() => {
|
||||
const options = getAllowedSysOrigins(accessStore.accessCodes || []);
|
||||
return options.length > 0 ? options : getAllowedSysOrigins([]);
|
||||
});
|
||||
|
||||
const loading = ref(false);
|
||||
const saving = ref(false);
|
||||
const stateLoading = ref(false);
|
||||
const orderLoading = ref(false);
|
||||
const stateTotal = ref(0);
|
||||
const orderTotal = ref(0);
|
||||
const stateList = ref<Array<Record<string, any>>>([]);
|
||||
const orderList = ref<Array<Record<string, any>>>([]);
|
||||
const resourceRows = reactive<Record<string, Array<Record<string, any>>>>({});
|
||||
const resourceLoading = reactive<Record<string, boolean>>({});
|
||||
|
||||
const form = reactive<Record<string, any>>({
|
||||
configured: false,
|
||||
levels: Array.from({ length: 5 }, (_, index) => createLevel(index + 1)),
|
||||
sysOrigin: '',
|
||||
updateTime: '',
|
||||
});
|
||||
|
||||
const stateQuery = reactive<Record<string, any>>({
|
||||
cursor: 1,
|
||||
limit: 20,
|
||||
status: '',
|
||||
userId: '',
|
||||
});
|
||||
|
||||
const orderQuery = reactive<Record<string, any>>({
|
||||
cursor: 1,
|
||||
limit: 20,
|
||||
orderType: '',
|
||||
status: '',
|
||||
userId: '',
|
||||
});
|
||||
|
||||
const statusMeta = computed(() => {
|
||||
if (!form.configured) {
|
||||
return {
|
||||
color: 'processing',
|
||||
description: '当前系统还没有保存 VIP 配置。',
|
||||
text: '未配置',
|
||||
};
|
||||
}
|
||||
const enabledCount = (form.levels || []).filter((item: Record<string, any>) =>
|
||||
Boolean(item.enabled),
|
||||
).length;
|
||||
return {
|
||||
color: enabledCount > 0 ? 'success' : 'default',
|
||||
description: `已配置 5 个等级,当前启用 ${enabledCount} 个等级。`,
|
||||
text: enabledCount > 0 ? '已启用' : '已关闭',
|
||||
};
|
||||
});
|
||||
|
||||
function createResource() {
|
||||
return {
|
||||
name: '',
|
||||
resourceId: '',
|
||||
url: '',
|
||||
};
|
||||
}
|
||||
|
||||
function createLevel(level: number) {
|
||||
return {
|
||||
avatarFrame: createResource(),
|
||||
badge: createResource(),
|
||||
chatBubble: createResource(),
|
||||
displayName: `VIP ${level}`,
|
||||
durationDays: 30,
|
||||
enabled: false,
|
||||
entryEffect: createResource(),
|
||||
floatPicture: createResource(),
|
||||
level,
|
||||
levelCode: `VIP${level}`,
|
||||
priceGold: 0,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeResource(value: Record<string, any> | null | undefined) {
|
||||
const resourceId = String(value?.resourceId ?? '').trim();
|
||||
return {
|
||||
name: String(value?.name || ''),
|
||||
resourceId: resourceId === '0' ? '' : resourceId,
|
||||
url: String(value?.url || ''),
|
||||
};
|
||||
}
|
||||
|
||||
function normalizePropsSourceResource(value: Record<string, any> | null | undefined) {
|
||||
const resourceId = String(value?.id ?? value?.resourceId ?? '').trim();
|
||||
return {
|
||||
name: String(value?.name || ''),
|
||||
resourceId,
|
||||
url: String(value?.sourceUrl || value?.url || value?.cover || ''),
|
||||
};
|
||||
}
|
||||
|
||||
function findResourceField(key: string) {
|
||||
return resourceFields.find((item) => item.key === key);
|
||||
}
|
||||
|
||||
function resetResourceRows() {
|
||||
for (const field of resourceFields) {
|
||||
resourceRows[field.key] = [];
|
||||
resourceLoading[field.key] = false;
|
||||
}
|
||||
}
|
||||
|
||||
function resourceSelectValue(record: Record<string, any>, key: string) {
|
||||
const resource = normalizeResource(record[key]);
|
||||
return resource.resourceId || undefined;
|
||||
}
|
||||
|
||||
function resourceSelectOptions(record: Record<string, any>, key: string) {
|
||||
const list = resourceRows[key] || [];
|
||||
const options = list.map((item) => {
|
||||
const code = String(item.code || '').trim();
|
||||
const name = String(item.name || '').trim();
|
||||
const id = String(item.id || '').trim();
|
||||
return {
|
||||
label: [id, name, code ? `(${code})` : ''].filter(Boolean).join(' / '),
|
||||
value: id as any,
|
||||
};
|
||||
});
|
||||
const current = normalizeResource(record[key]);
|
||||
if (
|
||||
current.resourceId &&
|
||||
!options.some((item) => String(item.value) === String(current.resourceId))
|
||||
) {
|
||||
options.unshift({
|
||||
label: `${current.resourceId} / ${current.name || '已保存资源'}`,
|
||||
value: current.resourceId as any,
|
||||
});
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
function resourceMeta(record: Record<string, any>, key: string) {
|
||||
const resource = normalizeResource(record[key]);
|
||||
if (!resource.resourceId) {
|
||||
return '';
|
||||
}
|
||||
return [resource.name, resource.url].filter(Boolean).join(' / ');
|
||||
}
|
||||
|
||||
async function loadResourceOptions(field: ResourceField) {
|
||||
if (!form.sysOrigin || resourceLoading[field.key]) {
|
||||
return;
|
||||
}
|
||||
resourceLoading[field.key] = true;
|
||||
try {
|
||||
resourceRows[field.key] = await listSysOriginTypeList(form.sysOrigin, field.type);
|
||||
} finally {
|
||||
resourceLoading[field.key] = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadResourceOptionsByKey(key: string) {
|
||||
const field = findResourceField(key);
|
||||
if (field) {
|
||||
await loadResourceOptions(field);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadAllResourceOptions() {
|
||||
await Promise.all(resourceFields.map((field) => loadResourceOptions(field)));
|
||||
}
|
||||
|
||||
function handleResourceChange(record: Record<string, any>, key: string, value?: number | string) {
|
||||
const field = findResourceField(key);
|
||||
if (!field) {
|
||||
return;
|
||||
}
|
||||
if (!value) {
|
||||
record[field.key] = createResource();
|
||||
return;
|
||||
}
|
||||
const selected = (resourceRows[field.key] || []).find(
|
||||
(item) => String(item.id) === String(value),
|
||||
);
|
||||
record[field.key] = selected
|
||||
? normalizePropsSourceResource(selected)
|
||||
: {
|
||||
...normalizeResource(record[field.key]),
|
||||
resourceId: String(value),
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeLevels(levels: Array<Record<string, any>> | undefined) {
|
||||
const levelMap = new Map<number, Record<string, any>>();
|
||||
for (const item of levels || []) {
|
||||
const level = Number(item.level || 0);
|
||||
if (level >= 1 && level <= 5) {
|
||||
levelMap.set(level, item);
|
||||
}
|
||||
}
|
||||
return Array.from({ length: 5 }, (_, index) => {
|
||||
const level = index + 1;
|
||||
const item = levelMap.get(level) || createLevel(level);
|
||||
return {
|
||||
...createLevel(level),
|
||||
displayName: String(item.displayName || `VIP ${level}`),
|
||||
enabled: Boolean(item.enabled),
|
||||
priceGold: Number(item.priceGold || 0),
|
||||
badge: normalizeResource(item.badge),
|
||||
avatarFrame: normalizeResource(item.avatarFrame),
|
||||
entryEffect: normalizeResource(item.entryEffect),
|
||||
chatBubble: normalizeResource(item.chatBubble),
|
||||
floatPicture: normalizeResource(item.floatPicture),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function applyConfig(result: Record<string, any> | null | undefined) {
|
||||
const value = result ?? {};
|
||||
form.configured = Boolean(value.configured);
|
||||
form.sysOrigin = String(value.sysOrigin || form.sysOrigin || sysOriginOptions.value[0]?.value || '');
|
||||
form.updateTime = String(value.updateTime || '');
|
||||
form.levels = normalizeLevels(value.levels);
|
||||
}
|
||||
|
||||
function buildSavePayload() {
|
||||
return {
|
||||
sysOrigin: form.sysOrigin,
|
||||
levels: (form.levels || []).map((item: Record<string, any>) => ({
|
||||
avatarFrame: normalizeResource(item.avatarFrame),
|
||||
badge: normalizeResource(item.badge),
|
||||
chatBubble: normalizeResource(item.chatBubble),
|
||||
displayName: String(item.displayName || '').trim(),
|
||||
durationDays: 30,
|
||||
enabled: Boolean(item.enabled),
|
||||
entryEffect: normalizeResource(item.entryEffect),
|
||||
floatPicture: normalizeResource(item.floatPicture),
|
||||
level: Number(item.level || 0),
|
||||
levelCode: `VIP${Number(item.level || 0)}`,
|
||||
priceGold: Number(item.priceGold || 0),
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
async function loadConfig() {
|
||||
if (!form.sysOrigin) {
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
try {
|
||||
const result = await getResidentVipConfig(form.sysOrigin);
|
||||
applyConfig(result);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadStates(reset = false) {
|
||||
if (!form.sysOrigin) {
|
||||
stateList.value = [];
|
||||
stateTotal.value = 0;
|
||||
return;
|
||||
}
|
||||
if (reset) {
|
||||
stateQuery.cursor = 1;
|
||||
}
|
||||
stateLoading.value = true;
|
||||
try {
|
||||
const result = await pageResidentVipUserStates({
|
||||
...stateQuery,
|
||||
sysOrigin: form.sysOrigin,
|
||||
});
|
||||
stateList.value = Array.isArray(result.records) ? result.records : [];
|
||||
stateTotal.value = Number(result.total || 0);
|
||||
} finally {
|
||||
stateLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadOrders(reset = false) {
|
||||
if (!form.sysOrigin) {
|
||||
orderList.value = [];
|
||||
orderTotal.value = 0;
|
||||
return;
|
||||
}
|
||||
if (reset) {
|
||||
orderQuery.cursor = 1;
|
||||
}
|
||||
orderLoading.value = true;
|
||||
try {
|
||||
const result = await pageResidentVipOrders({
|
||||
...orderQuery,
|
||||
sysOrigin: form.sysOrigin,
|
||||
});
|
||||
orderList.value = Array.isArray(result.records) ? result.records : [];
|
||||
orderTotal.value = Number(result.total || 0);
|
||||
} finally {
|
||||
orderLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
await Promise.all([loadConfig(), loadStates(true), loadOrders(true), loadAllResourceOptions()]);
|
||||
}
|
||||
|
||||
async function submitForm() {
|
||||
saving.value = true;
|
||||
try {
|
||||
await saveResidentVipConfig(buildSavePayload());
|
||||
message.success('保存成功');
|
||||
await loadConfig();
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleSave() {
|
||||
Modal.confirm({
|
||||
async onOk() {
|
||||
await submitForm();
|
||||
},
|
||||
title: '确认保存当前系统的 VIP 配置?',
|
||||
content: '保存后 app 会立即按新的等级价格和资源快照售卖 VIP。',
|
||||
});
|
||||
}
|
||||
|
||||
function handleStateSearch() {
|
||||
void loadStates(true);
|
||||
}
|
||||
|
||||
function handleOrderSearch() {
|
||||
void loadOrders(true);
|
||||
}
|
||||
|
||||
function handleStatePageChange(page: number, pageSize: number) {
|
||||
stateQuery.cursor = page;
|
||||
stateQuery.limit = pageSize;
|
||||
void loadStates();
|
||||
}
|
||||
|
||||
function handleOrderPageChange(page: number, pageSize: number) {
|
||||
orderQuery.cursor = page;
|
||||
orderQuery.limit = pageSize;
|
||||
void loadOrders();
|
||||
}
|
||||
|
||||
function statusColor(status: string) {
|
||||
switch (String(status || '').toUpperCase()) {
|
||||
case 'ACTIVE':
|
||||
case 'SUCCESS':
|
||||
return 'success';
|
||||
case 'FAILED':
|
||||
return 'error';
|
||||
case 'EXPIRED':
|
||||
return 'default';
|
||||
default:
|
||||
return 'processing';
|
||||
}
|
||||
}
|
||||
|
||||
function orderTypeText(type: string) {
|
||||
const match = orderTypeOptions.find((item) => item.value === String(type || '').toUpperCase());
|
||||
return match?.label || type || '-';
|
||||
}
|
||||
|
||||
function resourceSummary(record: Record<string, any>) {
|
||||
return resourceFields
|
||||
.map((field) => {
|
||||
const resource = record[field.key] || {};
|
||||
const text = String(resource.name || resource.resourceId || '').trim();
|
||||
return text ? `${field.title}: ${text}` : '';
|
||||
})
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
watch(
|
||||
sysOriginOptions,
|
||||
(options) => {
|
||||
if (!form.sysOrigin) {
|
||||
form.sysOrigin = String(options[0]?.value || '');
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
watch(
|
||||
() => form.sysOrigin,
|
||||
(value, oldValue) => {
|
||||
if (value) {
|
||||
if (value !== oldValue) {
|
||||
resetResourceRows();
|
||||
}
|
||||
void loadData();
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page auto-content-height>
|
||||
<Spin :spinning="loading">
|
||||
<Card :bordered="false" title="VIP配置">
|
||||
<Space class="toolbar" wrap>
|
||||
<Select
|
||||
v-model:value="form.sysOrigin"
|
||||
:options="sysOriginOptions"
|
||||
option-filter-prop="label"
|
||||
option-label-prop="label"
|
||||
placeholder="请选择系统"
|
||||
show-search
|
||||
style="width: 180px"
|
||||
/>
|
||||
<Tag :color="statusMeta.color">
|
||||
{{ statusMeta.text }}
|
||||
</Tag>
|
||||
<span class="status-desc">{{ statusMeta.description }}</span>
|
||||
</Space>
|
||||
|
||||
<Table
|
||||
:columns="levelColumns"
|
||||
:data-source="form.levels"
|
||||
:pagination="false"
|
||||
row-key="level"
|
||||
:scroll="{ x: 1700 }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'level'">
|
||||
<Tag color="gold">VIP {{ record.level }}</Tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'enabled'">
|
||||
<Switch v-model:checked="record.enabled" />
|
||||
</template>
|
||||
<template v-else-if="column.key === 'displayName'">
|
||||
<Input v-model:value="record.displayName" placeholder="等级名称" />
|
||||
</template>
|
||||
<template v-else-if="column.key === 'priceGold'">
|
||||
<InputNumber
|
||||
v-model:value="record.priceGold"
|
||||
:min="0"
|
||||
:precision="0"
|
||||
addon-after="金币"
|
||||
style="width: 130px"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="resourceFields.some((item) => item.key === String(column.key))">
|
||||
<div class="resource-editor">
|
||||
<Select
|
||||
:value="resourceSelectValue(record, String(column.key))"
|
||||
allow-clear
|
||||
:loading="resourceLoading[String(column.key)]"
|
||||
:not-found-content="resourceLoading[String(column.key)] ? undefined : '暂无资源'"
|
||||
option-filter-prop="label"
|
||||
option-label-prop="label"
|
||||
:options="resourceSelectOptions(record, String(column.key))"
|
||||
placeholder="请选择资源"
|
||||
show-search
|
||||
style="width: 100%"
|
||||
@change="(value) => handleResourceChange(record, String(column.key), value)"
|
||||
@focus="loadResourceOptionsByKey(String(column.key))"
|
||||
/>
|
||||
<div v-if="resourceMeta(record, String(column.key))" class="resource-meta">
|
||||
{{ resourceMeta(record, String(column.key)) }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</Table>
|
||||
|
||||
<div class="tips">
|
||||
<div>固定 5 个等级,每次购买 30 天;同等级再次购买会续 30 天。</div>
|
||||
<div>资源从道具资源配置选择,保存时会记录当前资源快照。</div>
|
||||
<div>用户只能升级,升级时支付目标等级和当前等级的差价,到期后资源自动失效。</div>
|
||||
<div>升级成功后会把徽章、头像框、进场特效、聊天气泡和飘窗替换成新等级资源。</div>
|
||||
</div>
|
||||
|
||||
<Space class="actions" wrap>
|
||||
<Button :loading="saving" type="primary" @click="handleSave">
|
||||
保存配置
|
||||
</Button>
|
||||
<span v-if="form.updateTime" class="field-desc">最后更新时间:{{ form.updateTime }}</span>
|
||||
</Space>
|
||||
</Card>
|
||||
|
||||
<Card :bordered="false" class="record-card" title="用户VIP状态">
|
||||
<Space class="toolbar" wrap>
|
||||
<Input
|
||||
v-model:value="stateQuery.userId"
|
||||
allow-clear
|
||||
placeholder="用户ID"
|
||||
style="width: 160px"
|
||||
/>
|
||||
<Select
|
||||
v-model:value="stateQuery.status"
|
||||
allow-clear
|
||||
:options="statusOptions"
|
||||
placeholder="状态"
|
||||
style="width: 140px"
|
||||
/>
|
||||
<Button :loading="stateLoading" type="primary" @click="handleStateSearch">
|
||||
查询
|
||||
</Button>
|
||||
</Space>
|
||||
|
||||
<Table
|
||||
:columns="stateColumns"
|
||||
:data-source="stateList"
|
||||
:loading="stateLoading"
|
||||
:pagination="false"
|
||||
row-key="userId"
|
||||
:scroll="{ x: 1220 }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<Tag :color="statusColor(record.status)">
|
||||
{{ record.status || '-' }}
|
||||
</Tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'level'">
|
||||
<Tag v-if="record.active" color="gold">VIP {{ record.level }}</Tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'resources'">
|
||||
<div v-if="resourceSummary(record).length > 0" class="resource-summary">
|
||||
<div v-for="item in resourceSummary(record)" :key="item">
|
||||
{{ item }}
|
||||
</div>
|
||||
</div>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</template>
|
||||
</Table>
|
||||
|
||||
<div v-if="stateTotal > 0" class="pager">
|
||||
<Pagination
|
||||
:current="stateQuery.cursor"
|
||||
:page-size="stateQuery.limit"
|
||||
:total="stateTotal"
|
||||
show-size-changer
|
||||
@change="handleStatePageChange"
|
||||
@showSizeChange="handleStatePageChange"
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card :bordered="false" class="record-card" title="VIP订单记录">
|
||||
<Space class="toolbar" wrap>
|
||||
<Input
|
||||
v-model:value="orderQuery.userId"
|
||||
allow-clear
|
||||
placeholder="用户ID"
|
||||
style="width: 160px"
|
||||
/>
|
||||
<Select
|
||||
v-model:value="orderQuery.orderType"
|
||||
allow-clear
|
||||
:options="orderTypeOptions"
|
||||
placeholder="订单类型"
|
||||
style="width: 140px"
|
||||
/>
|
||||
<Select
|
||||
v-model:value="orderQuery.status"
|
||||
allow-clear
|
||||
:options="orderStatusOptions"
|
||||
placeholder="状态"
|
||||
style="width: 140px"
|
||||
/>
|
||||
<Button :loading="orderLoading" type="primary" @click="handleOrderSearch">
|
||||
查询
|
||||
</Button>
|
||||
</Space>
|
||||
|
||||
<Table
|
||||
:columns="orderColumns"
|
||||
:data-source="orderList"
|
||||
:loading="orderLoading"
|
||||
:pagination="false"
|
||||
row-key="id"
|
||||
:scroll="{ x: 1370 }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'orderType'">
|
||||
{{ orderTypeText(record.orderType) }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'levelChange'">
|
||||
VIP {{ record.fromLevel || 0 }} → VIP {{ record.toLevel || 0 }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'status'">
|
||||
<Tag :color="statusColor(record.status)">
|
||||
{{ record.status || '-' }}
|
||||
</Tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'errorMessage'">
|
||||
{{ record.errorMessage || '-' }}
|
||||
</template>
|
||||
</template>
|
||||
</Table>
|
||||
|
||||
<div v-if="orderTotal > 0" class="pager">
|
||||
<Pagination
|
||||
:current="orderQuery.cursor"
|
||||
:page-size="orderQuery.limit"
|
||||
:total="orderTotal"
|
||||
show-size-changer
|
||||
@change="handleOrderPageChange"
|
||||
@showSizeChange="handleOrderPageChange"
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
</Spin>
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.toolbar {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.status-desc,
|
||||
.field-desc {
|
||||
color: rgb(100 116 139);
|
||||
}
|
||||
|
||||
.resource-editor {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.resource-meta {
|
||||
color: rgb(100 116 139);
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tips {
|
||||
background: rgb(248 250 252);
|
||||
border-radius: 8px;
|
||||
color: rgb(71 85 105);
|
||||
line-height: 1.8;
|
||||
margin-top: 16px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.record-card {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.resource-summary {
|
||||
color: rgb(51 65 85);
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.pager {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
@ -310,6 +310,112 @@ SET
|
||||
router = 'specified-gift-weekly-rank'
|
||||
WHERE alias = 'ResidentSpecifiedGiftWeeklyRank';
|
||||
|
||||
INSERT INTO sys_menu (
|
||||
id,
|
||||
parent_id,
|
||||
menu_name,
|
||||
path,
|
||||
menu_type,
|
||||
icon,
|
||||
create_user,
|
||||
update_user,
|
||||
sort,
|
||||
status,
|
||||
router,
|
||||
alias
|
||||
)
|
||||
SELECT
|
||||
1700000261,
|
||||
(SELECT id FROM sys_menu WHERE alias = 'ResidentActivityManager' LIMIT 1),
|
||||
'VIP',
|
||||
'resident-activity/vip-config/index',
|
||||
2,
|
||||
'form',
|
||||
'0',
|
||||
'0',
|
||||
6,
|
||||
0,
|
||||
'vip/config',
|
||||
'ResidentVipConfig'
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM sys_menu WHERE alias = 'ResidentVipConfig'
|
||||
);
|
||||
|
||||
UPDATE sys_menu
|
||||
SET
|
||||
parent_id = (
|
||||
SELECT resident_menu.id
|
||||
FROM (
|
||||
SELECT id
|
||||
FROM sys_menu
|
||||
WHERE alias = 'ResidentActivityManager'
|
||||
LIMIT 1
|
||||
) AS resident_menu
|
||||
),
|
||||
menu_name = 'VIP',
|
||||
path = 'resident-activity/vip-config/index',
|
||||
menu_type = 2,
|
||||
icon = 'form',
|
||||
create_user = '0',
|
||||
update_user = '0',
|
||||
sort = 6,
|
||||
status = 0,
|
||||
router = 'vip/config'
|
||||
WHERE alias = 'ResidentVipConfig';
|
||||
|
||||
INSERT INTO sys_menu (
|
||||
id,
|
||||
parent_id,
|
||||
menu_name,
|
||||
path,
|
||||
menu_type,
|
||||
icon,
|
||||
create_user,
|
||||
update_user,
|
||||
sort,
|
||||
status,
|
||||
router,
|
||||
alias
|
||||
)
|
||||
SELECT
|
||||
1700000262,
|
||||
(SELECT id FROM sys_menu WHERE alias = 'ResidentActivityManager' LIMIT 1),
|
||||
'充值奖励配置',
|
||||
'resident-activity/recharge-reward-config/index',
|
||||
2,
|
||||
'form',
|
||||
'0',
|
||||
'0',
|
||||
7,
|
||||
0,
|
||||
'recharge-reward/config',
|
||||
'ResidentRechargeRewardConfig'
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM sys_menu WHERE alias = 'ResidentRechargeRewardConfig'
|
||||
);
|
||||
|
||||
UPDATE sys_menu
|
||||
SET
|
||||
parent_id = (
|
||||
SELECT resident_menu.id
|
||||
FROM (
|
||||
SELECT id
|
||||
FROM sys_menu
|
||||
WHERE alias = 'ResidentActivityManager'
|
||||
LIMIT 1
|
||||
) AS resident_menu
|
||||
),
|
||||
menu_name = '充值奖励配置',
|
||||
path = 'resident-activity/recharge-reward-config/index',
|
||||
menu_type = 2,
|
||||
icon = 'form',
|
||||
create_user = '0',
|
||||
update_user = '0',
|
||||
sort = 7,
|
||||
status = 0,
|
||||
router = 'recharge-reward/config'
|
||||
WHERE alias = 'ResidentRechargeRewardConfig';
|
||||
|
||||
INSERT INTO sys_menu (
|
||||
id,
|
||||
parent_id,
|
||||
@ -451,6 +557,8 @@ JOIN sys_menu AS target_menu
|
||||
'ResidentInviteConfig',
|
||||
'ResidentInviteList',
|
||||
'ResidentSpecifiedGiftWeeklyRank',
|
||||
'ResidentVipConfig',
|
||||
'ResidentRechargeRewardConfig',
|
||||
'OperateGameManager',
|
||||
'GameConfig',
|
||||
'OperateBaishunGameManage'
|
||||
@ -485,6 +593,14 @@ UPDATE sys_menu
|
||||
SET menu_name = CONVERT(0xE591A8E6989F USING utf8mb4)
|
||||
WHERE alias = 'ResidentSpecifiedGiftWeeklyRank';
|
||||
|
||||
UPDATE sys_menu
|
||||
SET menu_name = 'VIP'
|
||||
WHERE alias = 'ResidentVipConfig';
|
||||
|
||||
UPDATE sys_menu
|
||||
SET menu_name = CONVERT(0xE58585E580BCE5A596E58AB1E9858DE7BDAE USING utf8mb4)
|
||||
WHERE alias = 'ResidentRechargeRewardConfig';
|
||||
|
||||
UPDATE sys_menu
|
||||
SET menu_name = CONVERT(0xE6B8B8E6888FE7AEA1E79086 USING utf8mb4)
|
||||
WHERE alias = 'OperateGameManager';
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user