adtec-core-package 2.9.8 → 3.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adtec-core-package",
3
- "version": "2.9.8",
3
+ "version": "3.0.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "scripts": {
@@ -16,6 +16,7 @@
16
16
  "@element-plus/icons-vue": "^2.3.1",
17
17
  "@onlyoffice/document-editor-vue": "1.4.0",
18
18
  "@types/echarts": "^5.0.0",
19
+ "@umoteam/editor": "^10.2.1",
19
20
  "@vueuse/components": "^13.0.0",
20
21
  "@vueuse/core": "^13.0.0",
21
22
  "@vxe-ui/plugin-export-pdf": "4.2.4",
@@ -67,5 +68,54 @@
67
68
  },
68
69
  "description": "This template should help get you started developing with Vue 3 in Vite.",
69
70
  "author": "",
70
- "license": "ISC"
71
+ "license": "ISC",
72
+ "pnpm": {
73
+ "peerDependencyRules": {
74
+ "allowedVersions": {
75
+ "@tiptap/core": "2 || 3",
76
+ "@tiptap/pm": "2 || 3",
77
+ "@tiptap/extensions": "3",
78
+ "@tiptap/extension-list": "3"
79
+ }
80
+ },
81
+ "packageExtensions": {
82
+ "@tiptap/extension-collaboration@3.20.0": {
83
+ "peerDependencies": {
84
+ "@tiptap/core": "3.26.0",
85
+ "@tiptap/pm": "3.26.0"
86
+ }
87
+ },
88
+ "@tiptap/extension-drag-handle@3.16.0": {
89
+ "peerDependencies": {
90
+ "@tiptap/core": "3.26.0",
91
+ "@tiptap/pm": "3.26.0"
92
+ }
93
+ },
94
+ "@tiptap/vue-3@3.26.0": {
95
+ "peerDependencies": {
96
+ "@tiptap/core": "3.26.0",
97
+ "@tiptap/pm": "3.26.0"
98
+ }
99
+ }
100
+ },
101
+ "overrides": {
102
+ "@umoteam/editor>@tiptap/core": "3.26.0",
103
+ "@umoteam/editor>@tiptap/pm": "3.26.0",
104
+ "@umoteam/editor>@tiptap/vue-3": "3.26.0",
105
+ "@umoteam/editor>@tiptap/starter-kit": "3.26.0",
106
+ "@umoteam/editor>@tiptap/extensions": "3.26.0",
107
+ "@umoteam/editor>@tiptap/extension-list": "3.26.0",
108
+ "@umoteam/editor>@tiptap/extension-collaboration": "3.26.0",
109
+ "@umoteam/editor>@tiptap/extension-drag-handle": "3.26.0",
110
+ "@umoteam/editor>@tiptap/extension-drag-handle-vue-3": "3.26.0",
111
+ "@umoteam/editor>@tiptap/extension-node-range": "3.26.0",
112
+ "@tiptap/extension-collaboration@3.20.0": "3.26.0",
113
+ "@tiptap/extension-drag-handle@3.16.0": "3.26.0",
114
+ "@tiptap/extension-drag-handle-vue-3@3.16.0": "3.26.0",
115
+ "@tiptap/vue-3>@tiptap/core": "3.26.0",
116
+ "@tiptap/vue-3>@tiptap/pm": "3.26.0",
117
+ "aieditor>@tiptap/core": "2.26.1",
118
+ "aieditor>@tiptap/pm": "2.26.1"
119
+ }
120
+ }
71
121
  }
