Merge branch 'master' of github.com:youlaitech/vue3-element-admin

This commit is contained in:
hxr
2024-05-24 07:23:09 +08:00
11 changed files with 129 additions and 117 deletions

View File

@@ -67,15 +67,21 @@
<template #reference> <template #reference>
<el-button icon="Operation" circle /> <el-button icon="Operation" circle />
</template> </template>
<template v-for="col in cols" :key="col"> <el-scrollbar max-height="350px">
<el-checkbox <template v-for="col in cols" :key="col">
v-if="col.prop" <el-checkbox
v-model="col.show" v-if="col.prop"
:label="col.label" v-model="col.show"
/> :label="col.label"
</template> />
</template>
</el-scrollbar>
</el-popover> </el-popover>
</template> </template>
<!-- 搜索 -->
<template v-else-if="item === 'search'">
<el-button icon="search" circle @click="handleToolbar(item)" />
</template>
</template> </template>
</div> </div>
</div> </div>
@@ -149,15 +155,22 @@
:disabled="!hasAuth(`${contentConfig.pageName}:modify`)" :disabled="!hasAuth(`${contentConfig.pageName}:modify`)"
@change=" @change="
pageData.length > 0 && pageData.length > 0 &&
handleSwitchChange( handleModify(col.prop, scope.row[col.prop], scope.row)
col.prop,
scope.row[col.prop],
scope.row
)
" "
/> />
</template> </template>
</template> </template>
<!-- 生成输入框组件 -->
<template v-else-if="col.templet === 'input'">
<template v-if="col.prop">
<el-input
v-model="scope.row[col.prop]"
:type="col.inputType ?? 'text'"
:disabled="!hasAuth(`${contentConfig.pageName}:modify`)"
@blur="handleModify(col.prop, scope.row[col.prop], scope.row)"
/>
</template>
</template>
<!-- 格式化为价格 --> <!-- 格式化为价格 -->
<template v-else-if="col.templet === 'price'"> <template v-else-if="col.templet === 'price'">
<template v-if="col.prop"> <template v-if="col.prop">
@@ -316,7 +329,7 @@ export interface IContentConfig<T = any> {
} }
>; >;
// 表格工具栏右侧图标 // 表格工具栏右侧图标
defaultToolbar?: ("refresh" | "filter")[]; defaultToolbar?: ("refresh" | "filter" | "search")[];
// table组件列属性(额外的属性templet,operat,slotName) // table组件列属性(额外的属性templet,operat,slotName)
cols: Array<{ cols: Array<{
type?: "default" | "selection" | "index" | "expand"; type?: "default" | "selection" | "index" | "expand";
@@ -330,6 +343,7 @@ export interface IContentConfig<T = any> {
| "list" | "list"
| "url" | "url"
| "switch" | "switch"
| "input"
| "price" | "price"
| "percent" | "percent"
| "icon" | "icon"
@@ -343,6 +357,7 @@ export interface IContentConfig<T = any> {
inactiveValue?: boolean | string | number; inactiveValue?: boolean | string | number;
activeText?: string; activeText?: string;
inactiveText?: string; inactiveText?: string;
inputType?: string;
priceFormat?: string; priceFormat?: string;
dateFormat?: string; dateFormat?: string;
operat?: Array< operat?: Array<
@@ -365,12 +380,11 @@ const props = defineProps<{
const emit = defineEmits<{ const emit = defineEmits<{
addClick: []; addClick: [];
exportClick: []; exportClick: [];
searchClick: [];
toolbarClick: [name: string]; toolbarClick: [name: string];
editClick: [row: IObject]; editClick: [row: IObject];
operatClick: [data: IOperatData]; operatClick: [data: IOperatData];
}>(); }>();
// 暴露的属性和方法
defineExpose({ fetchPageData, exportPageData });
// 主键 // 主键
const pk = props.contentConfig.pk ?? "id"; const pk = props.contentConfig.pk ?? "id";
@@ -380,6 +394,7 @@ const toolbar = props.contentConfig.toolbar ?? ["add", "delete"];
const defaultToolbar = props.contentConfig.defaultToolbar ?? [ const defaultToolbar = props.contentConfig.defaultToolbar ?? [
"refresh", "refresh",
"filter", "filter",
"search",
]; ];
// 表格列 // 表格列
const cols = ref( const cols = ref(
@@ -433,7 +448,7 @@ function handleSelectionChange(selection: any[]) {
} }
// 刷新 // 刷新
function handleRefresh() { function handleRefresh() {
fetchPageData({}, true); fetchPageData(lastFormData);
} }
// 删除 // 删除
function handleDelete(id?: number | string) { function handleDelete(id?: number | string) {
@@ -451,7 +466,7 @@ function handleDelete(id?: number | string) {
if (props.contentConfig.deleteAction) { if (props.contentConfig.deleteAction) {
props.contentConfig.deleteAction(ids).then(() => { props.contentConfig.deleteAction(ids).then(() => {
ElMessage.success("删除成功"); ElMessage.success("删除成功");
handleRefresh(); fetchPageData({}, true);
}); });
} else { } else {
ElMessage.error("未配置deleteAction"); ElMessage.error("未配置deleteAction");
@@ -468,6 +483,9 @@ function handleToolbar(name: string) {
case "refresh": case "refresh":
handleRefresh(); handleRefresh();
break; break;
case "search":
emit("searchClick");
break;
case "add": case "add":
emit("addClick"); emit("addClick");
break; break;
@@ -522,7 +540,7 @@ function exportPageData(queryParams: IObject = {}) {
} }
} }
// 属性修改 // 属性修改
function handleSwitchChange( function handleModify(
field: string, field: string,
value: boolean | string | number, value: boolean | string | number,
row: Record<string, any> row: Record<string, any>
@@ -537,6 +555,9 @@ function handleSwitchChange(
ElMessage.error("未配置modifyAction"); ElMessage.error("未配置modifyAction");
} }
} }
// 暴露的属性和方法
defineExpose({ fetchPageData, exportPageData });
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>

View File

@@ -103,13 +103,15 @@ const props = withDefaults(
); );
const formRef = ref<FormInstance>(); const formRef = ref<FormInstance>();
const formItems = reactive(props.formItems);
const formData = reactive<IObject>({}); const formData = reactive<IObject>({});
const formRules: FormRules = {}; const formRules: FormRules = {};
const watchArr = []; const watchArr = [];
const computedArr = []; const computedArr = [];
const watchEffectArr = []; const watchEffectArr = [];
// 初始化 // 初始化
for (const item of props.formItems) { for (const item of formItems) {
item.initFn && item.initFn(item);
formData[item.prop] = item.initialValue ?? ""; formData[item.prop] = item.initialValue ?? "";
formRules[item.prop] = item.rules ?? []; formRules[item.prop] = item.rules ?? [];
if (item.watch !== undefined) { if (item.watch !== undefined) {

View File

@@ -59,4 +59,6 @@ export type IFormItems<T = any> = Array<{
computed?: (data: T) => any; computed?: (data: T) => any;
// 监听收集函数 // 监听收集函数
watchEffect?: (data: T) => void; watchEffect?: (data: T) => void;
// 初始化数据函数扩展
initFn?: (formItem: IObject) => void;
}>; }>;

View File

@@ -1,10 +1,11 @@
<template> <template>
<div class="search-container" v-hasPerm="[`${searchConfig.pageName}:query`]"> <div
class="search-container"
v-show="visible"
v-hasPerm="[`${searchConfig.pageName}:query`]"
>
<el-form ref="queryFormRef" :model="queryParams" :inline="true"> <el-form ref="queryFormRef" :model="queryParams" :inline="true">
<template <template v-for="(item, index) in formItems" :key="item.prop">
v-for="(item, index) in searchConfig.formItems"
:key="item.prop"
>
<el-form-item <el-form-item
v-show="isExpand ? true : index < showNumber" v-show="isExpand ? true : index < showNumber"
:label="item.label" :label="item.label"
@@ -49,7 +50,7 @@
<el-button icon="refresh" @click="handleReset">重置</el-button> <el-button icon="refresh" @click="handleReset">重置</el-button>
<!-- 展开/收起 --> <!-- 展开/收起 -->
<el-link <el-link
v-if="isExpandable && searchConfig.formItems.length > showNumber" v-if="isExpandable && formItems.length > showNumber"
class="ml-2" class="ml-2"
type="primary" type="primary"
:underline="false" :underline="false"
@@ -87,12 +88,15 @@ export interface ISearchConfig {
initialValue?: any; initialValue?: any;
// 可选项(适用于select组件) // 可选项(适用于select组件)
options?: { label: string; value: any }[]; options?: { label: string; value: any }[];
// 初始化数据函数扩展
initFn?: (formItem: IObject) => void;
}>; }>;
// 是否开启展开和收缩 // 是否开启展开和收缩
isExpandable?: boolean; isExpandable?: boolean;
// 默认展示的表单项数量 // 默认展示的表单项数量
showNumber?: number; showNumber?: number;
} }
interface IProps { interface IProps {
searchConfig: ISearchConfig; searchConfig: ISearchConfig;
} }
@@ -102,9 +106,12 @@ const emit = defineEmits<{
queryClick: [queryParams: IObject]; queryClick: [queryParams: IObject];
resetClick: [queryParams: IObject]; resetClick: [queryParams: IObject];
}>(); }>();
// 暴露的属性和方法
defineExpose({ getQueryParams });
const queryFormRef = ref<FormInstance>();
// 是否显示
const visible = ref(true);
// 响应式的formItems
const formItems = reactive(props.searchConfig.formItems);
// 是否可展开/收缩 // 是否可展开/收缩
const isExpandable = ref(props.searchConfig.isExpandable ?? true); const isExpandable = ref(props.searchConfig.isExpandable ?? true);
// 是否已展开 // 是否已展开
@@ -114,14 +121,13 @@ const showNumber = computed(() => {
if (isExpandable.value === true) { if (isExpandable.value === true) {
return props.searchConfig.showNumber ?? 3; return props.searchConfig.showNumber ?? 3;
} else { } else {
return props.searchConfig.formItems.length; return formItems.length;
} }
}); });
const queryFormRef = ref<FormInstance>();
// 搜索表单数据 // 搜索表单数据
const queryParams = reactive<IObject>({}); const queryParams = reactive<IObject>({});
for (const item of props.searchConfig.formItems) { for (const item of formItems) {
item.initFn && item.initFn(item);
queryParams[item.prop] = item.initialValue ?? ""; queryParams[item.prop] = item.initialValue ?? "";
} }
// 重置操作 // 重置操作
@@ -137,6 +143,13 @@ function handleQuery() {
function getQueryParams() { function getQueryParams() {
return queryParams; return queryParams;
} }
// 显示/隐藏 SearchForm
function toggleVisible() {
visible.value = !visible.value;
}
// 暴露的属性和方法
defineExpose({ getQueryParams, toggleVisible });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@@ -146,6 +159,6 @@ function getQueryParams() {
background-color: var(--el-bg-color-overlay); background-color: var(--el-bg-color-overlay);
border: 1px solid var(--el-border-color-light); border: 1px solid var(--el-border-color-light);
border-radius: 4px; border-radius: 4px;
box-shadow: var(--el-box-shadow-light); box-shadow: none;
} }
</style> </style>

View File

@@ -40,6 +40,10 @@ function usePage() {
const queryParams = searchRef.value?.getQueryParams(); const queryParams = searchRef.value?.getQueryParams();
contentRef.value?.exportPageData(queryParams); contentRef.value?.exportPageData(queryParams);
} }
// 搜索显隐
function handleSearchClick() {
searchRef.value?.toggleVisible();
}
return { return {
searchRef, searchRef,
@@ -52,6 +56,7 @@ function usePage() {
handleEditClick, handleEditClick,
handleSubmitClick, handleSubmitClick,
handleExportClick, handleExportClick,
handleSearchClick,
}; };
} }

View File

@@ -162,6 +162,7 @@ function addTags() {
fullPath: route.fullPath, fullPath: route.fullPath,
affix: route.meta?.affix, affix: route.meta?.affix,
keepAlive: route.meta?.keepAlive, keepAlive: route.meta?.keepAlive,
query: route.query,
}); });
} }
} }
@@ -181,6 +182,7 @@ function moveToCurrentTag() {
fullPath: route.fullPath, fullPath: route.fullPath,
affix: route.meta?.affix, affix: route.meta?.affix,
keepAlive: route.meta?.keepAlive, keepAlive: route.meta?.keepAlive,
query: route.query,
}); });
} }
} }
@@ -222,7 +224,7 @@ function refreshSelectedTag(view: TagView) {
tagsViewStore.delCachedView(view); tagsViewStore.delCachedView(view);
const { fullPath } = view; const { fullPath } = view;
nextTick(() => { nextTick(() => {
router.replace({ path: "/redirect" + fullPath }); router.replace("/redirect" + fullPath);
}); });
} }
@@ -235,7 +237,7 @@ function toLastView(visitedViews: TagView[], view?: TagView) {
// you can adjust it according to your needs. // you can adjust it according to your needs.
if (view?.name === "Dashboard") { if (view?.name === "Dashboard") {
// to reload home page // to reload home page
router.replace({ path: "/redirect" + view.fullPath }); router.replace("/redirect" + view.fullPath);
} else { } else {
router.push("/"); router.push("/");
} }

View File

@@ -1,3 +1,6 @@
import DeptAPI from "@/api/dept";
import DictAPI from "@/api/dict";
import RoleAPI from "@/api/role";
import UserAPI from "@/api/user"; import UserAPI from "@/api/user";
import type { UserForm } from "@/api/user/model"; import type { UserForm } from "@/api/user/model";
import type { IModalConfig } from "@/components/PageModal/index.vue"; import type { IModalConfig } from "@/components/PageModal/index.vue";
@@ -42,26 +45,14 @@ const modalConfig: IModalConfig<UserForm> = {
type: "tree-select", type: "tree-select",
attrs: { attrs: {
placeholder: "请选择所属部门", placeholder: "请选择所属部门",
data: [ data: [],
{
value: 1,
label: "有来技术",
children: [
{
value: 2,
label: "研发部门",
},
{
value: 3,
label: "测试部门",
},
],
},
],
filterable: true, filterable: true,
"check-strictly": true, "check-strictly": true,
"render-after-expand": false, "render-after-expand": false,
}, },
async initFn(formItem) {
formItem.attrs.data = await DeptAPI.getOptions();
},
}, },
{ {
type: "select", type: "select",
@@ -70,11 +61,10 @@ const modalConfig: IModalConfig<UserForm> = {
attrs: { attrs: {
placeholder: "请选择", placeholder: "请选择",
}, },
options: [ options: [],
{ label: "男", value: 1 }, async initFn(formItem) {
{ label: "女", value: 2 }, formItem.options = await DictAPI.getDictOptions("gender");
{ label: "未知", value: 0 }, },
],
}, },
{ {
label: "角色", label: "角色",
@@ -85,18 +75,10 @@ const modalConfig: IModalConfig<UserForm> = {
placeholder: "请选择", placeholder: "请选择",
multiple: true, multiple: true,
}, },
options: [ options: [],
{ label: "系统管理员", value: 2 }, async initFn(formItem) {
{ label: "系统管理员1", value: 4 }, this.options = await RoleAPI.getOptions();
{ label: "系统管理员2", value: 5 }, },
{ label: "系统管理员3", value: 6 },
{ label: "系统管理员4", value: 7 },
{ label: "系统管理员5", value: 8 },
{ label: "系统管理员6", value: 9 },
{ label: "系统管理员7", value: 10 },
{ label: "系统管理员8", value: 11 },
{ label: "访问游客", value: 3 },
],
}, },
{ {
type: "input", type: "input",

View File

@@ -22,6 +22,7 @@ const contentConfig: IContentConfig = {
gender: 1, gender: 1,
status: 1, status: 1,
status2: 1, status2: 1,
sort: 99,
createTime: 1715647982437, createTime: 1715647982437,
}, },
{ {
@@ -36,13 +37,14 @@ const contentConfig: IContentConfig = {
gender: 0, gender: 0,
status: 0, status: 0,
status2: 0, status2: 0,
sort: 0,
createTime: 1715648977426, createTime: 1715648977426,
}, },
], ],
}); });
}, },
modifyAction(data) { modifyAction(data) {
// console.log("modifyAction:", data); console.log("modifyAction:", data);
return Promise.resolve(null); return Promise.resolve(null);
}, },
cols: [ cols: [
@@ -89,6 +91,13 @@ const contentConfig: IContentConfig = {
activeText: "启用", activeText: "启用",
inactiveText: "禁用", inactiveText: "禁用",
}, },
{
label: "排序",
align: "center",
prop: "sort",
templet: "input",
inputType: "number",
},
{ {
label: "创建时间", label: "创建时间",
align: "center", align: "center",

View File

@@ -1,3 +1,6 @@
import DeptAPI from "@/api/dept";
import DictAPI from "@/api/dict";
import RoleAPI from "@/api/role";
import UserAPI from "@/api/user"; import UserAPI from "@/api/user";
import type { UserForm } from "@/api/user/model"; import type { UserForm } from "@/api/user/model";
import type { IModalConfig } from "@/components/PageModal/index.vue"; import type { IModalConfig } from "@/components/PageModal/index.vue";
@@ -43,26 +46,14 @@ const modalConfig: IModalConfig<UserForm> = {
type: "tree-select", type: "tree-select",
attrs: { attrs: {
placeholder: "请选择所属部门", placeholder: "请选择所属部门",
data: [ data: [],
{
value: 1,
label: "有来技术",
children: [
{
value: 2,
label: "研发部门",
},
{
value: 3,
label: "测试部门",
},
],
},
],
filterable: true, filterable: true,
"check-strictly": true, "check-strictly": true,
"render-after-expand": false, "render-after-expand": false,
}, },
async initFn(formItem) {
formItem.attrs.data = await DeptAPI.getOptions();
},
}, },
{ {
type: "select", type: "select",
@@ -71,11 +62,10 @@ const modalConfig: IModalConfig<UserForm> = {
attrs: { attrs: {
placeholder: "请选择", placeholder: "请选择",
}, },
options: [ options: [],
{ label: "男", value: 1 }, async initFn(formItem) {
{ label: "女", value: 2 }, formItem.options = await DictAPI.getDictOptions("gender");
{ label: "未知", value: 0 }, },
],
}, },
{ {
label: "角色", label: "角色",
@@ -86,18 +76,10 @@ const modalConfig: IModalConfig<UserForm> = {
placeholder: "请选择", placeholder: "请选择",
multiple: true, multiple: true,
}, },
options: [ options: [],
{ label: "系统管理员", value: 2 }, async initFn(formItem) {
{ label: "系统管理员1", value: 4 }, this.options = await RoleAPI.getOptions();
{ label: "系统管理员2", value: 5 }, },
{ label: "系统管理员3", value: 6 },
{ label: "系统管理员4", value: 7 },
{ label: "系统管理员5", value: 8 },
{ label: "系统管理员6", value: 9 },
{ label: "系统管理员7", value: 10 },
{ label: "系统管理员8", value: 11 },
{ label: "访问游客", value: 3 },
],
}, },
{ {
type: "input", type: "input",

View File

@@ -1,3 +1,4 @@
import DeptAPI from "@/api/dept";
import type { ISearchConfig } from "@/components/PageSearch/index.vue"; import type { ISearchConfig } from "@/components/PageSearch/index.vue";
const searchConfig: ISearchConfig = { const searchConfig: ISearchConfig = {
@@ -21,22 +22,7 @@ const searchConfig: ISearchConfig = {
prop: "deptId", prop: "deptId",
attrs: { attrs: {
placeholder: "请选择", placeholder: "请选择",
data: [ data: [],
{
value: 1,
label: "有来技术",
children: [
{
value: 2,
label: "研发部门",
},
{
value: 3,
label: "测试部门",
},
],
},
],
filterable: true, filterable: true,
"check-strictly": true, "check-strictly": true,
"render-after-expand": false, "render-after-expand": false,
@@ -45,6 +31,11 @@ const searchConfig: ISearchConfig = {
width: "150px", width: "150px",
}, },
}, },
async initFn(formItem) {
formItem.attrs.data = await DeptAPI.getOptions();
// 注意:如果initFn函数不是箭头函数,this会指向此配置项对象,那么也就可以用this来替代形参formItem
// this.attrs!.data = await DeptAPI.getOptions();
},
}, },
{ {
type: "select", type: "select",

View File

@@ -24,6 +24,7 @@
@add-click="handleAddClick" @add-click="handleAddClick"
@edit-click="handleEditClick" @edit-click="handleEditClick"
@export-click="handleExportClick" @export-click="handleExportClick"
@search-click="handleSearchClick"
@toolbar-click="handleToolbarClick" @toolbar-click="handleToolbarClick"
@operat-click="handleOperatClick" @operat-click="handleOperatClick"
> >
@@ -56,6 +57,7 @@ import type { IObject, IOperatData } from "@/hooks/usePage";
import usePage from "@/hooks/usePage"; import usePage from "@/hooks/usePage";
import addModalConfig from "./config/add"; import addModalConfig from "./config/add";
import contentConfig from "./config/content"; import contentConfig from "./config/content";
// import contentConfig from "./config/content2";
import editModalConfig from "./config/edit"; import editModalConfig from "./config/edit";
import searchConfig from "./config/search"; import searchConfig from "./config/search";
@@ -70,6 +72,7 @@ const {
// handleEditClick, // handleEditClick,
handleSubmitClick, handleSubmitClick,
handleExportClick, handleExportClick,
handleSearchClick,
} = usePage(); } = usePage();
// 编辑 // 编辑
async function handleEditClick(row: IObject) { async function handleEditClick(row: IObject) {