活动优化
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",
|
"title": "Admin",
|
||||||
"user": "User",
|
"user": "User",
|
||||||
"setting": "Setting",
|
"setting": "Setting",
|
||||||
"log": "Log"
|
"log": "Log",
|
||||||
|
"config": "Config",
|
||||||
|
"permission": {
|
||||||
|
"userGroup": "User Groups",
|
||||||
|
"role": "Roles",
|
||||||
|
"permission": "Permissions",
|
||||||
|
"userAssign": "User Assignment"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
"title": "Dashboard",
|
"title": "Dashboard",
|
||||||
|
|||||||
@ -31,7 +31,13 @@
|
|||||||
"user": "用户管理",
|
"user": "用户管理",
|
||||||
"setting": "系统设置",
|
"setting": "系统设置",
|
||||||
"log": "操作日志",
|
"log": "操作日志",
|
||||||
"config": "配置管理"
|
"config": "配置管理",
|
||||||
|
"permission": {
|
||||||
|
"userGroup": "用户组管理",
|
||||||
|
"role": "权限组管理",
|
||||||
|
"permission": "单点权限管理",
|
||||||
|
"userAssign": "用户权限分配"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
"title": "运维管理",
|
"title": "运维管理",
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
export interface UserInfo {
|
export interface UserInfo {
|
||||||
|
id?: number;
|
||||||
username: string;
|
username: string;
|
||||||
password?: string;
|
password?: string;
|
||||||
phone: string;
|
phone: string;
|
||||||
|
|||||||
@ -48,6 +48,50 @@ const routes: RouteRecordRaw[] = [
|
|||||||
title: $t('page.admin.config'),
|
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',
|
inactiveNotis: 'Inactive Notis',
|
||||||
};
|
};
|
||||||
|
|
||||||
const DOTNET_TICKS_AT_UNIX_EPOCH = 621355968000000000n;
|
const DOTNET_TICKS_AT_UNIX_EPOCH = 621355968000000000;
|
||||||
const TICKS_PER_MILLISECOND = 10000n;
|
const TICKS_PER_MILLISECOND = 10000;
|
||||||
|
|
||||||
function cloneValue<T>(value: T): T {
|
function cloneValue<T>(value: T): T {
|
||||||
return JSON.parse(JSON.stringify(value)) as T;
|
return JSON.parse(JSON.stringify(value)) as T;
|
||||||
@ -79,8 +79,8 @@ function parseDotNetTicks(value?: number | string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const ticks = BigInt(String(value));
|
const ticks = Number(String(value));
|
||||||
const unixMilliseconds = Number((ticks - DOTNET_TICKS_AT_UNIX_EPOCH) / TICKS_PER_MILLISECOND);
|
const unixMilliseconds = (ticks - DOTNET_TICKS_AT_UNIX_EPOCH) / TICKS_PER_MILLISECOND;
|
||||||
const date = dayjs(unixMilliseconds);
|
const date = dayjs(unixMilliseconds);
|
||||||
return date.isValid() ? date : null;
|
return date.isValid() ? date : null;
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@ -4,6 +4,9 @@ import { compilerOptions } from 'vue3-pixi';
|
|||||||
|
|
||||||
export default defineConfig(async () => {
|
export default defineConfig(async () => {
|
||||||
return {
|
return {
|
||||||
|
build:{
|
||||||
|
target: 'esnext',
|
||||||
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
vue({
|
vue({
|
||||||
template: {
|
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