优化混炼示方,新增种类配置

This commit is contained in:
geht
2026-05-25 19:44:14 +08:00
parent c85657d199
commit dc3f305303
34 changed files with 3892 additions and 104 deletions

View File

@@ -18,7 +18,10 @@ 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 });
//update-begin---author:cursor ---date:20260525 for【XSLMES-20260525-A45】混炼示方主子表保存延长超时避免误报失败-----------
export const saveOrUpdate = (params, isUpdate) =>
defHttp.post({ url: isUpdate ? Api.edit : Api.save, params, timeout: 60 * 1000 });
//update-end---author:cursor ---date:20260525 for【XSLMES-20260525-A45】混炼示方主子表保存延长超时避免误报失败-----------
export const queryIssueNumberOptions = (params) => defHttp.get({ url: Api.queryIssueNumberOptions, params });
export const queryPurposeOptions = (params) => defHttp.get({ url: Api.queryPurposeOptions, params });

View File

@@ -91,10 +91,27 @@ 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: 'mixerMaterialName',
type: JVxeTypes.slot,
slotName: 'mixerMaterialNameSlot',
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' },
//update-begin---author:cursor ---date:20260525 for【XSLMES-20260525-A41】累计列按种类分组合计只读展示-----------
{
title: '累计',
key: 'accumWeight',
type: JVxeTypes.inputNumber,
width: 72,
minWidth: MIXING_MATERIAL_MIN_COLUMN_WIDTH,
align: 'center',
disabled: true,
},
//update-end---author:cursor ---date:20260525 for【XSLMES-20260525-A41】累计列按种类分组合计只读展示-----------
{ title: '顺序', key: 'seqNo', type: JVxeTypes.inputNumber, width: 64, minWidth: MIXING_MATERIAL_MIN_COLUMN_WIDTH, align: 'center' },
];
@@ -196,6 +213,280 @@ export function calcMixingMaterialTableWidth(columns: JVxeColumn[], widthMap: Re
}
//update-end---author:cursor ---date:20260522 for【XSLMES-20260522-A17】橡胶及配合剂明细列展示设置-----------
//update-begin---author:cursor ---date:20260525 for【XSLMES-20260525-A41】橡胶及配合剂明细累计按种类分组合计-----------
/** 是否为有效明细行(参与种类分组) */
function isMixingMaterialDataRow(row: Recordable): boolean {
if (!row) {
return false;
}
return !!(row.mixerMaterialName || row.materialKind || row.unitWeight != null && row.unitWeight !== '');
}
/** 规范化种类字段,用于连续行分组 */
function normalizeMixingMaterialKind(row: Recordable): string {
const kind = row?.materialKind;
return kind != null && String(kind).trim() !== '' ? String(kind).trim() : '';
}
/** 按种类连续分组,累计写入每组最后一行 */
export function fillMixingMaterialAccumWeight(rows: Recordable[] = []): Recordable[] {
if (!rows?.length) {
return rows;
}
let index = 0;
while (index < rows.length) {
const current = rows[index];
if (!isMixingMaterialDataRow(current)) {
current.accumWeight = null;
index++;
continue;
}
const kind = normalizeMixingMaterialKind(current);
let groupEnd = index;
let sum = 0;
while (groupEnd < rows.length) {
const row = rows[groupEnd];
if (!isMixingMaterialDataRow(row) || normalizeMixingMaterialKind(row) !== kind) {
break;
}
const weight = toMixingMaterialNumber(row.unitWeight);
if (weight != null) {
sum += weight;
}
groupEnd++;
}
for (let rowIndex = index; rowIndex < groupEnd; rowIndex++) {
rows[rowIndex].accumWeight =
rowIndex === groupEnd - 1 && sum !== 0 ? roundMixingMaterialNumber(sum) : null;
}
index = groupEnd;
}
return rows;
}
/** 安全解析明细数值,避免字符串拼接 */
function toMixingMaterialNumber(value: unknown): number | null {
if (value == null || value === '') {
return null;
}
const num = Number(value);
return Number.isFinite(num) ? num : null;
}
/** 混炼示方重量小数位(与后端 BigDecimal 精度一致) */
const MIXING_MATERIAL_WEIGHT_SCALE = 6;
/** 重量四舍五入,消除浮点累加误差 */
function roundMixingMaterialNumber(value: number): number {
return Number(value.toFixed(MIXING_MATERIAL_WEIGHT_SCALE));
}
/** 格式化重量展示文本 */
function formatMixingMaterialWeight(value: unknown): string {
const num = toMixingMaterialNumber(value);
if (num == null) {
return '';
}
return String(roundMixingMaterialNumber(num));
}
//update-begin---author:cursor ---date:20260525 for【XSLMES-20260525-A43】换算系数联动明细单重实时计算-----------
/** 规范化换算系数,空值或非正数按 1 处理 */
export function normalizeMixingConvertFactor(factor: unknown): number {
const num = toMixingMaterialNumber(factor);
if (num == null || num <= 0) {
return 1;
}
return num;
}
/** 基准单重 × 换算系数 */
export function calcMixingMaterialConvertedWeight(base: unknown, factor: unknown): number | null {
const baseNum = toMixingMaterialNumber(base);
if (baseNum == null) {
return null;
}
return Number((baseNum * normalizeMixingConvertFactor(factor)).toFixed(MIXING_MATERIAL_WEIGHT_SCALE));
}
/** 从当前显示单重反推基准单重 */
export function syncMaterialBaseUnitWeightFromDisplay(row: Recordable, factor: unknown) {
if (!row) {
return;
}
const unit = toMixingMaterialNumber(row.unitWeight);
if (unit == null) {
row.baseUnitWeight = null;
return;
}
row.baseUnitWeight = Number((unit / normalizeMixingConvertFactor(factor)).toFixed(MIXING_MATERIAL_WEIGHT_SCALE));
}
/** 初始化明细行基准单重(编辑加载时由已保存单重反推) */
export function initMaterialBaseUnitWeight(row: Recordable, factor: unknown, force = false) {
if (!isMixingMaterialDataRow(row)) {
row.baseUnitWeight = null;
return;
}
if (!force && toMixingMaterialNumber(row.baseUnitWeight) != null) {
return;
}
syncMaterialBaseUnitWeightFromDisplay(row, factor);
}
/** 批量初始化基准单重 */
export function initMaterialBaseUnitWeights(rows: Recordable[] = [], factor: unknown, force = false) {
for (const row of rows) {
initMaterialBaseUnitWeight(row, factor, force);
}
return rows;
}
/** 按换算系数重算所有明细单重 */
export function applyConvertFactorToMaterialRows(
rows: Recordable[] = [],
factor: unknown,
prevFactor?: unknown,
): Recordable[] {
const nextFactor = normalizeMixingConvertFactor(factor);
const oldFactor = prevFactor != null ? normalizeMixingConvertFactor(prevFactor) : nextFactor;
for (const row of rows) {
if (!isMixingMaterialDataRow(row)) {
continue;
}
let base = toMixingMaterialNumber(row.baseUnitWeight);
if (base == null) {
const unit = toMixingMaterialNumber(row.unitWeight);
if (unit == null) {
continue;
}
base = roundMixingMaterialNumber(unit / oldFactor);
row.baseUnitWeight = base;
}
row.unitWeight = calcMixingMaterialConvertedWeight(base, nextFactor);
}
return rows;
}
//update-end---author:cursor ---date:20260525 for【XSLMES-20260525-A43】换算系数联动明细单重实时计算-----------
//update-begin---author:cursor ---date:20260525 for【XSLMES-20260525-A46】填充体积按单重/比重/机台有效体积自动计算-----------
/** 解析设备有效体积(支持纯数字或带单位字符串) */
export function parseMixingEffectiveVolume(raw: unknown): number | null {
if (raw == null || raw === '') {
return null;
}
const text = String(raw).trim();
if (!text) {
return null;
}
const direct = toMixingMaterialNumber(text);
if (direct != null && direct > 0) {
return direct;
}
const matched = text.match(/([0-9]+(?:\.[0-9]+)?)/);
if (!matched) {
return null;
}
const parsed = Number(matched[1]);
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
}
/** 按段数与比重字段选择本段计算用比重 */
export function resolveMixingSpecificGravity(form: Recordable = {}): number | null {
const motherSg = toMixingMaterialNumber(form.motherRubberSg);
const finalSg = toMixingMaterialNumber(form.finalRubberSg);
const stageCount = String(form.stageCount || '').trim();
const stageMatch = stageCount.match(/^(\d+)\/(\d+)$/);
const isFinalStage = stageMatch ? stageMatch[1] === stageMatch[2] : false;
if (isFinalStage && finalSg != null && finalSg > 0) {
return finalSg;
}
if (motherSg != null && motherSg > 0) {
return motherSg;
}
if (finalSg != null && finalSg > 0) {
return finalSg;
}
return null;
}
/**
* 填充体积(%) = 单重合计 ÷ 比重 ÷ 机台有效体积(L) × 100
* 单重合计已含换算系数,此处不再重复乘换算系数
*/
export function calcMixingFillVolume(totalWeight: unknown, specificGravity: unknown, effectiveVolume: unknown): number | null {
const weight = toMixingMaterialNumber(totalWeight);
const sg = toMixingMaterialNumber(specificGravity);
const volume = parseMixingEffectiveVolume(effectiveVolume);
if (weight == null || weight <= 0 || sg == null || sg <= 0 || volume == null || volume <= 0) {
return null;
}
const materialVolume = weight / sg;
return Number(((materialVolume / volume) * 100).toFixed(6));
}
//update-end---author:cursor ---date:20260525 for【XSLMES-20260525-A46】填充体积按单重/比重/机台有效体积自动计算-----------
/** 汇总有效明细行的单重合计 */
export function calcMixingMaterialUnitWeightTotal(rows: Recordable[] = []): number | null {
let sum = 0;
let hasAny = false;
for (const row of rows) {
if (!isMixingMaterialDataRow(row)) {
continue;
}
const weight = toMixingMaterialNumber(row.unitWeight);
if (weight != null) {
sum += weight;
hasAny = true;
}
}
return hasAny ? roundMixingMaterialNumber(sum) : null;
}
/** 汇总有效明细行的累计合计(与单重合计一致) */
export function calcMixingMaterialAccumWeightTotal(rows: Recordable[] = []): number | null {
return calcMixingMaterialUnitWeightTotal(rows);
}
export interface MixingMaterialFooterCell {
key: string;
width: number;
text: string;
align?: 'left' | 'center' | 'right';
isLabel?: boolean;
isTotal?: boolean;
}
/** 构建橡胶及配合剂明细底部合计行单元格(列宽与明细表同步) */
export function buildMixingMaterialFooterCells(
columns: JVxeColumn[],
widthMap: Record<string, number>,
totals: { unitWeight?: number | null; accumWeight?: number | null },
): MixingMaterialFooterCell[] {
const unitWeightIndex = columns.findIndex((col) => String(col.key) === 'unitWeight');
const formatTotal = (value: number | null | undefined) => formatMixingMaterialWeight(value);
return columns.map((col, index) => {
const key = String(col.key);
const width = widthMap[key] ?? Number(col.width) ?? 80;
if (key === 'unitWeight') {
return { key, width, text: formatTotal(totals.unitWeight), align: 'center', isTotal: true };
}
if (key === 'accumWeight') {
return { key, width, text: formatTotal(totals.accumWeight), align: 'center', isTotal: true };
}
const isLabelCol = unitWeightIndex > 0 && index === unitWeightIndex - 1;
return {
key,
width,
text: isLabelCol ? '合计' : '',
align: 'center',
isLabel: isLabelCol,
};
});
}
//update-end---author:cursor ---date:20260525 for【XSLMES-20260525-A41】橡胶及配合剂明细累计按种类分组合计-----------
//update-begin---author:cursor ---date:20260522 for【XSLMES-20260522-A20】明细表默认列宽对齐参考图-----------
/** 混合步骤/下密炼机明细列可缩小到的最小宽度 */
export const MIXING_STEP_MIN_COLUMN_WIDTH = 48;
@@ -412,6 +703,9 @@ export const MIXING_VXE_MINI_HEADER_HEIGHT = 36;
/** vxe mini 行高 */
export const MIXING_VXE_MINI_ROW_HEIGHT = 32;
/** 橡胶及配合剂明细合计行高度(含边框) */
export const MIXING_MATERIAL_FOOTER_ROW_HEIGHT = MIXING_VXE_MINI_ROW_HEIGHT + 1;
//update-begin---author:cursor ---date:20260522 for【XSLMES-20260522-A29】胶料/混合步骤表格高度按行数完整展示-----------
/** 计算橡胶及配合剂明细表格展示高度 */
export function calcMixingMaterialTableHeight(rowCount = MIXING_MATERIAL_VISIBLE_ROW_COUNT) {
@@ -637,3 +931,99 @@ export function ensureMixingDetailRows(rows: Recordable[] = [], defaultCount: nu
}
//update-end---author:cursor ---date:20260522 for【XSLMES-20260522-A39】编辑页明细补齐默认空行与新增一致-----------
//update-end---author:cursor ---date:20260522 for【XSLMES-20260522-A22】明细表默认空行数-----------
//update-begin---author:cursor ---date:20260525 for【XSLMES-20260525-A50】混炼示方密炼物料选料弹窗与种类解析-----------
/** 混炼示方选料弹窗:隐藏的小类 ID 偏好 localStorage 键 */
export const MIXING_MATERIAL_PICKER_HIDDEN_CATEGORY_CACHE_KEY = 'mes_xsl_mixing_spec_material_picker_hidden_categories';
export interface MixingMaterialPickerCategoryItem {
id: string;
name: string;
majorId: string;
majorName: string;
label: string;
}
const mixingMaterialPickerStorage = createLocalStorage();
export function loadMixingMaterialPickerHiddenCategoryIds(): string[] {
const raw = mixingMaterialPickerStorage.get(MIXING_MATERIAL_PICKER_HIDDEN_CATEGORY_CACHE_KEY);
return Array.isArray(raw) ? raw.map(String) : [];
}
export function saveMixingMaterialPickerHiddenCategoryIds(ids: string[]) {
mixingMaterialPickerStorage.set(MIXING_MATERIAL_PICKER_HIDDEN_CATEGORY_CACHE_KEY, ids || []);
}
//update-begin---author:cursor ---date:20260525 for【XSLMES-20260525-A50】选料弹窗小类树为空时重置隐藏配置-----------
/** 过滤无效隐藏项;若全部小类被隐藏则自动重置,避免左侧树只剩「全部小类」 */
export function sanitizeMixingMaterialPickerHiddenCategoryIds(allMinorIds: string[], hidden: string[]) {
const allSet = new Set((allMinorIds || []).map(String));
const filtered = (hidden || []).map(String).filter((id) => allSet.has(id));
if (allSet.size > 0 && filtered.length >= allSet.size) {
return [];
}
return filtered;
}
//update-end---author:cursor ---date:20260525 for【XSLMES-20260525-A50】选料弹窗小类树为空时重置隐藏配置-----------
/** 解析混炼示方明细种类:小类勾选胶料则显示「胶料」,否则显示小类名 */
export function resolveMixingMaterialKindFromCategory(isRubber?: unknown, minorName?: string) {
if (isRubber === '1' || isRubber === 1 || isRubber === true) {
return '胶料';
}
return minorName != null && String(minorName).trim() !== '' ? String(minorName).trim() : '';
}
//update-begin---author:cursor ---date:20260525 for【XSLMES-20260525-A50】选料弹窗自动/人工称量列与种类映射-----------
/** 与配合示方「自动/人工」列相同字典 */
export const MIXING_MATERIAL_PICKER_WEIGH_MODE_DICT = 'xslmes_formula_spec_weigh_mode';
/** 选料弹窗表格列(隐藏 ERP 编号,新增仅本次有效的自动/人工称量) */
export const mixingMaterialPickerTableColumns: BasicColumn[] = [
{ title: '物料编码', align: 'center', width: 120, dataIndex: 'materialCode' },
{ title: '物料名称', align: 'center', width: 160, dataIndex: 'materialName' },
{ title: '自动/人工称量', align: 'center', width: 132, dataIndex: 'pickerWeighMode' },
{ title: '物料大类', align: 'center', width: 120, dataIndex: 'majorCategoryId_dictText' },
{ title: '物料小类', align: 'center', width: 120, dataIndex: 'minorCategoryId_dictText' },
{ title: '物料描述', align: 'center', width: 180, ellipsis: true, dataIndex: 'materialDesc' },
];
/** 配合示方称量方式 -> 混炼示方种类(与后端 resolveWeighModeMaterialKind 一致) */
export function resolveMixingMaterialKindFromWeighMode(weighMode?: string) {
if (weighMode == null || String(weighMode).trim() === '') {
return '';
}
const normalized = String(weighMode).trim();
const lower = normalized.toLowerCase();
if (lower.startsWith('auto') || normalized.includes('自动')) {
return '自动';
}
if (lower === 'manual' || normalized.includes('人工')) {
return '人工';
}
return '';
}
/** 选料确认时种类:称量方式优先,否则按小类胶料/小类名 */
export function resolveMixingMaterialKindForPicker(weighMode: string | undefined, isRubber?: unknown, minorName?: string) {
const fromWeighMode = resolveMixingMaterialKindFromWeighMode(weighMode);
if (fromWeighMode) {
return fromWeighMode;
}
return resolveMixingMaterialKindFromCategory(isRubber, minorName);
}
//update-end---author:cursor ---date:20260525 for【XSLMES-20260525-A50】选料弹窗自动/人工称量列与种类映射-----------
/** 选择密炼物料后回填混炼示方橡胶及配合剂明细行 */
export function applyMixingMaterialFromSelection(row: Recordable, material: Recordable, materialKind: string) {
if (!row || !material) {
return;
}
row.mixerMaterialName = material.materialName || material.materialCode || '';
row.mixerMaterialDesc = material.materialDesc || material.materialName || material.materialCode || '';
row.materialMajor = material.majorCategoryId_dictText || '';
row.materialMinor = material.minorCategoryId_dictText || '';
row.materialKind = materialKind || row.materialMinor || '';
}
//update-end---author:cursor ---date:20260525 for【XSLMES-20260525-A50】混炼示方密炼物料选料弹窗与种类解析-----------

View File

@@ -0,0 +1,221 @@
<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" :disabled="loading || !allCategoryIds.length" @change="onCheckAllChange">
小类展示
</Checkbox>
</div>
</template>
<template #content>
<Spin :spinning="loading">
<div v-if="!loading && !groupedCategories.length" :class="`${prefixCls}__empty`">
暂无物料小类请确认分类字典 XSLMES_MATERIAL 已配置
</div>
<div v-else :class="`${prefixCls}__list`">
<div v-for="group in groupedCategories" :key="group.majorId" :class="`${prefixCls}__group`">
<div :class="`${prefixCls}__group-title`">{{ group.majorName }}</div>
<div :class="`${prefixCls}__group-options`">
<Checkbox
v-for="opt in group.options"
:key="opt.value"
:checked="isMinorVisible(opt.value)"
@change="(e) => onMinorVisibleChange(opt.value, e.target.checked)"
>
{{ opt.label }}
</Checkbox>
</div>
</div>
</div>
</Spin>
<div :class="`${prefixCls}__footer`">
<a-button size="small" :disabled="loading || !allCategoryIds.length" @click="handleReset">重置</a-button>
<a-button size="small" type="primary" :disabled="loading || !allCategoryIds.length" @click="handleSave">保存</a-button>
</div>
</template>
<a-tooltip title="小类展示设置">
<a-button size="small" class="mixing-material-category-setting-btn" :disabled="loading" @click.stop>
<Icon icon="ant-design:setting-outlined" />
</a-button>
</a-tooltip>
</Popover>
</template>
<script lang="ts" setup>
import { computed, ref, watch, type PropType } from 'vue';
import { Popover, Checkbox, Spin } 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 { saveMixingMaterialPickerHiddenCategoryIds, type MixingMaterialPickerCategoryItem } from '../MesXslMixingSpec.data';
const prefixCls = 'mixing-material-category-setting';
const { createMessage } = useMessage();
const props = defineProps({
categories: {
type: Array as PropType<MixingMaterialPickerCategoryItem[]>,
default: () => [],
},
hiddenCategoryIds: {
type: Array as PropType<string[]>,
default: () => [],
},
loading: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(['update:hiddenCategoryIds', 'change']);
const popoverOpen = ref(false);
const draftVisibleIds = ref<string[]>([]);
const allCategoryIds = computed(() => (props.categories || []).map((item) => item.id));
//update-begin---author:cursor ---date:20260525 for【XSLMES-20260525-A50】选料弹窗小类设置按大类分组展示-----------
const groupedCategories = computed(() => {
const groupMap = new Map<string, { majorId: string; majorName: string; options: { label: string; value: string }[] }>();
(props.categories || []).forEach((item) => {
const majorId = item.majorId || 'unknown';
const majorName = item.majorName || '其他';
const group = groupMap.get(majorId) || { majorId, majorName, options: [] };
group.options.push({
label: item.name,
value: item.id,
});
groupMap.set(majorId, group);
});
return Array.from(groupMap.values());
});
//update-end---author:cursor ---date:20260525 for【XSLMES-20260525-A50】选料弹窗小类设置按大类分组展示-----------
const checkAll = computed(() => {
const all = allCategoryIds.value;
if (!all.length) {
return false;
}
return draftVisibleIds.value.length === all.length;
});
const indeterminate = computed(() => {
const total = allCategoryIds.value.length;
const checked = draftVisibleIds.value.length;
return checked > 0 && checked < total;
});
watch(
() => [props.hiddenCategoryIds, props.categories],
() => {
syncDraftFromHidden();
},
{ deep: true, immediate: true },
);
function syncDraftFromHidden() {
const hidden = new Set((props.hiddenCategoryIds || []).map(String));
draftVisibleIds.value = allCategoryIds.value.filter((id) => !hidden.has(String(id)));
}
function handleOpenChange(open: boolean) {
popoverOpen.value = open;
if (open) {
syncDraftFromHidden();
}
}
function onCheckAllChange(e: CheckboxChangeEvent) {
draftVisibleIds.value = e.target.checked ? [...allCategoryIds.value] : [];
}
//update-begin---author:cursor ---date:20260525 for【XSLMES-20260525-A50】小类展示分组勾选互不覆盖-----------
function isMinorVisible(id: string) {
return draftVisibleIds.value.map(String).includes(String(id));
}
function onMinorVisibleChange(id: string, checked: boolean) {
const visibleSet = new Set(draftVisibleIds.value.map(String));
const key = String(id);
if (checked) {
visibleSet.add(key);
} else {
visibleSet.delete(key);
}
draftVisibleIds.value = allCategoryIds.value.filter((itemId) => visibleSet.has(String(itemId)));
}
//update-end---author:cursor ---date:20260525 for【XSLMES-20260525-A50】小类展示分组勾选互不覆盖-----------
function handleReset() {
draftVisibleIds.value = [...allCategoryIds.value];
}
function handleSave() {
const visibleSet = new Set(draftVisibleIds.value.map(String));
const hidden = allCategoryIds.value.filter((id) => !visibleSet.has(String(id)));
saveMixingMaterialPickerHiddenCategoryIds(hidden);
emit('update:hiddenCategoryIds', hidden);
emit('change', hidden);
popoverOpen.value = false;
createMessage.success('小类展示设置已保存');
}
</script>
<style lang="less" scoped>
.mixing-material-category-setting-btn {
padding-inline: 8px;
}
</style>
<style lang="less">
.mixing-material-category-setting__popover {
.ant-popover-inner-content {
width: 320px;
max-width: 80vw;
}
}
.mixing-material-category-setting__empty {
padding: 12px 0;
color: var(--text-color-secondary, rgba(0, 0, 0, 0.45));
text-align: center;
}
.mixing-material-category-setting__list {
max-height: 320px;
overflow: auto;
padding: 4px 0 8px;
}
.mixing-material-category-setting__group + .mixing-material-category-setting__group {
margin-top: 12px;
}
.mixing-material-category-setting__group-title {
font-weight: 600;
margin-bottom: 6px;
color: var(--text-color, rgba(0, 0, 0, 0.88));
}
.mixing-material-category-setting__group {
.mixing-material-category-setting__group-options {
display: flex;
flex-direction: column;
gap: 8px;
}
}
.mixing-material-category-setting__footer {
display: flex;
justify-content: flex-end;
gap: 8px;
padding-top: 8px;
border-top: 1px solid var(--border-color-base, #f0f0f0);
}
</style>

View File

@@ -0,0 +1,406 @@
<template>
<BasicModal
v-bind="$attrs"
title="选择密炼物料"
:width="1180"
:getContainer="getModalContainer"
@register="registerModal"
@ok="handleOk"
>
<div class="mixing-material-picker">
<div class="mixing-material-picker-toolbar">
<a-input
v-model:value="keyword"
allow-clear
placeholder="关键字(物料编码/名称/描述)"
style="width: 280px"
@pressEnter="reloadTable"
/>
<a-button type="primary" @click="reloadTable">搜索</a-button>
<MesXslMixingMaterialCategorySetting
v-model:hiddenCategoryIds="hiddenCategoryIds"
:categories="allMinorCategories"
:loading="treeLoading"
@change="handleCategoryVisibilityChange"
/>
</div>
<div class="mixing-material-picker-body">
<aside class="mixing-material-picker-sider">
<div class="mixing-material-picker-sider-title">物料小类</div>
<Spin :spinning="treeLoading">
<BasicTree
:treeData="visibleCategoryTree"
:selectedKeys="selectedCategoryKeys"
:expandedKeys="expandedCategoryKeys"
defaultExpandLevel="2"
@update:selectedKeys="onCategorySelect"
@update:expandedKeys="onExpandedKeysChange"
/>
</Spin>
</aside>
<div class="mixing-material-picker-main">
<BasicTable @register="registerTable">
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'pickerWeighMode'">
<div class="mixing-material-picker-weigh-mode" @click.stop>
<JDictSelectTag
:value="getPickerWeighMode(record.id)"
:dictCode="MIXING_MATERIAL_PICKER_WEIGH_MODE_DICT"
:getPopupContainer="getSelectPopupContainer"
:showChooseOption="false"
placeholder="请选择"
popupClassName="mixing-material-picker-weigh-mode-dropdown"
style="width: 100%"
@change="(val) => setPickerWeighMode(record.id, val)"
/>
</div>
</template>
</template>
</BasicTable>
</div>
</div>
</div>
</BasicModal>
</template>
<script lang="ts" setup>
import { computed, onMounted, ref, watch } from 'vue';
import { Spin } from 'ant-design-vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { BasicTable, useTable } from '/@/components/Table';
import { BasicTree } from '/@/components/Tree';
import { defHttp } from '/@/utils/http/axios';
import { loadMesMaterialCategoryTreeData } from '/@/views/system/category/category.constants';
import { list as mixerList, queryById as queryMixerById } from '/@/views/mes/material/MesMixerMaterial.api';
import { useMessage } from '/@/hooks/web/useMessage';
import JDictSelectTag from '/@/components/Form/src/jeecg/components/JDictSelectTag.vue';
import MesXslMixingMaterialCategorySetting from './MesXslMixingMaterialCategorySetting.vue';
import {
applyMixingMaterialFromSelection,
loadMixingMaterialPickerHiddenCategoryIds,
MIXING_MATERIAL_PICKER_WEIGH_MODE_DICT,
mixingMaterialPickerTableColumns,
resolveMixingMaterialKindForPicker,
sanitizeMixingMaterialPickerHiddenCategoryIds,
saveMixingMaterialPickerHiddenCategoryIds,
type MixingMaterialPickerCategoryItem,
} from '../MesXslMixingSpec.data';
import type { KeyType } from '/@/components/Tree/src/types/tree';
const TREE_ALL = 'ALL';
const emit = defineEmits(['register', 'select']);
const { createMessage } = useMessage();
const keyword = ref('');
const treeLoading = ref(false);
const rawCategoryTree = ref<Recordable[]>([]);
const allMajorCategories = ref<Array<{ id: string; name: string; minors: MixingMaterialPickerCategoryItem[] }>>([]);
const allMinorCategories = ref<MixingMaterialPickerCategoryItem[]>([]);
const hiddenCategoryIds = ref<string[]>(loadMixingMaterialPickerHiddenCategoryIds());
const selectedCategoryKeys = ref<KeyType[]>([TREE_ALL]);
const expandedCategoryKeys = ref<KeyType[]>([TREE_ALL]);
const selectedRow = ref<Recordable | null>(null);
const categoryRubberMap = ref<Record<string, boolean>>({});
const pickerWeighModeMap = ref<Record<string, string>>({});
const hiddenCategoryIdSet = computed(() => new Set(hiddenCategoryIds.value.map(String)));
function getModalContainer() {
return document.body;
}
function getSelectPopupContainer() {
return document.body;
}
function filterHiddenCategoryTree(nodes: Recordable[], hidden: Set<string>): Recordable[] {
return (nodes || [])
.map((major) => {
const children = (major.children || [])
.filter((minor) => !hidden.has(String(minor.key)))
.map((minor) => ({
key: minor.key,
title: minor.title,
}));
if (!children.length) {
return null;
}
return {
key: major.key,
title: major.title,
children,
};
})
.filter(Boolean) as Recordable[];
}
const visibleCategoryTree = computed(() => [
{
key: TREE_ALL,
title: '全部小类',
children: filterHiddenCategoryTree(rawCategoryTree.value, hiddenCategoryIdSet.value),
},
]);
function syncExpandedCategoryKeys() {
const keys: KeyType[] = [TREE_ALL];
for (const major of visibleCategoryTree.value[0]?.children || []) {
keys.push(major.key);
}
expandedCategoryKeys.value = keys;
}
watch(
visibleCategoryTree,
() => {
syncExpandedCategoryKeys();
},
{ deep: true },
);
const selectedCategoryFilter = computed(() => {
const key = selectedCategoryKeys.value[0];
if (!key || key === TREE_ALL) {
return {};
}
const keyStr = String(key);
const major = allMajorCategories.value.find((item) => item.id === keyStr);
if (major) {
return { majorCategoryId: major.id };
}
return { minorCategoryId: keyStr };
});
const [registerTable, { reload, getSelectRowKeys, getSelectRows, clearSelectedRowKeys }] = useTable({
api: mixerList,
columns: mixingMaterialPickerTableColumns,
rowKey: 'id',
useSearchForm: false,
pagination: { pageSize: 10 },
canResize: false,
showIndexColumn: true,
immediate: true,
beforeFetch: (params) => {
const next = { ...params, ...selectedCategoryFilter.value };
const kw = keyword.value?.trim();
if (kw) {
next.materialName = `*${kw}*`;
}
return next;
},
rowSelection: {
type: 'radio',
columnWidth: 48,
onChange: (_keys, rows) => {
selectedRow.value = rows?.[0] ?? null;
},
},
clickToRowSelect: true,
});
//update-begin---author:cursor ---date:20260525 for【XSLMES-20260525-A50】选料弹窗打开时初始化对齐其他SelectModal-----------
const [registerModal, { setModalProps, closeModal }] = useModalInner(async () => {
await initPickerModal();
});
async function initPickerModal() {
selectedRow.value = null;
keyword.value = '';
pickerWeighModeMap.value = {};
clearSelectedRowKeys?.();
hiddenCategoryIds.value = loadMixingMaterialPickerHiddenCategoryIds();
selectedCategoryKeys.value = [TREE_ALL];
setModalProps({ confirmLoading: false });
await loadMaterialCategoryTree();
reloadTable();
}
onMounted(() => {
loadMaterialCategoryTree();
});
//update-end---author:cursor ---date:20260525 for【XSLMES-20260525-A50】选料弹窗打开时初始化对齐其他SelectModal-----------
async function loadMaterialCategoryTree() {
treeLoading.value = true;
try {
const { majors, minors, treeNodes } = await loadMesMaterialCategoryTreeData();
rawCategoryTree.value = treeNodes;
allMajorCategories.value = majors;
allMinorCategories.value = minors;
const sanitizedHidden = sanitizeMixingMaterialPickerHiddenCategoryIds(
minors.map((item) => item.id),
hiddenCategoryIds.value,
);
if (sanitizedHidden.length !== hiddenCategoryIds.value.length) {
hiddenCategoryIds.value = sanitizedHidden;
saveMixingMaterialPickerHiddenCategoryIds(sanitizedHidden);
}
categoryRubberMap.value = {};
syncExpandedCategoryKeys();
if (!minors.length) {
createMessage.warning('未加载到物料小类,请确认分类字典根编码 XSLMES_MATERIAL 及其下级分类已配置。');
}
} catch {
rawCategoryTree.value = [];
allMajorCategories.value = [];
allMinorCategories.value = [];
createMessage.warning('加载物料分类树失败,请检查分类根编码 XSLMES_MATERIAL 是否存在。');
} finally {
treeLoading.value = false;
}
}
function reloadTable() {
reload();
}
function getPickerWeighMode(materialId?: string) {
if (!materialId) {
return undefined;
}
return pickerWeighModeMap.value[String(materialId)];
}
function setPickerWeighMode(materialId: string | undefined, value?: string) {
if (!materialId) {
return;
}
const key = String(materialId);
const next = { ...pickerWeighModeMap.value };
if (value == null || value === '') {
delete next[key];
} else {
next[key] = String(value);
}
pickerWeighModeMap.value = next;
}
function onCategorySelect(keys: KeyType[]) {
selectedCategoryKeys.value = keys?.length ? keys : [TREE_ALL];
reloadTable();
}
function onExpandedKeysChange(keys: KeyType[]) {
expandedCategoryKeys.value = keys?.length ? keys : [TREE_ALL];
}
function handleCategoryVisibilityChange() {
const key = selectedCategoryKeys.value[0];
if (!key || key === TREE_ALL) {
syncExpandedCategoryKeys();
reloadTable();
return;
}
const keyStr = String(key);
const hidden = hiddenCategoryIdSet.value;
const major = allMajorCategories.value.find((item) => item.id === keyStr);
if (major) {
const hasVisibleMinor = major.minors.some((minor) => !hidden.has(String(minor.id)));
if (!hasVisibleMinor) {
selectedCategoryKeys.value = [TREE_ALL];
}
} else if (hidden.has(keyStr)) {
selectedCategoryKeys.value = [TREE_ALL];
}
syncExpandedCategoryKeys();
reloadTable();
}
async function resolveKindForMaterial(material: Recordable, weighMode?: string) {
const minorId = material?.minorCategoryId ? String(material.minorCategoryId) : '';
const minorName = material?.minorCategoryId_dictText || '';
if (!minorId) {
return resolveMixingMaterialKindForPicker(weighMode, false, minorName);
}
if (categoryRubberMap.value[minorId] === undefined) {
try {
const cat = await defHttp.get<Recordable>({ url: '/sys/category/queryById', params: { id: minorId } });
categoryRubberMap.value[minorId] = cat?.isRubber === '1' || cat?.isRubber === 1;
} catch {
categoryRubberMap.value[minorId] = false;
}
}
return resolveMixingMaterialKindForPicker(
weighMode,
categoryRubberMap.value[minorId] ? '1' : '0',
minorName,
);
}
async function handleOk() {
const keys = (getSelectRowKeys?.() || []) as string[];
let row = selectedRow.value || ((getSelectRows?.() || []) as Recordable[])[0];
if (!row && keys.length) {
try {
const raw = await queryMixerById({ id: keys[0] });
row = (raw as any)?.id != null ? raw : (raw as any)?.result;
} catch {
// ignore
}
}
if (!row?.id) {
createMessage.warning('请选择一条密炼物料');
return;
}
const weighMode = getPickerWeighMode(row.id);
const payload: Recordable = { ...row, pickerWeighMode: weighMode };
const materialKind = await resolveKindForMaterial(row, weighMode);
applyMixingMaterialFromSelection(payload, row, materialKind);
emit('select', payload);
closeModal();
}
</script>
<style lang="less" scoped>
.mixing-material-picker {
display: flex;
flex-direction: column;
gap: 12px;
min-height: 520px;
}
.mixing-material-picker-toolbar {
display: flex;
align-items: center;
gap: 8px;
}
.mixing-material-picker-body {
display: flex;
gap: 12px;
min-height: 480px;
}
.mixing-material-picker-sider {
width: 240px;
flex-shrink: 0;
border: 1px solid var(--border-color-base, #f0f0f0);
border-radius: 4px;
padding: 8px;
overflow: auto;
}
.mixing-material-picker-sider-title {
font-weight: 600;
margin-bottom: 8px;
}
.mixing-material-picker-main {
flex: 1;
min-width: 0;
}
.mixing-material-picker-weigh-mode {
min-width: 108px;
}
</style>
<style lang="less">
/* 下拉挂到 body避免表格 overflow 裁剪;层级高于 Modal */
.mixing-material-picker-weigh-mode-dropdown {
z-index: 2100 !important;
}
</style>

View File

@@ -55,7 +55,15 @@
<tr>
<th class="formTitle" colspan="1">换算系数</th>
<td class="formValue" colspan="2">
<a-input-number v-model:value="sheetForm.convertFactor" :disabled="!showFooter" :precision="6" :bordered="false" class="form-input" style="width: 100%" />
<a-input-number
v-model:value="sheetForm.convertFactor"
:disabled="!showFooter"
:precision="6"
:bordered="false"
class="form-input"
style="width: 100%"
@update:value="handleConvertFactorChange"
/>
</td>
<th class="formTitle" colspan="1">填充体积</th>
<td class="formValue" colspan="1">
@@ -80,7 +88,7 @@
</td>
<th class="formTitle" colspan="1">母胶比重</th>
<td class="formValue">
<a-input-number v-model:value="sheetForm.motherRubberSg" :disabled="!showFooter" :precision="6" :bordered="false" class="form-input" style="width: 100%" />
<a-input-number v-model:value="sheetForm.motherRubberSg" :disabled="!showFooter" :precision="6" :bordered="false" class="form-input" style="width: 100%" @update:value="recalcFillVolume" />
</td>
<th class="formTitle">段数</th>
<td class="formValue" colspan="2">
@@ -104,7 +112,7 @@
<tr>
<th class="formTitle" colspan="1">终炼胶比重</th>
<td class="formValue">
<a-input-number v-model:value="sheetForm.finalRubberSg" :disabled="!showFooter" :precision="6" :bordered="false" class="form-input" style="width: 100%" />
<a-input-number v-model:value="sheetForm.finalRubberSg" :disabled="!showFooter" :precision="6" :bordered="false" class="form-input" style="width: 100%" @update:value="recalcFillVolume" />
</td>
<th class="formTitle" colspan="1">适用工厂</th>
<td class="formValue" colspan="2">
@@ -181,25 +189,60 @@
</div>
</div>
<div class="material-table-wrap" :style="{ height: `${materialMainTableHeight}px` }">
<JVxeTable
:key="materialTableLayoutKey"
ref="materialRef"
row-number
keep-source
bordered
:fit="false"
:column-config="{ resizable: true }"
:row-config="materialRowConfig"
size="mini"
:height="materialMainTableHeight"
:scroll-x="{ enabled: false }"
:scroll-y="{ enabled: true }"
:columns="visibleMaterialColumns"
:dataSource="materialData"
:disabled="!showFooter"
@resizable-change="handleMaterialColumnResize"
@column-resizable-change="handleMaterialColumnResize"
/>
<!--update-begin---author:cursor ---date:20260525 forXSLMES-20260525-A42橡胶及配合剂明细底部固定合计行----------- -->
<div class="material-table-stack" :style="{ width: `${materialTableWidth}px` }">
<div class="material-table-body" :style="{ height: `${materialBodyTableHeight}px` }">
<JVxeTable
:key="materialTableLayoutKey"
ref="materialRef"
row-number
keep-source
bordered
:fit="false"
:column-config="{ resizable: true }"
:row-config="materialRowConfig"
size="mini"
:height="materialBodyTableHeight"
:scroll-x="{ enabled: false }"
:scroll-y="{ enabled: true }"
:columns="visibleMaterialColumns"
:dataSource="materialData"
:disabled="!showFooter"
@value-change="handleMaterialValueChange"
@resizable-change="handleMaterialColumnResize"
@column-resizable-change="handleMaterialColumnResize"
>
<template #mixerMaterialNameSlot="{ row }">
<div
class="mixing-material-name-cell"
:class="{ 'is-disabled': !showFooter }"
:style="{ minHeight: `${materialHeightPref.rowHeight}px` }"
@click.stop="openMixingMaterialPicker(row)"
>
<span v-if="row.mixerMaterialName" class="mixing-material-name-text">{{ row.mixerMaterialName }}</span>
</div>
</template>
</JVxeTable>
</div>
<div class="material-table-footer">
<div class="material-table-footer-row">
<div
class="material-footer-seq"
:style="{ width: `${MIXING_MATERIAL_ROW_NUMBER_WIDTH}px`, height: `${materialHeightPref.rowHeight}px` }"
></div>
<div
v-for="cell in materialFooterCells"
:key="cell.key"
class="material-footer-cell"
:class="{ 'is-label': cell.isLabel, 'is-total': cell.isTotal }"
:style="{ width: `${cell.width}px`, height: `${materialHeightPref.rowHeight}px` }"
>
{{ cell.text }}
</div>
</div>
</div>
</div>
<!--update-end---author:cursor ---date:20260525 forXSLMES-20260525-A42橡胶及配合剂明细底部固定合计行----------- -->
</div>
<!--update-begin---author:cursor ---date:20260522 forXSLMES-20260522-A18TCU温度条件表移至橡胶及配合剂下方----------- -->
<div class="left-panel-section left-panel-section-tcu">
@@ -368,11 +411,11 @@
</div>
<BasicForm v-show="false" @register="registerForm" />
<MesXslEquipmentLedgerSelectModal @register="registerMachineModal" @select="onMachineSelect" />
<MesXslMixerPsCompileSelectModal @register="registerIssueNumberModal" @select="onIssueNumberSelect" />
</div>
</BasicModal>
<MesXslEquipmentLedgerSelectModal @register="registerMachineModal" @select="onMachineSelect" />
<MesXslMixerPsCompileSelectModal @register="registerIssueNumberModal" @select="onIssueNumberSelect" />
<MesXslMixingMaterialSelectModal @register="registerMixingMaterialModal" @select="onMixingMaterialSelect" />
</template>
<script lang="ts" setup>
@@ -411,6 +454,19 @@ import {
DEFAULT_MIXING_STEP_ROW_COUNT,
DEFAULT_MIXING_DOWN_STEP_ROW_COUNT,
buildDefaultMixingTcuRows,
applyMixingMaterialFromSelection,
fillMixingMaterialAccumWeight,
calcMixingMaterialUnitWeightTotal,
calcMixingMaterialAccumWeightTotal,
buildMixingMaterialFooterCells,
normalizeMixingConvertFactor,
initMaterialBaseUnitWeights,
applyConvertFactorToMaterialRows,
syncMaterialBaseUnitWeightFromDisplay,
calcMixingFillVolume,
resolveMixingSpecificGravity,
MIXING_MATERIAL_ROW_NUMBER_WIDTH,
MIXING_MATERIAL_FOOTER_ROW_HEIGHT,
MIXING_MATERIAL_MIN_COLUMN_WIDTH,
MIXING_TCU_MIN_COLUMN_WIDTH,
MIXING_STEP_MIN_COLUMN_WIDTH,
@@ -422,7 +478,9 @@ import MesXslMixingStepSelectCell from './MesXslMixingStepSelectCell.vue';
import { list as mixerActionList } from '/@/views/xslmes/mesXslMixerAction/MesXslMixerAction.api';
import { list as mixerConditionList } from '/@/views/xslmes/mesXslMixerCondition/MesXslMixerCondition.api';
import MesXslEquipmentLedgerSelectModal from '/@/views/xslmes/mesXslEquipInspectConfig/components/MesXslEquipmentLedgerSelectModal.vue';
import { queryById as queryEquipmentById } from '/@/views/xslmes/mesXslEquipmentLedger/MesXslEquipmentLedger.api';
import MesXslMixerPsCompileSelectModal from '/@/views/xslmes/mesXslMixerPsCompile/components/MesXslMixerPsCompileSelectModal.vue';
import MesXslMixingMaterialSelectModal from './MesXslMixingMaterialSelectModal.vue';
const emit = defineEmits(['register', 'success']);
const { createMessage } = useMessage();
@@ -437,6 +495,7 @@ const mixerConditionOptions = ref<{ title: string; value: string }[]>([]);
//update-end---author:cursor ---date:20260522 for【XSLMES-20260522-A34】混合步骤动作/组合下拉选项-----------
const materialRef = ref();
const materialPickerRow = ref<Recordable | null>(null);
const stepRef = ref();
const downStepRef = ref();
const tcuRef = ref();
@@ -465,6 +524,22 @@ const stepHeightPref = ref(loadMixingTableHeightPreference('step'));
const downStepHeightPref = ref(loadMixingTableHeightPreference('downStep'));
const materialMainTableHeight = computed(() => calcMixingDetailTableViewportHeight('material', materialHeightPref.value));
//update-begin---author:cursor ---date:20260525 for【XSLMES-20260525-A42】橡胶及配合剂明细底部固定合计行-----------
const materialBodyTableHeight = computed(() =>
Math.max(materialMainTableHeight.value - MIXING_MATERIAL_FOOTER_ROW_HEIGHT, 80),
);
const materialUnitWeightTotal = ref<number | null>(null);
//update-begin---author:cursor ---date:20260525 for【XSLMES-20260525-A46】换算系数/单重/机台有效体积联动填充体积-----------
const machineEffectiveVolume = ref('');
//update-end---author:cursor ---date:20260525 for【XSLMES-20260525-A46】换算系数/单重/机台有效体积联动填充体积-----------
const materialAccumWeightTotal = ref<number | null>(null);
const materialFooterCells = computed(() =>
buildMixingMaterialFooterCells(visibleMaterialColumns.value, materialColumnWidths.value, {
unitWeight: materialUnitWeightTotal.value,
accumWeight: materialAccumWeightTotal.value,
}),
);
//update-end---author:cursor ---date:20260525 for【XSLMES-20260525-A42】橡胶及配合剂明细底部固定合计行-----------
const stepMainTableHeight = computed(() => calcMixingDetailTableViewportHeight('step', stepHeightPref.value));
const tcuTableHeight = computed(() => calcMixingDetailTableViewportHeight('tcu', tcuHeightPref.value));
const downStepTableHeight = computed(() => calcMixingDetailTableViewportHeight('downStep', downStepHeightPref.value));
@@ -509,6 +584,105 @@ function handleMaterialColumnResize(params: Recordable) {
};
saveMixingMaterialColumnWidths(materialColumnWidths.value);
}
//update-begin---author:cursor ---date:20260525 for【XSLMES-20260525-A41】橡胶及配合剂明细累计按种类分组合计-----------
/** JVxe getTableData 为浅拷贝,批量改值需用 fullData 原行并强制 refresh */
function resolveMaterialTableRawRows(): Recordable[] {
const fullData = materialRef.value?.getXTable?.()?.getTableData?.()?.fullData as Recordable[] | undefined;
if (Array.isArray(fullData) && fullData.length) {
return fullData;
}
return (materialRef.value?.getTableData?.() || materialData.value || []) as Recordable[];
}
function refreshMaterialTableView() {
materialRef.value?.getXTable?.()?.updateData?.();
}
function applyMaterialAccumWeight(rows?: Recordable[]) {
const targetRows = rows || resolveMaterialTableRawRows();
fillMixingMaterialAccumWeight(targetRows);
materialUnitWeightTotal.value = calcMixingMaterialUnitWeightTotal(targetRows);
materialAccumWeightTotal.value = calcMixingMaterialAccumWeightTotal(targetRows);
refreshMaterialTableView();
recalcFillVolume();
}
//update-begin---author:cursor ---date:20260525 for【XSLMES-20260525-A46】换算系数/单重/机台有效体积联动填充体积-----------
async function loadMachineEffectiveVolume(machineId?: string) {
const id = machineId || sheetForm.machineId;
if (!id) {
machineEffectiveVolume.value = '';
return;
}
try {
const raw = await queryEquipmentById({ id });
const row = (raw as Recordable)?.id != null ? raw : (raw as Recordable)?.result;
machineEffectiveVolume.value = row?.effectiveVolume || '';
} catch {
machineEffectiveVolume.value = '';
}
}
function recalcFillVolume() {
if (!showFooter.value) {
return;
}
const totalWeight =
materialUnitWeightTotal.value ?? calcMixingMaterialUnitWeightTotal(resolveMaterialTableRawRows());
const specificGravity = resolveMixingSpecificGravity(sheetForm);
const next = calcMixingFillVolume(totalWeight, specificGravity, machineEffectiveVolume.value);
if (next != null) {
sheetForm.fillVolume = next;
}
}
//update-end---author:cursor ---date:20260525 for【XSLMES-20260525-A46】换算系数/单重/机台有效体积联动填充体积-----------
//update-begin---author:cursor ---date:20260525 for【XSLMES-20260525-A43】换算系数联动明细单重实时计算-----------
const lastConvertFactor = ref<number>(1);
const convertFactorApplying = ref(false);
function applyConvertFactorToMaterials(factor: unknown) {
const rows = resolveMaterialTableRawRows();
applyConvertFactorToMaterialRows(rows, factor, lastConvertFactor.value);
lastConvertFactor.value = normalizeMixingConvertFactor(factor);
applyMaterialAccumWeight(rows);
}
function handleConvertFactorChange(value: unknown) {
if (!showFooter.value || convertFactorApplying.value) {
return;
}
applyConvertFactorToMaterials(value);
recalcFillVolume();
}
function stripMaterialRowForSave(row: Recordable) {
if (!row) {
return row;
}
const { baseUnitWeight: _baseUnitWeight, ...rest } = row;
return rest;
}
//update-end---author:cursor ---date:20260525 for【XSLMES-20260525-A43】换算系数联动明细单重实时计算-----------
function recalcMaterialAccumWeight() {
applyMaterialAccumWeight();
}
function handleMaterialValueChange(event) {
const key = event?.column?.key;
const row = event?.row;
//update-begin---author:cursor ---date:20260525 for【XSLMES-20260525-A43】换算系数联动明细单重实时计算-----------
if (key === 'unitWeight' && row) {
syncMaterialBaseUnitWeightFromDisplay(row, sheetForm.convertFactor);
}
//update-end---author:cursor ---date:20260525 for【XSLMES-20260525-A43】换算系数联动明细单重实时计算-----------
if (key === 'unitWeight' || key === 'materialKind') {
recalcMaterialAccumWeight();
}
}
//update-end---author:cursor ---date:20260525 for【XSLMES-20260525-A41】橡胶及配合剂明细累计按种类分组合计-----------
//update-end---author:cursor ---date:20260522 for【XSLMES-20260522-A17】橡胶及配合剂明细列展示设置-----------
//update-begin---author:cursor ---date:20260522 for【XSLMES-20260522-A19】TCU温度条件表列宽可调且表头换行-----------
@@ -710,6 +884,7 @@ const [registerForm, { resetFields, setFieldsValue, validate, setProps }] = useF
//update-begin---author:cursor ---date:20260522 for【XSLMES-20260522-A33】混炼示方主表选择弹窗-----------
const [registerMachineModal, { openModal: openMachineModalInner }] = useModal();
const [registerIssueNumberModal, { openModal: openIssueNumberModalInner }] = useModal();
const [registerMixingMaterialModal, { openModal: openMixingMaterialModalInner }] = useModal();
function openMachinePicker() {
if (!showFooter.value) {
@@ -721,6 +896,11 @@ function openMachinePicker() {
async function onMachineSelect(payload: Recordable | null) {
sheetForm.machineId = payload?.equipmentLedgerId || '';
sheetForm.machineName = payload?.equipmentName || '';
machineEffectiveVolume.value = payload?.effectiveVolume || '';
if (sheetForm.machineId && !machineEffectiveVolume.value) {
await loadMachineEffectiveVolume(sheetForm.machineId);
}
recalcFillVolume();
await loadMixerStepOptions(sheetForm.machineId);
}
@@ -738,6 +918,27 @@ function onIssueNumberSelect(payload: Recordable | null) {
mixerPsCompilePickerId.value = payload.psCompileId || '';
sheetForm.issueNumber = payload.psCode || '';
}
function openMixingMaterialPicker(row: Recordable) {
if (!showFooter.value || !row) {
return;
}
materialPickerRow.value = row;
openMixingMaterialModalInner(true, { picker: true, ts: Date.now() });
}
function onMixingMaterialSelect(payload: Recordable | null) {
if (!payload || !materialPickerRow.value) {
return;
}
applyMixingMaterialFromSelection(
materialPickerRow.value,
payload,
payload.materialKind || payload.materialMinor || '',
);
recalcMaterialAccumWeight();
materialPickerRow.value = null;
}
//update-end---author:cursor ---date:20260522 for【XSLMES-20260522-A33】混炼示方主表选择弹窗-----------
function ensureTcuDefaultRows(rows: Recordable[] = []) {
@@ -783,6 +984,7 @@ function resetSheetForm() {
sheetForm.approveTime = '';
sheetForm.changeDate = '';
mixerPsCompilePickerId.value = '';
machineEffectiveVolume.value = '';
refreshSignDisplay({});
}
@@ -806,6 +1008,9 @@ const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data
await resetFields();
resetSheetForm();
materialData.value = [];
materialUnitWeightTotal.value = null;
materialAccumWeightTotal.value = null;
lastConvertFactor.value = 1;
stepData.value = [];
downStepData.value = [];
tcuData.value = ensureTcuDefaultRows([]);
@@ -819,10 +1024,16 @@ const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data
const row = raw?.result || raw;
Object.assign(sheetForm, row || {});
refreshSignDisplay(row || {});
await loadMachineEffectiveVolume(sheetForm.machineId);
await loadMixerStepOptions(sheetForm.machineId);
await syncSheetToForm();
//update-begin---author:cursor ---date:20260522 for【XSLMES-20260522-A39】编辑页明细补齐默认空行与新增一致-----------
materialData.value = ensureMixingDetailRows(row?.materialList || [], DEFAULT_MIXING_MATERIAL_ROW_COUNT);
convertFactorApplying.value = true;
initMaterialBaseUnitWeights(materialData.value, sheetForm.convertFactor, true);
lastConvertFactor.value = normalizeMixingConvertFactor(sheetForm.convertFactor);
applyMaterialAccumWeight(materialData.value);
convertFactorApplying.value = false;
stepData.value = ensureMixingDetailRows(row?.stepList || [], DEFAULT_MIXING_STEP_ROW_COUNT);
downStepData.value = ensureMixingDetailRows(row?.downStepList || [], DEFAULT_MIXING_DOWN_STEP_ROW_COUNT);
//update-end---author:cursor ---date:20260522 for【XSLMES-20260522-A39】编辑页明细补齐默认空行与新增一致-----------
@@ -838,6 +1049,7 @@ const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data
});
//update-begin---author:cursor ---date:20260522 for【XSLMES-20260522-A22】明细表默认空行数-----------
materialData.value = createEmptyMaterialRows();
lastConvertFactor.value = normalizeMixingConvertFactor(sheetForm.convertFactor);
stepData.value = createEmptyStepRows();
downStepData.value = createEmptyDownStepRows();
//update-end---author:cursor ---date:20260522 for【XSLMES-20260522-A22】明细表默认空行数-----------
@@ -850,14 +1062,15 @@ const title = computed(() => (!showFooter.value && unref(isUpdate) ? '混炼示
async function handleSubmit() {
await syncSheetToForm();
const formValues = await validate();
const materialList = materialRef.value?.getTableData?.() || materialData.value;
const materialList = resolveMaterialTableRawRows();
applyMaterialAccumWeight(materialList);
const stepList = stepRef.value?.getTableData?.() || stepData.value;
const downStepList = downStepRef.value?.getTableData?.() || downStepData.value;
const tcuList = ensureTcuDefaultRows((tcuRef.value?.getTableData?.() || tcuData.value) as Recordable[]);
const cleanRows = (rows: Recordable[]) => (rows || []).filter((row) => Object.values(row || {}).some((v) => v != null && v !== ''));
const payload = {
...formValues,
materialList: cleanRows(materialList),
materialList: cleanRows(materialList).map(stripMaterialRowForSave),
stepList: cleanRows(stepList),
downStepList: cleanRows(downStepList),
tcuList: tcuList.map((row) => ({
@@ -1008,6 +1221,25 @@ async function handleSubmit() {
color: #262626;
}
:deep(.mixing-material-name-cell) {
display: flex;
align-items: center;
width: 100%;
height: 100%;
box-sizing: border-box;
padding: 2px 4px;
cursor: pointer;
}
:deep(.mixing-material-name-cell.is-disabled) {
cursor: not-allowed;
opacity: 0.65;
}
:deep(.mixing-material-name-text) {
color: #262626;
}
}
//update-end---author:cursor ---date:20260522 for【XSLMES-20260522-A17】顶部施工表对齐旧系统13列表格-----------
@@ -1027,6 +1259,9 @@ async function handleSubmit() {
.material-table-wrap {
overflow-x: auto;
overflow-y: hidden;
display: flex;
flex-direction: column;
:deep(.jeecg-j-vxe-table),
:deep(.j-vxe-table-box),
@@ -1040,6 +1275,76 @@ async function handleSubmit() {
}
}
//update-begin---author:cursor ---date:20260525 for【XSLMES-20260525-A42】橡胶及配合剂明细底部固定合计行-----------
.material-table-stack {
display: flex;
flex-direction: column;
flex-shrink: 0;
min-width: min-content;
}
.material-table-body {
flex: 0 0 auto;
overflow: hidden;
:deep(.vxe-table--footer-wrapper) {
display: none;
}
:deep(.col--mixerMaterialName .vxe-cell) {
padding: 0;
height: 100%;
}
:deep(.col--mixerMaterialName .vxe-cell > div) {
height: 100%;
}
}
.material-table-footer {
flex-shrink: 0;
border: 1px solid #e8e8e8;
border-top: none;
background: #fafafa;
box-sizing: border-box;
}
.material-table-footer-row {
display: flex;
align-items: stretch;
box-sizing: border-box;
font-size: 12px;
}
.material-footer-seq,
.material-footer-cell {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
border-right: 1px solid #e8e8e8;
box-sizing: border-box;
padding: 0 2px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
&:last-child {
border-right: none;
}
}
.material-footer-seq {
background: #fafafa;
}
.material-footer-cell.is-label,
.material-footer-cell.is-total {
font-weight: 600;
color: #262626;
}
//update-end---author:cursor ---date:20260525 for【XSLMES-20260525-A42】橡胶及配合剂明细底部固定合计行-----------
//update-begin---author:cursor ---date:20260522 for【XSLMES-20260522-A26】TCU紧凑两行且胶料表向下扩展-----------
.material-table-wrap--fill {
flex: 1;