优化 头部便签管理点击区域;

补充注释;
This commit is contained in:
User
2025-10-13 10:48:18 +08:00
parent 27133aa107
commit 2e933ece94
13 changed files with 523 additions and 175 deletions

63
package-lock.json generated
View File

@@ -13,6 +13,7 @@
"@capacitor/cli": "^5.7.2", "@capacitor/cli": "^5.7.2",
"@capacitor/core": "^5.7.2", "@capacitor/core": "^5.7.2",
"@capacitor/ios": "^5.7.2", "@capacitor/ios": "^5.7.2",
"@ionic/vue": "^8.7.6",
"@vue/cli-service": "^5.0.9", "@vue/cli-service": "^5.0.9",
"@vue/compiler-sfc": "^3.5.22", "@vue/compiler-sfc": "^3.5.22",
"basic-ftp": "^5.0.5", "basic-ftp": "^5.0.5",
@@ -2150,6 +2151,26 @@
"node": ">=16.0.0" "node": ">=16.0.0"
} }
}, },
"node_modules/@ionic/core": {
"version": "8.7.6",
"resolved": "https://registry.npmmirror.com/@ionic/core/-/core-8.7.6.tgz",
"integrity": "sha512-ufV64Pl0BYSoNla+DaTRXTS3hX6MQZZJPhAR3fJQ4N5Fg/vwMcHADQffstKZeoPqk6mbJoLqoTBjcWvaLRdO0g==",
"license": "MIT",
"dependencies": {
"@stencil/core": "4.38.0",
"ionicons": "^8.0.13",
"tslib": "^2.1.0"
}
},
"node_modules/@ionic/core/node_modules/ionicons": {
"version": "8.0.13",
"resolved": "https://registry.npmmirror.com/ionicons/-/ionicons-8.0.13.tgz",
"integrity": "sha512-2QQVyG2P4wszne79jemMjWYLp0DBbDhr4/yFroPCxvPP1wtMxgdIV3l5n+XZ5E9mgoXU79w7yTWpm2XzJsISxQ==",
"license": "MIT",
"dependencies": {
"@stencil/core": "^4.35.3"
}
},
"node_modules/@ionic/utils-array": { "node_modules/@ionic/utils-array": {
"version": "2.1.6", "version": "2.1.6",
"resolved": "https://registry.npmmirror.com/@ionic/utils-array/-/utils-array-2.1.6.tgz", "resolved": "https://registry.npmmirror.com/@ionic/utils-array/-/utils-array-2.1.6.tgz",
@@ -2359,6 +2380,26 @@
"node": ">=16.0.0" "node": ">=16.0.0"
} }
}, },
"node_modules/@ionic/vue": {
"version": "8.7.6",
"resolved": "https://registry.npmmirror.com/@ionic/vue/-/vue-8.7.6.tgz",
"integrity": "sha512-gK5x5Y0ZpZAW12gjvyBO9oUfwDZxMS7y0xcO0P9qzo++h3ZLcFcSGjHs8D4isUY/mF6mRagt1Y/5b0xDhgUBBw==",
"license": "MIT",
"dependencies": {
"@ionic/core": "8.7.6",
"@stencil/vue-output-target": "0.10.7",
"ionicons": "^8.0.13"
}
},
"node_modules/@ionic/vue/node_modules/ionicons": {
"version": "8.0.13",
"resolved": "https://registry.npmmirror.com/ionicons/-/ionicons-8.0.13.tgz",
"integrity": "sha512-2QQVyG2P4wszne79jemMjWYLp0DBbDhr4/yFroPCxvPP1wtMxgdIV3l5n+XZ5E9mgoXU79w7yTWpm2XzJsISxQ==",
"license": "MIT",
"dependencies": {
"@stencil/core": "^4.35.3"
}
},
"node_modules/@jridgewell/gen-mapping": { "node_modules/@jridgewell/gen-mapping": {
"version": "0.3.13", "version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
@@ -2936,6 +2977,28 @@
"@rollup/rollup-win32-x64-msvc": "4.34.9" "@rollup/rollup-win32-x64-msvc": "4.34.9"
} }
}, },
"node_modules/@stencil/vue-output-target": {
"version": "0.10.7",
"resolved": "https://registry.npmmirror.com/@stencil/vue-output-target/-/vue-output-target-0.10.7.tgz",
"integrity": "sha512-IYxDe+SLCkwhwsWRdynE31rTK1zN3hVwwojQ/V9lrN8Gnx4PTvrUQHiRno9jFo1dk+EaBZWX9gZSmXta0ZaZew==",
"license": "MIT",
"peerDependencies": {
"@stencil/core": ">=2.0.0 || >=3 || >= 4.0.0-beta.0 || >= 4.0.0",
"vue": "^3.4.38",
"vue-router": "^4.5.0"
},
"peerDependenciesMeta": {
"@stencil/core": {
"optional": true
},
"vue": {
"optional": false
},
"vue-router": {
"optional": true
}
}
},
"node_modules/@surma/rollup-plugin-off-main-thread": { "node_modules/@surma/rollup-plugin-off-main-thread": {
"version": "2.2.3", "version": "2.2.3",
"resolved": "https://registry.npmmirror.com/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", "resolved": "https://registry.npmmirror.com/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz",

View File

@@ -18,6 +18,7 @@
"@capacitor/cli": "^5.7.2", "@capacitor/cli": "^5.7.2",
"@capacitor/core": "^5.7.2", "@capacitor/core": "^5.7.2",
"@capacitor/ios": "^5.7.2", "@capacitor/ios": "^5.7.2",
"@ionic/vue": "^8.7.6",
"@vue/cli-service": "^5.0.9", "@vue/cli-service": "^5.0.9",
"@vue/compiler-sfc": "^3.5.22", "@vue/compiler-sfc": "^3.5.22",
"basic-ftp": "^5.0.5", "basic-ftp": "^5.0.5",

View File

@@ -6,5 +6,4 @@
<script setup> <script setup>
import '@/common/base.css' import '@/common/base.css'
// App根组件不需要额外的逻辑 </script>
</script>

View File

@@ -5,8 +5,8 @@
<img class="left-icon" :src="leftIconSource" @click="handleLeftAction" /> <img class="left-icon" :src="leftIconSource" @click="handleLeftAction" />
<!-- 标题区域 --> <!-- 标题区域 -->
<div class="title-container" @click="handleTitlePress"> <div class="title-container">
<span class="text">{{ title }}</span> <span class="text" @click="handleTitlePress">{{ title }}</span>
<!-- 文件夹展开图标 --> <!-- 文件夹展开图标 -->
<img v-if="showFolderIcon" class="folder-icon" :src="folderExpanded ? '/assets/icons/drawable-xxhdpi/folder_title_arrow_pressed.png' : '/assets/icons/drawable-xxhdpi/folder_title_arrow_normal.png'" @click.stop="handleFolderToggle" /> <img v-if="showFolderIcon" class="folder-icon" :src="folderExpanded ? '/assets/icons/drawable-xxhdpi/folder_title_arrow_pressed.png' : '/assets/icons/drawable-xxhdpi/folder_title_arrow_normal.png'" @click.stop="handleFolderToggle" />
</div> </div>
@@ -118,7 +118,7 @@ const handleLeftAction = () => {
} }
} }
const handleAction = (actionType) => { const handleAction = actionType => {
// 处理右侧操作按钮点击事件 // 处理右侧操作按钮点击事件
if (props.onAction) { if (props.onAction) {
props.onAction(actionType) props.onAction(actionType)
@@ -156,7 +156,7 @@ const handleTitlePress = () => {
cursor: pointer; cursor: pointer;
} }
.right-group { .right-group {
gap: .6rem; gap: 0.6rem;
} }
.image_4-placeholder { .image_4-placeholder {

View File

@@ -79,13 +79,15 @@ const isSlided = ref(false) // 是否已经滑动到阈值
const formattedDate = computed(() => { const formattedDate = computed(() => {
// 直接返回已经格式化的日期字符串 // 直接返回已经格式化的日期字符串
// 日期格式化已在父组件中完成
return props.date return props.date
}) })
// 处理显示内容过滤HTML标签并只显示第一行 // 处理显示内容过滤HTML标签并只显示第一行
// 用于在便签列表中显示便签的预览内容
const displayContent = computed(() => { const displayContent = computed(() => {
console.log('NoteItem content:', props.content) console.log('NoteItem content:', props.content)
// 过滤HTML标签 // 过滤HTML标签,只保留纯文本内容
let text = props.content.replace(/<[^>]*>/g, '') let text = props.content.replace(/<[^>]*>/g, '')
console.log('NoteItem text without HTML:', text) console.log('NoteItem text without HTML:', text)
@@ -100,8 +102,11 @@ const displayContent = computed(() => {
}) })
// 滑动阈值(删除按钮宽度) // 滑动阈值(删除按钮宽度)
// 当滑动距离超过此值时,显示删除按钮
const SLIDE_THRESHOLD = 64 // 4rem 转换为 px const SLIDE_THRESHOLD = 64 // 4rem 转换为 px
// 处理便签点击事件
// 只有在未滑动状态下才触发点击事件,避免与滑动操作冲突
const handlePress = () => { const handlePress = () => {
// 只有在未滑动状态下才触发点击事件 // 只有在未滑动状态下才触发点击事件
if (slideOffset.value === 0 && props.onPress) { if (slideOffset.value === 0 && props.onPress) {
@@ -109,31 +114,39 @@ const handlePress = () => {
} }
} }
// 处理星标切换事件
// 点击星标图标时调用父组件传递的回调函数
const handleStarToggle = () => { const handleStarToggle = () => {
if (props.onStarToggle) { if (props.onStarToggle) {
props.onStarToggle() props.onStarToggle()
} }
} }
// 处理置顶切换事件
// 点击置顶图标时调用父组件传递的回调函数
const handleTopToggle = () => { const handleTopToggle = () => {
if (props.onTopToggle) { if (props.onTopToggle) {
props.onTopToggle() props.onTopToggle()
} }
} }
// 处理删除事件
// 点击删除按钮时调用父组件传递的回调函数
const handleDelete = () => { const handleDelete = () => {
if (props.onDelete) { if (props.onDelete) {
props.onDelete() props.onDelete()
} }
} }
// 触摸开始 // 触摸开始事件处理函数
// 记录触摸开始时的X坐标用于计算滑动距离
const handleTouchStart = e => { const handleTouchStart = e => {
// 重置滑动状态 // 重置滑动状态
startX.value = e.touches[0].clientX startX.value = e.touches[0].clientX
} }
// 触摸移动 // 触摸移动事件处理函数
// 根据手指移动距离计算便签条的水平偏移量
const handleTouchMove = e => { const handleTouchMove = e => {
if (!startX.value) return if (!startX.value) return
@@ -147,15 +160,15 @@ const handleTouchMove = e => {
// 设置滑动状态 // 设置滑动状态
isSliding.value = true isSliding.value = true
// 应用阻尼效果 // 应用阻尼效果,使超过阈值后的滑动更加困难
let offset = 0 let offset = 0
if (diffX <= SLIDE_THRESHOLD) { if (diffX <= SLIDE_THRESHOLD) {
// 线性滑动 // 线性滑动,在阈值内正常滑动
offset = diffX offset = diffX
} else { } else {
// 超过阈值后应用阻尼效果 // 超过阈值后应用阻尼效果,增加滑动阻力
const excess = diffX - SLIDE_THRESHOLD const excess = diffX - SLIDE_THRESHOLD
offset = SLIDE_THRESHOLD + excess * 0.03 // 0.3 为阻尼系数 offset = SLIDE_THRESHOLD + excess * 0.03 // 0.03 为阻尼系数
} }
slideOffset.value = offset slideOffset.value = offset
@@ -171,12 +184,14 @@ const handleTouchMove = e => {
} }
} }
// 触摸结束 // 触摸结束事件处理函数
// 根据滑动距离决定便签条的最终位置
const handleTouchEnd = () => { const handleTouchEnd = () => {
if (!startX.value) return if (!startX.value) return
// 如果滑动超过阈值,保持滑出状态;否则回弹 // 如果滑动超过阈值,保持滑出状态;否则回弹
if (slideOffset.value >= SLIDE_THRESHOLD) { if (slideOffset.value >= SLIDE_THRESHOLD) {
// 保持滑出状态,显示删除按钮
slideOffset.value = SLIDE_THRESHOLD slideOffset.value = SLIDE_THRESHOLD
isSlided.value = true isSlided.value = true
} else { } else {

View File

@@ -50,42 +50,43 @@ onMounted(() => {
} }
}) })
// 工具配置 // 工具配置
// 定义富文本编辑器的所有工具按钮及其功能
const tools = ref([ const tools = ref([
{ {
name: 'bold', name: 'bold', // 加粗工具
icon: '/assets/icons/drawable-xxhdpi/rtf_bold_normal.9.png', icon: '/assets/icons/drawable-xxhdpi/rtf_bold_normal.9.png',
action: () => formatText('bold'), action: () => formatText('bold'), // 执行加粗格式化
active: false, active: false, // 工具是否处于激活状态
}, },
{ {
name: 'center', name: 'center', // 居中对齐工具
icon: '/assets/icons/drawable-xxhdpi/rtf_center_normal.9.png', icon: '/assets/icons/drawable-xxhdpi/rtf_center_normal.9.png',
action: () => formatText('justifyCenter'), action: () => formatText('justifyCenter'), // 执行居中对齐格式化
active: false, active: false,
}, },
{ {
name: 'todo', name: 'todo', // 待办事项工具
icon: '/assets/icons/drawable-xxhdpi/rtf_gtasks_normal.9.png', icon: '/assets/icons/drawable-xxhdpi/rtf_gtasks_normal.9.png',
action: () => formatText('insertTodoList'), action: () => formatText('insertTodoList'), // 插入待办事项列表
active: false, active: false,
}, },
{ {
name: 'list', name: 'list', // 无序列表工具
icon: '/assets/icons/drawable-xxhdpi/rtf_list_normal.9.png', icon: '/assets/icons/drawable-xxhdpi/rtf_list_normal.9.png',
action: () => formatText('insertUnorderedList'), action: () => formatText('insertUnorderedList'), // 插入无序列表
active: false, active: false,
}, },
{ {
name: 'header', name: 'header', // 标题工具
icon: '/assets/icons/drawable-xxhdpi/rtf_header_normal.9.png', icon: '/assets/icons/drawable-xxhdpi/rtf_header_normal.9.png',
action: () => formatText('formatBlock', 'h2'), action: () => formatText('formatBlock', 'h2'), // 格式化为二级标题
active: false, active: false,
}, },
{ {
name: 'quote', name: 'quote', // 引用工具
icon: '/assets/icons/drawable-xxhdpi/rtf_quot_normal.9.png', icon: '/assets/icons/drawable-xxhdpi/rtf_quot_normal.9.png',
action: () => insertQuote(), action: () => insertQuote(), // 插入引用格式
active: false, active: false,
}, },
]) ])
@@ -112,6 +113,7 @@ const handleInput = () => {
} }
// 检查当前选区是否已经在某种格式中 // 检查当前选区是否已经在某种格式中
// 用于防止重复应用相同的格式,例如重复加粗
const isAlreadyInFormat = formatType => { const isAlreadyInFormat = formatType => {
const selection = window.getSelection() const selection = window.getSelection()
if (selection.rangeCount > 0) { if (selection.rangeCount > 0) {
@@ -121,10 +123,15 @@ const isAlreadyInFormat = formatType => {
// 向上查找父元素,检查是否已经在指定格式中 // 向上查找父元素,检查是否已经在指定格式中
let current = container.nodeType === Node.TEXT_NODE ? container.parentElement : container let current = container.nodeType === Node.TEXT_NODE ? container.parentElement : container
while (current && current !== editorRef.value) { while (current && current !== editorRef.value) {
// 检查加粗格式
if (formatType === 'bold' && current.tagName === 'B') return true if (formatType === 'bold' && current.tagName === 'B') return true
// 检查居中对齐格式
if (formatType === 'center' && current.style.textAlign === 'center') return true if (formatType === 'center' && current.style.textAlign === 'center') return true
// 检查标题格式
if (formatType === 'header' && current.tagName === 'H2') return true if (formatType === 'header' && current.tagName === 'H2') return true
// 检查引用格式
if (formatType === 'quote' && (current.tagName === 'BLOCKQUOTE' || current.classList.contains('quote-content'))) return true if (formatType === 'quote' && (current.tagName === 'BLOCKQUOTE' || current.classList.contains('quote-content'))) return true
// 检查列表格式
if (formatType === 'list' && (current.tagName === 'UL' || current.tagName === 'OL' || current.tagName === 'LI')) return true if (formatType === 'list' && (current.tagName === 'UL' || current.tagName === 'OL' || current.tagName === 'LI')) return true
current = current.parentElement current = current.parentElement
} }
@@ -133,6 +140,7 @@ const isAlreadyInFormat = formatType => {
} }
// 检查是否在列表、引用或待办事项中(用于嵌套限制) // 检查是否在列表、引用或待办事项中(用于嵌套限制)
// 防止在已有的列表、引用或待办事项中再次插入相同类型的元素
const isInListOrQuote = () => { const isInListOrQuote = () => {
const selection = window.getSelection() const selection = window.getSelection()
if (selection.rangeCount > 0) { if (selection.rangeCount > 0) {
@@ -142,7 +150,10 @@ const isInListOrQuote = () => {
// 向上查找父元素,检查是否在列表、引用或待办事项中 // 向上查找父元素,检查是否在列表、引用或待办事项中
let current = container.nodeType === Node.TEXT_NODE ? container.parentElement : container let current = container.nodeType === Node.TEXT_NODE ? container.parentElement : container
while (current && current !== editorRef.value) { while (current && current !== editorRef.value) {
if (current.tagName === 'UL' || current.tagName === 'OL' || current.tagName === 'LI' || current.tagName === 'BLOCKQUOTE' || current.classList.contains('quote-content') || current.classList.contains('todo-container')) { // 检查是否在列表、引用或待办事项中
if (current.tagName === 'UL' || current.tagName === 'OL' || current.tagName === 'LI' ||
current.tagName === 'BLOCKQUOTE' || current.classList.contains('quote-content') ||
current.classList.contains('todo-container')) {
return true return true
} }
current = current.parentElement current = current.parentElement
@@ -152,8 +163,10 @@ const isInListOrQuote = () => {
} }
// 格式化文本 // 格式化文本
// 根据指定的命令和值对选中文本应用格式
const formatText = (command, value = null) => { const formatText = (command, value = null) => {
// 检查是否已经应用了相同的格式,如果已应用则取消格式 // 检查是否已经应用了相同的格式,如果已应用则取消格式
// 例如,如果文本已经是加粗的,再次点击加粗按钮会取消加粗
if (command === 'bold' && isAlreadyInFormat('bold')) { if (command === 'bold' && isAlreadyInFormat('bold')) {
document.execCommand('bold', false, null) document.execCommand('bold', false, null)
updateToolbarState() updateToolbarState()
@@ -161,6 +174,7 @@ const formatText = (command, value = null) => {
return return
} }
// 处理居中对齐切换:如果已居中则取消居中
if (command === 'justifyCenter' && isAlreadyInFormat('center')) { if (command === 'justifyCenter' && isAlreadyInFormat('center')) {
document.execCommand('justifyLeft', false, null) document.execCommand('justifyLeft', false, null)
updateToolbarState() updateToolbarState()
@@ -168,6 +182,7 @@ const formatText = (command, value = null) => {
return return
} }
// 处理标题格式切换:如果已是标题则转为普通段落
if (command === 'formatBlock' && value === 'h2' && isAlreadyInFormat('header')) { if (command === 'formatBlock' && value === 'h2' && isAlreadyInFormat('header')) {
document.execCommand('formatBlock', false, '<p>') document.execCommand('formatBlock', false, '<p>')
updateToolbarState() updateToolbarState()
@@ -175,6 +190,7 @@ const formatText = (command, value = null) => {
return return
} }
// 处理列表格式切换:如果已是列表则取消列表
if (command === 'insertUnorderedList' && isAlreadyInFormat('list')) { if (command === 'insertUnorderedList' && isAlreadyInFormat('list')) {
document.execCommand('insertUnorderedList', false, null) document.execCommand('insertUnorderedList', false, null)
updateToolbarState() updateToolbarState()
@@ -189,13 +205,16 @@ const formatText = (command, value = null) => {
return return
} }
// 检查嵌套限制 // 检查嵌套限制,防止在列表、引用中再次插入列表或引用
if ((command === 'insertUnorderedList' || (command === 'formatBlock' && value === 'blockquote')) && isInListOrQuote()) { if ((command === 'insertUnorderedList' || (command === 'formatBlock' && value === 'blockquote')) && isInListOrQuote()) {
return return
} }
// 执行格式化命令
document.execCommand(command, false, value) document.execCommand(command, false, value)
// 更新工具栏状态以反映当前格式
updateToolbarState() updateToolbarState()
// 触发输入事件以更新内容
handleInput() handleInput()
} }
@@ -283,39 +302,40 @@ const insertQuote = () => {
} }
// 插入待办事项列表 // 插入待办事项列表
// 创建一个可交互的待办事项元素,包含复选框图标和可编辑内容区域
const insertTodoList = () => { const insertTodoList = () => {
const selection = window.getSelection() const selection = window.getSelection()
if (selection.rangeCount > 0) { if (selection.rangeCount > 0) {
const range = selection.getRangeAt(0) const range = selection.getRangeAt(0)
// 检查嵌套限制 // 检查嵌套限制,防止在列表或引用中插入待办事项
if (isInListOrQuote()) return if (isInListOrQuote()) return
// 创建待办事项容器 // 创建待办事项容器
const todoContainer = document.createElement('div') const todoContainer = document.createElement('div')
todoContainer.contentEditable = false todoContainer.contentEditable = false // 容器本身不可编辑
todoContainer.className = 'todo-container' todoContainer.className = 'todo-container'
// 创建图标元素 // 创建图标元素(复选框)
const icon = document.createElement('img') const icon = document.createElement('img')
icon.className = 'todo-icon' icon.className = 'todo-icon'
icon.src = '/assets/icons/drawable-xxhdpi/rtf_icon_gtasks.png' icon.src = '/assets/icons/drawable-xxhdpi/rtf_icon_gtasks.png' // 未完成状态图标
icon.alt = '待办事项' icon.alt = '待办事项'
// 创建内容容器 // 创建内容容器(可编辑区域)
const contentSpan = document.createElement('div') const contentSpan = document.createElement('div')
contentSpan.contentEditable = true contentSpan.contentEditable = true // 内容区域可编辑
contentSpan.className = 'todo-content' contentSpan.className = 'todo-content'
contentSpan.textContent = '待办事项' contentSpan.textContent = '待办事项' // 默认文本
// 组装元素 // 组装元素:将图标和内容区域添加到容器中
todoContainer.appendChild(icon) todoContainer.appendChild(icon)
todoContainer.appendChild(contentSpan) todoContainer.appendChild(contentSpan)
// 插入到当前光标位置 // 插入到当前光标位置
range.insertNode(todoContainer) range.insertNode(todoContainer)
// 添加换行 // 添加换行,确保待办事项下方有空白行
const br = document.createElement('br') const br = document.createElement('br')
todoContainer.parentNode.insertBefore(br, todoContainer.nextSibling) todoContainer.parentNode.insertBefore(br, todoContainer.nextSibling)
@@ -338,19 +358,21 @@ const insertTodoList = () => {
} }
}, 0) }, 0)
// 添加事件监听器到图标 // 添加事件监听器到图标,实现待办事项完成状态切换
icon.addEventListener('click', function () { icon.addEventListener('click', function () {
// 根据当前状态切换图标 // 根据当前状态切换图标和样式
if (this.src.includes('rtf_icon_gtasks.png')) { if (this.src.includes('rtf_icon_gtasks.png')) {
this.src = '/assets/icons/drawable-xxhdpi/rtf_icon_gtasks_light.png' // 切换到完成状态
contentSpan.style.color = 'var(--text-tertiary)' this.src = '/assets/icons/drawable-xxhdpi/rtf_icon_gtasks_light.png' // 完成状态图标
contentSpan.style.textDecoration = 'line-through' contentSpan.style.color = 'var(--text-tertiary)' // 灰色文字
contentSpan.style.textDecoration = 'line-through' // 添加删除线
} else { } else {
this.src = '/assets/icons/drawable-xxhdpi/rtf_icon_gtasks.png' // 切换到未完成状态
contentSpan.style.color = 'var(--note-content)' this.src = '/assets/icons/drawable-xxhdpi/rtf_icon_gtasks.png' // 未完成状态图标
contentSpan.style.textDecoration = 'none' contentSpan.style.color = 'var(--note-content)' // 正常文字颜色
contentSpan.style.textDecoration = 'none' // 移除删除线
} }
handleInput() handleInput() // 触发内容更新
}) })
// 添加事件监听器到内容区域,监听内容变化和按键事件 // 添加事件监听器到内容区域,监听内容变化和按键事件
@@ -365,8 +387,8 @@ const insertTodoList = () => {
}, 0) }, 0)
} }
contentSpan.addEventListener('input', checkContent) contentSpan.addEventListener('input', checkContent) // 内容输入时检查
contentSpan.addEventListener('blur', checkContent) contentSpan.addEventListener('blur', checkContent) // 失去焦点时检查
// 添加焦点事件监听器,确保工具栏在待办事项获得焦点时保持可见 // 添加焦点事件监听器,确保工具栏在待办事项获得焦点时保持可见
contentSpan.addEventListener('focus', () => { contentSpan.addEventListener('focus', () => {

View File

@@ -3,30 +3,48 @@ import { createRouter, createWebHashHistory } from 'vue-router'
import { createPinia } from 'pinia' import { createPinia } from 'pinia'
import App from './App.vue' import App from './App.vue'
// Pages // 导入页面组件
// 便签列表页面
import NoteListPage from './pages/NoteListPage.vue' import NoteListPage from './pages/NoteListPage.vue'
// 便签编辑页面(用于新建和编辑便签)
import NoteEditorPage from './pages/NoteEditorPage.vue' import NoteEditorPage from './pages/NoteEditorPage.vue'
// 文件夹管理页面
import FolderPage from './pages/FolderPage.vue' import FolderPage from './pages/FolderPage.vue'
// 设置页面
import SettingsPage from './pages/SettingsPage.vue' import SettingsPage from './pages/SettingsPage.vue'
// Router // 配置路由规则
// 定义应用的所有路由路径和对应的组件
const routes = [ const routes = [
// 根路径重定向到便签列表页面
{ path: '/', redirect: '/notes' }, { path: '/', redirect: '/notes' },
// 便签列表页面路由
{ path: '/notes', component: NoteListPage }, { path: '/notes', component: NoteListPage },
// 编辑便签页面路由带便签ID参数
{ path: '/notes/:id', component: NoteEditorPage, props: true }, { path: '/notes/:id', component: NoteEditorPage, props: true },
// 新建便签页面路由
{ path: '/editor', component: NoteEditorPage }, { path: '/editor', component: NoteEditorPage },
// 编辑便签页面路由带便签ID参数
{ path: '/editor/:id', component: NoteEditorPage, props: true }, { path: '/editor/:id', component: NoteEditorPage, props: true },
// 文件夹管理页面路由
{ path: '/folders', component: FolderPage }, { path: '/folders', component: FolderPage },
// 设置页面路由
{ path: '/settings', component: SettingsPage } { path: '/settings', component: SettingsPage }
] ]
// 创建路由实例
// 使用Hash模式以支持静态文件部署
const router = createRouter({ const router = createRouter({
history: createWebHashHistory(), history: createWebHashHistory(),
routes routes
}) })
// App // 创建并挂载Vue应用实例
// 配置Pinia状态管理和Vue Router路由
const app = createApp(App) const app = createApp(App)
// 使用Pinia进行状态管理
app.use(createPinia()) app.use(createPinia())
// 使用Vue Router进行路由管理
app.use(router) app.use(router)
// 挂载应用到DOM元素
app.mount('#app') app.mount('#app')

View File

@@ -29,12 +29,14 @@
<script setup> <script setup>
import { ref, computed, onMounted } from 'vue' import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useAppStore } from '../stores/useAppStore' import { useAppStore } from '../stores/useAppStore'
import { search, closeCircle } from 'ionicons/icons' import { search, closeCircle } from 'ionicons/icons'
import FolderManage from '../components/FolderManage.vue' import FolderManage from '../components/FolderManage.vue'
import Header from '../components/Header.vue' import Header from '../components/Header.vue'
const store = useAppStore() const store = useAppStore()
const router = useRouter()
// 加载初始数据 // 加载初始数据
onMounted(() => { onMounted(() => {
@@ -44,7 +46,8 @@ onMounted(() => {
const searchQuery = ref('') const searchQuery = ref('')
const selectedFolder = ref('all') const selectedFolder = ref('all')
// Calculate note count for each folder // 计算每个文件夹中的便签数量
// 遍历所有自定义文件夹,统计每个文件夹中的便签数量
const foldersWithCount = computed(() => { const foldersWithCount = computed(() => {
return store.folders.map(folder => { return store.folders.map(folder => {
const noteCount = store.notes.filter(note => note.folderId === folder.id).length const noteCount = store.notes.filter(note => note.folderId === folder.id).length
@@ -55,13 +58,17 @@ const foldersWithCount = computed(() => {
}) })
}) })
// Add default folders at the beginning // 添加默认文件夹(全部便签、加星便签、回收站)到列表开头
// 计算全部便签数量
const allNotesCount = computed(() => store.notes.length) const allNotesCount = computed(() => store.notes.length)
// 计算加星便签数量
const starredNotesCount = computed(() => store.notes.filter(note => note.isStarred).length) const starredNotesCount = computed(() => store.notes.filter(note => note.isStarred).length)
// Assuming we have a way to track deleted notes in the future // 回收站便签数量(暂未实现完整功能)
const trashNotesCount = 0 const trashNotesCount = 0
// 归档便签数量(暂未实现完整功能)
const archiveCount = 0 const archiveCount = 0
// 合并默认文件夹和自定义文件夹
const foldersWithAllNotes = computed(() => { const foldersWithAllNotes = computed(() => {
return [ return [
{ id: 'all', name: '全部便签', noteCount: allNotesCount.value, createdAt: new Date() }, { id: 'all', name: '全部便签', noteCount: allNotesCount.value, createdAt: new Date() },
@@ -74,26 +81,28 @@ const foldersWithAllNotes = computed(() => {
const handleFolderPress = folderId => { const handleFolderPress = folderId => {
// 更新选中的文件夹状态 // 更新选中的文件夹状态
selectedFolder.value = folderId selectedFolder.value = folderId
// 在实际应用中这里会将选中的文件夹传递回NoteListScreen // 使用vue-router导航回便签列表页面并传递文件夹参数
// 通过导航参数传递选中的文件夹ID router.push(`/notes?folder=${folderId}`)
window.location.hash = `#/notes?folder=${folderId}`
} }
// 处理添加文件夹按钮点击事件
// 在完整实现中,这里会打开文件夹创建对话框
const handleAddFolder = () => { const handleAddFolder = () => {
// In a full implementation, this would open a folder creation dialog
console.log('Add folder pressed') console.log('Add folder pressed')
} }
// 处理搜索功能
// 在完整实现中,这里会根据搜索关键词过滤文件夹
const handleSearch = () => { const handleSearch = () => {
// In a full implementation, this would filter folders based on searchQuery
console.log('Search for:', searchQuery.value) console.log('Search for:', searchQuery.value)
} }
const handleBackPress = () => { const handleBackPress = () => {
window.history.back() router.back()
} }
// Filter folders based on search query // 根据搜索关键词过滤文件夹
// 将文件夹名称转换为小写进行模糊匹配
const filteredFolders = computed(() => { const filteredFolders = computed(() => {
return foldersWithAllNotes.value.filter(folder => folder.name.toLowerCase().includes(searchQuery.value.toLowerCase())) return foldersWithAllNotes.value.filter(folder => folder.name.toLowerCase().includes(searchQuery.value.toLowerCase()))
}) })

View File

@@ -40,6 +40,7 @@
<script setup> <script setup>
import { ref, computed, onMounted, nextTick, watch } from 'vue' import { ref, computed, onMounted, nextTick, watch } from 'vue'
import { useRouter } from 'vue-router'
import { useAppStore } from '../stores/useAppStore' import { useAppStore } from '../stores/useAppStore'
import Header from '../components/Header.vue' import Header from '../components/Header.vue'
import RichTextEditor from '../components/RichTextEditor.vue' import RichTextEditor from '../components/RichTextEditor.vue'
@@ -53,31 +54,35 @@ const props = defineProps({
}) })
// 为了保持向后兼容性我们也支持noteId属性 // 为了保持向后兼容性我们也支持noteId属性
// 通过计算属性确保无论使用id还是noteId都能正确获取便签ID
const noteId = computed(() => props.id || props.noteId) const noteId = computed(() => props.id || props.noteId)
const store = useAppStore() const store = useAppStore()
const router = useRouter()
const editorRef = ref(null) const editorRef = ref(null)
// 设置便签内容的函数 // 设置便签内容的函数
// 用于在编辑器中加载指定便签的内容
const setNoteContent = async (noteId) => { const setNoteContent = async (noteId) => {
// 确保store数据已加载 // 确保store数据已加载,如果便签列表为空则先加载数据
if (store.notes.length === 0) { if (store.notes.length === 0) {
await store.loadData() await store.loadData()
console.log('Store loaded, notes count:', store.notes.length) console.log('Store loaded, notes count:', store.notes.length)
} }
// 从store中查找指定ID的便签
const note = store.notes.find(n => n.id === noteId) const note = store.notes.find(n => n.id === noteId)
console.log('Found note:', note) console.log('Found note:', note)
// 确保编辑器已经初始化 // 确保编辑器已经初始化完成
await nextTick() await nextTick()
console.log('Editor ref:', editorRef.value) console.log('Editor ref:', editorRef.value)
if (note) { if (note) {
console.log('Setting content:', note.content) console.log('Setting content:', note.content)
// 无论editorRef是否可用都先设置content的值 // 无论editorRef是否可用都先设置content的值作为备份
content.value = note.content || '' content.value = note.content || ''
// 如果editorRef可用直接设置内容 // 如果editorRef可用直接设置编辑器内容
if (editorRef.value) { if (editorRef.value) {
editorRef.value.setContent(note.content || '') editorRef.value.setContent(note.content || '')
} }
@@ -113,11 +118,12 @@ watch(() => store.notes, async (newNotes) => {
} }
}, { immediate: true }) }, { immediate: true })
// Check if we're editing an existing note // 检查是否正在编辑现有便签
// 如果noteId存在则表示是编辑模式否则是新建模式
const isEditing = !!noteId.value const isEditing = !!noteId.value
const existingNote = isEditing ? store.notes.find(n => n.id === noteId.value) : null const existingNote = isEditing ? store.notes.find(n => n.id === noteId.value) : null
// Initialize state with existing note data or empty strings // 初始化内容状态,如果是编辑现有便签则使用便签内容,否则为空字符串
const content = ref(existingNote?.content || '') const content = ref(existingNote?.content || '')
// 当组件挂载时,确保编辑器初始化为空内容(针对新建便签) // 当组件挂载时,确保编辑器初始化为空内容(针对新建便签)
@@ -137,6 +143,7 @@ watch(() => store.notes, async (newNotes) => {
const showAlert = ref(false) const showAlert = ref(false)
// 防抖函数 // 防抖函数
// 用于避免函数在短时间内被频繁调用,提高性能
const debounce = (func, delay) => { const debounce = (func, delay) => {
let timeoutId let timeoutId
return function (...args) { return function (...args) {
@@ -146,18 +153,21 @@ const debounce = (func, delay) => {
} }
// 防抖处理内容变化 // 防抖处理内容变化
// 延迟300ms更新内容避免用户输入时频繁触发更新
const debouncedHandleContentChange = debounce((newContent) => { const debouncedHandleContentChange = debounce((newContent) => {
content.value = newContent content.value = newContent
console.log('Content updated:', newContent) console.log('Content updated:', newContent)
}, 300) }, 300)
// 监听编辑器内容变化 // 监听编辑器内容变化
// 当编辑器内容发生变化时调用此函数
const handleContentChange = (newContent) => { const handleContentChange = (newContent) => {
console.log('Editor content changed:', newContent) console.log('Editor content changed:', newContent)
debouncedHandleContentChange(newContent) debouncedHandleContentChange(newContent)
} }
// 计算属性 // 计算属性 - 格式化时间显示
// 如果是编辑现有便签则显示便签的更新时间,否则显示当前时间
const formattedTime = computed(() => { const formattedTime = computed(() => {
if (existingNote?.updatedAt) { if (existingNote?.updatedAt) {
return formatNoteEditorDate(existingNote.updatedAt) return formatNoteEditorDate(existingNote.updatedAt)
@@ -165,8 +175,10 @@ const formattedTime = computed(() => {
return formatNoteEditorDate(new Date()) return formatNoteEditorDate(new Date())
}) })
// 计算属性 - 计算字数
// 移除HTML标签后计算纯文本字数
const wordCount = computed(() => { const wordCount = computed(() => {
// 移除HTML标签计算字数 // 使用正则表达式移除HTML标签,只保留纯文本内容
const textContent = content.value.replace(/<[^>]*>/g, '') const textContent = content.value.replace(/<[^>]*>/g, '')
return textContent.length || 0 return textContent.length || 0
}) })
@@ -191,7 +203,7 @@ const handleSave = async () => {
} }
// Navigate back to the previous screen // Navigate back to the previous screen
window.location.hash = '#/notes' router.push('/notes')
} catch (error) { } catch (error) {
// In a full implementation, show an alert or toast // In a full implementation, show an alert or toast
console.log('Save error: Failed to save note. Please try again.') console.log('Save error: Failed to save note. Please try again.')
@@ -206,7 +218,7 @@ const handleCancel = () => {
if (hasUnsavedChanges) { if (hasUnsavedChanges) {
showAlert.value = true showAlert.value = true
} else { } else {
window.location.hash = '#/notes' router.push('/notes')
} }
} }
@@ -223,7 +235,7 @@ const handleCreate = async () => {
}) })
// Navigate back to the previous screen // Navigate back to the previous screen
window.location.hash = '#/notes' router.push('/notes')
} catch (error) { } catch (error) {
// In a full implementation, show an alert or toast // In a full implementation, show an alert or toast
console.log('Create error: Failed to create note. Please try again.') console.log('Create error: Failed to create note. Please try again.')

View File

@@ -1,12 +1,18 @@
<template> <template>
<ion-app> <ion-app>
<div class="container"> <div class="container">
<Header :title="headerTitle" :onAction="handleHeaderAction" actionIcon="create" leftType="settings" :onLeftAction="handleSettingsPress" :onFolderToggle="handleFolderToggle" :isFolderExpanded="isFolderExpanded" :onTitlePress="handleFolderToggle" /> <Header
:title="headerTitle"
:onAction="handleHeaderAction"
actionIcon="create"
leftType="settings"
:onLeftAction="handleSettingsPress"
:onFolderToggle="handleFolderToggle"
:isFolderExpanded="isFolderExpanded"
:onTitlePress="handleFolderToggle" />
<!-- 悬浮文件夹列表 - 使用绝对定位实现 --> <!-- 悬浮文件夹列表 - 使用绝对定位实现 -->
<div <div v-if="isFolderExpanded" class="folder-list">
v-if="isFolderExpanded"
class="folder-list">
<FolderManage <FolderManage
:allCount="allNotesCount" :allCount="allNotesCount"
:starredCount="starredNotesCount" :starredCount="starredNotesCount"
@@ -60,6 +66,7 @@
<script setup> <script setup>
import { ref, computed, onMounted } from 'vue' import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useAppStore } from '../stores/useAppStore' import { useAppStore } from '../stores/useAppStore'
import NoteItem from '../components/NoteItem.vue' import NoteItem from '../components/NoteItem.vue'
import Header from '../components/Header.vue' import Header from '../components/Header.vue'
@@ -68,22 +75,25 @@ import SearchBar from '../components/SearchBar.vue'
import { formatNoteListDate } from '../utils/dateUtils' import { formatNoteListDate } from '../utils/dateUtils'
const store = useAppStore() const store = useAppStore()
const router = useRouter()
// 加载初始数据 // 页面挂载时加载初始数据
onMounted(() => { onMounted(() => {
// 检查URL参数是否包含mock数据加载指令 // 检查URL参数是否包含mock数据加载指令,用于开发和演示
const urlParams = new URLSearchParams(window.location.search) const urlParams = new URLSearchParams(window.location.search)
if (urlParams.get('mock') === 'true') { if (urlParams.get('mock') === 'true') {
// 加载预设的模拟数据
store.loadMockData() store.loadMockData()
} else { } else {
// 从localStorage加载用户数据
store.loadData() store.loadData()
} }
}) })
const searchQuery = ref('') const searchQuery = ref('')
const sortBy = ref('date') // 'date', 'title', 'starred' const sortBy = ref('date') // 排序方式:'date'(按日期)、'title'(按标题)、'starred'(按星标)
const isFolderExpanded = ref(false) const isFolderExpanded = ref(false) // 文件夹列表是否展开
const currentFolder = ref('all') // 默认文件夹是"全部便签" const currentFolder = ref('all') // 当前选中的文件夹,默认是"全部便签"
const showAlert = ref(false) const showAlert = ref(false)
const noteToDelete = ref(null) const noteToDelete = ref(null)
@@ -101,13 +111,11 @@ const trashNotesCount = computed(() => {
const filteredNotes = computed(() => { const filteredNotes = computed(() => {
// 预处理搜索查询,提高性能 // 预处理搜索查询,提高性能
const lowerCaseQuery = searchQuery.value.toLowerCase().trim() const lowerCaseQuery = searchQuery.value.toLowerCase().trim()
return store.notes.filter(note => { return store.notes.filter(note => {
// 先检查搜索条件 // 先检查搜索条件
const matchesSearch = !lowerCaseQuery || const matchesSearch = !lowerCaseQuery || note.title.toLowerCase().includes(lowerCaseQuery) || note.content.toLowerCase().includes(lowerCaseQuery)
note.title.toLowerCase().includes(lowerCaseQuery) ||
note.content.toLowerCase().includes(lowerCaseQuery)
if (!matchesSearch) return false if (!matchesSearch) return false
// 再检查文件夹条件 // 再检查文件夹条件
@@ -128,7 +136,9 @@ const filteredNotes = computed(() => {
}) })
}) })
// Filter and sort notes // 过滤并排序便签列表
// 首先按置顶状态排序,置顶的便签排在前面
// 然后根据sortBy的值进行二次排序
const filteredAndSortedNotes = computed(() => { const filteredAndSortedNotes = computed(() => {
return [...filteredNotes.value].sort((a, b) => { return [...filteredNotes.value].sort((a, b) => {
// 置顶的便签排在前面 // 置顶的便签排在前面
@@ -138,9 +148,10 @@ const filteredAndSortedNotes = computed(() => {
// 根据排序方式排序 // 根据排序方式排序
switch (sortBy.value) { switch (sortBy.value) {
case 'title': case 'title':
// 按标题字母顺序排序
return a.title.localeCompare(b.title) return a.title.localeCompare(b.title)
case 'starred': case 'starred':
// 加星的便签排在前面 // 按星标状态排序,加星的便签排在前面
return (b.isStarred ? 1 : 0) - (a.isStarred ? 1 : 0) return (b.isStarred ? 1 : 0) - (a.isStarred ? 1 : 0)
case 'date': case 'date':
default: default:
@@ -170,17 +181,17 @@ const allNotesCount = computed(() => {
}) })
const handleNotePress = noteId => { const handleNotePress = noteId => {
// 导航到编辑页面的逻辑将在路由中处理 // 使用vue-router导航到编辑页面
window.location.hash = `#/editor/${noteId}` router.push(`/editor/${noteId}`)
} }
const handleAddNote = () => { const handleAddNote = () => {
// 导航到编辑页面的逻辑将在路由中处理 // 使用vue-router导航到新建便签页面
window.location.hash = '#/editor' router.push('/editor')
} }
// 处理Header组件的操作按钮点击事件 // 处理Header组件的操作按钮点击事件
const handleHeaderAction = (actionType) => { const handleHeaderAction = actionType => {
if (actionType === 'create') { if (actionType === 'create') {
handleAddNote() handleAddNote()
} }
@@ -236,13 +247,14 @@ const confirmDeleteNote = async () => {
showAlert.value = false showAlert.value = false
} }
// 处理排序方式切换
// 循环切换排序选项:按日期 -> 按标题 -> 按星标 -> 按日期...
const handleSort = () => { const handleSort = () => {
// In a full implementation, this would cycle through sort options
const sortOptions = ['date', 'title', 'starred'] const sortOptions = ['date', 'title', 'starred']
const currentIndex = sortOptions.indexOf(sortBy.value) const currentIndex = sortOptions.indexOf(sortBy.value)
const nextIndex = (currentIndex + 1) % sortOptions.length const nextIndex = (currentIndex + 1) % sortOptions.length
sortBy.value = sortOptions[nextIndex] sortBy.value = sortOptions[nextIndex]
console.log('Sort by:', sortOptions[nextIndex]) console.log('当前排序方式:', sortOptions[nextIndex])
} }
const handleAllNotesClick = () => { const handleAllNotesClick = () => {
@@ -261,13 +273,13 @@ const handleTrashNotesClick = () => {
} }
const handleFolderPress = () => { const handleFolderPress = () => {
// 导航到文件夹页面的逻辑将在路由中处理 // 使用vue-router导航到文件夹页面
window.location.hash = '#/folders' router.push('/folders')
} }
const handleSettingsPress = () => { const handleSettingsPress = () => {
// 导航到设置页面的逻辑将在路由中处理 // 使用vue-router导航到设置页面
window.location.hash = '#/settings' router.push('/settings')
} }
const handleFolderToggle = () => { const handleFolderToggle = () => {
@@ -278,7 +290,7 @@ const handleFolderToggle = () => {
const handleSearch = query => { const handleSearch = query => {
// 搜索功能已在computed属性filteredAndSortedNotes中实现 // 搜索功能已在computed属性filteredAndSortedNotes中实现
console.log('Search for:', query) console.log('Search for:', query)
// 可以在这里添加搜索统计或其它功能 // 可以在这里添加搜索统计或其它功能
if (query && query.length > 0) { if (query && query.length > 0) {
console.log(`Found ${filteredAndSortedNotes.value.length} matching notes`) console.log(`Found ${filteredAndSortedNotes.value.length} matching notes`)
@@ -288,7 +300,7 @@ const handleSearch = query => {
const handleClearSearch = () => { const handleClearSearch = () => {
// 清除搜索已在v-model中处理 // 清除搜索已在v-model中处理
console.log('Search cleared') console.log('Search cleared')
// 清除搜索后可以重置一些状态 // 清除搜索后可以重置一些状态
setSearchQuery('') setSearchQuery('')
} }
@@ -303,7 +315,8 @@ const handleSearchBlur = () => {
// 可以在这里添加失去焦点时的特殊处理 // 可以在这里添加失去焦点时的特殊处理
} }
// 防抖搜索函数,避免频繁触发搜索 // 防抖函数,用于避免频繁触发搜索
// 通过延迟执行函数,只在最后一次调用后执行
const debounceSearch = (func, delay) => { const debounceSearch = (func, delay) => {
let timeoutId let timeoutId
return function (...args) { return function (...args) {
@@ -312,8 +325,8 @@ const debounceSearch = (func, delay) => {
} }
} }
// 防抖搜索处理 // 防抖搜索处理函数延迟300ms执行搜索
const debouncedHandleSearch = debounceSearch((query) => { const debouncedHandleSearch = debounceSearch(query => {
handleSearch(query) handleSearch(query)
}, 300) }, 300)
@@ -382,4 +395,4 @@ const notes = computed(() => store.notes)
.note-item { .note-item {
margin: 0.4rem 0; margin: 0.4rem 0;
} }
</style> </style>

View File

@@ -81,61 +81,75 @@
<script setup> <script setup>
import { computed, onMounted } from 'vue'; import { computed, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { useAppStore } from '../stores/useAppStore'; import { useAppStore } from '../stores/useAppStore';
import Header from '../components/Header.vue'; import Header from '../components/Header.vue';
const store = useAppStore(); const store = useAppStore();
const router = useRouter();
// 加载初始数据 // 页面挂载时加载初始数据
// 从localStorage加载用户设置和便签数据
onMounted(() => { onMounted(() => {
store.loadData(); store.loadData();
}); });
// 切换云同步设置
// 调用store中的方法更新云同步状态
const toggleCloudSync = () => { const toggleCloudSync = () => {
store.toggleCloudSync(); store.toggleCloudSync();
}; };
// 切换深色模式设置
// 调用store中的方法更新深色模式状态
const toggleDarkMode = () => { const toggleDarkMode = () => {
store.toggleDarkMode(); store.toggleDarkMode();
}; };
// 处理登录云同步按钮点击事件
// 在完整实现中,这里会打开登录界面
const handleLogin = () => { const handleLogin = () => {
// In a full implementation, this would open a login screen
console.log('Login to cloud'); console.log('Login to cloud');
}; };
// 处理隐私政策按钮点击事件
// 在完整实现中,这里会显示隐私政策内容
const handlePrivacyPolicy = () => { const handlePrivacyPolicy = () => {
// In a full implementation, this would show the privacy policy
console.log('Privacy policy'); console.log('Privacy policy');
}; };
// 处理服务条款按钮点击事件
// 在完整实现中,这里会显示服务条款内容
const handleTermsOfService = () => { const handleTermsOfService = () => {
// In a full implementation, this would show the terms of service
console.log('Terms of service'); console.log('Terms of service');
}; };
// 处理备份便签按钮点击事件
// 在完整实现中,这里会执行便签备份操作
const handleBackup = () => { const handleBackup = () => {
// In a full implementation, this would backup notes
console.log('Backup notes'); console.log('Backup notes');
}; };
// 处理恢复便签按钮点击事件
// 在完整实现中,这里会执行便签恢复操作
const handleRestore = () => { const handleRestore = () => {
// In a full implementation, this would restore notes
console.log('Restore notes'); console.log('Restore notes');
}; };
// 处理导出便签按钮点击事件
// 在完整实现中,这里会执行便签导出操作
const handleExport = () => { const handleExport = () => {
// In a full implementation, this would export notes
console.log('Export notes'); console.log('Export notes');
}; };
// 处理导入便签按钮点击事件
// 在完整实现中,这里会执行便签导入操作
const handleImport = () => { const handleImport = () => {
// In a full implementation, this would import notes
console.log('Import notes'); console.log('Import notes');
}; };
const handleBackPress = () => { const handleBackPress = () => {
window.history.back(); router.back();
}; };
const settings = computed(() => store.settings); const settings = computed(() => store.settings);

View File

@@ -2,27 +2,59 @@ import { defineStore } from 'pinia'
import * as storage from '../utils/storage' import * as storage from '../utils/storage'
import { getCurrentDateTime, getPastDate } from '../utils/dateUtils' import { getCurrentDateTime, getPastDate } from '../utils/dateUtils'
/**
* 应用状态管理Store
* 使用Pinia进行状态管理包含便签、文件夹和设置数据
*/
export const useAppStore = defineStore('app', { export const useAppStore = defineStore('app', {
/**
* 状态定义
* 包含应用的核心数据:便签列表、文件夹列表和设置
*/
state: () => ({ state: () => ({
notes: [], notes: [], // 便签列表
folders: [], folders: [], // 文件夹列表
settings: { cloudSync: false, darkMode: false }, settings: { cloudSync: false, darkMode: false }, // 应用设置
}), }),
/**
* 计算属性
* 基于状态派生的计算值
*/
getters: { getters: {
/**
* 计算加星便签数量
* @param {Object} state - 当前状态对象
* @returns {number} 加星便签的数量
*/
starredNotesCount: state => { starredNotesCount: state => {
return state.notes.filter(note => note.isStarred).length return state.notes.filter(note => note.isStarred).length
}, },
/**
* 计算所有便签数量
* @param {Object} state - 当前状态对象
* @returns {number} 所有便签的数量
*/
allNotesCount: state => { allNotesCount: state => {
return state.notes.length return state.notes.length
}, },
}, },
/**
* 状态变更操作
* 包含所有修改状态的方法
*/
actions: { actions: {
// 初始化数据 /**
* 初始化数据
* 从localStorage加载便签、文件夹和设置数据
* 如果没有数据则加载预设的mock数据
* @returns {Promise<void>}
*/
async loadData() { async loadData() {
try { try {
// 从localStorage加载数据
const loadedNotes = await storage.getNotes() const loadedNotes = await storage.getNotes()
const loadedFolders = await storage.getFolders() const loadedFolders = await storage.getFolders()
const loadedSettings = await storage.getSettings() const loadedSettings = await storage.getSettings()
@@ -31,6 +63,7 @@ export const useAppStore = defineStore('app', {
if (loadedNotes.length === 0 && loadedFolders.length === 0) { if (loadedNotes.length === 0 && loadedFolders.length === 0) {
this.loadMockData() this.loadMockData()
} else { } else {
// 否则使用加载的数据
this.notes = loadedNotes this.notes = loadedNotes
this.folders = loadedFolders this.folders = loadedFolders
this.settings = loadedSettings this.settings = loadedSettings
@@ -40,7 +73,11 @@ export const useAppStore = defineStore('app', {
} }
}, },
// 加载mock数据 /**
* 加载预设的mock数据
* 用于开发和演示目的,提供示例便签、文件夹和设置
* @returns {Promise<void>}
*/
async loadMockData() { async loadMockData() {
// Mock notes - 使用固定的日期值以避免每次运行时变化 // Mock notes - 使用固定的日期值以避免每次运行时变化
const fixedCurrentDate = '2025-10-12T10:00:00.000Z'; const fixedCurrentDate = '2025-10-12T10:00:00.000Z';
@@ -50,6 +87,7 @@ export const useAppStore = defineStore('app', {
const fixedFourDaysAgo = '2025-10-08T10:00:00.000Z'; const fixedFourDaysAgo = '2025-10-08T10:00:00.000Z';
const fixedFiveDaysAgo = '2025-10-07T10:00:00.000Z'; const fixedFiveDaysAgo = '2025-10-07T10:00:00.000Z';
// 预设的便签示例数据
const mockNotes = [ const mockNotes = [
{ {
id: '1', id: '1',
@@ -58,10 +96,10 @@ export const useAppStore = defineStore('app', {
createdAt: fixedCurrentDate, createdAt: fixedCurrentDate,
updatedAt: fixedCurrentDate, updatedAt: fixedCurrentDate,
folderId: null, folderId: null,
isStarred: true, isStarred: true, // 加星便签
isTop: true, isTop: true, // 置顶便签
hasImage: false, hasImage: false, // 不包含图片
isDeleted: false, isDeleted: false, // 未删除
deletedAt: null, deletedAt: null,
}, },
{ {
@@ -71,10 +109,10 @@ export const useAppStore = defineStore('app', {
createdAt: fixedYesterday, createdAt: fixedYesterday,
updatedAt: fixedYesterday, updatedAt: fixedYesterday,
folderId: null, folderId: null,
isStarred: true, isStarred: true, // 加星便签
isTop: false, isTop: false, // 非置顶
hasImage: true, hasImage: true, // 包含图片
isDeleted: false, isDeleted: false, // 未删除
deletedAt: null, deletedAt: null,
}, },
{ {
@@ -84,10 +122,10 @@ export const useAppStore = defineStore('app', {
createdAt: fixedTwoDaysAgo, createdAt: fixedTwoDaysAgo,
updatedAt: fixedTwoDaysAgo, updatedAt: fixedTwoDaysAgo,
folderId: null, folderId: null,
isStarred: false, isStarred: false, // 非加星
isTop: false, isTop: false, // 非置顶
hasImage: false, hasImage: false, // 不包含图片
isDeleted: false, isDeleted: false, // 未删除
deletedAt: null, deletedAt: null,
}, },
{ {
@@ -97,10 +135,10 @@ export const useAppStore = defineStore('app', {
createdAt: fixedThreeDaysAgo, createdAt: fixedThreeDaysAgo,
updatedAt: fixedThreeDaysAgo, updatedAt: fixedThreeDaysAgo,
folderId: null, folderId: null,
isStarred: false, isStarred: false, // 非加星
isTop: false, isTop: false, // 非置顶
hasImage: false, hasImage: false, // 不包含图片
isDeleted: false, isDeleted: false, // 未删除
deletedAt: null, deletedAt: null,
}, },
{ {
@@ -110,10 +148,10 @@ export const useAppStore = defineStore('app', {
createdAt: fixedFourDaysAgo, createdAt: fixedFourDaysAgo,
updatedAt: fixedFourDaysAgo, updatedAt: fixedFourDaysAgo,
folderId: null, folderId: null,
isStarred: false, isStarred: false, // 非加星
isTop: false, isTop: false, // 非置顶
hasImage: false, hasImage: false, // 不包含图片
isDeleted: false, isDeleted: false, // 未删除
deletedAt: null, deletedAt: null,
}, },
{ {
@@ -123,15 +161,16 @@ export const useAppStore = defineStore('app', {
createdAt: fixedFiveDaysAgo, createdAt: fixedFiveDaysAgo,
updatedAt: fixedFiveDaysAgo, updatedAt: fixedFiveDaysAgo,
folderId: null, folderId: null,
isStarred: false, isStarred: false, // 非加星
isTop: false, isTop: false, // 非置顶
hasImage: false, hasImage: false, // 不包含图片
isDeleted: true, isDeleted: true, // 已删除
deletedAt: fixedYesterday, deletedAt: fixedYesterday,
}, },
] ]
// Mock folders - 使用固定的日期值 // Mock folders - 使用固定的日期值
// 预设的文件夹示例数据
const mockFolders = [ const mockFolders = [
{ {
id: 'folder1', id: 'folder1',
@@ -151,11 +190,13 @@ export const useAppStore = defineStore('app', {
] ]
// Mock settings // Mock settings
// 预设的设置示例数据
const mockSettings = { const mockSettings = {
cloudSync: false, cloudSync: false, // 云同步关闭
darkMode: false, darkMode: false, // 深色模式关闭
} }
// 更新store状态
this.notes = mockNotes this.notes = mockNotes
this.folders = mockFolders this.folders = mockFolders
this.settings = mockSettings this.settings = mockSettings
@@ -166,7 +207,10 @@ export const useAppStore = defineStore('app', {
await storage.saveSettings(mockSettings) await storage.saveSettings(mockSettings)
}, },
// 保存notes到localStorage /**
* 保存便签数据到localStorage
* @returns {Promise<void>}
*/
async saveNotes() { async saveNotes() {
try { try {
await storage.saveNotes(this.notes) await storage.saveNotes(this.notes)
@@ -175,7 +219,10 @@ export const useAppStore = defineStore('app', {
} }
}, },
// 保存folders到localStorage /**
* 保存文件夹数据到localStorage
* @returns {Promise<void>}
*/
async saveFolders() { async saveFolders() {
try { try {
await storage.saveFolders(this.folders) await storage.saveFolders(this.folders)
@@ -184,7 +231,10 @@ export const useAppStore = defineStore('app', {
} }
}, },
// 保存settings到localStorage /**
* 保存设置数据到localStorage
* @returns {Promise<void>}
*/
async saveSettings() { async saveSettings() {
try { try {
await storage.saveSettings(this.settings) await storage.saveSettings(this.settings)
@@ -193,11 +243,20 @@ export const useAppStore = defineStore('app', {
} }
}, },
// Note functions /**
* 便签操作函数
*/
/**
* 添加新便签
* @param {Object} note - 便签对象
* @returns {Promise<Object>} 新创建的便签对象
*/
async addNote(note) { async addNote(note) {
try { try {
const newNote = await storage.addNote(note) const newNote = await storage.addNote(note)
this.notes.push(newNote) this.notes.push(newNote)
return newNote return newNote
} catch (error) { } catch (error) {
console.error('Error adding note:', error) console.error('Error adding note:', error)
@@ -205,6 +264,12 @@ export const useAppStore = defineStore('app', {
} }
}, },
/**
* 更新便签
* @param {string} id - 便签ID
* @param {Object} updates - 要更新的属性对象
* @returns {Promise<Object>} 更新后的便签对象
*/
async updateNote(id, updates) { async updateNote(id, updates) {
try { try {
const updatedNote = await storage.updateNote(id, updates) const updatedNote = await storage.updateNote(id, updates)
@@ -221,6 +286,11 @@ export const useAppStore = defineStore('app', {
} }
}, },
/**
* 删除便签
* @param {string} id - 要删除的便签ID
* @returns {Promise<boolean>} 删除成功返回true失败返回false
*/
async deleteNote(id) { async deleteNote(id) {
try { try {
const result = await storage.deleteNote(id) const result = await storage.deleteNote(id)
@@ -234,7 +304,12 @@ export const useAppStore = defineStore('app', {
} }
}, },
// 将便签移至回收站 /**
* 将便签移至回收站
* 将便签标记为已删除状态,并记录删除时间
* @param {string} id - 便签ID
* @returns {Promise<Object>} 更新后的便签对象
*/
async moveToTrash(id) { async moveToTrash(id) {
try { try {
const updatedNote = await storage.updateNote(id, { isDeleted: true, deletedAt: getCurrentDateTime() }) const updatedNote = await storage.updateNote(id, { isDeleted: true, deletedAt: getCurrentDateTime() })
@@ -251,7 +326,12 @@ export const useAppStore = defineStore('app', {
} }
}, },
// 永久删除便签 /**
* 永久删除便签
* 从便签列表中彻底移除便签
* @param {string} id - 便签ID
* @returns {Promise<boolean>} 删除成功返回true失败返回false
*/
async permanentlyDeleteNote(id) { async permanentlyDeleteNote(id) {
try { try {
const result = await storage.deleteNote(id) const result = await storage.deleteNote(id)
@@ -265,7 +345,15 @@ export const useAppStore = defineStore('app', {
} }
}, },
// Folder functions /**
* 文件夹操作函数
*/
/**
* 添加新文件夹
* @param {Object} folder - 文件夹对象
* @returns {Promise<Object>} 新创建的文件夹对象
*/
async addFolder(folder) { async addFolder(folder) {
try { try {
const newFolder = await storage.addFolder(folder) const newFolder = await storage.addFolder(folder)
@@ -277,7 +365,15 @@ export const useAppStore = defineStore('app', {
} }
}, },
// Settings functions /**
* 设置操作函数
*/
/**
* 更新设置
* @param {Object} newSettings - 新的设置对象
* @returns {Promise<void>}
*/
async updateSettings(newSettings) { async updateSettings(newSettings) {
try { try {
const updatedSettings = { ...this.settings, ...newSettings } const updatedSettings = { ...this.settings, ...newSettings }
@@ -289,12 +385,20 @@ export const useAppStore = defineStore('app', {
} }
}, },
// 切换云同步设置 /**
* 切换云同步设置
* 开启或关闭云同步功能
* @returns {Promise<void>}
*/
async toggleCloudSync() { async toggleCloudSync() {
await this.updateSettings({ cloudSync: !this.settings.cloudSync }) await this.updateSettings({ cloudSync: !this.settings.cloudSync })
}, },
// 切换深色模式设置 /**
* 切换深色模式设置
* 开启或关闭深色模式
* @returns {Promise<void>}
*/
async toggleDarkMode() { async toggleDarkMode() {
await this.updateSettings({ darkMode: !this.settings.darkMode }) await this.updateSettings({ darkMode: !this.settings.darkMode })
}, },

View File

@@ -1,11 +1,19 @@
import { getCurrentDateTime, getTimestamp } from './dateUtils' import { getCurrentDateTime, getTimestamp } from './dateUtils'
// Storage keys // 本地存储键名常量
const NOTES_KEY = 'notes'; // 用于在localStorage中标识不同类型的数据
const FOLDERS_KEY = 'folders'; const NOTES_KEY = 'notes'; // 便签数据键名
const SETTINGS_KEY = 'settings'; const FOLDERS_KEY = 'folders'; // 文件夹数据键名
const SETTINGS_KEY = 'settings'; // 设置数据键名
// Notes functions // 便签操作函数
// 提供便签的增删改查功能
/**
* 获取所有便签数据
* 从localStorage中读取便签数据并解析为JavaScript对象
* @returns {Promise<Array>} 便签数组,如果读取失败则返回空数组
*/
export const getNotes = async () => { export const getNotes = async () => {
try { try {
const notesJson = localStorage.getItem(NOTES_KEY); const notesJson = localStorage.getItem(NOTES_KEY);
@@ -16,6 +24,12 @@ export const getNotes = async () => {
} }
}; };
/**
* 保存便签数据
* 将便签数组转换为JSON字符串并保存到localStorage
* @param {Array} notes - 便签数组
* @returns {Promise<void>}
*/
export const saveNotes = async (notes) => { export const saveNotes = async (notes) => {
try { try {
localStorage.setItem(NOTES_KEY, JSON.stringify(notes)); localStorage.setItem(NOTES_KEY, JSON.stringify(notes));
@@ -24,19 +38,27 @@ export const saveNotes = async (notes) => {
} }
}; };
/**
* 添加新便签
* 创建一个新的便签对象并添加到便签列表中
* @param {Object} note - 便签对象,包含便签内容和其他属性
* @returns {Promise<Object>} 新创建的便签对象
*/
export const addNote = async (note) => { export const addNote = async (note) => {
// 创建新的便签对象,添加必要的属性
const newNote = { const newNote = {
...note, ...note,
id: getTimestamp().toString(), id: getTimestamp().toString(), // 使用时间戳生成唯一ID
createdAt: getCurrentDateTime(), createdAt: getCurrentDateTime(), // 创建时间
updatedAt: getCurrentDateTime(), updatedAt: getCurrentDateTime(), // 更新时间
isStarred: note.isStarred || false, isStarred: note.isStarred || false, // 是否加星
isTop: note.isTop || false, isTop: note.isTop || false, // 是否置顶
hasImage: note.hasImage || false, hasImage: note.hasImage || false, // 是否包含图片
isDeleted: note.isDeleted || false, isDeleted: note.isDeleted || false, // 是否已删除
deletedAt: note.deletedAt || null deletedAt: note.deletedAt || null // 删除时间
}; };
// 获取现有便签列表,添加新便签并保存
const notes = await getNotes(); const notes = await getNotes();
notes.push(newNote); notes.push(newNote);
await saveNotes(notes); await saveNotes(notes);
@@ -44,35 +66,62 @@ export const addNote = async (note) => {
return newNote; return newNote;
}; };
/**
* 更新便签
* 根据ID查找并更新便签信息
* @param {string} id - 便签ID
* @param {Object} updates - 要更新的属性对象
* @returns {Promise<Object|null>} 更新后的便签对象如果未找到则返回null
*/
export const updateNote = async (id, updates) => { export const updateNote = async (id, updates) => {
// 获取所有便签并查找要更新的便签
const notes = await getNotes(); const notes = await getNotes();
const index = notes.findIndex(note => note.id === id); const index = notes.findIndex(note => note.id === id);
// 如果未找到指定ID的便签返回null
if (index === -1) return null; if (index === -1) return null;
// 创建更新后的便签对象
const updatedNote = { const updatedNote = {
...notes[index], ...notes[index],
...updates, ...updates,
updatedAt: getCurrentDateTime(), updatedAt: getCurrentDateTime(), // 更新最后修改时间
}; };
// 更新便签列表并保存
notes[index] = updatedNote; notes[index] = updatedNote;
await saveNotes(notes); await saveNotes(notes);
return updatedNote; return updatedNote;
}; };
/**
* 删除便签
* 根据ID从便签列表中移除便签
* @param {string} id - 要删除的便签ID
* @returns {Promise<boolean>} 删除成功返回true未找到便签返回false
*/
export const deleteNote = async (id) => { export const deleteNote = async (id) => {
// 获取所有便签并过滤掉要删除的便签
const notes = await getNotes(); const notes = await getNotes();
const filteredNotes = notes.filter(note => note.id !== id); const filteredNotes = notes.filter(note => note.id !== id);
// 如果便签数量没有变化,说明未找到要删除的便签
if (notes.length === filteredNotes.length) return false; if (notes.length === filteredNotes.length) return false;
// 保存更新后的便签列表
await saveNotes(filteredNotes); await saveNotes(filteredNotes);
return true; return true;
}; };
// Folders functions // 文件夹操作函数
// 提供文件夹的增删改查功能
/**
* 获取所有文件夹数据
* 从localStorage中读取文件夹数据并解析为JavaScript对象
* @returns {Promise<Array>} 文件夹数组,如果读取失败则返回空数组
*/
export const getFolders = async () => { export const getFolders = async () => {
try { try {
const foldersJson = localStorage.getItem(FOLDERS_KEY); const foldersJson = localStorage.getItem(FOLDERS_KEY);
@@ -83,6 +132,12 @@ export const getFolders = async () => {
} }
}; };
/**
* 保存文件夹数据
* 将文件夹数组转换为JSON字符串并保存到localStorage
* @param {Array} folders - 文件夹数组
* @returns {Promise<void>}
*/
export const saveFolders = async (folders) => { export const saveFolders = async (folders) => {
try { try {
localStorage.setItem(FOLDERS_KEY, JSON.stringify(folders)); localStorage.setItem(FOLDERS_KEY, JSON.stringify(folders));
@@ -91,13 +146,21 @@ export const saveFolders = async (folders) => {
} }
}; };
/**
* 添加新文件夹
* 创建一个新的文件夹对象并添加到文件夹列表中
* @param {Object} folder - 文件夹对象,包含文件夹名称等属性
* @returns {Promise<Object>} 新创建的文件夹对象
*/
export const addFolder = async (folder) => { export const addFolder = async (folder) => {
// 创建新的文件夹对象,添加必要的属性
const newFolder = { const newFolder = {
...folder, ...folder,
id: getTimestamp().toString(), id: getTimestamp().toString(), // 使用时间戳生成唯一ID
createdAt: getCurrentDateTime(), createdAt: getCurrentDateTime(), // 创建时间
}; };
// 获取现有文件夹列表,添加新文件夹并保存
const folders = await getFolders(); const folders = await getFolders();
folders.push(newFolder); folders.push(newFolder);
await saveFolders(folders); await saveFolders(folders);
@@ -105,17 +168,32 @@ export const addFolder = async (folder) => {
return newFolder; return newFolder;
}; };
// Settings functions // 设置操作函数
// 提供应用设置的读取和保存功能
/**
* 获取应用设置
* 从localStorage中读取设置数据并解析为JavaScript对象
* @returns {Promise<Object>} 设置对象,如果读取失败则返回默认设置
*/
export const getSettings = async () => { export const getSettings = async () => {
try { try {
const settingsJson = localStorage.getItem(SETTINGS_KEY); const settingsJson = localStorage.getItem(SETTINGS_KEY);
// 如果没有保存的设置,返回默认设置
return settingsJson ? JSON.parse(settingsJson) : { cloudSync: false, darkMode: false }; return settingsJson ? JSON.parse(settingsJson) : { cloudSync: false, darkMode: false };
} catch (error) { } catch (error) {
console.error('Error getting settings:', error); console.error('Error getting settings:', error);
// 出错时返回默认设置
return { cloudSync: false, darkMode: false }; return { cloudSync: false, darkMode: false };
} }
}; };
/**
* 保存应用设置
* 将设置对象转换为JSON字符串并保存到localStorage
* @param {Object} settings - 设置对象
* @returns {Promise<void>}
*/
export const saveSettings = async (settings) => { export const saveSettings = async (settings) => {
try { try {
localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings)); localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings));