diff --git a/.gitignore b/.gitignore index f2310ce4cb3fb3b9c55f237baccb8793456990ed..7a85b3c7f0677b9a48bb6ecb33a228a73f10a6c3 100644 --- a/.gitignore +++ b/.gitignore @@ -89,3 +89,8 @@ md转docx.py 生成源代码文档_v3.py 生成源代码文档_v4.py 生成源代码文档_v5.py +文档/使用说明.txt +文档/color_card_20260313_142803.json +文档/color_card_20260313_161405.json +/测试 +文档/代码审查报告-26.03.md diff --git a/README.md b/README.md index 93466baf473b343d557918893b2603ada45db4a6..2f3aa080c93813e55a02227691e5cfc8402134c6 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,8 @@ ## 使用说明 +📺 **视频教程**:[取色卡(Color Card)使用说明](https://www.bilibili.com/video/BV1vpckzhEH8/) + ### 基本操作 1. **导入图片**:点击「色彩提取」或「明度提取」面板中的图片显示区域即可导入图片,也支持拖拽导入 @@ -362,6 +364,8 @@ Unlike common color tools or websites that only provide a single function, Color ## Usage +📺 **Video Tutorial**: [Color Card Tutorial (in Chinese)](https://www.bilibili.com/video/BV1vpckzhEH8/) + ### Basic Operations 1. **Import Image**: Click "Open Image" button, supports drag-and-drop diff --git a/dialogs/about_dialog.py b/dialogs/about_dialog.py index 49cfd3f8fd7607b0e547fc57365514daba204abe..1df6dcd24c7e8fe8da9b3e9707eb82abc3f03ce4 100644 --- a/dialogs/about_dialog.py +++ b/dialogs/about_dialog.py @@ -9,7 +9,7 @@ from PySide6.QtGui import QDesktopServices from PySide6.QtWidgets import ( QDialog, QFrame, QHBoxLayout, QVBoxLayout, QWidget ) -from qfluentwidgets import CaptionLabel, PlainTextEdit, PrimaryPushButton, PushButton, isDarkTheme, qconfig +from qfluentwidgets import CaptionLabel, PlainTextEdit, PushButton, isDarkTheme, qconfig # 项目模块导入 from utils import tr, fix_windows_taskbar_icon_for_window, load_icon_universal, set_window_title_bar_theme @@ -44,7 +44,7 @@ class AboutDialog(QDialog): def __init__(self, parent=None): super().__init__(parent) self.setWindowTitle(tr('dialogs.about.title')) - self.setFixedSize(600, 550) + self.setFixedSize(700, 550) # 设置窗口图标 self.setWindowIcon(load_icon_universal()) @@ -142,8 +142,8 @@ class AboutDialog(QDialog): ) buttons_layout.addWidget(self.homepage_button) - # 项目地址按钮(主题色) - self.project_button = PrimaryPushButton(tr('dialogs.about.project')) + # 项目地址按钮 + self.project_button = PushButton(tr('dialogs.about.project')) self.project_button.setMinimumWidth(90) self.project_button.clicked.connect( lambda: self._open_url("https://gitee.com/qingshangongzai/color_card") @@ -161,7 +161,15 @@ class AboutDialog(QDialog): self.agreement_button.setMinimumWidth(90) self.agreement_button.clicked.connect(self._open_agreement_file) buttons_layout.addWidget(self.agreement_button) - + + # 使用说明按钮 + self.tutorial_button = PushButton(tr('dialogs.about.tutorial')) + self.tutorial_button.setMinimumWidth(90) + self.tutorial_button.clicked.connect( + lambda: self._open_url("https://www.bilibili.com/video/BV1vpckzhEH8/") + ) + buttons_layout.addWidget(self.tutorial_button) + buttons_layout.addStretch() parent_layout.addWidget(buttons_container) diff --git a/dialogs/edit_palette.py b/dialogs/edit_palette.py index f32d065e2c701e918453d939c65644e54b61f6cb..be6239e8e3c85785cfbe34ed7c0a3f68ac6ceb19 100644 --- a/dialogs/edit_palette.py +++ b/dialogs/edit_palette.py @@ -951,17 +951,19 @@ class ColorInputRow(QWidget): class EditPaletteDialog(QDialog): """编辑配色对话框""" - def __init__(self, default_name="", palette_data=None, parent=None): + def __init__(self, default_name="", palette_data=None, parent=None, show_name_input=True): """初始化添加/编辑配色对话框 Args: default_name: 默认配色名称 palette_data: 已有配色数据(编辑模式),None表示添加模式 parent: 父窗口 + show_name_input: 是否显示名称输入区域(默认True) """ super().__init__(parent) self._palette_data = palette_data self._is_edit_mode = palette_data is not None + self._show_name_input = show_name_input self.setWindowTitle("编辑配色" if self._is_edit_mode else "添加配色") self.setFixedSize(300, 400) self._default_name = default_name @@ -1010,25 +1012,26 @@ class EditPaletteDialog(QDialog): layout.setContentsMargins(20, 20, 20, 20) layout.setSpacing(15) - # 名称输入区域 - name_layout = QHBoxLayout() - name_label = QLabel("配色名称:") - name_label.setStyleSheet(f"color: {get_text_color().name()}; font-size: 13px;") - name_layout.addWidget(name_label) - - self.name_input = LineEdit() - self.name_input.setText(self._default_name) - self.name_input.setPlaceholderText("输入配色名称...") - self.name_input.setContextMenuPolicy(Qt.ContextMenuPolicy.NoContextMenu) - self.name_input.setClearButtonEnabled(True) - name_layout.addWidget(self.name_input) - layout.addLayout(name_layout) - - # 分隔线 - separator = QLabel() - separator.setFixedHeight(1) - separator.setStyleSheet(f"background-color: {get_border_color().name()};") - layout.addWidget(separator) + # 名称输入区域(根据参数决定是否显示) + if self._show_name_input: + name_layout = QHBoxLayout() + name_label = QLabel("配色名称:") + name_label.setStyleSheet(f"color: {get_text_color().name()}; font-size: 13px;") + name_layout.addWidget(name_label) + + self.name_input = LineEdit() + self.name_input.setText(self._default_name) + self.name_input.setPlaceholderText("输入配色名称...") + self.name_input.setContextMenuPolicy(Qt.ContextMenuPolicy.NoContextMenu) + self.name_input.setClearButtonEnabled(True) + name_layout.addWidget(self.name_input) + layout.addLayout(name_layout) + + # 分隔线 + separator = QLabel() + separator.setFixedHeight(1) + separator.setStyleSheet(f"background-color: {get_border_color().name()};") + layout.addWidget(separator) # 颜色列表标题 colors_title = QLabel(tr('dialogs.edit_palette.colors_title')) @@ -1080,9 +1083,10 @@ class EditPaletteDialog(QDialog): layout.addLayout(buttons_layout) - # 设置焦点到名称输入框 - self.name_input.setFocus() - self.name_input.selectAll() + # 设置焦点到名称输入框(如果存在) + if self._show_name_input: + self.name_input.setFocus() + self.name_input.selectAll() # 添加第一个颜色输入行(不滚动) self._on_add_color(scroll_to_bottom=False) @@ -1126,7 +1130,7 @@ class EditPaletteDialog(QDialog): # 设置名称 name = self._palette_data.get('name', '') - if name: + if name and self._show_name_input: self.name_input.setText(name) # 获取已有颜色 @@ -1168,7 +1172,7 @@ class EditPaletteDialog(QDialog): return # 获取名称 - name = self.name_input.text().strip() + name = self.name_input.text().strip() if self._show_name_input else "" if not name: name = self._default_name diff --git a/docs/changelog.json b/docs/changelog.json index 7cccaad092e988bac763cf71bf6088f3d455640d..aa413241b212e93d94497f8d33b62ef91e16b93e 100644 --- a/docs/changelog.json +++ b/docs/changelog.json @@ -1,5 +1,43 @@ { "versions": [ + { + "version": "v1.5.1", + "date": "2026-03-15", + "changes": [ + { + "category": "新增功能", + "items": [ + "新增使用说明视频教程入口" + ] + }, + { + "category": "问题修复", + "items": [ + "修复渐变提取HEX输入框可输入非法字符的问题" + ] + }, + { + "category": "界面优化", + "items": [ + "预览配色编辑对话框隐藏名称输入区域", + "配色管理卡片添加序号显示" + ] + }, + { + "category": "内容调整", + "items": [ + "重命名设置分组「色卡显示设置」为「颜色信息」", + "修正简体中文配置文件中的错误标题名称" + ] + }, + { + "category": "性能提升", + "items": [ + "优化配色组删除性能,避免重新加载导致的卡顿" + ] + } + ] + }, { "version": "v1.5.0", "date": "2026-03-08", diff --git a/docs/index.html b/docs/index.html index 1b728d0a2a3fd6e073d2f73cacc86691b165ca79..e5ad4931d9ac4767634c145d4567ac81ce3b2e3f 100644 --- a/docs/index.html +++ b/docs/index.html @@ -484,6 +484,7 @@ 项目起源 下载 更新日志 + 使用说明 反馈 关于我们 @@ -675,7 +676,7 @@

立即开始使用

无需联网、无需注册、隐私安全、一步即达

-
+
@@ -701,6 +702,17 @@ Gitee
+
+
+ +
+

使用说明

+

视频教程,快速上手

