diff --git a/apps/src/views/resident-activity/vip-config.vue b/apps/src/views/resident-activity/vip-config.vue index 841b401..a675451 100644 --- a/apps/src/views/resident-activity/vip-config.vue +++ b/apps/src/views/resident-activity/vip-config.vue @@ -4,20 +4,14 @@ 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, + Drawer, + Empty, Input, InputNumber, + message, Modal, Pagination, Select, @@ -25,10 +19,21 @@ import { Spin, Switch, Table, + TabPane, + Tabs, Tag, - message, + Tooltip, } from 'antdv-next'; +import { listSysOriginTypeList } from '#/api/legacy/props'; +import { + getResidentVipConfig, + pageResidentVipOrders, + pageResidentVipUserStates, + saveResidentVipConfig, +} from '#/api/legacy/resident-activity'; +import { getAllowedSysOrigins } from '#/views/system/shared'; + defineOptions({ name: 'ResidentVipConfigPage' }); const resourceFields = [ @@ -66,20 +71,13 @@ const levelColumns = [ { 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, - })), + { dataIndex: 'resources', key: 'resources', title: '素材', width: 560 }, ]; 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 }, ]; @@ -93,7 +91,7 @@ const orderColumns = [ { 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 }, + { dataIndex: 'errorMessage', key: 'errorMessage', title: '失败原因', width: 90 }, ]; const accessStore = useAccessStore(); @@ -112,6 +110,12 @@ const stateList = ref>>([]); const orderList = ref>>([]); const resourceRows = reactive>>>({}); const resourceLoading = reactive>({}); +const activeTab = ref<'orders' | 'states'>('states'); +const vipConfigDrawerOpen = ref(false); +const resourceDrawerOpen = ref(false); +const activeResourceLevel = ref>(null); +const activeResourceFieldKey = ref('longBadge'); +const resourceKeyword = ref(''); const form = reactive>({ configured: false, @@ -135,22 +139,37 @@ const orderQuery = reactive>({ userId: '', }); -const statusMeta = computed(() => { - if (!form.configured) { - return { - color: 'processing', - description: '当前系统还没有保存 VIP 配置。', - text: '未配置', - }; +const activeResourceField = computed( + () => findResourceField(activeResourceFieldKey.value) || resourceFields[0], +); + +const activeResourceList = computed(() => { + const field = activeResourceField.value; + const keyword = resourceKeyword.value.trim().toLowerCase(); + const rows = resourceRows[field.key] || []; + if (!keyword) { + return rows; } - const enabledCount = (form.levels || []).filter((item: Record) => - Boolean(item.enabled), - ).length; - return { - color: enabledCount > 0 ? 'success' : 'default', - description: `已配置 5 个等级,当前启用 ${enabledCount} 个等级。`, - text: enabledCount > 0 ? '已启用' : '已关闭', - }; + return rows.filter((item) => { + return [ + resourceOptionId(item), + resourceOptionName(item), + String(item.code || ''), + ].some((value) => value.toLowerCase().includes(keyword)); + }); +}); + +const activeResource = computed(() => { + const record = activeResourceLevel.value; + if (!record) { + return createResource(); + } + return normalizeResource(record[activeResourceField.value.key]); +}); + +const activeResourceTitle = computed(() => { + const level = activeResourceLevel.value?.level || '-'; + return `VIP ${level} 素材配置`; }); function createResource() { @@ -182,7 +201,7 @@ function createLevel(level: number) { }; } -function normalizeResource(value: Record | null | undefined) { +function normalizeResource(value: null | Record | undefined) { const resourceId = String(value?.resourceId ?? '').trim(); const url = String(value?.url || value?.sourceUrl || value?.resourceUrl || '').trim(); const cover = String(value?.cover || value?.coverUrl || value?.resourceCover || '').trim(); @@ -197,7 +216,7 @@ function normalizeResource(value: Record | null | undefined) { }; } -function normalizePropsSourceResource(value: Record | null | undefined) { +function normalizePropsSourceResource(value: null | Record | undefined) { const resourceId = String(value?.id ?? value?.resourceId ?? '').trim(); const cover = String( value?.cover || value?.coverUrl || value?.resourceCover || value?.selectUrl || '', @@ -232,50 +251,7 @@ function resetResourceRows() { } } -function resourceSelectValue(record: Record, key: string) { - const resource = normalizeResource(record[key]); - return resource.resourceId || undefined; -} - -function resourceSelectOptions(record: Record, 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, key: string) { - const resource = normalizeResource(record[key]); - if (!resource.resourceId) { - return ''; - } - return [ - resource.name, - resource.cover ? `封面: ${resource.cover}` : '', - resource.url ? `资源: ${resource.url}` : '', - ] - .filter(Boolean) - .join(' / '); -} - -function buildResourceSavePayload(value: Record | null | undefined) { +function buildResourceSavePayload(value: null | Record | undefined) { const resource = normalizeResource(value); return { resourceId: resource.resourceId || '0', @@ -328,37 +304,10 @@ function parseJsonObject(value: unknown) { } } -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, 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> | undefined) { const levelMap = new Map>(); for (const item of levels || []) { @@ -375,7 +324,7 @@ function normalizeLevels(levels: Array> | undefined) { displayName: String(item.displayName || `VIP ${level}`), enabled: Boolean(item.enabled), priceGold: Number(item.priceGold || 0), - longBadge: normalizeResource(item.longBadge || item['badge']), + longBadge: normalizeResource(item.longBadge || item.badge), shortBadge: normalizeResource(item.shortBadge), avatarFrame: normalizeResource(item.avatarFrame), entryEffect: normalizeResource(item.entryEffect), @@ -387,7 +336,7 @@ function normalizeLevels(levels: Array> | undefined) { }); } -function applyConfig(result: Record | null | undefined) { +function applyConfig(result: null | Record | undefined) { const value = result ?? {}; form.configured = Boolean(value.configured); form.sysOrigin = String(value.sysOrigin || form.sysOrigin || sysOriginOptions.value[0]?.value || ''); @@ -523,14 +472,18 @@ function handleOrderPageChange(page: number, pageSize: number) { function statusColor(status: string) { switch (String(status || '').toUpperCase()) { case 'ACTIVE': - case 'SUCCESS': + case 'SUCCESS': { return 'success'; - case 'FAILED': - return 'error'; - case 'EXPIRED': + } + case 'EXPIRED': { return 'default'; - default: + } + case 'FAILED': { + return 'error'; + } + default: { return 'processing'; + } } } @@ -539,14 +492,117 @@ function orderTypeText(type: string) { return match?.label || type || '-'; } -function resourceSummary(record: Record) { +function errorMessageText(record: Record) { + return String(record.errorMessage || '').trim(); +} + +function selectedResourceSummaries(record: Record) { return resourceFields .map((field) => { - const resource = record[field.key] || {}; - const text = String(resource.name || resource.resourceId || '').trim(); - return text ? `${field.title}: ${text}` : ''; + const resource = normalizeResource(record[field.key]); + const name = String(resource.name || resource.resourceId || '').trim(); + if (!name) { + return null; + } + return { + cover: resource.cover, + key: field.key, + name, + title: field.title, + }; }) - .filter(Boolean); + .filter(Boolean) as Array<{ + cover: string; + key: string; + name: string; + title: string; + }>; +} + +function selectedResourceCount(record: Record) { + return selectedResourceSummaries(record).length; +} + +function resourceFieldSelected(record: Record, field: ResourceField) { + return Boolean(normalizeResource(record[field.key]).resourceId); +} + +function resourceOptionId(item: Record) { + return String(item.id ?? item.resourceId ?? item.sourceId ?? '').trim(); +} + +function resourceOptionName(item: Record) { + const id = resourceOptionId(item); + return String( + item.name || + item.resourceName || + item.sourceName || + item.badgeName || + item.title || + (id ? `资源 ${id}` : ''), + ).trim(); +} + +function resourceOptionCover(item: Record) { + return String( + item.cover || + item.coverUrl || + item.resourceCover || + item.resourceCoverUrl || + item.selectUrl || + item.icon || + item.sourceUrl || + item.resourceUrl || + item.animationUrl || + '', + ).trim(); +} + +function resourceOptionMeta(item: Record) { + return [resourceOptionId(item) ? `ID ${resourceOptionId(item)}` : '', item.code || ''] + .filter(Boolean) + .join(' / '); +} + +function openResourceDrawer(record: Record) { + activeResourceLevel.value = record; + activeResourceFieldKey.value = + resourceFields.find((field) => !resourceFieldSelected(record, field))?.key || + resourceFields[0].key; + resourceKeyword.value = ''; + resourceDrawerOpen.value = true; + void loadAllResourceOptions(); +} + +function closeResourceDrawer() { + resourceDrawerOpen.value = false; + activeResourceLevel.value = null; +} + +function selectResourceField(field: ResourceField) { + activeResourceFieldKey.value = field.key; + resourceKeyword.value = ''; + void loadResourceOptions(field); +} + +function selectResource(item: Record) { + const record = activeResourceLevel.value; + if (!record) { + return; + } + record[activeResourceField.value.key] = normalizePropsSourceResource(item); +} + +function clearActiveResource() { + const record = activeResourceLevel.value; + if (!record) { + return; + } + record[activeResourceField.value.key] = createResource(); +} + +function isActiveResource(item: Record) { + return String(activeResource.value.resourceId) === resourceOptionId(item); } watch( @@ -576,29 +632,150 @@ watch(