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

1328 lines
42 KiB

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