feat: 实现CutThenThink P0阶段核心功能

项目初始化
- 创建完整项目结构(src/, data/, docs/, examples/, tests/)
- 配置requirements.txt依赖
- 创建.gitignore

P0基础框架
- 数据库模型:Record模型,6种分类类型
- 配置管理:YAML配置,支持AI/OCR/云存储/UI配置
- OCR模块:PaddleOCR本地识别,支持云端扩展
- AI模块:支持OpenAI/Claude/通义/Ollama,6种分类
- 存储模块:完整CRUD,搜索,统计,导入导出
- 主窗口框架:侧边栏导航,米白配色方案
- 图片处理:截图/剪贴板/文件选择/图片预览
- 处理流程整合:OCR→AI→存储串联,Markdown展示,剪贴板复制
- 分类浏览:卡片网格展示,分类筛选,搜索,详情查看

技术栈
- PyQt6 + SQLAlchemy + PaddleOCR + OpenAI/Claude SDK
- 共47个Python文件,4000+行代码

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
congsh
2026-02-11 18:21:31 +08:00
commit c4a77f8aa4
79 changed files with 19412 additions and 0 deletions

View File

@@ -0,0 +1,36 @@
"""
GUI样式和主题模块
提供颜色方案和主题样式表
"""
from src.gui.styles.colors import ColorScheme, COLORS, get_color
from src.gui.styles.theme import ThemeStyles
# 浏览视图样式(如果存在)
try:
from src.gui.styles.browse_style import (
get_style, get_category_color, get_category_name,
CATEGORY_COLORS, CATEGORY_NAMES
)
_has_browse_style = True
except ImportError:
_has_browse_style = False
__all__ = [
# 颜色和主题
'ColorScheme',
'COLORS',
'get_color',
'ThemeStyles',
]
# 如果浏览样式存在,添加到导出
if _has_browse_style:
__all__.extend([
'get_style',
'get_category_color',
'get_category_name',
'CATEGORY_COLORS',
'CATEGORY_NAMES',
])

View File

