SurveyFormDialog.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519
  1. <template>
  2. <el-dialog
  3. title="调查表填报"
  4. :visible.sync="dialogVisible"
  5. width="800px"
  6. :close-on-click-modal="false"
  7. :show-close="true"
  8. append-to-body
  9. :modal="false"
  10. @close="handleClose"
  11. >
  12. <el-form ref="surveyForm" :model="form" :rules="rules" label-width="120px">
  13. <el-row :gutter="20">
  14. <!-- 动态生成表单字段 -->
  15. <el-col
  16. v-for="(field, index) in formFields"
  17. :key="field.prop || field.id || `field-${index}`"
  18. :span="field.colSpan || 12"
  19. >
  20. <el-form-item
  21. :label="field.label"
  22. :prop="field.prop"
  23. :rules="field.rules"
  24. >
  25. <!-- 文本输入框 -->
  26. <el-input
  27. v-if="field.type === 'input' || !field.type"
  28. v-model="form[field.prop]"
  29. :placeholder="field.placeholder || `请输入${field.label}`"
  30. :disabled="field.disabled || isViewMode"
  31. />
  32. <!-- 数字输入框 -->
  33. <el-input-number
  34. v-else-if="field.type === 'number'"
  35. v-model="form[field.prop]"
  36. :placeholder="field.placeholder || `请输入${field.label}`"
  37. :disabled="field.disabled || isViewMode"
  38. :min="field.min"
  39. :max="field.max"
  40. :precision="field.precision"
  41. style="width: 100%"
  42. />
  43. <!-- 下拉选择框(字典类型) -->
  44. <el-select
  45. v-else-if="field.type === 'select' && field.dictType"
  46. v-model="form[field.prop]"
  47. :placeholder="field.placeholder || `请选择${field.label}`"
  48. :disabled="field.disabled || isViewMode"
  49. style="width: 100%"
  50. :clearable="field.clearable !== false"
  51. :multiple="field.multiple"
  52. >
  53. <el-option
  54. v-for="item in getDictOptions(field.dictType)"
  55. :key="item.key || item.value"
  56. :label="item.name || item.label"
  57. :value="item.key || item.value"
  58. />
  59. </el-select>
  60. <!-- 下拉选择框(自定义选项) -->
  61. <el-select
  62. v-else-if="field.type === 'select' && field.options"
  63. v-model="form[field.prop]"
  64. :placeholder="field.placeholder || `请选择${field.label}`"
  65. :disabled="field.disabled || isViewMode"
  66. style="width: 100%"
  67. :clearable="field.clearable !== false"
  68. :multiple="field.multiple"
  69. >
  70. <el-option
  71. v-for="item in field.options"
  72. :key="item.value || item.key"
  73. :label="item.label || item.name"
  74. :value="item.value || item.key"
  75. />
  76. </el-select>
  77. <!-- 日期选择器 -->
  78. <el-date-picker
  79. v-else-if="field.type === 'date'"
  80. v-model="form[field.prop]"
  81. type="date"
  82. :placeholder="field.placeholder || `请选择${field.label}`"
  83. :disabled="field.disabled || isViewMode"
  84. style="width: 100%"
  85. :format="field.format || 'yyyy-MM-dd'"
  86. :value-format="field.valueFormat || 'yyyy-MM-dd'"
  87. />
  88. <!-- 日期时间选择器 -->
  89. <el-date-picker
  90. v-else-if="field.type === 'datetime'"
  91. v-model="form[field.prop]"
  92. type="datetime"
  93. :placeholder="field.placeholder || `请选择${field.label}`"
  94. :disabled="field.disabled || isViewMode"
  95. style="width: 100%"
  96. :format="field.format || 'yyyy-MM-dd HH:mm:ss'"
  97. :value-format="field.valueFormat || 'yyyy-MM-dd HH:mm:ss'"
  98. />
  99. <!-- 年份选择器 -->
  100. <el-date-picker
  101. v-else-if="field.type === 'year'"
  102. v-model="form[field.prop]"
  103. type="year"
  104. :placeholder="field.placeholder || `请选择${field.label}`"
  105. :disabled="field.disabled || isViewMode"
  106. style="width: 100%"
  107. :format="field.format || 'yyyy'"
  108. :value-format="field.valueFormat || 'yyyy'"
  109. />
  110. <!-- 文本域 -->
  111. <el-input
  112. v-else-if="field.type === 'textarea'"
  113. v-model="form[field.prop]"
  114. type="textarea"
  115. :rows="field.rows || 3"
  116. :placeholder="field.placeholder || `请输入${field.label}`"
  117. :disabled="field.disabled || isViewMode"
  118. />
  119. </el-form-item>
  120. </el-col>
  121. </el-row>
  122. </el-form>
  123. <div slot="footer" class="dialog-footer">
  124. <el-button type="primary" @click="handleSave">保存</el-button>
  125. <el-button @click="handleCancel">取消</el-button>
  126. </div>
  127. </el-dialog>
  128. </template>
  129. <script>
  130. import { Message } from 'element-ui'
  131. import { dictMixin } from '@/mixins/useDict'
  132. import { saveSingleRecordSurvey } from '@/api/audit/survey'
  133. export default {
  134. name: 'SurveyFormDialog',
  135. mixins: [dictMixin],
  136. props: {
  137. visible: {
  138. type: Boolean,
  139. default: false,
  140. },
  141. surveyData: {
  142. type: Object,
  143. default: () => ({}),
  144. },
  145. // 表单字段配置
  146. // 格式: [
  147. // {
  148. // prop: 'institutionName', // 字段属性名
  149. // label: '机构名称', // 字段标签
  150. // type: 'input', // 字段类型: input, select, date, number, textarea等
  151. // colSpan: 12, // 列宽,默认12(占一半)
  152. // dictType: 'institutionNature', // 字典类型(如果type为select且使用字典)
  153. // options: [], // 自定义选项(如果type为select且不使用字典)
  154. // placeholder: '请输入机构名称',
  155. // rules: [], // 验证规则
  156. // defaultValue: '', // 默认值
  157. // disabled: false, // 是否禁用
  158. // clearable: true, // 是否可清空
  159. // multiple: false, // 是否多选
  160. // }
  161. // ]
  162. formFields: {
  163. type: Array,
  164. default: () => [],
  165. },
  166. // 是否查看模式
  167. isViewMode: {
  168. type: Boolean,
  169. default: false,
  170. },
  171. // 被监审单位ID
  172. auditedUnitId: {
  173. type: String,
  174. default: '',
  175. },
  176. // 上传记录ID
  177. uploadId: {
  178. type: String,
  179. default: '',
  180. },
  181. // 成本调查表模板ID
  182. surveyTemplateId: {
  183. type: String,
  184. default: '',
  185. },
  186. // 目录ID
  187. catalogId: {
  188. type: String,
  189. default: '',
  190. },
  191. },
  192. data() {
  193. return {
  194. dialogVisible: false,
  195. form: {},
  196. rules: {},
  197. dictData: {}, // 初始化字典数据对象
  198. }
  199. },
  200. computed: {
  201. // 计算需要获取的字典类型
  202. dictTypes() {
  203. const types = new Set()
  204. this.formFields.forEach((field) => {
  205. if (field.type === 'select' && field.dictType) {
  206. types.add(field.dictType)
  207. }
  208. })
  209. return Array.from(types)
  210. },
  211. },
  212. watch: {
  213. visible: {
  214. handler(newVal) {
  215. this.dialogVisible = newVal
  216. if (newVal) {
  217. // 弹窗打开时初始化表单
  218. this.initForm()
  219. }
  220. },
  221. immediate: true,
  222. },
  223. dialogVisible(newVal) {
  224. if (!newVal) {
  225. this.$emit('update:visible', false)
  226. }
  227. },
  228. formFields: {
  229. handler() {
  230. // 字段配置变化时重新初始化
  231. if (this.dialogVisible) {
  232. this.initForm()
  233. }
  234. },
  235. deep: true,
  236. },
  237. surveyData: {
  238. handler() {
  239. // 详情数据变化时重新初始化表单(用于回显数据)
  240. if (this.dialogVisible) {
  241. this.initForm()
  242. }
  243. },
  244. deep: true,
  245. },
  246. },
  247. created() {
  248. // 初始化字典数据
  249. this.initDictData()
  250. },
  251. methods: {
  252. // 初始化字典数据
  253. initDictData() {
  254. if (this.dictTypes.length > 0) {
  255. // 初始化字典数据对象
  256. this.dictTypes.forEach((type) => {
  257. if (!this.dictData[type]) {
  258. this.$set(this.dictData, type, [])
  259. }
  260. })
  261. // 调用父级 mixin 的方法获取字典数据
  262. if (this.dictTypes.length > 0) {
  263. this.getDictType()
  264. }
  265. }
  266. },
  267. // 获取字典选项
  268. getDictOptions(dictType) {
  269. if (!this.dictData || !this.dictData[dictType]) {
  270. return []
  271. }
  272. return this.dictData[dictType] || []
  273. },
  274. // 初始化表单
  275. initForm() {
  276. const form = {}
  277. const rules = {}
  278. // 如果没有传入字段配置,使用默认配置
  279. const fields =
  280. this.formFields && this.formFields.length > 0
  281. ? this.formFields
  282. : this.getDefaultFormFields()
  283. fields.forEach((field) => {
  284. // 初始化表单值
  285. // 优先使用传入的详情数据(surveyData 中可能包含从 getSurveyDetail 接口返回的数据)
  286. if (
  287. this.surveyData &&
  288. this.surveyData[field.prop] !== undefined &&
  289. this.surveyData[field.prop] !== null &&
  290. this.surveyData[field.prop] !== ''
  291. ) {
  292. // 优先使用传入的数据
  293. form[field.prop] = this.surveyData[field.prop]
  294. } else if (field.defaultValue !== undefined) {
  295. // 其次使用默认值
  296. form[field.prop] = field.defaultValue
  297. } else {
  298. // 最后使用空值
  299. form[field.prop] = field.multiple ? [] : ''
  300. }
  301. // 初始化验证规则
  302. if (field.rules && Array.isArray(field.rules)) {
  303. rules[field.prop] = field.rules
  304. } else if (field.required) {
  305. // 如果字段标记为必填,自动添加必填验证
  306. rules[field.prop] = [
  307. {
  308. required: true,
  309. message: `请输入${field.label}`,
  310. trigger: field.type === 'select' ? 'change' : 'blur',
  311. },
  312. ]
  313. }
  314. })
  315. this.form = form
  316. this.rules = rules
  317. // 初始化字典数据
  318. this.initDictData()
  319. },
  320. // 获取默认表单字段配置(兼容旧版本)
  321. getDefaultFormFields() {
  322. return [
  323. {
  324. prop: 'institutionName',
  325. label: '机构名称',
  326. type: 'input',
  327. colSpan: 12,
  328. defaultValue: '幼儿园基本情况',
  329. required: true,
  330. },
  331. {
  332. prop: 'institutionNature',
  333. label: '机构性质',
  334. type: 'select',
  335. colSpan: 12,
  336. dictType: 'institutionNature', // 字典类型
  337. defaultValue: '公办',
  338. required: true,
  339. },
  340. {
  341. prop: 'institutionLevel',
  342. label: '机构评定等级',
  343. type: 'select',
  344. colSpan: 12,
  345. dictType: 'institutionLevel', // 字典类型
  346. defaultValue: '省一级',
  347. required: true,
  348. },
  349. {
  350. prop: 'educationMode',
  351. label: '机构办学方式',
  352. type: 'select',
  353. colSpan: 12,
  354. dictType: 'educationMode', // 字典类型
  355. defaultValue: '全日制',
  356. required: true,
  357. },
  358. {
  359. prop: 'institutionAddress',
  360. label: '机构地址',
  361. type: 'input',
  362. colSpan: 12,
  363. required: true,
  364. },
  365. {
  366. prop: 'formFiller',
  367. label: '机构填表人',
  368. type: 'input',
  369. colSpan: 12,
  370. required: true,
  371. },
  372. {
  373. prop: 'financialManager',
  374. label: '机构财务负责人',
  375. type: 'input',
  376. colSpan: 12,
  377. required: true,
  378. },
  379. {
  380. prop: 'contactPhone',
  381. label: '机构联系电话',
  382. type: 'input',
  383. colSpan: 12,
  384. required: true,
  385. rules: [
  386. {
  387. required: true,
  388. message: '请输入机构联系电话',
  389. trigger: 'blur',
  390. },
  391. {
  392. pattern: /^1[3-9]\d{9}$/,
  393. message: '请输入正确的手机号码',
  394. trigger: 'blur',
  395. },
  396. ],
  397. },
  398. {
  399. prop: 'formFillDate',
  400. label: '机构填表日期',
  401. type: 'date',
  402. colSpan: 12,
  403. required: true,
  404. },
  405. ]
  406. },
  407. handleClose() {
  408. this.dialogVisible = false
  409. this.$emit('update:visible', false)
  410. },
  411. handleCancel() {
  412. this.handleClose()
  413. },
  414. async handleSave() {
  415. this.$refs.surveyForm.validate(async (valid) => {
  416. if (valid) {
  417. try {
  418. // 判断是否有数据(编辑模式):如果有 uploadId,说明是编辑已有数据
  419. const hasData = !!(
  420. this.uploadId ||
  421. this.surveyData.uploadId ||
  422. this.surveyData.id
  423. )
  424. // 将表单数据转换为接口需要的格式
  425. const saveData = this.formFields.map((field) => {
  426. const dataItem = {
  427. auditedUnitId:
  428. this.auditedUnitId || this.surveyData.auditedUnitId || '',
  429. surveyTemplateId:
  430. this.surveyTemplateId ||
  431. this.surveyData.surveyTemplateId ||
  432. '',
  433. catalogId: this.catalogId || this.surveyData.catalogId || '',
  434. rowid: field.prop, // 字段ID(对应 fixedFieldids)
  435. rkey: field.label, // 字段名称(对应 fixedFields,即 label)
  436. rvalue: this.form[field.prop] || '', // 字段值(表单输入的值)
  437. }
  438. // 如果有数据(编辑模式),添加 uploadId 字段
  439. if (hasData) {
  440. dataItem.uploadId =
  441. this.uploadId ||
  442. this.surveyData.uploadId ||
  443. this.surveyData.id ||
  444. ''
  445. }
  446. return dataItem
  447. })
  448. console.log('保存表单数据:', saveData)
  449. // 调用保存接口
  450. const res = await saveSingleRecordSurvey(saveData)
  451. if (res && res.code === 200) {
  452. Message.success('保存成功')
  453. // 触发保存事件,将数据传递给父组件
  454. this.$emit('save', { ...this.form })
  455. // 触发刷新事件,通知父组件刷新列表
  456. this.$emit('refresh')
  457. this.handleClose()
  458. } else {
  459. Message.error(res.message || '保存失败')
  460. }
  461. } catch (err) {
  462. console.error('保存失败', err)
  463. Message.error(err.message || '保存失败')
  464. }
  465. } else {
  466. Message.error('请完善表单信息')
  467. return false
  468. }
  469. })
  470. },
  471. },
  472. }
  473. </script>
  474. <style scoped lang="scss">
  475. .dialog-footer {
  476. text-align: center;
  477. .el-button {
  478. margin: 0 10px;
  479. }
  480. }
  481. ::v-deep .el-dialog__header {
  482. padding: 20px 20px 10px;
  483. .el-dialog__title {
  484. font-size: 18px;
  485. font-weight: 600;
  486. color: #303133;
  487. }
  488. }
  489. ::v-deep .el-form-item {
  490. margin-bottom: 20px;
  491. .el-form-item__label {
  492. // color: #409eff;
  493. font-weight: 500;
  494. }
  495. }
  496. </style>