407 lines
13 KiB
Vue
407 lines
13 KiB
Vue
<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>
|