Yue 4 дней назад
Родитель
Сommit
eee7aedf97

+ 0 - 940
UI/VAP_V3.VUE/src/components/qrcode/API_REFERENCE.md

@@ -1,940 +0,0 @@
-# 二维码组件 API 参考
-
-> **版本**: v2.0.0 | **更新时间**: 2026-04-13
-
----
-
-## 📦 VbQrGen - 单个二维码组件
-
-### 基础用法
-
-```vue
-<template>
-  <VbQrGen 
-    ref="qrRef"
-    v-model="text"
-    title="我的二维码"
-    :size="200"
-    @success="handleSuccess"
-    @error="handleError"
-  />
-  <el-button @click="generate">生成</el-button>
-</template>
-
-<script setup lang="ts">
-import { ref } from 'vue'
-import VbQrGen from '@/components/qrcode/VbQrGen.vue'
-
-const qrRef = ref<InstanceType<typeof VbQrGen>>()
-const text = ref('https://example.com')
-
-function generate() {
-  qrRef.value?.generateQRCode()
-}
-
-function handleSuccess(dataURL) {
-  console.log('生成成功', dataURL)
-}
-
-function handleError(error) {
-  console.error('生成失败', error)
-}
-</script>
-```
-
----
-
-### Props
-
-| 属性                  | 类型                | 默认值            | 说明                         |
-| --------------------- | ------------------- | ----------------- | ---------------------------- |
-| **modelValue**        | `string`            | `""`              | 二维码文本内容(v-model)    |
-| **title**             | `string`            | `""`              | 标题文本                     |
-| **titlePosition**     | `"top" \| "bottom"` | `"top"`           | 标题位置                     |
-| **titleStyle**        | `object`            | `undefined`       | 标题自定义样式               |
-| **size**              | `number`            | `200`             | 二维码尺寸(像素)           |
-| **margin**            | `number`            | `12`              | 二维码边距(像素)           |
-| **colorDark**         | `string`            | `"#000000"`       | 深色块颜色                   |
-| **colorLight**        | `string`            | `"#ffffff"`       | 浅色块颜色                   |
-| **correctLevel**      | `0 \| 1 \| 2 \| 3`  | `1`               | 纠错等级 (L/M/Q/H)           |
-| **logoImage**         | `string`            | `undefined`       | Logo 图片地址                |
-| **logoScale**         | `number`            | `0.4`             | Logo 相对比例 (0-1)          |
-| **logoMargin**        | `number`            | `6`               | Logo 边距(像素)            |
-| **logoCornerRadius**  | `number`            | `8`               | Logo 圆角(像素)            |
-| **backgroundImage**   | `string`            | `undefined`       | 背景图片地址                 |
-| **backgroundDimming** | `string`            | `"rgba(0,0,0,0)"` | 背景遮罩颜色                 |
-| **gifBackgroundURL**  | `string`            | `undefined`       | GIF 背景图片                 |
-| **whiteMargin**       | `boolean`           | `true`            | 白色边距                     |
-| **autoColor**         | `boolean`           | `true`            | 自动计算浅色                 |
-| **dotScale**          | `number`            | `1`               | 定位点缩放 (0-1),越小越精致 |
-| **loadingText**       | `string`            | `"生成中..."`     | 加载提示文本                 |
-| **errorText**         | `string`            | `"生成失败"`      | 错误提示文本                 |
-
-**纠错等级说明:**
-- `0` = L (7% 容错)
-- `1` = M (15% 容错) ⭐ 推荐
-- `2` = Q (25% 容错)
-- `3` = H (30% 容错)
-
-**dotScale 效果:**
-- `0.2` - 超精致定位点
-- `0.35` - 精致定位点 ⭐ 推荐
-- `0.5` - 适中
-- `0.8` - 传统风格
-- `1.0` - 默认大小
-
----
-
-### Events
-
-| 事件名                | 参数                                            | 触发时机       |
-| --------------------- | ----------------------------------------------- | -------------- |
-| **update:modelValue** | `(value: string)`                               | v-model 更新时 |
-| **success**           | `(dataURL: ArrayBuffer \| string \| undefined)` | 二维码生成成功 |
-| **error**             | `(error: unknown)`                              | 二维码生成失败 |
-
----
-
-### Exposed Methods
-
-通过 `ref` 访问的方法:
-
-```typescript
-interface VbQrGenExposed {
-  // ✅ 核心方法 - 返回 Promise
-  generateQRCode(options?: QRCodeGenerateOptions): Promise<ArrayBuffer | string | undefined>
-  clearQRCode(): void
-  downloadQRCode(fileName?: string): Promise<void>
-  printQRCode(title?: string): Promise<void>
-  getQRCodeDataURL(): string | null
-  
-  // ✅ 状态获取
-  getQRCodeState(): {
-    url: string | ArrayBuffer | undefined
-    loading: boolean
-    error: unknown
-  }
-}
-```
-
-#### generateQRCode(options?)
-
-生成二维码。
-
-**参数:**
-```typescript
-interface QRCodeGenerateOptions {
-  text?: string              // 文本内容(可选,默认使用 modelValue)
-  size?: number              // 尺寸
-  margin?: number            // 边距
-  correctLevel?: 0 | 1 | 2 | 3  // 纠错等级
-  maskPattern?: number       // 掩码图案 (0-7)
-  version?: number           // 版本 (1-40)
-  colorDark?: string         // 深色
-  colorLight?: string        // 浅色
-  autoColor?: boolean        // 自动计算浅色
-  logoImage?: string         // Logo
-  logoScale?: number         // Logo 比例
-  logoMargin?: number        // Logo 边距
-  logoCornerRadius?: number  // Logo 圆角
-  backgroundImage?: string   // 背景图
-  backgroundDimming?: string // 背景遮罩
-  gifBackgroundURL?: string  // GIF 背景
-  whiteMargin?: boolean      // 白色边距
-  dotScale?: number          // 定位点缩放
-}
-```
-
-**返回值:** `Promise<dataURL>`
-
-**示例:**
-```typescript
-// 基础用法 - 使用 modelValue
-qrRef.value?.generateQRCode()
-
-// 自定义配置
-qrRef.value?.generateQRCode({
-  text: 'https://example.com',
-  size: 300,
-  colorDark: '#1677ff',
-  dotScale: 0.35
-})
-
-// 链式调用
-qrRef.value?.generateQRCode()
-  .then((dataURL) => console.log('成功', dataURL))
-  .catch((error) => console.error('失败', error))
-```
-
----
-
-#### clearQRCode()
-
-清除当前二维码。
-
-**示例:**
-```typescript
-qrRef.value?.clearQRCode()
-```
-
----
-
-#### downloadQRCode(fileName?)
-
-下载二维码为 PNG 图片。
-
-**参数:**
-- `fileName`: 文件名(不含扩展名),默认 `"qrcode"`
-
-**返回值:** `Promise<void>`
-
-**示例:**
-```typescript
-qrRef.value?.downloadQRCode('my-qrcode')
-  .then(() => console.log('下载成功'))
-  .catch((error) => console.error('下载失败', error))
-```
-
----
-
-#### printQRCode(title?)
-
-打印二维码(打开打印窗口)。
-
-**参数:**
-- `title`: 打印标题,默认使用组件的 `title` prop 或 `"二维码"`
-
-**返回值:** `Promise<void>`
-
-**示例:**
-```typescript
-qrRef.value?.printQRCode('产品二维码')
-  .then(() => console.log('打印窗口已打开'))
-  .catch((error) => console.error('打印失败', error))
-```
-
----
-
-#### getQRCodeDataURL()
-
-获取二维码的 Data URL。
-
-**返回值:** `string | null`
-
-**示例:**
-```typescript
-const dataURL = qrRef.value?.getQRCodeDataURL()
-if (dataURL) {
-  console.log('Data URL:', dataURL.substring(0, 50) + '...')
-}
-```
-
----
-
-#### getQRCodeState()
-
-获取二维码当前状态。
-
-**返回值:**
-```typescript
-{
-  url: string | ArrayBuffer | undefined  // 二维码数据
-  loading: boolean                        // 是否正在生成
-  error: unknown                          // 错误信息
-}
-```
-
-**示例:**
-```typescript
-const state = qrRef.value?.getQRCodeState()
-console.log('加载状态:', state.loading)
-console.log('错误信息:', state.error)
-```
-
----
-
-### Slots
-
-| 插槽名      | 作用域参数            | 说明             |
-| ----------- | --------------------- | ---------------- |
-| **title**   | -                     | 自定义标题       |
-| **qrcode**  | `{ qrcodeURL, size }` | 自定义二维码显示 |
-| **loading** | -                     | 自定义加载状态   |
-| **error**   | `{ error }`           | 自定义错误状态   |
-
-**示例:**
-```vue
-<VbQrGen v-model="text">
-  <!-- 自定义标题 -->
-  <template #title>
-    <h2 style="color: blue">自定义标题</h2>
-  </template>
-  
-  <!-- 自定义二维码显示 -->
-  <template #qrcode="{ qrcodeURL, size }">
-    <img :src="qrcodeURL" :style="{ width: size + 'px' }" />
-  </template>
-  
-  <!-- 自定义加载状态 -->
-  <template #loading>
-    <div class="custom-loading">加载中...</div>
-  </template>
-  
-  <!-- 自定义错误状态 -->
-  <template #error="{ error }">
-    <div class="custom-error">生成失败: {{ error }}</div>
-  </template>
-</VbQrGen>
-```
-
----
-
-## 📦 VbQrMulti - 批量二维码组件
-
-### 基础用法
-
-```vue
-<template>
-  <VbQrMulti
-    ref="multiRef"
-    v-model:items="qrItems"
-    :columns="4"
-    @generate-all-complete="handleComplete"
-  />
-  <el-button @click="generateAll">批量生成</el-button>
-</template>
-
-<script setup lang="ts">
-import { ref } from 'vue'
-import VbQrMulti from '@/components/qrcode/VbQrMulti.vue'
-import type { QRCodeItem } from '@/components/qrcode/VbQrMulti.vue'
-
-const multiRef = ref<InstanceType<typeof VbQrMulti>>()
-const qrItems = ref<QRCodeItem[]>([
-  { id: '1', text: 'https://a.com', title: '产品 A' },
-  { id: '2', text: 'https://b.com', title: '产品 B' }
-])
-
-function generateAll() {
-  multiRef.value?.generateAll()
-    .then((result) => {
-      console.log(`成功: ${result.success}, 失败: ${result.fail}`)
-    })
-}
-
-function handleComplete(result: { success: number; fail: number }) {
-  console.log('批量生成完成', result)
-}
-</script>
-```
-
----
-
-### QRCodeItem 类型
-
-```typescript
-interface QRCodeItem {
-  id: string                    // 唯一标识
-  text: string                  // 二维码文本
-  title?: string                // 标题(可选)
-  options?: QRCodeGenerateOptions  // 自定义配置(可选)
-}
-```
-
----
-
-### Props
-
-| 属性                  | 类型                 | 默认值      | 说明                  |
-| --------------------- | -------------------- | ----------- | --------------------- |
-| **items**             | `QRCodeItem[]`       | `[]`        | 二维码列表(v-model) |
-| **size**              | `number`             | `150`       | 默认尺寸              |
-| **margin**            | `number`             | `10`        | 默认边距              |
-| **dotScale**          | `number`             | `0.35`      | 默认定位点缩放        |
-| **colorDark**         | `string`             | `undefined` | 默认深色              |
-| **colorLight**        | `string`             | `undefined` | 默认浅色              |
-| **correctLevel**      | `0 \| 1 \| 2 \| 3`   | `undefined` | 默认纠错等级          |
-| **logoImage**         | `string`             | `undefined` | 默认 Logo             |
-| **logoScale**         | `number`             | `undefined` | 默认 Logo 比例        |
-| **logoMargin**        | `number`             | `undefined` | 默认 Logo 边距        |
-| **logoCornerRadius**  | `number`             | `undefined` | 默认 Logo 圆角        |
-| **backgroundImage**   | `string`             | `undefined` | 默认背景图            |
-| **backgroundDimming** | `string`             | `undefined` | 默认背景遮罩          |
-| **gifBackgroundURL**  | `string`             | `undefined` | 默认 GIF 背景         |
-| **whiteMargin**       | `boolean`            | `undefined` | 默认白色边距          |
-| **autoColor**         | `boolean`            | `undefined` | 默认自动计算浅色      |
-| **maskPattern**       | `number`             | `undefined` | 默认掩码图案          |
-| **version**           | `number`             | `undefined` | 默认版本              |
-| **layout**            | `"grid" \| "list"`   | `"grid"`    | 布局方式              |
-| **columns**           | `number`             | `5`         | 网格列数              |
-| **title**             | `string \| function` | `undefined` | 标题或标题生成函数    |
-| **showItemActions**   | `boolean`            | `true`      | 显示单项操作按钮      |
-| **concurrency**       | `number`             | `50`        | 批量生成并发数        |
-| **autoGenerate**      | `boolean`            | `true`      | 自动生成开关          |
-
-**title 函数签名:**
-```typescript
-title?: (item: QRCodeItem, index: number) => string
-```
-
-**配置优先级:**
-1. `item.options` 中的配置(最高优先级)
-2. Props 中的默认配置
-3. 组件内部默认值
-
----
-
-### Events
-
-| 事件名                    | 参数                    | 触发时机       |
-| ------------------------- | ----------------------- | -------------- |
-| **update:items**          | `(items: QRCodeItem[])` | 列表变化时     |
-| **add**                   | `(item: QRCodeItem)`    | 添加二维码     |
-| **remove**                | `(id: string)`          | 删除二维码     |
-| **generate**              | `(item: QRCodeItem)`    | 生成单个二维码 |
-| **clear**                 | `(id: string)`          | 清除二维码     |
-| **generate-all-complete** | `({ success, fail })`   | ✅ 批量生成完成 |
-| **download-all-complete** | `(count: number)`       | ✅ 批量下载完成 |
-| **print-all-complete**    | `(count: number)`       | ✅ 批量打印完成 |
-
----
-
-### Exposed Methods
-
-```typescript
-interface VbQrMultiExposed {
-  // 数据
-  qrList: Ref<QRCodeItem[]>
-  
-  // 管理方法
-  addQRCode(item?: Partial<QRCodeItem>): void
-  removeQRCode(id: string): void
-  clearAll(): void
-  
-  // 单个操作
-  generateOne(item: QRCodeItem): void
-  downloadOne(item: QRCodeItem, index: number): void
-  printOne(item: QRCodeItem, index: number): void
-  
-  // 批量操作 - 返回 Promise
-  generateAll(): Promise<{ success: number; fail: number }>
-  downloadAll(): Promise<void>
-  printAll(): Promise<void>
-  
-  // 获取子组件引用
-  getQRRef(id: string): InstanceType<typeof VbQrGen> | undefined
-}
-```
-
-#### addQRCode(item?)
-
-添加新二维码。
-
-**参数:**
-```typescript
-{
-  id?: string      // 可选,自动生成
-  text?: string    // 文本内容
-  title?: string   // 标题
-  options?: QRCodeGenerateOptions  // 自定义配置
-}
-```
-
-**示例:**
-```typescript
-multiRef.value?.addQRCode({
-  text: 'https://example.com',
-  title: '新产品',
-  options: {
-    size: 200,
-    colorDark: '#1677ff'
-  }
-})
-```
-
----
-
-#### removeQRCode(id)
-
-删除指定二维码。
-
-**参数:**
-- `id`: 二维码 ID
-
-**示例:**
-```typescript
-multiRef.value?.removeQRCode('qr_123')
-```
-
----
-
-#### generateOne(item)
-
-生成单个二维码。
-
-**参数:**
-- `item`: QRCodeItem 对象
-
-**示例:**
-```typescript
-const item = qrItems.value[0]
-multiRef.value?.generateOne(item)
-```
-
----
-
-#### downloadOne(item, index)
-
-下载单个二维码。
-
-**参数:**
-- `item`: QRCodeItem 对象
-- `index`: 索引(用于生成文件名)
-
-**示例:**
-```typescript
-multiRef.value?.downloadOne(qrItems.value[0], 0)
-```
-
----
-
-#### printOne(item, index)
-
-打印单个二维码。
-
-**参数:**
-- `item`: QRCodeItem 对象
-- `index`: 索引(用于生成标题)
-
-**示例:**
-```typescript
-multiRef.value?.printOne(qrItems.value[0], 0)
-```
-
----
-
-#### generateAll()
-
-批量生成所有二维码(并发处理)。
-
-**返回值:** `Promise<{ success: number; fail: number }>`
-
-**示例:**
-```typescript
-multiRef.value?.generateAll()
-  .then((result) => {
-    console.log(`成功: ${result.success}, 失败: ${result.fail}`)
-  })
-  .catch((error) => {
-    console.error('批量生成出错', error)
-  })
-```
-
-**性能参考(100个二维码):**
-- 并发 50:约 5-10 秒
-- 并发 100:约 3-5 秒
-- 并发 200:约 2-3 秒
-
----
-
-#### downloadAll()
-
-批量下载所有二维码(合并为一张图片)。
-
-**返回值:** `Promise<void>`
-
-**示例:**
-```typescript
-multiRef.value?.downloadAll()
-  .then(() => console.log('批量下载完成'))
-  .catch((error) => console.error('下载失败', error))
-```
-
----
-
-#### printAll()
-
-批量打印所有二维码(合并在一个页面)。
-
-**返回值:** `Promise<void>`
-
-**示例:**
-```typescript
-multiRef.value?.printAll()
-  .then(() => console.log('批量打印完成'))
-  .catch((error) => console.error('打印失败', error))
-```
-
----
-
-#### clearAll()
-
-清空所有二维码(不清除列表,只清除生成的图片)。
-
-**示例:**
-```typescript
-multiRef.value?.clearAll()
-```
-
----
-
-#### getQRRef(id)
-
-获取指定二维码的子组件引用。
-
-**参数:**
-- `id`: 二维码 ID
-
-**返回值:** `InstanceType<typeof VbQrGen> | undefined`
-
-**示例:**
-```typescript
-const qrRef = multiRef.value?.getQRRef('qr_123')
-if (qrRef) {
-  qrRef.downloadQRCode('custom-name')
-}
-```
-
----
-
-### Slots
-
-| 插槽名    | 作用域参数                                                  | 说明             |
-| --------- | ----------------------------------------------------------- | ---------------- |
-| **item**  | `{ item, index, title, generate, download, print, remove }` | 自定义二维码卡片 |
-| **empty** | -                                                           | 自定义空状态     |
-
-**item 插槽参数详解:**
-```typescript
-{
-  item: QRCodeItem          // 当前项数据
-  index: number             // 索引
-  title: string             // 计算后的标题
-  generate: () => void      // 生成函数
-  download: () => void      // 下载函数
-  print: () => void         // 打印函数
-  remove: () => void        // 删除函数
-}
-```
-
-**示例:**
-```vue
-<VbQrMulti v-model:items="items">
-  <template #item="{ item, index, title, generate, download, print, remove }">
-    <div class="custom-card">
-      <div class="card-header">
-        <h3>{{ title }}</h3>
-        <el-button size="small" @click="remove">删除</el-button>
-      </div>
-      
-      <VbQrGen
-        :ref="(el) => setQRRef(item.id, el)"
-        v-model="item.text"
-        :title="title"
-      />
-      
-      <div class="card-footer">
-        <el-button size="small" @click="generate">生成</el-button>
-        <el-button size="small" @click="download">下载</el-button>
-        <el-button size="small" @click="print">打印</el-button>
-      </div>
-    </div>
-  </template>
-  
-  <template #empty>
-    <div class="empty-state">
-      <p>暂无二维码</p>
-      <el-button @click="addFirst">添加第一个</el-button>
-    </div>
-  </template>
-</VbQrMulti>
-```
-
----
-
-## 🎯 常见场景
-
-### 1. 模态框中使用
-
-```vue
-<template>
-  <el-dialog v-model="visible" title="二维码">
-    <VbQrGen
-      ref="qrRef"
-      :model-value="currentURL"
-      :size="300"
-    />
-    <template #footer>
-      <el-button @click="download">下载</el-button>
-      <el-button @click="print">打印</el-button>
-    </template>
-  </el-dialog>
-</template>
-
-<script setup lang="ts">
-import { ref, watch } from 'vue'
-
-const visible = ref(false)
-const currentURL = ref('')
-const qrRef = ref()
-
-// 对话框打开时生成
-watch(visible, (val) => {
-  if (val && currentURL.value) {
-    setTimeout(() => {
-      qrRef.value?.generateQRCode()
-    }, 100)
-  }
-})
-
-function download() {
-  qrRef.value?.downloadQRCode('qrcode')
-}
-
-function print() {
-  qrRef.value?.printQRCode('二维码')
-}
-</script>
-```
-
----
-
-### 2. 带 Logo 的二维码
-
-```vue
-<VbQrGen
-  v-model="text"
-  logo-image="/logo.png"
-  :logo-scale="0.3"
-  :logo-margin="8"
-  :logo-corner-radius="10"
-/>
-```
-
----
-
-### 3. 动态标题
-
-```vue
-<VbQrMulti
-  v-model:items="items"
-  :title="(item, index) => {
-    // 优先使用 item.title
-    if (item.title) return item.title
-    
-    // 否则从 URL 提取域名
-    try {
-      const url = new URL(item.text)
-      return url.hostname
-    } catch {
-      return `二维码 ${index + 1}`
-    }
-  }}"
-/>
-```
-
----
-
-### 4. 大量数据优化
-
-```vue
-<template>
-  <!-- 关闭自动生成,手动控制 -->
-  <VbQrMulti
-    ref="multiRef"
-    v-model:items="items"
-    :auto-generate="false"
-    :concurrency="30"
-  />
-  <el-button @click="manualGenerate">手动生成</el-button>
-</template>
-
-<script setup lang="ts">
-import { ref, onMounted } from 'vue'
-
-const multiRef = ref()
-const items = ref([...])  // 大量数据
-
-onMounted(() => {
-  // 等待 DOM 渲染后手动生成
-  setTimeout(() => {
-    multiRef.value?.generateAll()
-  }, 100)
-})
-
-function manualGenerate() {
-  multiRef.value?.generateAll()
-    .then((result) => {
-      console.log(`生成完成: ${result.success}/${result.fail}`)
-    })
-}
-</script>
-```
-
----
-
-### 5. 导出为 PDF
-
-```vue
-<script setup lang="ts">
-import jsPDF from 'jspdf'
-
-async function exportToPDF() {
-  // 先生成所有二维码
-  await multiRef.value?.generateAll()
-  
-  const pdf = new jsPDF()
-  let y = 20
-  
-  // 遍历所有二维码
-  for (let i = 0; i < items.value.length; i++) {
-    const qrRef = multiRef.value?.getQRRef(items.value[i].id)
-    const dataURL = qrRef?.getQRCodeDataURL()
-    
-    if (dataURL) {
-      pdf.addImage(dataURL, 'PNG', 20, y, 50, 50)
-      pdf.text(items.value[i].title || `二维码 ${i + 1}`, 80, y + 25)
-      y += 60
-      
-      if (y > 250) {
-        pdf.addPage()
-        y = 20
-      }
-    }
-  }
-  
-  pdf.save('qrcodes.pdf')
-}
-</script>
-```
-
----
-
-## ⚠️ 注意事项
-
-### 异步处理
-
-✅ **推荐** - 使用 Promise 链:
-```typescript
-qrRef.value?.generateQRCode()
-  .then((dataURL) => console.log('成功', dataURL))
-  .catch((error) => console.error('失败', error))
-```
-
-⚠️ **可用但不推荐** - async/await:
-```typescript
-async function handleGenerate() {
-  try {
-    const dataURL = await qrRef.value?.generateQRCode()
-    console.log('成功', dataURL)
-  } catch (error) {
-    console.error('失败', error)
-  }
-}
-```
-
----
-
-### 状态访问
-
-❌ **错误** - 直接访问内部状态:
-```typescript
-const url = qrRef.value?.qrcodeURL  // ❌ 不存在
-const loading = qrRef.value?.isLoading  // ❌ 不存在
-```
-
-✅ **正确** - 使用 getter 方法:
-```typescript
-const state = qrRef.value?.getQRCodeState()
-const url = state?.url
-const loading = state?.loading
-```
-
----
-
-### 性能建议
-
-1. **大量二维码时关闭自动生成**
-   ```vue
-   <VbQrMulti :auto-generate="false" />
-   ```
-
-2. **调整并发数**
-   ```vue
-   <!-- 根据机器性能调整 -->
-   <VbQrMulti :concurrency="30" />
-   ```
-
-3. **降低二维码尺寸**
-   ```vue
-   <VbQrMulti :size="100" />
-   ```
-
-4. **使用列表布局**
-   ```vue
-   <VbQrMulti layout="list" />
-   ```
-
----
-
-## 🔧 故障排查
-
-### Q1: 生成失败
-
-**可能原因:**
-- text 为空
-- 文本过长超出容量
-- 网络问题(在线资源)
-
-**解决:**
-```typescript
-qrRef.value?.generateQRCode()
-  .catch((error) => {
-    console.error('错误:', error)
-    // 检查 text
-    // 尝试降低 correctLevel
-    // 移除 logoImage 测试
-  })
-```
-
----
-
-### Q2: 批量生成部分失败
-
-**可能原因:**
-- 并发数过高
-- 某些文本无效
-
-**解决:**
-```vue
-<VbQrMulti 
-  :concurrency="20"
-  @generate-all-complete="handleComplete"
-/>
-
-<script setup>
-function handleComplete(result) {
-  console.log(`成功: ${result.success}, 失败: ${result.fail}`)
-}
-</script>
-```
-
----
-
-### Q3: 打印窗口被拦截
-
-**原因:** 浏览器弹窗拦截
-
-**解决:** 确保用户主动触发(点击按钮),避免在异步回调中调用
-
----
-
-## 📚 相关文档
-
-- [README.md](./README.md) - 快速开始指南
-- [OPTIMIZATION_SUMMARY.md](./OPTIMIZATION_SUMMARY.md) - 优化总结
-- [MIGRATION_GUIDE.md](./MIGRATION_GUIDE.md) - 迁移指南
-
----
-
-**最后更新**: 2026-04-13

