202 lines
7.0 KiB
TypeScript
202 lines
7.0 KiB
TypeScript
import React from 'react';
|
||
import {
|
||
VisibilityRule,
|
||
VisibilityOperator,
|
||
VisibilityRuleTarget,
|
||
ValueExtractConfig,
|
||
VISIBILITY_OPERATOR_LABELS,
|
||
VISIBILITY_RULE_TARGET_LABELS,
|
||
} from '../types';
|
||
import { visibilityOperatorNeedsValue, valueExtractConfigHasExtract } from '../utils';
|
||
import { FieldSelect } from './PsSelect';
|
||
import { ValueExtractFields } from './ValueExtractFields';
|
||
import { TextDisplayAffixFields } from './TextDisplayAffixFields';
|
||
|
||
export interface VisibilityRuleDraft extends ValueExtractConfig {
|
||
target: VisibilityRuleTarget;
|
||
variable: string;
|
||
operator: VisibilityOperator;
|
||
value?: string;
|
||
}
|
||
|
||
interface VisibilityRuleFormProps {
|
||
rule: VisibilityRuleDraft;
|
||
onChange: (patch: Partial<VisibilityRule>) => void;
|
||
variableOptions: string[];
|
||
/** 为 true 时仅支持按变量判断(如批量导出过滤) */
|
||
variableOnly?: boolean;
|
||
}
|
||
|
||
export const VisibilityRuleForm: React.FC<VisibilityRuleFormProps> = ({
|
||
rule,
|
||
onChange,
|
||
variableOptions,
|
||
variableOnly = false,
|
||
}) => {
|
||
const target = variableOnly ? 'variable' : (rule.target ?? 'variable');
|
||
const hasExtract = valueExtractConfigHasExtract(rule);
|
||
|
||
return (
|
||
<div className="space-y-3">
|
||
{!variableOnly && (
|
||
<div>
|
||
<label className="ps-field-label">条件对象</label>
|
||
<FieldSelect
|
||
value={target}
|
||
onChange={(e) => {
|
||
const nextTarget = e.target.value as VisibilityRuleTarget;
|
||
onChange({
|
||
target: nextTarget,
|
||
variable: nextTarget === 'content' ? '' : rule.variable,
|
||
});
|
||
}}
|
||
className="ps-field text-[11px] w-full mt-0.5"
|
||
>
|
||
{(Object.keys(VISIBILITY_RULE_TARGET_LABELS) as VisibilityRuleTarget[]).map((t) => (
|
||
<option key={t} value={t}>
|
||
{VISIBILITY_RULE_TARGET_LABELS[t]}
|
||
</option>
|
||
))}
|
||
</FieldSelect>
|
||
</div>
|
||
)}
|
||
{target === 'variable' ? (
|
||
<div>
|
||
<label className="ps-field-label">变量</label>
|
||
<FieldSelect
|
||
value={rule.variable}
|
||
onChange={(e) => onChange({ variable: e.target.value })}
|
||
className="ps-field text-[11px] w-full mt-0.5"
|
||
>
|
||
{!rule.variable && <option value="">请选择变量</option>}
|
||
{variableOptions.map((v) => (
|
||
<option key={v} value={v}>
|
||
{v}
|
||
</option>
|
||
))}
|
||
</FieldSelect>
|
||
{variableOptions.length === 0 && (
|
||
<p className="text-[9px] text-[#666] mt-1 leading-relaxed">
|
||
{variableOnly
|
||
? '请先在模板中添加变量后再设置过滤条件'
|
||
: '请先在「变量管理」中添加变量,或改用「内容值」作为条件对象'}
|
||
</p>
|
||
)}
|
||
</div>
|
||
) : (
|
||
!variableOnly && (
|
||
<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
|
||
value={rule.operator}
|
||
onChange={(e) => {
|
||
const operator = e.target.value as VisibilityOperator;
|
||
onChange({
|
||
operator,
|
||
value: visibilityOperatorNeedsValue(operator) ? rule.value : undefined,
|
||
});
|
||
}}
|
||
className="ps-field text-[11px] w-full mt-0.5"
|
||
>
|
||
{(Object.keys(VISIBILITY_OPERATOR_LABELS) as VisibilityOperator[]).map((op) => (
|
||
<option key={op} value={op}>
|
||
{VISIBILITY_OPERATOR_LABELS[op]}
|
||
</option>
|
||
))}
|
||
</FieldSelect>
|
||
</div>
|
||
{visibilityOperatorNeedsValue(rule.operator) && (
|
||
<div>
|
||
<div className="flex items-center justify-between gap-2">
|
||
<label className="ps-field-label">目标值</label>
|
||
<button
|
||
type="button"
|
||
disabled={!rule.value}
|
||
onClick={() => onChange({ value: '' })}
|
||
className="text-[9px] text-[#31a8ff] hover:underline cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed disabled:no-underline shrink-0"
|
||
>
|
||
清空
|
||
</button>
|
||
</div>
|
||
<input
|
||
type="text"
|
||
value={rule.value ?? ''}
|
||
onChange={(e) => onChange({ value: e.target.value })}
|
||
placeholder="比较目标值,可使用 {变量名}"
|
||
className="ps-field font-mono text-[11px] w-full mt-0.5"
|
||
/>
|
||
{variableOptions.length > 0 && (
|
||
<div className="flex flex-wrap gap-1 mt-1.5">
|
||
{variableOptions.map((v) => (
|
||
<button
|
||
key={v}
|
||
type="button"
|
||
onClick={() => {
|
||
const token = `{${v}}`;
|
||
const current = rule.value ?? '';
|
||
onChange({
|
||
value: current.includes(token)
|
||
? current
|
||
: current
|
||
? `${current}${current.endsWith(' ') ? '' : ' '}${token}`
|
||
: token,
|
||
});
|
||
}}
|
||
className="ps-btn text-[9px] font-mono"
|
||
>
|
||
{`{${v}}`}
|
||
</button>
|
||
))}
|
||
</div>
|
||
)}
|
||
<p className="text-[9px] text-[#666] mt-1 leading-relaxed">
|
||
支持字面量或 {`{变量名}`};导出时按数据行解析变量后再比较
|
||
</p>
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
};
|