This commit is contained in:
hy 2026-04-16 18:44:08 +08:00
parent 1e2aead70f
commit a5725f23d4
5 changed files with 434 additions and 262 deletions

View File

@ -0,0 +1,247 @@
import type { RouteRecordRaw } from 'vue-router';
const routes: RouteRecordRaw[] = [
{
meta: {
icon: 'lucide:users',
order: 49.5,
title: 'App用户管理',
},
name: 'AppUserManager',
path: '/app-user/manager',
redirect: '/app-user/manager/gold-balance',
children: [
{
name: 'GoldBalance',
path: 'gold-balance',
alias: '/operate/manager/gold-balance',
component: () => import('#/views/operate/gold-balance.vue'),
meta: { title: '金币余额' },
},
{
name: 'AutoSalaryPayRecord',
path: 'auto-salary-pay-record',
alias: '/auto-salary-pay-record',
component: () => import('#/views/operate/auto-salary-pay-record.vue'),
meta: { title: '自动发放工资凭据' },
},
{
name: 'UserBankWithdrawMoneyApply',
path: 'withdraw-money-apply-page',
alias: '/withdraw-money-apply-page',
component: () =>
import('#/views/operate/user-bank-withdraw-money-apply.vue'),
meta: { title: '用户银行卡提现现金申请' },
},
{
name: 'UserBankWithdrawDiamondApply',
path: 'withdraw-diamond-apply-page',
alias: '/withdraw-diamond-apply-page',
component: () =>
import('#/views/operate/user-bank-withdraw-diamond-apply.vue'),
meta: { title: '用户银行卡提现钻石申请' },
},
{
name: 'UserBankWithdrawGoldApply',
path: 'user-bank-withdraw-gold-apply',
alias: '/user-bank-withdraw-gold-apply',
component: () =>
import('#/views/operate/user-bank-withdraw-gold-apply.vue'),
meta: { title: '用户银行金币兑换申请' },
},
{
name: 'UserBankDiamondWithdrawGoldApply',
path: 'user-bank-diamond-withdraw-gold-apply',
alias: '/user-bank-diamond-withdraw-gold-apply',
component: () =>
import('#/views/operate/user-bank-diamond-withdraw-gold-apply.vue'),
meta: { title: '用户银行卡钻石兑换金币申请' },
},
{
name: 'UserBankBalance',
path: 'user-bank-balance',
alias: '/user-bank-balance',
component: () => import('#/views/operate/user-bank-balance.vue'),
meta: { title: '用户银行账户' },
},
{
name: 'UserBankRunningWater',
path: 'user-running-water',
alias: '/user-running-water',
component: () => import('#/views/operate/user-bank-running-water.vue'),
meta: { title: '用户银行流水' },
},
{
name: 'InviteUserLog',
path: 'invite-user-log',
alias: '/operate/manager/invite-user-log',
component: () => import('#/views/operate/invite-user-log.vue'),
meta: { title: '邀请用户日志' },
},
{
name: 'SysUserFriendshipCard',
path: 'sys/friendship-card',
alias: '/sys/friendship-card',
component: () => import('#/views/operate/friendship-card.vue'),
meta: { title: '用户友谊卡配置' },
},
{
name: 'LotfunInviteUserRule',
path: 'sys/invite-user-rule',
alias: '/sys/invite-user-rule',
component: () => import('#/views/operate/invite-user-rule.vue'),
meta: { title: '邀请用户配置' },
},
{
name: 'AswatUserAdvertising',
path: 'sys/user-advertising',
alias: '/sys/user-advertising',
component: () => import('#/views/operate/user-advertising.vue'),
meta: { title: '用户观看广告配置' },
},
{
name: 'OperateUserFreight',
path: 'user_freight',
alias: '/operate/manager/user_freight',
component: () => import('#/views/operate/user-freight.vue'),
meta: { title: '货运代理' },
},
{
name: 'InviteRewardRecord',
path: 'user/invite-reward',
alias: '/operate/manager/user/invite-reward',
component: () => import('#/views/operate/invite-reward.vue'),
meta: { title: '邀请用户奖励记录' },
},
{
name: 'OperateImAccountManager',
path: 'im/account/manager',
alias: '/operate/manager/im/account/manager',
component: () => import('#/views/operate/im-account-manager.vue'),
meta: { title: 'IM账号管理' },
},
{
name: 'CheckInLog',
path: 'check/in/log',
alias: '/operate/manager/check/in/log',
component: () => import('#/views/operate/check-in-log.vue'),
meta: { title: '用户签到' },
},
{
name: 'OperateUserManage',
path: 'user',
alias: '/operate/manager/user',
component: () => import('#/views/operate/user-manage.vue'),
meta: { title: '用户列表' },
},
{
name: 'OperateUserDevice',
path: 'user/user-device',
alias: '/operate/manager/user/user-device',
component: () => import('#/views/operate/user-device-list.vue'),
meta: { title: '用户最新设备列表' },
},
{
name: 'OperateUserFreightInfoList',
path: 'user_freight_info_list',
alias: '/operate/manager/user_freight_info_list',
component: () => import('#/views/operate/user-freight-info-list.vue'),
meta: { title: '货运代理流水' },
},
{
name: 'OperateQualityUsersList',
path: 'quality_users_list',
alias: '/operate/manager/quality_users_list',
component: () => import('#/views/operate/quality-users-list.vue'),
meta: { title: '高产用户列表' },
},
{
name: 'OperateGoldRunningWater',
path: 'running/water',
alias: '/operate/manager/running/water',
component: () => import('#/views/operate/gold-running-water.vue'),
meta: { title: '金币收支记录' },
},
{
name: 'OperateInsideOperationGold',
path: 'inside-operation-gold',
alias: '/operate/manager/inside-operation-gold',
component: () => import('#/views/operate/inside-operation-gold.vue'),
meta: { title: '内部操作金币' },
},
{
name: 'GameCouponRunningWater',
path: 'game/coupon/running/water',
alias: '/game/coupon/running/water',
component: () => import('#/views/operate/game-coupon-running-water.vue'),
meta: { title: '游戏券收支记录' },
},
{
name: 'OperateIntegralStream',
path: 'integral/stream',
alias: '/operate/manager/integral/stream',
component: () => import('#/views/operate/integral-stream.vue'),
meta: { title: '积分收支记录' },
},
{
name: 'OperateBadgeConfig',
path: 'badge',
alias: '/operate/manager/badge',
component: () => import('#/views/operate/badge-config.vue'),
meta: { title: '徽章配置管理' },
},
{
name: 'OperateGiftHistory',
path: 'gift/history',
alias: '/operate/manager/gift/history',
component: () => import('#/views/operate/gift-history.vue'),
meta: { title: '礼物赠送记录' },
},
{
name: 'RunningWaterUserProps',
path: 'running/water/user/props',
alias: '/running/water/user/props',
component: () => import('#/views/operate/user-props-running-water.vue'),
meta: { title: '用户道具流水' },
},
{
name: 'UserDiamondBalance',
path: 'user/diamond-balance',
alias: '/user/diamond-balance',
component: () => import('#/views/operate/user-diamond-balance.vue'),
meta: { title: '用户钻石余额列表' },
},
{
name: 'UserDiamondRunWater',
path: 'user/diamond-run-water',
alias: '/user/diamond-run-water',
component: () =>
import('#/views/operate/user-diamond-running-water.vue'),
meta: { title: '用户钻石流水列表' },
},
{
name: 'UserActivityReceive',
path: 'user/user-activity-receive',
alias: '/user/user-activity-receive',
component: () => import('#/views/operate/user-activity-receive.vue'),
meta: { title: '用户活动领奖记录' },
},
{
name: 'BeautifulNumberApply',
path: 'user/beautiful-number-apply',
alias: '/user/beautiful-number-apply',
component: () => import('#/views/operate/beautiful-number-apply.vue'),
meta: { title: '用户申请靓号记录' },
},
{
name: 'OperateBeautifulNumber',
path: 'beautiful-number',
alias: '/operate/manager/beautiful-number',
component: () => import('#/views/operate/beautiful-number.vue'),
meta: { title: '靓号池管理' },
},
],
},
];
export default routes;

