foucui 1.0.1 → 1.0.2

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.
Files changed (234) hide show
  1. package/dist/components/QiuCanvasKit/QiuCanvasEditor.vue.d.ts +3 -1
  2. package/dist/components/QiuCanvasKit/components/DataModulesPanel.vue.d.ts +1 -0
  3. package/dist/components/QiuTable/index.vue.d.ts +12 -12
  4. package/dist/foucui.css +1 -1
  5. package/dist/foucui.js +7476 -7373
  6. package/dist/foucui.umd.cjs +19 -19
  7. package/package.json +23 -13
  8. package/components/QiuAffix/index.vue +0 -186
  9. package/components/QiuAlert/index.vue +0 -208
  10. package/components/QiuAnchor/context.ts +0 -18
  11. package/components/QiuAnchor/index.vue +0 -180
  12. package/components/QiuAnchorLink/index.vue +0 -67
  13. package/components/QiuApp/index.vue +0 -9
  14. package/components/QiuAutocomplete/index.vue +0 -408
  15. package/components/QiuAvatar/index.vue +0 -140
  16. package/components/QiuBacktop/index.vue +0 -121
  17. package/components/QiuBadge/api/index.ts +0 -389
  18. package/components/QiuBadge/components/BadgeImagePickerDialog.vue +0 -243
  19. package/components/QiuBadge/components/BadgeLibraryManagerDialog.vue +0 -30
  20. package/components/QiuBadge/components/BadgeLibraryPanel.vue +0 -182
  21. package/components/QiuBadge/composables/useTableHostHeight.ts +0 -53
  22. package/components/QiuBadge/index.ts +0 -18
  23. package/components/QiuBadge/shared/BadgeTemplateRenderer.vue +0 -32
  24. package/components/QiuBadge/shared/assetUrl.ts +0 -26
  25. package/components/QiuBadge/shared/badgeTemplate.ts +0 -309
  26. package/components/QiuBadge/utils/chinaAreaData.ts +0 -44
  27. package/components/QiuBadge/utils/hostRegion.ts +0 -22
  28. package/components/QiuBadge/utils/pca-code.json +0 -1
  29. package/components/QiuBadge/utils/pdfFromElement.ts +0 -348
  30. package/components/QiuBadge/views/BadgeLibraryView.vue +0 -32
  31. package/components/QiuBadge/views/BadgeManageView.vue +0 -1364
  32. package/components/QiuBadge/views/BadgeTemplateEditorView.vue +0 -10
  33. package/components/QiuBadge/views/BadgeTemplateListView.vue +0 -229
  34. package/components/QiuBadge/views/DeskLabelTemplateListView.vue +0 -227
  35. package/components/QiuBadge/views/DeskLabelView.vue +0 -683
  36. package/components/QiuBadgeDot/index.vue +0 -87
  37. package/components/QiuButton/index.vue +0 -247
  38. package/components/QiuButtonGroup/index.vue +0 -63
  39. package/components/QiuCanvasKit/QiuCanvasEditor.vue +0 -5503
  40. package/components/QiuCanvasKit/QiuCanvasRenderer.vue +0 -348
  41. package/components/QiuCanvasKit/assetUrl.ts +0 -11
  42. package/components/QiuCanvasKit/components/DataModulesPanel.vue +0 -352
  43. package/components/QiuCanvasKit/export/html.ts +0 -132
  44. package/components/QiuCanvasKit/export/pdf.ts +0 -48
  45. package/components/QiuCanvasKit/export/png.ts +0 -50
  46. package/components/QiuCanvasKit/index.ts +0 -20
  47. package/components/QiuCanvasKit/modules/badgeCompat.ts +0 -84
  48. package/components/QiuCanvasKit/modules/dataRegistry.ts +0 -207
  49. package/components/QiuCanvasKit/modules/merge.ts +0 -71
  50. package/components/QiuCanvasKit/presets/badge.ts +0 -67
  51. package/components/QiuCanvasKit/presets/banner.ts +0 -79
  52. package/components/QiuCanvasKit/types.ts +0 -138
  53. package/components/QiuCard/index.vue +0 -104
  54. package/components/QiuCarousel/context.ts +0 -16
  55. package/components/QiuCarousel/index.vue +0 -343
  56. package/components/QiuCarouselItem/index.vue +0 -47
  57. package/components/QiuCascader/index.vue +0 -879
  58. package/components/QiuCascader/types.ts +0 -48
  59. package/components/QiuCheckbox/QiuCheckboxGroup.vue +0 -78
  60. package/components/QiuCheckbox/index.vue +0 -231
  61. package/components/QiuCheckbox/types.ts +0 -8
  62. package/components/QiuCode/index.vue +0 -64
  63. package/components/QiuCol/col.scss +0 -55
  64. package/components/QiuCol/index.vue +0 -64
  65. package/components/QiuCollapse/context.ts +0 -10
  66. package/components/QiuCollapse/index.vue +0 -70
  67. package/components/QiuCollapseItem/index.vue +0 -167
  68. package/components/QiuColorField/index.vue +0 -296
  69. package/components/QiuColorPicker/README.md +0 -118
  70. package/components/QiuColorPicker/index.vue +0 -60
  71. package/components/QiuColorfulCard/index.vue +0 -101
  72. package/components/QiuCombobox/index.vue +0 -277
  73. package/components/QiuConfigProvider/index.vue +0 -31
  74. package/components/QiuContextMenu/index.vue +0 -209
  75. package/components/QiuDate/index.vue +0 -1134
  76. package/components/QiuDescriptions/context.ts +0 -20
  77. package/components/QiuDescriptions/index.vue +0 -115
  78. package/components/QiuDescriptionsItem/index.vue +0 -103
  79. package/components/QiuDialog/index.vue +0 -828
  80. package/components/QiuDigitalCard/index.vue +0 -159
  81. package/components/QiuDivider/index.vue +0 -185
  82. package/components/QiuDot/index.vue +0 -83
  83. package/components/QiuDrawer/index.vue +0 -350
  84. package/components/QiuDropdown/QiuDropdownItem.vue +0 -74
  85. package/components/QiuDropdown/QiuDropdownMenu.vue +0 -22
  86. package/components/QiuDropdown/context.ts +0 -9
  87. package/components/QiuDropdown/index.vue +0 -189
  88. package/components/QiuEmpty/index.vue +0 -73
  89. package/components/QiuForm/index.vue +0 -172
  90. package/components/QiuForm/types.ts +0 -39
  91. package/components/QiuFormItem/index.vue +0 -276
  92. package/components/QiuFullscreen/index.vue +0 -11
  93. package/components/QiuGrid/index.vue +0 -408
  94. package/components/QiuHoverCard/index.vue +0 -192
  95. package/components/QiuIcon/index.vue +0 -117
  96. package/components/QiuIconPicker/iconify.libraries.ts +0 -182
  97. package/components/QiuIconPicker/icons.catalog.ts +0 -3072
  98. package/components/QiuIconPicker/index.vue +0 -488
  99. package/components/QiuImage/index.vue +0 -177
  100. package/components/QiuInput/index.vue +0 -328
  101. package/components/QiuInputNumber/index.vue +0 -295
  102. package/components/QiuInputOtp/index.vue +0 -190
  103. package/components/QiuKbd/index.vue +0 -57
  104. package/components/QiuLayoutContainer/index.vue +0 -50
  105. package/components/QiuLinkPreview/index.vue +0 -224
  106. package/components/QiuLoading/index.ts +0 -176
  107. package/components/QiuLoading/spinner.vue +0 -820
  108. package/components/QiuLoading/types.ts +0 -44
  109. package/components/QiuMention/index.vue +0 -299
  110. package/components/QiuMenu/QiuMenuRoot.vue +0 -241
  111. package/components/QiuMenu/components/QiuMenuItem.vue +0 -340
  112. package/components/QiuMenu/components/QiuSubMenu.vue +0 -504
  113. package/components/QiuMenu/context.ts +0 -21
  114. package/components/QiuMenu/index.vue +0 -43
  115. package/components/QiuMenuNav/QiuMenuNavItem.vue +0 -100
  116. package/components/QiuMenuNav/QiuMenuNavSub.vue +0 -120
  117. package/components/QiuMenuNav/context.ts +0 -14
  118. package/components/QiuMenuNav/index.vue +0 -148
  119. package/components/QiuMsg/QiuMsgItem.vue +0 -180
  120. package/components/QiuMsg/index.ts +0 -158
  121. package/components/QiuMsg/types.ts +0 -23
  122. package/components/QiuMsgBox/index.ts +0 -147
  123. package/components/QiuNotification/QiuNotificationItem.vue +0 -168
  124. package/components/QiuNotification/index.ts +0 -155
  125. package/components/QiuNotification/types.ts +0 -14
  126. package/components/QiuPage/index.vue +0 -12
  127. package/components/QiuPageHeader/index.vue +0 -115
  128. package/components/QiuPageMain/index.vue +0 -78
  129. package/components/QiuPagination/index.vue +0 -421
  130. package/components/QiuPasswordStrength/index.vue +0 -101
  131. package/components/QiuPopconfirm/index.vue +0 -196
  132. package/components/QiuPopover/index.vue +0 -277
  133. package/components/QiuProgress/index.vue +0 -235
  134. package/components/QiuQrcode/index.vue +0 -302
  135. package/components/QiuQueryForm/components/QiuQueryFormBottomPanel.vue +0 -13
  136. package/components/QiuQueryForm/components/QiuQueryFormLeftPanel.vue +0 -37
  137. package/components/QiuQueryForm/components/QiuQueryFormRightPanel.vue +0 -37
  138. package/components/QiuQueryForm/components/QiuQueryFormTopPanel.vue +0 -13
  139. package/components/QiuQueryForm/index.vue +0 -117
  140. package/components/QiuRadio/QiuRadioGroup.vue +0 -75
  141. package/components/QiuRadio/index.vue +0 -191
  142. package/components/QiuRadio/types.ts +0 -9
  143. package/components/QiuRate/index.vue +0 -184
  144. package/components/QiuRefresh/index.vue +0 -32
  145. package/components/QiuResult/index.vue +0 -103
  146. package/components/QiuRow/index.vue +0 -60
  147. package/components/QiuRow/types.ts +0 -5
  148. package/components/QiuScrollbar/index.vue +0 -107
  149. package/components/QiuSearchBar/index.vue +0 -154
  150. package/components/QiuSegmented/index.vue +0 -120
  151. package/components/QiuSelect/QiuOption.vue +0 -93
  152. package/components/QiuSelect/index.vue +0 -452
  153. package/components/QiuSelect/types.ts +0 -15
  154. package/components/QiuSkeleton/index.vue +0 -78
  155. package/components/QiuSlider/index.vue +0 -266
  156. package/components/QiuSpace/index.vue +0 -95
  157. package/components/QiuSparkline/index.vue +0 -92
  158. package/components/QiuStatistic/index.vue +0 -4
  159. package/components/QiuStatistics/index.vue +0 -123
  160. package/components/QiuStep/index.vue +0 -220
  161. package/components/QiuSteps/context.ts +0 -25
  162. package/components/QiuSteps/index.vue +0 -84
  163. package/components/QiuSwitch/index.vue +0 -145
  164. package/components/QiuTabBar/index.vue +0 -218
  165. package/components/QiuTabBar/types.ts +0 -19
  166. package/components/QiuTabPane/index.vue +0 -82
  167. package/components/QiuTable/columns.ts +0 -16
  168. package/components/QiuTable/export.ts +0 -102
  169. package/components/QiuTable/index.ts +0 -24
  170. package/components/QiuTable/index.vue +0 -3503
  171. package/components/QiuTable/registry.ts +0 -75
  172. package/components/QiuTable/styles/table.scss +0 -1230
  173. package/components/QiuTable/types.ts +0 -413
  174. package/components/QiuTable/useArea.ts +0 -202
  175. package/components/QiuTable/useColumns.ts +0 -325
  176. package/components/QiuTable/useEdit.ts +0 -136
  177. package/components/QiuTable/useExtras.ts +0 -115
  178. package/components/QiuTable/useHistory.ts +0 -77
  179. package/components/QiuTable/useMerge.ts +0 -105
  180. package/components/QiuTable/useResize.ts +0 -86
  181. package/components/QiuTable/useScrollSync.ts +0 -152
  182. package/components/QiuTable/useVirtual.ts +0 -97
  183. package/components/QiuTable/utils.ts +0 -165
  184. package/components/QiuTableColumn/index.vue +0 -101
  185. package/components/QiuTag/index.vue +0 -143
  186. package/components/QiuText/index.vue +0 -99
  187. package/components/QiuTime/index.vue +0 -548
  188. package/components/QiuTimeAgo/index.vue +0 -103
  189. package/components/QiuTimeline/index.vue +0 -54
  190. package/components/QiuTimelineItem/index.vue +0 -140
  191. package/components/QiuTips/index.vue +0 -323
  192. package/components/QiuTooltip/index.vue +0 -237
  193. package/components/QiuTransfer/index.vue +0 -277
  194. package/components/QiuTree/QiuTreeNode.vue +0 -146
  195. package/components/QiuTree/index.vue +0 -143
  196. package/components/QiuTree/types.ts +0 -30
  197. package/components/QiuTreeSelect/index.vue +0 -259
  198. package/components/QiuTrend/index.vue +0 -92
  199. package/components/QiuUpload/index.vue +0 -628
  200. package/components/QiuUploadCrop/index.vue +0 -1267
  201. package/components/QiuWatermark/index.vue +0 -121
  202. package/components/useBodyScrollLock.ts +0 -28
  203. package/dist/components/QiuBadge/utils/pca-code.json.d.ts +0 -3
  204. package/plugins/qiu.ts +0 -83
  205. package/plugins/shortcuts.ts +0 -69
  206. package/src/bridge/@/api/media.ts +0 -8
  207. package/src/bridge/@/api/notice.ts +0 -5
  208. package/src/bridge/@/config/index.ts +0 -38
  209. package/src/bridge/@/config/media.config.ts +0 -37
  210. package/src/bridge/@/config/security.config.ts +0 -8
  211. package/src/bridge/@/i18n/index.ts +0 -6
  212. package/src/bridge/@/store/modules/bing.ts +0 -9
  213. package/src/bridge/@/store/modules/errorLog.ts +0 -9
  214. package/src/bridge/@/store/modules/media.ts +0 -10
  215. package/src/bridge/@/store/modules/routes.ts +0 -10
  216. package/src/bridge/@/store/modules/settings.ts +0 -9
  217. package/src/bridge/@/store/modules/tabs.ts +0 -9
  218. package/src/bridge/@/store/modules/user.ts +0 -9
  219. package/src/bridge/@/utils/convertToCamelCase.ts +0 -11
  220. package/src/bridge/@/utils/index.ts +0 -9
  221. package/src/bridge/@/utils/pageTitle.ts +0 -4
  222. package/src/bridge/@/utils/routes.ts +0 -14
  223. package/src/bridge/@/utils/validate.ts +0 -17
  224. package/src/bridge/@qiu/plugins/shortcuts.ts +0 -3
  225. package/src/i18n.ts +0 -8
  226. package/src/icons/qiu-logo.svg +0 -1
  227. package/styles/dark.scss +0 -194
  228. package/styles/normalize.css +0 -349
  229. package/styles/plain.scss +0 -423
  230. package/styles/qiu-tokens.scss +0 -40
  231. package/styles/qiu.scss +0 -1149
  232. package/styles/technology.scss +0 -452
  233. package/styles/var.scss +0 -382
  234. package/styles/variables.scss +0 -77
