华西海圻ELN前端工程
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1469 lines
48 KiB

  1. <template>
  2. <div>
  3. <div class="custom-table-wrapper" :class="{ 'no-border': !isBorder }">
  4. <div class="custom-table-header no-break" v-if="isBorder">
  5. <div class="custom-table-row">
  6. <div v-if="showSort" class="custom-table-cell header-cell sort-cell">
  7. 序号
  8. </div>
  9. <div class="custom-table-cell header-cell c-cell" v-if="showCheckAll">
  10. <div class="checkbox-item">
  11. <el-checkbox v-model="checkAll" :indeterminate="isIndeterminate"
  12. @change="handleCheckAllChange"></el-checkbox>
  13. </div>
  14. </div>
  15. <div v-for="(col, colIndex) in columns" :key="colIndex"
  16. class="custom-table-cell header-cell no-break" :style="getCellWidth(col)">
  17. <div class="header-cell-content" v-if="col.headerColumns && col.headerColumns.length > 0">
  18. <div class="header-columns-grid"
  19. :style="{ 'grid-template-columns': `repeat(${col.span || 2}, 1fr)` }">
  20. <div v-for="(headerCol, headerIndex) in col.headerColumns" :key="headerIndex"
  21. class="header-column-item">
  22. <template v-if="headerCol.type === 'span'">
  23. <div class="span-content">{{ $t(headerCol.label) }}</div>
  24. </template>
  25. <template v-else-if="isRegent(headerCol)">
  26. <HandleFormItem
  27. :fieldKey="prefixKey + colIndex + '_' + headerCol.key + '_' + headerIndex"
  28. :fieldItemLabel="fieldItemLabel" :type="headerCol.type"
  29. class="body-clickable" sourceFrom="customTable"
  30. :item="getHeaderColumnItem(headerCol)"
  31. :value="headerFields[`${colIndex}_${headerIndex}`]"
  32. :error="hasHeaderError(colIndex, headerIndex, headerCol.key)"
  33. @update:error="onHeaderColumnErrorUpdate(colIndex, headerIndex, headerCol.key, $event)"
  34. :orange-bg="regentIsExpired(headerCol.key,rowIndex)"
  35. @onRegentSubmit="(data, inputValue) => onHeaderRegentSubmit(data, inputValue, colIndex, headerIndex)" />
  36. </template>
  37. <template
  38. v-else-if="headerCol.type === 'input' || headerCol.type === 'select' || headerCol.type === 'inputNumber'">
  39. <HandleFormItem
  40. :fieldKey="prefixKey + '_header_' + colIndex + '_' + headerIndex"
  41. :fieldItemLabel="fieldItemLabel" :type="headerCol.type"
  42. :item="getHeaderColumnItem(headerCol)"
  43. v-model="headerFields[`${colIndex}_${headerIndex}`]"
  44. @change="onHeaderColumnChange(colIndex, headerIndex, headerCol, $event)"
  45. :error="hasHeaderError(colIndex, headerIndex, headerCol.key)"
  46. @update:error="onHeaderColumnErrorUpdate(colIndex, headerIndex, headerCol.key, $event)" />
  47. </template>
  48. </div>
  49. </div>
  50. </div>
  51. <div class="header-cell-content" v-else>
  52. <div>{{ $t(col.label) }}</div>
  53. <template
  54. v-if="col.headerSelectKey && col.headerOptions && (showHeaderSelect || templateFillType === 'preFill')">
  55. <HandleFormItem :fieldKey="prefixKey + '_' + col.headerSelectKey"
  56. :fieldItemLabel="fieldItemLabel" type="select" class="header-select"
  57. :item="getHeaderItem(col)" v-model="headerSelectFields[col.headerSelectKey]"
  58. @change="onHeaderSelectChange(col, $event)"
  59. :error="hasError(-1, colIndex, col.headerSelectKey)"
  60. @update:error="onErrorUpdate(-1, colIndex, col.headerSelectKey, $event)" />
  61. </template>
  62. <div v-else-if="headerSelectFields[col.headerSelectKey]" class="fill-type-icon"
  63. :style="{ width: (templateFillType !== 'actFill') ? '60px' : 'auto' }">({{
  64. headerSelectFields[col.headerSelectKey] }})</div>
  65. </div>
  66. </div>
  67. <!-- 默认操作栏 -->
  68. <div class="custom-table-cell header-cell" :style="{ width: operationWidth }" v-if="showOperation">
  69. <div class="header-cell-content">
  70. <div>操作</div>
  71. </div>
  72. </div>
  73. </div>
  74. </div>
  75. <div class="custom-table-body">
  76. <div v-for="(row, rowIndex) in localDataSource" :key="rowIndex" class="custometable-row no-break">
  77. <div v-if="showSort" class="custom-table-cell body-cell sort-cell">
  78. {{ rowIndex + 1 }}
  79. </div>
  80. <div class="custom-table-cell body-cell c-cell" v-if="showCheckAll">
  81. <div class="checkbox-item">
  82. <el-checkbox v-model="row._checked" @change="handleCheckChange(row, $event)"></el-checkbox>
  83. </div>
  84. </div>
  85. <div v-for="(col, colIndex) in columns" :key="colIndex"
  86. class="custom-table-cell body-cell no-break" :style="getCellWidth(col)">
  87. <div class="inner-table-cell">
  88. <div class="flex1" :class="{ 'item-center': !isBorder && col.label }">
  89. <div v-if="!isBorder && col.label" class="mr-5">
  90. {{ $t(col.label) }}
  91. </div>
  92. <template
  93. v-if="col.bodyType === 'input' || col.bodyType === 'inputNumber' || col.bodyType === 'select' || col.bodyType === 'dateTimeRange' || col.bodyType === 'radio'">
  94. <div class="flex flex1">
  95. <HandleFormItem :fieldKey="prefixKey + '_' + col.prop + '_' + row.id"
  96. :fieldItemLabel="fieldItemLabel" :type="col.bodyType"
  97. @blur="onBlur(rowIndex, col.prop, $event)" @copy="onCopy(rowIndex, col)"
  98. class="body-input" :item="getBodyItem(col, rowIndex)"
  99. v-model="row[col.prop]" :ref="col.prop + rowIndex"
  100. @change="onBodyValueChange(rowIndex, colIndex, $event, row, col.bodyType)"
  101. :error="hasError(rowIndex, colIndex, col.prop)"
  102. @update:error="onErrorUpdate(rowIndex, colIndex, col.prop, $event)"
  103. @beforeSaveRecord="(data, callback) => beforeSaveRecord(data, callback, rowIndex, col, row)"
  104. :orange-bg="hasOrangeBg(rowIndex, colIndex, col.prop)" />
  105. </div>
  106. </template>
  107. <div v-else-if="col.bodyType === 'checkboxTree'">
  108. <HandleFormItem :field-item-label="fieldItemLabel"
  109. :field-key="prefixKey + '_' + col.prop + rowIndex" type="checkboxTree"
  110. :item="getBodyItem(col, rowIndex)" :value="row[col.prop]"
  111. @change="(e) => onBodyValueChange(rowIndex, colIndex, e, row, col.bodyType)"
  112. :error="hasError(rowIndex, colIndex, col.prop)"
  113. @update:error="onErrorUpdate(rowIndex, colIndex, col.prop, $event)"
  114. :orange-bg="hasOrangeBg(rowIndex, colIndex, col.prop)" />
  115. </div>
  116. <div v-else-if="col.bodyType === 'operableInput'" class="flex flex1">
  117. <div class="flex1 grid-container">
  118. <div class="flex"
  119. :class="{ 'full-row': row[col.prop] && row[col.prop].length == 1 }"
  120. v-for="(opItem, itemIndex) in row[col.prop]" :key="itemIndex">
  121. <HandleFormItem
  122. :fieldKey="prefixKey + '_' + col.prop + '_' + row.id + '_' + itemIndex"
  123. :fieldItemLabel="fieldItemLabel" type="input"
  124. @blur="onOperableInputBlur(opItem, $event)" class="body-input"
  125. :item="getBodyItem(col, rowIndex)" :value="opItem.value"
  126. :error="hasError(rowIndex, colIndex, rowIndex + col.prop + itemIndex)"
  127. @update:error="onErrorUpdate(rowIndex, colIndex, rowIndex + col.prop + itemIndex, $event)"
  128. :orange-bg="hasOrangeBg(rowIndex, colIndex, rowIndex + col.prop + itemIndex)" />
  129. <el-popconfirm confirm-button-text='确认' cancel-button-text='取消'
  130. icon="el-icon-info" icon-color="red" title="确认删除当前输入框?"
  131. @confirm="removeOperableInput(rowIndex, colIndex, col.prop, itemIndex)">
  132. <i slot="reference" class="el-icon-remove-outline remove-icon"
  133. v-if="itemIndex > 0 && templateFillType === 'actFill' && !row.isComplete"></i>
  134. </el-popconfirm>
  135. </div>
  136. </div>
  137. <i class="el-icon-circle-plus add-icon"
  138. v-if="templateFillType === 'actFill' && !row.isComplete"
  139. @click="addOperableInput(rowIndex, colIndex, col.prop)"></i>
  140. </div>
  141. <div class="flex flex1" v-else-if="col.bodyType === 'clickable'">
  142. <HandleFormItem :fieldKey="prefixKey + '_' + col.prop + '_' + row.id"
  143. :fieldItemLabel="fieldItemLabel" type="clickable" class="body-clickable"
  144. :item="getBodyItem(col, rowIndex)" :value="row[col.prop]"
  145. :error="hasError(rowIndex, colIndex, col.prop)"
  146. @clickable="handleClickable(col, rowIndex, colIndex, row)"
  147. @resetRecord="resetRecord(rowIndex, colIndex, col.prop)"
  148. @update:error="onErrorUpdate(rowIndex, colIndex, col.prop, $event)"
  149. :orange-bg="hasOrangeBg(rowIndex, colIndex, col.prop)" />
  150. </div>
  151. <div class="flex flex1" v-else-if="isRegent(col, 'bodyType')">
  152. <HandleFormItem :fieldKey="prefixKey + '_' + col.prop + '_' + row.id"
  153. :fieldItemLabel="fieldItemLabel" :type="col.bodyType" class="body-clickable"
  154. sourceFrom="customTable" :item="getBodyItem(col, rowIndex)"
  155. :value="row[col.prop]" :error="hasError(rowIndex, colIndex, col.prop)"
  156. @onRegentSubmit="(data, inputValue) => onRegentSubmit(data, inputValue, col, rowIndex, colIndex, row, col.prop)"
  157. @beforeReagentSubmit="(data, callback) => onBeforeReagentSubmit(data, callback, col, row)"
  158. @update:error="onErrorUpdate(rowIndex, colIndex, col.prop, $event)"
  159. :orange-bg="regentIsExpired(col.prop,rowIndex)" />
  160. </div>
  161. <template v-else-if="col.bodyType === 'span'">
  162. <div class="body-span">
  163. {{ row[col.prop] }}
  164. </div>
  165. </template>
  166. <template v-else-if="col.bodyType === 'checkboxTag'">
  167. <div class="flex flex-wrap"
  168. :class="{ 'row-error-border': hasError(rowIndex, colIndex, col.prop) }">
  169. <HandleFormItem :fieldKey="prefixKey + '_' + col.prop + '_' + row.id"
  170. :fieldItemLabel="fieldItemLabel" type="checkboxTag" :value="row[col.prop]"
  171. :item="getBodyItem(col, rowIndex)"
  172. @change="onCheckboxTagChange(rowIndex, colIndex, col, $event)"
  173. @deleteTag="onDeleteCheckboxTag(rowIndex, col, $event)"
  174. :error="hasError(rowIndex, colIndex, col.prop)"
  175. @update:error="onErrorUpdate(rowIndex, colIndex, col.prop, $event)" />
  176. </div>
  177. </template>
  178. <template v-else-if="col.bodyType === 'checkbox'">
  179. <HandleFormItem :fieldKey="prefixKey + '_' + col.prop + '_' + row.id"
  180. :fieldItemLabel="fieldItemLabel" type="checkbox" v-model="row[col.prop]"
  181. :item="getBodyItem(col, rowIndex)"
  182. @change="onCheckboxChange(rowIndex, colIndex, col, $event)"
  183. :error="hasError(rowIndex, colIndex, col.prop)"
  184. @update:error="onErrorUpdate(rowIndex, colIndex, col.prop, $event)" />
  185. </template>
  186. </div>
  187. <div v-show="isShowOther(row[col.prop], col)" class="flex flex1">
  188. <div class="other-title">{{ col.otherLabel ? $t(col.otherLabel) :
  189. $t("template.common.other") }}
  190. </div>
  191. <div class="flex flex1">
  192. <HandleFormItem :field-item-label="fieldItemLabel"
  193. :field-key="prefixKey + '_' + col.otherCode"
  194. @blur="onBlur(rowIndex, col.prop, $event)" :item="getOtherItem(col)"
  195. v-model="row[col.otherCode]"
  196. :error="hasError(rowIndex, colIndex, col.otherCode)"
  197. @update:error="onErrorUpdate(rowIndex, colIndex, col.otherCode, $event)"
  198. :orange-bg="hasOrangeBg(rowIndex, colIndex, col.otherCode)" />
  199. </div>
  200. </div>
  201. <div class="m-l-5 flex"
  202. :class="{ 'flex1': (col.bodySubType !== 'button' && col.bodySubType !== 'span') }"
  203. v-if="isShowBodySub(col, row)">
  204. <template
  205. v-if="col.bodySubType === 'inputNumber' || col.bodySubType === 'input' || col.bodySubType === 'select'">
  206. <HandleFormItem :fieldKey="prefixKey + '_' + col.bodySubKey + '_' + row.id"
  207. :fieldItemLabel="fieldItemLabel" :type="col.bodySubType"
  208. @blur="onSubBlur(rowIndex, col.bodySubKey, $event)"
  209. @copy="onCopy(rowIndex, col)" :item="getBodySubItem(col)"
  210. v-model="row[col.bodySubKey]"
  211. @change="onBodySubValueChange(rowIndex, colIndex, $event, row, col.bodySubType)"
  212. :error="hasError(rowIndex, colIndex, col.bodySubKey)"
  213. @update:error="onErrorUpdate(rowIndex, colIndex, col.bodySubKey, $event)"
  214. :orange-bg="hasOrangeBg(rowIndex, colIndex, col.bodySubKey)" />
  215. </template>
  216. <template v-else-if="col.bodySubType === 'span'">
  217. <div class="body-span">
  218. {{ row[col.bodySubKey] }}
  219. </div>
  220. </template>
  221. <template v-else-if="col.bodySubType === 'button'">
  222. <HandleFormItem class="ml-10" type="button" :item="getBodyButtonItem(col, rowIndex)"
  223. :value="row[col.bodySubKey]"
  224. :fieldKey="prefixKey + '_' + col.bodySubKey + '_' + row.id"
  225. @clickButton="(e, val, data) => handleClickButton(e, data, col.bodySubKey, rowIndex, colIndex)" />
  226. </template>
  227. <div class="flex flex1" v-else-if="isRegent(col, 'bodySubType')">
  228. <HandleFormItem :fieldKey="prefixKey + '_' + col.bodySubKey + '_' + row.id"
  229. :fieldItemLabel="fieldItemLabel" :type="col.bodySubType" class="body-clickable"
  230. sourceFrom="customTable" :item="getBodySubItem(col, rowIndex)"
  231. :value="row[col.bodySubKey]"
  232. :error="hasError(rowIndex, colIndex, col.bodySubKey)"
  233. @onRegentSubmit="(data, inputValue) => onRegentSubmit(data, inputValue, col, rowIndex, colIndex, row, col.bodySubKey)"
  234. @beforeReagentSubmit="(data, callback) => onBeforeReagentSubmit(data, callback, col, row)"
  235. @update:error="onErrorUpdate(rowIndex, colIndex, col.bodySubKey, $event)"
  236. :orange-bg="regentIsExpired(col.bodySubKey,rowIndex)" />
  237. </div>
  238. <template v-if="col.bodyThirdType === 'button'">
  239. <HandleFormItem class="ml-10" type="button"
  240. :item="getBodyThirdButtonItem(col, rowIndex)" :value="row[col.bodyThirdKey]"
  241. :fieldKey="prefixKey + '_' + col.bodyThirdKey + '_' + row.id"
  242. @clickButton="(e, val, data) => handleClickButton(e, data, col.bodyThirdKey, rowIndex, colIndex)" />
  243. </template>
  244. </div>
  245. </div>
  246. </div>
  247. <!-- 默认操作栏 -->
  248. <div class="custom-table-cell body-cell" :style="{ width: isBorder ? operationWidth : 'auto' }"
  249. v-if="showOperation">
  250. <div class="inner-table-cell">
  251. <slot name="operation" :row="row" :rowIndex="rowIndex" :columns="getOperationColumns()">
  252. </slot>
  253. </div>
  254. </div>
  255. </div>
  256. </div>
  257. <div v-if="localDataSource.length == 0">
  258. <div class="no-data">暂无数据</div>
  259. </div>
  260. </div>
  261. <div class="add-row" v-if="isShowAddRos()">
  262. <el-button type="primary" plain @click="onAddRow">添加行</el-button>
  263. </div>
  264. </div>
  265. </template>
  266. <script>
  267. import HandleFormItem from "./HandleFormItem.vue";
  268. import { isEqual } from "@/utils/index.js";
  269. import { isShowOther } from "@/utils/formPackageCommon.js";
  270. import { EventBus } from "@/utils/eventBus";
  271. import { getuuid, justUpdateFilledFormData } from "@/utils/index.js";
  272. import { isRegent } from "@/utils/index.js";
  273. import { isValueEmpty } from '@/utils/index.js';
  274. import moment from 'moment';
  275. import _ from "lodash";
  276. export default {
  277. inject: ['templateFillType', 'getZdxgjl', 'updateZdxgjl'],
  278. name: 'CustomTable',
  279. components: {
  280. HandleFormItem
  281. },
  282. props: {
  283. operationWidth: {
  284. type: String,
  285. default: '245px',
  286. },
  287. // 是否显示表头选择器
  288. showHeaderSelect: {
  289. type: Boolean,
  290. default: false,
  291. },
  292. showAddRow: {
  293. type: Boolean,
  294. default: undefined,
  295. },
  296. // 是否显示操作栏
  297. showOperation: {
  298. type: Boolean,
  299. default: true,
  300. },
  301. columns: {
  302. type: Array,
  303. required: true,
  304. // 示例格式:
  305. // [
  306. // { label: '姓名', prop: 'name' },
  307. // { label: '状态', prop: 'status', type: 'select', options: [{value:1,label:'启用'},...], selected: null }
  308. // ]
  309. },
  310. formData: {
  311. type: Object,
  312. default: () => {
  313. return {
  314. stepTableFormData: [],
  315. headerSelectFields: {},
  316. }
  317. }
  318. },
  319. fieldItemLabel: {
  320. type: String,
  321. default: '',
  322. },
  323. //循环组件的情况下需要用这个来区分字段
  324. prefixKey: {
  325. type: String,
  326. default: "",
  327. },
  328. isBorder: {//是否无边框,无边框的没有表头和border
  329. type: Boolean,
  330. default: true,
  331. },
  332. // 是否显示全选
  333. showCheckAll: {
  334. type: Boolean,
  335. default: false,
  336. },
  337. // 是否显示排序
  338. showSort: {
  339. type: Boolean,
  340. default: false,
  341. },
  342. },
  343. data() {
  344. return {
  345. localDataSource: [],
  346. headerSelectFields: {},
  347. headerFields: {}, // 存储 headerColumns 的数据
  348. formErrors: [], // 表单错误状态管理
  349. orangeBgCells: {}, // 存储需要橙色背景的单元格 {rowIndex-colIndex: true/false}
  350. isShowOther,
  351. oldLocalDataSource: [],
  352. uuid: getuuid(),
  353. isRegent,
  354. selectedRows: [], // 存储选中的行
  355. isIndeterminate: false, // 半选状态
  356. checkAll: false, // 全选状态
  357. }
  358. },
  359. watch: {
  360. formData: {
  361. immediate: true,
  362. handler(newData) {
  363. const { stepTableFormData = [], headerSelectFields = {}, headerFields = {} } = newData;
  364. this.updateDataSource(stepTableFormData);
  365. this.headerSelectFields = JSON.parse(JSON.stringify(headerSelectFields));
  366. this.headerFields = JSON.parse(JSON.stringify(headerFields));
  367. // 在数据加载后检查 compareTo 逻辑
  368. this.checkCompareToOnDataLoad();
  369. }
  370. },
  371. localDataSource: {
  372. immediate: true,
  373. deep: true,
  374. handler(newVal, oldVal) {
  375. // if(newVal.length == 0){
  376. // return
  377. // }
  378. // this.localDataSource = [...newVal];
  379. }
  380. }
  381. },
  382. mounted() {
  383. },
  384. unmounted() {
  385. this.oldLocalDataSource = [];
  386. },
  387. methods: {
  388. // 删除operableInput
  389. removeOperableInput(rowIndex, colIndex, prop, itemIndex) {
  390. this.localDataSource[rowIndex][prop].splice(itemIndex, 1);
  391. justUpdateFilledFormData();
  392. },
  393. // 添加operableInput
  394. addOperableInput(rowIndex, colIndex, prop) {
  395. this.localDataSource[rowIndex][prop].push({ value: undefined });
  396. justUpdateFilledFormData();
  397. },
  398. onOperableInputBlur(opItem, e) {
  399. opItem.value = e;
  400. },
  401. getHeaderColumnItem(headerCol) {
  402. return {
  403. label: headerCol.label || '',
  404. fillType: headerCol.fillType,
  405. options: headerCol.options,
  406. maxlength: headerCol.maxlength,
  407. checkType: headerCol.checkType,
  408. regentFillType: headerCol.regentFillType,
  409. type: headerCol.type,
  410. };
  411. },
  412. onHeaderColumnChange(colIndex, headerIndex, headerCol, value) {
  413. const fieldKey = `${colIndex}_${headerIndex}`;
  414. this.headerFields[fieldKey] = value;
  415. this.$emit('headerColumnChange', {
  416. colIndex,
  417. headerIndex,
  418. key: fieldKey,
  419. value,
  420. headerFields: this.headerFields
  421. });
  422. },
  423. hasHeaderError(colIndex, headerIndex, key) {
  424. return this.formErrors.some(error =>
  425. error.rowIndex === -1 &&
  426. error.colIndex === colIndex &&
  427. error.headerIndex === headerIndex &&
  428. error.field === key
  429. );
  430. },
  431. onHeaderColumnErrorUpdate(colIndex, headerIndex, key, isError) {
  432. if (!isError) {
  433. this.formErrors = this.formErrors.filter(error =>
  434. !(error.rowIndex === -1 &&
  435. error.colIndex === colIndex &&
  436. error.headerIndex === headerIndex &&
  437. error.field === key)
  438. );
  439. }
  440. },
  441. // 删除checkboxTag
  442. onDeleteCheckboxTag(rowIndex, col, tagIndex) {
  443. this.localDataSource[rowIndex][col.prop].splice(tagIndex, 1);
  444. this.$emit("onDeleteTag", rowIndex, col, tagIndex);
  445. justUpdateFilledFormData();
  446. },
  447. onCheckboxTagChange(rowIndex, colIndex, col, value) {
  448. // value 现在是整个数组
  449. this.localDataSource[rowIndex][col.prop] = value;
  450. // 根据校验规则判断是否清除错误状态
  451. let isValid = false;
  452. if (this.templateFillType === "actFill") {
  453. // actFill时,检查是否有checked为true的项
  454. isValid = value && value.some(tag => tag.checked === true);
  455. } else if (this.templateFillType === "preFill") {
  456. // preFill时,检查所有tagValue是否不为空
  457. isValid = value && value.every(tag => tag.tagValue && (tag.tagValue + '').trim() !== '');
  458. }
  459. this.onErrorUpdate(rowIndex, colIndex, col.prop, !isValid);
  460. this.$emit("onCheckboxTagChange", rowIndex, col, value)
  461. },
  462. // checkbox变化
  463. onCheckboxChange(rowIndex, colIndex, col, value) {
  464. this.localDataSource[rowIndex][col.prop] = value;
  465. // 输入时清除对应表单项的错误状态
  466. this.formErrors = this.formErrors.filter(error =>
  467. !(error.rowIndex === rowIndex &&
  468. error.colIndex === colIndex &&
  469. error.field === col.prop)
  470. );
  471. this.$emit("onCheckboxChange", rowIndex, col, value);
  472. justUpdateFilledFormData();
  473. },
  474. handleClickButton(e, data, key, rowIndex, colIndex) {
  475. this.$emit("clickButton", key, rowIndex, colIndex, e, data,)
  476. },
  477. beforeSaveRecord(data, callback, rowIndex, col, row) {
  478. this.$emit("beforeSaveRecord", { inputData: data, callback, rowIndex, key: col.prop, rowData: row, dataSource: this.localDataSource })
  479. },
  480. getCellWidth(col) {
  481. const { templateFillType } = this;
  482. let width = col.width ? col.width + 'px' : 'auto';
  483. if (templateFillType !== "actFill" && templateFillType !== "preFill") {
  484. width = (col.showWidth) ? col.showWidth + 'px' : (col.width ? col.width + 'px' : 'auto')
  485. }
  486. return { width }
  487. },
  488. //取消按钮 重置记录
  489. resetRecord(rowIndex, colIndex,) {
  490. if (this.localDataSource.length) {
  491. this.localDataSource = [...this.oldLocalDataSource];
  492. this.oldLocalDataSource = [];
  493. }
  494. },
  495. //获取操作栏的列
  496. getOperationColumns() {
  497. return { columnsData: this.columns, headerSelectFields: this.headerSelectFields, fieldItemLabel: this.fieldItemLabel }
  498. },
  499. //获取其他下拉框的配制
  500. getOtherItem(sItem) {
  501. return {
  502. label: sItem.otherLabel ? this.$t(sItem.otherLabel) : this.$t("template.common.other"),
  503. fillType: sItem.bodyFillType,
  504. maxlength: sItem.otherMaxlength || 50,
  505. parentLabel: sItem.label,
  506. type: "input"
  507. }
  508. },
  509. isShowBodySub(col, row) {
  510. if (col.hasOwnProperty("showBodySub")) {
  511. return col.showBodySub
  512. } else if (col.bodySubType === 'span' && !row[col.bodySubKey]) {//如果是span没有值的话就隐藏
  513. return false;
  514. }
  515. return col.bodySubType && col.bodySubKey;
  516. },
  517. // 点击事件
  518. handleClickable(col, rowIndex, colIndex, row) {
  519. // if (this.templateFillType !== 'actFill') {
  520. // return
  521. // }
  522. this.$emit("clickable", col, rowIndex, row)
  523. },
  524. onBeforeReagentSubmit(data, callback, col, row) {
  525. if (this.templateFillType !== 'actFill') {
  526. return
  527. }
  528. this.$emit("beforeReagentSubmit", { selectData: data, callback, key: col.prop, rowData: row })
  529. },
  530. onHeaderRegentSubmit(data, inputValue, colIndex, headerIndex) {
  531. this.headerFields[`${colIndex}_${headerIndex}`] = inputValue;
  532. this.$emit("onHeaderRegentSubmit", { selectInfo: data, headerIndex, colIndex, headerFields: this.headerFields })
  533. },
  534. //是否过期
  535. regentIsExpired(key,rowIndex) {
  536. const item = this.localDataSource[rowIndex];
  537. const { yxq, sxrq } = item[`selectInfo_${key}`] || {};
  538. const rq = sxrq || yxq;
  539. if (rq) {
  540. const time = moment(yxq);
  541. return time.isBefore(moment());
  542. } else {
  543. return false;
  544. }
  545. },
  546. onRegentSubmit(data, inputValue, col, rowIndex, colIndex, row, key) {
  547. // if (this.templateFillType !== 'actFill') {
  548. // return
  549. // }
  550. this.updateDataSourceByRowIndex(rowIndex, { [key]: inputValue, [`selectInfo_${key}`]: data.selectInfo })
  551. this.$emit("onRegentSubmit", { selectInfo: data, key, col, rowIndex, colIndex, rowData: row })
  552. },
  553. isShowAddRos() {
  554. if (this.showAddRow !== undefined) {
  555. return this.showAddRow
  556. }
  557. return this.templateFillType === 'preFill';
  558. },
  559. // 复制值
  560. onCopy(rowIndex, col) {
  561. if (col.copyFrom) {
  562. if (isValueEmpty(this.localDataSource[rowIndex][col.copyFrom])) {//没有值就不用复制了
  563. return
  564. }
  565. this.updateDataSourceByRowIndex(rowIndex, { [col.prop]: this.localDataSource[rowIndex][col.copyFrom] }, "clickable")
  566. this.onBlur(rowIndex, col.prop, this.localDataSource[rowIndex][col.prop]);
  567. }
  568. },
  569. // 初始化表头选择器值
  570. initHeaderSelectValues() {
  571. const headerSelectObj = {};
  572. this.columns.map(col => {
  573. if (col.headerSelectKey) {
  574. headerSelectObj[col.headerSelectKey] = col.defaultValue || col.headerOptions[0].value || ""
  575. }
  576. });
  577. this.headerSelectFields = headerSelectObj;
  578. },
  579. // 直接获取表单数据,不做校验
  580. getFilledFormData() {
  581. return {
  582. stepTableFormData: [...this.localDataSource],
  583. headerSelectFields: this.headerSelectFields,
  584. headerFields: this.headerFields,
  585. };
  586. },
  587. // 获取最新数据
  588. getFormData() {
  589. // 合并表头选择器值到 columns
  590. // 数据校验
  591. const validateResult = this.validateFormData();
  592. return new Promise((resolve, reject) => {
  593. if (validateResult.valid) {
  594. resolve({
  595. stepTableFormData: [...this.localDataSource],
  596. headerSelectFields: this.headerSelectFields,
  597. headerFields: this.headerFields,
  598. })
  599. } else {
  600. // this.$message.error("表单内容未填完,请填写后再提交");
  601. reject(validateResult.errors[0].error)
  602. }
  603. })
  604. },
  605. // 表单数据校验
  606. validateFormData() {
  607. const errors = [];
  608. // 清空之前的错误状态
  609. this.formErrors = [];
  610. // 校验表头的 HandleFormItem
  611. this.columns.forEach((col, colIndex) => {
  612. if (col.headerSelectKey && col.headerOptions && col.fillType === this.templateFillType) {
  613. const headerValue = this.headerSelectFields[col.headerSelectKey];
  614. if (isValueEmpty(headerValue)) {
  615. const errorItem = {
  616. rowIndex: -1, // 表头特殊标记
  617. colIndex,
  618. field: col.headerSelectKey,
  619. label: this.$t(col.label),
  620. error: `请选择${this.$t(col.label)}`
  621. };
  622. errors.push(errorItem);
  623. this.formErrors.push(errorItem);
  624. }
  625. } else if (col.headerColumns && col.headerColumns.length > 0) {
  626. col.headerColumns.forEach((headerCol, headerColIndex) => {
  627. const headerValue = this.headerFields[`${colIndex}_${headerColIndex}`];
  628. if (headerCol.fillType === this.templateFillType) {
  629. if (isValueEmpty(headerValue) && headerCol.type !== "span") {
  630. const errorItem = {
  631. rowIndex: -1, // 表头特殊标记
  632. colIndex,
  633. field: headerCol.key,
  634. label: this.$t(headerCol.label),
  635. headerIndex: headerColIndex,
  636. error: `请选择${this.$t(headerCol.label)}`
  637. };
  638. errors.push(errorItem);
  639. this.formErrors.push(errorItem);
  640. }
  641. }
  642. });
  643. }
  644. });
  645. // 遍历数据行
  646. this.localDataSource.forEach((row, rowIndex) => {
  647. // 遍历列
  648. this.columns.forEach((col, colIndex) => {
  649. // 只校验 fillType 与当前模板状态匹配的字段
  650. if (col.bodyFillType === this.templateFillType || col.bodySubFillType === this.templateFillType) {
  651. // 检查主字段
  652. const mainValue = row[col.prop];
  653. if (col.bodyType === "checkboxTag") {
  654. // checkboxTag类型的校验逻辑
  655. if (this.templateFillType === "actFill") {
  656. // actFill时,检查是否有checked为true的项
  657. const hasChecked = mainValue && mainValue.some(tag => tag.checked === true);
  658. if (!hasChecked) {
  659. const errorItem = {
  660. rowIndex,
  661. colIndex,
  662. field: col.prop,
  663. label: this.$t(col.label),
  664. error: `请勾选${this.$t(col.label)}`
  665. };
  666. errors.push(errorItem);
  667. this.formErrors.push(errorItem);
  668. }
  669. } else if (this.templateFillType === "preFill") {
  670. // preFill时,检查所有tagValue是否不为空
  671. const allTagValuesFilled = mainValue && mainValue.every(tag => tag.tagValue && (tag.tagValue + '').trim() !== '');
  672. if (!allTagValuesFilled) {
  673. const errorItem = {
  674. rowIndex,
  675. colIndex,
  676. field: col.prop,
  677. label: this.$t(col.label),
  678. error: `请填写${this.$t(col.label)}`
  679. };
  680. errors.push(errorItem);
  681. this.formErrors.push(errorItem);
  682. }
  683. }
  684. } else if (col.bodyType === "checkbox") {
  685. // checkbox类型的校验逻辑
  686. // checkbox只在actFill时进行必填校验
  687. if (this.templateFillType === 'actFill' && !col.isNeedCheck) {
  688. // 单个checkbox:值必须为true
  689. // checkbox组:至少选中一个
  690. const hasChecked = Array.isArray(mainValue) ? mainValue.length > 0 : mainValue === true;
  691. if (!hasChecked) {
  692. const errorItem = {
  693. rowIndex,
  694. colIndex,
  695. field: col.prop,
  696. label: this.$t(col.label),
  697. error: `请勾选${this.$t(col.label)}`
  698. };
  699. errors.push(errorItem);
  700. this.formErrors.push(errorItem);
  701. }
  702. }
  703. } else if (col.bodyType === "operableInput") {
  704. mainValue.forEach((itemItem, itemIndex) => {
  705. if (isValueEmpty(itemItem.value)) {
  706. const errorItem = {
  707. rowIndex,
  708. colIndex,
  709. field: rowIndex + col.prop + itemIndex,
  710. label: this.$t(col.label),
  711. error: `请填写${this.$t(col.label)}`
  712. };
  713. errors.push(errorItem);
  714. this.formErrors.push(errorItem);
  715. }
  716. })
  717. } else {
  718. if (isValueEmpty(mainValue) && col.bodyType !== 'span' && col.bodyType !== 'button') {
  719. const errorItem = {
  720. rowIndex,
  721. colIndex,
  722. field: col.prop,
  723. label: this.$t(col.label),
  724. error: `请填写${this.$t(col.label)}`
  725. };
  726. errors.push(errorItem);
  727. this.formErrors.push(errorItem);
  728. }
  729. // 检查子字段(如果有)
  730. if (col.bodySubKey && col.bodySubFillType === this.templateFillType && col.bodySubType !== 'span' && col.bodySubType !== "button") {
  731. const subValue = row[col.bodySubKey];
  732. if (isValueEmpty(subValue)) {
  733. const errorItem = {
  734. rowIndex,
  735. colIndex,
  736. field: col.bodySubKey,
  737. label: `${this.$t(col.label)}单位`,
  738. error: `请填写${this.$t(col.label)}单位`
  739. };
  740. errors.push(errorItem);
  741. this.formErrors.push(errorItem);
  742. }
  743. }
  744. }
  745. console.log(col.otherCode, "col.otherCode")
  746. // 检查其他输入框
  747. if (col.otherCode) {
  748. const isSelectedOther = this.isShowOther(mainValue);
  749. if (!isSelectedOther) {
  750. return;
  751. }
  752. const otherValue = row[col.otherCode];
  753. if (isValueEmpty(otherValue)) {
  754. const errorItem = {
  755. rowIndex,
  756. colIndex,
  757. field: col.otherCode,
  758. label: `${this.$t(col.label)}单位`,
  759. error: `请填写${this.$t(col.otherLabel) ? this.$t(col.otherLabel) : '其他'}信息`
  760. };
  761. errors.push(errorItem);
  762. this.formErrors.push(errorItem);
  763. }
  764. }
  765. }
  766. });
  767. });
  768. console.log(errors, this.localDataSource, "errors")
  769. return {
  770. valid: errors.length === 0,
  771. errors: errors
  772. };
  773. },
  774. // 表头选择器变化
  775. onHeaderSelectChange(col, value) {
  776. if (col.headerSelectTo) {
  777. this.headerSelectFields[col.headerSelectTo] = value;
  778. }
  779. this.headerSelectFields[col.headerSelectKey] = value;
  780. this.$emit('headerSelectChange', { key: col.headerSelectKey, headerSelectFields: this.headerSelectFields, dataSource: this.localDataSource });
  781. // 输入时清除对应表单项的错误状态
  782. this.formErrors = this.formErrors.filter(error =>
  783. !(error.rowIndex === -1 &&
  784. error.field === col.headerSelectKey)
  785. );
  786. },
  787. // 检查并应用 compareTo 逻辑
  788. checkCompareToLogic(rowIndex, colIndex, colKey, value) {
  789. const col = this.columns[colIndex];
  790. // 检查主字段的 compareTo 逻辑
  791. if (col && col.bodyFillType === "actFill" && col.compareTo) {
  792. const compareToValue = this.localDataSource[rowIndex][col.compareTo];
  793. // 比较当前值和compareTo值,如果不相等则设置橙色背景
  794. if (!isEqual(value, compareToValue)) {
  795. this.setOrangeBg(rowIndex, colIndex, colKey, true);
  796. } else {
  797. // 相等则移除橙色背景
  798. this.setOrangeBg(rowIndex, colIndex, colKey, false);
  799. }
  800. }
  801. },
  802. // 在数据加载时检查 compareTo 逻辑
  803. checkCompareToOnDataLoad() {
  804. // 遍历所有行和列,检查 compareTo 逻辑
  805. this.localDataSource.forEach((row, rowIndex) => {
  806. this.columns.forEach((col, colIndex) => {
  807. const currentValue = row[col.prop];
  808. const compareToValue = row[col.compareTo];
  809. if (col.compareTo && !isValueEmpty(currentValue) && !isValueEmpty(compareToValue)) {
  810. // 比较当前值和compareTo值,如果不相等则设置橙色背景
  811. if (!isEqual(currentValue, compareToValue)) {
  812. this.setOrangeBg(rowIndex, colIndex, col.prop, true);
  813. } else {
  814. // 相等则移除橙色背景
  815. this.setOrangeBg(rowIndex, colIndex, col.prop, false);
  816. }
  817. }
  818. // 检查子字段的 compareTo 逻辑
  819. if (col.bodySubFillType === "actFill" && col.bodySubCompareTo) {
  820. const currentValue = row[col.bodySubKey];
  821. const compareToValue = row[col.bodySubCompareTo];
  822. if (!isValueEmpty(currentValue) && !isValueEmpty(compareToValue)) {
  823. // 比较当前值和compareTo值,如果不相等则设置橙色背景
  824. if (!isEqual(currentValue, compareToValue)) {
  825. this.setOrangeBg(rowIndex, colIndex, col.bodySubKey, true);
  826. } else {
  827. // 相等则移除橙色背景
  828. this.setOrangeBg(rowIndex, colIndex, col.bodySubKey, false);
  829. }
  830. }
  831. }
  832. });
  833. });
  834. },
  835. // 表体值变化
  836. onBodyValueChange(rowIndex, colIndex, value, row, type) {
  837. const col = this.columns[colIndex];
  838. this.localDataSource[rowIndex][col.prop] = value;
  839. // 检查并应用 compareTo 逻辑
  840. this.checkCompareToLogic(rowIndex, colIndex, col.prop, value);
  841. // 输入时清除对应表单项的错误状态
  842. this.formErrors = this.formErrors.filter(error =>
  843. !(error.rowIndex === rowIndex &&
  844. error.colIndex === colIndex &&
  845. error.field === col.prop)
  846. );
  847. if (type === "select") {
  848. this.$emit('bodySelectChange', { rowIndex, item: row, colIndex, value, key: col.prop, dataSource: this.localDataSource, headerSelectFields: this.headerSelectFields });
  849. }
  850. },
  851. // 表体子值变化
  852. onBodySubValueChange(rowIndex, colIndex, value, row, type) {
  853. const col = this.columns[colIndex];
  854. this.localDataSource[rowIndex][col.bodySubKey] = value;
  855. // 检查子字段的 compareTo 逻辑
  856. if (col && col.bodySubFillType === "actFill" && col.bodySubCompareTo) {
  857. const compareToValue = this.localDataSource[rowIndex][col.bodySubCompareTo];
  858. // 比较当前值和compareTo值,如果不相等则设置橙色背景
  859. if (value !== compareToValue) {
  860. this.setOrangeBg(rowIndex, colIndex, col.bodySubKey, true);
  861. } else {
  862. // 相等则移除橙色背景
  863. this.setOrangeBg(rowIndex, colIndex, col.bodySubKey, false);
  864. }
  865. }
  866. // 输入时清除对应表单项的错误状态
  867. this.formErrors = this.formErrors.filter(error =>
  868. !(error.rowIndex === rowIndex &&
  869. error.colIndex === colIndex &&
  870. error.field === col.bodySubKey)
  871. );
  872. if (type === "select") {
  873. this.$emit('bodySelectChange', { rowIndex, item: row, colIndex, value, key: col.bodySubKey, dataSource: this.localDataSource, headerSelectFields: this.headerSelectFields });
  874. }
  875. },
  876. getHeaderItem(col) {
  877. return {
  878. fillType: col.fillType,
  879. options: col.headerOptions,
  880. label: ""
  881. }
  882. },
  883. getBodyItem(col, rowIndex) {
  884. const currentItem = this.localDataSource[rowIndex];
  885. const item = {
  886. fillType: col.bodyFillType,
  887. options: col.bodyOptions,
  888. maxlength: col.bodyMaxlength,
  889. label: this.$t(col.label),
  890. precision: currentItem[col.bodyPrecisionKey] || col.precision,
  891. copyFrom: col.copyFrom || "",
  892. compareTo: col.compareTo, // 添加 compareTo 字段
  893. type: col.bodyType || "input",
  894. filledCodes: col.filledCodes,
  895. };
  896. if (col.bodyDisabled) {
  897. item.disabled = col.bodyDisabled;
  898. }
  899. if (col.qxbdType) {
  900. item.qxbdType = col.qxbdType;
  901. }
  902. if (col.regentFillType) {
  903. item.regentFillType = col.regentFillType;
  904. }
  905. if (col.checkType) {
  906. item.checkType = col.checkType;
  907. }
  908. // 支持动态checkboxLabel - 从行数据中获取
  909. const dynamicLabelKey = col.prop + 'Label';
  910. if (currentItem && currentItem[dynamicLabelKey]) {
  911. // 优先从行数据中获取动态label(如jzbh1Label)
  912. item.checkboxLabel = currentItem[dynamicLabelKey];
  913. } else if (col.checkboxLabel !== undefined && col.checkboxLabel !== '') {
  914. // 否则使用列配制的checkboxLabel
  915. item.checkboxLabel = this.$t(col.checkboxLabel);
  916. }
  917. if (col.bodyType === "operableInput") {
  918. if (currentItem.isComplete || this.templateFillType !== 'actFill') {
  919. item.disabled = true;
  920. } else {
  921. item.disabled = false;
  922. }
  923. }
  924. if (col.noBorder) {
  925. item.noBorder = true;
  926. }
  927. if (col.bodyLayout) {
  928. item.layout = col.bodyLayout;
  929. }
  930. return item
  931. },
  932. getBodyButtonItem(col,) {
  933. return {
  934. buttonName: col.bodySubButtonName,
  935. fillType: col.bodySubFillType,
  936. type: "button",
  937. }
  938. },
  939. getBodyThirdButtonItem(col, rowIndex) {
  940. return {
  941. buttonName: col.bodyThirdButtonName,
  942. fillType: col.bodyThirdFillType,
  943. type: "button",
  944. }
  945. },
  946. getBodySubItem(col) {
  947. const item = {
  948. fillType: col.bodySubFillType,
  949. options: col.bodySubOptions,
  950. maxlength: col.bodySubMaxlength || 10,
  951. label: "",
  952. placeholder: col.bodySubPlaceholder || (col.bodySubType === 'select' ? '请选择' : '请输入'),
  953. precision: col.subPrecision,
  954. compareTo: col.bodySubCompareTo, // 添加 compareTo 字段
  955. type: col.bodySubType || "input",
  956. }
  957. if (col.bodySubDisabled) {
  958. item.disabled = col.bodySubDisabled;
  959. }
  960. return item
  961. },
  962. // 删除行
  963. deleteRow(rowIndex) {
  964. this.localDataSource.splice(rowIndex, 1);
  965. this.$emit('row-delete', rowIndex);
  966. },
  967. deleteRows(rowsIndex) {
  968. rowsIndex.sort((a, b) => b - a);
  969. rowsIndex.forEach(index => {
  970. this.localDataSource.splice(index, 1);
  971. this.$emit('row-delete', index);
  972. });
  973. },
  974. deleteSelectedRows(rowsIndex) {
  975. this.deleteRows(rowsIndex);
  976. this.selectedRows = [];
  977. this.isIndeterminate = false;
  978. this.$emit('selectionChange', this.selectedRows);
  979. },
  980. updateHeaderSelectFields(fields) {
  981. this.headerSelectFields = { ...this.headerSelectFields, ...fields };
  982. },
  983. // 更新数据方法,可在formData变更时调用,也可由父组件调用
  984. updateDataSource(dataSource = []) {
  985. this.oldLocalDataSource = JSON.parse(JSON.stringify(this.localDataSource));
  986. // 深拷贝数据以避免直接修改原始数据
  987. this.localDataSource = JSON.parse(JSON.stringify(dataSource || [])).map(row => ({
  988. ...row,
  989. _checked: false // 初始化选中状态为 false
  990. }));
  991. this.updateCheckStatus();
  992. this.checkCompareToOnDataLoad();
  993. },
  994. // 根据行索引更新数据 autoUpdateRecord 是否自动更新记录
  995. updateDataSourceByRowIndex(rowIndex, data, updateFieldsInfo = {}) {
  996. const { signData, updateFields = [] } = updateFieldsInfo;
  997. this.oldLocalDataSource = JSON.parse(JSON.stringify(this.localDataSource));
  998. this.localDataSource[rowIndex] = { ...this.localDataSource[rowIndex], ...data };
  999. this.localDataSource = [...this.localDataSource];
  1000. if (updateFields.length > 0) {
  1001. updateFields.map((key) => {
  1002. const ref = this.$refs[key + rowIndex];
  1003. if (ref) {
  1004. ref[0].handleUpdateRecord(signData, { oldValue: this.oldLocalDataSource[rowIndex][key], inputValue: data[key] });
  1005. }
  1006. })
  1007. }
  1008. this.checkCompareToOnDataLoad();
  1009. // justUpdateFilledFormData();
  1010. },
  1011. pushDataSource(data = []) {
  1012. this.localDataSource.push(...data);
  1013. this.localDataSource = [...this.localDataSource];
  1014. this.checkCompareToOnDataLoad();
  1015. justUpdateFilledFormData();
  1016. },
  1017. // 比较newData和oldData的值是否相等,只要有一对不相等就返回false
  1018. compareOldAndCurrentFormFields(newData, oldData) {
  1019. for (const key in newData) {
  1020. const oldValue = newData[key];
  1021. const currentValue = oldData[key];
  1022. if (JSON.stringify(oldValue) !== JSON.stringify(currentValue)) {
  1023. return false;
  1024. } else {
  1025. return false;
  1026. }
  1027. }
  1028. return true;
  1029. },
  1030. // 处理全选
  1031. handleCheckAllChange(val) {
  1032. this.localDataSource.forEach(row => {
  1033. row._checked = val;
  1034. });
  1035. this.updateCheckStatus();
  1036. this.$emit('selectionChange', this.selectedRows);
  1037. },
  1038. // 处理单个 checkbox 变化
  1039. handleCheckChange(row, val) {
  1040. row._checked = val;
  1041. this.updateCheckStatus();
  1042. this.$emit('selectionChange', this.selectedRows);
  1043. },
  1044. // 更新选中状态和半选状态
  1045. updateCheckStatus() {
  1046. const totalRows = this.localDataSource.length;
  1047. const checkedRows = this.localDataSource.filter(row => row._checked).length;
  1048. this.checkAll = checkedRows === totalRows && totalRows > 0;
  1049. this.isIndeterminate = checkedRows > 0 && checkedRows < totalRows;
  1050. // 记录选中的行数据和对应的行索引
  1051. this.selectedRows = this.localDataSource.map((row, rowIndex) => ({
  1052. ...row,
  1053. rowIndex
  1054. })).filter(item => item._checked);
  1055. },
  1056. onAddRow() {
  1057. if (this.$listeners && this.$listeners['onAddRow']) {
  1058. this.$emit('onAddRow', { dataSource: this.localDataSource });
  1059. return;
  1060. }
  1061. this.addRow({
  1062. actSolutionVolumePrecision: 3,//小数点精度默认为3
  1063. actSolutionConcentrationPrecision: 3,//小数点精度默认为3
  1064. targetDiluentVolumePrecision: 3,//小数点精度默认为3
  1065. targetStartSolutionVolumePrecision: 3,//小数点精度默认为3
  1066. id: getuuid(),
  1067. rowIndex: this.localDataSource.length,
  1068. });
  1069. justUpdateFilledFormData()
  1070. },
  1071. // 添加行
  1072. addRow(row = {}) {
  1073. this.localDataSource.push({
  1074. ...row,
  1075. _checked: false // 初始化选中状态为 false
  1076. });
  1077. this.updateCheckStatus();
  1078. },
  1079. addRows(rows = []) {
  1080. this.localDataSource.push(...rows.map(row => ({
  1081. ...row,
  1082. _checked: false // 初始化选中状态为 false
  1083. })));
  1084. this.updateCheckStatus();
  1085. },
  1086. getDataSource() {
  1087. return this.localDataSource;
  1088. },
  1089. // 判断表单项是否有错误
  1090. hasError(rowIndex, colIndex, field) {
  1091. return this.formErrors.some(error =>
  1092. error.rowIndex === rowIndex &&
  1093. error.colIndex === colIndex &&
  1094. error.field === field
  1095. );
  1096. },
  1097. // 处理错误状态更新
  1098. onErrorUpdate(rowIndex, colIndex, field, isError) {
  1099. if (!isError) {
  1100. this.formErrors = this.formErrors.filter(error =>
  1101. !(error.rowIndex === rowIndex &&
  1102. error.colIndex === colIndex &&
  1103. error.field === field)
  1104. );
  1105. }
  1106. },
  1107. // onSubBlur(rowIndex, colKey, value) {
  1108. // this.$emit("blur", { rowIndex, colKey, value, item: this.localDataSource[rowIndex] });
  1109. // },
  1110. // 检查是否需要橙色背景
  1111. hasOrangeBg(rowIndex, colIndex, field) {
  1112. const key = `${rowIndex}-${colIndex}-${field}`;
  1113. return this.orangeBgCells[key] || false;
  1114. },
  1115. // 设置橙色背景状态
  1116. setOrangeBg(rowIndex, colIndex, field, status) {
  1117. const key = `${rowIndex}-${colIndex}-${field}`;
  1118. this.$set(this.orangeBgCells, key, status);
  1119. },
  1120. onBlur(rowIndex, colKey) {
  1121. const value = this.localDataSource[rowIndex][colKey];
  1122. // 查找对应的列配制
  1123. const col = this.columns.find(c => c.prop === colKey);
  1124. if (col && col.bodyFillType === "actFill" && col.compareTo) {
  1125. const compareToValue = this.localDataSource[rowIndex][col.compareTo];
  1126. // 比较当前值和compareTo值,如果不相等则设置橙色背景
  1127. if (value !== compareToValue) {
  1128. this.setOrangeBg(rowIndex, this.columns.findIndex(c => c.prop === colKey), colKey, true);
  1129. } else {
  1130. // 相等则移除橙色背景
  1131. this.setOrangeBg(rowIndex, this.columns.findIndex(c => c.prop === colKey), colKey, false);
  1132. }
  1133. }
  1134. this.$emit("blur", { rowIndex, colKey, value, dataSource: this.localDataSource, headerSelectFields: this.headerSelectFields, item: this.localDataSource[rowIndex] });
  1135. },
  1136. onSubBlur(rowIndex, colKey, value) {
  1137. // 查找对应的列配制
  1138. const col = this.columns.find(c => c.bodySubKey === colKey);
  1139. if (col && col.bodySubFillType === "actFill" && col.bodySubCompareTo) {
  1140. const compareToValue = this.localDataSource[rowIndex][col.bodySubCompareTo];
  1141. // 比较当前值和compareTo值,如果不相等则设置橙色背景
  1142. if (value !== compareToValue) {
  1143. this.setOrangeBg(rowIndex, this.columns.findIndex(c => c.bodySubKey === colKey), colKey, true);
  1144. } else {
  1145. // 相等则移除橙色背景
  1146. this.setOrangeBg(rowIndex, this.columns.findIndex(c => c.bodySubKey === colKey), colKey, false);
  1147. }
  1148. }
  1149. this.$emit("blur", { rowIndex, colKey, value, item: this.localDataSource[rowIndex] });
  1150. }
  1151. }
  1152. };
  1153. </script>
  1154. <style scoped>
  1155. .custom-table-wrapper {
  1156. border: 1px solid #ebeef5;
  1157. border-radius: 4px;
  1158. overflow: hidden;
  1159. font-size: 14px;
  1160. color: #606266;
  1161. margin-top: 20px;
  1162. &.no-border {
  1163. box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  1164. border-radius: 5px 5px;
  1165. border: none;
  1166. .custom-table-cell {
  1167. border-right: none;
  1168. }
  1169. .custometable-row {
  1170. display: flex;
  1171. border-bottom: none;
  1172. }
  1173. }
  1174. }
  1175. .inner-table-cell {
  1176. display: flex;
  1177. align-items: center;
  1178. justify-content: center;
  1179. }
  1180. .m-l-5 {
  1181. margin-left: 5px;
  1182. }
  1183. .sub-input-number {
  1184. width: 145px;
  1185. .el-input-number--mini {
  1186. width: 145px;
  1187. }
  1188. }
  1189. /* 表头 */
  1190. .custom-table-header {
  1191. background-color: #f5f7fa;
  1192. border-bottom: 1px solid #ebeef5;
  1193. white-space: nowrap;
  1194. display: block;
  1195. }
  1196. .custom-table-body {
  1197. /* max-height: 500px; */
  1198. /* overflow-y: auto; */
  1199. /* 可根据需要调整或由父组件控制 */
  1200. }
  1201. .header-cell-content {
  1202. display: flex;
  1203. align-items: center;
  1204. justify-content: center;
  1205. }
  1206. .header-columns-grid {
  1207. display: grid;
  1208. gap: 10px;
  1209. }
  1210. .header-column-item {
  1211. display: flex;
  1212. align-items: center;
  1213. padding: 0 5px;
  1214. }
  1215. /* 共同行样式 */
  1216. .custom-table-row {
  1217. display: table;
  1218. width: 100%;
  1219. table-layout: fixed;
  1220. }
  1221. .custometable-row {
  1222. display: table;
  1223. width: 100%;
  1224. table-layout: fixed;
  1225. &:not(:last-child) {
  1226. border-bottom: 1px solid #ebeef5;
  1227. }
  1228. }
  1229. /* 单元格 */
  1230. .custom-table-cell {
  1231. display: table-cell;
  1232. padding: 12px 10px;
  1233. text-align: left;
  1234. vertical-align: middle;
  1235. border-right: 1px solid #ebeef5;
  1236. page-break-inside: avoid;
  1237. box-sizing: border-box;
  1238. }
  1239. .custom-table-cell:last-child {
  1240. border-right: none;
  1241. }
  1242. .header-cell {
  1243. color: #909399;
  1244. background-color: #f5f7fa;
  1245. font-size: 12px;
  1246. word-break: break-word;
  1247. white-space: normal;
  1248. }
  1249. .body-cell {
  1250. color: #606266;
  1251. page-break-inside: avoid;
  1252. background-color: #fff;
  1253. }
  1254. /* select 样式(模仿 Element UI) */
  1255. .header-cell select {
  1256. width: 100%;
  1257. padding: 4px 8px;
  1258. border: 1px solid #dcdfe6;
  1259. border-radius: 4px;
  1260. outline: none;
  1261. background-color: #fff;
  1262. font-size: 13px;
  1263. color: #606266;
  1264. appearance: none;
  1265. /* 隐藏默认箭头(可选) */
  1266. background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6,9 12,15 18,9'%3e%3c/polyline%3e%3c/svg%3e");
  1267. background-repeat: no-repeat;
  1268. background-position: right 8px center;
  1269. background-size: 14px;
  1270. padding-right: 28px;
  1271. }
  1272. /* 滚动容器:如果整体宽度超限,显示横向滚动条 */
  1273. .custom-table-wrapper {
  1274. display: flex;
  1275. flex-direction: column;
  1276. max-width: 100%;
  1277. /* 父容器决定宽度 */
  1278. overflow: auto;
  1279. max-height: 500px;
  1280. }
  1281. .custom-table-header,
  1282. .custom-table-body {
  1283. min-width: 100%;
  1284. }
  1285. .header-select {
  1286. width: 100px;
  1287. margin-left: 5px;
  1288. }
  1289. .no-data {
  1290. text-align: center;
  1291. padding: 20px 0;
  1292. color: rgb(144, 147, 153)
  1293. }
  1294. .add-row {
  1295. display: flex;
  1296. justify-content: center;
  1297. padding: 20px 0;
  1298. margin-top: 20px;
  1299. }
  1300. .flex1 {
  1301. flex: 1;
  1302. }
  1303. .flex {
  1304. display: flex;
  1305. }
  1306. .other-title {
  1307. text-align: right;
  1308. margin: 0 10px;
  1309. font-size: 14px;
  1310. font-weight: normal;
  1311. color: #606266;
  1312. width: auto;
  1313. }
  1314. .body-span {
  1315. text-align: center;
  1316. }
  1317. .item-center {
  1318. display: flex;
  1319. align-items: center;
  1320. }
  1321. .mr-5 {
  1322. margin-right: 5px;
  1323. }
  1324. .sort-cell {
  1325. text-align: center;
  1326. width: 100px;
  1327. }
  1328. .flex-wrap {
  1329. flex-wrap: wrap;
  1330. gap: 10px;
  1331. }
  1332. .row-error-border {
  1333. box-shadow: 0 0 6px #ffc3c3;
  1334. padding: 8px;
  1335. border-radius: 4px;
  1336. border: 1px solid #ff5d5d;
  1337. }
  1338. .checkbox-item {
  1339. /* width: 50px; */
  1340. display: flex;
  1341. align-items: center;
  1342. justify-content: center;
  1343. }
  1344. .c-cell {
  1345. width: 50px;
  1346. }
  1347. .span-content {
  1348. width: -webkit-fill-available;
  1349. text-align: center;
  1350. }
  1351. .add-icon {
  1352. color: #409eff;
  1353. font-size: 20px;
  1354. margin-left: 10px;
  1355. cursor: pointer;
  1356. }
  1357. .remove-icon {
  1358. color: #ff4949;
  1359. font-size: 20px;
  1360. margin-left: 5px;
  1361. cursor: pointer;
  1362. }
  1363. .full-row {
  1364. grid-column: span 2;
  1365. }
  1366. .grid-container {
  1367. display: grid;
  1368. grid-template-columns: repeat(2, 1fr);
  1369. /* 默认2列 */
  1370. gap: 5px;
  1371. /* 防止网格容器被分割到不同页面 */
  1372. page-break-inside: avoid;
  1373. break-inside: avoid;
  1374. }
  1375. </style>