View File

@ -9,7 +9,7 @@ const routes: RouteRecordRaw[] = [
},
name: 'OperateManager',
path: '/operate/manager',
redirect: '/operate/manager/gold-balance',
redirect: '/operate/manager/agentActivity',
children: [
{
name: 'AgentActivity',
@ -17,58 +17,6 @@ const routes: RouteRecordRaw[] = [
component: () => import('#/views/operate/agent-activity.vue'),
meta: { title: '代理活动' },
},
{
name: 'GoldBalance',
path: 'gold-balance',
component: () => import('#/views/operate/gold-balance.vue'),
meta: { title: '金币余额' },
},
{
name: 'AutoSalaryPayRecord',
path: '/auto-salary-pay-record',
component: () => import('#/views/operate/auto-salary-pay-record.vue'),
meta: { title: '自动发放工资凭据' },
},
{
name: 'UserBankWithdrawMoneyApply',
path: '/withdraw-money-apply-page',
component: () =>
import('#/views/operate/user-bank-withdraw-money-apply.vue'),
meta: { title: '用户银行卡提现现金申请' },
},
{
name: 'UserBankWithdrawDiamondApply',
path: '/withdraw-diamond-apply-page',
component: () =>
import('#/views/operate/user-bank-withdraw-diamond-apply.vue'),
meta: { title: '用户银行卡提现钻石申请' },
},
{
name: 'UserBankWithdrawGoldApply',
path: '/user-bank-withdraw-gold-apply',
component: () =>
import('#/views/operate/user-bank-withdraw-gold-apply.vue'),
meta: { title: '用户银行金币兑换申请' },
},
{
name: 'UserBankDiamondWithdrawGoldApply',
path: '/user-bank-diamond-withdraw-gold-apply',
component: () =>
import('#/views/operate/user-bank-diamond-withdraw-gold-apply.vue'),
meta: { title: '用户银行卡钻石兑换金币申请' },
},
{
name: 'UserBankBalance',
path: '/user-bank-balance',
component: () => import('#/views/operate/user-bank-balance.vue'),
meta: { title: '用户银行账户' },
},
{
name: 'UserBankRunningWater',
path: '/user-running-water',
component: () => import('#/views/operate/user-bank-running-water.vue'),
meta: { title: '用户银行流水' },
},
{
name: 'CustomerService',
path: '/customer-service',
@ -87,12 +35,6 @@ const routes: RouteRecordRaw[] = [
component: () => import('#/views/operate/game-pk.vue'),
meta: { title: 'PK记录' },
},
{
name: 'InviteUserLog',
path: 'invite-user-log',
component: () => import('#/views/operate/invite-user-log.vue'),
meta: { title: '邀请用户日志' },
},
{
name: 'OperateActivityConfig',
path: 'activity/cnf',
@ -105,24 +47,6 @@ const routes: RouteRecordRaw[] = [
component: () => import('#/views/operate/activity-template.vue'),
meta: { title: '活动模版' },
},
{
name: 'SysUserFriendshipCard',
path: '/sys/friendship-card',
component: () => import('#/views/operate/friendship-card.vue'),
meta: { title: '用户友谊卡配置' },
},
{
name: 'LotfunInviteUserRule',
path: '/sys/invite-user-rule',
component: () => import('#/views/operate/invite-user-rule.vue'),
meta: { title: '邀请用户配置' },
},
{
name: 'AswatUserAdvertising',
path: '/sys/user-advertising',
component: () => import('#/views/operate/user-advertising.vue'),
meta: { title: '用户观看广告配置' },
},
{
name: 'GameLudo',
path: '/game-ludo',
@ -189,54 +113,18 @@ const routes: RouteRecordRaw[] = [
component: () => import('#/views/operate/lottery-wheel-config.vue'),
meta: { title: '抽奖品配置' },
},
{
name: 'OperateUserFreight',
path: 'user_freight',
component: () => import('#/views/operate/user-freight.vue'),
meta: { title: '货运代理' },
},
{
name: 'OperateBusinessDevelopment',
path: 'room/business-development',
component: () => import('#/views/operate/business-development.vue'),
meta: { title: 'BD列表' },
},
{
name: 'InviteRewardRecord',
path: 'user/invite-reward',
component: () => import('#/views/operate/invite-reward.vue'),
meta: { title: '邀请用户奖励记录' },
},
{
name: 'OperateImAccountManager',
path: 'im/account/manager',
component: () => import('#/views/operate/im-account-manager.vue'),
meta: { title: 'IM账号管理' },
},
{
name: 'CheckInLog',
path: 'check/in/log',
component: () => import('#/views/operate/check-in-log.vue'),
meta: { title: '用户签到' },
},
{
name: 'OperateUserManage',
path: 'user',
component: () => import('#/views/operate/user-manage.vue'),
meta: { title: '用户列表' },
},
{
name: 'OperateAdministrator',
path: 'administrator',
component: () => import('#/views/operate/app-administrator.vue'),
meta: { title: 'APP管理员' },
},
{
name: 'OperateUserDevice',
path: 'user/user-device',
component: () => import('#/views/operate/user-device-list.vue'),
meta: { title: '用户最新设备列表' },
},
{
name: 'OperateCompensate',
path: 'compensate',
@ -255,42 +143,6 @@ const routes: RouteRecordRaw[] = [
component: () => import('#/views/operate/order-abnormal.vue'),
meta: { title: '异常订单' },
},
{
name: 'OperateUserFreightInfoList',
path: 'user_freight_info_list',
component: () => import('#/views/operate/user-freight-info-list.vue'),
meta: { title: '货运代理流水' },
},
{
name: 'OperateQualityUsersList',
path: 'quality_users_list',
component: () => import('#/views/operate/quality-users-list.vue'),
meta: { title: '高产用户列表' },
},
{
name: 'OperateGoldRunningWater',
path: 'running/water',
component: () => import('#/views/operate/gold-running-water.vue'),
meta: { title: '金币收支记录' },
},
{
name: 'OperateInsideOperationGold',
path: 'inside-operation-gold',
component: () => import('#/views/operate/inside-operation-gold.vue'),
meta: { title: '内部操作金币' },
},
{
name: 'GameCouponRunningWater',
path: '/game/coupon/running/water',
component: () => import('#/views/operate/game-coupon-running-water.vue'),
meta: { title: '游戏券收支记录' },
},
{
name: 'OperateIntegralStream',
path: 'integral/stream',
component: () => import('#/views/operate/integral-stream.vue'),
meta: { title: '积分收支记录' },
},
{
name: 'OperateOrderDetails',
path: 'order/details',
@ -315,18 +167,6 @@ const routes: RouteRecordRaw[] = [
component: () => import('#/views/operate/game-lottery-config.vue'),
meta: { title: '抽奖概率配置' },
},
{
name: 'OperateBadgeConfig',
path: 'badge',
component: () => import('#/views/operate/badge-config.vue'),
meta: { title: '徽章配置管理' },
},
{
name: 'OperateGiftHistory',
path: 'gift/history',
component: () => import('#/views/operate/gift-history.vue'),
meta: { title: '礼物赠送记录' },
},
{
name: 'GameRedPacketRecord',
path: '/game/red/packet/record',
@ -441,31 +281,6 @@ const routes: RouteRecordRaw[] = [
component: () => import('#/views/operate/mike-type-list.vue'),
meta: { title: '购买麦位类型流水' },
},
{
name: 'RunningWaterUserProps',
path: '/running/water/user/props',
component: () => import('#/views/operate/user-props-running-water.vue'),
meta: { title: '用户道具流水' },
},
{
name: 'UserDiamondBalance',
path: '/user/diamond-balance',
component: () => import('#/views/operate/user-diamond-balance.vue'),
meta: { title: '用户钻石余额列表' },
},
{
name: 'UserDiamondRunWater',
path: '/user/diamond-run-water',
component: () =>
import('#/views/operate/user-diamond-running-water.vue'),
meta: { title: '用户钻石流水列表' },
},
{
name: 'UserActivityReceive',
path: '/user/user-activity-receive',
component: () => import('#/views/operate/user-activity-receive.vue'),
meta: { title: '用户活动领奖记录' },
},
{
name: 'GameTrumpet',
path: '/game/trumpet',
@ -478,18 +293,6 @@ const routes: RouteRecordRaw[] = [
component: () => import('#/views/operate/game-egg.vue'),
meta: { title: '砸金蛋' },
},
{
name: 'BeautifulNumberApply',
path: '/user/beautiful-number-apply',
component: () => import('#/views/operate/beautiful-number-apply.vue'),
meta: { title: '用户申请靓号记录' },
},
{
name: 'OperateBeautifulNumber',
path: 'beautiful-number',
component: () => import('#/views/operate/beautiful-number.vue'),
meta: { title: '靓号池管理' },
},
{
name: 'ActivityRoomContribution',
path: '/activity/room/contribution',

View File

@ -4,15 +4,17 @@ import { computed, reactive, ref } from 'vue';
import { Page } from '@vben/common-ui';
import { useAccessStore } from '@vben/stores';
import { onlineRoom } from '#/api/legacy/room';
import { onlineRoom, updateProfile } from '#/api/legacy/room';
import { getAllowedSysOrigins } from '#/views/system/shared';
import {
Button,
Card,
Empty,
Modal,
Space,
Tag,
message,
} from 'antdv-next';
import AccountHandleModal from '#/views/approval/components/account-handle-modal.vue';
@ -35,6 +37,7 @@ const regionSelectRef = ref<InstanceType<typeof SystemRegionSelect> | null>(null
const loading = ref(false);
const searchLoading = ref(false);
const list = ref<Array<Record<string, any>>>([]);
const roomStatusSavingId = ref<number | string>('');
const membersOpen = ref(false);
const accountHandleOpen = ref(false);
const roomDetailsOpen = ref(false);
@ -102,6 +105,38 @@ function openRoomDetails(record: Record<string, any>) {
roomDetailsOpen.value = true;
}
function isRoomClosed(record: Record<string, any>) {
return record.profile?.event === 'CLOSE';
}
function handleToggleRoomStatus(record: Record<string, any>) {
const roomId = record.profile?.id;
const userId = record.profile?.userId;
if (!roomId || !userId) {
message.warning('房间信息不完整,无法操作');
return;
}
const nextEvent = isRoomClosed(record) ? 'AVAILABLE' : 'CLOSE';
Modal.confirm({
title: nextEvent === 'CLOSE' ? '是否确认关闭该房间?' : '是否确认恢复该房间?',
async onOk() {
roomStatusSavingId.value = roomId;
try {
await updateProfile({
event: nextEvent,
id: roomId,
sysOrigin: record.profile?.sysOrigin || query.sysOrigin,
userId,
});
message.success(nextEvent === 'CLOSE' ? '关闭成功' : '恢复成功');
await loadData();
} finally {
roomStatusSavingId.value = '';
}
},
});
}
loadData();
</script>
@ -166,6 +201,14 @@ loadData();
</div>
<div class="card-actions">
<Button size="small" type="link" @click="openMembers(item)">在线成员</Button>
<Button
size="small"
type="link"
:loading="roomStatusSavingId === item.profile?.id"
@click="handleToggleRoomStatus(item)"
>
{{ isRoomClosed(item) ? '恢复' : '关闭' }}
</Button>
<Button size="small" type="link" @click="openAccountHandle(item)">账号处理</Button>
<Button size="small" type="link" @click="openRoomDetails(item)">房间详情</Button>
</div>

View File

@ -55,6 +55,8 @@ function createEditForm() {
roomCover: '',
roomDesc: '',
roomName: '',
sysOrigin: '',
userId: '',
};
}
@ -69,6 +71,7 @@ const searchLoading = ref(false);
const list = ref<Array<Record<string, any>>>([]);
const listNotData = ref(false);
const loadMoreLoading = ref(false);
const roomStatusSavingId = ref<number | string>('');
const editOpen = ref(false);
const uploadLoading = ref(false);
@ -127,7 +130,7 @@ const columns = [
{ dataIndex: 'room', key: 'room', title: '封面 / 房间信息', width: 320 },
{ dataIndex: 'base', key: 'base', title: '房间信息', width: 180 },
{ dataIndex: 'time', key: 'time', title: '时间', width: 220 },
{ dataIndex: 'actions', key: 'actions', title: '操作', width: 260 },
{ dataIndex: 'actions', key: 'actions', title: '操作', width: 320 },
];
const roomEventSelectOptions = ROOM_EVENT_OPTIONS.map((item) => ({
label: item.name,
@ -247,6 +250,8 @@ function openEdit(record: Record<string, any>) {
roomCover: record.roomCover || '',
roomDesc: record.roomDesc || '',
roomName: record.roomName || '',
sysOrigin: record.sysOrigin || query.sysOrigin,
userId: record.userId || '',
});
editRow.value = record;
editOpen.value = true;
@ -300,6 +305,36 @@ async function handleSaveEdit() {
}
}
function isRoomClosed(record: Record<string, any>) {
return record.event === 'CLOSE';
}
function handleToggleRoomStatus(record: Record<string, any>) {
if (!record.id || !record.userId) {
message.warning('房间信息不完整,无法操作');
return;
}
const nextEvent = isRoomClosed(record) ? 'AVAILABLE' : 'CLOSE';
Modal.confirm({
title: nextEvent === 'CLOSE' ? '是否确认关闭该房间?' : '是否确认恢复该房间?',
async onOk() {
roomStatusSavingId.value = record.id;
try {
await updateProfile({
event: nextEvent,
id: record.id,
sysOrigin: record.sysOrigin || query.sysOrigin,
userId: record.userId,
});
message.success(nextEvent === 'CLOSE' ? '关闭成功' : '恢复成功');
await loadData(true);
} finally {
roomStatusSavingId.value = '';
}
},
});
}
function openApproval(record: Record<string, any>, type: string) {
approvalType.value = type;
approvalRoomId.value = record.id;
@ -494,6 +529,14 @@ async function handleCopy(value: number | string) {
<Space wrap>
<Button size="small" type="link" @click="handleCopy(record.id)">复制ID</Button>
<Button size="small" type="link" @click="openEdit(record)">编辑资料</Button>
<Button
size="small"
type="link"
:loading="roomStatusSavingId === record.id"
@click="handleToggleRoomStatus(record)"
>
{{ isRoomClosed(record) ? '恢复' : '关闭' }}
</Button>
<Button size="small" type="link" @click="openRoomMembers(record.id)">成员列表</Button>
<Button size="small" type="link" @click="openVisitors(record.id)">访客记录</Button>
<Button size="small" type="link" @click="openOperationLogs(record.id)">操作日志</Button>

View File

@ -1,10 +1,9 @@
<script lang="ts" setup>
import { computed, reactive, ref } from 'vue';
import { computed, onMounted, reactive, ref, watch } from 'vue';
import { Page } from '@vben/common-ui';
import { useAccessStore } from '@vben/stores';
import { regionConfigTable } from '#/api/legacy/system';
import {
deleteTeamPolicy,
teamPolicyAdd,
@ -15,6 +14,7 @@ import {
import { listPayOpenCountry } from '#/api/legacy/pay';
import InlineFilterField from '#/views/_shared/inline-filter-field.vue';
import InlineFilterToolbar from '#/views/_shared/inline-filter-toolbar.vue';
import SystemRegionSelect from '#/views/operate/components/system-region-select.vue';
import { formatDate, getAllowedSysOrigins } from '#/views/system/shared';
import {
@ -23,9 +23,9 @@ import {
Drawer,
Empty,
Input,
InputNumber,
Modal,
Select,
SelectOption,
Space,
Table,
Tag,
@ -57,7 +57,6 @@ const sysOriginOptions = computed(() => {
});
const loading = ref(false);
const regionLoading = ref(false);
const countryLoading = ref(false);
const historyLoading = ref(false);
const rewardOpen = ref(false);
@ -72,7 +71,6 @@ const currentRewardIndex = ref(-1);
const data = ref<Record<string, any>>({});
const historyList = ref<LegacyTeamPolicyItem[]>([]);
const policies = ref<Array<Record<string, any>>>([]);
const regionOptions = ref<Array<Record<string, any>>>([]);
const countryList = ref<Array<Record<string, any>>>([]);
const currentRewardRow = ref<Record<string, any>>({});
@ -102,6 +100,15 @@ const columns = computed(() => [
{ key: 'actions', title: '操作', width: isEditing.value ? 220 : 120, fixed: 'right' as const },
]);
const countryOptions = computed(() =>
countryList.value
.map((item) => ({
label: getCountryLabel(item),
value: item.country?.alphaTwo as string | undefined,
}))
.filter((item) => item.value),
);
function createFormData() {
return {
countryCode: '',
@ -131,26 +138,14 @@ function syncQueryToForm() {
formData.sysOrigin = query.sysOrigin;
}
async function loadRegions(sysOrigin: string) {
if (!sysOrigin) {
regionOptions.value = [];
return;
}
regionLoading.value = true;
try {
regionOptions.value = await regionConfigTable({ sysOrigin });
} finally {
regionLoading.value = false;
}
}
async function loadCountries() {
if (!meta.value.showCountryFilter) {
countryList.value = [];
return;
}
countryLoading.value = true;
try {
countryList.value = await listPayOpenCountry();
countryList.value = (await listPayOpenCountry()) || [];
} finally {
countryLoading.value = false;
}
@ -192,12 +187,8 @@ function handleSearch() {
void loadData();
}
async function handleSysOriginChange(value: string) {
function handleSysOriginChange(value: string) {
query.sysOrigin = value;
query.region = '';
isEditing.value = false;
clearCurrentData();
await loadRegions(value);
}
function handleRegionChange() {
@ -220,11 +211,13 @@ function handleCountryChange(value?: string) {
function openCreatePolicy() {
resetFormData();
policies.value = [];
syncQueryToForm();
policies.value = [createEmptyTeamPolicyRow()];
addRowsNumber.value = '1';
currentAddRowIndex.value = -1;
addRowOpen.value = true;
isClickNewPolicyButton.value = true;
addRowOpen.value = false;
isClickNewPolicyButton.value = false;
isEditing.value = true;
}
function openAddRow(index: number) {
@ -251,6 +244,20 @@ function handleAddRows() {
addRowOpen.value = false;
}
function normalizePolicyItem(item: Record<string, any>) {
return {
...item,
effectiveDay: Number(item.effectiveDay || 0),
level: Number(item.level || 0),
memberSalary: Number(item.memberSalary || 0),
onlineTime: Number(item.onlineTime || 0),
ownSalary: Number(item.ownSalary || 0),
propsRewards: Array.isArray(item.propsRewards) ? item.propsRewards : [],
target: Number(item.target || 0),
totalSalary: Number(getTeamPolicyTotalSalary(item)),
};
}
function movePolicyUp(index: number) {
if (index <= 0) {
return;
@ -299,6 +306,7 @@ function handleRewardSuccess(rewards: Array<Record<string, any>>) {
function handleEditHistory(item: LegacyTeamPolicyItem) {
policies.value = clonePolicies(item.policy);
formData.countryCode = item.countryCode ? String(item.countryCode) : query.countryCode;
formData.id = item.id ? String(item.id) : '';
formData.region = item.region ? String(item.region) : '';
formData.title = item.title || '';
@ -329,10 +337,7 @@ async function handleDeleteHistory(id?: number | string) {
async function persistPolicy(release: boolean) {
formData.countryCode = query.countryCode;
formData.policy = clonePolicies(policies.value).map((item) => ({
...item,
totalSalary: getTeamPolicyTotalSalary(item),
}));
formData.policy = clonePolicies(policies.value).map(normalizePolicyItem);
formData.region = query.region;
formData.release = release;
formData.sysOrigin = query.sysOrigin;
@ -345,6 +350,10 @@ async function persistPolicy(release: boolean) {
message.warning('请输入政策标题');
return;
}
if (formData.policy.length === 0) {
message.warning('请至少添加一条政策');
return;
}
loading.value = true;
try {
@ -380,8 +389,38 @@ function getCountryLabel(item: Record<string, any>) {
return item?.country?.aliasName || item?.country?.enName || '-';
}
loadRegions(query.sysOrigin);
loadCountries();
watch(
sysOriginOptions,
(options) => {
if (!query.sysOrigin) {
query.sysOrigin = String(options[0]?.value || '');
}
},
{ immediate: true },
);
watch(
() => query.sysOrigin,
(value, previousValue) => {
if (!value) {
query.region = '';
query.countryCode = '';
clearCurrentData();
return;
}
if (value !== previousValue) {
query.region = '';
query.countryCode = '';
isEditing.value = false;
clearCurrentData();
}
},
{ immediate: true },
);
onMounted(() => {
void loadCountries();
});
</script>
<template>
@ -398,23 +437,14 @@ loadCountries();
></SysOriginSelect>
</InlineFilterField>
<InlineFilterField label="区域">
<Select option-label-prop="label"
<SystemRegionSelect
v-model:value="query.region"
:loading="regionLoading"
allow-clear
clearable
placeholder="请选择区域"
:sys-origin="query.sysOrigin"
style="width: 180px"
@change="handleRegionChange"
>
<SelectOption
v-for="item in regionOptions"
:key="item.id"
:label="item.regionName"
:value="item.id"
>
{{ item.regionName }}
</SelectOption>
</Select>
/>
</InlineFilterField>
<InlineFilterField
v-if="meta.showCountryFilter"
@ -427,16 +457,8 @@ loadCountries();
placeholder="国家"
style="width: 220px"
@change="handleCountryChange"
>
<SelectOption
v-for="item in countryList"
:key="item.id"
:label="getCountryLabel(item)"
:value="item.country?.alphaTwo"
>
{{ getCountryLabel(item) }}
</SelectOption>
</Select>
:options="countryOptions"
/>
</InlineFilterField>
<Space wrap>
<Button
@ -569,39 +591,53 @@ loadCountries();
>
<template #bodyCell="{ column, index, record }">
<template v-if="column.key === 'level'">
<Input
<InputNumber
v-model:value="record.level"
:min="0"
placeholder="等级"
style="width: 100%"
/>
</template>
<template v-else-if="column.key === 'onlineTime'">
<Input
<InputNumber
v-model:value="record.onlineTime"
:min="0"
placeholder="时间"
style="width: 100%"
/>
</template>
<template v-else-if="column.key === 'effectiveDay'">
<Input
<InputNumber
v-model:value="record.effectiveDay"
:min="0"
placeholder="有效天"
style="width: 100%"
/>
</template>
<template v-else-if="column.key === 'target'">
<Input
<InputNumber
v-model:value="record.target"
:min="0"
placeholder="目标"
style="width: 100%"
/>
</template>
<template v-else-if="column.key === 'memberSalary'">
<Input
<InputNumber
v-model:value="record.memberSalary"
:min="0"
:precision="2"
:placeholder="meta.memberSalaryLabel"
style="width: 100%"
/>
</template>
<template v-else-if="column.key === 'ownSalary'">
<Input
<InputNumber
v-model:value="record.ownSalary"
:min="0"
:precision="2"
:placeholder="meta.ownSalaryLabel"
style="width: 100%"
/>
</template>
<template v-else-if="column.key === 'totalSalary'">