@@ -0,0 +1,341 @@
"""
浏览视图样式定义
包含卡片、按钮、对话框等组件的样式
"""
# 通用样式
COMMON_STYLES = """
QWidget {
font-family: "Microsoft YaHei", "PingFang SC", -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, sans-serif;
}
"""
# 卡片样式
CARD_STYLES = """
RecordCard {
background-color: white;
border-radius: 12px;
border: 1px solid #E8E8E8;
}
RecordCard:hover {
background-color: #FAFAFA;
border: 1px solid #4A90E2;
}
"""
# 按钮样式
BUTTON_STYLES = {
'primary': """
QPushButton {
background-color: #4A90E2;
color: white;
border: none;
padding: 10px 20px;
border-radius: 8px;
font-size: 14px;
font-weight: bold;
}
QPushButton:hover {
background-color: #357ABD;
}
QPushButton:pressed {
background-color: #2E6FA8;
}
QPushButton:disabled {
background-color: #BDC3C7;
color: #ECF0F1;
}
""",
'success': """
QPushButton {
background-color: #58D68D;
color: white;
border: none;
padding: 10px 20px;
border-radius: 8px;
font-size: 14px;
font-weight: bold;
}
QPushButton:hover {
background-color: #48C9B0;
}
QPushButton:pressed {
background-color: #45B39D;
}
""",
'danger': """
QPushButton {
background-color: #EC7063;
color: white;
border: none;
padding: 10px 20px;
border-radius: 8px;
font-size: 14px;
font-weight: bold;
}
QPushButton:hover {
background-color: #E74C3C;
}
QPushButton:pressed {
background-color: #C0392B;
}
""",
'secondary': """
QPushButton {
background-color: #95A5A6;
color: white;
border: none;
padding: 10px 20px;
border-radius: 8px;
font-size: 14px;
}
QPushButton:hover {
background-color: #7F8C8D;
}
QPushButton:pressed {
background-color: #6C7A7D;
}
""",
}
# 分类按钮颜色
CATEGORY_COLORS = {
"TODO": "#5DADE2",
"NOTE": "#58D68D",
"IDEA": "#F5B041",
"REF": "#AF7AC5",
"FUNNY": "#EC7063",
"TEXT": "#95A5A6",
}
# 分类名称
CATEGORY_NAMES = {
"TODO": "待办",
"NOTE": "笔记",
"IDEA": "灵感",
"REF": "参考",
"FUNNY": "趣味",
"TEXT": "文本",
}
# 输入框样式
INPUT_STYLES = """
QLineEdit {
padding: 10px 15px;
border: 2px solid #E0E0E0;
border-radius: 8px;
font-size: 14px;
background-color: #FAFAFA;
color: #333;
}
QLineEdit:focus {
border: 2px solid #4A90E2;
background-color: white;
}
QLineEdit:disabled {
background-color: #ECF0F1;
color: #95A5A6;
}
"""
# 文本编辑框样式
TEXTEDIT_STYLES = """
QTextEdit {
border: 1px solid #E0E0E0;
border-radius: 8px;
padding: 10px;
background-color: #FAFAFA;
font-size: 13px;
line-height: 1.6;
color: #333;
}
QTextEdit:focus {
border: 1px solid #4A90E2;
background-color: white;
}
"""
# 下拉框样式
COMBOBOX_STYLES = """
QComboBox {
padding: 8px 15px;
border: 2px solid #E0E0E0;
border-radius: 8px;
font-size: 14px;
background-color: white;
color: #333;
}
QComboBox:hover {
border: 2px solid #4A90E2;
}
QComboBox::drop-down {
border: none;
width: 30px;
}
QComboBox::down-arrow {
image: none;
border: 5px solid transparent;
border-top-color: #333;
margin-right: 10px;
}
QComboBox QAbstractItemView {
border: 1px solid #E0E0E0;
border-radius: 8px;
background-color: white;
selection-background-color: #4A90E2;
selection-color: white;
padding: 5px;
}
"""
# 滚动区域样式
SCROLLAREA_STYLES = """
QScrollArea {
border: none;
background-color: transparent;
}
QScrollBar:vertical {
border: none;
background-color: #F5F5F5;
width: 12px;
border-radius: 6px;
}
QScrollBar::handle:vertical {
background-color: #BDC3C7;
border-radius: 6px;
min-height: 30px;
}
QScrollBar::handle:vertical:hover {
background-color: #95A5A6;
}
QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {
border: none;
background: none;
}
QScrollBar:horizontal {
border: none;
background-color: #F5F5F5;
height: 12px;
border-radius: 6px;
}
QScrollBar::handle:horizontal {
background-color: #BDC3C7;
border-radius: 6px;
min-width: 30px;
}
QScrollBar::handle:horizontal:hover {
background-color: #95A5A6;
}
QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal {
border: none;
background: none;
}
"""
# 框架样式
FRAME_STYLES = """
QFrame {
background-color: white;
border-radius: 12px;
border: 1px solid #E0E0E0;
}
"""
# 标签样式
LABEL_STYLES = {
'title': """
QLabel {
font-size: 24px;
font-weight: bold;
color: #2C3E50;
}
""",
'subtitle': """
QLabel {
font-size: 18px;
font-weight: bold;
color: #34495E;
}
""",
'body': """
QLabel {
font-size: 14px;
color: #333;
}
""",
'caption': """
QLabel {
font-size: 12px;
color: #999;
}
""",
}
# 对话框样式
DIALOG_STYLES = """
QDialog {
background-color: #F5F7FA;
}
"""
# 工具栏样式
TOOLBAR_STYLES = """
QFrame {
background-color: white;
border-radius: 12px;
padding: 15px;
}
"""
def get_style(style_type: str, *args) -> str:
"""
获取样式字符串
Args:
style_type: 样式类型 (button, input, label等)
*args: 额外参数 (如button类型: primary, secondary等)
Returns:
样式字符串
"""
styles = {
'button': BUTTON_STYLES.get(args[0] if args else 'primary', ''),
'input': INPUT_STYLES,
'textedit': TEXTEDIT_STYLES,
'combobox': COMBOBOX_STYLES,
'scrollarea': SCROLLAREA_STYLES,
'frame': FRAME_STYLES,
'label': LABEL_STYLES.get(args[0] if args else 'body', ''),
'dialog': DIALOG_STYLES,
'toolbar': TOOLBAR_STYLES,
}
return styles.get(style_type, '')
def get_category_color(category: str) -> str:
"""
获取分类颜色
Args:
category: 分类代码
Returns:
颜色代码
"""
return CATEGORY_COLORS.get(category, "#95A5A6")
def get_category_name(category: str) -> str:
"""
获取分类中文名
Args:
category: 分类代码
Returns:
中文名
"""
return CATEGORY_NAMES.get(category, category)

