Browse Source

feat: [落笔留痕] 测试页面

master
memorylkf 6 days ago
parent
commit
109a9fe35b
4 changed files with 298 additions and 281 deletions
  1. +5
    -5
      src/views/business/template/list.vue
  2. +183
    -174
      src/views/lblh/components/AutoSaveManager.js
  3. +106
    -98
      src/views/lblh/components/storage/ServerStorage.js
  4. +4
    -4
      src/views/lblh/index.vue

+ 5
- 5
src/views/business/template/list.vue View File

@ -47,8 +47,8 @@
</el-table-column> </el-table-column>
<el-table-column label="模板类型" prop="type" align="center" width="100"> <el-table-column label="模板类型" prop="type" align="center" width="100">
<template slot-scope="scope"> <template slot-scope="scope">
<span v-if="scope.row.type === 10">敏感模板</span>
<span v-if="scope.row.type === 1">普通模板</span>
<span v-if="scope.row.type === 10">高风险表单</span>
<span v-if="scope.row.type === 1">普通表单</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="$t('form.operate')" fixed="right" align="center" width="100"> <el-table-column :label="$t('form.operate')" fixed="right" align="center" width="100">
@ -100,8 +100,8 @@
<el-col :span="24"> <el-col :span="24">
<el-form-item label="类型:" prop="type"> <el-form-item label="类型:" prop="type">
<el-select v-model="infoDialog.formData.type" :placeholder="$t('form.placeholderSelect')" style="width:100%"> <el-select v-model="infoDialog.formData.type" :placeholder="$t('form.placeholderSelect')" style="width:100%">
<el-option label="普通模板" :value="1" />
<el-option label="敏感模板" :value="10" />
<el-option label="普通表单" :value="1" />
<el-option label="高风险表单" :value="10" />
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
@ -131,7 +131,7 @@ const EmptyDialogData = {
deptId: '', deptId: '',
status:10, status:10,
needPre: 10, needPre: 10,
type: 1,
type: 10,
} }
export default { export default {
name: "Template", name: "Template",

+ 183
- 174
src/views/lblh/components/AutoSaveManager.js View File

@ -1,50 +1,53 @@
// import MemoryStorage from './storage/MemoryStorage.js'; // import MemoryStorage from './storage/MemoryStorage.js';
import LocalStorage from './storage/LocalStorage.js';
import LocalStorage from './storage/LocalStorage.js'
// import SessionStorage from './storage/SessionStorage.js'; // import SessionStorage from './storage/SessionStorage.js';
import IndexedDBStorage from './storage/IndexedDBStorage.js';
import ServerStorage from './storage/ServerStorage.js';
import IndexedDBStorage from './storage/IndexedDBStorage.js'
import ServerStorage from './storage/ServerStorage.js'
/** /**
* 自动保存管理器 * 自动保存管理器
* 协调各种存储方式处理冲突合并 * 协调各种存储方式处理冲突合并
*/ */
class AutoSaveManager { class AutoSaveManager {
constructor(key, serverOptions = {
endpoint:process.env.VUE_APP_BASE_API,
syncEndpoint:process.env.VUE_APP_BASE_API,
historyEndpoint:process.env.VUE_APP_BASE_API,
}) {
this.key = key;
constructor(
key,
serverOptions = {
endpoint: process.env.VUE_APP_BASE_API,
syncEndpoint: process.env.VUE_APP_BASE_API,
historyEndpoint: process.env.VUE_APP_BASE_API
}
) {
this.key = key
try { try {
this.storages = { this.storages = {
// memory: new MemoryStorage(key), // memory: new MemoryStorage(key),
// localStorage: new LocalStorage(key),
localStorage: new LocalStorage(key),
// sessionStorage: new SessionStorage(key), // sessionStorage: new SessionStorage(key),
indexedDB: new IndexedDBStorage(key),
// indexedDB: new IndexedDBStorage(key),
server: new ServerStorage(key, serverOptions) server: new ServerStorage(key, serverOptions)
};
}
} catch (error) { } catch (error) {
console.error('初始化存储失败:', error);
console.error('初始化存储失败:', error)
// 如果某个存储初始化失败,只使用可用的存储 // 如果某个存储初始化失败,只使用可用的存储
this.storages = { this.storages = {
// memory: new MemoryStorage(key), // memory: new MemoryStorage(key),
localStorage: new LocalStorage(key),
localStorage: new LocalStorage(key)
// sessionStorage: new SessionStorage(key) // sessionStorage: new SessionStorage(key)
};
}
} }
this.states = {};
this.initStates();
this.isSaving = false;
this.saveTimer = null;
this.debounceDelay = 1000;// 防抖延迟
this.currentContent = '';
this.lastSavedContent = '';
this.states = {}
this.initStates()
this.isSaving = false
this.saveTimer = null
this.debounceDelay = 1000 // 防抖延迟
this.currentContent = ''
this.lastSavedContent = ''
// 异步初始化 // 异步初始化
this.initPromise = this.init();
this.initPromise = this.init()
} }
/** /**
@ -52,12 +55,12 @@ class AutoSaveManager {
*/ */
initStates() { initStates() {
for (const type in this.storages) { for (const type in this.storages) {
this.states[type] = {
status: 'idle',
lastSaved: null,
this.states[type] = {
status: 'idle',
lastSaved: null,
error: null, error: null,
initialized: false initialized: false
};
}
} }
} }
@ -65,7 +68,7 @@ class AutoSaveManager {
* 等待初始化完成 * 等待初始化完成
*/ */
async waitForInit() { async waitForInit() {
await this.initPromise;
await this.initPromise
} }
/** /**
@ -74,17 +77,17 @@ class AutoSaveManager {
async init() { async init() {
try { try {
// 加载所有存储的数据 // 加载所有存储的数据
await this.loadFromAllStorages();
await this.loadFromAllStorages()
// 设置页面关闭前保存 // 设置页面关闭前保存
this.setupBeforeUnload();
this.setupBeforeUnload()
// 设置页面崩溃检测 // 设置页面崩溃检测
this.setupCrashDetection();
console.log(`AutoSaveManager 初始化完成,key: ${this.key}`);
this.setupCrashDetection()
console.log(`AutoSaveManager 初始化完成,key: ${this.key}`)
} catch (error) { } catch (error) {
console.error('AutoSaveManager 初始化失败:', error);
console.error('AutoSaveManager 初始化失败:', error)
} }
} }
@ -92,41 +95,43 @@ class AutoSaveManager {
* 从所有存储加载数据并合并 * 从所有存储加载数据并合并
*/ */
async loadFromAllStorages() { async loadFromAllStorages() {
const results = {};
const results = {}
// 并行加载所有存储 // 并行加载所有存储
const promises = Object.entries(this.storages).map(async ([type, storage]) => {
try {
// 确保存储已初始化
if (storage.waitForInit) {
await storage.waitForInit();
const promises = Object.entries(this.storages).map(
async ([type, storage]) => {
try {
// 确保存储已初始化
if (storage.waitForInit) {
await storage.waitForInit()
}
const result = await storage.load()
results[type] = result
this.updateState(type, 'loaded', {
...result,
initialized: true
})
return result
} catch (error) {
console.error(`加载${type}存储失败:`, error)
this.updateState(type, 'error', {
error: error.message,
initialized: false
})
return null
} }
const result = await storage.load();
results[type] = result;
this.updateState(type, 'loaded', {
...result,
initialized: true
});
return result;
} catch (error) {
console.error(`加载${type}存储失败:`, error);
this.updateState(type, 'error', {
error: error.message,
initialized: false
});
return null;
} }
});
await Promise.allSettled(promises);
)
await Promise.allSettled(promises)
// 选择最新版本的内容 // 选择最新版本的内容
// this.currentContent = this.resolveConflicts(results); // this.currentContent = this.resolveConflicts(results);
this.lastSavedContent = this.currentContent;
return this.currentContent;
this.lastSavedContent = this.currentContent
return this.currentContent
} }
/** /**
@ -135,31 +140,31 @@ class AutoSaveManager {
* @returns {string} 合并后的内容 * @returns {string} 合并后的内容
*/ */
resolveConflicts(results) { resolveConflicts(results) {
let latestContent = '';
let latestTimestamp = 0;
let latestContent = ''
let latestTimestamp = 0
Object.entries(results).forEach(([type, result]) => { Object.entries(results).forEach(([type, result]) => {
if (result && result.success && result.data) { if (result && result.success && result.data) {
let data = result.data;
let timestamp = 0;
let content = '';
let data = result.data
let timestamp = 0
let content = ''
if (typeof data === 'object' && data !== null) { if (typeof data === 'object' && data !== null) {
timestamp = data.timestamp || data.lastModified || 0;
content = data.content || '';
timestamp = data.timestamp || data.lastModified || 0
content = data.content || ''
} else if (typeof data === 'string') { } else if (typeof data === 'string') {
content = data;
timestamp = Date.now(); // 字符串数据给一个默认时间戳
content = data
timestamp = Date.now() // 字符串数据给一个默认时间戳
} }
if (timestamp > latestTimestamp) { if (timestamp > latestTimestamp) {
latestTimestamp = timestamp;
latestContent = content;
latestTimestamp = timestamp
latestContent = content
} }
} }
});
})
return latestContent || '';
return latestContent || ''
} }
/** /**
@ -167,22 +172,22 @@ class AutoSaveManager {
* @param {string} content - 要保存的内容 * @param {string} content - 要保存的内容
*/ */
save(content) { save(content) {
this.currentContent = content;
this.currentContent = content
// 如果内容没有变化,不保存 // 如果内容没有变化,不保存
if (content === this.lastSavedContent) { if (content === this.lastSavedContent) {
return;
return
} }
// 清除之前的定时器 // 清除之前的定时器
if (this.saveTimer) { if (this.saveTimer) {
clearTimeout(this.saveTimer);
clearTimeout(this.saveTimer)
} }
// 设置新的定时器 // 设置新的定时器
this.saveTimer = setTimeout(() => { this.saveTimer = setTimeout(() => {
this.doSave(content);
}, this.debounceDelay);
this.doSave(content)
}, this.debounceDelay)
} }
/** /**
@ -191,51 +196,53 @@ class AutoSaveManager {
*/ */
async doSave(content) { async doSave(content) {
if (this.isSaving) { if (this.isSaving) {
return;
return
} }
this.isSaving = true;
this.lastSavedContent = content;
this.isSaving = true
this.lastSavedContent = content
try { try {
// 并行保存到所有存储 // 并行保存到所有存储
const promises = Object.entries(this.storages).map(async ([type, storage]) => {
try {
// 检查存储是否可用
if (!storage || this.states[type]?.error) {
this.updateState(type, 'error', {
error: '存储不可用',
retry: true
});
return null;
}
this.updateState(type, 'saving');
const result = await storage.save(content);
if (result && result.success) {
this.updateState(type, 'saved', result);
} else {
const promises = Object.entries(this.storages).map(
async ([type, storage]) => {
try {
// 检查存储是否可用
if (!storage || this.states[type]?.error) {
this.updateState(type, 'error', {
error: '存储不可用',
retry: true
})
return null
}
this.updateState(type, 'saving')
const result = await storage.save(content)
if (result && result.success) {
this.updateState(type, 'saved', result)
} else {
this.updateState(type, 'error', {
error: result?.error || '保存失败',
retry: true
})
}
return result
} catch (error) {
console.error(`保存到${type}失败:`, error)
this.updateState(type, 'error', { this.updateState(type, 'error', {
error: result?.error || '保存失败',
error: error.message,
retry: true retry: true
});
})
return null
} }
return result;
} catch (error) {
console.error(`保存到${type}失败:`, error);
this.updateState(type, 'error', {
error: error.message,
retry: true
});
return null;
} }
});
)
await Promise.allSettled(promises);
await Promise.allSettled(promises)
} finally { } finally {
this.isSaving = false;
this.isSaving = false
} }
} }
@ -251,10 +258,10 @@ class AutoSaveManager {
status, status,
...data, ...data,
lastUpdated: Date.now() lastUpdated: Date.now()
};
}
if (status === 'saved' && data.timestamp) { if (status === 'saved' && data.timestamp) {
this.states[type].lastSaved = data.timestamp;
this.states[type].lastSaved = data.timestamp
} }
} }
@ -263,7 +270,7 @@ class AutoSaveManager {
* @returns {object} 状态对象 * @returns {object} 状态对象
*/ */
getAllStates() { getAllStates() {
return { ...this.states };
return { ...this.states }
} }
/** /**
@ -273,9 +280,9 @@ class AutoSaveManager {
*/ */
async getHistory(storageType) { async getHistory(storageType) {
if (this.storages[storageType]) { if (this.storages[storageType]) {
return await this.storages[storageType].getHistory();
return await this.storages[storageType].getHistory()
} }
return { success: false, error: '存储类型不存在' };
return { success: false, error: '存储类型不存在' }
} }
/** /**
@ -285,12 +292,12 @@ class AutoSaveManager {
*/ */
async restoreFromHistory(storageType, versionData) { async restoreFromHistory(storageType, versionData) {
if (this.storages[storageType]) { if (this.storages[storageType]) {
const content = versionData.content || versionData;
await this.doSave(content);
this.currentContent = content;
return { success: true, content };
const content = versionData.content || versionData
await this.doSave(content)
this.currentContent = content
return { success: true, content }
} }
return { success: false, error: '存储类型不存在' };
return { success: false, error: '存储类型不存在' }
} }
/** /**
@ -300,9 +307,9 @@ class AutoSaveManager {
window.addEventListener('beforeunload', (event) => { window.addEventListener('beforeunload', (event) => {
if (this.currentContent !== this.lastSavedContent) { if (this.currentContent !== this.lastSavedContent) {
// 同步保存(不防抖) // 同步保存(不防抖)
this.doSaveSync(this.currentContent);
this.doSaveSync(this.currentContent)
} }
});
})
} }
/** /**
@ -311,15 +318,15 @@ class AutoSaveManager {
*/ */
async doSaveSync(content) { async doSaveSync(content) {
// 只保存到本地存储,确保数据不丢失 // 只保存到本地存储,确保数据不丢失
const localStorages = ['memory', 'localStorage', 'sessionStorage'];
const localStorages = ['memory', 'localStorage', 'sessionStorage']
localStorages.forEach(async (type) => { localStorages.forEach(async (type) => {
try { try {
await this.storages[type].save(content);
await this.storages[type].save(content)
} catch (error) { } catch (error) {
console.error(`页面关闭前保存到${type}失败:`, error);
console.error(`页面关闭前保存到${type}失败:`, error)
} }
});
})
} }
/** /**
@ -330,23 +337,23 @@ class AutoSaveManager {
document.addEventListener('visibilitychange', () => { document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') { if (document.visibilityState === 'visible') {
// 页面恢复显示,检查是否需要同步 // 页面恢复显示,检查是否需要同步
this.checkAndSync();
this.checkAndSync()
} }
});
})
// 监听页面冻结和恢复 // 监听页面冻结和恢复
document.addEventListener('freeze', () => { document.addEventListener('freeze', () => {
localStorage.setItem(`pageFrozen_${this.key}`, Date.now());
});
localStorage.setItem(`pageFrozen_${this.key}`, Date.now())
})
document.addEventListener('resume', () => { document.addEventListener('resume', () => {
const frozenTime = localStorage.getItem(`pageFrozen_${this.key}`);
const frozenTime = localStorage.getItem(`pageFrozen_${this.key}`)
if (frozenTime && Date.now() - frozenTime > 5000) { if (frozenTime && Date.now() - frozenTime > 5000) {
// 页面被冻结超过5秒,可能是崩溃恢复 // 页面被冻结超过5秒,可能是崩溃恢复
this.showCrashNotification();
this.showCrashNotification()
} }
localStorage.removeItem(`pageFrozen_${this.key}`);
});
localStorage.removeItem(`pageFrozen_${this.key}`)
})
} }
/** /**
@ -354,11 +361,11 @@ class AutoSaveManager {
*/ */
async checkAndSync() { async checkAndSync() {
// 重新加载所有数据并合并 // 重新加载所有数据并合并
await this.loadFromAllStorages();
await this.loadFromAllStorages()
// 如果有服务器存储,尝试同步离线队列 // 如果有服务器存储,尝试同步离线队列
if (this.storages.server) { if (this.storages.server) {
this.storages.server.processQueue();
this.storages.server.processQueue()
} }
} }
@ -366,7 +373,7 @@ class AutoSaveManager {
* 显示崩溃通知 * 显示崩溃通知
*/ */
showCrashNotification() { showCrashNotification() {
const notification = document.createElement('div');
const notification = document.createElement('div')
notification.style.cssText = ` notification.style.cssText = `
position: fixed; position: fixed;
top: 20px; top: 20px;
@ -377,16 +384,16 @@ class AutoSaveManager {
border-radius: 5px; border-radius: 5px;
z-index: 10000; z-index: 10000;
box-shadow: 0 2px 10px rgba(0,0,0,0.2); box-shadow: 0 2px 10px rgba(0,0,0,0.2);
`;
`
notification.innerHTML = ` notification.innerHTML = `
<strong>页面恢复</strong><br> <strong>页面恢复</strong><br>
检测到页面可能发生了异常已自动恢复您的数据 检测到页面可能发生了异常已自动恢复您的数据
`;
document.body.appendChild(notification);
`
document.body.appendChild(notification)
setTimeout(() => { setTimeout(() => {
notification.remove();
}, 5000);
notification.remove()
}, 5000)
} }
/** /**
@ -394,20 +401,22 @@ class AutoSaveManager {
* @returns {Promise<Array>} 备份文件列表 * @returns {Promise<Array>} 备份文件列表
*/ */
async getBackupFiles() { async getBackupFiles() {
const backupPromises = Object.entries(this.storages).map(async ([type, storage]) => {
if (typeof storage.getBackupFiles === 'function') {
try {
const result = await storage.getBackupFiles();
return { type, ...result };
} catch (error) {
return { type, success: false, error: error.message };
const backupPromises = Object.entries(this.storages).map(
async ([type, storage]) => {
if (typeof storage.getBackupFiles === 'function') {
try {
const result = await storage.getBackupFiles()
return { type, ...result }
} catch (error) {
return { type, success: false, error: error.message }
}
} }
return { type, success: false, error: '不支持备份管理' }
} }
return { type, success: false, error: '不支持备份管理' };
});
)
const results = await Promise.all(backupPromises);
return results;
const results = await Promise.all(backupPromises)
return results
} }
/** /**
@ -415,10 +424,10 @@ class AutoSaveManager {
*/ */
async saveImmediately(currentContent) { async saveImmediately(currentContent) {
if (this.saveTimer) { if (this.saveTimer) {
clearTimeout(this.saveTimer);
clearTimeout(this.saveTimer)
} }
await this.doSave(currentContent);
await this.doSave(currentContent)
} }
} }
export default AutoSaveManager;
export default AutoSaveManager

+ 106
- 98
src/views/lblh/components/storage/ServerStorage.js View File

@ -6,33 +6,35 @@ import { getToken } from '@/utils/auth'
*/ */
class ServerStorage { class ServerStorage {
constructor(key, options = {}) { constructor(key, options = {}) {
this.key = key;
this.key = key
this.options = { this.options = {
endpoint: options.endpoint || '/system/business/test/testLblh', endpoint: options.endpoint || '/system/business/test/testLblh',
syncEndpoint: options.syncEndpoint || '/system/business/test/testLblhHistory',
historyEndpoint: options.historyEndpoint || '/system/business/test/testLblhHistory',
syncEndpoint:
options.syncEndpoint || '/system/business/test/testLblhHistory',
historyEndpoint:
options.historyEndpoint || '/system/business/test/testLblhHistory',
retryCount: options.retryCount || 3, //最多重试次数 retryCount: options.retryCount || 3, //最多重试次数
retryDelay: options.retryDelay || 1000,//重试等待 毫秒
retryDelay: options.retryDelay || 1000, //重试等待 毫秒
...options ...options
};
}
this.queue = [];
this.isOnline = navigator.onLine;
this.isSyncing = false;
this.initOfflineQueue();
this.setupNetworkListeners();
this.queue = []
this.isOnline = navigator.onLine
this.isSyncing = false
this.initOfflineQueue()
this.setupNetworkListeners()
} }
/** /**
* 初始化离线队列 * 初始化离线队列
*/ */
initOfflineQueue() { initOfflineQueue() {
const queueStr = localStorage.getItem(`hxhq_serverQueue_${this.key}`);
const queueStr = localStorage.getItem(`hxhq_serverQueue_${this.key}`)
if (queueStr) { if (queueStr) {
try { try {
this.queue = JSON.parse(queueStr);
this.queue = JSON.parse(queueStr)
} catch (error) { } catch (error) {
this.queue = [];
this.queue = []
} }
} }
} }
@ -42,20 +44,23 @@ class ServerStorage {
*/ */
setupNetworkListeners() { setupNetworkListeners() {
window.addEventListener('online', () => { window.addEventListener('online', () => {
this.isOnline = true;
this.processQueue();
});
this.isOnline = true
this.processQueue()
})
window.addEventListener('offline', () => { window.addEventListener('offline', () => {
this.isOnline = false;
});
this.isOnline = false
})
} }
/** /**
* 保存队列到本地存储 * 保存队列到本地存储
*/ */
saveQueue() { saveQueue() {
localStorage.setItem(`hxhq_serverQueue_${this.key}`, JSON.stringify(this.queue));
localStorage.setItem(
`hxhq_serverQueue_${this.key}`,
JSON.stringify(this.queue)
)
} }
/** /**
@ -63,34 +68,34 @@ class ServerStorage {
*/ */
async processQueue() { async processQueue() {
if (this.isSyncing || !this.isOnline || this.queue.length === 0) { if (this.isSyncing || !this.isOnline || this.queue.length === 0) {
return;
return
} }
this.isSyncing = true;
this.isSyncing = true
while (this.queue.length > 0 && this.isOnline) { while (this.queue.length > 0 && this.isOnline) {
const item = this.queue[0];
const item = this.queue[0]
try { try {
await this.sendToServer(item.content, item.timestamp, item.version);
this.queue.shift(); // 发送成功,从队列移除
this.saveQueue();
await this.sendToServer(item.content, item.timestamp, item.version)
this.queue.shift() // 发送成功,从队列移除
this.saveQueue()
} catch (error) { } catch (error) {
console.error('同步失败:', error);
item.retryCount = (item.retryCount || 0) + 1;
console.error('同步失败:', error)
item.retryCount = (item.retryCount || 0) + 1
if (item.retryCount >= this.options.retryCount) { if (item.retryCount >= this.options.retryCount) {
this.queue.shift(); // 超过重试次数,移除
this.saveQueue();
this.queue.shift() // 超过重试次数,移除
this.saveQueue()
} else { } else {
// 重试等待 // 重试等待
await new Promise(resolve =>
await new Promise((resolve) =>
setTimeout(resolve, this.options.retryDelay * item.retryCount) setTimeout(resolve, this.options.retryDelay * item.retryCount)
);
)
} }
} }
} }
this.isSyncing = false;
this.isSyncing = false
} }
/** /**
@ -101,30 +106,32 @@ class ServerStorage {
*/ */
async sendToServer(content, timestamp, version) { async sendToServer(content, timestamp, version) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('POST', this.options.endpoint);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.setRequestHeader('Authorization', 'Bearer ' + getToken()); // 让每个请求携带自定义token 请根据实际情况自行修改
const xhr = new XMLHttpRequest()
xhr.open('POST', this.options.endpoint)
xhr.setRequestHeader('Content-Type', 'application/json')
xhr.setRequestHeader('Authorization', 'Bearer ' + getToken()) // 让每个请求携带自定义token 请根据实际情况自行修改
xhr.onload = () => { xhr.onload = () => {
if (xhr.status === 200 || xhr.status === 201) { if (xhr.status === 200 || xhr.status === 201) {
resolve(JSON.parse(xhr.responseText));
resolve(JSON.parse(xhr.responseText))
} else { } else {
reject(new Error(`服务器错误: ${xhr.status}`));
reject(new Error(`服务器错误: ${xhr.status}`))
} }
};
xhr.onerror = () => reject(new Error('网络请求失败'));
xhr.ontimeout = () => reject(new Error('请求超时'));
xhr.send(JSON.stringify({
key: this.key,
content,
timestamp,
version,
syncedAt: Date.now()
}));
});
}
xhr.onerror = () => reject(new Error('网络请求失败'))
xhr.ontimeout = () => reject(new Error('请求超时'))
xhr.send(
JSON.stringify({
key: this.key,
content,
timestamp,
version,
syncedAt: Date.now()
})
)
})
} }
/** /**
@ -134,22 +141,22 @@ class ServerStorage {
*/ */
async save(content) { async save(content) {
try { try {
const timestamp = Date.now();
const version = await this.getNextVersion();
const timestamp = Date.now()
const version = await this.getNextVersion()
if (this.isOnline) { if (this.isOnline) {
try { try {
const result = await this.sendToServer(content, timestamp, version);
const result = await this.sendToServer(content, timestamp, version)
return { return {
success: true, success: true,
timestamp, timestamp,
version, version,
storage: 'server', storage: 'server',
synced: true synced: true
};
}
} catch (error) { } catch (error) {
// 服务器保存失败,加入离线队列 // 服务器保存失败,加入离线队列
console.warn('服务器保存失败,加入离线队列:', error);
console.warn('服务器保存失败,加入离线队列:', error)
} }
} }
@ -159,8 +166,8 @@ class ServerStorage {
timestamp, timestamp,
version, version,
addedAt: Date.now() addedAt: Date.now()
});
this.saveQueue();
})
this.saveQueue()
return { return {
success: true, success: true,
@ -169,13 +176,13 @@ class ServerStorage {
storage: 'server', storage: 'server',
synced: false, synced: false,
queued: true queued: true
};
}
} catch (error) { } catch (error) {
return { return {
success: false, success: false,
error: error.message, error: error.message,
storage: 'server' storage: 'server'
};
}
} }
} }
@ -185,40 +192,39 @@ class ServerStorage {
*/ */
async load() { async load() {
try { try {
if (this.isOnline) {
try {
const response = await fetch(`${this.options.syncEndpoint}?key=${this.key}`);
if (response.ok) {
const data = await response.json();
return {
success: true,
data,
storage: 'server',
fromServer: true
};
}
} catch (error) {
console.warn('从服务器加载失败:', error);
}
}
// if (this.isOnline) {
// try {
// const response = await fetch(`${this.options.syncEndpoint}?key=${this.key}`);
// if (response.ok) {
// const data = await response.json();
// return {
// success: true,
// data,
// storage: 'server',
// fromServer: true
// };
// }
// } catch (error) {
// console.warn('从服务器加载失败:', error);
// }
// }
// 返回队列中的最新数据 // 返回队列中的最新数据
const latestQueued = this.queue.length > 0
? this.queue[this.queue.length - 1]
: null;
const latestQueued =
this.queue.length > 0 ? this.queue[this.queue.length - 1] : null
return { return {
success: true, success: true,
data: latestQueued, data: latestQueued,
storage: 'server', storage: 'server',
fromServer: false fromServer: false
};
}
} catch (error) { } catch (error) {
return { return {
success: false, success: false,
error: error.message, error: error.message,
storage: 'server' storage: 'server'
};
}
} }
} }
@ -233,26 +239,28 @@ class ServerStorage {
success: false, success: false,
error: '网络未连接', error: '网络未连接',
storage: 'server' storage: 'server'
};
}
} }
const response = await fetch(`${this.options.historyEndpoint}?key=${this.key}`);
const response = await fetch(
`${this.options.historyEndpoint}?key=${this.key}`
)
if (!response.ok) { if (!response.ok) {
throw new Error(`服务器错误: ${response.status}`);
throw new Error(`服务器错误: ${response.status}`)
} }
const data = await response.json();
const data = await response.json()
return { return {
success: true, success: true,
data, data,
storage: 'server' storage: 'server'
};
}
} catch (error) { } catch (error) {
return { return {
success: false, success: false,
error: error.message, error: error.message,
storage: 'server' storage: 'server'
};
}
} }
} }
@ -262,13 +270,13 @@ class ServerStorage {
*/ */
async getNextVersion() { async getNextVersion() {
try { try {
const result = await this.load();
const result = await this.load()
if (result.success && result.data) { if (result.success && result.data) {
return result.data.version + 1;
return result.data.version + 1
} }
return 1;
return 1
} catch (error) { } catch (error) {
return 1;
return 1
} }
} }
@ -282,8 +290,8 @@ class ServerStorage {
isOnline: this.isOnline, isOnline: this.isOnline,
isSyncing: this.isSyncing, isSyncing: this.isSyncing,
nextRetry: this.queue.length > 0 ? this.queue[0].retryCount || 0 : 0 nextRetry: this.queue.length > 0 ? this.queue[0].retryCount || 0 : 0
};
}
} }
} }
export default ServerStorage;
export default ServerStorage

