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

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