feat(电子表格): 电子表格编辑缓存
This commit is contained in:
@@ -8,8 +8,7 @@ ws = "ws"
|
||||
guid = "guid"
|
||||
yoy = "yoy"
|
||||
Ein = "Ein"
|
||||
cpy1 = "cpy1"
|
||||
cpy2 = "cpy2"
|
||||
getCellDatas = "getCellDatas"
|
||||
cpy = "cpy"
|
||||
Datas = "Datas"
|
||||
[files]
|
||||
extend-exclude = ["mapFiles/**", "docs"]
|
||||
|
||||
@@ -5045,6 +5045,10 @@ export default {
|
||||
'After deletion, all resources under this folder will be deleted, please proceed with caution.',
|
||||
save_before_leave: 'Do you want to save before leaving?',
|
||||
unsaved_changes: 'There are unsaved changes',
|
||||
confirm_exit_without_save: 'Current changes have not been saved. Are you sure you want to exit?',
|
||||
save_current_before_create_dataset: 'Please save the current spreadsheet first',
|
||||
cache_use_tips:
|
||||
'An unsaved spreadsheet from the previous session was found. Do you want to restore it?',
|
||||
preview_select_tips: 'Please select a spreadsheet on the left',
|
||||
loading: 'Loading...',
|
||||
no_data: 'No data',
|
||||
|
||||
@@ -4867,6 +4867,9 @@ export default {
|
||||
delete_folder_confirm: '刪除後,此文件夾下的所有資源都會被刪除,請謹慎操作。',
|
||||
save_before_leave: '離開前是否保存?',
|
||||
unsaved_changes: '有未保存的更改',
|
||||
confirm_exit_without_save: '目前的變更尚未儲存,確定退出嗎?',
|
||||
save_current_before_create_dataset: '請先儲存目前電子表格',
|
||||
cache_use_tips: '檢查到上次有電子表格未能正常儲存,是否使用上次未儲存的電子表格?',
|
||||
preview_select_tips: '請在左側選擇電子表格',
|
||||
loading: '加載中...',
|
||||
no_data: '暫無資料',
|
||||
|
||||
@@ -4891,6 +4891,9 @@ export default {
|
||||
delete_folder_confirm: '删除后,此文件夹下的所有资源都会被删除,请谨慎操作。',
|
||||
save_before_leave: '离开前是否保存?',
|
||||
unsaved_changes: '有未保存的更改',
|
||||
confirm_exit_without_save: '当前的更改尚未保存,确定退出吗?',
|
||||
save_current_before_create_dataset: '请先保存当前电子表格',
|
||||
cache_use_tips: '检查到上次有电子表格未能正常保存,是否使用上次未保存的电子表格?',
|
||||
preview_select_tips: '请在左侧选择电子表格',
|
||||
loading: '加载中...',
|
||||
no_data: '暂无数据',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, nextTick } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus-secondary'
|
||||
import { ElMessage } from 'element-plus-secondary'
|
||||
import Icon from '@/components/icon-custom/src/Icon.vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import icon_left_outlined from '@/assets/svg/icon_left_outlined.svg'
|
||||
@@ -14,7 +14,6 @@ const { t } = useI18n()
|
||||
|
||||
const props = defineProps<{
|
||||
name: string
|
||||
hasChanges?: boolean
|
||||
saving?: boolean
|
||||
publishing?: boolean
|
||||
recovering?: boolean
|
||||
@@ -71,20 +70,7 @@ const closeEditCanvasName = () => {
|
||||
}
|
||||
|
||||
const handleBack = () => {
|
||||
if (props.hasChanges) {
|
||||
ElMessageBox.confirm(t('components.sure_to_exit'), {
|
||||
confirmButtonText: t('commons.confirm'),
|
||||
cancelButtonText: t('commons.cancel'),
|
||||
confirmButtonType: 'primary',
|
||||
type: 'warning',
|
||||
autofocus: false,
|
||||
showClose: false
|
||||
}).then(() => {
|
||||
emit('back')
|
||||
})
|
||||
} else {
|
||||
emit('back')
|
||||
}
|
||||
}
|
||||
|
||||
const handleSave = () => {
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
serializeSheetData
|
||||
} from '../utils/univerConfig'
|
||||
import type { UniverInstance } from '../utils/univerConfig'
|
||||
import type { IWorkbookData } from '@univerjs/core'
|
||||
import { ICommandService, type IWorkbookData } from '@univerjs/core'
|
||||
import { IRenderManagerService } from '@univerjs/engine-render'
|
||||
import type { SpreadsheetMode } from '../types/mode'
|
||||
|
||||
@@ -24,7 +24,7 @@ const props = withDefaults(
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', value: Partial<IWorkbookData>): void
|
||||
(e: 'change', value: Partial<IWorkbookData>): void
|
||||
(e: 'change'): void
|
||||
(e: 'ready'): void
|
||||
}>()
|
||||
|
||||
@@ -33,6 +33,7 @@ let univerInstance: UniverInstance | null
|
||||
const isReady = ref(false)
|
||||
const isInitializing = ref(false)
|
||||
let rafId: number | null = null
|
||||
let commandExecutedDisposable: { dispose: () => void } | undefined
|
||||
|
||||
// Internal data tracking
|
||||
let workbookData: Partial<IWorkbookData> = props.modelValue || createDefaultWorkbookData()
|
||||
@@ -69,6 +70,9 @@ const initUniver = async () => {
|
||||
workbookData,
|
||||
{ mode: props.mode }
|
||||
)
|
||||
const commandService = univerInstance.univer.__getInjector().get(ICommandService)
|
||||
// Univer 的原生编辑、插件命令最终都会经过命令服务,由上层统一比较持久化快照。
|
||||
commandExecutedDisposable = commandService.onCommandExecuted(() => emit('change'))
|
||||
isReady.value = true
|
||||
emit('ready')
|
||||
} catch (error) {
|
||||
@@ -132,6 +136,8 @@ const resize = () => {
|
||||
* Dispose instance
|
||||
*/
|
||||
const disposeInstance = () => {
|
||||
commandExecutedDisposable?.dispose()
|
||||
commandExecutedDisposable = undefined
|
||||
if (rafId !== null) {
|
||||
cancelAnimationFrame(rafId)
|
||||
rafId = null
|
||||
|
||||
@@ -33,6 +33,9 @@ const emit = defineEmits<{
|
||||
// 注入插件配置
|
||||
const pluginConfig = inject('pluginConfig') as any
|
||||
const datasetFields = inject<Ref<{ dimensions: FieldItemData[]; quotas: FieldItemData[] }>>('datasetFields')
|
||||
const beforeCreateDataset = inject<() => boolean | Promise<boolean>>(
|
||||
'beforeCreateSpreadsheetDataset'
|
||||
)
|
||||
const datasetSelectRef = ref<InstanceType<typeof DatasetSelect>>()
|
||||
const collapsed = ref(false)
|
||||
const curDatasetWeight = ref(0)
|
||||
@@ -422,7 +425,11 @@ const openEmbeddedDatasetEditor = (datasetId?: string | number) => {
|
||||
useEmitt().emitter.emit('changeCurrentComponent', 'DatasetEditor')
|
||||
}
|
||||
|
||||
const handleAddDataset = () => {
|
||||
const handleAddDataset = async () => {
|
||||
if (beforeCreateDataset && !(await beforeCreateDataset())) {
|
||||
return
|
||||
}
|
||||
|
||||
if (isDataEaseBi.value) {
|
||||
openEmbeddedDatasetEditor()
|
||||
return
|
||||
|
||||
@@ -5,6 +5,7 @@ import DatasetPanel from '../dataset-panel/index.vue'
|
||||
import type { FieldItemData, PluginConfig, TablePluginConfig } from '../../types/plugin'
|
||||
import { ElMessage } from 'element-plus-secondary'
|
||||
import { pluginRuntimeRegistry } from '../../services/plugin-runtime.service'
|
||||
import { dispatchSpreadsheetContentChanged } from '../../utils/events'
|
||||
|
||||
const univerApi = inject('univerApi') as any
|
||||
|
||||
@@ -87,6 +88,7 @@ const updateConfig = async (key: string, value: any) => {
|
||||
}
|
||||
|
||||
updatePluginConfig(key, value)
|
||||
dispatchSpreadsheetContentChanged()
|
||||
if (key.startsWith('style.')) {
|
||||
await runtime?.applyStyle?.({ univerApi: api, config })
|
||||
}
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, shallowRef, provide, type Component } from 'vue'
|
||||
import {
|
||||
computed,
|
||||
ref,
|
||||
shallowRef,
|
||||
provide,
|
||||
nextTick,
|
||||
onBeforeMount,
|
||||
onMounted,
|
||||
onBeforeUnmount,
|
||||
type Component
|
||||
} from 'vue'
|
||||
import router from '@/router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus-secondary'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
@@ -20,7 +30,6 @@ import PluginRenderIndicator from './components/PluginRenderIndicator.vue'
|
||||
import DatasetReplacementDialog from './components/dataset-replacement/DatasetReplacementDialog.vue'
|
||||
import SpreadsheetPreviewOverlay from './components/SpreadsheetPreviewOverlay.vue'
|
||||
import { useLocaleStoreWithOut } from '@/store/modules/locale'
|
||||
import { nextTick, onBeforeMount } from 'vue'
|
||||
import type { IWorkbookData } from '@univerjs/core'
|
||||
import { ISidebarService } from '@univerjs/ui'
|
||||
import { createDefaultWorkbookData, parseSheetData, serializeSheetData } from './utils/univerConfig'
|
||||
@@ -34,6 +43,12 @@ import { pluginSnapshotCleaningService } from './services/plugin-snapshot-cleani
|
||||
import { pluginRuntimeRegistry } from './services/plugin-runtime.service'
|
||||
import { PluginRenderStatusService } from './plugins/DataEaseRuntimePlugin/services/table'
|
||||
import { PluginAdapterManager } from './types/adapter'
|
||||
import {
|
||||
deleteSpreadsheetDraftCache,
|
||||
getSpreadsheetDraftCache,
|
||||
setSpreadsheetDraftCache,
|
||||
type SpreadsheetDraftCache
|
||||
} from './utils/draft-cache'
|
||||
|
||||
const { t } = useI18n()
|
||||
const localeStore = useLocaleStoreWithOut()
|
||||
@@ -74,7 +89,138 @@ const editorComponent = shallowRef<Component | null>(null)
|
||||
const createLocalizedDefaultWorkbookData = () =>
|
||||
createDefaultWorkbookData(t('spreadsheet.default_sheet_name'))
|
||||
|
||||
let baselineSheetData = ''
|
||||
let baselineSpreadsheetName = ''
|
||||
let draftCacheTimer: ReturnType<typeof setTimeout> | undefined
|
||||
let draftCheckRevision = 0
|
||||
let draftCheckPending = false
|
||||
let discardAndLeave = false
|
||||
|
||||
const getCurrentRawSheetData = () =>
|
||||
univerSheetRef.value?.getSheetData() ||
|
||||
serializeSheetData(workbookData.value || createLocalizedDefaultWorkbookData())
|
||||
|
||||
const cleanSheetData = async (sheetData: string): Promise<string> => {
|
||||
const snapshot = parseSheetData(sheetData) ?? createLocalizedDefaultWorkbookData()
|
||||
const cleanedSnapshot = await pluginSnapshotCleaningService.clean(snapshot)
|
||||
return serializeSheetData(cleanedSnapshot)
|
||||
}
|
||||
|
||||
const updateSavedBaseline = (sheetData: string, name: string) => {
|
||||
baselineSheetData = sheetData
|
||||
baselineSpreadsheetName = name
|
||||
}
|
||||
|
||||
const buildDraftCache = (sheetData: string): SpreadsheetDraftCache | undefined => {
|
||||
if (routeMode.value !== 'edit' || !routeSheetId.value) {
|
||||
return undefined
|
||||
}
|
||||
return {
|
||||
sheetId: routeSheetId.value,
|
||||
sourceVersion: spreadsheetVersion.value,
|
||||
name: spreadsheetName.value,
|
||||
remark: spreadsheetRemark.value,
|
||||
sheetData,
|
||||
cachedAt: Date.now()
|
||||
}
|
||||
}
|
||||
|
||||
const cacheCurrentDraft = (sheetData: string) => {
|
||||
const cache = buildDraftCache(sheetData)
|
||||
if (!cache) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
setSpreadsheetDraftCache(cache)
|
||||
} catch (error) {
|
||||
// 本地存储空间不足不能影响正常编辑和保存。
|
||||
console.warn('[Spreadsheet] Failed to cache spreadsheet draft:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const syncDraftCache = async () => {
|
||||
if (routeMode.value !== 'edit' || !routeSheetId.value || !baselineSheetData) {
|
||||
return
|
||||
}
|
||||
|
||||
const currentRevision = draftCheckRevision
|
||||
try {
|
||||
const currentSheetData = await cleanSheetData(getCurrentRawSheetData())
|
||||
if (currentRevision !== draftCheckRevision) {
|
||||
return
|
||||
}
|
||||
|
||||
draftCheckPending = false
|
||||
const contentChanged = currentSheetData !== baselineSheetData
|
||||
const nameChanged = spreadsheetName.value !== baselineSpreadsheetName
|
||||
hasChanges.value = contentChanged || nameChanged
|
||||
if (hasChanges.value) {
|
||||
cacheCurrentDraft(currentSheetData)
|
||||
} else {
|
||||
deleteSpreadsheetDraftCache(routeSheetId.value)
|
||||
}
|
||||
} catch (error) {
|
||||
draftCheckPending = false
|
||||
hasChanges.value = true
|
||||
cacheCurrentDraft(getCurrentRawSheetData())
|
||||
console.warn('[Spreadsheet] Failed to compare spreadsheet draft:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const markSpreadsheetChanged = () => {
|
||||
if (routeMode.value !== 'edit' || !routeSheetId.value) {
|
||||
hasChanges.value = true
|
||||
return
|
||||
}
|
||||
|
||||
draftCheckRevision++
|
||||
draftCheckPending = true
|
||||
if (draftCacheTimer) {
|
||||
clearTimeout(draftCacheTimer)
|
||||
}
|
||||
// 合并连续输入和样式操作,避免每条 Univer 命令都序列化整个工作簿。
|
||||
draftCacheTimer = setTimeout(() => {
|
||||
draftCacheTimer = undefined
|
||||
void syncDraftCache()
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
const flushDraftBeforeLeaving = async () => {
|
||||
if (draftCacheTimer) {
|
||||
clearTimeout(draftCacheTimer)
|
||||
draftCacheTimer = undefined
|
||||
}
|
||||
while (draftCheckPending) {
|
||||
await syncDraftCache()
|
||||
}
|
||||
}
|
||||
|
||||
const cacheRawDraftBeforeUnload = () => {
|
||||
if (!hasChanges.value && !draftCheckPending) {
|
||||
return
|
||||
}
|
||||
if (
|
||||
!draftCheckPending &&
|
||||
routeSheetId.value &&
|
||||
getSpreadsheetDraftCache(routeSheetId.value)
|
||||
) {
|
||||
return
|
||||
}
|
||||
// 页面卸载阶段不能等待异步清理;恢复时会再次经过快照清理器。
|
||||
cacheCurrentDraft(getCurrentRawSheetData())
|
||||
}
|
||||
|
||||
const handleBeforeCreateDataset = async (): Promise<boolean> => {
|
||||
if (routeMode.value !== 'edit' || !routeSheetId.value) {
|
||||
ElMessage.warning(t('spreadsheet.save_current_before_create_dataset'))
|
||||
return false
|
||||
}
|
||||
await flushDraftBeforeLeaving()
|
||||
return true
|
||||
}
|
||||
|
||||
provide('pluginConfig', currentPluginConfig)
|
||||
provide('beforeCreateSpreadsheetDataset', handleBeforeCreateDataset)
|
||||
|
||||
const closeNativeSidebar = () => {
|
||||
const injector = univerInstanceRef.value?.univer?.__getInjector?.()
|
||||
@@ -102,6 +248,11 @@ useEmitt({
|
||||
}
|
||||
})
|
||||
|
||||
useEmitt({
|
||||
name: SPREADSHEET_EVENTS.CONTENT_CHANGED,
|
||||
callback: markSpreadsheetChanged
|
||||
})
|
||||
|
||||
useEmitt({
|
||||
name: SPREADSHEET_EVENTS.OPEN_PLUGIN_EDITOR,
|
||||
callback: (payload: PluginEditPayload) => {
|
||||
@@ -185,7 +336,7 @@ useEmitt({
|
||||
if (nextConfig) {
|
||||
currentPluginConfig.value = nextConfig
|
||||
}
|
||||
hasChanges.value = true
|
||||
markSpreadsheetChanged()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -205,13 +356,14 @@ const persistSpreadsheet = async (
|
||||
|
||||
saving.value = true
|
||||
try {
|
||||
await flushDraftBeforeLeaving()
|
||||
const rawSheetDataStr =
|
||||
univerSheetRef.value?.getSheetData() ||
|
||||
JSON.stringify(workbookData.value || createLocalizedDefaultWorkbookData())
|
||||
const cleanedSheetData = await pluginSnapshotCleaningService.clean(
|
||||
JSON.parse(rawSheetDataStr)
|
||||
)
|
||||
const currentSheetDataStr = JSON.stringify(cleanedSheetData)
|
||||
const currentSheetDataStr = serializeSheetData(cleanedSheetData)
|
||||
|
||||
const editor: SpreadsheetEditor = {
|
||||
name: destination ? destination.name : spreadsheetName.value,
|
||||
@@ -242,6 +394,12 @@ const persistSpreadsheet = async (
|
||||
resourceGroupOptShow.value = false
|
||||
spreadsheetName.value = destination.name
|
||||
}
|
||||
updateSavedBaseline(currentSheetDataStr, editor.name || spreadsheetName.value)
|
||||
deleteSpreadsheetDraftCache(result.id)
|
||||
// 保存请求期间工作簿仍可编辑,保存完成后重新比较,避免把后续输入误判为已保存。
|
||||
draftCheckRevision++
|
||||
draftCheckPending = true
|
||||
await syncDraftCache()
|
||||
return result
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
@@ -334,10 +492,14 @@ const handleRecoverPublished = async () => {
|
||||
spreadsheetVersion.value = result.version ?? spreadsheetVersion.value
|
||||
loading.value = true
|
||||
await nextTick()
|
||||
workbookData.value = clearUniverProtectionResources(
|
||||
const recoveredWorkbookData = clearUniverProtectionResources(
|
||||
parseSheetData(result.sheetData) ?? createLocalizedDefaultWorkbookData()
|
||||
)
|
||||
const recoveredSheetData = await cleanSheetData(serializeSheetData(recoveredWorkbookData))
|
||||
workbookData.value = parseSheetData(recoveredSheetData) ?? createLocalizedDefaultWorkbookData()
|
||||
updateSavedBaseline(recoveredSheetData, spreadsheetName.value)
|
||||
hasChanges.value = false
|
||||
deleteSpreadsheetDraftCache(routeSheetId.value)
|
||||
loading.value = false
|
||||
ElMessage.success(t('spreadsheet.recover_publish_success'))
|
||||
} catch (error) {
|
||||
@@ -364,6 +526,7 @@ const handleCancelPublish = async () => {
|
||||
status: SpreadsheetPublishStatus.Unpublished
|
||||
})
|
||||
spreadsheetStatus.value = result.status ?? SpreadsheetPublishStatus.Unpublished
|
||||
spreadsheetVersion.value = result.version ?? spreadsheetVersion.value
|
||||
ElMessage.success(t('spreadsheet.cancel_publish_success'))
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
@@ -374,23 +537,29 @@ const handleCancelPublish = async () => {
|
||||
}
|
||||
|
||||
const handleBack = async () => {
|
||||
if (hasChanges.value) {
|
||||
await flushDraftBeforeLeaving()
|
||||
const isCreateMode = routeMode.value !== 'edit' || !routeSheetId.value
|
||||
if (isCreateMode || hasChanges.value) {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
t('spreadsheet.save_before_leave'),
|
||||
t('spreadsheet.confirm_exit_without_save'),
|
||||
t('spreadsheet.unsaved_changes'),
|
||||
{
|
||||
confirmButtonText: t('spreadsheet.save'),
|
||||
cancelButtonText: t('spreadsheet.discard'),
|
||||
distinguishCancelAndClose: true
|
||||
confirmButtonText: t('commons.confirm'),
|
||||
cancelButtonText: t('commons.cancel'),
|
||||
confirmButtonType: 'primary',
|
||||
type: 'warning',
|
||||
autofocus: false,
|
||||
showClose: false
|
||||
}
|
||||
)
|
||||
await handleSave()
|
||||
} catch (e) {
|
||||
// User chose to discard or closed dialog
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
}
|
||||
router.push('/spreadsheet/index')
|
||||
discardAndLeave = true
|
||||
deleteSpreadsheetDraftCache(routeSheetId.value)
|
||||
await router.push('/spreadsheet/index')
|
||||
}
|
||||
|
||||
const handleDatasetBinding = async (binding: DatasetBindDTO) => {
|
||||
@@ -404,6 +573,7 @@ const handleDatasetUnbind = async (datasetId: number) => {
|
||||
const handleRename = (name: string) => {
|
||||
if (spreadsheetName.value === name) return
|
||||
spreadsheetName.value = name
|
||||
markSpreadsheetChanged()
|
||||
}
|
||||
|
||||
const handleExport = () => {
|
||||
@@ -481,7 +651,48 @@ onBeforeMount(async () => {
|
||||
spreadsheetInfo.status ?? SpreadsheetPublishStatus.Unpublished
|
||||
spreadsheetRemark.value = spreadsheetInfo.remark ?? ''
|
||||
spreadsheetVersion.value = spreadsheetInfo.version ?? 1
|
||||
workbookData.value = cleanedWorkbookData
|
||||
const serverSheetData = serializeSheetData(cleanedWorkbookData)
|
||||
const serverWorkbookData = cleanedWorkbookData
|
||||
updateSavedBaseline(serverSheetData, spreadsheetInfo.name)
|
||||
|
||||
let nextWorkbookData = serverWorkbookData
|
||||
const cachedDraft = getSpreadsheetDraftCache(routeSheetId.value)
|
||||
if (cachedDraft && cachedDraft.sourceVersion === spreadsheetVersion.value) {
|
||||
try {
|
||||
const cachedWorkbookData = clearUniverProtectionResources(
|
||||
parseSheetData(cachedDraft.sheetData) ?? createLocalizedDefaultWorkbookData()
|
||||
)
|
||||
const cachedSheetData = serializeSheetData(cachedWorkbookData)
|
||||
const cacheHasChanges =
|
||||
cachedSheetData !== serverSheetData || cachedDraft.name !== spreadsheetInfo.name
|
||||
if (cacheHasChanges) {
|
||||
await ElMessageBox.confirm(t('spreadsheet.cache_use_tips'), {
|
||||
confirmButtonText: t('visualization.yes'),
|
||||
cancelButtonText: t('visualization.no'),
|
||||
confirmButtonType: 'primary',
|
||||
type: 'warning',
|
||||
autofocus: false,
|
||||
showClose: false
|
||||
})
|
||||
nextWorkbookData = cachedWorkbookData
|
||||
spreadsheetName.value = cachedDraft.name
|
||||
spreadsheetRemark.value = cachedDraft.remark
|
||||
hasChanges.value = true
|
||||
// 工作簿完成恢复后,命令监听会重新生成清理后的缓存快照。
|
||||
cacheCurrentDraft(cachedSheetData)
|
||||
} else {
|
||||
deleteSpreadsheetDraftCache(routeSheetId.value)
|
||||
}
|
||||
} catch {
|
||||
// 用户拒绝恢复或缓存内容损坏时均回退到服务端版本。
|
||||
deleteSpreadsheetDraftCache(routeSheetId.value)
|
||||
}
|
||||
} else if (cachedDraft) {
|
||||
// 服务端版本已经变化时,旧缓存不能覆盖较新的数据。
|
||||
deleteSpreadsheetDraftCache(routeSheetId.value)
|
||||
}
|
||||
|
||||
workbookData.value = nextWorkbookData
|
||||
loading.value = false
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -495,13 +706,26 @@ onBeforeMount(async () => {
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('pagehide', cacheRawDraftBeforeUnload)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('pagehide', cacheRawDraftBeforeUnload)
|
||||
if (draftCacheTimer) {
|
||||
clearTimeout(draftCacheTimer)
|
||||
}
|
||||
if (!discardAndLeave) {
|
||||
cacheRawDraftBeforeUnload()
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="editorRootRef" class="spreadsheet-editor" v-loading="loading">
|
||||
<SpreadsheetToolbar
|
||||
:name="spreadsheetName"
|
||||
:has-changes="hasChanges"
|
||||
:saving="saving"
|
||||
:publishing="publishing"
|
||||
:recovering="recovering"
|
||||
@@ -525,6 +749,7 @@ onBeforeMount(async () => {
|
||||
ref="univerSheetRef"
|
||||
:model-value="workbookData || undefined"
|
||||
mode="edit"
|
||||
@change="markSpreadsheetChanged"
|
||||
@ready="handleUniverReady"
|
||||
/>
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useEmitt } from '@/hooks/web/useEmitt'
|
||||
import type { SpreadsheetFilterConfig } from '../../../types/plugin'
|
||||
import { dispatchSpreadsheetContentChanged } from '../../../utils/events'
|
||||
|
||||
export interface SpreadsheetFilterQueryPayload {
|
||||
unitId?: string
|
||||
@@ -81,6 +82,7 @@ export const dispatchSpreadsheetFilterConfigChange = (config: SpreadsheetFilterC
|
||||
currentConfig = config
|
||||
currentVisible = config.visible
|
||||
emitter.emit(SPREADSHEET_FILTER_EVENTS.CONFIG_CHANGE, config)
|
||||
dispatchSpreadsheetContentChanged()
|
||||
}
|
||||
|
||||
export const dispatchSpreadsheetFilterRequestConfigContext = (
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { useCache } from '@/hooks/web/useCache'
|
||||
|
||||
const SPREADSHEET_DRAFT_CACHE_PREFIX = 'DE-SPREADSHEET-CATCH-'
|
||||
|
||||
export interface SpreadsheetDraftCache {
|
||||
sheetId: string
|
||||
sourceVersion: number
|
||||
name: string
|
||||
remark: string
|
||||
sheetData: string
|
||||
cachedAt: number
|
||||
}
|
||||
|
||||
const { wsCache } = useCache('localStorage')
|
||||
|
||||
const getCacheKey = (sheetId: string | number) =>
|
||||
`${SPREADSHEET_DRAFT_CACHE_PREFIX}${sheetId}`
|
||||
|
||||
export const getSpreadsheetDraftCache = (
|
||||
sheetId: string | number
|
||||
): SpreadsheetDraftCache | undefined => {
|
||||
const cache = wsCache.get(getCacheKey(sheetId)) as SpreadsheetDraftCache | undefined
|
||||
if (!cache || String(cache.sheetId) !== String(sheetId) || !cache.sheetData) {
|
||||
return undefined
|
||||
}
|
||||
return cache
|
||||
}
|
||||
|
||||
export const setSpreadsheetDraftCache = (cache: SpreadsheetDraftCache): void => {
|
||||
wsCache.set(getCacheKey(cache.sheetId), cache)
|
||||
}
|
||||
|
||||
export const deleteSpreadsheetDraftCache = (sheetId?: string | number): void => {
|
||||
if (sheetId === undefined || sheetId === null) {
|
||||
return
|
||||
}
|
||||
wsCache.delete(getCacheKey(sheetId))
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import { useEmitt } from '@/hooks/web/useEmitt'
|
||||
|
||||
export const SPREADSHEET_EVENTS = {
|
||||
OPEN_PLUGIN_EDITOR: 'OPEN_PLUGIN_EDITOR',
|
||||
CLOSE_PLUGIN_EDITOR: 'CLOSE_PLUGIN_EDITOR',
|
||||
@@ -9,7 +11,12 @@ export const SPREADSHEET_EVENTS = {
|
||||
REFRESH_PLUGIN_DATA: 'REFRESH_PLUGIN_DATA',
|
||||
OPEN_COMPONENT_DATASET_REPLACEMENT: 'OPEN_COMPONENT_DATASET_REPLACEMENT',
|
||||
DATASET_REPLACEMENT_COMPLETED: 'DATASET_REPLACEMENT_COMPLETED',
|
||||
CONTENT_CHANGED: 'SPREADSHEET_CONTENT_CHANGED',
|
||||
CELL_SELECTED: 'CELL_SELECTED',
|
||||
PLACEHOLDER_TOOLTIP_SHOW: 'PLACEHOLDER_TOOLTIP_SHOW',
|
||||
PLACEHOLDER_TOOLTIP_HIDE: 'PLACEHOLDER_TOOLTIP_HIDE'
|
||||
} as const
|
||||
|
||||
export const dispatchSpreadsheetContentChanged = () => {
|
||||
useEmitt().emitter.emit(SPREADSHEET_EVENTS.CONTENT_CHANGED)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user