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

856 lines
26 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. export default {
  153. inject: ['templateFillType'],
  154. name: 'CustomTable',
  155. components: {
  156. HandleFormItem
  157. },
  158. props: {
  159. // 是否显示表头选择器
  160. showHeaderSelect: {
  161. type: Boolean,
  162. default: false,
  163. },
  164. showAddRow: {
  165. type: Boolean,
  166. default: undefined,
  167. },
  168. // 是否显示操作栏
  169. showOperation: {
  170. type: Boolean,
  171. default: true,
  172. },
  173. columns: {
  174. type: Array,
  175. required: true,
  176. // 示例格式:
  177. // [
  178. // { label: '姓名', prop: 'name' },
  179. // { label: '状态', prop: 'status', type: 'select', options: [{value:1,label:'启用'},...], selected: null }
  180. // ]
  181. },
  182. formData: {
  183. type: Object,
  184. default: () => {
  185. return {
  186. stepTableFormData: [],
  187. headerSelectFields: {}
  188. }
  189. }
  190. },
  191. fieldItemLabel: {
  192. type: String,
  193. default: '',
  194. },
  195. //循环组件的情况下需要用这个来区分字段
  196. prefixKey: {
  197. type: String,
  198. default: "",
  199. }
  200. },
  201. data() {
  202. return {
  203. localDataSource: [],
  204. headerSelectFields: {},
  205. formErrors: [], // 表单错误状态管理
  206. orangeBgCells: {}, // 存储需要橙色背景的单元格 {rowIndex-colIndex: true/false}
  207. isShowOther,
  208. oldLocalDataSource: [],
  209. }
  210. },
  211. watch: {
  212. formData: {
  213. immediate: true,
  214. handler(newData) {
  215. const { stepTableFormData = [], headerSelectFields = {} } = newData;
  216. this.updateDataSource(stepTableFormData);
  217. this.headerSelectFields = JSON.parse(JSON.stringify(headerSelectFields));
  218. console.log(stepTableFormData,"stepTableFormData")
  219. // 在数据加载后检查 compareTo 逻辑
  220. this.checkCompareToOnDataLoad();
  221. }
  222. },
  223. localDataSource: {
  224. immediate: true,
  225. deep: true,
  226. handler(newVal, oldVal) {
  227. // if(newVal.length == 0){
  228. // return
  229. // }
  230. // this.localDataSource = [...newVal];
  231. }
  232. }
  233. },
  234. mounted() {
  235. // this.initHeaderSelectValues();
  236. },
  237. methods: {
  238. //取消按钮 重置记录
  239. resetRecord(rowIndex, colIndex,) {
  240. if(this.localDataSource.length){
  241. this.localDataSource = [...this.oldLocalDataSource];
  242. this.oldLocalDataSource = [];
  243. }
  244. },
  245. //获取操作栏的列
  246. getOperationColumns() {
  247. return { columnsData: this.columns, headerSelectFields: this.headerSelectFields }
  248. },
  249. //获取其他下拉框的配置
  250. getOtherItem(sItem) {
  251. return {
  252. label: sItem.otherLabel ? this.$t(sItem.otherLabel) : this.$t("template.common.other"),
  253. fillType: sItem.bodyFillType,
  254. maxlength: sItem.otherMaxlength || 50,
  255. parentLabel: sItem.label,
  256. type: "input"
  257. }
  258. },
  259. isShowBodySub(col, row) {
  260. if (col.hasOwnProperty("showBodySub")) {
  261. return col.showBodySub
  262. } else if (col.bodySubType === 'span' && !row[col.bodySubKey]) {//如果是span没有值的话就隐藏
  263. return false;
  264. }
  265. return col.bodySubType && col.bodySubKey;
  266. },
  267. // 点击事件
  268. handleClickable(col, rowIndex, colIndex, row) {
  269. console.log("clickable", rowIndex, colIndex, col, row)
  270. if (this.templateFillType !== 'actFill') {
  271. return
  272. }
  273. this.$emit("clickable", col, rowIndex, row)
  274. },
  275. isShowAddRos() {
  276. if (this.showAddRow !== undefined) {
  277. return this.showAddRow
  278. }
  279. return this.templateFillType === 'preFill';
  280. },
  281. // 复制值
  282. onCopy(rowIndex, col) {
  283. if (col.copyFrom) {
  284. if (!this.localDataSource[rowIndex][col.copyFrom]) {//没有值就不用复制了
  285. return
  286. }
  287. this.$set(this.localDataSource[rowIndex], col.prop, this.localDataSource[rowIndex][col.copyFrom])
  288. this.onBlur(rowIndex, col.prop, this.localDataSource[rowIndex][col.prop]);
  289. }
  290. },
  291. // 初始化表头选择器值
  292. initHeaderSelectValues() {
  293. const headerSelectObj = {};
  294. this.columns.map(col => {
  295. if (col.headerSelectKey) {
  296. headerSelectObj[col.headerSelectKey] = col.defaultValue || col.headerOptions[0].value || ""
  297. }
  298. });
  299. this.headerSelectFields = headerSelectObj;
  300. },
  301. // 直接获取表单数据,不做校验
  302. getFilledFormData() {
  303. return {
  304. stepTableFormData: [...this.localDataSource],
  305. headerSelectFields: this.headerSelectFields,
  306. };
  307. },
  308. // 获取最新数据
  309. getFormData() {
  310. // 合并表头选择器值到 columns
  311. // 数据校验
  312. const validateResult = this.validateFormData();
  313. return new Promise((resolve, reject) => {
  314. if (validateResult.valid) {
  315. resolve({
  316. stepTableFormData: [...this.localDataSource],
  317. headerSelectFields: this.headerSelectFields,
  318. })
  319. } else {
  320. // this.$message.error("表单内容未填完,请填写后再提交");
  321. reject(validateResult.errors[0].error)
  322. }
  323. })
  324. },
  325. // 表单数据校验
  326. validateFormData() {
  327. const errors = [];
  328. // 清空之前的错误状态
  329. this.formErrors = [];
  330. // 校验表头的 HandleFormItem
  331. this.columns.forEach((col, colIndex) => {
  332. if (col.headerSelectKey && col.headerOptions && col.fillType === this.templateFillType) {
  333. const headerValue = this.headerSelectFields[col.headerSelectKey];
  334. if (this.isValueEmpty(headerValue)) {
  335. const errorItem = {
  336. rowIndex: -1, // 表头特殊标记
  337. colIndex,
  338. field: col.headerSelectKey,
  339. label: this.$t(col.label),
  340. error: `请选择${this.$t(col.label)}`
  341. };
  342. errors.push(errorItem);
  343. this.formErrors.push(errorItem);
  344. }
  345. }
  346. });
  347. // 遍历数据行
  348. this.localDataSource.forEach((row, rowIndex) => {
  349. // 遍历列
  350. this.columns.forEach((col, colIndex) => {
  351. // 只校验 fillType 与当前模板状态匹配的字段
  352. if (col.bodyFillType === this.templateFillType || col.bodySubFillType === this.templateFillType) {
  353. // 检查主字段
  354. const mainValue = row[col.prop];
  355. if (this.isValueEmpty(mainValue) && !col.bodyDisabled && col.bodyType !== 'span') {
  356. const errorItem = {
  357. rowIndex,
  358. colIndex,
  359. field: col.prop,
  360. label: this.$t(col.label),
  361. error: `请填写${this.$t(col.label)}`
  362. };
  363. errors.push(errorItem);
  364. this.formErrors.push(errorItem);
  365. }
  366. // 检查子字段(如果有)
  367. if (col.bodySubKey && !col.bodySubDisabled && col.bodySubType !== 'span') {
  368. const subValue = row[col.bodySubKey];
  369. console.log(col, subValue, "subValue")
  370. if (this.isValueEmpty(subValue)) {
  371. const errorItem = {
  372. rowIndex,
  373. colIndex,
  374. field: col.bodySubKey,
  375. label: `${this.$t(col.label)}单位`,
  376. error: `请填写${this.$t(col.label)}单位`
  377. };
  378. errors.push(errorItem);
  379. this.formErrors.push(errorItem);
  380. }
  381. }
  382. console.log(col.otherCode, "col.otherCode")
  383. // 检查其他输入框
  384. if (col.otherCode) {
  385. const isSelectedOther = this.isShowOther(mainValue);
  386. if (!isSelectedOther) {
  387. return;
  388. }
  389. const otherValue = row[col.otherCode];
  390. if (this.isValueEmpty(otherValue)) {
  391. const errorItem = {
  392. rowIndex,
  393. colIndex,
  394. field: col.otherCode,
  395. label: `${this.$t(col.label)}单位`,
  396. error: `请填写${this.$t(col.otherLabel) ? this.$t(col.otherLabel) : '其他'}信息`
  397. };
  398. errors.push(errorItem);
  399. this.formErrors.push(errorItem);
  400. }
  401. }
  402. }
  403. });
  404. });
  405. console.log(errors, this.localDataSource, "errors")
  406. return {
  407. valid: errors.length === 0,
  408. errors: errors
  409. };
  410. },
  411. // 判断值是否为空
  412. isValueEmpty(value) {
  413. if (value === null || value === undefined || value === '') {
  414. return true;
  415. }
  416. if (typeof value === 'string' && value.trim() === '') {
  417. return true;
  418. }
  419. if (Array.isArray(value) && value.length === 0) {
  420. return true;
  421. }
  422. return false;
  423. },
  424. // 表头选择器变化
  425. onHeaderSelectChange(col, value) {
  426. if(col.headerSelectTo){
  427. this.headerSelectFields[col.headerSelectTo] = value;
  428. }
  429. this.headerSelectFields[col.headerSelectKey] = value;
  430. // 输入时清除对应表单项的错误状态
  431. this.formErrors = this.formErrors.filter(error =>
  432. !(error.rowIndex === -1 &&
  433. error.field === col.headerSelectKey)
  434. );
  435. },
  436. // 检查并应用 compareTo 逻辑
  437. checkCompareToLogic(rowIndex, colIndex, colKey, value) {
  438. const col = this.columns[colIndex];
  439. // 检查主字段的 compareTo 逻辑
  440. if (col && col.bodyFillType === "actFill" && col.compareTo) {
  441. const compareToValue = this.localDataSource[rowIndex][col.compareTo];
  442. // 比较当前值和compareTo值,如果不相等则设置橙色背景
  443. if (value !== compareToValue) {
  444. this.setOrangeBg(rowIndex, colIndex, colKey, true);
  445. } else {
  446. // 相等则移除橙色背景
  447. this.setOrangeBg(rowIndex, colIndex, colKey, false);
  448. }
  449. }
  450. },
  451. // 在数据加载时检查 compareTo 逻辑
  452. checkCompareToOnDataLoad() {
  453. // 遍历所有行和列,检查 compareTo 逻辑
  454. this.localDataSource.forEach((row, rowIndex) => {
  455. this.columns.forEach((col, colIndex) => {
  456. const currentValue = row[col.prop];
  457. const compareToValue = row[col.compareTo];
  458. if (col.compareTo && currentValue && compareToValue) {
  459. // 比较当前值和compareTo值,如果不相等则设置橙色背景
  460. if (!isEqual(currentValue, compareToValue)) {
  461. this.setOrangeBg(rowIndex, colIndex, col.prop, true);
  462. } else {
  463. // 相等则移除橙色背景
  464. this.setOrangeBg(rowIndex, colIndex, col.prop, false);
  465. }
  466. }
  467. // 检查子字段的 compareTo 逻辑
  468. if (col.bodySubFillType === "actFill" && col.bodySubCompareTo) {
  469. const currentValue = row[col.bodySubKey];
  470. const compareToValue = row[col.bodySubCompareTo];
  471. if (!!currentValue && !!compareToValue) {
  472. // 比较当前值和compareTo值,如果不相等则设置橙色背景
  473. if (!isEqual(currentValue, compareToValue)) {
  474. this.setOrangeBg(rowIndex, colIndex, col.bodySubKey, true);
  475. } else {
  476. // 相等则移除橙色背景
  477. this.setOrangeBg(rowIndex, colIndex, col.bodySubKey, false);
  478. }
  479. }
  480. }
  481. });
  482. });
  483. },
  484. // 表体值变化
  485. onBodyValueChange(rowIndex, colIndex, value) {
  486. const col = this.columns[colIndex];
  487. this.localDataSource[rowIndex][col.prop] = value;
  488. // 检查并应用 compareTo 逻辑
  489. this.checkCompareToLogic(rowIndex, colIndex, col.prop, value);
  490. // 输入时清除对应表单项的错误状态
  491. this.formErrors = this.formErrors.filter(error =>
  492. !(error.rowIndex === rowIndex &&
  493. error.colIndex === colIndex &&
  494. error.field === col.prop)
  495. );
  496. this.$emit('body-value-change', rowIndex, colIndex, value);
  497. },
  498. // 表体子值变化
  499. onBodySubValueChange(rowIndex, colIndex, value) {
  500. const col = this.columns[colIndex];
  501. this.localDataSource[rowIndex][col.bodySubKey] = value;
  502. // 检查子字段的 compareTo 逻辑
  503. if (col && col.bodySubFillType === "actFill" && col.bodySubCompareTo) {
  504. const compareToValue = this.localDataSource[rowIndex][col.bodySubCompareTo];
  505. // 比较当前值和compareTo值,如果不相等则设置橙色背景
  506. if (value !== compareToValue) {
  507. this.setOrangeBg(rowIndex, colIndex, col.bodySubKey, true);
  508. } else {
  509. // 相等则移除橙色背景
  510. this.setOrangeBg(rowIndex, colIndex, col.bodySubKey, false);
  511. }
  512. }
  513. // 输入时清除对应表单项的错误状态
  514. this.formErrors = this.formErrors.filter(error =>
  515. !(error.rowIndex === rowIndex &&
  516. error.colIndex === colIndex &&
  517. error.field === col.bodySubKey)
  518. );
  519. this.$emit('body-sub-value-change', rowIndex, colIndex, value);
  520. },
  521. getHeaderItem(col) {
  522. return {
  523. fillType: col.fillType,
  524. options: col.headerOptions,
  525. label: ""
  526. }
  527. },
  528. getBodyItem(col, rowIndex) {
  529. const currentItem = this.localDataSource[rowIndex];
  530. const item = {
  531. fillType: col.bodyFillType,
  532. options: col.bodyOptions,
  533. maxlength: col.bodyMaxlength,
  534. label: this.$t(col.label),
  535. precision: currentItem[col.bodyPrecisionKey] || col.precision,
  536. copyFrom: col.copyFrom || "",
  537. compareTo: col.compareTo, // 添加 compareTo 字段
  538. type: col.bodyType || "input",
  539. };
  540. if (col.bodyDisabled) {
  541. item.disabled = col.bodyDisabled;
  542. }
  543. return item
  544. },
  545. getBodySubItem(col) {
  546. const item = {
  547. fillType: col.bodySubFillType,
  548. options: col.bodySubOptions,
  549. maxlength: col.bodySubMaxlength || 10,
  550. label: "",
  551. placeholder: col.bodySubPlaceholder || (col.bodySubType === 'select' ? '请选择' : '请输入'),
  552. precision: col.subPrecision,
  553. compareTo: col.bodySubCompareTo, // 添加 compareTo 字段
  554. type: col.bodySubType || "input",
  555. }
  556. if (col.bodySubDisabled) {
  557. item.disabled = col.bodySubDisabled;
  558. }
  559. return item
  560. },
  561. // 删除行
  562. deleteRow(rowIndex) {
  563. this.localDataSource.splice(rowIndex, 1);
  564. this.$emit('row-delete', rowIndex);
  565. },
  566. // 更新数据方法,可在formData变更时调用,也可由父组件调用
  567. updateDataSource(dataSource = []) {
  568. this.oldLocalDataSource = JSON.parse(JSON.stringify(this.localDataSource));
  569. console.log(this.oldLocalDataSource, "oldLocalDataSource")
  570. // 深拷贝数据以避免直接修改原始数据
  571. this.localDataSource = JSON.parse(JSON.stringify(dataSource || []));
  572. },
  573. onAddRow() {
  574. this.addRow({
  575. actSolutionVolumePrecision: 3,//小数点精度默认为3
  576. actSolutionConcentrationPrecision: 3,//小数点精度默认为3
  577. targetDiluentVolumePrecision: 3,//小数点精度默认为3
  578. targetStartSolutionVolumePrecision: 3,//小数点精度默认为3
  579. });
  580. },
  581. // 添加行
  582. addRow(row = {}) {
  583. this.localDataSource.push(row);
  584. },
  585. getDataSource() {
  586. return this.localDataSource;
  587. },
  588. // 根据行索引更新数据
  589. updateDataSourceByRowIndex(rowIndex, data) {
  590. this.oldLocalDataSource = JSON.parse(JSON.stringify(this.localDataSource));
  591. this.localDataSource[rowIndex] = { ...this.localDataSource[rowIndex], ...data };
  592. this.localDataSource = [...this.localDataSource];
  593. },
  594. // 判断表单项是否有错误
  595. hasError(rowIndex, colIndex, field) {
  596. return this.formErrors.some(error =>
  597. error.rowIndex === rowIndex &&
  598. error.colIndex === colIndex &&
  599. error.field === field
  600. );
  601. },
  602. // 处理错误状态更新
  603. onErrorUpdate(rowIndex, colIndex, field, isError) {
  604. if (!isError) {
  605. this.formErrors = this.formErrors.filter(error =>
  606. !(error.rowIndex === rowIndex &&
  607. error.colIndex === colIndex &&
  608. error.field === field)
  609. );
  610. }
  611. },
  612. onSubBlur(rowIndex, colKey, value) {
  613. this.$emit("blur", { rowIndex, colKey, value, item: this.localDataSource[rowIndex] });
  614. },
  615. // 检查是否需要橙色背景
  616. hasOrangeBg(rowIndex, colIndex, field) {
  617. const key = `${rowIndex}-${colIndex}-${field}`;
  618. return this.orangeBgCells[key] || false;
  619. },
  620. // 设置橙色背景状态
  621. setOrangeBg(rowIndex, colIndex, field, status) {
  622. const key = `${rowIndex}-${colIndex}-${field}`;
  623. this.$set(this.orangeBgCells, key, status);
  624. },
  625. onBlur(rowIndex, colKey) {
  626. const value = this.localDataSource[rowIndex][colKey];
  627. // 查找对应的列配置
  628. const col = this.columns.find(c => c.prop === colKey);
  629. if (col && col.bodyFillType === "actFill" && col.compareTo) {
  630. const compareToValue = this.localDataSource[rowIndex][col.compareTo];
  631. // 比较当前值和compareTo值,如果不相等则设置橙色背景
  632. if (value !== compareToValue) {
  633. this.setOrangeBg(rowIndex, this.columns.findIndex(c => c.prop === colKey), colKey, true);
  634. } else {
  635. // 相等则移除橙色背景
  636. this.setOrangeBg(rowIndex, this.columns.findIndex(c => c.prop === colKey), colKey, false);
  637. }
  638. }
  639. this.$emit("blur", { rowIndex, colKey, value, item: this.localDataSource[rowIndex] });
  640. },
  641. onSubBlur(rowIndex, colKey, value) {
  642. // 查找对应的列配置
  643. const col = this.columns.find(c => c.bodySubKey === colKey);
  644. if (col && col.bodySubFillType === "actFill" && col.bodySubCompareTo) {
  645. const compareToValue = this.localDataSource[rowIndex][col.bodySubCompareTo];
  646. // 比较当前值和compareTo值,如果不相等则设置橙色背景
  647. if (value !== compareToValue) {
  648. this.setOrangeBg(rowIndex, this.columns.findIndex(c => c.bodySubKey === colKey), colKey, true);
  649. } else {
  650. // 相等则移除橙色背景
  651. this.setOrangeBg(rowIndex, this.columns.findIndex(c => c.bodySubKey === colKey), colKey, false);
  652. }
  653. }
  654. this.$emit("blur", { rowIndex, colKey, value, item: this.localDataSource[rowIndex] });
  655. }
  656. }
  657. };
  658. </script>
  659. <style scoped>
  660. .custom-table-wrapper {
  661. border: 1px solid #ebeef5;
  662. border-radius: 4px;
  663. overflow: hidden;
  664. font-size: 14px;
  665. color: #606266;
  666. margin-top: 20px;
  667. }
  668. .inner-table-cell {
  669. display: flex;
  670. align-items: center;
  671. justify-content: center;
  672. }
  673. .m-l-5 {
  674. margin-left: 5px;
  675. }
  676. .sub-input-number {
  677. width: 145px;
  678. .el-input-number--mini {
  679. width: 145px;
  680. }
  681. }
  682. /* 表头 */
  683. .custom-table-header {
  684. background-color: #f5f7fa;
  685. border-bottom: 1px solid #ebeef5;
  686. white-space: nowrap;
  687. display: block;
  688. }
  689. .custom-table-body {
  690. max-height: 300px;
  691. /* 可根据需要调整或由父组件控制 */
  692. }
  693. .header-cell-content {
  694. display: flex;
  695. align-items: center;
  696. justify-content: center;
  697. }
  698. /* 共同行样式 */
  699. .custom-table-row {
  700. display: table;
  701. width: 100%;
  702. table-layout: fixed;
  703. }
  704. .custometable-row {
  705. display: table;
  706. width: 100%;
  707. table-layout: fixed;
  708. &:not(:last-child) {
  709. border-bottom: 1px solid #ebeef5;
  710. }
  711. }
  712. /* 单元格 */
  713. .custom-table-cell {
  714. display: table-cell;
  715. padding: 12px 10px;
  716. text-align: left;
  717. vertical-align: middle;
  718. border-right: 1px solid #ebeef5;
  719. box-sizing: border-box;
  720. }
  721. .custom-table-cell:last-child {
  722. border-right: none;
  723. }
  724. .header-cell {
  725. font-weight: bold;
  726. color: #909399;
  727. background-color: #f5f7fa;
  728. }
  729. .body-cell {
  730. color: #606266;
  731. background-color: #fff;
  732. }
  733. /* select 样式(模仿 Element UI) */
  734. .header-cell select {
  735. width: 100%;
  736. padding: 4px 8px;
  737. border: 1px solid #dcdfe6;
  738. border-radius: 4px;
  739. outline: none;
  740. background-color: #fff;
  741. font-size: 13px;
  742. color: #606266;
  743. appearance: none;
  744. /* 隐藏默认箭头(可选) */
  745. 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");
  746. background-repeat: no-repeat;
  747. background-position: right 8px center;
  748. background-size: 14px;
  749. padding-right: 28px;
  750. }
  751. /* 滚动容器:如果整体宽度超限,显示横向滚动条 */
  752. .custom-table-wrapper {
  753. display: flex;
  754. flex-direction: column;
  755. max-width: 100%;
  756. /* 父容器决定宽度 */
  757. overflow-x: auto;
  758. }
  759. .custom-table-header,
  760. .custom-table-body {
  761. min-width: 100%;
  762. }
  763. .header-select {
  764. width: 100px;
  765. margin-left: 5px;
  766. }
  767. .no-data {
  768. text-align: center;
  769. padding: 20px 0;
  770. color: rgb(144, 147, 153)
  771. }
  772. .add-row {
  773. display: flex;
  774. justify-content: center;
  775. padding: 20px 0;
  776. margin-top: 20px;
  777. }
  778. .flex1 {
  779. flex: 1;
  780. }
  781. .flex {
  782. display: flex;
  783. }
  784. .other-title {
  785. text-align: right;
  786. margin: 0 10px;
  787. font-size: 14px;
  788. font-weight: normal;
  789. color: #606266;
  790. width: auto;
  791. }
  792. .body-span {
  793. text-align: center;
  794. }
  795. </style>