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

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