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

1023 lines
32 KiB

  1. <template>
  2. <div>
  3. <div class="custom-table-wrapper">
  4. <div class="custom-table-header">
  5. <div class="custom-table-row">
  6. <div v-for="(col, index) in columns" :key="index" class="custom-table-cell header-cell"
  7. :style="{ width: col.width ? col.width + 'px' : 'auto' }">
  8. <div class="header-cell-content">
  9. <div>{{ $t(col.label) }}</div>
  10. <template
  11. v-if="col.headerSelectKey && col.headerOptions && (showHeaderSelect || templateFillType === 'preFill')">
  12. <HandleFormItem :fieldKey="prefixKey + '_' + col.headerSelectKey"
  13. :fieldItemLabel="fieldItemLabel" type="select" class="header-select"
  14. :item="getHeaderItem(col)" v-model="headerSelectFields[col.headerSelectKey]"
  15. @change="onHeaderSelectChange(col, $event)"
  16. :error="hasError(-1, index, col.headerSelectKey)"
  17. @update:error="onErrorUpdate(-1, index, col.headerSelectKey, $event)" />
  18. </template>
  19. <span v-else-if="headerSelectFields[col.headerSelectKey]" class="fill-type-icon">({{
  20. headerSelectFields[col.headerSelectKey] }})</span>
  21. </div>
  22. </div>
  23. <!-- 默认操作栏 -->
  24. <div class="custom-table-cell header-cell" :style="{ width: '245px' }" v-if="showOperation">
  25. <div class="header-cell-content">
  26. <div>操作</div>
  27. </div>
  28. </div>
  29. </div>
  30. </div>
  31. <div class="custom-table-body">
  32. <div v-for="(row, rowIndex) in localDataSource" :key="rowIndex" class="custometable-row">
  33. <div v-for="(col, colIndex) in columns" :key="colIndex" class="custom-table-cell body-cell"
  34. :style="{ width: col.width ? col.width + 'px' : 'auto' }">
  35. <div class="inner-table-cell">
  36. <div class="flex1">
  37. <template v-if="col.bodyType === 'input'">
  38. <HandleFormItem :fieldKey="prefixKey + '_' + col.prop + '_' + rowIndex"
  39. :fieldItemLabel="fieldItemLabel" type="input"
  40. @blur="onBlur(rowIndex, col.prop, $event)" @copy="onCopy(rowIndex, col)"
  41. class="body-input" :item="getBodyItem(col, rowIndex)" v-model="row[col.prop]"
  42. @change="onBodyValueChange(rowIndex, colIndex, $event)"
  43. :error="hasError(rowIndex, colIndex, col.prop)"
  44. @update:error="onErrorUpdate(rowIndex, colIndex, col.prop, $event)"
  45. :orange-bg="hasOrangeBg(rowIndex, colIndex, col.prop)" />
  46. </template>
  47. <template v-else-if="col.bodyType === 'inputNumber'">
  48. <HandleFormItem :fieldKey="prefixKey + '_' + col.prop + '_' + rowIndex"
  49. :fieldItemLabel="fieldItemLabel" type="inputNumber"
  50. @copy="onCopy(rowIndex, col)" class="body-input-number"
  51. :item="getBodyItem(col, rowIndex)" v-model="row[col.prop]"
  52. @blur="onBlur(rowIndex, col.prop, $event)"
  53. @change="onBodyValueChange(rowIndex, colIndex, $event)"
  54. :error="hasError(rowIndex, colIndex, col.prop)"
  55. @update:error="onErrorUpdate(rowIndex, colIndex, col.prop, $event)"
  56. :orange-bg="hasOrangeBg(rowIndex, colIndex, col.prop)" />
  57. </template>
  58. <template v-else-if="col.bodyType === 'select'">
  59. <div class="flex flex1">
  60. <HandleFormItem :fieldKey="prefixKey + '_' + col.prop + '_' + rowIndex"
  61. :fieldItemLabel="fieldItemLabel" type="select" class="body-select"
  62. @blur="onBlur(rowIndex, col.prop, $event)"
  63. :item="getBodyItem(col, rowIndex)" v-model="row[col.prop]"
  64. @change="onBodyValueChange(rowIndex, colIndex, $event)"
  65. :error="hasError(rowIndex, colIndex, col.prop)"
  66. @update:error="onErrorUpdate(rowIndex, colIndex, col.prop, $event)"
  67. :orange-bg="hasOrangeBg(rowIndex, colIndex, col.prop)" />
  68. </div>
  69. </template>
  70. <div class="flex flex1" v-else-if="col.bodyType === 'clickable'">
  71. <HandleFormItem :fieldKey="prefixKey + '_' + col.prop + '_' + rowIndex"
  72. :fieldItemLabel="fieldItemLabel" type="clickable" class="body-clickable"
  73. :item="getBodyItem(col, rowIndex)" :value="row[col.prop]"
  74. :error="hasError(rowIndex, colIndex, col.prop)"
  75. @clickable="handleClickable(col, rowIndex, colIndex, row)"
  76. @resetRecord="resetRecord(rowIndex, colIndex, col.prop)"
  77. @update:error="onErrorUpdate(rowIndex, colIndex, col.prop, $event)"
  78. :orange-bg="hasOrangeBg(rowIndex, colIndex, col.prop)" />
  79. </div>
  80. <template v-else-if="col.bodyType === 'span'">
  81. <div class="body-span">
  82. {{ row[col.prop] }}
  83. </div>
  84. </template>
  85. </div>
  86. <div v-show="isShowOther(row[col.prop], col)" class="flex flex1">
  87. <div class="other-title">{{ col.otherLabel ? $t(col.otherLabel) :
  88. $t("template.common.other") }}
  89. </div>
  90. <div class="flex flex1">
  91. <HandleFormItem :field-item-label="fieldItemLabel"
  92. :field-key="prefixKey + '_' + col.otherCode"
  93. @blur="onBlur(rowIndex, col.prop, $event)" :item="getOtherItem(col)"
  94. v-model="row[col.otherCode]"
  95. :error="hasError(rowIndex, colIndex, col.otherCode)"
  96. @update:error="onErrorUpdate(rowIndex, colIndex, col.otherCode, $event)"
  97. :orange-bg="hasOrangeBg(rowIndex, colIndex, col.otherCode)" />
  98. </div>
  99. </div>
  100. <div class="m-l-5 flex1" v-if="isShowBodySub(col, row)">
  101. <template v-if="col.bodySubType === 'inputNumber'">
  102. <HandleFormItem :fieldKey="prefixKey + '_' + col.bodySubKey + '_' + rowIndex"
  103. :fieldItemLabel="fieldItemLabel" type="inputNumber"
  104. @blur="onSubBlur(rowIndex, col.bodySubKey, $event)"
  105. @copy="onCopy(rowIndex, col)" :item="getBodySubItem(col)"
  106. v-model="row[col.bodySubKey]"
  107. @change="onBodySubValueChange(rowIndex, colIndex, $event)"
  108. :error="hasError(rowIndex, colIndex, col.bodySubKey)"
  109. @update:error="onErrorUpdate(rowIndex, colIndex, col.bodySubKey, $event)"
  110. :orange-bg="hasOrangeBg(rowIndex, colIndex, col.bodySubKey)" />
  111. </template>
  112. <template v-else-if="col.bodySubType === 'select'">
  113. <HandleFormItem :fieldKey="prefixKey + '_' + col.bodySubKey + '_' + rowIndex"
  114. :fieldItemLabel="fieldItemLabel" type="select" class="body-select"
  115. @blur="onSubBlur(rowIndex, col.bodySubKey, $event)"
  116. :item="getBodySubItem(col, rowIndex)" v-model="row[col.bodySubKey]"
  117. @change="onBodySubValueChange(rowIndex, colIndex, $event)"
  118. :error="hasError(rowIndex, colIndex, col.bodySubKey)"
  119. @update:error="onErrorUpdate(rowIndex, colIndex, col.bodySubKey, $event)"
  120. :orange-bg="hasOrangeBg(rowIndex, colIndex, col.bodySubKey)" />
  121. </template>
  122. <template v-else-if="col.bodySubType === 'span'">
  123. <div class="body-span">
  124. {{ row[col.bodySubKey] }}
  125. </div>
  126. </template>
  127. </div>
  128. </div>
  129. </div>
  130. <!-- 默认操作栏 -->
  131. <div class="custom-table-cell body-cell" :style="{ width: '245px' }" v-if="showOperation">
  132. <div class="inner-table-cell">
  133. <slot name="operation" :row="row" :rowIndex="rowIndex" :columns="getOperationColumns()">
  134. </slot>
  135. </div>
  136. </div>
  137. </div>
  138. </div>
  139. <div v-if="localDataSource.length == 0">
  140. <div class="no-data">暂无数据</div>
  141. </div>
  142. </div>
  143. <div class="add-row" v-if="isShowAddRos()">
  144. <el-button type="primary" plain @click="onAddRow">添加行</el-button>
  145. </div>
  146. </div>
  147. </template>
  148. <script>
  149. import HandleFormItem from "./HandleFormItem.vue";
  150. import { isEqual } from "@/utils/index.js";
  151. import { isShowOther } from "@/utils/formPackageCommon.js";
  152. import { EventBus } from "@/utils/eventBus";
  153. import { getuuid } from "@/utils/index.js";
  154. import moment from "moment";
  155. import _ from "lodash";
  156. export default {
  157. inject: ['templateFillType', 'getZdxgjl', 'updateZdxgjl'],
  158. name: 'CustomTable',
  159. components: {
  160. HandleFormItem
  161. },
  162. props: {
  163. // 是否显示表头选择器
  164. showHeaderSelect: {
  165. type: Boolean,
  166. default: false,
  167. },
  168. showAddRow: {
  169. type: Boolean,
  170. default: undefined,
  171. },
  172. // 是否显示操作栏
  173. showOperation: {
  174. type: Boolean,
  175. default: true,
  176. },
  177. columns: {
  178. type: Array,
  179. required: true,
  180. // 示例格式:
  181. // [
  182. // { label: '姓名', prop: 'name' },
  183. // { label: '状态', prop: 'status', type: 'select', options: [{value:1,label:'启用'},...], selected: null }
  184. // ]
  185. },
  186. formData: {
  187. type: Object,
  188. default: () => {
  189. return {
  190. stepTableFormData: [],
  191. headerSelectFields: {}
  192. }
  193. }
  194. },
  195. fieldItemLabel: {
  196. type: String,
  197. default: '',
  198. },
  199. //循环组件的情况下需要用这个来区分字段
  200. prefixKey: {
  201. type: String,
  202. default: "",
  203. }
  204. },
  205. data() {
  206. return {
  207. localDataSource: [],
  208. headerSelectFields: {},
  209. formErrors: [], // 表单错误状态管理
  210. orangeBgCells: {}, // 存储需要橙色背景的单元格 {rowIndex-colIndex: true/false}
  211. isShowOther,
  212. oldLocalDataSource: [],
  213. uuid: getuuid(),
  214. }
  215. },
  216. watch: {
  217. formData: {
  218. immediate: true,
  219. handler(newData) {
  220. const { stepTableFormData = [], headerSelectFields = {} } = newData;
  221. this.updateDataSource(stepTableFormData);
  222. this.headerSelectFields = JSON.parse(JSON.stringify(headerSelectFields));
  223. // 在数据加载后检查 compareTo 逻辑
  224. this.checkCompareToOnDataLoad();
  225. }
  226. },
  227. localDataSource: {
  228. immediate: true,
  229. deep: true,
  230. handler(newVal, oldVal) {
  231. // if(newVal.length == 0){
  232. // return
  233. // }
  234. // this.localDataSource = [...newVal];
  235. }
  236. }
  237. },
  238. mounted() {
  239. EventBus.$on('onEditSignCallback', this.handleEditSignCallback);
  240. EventBus.$on('onFormEditSignCancel', this.handleEditSignCancel);
  241. },
  242. unmounted() {
  243. this.oldLocalDataSource = [];
  244. EventBus.$off('onEditSignCallback', this.handleEditSignCallback);
  245. EventBus.$off('onFormEditSignCancel', this.handleEditSignCancel);
  246. },
  247. methods: {
  248. handleEditSignCancel(data) {
  249. if (data.uuid === this.uuid) {
  250. this.resetRecord();
  251. }
  252. },
  253. handleEditSignCallback(data) {
  254. if (data.uuid === this.uuid) {
  255. this.updateRecords();
  256. }
  257. },
  258. getRecords() {
  259. const records = [];
  260. const { nickName, name } = this.$store.getters;
  261. const { oldLocalDataSource, localDataSource, columns, prefixKey, fieldItemLabel } = this;
  262. localDataSource.forEach((row, rowIndex) => {
  263. const oldRow = oldLocalDataSource[rowIndex];
  264. if (!oldRow) {
  265. return;
  266. }
  267. columns.forEach((col, colIndex) => {
  268. const oldValue = oldRow[col.prop];
  269. const newValue = row[col.prop];
  270. if (!isEqual(oldValue, newValue)) {
  271. const fieldLabelCn = this.$i18n.t(col.label, "zh_CN");
  272. const fieldLabelEn = this.$i18n.t(col.label, "en_US");
  273. const record = {
  274. userNameCn: nickName,
  275. userNameEn: name,
  276. key: prefixKey + '_' + col.prop + '_' + rowIndex,
  277. fieldCn: `${this.$i18n.t(fieldItemLabel, "zh_CN")}-${fieldLabelCn}`,
  278. fieldEn: `${this.$i18n.t(fieldItemLabel, "en_US")}-${fieldLabelEn}`,
  279. oldValue: oldValue,
  280. value: newValue,
  281. title: oldValue ? "修改" : "提交",
  282. time: moment().format("YYYY-MM-DD HH:mm:ss"),
  283. };
  284. this.updateZdxgjl(record);
  285. records.push(record);
  286. }
  287. if (col.bodySubKey) {
  288. const oldSubValue = oldRow[col.bodySubKey];
  289. const newSubValue = row[col.bodySubKey];
  290. if (!isEqual(oldSubValue, newSubValue)) {
  291. const fieldLabelCn = this.$i18n.t(col.label, "zh_CN");
  292. const fieldLabelEn = this.$i18n.t(col.label, "en_US");
  293. const record = {
  294. userNameCn: nickName,
  295. userNameEn: name,
  296. key: prefixKey + '_' + col.bodySubKey + '_' + rowIndex,
  297. fieldCn: `${this.$i18n.t(fieldItemLabel, "zh_CN")}-${fieldLabelCn}单位`,
  298. fieldEn: `${this.$i18n.t(fieldItemLabel, "en_US")}-${fieldLabelEn}单位`,
  299. oldValue: oldSubValue,
  300. value: newSubValue,
  301. title: oldSubValue ? "修改" : "提交",
  302. time: moment().format("YYYY-MM-DD HH:mm:ss"),
  303. };
  304. this.updateZdxgjl(record);
  305. records.push(record);
  306. }
  307. }
  308. if (col.otherCode) {
  309. const oldOtherValue = oldRow[col.otherCode];
  310. const newOtherValue = row[col.otherCode];
  311. if (!isEqual(oldOtherValue, newOtherValue)) {
  312. const fieldLabelCn = this.$i18n.t(col.label, "zh_CN");
  313. const fieldLabelEn = this.$i18n.t(col.label, "en_US");
  314. const otherLabelCn = col.otherLabel ? this.$i18n.t(col.otherLabel, "zh_CN") : this.$i18n.t("template.common.other", "zh_CN");
  315. const otherLabelEn = col.otherLabel ? this.$i18n.t(col.otherLabel, "en_US") : this.$i18n.t("template.common.other", "en_US");
  316. const record = {
  317. userNameCn: nickName,
  318. userNameEn: name,
  319. key: prefixKey + '_' + col.otherCode + '_' + rowIndex,
  320. fieldCn: `${this.$i18n.t(fieldItemLabel, "zh_CN")}-${fieldLabelCn}${otherLabelCn}`,
  321. fieldEn: `${this.$i18n.t(fieldItemLabel, "en_US")}-${fieldLabelEn}${otherLabelEn}`,
  322. oldValue: oldOtherValue,
  323. value: newOtherValue,
  324. title: oldOtherValue ? "修改" : "提交",
  325. time: moment().format("YYYY-MM-DD HH:mm:ss"),
  326. };
  327. this.updateZdxgjl(record);
  328. records.push(record);
  329. }
  330. }
  331. });
  332. });
  333. return records;
  334. },
  335. updateRecords() {
  336. this.$nextTick(() => {
  337. const records = this.getRecords();
  338. const params = {
  339. type: "fieldChanged",
  340. newRecord: records,
  341. resourceList: this.getZdxgjl(),
  342. source: "customTable",
  343. }
  344. if (records.length == 0) {
  345. return;
  346. }
  347. console.log(records, "records")
  348. EventBus.$emit('onModifyRecord', params);
  349. this.oldLocalDataSource = [];
  350. })
  351. },
  352. //取消按钮 重置记录
  353. resetRecord(rowIndex, colIndex,) {
  354. if (this.localDataSource.length) {
  355. this.localDataSource = [...this.oldLocalDataSource];
  356. this.oldLocalDataSource = [];
  357. }
  358. },
  359. //获取操作栏的列
  360. getOperationColumns() {
  361. return { columnsData: this.columns, headerSelectFields: this.headerSelectFields }
  362. },
  363. //获取其他下拉框的配置
  364. getOtherItem(sItem) {
  365. return {
  366. label: sItem.otherLabel ? this.$t(sItem.otherLabel) : this.$t("template.common.other"),
  367. fillType: sItem.bodyFillType,
  368. maxlength: sItem.otherMaxlength || 50,
  369. parentLabel: sItem.label,
  370. type: "input"
  371. }
  372. },
  373. isShowBodySub(col, row) {
  374. if (col.hasOwnProperty("showBodySub")) {
  375. return col.showBodySub
  376. } else if (col.bodySubType === 'span' && !row[col.bodySubKey]) {//如果是span没有值的话就隐藏
  377. return false;
  378. }
  379. return col.bodySubType && col.bodySubKey;
  380. },
  381. // 点击事件
  382. handleClickable(col, rowIndex, colIndex, row) {
  383. console.log("clickable", rowIndex, colIndex, col, row)
  384. if (this.templateFillType !== 'actFill') {
  385. return
  386. }
  387. this.$emit("clickable", col, rowIndex, row)
  388. },
  389. isShowAddRos() {
  390. if (this.showAddRow !== undefined) {
  391. return this.showAddRow
  392. }
  393. return this.templateFillType === 'preFill';
  394. },
  395. // 复制值
  396. onCopy(rowIndex, col) {
  397. if (col.copyFrom) {
  398. if (!this.localDataSource[rowIndex][col.copyFrom]) {//没有值就不用复制了
  399. return
  400. }
  401. this.$set(this.localDataSource[rowIndex], col.prop, this.localDataSource[rowIndex][col.copyFrom])
  402. this.onBlur(rowIndex, col.prop, this.localDataSource[rowIndex][col.prop]);
  403. }
  404. },
  405. // 初始化表头选择器值
  406. initHeaderSelectValues() {
  407. const headerSelectObj = {};
  408. this.columns.map(col => {
  409. if (col.headerSelectKey) {
  410. headerSelectObj[col.headerSelectKey] = col.defaultValue || col.headerOptions[0].value || ""
  411. }
  412. });
  413. this.headerSelectFields = headerSelectObj;
  414. },
  415. // 直接获取表单数据,不做校验
  416. getFilledFormData() {
  417. return {
  418. stepTableFormData: [...this.localDataSource],
  419. headerSelectFields: this.headerSelectFields,
  420. };
  421. },
  422. // 获取最新数据
  423. getFormData() {
  424. // 合并表头选择器值到 columns
  425. // 数据校验
  426. const validateResult = this.validateFormData();
  427. return new Promise((resolve, reject) => {
  428. if (validateResult.valid) {
  429. resolve({
  430. stepTableFormData: [...this.localDataSource],
  431. headerSelectFields: this.headerSelectFields,
  432. })
  433. } else {
  434. // this.$message.error("表单内容未填完,请填写后再提交");
  435. reject(validateResult.errors[0].error)
  436. }
  437. })
  438. },
  439. // 表单数据校验
  440. validateFormData() {
  441. const errors = [];
  442. // 清空之前的错误状态
  443. this.formErrors = [];
  444. // 校验表头的 HandleFormItem
  445. this.columns.forEach((col, colIndex) => {
  446. if (col.headerSelectKey && col.headerOptions && col.fillType === this.templateFillType) {
  447. const headerValue = this.headerSelectFields[col.headerSelectKey];
  448. if (this.isValueEmpty(headerValue)) {
  449. const errorItem = {
  450. rowIndex: -1, // 表头特殊标记
  451. colIndex,
  452. field: col.headerSelectKey,
  453. label: this.$t(col.label),
  454. error: `请选择${this.$t(col.label)}`
  455. };
  456. errors.push(errorItem);
  457. this.formErrors.push(errorItem);
  458. }
  459. }
  460. });
  461. // 遍历数据行
  462. this.localDataSource.forEach((row, rowIndex) => {
  463. // 遍历列
  464. this.columns.forEach((col, colIndex) => {
  465. // 只校验 fillType 与当前模板状态匹配的字段
  466. if (col.bodyFillType === this.templateFillType || col.bodySubFillType === this.templateFillType) {
  467. // 检查主字段
  468. const mainValue = row[col.prop];
  469. if (this.isValueEmpty(mainValue) && !col.bodyDisabled && col.bodyType !== 'span') {
  470. const errorItem = {
  471. rowIndex,
  472. colIndex,
  473. field: col.prop,
  474. label: this.$t(col.label),
  475. error: `请填写${this.$t(col.label)}`
  476. };
  477. errors.push(errorItem);
  478. this.formErrors.push(errorItem);
  479. }
  480. // 检查子字段(如果有)
  481. if (col.bodySubKey && !col.bodySubDisabled && col.bodySubType !== 'span') {
  482. const subValue = row[col.bodySubKey];
  483. console.log(col, subValue, "subValue")
  484. if (this.isValueEmpty(subValue)) {
  485. const errorItem = {
  486. rowIndex,
  487. colIndex,
  488. field: col.bodySubKey,
  489. label: `${this.$t(col.label)}单位`,
  490. error: `请填写${this.$t(col.label)}单位`
  491. };
  492. errors.push(errorItem);
  493. this.formErrors.push(errorItem);
  494. }
  495. }
  496. console.log(col.otherCode, "col.otherCode")
  497. // 检查其他输入框
  498. if (col.otherCode) {
  499. const isSelectedOther = this.isShowOther(mainValue);
  500. if (!isSelectedOther) {
  501. return;
  502. }
  503. const otherValue = row[col.otherCode];
  504. if (this.isValueEmpty(otherValue)) {
  505. const errorItem = {
  506. rowIndex,
  507. colIndex,
  508. field: col.otherCode,
  509. label: `${this.$t(col.label)}单位`,
  510. error: `请填写${this.$t(col.otherLabel) ? this.$t(col.otherLabel) : '其他'}信息`
  511. };
  512. errors.push(errorItem);
  513. this.formErrors.push(errorItem);
  514. }
  515. }
  516. }
  517. });
  518. });
  519. console.log(errors, this.localDataSource, "errors")
  520. return {
  521. valid: errors.length === 0,
  522. errors: errors
  523. };
  524. },
  525. // 判断值是否为空
  526. isValueEmpty(value) {
  527. if (value === null || value === undefined || value === '') {
  528. return true;
  529. }
  530. if (typeof value === 'string' && value.trim() === '') {
  531. return true;
  532. }
  533. if (Array.isArray(value) && value.length === 0) {
  534. return true;
  535. }
  536. return false;
  537. },
  538. // 表头选择器变化
  539. onHeaderSelectChange(col, value) {
  540. if (col.headerSelectTo) {
  541. this.headerSelectFields[col.headerSelectTo] = value;
  542. }
  543. this.headerSelectFields[col.headerSelectKey] = value;
  544. // 输入时清除对应表单项的错误状态
  545. this.formErrors = this.formErrors.filter(error =>
  546. !(error.rowIndex === -1 &&
  547. error.field === col.headerSelectKey)
  548. );
  549. },
  550. // 检查并应用 compareTo 逻辑
  551. checkCompareToLogic(rowIndex, colIndex, colKey, value) {
  552. const col = this.columns[colIndex];
  553. // 检查主字段的 compareTo 逻辑
  554. if (col && col.bodyFillType === "actFill" && col.compareTo) {
  555. const compareToValue = this.localDataSource[rowIndex][col.compareTo];
  556. // 比较当前值和compareTo值,如果不相等则设置橙色背景
  557. if (value !== compareToValue) {
  558. this.setOrangeBg(rowIndex, colIndex, colKey, true);
  559. } else {
  560. // 相等则移除橙色背景
  561. this.setOrangeBg(rowIndex, colIndex, colKey, false);
  562. }
  563. }
  564. },
  565. // 在数据加载时检查 compareTo 逻辑
  566. checkCompareToOnDataLoad() {
  567. // 遍历所有行和列,检查 compareTo 逻辑
  568. this.localDataSource.forEach((row, rowIndex) => {
  569. this.columns.forEach((col, colIndex) => {
  570. const currentValue = row[col.prop];
  571. const compareToValue = row[col.compareTo];
  572. if (col.compareTo && currentValue && compareToValue) {
  573. // 比较当前值和compareTo值,如果不相等则设置橙色背景
  574. if (!isEqual(currentValue, compareToValue)) {
  575. this.setOrangeBg(rowIndex, colIndex, col.prop, true);
  576. } else {
  577. // 相等则移除橙色背景
  578. this.setOrangeBg(rowIndex, colIndex, col.prop, false);
  579. }
  580. }
  581. // 检查子字段的 compareTo 逻辑
  582. if (col.bodySubFillType === "actFill" && col.bodySubCompareTo) {
  583. const currentValue = row[col.bodySubKey];
  584. const compareToValue = row[col.bodySubCompareTo];
  585. if (!!currentValue && !!compareToValue) {
  586. // 比较当前值和compareTo值,如果不相等则设置橙色背景
  587. if (!isEqual(currentValue, compareToValue)) {
  588. this.setOrangeBg(rowIndex, colIndex, col.bodySubKey, true);
  589. } else {
  590. // 相等则移除橙色背景
  591. this.setOrangeBg(rowIndex, colIndex, col.bodySubKey, false);
  592. }
  593. }
  594. }
  595. });
  596. });
  597. },
  598. // 表体值变化
  599. onBodyValueChange(rowIndex, colIndex, value) {
  600. const col = this.columns[colIndex];
  601. this.localDataSource[rowIndex][col.prop] = value;
  602. // 检查并应用 compareTo 逻辑
  603. this.checkCompareToLogic(rowIndex, colIndex, col.prop, value);
  604. // 输入时清除对应表单项的错误状态
  605. this.formErrors = this.formErrors.filter(error =>
  606. !(error.rowIndex === rowIndex &&
  607. error.colIndex === colIndex &&
  608. error.field === col.prop)
  609. );
  610. this.$emit('body-value-change', rowIndex, colIndex, value);
  611. },
  612. // 表体子值变化
  613. onBodySubValueChange(rowIndex, colIndex, value) {
  614. const col = this.columns[colIndex];
  615. this.localDataSource[rowIndex][col.bodySubKey] = value;
  616. // 检查子字段的 compareTo 逻辑
  617. if (col && col.bodySubFillType === "actFill" && col.bodySubCompareTo) {
  618. const compareToValue = this.localDataSource[rowIndex][col.bodySubCompareTo];
  619. // 比较当前值和compareTo值,如果不相等则设置橙色背景
  620. if (value !== compareToValue) {
  621. this.setOrangeBg(rowIndex, colIndex, col.bodySubKey, true);
  622. } else {
  623. // 相等则移除橙色背景
  624. this.setOrangeBg(rowIndex, colIndex, col.bodySubKey, false);
  625. }
  626. }
  627. // 输入时清除对应表单项的错误状态
  628. this.formErrors = this.formErrors.filter(error =>
  629. !(error.rowIndex === rowIndex &&
  630. error.colIndex === colIndex &&
  631. error.field === col.bodySubKey)
  632. );
  633. this.$emit('body-sub-value-change', rowIndex, colIndex, value);
  634. },
  635. getHeaderItem(col) {
  636. return {
  637. fillType: col.fillType,
  638. options: col.headerOptions,
  639. label: ""
  640. }
  641. },
  642. getBodyItem(col, rowIndex) {
  643. const currentItem = this.localDataSource[rowIndex];
  644. const item = {
  645. fillType: col.bodyFillType,
  646. options: col.bodyOptions,
  647. maxlength: col.bodyMaxlength,
  648. label: this.$t(col.label),
  649. precision: currentItem[col.bodyPrecisionKey] || col.precision,
  650. copyFrom: col.copyFrom || "",
  651. compareTo: col.compareTo, // 添加 compareTo 字段
  652. type: col.bodyType || "input",
  653. };
  654. if (col.bodyDisabled) {
  655. item.disabled = col.bodyDisabled;
  656. }
  657. return item
  658. },
  659. getBodySubItem(col) {
  660. const item = {
  661. fillType: col.bodySubFillType,
  662. options: col.bodySubOptions,
  663. maxlength: col.bodySubMaxlength || 10,
  664. label: "",
  665. placeholder: col.bodySubPlaceholder || (col.bodySubType === 'select' ? '请选择' : '请输入'),
  666. precision: col.subPrecision,
  667. compareTo: col.bodySubCompareTo, // 添加 compareTo 字段
  668. type: col.bodySubType || "input",
  669. }
  670. if (col.bodySubDisabled) {
  671. item.disabled = col.bodySubDisabled;
  672. }
  673. return item
  674. },
  675. // 删除行
  676. deleteRow(rowIndex) {
  677. this.localDataSource.splice(rowIndex, 1);
  678. this.$emit('row-delete', rowIndex);
  679. },
  680. // 更新数据方法,可在formData变更时调用,也可由父组件调用
  681. updateDataSource(dataSource = []) {
  682. this.oldLocalDataSource = JSON.parse(JSON.stringify(this.localDataSource));
  683. // 深拷贝数据以避免直接修改原始数据
  684. this.localDataSource = JSON.parse(JSON.stringify(dataSource || []));
  685. },
  686. // 根据行索引更新数据 autoUpdateRecord 是否自动更新记录
  687. updateDataSourceByRowIndex(rowIndex, data, type) {
  688. this.oldLocalDataSource = JSON.parse(JSON.stringify(this.localDataSource));
  689. if (type === "clickable") {//如果是custom内部的clickable需要判断是否弹窗
  690. this.showEditSignDialog(rowIndex,data);
  691. }else if(type === "blur"){
  692. this.updateRecords();
  693. }
  694. this.localDataSource[rowIndex] = { ...this.localDataSource[rowIndex], ...data };
  695. this.localDataSource = [...this.localDataSource];
  696. },
  697. showEditSignDialog: _.debounce(function (rowIndex, data) {
  698. const oldData = this.oldLocalDataSource[rowIndex];
  699. let isFirst = false;//是否是第一次添加,是否和旧数据相同
  700. const isSame = this.compareOldAndCurrentFormFields(data, oldData);
  701. // 遍历data对象,和oldData比较,判断data的key值在对应oldData里面是否有值
  702. for (const key in data) {
  703. if (!oldData[key] && oldData[key] != 0) {
  704. isFirst = true;
  705. break;
  706. }
  707. }
  708. if (!isFirst && !isSame && this.templateFillType === "actFill") {
  709. console.log("showww")
  710. setTimeout(() => {
  711. EventBus.$emit('showEditSignDialog', { uuid: this.uuid });
  712. }, 100);
  713. } else {
  714. console.log("not show")
  715. this.updateRecords();
  716. }
  717. }, 100),
  718. // 比较newData和oldData的值是否相等,只要有一对不相等就返回false
  719. compareOldAndCurrentFormFields(newData, oldData) {
  720. for (const key in newData) {
  721. const oldValue = newData[key];
  722. const currentValue = oldData[key];
  723. if (JSON.stringify(oldValue) !== JSON.stringify(currentValue)) {
  724. return false;
  725. } else {
  726. return false;
  727. }
  728. }
  729. return true;
  730. },
  731. onAddRow() {
  732. this.addRow({
  733. actSolutionVolumePrecision: 3,//小数点精度默认为3
  734. actSolutionConcentrationPrecision: 3,//小数点精度默认为3
  735. targetDiluentVolumePrecision: 3,//小数点精度默认为3
  736. targetStartSolutionVolumePrecision: 3,//小数点精度默认为3
  737. });
  738. },
  739. // 添加行
  740. addRow(row = {}) {
  741. this.localDataSource.push(row);
  742. },
  743. getDataSource() {
  744. return this.localDataSource;
  745. },
  746. // 判断表单项是否有错误
  747. hasError(rowIndex, colIndex, field) {
  748. return this.formErrors.some(error =>
  749. error.rowIndex === rowIndex &&
  750. error.colIndex === colIndex &&
  751. error.field === field
  752. );
  753. },
  754. // 处理错误状态更新
  755. onErrorUpdate(rowIndex, colIndex, field, isError) {
  756. if (!isError) {
  757. this.formErrors = this.formErrors.filter(error =>
  758. !(error.rowIndex === rowIndex &&
  759. error.colIndex === colIndex &&
  760. error.field === field)
  761. );
  762. }
  763. },
  764. onSubBlur(rowIndex, colKey, value) {
  765. this.$emit("blur", { rowIndex, colKey, value, item: this.localDataSource[rowIndex] });
  766. },
  767. // 检查是否需要橙色背景
  768. hasOrangeBg(rowIndex, colIndex, field) {
  769. const key = `${rowIndex}-${colIndex}-${field}`;
  770. return this.orangeBgCells[key] || false;
  771. },
  772. // 设置橙色背景状态
  773. setOrangeBg(rowIndex, colIndex, field, status) {
  774. const key = `${rowIndex}-${colIndex}-${field}`;
  775. this.$set(this.orangeBgCells, key, status);
  776. },
  777. onBlur(rowIndex, colKey) {
  778. const value = this.localDataSource[rowIndex][colKey];
  779. // 查找对应的列配置
  780. const col = this.columns.find(c => c.prop === colKey);
  781. if (col && col.bodyFillType === "actFill" && col.compareTo) {
  782. const compareToValue = this.localDataSource[rowIndex][col.compareTo];
  783. // 比较当前值和compareTo值,如果不相等则设置橙色背景
  784. if (value !== compareToValue) {
  785. this.setOrangeBg(rowIndex, this.columns.findIndex(c => c.prop === colKey), colKey, true);
  786. } else {
  787. // 相等则移除橙色背景
  788. this.setOrangeBg(rowIndex, this.columns.findIndex(c => c.prop === colKey), colKey, false);
  789. }
  790. }
  791. this.$emit("blur", { rowIndex, colKey, value, item: this.localDataSource[rowIndex] });
  792. },
  793. onSubBlur(rowIndex, colKey, value) {
  794. // 查找对应的列配置
  795. const col = this.columns.find(c => c.bodySubKey === colKey);
  796. if (col && col.bodySubFillType === "actFill" && col.bodySubCompareTo) {
  797. const compareToValue = this.localDataSource[rowIndex][col.bodySubCompareTo];
  798. // 比较当前值和compareTo值,如果不相等则设置橙色背景
  799. if (value !== compareToValue) {
  800. this.setOrangeBg(rowIndex, this.columns.findIndex(c => c.bodySubKey === colKey), colKey, true);
  801. } else {
  802. // 相等则移除橙色背景
  803. this.setOrangeBg(rowIndex, this.columns.findIndex(c => c.bodySubKey === colKey), colKey, false);
  804. }
  805. }
  806. this.$emit("blur", { rowIndex, colKey, value, item: this.localDataSource[rowIndex] });
  807. }
  808. }
  809. };
  810. </script>
  811. <style scoped>
  812. .custom-table-wrapper {
  813. border: 1px solid #ebeef5;
  814. border-radius: 4px;
  815. overflow: hidden;
  816. font-size: 14px;
  817. color: #606266;
  818. margin-top: 20px;
  819. }
  820. .inner-table-cell {
  821. display: flex;
  822. align-items: center;
  823. justify-content: center;
  824. }
  825. .m-l-5 {
  826. margin-left: 5px;
  827. }
  828. .sub-input-number {
  829. width: 145px;
  830. .el-input-number--mini {
  831. width: 145px;
  832. }
  833. }
  834. /* 表头 */
  835. .custom-table-header {
  836. background-color: #f5f7fa;
  837. border-bottom: 1px solid #ebeef5;
  838. white-space: nowrap;
  839. display: block;
  840. }
  841. .custom-table-body {
  842. max-height: 300px;
  843. /* 可根据需要调整或由父组件控制 */
  844. }
  845. .header-cell-content {
  846. display: flex;
  847. align-items: center;
  848. justify-content: center;
  849. }
  850. /* 共同行样式 */
  851. .custom-table-row {
  852. display: table;
  853. width: 100%;
  854. table-layout: fixed;
  855. }
  856. .custometable-row {
  857. display: table;
  858. width: 100%;
  859. table-layout: fixed;
  860. &:not(:last-child) {
  861. border-bottom: 1px solid #ebeef5;
  862. }
  863. }
  864. /* 单元格 */
  865. .custom-table-cell {
  866. display: table-cell;
  867. padding: 12px 10px;
  868. text-align: left;
  869. vertical-align: middle;
  870. border-right: 1px solid #ebeef5;
  871. box-sizing: border-box;
  872. }
  873. .custom-table-cell:last-child {
  874. border-right: none;
  875. }
  876. .header-cell {
  877. font-weight: bold;
  878. color: #909399;
  879. background-color: #f5f7fa;
  880. }
  881. .body-cell {
  882. color: #606266;
  883. background-color: #fff;
  884. }
  885. /* select 样式(模仿 Element UI) */
  886. .header-cell select {
  887. width: 100%;
  888. padding: 4px 8px;
  889. border: 1px solid #dcdfe6;
  890. border-radius: 4px;
  891. outline: none;
  892. background-color: #fff;
  893. font-size: 13px;
  894. color: #606266;
  895. appearance: none;
  896. /* 隐藏默认箭头(可选) */
  897. 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");
  898. background-repeat: no-repeat;
  899. background-position: right 8px center;
  900. background-size: 14px;
  901. padding-right: 28px;
  902. }
  903. /* 滚动容器:如果整体宽度超限,显示横向滚动条 */
  904. .custom-table-wrapper {
  905. display: flex;
  906. flex-direction: column;
  907. max-width: 100%;
  908. /* 父容器决定宽度 */
  909. overflow-x: auto;
  910. }
  911. .custom-table-header,
  912. .custom-table-body {
  913. min-width: 100%;
  914. }
  915. .header-select {
  916. width: 100px;
  917. margin-left: 5px;
  918. }
  919. .no-data {
  920. text-align: center;
  921. padding: 20px 0;
  922. color: rgb(144, 147, 153)
  923. }
  924. .add-row {
  925. display: flex;
  926. justify-content: center;
  927. padding: 20px 0;
  928. margin-top: 20px;
  929. }
  930. .flex1 {
  931. flex: 1;
  932. }
  933. .flex {
  934. display: flex;
  935. }
  936. .other-title {
  937. text-align: right;
  938. margin: 0 10px;
  939. font-size: 14px;
  940. font-weight: normal;
  941. color: #606266;
  942. width: auto;
  943. }
  944. .body-span {
  945. text-align: center;
  946. }
  947. </style>