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

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