新增MES混炼示方模块,包括主表及子表结构、控制器、服务和映射器的实现,支持增删改查功能,优化数据验证和用户体验,增强系统稳定性。

This commit is contained in:
geht
2026-05-22 16:34:33 +08:00
parent 680eb6c54c
commit d7fd9c6037
22 changed files with 3483 additions and 0 deletions

View File

@@ -0,0 +1,37 @@
import { defHttp } from '/@/utils/http/axios';
import { Modal } from 'ant-design-vue';
enum Api {
list = '/xslmes/mesXslMixingSpec/list',
save = '/xslmes/mesXslMixingSpec/add',
edit = '/xslmes/mesXslMixingSpec/edit',
deleteOne = '/xslmes/mesXslMixingSpec/delete',
deleteBatch = '/xslmes/mesXslMixingSpec/deleteBatch',
importExcel = '/xslmes/mesXslMixingSpec/importExcel',
exportXls = '/xslmes/mesXslMixingSpec/exportXls',
queryById = '/xslmes/mesXslMixingSpec/queryById',
queryIssueNumberOptions = '/xslmes/mesXslMixingSpec/queryIssueNumberOptions',
queryPurposeOptions = '/xslmes/mesXslMixingSpec/queryPurposeOptions',
}
export const getExportUrl = Api.exportXls;
export const getImportUrl = Api.importExcel;
export const list = (params) => defHttp.get({ url: Api.list, params });
export const queryById = (params) => defHttp.get({ url: Api.queryById, params });
export const saveOrUpdate = (params, isUpdate) => defHttp.post({ url: isUpdate ? Api.edit : Api.save, params });
export const queryIssueNumberOptions = (params) => defHttp.get({ url: Api.queryIssueNumberOptions, params });
export const queryPurposeOptions = (params) => defHttp.get({ url: Api.queryPurposeOptions, params });
export const deleteOne = (params, handleSuccess) =>
defHttp.delete({ url: Api.deleteOne, params }, { joinParamsToUrl: true }).then(() => handleSuccess());
export const batchDelete = (params, handleSuccess) => {
Modal.confirm({
title: '确认删除',
content: '是否删除选中数据',
okText: '确认',
cancelText: '取消',
onOk: () =>
defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => handleSuccess()),
});
};

View File

