feat: 浏览器类型字段改为下拉选择

- catalog.json: WebBrowser_Create/V2/V3/CreateBackground 的 type 字段补 options(内置运行浏览器/Chrome/Edge,label 与存储值分离)
- shared/types.ts: CatalogField.options 支持字符串或 {label,value} 对象
- Properties.tsx: select/radio 选项归一化渲染 label;当前值不在选项中时追加显示(兼容历史手填值如 zexplorer)
- typecheck 通过;全量 vitest 除 4 项依赖真实 COM 的历史失败外全部通过
This commit is contained in:
yu 2026-10-30 11:26:32 +08:00
parent 3d64392eb0
commit f28b5ffc5e
3 changed files with 88 additions and 14 deletions

View File

@ -100005,7 +100005,21 @@
"placeholder": "",
"description": "",
"advanced": false,
"valueTypes": []
"valueTypes": [],
"options": [
{
"label": "内置运行浏览器",
"value": "zexplorer"
},
{
"label": "Chrome 浏览器",
"value": "chrome"
},
{
"label": "Edge 浏览器",
"value": "edge"
}
]
},
{
"key": "url",
@ -101046,7 +101060,21 @@
"placeholder": "",
"description": "选择浏览器类型",
"advanced": false,
"valueTypes": []
"valueTypes": [],
"options": [
{
"label": "内置运行浏览器",
"value": "zexplorer"
},
{
"label": "Chrome 浏览器",
"value": "chrome"
},
{
"label": "Edge 浏览器",
"value": "edge"
}
]
},
{
"key": "url",
@ -103389,7 +103417,21 @@
"placeholder": "",
"description": "选择浏览器类型",
"advanced": false,
"valueTypes": []
"valueTypes": [],
"options": [
{
"label": "内置运行浏览器",
"value": "zexplorer"
},
{
"label": "Chrome 浏览器",
"value": "chrome"
},
{
"label": "Edge 浏览器",
"value": "edge"
}
]
},
{
"key": "url",
@ -106333,7 +106375,21 @@
"placeholder": "",
"description": "选择浏览器类型",
"advanced": false,
"valueTypes": []
"valueTypes": [],
"options": [
{
"label": "内置运行浏览器",
"value": "zexplorer"
},
{
"label": "Chrome 浏览器",
"value": "chrome"
},
{
"label": "Edge 浏览器",
"value": "edge"
}
]
},
{
"key": "url",
@ -113804,4 +113860,4 @@
"slots": []
}
]
}
}

View File

@ -46,6 +46,23 @@ function numberOr(value: unknown, fallback: number): number {
return Number.isFinite(parsed) ? parsed : fallback;
}
interface SelectOption {
label: string;
value: string;
}
/** 归一化下拉/单选选项:字符串 → {label,value};当前值不在选项中时追加进去(兼容历史手填值) */
function selectOptionsWith(field: CatalogField, rawValue: unknown): SelectOption[] {
const normalized = (field.options ?? []).map((option): SelectOption =>
typeof option === "string" ? { label: option, value: option } : { label: option.label, value: option.value }
);
const current = textOf(rawValue);
if (current && !normalized.some((option) => option.value === current)) {
normalized.unshift({ label: `${current}(当前值)`, value: current });
}
return normalized;
}
export function Properties({ catalog, onToast }: PropertiesProps) {
const [tab, setTab] = useState<Tab>("props");
const flow = useEditorStore((state) => state.flow);
@ -575,22 +592,22 @@ function FieldControl({
) : field.widget === "select" && field.options?.length ? (
<select className="select-input" value={textOf(raw)} onChange={(event) => onChange(field.key, event.target.value)}>
<option value="">请选择</option>
{field.options.map((option) => (
<option key={option} value={option}>
{option}
{selectOptionsWith(field, raw).map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
) : field.widget === "radio" && field.options?.length ? (
<div className="chip-row">
{field.options.map((option) => (
{selectOptionsWith(field, raw).map((option) => (
<button
type="button"
key={option}
className={`option-chip${textOf(raw) === option ? " active" : ""}`}
onClick={() => onChange(field.key, option)}
key={option.value}
className={`option-chip${textOf(raw) === option.value ? " active" : ""}`}
onClick={() => onChange(field.key, option.value)}
>
{option}
{option.label}
</button>
))}
</div>

View File

@ -25,7 +25,8 @@ export interface CatalogField {
description: string;
advanced: boolean;
valueTypes: string[];
options?: string[];
/** 下拉/单选选项:字符串(显示=存储值)或 { label, value }(显示与存储分离) */
options?: Array<string | { label: string; value: string }>;
unit?: string;
}