From e27bf28ec33d63cb6c22b3edfcdfd76a483856a8 Mon Sep 17 00:00:00 2001 From: liquidliang Date: Wed, 14 Jan 2026 17:16:04 +0800 Subject: [PATCH] =?UTF-8?q?docs(claude):=20=E6=B7=BB=E5=8A=A0=E6=96=B0?= =?UTF-8?q?=E8=A7=84=E5=88=99=20-=20Freehand=20=E5=85=83=E7=B4=A0=E5=B1=9E?= =?UTF-8?q?=E6=80=A7=E8=AE=BE=E7=BD=AE=E9=9C=80=E8=A6=81=E8=87=AA=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=20callback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CLAUDE.md | 27 ++++++++++++++ .../toolbar/popup-toolbar/fill-button.tsx | 2 +- .../toolbar/popup-toolbar/popup-toolbar.scss | 11 ++++++ .../RecentColorsContext.tsx | 12 ++++++- .../UnifiedColorPicker.tsx | 36 +++++++++++-------- .../unified-color-picker.scss | 16 +++++++-- packages/drawnix/src/constants/color.ts | 2 +- .../drawnix/src/plugins/freehand/utils.ts | 8 +++++ packages/drawnix/src/transforms/property.ts | 4 +-- packages/drawnix/src/utils/property.ts | 3 +- 10 files changed, 99 insertions(+), 22 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index b8170b80..00c847dd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1406,6 +1406,33 @@ function applyCursorStyle(board: PlaitBoard, size: number) { **检查方法**: 如果 JavaScript 设置的样式不生效,在浏览器开发者工具中检查元素样式,查看是否有 `!important` 规则覆盖。 +### Freehand 元素属性设置需要自定义 callback + +**场景**: 修改 Freehand(手绘线条)元素的属性(如 strokeStyle、strokeColor)时 + +❌ **错误示例**: +```typescript +// 错误:直接使用 PropertyTransforms,Freehand 元素可能不被正确处理 +const setStrokeStyle = (style: StrokeStyle) => { + PropertyTransforms.setStrokeStyle(board, style, { getMemorizeKey }); +}; +``` + +✅ **正确示例**: +```typescript +// 正确:使用 callback 确保所有选中元素都被处理 +export const setStrokeStyle = (board: PlaitBoard, strokeStyle: StrokeStyle) => { + PropertyTransforms.setStrokeStyle(board, strokeStyle, { + getMemorizeKey, + callback: (element: PlaitElement, path: Path) => { + Transforms.setNode(board, { strokeStyle }, path); + }, + }); +}; +``` + +**原因**: `PropertyTransforms` 的默认行为可能不会处理所有类型的元素(如自定义的 Freehand 元素)。通过提供 `callback` 函数,可以确保对所有选中的元素执行属性设置操作。颜色设置(`setStrokeColor`、`setFillColor`)也使用了相同的模式。 + ### 性能指南 - 使用 `React.lazy` 对大型组件进行代码分割 - 对图片实现懒加载和预加载 diff --git a/packages/drawnix/src/components/toolbar/popup-toolbar/fill-button.tsx b/packages/drawnix/src/components/toolbar/popup-toolbar/fill-button.tsx index 26adee2e..240df833 100644 --- a/packages/drawnix/src/components/toolbar/popup-toolbar/fill-button.tsx +++ b/packages/drawnix/src/components/toolbar/popup-toolbar/fill-button.tsx @@ -71,7 +71,7 @@ export const PopupFillButton: React.FC = ({ = ({ chil kvStorageService.get(RECENT_COLORS_KEY) .then((colors) => { if (colors && Array.isArray(colors)) { - setRecentColors(colors); + // 去重:使用 Set 并标准化颜色格式 + const seen = new Set(); + const uniqueColors = colors.filter((color) => { + const normalized = color.toUpperCase().replace(/\s/g, ''); + if (seen.has(normalized)) { + return false; + } + seen.add(normalized); + return true; + }); + setRecentColors(uniqueColors); } }) .catch((error) => { diff --git a/packages/drawnix/src/components/unified-color-picker/UnifiedColorPicker.tsx b/packages/drawnix/src/components/unified-color-picker/UnifiedColorPicker.tsx index 978a0c75..3174fe4d 100644 --- a/packages/drawnix/src/components/unified-color-picker/UnifiedColorPicker.tsx +++ b/packages/drawnix/src/components/unified-color-picker/UnifiedColorPicker.tsx @@ -21,6 +21,7 @@ import { setAlphaToHex, removeAlphaFromHex, isValidHex, + isLightColor, } from './utils'; import './unified-color-picker.scss'; @@ -42,6 +43,9 @@ export const UnifiedColorPicker: React.FC = ({ }) => { // 最近使用颜色 const { recentColors, addRecentColor } = useRecentColors(); + + // 缓存首次渲染时的最近使用颜色,避免选择颜色时顺序变化 + const [initialRecentColors] = useState(() => [...recentColors]); // 解析初始颜色 const initialColor = value && isValidHex(value) ? value : DEFAULT_COLOR; @@ -201,23 +205,27 @@ export const UnifiedColorPicker: React.FC = ({ )} {/* 最近使用颜色 */} - {showRecentColors && recentColors.length > 0 && ( + {showRecentColors && initialRecentColors.length > 0 && (
最近使用
- {recentColors.map((color, index) => ( -
)} diff --git a/packages/drawnix/src/components/unified-color-picker/unified-color-picker.scss b/packages/drawnix/src/components/unified-color-picker/unified-color-picker.scss index 029df599..172baed9 100644 --- a/packages/drawnix/src/components/unified-color-picker/unified-color-picker.scss +++ b/packages/drawnix/src/components/unified-color-picker/unified-color-picker.scss @@ -3,7 +3,7 @@ */ // 变量 - 优化紧凑布局 -$ucp-panel-width: 220px; +$ucp-panel-min-width: 200px; $ucp-panel-height: 140px; $ucp-slider-height: 10px; $ucp-indicator-size: 12px; @@ -22,11 +22,13 @@ $checkerboard-bg: repeating-conic-gradient( display: flex; flex-direction: column; gap: $ucp-gap; - width: $ucp-panel-width; + width: 100%; + min-width: $ucp-panel-min-width; padding: 8px; background: var(--color-surface, #ffffff); border-radius: $ucp-border-radius; user-select: none; + box-sizing: border-box; &.disabled { opacity: 0.6; @@ -173,6 +175,10 @@ $checkerboard-bg: repeating-conic-gradient( background: $checkerboard-bg !important; border: 1px solid rgba(0, 0, 0, 0.15); } + + &--light { + border: 1px solid rgba(0, 0, 0, 0.15); + } } &__no-color-icon { @@ -207,10 +213,12 @@ $checkerboard-bg: repeating-conic-gradient( display: flex; flex-wrap: nowrap; gap: 4px; + height: $ucp-recent-size; overflow: hidden; } &-item { + flex-shrink: 0; width: $ucp-recent-size; height: $ucp-recent-size; padding: 0; @@ -226,6 +234,10 @@ $checkerboard-bg: repeating-conic-gradient( &--selected { box-shadow: 0 0 0 2px var(--color-primary, #1890ff); } + + &--light { + border: 1px solid rgba(0, 0, 0, 0.15); + } } } diff --git a/packages/drawnix/src/constants/color.ts b/packages/drawnix/src/constants/color.ts index 343e2744..63242918 100644 --- a/packages/drawnix/src/constants/color.ts +++ b/packages/drawnix/src/constants/color.ts @@ -2,7 +2,7 @@ import { DEFAULT_COLOR } from '@plait/core'; import { NO_COLOR, WHITE } from '@aitu/utils'; export const CLASSIC_COLORS = [ - { name: 'Topic Color', value: NO_COLOR }, + { name: '无色', value: NO_COLOR }, { name: 'Basic Black', value: DEFAULT_COLOR }, { name: 'White', value: WHITE }, { name: 'Gray', value: '#808080' }, diff --git a/packages/drawnix/src/plugins/freehand/utils.ts b/packages/drawnix/src/plugins/freehand/utils.ts index 81dc1103..f96e4a99 100644 --- a/packages/drawnix/src/plugins/freehand/utils.ts +++ b/packages/drawnix/src/plugins/freehand/utils.ts @@ -189,6 +189,10 @@ export const getStrokeColorByElement = ( board: PlaitBoard, element: PlaitElement ) => { + // 如果明确设置为 'none',返回 'none'(无色) + if (element.strokeColor === 'none') { + return 'none'; + } const defaultColor = getFreehandDefaultStrokeColor( board.theme.themeColorMode ); @@ -197,6 +201,10 @@ export const getStrokeColorByElement = ( }; export const getFillByElement = (board: PlaitBoard, element: PlaitElement) => { + // 如果明确设置为 'none',返回 'none'(无色) + if (element.fill === 'none') { + return 'none'; + } const defaultFill = Freehand.isFreehand(element) && isClosedCustomGeometry(board, element) ? getFreehandDefaultFill(board.theme.themeColorMode) diff --git a/packages/drawnix/src/transforms/property.ts b/packages/drawnix/src/transforms/property.ts index 83fceddc..0642aeea 100644 --- a/packages/drawnix/src/transforms/property.ts +++ b/packages/drawnix/src/transforms/property.ts @@ -54,7 +54,7 @@ export const setFillColor = (board: PlaitBoard, fillColor: string) => { const currentFill = getCurrentFill(board, element); const currentOpacity = hexAlphaToOpacity(currentFill); if (isNoColor(fillColor)) { - Transforms.setNode(board, { fill: null }, path); + Transforms.setNode(board, { fill: 'none' }, path); } else { if ( isNullOrUndefined(currentOpacity) || @@ -97,7 +97,7 @@ export const setStrokeColor = (board: PlaitBoard, newColor: string) => { const currentStrokeColor = getCurrentStrokeColor(board, element); const currentOpacity = hexAlphaToOpacity(currentStrokeColor); if (isNoColor(newColor)) { - Transforms.setNode(board, { strokeColor: null }, path); + Transforms.setNode(board, { strokeColor: 'none' }, path); } else { if ( isNullOrUndefined(currentOpacity) || diff --git a/packages/drawnix/src/utils/property.ts b/packages/drawnix/src/utils/property.ts index cb5cd7d2..882f7d53 100644 --- a/packages/drawnix/src/utils/property.ts +++ b/packages/drawnix/src/utils/property.ts @@ -24,7 +24,8 @@ export const isClosedElement = (board: PlaitBoard, element: PlaitElement) => { return ( MindElement.isMindElement(board, element) || (PlaitDrawElement.isDrawElement(element) && isClosedDrawElement(element)) || - isClosedCustomGeometry(board, element) + isClosedCustomGeometry(board, element) || + Freehand.isFreehand(element) ); };