CostSurveyTab.vue 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269
  1. <template>
  2. <div>
  3. <!-- 调查表填报弹窗(单记录类型) -->
  4. <survey-form-dialog
  5. :visible.sync="surveyFormDialogVisible"
  6. :survey-data="{ ...currentSurveyRow, ...surveyDetailData }"
  7. :form-fields="formFields"
  8. :is-view-mode="isViewMode"
  9. :audited-unit-id="auditedUnitId"
  10. :upload-id="
  11. currentSurveyRow && currentSurveyRow.id ? currentSurveyRow.id : uploadId
  12. "
  13. :survey-template-id="
  14. currentSurveyRow && currentSurveyRow.surveyTemplateId
  15. ? currentSurveyRow.surveyTemplateId
  16. : surveyTemplateId
  17. "
  18. :catalog-id="catalogId"
  19. @save="handleSurveyFormSave"
  20. @refresh="handleRefresh"
  21. />
  22. <!-- 固定表填报弹窗 -->
  23. <fixed-table-dialog
  24. :visible.sync="fixedTableDialogVisible"
  25. :survey-data="{ ...currentSurveyRow, fixedHeaders }"
  26. :table-items="tableItems"
  27. :audit-periods="auditPeriods"
  28. :is-view-mode="isViewMode"
  29. :audited-unit-id="auditedUnitId"
  30. :upload-id="
  31. currentSurveyRow && currentSurveyRow.id ? currentSurveyRow.id : uploadId
  32. "
  33. :survey-template-id="
  34. currentSurveyRow && currentSurveyRow.surveyTemplateId
  35. ? currentSurveyRow.surveyTemplateId
  36. : surveyTemplateId
  37. "
  38. :catalog-id="catalogId"
  39. @save="handleFixedTableSave"
  40. @refresh="handleRefresh"
  41. />
  42. <!-- 动态表填报弹窗 -->
  43. <dynamic-table-dialog
  44. :key="dynamicDialogKey"
  45. :visible.sync="dynamicTableDialogVisible"
  46. :survey-data="currentSurveyRow"
  47. :table-data="dynamicTableData"
  48. :table-items="tableItems"
  49. :is-view-mode="isViewMode"
  50. :audited-unit-id="auditedUnitId"
  51. :upload-id="
  52. currentSurveyRow && (currentSurveyRow.uploadId || currentSurveyRow.id)
  53. ? currentSurveyRow.uploadId || currentSurveyRow.id
  54. : uploadId
  55. "
  56. :catalog-id="catalogId"
  57. :survey-template-id="
  58. currentSurveyRow && currentSurveyRow.surveyTemplateId
  59. ? currentSurveyRow.surveyTemplateId
  60. : surveyTemplateId
  61. "
  62. @save="handleDynamicTableSave"
  63. @refresh="handleRefresh"
  64. />
  65. <el-table
  66. style="width: 100%; margin-top: 20px"
  67. :data="paginatedData"
  68. border
  69. size="medium"
  70. >
  71. <!-- 序号列 -->
  72. <el-table-column prop="index" label="序号" width="60" align="center">
  73. <template slot-scope="scope">
  74. {{ scope.$index + 1 }}
  75. </template>
  76. </el-table-column>
  77. <el-table-column label="成本调查表" min-width="220">
  78. <template slot-scope="scope">
  79. <span
  80. :style="{
  81. color: scope.row.isDisabled ? '#909399' : '#409EFF',
  82. cursor: scope.row.isDisabled ? 'default' : 'pointer',
  83. }"
  84. @click="
  85. !scope.row.isDisabled && $emit('handle-survey-click', scope.row)
  86. "
  87. >
  88. {{ scope.row.name }}
  89. </span>
  90. </template>
  91. </el-table-column>
  92. <!-- 资料类型列 -->
  93. <el-table-column
  94. prop="dataType"
  95. label="资料类型"
  96. width="120"
  97. align="center"
  98. ></el-table-column>
  99. <!-- 表格类型列 -->
  100. <el-table-column
  101. prop="tableType"
  102. label="表格类型"
  103. width="120"
  104. align="center"
  105. ></el-table-column>
  106. <!-- 是否必填列 -->
  107. <el-table-column
  108. prop="isRequired"
  109. label="是否必填"
  110. width="100"
  111. align="center"
  112. >
  113. <template slot-scope="scope">
  114. <span>
  115. {{
  116. scope.row.isRequired === '是' ||
  117. scope.row.isRequired === '1' ||
  118. scope.row.isRequired === 1
  119. ? '是'
  120. : '否'
  121. }}
  122. </span>
  123. </template>
  124. </el-table-column>
  125. <!-- 是否上传列(红色“未上传”、绿色“已上传”) -->
  126. <el-table-column label="是否上传" width="100" align="center">
  127. <template slot-scope="scope">
  128. <span
  129. :style="{
  130. color: scope.row.isUploaded === true ? '#67c23a' : '#f56c6c',
  131. }"
  132. >
  133. {{ scope.row.isUploaded === true ? '已上传' : '未上传' }}
  134. </span>
  135. </template>
  136. </el-table-column>
  137. <!-- 操作列(根据“是否上传”“表格类型”显示不同按钮) -->
  138. <el-table-column label="操作" width="280" align="center">
  139. <template slot-scope="scope">
  140. <template v-if="scope.row.isUploaded">
  141. <el-button
  142. type="text"
  143. size="small"
  144. :disabled="isViewMode"
  145. @click="handleOnlineFillClick(scope.row)"
  146. >
  147. 在线填报
  148. </el-button>
  149. <el-button
  150. type="text"
  151. size="small"
  152. :disabled="isViewMode"
  153. @click="$emit('handle-modify', scope.row)"
  154. >
  155. 修改
  156. </el-button>
  157. <el-button
  158. type="text"
  159. size="small"
  160. :disabled="isViewMode"
  161. @click="$emit('handle-data-download', scope.row)"
  162. >
  163. 数据下载
  164. </el-button>
  165. <el-button
  166. type="text"
  167. size="small"
  168. :disabled="isViewMode"
  169. @click="$emit('handle-data-upload', scope.row)"
  170. >
  171. 数据上传
  172. </el-button>
  173. </template>
  174. <template v-else>
  175. <el-button
  176. type="text"
  177. size="small"
  178. :disabled="isViewMode"
  179. @click="handleOnlineFillClick(scope.row)"
  180. >
  181. 在线填报
  182. </el-button>
  183. <el-button
  184. v-if="scope.row.tableType === '动态表'"
  185. type="text"
  186. size="small"
  187. :disabled="isViewMode"
  188. @click="$emit('handle-template-download', scope.row)"
  189. >
  190. 模版下载
  191. </el-button>
  192. <el-button
  193. type="text"
  194. size="small"
  195. :disabled="isViewMode"
  196. @click="$emit('handle-data-upload', scope.row)"
  197. >
  198. 数据上传
  199. </el-button>
  200. </template>
  201. </template>
  202. </el-table-column>
  203. </el-table>
  204. <el-pagination
  205. background
  206. layout="total, sizes, prev, pager, next"
  207. :current-page="pagination.currentPage"
  208. :page-sizes="[10, 20, 30, 50]"
  209. :page-size="pagination.pageSize"
  210. :total="pagination.total"
  211. style="margin-top: 20px; text-align: right"
  212. @current-change="$emit('handle-page-change', $event)"
  213. @size-change="$emit('handle-size-change', $event)"
  214. />
  215. </div>
  216. </template>
  217. <script>
  218. import SurveyFormDialog from './SurveyFormDialog.vue'
  219. import FixedTableDialog from './FixedTableDialog.vue'
  220. import DynamicTableDialog from './DynamicTableDialog.vue'
  221. import {
  222. getSingleRecordSurveyList,
  223. getSurveyDetail,
  224. getDynamicTableData,
  225. } from '@/api/audit/survey'
  226. import { getListBySurveyTemplateIdAndVersion } from '@/api/costSurveyTemplateHeaders'
  227. export default {
  228. name: 'CostSurveyTab',
  229. components: {
  230. SurveyFormDialog,
  231. FixedTableDialog,
  232. DynamicTableDialog,
  233. },
  234. props: {
  235. paginatedData: {
  236. type: Array,
  237. default: () => [],
  238. },
  239. pagination: {
  240. type: Object,
  241. default: () => ({ currentPage: 1, pageSize: 10, total: 0 }),
  242. },
  243. isViewMode: {
  244. type: Boolean,
  245. default: false,
  246. },
  247. // 被监审单位ID
  248. auditedUnitId: {
  249. type: String,
  250. default: '',
  251. },
  252. // 上传记录ID
  253. uploadId: {
  254. type: String,
  255. default: '',
  256. },
  257. // 成本调查表模板ID
  258. surveyTemplateId: {
  259. type: String,
  260. default: '',
  261. },
  262. // 目录ID
  263. catalogId: {
  264. type: String,
  265. default: '',
  266. },
  267. // 监审期间(从立项信息中获取)
  268. auditPeriod: {
  269. type: [String, Array],
  270. default: null,
  271. },
  272. },
  273. data() {
  274. return {
  275. surveyFormDialogVisible: false,
  276. fixedTableDialogVisible: false,
  277. dynamicTableDialogVisible: false,
  278. currentSurveyRow: null,
  279. // 表单字段配置(可以从后台获取,或通过 props 传入)
  280. formFields: [],
  281. // 表单详情数据(用于回显)
  282. surveyDetailData: {},
  283. // 固定表数据配置
  284. tableItems: [],
  285. // 监审期间(年份数组)
  286. auditPeriods: [],
  287. // 动态表数据
  288. dynamicTableData: [],
  289. dynamicDialogKey: 0,
  290. dynamicTableLoading: false,
  291. fixedHeaders: null,
  292. }
  293. },
  294. mounted() {
  295. // 表单字段配置在打开弹窗时动态加载,不需要在 mounted 中初始化
  296. },
  297. methods: {
  298. // 处理在线填报点击
  299. async handleOnlineFillClick(row) {
  300. this.currentSurveyRow = row
  301. // 重置详情数据
  302. this.surveyDetailData = {}
  303. // 如果表格类型是"单记录",弹出调查表填报弹窗
  304. if (row.tableType === '单记录') {
  305. // 如果该行有 id,先调用接口获取详情数据
  306. if (row.id && this.auditedUnitId) {
  307. try {
  308. const params = {
  309. uploadId: row.id,
  310. auditedUnitId: this.auditedUnitId,
  311. }
  312. const res = await getSurveyDetail(params)
  313. console.log('单记录详情数据', res)
  314. if (res && res.code === 200 && res.value) {
  315. // 将详情数据转换为表单数据格式
  316. // 接口返回的数据可能是数组格式 [{rowid: 'xxx', rkey: 'xxx', rvalue: 'xxx'}, ...]
  317. // 需要转换为 {rowid: 'rvalue', ...} 的格式
  318. const detailData = {}
  319. if (Array.isArray(res.value)) {
  320. res.value.forEach((item) => {
  321. if (item.rowid && item.rvalue !== undefined) {
  322. detailData[item.rowid] = item.rvalue
  323. }
  324. })
  325. } else if (res.value) {
  326. // 如果不是数组,可能是对象格式,直接使用
  327. Object.assign(detailData, res.value)
  328. }
  329. this.surveyDetailData = detailData
  330. console.log('转换后的详情数据', this.surveyDetailData)
  331. }
  332. } catch (err) {
  333. console.error('获取单记录详情失败', err)
  334. }
  335. }
  336. // 调用接口获取单记录表单字段配置(详情数据已准备好)
  337. await this.initFormFields()
  338. // 接口调用完成后会自动打开弹窗(在 initFormFields 中处理)
  339. } else if (row.tableType === '固定表') {
  340. // 如果表格类型是"固定表",弹出固定表填报弹窗
  341. // 调用接口获取固定表配置
  342. await this.initFixedTableData()
  343. // 接口调用完成后会自动打开弹窗(在 initFixedTableData 中处理)
  344. } else if (row.tableType === '动态表') {
  345. // 如果表格类型是"动态表",弹出动态表填报弹窗
  346. this.resetDynamicDialogState()
  347. await this.initDynamicTableData()
  348. } else {
  349. // 其他类型,触发原有事件
  350. this.$emit('handle-online-fill', row)
  351. }
  352. },
  353. // 处理调查表保存
  354. handleSurveyFormSave(formData) {
  355. // 可以将保存的数据传递给父组件
  356. this.$emit('handle-survey-form-save', {
  357. row: this.currentSurveyRow,
  358. formData: formData,
  359. })
  360. },
  361. // 处理刷新事件
  362. handleRefresh() {
  363. // 触发父组件的刷新事件
  364. this.$emit('handle-survey-form-save', {
  365. row: this.currentSurveyRow,
  366. formData: {},
  367. })
  368. },
  369. // 处理固定表保存
  370. handleFixedTableSave(tableData) {
  371. // 可以将保存的数据传递给父组件
  372. this.$emit('handle-fixed-table-save', {
  373. row: this.currentSurveyRow,
  374. tableData: tableData,
  375. })
  376. },
  377. // 处理动态表保存
  378. handleDynamicTableSave(tableData) {
  379. // 将保存的数据传递给父组件
  380. this.$emit('handle-dynamic-table-save', {
  381. row: this.currentSurveyRow,
  382. tableData: tableData,
  383. })
  384. // 触发刷新事件
  385. this.handleRefresh()
  386. },
  387. // 初始化动态表数据
  388. async initDynamicTableData() {
  389. try {
  390. this.dynamicTableLoading = true
  391. const uploadId =
  392. (this.currentSurveyRow &&
  393. (this.currentSurveyRow.uploadId || this.currentSurveyRow.id)) ||
  394. this.uploadId ||
  395. ''
  396. const auditedUnitId =
  397. this.auditedUnitId ||
  398. (this.currentSurveyRow && this.currentSurveyRow.auditedUnitId) ||
  399. ''
  400. const catalogId =
  401. (this.currentSurveyRow && this.currentSurveyRow.catalogId) ||
  402. this.catalogId ||
  403. ''
  404. const surveyTemplateId =
  405. (this.currentSurveyRow && this.currentSurveyRow.surveyTemplateId) ||
  406. this.surveyTemplateId ||
  407. ''
  408. const params = {
  409. uploadId,
  410. auditedUnitId,
  411. catalogId,
  412. surveyTemplateId,
  413. }
  414. const res = await getDynamicTableData(params)
  415. if (res && res.code === 200) {
  416. const records = res.value?.records || res.value || []
  417. this.dynamicTableData = Array.isArray(records) ? records : []
  418. } else {
  419. // 接口调用失败,使用当前行的动态表数据或空数组
  420. this.dynamicTableData =
  421. this.currentSurveyRow?.dynamicTableData || []
  422. }
  423. // 初始化表格项配置(用于详情/编辑时显示表单)
  424. if (
  425. this.currentSurveyRow &&
  426. this.currentSurveyRow.tableItems &&
  427. this.currentSurveyRow.tableItems.length > 0
  428. ) {
  429. this.tableItems = this.currentSurveyRow.tableItems
  430. } else {
  431. this.tableItems = this.getMockTableItems()
  432. }
  433. this.dynamicTableDialogVisible = true
  434. } catch (error) {
  435. console.error('获取动态表数据失败', error)
  436. // 出错时使用当前行的动态表数据或空数组
  437. this.dynamicTableData = this.currentSurveyRow?.dynamicTableData || []
  438. this.tableItems =
  439. this.currentSurveyRow?.tableItems || this.getMockTableItems()
  440. // 出错时也打开弹窗
  441. this.dynamicTableDialogVisible = true
  442. } finally {
  443. this.dynamicTableLoading = false
  444. }
  445. },
  446. // 初始化表单字段配置
  447. async initFormFields() {
  448. // 如果是单记录类型,调用接口获取表单字段配置(包含校验规则)
  449. if (
  450. this.currentSurveyRow &&
  451. this.currentSurveyRow.tableType === '单记录' &&
  452. this.currentSurveyRow.surveyTemplateId
  453. ) {
  454. try {
  455. const params = {
  456. surveyTemplateId: this.currentSurveyRow.surveyTemplateId,
  457. }
  458. // 调用 getListBySurveyTemplateIdAndVersion 获取完整的字段配置(包含校验规则)
  459. const res = await getListBySurveyTemplateIdAndVersion(params)
  460. console.log('单记录表单字段配置(含校验规则)', res)
  461. if (res && res.code === 200) {
  462. let mapped = []
  463. if (Array.isArray(res.value)) {
  464. // 数组格式:直接映射每个字段(包含完整的校验规则)
  465. mapped = res.value
  466. .map((item, index) =>
  467. this.mapApiFieldToFormField(item, index)
  468. )
  469. .filter(Boolean)
  470. } else if (res.value && typeof res.value === 'object') {
  471. // 对象格式:从 fixedFields 和 fixedFieldids 解析(兼容旧格式)
  472. const { fixedFields, fixedFieldids } = res.value
  473. if (fixedFields && fixedFieldids) {
  474. const labels = fixedFields
  475. .split(',')
  476. .map((item) => item.trim())
  477. const ids = fixedFieldids
  478. .split(',')
  479. .map((item) => item.trim())
  480. mapped = labels.map((label, index) => ({
  481. prop: ids[index] || `field_${index}`,
  482. label: label,
  483. type: 'input',
  484. colSpan: 12,
  485. placeholder: `请输入${label}`,
  486. rules: [],
  487. defaultValue: '',
  488. disabled: false,
  489. clearable: true,
  490. multiple: false,
  491. required: false,
  492. }))
  493. }
  494. }
  495. if (mapped.length > 0) {
  496. // 使用包含完整校验规则的字段配置
  497. this.formFields = mapped
  498. console.log(
  499. '转换后的表单字段配置(含校验规则)',
  500. this.formFields
  501. )
  502. } else {
  503. // 如果没有数据,使用默认配置
  504. this.formFields = this.getMockFormFields()
  505. }
  506. } else {
  507. // 接口返回失败,使用默认配置
  508. this.formFields = this.getMockFormFields()
  509. }
  510. // 打开弹窗
  511. this.surveyFormDialogVisible = true
  512. } catch (err) {
  513. console.error('获取单记录表单字段配置失败', err)
  514. // 出错时使用默认配置
  515. this.formFields = this.getMockFormFields()
  516. this.surveyFormDialogVisible = true
  517. }
  518. } else if (this.currentSurveyRow && this.currentSurveyRow.formFields) {
  519. // 如果当前行有表单配置,则使用
  520. this.formFields = this.currentSurveyRow.formFields
  521. this.surveyFormDialogVisible = true
  522. } else {
  523. // 使用假数据作为测试(实际开发中应该从后台获取)
  524. this.formFields = this.getMockFormFields()
  525. this.surveyFormDialogVisible = true
  526. }
  527. },
  528. // 将 API 返回的字段数据映射为表单字段配置(包含校验规则)
  529. mapApiFieldToFormField(item, index = 0) {
  530. if (!item) return null
  531. const getVal = (keys, fallback) => {
  532. for (const key of keys) {
  533. if (
  534. key &&
  535. item[key] !== undefined &&
  536. item[key] !== null &&
  537. item[key] !== ''
  538. ) {
  539. return item[key]
  540. }
  541. }
  542. return fallback
  543. }
  544. const toBool = (value) => {
  545. if (value === undefined || value === null) return false
  546. if (typeof value === 'boolean') return value
  547. if (typeof value === 'number') return value === 1
  548. const str = String(value).trim().toLowerCase()
  549. return ['1', 'true', 'y', 'yes', '是'].includes(str)
  550. }
  551. const toNumber = (value) => {
  552. if (value === undefined || value === null || value === '')
  553. return undefined
  554. const num = Number(value)
  555. return Number.isNaN(num) ? undefined : num
  556. }
  557. const prop =
  558. getVal(
  559. [
  560. 'fieldName',
  561. 'field_name',
  562. 'columnName',
  563. 'column_name',
  564. 'fieldCode',
  565. ],
  566. undefined
  567. ) || `field_${index}`
  568. const label =
  569. getVal(
  570. [
  571. 'columnComment',
  572. 'column_comment',
  573. 'fieldCname',
  574. 'field_cname',
  575. 'fieldLabel',
  576. 'field_label',
  577. ],
  578. prop
  579. ) || prop
  580. const columnType =
  581. (getVal(
  582. ['columnType', 'column_type', 'fieldType', 'field_type'],
  583. ''
  584. ) || '') + ''
  585. const columnTypeLower = columnType.toLowerCase()
  586. const totalLength = toNumber(
  587. getVal(
  588. ['fieldTypeLen', 'field_typelen', 'length', 'fieldLength'],
  589. undefined
  590. )
  591. )
  592. const decimalLength = toNumber(
  593. getVal(
  594. ['fieldTypeNointLen', 'field_typenointlen', 'scale'],
  595. undefined
  596. )
  597. )
  598. const isAuditPeriod = toBool(
  599. getVal(['isAuditPeriod', 'is_audit_period'], false)
  600. )
  601. const dictCode =
  602. getVal(
  603. [
  604. 'dictCode',
  605. 'dict_code',
  606. 'dictId',
  607. 'dictid',
  608. 'dictType',
  609. 'dict_type',
  610. ],
  611. ''
  612. ) || ''
  613. const optionsRaw = getVal(['options'], [])
  614. let options = []
  615. if (Array.isArray(optionsRaw)) {
  616. options = optionsRaw
  617. } else if (typeof optionsRaw === 'string' && optionsRaw.trim() !== '') {
  618. options = optionsRaw.split(',').map((value) => ({
  619. label: value.trim(),
  620. value: value.trim(),
  621. }))
  622. }
  623. let type = getVal(['componentType', 'type'], '')
  624. if (!type) {
  625. if (dictCode || options.length > 0) {
  626. type = 'select'
  627. } else if (
  628. columnTypeLower.includes('datetime') ||
  629. columnTypeLower.includes('timestamp') ||
  630. columnTypeLower.includes('date time')
  631. ) {
  632. type = 'datetime'
  633. } else if (columnTypeLower.includes('date')) {
  634. type = 'date'
  635. } else if (columnTypeLower.includes('year')) {
  636. type = 'year'
  637. } else if (
  638. columnTypeLower.includes('int') ||
  639. columnTypeLower.includes('number') ||
  640. columnTypeLower.includes('decimal') ||
  641. columnTypeLower.includes('float') ||
  642. columnTypeLower.includes('double')
  643. ) {
  644. type = 'number'
  645. } else {
  646. type = 'input'
  647. }
  648. }
  649. const required = toBool(
  650. getVal(['isRequired', 'is_required', 'required'], false)
  651. )
  652. const multiple = toBool(
  653. getVal(['isMultiple', 'is_multiple', 'multiple'], false)
  654. )
  655. const colSpan =
  656. toNumber(
  657. getVal(['colSpan', 'colspan', 'columnSpan', 'column_span'], 12)
  658. ) || 12
  659. const placeholder =
  660. getVal(
  661. ['placeholder', 'columnComment', 'column_comment'],
  662. undefined
  663. ) || (type === 'select' ? `请选择${label}` : `请输入${label}`)
  664. const defaultValue = getVal(
  665. ['defaultValue', 'default_value', 'defaultVal', 'default_val'],
  666. undefined
  667. )
  668. const precision = toNumber(
  669. getVal(
  670. ['fieldTypeNointLen', 'field_typenointlen', 'precision'],
  671. undefined
  672. )
  673. )
  674. const min = toNumber(getVal(['min'], undefined))
  675. const max = toNumber(getVal(['max'], undefined))
  676. const format = getVal(['format'], undefined)
  677. const valueFormat =
  678. getVal(['valueFormat', 'value_format'], undefined) ||
  679. (type === 'datetime'
  680. ? 'yyyy-MM-dd HH:mm:ss'
  681. : type === 'date'
  682. ? 'yyyy-MM-dd'
  683. : type === 'year'
  684. ? 'yyyy'
  685. : undefined)
  686. const formatLength = this.extractLengthFromFormat(format)
  687. const rules = this.buildFieldRules({
  688. type,
  689. label,
  690. required,
  691. totalLength,
  692. decimalLength,
  693. formatLength,
  694. format,
  695. isAuditPeriod,
  696. })
  697. return {
  698. prop,
  699. label,
  700. type,
  701. colSpan,
  702. placeholder,
  703. dictCode,
  704. dictType: dictCode,
  705. options,
  706. required,
  707. defaultValue,
  708. multiple,
  709. precision,
  710. min,
  711. max,
  712. format,
  713. valueFormat,
  714. totalLength,
  715. decimalLength,
  716. formatLength,
  717. rules, // 包含完整的校验规则
  718. }
  719. },
  720. // 从 format 中提取长度
  721. extractLengthFromFormat(format) {
  722. if (!format) return undefined
  723. const str = String(format).trim()
  724. if (!str) return undefined
  725. const match = str.match(/\d+/)
  726. if (match && match[0]) {
  727. const len = Number(match[0])
  728. return Number.isNaN(len) ? undefined : len
  729. }
  730. return undefined
  731. },
  732. // 构建字段校验规则
  733. buildFieldRules(meta) {
  734. const {
  735. type,
  736. label,
  737. required,
  738. totalLength,
  739. decimalLength,
  740. formatLength,
  741. format,
  742. isAuditPeriod,
  743. } = meta || {}
  744. const rules = []
  745. const trigger = type === 'select' ? 'change' : 'blur'
  746. if (required) {
  747. rules.push({
  748. required: true,
  749. message: `${type === 'select' ? '请选择' : '请输入'}${label}`,
  750. trigger,
  751. })
  752. }
  753. const inputMaxLength = formatLength || totalLength
  754. if (type === 'input' && inputMaxLength) {
  755. rules.push({
  756. validator: (_, value, callback) => {
  757. if (value === undefined || value === null || value === '') {
  758. callback()
  759. return
  760. }
  761. const str = String(value)
  762. if (str.length > inputMaxLength) {
  763. callback(
  764. new Error(`${label}长度不能超过${inputMaxLength}个字符`)
  765. )
  766. } else {
  767. callback()
  768. }
  769. },
  770. trigger: 'blur',
  771. })
  772. }
  773. const numberTotal = totalLength || formatLength
  774. if (type === 'number') {
  775. rules.push({
  776. validator: (_, value, callback) => {
  777. if (value === undefined || value === null || value === '') {
  778. callback()
  779. return
  780. }
  781. if (Number.isNaN(Number(value))) {
  782. callback(new Error(`${label}必须为数字`))
  783. return
  784. }
  785. const pure = String(value).replace('-', '')
  786. if (numberTotal && pure.replace('.', '').length > numberTotal) {
  787. callback(new Error(`${label}总位数不能超过${numberTotal}`))
  788. return
  789. }
  790. if (decimalLength !== undefined && decimalLength !== null) {
  791. const decimals = pure.split('.')[1] || ''
  792. if (decimals.length > decimalLength) {
  793. callback(
  794. new Error(`${label}小数位不能超过${decimalLength}位`)
  795. )
  796. return
  797. }
  798. }
  799. callback()
  800. },
  801. trigger: 'blur',
  802. })
  803. }
  804. if (type === 'datetime' || type === 'date') {
  805. if (format) {
  806. rules.push({
  807. validator: (_, value, callback) => {
  808. if (value === undefined || value === null || value === '') {
  809. callback()
  810. return
  811. }
  812. callback()
  813. },
  814. trigger: 'change',
  815. })
  816. }
  817. }
  818. if (type === 'year' || isAuditPeriod) {
  819. rules.push({
  820. validator: (_, value, callback) => {
  821. if (value === undefined || value === null || value === '') {
  822. callback()
  823. return
  824. }
  825. const pattern = /^\d{4}$/
  826. if (!pattern.test(String(value))) {
  827. callback(new Error(`${label}必须是四位年份`))
  828. } else {
  829. callback()
  830. }
  831. },
  832. trigger: 'change',
  833. })
  834. }
  835. return rules
  836. },
  837. // 获取假数据表单字段配置(用于测试)
  838. getMockFormFields() {
  839. return [
  840. {
  841. prop: 'institutionName',
  842. label: '机构名称',
  843. type: 'input',
  844. colSpan: 12,
  845. defaultValue: '幼儿园基本情况',
  846. placeholder: '请输入机构名称',
  847. required: true,
  848. },
  849. {
  850. prop: 'institutionNature',
  851. label: '机构性质',
  852. type: 'select',
  853. colSpan: 12,
  854. dictType: 'institutionNature', // 字典类型
  855. defaultValue: '公办',
  856. placeholder: '请选择机构性质',
  857. required: true,
  858. clearable: true,
  859. },
  860. {
  861. prop: 'institutionLevel',
  862. label: '机构评定等级',
  863. type: 'select',
  864. colSpan: 12,
  865. dictType: 'institutionLevel', // 字典类型
  866. defaultValue: '省一级',
  867. placeholder: '请选择机构评定等级',
  868. required: true,
  869. clearable: true,
  870. },
  871. {
  872. prop: 'educationMode',
  873. label: '机构办学方式',
  874. type: 'select',
  875. colSpan: 12,
  876. dictType: 'educationMode', // 字典类型
  877. defaultValue: '全日制',
  878. placeholder: '请选择机构办学方式',
  879. required: true,
  880. clearable: true,
  881. },
  882. {
  883. prop: 'institutionAddress',
  884. label: '机构地址',
  885. type: 'input',
  886. colSpan: 12,
  887. placeholder: '请输入机构地址',
  888. required: true,
  889. },
  890. {
  891. prop: 'formFiller',
  892. label: '机构填表人',
  893. type: 'input',
  894. colSpan: 12,
  895. placeholder: '请输入机构填表人',
  896. required: true,
  897. },
  898. {
  899. prop: 'financialManager',
  900. label: '机构财务负责人',
  901. type: 'input',
  902. colSpan: 12,
  903. placeholder: '请输入机构财务负责人',
  904. required: true,
  905. },
  906. {
  907. prop: 'contactPhone',
  908. label: '机构联系电话',
  909. type: 'input',
  910. colSpan: 12,
  911. placeholder: '请输入机构联系电话',
  912. required: true,
  913. rules: [
  914. {
  915. required: true,
  916. message: '请输入机构联系电话',
  917. trigger: 'blur',
  918. },
  919. {
  920. pattern: /^1[3-9]\d{9}$/,
  921. message: '请输入正确的手机号码',
  922. trigger: 'blur',
  923. },
  924. ],
  925. },
  926. ]
  927. },
  928. // 初始化固定表数据
  929. async initFixedTableData() {
  930. // 如果是固定表类型,调用接口获取固定表配置
  931. if (
  932. this.currentSurveyRow &&
  933. this.currentSurveyRow.tableType === '固定表' &&
  934. this.currentSurveyRow.surveyTemplateId
  935. ) {
  936. try {
  937. const params = {
  938. surveyTemplateId: this.currentSurveyRow.surveyTemplateId,
  939. }
  940. const res = await getSingleRecordSurveyList(params)
  941. console.log('固定表配置数据', res)
  942. if (res && res.code === 200 && res.value) {
  943. // 将接口返回的数据转换为固定表配置格式
  944. // 固定表使用 itemlist,不使用 fixedFields 和 fixedFieldids
  945. const { itemlist } = res.value
  946. console.log('itemlist', itemlist)
  947. // 如果有 itemlist,使用 itemlist 作为表格项配置
  948. if (itemlist && Array.isArray(itemlist) && itemlist.length > 0) {
  949. this.tableItems = itemlist.map((item, index) => ({
  950. id: item.id || item.itemId || '',
  951. rowid: item.rowid || item.id || item.itemId || '', // rowid 用于父子关系
  952. seq: item.序号, // 序号就是序号
  953. itemName: item.项目 || '', // 项目就是项目
  954. unit: item.unit || '', // 单位是 unit
  955. isCategory: item.isCategory || false,
  956. categorySeq: item.categorySeq || '',
  957. categoryId: item.categoryId || '',
  958. parentid:
  959. item.parentid !== undefined
  960. ? item.parentid
  961. : item.parentId !== undefined
  962. ? item.parentId
  963. : '-1', // 父项ID,默认为 '-1'(父项)
  964. validateRules: item.validateRules || {},
  965. linkageRules: item.linkageRules || {},
  966. children: item.children || [],
  967. ...item, // 保留其他字段
  968. }))
  969. } else {
  970. // 如果没有 itemlist,使用假数据
  971. this.tableItems = this.getMockTableItems()
  972. }
  973. } else {
  974. // 接口返回失败,使用默认配置
  975. this.tableItems = this.getMockTableItems()
  976. }
  977. } catch (err) {
  978. console.error('获取固定表配置失败', err)
  979. // 出错时使用默认配置
  980. this.tableItems = this.getMockTableItems()
  981. }
  982. } else if (this.currentSurveyRow && this.currentSurveyRow.tableItems) {
  983. // 如果当前行有表格配置,则使用
  984. this.tableItems = this.currentSurveyRow.tableItems
  985. } else {
  986. // 使用假数据作为测试(实际开发中应该从后台获取)
  987. this.tableItems = this.getMockTableItems()
  988. }
  989. // 初始化监审期间(从立项信息中获取)
  990. if (this.auditPeriod) {
  991. // 如果传入了监审期间,使用传入的值
  992. if (Array.isArray(this.auditPeriod)) {
  993. this.auditPeriods = this.auditPeriod.map((p) => String(p))
  994. } else {
  995. this.auditPeriods = this.parseAuditPeriod(this.auditPeriod)
  996. }
  997. } else if (this.currentSurveyRow && this.currentSurveyRow.auditPeriod) {
  998. // 如果当前行有监审期间,使用当前行的
  999. this.auditPeriods = this.parseAuditPeriod(
  1000. this.currentSurveyRow.auditPeriod
  1001. )
  1002. } else {
  1003. // 默认使用最近3年
  1004. const currentYear = new Date().getFullYear()
  1005. this.auditPeriods = [
  1006. String(currentYear - 2),
  1007. String(currentYear - 1),
  1008. String(currentYear),
  1009. ]
  1010. }
  1011. // 额外拉取表头/规则信息,并透传给弹窗
  1012. try {
  1013. const headerRes = await getListBySurveyTemplateIdAndVersion({
  1014. surveyTemplateId: this.currentSurveyRow.surveyTemplateId,
  1015. })
  1016. if (headerRes && headerRes.code === 200) {
  1017. this.fixedHeaders = headerRes.value || null
  1018. } else {
  1019. this.fixedHeaders = null
  1020. }
  1021. } catch (e) {
  1022. this.fixedHeaders = null
  1023. }
  1024. // 打开弹窗
  1025. this.fixedTableDialogVisible = true
  1026. },
  1027. // 解析监审期间字符串(如 "2022,2023,2024" 或 "2022-2024")
  1028. parseAuditPeriod(periodStr) {
  1029. if (!periodStr) return []
  1030. if (periodStr.includes(',')) {
  1031. return periodStr.split(',').map((p) => p.trim())
  1032. }
  1033. if (periodStr.includes('-')) {
  1034. const parts = periodStr.split('-')
  1035. if (parts.length === 2) {
  1036. const start = parseInt(parts[0].trim())
  1037. const end = parseInt(parts[1].trim())
  1038. const years = []
  1039. for (let year = start; year <= end; year++) {
  1040. years.push(String(year))
  1041. }
  1042. return years
  1043. }
  1044. }
  1045. return [String(periodStr)]
  1046. },
  1047. // 获取假数据表格配置(用于测试)
  1048. getMockTableItems() {
  1049. return [
  1050. {
  1051. id: '1',
  1052. itemName: '班级数',
  1053. unit: '个',
  1054. isCategory: false,
  1055. seq: 1,
  1056. validateRules: {
  1057. required: true,
  1058. type: 'number',
  1059. min: 0,
  1060. },
  1061. },
  1062. {
  1063. id: '2',
  1064. itemName: '幼儿学生人数',
  1065. unit: '人',
  1066. isCategory: false,
  1067. seq: 2,
  1068. validateRules: {
  1069. required: true,
  1070. type: 'number',
  1071. min: 0,
  1072. },
  1073. },
  1074. {
  1075. id: 'III',
  1076. itemName: '在取做保职工总人数',
  1077. unit: '人',
  1078. isCategory: true,
  1079. categorySeq: 'III',
  1080. children: [
  1081. {
  1082. id: '3-1',
  1083. itemName: '行政管理人员数',
  1084. unit: '人',
  1085. isCategory: false,
  1086. categoryId: 'III',
  1087. seq: 3,
  1088. validateRules: {
  1089. required: true,
  1090. type: 'number',
  1091. min: 0,
  1092. },
  1093. linkageRules: {
  1094. parent: 'III',
  1095. relation: 'sum',
  1096. },
  1097. },
  1098. {
  1099. id: '3-2',
  1100. itemName: '教师人数',
  1101. unit: '人',
  1102. isCategory: false,
  1103. categoryId: 'III',
  1104. seq: 4,
  1105. validateRules: {
  1106. required: true,
  1107. type: 'number',
  1108. min: 0,
  1109. },
  1110. linkageRules: {
  1111. parent: 'III',
  1112. relation: 'sum',
  1113. },
  1114. },
  1115. {
  1116. id: '3-3',
  1117. itemName: '保育员人数',
  1118. unit: '人',
  1119. isCategory: false,
  1120. categoryId: 'III',
  1121. seq: 5,
  1122. validateRules: {
  1123. required: true,
  1124. type: 'number',
  1125. min: 0,
  1126. },
  1127. linkageRules: {
  1128. parent: 'III',
  1129. relation: 'sum',
  1130. },
  1131. },
  1132. {
  1133. id: '3-4',
  1134. itemName: '医务人员',
  1135. unit: '人',
  1136. isCategory: false,
  1137. categoryId: 'III',
  1138. seq: 6,
  1139. validateRules: {
  1140. required: true,
  1141. type: 'number',
  1142. min: 0,
  1143. },
  1144. linkageRules: {
  1145. parent: 'III',
  1146. relation: 'sum',
  1147. },
  1148. },
  1149. {
  1150. id: '3-5',
  1151. itemName: '工勤人员',
  1152. unit: '人',
  1153. isCategory: false,
  1154. categoryId: 'III',
  1155. seq: 7,
  1156. validateRules: {
  1157. required: true,
  1158. type: 'number',
  1159. min: 0,
  1160. },
  1161. linkageRules: {
  1162. parent: 'III',
  1163. relation: 'sum',
  1164. },
  1165. children: [
  1166. {
  1167. id: '3-5-1',
  1168. itemName: '炊事员',
  1169. unit: '人',
  1170. isCategory: false,
  1171. categoryId: '3-5',
  1172. seq: 8,
  1173. validateRules: {
  1174. required: true,
  1175. type: 'number',
  1176. min: 0,
  1177. },
  1178. linkageRules: {
  1179. parent: '3-5',
  1180. relation: 'sum',
  1181. },
  1182. },
  1183. {
  1184. id: '3-5-2',
  1185. itemName: '司机',
  1186. unit: '人',
  1187. isCategory: false,
  1188. categoryId: '3-5',
  1189. seq: 9,
  1190. validateRules: {
  1191. required: true,
  1192. type: 'number',
  1193. min: 0,
  1194. },
  1195. linkageRules: {
  1196. parent: '3-5',
  1197. relation: 'sum',
  1198. },
  1199. },
  1200. {
  1201. id: '3-5-3',
  1202. itemName: '清洁工',
  1203. unit: '人',
  1204. isCategory: false,
  1205. categoryId: '3-5',
  1206. seq: 10,
  1207. validateRules: {
  1208. required: true,
  1209. type: 'number',
  1210. min: 0,
  1211. },
  1212. linkageRules: {
  1213. parent: '3-5',
  1214. relation: 'sum',
  1215. },
  1216. },
  1217. ],
  1218. },
  1219. {
  1220. id: '3-6',
  1221. itemName: '其他人员',
  1222. unit: '人',
  1223. isCategory: false,
  1224. categoryId: 'III',
  1225. seq: 11,
  1226. validateRules: {
  1227. required: true,
  1228. type: 'number',
  1229. min: 0,
  1230. },
  1231. linkageRules: {
  1232. parent: 'III',
  1233. relation: 'sum',
  1234. },
  1235. },
  1236. ],
  1237. },
  1238. ]
  1239. },
  1240. resetDynamicDialogState() {
  1241. this.dynamicTableDialogVisible = false
  1242. this.dynamicTableData = []
  1243. this.tableItems = []
  1244. this.dynamicDialogKey = Date.now()
  1245. },
  1246. },
  1247. }
  1248. </script>