华西海圻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.

1454 lines
48 KiB

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