树形表单.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. <template>
  2. <el-dialog :title="form.${pk.attrName} ? '编辑' : '新增'" v-model="visible"
  3. :close-on-click-modal="false" draggable>
  4. <el-form ref="dataFormRef" :model="form" :rules="dataRules" formDialogRef label-width="90px" v-loading="loading">
  5. <el-row :gutter="24">
  6. <!-- 父级节点选择 -->
  7. <el-col :span="24" class="mb20">
  8. <el-form-item label="父级节点" prop="parentId">
  9. <el-tree-select
  10. v-model="form.parentId"
  11. :data="parentNodes"
  12. :props="treeSelectProps"
  13. check-strictly
  14. :render-after-expand="false"
  15. placeholder="请选择父级节点"
  16. style="width: 100%"
  17. clearable
  18. />
  19. </el-form-item>
  20. </el-col>
  21. #foreach($field in $formList)
  22. #if($field.attrName != ${pk.attrName} && $field.attrName != 'parentId')
  23. #if($formLayout == 1)
  24. <el-col :span="24" class="mb20">
  25. #elseif($formLayout == 2)
  26. <el-col :span="12" class="mb20">
  27. #end
  28. #if($field.formType == 'text')
  29. <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end" prop="${field.attrName}">
  30. <el-input v-model="form.${field.attrName}" placeholder="请输入#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end"/>
  31. </el-form-item>
  32. </el-col>
  33. #elseif($field.formType == 'textarea')
  34. <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end" prop="${field.attrName}">
  35. <el-input type="textarea" v-model="form.${field.attrName}" placeholder="请输入#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end"/>
  36. </el-form-item>
  37. </el-col>
  38. #elseif($field.formType == 'select')
  39. <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end" prop="${field.attrName}">
  40. <el-select v-model="form.${field.attrName}" placeholder="请选择#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end">
  41. #if($field.fieldDict)
  42. <el-option :value="item.value" :label="item.label" v-for="(item, index) in ${field.fieldDict}" :key="index"></el-option>
  43. #else
  44. <el-option label="请选择" value="0"></el-option>
  45. #end
  46. </el-select>
  47. </el-form-item>
  48. </el-col>
  49. #elseif($field.formType == 'radio')
  50. <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end" prop="${field.attrName}">
  51. <el-radio-group v-model="form.${field.attrName}">
  52. #if($field.fieldDict)
  53. <el-radio :label="item.value" v-for="(item, index) in ${field.fieldDict}" border :key="index">{{ item.label }}</el-radio>
  54. #else
  55. <el-radio label="${field.fieldComment}" border>${field.fieldComment}</el-radio>
  56. #end
  57. </el-radio-group>
  58. </el-form-item>
  59. </el-col>
  60. #elseif($field.formType == 'checkbox')
  61. <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end" prop="${field.attrName}">
  62. <el-checkbox-group v-model="form.${field.attrName}">
  63. #if($field.fieldDict)
  64. <el-checkbox :label="item.value" v-for="(item, index) in ${field.fieldDict}" :key="index">{{ item.label }}</el-checkbox>
  65. #else
  66. <el-checkbox label="启用" name="type"></el-checkbox>
  67. <el-checkbox label="禁用" name="type"></el-checkbox>
  68. #end
  69. </el-checkbox-group>
  70. </el-form-item>
  71. </el-col>
  72. #elseif($field.formType == 'date')
  73. <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end" prop="${field.attrName}">
  74. <el-date-picker type="date" placeholder="请选择#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end" v-model="form.${field.attrName}" :value-format="dateStr"></el-date-picker>
  75. </el-form-item>
  76. </el-col>
  77. #elseif($field.formType == 'datetime')
  78. <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end" prop="${field.attrName}">
  79. <el-date-picker type="datetime" placeholder="请选择#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end" v-model="form.${field.attrName}" :value-format="dateTimeStr"></el-date-picker>
  80. </el-form-item>
  81. </el-col>
  82. #elseif($field.formType == 'number')
  83. <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end" prop="${field.attrName}">
  84. <el-input-number :min="1" :max="1000" v-model="form.${field.attrName}" placeholder="请输入#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end"></el-input-number>
  85. </el-form-item>
  86. </el-col>
  87. #elseif($field.formType == 'upload-file')
  88. <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end" prop="${field.attrName}">
  89. <upload-file v-model="form.${field.attrName}"></upload-file>
  90. </el-form-item>
  91. </el-col>
  92. #elseif($field.formType == 'upload-img')
  93. <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end" prop="${field.attrName}">
  94. <upload-img v-model:imageUrl="form.${field.attrName}"></upload-img>
  95. </el-form-item>
  96. </el-col>
  97. #elseif($field.formType == 'editor')
  98. <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end" prop="${field.attrName}">
  99. <editor v-if="visible" v-model:get-html="form.${field.attrName}" placeholder="请输入#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end"></editor>
  100. </el-form-item>
  101. </el-col>
  102. #else
  103. <el-form-item label="#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end" prop="${field.attrName}">
  104. <el-input v-model="form.${field.attrName}" placeholder="请输入#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end"/>
  105. </el-form-item>
  106. </el-col>
  107. #end
  108. #end
  109. #end
  110. </el-row>
  111. </el-form>
  112. <template #footer>
  113. <span class="dialog-footer">
  114. <el-button @click="visible = false">取 消</el-button>
  115. <el-button type="primary" @click="onSubmit" :disabled="loading">确 认</el-button>
  116. </span>
  117. </template>
  118. </el-dialog>
  119. </template>
  120. <script setup lang="ts" name="${ClassName}TreeDialog">
  121. // ========== 1. 导入语句 ==========
  122. import { useDict } from '/@/hooks/dict';
  123. import { rule } from '/@/utils/validate';
  124. import { useMessage } from "/@/hooks/message";
  125. import { getObj, addObj, putObj, validateExist, getParentNodes } from '/@/api/${moduleName}/${functionName}';
  126. // ========== 2. 组件定义 ==========
  127. // 定义组件事件
  128. const emit = defineEmits(['refresh']);
  129. // ========== 3. 响应式数据定义 ==========
  130. // 基础响应式变量
  131. const dataFormRef = ref(); // 表单引用
  132. const visible = ref(false); // 弹窗显示状态
  133. const loading = ref(false); // 加载状态
  134. const parentNodes = ref([]); // 父级节点数据
  135. // 树形选择器配置
  136. const treeSelectProps = {
  137. children: 'children',
  138. label: 'name', // 假设显示字段为name,请根据实际情况调整
  139. value: '${pk.attrName}',
  140. checkStrictly: true
  141. };
  142. // 表单数据对象
  143. const form = reactive({
  144. #if(!$formList.contains(${pk.attrName}))
  145. ${pk.attrName}: '', // 主键
  146. #end
  147. parentId: null, // 父级ID
  148. #foreach($field in $formList)
  149. #if($field.attrName != 'parentId')
  150. #if($field.formType == 'number')
  151. ${field.attrName}: 0, // ${field.fieldComment}
  152. #elseif($field.formType == 'checkbox')
  153. ${field.attrName}: [], // ${field.fieldComment}
  154. #else
  155. ${field.attrName}: '', // ${field.fieldComment}
  156. #end
  157. #end
  158. #end
  159. });
  160. // ========== 4. 字典数据处理 ==========
  161. #set($fieldDict=[])
  162. #foreach($field in $gridList)
  163. #if($field.fieldDict)
  164. #set($void=$fieldDict.add($field.fieldDict))
  165. #end
  166. #end
  167. #if($fieldDict && $fieldDict.size() > 0)
  168. // 加载字典数据
  169. const { $dict.format($fieldDict) } = useDict($dict.quotation($fieldDict));
  170. #end
  171. // ========== 5. 表单校验规则 ==========
  172. const dataRules = ref({
  173. #foreach($field in $formList)
  174. #if($field.formRequired == '1' && $field.formValidator == 'duplicate')
  175. ${field.attrName}: [
  176. { required: true, message: '${field.fieldComment}不能为空', trigger: 'blur' },
  177. {
  178. validator: (rule: any, value: any, callback: any) => {
  179. // 重复性校验(编辑时跳过)
  180. validateExist(rule, value, callback, form.${pk.attrName} !== '');
  181. },
  182. trigger: 'blur',
  183. }
  184. ],
  185. #elseif($field.formRequired == '1' && $field.formValidator)
  186. ${field.attrName}: [
  187. { required: true, message: '${field.fieldComment}不能为空', trigger: 'blur' },
  188. { validator: rule.${field.formValidator}, trigger: 'blur' }
  189. ],
  190. #elseif($field.formRequired == '1')
  191. ${field.attrName}: [
  192. { required: true, message: '${field.fieldComment}不能为空', trigger: 'blur' }
  193. ],
  194. #elseif($field.formValidator)
  195. ${field.attrName}: [
  196. { validator: rule.${field.formValidator}, trigger: 'blur' }
  197. ],
  198. #end
  199. #end
  200. });
  201. // ========== 6. 方法定义 ==========
  202. // 获取详情数据
  203. const get${ClassName}Data = async (id: string) => {
  204. try {
  205. loading.value = true;
  206. const { data } = await getObj({ ${pk.attrName}: id });
  207. // 直接将第一条数据赋值给表单
  208. Object.assign(form, data[0]);
  209. } catch (error) {
  210. useMessage().error('获取数据失败');
  211. } finally {
  212. loading.value = false;
  213. }
  214. };
  215. // 获取父级节点数据
  216. const getParentNodesList = async () => {
  217. try {
  218. const { data } = await getParentNodes();
  219. // 添加根节点选项
  220. parentNodes.value = [
  221. { ${pk.attrName}: null, name: '根节点', children: [] },
  222. ...buildParentTree(data)
  223. ];
  224. } catch (error) {
  225. console.error('获取父级节点失败:', error);
  226. parentNodes.value = [{ ${pk.attrName}: null, name: '根节点', children: [] }];
  227. }
  228. };
  229. // 构建父级节点树形结构
  230. const buildParentTree = (data: any[]) => {
  231. // 如果后端已经返回树形结构,直接返回
  232. if (data && data.length > 0 && data[0].children !== undefined) {
  233. return data;
  234. }
  235. // 如果是平铺数据,需要构建树形结构
  236. const map = new Map();
  237. const roots: any[] = [];
  238. data.forEach(item => {
  239. map.set(item.${pk.attrName}, { ...item, children: [] });
  240. });
  241. data.forEach(item => {
  242. const node = map.get(item.${pk.attrName});
  243. if (item.parentId) {
  244. const parent = map.get(item.parentId);
  245. if (parent) {
  246. parent.children.push(node);
  247. } else {
  248. roots.push(node);
  249. }
  250. } else {
  251. roots.push(node);
  252. }
  253. });
  254. return roots;
  255. };
  256. // 打开弹窗方法
  257. const openDialog = (id: string, parentId?: string) => {
  258. visible.value = true;
  259. form.${pk.attrName} = '';
  260. form.parentId = parentId || null;
  261. // 获取父级节点数据
  262. getParentNodesList();
  263. // 重置表单数据
  264. nextTick(() => {
  265. dataFormRef.value?.resetFields();
  266. });
  267. // 获取${ClassName}信息
  268. if (id) {
  269. form.${pk.attrName} = id;
  270. get${ClassName}Data(id);
  271. }
  272. };
  273. // 提交表单方法
  274. const onSubmit = async () => {
  275. loading.value = true; // 防止重复提交
  276. // 表单校验
  277. const valid = await dataFormRef.value.validate().catch(() => {});
  278. if (!valid) {
  279. loading.value = false;
  280. return false;
  281. }
  282. try {
  283. // 处理父级ID为空的情况
  284. if (!form.parentId) {
  285. form.parentId = null;
  286. }
  287. // 根据是否有ID判断是新增还是修改
  288. form.${pk.attrName} ? await putObj(form) : await addObj(form);
  289. useMessage().success(form.${pk.attrName} ? '修改成功' : '添加成功');
  290. visible.value = false;
  291. emit('refresh'); // 通知父组件刷新列表
  292. } catch (err: any) {
  293. useMessage().error(err.msg);
  294. } finally {
  295. loading.value = false;
  296. }
  297. };
  298. // ========== 7. 对外暴露 ==========
  299. // 暴露方法给父组件
  300. defineExpose({
  301. openDialog
  302. });
  303. </script>