Merge branch 'master' of github.com:youlaitech/vue3-element-admin
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -59,4 +59,6 @@ export type IFormItems<T = any> = Array<{
|
||||
computed?: (data: T) => any;
|
||||
// 监听收集函数
|
||||
watchEffect?: (data: T) => void;
|
||||
// 初始化数据函数扩展
|
||||
initFn?: (formItem: IObject) => void;
|
||||
}>;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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("/");
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user