活动优化
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CodeQL / Analyze (${{ matrix.language }}) (none, javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
CI / CI OK (push) Has been cancelled
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CodeQL / Analyze (${{ matrix.language }}) (none, javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
CI / CI OK (push) Has been cancelled
This commit is contained in:
parent
3c746fcea9
commit
39195fb2e6
211
apps/web-antd/src/api/core/permission.ts
Normal file
211
apps/web-antd/src/api/core/permission.ts
Normal file
@ -0,0 +1,211 @@
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace PermissionApi {
|
||||
// ─── 用户组 ───────────────────────────────────────────────
|
||||
export interface UserGroup {
|
||||
id?: number;
|
||||
group_code: string;
|
||||
group_name: string;
|
||||
status: number;
|
||||
remark?: string;
|
||||
createTime?: number;
|
||||
updateTime?: number;
|
||||
}
|
||||
|
||||
export interface UserGroupListParams {
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
group_code?: string;
|
||||
group_name?: string;
|
||||
}
|
||||
|
||||
// ─── 角色(权限组)────────────────────────────────────────
|
||||
export interface Role {
|
||||
id?: number;
|
||||
role_code: string;
|
||||
role_name: string;
|
||||
status: number;
|
||||
is_system?: number;
|
||||
remark?: string;
|
||||
createTime?: number;
|
||||
updateTime?: number;
|
||||
}
|
||||
|
||||
export interface RoleListParams {
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
role_code?: string;
|
||||
role_name?: string;
|
||||
}
|
||||
|
||||
// ─── 单点权限 ─────────────────────────────────────────────
|
||||
export interface Permission {
|
||||
id?: number;
|
||||
permission_code: string;
|
||||
permission_name: string;
|
||||
permission_group?: string;
|
||||
api_path?: string;
|
||||
http_method?: string;
|
||||
status: number;
|
||||
remark?: string;
|
||||
createTime?: number;
|
||||
updateTime?: number;
|
||||
}
|
||||
|
||||
export interface PermissionListParams {
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
permission_code?: string;
|
||||
permission_name?: string;
|
||||
permission_group?: string;
|
||||
}
|
||||
|
||||
// ─── 关联关系 ─────────────────────────────────────────────
|
||||
export interface GroupRoleRelItem {
|
||||
group_id: number;
|
||||
role_id: number;
|
||||
}
|
||||
|
||||
export interface RolePermissionRelItem {
|
||||
role_id: number;
|
||||
permission_id: number;
|
||||
}
|
||||
|
||||
// ─── 用户权限分配 ──────────────────────────────────────────
|
||||
export interface UserGroupRelItem {
|
||||
admin_id: number;
|
||||
group_id: number;
|
||||
}
|
||||
|
||||
export interface UserGroupsAssignParams {
|
||||
admin_id: number;
|
||||
group_ids: number[];
|
||||
}
|
||||
|
||||
export interface UserGroupsOfUser {
|
||||
admin_id: number;
|
||||
groups: UserGroup[];
|
||||
}
|
||||
|
||||
// 用户直接单点权限(admin_user_permission_rel)
|
||||
export interface UserPermissionItem {
|
||||
permission_id: number;
|
||||
grant_type: 1 | 2; // 1=允许 2=拒绝
|
||||
}
|
||||
|
||||
export interface UserPermissionsAssignParams {
|
||||
admin_id: number;
|
||||
permissions: UserPermissionItem[];
|
||||
}
|
||||
|
||||
// ─── 通用分页响应 ─────────────────────────────────────────
|
||||
export interface PageResult<T> {
|
||||
items: T[];
|
||||
total: number;
|
||||
}
|
||||
}
|
||||
|
||||
// ═══ 用户组 API ════════════════════════════════════════════
|
||||
|
||||
export async function getUserGroupListApi(params: PermissionApi.UserGroupListParams) {
|
||||
return requestClient.post<PermissionApi.PageResult<PermissionApi.UserGroup>>(
|
||||
'/admin/usergroup/list',
|
||||
params,
|
||||
);
|
||||
}
|
||||
|
||||
export async function addUserGroupApi(params: PermissionApi.UserGroup) {
|
||||
return requestClient.post('/admin/usergroup/add', params);
|
||||
}
|
||||
|
||||
export async function editUserGroupApi(params: PermissionApi.UserGroup) {
|
||||
return requestClient.post('/admin/usergroup/edit', params);
|
||||
}
|
||||
|
||||
export async function deleteUserGroupApi(id: number) {
|
||||
return requestClient.post('/admin/usergroup/delete', { id });
|
||||
}
|
||||
|
||||
// ═══ 角色(权限组)API ══════════════════════════════════════
|
||||
|
||||
export async function getRoleListApi(params: PermissionApi.RoleListParams) {
|
||||
return requestClient.post<PermissionApi.PageResult<PermissionApi.Role>>(
|
||||
'/admin/role/list',
|
||||
params,
|
||||
);
|
||||
}
|
||||
|
||||
export async function addRoleApi(params: PermissionApi.Role) {
|
||||
return requestClient.post('/admin/role/add', params);
|
||||
}
|
||||
|
||||
export async function editRoleApi(params: PermissionApi.Role) {
|
||||
return requestClient.post('/admin/role/edit', params);
|
||||
}
|
||||
|
||||
export async function deleteRoleApi(id: number) {
|
||||
return requestClient.post('/admin/role/delete', { id });
|
||||
}
|
||||
|
||||
// 用户组绑定角色
|
||||
export async function setGroupRolesApi(params: { group_id: number; role_ids: number[] }) {
|
||||
return requestClient.post('/admin/usergroup/role/set', params);
|
||||
}
|
||||
|
||||
export async function getGroupRolesApi(group_id: number) {
|
||||
return requestClient.post<PermissionApi.Role[]>('/admin/usergroup/role/list', { group_id });
|
||||
}
|
||||
|
||||
// 角色绑定权限
|
||||
export async function setRolePermissionsApi(params: { role_id: number; permission_ids: number[] }) {
|
||||
return requestClient.post('/admin/role/permission/set', params);
|
||||
}
|
||||
|
||||
export async function getRolePermissionsApi(role_id: number) {
|
||||
return requestClient.post<PermissionApi.Permission[]>('/admin/role/permission/list', { role_id });
|
||||
}
|
||||
|
||||
// ═══ 单点权限 API ═══════════════════════════════════════════
|
||||
|
||||
export async function getPermissionListApi(params: PermissionApi.PermissionListParams) {
|
||||
return requestClient.post<PermissionApi.PageResult<PermissionApi.Permission>>(
|
||||
'/admin/permission/list',
|
||||
params,
|
||||
);
|
||||
}
|
||||
|
||||
export async function addPermissionApi(params: PermissionApi.Permission) {
|
||||
return requestClient.post('/admin/permission/add', params);
|
||||
}
|
||||
|
||||
export async function editPermissionApi(params: PermissionApi.Permission) {
|
||||
return requestClient.post('/admin/permission/edit', params);
|
||||
}
|
||||
|
||||
export async function deletePermissionApi(id: number) {
|
||||
return requestClient.post('/admin/permission/delete', { id });
|
||||
}
|
||||
|
||||
// ═══ 用户权限分配 API ═══════════════════════════════════════
|
||||
|
||||
export async function getUserGroupsApi(admin_id: number) {
|
||||
return requestClient.post<PermissionApi.UserGroup[]>('/admin/user/group/list', { admin_id });
|
||||
}
|
||||
|
||||
export async function setUserGroupsApi(params: PermissionApi.UserGroupsAssignParams) {
|
||||
return requestClient.post('/admin/user/group/set', params);
|
||||
}
|
||||
|
||||
// 用户直接单点权限
|
||||
export async function getUserPermissionsDirectApi(admin_id: number) {
|
||||
return requestClient.post<PermissionApi.UserPermissionItem[]>('/admin/user/permission/list', { admin_id });
|
||||
}
|
||||
|
||||
export async function setUserPermissionsDirectApi(params: PermissionApi.UserPermissionsAssignParams) {
|
||||
return requestClient.post('/admin/user/permission/set', params);
|
||||
}
|
||||
|
||||
// 获取用户通过用户组继承的角色列表
|
||||
export async function getUserRolesApi(admin_id: number) {
|
||||
return requestClient.post<PermissionApi.Role[]>('/admin/user/role/list', { admin_id });
|
||||
}
|
||||
@ -30,7 +30,14 @@
|
||||
"title": "Admin",
|
||||
"user": "User",
|
||||
"setting": "Setting",
|
||||
"log": "Log"
|
||||
"log": "Log",
|
||||
"config": "Config",
|
||||
"permission": {
|
||||
"userGroup": "User Groups",
|
||||
"role": "Roles",
|
||||
"permission": "Permissions",
|
||||
"userAssign": "User Assignment"
|
||||
}
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Dashboard",
|
||||
|
||||
@ -31,7 +31,13 @@
|
||||
"user": "用户管理",
|
||||
"setting": "系统设置",
|
||||
"log": "操作日志",
|
||||
"config": "配置管理"
|
||||
"config": "配置管理",
|
||||
"permission": {
|
||||
"userGroup": "用户组管理",
|
||||
"role": "权限组管理",
|
||||
"permission": "单点权限管理",
|
||||
"userAssign": "用户权限分配"
|
||||
}
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "运维管理",
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
export interface UserInfo {
|
||||
id?: number;
|
||||
username: string;
|
||||
password?: string;
|
||||
phone: string;
|
||||
|
||||
@ -48,6 +48,50 @@ const routes: RouteRecordRaw[] = [
|
||||
title: $t('page.admin.config'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'PermissionUserGroup',
|
||||
path: '/permission/user-group',
|
||||
component: () => import('#/views/admin/permission/user-group.vue'),
|
||||
meta: {
|
||||
authority: ['super'],
|
||||
affixTab: false,
|
||||
icon: 'material-symbols:group',
|
||||
title: $t('page.admin.permission.userGroup'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'PermissionRole',
|
||||
path: '/permission/role',
|
||||
component: () => import('#/views/admin/permission/role.vue'),
|
||||
meta: {
|
||||
authority: ['super'],
|
||||
affixTab: false,
|
||||
icon: 'material-symbols:shield-person',
|
||||
title: $t('page.admin.permission.role'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'PermissionItem',
|
||||
path: '/permission/permission',
|
||||
component: () => import('#/views/admin/permission/permission.vue'),
|
||||
meta: {
|
||||
authority: ['super'],
|
||||
affixTab: false,
|
||||
icon: 'material-symbols:key',
|
||||
title: $t('page.admin.permission.permission'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'PermissionUserAssign',
|
||||
path: '/permission/user-assign',
|
||||
component: () => import('#/views/admin/permission/user-assign.vue'),
|
||||
meta: {
|
||||
authority: ['super'],
|
||||
affixTab: false,
|
||||
icon: 'material-symbols:manage-accounts',
|
||||
title: $t('page.admin.permission.userAssign'),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
228
apps/web-antd/src/views/admin/permission/permission.vue
Normal file
228
apps/web-antd/src/views/admin/permission/permission.vue
Normal file
@ -0,0 +1,228 @@
|
||||
<script setup lang="ts">
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { useVbenModal, useVbenForm, Page } from '@vben/common-ui';
|
||||
import {
|
||||
getPermissionListApi,
|
||||
addPermissionApi,
|
||||
editPermissionApi,
|
||||
deletePermissionApi,
|
||||
} from '#/api/core/permission';
|
||||
import type { PermissionApi } from '#/api/core/permission';
|
||||
import { Button, Card, Space, Tag, Modal, message } from 'ant-design-vue';
|
||||
import { ref } from 'vue';
|
||||
|
||||
const HTTP_METHODS = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'];
|
||||
|
||||
const editingId = ref<number | undefined>(undefined);
|
||||
|
||||
const [Form, FormApi] = useVbenForm({
|
||||
layout: 'horizontal',
|
||||
wrapperClass: 'grid-cols-2',
|
||||
showDefaultActions: false,
|
||||
schema: [
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'permission_code',
|
||||
label: '权限编码',
|
||||
formItemClass: 'col-span-2',
|
||||
rules: 'required',
|
||||
componentProps: { placeholder: '例如:AC0004' },
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'permission_name',
|
||||
label: '权限名称',
|
||||
formItemClass: 'col-span-2',
|
||||
rules: 'required',
|
||||
componentProps: { placeholder: '例如:编辑活动' },
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'permission_group',
|
||||
label: '权限分组',
|
||||
formItemClass: 'col-span-2',
|
||||
componentProps: { placeholder: '例如:activity' },
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'api_path',
|
||||
label: '接口路径',
|
||||
formItemClass: 'col-span-2',
|
||||
componentProps: { placeholder: '例如:/api/activity/edit' },
|
||||
},
|
||||
{
|
||||
component: 'Select',
|
||||
fieldName: 'http_method',
|
||||
label: '请求方法',
|
||||
formItemClass: 'col-span-2',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
options: HTTP_METHODS.map((m) => ({ label: m, value: m })),
|
||||
},
|
||||
},
|
||||
{
|
||||
component: 'Select',
|
||||
fieldName: 'status',
|
||||
label: '状态',
|
||||
formItemClass: 'col-span-2',
|
||||
defaultValue: 1,
|
||||
componentProps: {
|
||||
options: [
|
||||
{ label: '启用', value: 1 },
|
||||
{ label: '停用', value: 0 },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
component: 'Textarea',
|
||||
fieldName: 'remark',
|
||||
label: '备注',
|
||||
formItemClass: 'col-span-2',
|
||||
componentProps: { rows: 3 },
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const [FormModal, FormModalApi] = useVbenModal({
|
||||
confirmText: '保存',
|
||||
onConfirm: async () => {
|
||||
const values = await FormApi.getValues();
|
||||
const params: PermissionApi.Permission = {
|
||||
id: editingId.value,
|
||||
permission_code: values.permission_code,
|
||||
permission_name: values.permission_name,
|
||||
permission_group: values.permission_group,
|
||||
api_path: values.api_path,
|
||||
http_method: values.http_method,
|
||||
status: values.status,
|
||||
remark: values.remark,
|
||||
};
|
||||
if (editingId.value) {
|
||||
await editPermissionApi(params);
|
||||
message.success('更新成功');
|
||||
} else {
|
||||
await addPermissionApi(params);
|
||||
message.success('新增成功');
|
||||
}
|
||||
FormModalApi.close();
|
||||
GridApi.reload();
|
||||
},
|
||||
});
|
||||
|
||||
const METHOD_COLORS: Record<string, string> = {
|
||||
GET: 'green',
|
||||
POST: 'blue',
|
||||
PUT: 'orange',
|
||||
DELETE: 'red',
|
||||
PATCH: 'purple',
|
||||
};
|
||||
|
||||
const gridOptions: VxeGridProps<PermissionApi.Permission> = {
|
||||
columns: [
|
||||
{ field: 'id', title: 'ID', width: 80 },
|
||||
{ field: 'permission_code', title: '权限编码', width: 130 },
|
||||
{ field: 'permission_name', title: '权限名称', minWidth: 150 },
|
||||
{ field: 'permission_group', title: '分组', width: 120 },
|
||||
{ field: 'api_path', title: '接口路径', minWidth: 200 },
|
||||
{
|
||||
field: 'http_method',
|
||||
title: '方法',
|
||||
width: 90,
|
||||
slots: { default: 'methodSlot' },
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
title: '状态',
|
||||
width: 90,
|
||||
slots: { default: 'statusSlot' },
|
||||
},
|
||||
{ fixed: 'right', slots: { default: 'actionSlot' }, title: '操作', width: 160 },
|
||||
],
|
||||
height: 'auto',
|
||||
pagerConfig: {},
|
||||
proxyConfig: {
|
||||
response: { result: 'items', total: 'total' },
|
||||
ajax: {
|
||||
query: async ({ page }) => {
|
||||
return await getPermissionListApi({
|
||||
page: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
rowConfig: { isHover: true },
|
||||
};
|
||||
|
||||
const [Grid, GridApi] = useVbenVxeGrid({ gridOptions });
|
||||
|
||||
function openCreate() {
|
||||
editingId.value = undefined;
|
||||
FormApi.resetForm();
|
||||
FormModalApi.setState({ title: '新增单点权限' });
|
||||
FormModalApi.open();
|
||||
}
|
||||
|
||||
function openEdit(row: PermissionApi.Permission) {
|
||||
editingId.value = row.id;
|
||||
FormApi.setValues({
|
||||
permission_code: row.permission_code,
|
||||
permission_name: row.permission_name,
|
||||
permission_group: row.permission_group,
|
||||
api_path: row.api_path,
|
||||
http_method: row.http_method,
|
||||
status: row.status,
|
||||
remark: row.remark,
|
||||
});
|
||||
FormModalApi.setState({ title: '编辑单点权限' });
|
||||
FormModalApi.open();
|
||||
}
|
||||
|
||||
function handleDelete(row: PermissionApi.Permission) {
|
||||
Modal.confirm({
|
||||
title: '删除确认',
|
||||
content: `确认删除权限「${row.permission_name}(${row.permission_code})」吗?`,
|
||||
async onOk() {
|
||||
await deletePermissionApi(row.id!);
|
||||
message.success('删除成功');
|
||||
GridApi.reload();
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page auto-content-height>
|
||||
<FormModal>
|
||||
<Form />
|
||||
</FormModal>
|
||||
|
||||
<Card class="mb-4">
|
||||
<template #extra>
|
||||
<Button type="primary" @click="openCreate">新增权限</Button>
|
||||
</template>
|
||||
<template #title>单点权限管理</template>
|
||||
</Card>
|
||||
|
||||
<Grid>
|
||||
<template #methodSlot="{ row }">
|
||||
<Tag v-if="row.http_method" :color="METHOD_COLORS[row.http_method] ?? 'default'">
|
||||
{{ row.http_method }}
|
||||
</Tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
<template #statusSlot="{ row }">
|
||||
<Tag :color="row.status === 1 ? 'green' : 'red'">
|
||||
{{ row.status === 1 ? '启用' : '停用' }}
|
||||
</Tag>
|
||||
</template>
|
||||
<template #actionSlot="{ row }">
|
||||
<Space>
|
||||
<Button size="small" type="primary" @click="openEdit(row)">编辑</Button>
|
||||
<Button danger size="small" @click="handleDelete(row)">删除</Button>
|
||||
</Space>
|
||||
</template>
|
||||
</Grid>
|
||||
</Page>
|
||||
</template>
|
||||
224
apps/web-antd/src/views/admin/permission/role.vue
Normal file
224
apps/web-antd/src/views/admin/permission/role.vue
Normal file
@ -0,0 +1,224 @@
|
||||
<script setup lang="ts">
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { Page } from '@vben/common-ui';
|
||||
import {
|
||||
getRoleListApi,
|
||||
addRoleApi,
|
||||
editRoleApi,
|
||||
deleteRoleApi,
|
||||
getPermissionListApi,
|
||||
getRolePermissionsApi,
|
||||
setRolePermissionsApi,
|
||||
} from '#/api/core/permission';
|
||||
import type { PermissionApi } from '#/api/core/permission';
|
||||
import {
|
||||
Button, Card, Space, Tag, Modal, message,
|
||||
Form, FormItem, Input, Textarea, Select, Spin, Tabs, TabPane, Transfer,
|
||||
} from 'ant-design-vue';
|
||||
import { ref, computed } from 'vue';
|
||||
|
||||
// ─── 弹窗状态 ─────────────────────────────────────────────
|
||||
const modalOpen = ref(false);
|
||||
const modalTitle = ref('新增权限组');
|
||||
const modalLoading = ref(false);
|
||||
const activeTab = ref('basic');
|
||||
|
||||
const editingId = ref<number | undefined>(undefined);
|
||||
const formState = ref({ role_code: '', role_name: '', status: 1 as 0 | 1, remark: '' });
|
||||
|
||||
// 单点权限 Transfer
|
||||
const allPermissions = ref<PermissionApi.Permission[]>([]);
|
||||
const selectedPermissionIds = ref<string[]>([]);
|
||||
const permissionTransferData = computed(() =>
|
||||
allPermissions.value.map((p) => ({
|
||||
key: String(p.id),
|
||||
title: `${p.permission_name}(${p.permission_code})`,
|
||||
description: p.api_path ? `${p.http_method ?? ''} ${p.api_path}` : '',
|
||||
disabled: p.status !== 1,
|
||||
})),
|
||||
);
|
||||
|
||||
async function openCreate() {
|
||||
editingId.value = undefined;
|
||||
formState.value = { role_code: '', role_name: '', status: 1, remark: '' };
|
||||
selectedPermissionIds.value = [];
|
||||
activeTab.value = 'basic';
|
||||
modalTitle.value = '新增权限组';
|
||||
modalLoading.value = true;
|
||||
modalOpen.value = true;
|
||||
try {
|
||||
const res = await getPermissionListApi({ page: 1, pageSize: 500 });
|
||||
allPermissions.value = res.items || [];
|
||||
} finally {
|
||||
modalLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function openEdit(row: PermissionApi.Role) {
|
||||
editingId.value = row.id;
|
||||
formState.value = { role_code: row.role_code, role_name: row.role_name, status: row.status as 0 | 1, remark: row.remark || '' };
|
||||
selectedPermissionIds.value = [];
|
||||
activeTab.value = 'basic';
|
||||
modalTitle.value = '编辑权限组';
|
||||
modalLoading.value = true;
|
||||
modalOpen.value = true;
|
||||
try {
|
||||
const [permsRes, rolePerms] = await Promise.all([
|
||||
getPermissionListApi({ page: 1, pageSize: 500 }),
|
||||
getRolePermissionsApi(row.id!),
|
||||
]);
|
||||
allPermissions.value = permsRes.items || [];
|
||||
selectedPermissionIds.value = (rolePerms || []).map((p) => String(p.id));
|
||||
} finally {
|
||||
modalLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleConfirm() {
|
||||
if (!formState.value.role_code || !formState.value.role_name) {
|
||||
message.warning('请填写角色编码和名称');
|
||||
activeTab.value = 'basic';
|
||||
return;
|
||||
}
|
||||
modalLoading.value = true;
|
||||
try {
|
||||
const params: PermissionApi.Role = {
|
||||
id: editingId.value,
|
||||
role_code: formState.value.role_code,
|
||||
role_name: formState.value.role_name,
|
||||
status: formState.value.status,
|
||||
remark: formState.value.remark,
|
||||
};
|
||||
let roleId = editingId.value;
|
||||
if (editingId.value) {
|
||||
await editRoleApi(params);
|
||||
message.success('更新成功');
|
||||
} else {
|
||||
const res: any = await addRoleApi(params);
|
||||
roleId = res?.id ?? res;
|
||||
message.success('新增成功');
|
||||
}
|
||||
if (roleId) {
|
||||
await setRolePermissionsApi({ role_id: roleId, permission_ids: selectedPermissionIds.value.map(Number) });
|
||||
}
|
||||
modalOpen.value = false;
|
||||
GridApi.reload();
|
||||
} finally {
|
||||
modalLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── 表格 ──────────────────────────────────────────────────
|
||||
const gridOptions: VxeGridProps<PermissionApi.Role> = {
|
||||
columns: [
|
||||
{ field: 'id', title: 'ID', width: 80 },
|
||||
{ field: 'role_code', title: '角色编码', width: 160 },
|
||||
{ field: 'role_name', title: '角色名称', minWidth: 150 },
|
||||
{ field: 'is_system', title: '类型', width: 100, slots: { default: 'isSystemSlot' } },
|
||||
{ field: 'status', title: '状态', width: 100, slots: { default: 'statusSlot' } },
|
||||
{ field: 'remark', title: '备注', minWidth: 200 },
|
||||
{ fixed: 'right', slots: { default: 'actionSlot' }, title: '操作', width: 160 },
|
||||
],
|
||||
height: 'auto',
|
||||
pagerConfig: {},
|
||||
proxyConfig: {
|
||||
response: { result: 'items', total: 'total' },
|
||||
ajax: {
|
||||
query: async ({ page }) =>
|
||||
getRoleListApi({ page: page.currentPage, pageSize: page.pageSize }),
|
||||
},
|
||||
},
|
||||
rowConfig: { isHover: true },
|
||||
};
|
||||
|
||||
const [Grid, GridApi] = useVbenVxeGrid({ gridOptions });
|
||||
|
||||
function handleDelete(row: PermissionApi.Role) {
|
||||
Modal.confirm({
|
||||
title: '删除确认',
|
||||
content: `确认删除权限组「${row.role_name}」吗?系统内置角色无法删除。`,
|
||||
async onOk() {
|
||||
await deleteRoleApi(row.id!);
|
||||
message.success('删除成功');
|
||||
GridApi.reload();
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page auto-content-height>
|
||||
<!-- 新增/编辑弹窗 -->
|
||||
<Modal
|
||||
v-model:open="modalOpen"
|
||||
:confirm-loading="modalLoading"
|
||||
:title="modalTitle"
|
||||
width="760px"
|
||||
@ok="handleConfirm"
|
||||
>
|
||||
<Spin :spinning="modalLoading">
|
||||
<Tabs v-model:activeKey="activeTab">
|
||||
<TabPane key="basic" tab="基本信息">
|
||||
<Form layout="vertical" class="pt-3">
|
||||
<FormItem label="角色编码" required>
|
||||
<Input v-model:value="formState.role_code" placeholder="例如:R_ACTIVITY" />
|
||||
</FormItem>
|
||||
<FormItem label="角色名称" required>
|
||||
<Input v-model:value="formState.role_name" placeholder="例如:活动管理员" />
|
||||
</FormItem>
|
||||
<FormItem label="状态">
|
||||
<Select
|
||||
v-model:value="formState.status"
|
||||
:options="[{ label: '启用', value: 1 }, { label: '停用', value: 0 }]"
|
||||
class="w-full"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="备注">
|
||||
<Textarea v-model:value="formState.remark" :rows="3" />
|
||||
</FormItem>
|
||||
</Form>
|
||||
</TabPane>
|
||||
|
||||
<TabPane key="permissions" tab="单点权限配置">
|
||||
<p class="mb-3 text-gray-500 text-sm">为该权限组(角色)绑定单点权限:</p>
|
||||
<Transfer
|
||||
v-model:target-keys="selectedPermissionIds"
|
||||
:data-source="permissionTransferData"
|
||||
:render="(item: any) => item.title"
|
||||
:titles="['可选权限', '已选权限']"
|
||||
show-search
|
||||
:list-style="{ width: '100%', height: '320px' }"
|
||||
/>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</Spin>
|
||||
</Modal>
|
||||
|
||||
<Card class="mb-4">
|
||||
<template #extra>
|
||||
<Button type="primary" @click="openCreate">新增权限组</Button>
|
||||
</template>
|
||||
<template #title>权限组(角色)管理</template>
|
||||
</Card>
|
||||
|
||||
<Grid>
|
||||
<template #isSystemSlot="{ row }">
|
||||
<Tag :color="row.is_system === 1 ? 'blue' : 'default'">
|
||||
{{ row.is_system === 1 ? '系统内置' : '自定义' }}
|
||||
</Tag>
|
||||
</template>
|
||||
<template #statusSlot="{ row }">
|
||||
<Tag :color="row.status === 1 ? 'green' : 'red'">
|
||||
{{ row.status === 1 ? '启用' : '停用' }}
|
||||
</Tag>
|
||||
</template>
|
||||
<template #actionSlot="{ row }">
|
||||
<Space>
|
||||
<Button size="small" type="primary" @click="openEdit(row)">编辑</Button>
|
||||
<Button danger size="small" :disabled="row.is_system === 1" @click="handleDelete(row)">删除</Button>
|
||||
</Space>
|
||||
</template>
|
||||
</Grid>
|
||||
</Page>
|
||||
</template>
|
||||
240
apps/web-antd/src/views/admin/permission/user-assign.vue
Normal file
240
apps/web-antd/src/views/admin/permission/user-assign.vue
Normal file
@ -0,0 +1,240 @@
|
||||
<script setup lang="ts">
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { Page } from '@vben/common-ui';
|
||||
import { getAdminListApi } from '#/api/core/admin.user';
|
||||
import {
|
||||
getUserGroupListApi,
|
||||
getUserGroupsApi,
|
||||
setUserGroupsApi,
|
||||
getUserRolesApi,
|
||||
getPermissionListApi,
|
||||
getUserPermissionsDirectApi,
|
||||
setUserPermissionsDirectApi,
|
||||
} from '#/api/core/permission';
|
||||
import type { PermissionApi } from '#/api/core/permission';
|
||||
import type { UserInfo } from '#/model/admin.user';
|
||||
import {
|
||||
Button, Card, Space, Tag, Spin, message,
|
||||
Modal, Tabs, TabPane, Transfer,
|
||||
} from 'ant-design-vue';
|
||||
import { ref, computed } from 'vue';
|
||||
|
||||
// ─── 行级缓存(用户组、角色)─────────────────────────────
|
||||
const userGroupsMap = ref<Map<number, PermissionApi.UserGroup[]>>(new Map());
|
||||
const userRolesMap = ref<Map<number, PermissionApi.Role[]>>(new Map());
|
||||
|
||||
async function loadRowDataForList(users: UserInfo[]) {
|
||||
const ids = users.map((u) => u.id).filter((id): id is number => id != null);
|
||||
const [groupResults, roleResults] = await Promise.all([
|
||||
Promise.allSettled(ids.map((id) => getUserGroupsApi(id))),
|
||||
Promise.allSettled(ids.map((id) => getUserRolesApi(id))),
|
||||
]);
|
||||
const gMap = new Map<number, PermissionApi.UserGroup[]>();
|
||||
const rMap = new Map<number, PermissionApi.Role[]>();
|
||||
ids.forEach((id, i) => {
|
||||
if (groupResults[i]!.status === 'fulfilled') gMap.set(id, (groupResults[i] as any).value || []);
|
||||
if (roleResults[i]!.status === 'fulfilled') rMap.set(id, (roleResults[i] as any).value || []);
|
||||
});
|
||||
userGroupsMap.value = gMap;
|
||||
userRolesMap.value = rMap;
|
||||
}
|
||||
|
||||
// ─── 分配弹窗 ─────────────────────────────────────────────
|
||||
const modalOpen = ref(false);
|
||||
const modalLoading = ref(false);
|
||||
const activeTab = ref('groups');
|
||||
const currentAdminId = ref<number | null>(null);
|
||||
const currentAdminName = ref('');
|
||||
|
||||
// 用户组 Transfer
|
||||
const allGroups = ref<PermissionApi.UserGroup[]>([]);
|
||||
const selectedGroupIds = ref<string[]>([]);
|
||||
const groupTransferData = computed(() =>
|
||||
allGroups.value.map((g) => ({
|
||||
key: String(g.id),
|
||||
title: `${g.group_name}(${g.group_code})`,
|
||||
disabled: g.status !== 1,
|
||||
})),
|
||||
);
|
||||
|
||||
// 单点权限 Transfer(allow/deny 两个列表,以 allow 为主)
|
||||
const allPermissions = ref<PermissionApi.Permission[]>([]);
|
||||
const selectedAllowPermIds = ref<string[]>([]);
|
||||
const selectedDenyPermIds = ref<string[]>([]);
|
||||
const permissionTransferData = computed(() =>
|
||||
allPermissions.value.map((p) => ({
|
||||
key: String(p.id),
|
||||
title: `${p.permission_name}(${p.permission_code})`,
|
||||
description: p.api_path ? `${p.http_method ?? ''} ${p.api_path}` : '',
|
||||
disabled: p.status !== 1,
|
||||
})),
|
||||
);
|
||||
|
||||
async function openAssignModal(row: UserInfo) {
|
||||
if (!row.id) {
|
||||
message.warning('该用户缺少 ID,无法分配权限');
|
||||
return;
|
||||
}
|
||||
currentAdminId.value = row.id;
|
||||
currentAdminName.value = row.username;
|
||||
activeTab.value = 'groups';
|
||||
modalLoading.value = true;
|
||||
modalOpen.value = true;
|
||||
try {
|
||||
const [groupsRes, permsRes, userGroups, userDirectPerms] = await Promise.all([
|
||||
getUserGroupListApi({ page: 1, pageSize: 500 }),
|
||||
getPermissionListApi({ page: 1, pageSize: 500 }),
|
||||
getUserGroupsApi(row.id),
|
||||
getUserPermissionsDirectApi(row.id),
|
||||
]);
|
||||
allGroups.value = groupsRes.items || [];
|
||||
allPermissions.value = permsRes.items || [];
|
||||
selectedGroupIds.value = (userGroups || []).map((g) => String(g.id));
|
||||
const directPerms = userDirectPerms || [];
|
||||
selectedAllowPermIds.value = directPerms.filter((p) => p.grant_type === 1).map((p) => String(p.permission_id));
|
||||
selectedDenyPermIds.value = directPerms.filter((p) => p.grant_type === 2).map((p) => String(p.permission_id));
|
||||
} catch {
|
||||
message.error('加载权限数据失败');
|
||||
} finally {
|
||||
modalLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleConfirm() {
|
||||
if (currentAdminId.value == null) return;
|
||||
modalLoading.value = true;
|
||||
try {
|
||||
const allowPerms = selectedAllowPermIds.value.map((id) => ({ permission_id: Number(id), grant_type: 1 as const }));
|
||||
const denyPerms = selectedDenyPermIds.value.map((id) => ({ permission_id: Number(id), grant_type: 2 as const }));
|
||||
await Promise.all([
|
||||
setUserGroupsApi({ admin_id: currentAdminId.value, group_ids: selectedGroupIds.value.map(Number) }),
|
||||
setUserPermissionsDirectApi({ admin_id: currentAdminId.value, permissions: [...allowPerms, ...denyPerms] }),
|
||||
]);
|
||||
message.success('权限分配成功');
|
||||
modalOpen.value = false;
|
||||
// 刷新当前行缓存
|
||||
const [groups, roles] = await Promise.all([
|
||||
getUserGroupsApi(currentAdminId.value),
|
||||
getUserRolesApi(currentAdminId.value),
|
||||
]);
|
||||
userGroupsMap.value = new Map(userGroupsMap.value).set(currentAdminId.value, groups || []);
|
||||
userRolesMap.value = new Map(userRolesMap.value).set(currentAdminId.value, roles || []);
|
||||
} finally {
|
||||
modalLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── 表格 ──────────────────────────────────────────────────
|
||||
const gridOptions: VxeGridProps<UserInfo> = {
|
||||
columns: [
|
||||
{ field: 'id', title: 'ID', width: 80 },
|
||||
{ field: 'username', title: '用户名', width: 160 },
|
||||
{ field: 'phone', title: '手机号', width: 140 },
|
||||
{ field: 'email', title: '邮箱', minWidth: 160 },
|
||||
{ field: 'userGroups', title: '用户组', minWidth: 200, slots: { default: 'userGroupsSlot' } },
|
||||
{ field: 'userRoles', title: '权限组(角色)', minWidth: 200, slots: { default: 'userRolesSlot' } },
|
||||
{ fixed: 'right', slots: { default: 'actionSlot' }, title: '操作', width: 120 },
|
||||
],
|
||||
height: '100%',
|
||||
pagerConfig: {},
|
||||
proxyConfig: {
|
||||
response: { total: 'total', result: 'data' },
|
||||
ajax: {
|
||||
query: async () => {
|
||||
const result = await getAdminListApi();
|
||||
const list: UserInfo[] = Array.isArray(result) ? result : ((result as any)?.data ?? []);
|
||||
void loadRowDataForList(list);
|
||||
return result;
|
||||
},
|
||||
},
|
||||
},
|
||||
rowConfig: { isHover: true },
|
||||
};
|
||||
|
||||
const [Grid, GridApi] = useVbenVxeGrid({ gridOptions });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page auto-content-height>
|
||||
<!-- 分配弹窗 -->
|
||||
<Modal
|
||||
v-model:open="modalOpen"
|
||||
:confirm-loading="modalLoading"
|
||||
:title="`权限分配 — ${currentAdminName}`"
|
||||
width="800px"
|
||||
@ok="handleConfirm"
|
||||
>
|
||||
<Spin :spinning="modalLoading">
|
||||
<Tabs v-model:activeKey="activeTab">
|
||||
<!-- 用户组分配 -->
|
||||
<TabPane key="groups" tab="用户组分配">
|
||||
<p class="mb-3 text-gray-500 text-sm">将用户组分配给该管理员(继承用户组内的权限组):</p>
|
||||
<Transfer
|
||||
v-model:target-keys="selectedGroupIds"
|
||||
:data-source="groupTransferData"
|
||||
:render="(item: any) => item.title"
|
||||
:titles="['可选用户组', '已选用户组']"
|
||||
show-search
|
||||
:list-style="{ width: '100%', height: '280px' }"
|
||||
/>
|
||||
</TabPane>
|
||||
|
||||
<!-- 单点权限:允许 -->
|
||||
<TabPane key="allow" tab="单点权限(允许)">
|
||||
<p class="mb-3 text-gray-500 text-sm">直接授予该用户的单点权限(grant_type=1,优先级高于角色):</p>
|
||||
<Transfer
|
||||
v-model:target-keys="selectedAllowPermIds"
|
||||
:data-source="permissionTransferData"
|
||||
:render="(item: any) => item.title"
|
||||
:titles="['可选权限', '已允许']"
|
||||
show-search
|
||||
:list-style="{ width: '100%', height: '280px' }"
|
||||
/>
|
||||
</TabPane>
|
||||
|
||||
<!-- 单点权限:拒绝 -->
|
||||
<TabPane key="deny" tab="单点权限(拒绝)">
|
||||
<p class="mb-3 text-gray-500 text-sm">明确拒绝该用户的单点权限(grant_type=2,可覆盖角色授权):</p>
|
||||
<Transfer
|
||||
v-model:target-keys="selectedDenyPermIds"
|
||||
:data-source="permissionTransferData"
|
||||
:render="(item: any) => item.title"
|
||||
:titles="['可选权限', '已拒绝']"
|
||||
show-search
|
||||
:list-style="{ width: '100%', height: '280px' }"
|
||||
/>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</Spin>
|
||||
</Modal>
|
||||
|
||||
<Card class="mb-4" title="用户权限分配">
|
||||
<template #extra>
|
||||
<Button @click="GridApi.reload()">刷新</Button>
|
||||
</template>
|
||||
</Card>
|
||||
|
||||
<Grid>
|
||||
<template #userGroupsSlot="{ row }">
|
||||
<Space wrap>
|
||||
<template v-if="row.id && userGroupsMap.get(row.id)?.length">
|
||||
<Tag v-for="g in userGroupsMap.get(row.id)" :key="g.id" color="blue">{{ g.group_name }}</Tag>
|
||||
</template>
|
||||
<span v-else class="text-gray-400 text-xs">未分配</span>
|
||||
</Space>
|
||||
</template>
|
||||
<template #userRolesSlot="{ row }">
|
||||
<Space wrap>
|
||||
<template v-if="row.id && userRolesMap.get(row.id)?.length">
|
||||
<Tag v-for="r in userRolesMap.get(row.id)" :key="r.id" color="purple">{{ r.role_name }}</Tag>
|
||||
</template>
|
||||
<span v-else class="text-gray-400 text-xs">无</span>
|
||||
</Space>
|
||||
</template>
|
||||
<template #actionSlot="{ row }">
|
||||
<Button size="small" type="primary" @click="openAssignModal(row)">分配权限</Button>
|
||||
</template>
|
||||
</Grid>
|
||||
</Page>
|
||||
</template>
|
||||
251
apps/web-antd/src/views/admin/permission/user-group.vue
Normal file
251
apps/web-antd/src/views/admin/permission/user-group.vue
Normal file
@ -0,0 +1,251 @@
|
||||
<script setup lang="ts">
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { Page } from '@vben/common-ui';
|
||||
import {
|
||||
getUserGroupListApi,
|
||||
addUserGroupApi,
|
||||
editUserGroupApi,
|
||||
deleteUserGroupApi,
|
||||
getRoleListApi,
|
||||
getGroupRolesApi,
|
||||
setGroupRolesApi,
|
||||
getPermissionListApi,
|
||||
} from '#/api/core/permission';
|
||||
import type { PermissionApi } from '#/api/core/permission';
|
||||
import {
|
||||
Button, Card, Space, Tag, Modal, message,
|
||||
Form, FormItem, Input, Textarea, Select, Spin, Tabs, TabPane, Transfer,
|
||||
} from 'ant-design-vue';
|
||||
import { ref, computed } from 'vue';
|
||||
|
||||
// ─── 弹窗状态 ─────────────────────────────────────────────
|
||||
const modalOpen = ref(false);
|
||||
const modalTitle = ref('新增用户组');
|
||||
const modalLoading = ref(false);
|
||||
const activeTab = ref('basic');
|
||||
|
||||
// 基本信息表单
|
||||
const editingId = ref<number | undefined>(undefined);
|
||||
const formState = ref({ group_code: '', group_name: '', status: 1 as 0 | 1, remark: '' });
|
||||
|
||||
// 权限组(角色)Transfer
|
||||
const allRoles = ref<PermissionApi.Role[]>([]);
|
||||
const selectedRoleIds = ref<string[]>([]);
|
||||
const roleTransferData = computed(() =>
|
||||
allRoles.value.map((r) => ({
|
||||
key: String(r.id),
|
||||
title: `${r.role_name}(${r.role_code})`,
|
||||
disabled: r.status !== 1,
|
||||
})),
|
||||
);
|
||||
|
||||
// 单点权限 Transfer
|
||||
const allPermissions = ref<PermissionApi.Permission[]>([]);
|
||||
const selectedPermissionIds = ref<string[]>([]);
|
||||
const permissionTransferData = computed(() =>
|
||||
allPermissions.value.map((p) => ({
|
||||
key: String(p.id),
|
||||
title: `${p.permission_name}(${p.permission_code})`,
|
||||
description: p.api_path ? `${p.http_method ?? ''} ${p.api_path}` : '',
|
||||
disabled: p.status !== 1,
|
||||
})),
|
||||
);
|
||||
|
||||
async function openCreate() {
|
||||
editingId.value = undefined;
|
||||
formState.value = { group_code: '', group_name: '', status: 1, remark: '' };
|
||||
selectedRoleIds.value = [];
|
||||
selectedPermissionIds.value = [];
|
||||
activeTab.value = 'basic';
|
||||
modalTitle.value = '新增用户组';
|
||||
modalLoading.value = true;
|
||||
modalOpen.value = true;
|
||||
try {
|
||||
const [rolesRes, permsRes] = await Promise.all([
|
||||
getRoleListApi({ page: 1, pageSize: 500 }),
|
||||
getPermissionListApi({ page: 1, pageSize: 500 }),
|
||||
]);
|
||||
allRoles.value = rolesRes.items || [];
|
||||
allPermissions.value = permsRes.items || [];
|
||||
} finally {
|
||||
modalLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function openEdit(row: PermissionApi.UserGroup) {
|
||||
editingId.value = row.id;
|
||||
formState.value = { group_code: row.group_code, group_name: row.group_name, status: row.status as 0 | 1, remark: row.remark || '' };
|
||||
selectedRoleIds.value = [];
|
||||
selectedPermissionIds.value = [];
|
||||
activeTab.value = 'basic';
|
||||
modalTitle.value = '编辑用户组';
|
||||
modalLoading.value = true;
|
||||
modalOpen.value = true;
|
||||
try {
|
||||
const [rolesRes, permsRes, groupRoles] = await Promise.all([
|
||||
getRoleListApi({ page: 1, pageSize: 500 }),
|
||||
getPermissionListApi({ page: 1, pageSize: 500 }),
|
||||
getGroupRolesApi(row.id!),
|
||||
]);
|
||||
allRoles.value = rolesRes.items || [];
|
||||
allPermissions.value = permsRes.items || [];
|
||||
selectedRoleIds.value = (groupRoles || []).map((r) => String(r.id));
|
||||
} finally {
|
||||
modalLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleConfirm() {
|
||||
if (!formState.value.group_code || !formState.value.group_name) {
|
||||
message.warning('请填写用户组编码和名称');
|
||||
activeTab.value = 'basic';
|
||||
return;
|
||||
}
|
||||
modalLoading.value = true;
|
||||
try {
|
||||
const params: PermissionApi.UserGroup = {
|
||||
id: editingId.value,
|
||||
group_code: formState.value.group_code,
|
||||
group_name: formState.value.group_name,
|
||||
status: formState.value.status,
|
||||
remark: formState.value.remark,
|
||||
};
|
||||
let groupId = editingId.value;
|
||||
if (editingId.value) {
|
||||
await editUserGroupApi(params);
|
||||
message.success('更新成功');
|
||||
} else {
|
||||
const res: any = await addUserGroupApi(params);
|
||||
groupId = res?.id ?? res;
|
||||
message.success('新增成功');
|
||||
}
|
||||
if (groupId) {
|
||||
await setGroupRolesApi({ group_id: groupId, role_ids: selectedRoleIds.value.map(Number) });
|
||||
}
|
||||
modalOpen.value = false;
|
||||
GridApi.reload();
|
||||
} finally {
|
||||
modalLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── 表格 ──────────────────────────────────────────────────
|
||||
const gridOptions: VxeGridProps<PermissionApi.UserGroup> = {
|
||||
columns: [
|
||||
{ field: 'id', title: 'ID', width: 80 },
|
||||
{ field: 'group_code', title: '用户组编码', width: 150 },
|
||||
{ field: 'group_name', title: '用户组名称', minWidth: 150 },
|
||||
{ field: 'status', title: '状态', width: 100, slots: { default: 'statusSlot' } },
|
||||
{ field: 'remark', title: '备注', minWidth: 200 },
|
||||
{ fixed: 'right', slots: { default: 'actionSlot' }, title: '操作', width: 160 },
|
||||
],
|
||||
height: 'auto',
|
||||
pagerConfig: {},
|
||||
proxyConfig: {
|
||||
response: { result: 'items', total: 'total' },
|
||||
ajax: {
|
||||
query: async ({ page }) =>
|
||||
getUserGroupListApi({ page: page.currentPage, pageSize: page.pageSize }),
|
||||
},
|
||||
},
|
||||
rowConfig: { isHover: true },
|
||||
};
|
||||
|
||||
const [Grid, GridApi] = useVbenVxeGrid({ gridOptions });
|
||||
|
||||
function handleDelete(row: PermissionApi.UserGroup) {
|
||||
Modal.confirm({
|
||||
title: '删除确认',
|
||||
content: `确认删除用户组「${row.group_name}」吗?`,
|
||||
async onOk() {
|
||||
await deleteUserGroupApi(row.id!);
|
||||
message.success('删除成功');
|
||||
GridApi.reload();
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page auto-content-height>
|
||||
<!-- 新增/编辑弹窗 -->
|
||||
<Modal
|
||||
v-model:open="modalOpen"
|
||||
:confirm-loading="modalLoading"
|
||||
:title="modalTitle"
|
||||
width="760px"
|
||||
@ok="handleConfirm"
|
||||
>
|
||||
<Spin :spinning="modalLoading">
|
||||
<Tabs v-model:activeKey="activeTab">
|
||||
<TabPane key="basic" tab="基本信息">
|
||||
<Form layout="vertical" class="pt-3">
|
||||
<FormItem label="用户组编码" required>
|
||||
<Input v-model:value="formState.group_code" placeholder="例如:G_OP" />
|
||||
</FormItem>
|
||||
<FormItem label="用户组名称" required>
|
||||
<Input v-model:value="formState.group_name" placeholder="例如:运营组" />
|
||||
</FormItem>
|
||||
<FormItem label="状态">
|
||||
<Select
|
||||
v-model:value="formState.status"
|
||||
:options="[{ label: '启用', value: 1 }, { label: '停用', value: 0 }]"
|
||||
class="w-full"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="备注">
|
||||
<Textarea v-model:value="formState.remark" :rows="3" />
|
||||
</FormItem>
|
||||
</Form>
|
||||
</TabPane>
|
||||
|
||||
<TabPane key="roles" tab="权限组配置">
|
||||
<p class="mb-3 text-gray-500 text-sm">将权限组(角色)绑定到该用户组:</p>
|
||||
<Transfer
|
||||
v-model:target-keys="selectedRoleIds"
|
||||
:data-source="roleTransferData"
|
||||
:render="(item: any) => item.title"
|
||||
:titles="['可选权限组', '已选权限组']"
|
||||
show-search
|
||||
:list-style="{ width: '100%', height: '280px' }"
|
||||
/>
|
||||
</TabPane>
|
||||
|
||||
<TabPane key="permissions" tab="单点权限配置">
|
||||
<p class="mb-3 text-gray-500 text-sm">直接为该用户组绑定单点权限(优先级高于角色):</p>
|
||||
<Transfer
|
||||
v-model:target-keys="selectedPermissionIds"
|
||||
:data-source="permissionTransferData"
|
||||
:render="(item: any) => item.title"
|
||||
:titles="['可选权限', '已选权限']"
|
||||
show-search
|
||||
:list-style="{ width: '100%', height: '280px' }"
|
||||
/>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</Spin>
|
||||
</Modal>
|
||||
|
||||
<Card class="mb-4">
|
||||
<template #extra>
|
||||
<Button type="primary" @click="openCreate">新增用户组</Button>
|
||||
</template>
|
||||
<template #title>用户组管理</template>
|
||||
</Card>
|
||||
|
||||
<Grid>
|
||||
<template #statusSlot="{ row }">
|
||||
<Tag :color="row.status === 1 ? 'green' : 'red'">
|
||||
{{ row.status === 1 ? '启用' : '停用' }}
|
||||
</Tag>
|
||||
</template>
|
||||
<template #actionSlot="{ row }">
|
||||
<Space>
|
||||
<Button size="small" type="primary" @click="openEdit(row)">编辑</Button>
|
||||
<Button danger size="small" @click="handleDelete(row)">删除</Button>
|
||||
</Space>
|
||||
</template>
|
||||
</Grid>
|
||||
</Page>
|
||||
</template>
|
||||
@ -62,8 +62,8 @@ const sectionTitles: Record<SectionKey, string> = {
|
||||
inactiveNotis: 'Inactive Notis',
|
||||
};
|
||||
|
||||
const DOTNET_TICKS_AT_UNIX_EPOCH = 621355968000000000n;
|
||||
const TICKS_PER_MILLISECOND = 10000n;
|
||||
const DOTNET_TICKS_AT_UNIX_EPOCH = 621355968000000000;
|
||||
const TICKS_PER_MILLISECOND = 10000;
|
||||
|
||||
function cloneValue<T>(value: T): T {
|
||||
return JSON.parse(JSON.stringify(value)) as T;
|
||||
@ -79,8 +79,8 @@ function parseDotNetTicks(value?: number | string) {
|
||||
}
|
||||
|
||||
try {
|
||||
const ticks = BigInt(String(value));
|
||||
const unixMilliseconds = Number((ticks - DOTNET_TICKS_AT_UNIX_EPOCH) / TICKS_PER_MILLISECOND);
|
||||
const ticks = Number(String(value));
|
||||
const unixMilliseconds = (ticks - DOTNET_TICKS_AT_UNIX_EPOCH) / TICKS_PER_MILLISECOND;
|
||||
const date = dayjs(unixMilliseconds);
|
||||
return date.isValid() ? date : null;
|
||||
} catch {
|
||||
|
||||
@ -4,6 +4,9 @@ import { compilerOptions } from 'vue3-pixi';
|
||||
|
||||
export default defineConfig(async () => {
|
||||
return {
|
||||
build:{
|
||||
target: 'esnext',
|
||||
},
|
||||
plugins: [
|
||||
vue({
|
||||
template: {
|
||||
|
||||
10
apps/web-antd/web-antd.code-workspace
Normal file
10
apps/web-antd/web-antd.code-workspace
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
},
|
||||
{
|
||||
"path": "../../../admin_backend"
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user