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

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

@@ -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>