@@ -0,0 +1,257 @@
1
+ <!-- Umo Editor 封装:与 RichTextEditor 保持相同对外 API,供结题验收等模块试点 -->
2
+ <template>
3
+ <el-flex ref="rootRef" class="umo-rich-text-editor-root" :style="{ height: '100%', width: '100%' }">
4
+ <UmoEditor
5
+ ref="editorRef"
6
+ class="umo-rich-text-editor"
7
+ v-bind="umoOptions"
8
+ @created="onEditorCreated"
9
+ @changed="onEditorChanged"
10
+ @blur="onEditorBlur"
11
+ />
12
+ </el-flex>
13
+ </template>
14
+
15
+ <script setup lang="ts">
16
+ import { UmoEditor } from '@umoteam/editor'
17
+ import '@umoteam/editor/style'
18
+ import { ElMessage } from 'element-plus'
19
+ import { computed, nextTick, onUnmounted, ref, watch } from 'vue'
20
+ import { v4 as uuidv4 } from 'uuid'
21
+ import { finalizeHtml, initHtml } from './richTextHtmlUtils'
22
+
23
+ export type RichTextEditorConfig = {
24
+ placeholder?: string
25
+ editable?: boolean
26
+ [key: string]: unknown
27
+ }
28
+
29
+ const emit = defineEmits<{
30
+ onBlur: [payload: { html: string; text: string }]
31
+ onChange: [payload: { html: string; text: string }]
32
+ }>()
33
+
34
+ const props = withDefaults(
35
+ defineProps<{
36
+ showToolbar?: boolean
37
+ borderWidth?: string
38
+ editorConfig?: RichTextEditorConfig
39
+ isEdit?: boolean
40
+ }>(),
41
+ {
42
+ showToolbar: true,
43
+ borderWidth: '1px',
44
+ editorConfig: () => ({}),
45
+ isEdit: true,
46
+ },
47
+ )
48
+
49
+ const model = defineModel<string | null | undefined>({
50
+ required: false,
51
+ default: null,
52
+ })
53
+ const text = defineModel<string>('text', {
54
+ required: false,
55
+ })
56
+
57
+ const editorRef = ref<InstanceType<typeof UmoEditor> | null>(null)
58
+ const rootRef = ref<HTMLElement | null>(null)
59
+ const editorKey = `umo-${uuidv4()}`
60
+ const ready = ref(false)
61
+ const syncingContent = ref(false)
62
+
63
+ const placeholderText = computed(() => {
64
+ const raw = props.editorConfig?.placeholder
65
+ if (typeof raw === 'string' && raw.trim()) return raw
66
+ return '请输入内容...'
67
+ })
68
+
69
+ const editable = computed(() => {
70
+ if (!props.isEdit) return false
71
+ if (props.editorConfig?.editable === false) return false
72
+ return true
73
+ })
74
+
75
+ const umoOptions = computed(() => ({
76
+ editorKey,
77
+ locale: 'zh-CN',
78
+ theme: 'light',
79
+ height: '100%',
80
+ toolbar: {
81
+ showSaveLabel: false,
82
+ defaultMode: 'classic',
83
+ menus: props.showToolbar ? ['base', 'insert', 'table', 'tools'] : ['base'],
84
+ },
85
+ page: {
86
+ layouts: ['web'],
87
+ },
88
+ document: {
89
+ title: '',
90
+ content: initHtml(model.value ?? ''),
91
+ placeholder: {
92
+ zh_CN: placeholderText.value,
93
+ },
94
+ readOnly: !editable.value,
95
+ enableMarkdown: false,
96
+ enableSpellcheck: false,
97
+ autoSave: {
98
+ enabled: false,
99
+ },
100
+ },
101
+ shareUrl: typeof location !== 'undefined' ? location.href : '',
102
+ async onFileUpload(file: File) {
103
+ if (file.type.startsWith('image/') && file.size > 2 * 1024 * 1024) {
104
+ ElMessage.warning('图片大小不能超过2M')
105
+ throw new Error('image too large')
106
+ }
107
+ const url = await readFileAsDataUrl(file)
108
+ return {
109
+ id: uuidv4(),
110
+ url,
111
+ }
112
+ },
113
+ }))
114
+
115
+ function readFileAsDataUrl(file: File) {
116
+ return new Promise<string>((resolve, reject) => {
117
+ const reader = new FileReader()
118
+ reader.onload = () => resolve(String(reader.result ?? ''))
119
+ reader.onerror = () => reject(reader.error)
120
+ reader.readAsDataURL(file)
121
+ })
122
+ }
123
+
124
+ function getEditorInstance() {
125
+ return editorRef.value as unknown as {
126
+ getHTML?: () => string
127
+ getText?: () => string
128
+ setContent?: (content: string, options?: Record<string, unknown>) => void
129
+ setReadOnly?: (value: boolean) => void
130
+ setToolbar?: (payload: { mode?: string; show?: boolean }) => void
131
+ setLayout?: (layout: 'page' | 'web') => void
132
+ } | null
133
+ }
134
+
135
+ function getRawHtml() {
136
+ const editor = getEditorInstance()
137
+ return editor?.getHTML?.() ?? ''
138
+ }
139
+
140
+ function getPlainText() {
141
+ const editor = getEditorInstance()
142
+ return editor?.getText?.() ?? ''
143
+ }
144
+
145
+ function applyEditorHtml(html: string, focus = false) {
146
+ const editor = getEditorInstance()
147
+ editor?.setContent?.(initHtml(html), {
148
+ emitUpdate: false,
149
+ focusPosition: focus ? 'end' : false,
150
+ })
151
+ }
152
+
153
+ function syncEditorPresentation() {
154
+ const editor = getEditorInstance()
155
+ if (!editor) return
156
+ editor.setLayout?.('web')
157
+ editor.setReadOnly?.(!editable.value)
158
+ editor.setToolbar?.({
159
+ mode: 'classic',
160
+ show: props.showToolbar && editable.value,
161
+ })
162
+ }
163
+
164
+ function publishModelFromEditor(finalize = false) {
165
+ const editor = getEditorInstance()
166
+ if (!editor) return
167
+ let html = getRawHtml()
168
+ if (finalize) {
169
+ html = finalizeHtml(html, rootRef.value)
170
+ }
171
+ syncingContent.value = true
172
+ model.value = html
173
+ text.value = getPlainText()
174
+ syncingContent.value = false
175
+ emit('onChange', { html, text: getPlainText() })
176
+ }
177
+
178
+ function onEditorCreated() {
179
+ ready.value = true
180
+ syncEditorPresentation()
181
+ applyEditorHtml(model.value ?? '', false)
182
+ }
183
+
184
+ function onEditorChanged() {
185
+ if (!ready.value || syncingContent.value) return
186
+ publishModelFromEditor(false)
187
+ }
188
+
189
+ function onEditorBlur() {
190
+ publishModelFromEditor(true)
191
+ emit('onBlur', { html: model.value ?? '', text: getPlainText() })
192
+ }
193
+
194
+ watch(
195
+ () => model.value,
196
+ (value) => {
197
+ if (!ready.value || syncingContent.value) return
198
+ const current = getRawHtml()
199
+ if ((value ?? '') === current) return
200
+ applyEditorHtml(value ?? '', false)
201
+ },
202
+ )
203
+
204
+ watch(
205
+ () => [editable.value, props.showToolbar] as const,
206
+ async () => {
207
+ if (!ready.value) return
208
+ await nextTick()
209
+ syncEditorPresentation()
210
+ },
211
+ )
212
+
213
+ watch(placeholderText, () => {
214
+ if (!ready.value) return
215
+ getEditorInstance()?.setContent?.(getRawHtml(), { emitUpdate: false })
216
+ })
217
+
218
+ defineExpose({
219
+ getHtml: () => {
220
+ if (!ready.value) return model.value ?? ''
221
+ return finalizeHtml(getRawHtml(), rootRef.value)
222
+ },
223
+ getText: () => {
224
+ if (!ready.value) return text.value ?? ''
225
+ return getPlainText()
226
+ },
227
+ setContent: (html: string, focus = false) => {
228
+ model.value = html
229
+ if (ready.value) {
230
+ applyEditorHtml(html, focus)
231
+ }
232
+ },
233
+ })
234
+
235
+ onUnmounted(() => {
236
+ ready.value = false
237
+ })
238
+ </script>
239
+
240
+ <style scoped lang="scss">
241
+ .umo-rich-text-editor-root {
242
+ min-height: 280px;
243
+ }
244
+
245
+ :deep(.umo-rich-text-editor) {
246
+ width: 100%;
247
+ height: 100%;
248
+ border-width: v-bind(borderWidth);
249
+ border-style: solid;
250
+ border-color: #dcdfe6;
251
+ box-sizing: border-box;
252
+ }
253
+
254
+ :deep(.umo-editor-container) {
255
+ min-height: 240px;
256
+ }
257
+ </style>
@@ -0,0 +1,166 @@
1
+ const fontSizes = [
2
+ { name: '八号', pt: 5, px: 5 },
3
+ { name: '七号', pt: 5.5, px: 6 },
4
+ { name: '小六', pt: 6.5, px: 8 },
5
+ { name: '六号', pt: 7.5, px: 9 },
6
+ { name: '小五', pt: 9, px: 11 },
7
+ { name: '五号', pt: 10.5, px: 12 },
8
+ { name: '小四', pt: 12, px: 14 },
9
+ { name: '四号', pt: 14, px: 16 },
10
+ { name: '小三', pt: 15, px: 18 },
11
+ { name: '三号', pt: 16, px: 19 },
12
+ { name: '小二', pt: 18, px: 21 },
13
+ { name: '二号', pt: 22, px: 26 },
14
+ { name: '小一', pt: 24, px: 28 },
15
+ { name: '一号', pt: 26, px: 30 },
16
+ { name: '小初', pt: 36, px: 42 },
17
+ { name: '初号', pt: 42, px: 49 },
18
+ ]
19
+
20
+ function setDefaultPx(html: string) {
21
+ const tempDiv = document.createElement('div')
22
+ tempDiv.innerHTML = html
23
+ tempDiv.querySelectorAll('p').forEach((p) => {
24
+ if (!p.style.fontSize) {
25
+ p.style.fontSize = '14px'
26
+ }
27
+ })
28
+ return tempDiv.innerHTML
29
+ }
30
+
31
+ export function convertPxToPt(html: string) {
32
+ html = setDefaultPx(html)
33
+ const regex = /font-size: (\d+)px/g
34
+ return html.replace(regex, (match, pxValue) => {
35
+ const px = parseInt(pxValue, 10)
36
+ const entry = fontSizes.find((item) => item.px === px)
37
+ if (entry) {
38
+ return `font-size: ${entry.pt}pt`
39
+ }
40
+ return match
41
+ })
42
+ }
43
+
44
+ export function convertPtToPx(html: string) {
45
+ const regex = /font-size: (\d+)pt/g
46
+ return html.replace(regex, (match, ptValue) => {
47
+ const pt = parseFloat(ptValue)
48
+ const entry = fontSizes.find((item) => item.pt === pt)
49
+ if (entry) {
50
+ return `font-size: ${entry.px}px`
51
+ }
52
+ return match
53
+ })
54
+ }
55
+
56
+ function normalizeStyleString(style: string): string {
57
+ style = style.trim()
58
+ if (!style) return ''
59
+ if (style.charAt(style.length - 1) !== ';') {
60
+ style += ';'
61
+ }
62
+ return style
63
+ }
64
+
65
+ export function removeAllFontFamilyStyles(html: string): string {
66
+ const tempDiv = document.createElement('div')
67
+ tempDiv.innerHTML = html
68
+
69
+ function processElement(element: Element) {
70
+ if (element.hasAttribute('style')) {
71
+ let style = element.getAttribute('style') || ''
72
+ style = normalizeStyleString(style)
73
+ style = style.replace(
74
+ /(?:font-family|mso-(?:ascii|hansi|bidi|fareast|east-asian|font)-font-family)\s*:\s*[^;]+;/gi,
75
+ '',
76
+ )
77
+ style = style.trim()
78
+ if (style) {
79
+ element.setAttribute('style', style)
80
+ } else {
81
+ element.removeAttribute('style')
82
+ }
83
+ }
84
+
85
+ const fontFamilyAttrs = [
86
+ 'font-family',
87
+ 'face',
88
+ 'mso-hansi-font-family',
89
+ 'mso-bidi-font-family',
90
+ 'mso-ascii-font-family',
91
+ 'mso-fareast-font-family',
92
+ 'mso-east-asian-font-family',
93
+ 'mso-font-font-family',
94
+ ]
95
+ fontFamilyAttrs.forEach((attr) => element.removeAttribute(attr))
96
+ Array.from(element.children).forEach((child) => processElement(child))
97
+ }
98
+
99
+ Array.from(tempDiv.children).forEach((child) => processElement(child))
100
+ return tempDiv.innerHTML
101
+ }
102
+
103
+ function customStringReplacement(
104
+ str: string,
105
+ startMarker: string,
106
+ endMarker: string,
107
+ target: string,
108
+ replacement: string,
109
+ ): string {
110
+ let result = ''
111
+ let currentIndex = 0
112
+
113
+ while (true) {
114
+ const startIndex = str.indexOf(startMarker, currentIndex)
115
+ if (startIndex === -1) {
116
+ result += str.slice(currentIndex)
117
+ break
118
+ }
119
+
120
+ const endIndex = str.indexOf(endMarker, startIndex + startMarker.length)
121
+ if (endIndex === -1) {
122
+ result += str.slice(currentIndex)
123
+ break
124
+ }
125
+
126
+ result += str.slice(currentIndex, startIndex + startMarker.length)
127
+ const middle = str.slice(startIndex + startMarker.length, endIndex)
128
+ const newMiddle = middle.replace(new RegExp(target, 'g'), replacement)
129
+ result += newMiddle
130
+ result += endMarker
131
+ currentIndex = endIndex + 1
132
+ }
133
+
134
+ return result
135
+ }
136
+
137
+ export function calcImagesHeight(html: string, root?: HTMLElement | null) {
138
+ if (!root) return html
139
+ const map = new Map<string, number>()
140
+ const imageElements = root.querySelectorAll('img')
141
+ imageElements.forEach((imageElement) => {
142
+ const width = imageElement.offsetWidth
143
+ const height = imageElement.offsetHeight
144
+ if (width > 1 && height > 1) {
145
+ const src =
146
+ imageElement.src.length > 100 ? imageElement.src.substring(0, 100) : imageElement.src
147
+ map.set(src, height)
148
+ }
149
+ })
150
+ for (const [key, value] of map) {
151
+ html = customStringReplacement(html, `${key}`, '>', 'height="auto"', `height="${value}"`)
152
+ }
153
+ return html
154
+ }
155
+
156
+ export function initHtml(html: string) {
157
+ if (!html) return ''
158
+ html = customStringReplacement(html, '<img', '>', 'height="[0-9]+"', 'height="auto"')
159
+ return convertPtToPx(html)
160
+ }
161
+
162
+ export function finalizeHtml(html: string, root?: HTMLElement | null) {
163
+ let result = html || ''
164
+ result = calcImagesHeight(result, root)
165
+ return convertPxToPt(result)
166
+ }
@@ -0,0 +1,8 @@
1
+ declare module '@umoteam/editor' {
2
+ import type { DefineComponent } from 'vue'
3
+
4
+ export const UmoEditor: DefineComponent<Record<string, unknown>, Record<string, unknown>, any>
5
+ export function useUmoEditor(options?: Record<string, unknown>): void
6
+ }
7
+
8
+ declare module '@umoteam/editor/style'