adtec-core-package 3.0.3 → 3.0.5

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.
@@ -1,8 +1,17 @@
1
+ import { ref } from 'vue'
2
+
1
3
  /** 工具栏 t-select 等抢焦点时缓存的非空文本选区 */
2
4
  let cachedRange = null
3
5
  /** 当前 t-select 弹层会话内快照(打开下拉时锁定,避免误恢复旧选区) */
4
6
  let pendingSelectRange = null
5
7
 
8
+ /** ProseMirror 选区/mark 变化时递增,供 Vue computed 订阅 */
9
+ export const toolbarEditorTick = ref(0)
10
+
11
+ export function bumpToolbarEditorState() {
12
+ toolbarEditorTick.value += 1
13
+ }
14
+
6
15
  export function cacheEditorTextSelection(editor) {
7
16
  if (!editor?.state) return
8
17
  const { from, to, empty } = editor.state.selection
@@ -17,11 +26,13 @@ export function syncCacheOnEditorSelectionUpdate(editor) {
17
26
  if (!empty && from !== to) {
18
27
  cachedRange = { from, to }
19
28
  }
29
+ bumpToolbarEditorState()
20
30
  }
21
31
 
22
32
  export function beginSelectMenuInteraction(editor) {
23
33
  if (!editor?.state) {
24
34
  pendingSelectRange = cachedRange ? { ...cachedRange } : null
35
+ bumpToolbarEditorState()
25
36
  return
26
37
  }
27
38
  const { from, to, empty } = editor.state.selection
@@ -33,10 +44,12 @@ export function beginSelectMenuInteraction(editor) {
33
44
  } else {
34
45
  pendingSelectRange = null
35
46
  }
47
+ bumpToolbarEditorState()
36
48
  }
37
49
 
38
50
  export function endSelectMenuInteraction() {
39
51
  pendingSelectRange = null
52
+ bumpToolbarEditorState()
40
53
  }
41
54
 
42
55
  function applyTextRange(editor, range) {
@@ -65,4 +78,224 @@ export function refreshCachedTextSelection(editor) {
65
78
  if (!empty && from !== to) {
66
79
  cachedRange = { from, to }
67
80
  }
81
+ bumpToolbarEditorState()
82
+ }
83
+
84
+ function clampRange(editor, range) {
85
+ if (!editor?.state || !range) return null
86
+ const docSize = editor.state.doc.content.size
87
+ const from = Math.max(0, Math.min(range.from, docSize))
88
+ const to = Math.max(from, Math.min(range.to, docSize))
89
+ if (to <= from) return null
90
+ return { from, to }
91
+ }
92
+
93
+ /** Word 中文字号 pt/px 对照(与 richTextHtmlUtils 一致) */
94
+ const CHINESE_FONT_SIZE_PT_PX = [
95
+ { pt: 42, px: 49 },
96
+ { pt: 36, px: 42 },
97
+ { pt: 26, px: 30 },
98
+ { pt: 24, px: 28 },
99
+ { pt: 22, px: 26 },
100
+ { pt: 18, px: 21 },
101
+ { pt: 16, px: 19 },
102
+ { pt: 15, px: 18 },
103
+ { pt: 14, px: 16 },
104
+ { pt: 12, px: 14 },
105
+ { pt: 10.5, px: 12 },
106
+ { pt: 9, px: 11 },
107
+ { pt: 7.5, px: 9 },
108
+ { pt: 6.5, px: 8 },
109
+ ]
110
+
111
+ function normalizeFontSizeValue(raw) {
112
+ if (!raw) return null
113
+ const value = String(raw).trim()
114
+ const pxMatch = value.match(/^(\d+(?:\.\d+)?)px$/i)
115
+ if (pxMatch) {
116
+ return `${Math.round(parseFloat(pxMatch[1]))}px`
117
+ }
118
+ const ptMatch = value.match(/^(\d+(?:\.\d+)?)pt$/i)
119
+ if (ptMatch) {
120
+ return `${parseFloat(ptMatch[1])}pt`
121
+ }
122
+ return value
123
+ }
124
+
125
+ /** 将 DOM/浏览器读到的 px 映射回 pt 选项值,供 t-select 显示中文标签 */
126
+ function resolveFontSizeForToolbarDisplay(raw) {
127
+ const normalized = normalizeFontSizeValue(raw)
128
+ if (!normalized) return null
129
+ if (/pt$/i.test(normalized)) return normalized
130
+
131
+ const pxMatch = normalized.match(/^(\d+(?:\.\d+)?)px$/i)
132
+ if (!pxMatch) return normalized
133
+
134
+ const px = Math.round(parseFloat(pxMatch[1]))
135
+ // 14px 为编辑器「默认」,勿映射为 12pt(小四)
136
+ if (px === 14) return '14px'
137
+
138
+ const byWordPx = CHINESE_FONT_SIZE_PT_PX.find((entry) => entry.px === px)
139
+ if (byWordPx) return `${byWordPx.pt}pt`
140
+
141
+ // 浏览器按 CSS 换算的 px(如 36pt → 48px)
142
+ const cssPt = (px * 72) / 96
143
+ const byCssPt = CHINESE_FONT_SIZE_PT_PX.find(
144
+ (entry) => Math.abs(entry.pt - cssPt) < 0.6,
145
+ )
146
+ if (byCssPt) return `${byCssPt.pt}pt`
147
+
148
+ return normalized
149
+ }
150
+
151
+ function readTextStyleMarkAtPos(editor, pos, attr) {
152
+ const $pos = editor.state.doc.resolve(pos)
153
+ const activeMarks = $pos.marks()
154
+ const storedMarks = editor.state.storedMarks ?? []
155
+ const marks = activeMarks.length ? activeMarks : storedMarks
156
+ const textStyle = marks.find((mark) => mark.type.name === 'textStyle')
157
+ return textStyle?.attrs?.[attr] ?? null
158
+ }
159
+
160
+ function readFontSizeFromDom(editor, pos) {
161
+ if (typeof window === 'undefined') return null
162
+ const view = editor.view
163
+ if (!view?.dom) return null
164
+
165
+ try {
166
+ const domPos = view.domAtPos(pos)
167
+ let node = domPos.node
168
+ if (node.nodeType === Node.TEXT_NODE) {
169
+ node = node.parentElement
170
+ }
171
+
172
+ while (node && node !== view.dom) {
173
+ const inlineSize = node.style?.fontSize
174
+ const computedSize =
175
+ inlineSize && inlineSize !== 'inherit'
176
+ ? inlineSize
177
+ : window.getComputedStyle(node).fontSize
178
+ const normalized = normalizeFontSizeValue(computedSize)
179
+ if (normalized) {
180
+ return normalized
181
+ }
182
+ node = node.parentElement
183
+ }
184
+ } catch {
185
+ return null
186
+ }
187
+
188
+ return null
189
+ }
190
+
191
+ function readFontSizeAtRange(editor, range) {
192
+ const clamped = clampRange(editor, range)
193
+ if (!clamped) return null
194
+
195
+ const { from, to } = clamped
196
+ let fontSize = null
197
+
198
+ editor.state.doc.nodesBetween(from, to, (node, pos) => {
199
+ if (fontSize || !node.isText) return
200
+ const mark = node.marks.find(
201
+ (item) => item.type.name === 'textStyle' && item.attrs?.fontSize,
202
+ )
203
+ if (mark) {
204
+ fontSize = mark.attrs.fontSize
205
+ }
206
+ })
207
+
208
+ if (!fontSize) {
209
+ const readPos = Math.min(from + 1, to - 1, editor.state.doc.content.size - 1)
210
+ fontSize = readTextStyleMarkAtPos(editor, Math.max(from, readPos), 'fontSize')
211
+ }
212
+
213
+ if (!fontSize) {
214
+ fontSize = readFontSizeFromDom(editor, from)
215
+ }
216
+
217
+ return resolveFontSizeForToolbarDisplay(fontSize)
218
+ }
219
+
220
+ function readFontFamilyAtRange(editor, range) {
221
+ const clamped = clampRange(editor, range)
222
+ if (!clamped) return null
223
+
224
+ const { from, to } = clamped
225
+ let fontFamily = null
226
+
227
+ editor.state.doc.nodesBetween(from, to, (node) => {
228
+ if (fontFamily || !node.isText) return
229
+ const mark = node.marks.find(
230
+ (item) => item.type.name === 'textStyle' && item.attrs?.fontFamily,
231
+ )
232
+ if (mark) {
233
+ fontFamily = mark.attrs.fontFamily
234
+ }
235
+ })
236
+
237
+ if (!fontFamily) {
238
+ const readPos = Math.min(from + 1, to - 1, editor.state.doc.content.size - 1)
239
+ fontFamily = readTextStyleMarkAtPos(
240
+ editor,
241
+ Math.max(from, readPos),
242
+ 'fontFamily',
243
+ )
244
+ }
245
+
246
+ return fontFamily ? fontFamily.replace(/"/g, '') : null
247
+ }
248
+
249
+ function getActiveTextRange(editor) {
250
+ if (!editor?.state) return null
251
+ const { from, to, empty } = editor.state.selection
252
+ if (!empty && from !== to) {
253
+ return { from, to }
254
+ }
255
+ return pendingSelectRange ?? cachedRange
256
+ }
257
+
258
+ /** t-select 抢焦点导致选区折叠时,仍从缓存选区读取字号供下拉框展示 */
259
+ export function getToolbarFontSize(editor) {
260
+ if (!editor?.state) return null
261
+
262
+ const { from, to, empty } = editor.state.selection
263
+ if (!empty && from !== to) {
264
+ return (
265
+ readFontSizeAtRange(editor, { from, to }) ??
266
+ resolveFontSizeForToolbarDisplay(
267
+ editor.getAttributes('textStyle').fontSize,
268
+ ) ??
269
+ resolveFontSizeForToolbarDisplay(readFontSizeFromDom(editor, from))
270
+ )
271
+ }
272
+
273
+ const range = getActiveTextRange(editor)
274
+ return (
275
+ readFontSizeAtRange(editor, range) ??
276
+ resolveFontSizeForToolbarDisplay(
277
+ editor.getAttributes('textStyle').fontSize,
278
+ ) ??
279
+ (range
280
+ ? resolveFontSizeForToolbarDisplay(readFontSizeFromDom(editor, range.from))
281
+ : null)
282
+ )
283
+ }
284
+
285
+ /** t-select 抢焦点导致选区折叠时,仍从缓存选区读取字体供下拉框展示 */
286
+ export function getToolbarFontFamily(editor) {
287
+ if (!editor?.state) return null
288
+
289
+ const { from, to, empty } = editor.state.selection
290
+ if (!empty && from !== to) {
291
+ return readFontFamilyAtRange(editor, { from, to })
292
+ }
293
+
294
+ const range = getActiveTextRange(editor)
295
+ return (
296
+ readFontFamilyAtRange(editor, range) ??
297
+ (editor.getAttributes('textStyle').fontFamily
298
+ ? editor.getAttributes('textStyle').fontFamily.replace(/"/g, '')
299
+ : null)
300
+ )
68
301
  }
@@ -51,8 +51,8 @@
51
51
  "12pt": "ཆུང་བའི་བཞི་པ།",
52
52
  "10_5pt": "ཨང་ལྔ་པ།",
53
53
  "9pt": "ཆུང་བའི་ལྔ་པ།",
54
- "7_5pt": "ཆུང་བའི་དྲུག་པ།",
55
- "6_5pt": "སྔོན་སྒྲིག"
54
+ "7_5pt": "དྲུག་པའི་ཨང་།",
55
+ "6_5pt": "ཆུང་བའི་དྲུག་པ།"
56
56
  },
57
57
  "formatPainter": {
58
58
  "text": "རྣམ་གཞག་འབྱུག་པ།",
@@ -66,8 +66,8 @@
66
66
  "12pt": "Sub-fourth Level",
67
67
  "10_5pt": "Fifth Level",
68
68
  "9pt": "Sub-fifth Level",
69
- "7_5pt": "Sub-sixth Level",
70
- "6_5pt": "Default"
69
+ "7_5pt": "Sixth Level",
70
+ "6_5pt": "Sub-sixth Level"
71
71
  },
72
72
  "formatPainter": {
73
73
  "text": "Format Painter",
@@ -51,8 +51,8 @@
51
51
  "12pt": "Sotto livello quattro",
52
52
  "10_5pt": "Quinto livello",
53
53
  "9pt": "Sotto livello cinque",
54
- "7_5pt": "Sotto livello sei",
55
- "6_5pt": "Predefinito"
54
+ "7_5pt": "Sesto livello",
55
+ "6_5pt": "Sotto livello sei"
56
56
  },
57
57
  "formatPainter": {
58
58
  "text": "Copia formato",
@@ -57,8 +57,8 @@
57
57
  "12pt": "Под-четвертый уровень",
58
58
  "10_5pt": "Пятый уровень",
59
59
  "9pt": "Под-пятый уровень",
60
- "7_5pt": "Под-шестой уровень",
61
- "6_5pt": "По умолчанию"
60
+ "7_5pt": "Шестой уровень",
61
+ "6_5pt": "Под-шестой уровень"
62
62
  },
63
63
  "formatPainter": {
64
64
  "text": "Формат по образцу",
@@ -66,8 +66,8 @@
66
66
  "12pt": "小四",
67
67
  "10_5pt": "五号",
68
68
  "9pt": "小五",
69
- "7_5pt": "小六",
70
- "6_5pt": "默认"
69
+ "7_5pt": "六号",
70
+ "6_5pt": "小六"
71
71
  },
72
72
  "formatPainter": {
73
73
  "text": "格式刷",
@@ -1,35 +1,59 @@
1
+ /** TipTap focus 选项:不触发 ProseMirror scrollIntoView */
2
+ export const FOCUS_WITHOUT_SCROLL = { scrollIntoView: false }
3
+
4
+ function pushScrollPosition(positions, seen, el) {
5
+ if (!el || seen.has(el)) return
6
+ seen.add(el)
7
+ positions.push({
8
+ el,
9
+ top: el.scrollTop,
10
+ left: el.scrollLeft,
11
+ })
12
+ }
13
+
1
14
  /**
2
- * 保存/恢复编辑器及外层滚动位置,避免 setBlockType 等命令触发 scrollIntoView 导致页面跳动。
15
+ * 保存/恢复编辑器及外层滚动位置,避免 toolbar focus / setBlockType 等触发 scrollIntoView 导致页面跳动。
3
16
  */
4
17
  export function captureScrollPositions(container) {
5
18
  const positions = []
19
+ const seen = new Set()
6
20
  const pageContainer = document.querySelector(
7
21
  `${container} .umo-zoomable-container`,
8
22
  )
9
- if (pageContainer) {
10
- positions.push({
11
- el: pageContainer,
12
- top: pageContainer.scrollTop,
13
- left: pageContainer.scrollLeft,
14
- })
15
- }
23
+ pushScrollPosition(positions, seen, pageContainer)
16
24
 
17
- let node = pageContainer?.parentElement ?? null
25
+ let node =
26
+ pageContainer?.parentElement ??
27
+ document.querySelector(container)?.parentElement ??
28
+ null
18
29
  while (node && node !== document.documentElement) {
19
30
  if (node.classList?.contains('el-scrollbar__wrap')) {
20
- positions.push({
21
- el: node,
22
- top: node.scrollTop,
23
- left: node.scrollLeft,
24
- })
25
- break
31
+ pushScrollPosition(positions, seen, node)
32
+ } else {
33
+ const style = getComputedStyle(node)
34
+ const overflow = `${style.overflow}${style.overflowY}${style.overflowX}`
35
+ if (/auto|scroll/.test(overflow)) {
36
+ pushScrollPosition(positions, seen, node)
37
+ }
26
38
  }
27
39
  node = node.parentElement
28
40
  }
29
41
 
42
+ pushScrollPosition(positions, seen, document.documentElement)
43
+ pushScrollPosition(positions, seen, document.body)
44
+
30
45
  return positions
31
46
  }
32
47
 
48
+ export function runPreservingScroll(container, fn) {
49
+ const scrollPositions = captureScrollPositions(container)
50
+ try {
51
+ return fn()
52
+ } finally {
53
+ restoreScrollPositions(scrollPositions)
54
+ }
55
+ }
56
+
33
57
  export function restoreScrollPositions(positions) {
34
58
  if (!positions.length) return
35
59
  const apply = () => {
@@ -7,14 +7,15 @@
7
7
  <script setup lang="ts">
8
8
  //@ts-ignore
9
9
  import { components, initVxeTableInPage } from 'adtec-core-package/src/config/VxeTableConfig'
10
- // 导入默认的语言
11
10
  import { defineComponent, getCurrentInstance, h } from 'vue'
11
+
12
12
  const { VxeGrid } = components
13
13
  defineComponent({
14
14
  components: {
15
15
  VxeGrid,
16
16
  },
17
17
  })
18
+
18
19
  const { renderComponent = { enableElementPlus: true, enableExcel: false, enablePdf: false } } =
19
20
  defineProps<{
20
21
  renderComponent?: {
@@ -24,12 +25,18 @@ const { renderComponent = { enableElementPlus: true, enableExcel: false, enableP
24
25
  }
25
26
  }>()
26
27
 
27
- initVxeTableInPage(renderComponent.enableExcel,renderComponent.enablePdf)
28
+ initVxeTableInPage(
29
+ renderComponent.enableExcel,
30
+ renderComponent.enablePdf,
31
+ renderComponent.enableElementPlus ?? true,
32
+ )
28
33
 
29
34
  const vm = getCurrentInstance()
30
35
 
31
- const changeRef = (exposed: any) => {
32
- vm && (vm.exposed = exposed)
36
+ const changeRef = (exposed: unknown) => {
37
+ if (vm) {
38
+ vm.exposed = exposed
39
+ }
33
40
  }
34
41
  </script>
35
42
 
@@ -1,6 +1,9 @@
1
1
  import { defineStore } from 'pinia'
2
- import { ref } from 'vue'
2
+ import { ref, type Ref } from 'vue'
3
+ import Base64 from 'crypto-js/enc-base64'
4
+ import Utf8 from 'crypto-js/enc-utf8'
3
5
  import type { ISysDictDataCacheVo } from '../interface/ISysDictDataCacheVo'
6
+
4
7
  // 定义包含map的整体类型
5
8
  export interface dictMapType {
6
9
  [key: string]: ISysDictDataCacheVo[]
@@ -14,12 +17,76 @@ export interface dictDataMapType {
14
17
  [key: string]: { [key: string]: ISysDictDataCacheVo }
15
18
  }
16
19
 
20
+ const DICT_STORAGE_KEY = 'dictStore'
21
+
22
+ export interface SharedDictState {
23
+ dictMap: Ref<dictMapType>
24
+ dictDataMap: Ref<dictDataMapType>
25
+ dictDefaultValueMap: Ref<dictMapType>
26
+ }
27
+
28
+ declare global {
29
+ interface Window {
30
+ __ADTEC_SHARED_DICT__?: SharedDictState
31
+ }
32
+ }
33
+
34
+ /** wujie 子应用走 parent,宿主/独立 dev 走自身 window —— 全应用共享同一份 ref */
35
+ function getRootWindow(): Window {
36
+ if (typeof window === 'undefined') return window
37
+ const w = window as Window & { __POWERED_BY_WUJIE__?: boolean }
38
+ return w.__POWERED_BY_WUJIE__ ? window.parent : window
39
+ }
40
+
41
+ function hydrateFromSessionStorage(state: SharedDictState) {
42
+ const raw = sessionStorage.getItem(DICT_STORAGE_KEY)
43
+ if (!raw) return
44
+ try {
45
+ const parsed = JSON.parse(Base64.parse(raw).toString(Utf8)) as {
46
+ dictMap?: dictMapType
47
+ dictDataMap?: dictDataMapType
48
+ dictDefaultValueMap?: dictMapType
49
+ }
50
+ if (parsed.dictMap) {
51
+ state.dictMap.value = { ...parsed.dictMap, ...state.dictMap.value }
52
+ }
53
+ if (parsed.dictDataMap) {
54
+ state.dictDataMap.value = { ...parsed.dictDataMap, ...state.dictDataMap.value }
55
+ }
56
+ if (parsed.dictDefaultValueMap) {
57
+ state.dictDefaultValueMap.value = {
58
+ ...parsed.dictDefaultValueMap,
59
+ ...state.dictDefaultValueMap.value,
60
+ }
61
+ }
62
+ } catch {
63
+ // ignore corrupt session data
64
+ }
65
+ }
66
+
67
+ /** 宿主 window 单例:alive 子应用与宿主读写同一组 Vue ref(刷新时仍由 sessionStorage 恢复) */
68
+ export function getSharedDictRefs(): SharedDictState {
69
+ const root = getRootWindow()
70
+ if (!root.__ADTEC_SHARED_DICT__) {
71
+ root.__ADTEC_SHARED_DICT__ = {
72
+ dictMap: ref<dictMapType>({}),
73
+ dictDataMap: ref<dictDataMapType>({}),
74
+ dictDefaultValueMap: ref<dictMapType>({}),
75
+ }
76
+ hydrateFromSessionStorage(root.__ADTEC_SHARED_DICT__)
77
+ }
78
+ return root.__ADTEC_SHARED_DICT__
79
+ }
80
+
81
+ /** @deprecated 已由 getSharedDictRefs 在初始化时 hydrate;保留兼容旧调用 */
82
+ export function syncDictFromSessionStorage(target: SharedDictState) {
83
+ hydrateFromSessionStorage(target)
84
+ }
85
+
17
86
  export const dictStore = defineStore('dictStore', () => {
18
- const dictMap = ref<dictMapType>({})
19
- const dictDataMap = ref<dictDataMapType>({})
20
- const dictDefaultValueMap = ref<dictMapType>({})
87
+ const { dictMap, dictDataMap, dictDefaultValueMap } = getSharedDictRefs()
21
88
 
22
- const clearDictCache = (dictType?: string[],userOrgId?: string, orgId?: string, all?: Boolean) => {
89
+ const clearDictCache = (dictType?: string[], userOrgId?: string, orgId?: string, all?: Boolean) => {
23
90
  if (dictType && dictType.length > 0) {
24
91
  dictType.forEach((item) => {
25
92
  if (orgId) {