122
src/gui/styles/colors.py Normal file
View File

@@ -0,0 +1,122 @@
"""
颜色定义模块
定义应用程序使用的颜色方案,采用米白色系
"""
from dataclasses import dataclass
from typing import Dict
@dataclass
class ColorScheme:
"""颜色方案类 - 米白色主题"""
# 主色调 - 米白色系
background_primary: str = "#FAF8F5" # 主背景色 - 米白色
background_secondary: str = "#F0ECE8" # 次要背景色 - 浅米色
background_card: str = "#FFFFFF" # 卡片背景色 - 纯白
# 文字颜色
text_primary: str = "#2C2C2C" # 主要文字 - 深灰
text_secondary: str = "#666666" # 次要文字 - 中灰
text_disabled: str = "#999999" # 禁用文字 - 浅灰
text_hint: str = "#B8B8B8" # 提示文字 - 更浅灰
# 强调色 - 温暖的棕色系
accent_primary: str = "#8B6914" # 主要强调色 - 金棕
accent_secondary: str = "#A67C52" # 次要强调色 - 驼色
accent_hover: str = "#D4A574" # 悬停色 - 浅驼
# 边框和分割线
border_light: str = "#E8E4E0" # 浅边框
border_medium: str = "#D0CCC6" # 中边框
border_dark: str = "#B0ABA5" # 深边框
# 功能色
success: str = "#6B9B3A" # 成功 - 橄榄绿
warning: str = "#D9A518" # 警告 - 金黄
error: str = "#C94B38" # 错误 - 铁锈红
info: str = "#5B8FB9" # 信息 - 钢蓝
# 阴影
shadow_light: str = "rgba(0, 0, 0, 0.05)" # 浅阴影
shadow_medium: str = "rgba(0, 0, 0, 0.1)" # 中阴影
shadow_dark: str = "rgba(0, 0, 0, 0.15)" # 深阴影
# 侧边栏
sidebar_background: str = "#F5F1EC" # 侧边栏背景
sidebar_item_hover: str = "#EBE7E2" # 侧边栏项悬停
sidebar_item_active: str = "#E0DCD6" # 侧边栏项激活
sidebar_text: str = "#4A4642" # 侧边栏文字
# 按钮
button_primary_bg: str = "#8B6914" # 主按钮背景
button_primary_hover: str = "#A67C52" # 主按钮悬停
button_primary_text: str = "#FFFFFF" # 主按钮文字
button_secondary_bg: str = "#E8E4E0" # 次要按钮背景
button_secondary_hover: str = "#D0CCC6" # 次要按钮悬停
button_secondary_text: str = "#2C2C2C" # 次要按钮文字
# 输入框
input_background: str = "#FFFFFF" # 输入框背景
input_border: str = "#D0CCC6" # 输入框边框
input_focus_border: str = "#8B6914" # 输入框聚焦边框
input_placeholder: str = "#B8B8B8" # 输入框占位符
def to_dict(self) -> Dict[str, str]:
"""转换为字典"""
return {
'background_primary': self.background_primary,
'background_secondary': self.background_secondary,
'background_card': self.background_card,
'text_primary': self.text_primary,
'text_secondary': self.text_secondary,
'text_disabled': self.text_disabled,
'text_hint': self.text_hint,
'accent_primary': self.accent_primary,
'accent_secondary': self.accent_secondary,
'accent_hover': self.accent_hover,
'border_light': self.border_light,
'border_medium': self.border_medium,
'border_dark': self.border_dark,
'success': self.success,
'warning': self.warning,
'error': self.error,
'info': self.info,
'shadow_light': self.shadow_light,
'shadow_medium': self.shadow_medium,
'shadow_dark': self.shadow_dark,
'sidebar_background': self.sidebar_background,
'sidebar_item_hover': self.sidebar_item_hover,
'sidebar_item_active': self.sidebar_item_active,
'sidebar_text': self.sidebar_text,
'button_primary_bg': self.button_primary_bg,
'button_primary_hover': self.button_primary_hover,
'button_primary_text': self.button_primary_text,
'button_secondary_bg': self.button_secondary_bg,
'button_secondary_hover': self.button_secondary_hover,
'button_secondary_text': self.button_secondary_text,
'input_background': self.input_background,
'input_border': self.input_border,
'input_focus_border': self.input_focus_border,
'input_placeholder': self.input_placeholder,
}
# 全局颜色方案实例
COLORS = ColorScheme()
def get_color(name: str) -> str:
"""
获取颜色值
Args:
name: 颜色名称
Returns:
颜色值(十六进制字符串)
"""
return getattr(COLORS, name, "#000000")