@@ -0,0 +1,587 @@
import { BasicColumn, FormSchema } from '/@/components/Table';
import { JVxeColumn, JVxeTypes } from '/@/components/jeecg/JVxeTable/types';
import { createLocalStorage } from '/@/utils/cache';
import { buildUUID } from '/@/utils/uuid';
const mixingMaterialColumnStorage = createLocalStorage();
/** 橡胶及配合剂明细列隐藏偏好 localStorage 键 */
export const MIXING_MATERIAL_COLUMN_CACHE_KEY = 'mes_xsl_mixing_spec_material_hidden_columns';
/** 橡胶及配合剂明细列宽偏好 localStorage 键 */
export const MIXING_MATERIAL_COLUMN_WIDTH_CACHE_KEY = 'mes_xsl_mixing_spec_material_column_widths_v2';
/** 行号列宽度(与 JVxeTable rowNumber 默认一致) */
export const MIXING_MATERIAL_ROW_NUMBER_WIDTH = 60;
/** 橡胶及配合剂明细列可缩小到的最小宽度 */
export const MIXING_MATERIAL_MIN_COLUMN_WIDTH = 40;
/** 默认隐藏的明细列 */
export const MIXING_MATERIAL_DEFAULT_HIDDEN_COLUMN_KEYS = ['materialMajor', 'materialKind'];
/** 不允许隐藏的明细列 */
export const MIXING_MATERIAL_LOCKED_COLUMN_KEYS = ['mixerMaterialName'];
export interface MixingMaterialColumnSettingItem {
key: string;
title: string;
locked?: boolean;
}
export const columns: BasicColumn[] = [
{ title: '规格', align: 'center', dataIndex: 'specName', width: 160, fixed: 'left' },
{ title: '用途', align: 'center', dataIndex: 'purpose', width: 180 },
{ title: '机台', align: 'center', dataIndex: 'machineName', width: 120 },
{ title: '制作日期', align: 'center', dataIndex: 'makeDate', width: 120 },
{ title: '发行编号', align: 'center', dataIndex: 'issueNumber', width: 150 },
{ title: '段数', align: 'center', dataIndex: 'stageCount', width: 80 },
{ title: '纯混炼时间(秒)', align: 'center', dataIndex: 'pureMixSec', width: 130 },
{ title: '变更日期', align: 'center', dataIndex: 'changeDate', width: 120 },
{ title: '修改时间', align: 'center', dataIndex: 'updateTime', width: 165 },
];
export const searchFormSchema: FormSchema[] = [
{ label: '关键字', field: 'keyword', component: 'Input', colProps: { span: 8 }, componentProps: { placeholder: '规格/用途/发行编号/机台' } },
{ label: '制作日期起', field: 'makeDate_begin', component: 'DatePicker', colProps: { span: 8 }, componentProps: { valueFormat: 'YYYY-MM-DD' } },
{ label: '制作日期止', field: 'makeDate_end', component: 'DatePicker', colProps: { span: 8 }, componentProps: { valueFormat: 'YYYY-MM-DD' } },
];
export const mainSchema: FormSchema[] = [
{ label: '', field: 'id', component: 'Input', show: false },
{ label: '规格', field: 'specName', component: 'Input', required: true, colProps: { span: 8 } },
{ label: '用途', field: 'purpose', component: 'AutoComplete', required: true, colProps: { span: 8 } },
{ label: '机台', field: 'machineName', component: 'Input', colProps: { span: 8 } },
{ label: '制作日期', field: 'makeDate', component: 'DatePicker', colProps: { span: 8 }, componentProps: { valueFormat: 'YYYY-MM-DD' } },
{ label: '发行编号', field: 'issueNumber', component: 'AutoComplete', required: true, colProps: { span: 8 } },
{ label: '换算系数', field: 'convertFactor', component: 'InputNumber', colProps: { span: 8 }, componentProps: { precision: 6, style: { width: '100%' } } },
{ label: '填充体积', field: 'fillVolume', component: 'InputNumber', colProps: { span: 8 }, componentProps: { precision: 6, style: { width: '100%' } } },
{ label: '回收炭黑(秒)', field: 'recycleCarbonSec', component: 'InputNumber', colProps: { span: 8 }, componentProps: { precision: 0, style: { width: '100%' } } },
{ label: '母胶比重', field: 'motherRubberSg', component: 'InputNumber', colProps: { span: 8 }, componentProps: { precision: 6, style: { width: '100%' } } },
{ label: '终炼胶比重', field: 'finalRubberSg', component: 'InputNumber', colProps: { span: 8 }, componentProps: { precision: 6, style: { width: '100%' } } },
{ label: '适用工厂', field: 'applyFactory', component: 'Input', colProps: { span: 8 } },
{ label: '段数', field: 'stageCount', component: 'InputNumber', colProps: { span: 8 }, componentProps: { precision: 0, style: { width: '100%' } } },
{ label: '纯混炼时间(秒)', field: 'pureMixSec', component: 'InputNumber', colProps: { span: 8 }, componentProps: { precision: 0, style: { width: '100%' } } },
{ label: '回收炭黑(KG)', field: 'recycleCarbonKg', component: 'InputNumber', colProps: { span: 8 }, componentProps: { precision: 6, style: { width: '100%' } } },
{ label: '自动小料打印设定', field: 'autoSmallPrintSetting', component: 'Input', colProps: { span: 8 } },
{ label: '设定车数', field: 'setTrainCount', component: 'InputNumber', colProps: { span: 8 }, componentProps: { precision: 0, style: { width: '100%' } } },
{ label: '侧壁水温', field: 'sideWallWaterTemp', component: 'InputNumber', colProps: { span: 8 }, componentProps: { precision: 6, style: { width: '100%' } } },
{ label: '超时排胶时间', field: 'overtimeDischargeSec', component: 'InputNumber', colProps: { span: 8 }, componentProps: { precision: 0, style: { width: '100%' } } },
{ label: '超温排胶时间', field: 'overtempDischargeSec', component: 'InputNumber', colProps: { span: 8 }, componentProps: { precision: 0, style: { width: '100%' } } },
{ label: '超温排胶温度', field: 'overtempDischargeTemp', component: 'InputNumber', colProps: { span: 8 }, componentProps: { precision: 6, style: { width: '100%' } } },
{ label: '卸料门水温', field: 'doorWaterTemp', component: 'InputNumber', colProps: { span: 8 }, componentProps: { precision: 6, style: { width: '100%' } } },
{ label: '转子水温', field: 'rotorWaterTemp', component: 'InputNumber', colProps: { span: 8 }, componentProps: { precision: 6, style: { width: '100%' } } },
{ label: '最高进料温度', field: 'maxFeedTemp', component: 'InputNumber', colProps: { span: 8 }, componentProps: { precision: 6, style: { width: '100%' } } },
{ label: '起草人', field: 'draftBy', component: 'Input', colProps: { span: 8 } },
{ label: '起草时间', field: 'draftTime', component: 'DatePicker', colProps: { span: 8 }, componentProps: { showTime: true, valueFormat: 'YYYY-MM-DD HH:mm:ss' } },
{ label: '校对人', field: 'proofreadBy', component: 'Input', colProps: { span: 8 } },
{ label: '校对时间', field: 'proofreadTime', component: 'DatePicker', colProps: { span: 8 }, componentProps: { showTime: true, valueFormat: 'YYYY-MM-DD HH:mm:ss' } },
{ label: '审核人', field: 'auditBy', component: 'Input', colProps: { span: 8 } },
{ label: '审核时间', field: 'auditTime', component: 'DatePicker', colProps: { span: 8 }, componentProps: { showTime: true, valueFormat: 'YYYY-MM-DD HH:mm:ss' } },
{ label: '批准人', field: 'approveBy', component: 'Input', colProps: { span: 8 } },
{ label: '批准时间', field: 'approveTime', component: 'DatePicker', colProps: { span: 8 }, componentProps: { showTime: true, valueFormat: 'YYYY-MM-DD HH:mm:ss' } },
{ label: '变更日期', field: 'changeDate', component: 'DatePicker', colProps: { span: 8 }, componentProps: { valueFormat: 'YYYY-MM-DD' } },
];
//update-begin---author:cursor ---date:20260522 for【XSLMES-20260522-A17】橡胶及配合剂明细列展示设置-----------
export const materialColumns: JVxeColumn[] = [
{ title: '物料大类', key: 'materialMajor', type: JVxeTypes.input, width: 100, minWidth: MIXING_MATERIAL_MIN_COLUMN_WIDTH },
{ title: '物料小类', key: 'materialMinor', type: JVxeTypes.input, width: 120, minWidth: MIXING_MATERIAL_MIN_COLUMN_WIDTH },
{ title: '种类', key: 'materialKind', type: JVxeTypes.input, width: 80, minWidth: MIXING_MATERIAL_MIN_COLUMN_WIDTH },
{ title: '密炼物料名称', key: 'mixerMaterialName', type: JVxeTypes.input, width: 160, minWidth: MIXING_MATERIAL_MIN_COLUMN_WIDTH },
{ title: '密炼物料描述', key: 'mixerMaterialDesc', type: JVxeTypes.input, width: 220, minWidth: MIXING_MATERIAL_MIN_COLUMN_WIDTH },
{ title: '单重', key: 'unitWeight', type: JVxeTypes.inputNumber, width: 72, minWidth: MIXING_MATERIAL_MIN_COLUMN_WIDTH, align: 'center' },
{ title: '累计', key: 'accumWeight', type: JVxeTypes.inputNumber, width: 72, minWidth: MIXING_MATERIAL_MIN_COLUMN_WIDTH, align: 'center' },
{ title: '顺序', key: 'seqNo', type: JVxeTypes.inputNumber, width: 64, minWidth: MIXING_MATERIAL_MIN_COLUMN_WIDTH, align: 'center' },
];
/** 橡胶及配合剂明细列设置项 */
export function getMixingMaterialColumnSettingItems(): MixingMaterialColumnSettingItem[] {
return materialColumns.map((col) => ({
key: String(col.key),
title: String(col.title),
locked: MIXING_MATERIAL_LOCKED_COLUMN_KEYS.includes(String(col.key)),
}));
}
/** 读取已隐藏的橡胶及配合剂明细列 key */
export function loadMixingMaterialHiddenColumnKeys(): string[] {
const saved = mixingMaterialColumnStorage.get(MIXING_MATERIAL_COLUMN_CACHE_KEY);
const validKeys = new Set(getMixingMaterialColumnSettingItems().map((item) => item.key));
if (!Array.isArray(saved)) {
return MIXING_MATERIAL_DEFAULT_HIDDEN_COLUMN_KEYS.filter((key) => validKeys.has(key));
}
return saved.filter(
(key) => typeof key === 'string' && !MIXING_MATERIAL_LOCKED_COLUMN_KEYS.includes(key) && validKeys.has(key),
);
}
/** 保存已隐藏的橡胶及配合剂明细列 key */
export function saveMixingMaterialHiddenColumnKeys(hiddenKeys: string[]) {
const validKeys = hiddenKeys.filter((key) => !MIXING_MATERIAL_LOCKED_COLUMN_KEYS.includes(key));
if (validKeys.length) {
mixingMaterialColumnStorage.set(MIXING_MATERIAL_COLUMN_CACHE_KEY, validKeys);
return;
}
mixingMaterialColumnStorage.remove(MIXING_MATERIAL_COLUMN_CACHE_KEY);
}
/** 按隐藏配置过滤橡胶及配合剂明细列 */
export function applyMixingMaterialColumnVisibility(columns: JVxeColumn[], hiddenKeys: string[]): JVxeColumn[] {
if (!hiddenKeys?.length) {
return columns;
}
const hiddenSet = new Set(hiddenKeys);
return columns.filter((col) => !col.key || !hiddenSet.has(String(col.key)));
}
/** 读取已保存的橡胶及配合剂明细列宽 */
export function loadMixingMaterialColumnWidths(): Record<string, number> {
const saved = mixingMaterialColumnStorage.get(MIXING_MATERIAL_COLUMN_WIDTH_CACHE_KEY);
const validKeys = new Set(materialColumns.map((col) => String(col.key)));
if (!saved || typeof saved !== 'object') {
return {};
}
const result: Record<string, number> = {};
Object.entries(saved as Record<string, unknown>).forEach(([key, value]) => {
if (validKeys.has(key) && typeof value === 'number' && value > 0) {
result[key] = value;
}
});
return result;
}
/** 保存橡胶及配合剂明细列宽 */
export function saveMixingMaterialColumnWidths(widthMap: Record<string, number>) {
const validKeys = new Set(materialColumns.map((col) => String(col.key)));
const next: Record<string, number> = {};
Object.entries(widthMap || {}).forEach(([key, value]) => {
if (validKeys.has(key) && typeof value === 'number' && value > 0) {
next[key] = value;
}
});
if (Object.keys(next).length) {
mixingMaterialColumnStorage.set(MIXING_MATERIAL_COLUMN_WIDTH_CACHE_KEY, next);
return;
}
mixingMaterialColumnStorage.remove(MIXING_MATERIAL_COLUMN_WIDTH_CACHE_KEY);
}
/** 应用已保存列宽minWidth 保持最小值,允许拖拽缩小) */
export function applyMixingMaterialColumnWidths(columns: JVxeColumn[], widthMap: Record<string, number>): JVxeColumn[] {
return columns.map((col) => {
const savedWidth = widthMap[String(col.key)];
const nextCol = {
...col,
minWidth: MIXING_MATERIAL_MIN_COLUMN_WIDTH,
};
if (savedWidth) {
nextCol.width = savedWidth;
}
return nextCol;
});
}
/** 计算橡胶及配合剂明细表总宽度 */
export function calcMixingMaterialTableWidth(columns: JVxeColumn[], widthMap: Record<string, number> = {}) {
const dataWidth = columns.reduce((sum, col) => {
const key = String(col.key);
const width = widthMap[key] ?? Number(col.width) ?? 80;
return sum + width;
}, 0);
return MIXING_MATERIAL_ROW_NUMBER_WIDTH + dataWidth + 1;
}
//update-end---author:cursor ---date:20260522 for【XSLMES-20260522-A17】橡胶及配合剂明细列展示设置-----------
//update-begin---author:cursor ---date:20260522 for【XSLMES-20260522-A20】明细表默认列宽对齐参考图-----------
/** 混合步骤/下密炼机明细列可缩小到的最小宽度 */
export const MIXING_STEP_MIN_COLUMN_WIDTH = 48;
export const stepColumns: JVxeColumn[] = [
{ title: '动作', key: 'actionName', type: JVxeTypes.input, width: 120, minWidth: MIXING_STEP_MIN_COLUMN_WIDTH },
{ title: '时间(")', key: 'actionSec', type: JVxeTypes.inputNumber, width: 80, minWidth: MIXING_STEP_MIN_COLUMN_WIDTH, align: 'center' },
{ title: '保护时间', key: 'protectSec', type: JVxeTypes.inputNumber, width: 80, minWidth: MIXING_STEP_MIN_COLUMN_WIDTH, align: 'center' },
{ title: '温度(℃)', key: 'tempC', type: JVxeTypes.inputNumber, width: 80, minWidth: MIXING_STEP_MIN_COLUMN_WIDTH, align: 'center' },
{ title: '功率(Kw)', key: 'powerKw', type: JVxeTypes.inputNumber, width: 80, minWidth: MIXING_STEP_MIN_COLUMN_WIDTH, align: 'center' },
{ title: '能量(Kwh)', key: 'energyKwh', type: JVxeTypes.inputNumber, width: 80, minWidth: MIXING_STEP_MIN_COLUMN_WIDTH, align: 'center' },
{ title: '组合', key: 'comboMode', type: JVxeTypes.input, width: 72, minWidth: MIXING_STEP_MIN_COLUMN_WIDTH, align: 'center' },
{ title: '转速(rpm)', key: 'speedRpm', type: JVxeTypes.inputNumber, width: 80, minWidth: MIXING_STEP_MIN_COLUMN_WIDTH, align: 'center' },
{ title: '压力(Mpa)', key: 'pressureMpa', type: JVxeTypes.inputNumber, width: 80, minWidth: MIXING_STEP_MIN_COLUMN_WIDTH, align: 'center' },
{ title: '栓(%)', key: 'boltPercent', type: JVxeTypes.inputNumber, width: 72, minWidth: MIXING_STEP_MIN_COLUMN_WIDTH, align: 'center' },
];
/** 混合步骤/下密炼机明细列宽偏好 localStorage 键(两表共用同步) */
export const MIXING_STEP_COLUMN_WIDTH_CACHE_KEY = 'mes_xsl_mixing_spec_step_column_widths_v1';
/** 读取已保存的混合步骤明细列宽 */
export function loadMixingStepColumnWidths(): Record<string, number> {
const saved = mixingMaterialColumnStorage.get(MIXING_STEP_COLUMN_WIDTH_CACHE_KEY);
const validKeys = new Set(stepColumns.map((col) => String(col.key)));
if (!saved || typeof saved !== 'object') {
return {};
}
const result: Record<string, number> = {};
Object.entries(saved as Record<string, unknown>).forEach(([key, value]) => {
if (validKeys.has(key) && typeof value === 'number' && value > 0) {
result[key] = value;
}
});
return result;
}
/** 保存混合步骤明细列宽 */
export function saveMixingStepColumnWidths(widthMap: Record<string, number>) {
const validKeys = new Set(stepColumns.map((col) => String(col.key)));
const next: Record<string, number> = {};
Object.entries(widthMap || {}).forEach(([key, value]) => {
if (validKeys.has(key) && typeof value === 'number' && value > 0) {
next[key] = value;
}
});
if (Object.keys(next).length) {
mixingMaterialColumnStorage.set(MIXING_STEP_COLUMN_WIDTH_CACHE_KEY, next);
return;
}
mixingMaterialColumnStorage.remove(MIXING_STEP_COLUMN_WIDTH_CACHE_KEY);
}
/** 应用已保存混合步骤列宽minWidth 保持最小值,允许拖拽缩小) */
export function applyMixingStepColumnWidths(columns: JVxeColumn[], widthMap: Record<string, number>): JVxeColumn[] {
return columns.map((col) => {
const savedWidth = widthMap[String(col.key)];
const nextCol = {
...col,
minWidth: MIXING_STEP_MIN_COLUMN_WIDTH,
};
if (savedWidth) {
nextCol.width = savedWidth;
}
return nextCol;
});
}
/** 计算混合步骤/下密炼机明细表总宽度 */
export function calcMixingStepTableWidth(columns: JVxeColumn[] = stepColumns, widthMap: Record<string, number> = {}) {
const dataWidth = columns.reduce((sum, col) => {
const key = String(col.key);
const width = widthMap[key] ?? Number(col.width) ?? Number(col.minWidth) ?? 80;
return sum + width;
}, 0);
return MIXING_MATERIAL_ROW_NUMBER_WIDTH + dataWidth + 1;
}
//update-end---author:cursor ---date:20260522 for【XSLMES-20260522-A20】明细表默认列宽对齐参考图-----------
export const downStepColumns: JVxeColumn[] = [...stepColumns];
//update-begin---author:cursor ---date:20260522 for【XSLMES-20260522-A19】TCU温度条件表列宽可调且表头换行-----------
/** TCU 温度条件明细列宽偏好 localStorage 键 */
export const MIXING_TCU_COLUMN_WIDTH_CACHE_KEY = 'mes_xsl_mixing_spec_tcu_column_widths_v2';
/** TCU 温度条件明细列可缩小到的最小宽度 */
export const MIXING_TCU_MIN_COLUMN_WIDTH = 48;
export const tcuColumns: JVxeColumn[] = [
{ title: '区分', key: 'sectionType', type: JVxeTypes.select, dictCode: 'xslmes_mixing_tcu_section', width: 96, minWidth: MIXING_TCU_MIN_COLUMN_WIDTH, align: 'center' },
{ title: '前转子温度', key: 'frontRotorTemp', type: JVxeTypes.inputNumber, width: 76, minWidth: MIXING_TCU_MIN_COLUMN_WIDTH, align: 'center' },
{ title: '后转子温度', key: 'rearRotorTemp', type: JVxeTypes.inputNumber, width: 76, minWidth: MIXING_TCU_MIN_COLUMN_WIDTH, align: 'center' },
{ title: '前混炼室温度', key: 'frontChamberTemp', type: JVxeTypes.inputNumber, width: 76, minWidth: MIXING_TCU_MIN_COLUMN_WIDTH, align: 'center' },
{ title: '后混炼室温度', key: 'rearChamberTemp', type: JVxeTypes.inputNumber, width: 76, minWidth: MIXING_TCU_MIN_COLUMN_WIDTH, align: 'center' },
{ title: '上下顶栓温度', key: 'topPlugTemp', type: JVxeTypes.inputNumber, width: 76, minWidth: MIXING_TCU_MIN_COLUMN_WIDTH, align: 'center' },
{ title: '药品称量位置', key: 'drugWeighPos', type: JVxeTypes.select, dictCode: 'xslmes_mixing_drug_weigh_pos', width: 76, minWidth: MIXING_TCU_MIN_COLUMN_WIDTH, align: 'center' },
];
/** 读取已保存的 TCU 温度条件明细列宽 */
export function loadMixingTcuColumnWidths(): Record<string, number> {
const saved = mixingMaterialColumnStorage.get(MIXING_TCU_COLUMN_WIDTH_CACHE_KEY);
const validKeys = new Set(tcuColumns.map((col) => String(col.key)));
if (!saved || typeof saved !== 'object') {
return {};
}
const result: Record<string, number> = {};
Object.entries(saved as Record<string, unknown>).forEach(([key, value]) => {
if (validKeys.has(key) && typeof value === 'number' && value > 0) {
result[key] = value;
}
});
return result;
}
/** 保存 TCU 温度条件明细列宽 */
export function saveMixingTcuColumnWidths(widthMap: Record<string, number>) {
const validKeys = new Set(tcuColumns.map((col) => String(col.key)));
const next: Record<string, number> = {};
Object.entries(widthMap || {}).forEach(([key, value]) => {
if (validKeys.has(key) && typeof value === 'number' && value > 0) {
next[key] = value;
}
});
if (Object.keys(next).length) {
mixingMaterialColumnStorage.set(MIXING_TCU_COLUMN_WIDTH_CACHE_KEY, next);
return;
}
mixingMaterialColumnStorage.remove(MIXING_TCU_COLUMN_WIDTH_CACHE_KEY);
}
/** 应用已保存 TCU 列宽minWidth 保持最小值,允许拖拽缩小) */
export function applyMixingTcuColumnWidths(columns: JVxeColumn[], widthMap: Record<string, number>): JVxeColumn[] {
return columns.map((col) => {
const savedWidth = widthMap[String(col.key)];
const nextCol = {
...col,
minWidth: MIXING_TCU_MIN_COLUMN_WIDTH,
};
if (savedWidth) {
nextCol.width = savedWidth;
}
return nextCol;
});
}
/** 计算 TCU 温度条件明细表总宽度 */
export function calcMixingTcuTableWidth(columns: JVxeColumn[] = tcuColumns, widthMap: Record<string, number> = {}) {
const dataWidth = columns.reduce((sum, col) => {
const key = String(col.key);
const width = widthMap[key] ?? Number(col.width) ?? Number(col.minWidth) ?? 80;
return sum + width;
}, 0);
return MIXING_MATERIAL_ROW_NUMBER_WIDTH + dataWidth + 1;
}
//update-end---author:cursor ---date:20260522 for【XSLMES-20260522-A19】TCU温度条件表列宽可调且表头换行-----------
//update-begin---author:cursor ---date:20260522 for【XSLMES-20260522-A26】TCU紧凑两行且胶料表向下扩展-----------
/** TCU 温度条件固定行数(上密炼机/下密炼机) */
export const DEFAULT_MIXING_TCU_ROW_COUNT = 2;
/** TCU 表头高度(列标题换行) */
export const MIXING_TCU_HEADER_HEIGHT = 48;
/** 计算 TCU 温度条件表格高度(仅展示固定两行) */
export function calcMixingTcuTableHeight(rowCount = DEFAULT_MIXING_TCU_ROW_COUNT) {
const rows = Math.max(1, rowCount);
return MIXING_TCU_HEADER_HEIGHT + rows * MIXING_VXE_MINI_ROW_HEIGHT + 2;
}
//update-end---author:cursor ---date:20260522 for【XSLMES-20260522-A26】TCU紧凑两行且胶料表向下扩展-----------
//update-begin---author:cursor ---date:20260522 for【XSLMES-20260522-A22】明细表默认空行数-----------
/** 橡胶及配合剂明细默认行数 */
export const DEFAULT_MIXING_MATERIAL_ROW_COUNT = 20;
/** 混合步骤明细默认行数 */
export const DEFAULT_MIXING_STEP_ROW_COUNT = 30;
//update-begin---author:cursor ---date:20260522 for【XSLMES-20260522-A30】胶料/混合步骤列表默认展示高度-----------
/** 橡胶及配合剂明细列表默认展示行数(可视高度) */
export const MIXING_MATERIAL_VISIBLE_ROW_COUNT = 17;
/** 混合步骤明细列表默认展示行数(可视高度) */
export const MIXING_STEP_VISIBLE_ROW_COUNT = 15;
//update-end---author:cursor ---date:20260522 for【XSLMES-20260522-A30】胶料/混合步骤列表默认展示高度-----------
//update-begin---author:cursor ---date:20260522 for【XSLMES-20260522-A27】下密炼机混炼条件默认行数改为4-----------
/** 下密炼机混炼条件明细默认行数 */
export const DEFAULT_MIXING_DOWN_STEP_ROW_COUNT = 4;
//update-end---author:cursor ---date:20260522 for【XSLMES-20260522-A27】下密炼机混炼条件默认行数改为4-----------
/** vxe mini 表头高度 */
export const MIXING_VXE_MINI_HEADER_HEIGHT = 36;
/** vxe mini 行高 */
export const MIXING_VXE_MINI_ROW_HEIGHT = 32;
//update-begin---author:cursor ---date:20260522 for【XSLMES-20260522-A29】胶料/混合步骤表格高度按行数完整展示-----------
/** 计算橡胶及配合剂明细表格展示高度 */
export function calcMixingMaterialTableHeight(rowCount = MIXING_MATERIAL_VISIBLE_ROW_COUNT) {
const rows = Math.max(1, rowCount);
return MIXING_VXE_MINI_HEADER_HEIGHT + rows * MIXING_VXE_MINI_ROW_HEIGHT + 4;
}
/** 计算混合步骤明细表格展示高度 */
export function calcMixingStepTableHeight(rowCount = MIXING_STEP_VISIBLE_ROW_COUNT) {
const rows = Math.max(1, rowCount);
return MIXING_VXE_MINI_HEADER_HEIGHT + rows * MIXING_VXE_MINI_ROW_HEIGHT + 4;
}
//update-begin---author:cursor ---date:20260522 for【XSLMES-20260522-A31】下密炼机4行表格高度微调-----------
/** 计算下密炼机混炼条件表格高度默认完整展示4行 */
export function calcMixingDownStepTableHeight(rowCount = DEFAULT_MIXING_DOWN_STEP_ROW_COUNT) {
const rows = Math.max(1, rowCount);
// 额外留白补偿边框与 vxe 内边距避免第4行底部被裁切
return MIXING_VXE_MINI_HEADER_HEIGHT + rows * MIXING_VXE_MINI_ROW_HEIGHT + 12;
}
//update-end---author:cursor ---date:20260522 for【XSLMES-20260522-A31】下密炼机4行表格高度微调-----------
//update-end---author:cursor ---date:20260522 for【XSLMES-20260522-A29】胶料/混合步骤表格高度按行数完整展示-----------
//update-begin---author:cursor ---date:20260522 for【XSLMES-20260522-A32】四明细表行高/展示行数设置持久化-----------
/** 混炼示方明细表标识 */
export type MixingDetailTableKey = 'material' | 'tcu' | 'step' | 'downStep';
/** 明细表行高与展示行数偏好 */
export interface MixingTableHeightPreference {
rowHeight: number;
visibleRowCount: number;
}
/** 明细表行高设置元数据 */
export interface MixingTableHeightSettingMeta {
key: MixingDetailTableKey;
label: string;
defaultRowHeight: number;
defaultVisibleRowCount: number;
minVisibleRowCount: number;
maxVisibleRowCount: number;
headerHeight: number;
extraPadding: number;
}
/** 明细表行高 localStorage 键 */
export const MIXING_TABLE_HEIGHT_PREF_CACHE_KEY = 'mes_xsl_mixing_spec_table_height_prefs_v1';
/** 行高可调范围px */
export const MIXING_TABLE_ROW_HEIGHT_MIN = 24;
export const MIXING_TABLE_ROW_HEIGHT_MAX = 56;
export const MIXING_TABLE_HEIGHT_SETTING_META: Record<MixingDetailTableKey, MixingTableHeightSettingMeta> = {
material: {
key: 'material',
label: '橡胶及配合剂',
defaultRowHeight: MIXING_VXE_MINI_ROW_HEIGHT,
defaultVisibleRowCount: MIXING_MATERIAL_VISIBLE_ROW_COUNT,
minVisibleRowCount: 5,
maxVisibleRowCount: 30,
headerHeight: MIXING_VXE_MINI_HEADER_HEIGHT,
extraPadding: 4,
},
tcu: {
key: 'tcu',
label: 'TCU温度条件',
defaultRowHeight: MIXING_VXE_MINI_ROW_HEIGHT,
defaultVisibleRowCount: DEFAULT_MIXING_TCU_ROW_COUNT,
minVisibleRowCount: 2,
maxVisibleRowCount: 6,
headerHeight: MIXING_TCU_HEADER_HEIGHT,
extraPadding: 2,
},
step: {
key: 'step',
label: '混合步骤',
defaultRowHeight: MIXING_VXE_MINI_ROW_HEIGHT,
defaultVisibleRowCount: MIXING_STEP_VISIBLE_ROW_COUNT,
minVisibleRowCount: 5,
maxVisibleRowCount: 40,
headerHeight: MIXING_VXE_MINI_HEADER_HEIGHT,
extraPadding: 4,
},
downStep: {
key: 'downStep',
label: '下密炼机混炼条件',
defaultRowHeight: MIXING_VXE_MINI_ROW_HEIGHT,
defaultVisibleRowCount: DEFAULT_MIXING_DOWN_STEP_ROW_COUNT,
minVisibleRowCount: 2,
maxVisibleRowCount: 12,
headerHeight: MIXING_VXE_MINI_HEADER_HEIGHT,
extraPadding: 12,
},
};
function clampMixingTableNumber(value: unknown, min: number, max: number, fallback: number) {
const num = Number(value);
if (Number.isNaN(num)) {
return fallback;
}
return Math.min(max, Math.max(min, Math.round(num)));
}
/** 获取明细表默认行高偏好 */
export function getMixingTableHeightDefault(tableKey: MixingDetailTableKey): MixingTableHeightPreference {
const meta = MIXING_TABLE_HEIGHT_SETTING_META[tableKey];
return {
rowHeight: meta.defaultRowHeight,
visibleRowCount: meta.defaultVisibleRowCount,
};
}
/** 规范化明细表行高偏好 */
export function normalizeMixingTableHeightPreference(
tableKey: MixingDetailTableKey,
preference?: Partial<MixingTableHeightPreference> | null,
): MixingTableHeightPreference {
const meta = MIXING_TABLE_HEIGHT_SETTING_META[tableKey];
const defaults = getMixingTableHeightDefault(tableKey);
return {
rowHeight: clampMixingTableNumber(
preference?.rowHeight,
MIXING_TABLE_ROW_HEIGHT_MIN,
MIXING_TABLE_ROW_HEIGHT_MAX,
defaults.rowHeight,
),
visibleRowCount: clampMixingTableNumber(
preference?.visibleRowCount,
meta.minVisibleRowCount,
meta.maxVisibleRowCount,
defaults.visibleRowCount,
),
};
}
/** 读取明细表行高偏好 */
export function loadMixingTableHeightPreference(tableKey: MixingDetailTableKey): MixingTableHeightPreference {
const saved = mixingMaterialColumnStorage.get(MIXING_TABLE_HEIGHT_PREF_CACHE_KEY);
const tableSaved =
saved && typeof saved === 'object' ? (saved as Record<string, Partial<MixingTableHeightPreference>>)[tableKey] : null;
return normalizeMixingTableHeightPreference(tableKey, tableSaved);
}
/** 保存明细表行高偏好 */
export function saveMixingTableHeightPreference(tableKey: MixingDetailTableKey, preference: MixingTableHeightPreference) {
const saved = mixingMaterialColumnStorage.get(MIXING_TABLE_HEIGHT_PREF_CACHE_KEY);
const next =
saved && typeof saved === 'object'
? { ...(saved as Record<string, MixingTableHeightPreference>) }
: ({} as Record<string, MixingTableHeightPreference>);
next[tableKey] = normalizeMixingTableHeightPreference(tableKey, preference);
mixingMaterialColumnStorage.set(MIXING_TABLE_HEIGHT_PREF_CACHE_KEY, next);
}
/** 计算明细表展示区域高度 */
export function calcMixingDetailTableViewportHeight(
tableKey: MixingDetailTableKey,
preference?: Partial<MixingTableHeightPreference>,
) {
const meta = MIXING_TABLE_HEIGHT_SETTING_META[tableKey];
const normalized = normalizeMixingTableHeightPreference(tableKey, preference);
return meta.headerHeight + normalized.visibleRowCount * normalized.rowHeight + meta.extraPadding;
}
/** 构建 JVxeTable rowConfig */
export function buildMixingTableRowConfig(preference: MixingTableHeightPreference) {
return {
isHover: true,
height: preference.rowHeight,
};
}
//update-end---author:cursor ---date:20260522 for【XSLMES-20260522-A32】四明细表行高/展示行数设置持久化-----------
function createEmptyMixingRows(count: number): Recordable[] {
return Array.from({ length: count }, () => ({ id: buildUUID() }));
}
/** 创建橡胶及配合剂默认空行 */
export function createEmptyMaterialRows(count = DEFAULT_MIXING_MATERIAL_ROW_COUNT): Recordable[] {
return createEmptyMixingRows(count);
}
/** 创建混合步骤默认空行 */
export function createEmptyStepRows(count = DEFAULT_MIXING_STEP_ROW_COUNT): Recordable[] {
return createEmptyMixingRows(count);
}
/** 创建下密炼机混炼条件默认空行 */
export function createEmptyDownStepRows(count = DEFAULT_MIXING_DOWN_STEP_ROW_COUNT): Recordable[] {
return createEmptyMixingRows(count);
}
/** 确保明细行具备唯一 idJVxeTable 依赖 rowKey=id */
export function normalizeMixingDetailRows(rows: Recordable[] = []): Recordable[] {
return (rows || []).map((row) => ({ ...row, id: row?.id || buildUUID() }));
}
//update-end---author:cursor ---date:20260522 for【XSLMES-20260522-A22】明细表默认空行数-----------

