@yxhl/specter-pui-vtk 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.
Files changed (49) hide show
  1. package/README.md +122 -0
  2. package/dist/specter-pui-vtk.css +1 -0
  3. package/dist/specter-pui.es.js +3289 -0
  4. package/dist/specter-pui.es.js.map +1 -0
  5. package/dist/specter-pui.umd.js +2 -0
  6. package/dist/specter-pui.umd.js.map +1 -0
  7. package/package.json +65 -0
  8. package/src/assets/css/globals.scss +250 -0
  9. package/src/assets/css/index.scss +271 -0
  10. package/src/assets/css/settings.scss +10 -0
  11. package/src/assets/css/variables.scss +0 -0
  12. package/src/assets/icon/logo.svg +9 -0
  13. package/src/assets/img/background.png +0 -0
  14. package/src/assets/img/dtx.png +0 -0
  15. package/src/commons/filters/dictionary.js +75 -0
  16. package/src/commons/filters/format.js +112 -0
  17. package/src/commons/filters/mask.js +25 -0
  18. package/src/commons/index.js +17 -0
  19. package/src/commons/request.js +89 -0
  20. package/src/commons/storage.js +41 -0
  21. package/src/commons/themes.js +153 -0
  22. package/src/commons/validation.js +72 -0
  23. package/src/components/README.md +35 -0
  24. package/src/components/assembly/VtkArea.vue +259 -0
  25. package/src/components/assembly/VtkCheckbox.vue +168 -0
  26. package/src/components/assembly/VtkCount.vue +403 -0
  27. package/src/components/assembly/VtkDatePicker.vue +326 -0
  28. package/src/components/assembly/VtkEmpty.vue +107 -0
  29. package/src/components/assembly/VtkFab.vue +78 -0
  30. package/src/components/assembly/VtkFormItem.vue +166 -0
  31. package/src/components/assembly/VtkImg.vue +372 -0
  32. package/src/components/assembly/VtkPage.vue +156 -0
  33. package/src/components/assembly/VtkPdf.vue +424 -0
  34. package/src/components/assembly/VtkProj.vue +539 -0
  35. package/src/components/assembly/VtkRadio.vue +82 -0
  36. package/src/components/assembly/VtkSearch.vue +145 -0
  37. package/src/components/assembly/VtkSelect.vue +104 -0
  38. package/src/components/assembly/VtkStepper.vue +160 -0
  39. package/src/components/message/alert.vue +31 -0
  40. package/src/components/message/confirm.vue +44 -0
  41. package/src/components/message/index.js +55 -0
  42. package/src/components/message/loading.vue +33 -0
  43. package/src/components/message/prompt.vue +57 -0
  44. package/src/components/message/toast.vue +45 -0
  45. package/src/components/message/vtkMessage.vue +27 -0
  46. package/src/composables/useMixins.js +2 -0
  47. package/src/composables/usePage.js +311 -0
  48. package/src/index.js +109 -0
  49. package/src/stores/message.js +79 -0