+ 0 - 246
UI/VAP_V3.VUE/src/components/qrcode/README.md

@@ -1,246 +0,0 @@
-# 二维码组件使用文档
-
-## 快速开始
-
-### VbQrGen - 单个二维码组件
-
-```vue
-<template>
-  <VbQrGen 
-    ref="qrRef"
-    v-model="text"
-    title="我的二维码"
-    :size="200"
-  />
-  <el-button @click="generate">生成</el-button>
-  <el-button @click="download">下载</el-button>
-</template>
-
-<script setup lang="ts">
-import { ref } from 'vue'
-import VbQrGen from '@/components/qrcode/VbQrGen.vue'
-
-const qrRef = ref<InstanceType<typeof VbQrGen>>()
-const text = ref('https://example.com')
-
-function generate() {
-  qrRef.value?.generateQRCode()
-}
-
-function download() {
-  qrRef.value?.downloadQRCode('my-qr')
-}
-</script>
-```
-
----
-
-### VbQrMulti - 批量二维码组件
-
-```vue
-<template>
-  <VbQrMulti
-    ref="multiRef"
-    v-model:items="qrItems"
-    :columns="4"
-  />
-  <el-button @click="generateAll">批量生成</el-button>
-  <el-button @click="downloadAll">批量下载</el-button>
-</template>
-
-<script setup lang="ts">
-import { ref } from 'vue'
-import VbQrMulti from '@/components/qrcode/VbQrMulti.vue'
-import type { QRCodeItem } from '@/components/qrcode/VbQrMulti.vue'
-
-const multiRef = ref<InstanceType<typeof VbQrMulti>>()
-const qrItems = ref<QRCodeItem[]>([
-  { id: '1', text: 'https://a.com', title: '产品 A' },
-  { id: '2', text: 'https://b.com', title: '产品 B' }
-])
-
-function generateAll() {
-  multiRef.value?.generateAll()
-    .then((result) => {
-      console.log(`成功: ${result.success}, 失败: ${result.fail}`)
-    })
-}
-
-function downloadAll() {
-  multiRef.value?.downloadAll()
-}
-</script>
-```
-
----
-
-## 核心 API
-
-### VbQrGen 方法
-
-| 方法                        | 说明          | 返回值                    |
-| --------------------------- | ------------- | ------------------------- |
-| `generateQRCode(options?)`  | 生成二维码    | `Promise<dataURL>`        |
-| `clearQRCode()`             | 清除二维码    | `void`                    |
-| `downloadQRCode(fileName?)` | 下载二维码    | `Promise<void>`           |
-| `printQRCode(title?)`       | 打印二维码    | `Promise<void>`           |
-| `getQRCodeDataURL()`        | 获取 Data URL | `string \| null`          |
-| `getQRCodeState()`          | 获取状态      | `{ url, loading, error }` |
-
-### VbQrMulti 方法
-
-| 方法                       | 说明       | 返回值                     |
-| -------------------------- | ---------- | -------------------------- |
-| `addQRCode(item?)`         | 添加二维码 | `void`                     |
-| `removeQRCode(id)`         | 删除二维码 | `void`                     |
-| `generateOne(item)`        | 生成单个   | `void`                     |
-| `downloadOne(item, index)` | 下载单个   | `void`                     |
-| `printOne(item, index)`    | 打印单个   | `void`                     |
-| `generateAll()`            | 批量生成   | `Promise<{success, fail}>` |
-| `downloadAll()`            | 批量下载   | `Promise<void>`            |
-| `printAll()`               | 批量打印   | `Promise<void>`            |
-| `clearAll()`               | 清空全部   | `void`                     |
-
----
-
-## 常用配置
-
-### VbQrGen Props
-
-```vue
-<VbQrGen
-  v-model="text"
-  :size="200"              <!-- 尺寸 -->
-  :margin="10"             <!-- 边距 -->
-  :dot-scale="0.35"        <!-- 定位点大小 (0-1) -->
-  color-dark="#000000"     <!-- 深色块颜色 -->
-  color-light="#ffffff"    <!-- 浅色块颜色 -->
-  :correct-level="1"       <!-- 纠错等级 (0-3) -->
-  logo-image="/logo.png"   <!-- Logo 图片 -->
-  :logo-scale="0.3"        <!-- Logo 比例 -->
-/>
-```
-
-### VbQrMulti Props
-
-```vue
-<VbQrMulti
-  v-model:items="items"
-  :layout="'grid'"         <!-- 布局: grid/list -->
-  :columns="5"             <!-- 网格列数 -->
-  :size="150"              <!-- 默认尺寸 -->
-  :dot-scale="0.35"        <!-- 默认定位点大小 -->
-  :concurrency="50"        <!-- 并发数 -->
-  :auto-generate="true"    <!-- 自动生成 -->
-  :show-item-actions="true"<!-- 显示操作按钮 -->
-/>
-```
-
----
-
-## 事件监听
-
-### VbQrGen Events
-
-```vue
-<VbQrGen
-  @success="(dataURL) => console.log('生成成功', dataURL)"
-  @error="(error) => console.error('生成失败', error)"
-/>
-```
-
-### VbQrMulti Events
-
-```vue
-<VbQrMulti
-  @add="(item) => console.log('添加', item)"
-  @remove="(id) => console.log('删除', id)"
-  @generate-all-complete="(result) => {
-    console.log(`成功: ${result.success}, 失败: ${result.fail}`)
-  }"
-  @download-all-complete="(count) => console.log(`下载 ${count} 个`)"
-  @print-all-complete="(count) => console.log(`打印 ${count} 个`)"
-/>
-```
-
----
-
-## 高级用法
-
-### 自定义配置生成
-
-```typescript
-// VbQrGen - 带自定义配置
-qrRef.value?.generateQRCode({
-  text: 'https://example.com',
-  size: 300,
-  colorDark: '#1677ff',
-  dotScale: 0.5
-})
-```
-
-### 动态标题
-
-```vue
-<!-- VbQrMulti - 动态生成标题 -->
-<VbQrMulti
-  :title="(item, index) => item.title || `二维码 ${index + 1}`"
-/>
-```
-
-### 自定义卡片
-
-```vue
-<VbQrMulti v-model:items="items">
-  <template #item="{ item, title, generate, download, print, remove }">
-    <div class="custom-card">
-      <h3>{{ title }}</h3>
-      <VbQrGen :ref="..." v-model="item.text" />
-      <el-button @click="generate">生成</el-button>
-      <el-button @click="download">下载</el-button>
-      <el-button @click="remove">删除</el-button>
-    </div>
-  </template>
-</VbQrMulti>
-```
-
-### 关闭自动生成
-
-```vue
-<!-- 大量数据时建议关闭,手动控制 -->
-<VbQrMulti
-  :auto-generate="false"
-  ref="multiRef"
-/>
-
-<script setup>
-onMounted(() => {
-  setTimeout(() => {
-    multiRef.value?.generateAll()
-  }, 100)
-})
-</script>
-```
-
----
-
-## 注意事项
-
-1. **异步处理**:所有方法返回 Promise,使用 `.then().catch()` 或 `await`
-2. **状态访问**:使用 `getQRCodeState()` 而非直接访问内部状态
-3. **并发控制**:VbQrMulti 默认并发 50,可根据性能调整
-4. **自动生成**:默认开启,大量数据时建议关闭
-5. **错误处理**:批量操作中单个失败不影响其他继续执行
-
----
-
-## 完整示例
-
-查看测试文件获取更多示例:
-- [VbQrGen 测试](../../views/test/index.vue)
-- [VbQrMulti 测试](../../views/test/index1.vue)
-
-详细文档:
-- [API 参考](./API_REFERENCE.md)
-- [优化总结](./OPTIMIZATION_SUMMARY.md)
-- [迁移指南](./MIGRATION_GUIDE.md)

