z-certificate-editor 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +557 -0
- package/dist/style.css +1 -0
- package/dist/z-certificate-editor.es.js +1602 -0
- package/dist/z-certificate-editor.umd.js +1 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
# 证书编辑器 (Certificate Editor)
|
|
2
|
+
|
|
3
|
+
一个功能强大的基于 Vue 3 的可视化证书编辑器,支持拖拽编辑、系统字段关联、模板管理、图片/二维码处理等功能。
|
|
4
|
+
|
|
5
|
+
## ✨ 功能特性
|
|
6
|
+
|
|
7
|
+
### 核心功能
|
|
8
|
+
|
|
9
|
+
- 🎨 **可视化编辑**: 直观的拖拽式编辑界面,所见即所得
|
|
10
|
+
- 📝 **文本元素**: 支持短文本和长文本元素,丰富的格式化选项
|
|
11
|
+
- 🖼️ **图片元素**: 支持本地上传、URL输入、关联系统字段(如用户头像)
|
|
12
|
+
- 🔲 **二维码元素**: 支持文本内容、URL输入、关联系统字段(如证书编号、身份证号)
|
|
13
|
+
- 🎯 **系统字段关联**: 动态关联系统字段,实现证书内容的自动化填充
|
|
14
|
+
- 📐 **精确定位**: 支持精确的位置(X、Y)和尺寸(宽度、高度)设置
|
|
15
|
+
- 🎨 **丰富的文本格式**: 字体大小、字体族、粗体、斜体、下划线、颜色、对齐方式、行高等
|
|
16
|
+
|
|
17
|
+
### 模板管理
|
|
18
|
+
|
|
19
|
+
- 📄 **预设模板**: 提供横版和竖版预设证书模板
|
|
20
|
+
- 🔄 **模板切换**: 支持横版/竖版模板快速切换
|
|
21
|
+
- 📤 **自定义模板**: 支持上传自定义背景图片(JPG、JPEG、PNG等格式,最大10MB)
|
|
22
|
+
- 📏 **智能尺寸建议**: 根据模板类型(横版/竖版)自动推荐合适的尺寸
|
|
23
|
+
|
|
24
|
+
### 数据管理
|
|
25
|
+
|
|
26
|
+
- 📊 **字段数据管理**: 可视化编辑和预览字段数据
|
|
27
|
+
- 🔍 **字段分类**: 系统字段按类别组织(基本信息、考试信息、证书信息、日期信息等)
|
|
28
|
+
- 💾 **配置保存**: 一键保存证书配置(模板、元素、字段数据)到控制台
|
|
29
|
+
- 👁️ **实时预览**: 支持预览最终证书效果,自动替换字段占位符为实际数据
|
|
30
|
+
|
|
31
|
+
### 交互功能
|
|
32
|
+
|
|
33
|
+
- 🖱️ **拖拽移动**: 选中元素后可拖拽移动位置
|
|
34
|
+
- 🔄 **调整大小**: 选中元素后拖动四角控制点调整大小
|
|
35
|
+
- ✏️ **属性编辑**: 右侧属性面板实时编辑选中元素的各项属性
|
|
36
|
+
- 🗑️ **元素删除**: 支持删除不需要的元素
|
|
37
|
+
|
|
38
|
+
## 📁 项目结构
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
drawcert/
|
|
42
|
+
├── src/
|
|
43
|
+
│ ├── components/
|
|
44
|
+
│ │ ├── CertificateEditor.vue # 主编辑器组件(核心组件)
|
|
45
|
+
│ │ ├── DesignPanel.vue # 左侧设计面板(元素添加、模板管理)
|
|
46
|
+
│ │ ├── CanvasArea.vue # 中间画布区域(证书展示和编辑)
|
|
47
|
+
│ │ ├── PropertiesPanel.vue # 右侧属性编辑面板
|
|
48
|
+
│ │ ├── FieldDataPanel.vue # 字段数据管理面板
|
|
49
|
+
│ │ ├── FieldDialog.vue # 字段关联对话框
|
|
50
|
+
│ │ └── CertificatePreview.vue # 证书预览组件(模态框)
|
|
51
|
+
│ ├── utils/
|
|
52
|
+
│ │ ├── fieldManager.js # 系统字段管理和工具函数
|
|
53
|
+
│ │ └── templateManager.js # 模板管理和工具函数
|
|
54
|
+
│ ├── App.vue # 根组件
|
|
55
|
+
│ ├── main.js # 入口文件
|
|
56
|
+
│ └── style.css # 全局样式
|
|
57
|
+
├── index.html
|
|
58
|
+
├── package.json
|
|
59
|
+
├── vite.config.js
|
|
60
|
+
└── README.md
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## 📦 作为 npm 包使用
|
|
64
|
+
|
|
65
|
+
### 安装
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npm install z-certificate-editor
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### 在 Vue 3 项目中使用
|
|
72
|
+
|
|
73
|
+
```vue
|
|
74
|
+
<template>
|
|
75
|
+
<CertificateEditor />
|
|
76
|
+
</template>
|
|
77
|
+
|
|
78
|
+
<script setup>
|
|
79
|
+
import CertificateEditor from 'z-certificate-editor'
|
|
80
|
+
import 'z-certificate-editor/style.css'
|
|
81
|
+
</script>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### 按需导入组件
|
|
85
|
+
|
|
86
|
+
```vue
|
|
87
|
+
<template>
|
|
88
|
+
<div>
|
|
89
|
+
<DesignPanel @add-element="handleAddElement" />
|
|
90
|
+
<CanvasArea :elements="elements" />
|
|
91
|
+
<PropertiesPanel :element="selectedElement" />
|
|
92
|
+
</div>
|
|
93
|
+
</template>
|
|
94
|
+
|
|
95
|
+
<script setup>
|
|
96
|
+
import {
|
|
97
|
+
DesignPanel,
|
|
98
|
+
CanvasArea,
|
|
99
|
+
PropertiesPanel
|
|
100
|
+
} from 'z-certificate-editor'
|
|
101
|
+
import 'z-certificate-editor/style.css'
|
|
102
|
+
</script>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### 使用工具函数
|
|
106
|
+
|
|
107
|
+
```javascript
|
|
108
|
+
import {
|
|
109
|
+
SYSTEM_FIELDS,
|
|
110
|
+
replaceFields,
|
|
111
|
+
extractFields,
|
|
112
|
+
PRESET_TEMPLATES
|
|
113
|
+
} from 'z-certificate-editor'
|
|
114
|
+
|
|
115
|
+
// 使用系统字段
|
|
116
|
+
console.log(SYSTEM_FIELDS)
|
|
117
|
+
|
|
118
|
+
// 替换字段占位符
|
|
119
|
+
const text = '{{姓名}}({{性别称呼}})'
|
|
120
|
+
const fieldData = { name: '张三', gender: '先生' }
|
|
121
|
+
const result = replaceFields(text, fieldData)
|
|
122
|
+
// 结果: '张三(先生)'
|
|
123
|
+
|
|
124
|
+
// 提取字段
|
|
125
|
+
const fields = extractFields(text)
|
|
126
|
+
// 结果: [{ key: 'name', label: '姓名', ... }, { key: 'gender', label: '性别称呼', ... }]
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## 🚀 本地开发
|
|
130
|
+
|
|
131
|
+
### 环境要求
|
|
132
|
+
|
|
133
|
+
- Node.js >= 16.0.0
|
|
134
|
+
- npm >= 7.0.0
|
|
135
|
+
|
|
136
|
+
### 安装依赖
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
npm install
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### 开发模式
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
npm run dev
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
启动后访问 `http://localhost:3000`(或终端显示的地址)
|
|
149
|
+
|
|
150
|
+
### 构建 npm 包
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
npm run build
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
构建产物将输出到 `dist` 目录,包含:
|
|
157
|
+
- `z-certificate-editor.es.js` - ES 模块版本
|
|
158
|
+
- `z-certificate-editor.umd.js` - UMD 版本
|
|
159
|
+
- `style.css` - 样式文件
|
|
160
|
+
|
|
161
|
+
### 预览生产版本
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
npm run preview
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## 📤 发布到 npm
|
|
168
|
+
|
|
169
|
+
1. 确保已登录 npm:
|
|
170
|
+
```bash
|
|
171
|
+
npm login
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
2. 更新版本号(可选):
|
|
175
|
+
```bash
|
|
176
|
+
npm version patch # 或 minor, major
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
3. 发布:
|
|
180
|
+
```bash
|
|
181
|
+
npm publish
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
注意:发布前会自动执行 `npm run build` 构建包。
|
|
185
|
+
|
|
186
|
+
## 📖 使用说明
|
|
187
|
+
|
|
188
|
+
### 基本操作流程
|
|
189
|
+
|
|
190
|
+
1. **选择模板**
|
|
191
|
+
- 点击左侧面板的"底图模板"标签
|
|
192
|
+
- 选择横版或竖版预设模板,或上传自定义背景图片
|
|
193
|
+
- 模板背景将自动铺满整个证书画布
|
|
194
|
+
|
|
195
|
+
2. **添加元素**
|
|
196
|
+
- 点击左侧面板的"设计元素"标签
|
|
197
|
+
- 选择要添加的元素类型:
|
|
198
|
+
- **短文本**: 适合标题、姓名等简短文本
|
|
199
|
+
- **长文本**: 适合描述、说明等较长文本
|
|
200
|
+
- **图片**: 支持上传图片或关联用户头像字段
|
|
201
|
+
- **二维码**: 支持文本内容或关联证书编号等字段
|
|
202
|
+
|
|
203
|
+
3. **编辑元素**
|
|
204
|
+
- 点击画布上的元素进行选择(选中后显示边框和控制点)
|
|
205
|
+
- 在右侧属性面板编辑元素的各种属性
|
|
206
|
+
- 可以拖拽移动元素位置
|
|
207
|
+
- 可以拖动四角控制点调整元素大小
|
|
208
|
+
|
|
209
|
+
4. **关联系统字段**
|
|
210
|
+
- 对于文本元素:点击内容输入框右侧的"+"按钮,打开字段关联对话框
|
|
211
|
+
- 选择要关联的系统字段,字段占位符将插入到文本中
|
|
212
|
+
- 在字段数据面板中编辑字段的实际值
|
|
213
|
+
|
|
214
|
+
5. **预览和保存**
|
|
215
|
+
- 点击顶部工具栏的"预览"按钮,查看最终证书效果
|
|
216
|
+
- 点击"保存"按钮,将当前配置保存到浏览器控制台
|
|
217
|
+
|
|
218
|
+
### 文本元素
|
|
219
|
+
|
|
220
|
+
#### 文本格式化选项
|
|
221
|
+
|
|
222
|
+
- **字体大小**: 从下拉菜单选择预设大小(12px - 72px)
|
|
223
|
+
- **字体**: 选择字体族(如:宋体、微软雅黑、Arial等)
|
|
224
|
+
- **样式**:
|
|
225
|
+
- 粗体 (B)
|
|
226
|
+
- 斜体 (I)
|
|
227
|
+
- 下划线 (U)
|
|
228
|
+
- **颜色**: 点击颜色选择器设置文字颜色
|
|
229
|
+
- **对齐**: 左对齐、居中、右对齐、两端对齐
|
|
230
|
+
- **行高**: 设置文本行高(1.0 - 3.0)
|
|
231
|
+
|
|
232
|
+
#### 字段关联
|
|
233
|
+
|
|
234
|
+
- 在文本内容中插入字段占位符,如:`{{姓名}}`、`{{考试名称}}`
|
|
235
|
+
- 系统会自动识别并高亮显示字段占位符
|
|
236
|
+
- 在字段数据面板中编辑字段的实际值
|
|
237
|
+
|
|
238
|
+
### 图片元素
|
|
239
|
+
|
|
240
|
+
#### 图片来源
|
|
241
|
+
|
|
242
|
+
1. **本地上传**
|
|
243
|
+
- 点击"本地上传"按钮选择本地图片文件
|
|
244
|
+
- 支持格式:JPG、JPEG、PNG、GIF、WEBP、SVG、BMP
|
|
245
|
+
- 文件大小限制:最大 2MB
|
|
246
|
+
- 上传后自动转换为 Base64 格式
|
|
247
|
+
|
|
248
|
+
2. **URL输入**
|
|
249
|
+
- 在URL输入框中输入图片的网络地址
|
|
250
|
+
- 支持 http/https 协议的图片链接
|
|
251
|
+
|
|
252
|
+
3. **关联字段**
|
|
253
|
+
- 选择关联的系统字段(如:头像、照片、头像图片)
|
|
254
|
+
- 系统会使用字段数据中的图片URL或Base64数据
|
|
255
|
+
|
|
256
|
+
#### 图片尺寸
|
|
257
|
+
|
|
258
|
+
- 可以自由设置图片的宽度和高度
|
|
259
|
+
- 支持像素(px)单位
|
|
260
|
+
|
|
261
|
+
### 二维码元素
|
|
262
|
+
|
|
263
|
+
#### 二维码内容来源
|
|
264
|
+
|
|
265
|
+
1. **本地上传**
|
|
266
|
+
- 点击"本地上传"按钮选择二维码图片
|
|
267
|
+
- 支持格式:JPG、JPEG、PNG、GIF、WEBP、SVG、BMP
|
|
268
|
+
- 文件大小限制:最大 2MB
|
|
269
|
+
|
|
270
|
+
2. **URL/内容输入**
|
|
271
|
+
- 输入文本内容(将生成二维码)
|
|
272
|
+
- 输入图片URL(直接显示二维码图片)
|
|
273
|
+
|
|
274
|
+
3. **关联字段**
|
|
275
|
+
- 选择关联的系统字段(如:证书编号、身份证号、链接地址、考试名称)
|
|
276
|
+
- 系统会使用字段数据中的内容生成二维码
|
|
277
|
+
|
|
278
|
+
#### 二维码尺寸
|
|
279
|
+
|
|
280
|
+
- 可以自由设置二维码的宽度和高度
|
|
281
|
+
- 支持像素(px)单位
|
|
282
|
+
|
|
283
|
+
### 模板管理
|
|
284
|
+
|
|
285
|
+
#### 预设模板
|
|
286
|
+
|
|
287
|
+
- 系统提供多套预设的横版和竖版证书模板
|
|
288
|
+
- 点击"底图模板"标签切换到模板管理
|
|
289
|
+
- 点击模板类型按钮("竖版"或"横版")筛选模板
|
|
290
|
+
- 点击模板卡片选择并应用模板
|
|
291
|
+
|
|
292
|
+
#### 自定义模板上传
|
|
293
|
+
|
|
294
|
+
1. 点击"上传自定义模板"按钮
|
|
295
|
+
2. 选择要上传的背景图片文件
|
|
296
|
+
3. 系统会根据当前选择的模板类型(横版/竖版)自动设置推荐尺寸
|
|
297
|
+
4. 上传成功后,模板将自动应用
|
|
298
|
+
|
|
299
|
+
**上传要求**:
|
|
300
|
+
- 支持格式:JPG、JPEG、PNG、GIF、WEBP、SVG、BMP
|
|
301
|
+
- 文件大小:最大 2MB
|
|
302
|
+
- 建议尺寸:
|
|
303
|
+
- 竖版:972 x 1378 px 或相近比例
|
|
304
|
+
- 横版:1303 x 897 px 或相近比例
|
|
305
|
+
|
|
306
|
+
### 字段数据管理
|
|
307
|
+
|
|
308
|
+
- 在右侧面板底部的"字段数据"区域管理字段值
|
|
309
|
+
- 系统会根据证书中使用的字段自动显示相应的输入框
|
|
310
|
+
- 编辑字段值后,画布上的占位符会自动更新(预览模式)
|
|
311
|
+
|
|
312
|
+
### 预览功能
|
|
313
|
+
|
|
314
|
+
- 点击顶部工具栏的"预览"按钮打开预览模态框
|
|
315
|
+
- 预览模式下,所有字段占位符会被替换为实际字段数据
|
|
316
|
+
- 预览的证书即为最终效果
|
|
317
|
+
|
|
318
|
+
### 保存功能
|
|
319
|
+
|
|
320
|
+
- 点击顶部工具栏的"保存"按钮
|
|
321
|
+
- 系统会将当前证书配置(模板、元素列表、字段数据)输出到浏览器控制台
|
|
322
|
+
- 打开浏览器开发者工具(F12)查看保存的数据
|
|
323
|
+
|
|
324
|
+
**保存的数据结构**:
|
|
325
|
+
```json
|
|
326
|
+
{
|
|
327
|
+
"template": {
|
|
328
|
+
"id": "template-id",
|
|
329
|
+
"name": "模板名称",
|
|
330
|
+
"background": "背景图片或颜色",
|
|
331
|
+
"width": 972,
|
|
332
|
+
"height": 1378
|
|
333
|
+
},
|
|
334
|
+
"elements": [
|
|
335
|
+
{
|
|
336
|
+
"id": 1234567890,
|
|
337
|
+
"type": "text",
|
|
338
|
+
"content": "{{姓名}}({{性别称呼}})",
|
|
339
|
+
"x": 100,
|
|
340
|
+
"y": 200,
|
|
341
|
+
"width": 300,
|
|
342
|
+
"fontSize": 24,
|
|
343
|
+
// ... 其他属性
|
|
344
|
+
}
|
|
345
|
+
],
|
|
346
|
+
"fieldData": {
|
|
347
|
+
"name": "张三",
|
|
348
|
+
"gender": "先生",
|
|
349
|
+
// ... 其他字段数据
|
|
350
|
+
},
|
|
351
|
+
"timestamp": "2024-01-01T00:00:00.000Z"
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
## 🧩 组件说明
|
|
356
|
+
|
|
357
|
+
### CertificateEditor
|
|
358
|
+
|
|
359
|
+
主编辑器组件,负责整体的状态管理和组件协调。
|
|
360
|
+
|
|
361
|
+
**功能**:
|
|
362
|
+
- 管理元素列表、选中元素、字段数据、当前模板
|
|
363
|
+
- 协调各子组件的通信
|
|
364
|
+
- 提供预览和保存功能
|
|
365
|
+
|
|
366
|
+
### DesignPanel
|
|
367
|
+
|
|
368
|
+
左侧设计面板,提供元素添加和模板管理功能。
|
|
369
|
+
|
|
370
|
+
**功能**:
|
|
371
|
+
- 切换"设计元素"和"底图模板"标签
|
|
372
|
+
- 提供元素添加按钮(短文本、长文本、图片、二维码)
|
|
373
|
+
- 管理预设模板和自定义模板上传
|
|
374
|
+
|
|
375
|
+
**Events**:
|
|
376
|
+
- `add-element`: 添加新元素时触发,参数为元素类型
|
|
377
|
+
- `select-template`: 选择模板时触发,参数为模板对象
|
|
378
|
+
- `upload-template`: 上传模板时触发,参数为模板对象
|
|
379
|
+
|
|
380
|
+
### CanvasArea
|
|
381
|
+
|
|
382
|
+
中间画布区域,显示证书模板和可编辑元素。
|
|
383
|
+
|
|
384
|
+
**功能**:
|
|
385
|
+
- 渲染证书背景模板
|
|
386
|
+
- 渲染和交互所有元素(文本、图片、二维码)
|
|
387
|
+
- 处理元素的拖拽和调整大小
|
|
388
|
+
- 替换字段占位符为实际数据
|
|
389
|
+
|
|
390
|
+
**Props**:
|
|
391
|
+
- `elements`: Array - 元素列表
|
|
392
|
+
- `selectedElement`: Object | null - 当前选中的元素
|
|
393
|
+
- `fieldData`: Object - 字段数据对象
|
|
394
|
+
- `template`: Object - 当前选中的模板
|
|
395
|
+
|
|
396
|
+
**Events**:
|
|
397
|
+
- `select-element`: 选择元素时触发
|
|
398
|
+
- `update-element`: 更新元素时触发
|
|
399
|
+
- `delete-element`: 删除元素时触发
|
|
400
|
+
|
|
401
|
+
### PropertiesPanel
|
|
402
|
+
|
|
403
|
+
右侧属性编辑面板,编辑选中元素的属性。
|
|
404
|
+
|
|
405
|
+
**功能**:
|
|
406
|
+
- 根据元素类型动态显示相应的编辑选项
|
|
407
|
+
- 提供文本格式化、图片设置、二维码设置等功能
|
|
408
|
+
- 实时更新元素属性
|
|
409
|
+
|
|
410
|
+
**Props**:
|
|
411
|
+
- `element`: Object - 当前选中的元素
|
|
412
|
+
|
|
413
|
+
**Events**:
|
|
414
|
+
- `update-element`: 更新元素时触发
|
|
415
|
+
|
|
416
|
+
### FieldDataPanel
|
|
417
|
+
|
|
418
|
+
字段数据管理面板,显示和编辑字段数据。
|
|
419
|
+
|
|
420
|
+
**功能**:
|
|
421
|
+
- 显示当前使用的系统字段
|
|
422
|
+
- 提供字段值的输入框
|
|
423
|
+
- 实时更新字段数据
|
|
424
|
+
|
|
425
|
+
**Props**:
|
|
426
|
+
- `fieldData`: Object - 字段数据对象
|
|
427
|
+
|
|
428
|
+
**Events**:
|
|
429
|
+
- `update-field-data`: 更新字段数据时触发
|
|
430
|
+
|
|
431
|
+
### FieldDialog
|
|
432
|
+
|
|
433
|
+
字段关联对话框,用于在文本中插入系统字段。
|
|
434
|
+
|
|
435
|
+
**功能**:
|
|
436
|
+
- 显示系统字段列表(按类别分组)
|
|
437
|
+
- 支持搜索字段
|
|
438
|
+
- 插入字段占位符到文本内容
|
|
439
|
+
|
|
440
|
+
### CertificatePreview
|
|
441
|
+
|
|
442
|
+
证书预览组件,以模态框形式显示最终证书效果。
|
|
443
|
+
|
|
444
|
+
**功能**:
|
|
445
|
+
- 渲染完整的证书预览
|
|
446
|
+
- 自动替换所有字段占位符为实际数据
|
|
447
|
+
- 支持关闭预览
|
|
448
|
+
|
|
449
|
+
## 🛠️ 技术栈
|
|
450
|
+
|
|
451
|
+
- **Vue 3**: 使用 Composition API 进行组件开发
|
|
452
|
+
- **Vite**: 快速的前端构建工具
|
|
453
|
+
- **原生 CSS**: 无第三方CSS框架,纯原生样式
|
|
454
|
+
- **Canvas/HTML**: 基于HTML和CSS的渲染,非Canvas绘制
|
|
455
|
+
|
|
456
|
+
## 📋 系统字段列表
|
|
457
|
+
|
|
458
|
+
系统内置了丰富的字段类型,按类别组织:
|
|
459
|
+
|
|
460
|
+
### 基本信息
|
|
461
|
+
- 姓名 (name)
|
|
462
|
+
- 性别称呼 (gender)
|
|
463
|
+
- 账号 (account)
|
|
464
|
+
|
|
465
|
+
### 考试信息
|
|
466
|
+
- 考试名称 (examName)
|
|
467
|
+
- 成绩 (score)
|
|
468
|
+
- 成绩等级 (scoreLevel)
|
|
469
|
+
- 考试分数 (examScore)
|
|
470
|
+
|
|
471
|
+
### 证书信息
|
|
472
|
+
- 证书编号 (certificateNumber)
|
|
473
|
+
- 有效期 (validityPeriod)
|
|
474
|
+
|
|
475
|
+
### 日期信息
|
|
476
|
+
- 签发日期 (issueDate)
|
|
477
|
+
- 签发日期-年 (issueDateYear)
|
|
478
|
+
- 签发日期-月 (issueDateMonth)
|
|
479
|
+
- 签发日期-日 (issueDateDay)
|
|
480
|
+
- 生效日期 (effectiveDate)
|
|
481
|
+
- 到期日期 (expiryDate)
|
|
482
|
+
|
|
483
|
+
### 其他信息
|
|
484
|
+
- 身份证号 (idNumber)
|
|
485
|
+
- 手机号 (phone)
|
|
486
|
+
- 邮箱 (email)
|
|
487
|
+
- 地址 (address)
|
|
488
|
+
- 机构名称 (organization)
|
|
489
|
+
- 链接地址 (url)
|
|
490
|
+
|
|
491
|
+
### 图片字段
|
|
492
|
+
- 头像 (avatar)
|
|
493
|
+
- 照片 (photo)
|
|
494
|
+
- 头像图片 (headImage)
|
|
495
|
+
|
|
496
|
+
## 🌐 浏览器支持
|
|
497
|
+
|
|
498
|
+
- ✅ Chrome (最新版)
|
|
499
|
+
- ✅ Firefox (最新版)
|
|
500
|
+
- ✅ Safari (最新版)
|
|
501
|
+
- ✅ Edge (最新版)
|
|
502
|
+
|
|
503
|
+
## 📝 开发计划
|
|
504
|
+
|
|
505
|
+
### 已完成功能 ✅
|
|
506
|
+
|
|
507
|
+
- [x] 可视化拖拽编辑
|
|
508
|
+
- [x] 文本元素(短文本、长文本)
|
|
509
|
+
- [x] 图片元素(本地上传、URL、关联字段)
|
|
510
|
+
- [x] 二维码元素(文本内容、URL、关联字段)
|
|
511
|
+
- [x] 系统字段关联
|
|
512
|
+
- [x] 预设模板管理
|
|
513
|
+
- [x] 自定义模板上传
|
|
514
|
+
- [x] 字段数据管理
|
|
515
|
+
- [x] 预览功能
|
|
516
|
+
- [x] 配置保存功能
|
|
517
|
+
|
|
518
|
+
### 计划中功能 🚧
|
|
519
|
+
|
|
520
|
+
- [ ] 二维码生成(根据文本内容自动生成二维码图片)
|
|
521
|
+
- [ ] 撤销/重做功能
|
|
522
|
+
- [ ] 元素复制/粘贴
|
|
523
|
+
- [ ] 元素对齐辅助线
|
|
524
|
+
- [ ] 导出功能(PNG、PDF)
|
|
525
|
+
- [ ] 导入功能(导入保存的配置)
|
|
526
|
+
- [ ] 元素层级管理(置顶、置底、上移、下移)
|
|
527
|
+
- [ ] 批量操作(多选、批量移动、批量删除)
|
|
528
|
+
- [ ] 模板收藏和分类
|
|
529
|
+
- [ ] 响应式设计适配
|
|
530
|
+
|
|
531
|
+
## 📄 许可证
|
|
532
|
+
|
|
533
|
+
MIT
|
|
534
|
+
|
|
535
|
+
## 👨💻 开发说明
|
|
536
|
+
|
|
537
|
+
### 核心概念
|
|
538
|
+
|
|
539
|
+
1. **元素系统**: 所有可编辑的元素(文本、图片、二维码)都遵循统一的数据结构
|
|
540
|
+
2. **字段系统**: 使用占位符(如 `{{姓名}}`)实现动态内容替换
|
|
541
|
+
3. **模板系统**: 支持预设模板和自定义模板,背景自动铺满画布
|
|
542
|
+
4. **状态管理**: 使用 Vue 3 Composition API 的 `ref` 和 `reactive` 管理状态
|
|
543
|
+
|
|
544
|
+
### 代码规范
|
|
545
|
+
|
|
546
|
+
- 使用 Composition API
|
|
547
|
+
- 组件采用单文件组件(SFC)格式
|
|
548
|
+
- 使用语义化的CSS类名
|
|
549
|
+
- 保持组件的单一职责原则
|
|
550
|
+
|
|
551
|
+
## 🤝 贡献
|
|
552
|
+
|
|
553
|
+
欢迎提交 Issue 和 Pull Request!
|
|
554
|
+
|
|
555
|
+
## 📞 联系方式
|
|
556
|
+
|
|
557
|
+
如有问题或建议,请提交 Issue。
|
package/dist/style.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.design-panel[data-v-b694f9ff]{display:flex;flex-direction:column;height:100%;background:#fff}.panel-tabs[data-v-b694f9ff]{display:flex;border-bottom:1px solid #e0e0e0;background:#fafafa}.tab-item[data-v-b694f9ff]{flex:1;padding:12px;text-align:center;cursor:pointer;font-size:14px;color:#666;border-bottom:2px solid transparent;transition:all .3s}.tab-item[data-v-b694f9ff]:hover{color:#1890ff;background:#f0f0f0}.tab-item.active[data-v-b694f9ff]{color:#1890ff;border-bottom-color:#1890ff;background:#fff;font-weight:500}.panel-content[data-v-b694f9ff]{flex:1;overflow-y:auto;padding:16px}.elements-section[data-v-b694f9ff]{width:100%}.elements-grid[data-v-b694f9ff]{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}.element-item[data-v-b694f9ff]{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:16px;border:1px solid #e0e0e0;border-radius:4px;cursor:pointer;transition:all .3s;background:#fff}.element-item[data-v-b694f9ff]:hover{border-color:#1890ff;background:#f0f7ff;transform:translateY(-2px);box-shadow:0 2px 8px #1890ff33}.element-icon[data-v-b694f9ff]{font-size:32px;margin-bottom:8px}.element-label[data-v-b694f9ff]{font-size:12px;color:#333}.templates-section[data-v-b694f9ff]{width:100%}.upload-section[data-v-b694f9ff]{padding:16px;border:1px dashed #d9d9d9;border-radius:4px;text-align:center;background:#fafafa;margin-bottom:16px}.template-type-switch[data-v-b694f9ff]{display:flex;gap:8px;margin-bottom:16px;padding:4px;background:#f5f5f5;border-radius:4px}.switch-btn[data-v-b694f9ff]{flex:1;padding:6px 12px;border:none;background:transparent;color:#666;border-radius:4px;cursor:pointer;font-size:14px;transition:all .3s}.switch-btn.active[data-v-b694f9ff]{background:#fff;color:#1890ff;font-weight:500;box-shadow:0 1px 2px #0000001a}.templates-list[data-v-b694f9ff]{display:flex;flex-direction:column;gap:12px;margin-bottom:16px}.template-item[data-v-b694f9ff]{border:1px solid #e0e0e0;border-radius:4px;overflow:hidden;cursor:pointer;transition:all .3s;background:#fff}.template-item[data-v-b694f9ff]:hover{border-color:#1890ff;box-shadow:0 2px 8px #1890ff33}.template-preview[data-v-b694f9ff]{width:100%;height:120px;background:#f5f5f5}.template-name[data-v-b694f9ff]{padding:8px 12px;font-size:13px;color:#333;text-align:center;background:#fff}.upload-btn[data-v-b694f9ff]{display:flex;align-items:center;justify-content:center;gap:6px;width:100%;padding:8px 16px;border:1px solid #d9d9d9;background:#fff;color:#333;border-radius:4px;cursor:pointer;font-size:14px;transition:all .3s;margin-bottom:8px}.upload-btn[data-v-b694f9ff]:hover{border-color:#1890ff;color:#1890ff}.upload-hint[data-v-b694f9ff]{font-size:11px;color:#999;line-height:1.5}.canvas-area[data-v-ac0f08fc]{width:100%;height:100%;overflow:auto;background:#f0f0f0;display:flex;justify-content:center;align-items:flex-start;padding:40px 20px}.certificate-canvas[data-v-ac0f08fc]{position:relative;width:800px;min-height:1000px;background:#fff;box-shadow:0 4px 12px #0000001a;padding:60px 50px}.certificate-border[data-v-ac0f08fc]{position:absolute;top:0;left:0;right:0;bottom:0;border:12px solid #1890ff;background-image:repeating-linear-gradient(0deg,transparent,transparent 2px,rgba(255,255,255,.1) 2px,rgba(255,255,255,.1) 4px),repeating-linear-gradient(90deg,transparent,transparent 2px,rgba(255,255,255,.1) 2px,rgba(255,255,255,.1) 4px);background-size:20px 20px;box-shadow:inset 0 0 0 2px #ffffff4d,inset 0 0 0 4px #1890ff,inset 0 0 0 6px #ffffff4d}.editable-element[data-v-ac0f08fc]{position:absolute;border:1px dashed #ccc;cursor:move;min-width:50px;min-height:30px;padding:4px}.editable-element.selected[data-v-ac0f08fc]{border-color:#1890ff;border-width:2px;background:#1890ff0d}.editable-element.text[data-v-ac0f08fc],.editable-element.longtext[data-v-ac0f08fc]{border:1px dashed #999}.editable-element.text.selected[data-v-ac0f08fc],.editable-element.longtext.selected[data-v-ac0f08fc]{border-color:#1890ff;border-width:2px}.text-content[data-v-ac0f08fc]{width:100%;height:100%;word-wrap:break-word;white-space:pre-wrap;padding:2px;min-height:20px}.field-placeholder-highlight[data-v-ac0f08fc]{background:#e6f7ff;color:#1890ff;padding:2px 4px;border-radius:2px;font-weight:500}.field-value[data-v-ac0f08fc]{color:#52c41a;font-weight:500}.image-placeholder[data-v-ac0f08fc],.qrcode-placeholder[data-v-ac0f08fc]{width:100%;height:100%;display:flex;align-items:center;justify-content:center;background:#f5f5f5;border:1px dashed #ccc;color:#999}.image-element[data-v-ac0f08fc],.qrcode-element[data-v-ac0f08fc]{width:100%;height:100%;overflow:hidden}.element-image[data-v-ac0f08fc]{width:100%;height:100%;object-fit:contain;display:block}.qrcode-content[data-v-ac0f08fc]{width:100%;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;background:#f5f5f5;border:1px dashed #ccc;padding:8px}.qrcode-placeholder-text[data-v-ac0f08fc]{font-size:12px;color:#999;margin-bottom:4px}.qrcode-info[data-v-ac0f08fc]{font-size:10px;color:#666;word-break:break-all;text-align:center}.resize-handles[data-v-ac0f08fc]{position:absolute;top:-4px;left:-4px;right:-4px;bottom:-4px;pointer-events:none}.handle[data-v-ac0f08fc]{position:absolute;width:8px;height:8px;background:#1890ff;border:1px solid #fff;pointer-events:all;cursor:nwse-resize}.handle-nw[data-v-ac0f08fc]{top:-4px;left:-4px;cursor:nwse-resize}.handle-ne[data-v-ac0f08fc]{top:-4px;right:-4px;cursor:nesw-resize}.handle-sw[data-v-ac0f08fc]{bottom:-4px;left:-4px;cursor:nesw-resize}.handle-se[data-v-ac0f08fc]{bottom:-4px;right:-4px;cursor:nwse-resize}.delete-btn[data-v-ac0f08fc]{position:absolute;top:-12px;right:-12px;width:24px;height:24px;border-radius:50%;background:#ff4d4f;color:#fff;border:none;cursor:pointer;font-size:18px;line-height:1;display:flex;align-items:center;justify-content:center;box-shadow:0 2px 4px #0003}.delete-btn[data-v-ac0f08fc]:hover{background:#ff7875}.field-dialog-overlay[data-v-6745c3ca]{position:fixed;top:0;left:0;right:0;bottom:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:1000}.field-dialog[data-v-6745c3ca]{background:#fff;border-radius:8px;width:600px;max-height:80vh;display:flex;flex-direction:column;box-shadow:0 4px 20px #00000026}.dialog-header[data-v-6745c3ca]{display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid #e0e0e0}.dialog-header h3[data-v-6745c3ca]{font-size:18px;font-weight:500;color:#333;margin:0}.close-btn[data-v-6745c3ca]{width:32px;height:32px;border:none;background:transparent;font-size:24px;color:#999;cursor:pointer;display:flex;align-items:center;justify-content:center;border-radius:4px;transition:all .3s}.close-btn[data-v-6745c3ca]:hover{background:#f5f5f5;color:#333}.dialog-content[data-v-6745c3ca]{flex:1;display:flex;flex-direction:column;padding:20px;overflow:hidden}.search-box[data-v-6745c3ca]{margin-bottom:16px}.search-input[data-v-6745c3ca]{width:100%;padding:8px 12px;border:1px solid #d9d9d9;border-radius:4px;font-size:14px}.search-input[data-v-6745c3ca]:focus{outline:none;border-color:#1890ff}.table-container[data-v-6745c3ca]{flex:1;overflow-y:auto;border:1px solid #e0e0e0;border-radius:4px}.fields-table[data-v-6745c3ca]{width:100%;border-collapse:collapse;background:#fff}.fields-table thead[data-v-6745c3ca]{background:#fafafa;position:sticky;top:0;z-index:10}.fields-table th[data-v-6745c3ca]{padding:12px 16px;text-align:left;font-size:14px;font-weight:500;color:#333;border-bottom:1px solid #e0e0e0}.fields-table th[data-v-6745c3ca]:first-child{width:70%}.fields-table th[data-v-6745c3ca]:last-child{width:30%;text-align:center}.fields-table tbody tr[data-v-6745c3ca]{border-bottom:1px solid #f0f0f0;transition:background .2s}.fields-table tbody tr[data-v-6745c3ca]:hover{background:#fafafa}.fields-table tbody tr[data-v-6745c3ca]:last-child{border-bottom:none}.field-row[data-v-6745c3ca]{cursor:pointer}.field-name[data-v-6745c3ca]{padding:12px 16px;font-size:14px;color:#333}.field-action[data-v-6745c3ca]{padding:12px 16px;text-align:center}.select-btn[data-v-6745c3ca]{padding:4px 16px;border:1px solid #1890ff;background:#fff;color:#1890ff;border-radius:4px;font-size:14px;cursor:pointer;transition:all .3s}.select-btn[data-v-6745c3ca]:hover{background:#1890ff;color:#fff}.pagination[data-v-6745c3ca]{display:flex;justify-content:center;align-items:center;gap:8px;margin-top:16px;padding-top:16px;border-top:1px solid #e0e0e0}.page-btn[data-v-6745c3ca]{min-width:32px;height:32px;padding:0 8px;border:1px solid #d9d9d9;background:#fff;color:#333;border-radius:4px;font-size:14px;cursor:pointer;transition:all .3s}.page-btn[data-v-6745c3ca]:hover:not(:disabled){border-color:#1890ff;color:#1890ff}.page-btn.active[data-v-6745c3ca]{background:#1890ff;border-color:#1890ff;color:#fff}.page-btn[data-v-6745c3ca]:disabled{opacity:.5;cursor:not-allowed}.properties-panel[data-v-f3dab8d1]{display:flex;flex-direction:column;height:100%;background:#fff}.panel-header[data-v-f3dab8d1]{padding:12px 16px;border-bottom:1px solid #e0e0e0}.panel-header h3[data-v-f3dab8d1]{font-size:14px;font-weight:500;color:#333;margin:0}.panel-content[data-v-f3dab8d1]{flex:1;overflow-y:auto;padding:16px}.section-title[data-v-f3dab8d1]{font-size:13px;font-weight:500;color:#666;margin-bottom:12px;padding-bottom:8px;border-bottom:1px solid #f0f0f0}.form-group[data-v-f3dab8d1]{margin-bottom:16px}.form-label[data-v-f3dab8d1]{display:block;font-size:12px;color:#666;margin-bottom:6px}.form-input[data-v-f3dab8d1],.form-select[data-v-f3dab8d1]{width:100%;padding:6px 10px;border:1px solid #d9d9d9;border-radius:4px;font-size:13px}.form-input[data-v-f3dab8d1]:focus,.form-select[data-v-f3dab8d1]:focus{outline:none;border-color:#1890ff}.content-input-wrapper[data-v-f3dab8d1]{display:flex;flex-direction:column;gap:8px}.content-textarea[data-v-f3dab8d1]{width:100%;padding:8px;border:1px solid #d9d9d9;border-radius:4px;font-size:13px;resize:vertical}.content-textarea[data-v-f3dab8d1]:focus{outline:none;border-color:#1890ff}.insert-field-btn[data-v-f3dab8d1]{display:flex;align-items:center;gap:4px;padding:6px 12px;border:1px solid #d9d9d9;background:#fff;color:#666;border-radius:4px;font-size:12px;cursor:pointer;transition:all .3s}.insert-field-btn[data-v-f3dab8d1]:hover{border-color:#1890ff;color:#1890ff}.style-buttons[data-v-f3dab8d1]{display:flex;gap:8px}.style-btn[data-v-f3dab8d1]{width:32px;height:32px;border:1px solid #d9d9d9;background:#fff;border-radius:4px;cursor:pointer;font-size:14px;transition:all .3s}.style-btn[data-v-f3dab8d1]:hover{border-color:#1890ff}.style-btn.active[data-v-f3dab8d1]{background:#1890ff;border-color:#1890ff;color:#fff}.color-input[data-v-f3dab8d1]{width:100%;height:32px;border:1px solid #d9d9d9;border-radius:4px;cursor:pointer}.align-buttons[data-v-f3dab8d1]{display:flex;gap:8px}.align-btn[data-v-f3dab8d1]{flex:1;padding:6px 12px;border:1px solid #d9d9d9;background:#fff;border-radius:4px;font-size:12px;cursor:pointer;transition:all .3s}.align-btn[data-v-f3dab8d1]:hover{border-color:#1890ff}.align-btn.active[data-v-f3dab8d1]{background:#1890ff;border-color:#1890ff;color:#fff}.radio-group[data-v-f3dab8d1]{display:flex;gap:16px}.radio-label[data-v-f3dab8d1]{display:flex;align-items:center;gap:6px;font-size:13px;cursor:pointer}.image-upload-wrapper[data-v-f3dab8d1],.qrcode-upload-wrapper[data-v-f3dab8d1]{display:flex;flex-direction:column;gap:8px}.upload-btn[data-v-f3dab8d1]{width:100%;padding:10px 16px;border:2px solid #1890ff;background:#1890ff;color:#fff;border-radius:4px;font-size:14px;font-weight:500;cursor:pointer;transition:all .3s;display:flex;align-items:center;justify-content:center;gap:6px}.upload-btn[data-v-f3dab8d1]:hover{background:#40a9ff;border-color:#40a9ff;box-shadow:0 2px 8px #1890ff4d}.upload-btn[data-v-f3dab8d1]:active{background:#096dd9;border-color:#096dd9}.image-url-input[data-v-f3dab8d1],.qrcode-input[data-v-f3dab8d1]{width:100%;padding:6px 10px;border:1px solid #d9d9d9;border-radius:4px;font-size:13px}.image-url-input[data-v-f3dab8d1]:focus,.qrcode-input[data-v-f3dab8d1]:focus{outline:none;border-color:#1890ff}.remove-btn[data-v-f3dab8d1]{padding:4px 12px;border:1px solid #ff4d4f;background:#fff;color:#ff4d4f;border-radius:4px;font-size:12px;cursor:pointer;transition:all .3s;align-self:flex-start}.remove-btn[data-v-f3dab8d1]:hover{background:#ff4d4f;color:#fff}.image-preview[data-v-f3dab8d1],.qrcode-preview[data-v-f3dab8d1]{margin-top:8px;padding:8px;border:1px solid #e0e0e0;border-radius:4px;background:#fafafa}.image-preview img[data-v-f3dab8d1],.qrcode-preview img[data-v-f3dab8d1]{max-width:100%;max-height:200px;display:block}.field-preview[data-v-f3dab8d1]{margin-top:8px;padding:6px 10px;background:#f0f7ff;border:1px solid #d6e4ff;border-radius:4px;font-size:12px;color:#1890ff}.form-row[data-v-f3dab8d1]{display:grid;grid-template-columns:1fr 1fr;gap:12px}.field-data-panel[data-v-33e53b63]{display:flex;flex-direction:column;height:100%;background:#fff;border-top:1px solid #e0e0e0}.panel-header[data-v-33e53b63]{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid #e0e0e0}.panel-header h3[data-v-33e53b63]{font-size:14px;font-weight:500;color:#333;margin:0}.reset-btn[data-v-33e53b63]{padding:4px 12px;border:1px solid #d9d9d9;background:#fff;border-radius:4px;font-size:12px;color:#666;cursor:pointer}.reset-btn[data-v-33e53b63]:hover{border-color:#1890ff;color:#1890ff}.panel-content[data-v-33e53b63]{flex:1;overflow-y:auto;padding:16px}.field-category[data-v-33e53b63]{margin-bottom:24px}.category-title[data-v-33e53b63]{font-size:13px;font-weight:500;color:#666;margin-bottom:12px;padding-bottom:8px;border-bottom:1px solid #f0f0f0}.fields-list[data-v-33e53b63]{display:flex;flex-direction:column;gap:16px}.field-item[data-v-33e53b63]{display:flex;flex-direction:column;gap:6px}.field-label[data-v-33e53b63]{font-size:13px;color:#333;font-weight:500}.field-input[data-v-33e53b63]{padding:6px 10px;border:1px solid #d9d9d9;border-radius:4px;font-size:13px}.field-input[data-v-33e53b63]:focus{outline:none;border-color:#1890ff}.field-hint[data-v-33e53b63]{font-size:11px;color:#999}.preview-overlay[data-v-2c0e9417]{position:fixed;top:0;left:0;right:0;bottom:0;background:#000000b3;display:flex;align-items:center;justify-content:center;z-index:2000}.preview-dialog[data-v-2c0e9417]{background:#fff;border-radius:8px;width:90%;max-width:1200px;max-height:90vh;display:flex;flex-direction:column;box-shadow:0 4px 20px #0000004d}.preview-header[data-v-2c0e9417]{display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid #e0e0e0}.preview-header h3[data-v-2c0e9417]{font-size:18px;font-weight:500;color:#333;margin:0}.close-btn[data-v-2c0e9417]{width:32px;height:32px;border:none;background:transparent;font-size:24px;color:#999;cursor:pointer;display:flex;align-items:center;justify-content:center;border-radius:4px;transition:all .3s}.close-btn[data-v-2c0e9417]:hover{background:#f5f5f5;color:#333}.preview-content[data-v-2c0e9417]{flex:1;overflow:auto;padding:40px;display:flex;justify-content:center;align-items:flex-start;background:#f5f5f5}.preview-canvas-wrapper[data-v-2c0e9417]{display:flex;justify-content:center;align-items:flex-start}.preview-canvas[data-v-2c0e9417]{background:#fff;box-shadow:0 4px 12px #00000026;position:relative;padding:60px 50px}.preview-element[data-v-2c0e9417]{position:absolute}.preview-text-content[data-v-2c0e9417]{width:100%;height:100%;word-wrap:break-word;white-space:pre-wrap}.preview-image-element[data-v-2c0e9417],.preview-qrcode-element[data-v-2c0e9417]{width:100%;height:100%;overflow:hidden}.preview-element-image[data-v-2c0e9417]{width:100%;height:100%;object-fit:contain;display:block}.preview-qrcode-text[data-v-2c0e9417]{width:100%;height:100%;display:flex;align-items:center;justify-content:center;background:#f5f5f5;border:1px solid #e0e0e0;font-size:12px;color:#666;word-break:break-all;padding:8px}.certificate-editor[data-v-b6b9b7fa]{display:flex;flex-direction:column;width:100vw;height:100vh;background:#f5f5f5}.toolbar[data-v-b6b9b7fa]{display:flex;align-items:center;gap:12px;padding:12px 20px;background:#fff;border-bottom:1px solid #e0e0e0;box-shadow:0 2px 4px #0000000d;flex-shrink:0}.toolbar-btn[data-v-b6b9b7fa]{display:flex;align-items:center;gap:6px;padding:8px 16px;border:1px solid #d9d9d9;background:#fff;color:#333;border-radius:4px;cursor:pointer;font-size:14px;transition:all .3s}.toolbar-btn[data-v-b6b9b7fa]:hover{border-color:#1890ff;color:#1890ff}.toolbar-btn.preview-btn[data-v-b6b9b7fa]:hover{background:#e6f7ff;border-color:#1890ff}.toolbar-btn.save-btn[data-v-b6b9b7fa]:hover{background:#f6ffed;border-color:#52c41a;color:#52c41a}.certificate-editor-content[data-v-b6b9b7fa]{display:flex;flex:1;min-height:0;overflow:hidden}.left-panel[data-v-b6b9b7fa]{width:280px;background:#fff;border-right:1px solid #e0e0e0;overflow-y:auto}.canvas-area[data-v-b6b9b7fa]{flex:1;overflow:auto;display:flex;justify-content:center;align-items:flex-start;padding:20px}.right-panel[data-v-b6b9b7fa]{width:350px;background:#fff;border-left:1px solid #e0e0e0;display:flex;flex-direction:column;overflow:hidden}.properties-section[data-v-b6b9b7fa]{flex:1;overflow-y:auto;border-bottom:1px solid #e0e0e0}.field-data-section[data-v-b6b9b7fa]{height:300px;overflow-y:auto}*{margin:0;padding:0;box-sizing:border-box}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;overflow:hidden}#app{width:100vw;height:100vh}
|