@@ -1,683 +0,0 @@
1
- <template>
2
- <div class="page-container desk-label-page">
3
- <qiu-card class="filter-card" shadow="never">
4
- <qiu-form :inline="true">
5
- <qiu-form-item label="选择比赛">
6
- <qiu-select
7
- v-model="competitionId"
8
- placeholder="选择比赛"
9
- style="width: 320px"
10
- :options="competitionSelectOptions"
11
- @change="onCompetitionChange"
12
- />
13
- </qiu-form-item>
14
- <qiu-form-item label="桌面贴模板">
15
- <qiu-select
16
- v-model="selectedTemplateId"
17
- clearable
18
- placeholder="默认模板"
19
- style="width: 260px"
20
- :disabled="!competitionId || templateLoading"
21
- :options="templateSelectOptions"
22
- @change="onTemplateChange"
23
- />
24
- </qiu-form-item>
25
- <qiu-form-item>
26
- <qiu-button type="primary" :disabled="!competitionId" @click="loadLabels">加载桌面贴数据</qiu-button>
27
- </qiu-form-item>
28
- <qiu-form-item v-if="competitionId">
29
- <qiu-button @click="goDeskTemplates">桌面贴模板</qiu-button>
30
- </qiu-form-item>
31
- </qiu-form>
32
- </qiu-card>
33
-
34
- <template v-if="Object.keys(labels).length">
35
- <qiu-card v-for="(students, room) in labels" :key="String(room)" style="margin-bottom: 16px">
36
- <template #header>{{ room }}</template>
37
- <div class="label-grid">
38
- <div class="label-card" v-for="s in students" :key="`${room}-${s.seat_no}`">
39
- <BadgeTemplateRenderer
40
- v-if="selectedTemplateDoc?.elements?.length"
41
- :doc="selectedTemplateDoc"
42
- :merge-data="flattenDeskLabelForMerge(s, competitionTitle)"
43
- :screen-scale="deskLabelPreviewScale"
44
- />
45
- <template v-else>
46
- <div class="label-seat">{{ s.seat_no }}号</div>
47
- <div class="label-name">{{ s.student_name }}</div>
48
- <div class="label-school">{{ s.school_name }}</div>
49
- </template>
50
- </div>
51
- </div>
52
- </qiu-card>
53
- <div class="toolbar-pdf">
54
- <qiu-button type="primary" :disabled="!competitionId" @click="openPaperDialog">生成预览与 PDF</qiu-button>
55
- </div>
56
- </template>
57
- <qiu-empty v-else description="请选择比赛并加载桌面贴" />
58
-
59
- <qiu-dialog v-model="paperPickVisible" title="预览排版设置" width="520px" destroy-on-close align-center>
60
- <p class="paper-tip">导出整版 PDF 前请选择纸张模式。可按 A4/A3 排版,也可直接按模板实际尺寸生成。预览、下载 PDF、打印都会使用这套排版设置。</p>
61
- <div class="paper-config">
62
- <div class="paper-config-block">
63
- <div class="paper-config-title">纸张尺寸</div>
64
- <qiu-radio-group v-model="paperSize">
65
- <qiu-radio label="a4">A4</qiu-radio>
66
- <qiu-radio label="a3">A3</qiu-radio>
67
- <qiu-radio label="custom">按模板尺寸</qiu-radio>
68
- </qiu-radio-group>
69
- </div>
70
- <div class="paper-config-block">
71
- <div class="paper-config-title">打印方向</div>
72
- <qiu-radio-group v-model="paperOrientation" :disabled="isDirectSizeMode">
73
- <qiu-radio label="portrait">纵向</qiu-radio>
74
- <qiu-radio label="landscape">横向</qiu-radio>
75
- </qiu-radio-group>
76
- <div v-if="isDirectSizeMode" class="paper-config-desc">按模板尺寸生成时,页面宽高直接取模板设置,方向无需单独选择。</div>
77
- </div>
78
- <div class="paper-config-block">
79
- <div class="paper-config-title">每行显示</div>
80
- <div class="paper-inline-row">
81
- <qiu-radio-group v-model="paperColumnsPreset" :disabled="isDirectSizeMode">
82
- <qiu-radio :label="1">1个</qiu-radio>
83
- <qiu-radio :label="2">2个</qiu-radio>
84
- <qiu-radio :label="3">3个</qiu-radio>
85
- <qiu-radio :label="0">自定义</qiu-radio>
86
- </qiu-radio-group>
87
- <qiu-input-number
88
- v-model="paperColumns"
89
- :min="1"
90
- :max="12"
91
- controls-position="right"
92
- style="width: 120px"
93
- :disabled="isDirectSizeMode"
94
- />
95
- </div>
96
- <div class="paper-config-desc">
97
- {{ isDirectSizeMode ? '按模板尺寸生成时固定为每页 1 个桌面贴。' : '只控制每行显示几个桌面贴;每页行数会按纸张高度自动计算。' }}
98
- </div>
99
- </div>
100
- </div>
101
- <template #footer>
102
- <qiu-button @click="paperPickVisible = false">取消</qiu-button>
103
- <qiu-button type="primary" @click="confirmPaperAndOpenPdf">下一步:整版预览</qiu-button>
104
- </template>
105
- </qiu-dialog>
106
-
107
- <qiu-dialog
108
- v-model="pdfDialogVisible"
109
- :title="`桌面贴整版预览 — ${competitionTitle || ''}`"
110
- width="96%"
111
- top="2vh"
112
- destroy-on-close
113
- class="pdf-preview-dialog"
114
- @opened="onPdfDialogOpened"
115
- >
116
- <div v-qiu-loading="pdfLoading" class="pdf-dialog-body">
117
- <div ref="pdfExportRoot" class="pdf-export-root" :style="deskPdfExportRootStyle">
118
- <template v-if="pdfTemplateDoc?.elements?.length && pdfMergeList.length">
119
- <div v-for="(page, pageIndex) in deskPdfPages" :key="pageIndex" class="pdf-page" :style="deskPdfPageStyle">
120
- <div class="pdf-page-grid" :style="deskPdfPageGridStyle(page.length)">
121
- <div v-for="(m, i) in page" :key="`${pageIndex}-${i}`" class="pdf-cell" :style="deskPdfCellStyle">
122
- <BadgeTemplateRenderer :doc="pdfTemplateDoc" :merge-data="m" :screen-scale="deskPdfTemplateScale" />
123
- </div>
124
- </div>
125
- </div>
126
- </template>
127
- <template v-else-if="pdfMergeList.length">
128
- <div v-for="(page, pageIndex) in deskPdfPages" :key="pageIndex" class="pdf-page" :style="deskPdfPageStyle">
129
- <div class="pdf-page-grid" :style="deskPdfPageGridStyle(page.length)">
130
- <div v-for="(m, i) in page" :key="`${pageIndex}-${i}`" class="pdf-cell simple" :style="deskPdfCellStyle">
131
- <div class="simple-seat">{{ m.seat_no }}号</div>
132
- <div class="simple-name">{{ m.student_name }}</div>
133
- <div class="simple-school">{{ m.school_name }}</div>
134
- <div class="simple-no">{{ m.competition_no }}</div>
135
- </div>
136
- </div>
137
- </div>
138
- </template>
139
- </div>
140
- </div>
141
- <template #footer>
142
- <qiu-button :disabled="!pdfMergeList.length" @click="onPrintPdf">打印整版</qiu-button>
143
- <qiu-button @click="pdfDialogVisible = false">关闭</qiu-button>
144
- <qiu-button type="primary" :loading="pdfDownloading" :disabled="!pdfMergeList.length" @click="onDownloadPdf">
145
- 下载 PDF
146
- </qiu-button>
147
- </template>
148
- </qiu-dialog>
149
- </div>
150
- </template>
151
-
152
- <script setup lang="ts">
153
- import { ref, computed, onMounted, nextTick, watch } from 'vue'
154
- import { useRouter } from 'vue-router'
155
- import { qiuMsg } from '../../QiuMsg'
156
- import {
157
- getCompetitions,
158
- getDeskLabelTemplate,
159
- getDeskLabelTemplateOne,
160
- listDeskLabelTemplates,
161
- } from '../api/index'
162
- import { getDeskLabels } from '../api/index'
163
- import BadgeTemplateRenderer from '../shared/BadgeTemplateRenderer.vue'
164
- import type { BadgeTemplateDoc } from '../shared/badgeTemplate'
165
- import { flattenDeskLabelForMerge } from '../shared/badgeTemplate'
166
- import {
167
- downloadPdfPagesFromElements,
168
- printPdfPagesFromElements,
169
- type PaperKind,
170
- type PaperOrientation,
171
- } from '../utils/pdfFromElement'
172
-
173
- const router = useRouter()
174
- const competitionId = ref(0)
175
- const competitions = ref<any[]>([])
176
- const labels = ref<Record<string, any[]>>({})
177
- const competitionTitle = ref('')
178
- const templateLoading = ref(false)
179
- const templateOptions = ref<any[]>([])
180
- const competitionSelectOptions = computed(() =>
181
- competitions.value.map((c) => ({ label: c.name, value: c.id }))
182
- )
183
- const templateSelectOptions = computed(() =>
184
- templateOptions.value.map((tpl) => ({
185
- label: tpl.is_default ? `${tpl.name}(默认)` : tpl.name,
186
- value: tpl.id,
187
- }))
188
- )
189
- const selectedTemplateId = ref<number | undefined>()
190
- const selectedTemplateDoc = ref<BadgeTemplateDoc | null>(null)
191
-
192
- const paperPickVisible = ref(false)
193
- const paperSize = ref<PaperKind>('a4')
194
- const paperOrientation = ref<PaperOrientation>('portrait')
195
- const paperColumnsPreset = ref<number>(2)
196
- const paperColumns = ref<number>(2)
197
- const pdfDialogVisible = ref(false)
198
- const pdfLoading = ref(false)
199
- const pdfDownloading = ref(false)
200
- const pdfExportRoot = ref<HTMLElement | null>(null)
201
- const pdfTemplateDoc = ref<BadgeTemplateDoc | null>(null)
202
- const pdfMergeList = ref<Record<string, string>[]>([])
203
-
204
- const deskLabelPreviewScale = computed(() => {
205
- const doc = selectedTemplateDoc.value
206
- const widthMm = Number(doc?.canvas?.widthMm || 90)
207
- const heightMm = Number(doc?.canvas?.heightMm || 50)
208
- const widthPx = (widthMm * 96) / 25.4
209
- const heightPx = (heightMm * 96) / 25.4
210
- const maxWidth = 180
211
- const maxHeight = 120
212
- return Math.min(1, maxWidth / widthPx, maxHeight / heightPx)
213
- })
214
-
215
- async function loadTemplateOptions() {
216
- if (!competitionId.value) {
217
- templateOptions.value = []
218
- selectedTemplateId.value = undefined
219
- selectedTemplateDoc.value = null
220
- return
221
- }
222
- templateLoading.value = true
223
- try {
224
- const res: any = await listDeskLabelTemplates(competitionId.value)
225
- const list = res.data?.list || []
226
- templateOptions.value = list
227
- const currentId = Number(selectedTemplateId.value || 0)
228
- const matched = currentId > 0 ? list.find((item: any) => Number(item.id) === currentId) : null
229
- const defaultTpl = list.find((item: any) => Boolean(item.is_default))
230
- selectedTemplateId.value = matched ? currentId : defaultTpl?.id ? Number(defaultTpl.id) : list[0]?.id ? Number(list[0].id) : undefined
231
- await loadSelectedTemplate()
232
- } catch {
233
- templateOptions.value = []
234
- selectedTemplateId.value = undefined
235
- selectedTemplateDoc.value = null
236
- qiuMsg.error('加载桌面贴模板失败')
237
- } finally {
238
- templateLoading.value = false
239
- }
240
- }
241
-
242
- async function loadSelectedTemplate() {
243
- if (!competitionId.value) {
244
- selectedTemplateDoc.value = null
245
- return
246
- }
247
- try {
248
- const res: any = selectedTemplateId.value
249
- ? await getDeskLabelTemplateOne(competitionId.value, Number(selectedTemplateId.value))
250
- : await getDeskLabelTemplate(competitionId.value)
251
- const tpl = res.data?.template_json
252
- if (tpl && typeof tpl === 'object' && Array.isArray((tpl as BadgeTemplateDoc).elements)) {
253
- selectedTemplateDoc.value = tpl as BadgeTemplateDoc
254
- return
255
- }
256
- } catch {
257
- // ignore and fallback below
258
- }
259
- selectedTemplateDoc.value = null
260
- }
261
-
262
- const loadLabels = async () => {
263
- if (!competitionId.value) return
264
- try {
265
- await loadSelectedTemplate()
266
- const res: any = await getDeskLabels(competitionId.value)
267
- labels.value = res.data || {}
268
- } catch {
269
- labels.value = {}
270
- }
271
- }
272
-
273
- const onCompetitionChange = async () => {
274
- labels.value = {}
275
- pdfTemplateDoc.value = null
276
- pdfMergeList.value = []
277
- const c = competitions.value.find((x) => x.id === competitionId.value)
278
- competitionTitle.value = c?.name || ''
279
- await loadTemplateOptions()
280
- }
281
-
282
- const onTemplateChange = async () => {
283
- await loadSelectedTemplate()
284
- }
285
-
286
- const goDeskTemplates = () => {
287
- if (!competitionId.value) return
288
- router.push(`/qiuui/badge/competition/${competitionId.value}/desk-label-templates`)
289
- }
290
-
291
- /** 按教室名、座位号排序,保证 PDF 顺序稳定 */
292
- function collectLabelRows(): Record<string, unknown>[] {
293
- const rows: Record<string, unknown>[] = []
294
- const rooms = Object.keys(labels.value).sort((a, b) => String(a).localeCompare(String(b), 'zh-CN'))
295
- for (const room of rooms) {
296
- const list = labels.value[room] || []
297
- const sorted = [...list].sort((a, b) => {
298
- const sa = Number(String(a?.seat_no ?? '').replace(/\D/g, '')) || 0
299
- const sb = Number(String(b?.seat_no ?? '').replace(/\D/g, '')) || 0
300
- if (sa !== sb) return sa - sb
301
- return String(a?.student_name ?? '').localeCompare(String(b?.student_name ?? ''), 'zh-CN')
302
- })
303
- for (const s of sorted) {
304
- rows.push({ ...s })
305
- }
306
- }
307
- return rows
308
- }
309
-
310
- const PAPER_SIZE_MM: Record<'a4' | 'a3', { w: number; h: number }> = {
311
- a4: { w: 210, h: 297 },
312
- a3: { w: 297, h: 420 },
313
- }
314
- const DESK_PDF_MARGIN_MM = 0
315
- const DESK_PDF_GAP_MM = 2
316
- const isDirectSizeMode = computed(() => paperSize.value === 'custom')
317
-
318
- const deskPdfPaperSize = computed(() => {
319
- if (isDirectSizeMode.value) {
320
- return {
321
- w: Number(pdfTemplateDoc.value?.canvas?.widthMm ?? selectedTemplateDoc.value?.canvas?.widthMm ?? 90),
322
- h: Number(pdfTemplateDoc.value?.canvas?.heightMm ?? selectedTemplateDoc.value?.canvas?.heightMm ?? 50),
323
- }
324
- }
325
- const base = PAPER_SIZE_MM[paperSize.value === 'a3' ? 'a3' : 'a4']
326
- return paperOrientation.value === 'landscape'
327
- ? { w: base.h, h: base.w }
328
- : base
329
- })
330
-
331
- const deskPdfLabelSize = computed(() => ({
332
- w: Number(pdfTemplateDoc.value?.canvas?.widthMm ?? selectedTemplateDoc.value?.canvas?.widthMm ?? 90),
333
- h: Number(pdfTemplateDoc.value?.canvas?.heightMm ?? selectedTemplateDoc.value?.canvas?.heightMm ?? 50),
334
- }))
335
-
336
- const deskPdfLayout = computed(() => {
337
- const page = deskPdfPaperSize.value
338
- const label = deskPdfLabelSize.value
339
- if (isDirectSizeMode.value) {
340
- return {
341
- page,
342
- requestedCols: 1,
343
- maxCols: 1,
344
- cols: 1,
345
- rowsPerPage: 1,
346
- perPage: 1,
347
- usableW: page.w,
348
- usableH: page.h,
349
- fitsWidth: true,
350
- fitsHeight: true,
351
- labelW: label.w,
352
- labelH: label.h,
353
- blockW: label.w,
354
- }
355
- }
356
- const requestedCols = Math.max(1, Number(paperColumns.value || 1))
357
- const usableW = Math.max(1, page.w - DESK_PDF_MARGIN_MM * 2)
358
- const usableH = Math.max(1, page.h - DESK_PDF_MARGIN_MM * 2)
359
- const fitsWidth = label.w <= usableW
360
- const fitsHeight = label.h <= usableH
361
- const maxCols = Math.max(1, Math.floor((usableW + DESK_PDF_GAP_MM) / (label.w + DESK_PDF_GAP_MM)))
362
- const cols = Math.min(requestedCols, maxCols)
363
- const rowsPerPage = Math.max(1, Math.floor((usableH + DESK_PDF_GAP_MM) / (label.h + DESK_PDF_GAP_MM)))
364
- const perPage = cols * rowsPerPage
365
- return {
366
- page,
367
- requestedCols,
368
- maxCols,
369
- cols,
370
- rowsPerPage,
371
- perPage,
372
- usableW,
373
- usableH,
374
- fitsWidth,
375
- fitsHeight,
376
- labelW: label.w,
377
- labelH: label.h,
378
- blockW: cols * label.w + Math.max(0, cols - 1) * DESK_PDF_GAP_MM,
379
- }
380
- })
381
-
382
- const deskPdfTemplateScale = computed(() => 1)
383
-
384
- const deskPdfPages = computed(() => {
385
- const perPage = deskPdfLayout.value.perPage
386
- const list = pdfMergeList.value
387
- const out: Record<string, string>[][] = []
388
- for (let i = 0; i < list.length; i += perPage) {
389
- out.push(list.slice(i, i + perPage))
390
- }
391
- return out
392
- })
393
-
394
- const deskPdfExportRootStyle = computed(() => ({
395
- width: `${deskPdfLayout.value.page.w}mm`,
396
- }))
397
-
398
- const deskPdfPageStyle = computed(() => ({
399
- width: `${deskPdfLayout.value.page.w}mm`,
400
- height: `${deskPdfLayout.value.page.h}mm`,
401
- padding: `${DESK_PDF_MARGIN_MM}mm`,
402
- boxSizing: 'border-box' as const,
403
- }))
404
-
405
- function deskPdfPageGridStyle(itemCount: number) {
406
- const { cols, labelW, labelH } = deskPdfLayout.value
407
- const n = Math.max(1, itemCount || 1)
408
- const rows = Math.max(1, Math.ceil(n / cols))
409
- const gridW = cols * labelW + (cols - 1) * DESK_PDF_GAP_MM
410
- const gridH = rows * labelH + (rows - 1) * DESK_PDF_GAP_MM
411
- return {
412
- display: 'grid',
413
- gridTemplateColumns: `repeat(${cols}, ${labelW}mm)`,
414
- gridAutoRows: `${labelH}mm`,
415
- gap: `${DESK_PDF_GAP_MM}mm`,
416
- width: `${gridW}mm`,
417
- height: `${gridH}mm`,
418
- boxSizing: 'border-box' as const,
419
- }
420
- }
421
-
422
- const deskPdfCellStyle = computed(() => {
423
- const { labelW, labelH } = deskPdfLayout.value
424
- return {
425
- width: `${labelW}mm`,
426
- height: `${labelH}mm`,
427
- boxSizing: 'border-box' as const,
428
- overflow: 'hidden',
429
- }
430
- })
431
-
432
- const openPaperDialog = () => {
433
- if (!competitionId.value || !Object.keys(labels.value).length) {
434
- qiuMsg.warning('请先加载桌面贴数据')
435
- return
436
- }
437
- paperSize.value = 'a4'
438
- paperOrientation.value = 'portrait'
439
- paperColumnsPreset.value = 2
440
- paperColumns.value = 2
441
- paperPickVisible.value = true
442
- }
443
-
444
- const confirmPaperAndOpenPdf = async () => {
445
- if (!competitionId.value) return
446
- paperPickVisible.value = false
447
- pdfLoading.value = true
448
- pdfDialogVisible.value = true
449
- pdfTemplateDoc.value = null
450
- pdfMergeList.value = []
451
- try {
452
- const rawRows = collectLabelRows()
453
- const tplRes: any = selectedTemplateId.value
454
- ? await getDeskLabelTemplateOne(competitionId.value, Number(selectedTemplateId.value))
455
- : await getDeskLabelTemplate(competitionId.value)
456
- const body = tplRes.data
457
- const tpl = body?.template_json
458
- if (tpl && typeof tpl === 'object' && Array.isArray((tpl as BadgeTemplateDoc).elements) && (tpl as BadgeTemplateDoc).elements!.length) {
459
- pdfTemplateDoc.value = tpl as BadgeTemplateDoc
460
- }
461
- pdfMergeList.value = rawRows.map((r) => flattenDeskLabelForMerge(r, competitionTitle.value))
462
- await nextTick()
463
- if (!deskPdfLayout.value.fitsWidth || !deskPdfLayout.value.fitsHeight) {
464
- pdfDialogVisible.value = false
465
- qiuMsg.warning(
466
- `当前桌面贴尺寸 ${deskPdfLabelSize.value.w} x ${deskPdfLabelSize.value.h} mm 已超出 ${paperSize.value.toUpperCase()}${paperOrientation.value === 'landscape' ? '横向' : '纵向'} 纸张尺寸,请调整模板尺寸或改用更大纸张`
467
- )
468
- return
469
- }
470
- if (deskPdfLayout.value.requestedCols > deskPdfLayout.value.maxCols) {
471
- pdfDialogVisible.value = false
472
- qiuMsg.warning(
473
- `${paperSize.value.toUpperCase()}${paperOrientation.value === 'landscape' ? '横向' : '纵向'} 最多每行显示 ${deskPdfLayout.value.maxCols} 个桌面贴,请调整后重试`
474
- )
475
- return
476
- }
477
- } catch {
478
- pdfMergeList.value = collectLabelRows().map((r) => flattenDeskLabelForMerge(r, competitionTitle.value))
479
- } finally {
480
- pdfLoading.value = false
481
- }
482
- }
483
-
484
- const onPdfDialogOpened = async () => {
485
- await nextTick()
486
- }
487
-
488
- const onDownloadPdf = async () => {
489
- const el = pdfExportRoot.value
490
- if (!el || !pdfMergeList.value.length) return
491
- pdfDownloading.value = true
492
- try {
493
- await nextTick()
494
- await new Promise((r) => setTimeout(r, 200))
495
- const name = `桌面贴_${competitionTitle.value || competitionId.value}.pdf`
496
- const pages = Array.from(el.querySelectorAll<HTMLElement>('.pdf-page'))
497
- await downloadPdfPagesFromElements(pages, name, {
498
- size: paperSize.value,
499
- orientation: paperOrientation.value,
500
- widthMm: deskPdfPaperSize.value.w,
501
- heightMm: deskPdfPaperSize.value.h,
502
- })
503
- } finally {
504
- pdfDownloading.value = false
505
- }
506
- }
507
-
508
- const onPrintPdf = async () => {
509
- const el = pdfExportRoot.value
510
- if (!el || !pdfMergeList.value.length) return
511
- try {
512
- await nextTick()
513
- await new Promise((r) => setTimeout(r, 200))
514
- const pages = Array.from(el.querySelectorAll<HTMLElement>('.pdf-page'))
515
- await printPdfPagesFromElements(pages, `桌面贴_${competitionTitle.value || competitionId.value}`, {
516
- size: paperSize.value,
517
- orientation: paperOrientation.value,
518
- widthMm: deskPdfPaperSize.value.w,
519
- heightMm: deskPdfPaperSize.value.h,
520
- })
521
- } catch (error) {
522
- qiuMsg.error(error instanceof Error ? error.message : '打印失败')
523
- }
524
- }
525
-
526
- onMounted(async () => {
527
- try {
528
- const res: any = await getCompetitions({ page_size: 200 })
529
- competitions.value = res.data?.data || []
530
- } catch {
531
- competitions.value = []
532
- }
533
- })
534
- watch(paperColumnsPreset, (value) => {
535
- if (isDirectSizeMode.value) return
536
- if (value > 0) {
537
- paperColumns.value = value
538
- }
539
- })
540
- watch(paperColumns, (value) => {
541
- if (isDirectSizeMode.value) {
542
- if (paperColumns.value !== 1) paperColumns.value = 1
543
- if (paperColumnsPreset.value !== 1) paperColumnsPreset.value = 1
544
- return
545
- }
546
- const normalized = Math.max(1, Number(value || 1))
547
- if (normalized !== value) {
548
- paperColumns.value = normalized
549
- return
550
- }
551
- paperColumnsPreset.value = [1, 2, 3].includes(normalized) ? normalized : 0
552
- })
553
- watch(paperSize, (value) => {
554
- if (value === 'custom') {
555
- paperOrientation.value = 'portrait'
556
- paperColumnsPreset.value = 1
557
- paperColumns.value = 1
558
- }
559
- })
560
- </script>
561
-
562
- <style scoped>
563
- .filter-card {
564
- margin-bottom: 16px;
565
- }
566
- .label-grid {
567
- display: grid;
568
- grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
569
- gap: 12px;
570
- }
571
- .label-card {
572
- border: 1px solid #dcdfe6;
573
- border-radius: 6px;
574
- padding: 12px;
575
- text-align: center;
576
- min-height: 132px;
577
- display: flex;
578
- align-items: center;
579
- justify-content: center;
580
- overflow: hidden;
581
- }
582
- .label-seat {
583
- font-size: 24px;
584
- font-weight: bold;
585
- color: #409eff;
586
- }
587
- .label-name {
588
- font-size: 14px;
589
- margin: 4px 0;
590
- }
591
- .label-school {
592
- font-size: 12px;
593
- color: #909399;
594
- }
595
- .toolbar-pdf {
596
- margin-top: 16px;
597
- }
598
- .paper-tip {
599
- margin: 0 0 12px;
600
- color: #606266;
601
- font-size: 14px;
602
- }
603
- .paper-config {
604
- display: flex;
605
- flex-direction: column;
606
- gap: 14px;
607
- }
608
- .paper-config-block {
609
- display: flex;
610
- flex-direction: column;
611
- gap: 8px;
612
- }
613
- .paper-inline-row {
614
- display: flex;
615
- align-items: center;
616
- gap: 12px;
617
- flex-wrap: wrap;
618
- }
619
- .paper-config-title {
620
- font-size: 13px;
621
- color: #606266;
622
- font-weight: 600;
623
- }
624
- .paper-config-desc {
625
- font-size: 12px;
626
- color: #909399;
627
- }
628
- .pdf-dialog-body {
629
- max-height: 78vh;
630
- overflow: auto;
631
- background: #f5f7fa;
632
- padding: 12px;
633
- border-radius: 8px;
634
- }
635
- .pdf-export-root {
636
- margin: 0 auto;
637
- }
638
- .pdf-page {
639
- margin: 0 auto 12px;
640
- background: #fff;
641
- box-sizing: border-box;
642
- display: flex;
643
- align-items: center;
644
- justify-content: center;
645
- break-after: page;
646
- }
647
- .pdf-page-grid {
648
- display: grid;
649
- align-content: center;
650
- justify-content: center;
651
- }
652
- .pdf-cell {
653
- background: #fff;
654
- border-radius: 4px;
655
- padding: 2px;
656
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
657
- }
658
- .pdf-cell.simple {
659
- text-align: center;
660
- padding: 6px;
661
- display: flex;
662
- flex-direction: column;
663
- align-items: center;
664
- justify-content: center;
665
- }
666
- .simple-seat {
667
- font-size: 20px;
668
- font-weight: 700;
669
- color: #409eff;
670
- }
671
- .simple-name {
672
- font-size: 14px;
673
- margin: 4px 0;
674
- }
675
- .simple-school {
676
- font-size: 12px;
677
- color: #909399;
678
- }
679
- .simple-no {
680
- font-size: 11px;
681
- color: #606266;
682
- }
683
- </style>