+ + + 观看教程 + +
@@ -1029,6 +1041,7 @@
diff --git a/locales/FR_FR.json b/locales/FR_FR.json index ebfd30201d7c08db7d75b0285ebe81bd767230d8..9d0ac79b984504b0d4983ed6e1ca6a9d0beae8a3 100644 --- a/locales/FR_FR.json +++ b/locales/FR_FR.json @@ -193,7 +193,8 @@ }, "settings": { "title": "Paramètres", - "card_display": "Paramètres d'affichage des cartes", + "card_display": "Paramètres d'infos couleur", + "card_display_desc": "Configurer l'affichage des valeurs et modes de couleur", "hex_display": "Afficher la valeur HEX", "hex_display_desc": "Afficher la valeur HEX et le bouton de copie dans les cartes de couleurs", "color_mode": "Affichage du mode couleur", @@ -251,7 +252,8 @@ "homepage": "Page d'accueil", "project": "Projet", "license": "Licence", - "agreement": "Contrat utilisateur", + "agreement": "Accord utilisateur", + "tutorial": "Tutoriel", "copyright": "Droits d'auteur", "license_note": "Sous licence GPL v3, uniquement à des fins éducatives" }, diff --git a/locales/JA_JP.json b/locales/JA_JP.json index fdae388bb205274b4d7ccbdbf154a1bab0cd8def..b20d7b62ac7749af5c4e4fea0449694a743099ff 100644 --- a/locales/JA_JP.json +++ b/locales/JA_JP.json @@ -193,7 +193,8 @@ }, "settings": { "title": "設定", - "card_display": "カード表示設定", + "card_display": "カラー情報設定", + "card_display_desc": "カラー値とカラーモードの表示方法を設定", "hex_display": "HEXカラー値を表示", "hex_display_desc": "カラーカードにHEXカラー値とコピーボタンを表示", "color_mode": "カラーモード表示", @@ -252,6 +253,7 @@ "project": "プロジェクト", "license": "ライセンス", "agreement": "利用規約", + "tutorial": "使い方", "copyright": "著作権", "license_note": "GPL v3の下でライセンスされています。教育目的のみです" }, diff --git a/locales/RU_RU.json b/locales/RU_RU.json index 21dc775ae4ee3646539fa5d42cbbad80e5c154a6..58af60fc789445cfeea4754adf1b07e3eee2fb41 100644 --- a/locales/RU_RU.json +++ b/locales/RU_RU.json @@ -193,7 +193,8 @@ }, "settings": { "title": "Настройки", - "card_display": "Настройки отображения карточек", + "card_display": "Настройки цветовой информации", + "card_display_desc": "Настройка отображения цветовых значений и цветовых режимов", "hex_display": "Показывать HEX значение", "hex_display_desc": "Отображать HEX значение цвета и кнопку копирования в цветовых карточках", "color_mode": "Отображение цветового режима", @@ -252,6 +253,7 @@ "project": "Проект", "license": "Лицензия", "agreement": "Пользовательское соглашение", + "tutorial": "Руководство", "copyright": "Авторские права", "license_note": "Лицензировано под GPL v3, только для образовательных целей" }, diff --git a/locales/ZW_FT.json b/locales/ZW_FT.json index 418d3eba7ab641d30728444d63a6fc5908ef0f65..53d0cff2feadc4381c2304446d04930388e49a87 100644 --- a/locales/ZW_FT.json +++ b/locales/ZW_FT.json @@ -193,7 +193,8 @@ }, "settings": { "title": "設置", - "card_display": "色卡顯示設置", + "card_display": "顏色信息設置", + "card_display_desc": "配置顏色值和色彩模式的顯示方式", "hex_display": "顯示16進制顏色值", "hex_display_desc": "在色彩提取面板的色卡中顯示16進制顏色值和複製按鈕", "color_mode": "色彩模式顯示", @@ -252,6 +253,7 @@ "project": "項目地址", "license": "開源許可", "agreement": "用戶協議", + "tutorial": "使用說明", "copyright": "版權所有", "license_note": "基於 GPL v3 開源,僅供學習交流使用" }, diff --git a/locales/ZW_JT.json b/locales/ZW_JT.json index 0da52d8fc67e193407da6425cbbae1425f22716d..16672d745048efd60dbb53c0ac4a5a0f0e346718 100644 --- a/locales/ZW_JT.json +++ b/locales/ZW_JT.json @@ -176,7 +176,7 @@ "hue_distribution_title": "色相分布" }, "preset_color": { - "title": "预设颜色", + "title": "内置色彩", "random_palette": "随机配色", "random": "随机", "desc_random": "从所有配色方案中随机筛选", @@ -193,7 +193,8 @@ }, "settings": { "title": "设置", - "card_display": "色卡显示设置", + "card_display": "颜色信息设置", + "card_display_desc": "配置颜色值和色彩模式的显示方式", "hex_display": "显示16进制颜色值", "hex_display_desc": "在色彩提取面板的色卡中显示16进制颜色值和复制按钮", "color_mode": "色彩模式显示", @@ -252,6 +253,7 @@ "project": "项目地址", "license": "开源许可", "agreement": "用户协议", + "tutorial": "使用说明", "copyright": "版权所有", "license_note": "基于 GPL v3 开源,仅供学习交流使用" }, diff --git a/locales/en_US.json b/locales/en_US.json index ca1c89415f4e8f65110fbf8bcbf7a8f5f0078daa..1ecb3cb99449fa785622c7295bce9bdef127eede 100644 --- a/locales/en_US.json +++ b/locales/en_US.json @@ -193,7 +193,8 @@ }, "settings": { "title": "Settings", - "card_display": "Card Display Settings", + "card_display": "Color Info Settings", + "card_display_desc": "Configure how color values and color modes are displayed", "hex_display": "Show HEX Color Value", "hex_display_desc": "Display HEX color value and copy button in color cards", "color_mode": "Color Mode Display", @@ -252,6 +253,7 @@ "project": "Project", "license": "License", "agreement": "User Agreement", + "tutorial": "Tutorial", "copyright": "Copyright", "license_note": "Licensed under GPL v3, for educational purposes only" }, diff --git a/ui/color_preview.py b/ui/color_preview.py index 3e2976e09ea8839440079b7a40cac5217b033592..b0b5b3709f407996dc411081390182ddc90487bc 100644 --- a/ui/color_preview.py +++ b/ui/color_preview.py @@ -177,8 +177,19 @@ class DraggableColorDot(QWidget): def _copy_hex_to_clipboard(self): """复制HEX值到剪贴板""" clipboard = QApplication.clipboard() - clipboard.setText(self._color.upper()) - log_user_action("copy_hex_to_clipboard", {"color": self._color.upper()}) + hex_value = self._color.upper() + clipboard.setText(hex_value) + log_user_action("copy_hex_to_clipboard", {"color": hex_value}) + + InfoBar.success( + title=tr('messages.copy_success.title'), + content=tr('messages.copy_success.content', value=hex_value), + orient=Qt.Orientation.Horizontal, + isClosable=True, + position=InfoBarPosition.TOP, + duration=2000, + parent=self.window() + ) # ============================================================================ @@ -294,8 +305,8 @@ class ColorDotBar(QWidget): 'colors': colors_info } - # 打开编辑对话框 - dialog = EditPaletteDialog(palette_data=palette_data, parent=self) + # 打开编辑对话框(预览配色场景不显示名称输入) + dialog = EditPaletteDialog(palette_data=palette_data, parent=self, show_name_input=False) if dialog.exec() == EditPaletteDialog.DialogCode.Accepted: new_palette_data = dialog.get_palette_data() if new_palette_data and 'colors' in new_palette_data: diff --git a/ui/gradient_extract.py b/ui/gradient_extract.py index 24ed6a8873f1c2da787bef4a492ffe35f92b6005..271da410c4192e4c51c8582b3e47c8fdcc43be01 100644 --- a/ui/gradient_extract.py +++ b/ui/gradient_extract.py @@ -308,7 +308,10 @@ class GradientExtractInterface(QWidget): self.start_color_input.setFixedHeight(28) self.start_color_input.setAlignment(Qt.AlignmentFlag.AlignCenter) self.start_color_input.setContextMenuPolicy(Qt.ContextMenuPolicy.NoContextMenu) - self.start_color_input.textChanged.connect(self._on_start_color_input_changed) + self.start_color_input.setMaxLength(7) # #RRGGBB 格式 + self.start_color_input.setPlaceholderText("#RRGGBB") + self.start_color_input.textChanged.connect(self._on_start_color_text_changed) + self.start_color_input.editingFinished.connect(self._on_start_color_editing_finished) self.start_color_dot.clicked.connect(self._open_start_color_picker) start_color_layout.addStretch() start_color_layout.addWidget(self.start_color_dot) @@ -328,7 +331,10 @@ class GradientExtractInterface(QWidget): self.end_color_input.setFixedHeight(28) self.end_color_input.setAlignment(Qt.AlignmentFlag.AlignCenter) self.end_color_input.setContextMenuPolicy(Qt.ContextMenuPolicy.NoContextMenu) - self.end_color_input.textChanged.connect(self._on_end_color_input_changed) + self.end_color_input.setMaxLength(7) # #RRGGBB 格式 + self.end_color_input.setPlaceholderText("#RRGGBB") + self.end_color_input.textChanged.connect(self._on_end_color_text_changed) + self.end_color_input.editingFinished.connect(self._on_end_color_editing_finished) self.end_color_dot.clicked.connect(self._open_end_color_picker) end_color_layout.addStretch() end_color_layout.addWidget(self.end_color_dot) @@ -447,21 +453,115 @@ class GradientExtractInterface(QWidget): self.random_button.setText(tr('gradient_extract.random')) self.favorite_button.setText(tr('gradient_extract.favorite')) - def _on_start_color_input_changed(self, text: str): - """起始颜色输入框改变""" - if self._is_valid_hex(text): - self._start_color = text.upper() - self.start_color_dot.set_color(self._start_color) - log_user_action("change_start_color", {"color": self._start_color}) - self._generate_gradient() - - def _on_end_color_input_changed(self, text: str): - """结束颜色输入框改变""" - if self._is_valid_hex(text): - self._end_color = text.upper() - self.end_color_dot.set_color(self._end_color) - log_user_action("change_end_color", {"color": self._end_color}) - self._generate_gradient() + def _on_start_color_text_changed(self, text: str): + """起始颜色文本变化处理(自动格式化为大写并确保#前缀)""" + if not text: + return + + # 自动转大写 + upper_text = text.upper() + + # 只允许有效的十六进制字符和# + valid_chars = '#0123456789ABCDEF' + filtered_text = ''.join(c for c in upper_text if c in valid_chars) + + # 确保以#开头 + if not filtered_text.startswith('#'): + filtered_text = '#' + filtered_text + + # 限制长度(# + 6位十六进制) + if len(filtered_text) > 7: + filtered_text = filtered_text[:7] + + # 更新文本(如果发生变化) + if text != filtered_text: + cursor_pos = self.start_color_input.cursorPosition() + self.start_color_input.setText(filtered_text) + # 保持光标位置 + new_pos = min(len(filtered_text), cursor_pos + (1 if not text.startswith('#') else 0)) + self.start_color_input.setCursorPosition(new_pos) + + def _on_start_color_editing_finished(self): + """起始颜色编辑完成处理(验证并更新颜色)""" + text = self.start_color_input.text().strip().upper() + + if not text: + return + + # 添加 # 前缀 + if not text.startswith('#'): + text = '#' + text + + # 验证HEX格式 + if not self._is_valid_hex(text): + # 无效则恢复原值 + self.start_color_input.setText(self._start_color) + return + + # 如果颜色没有变化,不进行处理 + if text == self._start_color: + return + + # 更新颜色 + self._start_color = text + self.start_color_dot.set_color(self._start_color) + log_user_action("change_start_color", {"color": self._start_color}) + self._generate_gradient() + + def _on_end_color_text_changed(self, text: str): + """结束颜色文本变化处理(自动格式化为大写并确保#前缀)""" + if not text: + return + + # 自动转大写 + upper_text = text.upper() + + # 只允许有效的十六进制字符和# + valid_chars = '#0123456789ABCDEF' + filtered_text = ''.join(c for c in upper_text if c in valid_chars) + + # 确保以#开头 + if not filtered_text.startswith('#'): + filtered_text = '#' + filtered_text + + # 限制长度(# + 6位十六进制) + if len(filtered_text) > 7: + filtered_text = filtered_text[:7] + + # 更新文本(如果发生变化) + if text != filtered_text: + cursor_pos = self.end_color_input.cursorPosition() + self.end_color_input.setText(filtered_text) + # 保持光标位置 + new_pos = min(len(filtered_text), cursor_pos + (1 if not text.startswith('#') else 0)) + self.end_color_input.setCursorPosition(new_pos) + + def _on_end_color_editing_finished(self): + """结束颜色编辑完成处理(验证并更新颜色)""" + text = self.end_color_input.text().strip().upper() + + if not text: + return + + # 添加 # 前缀 + if not text.startswith('#'): + text = '#' + text + + # 验证HEX格式 + if not self._is_valid_hex(text): + # 无效则恢复原值 + self.end_color_input.setText(self._end_color) + return + + # 如果颜色没有变化,不进行处理 + if text == self._end_color: + return + + # 更新颜色 + self._end_color = text + self.end_color_dot.set_color(self._end_color) + log_user_action("change_end_color", {"color": self._end_color}) + self._generate_gradient() def _open_start_color_picker(self): """打开起始颜色选择器""" diff --git a/ui/palette_management.py b/ui/palette_management.py index 624e74309038bf84cc0906ade1d4e2adc996cead..8565a1e49e73157277c08a4fe8a061a89674598f 100644 --- a/ui/palette_management.py +++ b/ui/palette_management.py @@ -468,8 +468,9 @@ class PaletteManagementCard(CardWidget): export_ase_requested = Signal(dict) MAX_COLORS_PER_ROW = 6 - def __init__(self, favorite_data: Dict[str, Any], parent=None): + def __init__(self, favorite_data: Dict[str, Any], card_index: int = 0, parent=None): self._favorite_data = favorite_data + self._card_index = card_index self._hex_visible = True self._color_modes = ['HSB', 'LAB'] self._color_cards = [] @@ -635,7 +636,8 @@ class PaletteManagementCard(CardWidget): def _load_favorite_data(self): """加载收藏数据""" - self.name_label.setText(self._favorite_data.get('name', tr('palette_management.unnamed'))) + name = self._favorite_data.get('name', tr('palette_management.unnamed')) + self.name_label.setText(f"{self._card_index + 1}.{name}") created_at = self._favorite_data.get('created_at', '') if created_at: @@ -739,6 +741,7 @@ class PaletteManagementList(QWidget): self._groups = [] self._current_group_index = 0 self._loader = None + self._current_group_indices = [] # 当前分组的索引列表 super().__init__(parent) self.setup_ui() qconfig.themeChangedFinished.connect(self._update_styles) @@ -839,47 +842,52 @@ class PaletteManagementList(QWidget): """加载当前分组的数据""" self._cancel_loader() self._clear_cards() - + if not self._groups or self._current_group_index >= len(self._groups): return - + current_group = self._groups[self._current_group_index] - group_indices = current_group.get("indices", []) - - if len(group_indices) > self.BATCH_THRESHOLD: - self._start_batch_loading(group_indices) + self._current_group_indices = current_group.get("indices", []) + + if len(self._current_group_indices) > self.BATCH_THRESHOLD: + self._start_batch_loading(self._current_group_indices) else: - self._load_group_directly(group_indices) + self._load_group_directly(self._current_group_indices) - def _load_group_directly(self, group_indices: list): - """直接加载分组数据(小数据量) + def _create_palette_card(self, favorite: Dict[str, Any], card_index: int) -> PaletteManagementCard: + """创建配色卡片并设置连接 Args: - group_indices: 分组索引列表 + favorite: 收藏数据 + card_index: 卡片索引(用于显示序号) + + Returns: + PaletteManagementCard: 创建的卡片 """ - # 禁用UI更新,批量处理 - self.content_widget.setUpdatesEnabled(False) + card = PaletteManagementCard(favorite, card_index=card_index) + card.set_hex_visible(self._hex_visible) + card.set_color_modes(self._color_modes) + card.delete_requested.connect(self.favorite_deleted) + card.preview_requested.connect(self._on_preview_requested) + card.contrast_requested.connect(self._on_contrast_requested) + card.color_changed.connect(self._on_color_changed) + card.preview_in_panel_requested.connect(self._on_preview_in_panel_requested) + card.edit_requested.connect(self._on_edit_requested) + card.export_ase_requested.connect(self._on_export_ase_requested) + return card + def _load_group_directly(self, group_indices: list): + """直接加载分组数据(小数据量)""" + self.content_widget.setUpdatesEnabled(False) try: for idx in group_indices: if 0 <= idx < len(self._favorites): favorite = self._favorites[idx] - card = PaletteManagementCard(favorite) - card.set_hex_visible(self._hex_visible) - card.set_color_modes(self._color_modes) - card.delete_requested.connect(self.favorite_deleted) - card.preview_requested.connect(self._on_preview_requested) - card.contrast_requested.connect(self._on_contrast_requested) - card.color_changed.connect(self._on_color_changed) - card.preview_in_panel_requested.connect(self._on_preview_in_panel_requested) - card.edit_requested.connect(self._on_edit_requested) - card.export_ase_requested.connect(self._on_export_ase_requested) + card = self._create_palette_card(favorite, idx) self.content_layout.addWidget(card) self._favorite_cards[favorite.get('id', '')] = card - self.content_layout.addStretch() finally: - # 恢复UI更新 self.content_widget.setUpdatesEnabled(True) def _start_batch_loading(self, group_indices: List[int]): @@ -896,26 +904,13 @@ class PaletteManagementList(QWidget): self._loader.start() def _on_batch_data_ready(self, batch_idx: int, batch_data: List[Dict[str, Any]]): - """批次数据就绪回调 - - Args: - batch_idx: 批次索引 - batch_data: 批次数据列表 - """ - for favorite in batch_data: - card = PaletteManagementCard(favorite) - card.set_hex_visible(self._hex_visible) - card.set_color_modes(self._color_modes) - card.delete_requested.connect(self.favorite_deleted) - card.preview_requested.connect(self._on_preview_requested) - card.contrast_requested.connect(self._on_contrast_requested) - card.color_changed.connect(self._on_color_changed) - card.preview_in_panel_requested.connect(self._on_preview_in_panel_requested) - card.edit_requested.connect(self._on_edit_requested) - card.export_ase_requested.connect(self._on_export_ase_requested) + """批次数据就绪回调""" + start_index = batch_idx * self.BATCH_SIZE + for i, favorite in enumerate(batch_data): + global_index = self._current_group_indices[start_index + i] + card = self._create_palette_card(favorite, global_index) self.content_layout.addWidget(card) self._favorite_cards[favorite.get('id', '')] = card - QApplication.processEvents() def _on_loading_finished(self): @@ -1058,6 +1053,38 @@ class PaletteManagementList(QWidget): except RuntimeError as e: logger.debug(f"Hint label already deleted: {e}") + def remove_favorite_card(self, favorite_id: str) -> bool: + """局部删除指定收藏卡片,其他卡片序号保持不变""" + card = self._favorite_cards.get(favorite_id) + if not card: + return False + + deleted_idx = next((i for i, fav in enumerate(self._favorites) + if fav.get('id', '') == favorite_id), -1) + if deleted_idx < 0: + return False + + self.content_layout.removeWidget(card) + card.deleteLater() + del self._favorite_cards[favorite_id] + self._favorites.pop(deleted_idx) + + if deleted_idx in self._current_group_indices: + self._current_group_indices.remove(deleted_idx) + + if not self._current_group_indices: + if self._current_group_index > 0: + self._current_group_index -= 1 + elif self._current_group_index < len(self._groups) - 1: + self._current_group_index += 1 + else: + self._show_empty_state() + return True + self.group_changed.emit(self._current_group_index) + self._load_current_group() + + return True + # ============================================================================= # 配色管理界面 @@ -1225,7 +1252,10 @@ class PaletteManagementInterface(QWidget): log_user_action("clear_all_favorites", {"count": favorites_count}, "success") def _on_favorite_deleted(self, favorite_id): - """收藏删除回调""" + """收藏删除回调(优化版) + + 使用局部刷新替代完整重新加载,避免卡顿。 + """ favorite = self._config_manager.get_favorite(favorite_id) palette_name = favorite.get('name', tr('palette_management.unnamed')) if favorite else tr('palette_management.unnamed') @@ -1240,7 +1270,14 @@ class PaletteManagementInterface(QWidget): if msg_box.exec(): self._config_manager.delete_favorite(favorite_id) self._config_manager.save() - self._load_favorites() + + # 局部刷新:仅移除对应卡片,不重新加载 + success = self.palette_management_list.remove_favorite_card(favorite_id) + + if not success: + # 局部删除失败时回退到完整刷新 + self._load_favorites() + log_user_action("delete_favorite", {"id": favorite_id, "name": palette_name}, "success") def _on_import_clicked(self): diff --git a/version.py b/version.py index b042171aa66526bc940b3b04727038b6bf3120d3..a22ae180cb398401c6e642542a7e676931d2bcff 100644 --- a/version.py +++ b/version.py @@ -9,7 +9,7 @@ class VersionManager: # 版本号组件 self.major: int = 1 self.minor: int = 5 - self.patch: int = 0 + self.patch: int = 1 self.build: int = 0 self.prerelease: str = "" diff --git a/version.txt b/version.txt index 9e9b5a71216568e2b830ca48b9c6e1b987329d80..ab7dd437beb6065f1915ee9fdc567fca700d6930 100644 --- a/version.txt +++ b/version.txt @@ -1,6 +1,6 @@ -1.5.0 -2026.3.8.1 -1.5.0.0 +1.5.1 +2026.3.15.1 +1.5.1.0 浮晓 HXiao Studio © 2026 浮晓 HXiao Studio 取色卡 - Color Card \ No newline at end of file diff --git a/version_info.txt b/version_info.txt index 92cc9a49fcb05af36b4d5f91ad0a2d93517a1fac..691c4f15d21f4e7952ef9e5c9022e7ca673a85e4 100644 --- a/version_info.txt +++ b/version_info.txt @@ -1,7 +1,7 @@ VSVersionInfo( ffi=FixedFileInfo( - filevers=(2026,3,8,1), - prodvers=(1,5,0,0), + filevers=(2026,3,15,1), + prodvers=(1,5,1,0), mask=0x3f, flags=0x0, OS=0x4, @@ -17,12 +17,12 @@ VSVersionInfo( [ StringStruct(u'CompanyName', u'浮晓 HXiao Studio'), StringStruct(u'FileDescription', u'取色卡 - Color Card'), - StringStruct(u'FileVersion', u'1.5.0'), + StringStruct(u'FileVersion', u'1.5.1'), StringStruct(u'InternalName', u'Color_Card'), StringStruct(u'LegalCopyright', u'© 2026 浮晓 HXiao Studio'), StringStruct(u'OriginalFilename', u'Color_Card.exe'), StringStruct(u'ProductName', u'取色卡'), - StringStruct(u'ProductVersion', u'1.5.0'), + StringStruct(u'ProductVersion', u'1.5.1'), StringStruct(u'Comments', u'一站式的图片的图片分析和配色工具') ] ) diff --git a/website/index.html b/website/index.html index 1b728d0a2a3fd6e073d2f73cacc86691b165ca79..e5ad4931d9ac4767634c145d4567ac81ce3b2e3f 100644 --- a/website/index.html +++ b/website/index.html @@ -484,6 +484,7 @@ 项目起源 下载 更新日志 + 使用说明 反馈 关于我们
@@ -675,7 +676,7 @@

立即开始使用

无需联网、无需注册、隐私安全、一步即达

-
+
@@ -701,6 +702,17 @@ Gitee
+
+
+ +
+

使用说明

+

视频教程,快速上手