+ 208 - 13
UI/VAP_V3.VUE/src/components/qrcode/VbQrGen.vue

@@ -26,7 +26,7 @@ export interface QRCodeGenerateOptions {
 
 const props = withDefaults(
 	defineProps<{
-		modelValue?: string // 二维码文本内容
+		text?: string // 二维码文本内容
 		title?: string // 标题文本
 		titlePosition?: "top" | "bottom" // 标题位置:上方或下方
 		titleStyle?: object // 标题自定义样式
@@ -46,10 +46,11 @@ const props = withDefaults(
 		autoColor?: boolean // 是否自动计算浅色值
 		dotScale?: number // 点的缩放比例 (0-1),默认 1,越小定位点越小
 		loadingText?: string // 加载提示文本
-		errorText?: string // 错误提示文本
+		errorText?: string // 错误提示文本,
+		autoGenerate?: boolean // 是否自动生成
 	}>(),
 	{
-		modelValue: "",
+		text: "",
 		title: "",
 		titlePosition: "top",
 		titleStyle: undefined,
@@ -69,12 +70,12 @@ const props = withDefaults(
 		autoColor: true,
 		dotScale: 1, // 默认减小定位点大小
 		loadingText: "生成中...",
-		errorText: "生成失败"
+		errorText: "生成失败",
+		autoGenerate: true
 	}
 )
 
 const emits = defineEmits<{
-	(e: "update:modelValue", v: string): void
 	(e: "success", dataURL: ArrayBuffer | string | undefined): void
 	(e: "error", error: unknown): void
 }>()
@@ -90,8 +91,8 @@ const { qrcodeURL, isLoading, error: qrError, generate, clear } = useQRCode()
 function generateQRCode(
 	options?: QRCodeGenerateOptions
 ): Promise<ArrayBuffer | string | undefined> {
-	// 如果传入了 text 则使用传入的,否则使用 modelValue
-	const textToUse = options?.text !== undefined ? options.text : props.modelValue
+	// 如果传入了 text 则使用传入的,否则使用 props.text
+	const textToUse = options?.text !== undefined ? options.text : props.text
 
 	if (!textToUse) {
 		console.warn("VbQrGen:未提供二维码文本内容")
@@ -141,33 +142,39 @@ function clearQRCode() {
 }
 
 /**
- * 下载二维码图片
+ * 下载二维码图片(包含标题)
  * @param fileName 文件名(不含扩展名),默认为 'qrcode'
  * @returns Promise
  */
 function downloadQRCode(fileName: string = "qrcode"): Promise<void> {
 	return new Promise((resolve, reject) => {
 		try {
-			const dataURL = qrcodeURL.value
-			if (!dataURL || typeof dataURL !== "string") {
+			const result = getQRCodeDataURLWithTitle()
+
+			if (!result) {
 				const error = new Error("VbQrGen:二维码数据未生成")
 				console.error(error.message)
 				reject(error)
 				return
 			}
 
+			// 设置 loading 状态
+			isLoading.value = true
+
 			// 创建下载链接
 			const link = document.createElement("a")
-			link.href = dataURL
+			link.href = result.dataURL
 			link.download = `${fileName}.png`
 			document.body.appendChild(link)
 			link.click()
 			document.body.removeChild(link)
 
-			console.log("VbQrGen:二维码下载成功")
+			console.log("VbQrGen:二维码下载成功(含标题)")
+			isLoading.value = false
 			resolve()
 		} catch (error) {
 			console.error("VbQrGen:二维码下载失败", error)
+			isLoading.value = false
 			reject(error)
 		}
 	})
@@ -282,6 +289,183 @@ function printQRCode(title?: string): Promise<void> {
 	})
 }
 
+/**
+ * 将文本按宽度换行
+ * @param text 文本内容
+ * @param maxWidth 最大宽度
+ * @param fontSize 字体大小
+ * @param ctx Canvas 上下文
+ * @returns 换行后的文本数组
+ */
+function getWrappedLines(
+	text: string,
+	maxWidth: number,
+	fontSize: number,
+	ctx: CanvasRenderingContext2D
+): string[] {
+	ctx.font = `bold ${fontSize}px Arial`
+	const chars = text.split("")
+	const lines: string[] = []
+	let currentLine = ""
+
+	for (let i = 0; i < chars.length; i++) {
+		const testLine = currentLine + chars[i]
+		const metrics = ctx.measureText(testLine)
+		const testWidth = metrics.width
+
+		if (testWidth > maxWidth && currentLine !== "") {
+			lines.push(currentLine)
+			currentLine = chars[i]
+		} else {
+			currentLine = testLine
+		}
+	}
+
+	if (currentLine !== "") {
+		lines.push(currentLine)
+	}
+
+	return lines.length > 0 ? lines : [text]
+}
+
+/**
+ * 计算标题所需的高度(不绘制,仅计算)
+ * @param title 标题文本
+ * @param maxWidth 最大宽度
+ * @param fontSize 字体大小
+ * @param lineHeight 行高
+ * @returns 标题实际高度
+ */
+function calculateTitleHeight(
+	title: string,
+	maxWidth: number,
+	fontSize: number = 16,
+	lineHeight: number = 20
+): number {
+	// 创建一个临时的 canvas 来测量文本
+	const tempCanvas = document.createElement("canvas")
+	const tempCtx = tempCanvas.getContext("2d")
+	if (!tempCtx) {
+		return lineHeight // 默认返回一行高度
+	}
+
+	const lines = getWrappedLines(title, maxWidth, fontSize, tempCtx)
+	return lines.length * lineHeight + 10 // 额外留白
+}
+
+/**
+ * 在 Canvas 上绘制标题(支持换行)
+ * @param ctx Canvas 上下文
+ * @param title 标题文本
+ * @param x X 坐标(中心点)
+ * @param y Y 坐标(起始位置)
+ * @param maxWidth 最大宽度
+ * @param fontSize 字体大小
+ * @param lineHeight 行高
+ * @returns 实际使用的标题高度
+ */
+function drawTitleOnCanvas(
+	ctx: CanvasRenderingContext2D,
+	title: string,
+	x: number,
+	y: number,
+	maxWidth: number,
+	fontSize: number = 14,
+	lineHeight: number = 18
+): number {
+	const lines = getWrappedLines(title, maxWidth, fontSize, ctx)
+	ctx.fillStyle = "#333333"
+	ctx.font = `bold ${fontSize}px Arial`
+	ctx.textAlign = "center"
+	ctx.textBaseline = "top"
+
+	// 逐行绘制标题
+	lines.forEach((line, lineIndex) => {
+		const lineY = y + lineIndex * lineHeight + 5
+		ctx.fillText(line, x, lineY)
+	})
+
+	// 返回实际占用的标题高度
+	return lines.length * lineHeight + 10
+}
+
+/**
+ * 获取包含标题的二维码 Data URL(用于批量下载)
+ * @param title 标题文本
+ * @param fontSize 字体大小
+ * @param lineHeight 行高
+ * @returns 包含 dataURL 和 height 的对象,或 null
+ */
+function getQRCodeDataURLWithTitle(
+	title?: string,
+	fontSize: number = 16,
+	lineHeight: number = 20
+): { dataURL: string; height: number } | null {
+	try {
+		const dataURL = qrcodeURL.value
+		if (!dataURL || typeof dataURL !== "string") {
+			console.warn("VbQrGen:二维码数据未生成")
+			return null
+		}
+
+		const titleText = title || props.title || ""
+		const hasTitle = titleText && titleText.trim() !== ""
+
+		if (!hasTitle) {
+			// 无标题时直接返回原图
+			return {
+				dataURL,
+				height: props.size
+			}
+		}
+
+		// 有标题时,创建 canvas 合并
+		const canvas = document.createElement("canvas")
+		const ctx = canvas.getContext("2d")
+		if (!ctx) {
+			throw new Error("无法创建 canvas 上下文")
+		}
+
+		// 加载二维码图片
+		const img = new Image()
+		img.src = dataURL
+
+		// 计算标题高度(不绘制,仅计算)
+		const padding = 20
+		const maxWidth = props.size
+		const titleHeight = calculateTitleHeight(titleText, maxWidth, fontSize, lineHeight)
+
+		canvas.width = props.size + padding * 2
+		canvas.height = props.size + titleHeight + padding * 2
+
+		// 填充白色背景
+		ctx.fillStyle = "#ffffff"
+		ctx.fillRect(0, 0, canvas.width, canvas.height)
+
+		// 绘制标题
+		drawTitleOnCanvas(
+			ctx,
+			titleText,
+			maxWidth / 2 + padding,
+			padding,
+			maxWidth,
+			fontSize,
+			lineHeight
+		)
+
+		// 绘制二维码
+		ctx.drawImage(img, padding, titleHeight + padding, props.size, props.size)
+
+		return {
+			dataURL: canvas.toDataURL("image/png"),
+			height: canvas.height
+		}
+	} catch (error) {
+		console.error("VbQrGen:获取带标题的 Data URL 失败", error)
+		return null
+	}
+}
+
 /**
  * 获取二维码 Data URL
  * @returns Data URL 字符串或 null
@@ -319,6 +503,17 @@ const titleStyle = computed(() => {
 // 内容容器引用(用于打印时获取自定义插槽内容)
 const contentRef = ref<HTMLElement | null>(null)
 
+watch(
+	() => props.text,
+	(newValue) => {
+		if (!props.autoGenerate) return
+		if (newValue) {
+			generateQRCode()
+		}
+	},
+	{ immediate: true }
+)
+
 // 暴露方法给父组件
 defineExpose({
 	// 核心方法
@@ -327,7 +522,7 @@ defineExpose({
 	downloadQRCode,
 	printQRCode,
 	getQRCodeDataURL,
-	// 状态获取方法(不直接暴露内部状态)
+	getQRCodeDataURLWithTitle,
 	getQRCodeState() {
 		return {
 			url: qrcodeURL.value,

+ 68 - 87
UI/VAP_V3.VUE/src/components/qrcode/VbQrMulti.vue

@@ -36,6 +36,7 @@ const props = withDefaults(
 		showItemActions?: boolean // 是否显示每个二维码卡片的操作按钮
 		concurrency?: number // 批量生成并发数,默认20
 		autoGenerate?: boolean // 是否自动生成:true=页面初始化时自动批量生成,添加新项后也自动生成
+		alert?: boolean // 是否显示提示信息
 	}>(),
 	{
 		items: () => [],
@@ -59,9 +60,10 @@ const props = withDefaults(
 		layout: "grid",
 		columns: 5,
 		title: undefined,
-		showItemActions: true,
+		showItemActions: false,
 		concurrency: 50,
-		autoGenerate: true
+		autoGenerate: true,
+		alert: true
 	}
 )
 
@@ -165,7 +167,7 @@ function addQRCode(item?: Partial<QRCodeItem>) {
 	qrList.value.push(newItem)
 	emits("update:items", qrList.value)
 	emits("add", newItem)
-	message.msgSuccess("已添加新二维码")
+	props.alert && message.msgSuccess("已添加新二维码")
 
 	// 如果开启了自动生成,立即生成新添加的二维码
 	if (props.autoGenerate && newItem.text) {
@@ -194,7 +196,7 @@ function removeQRCode(id: string) {
 		qrRefs.value.delete(id)
 		emits("update:items", qrList.value)
 		emits("remove", id)
-		message.msgSuccess("已删除二维码")
+		props.alert && message.msgSuccess("已删除二维码")
 	}
 }
 
@@ -206,7 +208,7 @@ function generateAll(): Promise<{ success: number; fail: number }> {
 	return new Promise((resolve, reject) => {
 		const total = qrList.value.length
 		if (total === 0) {
-			message.msgWarning("没有可生成的二维码")
+			props.alert && message.msgWarning("没有可生成的二维码")
 			resolve({ success: 0, fail: 0 })
 			return
 		}
@@ -341,7 +343,7 @@ function generateOne(item: QRCodeItem) {
 		qrRef.clearQRCode()
 		qrRef.generateQRCode(options)
 	} else {
-		message.msgWarning("二维码文本为空")
+		props.alert && message.msgWarning("二维码文本为空")
 	}
 }
 
@@ -355,7 +357,7 @@ function downloadOne(item: QRCodeItem, index: number) {
 		qrRef
 			.downloadQRCode(title)
 			.then(() => {
-				message.msgSuccess(`已下载:${title}`)
+				props.alert && message.msgSuccess(`已下载:${title}`)
 			})
 			.catch((error) => {
 				console.error(`二维码 ${item.id} 下载失败:`, error)
@@ -374,7 +376,7 @@ function printOne(item: QRCodeItem, index: number) {
 		qrRef
 			.printQRCode(title)
 			.then(() => {
-				message.msgSuccess(`已打开打印窗口:${title}`)
+				props.alert && message.msgSuccess(`已打开打印窗口:${title}`)
 			})
 			.catch((error) => {
 				console.error(`二维码 ${item.id} 打印失败:`, error)
@@ -387,6 +389,7 @@ function printOne(item: QRCodeItem, index: number) {
  * 批量清除所有二维码
  */
 function clearAll() {
+	console.log("clearAll")
 	qrList.value.forEach((item) => {
 		const qrRef = qrRefs.value.get(item.id)
 		if (qrRef) {
@@ -394,7 +397,6 @@ function clearAll() {
 		}
 	})
 	emits("clear", "all")
-	message.msgSuccess("已清除所有二维码")
 }
 
 /**
@@ -404,25 +406,28 @@ function clearAll() {
 function downloadAll(): Promise<void> {
 	return new Promise((resolve, reject) => {
 		try {
-			// 收集所有已生成的二维码 Data URL
-			const dataURLs: { title: string; dataURL: string }[] = []
+			// 收集所有已生成的二维码 Data URL(包含标题和高度)
+			const qrDataList: { title: string; dataURL: string; height: number }[] = []
 
 			for (let i = 0; i < qrList.value.length; i++) {
 				const item = qrList.value[i]
 				const qrRef = qrRefs.value.get(item.id)
 				if (qrRef) {
-					const dataURL = qrRef.getQRCodeDataURL()
-					if (dataURL) {
-						dataURLs.push({
-							title: getQRTitle(item, i),
-							dataURL
+					const title = getQRTitle(item, i)
+					// 调用子组件方法获取包含标题的 Data URL 和高度
+					const result = qrRef.getQRCodeDataURLWithTitle(title)
+					if (result) {
+						qrDataList.push({
+							title,
+							dataURL: result.dataURL,
+							height: result.height
 						})
 					}
 				}
 			}
 
-			if (dataURLs.length === 0) {
-				message.msgWarning("没有可下载的二维码,请先生成")
+			if (qrDataList.length === 0) {
+				props.alert && message.msgWarning("没有可下载的二维码,请先生成")
 				resolve()
 				return
 			}
@@ -437,12 +442,28 @@ function downloadAll(): Promise<void> {
 			// 计算画布大小
 			const qrSize = props.size
 			const padding = 40
-			const titleHeight = 30
-			const cols = Math.min(dataURLs.length, props.columns)
-			const rows = Math.ceil(dataURLs.length / cols)
+			const cols = Math.min(qrDataList.length, props.columns)
+			const rows = Math.ceil(qrDataList.length / cols)
+
+			// 计算每行的最大高度
+			const rowMaxHeights: number[] = []
+			for (let row = 0; row < rows; row++) {
+				let maxHeight = 0
+				for (let col = 0; col < cols; col++) {
+					const index = row * cols + col
+					if (index < qrDataList.length) {
+						maxHeight = Math.max(maxHeight, qrDataList[index].height)
+					}
+				}
+				rowMaxHeights.push(maxHeight)
+			}
+
+			// 计算总高度
+			const totalHeight =
+				rowMaxHeights.reduce((sum, h) => sum + h + padding, -padding) + padding * 2
 
 			canvas.width = cols * (qrSize + padding) + padding
-			canvas.height = rows * (qrSize + titleHeight + padding) + padding
+			canvas.height = totalHeight
 
 			// 填充白色背景
 			ctx.fillStyle = "#ffffff"
@@ -450,7 +471,7 @@ function downloadAll(): Promise<void> {
 
 			// 绘制所有二维码
 			let loadedCount = 0
-			const totalImages = dataURLs.length
+			const totalImages = qrDataList.length
 
 			const checkComplete = () => {
 				loadedCount++
@@ -463,35 +484,32 @@ function downloadAll(): Promise<void> {
 					link.click()
 					document.body.removeChild(link)
 
-					emits("download-all-complete", dataURLs.length)
-					message.msgSuccess(`已下载包含 ${dataURLs.length} 个二维码的图片`)
+					emits("download-all-complete", qrDataList.length)
+					props.alert && message.msgSuccess(`已下载包含 ${qrDataList.length} 个二维码的图片`)
 					resolve()
 				}
 			}
 
-			for (let i = 0; i < dataURLs.length; i++) {
+			for (let i = 0; i < qrDataList.length; i++) {
 				const col = i % cols
 				const row = Math.floor(i / cols)
 				const x = col * (qrSize + padding) + padding
-				const y = row * (qrSize + titleHeight + padding) + padding
 
-				// 绘制标题
-				ctx.fillStyle = "#333333"
-				ctx.font = "bold 14px Arial"
-				ctx.textAlign = "center"
-				ctx.fillText(dataURLs[i].title, x + qrSize / 2, y + 20)
+				// 计算 Y 坐标:累加前面所有行的高度
+				const y = rowMaxHeights.slice(0, row).reduce((sum, h) => sum + h + padding, padding)
 
-				// 绘制二维码
+				// 直接绘制已包含标题的二维码图片
 				const img = new Image()
 				img.onload = () => {
-					ctx.drawImage(img, x, y + titleHeight, qrSize, qrSize)
+					// 使用图片的实际宽度和高度,保持原始比例
+					ctx.drawImage(img, x, y, img.width, img.height)
 					checkComplete()
 				}
 				img.onerror = () => {
-					console.error(`图片加载失败: ${dataURLs[i].title}`)
+					console.error(`图片加载失败: ${qrDataList[i].title}`)
 					checkComplete()
 				}
-				img.src = dataURLs[i].dataURL
+				img.src = qrDataList[i].dataURL
 			}
 		} catch (error) {
 			console.error("批量下载失败:", error)
@@ -526,7 +544,7 @@ function printAll(): Promise<void> {
 			}
 
 			if (dataURLs.length === 0) {
-				message.msgWarning("没有可打印的二维码,请先生成")
+				props.alert && message.msgWarning("没有可打印的二维码,请先生成")
 				resolve()
 				return
 			}
@@ -621,7 +639,7 @@ function printAll(): Promise<void> {
 			printWindow.location.href = url
 
 			emits("print-all-complete", dataURLs.length)
-			message.msgSuccess(`已打开打印窗口,包含 ${dataURLs.length} 个二维码`)
+			props.alert && message.msgSuccess(`已打开打印窗口,包含 ${dataURLs.length} 个二维码`)
 			resolve()
 		} catch (error) {
 			console.error("批量打印失败:", error)
@@ -642,63 +660,25 @@ function setQRRef(id: string, ref: InstanceType<typeof VbQrGen> | null) {
 	}
 }
 
-// 页面挂载后,如果开启了自动生成,则自动批量生成
-onMounted(() => {
-	if (props.autoGenerate && qrList.value.length > 0) {
-		// 等待 DOM 渲染完成
-		nextTick(() => {
-			// 延迟一小段时间确保所有子组件都已挂载
-			setTimeout(() => {
-				generateAll()
-			}, 100)
-		})
-	}
-})
-
-// 监听 items 变化,如果有新项且开启了自动生成,则自动生成新项
+// 监听 items 变化
 watch(
 	() => props.items,
-	(newItems, oldItems) => {
+	(newItems) => {
+		qrList.value = newItems
 		if (!props.autoGenerate) return
-
-		// 如果是初始化,不处理(由 onMounted 处理)
-		if (!oldItems || oldItems.length === 0) return
-
-		// 检测是否有新项
-		if (newItems.length > oldItems.length) {
-			// 找到新添加的项
-			const newIds = newItems.map((item) => item.id)
-			const oldIds = oldItems.map((item) => item.id)
-			const addedIds = newIds.filter((id) => !oldIds.includes(id))
-
-			// 为每个新项生成二维码
-			if (addedIds.length > 0) {
-				nextTick(() => {
-					addedIds.forEach((id) => {
-						const item = newItems.find((i) => i.id === id)
-						if (item && item.text) {
-							const qrRef = qrRefs.value.get(id)
-							if (qrRef) {
-								try {
-									const options = mergeOptions(item)
-									options.text = item.text
-									qrRef.generateQRCode(options)
-								} catch (error) {
-									console.error(`自动生成失败:`, error)
-								}
-							}
-						}
-					})
-				})
-			}
+		if (qrList.value.length > 0) {
+			nextTick(() => {
+				setTimeout(() => {
+					generateAll()
+				}, 10)
+			})
 		}
 	},
-	{ deep: true }
+	{ immediate: true }
 )
 
 // 暴露方法给父组件
 defineExpose({
-	qrList,
 	addQRCode,
 	removeQRCode,
 	generateAll,
@@ -708,6 +688,7 @@ defineExpose({
 	clearAll,
 	downloadAll,
 	printAll,
+	getQRList: () => qrList.value,
 	getQRRef: (id: string) => qrRefs.value.get(id)
 })
 </script>
@@ -731,7 +712,7 @@ defineExpose({
 					<!-- 二维码组件 -->
 					<VbQrGen
 						:ref="(el) => setQRRef(item.id, el as InstanceType<typeof VbQrGen>)"
-						v-model="item.text"
+						text="item.text"
 						:title="getQRTitle(item, index)"
 						:size="item.options?.size ?? size"
 						:margin="item.options?.margin ?? margin"