From 9572e16dcb4cbcaac2c841c9d3be2d0f8a12e214 Mon Sep 17 00:00:00 2001 From: t0ng7u Date: Sun, 10 Aug 2025 01:18:36 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat:=20Support=20dot=E2=80=91chain?= =?UTF-8?q?ed=20props=20for=20LobeHub=20icons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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. --- .../table/models/modals/EditVendorModal.jsx | 4 +- web/src/helpers/render.js | 72 ++++++++++++++++--- web/src/i18n/locales/en.json | 2 +- 3 files changed, 64 insertions(+), 14 deletions(-) diff --git a/web/src/components/table/models/modals/EditVendorModal.jsx b/web/src/components/table/models/modals/EditVendorModal.jsx index f0e00387..16e9e10f 100644 --- a/web/src/components/table/models/modals/EditVendorModal.jsx +++ b/web/src/components/table/models/modals/EditVendorModal.jsx @@ -158,10 +158,10 @@ const EditVendorModal = ({ visible, handleClose, refresh, editingVendor }) => { - {t('图标使用@lobehub/icons库,查询所有可用图标 ')} + {t('图标使用@lobehub/icons库,如:OpenAI、Claude.Color,支持链式参数:OpenAI.Avatar.type={\'platform\'}、OpenRouter.Avatar.shape={\'square\'},查询所有可用图标请 ')} } diff --git a/web/src/helpers/render.js b/web/src/helpers/render.js index 4a1c3b9e..f140ba4f 100644 --- a/web/src/helpers/render.js +++ b/web/src/helpers/render.js @@ -419,7 +419,11 @@ export function getChannelIcon(channelType) { /** * 根据图标名称动态获取 LobeHub 图标组件 - * @param {string} iconName - 图标名称 + * 支持: + * - 基础:"OpenAI"、"OpenAI.Color" 等 + * - 额外属性(点号链式):"OpenAI.Avatar.type={'platform'}"、"OpenRouter.Avatar.shape={'square'}" + * - 继续兼容第二参数 size;若字符串里有 size=,以字符串为准 + * @param {string} iconName - 图标名称/描述 * @param {number} size - 图标大小,默认为 14 * @returns {JSX.Element} - 对应的图标组件或 Avatar */ @@ -430,22 +434,68 @@ export function getLobeHubIcon(iconName, size = 14) { return ?; } - let IconComponent; + // 解析组件路径与点号链式属性 + const segments = String(iconName).split('.'); + const baseKey = segments[0]; + const BaseIcon = LobeIcons[baseKey]; - if (iconName.includes('.')) { - const [base, variant] = iconName.split('.'); - const BaseIcon = LobeIcons[base]; - IconComponent = BaseIcon ? BaseIcon[variant] : undefined; + let IconComponent = undefined; + let propStartIndex = 1; + + if (BaseIcon && segments.length > 1 && BaseIcon[segments[1]]) { + IconComponent = BaseIcon[segments[1]]; + propStartIndex = 2; } else { - IconComponent = LobeIcons[iconName]; + IconComponent = LobeIcons[baseKey]; + propStartIndex = 1; } - if (IconComponent && (typeof IconComponent === 'function' || typeof IconComponent === 'object')) { - return ; + // 失败兜底 + if (!IconComponent || (typeof IconComponent !== 'function' && typeof IconComponent !== 'object')) { + const firstLetter = String(iconName).charAt(0).toUpperCase(); + return {firstLetter}; } - const firstLetter = iconName.charAt(0).toUpperCase(); - return {firstLetter}; + // 解析点号链式属性,形如:key={...}、key='...'、key="..."、key=123、key、key=true/false + 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 ; } // 颜色列表 diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index 22a7d6b9..a55e1baf 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -1862,7 +1862,7 @@ "请输入供应商描述": "Please enter the vendor description", "供应商图标": "Vendor icon", "请输入图标名称,如: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", "精确": "Exact", "前缀": "Prefix",