feat: improve ocd conflict merge ids

This commit is contained in:
hanshiyang 2026-06-03 20:32:16 +08:00
parent a946dc7ae8
commit d34d1709ef
6 changed files with 122 additions and 7 deletions

View File

@ -72,6 +72,7 @@
"vue": "^3.2.45" "vue": "^3.2.45"
}, },
"dependencies": { "dependencies": {
"nanoid": "3.3.11",
"prismjs": "^1.27.0" "prismjs": "^1.27.0"
}, },
"simple-git-hooks": { "simple-git-hooks": {

View File

@ -8,6 +8,9 @@ importers:
.: .:
dependencies: dependencies:
nanoid:
specifier: 3.3.11
version: 3.3.11
prismjs: prismjs:
specifier: ^1.27.0 specifier: ^1.27.0
version: 1.30.0 version: 1.30.0

View File

@ -29,6 +29,7 @@ import {
} from '../../interface/Element' } from '../../interface/Element'
import { IRow, IRowElement } from '../../interface/Row' import { IRow, IRowElement } from '../../interface/Row'
import { deepClone, getUUID, nextTick } from '../../utils' import { deepClone, getUUID, nextTick } from '../../utils'
import { prepareOcdDocumentForSave } from '../../../utils/ocd'
import { Cursor } from '../cursor/Cursor' import { Cursor } from '../cursor/Cursor'
import { CanvasEvent } from '../event/CanvasEvent' import { CanvasEvent } from '../event/CanvasEvent'
import { GlobalEvent } from '../event/GlobalEvent' import { GlobalEvent } from '../event/GlobalEvent'
@ -1196,7 +1197,7 @@ export class Draw {
} }
return { return {
version, version,
data, data: prepareOcdDocumentForSave(data),
options: deepClone(this.options) options: deepClone(this.options)
} }
} }

View File

