fix: sync admin menu access and bd management
This commit is contained in:
parent
246697871c
commit
8f2b5ad3dd
@ -69,10 +69,19 @@ function convertMenuNodes(menus: LegacyMenu[]): RouteRecordStringComponent[] {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户所有原始菜单
|
||||
*/
|
||||
export async function getLegacyMenusApi() {
|
||||
return (await requestClient.get<LegacyMenu[]>('/account/menus')) || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户所有菜单
|
||||
*/
|
||||
export async function getAllMenusApi() {
|
||||
const result = await requestClient.get<LegacyMenu[]>('/account/menus');
|
||||
const result = await getLegacyMenusApi();
|
||||
return convertMenuNodes(result || []);
|
||||
}
|
||||
|
||||
export type { LegacyMenu };
|
||||
|
||||
@ -193,6 +193,48 @@ export async function pageBdLead(params: Record<string, any>) {
|
||||
);
|
||||
}
|
||||
|
||||
export async function pageBdTable(params: Record<string, any>) {
|
||||
return requestClient.get<LegacyPageResult<Record<string, any>>>(
|
||||
'/team/bd/page',
|
||||
{
|
||||
params,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export async function pageBdMemberTable(params: Record<string, any>) {
|
||||
return requestClient.get<LegacyPageResult<Record<string, any>>>(
|
||||
'/team/bd/member/page',
|
||||
{
|
||||
params,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export async function deleteBd(id: number | string) {
|
||||
return requestClient.get('/team/bd/del', {
|
||||
params: { id },
|
||||
});
|
||||
}
|
||||
|
||||
export async function deleteBdMember(id: number | string) {
|
||||
return requestClient.get('/team/bd/member/del', {
|
||||
params: { id },
|
||||
});
|
||||
}
|
||||
|
||||
export async function addBd(data: Record<string, any>) {
|
||||
return requestClient.post('/team/bd/add', data);
|
||||
}
|
||||
|
||||
export async function updateBd(data: Record<string, any>) {
|
||||
return requestClient.post('/team/bd/update', data);
|
||||
}
|
||||
|
||||
export async function changeBdLeader(data: Record<string, any>) {
|
||||
return requestClient.post('/team/bd/change-bd-leader', data);
|
||||
}
|
||||
|
||||
export async function deleteBdLead(id: number | string) {
|
||||
return requestClient.get('/team/bd/leader/del', {
|
||||
params: { id },
|
||||
|
||||
@ -8,6 +8,7 @@ import { defineOverridesPreferences } from '@vben/preferences';
|
||||
export const overridesPreferences = defineOverridesPreferences({
|
||||
// overrides
|
||||
app: {
|
||||
accessMode: 'frontend',
|
||||
defaultHomePath: '/workspace',
|
||||
name: '数据平台',
|
||||
},
|
||||
|
||||
@ -2,18 +2,169 @@ import type {
|
||||
ComponentRecordType,
|
||||
GenerateMenuAndRoutesOptions,
|
||||
} from '@vben/types';
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
import { generateAccessible } from '@vben/access';
|
||||
import { preferences } from '@vben/preferences';
|
||||
|
||||
import { message } from 'antdv-next';
|
||||
|
||||
import { getAllMenusApi } from '#/api';
|
||||
import { getAllMenusApi, getLegacyMenusApi, type LegacyMenu } from '#/api';
|
||||
import { BasicLayout, IFrameView } from '#/layouts';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
const forbiddenComponent = () => import('#/views/_core/fallback/forbidden.vue');
|
||||
|
||||
const ROUTE_MENU_OVERRIDES: Record<
|
||||
string,
|
||||
{
|
||||
aliases?: string[];
|
||||
routers?: string[];
|
||||
titles?: string[];
|
||||
}
|
||||
> = {
|
||||
BdLead: { aliases: ['BdLead'], routers: ['team/bd-lead'] },
|
||||
DynamicTagList: { aliases: ['DynamicTag'], routers: ['dynamic/tag'] },
|
||||
DynamicUserList: { aliases: ['UserDynamicList'], routers: ['user/dynamic/list'] },
|
||||
OperateBusinessDevelopment: {
|
||||
aliases: ['BusinessDevelopment'],
|
||||
routers: ['room/business-development'],
|
||||
titles: ['BD列表'],
|
||||
},
|
||||
PropsResourceConfig: { aliases: ['PropsConfig'], routers: ['props/config'] },
|
||||
PropsSourceGroup: {
|
||||
aliases: ['PropsActivityRewardCnf'],
|
||||
routers: ['props/props_source_group'],
|
||||
},
|
||||
SystemMenusManager: { aliases: ['MenusManager'], routers: ['menus'] },
|
||||
SystemResourceManager: { aliases: ['ResourceManager'], routers: ['resource'] },
|
||||
SystemRoleManager: { aliases: ['RolesManager'], routers: ['roles'] },
|
||||
SystemUserManager: { aliases: ['UserManager'], routers: ['user'] },
|
||||
TeamBillList: { aliases: ['TeamBill'], routers: ['team/bill'] },
|
||||
TeamMemberList: { aliases: ['TeamMember'], routers: ['team/member'] },
|
||||
UserDiamondBalance: {
|
||||
aliases: ['GameDoubleLayerFruit'],
|
||||
routers: ['/user/diamond-balance'],
|
||||
},
|
||||
UserDiamondRunWater: {
|
||||
aliases: ['GameDoubleLayerFruit'],
|
||||
routers: ['/user/diamond-run-water'],
|
||||
},
|
||||
};
|
||||
|
||||
function normalizeRoutePath(path?: string | null) {
|
||||
return String(path || '')
|
||||
.trim()
|
||||
.replace(/^\/+/, '')
|
||||
.replace(/\/+$/, '');
|
||||
}
|
||||
|
||||
function collectRouteAliases(route: RouteRecordRaw) {
|
||||
const values: string[] = [];
|
||||
if (route.name) {
|
||||
values.push(String(route.name));
|
||||
}
|
||||
const override = ROUTE_MENU_OVERRIDES[String(route.name || '')];
|
||||
if (override?.aliases) {
|
||||
values.push(...override.aliases);
|
||||
}
|
||||
return new Set(values.filter(Boolean));
|
||||
}
|
||||
|
||||
function collectRoutePaths(route: RouteRecordRaw) {
|
||||
const values = [normalizeRoutePath(route.path)];
|
||||
const aliases = Array.isArray(route.alias)
|
||||
? route.alias
|
||||
: route.alias
|
||||
? [route.alias]
|
||||
: [];
|
||||
values.push(...aliases.map((item) => normalizeRoutePath(String(item))));
|
||||
const override = ROUTE_MENU_OVERRIDES[String(route.name || '')];
|
||||
if (override?.routers) {
|
||||
values.push(...override.routers.map((item) => normalizeRoutePath(item)));
|
||||
}
|
||||
return new Set(values.filter(Boolean));
|
||||
}
|
||||
|
||||
function collectRouteTitles(route: RouteRecordRaw) {
|
||||
const values = [String(route.meta?.title || '').trim()];
|
||||
const override = ROUTE_MENU_OVERRIDES[String(route.name || '')];
|
||||
if (override?.titles) {
|
||||
values.push(...override.titles.map((item) => item.trim()));
|
||||
}
|
||||
return new Set(values.filter(Boolean));
|
||||
}
|
||||
|
||||
function buildAuthorizedMenuMatcher(menus: LegacyMenu[]) {
|
||||
const aliasSet = new Set<string>();
|
||||
const routerSet = new Set<string>();
|
||||
const titleSet = new Set<string>();
|
||||
const queue = [...menus];
|
||||
|
||||
while (queue.length > 0) {
|
||||
const item = queue.shift();
|
||||
if (!item) {
|
||||
continue;
|
||||
}
|
||||
if (item.alias) {
|
||||
aliasSet.add(String(item.alias));
|
||||
}
|
||||
if (item.router) {
|
||||
routerSet.add(normalizeRoutePath(item.router));
|
||||
}
|
||||
if (item.menuType === 2 && item.menuName) {
|
||||
titleSet.add(String(item.menuName).trim());
|
||||
}
|
||||
if (Array.isArray(item.childrens) && item.childrens.length > 0) {
|
||||
queue.push(...item.childrens);
|
||||
}
|
||||
}
|
||||
|
||||
return (route: RouteRecordRaw) => {
|
||||
for (const alias of collectRouteAliases(route)) {
|
||||
if (aliasSet.has(alias)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (const path of collectRoutePaths(route)) {
|
||||
if (routerSet.has(path)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (const title of collectRouteTitles(route)) {
|
||||
if (titleSet.has(title)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
function filterRoutesByAuthorizedMenus(
|
||||
routes: RouteRecordRaw[],
|
||||
isAuthorized: (route: RouteRecordRaw) => boolean,
|
||||
): RouteRecordRaw[] {
|
||||
return routes.flatMap((route) => {
|
||||
const nextRoute: RouteRecordRaw = { ...route };
|
||||
const children = route.children
|
||||
? filterRoutesByAuthorizedMenus(route.children, isAuthorized)
|
||||
: [];
|
||||
|
||||
if (route.meta?.ignoreAccess || route.meta?.hideInMenu) {
|
||||
nextRoute.children = children;
|
||||
return [nextRoute];
|
||||
}
|
||||
|
||||
if (children.length > 0) {
|
||||
nextRoute.children = children;
|
||||
return [nextRoute];
|
||||
}
|
||||
|
||||
delete nextRoute.children;
|
||||
return isAuthorized(route) ? [nextRoute] : [];
|
||||
});
|
||||
}
|
||||
|
||||
async function generateAccess(options: GenerateMenuAndRoutesOptions) {
|
||||
const pageMap: ComponentRecordType = import.meta.glob('../views/**/*.vue');
|
||||
|
||||
@ -22,8 +173,21 @@ async function generateAccess(options: GenerateMenuAndRoutesOptions) {
|
||||
IFrameView,
|
||||
};
|
||||
|
||||
let routes = options.routes || [];
|
||||
|
||||
if (preferences.app.accessMode === 'frontend') {
|
||||
message.loading({
|
||||
content: `${$t('common.loadingMenu')}...`,
|
||||
duration: 1.5,
|
||||
});
|
||||
const legacyMenus = await getLegacyMenusApi();
|
||||
const isAuthorized = buildAuthorizedMenuMatcher(legacyMenus);
|
||||
routes = filterRoutesByAuthorizedMenus(routes, isAuthorized);
|
||||
}
|
||||
|
||||
return await generateAccessible(preferences.app.accessMode, {
|
||||
...options,
|
||||
routes,
|
||||
fetchMenuListAsync: async () => {
|
||||
message.loading({
|
||||
content: `${$t('common.loadingMenu')}...`,
|
||||
|
||||
@ -3,6 +3,7 @@ import type { RouteRecordRaw } from 'vue-router';
|
||||
import { mergeRouteModules, traverseTreeValues } from '@vben/utils';
|
||||
|
||||
import { coreRoutes, fallbackNotFoundRoute } from './core';
|
||||
import dashboardRoutes from './modules/dashboard';
|
||||
|
||||
const dynamicRouteFiles = import.meta.glob('./modules/**/*.ts', {
|
||||
eager: true,
|
||||
@ -13,8 +14,14 @@ const externalRouteFiles = import.meta.glob('./external/**/*.ts', {
|
||||
});
|
||||
// const staticRouteFiles = import.meta.glob('./static/**/*.ts', { eager: true });
|
||||
|
||||
const accessRouteFiles = Object.fromEntries(
|
||||
Object.entries(dynamicRouteFiles).filter(
|
||||
([path]) => !path.endsWith('/dashboard.ts'),
|
||||
),
|
||||
);
|
||||
|
||||
/** 动态路由 */
|
||||
const dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(dynamicRouteFiles);
|
||||
const dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(accessRouteFiles);
|
||||
|
||||
/** 外部路由列表,访问这些页面可以不需要Layout,可能用于内嵌在别的系统(不会显示在菜单中) */
|
||||
const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles);
|
||||
@ -25,6 +32,7 @@ const staticRoutes: RouteRecordRaw[] = [];
|
||||
* 无需走权限验证(会一直显示在菜单中) */
|
||||
const routes: RouteRecordRaw[] = [
|
||||
...coreRoutes,
|
||||
...dashboardRoutes,
|
||||
...externalRoutes,
|
||||
fallbackNotFoundRoute,
|
||||
];
|
||||
|
||||
@ -25,13 +25,6 @@ const routes: RouteRecordRaw[] = [
|
||||
component: () => import('#/views/operate/set-hot-room.vue'),
|
||||
meta: { title: '热门房间' },
|
||||
},
|
||||
{
|
||||
name: 'OperateBusinessDevelopment',
|
||||
path: 'business-development',
|
||||
alias: '/operate/manager/room/business-development',
|
||||
component: () => import('#/views/operate/business-development.vue'),
|
||||
meta: { title: 'BD列表' },
|
||||
},
|
||||
{
|
||||
name: 'OperateContributionBalance',
|
||||
path: 'contribution/balance',
|
||||
|
||||
@ -52,6 +52,13 @@ const routes: RouteRecordRaw[] = [
|
||||
component: () => import('#/views/team/application-process-list.vue'),
|
||||
meta: { title: '成员审核日志' },
|
||||
},
|
||||
{
|
||||
name: 'OperateBusinessDevelopment',
|
||||
path: 'business-development',
|
||||
alias: '/operate/manager/room/business-development',
|
||||
component: () => import('#/views/operate/business-development.vue'),
|
||||
meta: { title: 'BD列表' },
|
||||
},
|
||||
{
|
||||
name: 'BdLead',
|
||||
path: 'team/bd-lead',
|
||||
|
||||
@ -1,9 +1,444 @@
|
||||
<script lang="ts" setup>
|
||||
import TeamBdLead from '#/views/team/bd-lead.vue';
|
||||
import { computed, reactive, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { Page } from '@vben/common-ui';
|
||||
import { useAccessStore, useUserStore } from '@vben/stores';
|
||||
|
||||
import {
|
||||
deleteBd,
|
||||
listMembers,
|
||||
pageBdTable,
|
||||
} from '#/api/legacy/team';
|
||||
import AccountInput from '#/components/account-input.vue';
|
||||
import SysOriginLabel from '#/components/sys-origin-label.vue';
|
||||
import SysOriginSelect from '#/components/sys-origin-select.vue';
|
||||
import TeamCreateDrawer from '#/views/team/components/team-create-drawer.vue';
|
||||
import {
|
||||
formatDate,
|
||||
getAllowedSysOrigins,
|
||||
} from '#/views/system/shared';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Input,
|
||||
Modal,
|
||||
Pagination,
|
||||
Select,
|
||||
Space,
|
||||
Table,
|
||||
message,
|
||||
} from 'antdv-next';
|
||||
|
||||
import BdBindLeaderModal from './components/bd-bind-leader-modal.vue';
|
||||
import BdBindTeamModal from './components/bd-bind-team-modal.vue';
|
||||
import BdFormModal from './components/bd-form-modal.vue';
|
||||
import BdMemberDrawer from './components/bd-member-drawer.vue';
|
||||
import SystemRegionSelect from './components/system-region-select.vue';
|
||||
|
||||
defineOptions({ name: 'OperateBusinessDevelopment' });
|
||||
|
||||
const router = useRouter();
|
||||
const accessStore = useAccessStore();
|
||||
const userStore = useUserStore();
|
||||
const sysOriginOptions = computed(() => {
|
||||
const options = getAllowedSysOrigins(accessStore.accessCodes || []);
|
||||
return options.length > 0 ? options : getAllowedSysOrigins([]);
|
||||
});
|
||||
|
||||
const loading = ref(false);
|
||||
const membersLoading = ref(false);
|
||||
const total = ref(0);
|
||||
const list = ref<Array<Record<string, any>>>([]);
|
||||
const members = ref<Array<Record<string, any>>>([]);
|
||||
const formOpen = ref(false);
|
||||
const bindLeaderOpen = ref(false);
|
||||
const bindTeamOpen = ref(false);
|
||||
const memberOpen = ref(false);
|
||||
const createTeamOpen = ref(false);
|
||||
const activeRow = ref<null | Record<string, any>>(null);
|
||||
|
||||
const query = reactive({
|
||||
bdLeadUserId: '',
|
||||
createUser: '',
|
||||
cursor: 1,
|
||||
limit: 20,
|
||||
memberQuantityRange: '',
|
||||
region: '',
|
||||
sysOrigin: '',
|
||||
userId: '',
|
||||
});
|
||||
|
||||
const columns = [
|
||||
{ dataIndex: 'sysOrigin', key: 'sysOrigin', title: '系统', width: 100 },
|
||||
{ dataIndex: 'userProfile', key: 'userProfile', title: '用户', width: 240 },
|
||||
{ dataIndex: 'regionName', key: 'regionName', title: '区域', width: 120 },
|
||||
{ dataIndex: 'agentCount', key: 'agentCount', title: '团队数量', width: 110 },
|
||||
{ dataIndex: 'bdLeadUserProfile', key: 'bdLeadUserProfile', title: 'BD Leader', width: 240 },
|
||||
{ dataIndex: 'contact', key: 'contact', title: '联系方式', width: 180 },
|
||||
{ dataIndex: 'createUserName', key: 'createUserName', title: '创建人', width: 140 },
|
||||
{ dataIndex: 'updateUserName', key: 'updateUserName', title: '修改人', width: 140 },
|
||||
{ dataIndex: 'createTime', key: 'createTime', title: '创建时间', width: 180 },
|
||||
{ dataIndex: 'actions', key: 'actions', title: '操作', width: 280 },
|
||||
];
|
||||
|
||||
function hasPermission(code: string) {
|
||||
const codes = accessStore.accessCodes || [];
|
||||
return codes.length === 0 || codes.includes(code);
|
||||
}
|
||||
|
||||
const canQueryAll = computed(() => hasPermission('bd:list:query:all'));
|
||||
const canQuerySelf = computed(() => hasPermission('bd:list:query:self'));
|
||||
const canQuery = computed(() => canQueryAll.value || canQuerySelf.value);
|
||||
const canCreate = computed(() => hasPermission('bd:list:add'));
|
||||
const canEdit = computed(() => hasPermission('bd:list:edit'));
|
||||
const canDelete = computed(() => hasPermission('bd:list:del'));
|
||||
const canAddMember = computed(() => hasPermission('bd:list:add:member'));
|
||||
const canBindMember = computed(() => hasPermission('bd:list:add:bind'));
|
||||
const showOperationCol = computed(
|
||||
() =>
|
||||
canEdit.value ||
|
||||
canDelete.value ||
|
||||
canAddMember.value ||
|
||||
canBindMember.value,
|
||||
);
|
||||
|
||||
function getUserText(profile?: Record<string, any>) {
|
||||
if (!profile) {
|
||||
return '-';
|
||||
}
|
||||
const nickname = profile.userNickname || profile.nickname || '-';
|
||||
const account = profile.actualAccount || profile.account || profile.id;
|
||||
return account ? `${nickname} / ${account}` : nickname;
|
||||
}
|
||||
|
||||
function openUserDetails(userId?: number | string) {
|
||||
if (!userId) {
|
||||
return;
|
||||
}
|
||||
router.push(`/common/user/deatils/${userId}`);
|
||||
}
|
||||
|
||||
async function loadMembers() {
|
||||
if (!canQueryAll.value) {
|
||||
members.value = [];
|
||||
return;
|
||||
}
|
||||
membersLoading.value = true;
|
||||
try {
|
||||
members.value = await listMembers();
|
||||
} finally {
|
||||
membersLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadData(reset = false) {
|
||||
if (!canQuery.value) {
|
||||
list.value = [];
|
||||
total.value = 0;
|
||||
return;
|
||||
}
|
||||
if (reset) {
|
||||
query.cursor = 1;
|
||||
}
|
||||
loading.value = true;
|
||||
try {
|
||||
const result = await pageBdTable({ ...query });
|
||||
list.value = result.records || [];
|
||||
total.value = result.total || 0;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
loadData(true);
|
||||
}
|
||||
|
||||
function handlePageChange(page: number, pageSize: number) {
|
||||
query.cursor = page;
|
||||
query.limit = pageSize;
|
||||
loadData();
|
||||
}
|
||||
|
||||
function openCreate() {
|
||||
activeRow.value = {
|
||||
sysOrigin: query.sysOrigin,
|
||||
};
|
||||
formOpen.value = true;
|
||||
}
|
||||
|
||||
function openEdit(record: Record<string, any>) {
|
||||
activeRow.value = {
|
||||
...record,
|
||||
sysOrigin: record.sysOrigin || query.sysOrigin,
|
||||
};
|
||||
formOpen.value = true;
|
||||
}
|
||||
|
||||
function openBindLeader(record: Record<string, any>) {
|
||||
activeRow.value = { ...record };
|
||||
bindLeaderOpen.value = true;
|
||||
}
|
||||
|
||||
function openBindTeam(record: Record<string, any>) {
|
||||
activeRow.value = { ...record };
|
||||
bindTeamOpen.value = true;
|
||||
}
|
||||
|
||||
function openCreateTeam(record: Record<string, any>) {
|
||||
activeRow.value = { ...record };
|
||||
createTeamOpen.value = true;
|
||||
}
|
||||
|
||||
function openMembers(record: Record<string, any>) {
|
||||
activeRow.value = { ...record };
|
||||
memberOpen.value = true;
|
||||
}
|
||||
|
||||
function handleDelete(record: Record<string, any>) {
|
||||
Modal.confirm({
|
||||
title: '您确定要删除吗(将无法恢复, 解散当前团队成员)?',
|
||||
async onOk() {
|
||||
await deleteBd(record.id);
|
||||
message.success('删除成功');
|
||||
await loadData(true);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
query.sysOrigin = String(sysOriginOptions.value[0]?.value || '');
|
||||
if (canQuerySelf.value && !canQueryAll.value) {
|
||||
query.createUser = String(userStore.userInfo?.userId || '');
|
||||
}
|
||||
|
||||
loadMembers();
|
||||
loadData(true);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TeamBdLead />
|
||||
<Page title="BD列表">
|
||||
<Card>
|
||||
<div v-if="canQuery" class="toolbar">
|
||||
<Space wrap>
|
||||
<SysOriginSelect
|
||||
v-model:value="query.sysOrigin"
|
||||
:options="sysOriginOptions"
|
||||
style="width: 140px"
|
||||
@change="handleSearch"
|
||||
/>
|
||||
<AccountInput
|
||||
v-model:value="query.userId"
|
||||
:sys-origin="query.sysOrigin"
|
||||
placeholder="BD用户ID"
|
||||
style="width: 220px"
|
||||
/>
|
||||
<AccountInput
|
||||
v-model:value="query.bdLeadUserId"
|
||||
:sys-origin="query.sysOrigin"
|
||||
placeholder="BD Lead用户ID"
|
||||
style="width: 220px"
|
||||
/>
|
||||
<SystemRegionSelect
|
||||
v-model:value="query.region"
|
||||
:sys-origin="query.sysOrigin"
|
||||
clearable
|
||||
placeholder="区域"
|
||||
style="width: 160px"
|
||||
@change="handleSearch"
|
||||
/>
|
||||
<Input
|
||||
v-model:value="query.memberQuantityRange"
|
||||
placeholder="开始~结束成员数量"
|
||||
style="width: 180px"
|
||||
/>
|
||||
<Select
|
||||
v-if="canQueryAll"
|
||||
v-model:value="query.createUser"
|
||||
:loading="membersLoading"
|
||||
allow-clear
|
||||
:field-names="{ label: 'nickname', value: 'id' }"
|
||||
:options="members"
|
||||
placeholder="后台成员"
|
||||
show-search
|
||||
style="width: 160px"
|
||||
@change="handleSearch"
|
||||
/>
|
||||
<Button :loading="loading" type="primary" @click="handleSearch">
|
||||
搜索
|
||||
</Button>
|
||||
<Button v-if="canCreate" @click="openCreate">新增BD</Button>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
<div v-else class="permission-tip">
|
||||
抱歉您无权查看,请联系管理员开通查看权限【bd:list:query:all / bd:list:query:self】
|
||||
</div>
|
||||
|
||||
<Table
|
||||
v-if="canQuery"
|
||||
:columns="columns"
|
||||
:data-source="list"
|
||||
:loading="loading"
|
||||
:pagination="false"
|
||||
row-key="id"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'sysOrigin'">
|
||||
<SysOriginLabel :value="record.sysOrigin" />
|
||||
</template>
|
||||
<template v-else-if="column.key === 'userProfile'">
|
||||
<Button
|
||||
v-if="record.userProfile?.id"
|
||||
type="link"
|
||||
@click="openUserDetails(record.userProfile?.id)"
|
||||
>
|
||||
{{ getUserText(record.userProfile) }}
|
||||
</Button>
|
||||
<span v-else>{{ getUserText(record.userProfile) }}</span>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'agentCount'">
|
||||
<Button
|
||||
v-if="Number(record.agentCount || 0) > 0"
|
||||
size="small"
|
||||
type="link"
|
||||
@click="openMembers(record)"
|
||||
>
|
||||
{{ record.agentCount }}
|
||||
</Button>
|
||||
<span v-else>0</span>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'bdLeadUserProfile'">
|
||||
<Button
|
||||
v-if="record.bdLeadUserProfile?.id"
|
||||
type="link"
|
||||
@click="openUserDetails(record.bdLeadUserProfile?.id)"
|
||||
>
|
||||
{{ getUserText(record.bdLeadUserProfile) }}
|
||||
</Button>
|
||||
<span v-else>{{ getUserText(record.bdLeadUserProfile) }}</span>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'contact'">
|
||||
{{ canEdit ? record.contact || '-' : '-' }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'createTime'">
|
||||
{{ formatDate(record.createTime) }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'actions' && showOperationCol">
|
||||
<Space wrap>
|
||||
<Button
|
||||
v-if="canAddMember"
|
||||
size="small"
|
||||
type="link"
|
||||
@click="openCreateTeam(record)"
|
||||
>
|
||||
新增代理
|
||||
</Button>
|
||||
<Button
|
||||
v-if="canBindMember"
|
||||
size="small"
|
||||
type="link"
|
||||
@click="openBindTeam(record)"
|
||||
>
|
||||
绑定代理
|
||||
</Button>
|
||||
<Button
|
||||
v-if="canEdit"
|
||||
size="small"
|
||||
type="link"
|
||||
@click="openEdit(record)"
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
<Button
|
||||
v-if="canEdit"
|
||||
size="small"
|
||||
type="link"
|
||||
@click="openBindLeader(record)"
|
||||
>
|
||||
换绑BD Leader
|
||||
</Button>
|
||||
<Button
|
||||
v-if="canDelete"
|
||||
danger
|
||||
size="small"
|
||||
type="link"
|
||||
@click="handleDelete(record)"
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
</Space>
|
||||
</template>
|
||||
</template>
|
||||
</Table>
|
||||
|
||||
<div v-if="canQuery" class="pagination">
|
||||
<Pagination
|
||||
:current="query.cursor"
|
||||
:page-size="query.limit"
|
||||
:total="total"
|
||||
show-size-changer
|
||||
@change="handlePageChange"
|
||||
@showSizeChange="handlePageChange"
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<BdFormModal
|
||||
:open="formOpen"
|
||||
:row="activeRow"
|
||||
:sys-origin="query.sysOrigin"
|
||||
@close="formOpen = false"
|
||||
@success="loadData(true)"
|
||||
/>
|
||||
|
||||
<BdBindLeaderModal
|
||||
:open="bindLeaderOpen"
|
||||
:row="activeRow"
|
||||
@close="bindLeaderOpen = false"
|
||||
@success="loadData(true)"
|
||||
/>
|
||||
|
||||
<BdBindTeamModal
|
||||
:open="bindTeamOpen"
|
||||
:row="activeRow"
|
||||
@close="bindTeamOpen = false"
|
||||
@success="loadData(true)"
|
||||
/>
|
||||
|
||||
<BdMemberDrawer
|
||||
:open="memberOpen"
|
||||
:row="activeRow"
|
||||
@close="memberOpen = false"
|
||||
/>
|
||||
|
||||
<TeamCreateDrawer
|
||||
:default-bd-user-id="String(activeRow?.userProfile?.id || activeRow?.userId || '')"
|
||||
:default-region="String(activeRow?.region || '')"
|
||||
:lock-bind-bd="true"
|
||||
:open="createTeamOpen"
|
||||
:sys-origin="String(activeRow?.sysOrigin || query.sysOrigin || '')"
|
||||
@close="createTeamOpen = false"
|
||||
@success="loadData(true)"
|
||||
/>
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.toolbar {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.permission-tip {
|
||||
color: #64748b;
|
||||
padding: 24px 0;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
71
apps/src/views/operate/components/bd-bind-leader-modal.vue
Normal file
71
apps/src/views/operate/components/bd-bind-leader-modal.vue
Normal file
@ -0,0 +1,71 @@
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, watch } from 'vue';
|
||||
|
||||
import { changeBdLeader } from '#/api/legacy/team';
|
||||
|
||||
import { Form, FormItem, Input, Modal, message } from 'antdv-next';
|
||||
|
||||
const props = defineProps<{
|
||||
open: boolean;
|
||||
row: null | Record<string, any>;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
close: [];
|
||||
success: [];
|
||||
}>();
|
||||
|
||||
const saving = ref(false);
|
||||
const form = reactive({
|
||||
bdId: '',
|
||||
newAccount: '',
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.open,
|
||||
(open) => {
|
||||
if (!open) {
|
||||
return;
|
||||
}
|
||||
form.bdId = String(props.row?.id || '');
|
||||
form.newAccount = '';
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!form.newAccount) {
|
||||
message.warning('请输入BD Leader账号');
|
||||
return;
|
||||
}
|
||||
saving.value = true;
|
||||
try {
|
||||
await changeBdLeader({ ...form });
|
||||
message.success('保存成功');
|
||||
emit('success');
|
||||
emit('close');
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
:confirm-loading="saving"
|
||||
:open="open"
|
||||
destroy-on-close
|
||||
title="换绑BD Leader"
|
||||
@cancel="emit('close')"
|
||||
@ok="handleSubmit"
|
||||
>
|
||||
<Form layout="vertical">
|
||||
<FormItem label="BD Leader">
|
||||
<Input
|
||||
v-model:value="form.newAccount"
|
||||
placeholder="请输入BD Leader账号"
|
||||
/>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</Modal>
|
||||
</template>
|
||||
77
apps/src/views/operate/components/bd-bind-team-modal.vue
Normal file
77
apps/src/views/operate/components/bd-bind-team-modal.vue
Normal file
@ -0,0 +1,77 @@
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, watch } from 'vue';
|
||||
|
||||
import { teamBindBd } from '#/api/legacy/team';
|
||||
import AccountInput from '#/components/account-input.vue';
|
||||
|
||||
import { Form, FormItem, Modal, message } from 'antdv-next';
|
||||
|
||||
const props = defineProps<{
|
||||
open: boolean;
|
||||
row: null | Record<string, any>;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
close: [];
|
||||
success: [];
|
||||
}>();
|
||||
|
||||
const saving = ref(false);
|
||||
const sysOrigin = ref('');
|
||||
const form = reactive({
|
||||
bdUserId: '',
|
||||
ownUserId: '',
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.open,
|
||||
(open) => {
|
||||
if (!open) {
|
||||
return;
|
||||
}
|
||||
sysOrigin.value = String(
|
||||
props.row?.userProfile?.originSys || props.row?.sysOrigin || '',
|
||||
);
|
||||
form.bdUserId = String(props.row?.userProfile?.id || props.row?.userId || '');
|
||||
form.ownUserId = '';
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!form.ownUserId) {
|
||||
message.warning('请输入代理账号');
|
||||
return;
|
||||
}
|
||||
saving.value = true;
|
||||
try {
|
||||
await teamBindBd({ ...form });
|
||||
message.success('保存成功');
|
||||
emit('success');
|
||||
emit('close');
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
:confirm-loading="saving"
|
||||
:open="open"
|
||||
destroy-on-close
|
||||
title="绑定代理"
|
||||
@cancel="emit('close')"
|
||||
@ok="handleSubmit"
|
||||
>
|
||||
<Form layout="vertical">
|
||||
<FormItem label="代理">
|
||||
<AccountInput
|
||||
v-model:value="form.ownUserId"
|
||||
:sys-origin="sysOrigin"
|
||||
placeholder="请输入代理账号"
|
||||
/>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</Modal>
|
||||
</template>
|
||||
143
apps/src/views/operate/components/bd-form-modal.vue
Normal file
143
apps/src/views/operate/components/bd-form-modal.vue
Normal file
@ -0,0 +1,143 @@
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, watch } from 'vue';
|
||||
|
||||
import { addBd, updateBd } from '#/api/legacy/team';
|
||||
import AccountInput from '#/components/account-input.vue';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Drawer,
|
||||
Form,
|
||||
FormItem,
|
||||
Input,
|
||||
message,
|
||||
} from 'antdv-next';
|
||||
|
||||
import SystemRegionSelect from './system-region-select.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
open: boolean;
|
||||
row: null | Record<string, any>;
|
||||
sysOrigin: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
close: [];
|
||||
success: [];
|
||||
}>();
|
||||
|
||||
const saving = ref(false);
|
||||
const form = reactive({
|
||||
contact: '',
|
||||
id: '',
|
||||
region: '',
|
||||
sysOrigin: '',
|
||||
userId: '',
|
||||
});
|
||||
|
||||
const isUpdate = ref(false);
|
||||
const title = ref('新增BD');
|
||||
|
||||
watch(
|
||||
() => props.open,
|
||||
(open) => {
|
||||
if (!open) {
|
||||
return;
|
||||
}
|
||||
isUpdate.value = !!props.row?.id;
|
||||
title.value = isUpdate.value ? '编辑BD' : '新增BD';
|
||||
form.id = String(props.row?.id || '');
|
||||
form.userId = String(props.row?.userId || props.row?.userProfile?.id || '');
|
||||
form.region = String(props.row?.region || '');
|
||||
form.contact = String(props.row?.contact || '');
|
||||
form.sysOrigin = String(props.row?.sysOrigin || props.sysOrigin || '');
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.sysOrigin,
|
||||
(value) => {
|
||||
if (!isUpdate.value) {
|
||||
form.sysOrigin = String(value || '');
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!form.userId) {
|
||||
message.warning('请输入BD用户ID');
|
||||
return;
|
||||
}
|
||||
if (!form.region) {
|
||||
message.warning('请选择区域');
|
||||
return;
|
||||
}
|
||||
saving.value = true;
|
||||
try {
|
||||
const payload = { ...form, sysOrigin: form.sysOrigin || props.sysOrigin };
|
||||
if (isUpdate.value) {
|
||||
await updateBd(payload);
|
||||
} else {
|
||||
await addBd(payload);
|
||||
}
|
||||
message.success('保存成功');
|
||||
emit('success');
|
||||
emit('close');
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Drawer
|
||||
:open="open"
|
||||
:title="title"
|
||||
destroy-on-close
|
||||
width="520px"
|
||||
@close="emit('close')"
|
||||
>
|
||||
<Form layout="vertical">
|
||||
<FormItem label="BD">
|
||||
<AccountInput
|
||||
v-model:value="form.userId"
|
||||
:disabled="isUpdate"
|
||||
:sys-origin="form.sysOrigin || props.sysOrigin"
|
||||
placeholder="请输入BD用户ID"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="区域">
|
||||
<SystemRegionSelect
|
||||
v-model:value="form.region"
|
||||
:sys-origin="form.sysOrigin || props.sysOrigin"
|
||||
clearable
|
||||
placeholder="请选择区域"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="联系方式">
|
||||
<Input
|
||||
v-model:value="form.contact"
|
||||
placeholder="请输入联系方式"
|
||||
/>
|
||||
</FormItem>
|
||||
</Form>
|
||||
|
||||
<template #footer>
|
||||
<div class="drawer-footer">
|
||||
<Button @click="emit('close')">取消</Button>
|
||||
<Button :loading="saving" type="primary" @click="handleSubmit">
|
||||
保存
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
</Drawer>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.drawer-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
189
apps/src/views/operate/components/bd-member-drawer.vue
Normal file
189
apps/src/views/operate/components/bd-member-drawer.vue
Normal file
@ -0,0 +1,189 @@
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, watch } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { deleteBdMember, pageBdMemberTable } from '#/api/legacy/team';
|
||||
import AccountInput from '#/components/account-input.vue';
|
||||
import SysOriginLabel from '#/components/sys-origin-label.vue';
|
||||
import { formatDate } from '#/views/system/shared';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Drawer,
|
||||
Modal,
|
||||
Pagination,
|
||||
Space,
|
||||
Table,
|
||||
message,
|
||||
} from 'antdv-next';
|
||||
|
||||
const props = defineProps<{
|
||||
open: boolean;
|
||||
row: null | Record<string, any>;
|
||||
}>();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const loading = ref(false);
|
||||
const total = ref(0);
|
||||
const list = ref<Array<Record<string, any>>>([]);
|
||||
const sysOrigin = ref('');
|
||||
|
||||
const query = reactive({
|
||||
agentId: '',
|
||||
cursor: 1,
|
||||
limit: 20,
|
||||
userId: '',
|
||||
});
|
||||
|
||||
const columns = [
|
||||
{ dataIndex: 'sysOrigin', key: 'sysOrigin', title: '系统', width: 100 },
|
||||
{ dataIndex: 'userProfile', key: 'userProfile', title: '用户', width: 260 },
|
||||
{ dataIndex: 'anchorQuantity', key: 'anchorQuantity', title: '主播数量', width: 100 },
|
||||
{ dataIndex: 'createTime', key: 'createTime', title: '创建时间', width: 180 },
|
||||
{ dataIndex: 'actions', key: 'actions', title: '操作', width: 80 },
|
||||
];
|
||||
|
||||
watch(
|
||||
() => props.open,
|
||||
async (open) => {
|
||||
if (!open) {
|
||||
return;
|
||||
}
|
||||
query.cursor = 1;
|
||||
query.agentId = '';
|
||||
query.userId = String(props.row?.userId || props.row?.userProfile?.id || '');
|
||||
sysOrigin.value = String(
|
||||
props.row?.userProfile?.originSys || props.row?.sysOrigin || '',
|
||||
);
|
||||
await loadData(true);
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
function getUserText(profile?: Record<string, any>) {
|
||||
if (!profile) {
|
||||
return '-';
|
||||
}
|
||||
const nickname = profile.userNickname || profile.nickname || '-';
|
||||
const account = profile.actualAccount || profile.account || profile.id;
|
||||
return account ? `${nickname} / ${account}` : nickname;
|
||||
}
|
||||
|
||||
function openUserDetails(userId?: number | string) {
|
||||
if (!userId) {
|
||||
return;
|
||||
}
|
||||
router.push(`/common/user/deatils/${userId}`);
|
||||
}
|
||||
|
||||
async function loadData(reset = false) {
|
||||
if (reset) {
|
||||
query.cursor = 1;
|
||||
}
|
||||
loading.value = true;
|
||||
try {
|
||||
const result = await pageBdMemberTable({ ...query });
|
||||
list.value = result.records || [];
|
||||
total.value = result.total || 0;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handlePageChange(page: number, pageSize: number) {
|
||||
query.cursor = page;
|
||||
query.limit = pageSize;
|
||||
loadData();
|
||||
}
|
||||
|
||||
function handleDelete(record: Record<string, any>) {
|
||||
Modal.confirm({
|
||||
title: '您确定要删除吗?',
|
||||
async onOk() {
|
||||
await deleteBdMember(record.id);
|
||||
message.success('删除成功');
|
||||
await loadData(true);
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Drawer
|
||||
:open="open"
|
||||
:title="`BD团队代理成员 · ${props.row?.userProfile?.userNickname || props.row?.userId || ''}`"
|
||||
destroy-on-close
|
||||
width="960"
|
||||
@close="$emit('close')"
|
||||
>
|
||||
<div class="toolbar">
|
||||
<Space wrap>
|
||||
<AccountInput
|
||||
v-model:value="query.agentId"
|
||||
:sys-origin="sysOrigin"
|
||||
placeholder="用户ID"
|
||||
style="width: 220px"
|
||||
/>
|
||||
<Button :loading="loading" type="primary" @click="loadData(true)">
|
||||
搜索
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
<Table
|
||||
:columns="columns"
|
||||
:data-source="list"
|
||||
:loading="loading"
|
||||
:pagination="false"
|
||||
row-key="id"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'sysOrigin'">
|
||||
<SysOriginLabel :value="record.sysOrigin" />
|
||||
</template>
|
||||
<template v-else-if="column.key === 'userProfile'">
|
||||
<Button
|
||||
v-if="record.userProfile?.id"
|
||||
type="link"
|
||||
@click="openUserDetails(record.userProfile?.id)"
|
||||
>
|
||||
{{ getUserText(record.userProfile) }}
|
||||
</Button>
|
||||
<span v-else>{{ getUserText(record.userProfile) }}</span>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'createTime'">
|
||||
{{ formatDate(record.createTime) }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'actions'">
|
||||
<Button danger size="small" type="link" @click="handleDelete(record)">
|
||||
删除
|
||||
</Button>
|
||||
</template>
|
||||
</template>
|
||||
</Table>
|
||||
|
||||
<div class="pagination">
|
||||
<Pagination
|
||||
:current="query.cursor"
|
||||
:page-size="query.limit"
|
||||
:total="total"
|
||||
show-size-changer
|
||||
@change="handlePageChange"
|
||||
@showSizeChange="handlePageChange"
|
||||
/>
|
||||
</div>
|
||||
</Drawer>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.toolbar {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
@ -25,6 +25,9 @@ import {
|
||||
} from '../shared';
|
||||
|
||||
const props = defineProps<{
|
||||
defaultBdUserId?: string;
|
||||
defaultRegion?: string;
|
||||
lockBindBd?: boolean;
|
||||
open: boolean;
|
||||
sysOrigin: string;
|
||||
}>();
|
||||
@ -66,9 +69,11 @@ watch(
|
||||
return;
|
||||
}
|
||||
Object.assign(form, createForm(), {
|
||||
bdUserId: props.defaultBdUserId || '',
|
||||
region: props.defaultRegion || '',
|
||||
sysOrigin: props.sysOrigin || '',
|
||||
});
|
||||
bindBd.value = 'no';
|
||||
bindBd.value = props.defaultBdUserId ? 'yes' : 'no';
|
||||
formSettingDefault.value = false;
|
||||
formContactAdd.value = false;
|
||||
},
|
||||
@ -158,7 +163,7 @@ async function handleSubmit() {
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="绑定BD">
|
||||
<RadioGroup v-model:value="bindBd">
|
||||
<RadioGroup v-model:value="bindBd" :disabled="props.lockBindBd">
|
||||
<Radio value="yes">是</Radio>
|
||||
<Radio value="no">否</Radio>
|
||||
</RadioGroup>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user