index.vue 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. <script setup lang="tsx">
  2. import { nextTick, ref } from 'vue';
  3. import { NButton, NPopconfirm, NTag } from 'naive-ui';
  4. import type { InternalRowData } from 'naive-ui/es/data-table/src/interface';
  5. import { yesOrNoRecord } from '@/constants/common';
  6. import { menuTypeRecord } from '@/constants/business';
  7. import { fetchAddMenu, fetchDelMenu, fetchEditMenu, fetchGetMenuList } from '@/service/api';
  8. import { useAppStore } from '@/store/modules/app';
  9. import { buildMenuTree } from '@/utils/zt';
  10. import { $t } from '@/locales';
  11. import SvgIcon from '@/components/custom/svg-icon.vue';
  12. import { useForm } from '@/components/zt/Form/hooks/useForm';
  13. import { type OperateType } from './modules/menu-operate-modal.vue';
  14. import { formSchems } from './modules/shared';
  15. const appStore = useAppStore();
  16. const checkedRowKeys = ref([]);
  17. const wrapperRef = ref<HTMLElement | null>(null);
  18. const menusData = ref([]);
  19. const loading = ref(false);
  20. const showModal = ref(false);
  21. const formLoading = ref(false);
  22. const [registerForm, { setFieldsValue, validate, getFieldsValue, updateSchema }] = useForm({
  23. schemas: formSchems,
  24. showSubmitButton: false,
  25. showAdvancedButton: false,
  26. showResetButton: false,
  27. labelWidth: 120,
  28. showActionButtonGroup: false,
  29. layout: 'horizontal',
  30. gridProps: {
  31. cols: '1 xl:4 s:1 l:3',
  32. itemResponsive: true
  33. },
  34. collapsedRows: 1
  35. });
  36. const colums: NaiveUI.TableColumn<InternalRowData>[] = [
  37. {
  38. type: 'selection',
  39. align: 'center',
  40. width: 48
  41. },
  42. {
  43. key: 'id',
  44. title: $t('page.manage.menu.id'),
  45. align: 'center'
  46. },
  47. {
  48. key: 'type',
  49. title: $t('page.manage.menu.menuType'),
  50. align: 'center',
  51. width: 80,
  52. render: (row: InternalRowData) => {
  53. const tagMap: Record<Api.SystemManage.MenuType, NaiveUI.ThemeColor> = {
  54. 0: 'default',
  55. 1: 'primary',
  56. 2: 'error'
  57. };
  58. const type = row.type as keyof typeof menuTypeRecord;
  59. const label = $t(menuTypeRecord[type]);
  60. return <NTag type={tagMap[type]}>{label}</NTag>;
  61. }
  62. },
  63. {
  64. key: 'name',
  65. title: $t('page.manage.menu.menuName'),
  66. align: 'center',
  67. minWidth: 120,
  68. render: (row: InternalRowData) => {
  69. return <span>{row.name}</span>;
  70. }
  71. },
  72. {
  73. key: 'icon',
  74. title: $t('page.manage.menu.icon'),
  75. align: 'center',
  76. width: 60,
  77. render: (row: InternalRowData) => {
  78. return (
  79. <div class="flex-center">
  80. <SvgIcon icon={String(row.icon)} class="text-icon" />
  81. </div>
  82. );
  83. }
  84. },
  85. {
  86. key: 'routeName',
  87. title: $t('page.manage.menu.routeName'),
  88. align: 'center',
  89. minWidth: 120
  90. },
  91. {
  92. key: 'url',
  93. title: $t('page.manage.menu.routePath'),
  94. align: 'center',
  95. minWidth: 120
  96. },
  97. {
  98. key: 'hideInMenu',
  99. title: $t('page.manage.menu.hideInMenu'),
  100. align: 'center',
  101. width: 80,
  102. render: (row: InternalRowData) => {
  103. const hide: CommonType.YesOrNo = row.hideInMenu ? 'Y' : 'N';
  104. const tagMap: Record<CommonType.YesOrNo, NaiveUI.ThemeColor> = {
  105. Y: 'error',
  106. N: 'default'
  107. };
  108. const label = $t(yesOrNoRecord[hide]);
  109. return <NTag type={tagMap[hide]}>{label}</NTag>;
  110. }
  111. },
  112. {
  113. key: 'parentId',
  114. title: $t('page.manage.menu.parentId'),
  115. width: 90,
  116. align: 'center'
  117. },
  118. {
  119. key: 'order',
  120. title: $t('page.manage.menu.order'),
  121. align: 'center',
  122. width: 60
  123. },
  124. {
  125. key: 'operate',
  126. title: $t('common.operate'),
  127. align: 'center',
  128. width: 230,
  129. fixed: 'right',
  130. render: (row: any) => (
  131. <div class="flex-center justify-end gap-8px">
  132. {row.type == '0' && (
  133. <NButton type="primary" ghost size="small" onClick={() => handleAddChildMenu(row)}>
  134. {$t('page.manage.menu.addChildMenu')}
  135. </NButton>
  136. )}
  137. <NButton type="primary" ghost size="small" onClick={() => handleEdit(row)}>
  138. {$t('common.edit')}
  139. </NButton>
  140. <NPopconfirm onPositiveClick={() => handleDelete(row.menuId)}>
  141. {{
  142. default: () => $t('common.confirmDelete'),
  143. trigger: () => (
  144. <NButton type="error" ghost size="small" loading={formLoading.value}>
  145. {$t('common.delete')}
  146. </NButton>
  147. )
  148. }}
  149. </NPopconfirm>
  150. </div>
  151. )
  152. }
  153. ];
  154. // const { checkedRowKeys, onBatchDeleted, onDeleted } = useTableOperate(data, 'id', getData);
  155. const operateType = ref<OperateType>('add');
  156. function handleAdd() {
  157. operateType.value = 'add';
  158. showModal.value = true;
  159. }
  160. async function handleBatchDelete() {
  161. // request
  162. console.log(checkedRowKeys.value);
  163. }
  164. async function handleDelete(id: number) {
  165. // request
  166. console.log(id);
  167. formLoading.value = true;
  168. await fetchDelMenu(id);
  169. init();
  170. formLoading.value = false;
  171. }
  172. function handleEdit(item: Api.SystemManage.Menu) {
  173. showModal.value = true;
  174. operateType.value = 'edit';
  175. nextTick(async () => {
  176. await setFieldsValue(item);
  177. updateSchema([{ field: 'type', componentProps: { disabled: true } }]);
  178. });
  179. }
  180. function handleAddChildMenu(item: any) {
  181. operateType.value = 'addChild';
  182. showModal.value = true;
  183. nextTick(() => {
  184. setFieldsValue({ parentId: item.menuId, url: item.url });
  185. });
  186. }
  187. async function getAllPages() {
  188. const { data } = await fetchGetMenuList();
  189. const menuData = buildMenuTree(data);
  190. menusData.value = menuData;
  191. }
  192. function init() {
  193. getAllPages();
  194. }
  195. // init
  196. init();
  197. async function handleSubmit() {
  198. await validate();
  199. formLoading.value = true;
  200. const form = getFieldsValue();
  201. if (operateType.value == 'add' || operateType.value == 'addChild') {
  202. await fetchAddMenu(form as Api.SystemManage.AddMenu);
  203. formLoading.value = false;
  204. showModal.value = false;
  205. init();
  206. } else {
  207. await fetchEditMenu(form as Api.SystemManage.AddMenu);
  208. formLoading.value = false;
  209. showModal.value = false;
  210. init();
  211. }
  212. }
  213. </script>
  214. <template>
  215. <div ref="wrapperRef" class="flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
  216. <NCard :title="$t('page.manage.menu.title')" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
  217. <template #header-extra>
  218. <TableHeaderOperation
  219. :disabled-delete="checkedRowKeys.length === 0"
  220. :loading="loading"
  221. is-add
  222. @add="handleAdd"
  223. @delete="handleBatchDelete"
  224. @refresh="init"
  225. />
  226. </template>
  227. <NDataTable
  228. v-model:checked-row-keys="checkedRowKeys"
  229. :columns="colums"
  230. :data="menusData"
  231. size="small"
  232. :flex-height="!appStore.isMobile"
  233. :scroll-x="1088"
  234. :loading="loading"
  235. :row-key="row => row.menuId"
  236. remote
  237. class="sm:h-full"
  238. />
  239. <!--
  240. <MenuOperateModal
  241. v-model:visible="visible"
  242. :operate-type="operateType"
  243. :row-data="editingData"
  244. :all-pages="allPages"
  245. />
  246. -->
  247. <NModal v-model:show="showModal">
  248. <NCard class="w-800px">
  249. <template #header>
  250. <div>
  251. {{ operateType == 'add' ? '新增菜单' : operateType == 'addChild' ? '新增子菜单' : ' 编辑菜单' }}
  252. </div>
  253. </template>
  254. <BasicForm @register-form="registerForm">
  255. <template #parentId="{ model, field }">
  256. <NTreeSelect
  257. v-model:value="model[field]"
  258. :options="menusData"
  259. label-field="name"
  260. key-field="menuId"
  261. ></NTreeSelect>
  262. </template>
  263. </BasicForm>
  264. <template #footer>
  265. <div class="flex justify-end">
  266. <NButton class="mr-3" @click="showModal = false">取消</NButton>
  267. <NButton type="primary" :loading="formLoading" @click="handleSubmit">确定</NButton>
  268. </div>
  269. </template>
  270. </NCard>
  271. </NModal>
  272. </NCard>
  273. </div>
  274. </template>
  275. <style scoped></style>