@ -8,6 +8,7 @@ import { ICatalog } from '../../interface/Catalog'
import { IEditorResult } from '../../interface/Editor' import { IEditorResult } from '../../interface/Editor'
import { IGetValueOption } from '../../interface/Draw' import { IGetValueOption } from '../../interface/Draw'
import { deepClone } from '../../utils' import { deepClone } from '../../utils'
import { prepareOcdDocumentForSave } from '../../../utils/ocd'
export class WorkerManager { export class WorkerManager {
private draw: Draw private draw: Draw
@ -78,7 +79,7 @@ export class WorkerManager {
this.valueWorker.onmessage = evt => { this.valueWorker.onmessage = evt => {
resolve({ resolve({
version, version,
data: evt.data, data: prepareOcdDocumentForSave(evt.data),
options: deepClone(this.draw.getOptions()) options: deepClone(this.draw.getOptions())
}) })
} }

View File

@ -26,6 +26,10 @@ import { Dialog } from './components/dialog/Dialog'
import { formatPrismToken } from './utils/prism' import { formatPrismToken } from './utils/prism'
import { Signature } from './components/signature/Signature' import { Signature } from './components/signature/Signature'
import { debounce, nextTick, scrollIntoView } from './utils' import { debounce, nextTick, scrollIntoView } from './utils'
import {
prepareOcdDocumentForOpen,
prepareOcdDocumentForSave
} from './utils/ocd'
/** /**
* URL中提取文件路径 * URL中提取文件路径
@ -249,7 +253,7 @@ window.onload = function () {
} catch { } catch {
editorData = { main: [{ value: text }] } editorData = { main: [{ value: text }] }
} }
instance.command.executeSetValue(editorData) instance.command.executeSetValue(prepareOcdDocumentForOpen(editorData))
console.log('[iframe] 通过 Electron 加载本地文件完成:', localFilePath) console.log('[iframe] 通过 Electron 加载本地文件完成:', localFilePath)
} catch (err) { } catch (err) {
console.error('[iframe] 本地文件解析失败:', err) console.error('[iframe] 本地文件解析失败:', err)
@ -273,7 +277,7 @@ window.onload = function () {
} catch { } catch {
editorData = { main: [{ value: text }] } editorData = { main: [{ value: text }] }
} }
instance.command.executeSetValue(editorData) instance.command.executeSetValue(prepareOcdDocumentForOpen(editorData))
console.log('[iframe] 根据 filePath 加载完成:', url) console.log('[iframe] 根据 filePath 加载完成:', url)
} catch (err) { } catch (err) {
console.error('[iframe] 加载失败:', err) console.error('[iframe] 加载失败:', err)
@ -334,7 +338,7 @@ window.onload = function () {
} }
// 设置编辑器内容 // 设置编辑器内容
instance.command.executeSetValue(editorData) instance.command.executeSetValue(prepareOcdDocumentForOpen(editorData))
console.log('文档已打开') console.log('文档已打开')
} catch (error) { } catch (error) {
console.error('打开文档失败:', error) console.error('打开文档失败:', error)
@ -358,7 +362,11 @@ window.onload = function () {
try { try {
// 获取编辑器内容 // 获取编辑器内容
const editorValue = instance.command.getValue() const editorValue = instance.command.getValue()
const content = JSON.stringify(editorValue.data, null, 2) const content = JSON.stringify(
prepareOcdDocumentForSave(editorValue.data),
null,
2
)
// 创建下载链接 // 创建下载链接
const blob = new Blob([content], { type: 'application/json' }) const blob = new Blob([content], { type: 'application/json' })
@ -391,7 +399,11 @@ window.onload = function () {
saveCallbackDom.onclick = function () { saveCallbackDom.onclick = function () {
try { try {
const editorValue = instance.command.getValue() const editorValue = instance.command.getValue()
const content = JSON.stringify(editorValue.data, null, 2) const content = JSON.stringify(
prepareOcdDocumentForSave(editorValue.data),
null,
2
)
const parentWin: any = window.parent || window const parentWin: any = window.parent || window
const viewer = const viewer =

97
src/utils/ocd.ts Normal file
View File

@ -0,0 +1,97 @@
import { nanoid } from 'nanoid'
import { IEditorData } from '../editor'
const CONFLICT_MARKER_HIGHLIGHT = '#fff3cd'
const CONFLICT_MARKER_COLOR = '#b42318'
const CONFLICT_MARKER_REG = /^(<<<<<<<|=======|>>>>>>>)\b/
type UnknownRecord = Record<string, unknown>
function isRecord(value: unknown): value is UnknownRecord {
return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
}
function isElementList(value: unknown): value is UnknownRecord[] {
return Array.isArray(value) && value.every(isRecord)
}
function isConflictMarkerText(value: unknown) {
return typeof value === 'string' && CONFLICT_MARKER_REG.test(value.trim())
}
function normalizeConflictElement(element: UnknownRecord, isMarker: boolean) {
if (isMarker) {
return
}
if (element.highlight === CONFLICT_MARKER_HIGHLIGHT) {
delete element.highlight
}
if (element.color === CONFLICT_MARKER_COLOR) {
delete element.color
}
}
function prepareElementList(elementList: UnknownRecord[]) {
for (const element of elementList) {
if (isElementList(element.valueList)) {
const isMarker =
element._ocdConflictMarker === true ||
element.valueList.some(item => isConflictMarkerText(item.value))
for (const item of element.valueList) {
if (typeof item.id !== 'string' || !item.id) {
item.id = nanoid()
}
normalizeConflictElement(item, isMarker || isConflictMarkerText(item.value))
prepareNestedElement(item)
}
}
prepareNestedElement(element)
}
}
function prepareNestedElement(element: UnknownRecord) {
const trList = element.trList
if (Array.isArray(trList)) {
for (const tr of trList) {
if (!isRecord(tr) || !Array.isArray(tr.tdList)) continue
for (const td of tr.tdList) {
if (!isRecord(td) || !isElementList(td.value)) continue
prepareElementList(td.value)
}
}
}
const control = element.control
if (isRecord(control) && isElementList(control.value)) {
prepareElementList(control.value)
}
}
function cloneEditorData(data: IEditorData): IEditorData {
if (typeof structuredClone === 'function') {
return structuredClone(data) as IEditorData
}
return JSON.parse(JSON.stringify(data)) as IEditorData
}
export function prepareOcdDocumentForSave(data: IEditorData): IEditorData {
const prepared = cloneEditorData(data)
;(['header', 'main', 'footer'] as const).forEach(key => {
const elementList = prepared[key]
if (isElementList(elementList)) {
prepareElementList(elementList)
}
})
return prepared
}
export function prepareOcdDocumentForOpen(data: IEditorData): IEditorData {
if (!isRecord(data)) return data
;(['header', 'main', 'footer'] as const).forEach(key => {
const elementList = data[key]
if (isElementList(elementList)) {
prepareElementList(elementList)
}
})
return data
}