完美的
This commit is contained in:
@@ -796,7 +796,10 @@ export const PropSidebar: React.FC<PropSidebarProps> = ({
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<ValueExtractFields elem={selectedElem} onChange={handleElemChange} />
|
||||
<ValueExtractFields
|
||||
config={selectedElem}
|
||||
onChange={(patch) => handleElemChange({ ...selectedElem, ...patch })}
|
||||
/>
|
||||
{selectedElem.type === 'text' && (
|
||||
<TextDisplayAffixFields elem={selectedElem} onChange={handleElemChange} />
|
||||
)}
|
||||
|
||||
@@ -456,7 +456,10 @@ export const TableCellTextFields: React.FC<TableCellTextFieldsProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ValueExtractFields elem={textElem} onChange={patchText} />
|
||||
<ValueExtractFields
|
||||
config={textElem}
|
||||
onChange={(patch) => patchText({ ...textElem, ...patch })}
|
||||
/>
|
||||
<TextDisplayAffixFields elem={textElem} onChange={patchText} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -45,7 +45,7 @@ export const TextDisplayAffixFields: React.FC<TextDisplayAffixFieldsProps> = ({
|
||||
/>
|
||||
</div>
|
||||
<p className="col-span-2 text-[9px] text-[#666] leading-relaxed">
|
||||
在值提取完成后追加,仅影响显示;不参与「内容值」显示条件判断
|
||||
在值提取完成后追加,仅影响显示;反选模式下会一并参与从原值中去除;不参与「内容值」显示条件判断
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,34 +1,39 @@
|
||||
import React from 'react';
|
||||
import { TemplateElement } from '../types';
|
||||
import type { ValueExtractConfig } from '../types';
|
||||
import { CommitInput } from './CommitInput';
|
||||
import { FieldSelect } from './PsSelect';
|
||||
|
||||
interface ValueExtractFieldsProps {
|
||||
elem: TemplateElement;
|
||||
onChange: (elem: TemplateElement) => void;
|
||||
config: ValueExtractConfig;
|
||||
onChange: (patch: Partial<ValueExtractConfig>) => void;
|
||||
}
|
||||
|
||||
const PropInput = CommitInput;
|
||||
|
||||
function getEffectiveExtractMode(elem: TemplateElement) {
|
||||
if (elem.textExtractMode !== undefined) return elem.textExtractMode;
|
||||
return elem.textSplitDelimiter?.trim() ? 'split' : 'none';
|
||||
function getEffectiveExtractMode(config: ValueExtractConfig) {
|
||||
if (config.textExtractMode !== undefined) return config.textExtractMode;
|
||||
return config.textSplitDelimiter?.trim() ? 'split' : 'none';
|
||||
}
|
||||
|
||||
export const ValueExtractFields: React.FC<ValueExtractFieldsProps> = ({ elem, onChange }) => {
|
||||
const extractMode = getEffectiveExtractMode(elem);
|
||||
type ExtractSelectValue = 'none' | 'split' | 'prefix' | 'suffix' | 'inverse';
|
||||
|
||||
export const ValueExtractFields: React.FC<ValueExtractFieldsProps> = ({ config, onChange }) => {
|
||||
const isInverse = config.textExtractInverse === true;
|
||||
const extractMode = getEffectiveExtractMode(config);
|
||||
const selectValue: ExtractSelectValue = isInverse ? 'inverse' : extractMode;
|
||||
const showExtractFields = selectValue !== 'none';
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<div>
|
||||
<label className="ps-field-label">值提取</label>
|
||||
<FieldSelect
|
||||
value={extractMode}
|
||||
value={selectValue}
|
||||
onChange={(e) => {
|
||||
const mode = e.target.value as 'none' | 'split' | 'prefix' | 'suffix';
|
||||
const mode = e.target.value as ExtractSelectValue;
|
||||
if (mode === 'none') {
|
||||
onChange({
|
||||
...elem,
|
||||
textExtractInverse: false,
|
||||
textExtractMode: 'none',
|
||||
textSplitDelimiter: undefined,
|
||||
textSplitIndex: undefined,
|
||||
@@ -36,14 +41,23 @@ export const ValueExtractFields: React.FC<ValueExtractFieldsProps> = ({ elem, on
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (mode === 'inverse') {
|
||||
onChange({
|
||||
textExtractInverse: true,
|
||||
textExtractMode: extractMode === 'none' ? 'prefix' : extractMode,
|
||||
textSplitIndex: config.textSplitIndex ?? 1,
|
||||
textExtractLength: config.textExtractLength ?? 1,
|
||||
});
|
||||
return;
|
||||
}
|
||||
onChange({
|
||||
...elem,
|
||||
textExtractInverse: false,
|
||||
textExtractMode: mode,
|
||||
textSplitIndex: mode === 'split' ? elem.textSplitIndex ?? 1 : elem.textSplitIndex,
|
||||
textSplitIndex: mode === 'split' ? config.textSplitIndex ?? 1 : config.textSplitIndex,
|
||||
textExtractLength:
|
||||
mode === 'prefix' || mode === 'suffix'
|
||||
? elem.textExtractLength ?? 1
|
||||
: elem.textExtractLength,
|
||||
? config.textExtractLength ?? 1
|
||||
: config.textExtractLength,
|
||||
});
|
||||
}}
|
||||
className="ps-field"
|
||||
@@ -52,48 +66,76 @@ export const ValueExtractFields: React.FC<ValueExtractFieldsProps> = ({ elem, on
|
||||
<option value="split">按分隔符</option>
|
||||
<option value="prefix">前 N 位</option>
|
||||
<option value="suffix">后 N 位</option>
|
||||
<option value="inverse">反选</option>
|
||||
</FieldSelect>
|
||||
</div>
|
||||
{extractMode === 'split' && (
|
||||
{isInverse && (
|
||||
<div>
|
||||
<label className="ps-field-label">去除段</label>
|
||||
<FieldSelect
|
||||
value={extractMode === 'none' ? 'prefix' : extractMode}
|
||||
onChange={(e) => {
|
||||
const mode = e.target.value as 'split' | 'prefix' | 'suffix';
|
||||
onChange({
|
||||
textExtractInverse: true,
|
||||
textExtractMode: mode,
|
||||
textSplitIndex: mode === 'split' ? config.textSplitIndex ?? 1 : config.textSplitIndex,
|
||||
textExtractLength:
|
||||
mode === 'prefix' || mode === 'suffix'
|
||||
? config.textExtractLength ?? 1
|
||||
: config.textExtractLength,
|
||||
});
|
||||
}}
|
||||
className="ps-field"
|
||||
>
|
||||
<option value="prefix">前 N 位</option>
|
||||
<option value="split">按分隔符</option>
|
||||
<option value="suffix">后 N 位</option>
|
||||
</FieldSelect>
|
||||
<p className="text-[9px] text-[#666] mt-1 leading-relaxed">
|
||||
从原值中去掉「显示前缀 + 提取段 + 显示后缀」。例如原值 A-1-1-1,提取前 1 位并追加后缀
|
||||
「-」,则得到 1-1-1
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{showExtractFields && extractMode === 'split' && (
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div>
|
||||
<label className="ps-field-label">分隔符</label>
|
||||
<PropInput
|
||||
type="text"
|
||||
value={elem.textSplitDelimiter ?? ''}
|
||||
value={config.textSplitDelimiter ?? ''}
|
||||
placeholder="如 -"
|
||||
onCommit={(val) => {
|
||||
const trimmed = val.trim();
|
||||
if (!trimmed) {
|
||||
onChange({
|
||||
...elem,
|
||||
textExtractMode: 'none',
|
||||
textExtractMode: isInverse ? 'split' : 'none',
|
||||
textExtractInverse: isInverse ? true : false,
|
||||
textSplitDelimiter: undefined,
|
||||
textSplitIndex: undefined,
|
||||
});
|
||||
return;
|
||||
}
|
||||
onChange({
|
||||
...elem,
|
||||
textExtractMode: 'split',
|
||||
textSplitDelimiter: trimmed,
|
||||
textSplitIndex: elem.textSplitIndex ?? 1,
|
||||
textSplitIndex: config.textSplitIndex ?? 1,
|
||||
});
|
||||
}}
|
||||
className="ps-field font-mono"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="ps-field-label">显示第几位</label>
|
||||
<label className="ps-field-label">{isInverse ? '去除第几位' : '显示第几位'}</label>
|
||||
<PropInput
|
||||
type="number"
|
||||
min="1"
|
||||
max="99"
|
||||
value={elem.textSplitIndex ?? 1}
|
||||
disabled={!elem.textSplitDelimiter?.trim()}
|
||||
value={config.textSplitIndex ?? 1}
|
||||
disabled={!config.textSplitDelimiter?.trim()}
|
||||
onCommit={(val) =>
|
||||
onChange({
|
||||
...elem,
|
||||
textExtractMode: 'split',
|
||||
textSplitIndex: Math.max(1, Math.min(99, Number(val) || 1)),
|
||||
})
|
||||
@@ -101,34 +143,37 @@ export const ValueExtractFields: React.FC<ValueExtractFieldsProps> = ({ elem, on
|
||||
className="ps-field font-mono"
|
||||
/>
|
||||
</div>
|
||||
<p className="col-span-2 text-[9px] text-[#666] leading-relaxed">
|
||||
变量替换为完整内容后,按分隔符拆分并取第 N 段(从 1 开始);段数不足时为空
|
||||
</p>
|
||||
{!isInverse && (
|
||||
<p className="col-span-2 text-[9px] text-[#666] leading-relaxed">
|
||||
变量替换为完整内容后,按分隔符拆分并取第 N 段(从 1 开始);段数不足时为空
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{(extractMode === 'prefix' || extractMode === 'suffix') && (
|
||||
{showExtractFields && (extractMode === 'prefix' || extractMode === 'suffix') && (
|
||||
<div>
|
||||
<label className="ps-field-label">
|
||||
{extractMode === 'prefix' ? '前' : '后'} N 位
|
||||
{isInverse ? '去除' : extractMode === 'prefix' ? '前' : '后'} N 位
|
||||
</label>
|
||||
<PropInput
|
||||
type="number"
|
||||
min="1"
|
||||
max="999"
|
||||
value={elem.textExtractLength ?? 1}
|
||||
value={config.textExtractLength ?? 1}
|
||||
onCommit={(val) =>
|
||||
onChange({
|
||||
...elem,
|
||||
textExtractLength: Math.max(1, Math.min(999, Number(val) || 1)),
|
||||
})
|
||||
}
|
||||
className="ps-field font-mono"
|
||||
/>
|
||||
<p className="text-[9px] text-[#666] mt-1 leading-relaxed">
|
||||
{extractMode === 'prefix'
|
||||
? '变量替换为完整内容后,取最前面的 N 个字符'
|
||||
: '变量替换为完整内容后,取最后面的 N 个字符'}
|
||||
</p>
|
||||
{!isInverse && (
|
||||
<p className="text-[9px] text-[#666] mt-1 leading-relaxed">
|
||||
{extractMode === 'prefix'
|
||||
? '变量替换为完整内容后,取最前面的 N 个字符'
|
||||
: '变量替换为完整内容后,取最后面的 N 个字符'}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { X } from 'lucide-react';
|
||||
import { VisibilityRule } from '../types';
|
||||
import { visibilityOperatorNeedsValue } from '../utils';
|
||||
import { visibilityOperatorNeedsValue, valueExtractConfigHasExtract } from '../utils';
|
||||
import { VisibilityRuleForm, type VisibilityRuleDraft } from './VisibilityRuleForm';
|
||||
|
||||
const EMPTY_DRAFT: VisibilityRuleDraft = {
|
||||
@@ -12,6 +12,46 @@ const EMPTY_DRAFT: VisibilityRuleDraft = {
|
||||
value: '',
|
||||
};
|
||||
|
||||
function pickRuleExtractFields(draft: VisibilityRuleDraft): Partial<VisibilityRule> {
|
||||
if (!valueExtractConfigHasExtract(draft)) {
|
||||
return {
|
||||
textExtractMode: undefined,
|
||||
textSplitDelimiter: undefined,
|
||||
textSplitIndex: undefined,
|
||||
textExtractLength: undefined,
|
||||
textExtractInverse: undefined,
|
||||
textDisplayPrefix: undefined,
|
||||
textDisplaySuffix: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
textExtractMode: draft.textExtractMode,
|
||||
textSplitDelimiter: draft.textSplitDelimiter,
|
||||
textSplitIndex: draft.textSplitIndex,
|
||||
textExtractLength: draft.textExtractLength,
|
||||
textExtractInverse: draft.textExtractInverse || undefined,
|
||||
textDisplayPrefix: draft.textDisplayPrefix || undefined,
|
||||
textDisplaySuffix: draft.textDisplaySuffix || undefined,
|
||||
};
|
||||
}
|
||||
|
||||
function ruleToDraft(rule: VisibilityRule): VisibilityRuleDraft {
|
||||
return {
|
||||
target: rule.target ?? 'variable',
|
||||
variable: rule.variable,
|
||||
operator: rule.operator,
|
||||
value: rule.value ?? '',
|
||||
textExtractMode: rule.textExtractMode,
|
||||
textSplitDelimiter: rule.textSplitDelimiter,
|
||||
textSplitIndex: rule.textSplitIndex,
|
||||
textExtractLength: rule.textExtractLength,
|
||||
textExtractInverse: rule.textExtractInverse,
|
||||
textDisplayPrefix: rule.textDisplayPrefix,
|
||||
textDisplaySuffix: rule.textDisplaySuffix,
|
||||
};
|
||||
}
|
||||
|
||||
interface VisibilityRuleDialogProps {
|
||||
open: boolean;
|
||||
mode: 'add' | 'edit';
|
||||
@@ -40,12 +80,7 @@ export const VisibilityRuleDialog: React.FC<VisibilityRuleDialogProps> = ({
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
if (mode === 'edit' && initialRule) {
|
||||
setDraft({
|
||||
target: initialRule.target ?? 'variable',
|
||||
variable: initialRule.variable,
|
||||
operator: initialRule.operator,
|
||||
value: initialRule.value ?? '',
|
||||
});
|
||||
setDraft(ruleToDraft(initialRule));
|
||||
} else {
|
||||
setDraft({
|
||||
...EMPTY_DRAFT,
|
||||
@@ -83,6 +118,7 @@ export const VisibilityRuleDialog: React.FC<VisibilityRuleDialogProps> = ({
|
||||
value: visibilityOperatorNeedsValue(draft.operator)
|
||||
? draft.value?.trim() || undefined
|
||||
: undefined,
|
||||
...pickRuleExtractFields(draft),
|
||||
});
|
||||
onClose();
|
||||
};
|
||||
|
||||
@@ -3,13 +3,16 @@ import {
|
||||
VisibilityRule,
|
||||
VisibilityOperator,
|
||||
VisibilityRuleTarget,
|
||||
ValueExtractConfig,
|
||||
VISIBILITY_OPERATOR_LABELS,
|
||||
VISIBILITY_RULE_TARGET_LABELS,
|
||||
} from '../types';
|
||||
import { visibilityOperatorNeedsValue } from '../utils';
|
||||
import { visibilityOperatorNeedsValue, valueExtractConfigHasExtract } from '../utils';
|
||||
import { FieldSelect } from './PsSelect';
|
||||
import { ValueExtractFields } from './ValueExtractFields';
|
||||
import { TextDisplayAffixFields } from './TextDisplayAffixFields';
|
||||
|
||||
export interface VisibilityRuleDraft {
|
||||
export interface VisibilityRuleDraft extends ValueExtractConfig {
|
||||
target: VisibilityRuleTarget;
|
||||
variable: string;
|
||||
operator: VisibilityOperator;
|
||||
@@ -31,6 +34,7 @@ export const VisibilityRuleForm: React.FC<VisibilityRuleFormProps> = ({
|
||||
variableOnly = false,
|
||||
}) => {
|
||||
const target = variableOnly ? 'variable' : (rule.target ?? 'variable');
|
||||
const hasExtract = valueExtractConfigHasExtract(rule);
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
@@ -81,11 +85,48 @@ export const VisibilityRuleForm: React.FC<VisibilityRuleFormProps> = ({
|
||||
</div>
|
||||
) : (
|
||||
!variableOnly && (
|
||||
<p className="text-[9px] text-[#666] leading-relaxed">
|
||||
使用本元素绑定内容解析并提取后的显示文本(含数值格式等;不含前缀/后缀字符)
|
||||
</p>
|
||||
<p className="text-[9px] text-[#666] leading-relaxed">
|
||||
使用本元素绑定内容的变量替换与数值格式化结果(不含元素自身的值提取与显示前后缀)
|
||||
</p>
|
||||
)
|
||||
)}
|
||||
<div className="pt-2 border-t border-[#3a3a3a] space-y-2">
|
||||
<div className="text-[10px] font-bold text-[#31a8ff]">值提取</div>
|
||||
<ValueExtractFields config={rule} onChange={onChange} />
|
||||
{hasExtract && rule.textExtractInverse && (
|
||||
<TextDisplayAffixFields
|
||||
elem={{
|
||||
id: '',
|
||||
type: 'text',
|
||||
name: '',
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
content: '',
|
||||
fontSize: 10,
|
||||
textAlign: 'left',
|
||||
fontWeight: 'normal',
|
||||
barcodeFormat: 'CODE128',
|
||||
showText: true,
|
||||
fontFamily: 'sans',
|
||||
textDisplayPrefix: rule.textDisplayPrefix,
|
||||
textDisplaySuffix: rule.textDisplaySuffix,
|
||||
}}
|
||||
onChange={(patch) =>
|
||||
onChange({
|
||||
textDisplayPrefix: patch.textDisplayPrefix,
|
||||
textDisplaySuffix: patch.textDisplaySuffix,
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{hasExtract && !rule.textExtractInverse && (
|
||||
<p className="text-[9px] text-[#666] leading-relaxed">
|
||||
条件判断前先对{target === 'content' ? '内容值' : '变量值'}做提取,再与目标值比较
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<label className="ps-field-label">条件</label>
|
||||
<FieldSelect
|
||||
|
||||
Reference in New Issue
Block a user