index.vue 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. <script setup lang="ts">
  2. import * as MenuApi from '@/api/system/menu'
  3. import { MenuVO } from '@/api/system/menu/types'
  4. import { useI18n } from '@/hooks/web/useI18n'
  5. import { useMessage } from '@/hooks/web/useMessage'
  6. import { required } from '@/utils/formRules.js'
  7. import { onMounted, reactive, ref } from 'vue'
  8. import { VxeTableInstance } from 'vxe-table'
  9. import { DICT_TYPE } from '@/utils/dict'
  10. import { SystemMenuTypeEnum, CommonStatusEnum } from '@/utils/constants'
  11. const { t } = useI18n() // 国际化
  12. const message = useMessage()
  13. const xTable = ref<VxeTableInstance>()
  14. const tableLoading = ref(false)
  15. const tableData = ref()
  16. const actionLoading = ref(false) // 遮罩层
  17. const actionType = ref('') // 操作按钮的类型
  18. const dialogVisible = ref(false) // 是否显示弹出层
  19. const dialogTitle = ref('edit') // 弹出层标题
  20. const menuForm = ref<MenuVO>({
  21. id: 0,
  22. name: '',
  23. permission: '',
  24. type: SystemMenuTypeEnum.DIR,
  25. sort: 1,
  26. parentId: 0,
  27. path: '',
  28. icon: '',
  29. component: '',
  30. status: CommonStatusEnum.ENABLE,
  31. visible: true,
  32. keepAlive: true,
  33. createTime: ''
  34. })
  35. // ========== 查询 ==========
  36. const queryParams = reactive({
  37. name: undefined,
  38. status: undefined
  39. })
  40. const getList = async () => {
  41. tableLoading.value = true
  42. const res = await MenuApi.getMenuListApi(queryParams)
  43. tableData.value = res
  44. tableLoading.value = false
  45. }
  46. // 设置标题
  47. const setDialogTile = (type: string) => {
  48. dialogTitle.value = t('action.' + type)
  49. actionType.value = type
  50. dialogVisible.value = true
  51. }
  52. // 修改操作
  53. const handleUpdate = async (row: MenuVO) => {
  54. // 设置数据
  55. const res = await MenuApi.getMenuApi(row.id)
  56. console.log(res)
  57. menuForm.value = res
  58. setDialogTile('update')
  59. }
  60. // 删除操作
  61. const handleDelete = async (row: MenuVO) => {
  62. message.confirm(t('common.delDataMessage'), t('common.confirmTitle')).then(async () => {
  63. await MenuApi.deleteMenuApi(row.id)
  64. message.success(t('common.delSuccess'))
  65. await getList()
  66. })
  67. }
  68. // 表单校验
  69. const rules = reactive({
  70. name: [required],
  71. sort: [required],
  72. path: [required],
  73. status: [required]
  74. })
  75. // 保存操作
  76. const isExternal = (path: string) => {
  77. return /^(https?:|mailto:|tel:)/.test(path)
  78. }
  79. const submitForm = async () => {
  80. actionLoading.value = true
  81. // 提交请求
  82. try {
  83. if (
  84. menuForm.value.type === SystemMenuTypeEnum.DIR ||
  85. menuForm.value.type === SystemMenuTypeEnum.MENU
  86. ) {
  87. if (!isExternal(menuForm.value.path)) {
  88. if (menuForm.value.parentId === 0 && menuForm.value.path.charAt(0) !== '/') {
  89. message.error('路径必须以 / 开头')
  90. return
  91. } else if (menuForm.value.parentId !== 0 && menuForm.value.path.charAt(0) === '/') {
  92. message.error('路径不能以 / 开头')
  93. return
  94. }
  95. }
  96. }
  97. if (actionType.value === 'create') {
  98. await MenuApi.createMenuApi(menuForm.value)
  99. message.success(t('common.createSuccess'))
  100. } else {
  101. await MenuApi.updateMenuApi(menuForm.value)
  102. message.success(t('common.updateSuccess'))
  103. }
  104. // 操作成功,重新加载列表
  105. dialogVisible.value = false
  106. await getList()
  107. } finally {
  108. actionLoading.value = false
  109. }
  110. }
  111. onMounted(async () => {
  112. await getList()
  113. })
  114. </script>
  115. <template>
  116. <ContentWrap>
  117. <vxe-toolbar>
  118. <template #buttons>
  119. <vxe-button @click="xTable?.setAllTreeExpand(true)">展开所有</vxe-button>
  120. <vxe-button @click="xTable?.clearTreeExpand()">关闭所有</vxe-button>
  121. </template>
  122. </vxe-toolbar>
  123. <vxe-table
  124. show-overflow
  125. keep-source
  126. ref="xTable"
  127. :loading="tableLoading"
  128. :row-config="{ keyField: 'id' }"
  129. :column-config="{ resizable: true }"
  130. :tree-config="{ transform: true, rowField: 'id', parentField: 'parentId' }"
  131. :print-config="{}"
  132. :export-config="{}"
  133. :data="tableData"
  134. >
  135. <vxe-column title="菜单名称" field="name" width="200" tree-node>
  136. <template #default="{ row }">
  137. <Icon :icon="row.icon" />
  138. <span class="ml-3">{{ row.name }}</span>
  139. </template>
  140. </vxe-column>
  141. <vxe-column title="菜单类型" field="type">
  142. <template #default="{ row }">
  143. <DictTag :type="DICT_TYPE.SYSTEM_MENU_TYPE" :value="row.type" />
  144. </template>
  145. </vxe-column>
  146. <vxe-column title="路由地址" field="path" />
  147. <vxe-column title="组件路径" field="component" />
  148. <vxe-column title="权限标识" field="permission" />
  149. <vxe-column title="排序" field="sort" />
  150. <vxe-column title="状态" field="status">
  151. <template #default="{ row }">
  152. <DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
  153. </template>
  154. </vxe-column>
  155. <vxe-column title="创建时间" field="createTime" formatter="formatDate" />
  156. <vxe-column title="操作" width="200">
  157. <template #default="{ row }">
  158. <vxe-button
  159. type="text"
  160. status="primary"
  161. v-hasPermi="['system:menu:update']"
  162. @click="handleUpdate(row)"
  163. >
  164. <Icon icon="ep:edit" class="mr-1px" /> {{ t('action.edit') }}
  165. </vxe-button>
  166. <vxe-button
  167. type="text"
  168. status="primary"
  169. v-hasPermi="['system:menu:delete']"
  170. @click="handleDelete(row)"
  171. >
  172. <Icon icon="ep:delete" class="mr-1px" /> {{ t('action.del') }}
  173. </vxe-button>
  174. </template>
  175. </vxe-column>
  176. </vxe-table>
  177. </ContentWrap>
  178. <vxe-modal
  179. v-model="dialogVisible"
  180. id="menuModel"
  181. :title="dialogTitle"
  182. width="800"
  183. height="400"
  184. min-width="460"
  185. min-height="320"
  186. show-zoom
  187. resize
  188. remember
  189. storage
  190. transfer
  191. show-footer
  192. >
  193. <template #default>
  194. <!-- 对话框(添加 / 修改) -->
  195. <vxe-form
  196. ref="xForm"
  197. v-if="['create', 'update'].includes(actionType)"
  198. :data="menuForm"
  199. :rules="rules"
  200. @submit="submitForm"
  201. >
  202. <vxe-form-item title="菜单名称" field="name" :item-render="{}">
  203. <template #default="{ data }">
  204. <vxe-input v-model="data.name" placeholder="请输入菜单名称" clearable />
  205. </template>
  206. </vxe-form-item>
  207. </vxe-form>
  208. </template>
  209. </vxe-modal>
  210. </template>