Files
label-designer/src/components/VisibilityRuleForm.tsx
2026-06-15 17:53:51 +08:00

202 lines
7.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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>
);
};