3.8号码提交
This commit is contained in:
parent
2bf41d8fb4
commit
ef84a0b097
|
|
@ -11,7 +11,7 @@ export interface MenuFormParams {
|
||||||
type: number; // 类型:1菜单2按钮(number类型)
|
type: number; // 类型:1菜单2按钮(number类型)
|
||||||
status?: number; // 状态:1启用2禁用(可选,编辑时用)
|
status?: number; // 状态:1启用2禁用(可选,编辑时用)
|
||||||
id?: number; // 菜单ID(仅编辑/删除时传)
|
id?: number; // 菜单ID(仅编辑/删除时传)
|
||||||
parent_id?: number | null; // 上级菜单ID(number类型)
|
parent_id?: string | null; // 上级菜单ID(number类型)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除菜单参数类型 */
|
/** 删除菜单参数类型 */
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="父级菜单" prop="parentId">
|
<el-form-item label="父级菜单" prop="parentId">
|
||||||
<el-select v-model="menuForm.parentId" placeholder="请选择父级菜单" style="width:100%">
|
<el-select v-model="menuForm.parentId" placeholder="请选择父级菜单" style="width:100%">
|
||||||
<el-option label="一级菜单" value="0" />
|
<el-option label="主菜单" value="0" />
|
||||||
<el-option v-for="item in menuTree" :key="item.id" :label="item.name" :value="item.id + ''" />
|
<el-option v-for="item in menuTree" :key="item.id" :label="item.name" :value="item.id + ''" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
@ -67,10 +67,21 @@
|
||||||
<el-input v-model="menuForm.api" placeholder="例如:/api/system/menu/list(可选)" />
|
<el-input v-model="menuForm.api" placeholder="例如:/api/system/menu/list(可选)" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="图标名称" prop="icon">
|
<el-form-item label="图标名称" prop="icon">
|
||||||
<el-input v-model="menuForm.icon" placeholder="例如:Tools、List" />
|
<el-select v-model="menuForm.icon" placeholder="请选择图标(二级菜单可留空)" style="width: 100%" clearable>
|
||||||
|
<el-option v-for="item in iconOptions" :key="item.value" :label="item.label" :value="item.value">
|
||||||
|
<template #label>
|
||||||
|
<span style="display: flex; align-items: center; gap: 8px;">
|
||||||
|
<el-icon v-if="item.icon">
|
||||||
|
<component :is="item.icon" />
|
||||||
|
</el-icon>
|
||||||
|
<span>{{ item.label }}</span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="排序" prop="sort">
|
<el-form-item label="排序" prop="sort">
|
||||||
<el-input-number v-model="menuForm.sort" :min="0" placeholder="数字越小越靠前(默认1)" />
|
<el-input-number v-model="menuForm.sort" :min="0" placeholder="数字越小越靠前(默认1)" style="width: 100%" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="菜单类型" prop="type">
|
<el-form-item label="菜单类型" prop="type">
|
||||||
<el-radio-group v-model="menuForm.type">
|
<el-radio-group v-model="menuForm.type">
|
||||||
|
|
@ -81,7 +92,7 @@
|
||||||
|
|
||||||
</el-form>
|
</el-form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button @click="dialogVisible = false">取消</el-button>
|
<el-button @click="() => { { dialogVisible = false, dialogType = 'add' } }">取消</el-button>
|
||||||
<el-button type="primary" @click="handleSubmitMenu">确定</el-button>
|
<el-button type="primary" @click="handleSubmitMenu">确定</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
@ -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 { Tools, List, HomeFilled, UserFilled, Platform, BellFilled, GoodsFilled, WarningFilled, Plus } from '@element-plus/icons-vue'
|
||||||
|
|
||||||
import { getMenuApi, addMenuApi, deleteMenuApi, editMenuApi } from '@/api/modules/menu'
|
import { getMenuApi, addMenuApi, deleteMenuApi, editMenuApi } from '@/api/modules/menu'
|
||||||
|
import { rules } from './rules'
|
||||||
|
|
||||||
//menu数据
|
// 定义图标映射表和选择图标选项列表
|
||||||
const menuTree = ref<any[]>([])
|
|
||||||
|
|
||||||
// 定义菜单表单数据
|
|
||||||
const menuFormRef = ref<FormInstance>()
|
|
||||||
// 核心:表单验证规则
|
|
||||||
const menuFormRules = reactive<FormRules>({
|
|
||||||
// 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<string, any> = {
|
const iconMap: Record<string, any> = {
|
||||||
Tools,
|
Tools,
|
||||||
List,
|
List,
|
||||||
|
|
@ -157,6 +116,36 @@ const iconMap: Record<string, any> = {
|
||||||
GoodsFilled,
|
GoodsFilled,
|
||||||
WarningFilled
|
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<any[]>([])
|
||||||
|
|
||||||
|
// 定义菜单表单数据
|
||||||
|
const menuForm = ref({
|
||||||
|
name: '', // 菜单名称
|
||||||
|
parentId: '0', // 默认一级菜单
|
||||||
|
path: '', // 路由路径
|
||||||
|
api: '', // 后端路由
|
||||||
|
icon: '', // 图标名称
|
||||||
|
sort: 1, // 排序(默认1)
|
||||||
|
type: 1 // 默认是菜单类型
|
||||||
|
})
|
||||||
|
const menuFormRef = ref<FormInstance>()
|
||||||
|
// 核心:表单验证规则
|
||||||
|
const menuFormRules = rules(menuForm.value, iconOptions)
|
||||||
|
|
||||||
// 定义弹窗显示状态
|
// 定义弹窗显示状态
|
||||||
const dialogVisible = ref(false)
|
const dialogVisible = ref(false)
|
||||||
// 新增:区分新增/编辑模式
|
// 新增:区分新增/编辑模式
|
||||||
|
|
@ -172,7 +161,7 @@ onMounted(async () => {
|
||||||
const res = await getMenuApi()
|
const res = await getMenuApi()
|
||||||
menuTree.value = filterEnabledMenus(res)
|
menuTree.value = filterEnabledMenus(res)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ElMessage.error('获取菜单列表失败')
|
console.error('获取菜单列表失败')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -200,6 +189,7 @@ const handleAddMenu = () => {
|
||||||
type: 1
|
type: 1
|
||||||
}
|
}
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
|
console.log('dialogType.value', dialogType.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
//打开编辑弹窗
|
//打开编辑弹窗
|
||||||
|
|
@ -209,7 +199,7 @@ const handleEditMenu = (row: any) => {
|
||||||
|
|
||||||
menuForm.value = {
|
menuForm.value = {
|
||||||
name: row.name,
|
name: row.name,
|
||||||
parentId: row.parentId,
|
parentId: row.parent_id,
|
||||||
path: row.path,
|
path: row.path,
|
||||||
api: row.api,
|
api: row.api,
|
||||||
icon: row.icon,
|
icon: row.icon,
|
||||||
|
|
@ -228,6 +218,8 @@ const handleSubmitMenu = async () => {
|
||||||
...menuForm.value,
|
...menuForm.value,
|
||||||
parent_id: menuForm.value.parentId
|
parent_id: menuForm.value.parentId
|
||||||
})
|
})
|
||||||
|
const res = await getMenuApi()
|
||||||
|
menuTree.value = filterEnabledMenus(res)
|
||||||
} else {
|
} else {
|
||||||
await editMenuApi({
|
await editMenuApi({
|
||||||
id: currentEditId.value || 0,
|
id: currentEditId.value || 0,
|
||||||
|
|
@ -235,6 +227,8 @@ const handleSubmitMenu = async () => {
|
||||||
parent_id: menuForm.value.parentId,
|
parent_id: menuForm.value.parentId,
|
||||||
...menuForm.value
|
...menuForm.value
|
||||||
})
|
})
|
||||||
|
const res = await getMenuApi()
|
||||||
|
menuTree.value = filterEnabledMenus(res)
|
||||||
}
|
}
|
||||||
dialogVisible.value = false
|
dialogVisible.value = false
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
@ -246,6 +240,8 @@ const handleSubmitMenu = async () => {
|
||||||
const handleDeleteMenu = async (row: any) => {
|
const handleDeleteMenu = async (row: any) => {
|
||||||
try {
|
try {
|
||||||
await deleteMenuApi({ id: row.id })
|
await deleteMenuApi({ id: row.id })
|
||||||
|
const res = await getMenuApi()
|
||||||
|
menuTree.value = filterEnabledMenus(res)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log('删除菜单失败', err)
|
console.log('删除菜单失败', err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -156,7 +156,7 @@ const sendCodeApi = async () => {
|
||||||
const sendParams = {
|
const sendParams = {
|
||||||
email: forgotPasswordForm.value.email,
|
email: forgotPasswordForm.value.email,
|
||||||
mobile: forgotPasswordForm.value.phone,
|
mobile: forgotPasswordForm.value.phone,
|
||||||
reg_type: activeTab.value === 'email' ? 1 : 2,
|
reg_type: getStorage('forgotType', 'session') === 'email' ? 1 : 2,
|
||||||
type:'transfer_code'
|
type:'transfer_code'
|
||||||
}
|
}
|
||||||
return userStore.sendCode(sendParams)
|
return userStore.sendCode(sendParams)
|
||||||
|
|
@ -166,10 +166,9 @@ const sendCodeApi = async () => {
|
||||||
const handleSendCode = async () => {
|
const handleSendCode = async () => {
|
||||||
// 第二步:确定类型、接收方、对应的倒计时状态和发送函数
|
// 第二步:确定类型、接收方、对应的倒计时状态和发送函数
|
||||||
const type: string = getStorage('forgotType', 'session') || 'email'
|
const type: string = getStorage('forgotType', 'session') || 'email'
|
||||||
|
|
||||||
const receiver = type === 'phone' ? forgotPasswordForm.value.phone : forgotPasswordForm.value.email;
|
const receiver = type === 'phone' ? forgotPasswordForm.value.phone : forgotPasswordForm.value.email;
|
||||||
const currentCaptchaState = type === 'phone' ? phoneCaptchaState : emailCaptchaState;
|
const currentCaptchaState = type === 'phone' ? phoneCaptchaState : emailCaptchaState;
|
||||||
const sendFunc = type === 'phone' ? "sendPhoneCaptcha" : "sendEmailCaptcha";
|
|
||||||
|
|
||||||
// 第三步:调用封装的发送函数(传正确的函数,而非字符串)
|
// 第三步:调用封装的发送函数(传正确的函数,而非字符串)
|
||||||
await sendCaptcha(
|
await sendCaptcha(
|
||||||
type,
|
type,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue