refactor: ♻️ pageContent组件,工具栏按钮适配移动端,提取按钮公共方法
This commit is contained in:
@@ -1,142 +1,39 @@
|
||||
<template>
|
||||
<el-card shadow="never">
|
||||
<!-- 表格工具栏 -->
|
||||
<div class="flex-x-between mb-[10px]">
|
||||
<div class="flex flex-col md:flex-row justify-between gap-y-2.5 mb-2.5">
|
||||
<!-- 左侧工具栏 -->
|
||||
<div>
|
||||
<template v-for="item in toolbar" :key="item">
|
||||
<template v-if="typeof item === 'string'">
|
||||
<!-- 新增 -->
|
||||
<template v-if="item === 'add'">
|
||||
<el-button
|
||||
v-hasPerm="[`${contentConfig.pageName}:${item}`]"
|
||||
type="success"
|
||||
icon="plus"
|
||||
@click="handleToolbar(item)"
|
||||
>
|
||||
新增
|
||||
</el-button>
|
||||
</template>
|
||||
<!-- 删除 -->
|
||||
<template v-else-if="item === 'delete'">
|
||||
<el-button
|
||||
v-hasPerm="[`${contentConfig.pageName}:${item}`]"
|
||||
type="danger"
|
||||
icon="delete"
|
||||
:disabled="removeIds.length === 0"
|
||||
@click="handleToolbar(item)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
<!-- 导入 -->
|
||||
<template v-else-if="item === 'import'">
|
||||
<el-button
|
||||
v-hasPerm="[`${contentConfig.pageName}:${item}`]"
|
||||
type="default"
|
||||
icon="upload"
|
||||
@click="handleToolbar(item)"
|
||||
>
|
||||
导入
|
||||
</el-button>
|
||||
</template>
|
||||
<!-- 导出 -->
|
||||
<template v-else-if="item === 'export'">
|
||||
<el-button
|
||||
v-hasPerm="[`${contentConfig.pageName}:${item}`]"
|
||||
type="default"
|
||||
icon="download"
|
||||
@click="handleToolbar(item)"
|
||||
>
|
||||
导出
|
||||
</el-button>
|
||||
</template>
|
||||
</template>
|
||||
<!-- 其他 -->
|
||||
<template v-else-if="typeof item === 'object'">
|
||||
<el-button
|
||||
v-hasPerm="[`${contentConfig.pageName}:${item.auth}`]"
|
||||
:icon="item.icon"
|
||||
:type="item.type ?? 'default'"
|
||||
@click="handleToolbar(item.name)"
|
||||
>
|
||||
{{ item.text }}
|
||||
</el-button>
|
||||
</template>
|
||||
<div class="toolbar-left flex gap-y-2.5 gap-x-2 md:gap-x-3 flex-wrap">
|
||||
<template v-for="btn in toolbarLeftBtn">
|
||||
<el-button
|
||||
v-hasPerm="authName ? `${authName}:${btn.auth}` : '*:*:*'"
|
||||
:disabled="btn.name === 'delete' && removeIds.length === 0"
|
||||
v-bind="btn.attrs"
|
||||
@click="handleToolbar(btn.name)"
|
||||
>
|
||||
{{ btn.text }}
|
||||
</el-button>
|
||||
</template>
|
||||
</div>
|
||||
<!-- 右侧工具栏 -->
|
||||
<div>
|
||||
<template v-for="item in defaultToolbar" :key="item">
|
||||
<template v-if="typeof item === 'string'">
|
||||
<!-- 刷新 -->
|
||||
<template v-if="item === 'refresh'">
|
||||
<el-button icon="refresh" circle title="刷新" @click="handleToolbar(item)" />
|
||||
<div class="toolbar-right flex gap-y-2.5 gap-x-2 md:gap-x-3 flex-wrap">
|
||||
<template v-for="btn in toolbarRightBtn">
|
||||
<el-popover v-if="btn.name === 'filter'" placement="bottom" trigger="click">
|
||||
<template #reference>
|
||||
<el-button v-bind="btn.attrs"></el-button>
|
||||
</template>
|
||||
<!-- 筛选列 -->
|
||||
<template v-else-if="item === 'filter'">
|
||||
<el-popover placement="bottom" trigger="click">
|
||||
<template #reference>
|
||||
<el-button icon="Operation" circle title="筛选列" />
|
||||
</template>
|
||||
<el-scrollbar max-height="350px">
|
||||
<template v-for="col in cols" :key="col">
|
||||
<el-checkbox v-if="col.prop" v-model="col.show" :label="col.label" />
|
||||
</template>
|
||||
</el-scrollbar>
|
||||
</el-popover>
|
||||
</template>
|
||||
<!-- 导出 -->
|
||||
<template v-else-if="item === 'exports'">
|
||||
<el-button
|
||||
v-hasPerm="[`${contentConfig.pageName}:export`]"
|
||||
icon="download"
|
||||
circle
|
||||
title="导出"
|
||||
@click="handleToolbar(item)"
|
||||
/>
|
||||
</template>
|
||||
<!-- 导入 -->
|
||||
<template v-else-if="item === 'imports'">
|
||||
<el-button
|
||||
v-hasPerm="[`${contentConfig.pageName}:import`]"
|
||||
icon="upload"
|
||||
circle
|
||||
title="导入"
|
||||
@click="handleToolbar(item)"
|
||||
/>
|
||||
</template>
|
||||
<!-- 搜索 -->
|
||||
<template v-else-if="item === 'search'">
|
||||
<el-button
|
||||
v-hasPerm="[`${contentConfig.pageName}:query`]"
|
||||
icon="search"
|
||||
circle
|
||||
title="搜索"
|
||||
@click="handleToolbar(item)"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
<!-- 其他 -->
|
||||
<template v-else-if="typeof item === 'object'">
|
||||
<template v-if="item.auth">
|
||||
<el-button
|
||||
v-hasPerm="[`${contentConfig.pageName}:${item.auth}`]"
|
||||
:icon="item.icon"
|
||||
circle
|
||||
:title="item.title"
|
||||
@click="handleToolbar(item.name)"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-button
|
||||
:icon="item.icon"
|
||||
circle
|
||||
:title="item.title"
|
||||
@click="handleToolbar(item.name)"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
<el-scrollbar max-height="350px">
|
||||
<template v-for="col in cols" :key="col">
|
||||
<el-checkbox v-if="col.prop" v-model="col.show" :label="col.label" />
|
||||
</template>
|
||||
</el-scrollbar>
|
||||
</el-popover>
|
||||
<el-button
|
||||
v-else
|
||||
v-hasPerm="authName ? `${authName}:${btn.auth}` : '*:*:*'"
|
||||
v-bind="btn.attrs"
|
||||
@click="handleToolbar(btn.name)"
|
||||
></el-button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
@@ -257,52 +154,22 @@
|
||||
</template>
|
||||
<!-- 列操作栏 -->
|
||||
<template v-else-if="col.templet === 'tool'">
|
||||
<template v-for="item in col.operat ?? ['edit', 'delete']" :key="item">
|
||||
<template v-if="typeof item === 'string'">
|
||||
<!-- 编辑/删除 -->
|
||||
<template v-if="item === 'edit' || item === 'delete'">
|
||||
<el-button
|
||||
v-hasPerm="[`${contentConfig.pageName}:${item}`]"
|
||||
:type="item === 'edit' ? 'primary' : 'danger'"
|
||||
:icon="item"
|
||||
size="small"
|
||||
link
|
||||
@click="
|
||||
handleOperat({
|
||||
name: item,
|
||||
row: scope.row,
|
||||
column: scope.column,
|
||||
$index: scope.$index,
|
||||
})
|
||||
"
|
||||
>
|
||||
{{ item === "edit" ? "编辑" : "删除" }}
|
||||
</el-button>
|
||||
</template>
|
||||
</template>
|
||||
<!-- 其他 -->
|
||||
<template v-else-if="typeof item === 'object'">
|
||||
<el-button
|
||||
v-if="item.render === undefined || item.render(scope.row)"
|
||||
v-bind="
|
||||
item.auth ? { 'v-hasPerm': [`${contentConfig.pageName}:${item.auth}`] } : {}
|
||||
"
|
||||
:icon="item.icon"
|
||||
:type="item.type ?? 'primary'"
|
||||
size="small"
|
||||
link
|
||||
@click="
|
||||
handleOperat({
|
||||
name: item.name,
|
||||
row: scope.row,
|
||||
column: scope.column,
|
||||
$index: scope.$index,
|
||||
})
|
||||
"
|
||||
>
|
||||
{{ item.text }}
|
||||
</el-button>
|
||||
</template>
|
||||
<template v-for="btn in tableToolbarBtn">
|
||||
<el-button
|
||||
v-if="btn.render === undefined || btn.render(scope.row)"
|
||||
v-hasPerm="authName ? `${authName}:${btn.auth}` : '*:*:*'"
|
||||
v-bind="btn.attrs"
|
||||
@click="
|
||||
handleOperat({
|
||||
name: btn.name,
|
||||
row: scope.row,
|
||||
column: scope.column,
|
||||
$index: scope.$index,
|
||||
})
|
||||
"
|
||||
>
|
||||
{{ btn.text }}
|
||||
</el-button>
|
||||
</template>
|
||||
</template>
|
||||
<!-- 自定义 -->
|
||||
@@ -464,12 +331,10 @@ import {
|
||||
} from "element-plus";
|
||||
import ExcelJS from "exceljs";
|
||||
import { reactive, ref } from "vue";
|
||||
import type { IContentConfig, IObject, IOperatData } from "./types";
|
||||
import type { IContentConfig, IObject, IOperatData, IToolsDefault } from "./types";
|
||||
|
||||
// 定义接收的属性
|
||||
const props = defineProps<{
|
||||
contentConfig: IContentConfig;
|
||||
}>();
|
||||
const props = defineProps<{ contentConfig: IContentConfig }>();
|
||||
// 定义自定义事件
|
||||
const emit = defineEmits<{
|
||||
addClick: [];
|
||||
@@ -483,10 +348,50 @@ const emit = defineEmits<{
|
||||
|
||||
// 主键
|
||||
const pk = props.contentConfig.pk ?? "id";
|
||||
// 表格工具栏按钮配置
|
||||
const config = computed(() => props.contentConfig);
|
||||
const authName = computed(() => props.contentConfig.pageName);
|
||||
const buttonConfig = reactive<Record<string, IObject>>({
|
||||
add: { text: "新增", attrs: { icon: "plus", type: "success" }, auth: "add" },
|
||||
delete: { text: "删除", attrs: { icon: "delete", type: "danger" }, auth: "delete" },
|
||||
import: { text: "导入", attrs: { icon: "upload", type: "" }, auth: "import" },
|
||||
export: { text: "导出", attrs: { icon: "download", type: "" }, auth: "export" },
|
||||
refresh: { text: "刷新", attrs: { icon: "refresh", type: "" }, auth: "*:*:*" },
|
||||
filter: { text: "筛选列", attrs: { icon: "operation", type: "" }, auth: "*:*:*" },
|
||||
search: { text: "搜索", attrs: { icon: "search", type: "" }, auth: "search" },
|
||||
imports: { text: "批量导入", attrs: { icon: "upload", type: "" }, auth: "imports" },
|
||||
exports: { text: "批量导出", attrs: { icon: "download", type: "" }, auth: "exports" },
|
||||
view: { text: "查看", attrs: { icon: "view", type: "primary" }, auth: "view" },
|
||||
edit: { text: "编辑", attrs: { icon: "edit", type: "primary" }, auth: "edit" },
|
||||
});
|
||||
const createToolbar = (toolbar: Array<IToolsDefault>, attr = {}) => {
|
||||
return toolbar.map((item) => {
|
||||
const isString = typeof item === "string";
|
||||
return {
|
||||
name: isString ? item : item?.name || "",
|
||||
text: isString ? buttonConfig[item].text : item?.text,
|
||||
attrs: {
|
||||
type: isString ? buttonConfig[item].type : "",
|
||||
icon: isString ? buttonConfig[item].icon : "",
|
||||
title: isString ? buttonConfig[item].text : item?.text,
|
||||
...attr,
|
||||
...(isString ? buttonConfig[item].attrs : item?.attrs),
|
||||
},
|
||||
render: isString ? undefined : (item?.render ?? undefined),
|
||||
auth: isString ? buttonConfig[item].auth : (item?.auth ?? "*:*:*"),
|
||||
};
|
||||
});
|
||||
};
|
||||
// 表格左侧工具栏
|
||||
const toolbar = props.contentConfig.toolbar ?? ["add", "delete"];
|
||||
const toolbarLeft = props.contentConfig?.toolbar ?? ["add", "delete"];
|
||||
const toolbarLeftBtn = createToolbar(toolbarLeft, {});
|
||||
// 表格右侧工具栏
|
||||
const defaultToolbar = props.contentConfig.defaultToolbar ?? ["refresh", "filter"];
|
||||
const toolbarRight = props.contentConfig?.defaultToolbar ?? ["refresh", "filter"];
|
||||
const toolbarRightBtn = createToolbar(toolbarRight, { circle: true });
|
||||
// 表格操作工具栏
|
||||
const tableToolbar = config.value.cols[config.value.cols.length - 1].operat ?? ["edit", "delete"];
|
||||
const tableToolbarBtn = createToolbar(tableToolbar, { link: true, size: "small" });
|
||||
|
||||
// 表格列
|
||||
const cols = ref(
|
||||
props.contentConfig.cols.map((col) => {
|
||||
@@ -515,7 +420,7 @@ const pageData = ref<IObject[]>([]);
|
||||
// 显示分页
|
||||
const showPagination = props.contentConfig.pagination !== false;
|
||||
// 分页配置
|
||||
const defalutPagination = {
|
||||
const defaultPagination = {
|
||||
background: true,
|
||||
layout: "total, sizes, prev, pager, next, jumper",
|
||||
pageSize: 20,
|
||||
@@ -525,8 +430,8 @@ const defalutPagination = {
|
||||
};
|
||||
const pagination = reactive(
|
||||
typeof props.contentConfig.pagination === "object"
|
||||
? { ...defalutPagination, ...props.contentConfig.pagination }
|
||||
: defalutPagination
|
||||
? { ...defaultPagination, ...props.contentConfig.pagination }
|
||||
: defaultPagination
|
||||
);
|
||||
// 分页相关的请求参数
|
||||
const request = props.contentConfig.request ?? {
|
||||
@@ -649,7 +554,7 @@ function handleExports() {
|
||||
workbook.xlsx
|
||||
.writeBuffer()
|
||||
.then((buffer) => {
|
||||
saveXlsx(buffer, filename);
|
||||
saveXlsx(buffer, filename as string);
|
||||
})
|
||||
.catch((error) => console.log(error));
|
||||
});
|
||||
@@ -663,7 +568,7 @@ function handleExports() {
|
||||
workbook.xlsx
|
||||
.writeBuffer()
|
||||
.then((buffer) => {
|
||||
saveXlsx(buffer, filename);
|
||||
saveXlsx(buffer, filename as string);
|
||||
})
|
||||
.catch((error) => console.log(error));
|
||||
}
|
||||
@@ -975,4 +880,12 @@ function saveXlsx(fileData: any, fileName: string) {
|
||||
defineExpose({ fetchPageData, exportPageData, getFilterParams, getSelectionData });
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
<style lang="scss" scoped>
|
||||
.toolbar-left,
|
||||
.toolbar-right {
|
||||
.el-button {
|
||||
margin-right: 0 !important;
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { DialogProps, DrawerProps, FormItemRule, PaginationProps } from "element-plus";
|
||||
import type { FormProps, TableProps, ColProps, ButtonProps, CardProps } from "element-plus";
|
||||
import type { ButtonType, ElTooltipProps } from "element-plus";
|
||||
import type { ElTooltipProps } from "element-plus";
|
||||
import type PageContent from "./PageContent.vue";
|
||||
import type PageForm from "./PageForm.vue";
|
||||
import type PageModal from "./PageModal.vue";
|
||||
@@ -24,12 +24,11 @@ type ToolbarLeft = "add" | "delete" | "import" | "export";
|
||||
type ToolbarRight = "refresh" | "filter" | "imports" | "exports" | "search";
|
||||
type ToolbarTable = "edit" | "view" | "delete";
|
||||
type IToolsButton = {
|
||||
name?: string; // 按钮名称
|
||||
name: string; // 按钮名称
|
||||
text?: string; // 按钮文本
|
||||
icon?: string; // 按钮图标
|
||||
type?: ButtonType; // 按钮类型
|
||||
auth?: Array<string> | string; // 按钮权限
|
||||
attrs?: Partial<ButtonProps>; // 按钮属性
|
||||
attrs?: Partial<ButtonProps> & { style?: CSSProperties }; // 按钮属性
|
||||
render?: (row: IObject) => boolean; // 条件渲染
|
||||
};
|
||||
export type IToolsDefault = ToolbarLeft | ToolbarRight | ToolbarTable | IToolsButton;
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { UserPageQuery } from "@/api/system/user.api";
|
||||
import type { IContentConfig } from "@/components/CURD/types";
|
||||
|
||||
const contentConfig: IContentConfig<UserPageQuery> = {
|
||||
pageName: "sys:user",
|
||||
// pageName: "sys:demo", // 不写不进行按钮权限校验
|
||||
table: {
|
||||
border: true,
|
||||
highlightCurrentRow: true,
|
||||
@@ -15,6 +15,12 @@ const contentConfig: IContentConfig<UserPageQuery> = {
|
||||
pageSize: 20,
|
||||
pageSizes: [10, 20, 30, 50],
|
||||
},
|
||||
parseData(res) {
|
||||
return {
|
||||
total: res.total,
|
||||
list: res.list,
|
||||
};
|
||||
},
|
||||
indexAction: function (params) {
|
||||
return UserAPI.getPage(params);
|
||||
},
|
||||
@@ -43,10 +49,9 @@ const contentConfig: IContentConfig<UserPageQuery> = {
|
||||
"export",
|
||||
{
|
||||
name: "custom1",
|
||||
icon: "plus",
|
||||
text: "自定义1",
|
||||
auth: "import",
|
||||
type: "info",
|
||||
auth: "custom",
|
||||
attrs: { icon: "plus", color: "#626AEF" },
|
||||
},
|
||||
],
|
||||
defaultToolbar: ["refresh", "filter", "imports", "exports", "search"],
|
||||
@@ -105,15 +110,22 @@ const contentConfig: IContentConfig<UserPageQuery> = {
|
||||
templet: "tool",
|
||||
operat: [
|
||||
{
|
||||
icon: "Document",
|
||||
name: "detail",
|
||||
text: "详情",
|
||||
attrs: { icon: "Document" },
|
||||
},
|
||||
{
|
||||
name: "reset_pwd",
|
||||
auth: "password:reset",
|
||||
icon: "refresh-left",
|
||||
text: "重置密码",
|
||||
auth: "password:reset",
|
||||
attrs: {
|
||||
icon: "refresh-left",
|
||||
// color: "#626AEF", // 使用 text 属性,颜色不生效
|
||||
style: {
|
||||
"--el-button-text-color": "#626AEF",
|
||||
"--el-button-hover-link-text-color": "#9197f4",
|
||||
},
|
||||
},
|
||||
},
|
||||
"edit",
|
||||
"delete",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { IContentConfig } from "@/components/CURD/types";
|
||||
|
||||
const contentConfig: IContentConfig = {
|
||||
pageName: "sys:user",
|
||||
// pageName: "sys:demo", // 不写不进行按钮权限校验
|
||||
table: {
|
||||
showOverflowTooltip: true,
|
||||
},
|
||||
@@ -117,11 +117,11 @@ const contentConfig: IContentConfig = {
|
||||
operat: [
|
||||
{
|
||||
name: "reset_pwd",
|
||||
auth: "password:reset",
|
||||
icon: "refresh-left",
|
||||
text: "重置密码",
|
||||
type: "primary",
|
||||
auth: "password:reset",
|
||||
attrs: { icon: "refresh-left", type: "primary" },
|
||||
render(row) {
|
||||
// 根据条件,显示或隐藏
|
||||
return row.id === 1;
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user