|
| 1 | +# HtmlEditModal 卡片选择工具可行性分析 |
| 2 | + |
| 3 | +## 1. 背景与需求 |
| 4 | + |
| 5 | +### 1.1 当前状态 |
| 6 | +- HtmlEditModal 已实现涂抹框选功能,允许用户通过画笔选择页面元素 |
| 7 | +- 后端 `/api/html/edit` 接口已经支持接收选中的元素并进行AI修改 |
| 8 | +- 界面布局已经成熟,包含工具栏、内容区、选中元素列表等 |
| 9 | + |
| 10 | +### 1.2 新需求 |
| 11 | +- 在现有工具栏中增加"卡片选择"模式 |
| 12 | +- 用户点击该模式后,点击任何卡片即可选中整张卡片 |
| 13 | +- 保持现有界面布局和后端接口不变 |
| 14 | +- 与现有涂抹选择功能并存,用户可自由切换 |
| 15 | + |
| 16 | +## 2. 技术可行性分析 |
| 17 | + |
| 18 | +### 2.1 前端实现可行性 |
| 19 | + |
| 20 | +#### 2.1.1 工具栏扩展 |
| 21 | +```javascript |
| 22 | +// 现有工具定义 |
| 23 | +const tools = [ |
| 24 | + { mode: 'paint', icon: 'Brush', label: '涂抹选择' }, |
| 25 | + // 新增卡片选择工具 |
| 26 | + { mode: 'card', icon: 'CreditCard', label: '卡片选择' } |
| 27 | +] |
| 28 | +``` |
| 29 | + |
| 30 | +**可行性:高** |
| 31 | +- 工具栏已采用动态渲染方式,易于扩展 |
| 32 | +- 通过 `v-for` 循环渲染工具按钮,添加新工具无需修改模板结构 |
| 33 | +- 现有的 `toggleTool(mode)` 方法可直接支持新模式 |
| 34 | + |
| 35 | +#### 2.1.2 卡片检测与识别 |
| 36 | +```javascript |
| 37 | +// 卡片识别逻辑 |
| 38 | +function detectCardElement(clickedElement) { |
| 39 | + // 向上遍历DOM树,寻找卡片容器 |
| 40 | + let current = clickedElement |
| 41 | + while (current && current !== document.body) { |
| 42 | + // 检测常见的卡片类名 |
| 43 | + if (current.classList.contains('card') || |
| 44 | + current.classList.contains('card-container') || |
| 45 | + current.classList.contains('card-item') || |
| 46 | + current.getAttribute('data-card') !== null) { |
| 47 | + return current |
| 48 | + } |
| 49 | + current = current.parentElement |
| 50 | + } |
| 51 | + return null |
| 52 | +} |
| 53 | +``` |
| 54 | + |
| 55 | +**可行性:高** |
| 56 | +- 根据提取结果,卡片都有明确的 class 标识(如 `card-container`) |
| 57 | +- 可以通过 DOM 树遍历准确定位卡片边界 |
| 58 | +- 支持多种卡片识别策略(类名、属性、结构特征) |
| 59 | + |
| 60 | +#### 2.1.3 点击事件处理 |
| 61 | +```javascript |
| 62 | +// 在 SelectionOverlay 组件中添加卡片模式处理 |
| 63 | +if (this.config.mode === 'card') { |
| 64 | + canvas.addEventListener('click', (e) => { |
| 65 | + const rect = canvas.getBoundingClientRect() |
| 66 | + const x = e.clientX - rect.left |
| 67 | + const y = e.clientY - rect.top |
| 68 | + |
| 69 | + // 获取iframe中对应位置的元素 |
| 70 | + const element = getElementAtPosition(x, y) |
| 71 | + |
| 72 | + // 检测是否为卡片 |
| 73 | + const card = detectCardElement(element) |
| 74 | + if (card) { |
| 75 | + // 创建覆盖整个卡片的选区 |
| 76 | + this.createCardSelection(card) |
| 77 | + } |
| 78 | + }) |
| 79 | +} |
| 80 | +``` |
| 81 | + |
| 82 | +**可行性:高** |
| 83 | +- SelectionOverlay 已经支持多种模式(paint、rect) |
| 84 | +- 可以复用现有的选区创建和管理机制 |
| 85 | +- iframe 内容访问机制已经建立 |
| 86 | + |
| 87 | +### 2.2 与现有功能的兼容性 |
| 88 | + |
| 89 | +#### 2.2.1 选区数据结构兼容 |
| 90 | +```javascript |
| 91 | +// 现有选区结构 |
| 92 | +{ |
| 93 | + id: 'selection_xxx', |
| 94 | + rect: { x, y, width, height }, |
| 95 | + elements: [ |
| 96 | + { |
| 97 | + selected_element: '<div class="card">...</div>', |
| 98 | + selection_coverage_percentage: 100 |
| 99 | + } |
| 100 | + ] |
| 101 | +} |
| 102 | +``` |
| 103 | + |
| 104 | +**兼容性:完全兼容** |
| 105 | +- 卡片选择生成的数据结构与涂抹选择完全一致 |
| 106 | +- 后端接口无需任何修改 |
| 107 | +- 选中元素列表展示逻辑无需调整 |
| 108 | + |
| 109 | +#### 2.2.2 交互模式切换 |
| 110 | +```javascript |
| 111 | +// 模式切换已经实现 |
| 112 | +toggleTool(mode) { |
| 113 | + if (toolActive.value && currentMode.value === mode) { |
| 114 | + toolActive.value = false |
| 115 | + } else { |
| 116 | + currentMode.value = mode |
| 117 | + overlayConfig.mode = mode |
| 118 | + toolActive.value = true |
| 119 | + } |
| 120 | +} |
| 121 | +``` |
| 122 | + |
| 123 | +**兼容性:完全兼容** |
| 124 | +- 现有的工具切换机制可直接支持新模式 |
| 125 | +- 不同模式间的切换逻辑已经完善 |
| 126 | +- 清除选区、撤销等功能可共享 |
| 127 | + |
| 128 | +### 2.3 用户体验优势 |
| 129 | + |
| 130 | +#### 2.3.1 操作简化 |
| 131 | +- **涂抹模式**:需要精确涂抹覆盖想要的内容,可能需要多次操作 |
| 132 | +- **卡片模式**:一次点击即可选中整张卡片,效率提升显著 |
| 133 | + |
| 134 | +#### 2.3.2 精确度提升 |
| 135 | +- **涂抹模式**:可能选中部分卡片或跨越多个元素 |
| 136 | +- **卡片模式**:确保选中完整的语义单元(整张卡片) |
| 137 | + |
| 138 | +#### 2.3.3 视觉反馈 |
| 139 | +```javascript |
| 140 | +// 卡片悬停效果 |
| 141 | +if (mode === 'card') { |
| 142 | + // 鼠标悬停时高亮潜在可选卡片 |
| 143 | + canvas.addEventListener('mousemove', (e) => { |
| 144 | + const card = detectCardAtPosition(e.x, e.y) |
| 145 | + if (card) { |
| 146 | + showCardOutline(card) // 显示卡片轮廓 |
| 147 | + } |
| 148 | + }) |
| 149 | +} |
| 150 | +``` |
| 151 | + |
| 152 | +## 3. 实现方案设计 |
| 153 | + |
| 154 | +### 3.1 最小化改动方案 |
| 155 | + |
| 156 | +#### 3.1.1 文件修改清单 |
| 157 | +1. `/terminal-ui/src/components/HtmlEditModal/index.vue` |
| 158 | + - 添加卡片选择工具到 tools 数组 |
| 159 | + - 更新提示文本逻辑 |
| 160 | + |
| 161 | +2. `/terminal-ui/src/components/SelectionOverlay/index.vue` |
| 162 | + - 添加卡片模式的事件处理 |
| 163 | + - 实现卡片检测算法 |
| 164 | + - 添加卡片悬停效果 |
| 165 | + |
| 166 | +3. `/terminal-ui/src/adapters/HTMLSelectionAdapter.js`(可选) |
| 167 | + - 添加卡片识别辅助方法 |
| 168 | + |
| 169 | +#### 3.1.2 核心代码改动 |
| 170 | +```vue |
| 171 | +<!-- HtmlEditModal/index.vue --> |
| 172 | +<script setup> |
| 173 | +// 工具配置 - 添加卡片选择 |
| 174 | +const tools = ref([ |
| 175 | + { mode: 'paint', icon: markRaw(Brush), label: '涂抹选择' }, |
| 176 | + { mode: 'card', icon: markRaw(CreditCard), label: '卡片选择' } // 新增 |
| 177 | +]) |
| 178 | +
|
| 179 | +// 更新提示文本 |
| 180 | +const currentTip = computed(() => { |
| 181 | + if (!toolActive.value) { |
| 182 | + return '当前为浏览模式,点击工具按钮激活选择功能' |
| 183 | + } |
| 184 | + if (currentMode.value === 'card') { |
| 185 | + return '点击任意卡片即可选中整张卡片' // 新增提示 |
| 186 | + } |
| 187 | + if (selectedElements.value.length === 0) { |
| 188 | + return '使用涂抹工具选择要编辑的内容' |
| 189 | + } |
| 190 | + return `已选择 ${selectedElements.value.length} 个元素` |
| 191 | +}) |
| 192 | +</script> |
| 193 | +``` |
| 194 | + |
| 195 | +### 3.2 SelectionOverlay 组件扩展 |
| 196 | + |
| 197 | +```vue |
| 198 | +<!-- SelectionOverlay/index.vue --> |
| 199 | +<script setup> |
| 200 | +// 卡片检测算法 |
| 201 | +const detectCard = (element) => { |
| 202 | + const cardSelectors = [ |
| 203 | + '.card', |
| 204 | + '.card-container', |
| 205 | + '.card-item', |
| 206 | + '[data-card]', |
| 207 | + '.post-card', |
| 208 | + '.content-card' |
| 209 | + ] |
| 210 | +
|
| 211 | + // 从点击元素向上查找 |
| 212 | + let current = element |
| 213 | + while (current && current !== document.body) { |
| 214 | + for (const selector of cardSelectors) { |
| 215 | + if (current.matches && current.matches(selector)) { |
| 216 | + return current |
| 217 | + } |
| 218 | + } |
| 219 | + current = current.parentElement |
| 220 | + } |
| 221 | + return null |
| 222 | +} |
| 223 | +
|
| 224 | +// 卡片模式处理 |
| 225 | +const handleCardMode = () => { |
| 226 | + if (props.config.mode !== 'card') return |
| 227 | +
|
| 228 | + canvas.value.addEventListener('click', handleCardClick) |
| 229 | + canvas.value.addEventListener('mousemove', handleCardHover) |
| 230 | +} |
| 231 | +
|
| 232 | +// 卡片点击处理 |
| 233 | +const handleCardClick = (e) => { |
| 234 | + const element = getElementAtCanvasPosition(e.offsetX, e.offsetY) |
| 235 | + const card = detectCard(element) |
| 236 | +
|
| 237 | + if (card) { |
| 238 | + // 获取卡片边界 |
| 239 | + const rect = card.getBoundingClientRect() |
| 240 | + const containerRect = props.container.getBoundingClientRect() |
| 241 | +
|
| 242 | + // 创建选区 |
| 243 | + const selection = { |
| 244 | + id: `card_${Date.now()}`, |
| 245 | + rect: { |
| 246 | + x: rect.left - containerRect.left, |
| 247 | + y: rect.top - containerRect.top, |
| 248 | + width: rect.width, |
| 249 | + height: rect.height |
| 250 | + }, |
| 251 | + elements: [{ |
| 252 | + selected_element: card.outerHTML, |
| 253 | + selection_coverage_percentage: 100 |
| 254 | + }] |
| 255 | + } |
| 256 | +
|
| 257 | + // 添加到选区列表 |
| 258 | + selections.value.push(selection) |
| 259 | + emits('selection-complete', selection) |
| 260 | + } |
| 261 | +} |
| 262 | +
|
| 263 | +// 卡片悬停效果 |
| 264 | +const handleCardHover = (e) => { |
| 265 | + const element = getElementAtCanvasPosition(e.offsetX, e.offsetY) |
| 266 | + const card = detectCard(element) |
| 267 | +
|
| 268 | + if (card) { |
| 269 | + canvas.value.style.cursor = 'pointer' |
| 270 | + // 绘制卡片轮廓提示 |
| 271 | + drawCardOutline(card) |
| 272 | + } else { |
| 273 | + canvas.value.style.cursor = 'default' |
| 274 | + clearCardOutline() |
| 275 | + } |
| 276 | +} |
| 277 | +</script> |
| 278 | +``` |
| 279 | + |
| 280 | +## 4. 风险评估与应对 |
| 281 | + |
| 282 | +### 4.1 技术风险 |
| 283 | + |
| 284 | +| 风险项 | 可能性 | 影响 | 应对措施 | |
| 285 | +|-------|--------|------|----------| |
| 286 | +| 卡片边界识别不准 | 低 | 中 | 提供多种识别策略,支持配置 | |
| 287 | +| 嵌套卡片处理 | 中 | 低 | 优先选择最内层卡片,提供切换选项 | |
| 288 | +| 动态加载的卡片 | 低 | 低 | 使用 MutationObserver 监听DOM变化 | |
| 289 | +| 性能问题 | 低 | 低 | 使用防抖处理鼠标移动事件 | |
| 290 | + |
| 291 | +### 4.2 用户体验风险 |
| 292 | + |
| 293 | +| 风险项 | 可能性 | 影响 | 应对措施 | |
| 294 | +|-------|--------|------|----------| |
| 295 | +| 误选相邻卡片 | 低 | 低 | 提供视觉反馈,悬停时显示将选中的区域 | |
| 296 | +| 无法选中部分内容 | 中 | 中 | 保留涂抹模式,用户可切换使用 | |
| 297 | +| 卡片定义不明确 | 低 | 中 | 提供卡片识别规则说明 | |
| 298 | + |
| 299 | +## 5. 实施建议 |
| 300 | + |
| 301 | +### 5.1 分阶段实施 |
| 302 | + |
| 303 | +#### Phase 1: 基础功能(2小时) |
| 304 | +- [ ] 添加卡片选择工具到工具栏 |
| 305 | +- [ ] 实现基础的卡片点击选择 |
| 306 | +- [ ] 确保与后端接口兼容 |
| 307 | + |
| 308 | +#### Phase 2: 体验优化(1小时) |
| 309 | +- [ ] 添加卡片悬停提示效果 |
| 310 | +- [ ] 优化卡片识别算法 |
| 311 | +- [ ] 添加卡片选择的视觉反馈 |
| 312 | + |
| 313 | +#### Phase 3: 高级功能(可选,1小时) |
| 314 | +- [ ] 支持批量选择多张卡片(Ctrl+点击) |
| 315 | +- [ ] 支持卡片选择的快捷键 |
| 316 | +- [ ] 添加智能卡片边界检测 |
| 317 | + |
| 318 | +### 5.2 测试要点 |
| 319 | + |
| 320 | +1. **功能测试** |
| 321 | + - 卡片选择的准确性 |
| 322 | + - 模式切换的流畅性 |
| 323 | + - 选区数据的正确性 |
| 324 | + |
| 325 | +2. **兼容性测试** |
| 326 | + - 与涂抹模式的切换 |
| 327 | + - 与现有编辑功能的配合 |
| 328 | + - 不同HTML结构的适配 |
| 329 | + |
| 330 | +3. **性能测试** |
| 331 | + - 大量卡片场景的响应速度 |
| 332 | + - 内存占用情况 |
| 333 | + - 渲染性能 |
| 334 | + |
| 335 | +## 6. 技术优势总结 |
| 336 | + |
| 337 | +### 6.1 实现简单 |
| 338 | +- 复用现有的 SelectionOverlay 架构 |
| 339 | +- 无需修改后端接口 |
| 340 | +- 代码改动量小(预计不超过200行) |
| 341 | + |
| 342 | +### 6.2 用户友好 |
| 343 | +- 一键选中整张卡片,操作直观 |
| 344 | +- 保留原有涂抹功能,灵活性高 |
| 345 | +- 视觉反馈清晰,用户体验好 |
| 346 | + |
| 347 | +### 6.3 维护方便 |
| 348 | +- 代码结构清晰,易于理解 |
| 349 | +- 与现有功能解耦,便于独立维护 |
| 350 | +- 可配置性强,易于扩展 |
| 351 | + |
| 352 | +## 7. 结论 |
| 353 | + |
| 354 | +**可行性评级:⭐⭐⭐⭐⭐(非常可行)** |
| 355 | + |
| 356 | +### 7.1 技术可行性 |
| 357 | +- 现有架构完全支持该功能扩展 |
| 358 | +- 无需大规模重构 |
| 359 | +- 技术风险低,实现路径清晰 |
| 360 | + |
| 361 | +### 7.2 业务价值 |
| 362 | +- 显著提升用户选择卡片的效率 |
| 363 | +- 降低操作复杂度 |
| 364 | +- 提高编辑精确度 |
| 365 | + |
| 366 | +### 7.3 实施建议 |
| 367 | +建议立即实施该功能: |
| 368 | +1. 技术方案成熟,风险可控 |
| 369 | +2. 用户体验提升明显 |
| 370 | +3. 开发成本低,收益高 |
| 371 | + |
| 372 | +预计总开发时间:3-4小时 |
| 373 | +预计测试时间:1-2小时 |
0 commit comments