8
0

menu.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. <template>
  2. <hc-new-card>
  3. <template #header>
  4. <div class="w-40">
  5. <el-select v-model="searchForm.sysId" filterable clearable block placeholder="选择所属系统">
  6. <el-option v-for="item in clinets" :key="item.id" :label="item.name" :value="item.id" />
  7. </el-select>
  8. </div>
  9. <div class="ml-3 w-60">
  10. <hc-search-input v-model="searchForm.name" placeholder="请输入菜单名称" @search="searchClick" />
  11. </div>
  12. </template>
  13. <template #extra>
  14. <el-button hc-btn type="primary" @click="addClick">新增</el-button>
  15. <el-button hc-btn type="danger" @click="delClick">删除</el-button>
  16. </template>
  17. <hc-table
  18. ref="tableRef" :column="tableColumn" :datas="tableData" :loading="tableLoading"
  19. :is-index="false" is-new is-check :check-style="{ width: 29 }" lazy :load="tableLoad"
  20. @selection-change="tableCheckChange"
  21. >
  22. <template #sysId="{ row }">
  23. {{ getSystemNmae(row.sysId) }}
  24. </template>
  25. <template #category="{ row }">
  26. {{ row.category === 1 ? '菜单' : '按钮' }}
  27. </template>
  28. <template #action="{ row }">
  29. <el-link type="warning" @click="editRowClick(row)">修改</el-link>
  30. <el-link type="success" @click="addRowClick(row)">新增子项</el-link>
  31. <el-link type="primary" @click="copyRowClick(row)">复制</el-link>
  32. <el-link type="danger" @click="delRowClick(row)">删除</el-link>
  33. </template>
  34. </hc-table>
  35. <!-- 新增/修改 菜单 -->
  36. <hc-new-dialog v-model="isDialogShow" widths="800px" is-footer-center :title="iconDialogTitle" @close="dialogClose">
  37. <el-form ref="formRef" :model="formModel" :rules="formRules" label-position="top" label-width="auto">
  38. <el-row :gutter="20">
  39. <el-col :span="8">
  40. <el-form-item label="菜单名称:" prop="name">
  41. <el-input v-model="formModel.name" clearable />
  42. </el-form-item>
  43. </el-col>
  44. <el-col :span="8">
  45. <el-form-item label="路由地址:" prop="path">
  46. <el-input v-model="formModel.path" clearable />
  47. </el-form-item>
  48. </el-col>
  49. <el-col :span="8">
  50. <el-form-item label="菜单编号:" prop="code">
  51. <el-input v-model="formModel.code" clearable />
  52. </el-form-item>
  53. </el-col>
  54. <el-col :span="8">
  55. <el-form-item label="菜单图标:">
  56. <el-input v-model="formModel.source" clearable>
  57. <template #append>
  58. <el-button @click="isIconShow = true">选择图标</el-button>
  59. </template>
  60. </el-input>
  61. </el-form-item>
  62. </el-col>
  63. <el-col :span="8">
  64. <el-form-item label="所属系统:" prop="sysId">
  65. <el-select v-model="formModel.sysId" placeholder="选择所属系统" filterable clearable block>
  66. <el-option v-for="item in clinets" :key="item.id" :label="item.name" :value="item.id" />
  67. </el-select>
  68. </el-form-item>
  69. </el-col>
  70. <el-col :span="8">
  71. <el-form-item label="上级菜单:">
  72. <el-tree-select
  73. v-model="formModel.parentId" placeholder="选择上级菜单"
  74. :data="levelMenu" :props="levelMenuProps" clearable filterable check-strictly block :render-after-expand="false"
  75. />
  76. </el-form-item>
  77. </el-col>
  78. <el-col :span="8">
  79. <el-form-item label="菜单类型:" prop="category">
  80. <div class="form-item-div">
  81. <el-radio-group v-model="formModel.category">
  82. <el-radio :label="1">菜单</el-radio>
  83. <el-radio :label="2">按钮</el-radio>
  84. </el-radio-group>
  85. </div>
  86. </el-form-item>
  87. </el-col>
  88. <el-col v-if="formModel.category === 2" :span="8">
  89. <el-form-item label="按钮是否显示:" prop="isShowButton">
  90. <div class="form-item-div">
  91. <el-radio-group v-model="formModel.isShowButton">
  92. <el-radio :label="2">否</el-radio>
  93. <el-radio :label="1">是</el-radio>
  94. </el-radio-group>
  95. </div>
  96. </el-form-item>
  97. </el-col>
  98. <el-col v-if="formModel.category === 1" :span="8">
  99. <el-form-item label="是否外层:" prop="isLayout">
  100. <div class="form-item-div">
  101. <el-radio-group v-model="formModel.isLayout">
  102. <el-radio :label="1">否</el-radio>
  103. <el-radio :label="2">是</el-radio>
  104. </el-radio-group>
  105. </div>
  106. </el-form-item>
  107. </el-col>
  108. <el-col :span="8">
  109. <el-form-item label="是否新窗口:" prop="isOpen">
  110. <div class="form-item-div">
  111. <el-radio-group v-model="formModel.isOpen">
  112. <el-radio :label="1">否</el-radio>
  113. <el-radio :label="2">是</el-radio>
  114. </el-radio-group>
  115. </div>
  116. </el-form-item>
  117. </el-col>
  118. <el-col :span="8">
  119. <el-form-item label="菜单排序:" prop="sort">
  120. <el-input-number v-model="formModel.sort" :min="1" block controls-position="right" />
  121. </el-form-item>
  122. </el-col>
  123. <el-col :span="16">
  124. <el-form-item label="按钮提示语:">
  125. <el-input v-model="formModel.textInfo" clearable :disabled="formModel.category === 1" />
  126. </el-form-item>
  127. </el-col>
  128. <el-col :span="24">
  129. <el-form-item label="菜单备注:">
  130. <el-input v-model="formModel.remark" :autosize="{ minRows: 2, maxRows: 4 }" type="textarea" />
  131. </el-form-item>
  132. </el-col>
  133. <el-col :span="24">
  134. <el-form-item>
  135. <template #label>
  136. <div class="hc-form-item-label">
  137. <div class="title-content">
  138. <span class="title">菜单备注:</span>
  139. <span content="text">(只能上传MP4格式,且不能用QQ录屏,推荐使用win10自带录屏录制,文件大小限制50兆)</span>
  140. </div>
  141. <div class="right-content">
  142. <el-link type="warning" @click="formModel.videoUrl = ''">清除</el-link>
  143. </div>
  144. </div>
  145. </template>
  146. <hc-form-upload :src="formModel.videoUrl" :num="1" @upload="videoUpload" />
  147. </el-form-item>
  148. </el-col>
  149. <el-col :span="24">
  150. <el-form-item label="文档信息:">
  151. <template #label>
  152. <div class="hc-form-item-label">
  153. <div class="title-content">文档信息:</div>
  154. <div class="right-content">
  155. <el-link type="warning" @click="formModel.excelUrl = ''">清除</el-link>
  156. </div>
  157. </div>
  158. </template>
  159. <hc-form-upload :src="formModel.excelUrl" :num="1" @upload="excelUpload" />
  160. </el-form-item>
  161. </el-col>
  162. </el-row>
  163. </el-form>
  164. <template #footer>
  165. <el-button hc-btn @click="dialogClose">取消</el-button>
  166. <el-button hc-btn type="primary" :loading="submitLoading" @click="dialogSubmit">提交</el-button>
  167. </template>
  168. </hc-new-dialog>
  169. <!-- 图标选择 -->
  170. <hc-menu-icon v-model="isIconShow" @finish="menuIconFinish" />
  171. <!-- 上传文件 -->
  172. <hc-upload-file ref="uploadRef" :echo-params="uploadParams" :options="uploadOptions" @success="uploadSuccess" />
  173. </hc-new-card>
  174. </template>
  175. <script setup>
  176. import { nextTick, onActivated, ref } from 'vue'
  177. import { useRoute, useRouter } from 'vue-router'
  178. import { arrToId, formValidate, getArrValue, getObjValue } from 'js-fast-way'
  179. import { delMessage, reloadPage } from '~uti/tools'
  180. import { getClinetAll } from '~api/other'
  181. import { getHeader } from 'hc-vue3-ui'
  182. import mainApi from '~api/system/menu'
  183. //初始组合式
  184. const router = useRouter()
  185. const useRoutes = useRoute()
  186. //激活
  187. onActivated(() => {
  188. //获取参数
  189. const { sysId, name } = getObjValue(useRoutes.query)
  190. searchForm.value = { sysId: sysId, name: name }
  191. //获取数据
  192. getClinetAllApi()
  193. getTableData()
  194. getLevelMenuData()
  195. })
  196. //获取所有系统
  197. const clinets = ref([])
  198. const getClinetAllApi = async () => {
  199. const { data } = await getClinetAll()
  200. clinets.value = getArrValue(data)
  201. }
  202. //获取系统名称
  203. const getSystemNmae = (id) => {
  204. const item = clinets.value.find((item) => item.id === id)
  205. return item ? item.name : ''
  206. }
  207. //搜索表单
  208. const searchForm = ref({
  209. sysId: null, name: null,
  210. })
  211. //搜索
  212. const searchClick = () => {
  213. router.push({
  214. name: 'menu',
  215. query: searchForm.value,
  216. })
  217. getTableData()
  218. }
  219. //表格数据
  220. const tableRef = ref(null)
  221. const tableColumn = ref([
  222. { key: 'name', name: '菜单名称' },
  223. { key: 'sysId', name: '所属系统' },
  224. { key: 'path', name: '路由地址' },
  225. { key: 'code', name: '菜单编号' },
  226. { key: 'category', name: '菜单类型', width: 90, align: 'center' },
  227. { key: 'sort', name: '排序', width: 80, align: 'center' },
  228. { key: 'action', name: '操作', width: 200, align: 'center' },
  229. ])
  230. //获取表格数据
  231. const tableLoading = ref(false)
  232. const tableData = ref([{}])
  233. const getTableData = async () => {
  234. tableData.value = []
  235. tableLoading.value = true
  236. const { data } = await mainApi.getLazyList({
  237. ...searchForm.value,
  238. parentId: 0,
  239. })
  240. tableData.value = getArrValue(data)
  241. tableLoading.value = false
  242. }
  243. //懒加载表格
  244. const tableLoad = async (row, node, resolve) => {
  245. const { data } = await mainApi.getLazyList({
  246. ...searchForm.value,
  247. parentId: row.id,
  248. })
  249. resolve(getArrValue(data))
  250. }
  251. //表格被选择
  252. const tableCheckKeys = ref([])
  253. const tableCheckChange = (rows) => {
  254. tableCheckKeys.value = rows
  255. }
  256. //上级菜单
  257. const levelMenuProps = {
  258. label: 'title',
  259. }
  260. const levelMenu = ref([])
  261. const getLevelMenuData = async () => {
  262. const { data } = await mainApi.getMenuTree()
  263. levelMenu.value = getArrValue(data)
  264. }
  265. //新增/修改 弹窗
  266. const isDialogShow = ref(false)
  267. const iconDialogTitle = ref('')
  268. //菜单表单
  269. const formRef = ref(null)
  270. const formModel = ref({})
  271. const formRules = {
  272. name: {
  273. required: true,
  274. trigger: 'blur',
  275. message: '请输入菜单名称',
  276. },
  277. path: {
  278. required: true,
  279. trigger: 'blur',
  280. message: '请输入路由地址',
  281. },
  282. code: {
  283. required: true,
  284. trigger: 'blur',
  285. message: '请输入菜单编号',
  286. },
  287. sysId: {
  288. required: true,
  289. trigger: 'blur',
  290. message: '请选择所属系统',
  291. },
  292. category: {
  293. required: true,
  294. trigger: 'blur',
  295. message: '请选择菜单类型',
  296. },
  297. isShowButton: {
  298. required: true,
  299. trigger: 'blur',
  300. message: '请选择按钮是否显示',
  301. },
  302. isLayout: {
  303. required: true,
  304. trigger: 'blur',
  305. message: '请选择是否外层',
  306. },
  307. isOpen: {
  308. required: true,
  309. trigger: 'blur',
  310. message: '请选择是否新窗口',
  311. },
  312. sort: {
  313. required: true,
  314. trigger: 'blur',
  315. message: '请输入菜单排序',
  316. },
  317. }
  318. //图标选择
  319. const isIconShow = ref(false)
  320. const menuIconFinish = (icon) => {
  321. formModel.value.source = icon
  322. isIconShow.value = false
  323. }
  324. //新增菜单
  325. const addClick = () => {
  326. iconDialogTitle.value = '新增菜单'
  327. formModel.value = {
  328. category: 1, isOpen: 1, isLayout: 1, isShowButton: 1,
  329. sort: 1, sysId: searchForm.value.sysId ?? null, parentId: null,
  330. }
  331. //显示表单弹窗
  332. nextTick(() => {
  333. isDialogShow.value = true
  334. })
  335. }
  336. //修改菜单
  337. const editRowClick = (row) => {
  338. formModel.value = {}
  339. iconDialogTitle.value = '修改菜单'
  340. formModel.value = { ...row }
  341. //显示表单弹窗
  342. nextTick(() => {
  343. isDialogShow.value = true
  344. })
  345. }
  346. //新增子项
  347. const addRowClick = (row) => {
  348. iconDialogTitle.value = '新增子菜单'
  349. formModel.value = {
  350. name: row.name, path: row.path, code: row.code, source: row.source,
  351. sysId: row.sysId, parentId: row.id,
  352. category: 1, isOpen: 1, isLayout: 1, isShowButton: 1, sort: 1,
  353. }
  354. //显示表单弹窗
  355. nextTick(() => {
  356. isDialogShow.value = true
  357. })
  358. }
  359. //复制菜单
  360. const copyRowClick = (row) => {
  361. iconDialogTitle.value = '复制菜单'
  362. formModel.value = {
  363. ...row,
  364. id: null,
  365. sort: row.sort + 1,
  366. }
  367. //显示表单弹窗
  368. nextTick(() => {
  369. isDialogShow.value = true
  370. })
  371. }
  372. //删除菜单
  373. const delRowClick = (row) => {
  374. delMessage(async () => {
  375. const { code, msg } = await mainApi.del(row.id)
  376. if (code === 200) {
  377. window.$message.success('删除成功')
  378. reloadPage()
  379. } else {
  380. window.$message.error(msg ?? '删除失败')
  381. }
  382. })
  383. }
  384. //批量删除菜单
  385. const delClick = () => {
  386. const rows = tableCheckKeys.value
  387. if (rows.length <= 0) {
  388. window.$message.warning('请选择要删除的菜单')
  389. return false
  390. }
  391. //确认删除菜单
  392. delMessage(async () => {
  393. const ids = arrToId(rows)
  394. const { code, msg } = await mainApi.del(ids)
  395. if (code === 200) {
  396. window.$message.success('删除成功')
  397. reloadPage()
  398. } else {
  399. window.$message.error(msg ?? '删除失败')
  400. }
  401. })
  402. }
  403. //上传文件
  404. const uploadRef = ref(null)
  405. const uploadParams = ref({})
  406. const uploadOptions = ref({
  407. url: '/api/blade-resource/oss/endpoint/put-file2',
  408. headers: getHeader(),
  409. multiple: false,
  410. })
  411. //上传视频
  412. const videoUpload = () => {
  413. uploadParams.value = { type: '视频文件' }
  414. uploadOptions.value.accept = 'video/mp4'
  415. uploadOptions.value.accept_tip = '只能上传MP4格式,且不能用QQ录屏,推荐使用win10自带录屏录制,文件大小限制50兆'
  416. uploadOptions.value.size = 50
  417. nextTick(() => {
  418. uploadRef.value?.selectFile()
  419. })
  420. }
  421. //上传文档
  422. const excelUpload = () => {
  423. uploadParams.value = { type: '文档文件' }
  424. uploadOptions.value.accept = null
  425. uploadOptions.value.accept_tip = null
  426. uploadOptions.value.size = null
  427. nextTick(() => {
  428. uploadRef.value?.selectFile()
  429. })
  430. }
  431. // 文件上传成功的回调
  432. const uploadSuccess = ({ echoParams, resData }) => {
  433. if (echoParams.type === '视频文件') {
  434. formModel.value.videoUrl = resData
  435. } else if (echoParams.type === '文档文件') {
  436. formModel.value.excelUrl = resData
  437. }
  438. //关闭弹窗
  439. uploadRef.value?.setModalShow(false)
  440. }
  441. //提交表单
  442. const submitLoading = ref(false)
  443. const dialogSubmit = async () => {
  444. const formRes = await formValidate(formRef.value)
  445. if (!formRes) return false
  446. submitLoading.value = true
  447. //处理数据
  448. const form = formModel.value
  449. form.parentId = form.parentId ?? 0
  450. form.alias = form.alias ?? form.code
  451. //发起请求
  452. const { error, code, msg } = await mainApi.submit(form)
  453. submitLoading.value = false
  454. if (!error && code === 200) {
  455. dialogClose()
  456. window?.$message?.success('操作成功')
  457. reloadPage()
  458. } else {
  459. window?.$message?.error(msg ?? '操作失败')
  460. }
  461. }
  462. //关闭弹窗
  463. const dialogClose = () => {
  464. isDialogShow.value = false
  465. submitLoading.value = false
  466. formModel.value = {}
  467. }
  468. </script>