costSurvey.vue 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011
  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="true"
  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="noop"
  20. @refresh="noop"
  21. />
  22. <!-- 固定表弹窗(查看) -->
  23. <fixed-table-dialog
  24. :visible.sync="fixedDialogVisible"
  25. :survey-data="surveyDetailData"
  26. :fixed-fields="fixedFields || ''"
  27. :fixed-fieldids="fixedFieldids || ''"
  28. :is-view-mode="true"
  29. :request-type="1"
  30. :audited-unit-id="
  31. (currentTemplateRow && currentTemplateRow.auditedUnitId) || ''
  32. "
  33. :upload-id="(currentTemplateRow && currentTemplateRow.uploadId) || ''"
  34. :survey-template-id="
  35. (currentTemplateRow &&
  36. (currentTemplateRow.templateId ||
  37. currentTemplateRow.surveyTemplateId)) ||
  38. ''
  39. "
  40. :catalog-id="(currentTemplateRow && currentTemplateRow.catalogId) || ''"
  41. :table-items="tableItems"
  42. :audit-periods="
  43. projectAuditPeriods && projectAuditPeriods.length
  44. ? projectAuditPeriods
  45. : auditPeriods
  46. "
  47. :project-audit-periods="projectAuditPeriods"
  48. :project-audit-period="projectAuditPeriod"
  49. />
  50. <!-- 动态表填报弹窗(只读) -->
  51. <dynamic-table-dialog
  52. :key="dynamicDialogKey"
  53. :visible.sync="dynamicTableDialogVisible"
  54. :survey-data="currentSurveyRow"
  55. :table-data="dynamicTableData"
  56. :table-items="tableItems"
  57. :is-view-mode="true"
  58. :audited-unit-id="auditedUnitId"
  59. :upload-id="
  60. currentSurveyRow && (currentSurveyRow.uploadId || currentSurveyRow.id)
  61. ? currentSurveyRow.uploadId || currentSurveyRow.id
  62. : uploadId
  63. "
  64. :catalog-id="catalogId"
  65. :survey-template-id="
  66. currentSurveyRow && currentSurveyRow.surveyTemplateId
  67. ? currentSurveyRow.surveyTemplateId
  68. : surveyTemplateId
  69. "
  70. @save="noop"
  71. @refresh="noop"
  72. />
  73. <el-table
  74. style="width: 100%; margin-top: 20px"
  75. :data="paginatedData"
  76. border
  77. size="medium"
  78. >
  79. <!-- 序号列 -->
  80. <el-table-column prop="index" label="序号" width="60" align="center">
  81. <template slot-scope="scope">{{ scope.$index + 1 }}</template>
  82. </el-table-column>
  83. <!-- 成本调查表名称(点击查看) -->
  84. <el-table-column
  85. label="成本调查表"
  86. min-width="220"
  87. header-align="center"
  88. align="left"
  89. >
  90. <template slot-scope="scope">
  91. <span
  92. :style="{
  93. color: scope.row.isDisabled ? '#909399' : '#409EFF',
  94. cursor: scope.row.isDisabled ? 'default' : 'pointer',
  95. }"
  96. @click="!scope.row.isDisabled && handleOnlineFillClick(scope.row)"
  97. >
  98. {{ scope.row.name }}
  99. </span>
  100. </template>
  101. </el-table-column>
  102. <!-- 资料类型 -->
  103. <el-table-column
  104. prop="dataType"
  105. label="资料类型"
  106. width="120"
  107. align="center"
  108. />
  109. <!-- 表格类型 -->
  110. <el-table-column
  111. prop="tableType"
  112. label="表格类型"
  113. width="120"
  114. align="center"
  115. />
  116. <!-- 是否必填 -->
  117. <el-table-column
  118. prop="isRequired"
  119. label="是否必填"
  120. width="100"
  121. align="center"
  122. >
  123. <template slot-scope="scope">
  124. <span>
  125. {{
  126. scope.row.isRequired === '是' ||
  127. scope.row.isRequired === '1' ||
  128. scope.row.isRequired === 1
  129. ? '是'
  130. : '否'
  131. }}
  132. </span>
  133. </template>
  134. </el-table-column>
  135. <!-- 是否上传(只读显示) -->
  136. <el-table-column label="是否上传" width="100" align="center">
  137. <template slot-scope="scope">
  138. <span
  139. :style="{
  140. color: scope.row.isUploaded === true ? '#67c23a' : '#f56c6c',
  141. }"
  142. >
  143. {{ scope.row.isUploaded === true ? '已上传' : '未上传' }}
  144. </span>
  145. </template>
  146. </el-table-column>
  147. <el-table-column label="初审结果" width="100" align="center">
  148. <template slot-scope="scope">
  149. <span
  150. :class="{
  151. 'result-pending':
  152. !scope.row.auditedStatus || scope.row.auditedStatus === '0',
  153. 'result-pass': scope.row.auditedStatus === '1',
  154. 'result-fail': scope.row.auditedStatus === '2',
  155. }"
  156. >
  157. {{
  158. !scope.row.auditedStatus || scope.row.auditedStatus === '0'
  159. ? '未审核'
  160. : scope.row.auditedStatus === '1'
  161. ? '通过'
  162. : '不通过'
  163. }}
  164. </span>
  165. </template>
  166. </el-table-column>
  167. <!-- 操作列(只保留查看) -->
  168. <el-table-column label="操作" width="160" align="center">
  169. <template slot-scope="scope">
  170. <el-button
  171. v-if="
  172. scope.row.auditedStatus === '0' &&
  173. (currentStatus === 200 || currentStatus === '200') &&
  174. (currentNode === 'clcs' || currentNode === 'sdsh')
  175. "
  176. type="text"
  177. size="small"
  178. @click="handleAuditMaterial(scope.row)"
  179. >
  180. 审核
  181. </el-button>
  182. <el-button
  183. type="text"
  184. size="small"
  185. @click="handleOnlineFillClick(scope.row)"
  186. >
  187. 查看
  188. </el-button>
  189. </template>
  190. </el-table-column>
  191. </el-table>
  192. <el-pagination
  193. background
  194. layout="total, sizes, prev, pager, next"
  195. :current-page="pagination.currentPage"
  196. :page-sizes="[10, 20, 30, 50]"
  197. :page-size="pagination.pageSize"
  198. :total="pagination.total"
  199. style="margin-top: 20px; text-align: right"
  200. @current-change="handlePageChange"
  201. @size-change="handleSizeChange"
  202. />
  203. <!-- 资料审核弹窗 -->
  204. <el-dialog
  205. title="资料审核"
  206. :visible.sync="showAuditDialog"
  207. width="400px"
  208. center
  209. :modal="false"
  210. append-to-body
  211. >
  212. <div class="audit-material-info">
  213. <p>
  214. <strong>资料名称:</strong>
  215. {{
  216. (currentAuditMaterial &&
  217. (currentAuditMaterial.name ||
  218. currentAuditMaterial.informationName)) ||
  219. ''
  220. }}
  221. </p>
  222. </div>
  223. <el-form ref="auditForm" :model="auditForm" label-width="80px">
  224. <el-form-item label="审核结果" prop="auditedStatus">
  225. <el-radio-group v-model="auditForm.auditedStatus">
  226. <el-radio label="1">审核通过</el-radio>
  227. <el-radio label="2">不通过</el-radio>
  228. </el-radio-group>
  229. </el-form-item>
  230. </el-form>
  231. <div slot="footer" class="dialog-footer">
  232. <el-button @click="showAuditDialog = false">取消</el-button>
  233. <el-button type="primary" @click="handleAuditSubmit">提交</el-button>
  234. </div>
  235. </el-dialog>
  236. </div>
  237. </template>
  238. <script>
  239. import SurveyFormDialog from '@/views/EntDeclaration/auditTaskManagement/components/SurveyFormDialog.vue'
  240. import FixedTableDialog from '@/views/EntDeclaration/auditTaskManagement/components/FixedTableDialog.vue'
  241. import DynamicTableDialog from '@/views/EntDeclaration/auditTaskManagement/components/DynamicTableDialog.vue'
  242. import {
  243. getSingleRecordSurveyList,
  244. getSurveyDetail,
  245. getDynamicTableData,
  246. reviewCastTaskInfo,
  247. } from '@/api/audit/survey'
  248. import { getListBySurveyTemplateIdAndVersion } from '@/api/costSurveyTemplateHeaders'
  249. import { getProjectInformationInfo } from '@/api/auditTaskProcessing'
  250. export default {
  251. name: 'CostSurveyViewOnly',
  252. components: { SurveyFormDialog, FixedTableDialog, DynamicTableDialog },
  253. props: {
  254. paginatedData: { type: Array, default: () => [] },
  255. pagination: {
  256. type: Object,
  257. default: () => ({ currentPage: 1, pageSize: 10, total: 0 }),
  258. },
  259. auditedUnitId: { type: String, default: '' },
  260. uploadId: { type: String, default: '' },
  261. surveyTemplateId: { type: String, default: '' },
  262. catalogId: { type: String, default: '' },
  263. // 立项信息ID(用于获取监审期间)
  264. projectId: { type: [String, Number], default: '' },
  265. currentNode: {
  266. type: String,
  267. default: '',
  268. },
  269. currentStatus: {
  270. type: String,
  271. default: '',
  272. },
  273. },
  274. data() {
  275. return {
  276. surveyFormDialogVisible: false,
  277. fixedTableDialogVisible: false,
  278. fixedDialogVisible: false,
  279. dynamicTableDialogVisible: false,
  280. currentSurveyRow: null,
  281. currentTemplateRow: null,
  282. formFields: [],
  283. surveyDetailData: {},
  284. tableItems: [],
  285. auditPeriods: [],
  286. projectAuditPeriods: [],
  287. projectAuditPeriod: '',
  288. fixedFields: '',
  289. fixedFieldids: '',
  290. dynamicTableData: [],
  291. dynamicDialogKey: 0,
  292. dynamicTableLoading: false,
  293. fixedHeaders: null,
  294. currentStatusLocal: '',
  295. currentNodeLocal: '',
  296. showAuditDialog: false,
  297. currentAuditMaterial: null,
  298. auditForm: {
  299. auditedStatus: '1',
  300. auditOpinion: '',
  301. },
  302. }
  303. },
  304. watch: {
  305. currentNode(newVal) {
  306. this.currentNodeLocal = newVal
  307. },
  308. currentStatus(newVal) {
  309. this.currentStatusLocal = newVal
  310. },
  311. },
  312. methods: {
  313. // 查看(只读)
  314. async handleOnlineFillClick(row) {
  315. this.currentSurveyRow = row
  316. this.currentTemplateRow = row
  317. this.surveyDetailData = {}
  318. if (row.tableType === '单记录') {
  319. if (row.id && this.auditedUnitId) {
  320. try {
  321. const params = {
  322. uploadId: row.id,
  323. auditedUnitId: this.auditedUnitId,
  324. type: 1,
  325. }
  326. const res = await getSurveyDetail(params)
  327. if (res && res.code === 200 && res.value) {
  328. const detailData = {}
  329. if (Array.isArray(res.value)) {
  330. res.value.forEach((item) => {
  331. if (item.rowid && item.rvalue !== undefined)
  332. detailData[item.rowid] = item.rvalue
  333. })
  334. } else if (res.value) {
  335. Object.assign(detailData, res.value)
  336. }
  337. this.surveyDetailData = detailData
  338. }
  339. } catch (err) {}
  340. }
  341. await this.initFormFields()
  342. } else if (row.tableType === '固定表') {
  343. await this.initFixedTableData()
  344. } else if (row.tableType === '动态表') {
  345. this.resetDynamicDialogState()
  346. await this.initDynamicTableData()
  347. }
  348. },
  349. // 处理审核点击
  350. handleAuditMaterial(row) {
  351. this.currentAuditMaterial = { ...row }
  352. this.auditForm = {
  353. auditedStatus:
  354. this.currentAuditMaterial && this.currentAuditMaterial.auditedStatus
  355. ? String(this.currentAuditMaterial.auditedStatus)
  356. : '1',
  357. auditOpinion: '',
  358. }
  359. this.showAuditDialog = true
  360. },
  361. // 提交审核
  362. async handleAuditSubmit() {
  363. try {
  364. const payload = {
  365. ...this.currentAuditMaterial,
  366. auditedStatus: this.auditForm.auditedStatus,
  367. auditResult: this.auditForm.auditedStatus,
  368. auditOpinion: this.auditForm.auditOpinion,
  369. type: 1,
  370. }
  371. const res = await reviewCastTaskInfo(payload)
  372. if (res && res.code === 200) {
  373. this.$message({
  374. type: 'success',
  375. message: res.value || '审核已提交',
  376. })
  377. this.$emit('refresh')
  378. this.showAuditDialog = false
  379. } else {
  380. this.$message({
  381. type: 'error',
  382. message: (res && res.message) || '提交失败',
  383. })
  384. }
  385. } catch (e) {
  386. // this.$message.error('提交失败')
  387. console.error('提交失败')
  388. }
  389. },
  390. handlePageChange(page) {
  391. this.$emit('handle-page-change', page)
  392. },
  393. handleSizeChange(size) {
  394. this.$emit('handle-size-change', size)
  395. },
  396. async initDynamicTableData() {
  397. try {
  398. this.dynamicTableLoading = true
  399. const uploadId =
  400. (this.currentSurveyRow &&
  401. (this.currentSurveyRow.uploadId || this.currentSurveyRow.id)) ||
  402. this.uploadId ||
  403. ''
  404. const auditedUnitId =
  405. this.auditedUnitId ||
  406. (this.currentSurveyRow && this.currentSurveyRow.auditedUnitId) ||
  407. ''
  408. const catalogId =
  409. (this.currentSurveyRow && this.currentSurveyRow.catalogId) ||
  410. this.catalogId ||
  411. ''
  412. const surveyTemplateId =
  413. (this.currentSurveyRow && this.currentSurveyRow.surveyTemplateId) ||
  414. this.surveyTemplateId ||
  415. ''
  416. const params = {
  417. uploadId,
  418. auditedUnitId,
  419. catalogId,
  420. surveyTemplateId,
  421. type: 1,
  422. }
  423. const res = await getDynamicTableData(params)
  424. if (res && res.code === 200) {
  425. const records =
  426. (res.value && (res.value.records || res.value)) || []
  427. this.dynamicTableData = Array.isArray(records) ? records : []
  428. } else {
  429. this.dynamicTableData =
  430. (this.currentSurveyRow &&
  431. this.currentSurveyRow.dynamicTableData) ||
  432. []
  433. }
  434. if (
  435. this.currentSurveyRow &&
  436. this.currentSurveyRow.tableItems &&
  437. this.currentSurveyRow.tableItems.length > 0
  438. ) {
  439. this.tableItems = this.currentSurveyRow.tableItems
  440. } else {
  441. this.tableItems = this.getMockTableItems()
  442. }
  443. this.dynamicTableDialogVisible = true
  444. } catch (error) {
  445. this.dynamicTableData =
  446. (this.currentSurveyRow && this.currentSurveyRow.dynamicTableData) ||
  447. []
  448. this.tableItems =
  449. (this.currentSurveyRow && this.currentSurveyRow.tableItems) ||
  450. this.getMockTableItems()
  451. this.dynamicTableDialogVisible = true
  452. } finally {
  453. this.dynamicTableLoading = false
  454. }
  455. },
  456. async initFormFields() {
  457. if (
  458. this.currentSurveyRow &&
  459. this.currentSurveyRow.tableType === '单记录' &&
  460. this.currentSurveyRow.surveyTemplateId
  461. ) {
  462. try {
  463. const params = {
  464. surveyTemplateId: this.currentSurveyRow.surveyTemplateId,
  465. type: 1,
  466. }
  467. const res = await getListBySurveyTemplateIdAndVersion(params)
  468. if (res && res.code === 200) {
  469. let mapped = []
  470. if (Array.isArray(res.value)) {
  471. mapped = res.value
  472. .map((item, index) =>
  473. this.mapApiFieldToFormField(item, index)
  474. )
  475. .filter(Boolean)
  476. } else if (res.value && typeof res.value === 'object') {
  477. const { fixedFields, fixedFieldids } = res.value
  478. if (fixedFields && fixedFieldids) {
  479. const labels = fixedFields.split(',').map((i) => i.trim())
  480. const ids = fixedFieldids.split(',').map((i) => i.trim())
  481. mapped = labels.map((label, index) => ({
  482. prop: ids[index] || `field_${index}`,
  483. label,
  484. type: 'input',
  485. colSpan: 12,
  486. placeholder: `请输入${label}`,
  487. rules: [],
  488. defaultValue: '',
  489. disabled: true,
  490. clearable: true,
  491. multiple: false,
  492. required: false,
  493. }))
  494. }
  495. }
  496. this.formFields =
  497. mapped.length > 0 ? mapped : this.getMockFormFields()
  498. } else {
  499. this.formFields = this.getMockFormFields()
  500. }
  501. this.surveyFormDialogVisible = true
  502. } catch (err) {
  503. this.formFields = this.getMockFormFields()
  504. this.surveyFormDialogVisible = true
  505. }
  506. } else if (this.currentSurveyRow && this.currentSurveyRow.formFields) {
  507. this.formFields = this.currentSurveyRow.formFields
  508. this.surveyFormDialogVisible = true
  509. } else {
  510. this.formFields = this.getMockFormFields()
  511. this.surveyFormDialogVisible = true
  512. }
  513. },
  514. mapApiFieldToFormField(item, index = 0) {
  515. if (!item) return null
  516. const getVal = (keys, fallback) => {
  517. for (const key of keys) {
  518. if (
  519. key &&
  520. item[key] !== undefined &&
  521. item[key] !== null &&
  522. item[key] !== ''
  523. ) {
  524. return item[key]
  525. }
  526. }
  527. return fallback
  528. }
  529. const toBool = (value) => {
  530. if (value === undefined || value === null) return false
  531. if (typeof value === 'boolean') return value
  532. if (typeof value === 'number') return value === 1
  533. const str = String(value).trim().toLowerCase()
  534. return ['1', 'true', 'y', 'yes', '是'].includes(str)
  535. }
  536. const toNumber = (value) => {
  537. if (value === undefined || value === null || value === '')
  538. return undefined
  539. const num = Number(value)
  540. return Number.isNaN(num) ? undefined : num
  541. }
  542. const prop =
  543. getVal(
  544. [
  545. 'fieldName',
  546. 'field_name',
  547. 'columnName',
  548. 'column_name',
  549. 'fieldCode',
  550. ],
  551. undefined
  552. ) || `field_${index}`
  553. const label =
  554. getVal(
  555. [
  556. 'columnComment',
  557. 'column_comment',
  558. 'fieldCname',
  559. 'field_cname',
  560. 'fieldLabel',
  561. 'field_label',
  562. ],
  563. prop
  564. ) || prop
  565. const columnType =
  566. (getVal(
  567. ['columnType', 'column_type', 'fieldType', 'field_type'],
  568. ''
  569. ) || '') + ''
  570. const columnTypeLower = columnType.toLowerCase()
  571. const totalLength = toNumber(
  572. getVal(
  573. ['fieldTypeLen', 'field_typelen', 'length', 'fieldLength'],
  574. undefined
  575. )
  576. )
  577. const decimalLength = toNumber(
  578. getVal(
  579. ['fieldTypeNointLen', 'field_typenointlen', 'scale'],
  580. undefined
  581. )
  582. )
  583. const isAuditPeriod = toBool(
  584. getVal(['isAuditPeriod', 'is_audit_period'], false)
  585. )
  586. const dictCode =
  587. getVal(
  588. [
  589. 'dictCode',
  590. 'dict_code',
  591. 'dictId',
  592. 'dictid',
  593. 'dictType',
  594. 'dict_type',
  595. ],
  596. ''
  597. ) || ''
  598. const optionsRaw = getVal(['options'], [])
  599. let options = []
  600. if (Array.isArray(optionsRaw)) options = optionsRaw
  601. else if (typeof optionsRaw === 'string' && optionsRaw.trim() !== '') {
  602. options = optionsRaw
  603. .split(',')
  604. .map((value) => ({ label: value.trim(), value: value.trim() }))
  605. }
  606. let type = getVal(['componentType', 'type'], '')
  607. if (!type) {
  608. if (dictCode || options.length > 0) type = 'select'
  609. else if (
  610. columnTypeLower.includes('datetime') ||
  611. columnTypeLower.includes('timestamp') ||
  612. columnTypeLower.includes('date time')
  613. )
  614. type = 'datetime'
  615. else if (columnTypeLower.includes('date')) type = 'date'
  616. else if (columnTypeLower.includes('year')) type = 'year'
  617. else if (
  618. columnTypeLower.includes('int') ||
  619. columnTypeLower.includes('number') ||
  620. columnTypeLower.includes('decimal') ||
  621. columnTypeLower.includes('float') ||
  622. columnTypeLower.includes('double')
  623. )
  624. type = 'number'
  625. else type = 'input'
  626. }
  627. const required = toBool(
  628. getVal(['isRequired', 'is_required', 'required'], false)
  629. )
  630. const multiple = toBool(
  631. getVal(['isMultiple', 'is_multiple', 'multiple'], false)
  632. )
  633. const colSpan =
  634. toNumber(
  635. getVal(['colSpan', 'colspan', 'columnSpan', 'column_span'], 12)
  636. ) || 12
  637. const placeholder =
  638. getVal(
  639. ['placeholder', 'columnComment', 'column_comment'],
  640. undefined
  641. ) || (type === 'select' ? `请选择${label}` : `请输入${label}`)
  642. const defaultValue = getVal(
  643. ['defaultValue', 'default_value', 'defaultVal', 'default_val'],
  644. undefined
  645. )
  646. const precision = toNumber(
  647. getVal(
  648. ['fieldTypeNointLen', 'field_typenointlen', 'precision'],
  649. undefined
  650. )
  651. )
  652. const min = toNumber(getVal(['min'], undefined))
  653. const max = toNumber(getVal(['max'], undefined))
  654. const format = getVal(['format'], undefined)
  655. const valueFormat =
  656. getVal(['valueFormat', 'value_format'], undefined) ||
  657. (type === 'datetime'
  658. ? 'yyyy-MM-dd HH:mm:ss'
  659. : type === 'date'
  660. ? 'yyyy-MM-dd'
  661. : type === 'year'
  662. ? 'yyyy'
  663. : undefined)
  664. const formatLength = this.extractLengthFromFormat(format)
  665. const rules = this.buildFieldRules({
  666. type,
  667. label,
  668. required,
  669. totalLength,
  670. decimalLength,
  671. formatLength,
  672. format,
  673. isAuditPeriod,
  674. })
  675. return {
  676. prop,
  677. label,
  678. type,
  679. colSpan,
  680. placeholder,
  681. dictCode,
  682. dictType: dictCode,
  683. options,
  684. required,
  685. defaultValue,
  686. multiple,
  687. precision,
  688. min,
  689. max,
  690. format,
  691. valueFormat,
  692. totalLength,
  693. decimalLength,
  694. formatLength,
  695. rules,
  696. }
  697. },
  698. extractLengthFromFormat(format) {
  699. if (!format) return undefined
  700. const str = String(format).trim()
  701. if (!str) return undefined
  702. const match = str.match(/\d+/)
  703. if (match && match[0]) {
  704. const len = Number(match[0])
  705. return Number.isNaN(len) ? undefined : len
  706. }
  707. return undefined
  708. },
  709. buildFieldRules(meta) {
  710. const {
  711. type,
  712. label,
  713. required,
  714. totalLength,
  715. decimalLength,
  716. formatLength,
  717. format,
  718. isAuditPeriod,
  719. } = meta || {}
  720. const rules = []
  721. const trigger = type === 'select' ? 'change' : 'blur'
  722. if (required) {
  723. rules.push({
  724. required: true,
  725. message: `${type === 'select' ? '请选择' : '请输入'}${label}`,
  726. trigger,
  727. })
  728. }
  729. const inputMaxLength = formatLength || totalLength
  730. if (type === 'input' && inputMaxLength) {
  731. rules.push({
  732. validator: (_, _value, callback) => {
  733. const value = _value
  734. if (value === undefined || value === null || value === '') {
  735. callback()
  736. return
  737. }
  738. const str = String(value)
  739. if (str.length > inputMaxLength) {
  740. callback(
  741. new Error(`${label}长度不能超过${inputMaxLength}个字符`)
  742. )
  743. } else {
  744. callback()
  745. }
  746. },
  747. trigger: 'blur',
  748. })
  749. }
  750. const numberTotal = totalLength || formatLength
  751. if (type === 'number') {
  752. rules.push({
  753. validator: (_, _value, callback) => {
  754. const value = _value
  755. if (value === undefined || value === null || value === '') {
  756. callback()
  757. return
  758. }
  759. if (Number.isNaN(Number(value))) {
  760. callback(new Error(`${label}必须为数字`))
  761. return
  762. }
  763. const pure = String(value).replace('-', '')
  764. if (numberTotal && pure.replace('.', '').length > numberTotal) {
  765. callback(new Error(`${label}总位数不能超过${numberTotal}`))
  766. return
  767. }
  768. if (decimalLength !== undefined && decimalLength !== null) {
  769. const decimals = pure.split('.')[1] || ''
  770. if (decimals.length > decimalLength) {
  771. callback(
  772. new Error(`${label}小数位不能超过${decimalLength}位`)
  773. )
  774. return
  775. }
  776. }
  777. callback()
  778. },
  779. trigger: 'blur',
  780. })
  781. }
  782. if (type === 'datetime' || type === 'date') {
  783. if (format) {
  784. rules.push({
  785. validator: (_, _value, callback) => {
  786. callback()
  787. },
  788. trigger: 'change',
  789. })
  790. }
  791. }
  792. if (type === 'year' || isAuditPeriod) {
  793. rules.push({
  794. validator: (_, _value, callback) => {
  795. const value = _value
  796. if (value === undefined || value === null || value === '') {
  797. callback()
  798. return
  799. }
  800. const pattern = /^\d{4}$/
  801. if (!pattern.test(String(value))) {
  802. callback(new Error(`${label}必须是四位年份`))
  803. } else {
  804. callback()
  805. }
  806. },
  807. trigger: 'change',
  808. })
  809. }
  810. return rules
  811. },
  812. getMockTableItems() {
  813. return []
  814. },
  815. // 解析监审期间字符串(如 "2022,2023,2024" 或 "2022-2024")
  816. parseAuditPeriod(periodStr) {
  817. if (!periodStr) return []
  818. const str = String(periodStr)
  819. if (str.includes(',')) {
  820. return str.split(',').map((p) => String(p).trim())
  821. }
  822. if (str.includes('-')) {
  823. const parts = str.split('-')
  824. if (parts.length === 2) {
  825. const start = parseInt(parts[0].trim())
  826. const end = parseInt(parts[1].trim())
  827. const years = []
  828. for (let y = start; y <= end; y++) years.push(String(y))
  829. return years
  830. }
  831. }
  832. return [String(str)]
  833. },
  834. // 初始化固定表数据(只读)
  835. async initFixedTableData() {
  836. // 仅当当前行为固定表且包含模板ID时调用
  837. if (
  838. this.currentSurveyRow &&
  839. this.currentSurveyRow.tableType === '固定表' &&
  840. this.currentSurveyRow.surveyTemplateId
  841. ) {
  842. try {
  843. // 1) 获取固定表项配置(itemlist)
  844. const params = {
  845. surveyTemplateId: this.currentSurveyRow.surveyTemplateId,
  846. type: 1,
  847. }
  848. const res = await getSingleRecordSurveyList(params)
  849. if (res && res.code === 200 && res.value) {
  850. this.fixedFields = res.value.fixedFields || ''
  851. this.fixedFieldids = res.value.fixedFieldids || ''
  852. const { itemlist } = res.value || {}
  853. if (Array.isArray(itemlist) && itemlist.length > 0) {
  854. this.tableItems = itemlist.map((item) => ({
  855. id: item.id || item.itemId || '',
  856. rowid: item.rowid || item.id || item.itemId || '',
  857. seq: item.序号,
  858. itemName: item.项目 || '',
  859. unit: item.unit || '',
  860. isCategory: item.isCategory || false,
  861. categorySeq: item.categorySeq || '',
  862. categoryId: item.categoryId || '',
  863. parentid:
  864. item.parentid !== undefined
  865. ? item.parentid
  866. : item.parentId !== undefined
  867. ? item.parentId
  868. : '-1',
  869. validateRules: item.validateRules || {},
  870. linkageRules: item.linkageRules || {},
  871. children: item.children || [],
  872. ...item,
  873. }))
  874. } else {
  875. this.tableItems = this.getMockTableItems()
  876. }
  877. } else {
  878. this.tableItems = this.getMockTableItems()
  879. }
  880. } catch (err) {
  881. this.tableItems = this.getMockTableItems()
  882. }
  883. // 2) 初始化监审期间:优先当前行,其次默认最近3年
  884. if (this.currentSurveyRow && this.currentSurveyRow.auditPeriod) {
  885. this.auditPeriods = this.parseAuditPeriod(
  886. this.currentSurveyRow.auditPeriod
  887. )
  888. }
  889. // else {
  890. // const currentYear = new Date().getFullYear()
  891. // this.auditPeriods = [
  892. // String(currentYear - 2),
  893. // String(currentYear - 1),
  894. // String(currentYear),
  895. // ]
  896. // }
  897. // 2.1) 获取立项信息监审期间作为优先回显(FixedTableDialog 内部会优先使用 projectAuditPeriods)
  898. await this.fetchProjectAuditPeriods()
  899. // 若立项未配置或接口未返回有效年份,则用已解析的 auditPeriods 覆盖传入,避免子组件回退到“最近三年”导致出现不期望年份
  900. if (
  901. (!this.projectAuditPeriods ||
  902. this.projectAuditPeriods.length === 0) &&
  903. Array.isArray(this.auditPeriods) &&
  904. this.auditPeriods.length > 0
  905. ) {
  906. this.projectAuditPeriods = this.auditPeriods.slice()
  907. this.projectAuditPeriod = this.auditPeriods.join(',')
  908. }
  909. // 3) 额外拉取表头/规则信息,并透传给弹窗
  910. try {
  911. const headerRes = await getListBySurveyTemplateIdAndVersion({
  912. surveyTemplateId: this.currentSurveyRow.surveyTemplateId,
  913. type: 1,
  914. })
  915. if (headerRes && headerRes.code === 200) {
  916. this.fixedHeaders = headerRes.value || null
  917. } else {
  918. this.fixedHeaders = null
  919. }
  920. } catch (e) {
  921. this.fixedHeaders = null
  922. }
  923. // 3.1) 将最终年份写入 surveyData,避免子组件走默认最近三年
  924. const finalYears =
  925. (this.projectAuditPeriods && this.projectAuditPeriods.length
  926. ? this.projectAuditPeriods
  927. : this.auditPeriods) || []
  928. if (Array.isArray(finalYears) && finalYears.length > 0) {
  929. const apStr = finalYears.join(',')
  930. this.surveyDetailData = {
  931. ...(this.surveyDetailData || {}),
  932. auditPeriod: apStr,
  933. }
  934. }
  935. // 4) 打开固定表弹窗
  936. this.fixedTableDialogVisible = true
  937. this.fixedDialogVisible = true
  938. return
  939. }
  940. // 兜底:没有条件则使用当前行自带配置或空
  941. this.tableItems =
  942. (this.currentSurveyRow && this.currentSurveyRow.tableItems) ||
  943. this.getMockTableItems()
  944. this.fixedHeaders = null
  945. this.fixedTableDialogVisible = true
  946. this.fixedDialogVisible = true
  947. },
  948. // 获取立项信息监审期间,供固定表年份优先回显
  949. async fetchProjectAuditPeriods() {
  950. try {
  951. this.projectAuditPeriods = []
  952. this.projectAuditPeriod = ''
  953. const pid = this.projectId
  954. if (!pid) return
  955. const res = await getProjectInformationInfo(pid)
  956. const val = res && res.value
  957. if (!val) return
  958. // 支持两种来源:字符串 auditPeriod 或 数组 auditPeriodArray
  959. let years = []
  960. if (typeof val.auditPeriod === 'string' && val.auditPeriod.trim()) {
  961. years = this.parseAuditPeriod(val.auditPeriod)
  962. } else if (Array.isArray(val.auditPeriod)) {
  963. years = val.auditPeriod.map((y) => String(y))
  964. } else if (Array.isArray(val.auditPeriodArray)) {
  965. years = val.auditPeriodArray
  966. .map((it) => it && (it.value || it))
  967. .map((y) => String(y))
  968. }
  969. // 规范化:去空白、保留顺序去重、仅保留四位年份
  970. const seen = new Set()
  971. years = years
  972. .map((y) => String(y || '').trim())
  973. .filter(
  974. (y) => /^(19|20)\d{2}$/.test(y) && !seen.has(y) && seen.add(y)
  975. )
  976. this.projectAuditPeriods = years
  977. this.projectAuditPeriod = years.join(',')
  978. console.log(this.projectAuditPeriods, 'this.projectAuditPeriods')
  979. console.log(this.projectAuditPeriod, 'this.projectAuditPeriod')
  980. } catch (e) {
  981. // 忽略失败,保留已有解析结果
  982. }
  983. },
  984. resetDynamicDialogState() {
  985. this.dynamicDialogKey += 1
  986. },
  987. noop() {},
  988. },
  989. }
  990. </script>