修复时间字段
This commit is contained in:
parent
8d0836e7e8
commit
aa0988af6b
@ -37,6 +37,12 @@ export async function pageBank(params: Record<string, any>) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getBankStatistics(params: Record<string, any>) {
|
||||||
|
return requestClient.get<Record<string, any>>('/user-bank-balance/statistics', {
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export async function exprotBank(params: Record<string, any>) {
|
export async function exprotBank(params: Record<string, any>) {
|
||||||
return downloadLegacyExcel(
|
return downloadLegacyExcel(
|
||||||
'/user-bank-balance/export',
|
'/user-bank-balance/export',
|
||||||
|
|||||||
@ -1,21 +1,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, reactive, ref, watch } from 'vue';
|
import { computed, reactive, ref, watch } from 'vue';
|
||||||
|
|
||||||
import {
|
|
||||||
addGift,
|
|
||||||
updateGift,
|
|
||||||
} from '#/api/legacy/gift';
|
|
||||||
import {
|
|
||||||
mapLuckyGiftStandard,
|
|
||||||
} from '#/api/legacy/game-lucky-gift';
|
|
||||||
import {
|
|
||||||
getAccessImgUrl,
|
|
||||||
simpleUploadFile,
|
|
||||||
} from '#/api/legacy/oss';
|
|
||||||
import {
|
|
||||||
regionConfigTable,
|
|
||||||
} from '#/api/legacy/system';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
DatePicker,
|
DatePicker,
|
||||||
@ -24,14 +9,29 @@ import {
|
|||||||
FormItem,
|
FormItem,
|
||||||
Image,
|
Image,
|
||||||
Input,
|
Input,
|
||||||
|
message,
|
||||||
Select,
|
Select,
|
||||||
Space,
|
Space,
|
||||||
Spin,
|
Spin,
|
||||||
Switch,
|
Switch,
|
||||||
TextArea,
|
TextArea,
|
||||||
message,
|
|
||||||
} from 'antdv-next';
|
} from 'antdv-next';
|
||||||
|
|
||||||
|
import {
|
||||||
|
mapLuckyGiftStandard,
|
||||||
|
} from '#/api/legacy/game-lucky-gift';
|
||||||
|
import {
|
||||||
|
addGift,
|
||||||
|
updateGift,
|
||||||
|
} from '#/api/legacy/gift';
|
||||||
|
import {
|
||||||
|
getAccessImgUrl,
|
||||||
|
simpleUploadFile,
|
||||||
|
} from '#/api/legacy/oss';
|
||||||
|
import {
|
||||||
|
regionConfigTable,
|
||||||
|
} from '#/api/legacy/system';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
GIFT_CONFIG_TAB_OPTIONS,
|
GIFT_CONFIG_TAB_OPTIONS,
|
||||||
GIFT_SPECIAL_OPTIONS,
|
GIFT_SPECIAL_OPTIONS,
|
||||||
@ -39,6 +39,17 @@ import {
|
|||||||
|
|
||||||
defineOptions({ name: 'OperateGiftFormModal' });
|
defineOptions({ name: 'OperateGiftFormModal' });
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
open: boolean;
|
||||||
|
row?: null | Record<string, any>;
|
||||||
|
sysOrigin: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
close: [];
|
||||||
|
success: [];
|
||||||
|
}>();
|
||||||
|
|
||||||
function createForm(sysOrigin = '') {
|
function createForm(sysOrigin = '') {
|
||||||
return {
|
return {
|
||||||
account: '',
|
account: '',
|
||||||
@ -61,17 +72,6 @@ function createForm(sysOrigin = '') {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
open: boolean;
|
|
||||||
row?: null | Record<string, any>;
|
|
||||||
sysOrigin: string;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
close: [];
|
|
||||||
success: [];
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const form = reactive(createForm());
|
const form = reactive(createForm());
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const saving = ref(false);
|
const saving = ref(false);
|
||||||
@ -128,6 +128,13 @@ function normalizeMultiValue(value: any) {
|
|||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeTimestampValue(value: any) {
|
||||||
|
if (value === '' || value === null || value === undefined) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return String(value);
|
||||||
|
}
|
||||||
|
|
||||||
function resetForm() {
|
function resetForm() {
|
||||||
Object.assign(form, createForm(props.sysOrigin));
|
Object.assign(form, createForm(props.sysOrigin));
|
||||||
previousStandardId.value = '';
|
previousStandardId.value = '';
|
||||||
@ -157,6 +164,7 @@ watch(
|
|||||||
Object.assign(form, {
|
Object.assign(form, {
|
||||||
...props.row,
|
...props.row,
|
||||||
explanationGift: Boolean(props.row.explanationGift),
|
explanationGift: Boolean(props.row.explanationGift),
|
||||||
|
expiredTime: normalizeTimestampValue(props.row.expiredTime),
|
||||||
regionList: normalizeMultiValue(props.row.regions || props.row.regionList),
|
regionList: normalizeMultiValue(props.row.regions || props.row.regionList),
|
||||||
specialList: normalizeMultiValue(props.row.special || props.row.specialList),
|
specialList: normalizeMultiValue(props.row.special || props.row.specialList),
|
||||||
sysOrigin: props.row.sysOrigin || props.sysOrigin,
|
sysOrigin: props.row.sysOrigin || props.sysOrigin,
|
||||||
@ -290,11 +298,7 @@ async function handleSubmit() {
|
|||||||
giftCandy: form.type === 'FREE' ? '' : form.giftCandy,
|
giftCandy: form.type === 'FREE' ? '' : form.giftCandy,
|
||||||
giftIntegral: form.type === 'FREE' ? '' : form.giftIntegral,
|
giftIntegral: form.type === 'FREE' ? '' : form.giftIntegral,
|
||||||
};
|
};
|
||||||
if (payload.id) {
|
await (payload.id ? updateGift(payload) : addGift(payload));
|
||||||
await updateGift(payload);
|
|
||||||
} else {
|
|
||||||
await addGift(payload);
|
|
||||||
}
|
|
||||||
message.success('保存成功');
|
message.success('保存成功');
|
||||||
emit('success');
|
emit('success');
|
||||||
emit('close');
|
emit('close');
|
||||||
@ -323,10 +327,11 @@ async function handleSubmit() {
|
|||||||
<Button size="small" @click="toggleAllRegions">
|
<Button size="small" @click="toggleAllRegions">
|
||||||
{{ form.regionList.length === regions.length ? '取消全选' : '全选' }}
|
{{ form.regionList.length === regions.length ? '取消全选' : '全选' }}
|
||||||
</Button>
|
</Button>
|
||||||
<Select option-label-prop="label"
|
<Select
|
||||||
v-model:value="form.regionList"
|
v-model:value="form.regionList"
|
||||||
:options="regionOptions"
|
:options="regionOptions"
|
||||||
mode="multiple"
|
mode="multiple"
|
||||||
|
option-label-prop="label"
|
||||||
placeholder="请选择区域"
|
placeholder="请选择区域"
|
||||||
show-search
|
show-search
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
@ -461,14 +466,14 @@ async function handleSubmit() {
|
|||||||
hidden
|
hidden
|
||||||
type="file"
|
type="file"
|
||||||
@change="handleCoverUpload"
|
@change="handleCoverUpload"
|
||||||
>
|
/>
|
||||||
<input
|
<input
|
||||||
ref="sourceInputRef"
|
ref="sourceInputRef"
|
||||||
accept=".svga,.pag,.mp4"
|
accept=".svga,.pag,.mp4"
|
||||||
hidden
|
hidden
|
||||||
type="file"
|
type="file"
|
||||||
@change="handleSourceUpload"
|
@change="handleSourceUpload"
|
||||||
>
|
/>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<Space>
|
<Space>
|
||||||
|
|||||||
@ -8,10 +8,21 @@ import {
|
|||||||
import { Page } from '@vben/common-ui';
|
import { Page } from '@vben/common-ui';
|
||||||
import { useAccessStore } from '@vben/stores';
|
import { useAccessStore } from '@vben/stores';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Input,
|
||||||
|
message,
|
||||||
|
Modal,
|
||||||
|
Space,
|
||||||
|
Table,
|
||||||
|
} from 'antdv-next';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
confirmImport,
|
confirmImport,
|
||||||
deductMoney,
|
deductMoney,
|
||||||
exprotBank,
|
exprotBank,
|
||||||
|
getBankStatistics,
|
||||||
pageBank,
|
pageBank,
|
||||||
sendMoney,
|
sendMoney,
|
||||||
transferMoney,
|
transferMoney,
|
||||||
@ -20,16 +31,6 @@ import AccountInput from '#/components/account-input.vue';
|
|||||||
import { formatDate,
|
import { formatDate,
|
||||||
getAllowedSysOrigins } from '#/views/system/shared';
|
getAllowedSysOrigins } from '#/views/system/shared';
|
||||||
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
Card,
|
|
||||||
Input,
|
|
||||||
Modal,
|
|
||||||
Space,
|
|
||||||
Table,
|
|
||||||
message,
|
|
||||||
} from 'antdv-next';
|
|
||||||
|
|
||||||
import UserBankCreateModal from './components/user-bank-create-modal.vue';
|
import UserBankCreateModal from './components/user-bank-create-modal.vue';
|
||||||
import UserBankRunningWaterModal from './components/user-bank-running-water-modal.vue';
|
import UserBankRunningWaterModal from './components/user-bank-running-water-modal.vue';
|
||||||
import UserProfileLink from './components/user-profile-link.vue';
|
import UserProfileLink from './components/user-profile-link.vue';
|
||||||
@ -45,6 +46,7 @@ const sysOriginOptions = computed(() => {
|
|||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const loadMoreLoading = ref(false);
|
const loadMoreLoading = ref(false);
|
||||||
const exportLoading = ref(false);
|
const exportLoading = ref(false);
|
||||||
|
const statisticsLoading = ref(false);
|
||||||
const notData = ref(false);
|
const notData = ref(false);
|
||||||
const list = ref<Array<Record<string, any>>>([]);
|
const list = ref<Array<Record<string, any>>>([]);
|
||||||
const createOpen = ref(false);
|
const createOpen = ref(false);
|
||||||
@ -54,16 +56,33 @@ const importLoading = ref(false);
|
|||||||
const actionModalOpen = ref(false);
|
const actionModalOpen = ref(false);
|
||||||
const transferOpen = ref(false);
|
const transferOpen = ref(false);
|
||||||
const actionType = ref<'deduct' | 'send'>('send');
|
const actionType = ref<'deduct' | 'send'>('send');
|
||||||
const activeRow = ref<Record<string, any> | null>(null);
|
const activeRow = ref<null | Record<string, any>>(null);
|
||||||
const importInput = ref<HTMLInputElement | null>(null);
|
const importInput = ref<HTMLInputElement | null>(null);
|
||||||
|
|
||||||
const query = reactive({
|
const query = reactive({
|
||||||
lastId: '',
|
lastId: '',
|
||||||
limit: 20,
|
limit: 20,
|
||||||
|
pageNo: 1,
|
||||||
|
sortField: '',
|
||||||
|
sortOrder: '',
|
||||||
sysOrigin: '',
|
sysOrigin: '',
|
||||||
userId: '',
|
userId: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const statistics = reactive({
|
||||||
|
totalBalance: 0,
|
||||||
|
totalConsumptionPoints: 0,
|
||||||
|
totalEarnPoints: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const sortableAmountKeys = new Set(['balance', 'consumptionPoints', 'earnPoints']);
|
||||||
|
|
||||||
|
type BankSortOrder = 'ascend' | 'descend';
|
||||||
|
|
||||||
|
const isSortActive = computed(() => {
|
||||||
|
return Boolean(query.sortField && query.sortOrder);
|
||||||
|
});
|
||||||
|
|
||||||
const actionForm = reactive({
|
const actionForm = reactive({
|
||||||
quantity: '',
|
quantity: '',
|
||||||
remark: '',
|
remark: '',
|
||||||
@ -79,17 +98,96 @@ const transferForm = reactive({
|
|||||||
userId: '',
|
userId: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
const columns = [
|
const columns = computed(() => [
|
||||||
{ dataIndex: 'index', key: 'index', title: 'No', width: 70 },
|
{ dataIndex: 'index', key: 'index', title: 'No', width: 70 },
|
||||||
{ dataIndex: 'sysOrigin', key: 'sysOrigin', title: '系统', width: 90 },
|
{ dataIndex: 'sysOrigin', key: 'sysOrigin', title: '系统', width: 90 },
|
||||||
{ dataIndex: 'userProfile', key: 'userProfile', title: '用户', width: 300 },
|
{ dataIndex: 'userProfile', key: 'userProfile', title: '用户', width: 300 },
|
||||||
{ dataIndex: 'earnPoints', key: 'earnPoints', title: '获得总额', width: 120 },
|
{
|
||||||
{ dataIndex: 'consumptionPoints', key: 'consumptionPoints', title: '消费总额', width: 120 },
|
align: 'right' as const,
|
||||||
{ dataIndex: 'balance', key: 'balance', title: '余额', width: 120 },
|
dataIndex: 'earnPoints',
|
||||||
|
key: 'earnPoints',
|
||||||
|
sorter: true,
|
||||||
|
sortOrder: getSortOrder('earnPoints'),
|
||||||
|
title: '获得总额',
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'right' as const,
|
||||||
|
dataIndex: 'consumptionPoints',
|
||||||
|
key: 'consumptionPoints',
|
||||||
|
sorter: true,
|
||||||
|
sortOrder: getSortOrder('consumptionPoints'),
|
||||||
|
title: '消费总额',
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'right' as const,
|
||||||
|
dataIndex: 'balance',
|
||||||
|
key: 'balance',
|
||||||
|
sorter: true,
|
||||||
|
sortOrder: getSortOrder('balance'),
|
||||||
|
title: '余额',
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
{ dataIndex: 'createTime', key: 'createTime', title: '创建时间', width: 180 },
|
{ dataIndex: 'createTime', key: 'createTime', title: '创建时间', width: 180 },
|
||||||
{ dataIndex: 'updateTime', key: 'updateTime', title: '修改时间', width: 180 },
|
{ dataIndex: 'updateTime', key: 'updateTime', title: '修改时间', width: 180 },
|
||||||
{ dataIndex: 'actions', key: 'actions', title: '操作', width: 260, fixed: 'right' as const },
|
{ dataIndex: 'actions', key: 'actions', title: '操作', width: 260, fixed: 'right' as const },
|
||||||
];
|
]);
|
||||||
|
|
||||||
|
function isBankSortOrder(value: string): value is BankSortOrder {
|
||||||
|
return value === 'ascend' || value === 'descend';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSortOrder(field: string) {
|
||||||
|
return query.sortField === field && isBankSortOrder(query.sortOrder)
|
||||||
|
? query.sortOrder
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatAmount(value: any) {
|
||||||
|
const rawValue = String(value ?? '0').replaceAll(',', '');
|
||||||
|
if (!rawValue || rawValue === 'null' || rawValue === 'undefined') {
|
||||||
|
return '0';
|
||||||
|
}
|
||||||
|
const [integerPart = '0', decimalPart = ''] = rawValue.split('.');
|
||||||
|
const isNegative = integerPart.startsWith('-');
|
||||||
|
const unsignedInteger = isNegative ? integerPart.slice(1) : integerPart;
|
||||||
|
const formattedInteger =
|
||||||
|
unsignedInteger.replaceAll(/\B(?=(\d{3})+(?!\d))/g, ',') || '0';
|
||||||
|
const trimmedDecimal = decimalPart.replaceAll(/0+$/g, '');
|
||||||
|
return `${isNegative ? '-' : ''}${formattedInteger}${trimmedDecimal ? `.${trimmedDecimal}` : ''}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildListParams() {
|
||||||
|
return {
|
||||||
|
...query,
|
||||||
|
lastId: isSortActive.value ? undefined : query.lastId,
|
||||||
|
pageNo: isSortActive.value ? query.pageNo : undefined,
|
||||||
|
sortField: isSortActive.value ? query.sortField : undefined,
|
||||||
|
sortOrder: isSortActive.value ? query.sortOrder : undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildStatisticsParams() {
|
||||||
|
return {
|
||||||
|
sysOrigin: query.sysOrigin,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadStatistics() {
|
||||||
|
if (!query.sysOrigin) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
statisticsLoading.value = true;
|
||||||
|
try {
|
||||||
|
const result = await getBankStatistics(buildStatisticsParams());
|
||||||
|
statistics.totalEarnPoints = result?.totalEarnPoints ?? 0;
|
||||||
|
statistics.totalConsumptionPoints = result?.totalConsumptionPoints ?? 0;
|
||||||
|
statistics.totalBalance = result?.totalBalance ?? 0;
|
||||||
|
} finally {
|
||||||
|
statisticsLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
sysOriginOptions,
|
sysOriginOptions,
|
||||||
@ -107,6 +205,7 @@ async function loadData(reset = false) {
|
|||||||
}
|
}
|
||||||
if (reset) {
|
if (reset) {
|
||||||
query.lastId = '';
|
query.lastId = '';
|
||||||
|
query.pageNo = 1;
|
||||||
list.value = [];
|
list.value = [];
|
||||||
notData.value = false;
|
notData.value = false;
|
||||||
}
|
}
|
||||||
@ -116,12 +215,20 @@ async function loadData(reset = false) {
|
|||||||
loadMoreLoading.value = true;
|
loadMoreLoading.value = true;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const result = await pageBank({ ...query });
|
const result = reset
|
||||||
|
? await Promise.all([pageBank(buildListParams()), loadStatistics()]).then(
|
||||||
|
([rows]) => rows,
|
||||||
|
)
|
||||||
|
: await pageBank(buildListParams());
|
||||||
const current = result || [];
|
const current = result || [];
|
||||||
notData.value = current.length <= 0;
|
notData.value = current.length < Number(query.limit || 20);
|
||||||
if (!notData.value) {
|
if (current.length > 0) {
|
||||||
list.value = [...list.value, ...current];
|
list.value = [...list.value, ...current];
|
||||||
query.lastId = String(list.value[list.value.length - 1]?.id || '');
|
if (isSortActive.value) {
|
||||||
|
query.pageNo += 1;
|
||||||
|
} else {
|
||||||
|
query.lastId = String(list.value[list.value.length - 1]?.id || '');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
@ -133,6 +240,20 @@ function handleSearch() {
|
|||||||
void loadData(true);
|
void loadData(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleTableChange(_: any, __: any, sorter: any) {
|
||||||
|
const activeSorter = Array.isArray(sorter) ? sorter[0] : sorter;
|
||||||
|
const field = String(activeSorter?.columnKey || '');
|
||||||
|
const order = String(activeSorter?.order || '');
|
||||||
|
if (isBankSortOrder(order) && sortableAmountKeys.has(field)) {
|
||||||
|
query.sortField = field;
|
||||||
|
query.sortOrder = order;
|
||||||
|
} else {
|
||||||
|
query.sortField = '';
|
||||||
|
query.sortOrder = '';
|
||||||
|
}
|
||||||
|
void loadData(true);
|
||||||
|
}
|
||||||
|
|
||||||
function openAction(record: Record<string, any>, type: 'deduct' | 'send') {
|
function openAction(record: Record<string, any>, type: 'deduct' | 'send') {
|
||||||
activeRow.value = record;
|
activeRow.value = record;
|
||||||
actionType.value = type;
|
actionType.value = type;
|
||||||
@ -165,11 +286,7 @@ async function submitAction() {
|
|||||||
}
|
}
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
if (actionType.value === 'send') {
|
await (actionType.value === 'send' ? sendMoney : deductMoney)({ ...actionForm });
|
||||||
await sendMoney({ ...actionForm });
|
|
||||||
} else {
|
|
||||||
await deductMoney({ ...actionForm });
|
|
||||||
}
|
|
||||||
message.success('操作成功');
|
message.success('操作成功');
|
||||||
actionModalOpen.value = false;
|
actionModalOpen.value = false;
|
||||||
await loadData(true);
|
await loadData(true);
|
||||||
@ -252,11 +369,28 @@ watch(
|
|||||||
<template>
|
<template>
|
||||||
<Page title="用户银行账户">
|
<Page title="用户银行账户">
|
||||||
<Card>
|
<Card>
|
||||||
|
<div class="statistics-card" :class="{ 'is-loading': statisticsLoading }">
|
||||||
|
<div class="statistics-item">
|
||||||
|
<span class="statistics-label">所有用户获得总额</span>
|
||||||
|
<strong>{{ formatAmount(statistics.totalEarnPoints) }}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="statistics-item">
|
||||||
|
<span class="statistics-label">所有用户消费总额</span>
|
||||||
|
<strong>{{ formatAmount(statistics.totalConsumptionPoints) }}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="statistics-item">
|
||||||
|
<span class="statistics-label">所有用户余额</span>
|
||||||
|
<strong>{{ formatAmount(statistics.totalBalance) }}</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
<SysOriginSelect v-model:value="query.sysOrigin" style="width: 140px"
|
<SysOriginSelect
|
||||||
|
v-model:value="query.sysOrigin"
|
||||||
:options="sysOriginOptions"
|
:options="sysOriginOptions"
|
||||||
></SysOriginSelect>
|
style="width: 140px"
|
||||||
|
/>
|
||||||
<AccountInput
|
<AccountInput
|
||||||
v-model:value="query.userId"
|
v-model:value="query.userId"
|
||||||
:sys-origin="query.sysOrigin"
|
:sys-origin="query.sysOrigin"
|
||||||
@ -279,6 +413,7 @@ watch(
|
|||||||
:pagination="false"
|
:pagination="false"
|
||||||
row-key="id"
|
row-key="id"
|
||||||
:scroll="{ x: 1500 }"
|
:scroll="{ x: 1500 }"
|
||||||
|
@change="handleTableChange"
|
||||||
>
|
>
|
||||||
<template #bodyCell="{ column, record, index }">
|
<template #bodyCell="{ column, record, index }">
|
||||||
<template v-if="column.key === 'index'">
|
<template v-if="column.key === 'index'">
|
||||||
@ -287,6 +422,15 @@ watch(
|
|||||||
<template v-else-if="column.key === 'userProfile'">
|
<template v-else-if="column.key === 'userProfile'">
|
||||||
<UserProfileLink :profile="record.userProfile" />
|
<UserProfileLink :profile="record.userProfile" />
|
||||||
</template>
|
</template>
|
||||||
|
<template
|
||||||
|
v-else-if="
|
||||||
|
column.key === 'earnPoints' ||
|
||||||
|
column.key === 'consumptionPoints' ||
|
||||||
|
column.key === 'balance'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ formatAmount(record[column.key]) }}
|
||||||
|
</template>
|
||||||
<template v-else-if="column.key === 'createTime'">
|
<template v-else-if="column.key === 'createTime'">
|
||||||
{{ formatDate(record.createTime) }}
|
{{ formatDate(record.createTime) }}
|
||||||
</template>
|
</template>
|
||||||
@ -438,7 +582,7 @@ watch(
|
|||||||
class="hidden-input"
|
class="hidden-input"
|
||||||
type="file"
|
type="file"
|
||||||
@change="handleImportChange"
|
@change="handleImportChange"
|
||||||
>
|
/>
|
||||||
<Button :loading="importLoading" block @click="chooseImportFile">
|
<Button :loading="importLoading" block @click="chooseImportFile">
|
||||||
选择文件并导入
|
选择文件并导入
|
||||||
</Button>
|
</Button>
|
||||||
@ -448,6 +592,39 @@ watch(
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.statistics-card {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
padding: 14px 16px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
background: #f8fafc;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statistics-card.is-loading {
|
||||||
|
opacity: 0.68;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statistics-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statistics-label {
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statistics-item strong {
|
||||||
|
color: #0f172a;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.25;
|
||||||
|
}
|
||||||
|
|
||||||
.toolbar {
|
.toolbar {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,10 +17,10 @@ import { listEmojiGroupsBySysOrigin } from '#/api/legacy/app-system';
|
|||||||
import { listBadgePictureBySysOrigin } from '#/api/legacy/badge';
|
import { listBadgePictureBySysOrigin } from '#/api/legacy/badge';
|
||||||
import { listGiftBySysOrigin } from '#/api/legacy/gift';
|
import { listGiftBySysOrigin } from '#/api/legacy/gift';
|
||||||
import {
|
import {
|
||||||
listNotFamilyBySysOriginType,
|
|
||||||
listSysOriginTypeList,
|
listSysOriginTypeList,
|
||||||
saveOrUpdatePropsActivityRewardGroup,
|
saveOrUpdatePropsActivityRewardGroup,
|
||||||
} from '#/api/legacy/props';
|
} from '#/api/legacy/props';
|
||||||
|
import { getResidentVipConfig } from '#/api/legacy/resident-activity';
|
||||||
import { getDisplaySysOriginText } from '#/views/system/shared';
|
import { getDisplaySysOriginText } from '#/views/system/shared';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -53,8 +53,11 @@ const draftLoading = ref(false);
|
|||||||
type DraftSourceOption = {
|
type DraftSourceOption = {
|
||||||
badgeName?: string;
|
badgeName?: string;
|
||||||
cover?: string;
|
cover?: string;
|
||||||
|
durationDays?: number;
|
||||||
id: number | string;
|
id: number | string;
|
||||||
|
level?: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
priceGold?: number;
|
||||||
remark?: string;
|
remark?: string;
|
||||||
sourceUrl?: string;
|
sourceUrl?: string;
|
||||||
};
|
};
|
||||||
@ -199,6 +202,35 @@ function needsRemoteOptions(type: string) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeVipResource(value: null | Record<string, any> | undefined) {
|
||||||
|
return {
|
||||||
|
cover: String(value?.cover || value?.coverUrl || '').trim(),
|
||||||
|
sourceUrl: String(
|
||||||
|
value?.sourceUrl || value?.resourceUrl || value?.url || value?.cover || '',
|
||||||
|
).trim(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickVipPreviewResource(item: Record<string, any>) {
|
||||||
|
const fields = [
|
||||||
|
'longBadge',
|
||||||
|
'shortBadge',
|
||||||
|
'avatarFrame',
|
||||||
|
'entryEffect',
|
||||||
|
'chatBubble',
|
||||||
|
'floatPicture',
|
||||||
|
'backgroundCard',
|
||||||
|
'effectImage',
|
||||||
|
];
|
||||||
|
for (const field of fields) {
|
||||||
|
const resource = normalizeVipResource(item[field]);
|
||||||
|
if (resource.cover || resource.sourceUrl) {
|
||||||
|
return resource;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { cover: '', sourceUrl: '' };
|
||||||
|
}
|
||||||
|
|
||||||
async function loadOptions(type: string) {
|
async function loadOptions(type: string) {
|
||||||
if (!type || !form.sysOrigin || !needsRemoteOptions(type)) {
|
if (!type || !form.sysOrigin || !needsRemoteOptions(type)) {
|
||||||
return;
|
return;
|
||||||
@ -243,13 +275,25 @@ async function loadOptions(type: string) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'NOBLE_VIP': {
|
case 'NOBLE_VIP': {
|
||||||
const list = await listNotFamilyBySysOriginType(form.sysOrigin, type);
|
const config = await getResidentVipConfig(form.sysOrigin);
|
||||||
store.list = list.map((item) => ({
|
const levels = Array.isArray(config?.levels) ? config.levels : [];
|
||||||
cover: item.cover || '',
|
store.list = levels
|
||||||
id: item.id ?? '',
|
.filter((item: Record<string, any>) => item.enabled && item.id)
|
||||||
name: item.name || '',
|
.map((item: Record<string, any>) => {
|
||||||
sourceUrl: item.sourceUrl || '',
|
const preview = pickVipPreviewResource(item);
|
||||||
}));
|
const durationDays = Number(item.durationDays || 30);
|
||||||
|
const level = Number(item.level || 0);
|
||||||
|
return {
|
||||||
|
cover: preview.cover,
|
||||||
|
durationDays,
|
||||||
|
id: item.id ?? '',
|
||||||
|
level,
|
||||||
|
name: String(item.displayName || item.levelCode || `VIP ${level}`),
|
||||||
|
priceGold: Number(item.priceGold || 0),
|
||||||
|
remark: `${durationDays} 天`,
|
||||||
|
sourceUrl: preview.sourceUrl,
|
||||||
|
};
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
@ -296,6 +340,10 @@ function handleDraftContentChange(value: number | string) {
|
|||||||
(item) => String(item.id) === String(value),
|
(item) => String(item.id) === String(value),
|
||||||
);
|
);
|
||||||
selectedDraftResource.value = option || null;
|
selectedDraftResource.value = option || null;
|
||||||
|
if (draft.type === 'NOBLE_VIP' && option) {
|
||||||
|
draft.quantity = option.durationDays || 30;
|
||||||
|
draft.remark = option.name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildRewardItem(): null | RewardGroupItem {
|
function buildRewardItem(): null | RewardGroupItem {
|
||||||
@ -311,7 +359,7 @@ function buildRewardItem(): null | RewardGroupItem {
|
|||||||
cover: selectedDraftResource.value?.cover || '',
|
cover: selectedDraftResource.value?.cover || '',
|
||||||
detailType: draft.type,
|
detailType: draft.type,
|
||||||
quantity: draft.quantity,
|
quantity: draft.quantity,
|
||||||
remark: draft.remark,
|
remark: draft.remark || selectedDraftResource.value?.remark || selectedDraftResource.value?.name || '',
|
||||||
sourceUrl: selectedDraftResource.value?.sourceUrl || '',
|
sourceUrl: selectedDraftResource.value?.sourceUrl || '',
|
||||||
type: mapping.value,
|
type: mapping.value,
|
||||||
};
|
};
|
||||||
@ -370,6 +418,10 @@ function validateDraft() {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (draft.type === 'NOBLE_VIP') {
|
||||||
|
draft.quantity = draft.quantity || selectedDraftResource.value?.durationDays || 30;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (draft.quantity === '' || Number.isNaN(Number(draft.quantity))) {
|
if (draft.quantity === '' || Number.isNaN(Number(draft.quantity))) {
|
||||||
message.warning('请输入数量/天数');
|
message.warning('请输入数量/天数');
|
||||||
return false;
|
return false;
|
||||||
@ -550,6 +602,13 @@ async function handleSubmit() {
|
|||||||
<Input v-model:value="draft.remark" placeholder="请输入描述" />
|
<Input v-model:value="draft.remark" placeholder="请输入描述" />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
||||||
|
<FormItem v-else-if="draft.type === 'NOBLE_VIP'" label="有效期">
|
||||||
|
<Input
|
||||||
|
:value="`${draft.quantity || selectedDraftResource?.durationDays || 30} 天`"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
|
||||||
<FormItem v-else label="数量/天数">
|
<FormItem v-else label="数量/天数">
|
||||||
<Input
|
<Input
|
||||||
v-model:value="draft.quantity"
|
v-model:value="draft.quantity"
|
||||||
|
|||||||
@ -116,7 +116,7 @@ export const PROPS_SOURCE_GROUP_TYPE_MAP: Record<
|
|||||||
> = {
|
> = {
|
||||||
AVATAR_FRAME: { value: 'PROPS', name: '头像框' },
|
AVATAR_FRAME: { value: 'PROPS', name: '头像框' },
|
||||||
RIDE: { value: 'PROPS', name: '座驾' },
|
RIDE: { value: 'PROPS', name: '座驾' },
|
||||||
NOBLE_VIP: { value: 'PROPS', name: '贵族' },
|
NOBLE_VIP: { value: 'PROPS', name: 'VIP' },
|
||||||
THEME: { value: 'PROPS', name: '房间背景主题' },
|
THEME: { value: 'PROPS', name: '房间背景主题' },
|
||||||
GIFT: { value: 'GIFT', name: '礼物' },
|
GIFT: { value: 'GIFT', name: '礼物' },
|
||||||
SPECIAL_ID: { value: 'SPECIAL_ID', name: '靓号' },
|
SPECIAL_ID: { value: 'SPECIAL_ID', name: '靓号' },
|
||||||
@ -318,6 +318,12 @@ export function formatRewardText(item: Record<string, any>) {
|
|||||||
quantity <= 0 ? '永久' : `${quantity}天`
|
quantity <= 0 ? '永久' : `${quantity}天`
|
||||||
}`;
|
}`;
|
||||||
}
|
}
|
||||||
|
if (detailType === 'NOBLE_VIP') {
|
||||||
|
const quantity = Number(item.quantity || 0);
|
||||||
|
return `${item.name || item.remark || item.content || '-'} ${
|
||||||
|
quantity <= 0 ? '' : `${quantity}天`
|
||||||
|
}`.trim();
|
||||||
|
}
|
||||||
if (detailType === 'GIFT' || detailType === 'FRAGMENTS') {
|
if (detailType === 'GIFT' || detailType === 'FRAGMENTS') {
|
||||||
return `数量:${item.quantity || 0}`;
|
return `数量:${item.quantity || 0}`;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user