SurveyFormDialog.vue 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991
  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 effectiveFormFields"
  17. :key="`${field.prop || field.id || 'field'}-${index}`"
  18. :span="field.colSpan || 12"
  19. >
  20. <el-form-item :label="field.label" :prop="field.prop">
  21. <!-- 文本输入框 -->
  22. <el-input
  23. v-if="field.type === 'input' || !field.type"
  24. v-model="form[field.prop]"
  25. :placeholder="field.placeholder || `请输入${field.label}`"
  26. :disabled="field.disabled || isViewMode"
  27. :maxlength="field.formatLength || field.totalLength"
  28. />
  29. <!-- 数字输入框 -->
  30. <el-input-number
  31. v-else-if="field.type === 'number'"
  32. v-model="form[field.prop]"
  33. :placeholder="field.placeholder || `请输入${field.label}`"
  34. :disabled="field.disabled || isViewMode"
  35. :min="field.min"
  36. :max="field.max"
  37. :precision="field.precision"
  38. style="width: 100%"
  39. />
  40. <!-- 下拉选择框(字典类型) -->
  41. <el-select
  42. v-else-if="
  43. field.type === 'select' && (field.dictCode || field.dictType)
  44. "
  45. v-model="form[field.prop]"
  46. :placeholder="field.placeholder || `请选择${field.label}`"
  47. :disabled="field.disabled || isViewMode"
  48. style="width: 100%"
  49. :clearable="field.clearable !== false"
  50. :multiple="field.multiple"
  51. >
  52. <el-option
  53. v-for="item in getDictOptions(field.dictCode || field.dictType)"
  54. :key="item.key || item.value"
  55. :label="item.name || item.label"
  56. :value="item.key || item.value"
  57. />
  58. </el-select>
  59. <!-- 下拉选择框(自定义选项) -->
  60. <el-select
  61. v-else-if="field.type === 'select' && field.options"
  62. v-model="form[field.prop]"
  63. :placeholder="field.placeholder || `请选择${field.label}`"
  64. :disabled="field.disabled || isViewMode"
  65. style="width: 100%"
  66. :clearable="field.clearable !== false"
  67. :multiple="field.multiple"
  68. >
  69. <el-option
  70. v-for="item in field.options"
  71. :key="item.value || item.key"
  72. :label="item.label || item.name"
  73. :value="item.value || item.key"
  74. />
  75. </el-select>
  76. <!-- 日期选择器 -->
  77. <el-date-picker
  78. v-else-if="field.type === 'date'"
  79. v-model="form[field.prop]"
  80. type="date"
  81. :placeholder="field.placeholder || `请选择${field.label}`"
  82. :disabled="field.disabled || isViewMode"
  83. style="width: 100%"
  84. :format="field.format || 'yyyy-MM-dd'"
  85. :value-format="field.valueFormat || 'yyyy-MM-dd'"
  86. />
  87. <!-- 日期时间选择器 -->
  88. <el-date-picker
  89. v-else-if="field.type === 'datetime'"
  90. v-model="form[field.prop]"
  91. type="datetime"
  92. :placeholder="field.placeholder || `请选择${field.label}`"
  93. :disabled="field.disabled || isViewMode"
  94. style="width: 100%"
  95. :format="field.format || 'yyyy-MM-dd HH:mm:ss'"
  96. :value-format="field.valueFormat || 'yyyy-MM-dd HH:mm:ss'"
  97. />
  98. <!-- 年份选择器 -->
  99. <el-date-picker
  100. v-else-if="field.type === 'year'"
  101. v-model="form[field.prop]"
  102. type="year"
  103. :placeholder="field.placeholder || `请选择${field.label}`"
  104. :disabled="field.disabled || isViewMode"
  105. style="width: 100%"
  106. :format="field.format || 'yyyy'"
  107. :value-format="field.valueFormat || 'yyyy'"
  108. />
  109. <!-- 文本域 -->
  110. <el-input
  111. v-else-if="field.type === 'textarea'"
  112. v-model="form[field.prop]"
  113. type="textarea"
  114. :rows="field.rows || 3"
  115. :placeholder="field.placeholder || `请输入${field.label}`"
  116. :disabled="field.disabled || isViewMode"
  117. />
  118. </el-form-item>
  119. </el-col>
  120. </el-row>
  121. </el-form>
  122. <div slot="footer" class="dialog-footer">
  123. <el-button type="primary" @click="handleSave">保存</el-button>
  124. <el-button @click="handleCancel">取消</el-button>
  125. </div>
  126. </el-dialog>
  127. </template>
  128. <script>
  129. import { Message } from 'element-ui'
  130. import { dictMixin } from '@/mixins/useDict'
  131. import { saveSingleRecordSurvey } from '@/api/audit/survey'
  132. import { getListBySurveyTemplateIdAndVersion } from '@/api/costSurveyTemplateHeaders'
  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. // 任务ID
  192. taskId: {
  193. type: [String, Number],
  194. default: '',
  195. },
  196. // 统一控制接口 type(1=成本调查表,2=报送资料)
  197. requestType: {
  198. type: [String, Number],
  199. default: 1,
  200. },
  201. },
  202. data() {
  203. return {
  204. dialogVisible: false,
  205. form: {},
  206. rules: {},
  207. dictData: {}, // 初始化字典数据对象
  208. internalFormFields: [],
  209. }
  210. },
  211. computed: {
  212. // 计算需要获取的字典类型
  213. dictTypes() {
  214. const types = new Set()
  215. this.effectiveFormFields.forEach((field) => {
  216. const dictKey = field.dictCode || field.dictType
  217. if (field.type === 'select' && dictKey) {
  218. types.add(dictKey)
  219. }
  220. })
  221. return Array.from(types)
  222. },
  223. effectiveFormFields() {
  224. const pickVisible = (arr) =>
  225. (arr || []).filter(
  226. (f) =>
  227. f &&
  228. (f.showVisible === undefined || String(f.showVisible) !== '0')
  229. )
  230. if (this.internalFormFields && this.internalFormFields.length > 0) {
  231. return pickVisible(this.internalFormFields)
  232. }
  233. if (Array.isArray(this.formFields) && this.formFields.length > 0) {
  234. return pickVisible(this.formFields)
  235. }
  236. return pickVisible(this.getDefaultFormFields())
  237. },
  238. },
  239. watch: {
  240. visible: {
  241. async handler(newVal) {
  242. this.dialogVisible = newVal
  243. if (newVal) {
  244. // 弹窗打开时,强制重新获取字段配置并初始化表单
  245. // 先清空旧数据,确保重新加载
  246. this.internalFormFields = []
  247. this.form = {}
  248. this.rules = {}
  249. // 等待字段配置加载完成
  250. await this.ensureTemplateFields()
  251. // 确保 DOM 更新后再初始化表单
  252. await this.$nextTick()
  253. this.initForm()
  254. } else {
  255. // 关闭弹窗时,清理表单数据但保留字段配置(可选)
  256. // this.internalFormFields = []
  257. this.form = {}
  258. this.rules = {}
  259. if (this.$refs.surveyForm) {
  260. this.$refs.surveyForm.clearValidate()
  261. }
  262. }
  263. },
  264. immediate: true,
  265. },
  266. dialogVisible(newVal) {
  267. if (!newVal) {
  268. this.$emit('update:visible', false)
  269. }
  270. },
  271. formFields: {
  272. handler(newVal) {
  273. if (Array.isArray(newVal) && newVal.length > 0) {
  274. this.internalFormFields = []
  275. }
  276. // 字段配置变化时重新初始化
  277. if (this.dialogVisible) {
  278. this.initForm()
  279. }
  280. },
  281. deep: true,
  282. },
  283. surveyData: {
  284. async handler() {
  285. await this.ensureTemplateFields()
  286. // 详情数据变化时重新初始化表单(用于回显数据)
  287. if (this.dialogVisible) {
  288. this.initForm()
  289. }
  290. },
  291. deep: true,
  292. },
  293. surveyTemplateId: {
  294. async handler() {
  295. await this.ensureTemplateFields()
  296. if (this.dialogVisible) {
  297. this.initForm()
  298. }
  299. },
  300. },
  301. },
  302. async created() {
  303. await this.ensureTemplateFields()
  304. // 初始化字典数据
  305. this.initDictData()
  306. },
  307. methods: {
  308. async ensureTemplateFields() {
  309. const hasExternalFields =
  310. Array.isArray(this.formFields) && this.formFields.length > 0
  311. const templateId =
  312. this.surveyTemplateId ||
  313. this.surveyData.surveyTemplateId ||
  314. this.surveyData.surveyId ||
  315. ''
  316. if (!templateId) {
  317. return
  318. }
  319. // 只有在没有外部字段配置时才获取内部字段
  320. if (!hasExternalFields) {
  321. try {
  322. const params = {
  323. surveyTemplateId: templateId,
  324. type: this.requestType,
  325. }
  326. const res = await getListBySurveyTemplateIdAndVersion(params)
  327. if (res && res.code === 200) {
  328. let mapped = []
  329. if (Array.isArray(res.value)) {
  330. // 数组格式:直接映射每个字段
  331. mapped = res.value
  332. .map((item, index) =>
  333. this.mapApiFieldToFormField(item, index)
  334. )
  335. .filter(Boolean)
  336. } else if (res.value && typeof res.value === 'object') {
  337. // 对象格式:从 fixedFields 和 fixedFieldids 解析
  338. const { fixedFields, fixedFieldids } = res.value
  339. if (fixedFields && fixedFieldids) {
  340. const labels = String(fixedFields)
  341. .split(',')
  342. .map((item) => item.trim())
  343. .filter(Boolean)
  344. const ids = String(fixedFieldids)
  345. .split(',')
  346. .map((item) => item.trim())
  347. .filter(Boolean)
  348. mapped = labels.map((label, index) => ({
  349. prop: ids[index] || `field_${index}`,
  350. label,
  351. type: 'input',
  352. colSpan: 12,
  353. placeholder: `请输入${label}`,
  354. required: false,
  355. }))
  356. }
  357. }
  358. // 使用 Vue.set 确保响应式更新
  359. if (mapped.length > 0) {
  360. this.$set(this, 'internalFormFields', mapped)
  361. }
  362. }
  363. } catch (error) {
  364. console.error('获取调查表字段失败:', error)
  365. }
  366. }
  367. },
  368. mapApiFieldToFormField(item, index = 0) {
  369. if (!item) return null
  370. const getVal = (keys, fallback) => {
  371. for (const key of keys) {
  372. if (
  373. key &&
  374. item[key] !== undefined &&
  375. item[key] !== null &&
  376. item[key] !== ''
  377. ) {
  378. return item[key]
  379. }
  380. }
  381. return fallback
  382. }
  383. const toBool = (value) => {
  384. if (value === undefined || value === null) return false
  385. if (typeof value === 'boolean') return value
  386. if (typeof value === 'number') return value === 1
  387. const str = String(value).trim().toLowerCase()
  388. return ['1', 'true', 'y', 'yes', '是'].includes(str)
  389. }
  390. const toNumber = (value) => {
  391. if (value === undefined || value === null || value === '')
  392. return undefined
  393. const num = Number(value)
  394. return Number.isNaN(num) ? undefined : num
  395. }
  396. const prop =
  397. getVal(
  398. [
  399. 'fieldName',
  400. 'field_name',
  401. 'columnName',
  402. 'column_name',
  403. 'fieldCode',
  404. ],
  405. undefined
  406. ) || `field_${index}`
  407. const label =
  408. getVal(
  409. [
  410. 'columnComment',
  411. 'column_comment',
  412. 'fieldCname',
  413. 'field_cname',
  414. 'fieldLabel',
  415. 'field_label',
  416. ],
  417. prop
  418. ) || prop
  419. const columnType =
  420. (getVal(
  421. ['columnType', 'column_type', 'fieldType', 'field_type'],
  422. ''
  423. ) || '') + ''
  424. const columnTypeLower = columnType.toLowerCase()
  425. const explicitFieldType =
  426. (getVal(['fieldType', 'field_type'], '') || '') + ''
  427. const ftLower = explicitFieldType.toLowerCase()
  428. const totalLength = toNumber(
  429. getVal(
  430. ['fieldTypeLen', 'field_typelen', 'length', 'fieldLength'],
  431. undefined
  432. )
  433. )
  434. const decimalLength = toNumber(
  435. getVal(
  436. ['fieldTypeNointLen', 'field_typenointlen', 'scale'],
  437. undefined
  438. )
  439. )
  440. const isAuditPeriod = toBool(
  441. getVal(['isAuditPeriod', 'is_audit_period'], false)
  442. )
  443. const dictCode =
  444. getVal(
  445. [
  446. 'dictCode',
  447. 'dict_code',
  448. 'dictId',
  449. 'dictid',
  450. 'dictType',
  451. 'dict_type',
  452. ],
  453. ''
  454. ) || ''
  455. const optionsRaw = getVal(['options'], [])
  456. let options = []
  457. if (Array.isArray(optionsRaw)) {
  458. options = optionsRaw
  459. } else if (typeof optionsRaw === 'string' && optionsRaw.trim() !== '') {
  460. options = optionsRaw.split(',').map((value) => ({
  461. label: value.trim(),
  462. value: value.trim(),
  463. }))
  464. }
  465. let type = getVal(['componentType', 'type'], '')
  466. if (!type) {
  467. if (ftLower === 'boolean' || columnTypeLower.includes('boolean')) {
  468. type = 'select'
  469. // 若后端未提供字典,提供是否选项
  470. if (!options || options.length === 0) {
  471. options = [
  472. { label: '是', value: 'true' },
  473. { label: '否', value: 'false' },
  474. ]
  475. }
  476. } else if (
  477. columnTypeLower.includes('datetime') ||
  478. columnTypeLower.includes('timestamp') ||
  479. columnTypeLower.includes('date time')
  480. ) {
  481. type = 'datetime'
  482. } else if (columnTypeLower.includes('date')) {
  483. type = 'date'
  484. } else if (columnTypeLower.includes('year')) {
  485. type = 'year'
  486. } else if (ftLower === 'integer' || columnTypeLower.includes('int')) {
  487. type = 'number'
  488. } else if (
  489. ftLower === 'double' ||
  490. columnTypeLower.includes('decimal') ||
  491. columnTypeLower.includes('float') ||
  492. columnTypeLower.includes('double')
  493. ) {
  494. type = 'number'
  495. } else if (dictCode || options.length > 0) {
  496. type = 'select'
  497. } else {
  498. type = 'input'
  499. }
  500. }
  501. const required = toBool(
  502. getVal(['isRequired', 'is_required', 'required'], false)
  503. )
  504. const multiple = toBool(
  505. getVal(['isMultiple', 'is_multiple', 'multiple'], false)
  506. )
  507. const colSpan =
  508. toNumber(
  509. getVal(['colSpan', 'colspan', 'columnSpan', 'column_span'], 12)
  510. ) || 12
  511. const placeholder =
  512. getVal(
  513. ['placeholder', 'columnComment', 'column_comment'],
  514. undefined
  515. ) || (type === 'select' ? `请选择${label}` : `请输入${label}`)
  516. const defaultValue = getVal(
  517. ['defaultValue', 'default_value', 'defaultVal', 'default_val'],
  518. undefined
  519. )
  520. let precision = toNumber(
  521. getVal(
  522. ['fieldTypeNointLen', 'field_typenointlen', 'precision'],
  523. undefined
  524. )
  525. )
  526. // 根据字段类型修正精度:integer=0,double=指定小数位
  527. if (type === 'number') {
  528. if (ftLower === 'integer') precision = 0
  529. if (ftLower === 'double' && precision === undefined)
  530. precision = decimalLength !== undefined ? decimalLength : 2
  531. }
  532. const min = toNumber(getVal(['min'], undefined))
  533. const max = toNumber(getVal(['max'], undefined))
  534. const format = getVal(['format'], undefined)
  535. const valueFormat =
  536. getVal(['valueFormat', 'value_format'], undefined) ||
  537. (type === 'datetime'
  538. ? 'yyyy-MM-dd HH:mm:ss'
  539. : type === 'date'
  540. ? 'yyyy-MM-dd'
  541. : type === 'year'
  542. ? 'yyyy'
  543. : undefined)
  544. const formatLength = this.extractLengthFromFormat(format)
  545. const rules = this.buildFieldRules({
  546. type,
  547. label,
  548. required,
  549. totalLength,
  550. decimalLength,
  551. formatLength,
  552. format,
  553. isAuditPeriod,
  554. })
  555. return {
  556. prop,
  557. label,
  558. type,
  559. colSpan,
  560. placeholder,
  561. dictCode,
  562. dictType: dictCode,
  563. options,
  564. required,
  565. defaultValue,
  566. multiple,
  567. precision,
  568. min,
  569. max,
  570. format,
  571. valueFormat,
  572. totalLength,
  573. decimalLength,
  574. formatLength,
  575. showVisible: getVal(['showVisible', 'show_visible'], '1'),
  576. rules,
  577. }
  578. },
  579. extractLengthFromFormat(format) {
  580. if (!format) return undefined
  581. const str = String(format).trim()
  582. if (!str) return undefined
  583. const match = str.match(/\d+/)
  584. if (match && match[0]) {
  585. const len = Number(match[0])
  586. return Number.isNaN(len) ? undefined : len
  587. }
  588. return undefined
  589. },
  590. buildFieldRules(meta) {
  591. const {
  592. type,
  593. label,
  594. required,
  595. totalLength,
  596. decimalLength,
  597. formatLength,
  598. format,
  599. isAuditPeriod,
  600. } = meta || {}
  601. const rules = []
  602. const trigger = type === 'select' ? 'change' : 'blur'
  603. if (required) {
  604. rules.push({
  605. required: true,
  606. message: `${type === 'select' ? '请选择' : '请输入'}${label}`,
  607. trigger,
  608. })
  609. }
  610. const inputMaxLength = formatLength || totalLength
  611. if (type === 'input' && inputMaxLength) {
  612. rules.push({
  613. validator: (_, value, callback) => {
  614. if (value === undefined || value === null || value === '') {
  615. callback()
  616. return
  617. }
  618. const str = String(value)
  619. if (str.length > inputMaxLength) {
  620. callback(
  621. new Error(`${label}长度不能超过${inputMaxLength}个字符`)
  622. )
  623. } else {
  624. callback()
  625. }
  626. },
  627. trigger: 'blur',
  628. })
  629. }
  630. const numberTotal = totalLength || formatLength
  631. if (type === 'number') {
  632. rules.push({
  633. validator: (_, value, callback) => {
  634. if (value === undefined || value === null || value === '') {
  635. callback()
  636. return
  637. }
  638. if (Number.isNaN(Number(value))) {
  639. callback(new Error(`${label}必须为数字`))
  640. return
  641. }
  642. const pure = String(value).replace('-', '')
  643. if (numberTotal && pure.replace('.', '').length > numberTotal) {
  644. callback(new Error(`${label}总位数不能超过${numberTotal}`))
  645. return
  646. }
  647. if (decimalLength !== undefined && decimalLength !== null) {
  648. const decimals = pure.split('.')[1] || ''
  649. if (decimals.length > decimalLength) {
  650. callback(
  651. new Error(`${label}小数位不能超过${decimalLength}位`)
  652. )
  653. return
  654. }
  655. }
  656. callback()
  657. },
  658. trigger: 'blur',
  659. })
  660. }
  661. if (type === 'datetime' || type === 'date') {
  662. if (format) {
  663. rules.push({
  664. validator: (_, value, callback) => {
  665. if (value === undefined || value === null || value === '') {
  666. callback()
  667. return
  668. }
  669. callback()
  670. },
  671. trigger: 'change',
  672. })
  673. }
  674. }
  675. if (type === 'year' || isAuditPeriod) {
  676. rules.push({
  677. validator: (_, value, callback) => {
  678. if (value === undefined || value === null || value === '') {
  679. callback()
  680. return
  681. }
  682. const pattern = /^\d{4}$/
  683. if (!pattern.test(String(value))) {
  684. callback(new Error(`${label}必须是四位年份`))
  685. } else {
  686. callback()
  687. }
  688. },
  689. trigger: 'change',
  690. })
  691. }
  692. return rules
  693. },
  694. // 初始化字典数据
  695. initDictData() {
  696. if (this.dictTypes.length > 0) {
  697. // 初始化字典数据对象
  698. this.dictTypes.forEach((type) => {
  699. if (!this.dictData[type]) {
  700. this.$set(this.dictData, type, [])
  701. }
  702. })
  703. // 调用父级 mixin 的方法获取字典数据
  704. if (this.dictTypes.length > 0) {
  705. this.getDictType()
  706. }
  707. }
  708. },
  709. // 获取字典选项
  710. getDictOptions(dictType) {
  711. if (!dictType || !this.dictData || !this.dictData[dictType]) {
  712. return []
  713. }
  714. return this.dictData[dictType] || []
  715. },
  716. initForm() {
  717. const fields = this.effectiveFormFields
  718. if (!fields || fields.length === 0) {
  719. return
  720. }
  721. const form = {}
  722. const rules = {}
  723. fields.forEach((field) => {
  724. if (!field || !field.prop) {
  725. return
  726. }
  727. // 初始化表单值
  728. if (
  729. this.surveyData &&
  730. this.surveyData[field.prop] !== undefined &&
  731. this.surveyData[field.prop] !== null &&
  732. this.surveyData[field.prop] !== ''
  733. ) {
  734. form[field.prop] = this.surveyData[field.prop]
  735. } else if (field.defaultValue !== undefined) {
  736. form[field.prop] = field.defaultValue
  737. } else {
  738. form[field.prop] = field.multiple ? [] : ''
  739. }
  740. // 初始化验证规则 - 这是关键!必须正确设置 rules
  741. // 优先使用字段配置中的 rules(从 mapApiFieldToFormField 返回的完整规则)
  742. if (
  743. field.rules &&
  744. Array.isArray(field.rules) &&
  745. field.rules.length > 0
  746. ) {
  747. // 使用完整的规则数组
  748. rules[field.prop] = [...field.rules]
  749. } else if (field.required) {
  750. // 如果字段是必填的但没有 rules,添加必填验证
  751. const message =
  752. field.type === 'select'
  753. ? `请选择${field.label}`
  754. : field.type === 'date' ||
  755. field.type === 'datetime' ||
  756. field.type === 'year'
  757. ? `请选择${field.label}`
  758. : `请输入${field.label}`
  759. rules[field.prop] = [
  760. {
  761. required: true,
  762. message: message,
  763. trigger:
  764. field.type === 'select' ||
  765. field.type === 'date' ||
  766. field.type === 'datetime' ||
  767. field.type === 'year'
  768. ? 'change'
  769. : 'blur',
  770. },
  771. ]
  772. }
  773. })
  774. // 使用 Vue.set 确保响应式更新
  775. this.$set(this, 'form', form)
  776. this.$set(this, 'rules', rules)
  777. // 初始化字典数据
  778. this.initDictData()
  779. // 确保表单组件能识别新的规则
  780. this.$nextTick(() => {
  781. if (this.$refs.surveyForm) {
  782. this.$refs.surveyForm.clearValidate()
  783. }
  784. })
  785. },
  786. // 获取默认表单字段配置(兼容旧版本)
  787. getDefaultFormFields() {
  788. return [
  789. // {
  790. // prop: 'institutionName',
  791. // label: '机构名称',
  792. // type: 'input',
  793. // colSpan: 12,
  794. // defaultValue: '幼儿园基本情况',
  795. // required: true,
  796. // },
  797. // {
  798. // prop: 'institutionNature',
  799. // label: '机构性质',
  800. // type: 'select',
  801. // colSpan: 12,
  802. // dictType: 'institutionNature', // 字典类型
  803. // defaultValue: '公办',
  804. // required: true,
  805. // },
  806. // {
  807. // prop: 'institutionLevel',
  808. // label: '机构评定等级',
  809. // type: 'select',
  810. // colSpan: 12,
  811. // dictType: 'institutionLevel', // 字典类型
  812. // defaultValue: '省一级',
  813. // required: true,
  814. // },
  815. // {
  816. // prop: 'educationMode',
  817. // label: '机构办学方式',
  818. // type: 'select',
  819. // colSpan: 12,
  820. // dictType: 'educationMode', // 字典类型
  821. // defaultValue: '全日制',
  822. // required: true,
  823. // },
  824. // {
  825. // prop: 'institutionAddress',
  826. // label: '机构地址',
  827. // type: 'input',
  828. // colSpan: 12,
  829. // required: true,
  830. // },
  831. // {
  832. // prop: 'formFiller',
  833. // label: '机构填表人',
  834. // type: 'input',
  835. // colSpan: 12,
  836. // required: true,
  837. // },
  838. // {
  839. // prop: 'financialManager',
  840. // label: '机构财务负责人',
  841. // type: 'input',
  842. // colSpan: 12,
  843. // required: true,
  844. // },
  845. // {
  846. // prop: 'contactPhone',
  847. // label: '机构联系电话',
  848. // type: 'input',
  849. // colSpan: 12,
  850. // required: true,
  851. // rules: [
  852. // {
  853. // required: true,
  854. // message: '请输入机构联系电话',
  855. // trigger: 'blur',
  856. // },
  857. // {
  858. // pattern: /^1[3-9]\d{9}$/,
  859. // message: '请输入正确的手机号码',
  860. // trigger: 'blur',
  861. // },
  862. // ],
  863. // },
  864. // {
  865. // prop: 'formFillDate',
  866. // label: '机构填表日期',
  867. // type: 'date',
  868. // colSpan: 12,
  869. // required: true,
  870. // },
  871. ]
  872. },
  873. handleClose() {
  874. this.dialogVisible = false
  875. this.$emit('update:visible', false)
  876. },
  877. handleCancel() {
  878. this.handleClose()
  879. },
  880. async handleSave() {
  881. this.$refs.surveyForm.validate(async (valid) => {
  882. if (valid) {
  883. try {
  884. // 判断是否有数据(编辑模式):如果有 uploadId,说明是编辑已有数据
  885. const hasData = !!(
  886. this.uploadId ||
  887. this.surveyData.uploadId ||
  888. this.surveyData.id
  889. )
  890. // 将表单数据转换为接口需要的格式
  891. const saveData = this.effectiveFormFields.map((field) => {
  892. const dataItem = {
  893. auditedUnitId:
  894. this.auditedUnitId || this.surveyData.auditedUnitId || '',
  895. surveyTemplateId:
  896. this.surveyTemplateId ||
  897. this.surveyData.surveyTemplateId ||
  898. '',
  899. catalogId: this.catalogId || this.surveyData.catalogId || '',
  900. taskId: this.taskId || this.surveyData.taskId || '',
  901. rowid: field.prop, // 字段ID(对应 fixedFieldids)
  902. rkey: field.label, // 字段名称(对应 fixedFields,即 label)
  903. rvalue: this.form[field.prop] || '', // 字段值(表单输入的值)
  904. type: this.requestType,
  905. }
  906. // 如果有数据(编辑模式),添加 uploadId 字段
  907. if (hasData) {
  908. dataItem.uploadId =
  909. this.uploadId ||
  910. this.surveyData.uploadId ||
  911. this.surveyData.id ||
  912. ''
  913. }
  914. return dataItem
  915. })
  916. console.log('保存表单数据:', saveData)
  917. // 调用保存接口
  918. const res = await saveSingleRecordSurvey(saveData)
  919. if (res && res.code === 200) {
  920. Message.success('保存成功')
  921. // 触发保存事件,将数据传递给父组件
  922. this.$emit('save', { ...this.form })
  923. // 触发刷新事件,通知父组件刷新列表
  924. this.$emit('refresh')
  925. this.handleClose()
  926. } else {
  927. Message.error(res.message || '保存失败')
  928. }
  929. } catch (err) {
  930. console.error('保存失败', err)
  931. // Message.error(err.message || '保存失败')
  932. }
  933. } else {
  934. Message.error('请完善表单信息')
  935. return false
  936. }
  937. })
  938. },
  939. },
  940. }
  941. </script>
  942. <style scoped lang="scss">
  943. .dialog-footer {
  944. text-align: center;
  945. .el-button {
  946. margin: 0 10px;
  947. }
  948. }
  949. ::v-deep .el-dialog__header {
  950. padding: 20px 20px 10px;
  951. .el-dialog__title {
  952. font-size: 18px;
  953. font-weight: 600;
  954. color: #303133;
  955. }
  956. }
  957. ::v-deep .el-form-item {
  958. margin-bottom: 20px;
  959. .el-form-item__label {
  960. // color: #409eff;
  961. font-weight: 500;
  962. }
  963. }
  964. </style>