View File

@@ -0,0 +1,115 @@
<template>
<div>
<BasicTable @register="registerTable" :rowSelection="rowSelection">
<template #tableTitle>
<a-button type="primary" v-auth="'xslmes:mes_xsl_mixing_spec:add'" @click="handleAdd" preIcon="ant-design:plus-outlined"> 新增 </a-button>
<a-button type="primary" v-auth="'xslmes:mes_xsl_mixing_spec:exportXls'" preIcon="ant-design:export-outlined" @click="onExportXls"> 导出 </a-button>
<j-upload-button type="primary" v-auth="'xslmes:mes_xsl_mixing_spec:importExcel'" preIcon="ant-design:import-outlined" @click="onImportXls">
导入
</j-upload-button>
<a-dropdown v-if="selectedRowKeys.length > 0">
<template #overlay>
<a-menu>
<a-menu-item key="1" @click="batchHandleDelete">
<Icon icon="ant-design:delete-outlined" />
删除
</a-menu-item>
</a-menu>
</template>
<a-button v-auth="'xslmes:mes_xsl_mixing_spec:deleteBatch'">
批量操作
<Icon icon="mdi:chevron-down" />
</a-button>
</a-dropdown>
</template>
<template #action="{ record }">
<TableAction :actions="getTableActions(record)" />
</template>
</BasicTable>
<MesXslMixingSpecModal @register="registerModal" @success="handleSuccess" />
</div>
</template>
<script lang="ts" name="xslmes-mesXslMixingSpec" setup>
import { reactive } from 'vue';
import { BasicTable, TableAction } from '/@/components/Table';
import { useModal } from '/@/components/Modal';
import { useListPage } from '/@/hooks/system/useListPage';
import Icon from '/@/components/Icon';
import MesXslMixingSpecModal from './components/MesXslMixingSpecModal.vue';
import { columns, searchFormSchema } from './MesXslMixingSpec.data';
import { list, deleteOne, batchDelete, getExportUrl, getImportUrl } from './MesXslMixingSpec.api';
const queryParam = reactive<any>({});
const [registerModal, { openModal }] = useModal();
const { tableContext, onExportXls, onImportXls } = useListPage({
tableProps: {
title: '混炼示方',
api: list,
columns,
canResize: true,
formConfig: {
schemas: searchFormSchema,
labelWidth: 90,
autoSubmitOnEnter: true,
},
actionColumn: {
title: '操作',
dataIndex: 'action',
width: 160,
fixed: 'right',
slots: { customRender: 'action' },
},
beforeFetch: (params) => Object.assign(params, queryParam),
},
exportConfig: {
name: '混炼示方',
url: getExportUrl,
params: queryParam,
},
importConfig: {
url: getImportUrl,
success: handleSuccess,
},
});
const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
function handleAdd() {
openModal(true, { isUpdate: false, showFooter: true });
}
function handleEdit(record: Recordable) {
openModal(true, { record, isUpdate: true, showFooter: true });
}
function handleDetail(record: Recordable) {
openModal(true, { record, isUpdate: true, showFooter: false });
}
function handleDelete(record: Recordable) {
deleteOne({ id: record.id }, handleSuccess);
}
function batchHandleDelete() {
batchDelete({ ids: selectedRowKeys.value.join(',') }, handleSuccess);
}
function handleSuccess() {
reload();
selectedRowKeys.value = [];
}
function getTableActions(record: Recordable) {
return [
{ label: '编辑', onClick: handleEdit.bind(null, record), auth: 'xslmes:mes_xsl_mixing_spec:edit' },
{ label: '详情', onClick: handleDetail.bind(null, record) },
{
label: '删除',
auth: 'xslmes:mes_xsl_mixing_spec:delete',
popConfirm: { title: '是否确认删除', confirm: handleDelete.bind(null, record) },
},
];
}
</script>

