surveyDialog.vue 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095
  1. <template>
  2. <div class="survey-dialog-container">
  3. <CostAuditDialog
  4. :title="dialogTitle"
  5. :visible="dialogVisible"
  6. :width="dialogWidth"
  7. :close-on-click-modal="false"
  8. :show-confirm-btn="false"
  9. cancel-text="关闭"
  10. append-to-body
  11. @cancel="handleCancel"
  12. >
  13. <div class="content-edit-container">
  14. <div class="table-header-info mb20">
  15. <div class="table-name">
  16. 表名:{{ contentEditForm.surveyTemplateName }}
  17. </div>
  18. <div class="table-style">
  19. 表单样式:
  20. <el-radio-group v-model="contentEditForm.templateType" disabled>
  21. <el-radio label="1">单记录</el-radio>
  22. <el-radio label="2">固定表</el-radio>
  23. <el-radio label="3">动态表</el-radio>
  24. </el-radio-group>
  25. </div>
  26. </div>
  27. <!-- 单记录列表 -->
  28. <div v-if="contentEditForm.templateType == '1'">
  29. <div class="table-edit-container">
  30. <el-table
  31. :data="contentEditForm.fixedTable.fixedTables"
  32. border
  33. style="width: 100%"
  34. >
  35. <!-- <el-table-column
  36. label="序号"
  37. width="100"
  38. align="center"
  39. prop="orderNum"
  40. ></el-table-column> -->
  41. <el-table-column
  42. v-for="(item, index) in contentEditForm.fixedTable
  43. .fixedTablesTitle"
  44. :key="index"
  45. :label="item.rkey"
  46. align="center"
  47. >
  48. <!-- <template slot-scope="scope">
  49. {{
  50. scope.row.fixedValues
  51. ? scope.row.fixedValues[item.rkey]
  52. : ''
  53. }}
  54. </template> -->
  55. </el-table-column>
  56. </el-table>
  57. </div>
  58. </div>
  59. <!-- 固定表时显示 -->
  60. <div v-if="contentEditForm.templateType == '2'">
  61. <div class="table-edit-container">
  62. <el-table
  63. :data="fixedTableDisplayData"
  64. border
  65. style="width: 100%"
  66. :row-class-name="getFixedPreviewRowClass"
  67. >
  68. <el-table-column label="序号" width="80" align="center">
  69. <template slot-scope="scope">
  70. {{ scope.row._displaySeq || '-' }}
  71. </template>
  72. </el-table-column>
  73. <el-table-column label="项目" min-width="200" align="left">
  74. <template slot-scope="scope">
  75. <span
  76. :class="[
  77. 'table-item-text',
  78. scope.row._isCategory ? 'category-name' : '',
  79. ]"
  80. :style="{ paddingLeft: `${scope.row._indentLevel * 20}px` }"
  81. >
  82. {{ scope.row._displayName || '-' }}
  83. </span>
  84. </template>
  85. </el-table-column>
  86. <el-table-column label="单位" width="100" align="center">
  87. <template slot-scope="scope">
  88. <span v-if="!scope.row._isCategory">
  89. {{ scope.row._displayUnit || '-' }}
  90. </span>
  91. </template>
  92. </el-table-column>
  93. <el-table-column
  94. v-for="column in fixedTableValueColumns"
  95. :key="column.key"
  96. :label="column.label"
  97. min-width="120"
  98. align="center"
  99. >
  100. <template slot-scope="scope">
  101. <el-input
  102. v-if="!scope.row._isCategory"
  103. :value="scope.row._displayValues[column.key] || ''"
  104. :placeholder="column.placeholder"
  105. disabled
  106. />
  107. </template>
  108. </el-table-column>
  109. <el-table-column label="备注" min-width="160" align="left">
  110. <template slot-scope="scope">
  111. <!-- <span v-if="!scope.row._isCategory">
  112. {{ scope.row._displayRemark || '-' }}
  113. </span> -->
  114. <el-input
  115. v-if="!scope.row._isCategory"
  116. :value="scope.row._displayRemark || ''"
  117. placeholder="请输入备注"
  118. disabled
  119. />
  120. </template>
  121. </el-table-column>
  122. <!-- <el-table-column label="指标编号" width="120" align="center">
  123. <template slot-scope="scope">
  124. <span v-if="!scope.row._isCategory">
  125. {{ scope.row.cellCode || '-' }}
  126. </span>
  127. </template>
  128. </el-table-column>
  129. <el-table-column label="计算公式" min-width="150" align="left">
  130. <template slot-scope="scope">
  131. <span v-if="!scope.row._isCategory">
  132. {{ scope.row.calculationFormula || '-' }}
  133. </span>
  134. </template>
  135. </el-table-column> -->
  136. </el-table>
  137. </div>
  138. </div>
  139. <!-- 动态表时显示 -->
  140. <div v-if="contentEditForm.templateType == '3'">
  141. <div class="table-edit-container">
  142. <el-table
  143. :data="contentEditForm.dynamicTable.dynamicTables"
  144. border
  145. style="width: 100%"
  146. >
  147. <el-table-column
  148. label="序号"
  149. width="100"
  150. align="center"
  151. prop="orderNum"
  152. >
  153. <template slot-scope="scope">
  154. {{
  155. scope.row.dynamicValues
  156. ? scope.row.dynamicValues['序号']
  157. : ''
  158. }}
  159. </template>
  160. </el-table-column>
  161. <el-table-column
  162. v-for="(item, index) in contentEditForm.dynamicTable
  163. .dynamicTableHeaders"
  164. :key="index"
  165. :label="item.rkey"
  166. align="center"
  167. >
  168. <template slot-scope="scope">
  169. {{
  170. scope.row.dynamicValues
  171. ? scope.row.dynamicValues[item.rkey]
  172. : ''
  173. }}
  174. </template>
  175. </el-table-column>
  176. </el-table>
  177. </div>
  178. </div>
  179. </div>
  180. </CostAuditDialog>
  181. </div>
  182. </template>
  183. <script>
  184. import CostAuditDialog from '@/components/costAudit/CostAuditDialog'
  185. import { listByCurrentTemplateId } from '@/api/catalogManage.js'
  186. export default {
  187. name: 'SurveyDialog',
  188. components: {
  189. CostAuditDialog,
  190. },
  191. props: {
  192. // 弹窗可见状态
  193. dialogVisible: {
  194. type: Boolean,
  195. default: false,
  196. },
  197. // 父组件传入的监审期间(字符串、数组或范围)
  198. auditPeriodFromParent: {
  199. type: [String, Array],
  200. default: '',
  201. },
  202. // 弹窗标题
  203. dialogTitle: {
  204. type: String,
  205. default: '查看',
  206. },
  207. // 弹窗宽度
  208. dialogWidth: {
  209. type: String,
  210. default: '70%',
  211. },
  212. // 表单数据
  213. formData: {
  214. type: Object,
  215. default: () => ({
  216. surveyTemplateName: '',
  217. templateType: '1',
  218. versionId: '',
  219. // 单记录列表
  220. tableHeaders: [],
  221. // 固定表列表
  222. fixedTable: {
  223. tableHeaders: [],
  224. fixedTables: [],
  225. fixedTablesTitle: [],
  226. fixedTableHeaders: [],
  227. },
  228. // 动态表列表
  229. dynamicTable: {
  230. tableHeaders: [],
  231. dynamicTables: [],
  232. dynamicTablesTitle: [],
  233. dynamicTableHeaders: [],
  234. },
  235. isDynamicTables: false,
  236. isFixedTables: false,
  237. }),
  238. },
  239. // 是否禁用编辑
  240. disabled: {
  241. type: Boolean,
  242. default: true,
  243. },
  244. },
  245. data() {
  246. return {
  247. contentEditForm: {
  248. surveyTemplateName: '',
  249. templateType: '1',
  250. versionId: '',
  251. // 单记录列表
  252. tableHeaders: [],
  253. // 固定表列表
  254. fixedTable: {
  255. tableHeaders: [],
  256. fixedTables: [],
  257. fixedTablesTitle: [],
  258. fixedTableHeaders: [],
  259. },
  260. // 动态表列表
  261. dynamicTable: {
  262. tableHeaders: [],
  263. dynamicTables: [],
  264. dynamicTablesTitle: [],
  265. dynamicTableHeaders: [],
  266. },
  267. isDynamicTables: false,
  268. isFixedTables: false,
  269. },
  270. }
  271. },
  272. computed: {
  273. auditPeriodYears() {
  274. const collectedYears = []
  275. const pushYear = (year) => {
  276. if (!year) return
  277. const str = String(year).trim()
  278. if (!str) return
  279. const matchList = str.match(/(19|20)\d{2}/g)
  280. if (matchList && matchList.length > 0) {
  281. matchList.forEach((match) => {
  282. if (!collectedYears.includes(match)) {
  283. collectedYears.push(match)
  284. }
  285. })
  286. return
  287. }
  288. if (/^\d{4}$/.test(str)) {
  289. if (!collectedYears.includes(str)) {
  290. collectedYears.push(str)
  291. }
  292. }
  293. }
  294. const parseMaybeString = (value) => {
  295. if (!value) return
  296. if (Array.isArray(value)) {
  297. value.forEach((item) => parseMaybeString(item))
  298. return
  299. }
  300. if (typeof value === 'object') {
  301. if (value.value !== undefined) {
  302. parseMaybeString(value.value)
  303. }
  304. if (value.label !== undefined) {
  305. parseMaybeString(value.label)
  306. }
  307. return
  308. }
  309. if (typeof value === 'number') {
  310. pushYear(value)
  311. return
  312. }
  313. if (typeof value === 'string') {
  314. if (value.includes(',')) {
  315. value
  316. .split(',')
  317. .map((item) => item.trim())
  318. .forEach((item) => parseMaybeString(item))
  319. return
  320. }
  321. if (value.includes('-')) {
  322. const parts = value.split('-')
  323. if (parts.length === 2) {
  324. const start = parseInt(parts[0].trim(), 10)
  325. const end = parseInt(parts[1].trim(), 10)
  326. if (!isNaN(start) && !isNaN(end) && start <= end) {
  327. for (let year = start; year <= end; year++) {
  328. pushYear(year)
  329. }
  330. return
  331. }
  332. }
  333. }
  334. }
  335. pushYear(value)
  336. }
  337. const form = this.contentEditForm || {}
  338. const data = form.data || {}
  339. const candidateSources = [
  340. this.auditPeriodFromParent,
  341. form.auditPeriod,
  342. form.auditPeriods,
  343. form.auditPeriodList,
  344. form.auditPeriodArray,
  345. form.auditPeriodStr,
  346. data.auditPeriod,
  347. data.auditPeriods,
  348. data.auditPeriodList,
  349. data.auditPeriodArray,
  350. data.auditPeriodStr,
  351. data.basicInfo && data.basicInfo.auditPeriod,
  352. data.basicInfo && data.basicInfo.auditPeriodArray,
  353. data.projectInfo && data.projectInfo.auditPeriod,
  354. data.project && data.project.auditPeriod,
  355. ]
  356. candidateSources.forEach((source) => parseMaybeString(source))
  357. if (collectedYears.length === 0) {
  358. const candidateLabels = this.fixedTableColumnCandidates
  359. candidateLabels
  360. .map((label) => {
  361. const match = String(label).match(/(19|20)\d{2}/)
  362. return match ? match[0] : null
  363. })
  364. .filter(Boolean)
  365. .forEach((year) => {
  366. if (!collectedYears.includes(year)) {
  367. collectedYears.push(year)
  368. }
  369. })
  370. }
  371. return collectedYears
  372. },
  373. fixedTableColumnCandidates() {
  374. const labels = new Set()
  375. const addLabel = (value) => {
  376. if (value === undefined || value === null) return
  377. const str = String(value).trim()
  378. if (!str) return
  379. labels.add(str)
  380. }
  381. const fixedTable =
  382. (this.contentEditForm && this.contentEditForm.fixedTable) || {}
  383. const potentialSources = [
  384. fixedTable.fixedTablesTitle,
  385. fixedTable.fixedTableHeaders,
  386. fixedTable.tableHeaders,
  387. ]
  388. potentialSources.forEach((source) => {
  389. if (!Array.isArray(source)) return
  390. source.forEach((item) => {
  391. if (item && typeof item === 'object') {
  392. addLabel(item.rkey || item.label || item.fieldName || item.name)
  393. } else {
  394. addLabel(item)
  395. }
  396. })
  397. })
  398. const rows = fixedTable.fixedTables
  399. if (Array.isArray(rows)) {
  400. rows.forEach((row) => {
  401. if (row && typeof row === 'object' && row.fixedValues) {
  402. Object.keys(row.fixedValues).forEach((key) => addLabel(key))
  403. }
  404. })
  405. }
  406. return Array.from(labels)
  407. },
  408. fixedTableValueColumns() {
  409. const auditYears = this.auditPeriodYears
  410. const candidateLabels = this.fixedTableColumnCandidates
  411. const usedLabels = new Set()
  412. const resultColumns = []
  413. const extractYear = (label) => {
  414. if (!label) return ''
  415. const match = String(label).match(/(19|20)\d{2}/)
  416. return match ? match[0] : ''
  417. }
  418. const buildColumn = ({ key, label, placeholder, candidates }) => ({
  419. key,
  420. label,
  421. placeholder,
  422. candidates,
  423. })
  424. auditYears.forEach((year) => {
  425. const matchLabel =
  426. candidateLabels.find((label) => {
  427. const normalized = extractYear(label)
  428. return normalized && normalized === String(year)
  429. }) || ''
  430. const displayLabel = matchLabel || `${year}年`
  431. const columnKey = `year-${year}`
  432. usedLabels.add(matchLabel)
  433. resultColumns.push(
  434. buildColumn({
  435. key: columnKey,
  436. label: displayLabel,
  437. placeholder: `请输入${displayLabel}数据`,
  438. candidates: [
  439. matchLabel,
  440. displayLabel,
  441. `${year}年`,
  442. `${year}年度`,
  443. `${year}`,
  444. `year_${year}`,
  445. ].filter(Boolean),
  446. })
  447. )
  448. })
  449. candidateLabels.forEach((label) => {
  450. if (!label || usedLabels.has(label)) {
  451. return
  452. }
  453. if (
  454. ['序号', '项目', '指标编号', '计算公式', '单位', '备注'].includes(
  455. label
  456. )
  457. ) {
  458. return
  459. }
  460. resultColumns.push(
  461. buildColumn({
  462. key: `col-${label}`,
  463. label,
  464. placeholder: `请输入${label}`,
  465. candidates: [label],
  466. })
  467. )
  468. usedLabels.add(label)
  469. })
  470. return resultColumns
  471. },
  472. fixedTableDisplayData() {
  473. const rows =
  474. (this.contentEditForm &&
  475. this.contentEditForm.fixedTable &&
  476. this.contentEditForm.fixedTable.fixedTables) ||
  477. []
  478. if (!Array.isArray(rows) || rows.length === 0) {
  479. return []
  480. }
  481. const normalizeId = (val) => {
  482. if (val === undefined || val === null) return ''
  483. return String(val)
  484. }
  485. const parentMap = {}
  486. rows.forEach((row, index) => {
  487. const rowId = normalizeId(row.rowid || row.itemId || index)
  488. const parentId = row.parentid
  489. if (
  490. parentId !== undefined &&
  491. parentId !== null &&
  492. parentId !== '' &&
  493. parentId !== '-1' &&
  494. parentId !== -1
  495. ) {
  496. parentMap[rowId] = normalizeId(parentId)
  497. } else {
  498. parentMap[rowId] = ''
  499. }
  500. })
  501. const childMap = {}
  502. Object.keys(parentMap).forEach((rowId) => {
  503. const parentId = parentMap[rowId]
  504. if (parentId) {
  505. if (!childMap[parentId]) {
  506. childMap[parentId] = []
  507. }
  508. childMap[parentId].push(rowId)
  509. }
  510. })
  511. const computedLevels = {}
  512. const getLevel = (rowId, depth = 0) => {
  513. if (!rowId) return 0
  514. if (computedLevels[rowId] !== undefined) {
  515. return computedLevels[rowId]
  516. }
  517. if (depth > rows.length) {
  518. return 0
  519. }
  520. const parentId = parentMap[rowId]
  521. if (!parentId) {
  522. computedLevels[rowId] = 0
  523. return 0
  524. }
  525. const level = 1 + getLevel(parentId, depth + 1)
  526. computedLevels[rowId] = level
  527. return level
  528. }
  529. const columnDefs = this.fixedTableValueColumns || []
  530. return rows.map((row, index) => {
  531. const rowId = normalizeId(row.rowid || row.itemId || index)
  532. const fixedValues = row.fixedValues || {}
  533. const displaySeq =
  534. fixedValues['序号'] ||
  535. row.orderText ||
  536. row.orderNum ||
  537. row.seq ||
  538. ''
  539. const displayName =
  540. fixedValues['项目'] ||
  541. fixedValues['项目名称'] ||
  542. row.itemName ||
  543. row.projectName ||
  544. ''
  545. const displayUnit = fixedValues['单位'] || row.unit || ''
  546. const displayRemark = fixedValues['备注'] || row.remark || ''
  547. const hasChildren = !!childMap[rowId]
  548. const isChild =
  549. row.isChild ||
  550. row.isSubItem ||
  551. (row.parentid !== undefined &&
  552. row.parentid !== null &&
  553. row.parentid !== '' &&
  554. row.parentid !== '-1' &&
  555. row.parentid !== -1)
  556. const isCategory =
  557. row.isCategory === true || (!isChild && hasChildren)
  558. const displayValues = {}
  559. columnDefs.forEach((column) => {
  560. let value = ''
  561. const candidates = column.candidates || []
  562. for (let i = 0; i < candidates.length; i += 1) {
  563. const candidate = candidates[i]
  564. if (!candidate) continue
  565. if (
  566. fixedValues[candidate] !== undefined &&
  567. fixedValues[candidate] !== null &&
  568. fixedValues[candidate] !== ''
  569. ) {
  570. value = fixedValues[candidate]
  571. break
  572. }
  573. if (
  574. row[candidate] !== undefined &&
  575. row[candidate] !== null &&
  576. row[candidate] !== ''
  577. ) {
  578. value = row[candidate]
  579. break
  580. }
  581. const altKey = String(candidate).replace(/年|年度/g, '')
  582. if (
  583. altKey &&
  584. fixedValues[altKey] !== undefined &&
  585. fixedValues[altKey] !== null &&
  586. fixedValues[altKey] !== ''
  587. ) {
  588. value = fixedValues[altKey]
  589. break
  590. }
  591. }
  592. displayValues[column.key] = value
  593. })
  594. const remarkValue =
  595. fixedValues['备注'] !== undefined && fixedValues['备注'] !== null
  596. ? fixedValues['备注']
  597. : displayRemark
  598. return {
  599. ...row,
  600. _rowId: rowId,
  601. _displaySeq: displaySeq,
  602. _displayName: displayName,
  603. _displayUnit: displayUnit,
  604. _displayRemark: remarkValue,
  605. _hasChildren: hasChildren,
  606. _isCategory: isCategory,
  607. _indentLevel: getLevel(rowId),
  608. _displayValues: displayValues,
  609. }
  610. })
  611. },
  612. },
  613. watch: {
  614. // 监听弹窗可见状态变化
  615. dialogVisible: {
  616. handler(newVal) {
  617. if (newVal) {
  618. this.initForm()
  619. }
  620. },
  621. immediate: true,
  622. },
  623. // 监听表单数据变化
  624. formData: {
  625. handler(newVal) {
  626. // 只有在弹窗未打开时预初始化一次数据,避免弹窗打开后多次重复请求
  627. if (Object.keys(newVal).length > 0 && !this.dialogVisible) {
  628. this.initForm()
  629. }
  630. },
  631. deep: true,
  632. },
  633. },
  634. methods: {
  635. // 初始化表单
  636. initForm() {
  637. if (Object.keys(this.formData).length > 0) {
  638. this.contentEditForm = { ...this.contentEditForm, ...this.formData }
  639. this.getData()
  640. }
  641. },
  642. getData() {
  643. listByCurrentTemplateId({
  644. surveyTemplateId: this.contentEditForm.data.surveyId,
  645. }).then((res) => {
  646. // 根据模板类型解析并显示数据
  647. // if (this.contentEditForm.templateType === '1') {
  648. // if (res.value.fixedFields) {
  649. // console.log(this.contentEditForm)
  650. // this.contentEditForm.fixedTable.fixedTablesTitle =
  651. // this.stringToObjects(res.value.fixedFields || '')
  652. // this.contentEditForm.fixedTable.fixedTables = []
  653. // console.log(this.contentEditForm.fixedTable.fixedTablesTitle)
  654. // }
  655. // }
  656. if (
  657. this.contentEditForm.templateType === '1' ||
  658. this.contentEditForm.templateType === '2'
  659. ) {
  660. // 解析并显示固定表项目数据
  661. this.parseAndDisplayFixedTableData(res)
  662. } else if (this.contentEditForm.templateType === '3') {
  663. // 解析并显示动态表项目数据
  664. this.parseAndDisplayDynamicTableData(res)
  665. }
  666. })
  667. },
  668. /**
  669. * 解析并回显固定表、单记录项目数据
  670. * @param {Object} responseData - listByCurrentTemplateId接口返回的数据
  671. */
  672. parseAndDisplayFixedTableData(responseData) {
  673. if (responseData.value.fixedFields) {
  674. this.contentEditForm.fixedTable.fixedTablesTitle =
  675. this.stringToObjects(responseData.value.fixedFields || '')
  676. } else {
  677. let fixedFields = this.contentEditForm.fixedTable.tableHeaders
  678. .map((item) => item.fieldName)
  679. .join(',')
  680. this.contentEditForm.fixedTable.fixedTablesTitle =
  681. this.stringToObjects(fixedFields || '')
  682. }
  683. const fixedTitles = this.contentEditForm.fixedTable.fixedTablesTitle
  684. this.contentEditForm.fixedTable.fixedTableHeaders = fixedTitles.filter(
  685. (title) => title.rkey !== '序号'
  686. )
  687. if (
  688. !responseData ||
  689. !responseData.value ||
  690. !responseData.value.itemlist
  691. ) {
  692. return
  693. }
  694. const itemList = responseData.value.itemlist
  695. const allRows = []
  696. // 清空现有数据
  697. this.contentEditForm.fixedTable.fixedTables = []
  698. // 遍历itemList,为每个项目创建一行数据
  699. itemList.forEach((item, index) => {
  700. // 判断是否为子项(parentid不为-1且不为"-1")
  701. const isSubItem =
  702. item.parentid && item.parentid !== -1 && item.parentid !== '-1'
  703. const newRow = {
  704. orderText: item.orderNum || '', // 显示用序号
  705. orderNum: item.orderNum || '', // 保留原始序号用于发送后端
  706. surveyTemplateId: item.surveyTemplateId,
  707. versionId: item.versionId,
  708. cellCode: item.cellCode || '',
  709. calculationFormula: item.calculationFormula || '',
  710. unit: item.unit || '',
  711. fixedValues: {},
  712. itemId: item.id || null,
  713. parentid: item.parentid || -1,
  714. isChild: isSubItem,
  715. isSubItem: isSubItem,
  716. rowid: item.rowid || this.generateUUID(),
  717. jsonstr: item.jsonstr || null,
  718. }
  719. // 确保orderNum是数字类型
  720. if (item.orderNum) {
  721. newRow.orderNum = parseInt(item.orderNum, 10) || 0
  722. }
  723. // 初始化fixedValues并填充实际值
  724. fixedTitles.forEach((title) => {
  725. newRow.fixedValues[title.rkey] = item[title.rkey] || ''
  726. })
  727. allRows.push(newRow)
  728. })
  729. // 按父子关系排序:父项在前,子项在对应的父项后面
  730. allRows.sort((a, b) => {
  731. // 先按orderNum排序父项
  732. if (a.isChild && !b.isChild) {
  733. // 如果a是子项,b是父项,需要检查a的父项是否在b之后
  734. const parentOfA = allRows.find(
  735. (item) => item.rowid === a.parentid || item.itemId === a.parentid
  736. )
  737. if (parentOfA && parentOfA.orderNum > b.orderNum) {
  738. return 1
  739. }
  740. return -1
  741. }
  742. if (!a.isChild && b.isChild) {
  743. // 如果a是父项,b是子项,需要检查b的父项是否在a之前
  744. const parentOfB = allRows.find(
  745. (item) => item.rowid === b.parentid || item.itemId === b.parentid
  746. )
  747. if (parentOfB && parentOfB.orderNum < a.orderNum) {
  748. return -1
  749. }
  750. return 1
  751. }
  752. // 如果都是父项或都是子项,按orderNum排序
  753. return (a.orderNum || 0) - (b.orderNum || 0)
  754. })
  755. // 重新排序,确保子项紧跟在其对应的父项后面
  756. const sortedArray = []
  757. const parentItems = allRows.filter((item) => !item.isChild)
  758. const childItems = allRows.filter((item) => item.isChild)
  759. parentItems.forEach((parent) => {
  760. sortedArray.push(parent)
  761. // 找到并添加该父项的所有子项
  762. const relatedChildren = childItems.filter(
  763. (child) =>
  764. child.parentid === parent.rowid ||
  765. child.parentid === parent.itemId
  766. )
  767. // 子项按orderNum排序
  768. relatedChildren.sort((a, b) => (a.orderNum || 0) - (b.orderNum || 0))
  769. sortedArray.push(...relatedChildren)
  770. })
  771. this.contentEditForm.fixedTable.fixedTables = sortedArray
  772. },
  773. getFixedPreviewRowClass({ row }) {
  774. if (!row) return ''
  775. return row._isCategory ? 'category-row' : ''
  776. },
  777. /**
  778. * 解析并回显动态表项目数据
  779. * @param {Object} responseData - listByCurrentTemplateId接口返回的数据
  780. */
  781. parseAndDisplayDynamicTableData(responseData) {
  782. // 确保数据初始化
  783. if (!this.contentEditForm.dynamicTable) {
  784. this.contentEditForm.dynamicTable = {
  785. tableHeaders: [],
  786. dynamicTables: [],
  787. dynamicTablesTitle: [],
  788. dynamicTableHeaders: [],
  789. }
  790. }
  791. // 使用API返回的字段配置(如果有)
  792. if (responseData.value.fixedFields) {
  793. this.contentEditForm.dynamicTable.dynamicTablesTitle =
  794. this.stringToObjects(responseData.value.fixedFields || '')
  795. } else if (
  796. this.contentEditForm.dynamicTable.tableHeaders &&
  797. this.contentEditForm.dynamicTable.tableHeaders.length > 0
  798. ) {
  799. let fixedFields = this.contentEditForm.dynamicTable.tableHeaders
  800. .map((item) => item.fieldName || '')
  801. .filter(Boolean)
  802. .join(',')
  803. if (fixedFields) {
  804. this.contentEditForm.dynamicTable.dynamicTablesTitle =
  805. this.stringToObjects(fixedFields || '')
  806. }
  807. }
  808. const dynamicTitles =
  809. this.contentEditForm.dynamicTable.dynamicTablesTitle
  810. this.contentEditForm.dynamicTable.dynamicTableHeaders =
  811. dynamicTitles.filter((title) => title.rkey !== '序号')
  812. if (
  813. !responseData ||
  814. !responseData.value ||
  815. !responseData.value.itemlist
  816. ) {
  817. return
  818. }
  819. const itemList = responseData.value.itemlist
  820. const allRows = []
  821. // 清空现有数据
  822. this.contentEditForm.dynamicTable.dynamicTables = []
  823. // 遍历itemList,为每个项目创建一行数据
  824. itemList.forEach((item, index) => {
  825. // 判断是否为子项(parentid不为-1且不为"-1")
  826. const isSubItem =
  827. item.parentid && item.parentid !== -1 && item.parentid !== '-1'
  828. const newRow = {
  829. orderText: item.orderNum || '', // 显示用序号
  830. orderNum: item.orderNum || '', // 保留原始序号用于发送后端
  831. surveyTemplateId: item.surveyTemplateId,
  832. dynamicValues: {},
  833. itemId: item.id || null,
  834. parentid: item.parentid || -1,
  835. isChild: isSubItem,
  836. isSubItem: isSubItem,
  837. rowid: item.rowid || this.generateUUID(),
  838. jsonstr: item.jsonstr || null,
  839. }
  840. // 确保orderNum是数字类型
  841. if (item.orderNum) {
  842. newRow.orderNum = parseInt(item.orderNum, 10) || 0
  843. }
  844. // 初始化dynamicValues
  845. newRow.dynamicValues = {}
  846. dynamicTitles.forEach((title) => {
  847. // 特殊处理序号字段
  848. if (title.rkey === '序号') {
  849. // 优先使用item中的序号值,如果没有则使用orderNum或index+1
  850. newRow.dynamicValues[title.rkey] =
  851. item[title.rkey] || item.orderNum || index + 1
  852. }
  853. // 排除其他序号相关字段,避免重复显示
  854. else if (title.rkey !== 'ordernum' && title.rkey !== 'orderText') {
  855. newRow.dynamicValues[title.rkey] = item[title.rkey] || ''
  856. }
  857. })
  858. allRows.push(newRow)
  859. })
  860. // 按父子关系排序:父项在前,子项在对应的父项后面
  861. // 1. 先创建一个映射表,方便通过ID查找父项
  862. const rowMap = new Map()
  863. const sortedArray = []
  864. const addedItems = new Set()
  865. // 深拷贝原始数据并过滤掉重复项
  866. const uniqueRows = allRows.reduce((acc, current) => {
  867. const isDuplicate = acc.some(
  868. (row) =>
  869. (row.rowid && current.rowid && row.rowid === current.rowid) ||
  870. (row.itemId && current.itemId && row.itemId === current.itemId)
  871. )
  872. if (!isDuplicate) {
  873. acc.push(JSON.parse(JSON.stringify(current)))
  874. }
  875. return acc
  876. }, [])
  877. // 填充映射表
  878. uniqueRows.forEach((row) => {
  879. if (row.rowid) rowMap.set(String(row.rowid), row)
  880. if (row.itemId) rowMap.set(String(row.itemId), row)
  881. })
  882. // 先按orderNum排序所有父项
  883. const parentItems = uniqueRows.filter((item) => !item.isChild)
  884. parentItems.sort(
  885. (a, b) => (Number(a.orderNum) || 0) - (Number(b.orderNum) || 0)
  886. )
  887. // 递归添加父项及其子项的函数
  888. const addItemWithChildren = (item) => {
  889. // 确保项目ID唯一
  890. const itemIdKey = String(
  891. item.rowid || item.itemId || `temp_${Math.random()}`
  892. )
  893. // 如果已经添加过,则跳过
  894. if (addedItems.has(itemIdKey)) return
  895. // 添加当前项目
  896. sortedArray.push(item)
  897. addedItems.add(itemIdKey)
  898. // 找到并添加所有子项
  899. const children = uniqueRows.filter((child) => {
  900. if (child.isChild) {
  901. const childParentId = String(child.parentid)
  902. return (
  903. childParentId === String(item.rowid) ||
  904. childParentId === String(item.itemId)
  905. )
  906. }
  907. return false
  908. })
  909. // 子项按orderNum排序
  910. children.sort(
  911. (a, b) => (Number(a.orderNum) || 0) - (Number(b.orderNum) || 0)
  912. )
  913. // 添加所有子项
  914. children.forEach((child) => addItemWithChildren(child))
  915. }
  916. // 遍历所有父项,添加父项及其子项
  917. parentItems.forEach((parent) => addItemWithChildren(parent))
  918. // 添加剩余的没有被添加的项目(应该很少,但作为兜底)
  919. uniqueRows.forEach((row) => {
  920. const itemIdKey = String(
  921. row.rowid || row.itemId || `temp_${Math.random()}`
  922. )
  923. if (!addedItems.has(itemIdKey)) {
  924. sortedArray.push(row)
  925. addedItems.add(itemIdKey)
  926. }
  927. })
  928. // 最后,重新计算显示序号,确保序号连续且正确
  929. sortedArray.forEach((item, index) => {
  930. item.orderText = String(index + 1)
  931. })
  932. this.contentEditForm.dynamicTable.dynamicTables = sortedArray
  933. },
  934. // 字符串转对象数组
  935. stringToObjects(str) {
  936. if (!str || typeof str !== 'string') return []
  937. const result = str
  938. .split(',')
  939. .map((item) => {
  940. const trimmed = item.trim()
  941. return {
  942. rkey: trimmed,
  943. rvalue: trimmed,
  944. }
  945. })
  946. .filter((item) => item.rkey)
  947. return result
  948. },
  949. // 生成UUID用于临时行ID
  950. generateUUID() {
  951. return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(
  952. /[xy]/g,
  953. function (c) {
  954. const r = (Math.random() * 16) | 0
  955. const v = c === 'x' ? r : (r & 0x3) | 0x8
  956. return v.toString(16)
  957. }
  958. )
  959. },
  960. // 处理选择变化
  961. handleSelectionChange(val) {
  962. this.$emit('selection-change', val)
  963. },
  964. // 获取格式占位符
  965. getFormatPlaceholder(fieldType) {
  966. switch (fieldType) {
  967. case '布尔值':
  968. return '请选择布尔值'
  969. case '下拉选择':
  970. return '请选择选项'
  971. default:
  972. return '请输入格式'
  973. }
  974. },
  975. // 处理取消
  976. handleCancel() {
  977. this.$emit('cancel')
  978. },
  979. },
  980. }
  981. </script>
  982. <style scoped lang="scss">
  983. @import '@/styles/costAudit.scss';
  984. .survey-dialog-container {
  985. .content-edit-container {
  986. .table-header-info {
  987. display: flex;
  988. justify-content: space-between;
  989. margin-bottom: 20px;
  990. padding: 10px;
  991. background-color: #f5f7fa;
  992. border-radius: 4px;
  993. .table-name {
  994. font-weight: bold;
  995. color: #303133;
  996. }
  997. .table-style {
  998. .el-radio-group {
  999. margin-left: 10px;
  1000. }
  1001. }
  1002. }
  1003. .table-edit-container {
  1004. margin-top: 20px;
  1005. .format-input {
  1006. display: flex;
  1007. align-items: center;
  1008. .format-prefix {
  1009. margin-right: 5px;
  1010. color: #606266;
  1011. }
  1012. }
  1013. .bind-dict-column {
  1014. display: flex;
  1015. flex-direction: column;
  1016. .dict-select {
  1017. margin-left: 0 !important;
  1018. }
  1019. }
  1020. }
  1021. ::v-deep .category-row {
  1022. background-color: #f5f7fa !important;
  1023. td {
  1024. background-color: #f5f7fa !important;
  1025. font-weight: 600;
  1026. }
  1027. .category-name {
  1028. color: #409eff;
  1029. font-weight: 600;
  1030. }
  1031. }
  1032. .table-item-text {
  1033. display: inline-block;
  1034. width: 100%;
  1035. color: #303133;
  1036. }
  1037. }
  1038. }
  1039. </style>