新增MES混炼示方模块,包括主表及子表结构、控制器、服务和映射器的实现,支持增删改查功能,优化数据验证和用户体验,增强系统稳定性。
This commit is contained in:
@@ -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>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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>
|
||||
Reference in New Issue
Block a user