优化混炼示方,新增种类配置
This commit is contained in:
@@ -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>
|
||||
Reference in New Issue
Block a user