优化修复
This commit is contained in:
150
src/utils.ts
150
src/utils.ts
@@ -45,6 +45,70 @@ export function centerElementOnLabel(
|
||||
};
|
||||
}
|
||||
|
||||
const AUTO_ELEMENT_NAME_RE = /^(文本|条形码|二维码|图片|表格) \d+$/;
|
||||
const MAX_LAYER_NAME_FROM_CONTENT = 40;
|
||||
|
||||
/** 是否为新建时的默认图层名(含粘贴副本后缀) */
|
||||
export function isAutoGeneratedElementName(name: string): boolean {
|
||||
const trimmed = name.trim();
|
||||
if (!trimmed || trimmed === '未命名元素') return true;
|
||||
if (AUTO_ELEMENT_NAME_RE.test(trimmed)) return true;
|
||||
if (trimmed.endsWith(' 副本')) {
|
||||
const base = trimmed.slice(0, -3).trim();
|
||||
if (AUTO_ELEMENT_NAME_RE.test(base) || base === '未命名元素') return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** 是否应根据内容自动同步图层名称 */
|
||||
export function shouldSyncElementNameFromContent(elem: TemplateElement): boolean {
|
||||
if (elem.nameCustomized) return false;
|
||||
return isAutoGeneratedElementName(elem.name);
|
||||
}
|
||||
|
||||
/** 从元素内容推导图层显示名称 */
|
||||
export function deriveElementNameFromContent(
|
||||
content: string,
|
||||
type: TemplateElement['type']
|
||||
): string {
|
||||
const trimmed = content.trim();
|
||||
if (!trimmed) return '未命名元素';
|
||||
|
||||
if (type === 'image') {
|
||||
if (trimmed.startsWith('data:')) return '图片';
|
||||
const withoutVars = trimmed.replace(/\{[^}]+\}/g, '').trim();
|
||||
if (withoutVars && /^https?:\/\//i.test(withoutVars)) {
|
||||
try {
|
||||
const path = withoutVars.split(/[?#]/)[0];
|
||||
const segment = path.split('/').filter(Boolean).pop();
|
||||
if (segment) {
|
||||
return decodeURIComponent(segment).slice(0, MAX_LAYER_NAME_FROM_CONTENT);
|
||||
}
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const singleLine = trimmed.split(/\r?\n/)[0].trim();
|
||||
if (!singleLine) return '未命名元素';
|
||||
if (singleLine.startsWith('data:')) return type === 'image' ? '图片' : '未命名元素';
|
||||
return singleLine.slice(0, MAX_LAYER_NAME_FROM_CONTENT);
|
||||
}
|
||||
|
||||
/** 更新内容并在需要时同步图层名称 */
|
||||
export function withContentAndSyncedName(
|
||||
elem: TemplateElement,
|
||||
content: string
|
||||
): TemplateElement {
|
||||
const updated = { ...elem, content };
|
||||
if (!shouldSyncElementNameFromContent(elem)) return updated;
|
||||
return {
|
||||
...updated,
|
||||
name: deriveElementNameFromContent(content, elem.type),
|
||||
};
|
||||
}
|
||||
|
||||
/** 深拷贝元素并生成新 id,用于复制/粘贴 */
|
||||
export function cloneTemplateElementsForPaste(
|
||||
elements: TemplateElement[],
|
||||
@@ -251,8 +315,8 @@ export function getExportRangeIndices(
|
||||
export const DEFAULT_CUT_LINE_CONFIG: CutLineConfig = {
|
||||
enabled: true,
|
||||
style: 'dashed',
|
||||
width: 0.1,
|
||||
color: '#cccccc',
|
||||
width: 0.2,
|
||||
color: '#333333',
|
||||
};
|
||||
|
||||
/** 裁切线有效线宽 (mm),预览与 PDF 共用 */
|
||||
@@ -469,8 +533,8 @@ export const DEFAULT_PAPER_CONFIG: PaperConfig = {
|
||||
marginBottom: AUTO_LAYOUT_MIN_PAGE_MARGIN_MM,
|
||||
marginLeft: AUTO_LAYOUT_MIN_PAGE_MARGIN_MM,
|
||||
marginRight: AUTO_LAYOUT_MIN_PAGE_MARGIN_MM,
|
||||
columnGap: 2,
|
||||
rowGap: 2,
|
||||
columnGap: 0,
|
||||
rowGap: 0,
|
||||
flow: 'left-right-top-bottom',
|
||||
};
|
||||
|
||||
@@ -571,6 +635,24 @@ export function resolveElementBackgroundFill(
|
||||
return color;
|
||||
}
|
||||
|
||||
/** 从显示/过滤条件中收集引用的变量名 */
|
||||
function collectVariablesFromVisibilityRule(rule: VisibilityRule, vars: Set<string>) {
|
||||
const name = (rule.variable ?? '').trim();
|
||||
if (name) vars.add(name);
|
||||
if (rule.value) {
|
||||
for (const v of extractVariablesFromContent(rule.value)) {
|
||||
vars.add(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function collectVariablesFromVisibilityRules(rules: VisibilityRule[] | undefined, vars: Set<string>) {
|
||||
if (!rules) return;
|
||||
for (const rule of rules) {
|
||||
collectVariablesFromVisibilityRule(rule, vars);
|
||||
}
|
||||
}
|
||||
|
||||
/** 从元素内容(含表格单元格)提取变量名 */
|
||||
export function extractVariablesFromElement(elem: TemplateElement): string[] {
|
||||
const vars = new Set<string>();
|
||||
@@ -593,32 +675,36 @@ export function extractVariablesFromElement(elem: TemplateElement): string[] {
|
||||
export function extractTemplateVariables(template: {
|
||||
variableList?: string[];
|
||||
elements: TemplateElement[];
|
||||
dataFilterRules?: VisibilityRule[];
|
||||
exportImageFilterRules?: VisibilityRule[];
|
||||
}): string[] {
|
||||
const vars = new Set<string>(template.variableList ?? []);
|
||||
for (const elem of template.elements) {
|
||||
for (const v of extractVariablesFromElement(elem)) {
|
||||
vars.add(v);
|
||||
}
|
||||
for (const rule of resolveVisibilityRules(elem)) {
|
||||
const name = (rule.variable ?? '').trim();
|
||||
if (name) vars.add(name);
|
||||
}
|
||||
collectVariablesFromVisibilityRules(resolveVisibilityRules(elem), vars);
|
||||
}
|
||||
collectVariablesFromVisibilityRules(template.dataFilterRules, vars);
|
||||
collectVariablesFromVisibilityRules(template.exportImageFilterRules, vars);
|
||||
return Array.from(vars).sort((a, b) => a.localeCompare(b, 'zh-CN'));
|
||||
}
|
||||
|
||||
/** 被元素内容或显示条件引用的变量 */
|
||||
export function getUsedTemplateVariables(template: { elements: TemplateElement[] }): string[] {
|
||||
export function getUsedTemplateVariables(template: {
|
||||
elements: TemplateElement[];
|
||||
dataFilterRules?: VisibilityRule[];
|
||||
exportImageFilterRules?: VisibilityRule[];
|
||||
}): string[] {
|
||||
const vars = new Set<string>();
|
||||
for (const elem of template.elements) {
|
||||
for (const v of extractVariablesFromElement(elem)) {
|
||||
vars.add(v);
|
||||
}
|
||||
for (const rule of resolveVisibilityRules(elem)) {
|
||||
const name = (rule.variable ?? '').trim();
|
||||
if (name) vars.add(name);
|
||||
}
|
||||
collectVariablesFromVisibilityRules(resolveVisibilityRules(elem), vars);
|
||||
}
|
||||
collectVariablesFromVisibilityRules(template.dataFilterRules, vars);
|
||||
collectVariablesFromVisibilityRules(template.exportImageFilterRules, vars);
|
||||
return Array.from(vars);
|
||||
}
|
||||
|
||||
@@ -781,6 +867,20 @@ function getVisibilityRuleRawValue(
|
||||
return getVariableRawValue(rule.variable, row, defaults, mode);
|
||||
}
|
||||
|
||||
/** 解析条件目标值(支持 {变量名}) */
|
||||
export function resolveVisibilityRuleCompareTarget(
|
||||
targetTemplate: string | undefined,
|
||||
row: RecordRow | null,
|
||||
defaults: Record<string, string> | null | undefined,
|
||||
mode: ContentResolveMode,
|
||||
rowIndex: number = 0
|
||||
): string {
|
||||
const trimmed = (targetTemplate ?? '').trim();
|
||||
if (!trimmed) return '';
|
||||
if (!trimmed.includes('{')) return trimmed;
|
||||
return resolveContent(trimmed, row, rowIndex, defaults, { mode }).trim();
|
||||
}
|
||||
|
||||
/** 单条显示条件是否满足 */
|
||||
export function evaluateVisibilityRule(
|
||||
rule: VisibilityRule,
|
||||
@@ -792,6 +892,13 @@ export function evaluateVisibilityRule(
|
||||
): boolean {
|
||||
const raw = getVisibilityRuleRawValue(rule, elem, row, rowIndex, defaults, mode);
|
||||
const isEmpty = raw === undefined;
|
||||
const compareTarget = resolveVisibilityRuleCompareTarget(
|
||||
rule.value,
|
||||
row,
|
||||
defaults,
|
||||
mode,
|
||||
rowIndex
|
||||
);
|
||||
|
||||
switch (rule.operator) {
|
||||
case 'empty':
|
||||
@@ -802,23 +909,20 @@ export function evaluateVisibilityRule(
|
||||
case 'lt':
|
||||
case 'eq': {
|
||||
if (isEmpty) return false;
|
||||
return compareVariableToTarget(raw!, (rule.value ?? '').trim(), rule.operator);
|
||||
return compareVariableToTarget(raw!, compareTarget, rule.operator);
|
||||
}
|
||||
case 'neq': {
|
||||
const target = (rule.value ?? '').trim();
|
||||
if (isEmpty) return target !== '';
|
||||
return compareVariableToTarget(raw!, target, 'neq');
|
||||
if (isEmpty) return compareTarget !== '';
|
||||
return compareVariableToTarget(raw!, compareTarget, 'neq');
|
||||
}
|
||||
case 'contains': {
|
||||
const target = (rule.value ?? '').trim();
|
||||
if (!target || isEmpty) return false;
|
||||
return raw!.includes(target);
|
||||
if (!compareTarget || isEmpty) return false;
|
||||
return raw!.includes(compareTarget);
|
||||
}
|
||||
case 'not_contains': {
|
||||
const target = (rule.value ?? '').trim();
|
||||
if (!target) return true;
|
||||
if (!compareTarget) return true;
|
||||
if (isEmpty) return true;
|
||||
return !raw!.includes(target);
|
||||
return !raw!.includes(compareTarget);
|
||||
}
|
||||
default:
|
||||
return true;
|
||||
|
||||
Reference in New Issue
Block a user