View File

@@ -0,0 +1,166 @@
<template>
<Popover
v-model:open="popoverOpen"
trigger="click"
placement="bottomRight"
:overlayClassName="`${prefixCls}__popover`"
@open-change="handleOpenChange"
>
<template #title>
<div :class="`${prefixCls}__title`">
<Checkbox :indeterminate="indeterminate" :checked="checkAll" @change="onCheckAllChange">列展示</Checkbox>
</div>
</template>
<template #content>
<div :class="`${prefixCls}__list`">
<CheckboxGroup v-model:value="draftCheckedList" :options="columnOptions" />
</div>
<div :class="`${prefixCls}__footer`">
<a-button size="small" @click="handleReset">重置</a-button>
<a-button size="small" type="primary" @click="handleSave">保存</a-button>
</div>
</template>
<a-tooltip title="列设置">
<a-button size="small" class="mixing-material-column-setting-btn" @click.stop>
<Icon icon="ant-design:setting-outlined" />
</a-button>
</a-tooltip>
</Popover>
</template>
<script lang="ts" setup>
import { computed, ref, type PropType } from 'vue';
import { Popover, Checkbox } from 'ant-design-vue';
import type { CheckboxChangeEvent } from 'ant-design-vue/lib/checkbox/interface';
import { Icon } from '/@/components/Icon';
import { useMessage } from '/@/hooks/web/useMessage';
import {
MIXING_MATERIAL_LOCKED_COLUMN_KEYS,
getMixingMaterialColumnSettingItems,
saveMixingMaterialHiddenColumnKeys,
type MixingMaterialColumnSettingItem,
} from '../MesXslMixingSpec.data';
const CheckboxGroup = Checkbox.Group;
const prefixCls = 'mixing-material-column-setting';
const { createMessage } = useMessage();
const props = defineProps({
hiddenKeys: {
type: Array as PropType<string[]>,
default: () => [],
},
});
const emit = defineEmits<{
(e: 'update:hiddenKeys', value: string[]): void;
(e: 'change', value: string[]): void;
}>();
const popoverOpen = ref(false);
const columnItems = ref<MixingMaterialColumnSettingItem[]>(getMixingMaterialColumnSettingItems());
const allKeys = computed(() => columnItems.value.map((item) => item.key));
const lockableKeys = computed(() => columnItems.value.filter((item) => !item.locked).map((item) => item.key));
const draftCheckedList = ref<string[]>([]);
const columnOptions = computed(() =>
columnItems.value.map((item) => ({
label: item.title,
value: item.key,
disabled: item.locked,
})),
);
const checkAll = computed(() => {
const keys = lockableKeys.value;
return keys.length > 0 && keys.every((key) => draftCheckedList.value.includes(key));
});
const indeterminate = computed(() => {
const keys = lockableKeys.value;
const checkedCount = keys.filter((key) => draftCheckedList.value.includes(key)).length;
return checkedCount > 0 && checkedCount < keys.length;
});
function ensureLockedChecked() {
const next = new Set(draftCheckedList.value);
MIXING_MATERIAL_LOCKED_COLUMN_KEYS.forEach((key) => next.add(key));
draftCheckedList.value = Array.from(next);
}
function syncDraftFromHidden(hiddenKeys: string[]) {
const hiddenSet = new Set(hiddenKeys || []);
draftCheckedList.value = allKeys.value.filter((key) => !hiddenSet.has(key));
ensureLockedChecked();
}
function buildHiddenKeysFromDraft() {
ensureLockedChecked();
return allKeys.value.filter((key) => !draftCheckedList.value.includes(key));
}
function handleOpenChange(open: boolean) {
if (open) {
syncDraftFromHidden(props.hiddenKeys);
}
}
function onCheckAllChange(e: CheckboxChangeEvent) {
draftCheckedList.value = e.target.checked ? [...allKeys.value] : [...MIXING_MATERIAL_LOCKED_COLUMN_KEYS];
}
function handleReset() {
draftCheckedList.value = [...allKeys.value];
}
function handleSave() {
const hiddenKeys = buildHiddenKeysFromDraft();
saveMixingMaterialHiddenColumnKeys(hiddenKeys);
emit('update:hiddenKeys', hiddenKeys);
emit('change', hiddenKeys);
createMessage.success('保存成功');
popoverOpen.value = false;
}
</script>
<style lang="less" scoped>
.mixing-material-column-setting-btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding-inline: 8px;
}
</style>
<style lang="less">
.mixing-material-column-setting__popover {
.mixing-material-column-setting__title {
min-width: 180px;
}
.mixing-material-column-setting__list {
max-height: 320px;
overflow-y: auto;
margin-bottom: 8px;
.ant-checkbox-group {
display: flex;
flex-direction: column;
gap: 4px;
}
.ant-checkbox-group-item {
margin-inline-start: 0;
white-space: nowrap;
}
}
.mixing-material-column-setting__footer {
display: flex;
justify-content: flex-end;
gap: 8px;
padding-top: 4px;
border-top: 1px solid #f0f0f0;
}
}
</style>

