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', name: 'OperateManager',
path: '/operate/manager', path: '/operate/manager',
redirect: '/operate/manager/gold-balance', redirect: '/operate/manager/agentActivity',
children: [ children: [
{ {
name: 'AgentActivity', name: 'AgentActivity',
@ -17,58 +17,6 @@ const routes: RouteRecordRaw[] = [
component: () => import('#/views/operate/agent-activity.vue'), component: () => import('#/views/operate/agent-activity.vue'),
meta: { title: '代理活动' }, 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', name: 'CustomerService',
path: '/customer-service', path: '/customer-service',
@ -87,12 +35,6 @@ const routes: RouteRecordRaw[] = [
component: () => import('#/views/operate/game-pk.vue'), component: () => import('#/views/operate/game-pk.vue'),
meta: { title: 'PK记录' }, meta: { title: 'PK记录' },
}, },
{
name: 'InviteUserLog',
path: 'invite-user-log',
component: () => import('#/views/operate/invite-user-log.vue'),
meta: { title: '邀请用户日志' },
},
{ {
name: 'OperateActivityConfig', name: 'OperateActivityConfig',
path: 'activity/cnf', path: 'activity/cnf',
@ -105,24 +47,6 @@ const routes: RouteRecordRaw[] = [
component: () => import('#/views/operate/activity-template.vue'), component: () => import('#/views/operate/activity-template.vue'),
meta: { title: '活动模版' }, 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', name: 'GameLudo',
path: '/game-ludo', path: '/game-ludo',
@ -189,54 +113,18 @@ const routes: RouteRecordRaw[] = [
component: () => import('#/views/operate/lottery-wheel-config.vue'), component: () => import('#/views/operate/lottery-wheel-config.vue'),
meta: { title: '抽奖品配置' }, meta: { title: '抽奖品配置' },
}, },
{
name: 'OperateUserFreight',
path: 'user_freight',
component: () => import('#/views/operate/user-freight.vue'),
meta: { title: '货运代理' },
},
{ {
name: 'OperateBusinessDevelopment', name: 'OperateBusinessDevelopment',
path: 'room/business-development', path: 'room/business-development',
component: () => import('#/views/operate/business-development.vue'), component: () => import('#/views/operate/business-development.vue'),
meta: { title: 'BD列表' }, 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', name: 'OperateAdministrator',
path: 'administrator', path: 'administrator',
component: () => import('#/views/operate/app-administrator.vue'), component: () => import('#/views/operate/app-administrator.vue'),
meta: { title: 'APP管理员' }, meta: { title: 'APP管理员' },
}, },
{
name: 'OperateUserDevice',
path: 'user/user-device',
component: () => import('#/views/operate/user-device-list.vue'),
meta: { title: '用户最新设备列表' },
},
{ {
name: 'OperateCompensate', name: 'OperateCompensate',
path: 'compensate', path: 'compensate',
@ -255,42 +143,6 @@ const routes: RouteRecordRaw[] = [
component: () => import('#/views/operate/order-abnormal.vue'), component: () => import('#/views/operate/order-abnormal.vue'),
meta: { title: '异常订单' }, 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', name: 'OperateOrderDetails',
path: 'order/details', path: 'order/details',
@ -315,18 +167,6 @@ const routes: RouteRecordRaw[] = [
component: () => import('#/views/operate/game-lottery-config.vue'), component: () => import('#/views/operate/game-lottery-config.vue'),
meta: { title: '抽奖概率配置' }, 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', name: 'GameRedPacketRecord',
path: '/game/red/packet/record', path: '/game/red/packet/record',
@ -441,31 +281,6 @@ const routes: RouteRecordRaw[] = [
component: () => import('#/views/operate/mike-type-list.vue'), component: () => import('#/views/operate/mike-type-list.vue'),
meta: { title: '购买麦位类型流水' }, 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', name: 'GameTrumpet',
path: '/game/trumpet', path: '/game/trumpet',
@ -478,18 +293,6 @@ const routes: RouteRecordRaw[] = [
component: () => import('#/views/operate/game-egg.vue'), component: () => import('#/views/operate/game-egg.vue'),
meta: { title: '砸金蛋' }, 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', name: 'ActivityRoomContribution',
path: '/activity/room/contribution', path: '/activity/room/contribution',

View File

@ -4,15 +4,17 @@ import { computed, reactive, ref } from 'vue';
import { Page } from '@vben/common-ui'; import { Page } from '@vben/common-ui';
import { useAccessStore } from '@vben/stores'; 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 { getAllowedSysOrigins } from '#/views/system/shared';
import { import {
Button, Button,
Card, Card,
Empty, Empty,
Modal,
Space, Space,
Tag, Tag,
message,
} from 'antdv-next'; } from 'antdv-next';
import AccountHandleModal from '#/views/approval/components/account-handle-modal.vue'; 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 loading = ref(false);
const searchLoading = ref(false); const searchLoading = ref(false);
const list = ref<Array<Record<string, any>>>([]); const list = ref<Array<Record<string, any>>>([]);
const roomStatusSavingId = ref<number | string>('');
const membersOpen = ref(false); const membersOpen = ref(false);
const accountHandleOpen = ref(false); const accountHandleOpen = ref(false);
const roomDetailsOpen = ref(false); const roomDetailsOpen = ref(false);
@ -102,6 +105,38 @@ function openRoomDetails(record: Record<string, any>) {
roomDetailsOpen.value = true; 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(); loadData();
</script> </script>
@ -166,6 +201,14 @@ loadData();
</div> </div>
<div class="card-actions"> <div class="card-actions">
<Button size="small" type="link" @click="openMembers(item)">在线成员</Button> <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="openAccountHandle(item)">账号处理</Button>
<Button size="small" type="link" @click="openRoomDetails(item)">房间详情</Button> <Button size="small" type="link" @click="openRoomDetails(item)">房间详情</Button>
</div> </div>

View File

@ -55,6 +55,8 @@ function createEditForm() {
roomCover: '', roomCover: '',
roomDesc: '', roomDesc: '',
roomName: '', roomName: '',
sysOrigin: '',
userId: '',
}; };
} }
@ -69,6 +71,7 @@ const searchLoading = ref(false);
const list = ref<Array<Record<string, any>>>([]); const list = ref<Array<Record<string, any>>>([]);
const listNotData = ref(false); const listNotData = ref(false);
const loadMoreLoading = ref(false); const loadMoreLoading = ref(false);
const roomStatusSavingId = ref<number | string>('');
const editOpen = ref(false); const editOpen = ref(false);
const uploadLoading = ref(false); const uploadLoading = ref(false);
@ -127,7 +130,7 @@ const columns = [
{ dataIndex: 'room', key: 'room', title: '封面 / 房间信息', width: 320 }, { dataIndex: 'room', key: 'room', title: '封面 / 房间信息', width: 320 },
{ dataIndex: 'base', key: 'base', title: '房间信息', width: 180 }, { dataIndex: 'base', key: 'base', title: '房间信息', width: 180 },
{ dataIndex: 'time', key: 'time', title: '时间', width: 220 }, { 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) => ({ const roomEventSelectOptions = ROOM_EVENT_OPTIONS.map((item) => ({
label: item.name, label: item.name,
@ -247,6 +250,8 @@ function openEdit(record: Record<string, any>) {
roomCover: record.roomCover || '', roomCover: record.roomCover || '',
roomDesc: record.roomDesc || '', roomDesc: record.roomDesc || '',
roomName: record.roomName || '', roomName: record.roomName || '',
sysOrigin: record.sysOrigin || query.sysOrigin,
userId: record.userId || '',
}); });
editRow.value = record; editRow.value = record;
editOpen.value = true; 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) { function openApproval(record: Record<string, any>, type: string) {
approvalType.value = type; approvalType.value = type;
approvalRoomId.value = record.id; approvalRoomId.value = record.id;
@ -494,6 +529,14 @@ async function handleCopy(value: number | string) {
<Space wrap> <Space wrap>
<Button size="small" type="link" @click="handleCopy(record.id)">复制ID</Button> <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" @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="openRoomMembers(record.id)">成员列表</Button>
<Button size="small" type="link" @click="openVisitors(record.id)">访客记录</Button> <Button size="small" type="link" @click="openVisitors(record.id)">访客记录</Button>
<Button size="small" type="link" @click="openOperationLogs(record.id)">操作日志</Button> <Button size="small" type="link" @click="openOperationLogs(record.id)">操作日志</Button>

View File

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