@@ -0,0 +1,156 @@
1
+ <template>
2
+ <div class="m_pagination">
3
+ <VRow v-if="pageDatas.rowCount > 0" class="pagination box" align="center">
4
+ <span class="page-total d-none d-md-flex align-center">共{{ pageDatas.rowCount }}条</span>
5
+ <VSelect
6
+ v-model="pagination.limit"
7
+ :items="pageSizeObjs"
8
+ variant="solo"
9
+ density="compact"
10
+ :menu-props="{ offsetY: true }"
11
+ hide-details
12
+ class="page-select ml-2 d-none d-md-flex"
13
+ @update:model-value="pageSizeChange()"
14
+ />
15
+ <span class="my-2" style="font-size: 12px">
16
+ <v-pagination
17
+ v-model="pageDatas.pageno"
18
+ :total-visible="6"
19
+ :length="pageDatas.pageCount"
20
+ @update:model-value="pageChange()"
21
+ />
22
+ </span>
23
+ <span class="page-total ml-4 d-none d-md-flex align-center">到</span>
24
+ <VTextField
25
+ v-model="pagination.pageno"
26
+ variant="solo"
27
+ density="compact"
28
+ hide-details
29
+ class="page-go ml-2 d-none d-md-flex"
30
+ @keyup.enter="toPage()"
31
+ />
32
+ <span class="page-total ml-2 d-none d-md-flex align-center">页</span>
33
+ </VRow>
34
+ </div>
35
+ </template>
36
+
37
+ <script setup>
38
+ import { ref, reactive, computed, watch, onMounted } from 'vue';
39
+
40
+ const props = defineProps({
41
+ pageData: {
42
+ type: Object,
43
+ default: () => ({})
44
+ },
45
+ pageSizes: {
46
+ type: Array,
47
+ default: () => [10, 20, 30]
48
+ }
49
+ });
50
+
51
+ const emit = defineEmits(['pageChange']);
52
+
53
+ const pagination = reactive({
54
+ pageno: 1,
55
+ limit: 10
56
+ });
57
+
58
+ const pageSizeObjs = ref([]);
59
+ const pageDatas = ref({ ...props.pageData });
60
+
61
+ const paginationChange = computed(() => pageDatas.value.pageno);
62
+
63
+ watch(paginationChange, () => {
64
+ pagination.pageno = pageDatas.value.pageno;
65
+ });
66
+
67
+ watch(() => props.pageData, (newVal) => {
68
+ pageDatas.value = { ...newVal };
69
+ if (newVal.rowCount < 1 && newVal.rows > 0) {
70
+ pageDatas.value.pageno = newVal.pageno - 1;
71
+ pageChange();
72
+ }
73
+ }, { deep: true });
74
+
75
+ onMounted(() => {
76
+ pageSizeFormat();
77
+ });
78
+
79
+ const toPage = () => {
80
+ const re = /^[0-9]+$/;
81
+ if (re.test(pagination.pageno) && pagination.pageno <= pageDatas.value.pageCount) {
82
+ pageDatas.value.pageno = parseInt(pagination.pageno);
83
+ pageChange();
84
+ } else {
85
+ pagination.pageno = pageDatas.value.pageno;
86
+ }
87
+ };
88
+
89
+ const pageSizeChange = () => {
90
+ pageDatas.value.pageCount = 1;
91
+ pageChange();
92
+ };
93
+
94
+ const pageSizeFormat = () => {
95
+ const temp = props.pageSizes;
96
+ temp.forEach((value) => {
97
+ const obj = {
98
+ title: value + "条/页",
99
+ value: value
100
+ };
101
+ pageSizeObjs.value.push(obj);
102
+ });
103
+ pagination.limit = temp[0];
104
+ };
105
+
106
+ const pageChange = () => {
107
+ const page = {
108
+ pageno: pageDatas.value.pageno,
109
+ limit: pagination.limit
110
+ };
111
+ emit('pageChange', page);
112
+ };
113
+ </script>
114
+
115
+ <style lang="scss" scoped>
116
+ .mt-1 {
117
+ margin-top: 11px !important;
118
+ }
119
+ .my-5 {
120
+ margin-top: 20px !important;
121
+ margin-bottom: 20px !important;
122
+ }
123
+ .m_pagination {
124
+ width: 100%;
125
+ min-height: 60px;
126
+ .box {
127
+ position: absolute;
128
+ right: 16px;
129
+ }
130
+ }
131
+ .page-total {
132
+ display: flex;
133
+ align-items: center;
134
+ height: 40px; // 与 v-select 高度保持一致
135
+ }
136
+ .page-select {
137
+ max-width: 120px;
138
+ :deep(.v-input__control) {
139
+ min-height: 40px !important;
140
+ height: 40px !important;
141
+ }
142
+ :deep(.v-field) {
143
+ height: 40px !important;
144
+ }
145
+ }
146
+ .page-go {
147
+ max-width: 80px;
148
+ :deep(.v-input__control) {
149
+ min-height: 40px !important;
150
+ height: 40px !important;
151
+ }
152
+ :deep(.v-field) {
153
+ height: 40px !important;
154
+ }
155
+ }
156
+ </style>
@@ -0,0 +1,424 @@
1
+ <template>
2
+ <div class="vtk-pdf-container">
3
+ <VCard
4
+ :class="['vtk-pdf-card', { 'vtk-pdf-card--preview': preview }]"
5
+ @click="openPreview"
6
+ >
7
+ <!-- PDF封面或默认图标 -->
8
+ <div class="vtk-pdf-cover" :style="{ aspectRatio: aspectRatio }">
9
+ <VIcon size="48" color="red">mdi-file-pdf-box</VIcon>
10
+ <div class="vtk-pdf-label">PDF</div>
11
+ </div>
12
+
13
+ <!-- PDF标题和描述 -->
14
+ <div class="vtk-pdf-info" v-if="showTitle || showDescription">
15
+ <VCardTitle v-if="showTitle" class="vtk-pdf-title text-subtitle-2 pa-2">
16
+ {{ title }}
17
+ </VCardTitle>
18
+ <v-card-subtitle v-if="showDescription" class="vtk-pdf-description text-caption pa-2">
19
+ {{ description }}
20
+ </v-card-subtitle>
21
+ </div>
22
+ </VCard>
23
+
24
+ <!-- 使用 v-dialog 的PDF预览 -->
25
+ <VDialog
26
+ v-model="showPreview"
27
+ max-width="90%"
28
+ fullscreen
29
+ @click:outside="closePreview"
30
+ >
31
+ <VCard class="pdf-viewer-card">
32
+ <v-toolbar dark color="red darken-1">
33
+ <VBtn icon dark @click="closePreview">
34
+ <VIcon>mdi-close</VIcon>
35
+ </VBtn>
36
+ <v-toolbar-title>{{ title }}</v-toolbar-title>
37
+ <VSpacer></VSpacer>
38
+ <v-toolbar-items>
39
+ <VBtn icon dark @click="downloadPdf">
40
+ <VIcon>mdi-download</VIcon>
41
+ </VBtn>
42
+ </v-toolbar-items>
43
+ </v-toolbar>
44
+
45
+ <div class="pdf-viewer-content">
46
+ <div class="pdf-container" ref="pdfContainer">
47
+ <!-- PDF内容将通过PDF.js渲染 -->
48
+ <div id="pdf-viewer"></div>
49
+ </div>
50
+ </div>
51
+
52
+ <div class="pdf-viewer-footer" v-if="pdfLoaded">
53
+ <VRow align="center" justify="center">
54
+ <VCol cols="12" sm="6" class="d-flex justify-center">
55
+ <VBtn icon @click="prevPage" :disabled="currentPage <= 1">
56
+ <VIcon>mdi-chevron-left</VIcon>
57
+ </VBtn>
58
+ <div class="mx-4">
59
+ <span>第 {{ currentPage }} 页 / 共 {{ pageCount }} 页</span>
60
+ </div>
61
+ <VBtn icon @click="nextPage" :disabled="currentPage >= pageCount">
62
+ <VIcon>mdi-chevron-right</VIcon>
63
+ </VBtn>
64
+ </VCol>
65
+ <VCol cols="12" sm="6" class="d-flex justify-center">
66
+ <VSlider
67
+ v-model="currentPage"
68
+ :min="1"
69
+ :max="pageCount"
70
+ hide-details
71
+ class="mx-4"
72
+ ></VSlider>
73
+ </VCol>
74
+ </VRow>
75
+ </div>
76
+
77
+ <div class="pdf-loading" v-if="loading">
78
+ <v-progress-circular indeterminate color="red"></v-progress-circular>
79
+ <p class="mt-2">正在加载PDF...</p>
80
+ </div>
81
+ </VCard>
82
+ </VDialog>
83
+ </div>
84
+ </template>
85
+
86
+ <script setup>
87
+ import { ref, computed, onMounted, onBeforeUnmount } from 'vue';
88
+ // import * as pdfjsLib from 'pdfjs-dist';
89
+ // 注意:在实际项目中,您需要安装 pdfjs-dist 库
90
+ // npm install pdfjs-dist
91
+ // import * as pdfjsLib from 'pdfjs-dist';
92
+
93
+ defineOptions({
94
+ name: 'VtkPdf',
95
+ inheritAttrs: true
96
+ });
97
+
98
+ const props = defineProps({
99
+ src: {
100
+ type: String,
101
+ default: null,
102
+ },
103
+ preview: {
104
+ type: Boolean,
105
+ default: true
106
+ },
107
+ title: {
108
+ type: String,
109
+ default: ''
110
+ },
111
+ description: {
112
+ type: String,
113
+ default: ''
114
+ },
115
+ // 是否显示标题
116
+ showTitle: {
117
+ type: Boolean,
118
+ default: true
119
+ },
120
+ // 是否显示描述
121
+ showDescription: {
122
+ type: Boolean,
123
+ default: true
124
+ },
125
+ // 封面宽高比
126
+ aspectRatio: {
127
+ type: [String, Number],
128
+ default: 16/9
129
+ }
130
+ });
131
+
132
+ // 预览状态
133
+ const showPreview = ref(false);
134
+ // PDF相关状态
135
+ const loading = ref(false);
136
+ const pdfLoaded = ref(false);
137
+ const currentPage = ref(1);
138
+ const pageCount = ref(0);
139
+ const pdfContainer = ref(null);
140
+
141
+ // PDF对象(实际使用时需要引入pdfjsLib)
142
+ let pdfDoc = null;
143
+
144
+ // 使用计算属性处理带token的URL
145
+ const srcWithToken = computed(() => {
146
+ if (!props.src) return props.src;
147
+
148
+ try {
149
+ // 首先尝试使用 $vtk.storage
150
+ if (window.$vtk && typeof window.$vtk.storage?.get === 'function') {
151
+ const token = window.$vtk.storage.get('token');
152
+ return token ? `${props.src}?stoken=${token}` : props.src;
153
+ }
154
+ // 如果 $vtk 不存在,尝试从 localStorage 获取 token
155
+ else {
156
+ const token = localStorage.getItem('token');
157
+ return token ? `${props.src}?stoken=${token}` : props.src;
158
+ }
159
+ } catch (error) {
160
+ // 发生错误时返回原始src
161
+ console.warn('VtkPdf: Failed to get token, using src without token', error);
162
+ return props.src;
163
+ }
164
+ });
165
+
166
+ // 打开预览
167
+ const openPreview = () => {
168
+ if (props.preview) {
169
+ showPreview.value = true;
170
+ // 延迟加载PDF以确保DOM已准备好
171
+ setTimeout(() => {
172
+ loadPdf();
173
+ }, 300);
174
+ }
175
+ };
176
+
177
+ // 关闭预览
178
+ const closePreview = () => {
179
+ showPreview.value = false;
180
+ // 清理PDF对象
181
+ pdfDoc = null;
182
+ pdfLoaded.value = false;
183
+ currentPage.value = 1;
184
+ pageCount.value = 0;
185
+ };
186
+
187
+ // 加载PDF(示例实现,实际使用需要PDF.js库)
188
+ const loadPdf = async () => {
189
+ if (!srcWithToken.value) return;
190
+
191
+ loading.value = true;
192
+
193
+ try {
194
+ // 模拟PDF加载(实际实现需要使用PDF.js)
195
+ /*
196
+ // 实际使用时的代码示例:
197
+ pdfjsLib.GlobalWorkerOptions.workerSrc = '//cdnjs.cloudflare.com/ajax/libs/pdf.js/2.11.338/pdf.worker.min.js';
198
+
199
+ const loadingTask = pdfjsLib.getDocument(srcWithToken.value);
200
+ pdfDoc = await loadingTask.promise;
201
+ pageCount.value = pdfDoc.numPages;
202
+ pdfLoaded.value = true;
203
+
204
+ // 渲染第一页
205
+ renderPage(currentPage.value);
206
+ */
207
+
208
+ // 模拟加载过程
209
+ await new Promise(resolve => setTimeout(resolve, 1000));
210
+ pageCount.value = 10; // 模拟10页PDF
211
+ pdfLoaded.value = true;
212
+ } catch (error) {
213
+ console.error('加载PDF失败:', error);
214
+ } finally {
215
+ loading.value = false;
216
+ }
217
+ };
218
+
219
+ // 渲染PDF页面(示例实现)
220
+ const renderPage = async (pageNum) => {
221
+ if (!pdfDoc || !pdfContainer.value) return;
222
+
223
+ try {
224
+ /*
225
+ // 实际使用时的代码示例:
226
+ const page = await pdfDoc.getPage(pageNum);
227
+ const viewport = page.getViewport({ scale: 1.5 });
228
+
229
+ // 准备canvas
230
+ const canvas = document.createElement('canvas');
231
+ const context = canvas.getContext('2d');
232
+ canvas.height = viewport.height;
233
+ canvas.width = viewport.width;
234
+
235
+ // 清空容器
236
+ pdfContainer.value.innerHTML = '';
237
+ pdfContainer.value.appendChild(canvas);
238
+
239
+ // 渲染页面
240
+ const renderContext = {
241
+ canvasContext: context,
242
+ viewport: viewport
243
+ };
244
+ await page.render(renderContext).promise;
245
+ */
246
+ } catch (error) {
247
+ console.error('渲染PDF页面失败:', error);
248
+ }
249
+ };
250
+
251
+ // 上一页
252
+ const prevPage = () => {
253
+ if (currentPage.value > 1) {
254
+ currentPage.value--;
255
+ renderPage(currentPage.value);
256
+ }
257
+ };
258
+
259
+ // 下一页
260
+ const nextPage = () => {
261
+ if (currentPage.value < pageCount.value) {
262
+ currentPage.value++;
263
+ renderPage(currentPage.value);
264
+ }
265
+ };
266
+
267
+ // 下载PDF
268
+ const downloadPdf = async () => {
269
+ if (!srcWithToken.value) return;
270
+
271
+ try {
272
+ // 获取PDF文件的二进制数据
273
+ const response = await fetch(srcWithToken.value);
274
+ if (!response.ok) {
275
+ throw new Error(`HTTP error! status: ${response.status}`);
276
+ }
277
+
278
+ const blob = await response.blob();
279
+ const fileName = props.title && props.title.trim() !== ''
280
+ ? `${props.title}.pdf`
281
+ : 'document.pdf';
282
+
283
+ // 创建下载链接
284
+ const url = window.URL.createObjectURL(blob);
285
+ const link = document.createElement('a');
286
+ link.href = url;
287
+ link.download = fileName;
288
+ document.body.appendChild(link);
289
+ link.click();
290
+
291
+ // 清理
292
+ document.body.removeChild(link);
293
+ window.URL.revokeObjectURL(url);
294
+ } catch (error) {
295
+ console.error('下载PDF失败:', error);
296
+ // 备用方案:在新窗口打开
297
+ window.open(srcWithToken.value, '_blank');
298
+ }
299
+ };
300
+
301
+ // 监听页码变化
302
+ const handlePageChange = (newPage) => {
303
+ if (newPage >= 1 && newPage <= pageCount.value) {
304
+ renderPage(newPage);
305
+ }
306
+ };
307
+
308
+ // 监听 currentPage 变化
309
+ // 在实际实现中,您需要使用 watch 监听 currentPage 变化
310
+ // watch(currentPage, handlePageChange);
311
+
312
+ // 组件销毁前清理
313
+ onBeforeUnmount(() => {
314
+ pdfDoc = null;
315
+ });
316
+ </script>
317
+
318
+ <style lang="scss" scoped>
319
+ .vtk-pdf-container {
320
+ position: relative;
321
+ display: inline-block;
322
+ width: 100%;
323
+ height: 100%;
324
+ }
325
+
326
+ .vtk-pdf-card {
327
+ height: 100%;
328
+ }
329
+
330
+ .vtk-pdf-card--preview {
331
+ cursor: pointer;
332
+ transition: transform 0.2s ease-in-out;
333
+ }
334
+
335
+ .vtk-pdf-card--preview:hover {
336
+ transform: translateY(-5px);
337
+ }
338
+
339
+ .vtk-pdf-cover {
340
+ display: flex;
341
+ flex-direction: column;
342
+ align-items: center;
343
+ justify-content: center;
344
+ background-color: #f5f5f5;
345
+ border: 1px solid #ddd;
346
+ width: 100%;
347
+ }
348
+
349
+ .vtk-pdf-label {
350
+ margin-top: 8px;
351
+ font-weight: 500;
352
+ color: #666;
353
+ }
354
+
355
+ .vtk-pdf-info {
356
+ padding: 0;
357
+ }
358
+
359
+ .vtk-pdf-title {
360
+ word-break: break-word;
361
+ }
362
+
363
+ .vtk-pdf-description {
364
+ word-break: break-word;
365
+ }
366
+
367
+ /* PDF查看器样式 */
368
+ .pdf-viewer-card {
369
+ background-color: #e0e0e0;
370
+ color: #333;
371
+ }
372
+
373
+ .pdf-viewer-content {
374
+ display: flex;
375
+ align-items: center;
376
+ justify-content: center;
377
+ height: calc(100vh - 150px);
378
+ position: relative;
379
+ background-color: #555;
380
+ overflow: auto;
381
+ }
382
+
383
+ .pdf-container {
384
+ display: flex;
385
+ align-items: center;
386
+ justify-content: center;
387
+ width: 100%;
388
+ height: 100%;
389
+ }
390
+
391
+ #pdf-viewer {
392
+ max-width: 100%;
393
+ max-height: 100%;
394
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
395
+ background-color: white;
396
+ /* 实际PDF内容将在此处渲染 */
397
+ display: flex;
398
+ align-items: center;
399
+ justify-content: center;
400
+ min-height: 500px;
401
+ min-width: 400px;
402
+ }
403
+
404
+ .pdf-viewer-footer {
405
+ padding: 16px;
406
+ background-color: #f5f5f5;
407
+ border-top: 1px solid #ddd;
408
+ }
409
+
410
+ .pdf-loading {
411
+ position: absolute;
412
+ top: 50%;
413
+ left: 50%;
414
+ transform: translate(-50%, -50%);
415
+ display: flex;
416
+ flex-direction: column;
417
+ align-items: center;
418
+ justify-content: center;
419
+ background-color: rgba(255, 255, 255, 0.9);
420
+ padding: 20px;
421
+ border-radius: 8px;
422
+ z-index: 100;
423
+ }
424
+ </style>