Compare commits
24 Commits
9091e99a57
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4d325c222 | ||
|
|
ef6010a8b2 | ||
|
|
b9fadbd31e | ||
|
|
a732428a70 | ||
|
|
d25ee16d3b | ||
|
|
c28f37e81e | ||
|
|
f64e83e8cd | ||
|
|
4c898306e0 | ||
|
|
1983c15fb9 | ||
|
|
e46805deca | ||
|
|
848277849a | ||
|
|
9d9c4e4186 | ||
|
|
8734a48b76 | ||
|
|
13a282d93a | ||
|
|
0665987a03 | ||
|
|
da2f3cfbff | ||
|
|
89b7f0162f | ||
|
|
cd991c33d3 | ||
|
|
ce356586a3 | ||
|
|
1341152002 | ||
| 4f62f61fce | |||
|
|
bbbfddf170 | ||
|
|
13e4dee4b8 | ||
|
|
b3a3ca91d5 |
295
IMPROVEMENTS.md
Normal file
295
IMPROVEMENTS.md
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
# 极简邀请函制作工具 - 升级改进计划
|
||||||
|
|
||||||
|
## 一、架构与工程化改进
|
||||||
|
|
||||||
|
### 1. 模块化重构
|
||||||
|
- **现状**: 所有代码集中在 3000+ 行的 `script.js` 文件中
|
||||||
|
- **改进方案**: 拆分为多个功能模块
|
||||||
|
- `state.js` - 状态管理(撤销/重做栈、当前选中元素等)
|
||||||
|
- `canvas.js` - 画布渲染与导出逻辑
|
||||||
|
- `textEditor.js` - 文本编辑功能
|
||||||
|
- `imageEditor.js` - 图片编辑功能
|
||||||
|
- `ui.js` - UI 交互与事件绑定
|
||||||
|
- `utils.js` - 工具函数
|
||||||
|
|
||||||
|
### 2. 引入构建工具
|
||||||
|
- **现状**: 原生 HTML/CSS/JS,无构建流程
|
||||||
|
- **改进方案**: 引入 Vite 或 Webpack
|
||||||
|
- 支持 ES6 Modules
|
||||||
|
- 代码压缩与优化
|
||||||
|
- 热更新开发体验
|
||||||
|
- 生产环境自动构建
|
||||||
|
|
||||||
|
### 3. TypeScript 迁移
|
||||||
|
- **改进方案**: 将 JavaScript 迁移至 TypeScript
|
||||||
|
- 添加类型定义,减少运行时错误
|
||||||
|
- 增强 IDE 智能提示
|
||||||
|
- 提升代码健壮性和可维护性
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、功能增强
|
||||||
|
|
||||||
|
### 4. 图层管理系统
|
||||||
|
- **现状**: 仅支持背景图、层叠图、文字三层简单叠加
|
||||||
|
- **改进方案**:
|
||||||
|
- 图层列表面板(显示所有元素层级)
|
||||||
|
- 图层顺序调整(上移/下移/置顶/置底)
|
||||||
|
- 图层命名与锁定功能
|
||||||
|
- 图层可见性切换(显示/隐藏)
|
||||||
|
- 图层透明度独立调节
|
||||||
|
|
||||||
|
### 5. 更多元素类型
|
||||||
|
- **新增元素**:
|
||||||
|
- **形状元素**: 矩形、圆形、三角形、线条、箭头
|
||||||
|
- **二维码/条形码**: 支持输入内容生成二维码
|
||||||
|
- **图标库**: 集成 FontAwesome 或自定义图标库
|
||||||
|
- **贴纸/装饰**: 预设装饰元素库(花边、边框、图案)
|
||||||
|
- **日期时间**: 自动插入当前日期,支持多种格式
|
||||||
|
|
||||||
|
### 6. 高级文本功能
|
||||||
|
- **现状**: 仅支持基础字体、颜色、大小、加粗、斜体
|
||||||
|
- **改进方案**:
|
||||||
|
- 文本对齐方式(左对齐/居中/右对齐/两端对齐)
|
||||||
|
- 行高调整
|
||||||
|
- 字间距调整
|
||||||
|
- 文本阴影/描边效果
|
||||||
|
- 富文本编辑器(支持部分文字独立设置样式)
|
||||||
|
- 文字竖排支持
|
||||||
|
- 文字沿路径排列
|
||||||
|
|
||||||
|
### 7. 图片编辑增强
|
||||||
|
- **现状**: 仅支持透明度、旋转、水平镜像
|
||||||
|
- **改进方案**:
|
||||||
|
- 图片滤镜(亮度、对比度、饱和度、色相、模糊、灰度)
|
||||||
|
- 图片裁剪功能(自由裁剪、固定比例裁剪)
|
||||||
|
- 垂直镜像
|
||||||
|
- 圆角裁剪
|
||||||
|
- 旋转角度精确输入(支持小数角度)
|
||||||
|
- 图片混合模式(正片叠底、滤色、叠加等)
|
||||||
|
|
||||||
|
### 8. 模板系统
|
||||||
|
- **改进方案**:
|
||||||
|
- 预设邀请函模板库(婚礼、生日、会议、活动、节日等)
|
||||||
|
- 模板分类与搜索
|
||||||
|
- 用户自定义模板保存
|
||||||
|
- 模板分享功能
|
||||||
|
- 热门模板推荐
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、性能优化 ✅ 已完成
|
||||||
|
|
||||||
|
### 9. Canvas 渲染优化 ✅
|
||||||
|
- **现状**: `generateCanvas` 函数逻辑复杂,渲染耗时
|
||||||
|
- **改进方案**:
|
||||||
|
- ✅ 使用 `OffscreenCanvas` 进行后台渲染
|
||||||
|
- ✅ 图片预加载与缓存机制(新增 `imageCache` Map)
|
||||||
|
- ✅ 大图片分片渲染(批量处理,每批5个)
|
||||||
|
- ✅ 渲染进度提示(Loading 管理器实时更新进度)
|
||||||
|
- **实现细节**:
|
||||||
|
- 新增 `getCachedImage()` 函数缓存已加载图片
|
||||||
|
- `drawOverlayImagesHD()` 改为异步批量处理
|
||||||
|
- 每批处理后让出主线程,避免阻塞 UI
|
||||||
|
|
||||||
|
### 10. 内存管理优化 ✅
|
||||||
|
- **现状**: 历史状态使用深拷贝,可能占用大量内存
|
||||||
|
- **改进方案**:
|
||||||
|
- ✅ 状态压缩(`compressState()` 限制文本长度)
|
||||||
|
- ✅ 根据历史记录数量动态清理(超过80%时自动清理到60%)
|
||||||
|
- ✅ 图片缓存大小限制(最大20张)
|
||||||
|
- ✅ 提供清理缓存功能(`clearImageCache()`)
|
||||||
|
- **实现细节**:
|
||||||
|
- 新增 `memoryMonitor` 内存监控器,每30秒检查一次
|
||||||
|
- 状态对比优化(先比较时间戳和关键属性)
|
||||||
|
- 状态添加时间戳便于管理
|
||||||
|
|
||||||
|
### 11. 防抖/节流优化 ✅
|
||||||
|
- **现状**: 拖拽过程中频繁触发状态保存
|
||||||
|
- **改进方案**:
|
||||||
|
- ✅ 拖拽结束只保存一次(使用 `immediate` 模式)
|
||||||
|
- ✅ 中间过程不保存状态
|
||||||
|
- ✅ 大操作使用 loading 提示(`loadingManager`)
|
||||||
|
- ✅ 性能计时(`performance.now()` 记录耗时)
|
||||||
|
- **实现细节**:
|
||||||
|
- 新增 `throttle()` 和 `debounce()` 工具函数
|
||||||
|
- 新增 `loadingManager` 全局 Loading 管理器
|
||||||
|
- 图片保存过程显示详细进度(初始化画布→绘制背景→绘制层叠图→绘制文字)
|
||||||
|
- 控制台输出详细性能日志
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、用户体验改进
|
||||||
|
|
||||||
|
### 12. 移动端优化
|
||||||
|
- **现状**: 基础移动端适配已具备
|
||||||
|
- **改进方案**:
|
||||||
|
- 触摸手势支持(双指缩放、旋转)
|
||||||
|
- 底部操作栏适配 iPhone 安全区
|
||||||
|
- 横屏模式支持
|
||||||
|
- 触摸反馈优化
|
||||||
|
- 虚拟键盘适配优化
|
||||||
|
|
||||||
|
### 13. 键盘快捷键扩展
|
||||||
|
- **现状**: 仅支持撤销/重做和方向键微调
|
||||||
|
- **改进方案**:
|
||||||
|
- `Delete` / `Backspace` - 删除选中元素
|
||||||
|
- `Ctrl+C` / `Ctrl+V` - 复制粘贴元素
|
||||||
|
- `Ctrl+D` - 复制元素
|
||||||
|
- `Ctrl+G` - 组合/解组元素
|
||||||
|
- `Esc` - 取消选中/退出编辑
|
||||||
|
- `Ctrl+S` - 保存草稿
|
||||||
|
- `Ctrl+A` - 全选元素
|
||||||
|
- `↑↓←→` + `Shift` - 快速移动(10px)
|
||||||
|
- `↑↓←→` + `Alt` - 微调移动(0.5px)
|
||||||
|
|
||||||
|
### 14. 对齐与分布工具
|
||||||
|
- **改进方案**:
|
||||||
|
- 多选元素对齐(左对齐、水平居中、右对齐、顶部对齐、垂直居中、底部对齐)
|
||||||
|
- 元素等间距分布(水平分布、垂直分布)
|
||||||
|
- 相对于画布居中
|
||||||
|
- 智能对齐线增强(显示距离数值)
|
||||||
|
- 网格吸附(可选开启)
|
||||||
|
|
||||||
|
### 15. 预览与导出增强
|
||||||
|
- **现状**: 仅支持 PNG 格式导出
|
||||||
|
- **改进方案**:
|
||||||
|
- 多种导出格式(PNG、JPG、WebP、PDF)
|
||||||
|
- 导出质量选择(低/中/高/无损)
|
||||||
|
- 导出尺寸预设(微信头像、朋友圈、打印尺寸、A4等)
|
||||||
|
- 实时预览模式
|
||||||
|
- 导出前预览确认
|
||||||
|
- 批量导出功能
|
||||||
|
|
||||||
|
### 16. 自动保存与草稿
|
||||||
|
- **改进方案**:
|
||||||
|
- LocalStorage 自动保存(可设置间隔)
|
||||||
|
- 草稿恢复提示(意外关闭后)
|
||||||
|
- 多草稿管理(命名、删除、切换)
|
||||||
|
- 手动保存快照
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、代码质量改进
|
||||||
|
|
||||||
|
### 17. 错误处理
|
||||||
|
- **现状**: 异步操作缺少完善的错误处理
|
||||||
|
- **改进方案**:
|
||||||
|
- 添加全局错误边界
|
||||||
|
- 图片加载失败优雅降级
|
||||||
|
- 网络异常处理
|
||||||
|
- 用户友好的错误提示
|
||||||
|
|
||||||
|
### 18. 事件委托优化
|
||||||
|
- **现状**: 大量事件监听器直接绑定在元素上
|
||||||
|
- **改进方案**:
|
||||||
|
- 使用事件委托减少内存占用
|
||||||
|
- 统一事件管理
|
||||||
|
- 避免内存泄漏
|
||||||
|
|
||||||
|
### 19. CSS 优化
|
||||||
|
- **现状**: 使用传统 CSS 文件
|
||||||
|
- **改进方案**:
|
||||||
|
- 使用 CSS 变量统一管理颜色、间距、阴影等
|
||||||
|
- 考虑使用 CSS-in-JS 或 Tailwind CSS
|
||||||
|
- 添加暗黑模式支持
|
||||||
|
- 响应式断点优化
|
||||||
|
|
||||||
|
### 20. 无障碍支持 (a11y)
|
||||||
|
- **改进方案**:
|
||||||
|
- 添加 ARIA 标签
|
||||||
|
- 键盘导航支持
|
||||||
|
- 屏幕阅读器适配
|
||||||
|
- 高对比度模式
|
||||||
|
- 字体大小调节支持
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、可扩展性
|
||||||
|
|
||||||
|
### 21. 插件系统
|
||||||
|
- **改进方案**:
|
||||||
|
- 设计插件接口规范
|
||||||
|
- 允许第三方开发插件
|
||||||
|
- 插件市场/管理器
|
||||||
|
- 预设组件库扩展
|
||||||
|
|
||||||
|
### 22. 数据持久化
|
||||||
|
- **改进方案**:
|
||||||
|
- 后端服务集成
|
||||||
|
- 用户账号系统
|
||||||
|
- 云端保存作品
|
||||||
|
- 作品分享链接
|
||||||
|
- 团队协作功能
|
||||||
|
|
||||||
|
### 23. 国际化 (i18n)
|
||||||
|
- **改进方案**:
|
||||||
|
- 多语言支持(中文、英文、日文等)
|
||||||
|
- RTL 布局适配
|
||||||
|
- 时区适配
|
||||||
|
- 本地化日期格式
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 优先级规划
|
||||||
|
|
||||||
|
### 🔴 高优先级(核心改进)
|
||||||
|
| 序号 | 改进项 | 预期收益 |
|
||||||
|
|------|--------|----------|
|
||||||
|
| 1 | 模块化重构 | 提升代码可维护性,便于团队协作 |
|
||||||
|
| 2 | TypeScript 迁移 | 减少 Bug,提升开发效率 |
|
||||||
|
| 3 | 图层管理系统 | 核心功能增强,用户体验大幅提升 |
|
||||||
|
| 4 | 自动保存与草稿 | 防止数据丢失,提升用户体验 |
|
||||||
|
| 5 | 错误处理完善 | 提升应用稳定性 |
|
||||||
|
|
||||||
|
### 🟡 中优先级(功能扩展)
|
||||||
|
| 序号 | 改进项 | 预期收益 |
|
||||||
|
|------|--------|----------|
|
||||||
|
| 6 | 模板系统 | 降低用户使用门槛,提升效率 |
|
||||||
|
| 7 | 更多元素类型 | 丰富设计能力 |
|
||||||
|
| 8 | 高级文本功能 | 满足专业排版需求 |
|
||||||
|
| 9 | 图片编辑增强 | 减少对外部工具的依赖 |
|
||||||
|
| 10 | 键盘快捷键扩展 | 提升专业用户效率 |
|
||||||
|
| 11 | 对齐与分布工具 | 提升排版效率 |
|
||||||
|
|
||||||
|
### 🟢 低优先级(长期规划)
|
||||||
|
| 序号 | 改进项 | 预期收益 |
|
||||||
|
|------|--------|----------|
|
||||||
|
| 12 | 插件系统 | 构建生态,无限扩展可能 |
|
||||||
|
| 13 | 后端集成 | 实现数据云同步和社交功能 |
|
||||||
|
| 14 | 国际化 | 拓展海外市场 |
|
||||||
|
| 15 | 暗黑模式 | 提升视觉体验 |
|
||||||
|
| 16 | 无障碍支持 | 社会责任,覆盖更多用户 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 实施建议
|
||||||
|
|
||||||
|
### 阶段一:基础重构(1-2周)
|
||||||
|
1. 引入 Vite 构建工具
|
||||||
|
2. 代码模块化拆分
|
||||||
|
3. TypeScript 迁移
|
||||||
|
4. 完善错误处理
|
||||||
|
|
||||||
|
### 阶段二:核心功能(2-3周)
|
||||||
|
1. 图层管理系统
|
||||||
|
2. 自动保存与草稿
|
||||||
|
3. 对齐与分布工具
|
||||||
|
4. 键盘快捷键扩展
|
||||||
|
|
||||||
|
### 阶段三:功能扩展(3-4周)
|
||||||
|
1. 模板系统
|
||||||
|
2. 更多元素类型
|
||||||
|
3. 高级文本功能
|
||||||
|
4. 图片编辑增强
|
||||||
|
|
||||||
|
### 阶段四:优化与扩展(持续)
|
||||||
|
1. 性能优化
|
||||||
|
2. 移动端优化
|
||||||
|
3. 插件系统
|
||||||
|
4. 后端集成
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*文档生成时间: 2026-04-23*
|
||||||
|
*基于项目版本分析*
|
||||||
21
README.md
21
README.md
@@ -67,14 +67,13 @@
|
|||||||
27. 缩小背景图/层叠图/文本按钮宽度,优化手机端保存图片直接存入相册功能
|
27. 缩小背景图/层叠图/文本按钮宽度,优化手机端保存图片直接存入相册功能
|
||||||
28. 设置默认背景图,页面打开自动加载,保留原有功能,点击背景图按钮可更换
|
28. 设置默认背景图,页面打开自动加载,保留原有功能,点击背景图按钮可更换
|
||||||
29. 将编辑器工具栏颜色,字体,字号间距缩小一些,美观大方
|
29. 将编辑器工具栏颜色,字体,字号间距缩小一些,美观大方
|
||||||
|
30. 缩小编辑器工具栏间距,适应小屏幕
|
||||||
|
31. 适配微信浏览器优化导出图片质量,优化画布尺寸计算逻辑以保持宽高比
|
||||||
|
32. 限制边界不强制吸附,优化移动端编辑交互逻辑,解决ios设备上键盘无法弹出的问题,优化统一桌面和移动端触摸编辑和键盘触发逻辑
|
||||||
|
33. 导出高清背景图文本的缩放计算逻辑,优化编辑栏固定与交互,改进字体加载和文本渲染逻辑,支持在选中元素上应用样式
|
||||||
|
34. 调整输入框位置,优化字体设置与选择器匹配逻辑,添加输入框字体加粗样式
|
||||||
|
35. 添加层叠图片旋转功能,为旋转滑动条添加0度,90度,-90度吸附功能,数值单独占位
|
||||||
## 需要注意的点
|
36. 层叠图镜像功能,优化编辑器工具栏按钮样式间距,调整背景图缩放和透明度顺序
|
||||||
|
37. 项目添加撤销重做功能,检查修复撤销重做后背景图和文本位置显示异常问题,撤销重做按钮只需要点击一次就能执行操作
|
||||||
1. 图片要必须能左右滑动调整位置,保持图片比例不变,图片任何内容都可以被拖动到预览区域内任意位置
|
38. 优化撤销功能,优化顶部编辑栏按钮占位,优化撤销/重做按钮与文本按钮和保存/预览按钮的间距,文本按钮 ↔ [4px] ↔ 分隔线 ↔ 撤销按钮 ↔ [3px] ↔ 重做按钮 ↔ 分隔线 ↔ [4px] ↔ 保存按钮
|
||||||
|
39. 优化顶部工具栏样式,调整撤销重做按钮分隔栏边距,重构层叠图渲染到最终保存图片上的逻辑
|
||||||
|
|
||||||
377
css/style.css
377
css/style.css
@@ -23,18 +23,19 @@ html, body {
|
|||||||
.toolbar {
|
.toolbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 8px;
|
align-items: center;
|
||||||
padding: 12px 16px;
|
gap: 4px;
|
||||||
|
padding: 10px 12px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border-bottom: 1px solid #e0e0e0;
|
border-bottom: 1px solid #e0e0e0;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
min-width: 72px;
|
min-width: 56px;
|
||||||
height: 40px;
|
height: 32px;
|
||||||
padding: 0 12px;
|
padding: 0 8px;
|
||||||
font-size: 15px;
|
font-size: 13px;
|
||||||
border: 1px solid #ddd;
|
border: 1px solid #ddd;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
@@ -52,7 +53,7 @@ html, body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary {
|
.btn-primary {
|
||||||
min-width: 100px;
|
min-width: 80px;
|
||||||
background: #4a90e2;
|
background: #4a90e2;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
border-color: #4a90e2;
|
border-color: #4a90e2;
|
||||||
@@ -62,6 +63,80 @@ html, body {
|
|||||||
background: #3a7bc8;
|
background: #3a7bc8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 工具栏垂直分隔线 */
|
||||||
|
.toolbar-divider-v {
|
||||||
|
width: 1px;
|
||||||
|
height: 18px;
|
||||||
|
background: #e0e0e0;
|
||||||
|
margin: 0 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 工具栏图标按钮 */
|
||||||
|
.toolbar-btn-icon {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
font-size: 12px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: #fff;
|
||||||
|
color: #555;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-btn-icon:hover {
|
||||||
|
background: #f5f5f5;
|
||||||
|
border-color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-btn-icon:active {
|
||||||
|
background: #e8e8e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-btn-icon:disabled {
|
||||||
|
opacity: 0.3;
|
||||||
|
cursor: not-allowed;
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 撤销重做按钮组 */
|
||||||
|
.toolbar-action-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 2px;
|
||||||
|
margin: 0 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 左侧分隔线(文本按钮后) */
|
||||||
|
.toolbar-divider-v.divider-left {
|
||||||
|
margin-left: 2px;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 右侧分隔线(保存按钮前) */
|
||||||
|
.toolbar-divider-v.divider-right {
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 撤销重做按钮组 */
|
||||||
|
.toolbar-action-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 3px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 保存/预览按钮 */
|
||||||
|
.btn-primary {
|
||||||
|
min-width: 80px;
|
||||||
|
height: 36px;
|
||||||
|
padding: 0 14px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
/* 文本输入框删除按钮 */
|
/* 文本输入框删除按钮 */
|
||||||
.text-input .text-delete-btn {
|
.text-input .text-delete-btn {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -88,7 +163,7 @@ html, body {
|
|||||||
/* 编辑器工具栏(文本/图片共用) */
|
/* 编辑器工具栏(文本/图片共用) */
|
||||||
.editor-toolbar {
|
.editor-toolbar {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 65px;
|
top: 52px;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -128,6 +203,8 @@ html, body {
|
|||||||
.toolbar-group {
|
.toolbar-group {
|
||||||
gap: 2px;
|
gap: 2px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
.toolbar-btn {
|
.toolbar-btn {
|
||||||
width: 32px;
|
width: 32px;
|
||||||
@@ -135,10 +212,10 @@ html, body {
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
.toolbar-label {
|
.toolbar-label {
|
||||||
font-size: 11px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
.toolbar-label span {
|
.toolbar-label span:first-child {
|
||||||
display: none;
|
display: inline;
|
||||||
}
|
}
|
||||||
.roller-picker {
|
.roller-picker {
|
||||||
min-width: 50px;
|
min-width: 50px;
|
||||||
@@ -156,7 +233,8 @@ html, body {
|
|||||||
display: none;
|
display: none;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4px;
|
gap: 2px;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolbar-group.active {
|
.toolbar-group.active {
|
||||||
@@ -190,17 +268,24 @@ html, body {
|
|||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 镜像按钮样式 */
|
||||||
|
.toolbar-btn.mirror-btn {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.toolbar-divider {
|
.toolbar-divider {
|
||||||
width: 1px;
|
width: 1px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
background: #ddd;
|
background: #ddd;
|
||||||
margin: 0 2px;
|
margin: 0 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolbar-label {
|
.toolbar-label {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4px;
|
gap: 2px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #666;
|
color: #666;
|
||||||
}
|
}
|
||||||
@@ -230,6 +315,28 @@ html, body {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.rotate-slider {
|
||||||
|
width: 100px;
|
||||||
|
height: 32px;
|
||||||
|
cursor: pointer;
|
||||||
|
accent-color: #4a90e2;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 旋转度数显示固定宽度 */
|
||||||
|
#rotateValue {
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 40px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 百分比显示固定宽度 */
|
||||||
|
#opacityValue, #bgScaleValue, #bgOpacityValue {
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 40px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
/* 字号滚轮选择器 */
|
/* 字号滚轮选择器 */
|
||||||
.roller-picker {
|
.roller-picker {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -438,6 +545,7 @@ html, body {
|
|||||||
overflow: visible;
|
overflow: visible;
|
||||||
max-width: none;
|
max-width: none;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 选中状态(单击/长按) - 仅显示虚线轮廓 */
|
/* 选中状态(单击/长按) - 仅显示虚线轮廓 */
|
||||||
@@ -493,27 +601,6 @@ html, body {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 长按提示 */
|
|
||||||
.long-press-tip {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 20px;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
padding: 8px 16px;
|
|
||||||
background: rgba(0,0,0,0.7);
|
|
||||||
color: #fff;
|
|
||||||
font-size: 12px;
|
|
||||||
border-radius: 16px;
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0.3s;
|
|
||||||
pointer-events: none;
|
|
||||||
z-index: 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.long-press-tip.show {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 右键菜单 */
|
/* 右键菜单 */
|
||||||
.context-menu {
|
.context-menu {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@@ -589,3 +676,223 @@ html, body {
|
|||||||
.grid-lines.visible {
|
.grid-lines.visible {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ==================== 图层面板样式 ==================== */
|
||||||
|
.layers-panel {
|
||||||
|
position: fixed;
|
||||||
|
top: 60px;
|
||||||
|
right: -280px;
|
||||||
|
width: 260px;
|
||||||
|
height: calc(100vh - 80px);
|
||||||
|
background: #fff;
|
||||||
|
border-left: 1px solid #e0e0e0;
|
||||||
|
box-shadow: -2px 0 10px rgba(0,0,0,0.1);
|
||||||
|
z-index: 200;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
transition: right 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layers-panel.active {
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layers-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-bottom: 1px solid #e0e0e0;
|
||||||
|
background: #f8f8f8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layers-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layers-close {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #666;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layers-close:hover {
|
||||||
|
background: #e0e0e0;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layers-list {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px 12px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
background: #f5f5f5;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-item:hover {
|
||||||
|
background: #e8e8e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-item.selected {
|
||||||
|
background: #e3f2fd;
|
||||||
|
border-color: #4a90e2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-item.hidden {
|
||||||
|
opacity: 0.5;
|
||||||
|
display: flex !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-icon {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-right: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-info {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-name {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #333;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-type {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #999;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-visibility {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.2s;
|
||||||
|
background: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-visibility.is-hidden {
|
||||||
|
background: #f44336;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-visibility:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-visibility:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.layers-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 12px;
|
||||||
|
border-top: 1px solid #e0e0e0;
|
||||||
|
background: #f8f8f8;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layers-btn {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layers-btn:hover {
|
||||||
|
background: #f0f0f0;
|
||||||
|
border-color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layers-btn:disabled {
|
||||||
|
opacity: 0.3;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layers-btn-danger:hover {
|
||||||
|
background: #ffebee;
|
||||||
|
border-color: #ef5350;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layers-divider {
|
||||||
|
width: 1px;
|
||||||
|
height: 24px;
|
||||||
|
background: #ddd;
|
||||||
|
margin: 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 图层隐藏状态(用于画布上的元素) */
|
||||||
|
.layer-hidden {
|
||||||
|
visibility: hidden !important;
|
||||||
|
pointer-events: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移动端适配 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.layers-panel {
|
||||||
|
width: 240px;
|
||||||
|
right: -260px;
|
||||||
|
top: 50px;
|
||||||
|
height: calc(100vh - 50px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.layers-actions {
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layers-btn {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
49
index.html
49
index.html
@@ -13,6 +13,13 @@
|
|||||||
<button class="btn" id="addImageBtn">背景图</button>
|
<button class="btn" id="addImageBtn">背景图</button>
|
||||||
<button class="btn" id="addOverlayBtn">层叠图</button>
|
<button class="btn" id="addOverlayBtn">层叠图</button>
|
||||||
<button class="btn" id="addTextBtn">文本</button>
|
<button class="btn" id="addTextBtn">文本</button>
|
||||||
|
<div class="toolbar-divider-v divider-left"></div>
|
||||||
|
<div class="toolbar-action-group">
|
||||||
|
<button class="toolbar-btn-icon" id="undoBtn" title="撤销 (Ctrl+Z)">↩</button>
|
||||||
|
<button class="toolbar-btn-icon" id="redoBtn" title="重做 (Ctrl+Y)">↪</button>
|
||||||
|
</div>
|
||||||
|
<div class="toolbar-divider-v divider-right"></div>
|
||||||
|
<button class="btn" id="layersBtn">图层</button>
|
||||||
<button class="btn btn-primary" id="saveBtn">保存/预览</button>
|
<button class="btn btn-primary" id="saveBtn">保存/预览</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -57,20 +64,28 @@
|
|||||||
<input type="range" id="opacitySlider" min="0" max="100" value="100" class="opacity-slider">
|
<input type="range" id="opacitySlider" min="0" max="100" value="100" class="opacity-slider">
|
||||||
<span id="opacityValue">100%</span>
|
<span id="opacityValue">100%</span>
|
||||||
</label>
|
</label>
|
||||||
|
<div class="toolbar-divider"></div>
|
||||||
|
<label class="toolbar-label">
|
||||||
|
<span>旋转</span>
|
||||||
|
<input type="range" id="rotateSlider" min="-180" max="180" value="0" class="rotate-slider">
|
||||||
|
<span id="rotateValue">0°</span>
|
||||||
|
</label>
|
||||||
|
<div class="toolbar-divider"></div>
|
||||||
|
<button class="toolbar-btn mirror-btn" id="mirrorHBtn" title="水平镜像">↔</button>
|
||||||
</div>
|
</div>
|
||||||
<!-- 背景图编辑工具 -->
|
<!-- 背景图编辑工具 -->
|
||||||
<div class="toolbar-group bg-tools" id="bgTools">
|
<div class="toolbar-group bg-tools" id="bgTools">
|
||||||
<label class="toolbar-label">
|
|
||||||
<span>缩放</span>
|
|
||||||
<input type="range" id="bgScaleSlider" min="100" max="300" value="100" class="scale-slider">
|
|
||||||
<span id="bgScaleValue">100%</span>
|
|
||||||
</label>
|
|
||||||
<div class="toolbar-divider"></div>
|
|
||||||
<label class="toolbar-label">
|
<label class="toolbar-label">
|
||||||
<span>透明度</span>
|
<span>透明度</span>
|
||||||
<input type="range" id="bgOpacitySlider" min="0" max="100" value="100" class="opacity-slider">
|
<input type="range" id="bgOpacitySlider" min="0" max="100" value="100" class="opacity-slider">
|
||||||
<span id="bgOpacityValue">100%</span>
|
<span id="bgOpacityValue">100%</span>
|
||||||
</label>
|
</label>
|
||||||
|
<div class="toolbar-divider"></div>
|
||||||
|
<label class="toolbar-label">
|
||||||
|
<span>缩放</span>
|
||||||
|
<input type="range" id="bgScaleSlider" min="100" max="300" value="100" class="scale-slider">
|
||||||
|
<span id="bgScaleValue">100%</span>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -91,19 +106,31 @@
|
|||||||
<!-- 输入框图层 -->
|
<!-- 输入框图层 -->
|
||||||
<div class="preview-input-layer" id="previewInputLayer">
|
<div class="preview-input-layer" id="previewInputLayer">
|
||||||
<!-- 文字输入框1 - 企业 -->
|
<!-- 文字输入框1 - 企业 -->
|
||||||
<div class="text-input hidden" id="input1" contenteditable="false" data-placeholder="请输入" style="left: 98px; top: 234.445px;"></div>
|
<div class="text-input hidden" id="input1" contenteditable="false" data-placeholder="请输入" style="left: 98px; top: 234.445px; font-weight: bold;"></div>
|
||||||
<!-- 文字输入框2 - 姓名 -->
|
<!-- 文字输入框2 - 姓名 -->
|
||||||
<div class="text-input hidden" id="input2" contenteditable="false" data-placeholder="请输入" style="left: 98px; top: 276.355px;"></div>
|
<div class="text-input hidden" id="input2" contenteditable="false" data-placeholder="请输入" style="left: 98px; top: 276.355px; font-weight: bold;"></div>
|
||||||
<!-- 文字输入框3 - 编号 -->
|
<!-- 文字输入框3 - 编号 -->
|
||||||
<div class="text-input hidden" id="input3" contenteditable="false" data-placeholder="请输入" style="left: 98px; top: 319.048px;"></div>
|
<div class="text-input hidden" id="input3" contenteditable="false" data-placeholder="请输入" style="left: 98px; top: 319.048px; font-weight: bold;"></div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 提示文字(最上层,层叠图之下) -->
|
<!-- 提示文字(最上层,层叠图之下) -->
|
||||||
<div class="empty-tip" id="emptyTip">请点击上方「添加图片」按钮上传背景图</div>
|
<div class="empty-tip" id="emptyTip">请点击上方「添加图片」按钮上传背景图</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 长按提示 -->
|
|
||||||
<div class="long-press-tip" id="longPressTip">长按屏幕可保存图片</div>
|
<!-- 图层面板 -->
|
||||||
|
<div class="layers-panel" id="layersPanel">
|
||||||
|
<div class="layers-header">
|
||||||
|
<span class="layers-title">图层</span>
|
||||||
|
<button class="layers-close" id="layersClose">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="layers-list" id="layersList">
|
||||||
|
<!-- 图层项将动态生成 -->
|
||||||
|
</div>
|
||||||
|
<div class="layers-actions">
|
||||||
|
<button class="layers-btn" id="layerToggleVisible" title="显示/隐藏">👁</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input type="file" id="fileInput" accept="image/*" hidden>
|
<input type="file" id="fileInput" accept="image/*" hidden>
|
||||||
|
|||||||
1
js/qrcode.min.js
vendored
Normal file
1
js/qrcode.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1204
js/script.js
1204
js/script.js
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user