金币权限
This commit is contained in:
parent
a38e98e29e
commit
9dbb25287d
23
apps/src/api/legacy/gm.ts
Normal file
23
apps/src/api/legacy/gm.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import type { LegacyPageResult } from '#/api/legacy/system';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
export async function submitGmGoldOperation(data: Record<string, any>) {
|
||||||
|
return requestClient.post<Record<string, any>>('/gm/gold-operation', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function pageGmGoldOperation(params: Record<string, any>) {
|
||||||
|
return requestClient.get<LegacyPageResult<Record<string, any>>>(
|
||||||
|
'/gm/gold-operation/page',
|
||||||
|
{
|
||||||
|
params,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function reviewGmGoldOperation(data: Record<string, any>) {
|
||||||
|
return requestClient.post<Record<string, any>>(
|
||||||
|
'/gm/gold-operation/review',
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -106,6 +106,16 @@ export async function updatePayFactory(data: Record<string, any>) {
|
|||||||
return requestClient.post('/sys-pay-factory/update', data);
|
return requestClient.post('/sys-pay-factory/update', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getPayFactoryConfig(params: Record<string, any>) {
|
||||||
|
return requestClient.get<Record<string, any>>('/sys-pay-factory/config', {
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function savePayFactoryConfig(data: Record<string, any>) {
|
||||||
|
return requestClient.post('/sys-pay-factory/config/save', data);
|
||||||
|
}
|
||||||
|
|
||||||
export async function addPayFactoryAssociatedChannels(
|
export async function addPayFactoryAssociatedChannels(
|
||||||
data: Array<Record<string, any>>,
|
data: Array<Record<string, any>>,
|
||||||
) {
|
) {
|
||||||
|
|||||||
30
apps/src/router/routes/modules/gm.ts
Normal file
30
apps/src/router/routes/modules/gm.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import type { RouteRecordRaw } from 'vue-router';
|
||||||
|
|
||||||
|
const routes: RouteRecordRaw[] = [
|
||||||
|
{
|
||||||
|
meta: {
|
||||||
|
icon: 'lucide:shield',
|
||||||
|
order: 49.4,
|
||||||
|
title: 'GM操作',
|
||||||
|
},
|
||||||
|
name: 'GmOperation',
|
||||||
|
path: '/gm/operation',
|
||||||
|
redirect: '/gm/operation/gold-operation',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'GmGoldOperation',
|
||||||
|
path: 'gold-operation',
|
||||||
|
component: () => import('#/views/gm/gold-operation.vue'),
|
||||||
|
meta: { title: '金币操作' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'GmGoldOperationReview',
|
||||||
|
path: 'gold-operation-review',
|
||||||
|
component: () => import('#/views/gm/gold-operation-review.vue'),
|
||||||
|
meta: { title: '金币操作审核' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default routes;
|
||||||
305
apps/src/views/gm/gold-operation-review.vue
Normal file
305
apps/src/views/gm/gold-operation-review.vue
Normal file
@ -0,0 +1,305 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { reactive, ref, watch } from 'vue';
|
||||||
|
|
||||||
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
DatePicker,
|
||||||
|
Input,
|
||||||
|
message,
|
||||||
|
Modal,
|
||||||
|
Pagination,
|
||||||
|
Select,
|
||||||
|
Space,
|
||||||
|
Table,
|
||||||
|
Tag,
|
||||||
|
} from 'antdv-next';
|
||||||
|
|
||||||
|
import {
|
||||||
|
pageGmGoldOperation,
|
||||||
|
reviewGmGoldOperation,
|
||||||
|
} from '#/api/legacy/gm';
|
||||||
|
import { getUserBaseInfoBySysOriginAccount } from '#/api/legacy/user';
|
||||||
|
import AccountInput from '#/components/account-input.vue';
|
||||||
|
import UserProfileLink from '#/views/operate/components/user-profile-link.vue';
|
||||||
|
import {
|
||||||
|
formatDate,
|
||||||
|
getDisplaySysOriginText,
|
||||||
|
} from '#/views/system/shared';
|
||||||
|
|
||||||
|
defineOptions({ name: 'GmGoldOperationReview' });
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
const reviewing = ref(false);
|
||||||
|
const list = ref<Array<Record<string, any>>>([]);
|
||||||
|
const total = ref(0);
|
||||||
|
const rangeDate = ref<[string, string] | null>(null);
|
||||||
|
const userSearch = ref('');
|
||||||
|
|
||||||
|
const query = reactive({
|
||||||
|
applicantId: '',
|
||||||
|
endTime: '',
|
||||||
|
operationType: undefined as number | undefined,
|
||||||
|
pageIndex: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
startTime: '',
|
||||||
|
status: 'PENDING',
|
||||||
|
userId: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ dataIndex: 'userProfile', key: 'userProfile', title: '用户', width: 240 },
|
||||||
|
{ dataIndex: 'sysOrigin', key: 'sysOrigin', title: '系统', width: 100 },
|
||||||
|
{ dataIndex: 'operationType', key: 'operationType', title: '类型', width: 100 },
|
||||||
|
{ dataIndex: 'quantity', key: 'quantity', title: '金币数量', width: 120 },
|
||||||
|
{ dataIndex: 'reasonName', key: 'reasonName', title: '原因', width: 120 },
|
||||||
|
{ dataIndex: 'remarks', key: 'remarks', title: '备注', width: 220 },
|
||||||
|
{ dataIndex: 'applicant', key: 'applicant', title: '申请人', width: 160 },
|
||||||
|
{ dataIndex: 'status', key: 'status', title: '状态', width: 110 },
|
||||||
|
{ dataIndex: 'reviewer', key: 'reviewer', title: '审核人', width: 160 },
|
||||||
|
{ dataIndex: 'time', key: 'time', title: '时间', width: 240 },
|
||||||
|
{ dataIndex: 'actions', key: 'actions', title: '操作', width: 160 },
|
||||||
|
];
|
||||||
|
|
||||||
|
watch(rangeDate, (value) => {
|
||||||
|
query.startTime = value?.[0] || '';
|
||||||
|
query.endTime = value?.[1] || '';
|
||||||
|
});
|
||||||
|
|
||||||
|
function parseEncodedValue(value: unknown) {
|
||||||
|
const raw = String(value ?? '');
|
||||||
|
const match = raw.match(/^\*(.*)\*$/);
|
||||||
|
if (!match) {
|
||||||
|
return {
|
||||||
|
accountType: '',
|
||||||
|
content: raw,
|
||||||
|
sysOrigin: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const body = match[1] || '';
|
||||||
|
return {
|
||||||
|
accountType: body.match(/accountType:([^,]*)/)?.[1] || '',
|
||||||
|
content: body.match(/content:(.*)$/)?.[1] || '',
|
||||||
|
sysOrigin: body.match(/sysOrigin:([^,]*)/)?.[1] || '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resolveUserId() {
|
||||||
|
const parsed = parseEncodedValue(userSearch.value);
|
||||||
|
const content = String(parsed.content || '').trim();
|
||||||
|
if (!content) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if (!parsed.accountType || parsed.accountType === 'LONG') {
|
||||||
|
return content.replace(/[^\d]/g, '');
|
||||||
|
}
|
||||||
|
if (!parsed.sysOrigin) {
|
||||||
|
message.warning('请选择系统');
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const result = await getUserBaseInfoBySysOriginAccount(parsed.sysOrigin, content);
|
||||||
|
const userId = result?.id || result?.userId;
|
||||||
|
if (!userId) {
|
||||||
|
message.warning('未找到用户');
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return String(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadData(reset = false) {
|
||||||
|
if (reset) {
|
||||||
|
query.pageIndex = 1;
|
||||||
|
}
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
query.userId = await resolveUserId();
|
||||||
|
const result = await pageGmGoldOperation({
|
||||||
|
...query,
|
||||||
|
applicantId: query.applicantId || undefined,
|
||||||
|
operationType: query.operationType,
|
||||||
|
userId: query.userId || undefined,
|
||||||
|
});
|
||||||
|
list.value = result.records || [];
|
||||||
|
total.value = result.total || 0;
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePageChange(page: number, pageSize: number) {
|
||||||
|
query.pageIndex = page;
|
||||||
|
query.pageSize = pageSize;
|
||||||
|
void loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
function statusColor(status?: string) {
|
||||||
|
if (status === 'APPROVED') {
|
||||||
|
return 'success';
|
||||||
|
}
|
||||||
|
if (status === 'REJECTED') {
|
||||||
|
return 'error';
|
||||||
|
}
|
||||||
|
return 'warning';
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleReview(record: Record<string, any>, approved: boolean) {
|
||||||
|
Modal.confirm({
|
||||||
|
title: approved ? '确认通过该金币操作?' : '确认拒绝该金币操作?',
|
||||||
|
okText: approved ? '通过' : '拒绝',
|
||||||
|
okType: approved ? 'primary' : 'danger',
|
||||||
|
async onOk() {
|
||||||
|
reviewing.value = true;
|
||||||
|
try {
|
||||||
|
await reviewGmGoldOperation({
|
||||||
|
approved,
|
||||||
|
id: record.id,
|
||||||
|
reviewRemark: approved ? '同意' : '拒绝',
|
||||||
|
});
|
||||||
|
message.success(approved ? '已通过并执行金币操作' : '已拒绝');
|
||||||
|
await loadData();
|
||||||
|
} finally {
|
||||||
|
reviewing.value = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadData(true);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page title="金币操作审核">
|
||||||
|
<Card>
|
||||||
|
<div class="toolbar">
|
||||||
|
<Select
|
||||||
|
v-model:value="query.status"
|
||||||
|
allow-clear
|
||||||
|
placeholder="状态"
|
||||||
|
style="width: 140px"
|
||||||
|
:options="[
|
||||||
|
{ label: '待审核', value: 'PENDING' },
|
||||||
|
{ label: '已通过', value: 'APPROVED' },
|
||||||
|
{ label: '已拒绝', value: 'REJECTED' },
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
v-model:value="query.operationType"
|
||||||
|
allow-clear
|
||||||
|
placeholder="操作类型"
|
||||||
|
style="width: 140px"
|
||||||
|
:options="[
|
||||||
|
{ label: '增加金币', value: 0 },
|
||||||
|
{ label: '扣除金币', value: 1 },
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
<AccountInput
|
||||||
|
v-model:value="userSearch"
|
||||||
|
default-select-type="LONG"
|
||||||
|
placeholder="用户ID"
|
||||||
|
style="width: 260px"
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
v-model:value="query.applicantId"
|
||||||
|
allow-clear
|
||||||
|
placeholder="申请人后台ID"
|
||||||
|
style="width: 160px"
|
||||||
|
/>
|
||||||
|
<DatePicker.RangePicker
|
||||||
|
v-model:value="rangeDate"
|
||||||
|
show-time
|
||||||
|
style="width: 360px"
|
||||||
|
value-format="x"
|
||||||
|
/>
|
||||||
|
<Button :loading="loading" type="primary" @click="loadData(true)">
|
||||||
|
搜索
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Table
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="list"
|
||||||
|
:loading="loading || reviewing"
|
||||||
|
:pagination="false"
|
||||||
|
row-key="id"
|
||||||
|
:scroll="{ x: 1650 }"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'userProfile'">
|
||||||
|
<UserProfileLink :profile="record.userProfile" />
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'sysOrigin'">
|
||||||
|
{{ getDisplaySysOriginText(record.sysOrigin) }}
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'operationType'">
|
||||||
|
<Tag :color="record.operationType === 0 ? 'red' : 'default'">
|
||||||
|
{{ record.operationName }}
|
||||||
|
</Tag>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'status'">
|
||||||
|
<Tag :color="statusColor(record.status)">
|
||||||
|
{{ record.statusName }}
|
||||||
|
</Tag>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'applicant'">
|
||||||
|
<div>{{ record.applicantName || '-' }}</div>
|
||||||
|
<div class="muted">{{ record.applicantId || '-' }}</div>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'reviewer'">
|
||||||
|
<div>{{ record.reviewerName || '-' }}</div>
|
||||||
|
<div class="muted">{{ record.reviewerId || '-' }}</div>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'time'">
|
||||||
|
<div>申请:{{ formatDate(record.createTime) }}</div>
|
||||||
|
<div>审核:{{ formatDate(record.reviewTime) }}</div>
|
||||||
|
<div>执行:{{ formatDate(record.executedTime) }}</div>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'actions'">
|
||||||
|
<Space v-if="record.status === 'PENDING'">
|
||||||
|
<Button size="small" type="link" @click="handleReview(record, true)">
|
||||||
|
通过
|
||||||
|
</Button>
|
||||||
|
<Button danger size="small" type="link" @click="handleReview(record, false)">
|
||||||
|
拒绝
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</Table>
|
||||||
|
|
||||||
|
<div class="pager">
|
||||||
|
<Pagination
|
||||||
|
:current="query.pageIndex"
|
||||||
|
:page-size="query.pageSize"
|
||||||
|
:total="total"
|
||||||
|
show-size-changer
|
||||||
|
@change="handlePageChange"
|
||||||
|
@showSizeChange="handlePageChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.toolbar {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pager {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.muted {
|
||||||
|
color: #8c8c8c;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
590
apps/src/views/gm/gold-operation.vue
Normal file
590
apps/src/views/gm/gold-operation.vue
Normal file
@ -0,0 +1,590 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, reactive, ref, watch } from 'vue';
|
||||||
|
|
||||||
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
DatePicker,
|
||||||
|
Form,
|
||||||
|
FormItem,
|
||||||
|
InputNumber,
|
||||||
|
message,
|
||||||
|
Modal,
|
||||||
|
Pagination,
|
||||||
|
Select,
|
||||||
|
Space,
|
||||||
|
Table,
|
||||||
|
Tag,
|
||||||
|
TextArea,
|
||||||
|
} from 'antdv-next';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
import { submitGmGoldOperation } from '#/api/legacy/gm';
|
||||||
|
import { getClsGoldRunningWater } from '#/api/legacy/operate';
|
||||||
|
import { listMembers } from '#/api/legacy/team';
|
||||||
|
import { getUserBaseInfoBySysOriginAccount } from '#/api/legacy/user';
|
||||||
|
import AccountInput from '#/components/account-input.vue';
|
||||||
|
import UserProfileLink from '#/views/operate/components/user-profile-link.vue';
|
||||||
|
import { formatDate } from '#/views/system/shared';
|
||||||
|
|
||||||
|
defineOptions({ name: 'GmGoldOperation' });
|
||||||
|
|
||||||
|
const INCOME_REASON_OPTIONS = [
|
||||||
|
{ label: '奖励', value: 1 },
|
||||||
|
{ label: '内部', value: 2 },
|
||||||
|
{ label: '工资', value: 3 },
|
||||||
|
{ label: '充值', value: 4 },
|
||||||
|
{ label: '其他', value: 5 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const DEDUCT_REASON_OPTIONS = [
|
||||||
|
{ label: '违规', value: 1 },
|
||||||
|
{ label: '多发', value: 2 },
|
||||||
|
{ label: '操作错误', value: 3 },
|
||||||
|
{ label: '其他', value: 4 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const TYPE_OPTIONS = [
|
||||||
|
{ label: '增加', value: 0 },
|
||||||
|
{ label: '减少', value: 1 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const DEFAULT_PAGE_SIZE = 100;
|
||||||
|
|
||||||
|
const open = ref(false);
|
||||||
|
const loading = ref(false);
|
||||||
|
const loadMoreLoading = ref(false);
|
||||||
|
const submitting = ref(false);
|
||||||
|
const list = ref<Array<Record<string, any>>>([]);
|
||||||
|
const notMore = ref(false);
|
||||||
|
const currentPage = ref(1);
|
||||||
|
const rangeDate = ref<[string, string] | null>(null);
|
||||||
|
const operators = ref<Array<Record<string, any>>>([]);
|
||||||
|
|
||||||
|
const query = reactive<Record<string, any>>({
|
||||||
|
backOperationUser: undefined,
|
||||||
|
context: '',
|
||||||
|
endTime: '',
|
||||||
|
limit: DEFAULT_PAGE_SIZE,
|
||||||
|
startTime: '',
|
||||||
|
type: undefined,
|
||||||
|
userId: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const form = reactive({
|
||||||
|
operationType: 0,
|
||||||
|
quantity: undefined as number | undefined,
|
||||||
|
reasonType: 1,
|
||||||
|
remarks: '',
|
||||||
|
usdQuantity: undefined as number | undefined,
|
||||||
|
userSearch: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ dataIndex: 'userProfile', key: 'userProfile', title: '用户', width: 240 },
|
||||||
|
{ dataIndex: 'type', key: 'type', title: '类型', width: 100 },
|
||||||
|
{ dataIndex: 'quantity', key: 'quantity', title: '金额', width: 130 },
|
||||||
|
{ dataIndex: 'balance', key: 'balance', title: '余额', width: 130 },
|
||||||
|
{ dataIndex: 'originName', key: 'originName', title: '原因', width: 220 },
|
||||||
|
{ dataIndex: 'remark', key: 'remark', title: '备注', width: 260 },
|
||||||
|
{ dataIndex: 'backOperationName', key: 'backOperationName', title: '操作人', width: 160 },
|
||||||
|
{ dataIndex: 'createTime', key: 'createTime', title: '操作时间', width: 180 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const reasonOptions = computed(() =>
|
||||||
|
form.operationType === 0 ? INCOME_REASON_OPTIONS : DEDUCT_REASON_OPTIONS,
|
||||||
|
);
|
||||||
|
|
||||||
|
const showUsdQuantity = computed(
|
||||||
|
() => form.operationType === 0 && [3, 4].includes(Number(form.reasonType)),
|
||||||
|
);
|
||||||
|
|
||||||
|
const operatorOptions = computed(() =>
|
||||||
|
operators.value.map((item) => ({
|
||||||
|
label: `${item.nickname || item.loginName || item.id} / ${item.loginName || item.id}`,
|
||||||
|
value: item.id,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
|
const currentPageList = computed(() => {
|
||||||
|
const pageSize = Number(query.limit || DEFAULT_PAGE_SIZE);
|
||||||
|
const startIndex = (currentPage.value - 1) * pageSize;
|
||||||
|
return list.value.slice(startIndex, startIndex + pageSize);
|
||||||
|
});
|
||||||
|
|
||||||
|
const paginationTotal = computed(() => {
|
||||||
|
const pageSize = Number(query.limit || DEFAULT_PAGE_SIZE);
|
||||||
|
if (notMore.value) {
|
||||||
|
return list.value.length;
|
||||||
|
}
|
||||||
|
return Math.max(list.value.length + pageSize, (currentPage.value + 1) * pageSize);
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
rangeDate,
|
||||||
|
(value) => {
|
||||||
|
query.startTime = value?.[0] || '';
|
||||||
|
query.endTime = value?.[1] || '';
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => form.operationType,
|
||||||
|
() => {
|
||||||
|
form.reasonType = reasonOptions.value[0]?.value || 1;
|
||||||
|
form.usdQuantity = undefined;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(showUsdQuantity, (value) => {
|
||||||
|
if (!value) {
|
||||||
|
form.usdQuantity = undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function createDefaultRange() {
|
||||||
|
const end = dayjs().endOf('day');
|
||||||
|
const start = dayjs().subtract(1, 'day').startOf('day');
|
||||||
|
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 resetForm() {
|
||||||
|
form.operationType = 0;
|
||||||
|
form.quantity = undefined;
|
||||||
|
form.reasonType = 1;
|
||||||
|
form.remarks = '';
|
||||||
|
form.usdQuantity = undefined;
|
||||||
|
form.userSearch = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseEncodedValue(value: unknown) {
|
||||||
|
const raw = String(value ?? '');
|
||||||
|
const match = raw.match(/^\*(.*)\*$/);
|
||||||
|
if (!match) {
|
||||||
|
return {
|
||||||
|
accountType: '',
|
||||||
|
content: raw,
|
||||||
|
sysOrigin: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const body = match[1] || '';
|
||||||
|
return {
|
||||||
|
accountType: body.match(/accountType:([^,]*)/)?.[1] || '',
|
||||||
|
content: body.match(/content:(.*)$/)?.[1] || '',
|
||||||
|
sysOrigin: body.match(/sysOrigin:([^,]*)/)?.[1] || '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resolveUserId() {
|
||||||
|
const parsed = parseEncodedValue(form.userSearch);
|
||||||
|
const content = String(parsed.content || '').trim();
|
||||||
|
if (!content) {
|
||||||
|
message.warning('请输入用户ID');
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if (!parsed.accountType || parsed.accountType === 'LONG') {
|
||||||
|
return content.replace(/[^\d]/g, '');
|
||||||
|
}
|
||||||
|
if (!parsed.sysOrigin) {
|
||||||
|
message.warning('请选择系统');
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const result = await getUserBaseInfoBySysOriginAccount(parsed.sysOrigin, content);
|
||||||
|
const userId = result?.id || result?.userId;
|
||||||
|
if (!userId) {
|
||||||
|
message.warning('未找到用户');
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return String(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateForm() {
|
||||||
|
if (!String(form.userSearch || '').trim()) {
|
||||||
|
message.warning('请输入用户ID');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!form.quantity || Number(form.quantity) <= 0) {
|
||||||
|
message.warning('请输入大于0的金币数量');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!String(form.remarks || '').trim()) {
|
||||||
|
message.warning('请输入备注');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatRunningWaterTime(value: any) {
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
const trimmed = value.trim();
|
||||||
|
if (/^\d+$/.test(trimmed)) {
|
||||||
|
return formatDate(Number(trimmed));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return formatDate(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRunningWater(record: Record<string, any>) {
|
||||||
|
return record.runningWater || record;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRowKey(record: Record<string, any>) {
|
||||||
|
return getRunningWater(record).id || record.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRecordType(record: Record<string, any>) {
|
||||||
|
return Number(getRunningWater(record).type);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAmountPrefix(record: Record<string, any>) {
|
||||||
|
return getRecordType(record) === 0 ? '+' : '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAmountClass(record: Record<string, any>) {
|
||||||
|
return getRecordType(record) === 0 ? 'amount--income' : 'amount--expense';
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterOperatorOption(input: string, option: any) {
|
||||||
|
return String(option?.label || '').toLowerCase().includes(input.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadOperators() {
|
||||||
|
operators.value = (await listMembers()) || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadData(reset = false) {
|
||||||
|
if (loading.value || loadMoreLoading.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!query.startTime || !query.endTime) {
|
||||||
|
message.warning('请选择时间范围');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const startYear = new Date(Number(query.startTime)).getFullYear();
|
||||||
|
const endYear = new Date(Number(query.endTime)).getFullYear();
|
||||||
|
if (startYear !== endYear) {
|
||||||
|
message.warning('不允许跨年查询');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reset) {
|
||||||
|
list.value = [];
|
||||||
|
notMore.value = false;
|
||||||
|
query.context = '';
|
||||||
|
currentPage.value = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reset) {
|
||||||
|
loading.value = true;
|
||||||
|
} else {
|
||||||
|
loadMoreLoading.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await getClsGoldRunningWater({
|
||||||
|
backOperationUser: query.backOperationUser || undefined,
|
||||||
|
context: query.context || undefined,
|
||||||
|
endTime: query.endTime,
|
||||||
|
limit: query.limit,
|
||||||
|
queryBackOperationUser: true,
|
||||||
|
startTime: query.startTime,
|
||||||
|
type: query.type,
|
||||||
|
userId: query.userId || undefined,
|
||||||
|
});
|
||||||
|
const waters = result?.waters || [];
|
||||||
|
list.value = reset ? waters : [...list.value, ...waters];
|
||||||
|
query.context = result?.context || '';
|
||||||
|
notMore.value = Boolean(result?.listOver) || !query.context;
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
loadMoreLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ensurePageLoaded(page: number) {
|
||||||
|
const pageSize = Number(query.limit || DEFAULT_PAGE_SIZE);
|
||||||
|
let guard = 0;
|
||||||
|
while (!notMore.value && list.value.length < page * pageSize && guard < page) {
|
||||||
|
const beforeLength = list.value.length;
|
||||||
|
const beforeContext = query.context;
|
||||||
|
await loadData(false);
|
||||||
|
guard += 1;
|
||||||
|
if (list.value.length === beforeLength && query.context === beforeContext) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handlePageChange(page: number) {
|
||||||
|
await ensurePageLoaded(page);
|
||||||
|
if (notMore.value) {
|
||||||
|
const maxPage = Math.max(1, Math.ceil(list.value.length / Number(query.limit || DEFAULT_PAGE_SIZE)));
|
||||||
|
currentPage.value = Math.min(page, maxPage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
currentPage.value = page;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSearch() {
|
||||||
|
await loadData(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSubmit() {
|
||||||
|
if (!validateForm()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const userId = await resolveUserId();
|
||||||
|
if (!userId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
submitting.value = true;
|
||||||
|
try {
|
||||||
|
const result = await submitGmGoldOperation({
|
||||||
|
operationType: form.operationType,
|
||||||
|
quantity: form.quantity,
|
||||||
|
reasonType: form.reasonType,
|
||||||
|
remarks: form.remarks,
|
||||||
|
usdQuantity: showUsdQuantity.value ? form.usdQuantity : undefined,
|
||||||
|
userId,
|
||||||
|
});
|
||||||
|
if (result?.status === 'PENDING') {
|
||||||
|
message.success('已提交 yumiadmin 审核');
|
||||||
|
} else {
|
||||||
|
message.success('金币操作已执行');
|
||||||
|
}
|
||||||
|
await loadData(true);
|
||||||
|
open.value = false;
|
||||||
|
resetForm();
|
||||||
|
} finally {
|
||||||
|
submitting.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
applyRangeDate(createDefaultRange());
|
||||||
|
void loadOperators();
|
||||||
|
void loadData(true);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page title="金币操作">
|
||||||
|
<Card>
|
||||||
|
<div class="page-head">
|
||||||
|
<div>
|
||||||
|
<div class="page-head__title">后台金币增减记录</div>
|
||||||
|
<div class="page-head__desc">查询所有管理员给用户发放或扣除金币的操作流水</div>
|
||||||
|
</div>
|
||||||
|
<Button type="primary" @click="open = true">金币操作</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="toolbar">
|
||||||
|
<DatePicker.RangePicker
|
||||||
|
v-model:value="rangeDate"
|
||||||
|
show-time
|
||||||
|
style="width: 360px"
|
||||||
|
value-format="x"
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
v-model:value="query.type"
|
||||||
|
allow-clear
|
||||||
|
placeholder="类型"
|
||||||
|
style="width: 140px"
|
||||||
|
:options="TYPE_OPTIONS"
|
||||||
|
/>
|
||||||
|
<AccountInput
|
||||||
|
v-model:value="query.userId"
|
||||||
|
default-select-type="LONG"
|
||||||
|
placeholder="用户ID"
|
||||||
|
style="width: 240px"
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
v-model:value="query.backOperationUser"
|
||||||
|
allow-clear
|
||||||
|
show-search
|
||||||
|
placeholder="操作人"
|
||||||
|
style="width: 220px"
|
||||||
|
:filter-option="filterOperatorOption"
|
||||||
|
:options="operatorOptions"
|
||||||
|
/>
|
||||||
|
<Button :loading="loading" type="primary" @click="handleSearch">查询</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Table
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="currentPageList"
|
||||||
|
:loading="loading"
|
||||||
|
:pagination="false"
|
||||||
|
:row-key="getRowKey"
|
||||||
|
:scroll="{ x: 1420 }"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'userProfile'">
|
||||||
|
<UserProfileLink :profile="record.userProfile" />
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'type'">
|
||||||
|
<Tag :color="getRecordType(record) === 0 ? 'green' : 'red'">
|
||||||
|
{{ getRecordType(record) === 0 ? '增加' : '减少' }}
|
||||||
|
</Tag>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'quantity'">
|
||||||
|
<span :class="['amount', getAmountClass(record)]">
|
||||||
|
{{ getAmountPrefix(record) }}{{ getRunningWater(record).quantity ?? 0 }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'balance'">
|
||||||
|
{{ getRunningWater(record).balance ?? '-' }}
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'originName'">
|
||||||
|
{{ getRunningWater(record).originName || '-' }}
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'remark'">
|
||||||
|
{{ getRunningWater(record).remark || '-' }}
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'backOperationName'">
|
||||||
|
{{ record.backOperationName || '-' }}
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'createTime'">
|
||||||
|
{{ formatRunningWaterTime(getRunningWater(record).createTime) }}
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</Table>
|
||||||
|
|
||||||
|
<div v-if="list.length > 0" class="pagination">
|
||||||
|
<Pagination
|
||||||
|
:current="currentPage"
|
||||||
|
:disabled="loading || loadMoreLoading"
|
||||||
|
:page-size="query.limit"
|
||||||
|
:total="paginationTotal"
|
||||||
|
show-less-items
|
||||||
|
@change="handlePageChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
v-model:open="open"
|
||||||
|
title="金币操作"
|
||||||
|
:confirm-loading="submitting"
|
||||||
|
width="560px"
|
||||||
|
@cancel="resetForm"
|
||||||
|
@ok="handleSubmit"
|
||||||
|
>
|
||||||
|
<Form layout="vertical">
|
||||||
|
<FormItem label="用户ID">
|
||||||
|
<AccountInput
|
||||||
|
v-model:value="form.userSearch"
|
||||||
|
default-select-type="LONG"
|
||||||
|
placeholder="请输入长ID或短ID"
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="操作类型">
|
||||||
|
<Select
|
||||||
|
v-model:value="form.operationType"
|
||||||
|
:options="[
|
||||||
|
{ label: '增加金币', value: 0 },
|
||||||
|
{ label: '扣除金币', value: 1 },
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<Space class="form-row" :size="12">
|
||||||
|
<FormItem class="form-row__item" label="金币数量">
|
||||||
|
<InputNumber
|
||||||
|
v-model:value="form.quantity"
|
||||||
|
:min="0.01"
|
||||||
|
:precision="2"
|
||||||
|
class="full-input"
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem class="form-row__item" label="原因">
|
||||||
|
<Select v-model:value="form.reasonType" :options="reasonOptions" />
|
||||||
|
</FormItem>
|
||||||
|
</Space>
|
||||||
|
<FormItem v-if="showUsdQuantity" label="折算USD金额">
|
||||||
|
<InputNumber
|
||||||
|
v-model:value="form.usdQuantity"
|
||||||
|
:min="0"
|
||||||
|
:precision="2"
|
||||||
|
class="full-input"
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="备注">
|
||||||
|
<TextArea
|
||||||
|
v-model:value="form.remarks"
|
||||||
|
:maxlength="500"
|
||||||
|
:rows="4"
|
||||||
|
show-count
|
||||||
|
placeholder="请输入操作原因和备注"
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.page-head {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-head__title {
|
||||||
|
color: #111827;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-head__desc {
|
||||||
|
color: #6b7280;
|
||||||
|
font-size: 13px;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount--income {
|
||||||
|
color: #16a34a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount--expense {
|
||||||
|
color: #dc2626;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-row {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-row__item {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.full-input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
231
apps/src/views/operate/components/pay-factory-config-modal.vue
Normal file
231
apps/src/views/operate/components/pay-factory-config-modal.vue
Normal file
@ -0,0 +1,231 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { reactive, ref, watch } from 'vue';
|
||||||
|
|
||||||
|
import SysOriginSelect from '#/components/sys-origin-select.vue';
|
||||||
|
import {
|
||||||
|
getPayFactoryConfig,
|
||||||
|
savePayFactoryConfig,
|
||||||
|
} from '#/api/legacy/pay';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Form,
|
||||||
|
FormItem,
|
||||||
|
Input,
|
||||||
|
Modal,
|
||||||
|
Select,
|
||||||
|
Switch,
|
||||||
|
TextArea,
|
||||||
|
message,
|
||||||
|
} from 'antdv-next';
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
open: boolean;
|
||||||
|
row?: Record<string, any>;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
row: () => ({}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
close: [];
|
||||||
|
success: [];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
const saving = ref(false);
|
||||||
|
const form = reactive<Record<string, any>>({
|
||||||
|
enabled: true,
|
||||||
|
env: 'PROD',
|
||||||
|
factoryCode: '',
|
||||||
|
gatewayBaseUrl: '',
|
||||||
|
id: '',
|
||||||
|
merAccount: '',
|
||||||
|
merNo: '',
|
||||||
|
notifyUrl: '',
|
||||||
|
platformRsaPublicKey: '',
|
||||||
|
rsaPrivateKey: '',
|
||||||
|
sysOrigin: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const envOptions = [
|
||||||
|
{ label: 'PROD', value: 'PROD' },
|
||||||
|
{ label: 'TEST', value: 'TEST' },
|
||||||
|
{ label: '通用', value: '' },
|
||||||
|
];
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.open,
|
||||||
|
(open) => {
|
||||||
|
if (!open) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const row = props.row || {};
|
||||||
|
Object.assign(form, {
|
||||||
|
enabled: true,
|
||||||
|
env: 'PROD',
|
||||||
|
factoryCode: row.factoryCode || '',
|
||||||
|
gatewayBaseUrl: '',
|
||||||
|
id: '',
|
||||||
|
merAccount: '',
|
||||||
|
merNo: '',
|
||||||
|
notifyUrl: '',
|
||||||
|
platformRsaPublicKey: '',
|
||||||
|
rsaPrivateKey: '',
|
||||||
|
sysOrigin: '',
|
||||||
|
});
|
||||||
|
if (form.factoryCode) {
|
||||||
|
void loadConfig();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
function resetConfigFields() {
|
||||||
|
Object.assign(form, {
|
||||||
|
enabled: true,
|
||||||
|
gatewayBaseUrl: '',
|
||||||
|
id: '',
|
||||||
|
merAccount: '',
|
||||||
|
merNo: '',
|
||||||
|
notifyUrl: '',
|
||||||
|
platformRsaPublicKey: '',
|
||||||
|
rsaPrivateKey: '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadConfig() {
|
||||||
|
if (!form.factoryCode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const scope = {
|
||||||
|
env: form.env || '',
|
||||||
|
factoryCode: form.factoryCode,
|
||||||
|
sysOrigin: form.sysOrigin || '',
|
||||||
|
};
|
||||||
|
const result = await getPayFactoryConfig(scope);
|
||||||
|
resetConfigFields();
|
||||||
|
Object.assign(form, scope, result || {});
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateForm() {
|
||||||
|
const requiredFields: Array<[string, string]> = [
|
||||||
|
['merAccount', '请输入商户标识'],
|
||||||
|
['merNo', '请输入商户编号'],
|
||||||
|
['gatewayBaseUrl', '请输入网关地址'],
|
||||||
|
['rsaPrivateKey', '请输入商户私钥'],
|
||||||
|
['platformRsaPublicKey', '请输入平台公钥'],
|
||||||
|
['notifyUrl', '请输入回调地址'],
|
||||||
|
];
|
||||||
|
for (const [key, text] of requiredFields) {
|
||||||
|
if (!String(form[key] || '').trim()) {
|
||||||
|
message.warning(text);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSubmit() {
|
||||||
|
if (!validateForm()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
saving.value = true;
|
||||||
|
try {
|
||||||
|
await savePayFactoryConfig({ ...form });
|
||||||
|
message.success('保存成功');
|
||||||
|
emit('success');
|
||||||
|
emit('close');
|
||||||
|
} finally {
|
||||||
|
saving.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Modal
|
||||||
|
:confirm-loading="saving"
|
||||||
|
:open="open"
|
||||||
|
destroy-on-close
|
||||||
|
title="MiFaPay 配置"
|
||||||
|
width="760px"
|
||||||
|
@cancel="emit('close')"
|
||||||
|
@ok="handleSubmit"
|
||||||
|
>
|
||||||
|
<Form layout="vertical">
|
||||||
|
<div class="scope-row">
|
||||||
|
<FormItem label="厂商Code">
|
||||||
|
<Input v-model:value="form.factoryCode" disabled />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="环境">
|
||||||
|
<Select
|
||||||
|
v-model:value="form.env"
|
||||||
|
:options="envOptions"
|
||||||
|
@change="loadConfig"
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="来源系统">
|
||||||
|
<SysOriginSelect
|
||||||
|
v-model:value="form.sysOrigin"
|
||||||
|
allow-clear
|
||||||
|
placeholder="通用"
|
||||||
|
@change="loadConfig"
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label=" ">
|
||||||
|
<Button :loading="loading" @click="loadConfig">读取配置</Button>
|
||||||
|
</FormItem>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid">
|
||||||
|
<FormItem label="商户标识 merAccount">
|
||||||
|
<Input v-model:value="form.merAccount" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="商户编号 merNo">
|
||||||
|
<Input v-model:value="form.merNo" />
|
||||||
|
</FormItem>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<FormItem label="网关地址 gatewayBaseUrl">
|
||||||
|
<Input v-model:value="form.gatewayBaseUrl" />
|
||||||
|
</FormItem>
|
||||||
|
|
||||||
|
<FormItem label="商户RSA私钥">
|
||||||
|
<TextArea v-model:value="form.rsaPrivateKey" :rows="5" />
|
||||||
|
</FormItem>
|
||||||
|
|
||||||
|
<FormItem label="平台RSA公钥">
|
||||||
|
<TextArea v-model:value="form.platformRsaPublicKey" :rows="5" />
|
||||||
|
</FormItem>
|
||||||
|
|
||||||
|
<FormItem label="回调地址 notifyUrl">
|
||||||
|
<Input v-model:value="form.notifyUrl" />
|
||||||
|
</FormItem>
|
||||||
|
|
||||||
|
<FormItem label="启用">
|
||||||
|
<Switch v-model:checked="form.enabled" />
|
||||||
|
</FormItem>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.grid {
|
||||||
|
column-gap: 12px;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.scope-row {
|
||||||
|
column-gap: 12px;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 180px 130px minmax(180px, 1fr) 96px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -16,6 +16,7 @@ import {
|
|||||||
} from 'antdv-next';
|
} from 'antdv-next';
|
||||||
|
|
||||||
import PayFactoryAssociatedModal from './components/pay-factory-associated-modal.vue';
|
import PayFactoryAssociatedModal from './components/pay-factory-associated-modal.vue';
|
||||||
|
import PayFactoryConfigModal from './components/pay-factory-config-modal.vue';
|
||||||
import PayFactoryEditModal from './components/pay-factory-edit-modal.vue';
|
import PayFactoryEditModal from './components/pay-factory-edit-modal.vue';
|
||||||
|
|
||||||
defineOptions({ name: 'OperatePayFactory' });
|
defineOptions({ name: 'OperatePayFactory' });
|
||||||
@ -25,6 +26,7 @@ const total = ref(0);
|
|||||||
const list = ref<Array<Record<string, any>>>([]);
|
const list = ref<Array<Record<string, any>>>([]);
|
||||||
const formOpen = ref(false);
|
const formOpen = ref(false);
|
||||||
const associatedOpen = ref(false);
|
const associatedOpen = ref(false);
|
||||||
|
const configOpen = ref(false);
|
||||||
const activeRow = ref<Record<string, any>>({});
|
const activeRow = ref<Record<string, any>>({});
|
||||||
|
|
||||||
const query = reactive<Record<string, any>>({
|
const query = reactive<Record<string, any>>({
|
||||||
@ -39,7 +41,7 @@ const columns: any[] = [
|
|||||||
{ dataIndex: 'factoryName', key: 'factoryName', title: '名称', width: 220 },
|
{ dataIndex: 'factoryName', key: 'factoryName', title: '名称', width: 220 },
|
||||||
{ dataIndex: 'factoryIcon', key: 'factoryIcon', title: 'Icon', width: 120 },
|
{ dataIndex: 'factoryIcon', key: 'factoryIcon', title: 'Icon', width: 120 },
|
||||||
{ dataIndex: 'createTime', key: 'createTime', title: '创建时间', width: 180 },
|
{ dataIndex: 'createTime', key: 'createTime', title: '创建时间', width: 180 },
|
||||||
{ dataIndex: 'actions', key: 'actions', title: '操作', width: 140 },
|
{ dataIndex: 'actions', key: 'actions', title: '操作', width: 220 },
|
||||||
];
|
];
|
||||||
|
|
||||||
async function loadData(reset = false) {
|
async function loadData(reset = false) {
|
||||||
@ -81,6 +83,11 @@ function handleAssociated(record: Record<string, any>) {
|
|||||||
associatedOpen.value = true;
|
associatedOpen.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleConfig(record: Record<string, any>) {
|
||||||
|
activeRow.value = { ...record };
|
||||||
|
configOpen.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
void loadData(true);
|
void loadData(true);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -126,6 +133,7 @@ void loadData(true);
|
|||||||
<Button type="link" @click="handleAssociated(record)">
|
<Button type="link" @click="handleAssociated(record)">
|
||||||
关联渠道
|
关联渠道
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button type="link" @click="handleConfig(record)">配置</Button>
|
||||||
<Button type="link" @click="handleEdit(record)">编辑</Button>
|
<Button type="link" @click="handleEdit(record)">编辑</Button>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
@ -155,6 +163,12 @@ void loadData(true);
|
|||||||
:open="associatedOpen"
|
:open="associatedOpen"
|
||||||
@close="associatedOpen = false"
|
@close="associatedOpen = false"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<PayFactoryConfigModal
|
||||||
|
:open="configOpen"
|
||||||
|
:row="activeRow"
|
||||||
|
@close="configOpen = false"
|
||||||
|
/>
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@ -46,7 +46,6 @@ import {
|
|||||||
USER_TYPE_OPTIONS,
|
USER_TYPE_OPTIONS,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
import UserAuthInfoDrawer from './components/user-auth-info-drawer.vue';
|
import UserAuthInfoDrawer from './components/user-auth-info-drawer.vue';
|
||||||
import UserBalanceHandleModal from './components/user-balance-handle-modal.vue';
|
|
||||||
import UserResetPasswordModal from './components/user-reset-password-modal.vue';
|
import UserResetPasswordModal from './components/user-reset-password-modal.vue';
|
||||||
import {
|
import {
|
||||||
getUserProfileAvatar,
|
getUserProfileAvatar,
|
||||||
@ -98,12 +97,9 @@ const accountLogOpen = ref(false);
|
|||||||
const violationOpen = ref(false);
|
const violationOpen = ref(false);
|
||||||
const authInfoOpen = ref(false);
|
const authInfoOpen = ref(false);
|
||||||
const resetPasswordOpen = ref(false);
|
const resetPasswordOpen = ref(false);
|
||||||
const balanceOpen = ref(false);
|
|
||||||
|
|
||||||
const activeUserId = ref<number | string>('');
|
const activeUserId = ref<number | string>('');
|
||||||
const activeUserRecord = ref<Record<string, any> | null>(null);
|
const activeUserRecord = ref<Record<string, any> | null>(null);
|
||||||
const balanceAction = ref<'deduct' | 'reward'>('reward');
|
|
||||||
const balanceType = ref<'diamond' | 'gameCoupon' | 'gold'>('gold');
|
|
||||||
|
|
||||||
const query = reactive(createQuery());
|
const query = reactive(createQuery());
|
||||||
|
|
||||||
@ -120,7 +116,7 @@ const columns = [
|
|||||||
{ dataIndex: 'accountStatus', key: 'accountStatus', title: '账户', width: 120 },
|
{ dataIndex: 'accountStatus', key: 'accountStatus', title: '账户', width: 120 },
|
||||||
{ dataIndex: 'registerSource', key: 'registerSource', title: '注册来源', width: 180 },
|
{ dataIndex: 'registerSource', key: 'registerSource', title: '注册来源', width: 180 },
|
||||||
{ dataIndex: 'time', key: 'time', title: '时间', width: 220 },
|
{ dataIndex: 'time', key: 'time', title: '时间', width: 220 },
|
||||||
{ dataIndex: 'actions', key: 'actions', title: '操作', width: 320 },
|
{ dataIndex: 'actions', key: 'actions', title: '操作', width: 260 },
|
||||||
];
|
];
|
||||||
|
|
||||||
const filteredCountries = computed(() => {
|
const filteredCountries = computed(() => {
|
||||||
@ -295,22 +291,11 @@ function openResetPassword(record: Record<string, any>) {
|
|||||||
resetPasswordOpen.value = true;
|
resetPasswordOpen.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function openBalanceModal(
|
|
||||||
record: Record<string, any>,
|
|
||||||
type: 'diamond' | 'gameCoupon' | 'gold',
|
|
||||||
action: 'deduct' | 'reward',
|
|
||||||
) {
|
|
||||||
activeUserId.value = record.userProfile?.id || '';
|
|
||||||
balanceType.value = type;
|
|
||||||
balanceAction.value = action;
|
|
||||||
balanceOpen.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildEncodedAccount(sysOrigin: string, userId: number | string) {
|
function buildEncodedAccount(sysOrigin: string, userId: number | string) {
|
||||||
return `*type:USER,accountType:LONG,sysOrigin:${sysOrigin},content:${userId}*`;
|
return `*type:USER,accountType:LONG,sysOrigin:${sysOrigin},content:${userId}*`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function openRunningWater(record: Record<string, any>, kind: 'diamond' | 'gameCoupon' | 'gold') {
|
function openRunningWater(record: Record<string, any>, kind: 'diamond' | 'gold') {
|
||||||
const userId = record.userProfile?.id;
|
const userId = record.userProfile?.id;
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
return;
|
return;
|
||||||
@ -333,12 +318,7 @@ function openRunningWater(record: Record<string, any>, kind: 'diamond' | 'gameCo
|
|||||||
path: '/user/diamond-run-water',
|
path: '/user/diamond-run-water',
|
||||||
query: { sysOrigin, userId: encoded },
|
query: { sysOrigin, userId: encoded },
|
||||||
});
|
});
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
router.push({
|
|
||||||
path: '/game/coupon/running/water',
|
|
||||||
query: { userId: encoded },
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function copyUserId(value?: number | string) {
|
async function copyUserId(value?: number | string) {
|
||||||
@ -515,18 +495,6 @@ void loadCountries();
|
|||||||
<Button size="small" type="link" @click="openAccountLog(record)">
|
<Button size="small" type="link" @click="openAccountLog(record)">
|
||||||
处理记录
|
处理记录
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="small" type="link" @click="openBalanceModal(record, 'gold', 'reward')">
|
|
||||||
金币奖励
|
|
||||||
</Button>
|
|
||||||
<Button size="small" type="link" @click="openBalanceModal(record, 'gold', 'deduct')">
|
|
||||||
金币扣除
|
|
||||||
</Button>
|
|
||||||
<Button size="small" type="link" @click="openBalanceModal(record, 'diamond', 'reward')">
|
|
||||||
钻石奖励
|
|
||||||
</Button>
|
|
||||||
<Button size="small" type="link" @click="openBalanceModal(record, 'diamond', 'deduct')">
|
|
||||||
钻石扣除
|
|
||||||
</Button>
|
|
||||||
<Button size="small" type="link" @click="openAuthInfo(record)">
|
<Button size="small" type="link" @click="openAuthInfo(record)">
|
||||||
认证信息
|
认证信息
|
||||||
</Button>
|
</Button>
|
||||||
@ -538,14 +506,6 @@ void loadCountries();
|
|||||||
>
|
>
|
||||||
重置密码
|
重置密码
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
|
||||||
v-if="activeTab === 'table'"
|
|
||||||
size="small"
|
|
||||||
type="link"
|
|
||||||
@click="openRunningWater(record, 'gameCoupon')"
|
|
||||||
>
|
|
||||||
游戏券收支
|
|
||||||
</Button>
|
|
||||||
</Space>
|
</Space>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
@ -599,14 +559,6 @@ void loadCountries();
|
|||||||
@close="resetPasswordOpen = false"
|
@close="resetPasswordOpen = false"
|
||||||
@success="resetPasswordOpen = false"
|
@success="resetPasswordOpen = false"
|
||||||
/>
|
/>
|
||||||
<UserBalanceHandleModal
|
|
||||||
:action="balanceAction"
|
|
||||||
:open="balanceOpen"
|
|
||||||
:type="balanceType"
|
|
||||||
:user-id="activeUserId"
|
|
||||||
@close="balanceOpen = false"
|
|
||||||
@success="balanceOpen = false; loadData()"
|
|
||||||
/>
|
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@ -48,7 +48,7 @@ const fullscreen = ref(false);
|
|||||||
const visualMode = ref(false);
|
const visualMode = ref(false);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const dashboard = ref<CountryDashboardResult | null>(null);
|
const dashboard = ref<CountryDashboardResult | null>(null);
|
||||||
const upDashboards = ref<{
|
const arpuDashboards = ref<{
|
||||||
day: CountryDashboardResult | null;
|
day: CountryDashboardResult | null;
|
||||||
threeDay: CountryDashboardResult | null;
|
threeDay: CountryDashboardResult | null;
|
||||||
week: CountryDashboardResult | null;
|
week: CountryDashboardResult | null;
|
||||||
@ -282,12 +282,12 @@ const appDataCards = computed(() => {
|
|||||||
{ label: '30 日留存率', value: formatPercent(summary.d30RetentionRate) },
|
{ label: '30 日留存率', value: formatPercent(summary.d30RetentionRate) },
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
const upValueCards = computed(() => {
|
const arpuCards = computed(() => {
|
||||||
return [
|
return [
|
||||||
{ label: '日 UP 值', value: formatUpValue(upDashboards.value.day) },
|
{ label: '日 ARPU', value: formatArpu(arpuDashboards.value.day) },
|
||||||
{ label: '3 日 UP 值', value: formatUpValue(upDashboards.value.threeDay) },
|
{ label: '3 日 ARPU', value: formatArpu(arpuDashboards.value.threeDay) },
|
||||||
{ label: '周 UP 值', value: formatUpValue(upDashboards.value.week) },
|
{ label: '周 ARPU', value: formatArpu(arpuDashboards.value.week) },
|
||||||
{ label: '筛选 UP 值', value: formatAmount(calcUpValue(visibleSummary.value)) },
|
{ label: '筛选 ARPU', value: formatAmount(calcArpu(visibleSummary.value)) },
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -372,7 +372,7 @@ async function loadDashboard() {
|
|||||||
}
|
}
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
const [mainDashboard, dayUpDashboard, threeDayUpDashboard, weekUpDashboard] =
|
const [mainDashboard, dayArpuDashboard, threeDayArpuDashboard, weekArpuDashboard] =
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
countryDashboard(buildParams()),
|
countryDashboard(buildParams()),
|
||||||
countryDashboard(buildRecentDayParams(1)),
|
countryDashboard(buildRecentDayParams(1)),
|
||||||
@ -380,14 +380,14 @@ async function loadDashboard() {
|
|||||||
countryDashboard(buildRecentDayParams(7)),
|
countryDashboard(buildRecentDayParams(7)),
|
||||||
]);
|
]);
|
||||||
dashboard.value = mainDashboard;
|
dashboard.value = mainDashboard;
|
||||||
upDashboards.value = {
|
arpuDashboards.value = {
|
||||||
day: dayUpDashboard,
|
day: dayArpuDashboard,
|
||||||
threeDay: threeDayUpDashboard,
|
threeDay: threeDayArpuDashboard,
|
||||||
week: weekUpDashboard,
|
week: weekArpuDashboard,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
dashboard.value = null;
|
dashboard.value = null;
|
||||||
upDashboards.value = {
|
arpuDashboards.value = {
|
||||||
day: null,
|
day: null,
|
||||||
threeDay: null,
|
threeDay: null,
|
||||||
week: null,
|
week: null,
|
||||||
@ -495,7 +495,7 @@ function resolveMetricRate(record: Record<string, any>, key: string) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function calcUpValue(summary: Record<string, any>) {
|
function calcArpu(summary: Record<string, any>) {
|
||||||
const activeUser = toAmount(summary.dailyActiveUser);
|
const activeUser = toAmount(summary.dailyActiveUser);
|
||||||
if (activeUser === 0) {
|
if (activeUser === 0) {
|
||||||
return 0;
|
return 0;
|
||||||
@ -503,9 +503,9 @@ function calcUpValue(summary: Record<string, any>) {
|
|||||||
return toAmount(summary.totalRecharge) / activeUser;
|
return toAmount(summary.totalRecharge) / activeUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatUpValue(result?: CountryDashboardResult | null) {
|
function formatArpu(result?: CountryDashboardResult | null) {
|
||||||
const summary = buildVisibleSummary(filterRecordsBySelectedCountries(result?.records || []));
|
const summary = buildVisibleSummary(filterRecordsBySelectedCountries(result?.records || []));
|
||||||
return formatAmount(calcUpValue(summary));
|
return formatAmount(calcArpu(summary));
|
||||||
}
|
}
|
||||||
|
|
||||||
function emptySummary(): SummaryMetric {
|
function emptySummary(): SummaryMetric {
|
||||||
@ -887,10 +887,10 @@ onBeforeUnmount(() => {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section class="panel metric-section">
|
<section class="panel metric-section">
|
||||||
<div class="metric-section__title">UP 值</div>
|
<div class="metric-section__title">ARPU</div>
|
||||||
<div class="metric-card-grid metric-card-grid--up">
|
<div class="metric-card-grid metric-card-grid--arpu">
|
||||||
<div
|
<div
|
||||||
v-for="item in upValueCards"
|
v-for="item in arpuCards"
|
||||||
:key="item.label"
|
:key="item.label"
|
||||||
class="metric-card"
|
class="metric-card"
|
||||||
>
|
>
|
||||||
@ -1074,7 +1074,7 @@ onBeforeUnmount(() => {
|
|||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
|
|
||||||
.metric-card-grid--up {
|
.metric-card-grid--arpu {
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1195,7 +1195,7 @@ onBeforeUnmount(() => {
|
|||||||
.metric-section-grid,
|
.metric-section-grid,
|
||||||
.metric-card-grid,
|
.metric-card-grid,
|
||||||
.metric-card-grid--app,
|
.metric-card-grid--app,
|
||||||
.metric-card-grid--up {
|
.metric-card-grid--arpu {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
214
scripts/sql/20260430_gm_gold_operation.sql
Normal file
214
scripts/sql/20260430_gm_gold_operation.sql
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
SET NAMES utf8mb4;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `gm_gold_operation_record` (
|
||||||
|
`id` bigint NOT NULL COMMENT '记录ID',
|
||||||
|
`user_id` bigint NOT NULL COMMENT 'APP用户ID',
|
||||||
|
`sys_origin` varchar(32) DEFAULT NULL COMMENT '系统来源',
|
||||||
|
`operation_type` tinyint NOT NULL COMMENT '操作类型:0增加,1扣除',
|
||||||
|
`quantity` decimal(20,2) NOT NULL COMMENT '金币数量',
|
||||||
|
`reason_type` tinyint NOT NULL COMMENT '原因类型',
|
||||||
|
`usd_quantity` decimal(20,2) DEFAULT NULL COMMENT '工资/充值折算USD金额',
|
||||||
|
`remarks` varchar(500) NOT NULL COMMENT '备注',
|
||||||
|
`status` varchar(20) NOT NULL DEFAULT 'PENDING' COMMENT '状态:PENDING待审核,APPROVED已通过,REJECTED已拒绝',
|
||||||
|
`applicant_id` bigint NOT NULL COMMENT '申请后台用户ID',
|
||||||
|
`applicant_name` varchar(100) DEFAULT NULL COMMENT '申请后台用户名称',
|
||||||
|
`reviewer_id` bigint DEFAULT NULL COMMENT '审核后台用户ID',
|
||||||
|
`reviewer_name` varchar(100) DEFAULT NULL COMMENT '审核后台用户名称',
|
||||||
|
`review_remark` varchar(500) DEFAULT NULL COMMENT '审核备注',
|
||||||
|
`track_id` varchar(64) NOT NULL COMMENT '钱包流水跟踪ID',
|
||||||
|
`review_time` datetime DEFAULT NULL COMMENT '审核时间',
|
||||||
|
`executed_time` datetime DEFAULT NULL COMMENT '执行时间',
|
||||||
|
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||||
|
`create_user` bigint DEFAULT NULL COMMENT '创建用户',
|
||||||
|
`update_user` bigint DEFAULT NULL COMMENT '更新用户',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_gm_gold_track_id` (`track_id`),
|
||||||
|
KEY `idx_gm_gold_status_time` (`status`, `create_time`),
|
||||||
|
KEY `idx_gm_gold_user` (`user_id`),
|
||||||
|
KEY `idx_gm_gold_applicant` (`applicant_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='GM金币操作申请记录';
|
||||||
|
|
||||||
|
INSERT INTO sys_menu (
|
||||||
|
id,
|
||||||
|
parent_id,
|
||||||
|
menu_name,
|
||||||
|
path,
|
||||||
|
menu_type,
|
||||||
|
icon,
|
||||||
|
create_user,
|
||||||
|
update_user,
|
||||||
|
sort,
|
||||||
|
status,
|
||||||
|
router,
|
||||||
|
alias
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
1700000400,
|
||||||
|
0,
|
||||||
|
'GM操作',
|
||||||
|
'/gm/operation',
|
||||||
|
1,
|
||||||
|
'lucide:shield',
|
||||||
|
'0',
|
||||||
|
'0',
|
||||||
|
48,
|
||||||
|
0,
|
||||||
|
'/gm/operation',
|
||||||
|
'GmOperation'
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM sys_menu WHERE alias = 'GmOperation'
|
||||||
|
);
|
||||||
|
|
||||||
|
UPDATE sys_menu
|
||||||
|
SET
|
||||||
|
parent_id = 0,
|
||||||
|
menu_name = 'GM操作',
|
||||||
|
path = '/gm/operation',
|
||||||
|
menu_type = 1,
|
||||||
|
icon = 'lucide:shield',
|
||||||
|
create_user = '0',
|
||||||
|
update_user = '0',
|
||||||
|
sort = 48,
|
||||||
|
status = 0,
|
||||||
|
router = '/gm/operation'
|
||||||
|
WHERE alias = 'GmOperation';
|
||||||
|
|
||||||
|
INSERT INTO sys_menu (
|
||||||
|
id,
|
||||||
|
parent_id,
|
||||||
|
menu_name,
|
||||||
|
path,
|
||||||
|
menu_type,
|
||||||
|
icon,
|
||||||
|
create_user,
|
||||||
|
update_user,
|
||||||
|
sort,
|
||||||
|
status,
|
||||||
|
router,
|
||||||
|
alias
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
1700000401,
|
||||||
|
(SELECT id FROM (SELECT id FROM sys_menu WHERE alias = 'GmOperation' LIMIT 1) AS parent_menu),
|
||||||
|
'金币操作',
|
||||||
|
'gm/gold-operation',
|
||||||
|
2,
|
||||||
|
'lucide:coins',
|
||||||
|
'0',
|
||||||
|
'0',
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
'gold-operation',
|
||||||
|
'GmGoldOperation'
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM sys_menu WHERE alias = 'GmGoldOperation'
|
||||||
|
);
|
||||||
|
|
||||||
|
UPDATE sys_menu
|
||||||
|
SET
|
||||||
|
parent_id = (SELECT id FROM (SELECT id FROM sys_menu WHERE alias = 'GmOperation' LIMIT 1) AS parent_menu),
|
||||||
|
menu_name = '金币操作',
|
||||||
|
path = 'gm/gold-operation',
|
||||||
|
menu_type = 2,
|
||||||
|
icon = 'lucide:coins',
|
||||||
|
create_user = '0',
|
||||||
|
update_user = '0',
|
||||||
|
sort = 1,
|
||||||
|
status = 0,
|
||||||
|
router = 'gold-operation'
|
||||||
|
WHERE alias = 'GmGoldOperation';
|
||||||
|
|
||||||
|
INSERT INTO sys_menu (
|
||||||
|
id,
|
||||||
|
parent_id,
|
||||||
|
menu_name,
|
||||||
|
path,
|
||||||
|
menu_type,
|
||||||
|
icon,
|
||||||
|
create_user,
|
||||||
|
update_user,
|
||||||
|
sort,
|
||||||
|
status,
|
||||||
|
router,
|
||||||
|
alias
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
1700000402,
|
||||||
|
(SELECT id FROM (SELECT id FROM sys_menu WHERE alias = 'GmOperation' LIMIT 1) AS parent_menu),
|
||||||
|
'金币操作审核',
|
||||||
|
'gm/gold-operation-review',
|
||||||
|
2,
|
||||||
|
'lucide:badge-check',
|
||||||
|
'0',
|
||||||
|
'0',
|
||||||
|
2,
|
||||||
|
0,
|
||||||
|
'gold-operation-review',
|
||||||
|
'GmGoldOperationReview'
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM sys_menu WHERE alias = 'GmGoldOperationReview'
|
||||||
|
);
|
||||||
|
|
||||||
|
UPDATE sys_menu
|
||||||
|
SET
|
||||||
|
parent_id = (SELECT id FROM (SELECT id FROM sys_menu WHERE alias = 'GmOperation' LIMIT 1) AS parent_menu),
|
||||||
|
menu_name = '金币操作审核',
|
||||||
|
path = 'gm/gold-operation-review',
|
||||||
|
menu_type = 2,
|
||||||
|
icon = 'lucide:badge-check',
|
||||||
|
create_user = '0',
|
||||||
|
update_user = '0',
|
||||||
|
sort = 2,
|
||||||
|
status = 0,
|
||||||
|
router = 'gold-operation-review'
|
||||||
|
WHERE alias = 'GmGoldOperationReview';
|
||||||
|
|
||||||
|
DELETE role_menu
|
||||||
|
FROM sys_role_menu AS role_menu
|
||||||
|
JOIN sys_menu AS gm_menu
|
||||||
|
ON gm_menu.id = role_menu.menu_id
|
||||||
|
LEFT JOIN sys_role AS valid_role
|
||||||
|
ON valid_role.id = role_menu.role_id
|
||||||
|
WHERE gm_menu.alias IN ('GmOperation', 'GmGoldOperation', 'GmGoldOperationReview')
|
||||||
|
AND valid_role.id IS NULL;
|
||||||
|
|
||||||
|
INSERT INTO sys_role_menu (role_id, menu_id)
|
||||||
|
SELECT DISTINCT
|
||||||
|
valid_source_role.id,
|
||||||
|
target_menu.id
|
||||||
|
FROM sys_role_menu AS source_role
|
||||||
|
JOIN sys_role AS valid_source_role
|
||||||
|
ON valid_source_role.id = source_role.role_id
|
||||||
|
JOIN sys_menu AS source_menu
|
||||||
|
ON source_menu.id = source_role.menu_id
|
||||||
|
JOIN sys_menu AS target_menu
|
||||||
|
ON target_menu.alias IN ('GmOperation', 'GmGoldOperation')
|
||||||
|
LEFT JOIN sys_role_menu AS existing_role_menu
|
||||||
|
ON existing_role_menu.role_id = valid_source_role.id
|
||||||
|
AND existing_role_menu.menu_id = target_menu.id
|
||||||
|
WHERE source_menu.alias IN ('OperateInsideOperationGold', 'OperateUserManage')
|
||||||
|
AND existing_role_menu.id IS NULL;
|
||||||
|
|
||||||
|
DELETE role_menu
|
||||||
|
FROM sys_role_menu AS role_menu
|
||||||
|
JOIN sys_menu AS review_menu
|
||||||
|
ON review_menu.id = role_menu.menu_id
|
||||||
|
WHERE review_menu.alias = 'GmGoldOperationReview';
|
||||||
|
|
||||||
|
INSERT INTO sys_role_menu (role_id, menu_id)
|
||||||
|
SELECT DISTINCT
|
||||||
|
yumi_role.id,
|
||||||
|
target_menu.id
|
||||||
|
FROM sys_user AS yumi_user
|
||||||
|
JOIN sys_user_role AS user_role
|
||||||
|
ON user_role.uid = yumi_user.uid
|
||||||
|
JOIN sys_role AS yumi_role
|
||||||
|
ON yumi_role.id = user_role.role_id
|
||||||
|
JOIN sys_menu AS target_menu
|
||||||
|
ON target_menu.alias IN ('GmOperation', 'GmGoldOperation', 'GmGoldOperationReview')
|
||||||
|
LEFT JOIN sys_role_menu AS existing_role_menu
|
||||||
|
ON existing_role_menu.role_id = yumi_role.id
|
||||||
|
AND existing_role_menu.menu_id = target_menu.id
|
||||||
|
WHERE LOWER(yumi_user.login_name) = 'yumiadmin'
|
||||||
|
AND existing_role_menu.id IS NULL;
|
||||||
Loading…
x
Reference in New Issue
Block a user