Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e41b630e02 | ||
|
|
9678f262a9 | ||
|
|
25ccb09f31 | ||
|
|
a73b079333 | ||
|
|
7510dac801 | ||
|
|
5befd6297b | ||
|
|
cd9cbea7d6 | ||
|
|
d56fc0f50e | ||
|
|
915c8bf0be | ||
|
|
c22cff97c3 | ||
|
|
1995f9b078 | ||
|
|
33db3e0ea2 | ||
|
|
66568253aa | ||
|
|
b94e6db9af | ||
|
|
2ef1aa24ea | ||
|
|
a62e5a65a5 | ||
|
|
d92285ae4a | ||
|
|
b2fb090fb1 |
@ -41,9 +41,11 @@
|
|||||||
"@vben/types": "workspace:*",
|
"@vben/types": "workspace:*",
|
||||||
"@vben/utils": "workspace:*",
|
"@vben/utils": "workspace:*",
|
||||||
"@vueuse/core": "catalog:",
|
"@vueuse/core": "catalog:",
|
||||||
|
"@types/json-bigint": "catalog:",
|
||||||
"ali-oss": "^6.23.0",
|
"ali-oss": "^6.23.0",
|
||||||
"antdv-next": "catalog:",
|
"antdv-next": "catalog:",
|
||||||
"dayjs": "catalog:",
|
"dayjs": "catalog:",
|
||||||
|
"json-bigint": "catalog:",
|
||||||
"pinia": "catalog:",
|
"pinia": "catalog:",
|
||||||
"sockjs-client": "^1.6.1",
|
"sockjs-client": "^1.6.1",
|
||||||
"stompjs": "^2.3.3",
|
"stompjs": "^2.3.3",
|
||||||
|
|||||||
@ -108,3 +108,51 @@ export async function syncLingxianGames(data: Record<string, any>) {
|
|||||||
data,
|
data,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getHotgameProviderConfig(params: Record<string, any>) {
|
||||||
|
return requestClient.get<Record<string, any>>('/go/operate/hotgame-game/config', {
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function saveHotgameProviderConfig(data: Record<string, any>) {
|
||||||
|
return requestClient.post<Record<string, any>>(
|
||||||
|
'/go/operate/hotgame-game/config',
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function activateHotgameProviderProfile(data: Record<string, any>) {
|
||||||
|
return requestClient.post<Record<string, any>>(
|
||||||
|
'/go/operate/hotgame-game/config/activate',
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function pageHotgameCatalog(params: Record<string, any>) {
|
||||||
|
return requestClient.get<Record<string, any>>(
|
||||||
|
'/go/operate/hotgame-game/catalog/page',
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchHotgameCatalog(data: Record<string, any>) {
|
||||||
|
return requestClient.post<Record<string, any>>(
|
||||||
|
'/go/operate/hotgame-game/catalog/fetch',
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function importHotgameCatalog(data: Record<string, any>) {
|
||||||
|
return requestClient.post<Record<string, any>>(
|
||||||
|
'/go/operate/hotgame-game/import-catalog',
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function syncHotgameGames(data: Record<string, any>) {
|
||||||
|
return requestClient.post<Record<string, any>>(
|
||||||
|
'/go/operate/hotgame-game/sync',
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@ -14,6 +14,7 @@ export interface CountryDashboardMetric {
|
|||||||
d30RetentionRate: number | string;
|
d30RetentionRate: number | string;
|
||||||
d30RetentionUser: number;
|
d30RetentionUser: number;
|
||||||
dailyActiveUser: number;
|
dailyActiveUser: number;
|
||||||
|
dailyRechargeUser: number;
|
||||||
dealerRecharge: number | string;
|
dealerRecharge: number | string;
|
||||||
googleRecharge: number | string;
|
googleRecharge: number | string;
|
||||||
gamePayoutRate: number | string;
|
gamePayoutRate: number | string;
|
||||||
@ -32,6 +33,7 @@ export interface CountryDashboardMetric {
|
|||||||
luckyGiftUser: number;
|
luckyGiftUser: number;
|
||||||
luckyGiftUserRate: number | string;
|
luckyGiftUserRate: number | string;
|
||||||
mifapayRecharge: number | string;
|
mifapayRecharge: number | string;
|
||||||
|
newDealerUserRecharge: number | string;
|
||||||
newUserRecharge: number | string;
|
newUserRecharge: number | string;
|
||||||
officialRecharge: number | string;
|
officialRecharge: number | string;
|
||||||
periodKey: string;
|
periodKey: string;
|
||||||
@ -39,9 +41,11 @@ export interface CountryDashboardMetric {
|
|||||||
salaryExchange: number | string;
|
salaryExchange: number | string;
|
||||||
salaryTransfer: number | string;
|
salaryTransfer: number | string;
|
||||||
totalRecharge: number | string;
|
totalRecharge: number | string;
|
||||||
|
userRecharge: number | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CountryDashboardResult {
|
export interface CountryDashboardResult {
|
||||||
|
arpuSummary?: CountryDashboardArpuSummary;
|
||||||
computedAt: string;
|
computedAt: string;
|
||||||
endDate?: string;
|
endDate?: string;
|
||||||
periodType: string;
|
periodType: string;
|
||||||
@ -51,6 +55,12 @@ export interface CountryDashboardResult {
|
|||||||
total: CountryDashboardMetric;
|
total: CountryDashboardMetric;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CountryDashboardArpuSummary {
|
||||||
|
day?: CountryDashboardResult | null;
|
||||||
|
threeDay?: CountryDashboardResult | null;
|
||||||
|
week?: CountryDashboardResult | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface CountryDashboardRechargeDetail {
|
export interface CountryDashboardRechargeDetail {
|
||||||
amount: number | string;
|
amount: number | string;
|
||||||
coinQuantity: number | string;
|
coinQuantity: number | string;
|
||||||
|
|||||||
@ -212,6 +212,17 @@ export async function getClsGoldRunningWater(params: Record<string, any>) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function exportClsGoldRunningWater(
|
||||||
|
params: Record<string, any>,
|
||||||
|
filename = 'GmGoldRunningWater',
|
||||||
|
) {
|
||||||
|
return downloadLegacyExcel(
|
||||||
|
'/user-wallet/gold/running-water/cls/export',
|
||||||
|
params,
|
||||||
|
filename,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export async function getPurchaseTable(params: Record<string, any>) {
|
export async function getPurchaseTable(params: Record<string, any>) {
|
||||||
return requestClient.get<LegacyPageResult<Record<string, any>>>(
|
return requestClient.get<LegacyPageResult<Record<string, any>>>(
|
||||||
'/order/purchase/history/page',
|
'/order/purchase/history/page',
|
||||||
@ -230,6 +241,17 @@ export async function listInAppPurchase(params: Record<string, any>) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function exportInAppPurchaseDetails(
|
||||||
|
params: Record<string, any>,
|
||||||
|
filename = 'InAppPurchaseOrders',
|
||||||
|
) {
|
||||||
|
return downloadLegacyExcel(
|
||||||
|
'/order/in-app-purchase/details/export',
|
||||||
|
params,
|
||||||
|
filename,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export async function getInAppPurchase(id: number | string) {
|
export async function getInAppPurchase(id: number | string) {
|
||||||
return requestClient.get<Record<string, any>>(
|
return requestClient.get<Record<string, any>>(
|
||||||
'/order/in-app-purchase/details',
|
'/order/in-app-purchase/details',
|
||||||
|
|||||||
@ -78,6 +78,13 @@ export async function updatePayOpenCountyShelf(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function syncPayOpenCountryCurrencyRates(data: Record<string, any>) {
|
||||||
|
return requestClient.post<Record<string, any>>(
|
||||||
|
'/sys-pay-open-country/sync-currency-rates',
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export async function pagePayChannel(params: Record<string, any>) {
|
export async function pagePayChannel(params: Record<string, any>) {
|
||||||
return requestClient.get<LegacyPageResult<Record<string, any>>>(
|
return requestClient.get<LegacyPageResult<Record<string, any>>>(
|
||||||
'/sys-pay-channel/page',
|
'/sys-pay-channel/page',
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import JsonBigint from 'json-bigint';
|
||||||
|
|
||||||
import { requestClient } from '#/api/request';
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
export interface LegacyPageResult<T = Record<string, any>> {
|
export interface LegacyPageResult<T = Record<string, any>> {
|
||||||
@ -5,6 +7,16 @@ export interface LegacyPageResult<T = Record<string, any>> {
|
|||||||
total: number;
|
total: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const jsonBigint = JsonBigint({ storeAsString: true });
|
||||||
|
|
||||||
|
function parseLegacyBodyWithBigint<T>(raw: string) {
|
||||||
|
const data = jsonBigint.parse(raw);
|
||||||
|
if (data?.errorCode === 401 || data?.errorCode !== 0) {
|
||||||
|
throw new Error(data?.errorMsg || data?.message || 'Error');
|
||||||
|
}
|
||||||
|
return data?.body as T;
|
||||||
|
}
|
||||||
|
|
||||||
export async function pagePropsSource(params: Record<string, any>) {
|
export async function pagePropsSource(params: Record<string, any>) {
|
||||||
return requestClient.get<LegacyPageResult<Record<string, any>>>(
|
return requestClient.get<LegacyPageResult<Record<string, any>>>(
|
||||||
'/props/source/record/page',
|
'/props/source/record/page',
|
||||||
@ -53,12 +65,18 @@ export async function listNotFamilyBySysOriginType(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function pagePropsActivityRewardGroup(params: Record<string, any>) {
|
export async function pagePropsActivityRewardGroup(params: Record<string, any>) {
|
||||||
return requestClient.get<LegacyPageResult<Record<string, any>>>(
|
const response = await requestClient.get<{ data: string }>(
|
||||||
'/props/activity/reward/group/page',
|
'/props/activity/reward/group/page',
|
||||||
{
|
{
|
||||||
params,
|
params,
|
||||||
|
responseReturn: 'raw',
|
||||||
|
responseType: 'text',
|
||||||
|
transformResponse: [(data) => data],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
return parseLegacyBodyWithBigint<LegacyPageResult<Record<string, any>>>(
|
||||||
|
response.data,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getByGroupId(id: number | string) {
|
export async function getByGroupId(id: number | string) {
|
||||||
|
|||||||
@ -43,12 +43,23 @@ export interface LegacyUser {
|
|||||||
}>;
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface LegacyBackUserOption {
|
||||||
|
id: number | string;
|
||||||
|
loginName?: string;
|
||||||
|
nickname?: string;
|
||||||
|
status?: number | string;
|
||||||
|
}
|
||||||
|
|
||||||
export async function pageUsers(params: Record<string, any>) {
|
export async function pageUsers(params: Record<string, any>) {
|
||||||
return requestClient.get<LegacyPageResult<LegacyUser>>('/users', {
|
return requestClient.get<LegacyPageResult<LegacyUser>>('/users', {
|
||||||
params,
|
params,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function listBackUserOptions() {
|
||||||
|
return requestClient.get<LegacyBackUserOption[]>('/users/list/combo');
|
||||||
|
}
|
||||||
|
|
||||||
export async function switchUserStatus(
|
export async function switchUserStatus(
|
||||||
id: number | string,
|
id: number | string,
|
||||||
status: number | string,
|
status: number | string,
|
||||||
|
|||||||
@ -216,11 +216,14 @@ export async function pageFreightRunningWater(params: Record<string, any>) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function exportFreightWaters(params: Record<string, any>) {
|
export async function exportFreightWaters(
|
||||||
|
params: Record<string, any>,
|
||||||
|
filename = 'ExportFreightWaters',
|
||||||
|
) {
|
||||||
return downloadLegacyExcel(
|
return downloadLegacyExcel(
|
||||||
'/freight/running-water/export',
|
'/freight/running-water/export',
|
||||||
params,
|
params,
|
||||||
'ExportFreightWaters',
|
filename,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -29,6 +29,12 @@ const routes: RouteRecordRaw[] = [
|
|||||||
component: () => import('#/views/operate/lingxian-config.vue'),
|
component: () => import('#/views/operate/lingxian-config.vue'),
|
||||||
meta: { title: '灵仙配置' },
|
meta: { title: '灵仙配置' },
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'OperateHotgameConfig',
|
||||||
|
path: 'hotgame-config',
|
||||||
|
component: () => import('#/views/operate/hotgame-config.vue'),
|
||||||
|
meta: { title: '热游配置' },
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@ -21,9 +21,12 @@ import {
|
|||||||
} from 'antdv-next';
|
} from 'antdv-next';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
import { submitGmGoldOperation } from '#/api/legacy/gm';
|
import { pageGmGoldOperation, submitGmGoldOperation } from '#/api/legacy/gm';
|
||||||
import { getClsGoldRunningWater } from '#/api/legacy/operate';
|
import {
|
||||||
import { listMembers } from '#/api/legacy/team';
|
exportClsGoldRunningWater,
|
||||||
|
getClsGoldRunningWater,
|
||||||
|
} from '#/api/legacy/operate';
|
||||||
|
import { listBackUserOptions } from '#/api/legacy/system';
|
||||||
import { getUserBaseInfoBySysOriginAccount } from '#/api/legacy/user';
|
import { getUserBaseInfoBySysOriginAccount } from '#/api/legacy/user';
|
||||||
import AccountInput from '#/components/account-input.vue';
|
import AccountInput from '#/components/account-input.vue';
|
||||||
import UserProfileLink from '#/views/operate/components/user-profile-link.vue';
|
import UserProfileLink from '#/views/operate/components/user-profile-link.vue';
|
||||||
@ -56,6 +59,8 @@ const DEFAULT_PAGE_SIZE = 100;
|
|||||||
const open = ref(false);
|
const open = ref(false);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const loadMoreLoading = ref(false);
|
const loadMoreLoading = ref(false);
|
||||||
|
const operatorsLoading = ref(false);
|
||||||
|
const exporting = ref(false);
|
||||||
const submitting = ref(false);
|
const submitting = ref(false);
|
||||||
const list = ref<Array<Record<string, any>>>([]);
|
const list = ref<Array<Record<string, any>>>([]);
|
||||||
const notMore = ref(false);
|
const notMore = ref(false);
|
||||||
@ -64,7 +69,7 @@ const rangeDate = ref<[string, string] | null>(null);
|
|||||||
const operators = ref<Array<Record<string, any>>>([]);
|
const operators = ref<Array<Record<string, any>>>([]);
|
||||||
|
|
||||||
const query = reactive<Record<string, any>>({
|
const query = reactive<Record<string, any>>({
|
||||||
backOperationUser: undefined,
|
backOperationUsers: [],
|
||||||
context: '',
|
context: '',
|
||||||
endTime: '',
|
endTime: '',
|
||||||
lastId: '',
|
lastId: '',
|
||||||
@ -103,10 +108,14 @@ const showUsdQuantity = computed(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const operatorOptions = computed(() =>
|
const operatorOptions = computed(() =>
|
||||||
operators.value.map((item) => ({
|
operators.value.map((item) => {
|
||||||
label: `${item.nickname || item.loginName || item.id} / ${item.loginName || item.id}`,
|
const name = item.nickname || item.loginName || item.name || item.id;
|
||||||
|
const account = item.loginName && item.loginName !== name ? ` / ${item.loginName}` : '';
|
||||||
|
return {
|
||||||
|
label: `${name}${account}`,
|
||||||
value: item.id,
|
value: item.id,
|
||||||
})),
|
};
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const currentPageList = computed(() => {
|
const currentPageList = computed(() => {
|
||||||
@ -259,22 +268,81 @@ function filterOperatorOption(input: string, option: any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function loadOperators() {
|
async function loadOperators() {
|
||||||
operators.value = (await listMembers()) || [];
|
operatorsLoading.value = true;
|
||||||
|
try {
|
||||||
|
let backUsers: Array<Record<string, any>> = [];
|
||||||
|
try {
|
||||||
|
const result = await listBackUserOptions();
|
||||||
|
backUsers = Array.isArray(result) ? result : [];
|
||||||
|
} catch {
|
||||||
|
backUsers = [];
|
||||||
|
}
|
||||||
|
operators.value =
|
||||||
|
backUsers.length > 0
|
||||||
|
? normalizeOperators(backUsers)
|
||||||
|
: await loadRecentGmOperators().catch(() => []);
|
||||||
|
} finally {
|
||||||
|
operatorsLoading.value = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadData(reset = false) {
|
function normalizeOperators(items: Array<Record<string, any>>) {
|
||||||
if (loading.value || loadMoreLoading.value) {
|
const operatorMap = new Map<string, Record<string, any>>();
|
||||||
|
items.forEach((item) => {
|
||||||
|
const id = item.id || item.applicantId;
|
||||||
|
if (!id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
operatorMap.set(String(id), {
|
||||||
|
id,
|
||||||
|
loginName: item.loginName,
|
||||||
|
name: item.name,
|
||||||
|
nickname: item.nickname || item.applicantName,
|
||||||
|
status: item.status,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return [...operatorMap.values()];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadRecentGmOperators() {
|
||||||
|
const result = await pageGmGoldOperation({ cursor: 1, limit: 500 });
|
||||||
|
return normalizeOperators(result?.records || []);
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateQueryTimeRange() {
|
||||||
if (!query.startTime || !query.endTime) {
|
if (!query.startTime || !query.endTime) {
|
||||||
message.warning('请选择时间范围');
|
message.warning('请选择时间范围');
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const startYear = new Date(Number(query.startTime)).getFullYear();
|
const startYear = new Date(Number(query.startTime)).getFullYear();
|
||||||
const endYear = new Date(Number(query.endTime)).getFullYear();
|
const endYear = new Date(Number(query.endTime)).getFullYear();
|
||||||
if (startYear !== endYear) {
|
if (startYear !== endYear) {
|
||||||
message.warning('不允许跨年查询');
|
message.warning('不允许跨年查询');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildRunningWaterParams() {
|
||||||
|
const backOperationUsers = Array.isArray(query.backOperationUsers)
|
||||||
|
? query.backOperationUsers.filter(Boolean).join(',')
|
||||||
|
: '';
|
||||||
|
return {
|
||||||
|
backOperationUsers: backOperationUsers || undefined,
|
||||||
|
endTime: query.endTime,
|
||||||
|
queryBackOperationUser: true,
|
||||||
|
startTime: query.startTime,
|
||||||
|
type: query.type,
|
||||||
|
userId: query.userId || undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadData(reset = false) {
|
||||||
|
if (loading.value || loadMoreLoading.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!validateQueryTimeRange()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -294,15 +362,10 @@ async function loadData(reset = false) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await getClsGoldRunningWater({
|
const result = await getClsGoldRunningWater({
|
||||||
backOperationUser: query.backOperationUser || undefined,
|
...buildRunningWaterParams(),
|
||||||
context: query.context || undefined,
|
context: query.context || undefined,
|
||||||
endTime: query.endTime,
|
|
||||||
lastId: query.lastId || undefined,
|
lastId: query.lastId || undefined,
|
||||||
limit: query.limit,
|
limit: query.limit,
|
||||||
queryBackOperationUser: true,
|
|
||||||
startTime: query.startTime,
|
|
||||||
type: query.type,
|
|
||||||
userId: query.userId || undefined,
|
|
||||||
});
|
});
|
||||||
const waters = result?.waters || [];
|
const waters = result?.waters || [];
|
||||||
const nextList = reset ? waters : [...list.value, ...waters];
|
const nextList = reset ? waters : [...list.value, ...waters];
|
||||||
@ -352,6 +415,21 @@ async function handleSearch() {
|
|||||||
await loadData(true);
|
await loadData(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleExport() {
|
||||||
|
if (exporting.value || !validateQueryTimeRange()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
exporting.value = true;
|
||||||
|
try {
|
||||||
|
await exportClsGoldRunningWater(
|
||||||
|
buildRunningWaterParams(),
|
||||||
|
`GmGoldRunningWater_${dayjs(Number(query.startTime)).format('YYYYMMDD')}_${dayjs(Number(query.endTime)).format('YYYYMMDD')}`,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
exporting.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
if (!validateForm()) {
|
if (!validateForm()) {
|
||||||
return;
|
return;
|
||||||
@ -420,15 +498,19 @@ void loadData(true);
|
|||||||
style="width: 240px"
|
style="width: 240px"
|
||||||
/>
|
/>
|
||||||
<Select
|
<Select
|
||||||
v-model:value="query.backOperationUser"
|
v-model:value="query.backOperationUsers"
|
||||||
allow-clear
|
allow-clear
|
||||||
|
mode="multiple"
|
||||||
show-search
|
show-search
|
||||||
|
max-tag-count="responsive"
|
||||||
placeholder="操作人"
|
placeholder="操作人"
|
||||||
style="width: 220px"
|
style="width: 220px"
|
||||||
:filter-option="filterOperatorOption"
|
:filter-option="filterOperatorOption"
|
||||||
|
:loading="operatorsLoading"
|
||||||
:options="operatorOptions"
|
:options="operatorOptions"
|
||||||
/>
|
/>
|
||||||
<Button :loading="loading" type="primary" @click="handleSearch">查询</Button>
|
<Button :loading="loading" type="primary" @click="handleSearch">查询</Button>
|
||||||
|
<Button :loading="exporting" @click="handleExport">导出Excel</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Table
|
<Table
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, reactive, ref, watch } from 'vue';
|
import { computed, reactive, ref, watch } from 'vue';
|
||||||
|
|
||||||
|
import type { Dayjs } from 'dayjs';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
OSS_FILE_BUCKETS,
|
OSS_FILE_BUCKETS,
|
||||||
getAccessImgUrl,
|
getAccessImgUrl,
|
||||||
@ -113,6 +116,8 @@ const selectCountryCodes = ref<string[]>([]);
|
|||||||
const countriesLoading = ref(false);
|
const countriesLoading = ref(false);
|
||||||
const regionsLoading = ref(false);
|
const regionsLoading = ref(false);
|
||||||
const form = reactive<Record<string, any>>(createForm());
|
const form = reactive<Record<string, any>>(createForm());
|
||||||
|
const startTimePicker = ref<Dayjs | null>(null);
|
||||||
|
const expiredTimePicker = ref<Dayjs | null>(null);
|
||||||
|
|
||||||
const regionSelectOptions = computed(() =>
|
const regionSelectOptions = computed(() =>
|
||||||
regionOptions.value.map((item) => ({
|
regionOptions.value.map((item) => ({
|
||||||
@ -156,6 +161,27 @@ function normalizeMultiValue(value: any) {
|
|||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizePickerValue(value: any) {
|
||||||
|
if (value === '' || value === null || value === undefined) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (dayjs.isDayjs(value)) {
|
||||||
|
return value.isValid() ? value : null;
|
||||||
|
}
|
||||||
|
const raw = String(value).trim();
|
||||||
|
const numericValue = Number(raw);
|
||||||
|
const parsed = raw !== '' && Number.isFinite(numericValue) ? dayjs(numericValue) : dayjs(value);
|
||||||
|
return parsed.isValid() ? parsed : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickerToTimestamp(value: Dayjs | null) {
|
||||||
|
return value?.isValid() ? String(value.valueOf()) : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDatePickerPopupContainer(triggerNode: HTMLElement) {
|
||||||
|
return triggerNode.parentElement || document.body;
|
||||||
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => ({ open: props.open, row: props.row, sysOrigin: props.sysOrigin }),
|
() => ({ open: props.open, row: props.row, sysOrigin: props.sysOrigin }),
|
||||||
({ open, row, sysOrigin }) => {
|
({ open, row, sysOrigin }) => {
|
||||||
@ -174,6 +200,8 @@ watch(
|
|||||||
roomSearchValue.value = String(form.params);
|
roomSearchValue.value = String(form.params);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
startTimePicker.value = normalizePickerValue(form.startTime);
|
||||||
|
expiredTimePicker.value = normalizePickerValue(form.expiredTime);
|
||||||
void Promise.all([
|
void Promise.all([
|
||||||
loadCountries(),
|
loadCountries(),
|
||||||
sysOrigin ? loadRegions(sysOrigin) : Promise.resolve(),
|
sysOrigin ? loadRegions(sysOrigin) : Promise.resolve(),
|
||||||
@ -204,6 +232,14 @@ watch(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
watch(startTimePicker, (value) => {
|
||||||
|
form.startTime = pickerToTimestamp(value);
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(expiredTimePicker, (value) => {
|
||||||
|
form.expiredTime = pickerToTimestamp(value);
|
||||||
|
});
|
||||||
|
|
||||||
async function loadCountries() {
|
async function loadCountries() {
|
||||||
countriesLoading.value = true;
|
countriesLoading.value = true;
|
||||||
try {
|
try {
|
||||||
@ -287,10 +323,18 @@ async function submitForm() {
|
|||||||
message.warning('请选择系统');
|
message.warning('请选择系统');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!form.startTime) {
|
||||||
|
message.warning('请选择开始时间');
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!form.expiredTime) {
|
if (!form.expiredTime) {
|
||||||
message.warning('请选择过期时间');
|
message.warning('请选择过期时间');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (Number(form.expiredTime) <= Number(form.startTime)) {
|
||||||
|
message.warning('过期时间必须晚于开始时间');
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (form.content === 'ENTER_ROOM' && !form.params) {
|
if (form.content === 'ENTER_ROOM' && !form.params) {
|
||||||
message.warning('请选择房间');
|
message.warning('请选择房间');
|
||||||
return;
|
return;
|
||||||
@ -486,20 +530,22 @@ async function submitForm() {
|
|||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="label">开始时间</div>
|
<div class="label">开始时间</div>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
v-model:value="form.startTime"
|
v-model:value="startTimePicker"
|
||||||
|
format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
:get-popup-container="getDatePickerPopupContainer"
|
||||||
show-time
|
show-time
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
value-format="x"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="label">过期时间</div>
|
<div class="label">过期时间</div>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
v-model:value="form.expiredTime"
|
v-model:value="expiredTimePicker"
|
||||||
|
format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
:get-popup-container="getDatePickerPopupContainer"
|
||||||
show-time
|
show-time
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
value-format="x"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +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 {
|
|
||||||
addOrUpdatePayCommodity,
|
|
||||||
listCountrytSupportAmountChannels,
|
|
||||||
} from '#/api/legacy/pay';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Collapse,
|
Collapse,
|
||||||
CollapsePanel,
|
CollapsePanel,
|
||||||
@ -13,11 +8,16 @@ import {
|
|||||||
FormItem,
|
FormItem,
|
||||||
Image,
|
Image,
|
||||||
Input,
|
Input,
|
||||||
|
message,
|
||||||
Modal,
|
Modal,
|
||||||
Select,
|
Select,
|
||||||
message,
|
|
||||||
} from 'antdv-next';
|
} from 'antdv-next';
|
||||||
|
|
||||||
|
import {
|
||||||
|
addOrUpdatePayCommodity,
|
||||||
|
listCountrytSupportAmountChannels,
|
||||||
|
} from '#/api/legacy/pay';
|
||||||
|
|
||||||
import { WEB_PRODUCT_TYPE_OPTIONS } from '../pay-shared';
|
import { WEB_PRODUCT_TYPE_OPTIONS } from '../pay-shared';
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
@ -60,6 +60,7 @@ const form = reactive<Record<string, any>>({
|
|||||||
content: '',
|
content: '',
|
||||||
id: '',
|
id: '',
|
||||||
payCountryId: '',
|
payCountryId: '',
|
||||||
|
payCountryIds: [],
|
||||||
regionId: '',
|
regionId: '',
|
||||||
shelf: true,
|
shelf: true,
|
||||||
type: 'GOLD',
|
type: 'GOLD',
|
||||||
@ -68,7 +69,27 @@ const form = reactive<Record<string, any>>({
|
|||||||
const isUpdate = computed(() => Boolean(form.id));
|
const isUpdate = computed(() => Boolean(form.id));
|
||||||
const modalTitle = computed(() => (isUpdate.value ? '修改商品' : '创建商品'));
|
const modalTitle = computed(() => (isUpdate.value ? '修改商品' : '创建商品'));
|
||||||
const selectedApp = computed(() => props.row?.selectApp || {});
|
const selectedApp = computed(() => props.row?.selectApp || {});
|
||||||
const selectedCountry = computed(() => props.row?.country || {});
|
const supportCountries = computed(() => props.row?.supportCountries || []);
|
||||||
|
const countryOptions = computed(() =>
|
||||||
|
supportCountries.value.map((item: Record<string, any>) => ({
|
||||||
|
label:
|
||||||
|
item.country?.aliasName ||
|
||||||
|
item.country?.enName ||
|
||||||
|
item.country?.countryName ||
|
||||||
|
'-',
|
||||||
|
value: item.id,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
const selectedCountry = computed(() => {
|
||||||
|
const currentPayCountryId = form.payCountryId || form.payCountryIds?.[0];
|
||||||
|
const current = supportCountries.value.find(
|
||||||
|
(item: Record<string, any>) => String(item.id) === String(currentPayCountryId),
|
||||||
|
);
|
||||||
|
if (!current) {
|
||||||
|
return props.row?.country || {};
|
||||||
|
}
|
||||||
|
return toCountryMeta(current);
|
||||||
|
});
|
||||||
const regionName = computed(() => props.row?.regionName || '');
|
const regionName = computed(() => props.row?.regionName || '');
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
@ -84,6 +105,14 @@ watch(
|
|||||||
form.content = row.content ?? '';
|
form.content = row.content ?? '';
|
||||||
form.id = row.id ?? '';
|
form.id = row.id ?? '';
|
||||||
form.payCountryId = row.payCountryId ?? '';
|
form.payCountryId = row.payCountryId ?? '';
|
||||||
|
form.payCountryIds = Array.isArray(row.payCountryIds)
|
||||||
|
? [...row.payCountryIds]
|
||||||
|
: (row.payCountryId
|
||||||
|
? [row.payCountryId]
|
||||||
|
: []);
|
||||||
|
if (props.mode === 'country' && !form.payCountryId && form.payCountryIds.length > 0) {
|
||||||
|
form.payCountryId = form.payCountryIds[0];
|
||||||
|
}
|
||||||
form.regionId = row.regionId ?? '';
|
form.regionId = row.regionId ?? '';
|
||||||
form.shelf = row.shelf ?? true;
|
form.shelf = row.shelf ?? true;
|
||||||
form.type = row.type ?? 'GOLD';
|
form.type = row.type ?? 'GOLD';
|
||||||
@ -92,12 +121,26 @@ watch(
|
|||||||
{ immediate: true },
|
{ immediate: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function toCountryMeta(country: Record<string, any>) {
|
||||||
|
return {
|
||||||
|
countryName:
|
||||||
|
country.country?.aliasName ||
|
||||||
|
country.country?.enName ||
|
||||||
|
country.country?.countryName ||
|
||||||
|
'-',
|
||||||
|
currency: country.currency || '',
|
||||||
|
exchangeRate: country.usdExchangeRate || '',
|
||||||
|
icon: country.country?.nationalFlag || '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function isPositiveNumberPointTwo(value: string) {
|
function isPositiveNumberPointTwo(value: string) {
|
||||||
return /^\d+(\.\d{1,2})?$/.test(value);
|
return /^\d+(\.\d{1,2})?$/.test(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadSupportAmountChannels() {
|
async function loadSupportAmountChannels() {
|
||||||
if (props.mode !== 'country' || !form.payCountryId || !isPositiveNumberPointTwo(String(form.amountUsd || ''))) {
|
const currentPayCountryId = form.payCountryId || form.payCountryIds?.[0];
|
||||||
|
if (props.mode !== 'country' || !currentPayCountryId || !isPositiveNumberPointTwo(String(form.amountUsd || ''))) {
|
||||||
supportAmounts.value = [];
|
supportAmounts.value = [];
|
||||||
computedAmount.value = '';
|
computedAmount.value = '';
|
||||||
amountUsdText.value = '';
|
amountUsdText.value = '';
|
||||||
@ -105,8 +148,9 @@ async function loadSupportAmountChannels() {
|
|||||||
}
|
}
|
||||||
associateLoading.value = true;
|
associateLoading.value = true;
|
||||||
try {
|
try {
|
||||||
|
// 新增允许多国家,渠道限额预览只展示首个国家;保存时后端会按每个国家拆分商品记录.
|
||||||
const result = await listCountrytSupportAmountChannels(
|
const result = await listCountrytSupportAmountChannels(
|
||||||
form.payCountryId,
|
currentPayCountryId,
|
||||||
form.amountUsd,
|
form.amountUsd,
|
||||||
);
|
);
|
||||||
supportAmounts.value = result?.channels || [];
|
supportAmounts.value = result?.channels || [];
|
||||||
@ -124,11 +168,20 @@ function handleAmountChange() {
|
|||||||
void loadSupportAmountChannels();
|
void loadSupportAmountChannels();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleCountryIdsChange(value: Array<number | string>) {
|
||||||
|
form.payCountryId = value?.[0] || '';
|
||||||
|
void loadSupportAmountChannels();
|
||||||
|
}
|
||||||
|
|
||||||
function validateForm() {
|
function validateForm() {
|
||||||
if (props.mode === 'country' && !form.payCountryId) {
|
if (props.mode === 'country' && isUpdate.value && !form.payCountryId) {
|
||||||
message.warning('缺少支付国家');
|
message.warning('缺少支付国家');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (props.mode === 'country' && !isUpdate.value && form.payCountryIds.length === 0) {
|
||||||
|
message.warning('请选择售卖国家');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (props.mode === 'region' && !form.regionId) {
|
if (props.mode === 'region' && !form.regionId) {
|
||||||
message.warning('缺少区域');
|
message.warning('缺少区域');
|
||||||
return false;
|
return false;
|
||||||
@ -162,13 +215,16 @@ async function handleSubmit() {
|
|||||||
}
|
}
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
|
const createCountryIds =
|
||||||
|
props.mode === 'country' && !isUpdate.value ? form.payCountryIds : undefined;
|
||||||
await addOrUpdatePayCommodity({
|
await addOrUpdatePayCommodity({
|
||||||
amountUsd: form.amountUsd,
|
amountUsd: form.amountUsd,
|
||||||
applicationId: form.applicationId,
|
applicationId: form.applicationId,
|
||||||
awardContent: form.awardContent,
|
awardContent: form.awardContent,
|
||||||
content: form.content,
|
content: form.content,
|
||||||
id: form.id,
|
id: form.id,
|
||||||
payCountryId: form.payCountryId,
|
payCountryId: form.payCountryId || form.payCountryIds?.[0],
|
||||||
|
payCountryIds: createCountryIds,
|
||||||
regionId: form.regionId,
|
regionId: form.regionId,
|
||||||
shelf: form.shelf,
|
shelf: form.shelf,
|
||||||
type: form.type,
|
type: form.type,
|
||||||
@ -199,7 +255,16 @@ async function handleSubmit() {
|
|||||||
|
|
||||||
<Form layout="vertical">
|
<Form layout="vertical">
|
||||||
<FormItem v-if="mode === 'country'" label="售卖国家">
|
<FormItem v-if="mode === 'country'" label="售卖国家">
|
||||||
<div class="inline-meta">
|
<Select
|
||||||
|
v-if="!isUpdate"
|
||||||
|
v-model:value="form.payCountryIds"
|
||||||
|
mode="multiple"
|
||||||
|
:options="countryOptions"
|
||||||
|
option-label-prop="label"
|
||||||
|
placeholder="请选择售卖国家"
|
||||||
|
@change="handleCountryIdsChange"
|
||||||
|
/>
|
||||||
|
<div v-else class="inline-meta">
|
||||||
<Image
|
<Image
|
||||||
v-if="selectedCountry.icon"
|
v-if="selectedCountry.icon"
|
||||||
:preview="false"
|
:preview="false"
|
||||||
@ -252,7 +317,7 @@ async function handleSubmit() {
|
|||||||
<div v-else-if="supportAmounts.length === 0" class="support-empty">
|
<div v-else-if="supportAmounts.length === 0" class="support-empty">
|
||||||
暂无可展示渠道
|
暂无可展示渠道
|
||||||
</div>
|
</div>
|
||||||
<Collapse v-else v-model:activeKey="activeKeys">
|
<Collapse v-else v-model:active-key="activeKeys">
|
||||||
<CollapsePanel
|
<CollapsePanel
|
||||||
v-for="item in supportAmounts"
|
v-for="item in supportAmounts"
|
||||||
:key="String(item.channel?.channelCode || '')"
|
:key="String(item.channel?.channelCode || '')"
|
||||||
|
|||||||
@ -1,14 +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 { regionConfigTable } from '#/api/legacy/system';
|
|
||||||
import {
|
|
||||||
listPayOpenCountry,
|
|
||||||
pagePayCommodity,
|
|
||||||
switchShelfCommodity,
|
|
||||||
} from '#/api/legacy/pay';
|
|
||||||
import { formatDate } from '#/views/system/shared';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
@ -19,8 +11,15 @@ import {
|
|||||||
Table,
|
Table,
|
||||||
} from 'antdv-next';
|
} from 'antdv-next';
|
||||||
|
|
||||||
import { WEB_PRODUCT_TYPE_OPTIONS } from '../pay-shared';
|
import {
|
||||||
|
listPayOpenCountry,
|
||||||
|
pagePayCommodity,
|
||||||
|
switchShelfCommodity,
|
||||||
|
} from '#/api/legacy/pay';
|
||||||
|
import { regionConfigTable } from '#/api/legacy/system';
|
||||||
|
import { formatDate } from '#/views/system/shared';
|
||||||
|
|
||||||
|
import { WEB_PRODUCT_TYPE_OPTIONS } from '../pay-shared';
|
||||||
import PayCommodityEditModal from './pay-commodity-edit-modal.vue';
|
import PayCommodityEditModal from './pay-commodity-edit-modal.vue';
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
@ -185,7 +184,7 @@ function handlePageChange(page: number, pageSize: number) {
|
|||||||
void loadData();
|
void loadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleApplicationChange(value: string | number) {
|
function handleApplicationChange(value: number | string) {
|
||||||
selectedApp.value =
|
selectedApp.value =
|
||||||
props.appInfo?.appList?.find((item: Record<string, any>) => item.id === value) ||
|
props.appInfo?.appList?.find((item: Record<string, any>) => item.id === value) ||
|
||||||
{};
|
{};
|
||||||
@ -193,7 +192,7 @@ function handleApplicationChange(value: string | number) {
|
|||||||
void loadSupportData().then(() => loadData(true));
|
void loadSupportData().then(() => loadData(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDimensionChange(value: string | number) {
|
function handleDimensionChange(value: number | string) {
|
||||||
selectedDimension.value =
|
selectedDimension.value =
|
||||||
supportList.value.find((item) => item.id === value) || {};
|
supportList.value.find((item) => item.id === value) || {};
|
||||||
if (props.mode === 'country') {
|
if (props.mode === 'country') {
|
||||||
@ -218,10 +217,12 @@ function openCreate() {
|
|||||||
amountUsd: '',
|
amountUsd: '',
|
||||||
applicationId: query.applicationId,
|
applicationId: query.applicationId,
|
||||||
payCountryId: query.payCountryId,
|
payCountryId: query.payCountryId,
|
||||||
|
payCountryIds: props.mode === 'country' ? [query.payCountryId].filter(Boolean) : [],
|
||||||
regionId: query.regionId,
|
regionId: query.regionId,
|
||||||
regionName: selectedDimension.value.regionName || '',
|
regionName: selectedDimension.value.regionName || '',
|
||||||
selectApp: selectedApp.value,
|
selectApp: selectedApp.value,
|
||||||
shelf: true,
|
shelf: true,
|
||||||
|
supportCountries: props.mode === 'country' ? supportList.value : [],
|
||||||
type: query.type,
|
type: query.type,
|
||||||
...(props.mode === 'country'
|
...(props.mode === 'country'
|
||||||
? {
|
? {
|
||||||
@ -243,8 +244,11 @@ function openCreate() {
|
|||||||
function openEdit(record: Record<string, any>) {
|
function openEdit(record: Record<string, any>) {
|
||||||
activeRow.value = {
|
activeRow.value = {
|
||||||
...record,
|
...record,
|
||||||
|
payCountryIds:
|
||||||
|
props.mode === 'country' ? [record.payCountryId || query.payCountryId].filter(Boolean) : [],
|
||||||
regionName: record.regionName || selectedDimension.value.regionName || '',
|
regionName: record.regionName || selectedDimension.value.regionName || '',
|
||||||
selectApp: selectedApp.value,
|
selectApp: selectedApp.value,
|
||||||
|
supportCountries: props.mode === 'country' ? supportList.value : [],
|
||||||
...(props.mode === 'country'
|
...(props.mode === 'country'
|
||||||
? {
|
? {
|
||||||
country: {
|
country: {
|
||||||
@ -266,20 +270,23 @@ function openEdit(record: Record<string, any>) {
|
|||||||
<template>
|
<template>
|
||||||
<Card :title="title">
|
<Card :title="title">
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<Select option-label-prop="label"
|
<Select
|
||||||
|
option-label-prop="label"
|
||||||
v-model:value="query.type"
|
v-model:value="query.type"
|
||||||
:options="productTypeOptions"
|
:options="productTypeOptions"
|
||||||
style="width: 180px"
|
style="width: 180px"
|
||||||
@change="handleSearch"
|
@change="handleSearch"
|
||||||
/>
|
/>
|
||||||
<Select option-label-prop="label"
|
<Select
|
||||||
|
option-label-prop="label"
|
||||||
v-model:value="query.applicationId"
|
v-model:value="query.applicationId"
|
||||||
:options="applicationOptions"
|
:options="applicationOptions"
|
||||||
placeholder="应用"
|
placeholder="应用"
|
||||||
style="width: 200px"
|
style="width: 200px"
|
||||||
@change="handleApplicationChange"
|
@change="handleApplicationChange"
|
||||||
/>
|
/>
|
||||||
<Select option-label-prop="label"
|
<Select
|
||||||
|
option-label-prop="label"
|
||||||
v-model:value="selectedDimensionId"
|
v-model:value="selectedDimensionId"
|
||||||
:loading="supportLoading"
|
:loading="supportLoading"
|
||||||
:options="supportOptions"
|
:options="supportOptions"
|
||||||
@ -287,7 +294,8 @@ function openEdit(record: Record<string, any>) {
|
|||||||
style="width: 200px"
|
style="width: 200px"
|
||||||
@change="handleDimensionChange"
|
@change="handleDimensionChange"
|
||||||
/>
|
/>
|
||||||
<Select option-label-prop="label"
|
<Select
|
||||||
|
option-label-prop="label"
|
||||||
v-model:value="query.shelf"
|
v-model:value="query.shelf"
|
||||||
:options="shelfOptions"
|
:options="shelfOptions"
|
||||||
style="width: 140px"
|
style="width: 140px"
|
||||||
@ -341,7 +349,7 @@ function openEdit(record: Record<string, any>) {
|
|||||||
:total="total"
|
:total="total"
|
||||||
show-size-changer
|
show-size-changer
|
||||||
@change="handlePageChange"
|
@change="handlePageChange"
|
||||||
@showSizeChange="handlePageChange"
|
@show-size-change="handlePageChange"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
468
apps/src/views/operate/hotgame-config.vue
Normal file
468
apps/src/views/operate/hotgame-config.vue
Normal file
@ -0,0 +1,468 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, reactive, ref, watch } from 'vue';
|
||||||
|
|
||||||
|
import { Page } from '@vben/common-ui';
|
||||||
|
import { useAccessStore } from '@vben/stores';
|
||||||
|
|
||||||
|
import {
|
||||||
|
activateHotgameProviderProfile,
|
||||||
|
fetchHotgameCatalog,
|
||||||
|
getHotgameProviderConfig,
|
||||||
|
importHotgameCatalog,
|
||||||
|
pageHotgameCatalog,
|
||||||
|
saveHotgameProviderConfig,
|
||||||
|
} from '#/api/legacy/baishun-game';
|
||||||
|
import { getAllowedSysOrigins } from '#/views/system/shared';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Collapse,
|
||||||
|
CollapsePanel,
|
||||||
|
Form,
|
||||||
|
FormItem,
|
||||||
|
Input,
|
||||||
|
Pagination,
|
||||||
|
Select,
|
||||||
|
Table,
|
||||||
|
Tag,
|
||||||
|
message,
|
||||||
|
} from 'antdv-next';
|
||||||
|
|
||||||
|
defineOptions({ name: 'OperateHotgameConfig' });
|
||||||
|
|
||||||
|
function createProviderForm(sysOrigin = '') {
|
||||||
|
return {
|
||||||
|
appKey: '',
|
||||||
|
callbackBaseUrl: '',
|
||||||
|
profile: 'PROD',
|
||||||
|
sysOrigin,
|
||||||
|
testToken: '',
|
||||||
|
testUid: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const accessStore = useAccessStore();
|
||||||
|
const sysOriginOptions = computed(() => {
|
||||||
|
const options = getAllowedSysOrigins(accessStore.accessCodes || []);
|
||||||
|
return options.length > 0 ? options : getAllowedSysOrigins([]);
|
||||||
|
});
|
||||||
|
const profileOptions = [
|
||||||
|
{ label: '正式环境', value: 'PROD' },
|
||||||
|
{ label: '测试环境', value: 'TEST' },
|
||||||
|
];
|
||||||
|
const importShowcaseOptions = [
|
||||||
|
{ label: '默认上架', value: true as any },
|
||||||
|
{ label: '默认下架', value: false as any },
|
||||||
|
];
|
||||||
|
|
||||||
|
const contextQuery = reactive({ profile: 'PROD', sysOrigin: '' });
|
||||||
|
const providerForm = reactive(createProviderForm());
|
||||||
|
const catalogQuery = reactive<Record<string, any>>({
|
||||||
|
cursor: 1,
|
||||||
|
keyword: '',
|
||||||
|
limit: 20,
|
||||||
|
});
|
||||||
|
|
||||||
|
const providerLoading = ref(false);
|
||||||
|
const providerSaving = ref(false);
|
||||||
|
const catalogLoading = ref(false);
|
||||||
|
const catalogFetching = ref(false);
|
||||||
|
const catalogImporting = ref(false);
|
||||||
|
const profileActivating = ref(false);
|
||||||
|
const totalCatalog = ref(0);
|
||||||
|
const catalogList = ref<Array<Record<string, any>>>([]);
|
||||||
|
const selectedCatalogKeys = ref<Array<string>>([]);
|
||||||
|
const activeProfile = ref('PROD');
|
||||||
|
const importDefaultShowcase = ref(true as any);
|
||||||
|
|
||||||
|
const isCurrentProfileActive = computed(
|
||||||
|
() => activeProfile.value === contextQuery.profile,
|
||||||
|
);
|
||||||
|
const activeProfileLabel = computed(
|
||||||
|
() =>
|
||||||
|
profileOptions.find((item) => item.value === activeProfile.value)?.label ||
|
||||||
|
activeProfile.value,
|
||||||
|
);
|
||||||
|
|
||||||
|
const catalogColumns = [
|
||||||
|
{ dataIndex: 'name', key: 'name', title: '名称', width: 180 },
|
||||||
|
{ dataIndex: 'gameId', key: 'gameId', title: '内部ID', width: 150 },
|
||||||
|
{ dataIndex: 'vendorGameId', key: 'vendorGameId', title: '热游ID', width: 120 },
|
||||||
|
{ dataIndex: 'status', key: 'status', title: '目录状态', width: 110 },
|
||||||
|
{ dataIndex: 'packageVersion', key: 'packageVersion', title: '版本', width: 90 },
|
||||||
|
{ dataIndex: 'added', key: 'added', title: '统一游戏列表', width: 140 },
|
||||||
|
{ dataIndex: 'downloadUrl', key: 'downloadUrl', title: '启动地址', width: 520 },
|
||||||
|
{ dataIndex: 'updateTime', key: 'updateTime', title: '更新时间', width: 180 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const catalogRowSelection = computed(() => ({
|
||||||
|
selectedRowKeys: selectedCatalogKeys.value,
|
||||||
|
onChange: (keys: Array<number | string>) => {
|
||||||
|
selectedCatalogKeys.value = keys.map((item) => String(item));
|
||||||
|
},
|
||||||
|
getCheckboxProps: (record: Record<string, any>) => ({
|
||||||
|
disabled: Boolean(record.added),
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
watch(
|
||||||
|
sysOriginOptions,
|
||||||
|
(options) => {
|
||||||
|
if (!contextQuery.sysOrigin) {
|
||||||
|
contextQuery.sysOrigin = String(options[0]?.value || '');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => [contextQuery.sysOrigin, contextQuery.profile],
|
||||||
|
([sysOrigin, profile]) => {
|
||||||
|
if (!sysOrigin || !profile) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Object.assign(providerForm, createProviderForm(sysOrigin), {
|
||||||
|
profile,
|
||||||
|
sysOrigin,
|
||||||
|
});
|
||||||
|
selectedCatalogKeys.value = [];
|
||||||
|
void Promise.all([loadProviderConfig(), loadCatalog(true)]);
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
function applyProviderForm(record: Record<string, any>) {
|
||||||
|
activeProfile.value = String(record.activeProfile || activeProfile.value || 'PROD');
|
||||||
|
Object.assign(providerForm, createProviderForm(contextQuery.sysOrigin), {
|
||||||
|
...record,
|
||||||
|
profile: contextQuery.profile,
|
||||||
|
sysOrigin: contextQuery.sysOrigin,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadProviderConfig() {
|
||||||
|
if (!contextQuery.sysOrigin) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
providerLoading.value = true;
|
||||||
|
try {
|
||||||
|
const result = await getHotgameProviderConfig({
|
||||||
|
profile: contextQuery.profile,
|
||||||
|
sysOrigin: contextQuery.sysOrigin,
|
||||||
|
});
|
||||||
|
applyProviderForm(result || {});
|
||||||
|
} finally {
|
||||||
|
providerLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function persistProviderConfig(showSuccess = true) {
|
||||||
|
if (!contextQuery.sysOrigin || !providerForm.appKey) {
|
||||||
|
message.warning('请补全热游签名 Key');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
providerSaving.value = true;
|
||||||
|
try {
|
||||||
|
const result = await saveHotgameProviderConfig({
|
||||||
|
...providerForm,
|
||||||
|
profile: contextQuery.profile,
|
||||||
|
sysOrigin: contextQuery.sysOrigin,
|
||||||
|
});
|
||||||
|
applyProviderForm(result || providerForm);
|
||||||
|
if (showSuccess) {
|
||||||
|
message.success('热游配置已保存');
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
providerSaving.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadCatalog(reset = false) {
|
||||||
|
if (!contextQuery.sysOrigin) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (reset) {
|
||||||
|
catalogQuery.cursor = 1;
|
||||||
|
}
|
||||||
|
catalogLoading.value = true;
|
||||||
|
try {
|
||||||
|
const result = await pageHotgameCatalog({
|
||||||
|
...catalogQuery,
|
||||||
|
profile: contextQuery.profile,
|
||||||
|
sysOrigin: contextQuery.sysOrigin,
|
||||||
|
});
|
||||||
|
catalogList.value = Array.isArray(result.records) ? result.records : [];
|
||||||
|
totalCatalog.value = Number(result.total || 0);
|
||||||
|
} finally {
|
||||||
|
catalogLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCatalogPageChange(page: number, pageSize: number) {
|
||||||
|
catalogQuery.cursor = page;
|
||||||
|
catalogQuery.limit = pageSize;
|
||||||
|
void loadCatalog();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleActivateProfile() {
|
||||||
|
if (!(await persistProviderConfig(false))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
profileActivating.value = true;
|
||||||
|
try {
|
||||||
|
const result = await activateHotgameProviderProfile({
|
||||||
|
profile: contextQuery.profile,
|
||||||
|
sysOrigin: contextQuery.sysOrigin,
|
||||||
|
});
|
||||||
|
applyProviderForm(result || providerForm);
|
||||||
|
message.success(`已切换到${activeProfileLabel.value}`);
|
||||||
|
await loadCatalog(true);
|
||||||
|
} finally {
|
||||||
|
profileActivating.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleFetchCatalog() {
|
||||||
|
if (!(await persistProviderConfig(false))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catalogFetching.value = true;
|
||||||
|
try {
|
||||||
|
const result = await fetchHotgameCatalog({
|
||||||
|
profile: contextQuery.profile,
|
||||||
|
sysOrigin: contextQuery.sysOrigin,
|
||||||
|
});
|
||||||
|
message.success(
|
||||||
|
`同步完成:新增 ${Number(result.inserted || 0)},更新 ${Number(result.updated || 0)},跳过 ${Number(result.skipped || 0)}`,
|
||||||
|
);
|
||||||
|
await loadCatalog(true);
|
||||||
|
} finally {
|
||||||
|
catalogFetching.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleImportSelected() {
|
||||||
|
if (selectedCatalogKeys.value.length === 0) {
|
||||||
|
message.warning('请选择要添加到统一游戏列表的热游游戏');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catalogImporting.value = true;
|
||||||
|
try {
|
||||||
|
const result = await importHotgameCatalog({
|
||||||
|
profile: contextQuery.profile,
|
||||||
|
sysOrigin: contextQuery.sysOrigin,
|
||||||
|
vendorGameIds: selectedCatalogKeys.value,
|
||||||
|
defaultShowcase: importDefaultShowcase.value,
|
||||||
|
});
|
||||||
|
selectedCatalogKeys.value = [];
|
||||||
|
message.success(
|
||||||
|
`添加完成:新增 ${Number(result.imported || 0)},已存在 ${Number(result.existing || 0)}`,
|
||||||
|
);
|
||||||
|
await loadCatalog(true);
|
||||||
|
} finally {
|
||||||
|
catalogImporting.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page title="热游配置">
|
||||||
|
<Card :loading="providerLoading" title="热游接入配置">
|
||||||
|
<Form layout="vertical">
|
||||||
|
<div class="form-grid provider-grid">
|
||||||
|
<FormItem label="系统">
|
||||||
|
<SysOriginSelect
|
||||||
|
v-model:value="contextQuery.sysOrigin"
|
||||||
|
:options="sysOriginOptions"
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="配置环境" extra="app 侧只读取当前启用环境。">
|
||||||
|
<Select
|
||||||
|
v-model:value="contextQuery.profile"
|
||||||
|
:options="profileOptions"
|
||||||
|
option-label-prop="label"
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="回调域名">
|
||||||
|
<Input
|
||||||
|
v-model:value="providerForm.callbackBaseUrl"
|
||||||
|
placeholder="https://api.global-interaction.com/"
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="Key">
|
||||||
|
<Input
|
||||||
|
v-model:value="providerForm.appKey"
|
||||||
|
placeholder="请输入热游签名 Key"
|
||||||
|
type="password"
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
</div>
|
||||||
|
<Collapse class="test-user-collapse" ghost>
|
||||||
|
<CollapsePanel key="test-user" header="测试用户">
|
||||||
|
<div class="form-grid">
|
||||||
|
<FormItem label="UID">
|
||||||
|
<Input v-model:value="providerForm.testUid" placeholder="可选" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="Token">
|
||||||
|
<Input
|
||||||
|
v-model:value="providerForm.testToken"
|
||||||
|
placeholder="可选"
|
||||||
|
type="password"
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
</div>
|
||||||
|
</CollapsePanel>
|
||||||
|
</Collapse>
|
||||||
|
<div class="toolbar">
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
:loading="providerSaving"
|
||||||
|
@click="persistProviderConfig(true)"
|
||||||
|
>
|
||||||
|
保存配置
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
:disabled="isCurrentProfileActive"
|
||||||
|
:loading="profileActivating"
|
||||||
|
@click="handleActivateProfile"
|
||||||
|
>
|
||||||
|
启用此环境
|
||||||
|
</Button>
|
||||||
|
<Button :loading="catalogFetching" @click="handleFetchCatalog">
|
||||||
|
同步热游目录
|
||||||
|
</Button>
|
||||||
|
<Tag :color="isCurrentProfileActive ? 'success' : 'warning'">
|
||||||
|
当前启用:{{ activeProfileLabel }}
|
||||||
|
</Tag>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card class="content-card" title="热游目录">
|
||||||
|
<div class="toolbar">
|
||||||
|
<Input
|
||||||
|
v-model:value="catalogQuery.keyword"
|
||||||
|
placeholder="搜索目录名称 / 内部ID / 热游ID"
|
||||||
|
style="width: 280px"
|
||||||
|
@pressEnter="loadCatalog(true)"
|
||||||
|
/>
|
||||||
|
<Button :loading="catalogLoading" type="primary" @click="loadCatalog(true)">
|
||||||
|
搜索
|
||||||
|
</Button>
|
||||||
|
<Button :loading="catalogFetching" @click="handleFetchCatalog">
|
||||||
|
重新同步目录
|
||||||
|
</Button>
|
||||||
|
<Select
|
||||||
|
v-model:value="importDefaultShowcase"
|
||||||
|
:options="importShowcaseOptions"
|
||||||
|
style="width: 120px"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
:loading="catalogImporting"
|
||||||
|
@click="handleImportSelected"
|
||||||
|
>
|
||||||
|
添加已选游戏
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Table
|
||||||
|
:columns="catalogColumns"
|
||||||
|
:data-source="catalogList"
|
||||||
|
:loading="catalogLoading"
|
||||||
|
:pagination="false"
|
||||||
|
row-key="vendorGameId"
|
||||||
|
:row-selection="catalogRowSelection"
|
||||||
|
:scroll="{ x: 1500 }"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'status'">
|
||||||
|
<Tag :color="record.status === 'ENABLED' ? 'success' : 'default'">
|
||||||
|
{{ record.status || '-' }}
|
||||||
|
</Tag>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'added'">
|
||||||
|
<Tag :color="record.added ? 'success' : 'default'">
|
||||||
|
{{ record.added ? '已进入统一游戏列表' : '未添加' }}
|
||||||
|
</Tag>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'downloadUrl'">
|
||||||
|
<a
|
||||||
|
v-if="record.downloadUrl"
|
||||||
|
:href="record.downloadUrl"
|
||||||
|
class="link-cell"
|
||||||
|
rel="noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
{{ record.downloadUrl }}
|
||||||
|
</a>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</Table>
|
||||||
|
|
||||||
|
<div v-if="totalCatalog > 0" class="pager">
|
||||||
|
<Pagination
|
||||||
|
:current="catalogQuery.cursor"
|
||||||
|
:page-size="catalogQuery.limit"
|
||||||
|
:total="totalCatalog"
|
||||||
|
show-size-changer
|
||||||
|
@change="handleCatalogPageChange"
|
||||||
|
@showSizeChange="handleCatalogPageChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.content-card {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pager {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.provider-grid {
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.test-user-collapse {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-cell {
|
||||||
|
display: inline-block;
|
||||||
|
max-width: 500px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 960px) {
|
||||||
|
.form-grid,
|
||||||
|
.provider-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -6,6 +6,8 @@ import {
|
|||||||
watch,
|
watch,
|
||||||
} from 'vue';
|
} from 'vue';
|
||||||
|
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
import { Page } from '@vben/common-ui';
|
import { Page } from '@vben/common-ui';
|
||||||
import { useAccessStore } from '@vben/stores';
|
import { useAccessStore } from '@vben/stores';
|
||||||
|
|
||||||
@ -21,6 +23,7 @@ import { getAllowedSysOrigins } from '#/views/system/shared';
|
|||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
|
DatePicker,
|
||||||
Modal,
|
Modal,
|
||||||
Select,
|
Select,
|
||||||
Space,
|
Space,
|
||||||
@ -59,6 +62,7 @@ const salarySortKey = ref<'' | SalarySortKey>('');
|
|||||||
const salarySortOrder = ref<SalarySortOrder>('');
|
const salarySortOrder = ref<SalarySortOrder>('');
|
||||||
|
|
||||||
const query = reactive({
|
const query = reactive({
|
||||||
|
billBelong: dayjs().format('YYYYMM'),
|
||||||
countryCode: '',
|
countryCode: '',
|
||||||
cursor: 1,
|
cursor: 1,
|
||||||
limit: 20,
|
limit: 20,
|
||||||
@ -75,13 +79,13 @@ const columns = [
|
|||||||
{ dataIndex: 'currentTarget', key: 'currentTarget', title: '本次积分', width: 130 },
|
{ dataIndex: 'currentTarget', key: 'currentTarget', title: '本次积分', width: 130 },
|
||||||
{ dataIndex: 'policyLevel', key: 'policyLevel', title: '本次积分档位', width: 130 },
|
{ dataIndex: 'policyLevel', key: 'policyLevel', title: '本次积分档位', width: 130 },
|
||||||
{ dataIndex: 'expectedMemberSalary', key: 'expectedMemberSalary', title: '主播应发', width: 120 },
|
{ dataIndex: 'expectedMemberSalary', key: 'expectedMemberSalary', title: '主播应发', width: 120 },
|
||||||
{ dataIndex: 'expectedAgentSalary', key: 'expectedAgentSalary', title: '代理应发', width: 120 },
|
{ dataIndex: 'expectedAgentSalary', key: 'expectedAgentSalary', title: '上级代理应发', width: 130 },
|
||||||
{ dataIndex: 'expectedSalary', key: 'expectedSalary', title: '应发合计', width: 120 },
|
{ dataIndex: 'expectedSalary', key: 'expectedSalary', title: '应发合计', width: 120 },
|
||||||
{ dataIndex: 'payableMemberSalary', key: 'payableMemberSalary', title: '主播差额', width: 120 },
|
{ dataIndex: 'payableMemberSalary', key: 'payableMemberSalary', title: '主播差额', width: 120 },
|
||||||
{ dataIndex: 'payableAgentSalary', key: 'payableAgentSalary', title: '代理差额', width: 120 },
|
{ dataIndex: 'payableAgentSalary', key: 'payableAgentSalary', title: '上级代理差额', width: 130 },
|
||||||
{ dataIndex: 'payableSalary', key: 'payableSalary', title: '合计差额', width: 140 },
|
{ dataIndex: 'payableSalary', key: 'payableSalary', title: '合计待发', width: 140 },
|
||||||
{ dataIndex: 'memberIssuedSalary', key: 'memberIssuedSalary', title: '主播实收', width: 140 },
|
{ dataIndex: 'memberIssuedSalary', key: 'memberIssuedSalary', title: '主播实收', width: 140 },
|
||||||
{ dataIndex: 'agentIssuedSalary', key: 'agentIssuedSalary', title: '代理实收', width: 140 },
|
{ dataIndex: 'agentIssuedSalary', key: 'agentIssuedSalary', title: '上级代理实收', width: 140 },
|
||||||
{ dataIndex: 'totalIssuedSalary', key: 'totalIssuedSalary', title: '总发工资', width: 140 },
|
{ dataIndex: 'totalIssuedSalary', key: 'totalIssuedSalary', title: '总发工资', width: 140 },
|
||||||
{ dataIndex: 'currentSalaryBalance', key: 'currentSalaryBalance', title: '当前工资', width: 140 },
|
{ dataIndex: 'currentSalaryBalance', key: 'currentSalaryBalance', title: '当前工资', width: 140 },
|
||||||
{ dataIndex: 'action', fixed: 'right', key: 'action', title: '操作', width: 110 },
|
{ dataIndex: 'action', fixed: 'right', key: 'action', title: '操作', width: 110 },
|
||||||
@ -168,7 +172,7 @@ watch(
|
|||||||
);
|
);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
[() => query.sysOrigin, () => query.countryCode, () => query.userId],
|
[() => query.sysOrigin, () => query.countryCode, () => query.userId, () => query.billBelong],
|
||||||
() => {
|
() => {
|
||||||
selectedUserIds.value = [];
|
selectedUserIds.value = [];
|
||||||
},
|
},
|
||||||
@ -226,19 +230,8 @@ function formatMoney(value?: number | string) {
|
|||||||
return `$${amount.toFixed(2)}`;
|
return `$${amount.toFixed(2)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function shouldShowAgentSalary(record: Record<string, any>) {
|
|
||||||
if (record.agentMember !== undefined && record.agentMember !== null) {
|
|
||||||
return record.agentMember === true;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
!!record.userId &&
|
|
||||||
!!record.teamOwnId &&
|
|
||||||
String(record.userId) === String(record.teamOwnId)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatAgentMoney(record: Record<string, any>, key: string) {
|
function formatAgentMoney(record: Record<string, any>, key: string) {
|
||||||
return shouldShowAgentSalary(record) ? formatMoney(record[key]) : '-';
|
return formatMoney(record[key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatInteger(value?: number | string) {
|
function formatInteger(value?: number | string) {
|
||||||
@ -256,6 +249,13 @@ function countryLabel(code?: string) {
|
|||||||
return `${country.phonePrefix || ''} ${country.countryName || country.aliasName || code}`;
|
return `${country.phonePrefix || ''} ${country.countryName || country.aliasName || code}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function billBelongLabel() {
|
||||||
|
if (!query.billBelong || query.billBelong.length !== 6) {
|
||||||
|
return '当前账期';
|
||||||
|
}
|
||||||
|
return `${query.billBelong.slice(0, 4)}-${query.billBelong.slice(4, 6)}`;
|
||||||
|
}
|
||||||
|
|
||||||
function getRowKey(record: Record<string, any>) {
|
function getRowKey(record: Record<string, any>) {
|
||||||
return String(record.id || record.userProfile?.account || record.userId || '');
|
return String(record.id || record.userProfile?.account || record.userId || '');
|
||||||
}
|
}
|
||||||
@ -327,6 +327,7 @@ function toggleSalarySort(key?: number | string) {
|
|||||||
async function submitManualSalary(userIds?: string[]) {
|
async function submitManualSalary(userIds?: string[]) {
|
||||||
const hasSelectedUsers = !!userIds?.length;
|
const hasSelectedUsers = !!userIds?.length;
|
||||||
resultRows.value = await confirmManualSalary({
|
resultRows.value = await confirmManualSalary({
|
||||||
|
billBelong: Number(query.billBelong),
|
||||||
countryCode: query.countryCode,
|
countryCode: query.countryCode,
|
||||||
sysOrigin: query.sysOrigin,
|
sysOrigin: query.sysOrigin,
|
||||||
...(hasSelectedUsers ? { userIds } : {}),
|
...(hasSelectedUsers ? { userIds } : {}),
|
||||||
@ -354,8 +355,8 @@ function confirmPay() {
|
|||||||
? '本次只发放已勾选的用户。'
|
? '本次只发放已勾选的用户。'
|
||||||
: '未勾选用户,本次将发放当前筛选国家的所有可发工资用户。',
|
: '未勾选用户,本次将发放当前筛选国家的所有可发工资用户。',
|
||||||
title: hasSelectedUsers
|
title: hasSelectedUsers
|
||||||
? `确认发放已选择的 ${userIds.length} 人当前账期工资?`
|
? `确认发放已选择的 ${userIds.length} 人 ${billBelongLabel()} 工资?`
|
||||||
: `确认发放 ${countryLabel(query.countryCode)} 当前账期工资?`,
|
: `确认发放 ${countryLabel(query.countryCode)} ${billBelongLabel()} 工资?`,
|
||||||
async onOk() {
|
async onOk() {
|
||||||
paying.value = true;
|
paying.value = true;
|
||||||
try {
|
try {
|
||||||
@ -383,7 +384,7 @@ function settleUser(record: Record<string, any>) {
|
|||||||
content: payableSalary < 0
|
content: payableSalary < 0
|
||||||
? '该用户应发小于实收,本次发放0并置为已结算,不会对用户扣款。'
|
? '该用户应发小于实收,本次发放0并置为已结算,不会对用户扣款。'
|
||||||
: '本次只结算当前这一行用户的工资。',
|
: '本次只结算当前这一行用户的工资。',
|
||||||
title: `确认结算用户 ${record.userProfile?.account || userId} 的当前账期工资?`,
|
title: `确认结算用户 ${record.userProfile?.account || userId} 的 ${billBelongLabel()} 工资?`,
|
||||||
async onOk() {
|
async onOk() {
|
||||||
settlingUserId.value = userId;
|
settlingUserId.value = userId;
|
||||||
try {
|
try {
|
||||||
@ -428,6 +429,16 @@ void loadCountries();
|
|||||||
@change="loadData(true)"
|
@change="loadData(true)"
|
||||||
@search="countryKeyword = $event"
|
@search="countryKeyword = $event"
|
||||||
/>
|
/>
|
||||||
|
<DatePicker
|
||||||
|
v-model:value="query.billBelong"
|
||||||
|
:allow-clear="false"
|
||||||
|
format="YYYYMM"
|
||||||
|
picker="month"
|
||||||
|
placeholder="账期月份"
|
||||||
|
style="width: 140px"
|
||||||
|
value-format="YYYYMM"
|
||||||
|
@change="loadData(true)"
|
||||||
|
/>
|
||||||
<AccountInput
|
<AccountInput
|
||||||
v-model:value="query.userId"
|
v-model:value="query.userId"
|
||||||
:sys-origin="query.sysOrigin"
|
:sys-origin="query.sysOrigin"
|
||||||
@ -546,7 +557,7 @@ void loadCountries();
|
|||||||
<template v-else-if="column.key === 'agentIssuedSalary'">
|
<template v-else-if="column.key === 'agentIssuedSalary'">
|
||||||
<span>{{ formatAgentMoney(record, 'agentIssuedSalary') }}</span>
|
<span>{{ formatAgentMoney(record, 'agentIssuedSalary') }}</span>
|
||||||
<span
|
<span
|
||||||
v-if="shouldShowAgentSalary(record) && Number(record.agentOverIssuedSalary || 0) > 0"
|
v-if="Number(record.agentOverIssuedSalary || 0) > 0"
|
||||||
class="over-issued-salary"
|
class="over-issued-salary"
|
||||||
>
|
>
|
||||||
({{ formatMoney(record.agentOverIssuedSalary) }})
|
({{ formatMoney(record.agentOverIssuedSalary) }})
|
||||||
|
|||||||
@ -1,33 +1,30 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {
|
import { computed, reactive, ref, watch } from 'vue';
|
||||||
computed,
|
|
||||||
reactive,
|
|
||||||
ref,
|
|
||||||
watch } from 'vue';
|
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
import { Page } from '@vben/common-ui';
|
import { Page } from '@vben/common-ui';
|
||||||
import { useAccessStore } from '@vben/stores';
|
import { useAccessStore } from '@vben/stores';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
exportInAppPurchaseDetails,
|
||||||
getOrderAmount,
|
getOrderAmount,
|
||||||
listInAppPurchase,
|
listInAppPurchase,
|
||||||
} from '#/api/legacy/operate';
|
} from '#/api/legacy/operate';
|
||||||
import { regionConfigTable } from '#/api/legacy/system';
|
import { regionConfigTable } from '#/api/legacy/system';
|
||||||
import AccountInput from '#/components/account-input.vue';
|
import AccountInput from '#/components/account-input.vue';
|
||||||
import SysOriginTag from '#/components/sys-origin-tag.vue';
|
import SysOriginTag from '#/components/sys-origin-tag.vue';
|
||||||
import { formatDate,
|
import { formatDate, getAllowedSysOrigins } from '#/views/system/shared';
|
||||||
getAllowedSysOrigins } from '#/views/system/shared';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
DatePicker,
|
DatePicker,
|
||||||
Input,
|
Input,
|
||||||
|
message,
|
||||||
Select,
|
Select,
|
||||||
Space,
|
Space,
|
||||||
Table,
|
Table,
|
||||||
Tag
|
Tag,
|
||||||
} from 'antdv-next';
|
} from 'antdv-next';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -50,6 +47,7 @@ const sysOriginOptions = computed(() => {
|
|||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const loadMoreLoading = ref(false);
|
const loadMoreLoading = ref(false);
|
||||||
|
const exporting = ref(false);
|
||||||
const list = ref<Array<Record<string, any>>>([]);
|
const list = ref<Array<Record<string, any>>>([]);
|
||||||
const regions = ref<Array<Record<string, any>>>([]);
|
const regions = ref<Array<Record<string, any>>>([]);
|
||||||
const totalInfo = ref<Record<string, any>>({});
|
const totalInfo = ref<Record<string, any>>({});
|
||||||
@ -137,10 +135,15 @@ async function loadData(reset = false) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildQueryParams() {
|
function buildQueryParams(includePaging = true) {
|
||||||
query.sysOrigins = getEffectiveSysOrigin();
|
query.sysOrigins = getEffectiveSysOrigin();
|
||||||
|
const params = { ...query };
|
||||||
|
if (!includePaging) {
|
||||||
|
delete params.lastId;
|
||||||
|
delete params.limit;
|
||||||
|
}
|
||||||
return Object.fromEntries(
|
return Object.fromEntries(
|
||||||
Object.entries({ ...query }).filter(([, value]) => {
|
Object.entries(params).filter(([, value]) => {
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
return value.length > 0;
|
return value.length > 0;
|
||||||
}
|
}
|
||||||
@ -153,6 +156,16 @@ function handleSearch() {
|
|||||||
void loadData(true);
|
void loadData(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleExport() {
|
||||||
|
exporting.value = true;
|
||||||
|
try {
|
||||||
|
await exportInAppPurchaseDetails(buildQueryParams(false));
|
||||||
|
message.success('导出成功');
|
||||||
|
} finally {
|
||||||
|
exporting.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function openDetails(record: Record<string, any>) {
|
function openDetails(record: Record<string, any>) {
|
||||||
activeOrderId.value = record.details?.id || '';
|
activeOrderId.value = record.details?.id || '';
|
||||||
detailsOpen.value = true;
|
detailsOpen.value = true;
|
||||||
@ -233,6 +246,9 @@ void loadData(true);
|
|||||||
<Button :loading="loading" type="primary" @click="handleSearch">
|
<Button :loading="loading" type="primary" @click="handleSearch">
|
||||||
搜索
|
搜索
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button :loading="exporting" @click="handleExport">
|
||||||
|
导出
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="summary">
|
<div class="summary">
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import { Page } from '@vben/common-ui';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
pagePayOpenCounty,
|
pagePayOpenCounty,
|
||||||
|
syncPayOpenCountryCurrencyRates,
|
||||||
updatePayOpenCountryCurrent,
|
updatePayOpenCountryCurrent,
|
||||||
updatePayOpenCountryExchangeRate,
|
updatePayOpenCountryExchangeRate,
|
||||||
updatePayOpenCountrySort,
|
updatePayOpenCountrySort,
|
||||||
@ -41,6 +42,9 @@ const activeRow = ref<Record<string, any>>({});
|
|||||||
const editOpen = ref(false);
|
const editOpen = ref(false);
|
||||||
const editLoading = ref(false);
|
const editLoading = ref(false);
|
||||||
const editValue = ref('');
|
const editValue = ref('');
|
||||||
|
const syncOpen = ref(false);
|
||||||
|
const syncLoading = ref(false);
|
||||||
|
const syncMarkupPercent = ref('');
|
||||||
const editConfig = reactive({
|
const editConfig = reactive({
|
||||||
field: '' as 'currency' | 'rate' | 'sort' | '',
|
field: '' as 'currency' | 'rate' | 'sort' | '',
|
||||||
title: '',
|
title: '',
|
||||||
@ -172,6 +176,34 @@ function openAssociated(row: Record<string, any>) {
|
|||||||
associatedOpen.value = true;
|
associatedOpen.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openSyncRates() {
|
||||||
|
syncMarkupPercent.value = '';
|
||||||
|
syncOpen.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateSyncMarkupPercent() {
|
||||||
|
return /^(100(\.0{1,4})?|([1-9]\d?|0)(\.\d{1,4})?)$/.test(syncMarkupPercent.value.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitSyncRates() {
|
||||||
|
if (!validateSyncMarkupPercent()) {
|
||||||
|
message.warning('请输入0~100之间的上浮比例,最多四位小数');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
syncLoading.value = true;
|
||||||
|
try {
|
||||||
|
const result = await syncPayOpenCountryCurrencyRates({
|
||||||
|
markupPercent: syncMarkupPercent.value.trim(),
|
||||||
|
});
|
||||||
|
const skippedText = result?.skippedCount ? `,跳过${result.skippedCount}个` : '';
|
||||||
|
message.success(`同步成功,更新${result?.updatedCount || 0}个国家${skippedText}`);
|
||||||
|
syncOpen.value = false;
|
||||||
|
await loadData();
|
||||||
|
} finally {
|
||||||
|
syncLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void loadData(true);
|
void loadData(true);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -188,6 +220,7 @@ void loadData(true);
|
|||||||
/>
|
/>
|
||||||
<Button type="primary" @click="countrySelectOpen = true">添加</Button>
|
<Button type="primary" @click="countrySelectOpen = true">添加</Button>
|
||||||
<Button type="primary" @click="handleSearch">刷新</Button>
|
<Button type="primary" @click="handleSearch">刷新</Button>
|
||||||
|
<Button :loading="syncLoading" @click="openSyncRates">同步全球汇率</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Table
|
<Table
|
||||||
@ -268,6 +301,19 @@ void loadData(true);
|
|||||||
>
|
>
|
||||||
<Input v-model:value="editValue" />
|
<Input v-model:value="editValue" />
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
:confirm-loading="syncLoading"
|
||||||
|
:open="syncOpen"
|
||||||
|
title="同步全球汇率"
|
||||||
|
@cancel="syncOpen = false"
|
||||||
|
@ok="submitSyncRates"
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
v-model:value="syncMarkupPercent"
|
||||||
|
placeholder="请输入上浮比例,5表示上浮5%"
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,5 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {
|
import { computed, reactive, ref, watch } from 'vue';
|
||||||
computed,
|
|
||||||
reactive,
|
|
||||||
ref,
|
|
||||||
watch } from 'vue';
|
|
||||||
|
|
||||||
import { Page } from '@vben/common-ui';
|
import { Page } from '@vben/common-ui';
|
||||||
import { useAccessStore } from '@vben/stores';
|
import { useAccessStore } from '@vben/stores';
|
||||||
@ -11,10 +7,10 @@ import { useAccessStore } from '@vben/stores';
|
|||||||
import {
|
import {
|
||||||
exportFreightWaters,
|
exportFreightWaters,
|
||||||
pageFreightRunningWater,
|
pageFreightRunningWater,
|
||||||
} from '#/api/legacy/user';
|
} from '#/api/legacy/user';
|
||||||
|
import { listBackUserOptions } from '#/api/legacy/system';
|
||||||
import AccountInput from '#/components/account-input.vue';
|
import AccountInput from '#/components/account-input.vue';
|
||||||
import { formatDate,
|
import { formatDate, getAllowedSysOrigins } from '#/views/system/shared';
|
||||||
getAllowedSysOrigins } from '#/views/system/shared';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@ -23,7 +19,7 @@ import {
|
|||||||
Pagination,
|
Pagination,
|
||||||
Select,
|
Select,
|
||||||
Table,
|
Table,
|
||||||
message
|
message,
|
||||||
} from 'antdv-next';
|
} from 'antdv-next';
|
||||||
|
|
||||||
import UserProfileLink from './components/user-profile-link.vue';
|
import UserProfileLink from './components/user-profile-link.vue';
|
||||||
@ -50,11 +46,14 @@ const loading = ref(false);
|
|||||||
const total = ref(0);
|
const total = ref(0);
|
||||||
const list = ref<Array<Record<string, any>>>([]);
|
const list = ref<Array<Record<string, any>>>([]);
|
||||||
const exporting = ref(false);
|
const exporting = ref(false);
|
||||||
|
const operatorsLoading = ref(false);
|
||||||
|
const operators = ref<Array<Record<string, any>>>([]);
|
||||||
const rangeDate = ref<[string, string] | null>(null);
|
const rangeDate = ref<[string, string] | null>(null);
|
||||||
|
|
||||||
const query = reactive<Record<string, any>>({
|
const query = reactive<Record<string, any>>({
|
||||||
acceptUserId: '',
|
acceptUserId: '',
|
||||||
cursor: 1,
|
cursor: 1,
|
||||||
|
createUsers: [],
|
||||||
endTime: '',
|
endTime: '',
|
||||||
limit: 20,
|
limit: 20,
|
||||||
origin: undefined,
|
origin: undefined,
|
||||||
@ -76,6 +75,17 @@ const columns = [
|
|||||||
{ dataIndex: 'createTime', key: 'createTime', title: '创建时间', width: 180 },
|
{ dataIndex: 'createTime', key: 'createTime', title: '创建时间', width: 180 },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const operatorOptions = computed(() =>
|
||||||
|
operators.value.map((item) => {
|
||||||
|
const name = item.nickname || item.loginName || item.name || item.id;
|
||||||
|
const account = item.loginName && item.loginName !== name ? ` / ${item.loginName}` : '';
|
||||||
|
return {
|
||||||
|
label: `${name}${account}`,
|
||||||
|
value: item.id,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
sysOriginOptions,
|
sysOriginOptions,
|
||||||
(options) => {
|
(options) => {
|
||||||
@ -103,6 +113,63 @@ watch(
|
|||||||
{ immediate: true },
|
{ immediate: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function normalizeOperators(items: Array<Record<string, any>>) {
|
||||||
|
const operatorMap = new Map<string, Record<string, any>>();
|
||||||
|
items.forEach((item) => {
|
||||||
|
const id = item.id || item.applicantId;
|
||||||
|
if (!id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
operatorMap.set(String(id), {
|
||||||
|
id,
|
||||||
|
loginName: item.loginName,
|
||||||
|
name: item.name,
|
||||||
|
nickname: item.nickname || item.applicantName,
|
||||||
|
status: item.status,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return [...operatorMap.values()];
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterOperatorOption(input: string, option: any) {
|
||||||
|
return String(option?.label || '')
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(input.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadOperators() {
|
||||||
|
operatorsLoading.value = true;
|
||||||
|
try {
|
||||||
|
const result = await listBackUserOptions();
|
||||||
|
operators.value = normalizeOperators(Array.isArray(result) ? result : []);
|
||||||
|
} catch {
|
||||||
|
operators.value = [];
|
||||||
|
} finally {
|
||||||
|
operatorsLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildFreightRunningWaterParams(withPage = true) {
|
||||||
|
const createUsers = Array.isArray(query.createUsers)
|
||||||
|
? query.createUsers.filter(Boolean).join(',')
|
||||||
|
: '';
|
||||||
|
const params: Record<string, any> = {
|
||||||
|
acceptUserId: query.acceptUserId || undefined,
|
||||||
|
createUsers: createUsers || undefined,
|
||||||
|
endTime: query.endTime || undefined,
|
||||||
|
origin: query.origin || undefined,
|
||||||
|
queryBackOperationUser: query.queryBackOperationUser,
|
||||||
|
startTime: query.startTime || undefined,
|
||||||
|
sysOrigin: query.sysOrigin,
|
||||||
|
userId: query.userId || undefined,
|
||||||
|
};
|
||||||
|
if (withPage) {
|
||||||
|
params.cursor = query.cursor;
|
||||||
|
params.limit = query.limit;
|
||||||
|
}
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
async function loadData(reset = false) {
|
async function loadData(reset = false) {
|
||||||
if (!query.sysOrigin) {
|
if (!query.sysOrigin) {
|
||||||
return;
|
return;
|
||||||
@ -112,7 +179,7 @@ async function loadData(reset = false) {
|
|||||||
}
|
}
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
const result = await pageFreightRunningWater({ ...query });
|
const result = await pageFreightRunningWater(buildFreightRunningWaterParams());
|
||||||
list.value = result.records || [];
|
list.value = result.records || [];
|
||||||
total.value = result.total || 0;
|
total.value = result.total || 0;
|
||||||
} finally {
|
} finally {
|
||||||
@ -137,19 +204,26 @@ async function handleExport() {
|
|||||||
}
|
}
|
||||||
exporting.value = true;
|
exporting.value = true;
|
||||||
try {
|
try {
|
||||||
await exportFreightWaters({ ...query });
|
await exportFreightWaters(
|
||||||
|
buildFreightRunningWaterParams(false),
|
||||||
|
`ExportFreightWaters_${query.startTime || 'all'}_${query.endTime || 'all'}`,
|
||||||
|
);
|
||||||
message.success('导出成功');
|
message.success('导出成功');
|
||||||
} finally {
|
} finally {
|
||||||
exporting.value = false;
|
exporting.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void loadOperators();
|
||||||
void loadData(true);
|
void loadData(true);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page v-if="!queryBackOperation" title="货运代理流水">
|
<component
|
||||||
<Card>
|
:is="queryBackOperation ? 'div' : Page"
|
||||||
|
:title="queryBackOperation ? undefined : '货运代理流水'"
|
||||||
|
>
|
||||||
|
<component :is="queryBackOperation ? 'div' : Card">
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<SysOriginSelect
|
<SysOriginSelect
|
||||||
v-model:value="query.sysOrigin"
|
v-model:value="query.sysOrigin"
|
||||||
@ -175,9 +249,21 @@ void loadData(true);
|
|||||||
allow-clear
|
allow-clear
|
||||||
placeholder="来源"
|
placeholder="来源"
|
||||||
style="width: 140px"
|
style="width: 140px"
|
||||||
|
|
||||||
:options="FREIGHT_BALANCE_ORIGIN_OPTIONS.map((item) => ({ label: `${item.name}`, value: item.value as any }))"
|
:options="FREIGHT_BALANCE_ORIGIN_OPTIONS.map((item) => ({ label: `${item.name}`, value: item.value as any }))"
|
||||||
/>
|
/>
|
||||||
|
<Select
|
||||||
|
v-model:value="query.createUsers"
|
||||||
|
allow-clear
|
||||||
|
mode="multiple"
|
||||||
|
show-search
|
||||||
|
max-tag-count="responsive"
|
||||||
|
option-label-prop="label"
|
||||||
|
placeholder="操作人"
|
||||||
|
style="width: 220px"
|
||||||
|
:filter-option="filterOperatorOption"
|
||||||
|
:loading="operatorsLoading"
|
||||||
|
:options="operatorOptions"
|
||||||
|
/>
|
||||||
<DatePicker.RangePicker
|
<DatePicker.RangePicker
|
||||||
v-model:value="rangeDate"
|
v-model:value="rangeDate"
|
||||||
show-time
|
show-time
|
||||||
@ -234,8 +320,8 @@ void loadData(true);
|
|||||||
@showSizeChange="handlePageChange"
|
@showSizeChange="handlePageChange"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</component>
|
||||||
</Page>
|
</component>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { reactive, ref, watch } from 'vue';
|
import { reactive, ref, watch } from 'vue';
|
||||||
|
|
||||||
import { pagePropsActivityRewardGroup } from '#/api/legacy/props';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Drawer,
|
Drawer,
|
||||||
@ -13,8 +11,10 @@ import {
|
|||||||
Table,
|
Table,
|
||||||
} from 'antdv-next';
|
} from 'antdv-next';
|
||||||
|
|
||||||
import RewardRow from './reward-row.vue';
|
import { pagePropsActivityRewardGroup } from '#/api/legacy/props';
|
||||||
|
|
||||||
import { PROPS_SHELF_STATUS_OPTIONS } from '../shared';
|
import { PROPS_SHELF_STATUS_OPTIONS } from '../shared';
|
||||||
|
import RewardRow from './reward-row.vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@ -52,6 +52,11 @@ const shelfStatusOptions = PROPS_SHELF_STATUS_OPTIONS.map((item) => ({
|
|||||||
value: item.value as any,
|
value: item.value as any,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
function normalizeID(value: unknown) {
|
||||||
|
const text = String(value ?? '').trim();
|
||||||
|
return text && text !== '0' ? text : '';
|
||||||
|
}
|
||||||
|
|
||||||
async function loadData(reset = false) {
|
async function loadData(reset = false) {
|
||||||
if (!props.open || !query.sysOrigin) {
|
if (!props.open || !query.sysOrigin) {
|
||||||
return;
|
return;
|
||||||
@ -62,7 +67,10 @@ async function loadData(reset = false) {
|
|||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
const result = await pagePropsActivityRewardGroup({ ...query });
|
const result = await pagePropsActivityRewardGroup({ ...query });
|
||||||
list.value = result.records || [];
|
list.value = (result.records || []).map((item: Record<string, any>) => ({
|
||||||
|
...item,
|
||||||
|
id: normalizeID(item.id),
|
||||||
|
}));
|
||||||
total.value = result.total || 0;
|
total.value = result.total || 0;
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
@ -156,7 +164,7 @@ watch(
|
|||||||
:total="total"
|
:total="total"
|
||||||
show-size-changer
|
show-size-changer
|
||||||
@change="handlePageChange"
|
@change="handlePageChange"
|
||||||
@showSizeChange="handlePageChange"
|
@show-size-change="handlePageChange"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|||||||
@ -16,8 +16,8 @@ import {
|
|||||||
Spin,
|
Spin,
|
||||||
Switch,
|
Switch,
|
||||||
Table,
|
Table,
|
||||||
Tabs,
|
|
||||||
TabPane,
|
TabPane,
|
||||||
|
Tabs,
|
||||||
Tag,
|
Tag,
|
||||||
} from 'antdv-next';
|
} from 'antdv-next';
|
||||||
|
|
||||||
@ -42,6 +42,7 @@ function createRowKey(seed?: number | string) {
|
|||||||
function createLevel(level = 1, rechargeAmount = 10) {
|
function createLevel(level = 1, rechargeAmount = 10) {
|
||||||
return {
|
return {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
googleProductId: '',
|
||||||
id: null,
|
id: null,
|
||||||
level,
|
level,
|
||||||
rechargeAmount,
|
rechargeAmount,
|
||||||
@ -53,6 +54,15 @@ function createLevel(level = 1, rechargeAmount = 10) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeID(value: unknown) {
|
||||||
|
const text = String(value ?? '').trim();
|
||||||
|
return text && text !== '0' ? text : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasPositiveID(value: unknown) {
|
||||||
|
return /^\d+$/.test(normalizeID(value));
|
||||||
|
}
|
||||||
|
|
||||||
function defaultLevels() {
|
function defaultLevels() {
|
||||||
return [createLevel(1, 10), createLevel(2, 50), createLevel(3, 100)];
|
return [createLevel(1, 10), createLevel(2, 50), createLevel(3, 100)];
|
||||||
}
|
}
|
||||||
@ -76,6 +86,7 @@ const form = reactive<Record<string, any>>({
|
|||||||
enabled: false,
|
enabled: false,
|
||||||
id: 0,
|
id: 0,
|
||||||
levelConfigs: defaultLevels(),
|
levelConfigs: defaultLevels(),
|
||||||
|
minAppVersion: '',
|
||||||
sysOrigin: '',
|
sysOrigin: '',
|
||||||
updateTime: '',
|
updateTime: '',
|
||||||
});
|
});
|
||||||
@ -84,6 +95,7 @@ const levelColumns = [
|
|||||||
{ dataIndex: 'enabled', key: 'enabled', title: '启用', width: 90 },
|
{ dataIndex: 'enabled', key: 'enabled', title: '启用', width: 90 },
|
||||||
{ dataIndex: 'level', key: 'level', title: '档位', width: 110 },
|
{ dataIndex: 'level', key: 'level', title: '档位', width: 110 },
|
||||||
{ dataIndex: 'rechargeAmount', key: 'rechargeAmount', title: '首冲金额', width: 180 },
|
{ dataIndex: 'rechargeAmount', key: 'rechargeAmount', title: '首冲金额', width: 180 },
|
||||||
|
{ dataIndex: 'googleProductId', key: 'googleProductId', title: '谷歌商品ID', width: 240 },
|
||||||
{ dataIndex: 'rewardGroup', key: 'rewardGroup', title: '奖励资源组', width: 420 },
|
{ dataIndex: 'rewardGroup', key: 'rewardGroup', title: '奖励资源组', width: 420 },
|
||||||
{ dataIndex: 'actions', key: 'actions', title: '操作', width: 100 },
|
{ dataIndex: 'actions', key: 'actions', title: '操作', width: 100 },
|
||||||
];
|
];
|
||||||
@ -154,6 +166,7 @@ function normalizeConfig(result: null | Record<string, any> | undefined) {
|
|||||||
form.configured = Boolean(value.configured);
|
form.configured = Boolean(value.configured);
|
||||||
form.enabled = Boolean(value.enabled);
|
form.enabled = Boolean(value.enabled);
|
||||||
form.id = value.id || 0;
|
form.id = value.id || 0;
|
||||||
|
form.minAppVersion = String(value.minAppVersion || '');
|
||||||
form.sysOrigin = String(value.sysOrigin || selectedSysOrigin.value || '');
|
form.sysOrigin = String(value.sysOrigin || selectedSysOrigin.value || '');
|
||||||
form.updateTime = String(value.updateTime || '');
|
form.updateTime = String(value.updateTime || '');
|
||||||
const levels = Array.isArray(value.levelConfigs) && value.levelConfigs.length > 0
|
const levels = Array.isArray(value.levelConfigs) && value.levelConfigs.length > 0
|
||||||
@ -166,11 +179,12 @@ function normalizeConfig(result: null | Record<string, any> | undefined) {
|
|||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
enabled: item.enabled !== false,
|
enabled: item.enabled !== false,
|
||||||
|
googleProductId: String(item.googleProductId || ''),
|
||||||
id: item.id ?? null,
|
id: item.id ?? null,
|
||||||
level: Number(item.level || index + 1),
|
level: Number(item.level || index + 1),
|
||||||
rechargeAmount: Number.isFinite(rechargeAmount) ? rechargeAmount : 0,
|
rechargeAmount: Number.isFinite(rechargeAmount) ? rechargeAmount : 0,
|
||||||
rechargeAmountCents: Number(item.rechargeAmountCents || 0),
|
rechargeAmountCents: Number(item.rechargeAmountCents || 0),
|
||||||
rewardGroupId: item.rewardGroupId ?? null,
|
rewardGroupId: normalizeID(item.rewardGroupId) || null,
|
||||||
rewardGroupName: String(item.rewardGroupName || ''),
|
rewardGroupName: String(item.rewardGroupName || ''),
|
||||||
rewardItems: Array.isArray(item.rewardItems) ? item.rewardItems : [],
|
rewardItems: Array.isArray(item.rewardItems) ? item.rewardItems : [],
|
||||||
rowKey: createRowKey(item.id || index),
|
rowKey: createRowKey(item.id || index),
|
||||||
@ -254,8 +268,8 @@ function handleRewardGroupSelect(row: Record<string, any>) {
|
|||||||
groupPickerOpen.value = false;
|
groupPickerOpen.value = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
target.rewardGroupId = row.id;
|
target.rewardGroupId = normalizeID(row.id) || null;
|
||||||
target.rewardGroupName = row.name;
|
target.rewardGroupName = String(row.name || '');
|
||||||
target.rewardItems = Array.isArray(row.rewardConfigList) ? row.rewardConfigList : [];
|
target.rewardItems = Array.isArray(row.rewardConfigList) ? row.rewardConfigList : [];
|
||||||
groupPickerOpen.value = false;
|
groupPickerOpen.value = false;
|
||||||
}
|
}
|
||||||
@ -268,10 +282,10 @@ function validateBeforeSave() {
|
|||||||
}
|
}
|
||||||
const seenLevels = new Set<string>();
|
const seenLevels = new Set<string>();
|
||||||
const seenAmounts = new Set<string>();
|
const seenAmounts = new Set<string>();
|
||||||
|
const seenGoogleProductIds = new Set<string>();
|
||||||
for (const item of levels) {
|
for (const item of levels) {
|
||||||
const level = Number(item.level || 0);
|
const level = Number(item.level || 0);
|
||||||
const amountCents = toAmountCents(item.rechargeAmount);
|
const amountCents = toAmountCents(item.rechargeAmount);
|
||||||
const rewardGroupId = Number(item.rewardGroupId || 0);
|
|
||||||
if (level <= 0) {
|
if (level <= 0) {
|
||||||
message.warning('档位必须大于 0');
|
message.warning('档位必须大于 0');
|
||||||
return false;
|
return false;
|
||||||
@ -280,7 +294,7 @@ function validateBeforeSave() {
|
|||||||
message.warning('首冲金额必须大于 0');
|
message.warning('首冲金额必须大于 0');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (rewardGroupId <= 0) {
|
if (!hasPositiveID(item.rewardGroupId)) {
|
||||||
message.warning('每个档位都需要配置奖励资源组');
|
message.warning('每个档位都需要配置奖励资源组');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -292,6 +306,14 @@ function validateBeforeSave() {
|
|||||||
message.warning('首冲金额不能重复');
|
message.warning('首冲金额不能重复');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
const googleProductId = String(item.googleProductId || '').trim();
|
||||||
|
if (googleProductId) {
|
||||||
|
if (seenGoogleProductIds.has(googleProductId)) {
|
||||||
|
message.warning('谷歌商品ID不能重复');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
seenGoogleProductIds.add(googleProductId);
|
||||||
|
}
|
||||||
seenLevels.add(String(level));
|
seenLevels.add(String(level));
|
||||||
seenAmounts.add(String(amountCents));
|
seenAmounts.add(String(amountCents));
|
||||||
}
|
}
|
||||||
@ -299,6 +321,10 @@ function validateBeforeSave() {
|
|||||||
message.warning('启用配置时至少要启用一个首冲奖励档位');
|
message.warning('启用配置时至少要启用一个首冲奖励档位');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (form.enabled && !String(form.minAppVersion || '').trim()) {
|
||||||
|
message.warning('启用配置时需要填写最低App版本');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -313,13 +339,15 @@ async function submitForm() {
|
|||||||
id: form.id || 0,
|
id: form.id || 0,
|
||||||
levelConfigs: (form.levelConfigs || []).map((item: Record<string, any>) => ({
|
levelConfigs: (form.levelConfigs || []).map((item: Record<string, any>) => ({
|
||||||
enabled: item.enabled !== false,
|
enabled: item.enabled !== false,
|
||||||
|
googleProductId: String(item.googleProductId || '').trim(),
|
||||||
id: item.id || 0,
|
id: item.id || 0,
|
||||||
level: Number(item.level || 0),
|
level: Number(item.level || 0),
|
||||||
rechargeAmount: formatAmount(item.rechargeAmount),
|
rechargeAmount: formatAmount(item.rechargeAmount),
|
||||||
rechargeAmountCents: toAmountCents(item.rechargeAmount),
|
rechargeAmountCents: toAmountCents(item.rechargeAmount),
|
||||||
rewardGroupId: item.rewardGroupId ?? null,
|
rewardGroupId: normalizeID(item.rewardGroupId) || null,
|
||||||
rewardGroupName: String(item.rewardGroupName || ''),
|
rewardGroupName: String(item.rewardGroupName || ''),
|
||||||
})),
|
})),
|
||||||
|
minAppVersion: String(form.minAppVersion || '').trim(),
|
||||||
sysOrigin: selectedSysOrigin.value,
|
sysOrigin: selectedSysOrigin.value,
|
||||||
});
|
});
|
||||||
message.success('保存成功');
|
message.success('保存成功');
|
||||||
@ -407,12 +435,19 @@ watch(activeTab, (value) => {
|
|||||||
<Button :loading="loading" @click="loadConfig">刷新</Button>
|
<Button :loading="loading" @click="loadConfig">刷新</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Tabs v-model:activeKey="activeTab">
|
<Tabs v-model:active-key="activeTab">
|
||||||
<TabPane key="config" tab="奖励配置">
|
<TabPane key="config" tab="奖励配置">
|
||||||
<div class="config-row">
|
<div class="config-row">
|
||||||
<Space align="center" wrap>
|
<Space align="center" wrap>
|
||||||
<span class="field-label">启用</span>
|
<span class="field-label">启用</span>
|
||||||
<Switch v-model:checked="form.enabled" />
|
<Switch v-model:checked="form.enabled" />
|
||||||
|
<span class="field-label">最低App版本</span>
|
||||||
|
<Input
|
||||||
|
v-model:value="form.minAppVersion"
|
||||||
|
allow-clear
|
||||||
|
placeholder="例如 1.5.0"
|
||||||
|
style="width: 160px"
|
||||||
|
/>
|
||||||
<span class="sub-text">更新时间:{{ form.updateTime || '-' }}</span>
|
<span class="sub-text">更新时间:{{ form.updateTime || '-' }}</span>
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
@ -423,7 +458,7 @@ watch(activeTab, (value) => {
|
|||||||
:loading="loading"
|
:loading="loading"
|
||||||
:pagination="false"
|
:pagination="false"
|
||||||
row-key="rowKey"
|
row-key="rowKey"
|
||||||
:scroll="{ x: 900 }"
|
:scroll="{ x: 1140 }"
|
||||||
>
|
>
|
||||||
<template #bodyCell="{ column, index, record }">
|
<template #bodyCell="{ column, index, record }">
|
||||||
<template v-if="column.key === 'enabled'">
|
<template v-if="column.key === 'enabled'">
|
||||||
@ -445,6 +480,14 @@ watch(activeTab, (value) => {
|
|||||||
style="width: 140px"
|
style="width: 140px"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
<template v-else-if="column.key === 'googleProductId'">
|
||||||
|
<Input
|
||||||
|
v-model:value="record.googleProductId"
|
||||||
|
allow-clear
|
||||||
|
placeholder="Google商品ID"
|
||||||
|
style="width: 200px"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
<template v-else-if="column.key === 'rewardGroup'">
|
<template v-else-if="column.key === 'rewardGroup'">
|
||||||
<div class="reward-group-cell">
|
<div class="reward-group-cell">
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
@ -518,7 +561,7 @@ watch(activeTab, (value) => {
|
|||||||
v-if="record.userAvatar"
|
v-if="record.userAvatar"
|
||||||
class="user-avatar"
|
class="user-avatar"
|
||||||
:style="avatarBackgroundStyle(record.userAvatar)"
|
:style="avatarBackgroundStyle(record.userAvatar)"
|
||||||
/>
|
></div>
|
||||||
<div class="user-main">
|
<div class="user-main">
|
||||||
<div class="user-name">{{ record.userNickname || '-' }}</div>
|
<div class="user-name">{{ record.userNickname || '-' }}</div>
|
||||||
<div class="sub-text">
|
<div class="sub-text">
|
||||||
|
|||||||
@ -16,8 +16,8 @@ import {
|
|||||||
Spin,
|
Spin,
|
||||||
Switch,
|
Switch,
|
||||||
Table,
|
Table,
|
||||||
Tabs,
|
|
||||||
TabPane,
|
TabPane,
|
||||||
|
Tabs,
|
||||||
Tag,
|
Tag,
|
||||||
} from 'antdv-next';
|
} from 'antdv-next';
|
||||||
|
|
||||||
@ -54,6 +54,15 @@ function createLevel(level = 1, rechargeAmount = 100, rewardGold = 0) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeID(value: unknown) {
|
||||||
|
const text = String(value ?? '').trim();
|
||||||
|
return text && text !== '0' ? text : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasPositiveID(value: unknown) {
|
||||||
|
return /^\d+$/.test(normalizeID(value));
|
||||||
|
}
|
||||||
|
|
||||||
function defaultLevels() {
|
function defaultLevels() {
|
||||||
return [
|
return [
|
||||||
createLevel(1, 100, 0),
|
createLevel(1, 100, 0),
|
||||||
@ -170,7 +179,7 @@ function normalizeConfig(result: null | Record<string, any> | undefined) {
|
|||||||
rechargeAmount: Number.isFinite(rechargeAmount) ? rechargeAmount : 0,
|
rechargeAmount: Number.isFinite(rechargeAmount) ? rechargeAmount : 0,
|
||||||
rechargeAmountCents: Number(item.rechargeAmountCents || 0),
|
rechargeAmountCents: Number(item.rechargeAmountCents || 0),
|
||||||
rewardGold: Number(item.rewardGold || 0),
|
rewardGold: Number(item.rewardGold || 0),
|
||||||
rewardGroupId: item.rewardGroupId ?? null,
|
rewardGroupId: normalizeID(item.rewardGroupId) || null,
|
||||||
rewardGroupName: String(item.rewardGroupName || ''),
|
rewardGroupName: String(item.rewardGroupName || ''),
|
||||||
rewardItems: Array.isArray(item.rewardItems) ? item.rewardItems : [],
|
rewardItems: Array.isArray(item.rewardItems) ? item.rewardItems : [],
|
||||||
rowKey: createRowKey(item.id || index),
|
rowKey: createRowKey(item.id || index),
|
||||||
@ -259,8 +268,8 @@ function handleRewardGroupSelect(row: Record<string, any>) {
|
|||||||
groupPickerOpen.value = false;
|
groupPickerOpen.value = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
target.rewardGroupId = row.id;
|
target.rewardGroupId = normalizeID(row.id) || null;
|
||||||
target.rewardGroupName = row.name;
|
target.rewardGroupName = String(row.name || '');
|
||||||
target.rewardItems = Array.isArray(row.rewardConfigList) ? row.rewardConfigList : [];
|
target.rewardItems = Array.isArray(row.rewardConfigList) ? row.rewardConfigList : [];
|
||||||
groupPickerOpen.value = false;
|
groupPickerOpen.value = false;
|
||||||
}
|
}
|
||||||
@ -277,7 +286,6 @@ function validateBeforeSave() {
|
|||||||
const level = Number(item.level || 0);
|
const level = Number(item.level || 0);
|
||||||
const amountCents = toAmountCents(item.rechargeAmount);
|
const amountCents = toAmountCents(item.rechargeAmount);
|
||||||
const rewardGold = Number(item.rewardGold || 0);
|
const rewardGold = Number(item.rewardGold || 0);
|
||||||
const rewardGroupId = Number(item.rewardGroupId || 0);
|
|
||||||
if (level <= 0) {
|
if (level <= 0) {
|
||||||
message.warning('档位必须大于 0');
|
message.warning('档位必须大于 0');
|
||||||
return false;
|
return false;
|
||||||
@ -286,7 +294,7 @@ function validateBeforeSave() {
|
|||||||
message.warning('充值金额必须大于 0');
|
message.warning('充值金额必须大于 0');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (rewardGold <= 0 && rewardGroupId <= 0) {
|
if (rewardGold <= 0 && !hasPositiveID(item.rewardGroupId)) {
|
||||||
message.warning('每个档位至少要配置金币或奖励组');
|
message.warning('每个档位至少要配置金币或奖励组');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -324,7 +332,7 @@ async function submitForm() {
|
|||||||
rechargeAmount: formatAmount(item.rechargeAmount),
|
rechargeAmount: formatAmount(item.rechargeAmount),
|
||||||
rechargeAmountCents: toAmountCents(item.rechargeAmount),
|
rechargeAmountCents: toAmountCents(item.rechargeAmount),
|
||||||
rewardGold: Number(item.rewardGold || 0),
|
rewardGold: Number(item.rewardGold || 0),
|
||||||
rewardGroupId: item.rewardGroupId ?? null,
|
rewardGroupId: normalizeID(item.rewardGroupId) || null,
|
||||||
rewardGroupName: String(item.rewardGroupName || ''),
|
rewardGroupName: String(item.rewardGroupName || ''),
|
||||||
})),
|
})),
|
||||||
sysOrigin: selectedSysOrigin.value,
|
sysOrigin: selectedSysOrigin.value,
|
||||||
@ -429,7 +437,7 @@ watch(activeTab, (value) => {
|
|||||||
<Button :loading="loading" @click="loadConfig">刷新</Button>
|
<Button :loading="loading" @click="loadConfig">刷新</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Tabs v-model:activeKey="activeTab">
|
<Tabs v-model:active-key="activeTab">
|
||||||
<TabPane key="config" tab="奖励配置">
|
<TabPane key="config" tab="奖励配置">
|
||||||
<div class="config-row">
|
<div class="config-row">
|
||||||
<Space align="center" wrap>
|
<Space align="center" wrap>
|
||||||
@ -551,7 +559,7 @@ watch(activeTab, (value) => {
|
|||||||
v-if="record.userAvatar"
|
v-if="record.userAvatar"
|
||||||
class="user-avatar"
|
class="user-avatar"
|
||||||
:style="avatarBackgroundStyle(record.userAvatar)"
|
:style="avatarBackgroundStyle(record.userAvatar)"
|
||||||
/>
|
></div>
|
||||||
<div class="user-main">
|
<div class="user-main">
|
||||||
<div class="user-name">{{ record.userNickname || '-' }}</div>
|
<div class="user-name">{{ record.userNickname || '-' }}</div>
|
||||||
<div class="sub-text">
|
<div class="sub-text">
|
||||||
|
|||||||
@ -196,7 +196,9 @@ const recordSummary = reactive<Record<string, any>>({
|
|||||||
endTime: '',
|
endTime: '',
|
||||||
participantCount: 0,
|
participantCount: 0,
|
||||||
startTime: '',
|
startTime: '',
|
||||||
|
totalDecorationRewardGold: 0,
|
||||||
totalDrawTimes: 0,
|
totalDrawTimes: 0,
|
||||||
|
totalGiftRewardGold: 0,
|
||||||
totalPaidGold: 0,
|
totalPaidGold: 0,
|
||||||
totalRewardGold: 0,
|
totalRewardGold: 0,
|
||||||
});
|
});
|
||||||
@ -418,7 +420,9 @@ function normalizeRecordPage(result: null | Record<string, any> | undefined) {
|
|||||||
endTime: String(value.endTime || ''),
|
endTime: String(value.endTime || ''),
|
||||||
participantCount: Number(value.participantCount || 0),
|
participantCount: Number(value.participantCount || 0),
|
||||||
startTime: String(value.startTime || ''),
|
startTime: String(value.startTime || ''),
|
||||||
|
totalDecorationRewardGold: Number(value.totalDecorationRewardGold || 0),
|
||||||
totalDrawTimes: Number(value.totalDrawTimes || 0),
|
totalDrawTimes: Number(value.totalDrawTimes || 0),
|
||||||
|
totalGiftRewardGold: Number(value.totalGiftRewardGold || 0),
|
||||||
totalPaidGold: Number(value.totalPaidGold || 0),
|
totalPaidGold: Number(value.totalPaidGold || 0),
|
||||||
totalRewardGold: Number(value.totalRewardGold || 0),
|
totalRewardGold: Number(value.totalRewardGold || 0),
|
||||||
});
|
});
|
||||||
@ -1173,8 +1177,12 @@ watch(activePageTab, (value) => {
|
|||||||
<strong>{{ numberText(recordSummary.totalPaidGold) }}</strong>
|
<strong>{{ numberText(recordSummary.totalPaidGold) }}</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="record-summary-item">
|
<div class="record-summary-item">
|
||||||
<span>礼物发放金币总价值</span>
|
<span>礼物价值</span>
|
||||||
<strong>{{ numberText(recordSummary.totalRewardGold) }}</strong>
|
<strong>{{ numberText(recordSummary.totalGiftRewardGold) }}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="record-summary-item">
|
||||||
|
<span>装扮价值</span>
|
||||||
|
<strong>{{ numberText(recordSummary.totalDecorationRewardGold) }}</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="record-summary-item">
|
<div class="record-summary-item">
|
||||||
<span>用户总抽奖次数</span>
|
<span>用户总抽奖次数</span>
|
||||||
@ -1318,7 +1326,7 @@ watch(activePageTab, (value) => {
|
|||||||
.record-summary {
|
.record-summary {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
grid-template-columns: repeat(4, minmax(160px, 1fr));
|
grid-template-columns: repeat(5, minmax(160px, 1fr));
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -164,6 +164,7 @@ const metricColumns = new Set([
|
|||||||
'd7RetentionRate',
|
'd7RetentionRate',
|
||||||
'd30RetentionRate',
|
'd30RetentionRate',
|
||||||
'dailyActiveUser',
|
'dailyActiveUser',
|
||||||
|
'dailyRechargeUser',
|
||||||
'dealerRecharge',
|
'dealerRecharge',
|
||||||
'gamePayout',
|
'gamePayout',
|
||||||
'gamePayoutRate',
|
'gamePayoutRate',
|
||||||
@ -181,15 +182,18 @@ const metricColumns = new Set([
|
|||||||
'luckyGiftUser',
|
'luckyGiftUser',
|
||||||
'luckyGiftUserRate',
|
'luckyGiftUserRate',
|
||||||
'mifapayRecharge',
|
'mifapayRecharge',
|
||||||
|
'newDealerUserRecharge',
|
||||||
'newUserRecharge',
|
'newUserRecharge',
|
||||||
'salaryExchange',
|
'salaryExchange',
|
||||||
'salaryTransfer',
|
'salaryTransfer',
|
||||||
'totalRecharge',
|
'totalRecharge',
|
||||||
|
'userRecharge',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const integerMetricColumns = new Set([
|
const integerMetricColumns = new Set([
|
||||||
'countryNewUser',
|
'countryNewUser',
|
||||||
'dailyActiveUser',
|
'dailyActiveUser',
|
||||||
|
'dailyRechargeUser',
|
||||||
'gameUser',
|
'gameUser',
|
||||||
'luckyGiftUser',
|
'luckyGiftUser',
|
||||||
]);
|
]);
|
||||||
@ -208,8 +212,11 @@ const percentMetricColumns = new Set([
|
|||||||
const summaryMetricKeys = [
|
const summaryMetricKeys = [
|
||||||
'countryNewUser',
|
'countryNewUser',
|
||||||
'dailyActiveUser',
|
'dailyActiveUser',
|
||||||
|
'dailyRechargeUser',
|
||||||
'newUserRecharge',
|
'newUserRecharge',
|
||||||
'dealerRecharge',
|
'dealerRecharge',
|
||||||
|
'newDealerUserRecharge',
|
||||||
|
'userRecharge',
|
||||||
'mifapayRecharge',
|
'mifapayRecharge',
|
||||||
'googleRecharge',
|
'googleRecharge',
|
||||||
'salaryExchange',
|
'salaryExchange',
|
||||||
@ -235,8 +242,11 @@ const summaryMetricKeys = [
|
|||||||
const summaryMetricDefinitions = [
|
const summaryMetricDefinitions = [
|
||||||
{ key: 'countryNewUser', label: '新增' },
|
{ key: 'countryNewUser', label: '新增' },
|
||||||
{ key: 'dailyActiveUser', label: '当日活跃' },
|
{ key: 'dailyActiveUser', label: '当日活跃' },
|
||||||
{ key: 'newUserRecharge', label: '新增充值' },
|
{ key: 'dailyRechargeUser', label: '新增当日充值用户数' },
|
||||||
{ key: 'dealerRecharge', label: '币商充值' },
|
{ key: 'newUserRecharge', label: '新增用户充值' },
|
||||||
|
{ key: 'dealerRecharge', label: '币商平台充值' },
|
||||||
|
{ key: 'newDealerUserRecharge', label: '新增币商用户充值' },
|
||||||
|
{ key: 'userRecharge', label: '用户充值' },
|
||||||
{ key: 'mifapayRecharge', label: 'MifaPay 充值' },
|
{ key: 'mifapayRecharge', label: 'MifaPay 充值' },
|
||||||
{ key: 'googleRecharge', label: 'Google 充值' },
|
{ key: 'googleRecharge', label: 'Google 充值' },
|
||||||
{ key: 'salaryExchange', label: '工资兑换' },
|
{ key: 'salaryExchange', label: '工资兑换' },
|
||||||
@ -284,8 +294,11 @@ const overviewColumns: any[] = [
|
|||||||
{ align: 'right', dataIndex: 'd1RetentionRate', key: 'd1RetentionRate', title: '次日留存率', width: 140 },
|
{ align: 'right', dataIndex: 'd1RetentionRate', key: 'd1RetentionRate', title: '次日留存率', width: 140 },
|
||||||
{ align: 'right', dataIndex: 'd7RetentionRate', key: 'd7RetentionRate', title: '七日留存率', width: 140 },
|
{ align: 'right', dataIndex: 'd7RetentionRate', key: 'd7RetentionRate', title: '七日留存率', width: 140 },
|
||||||
{ align: 'right', dataIndex: 'd30RetentionRate', key: 'd30RetentionRate', title: '30 日留存率', width: 150 },
|
{ align: 'right', dataIndex: 'd30RetentionRate', key: 'd30RetentionRate', title: '30 日留存率', width: 150 },
|
||||||
{ align: 'right', dataIndex: 'newUserRecharge', key: 'newUserRecharge', title: '新增充值', width: 130 },
|
{ align: 'right', dataIndex: 'dailyRechargeUser', key: 'dailyRechargeUser', title: '新增当日充值用户数', width: 180 },
|
||||||
{ align: 'right', dataIndex: 'dealerRecharge', key: 'dealerRecharge', title: '币商充值', width: 130 },
|
{ align: 'right', dataIndex: 'newUserRecharge', key: 'newUserRecharge', title: '新增用户充值', width: 140 },
|
||||||
|
{ align: 'right', dataIndex: 'dealerRecharge', key: 'dealerRecharge', title: '币商平台充值', width: 140 },
|
||||||
|
{ align: 'right', dataIndex: 'newDealerUserRecharge', key: 'newDealerUserRecharge', title: '新增币商用户充值', width: 170 },
|
||||||
|
{ align: 'right', dataIndex: 'userRecharge', key: 'userRecharge', title: '用户充值', width: 130 },
|
||||||
{ align: 'right', dataIndex: 'mifapayRecharge', key: 'mifapayRecharge', title: 'MifaPay 充值', width: 150 },
|
{ align: 'right', dataIndex: 'mifapayRecharge', key: 'mifapayRecharge', title: 'MifaPay 充值', width: 150 },
|
||||||
{ align: 'right', dataIndex: 'googleRecharge', key: 'googleRecharge', title: 'Google 充值', width: 140 },
|
{ align: 'right', dataIndex: 'googleRecharge', key: 'googleRecharge', title: 'Google 充值', width: 140 },
|
||||||
{ align: 'right', dataIndex: 'salaryExchange', key: 'salaryExchange', title: '工资兑换', width: 130 },
|
{ align: 'right', dataIndex: 'salaryExchange', key: 'salaryExchange', title: '工资兑换', width: 130 },
|
||||||
@ -315,7 +328,9 @@ const rechargeColumns: any[] = [
|
|||||||
{ dataIndex: 'countryName', fixed: 'left' as const, key: 'countryName', title: '国家', width: 150 },
|
{ dataIndex: 'countryName', fixed: 'left' as const, key: 'countryName', title: '国家', width: 150 },
|
||||||
{ dataIndex: 'countryCode', key: 'countryCode', title: 'Code', width: 86 },
|
{ dataIndex: 'countryCode', key: 'countryCode', title: 'Code', width: 86 },
|
||||||
{ dataIndex: 'periodName', key: 'periodName', title: '周期', width: 110 },
|
{ dataIndex: 'periodName', key: 'periodName', title: '周期', width: 110 },
|
||||||
{ align: 'right', dataIndex: 'dealerRecharge', key: 'dealerRecharge', title: '币商充值', width: 140 },
|
{ align: 'right', dataIndex: 'dealerRecharge', key: 'dealerRecharge', title: '币商平台充值', width: 150 },
|
||||||
|
{ align: 'right', dataIndex: 'newDealerUserRecharge', key: 'newDealerUserRecharge', title: '新增币商用户充值', width: 170 },
|
||||||
|
{ align: 'right', dataIndex: 'userRecharge', key: 'userRecharge', title: '用户充值', width: 140 },
|
||||||
{ align: 'right', dataIndex: 'mifapayRecharge', key: 'mifapayRecharge', title: 'MifaPay 充值', width: 160 },
|
{ align: 'right', dataIndex: 'mifapayRecharge', key: 'mifapayRecharge', title: 'MifaPay 充值', width: 160 },
|
||||||
{ align: 'right', dataIndex: 'googleRecharge', key: 'googleRecharge', title: 'Google 充值', width: 150 },
|
{ align: 'right', dataIndex: 'googleRecharge', key: 'googleRecharge', title: 'Google 充值', width: 150 },
|
||||||
{ align: 'right', dataIndex: 'totalRecharge', key: 'totalRecharge', title: '总充值', width: 140 },
|
{ align: 'right', dataIndex: 'totalRecharge', key: 'totalRecharge', title: '总充值', width: 140 },
|
||||||
@ -385,7 +400,7 @@ const rechargeDetailScrollX = rechargeDetailColumns.reduce(
|
|||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
const rechargeSourceNameMap: Record<string, string> = {
|
const rechargeSourceNameMap: Record<string, string> = {
|
||||||
DEALER: '币商充值',
|
DEALER: '币商平台充值',
|
||||||
MIFAPAY: 'MifaPay 充值',
|
MIFAPAY: 'MifaPay 充值',
|
||||||
OFFICIAL: '官方充值',
|
OFFICIAL: '官方充值',
|
||||||
};
|
};
|
||||||
@ -446,6 +461,7 @@ const appDataCards = computed(() => {
|
|||||||
const arpuCards = computed(() => {
|
const arpuCards = computed(() => {
|
||||||
return [
|
return [
|
||||||
{ label: '日 ARPU', value: formatArpu(arpuDashboards.value.day) },
|
{ label: '日 ARPU', value: formatArpu(arpuDashboards.value.day) },
|
||||||
|
{ label: '充值用户日 ARPU', value: formatRechargeUserDayArpu(arpuDashboards.value.day) },
|
||||||
{ label: '3 日 ARPU', value: formatArpu(arpuDashboards.value.threeDay) },
|
{ label: '3 日 ARPU', value: formatArpu(arpuDashboards.value.threeDay) },
|
||||||
{ label: '周 ARPU', value: formatArpu(arpuDashboards.value.week) },
|
{ label: '周 ARPU', value: formatArpu(arpuDashboards.value.week) },
|
||||||
{ label: '筛选 ARPU', value: formatAmount(calcArpu(visibleSummary.value)) },
|
{ label: '筛选 ARPU', value: formatAmount(calcArpu(visibleSummary.value)) },
|
||||||
@ -455,6 +471,10 @@ const totalRechargeCard = computed(() => ({
|
|||||||
label: '总充值',
|
label: '总充值',
|
||||||
value: formatAmount(visibleSummary.value.totalRecharge),
|
value: formatAmount(visibleSummary.value.totalRecharge),
|
||||||
}));
|
}));
|
||||||
|
const userRechargeCard = computed(() => ({
|
||||||
|
label: '用户充值',
|
||||||
|
value: formatAmount(visibleSummary.value.userRecharge),
|
||||||
|
}));
|
||||||
|
|
||||||
function hasPermission(code: string) {
|
function hasPermission(code: string) {
|
||||||
const codes = accessCodes.value;
|
const codes = accessCodes.value;
|
||||||
@ -502,36 +522,18 @@ function buildParams() {
|
|||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildRecentDayParams(dayCount: number) {
|
|
||||||
const end = dayjs().tz(DEFAULT_STAT_TIMEZONE);
|
|
||||||
const start = end.subtract(dayCount - 1, 'day');
|
|
||||||
return {
|
|
||||||
periodType: 'DAY',
|
|
||||||
startDate: start.format('YYYY-MM-DD'),
|
|
||||||
endDate: end.format('YYYY-MM-DD'),
|
|
||||||
statTimezone: DEFAULT_STAT_TIMEZONE,
|
|
||||||
sysOrigin: query.sysOrigin || undefined,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadDashboard() {
|
async function loadDashboard() {
|
||||||
if (!canQuery.value) {
|
if (!canQuery.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
const [mainDashboard, dayArpuDashboard, threeDayArpuDashboard, weekArpuDashboard] =
|
const mainDashboard = await countryDashboard(buildParams());
|
||||||
await Promise.all([
|
|
||||||
countryDashboard(buildParams()),
|
|
||||||
countryDashboard(buildRecentDayParams(1)),
|
|
||||||
countryDashboard(buildRecentDayParams(3)),
|
|
||||||
countryDashboard(buildRecentDayParams(7)),
|
|
||||||
]);
|
|
||||||
dashboard.value = mainDashboard;
|
dashboard.value = mainDashboard;
|
||||||
arpuDashboards.value = {
|
arpuDashboards.value = {
|
||||||
day: dayArpuDashboard,
|
day: mainDashboard.arpuSummary?.day || null,
|
||||||
threeDay: threeDayArpuDashboard,
|
threeDay: mainDashboard.arpuSummary?.threeDay || null,
|
||||||
week: weekArpuDashboard,
|
week: mainDashboard.arpuSummary?.week || null,
|
||||||
};
|
};
|
||||||
if (activeTableTab.value === 'game') {
|
if (activeTableTab.value === 'game') {
|
||||||
await loadGameMetrics();
|
await loadGameMetrics();
|
||||||
@ -818,11 +820,24 @@ function calcArpu(summary: Record<string, any>) {
|
|||||||
return toAmount(summary.totalRecharge) / activeUser;
|
return toAmount(summary.totalRecharge) / activeUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function calcRechargeUserDayArpu(summary: Record<string, any>) {
|
||||||
|
const dailyRechargeUser = toAmount(summary.dailyRechargeUser);
|
||||||
|
if (dailyRechargeUser === 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return toAmount(summary.userRecharge) / dailyRechargeUser;
|
||||||
|
}
|
||||||
|
|
||||||
function formatArpu(result?: CountryDashboardResult | null) {
|
function formatArpu(result?: CountryDashboardResult | null) {
|
||||||
const summary = buildVisibleSummary(filterRecordsBySelectedCountries(result?.records || []));
|
const summary = buildVisibleSummary(filterRecordsBySelectedCountries(result?.records || []));
|
||||||
return formatAmount(calcArpu(summary));
|
return formatAmount(calcArpu(summary));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatRechargeUserDayArpu(result?: CountryDashboardResult | null) {
|
||||||
|
const summary = buildVisibleSummary(filterRecordsBySelectedCountries(result?.records || []));
|
||||||
|
return formatAmount(calcRechargeUserDayArpu(summary));
|
||||||
|
}
|
||||||
|
|
||||||
function emptySummary(): SummaryMetric {
|
function emptySummary(): SummaryMetric {
|
||||||
return {
|
return {
|
||||||
countryNewUser: 0,
|
countryNewUser: 0,
|
||||||
@ -836,6 +851,7 @@ function emptySummary(): SummaryMetric {
|
|||||||
d30RetentionRate: 0,
|
d30RetentionRate: 0,
|
||||||
d30RetentionUser: 0,
|
d30RetentionUser: 0,
|
||||||
dailyActiveUser: 0,
|
dailyActiveUser: 0,
|
||||||
|
dailyRechargeUser: 0,
|
||||||
dealerRecharge: 0,
|
dealerRecharge: 0,
|
||||||
googleRecharge: 0,
|
googleRecharge: 0,
|
||||||
gamePayout: 0,
|
gamePayout: 0,
|
||||||
@ -853,10 +869,12 @@ function emptySummary(): SummaryMetric {
|
|||||||
luckyGiftUser: 0,
|
luckyGiftUser: 0,
|
||||||
luckyGiftUserRate: 0,
|
luckyGiftUserRate: 0,
|
||||||
mifapayRecharge: 0,
|
mifapayRecharge: 0,
|
||||||
|
newDealerUserRecharge: 0,
|
||||||
newUserRecharge: 0,
|
newUserRecharge: 0,
|
||||||
salaryExchange: 0,
|
salaryExchange: 0,
|
||||||
salaryTransfer: 0,
|
salaryTransfer: 0,
|
||||||
totalRecharge: 0,
|
totalRecharge: 0,
|
||||||
|
userRecharge: 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1075,8 +1093,11 @@ async function renderVisualCharts() {
|
|||||||
}
|
}
|
||||||
renderUserRechargeChart(baseChartOption('新增用户与充值', list, [
|
renderUserRechargeChart(baseChartOption('新增用户与充值', list, [
|
||||||
amountSeries('新增用户', 'countryNewUser', list),
|
amountSeries('新增用户', 'countryNewUser', list),
|
||||||
amountSeries('新增充值', 'newUserRecharge', list),
|
amountSeries('当日充值用户数', 'dailyRechargeUser', list),
|
||||||
amountSeries('币商充值', 'dealerRecharge', list),
|
amountSeries('新增用户充值', 'newUserRecharge', list),
|
||||||
|
amountSeries('币商平台充值', 'dealerRecharge', list),
|
||||||
|
amountSeries('新增币商用户充值', 'newDealerUserRecharge', list),
|
||||||
|
amountSeries('用户充值', 'userRecharge', list),
|
||||||
amountSeries('MifaPay 充值', 'mifapayRecharge', list),
|
amountSeries('MifaPay 充值', 'mifapayRecharge', list),
|
||||||
amountSeries('Google 充值', 'googleRecharge', list),
|
amountSeries('Google 充值', 'googleRecharge', list),
|
||||||
amountSeries('工资兑换', 'salaryExchange', list),
|
amountSeries('工资兑换', 'salaryExchange', list),
|
||||||
@ -1242,6 +1263,10 @@ onBeforeUnmount(() => {
|
|||||||
<div class="metric-card__label">{{ totalRechargeCard.label }}</div>
|
<div class="metric-card__label">{{ totalRechargeCard.label }}</div>
|
||||||
<div class="metric-card__value">{{ totalRechargeCard.value }}</div>
|
<div class="metric-card__value">{{ totalRechargeCard.value }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="metric-card">
|
||||||
|
<div class="metric-card__label">{{ userRechargeCard.label }}</div>
|
||||||
|
<div class="metric-card__value">{{ userRechargeCard.value }}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@ -1583,7 +1608,7 @@ onBeforeUnmount(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.metric-card-grid--recharge {
|
.metric-card-grid--recharge {
|
||||||
grid-template-columns: minmax(180px, 260px);
|
grid-template-columns: repeat(2, minmax(180px, 260px));
|
||||||
}
|
}
|
||||||
|
|
||||||
.metric-card {
|
.metric-card {
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import {
|
|||||||
FormItem,
|
FormItem,
|
||||||
Input,
|
Input,
|
||||||
Modal,
|
Modal,
|
||||||
|
Switch,
|
||||||
message,
|
message,
|
||||||
} from 'antdv-next';
|
} from 'antdv-next';
|
||||||
|
|
||||||
@ -29,7 +30,19 @@ const emit = defineEmits<{
|
|||||||
|
|
||||||
const saving = ref(false);
|
const saving = ref(false);
|
||||||
|
|
||||||
|
type FeatureSwitchKey =
|
||||||
|
| 'canAddBdLeader'
|
||||||
|
| 'canBanUser'
|
||||||
|
| 'canChangeCountry'
|
||||||
|
| 'canGiftProps'
|
||||||
|
| 'canUnbanUser';
|
||||||
|
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
|
canAddBdLeader: true,
|
||||||
|
canBanUser: true,
|
||||||
|
canChangeCountry: true,
|
||||||
|
canGiftProps: true,
|
||||||
|
canUnbanUser: true,
|
||||||
contact: '',
|
contact: '',
|
||||||
id: '',
|
id: '',
|
||||||
regionId: '',
|
regionId: '',
|
||||||
@ -40,6 +53,46 @@ const form = reactive({
|
|||||||
const isUpdate = computed(() => Boolean(props.row?.id));
|
const isUpdate = computed(() => Boolean(props.row?.id));
|
||||||
const title = computed(() => (isUpdate.value ? '修改经理' : '添加经理'));
|
const title = computed(() => (isUpdate.value ? '修改经理' : '添加经理'));
|
||||||
|
|
||||||
|
function normalizeFeatureEnabled(value: unknown) {
|
||||||
|
if (value === undefined || value === null || value === '') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (typeof value === 'boolean') {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
if (typeof value === 'number') {
|
||||||
|
return value !== 0;
|
||||||
|
}
|
||||||
|
const normalized = String(value).trim().toLowerCase();
|
||||||
|
return !['0', 'false', 'n', 'no', 'off', '关', '关闭'].includes(normalized);
|
||||||
|
}
|
||||||
|
|
||||||
|
function camelToSnake(key: string) {
|
||||||
|
return key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function featurePayloadKey(key: FeatureSwitchKey) {
|
||||||
|
const bodyKey = key.replace(/^can/, '');
|
||||||
|
return bodyKey ? `${bodyKey[0]?.toLowerCase()}${bodyKey.slice(1)}` : key;
|
||||||
|
}
|
||||||
|
|
||||||
|
function rowFeatureValue(row: null | Record<string, any>, key: FeatureSwitchKey) {
|
||||||
|
const snakeKey = camelToSnake(key);
|
||||||
|
const value = Object.prototype.hasOwnProperty.call(row || {}, key)
|
||||||
|
? row?.[key]
|
||||||
|
: undefined;
|
||||||
|
if (value !== undefined) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
if (Object.prototype.hasOwnProperty.call(row || {}, snakeKey)) {
|
||||||
|
return row?.[snakeKey];
|
||||||
|
}
|
||||||
|
const payloadKey = featurePayloadKey(key);
|
||||||
|
return Object.prototype.hasOwnProperty.call(row?.features || {}, payloadKey)
|
||||||
|
? row?.features?.[payloadKey]
|
||||||
|
: row?.features?.[key];
|
||||||
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.open,
|
() => props.open,
|
||||||
(open) => {
|
(open) => {
|
||||||
@ -51,10 +104,23 @@ watch(
|
|||||||
form.sysOrigin = props.row?.sysOrigin || props.sysOrigin || 'LIKEI';
|
form.sysOrigin = props.row?.sysOrigin || props.sysOrigin || 'LIKEI';
|
||||||
form.contact = props.row?.contact || '';
|
form.contact = props.row?.contact || '';
|
||||||
form.regionId = String(props.row?.regionId || '');
|
form.regionId = String(props.row?.regionId || '');
|
||||||
|
form.canAddBdLeader = normalizeFeatureEnabled(rowFeatureValue(props.row, 'canAddBdLeader'));
|
||||||
|
form.canGiftProps = normalizeFeatureEnabled(rowFeatureValue(props.row, 'canGiftProps'));
|
||||||
|
form.canBanUser = normalizeFeatureEnabled(rowFeatureValue(props.row, 'canBanUser'));
|
||||||
|
form.canUnbanUser = normalizeFeatureEnabled(rowFeatureValue(props.row, 'canUnbanUser'));
|
||||||
|
form.canChangeCountry = normalizeFeatureEnabled(rowFeatureValue(props.row, 'canChangeCountry'));
|
||||||
},
|
},
|
||||||
{ immediate: true },
|
{ immediate: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function setFeature(key: FeatureSwitchKey, checked: unknown) {
|
||||||
|
form[key] = normalizeFeatureEnabled(checked);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleFeature(key: FeatureSwitchKey) {
|
||||||
|
form[key] = !form[key];
|
||||||
|
}
|
||||||
|
|
||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
if (!isUpdate.value && !String(form.userId || '').trim()) {
|
if (!isUpdate.value && !String(form.userId || '').trim()) {
|
||||||
message.warning('请输入经理用户ID');
|
message.warning('请输入经理用户ID');
|
||||||
@ -67,6 +133,11 @@ async function handleSubmit() {
|
|||||||
saving.value = true;
|
saving.value = true;
|
||||||
try {
|
try {
|
||||||
const payload = {
|
const payload = {
|
||||||
|
canAddBdLeader: form.canAddBdLeader,
|
||||||
|
canBanUser: form.canBanUser,
|
||||||
|
canChangeCountry: form.canChangeCountry,
|
||||||
|
canGiftProps: form.canGiftProps,
|
||||||
|
canUnbanUser: form.canUnbanUser,
|
||||||
contact: form.contact.trim(),
|
contact: form.contact.trim(),
|
||||||
id: form.id || undefined,
|
id: form.id || undefined,
|
||||||
regionId: form.regionId,
|
regionId: form.regionId,
|
||||||
@ -114,6 +185,123 @@ async function handleSubmit() {
|
|||||||
<FormItem label="联系方式">
|
<FormItem label="联系方式">
|
||||||
<Input v-model:value="form.contact" placeholder="联系方式" />
|
<Input v-model:value="form.contact" placeholder="联系方式" />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
<FormItem label="功能开关">
|
||||||
|
<div class="feature-switches">
|
||||||
|
<div
|
||||||
|
class="feature-switch-row"
|
||||||
|
role="switch"
|
||||||
|
tabindex="0"
|
||||||
|
:aria-checked="form.canAddBdLeader"
|
||||||
|
@click="toggleFeature('canAddBdLeader')"
|
||||||
|
@keydown.enter.prevent="toggleFeature('canAddBdLeader')"
|
||||||
|
@keydown.space.prevent="toggleFeature('canAddBdLeader')"
|
||||||
|
>
|
||||||
|
<span>加BD Leader</span>
|
||||||
|
<span class="feature-switch-control" @click.stop>
|
||||||
|
<Switch
|
||||||
|
:checked="form.canAddBdLeader"
|
||||||
|
@change="(checked) => setFeature('canAddBdLeader', checked)"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="feature-switch-row"
|
||||||
|
role="switch"
|
||||||
|
tabindex="0"
|
||||||
|
:aria-checked="form.canGiftProps"
|
||||||
|
@click="toggleFeature('canGiftProps')"
|
||||||
|
@keydown.enter.prevent="toggleFeature('canGiftProps')"
|
||||||
|
@keydown.space.prevent="toggleFeature('canGiftProps')"
|
||||||
|
>
|
||||||
|
<span>赠送道具</span>
|
||||||
|
<span class="feature-switch-control" @click.stop>
|
||||||
|
<Switch
|
||||||
|
:checked="form.canGiftProps"
|
||||||
|
@change="(checked) => setFeature('canGiftProps', checked)"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="feature-switch-row"
|
||||||
|
role="switch"
|
||||||
|
tabindex="0"
|
||||||
|
:aria-checked="form.canBanUser"
|
||||||
|
@click="toggleFeature('canBanUser')"
|
||||||
|
@keydown.enter.prevent="toggleFeature('canBanUser')"
|
||||||
|
@keydown.space.prevent="toggleFeature('canBanUser')"
|
||||||
|
>
|
||||||
|
<span>封禁用户</span>
|
||||||
|
<span class="feature-switch-control" @click.stop>
|
||||||
|
<Switch
|
||||||
|
:checked="form.canBanUser"
|
||||||
|
@change="(checked) => setFeature('canBanUser', checked)"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="feature-switch-row"
|
||||||
|
role="switch"
|
||||||
|
tabindex="0"
|
||||||
|
:aria-checked="form.canUnbanUser"
|
||||||
|
@click="toggleFeature('canUnbanUser')"
|
||||||
|
@keydown.enter.prevent="toggleFeature('canUnbanUser')"
|
||||||
|
@keydown.space.prevent="toggleFeature('canUnbanUser')"
|
||||||
|
>
|
||||||
|
<span>解封用户</span>
|
||||||
|
<span class="feature-switch-control" @click.stop>
|
||||||
|
<Switch
|
||||||
|
:checked="form.canUnbanUser"
|
||||||
|
@change="(checked) => setFeature('canUnbanUser', checked)"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="feature-switch-row"
|
||||||
|
role="switch"
|
||||||
|
tabindex="0"
|
||||||
|
:aria-checked="form.canChangeCountry"
|
||||||
|
@click="toggleFeature('canChangeCountry')"
|
||||||
|
@keydown.enter.prevent="toggleFeature('canChangeCountry')"
|
||||||
|
@keydown.space.prevent="toggleFeature('canChangeCountry')"
|
||||||
|
>
|
||||||
|
<span>改国家</span>
|
||||||
|
<span class="feature-switch-control" @click.stop>
|
||||||
|
<Switch
|
||||||
|
:checked="form.canChangeCountry"
|
||||||
|
@change="(checked) => setFeature('canChangeCountry', checked)"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</FormItem>
|
||||||
</Form>
|
</Form>
|
||||||
</Modal>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.feature-switches {
|
||||||
|
display: grid;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-switch-row {
|
||||||
|
align-items: center;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 10px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-switch-row:focus-visible {
|
||||||
|
border-color: #1677ff;
|
||||||
|
outline: 2px solid rgb(22 119 255 / 20%);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-switch-control {
|
||||||
|
align-items: center;
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@ -23,6 +23,7 @@ import {
|
|||||||
Pagination,
|
Pagination,
|
||||||
Space,
|
Space,
|
||||||
Table,
|
Table,
|
||||||
|
Tag,
|
||||||
message,
|
message,
|
||||||
} from 'antdv-next';
|
} from 'antdv-next';
|
||||||
|
|
||||||
@ -55,12 +56,21 @@ const columns = [
|
|||||||
{ dataIndex: 'userProfile', key: 'userProfile', title: '经理', width: 240 },
|
{ dataIndex: 'userProfile', key: 'userProfile', title: '经理', width: 240 },
|
||||||
{ dataIndex: 'regionName', key: 'regionName', title: '区域', width: 140 },
|
{ dataIndex: 'regionName', key: 'regionName', title: '区域', width: 140 },
|
||||||
{ dataIndex: 'contact', key: 'contact', title: '联系方式', width: 180 },
|
{ dataIndex: 'contact', key: 'contact', title: '联系方式', width: 180 },
|
||||||
|
{ dataIndex: 'features', key: 'features', title: '功能开关', width: 340 },
|
||||||
{ dataIndex: 'createUserName', key: 'createUserName', title: '创建人', width: 140 },
|
{ dataIndex: 'createUserName', key: 'createUserName', title: '创建人', width: 140 },
|
||||||
{ dataIndex: 'updateUserName', key: 'updateUserName', title: '修改人', width: 140 },
|
{ dataIndex: 'updateUserName', key: 'updateUserName', title: '修改人', width: 140 },
|
||||||
{ dataIndex: 'createTime', key: 'createTime', title: '创建时间', width: 180 },
|
{ dataIndex: 'createTime', key: 'createTime', title: '创建时间', width: 180 },
|
||||||
{ dataIndex: 'actions', key: 'actions', title: '操作', width: 120 },
|
{ dataIndex: 'actions', key: 'actions', title: '操作', width: 120 },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const managerFeatureSwitches = [
|
||||||
|
{ key: 'canAddBdLeader', label: '加BD Leader' },
|
||||||
|
{ key: 'canGiftProps', label: '赠送道具' },
|
||||||
|
{ key: 'canBanUser', label: '封禁用户' },
|
||||||
|
{ key: 'canUnbanUser', label: '解封用户' },
|
||||||
|
{ key: 'canChangeCountry', label: '改国家' },
|
||||||
|
];
|
||||||
|
|
||||||
function hasPermission(code: string) {
|
function hasPermission(code: string) {
|
||||||
const codes = accessStore.accessCodes || [];
|
const codes = accessStore.accessCodes || [];
|
||||||
return codes.length === 0 || codes.includes(code);
|
return codes.length === 0 || codes.includes(code);
|
||||||
@ -75,6 +85,46 @@ function getUserShortId(profile?: Record<string, any> | null) {
|
|||||||
return profile?.actualAccount || profile?.account || profile?.id || '-';
|
return profile?.actualAccount || profile?.account || profile?.id || '-';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeFeatureEnabled(value: unknown) {
|
||||||
|
if (value === undefined || value === null || value === '') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (typeof value === 'boolean') {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
if (typeof value === 'number') {
|
||||||
|
return value !== 0;
|
||||||
|
}
|
||||||
|
const normalized = String(value).trim().toLowerCase();
|
||||||
|
return !['0', 'false', 'n', 'no', 'off', '关', '关闭'].includes(normalized);
|
||||||
|
}
|
||||||
|
|
||||||
|
function camelToSnake(key: string) {
|
||||||
|
return key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function featurePayloadKey(key: string) {
|
||||||
|
const bodyKey = key.replace(/^can/, '');
|
||||||
|
return bodyKey ? `${bodyKey[0]?.toLowerCase()}${bodyKey.slice(1)}` : key;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isFeatureEnabled(record: Record<string, any>, key: string) {
|
||||||
|
const snakeKey = camelToSnake(key);
|
||||||
|
let value = Object.prototype.hasOwnProperty.call(record || {}, key)
|
||||||
|
? record?.[key]
|
||||||
|
: undefined;
|
||||||
|
if (value === undefined && Object.prototype.hasOwnProperty.call(record || {}, snakeKey)) {
|
||||||
|
value = record?.[snakeKey];
|
||||||
|
}
|
||||||
|
if (value === undefined && record?.features) {
|
||||||
|
const payloadKey = featurePayloadKey(key);
|
||||||
|
value = Object.prototype.hasOwnProperty.call(record.features, payloadKey)
|
||||||
|
? record.features[payloadKey]
|
||||||
|
: record.features[key];
|
||||||
|
}
|
||||||
|
return normalizeFeatureEnabled(value);
|
||||||
|
}
|
||||||
|
|
||||||
async function loadData(reset = false) {
|
async function loadData(reset = false) {
|
||||||
if (reset) {
|
if (reset) {
|
||||||
query.cursor = 1;
|
query.cursor = 1;
|
||||||
@ -186,6 +236,17 @@ loadData(true);
|
|||||||
<template v-else-if="column.key === 'contact'">
|
<template v-else-if="column.key === 'contact'">
|
||||||
{{ canEdit ? record.contact || '-' : '-' }}
|
{{ canEdit ? record.contact || '-' : '-' }}
|
||||||
</template>
|
</template>
|
||||||
|
<template v-else-if="column.key === 'features'">
|
||||||
|
<Space wrap :size="[4, 4]">
|
||||||
|
<Tag
|
||||||
|
v-for="item in managerFeatureSwitches"
|
||||||
|
:key="item.key"
|
||||||
|
:color="isFeatureEnabled(record, item.key) ? 'green' : 'red'"
|
||||||
|
>
|
||||||
|
{{ item.label }}:{{ isFeatureEnabled(record, item.key) ? '开' : '关' }}
|
||||||
|
</Tag>
|
||||||
|
</Space>
|
||||||
|
</template>
|
||||||
<template v-else-if="column.key === 'createTime'">
|
<template v-else-if="column.key === 'createTime'">
|
||||||
{{ formatDate(record.createTime) }}
|
{{ formatDate(record.createTime) }}
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -5,8 +5,7 @@ const DEV_API_INTERNAL_PREFIX = '/__dev_api_internal__';
|
|||||||
const DEV_API_PROD_PREFIX = '/__dev_api_prod__';
|
const DEV_API_PROD_PREFIX = '/__dev_api_prod__';
|
||||||
const DEV_INTERNAL_CONSOLE_TARGET = 'http://127.0.0.1:2700';
|
const DEV_INTERNAL_CONSOLE_TARGET = 'http://127.0.0.1:2700';
|
||||||
const DEV_INTERNAL_GOLANG_TARGET = 'http://127.0.0.1:2900';
|
const DEV_INTERNAL_GOLANG_TARGET = 'http://127.0.0.1:2900';
|
||||||
const DEV_PROD_GOLANG_TARGET = 'https://jvapi.haiyihy.com';
|
const DEV_PROD_TARGET = 'https://yumiadmin-acc.global-interaction.com';
|
||||||
const DEV_PROD_TARGET = 'https://yumi-admin.haiyihy.com';
|
|
||||||
|
|
||||||
function getBundledAssetFileName(assetInfo: {
|
function getBundledAssetFileName(assetInfo: {
|
||||||
name?: string;
|
name?: string;
|
||||||
@ -79,8 +78,11 @@ export default defineConfig(async () => {
|
|||||||
[`${DEV_API_PROD_PREFIX}/go`]: {
|
[`${DEV_API_PROD_PREFIX}/go`]: {
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
rewrite: (path) =>
|
rewrite: (path) =>
|
||||||
path.replace(createPrefixRegExp(DEV_API_PROD_PREFIX), ''),
|
path.replace(
|
||||||
target: DEV_PROD_GOLANG_TARGET,
|
createPrefixRegExp(`${DEV_API_PROD_PREFIX}/go`),
|
||||||
|
`${DEV_CONSOLE_PREFIX}/go`,
|
||||||
|
),
|
||||||
|
target: DEV_PROD_TARGET,
|
||||||
ws: true,
|
ws: true,
|
||||||
},
|
},
|
||||||
[DEV_API_PROD_PREFIX]: {
|
[DEV_API_PROD_PREFIX]: {
|
||||||
|
|||||||
6
pnpm-lock.yaml
generated
6
pnpm-lock.yaml
generated
@ -480,6 +480,9 @@ importers:
|
|||||||
|
|
||||||
apps:
|
apps:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@types/json-bigint':
|
||||||
|
specifier: 'catalog:'
|
||||||
|
version: 1.0.4
|
||||||
'@vben/access':
|
'@vben/access':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../packages/effects/access
|
version: link:../packages/effects/access
|
||||||
@ -534,6 +537,9 @@ importers:
|
|||||||
dayjs:
|
dayjs:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 1.11.20
|
version: 1.11.20
|
||||||
|
json-bigint:
|
||||||
|
specifier: 'catalog:'
|
||||||
|
version: 1.0.0
|
||||||
pinia:
|
pinia:
|
||||||
specifier: ^3.0.4
|
specifier: ^3.0.4
|
||||||
version: 3.0.4(typescript@5.9.3)(vue@3.5.30(typescript@5.9.3))
|
version: 3.0.4(typescript@5.9.3)(vue@3.5.30(typescript@5.9.3))
|
||||||
|
|||||||
@ -650,6 +650,71 @@ SET
|
|||||||
router = 'baishun-config'
|
router = 'baishun-config'
|
||||||
WHERE alias = 'OperateBaishunGameManage';
|
WHERE alias = 'OperateBaishunGameManage';
|
||||||
|
|
||||||
|
INSERT INTO sys_menu (
|
||||||
|
id,
|
||||||
|
parent_id,
|
||||||
|
menu_name,
|
||||||
|
path,
|
||||||
|
menu_type,
|
||||||
|
icon,
|
||||||
|
create_user,
|
||||||
|
update_user,
|
||||||
|
sort,
|
||||||
|
status,
|
||||||
|
router,
|
||||||
|
alias
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
next_id,
|
||||||
|
(
|
||||||
|
SELECT game_manager.id
|
||||||
|
FROM (
|
||||||
|
SELECT id
|
||||||
|
FROM sys_menu
|
||||||
|
WHERE alias = 'OperateGameManager'
|
||||||
|
LIMIT 1
|
||||||
|
) AS game_manager
|
||||||
|
),
|
||||||
|
'热游配置',
|
||||||
|
'operate/hotgame-config',
|
||||||
|
2,
|
||||||
|
'form',
|
||||||
|
'0',
|
||||||
|
'0',
|
||||||
|
4,
|
||||||
|
0,
|
||||||
|
'hotgame-config',
|
||||||
|
'OperateHotgameConfig'
|
||||||
|
FROM (
|
||||||
|
SELECT COALESCE(MAX(id), 1700000000) + 1 AS next_id
|
||||||
|
FROM sys_menu
|
||||||
|
) AS menu_id
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM sys_menu WHERE alias = 'OperateHotgameConfig'
|
||||||
|
);
|
||||||
|
|
||||||
|
UPDATE sys_menu
|
||||||
|
SET
|
||||||
|
parent_id = (
|
||||||
|
SELECT game_manager.id
|
||||||
|
FROM (
|
||||||
|
SELECT id
|
||||||
|
FROM sys_menu
|
||||||
|
WHERE alias = 'OperateGameManager'
|
||||||
|
LIMIT 1
|
||||||
|
) AS game_manager
|
||||||
|
),
|
||||||
|
menu_name = '热游配置',
|
||||||
|
path = 'operate/hotgame-config',
|
||||||
|
menu_type = 2,
|
||||||
|
icon = 'form',
|
||||||
|
create_user = '0',
|
||||||
|
update_user = '0',
|
||||||
|
sort = 4,
|
||||||
|
status = 0,
|
||||||
|
router = 'hotgame-config'
|
||||||
|
WHERE alias = 'OperateHotgameConfig';
|
||||||
|
|
||||||
INSERT INTO sys_role_menu (role_id, menu_id)
|
INSERT INTO sys_role_menu (role_id, menu_id)
|
||||||
SELECT DISTINCT
|
SELECT DISTINCT
|
||||||
parent_role.role_id,
|
parent_role.role_id,
|
||||||
@ -669,7 +734,8 @@ JOIN sys_menu AS target_menu
|
|||||||
'ResidentDailyTaskConfig',
|
'ResidentDailyTaskConfig',
|
||||||
'OperateGameManager',
|
'OperateGameManager',
|
||||||
'GameConfig',
|
'GameConfig',
|
||||||
'OperateBaishunGameManage'
|
'OperateBaishunGameManage',
|
||||||
|
'OperateHotgameConfig'
|
||||||
)
|
)
|
||||||
LEFT JOIN sys_role_menu AS existing_role_menu
|
LEFT JOIN sys_role_menu AS existing_role_menu
|
||||||
ON existing_role_menu.role_id = parent_role.role_id
|
ON existing_role_menu.role_id = parent_role.role_id
|
||||||
@ -677,6 +743,21 @@ LEFT JOIN sys_role_menu AS existing_role_menu
|
|||||||
WHERE parent_role.menu_id = 1700000057
|
WHERE parent_role.menu_id = 1700000057
|
||||||
AND existing_role_menu.id IS NULL;
|
AND existing_role_menu.id IS NULL;
|
||||||
|
|
||||||
|
INSERT INTO sys_role_menu (role_id, menu_id)
|
||||||
|
SELECT DISTINCT
|
||||||
|
source_role.role_id,
|
||||||
|
target_menu.id
|
||||||
|
FROM sys_role_menu AS source_role
|
||||||
|
JOIN sys_menu AS source_menu
|
||||||
|
ON source_menu.id = source_role.menu_id
|
||||||
|
JOIN sys_menu AS target_menu
|
||||||
|
ON target_menu.alias = 'OperateHotgameConfig'
|
||||||
|
LEFT JOIN sys_role_menu AS existing_role_menu
|
||||||
|
ON existing_role_menu.role_id = source_role.role_id
|
||||||
|
AND existing_role_menu.menu_id = target_menu.id
|
||||||
|
WHERE source_menu.alias IN ('OperateGameManager', 'GameConfig', 'OperateBaishunGameManage', 'OperateLingxianConfig')
|
||||||
|
AND existing_role_menu.id IS NULL;
|
||||||
|
|
||||||
INSERT INTO sys_role_menu (role_id, menu_id)
|
INSERT INTO sys_role_menu (role_id, menu_id)
|
||||||
SELECT DISTINCT
|
SELECT DISTINCT
|
||||||
parent_role.role_id,
|
parent_role.role_id,
|
||||||
@ -831,6 +912,10 @@ UPDATE sys_menu
|
|||||||
SET menu_name = CONVERT(0xE799BEE9A1BAE9858DE7BDAE USING utf8mb4)
|
SET menu_name = CONVERT(0xE799BEE9A1BAE9858DE7BDAE USING utf8mb4)
|
||||||
WHERE alias = 'OperateBaishunGameManage';
|
WHERE alias = 'OperateBaishunGameManage';
|
||||||
|
|
||||||
|
UPDATE sys_menu
|
||||||
|
SET menu_name = CONVERT(0xE783ADE6B8B8E9858DE7BDAE USING utf8mb4)
|
||||||
|
WHERE alias = 'OperateHotgameConfig';
|
||||||
|
|
||||||
SET @resident_activity_id := (
|
SET @resident_activity_id := (
|
||||||
SELECT id FROM sys_menu WHERE alias = 'ResidentActivityManager' LIMIT 1
|
SELECT id FROM sys_menu WHERE alias = 'ResidentActivityManager' LIMIT 1
|
||||||
);
|
);
|
||||||
|
|||||||
81
scripts/sql/sync_hotgame_menu.sql
Normal file
81
scripts/sql/sync_hotgame_menu.sql
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
SET NAMES utf8mb4;
|
||||||
|
|
||||||
|
INSERT INTO sys_menu (
|
||||||
|
id,
|
||||||
|
parent_id,
|
||||||
|
menu_name,
|
||||||
|
path,
|
||||||
|
menu_type,
|
||||||
|
icon,
|
||||||
|
create_user,
|
||||||
|
update_user,
|
||||||
|
sort,
|
||||||
|
status,
|
||||||
|
router,
|
||||||
|
alias
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
next_id,
|
||||||
|
(
|
||||||
|
SELECT game_manager.id
|
||||||
|
FROM (
|
||||||
|
SELECT id
|
||||||
|
FROM sys_menu
|
||||||
|
WHERE alias = 'OperateGameManager'
|
||||||
|
LIMIT 1
|
||||||
|
) AS game_manager
|
||||||
|
),
|
||||||
|
'热游配置',
|
||||||
|
'operate/hotgame-config',
|
||||||
|
2,
|
||||||
|
'form',
|
||||||
|
'0',
|
||||||
|
'0',
|
||||||
|
4,
|
||||||
|
0,
|
||||||
|
'hotgame-config',
|
||||||
|
'OperateHotgameConfig'
|
||||||
|
FROM (
|
||||||
|
SELECT COALESCE(MAX(id), 1700000000) + 1 AS next_id
|
||||||
|
FROM sys_menu
|
||||||
|
) AS menu_id
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM sys_menu WHERE alias = 'OperateHotgameConfig'
|
||||||
|
);
|
||||||
|
|
||||||
|
UPDATE sys_menu
|
||||||
|
SET
|
||||||
|
parent_id = (
|
||||||
|
SELECT game_manager.id
|
||||||
|
FROM (
|
||||||
|
SELECT id
|
||||||
|
FROM sys_menu
|
||||||
|
WHERE alias = 'OperateGameManager'
|
||||||
|
LIMIT 1
|
||||||
|
) AS game_manager
|
||||||
|
),
|
||||||
|
menu_name = '热游配置',
|
||||||
|
path = 'operate/hotgame-config',
|
||||||
|
menu_type = 2,
|
||||||
|
icon = 'form',
|
||||||
|
create_user = '0',
|
||||||
|
update_user = '0',
|
||||||
|
sort = 4,
|
||||||
|
status = 0,
|
||||||
|
router = 'hotgame-config'
|
||||||
|
WHERE alias = 'OperateHotgameConfig';
|
||||||
|
|
||||||
|
INSERT INTO sys_role_menu (role_id, menu_id)
|
||||||
|
SELECT DISTINCT
|
||||||
|
source_role.role_id,
|
||||||
|
target_menu.id
|
||||||
|
FROM sys_role_menu AS source_role
|
||||||
|
JOIN sys_menu AS source_menu
|
||||||
|
ON source_menu.id = source_role.menu_id
|
||||||
|
JOIN sys_menu AS target_menu
|
||||||
|
ON target_menu.alias = 'OperateHotgameConfig'
|
||||||
|
LEFT JOIN sys_role_menu AS existing_role_menu
|
||||||
|
ON existing_role_menu.role_id = source_role.role_id
|
||||||
|
AND existing_role_menu.menu_id = target_menu.id
|
||||||
|
WHERE source_menu.alias IN ('OperateGameManager', 'GameConfig', 'OperateBaishunGameManage', 'OperateLingxianConfig')
|
||||||
|
AND existing_role_menu.id IS NULL;
|
||||||
Loading…
x
Reference in New Issue
Block a user