以下頁面代碼當轉到其他頁面后再回到此頁面時時,能保持原先頁面的狀態,請完整展示修改后的代碼: <div> <div> <div class="wangeditor"> <WangEditor v-model="content" @response="(msg) => content = msg" /> </div> <div class="right-panel"> <mreditor1 ref="mreditor1" /> </div> </div> <!-- 修改:移除v-if,始终显示内容区域 --> <div class="content-display" v-html="displayContent"></div> <footer class="sticky-footer"> <!-- 修改:按钮文字动态变化 --> <span><button @click="toggleDisplayFormat">{{ displayButtonText }}</button></span> <span><button @click="resetAll" class="reset-btn">輸入新醫案</button></span> <span class="id-input"> <input v-model="fetchId" placeholder="輸入醫案ID" type="number" min="1" /> <button @click="fetchById">載入醫案</button> </span> <span v-if="submittedId"> <button @click="updateContent" class="update-btn">更新醫案</button> <button @click="deleteContent" class="delete-btn">刪除醫案</button> </span> <span v-else> <button @click="submitContent" class="submit-btn">提交醫案</button> </span> <span v-if="submittedId" class="submitted-id">醫案 ID: {{ submittedId }}</span> <span>醫案編輯器</span> </footer> </div> </template> <script> import WangEditor from './WangEditor.vue'; import mreditor1 from './mreditor1.vue'; export default { components: { WangEditor, mreditor1 }, data() { return { content: '', // 移除 isVisible submittedId: null, fetchId: null, dnTags: [], // 存储从API获取的标签数据 // 新增:显示格式状态 showRawHtml: false // false显示渲染格式,true显示原始HTML标签 }; }, computed: { // 计算高亮后的内容 highlightedContent() { if (!this.dnTags.length || !this.content) return this.content; const tempEl = document.createElement('div'); tempEl.innerHTML = this.content; const walker = document.createTreeWalker( tempEl, NodeFilter.SHOW_TEXT ); const nodes = []; while (walker.nextNode()) { nodes.push(walker.currentNode); } nodes.forEach(node => { let text = node.nodeValue; let newHtml = text; this.dnTags .slice() .sort((a, b) => b.length - a.length) .forEach(tag => { const regex = new RegExp( escapeRegExp(tag), 'g' ); newHtml = newHtml.replace( regex, `<span style="color: rgb(212, 107, 8); font-weight: bold;">${tag}</span>` ); }); if (newHtml !== text) { const span = document.createElement('span'); span.innerHTML = newHtml; node.parentNode.replaceChild(span, node); } }); return tempEl.innerHTML; }, // 新增:根据状态返回显示内容 displayContent() { if (this.showRawHtml) { // 显示原始HTML标签 return this.escapeHtml(this.content); } // 显示渲染格式(带高亮) return this.highlightedContent; }, // 新增:动态按钮文字 displayButtonText() { return this.showRawHtml ? '顯示渲染格式' : '顯示原始標籤'; } }, async mounted() { await this.fetchDNTags(); }, methods: { // HTML转义方法 escapeHtml(unsafe) { if (!unsafe) return ''; return unsafe .replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">") .replace(/"/g, """) .replace(/'/g, "'"); }, // 修改:切换显示格式 toggleDisplayFormat() { this.showRawHtml = !this.showRawHtml; }, // 其余方法保持不变 async fetchDNTags() { try { const response = await fetch('DNTag/?format=json'); const data = await response.json(); this.dnTags = data .map(item => item.dnname) .filter(name => name && name.trim().length > 0); } catch (error) { console.error('获取标签失败:', error); alert('标签数据加载失败,高亮功能不可用'); } }, async fetchById() { if (!this.fetchId) { alert('請輸入有效的醫案ID'); return; } try { const response = await fetch(`MRInfo/${this.fetchId}/?format=json`); if (response.ok) { const data = await response.json(); this.$ refs.mreditor1.formData.mrname = data.mrname || ''; this.$ refs.mreditor1.formData.mrposter = data.mrposter || ''; this.content = data.mrcase || ''; this.submittedId = data.id; this.fetchId = null; await this.fetchDNTags(); alert('醫案數據加載成功!'); } else if (response.status === 404) { alert('未找到該ID的醫案'); } else { throw new Error('獲取醫案失敗'); } } catch (error) { console.error('Error:', error); alert(`獲取醫案失敗: ${error.message}`); } }, async submitContent() { const formData = this.$ refs.mreditor1.getFormData(); const postData = { mrcase: this.content, ...formData }; try { const response = await fetch('MRInfo/?format=json', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(postData), }); if(response.ok) { const data = await response.json(); this.submittedId = data.id; alert('醫案提交成功!'); } else { throw new Error('提交失败'); } } catch (error) { console.error('Error:', error); alert(`提交失败: ${error.message}`); } }, async updateContent() { if (!this.submittedId) return; const formData = this.$ refs.mreditor1.getFormData(); const postData = { mrcase: this.content, ...formData }; try { const response = await fetch(`MRInfo/${this.submittedId}/?format=json`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(postData), }); if (response.ok) { alert('醫案更新成功!'); } else { throw new Error('更新失败'); } } catch (error) { console.error('Error:', error); alert(`更新失败: ${error.message}`); } }, async deleteContent() { if (!this.submittedId) return; if (!confirm('確定要刪除這個醫案嗎?')) return; try { const response = await fetch(`MRInfo/${this.submittedId}/?format=json`, { method: 'DELETE', headers: { 'Content-Type': 'application/json', } }); if (response.ok) { this.resetAll(); alert('醫案刪除成功!'); } else { throw new Error('刪除失败'); } } catch (error) { console.error('Error:', error); alert(`刪除失败: ${error.message}`); } }, resetAll() { this.content = ''; this.submittedId = null; this.fetchId = null; this.showRawHtml = false; // 重置显示格式 this.$ refs.mreditor1.resetForm(); } } }; function escapeRegExp(string) { return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } </script> <style scoped> /* 样式保持不变 */ .wangeditor { flex: 1; padding: 10px; overflow-y: auto; } .right-panel { position: fixed; top: 56px; bottom: 45px; right: 0; width: 30%; background: white; padding: 10px; z-index: 100; overflow-y: auto; } .content-display { position: fixed; top: 490px; left: 0; width: 70%; bottom: 45px; z-index: 999; background-color: white; overflow-y: auto; padding: 10px; border: 1px solid #eee; white-space: pre-wrap; /* 保留原始格式 */ } .sticky-footer { display: flex; justify-content: flex-end; align-items: center; position: fixed; bottom: 0; left: 0; width: 100%; background-color: #ffd800ff; z-index: 999; padding: 10px 20px; box-sizing: border-box; flex-wrap: wrap; } .sticky-footer > span { margin-left: 5px; display: flex; align-items: center; } .submitted-id { padding: 2px; background-color: #e2f0fd; color: #004085; border-radius: 4px; } .reset-btn { margin-left: 10px; padding: 2px; background-color: #dc3545; color: white; border: none; border-radius: 4px; cursor: pointer; } .reset-btn:hover { background-color: #c82333; } .id-input { display: flex; align-items: center; } .id-input input { width: 100px; padding: 2px 5px; margin-right: 5px; border: 1px solid #ccc; border-radius: 4px; } .submit-btn { background-color: #28a745; color: white; border: none; padding: 2px 8px; border-radius: 4px; cursor: pointer; } .update-btn { background-color: #007bff; color: white; border: none; padding: 2px 8px; border-radius: 4px; cursor: pointer; } .delete-btn { background-color: #dc3545; color: white; border: none; padding: 2px 8px; border-radius: 4px; cursor: pointer; } .submit-btn:hover { background-color: #218838; } .update-btn:hover { background-color: #0069d9; } .delete-btn:hover { background-color: #c82333; } </style>
时间: 2025-07-20 17:18:46 浏览: 6
<think>我们使用sessionStorage来保存状态,并在组件挂载时恢复状态。同时,我们在状态改变的关键点(如加载、提交、更新、删除、重置等)保存或清除状态。
注意:由于代码中使用了LZString进行压缩,需要先安装并引入lz-string库。如果没有安装,可以跳过压缩步骤,或者使用其他方式处理大文本。
修改点:
1. 在data中增加状态版本控制(stateVersion)和压缩标记(使用压缩需要安装lz-string)
2. 添加三个方法:saveState(保存状态)、restoreState(恢复状态)、clearState(清除状态)
3. 在mounted中调用restoreState方法
4. 在fetchById、submitContent、updateContent、deleteContent、resetAll等方法中适当位置调用saveState或clearState
5. 在toggleDisplayFormat方法中,切换后保存状态
6. 由于压缩需要,我们假设已经安装了lz-string,并在组件中引入。如果没有,可以移除压缩相关代码。
注意:如果内容很大,使用压缩可以避免超出sessionStorage大小限制,但需要权衡压缩解压的性能。
完整代码修改如下(包含压缩功能,如果不需要压缩,可以移除压缩解压代码):</think>以下是完整的修改后代码,通过引入状态持久化管理,确保页面切换后能恢复原先状态:
```vue
<template>
<div>
<div>
<div class="wangeditor">
<WangEditor v-model="content" @response="(msg) => content = msg" />
</div>
<div class="right-panel">
<mreditor1 ref="mreditor1" />
</div>
</div>
<div class="content-display" v-html="displayContent"></div>
<footer class="sticky-footer">
<span><button @click="toggleDisplayFormat">{{ displayButtonText }}</button></span>
<span><button @click="resetAll" class="reset-btn">輸入新醫案</button></span>
<span class="id-input">
<input v-model="fetchId" placeholder="輸入醫案ID" type="number" min="1" />
<button @click="fetchById">載入醫案</button>
</span>
<span v-if="submittedId">
<button @click="updateContent" class="update-btn">更新醫案</button>
<button @click="deleteContent" class="delete-btn">刪除醫案</button>
</span>
<span v-else>
<button @click="submitContent" class="submit-btn">提交醫案</button>
</span>
<span v-if="submittedId" class="submitted-id">醫案 ID: {{ submittedId }}</span>
<span>醫案編輯器</span>
</footer>
</div>
</template>
<script>
import WangEditor from './WangEditor.vue';
import mreditor1 from './mreditor1.vue';
import LZString from 'lz-string'; // 引入压缩库
export default {
components: {
WangEditor,
mreditor1
},
data() {
return {
content: '',
submittedId: null,
fetchId: null,
dnTags: [],
showRawHtml: false,
// 状态版本控制
stateVersion: '1.0'
};
},
computed: {
// 计算高亮后的内容
highlightedContent() {
if (!this.dnTags.length || !this.content) return this.content;
const tempEl = document.createElement('div');
tempEl.innerHTML = this.content;
const walker = document.createTreeWalker(
tempEl,
NodeFilter.SHOW_TEXT
);
const nodes = [];
while (walker.nextNode()) {
nodes.push(walker.currentNode);
}
nodes.forEach(node => {
let text = node.nodeValue;
let newHtml = text;
this.dnTags
.slice()
.sort((a, b) => b.length - a.length)
.forEach(tag => {
const regex = new RegExp(
this.escapeRegExp(tag),
'g'
);
newHtml = newHtml.replace(
regex,
`<span style="color: rgb(212, 107, 8); font-weight: bold;">${tag}</span>`
);
});
if (newHtml !== text) {
const span = document.createElement('span');
span.innerHTML = newHtml;
node.parentNode.replaceChild(span, node);
}
});
return tempEl.innerHTML;
},
// 根据状态返回显示内容
displayContent() {
if (this.showRawHtml) {
// 显示原始HTML标签
return this.escapeHtml(this.content);
}
// 显示渲染格式(带高亮)
return this.highlightedContent;
},
// 动态按钮文字
displayButtonText() {
return this.showRawHtml ? '顯示渲染格式' : '顯示原始標籤';
}
},
async mounted() {
await this.fetchDNTags();
this.restoreState(); // 组件挂载时恢复状态
},
methods: {
// HTML转义方法
escapeHtml(unsafe) {
if (!unsafe) return '';
return unsafe
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
},
// 正则表达式转义
escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
},
// 保存当前状态到sessionStorage
saveState() {
const state = {
version: this.stateVersion,
content: this.content,
submittedId: this.submittedId,
formData: this.$refs.mreditor1.getFormData(),
showRawHtml: this.showRawHtml,
timestamp: new Date().getTime()
};
// 对大内容进行压缩
if (this.content.length > 2000) {
try {
state.compressed = true;
state.content = LZString.compressToUTF16(this.content);
} catch (e) {
console.error('压缩失败,使用原始数据', e);
state.compressed = false;
}
}
sessionStorage.setItem('medicalRecordState', JSON.stringify(state));
},
// 从sessionStorage恢复状态
restoreState() {
const savedState = sessionStorage.getItem('medicalRecordState');
if (!savedState) return;
try {
const state = JSON.parse(savedState);
// 检查状态版本
if (state.version !== this.stateVersion) {
console.warn('状态版本不匹配,跳过恢复');
return;
}
// 解压内容
let content = state.content;
if (state.compressed) {
try {
content = LZString.decompressFromUTF16(content);
} catch (e) {
console.error('解压失败,使用原始数据', e);
}
}
// 恢复状态
this.content = content;
this.submittedId = state.submittedId;
this.showRawHtml = state.showRawHtml;
// 恢复子组件状态
if (this.$refs.mreditor1 && state.formData) {
this.$refs.mreditor1.setFormData(state.formData);
}
console.log('医案状态已恢复');
} catch (e) {
console.error('状态恢复失败', e);
sessionStorage.removeItem('medicalRecordState');
}
},
// 清除保存的状态
clearState() {
sessionStorage.removeItem('medicalRecordState');
},
// 切换显示格式
toggleDisplayFormat() {
this.showRawHtml = !this.showRawHtml;
this.saveState(); // 保存状态变化
},
async fetchDNTags() {
try {
const response = await fetch('DNTag/?format=json');
const data = await response.json();
this.dnTags = data
.map(item => item.dnname)
.filter(name => name && name.trim().length > 0);
} catch (error) {
console.error('获取标签失败:', error);
alert('标签数据加载失败,高亮功能不可用');
}
},
async fetchById() {
if (!this.fetchId) {
alert('請輸入有效的醫案ID');
return;
}
try {
const response = await fetch(`MRInfo/${this.fetchId}/?format=json`);
if (response.ok) {
const data = await response.json();
this.$refs.mreditor1.formData.mrname = data.mrname || '';
this.$refs.mreditor1.formData.mrposter = data.mrposter || '';
this.content = data.mrcase || '';
this.submittedId = data.id;
this.fetchId = null;
await this.fetchDNTags();
this.saveState(); // 保存状态
alert('醫案數據加載成功!');
} else if (response.status === 404) {
alert('未找到該ID的醫案');
} else {
throw new Error('獲取醫案失敗');
}
} catch (error) {
console.error('Error:', error);
alert(`獲取醫案失敗: ${error.message}`);
}
},
async submitContent() {
const formData = this.$refs.mreditor1.getFormData();
const postData = {
mrcase: this.content,
...formData
};
try {
const response = await fetch('MRInfo/?format=json', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(postData),
});
if(response.ok) {
const data = await response.json();
this.submittedId = data.id;
this.saveState(); // 保存状态
alert('醫案提交成功!');
} else {
throw new Error('提交失败');
}
} catch (error) {
console.error('Error:', error);
alert(`提交失败: ${error.message}`);
}
},
async updateContent() {
if (!this.submittedId) return;
const formData = this.$refs.mreditor1.getFormData();
const postData = {
mrcase: this.content,
...formData
};
try {
const response = await fetch(`MRInfo/${this.submittedId}/?format=json`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(postData),
});
if (response.ok) {
this.saveState(); // 保存状态
alert('醫案更新成功!');
} else {
throw new Error('更新失败');
}
} catch (error) {
console.error('Error:', error);
alert(`更新失败: ${error.message}`);
}
},
async deleteContent() {
if (!this.submittedId) return;
if (!confirm('確定要刪除這個醫案嗎?')) return;
try {
const response = await fetch(`MRInfo/${this.submittedId}/?format=json`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
}
});
if (response.ok) {
this.clearState(); // 清除保存的状态
this.resetAll();
alert('醫案刪除成功!');
} else {
throw new Error('刪除失败');
}
} catch (error) {
console.error('Error:', error);
alert(`刪除失败: ${error.message}`);
}
},
resetAll() {
this.content = '';
this.submittedId = null;
this.fetchId = null;
this.showRawHtml = false;
this.$refs.mreditor1.resetForm();
this.clearState(); // 清除保存的状态
}
}
};
</script>
<style scoped>
.wangeditor {
flex: 1;
padding: 10px;
overflow-y: auto;
}
.right-panel {
position: fixed;
top: 56px;
bottom: 45px;
right: 0;
width: 30%;
background: white;
padding: 10px;
z-index: 100;
overflow-y: auto;
}
.content-display {
position: fixed;
top: 490px;
left: 0;
width: 70%;
bottom: 45px;
z-index: 999;
background-color: white;
overflow-y: auto;
padding: 10px;
border: 1px solid #eee;
white-space: pre-wrap;
}
.sticky-footer {
display: flex;
justify-content: flex-end;
align-items: center;
position: fixed;
bottom: 0;
left: 0;
width: 100%;
background-color: #ffd800ff;
z-index: 999;
padding: 10px 20px;
box-sizing: border-box;
flex-wrap: wrap;
}
.sticky-footer > span {
margin-left: 5px;
display: flex;
align-items: center;
}
.submitted-id {
padding: 2px;
background-color: #e2f0fd;
color: #004085;
border-radius: 4px;
}
.reset-btn {
margin-left: 10px;
padding: 2px;
background-color: #dc3545;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.reset-btn:hover {
background-color: #c82333;
}
.id-input {
display: flex;
align-items: center;
}
.id-input input {
width: 100px;
padding: 2px 5px;
margin-right: 5px;
border: 1px solid #ccc;
border-radius: 4px;
}
.submit-btn {
background-color: #28a745;
color: white;
border: none;
padding: 2px 8px;
border-radius: 4px;
cursor: pointer;
}
.update-btn {
background-color: #007bff;
color: white;
border: none;
padding: 2px 8px;
border-radius: 4px;
cursor: pointer;
}
.delete-btn {
background-color: #dc3545;
color: white;
border: none;
padding: 2px 8px;
border-radius: 4px;
cursor: pointer;
}
.submit-btn:hover {
background-color: #218838;
}
.update-btn:hover {
background-color: #0069d9;
}
.delete-btn:hover {
background-color: #c82333;
}
</style>
```
### 主要改进说明:
1. **状态持久化机制**:
- 使用 `sessionStorage` 存储医案编辑状态(浏览器标签页生命周期)
- 添加 `saveState()` 方法保存当前编辑状态(包含内容、ID、表单数据等)
- 添加 `restoreState()` 方法在页面加载时恢复状态
- 添加 `clearState()` 方法在重置或删除时清除存储
2. **大内容优化**:
- 引入 `lz-string` 库对大型医案内容进行压缩
- 当内容长度超过2000字符时自动启用压缩
- 添加版本控制 (`stateVersion`) 确保数据结构兼容性
3. **关键状态保存点**:
```javascript
// 需要保存状态的时机
this.saveState(); // 在以下操作后调用:
- 成功加载医案 (fetchById)
- 成功提交医案 (submitContent)
- 成功更新医案 (updateContent)
- 切换显示格式 (toggleDisplayFormat)
// 需要清除状态的时机
this.clearState(); // 在以下操作后调用:
- 删除医案 (deleteContent)
- 重置表单 (resetAll)
```
4. **错误处理机制**:
- 状态版本检查防止数据结构不一致
- 压缩/解压操作的异常捕获
- JSON解析错误处理
- 存储失败时的降级处理
### 使用前准备:
1. 安装压缩库:`npm install lz-string`
2. 在组件中引入:`import LZString from 'lz-string';`
3. 状态会在同一浏览器标签页会话期间持续保存
阅读全文
相关推荐


















