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

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