前言:从学到用的蜕变
恭喜你!经过前九篇的学习,你已经掌握了QML开发的完整知识体系。现在是时候将这些知识融会贯通,构建一个真正的完整应用了。本篇我们将创建一个个人知识管理应用,它不仅功能完整,而且能体现现代应用开发的最佳实践。

项目预览:智慧笔记 (SmartNotes)
我们将构建一个名为"智慧笔记"的个人知识管理应用,它包含以下核心功能:
-
知识卡片:创建、编辑、组织知识卡片
-
智能标签:自动分类和组织内容
-
知识图谱:可视化知识之间的关系
-
搜索与过滤:快速找到所需信息
-
数据同步:跨设备同步笔记
-
统计面板:了解学习进度和知识结构

第一部分:项目架构设计
1.1 技术栈选择
bash
技术栈选择:
├── 前端框架
│ ├── Qt 6.5 + QML
│ ├── Qt Quick Controls 2
│ └── Qt Quick 3D (用于知识图谱)
├── 后端语言
│ ├── Python 3.10 (业务逻辑)
│ └── SQLite (数据库)
├── 开发工具
│ ├── Qt Creator IDE
│ ├── Git 版本控制
│ └── CMake 构建系统
└── 质量保证
├── 单元测试 (pytest)
├── 集成测试
└── 性能分析工具
1.2 项目目录结构
bash
smartnotes/
├── src/ # 源代码
│ ├── frontend/ # 前端代码
│ │ ├── qml/ # QML文件
│ │ ├── resources/ # 资源文件
│ │ └── styles/ # 样式文件
│ ├── backend/ # 后端代码
│ │ ├── core/ # 核心逻辑
│ │ ├── database/ # 数据库操作
│ │ ├── services/ # 业务服务
│ │ └── utils/ # 工具函数
│ └── shared/ # 共享代码
│ ├── models/ # 数据模型
│ ├── constants/ # 常量定义
│ └── types/ # 类型定义
├── tests/ # 测试代码
│ ├── unit/ # 单元测试
│ ├── integration/ # 集成测试
│ └── ui/ # UI测试
├── docs/ # 文档
│ ├── api/ # API文档
│ ├── guides/ # 使用指南
│ └── design/ # 设计文档
├── scripts/ # 脚本文件
│ ├── build/ # 构建脚本
│ ├── deploy/ # 部署脚本
│ └── tools/ # 开发工具
└── platform/ # 平台特定配置
├── windows/ # Windows配置
├── macos/ # macOS配置
├── linux/ # Linux配置
├── android/ # Android配置
└── ios/ # iOS配置
第二部分:核心功能实现
2.1 数据模型设计
让我们从最核心的数据模型开始。我们将创建几个简单的数据类来表示知识卡片、标签和关系。
python
# src/shared/models/__init__.py
"""
数据模型定义
这个文件定义了应用的核心数据模型
"""
from dataclasses import dataclass, field, asdict
from datetime import datetime
from typing import List, Dict, Any, Optional, Set
from enum import Enum
import uuid
class CardType(Enum):
"""知识卡片类型枚举"""
NOTE = "note" # 笔记
IDEA = "idea" # 想法
TASK = "task" # 任务
QUESTION = "question" # 问题
ANSWER = "answer" # 答案
REFERENCE = "reference" # 参考
class CardStatus(Enum):
"""卡片状态枚举"""
DRAFT = "draft" # 草稿
ACTIVE = "active" # 活跃
ARCHIVED = "archived" # 归档
DELETED = "deleted" # 删除
@dataclass
class KnowledgeCard:
"""知识卡片基础模型"""
id: str = field(default_factory=lambda: str(uuid.uuid4()))
title: str = ""
content: str = ""
type: CardType = CardType.NOTE
status: CardStatus = CardStatus.DRAFT
tags: List[str] = field(default_factory=list)
created_at: datetime = field(default_factory=datetime.now)
updated_at: datetime = field(default_factory=datetime.now)
# 元数据
priority: int = 0 # 优先级 0-10
difficulty: int = 0 # 难度 0-10
importance: int = 0 # 重要性 0-10
def to_dict(self) -> Dict[str, Any]:
"""转换为字典"""
data = asdict(self)
data['type'] = self.type.value
data['status'] = self.status.value
data['created_at'] = self.created_at.isoformat()
data['updated_at'] = self.updated_at.isoformat()
return data
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> 'KnowledgeCard':
"""从字典创建实例"""
data = data.copy()
data['type'] = CardType(data.get('type', 'note'))
data['status'] = CardStatus(data.get('status', 'draft'))
# 转换时间字符串
for time_key in ['created_at', 'updated_at']:
if time_key in data and isinstance(data[time_key], str):
data[time_key] = datetime.fromisoformat(data[time_key])
return cls(**data)
def update(self, **kwargs):
"""更新卡片属性"""
for key, value in kwargs.items():
if hasattr(self, key):
setattr(self, key, value)
self.updated_at = datetime.now()
def get_summary(self, max_length: int = 100) -> str:
"""获取内容摘要"""
if not self.content:
return ""
if len(self.content) <= max_length:
return self.content
return self.content[:max_length] + "..."
@dataclass
class Tag:
"""标签模型"""
id: str
name: str
color: str = "#2196F3" # 默认蓝色
description: str = ""
created_at: datetime = field(default_factory=datetime.now)
usage_count: int = 0
def to_dict(self) -> Dict[str, Any]:
data = asdict(self)
data['created_at'] = self.created_at.isoformat()
return data
@dataclass
class CardRelationship:
"""卡片关系模型"""
id: str = field(default_factory=lambda: str(uuid.uuid4()))
source_card_id: str = ""
target_card_id: str = ""
relationship_type: str = "related" # related, depends_on, answers, etc.
strength: float = 1.0 # 关系强度 0.0-1.0
created_at: datetime = field(default_factory=datetime.now)
@dataclass
class SmartTag:
"""智能标签(自动分类)"""
id: str
name: str
keywords: List[str] # 关键词列表
description: str = ""
auto_apply: bool = True # 是否自动应用
confidence_threshold: float = 0.7 # 置信度阈值
def should_apply(self, card: KnowledgeCard) -> bool:
"""判断是否应该应用此标签"""
if not self.auto_apply:
return False
# 简单的关键词匹配算法
text_to_check = f"{card.title} {card.content}".lower()
matched_keywords = 0
for keyword in self.keywords:
if keyword.lower() in text_to_check:
matched_keywords += 1
confidence = matched_keywords / len(self.keywords) if self.keywords else 0
return confidence >= self.confidence_threshold
数据关系模型:

