feat: Support dot‑chained props for LobeHub icons

- render.js: Enhance getLobeHubIcon to parse dot‑chained props, e.g.:
  - OpenAI.Avatar.type={'platform'}
  - OpenRouter.Avatar.shape={'square'}
  - Parses booleans/numbers/strings and {…} wrappers; keeps the 2nd arg `size` unless overridden by chain props. Backward compatible.
- EditVendorModal.jsx: Update UI copy — simplify placeholder; document chain‑parameter examples in extra text with doc link.
- en.json: Fix invalid escape sequences in the new i18n string to satisfy linter.

No behavioral changes outside icon rendering; lints pass.
This commit is contained in:
t0ng7u
2025-08-10 01:18:36 +08:00
parent 459fce196f
commit 9572e16dcb
3 changed files with 64 additions and 14 deletions

View File

@@ -158,10 +158,10 @@ const EditVendorModal = ({ visible, handleClose, refresh, editingVendor }) => {
<Form.Input <Form.Input
field="icon" field="icon"
label={t('供应商图标')} label={t('供应商图标')}
placeholder={t('请输入图标名称OpenAI、Claude.Color')} placeholder={t("请输入图标名称")}
extraText={ extraText={
<span> <span>
{t('图标使用@lobehub/icons库查询所有可用图标 ')} {t('图标使用@lobehub/icons库OpenAI、Claude.Color支持链式参数OpenAI.Avatar.type={\'platform\'}、OpenRouter.Avatar.shape={\'square\'}查询所有可用图标 ')}
<Typography.Text <Typography.Text
link={{ href: 'https://icons.lobehub.com/components/lobe-hub', target: '_blank' }} link={{ href: 'https://icons.lobehub.com/components/lobe-hub', target: '_blank' }}
icon={<IconLink />} icon={<IconLink />}

View File

@@ -419,7 +419,11 @@ export function getChannelIcon(channelType) {
/** /**
* 根据图标名称动态获取 LobeHub 图标组件 * 根据图标名称动态获取 LobeHub 图标组件
* @param {string} iconName - 图标名称 * 支持:
* - 基础:"OpenAI"、"OpenAI.Color" 等
* - 额外属性(点号链式):"OpenAI.Avatar.type={'platform'}"、"OpenRouter.Avatar.shape={'square'}"
* - 继续兼容第二参数 size若字符串里有 size=,以字符串为准
* @param {string} iconName - 图标名称/描述
* @param {number} size - 图标大小,默认为 14 * @param {number} size - 图标大小,默认为 14
* @returns {JSX.Element} - 对应的图标组件或 Avatar * @returns {JSX.Element} - 对应的图标组件或 Avatar
*/ */
@@ -430,22 +434,68 @@ export function getLobeHubIcon(iconName, size = 14) {
return <Avatar size="extra-extra-small">?</Avatar>; return <Avatar size="extra-extra-small">?</Avatar>;
} }
let IconComponent; // 解析组件路径与点号链式属性
const segments = String(iconName).split('.');
const baseKey = segments[0];
const BaseIcon = LobeIcons[baseKey];
if (iconName.includes('.')) { let IconComponent = undefined;
const [base, variant] = iconName.split('.'); let propStartIndex = 1;
const BaseIcon = LobeIcons[base];
IconComponent = BaseIcon ? BaseIcon[variant] : undefined; if (BaseIcon && segments.length > 1 && BaseIcon[segments[1]]) {
IconComponent = BaseIcon[segments[1]];
propStartIndex = 2;
} else { } else {
IconComponent = LobeIcons[iconName]; IconComponent = LobeIcons[baseKey];
propStartIndex = 1;
} }
if (IconComponent && (typeof IconComponent === 'function' || typeof IconComponent === 'object')) { // 失败兜底
return <IconComponent size={size} />; if (!IconComponent || (typeof IconComponent !== 'function' && typeof IconComponent !== 'object')) {
const firstLetter = String(iconName).charAt(0).toUpperCase();
return <Avatar size="extra-extra-small">{firstLetter}</Avatar>;
} }
const firstLetter = iconName.charAt(0).toUpperCase(); // 解析点号链式属性形如key={...}、key='...'、key="..."、key=123、key、key=true/false
return <Avatar size="extra-extra-small">{firstLetter}</Avatar>; const props = {};
const parseValue = (raw) => {
if (raw == null) return true;
let v = String(raw).trim();
// 去除一层花括号包裹
if (v.startsWith('{') && v.endsWith('}')) {
v = v.slice(1, -1).trim();
}
// 去除引号
if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) {
return v.slice(1, -1);
}
// 布尔
if (v === 'true') return true;
if (v === 'false') return false;
// 数字
if (/^-?\d+(?:\.\d+)?$/.test(v)) return Number(v);
// 其他原样返回字符串
return v;
};
for (let i = propStartIndex; i < segments.length; i++) {
const seg = segments[i];
if (!seg) continue;
const eqIdx = seg.indexOf('=');
if (eqIdx === -1) {
props[seg.trim()] = true;
continue;
}
const key = seg.slice(0, eqIdx).trim();
const valRaw = seg.slice(eqIdx + 1).trim();
props[key] = parseValue(valRaw);
}
// 兼容第二参数 size若字符串中未显式指定 size则使用函数入参
if (props.size == null && size != null) props.size = size;
return <IconComponent {...props} />;
} }
// 颜色列表 // 颜色列表

View File

@@ -1862,7 +1862,7 @@
"请输入供应商描述": "Please enter the vendor description", "请输入供应商描述": "Please enter the vendor description",
"供应商图标": "Vendor icon", "供应商图标": "Vendor icon",
"请输入图标名称OpenAI、Claude.Color": "Please enter the icon name, such as: OpenAI, Claude.Color", "请输入图标名称OpenAI、Claude.Color": "Please enter the icon name, such as: OpenAI, Claude.Color",
"图标使用@lobehub/icons库查询所有可用图标 ": "The icon uses the @lobehub/icons library, query all available icons ", "图标使用@lobehub/icons库OpenAI、Claude.Color支持链式参数OpenAI.Avatar.type={'platform'}、OpenRouter.Avatar.shape={'square'}查询所有可用图标 ": "The icon uses the @lobehub/icons library, such as: OpenAI, Claude.Color, supports chain parameters: OpenAI.Avatar.type={'platform'}, OpenRouter.Avatar.shape={'square'}, query all available icons please ",
"请点击我": "Please click me", "请点击我": "Please click me",
"精确": "Exact", "精确": "Exact",
"前缀": "Prefix", "前缀": "Prefix",