Skip to content

Commit d72101b

Browse files
YunaiVgitee-org
authored andcommitted
!54 完成菜单模块
Merge pull request !54 from Theo/master
2 parents 9c869de + 8535409 commit d72101b

File tree

3 files changed

+436
-398
lines changed

3 files changed

+436
-398
lines changed

src/views/system/menu/form.vue

Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
<template>
2+
<Dialog :title="modelTitle" v-model="modelVisible">
3+
<el-form
4+
ref="formRef"
5+
:model="formData"
6+
:rules="formRules"
7+
label-width="80px"
8+
v-loading="formLoading"
9+
>
10+
<el-form-item label="上级菜单">
11+
<el-tree-select
12+
node-key="id"
13+
v-model="formData.parentId"
14+
:props="defaultProps"
15+
:data="menuOptions"
16+
:default-expanded-keys="[0]"
17+
check-strictly
18+
/>
19+
</el-form-item>
20+
<el-col :span="16">
21+
<el-form-item label="菜单名称" prop="name">
22+
<el-input v-model="formData.name" placeholder="请输入菜单名称" clearable />
23+
</el-form-item>
24+
</el-col>
25+
<el-form-item label="菜单类型" prop="type">
26+
<el-radio-group v-model="formData.type">
27+
<el-radio-button
28+
v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_MENU_TYPE)"
29+
:key="dict.label"
30+
:label="dict.value"
31+
>
32+
{{ dict.label }}
33+
</el-radio-button>
34+
</el-radio-group>
35+
</el-form-item>
36+
<template v-if="formData.type !== 3">
37+
<el-form-item label="菜单图标">
38+
<IconSelect v-model="formData.icon" clearable />
39+
</el-form-item>
40+
<el-col :span="16">
41+
<el-form-item label="路由地址" prop="path">
42+
<template #label>
43+
<Tooltip
44+
titel="路由地址"
45+
message="访问的路由地址,如:`user`。如需外网地址时,则以 `http(s)://` 开头"
46+
/>
47+
</template>
48+
<el-input v-model="formData.path" placeholder="请输入路由地址" clearable />
49+
</el-form-item>
50+
</el-col>
51+
</template>
52+
<template v-if="formData.type === 2">
53+
<el-col :span="16">
54+
<el-form-item label="组件地址" prop="component">
55+
<el-input
56+
v-model="formData.component"
57+
placeholder="例如说:system/user/index"
58+
clearable
59+
/>
60+
</el-form-item>
61+
</el-col>
62+
<el-col :span="16">
63+
<el-form-item label="组件名字" prop="componentName">
64+
<el-input v-model="formData.componentName" placeholder="例如说:SystemUser" clearable />
65+
</el-form-item>
66+
</el-col>
67+
</template>
68+
<template v-if="formData.type !== 1">
69+
<el-col :span="16">
70+
<el-form-item label="权限标识" prop="permission">
71+
<template #label>
72+
<Tooltip
73+
titel="权限标识"
74+
message="Controller 方法上的权限字符,如:@PreAuthorize(`@ss.hasPermission('system:user:list')`)"
75+
/>
76+
</template>
77+
<el-input v-model="formData.permission" placeholder="请输入权限标识" clearable />
78+
</el-form-item>
79+
</el-col>
80+
</template>
81+
<el-col :span="16">
82+
<el-form-item label="显示排序" prop="sort">
83+
<el-input-number v-model="formData.sort" controls-position="right" :min="0" clearable />
84+
</el-form-item>
85+
</el-col>
86+
<el-col :span="16">
87+
<el-form-item label="菜单状态" prop="status">
88+
<el-radio-group v-model="formData.status">
89+
<el-radio
90+
border
91+
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
92+
:key="dict.label"
93+
:label="dict.value"
94+
>
95+
{{ dict.label }}
96+
</el-radio>
97+
</el-radio-group>
98+
</el-form-item>
99+
</el-col>
100+
<template v-if="formData.type !== 3">
101+
<el-col :span="16">
102+
<el-form-item label="显示状态" prop="visible">
103+
<template #label>
104+
<Tooltip
105+
titel="显示状态"
106+
message="选择隐藏时,路由将不会出现在侧边栏,但仍然可以访问"
107+
/>
108+
</template>
109+
<el-radio-group v-model="formData.visible">
110+
<el-radio border key="true" :label="true">显示</el-radio>
111+
<el-radio border key="false" :label="false">隐藏</el-radio>
112+
</el-radio-group>
113+
</el-form-item>
114+
</el-col>
115+
</template>
116+
<template v-if="formData.type !== 3">
117+
<el-col :span="16">
118+
<el-form-item label="总是显示" prop="alwaysShow">
119+
<template #label>
120+
<Tooltip
121+
titel="总是显示"
122+
message="选择不是时,当该菜单只有一个子菜单时,不展示自己,直接展示子菜单"
123+
/>
124+
</template>
125+
<el-radio-group v-model="formData.alwaysShow">
126+
<el-radio border key="true" :label="true">总是</el-radio>
127+
<el-radio border key="false" :label="false">不是</el-radio>
128+
</el-radio-group>
129+
</el-form-item>
130+
</el-col>
131+
</template>
132+
<template v-if="formData.type === 2">
133+
<el-col :span="16">
134+
<el-form-item label="缓存状态" prop="keepAlive">
135+
<template #label>
136+
<Tooltip
137+
titel="缓存状态"
138+
message="选择缓存时,则会被 `keep-alive` 缓存,必须填写「组件名称」字段"
139+
/>
140+
</template>
141+
<el-radio-group v-model="formData.keepAlive">
142+
<el-radio border key="true" :label="true">缓存</el-radio>
143+
<el-radio border key="false" :label="false">不缓存</el-radio>
144+
</el-radio-group>
145+
</el-form-item>
146+
</el-col>
147+
</template>
148+
</el-form>
149+
<template #footer>
150+
<div class="dialog-footer">
151+
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
152+
<el-button @click="modelVisible = false">取 消</el-button>
153+
</div>
154+
</template>
155+
</Dialog>
156+
</template>
157+
<script setup lang="ts">
158+
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
159+
import * as MenuApi from '@/api/system/menu'
160+
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
161+
import { SystemMenuTypeEnum, CommonStatusEnum } from '@/utils/constants'
162+
import { handleTree, defaultProps } from '@/utils/tree'
163+
const { wsCache } = useCache()
164+
const { t } = useI18n() // 国际化
165+
const message = useMessage() // 消息弹窗
166+
167+
const modelVisible = ref(false) // 弹窗的是否展示
168+
const modelTitle = ref('') // 弹窗的标题
169+
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
170+
const formType = ref('') // 表单的类型:create - 新增;update - 修改
171+
const formData = ref({
172+
id: 0,
173+
name: '',
174+
permission: '',
175+
type: SystemMenuTypeEnum.DIR,
176+
sort: 1,
177+
parentId: 0,
178+
path: '',
179+
icon: '',
180+
component: '',
181+
componentName: '',
182+
status: CommonStatusEnum.ENABLE,
183+
visible: true,
184+
keepAlive: true,
185+
alwaysShow: true,
186+
createTime: new Date()
187+
})
188+
189+
const formRules = reactive({
190+
name: [{ required: true, message: '菜单名称不能为空', trigger: 'blur' }],
191+
sort: [{ required: true, message: '菜单顺序不能为空', trigger: 'blur' }],
192+
path: [{ required: true, message: '路由地址不能为空', trigger: 'blur' }],
193+
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }]
194+
})
195+
const formRef = ref() // 表单 Ref
196+
197+
/** 打开弹窗 */
198+
const openModal = async (type: string, id?: number) => {
199+
modelVisible.value = true
200+
modelTitle.value = t('action.' + type)
201+
formType.value = type
202+
resetForm()
203+
await getTree()
204+
// 修改时,设置数据
205+
if (id) {
206+
formLoading.value = true
207+
try {
208+
formData.value = await MenuApi.getMenuApi(id)
209+
// TODO 芋艿:这块要优化下,部分字段未重置,无法修改
210+
// formData.value.componentName = res.componentName || ''
211+
// formData.value.alwaysShow = res.alwaysShow !== undefined ? res.alwaysShow : true
212+
} finally {
213+
formLoading.value = false
214+
}
215+
}
216+
}
217+
defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗
218+
219+
/** 提交表单 */
220+
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
221+
const submitForm = async () => {
222+
// 校验表单
223+
if (!formRef) return
224+
const valid = await formRef.value.validate()
225+
if (!valid) return
226+
// 提交请求
227+
formLoading.value = true
228+
try {
229+
if (
230+
formData.value.type === SystemMenuTypeEnum.DIR ||
231+
formData.value.type === SystemMenuTypeEnum.MENU
232+
) {
233+
if (!isExternal(formData.value.path)) {
234+
if (formData.value.parentId === 0 && formData.value.path.charAt(0) !== '/') {
235+
message.error('路径必须以 / 开头')
236+
return
237+
} else if (formData.value.parentId !== 0 && formData.value.path.charAt(0) === '/') {
238+
message.error('路径不能以 / 开头')
239+
return
240+
}
241+
}
242+
}
243+
const data = formData.value
244+
if (formType.value === 'create') {
245+
await MenuApi.createMenuApi(data)
246+
message.success(t('common.createSuccess'))
247+
} else {
248+
await MenuApi.updateMenuApi(data)
249+
message.success(t('common.updateSuccess'))
250+
}
251+
modelVisible.value = false
252+
// 发送操作成功的事件
253+
emit('success')
254+
} finally {
255+
formLoading.value = false
256+
wsCache.delete(CACHE_KEY.ROLE_ROUTERS)
257+
}
258+
}
259+
260+
// ========== 下拉框[上级菜单] ==========
261+
const menuOptions = ref<any[]>([]) // 树形结构
262+
// 获取下拉框[上级菜单]的数据
263+
const getTree = async () => {
264+
menuOptions.value = []
265+
const res = await MenuApi.listSimpleMenusApi()
266+
let menu: Tree = { id: 0, name: '主类目', children: [] }
267+
menu.children = handleTree(res)
268+
menuOptions.value.push(menu)
269+
}
270+
271+
/** 重置表单 */
272+
const resetForm = () => {
273+
formData.value = {
274+
id: 0,
275+
name: '',
276+
permission: '',
277+
type: SystemMenuTypeEnum.DIR,
278+
sort: 1,
279+
parentId: 0,
280+
path: '',
281+
icon: '',
282+
component: '',
283+
componentName: '',
284+
status: CommonStatusEnum.ENABLE,
285+
visible: true,
286+
keepAlive: true,
287+
alwaysShow: true,
288+
createTime: new Date()
289+
}
290+
formRef.value?.resetFields()
291+
}
292+
293+
// 判断 path 是不是外部的 HTTP 等链接
294+
const isExternal = (path: string) => {
295+
return /^(https?:|mailto:|tel:)/.test(path)
296+
}
297+
</script>

0 commit comments

Comments
 (0)