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

View File

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

View File

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

View File

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

View File

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

View File

@@ -162,6 +162,7 @@ function addTags() {
fullPath: route.fullPath,
affix: route.meta?.affix,
keepAlive: route.meta?.keepAlive,
query: route.query,
});
}
}
@@ -181,6 +182,7 @@ function moveToCurrentTag() {
fullPath: route.fullPath,
affix: route.meta?.affix,
keepAlive: route.meta?.keepAlive,
query: route.query,
});
}
}
@@ -222,7 +224,7 @@ function refreshSelectedTag(view: TagView) {
tagsViewStore.delCachedView(view);
const { fullPath } = view;
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.
if (view?.name === "Dashboard") {
// to reload home page
router.replace({ path: "/redirect" + view.fullPath });
router.replace("/redirect" + view.fullPath);
} else {
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 type { UserForm } from "@/api/user/model";
import type { IModalConfig } from "@/components/PageModal/index.vue";
@@ -42,26 +45,14 @@ const modalConfig: IModalConfig<UserForm> = {
type: "tree-select",
attrs: {
placeholder: "请选择所属部门",
data: [
{
value: 1,
label: "有来技术",
children: [
{
value: 2,
label: "研发部门",
},
{
value: 3,
label: "测试部门",
},
],
},
],
data: [],
filterable: true,
"check-strictly": true,
"render-after-expand": false,
},
async initFn(formItem) {
formItem.attrs.data = await DeptAPI.getOptions();
},
},
{
type: "select",
@@ -70,11 +61,10 @@ const modalConfig: IModalConfig<UserForm> = {
attrs: {
placeholder: "请选择",
},
options: [
{ label: "男", value: 1 },
{ label: "女", value: 2 },
{ label: "未知", value: 0 },
],
options: [],
async initFn(formItem) {
formItem.options = await DictAPI.getDictOptions("gender");
},
},
{
label: "角色",
@@ -85,18 +75,10 @@ const modalConfig: IModalConfig<UserForm> = {
placeholder: "请选择",
multiple: true,
},
options: [
{ label: "系统管理员", value: 2 },
{ label: "系统管理员1", value: 4 },
{ 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 },
],
options: [],
async initFn(formItem) {
this.options = await RoleAPI.getOptions();
},
},
{
type: "input",

View File

@@ -22,6 +22,7 @@ const contentConfig: IContentConfig = {
gender: 1,
status: 1,
status2: 1,
sort: 99,
createTime: 1715647982437,
},
{
@@ -36,13 +37,14 @@ const contentConfig: IContentConfig = {
gender: 0,
status: 0,
status2: 0,
sort: 0,
createTime: 1715648977426,
},
],
});
},
modifyAction(data) {
// console.log("modifyAction:", data);
console.log("modifyAction:", data);
return Promise.resolve(null);
},
cols: [
@@ -89,6 +91,13 @@ const contentConfig: IContentConfig = {
activeText: "启用",
inactiveText: "禁用",
},
{
label: "排序",
align: "center",
prop: "sort",
templet: "input",
inputType: "number",
},
{
label: "创建时间",
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 type { UserForm } from "@/api/user/model";
import type { IModalConfig } from "@/components/PageModal/index.vue";
@@ -43,26 +46,14 @@ const modalConfig: IModalConfig<UserForm> = {
type: "tree-select",
attrs: {
placeholder: "请选择所属部门",
data: [
{
value: 1,
label: "有来技术",
children: [
{
value: 2,
label: "研发部门",
},
{
value: 3,
label: "测试部门",
},
],
},
],
data: [],
filterable: true,
"check-strictly": true,
"render-after-expand": false,
},
async initFn(formItem) {
formItem.attrs.data = await DeptAPI.getOptions();
},
},
{
type: "select",
@@ -71,11 +62,10 @@ const modalConfig: IModalConfig<UserForm> = {
attrs: {
placeholder: "请选择",
},
options: [
{ label: "男", value: 1 },
{ label: "女", value: 2 },
{ label: "未知", value: 0 },
],
options: [],
async initFn(formItem) {
formItem.options = await DictAPI.getDictOptions("gender");
},
},
{
label: "角色",
@@ -86,18 +76,10 @@ const modalConfig: IModalConfig<UserForm> = {
placeholder: "请选择",
multiple: true,
},
options: [
{ label: "系统管理员", value: 2 },
{ label: "系统管理员1", value: 4 },
{ 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 },
],
options: [],
async initFn(formItem) {
this.options = await RoleAPI.getOptions();
},
},
{
type: "input",

View File

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

View File

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