"新增:便签编辑页面预览模式功能并完善删除动画"

This commit is contained in:
yuantao
2025-10-17 13:00:48 +08:00
parent e019d7050b
commit 836a3e1916
3 changed files with 255 additions and 38 deletions

View File

@@ -1,25 +1,29 @@
<template>
<ion-page>
<div class="container">
<Header :onBack="handleCancel" :onAction="handleAction" actionIcon="save" />
<!-- 头部编辑模式 -->
<Header v-if="isEditorFocus" :onBack="handleCancel" :onAction="handleAction" actionIcon="edit" />
<!-- 头部预览模式 -->
<Header v-else ref="headerRef" :onBack="handleCancel" :onAction="handleAction" actionIcon="preview" />
<section>
<!-- 顶部信息栏 -->
<div class="header-info">
<span class="edit-time">{{ formattedTime }}</span>
<span>|</span>
<span class="word-count">{{ wordCount }}</span>
</div>
<!-- 顶部信息栏 -->
<div class="header-info">
<span class="edit-time">{{ formattedTime }}</span>
<span>|</span>
<span class="word-count">{{ wordCount }}</span>
</div>
<!-- 富文本编辑器 -->
<div class="editor-container">
<RichTextEditor ref="editorRef" :modelValue="content" @update:modelValue="handleContentChange" class="rich-text-editor" />
</div>
<!-- 富文本编辑器 -->
<div class="editor-container">
<RichTextEditor ref="editorRef" :modelValue="content" @update:modelValue="handleContentChange" @focus="handleEditorFocus" @blur="handleEditorBlur" class="rich-text-editor" />
</div>
</section>
</div>
</ion-page>
</template>
<script setup>
import { ref, computed, onMounted, nextTick, watch } from 'vue'
import { ref, computed, onMounted, nextTick, watch, onBeforeUnmount } from 'vue'
import { useRouter } from 'vue-router'
import { useAppStore } from '../stores/useAppStore'
import Header from '../components/Header.vue'
@@ -40,6 +44,9 @@ const noteId = computed(() => props.id || props.noteId)
const store = useAppStore()
const router = useRouter()
const editorRef = ref(null)
const headerRef = ref(null)
// 是否聚焦编辑器
const isEditorFocus = ref(false)
// 设置便签内容的函数
// 用于在编辑器中加载指定便签的内容
@@ -170,53 +177,119 @@ const handleSave = async () => {
// 获取编辑器中的实际内容
const editorContent = editorRef.value ? editorRef.value.getContent() : content.value
if (isEditing && existingNote) {
// Update existing note
// 检查内容是否为空
if (isContentEmpty(editorContent)) {
// 如果是编辑模式且内容为空,则删除便签
if (isEditing && existingNote) {
await store.deleteNote(noteId.value)
console.log('空便签已删除')
// 删除后返回便签列表页面
router.push('/notes')
return
}
} else if (isEditing && existingNote) {
// 更新现有便签
await store.updateNote(noteId.value, {
content: editorContent,
})
console.log('便签已保存')
} else {
// Create new note
// 创建新便签
await store.addNote({
content: editorContent,
isStarred: false,
})
console.log('新便签已创建')
}
// Navigate back to the previous screen
router.push('/notes')
// 保存后切换到预览模式(失去编辑器焦点)
isEditorFocus.value = false
} catch (error) {
// In a full implementation, show an alert or toast
console.log('Save error: Failed to save note. Please try again.')
}
}
// 处理取消
const handleCancel = () => {
// Check if there are unsaved changes
const hasUnsavedChanges = content.value !== (existingNote?.content || '')
// 检查内容是否为空(无实质性内容)
const isContentEmpty = content => {
if (!content) return true
if (hasUnsavedChanges) {
showAlert.value = true
} else {
router.push('/notes')
// 移除HTML标签和空白字符后检查是否为空
const plainText = content.replace(/<[^>]*>/g, '').trim()
if (plainText === '') return true
// 检查是否只有空的HTML元素
const strippedContent = content
.replace(/\s+/g, '')
.replace(/<br>/g, '')
.replace(/<br\/>/g, '')
if (strippedContent === '<p></p>' || strippedContent === '<div></div>' || strippedContent === '') return true
return false
}
// 自动保存便签(仅在非主动保存操作时调用)
const autoSaveNote = async () => {
try {
// 获取编辑器中的实际内容
const editorContent = editorRef.value ? editorRef.value.getContent() : content.value
if (isEditing && existingNote) {
// 检查内容是否为空,如果为空则删除便签
if (isContentEmpty(editorContent)) {
// 删除便签
await store.deleteNote(noteId.value)
console.log('空便签已自动删除')
} else if (editorContent !== (existingNote?.content || '')) {
// 更新现有便签(仅当内容有变化时)
await store.updateNote(noteId.value, {
content: editorContent,
})
console.log('便签已自动保存')
}
} else if (!isContentEmpty(editorContent)) {
// 创建新便签(仅当有内容时)
// 检查是否已经存在相同内容的便签以避免重复创建
const existingNotes = store.notes.filter(n => n.content === editorContent && !n.isDeleted)
if (existingNotes.length === 0) {
await store.addNote({
content: editorContent,
isStarred: false,
})
console.log('新便签已自动保存')
}
}
} catch (error) {
console.error('自动保存失败:', error)
}
}
// 处理取消
const handleCancel = async () => {
// 自动保存便签
await autoSaveNote()
// 直接导航回便签列表页面,因为已经处理了保存或删除逻辑
router.push('/notes')
}
// 处理创建(用于新建便签)
const handleCreate = async () => {
try {
// 获取编辑器中的实际内容
const editorContent = editorRef.value ? editorRef.value.getContent() : content.value
// Create new note
await store.addNote({
content: editorContent,
isStarred: false,
})
// 只有当有内容时才创建新便签
if (!isContentEmpty(editorContent)) {
await store.addNote({
content: editorContent,
isStarred: false,
})
console.log('新便签已创建')
}
// Navigate back to the previous screen
router.push('/notes')
// 创建后切换到预览模式(失去编辑器焦点)
isEditorFocus.value = false
} catch (error) {
// In a full implementation, show an alert or toast
console.log('Create error: Failed to create note. Please try again.')
@@ -235,12 +308,92 @@ const handleAction = actionType => {
// 通过editorRef调用RichTextEditor组件的方法来插入图片
editorRef.value.insertImage()
}
} else if (actionType === 'delete') {
// 删除便签
handleDelete()
} else if (actionType === 'share') {
// 分享便签
handleShare()
}
}
const setShowAlert = value => {
showAlert.value = value
}
// 处理编辑器获得焦点
const handleEditorFocus = () => {
isEditorFocus.value = true
}
// 处理编辑器失去焦点
const handleEditorBlur = () => {
isEditorFocus.value = false
}
// 处理删除便签
const handleDelete = async () => {
if (isEditing && existingNote) {
// 播放删除按钮动画
if (headerRef.value && headerRef.value.playDeleteAnimation) {
headerRef.value.playDeleteAnimation()
}
// 等待动画播放完成后再执行删除操作
// 15帧 * 50ms = 750ms再加上一些缓冲时间
setTimeout(async () => {
try {
// 删除便签
await store.deleteNote(noteId.value)
console.log('便签已删除')
// 返回便签列表页面
router.push('/notes')
} catch (error) {
console.error('删除便签失败:', error)
}
}, 800) // 等待约800ms让动画播放完成
}
}
// 处理分享便签
const handleShare = () => {
// 获取编辑器中的实际内容
const editorContent = editorRef.value ? editorRef.value.getContent() : content.value
// 移除HTML标签获取纯文本内容用于分享
const plainText = editorContent.replace(/<[^>]*>/g, '').trim()
if (plainText) {
// 在实际应用中,这里会调用设备的分享功能
// 为了演示我们使用Web Share API如果支持
if (navigator.share) {
navigator
.share({
title: '分享便签',
text: plainText,
})
.catch(error => {
console.log('分享取消或失败:', error)
})
} else {
// 如果不支持Web Share API可以复制到剪贴板
navigator.clipboard
.writeText(plainText)
.then(() => {
console.log('内容已复制到剪贴板')
// 在实际应用中,这里会显示一个提示消息
})
.catch(error => {
console.error('复制失败:', error)
})
}
}
}
// 在组件卸载前自动保存或删除
onBeforeUnmount(async () => {
await autoSaveNote()
})
</script>
<style lang="less" scoped>
@@ -249,12 +402,20 @@ const setShowAlert = value => {
flex-direction: column;
height: 100vh;
background-color: var(--background);
section {
width: 100%;
height: 100%;
}
}
.editor-container {
flex: 1;
min-height: 100%;
overflow-y: auto;
background-color: var(--background-card);
&.disabled {
pointer-events: none;
}
}
.rich-text-editor {