diff --git a/src/api/types/menu.d.ts b/src/api/types/menu.d.ts index 5267ce1..aac6430 100644 --- a/src/api/types/menu.d.ts +++ b/src/api/types/menu.d.ts @@ -11,7 +11,7 @@ export interface MenuFormParams { type: number; // 类型:1菜单2按钮(number类型) status?: number; // 状态:1启用2禁用(可选,编辑时用) id?: number; // 菜单ID(仅编辑/删除时传) - parent_id?: number | null; // 上级菜单ID(number类型) + parent_id?: string | null; // 上级菜单ID(number类型) } /** 删除菜单参数类型 */ diff --git a/src/views/system/menuManagement/index.vue b/src/views/system/menuManagement/index.vue index 0afe24f..a952cb6 100644 --- a/src/views/system/menuManagement/index.vue +++ b/src/views/system/menuManagement/index.vue @@ -56,7 +56,7 @@ - + @@ -67,10 +67,21 @@ - + + + + + - + @@ -81,7 +92,7 @@ @@ -92,61 +103,9 @@ import type { FormInstance, FormRules } from 'element-plus' import { Tools, List, HomeFilled, UserFilled, Platform, BellFilled, GoodsFilled, WarningFilled, Plus } from '@element-plus/icons-vue' import { getMenuApi, addMenuApi, deleteMenuApi, editMenuApi } from '@/api/modules/menu' +import { rules } from './rules' -//menu数据 -const menuTree = ref([]) - -// 定义菜单表单数据 -const menuFormRef = ref() -// 核心:表单验证规则 -const menuFormRules = reactive({ - // 1. 菜单名称:必填 + 长度限制 - name: [ - { required: true, message: '请输入菜单名称', trigger: 'blur' }, - { min: 2, max: 20, message: '菜单名称长度在 2 到 20 个字符之间', trigger: 'blur' } - ], - // 2. 父级菜单:必填(默认0,无需手动填) - parentId: [{ required: true, message: '请选择父级菜单', trigger: 'change' } - ], - // 3. 路由路径:必填 + 格式校验(以/开头,仅包含字母/数字/下划线/斜杠) - path: [ - { required: true, message: '请输入路由路径', trigger: 'blur' }, - { pattern: /^\/[a-zA-Z0-9_\/]+$/, message: '路由路径必须以/开头,仅包含字母、数字、下划线和斜杠', trigger: 'blur' } - ], - // 4. 后端路由:可选,但填了就必须符合格式 - api: [ - { required: true, pattern: /(^$)|(^\/[a-zA-Z0-9_\/]+$)/, message: '后端路由若填写,必须以/开头,仅包含字母、数字、下划线和斜杠', trigger: 'blur' } - ], - // 5. 图标名称:可选,但填了就必须是指定范围(匹配你提供的图标列表) - icon: [ - { - required: true, - pattern: /(^$)|(^Tools$|^List$|^HomeFilled$|^UserFilled$|^Platform$|^BellFilled$|^GoodsFilled$|^WarningFilled$)/ - , message: '一级菜单有图标二级菜单没有', trigger: 'blur' - } - ], - // 6. 排序:必填 + 数字 + 最小值0 - sort: [ - { required: true, message: '请输入排序值', trigger: 'blur' }, - { type: 'number', min: 0, message: '排序值必须是大于等于0的数字', trigger: 'blur' } - ], - // 7. 菜单类型:必填(单选框默认1,无需手动选) - type: [ - { required: true, message: '请选择菜单类型', trigger: 'change' }, - { type: 'number', enum: [1, 2], message: '菜单类型只能是1(菜单)或2(按钮)', trigger: 'change' } - ] -}) -const menuForm = ref({ - name: '', // 菜单名称 - parentId: '0', // 默认一级菜单 - path: '', // 路由路径 - api: '', // 后端路由 - icon: '', // 图标名称 - sort: 1, // 排序(默认1) - type: 1 // 默认是菜单类型 -}) - -// 定义图标映射表(和侧边栏逻辑一致) +// 定义图标映射表和选择图标选项列表 const iconMap: Record = { Tools, List, @@ -157,6 +116,36 @@ const iconMap: Record = { GoodsFilled, WarningFilled } +// 图标配置(可随时扩展) +const iconOptions = [ + { label: "无图标", value: "无图标" }, + { label: "Tools(工具)", value: "Tools", icon: Tools }, + { label: "List(列表)", value: "List", icon: List }, + { label: "HomeFilled(首页)", value: "HomeFilled", icon: HomeFilled }, + { label: "UserFilled(用户)", value: "UserFilled", icon: UserFilled }, + { label: "Platform(平台)", value: "Platform", icon: Platform }, + { label: "BellFilled(铃铛)", value: "BellFilled", icon: BellFilled }, + { label: "GoodsFilled(商品)", value: "GoodsFilled", icon: GoodsFilled }, + { label: "WarningFilled(警告)", value: "WarningFilled", icon: WarningFilled }, +] + +//menu数据 +const menuTree = ref([]) + +// 定义菜单表单数据 +const menuForm = ref({ + name: '', // 菜单名称 + parentId: '0', // 默认一级菜单 + path: '', // 路由路径 + api: '', // 后端路由 + icon: '', // 图标名称 + sort: 1, // 排序(默认1) + type: 1 // 默认是菜单类型 +}) +const menuFormRef = ref() +// 核心:表单验证规则 +const menuFormRules = rules(menuForm.value, iconOptions) + // 定义弹窗显示状态 const dialogVisible = ref(false) // 新增:区分新增/编辑模式 @@ -172,7 +161,7 @@ onMounted(async () => { const res = await getMenuApi() menuTree.value = filterEnabledMenus(res) } catch (err) { - ElMessage.error('获取菜单列表失败') + console.error('获取菜单列表失败') } }) @@ -200,6 +189,7 @@ const handleAddMenu = () => { type: 1 } dialogVisible.value = true + console.log('dialogType.value', dialogType.value); } //打开编辑弹窗 @@ -209,7 +199,7 @@ const handleEditMenu = (row: any) => { menuForm.value = { name: row.name, - parentId: row.parentId, + parentId: row.parent_id, path: row.path, api: row.api, icon: row.icon, @@ -228,6 +218,8 @@ const handleSubmitMenu = async () => { ...menuForm.value, parent_id: menuForm.value.parentId }) + const res = await getMenuApi() + menuTree.value = filterEnabledMenus(res) } else { await editMenuApi({ id: currentEditId.value || 0, @@ -235,6 +227,8 @@ const handleSubmitMenu = async () => { parent_id: menuForm.value.parentId, ...menuForm.value }) + const res = await getMenuApi() + menuTree.value = filterEnabledMenus(res) } dialogVisible.value = false } catch (err) { @@ -246,6 +240,8 @@ const handleSubmitMenu = async () => { const handleDeleteMenu = async (row: any) => { try { await deleteMenuApi({ id: row.id }) + const res = await getMenuApi() + menuTree.value = filterEnabledMenus(res) } catch (err) { console.log('删除菜单失败', err) } diff --git a/src/views/system/menuManagement/mock.ts b/src/views/system/menuManagement/mock.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/views/system/menuManagement/rules.ts b/src/views/system/menuManagement/rules.ts new file mode 100644 index 0000000..6b03c27 --- /dev/null +++ b/src/views/system/menuManagement/rules.ts @@ -0,0 +1,55 @@ +import type { FormRules } from 'element-plus' + +export const rules = (menuForm: any, iconOptions: any[]): FormRules => { + return { + // 1. 菜单名称:必填 + 长度限制 + name: [ + { required: true, message: '请输入菜单名称', trigger: 'blur' }, + { min: 2, max: 20, message: '菜单名称长度在 2 到 20 个字符之间', trigger: 'blur' } + ], + // 2. 父级菜单:必填(默认0,无需手动填) + parentId: [{ required: true, message: '请选择父级菜单', trigger: 'change' } + ], + // 3. 路由路径:必填 + 格式校验(以/开头,仅包含字母/数字/下划线/斜杠) + path: [ + { required: true, message: '请输入路由路径', trigger: 'blur' }, + { pattern: /^\/[a-zA-Z0-9_\/]+$/, message: '路由路径必须以/开头,仅包含字母、数字、下划线和斜杠', trigger: 'blur' } + ], + // 4. 后端路由:可选,但填了就必须符合格式 + api: [ + { required: true, pattern: /(^$)|(^\/[a-zA-Z0-9_\/]+$)/, message: '后端路由若填写,必须以/开头,仅包含字母、数字、下划线和斜杠', trigger: 'blur' } + ], + // 5. 图标名称:可选,但填了就必须是指定范围(匹配你提供的图标列表) + icon: [ + { + validator: (rule, value, callback) => { + // 一级菜单必须选图标 + if (menuForm.parentId === "0" && !value) { + callback(new Error("一级菜单必须选择图标")) + } + // 若填写了图标,必须是指定范围内的 + else if ( + value && + !iconOptions.some(item => item.value === value) + ) { + callback(new Error("请选择预设图标")) + } + else { + callback() + } + }, + trigger: "change", + }, + ], + // 6. 排序:必填 + 数字 + 最小值0 + sort: [ + { required: true, message: '请输入排序值', trigger: 'blur' }, + { type: 'number', min: 0, message: '排序值必须是大于等于0的数字', trigger: 'blur' } + ], + // 7. 菜单类型:必填(单选框默认1,无需手动选) + type: [ + { required: true, message: '请选择菜单类型', trigger: 'change' }, + { type: 'number', enum: [1, 2], message: '菜单类型只能是1(菜单)或2(按钮)', trigger: 'change' } + ] + } +} \ No newline at end of file diff --git a/src/views/user/ForgotPassword.vue b/src/views/user/ForgotPassword.vue index 0fa7b4b..f3c4e2c 100644 --- a/src/views/user/ForgotPassword.vue +++ b/src/views/user/ForgotPassword.vue @@ -156,7 +156,7 @@ const sendCodeApi = async () => { const sendParams = { email: forgotPasswordForm.value.email, mobile: forgotPasswordForm.value.phone, - reg_type: activeTab.value === 'email' ? 1 : 2, + reg_type: getStorage('forgotType', 'session') === 'email' ? 1 : 2, type:'transfer_code' } return userStore.sendCode(sendParams) @@ -166,10 +166,9 @@ const sendCodeApi = async () => { const handleSendCode = async () => { // 第二步:确定类型、接收方、对应的倒计时状态和发送函数 const type: string = getStorage('forgotType', 'session') || 'email' + const receiver = type === 'phone' ? forgotPasswordForm.value.phone : forgotPasswordForm.value.email; const currentCaptchaState = type === 'phone' ? phoneCaptchaState : emailCaptchaState; - const sendFunc = type === 'phone' ? "sendPhoneCaptcha" : "sendEmailCaptcha"; - // 第三步:调用封装的发送函数(传正确的函数,而非字符串) await sendCaptcha( type,