+ 4
- 4
src/views/lblh/index.vue View File

@ -35,8 +35,8 @@
<h3 style="color: red;">通过多层存储确保在各种异常情况下网络中断浏览器崩溃存储限制等用户内容都能得到妥善保存</h3> <h3 style="color: red;">通过多层存储确保在各种异常情况下网络中断浏览器崩溃存储限制等用户内容都能得到妥善保存</h3>
<h2>分层存储数据流向用户输入 本地持久化 云端</h2> <h2>分层存储数据流向用户输入 本地持久化 云端</h2>
<p> 1.服务器存储云端同步跨设备访问</p> <p> 1.服务器存储云端同步跨设备访问</p>
<p> 2.IndexedDB存储浏览器内置 NoSQL 数据库所有pc浏览器都支持移动端浏览器也支持存储数据可达50%的磁盘容量并且支持事务索引关闭标签页/窗口 数据保留刷新页面 数据保留</p>
<p> 3.LocalStorage本地存储 持久化跨会话保存关闭标签页/窗口 数据保留刷新页面 数据保留如果IndexedDB初始化失败则启用</p>
<p> 2.LocalStorage本地存储 持久化跨会话保存关闭标签页/窗口 数据保留刷新页面 数据保留</p>
<h2>实时保存</h2> <h2>实时保存</h2>
<p>3.输入结束失去焦点后触发多存储并行保存</p> <p>3.输入结束失去焦点后触发多存储并行保存</p>
<p>4.页面关闭前强制同步保存</p> <p>4.页面关闭前强制同步保存</p>
@ -75,8 +75,8 @@ export default {
content8: '', content8: '',
dataSelect: [{ value: '迈瑞', label: '迈瑞' },{ value: '大疆', label: '大疆' }], dataSelect: [{ value: '迈瑞', label: '迈瑞' },{ value: '大疆', label: '大疆' }],
serverConfig: { serverConfig: {
endpoint: '/api/save-text',
syncEndpoint: '/api/sync-text',
endpoint: '/dev-api/system/business/test/testLblh',
syncEndpoint: '/dev-api/system/business/test/testLblh2',
historyEndpoint: '/api/text-history', historyEndpoint: '/api/text-history',
retryCount: 3, retryCount: 3,
retryDelay: 1000 retryDelay: 1000

Loading…
Cancel
Save