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

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