+ + + 观看教程 + +
@@ -1029,6 +1041,7 @@
diff --git a/website/public/changelog.json b/website/public/changelog.json index 6fa6753b1bad23aa5e2c7ffacbfff6488568aac7..65bb19fa4b2f94ece25776f4050235aa6a07c6d0 100644 --- a/website/public/changelog.json +++ b/website/public/changelog.json @@ -1,5 +1,43 @@ { "versions": [ + { + "version": "v1.5.1", + "date": "2026-03-15", + "changes": [ + { + "category": "新增功能", + "items": [ + "新增使用说明视频教程入口" + ] + }, + { + "category": "问题修复", + "items": [ + "修复渐变提取HEX输入框可输入非法字符的问题" + ] + }, + { + "category": "界面优化", + "items": [ + "预览配色编辑对话框隐藏名称输入区域", + "配色管理卡片添加序号显示" + ] + }, + { + "category": "内容调整", + "items": [ + "重命名设置分组「色卡显示设置」为「颜色信息」", + "修正简体中文配置文件中的错误标题名称" + ] + }, + { + "category": "性能提升", + "items": [ + "优化配色组删除性能,避免重新加载导致的卡顿" + ] + } + ] + }, { "version": "v1.5.0", "date": "2026-03-08", diff --git "a/\346\226\207\346\241\243/\345\274\200\345\217\221\350\247\204\350\214\203.md" "b/\346\226\207\346\241\243/\345\274\200\345\217\221\350\247\204\350\214\203.md" index 8b68ea512dfc0d6c331f24ed1f074f569d180639..107b29e7ccdcdc437f93eadb6d022d1ccd41e0de 100644 --- "a/\346\226\207\346\241\243/\345\274\200\345\217\221\350\247\204\350\214\203.md" +++ "b/\346\226\207\346\241\243/\345\274\200\345\217\221\350\247\204\350\214\203.md" @@ -9,7 +9,7 @@ Color Card(取色卡)是一个基于 PySide6 开发的图片颜色分析工 **核心功能:** - 图片导入(JPG、PNG、BMP、GIF) -- 色彩提取:5个可拖动取色点,实时显示色彩值 +- 色彩提取:6个可拖动取色点,实时显示色彩值 - 多色彩模式:HSB、LAB、HSL、CMYK、RGB - 明度提取与直方图可视化 - 双面板数据同步 @@ -109,8 +109,7 @@ color_card/ └── .json 颜色库 ``` - ---- +*** ## 2. 代码组织规范 @@ -122,13 +121,13 @@ color_card/ ### 2.2 模块划分 -|模块类型 |职责 |示例文件 | -|:---:|:---:|:---:| -|入口模块 |应用程序入口 |`main.py` | -|核心模块 |颜色处理、配置管理 |`core/color.py`, `core/config.py` | -|UI 模块 |界面组件和面板 |`ui/canvases.py`, `ui/cards.py` | -|对话框模块 |弹出对话框 |`dialogs/about_dialog.py` | -|工具模块 |通用功能 |`utils/icon.py`, `utils/platform.py` | +| 模块类型 | 职责 | 示例文件 | +| :---: | :-------: | :----------------------------------: | +| 入口模块 | 应用程序入口 | `main.py` | +| 核心模块 | 颜色处理、配置管理 | `core/color.py`, `core/config.py` | +| UI 模块 | 界面组件和面板 | `ui/canvases.py`, `ui/cards.py` | +| 对话框模块 | 弹出对话框 | `dialogs/about_dialog.py` | +| 工具模块 | 通用功能 | `utils/icon.py`, `utils/platform.py` | ### 2.3 目录结构原则 @@ -156,95 +155,45 @@ color_card/ ← 第1级 项目使用 PyInstaller 打包为独立可执行文件,资源路径获取必须兼容开发环境和打包后的环境。 -**路径获取函数:** - -```python -import os -import sys - -def get_base_path() -> str: - """获取应用程序基础路径 - - 支持开发环境和 PyInstaller 打包后的环境 - - Returns: - str: 应用程序基础路径 - """ - if getattr(sys, 'frozen', False): - # PyInstaller 打包后的环境 - if hasattr(sys, '_MEIPASS'): - return sys._MEIPASS - return os.path.dirname(sys.executable) - # 开发环境 - 返回当前文件所在目录的父目录(项目根目录) - return os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -``` +**路径获取函数:** 使用 `get_base_path()` 获取应用程序基础路径,支持开发环境和 PyInstaller 打包后的环境。 **使用规范:** -- 所有资源文件(语言包、数据文件、配置文件)路径必须通过 `get_base_path()` 获取 +- 所有资源文件路径必须通过 `get_base_path()` 获取 - 禁止直接使用 `__file__` 获取项目根目录(打包后会失效) -- 资源目录应使用相对路径拼接 ```python -# 正确示例 -from utils.icon import get_base_path - +# 正确用法 base_path = get_base_path() locales_dir = os.path.join(base_path, 'locales') -config_path = os.path.join(base_path, 'color_data', 'config.json') -# 错误示例 - 打包后会找不到资源 -current_dir = os.path.dirname(os.path.abspath(__file__)) -project_root = os.path.dirname(current_dir) # 开发环境有效,打包后失效 +# 错误用法 - 打包后会找不到资源 +project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) ``` -**已适配的模块:** -- `utils/icon.py` - 图标资源路径 -- `utils/locale.py` - 语言包路径 -- `core/config.py` - 场景配置路径 -- `core/color_data.py` - 配色数据路径 ### 2.5 导入规范 **导入顺序:** 标准库 → 第三方库 → 项目模块 -**规范要求:** - -- 按模块类型分组导入,添加清晰的分组注释 -- 定期清理未使用的导入 -- 合并同一模块的多次导入 -- 优先使用绝对导入 - ```python # 标准库导入 import sys -import math from pathlib import Path # 第三方库导入 -from PySide6.QtWidgets import QApplication, QMainWindow -from PySide6.QtCore import Qt, Signal +from PySide6.QtWidgets import QApplication from qfluentwidgets import FluentWindow # 项目模块导入 -from core import get_color_info, get_config_manager +from core import get_color_info from ui import MainWindow ``` -**导入清理示例:** - -```python -# 修改前 -from styles import UnifiedStyleHelper -from styles import get_global_font_manager - -# 修改后 -from styles import UnifiedStyleHelper, get_global_font_manager -``` - +**规范要求:** 按模块类型分组导入,定期清理未使用的导入,合并同一模块的多次导入,优先使用绝对导入。 ---- +*** ## 3. 代码编写规范 @@ -257,82 +206,37 @@ from styles import UnifiedStyleHelper, get_global_font_manager ### 3.2 代码清理原则 -**修改代码时应同步进行以下清理:** - -- 删除未使用的变量、函数、导入和注释 -- 合并重复的逻辑和相似的功能 -- 检查并清除相关的重复冗余代码 -- 保持代码整洁,提高代码复用性 - -**清理示例:** - -```python -# 修改前 -import json -import re - -def calculate(): - temp = 0 # 未使用的变量 - return result - -# 修改后(删除未使用的导入和变量) -def calculate(): - return result -``` +**修改代码时应同步进行以下清理:** 删除未使用的变量、函数、导入和注释;合并重复的逻辑和相似的功能;保持代码整洁,提高代码复用性。 ### 3.3 命名规范 **详细规范请参阅** -|类型 |规范 |示例 | -|:---:|:---:|:---:| -|类名 |驼峰命名法 |`ColorPicker`, `ImageCanvas` | -|函数/方法 |小写+下划线 |`extract_color()` | -|变量 |小写+下划线 |`picker_positions` | -|常量 |大写+下划线 |`PICKER_RADIUS = 12` | -|私有属性 |单下划线前缀 |`_dragging` | -|信号 |小写+下划线 |`color_picked = Signal(int, tuple)` | +| 类型 | 规范 | 示例 | +| :---: | :----: | :---------------------------------: | +| 类名 | 驼峰命名法 | `ColorPicker`, `ImageCanvas` | +| 函数/方法 | 小写+下划线 | `extract_color()` | +| 变量 | 小写+下划线 | `picker_positions` | +| 常量 | 大写+下划线 | `PICKER_RADIUS = 12` | +| 私有属性 | 单下划线前缀 | `_dragging` | +| 信号 | 小写+下划线 | `color_picked = Signal(int, tuple)` | ### 3.3.1 服务类命名规范 业务服务类统一命名为 `XxxService`,放在 `core/` 目录下: -|服务类 |职责 |涉及文件 | -|:---:|:---|:---| -|`ColorService` |颜色提取服务 |`core/color_service.py` | -|`PaletteService` |配色导入导出服务 |`core/palette_service.py` | -|`ImageService` |图片加载服务 |`core/image_service.py` | -|`LuminanceService` |明度计算服务 |`core/luminance_service.py` | -|`PreviewService` |预览场景管理服务 |`core/preview_service.py` | -|`HistogramService` |直方图计算服务 |`core/histogram_service.py` | - -**使用示例:** - -```python -from core import PreviewService - -class ColorPreviewInterface(QWidget): - def __init__(self, parent=None): - super().__init__(parent) - self._preview_service = PreviewService(self) - - def _on_import_config(self): - # UI层只负责界面交互,业务逻辑下沉到服务层 - success, message = self._preview_service.add_user_template( - self._current_scene, file_path - ) - # 根据结果显示UI反馈 -``` +| 服务类 | 职责 | 涉及文件 | +| :----------------: | :------- | :-------------------------- | +| `ColorService` | 颜色提取服务 | `core/color_service.py` | +| `PaletteService` | 配色导入导出服务 | `core/palette_service.py` | +| `ImageService` | 图片加载服务 | `core/image_service.py` | +| `LuminanceService` | 明度计算服务 | `core/luminance_service.py` | +| `PreviewService` | 预览场景管理服务 | `core/preview_service.py` | +| `HistogramService` | 直方图计算服务 | `core/histogram_service.py` | ### 3.4 异常处理规范 -**基本原则:** - -- 避免使用裸 `except:` 或 `except Exception:` -- 应指定具体异常类型 -- 提供详细的错误信息,便于调试 - -**示例:** +**基本原则:** 避免使用裸 `except:` 或 `except Exception:`,应指定具体异常类型,提供详细的错误信息,便于调试。 ```python # 错误示例 @@ -342,48 +246,32 @@ except Exception: # 正确示例 except (OSError, ValueError) as e: error_msg = f"文件读取失败: {str(e)}" - print(error_msg) ``` ### 3.5 文档字符串规范 -**基本原则:** - -- 所有公共类和方法必须添加文档字符串 -- 使用简洁的中文描述,避免冗余 -- 类文档字符串保持简洁(单行或简短段落) -- 方法文档字符串包含 Args、Returns 说明 - -**精简示例:** +**基本原则:** 所有公共类和方法必须添加文档字符串;使用简洁的中文描述,避免冗余;类文档字符串保持简洁(单行或简短段落);方法文档字符串包含 Args、Returns 说明。 ```python -# 类文档字符串(简洁) class ImageCanvas(QWidget): """图片显示画布,支持取色点拖动""" - pass -# 方法文档字符串(完整但简洁) def set_image(self, image_path): """加载并显示图片 Args: image_path: 图片文件的完整路径 """ - pass -# 带返回值的文档字符串 def get_color_info(self, r, g, b): """获取颜色信息 Args: - r: 红色通道值 (0-255) - g: 绿色通道值 (0-255) - b: 蓝色通道值 (0-255) + r, g, b: RGB通道值 (0-255) Returns: dict: 包含RGB、HSB、LAB、HEX颜色信息的字典 """ - pass ``` ### 3.6 信号命名规范 @@ -393,100 +281,48 @@ def get_color_info(self, r, g, b): ```python class ImageCanvas(QWidget): - color_picked = Signal(int, tuple) # 信号:索引, RGB颜色 - image_loaded = Signal(str) # 信号:图片路径 - image_cleared = Signal() # 信号:图片已清空 + color_picked = Signal(int, tuple) # 信号:索引, RGB颜色 + image_loaded = Signal(str) # 信号:图片路径 ``` ### 3.7 类型注解规范 -**基本原则:** - -- 使用 `typing` 模块的类型,而非内置小写类型 -- 避免使用 Python 3.9+ 内置泛型语法,统一使用 `typing` 模块 -- 公共方法应添加返回类型注解 +**基本原则:** 使用 `typing` 模块的类型,而非内置小写类型;避免使用 Python 3.9+ 内置泛型语法;公共方法应添加返回类型注解。 **类型对照表:** -|内置类型(避免) |typing 类型(推荐) |说明 | -|:---:|:---:|:---| -|`list` |`List[元素类型]` |列表类型 | -|`dict` |`Dict[键类型, 值类型]` |字典类型 | -|`tuple` |`Tuple[元素类型, ...]` |元组类型 | -|`type` |`Type[类名]` |类型对象 | - -**正确示例:** +| 内置类型(避免) | typing 类型(推荐) | 说明 | +| :------: | :----------------: | :--- | +| `list` | `List[元素类型]` | 列表类型 | +| `dict` | `Dict[键类型, 值类型]` | 字典类型 | +| `tuple` | `Tuple[元素类型, ...]` | 元组类型 | +| `type` | `Type[类名]` | 类型对象 | ```python -from typing import List, Dict, Any, Tuple, Type, Optional +from typing import List, Dict, Any, Tuple, Optional -# 列表类型 def get_colors(self) -> List[str]: return ['#FF5733', '#33FF57'] -# 字典类型(结构不确定时使用 Any) def get_config(self) -> Dict[str, Any]: return {'name': 'test', 'value': 123} -# 元组类型 -def get_position(self) -> Tuple[int, int]: - return (100, 200) - -# 类型对象 -def register(self, scene_class: Type['BaseScene']) -> None: - pass - -# 可选类型 def find_item(self, item_id: str) -> Optional[Dict[str, Any]]: return None # 或返回字典 ``` -**错误示例:** - -```python -# 错误:使用小写内置类型 -def get_colors(self) -> list: - pass - -# 错误:使用 Python 3.9+ 内置泛型语法 -def get_colors(self) -> list[str]: - pass - -# 错误:字典类型未参数化 -def get_config(self) -> dict: - pass -``` - -**导入规范:** - -```python -# 标准库导入 -from typing import List, Dict, Any, Tuple, Type, Optional - -# 按需导入,避免导入未使用的类型 -``` - ### 3.8 主题与样式规范 #### 3.8.1 主题切换实现规范 -**使用 qfluentwidgets 的主题系统:** - -- 使用 `setTheme(Theme.LIGHT/DARK)` 切换主题 -- 使用 `isDarkTheme()` 检测当前主题 -- 使用 `qconfig.themeChangedFinished` 信号监听主题变化 - -**示例代码:** +**使用 qfluentwidgets 的主题系统:** 使用 `setTheme(Theme.LIGHT/DARK)` 切换主题;使用 `isDarkTheme()` 检测当前主题;使用 `qconfig.themeChangedFinished` 信号监听主题变化。 ```python from qfluentwidgets import setTheme, Theme, isDarkTheme, qconfig # 切换主题 def toggle_theme(): - if isDarkTheme(): - setTheme(Theme.LIGHT) - else: - setTheme(Theme.DARK) + setTheme(Theme.DARK if isDarkTheme() else Theme.LIGHT) # 监听主题变化 qconfig.themeChangedFinished.connect(self._update_styles) @@ -494,22 +330,12 @@ qconfig.themeChangedFinished.connect(self._update_styles) #### 3.8.2 颜色管理规范 -**集中管理颜色值:** - -- 所有颜色值应定义在 `theme_colors.py` 中 -- 使用函数返回 QColor,支持主题感知 -- 避免在组件中硬编码颜色值 - -**颜色函数命名规范:** +**集中管理颜色值:** 所有颜色值应定义在 `theme_colors.py` 中;使用函数返回 QColor,支持主题感知;避免在组件中硬编码颜色值。 ```python -# 背景颜色 - def get_card_background_color(): return QColor(42, 42, 42) if isDarkTheme() else QColor(255, 255, 255) -# 文本颜色 - def get_text_color(secondary=False): if isDarkTheme(): return QColor(160, 160, 160) if secondary else QColor(255, 255, 255) @@ -519,13 +345,7 @@ def get_text_color(secondary=False): #### 3.7.3 主题自适应组件实现 -**必须实现** `_update_styles()` 方法: - -- 在 `__init__` 中调用 `_update_styles()` 初始化样式 -- 连接 `qconfig.themeChangedFinished` 信号 -- 根据 `isDarkTheme()` 返回不同颜色值 - -**示例:** +**必须实现** `_update_styles()` 方法:在 `__init__` 中调用 `_update_styles()` 初始化样式;连接 `qconfig.themeChangedFinished` 信号;根据 `isDarkTheme()` 返回不同颜色值。 ```python class MyWidget(QWidget): @@ -537,52 +357,21 @@ class MyWidget(QWidget): def _update_styles(self): """更新样式以适配主题""" - if isDarkTheme(): - label_color = "#ffffff" - value_color = "#ffffff" - else: - label_color = "#333333" - value_color = "#333333" - + label_color = "#ffffff" if isDarkTheme() else "#333333" self.label.setStyleSheet(f"color: {label_color};") ``` -**注意事项:** - -- `_update_styles()` 中只更新必要的文字颜色,避免调用 `setStyleSheet` 设置容器样式 -- 让 qfluentwidgets 原生机制处理主题切换,避免双重样式更新导致性能问题 -- qfluentwidgets 组件(如 CardWidget、ScrollArea)会自动适配主题 +**注意事项:** `_update_styles()` 中只更新必要的文字颜色,避免调用 `setStyleSheet` 设置容器样式;让 qfluentwidgets 原生机制处理主题切换。 #### 3.7.4 样式表使用规范 -**避免使用** `!important`: - -- 优先使用组件特定的选择器 -- 如必须使用,确保在主题切换后重新应用 - -**自定义标题栏按钮样式:** - -```python -# 在主题切换后重新应用样式 - -def _toggle_theme(self): - if isDarkTheme(): - setTheme(Theme.LIGHT) - else: - setTheme(Theme.DARK) - # 重新应用自定义样式 - self._apply_custom_style() -``` +**避免使用** `!important`:优先使用组件特定的选择器;如必须使用,确保在主题切换后重新应用。 #### 3.8.5 Windows 原生标题栏深色模式 -**使用 Windows DWM API 设置原生标题栏主题:** - -对于继承自 `QDialog` 的对话框,使用 Windows DWM (Desktop Window Manager) API 设置原生标题栏的沉浸式深色模式。 - -**实现步骤:** +对于继承自 `QDialog` 的对话框,使用 Windows DWM API 设置原生标题栏的沉浸式深色模式。 -1. **创建工具函数**(放在 `utils/platform.py`): +**工具函数**(放在 `utils/platform.py`): ```python import ctypes @@ -592,61 +381,38 @@ DWMWA_USE_IMMERSIVE_DARK_MODE = 20 def set_window_title_bar_theme(window, is_dark=False): """为窗口设置标题栏主题(Windows 10+)""" + if sys.platform != "win32": + return False try: - if sys.platform != "win32": - return False - - window_handle = window.windowHandle() - if not window_handle: - return False - - hwnd = int(window_handle.winId()) + hwnd = int(window.windowHandle().winId()) value = ctypes.c_int(1 if is_dark else 0) - - result = ctypes.windll.dwmapi.DwmSetWindowAttribute( - hwnd, - DWMWA_USE_IMMERSIVE_DARK_MODE, - ctypes.byref(value), - ctypes.sizeof(value) - ) - return result == 0 + return ctypes.windll.dwmapi.DwmSetWindowAttribute( + hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE, + ctypes.byref(value), ctypes.sizeof(value) + ) == 0 except Exception: return False ``` -2. **在对话框中应用**(使用 `showEvent` 避免闪烁): +**在对话框中应用**(使用 `showEvent` 避免闪烁): ```python -from qfluentwidgets import isDarkTheme, qconfig -from utils import set_window_title_bar_theme - class MyDialog(QDialog): def __init__(self, parent=None): super().__init__(parent) - # ... 其他初始化代码 ... - - # 监听主题变化(用于更新已打开的对话框) qconfig.themeChangedFinished.connect(self._update_title_bar_theme) def showEvent(self, event): - """窗口显示前设置标题栏主题,避免闪烁""" self._update_title_bar_theme() super().showEvent(event) def _update_title_bar_theme(self): - """更新标题栏主题""" set_window_title_bar_theme(self, isDarkTheme()) ``` -**关键要点:** - -- 使用 `showEvent` 在窗口显示前设置标题栏主题,避免闪烁 -- 连接 `qconfig.themeChangedFinished` 信号,支持已打开对话框的主题切换 -- 仅支持 Windows 10 版本 2004 (Build 19041) 及以上,Windows 11 完全支持 -- 非 Windows 平台静默跳过,不影响程序运行 +**关键要点:** 使用 `showEvent` 在窗口显示前设置标题栏主题;连接 `qconfig.themeChangedFinished` 信号支持已打开对话框的主题切换;仅支持 Windows 10 版本 2004 及以上。 - ---- +*** ## 4. 基类设计规范 @@ -654,23 +420,16 @@ class MyDialog(QDialog): **文件位置:** `ui/canvases.py` -**职责:** - -- 提供图片加载、显示的基础功能 -- 实现坐标转换(画布坐标 ↔ 图片坐标) -- 管理图片相对坐标系统 -- 提供右键菜单框架 +**职责:** 提供图片加载、显示的基础功能;实现坐标转换(画布坐标 ↔ 图片坐标);管理图片相对坐标系统;提供右键菜单框架。 **子类必须实现的方法:** ```python def _on_image_loaded(self): """图片加载后的处理(子类重写)""" - pass def _on_image_cleared(self): """图片清空后的处理(子类重写)""" - pass def _draw_overlay(self, painter: QPainter): """绘制叠加内容(子类必须实现)""" @@ -681,27 +440,15 @@ def _draw_overlay(self, painter: QPainter): **文件位置:** `ui/cards.py` -**职责:** - -- 提供统一的卡片接口 -- 管理卡片列表(BaseCardPanel) -- 支持批量清空卡片 - -**设计原则:** +**职责:** 提供统一的卡片接口;管理卡片列表(BaseCardPanel);支持批量清空卡片。 -- 保持接口简洁(setup_ui, clear) -- 灵活的卡片创建方法 -- 清晰的职责分离 +**设计原则:** 保持接口简洁(setup_ui, clear);灵活的卡片创建方法;清晰的职责分离。 ### 4.3 直方图基类 (BaseHistogram) **文件位置:** `ui/histograms.py` -**职责:** - -- 提供通用的数据管理(set_data, clear) -- 提供通用的绘制框架(paintEvent) -- 定义抽象方法接口 +**职责:** 提供通用的数据管理(set_data, clear);提供通用的绘制框架(paintEvent);定义抽象方法接口。 **子类必须实现的方法:** @@ -712,11 +459,9 @@ def _draw_histogram(self, painter: QPainter): def _draw_custom_overlay(self, painter: QPainter): """绘制自定义叠加内容(子类重写)""" - pass def _draw_labels(self, painter: QPainter): """绘制标签(子类重写)""" - pass ``` ### 4.4 基类设计原则 @@ -726,8 +471,7 @@ def _draw_labels(self, painter: QPainter): 3. **可扩展性**:便于添加新的子类实现 4. **代码复用**:提取公共逻辑,消除重复代码 - ---- +*** ## 5. PySide6 开发规范 @@ -745,11 +489,7 @@ setThemeColor('#0078d4') class MainWindow(FluentWindow): def setup_navigation(self): - self.addSubInterface( - self.interface, - FluentIcon.PALETTE, - "界面名称" - ) + self.addSubInterface(self.interface, FluentIcon.PALETTE, "界面名称") ``` ### 5.2 界面组织规范 @@ -776,31 +516,17 @@ class ColorExtractInterface(QWidget): # 信号连接 self.image_canvas.color_picked.connect(self.on_color_picked) -# 槽函数实现 -def on_color_picked(self, index, rgb): - """颜色提取回调""" - pass - -# 防止双向同步循环的示例 +# 防止双向同步循环 def set_image_data(self, pixmap, image, emit_sync=True): """设置图片数据 Args: - pixmap: QPixmap 对象 - image: QImage 对象 emit_sync: 是否发射同步信号(默认True,从其他面板同步时设为False) """ self._original_pixmap = pixmap self._image = image - - # 只在独立导入时发射同步信号,防止双向循环 if emit_sync: self.image_loaded.emit(file_path) - self.image_data_loaded.emit(pixmap, image) - -# 从其他面板同步时禁用信号发射 -# 面板A导入图片 → 同步到面板B(emit_sync=False) -# 面板B不会发射信号回到面板A,打破循环 ``` ### 5.3.1 图片状态中介者(ImageMediator) @@ -815,7 +541,6 @@ self._image_mediator = ImageMediator(self) # 连接信号 self._image_mediator.image_updated.connect(self._on_image_updated) -self._image_mediator.image_cleared.connect(self._on_image_cleared) # 设置图片(带来源标识,防止循环) self._image_mediator.set_image(pixmap, image, 'color') @@ -824,33 +549,7 @@ self._image_mediator.set_image(pixmap, image, 'color') self._image_mediator.clear_image('color') ``` -**清空同步规范:** - -界面类应提供 `clear_all(emit_signal=True)` 方法统一处理所有清空操作: - -```python -def clear_all(self, emit_signal: bool = True): - """清空所有相关内容 - - Args: - emit_signal: 是否发射清空信号(默认True,从其他面板同步时设为False) - """ - self.image_canvas.clear_image(emit_signal) - self.color_card_panel.clear_all() - self.hsb_color_wheel.clear_sample_points() - self.rgb_histogram_widget.clear() - self.hue_histogram_widget.clear() - -def clear_image(self): - """清空图片(供外部调用,会发射信号同步到其他面板)""" - self.clear_all(emit_signal=True) -``` - -**要点:** - -- 使用 `clear_all()` 统一处理所有组件的清理,避免遗漏 -- 通过 `emit_signal` 参数控制是否触发同步,防止循环 -- 从其他面板同步清空时,使用 `clear_all(emit_signal=False)` +**清空同步规范:** 界面类应提供 `clear_all(emit_signal=True)` 方法统一处理所有清空操作,通过 `emit_signal` 参数控制是否触发同步,防止循环。 ### 5.4 自定义控件规范 @@ -864,7 +563,6 @@ class ColorPicker(QWidget): def paintEvent(self, event): painter = QPainter(self) painter.setRenderHint(QPainter.RenderHint.Antialiasing) - # 绘制代码... ``` ### 5.5 样式设置规范 @@ -876,54 +574,29 @@ class ColorPicker(QWidget): 所有颜色必须通过 `utils/theme_colors.py` 模块统一管理: ```python -# 正确用法 - 从主题颜色模块导入 -from utils.theme_colors import get_text_color, get_canvas_background_color - -# 获取主题感知的文本颜色 +# 正确用法 +from utils.theme_colors import get_text_color text_color = get_text_color() -# 获取固定颜色(如图片显示器背景) -bg_color = get_canvas_background_color() # 固定灰黑色 #2a2a2a -``` - -**禁止的做法:** - -```python -# 错误 - 硬编码颜色值 +# 错误用法 painter.setPen(QColor(255, 255, 255)) -widget.setStyleSheet("background-color: #2a2a2a;") ``` #### 5.5.2 主题颜色模块 (utils/theme_colors.py) -**设计原则:** - -- **集中管理**:所有颜色值集中在 theme_colors.py 中定义 -- **主题感知**:颜色函数根据当前主题(深色/浅色)自动返回对应颜色 -- **分类清晰**:按用途分类(背景色、文本色、边框色、控件颜色等) +**设计原则:** 集中管理所有颜色值;颜色函数根据当前主题自动返回对应颜色;按用途分类(背景色、文本色、边框色、控件颜色等)。 **颜色分类:** -|分类 |函数示例 |说明 | -|:---:|:---|:---| -|背景色 |`get_canvas_background_color()` |图片显示器背景(固定 #2a2a2a) | -|背景色 |`get_card_background_color()` |卡片背景(主题感知) | -|背景色 |`get_histogram_background_color()` |直方图背景(固定 #2a2a2a) | -|文本色 |`get_text_color()` |主文本颜色(主题感知) | -|文本色 |`get_secondary_text_color()` |次要文本颜色(主题感知) | -|文本色 |`get_title_color()` |标题颜色(主题感知) | -|边框色 |`get_border_color()` |边框颜色(主题感知) | -|控件色 |`get_picker_border_color()` |取色点边框颜色 | -|控件色 |`get_picker_fill_color()` |取色点填充颜色 | -|Zone色 |`get_zone_background_color()` |Zone框背景颜色 | -|Zone色 |`get_zone_text_color()` |Zone框文字颜色 | - -**添加新颜色的步骤:** - -1. 在 `utils/theme_colors.py` 中添加颜色函数 -2. 根据用途选择合适的分类 -3. 确定是固定颜色还是主题感知颜色 -4. 在相关组件中使用新函数 +| 分类 | 函数示例 | 说明 | +| :---: | :--------------------------------- | :------------------ | +| 背景色 | `get_canvas_background_color()` | 图片显示器背景(固定 #2a2a2a) | +| 背景色 | `get_card_background_color()` | 卡片背景(主题感知) | +| 文本色 | `get_text_color()` | 主文本颜色(主题感知) | +| 边框色 | `get_border_color()` | 边框颜色(主题感知) | +| 控件色 | `get_picker_border_color()` | 取色点边框颜色 | + +**添加新颜色的步骤:** 在 `utils/theme_colors.py` 中添加颜色函数;根据用途选择合适的分类;在相关组件中使用新函数。 #### 5.5.3 主题设置 @@ -932,45 +605,29 @@ widget.setStyleSheet("background-color: #2a2a2a;") - 使用 `setThemeColor()` 设置主题色 ```python -from qfluentwidgets import FluentWindow, setTheme, Theme, FluentIcon, setThemeColor +from qfluentwidgets import setTheme, Theme, setThemeColor setTheme(Theme.AUTO) setThemeColor('#0078d4') - -class MainWindow(FluentWindow): - pass ``` #### 5.5.4 禁用Qt原生功能规范 **核心原则:新组件应禁用不符合项目需求的Qt原生功能** -Qt控件默认提供的某些功能可能与项目设计不符,应在创建组件时主动禁用。 - -**需要禁用的常见原生功能:** - -|功能 |禁用方法 |适用场景 | -|:---:|:---|:---| -|右键上下文菜单 |`setContextMenuPolicy(Qt.ContextMenuPolicy.NoContextMenu)` |输入框、标签等不需要原生编辑菜单的控件 | -|原生工具提示 |不调用 `setToolTip()` 或设置为空字符串 |使用自定义提示或不需要提示的按钮 | -|焦点虚线框 |`setFocusPolicy(Qt.FocusPolicy.NoFocus)` 或样式表 |不需要键盘焦点的装饰性控件 | - -**示例 - 禁用输入框右键菜单:** +| 功能 | 禁用方法 | 适用场景 | +| :-----: | :--------------------------------------------------------- | :------------------ | +| 右键上下文菜单 | `setContextMenuPolicy(Qt.ContextMenuPolicy.NoContextMenu)` | 输入框、标签等不需要原生编辑菜单的控件 | +| 原生工具提示 | 不调用 `setToolTip()` 或设置为空字符串 | 使用自定义提示或不需要提示的按钮 | +| 焦点虚线框 | `setFocusPolicy(Qt.FocusPolicy.NoFocus)` 或样式表 | 不需要键盘焦点的装饰性控件 | ```python -from PySide6.QtWidgets import QLineEdit -from PySide6.QtCore import Qt - -# 创建输入框并禁用原生右键菜单 +# 禁用输入框右键菜单 self.hex_input = QLineEdit() self.hex_input.setContextMenuPolicy(Qt.ContextMenuPolicy.NoContextMenu) ``` -**注意事项:** - -- 仅在确实不需要原生功能时禁用 -- 如果需要自定义右键菜单,应使用 `RoundMenu` 替代 -- 禁用工具提示时,确保用户通过其他方式了解按钮功能 +**注意事项:** 仅在确实不需要原生功能时禁用;如果需要自定义右键菜单,应使用 `RoundMenu` 替代。 ### 5.6 右键菜单规范 @@ -982,13 +639,11 @@ from qfluentwidgets import RoundMenu, Action, FluentIcon def contextMenuEvent(self, event): menu = RoundMenu("") - change_action = Action(FluentIcon.PHOTO, "更换图片") - menu.addAction(change_action) + menu.addAction(Action(FluentIcon.PHOTO, "更换图片")) menu.exec(event.globalPos()) ``` - ---- +*** ## 6. 颜色处理规范 @@ -1010,66 +665,37 @@ def get_color_info(r, g, b): ### 6.2 明度计算规范 -使用 Rec. 709 标准计算亮度值,包含 sRGB Gamma 校正。 - -```python -def get_luminance(r: int, g: int, b: int) -> int: - """计算像素的明度值 (0-255) - - 使用 Rec. 709 标准计算亮度值,包含 sRGB Gamma 校正 - 这是 Lightroom、Photoshop 等专业软件使用的标准方法 - """ - # 步骤1: 归一化到 0-1 范围 - r_norm, g_norm, b_norm = r / 255.0, g / 255.0, b / 255.0 - - # 步骤2: sRGB Gamma 解码(转换到线性空间) - def srgb_to_linear(c): - if c <= 0.04045: - return c / 12.92 - else: - return ((c + 0.055) / 1.055) ** 2.4 - - r_linear = srgb_to_linear(r_norm) - g_linear = srgb_to_linear(g_norm) - b_linear = srgb_to_linear(b_norm) - - # 步骤3: 在线性空间应用 Rec. 709 权重 - luminance_linear = 0.2126 * r_linear + 0.7152 * g_linear + 0.0722 * b_linear - - # 步骤4-5: 编码回 sRGB 空间并转换到 0-255 - # ... -``` +使用 Rec. 709 标准计算亮度值,包含 sRGB Gamma 校正。这是 Lightroom、Photoshop 等专业软件使用的标准方法。 ### 6.3 Zone 分区规范 采用 Adobe 官方标准的 5 个明度区域: -|区域 |明度范围 |英文名 |描述 | -|:---:|:---:|:---:|:---| -|黑色 |0%–10% |Blacks |黑点区域,最暗的部分 | -|阴影 |10%–30% |Shadows |阴影区域,较暗的色调 | -|中间调 |30%–70% |Midtones |中间亮度区域,由 Exposure/Contrast 负责调整 | -|高光 |70%–90% |Highlights |高光区域,较亮的色调 | -|白色 |90%–100% |Whites |白点区域,最亮的部分 | +| 区域 | 明度范围 | 英文名 | 描述 | +| :-: | :------: | :--------: | :------------------------------ | +| 黑色 | 0%–10% | Blacks | 黑点区域,最暗的部分 | +| 阴影 | 10%–30% | Shadows | 阴影区域,较暗的色调 | +| 中间调 | 30%–70% | Midtones | 中间亮度区域,由 Exposure/Contrast 负责调整 | +| 高光 | 70%–90% | Highlights | 高光区域,较亮的色调 | +| 白色 | 90%–100% | Whites | 白点区域,最亮的部分 | **技术映射(0-255 值到区域):** 使用 `ZONE_WIDTH = 255 / 9 ≈ 28.33` 计算每个Zone的范围: -|Zone |明度范围 |百分比 |区域 | -|:---:|:---:|:---:|:---:| -|Zone 0 |0-28 |0-11% |黑色(Blacks) | -|Zone 1 |28-56 |11-22% |阴影(Shadows) | -|Zone 2 |56-85 |22-33% |阴影(Shadows) | -|Zone 3 |85-113 |33-44% |中间调(Midtones) | -|Zone 4 |113-141 |44-55% |中间调(Midtones) | -|Zone 5 |141-170 |55-67% |中间调(Midtones) | -|Zone 6 |170-198 |67-78% |高光(Highlights) | -|Zone 7 |198-226 |78-89% |高光(Highlights) | -|Zone 8 |226-255 |89-100% |白色(Whites) | +| Zone | 明度范围 | 百分比 | 区域 | +| :----: | :-----: | :-----: | :------------: | +| Zone 0 | 0-28 | 0-11% | 黑色(Blacks) | +| Zone 1 | 28-56 | 11-22% | 阴影(Shadows) | +| Zone 2 | 56-85 | 22-33% | 阴影(Shadows) | +| Zone 3 | 85-113 | 33-44% | 中间调(Midtones) | +| Zone 4 | 113-141 | 44-55% | 中间调(Midtones) | +| Zone 5 | 141-170 | 55-67% | 中间调(Midtones) | +| Zone 6 | 170-198 | 67-78% | 高光(Highlights) | +| Zone 7 | 198-226 | 78-89% | 高光(Highlights) | +| Zone 8 | 226-255 | 89-100% | 白色(Whites) | - ---- +*** ## 7. 图片处理规范 @@ -1083,10 +709,10 @@ def get_luminance(r: int, g: int, b: int) -> int: **核心原则:** 采样点位置使用**图片相对坐标**(归一化坐标 0.0-1.0)存储。 -|坐标类型 |说明 |范围 | -|:---:|:---|:---:| -|相对坐标 |相对于图片的归一化坐标 |0.0-1.0 | -|画布坐标 |相对于画布控件的像素坐标 |像素值 | +| 坐标类型 | 说明 | 范围 | +| :--: | :----------- | :-----: | +| 相对坐标 | 相对于图片的归一化坐标 | 0.0-1.0 | +| 画布坐标 | 相对于画布控件的像素坐标 | 像素值 | ```python # 相对坐标 → 画布坐标 @@ -1098,17 +724,9 @@ rel_x = (canvas_x - disp_x) / disp_w ### 7.3 性能优化规范 -**基本策略:** - -- 使用 `QThread` 在子线程读取图片文件 -- 使用 `QTimer.singleShot()` 延迟执行耗时操作 -- 直方图计算使用采样优化 +**基本策略:** 使用 `QThread` 在子线程读取图片文件;使用 `QTimer.singleShot()` 延迟执行耗时操作;直方图计算使用采样优化。 -**QThread 取消机制:** - -- 使用标志位机制实现线程取消,避免使用 `wait()` 阻塞UI线程 -- 在 `run()` 方法的关键检查点检查取消标志 -- 不等待旧线程结束,立即启动新线程 +**QThread 取消机制:** 使用标志位机制实现线程取消,避免使用 `wait()` 阻塞UI线程;在 `run()` 方法的关键检查点检查取消标志;不等待旧线程结束,立即启动新线程。 ```python class ImageLoader(QThread): @@ -1116,60 +734,27 @@ class ImageLoader(QThread): def __init__(self, image_path: str) -> None: super().__init__() - self._image_path = image_path - self._is_cancelled = False # 取消标志 + self._is_cancelled = False def cancel(self) -> None: - """请求取消加载(线程安全)""" self._is_cancelled = True - def _check_cancelled(self) -> bool: - """检查是否被取消""" - return self._is_cancelled - def run(self) -> None: - """在子线程中加载图片""" - try: - with Image.open(self._image_path) as pil_image: - # 关键检查点1 - if self._check_cancelled(): - return - - # 耗时操作前检查 - if self._check_cancelled(): - return - - # 执行耗时操作... - - # 关键检查点2 - if self._check_cancelled(): - return - - except Exception as e: - if not self._check_cancelled(): - self.error.emit(str(e)) + if self._is_cancelled: + return + # 执行耗时操作... # 使用示例(非阻塞切换) def set_image(self, image_path: str) -> None: - # 取消旧线程(非阻塞) if self._loader is not None: - self._loader.cancel() - # 注意:不调用 wait(),避免阻塞UI线程 - self._loader = None - - # 立即启动新线程 + self._loader.cancel() # 不调用 wait() self._loader = ImageLoader(image_path) self._loader.start() ``` -**UI性能优化:** - -- 批量更新UI时使用 `setUpdatesEnabled(False/True)` 包裹更新操作 -- 避免在循环中频繁更新UI,先收集数据再批量更新 -- 使用 `try-finally` 确保 `setUpdatesEnabled(True)` 一定会执行 +**UI性能优化:** 批量更新UI时使用 `setUpdatesEnabled(False/True)` 包裹更新操作;避免在循环中频繁更新UI;使用 `try-finally` 确保 `setUpdatesEnabled(True)` 一定会执行。 ```python -# 批量更新UI示例 def update_table_data(self): self.table.setUpdatesEnabled(False) try: @@ -1179,119 +764,33 @@ def update_table_data(self): self.table.setUpdatesEnabled(True) ``` -**数据结构优化:** - -- 将列表转换为集合,将查找时间复杂度从 O(n) 降到 O(1) -- 缓存计算结果,避免重复计算和I/O操作 -- 缓存应在数据变化时自动失效,确保数据一致性 +**数据结构优化:** 将列表转换为集合,将查找时间复杂度从 O(n) 降到 O(1);缓存计算结果,避免重复计算和I/O操作。 **应用启动优化策略:** - **界面预创建策略**:启动时创建所有界面,避免切换时的视觉闪烁 - - 优点:切换流畅,无延迟 - - 缺点:启动时间增加 -- **组件缓存延迟生成**:耗时计算(如色轮逐像素渲染)延迟到首次绘制时执行 - - 使用 `QTimer.singleShot(0, self._generate_cache)` 异步生成 - - 生成期间先显示简单背景,完成后刷新 +- **组件缓存延迟生成**:耗时计算延迟到首次绘制时执行,使用 `QTimer.singleShot(0, self._generate_cache)` 异步生成 -**示例 - 色轮缓存延迟生成:** - -```python -def paintEvent(self, event): - painter = QPainter(self) - painter.setRenderHint(QPainter.RenderHint.Antialiasing) - - # 检查是否需要重新生成缓存 - if not self._cache_valid: - # 延迟生成缓存,避免阻塞启动 - if not self._pending_generation: - self._pending_generation = True - QTimer.singleShot(0, self._generate_wheel_cache_async) - # 绘制简单背景 - painter.fillRect(self.rect(), self._get_theme_colors()['bg']) - elif self._wheel_cache: - # 绘制缓存的色环背景 - painter.drawPixmap(0, 0, self._wheel_cache) - - # 绘制其他内容... - -def _generate_wheel_cache_async(self): - """异步生成色轮缓存""" - self._pending_generation = False - self._generate_wheel_cache() - self.update() -``` - -**决策原则:** - -- 界面结构简单 → 预创建所有界面 -- 界面结构复杂/组件初始化耗时 → 延迟创建(需确保切换无闪烁) -- 绘制密集型组件(色轮、直方图) → 延迟生成缓存 +**决策原则:** 界面结构简单 → 预创建所有界面;界面结构复杂/组件初始化耗时 → 延迟创建;绘制密集型组件(色轮、直方图) → 延迟生成缓存。 ### 7.4 内存管理规范 -**使用 ImageMemoryManager 管理图片内存:** - -- 使用 `ImageMemoryManager` 统一管理 QPixmap 和 QImage 的内存 -- 实现 LRU 缓存策略,自动清理最久未使用的图片 -- 限制最大内存占用(默认 500MB) +**使用 ImageMemoryManager 管理图片内存:** 使用 `ImageMemoryManager` 统一管理 QPixmap 和 QImage 的内存;实现 LRU 缓存策略,自动清理最久未使用的图片;限制最大内存占用(默认 500MB)。 ```python from core import get_memory_manager -# 获取全局内存管理器 memory_manager = get_memory_manager() - -# 添加图片到内存管理器 memory_manager.add_image(source_id, pixmap, image) - -# 获取图片(自动更新访问时间) pixmap, image = memory_manager.get_image(source_id) - -# 移除指定图片 -memory_manager.remove_image(source_id) - -# 清空所有图片 memory_manager.clear_all() - -# 获取内存统计信息 -stats = memory_manager.get_memory_stats() ``` -**内存清理规范:** - -- 清空图片时直接赋值为 `None`,让 Python 垃圾回收处理 -- 清理相关缓存(高亮遮罩等) -- 触发垃圾回收 `gc.collect()` - -```python -def clear_image(self, emit_signal: bool = True) -> None: - """清空图片""" - # 直接赋值为 None,让 Python 垃圾回收处理 - self._original_pixmap = None - self._image = None - - # 触发垃圾回收 - gc.collect() -``` - -**缩略图生成优化:** - -- 使用 QImage 进行缩放,然后转换为 QPixmap -- 限制缩略图最大尺寸 - -```python -# 使用QImage进行缩放,内存效率更高 -thumbnail_image = image.scaled( - size, size, - aspectMode=Qt.AspectRatioMode.KeepAspectRatio, - transformMode=Qt.TransformationMode.SmoothTransformation -) -return QPixmap.fromImage(thumbnail_image) -``` +**内存清理规范:** 清空图片时直接赋值为 `None`,让 Python 垃圾回收处理;清理相关缓存;触发垃圾回收 `gc.collect()`。 +**缩略图生成优化:** 使用 QImage 进行缩放,然后转换为 QPixmap;限制缩略图最大尺寸。 ---- +*** ## 8. 配色数据JSON格式规范 @@ -1327,21 +826,21 @@ return QPixmap.fromImage(thumbnail_image) ### 8.3 字段说明 -|字段 |类型 |必填 |说明 | -|:---|:---:|:---:|:---| -|`version` |string |是 |格式版本号,当前为 "1.0" | -|`id` |string |是 |唯一标识符 | -|`name` |string |是 |配色方案名称(英文) | -|`name_zh` |string |否 |配色方案中文名称 | -|`description` |string |否 |描述信息 | -|`author` |string |是 |作者名称 | -|`category` |string |否 |分类:`design_system`/`theme`/`palette`/`user_palette` | -|`palettes` |array |是 |配色组列表 | -|`palettes[].name` |string |是 |配色组名称 | -|`palettes[].colors` |array |是 |颜色值数组(HEX格式) | -|`groups` |array |否 |分组定义(可选) | -|`groups[].name` |string |是 |分组显示名称 | -|`groups[].indices` |array |是 |配色组索引列表 | +| 字段 | 类型 | 必填 | 说明 | +| :------------------ | :----: | :-: | :-------------------------------------------------- | +| `version` | string | 是 | 格式版本号,当前为 "1.0" | +| `id` | string | 是 | 唯一标识符 | +| `name` | string | 是 | 配色方案名称(英文) | +| `name_zh` | string | 否 | 配色方案中文名称 | +| `description` | string | 否 | 描述信息 | +| `author` | string | 是 | 作者名称 | +| `category` | string | 否 | 分类:`design_system`/`theme`/`palette`/`user_palette` | +| `palettes` | array | 是 | 配色组列表 | +| `palettes[].name` | string | 是 | 配色组名称 | +| `palettes[].colors` | array | 是 | 颜色值数组(HEX格式) | +| `groups` | array | 否 | 分组定义(可选) | +| `groups[].name` | string | 是 | 分组显示名称 | +| `groups[].indices` | array | 是 | 配色组索引列表 | ### 8.4 分组配置 @@ -1349,10 +848,10 @@ return QPixmap.fromImage(thumbnail_image) **分组规则**: -|配色组数量 |分组方式 |示例 | -|:---:|:---|:---| -|< 20 |单分组 |"全部 (15组)" | -|≥ 20 |按20组分组 |"第 1-20 组"、"第 21-40 组" | +| 配色组数量 | 分组方式 | 示例 | +| :---: | :----- | :--------------------- | +| < 20 | 单分组 | "全部 (15组)" | +| ≥ 20 | 按20组分组 | "第 1-20 组"、"第 21-40 组" | **分组示例**: @@ -1378,8 +877,7 @@ return QPixmap.fromImage(thumbnail_image) 用户可在配色管理面板导出配色,导出的JSON文件符合标准格式,可直接作为配色源使用。 - ---- +*** ## 9. 场景配置化规范 @@ -1421,7 +919,7 @@ scenes_data/ # 场景数据目录(打包后只读) ### 9.3 场景类型定义 -**scene_types.json**: +**scene\_types.json**: ```json { @@ -1456,14 +954,14 @@ scenes_data/ # 场景数据目录(打包后只读) **内置布局类型**: -|类型ID |描述 |效果 | -|---|---|---| -|`single` |单图模式 |一次显示一个,左右切换 | -|`scroll_v` |垂直滚动 |多个SVG垂直排列,可滚动 | -|`scroll_h` |水平滚动 |多个SVG水平排列,可滚动 | -|`grid_2x2` |2x2网格 |4个SVG网格展示 | -|`grid_3x2` |3x2网格 |6个SVG网格展示 | -|`mixed` |混合布局 |左侧2x2网格 + 右侧大图 | +| 类型ID | 描述 | 效果 | +| ---------- | ----- | -------------- | +| `single` | 单图模式 | 一次显示一个,左右切换 | +| `scroll_v` | 垂直滚动 | 多个SVG垂直排列,可滚动 | +| `scroll_h` | 水平滚动 | 多个SVG水平排列,可滚动 | +| `grid_2x2` | 2x2网格 | 4个SVG网格展示 | +| `grid_3x2` | 3x2网格 | 6个SVG网格展示 | +| `mixed` | 混合布局 | 左侧2x2网格 + 右侧大图 | ### 9.5 场景管理 @@ -1473,18 +971,9 @@ scenes_data/ # 场景数据目录(打包后只读) from core import get_scene_type_manager manager = get_scene_type_manager() - -# 获取所有场景类型 scene_types = manager.get_all_scene_types() - -# 获取场景的所有模板(内置 + 用户) templates = manager.get_all_templates("ui") - -# 获取内置SVG路径 builtin_path = manager.get_builtin_svg_path("ui") - -# 获取布局配置 -layout_config = manager.get_layout_config("ui") ``` ### 9.6 用户模板管理 @@ -1495,19 +984,9 @@ layout_config = manager.get_layout_config("ui") from core import get_config_manager config_manager = get_config_manager() - -# 获取场景的用户模板 templates = config_manager.get_scene_templates_by_type("ui") - -# 添加用户模板 -config_manager.add_scene_template("ui", { - "path": "D:/设计素材/my_ui.svg", - "name": "我的UI模板", - "added_at": "2026-02-16" -}) - -# 删除用户模板 -config_manager.remove_scene_template("ui", "D:/设计素材/my_ui.svg") +config_manager.add_scene_template("ui", {"path": "D:/my_ui.svg", "name": "我的UI模板"}) +config_manager.remove_scene_template("ui", "D:/my_ui.svg") config_manager.save() ``` @@ -1519,22 +998,22 @@ SVG模板使用语义化 `class` 命名规范,支持智能配色映射。 #### 9.7.1 元素分类依据 -|优先级 |依据 |示例 | -|:---:|---|---| -|1 |class 属性关键词 |`class="background"` → 背景 | -|2 |id 属性关键词 |`id="main-title"` → 文字 | -|3 |标签类型 + 面积 |最大矩形 → 背景 | +| 优先级 | 依据 | 示例 | +| :-: | ----------- | ------------------------- | +| 1 | class 属性关键词 | `class="background"` → 背景 | +| 2 | id 属性关键词 | `id="main-title"` → 文字 | +| 3 | 标签类型 + 面积 | 最大矩形 → 背景 | #### 9.7.2 支持的关键词 -|元素类型 |class/id 关键词 |颜色映射 | -|---|---|---| -|背景 |`background`, `bg`, `back` |配色[0] | -|主元素 |`primary`, `main` |配色[1] | -|次要元素 |`secondary`, `sub` |配色[2] | -|强调元素 |`accent`, `highlight` |配色[3] | -|文字 |`text`, `title`, `label` |配色[4] | -|描边 |`stroke`, `border`, `line` |配色[4] | +| 元素类型 | class/id 关键词 | 颜色映射 | +| ---- | -------------------------- | ------ | +| 背景 | `background`, `bg`, `back` | 配色\[0] | +| 主元素 | `primary`, `main` | 配色\[1] | +| 次要元素 | `secondary`, `sub` | 配色\[2] | +| 强调元素 | `accent`, `highlight` | 配色\[3] | +| 文字 | `text`, `title`, `label` | 配色\[4] | +| 描边 | `stroke`, `border`, `line` | 配色\[4] | #### 9.7.3 双模式映射策略 @@ -1555,15 +1034,15 @@ else: **映射规则**: -|元素类型 |使用配色索引 |说明 | -|---|---|---| -|BACKGROUND |colors[0] |背景元素 | -|PRIMARY |colors[1] |主元素 | -|SECONDARY |colors[2] |次要元素 | -|ACCENT |colors[3] |强调元素 | -|TEXT |colors[4] |文字元素 | -|STROKE |colors[4] |描边元素 | -|UNKNOWN |不映射 |保持原色 | +| 元素类型 | 使用配色索引 | 说明 | +| ---------- | ---------- | ---- | +| BACKGROUND | colors\[0] | 背景元素 | +| PRIMARY | colors\[1] | 主元素 | +| SECONDARY | colors\[2] | 次要元素 | +| ACCENT | colors\[3] | 强调元素 | +| TEXT | colors\[4] | 文字元素 | +| STROKE | colors\[4] | 描边元素 | +| UNKNOWN | 不映射 | 保持原色 | **固定颜色处理**: @@ -1597,20 +1076,11 @@ else: #### 9.7.4 SVG模板示例 ```svg - - - - - - - - - - + 标题 @@ -1618,58 +1088,31 @@ else: #### 9.7.5 创建SVG模板规范 -1. **画布尺寸**: - - 参考尺寸:400×300(适用于单图、滚动布局) - - 根据实际布局需求调整尺寸 - - 网格布局可使用较小尺寸(如 200×150) - - 混合布局可根据展示区域调整 -2. **命名规范**: - - 使用语义化的 `class` 属性 - - 使用描述性的 `id` 属性 +1. **画布尺寸**:参考尺寸 400×300(适用于单图、滚动布局);网格布局可使用较小尺寸(如 200×150) +2. **命名规范**:使用语义化的 `class` 属性;使用描述性的 `id` 属性 3. **颜色填充**:使用任意颜色,系统会自动替换 4. **导出格式**:使用"演示 SVG"或"内联样式"格式,避免外部CSS引用 -5. **固定颜色属性**: - - 使用 `data-fixed-color` 属性标记不参与配色映射的元素 - - `data-fixed-color="black"`:固定使用黑色(适用于文字) - - `data-fixed-color="original"`:保持原始颜色(适用于边框、框架等) - -**固定颜色示例**: +5. **固定颜色属性**:使用 `data-fixed-color` 属性标记不参与配色映射的元素 + - `data-fixed-color="black"`:固定使用黑色 + - `data-fixed-color="original"`:保持原始颜色 ```svg - - - + - - - - - - - - - + 首页 ``` **使用场景**: -|场景 |元素类型 |推荐属性值 |说明 | -|:---:|:---:|:---:|:---| -|手机UI |背景 |`original` |手机边框外的背景固定为白色 | -|手机UI |手机外框 |`original` |边框颜色不应随配色变化 | -|手机UI |文字 |`black` |文字应始终使用黑色确保可读性 | -|网页 |背景 |`original` |网页场景背景固定为白色 | -|网页 |文字 |`black` |导航、标题等文字固定黑色 | -|网页 |按钮文字 |`black` |按钮内文字固定黑色 | - -**容器背景处理**: - -当SVG中有元素标记 `data-fixed-color="original"` 时,预览容器会自动使用白色背景(而非配色第一个颜色),确保固定颜色的元素能够正确显示。 +| 场景 | 元素类型 | 推荐属性值 | 说明 | +| :--: | :--: | :--------: | :------------- | +| 手机UI | 手机外框 | `original` | 边框颜色不应随配色变化 | +| 手机UI | 文字 | `black` | 文字应始终使用黑色确保可读性 | +| 网页 | 背景 | `original` | 网页场景背景固定为白色 | ### 9.8 右键菜单规范 @@ -1678,8 +1121,7 @@ else: - 内置SVG:点击后提示"内置场景,不可删除" - 用户SVG:点击后执行删除 - ---- +*** ## 10. 界面布局规范 @@ -1690,158 +1132,67 @@ else: ### 10.2 防止布局重叠的规范 -**问题描述:** -当窗口被压缩(尤其是垂直方向)时,组件之间可能出现重叠,导致界面显示异常。 - -**根本原因:** +**问题描述:** 当窗口被压缩时,组件之间可能出现重叠,导致界面显示异常。 -1. 组件设置了过大的 `minimumSize`,导致无法压缩 -2. 缺少 `sizePolicy` 设置,组件无法正确响应布局变化 -3. 使用 `setFixedHeight()` 等固定尺寸方法,阻止了自动调整 +**根本原因:** 组件设置了过大的 `minimumSize`;缺少 `sizePolicy` 设置;使用 `setFixedHeight()` 等固定尺寸方法。 **解决方案:** 1. **设置合理的 minimumSize** - ```python - # 错误示例:最小尺寸过大,导致无法压缩 - self.setMinimumSize(600, 400) - - # 正确示例:根据内容设置合理的最小尺寸 - self.setMinimumSize(300, 200) + self.setMinimumSize(300, 200) # 根据内容设置合理的最小尺寸 ``` 2. **使用 sizePolicy 控制扩展行为** - ```python - from PySide6.QtWidgets import QSizePolicy - - # 允许组件在水平和垂直方向上都充分扩展和压缩 self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) - - # 色卡面板:水平扩展,垂直优先压缩 - self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred) ``` 3. **避免使用固定尺寸,使用最小/最大尺寸范围** - ```python - # 错误示例:固定高度,无法调整 - self.color_block.setFixedHeight(80) - - # 正确示例:允许在一定范围内调整 self.color_block.setMinimumHeight(40) self.color_block.setMaximumHeight(80) ``` -4. **为关键区域设置最小高度约束** - ```python - # 数值区域需要保证文字可见 - self.values_container.setMinimumHeight(60) - - # 16进制显示区域 - self.hex_container.setMinimumHeight(30) - self.hex_container.setMaximumHeight(40) - ``` -5. **QSplitter 中的组件设置** - - ```python - # 为 splitter 中的每个组件设置最小高度 - main_splitter.setMinimumHeight(400) - - # 为 splitter 中的子组件设置约束 - self.image_canvas.setMinimumHeight(200) - self.color_card_panel.setMinimumHeight(200) - ``` - -**实践:** - -- 始终为自定义组件设置 `sizePolicy` -- 最小尺寸应根据内容实际需求设置,不宜过大 -- 使用 `setMinimumHeight()` 和 `setMaximumHeight()` 组合代替 `setFixedHeight()` -- 在 QSplitter 中,为每个子组件设置合理的最小尺寸 +**实践:** 始终为自定义组件设置 `sizePolicy`;最小尺寸应根据内容实际需求设置;使用 `setMinimumHeight()` 和 `setMaximumHeight()` 组合代替 `setFixedHeight()`。 - 测试时尝试将窗口压缩到最小尺寸,检查是否有重叠 ### 10.3 控件尺寸参考 -|控件 |推荐尺寸 |紧凑尺寸 |说明 | -|:---:|:---:|:---:|:---| -|主窗口 |1095×660 |- |默认尺寸 | -|主窗口最小 |1095×600 |- |保证内容完整显示 | -|画布最小 |300×200 |300×150 |图片显示区域 | -|色卡面板最小高度 |200 |130 |保证色卡内容可见 | -|单个色卡最小高度 |160 |120 |包含色块+文字+16进制 | -|色块高度范围 |40-80 |30-80 |可压缩范围 | -|数值区域最小高度 |60 |45 |HSB/LAB等数值显示 | -|16进制区域最小高度 |30 |26 |16进制码显示区 | -|取色点半径 |12px |- |便于拖动操作 | -|按钮高度 |28-32 |24 |根据空间调整 | +| 控件 | 推荐尺寸 | 紧凑尺寸 | 说明 | +| :--------: | :------: | :-----: | :----------- | +| 主窗口 | 1095×660 | - | 默认尺寸 | +| 主窗口最小 | 1095×600 | - | 保证内容完整显示 | +| 画布最小 | 300×200 | 300×150 | 图片显示区域 | +| 色卡面板最小高度 | 200 | 130 | 保证色卡内容可见 | +| 单个色卡最小高度 | 160 | 120 | 包含色块+文字+16进制 | +| 色块高度范围 | 40-80 | 30-80 | 可压缩范围 | +| 数值区域最小高度 | 60 | 45 | HSB/LAB等数值显示 | +| 16进制区域最小高度 | 30 | 26 | 16进制码显示区 | +| 取色点半径 | 12px | - | 便于拖动操作 | +| 按钮高度 | 28-32 | 24 | 根据空间调整 | ### 10.4 网格布局自适应宽度经验 -**问题描述:** -当使用 QGridLayout 显示多行色卡,且每行色卡数量不同时(如第一行6个,第三行4个),无法实现每行独立占满整行宽度。 +**问题描述:** 当使用 QGridLayout 显示多行色卡,且每行色卡数量不同时,无法实现每行独立占满整行宽度。 -**根本原因:** +**根本原因:** QGridLayout 的所有行共享相同的列宽;不同行的列数不同会导致拉伸效果不一致。 -- QGridLayout 的所有行共享相同的列宽 -- 不同行的列数不同会导致拉伸效果不一致 -- 设置 `setColumnStretch` 只在列数相同时有效 - -**解决方案:** -改用 **垂直布局 + 水平布局** 的组合方式: +**解决方案:** 改用 **垂直布局 + 水平布局** 的组合方式: ```python -# 外层使用垂直布局 self.cards_layout = QVBoxLayout() -self.cards_layout.setSpacing(10) - -# 计算每行显示的列数 columns = self._calculate_columns(len(colors)) -# 按行创建色卡 current_row_layout = None for i, color in enumerate(colors): - # 每行开始时创建新的水平布局 if i % columns == 0: current_row_layout = QHBoxLayout() - current_row_layout.setContentsMargins(0, 0, 0, 0) - current_row_layout.setSpacing(10) self.cards_layout.addLayout(current_row_layout) - - card = ColorCard() - # ... 设置卡片属性 ... - - # 添加到当前行的水平布局,设置stretch=1使色卡均匀分布 - current_row_layout.addWidget(card, stretch=1) + current_row_layout.addWidget(ColorCard(), stretch=1) ``` -**清空布局的方法:** +**决策原则:** 每行列数相同 → 使用 QGridLayout;每行列数不同 → 使用垂直+水平组合布局。 -```python -def _clear_color_cards(self): - """清空所有色卡""" - # 清空色卡列表 - for card in self._color_cards: - card.deleteLater() - self._color_cards.clear() - - # 清空所有行布局 - while self.cards_layout.count(): - item = self.cards_layout.takeAt(0) - if item.layout(): - # 删除行布局中的所有色卡 - while item.layout().count(): - child = item.layout().takeAt(0) - if child.widget(): - child.widget().deleteLater() -``` - -**决策原则:** - -- 每行列数相同 → 使用 QGridLayout -- 每行列数不同 → 使用垂直+水平组合布局 - - ---- +*** ## 11. 交互设计规范 @@ -1853,13 +1204,12 @@ def _clear_color_cards(self): ### 11.2 快捷键规范 -|快捷键 |功能 | -|:---:|:---:| -|Ctrl + O |打开图片 | -|Ctrl + Q |退出程序 | +| 快捷键 | 功能 | +| :------: | :--: | +| Ctrl + O | 打开图片 | +| Ctrl + Q | 退出程序 | - ---- +*** ## 12. 版本管理规范 @@ -1895,8 +1245,7 @@ def _clear_color_cards(self): - `[文档] 更新 README.md 和开发规范` - `[样式] 统一图片显示区域和色环背景色为纯黑色` - ---- +*** ## 13. 更新日志格式规范 @@ -1921,13 +1270,13 @@ def _clear_color_cards(self): ### 13.2 分类说明 -|分类 |Emoji |说明 | -|:---:|:---:|:---| -|新增功能 |✨ |新功能、新特性 | -|问题修复 |🔧 |Bug修复 | -|界面优化 |🎨 |UI样式、布局调整 | -|性能提升 |⚡ |性能优化 | -|代码重构 |🏗️ |代码结构调整 | +| 分类 | Emoji | 说明 | +| :--: | :---: | :-------- | +| 新增功能 | ✨ | 新功能、新特性 | +| 问题修复 | 🔧 | Bug修复 | +| 界面优化 | 🎨 | UI样式、布局调整 | +| 性能提升 | ⚡ | 性能优化 | +| 代码重构 | 🏗️ | 代码结构调整 | **注意:** 分类可根据实际更新内容灵活调整,只需保持格式正确(`## Emoji 分类名称`)即可。 @@ -1962,8 +1311,7 @@ def _clear_color_cards(self): - 优化应用启动速度 ``` - ---- +*** ## 14. 配置管理规范 @@ -1976,17 +1324,17 @@ def _clear_color_cards(self): ### 14.2 配置项说明 -|配置键 |类型 |默认值 |说明 | -|:---:|:---:|:---:|:---| -|`settings.hex_visible` |bool |true |是否显示16进制颜色值 | -|`settings.color_modes` |list |["HSB", "LAB"] |色卡中显示的色彩模式 | -|`settings.color_sample_count` |int |5 |色彩提取采样点数量 | -|`settings.language` |string |"ZW_JT" |界面语言(ZW_JT/ZW_FT/EN_US) | -|`window.width` |int |940 |窗口宽度 | -|`window.height` |int |660 |窗口高度 | -|`window.is_maximized` |bool |false |窗口是否最大化 | -|`window.is_fullscreen` |bool |false |窗口是否全屏 | -|`favorites` |list |[] |收藏的配色列表 | +| 配置键 | 类型 | 默认值 | 说明 | +| :---------------------------: | :----: | :-------------: | :------------------------- | +| `settings.hex_visible` | bool | true | 是否显示16进制颜色值 | +| `settings.color_modes` | list | \["HSB", "LAB"] | 色卡中显示的色彩模式 | +| `settings.color_sample_count` | int | 5 | 色彩提取采样点数量 | +| `settings.language` | string | "ZW\_JT" | 界面语言(ZW\_JT/ZW\_FT/EN\_US) | +| `window.width` | int | 940 | 窗口宽度 | +| `window.height` | int | 660 | 窗口高度 | +| `window.is_maximized` | bool | false | 窗口是否最大化 | +| `window.is_fullscreen` | bool | false | 窗口是否全屏 | +| `favorites` | list | \[] | 收藏的配色列表 | ### 14.3 使用示例 @@ -1994,24 +1342,18 @@ def _clear_color_cards(self): from core import get_config_manager config_manager = get_config_manager() -config = config_manager.load() - -# 获取配置项 hex_visible = config_manager.get('settings.hex_visible', True) - -# 设置配置项 config_manager.set('settings.hex_visible', False) config_manager.save() ``` - ---- +*** ## 15. 项目官网维护规范 ### 15.1 官网结构 -**官网地址:** https://qingshangongzai.github.io/Color_Card/ +**官网地址:** **目录结构:** @@ -2045,20 +1387,8 @@ website/ # 官网开发目录 "version": "v1.4.1", "date": "2026-03-02", "changes": [ - { - "category": "问题修复", - "items": ["修复问题描述"] - }, - { - "category": "界面优化", - "items": ["优化描述"] - }, - { - "category": "新增功能", - "items": [ - {"title": "功能名称", "desc": "功能描述"} - ] - } + {"category": "问题修复", "items": ["修复问题描述"]}, + {"category": "新增功能", "items": [{"title": "功能名称", "desc": "功能描述"}]} ] } ] @@ -2067,75 +1397,43 @@ website/ # 官网开发目录 **字段说明:** -|字段 |类型 |说明 | -|:---|:---:|:---| -|`version` |string |版本号 | -|`date` |string |发布日期(YYYY-MM-DD) | -|`changes` |array |更新内容列表 | -|`category` |string |分类名称(问题修复/界面优化/新增功能等) | -|`items` |array |条目列表,简单条目用字符串,复杂条目用对象 | - -**维护要求:** +| 字段 | 类型 | 说明 | +| :--------- | :----: | :-------------------- | +| `version` | string | 版本号 | +| `date` | string | 发布日期(YYYY-MM-DD) | +| `changes` | array | 更新内容列表 | +| `category` | string | 分类名称(问题修复/界面优化/新增功能等) | +| `items` | array | 条目列表,简单条目用字符串,复杂条目用对象 | -- 发布新版本时同步更新 `changelog.json` -- 保持两个文件同步(docs 和 website/public) -- 分类名称与第13章更新日志分类保持一致 - -**同步命令:** - -```bash -Copy-Item website\public\changelog.json docs\changelog.json -Force -``` +**维护要求:** 发布新版本时同步更新 `changelog.json`;保持两个文件同步(docs 和 website/public);分类名称与第13章更新日志分类保持一致。 ### 15.3 截图更新 -**截图存放位置:** - -- `docs/screenshots/` -- `website/public/screenshots/` +**截图存放位置:** `docs/screenshots/` 和 `website/public/screenshots/` -**截图尺寸建议:** +**截图尺寸建议:** 宽度 1200-1920px;格式 PNG;命名使用小写英文字母,如 `color-extract.png` -- 宽度:1200-1920px -- 格式:PNG -- 命名:使用小写英文字母,如 `color-extract.png` - -**更新步骤:** - -1. 替换 `website/public/screenshots/` 中的图片 -2. 同步复制到 `docs/screenshots/` -3. 刷新网页查看效果 +**更新步骤:** 替换 `website/public/screenshots/` 中的图片;同步复制到 `docs/screenshots/`;刷新网页查看效果。 ### 15.4 部署流程 -**自动部署:** - -- 推送代码到 GitHub 后自动触发 GitHub Actions 部署 -- 部署源:`docs/` 目录 +**自动部署:** 推送代码到 GitHub 后自动触发 GitHub Actions 部署;部署源:`docs/` 目录 **手动同步文件:** ```bash -# 同步更新日志 Copy-Item website\public\changelog.json docs\changelog.json -Force - -# 同步截图 Copy-Item website\public\screenshots\* docs\screenshots\ -Force - -# 同步配色数据(如有更新) -Copy-Item color_data\* docs\palettes\ -Force ``` - ### 15.5 注意事项 -- JSON 文件中避免使用中文引号 `"` `"` +- JSON 文件中避免使用中文引号 - 截图文件不要过大,建议压缩后上传 - 更新日志保持简洁,突出重点 - 确保两个目录的文件保持同步 - ---- +*** ## 16. 多语言国际化规范 @@ -2145,46 +1443,31 @@ Copy-Item color_data\* docs\palettes\ -Force **核心组件:** -|组件 |说明 | -|:---:|:---| -|`LocaleManager` |多语言管理器,负责语言包加载、切换和翻译文本获取 | -|`tr()` |翻译文本获取函数 | -|`set_language()` |语言切换函数 | -|`get_locale_manager()` |获取全局语言管理器实例 | +| 组件 | 说明 | +| :--------------------: | :----------------------- | +| `LocaleManager` | 多语言管理器,负责语言包加载、切换和翻译文本获取 | +| `tr()` | 翻译文本获取函数 | +| `set_language()` | 语言切换函数 | +| `get_locale_manager()` | 获取全局语言管理器实例 | -### 15.2 语言包结构 +### 16.2 语言包结构 语言包存放在 `locales/` 目录,使用 JSON 格式。 -**语言包格式:** - ```json { - "app": { - "name": "取色卡", - "name_en": "Color Card" - }, - "navigation": { - "color_extract": "色彩提取", - "settings": "设置" - }, - "messages": { - "copy_success": { - "title": "已复制", - "content": "颜色值已复制到剪贴板" - } - } + "app": {"name": "取色卡", "name_en": "Color Card"}, + "navigation": {"color_extract": "色彩提取", "settings": "设置"}, + "messages": {"copy_success": {"title": "已复制", "content": "颜色值已复制到剪贴板"}} } ``` -### 15.3 使用方法 +### 16.3 使用方法 **基本翻译:** ```python from utils import tr - -# 获取翻译文本 text = tr('navigation.color_extract') # 返回 "色彩提取" 或 "Color Extract" ``` @@ -2193,7 +1476,6 @@ text = tr('navigation.color_extract') # 返回 "色彩提取" 或 "Color Extrac ```python # 语言包中定义: "content": "颜色值 {value} 已复制到剪贴板" text = tr('messages.copy_success.content', value='#FF5733') -# 返回 "颜色值 #FF5733 已复制到剪贴板" ``` **语言切换:** @@ -2201,58 +1483,46 @@ text = tr('messages.copy_success.content', value='#FF5733') ```python from utils import set_language, get_locale_manager -# 切换语言 set_language('EN_US') - -# 监听语言切换信号 get_locale_manager().language_changed.connect(self._on_language_changed) ``` -### 15.4 界面国际化规范 - -**实现语言切换支持:** +### 16.4 界面国际化规范 ```python class MyInterface(QWidget): def __init__(self, parent=None): super().__init__(parent) - # 连接语言切换信号 get_locale_manager().language_changed.connect(self._on_language_changed) def _on_language_changed(self, language_code): - """语言切换回调""" self.update_texts() def update_texts(self): - """更新界面文本""" self.title_label.setText(tr('my_interface.title')) - self.button.setText(tr('my_interface.button')) ``` **翻译键命名规范:** -|模块 |命名格式 |示例 | -|:---:|:---|:---| -|应用通用 |`app.*` |`app.name` | -|导航栏 |`navigation.*` |`navigation.color_extract` | -|界面模块 |`{module}.*` |`settings.title` | -|提示消息 |`messages.*` |`messages.copy_success.title` | -|对话框 |`dialogs.{dialog}.*` |`dialogs.about.title` | -|通用文本 |`common.*` |`common.delete` | +| 模块 | 命名格式 | 示例 | +| :--: | :------------------- | :---------------------------- | +| 应用通用 | `app.*` | `app.name` | +| 导航栏 | `navigation.*` | `navigation.color_extract` | +| 界面模块 | `{module}.*` | `settings.title` | +| 提示消息 | `messages.*` | `messages.copy_success.title` | ### 15.5 支持的语言 -|语言代码 |语言名称 | -|:---:|:---| -|`ZW_JT` |简体中文(默认) | -|`ZW_FT` |繁體中文 | -|`EN_US` |English | -|`JA_JP` |日本語 | -|`FR_FR` |Français | -|`RU_RU` |Русский | +| 语言代码 | 语言名称 | +| :-----: | :------- | +| `ZW_JT` | 简体中文(默认) | +| `ZW_FT` | 繁體中文 | +| `EN_US` | English | +| `JA_JP` | 日本語 | +| `FR_FR` | Français | +| `RU_RU` | Русский | - ---- +*** ## 17. 日志系统规范 @@ -2262,40 +1532,30 @@ class MyInterface(QWidget): **核心组件:** -|组件 |文件 |职责 | -|:---:|:---:|:---| -|`LoggerManager` |`core/logger.py` |日志管理器,统一配置和控制 | -|`get_logger()` |`core/logger.py` |获取模块级日志记录器 | -|`log_user_action()` |`core/logger.py` |记录用户操作 | -|`log_performance()` |`core/logger.py` |记录性能数据 | +| 组件 | 文件 | 职责 | +| :-----------------: | :--------------: | :------------ | +| `LoggerManager` | `core/logger.py` | 日志管理器,统一配置和控制 | +| `get_logger()` | `core/logger.py` | 获取模块级日志记录器 | +| `log_user_action()` | `core/logger.py` | 记录用户操作 | +| `log_performance()` | `core/logger.py` | 记录性能数据 | ### 17.2 日志配置 -**存储位置:** - -- 日志存储在配置目录下的 `logs/` 子目录 -- 路径:`~/.color_card/logs/` +**存储位置:** 日志存储在配置目录下的 `logs/` 子目录,路径:`~/.color_card/logs/` -**文件命名:** +**文件命名:** 格式 `Color Card_版本号_YYYYMMDD_HHMMSS.log`;每次启动应用创建一个新的日志文件 -- 格式:`Color Card_版本号_YYYYMMDD_HHMMSS.log` -- 示例:`Color Card_1.4.0_20260302_143025.log` -- 每次启动应用创建一个新的日志文件 - -**清理策略:** - -- 保留天数:保留最近 30 天的日志文件 -- 启动时自动清理过期日志 +**清理策略:** 保留最近 30 天的日志文件;启动时自动清理过期日志 ### 17.3 日志级别定义 -|级别 |用途 |示例 | -|:---:|:---|:---| -|`DEBUG` |开发调试信息 |函数调用、变量值、内部状态 | -|`INFO` |关键操作记录 |打开图片、提取颜色、保存配置 | -|`WARNING` |非致命异常 |文件不存在但可恢复、配置项缺失使用默认值 | -|`ERROR` |需要关注的问题 |保存失败、解析错误、服务初始化失败 | -|`CRITICAL` |致命错误 |程序无法继续运行 | +| 级别 | 用途 | 示例 | +| :--------: | :------ | :------------------- | +| `DEBUG` | 开发调试信息 | 函数调用、变量值、内部状态 | +| `INFO` | 关键操作记录 | 打开图片、提取颜色、保存配置 | +| `WARNING` | 非致命异常 | 文件不存在但可恢复、配置项缺失使用默认值 | +| `ERROR` | 需要关注的问题 | 保存失败、解析错误、服务初始化失败 | +| `CRITICAL` | 致命错误 | 程序无法继续运行 | ### 17.4 日志格式 @@ -2304,10 +1564,8 @@ class MyInterface(QWidget): ``` 示例: - ```text [2026-02-28 14:30:25] [INFO] [color_service] 提取主色调: 图片=photo.jpg, 颜色数量=5 -[2026-02-28 14:30:26] [WARNING] [config] 配置项缺失,使用默认值: key=settings.theme, default=auto [2026-02-28 14:30:30] [ERROR] [image_service] 图片加载失败: path=invalid.jpg, error=文件不存在 ``` @@ -2322,13 +1580,13 @@ logger = get_logger("color_service") class ColorService: def extract_colors(self, image_path: str, count: int = 5): - logger.info(f"开始提取主色调: path={image_path}, count={count}") + logger.info(f"开始提取主色调: path={image_path}") try: colors = self._do_extract(image_path, count) - logger.info(f"主色调提取完成: 提取到 {len(colors)} 个颜色") + logger.info(f"主色调提取完成: {len(colors)} 个颜色") return colors except Exception as e: - logger.error(f"主色调提取失败: error={str(e)}") + logger.error(f"主色调提取失败: {str(e)}") raise ``` @@ -2337,14 +1595,7 @@ class ColorService: ```python from core import log_user_action -def _on_open_image(self): - file_path = self._show_file_dialog() - if file_path: - log_user_action( - action="open_image", - params={"path": file_path, "source": "color_extract"}, - result="success" - ) +log_user_action(action="open_image", params={"path": file_path}, result="success") ``` **性能记录:** @@ -2352,48 +1603,25 @@ def _on_open_image(self): ```python from core import log_performance -def generate_scheme(base_hue: float, scheme_type: str): - with log_performance("generate_scheme", {"type": scheme_type, "hue": base_hue}): - return _do_generate_scheme(base_hue, scheme_type) +with log_performance("generate_scheme", {"type": scheme_type}): + return _do_generate_scheme(base_hue, scheme_type) ``` ### 17.6 必须记录的场景 -**系统事件:** - -- 程序启动/退出 -- 配置加载/保存 -- 主题切换 -- 语言切换 +**系统事件:** 程序启动/退出;配置加载/保存;主题切换;语言切换 -**用户操作:** +**用户操作:** 打开/关闭图片;提取颜色;生成配色方案;保存/删除收藏;导入/导出配色 -- 打开/关闭图片 -- 提取颜色(色彩/明度/渐变) -- 生成配色方案 -- 保存/删除收藏 -- 导入/导出配色 - -**错误和异常:** - -- 文件操作失败 -- 图片解析错误 -- 配置读写错误 -- 服务初始化失败 +**错误和异常:** 文件操作失败;图片解析错误;配置读写错误;服务初始化失败 ### 17.7 AI辅助调试 -**日志查看流程:** - -1. 应用程序启动失败时,查看控制台输出定位问题 -2. 功能执行出现异常时,分析错误信息 -3. 结合代码上下文,理解错误原因 -4. 根据日志信息制定修复方案 +**日志查看流程:** 应用程序启动失败时,查看控制台输出定位问题;功能执行出现异常时,分析错误信息;结合代码上下文,理解错误原因;根据日志信息制定修复方案。 **日志文件位置:** `~/.color_card/logs/color_card.log` - ---- +*** ## 18. 重构规范 @@ -2404,17 +1632,9 @@ def generate_scheme(base_hue: float, scheme_type: str): 3. **先整理后重构**:先规范现有代码,再提取基类,最后重组目录 4. **代码清理同步**:修改代码时同步清理相关重复代码 -### 17.2 基类提取规范 - -**步骤:** +### 18.2 基类提取规范 -1. 分析相似组件的公共逻辑 -2. 设计基类接口(抽象方法) -3. 迁移第一个子类,验证基类设计 -4. 迁移其他子类 -5. 全面测试验证 - -**示例:** +**步骤:** 分析相似组件的公共逻辑;设计基类接口(抽象方法);迁移第一个子类,验证基类设计;迁移其他子类;全面测试验证。 ```python # 基类定义 @@ -2426,26 +1646,16 @@ class BaseCanvas(QWidget): # 子类实现 class ImageCanvas(BaseCanvas): def _draw_overlay(self, painter: QPainter): - """绘制取色点""" # 具体实现... ``` -### 17.3 模块合并规范 - -**合并原则:** +### 18.3 模块合并规范 -- 将紧密相关的类合并到同一文件 -- 避免过度拆分,提高代码可维护性 -- 保持模块化,但减少文件数量 - -**示例:** +**合并原则:** 将紧密相关的类合并到同一文件;避免过度拆分,提高代码可维护性;保持模块化,但减少文件数量。 -- `cards.py`:合并 ColorCard、LuminanceCard 及相关基类 -- `histograms.py`:合并 LuminanceHistogramWidget、RGBHistogramWidget 及基类 -- `canvases.py`:合并 BaseCanvas、ImageCanvas、LuminanceCanvas +**示例:** `cards.py` 合并 ColorCard、LuminanceCard 及相关基类;`histograms.py` 合并直方图组件及基类;`canvases.py` 合并画布组件及基类。 - ---- +*** ## 19. 开发经验总结 @@ -2461,8 +1671,7 @@ class ImageCanvas(BaseCanvas): - 配置数据迁移实践 - 内置色彩多段显示经验 - ---- +*** ## 20. 测试规范 @@ -2522,11 +1731,11 @@ pip install -r tests/requirements-dev.txt ### 20.4 测试命名规范 -|类型 |命名规则 |示例 | -|---|---|---| -|测试文件 |`test_*.py` |`test_threading.py` | -|测试类 |`Test*` |`TestHistogramCalculator` | -|测试方法 |`test_*` |`test_cancel_does_not_block` | +| 类型 | 命名规则 | 示例 | +| ---- | ----------- | ---------------------------- | +| 测试文件 | `test_*.py` | `test_threading.py` | +| 测试类 | `Test*` | `TestHistogramCalculator` | +| 测试方法 | `test_*` | `test_cancel_does_not_block` | ### 20.5 测试分类标记 @@ -2537,90 +1746,43 @@ import pytest @pytest.mark.unit def test_color_conversion(): - """单元测试:颜色转换""" - pass - -@pytest.mark.integration -def test_service_integration(): - """集成测试:服务集成""" pass @pytest.mark.slow def test_large_image_processing(): - """慢速测试:大图片处理""" - pass - -@pytest.mark.ui -def test_dialog_interaction(qtbot): - """UI 测试:对话框交互""" pass ``` 运行特定标记的测试: ```bash -# 只运行单元测试 -python -m pytest -m unit - -# 跳过慢速测试 -python -m pytest -m "not slow" - -# 只运行 UI 测试 -python -m pytest -m ui +python -m pytest -m unit # 只运行单元测试 +python -m pytest -m "not slow" # 跳过慢速测试 ``` ### 20.6 Qt 测试规范 -#### 20.6.1 使用 pytest-qt - -pytest-qt 提供了 `qtbot` fixture 用于 Qt 组件测试: +**使用 pytest-qt:** pytest-qt 提供了 `qtbot` fixture 用于 Qt 组件测试。 ```python def test_button_click(qtbot): - """测试按钮点击""" button = QPushButton("Click me") qtbot.addWidget(button) - - # 点击按钮 qtbot.mouseClick(button, Qt.LeftButton) ``` -#### 20.6.2 等待信号 - -使用 `qtbot.waitSignal` 等待异步信号: +**等待信号:** 使用 `qtbot.waitSignal` 等待异步信号。 ```python def test_async_calculation(qtbot): - """测试异步计算""" calculator = HistogramCalculator(image, "luminance") - - # 等待 finished 信号 with qtbot.waitSignal(calculator.finished, timeout=5000) as blocker: calculator.start() - - # 验证结果 - result = blocker.args[0] - assert len(result) == 256 -``` - -#### 20.6.3 等待窗口显示 - -```python -def test_dialog_show(qtbot): - """测试对话框显示""" - dialog = MyDialog() - dialog.show() - - # 等待窗口显示 - qtbot.waitForWindowShown(dialog) - - assert dialog.isVisible() + assert len(blocker.args[0]) == 256 ``` ### 20.7 线程安全测试 -#### 20.7.1 测试取消机制 - ```python def test_cancel_does_not_block(qtbot): """测试 cancel() 不阻塞调用线程""" @@ -2632,46 +1794,17 @@ def test_cancel_does_not_block(qtbot): calculator.cancel() elapsed = time.time() - start_time - # cancel 应该在 10ms 内返回 - assert elapsed < 0.01 + assert elapsed < 0.01 # cancel 应该在 10ms 内返回 calculator.wait(1000) ``` -#### 20.7.2 测试线程安全退出 - -```python -def test_safe_cancel_without_terminate(qtbot): - """测试不使用 terminate() 也能正常取消""" - calculator = HistogramCalculator(image, "luminance") - calculator.start() - - calculator.cancel() - calculator.wait(1000) - - # 线程应该已停止 - assert not calculator.isRunning() -``` - ### 20.8 运行测试 ```bash -# 运行所有测试 -python -m pytest - -# 运行特定文件 -python -m pytest tests/test_threading.py - -# 运行特定测试 -python -m pytest tests/test_threading.py::TestHistogramCalculator::test_cancel_does_not_block - -# 显示更详细的输出 -python -m pytest -vv - -# 在第一个失败时停止 -python -m pytest -x - -# 生成覆盖率报告(需要 pytest-cov) -python -m pytest --cov=core --cov-report=html +python -m pytest # 运行所有测试 +python -m pytest tests/test_threading.py # 运行特定文件 +python -m pytest -x # 在第一个失败时停止 +python -m pytest --cov=core --cov-report=html # 生成覆盖率报告 ``` ### 20.9 测试最佳实践 @@ -2679,12 +1812,10 @@ python -m pytest --cov=core --cov-report=html 1. **测试独立性**:每个测试应该独立运行,不依赖其他测试 2. **测试可重复性**:相同输入应该产生相同结果 3. **测试命名清晰**:测试名称应描述测试内容 -4. **一个测试一个断言**:每个测试只验证一个行为 -5. **使用 fixture 复用代码**:提取公共设置代码到 fixture -6. **Mock 外部依赖**:使用 `unittest.mock` 隔离外部依赖 +4. **使用 fixture 复用代码**:提取公共设置代码到 fixture +5. **Mock 外部依赖**:使用 `unittest.mock` 隔离外部依赖 - ---- +*** ## 21. 开源许可证管理规范 @@ -2694,55 +1825,42 @@ python -m pytest --cov=core --cov-report=html **涉及的许可证类型:** -|类型 |许可证 |用途 | -|:---:|:---:|:---:| -|主项目 |GPLv3 |取色卡项目本身 | -|第三方库 |LGPL-3.0 |PySide6 | -|第三方库 |GPLv3 |PySide6-Fluent-Widgets | -|第三方库 |MIT |Pillow | -|第三方库 |Apache-2.0 |requests | -|第三方库 |BSD-3-Clause |numpy | -|第三方库 |MIT |Open Color | -|第三方库 |MIT |Tailwind CSS Colors | -|第三方库 |MIT |Nice Color Palettes | -|工具链 |MIT |auto-py-to-exe | -|工具链 |GPL-2.0+ with Bootloader Exception |PyInstaller | -|工具链 |GPLv2+ |UPX | -|工具链 |Modified BSD |Inno Setup | +| 类型 | 许可证 | 用途 | +| :--: | :--------------------------------: | :--------------------: | +| 主项目 | GPLv3 | 取色卡项目本身 | +| 第三方库 | LGPL-3.0 | PySide6 | +| 第三方库 | GPLv3 | PySide6-Fluent-Widgets | +| 第三方库 | MIT | Pillow | +| 第三方库 | Apache-2.0 | requests | +| 第三方库 | BSD-3-Clause | numpy | +| 第三方库 | MIT | Open Color | +| 第三方库 | MIT | Tailwind CSS Colors | +| 第三方库 | MIT | Nice Color Palettes | +| 工具链 | MIT | auto-py-to-exe | +| 工具链 | GPL-2.0+ with Bootloader Exception | PyInstaller | +| 工具链 | GPLv2+ | UPX | +| 工具链 | Modified BSD | Inno Setup | ### 21.2 许可证文件管理 **必须维护的许可证文件:** -1. **LICENSE** - 主许可证文本文件 - - 包含完整的 GPLv3 许可证文本 - - 包含所有第三方库的完整许可证信息 - - 包含开发工具链的完整许可证信息 - - 使用统一的文本格式(`====` 和 `----` 分隔线) -2. **file/LICENSE.html** - HTML 格式的许可证文件 - - 用于应用程序内显示 - - 包含格式化的 HTML 样式 - - 与文本版 LICENSE 内容保持一致 +1. **LICENSE** - 主许可证文本文件:包含完整的 GPLv3 许可证文本;包含所有第三方库的完整许可证信息;包含开发工具链的完整许可证信息 +2. **file/LICENSE.html** - HTML 格式的许可证文件:用于应用程序内显示;与文本版 LICENSE 内容保持一致 ### 21.3 第三方库许可证信息收集规范 **收集内容清单:** -|信息项 |说明 |示例 | -|:---:|:---|:---| -|库名称 |完整的库名称 |PySide6 | -|版本要求 |项目使用的版本范围 |>=6.0.0 | -|许可证类型 |SPDX 标识符 |LGPL-3.0 | -|版权所有 |作者或组织名称 |The Qt Company | -|项目地址 |官方网站或仓库地址 |https://www.qt.io/ | -|完整许可证文本 |官方许可证全文 |从官方网站获取 | - -**收集渠道:** +| 信息项 | 说明 | 示例 | +| :-----: | :-------- | :------------------- | +| 库名称 | 完整的库名称 | PySide6 | +| 版本要求 | 项目使用的版本范围 | >=6.0.0 | +| 许可证类型 | SPDX 标识符 | LGPL-3.0 | +| 版权所有 | 作者或组织名称 | The Qt Company | +| 项目地址 | 官方网站或仓库地址 | | -- 官方 GitHub 仓库的 LICENSE 文件 -- 官方网站许可证页面 -- PyPI 项目页面的元数据 -- 源码包中的 LICENSE 文件 +**收集渠道:** 官方 GitHub 仓库的 LICENSE 文件;官方网站许可证页面;PyPI 项目页面的元数据;源码包中的 LICENSE 文件。 ### 21.4 许可证文本格式规范 @@ -2759,22 +1877,12 @@ python -m pytest --cov=core --cov-report=html -------------------------------------------------------------------------------- 版权所有:作者/组织名称 项目地址:https://example.com/ -许可证:完整许可证名称 -------------------------------------------------------------------------------- [完整的许可证文本] - -================================================================================ -2. 下一个库... ``` -**格式要求:** - -- 使用 `====`(80个字符)作为章节分隔线 -- 使用 `----`(80个字符)作为子章节分隔线 -- 每个库独立成节,编号排序 -- 许可证文本保持原始格式,不修改内容 -- LGPL 许可证需注明引用 GPL 的条款 +**格式要求:** 使用 `====`(80个字符)作为章节分隔线;使用 `----`(80个字符)作为子章节分隔线;每个库独立成节,编号排序;许可证文本保持原始格式。 ### 21.5 关于窗口许可证显示规范 @@ -2788,98 +1896,56 @@ def _get_about_text(self): 版权所有:The Qt Company 许可证:LGPL v3 官网:https://www.qt.io/ - - • 本程序 UI 组件使用 PySide6-Fluent-Widgets - 版权所有:zhiyiYo - 许可证:GPLv3 - 项目地址:https://github.com/zhiyiYo/PyQt-Fluent-Widgets 【开发工具链】 • 本程序使用 auto-py-to-exe 工具打包 版权所有:Brent Vollebregt 许可证:MIT - 项目地址:https://github.com/brentvollebregt/auto-py-to-exe """ ``` -**显示要求:** - -- 每个库必须包含:版权所有、许可证类型、项目地址 -- 按类别分组(开源项目、开发工具链) -- 格式统一,便于阅读 +**显示要求:** 每个库必须包含:版权所有、许可证类型、项目地址;按类别分组(开源项目、开发工具链);格式统一,便于阅读。 ### 21.6 许可证兼容性检查 **兼容性原则:** -|主许可证 |兼容的许可证 |不兼容的许可证 | -|:---:|:---:|:---:| -|GPLv3 |LGPL-3.0, MIT, Apache-2.0, BSD |专有许可证 | +| 主许可证 | 兼容的许可证 | 不兼容的许可证 | +| :---: | :----------------------------: | :-----: | +| GPLv3 | LGPL-3.0, MIT, Apache-2.0, BSD | 专有许可证 | -**检查清单:** - -- [ ] 所有第三方许可证与 GPLv3 兼容 -- [ ] 所有许可证文本完整且最新 -- [ ] 所有版权信息准确无误 -- [ ] 所有项目链接可访问 - - +**检查清单:** 所有第三方许可证与 GPLv3 兼容;所有许可证文本完整且最新;所有版权信息准确无误;所有项目链接可访问。 ### 21.7 许可证更新流程 **新增第三方库时的步骤:** -1. **信息收集** - - 收集库名称、版本、许可证类型 - - 获取版权所有者信息 - - 获取项目地址 - - 下载完整许可证文本 -2. **兼容性验证** - - 确认许可证与 GPLv3 兼容 - - 检查许可证版本是否最新 -3. **文件更新** - - 更新 LICENSE 文本文件 - - 更新 file/LICENSE.html - - 更新关于窗口的 `_get_about_text()` -4. **验证测试** - -- 检查文本格式是否正确 -- 验证链接可访问性 -- 确认所有文件内容一致 +1. **信息收集**:收集库名称、版本、许可证类型;获取版权所有者信息;获取项目地址;下载完整许可证文本 +2. **兼容性验证**:确认许可证与 GPLv3 兼容;检查许可证版本是否最新 +3. **文件更新**:更新 LICENSE 文本文件;更新 file/LICENSE.html;更新关于窗口的许可证显示 +4. **验证测试**:检查文本格式是否正确;验证链接可访问性;确认所有文件内容一致 ### 21.8 常见许可证文本获取地址 -|许可证 |官方地址 | -|:---:|:---| -|GPLv3 |https://www.gnu.org/licenses/gpl-3.0.txt | -|LGPLv3 |https://www.gnu.org/licenses/lgpl-3.0.txt | -|MIT |https://opensource.org/licenses/MIT | -|Apache-2.0 |https://www.apache.org/licenses/LICENSE-2.0.txt | -|BSD-3-Clause |https://opensource.org/licenses/BSD-3-Clause | -|GPLv2 |https://www.gnu.org/licenses/gpl-2.0.txt | +| 许可证 | 官方地址 | +| :----------: | :------------------------------------------------ | +| GPLv3 | | +| LGPLv3 | | +| MIT | | +| Apache-2.0 | | +| BSD-3-Clause | | ### 21.9 注意事项 **必须避免的问题:** -1. **不要遗漏版权声明** - - 每个第三方库都必须有明确的版权声明 - - 不能只列出库名和许可证类型 -2. **不要修改许可证文本** - - 保持许可证文本的原始内容 - - 不要删除或添加任何内容 -3. **不要遗漏工具链** - - 打包工具、压缩工具、安装程序制作工具都需要声明 - - 这些工具的许可证同样需要完整列出 -4. **保持格式一致** - - LICENSE 和 LICENSE.html 内容要一致 - - 使用统一的格式风格 -5. **LGPL 特殊处理** - - LGPL 是 GPL 的补充,需要明确说明引用关系 - - 在 LGPL 章节开头说明其引用了 GPL 的条款 - - ---- +1. **不要遗漏版权声明**:每个第三方库都必须有明确的版权声明 +2. **不要修改许可证文本**:保持许可证文本的原始内容 +3. **不要遗漏工具链**:打包工具、压缩工具、安装程序制作工具都需要声明 +4. **保持格式一致**:LICENSE 和 LICENSE.html 内容要一致 +5. **LGPL 特殊处理**:LGPL 是 GPL 的补充,需要明确说明引用关系 + +*** ## 22. 附录 @@ -2891,71 +1957,71 @@ def _get_about_text(self): **(变更内容填写对开发规范的内容更新,而不是对代码调整的详细记录)** -|版本 |日期 |变更内容 | -|:---:|:---:|:---:| -|3.40 |2026-03-07 |更新项目结构(1.3节):新增 export_settings_dialog.py 到 dialogs/ 目录 | -|3.39 |2026-03-04 |新增资源路径规范(2.4节):说明 PyInstaller 打包后的资源路径获取方式,避免打包后资源加载失败 | -|3.38 |2026-03-04 |更新主题自适应组件实现规范(3.7.3节):新增注意事项,避免在 _update_styles 中调用 setStyleSheet 设置容器样式 | -|3.37 |2026-03-04 |更新changelog.json数据格式说明(15.2节),与实际结构保持一致 | -|3.36 |2026-03-04 |精简更新日志格式规范(第13章) | -|3.35 |2026-03-02 |优化更新日志分类规范:新增分类说明,明确分类可灵活调整;新增"功能优化"分类;添加灵活调整示例说明 | -|3.34 |2026-02-28 |新增日志系统,更新项目结构和开发规范(新增第17章日志系统规范) | -|3.33 |2026-02-28 |新增第20章测试规范;更新章节编号(原第20章开源许可证管理规范改为第21章,原第21章附录改为第22章);新增线程取消机制规范(7.3节) | -|3.32 |2026-02-27 |更新Zone分区规范(6.3节):将直方图分区从8个改为9个(Zone 0-8) | -|3.31 |2026-02-26 |更新SVG智能映射规则说明(9.7.3节):新增覆盖检测功能说明 | -|3.30 |2026-02-26 |新增渐变提取功能规范;更新项目结构和开发规范 | -|3.29 |2026-02-24 |新增第15章项目官网维护规范;更新项目结构说明 | -|3.28 |2026-02-22 |新增类型注解规范(3.7节) | -|3.27 |2026-02-22 |新增服务工厂规范(3.3.1节);更新项目结构说明 | -|3.26 |2026-02-21 |移动 theme_colors.py 到 utils/ | -|3.25 |2026-02-20 |新增缓存基类规范;更新项目结构说明 | -|3.24 |2026-02-20 |新增配色计算缓存规范;更新项目结构说明 | -|3.23 |2026-02-19 |新增第16章多语言国际化规范;更新项目结构说明 | -|3.22 |2026-02-18 |新增直方图计算服务规范;更新项目结构说明 | -|3.21 |2026-02-18 |新增预览场景管理服务规范;更新项目结构说明 | -|3.20 |2026-02-18 |新增图片加载服务和明度计算服务规范;更新项目结构说明 | -|3.19 |2026-02-18 |新增配色导入导出服务规范;更新项目结构说明 | -|3.18 |2026-02-18 |新增颜色提取服务规范;更新项目结构说明 | -|3.17 |2026-02-18 |更新SVG智能映射规则说明(9.7.3节) | -|3.16 |2026-02-18 |更新SVG智能映射规则说明(9.7.3节) | -|3.15 |2026-02-18 |更新SVG智能映射规则说明(9.7.3节) | -|3.14 |2026-02-18 |更新SVG映射策略规范(9.7.3节):新增双模式映射策略说明 | -|3.13 |2026-02-18 |更新项目结构说明 | -|3.12 |2026-02-18 |更新项目结构说明 | -|3.11 |2026-02-18 |更新SVG映射策略规范(9.7.3节):明确背景元素检测标准 | -|3.10 |2026-02-17 |更新项目结构说明 | -|3.9 |2026-02-17 |更新项目结构说明 | -|3.8 |2026-02-17 |更新项目结构说明 | -|3.7 |2026-02-17 |新增图片状态中介者规范(5.3.1节);更新项目结构说明 | -|3.6 |2026-02-17 |更新项目结构说明 | -|3.5 |2026-02-17 |新增分阶段图片加载规范(7.3节) | -|3.4 |2026-02-17 |新增异步加载基类规范;更新项目结构说明 | -|3.3 |2026-02-16 |新增SVG固定颜色属性规范:支持 `data-fixed-color` 属性标记不参与配色映射的元素,添加使用示例和场景推荐(第9.7.5节) | -|3.2 |2026-02-16 |更新场景配置化规范(第9章):采用纯SVG模板模式,新增布局系统说明 | -|3.1 |2026-02-16 |新增第8章配色数据JSON格式规范:说明统一的 palettes 格式、groups 字段、社区贡献指南;章节序号顺延调整 | -|3.0 |2026-02-13 |文档重构:拆分为核心原则、完整版、经验总结三个独立文档;合并第9章和第10章布局规范;第14章开发经验总结独立为 `开发经验总结.md` | -|2.21 |2026-02-11 |新增网格布局自适应宽度经验(第10章) | -|2.20 |2026-02-11 |更新场景类型规范(第9章):新增四个配置化场景 | -|2.19 |2026-02-11 |新增SVG智能配色映射规范(9.7节);更新项目结构说明 | -|2.18 |2026-02-10 |新增场景配置化规范(第9章);更新项目结构说明 | -|2.17 |2026-02-10 |重命名"配色方案"为"配色生成","色彩管理"为"配色管理"更新所有相关引用、导航栏显示和文档 | -|2.16 |2026-02-10 |新增配色预览功能规范;更新项目结构说明 | -|2.15 |2026-02-09 |新增应用启动优化策略规范(7.3节) | -|2.14 |2026-02-09 |重命名"色卡收藏"面板为"色彩管理",更新所有相关引用和导航栏显示 | -|2.13 |2026-02-09 |更新配色方案算法设计规范(14.1.1节) | -|2.12 |2026-02-09 |更新色轮交互设计规范(14.1.4节) | -|2.11 |2026-02-08 |新增第21章开源许可证管理规范 | -|2.10 |2026-02-07 |新增信号循环预防规范(5.3节)、QThread取消机制(7.3节) | -|2.9 |2026-02-07 |新增主题颜色管理规范(5.5节) | -|2.8 |2026-02-06 |新增工具栏/按钮容器间距规范,补充 QSplitter 分隔条样式注意事项 | -|2.7 |2026-02-06 |更新控件尺寸参考表(10.3节),完善布局压缩最佳实践 | -|2.6 |2026-02-06 |新增防止布局重叠的规范(10.2节);更新控件尺寸参考表 | -|2.5 |2026-02-06 |新增收藏功能规范;新增MessageBox使用规范、配置数据迁移实践 | -|2.4 |2026-02-06 |新增布局设计最佳实践经验 | -|2.3 |2026-02-06 |新增配色生成功能规范;更新项目结构说明 | -|2.2 |2026-02-05 |更新项目结构说明;新增基类设计规范(第4章) | -|2.1 |2026-02-04 |新增导入规范、代码清理原则、异常处理规范、性能优化建议、调试规范 | -|2.0 |2026-02-04 |重构文档结构,精简冗余内容,优化版本号体系 | -|1.0 |2026-02-03 |初始版本,建立基础开发规范 | - +| 版本 | 日期 | 变更内容 | +| :--: | :--------: | :------------------------------------------------------------------------: | +| 3.41 | 2026-03-13 | 精简开发规范中的代码示例,删除冗余注释和重复内容,使文档更加简洁易读 | +| 3.40 | 2026-03-07 | 更新项目结构(1.3节):新增 export\_settings\_dialog.py 到 dialogs/ 目录 | +| 3.39 | 2026-03-04 | 新增资源路径规范(2.4节):说明 PyInstaller 打包后的资源路径获取方式,避免打包后资源加载失败 | +| 3.38 | 2026-03-04 | 更新主题自适应组件实现规范(3.7.3节):新增注意事项,避免在 \_update\_styles 中调用 setStyleSheet 设置容器样式 | +| 3.37 | 2026-03-04 | 更新changelog.json数据格式说明(15.2节),与实际结构保持一致 | +| 3.36 | 2026-03-04 | 精简更新日志格式规范(第13章) | +| 3.35 | 2026-03-02 | 优化更新日志分类规范:新增分类说明,明确分类可灵活调整;新增"功能优化"分类;添加灵活调整示例说明 | +| 3.34 | 2026-02-28 | 新增日志系统,更新项目结构和开发规范(新增第17章日志系统规范) | +| 3.33 | 2026-02-28 | 新增第20章测试规范;更新章节编号(原第20章开源许可证管理规范改为第21章,原第21章附录改为第22章);新增线程取消机制规范(7.3节) | +| 3.32 | 2026-02-27 | 更新Zone分区规范(6.3节):将直方图分区从8个改为9个(Zone 0-8) | +| 3.31 | 2026-02-26 | 更新SVG智能映射规则说明(9.7.3节):新增覆盖检测功能说明 | +| 3.30 | 2026-02-26 | 新增渐变提取功能规范;更新项目结构和开发规范 | +| 3.29 | 2026-02-24 | 新增第15章项目官网维护规范;更新项目结构说明 | +| 3.28 | 2026-02-22 | 新增类型注解规范(3.7节) | +| 3.27 | 2026-02-22 | 新增服务工厂规范(3.3.1节);更新项目结构说明 | +| 3.26 | 2026-02-21 | 移动 theme\_colors.py 到 utils/ | +| 3.25 | 2026-02-20 | 新增缓存基类规范;更新项目结构说明 | +| 3.24 | 2026-02-20 | 新增配色计算缓存规范;更新项目结构说明 | +| 3.23 | 2026-02-19 | 新增第16章多语言国际化规范;更新项目结构说明 | +| 3.22 | 2026-02-18 | 新增直方图计算服务规范;更新项目结构说明 | +| 3.21 | 2026-02-18 | 新增预览场景管理服务规范;更新项目结构说明 | +| 3.20 | 2026-02-18 | 新增图片加载服务和明度计算服务规范;更新项目结构说明 | +| 3.19 | 2026-02-18 | 新增配色导入导出服务规范;更新项目结构说明 | +| 3.18 | 2026-02-18 | 新增颜色提取服务规范;更新项目结构说明 | +| 3.17 | 2026-02-18 | 更新SVG智能映射规则说明(9.7.3节) | +| 3.16 | 2026-02-18 | 更新SVG智能映射规则说明(9.7.3节) | +| 3.15 | 2026-02-18 | 更新SVG智能映射规则说明(9.7.3节) | +| 3.14 | 2026-02-18 | 更新SVG映射策略规范(9.7.3节):新增双模式映射策略说明 | +| 3.13 | 2026-02-18 | 更新项目结构说明 | +| 3.12 | 2026-02-18 | 更新项目结构说明 | +| 3.11 | 2026-02-18 | 更新SVG映射策略规范(9.7.3节):明确背景元素检测标准 | +| 3.10 | 2026-02-17 | 更新项目结构说明 | +| 3.9 | 2026-02-17 | 更新项目结构说明 | +| 3.8 | 2026-02-17 | 更新项目结构说明 | +| 3.7 | 2026-02-17 | 新增图片状态中介者规范(5.3.1节);更新项目结构说明 | +| 3.6 | 2026-02-17 | 更新项目结构说明 | +| 3.5 | 2026-02-17 | 新增分阶段图片加载规范(7.3节) | +| 3.4 | 2026-02-17 | 新增异步加载基类规范;更新项目结构说明 | +| 3.3 | 2026-02-16 | 新增SVG固定颜色属性规范:支持 `data-fixed-color` 属性标记不参与配色映射的元素,添加使用示例和场景推荐(第9.7.5节) | +| 3.2 | 2026-02-16 | 更新场景配置化规范(第9章):采用纯SVG模板模式,新增布局系统说明 | +| 3.1 | 2026-02-16 | 新增第8章配色数据JSON格式规范:说明统一的 palettes 格式、groups 字段、社区贡献指南;章节序号顺延调整 | +| 3.0 | 2026-02-13 | 文档重构:拆分为核心原则、完整版、经验总结三个独立文档;合并第9章和第10章布局规范;第14章开发经验总结独立为 `开发经验总结.md` | +| 2.21 | 2026-02-11 | 新增网格布局自适应宽度经验(第10章) | +| 2.20 | 2026-02-11 | 更新场景类型规范(第9章):新增四个配置化场景 | +| 2.19 | 2026-02-11 | 新增SVG智能配色映射规范(9.7节);更新项目结构说明 | +| 2.18 | 2026-02-10 | 新增场景配置化规范(第9章);更新项目结构说明 | +| 2.17 | 2026-02-10 | 重命名"配色方案"为"配色生成","色彩管理"为"配色管理"更新所有相关引用、导航栏显示和文档 | +| 2.16 | 2026-02-10 | 新增配色预览功能规范;更新项目结构说明 | +| 2.15 | 2026-02-09 | 新增应用启动优化策略规范(7.3节) | +| 2.14 | 2026-02-09 | 重命名"色卡收藏"面板为"色彩管理",更新所有相关引用和导航栏显示 | +| 2.13 | 2026-02-09 | 更新配色方案算法设计规范(14.1.1节) | +| 2.12 | 2026-02-09 | 更新色轮交互设计规范(14.1.4节) | +| 2.11 | 2026-02-08 | 新增第21章开源许可证管理规范 | +| 2.10 | 2026-02-07 | 新增信号循环预防规范(5.3节)、QThread取消机制(7.3节) | +| 2.9 | 2026-02-07 | 新增主题颜色管理规范(5.5节) | +| 2.8 | 2026-02-06 | 新增工具栏/按钮容器间距规范,补充 QSplitter 分隔条样式注意事项 | +| 2.7 | 2026-02-06 | 更新控件尺寸参考表(10.3节),完善布局压缩最佳实践 | +| 2.6 | 2026-02-06 | 新增防止布局重叠的规范(10.2节);更新控件尺寸参考表 | +| 2.5 | 2026-02-06 | 新增收藏功能规范;新增MessageBox使用规范、配置数据迁移实践 | +| 2.4 | 2026-02-06 | 新增布局设计最佳实践经验 | +| 2.3 | 2026-02-06 | 新增配色生成功能规范;更新项目结构说明 | +| 2.2 | 2026-02-05 | 更新项目结构说明;新增基类设计规范(第4章) | +| 2.1 | 2026-02-04 | 新增导入规范、代码清理原则、异常处理规范、性能优化建议、调试规范 | +| 2.0 | 2026-02-04 | 重构文档结构,精简冗余内容,优化版本号体系 | +| 1.0 | 2026-02-03 | 初始版本,建立基础开发规范 |