修复 动态edit页面

This commit is contained in:
小肥羊 2025-08-11 18:24:53 +08:00
parent 78d85e8aac
commit e113af5378
12 changed files with 969 additions and 179 deletions

View File

@ -1,5 +1,6 @@
import { http } from "@/utils/http"; import { http } from "@/utils/http";
import type { Res } from "@/utils/http/types"; import type { Res } from "@/utils/http/types";
import type { ComboModel } from "@/components/hTable/hTable";
export class hTableAPI { export class hTableAPI {
url = ""; url = "";
@ -10,8 +11,8 @@ export class hTableAPI {
PageList(data = {}) { PageList(data = {}) {
return http.request<Res<any>>("post", `${this.url}/PageList`, { data }); return http.request<Res<any>>("post", `${this.url}/PageList`, { data });
} }
Info(id, tag = {}) { Info(tag = {}) {
const pUrl = `${this.url}/${id}`; const pUrl = `${this.url}/Info`;
let getUrl = pUrl; let getUrl = pUrl;
for (const key in tag) { for (const key in tag) {
const el = tag[key]; const el = tag[key];
@ -26,6 +27,8 @@ export class hTableAPI {
return http.request<Res<any>>("post", `${this.url}/Del`, { data }); return http.request<Res<any>>("post", `${this.url}/Del`, { data });
} }
querycombo(data) { querycombo(data) {
return http.request<Res<any>>("post", `${this.url}/QueryCombo`, { data }); return http.request<Res<ComboModel[]>>("post", `${this.url}/QueryCombo`, {
data
});
} }
} }

View File

@ -3,7 +3,7 @@ import type { Res } from "@/utils/http/types";
// 定义菜单项接口 // 定义菜单项接口
export interface MenuItem { export interface MenuItem {
id: number; id?: number;
name: string; name: string;
path?: string; path?: string;
isButton: boolean; isButton: boolean;
@ -22,3 +22,11 @@ export interface MenuItem {
export function MenuAll() { export function MenuAll() {
return http.request<Res<MenuItem[]>>("get", `Menu/All`); return http.request<Res<MenuItem[]>>("get", `Menu/All`);
} }
/**
* @description
* @return {object}
*/
export function Edit(info: MenuItem) {
return http.request<Res<MenuItem[]>>("post", `Menu/Edit`, { data: info });
}

37
src/api/school.ts Normal file
View File

@ -0,0 +1,37 @@
import { http } from "@/utils/http";
import type { Res } from "@/utils/http/types";
/**
* @description
* @param {string} type type=StatusEnum
* @return {object}
*/
export function getenum(type) {
return http.request<Res<any>>("get", `public/enum/${type}`);
}
/**
* @description
* @param {string} type type=StatusEnum
* @return {object}
*/
export function getenumDic(type) {
return http.request<Res<any>>("get", `public/enum/${type}/Dic`);
}
export function getProvince() {
return http.request<Res<any>>("get", `address/province`);
}
export function getcity(c) {
return http.request<Res<any>>("get", `address/${c}/city`);
}
export function getregion(r) {
return http.request<Res<any>>("get", `address/${r}/region`);
}
/**
* @description
* @return {void}
*/
export function EditSchool(data: any) {
return http.request<Res<any>>("post", `School/Edit`, { data });
}

View File

@ -78,14 +78,14 @@ export interface FieldSetting {
*/ */
imgUrl?: (value: any, row: any) => string; imgUrl?: (value: any, row: any) => string;
/* 数据源 */ /* 数据源 */
datasource?: Array<{ datasource?: ComboModel[];
Value: any;
Text: string;
}>;
} }
///* 表格列配置 */ ///* 表格列配置 */
//export interface TableEditColumn {} //export interface TableEditColumn {}
export interface ComboModel {
Value: any;
Text: string;
}
/* 表格列配置 */ /* 表格列配置 */
export interface TableColumn { export interface TableColumn {
/* 显示标签 */ /* 显示标签 */
@ -103,7 +103,7 @@ export interface TableColumn {
/* 是否允许添加 */ /* 是否允许添加 */
add: boolean; add: boolean;
/* 是否允许修改 */ /* 是否允许修改 */
edit: boolean; edit?: boolean;
/* 列宽度 */ /* 列宽度 */
width?: string; width?: string;
/* 字段类型 */ /* 字段类型 */
@ -118,9 +118,9 @@ export interface TableColumn {
/* 字段设置 */ /* 字段设置 */
setting?: FieldSetting; setting?: FieldSetting;
/* 修改时的编辑值 */ /* 修改时的编辑值 */
valueE?: Array<string> | string; valueE?: Array<string> | string | number | boolean | Date;
/* 查询值 */ /* 查询值 */
value?: Array<string> | string; value?: Array<string> | string | number | boolean | Date;
/** textarea编辑时的行数 */ /** textarea编辑时的行数 */
editRows?: number; editRows?: number;
/**编辑时值发生变化 */ /**编辑时值发生变化 */
@ -179,4 +179,6 @@ export interface TableConfig {
selectRows: any[]; selectRows: any[];
/* 是否显示边框 */ /* 是否显示边框 */
border: boolean; border: boolean;
/**是否显示 */
show?: boolean;
} }

View File

@ -25,7 +25,7 @@ const props = defineProps({
}); });
const emit = defineEmits(["handlePagedCallback"]); const emit = defineEmits(["handlePagedCallback"]);
const editFormRef = ref<FormInstance>(); const editFormRef = ref<FormInstance>();
const column: Record<string, TableColumn> = {}; const column = ref<Record<string, TableColumn>>({});
const editData = ref({ const editData = ref({
frorm: {}, frorm: {},
isedit: props.id !== -1, isedit: props.id !== -1,
@ -118,7 +118,7 @@ function fetchFormData() {
editData.value.loading = false; editData.value.loading = false;
handleResetForm(); handleResetForm();
if (editData.value.isedit) { if (editData.value.isedit) {
Api.Info(props.id).then(res => { Api.Info({ id: props.id }).then(res => {
if (res.code === 200) { if (res.code === 200) {
editData.value.frorm = res.data; editData.value.frorm = res.data;
for (const key in column.value) { for (const key in column.value) {
@ -141,7 +141,6 @@ function fetchFormData() {
ref="editFormRef" ref="editFormRef"
:model="editData.table.column" :model="editData.table.column"
:label-width="editData.formLabelWidth" :label-width="editData.formLabelWidth"
size="small"
clearable clearable
> >
<el-form-item <el-form-item
@ -154,7 +153,7 @@ function fetchFormData() {
> >
<div v-if="o.type.trim() == 'datetime'"> <div v-if="o.type.trim() == 'datetime'">
<el-date-picker <el-date-picker
v-model="o.valueE" v-model="o.valueE as Date"
format="yyyy-MM-dd HH:mm:ss" format="yyyy-MM-dd HH:mm:ss"
value-format="yyyy-MM-dd HH:mm:ss" value-format="yyyy-MM-dd HH:mm:ss"
type="datetime" type="datetime"

View File

@ -16,7 +16,7 @@ import {
import { Search } from "@element-plus/icons-vue"; import { Search } from "@element-plus/icons-vue";
import { ElMessage, ElMessageBox } from "element-plus"; import { ElMessage, ElMessageBox } from "element-plus";
import { defineAsyncComponent, AsyncComponentLoader } from "vue"; import { defineAsyncComponent, AsyncComponentLoader } from "vue";
import { Dialog, TableConfig } from "./hTable"; import { Dialog, TableColumn, TableConfig } from "./hTable";
import hTableEdit from "./hTableEdit.vue"; import hTableEdit from "./hTableEdit.vue";
import { hTableAPI } from "@/api/hTable"; import { hTableAPI } from "@/api/hTable";
import { getenum } from "@/api/enum"; import { getenum } from "@/api/enum";
@ -33,10 +33,16 @@ const props = defineProps({
}); });
const table = ref<TableConfig>(props.tableConfig); const table = ref<TableConfig>(props.tableConfig);
const tableShowColumn = ref<Record<string, TableColumn>>();
onBeforeMount(() => { onBeforeMount(() => {
/* 初始化系统配置 */ /* 初始化系统配置 */
nextTick(async () => { nextTick(async () => {
intdata(); intdata();
//
tableShowColumn.value = Object.fromEntries(
Object.entries(table.value.column).filter(([_, s]) => s.show)
);
Api = new hTableAPI(table.value.apiUrl); Api = new hTableAPI(table.value.apiUrl);
init.value = true; init.value = true;
tableShow.value = true; tableShow.value = true;
@ -55,7 +61,6 @@ onUnmounted(() => {});
// }); // });
const tableShow = ref(false); const tableShow = ref(false);
let Api: hTableAPI = null; let Api: hTableAPI = null;
const instance = getCurrentInstance();
const init = ref(false); const init = ref(false);
const tableHeight = ref(0); const tableHeight = ref(0);
@ -109,9 +114,9 @@ function intdata() {
if (element.show === undefined) element.show = true; if (element.show === undefined) element.show = true;
if (element.editShow === undefined) element.editShow = true; if (element.editShow === undefined) element.editShow = true;
if (!element.setting) if (!element.setting)
element.setting = { datasource: [], mapValue: "Value", maplabel: "Text" }; element.setting = { datasource: [], mapValue: "value", maplabel: "text" };
if (!element.setting.mapValue) element.setting.mapValue = "Value"; if (!element.setting.mapValue) element.setting.mapValue = "value";
if (!element.setting.maplabel) element.setting.maplabel = "Text"; if (!element.setting.maplabel) element.setting.maplabel = "text";
if (!element.change) element.change = () => {}; if (!element.change) element.change = () => {};
} }
@ -171,7 +176,7 @@ function handleAdd() {
dialog.value.width = "500px"; dialog.value.width = "500px";
} }
function handleEdit(obj, row) { function handleEdit(obj, row) {
dialog.value.edit.id = row[0].Id; dialog.value.edit.id = row[0].id;
dialog.value.edit.row = row[0]; dialog.value.edit.row = row[0];
dialog.value.edit.tagData = obj.tagData; dialog.value.edit.tagData = obj.tagData;
dialog.value.title = obj.label || "修改"; dialog.value.title = obj.label || "修改";
@ -185,7 +190,7 @@ function handleCustom(obj, row, custom) {
dialog.value.custom.custom = custom || []; dialog.value.custom.custom = custom || [];
// //
dialog.value.custom.component = defineAsyncComponent({ dialog.value.custom.component = defineAsyncComponent({
loader: () => import(/* @vite-ignore */ `../${custom.src}.vue`) loader: () => import(/* @vite-ignore */ `../../views/${custom.src}.vue`)
}); });
dialog.value.width = custom.width; dialog.value.width = custom.width;
dialog.value.title = custom.title; dialog.value.title = custom.title;
@ -206,7 +211,7 @@ function handleDelete(obj, row) {
} }
const ids: any[] = []; const ids: any[] = [];
row.forEach(it => { row.forEach(it => {
ids.push(it.Id); ids.push(it.id);
}); });
ElMessageBox.confirm("此操作将永久删除勾选记录, 是否继续?").then(() => { ElMessageBox.confirm("此操作将永久删除勾选记录, 是否继续?").then(() => {
Api.delete(ids).then(res => { Api.delete(ids).then(res => {
@ -309,12 +314,16 @@ async function fetchInitData() {
element.type === "string" || element.type === "string" ||
element.type === undefined) element.type === undefined)
) { ) {
element.custom = row => { if (element.type === "string" || element.type === undefined)
let sc = element.setting.datasource.find( element.custom = row => row[key];
s => s[element.setting.mapValue] + "" == row[key] + "" else {
); element.custom = row => {
return !sc ? row[key] : sc[element.setting.maplabel]; let sc = element.setting.datasource.find(
}; s => s[element.setting.mapValue] + "" == row[key] + ""
);
return !sc ? row[key] : sc[element.setting.maplabel];
};
}
} else if (element.custom == undefined) { } else if (element.custom == undefined) {
element.custom = row => row[key]; element.custom = row => row[key];
} }
@ -370,7 +379,7 @@ function fetchPagedData() {
> >
<el-date-picker <el-date-picker
v-if="o.type.trim() == 'datetime'" v-if="o.type.trim() == 'datetime'"
v-model="o.value" v-model="o.value as Date"
format="yyyy-MM-dd" format="yyyy-MM-dd"
value-format="yyyy-MM-dd" value-format="yyyy-MM-dd"
type="date" type="date"
@ -417,6 +426,39 @@ function fetchPagedData() {
> >
</el-form-item> </el-form-item>
</el-form> </el-form>
<div class="dialog-container">
<el-dialog
v-if="dialog.visible"
v-model="dialog.visible"
:class="dialog.title ? '' : 'noHeader'"
:title="dialog.title"
:width="dialog.width"
:close-on-click-modal="false"
:close-on-press-escape="dialog.close"
:before-close="tableClose"
append-to-body
>
<hTableEdit
v-if="dialog.edit.visible"
:id="dialog.edit.id"
:tableData="table"
:row="dialog.edit.row"
:tagData="dialog.edit.tagData"
@handlePagedCallback="handleAddCallback"
/>
<component
:is="dialog.custom.component"
v-if="dialog.custom.visible"
:style="{ height: 'calc( ' + dialog.custom.height + ' - 84px )' }"
:iscomponent="true"
:custom="dialog.custom.custom"
:CancelCallback="handleAddCallback"
:data="dialog.custom.data"
@handlePagedCallback="handleAddCallback"
/>
</el-dialog>
</div>
</div> </div>
<div v-if="table.operationTop" class="toolbar-container"> <div v-if="table.operationTop" class="toolbar-container">
@ -427,7 +469,8 @@ function fetchPagedData() {
:key="i" :key="i"
:type="e.btnStyle || 'info'" :type="e.btnStyle || 'info'"
@click="getbtnClick(e, null)" @click="getbtnClick(e, null)"
>{{ e.label }}</el-button> >{{ e.label }}</el-button
>
</div> </div>
<el-table <el-table
v-if="init" v-if="init"
@ -467,8 +510,7 @@ function fetchPagedData() {
</template> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column
v-for="(item, name, i) of table.column" v-for="(item, name, i) of tableShowColumn"
v-show="item.show"
:key="i" :key="i"
:prop="name" :prop="name"
:label="item.label" :label="item.label"
@ -480,12 +522,6 @@ function fetchPagedData() {
<div v-if="item.type.trim() == 'math'"> <div v-if="item.type.trim() == 'math'">
{{ item.custom(scope.row) }} {{ item.custom(scope.row) }}
</div> </div>
<!-- <ShowMathJax
v-if="item.type.trim() == 'math'"
style="zoom: 0.8"
:str="item.custom(scope.row)"
:divId="'MathJax_' + scope.row.Id"
/> -->
<el-image <el-image
v-else-if="item.type.trim() == 'img'" v-else-if="item.type.trim() == 'img'"
style="width: 300px; height: 100px" style="width: 300px; height: 100px"
@ -513,7 +549,6 @@ function fetchPagedData() {
{{ item.custom(scope.row) }} {{ item.custom(scope.row) }}
</div> </div>
</el-tooltip> </el-tooltip>
<!-- <p ></p> -->
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
@ -529,41 +564,6 @@ function fetchPagedData() {
@current-change="pageIndexChange" @current-change="pageIndexChange"
/> />
</div> </div>
<div class="dialog-container">
<el-dialog
v-if="dialog.visible"
ref="elDialog"
v-model:visible="dialog.visible"
:class="dialog.title ? '' : 'noHeader'"
:title="dialog.title"
:width="dialog.width"
:close-on-click-modal="false"
:close-on-press-escape="dialog.close"
:before-close="tableClose"
append-to-body
>
<hTableEdit
v-if="dialog.edit.visible"
:id="dialog.edit.id"
:data="table"
:tagData="dialog.edit.tagData"
:row="dialog.edit.row"
@handlePagedCallback="handleAddCallback"
/>
/>
<component
:is="dialog.custom.component"
v-if="dialog.custom.visible"
ref="custom"
:style="{ height: 'calc( ' + dialog.custom.height + ' - 84px )' }"
:iscomponent="true"
:custom="dialog.custom.custom"
:CancelCallback="handleAddCallback"
:data="dialog.custom.data"
@handlePagedCallback="handleAddCallback"
/>
</el-dialog>
</div>
</div> </div>
</template> </template>

View File

@ -29,15 +29,6 @@ export default {
title: "学校", title: "学校",
showLink: true showLink: true
} }
},
{
path: "/menu",
name: "Menu",
component: () => import("@/views/menu/index.vue"),
meta: {
title: "菜单",
showLink: true
}
} }
] ]
} satisfies RouteConfigsTable; } satisfies RouteConfigsTable;

138
src/views/admin/index.vue Normal file
View File

@ -0,0 +1,138 @@
<script setup lang="ts">
import ahTable from "@/components/hTable/index.vue";
import { TableConfig } from "@/components/hTable/hTable";
import { onMounted, ref } from "vue";
import { fa } from "element-plus/es/locales.mjs";
import { hTableAPI } from "@/api/hTable";
const ControllerName = "Admin";
defineOptions({
name: ControllerName
});
function searchCallback(data) {
// let c = data.Conditions.find(s => s.FieldName === "Enable");
// if (c) {
// if (c.FieldValue == "true") {
// c.FieldValue = 1;
// } else {
// c.FieldValue = 0;
// }
// }
}
const RoleApi = new hTableAPI("AdminRole");
const table = ref<{ initTable: (config: TableConfig) => void }>(null);
const tableData: TableConfig = {
apiUrl: ControllerName,
selectColumn: false, //
border: false, //
searchCallback: searchCallback,
search: {
//
show: true,
PageIndex: 0,
PageSize: 20,
OrderBy: "CreateTime", //
defaultConditions: [], //
Conditions: []
},
operationColumn: true, //
operationColumnData: [
{
//
topBtn: false, //
label: "修改",
btnType: "edit" // add edit del custom
},
{
//
topBtn: true, //
label: "添加",
btnStyle: "success",
btnType: "add" // add edit del custom
},
{
topBtn: false, //
show: true,
label: "删除",
btnType: "del", // add edit del
btnStyle: "danger" // topBtn: true success danger
}
],
column: {
//
id: {
label: "编号",
search: true,
add: false, //
edit: false, //
width: "150px"
},
name: {
label: "名称",
width: "180px",
search: true,
searchType: "Like",
add: true, //
edit: true //
},
Phone: {
label: "手机号",
width: "200px",
search: true,
add: true, //
edit: true //
},
account: {
label: "账号",
search: true,
add: true, //
edit: false //
},
password: {
label: "密码",
show: false,
search: false,
add: true, //
edit: false //
},
enable: {
label: "启用",
type: "switch",
search: false,
add: false, //
edit: true, //
valueE: true //
},
roleId: {
label: "角色",
type: "dropdown",
search: true,
add: true, //
edit: false, //
setting: {
datasource: []
}
}
},
data: [],
pageData: {
total: 0
},
selectRows: []
};
const showTable = ref(false);
onMounted(async () => {
//
tableData.column.roleId.setting.datasource = (
await RoleApi.querycombo({ TextName: "Name", ValueName: "Id" })
).data;
showTable.value = true;
});
</script>
<template>
<div><ahTable v-if="showTable" ref="table" :tableConfig="tableData" /></div>
</template>

371
src/views/menu/edit.vue Normal file
View File

@ -0,0 +1,371 @@
<template>
<div class="menu-edit-container">
<el-form
ref="menuFormRef"
:model="menuForm"
:rules="formRules"
label-position="top"
class="menu-form"
>
<el-row :gutter="30">
<!-- 左侧表单区域 -->
<el-col :span="12">
<el-form-item label="路由名称" prop="name">
<el-input
v-model="menuForm.name"
placeholder="请输入路由唯一名称"
clearable
/>
</el-form-item>
<el-form-item label="菜单标题" prop="title">
<el-input
v-model="menuForm.title"
placeholder="请输入菜单标题"
clearable
/>
</el-form-item>
<el-form-item label="路由路径" prop="path">
<el-input
v-model="menuForm.path"
placeholder="请输入路由路径,如 /dashboard"
clearable
/>
<div class="form-tip">如果是按钮权限可留空</div>
</el-form-item>
<el-form-item label="图标" prop="icon">
<el-input
v-model="menuForm.icon"
placeholder="请输入图标类名,如 el-icon-menu"
clearable
>
</el-input>
<div v-if="menuForm.icon" class="icon-preview">
<i
:class="menuForm.icon"
style="font-size: 24px; margin-top: 8px"
></i>
</div>
</el-form-item>
<el-form-item label="授权码" prop="auths">
<el-input
v-model="menuForm.auths"
placeholder="请输入授权码,多个用逗号分隔"
clearable
/>
<div class="form-tip">按钮权限需要的授权码</div>
</el-form-item>
</el-col>
<!-- 右侧表单区域 -->
<el-col :span="12">
<el-form-item label="父级菜单" prop="parentId">
<el-tree-select
v-model="menuForm.parentId"
:data="menuTreeData"
:props="treeProps"
check-strictly
placeholder="请选择父级菜单"
clearable
:expand-on-click-node="false"
:render-after-expand="false"
:default-expand-all="true"
:highlight-current="true"
/>
</el-form-item>
<el-form-item label="排序排名" prop="rank">
<el-input-number
v-model="menuForm.rank"
:min="0"
:max="100"
controls-position="right"
/>
<div class="form-tip">数值越小排名越靠前</div>
</el-form-item>
<el-form-item label="菜单类型">
<el-switch
v-model="menuForm.isButton"
active-text="按钮权限"
inactive-text="常规菜单"
style="
--el-switch-on-color: #13ce66;
--el-switch-off-color: #409eff;
"
/>
</el-form-item>
<el-form-item label="显示状态">
<el-switch
v-model="menuForm.showLink"
active-text="显示"
inactive-text="隐藏"
/>
</el-form-item>
<div v-if="menuForm.isButton" class="button-permission-info">
<el-alert title="按钮权限说明" type="info" :closable="false">
<p>1. 按钮权限不会显示在导航菜单中</p>
<p>2. 需要填写授权码用于权限控制</p>
<p>3. 路由路径可留空</p>
</el-alert>
</div>
</el-col>
</el-row>
</el-form>
<el-divider />
<div class="header">
<div class="header-actions">
<el-button @click="resetForm">重置</el-button>
<el-button type="primary" @click="submitForm" :loading="submitting">
{{ isEditMode ? "更新菜单" : "创建菜单" }}
</el-button>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { defineComponent, ref, reactive, onMounted, onBeforeMount } from "vue";
import { MenuItem, Edit } from "@/api/menu";
import {
ElMessage,
ElMessageBox,
FormInstance,
FormValidateCallback
} from "element-plus";
import propTypes from "@/utils/propTypes";
defineOptions({
name: "MenuEdit"
});
const props = defineProps({
treeData: {
type: Object as PropType<MenuItem[]>
},
info: {
type: Object as PropType<MenuItem>,
default: null
}
});
const emit = defineEmits(["callbackFun"]);
const menuFormRef = ref<FormInstance>();
const menuForm = reactive<MenuItem>({
name: "",
path: "",
isButton: false,
title: "",
icon: "",
auths: "",
rank: 50,
showLink: true,
parentId: undefined
});
const menuTreeData = ref<MenuItem[]>(props.treeData);
const treeProps = {
value: "id",
label: "title",
children: "children"
};
const isEditMode = ref(false);
const submitting = ref(false);
const iconDialogVisible = ref(false);
//
const icons = ref([
"el-icon-menu",
"el-icon-setting",
"el-icon-user",
"el-icon-s-home",
"el-icon-s-data",
"el-icon-s-check",
"el-icon-s-opportunity",
"el-icon-s-order",
"el-icon-s-platform",
"el-icon-s-promotion",
"el-icon-s-shop",
"el-icon-s-marketing",
"el-icon-s-flag",
"el-icon-s-comment",
"el-icon-s-finance"
]);
const formRules = {
name: [
{ required: true, message: "请输入菜单名称", trigger: "blur" },
{ min: 2, max: 20, message: "长度在 2 到 20 个字符", trigger: "blur" }
],
title: [
{ required: true, message: "请输入菜单标题", trigger: "blur" },
{ min: 2, max: 20, message: "长度在 2 到 20 个字符", trigger: "blur" }
],
path: [
{
validator: (rule: any, value: string, callback: any) => {
if (!menuForm.isButton && !value) {
callback(new Error("常规菜单必须填写路由路径"));
} else {
callback();
}
},
trigger: "blur"
}
],
auths: [
{
validator: (rule: any, value: string, callback: any) => {
if (menuForm.isButton && !value) {
callback(new Error("按钮权限必须填写授权码"));
} else {
callback();
}
},
trigger: "blur"
}
],
rank: [{ required: true, message: "请输入排序值", trigger: "blur" }]
};
//
onMounted(() => {
Object.assign(menuForm, props.info);
if (props.info != null && props.info.id > 0) {
isEditMode.value = true;
} else menuForm.id = 0;
});
const submitForm = () => {
menuFormRef.value?.validate(valid => {
if (valid) {
submitting.value = true;
Edit(menuForm).then(res => {
submitting.value = false;
ElMessage.success(
isEditMode.value ? "菜单更新成功!" : "菜单创建成功!"
);
goBack();
});
} else {
ElMessage.warning("请正确填写表单内容");
}
});
};
//
const resetForm = () => {
ElMessageBox.confirm("确定要重置表单内容吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
menuFormRef.value?.resetFields();
if (!isEditMode.value) {
Object.assign(menuForm, {
isButton: false,
showLink: true,
rank: 50,
parentId: menuForm.parentId
});
}
});
};
//
const goBack = () => emit("callbackFun"); // ;
</script>
<style scoped>
.menu-edit-container {
max-width: 1200px;
margin: 0 auto;
}
.header {
display: flex;
justify-content: flex-end;
align-items: center;
margin-bottom: 20px;
}
.header h1 {
margin: 0;
font-size: 24px;
color: #303133;
}
.header-actions {
display: flex;
gap: 10px;
}
.menu-form {
margin-top: 20px;
}
.form-tip {
font-size: 12px;
color: #909399;
margin-top: 5px;
}
.button-permission-info {
margin-top: 20px;
padding: 15px;
background-color: #f4f4f5;
border-radius: 4px;
}
.icon-grid {
display: grid;
grid-template-columns: repeat(6, 1fr);
gap: 15px;
max-height: 400px;
overflow-y: auto;
}
.icon-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 15px 10px;
border: 1px solid #ebeef5;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s;
}
.icon-item:hover {
background-color: #ecf5ff;
border-color: #409eff;
}
.icon-item.selected {
background-color: #ecf5ff;
border-color: #409eff;
color: #409eff;
}
.icon-item i {
font-size: 24px;
margin-bottom: 8px;
}
.icon-name {
font-size: 12px;
text-align: center;
word-break: break-all;
}
.el-alert p {
margin: 5px 0;
font-size: 13px;
line-height: 1.5;
}
</style>

View File

@ -1,6 +1,9 @@
<template> <template>
<div> <div>
<div style="padding-bottom: 5px;"> <div style="padding-bottom: 5px">
<el-button type="success" @click="() => showDialog(null)">
新增根菜单
</el-button>
<el-button type="primary"> 分配权限 </el-button> <el-button type="primary"> 分配权限 </el-button>
</div> </div>
<el-tree <el-tree
@ -9,6 +12,7 @@
node-key="id" node-key="id"
:default-expand-all="true" :default-expand-all="true"
:highlight-current="true" :highlight-current="true"
:expand-on-click-node="false"
show-checkbox show-checkbox
class="menu-tree" class="menu-tree"
> >
@ -16,82 +20,75 @@
<div class="menu-node"> <div class="menu-node">
<i v-if="data.icon" :class="data.icon" class="menu-icon"></i> <i v-if="data.icon" :class="data.icon" class="menu-icon"></i>
<span class="menu-title">{{ data.title }}</span> <span class="menu-title">{{ data.title }}</span>
<span class="menu-path-Rank" v-if="data.rank"
>排序[{{ data.rank }}]</span
>
<span class="menu-path" v-if="data.path">{{ data.path }}</span> <span class="menu-path" v-if="data.path">{{ data.path }}</span>
</div> </div>
<div style="display: flex; gap: 6px"> <div style="display: flex; gap: 6px">
<el-button type="success" link @click="() => {}"> 添加 </el-button> <el-button
<el-button type="primary" link @click="() => {}"> 编辑 </el-button> type="success"
link
@click="() => showDialog({ parentId: data.id } as MenuItem)"
>
添加
</el-button>
<el-button type="primary" link @click="() => showDialog(data)">
编辑
</el-button>
<el-button type="danger" link @click="() => {}"> 删除 </el-button> <el-button type="danger" link @click="() => {}"> 删除 </el-button>
</div> </div>
</template> </template>
</el-tree> </el-tree>
<el-dialog
v-if="dialogData.visible"
v-model="dialogData.visible"
title="菜单编辑"
width="800"
:before-close="handleClose"
>
<MenuEdit
@callbackFun="EditCallback"
:info="dialogData.info"
:treeData="treeData"
></MenuEdit>
</el-dialog>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { MenuAll, MenuItem } from "@/api/menu"; import { MenuAll, MenuItem } from "@/api/menu";
import { ElMessage } from "element-plus"; import { ElMessage, ElMessageBox } from "element-plus";
import MenuEdit from "./edit.vue";
import { ref, computed, onMounted } from "vue"; import { ref, computed, onMounted } from "vue";
defineOptions({ defineOptions({
name: "Menu" name: "Menu"
}); });
// API /** 显示弹窗 */
const mockMenuData: MenuItem[] = [ const dialogData = ref({
{ visible: false,
id: 1, info: null
name: "dashboard", });
path: "/dashboard", function showDialog(info: MenuItem) {
isButton: false, dialogData.value.visible = true;
title: "控制台", dialogData.value.info = info;
icon: "el-icon-monitor", }
rank: 1, async function EditCallback(info: MenuItem) {
showLink: true, await fetchInitData();
parentId: 0 dialogData.value.visible = false;
}, }
{ const handleClose = (done: () => void) => {
id: 2, done();
name: "system", // ElMessageBox.confirm("?")
path: "/system", // .then(() => {
isButton: false, // done();
title: "系统管理", // })
icon: "el-icon-setting", // .catch(() => {
rank: 2, // // catch error
showLink: true, // });
parentId: 0 };
},
{
id: 3,
name: "user",
path: "/system/user",
isButton: false,
title: "用户管理",
icon: "el-icon-user",
rank: 1,
showLink: true,
parentId: 2
},
{
id: 4,
name: "role",
path: "/system/role",
isButton: false,
title: "角色管理",
icon: "el-icon-s-custom",
rank: 2,
showLink: true,
parentId: 2
},
{
id: 5,
name: "createUser",
isButton: true,
title: "创建用户",
rank: 1,
showLink: true,
parentId: 3
}
];
// //
const convertToTree = (menus: MenuItem[]): MenuItem[] => { const convertToTree = (menus: MenuItem[]): MenuItem[] => {
@ -137,26 +134,16 @@ const treeProps = {
label: "title", label: "title",
children: "children" children: "children"
}; };
async function fetchInitData() {
// API
const fetchMenuData = async (): Promise<MenuItem[]> => {
// API:
// const response = await fetch('/api/menus')
// return await response.json()
// 使
return new Promise(resolve => {
setTimeout(() => resolve(mockMenuData), 300);
});
};
onMounted(async () => {
const flatData = await MenuAll(); const flatData = await MenuAll();
if (flatData.code === 200) { if (flatData.code === 200) {
treeData.value = convertToTree(flatData.data); treeData.value = convertToTree(flatData.data);
} else { } else {
ElMessage.error(flatData.message); ElMessage.error(flatData.message);
} }
}
onMounted(async () => {
await fetchInitData();
}); });
</script> </script>
@ -185,11 +172,16 @@ onMounted(async () => {
margin-right: 10px; margin-right: 10px;
} }
.menu-path-Rank {
font-size: 13px;
margin-right: 10px;
color: #909399;
}
.menu-path { .menu-path {
font-size: 12px; font-size: 12px;
color: #909399; color: #909399;
margin-right: 10px; margin-right: 10px;
flex-grow: 1; flex-grow: 0;
} }
.menu-tree { .menu-tree {
padding: 20px; /* 增加内边距 */ padding: 20px; /* 增加内边距 */

View File

@ -0,0 +1,255 @@
<template>
<div>
<el-form
ref="schoolAddForm"
:model="eData.form"
:label-width="eData.formLabelWidth"
clearable
>
<el-form-item
label="学校名称:"
prop="Name"
:rules="eData.rules.Required"
>
<el-input
@change="inputChange"
type="text"
v-model="eData.form.Name"
autocomplete="off"
maxlength="20"
:show-word-limit="true"
/>
</el-form-item>
<el-form-item label="地区" :rules="eData.rules.Required" prop="pname">
<el-col :span="24">
<div style="display: flex; gap: 10px">
<el-select
v-model="eData.form.pid"
clearable
@change="v => selectChange(v, 1)"
filterable
placeholder="省份"
style="width: 180px"
>
<el-option
v-for="item in eData.LocaArr1"
:key="item.id"
autocomplete="off"
:label="item.Text"
:value="item.Value"
>
</el-option>
</el-select>
<el-select
v-model="eData.form.cid"
clearable
@change="v => selectChange(v, 2)"
filterable
placeholder="市"
style="width: 180px"
>
<el-option
v-for="item in eData.LocaArr2"
:key="item.id"
autocomplete="off"
:label="item.Text"
:value="item.Value"
>
</el-option>
</el-select>
<el-select
v-model="eData.form.rid"
clearable
@change="v => selectChange(v, 3)"
filterable
placeholder="区"
style="width: 180px"
>
<el-option
v-for="item in eData.LocaArr3"
:key="item.id"
autocomplete="off"
:label="item.Text"
:value="item.Value"
>
</el-option>
</el-select>
</div>
</el-col>
</el-form-item>
<el-form-item label="启用:" prop="Enable">
<el-switch
v-model="eData.form.Enable"
active-text="启用"
inactive-text="禁用"
>
</el-switch>
</el-form-item>
<div style="padding-left: 4rem">
创建学校会生成一个默认管理员账号
<br />
<br />
</div>
<el-form-item>
<el-button
type="primary"
:loading="eData.loading"
@click="handleSubmitForm()"
>立即提交</el-button
>
<el-button @click="handleResetForm()">重置</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script setup lang="ts">
import { getregion, getcity, getProvince, EditSchool } from "@/api/school";
import { ElMessage, FormInstance } from "element-plus";
import { onMounted, ref } from "vue";
const props = defineProps({
id: {
type: Number,
default: -1
},
row: {
type: Object as PropType<any[]>,
default: null
}
});
const eData = ref({
LocaArr1: [],
LocaArr2: [],
LocaArr3: [],
rules: {
Required: {
required: true,
message: "必填项",
trigger: "blur"
}
},
formLabelWidth: "120px",
size: "small",
loading: false,
form: {
Name: "",
id: props.id,
pid: "",
cid: "",
rid: "",
pname: "",
cname: "",
rname: "",
Enable: true
}
});
onMounted(() => {
fetchInitData();
fetchFormData();
});
async function selectChange(value, index) {
let nid = value;
let GetInfo = getcity;
// eslint-disable-next-line no-unused-vars
let loc = eData.value.LocaArr2;
if (index == 1) {
eData.value.form.pname =
value == "" ? "" : eData.value.LocaArr1.find(s => s.Value == value).Text;
eData.value.form.cname = "";
eData.value.form.rname = "";
eData.value.form.cid = "";
eData.value.form.rid = "";
} else if (index == 2) {
loc = eData.value.LocaArr3;
GetInfo = getregion;
eData.value.form.cname =
value == "" ? "" : eData.value.LocaArr2.find(s => s.Value == value).Text;
eData.value.form.rname = "";
eData.value.form.rid = "";
} else {
eData.value.form.rname =
value == "" ? "" : eData.value.LocaArr3.find(s => s.Value == value).Text;
}
if (value == "" && index == 3) return;
let nodes = (await GetInfo(nid)).data.map(item => ({
Value: item.rid || item.cid || item.pid,
Text: item.rname || item.cname || item.pname
}));
loc.splice(0, loc.length);
loc.push(...nodes);
}
async function inputChange() {
if (eData.value.form.pname == "") {
let p = eData.value.LocaArr1.find(s =>
eData.value.form.Name.includes(s.Text)
);
if (!p) return;
eData.value.form.pid = p.Value;
await selectChange(eData.value.form.pid, 1);
let p1 = eData.value.LocaArr2.find(s =>
eData.value.form.Name.includes(s.Text)
);
if (!p1) return;
eData.value.form.cid = p1.Value;
await selectChange(eData.value.form.cid, 2);
let p2 = eData.value.LocaArr3.find(s =>
eData.value.form.Name.includes(s.Text)
);
if (!p2) return;
eData.value.form.rid = p2.Value;
await selectChange(eData.value.form.rid, 3);
}
}
const emit = defineEmits(["handlePagedCallback"]);
function handlePagedCallback() {
emit("handlePagedCallback");
}
const schoolAddForm = ref<FormInstance>();
function handleSubmitForm() {
schoolAddForm.value.validate(valid => {
if (valid) {
eData.value.loading = true;
let ids = ["pid", "cid", "rid"];
for (const key of ids) {
eData.value.form[key] =
eData.value.form[key] == "" ? 0 : eData.value.form[key];
}
if (!(props.id !== -1)) {
eData.value.form.id = 0;
}
EditSchool(eData.value.form).then(res => {
eData.value.loading = false;
if (res.code === 200) {
ElMessage.success("操作成功");
handlePagedCallback();
} else {
ElMessage.error(res.message);
}
});
}
});
}
function handleResetForm() {}
async function fetchInitData() {
eData.value.LocaArr1 = (await getProvince()).data.map(item => ({
Value: item.rid || item.cid || item.pid,
Text: item.rname || item.cname || item.pname
}));
}
function fetchFormData() {
handleResetForm();
if (props.id !== -1) {
eData.value.form = props.row[0];
}
}
</script>

View File

@ -2,6 +2,7 @@
import ahTable from "@/components/hTable/index.vue"; import ahTable from "@/components/hTable/index.vue";
import { TableConfig } from "@/components/hTable/hTable"; import { TableConfig } from "@/components/hTable/hTable";
import { onMounted, ref } from "vue"; import { onMounted, ref } from "vue";
import { fa } from "element-plus/es/locales.mjs";
defineOptions({ defineOptions({
name: "School" name: "School"
}); });
@ -42,24 +43,17 @@ const tableData: TableConfig = {
}, },
{ {
topBtn: true, // topBtn: true, //
show: true, label: "新增学校",
label: "新增", btnType: "custom", // add edit del custom
btnType: "add", // add edit del custom btnStyle: "success", // topBtn: true success danger
btnStyle: "success" // topBtn: true success danger custom: {
// custom
title: "新增学校", // title
src: "school/SchoolEdit", //
width: "550px", //
height: "300px" //
}
}, },
// {
// topBtn: true, //
// label: "",
// btnType: "custom", // add edit del custom
// btnStyle: "success", // topBtn: true success danger
// custom: {
// // custom
// title: "", // title
// src: "school/SchoolEdit", //
// width: "550px", //
// height: "300px" //
// }
// },
{ {
topBtn: false, // topBtn: false, //
show: true, show: true,
@ -73,8 +67,8 @@ const tableData: TableConfig = {
id: { id: {
label: "编号", label: "编号",
search: true, search: true,
add: true, // add: false, //
edit: true, // edit: false, //
width: "150px" width: "150px"
}, },
name: { name: {