refactor: ♻️ 优化CURD组件权限处理
将权限属性名从 auth 统一修改为 perm,保持命名一致性 更新 permPrefix 替代 pageName 作为权限前缀标识 优化权限检查逻辑,未设置权限标识的按钮默认显示
This commit is contained in:
@@ -6,7 +6,7 @@
|
|||||||
<div class="toolbar-left flex gap-y-2.5 gap-x-2 md:gap-x-3 flex-wrap">
|
<div class="toolbar-left flex gap-y-2.5 gap-x-2 md:gap-x-3 flex-wrap">
|
||||||
<template v-for="btn in toolbarLeftBtn">
|
<template v-for="btn in toolbarLeftBtn">
|
||||||
<el-button
|
<el-button
|
||||||
v-hasPerm="authName ? `${authName}:${btn.auth}` : '*:*:*'"
|
v-hasPerm="btn.perm ?? '*:*:*'"
|
||||||
:disabled="btn.name === 'delete' && removeIds.length === 0"
|
:disabled="btn.name === 'delete' && removeIds.length === 0"
|
||||||
v-bind="btn.attrs"
|
v-bind="btn.attrs"
|
||||||
@click="handleToolbar(btn.name)"
|
@click="handleToolbar(btn.name)"
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
</el-popover>
|
</el-popover>
|
||||||
<el-button
|
<el-button
|
||||||
v-else
|
v-else
|
||||||
v-hasPerm="authName ? `${authName}:${btn.auth}` : '*:*:*'"
|
v-hasPerm="btn.perm ?? '*:*:*'"
|
||||||
v-bind="btn.attrs"
|
v-bind="btn.attrs"
|
||||||
@click="handleToolbar(btn.name)"
|
@click="handleToolbar(btn.name)"
|
||||||
></el-button>
|
></el-button>
|
||||||
@@ -100,7 +100,7 @@
|
|||||||
:active-text="col.activeText ?? ''"
|
:active-text="col.activeText ?? ''"
|
||||||
:inactive-text="col.inactiveText ?? ''"
|
:inactive-text="col.inactiveText ?? ''"
|
||||||
:validate-event="false"
|
:validate-event="false"
|
||||||
:disabled="!hasAuth(`${contentConfig.pageName}:modify`)"
|
:disabled="!hasButtonPerm(col.prop)"
|
||||||
@change="
|
@change="
|
||||||
pageData.length > 0 && handleModify(col.prop, scope.row[col.prop], scope.row)
|
pageData.length > 0 && handleModify(col.prop, scope.row[col.prop], scope.row)
|
||||||
"
|
"
|
||||||
@@ -113,7 +113,7 @@
|
|||||||
<el-input
|
<el-input
|
||||||
v-model="scope.row[col.prop]"
|
v-model="scope.row[col.prop]"
|
||||||
:type="col.inputType ?? 'text'"
|
:type="col.inputType ?? 'text'"
|
||||||
:disabled="!hasAuth(`${contentConfig.pageName}:modify`)"
|
:disabled="!hasButtonPerm(col.prop)"
|
||||||
@blur="handleModify(col.prop, scope.row[col.prop], scope.row)"
|
@blur="handleModify(col.prop, scope.row[col.prop], scope.row)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
@@ -157,7 +157,7 @@
|
|||||||
<template v-for="btn in tableToolbarBtn">
|
<template v-for="btn in tableToolbarBtn">
|
||||||
<el-button
|
<el-button
|
||||||
v-if="btn.render === undefined || btn.render(scope.row)"
|
v-if="btn.render === undefined || btn.render(scope.row)"
|
||||||
v-hasPerm="authName ? `${authName}:${btn.auth}` : '*:*:*'"
|
v-hasPerm="btn.perm ?? '*:*:*'"
|
||||||
v-bind="btn.attrs"
|
v-bind="btn.attrs"
|
||||||
@click="
|
@click="
|
||||||
handleOperat({
|
handleOperat({
|
||||||
@@ -330,8 +330,9 @@ import {
|
|||||||
type TableInstance,
|
type TableInstance,
|
||||||
} from "element-plus";
|
} from "element-plus";
|
||||||
import ExcelJS from "exceljs";
|
import ExcelJS from "exceljs";
|
||||||
import { reactive, ref } from "vue";
|
import { reactive, ref, computed } from "vue";
|
||||||
import type { IContentConfig, IObject, IOperatData, IToolsDefault } from "./types";
|
import type { IContentConfig, IObject, IOperatData } from "./types";
|
||||||
|
import type { IToolsButton } from "./types";
|
||||||
|
|
||||||
// 定义接收的属性
|
// 定义接收的属性
|
||||||
const props = defineProps<{ contentConfig: IContentConfig }>();
|
const props = defineProps<{ contentConfig: IContentConfig }>();
|
||||||
@@ -346,48 +347,123 @@ const emit = defineEmits<{
|
|||||||
filterChange: [data: IObject];
|
filterChange: [data: IObject];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
// 主键
|
// 按钮文本映射
|
||||||
const pk = props.contentConfig.pk ?? "id";
|
const BUTTONS_TEXT: Record<string, string> = {
|
||||||
|
add: "新增",
|
||||||
|
delete: "删除",
|
||||||
|
import: "导入",
|
||||||
|
export: "导出",
|
||||||
|
refresh: "刷新",
|
||||||
|
filter: "筛选列",
|
||||||
|
search: "搜索",
|
||||||
|
imports: "批量导入",
|
||||||
|
exports: "批量导出",
|
||||||
|
view: "查看",
|
||||||
|
edit: "编辑",
|
||||||
|
};
|
||||||
|
|
||||||
|
// 按钮权限映射
|
||||||
|
const AUTH_MAP: Record<string, string> = {
|
||||||
|
add: "add",
|
||||||
|
delete: "delete",
|
||||||
|
import: "import",
|
||||||
|
export: "export",
|
||||||
|
refresh: "*:*:*",
|
||||||
|
filter: "*:*:*",
|
||||||
|
search: "search",
|
||||||
|
imports: "imports",
|
||||||
|
exports: "exports",
|
||||||
|
view: "view",
|
||||||
|
edit: "edit",
|
||||||
|
};
|
||||||
|
|
||||||
|
// 按钮图标映射
|
||||||
|
const BUTTONS_ICON: Record<string, string> = {
|
||||||
|
add: "plus",
|
||||||
|
delete: "delete",
|
||||||
|
import: "upload",
|
||||||
|
export: "download",
|
||||||
|
refresh: "refresh",
|
||||||
|
filter: "operation",
|
||||||
|
search: "search",
|
||||||
|
imports: "upload",
|
||||||
|
exports: "download",
|
||||||
|
view: "view",
|
||||||
|
edit: "edit",
|
||||||
|
};
|
||||||
|
|
||||||
// 表格工具栏按钮配置
|
// 表格工具栏按钮配置
|
||||||
const config = computed(() => props.contentConfig);
|
const config = computed(() => props.contentConfig);
|
||||||
const authName = computed(() => props.contentConfig.pageName);
|
|
||||||
const buttonConfig = reactive<Record<string, IObject>>({
|
const buttonConfig = reactive<Record<string, IObject>>({
|
||||||
add: { text: "新增", attrs: { icon: "plus", type: "success" }, auth: "add" },
|
add: { text: "新增", attrs: { icon: "plus", type: "success" }, perm: "add" },
|
||||||
delete: { text: "删除", attrs: { icon: "delete", type: "danger" }, auth: "delete" },
|
delete: { text: "删除", attrs: { icon: "delete", type: "danger" }, perm: "delete" },
|
||||||
import: { text: "导入", attrs: { icon: "upload", type: "" }, auth: "import" },
|
import: { text: "导入", attrs: { icon: "upload", type: "" }, perm: "import" },
|
||||||
export: { text: "导出", attrs: { icon: "download", type: "" }, auth: "export" },
|
export: { text: "导出", attrs: { icon: "download", type: "" }, perm: "export" },
|
||||||
refresh: { text: "刷新", attrs: { icon: "refresh", type: "" }, auth: "*:*:*" },
|
refresh: { text: "刷新", attrs: { icon: "refresh", type: "" }, perm: "*:*:*" },
|
||||||
filter: { text: "筛选列", attrs: { icon: "operation", type: "" }, auth: "*:*:*" },
|
filter: { text: "筛选列", attrs: { icon: "operation", type: "" }, perm: "*:*:*" },
|
||||||
search: { text: "搜索", attrs: { icon: "search", type: "" }, auth: "search" },
|
search: { text: "搜索", attrs: { icon: "search", type: "" }, perm: "search" },
|
||||||
imports: { text: "批量导入", attrs: { icon: "upload", type: "" }, auth: "imports" },
|
imports: { text: "批量导入", attrs: { icon: "upload", type: "" }, perm: "imports" },
|
||||||
exports: { text: "批量导出", attrs: { icon: "download", type: "" }, auth: "exports" },
|
exports: { text: "批量导出", attrs: { icon: "download", type: "" }, perm: "exports" },
|
||||||
view: { text: "查看", attrs: { icon: "view", type: "primary" }, auth: "view" },
|
view: { text: "查看", attrs: { icon: "view", type: "primary" }, perm: "view" },
|
||||||
edit: { text: "编辑", attrs: { icon: "edit", type: "primary" }, auth: "edit" },
|
edit: { text: "编辑", attrs: { icon: "edit", type: "primary" }, perm: "edit" },
|
||||||
});
|
});
|
||||||
const createToolbar = (toolbar: Array<IToolsDefault>, attr = {}) => {
|
|
||||||
|
// 主键
|
||||||
|
const pk = props.contentConfig.pk ?? "id";
|
||||||
|
// 权限名称前缀
|
||||||
|
const authPrefix = computed(() => props.contentConfig.permPrefix);
|
||||||
|
|
||||||
|
// 获取按钮权限标识
|
||||||
|
function getButtonPerm(action: string): string | null {
|
||||||
|
// 如果action已经包含完整路径(包含冒号),则直接使用
|
||||||
|
if (action.includes(":")) {
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
// 否则使用权限前缀组合
|
||||||
|
return authPrefix.value ? `${authPrefix.value}:${action}` : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否有权限
|
||||||
|
function hasButtonPerm(action: string): boolean {
|
||||||
|
const perm = getButtonPerm(action);
|
||||||
|
// 如果没有设置权限标识,则默认具有权限
|
||||||
|
if (!perm) return true;
|
||||||
|
return hasAuth(perm);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建工具栏按钮
|
||||||
|
function createToolbar(toolbar: Array<string | IToolsButton>, attr = {}) {
|
||||||
return toolbar.map((item) => {
|
return toolbar.map((item) => {
|
||||||
const isString = typeof item === "string";
|
const isString = typeof item === "string";
|
||||||
return {
|
return {
|
||||||
name: isString ? item : item?.name || "",
|
name: isString ? item : item?.name || "",
|
||||||
text: isString ? buttonConfig[item].text : item?.text,
|
text: isString ? buttonConfig[item].text : item?.text,
|
||||||
attrs: {
|
attrs: {
|
||||||
type: isString ? buttonConfig[item].type : "",
|
|
||||||
icon: isString ? buttonConfig[item].icon : "",
|
|
||||||
title: isString ? buttonConfig[item].text : item?.text,
|
|
||||||
...attr,
|
...attr,
|
||||||
...(isString ? buttonConfig[item].attrs : item?.attrs),
|
...(isString ? buttonConfig[item].attrs : item?.attrs),
|
||||||
},
|
},
|
||||||
render: isString ? undefined : (item?.render ?? undefined),
|
render: isString ? undefined : (item?.render ?? undefined),
|
||||||
auth: isString ? buttonConfig[item].auth : (item?.auth ?? "*:*:*"),
|
perm: isString
|
||||||
|
? getButtonPerm(buttonConfig[item].perm)
|
||||||
|
: item?.perm
|
||||||
|
? getButtonPerm(item.perm as string)
|
||||||
|
: "*:*:*",
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
// 表格左侧工具栏
|
|
||||||
const toolbarLeft = props.contentConfig?.toolbar ?? ["add", "delete"];
|
// 左侧工具栏按钮
|
||||||
const toolbarLeftBtn = createToolbar(toolbarLeft, {});
|
const toolbarLeftBtn = computed(() => {
|
||||||
// 表格右侧工具栏
|
if (!config.value.toolbar || config.value.toolbar.length === 0) return [];
|
||||||
const toolbarRight = props.contentConfig?.defaultToolbar ?? ["refresh", "filter"];
|
return createToolbar(config.value.toolbar, {});
|
||||||
const toolbarRightBtn = createToolbar(toolbarRight, { circle: true });
|
});
|
||||||
|
|
||||||
|
// 右侧工具栏按钮
|
||||||
|
const toolbarRightBtn = computed(() => {
|
||||||
|
if (!config.value.defaultToolbar || config.value.defaultToolbar.length === 0) return [];
|
||||||
|
return createToolbar(config.value.defaultToolbar, { circle: true });
|
||||||
|
});
|
||||||
|
|
||||||
// 表格操作工具栏
|
// 表格操作工具栏
|
||||||
const tableToolbar = config.value.cols[config.value.cols.length - 1].operat ?? ["edit", "delete"];
|
const tableToolbar = config.value.cols[config.value.cols.length - 1].operat ?? ["edit", "delete"];
|
||||||
const tableToolbarBtn = createToolbar(tableToolbar, { link: true, size: "small" });
|
const tableToolbarBtn = createToolbar(tableToolbar, { link: true, size: "small" });
|
||||||
@@ -536,7 +612,7 @@ function handleCloseExportsModal() {
|
|||||||
function handleExports() {
|
function handleExports() {
|
||||||
const filename = exportsFormData.filename
|
const filename = exportsFormData.filename
|
||||||
? exportsFormData.filename
|
? exportsFormData.filename
|
||||||
: props.contentConfig.pageName;
|
: props.contentConfig.permPrefix || "export";
|
||||||
const sheetname = exportsFormData.sheetname ? exportsFormData.sheetname : "sheet";
|
const sheetname = exportsFormData.sheetname ? exportsFormData.sheetname : "sheet";
|
||||||
const workbook = new ExcelJS.Workbook();
|
const workbook = new ExcelJS.Workbook();
|
||||||
const worksheet = workbook.addWorksheet(sheetname);
|
const worksheet = workbook.addWorksheet(sheetname);
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-show="visible" v-bind="{ style: { 'margin-bottom': '12px' }, ...cardAttrs }">
|
<div v-show="visible" v-bind="{ style: { 'margin-bottom': '12px' }, ...cardAttrs }">
|
||||||
<el-card
|
<el-card v-bind="cardAttrs">
|
||||||
v-hasPerm="searchConfig?.pageName ? `${searchConfig.pageName}:query` : '*:*:*'"
|
|
||||||
v-bind="cardAttrs"
|
|
||||||
>
|
|
||||||
<el-form ref="queryFormRef" :model="queryParams" v-bind="formAttrs" :class="isGrid">
|
<el-form ref="queryFormRef" :model="queryParams" v-bind="formAttrs" :class="isGrid">
|
||||||
<template v-for="(item, index) in formItems" :key="item.prop">
|
<template v-for="(item, index) in formItems" :key="item.prop">
|
||||||
<el-form-item
|
<el-form-item
|
||||||
|
|||||||
@@ -23,10 +23,12 @@ export type IComponentType = DataComponent | InputComponent | OtherComponent;
|
|||||||
type ToolbarLeft = "add" | "delete" | "import" | "export";
|
type ToolbarLeft = "add" | "delete" | "import" | "export";
|
||||||
type ToolbarRight = "refresh" | "filter" | "imports" | "exports" | "search";
|
type ToolbarRight = "refresh" | "filter" | "imports" | "exports" | "search";
|
||||||
type ToolbarTable = "edit" | "view" | "delete";
|
type ToolbarTable = "edit" | "view" | "delete";
|
||||||
type IToolsButton = {
|
export type IToolsButton = {
|
||||||
name: string; // 按钮名称
|
name: string; // 按钮名称
|
||||||
text?: string; // 按钮文本
|
text?: string; // 按钮文本
|
||||||
auth?: Array<string> | string; // 按钮权限
|
perm?: Array<string> | string; // 权限标识(可以是完整权限字符串如'sys:user:add'或操作权限如'add')
|
||||||
|
icon?: string; // 按钮图标
|
||||||
|
type?: string; // 按钮类型
|
||||||
attrs?: Partial<ButtonProps> & { style?: CSSProperties }; // 按钮属性
|
attrs?: Partial<ButtonProps> & { style?: CSSProperties }; // 按钮属性
|
||||||
render?: (row: IObject) => boolean; // 条件渲染
|
render?: (row: IObject) => boolean; // 条件渲染
|
||||||
};
|
};
|
||||||
@@ -40,8 +42,8 @@ export interface IOperatData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ISearchConfig {
|
export interface ISearchConfig {
|
||||||
// 页面名称(参与组成权限标识,如sys:user:xxx),不填则不进行权限校验
|
// 权限前缀(如sys:user,用于组成权限标识),不提供则不进行权限校验
|
||||||
pageName?: string;
|
permPrefix?: string;
|
||||||
// 标签冒号(默认:false)
|
// 标签冒号(默认:false)
|
||||||
colon?: boolean;
|
colon?: boolean;
|
||||||
// 表单项(默认:[])
|
// 表单项(默认:[])
|
||||||
@@ -78,8 +80,8 @@ export interface ISearchConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IContentConfig<T = any> {
|
export interface IContentConfig<T = any> {
|
||||||
// 页面名称(参与组成权限标识,如sys:user:xxx)
|
// 权限前缀(如sys:user,用于组成权限标识),不提供则不进行权限校验
|
||||||
pageName?: string;
|
permPrefix?: string;
|
||||||
// table组件属性
|
// table组件属性
|
||||||
table?: Omit<TableProps<any>, "data">;
|
table?: Omit<TableProps<any>, "data">;
|
||||||
// 分页组件位置(默认:left)
|
// 分页组件位置(默认:left)
|
||||||
@@ -181,8 +183,8 @@ export interface IContentConfig<T = any> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IModalConfig<T = any> {
|
export interface IModalConfig<T = any> {
|
||||||
// 页面名称
|
// 权限前缀(如sys:user,用于组成权限标识),不提供则不进行权限校验
|
||||||
pageName?: string;
|
permPrefix?: string;
|
||||||
// 主键名(主要用于编辑数据,默认为id)
|
// 主键名(主要用于编辑数据,默认为id)
|
||||||
pk?: string;
|
pk?: string;
|
||||||
// 组件类型
|
// 组件类型
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import UserAPI, { type UserForm } from "@/api/system/user.api";
|
|||||||
import type { IModalConfig } from "@/components/CURD/types";
|
import type { IModalConfig } from "@/components/CURD/types";
|
||||||
|
|
||||||
const modalConfig: IModalConfig<UserForm> = {
|
const modalConfig: IModalConfig<UserForm> = {
|
||||||
pageName: "sys:user",
|
permPrefix: "sys:user",
|
||||||
dialog: {
|
dialog: {
|
||||||
title: "新增用户",
|
title: "新增用户",
|
||||||
width: 800,
|
width: 800,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import type { UserPageQuery } from "@/api/system/user.api";
|
|||||||
import type { IContentConfig } from "@/components/CURD/types";
|
import type { IContentConfig } from "@/components/CURD/types";
|
||||||
|
|
||||||
const contentConfig: IContentConfig<UserPageQuery> = {
|
const contentConfig: IContentConfig<UserPageQuery> = {
|
||||||
// pageName: "sys:demo", // 不写不进行按钮权限校验
|
permPrefix: "sys:user", // 不写不进行按钮权限校验
|
||||||
table: {
|
table: {
|
||||||
border: true,
|
border: true,
|
||||||
highlightCurrentRow: true,
|
highlightCurrentRow: true,
|
||||||
@@ -50,7 +50,7 @@ const contentConfig: IContentConfig<UserPageQuery> = {
|
|||||||
{
|
{
|
||||||
name: "custom1",
|
name: "custom1",
|
||||||
text: "自定义1",
|
text: "自定义1",
|
||||||
auth: "custom",
|
perm: "add",
|
||||||
attrs: { icon: "plus", color: "#626AEF" },
|
attrs: { icon: "plus", color: "#626AEF" },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -117,7 +117,7 @@ const contentConfig: IContentConfig<UserPageQuery> = {
|
|||||||
{
|
{
|
||||||
name: "reset_pwd",
|
name: "reset_pwd",
|
||||||
text: "重置密码",
|
text: "重置密码",
|
||||||
auth: "password:reset",
|
perm: "password:reset",
|
||||||
attrs: {
|
attrs: {
|
||||||
icon: "refresh-left",
|
icon: "refresh-left",
|
||||||
// color: "#626AEF", // 使用 text 属性,颜色不生效
|
// color: "#626AEF", // 使用 text 属性,颜色不生效
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { IContentConfig } from "@/components/CURD/types";
|
import type { IContentConfig } from "@/components/CURD/types";
|
||||||
|
|
||||||
const contentConfig: IContentConfig = {
|
const contentConfig: IContentConfig = {
|
||||||
// pageName: "sys:demo", // 不写不进行按钮权限校验
|
// permPrefix: "sys:demo", // 不写不进行按钮权限校验
|
||||||
table: {
|
table: {
|
||||||
showOverflowTooltip: true,
|
showOverflowTooltip: true,
|
||||||
},
|
},
|
||||||
@@ -118,7 +118,7 @@ const contentConfig: IContentConfig = {
|
|||||||
{
|
{
|
||||||
name: "reset_pwd",
|
name: "reset_pwd",
|
||||||
text: "重置密码",
|
text: "重置密码",
|
||||||
auth: "password:reset",
|
perm: "password:reset",
|
||||||
attrs: { icon: "refresh-left", type: "primary" },
|
attrs: { icon: "refresh-left", type: "primary" },
|
||||||
render(row) {
|
render(row) {
|
||||||
// 根据条件,显示或隐藏
|
// 根据条件,显示或隐藏
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { DeviceEnum } from "@/enums/settings/device.enum";
|
|||||||
import { useAppStore } from "@/store";
|
import { useAppStore } from "@/store";
|
||||||
|
|
||||||
const modalConfig: IModalConfig<UserForm> = {
|
const modalConfig: IModalConfig<UserForm> = {
|
||||||
pageName: "sys:user",
|
permPrefix: "sys:user",
|
||||||
component: "drawer",
|
component: "drawer",
|
||||||
drawer: {
|
drawer: {
|
||||||
title: "修改用户",
|
title: "修改用户",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import DeptAPI from "@/api/system/dept.api";
|
|||||||
import type { ISearchConfig } from "@/components/CURD/types";
|
import type { ISearchConfig } from "@/components/CURD/types";
|
||||||
|
|
||||||
const searchConfig: ISearchConfig = {
|
const searchConfig: ISearchConfig = {
|
||||||
pageName: "sys:user",
|
permPrefix: "sys:user",
|
||||||
formItems: [
|
formItems: [
|
||||||
{
|
{
|
||||||
tips: "支持模糊搜索",
|
tips: "支持模糊搜索",
|
||||||
|
|||||||
Reference in New Issue
Block a user