View File

@@ -0,0 +1,169 @@
<template>
<Popover
v-model:open="popoverOpen"
trigger="click"
placement="bottomRight"
:overlayClassName="`${prefixCls}__popover`"
@open-change="handleOpenChange"
>
<template #title>
<div :class="`${prefixCls}__title`">{{ meta.label }} - 行高设置</div>
</template>
<template #content>
<div :class="`${prefixCls}__form`">
<div :class="`${prefixCls}__field`">
<span class="field-label">行高(px)</span>
<InputNumber
v-model:value="draftRowHeight"
:min="MIXING_TABLE_ROW_HEIGHT_MIN"
:max="MIXING_TABLE_ROW_HEIGHT_MAX"
:step="1"
size="small"
style="width: 100%"
/>
</div>
<div :class="`${prefixCls}__field`">
<span class="field-label">展示行数</span>
<InputNumber
v-model:value="draftVisibleRowCount"
:min="meta.minVisibleRowCount"
:max="meta.maxVisibleRowCount"
:step="1"
size="small"
style="width: 100%"
/>
</div>
<div :class="`${prefixCls}__hint`">展示行数为列表可视区域高度数据超出时可滚动查看</div>
</div>
<div :class="`${prefixCls}__footer`">
<a-button size="small" @click="handleReset">重置</a-button>
<a-button size="small" type="primary" @click="handleSave">保存</a-button>
</div>
</template>
<a-tooltip title="行高设置">
<a-button size="small" :class="`${prefixCls}-btn`" @click.stop>
<Icon icon="ant-design:column-height-outlined" />
</a-button>
</a-tooltip>
</Popover>
</template>
<script lang="ts" setup>
import { computed, ref, type PropType } from 'vue';
import { Popover, InputNumber } from 'ant-design-vue';
import { Icon } from '/@/components/Icon';
import { useMessage } from '/@/hooks/web/useMessage';
import {
MIXING_TABLE_HEIGHT_SETTING_META,
MIXING_TABLE_ROW_HEIGHT_MAX,
MIXING_TABLE_ROW_HEIGHT_MIN,
getMixingTableHeightDefault,
normalizeMixingTableHeightPreference,
saveMixingTableHeightPreference,
type MixingDetailTableKey,
type MixingTableHeightPreference,
} from '../MesXslMixingSpec.data';
const prefixCls = 'mixing-table-row-height-setting';
const { createMessage } = useMessage();
const props = defineProps({
tableKey: {
type: String as PropType<MixingDetailTableKey>,
required: true,
},
preference: {
type: Object as PropType<MixingTableHeightPreference>,
required: true,
},
});
const emit = defineEmits<{
(e: 'update:preference', value: MixingTableHeightPreference): void;
(e: 'change', value: MixingTableHeightPreference): void;
}>();
const popoverOpen = ref(false);
const meta = computed(() => MIXING_TABLE_HEIGHT_SETTING_META[props.tableKey]);
const draftRowHeight = ref(meta.value.defaultRowHeight);
const draftVisibleRowCount = ref(meta.value.defaultVisibleRowCount);
function syncDraftFromPreference(preference: MixingTableHeightPreference) {
const normalized = normalizeMixingTableHeightPreference(props.tableKey, preference);
draftRowHeight.value = normalized.rowHeight;
draftVisibleRowCount.value = normalized.visibleRowCount;
}
function handleOpenChange(open: boolean) {
if (open) {
syncDraftFromPreference(props.preference);
}
}
function handleReset() {
syncDraftFromPreference(getMixingTableHeightDefault(props.tableKey));
}
function handleSave() {
const next = normalizeMixingTableHeightPreference(props.tableKey, {
rowHeight: draftRowHeight.value,
visibleRowCount: draftVisibleRowCount.value,
});
saveMixingTableHeightPreference(props.tableKey, next);
emit('update:preference', next);
emit('change', next);
createMessage.success('保存成功');
popoverOpen.value = false;
}
</script>
<style lang="less" scoped>
.mixing-table-row-height-setting-btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding-inline: 8px;
}
</style>
<style lang="less">
.mixing-table-row-height-setting__popover {
.mixing-table-row-height-setting__title {
min-width: 180px;
font-weight: 600;
}
.mixing-table-row-height-setting__form {
width: 220px;
display: flex;
flex-direction: column;
gap: 10px;
margin-bottom: 8px;
}
.mixing-table-row-height-setting__field {
display: flex;
flex-direction: column;
gap: 4px;
.field-label {
font-size: 12px;
color: #595959;
}
}
.mixing-table-row-height-setting__hint {
font-size: 12px;
color: #8c8c8c;
line-height: 1.4;
}
.mixing-table-row-height-setting__footer {
display: flex;
justify-content: flex-end;
gap: 8px;
padding-top: 4px;
border-top: 1px solid #f0f0f0;
}
}
</style>