167 lines
7.1 KiB
TypeScript
167 lines
7.1 KiB
TypeScript
import { ColorPicker, Empty, Input, InputNumber, Select, Slider, Switch } from 'antd';
|
|
import type { AnnotationElement } from '@/types/annotation';
|
|
import { FONT_FAMILIES } from '@/utils/annotation';
|
|
|
|
interface ElementPropsPanelProps {
|
|
element: AnnotationElement | null;
|
|
onUpdate: (id: number, patch: Partial<AnnotationElement>) => void;
|
|
}
|
|
|
|
const FONT_OPTIONS = FONT_FAMILIES.map((f) => ({ value: f, label: f }));
|
|
|
|
const ElementPropsPanel = ({ element, onUpdate }: ElementPropsPanelProps) => {
|
|
if (!element) {
|
|
return (
|
|
<div className="props-empty">
|
|
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="未选中元素" />
|
|
<p className="props-hint">点击画布上的文字或标尺进行编辑</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const update = (patch: Partial<AnnotationElement>) => onUpdate(element.id, patch);
|
|
|
|
return (
|
|
<div className="props-panel">
|
|
{element.type === 'text' ? (
|
|
<>
|
|
<div className="prop-row">
|
|
<span className="prop-label">内容</span>
|
|
<Input.TextArea
|
|
value={element.content}
|
|
autoSize={{ minRows: 1, maxRows: 4 }}
|
|
onChange={(e) => update({ content: e.target.value })}
|
|
/>
|
|
</div>
|
|
<div className="prop-row">
|
|
<span className="prop-label">字体</span>
|
|
<Select
|
|
value={element.fontFamily}
|
|
options={FONT_OPTIONS}
|
|
onChange={(v) => update({ fontFamily: v })}
|
|
style={{ flex: 1 }}
|
|
/>
|
|
</div>
|
|
<div className="prop-row">
|
|
<span className="prop-label">字号</span>
|
|
<InputNumber
|
|
value={element.fontSize}
|
|
min={8}
|
|
onChange={(v) => update({ fontSize: v ?? 18 })}
|
|
style={{ flex: 1 }}
|
|
/>
|
|
</div>
|
|
<div className="prop-row">
|
|
<span className="prop-label">加粗</span>
|
|
<Switch checked={element.bold} onChange={(v) => update({ bold: v })} />
|
|
</div>
|
|
<div className="prop-row">
|
|
<span className="prop-label">文字颜色</span>
|
|
<ColorPicker
|
|
value={element.color}
|
|
onChange={(c) => update({ color: c.toHexString() })}
|
|
/>
|
|
</div>
|
|
<div className="prop-row">
|
|
<span className="prop-label">描边颜色</span>
|
|
<ColorPicker
|
|
value={element.strokeColor}
|
|
onChange={(c) => update({ strokeColor: c.toHexString() })}
|
|
/>
|
|
</div>
|
|
<div className="prop-row">
|
|
<span className="prop-label">描边宽度</span>
|
|
<InputNumber
|
|
value={element.strokeWidth}
|
|
min={0}
|
|
onChange={(v) => update({ strokeWidth: v ?? 0 })}
|
|
style={{ flex: 1 }}
|
|
/>
|
|
</div>
|
|
</>
|
|
) : (
|
|
<>
|
|
<div className="prop-row">
|
|
<span className="prop-label">长度(px)</span>
|
|
<InputNumber
|
|
value={Math.round(element.length)}
|
|
min={4}
|
|
onChange={(v) => update({ length: v ?? 50 })}
|
|
style={{ flex: 1 }}
|
|
/>
|
|
</div>
|
|
<div className="prop-row">
|
|
<span className="prop-label">线色</span>
|
|
<ColorPicker
|
|
value={element.lineColor}
|
|
onChange={(c) => update({ lineColor: c.toHexString() })}
|
|
/>
|
|
</div>
|
|
<div className="prop-row">
|
|
<span className="prop-label">线宽</span>
|
|
<InputNumber
|
|
value={element.lineWidth}
|
|
min={1}
|
|
onChange={(v) => update({ lineWidth: v ?? 2 })}
|
|
style={{ flex: 1 }}
|
|
/>
|
|
</div>
|
|
<div className="prop-row">
|
|
<span className="prop-label">T字大小</span>
|
|
<InputNumber
|
|
value={Math.round(element.tSize)}
|
|
min={4}
|
|
onChange={(v) => update({ tSize: v ?? 14 })}
|
|
style={{ flex: 1 }}
|
|
/>
|
|
</div>
|
|
<div className="prop-row">
|
|
<span className="prop-label">标签文字</span>
|
|
<Input value={element.label} onChange={(e) => update({ label: e.target.value })} />
|
|
</div>
|
|
<div className="prop-row">
|
|
<span className="prop-label">标签颜色</span>
|
|
<ColorPicker
|
|
value={element.labelColor}
|
|
onChange={(c) => update({ labelColor: c.toHexString() })}
|
|
/>
|
|
</div>
|
|
<div className="prop-row">
|
|
<span className="prop-label">标签字号</span>
|
|
<InputNumber
|
|
value={Math.round(element.labelFontSize)}
|
|
min={8}
|
|
onChange={(v) => update({ labelFontSize: v ?? 14 })}
|
|
style={{ flex: 1 }}
|
|
/>
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
<div className="prop-row">
|
|
<span className="prop-label">透明度</span>
|
|
<Slider
|
|
min={0.05}
|
|
max={1}
|
|
step={0.05}
|
|
value={element.opacity}
|
|
onChange={(v) => update({ opacity: v })}
|
|
style={{ flex: 1 }}
|
|
/>
|
|
</div>
|
|
<div className="prop-row">
|
|
<span className="prop-label">旋转(°)</span>
|
|
<InputNumber
|
|
value={Math.round(element.rotation)}
|
|
min={0}
|
|
max={360}
|
|
onChange={(v) => update({ rotation: (v ?? 0) % 360 })}
|
|
style={{ flex: 1 }}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default ElementPropsPanel;
|