树形表格.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. <template>
  2. <div class="layout-padding">
  3. <div class="layout-padding-auto layout-padding-view">
  4. #if($queryList)
  5. <!-- 查询表单区域 -->
  6. <el-row v-show="showSearch">
  7. <el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
  8. #foreach($field in $queryList)
  9. #if($field.queryFormType == 'select')
  10. <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end" prop="${field.attrName}">
  11. <el-select v-model="state.queryForm.${field.attrName}" placeholder="请选择#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end">
  12. #if($field.fieldDict)
  13. <el-option
  14. :label="item.label"
  15. :value="item.value"
  16. v-for="(item, index) in ${field.fieldDict}"
  17. :key="index"
  18. />
  19. #else
  20. <el-option label="请选择" value="0" />
  21. #end
  22. </el-select>
  23. </el-form-item>
  24. #elseif($field.queryFormType == 'date')
  25. <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end" prop="${field.attrName}">
  26. <el-date-picker
  27. type="date"
  28. placeholder="请输入#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end"
  29. v-model="state.queryForm.${field.attrName}"
  30. :value-format="dateStr"
  31. />
  32. </el-form-item>
  33. #elseif($field.queryFormType == 'date-range')
  34. <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end" prop="${field.attrName}Range">
  35. <el-date-picker
  36. type="daterange"
  37. start-placeholder="开始日期"
  38. end-placeholder="结束日期"
  39. v-model="state.queryForm.${field.attrName}Range"
  40. value-format="YYYY-MM-DD"
  41. />
  42. </el-form-item>
  43. #elseif($field.queryFormType == 'datetime')
  44. <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end" prop="${field.attrName}">
  45. <el-date-picker
  46. type="datetime"
  47. placeholder="请输入#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end"
  48. v-model="state.queryForm.${field.attrName}"
  49. :value-format="dateTimeStr"
  50. />
  51. </el-form-item>
  52. #elseif($field.queryFormType == 'datetime-range')
  53. <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end" prop="${field.attrName}Range">
  54. <el-date-picker
  55. type="datetimerange"
  56. start-placeholder="开始时间"
  57. end-placeholder="结束时间"
  58. v-model="state.queryForm.${field.attrName}Range"
  59. value-format="YYYY-MM-DD HH:mm:ss"
  60. />
  61. </el-form-item>
  62. #elseif($field.formType == 'radio')
  63. <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end" prop="${field.attrName}">
  64. <el-radio-group v-model="state.queryForm.${field.attrName}">
  65. #if($field.fieldDict)
  66. <el-radio
  67. :label="item.value"
  68. v-for="(item, index) in ${field.fieldDict}"
  69. border
  70. :key="index"
  71. >
  72. {{ item.label }}
  73. </el-radio>
  74. #else
  75. <el-radio label="${field.fieldComment}" border>
  76. ${field.fieldComment}
  77. </el-radio>
  78. #end
  79. </el-radio-group>
  80. </el-form-item>
  81. #else
  82. <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end" prop="${field.attrName}">
  83. <el-input
  84. placeholder="请输入#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end"
  85. v-model="state.queryForm.${field.attrName}"
  86. />
  87. </el-form-item>
  88. #end
  89. #end
  90. <el-form-item>
  91. <el-button icon="search" type="primary" @click="getDataList">
  92. 查询
  93. </el-button>
  94. <el-button icon="Refresh" @click="resetQuery">重置</el-button>
  95. </el-form-item>
  96. </el-form>
  97. </el-row>
  98. #end
  99. <!-- 操作按钮区域 -->
  100. <el-row>
  101. <div class="mb8" style="width: 100%">
  102. <el-button
  103. icon="folder-add"
  104. type="primary"
  105. class="ml10"
  106. @click="formDialogRef.openDialog()"
  107. v-auth="'${moduleName}_${functionName}_add'"
  108. >
  109. 新增
  110. </el-button>
  111. <el-button
  112. plain
  113. :disabled="multiple"
  114. icon="Delete"
  115. type="primary"
  116. v-auth="'${moduleName}_${functionName}_del'"
  117. @click="handleDelete(selectObjs)"
  118. >
  119. 删除
  120. </el-button>
  121. <el-button
  122. plain
  123. :icon="isExpand ? 'FolderOpened' : 'Folder'"
  124. type="primary"
  125. @click="handleExpand"
  126. >
  127. {{ isExpand ? '全部折叠' : '全部展开' }}
  128. </el-button>
  129. <right-toolbar
  130. v-model:showSearch="showSearch"
  131. class="ml10 mr20"
  132. style="float: right;"
  133. @queryTable="getDataList"
  134. />
  135. </div>
  136. </el-row>
  137. <!-- 树形数据表格区域 -->
  138. <el-table
  139. ref="tableRef"
  140. :data="state.dataList"
  141. v-loading="state.loading"
  142. border
  143. row-key="${pk.attrName}"
  144. default-expand-all
  145. :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
  146. :cell-style="tableStyle.cellStyle"
  147. :header-cell-style="tableStyle.headerCellStyle"
  148. @selection-change="selectionChangHandle"
  149. @expand-change="onExpandChange"
  150. >
  151. <el-table-column type="selection" width="40" align="center" />
  152. <el-table-column type="index" label="#" width="40" />
  153. #foreach($field in $gridList)
  154. #if($field.fieldDict)
  155. <el-table-column prop="${field.attrName}" label="#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end" show-overflow-tooltip>
  156. <template #default="scope">
  157. <dict-tag :options="${field.fieldDict}" :value="scope.row.${field.attrName}" />
  158. </template>
  159. </el-table-column>
  160. #elseif($field.formType == 'upload-img')
  161. <el-table-column prop="${field.attrName}" label="#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end" width="120">
  162. <template #default="scope">
  163. <el-image
  164. v-if="scope.row.${field.attrName}"
  165. :src="baseURL + scope.row.${field.attrName}"
  166. fit="cover"
  167. class="w-20 h-20 rounded"
  168. :preview-teleported="true"
  169. />
  170. <span v-else class="text-gray-400">暂无图片</span>
  171. </template>
  172. </el-table-column>
  173. #else
  174. <el-table-column
  175. prop="${field.attrName}"
  176. label="#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end"
  177. show-overflow-tooltip
  178. #if($field == $gridList[0])
  179. width="200"
  180. #end
  181. />
  182. #end
  183. #end
  184. <el-table-column label="操作" width="200" fixed="right">
  185. <template #default="scope">
  186. <el-button
  187. icon="plus"
  188. text
  189. type="primary"
  190. v-auth="'${moduleName}_${functionName}_add'"
  191. @click="formDialogRef.openDialog(undefined, scope.row.${pk.attrName})"
  192. >
  193. 新增
  194. </el-button>
  195. <el-button
  196. icon="edit-pen"
  197. text
  198. type="primary"
  199. v-auth="'${moduleName}_${functionName}_edit'"
  200. @click="formDialogRef.openDialog(scope.row.${pk.attrName})"
  201. >
  202. 编辑
  203. </el-button>
  204. <el-button
  205. icon="delete"
  206. text
  207. type="primary"
  208. v-auth="'${moduleName}_${functionName}_del'"
  209. @click="handleDelete([scope.row.${pk.attrName}])"
  210. >
  211. 删除
  212. </el-button>
  213. </template>
  214. </el-table-column>
  215. </el-table>
  216. </div>
  217. <!-- 编辑、新增弹窗 -->
  218. <form-dialog ref="formDialogRef" @refresh="getDataList(false)" />
  219. </div>
  220. </template>
  221. <script setup lang="ts" name="system${ClassName}Tree">
  222. // ========== 导入声明 ==========
  223. import { BasicTableProps, useTable } from "/@/hooks/table";
  224. import { fetchTreeList, delObjs } from "/@/api/${moduleName}/${functionName}";
  225. import { useMessage, useMessageBox } from "/@/hooks/message";
  226. import { useDict } from '/@/hooks/dict';
  227. // ========== 组件声明 ==========
  228. // 异步加载表单弹窗组件
  229. const FormDialog = defineAsyncComponent(() => import('./form.vue'));
  230. // ========== 字典数据 ==========
  231. #set($fieldDict=[])
  232. #foreach($field in $queryList)
  233. #if($field.fieldDict)
  234. #set($void=$fieldDict.add($field.fieldDict))
  235. #end
  236. #end
  237. #foreach($field in $gridList)
  238. #if($field.fieldDict)
  239. #set($void=$fieldDict.add($field.fieldDict))
  240. #end
  241. #end
  242. #if($fieldDict && $fieldDict.size() > 0)
  243. // 加载字典数据
  244. const { $dict.format($fieldDict) } = useDict($dict.quotation($fieldDict));
  245. #end
  246. // ========== 组件引用 ==========
  247. const formDialogRef = ref(); // 表单弹窗引用
  248. const queryRef = ref(); // 查询表单引用
  249. const tableRef = ref(); // 表格引用
  250. // ========== 响应式数据 ==========
  251. const showSearch = ref(true); // 是否显示搜索区域
  252. const selectObjs = ref([]) as any; // 表格多选数据
  253. const multiple = ref(true); // 是否多选
  254. const isExpand = ref(true); // 是否展开状态,默认展开
  255. // ========== 表格状态 ==========
  256. const state: BasicTableProps = reactive<BasicTableProps>({
  257. isPage: false, // 是否分页
  258. queryForm: {}, // 查询参数
  259. pageList: fetchTreeList, // 树形数据查询方法(不分页)
  260. loading: false, // 加载状态
  261. dataList: [] // 数据列表
  262. });
  263. // ========== Hook引用 ==========
  264. // 表格相关Hook (树形表格不使用分页)
  265. const {
  266. getDataList,
  267. tableStyle
  268. } = useTable(state);
  269. // ========== 方法定义 ==========
  270. /**
  271. * 重置查询条件
  272. */
  273. const resetQuery = () => {
  274. // 清空搜索条件
  275. queryRef.value?.resetFields();
  276. // 清空多选
  277. selectObjs.value = [];
  278. // 重新查询
  279. getDataList();
  280. };
  281. /**
  282. * 表格多选事件处理
  283. * @param objs 选中的数据行
  284. */
  285. const selectionChangHandle = (objs: { ${pk.attrName}: string }[]) => {
  286. selectObjs.value = objs.map(({ ${pk.attrName} }) => ${pk.attrName});
  287. multiple.value = !objs.length;
  288. };
  289. /**
  290. * 树形表格展开状态变化事件
  291. * 当用户手动点击展开/折叠按钮时触发
  292. * @param row 当前行数据
  293. * @param expanded 是否展开
  294. */
  295. const onExpandChange = (row: any, expanded: boolean) => {
  296. // 可以在这里添加额外的逻辑,比如记录展开状态等
  297. // console.log('Row expand changed:', row, expanded);
  298. };
  299. /**
  300. * 删除数据处理
  301. * @param ids 要删除的数据ID数组
  302. */
  303. const handleDelete = async (ids: string[]) => {
  304. try {
  305. await useMessageBox().confirm('此操作将永久删除选中数据及其子数据,是否继续?');
  306. } catch {
  307. return;
  308. }
  309. try {
  310. await delObjs(ids);
  311. getDataList();
  312. useMessage().success('删除成功');
  313. } catch (err: any) {
  314. useMessage().error(err.msg);
  315. }
  316. };
  317. /**
  318. * 展开/折叠树形表格
  319. * 基于Element Plus官方文档的toggleRowExpansion方法实现
  320. */
  321. const handleExpand = () => {
  322. isExpand.value = !isExpand.value;
  323. // 等待DOM更新后再进行展开/折叠操作
  324. nextTick(() => {
  325. if (state.dataList && state.dataList.length > 0 && tableRef.value) {
  326. toggleExpand(state.dataList, isExpand.value);
  327. }
  328. });
  329. };
  330. /**
  331. * 递归展开/折叠所有节点
  332. * 使用Element Plus Table组件的toggleRowExpansion方法
  333. * @param children 子节点数组
  334. * @param unfold 是否展开
  335. */
  336. const toggleExpand = (children: any[], unfold = true) => {
  337. for (const item of children) {
  338. // 使用Element Plus官方的toggleRowExpansion方法
  339. // 第二个参数为可选的布尔值,直接设置展开状态
  340. tableRef.value?.toggleRowExpansion(item, unfold);
  341. // 递归处理子节点
  342. if (item.children && item.children.length > 0) {
  343. toggleExpand(item.children, unfold);
  344. }
  345. }
  346. };
  347. // ========== 生命周期 ==========
  348. // 组件挂载时获取数据
  349. onMounted(() => {
  350. getDataList();
  351. });
  352. </script>