左树右表表格.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. #foreach($field in $childFieldList)
  2. #if($field.primaryPk == '1')
  3. #set($childPkField = $field)
  4. #break
  5. #end
  6. #end
  7. <template>
  8. <div class="layout-padding">
  9. <splitpanes>
  10. <pane size="15">
  11. <div class="layout-padding-auto layout-padding-view">
  12. <el-scrollbar>
  13. <div class="mb8" style="display: flex; align-items: center; justify-content: space-between;">
  14. <span style="font-weight: 500;">左侧节点</span>
  15. <div>
  16. <el-tooltip :content="currentTreeId ? '新增子节点' : '新增根节点'" placement="top">
  17. <el-button link type="primary" icon="FolderAdd" @click="handleAddTree" />
  18. </el-tooltip>
  19. </div>
  20. </div>
  21. <el-tree
  22. ref="treeRef"
  23. :data="treeData"
  24. :props="treeProps"
  25. node-key="${childPkField.attrName}"
  26. highlight-current
  27. default-expand-all
  28. :expand-on-click-node="false"
  29. @node-click="handleTreeNodeClick"
  30. >
  31. <template #default="{ node, data }">
  32. <span style="flex: 1; display: flex; align-items: center; justify-content: space-between;">
  33. <span>{{ node.label }}</span>
  34. <span v-if="currentTreeId === data.${childPkField.attrName}" style="margin-left: 8px;">
  35. <el-tooltip content="编辑" placement="top">
  36. <el-button link type="primary" icon="Edit" size="small" @click.stop="handleEditTree" />
  37. </el-tooltip>
  38. <el-tooltip content="删除" placement="top">
  39. <el-button link type="danger" icon="Delete" size="small" @click.stop="handleDeleteTree" />
  40. </el-tooltip>
  41. </span>
  42. </span>
  43. </template>
  44. </el-tree>
  45. </el-scrollbar>
  46. </div>
  47. </pane>
  48. <pane class="ml8">
  49. <div class="layout-padding-auto layout-padding-view">
  50. #if($queryList)
  51. <el-row v-show="showSearch">
  52. <el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
  53. #foreach($field in $queryList)
  54. #if($field.attrName != ${childField})
  55. #if($field.queryFormType == 'select')
  56. <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end" prop="${field.attrName}">
  57. <el-select v-model="state.queryForm.${field.attrName}" placeholder="请选择#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end">
  58. #if($field.fieldDict)
  59. <el-option
  60. :label="item.label"
  61. :value="item.value"
  62. v-for="(item, index) in ${field.fieldDict}"
  63. :key="index"
  64. />
  65. #else
  66. <el-option label="请选择" value="0" />
  67. #end
  68. </el-select>
  69. </el-form-item>
  70. #elseif($field.queryFormType == 'date')
  71. <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end" prop="${field.attrName}">
  72. <el-date-picker
  73. type="date"
  74. placeholder="请输入#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end"
  75. v-model="state.queryForm.${field.attrName}"
  76. :value-format="dateStr"
  77. />
  78. </el-form-item>
  79. #elseif($field.queryFormType == 'daterange')
  80. <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end" prop="${field.attrName}Range">
  81. <el-date-picker
  82. type="daterange"
  83. range-separator="至"
  84. start-placeholder="开始日期"
  85. end-placeholder="结束日期"
  86. v-model="state.queryForm.${field.attrName}Range"
  87. :value-format="dateStr"
  88. />
  89. </el-form-item>
  90. #elseif($field.queryFormType == 'datetime')
  91. <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end" prop="${field.attrName}">
  92. <el-date-picker
  93. type="datetime"
  94. placeholder="请输入#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end"
  95. v-model="state.queryForm.${field.attrName}"
  96. :value-format="dateTimeStr"
  97. />
  98. </el-form-item>
  99. #elseif($field.queryFormType == 'datetimerange')
  100. <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end" prop="${field.attrName}Range">
  101. <el-date-picker
  102. type="datetimerange"
  103. range-separator="至"
  104. start-placeholder="开始时间"
  105. end-placeholder="结束时间"
  106. v-model="state.queryForm.${field.attrName}Range"
  107. :value-format="dateTimeStr"
  108. />
  109. </el-form-item>
  110. #elseif($field.formType == 'radio')
  111. <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end" prop="${field.attrName}">
  112. <el-radio-group v-model="state.queryForm.${field.attrName}">
  113. #if($field.fieldDict)
  114. <el-radio
  115. :label="item.value"
  116. v-for="(item, index) in ${field.fieldDict}"
  117. border
  118. :key="index"
  119. >
  120. {{ item.label }}
  121. </el-radio>
  122. #else
  123. <el-radio label="${field.fieldComment}" border>
  124. ${field.fieldComment}
  125. </el-radio>
  126. #end
  127. </el-radio-group>
  128. </el-form-item>
  129. #else
  130. <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end" prop="${field.attrName}">
  131. <el-input
  132. placeholder="请输入#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end"
  133. v-model="state.queryForm.${field.attrName}"
  134. />
  135. </el-form-item>
  136. #end
  137. #end
  138. #end
  139. <el-form-item>
  140. <el-button icon="search" type="primary" @click="getDataList">
  141. 查询
  142. </el-button>
  143. <el-button icon="Refresh" @click="resetQuery">重置</el-button>
  144. </el-form-item>
  145. </el-form>
  146. </el-row>
  147. #end
  148. <el-row>
  149. <div class="mb8" style="width: 100%">
  150. <el-button
  151. icon="folder-add"
  152. type="primary"
  153. class="ml10"
  154. @click="handleAddRow"
  155. v-auth="'$str.lowerCase($moduleName)_$str.lowerCase($functionName)_add'"
  156. >
  157. 新增
  158. </el-button>
  159. <el-button
  160. plain
  161. icon="upload-filled"
  162. type="primary"
  163. class="ml10"
  164. @click="excelUploadRef.show()"
  165. v-auth="'$str.lowerCase($moduleName)_$str.lowerCase($functionName)_add'"
  166. >
  167. 导入
  168. </el-button>
  169. <el-button
  170. plain
  171. :disabled="multiple"
  172. icon="Delete"
  173. type="primary"
  174. v-auth="'$str.lowerCase($moduleName)_$str.lowerCase($functionName)_del'"
  175. @click="handleDelete(selectObjs)"
  176. >
  177. 删除
  178. </el-button>
  179. <right-toolbar
  180. v-model:showSearch="showSearch"
  181. :export="'$str.lowerCase($moduleName)_$str.lowerCase($functionName)_export'"
  182. @exportExcel="exportExcel"
  183. class="ml10 mr20"
  184. style="float: right;"
  185. @queryTable="getDataList"
  186. />
  187. </div>
  188. </el-row>
  189. <el-table
  190. :data="state.dataList"
  191. v-loading="state.loading"
  192. border
  193. :cell-style="tableStyle.cellStyle"
  194. :header-cell-style="tableStyle.headerCellStyle"
  195. @selection-change="selectionChangeHandle"
  196. @sort-change="sortChangeHandle"
  197. >
  198. <el-table-column type="selection" width="40" align="center" />
  199. <el-table-column type="index" label="#" width="40" />
  200. #foreach($field in $gridList)
  201. #if($field.fieldDict)
  202. <el-table-column prop="${field.attrName}" label="#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end" show-overflow-tooltip>
  203. <template #default="scope">
  204. <dict-tag :options="${field.fieldDict}" :value="scope.row.${field.attrName}" />
  205. </template>
  206. </el-table-column>
  207. #elseif(${field.formType} == 'upload-img')
  208. <el-table-column prop="${field.attrName}" label="#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end">
  209. <template #default="{ row }">
  210. <upload-img disabled v-model:imageUrl="row.${field.attrName}"></upload-img>
  211. </template>
  212. </el-table-column>
  213. #else
  214. <el-table-column
  215. prop="${field.attrName}"
  216. label="#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end"
  217. #if(${field.gridSort} == '1')
  218. sortable="custom"
  219. #end
  220. show-overflow-tooltip
  221. />
  222. #end
  223. #end
  224. <el-table-column label="操作" width="200">
  225. <template #default="scope">
  226. <el-button
  227. icon="edit-pen"
  228. text
  229. type="primary"
  230. v-auth="'$str.lowerCase($moduleName)_$str.lowerCase($functionName)_edit'"
  231. @click="handleEditRow(scope.row)"
  232. >
  233. 编辑
  234. </el-button>
  235. <el-button
  236. icon="delete"
  237. text
  238. type="primary"
  239. v-auth="'$str.lowerCase($moduleName)_$str.lowerCase($functionName)_del'"
  240. @click="handleDelete([scope.row.${pk.attrName}])"
  241. >
  242. 删除
  243. </el-button>
  244. </template>
  245. </el-table-column>
  246. </el-table>
  247. <pagination
  248. @size-change="sizeChangeHandle"
  249. @current-change="currentChangeHandle"
  250. v-bind="state.pagination"
  251. />
  252. </div>
  253. </pane>
  254. </splitpanes>
  255. <tree-form ref="treeFormDialogRef" @refresh="reloadTree" />
  256. <form-dialog ref="formDialogRef" @refresh="getDataList(false)" />
  257. <upload-excel
  258. ref="excelUploadRef"
  259. title="导入"
  260. url="/${moduleName}/${functionName}/import"
  261. temp-url="/admin/sys-file/local/file/${functionName}.xlsx"
  262. @refreshDataList="getDataList"
  263. />
  264. </div>
  265. </template>
  266. <script setup lang="ts" name="system${ClassName}TreeTable">
  267. import { BasicTableProps, useTable } from "/@/hooks/table";
  268. import { fetchList, delObjs, fetchTreeList, delTreeObjs } from "/@/api/${moduleName}/${functionName}";
  269. import { useMessage, useMessageBox } from "/@/hooks/message";
  270. import { useDict } from '/@/hooks/dict';
  271. const FormDialog = defineAsyncComponent(() => import('./form.vue'));
  272. const TreeForm = defineAsyncComponent(() => import('./tree-form.vue'));
  273. #set($fieldDict=[])
  274. #foreach($field in $queryList)
  275. #if($field.fieldDict)
  276. #set($void=$fieldDict.add($field.fieldDict))
  277. #end
  278. #end
  279. #foreach($field in $gridList)
  280. #if($field.fieldDict)
  281. #set($void=$fieldDict.add($field.fieldDict))
  282. #end
  283. #end
  284. #if($fieldDict && $fieldDict.size() > 0)
  285. const { $dict.format($fieldDict) } = useDict($dict.quotation($fieldDict));
  286. #end
  287. interface TreeNode {
  288. [key: string]: any;
  289. children?: TreeNode[];
  290. }
  291. const formDialogRef = ref();
  292. const treeFormDialogRef = ref();
  293. const excelUploadRef = ref();
  294. const queryRef = ref();
  295. const treeRef = ref();
  296. const showSearch = ref(true);
  297. const selectObjs = ref([]) as any;
  298. const multiple = ref(true);
  299. const currentTreeId = ref<string | number | null>(null);
  300. const treeData = ref<TreeNode[]>([]);
  301. const treeProps = {
  302. children: 'children',
  303. label: '${nameField}'
  304. };
  305. const state: BasicTableProps = reactive<BasicTableProps>({
  306. queryForm: {},
  307. pageList: fetchList
  308. });
  309. const {
  310. getDataList,
  311. currentChangeHandle,
  312. sizeChangeHandle,
  313. sortChangeHandle,
  314. downBlobFile,
  315. tableStyle
  316. } = useTable(state);
  317. const reloadTree = async () => {
  318. const { data } = await fetchTreeList();
  319. treeData.value = data || [];
  320. nextTick(() => {
  321. treeRef.value?.setCurrentKey(currentTreeId.value);
  322. });
  323. };
  324. const resetQuery = () => {
  325. queryRef.value?.resetFields();
  326. selectObjs.value = [];
  327. if (currentTreeId.value) {
  328. state.queryForm.${childField} = currentTreeId.value;
  329. } else {
  330. delete state.queryForm.${childField};
  331. }
  332. getDataList();
  333. };
  334. const exportExcel = () => {
  335. downBlobFile(
  336. '/${moduleName}/${functionName}/export',
  337. { ...state.queryForm, ids: selectObjs.value },
  338. '${functionName}.xlsx'
  339. );
  340. };
  341. const selectionChangeHandle = (objs: { ${pk.attrName}: string }[]) => {
  342. selectObjs.value = objs.map(({ ${pk.attrName} }) => ${pk.attrName});
  343. multiple.value = !objs.length;
  344. };
  345. const handleDelete = async (ids: string[]) => {
  346. try {
  347. await useMessageBox().confirm('此操作将永久删除');
  348. } catch {
  349. return;
  350. }
  351. try {
  352. await delObjs(ids);
  353. getDataList();
  354. useMessage().success('删除成功');
  355. } catch (err: any) {
  356. useMessage().error(err.msg);
  357. }
  358. };
  359. const handleTreeNodeClick = (data: TreeNode) => {
  360. currentTreeId.value = data.${childPkField.attrName};
  361. state.queryForm.${childField} = currentTreeId.value;
  362. treeRef.value?.setCurrentKey(currentTreeId.value);
  363. getDataList();
  364. };
  365. const clearTreeSelection = () => {
  366. currentTreeId.value = null;
  367. treeRef.value?.setCurrentKey(null);
  368. delete state.queryForm.${childField};
  369. };
  370. const handleAddTree = () => {
  371. treeFormDialogRef.value?.openDialog(undefined, currentTreeId.value ?? 0);
  372. };
  373. const handleEditTree = () => {
  374. if (!currentTreeId.value) {
  375. useMessage().warning('请先选择一个左侧节点');
  376. return;
  377. }
  378. treeFormDialogRef.value?.openDialog(currentTreeId.value);
  379. };
  380. const handleDeleteTree = async () => {
  381. if (!currentTreeId.value) {
  382. useMessage().warning('请先选择一个左侧节点');
  383. return;
  384. }
  385. try {
  386. await useMessageBox().confirm('此操作将永久删除左侧节点');
  387. } catch {
  388. return;
  389. }
  390. try {
  391. await delTreeObjs([currentTreeId.value]);
  392. clearTreeSelection();
  393. await reloadTree();
  394. getDataList();
  395. useMessage().success('删除成功');
  396. } catch (err: any) {
  397. useMessage().error(err.msg);
  398. }
  399. };
  400. const handleAddRow = () => {
  401. formDialogRef.value?.openDialog(undefined, currentTreeId.value ?? undefined);
  402. };
  403. const handleEditRow = (row: any) => {
  404. formDialogRef.value?.openDialog(row.${pk.attrName}, currentTreeId.value ?? undefined);
  405. };
  406. onMounted(async () => {
  407. await reloadTree();
  408. getDataList();
  409. });
  410. </script>