2.2 数据库服务
现在让我们实现数据库服务,使用SQLite作为本地存储:
python
# src/backend/database/database_service.py
import sqlite3
from typing import List, Dict, Any, Optional, Tuple
from datetime import datetime
import json
from pathlib import Path
from ..shared.models import (
KnowledgeCard, CardType, CardStatus,
Tag, CardRelationship, SmartTag
)
class DatabaseService:
"""数据库服务"""
def __init__(self, db_path: str = "smartnotes.db"):
"""
初始化数据库服务
Args:
db_path: 数据库文件路径
"""
self.db_path = db_path
self._connection = None
self._init_database()
def _get_connection(self) -> sqlite3.Connection:
"""获取数据库连接"""
if self._connection is None:
self._connection = sqlite3.connect(
self.db_path,
detect_types=sqlite3.PARSE_DECLTYPES
)
self._connection.row_factory = sqlite3.Row
return self._connection
def _init_database(self):
"""初始化数据库表"""
conn = self._get_connection()
cursor = conn.cursor()
# 创建知识卡片表
cursor.execute("""
CREATE TABLE IF NOT EXISTS knowledge_cards (
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
content TEXT NOT NULL,
type TEXT NOT NULL,
status TEXT NOT NULL,
tags_json TEXT NOT NULL,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL,
priority INTEGER DEFAULT 0,
difficulty INTEGER DEFAULT 0,
importance INTEGER DEFAULT 0
)
""")
# 创建标签表
cursor.execute("""
CREATE TABLE IF NOT EXISTS tags (
id TEXT PRIMARY KEY,
name TEXT UNIQUE NOT NULL,
color TEXT NOT NULL,
description TEXT,
created_at TIMESTAMP NOT NULL,
usage_count INTEGER DEFAULT 0
)
""")
# 创建卡片关系表
cursor.execute("""
CREATE TABLE IF NOT EXISTS card_relationships (
id TEXT PRIMARY KEY,
source_card_id TEXT NOT NULL,
target_card_id TEXT NOT NULL,
relationship_type TEXT NOT NULL,
strength REAL DEFAULT 1.0,
created_at TIMESTAMP NOT NULL,
FOREIGN KEY (source_card_id) REFERENCES knowledge_cards (id) ON DELETE CASCADE,
FOREIGN KEY (target_card_id) REFERENCES knowledge_cards (id) ON DELETE CASCADE
)
""")
# 创建智能标签表
cursor.execute("""
CREATE TABLE IF NOT EXISTS smart_tags (
id TEXT PRIMARY KEY,
name TEXT UNIQUE NOT NULL,
keywords_json TEXT NOT NULL,
description TEXT,
auto_apply BOOLEAN DEFAULT 1,
confidence_threshold REAL DEFAULT 0.7
)
""")
# 创建索引
cursor.execute("CREATE INDEX IF NOT EXISTS idx_cards_created ON knowledge_cards(created_at)")
cursor.execute("CREATE INDEX IF NOT EXISTS idx_cards_status ON knowledge_cards(status)")
cursor.execute("CREATE INDEX IF NOT EXISTS idx_relationships_source ON card_relationships(source_card_id)")
cursor.execute("CREATE INDEX IF NOT EXISTS idx_relationships_target ON card_relationships(target_card_id)")
conn.commit()
# 初始化一些默认智能标签
self._init_default_smart_tags()
def _init_default_smart_tags(self):
"""初始化默认智能标签"""
default_tags = [
SmartTag(
id="tag_question",
name="问题",
keywords=["?", "为什么", "如何", "怎么", "什么"],
description="包含问题的卡片",
auto_apply=True
),
SmartTag(
id="tag_important",
name="重要",
keywords=["重要", "关键", "核心", "必须", "必要"],
description="重要的内容",
auto_apply=True
),
SmartTag(
id="tag_todo",
name="待办",
keywords=["TODO", "待办", "要做", "需要做", "计划"],
description="待办事项",
auto_apply=True
),
SmartTag(
id="tag_reference",
name="参考",
keywords=["参考", "来源", "引用", "参考文献", "链接"],
description="参考资料",
auto_apply=True
)
]
for tag in default_tags:
self.save_smart_tag(tag)
# === 知识卡片操作 ===
def save_card(self, card: KnowledgeCard) -> str:
"""保存知识卡片"""
conn = self._get_connection()
cursor = conn.cursor()
tags_json = json.dumps(card.tags)
cursor.execute("""
INSERT OR REPLACE INTO knowledge_cards
(id, title, content, type, status, tags_json, created_at, updated_at,
priority, difficulty, importance)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (
card.id, card.title, card.content, card.type.value, card.status.value,
tags_json, card.created_at, card.updated_at,
card.priority, card.difficulty, card.importance
))
conn.commit()
# 更新标签使用计数
for tag in card.tags:
self._increment_tag_usage(tag)
return card.id
def get_card(self, card_id: str) -> Optional[KnowledgeCard]:
"""获取知识卡片"""
conn = self._get_connection()
cursor = conn.cursor()
cursor.execute("SELECT * FROM knowledge_cards WHERE id = ?", (card_id,))
row = cursor.fetchone()
if row is None:
return None
return self._row_to_card(row)
def get_all_cards(
self,
status_filter: Optional[CardStatus] = None,
type_filter: Optional[CardType] = None
) -> List[KnowledgeCard]:
"""获取所有知识卡片"""
conn = self._get_connection()
cursor = conn.cursor()
query = "SELECT * FROM knowledge_cards WHERE 1=1"
params = []
if status_filter:
query += " AND status = ?"
params.append(status_filter.value)
if type_filter:
query += " AND type = ?"
params.append(type_filter.value)
query += " ORDER BY updated_at DESC"
cursor.execute(query, params)
rows = cursor.fetchall()
return [self._row_to_card(row) for row in rows]
def search_cards(
self,
query_text: str,
limit: int = 50
) -> List[KnowledgeCard]:
"""搜索知识卡片"""
conn = self._get_connection()
cursor = conn.cursor()
search_term = f"%{query_text}%"
cursor.execute("""
SELECT * FROM knowledge_cards
WHERE title LIKE ? OR content LIKE ?
ORDER BY
CASE
WHEN title LIKE ? THEN 1
ELSE 2
END,
updated_at DESC
LIMIT ?
""", (search_term, search_term, search_term, limit))
rows = cursor.fetchall()
return [self._row_to_card(row) for row in rows]
def delete_card(self, card_id: str) -> bool:
"""删除知识卡片"""
conn = self._get_connection()
cursor = conn.cursor()
# 先获取卡片的标签,用于更新使用计数
cursor.execute("SELECT tags_json FROM knowledge_cards WHERE id = ?", (card_id,))
row = cursor.fetchone()
if row:
tags = json.loads(row['tags_json'])
# 删除卡片
cursor.execute("DELETE FROM knowledge_cards WHERE id = ?", (card_id,))
conn.commit()
# 更新标签使用计数
for tag in tags:
self._decrement_tag_usage(tag)
return True
return False
def _row_to_card(self, row) -> KnowledgeCard:
"""将数据库行转换为KnowledgeCard对象"""
tags = json.loads(row['tags_json']) if row['tags_json'] else []
return KnowledgeCard(
id=row['id'],
title=row['title'],
content=row['content'],
type=CardType(row['type']),
status=CardStatus(row['status']),
tags=tags,
created_at=row['created_at'],
updated_at=row['updated_at'],
priority=row['priority'],
difficulty=row['difficulty'],
importance=row['importance']
)
# === 标签操作 ===
def save_tag(self, tag: Tag) -> str:
"""保存标签"""
conn = self._get_connection()
cursor = conn.cursor()
cursor.execute("""
INSERT OR REPLACE INTO tags
(id, name, color, description, created_at, usage_count)
VALUES (?, ?, ?, ?, ?, ?)
""", (
tag.id, tag.name, tag.color, tag.description,
tag.created_at, tag.usage_count
))
conn.commit()
return tag.id
def get_all_tags(self) -> List[Tag]:
"""获取所有标签"""
conn = self._get_connection()
cursor = conn.cursor()
cursor.execute("SELECT * FROM tags ORDER BY usage_count DESC")
rows = cursor.fetchall()
return [
Tag(
id=row['id'],
name=row['name'],
color=row['color'],
description=row['description'],
created_at=row['created_at'],
usage_count=row['usage_count']
)
for row in rows
]
def _increment_tag_usage(self, tag_name: str):
"""增加标签使用计数"""
conn = self._get_connection()
cursor = conn.cursor()
cursor.execute("""
UPDATE tags
SET usage_count = usage_count + 1
WHERE name = ?
""", (tag_name,))
conn.commit()
def _decrement_tag_usage(self, tag_name: str):
"""减少标签使用计数"""
conn = self._get_connection()
cursor = conn.cursor()
cursor.execute("""
UPDATE tags
SET usage_count = GREATEST(usage_count - 1, 0)
WHERE name = ?
""", (tag_name,))
conn.commit()
# === 智能标签操作 ===
def save_smart_tag(self, smart_tag: SmartTag) -> str:
"""保存智能标签"""
conn = self._get_connection()
cursor = conn.cursor()
keywords_json = json.dumps(smart_tag.keywords)
cursor.execute("""
INSERT OR REPLACE INTO smart_tags
(id, name, keywords_json, description, auto_apply, confidence_threshold)
VALUES (?, ?, ?, ?, ?, ?)
""", (
smart_tag.id, smart_tag.name, keywords_json,
smart_tag.description, smart_tag.auto_apply,
smart_tag.confidence_threshold
))
conn.commit()
return smart_tag.id
def get_smart_tags(self) -> List[SmartTag]:
"""获取所有智能标签"""
conn = self._get_connection()
cursor = conn.cursor()
cursor.execute("SELECT * FROM smart_tags")
rows = cursor.fetchall()
smart_tags = []
for row in rows:
keywords = json.loads(row['keywords_json'])
smart_tags.append(
SmartTag(
id=row['id'],
name=row['name'],
keywords=keywords,
description=row['description'],
auto_apply=bool(row['auto_apply']),
confidence_threshold=row['confidence_threshold']
)
)
return smart_tags
def apply_smart_tags(self, card: KnowledgeCard) -> KnowledgeCard:
"""自动应用智能标签"""
smart_tags = self.get_smart_tags()
for smart_tag in smart_tags:
if smart_tag.should_apply(card) and smart_tag.name not in card.tags:
card.tags.append(smart_tag.name)
return card
# === 统计信息 ===
def get_statistics(self) -> Dict[str, Any]:
"""获取统计信息"""
conn = self._get_connection()
cursor = conn.cursor()
stats = {}
# 卡片总数
cursor.execute("SELECT COUNT(*) as count FROM knowledge_cards WHERE status != 'deleted'")
stats['total_cards'] = cursor.fetchone()['count']
# 按类型统计
cursor.execute("""
SELECT type, COUNT(*) as count
FROM knowledge_cards
WHERE status != 'deleted'
GROUP BY type
""")
stats['cards_by_type'] = {row['type']: row['count'] for row in cursor.fetchall()}
# 按状态统计
cursor.execute("""
SELECT status, COUNT(*) as count
FROM knowledge_cards
GROUP BY status
""")
stats['cards_by_status'] = {row['status']: row['count'] for row in cursor.fetchall()}
# 最活跃的标签
cursor.execute("""
SELECT name, usage_count
FROM tags
ORDER BY usage_count DESC
LIMIT 10
""")
stats['top_tags'] = [
{'name': row['name'], 'count': row['usage_count']}
for row in cursor.fetchall()
]
# 最近更新
cursor.execute("""
SELECT COUNT(*) as count
FROM knowledge_cards
WHERE updated_at > datetime('now', '-7 days')
""")
stats['recently_updated'] = cursor.fetchone()['count']
return stats
def close(self):
"""关闭数据库连接"""
if self._connection:
self._connection.close()
self._connection = None
数据库服务架构:

2.3 核心业务逻辑
现在让我们实现应用的核心业务逻辑:
python
# src/backend/services/knowledge_service.py
"""
知识管理服务
处理所有与知识卡片相关的业务逻辑
"""
from typing import List, Dict, Any, Optional, Tuple
from datetime import datetime, timedelta
import re
from collections import Counter
from ..shared.models import (
KnowledgeCard, CardType, CardStatus,
Tag, CardRelationship, SmartTag
)
from ..database.database_service import DatabaseService
class KnowledgeService:
"""知识管理服务"""
def __init__(self, db_service: DatabaseService):
self.db = db_service
# === 卡片管理 ===
def create_card(
self,
title: str,
content: str,
card_type: CardType = CardType.NOTE,
tags: Optional[List[str]] = None
) -> KnowledgeCard:
"""创建新卡片"""
card = KnowledgeCard(
title=title,
content=content,
type=card_type,
tags=tags or []
)
# 应用智能标签
card = self.db.apply_smart_tags(card)
# 保存到数据库
self.db.save_card(card)
return card
def update_card(
self,
card_id: str,
**updates
) -> Optional[KnowledgeCard]:
"""更新卡片"""
card = self.db.get_card(card_id)
if not card:
return None
# 更新属性
card.update(**updates)
# 重新应用智能标签
if 'title' in updates or 'content' in updates:
card = self.db.apply_smart_tags(card)
# 保存更新
self.db.save_card(card)
return card
def get_card_with_details(self, card_id: str) -> Optional[Dict[str, Any]]:
"""获取卡片详情(包含相关卡片)"""
card = self.db.get_card(card_id)
if not card:
return None
# 获取相关卡片
related_cards = self.get_related_cards(card_id)
# 获取建议标签
suggested_tags = self.suggest_tags_for_card(card)
return {
'card': card,
'related_cards': related_cards,
'suggested_tags': suggested_tags,
'statistics': self.get_card_statistics(card)
}
def search_cards_advanced(
self,
query: str,
filters: Optional[Dict[str, Any]] = None
) -> List[KnowledgeCard]:
"""高级搜索"""
filters = filters or {}
# 如果是简单搜索,使用数据库搜索
if len(query.split()) <= 2:
return self.db.search_cards(query)
# 高级搜索逻辑
cards = self.db.get_all_cards()
# 应用过滤器
if filters.get('type'):
cards = [c for c in cards if c.type.value == filters['type']]
if filters.get('status'):
cards = [c for c in cards if c.status.value == filters['status']]
if filters.get('tags'):
required_tags = set(filters['tags'])
cards = [
c for c in cards
if required_tags.issubset(set(c.tags))
]
# 搜索评分
scored_cards = []
for card in cards:
score = self._calculate_search_score(card, query)
if score > 0:
scored_cards.append((score, card))
# 按分数排序
scored_cards.sort(key=lambda x: x[0], reverse=True)
return [card for _, card in scored_cards[:50]]
def _calculate_search_score(self, card: KnowledgeCard, query: str) -> float:
"""计算搜索相关性分数"""
score = 0.0
# 标题匹配(权重最高)
if query.lower() in card.title.lower():
score += 5.0
# 内容匹配
content_matches = len(re.findall(
re.escape(query.lower()),
card.content.lower()
))
score += content_matches * 0.5
# 标签匹配
for tag in card.tags:
if query.lower() in tag.lower():
score += 2.0
# 时间衰减(越新的卡片分数越高)
days_old = (datetime.now() - card.updated_at).days
time_factor = max(0, 1.0 - (days_old / 365)) # 一年内衰减
score *= (0.5 + 0.5 * time_factor)
return score
# === 标签管理 ===
def suggest_tags_for_card(self, card: KnowledgeCard) -> List[str]:
"""为卡片建议标签"""
suggestions = []
# 从内容中提取关键词
text = f"{card.title} {card.content}"
words = re.findall(r'\b\w{3,}\b', text.lower())
# 统计词频
word_counts = Counter(words)
# 排除常见词
common_words = {'the', 'and', 'for', 'with', 'this', 'that', 'have', 'from'}
for word in common_words:
word_counts.pop(word, None)
# 获取高频词作为建议
for word, count in word_counts.most_common(5):
if count >= 2: # 至少出现两次
suggestions.append(word)
return suggestions
def merge_tags(self, old_tag: str, new_tag: str) -> bool:
"""合并标签"""
# 获取所有使用旧标签的卡片
all_cards = self.db.get_all_cards()
cards_to_update = [
card for card in all_cards
if old_tag in card.tags
]
# 更新卡片标签
for card in cards_to_update:
card.tags = [
new_tag if tag == old_tag else tag
for tag in card.tags
]
self.db.save_card(card)
# 删除旧标签
# 注意:这里简化处理,实际应该更新标签表
return True
# === 卡片关系 ===
def create_relationship(
self,
source_card_id: str,
target_card_id: str,
relationship_type: str = "related"
) -> Optional[CardRelationship]:
"""创建卡片关系"""
# 验证卡片存在
source_card = self.db.get_card(source_card_id)
target_card = self.db.get_card(target_card_id)
if not source_card or not target_card:
return None
# 检查关系是否已存在
# 这里简化处理
relationship = CardRelationship(
source_card_id=source_card_id,
target_card_id=target_card_id,
relationship_type=relationship_type
)
# 保存关系
# 这里需要实现保存逻辑
return relationship
def get_related_cards(self, card_id: str) -> List[Dict[str, Any]]:
"""获取相关卡片"""
# 这里简化实现
all_cards = self.db.get_all_cards()
# 查找有相同标签的卡片
current_card = self.db.get_card(card_id)
if not current_card:
return []
related = []
for card in all_cards:
if card.id == card_id:
continue
# 计算相似度
similarity = self._calculate_card_similarity(current_card, card)
if similarity > 0.3: # 相似度阈值
related.append({
'card': card,
'similarity': similarity,
'reason': self._get_similarity_reason(current_card, card)
})
# 按相似度排序
related.sort(key=lambda x: x['similarity'], reverse=True)
return related[:10] # 返回前10个
def _calculate_card_similarity(
self,
card1: KnowledgeCard,
card2: KnowledgeCard
) -> float:
"""计算卡片相似度"""
similarity = 0.0
# 标签相似度
tags1 = set(card1.tags)
tags2 = set(card2.tags)
if tags1 and tags2:
tag_similarity = len(tags1.intersection(tags2)) / len(tags1.union(tags2))
similarity += tag_similarity * 0.4
# 内容相似度(简化版本)
content_words1 = set(re.findall(r'\b\w{4,}\b', card1.content.lower()))
content_words2 = set(re.findall(r'\b\w{4,}\b', card2.content.lower()))
if content_words1 and content_words2:
common_words = content_words1.intersection(content_words2)
total_words = content_words1.union(content_words2)
if total_words:
content_similarity = len(common_words) / len(total_words)
similarity += content_similarity * 0.6
return similarity
def _get_similarity_reason(
self,
card1: KnowledgeCard,
card2: KnowledgeCard
) -> str:
"""获取相似原因"""
common_tags = set(card1.tags).intersection(set(card2.tags))
if common_tags:
return f"共享标签: {', '.join(list(common_tags)[:3])}"
return "内容相关"
# === 智能功能 ===
def get_recommendations(self, card_id: str) -> List[Dict[str, Any]]:
"""获取推荐卡片"""
# 这里实现简单的推荐逻辑
all_cards = self.db.get_all_cards()
current_card = self.db.get_card(card_id)
if not current_card:
return []
recommendations = []
for card in all_cards:
if card.id == card_id or card.status != CardStatus.ACTIVE:
continue
# 计算推荐分数
score = 0.0
# 相同类型加分
if card.type == current_card.type:
score += 1.0
# 相似标签加分
common_tags = len(set(card.tags).intersection(set(current_card.tags)))
score += common_tags * 0.5
# 最近更新加分
days_since_update = (datetime.now() - card.updated_at).days
if days_since_update < 7:
score += 1.0
if score > 0.5:
recommendations.append({
'card': card,
'score': score,
'reason': self._get_recommendation_reason(card, current_card)
})
recommendations.sort(key=lambda x: x['score'], reverse=True)
return recommendations[:5]
def _get_recommendation_reason(
self,
card: KnowledgeCard,
current_card: KnowledgeCard
) -> str:
"""获取推荐理由"""
if card.type == current_card.type:
return f"相同类型: {card.type.value}"
common_tags = set(card.tags).intersection(set(current_card.tags))
if common_tags:
return f"共享标签: {', '.join(list(common_tags)[:2])}"
return "您可能感兴趣"
def generate_knowledge_graph(self) -> Dict[str, Any]:
"""生成知识图谱数据"""
all_cards = self.db.get_all_cards(CardStatus.ACTIVE)
nodes = []
links = []
# 创建节点
for card in all_cards:
nodes.append({
'id': card.id,
'name': card.title,
'type': card.type.value,
'value': len(card.tags) + 1, # 节点大小
'group': hash(card.type.value) % 5 # 分组
})
# 创建连接(基于标签)
for i, card1 in enumerate(all_cards):
for j, card2 in enumerate(all_cards[i+1:], i+1):
common_tags = set(card1.tags).intersection(set(card2.tags))
if common_tags:
links.append({
'source': card1.id,
'target': card2.id,
'value': len(common_tags), # 连接强度
'tags': list(common_tags)[:2]
})
return {
'nodes': nodes[:50], # 限制数量
'links': links[:100] # 限制数量
}
# === 统计和分析 ===
def get_card_statistics(self, card: KnowledgeCard) -> Dict[str, Any]:
"""获取卡片统计信息"""
return {
'word_count': len(card.content.split()),
'tag_count': len(card.tags),
'age_days': (datetime.now() - card.created_at).days,
'last_updated_days': (datetime.now() - card.updated_at).days
}
def get_learning_insights(self) -> Dict[str, Any]:
"""获取学习洞察"""
all_cards = self.db.get_all_cards()
insights = {
'total_cards': len(all_cards),
'active_cards': len([c for c in all_cards if c.status == CardStatus.ACTIVE]),
'cards_by_type': {},
'cards_by_week': {},
'knowledge_growth': []
}
# 按类型统计
for card in all_cards:
card_type = card.type.value
insights['cards_by_type'][card_type] = (
insights['cards_by_type'].get(card_type, 0) + 1
)
# 按周统计
for card in all_cards:
week_key = card.created_at.strftime("%Y-%W")
insights['cards_by_week'][week_key] = (
insights['cards_by_week'].get(week_key, 0) + 1
)
# 知识增长趋势
sorted_weeks = sorted(insights['cards_by_week'].keys())
cumulative = 0
for week in sorted_weeks[-12:]: # 最近12周
cumulative += insights['cards_by_week'][week]
insights['knowledge_growth'].append({
'week': week,
'new_cards': insights['cards_by_week'][week],
'cumulative': cumulative
})
return insights
业务逻辑架构:

第三部分:前端界面实现
3.1 主应用框架
现在让我们实现QML前端界面。首先是应用的主框架:
javascript
// src/frontend/qml/main.qml
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Window
import Qt.labs.platform
import Qt5Compat.GraphicalEffects
ApplicationWindow {
id: appWindow
width: 1200
height: 800
visible: true
title: "智慧笔记 - 个人知识管理"
minimumWidth: 800
minimumHeight: 600
// 主题管理
property bool darkMode: false
property color primaryColor: "#2196F3"
property color backgroundColor: darkMode ? "#1E1E1E" : "#F5F5F5"
property color surfaceColor: darkMode ? "#2D2D2D" : "#FFFFFF"
property color textColor: darkMode ? "#FFFFFF" : "#333333"
property color textSecondaryColor: darkMode ? "#B0B0B0" : "#666666"
// 平台检测
readonly property bool isMobile: Qt.platform.os === "android" || Qt.platform.os === "ios"
readonly property bool isDesktop: !isMobile
// 响应式设计
property real responsiveScale: Math.min(width / 1200, 1.0)
property bool showSidebar: width > 800
property bool showRightPanel: width > 1000
// 初始化
Component.onCompleted: {
console.log("智慧笔记启动")
console.log("平台:", Qt.platform.os)
console.log("屏幕尺寸:", width, "x", height)
// 加载用户设置
loadSettings()
}
// 加载设置
function loadSettings() {
// 这里从本地存储加载设置
darkMode = false
}
// 保存设置
function saveSettings() {
// 这里保存设置到本地存储
}
// 主布局
SplitView {
anchors.fill: parent
// 左侧边栏
Sidebar {
id: sidebar
SplitView.preferredWidth: 280
SplitView.minimumWidth: 200
SplitView.maximumWidth: 400
visible: showSidebar
}
// 主内容区域
MainContent {
id: mainContent
SplitView.fillWidth: true
}
// 右侧面板
RightPanel {
id: rightPanel
SplitView.preferredWidth: 320
SplitView.minimumWidth: 250
SplitView.maximumWidth: 450
visible: showRightPanel
}
}
// 状态栏
StatusBar {
id: statusBar
}
// 快捷键
Shortcut {
sequence: "Ctrl+N"
onActivated: mainContent.createNewCard()
}
Shortcut {
sequence: "Ctrl+F"
onActivated: mainContent.showSearch()
}
Shortcut {
sequence: "Ctrl+," // Ctrl+逗号
onActivated: settingsDialog.open()
}
// 设置对话框
SettingsDialog {
id: settingsDialog
}
// 关于对话框
AboutDialog {
id: aboutDialog
}
}
3.2 知识卡片组件
让我们创建一个美观的知识卡片组件:
javascript
// src/frontend/qml/components/KnowledgeCard.qml
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
Rectangle {
id: card
// 属性
property var cardData: ({})
property bool isSelected: false
property bool isExpanded: false
property real cornerRadius: 12
// 信号
signal clicked()
signal doubleClicked()
signal tagClicked(string tag)
signal editRequested()
signal deleteRequested()
// 尺寸
width: ListView.view ? ListView.view.width : 300
height: isExpanded ? contentColumn.implicitHeight + 40 : 120
// 外观
color: surfaceColor
radius: cornerRadius
border.color: isSelected ? primaryColor : "#E0E0E0"
border.width: isSelected ? 2 : 1
// 阴影效果
layer.enabled: true
layer.effect: DropShadow {
color: "#20000000"
radius: 8
samples: 16
verticalOffset: 2
}
// 卡片内容
ColumnLayout {
id: contentColumn
anchors.fill: parent
anchors.margins: 16
spacing: 12
// 标题行
RowLayout {
Layout.fillWidth: true
// 类型图标
Rectangle {
width: 32
height: 32
radius: 16
color: getTypeColor(cardData.type)
Text {
text: getTypeIcon(cardData.type)
color: "white"
font.pixelSize: 16
anchors.centerIn: parent
}
}
// 标题
ColumnLayout {
spacing: 2
Layout.fillWidth: true
Text {
text: cardData.title || "无标题"
font.pixelSize: 18
font.bold: true
color: textColor
elide: Text.ElideRight
maximumLineCount: 1
Layout.fillWidth: true
}
// 元信息
RowLayout {
spacing: 8
Text {
text: formatDate(cardData.updated_at)
color: textSecondaryColor
font.pixelSize: 12
}
// 状态标签
Rectangle {
visible: cardData.status && cardData.status !== "active"
height: 16
radius: 8
color: getStatusColor(cardData.status)
Text {
text: getStatusText(cardData.status)
color: "white"
font.pixelSize: 10
font.bold: true
anchors.centerIn: parent
anchors.margins: 6
}
}
}
}
// 操作按钮
MenuButton {
Layout.alignment: Qt.AlignTop
MenuItem {
text: "编辑"
onTriggered: card.editRequested()
}
MenuItem {
text: isExpanded ? "收起" : "展开"
onTriggered: isExpanded = !isExpanded
}
MenuItem {
text: "复制链接"
}
MenuSeparator {}
MenuItem {
text: "删除"
textColor: "red"
onTriggered: card.deleteRequested()
}
}
}
// 内容预览
Text {
id: contentPreview
text: cardData.content || ""
color: textSecondaryColor
font.pixelSize: 14
wrapMode: Text.WordWrap
maximumLineCount: isExpanded ? 0 : 2
elide: Text.ElideRight
lineHeight: 1.4
Layout.fillWidth: true
visible: text.length > 0
}
// 标签区域
Flow {
id: tagsFlow
spacing: 6
Layout.fillWidth: true
visible: cardData.tags && cardData.tags.length > 0
Repeater {
model: cardData.tags || []
Rectangle {
height: 24
radius: 12
color: Qt.lighter(primaryColor, 1.8)
RowLayout {
height: parent.height
spacing: 4
Text {
text: modelData
color: primaryColor
font.pixelSize: 11
font.bold: true
}
Text {
text: "×"
color: primaryColor
font.pixelSize: 14
font.bold: true
visible: false // 暂时隐藏删除按钮
}
}
MouseArea {
anchors.fill: parent
onClicked: card.tagClicked(modelData)
cursorShape: Qt.PointingHandCursor
}
}
}
}
// 优先级指示器
RowLayout {
spacing: 8
visible: cardData.priority > 0
Text {
text: "优先级:"
color: textSecondaryColor
font.pixelSize: 12
}
Repeater {
model: 5
Rectangle {
width: 12
height: 12
radius: 6
color: index < cardData.priority ?
getPriorityColor(cardData.priority) : "#E0E0E0"
}
}
}
}
// 点击效果
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: (mouse) => {
if (mouse.button === Qt.LeftButton) {
card.clicked()
}
}
onDoubleClicked: card.doubleClicked()
// 点击反馈
Rectangle {
anchors.fill: parent
color: "white"
opacity: parent.pressed ? 0.1 : 0
radius: card.radius
}
}
// 工具函数
function getTypeColor(type) {
switch(type) {
case "note": return "#4CAF50"
case "idea": return "#2196F3"
case "task": return "#FF9800"
case "question": return "#9C27B0"
case "answer": return "#009688"
case "reference": return "#795548"
default: return "#9E9E9E"
}
}
function getTypeIcon(type) {
switch(type) {
case "note": return "📝"
case "idea": return "💡"
case "task": return "✅"
case "question": return "❓"
case "answer": return "💬"
case "reference": return "📚"
default: return "📄"
}
}
function getStatusColor(status) {
switch(status) {
case "draft": return "#FF9800"
case "active": return "#4CAF50"
case "archived": return "#9E9E9E"
case "deleted": return "#F44336"
default: return "#9E9E9E"
}
}
function getStatusText(status) {
switch(status) {
case "draft": return "草稿"
case "active": return "活跃"
case "archived": return "归档"
case "deleted": return "已删除"
default: return "未知"
}
}
function getPriorityColor(priority) {
if (priority <= 2) return "#4CAF50"
if (priority <= 4) return "#FF9800"
return "#F44336"
}
function formatDate(dateString) {
if (!dateString) return ""
var date = new Date(dateString)
var now = new Date()
var diffMs = now - date
var diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24))
if (diffDays === 0) {
return "今天"
} else if (diffDays === 1) {
return "昨天"
} else if (diffDays < 7) {
return diffDays + "天前"
} else if (diffDays < 30) {
return Math.floor(diffDays / 7) + "周前"
} else {
return date.toLocaleDateString()
}
}
}
组件交互流程:

.3 知识图谱可视化
让我们实现一个简单的知识图谱可视化组件:
javascript
// src/frontend/qml/components/KnowledgeGraph.qml
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
Rectangle {
id: graph
// 属性
property var graphData: ({ nodes: [], links: [] })
property var selectedNode: null
// 信号
signal nodeClicked(var node)
signal nodeDoubleClicked(var node)
// 外观
color: "#1E1E1E"
radius: 8
// 画布
Canvas {
id: canvas
anchors.fill: parent
// 物理模拟属性
property real gravity: 0.1
property real springLength: 100
property real springStrength: 0.002
property real repulsionStrength: 10000
property real friction: 0.9
// 节点位置
property var nodePositions: ({})
property var nodeVelocities: ({})
onPaint: {
var ctx = getContext("2d")
// 清空画布
ctx.clearRect(0, 0, width, height)
// 绘制连接
ctx.strokeStyle = "#666666"
ctx.lineWidth = 1
for (var i = 0; i < graphData.links.length; i++) {
var link = graphData.links[i]
var source = graphData.nodes.find(n => n.id === link.source)
var target = graphData.nodes.find(n => n.id === link.target)
if (source && target && nodePositions[source.id] && nodePositions[target.id]) {
var pos1 = nodePositions[source.id]
var pos2 = nodePositions[target.id]
// 绘制连线
ctx.beginPath()
ctx.moveTo(pos1.x, pos1.y)
ctx.lineTo(pos2.x, pos2.y)
ctx.stroke()
// 绘制连线上的标签
if (link.tags && link.tags.length > 0) {
var midX = (pos1.x + pos2.x) / 2
var midY = (pos1.y + pos2.y) / 2
ctx.fillStyle = "#666666"
ctx.font = "10px Arial"
ctx.fillText(link.tags[0], midX, midY)
}
}
}
// 绘制节点
for (var i = 0; i < graphData.nodes.length; i++) {
var node = graphData.nodes[i]
var pos = nodePositions[node.id]
if (!pos) continue
// 节点颜色
var colors = ["#4CAF50", "#2196F3", "#FF9800", "#9C27B0", "#009688"]
var color = colors[node.group % colors.length]
// 绘制节点
ctx.beginPath()
ctx.arc(pos.x, pos.y, 10 + node.value * 2, 0, Math.PI * 2)
if (selectedNode && selectedNode.id === node.id) {
// 选中的节点
ctx.fillStyle = "white"
ctx.fill()
ctx.beginPath()
ctx.arc(pos.x, pos.y, 12 + node.value * 2, 0, Math.PI * 2)
ctx.strokeStyle = "#FFEB3B"
ctx.lineWidth = 3
ctx.stroke()
ctx.fillStyle = color
ctx.fill()
} else {
// 普通节点
var gradient = ctx.createRadialGradient(
pos.x, pos.y, 0,
pos.x, pos.y, 20
)
gradient.addColorStop(0, Qt.lighter(color, 1.5))
gradient.addColorStop(1, color)
ctx.fillStyle = gradient
ctx.fill()
}
// 绘制节点标签
ctx.fillStyle = "white"
ctx.font = "bold 12px Arial"
ctx.textAlign = "center"
ctx.fillText(node.name.substring(0, 10), pos.x, pos.y - 20)
// 绘制类型
ctx.fillStyle = "#AAAAAA"
ctx.font = "10px Arial"
ctx.fillText(node.type, pos.x, pos.y + 30)
}
}
// 初始化物理模拟
Component.onCompleted: {
initializePhysics()
simulationTimer.start()
}
// 初始化物理模拟
function initializePhysics() {
nodePositions = {}
nodeVelocities = {}
// 随机初始化位置
for (var i = 0; i < graphData.nodes.length; i++) {
var node = graphData.nodes[i]
nodePositions[node.id] = {
x: Math.random() * width,
y: Math.random() * height
}
nodeVelocities[node.id] = { x: 0, y: 0 }
}
}
// 物理模拟
function simulate() {
// 计算排斥力
for (var i = 0; i < graphData.nodes.length; i++) {
var node1 = graphData.nodes[i]
var pos1 = nodePositions[node1.id]
var vel1 = nodeVelocities[node1.id]
for (var j = i + 1; j < graphData.nodes.length; j++) {
var node2 = graphData.nodes[j]
var pos2 = nodePositions[node2.id]
var dx = pos2.x - pos1.x
var dy = pos2.y - pos1.y
var distance = Math.sqrt(dx * dx + dy * dy) + 0.1
// 排斥力
var force = repulsionStrength / (distance * distance)
var fx = force * dx / distance
var fy = force * dy / distance
vel1.x -= fx
vel1.y -= fy
nodeVelocities[node2.id].x += fx
nodeVelocities[node2.id].y += fy
}
}
// 计算弹簧力
for (var i = 0; i < graphData.links.length; i++) {
var link = graphData.links[i]
var pos1 = nodePositions[link.source]
var pos2 = nodePositions[link.target]
var vel1 = nodeVelocities[link.source]
var vel2 = nodeVelocities[link.target]
if (!pos1 || !pos2) continue
var dx = pos2.x - pos1.x
var dy = pos2.y - pos1.y
var distance = Math.sqrt(dx * dx + dy * dy)
// 弹簧力
var force = springStrength * (distance - springLength)
var fx = force * dx / distance
var fy = force * dy / distance
vel1.x += fx
vel1.y += fy
vel2.x -= fx
vel2.y -= fy
}
// 应用速度和边界
for (var i = 0; i < graphData.nodes.length; i++) {
var node = graphData.nodes[i]
var pos = nodePositions[node.id]
var vel = nodeVelocities[node.id]
// 更新速度
vel.x *= friction
vel.y *= friction
// 更新位置
pos.x += vel.x
pos.y += vel.y
// 边界碰撞
var radius = 10 + node.value * 2
if (pos.x < radius) {
pos.x = radius
vel.x = -vel.x * 0.5
}
if (pos.x > width - radius) {
pos.x = width - radius
vel.x = -vel.x * 0.5
}
if (pos.y < radius) {
pos.y = radius
vel.y = -vel.y * 0.5
}
if (pos.y > height - radius) {
pos.y = height - radius
vel.y = -vel.y * 0.5
}
}
// 重绘画布
canvas.requestPaint()
}
// 模拟定时器
Timer {
id: simulationTimer
interval: 16 // ~60fps
running: false
repeat: true
onTriggered: canvas.simulate()
}
// 鼠标交互
MouseArea {
id: graphMouseArea
anchors.fill: parent
hoverEnabled: true
drag.threshold: 5
property var draggedNode: null
property point lastMousePos
onPressed: (mouse) => {
// 检查是否点击了节点
var clickedNode = getNodeAt(mouse.x, mouse.y)
if (clickedNode) {
draggedNode = clickedNode
selectedNode = clickedNode
graph.nodeClicked(clickedNode)
lastMousePos = Qt.point(mouse.x, mouse.y)
} else {
selectedNode = null
}
}
onPositionChanged: (mouse) => {
if (draggedNode) {
// 拖动节点
var pos = canvas.nodePositions[draggedNode.id]
var vel = canvas.nodeVelocities[draggedNode.id]
var dx = mouse.x - lastMousePos.x
var dy = mouse.y - lastMousePos.y
pos.x += dx
pos.y += dy
vel.x = dx * 0.5
vel.y = dy * 0.5
lastMousePos = Qt.point(mouse.x, mouse.y)
}
}
onReleased: {
draggedNode = null
}
onDoubleClicked: (mouse) => {
var clickedNode = getNodeAt(mouse.x, mouse.y)
if (clickedNode) {
graph.nodeDoubleClicked(clickedNode)
}
}
// 获取鼠标位置的节点
function getNodeAt(x, y) {
for (var i = 0; i < graphData.nodes.length; i++) {
var node = graphData.nodes[i]
var pos = canvas.nodePositions[node.id]
if (!pos) continue
var dx = x - pos.x
var dy = y - pos.y
var distance = Math.sqrt(dx * dx + dy * dy)
var radius = 10 + node.value * 2
if (distance < radius) {
return node
}
}
return null
}
}
}
// 控制面板
Rectangle {
anchors.top: parent.top
anchors.right: parent.right
anchors.margins: 10
width: 200
height: 120
radius: 6
color: "#2D2D2D"
opacity: 0.9
ColumnLayout {
anchors.fill: parent
anchors.margins: 10
spacing: 5
Text {
text: "知识图谱"
color: "white"
font.bold: true
font.pixelSize: 16
}
RowLayout {
Text {
text: "节点:"
color: "#AAAAAA"
}
Text {
text: graphData.nodes.length
color: "white"
}
Item { Layout.fillWidth: true }
}
RowLayout {
Text {
text: "连接:"
color: "#AAAAAA"
}
Text {
text: graphData.links.length
color: "white"
}
Item { Layout.fillWidth: true }
}
Button {
text: "重置布局"
onClicked: canvas.initializePhysics()
}
}
}
}
知识图谱可视化流程:

第四部分:集成与优化
4.1 应用集成
让我们将所有组件集成在一起:
python
# src/backend/main.py
"""
智慧笔记应用主入口
"""
import sys
import os
from pathlib import Path
# 添加项目根目录到Python路径
project_root = Path(__file__).parent.parent.parent
sys.path.insert(0, str(project_root))
from PySide6.QtGui import QGuiApplication
from PySide6.QtQml import QQmlApplicationEngine
from PySide6.QtCore import QUrl
from backend.database.database_service import DatabaseService
from backend.services.knowledge_service import KnowledgeService
class SmartNotesApp:
"""智慧笔记应用主类"""
def __init__(self):
# 初始化应用
self.app = QGuiApplication(sys.argv)
self.app.setApplicationName("智慧笔记")
self.app.setOrganizationName("SmartNotes")
self.app.setOrganizationDomain("smartnotes.app")
# 初始化数据库
self.db_service = DatabaseService()
# 初始化业务服务
self.knowledge_service = KnowledgeService(self.db_service)
# 初始化QML引擎
self.engine = QQmlApplicationEngine()
def setup_qml_context(self):
"""设置QML上下文"""
# 注册Python对象到QML
context = self.engine.rootContext()
context.setContextProperty("knowledgeService", self.knowledge_service)
context.setContextProperty("databaseService", self.db_service)
def load_qml(self):
"""加载QML界面"""
# 获取QML文件路径
qml_path = project_root / "src" / "frontend" / "qml" / "main.qml"
if not qml_path.exists():
print(f"错误: 找不到QML文件: {qml_path}")
sys.exit(1)
# 加载QML
self.engine.load(QUrl.fromLocalFile(str(qml_path)))
if not self.engine.rootObjects():
print("错误: 加载QML失败")
sys.exit(1)
def run(self):
"""运行应用"""
self.setup_qml_context()
self.load_qml()
# 运行应用
return self.app.exec()
def cleanup(self):
"""清理资源"""
self.db_service.close()
def main():
"""主函数"""
app = SmartNotesApp()
try:
return app.run()
except KeyboardInterrupt:
print("\n应用被用户中断")
except Exception as e:
print(f"应用错误: {e}")
import traceback
traceback.print_exc()
finally:
app.cleanup()
if __name__ == "__main__":
sys.exit(main())
4.2 构建和部署
创建一个简单的构建脚本:
python
# scripts/build.py
"""
构建脚本
"""
import sys
import os
import shutil
from pathlib import Path
import subprocess
import argparse
class Builder:
"""应用构建器"""
def __init__(self, project_root=None):
self.project_root = Path(project_root or Path(__file__).parent.parent)
self.build_dir = self.project_root / "build"
def clean(self):
"""清理构建目录"""
if self.build_dir.exists():
print(f"清理构建目录: {self.build_dir}")
shutil.rmtree(self.build_dir)
def prepare_build_dir(self):
"""准备构建目录"""
self.build_dir.mkdir(exist_ok=True)
print(f"创建构建目录: {self.build_dir}")
def copy_resources(self):
"""复制资源文件"""
resources_dir = self.project_root / "src" / "frontend" / "resources"
if resources_dir.exists():
dest_dir = self.build_dir / "resources"
if dest_dir.exists():
shutil.rmtree(dest_dir)
shutil.copytree(resources_dir, dest_dir)
print(f"复制资源文件到: {dest_dir}")
def copy_qml_files(self):
"""复制QML文件"""
qml_dir = self.project_root / "src" / "frontend" / "qml"
if qml_dir.exists():
dest_dir = self.build_dir / "qml"
if dest_dir.exists():
shutil.rmtree(dest_dir)
shutil.copytree(qml_dir, dest_dir)
print(f"复制QML文件到: {dest_dir}")
def copy_python_files(self):
"""复制Python文件"""
backend_dir = self.project_root / "src" / "backend"
if backend_dir.exists():
dest_dir = self.build_dir / "backend"
if dest_dir.exists():
shutil.rmtree(dest_dir)
shutil.copytree(backend_dir, dest_dir)
print(f"复制Python文件到: {dest_dir}")
# 复制共享文件
shared_dir = self.project_root / "src" / "shared"
if shared_dir.exists():
dest_dir = self.build_dir / "shared"
if dest_dir.exists():
shutil.rmtree(dest_dir)
shutil.copytree(shared_dir, dest_dir)
print(f"复制共享文件到: {dest_dir}")
def create_entry_point(self, platform="desktop"):
"""创建入口点"""
if platform == "desktop":
entry_content = '''#!/usr/bin/env python3
"""
智慧笔记应用入口点
"""
import sys
import os
# 添加构建目录到Python路径
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from backend.main import main
if __name__ == "__main__":
sys.exit(main())
'''
elif platform == "android":
entry_content = '''#!/usr/bin/env python3
"""
智慧笔记Android入口点
"""
# Android特定初始化代码
'''
else:
entry_content = '''#!/usr/bin/env python3
"""
智慧笔记应用入口点
"""
import sys
import os
# 添加构建目录到Python路径
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from backend.main import main
if __name__ == "__main__":
sys.exit(main())
'''
entry_path = self.build_dir / "smartnotes.py"
with open(entry_path, "w", encoding="utf-8") as f:
f.write(entry_content)
# 设置可执行权限(Unix-like系统)
if os.name != 'nt':
os.chmod(entry_path, 0o755)
print(f"创建入口点: {entry_path}")
def create_requirements_file(self):
"""创建requirements.txt文件"""
requirements_content = '''PySide6>=6.5.0
'''
requirements_path = self.build_dir / "requirements.txt"
with open(requirements_path, "w", encoding="utf-8") as f:
f.write(requirements_content)
print(f"创建依赖文件: {requirements_path}")
def create_readme(self):
"""创建README文件"""
readme_content = """# 智慧笔记
一个跨平台的个人知识管理应用。
## 功能特性
- 📝 创建和管理知识卡片
- 🏷️ 智能标签和分类
- 🔗 知识图谱可视化
- 🔍 强大的搜索功能
- 📊 学习统计和洞察
- 🌐 跨平台支持
## 运行要求
- Python 3.8 或更高版本
- PySide6
- SQLite3
## 快速开始
1. 安装依赖:
bash
pip install -r requirements.txt
2. 运行应用:
bash
python smartnotes.py
## 构建
使用构建脚本:
bash
python smartnotes.py
## 许可
MIT License
"""
readme_path = self.build_dir / "README.md"
with open(readme_path, "w", encoding="utf-8") as f:
f.write(readme_content)
print(f"创建README文件: {readme_path}")
def install_dependencies(self):
"""安装Python依赖"""
print("安装Python依赖...")
requirements_path = self.build_dir / "requirements.txt"
try:
subprocess.check_call([
sys.executable, "-m", "pip", "install",
"-r", str(requirements_path)
])
print("依赖安装完成")
except subprocess.CalledProcessError as e:
print(f"依赖安装失败: {e}")
return False
return True
def build_desktop(self):
"""构建桌面应用"""
print("开始构建桌面应用...")
# 清理构建目录
self.clean()
# 准备构建目录
self.prepare_build_dir()
# 复制文件
self.copy_resources()
self.copy_qml_files()
self.copy_python_files()
# 创建配置文件
self.create_entry_point("desktop")
self.create_requirements_file()
self.create_readme()
print("桌面应用构建完成")
# 提示如何运行
print("\n运行应用:")
print(f" cd {self.build_dir}")
print(" python smartnotes.py")
return True
def build_android(self):
"""构建Android应用"""
print("开始构建Android应用...")
# 清理构建目录
self.clean()
# 准备构建目录
self.prepare_build_dir()
# 复制文件
self.copy_resources()
self.copy_qml_files()
self.copy_python_files()
# 创建配置文件
self.create_entry_point("android")
self.create_requirements_file()
self.create_readme()
# 复制Android特定文件
android_dir = self.project_root / "platform" / "android"
if android_dir.exists():
dest_dir = self.build_dir / "android"
if dest_dir.exists():
shutil.rmtree(dest_dir)
shutil.copytree(android_dir, dest_dir)
print(f"复制Android文件到: {dest_dir}")
print("Android应用构建完成")
print("注意: Android构建需要额外的Qt for Android配置")
return True
def create_installer(self, platform="windows"):
"""创建安装包"""
print(f"开始创建{platform}安装包...")
installer_dir = self.project_root / "installer"
installer_dir.mkdir(exist_ok=True)
if platform == "windows":
# 创建Windows安装脚本
self._create_windows_installer()
elif platform == "macos":
# 创建macOS安装包
self._create_macos_installer()
elif platform == "linux":
# 创建Linux安装包
self._create_linux_installer()
print(f"{platform}安装包创建完成")
return True
def _create_windows_installer(self):
"""创建Windows安装器"""
# 这里可以使用PyInstaller或NSIS
# 简化版本:创建批处理脚本
installer_content = '''@echo off
echo 正在安装智慧笔记...
echo.
REM 检查Python
python --version >nul 2>&1
if errorlevel 1 (
echo 错误: 未找到Python
echo 请从 https://python.org 下载并安装Python
pause
exit /b 1
)
REM 检查PySide6
python -c "import PySide6" >nul 2>&1
if errorlevel 1 (
echo 安装PySide6...
pip install PySide6
)
REM 创建桌面快捷方式
echo 创建桌面快捷方式...
echo 安装完成!
pause
'''
installer_path = self.project_root / "installer" / "install.bat"
with open(installer_path, "w", encoding="gbk") as f:
f.write(installer_content)
print(f"创建Windows安装脚本: {installer_path}")
def _create_macos_installer(self):
"""创建macOS安装包"""
# 创建macOS应用包结构
app_name = "SmartNotes.app"
app_path = self.project_root / "installer" / app_name
# 创建应用包结构
contents_dir = app_path / "Contents"
macos_dir = contents_dir / "MacOS"
resources_dir = contents_dir / "Resources"
macos_dir.mkdir(parents=True, exist_ok=True)
resources_dir.mkdir(parents=True, exist_ok=True)
# 创建Info.plist
plist_content = '''<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDisplayName</key>
<string>智慧笔记</string>
<key>CFBundleExecutable</key>
<string>smartnotes</string>
<key>CFBundleIdentifier</key>
<string>com.smartnotes.app</string>
<key>CFBundleName</key>
<string>SmartNotes</string>
<key>CFBundleVersion</key>
<string>1.0.0</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>LSMinimumSystemVersion</key>
<string>10.12</string>
</dict>
</plist>
'''
plist_path = contents_dir / "Info.plist"
with open(plist_path, "w", encoding="utf-8") as f:
f.write(plist_content)
print(f"创建macOS应用包: {app_path}")
def _create_linux_installer(self):
"""创建Linux安装包"""
# 创建deb包控制文件
control_content = '''Package: smartnotes
Version: 1.0.0
Section: utils
Priority: optional
Architecture: all
Depends: python3, python3-pyside6.qml
Maintainer: SmartNotes Team <team@smartnotes.app>
Description: Personal Knowledge Management Application
A cross-platform personal knowledge management application
with smart tagging, knowledge graph, and learning insights.
'''
control_dir = self.project_root / "installer" / "DEBIAN"
control_dir.mkdir(parents=True, exist_ok=True)
control_path = control_dir / "control"
with open(control_path, "w", encoding="utf-8") as f:
f.write(control_content)
print(f"创建Linux安装包控制文件: {control_path}")
def main():
"""主函数"""
parser = argparse.ArgumentParser(description="智慧笔记构建工具")
parser.add_argument(
"action",
choices=["build", "clean", "install", "package"],
help="构建动作"
)
parser.add_argument(
"--platform",
choices=["desktop", "android", "ios"],
default="desktop",
help="目标平台"
)
parser.add_argument(
"--installer",
choices=["windows", "macos", "linux"],
help="创建安装包"
)
args = parser.parse_args()
builder = Builder()
if args.action == "clean":
builder.clean()
print("清理完成")
elif args.action == "build":
if args.platform == "desktop":
builder.build_desktop()
elif args.platform == "android":
builder.build_android()
elif args.platform == "ios":
print("iOS构建暂不支持")
elif args.action == "install":
builder.build_desktop()
if builder.install_dependencies():
print("安装完成")
else:
print("安装失败")
elif args.action == "package":
if args.installer:
builder.build_desktop()
builder.create_installer(args.installer)
else:
print("错误: 需要指定--installer参数")
else:
print("错误: 未知动作")
if __name__ == "__main__":
main()
bash
bash
pip install -r requirements.txt
2. 运行应用:
bash
python smartnotes.py
## 构建
使用构建脚本:
bash
python smartnotes.py
第五部分:总结
5.1 项目回顾
通过这个综合实战项目,我们构建了一个完整的跨平台个人知识管理应用。让我们回顾一下学到的关键知识:

5.2 关键知识点
-
架构设计:清晰的分层架构,前后端分离
-
数据建模:使用dataclass和枚举定义数据模型
-
数据库设计:SQLite数据库设计和管理
-
业务逻辑:复杂的业务逻辑实现和优化
-
QML界面:响应式、美观的用户界面
-
知识图谱:数据可视化技术
-
构建部署:自动化构建和打包流程
5.3 进一步改进方向
这个应用还有很多可以改进和扩展的地方:
-
云同步:添加云存储和同步功能
-
协作功能:支持多人协作编辑
-
AI增强:集成AI进行智能分析和建议
-
移动端优化:专门为移动端优化的界面
-
插件系统:支持第三方插件扩展
-
离线功能:完善的离线工作支持
-
数据分析:更深入的学习分析功能
5.4 学习建议
-
从简单开始:先实现核心功能,再逐步添加高级功能
-
测试驱动:为每个功能编写测试代码
-
代码审查:定期审查代码,保持代码质量
-
文档先行:编写清晰的文档和注释
-
用户反馈:收集用户反馈,持续改进
-
性能监控:监控应用性能,及时优化
结束语
恭喜你完成了这个完整的跨平台应用开发实战!通过这个项目,你不仅学习了Qt/QML和Python的技术知识,更重要的是掌握了完整应用开发的全流程。
记住,真正的开发者不是在象牙塔里写代码,而是在解决真实世界的问题。这个智慧笔记应用虽然只是一个示例,但它包含了现代应用开发的几乎所有关键要素。