437
src/gui/styles/theme.py Normal file
View File

@@ -0,0 +1,437 @@
"""
主题样式表模块
定义 Qt 样式表QSS实现米白色主题
"""
from .colors import COLORS
class ThemeStyles:
"""主题样式表类"""
@staticmethod
def get_main_window_stylesheet() -> str:
"""
获取主窗口样式表
Returns:
QSS 样式表字符串
"""
return f"""
/* ========== 主窗口 ========== */
QMainWindow {{
background-color: {COLORS.background_primary};
}}
/* ========== 侧边栏 ========== */
QWidget#sidebar {{
background-color: {COLORS.sidebar_background};
border-right: 1px solid {COLORS.border_light};
}}
QPushButton#navButton {{
background-color: transparent;
border: none;
border-radius: 8px;
padding: 12px 16px;
text-align: left;
color: {COLORS.sidebar_text};
font-size: 14px;
font-weight: 500;
}}
QPushButton#navButton:hover {{
background-color: {COLORS.sidebar_item_hover};
}}
QPushButton#navButton:checked {{
background-color: {COLORS.sidebar_item_active};
color: {COLORS.accent_primary};
font-weight: 600;
}}
QWidget#navSeparator {{
background-color: {COLORS.border_light};
max-height: 1px;
min-height: 1px;
margin: 8px 16px;
}}
/* ========== 主内容区域 ========== */
QWidget#contentArea {{
background-color: {COLORS.background_primary};
}}
QStackedWidget#contentStack {{
background-color: transparent;
border: none;
}}
/* ========== 标题 ========== */
QLabel#pageTitle {{
color: {COLORS.text_primary};
font-size: 24px;
font-weight: 600;
padding: 8px 0;
}}
QLabel#sectionTitle {{
color: {COLORS.text_primary};
font-size: 18px;
font-weight: 600;
padding: 4px 0;
}}
/* ========== 卡片 ========== */
QWidget#card {{
background-color: {COLORS.background_card};
border-radius: 12px;
border: 1px solid {COLORS.border_light};
padding: 16px;
}}
/* ========== 按钮 ========== */
QPushButton {{
background-color: {COLORS.button_secondary_bg};
color: {COLORS.button_secondary_text};
border: none;
border-radius: 6px;
padding: 8px 16px;
font-size: 14px;
font-weight: 500;
}}
QPushButton:hover {{
background-color: {COLORS.button_secondary_hover};
}}
QPushButton:pressed {{
background-color: {COLORS.border_medium};
}}
QPushButton:disabled {{
background-color: {COLORS.background_secondary};
color: {COLORS.text_disabled};
}}
QPushButton#primaryButton {{
background-color: {COLORS.button_primary_bg};
color: {COLORS.button_primary_text};
}}
QPushButton#primaryButton:hover {{
background-color: {COLORS.button_primary_hover};
}}
/* ========== 输入框 ========== */
QLineEdit, QTextEdit, QPlainTextEdit {{
background-color: {COLORS.input_background};
border: 1px solid {COLORS.input_border};
border-radius: 6px;
padding: 8px 12px;
color: {COLORS.text_primary};
font-size: 14px;
selection-background-color: {COLORS.accent_secondary};
}}
QLineEdit:hover, QTextEdit:hover, QPlainTextEdit:hover {{
border-color: {COLORS.border_dark};
}}
QLineEdit:focus, QTextEdit:focus, QPlainTextEdit:focus {{
border: 2px solid {COLORS.input_focus_border};
padding: 7px 11px;
}}
QLineEdit:disabled, QTextEdit:disabled, QPlainTextEdit:disabled {{
background-color: {COLORS.background_secondary};
color: {COLORS.text_disabled};
}}
/* ========== 下拉框 ========== */
QComboBox {{
background-color: {COLORS.input_background};
border: 1px solid {COLORS.input_border};
border-radius: 6px;
padding: 8px 12px;
color: {COLORS.text_primary};
font-size: 14px;
}}
QComboBox:hover {{
border-color: {COLORS.border_dark};
}}
QComboBox:focus {{
border: 2px solid {COLORS.input_focus_border};
}}
QComboBox::drop-down {{
border: none;
width: 20px;
}}
QComboBox::down-arrow {{
image: none;
border: 5px solid transparent;
border-top-color: {COLORS.text_secondary};
margin-right: 5px;
}}
QComboBox QAbstractItemView {{
background-color: {COLORS.background_card};
border: 1px solid {COLORS.border_light};
selection-background-color: {COLORS.sidebar_item_active};
selection-color: {COLORS.text_primary};
padding: 4px;
}}
/* ========== 滚动条 ========== */
QScrollBar:vertical {{
background-color: transparent;
width: 10px;
margin: 0px;
}}
QScrollBar::handle:vertical {{
background-color: {COLORS.border_medium};
border-radius: 5px;
min-height: 30px;
}}
QScrollBar::handle:vertical:hover {{
background-color: {COLORS.border_dark};
}}
QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {{
height: 0px;
}}
QScrollBar:horizontal {{
background-color: transparent;
height: 10px;
margin: 0px;
}}
QScrollBar::handle:horizontal {{
background-color: {COLORS.border_medium};
border-radius: 5px;
min-width: 30px;
}}
QScrollBar::handle:horizontal:hover {{
background-color: {COLORS.border_dark};
}}
QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal {{
width: 0px;
}}
/* ========== 分组框 ========== */
QGroupBox {{
background-color: {COLORS.background_card};
border: 1px solid {COLORS.border_light};
border-radius: 8px;
margin-top: 12px;
padding: 16px;
font-size: 14px;
font-weight: 600;
color: {COLORS.text_primary};
}}
QGroupBox::title {{
subcontrol-origin: margin;
left: 16px;
padding: 0 8px;
}}
/* ========== 标签页 ========== */
QTabWidget::pane {{
background-color: {COLORS.background_card};
border: 1px solid {COLORS.border_light};
border-radius: 8px;
top: -1px;
}}
QTabBar::tab {{
background-color: {COLORS.background_secondary};
color: {COLORS.text_secondary};
border: none;
padding: 10px 20px;
margin-right: 4px;
font-size: 14px;
font-weight: 500;
}}
QTabBar::tab:selected {{
background-color: {COLORS.background_card};
color: {COLORS.text_primary};
font-weight: 600;
}}
QTabBar::tab:hover:!selected {{
background-color: {COLORS.sidebar_item_hover};
}}
/* ========== 复选框 ========== */
QCheckBox {{
color: {COLORS.text_primary};
font-size: 14px;
spacing: 8px;
}}
QCheckBox::indicator {{
width: 18px;
height: 18px;
border: 2px solid {COLORS.input_border};
border-radius: 4px;
background-color: {COLORS.input_background};
}}
QCheckBox::indicator:hover {{
border-color: {COLORS.border_dark};
}}
QCheckBox::indicator:checked {{
background-color: {COLORS.accent_primary};
border-color: {COLORS.accent_primary};
image: none;
}}
QCheckBox::indicator:checked::after {{
content: "";
color: {COLORS.button_primary_text};
}}
/* ========== 单选框 ========== */
QRadioButton {{
color: {COLORS.text_primary};
font-size: 14px;
spacing: 8px;
}}
QRadioButton::indicator {{
width: 18px;
height: 18px;
border: 2px solid {COLORS.input_border};
border-radius: 9px;
background-color: {COLORS.input_background};
}}
QRadioButton::indicator:hover {{
border-color: {COLORS.border_dark};
}}
QRadioButton::indicator:checked {{
background-color: {COLORS.input_background};
border-color: {COLORS.accent_primary};
}}
QRadioButton::indicator:checked::after {{
content: "";
width: 8px;
height: 8px;
border-radius: 4px;
background-color: {COLORS.accent_primary};
}}
/* ========== 进度条 ========== */
QProgressBar {{
background-color: {COLORS.background_secondary};
border: none;
border-radius: 6px;
height: 8px;
text-align: center;
color: {COLORS.text_primary};
font-size: 12px;
}}
QProgressBar::chunk {{
background-color: {COLORS.accent_primary};
border-radius: 6px;
}}
/* ========== 分隔线 ========== */
QFrame[frameShape="4"], QFrame[frameShape="5"] {{
color: {COLORS.border_light};
}}
/* ========== 工具提示 ========== */
QToolTip {{
background-color: {COLORS.text_primary};
color: {COLORS.background_primary};
border: none;
border-radius: 4px;
padding: 6px 10px;
font-size: 12px;
}}
/* ========== 菜单 ========== */
QMenu {{
background-color: {COLORS.background_card};
border: 1px solid {COLORS.border_light};
border-radius: 6px;
padding: 4px;
}}
QMenu::item {{
padding: 8px 16px;
border-radius: 4px;
color: {COLORS.text_primary};
font-size: 14px;
}}
QMenu::item:selected {{
background-color: {COLORS.sidebar_item_active};
color: {COLORS.accent_primary};
}}
QMenu::separator {{
height: 1px;
background-color: {COLORS.border_light};
margin: 4px 8px;
}}
/* ========== 列表 ========== */
QListWidget {{
background-color: {COLORS.background_card};
border: 1px solid {COLORS.border_light};
border-radius: 6px;
padding: 4px;
}}
QListWidget::item {{
padding: 8px 12px;
border-radius: 4px;
color: {COLORS.text_primary};
font-size: 14px;
}}
QListWidget::item:hover {{
background-color: {COLORS.sidebar_item_hover};
}}
QListWidget::item:selected {{
background-color: {COLORS.sidebar_item_active};
color: {COLORS.accent_primary};
}}
/* ========== 状态栏 ========== */
QStatusBar {{
background-color: {COLORS.background_secondary};
color: {COLORS.text_secondary};
border-top: 1px solid {COLORS.border_light};
font-size: 12px;
}}
"""
@staticmethod
def apply_style(widget) -> None:
"""
应用样式到部件
Args:
widget: Qt 部件
"""
widget.setStyleSheet(ThemeStyles.get_main_window_stylesheet())