新增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,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>