adtec-core-package 3.1.7 → 3.1.8

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.
Binary file
@@ -0,0 +1,86 @@
1
+ /* color palette from <https://github.com/vuejs/theme> */
2
+ :root {
3
+ --vt-c-white: #ffffff;
4
+ --vt-c-white-soft: #f8f8f8;
5
+ --vt-c-white-mute: #f2f2f2;
6
+
7
+ --vt-c-black: #181818;
8
+ --vt-c-black-soft: #222222;
9
+ --vt-c-black-mute: #282828;
10
+
11
+ --vt-c-indigo: #2c3e50;
12
+
13
+ --vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
14
+ --vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
15
+ --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
16
+ --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
17
+
18
+ --vt-c-text-light-1: var(--vt-c-indigo);
19
+ --vt-c-text-light-2: rgba(60, 60, 60, 0.66);
20
+ --vt-c-text-dark-1: var(--vt-c-white);
21
+ --vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
22
+ }
23
+
24
+ /* semantic color variables for this project */
25
+ :root {
26
+ --color-background: var(--vt-c-white);
27
+ --color-background-soft: var(--vt-c-white-soft);
28
+ --color-background-mute: var(--vt-c-white-mute);
29
+
30
+ --color-border: var(--vt-c-divider-light-2);
31
+ --color-border-hover: var(--vt-c-divider-light-1);
32
+
33
+ --color-heading: var(--vt-c-text-light-1);
34
+ --color-text: var(--vt-c-text-light-1);
35
+
36
+ --section-gap: 160px;
37
+ }
38
+
39
+ @media (prefers-color-scheme: dark) {
40
+ :root {
41
+ --color-background: var(--vt-c-black);
42
+ --color-background-soft: var(--vt-c-black-soft);
43
+ --color-background-mute: var(--vt-c-black-mute);
44
+
45
+ --color-border: var(--vt-c-divider-dark-2);
46
+ --color-border-hover: var(--vt-c-divider-dark-1);
47
+
48
+ --color-heading: var(--vt-c-text-dark-1);
49
+ --color-text: var(--vt-c-text-dark-2);
50
+ }
51
+ }
52
+
53
+ *,
54
+ *::before,
55
+ *::after {
56
+ box-sizing: border-box;
57
+ margin: 0;
58
+ font-weight: normal;
59
+ }
60
+
61
+ body {
62
+ min-height: 100vh;
63
+ color: var(--color-text);
64
+ background: var(--color-background);
65
+ transition:
66
+ color 0.5s,
67
+ background-color 0.5s;
68
+ line-height: 1.6;
69
+ font-family:
70
+ Inter,
71
+ -apple-system,
72
+ BlinkMacSystemFont,
73
+ 'Segoe UI',
74
+ Roboto,
75
+ Oxygen,
76
+ Ubuntu,
77
+ Cantarell,
78
+ 'Fira Sans',
79
+ 'Droid Sans',
80
+ 'Helvetica Neue',
81
+ sans-serif;
82
+ font-size: 15px;
83
+ text-rendering: optimizeLegibility;
84
+ -webkit-font-smoothing: antialiased;
85
+ -moz-osx-font-smoothing: grayscale;
86
+ }
@@ -0,0 +1,35 @@
1
+ @import './base.css';
2
+
3
+ #app {
4
+ max-width: 1280px;
5
+ margin: 0 auto;
6
+ padding: 2rem;
7
+ font-weight: normal;
8
+ }
9
+
10
+ a,
11
+ .green {
12
+ text-decoration: none;
13
+ color: hsla(160, 100%, 37%, 1);
14
+ transition: 0.4s;
15
+ padding: 3px;
16
+ }
17
+
18
+ @media (hover: hover) {
19
+ a:hover {
20
+ background-color: hsla(160, 100%, 37%, 0.2);
21
+ }
22
+ }
23
+
24
+ @media (min-width: 1024px) {
25
+ body {
26
+ display: flex;
27
+ place-items: center;
28
+ }
29
+
30
+ #app {
31
+ display: grid;
32
+ grid-template-columns: 1fr 1fr;
33
+ padding: 0 2rem;
34
+ }
35
+ }
@@ -0,0 +1,110 @@
1
+ import { Mark, mergeAttributes } from '@tiptap/core'
2
+ import { TextSelection } from '@tiptap/pm/state'
3
+
4
+ import { shortId } from '../utils/short-id'
5
+
6
+ // 书签格式 创建一个书签
7
+ export default Mark.create({
8
+ name: 'bookmark',
9
+ priority: 1000,
10
+ keepOnSplit: false,
11
+ exitable: true,
12
+ addOptions() {
13
+ return {
14
+ bookmarkName: '',
15
+ class: 'umo-editor-bookmark',
16
+ }
17
+ },
18
+ addAttributes() {
19
+ return {
20
+ bookmarkName: {
21
+ default: 'bookmarkName',
22
+ },
23
+ class: {
24
+ default: this.options.class,
25
+ },
26
+ }
27
+ },
28
+
29
+ parseHTML() {
30
+ return [
31
+ {
32
+ tag: 'bookmark',
33
+ },
34
+ ]
35
+ },
36
+
37
+ renderHTML({ HTMLAttributes }) {
38
+ return ['bookmark', mergeAttributes(this.options, HTMLAttributes), 0]
39
+ },
40
+
41
+ addCommands() {
42
+ return {
43
+ // 设置书签 若书签有选中区域数据 否则默认值为书签名称
44
+ setBookmark:
45
+ (attributes) =>
46
+ ({ chain, editor }) => {
47
+ try {
48
+ chain().setMark(this.name, attributes).run()
49
+ const { empty } = editor.state.selection
50
+ if (empty && attributes.bookmarkName) {
51
+ chain().focus().insertContent(attributes.bookmarkName).run()
52
+ }
53
+ return true
54
+ } catch (e) {
55
+ return false
56
+ }
57
+ },
58
+ focusBookmark:
59
+ (bookmarkName) =>
60
+ ({ editor, tr }) => {
61
+ if (bookmarkName) {
62
+ const element = editor.view.dom.querySelector(
63
+ `bookmark[bookmarkName="${bookmarkName}"]`,
64
+ )
65
+ if (element) {
66
+ element.scrollIntoView({
67
+ behavior: 'smooth',
68
+ block: 'center',
69
+ inline: 'nearest',
70
+ })
71
+ const pos = editor.view.posAtDOM(element, 0)
72
+ if (tr) {
73
+ tr.setSelection(new TextSelection(tr.doc.resolve(pos)))
74
+ editor.view.dispatch(tr)
75
+ editor.view.focus()
76
+ }
77
+ }
78
+ return true
79
+ } else return false
80
+ },
81
+ getAllBookmarks:
82
+ (callback) =>
83
+ ({ editor }) => {
84
+ const bookmarkData = []
85
+ try {
86
+ const alltext = editor.getHTML()
87
+ const parser = new DOMParser()
88
+ const doc = parser.parseFromString(alltext, 'text/html')
89
+ // 获取所有的 <bookmark> 元素
90
+ const bookmarks = doc.body.querySelectorAll(this.name)
91
+ const keyNode = []
92
+ Array.from(bookmarks).forEach((node) => {
93
+ if (node !== null) {
94
+ const bookName = node.getAttribute('bookmarkName')
95
+ if (bookName && !keyNode.includes(bookName)) {
96
+ keyNode.push(bookName)
97
+ bookmarkData.push({
98
+ bookmarkRowId: shortId(),
99
+ bookmarkRowName: bookName,
100
+ })
101
+ }
102
+ }
103
+ })
104
+ } catch (e) {}
105
+ callback(bookmarkData)
106
+ return true
107
+ },
108
+ }
109
+ },
110
+ })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adtec-core-package",
3
- "version": "3.1.7",
3
+ "version": "3.1.8",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "exports": {
@@ -1,13 +1,26 @@
1
1
  <template>
2
- <div ref="scrollRef" class="excel-js-viewer">
3
- <div v-if="loading" class="excel-loading">正在解析表格…</div>
4
- <div v-else-if="sheets.length === 0" class="excel-empty">表格为空或无法解析</div>
5
- <div v-else class="excel-body">
6
- <div class="excel-sheet-name">{{ activeSheet?.name }}</div>
7
- <div class="excel-scale-host" :style="scaleHostStyle">
2
+ <div class="excel-js-viewer">
3
+ <div ref="scrollRef" class="excel-grid-scroll">
4
+ <div v-if="loading" class="excel-loading">正在解析表格…</div>
5
+ <div v-else-if="sheets.length === 0" class="excel-empty">表格为空或无法解析</div>
6
+ <div v-else class="excel-scale-host" :style="scaleHostStyle">
8
7
  <table class="excel-table" :style="tableStyle">
8
+ <thead>
9
+ <tr>
10
+ <th class="excel-corner" />
11
+ <th
12
+ v-for="col in columnHeaders"
13
+ :key="col"
14
+ class="excel-col-header"
15
+ :style="{ minWidth: `${getColWidth(col)}px`, width: `${getColWidth(col)}px` }"
16
+ >
17
+ {{ toExcelColumnLabel(col) }}
18
+ </th>
19
+ </tr>
20
+ </thead>
9
21
  <tbody>
10
22
  <tr v-for="(row, rowIndex) in activeSheet?.rows || []" :key="rowIndex">
23
+ <th class="excel-row-header">{{ (row[0]?.row ?? rowIndex) + 1 }}</th>
11
24
  <td
12
25
  v-for="cell in row"
13
26
  :key="`${cell.row}-${cell.col}`"
@@ -16,6 +29,8 @@
16
29
  :data-row="cell.row"
17
30
  :data-col="cell.col"
18
31
  :class="cellClass(cell)"
32
+ :style="cellInlineStyle(cell)"
33
+ @click="selectCell(cell)"
19
34
  >
20
35
  {{ cell.value }}
21
36
  </td>
@@ -24,6 +39,24 @@
24
39
  </table>
25
40
  </div>
26
41
  </div>
42
+
43
+ <div v-if="sheets.length > 1" class="excel-sheet-tabs">
44
+ <button
45
+ v-for="(sheet, index) in sheets"
46
+ :key="sheet.name + index"
47
+ type="button"
48
+ class="excel-sheet-tab"
49
+ :class="{ 'is-active': currentSheet === index + 1 }"
50
+ @click="switchSheet(index + 1)"
51
+ >
52
+ {{ sheet.name }}
53
+ </button>
54
+ </div>
55
+
56
+ <div v-if="sheets.length > 0 && !loading" class="excel-zoom-float">
57
+ <button type="button" class="excel-zoom-btn" title="放大" @click="emit('zoom-in')">+</button>
58
+ <button type="button" class="excel-zoom-btn" title="缩小" @click="emit('zoom-out')">−</button>
59
+ </div>
27
60
  </div>
28
61
  </template>
29
62
 
@@ -33,8 +66,10 @@ import {
33
66
  type ExcelPreviewCell,
34
67
  type ExcelPreviewSheet,
35
68
  type ExcelSearchMatch,
69
+ cellStyleToCss,
36
70
  parseExcelWorkbook,
37
71
  searchExcelSheets,
72
+ toExcelColumnLabel,
38
73
  } from '../../utils/excelPreviewUtil.ts'
39
74
 
40
75
  const props = withDefaults(
@@ -49,6 +84,8 @@ const emit = defineEmits<{
49
84
  loaded: []
50
85
  error: [err?: unknown]
51
86
  'page-change': [page: number]
87
+ 'zoom-in': []
88
+ 'zoom-out': []
52
89
  }>()
53
90
 
54
91
  const scrollRef = ref<HTMLElement | null>(null)
@@ -57,10 +94,19 @@ const loading = ref(false)
57
94
  const currentSheet = ref(1)
58
95
  const searchMatches = ref<ExcelSearchMatch[]>([])
59
96
  const activeMatchIndex = ref(-1)
97
+ const selectedCell = ref<{ row: number; col: number } | null>(null)
60
98
 
61
99
  const totalPages = ref(0)
62
100
  const activeSheet = computed(() => sheets.value[currentSheet.value - 1])
63
101
 
102
+ const columnHeaders = computed(() => {
103
+ const sheet = activeSheet.value
104
+ if (!sheet) return []
105
+ const cols: number[] = []
106
+ for (let c = sheet.startCol; c <= sheet.endCol; c++) cols.push(c)
107
+ return cols
108
+ })
109
+
64
110
  const userScale = computed(() => props.scale ?? 1)
65
111
  const tableStyle = computed(() => ({
66
112
  transform: `scale(${userScale.value})`,
@@ -70,11 +116,11 @@ const scaleHostStyle = computed(() => {
70
116
  const sheet = activeSheet.value
71
117
  if (!sheet) return {}
72
118
  const rowCount = sheet.rows.length || 1
73
- const colCount = Math.max(...sheet.rows.map((row) => row.length), 1)
74
- const baseWidth = colCount * 96 + 32
75
- const baseHeight = rowCount * 28 + 48
119
+ const colCount = columnHeaders.value.length || 1
120
+ const baseWidth = columnHeaders.value.reduce((sum, col) => sum + getColWidth(col), 42)
121
+ const baseHeight = rowCount * 24 + 28
76
122
  return {
77
- width: `${baseWidth * userScale.value}px`,
123
+ width: `${Math.max(baseWidth, colCount * 72 + 42) * userScale.value}px`,
78
124
  height: `${baseHeight * userScale.value}px`,
79
125
  }
80
126
  })
@@ -83,6 +129,8 @@ const activeMatch = computed(() =>
83
129
  activeMatchIndex.value >= 0 ? searchMatches.value[activeMatchIndex.value] : null,
84
130
  )
85
131
 
132
+ const getColWidth = (col: number) => activeSheet.value?.colWidths[col] ?? 72
133
+
86
134
  const cellClass = (cell: ExcelPreviewCell) => {
87
135
  const classes: string[] = []
88
136
  const hasMatch = searchMatches.value.some(
@@ -97,8 +145,32 @@ const cellClass = (cell: ExcelPreviewCell) => {
97
145
  ) {
98
146
  classes.push('excel-cell--active')
99
147
  }
100
- if (classes.length === 0) return undefined
101
- return classes
148
+ if (
149
+ selectedCell.value &&
150
+ selectedCell.value.row === cell.row &&
151
+ selectedCell.value.col === cell.col
152
+ ) {
153
+ classes.push('excel-cell--selected')
154
+ }
155
+ return classes.length > 0 ? classes : undefined
156
+ }
157
+
158
+ const cellInlineStyle = (cell: ExcelPreviewCell) => {
159
+ const css = cellStyleToCss(cell.style)
160
+ if (cell.width) {
161
+ css.minWidth = `${cell.width}px`
162
+ css.width = `${cell.width}px`
163
+ css.maxWidth = `${Math.max(cell.width, 280)}px`
164
+ }
165
+ return css
166
+ }
167
+
168
+ const selectCell = (cell: ExcelPreviewCell) => {
169
+ selectedCell.value = { row: cell.row, col: cell.col }
170
+ }
171
+
172
+ const switchSheet = async (sheetIndex: number) => {
173
+ await scrollToPage(sheetIndex)
102
174
  }
103
175
 
104
176
  const resetViewer = () => {
@@ -107,6 +179,7 @@ const resetViewer = () => {
107
179
  currentSheet.value = 1
108
180
  searchMatches.value = []
109
181
  activeMatchIndex.value = -1
182
+ selectedCell.value = null
110
183
  loading.value = false
111
184
  }
112
185
 
@@ -116,6 +189,7 @@ const loadWorkbook = async (src: Blob | ArrayBuffer) => {
116
189
  sheets.value = await parseExcelWorkbook(src)
117
190
  totalPages.value = sheets.value.length
118
191
  currentSheet.value = 1
192
+ selectedCell.value = null
119
193
  emit('page-change', 1)
120
194
  emit('loaded')
121
195
  } catch (err) {
@@ -138,15 +212,17 @@ const scrollToCell = async (match: ExcelSearchMatch) => {
138
212
  ) as HTMLElement | null
139
213
  if (!container || !cellEl) return
140
214
  const top = cellEl.offsetTop - container.clientHeight * 0.3
141
- container.scrollTo({ top: Math.max(0, top), behavior: 'smooth' })
215
+ const left = cellEl.offsetLeft - container.clientWidth * 0.2
216
+ container.scrollTo({ top: Math.max(0, top), left: Math.max(0, left), behavior: 'smooth' })
142
217
  }
143
218
 
144
219
  const scrollToPage = async (pageNum: number) => {
145
220
  const target = Math.min(Math.max(1, pageNum), totalPages.value || 1)
146
221
  currentSheet.value = target
222
+ selectedCell.value = null
147
223
  emit('page-change', target)
148
224
  await nextTick()
149
- scrollRef.value?.scrollTo({ top: 0, behavior: 'smooth' })
225
+ scrollRef.value?.scrollTo({ top: 0, left: 0, behavior: 'smooth' })
150
226
  }
151
227
 
152
228
  const runSearch = async (query: string) => {
@@ -178,7 +254,7 @@ const gotoSearchMatch = async (direction: 1 | -1) => {
178
254
  }
179
255
 
180
256
  const refitLayout = () => {
181
- scrollRef.value?.scrollTo({ top: 0 })
257
+ scrollRef.value?.scrollTo({ top: 0, left: 0 })
182
258
  }
183
259
 
184
260
  watch(
@@ -202,51 +278,94 @@ defineExpose({
202
278
 
203
279
  <style scoped lang="scss">
204
280
  .excel-js-viewer {
281
+ position: relative;
282
+ display: flex;
205
283
  flex: 1;
284
+ flex-direction: column;
206
285
  width: 100%;
207
286
  min-height: 0;
208
- overflow: auto;
209
- background: #f5f5f5;
210
- }
211
-
212
- .excel-body {
213
- min-height: 100%;
214
- padding: 12px;
287
+ background: #e6e6e6;
215
288
  }
216
289
 
217
- .excel-sheet-name {
218
- margin: 0 0 8px;
219
- font-size: 13px;
220
- font-weight: 600;
221
- color: #374151;
290
+ .excel-grid-scroll {
291
+ flex: 1;
292
+ min-height: 0;
293
+ overflow: auto;
294
+ background: #e6e6e6;
222
295
  }
223
296
 
224
297
  .excel-scale-host {
225
298
  display: inline-block;
226
299
  min-width: 100%;
300
+ padding: 0;
227
301
  }
228
302
 
229
303
  .excel-table {
230
- border-collapse: collapse;
304
+ border-collapse: separate;
305
+ border-spacing: 0;
231
306
  background: #fff;
232
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
233
- font-size: 13px;
234
- line-height: 1.4;
307
+ font-size: 12px;
308
+ line-height: 1.35;
309
+ }
310
+
311
+ .excel-corner,
312
+ .excel-col-header,
313
+ .excel-row-header {
314
+ position: sticky;
315
+ z-index: 2;
316
+ background: #f0f0f0;
317
+ color: #444;
318
+ font-weight: 400;
319
+ text-align: center;
320
+ user-select: none;
321
+ }
322
+
323
+ .excel-corner {
324
+ top: 0;
325
+ left: 0;
326
+ z-index: 4;
327
+ width: 42px;
328
+ min-width: 42px;
329
+ border-right: 1px solid #c7c7c7;
330
+ border-bottom: 1px solid #c7c7c7;
331
+ }
332
+
333
+ .excel-col-header {
334
+ top: 0;
335
+ z-index: 3;
336
+ min-width: 72px;
337
+ height: 22px;
338
+ padding: 2px 6px;
339
+ border-right: 1px solid #d4d4d4;
340
+ border-bottom: 1px solid #c7c7c7;
341
+ }
342
+
343
+ .excel-row-header {
344
+ left: 0;
345
+ z-index: 2;
346
+ width: 42px;
347
+ min-width: 42px;
348
+ padding: 2px 4px;
349
+ border-right: 1px solid #c7c7c7;
350
+ border-bottom: 1px solid #d4d4d4;
235
351
  }
236
352
 
237
353
  .excel-table td {
238
354
  min-width: 72px;
239
- max-width: 280px;
240
- padding: 6px 10px;
241
- border: 1px solid #e5e7eb;
355
+ max-width: 320px;
356
+ padding: 2px 6px;
357
+ border-right: 1px solid #d4d4d4;
358
+ border-bottom: 1px solid #d4d4d4;
242
359
  color: #111827;
243
- vertical-align: top;
360
+ vertical-align: middle;
244
361
  word-break: break-word;
245
362
  white-space: pre-wrap;
363
+ background: #fff;
364
+ cursor: cell;
246
365
  }
247
366
 
248
- .excel-table tr:nth-child(even) td {
249
- background: #fafafa;
367
+ .excel-cell--selected {
368
+ box-shadow: inset 0 0 0 2px #217346 !important;
250
369
  }
251
370
 
252
371
  .excel-cell--match {
@@ -258,6 +377,61 @@ defineExpose({
258
377
  box-shadow: inset 0 0 0 2px rgba(255, 87, 34, 0.75);
259
378
  }
260
379
 
380
+ .excel-sheet-tabs {
381
+ display: flex;
382
+ gap: 2px;
383
+ align-items: stretch;
384
+ min-height: 30px;
385
+ padding: 0 6px;
386
+ overflow-x: auto;
387
+ background: #f0f0f0;
388
+ border-top: 1px solid #c7c7c7;
389
+ }
390
+
391
+ .excel-sheet-tab {
392
+ flex-shrink: 0;
393
+ padding: 4px 14px;
394
+ border: none;
395
+ border-top: 2px solid transparent;
396
+ background: #e2e2e2;
397
+ color: #333;
398
+ font-size: 12px;
399
+ cursor: pointer;
400
+
401
+ &.is-active {
402
+ background: #fff;
403
+ border-top-color: #217346;
404
+ font-weight: 600;
405
+ }
406
+ }
407
+
408
+ .excel-zoom-float {
409
+ position: absolute;
410
+ right: 16px;
411
+ bottom: 42px;
412
+ z-index: 5;
413
+ display: flex;
414
+ flex-direction: column;
415
+ gap: 8px;
416
+ }
417
+
418
+ .excel-zoom-btn {
419
+ width: 34px;
420
+ height: 34px;
421
+ border: none;
422
+ border-radius: 50%;
423
+ background: rgba(255, 255, 255, 0.92);
424
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
425
+ color: #333;
426
+ font-size: 20px;
427
+ line-height: 1;
428
+ cursor: pointer;
429
+
430
+ &:hover {
431
+ background: #fff;
432
+ }
433
+ }
434
+
261
435
  .excel-loading,
262
436
  .excel-empty {
263
437
  display: flex;
@@ -11,7 +11,7 @@
11
11
  :search-index="searchMatchIndex"
12
12
  :search-total="searchMatchTotal"
13
13
  :show-search="showSearch"
14
- :show-page-nav="showPageNav"
14
+ :show-page-nav="showPageNav && officeType !== 'excel'"
15
15
  :show-zoom="showZoom"
16
16
  :show-download="showDownload"
17
17
  :show-refresh="showRefresh"
@@ -55,6 +55,8 @@
55
55
  @loaded="handleRendered"
56
56
  @error="handleError"
57
57
  @page-change="previewCurrentPage = $event"
58
+ @zoom-in="zoomIn"
59
+ @zoom-out="zoomOut"
58
60
  />
59
61
  <docx-js-viewer
60
62
  v-else-if="officeType === 'docx' && !previewAsPdf"