- Add AGENTS.md knowledge base with project documentation - Move UserPreferences model to separate models.py file - Extract API_KEY to environment variable for security - Enhance Univer Editor with PPTX support and improved UI - Improve file system handling with binary file detection - Add HF_ENDPOINT mirror for better China connectivity - Clean up unused imports and code structure
303 lines
7.4 KiB
JavaScript
303 lines
7.4 KiB
JavaScript
/**
|
||
* Univer 编辑器桥接服务
|
||
* 封装 Univer 的初始化、加载、导出等操作
|
||
*
|
||
* 支持三种文档格式:
|
||
* - DOCX: 使用 DocsCorePreset
|
||
* - XLSX: 使用 SheetsCorePreset
|
||
* - PPTX: 使用 DocsCorePreset + Slides 插件组合
|
||
*/
|
||
import { createUniver, LocaleType, merge } from '@univerjs/presets'
|
||
import { UniverDocsCorePreset } from '@univerjs/preset-docs-core'
|
||
import { UniverSheetsCorePreset } from '@univerjs/preset-sheets-core'
|
||
import { UniverSlidesPlugin } from '@univerjs/slides'
|
||
import { UniverSlidesUIPlugin } from '@univerjs/slides-ui'
|
||
|
||
// 导入样式
|
||
import '@univerjs/preset-docs-core/lib/index.css'
|
||
import '@univerjs/preset-sheets-core/lib/index.css'
|
||
import '@univerjs/slides-ui/lib/index.css'
|
||
|
||
// 导入语言包
|
||
import DocsCoreEnUS from '@univerjs/preset-docs-core/locales/en-US'
|
||
import SheetsCoreEnUS from '@univerjs/preset-sheets-core/locales/en-US'
|
||
import DocsCoreZhCN from '@univerjs/preset-docs-core/locales/zh-CN'
|
||
import SheetsCoreZhCN from '@univerjs/preset-sheets-core/locales/zh-CN'
|
||
|
||
// Slides 语言包(使用空对象作为 fallback,因为 slides 语言包可能不存在)
|
||
const SlidesZhCN = {}
|
||
const SlidesEnUS = {}
|
||
|
||
export const OfficeFormat = {
|
||
DOCX: 'docx',
|
||
XLSX: 'xlsx',
|
||
PPTX: 'pptx'
|
||
}
|
||
|
||
export const OfficePresetType = {
|
||
DOCS: 'docs',
|
||
SHEETS: 'sheets',
|
||
SLIDES: 'slides'
|
||
}
|
||
|
||
/**
|
||
* 根据文件扩展名判断 Office 格式
|
||
*/
|
||
export function detectOfficeFormat(filename) {
|
||
const ext = filename?.toLowerCase().split('.').pop() || ''
|
||
if (ext === 'docx') return OfficeFormat.DOCX
|
||
if (ext === 'xlsx') return OfficeFormat.XLSX
|
||
if (ext === 'pptx') return OfficeFormat.PPTX
|
||
return null
|
||
}
|
||
|
||
/**
|
||
* 根据格式获取对应的 Preset 类型
|
||
*/
|
||
export function getPresetType(format) {
|
||
switch (format) {
|
||
case OfficeFormat.DOCX:
|
||
return OfficePresetType.DOCS
|
||
case OfficeFormat.XLSX:
|
||
return OfficePresetType.SHEETS
|
||
case OfficeFormat.PPTX:
|
||
return OfficePresetType.SLIDES
|
||
default:
|
||
return null
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 创建 Univer 实例
|
||
*/
|
||
export async function createUniverInstance(container, options = {}) {
|
||
const {
|
||
format = OfficeFormat.DOCX,
|
||
locale = 'zh-CN',
|
||
theme = 'light'
|
||
} = options
|
||
|
||
const localeType = locale === 'zh-CN' ? LocaleType.ZH_CN : LocaleType.EN_US
|
||
// 合并所有语言包(包括 Slides)
|
||
const locales = locale === 'zh-CN'
|
||
? { [LocaleType.ZH_CN]: merge({}, DocsCoreZhCN, SheetsCoreZhCN, SlidesZhCN) }
|
||
: { [LocaleType.EN_US]: merge({}, DocsCoreEnUS, SheetsCoreEnUS, SlidesEnUS) }
|
||
|
||
const presets = []
|
||
const extraPlugins = []
|
||
|
||
// 根据格式添加对应的 Preset 和插件
|
||
switch (format) {
|
||
case OfficeFormat.DOCX:
|
||
// DOCX 只需要 DocsCorePreset
|
||
presets.push(UniverDocsCorePreset({
|
||
container,
|
||
theme: theme === 'dark' ? 'dark' : 'default'
|
||
}))
|
||
break
|
||
|
||
case OfficeFormat.XLSX:
|
||
// XLSX 只需要 SheetsCorePreset
|
||
presets.push(UniverSheetsCorePreset({
|
||
container,
|
||
theme: theme === 'dark' ? 'dark' : 'default'
|
||
}))
|
||
break
|
||
|
||
case OfficeFormat.PPTX:
|
||
// PPTX 需要 DocsCorePreset + Slides 插件
|
||
// DocsCorePreset 提供基础文档渲染能力
|
||
presets.push(UniverDocsCorePreset({
|
||
container,
|
||
theme: theme === 'dark' ? 'dark' : 'default'
|
||
}))
|
||
// Slides 插件提供演示文稿功能
|
||
// 注意:必须作为 plugins 而非 presets 传入
|
||
extraPlugins.push([UniverSlidesPlugin])
|
||
extraPlugins.push([UniverSlidesUIPlugin])
|
||
break
|
||
|
||
default:
|
||
// 默认使用 Docs 作为兜底
|
||
presets.push(UniverDocsCorePreset({
|
||
container,
|
||
theme: theme === 'dark' ? 'dark' : 'default'
|
||
}))
|
||
}
|
||
|
||
try {
|
||
const { univer, univerAPI } = createUniver({
|
||
locale: localeType,
|
||
locales,
|
||
presets,
|
||
plugins: extraPlugins.length > 0 ? extraPlugins : undefined,
|
||
collaboration: false // 纯前端模式,不启用协作
|
||
})
|
||
|
||
console.log(`[Univer] 初始化成功,格式: ${format}, 预设数量: ${presets.length}, 插件数量: ${extraPlugins.length}`)
|
||
return { univer, univerAPI }
|
||
} catch (error) {
|
||
console.error('[Univer] 初始化失败:', error)
|
||
throw new Error(`Univer 初始化失败: ${error.message}`)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Univer 编辑器实例包装类
|
||
*/
|
||
export class UniverEditorInstance {
|
||
constructor() {
|
||
this.univer = null
|
||
this.univerAPI = null
|
||
this.container = null
|
||
this.currentFormat = null
|
||
}
|
||
|
||
/**
|
||
* 初始化编辑器
|
||
*/
|
||
async init(container, options = {}) {
|
||
if (this.univer) {
|
||
await this.destroy()
|
||
}
|
||
|
||
this.container = container
|
||
this.currentFormat = options.format || OfficeFormat.DOCX
|
||
|
||
const result = await createUniverInstance(container, {
|
||
format: this.currentFormat,
|
||
...options
|
||
})
|
||
|
||
this.univer = result.univer
|
||
this.univerAPI = result.univerAPI
|
||
|
||
// 创建初始文档
|
||
if (this.currentFormat === OfficeFormat.XLSX) {
|
||
this.univerAPI.createWorkbook({})
|
||
} else {
|
||
this.univerAPI.createUniverDoc({})
|
||
}
|
||
|
||
return this
|
||
}
|
||
|
||
/**
|
||
* 从字节数组加载文档
|
||
*/
|
||
async loadFromBytes(bytes, format) {
|
||
if (!this.univerAPI) {
|
||
throw new Error('Univer 实例未初始化')
|
||
}
|
||
|
||
// 注意:纯前端模式下,Univer 不支持直接从 DOCX/XLSX/PPTX 字节流加载
|
||
// 这里需要使用快照模式或后端服务来解析
|
||
// 当前实现为占位,实际需要配合快照格式
|
||
console.warn('纯前端模式暂不支持从 DOCX/XLSX/PPTX 字节流加载,请使用快照模式')
|
||
return false
|
||
}
|
||
|
||
/**
|
||
* 导出为快照数据
|
||
*/
|
||
async exportSnapshot() {
|
||
if (!this.univerAPI) {
|
||
throw new Error('Univer 实例未初始化')
|
||
}
|
||
|
||
const activeDoc = this.univerAPI.getActiveDocument()
|
||
const activeSheet = this.univerAPI.getActiveWorkbook()
|
||
|
||
if (activeSheet) {
|
||
return {
|
||
type: OfficePresetType.SHEETS,
|
||
format: OfficeFormat.XLSX,
|
||
data: activeSheet.getSnapshot()
|
||
}
|
||
}
|
||
|
||
if (activeDoc) {
|
||
return {
|
||
type: OfficePresetType.DOCS,
|
||
format: OfficeFormat.DOCX,
|
||
data: activeDoc.getSnapshot()
|
||
}
|
||
}
|
||
|
||
return null
|
||
}
|
||
|
||
/**
|
||
* 从快照数据导入
|
||
*/
|
||
async importSnapshot(snapshot) {
|
||
if (!this.univerAPI || !snapshot?.data) {
|
||
throw new Error('无效的快照数据')
|
||
}
|
||
|
||
// 快照数据可以直接用于恢复文档状态
|
||
// 具体实现取决于 Univer API
|
||
console.log('导入快照:', snapshot.type, snapshot.format)
|
||
return true
|
||
}
|
||
|
||
/**
|
||
* 监听文档变化
|
||
*/
|
||
onChange(callback) {
|
||
if (!this.univerAPI) return
|
||
|
||
// Univer API 的事件监听
|
||
this.univerAPI.addEvent(this.univerAPI.Event.CommandExecuted, (event) => {
|
||
callback({
|
||
type: 'command',
|
||
data: event
|
||
})
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 销毁实例
|
||
*/
|
||
async destroy() {
|
||
if (this.univer) {
|
||
this.univer.dispose()
|
||
this.univer = null
|
||
this.univerAPI = null
|
||
this.container = null
|
||
this.currentFormat = null
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取当前格式
|
||
*/
|
||
getFormat() {
|
||
return this.currentFormat
|
||
}
|
||
|
||
/**
|
||
* 检查是否已初始化
|
||
*/
|
||
isInitialized() {
|
||
return this.univer !== null && this.univerAPI !== null
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 创建 Univer 编辑器实例
|
||
*/
|
||
export function createUniverEditor() {
|
||
return new UniverEditorInstance()
|
||
}
|
||
|
||
export default {
|
||
createUniverInstance,
|
||
createUniverEditor,
|
||
detectOfficeFormat,
|
||
getPresetType,
|
||
OfficeFormat,
|
||
OfficePresetType,
|
||
UniverEditorInstance
|
||
}
|