1034 lines
32 KiB
Vue
1034 lines
32 KiB
Vue
|
|
import { BasicColumn, FormSchema } from '/@/components/Table';
|
|||
|
|
import { JVxeColumn, JVxeTypes } from '/@/components/jeecg/JVxeTable/types';
|
|||
|
|
import { buildUUID } from '/@/utils/uuid';
|
|||
|
|
import { createLocalStorage } from '/@/utils/cache';
|
|||
|
|
import { loadCategoryData } from '/@/api/common/api';
|
|||
|
|
import { loadTreeData as loadCategoryTreeRoot } from '/@/views/system/category/category.api';
|
|||
|
|
import { MATERIAL_CATEGORY_ROOT_CODE } from '/@/views/mes/material/MesMixerMaterialSysCategory.api';
|
|||
|
|
|
|||
|
|
const formulaLineColumnStorage = createLocalStorage();
|
|||
|
|
|
|||
|
|
/** 明细列隐藏偏好 localStorage 键 */
|
|||
|
|
export const FORMULA_LINE_COLUMN_CACHE_KEY = 'mes_xsl_formula_spec_line_hidden_columns';
|
|||
|
|
|
|||
|
|
/** 不允许隐藏的明细列 */
|
|||
|
|
export const FORMULA_LINE_LOCKED_COLUMN_KEYS = ['phr', 'mixerMaterialId'];
|
|||
|
|
|
|||
|
|
export interface FormulaLineColumnSettingItem {
|
|||
|
|
key: string;
|
|||
|
|
title: string;
|
|||
|
|
locked?: boolean;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const colHalf = { span: 12 };
|
|||
|
|
const colThird = { span: 8 };
|
|||
|
|
const colQuarter = { span: 6 };
|
|||
|
|
|
|||
|
|
/** 汇总区隐藏字段(明细表 footer 行录入,表单仅承载数据) */
|
|||
|
|
export const summaryFooterHiddenFields: FormSchema[] = [
|
|||
|
|
{ field: 'aRubberTotalPhr', label: '', component: 'InputNumber', show: false },
|
|||
|
|
{ field: 'totalPhr', label: '', component: 'InputNumber', show: false },
|
|||
|
|
...Array.from({ length: 7 }, (_, index) => ({
|
|||
|
|
field: `stage${index + 1}Total`,
|
|||
|
|
label: '',
|
|||
|
|
component: 'InputNumber',
|
|||
|
|
show: false,
|
|||
|
|
})),
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
export const SUMMARY_FOOTER_FIELD_KEYS = [
|
|||
|
|
'aRubberTotalPhr',
|
|||
|
|
'totalPhr',
|
|||
|
|
'stage1Total',
|
|||
|
|
'stage2Total',
|
|||
|
|
'stage3Total',
|
|||
|
|
'stage4Total',
|
|||
|
|
'stage5Total',
|
|||
|
|
'stage6Total',
|
|||
|
|
'stage7Total',
|
|||
|
|
] as const;
|
|||
|
|
|
|||
|
|
export const SUMMARY_METRICS_FIELD_KEYS = [
|
|||
|
|
'naturalRubber',
|
|||
|
|
'syntheticRubber',
|
|||
|
|
'totalAmount',
|
|||
|
|
'weightUnitPrice',
|
|||
|
|
'volumeUnitPrice',
|
|||
|
|
'qRubberSg',
|
|||
|
|
'aRubberSg',
|
|||
|
|
] as const;
|
|||
|
|
|
|||
|
|
/** 汇总指标隐藏字段(自定义表格录入,表单仅承载数据) */
|
|||
|
|
export const summaryMetricsHiddenFields: FormSchema[] = SUMMARY_METRICS_FIELD_KEYS.map((field) => ({
|
|||
|
|
field,
|
|||
|
|
label: '',
|
|||
|
|
component: 'InputNumber',
|
|||
|
|
show: false,
|
|||
|
|
}));
|
|||
|
|
|
|||
|
|
export const stageTotalNumberProps = {
|
|||
|
|
min: 0,
|
|||
|
|
precision: 4,
|
|||
|
|
style: { width: '100%' },
|
|||
|
|
bordered: false,
|
|||
|
|
controls: false,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
export const summaryMetricNumberProps = {
|
|||
|
|
min: 0,
|
|||
|
|
precision: 4,
|
|||
|
|
style: { width: '100%' },
|
|||
|
|
bordered: false,
|
|||
|
|
controls: false,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
export const summarySgNumberProps = {
|
|||
|
|
min: 0,
|
|||
|
|
precision: 6,
|
|||
|
|
style: { width: '100%' },
|
|||
|
|
bordered: false,
|
|||
|
|
controls: false,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
/** 明细行 PHR 合计(作为重量%计算的分母) */
|
|||
|
|
export function calcTotalPhrFromLines(lines: Recordable[]): number | null {
|
|||
|
|
const sum = (lines || []).reduce((acc, row) => {
|
|||
|
|
const value = Number(row?.phr);
|
|||
|
|
return acc + (Number.isFinite(value) ? value : 0);
|
|||
|
|
}, 0);
|
|||
|
|
return sum > 0 ? Number(sum.toFixed(4)) : null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** 按 单条PHR / 总PHR × 100 回填各行重量% */
|
|||
|
|
export function applyWeightPercentToLines(lines: Recordable[]): void {
|
|||
|
|
const totalPhr = calcTotalPhrFromLines(lines);
|
|||
|
|
(lines || []).forEach((row) => {
|
|||
|
|
if (!row) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
const phr = Number(row.phr);
|
|||
|
|
if (!Number.isFinite(phr) || totalPhr == null || totalPhr <= 0) {
|
|||
|
|
row.weightPercent = null;
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
row.weightPercent = Number(((phr / totalPhr) * 100).toFixed(1));
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** STEP=A 的 PHR 合计(A胶 TOTAL PHR) */
|
|||
|
|
export function calcARubberTotalPhrFromLines(lines: Recordable[]): number | null {
|
|||
|
|
const sum = (lines || []).reduce((acc, row) => {
|
|||
|
|
if (row?.step !== 'A') {
|
|||
|
|
return acc;
|
|||
|
|
}
|
|||
|
|
const value = Number(row?.phr);
|
|||
|
|
return acc + (Number.isFinite(value) ? value : 0);
|
|||
|
|
}, 0);
|
|||
|
|
return sum > 0 ? Number(sum.toFixed(4)) : null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 混合段累计合计(仅统计可编辑段数):
|
|||
|
|
* stage1Total=列1总和,stage2Total=列1+列2,…;超出混合段数的框置空。
|
|||
|
|
*/
|
|||
|
|
export function calcStageTotalsFromLines(lines: Recordable[], mixingStages?: number | string | null): Recordable {
|
|||
|
|
const totals: Recordable = {};
|
|||
|
|
const stageCount = getActiveStageCount(mixingStages);
|
|||
|
|
|
|||
|
|
for (let i = 1; i <= 7; i++) {
|
|||
|
|
totals[`stage${i}Total`] = null;
|
|||
|
|
}
|
|||
|
|
if (stageCount <= 0) {
|
|||
|
|
return totals;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const colSums: number[] = [];
|
|||
|
|
for (let i = 1; i <= stageCount; i++) {
|
|||
|
|
const key = `stage${i}`;
|
|||
|
|
const sum = (lines || []).reduce((acc, row) => {
|
|||
|
|
const value = Number(row?.[key]);
|
|||
|
|
return acc + (Number.isFinite(value) ? value : 0);
|
|||
|
|
}, 0);
|
|||
|
|
colSums.push(sum);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let cumulative = 0;
|
|||
|
|
for (let i = 1; i <= stageCount; i++) {
|
|||
|
|
cumulative += colSums[i - 1];
|
|||
|
|
totals[`stage${i}Total`] = cumulative > 0 ? Number(cumulative.toFixed(4)) : null;
|
|||
|
|
}
|
|||
|
|
return totals;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** 底部汇总区:A胶 TOTAL PHR、TOTAL PHR、混合段 1..N 累计合计(N=混合段数) */
|
|||
|
|
export function calcFooterSummaryFromLines(lines: Recordable[], mixingStages?: number | string | null): Recordable {
|
|||
|
|
return {
|
|||
|
|
aRubberTotalPhr: calcARubberTotalPhrFromLines(lines),
|
|||
|
|
totalPhr: calcTotalPhrFromLines(lines),
|
|||
|
|
...calcStageTotalsFromLines(lines, mixingStages),
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** 明细行有效体积(优先使用已计算的 volume) */
|
|||
|
|
function getLineVolumeValue(row: Recordable): number | null {
|
|||
|
|
const volume = Number(row?.volume);
|
|||
|
|
return Number.isFinite(volume) && volume > 0 ? volume : null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* A胶比重 = STEP=A 各行 PHR 合计 ÷ STEP=A 各行体积合计
|
|||
|
|
* 单行体积 = PHR ÷ 物料比重
|
|||
|
|
*/
|
|||
|
|
export function calcARubberSgFromLines(lines: Recordable[]): number | null {
|
|||
|
|
let totalPhr = 0;
|
|||
|
|
let totalVolume = 0;
|
|||
|
|
(lines || []).forEach((row) => {
|
|||
|
|
if (row?.step !== 'A') {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
const phr = Number(row?.phr);
|
|||
|
|
const volume = getLineVolumeValue(row);
|
|||
|
|
if (!Number.isFinite(phr) || phr <= 0 || volume == null) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
totalPhr += phr;
|
|||
|
|
totalVolume += volume;
|
|||
|
|
});
|
|||
|
|
if (totalPhr <= 0 || totalVolume <= 0) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
return Number((totalPhr / totalVolume).toFixed(6));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Q胶比重 = STEP=Q 各行 PHR 合计 ÷ STEP=Q 各行体积合计
|
|||
|
|
* 单行体积 = PHR ÷ 物料比重
|
|||
|
|
*/
|
|||
|
|
export function calcQRubberSgFromLines(lines: Recordable[]): number | null {
|
|||
|
|
let totalPhr = 0;
|
|||
|
|
let totalVolume = 0;
|
|||
|
|
(lines || []).forEach((row) => {
|
|||
|
|
if (row?.step !== 'Q') {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
const phr = Number(row?.phr);
|
|||
|
|
const volume = getLineVolumeValue(row);
|
|||
|
|
if (!Number.isFinite(phr) || phr <= 0 || volume == null) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
totalPhr += phr;
|
|||
|
|
totalVolume += volume;
|
|||
|
|
});
|
|||
|
|
if (totalPhr <= 0 || totalVolume <= 0) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
return Number((totalPhr / totalVolume).toFixed(6));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** 默认物料小类编码(仅作初始配置参考) */
|
|||
|
|
export const MIXER_MINOR_CATEGORY_CODE = {
|
|||
|
|
NATURAL_RUBBER: 'XSLMES_MATERIAL_RAW_AUX_TRJ',
|
|||
|
|
SYNTHETIC_RUBBER: 'XSLMES_MATERIAL_RAW_AUX_HCJ',
|
|||
|
|
} as const;
|
|||
|
|
|
|||
|
|
export type RubberContentSetting = {
|
|||
|
|
naturalMinorCategoryIds: string[];
|
|||
|
|
syntheticMinorCategoryIds: string[];
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
export const EMPTY_RUBBER_CONTENT_SETTING: RubberContentSetting = {
|
|||
|
|
naturalMinorCategoryIds: [],
|
|||
|
|
syntheticMinorCategoryIds: [],
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
/** 将 MES物料分类 树递归展开为下拉选项(含大类、小类等全部节点) */
|
|||
|
|
function flattenMaterialCategoryTreeToOptions(
|
|||
|
|
nodes: Recordable[],
|
|||
|
|
parentLabel = '',
|
|||
|
|
): Array<{ label: string; value: string }> {
|
|||
|
|
const options: Array<{ label: string; value: string }> = [];
|
|||
|
|
(nodes || []).forEach((node) => {
|
|||
|
|
const id = String(node?.key ?? node?.id ?? node?.value ?? '');
|
|||
|
|
const name = String(node?.title ?? node?.name ?? node?.text ?? '');
|
|||
|
|
if (!id || !name) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
const label = parentLabel ? `${parentLabel} / ${name}` : name;
|
|||
|
|
options.push({ label, value: id });
|
|||
|
|
const children = (node?.children as Recordable[]) || [];
|
|||
|
|
if (children.length) {
|
|||
|
|
options.push(...flattenMaterialCategoryTreeToOptions(children, label));
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
return options;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** 一次性加载 MES物料分类 全部节点,供含胶率设置手动多选 */
|
|||
|
|
export async function fetchMaterialCategoryOptions(): Promise<Array<{ label: string; value: string }>> {
|
|||
|
|
try {
|
|||
|
|
const list = await loadCategoryData({ code: MATERIAL_CATEGORY_ROOT_CODE });
|
|||
|
|
const rows = Array.isArray(list) ? list : [];
|
|||
|
|
if (rows.length) {
|
|||
|
|
return rows
|
|||
|
|
.map((item: Recordable) => ({
|
|||
|
|
label: String(item?.text ?? item?.label ?? item?.title ?? item?.name ?? ''),
|
|||
|
|
value: String(item?.value ?? item?.id ?? item?.key ?? ''),
|
|||
|
|
}))
|
|||
|
|
.filter((item) => item.value && item.label);
|
|||
|
|
}
|
|||
|
|
} catch {
|
|||
|
|
// 回退到与密炼物料页一致的分类树加载
|
|||
|
|
}
|
|||
|
|
const treeRaw = await loadCategoryTreeRoot({ async: false, pcode: MATERIAL_CATEGORY_ROOT_CODE });
|
|||
|
|
const tree = Array.isArray(treeRaw) ? treeRaw : [];
|
|||
|
|
return flattenMaterialCategoryTreeToOptions(tree);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** @deprecated 请使用 fetchMaterialCategoryOptions */
|
|||
|
|
export const fetchMixerMinorCategoryOptions = fetchMaterialCategoryOptions;
|
|||
|
|
|
|||
|
|
/** 汇总区比重指标:Q胶比重、A胶比重 */
|
|||
|
|
export function calcRubberSgMetricsFromLines(lines: Recordable[]): Recordable {
|
|||
|
|
return {
|
|||
|
|
qRubberSg: calcQRubberSgFromLines(lines),
|
|||
|
|
aRubberSg: calcARubberSgFromLines(lines),
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 含胶率:天然橡胶 / 合成橡胶 = 对应小类密炼物料的重量%之和,合计为两者之和
|
|||
|
|
*/
|
|||
|
|
export function calcRubberContentMetricsFromLines(
|
|||
|
|
lines: Recordable[],
|
|||
|
|
naturalMinorCategoryIds?: string[] | null,
|
|||
|
|
syntheticMinorCategoryIds?: string[] | null,
|
|||
|
|
): Recordable {
|
|||
|
|
const naturalIdSet = new Set((naturalMinorCategoryIds || []).filter(Boolean));
|
|||
|
|
const syntheticIdSet = new Set((syntheticMinorCategoryIds || []).filter(Boolean));
|
|||
|
|
let naturalRubber = 0;
|
|||
|
|
let syntheticRubber = 0;
|
|||
|
|
let hasNatural = false;
|
|||
|
|
let hasSynthetic = false;
|
|||
|
|
(lines || []).forEach((row) => {
|
|||
|
|
if (!row?.mixerMinorCategoryId) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
const weightPercent = Number(row?.weightPercent);
|
|||
|
|
if (!Number.isFinite(weightPercent)) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
if (naturalIdSet.has(row.mixerMinorCategoryId)) {
|
|||
|
|
naturalRubber += weightPercent;
|
|||
|
|
hasNatural = true;
|
|||
|
|
} else if (syntheticIdSet.has(row.mixerMinorCategoryId)) {
|
|||
|
|
syntheticRubber += weightPercent;
|
|||
|
|
hasSynthetic = true;
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
const naturalValue = hasNatural ? Number(naturalRubber.toFixed(4)) : null;
|
|||
|
|
const syntheticValue = hasSynthetic ? Number(syntheticRubber.toFixed(4)) : null;
|
|||
|
|
const totalAmount =
|
|||
|
|
naturalValue != null || syntheticValue != null
|
|||
|
|
? Number(((naturalValue ?? 0) + (syntheticValue ?? 0)).toFixed(4))
|
|||
|
|
: null;
|
|||
|
|
return {
|
|||
|
|
naturalRubber: naturalValue,
|
|||
|
|
syntheticRubber: syntheticValue,
|
|||
|
|
totalAmount,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** 明细行仅前端展示字段(不落库) */
|
|||
|
|
export const FORMULA_LINE_DISPLAY_FIELDS = ['mixerMajorCategoryText', 'mixerMinorCategoryText', 'mixerMinorCategoryId'] as const;
|
|||
|
|
|
|||
|
|
/** 写入密炼物料关联的大类/小类展示文本 */
|
|||
|
|
export function applyMixerCategoryDisplay(row: Recordable, material?: Recordable | null) {
|
|||
|
|
if (!row) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
if (!material) {
|
|||
|
|
row.mixerMajorCategoryText = '';
|
|||
|
|
row.mixerMinorCategoryText = '';
|
|||
|
|
row.mixerMinorCategoryId = null;
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
row.mixerMajorCategoryText = material.majorCategoryId_dictText || material.majorCategoryText || '';
|
|||
|
|
row.mixerMinorCategoryText = material.minorCategoryId_dictText || material.minorCategoryText || '';
|
|||
|
|
row.mixerMinorCategoryId = material.minorCategoryId || null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let materialCategoryNameCache: Map<string, string> | null = null;
|
|||
|
|
|
|||
|
|
async function getMaterialCategoryNameCache(): Promise<Map<string, string>> {
|
|||
|
|
if (materialCategoryNameCache) {
|
|||
|
|
return materialCategoryNameCache;
|
|||
|
|
}
|
|||
|
|
try {
|
|||
|
|
const list = await loadCategoryData({ code: MATERIAL_CATEGORY_ROOT_CODE });
|
|||
|
|
const rows = Array.isArray(list) ? list : [];
|
|||
|
|
materialCategoryNameCache = new Map(
|
|||
|
|
rows
|
|||
|
|
.map((item: Recordable) => [String(item?.value ?? item?.id ?? ''), String(item?.text ?? item?.label ?? '')] as const)
|
|||
|
|
.filter(([id, name]) => id && name),
|
|||
|
|
);
|
|||
|
|
} catch {
|
|||
|
|
materialCategoryNameCache = new Map();
|
|||
|
|
}
|
|||
|
|
return materialCategoryNameCache;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** 关联查询并补全大类/小类展示(queryById 无 dictText 时回退分类字典) */
|
|||
|
|
export async function hydrateMixerCategoryDisplay(row: Recordable, material?: Recordable | null) {
|
|||
|
|
applyMixerMaterialMeta(row, material);
|
|||
|
|
if (!row || !material) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
const needMajor = !row.mixerMajorCategoryText && material.majorCategoryId;
|
|||
|
|
const needMinor = !row.mixerMinorCategoryText && material.minorCategoryId;
|
|||
|
|
if (!needMajor && !needMinor) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
const cache = await getMaterialCategoryNameCache();
|
|||
|
|
if (needMajor) {
|
|||
|
|
row.mixerMajorCategoryText = cache.get(String(material.majorCategoryId)) || '';
|
|||
|
|
}
|
|||
|
|
if (needMinor) {
|
|||
|
|
row.mixerMinorCategoryText = cache.get(String(material.minorCategoryId)) || '';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** 选择配合剂后回填物料关联信息(含大类/小类展示) */
|
|||
|
|
export function applyMixerMaterialMeta(row: Recordable, material?: Recordable | null) {
|
|||
|
|
if (!row) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
if (!material) {
|
|||
|
|
row.mixerMaterialName = '';
|
|||
|
|
row.mixerMaterialCode = '';
|
|||
|
|
applyMixerCategoryDisplay(row, null);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
row.mixerMaterialName = material.materialName || row.mixerMaterialName || '';
|
|||
|
|
row.mixerMaterialCode = material.materialCode || row.mixerMaterialCode || '';
|
|||
|
|
applyMixerCategoryDisplay(row, material);
|
|||
|
|
if (!row.materialDesc) {
|
|||
|
|
row.materialDesc = material.materialDesc || material.materialName || '';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** 提交前剔除明细行展示字段 */
|
|||
|
|
export function stripFormulaLineDisplayFields(line: Recordable) {
|
|||
|
|
const { mixerMajorCategoryText, mixerMinorCategoryText, mixerMinorCategoryId, ...rest } = line;
|
|||
|
|
return rest;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** 明细表默认空行数(参照原系统约 20 行) */
|
|||
|
|
export const DEFAULT_LINE_ROW_COUNT = 20;
|
|||
|
|
|
|||
|
|
export function createEmptyLineRows(count = DEFAULT_LINE_ROW_COUNT): Recordable[] {
|
|||
|
|
return Array.from({ length: count }, () => ({ id: buildUUID() }));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** 确保每行有唯一 id(JVxeTable rowKey 依赖 id,否则单元格无法渲染) */
|
|||
|
|
export function normalizeLineRows(rows: Recordable[]): Recordable[] {
|
|||
|
|
return (rows || []).map((r) => ({ ...r, id: r?.id || buildUUID() }));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function sectionTitle(label: string, field: string): FormSchema {
|
|||
|
|
return {
|
|||
|
|
field,
|
|||
|
|
label,
|
|||
|
|
component: 'Divider',
|
|||
|
|
componentProps: { orientation: 'left', plain: false },
|
|||
|
|
colProps: { span: 24 },
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const hasWorkflowInfo = ({ values }) =>
|
|||
|
|
!!(values.proofreadBy || values.proofreadTime || values.auditBy || values.auditTime || values.approveBy || values.approveTime);
|
|||
|
|
|
|||
|
|
export const columns: BasicColumn[] = [
|
|||
|
|
{ title: '示方编号', align: 'center', dataIndex: 'specCode', width: 150, fixed: 'left' },
|
|||
|
|
{ title: '发行编号', align: 'center', dataIndex: 'issueNumber', width: 140 },
|
|||
|
|
{ title: '胶料代号', align: 'center', dataIndex: 'rubberCode', width: 140 },
|
|||
|
|
{ title: '分类', align: 'center', dataIndex: 'category_dictText', width: 80 },
|
|||
|
|
{ title: '用途', align: 'center', dataIndex: 'purpose', width: 120, ellipsis: true },
|
|||
|
|
{
|
|||
|
|
title: '人工配料',
|
|||
|
|
align: 'center',
|
|||
|
|
dataIndex: 'hasManualBatch',
|
|||
|
|
width: 90,
|
|||
|
|
customRender: ({ text }) => (text === 1 ? '是' : '否'),
|
|||
|
|
},
|
|||
|
|
{ title: '发行日期', align: 'center', dataIndex: 'issueDate', width: 110 },
|
|||
|
|
{
|
|||
|
|
title: '制定人',
|
|||
|
|
align: 'center',
|
|||
|
|
dataIndex: 'createBy',
|
|||
|
|
width: 100,
|
|||
|
|
customRender: ({ record }) => record?.createBy_dictText || record?.createBy || '',
|
|||
|
|
},
|
|||
|
|
{ title: '审核人', align: 'center', dataIndex: 'auditBy', width: 100, defaultHidden: true },
|
|||
|
|
{ title: '批准人', align: 'center', dataIndex: 'approveBy', width: 100, defaultHidden: true },
|
|||
|
|
{ title: '状态', align: 'center', dataIndex: 'status_dictText', width: 120 },
|
|||
|
|
{ title: '混合段数', align: 'center', dataIndex: 'mixingStages', width: 90, defaultHidden: true },
|
|||
|
|
{ title: 'TOTAL PHR', align: 'center', dataIndex: 'totalPhr', width: 100, defaultHidden: true },
|
|||
|
|
{ title: '修改时间', align: 'center', dataIndex: 'updateTime', width: 165 },
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
export const searchFormSchema: FormSchema[] = [
|
|||
|
|
{ label: '关键字', field: 'keyword', component: 'Input', colProps: { span: 6 }, componentProps: { placeholder: '示方编号/胶料代号/发行编号/用途' } },
|
|||
|
|
{
|
|||
|
|
label: '分类',
|
|||
|
|
field: 'category',
|
|||
|
|
component: 'JDictSelectTag',
|
|||
|
|
componentProps: { dictCode: 'xslmes_formula_spec_category', placeholder: '请选择分类' },
|
|||
|
|
colProps: { span: 6 },
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: '状态',
|
|||
|
|
field: 'status',
|
|||
|
|
component: 'JDictSelectTag',
|
|||
|
|
componentProps: { dictCode: 'xslmes_formula_spec_status', placeholder: '请选择状态' },
|
|||
|
|
colProps: { span: 6 },
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: '发行日期起',
|
|||
|
|
field: 'issueDate_begin',
|
|||
|
|
component: 'DatePicker',
|
|||
|
|
componentProps: { valueFormat: 'YYYY-MM-DD', placeholder: '开始日期' },
|
|||
|
|
colProps: { span: 6 },
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: '发行日期止',
|
|||
|
|
field: 'issueDate_end',
|
|||
|
|
component: 'DatePicker',
|
|||
|
|
componentProps: { valueFormat: 'YYYY-MM-DD', placeholder: '结束日期' },
|
|||
|
|
colProps: { span: 6 },
|
|||
|
|
},
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
/** 基本信息(顶部) */
|
|||
|
|
export const basicFormSchema: FormSchema[] = [
|
|||
|
|
{ label: '', field: 'id', component: 'Input', show: false },
|
|||
|
|
{
|
|||
|
|
label: '分类',
|
|||
|
|
field: 'category',
|
|||
|
|
component: 'JDictSelectTag',
|
|||
|
|
defaultValue: 'S',
|
|||
|
|
show: false,
|
|||
|
|
componentProps: { dictCode: 'xslmes_formula_spec_category', type: 'radioButton' },
|
|||
|
|
colProps: { span: 24 },
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: '示方编号',
|
|||
|
|
field: 'specCode',
|
|||
|
|
component: 'Input',
|
|||
|
|
show: false,
|
|||
|
|
dynamicRules: () => [{ required: true, message: '请选择胶料并生成示方编号' }],
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: '胶料代号',
|
|||
|
|
field: 'rubberCode',
|
|||
|
|
component: 'Input',
|
|||
|
|
show: false,
|
|||
|
|
dynamicRules: () => [{ required: true, message: '请选择胶料并生成胶料代号' }],
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: '',
|
|||
|
|
field: 'rubberMaterialId',
|
|||
|
|
component: 'Input',
|
|||
|
|
show: false,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: '基本配合',
|
|||
|
|
field: 'basicFormula',
|
|||
|
|
component: 'Input',
|
|||
|
|
show: false,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: '发行日期',
|
|||
|
|
field: 'issueDate',
|
|||
|
|
component: 'Input',
|
|||
|
|
show: false,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: '发行编号',
|
|||
|
|
field: 'issueNumber',
|
|||
|
|
component: 'Input',
|
|||
|
|
show: false,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: '用途',
|
|||
|
|
field: 'purpose',
|
|||
|
|
component: 'Input',
|
|||
|
|
show: false,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: '混合段数',
|
|||
|
|
field: 'mixingStages',
|
|||
|
|
component: 'InputNumber',
|
|||
|
|
show: false,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: '混合机器',
|
|||
|
|
field: 'mixingMachine',
|
|||
|
|
component: 'Input',
|
|||
|
|
show: false,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: '发行部门',
|
|||
|
|
field: 'issueDeptName',
|
|||
|
|
component: 'Input',
|
|||
|
|
show: false,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: '',
|
|||
|
|
field: 'issueDeptId',
|
|||
|
|
component: 'Input',
|
|||
|
|
show: false,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: '状态',
|
|||
|
|
field: 'status',
|
|||
|
|
component: 'JDictSelectTag',
|
|||
|
|
defaultValue: 'compile',
|
|||
|
|
show: false,
|
|||
|
|
componentProps: { dictCode: 'xslmes_formula_spec_status', disabled: true },
|
|||
|
|
colProps: colThird,
|
|||
|
|
},
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
/** 汇总信息(底部,参照原配合施工表布局) */
|
|||
|
|
export const summaryFormSchema: FormSchema[] = [
|
|||
|
|
...summaryFooterHiddenFields,
|
|||
|
|
...summaryMetricsHiddenFields,
|
|||
|
|
{
|
|||
|
|
label: '发行理由',
|
|||
|
|
field: 'issueReason',
|
|||
|
|
component: 'InputTextArea',
|
|||
|
|
colProps: { span: 24 },
|
|||
|
|
componentProps: { rows: 2, placeholder: '请输入发行理由', maxlength: 1000, showCount: true, bordered: false },
|
|||
|
|
},
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
/** 审批记录(详情时展示) */
|
|||
|
|
export const workflowFormSchema: FormSchema[] = [
|
|||
|
|
sectionTitle('审批记录', 'dividerWorkflow'),
|
|||
|
|
{
|
|||
|
|
label: '校对人',
|
|||
|
|
field: 'proofreadBy',
|
|||
|
|
component: 'Input',
|
|||
|
|
componentProps: { disabled: true, bordered: false },
|
|||
|
|
colProps: colHalf,
|
|||
|
|
ifShow: ({ values }) => !!values.proofreadBy,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: '校对时间',
|
|||
|
|
field: 'proofreadTime',
|
|||
|
|
component: 'Input',
|
|||
|
|
componentProps: { disabled: true, bordered: false },
|
|||
|
|
colProps: colHalf,
|
|||
|
|
ifShow: ({ values }) => !!values.proofreadTime,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: '审核人',
|
|||
|
|
field: 'auditBy',
|
|||
|
|
component: 'Input',
|
|||
|
|
componentProps: { disabled: true, bordered: false },
|
|||
|
|
colProps: colHalf,
|
|||
|
|
ifShow: ({ values }) => !!values.auditBy,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: '审核时间',
|
|||
|
|
field: 'auditTime',
|
|||
|
|
component: 'Input',
|
|||
|
|
componentProps: { disabled: true, bordered: false },
|
|||
|
|
colProps: colHalf,
|
|||
|
|
ifShow: ({ values }) => !!values.auditTime,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: '批准人',
|
|||
|
|
field: 'approveBy',
|
|||
|
|
component: 'Input',
|
|||
|
|
componentProps: { disabled: true, bordered: false },
|
|||
|
|
colProps: colHalf,
|
|||
|
|
ifShow: ({ values }) => !!values.approveBy,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: '批准时间',
|
|||
|
|
field: 'approveTime',
|
|||
|
|
component: 'Input',
|
|||
|
|
componentProps: { disabled: true, bordered: false },
|
|||
|
|
colProps: colHalf,
|
|||
|
|
ifShow: ({ values }) => !!values.approveTime,
|
|||
|
|
},
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
/** 有效混合段数(1-7),未配置时返回 0 */
|
|||
|
|
export function getActiveStageCount(mixingStages?: number | string | null): number {
|
|||
|
|
if (mixingStages == null || mixingStages === '') {
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
const n = Number(mixingStages);
|
|||
|
|
if (Number.isNaN(n) || n <= 0) {
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
return Math.min(Math.max(Math.floor(n), 1), 7);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* STEP=A:PHR 写入第 1 混合段;STEP=Q:PHR 写入当前混合段数的最后一列。
|
|||
|
|
* 返回需写回表格的 stage1-stage7 补丁;非 A/Q 或无可编辑段时返回 null。
|
|||
|
|
*/
|
|||
|
|
export function buildStepStageValues(row: Recordable, mixingStages?: number | null): Recordable | null {
|
|||
|
|
const step = row?.step;
|
|||
|
|
if (step !== 'A' && step !== 'Q') {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
const stageCount = getActiveStageCount(mixingStages);
|
|||
|
|
if (stageCount <= 0) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
const phrRaw = row?.phr;
|
|||
|
|
const phr =
|
|||
|
|
phrRaw != null && phrRaw !== '' && Number.isFinite(Number(phrRaw)) ? Number(phrRaw) : null;
|
|||
|
|
const targetStage = step === 'A' ? 1 : stageCount;
|
|||
|
|
const patch: Recordable = {};
|
|||
|
|
for (let i = 1; i <= 7; i++) {
|
|||
|
|
patch[`stage${i}`] = i === targetStage ? phr : null;
|
|||
|
|
}
|
|||
|
|
return patch;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** 将 STEP 规则应用到行对象(就地修改) */
|
|||
|
|
export function applyStepPhrToLineRow(row: Recordable, mixingStages?: number | null): Recordable | null {
|
|||
|
|
const patch = buildStepStageValues(row, mixingStages);
|
|||
|
|
if (!patch) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
Object.assign(row, patch);
|
|||
|
|
return patch;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** 解析行 PHR(无效时返回 null) */
|
|||
|
|
export function getLinePhrValue(row: Recordable): number | null {
|
|||
|
|
const phr = Number(row?.phr);
|
|||
|
|
return Number.isFinite(phr) && phr > 0 ? phr : null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** 可编辑混合段列合计(仅统计 1..stageCount) */
|
|||
|
|
export function calcLineActiveStageSum(row: Recordable, stageCount: number): number {
|
|||
|
|
let sum = 0;
|
|||
|
|
for (let i = 1; i <= stageCount; i++) {
|
|||
|
|
const v = Number(row[`stage${i}`]);
|
|||
|
|
if (Number.isFinite(v)) {
|
|||
|
|
sum += v;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return sum;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** 混合段合计超过 PHR 时,扣减当前编辑列 */
|
|||
|
|
export function clampStageToPhr(row: Recordable, stageCount: number, editedKey: string): number | null {
|
|||
|
|
const phr = getLinePhrValue(row);
|
|||
|
|
if (phr == null || stageCount <= 0) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
const total = calcLineActiveStageSum(row, stageCount);
|
|||
|
|
if (total <= phr) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
const current = Number(row[editedKey]);
|
|||
|
|
if (!Number.isFinite(current)) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
const clamped = Math.max(0, Number((current - (total - phr)).toFixed(4)));
|
|||
|
|
row[editedKey] = clamped;
|
|||
|
|
return clamped;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** STEP=A:手动编辑 2..N 列时,第 1 列 = PHR − 其余可编辑段之和 */
|
|||
|
|
export function balanceStepAStages(row: Recordable, stageCount: number, editedKey?: string): Recordable {
|
|||
|
|
const patch: Recordable = {};
|
|||
|
|
if (editedKey === 'stage1') {
|
|||
|
|
return patch;
|
|||
|
|
}
|
|||
|
|
const phr = getLinePhrValue(row);
|
|||
|
|
if (phr == null || stageCount <= 0) {
|
|||
|
|
return patch;
|
|||
|
|
}
|
|||
|
|
let otherSum = 0;
|
|||
|
|
for (let i = 2; i <= stageCount; i++) {
|
|||
|
|
const v = Number(row[`stage${i}`]);
|
|||
|
|
if (Number.isFinite(v)) {
|
|||
|
|
otherSum += v;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
const stage1 = Math.max(0, Number((phr - otherSum).toFixed(4)));
|
|||
|
|
const next = otherSum > 0 ? stage1 : phr;
|
|||
|
|
if (Number(row.stage1) !== next) {
|
|||
|
|
row.stage1 = next;
|
|||
|
|
patch.stage1 = next;
|
|||
|
|
}
|
|||
|
|
return patch;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** STEP=Q:手动编辑非末列时,末列 = PHR − 其余可编辑段之和 */
|
|||
|
|
export function balanceStepQStages(row: Recordable, stageCount: number, editedKey?: string): Recordable {
|
|||
|
|
const patch: Recordable = {};
|
|||
|
|
const phr = getLinePhrValue(row);
|
|||
|
|
if (phr == null || stageCount <= 0) {
|
|||
|
|
return patch;
|
|||
|
|
}
|
|||
|
|
const lastKey = `stage${stageCount}`;
|
|||
|
|
if (editedKey === lastKey) {
|
|||
|
|
return patch;
|
|||
|
|
}
|
|||
|
|
let otherSum = 0;
|
|||
|
|
for (let i = 1; i < stageCount; i++) {
|
|||
|
|
const v = Number(row[`stage${i}`]);
|
|||
|
|
if (Number.isFinite(v)) {
|
|||
|
|
otherSum += v;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
const lastVal = Math.max(0, Number((phr - otherSum).toFixed(4)));
|
|||
|
|
const next = otherSum > 0 ? lastVal : phr;
|
|||
|
|
if (Number(row[lastKey]) !== next) {
|
|||
|
|
row[lastKey] = next;
|
|||
|
|
patch[lastKey] = next;
|
|||
|
|
}
|
|||
|
|
return patch;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 混合段单元格编辑后:合计不超过 PHR;STEP=A/Q 自动平衡锚定列。
|
|||
|
|
* STEP=A 编辑 2..N 列、STEP=Q 编辑非末列时:先扣减锚定列,再判断是否需截断当前列。
|
|||
|
|
*/
|
|||
|
|
export function processStageCellEdit(
|
|||
|
|
row: Recordable,
|
|||
|
|
editedKey: string,
|
|||
|
|
mixingStages?: number | string | null
|
|||
|
|
): { patch: Recordable; exceeded: boolean; needPhr: boolean } {
|
|||
|
|
const patch: Recordable = {};
|
|||
|
|
const stageCount = getActiveStageCount(mixingStages);
|
|||
|
|
if (stageCount <= 0 || !/^stage\d+$/.test(editedKey)) {
|
|||
|
|
return { patch, exceeded: false, needPhr: false };
|
|||
|
|
}
|
|||
|
|
const phr = getLinePhrValue(row);
|
|||
|
|
if (phr == null) {
|
|||
|
|
return { patch, exceeded: false, needPhr: true };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const lastKey = `stage${stageCount}`;
|
|||
|
|
let exceeded = false;
|
|||
|
|
|
|||
|
|
const mergePatch = (p: Recordable) => {
|
|||
|
|
Object.keys(p).forEach((k) => {
|
|||
|
|
patch[k] = p[k];
|
|||
|
|
});
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// STEP=A:编辑第 2..N 列时先从第 1 列扣减,避免误把当前列截断为 0
|
|||
|
|
if (row.step === 'A' && editedKey !== 'stage1') {
|
|||
|
|
mergePatch(balanceStepAStages(row, stageCount, editedKey));
|
|||
|
|
const clamped = clampStageToPhr(row, stageCount, editedKey);
|
|||
|
|
if (clamped != null) {
|
|||
|
|
patch[editedKey] = clamped;
|
|||
|
|
exceeded = true;
|
|||
|
|
mergePatch(balanceStepAStages(row, stageCount, editedKey));
|
|||
|
|
}
|
|||
|
|
return { patch, exceeded, needPhr: false };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// STEP=Q:编辑非末列时先从末列扣减
|
|||
|
|
if (row.step === 'Q' && editedKey !== lastKey) {
|
|||
|
|
mergePatch(balanceStepQStages(row, stageCount, editedKey));
|
|||
|
|
const clamped = clampStageToPhr(row, stageCount, editedKey);
|
|||
|
|
if (clamped != null) {
|
|||
|
|
patch[editedKey] = clamped;
|
|||
|
|
exceeded = true;
|
|||
|
|
mergePatch(balanceStepQStages(row, stageCount, editedKey));
|
|||
|
|
}
|
|||
|
|
return { patch, exceeded, needPhr: false };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 编辑锚定列或无 STEP:仅截断当前列
|
|||
|
|
const clamped = clampStageToPhr(row, stageCount, editedKey);
|
|||
|
|
if (clamped != null) {
|
|||
|
|
patch[editedKey] = clamped;
|
|||
|
|
exceeded = true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return { patch, exceeded, needPhr: false };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** 根据混合段数动态生成明细列(1-7 段列按混合段数控制可编辑;未填混合段数时全部禁用) */
|
|||
|
|
export function buildLineJVxeColumns(mixingStages?: number | null, tableDisabled = false): JVxeColumn[] {
|
|||
|
|
const stageCount = getActiveStageCount(mixingStages);
|
|||
|
|
const hasStages = stageCount > 0;
|
|||
|
|
|
|||
|
|
const baseCols: JVxeColumn[] = [
|
|||
|
|
{
|
|||
|
|
title: 'PHR',
|
|||
|
|
key: 'phr',
|
|||
|
|
type: JVxeTypes.inputNumber,
|
|||
|
|
minWidth: 90,
|
|||
|
|
align: 'center',
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
title: '配合剂',
|
|||
|
|
key: 'mixerMaterialId',
|
|||
|
|
type: JVxeTypes.slot,
|
|||
|
|
slotName: 'mixerMaterialSlot',
|
|||
|
|
minWidth: 200,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
title: '物料大类',
|
|||
|
|
key: 'mixerMajorCategoryText',
|
|||
|
|
type: JVxeTypes.normal,
|
|||
|
|
minWidth: 110,
|
|||
|
|
align: 'center',
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
title: '物料小类',
|
|||
|
|
key: 'mixerMinorCategoryText',
|
|||
|
|
type: JVxeTypes.normal,
|
|||
|
|
minWidth: 110,
|
|||
|
|
align: 'center',
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
title: '物料描述',
|
|||
|
|
key: 'materialDesc',
|
|||
|
|
type: JVxeTypes.input,
|
|||
|
|
minWidth: 160,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
title: 'STEP',
|
|||
|
|
key: 'step',
|
|||
|
|
type: JVxeTypes.select,
|
|||
|
|
minWidth: 80,
|
|||
|
|
align: 'center',
|
|||
|
|
dictCode: 'xslmes_formula_spec_step',
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
title: '自动/人工',
|
|||
|
|
key: 'weighMode',
|
|||
|
|
type: JVxeTypes.select,
|
|||
|
|
minWidth: 110,
|
|||
|
|
align: 'center',
|
|||
|
|
dictCode: 'xslmes_formula_spec_weigh_mode',
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
title: '重量%',
|
|||
|
|
key: 'weightPercent',
|
|||
|
|
type: JVxeTypes.inputNumber,
|
|||
|
|
minWidth: 90,
|
|||
|
|
align: 'center',
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
title: '体积',
|
|||
|
|
key: 'volume',
|
|||
|
|
type: JVxeTypes.inputNumber,
|
|||
|
|
minWidth: 90,
|
|||
|
|
align: 'center',
|
|||
|
|
placeholder: '自动计算',
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
title: '备注',
|
|||
|
|
key: 'remark',
|
|||
|
|
type: JVxeTypes.input,
|
|||
|
|
minWidth: 80,
|
|||
|
|
},
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
for (let i = 1; i <= 7; i++) {
|
|||
|
|
const stageNo = i;
|
|||
|
|
const stageDisabled = tableDisabled || !hasStages || stageNo > stageCount;
|
|||
|
|
baseCols.push({
|
|||
|
|
title: String(i),
|
|||
|
|
key: `stage${i}`,
|
|||
|
|
type: JVxeTypes.inputNumber,
|
|||
|
|
minWidth: 56,
|
|||
|
|
align: 'center',
|
|||
|
|
disabled: stageDisabled,
|
|||
|
|
className: stageDisabled ? 'formula-stage-cell-disabled' : '',
|
|||
|
|
headerClassName: stageDisabled ? 'formula-stage-header-disabled' : '',
|
|||
|
|
props: {
|
|||
|
|
isDisabledCell: () => stageDisabled,
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
return baseCols;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** 明细列设置项(1-7 段列固定展示在设置列表中) */
|
|||
|
|
export function getFormulaLineColumnSettingItems(): FormulaLineColumnSettingItem[] {
|
|||
|
|
return buildLineJVxeColumns(null, false).map((col) => ({
|
|||
|
|
key: String(col.key),
|
|||
|
|
title: String(col.title),
|
|||
|
|
locked: FORMULA_LINE_LOCKED_COLUMN_KEYS.includes(String(col.key)),
|
|||
|
|
}));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** 读取已隐藏的明细列 key */
|
|||
|
|
export function loadFormulaLineHiddenColumnKeys(): string[] {
|
|||
|
|
const saved = formulaLineColumnStorage.get(FORMULA_LINE_COLUMN_CACHE_KEY);
|
|||
|
|
if (!Array.isArray(saved)) {
|
|||
|
|
return [];
|
|||
|
|
}
|
|||
|
|
const validKeys = new Set(getFormulaLineColumnSettingItems().map((item) => item.key));
|
|||
|
|
return saved.filter(
|
|||
|
|
(key) => typeof key === 'string' && !FORMULA_LINE_LOCKED_COLUMN_KEYS.includes(key) && validKeys.has(key),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** 保存已隐藏的明细列 key */
|
|||
|
|
export function saveFormulaLineHiddenColumnKeys(hiddenKeys: string[]) {
|
|||
|
|
const validKeys = hiddenKeys.filter((key) => !FORMULA_LINE_LOCKED_COLUMN_KEYS.includes(key));
|
|||
|
|
if (validKeys.length) {
|
|||
|
|
formulaLineColumnStorage.set(FORMULA_LINE_COLUMN_CACHE_KEY, validKeys);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
formulaLineColumnStorage.remove(FORMULA_LINE_COLUMN_CACHE_KEY);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** 按隐藏配置过滤明细列 */
|
|||
|
|
export function applyFormulaLineColumnVisibility(columns: JVxeColumn[], hiddenKeys: string[]): JVxeColumn[] {
|
|||
|
|
if (!hiddenKeys?.length) {
|
|||
|
|
return columns;
|
|||
|
|
}
|
|||
|
|
const hiddenSet = new Set(hiddenKeys);
|
|||
|
|
return columns.filter((col) => !col.key || !hiddenSet.has(String(col.key)));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** 审批进度操作人展示:优先字典翻译姓名,其次当前登录用户匹配用户名时取 realname */
|
|||
|
|
export function resolveFormulaSpecUserDisplayName(
|
|||
|
|
username?: string | null,
|
|||
|
|
dictText?: string | null,
|
|||
|
|
fallbackUser?: { username?: string; realname?: string },
|
|||
|
|
) {
|
|||
|
|
if (dictText != null && dictText !== '') {
|
|||
|
|
return String(dictText);
|
|||
|
|
}
|
|||
|
|
const user = fallbackUser || {};
|
|||
|
|
if (username != null && username !== '') {
|
|||
|
|
const name = String(username);
|
|||
|
|
if (user.username && name === user.username && user.realname) {
|
|||
|
|
return user.realname;
|
|||
|
|
}
|
|||
|
|
return name;
|
|||
|
|
}
|
|||
|
|
return user.realname || user.username || '';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export const superQuerySchema = {
|
|||
|
|
specCode: { title: '示方编号', order: 0, view: 'text' },
|
|||
|
|
rubberCode: { title: '胶料代号', order: 1, view: 'text' },
|
|||
|
|
issueNumber: { title: '发行编号', order: 2, view: 'text' },
|
|||
|
|
category: { title: '分类', order: 3, view: 'list', dictCode: 'xslmes_formula_spec_category' },
|
|||
|
|
purpose: { title: '用途', order: 4, view: 'text' },
|
|||
|
|
status: { title: '状态', order: 5, view: 'list', dictCode: 'xslmes_formula_spec_status' },
|
|||
|
|
issueDate: { title: '发行日期', order: 6, view: 'date' },
|
|||
|
|
};
|