mirror of
https://github.com/linshenkx/prompt-optimizer.git
synced 2026-05-07 05:56:49 +08:00
新增核心功能: - 基于单文档存储的上下文仓库(ContextRepo),支持CRUD和导入导出 - 预定义变量保护机制,防止系统变量被意外覆盖 - 上下文级变量管理,支持独立的变量作用域 - 完整的Electron IPC代理层,支持跨进程通信 - DataManager集成上下文服务,统一数据导入导出接口 技术实现亮点: - 使用updateData原子操作确保并发安全 - 三种导入模式(replace/append/merge)灵活应对不同场景 - 完整的TypeScript类型安全和错误处理体系 - 38个单元测试用例覆盖所有核心功能 - 国际化支持,移除所有硬编码字符串 架构优化: - 严格遵循SOLID原则的模块设计 - Repository模式实现数据持久化抽象 - 工厂模式支持多环境部署(Web/Desktop) - 统一的IImportExportable接口规范 UI增强: - 上下文编辑器支持实时持久化和变量管理 - 数据管理器新增上下文导入导出功能 - 完整的中英文国际化资源 文件变更: - 新增: packages/core/src/services/context/* (核心服务) - 新增: packages/core/tests/unit/context/* (单元测试) - 更新: DataManager支持上下文服务集成 - 更新: Electron主进程和预加载脚本IPC处理 - 更新: UI组件支持上下文功能和国际化 此次提交完成了上下文持久化功能的完整实现,为用户提供了 强大的对话上下文管理和变量系统,显著提升了应用的易用性。
22 KiB
22 KiB
Naive UI 重构组件文档
概述
本文档记录了经过 Naive UI 重构后的所有核心组件,包括新增的可访问性功能、性能优化和响应式支持。所有组件均符合 WCAG 2.1 AA/AAA 标准,提供完整的键盘导航和屏幕阅读器支持。
组件架构
设计原则
- SOLID: 单一职责、开闭原则、里氏替换、接口隔离、依赖倒置
- KISS: 保持简单,避免过度复杂的设计
- DRY: 避免重复代码,统一通用逻辑
- YAGNI: 只实现当前需要的功能
技术栈
- Vue 3: Composition API + TypeScript
- Naive UI: 现代化组件库
- 无障碍: WCAG 2.1 AA/AAA 标准
- 响应式: 移动端优先设计
- 性能: 虚拟化、防抖节流、懒加载
核心组件
1. ContextEditor(上下文编辑器)
描述: 完全重构的上下文编辑器,提供消息管理、变量处理和工具配置功能。
文件位置: packages/ui/src/components/ContextEditor.vue
Props
interface ContextEditorProps {
/** 模态框可见性 */
visible: boolean
/** 上下文状态数据 */
state: ContextState
/** 只读模式 */
readonly?: boolean
/** 自定义样式类名 */
customClass?: string
/** 尺寸大小 */
size?: 'small' | 'medium' | 'large'
/** 全局可用变量(用于变量解析和预览) */
availableVariables?: Record<string, string>
}
interface ContextState {
/** 消息列表 */
messages: ConversationMessage[]
/** 变量映射 */
variables: Record<string, string>
/** 工具配置 */
tools: ToolConfig[]
/** 显示变量预览 */
showVariablePreview: boolean
/** 显示工具管理器 */
showToolManager: boolean
/** 编辑模式 */
mode: 'edit' | 'preview'
}
Events
interface ContextEditorEmits {
/** 保存上下文 */
save: (context: ContextState) => void
/** 取消编辑 */
cancel: () => void
/** 更新可见性 */
'update:visible': (visible: boolean) => void
/** 上下文状态更新 */
'update:state': (state: ContextState) => void
/** 上下文内容变更 */
contextChange: (context: ContextState) => void
}
Slots
<template>
<ContextEditor>
<!-- 自定义工具栏 -->
<template #toolbar>
<NButton>自定义按钮</NButton>
</template>
<!-- 自定义底部 -->
<template #footer>
<div class="custom-footer">自定义内容</div>
</template>
</ContextEditor>
</template>
功能特性
- 多标签页界面: 消息编辑、变量管理、工具配置三个标签页
- 变量管理: 上下文级变量覆盖,不影响全局变量
- 预定义变量保护: 防止覆盖系统预定义变量
- 变量预览与缺失检测: 实时显示变量替换结果和缺失变量
- 直接持久化: 编辑内容实时保存,无需手动保存
- 导入导出支持: 支持上下文集合的批量导入导出
变量标签页
变量标签页专门用于管理上下文级变量覆盖:
- 变量列表: 显示变量名、当前值、来源(覆盖/全局/预定义)和状态
- 新增/编辑: 支持添加或修改上下文变量,自动校验格式和预定义冲突
- 删除覆盖: 删除覆盖项后回退到全局或预定义值
- 缺失变量处理: 点击缺失变量按钮直接进入上下文变量编辑
可访问性特性
- ARIA: 完整的
role、aria-label、aria-describedby支持 - 键盘导航: Tab、Enter、Escape、方向键导航
- 屏幕阅读器: 实时状态通知和上下文变更提示
- 焦点管理: 自动焦点陷阱和还原
使用示例
<template>
<div>
<NButton @click="showEditor = true">
打开编辑器
</NButton>
<ContextEditor
v-model:visible="showEditor"
:state="contextState"
:available-variables="availableVariables"
@save="handleSave"
@cancel="handleCancel"
@update:state="handleStateUpdate"
@contextChange="handleContextChange"
/>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { ContextEditor, type ContextState } from '@prompt-optimizer/ui'
const showEditor = ref(false)
// 上下文状态数据
const contextState = ref<ContextState>({
messages: [
{ role: 'user', content: 'Hello {{name}}' },
{ role: 'assistant', content: 'Hi there!' }
],
variables: { name: 'World' }, // 上下文覆盖变量
tools: [],
showVariablePreview: true,
showToolManager: true,
mode: 'edit'
})
// 全局可用变量(包括预定义和全局变量)
const availableVariables = ref<Record<string, string>>({
currentDate: new Date().toISOString(),
userName: 'Default User',
// 其他全局变量...
})
const handleSave = (context: ContextState) => {
console.log('Context saved:', context)
showEditor.value = false
}
const handleCancel = () => {
showEditor.value = false
}
const handleStateUpdate = (state: ContextState) => {
console.log('State updated:', state)
// 实时持久化逻辑
}
const handleContextChange = (context: ContextState) => {
console.log('Context changed:', context)
// 上下文变更处理逻辑
}
</script>
2. ToolCallDisplay(工具调用显示)
描述: 用于显示和管理工具调用结果的折叠面板组件。
文件位置: packages/ui/src/components/ToolCallDisplay.vue
Props
interface ToolCallDisplayProps {
/** 工具调用列表 */
toolCalls?: ToolCall[]
/** 初始折叠状态 */
collapsed?: boolean
/** 组件大小 */
size?: 'small' | 'medium' | 'large'
/** 最大显示数量 */
maxItems?: number
}
interface ToolCall {
/** 调用ID */
id: string
/** 工具名称 */
name: string
/** 调用参数 */
arguments?: Record<string, any>
/** 调用结果 */
result?: any
/** 错误信息 */
error?: string
/** 调用状态 */
status: 'pending' | 'success' | 'error'
/** 时间戳 */
timestamp: number
}
特性
- 智能折叠: 根据内容长度自动调整显示
- 状态标识: 成功、失败、等待状态的视觉区分
- JSON 格式化: 美化显示复杂参数和结果
- 错误处理: 优雅处理循环引用和无效数据
- 性能优化: 虚拟滚动支持大量数据
使用示例
<template>
<ToolCallDisplay
:tool-calls="toolCalls"
:collapsed="false"
size="medium"
:max-items="50"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { ToolCallDisplay, type ToolCall } from '@prompt-optimizer/ui'
const toolCalls = ref<ToolCall[]>([
{
id: 'call_1',
name: 'get_weather',
arguments: { location: 'Beijing', unit: 'celsius' },
result: { temperature: 25, condition: 'sunny' },
status: 'success',
timestamp: Date.now()
},
{
id: 'call_2',
name: 'send_email',
arguments: { to: 'user@example.com', subject: 'Test' },
error: 'Network timeout',
status: 'error',
timestamp: Date.now()
}
])
</script>
3. ScreenReaderSupport(屏幕阅读器支持)
描述: 专门为屏幕阅读器用户提供增强支持的组件。
文件位置: packages/ui/src/components/ScreenReaderSupport.vue
Props
interface ScreenReaderSupportProps {
/** 增强模式 */
enhanced?: boolean
/** 显示导航帮助 */
showNavigationHelp?: boolean
/** 显示快捷键帮助 */
showShortcutHelp?: boolean
/** 自动通知 */
autoAnnounce?: boolean
}
功能特性
- 实时区域:
aria-live区域用于状态更新通知 - 快捷键支持: 全局键盘快捷键处理
- 导航提示: 页面结构和导航帮助
- 上下文感知: 根据当前焦点提供相关提示
Methods
interface ScreenReaderSupportMethods {
/** 发送通知消息 */
announce(message: string, priority: 'polite' | 'assertive'): void
/** 显示快捷键帮助 */
showShortcuts(): void
/** 显示导航帮助 */
showNavigation(): void
}
使用示例
<template>
<div>
<ScreenReaderSupport
ref="screenReader"
:enhanced="accessibilityMode"
:show-navigation-help="showNav"
:show-shortcut-help="showShortcuts"
@shortcut="handleShortcut"
/>
<NButton @click="notifyUser">
发送通知
</NButton>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { ScreenReaderSupport } from '@prompt-optimizer/ui'
const screenReader = ref<InstanceType<typeof ScreenReaderSupport>>()
const accessibilityMode = ref(false)
const showNav = ref(false)
const showShortcuts = ref(false)
const notifyUser = () => {
screenReader.value?.announce('操作完成', 'polite')
}
const handleShortcut = (key: string) => {
console.log('快捷键触发:', key)
}
</script>
Composables(组合式函数)
1. useAccessibility(可访问性支持)
描述: 提供全面的可访问性功能,包括键盘导航、ARIA 管理和屏幕阅读器支持。
文件位置: packages/ui/src/composables/useAccessibility.ts
API
function useAccessibility(componentName?: string): {
// 键盘导航
keyboard: {
handleKeyPress: (event: KeyboardEvent) => boolean
setFocusableElements: (elements: HTMLElement[]) => void
focusNext: () => void
focusPrevious: () => void
focusFirst: () => void
focusLast: () => void
}
// ARIA 标签管理
aria: {
getLabel: (key: string, fallback?: string) => string
getDescription: (key: string, fallback?: string) => string
getRole: (elementType: string) => string
getLiveRegionText: (key: string) => string
}
// 消息通知
announce: (message: string, priority?: 'polite' | 'assertive') => void
// 焦点管理
enableFocusTrap: () => void
disableFocusTrap: () => void
// 响应式状态
focusableElements: Ref<HTMLElement[]>
currentFocusIndex: Ref<number>
trapFocus: Ref<boolean>
isAccessibilityMode: Ref<boolean>
accessibilityClasses: Ref<Record<string, boolean>>
liveRegionMessage: Ref<string>
announcements: Ref<string[]>
features: Ref<AccessibilityFeatures>
}
interface AccessibilityFeatures {
reduceMotion: boolean
highContrast: boolean
screenReaderMode: boolean
keyboardOnly: boolean
}
使用示例
<template>
<div :class="accessibilityClasses">
<button
v-for="(item, index) in items"
:key="item.id"
:aria-label="aria.getLabel('item', item.name)"
@keydown="keyboard.handleKeyPress"
>
{{ item.name }}
</button>
<div
role="status"
aria-live="polite"
class="sr-only"
>
{{ liveRegionMessage }}
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { useAccessibility } from '@prompt-optimizer/ui'
const items = ref([
{ id: 1, name: '项目1' },
{ id: 2, name: '项目2' },
{ id: 3, name: '项目3' }
])
const {
keyboard,
aria,
announce,
enableFocusTrap,
disableFocusTrap,
accessibilityClasses,
liveRegionMessage
} = useAccessibility('MyComponent')
onMounted(() => {
const buttons = document.querySelectorAll('button')
keyboard.setFocusableElements(Array.from(buttons) as HTMLElement[])
enableFocusTrap()
announce('组件已加载', 'polite')
})
</script>
2. useFocusManager(焦点管理)
描述: 专业的焦点管理系统,支持焦点陷阱、键盘导航和自动焦点恢复。
文件位置: packages/ui/src/composables/useFocusManager.ts
API
function useFocusManager(options: FocusManagerOptions = {}): {
// 核心方法
trapFocus: () => Promise<void>
releaseFocus: () => void
moveFocusNext: () => boolean
moveFocusPrevious: () => boolean
focusFirstElement: () => boolean
focusLastElement: () => boolean
// 工具方法
updateFocusableElements: () => HTMLElement[]
isFocusable: (element: HTMLElement) => boolean
// 响应式状态
focusableElements: Ref<HTMLElement[]>
currentFocusIndex: Ref<number>
isTrapped: Ref<boolean>
lastFocusedElement: Ref<HTMLElement | null>
}
interface FocusManagerOptions {
container?: string | HTMLElement
autoTrap?: boolean
restoreFocus?: boolean
skipHidden?: boolean
}
使用示例
<template>
<div ref="containerRef" class="focus-container">
<h2>焦点管理示例</h2>
<NButton @click="trapFocus">启用焦点陷阱</NButton>
<NButton @click="releaseFocus">释放焦点陷阱</NButton>
<NInput placeholder="输入框1" />
<NInput placeholder="输入框2" />
<NButton>确认</NButton>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useFocusManager } from '@prompt-optimizer/ui'
const containerRef = ref<HTMLElement>()
const {
trapFocus,
releaseFocus,
moveFocusNext,
moveFocusPrevious,
focusableElements,
currentFocusIndex,
isTrapped
} = useFocusManager({
container: containerRef,
restoreFocus: true
})
onMounted(() => {
// 监听键盘事件
document.addEventListener('keydown', (e) => {
if (!isTrapped.value) return
if (e.key === 'Tab') {
e.preventDefault()
if (e.shiftKey) {
moveFocusPrevious()
} else {
moveFocusNext()
}
}
})
})
</script>
3. useAccessibilityTesting(可访问性测试)
描述: WCAG 合规性自动化测试工具,用于检测和验证可访问性问题。
文件位置: packages/ui/src/composables/useAccessibilityTesting.ts
API
function useAccessibilityTesting(): {
runTest: (options: TestOptions) => Promise<TestResult>
runSingleRule: (rule: string, scope?: Element) => TestResult
getAvailableRules: () => TestRule[]
}
interface TestOptions {
scope?: Element
wcagLevel?: 'A' | 'AA' | 'AAA'
rules?: string[]
includeWarnings?: boolean
}
interface TestResult {
score: number
issues: AccessibilityIssue[]
warnings: AccessibilityIssue[]
passedRules: string[]
timestamp: number
}
interface AccessibilityIssue {
rule: string
severity: 'critical' | 'major' | 'minor'
message: string
element?: HTMLElement
wcagLevel: 'A' | 'AA' | 'AAA'
}
使用示例
<script setup lang="ts">
import { onMounted } from 'vue'
import { useAccessibilityTesting } from '@prompt-optimizer/ui'
const { runTest, runSingleRule } = useAccessibilityTesting()
onMounted(async () => {
// 运行全面测试
const result = await runTest({
scope: document.body,
wcagLevel: 'AA',
includeWarnings: true
})
console.log('可访问性测试结果:', result)
if (result.score < 80) {
console.warn('可访问性分数较低:', result.score)
result.issues.forEach(issue => {
console.error(`${issue.rule}: ${issue.message}`)
})
}
// 单独测试某个规则
const imgAltResult = runSingleRule('img-alt')
if (imgAltResult.issues.length > 0) {
console.warn('图片缺少alt属性')
}
})
</script>
样式系统
CSS 类命名约定
所有组件遵循统一的 CSS 类命名规范:
// 基础组件类
.component-name {
// 基础样式
}
// 状态类
.component-name--state {
// 状态样式
}
// 修饰符类
.component-name__element {
// 元素样式
}
// 可访问性相关类
.sr-only {
// 仅屏幕阅读器可见
}
.keyboard-focus {
// 键盘焦点样式
}
.accessibility-mode {
// 可访问性模式样式
}
响应式断点
// 移动端
@media (max-width: 767px) {
.responsive-mobile { /* 样式 */ }
}
// 平板端
@media (min-width: 768px) and (max-width: 1023px) {
.responsive-tablet { /* 样式 */ }
}
// 桌面端
@media (min-width: 1024px) {
.responsive-desktop { /* 样式 */ }
}
性能优化
1. 懒加载和代码分割
// 组件懒加载
const ContextEditor = defineAsyncComponent(
() => import('./components/ContextEditor.vue')
)
// 路由级别代码分割
const routes = [
{
path: '/editor',
component: () => import('./pages/EditorPage.vue')
}
]
2. 虚拟化支持
<template>
<!-- 大量数据的虚拟列表 -->
<VirtualList
:items="largeDataset"
:item-height="50"
:visible-count="10"
>
<template #item="{ item }">
<div class="virtual-item">{{ item.name }}</div>
</template>
</VirtualList>
</template>
3. 防抖和节流
import { useDebounceThrottle } from '@prompt-optimizer/ui'
const { debounce, throttle } = useDebounceThrottle()
// 搜索输入防抖
const handleSearch = debounce((query: string) => {
// 执行搜索逻辑
}, 300)
// 滚动事件节流
const handleScroll = throttle(() => {
// 处理滚动逻辑
}, 16)
国际化支持
语言配置
import { createI18n } from 'vue-i18n'
import zhCN from './locales/zh-CN'
import enUS from './locales/en-US'
const i18n = createI18n({
locale: 'zh-CN',
fallbackLocale: 'en-US',
messages: {
'zh-CN': zhCN,
'en-US': enUS
}
})
可访问性文本
// zh-CN.ts
export default {
accessibility: {
labels: {
contextEditor: '上下文编辑器',
closeButton: '关闭按钮',
saveButton: '保存按钮'
},
descriptions: {
contextEditor: '编辑消息、变量和工具配置',
navigationHelp: '使用Tab键在元素间导航'
},
announcements: {
saved: '内容已保存',
loading: '正在加载中',
error: '发生错误,请重试'
}
}
}
测试策略
1. 单元测试
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import ContextEditor from '../ContextEditor.vue'
describe('ContextEditor', () => {
it('应该正确渲染基本结构', () => {
const wrapper = mount(ContextEditor, {
props: {
visible: true,
state: {
messages: [],
variables: {},
tools: []
}
}
})
expect(wrapper.find('[role="dialog"]').exists()).toBe(true)
})
})
2. 可访问性测试
import { useAccessibilityTesting } from '@prompt-optimizer/ui'
describe('Accessibility Tests', () => {
it('应该通过WCAG AA标准', async () => {
const { runTest } = useAccessibilityTesting()
const result = await runTest({ wcagLevel: 'AA' })
expect(result.score).toBeGreaterThan(80)
expect(result.issues.filter(i => i.severity === 'critical')).toHaveLength(0)
})
})
3. E2E 测试
describe('端到端测试', () => {
it('应该支持完整的用户流程', async () => {
// 测试完整的用户交互流程
await page.goto('/')
await page.click('[data-testid="open-editor"]')
await page.fill('[aria-label="消息输入框"]', '测试内容')
await page.click('[aria-label="保存按钮"]')
expect(await page.textContent('[role="status"]')).toContain('保存成功')
})
})
部署和构建
1. 构建配置
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
build: {
lib: {
entry: 'src/index.ts',
name: 'PromptOptimizerUI',
formats: ['es', 'cjs']
},
rollupOptions: {
external: ['vue', 'naive-ui'],
output: {
globals: {
vue: 'Vue',
'naive-ui': 'NaiveUI'
}
}
}
}
})
2. 包管理
{
"name": "@prompt-optimizer/ui",
"version": "1.0.0",
"main": "dist/index.cjs",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs",
"types": "./dist/index.d.ts"
},
"./style": "./dist/style.css"
}
}
最佳实践
1. 组件开发指南
- 始终使用TypeScript: 提供类型安全和更好的开发体验
- 遵循可访问性标准: 确保所有组件符合WCAG 2.1 AA标准
- 编写测试: 单元测试、集成测试和E2E测试覆盖
- 性能优化: 使用虚拟化、懒加载和防抖节流
- 响应式设计: 移动端优先,适配不同屏幕尺寸
2. 代码风格
// 推荐的组件结构
<template>
<div class="component-name" :class="componentClasses">
<!-- 内容 -->
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
import { useAccessibility } from '../composables/useAccessibility'
// Props 定义
interface Props {
visible: boolean
readonly?: boolean
}
const props = withDefaults(defineProps<Props>(), {
readonly: false
})
// Emits 定义
interface Emits {
'update:visible': [visible: boolean]
}
const emit = defineEmits<Emits>()
// 可访问性支持
const { accessibility } = useAccessibility('ComponentName')
// 响应式状态
const localVisible = computed({
get: () => props.visible,
set: (value) => emit('update:visible', value)
})
// 计算属性
const componentClasses = computed(() => ({
'component-name--readonly': props.readonly,
...accessibility.classes.value
}))
</script>
<style scoped>
.component-name {
/* 基础样式 */
}
.component-name--readonly {
/* 只读状态样式 */
}
</style>
3. 可访问性检查清单
- 所有交互元素都有适当的ARIA标签
- 键盘导航功能完整
- 颜色对比度符合WCAG标准
- 屏幕阅读器兼容性测试通过
- 焦点管理正确实现
- 状态变更有适当的通知
更新日志
v1.0.0 (2024-XX-XX)
- ✨ 完成Naive UI重构
- ✨ 新增完整可访问性支持
- ✨ 实现响应式布局
- ✨ 添加性能优化特性
- ✨ 完整的TypeScript类型支持
- ✨ 国际化支持
- ✨ 完整的测试套件
反馈和支持
如有问题或建议,请通过以下方式联系:
- GitHub Issues: 项目仓库
- 文档更新: 欢迎提交PR改进文档
- 功能请求: 在Issues中标记为Feature Request
最后更新时间: 2024年XX月XX日