You've already forked SmartisanNote.Remake
优化 头部便签管理点击区域;
补充注释;
This commit is contained in:
63
package-lock.json
generated
63
package-lock.json
generated
@@ -13,6 +13,7 @@
|
||||
"@capacitor/cli": "^5.7.2",
|
||||
"@capacitor/core": "^5.7.2",
|
||||
"@capacitor/ios": "^5.7.2",
|
||||
"@ionic/vue": "^8.7.6",
|
||||
"@vue/cli-service": "^5.0.9",
|
||||
"@vue/compiler-sfc": "^3.5.22",
|
||||
"basic-ftp": "^5.0.5",
|
||||
@@ -2150,6 +2151,26 @@
|
||||
"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": {
|
||||
"version": "2.1.6",
|
||||
"resolved": "https://registry.npmmirror.com/@ionic/utils-array/-/utils-array-2.1.6.tgz",
|
||||
@@ -2359,6 +2380,26 @@
|
||||
"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": {
|
||||
"version": "0.3.13",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmmirror.com/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz",
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
"@capacitor/cli": "^5.7.2",
|
||||
"@capacitor/core": "^5.7.2",
|
||||
"@capacitor/ios": "^5.7.2",
|
||||
"@ionic/vue": "^8.7.6",
|
||||
"@vue/cli-service": "^5.0.9",
|
||||
"@vue/compiler-sfc": "^3.5.22",
|
||||
"basic-ftp": "^5.0.5",
|
||||
|
||||
@@ -6,5 +6,4 @@
|
||||
|
||||
<script setup>
|
||||
import '@/common/base.css'
|
||||
// App根组件不需要额外的逻辑
|
||||
</script>
|
||||
@@ -5,8 +5,8 @@
|
||||
<img class="left-icon" :src="leftIconSource" @click="handleLeftAction" />
|
||||
|
||||
<!-- 标题区域 -->
|
||||
<div class="title-container" @click="handleTitlePress">
|
||||
<span class="text">{{ title }}</span>
|
||||
<div class="title-container">
|
||||
<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" />
|
||||
</div>
|
||||
@@ -118,7 +118,7 @@ const handleLeftAction = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const handleAction = (actionType) => {
|
||||
const handleAction = actionType => {
|
||||
// 处理右侧操作按钮点击事件
|
||||
if (props.onAction) {
|
||||
props.onAction(actionType)
|
||||
@@ -156,7 +156,7 @@ const handleTitlePress = () => {
|
||||
cursor: pointer;
|
||||
}
|
||||
.right-group {
|
||||
gap: .6rem;
|
||||
gap: 0.6rem;
|
||||
}
|
||||
|
||||
.image_4-placeholder {
|
||||
|
||||
@@ -79,13 +79,15 @@ const isSlided = ref(false) // 是否已经滑动到阈值
|
||||
|
||||
const formattedDate = computed(() => {
|
||||
// 直接返回已经格式化的日期字符串
|
||||
// 日期格式化已在父组件中完成
|
||||
return props.date
|
||||
})
|
||||
|
||||
// 处理显示内容,过滤HTML标签并只显示第一行
|
||||
// 用于在便签列表中显示便签的预览内容
|
||||
const displayContent = computed(() => {
|
||||
console.log('NoteItem content:', props.content)
|
||||
// 过滤HTML标签
|
||||
// 过滤HTML标签,只保留纯文本内容
|
||||
let text = props.content.replace(/<[^>]*>/g, '')
|
||||
console.log('NoteItem text without HTML:', text)
|
||||
|
||||
@@ -100,8 +102,11 @@ const displayContent = computed(() => {
|
||||
})
|
||||
|
||||
// 滑动阈值(删除按钮宽度)
|
||||
// 当滑动距离超过此值时,显示删除按钮
|
||||
const SLIDE_THRESHOLD = 64 // 4rem 转换为 px
|
||||
|
||||
// 处理便签点击事件
|
||||
// 只有在未滑动状态下才触发点击事件,避免与滑动操作冲突
|
||||
const handlePress = () => {
|
||||
// 只有在未滑动状态下才触发点击事件
|
||||
if (slideOffset.value === 0 && props.onPress) {
|
||||
@@ -109,31 +114,39 @@ const handlePress = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 处理星标切换事件
|
||||
// 点击星标图标时调用父组件传递的回调函数
|
||||
const handleStarToggle = () => {
|
||||
if (props.onStarToggle) {
|
||||
props.onStarToggle()
|
||||
}
|
||||
}
|
||||
|
||||
// 处理置顶切换事件
|
||||
// 点击置顶图标时调用父组件传递的回调函数
|
||||
const handleTopToggle = () => {
|
||||
if (props.onTopToggle) {
|
||||
props.onTopToggle()
|
||||
}
|
||||
}
|
||||
|
||||
// 处理删除事件
|
||||
// 点击删除按钮时调用父组件传递的回调函数
|
||||
const handleDelete = () => {
|
||||
if (props.onDelete) {
|
||||
props.onDelete()
|
||||
}
|
||||
}
|
||||
|
||||
// 触摸开始
|
||||
// 触摸开始事件处理函数
|
||||
// 记录触摸开始时的X坐标,用于计算滑动距离
|
||||
const handleTouchStart = e => {
|
||||
// 重置滑动状态
|
||||
startX.value = e.touches[0].clientX
|
||||
}
|
||||
|
||||
// 触摸移动
|
||||
// 触摸移动事件处理函数
|
||||
// 根据手指移动距离计算便签条的水平偏移量
|
||||
const handleTouchMove = e => {
|
||||
if (!startX.value) return
|
||||
|
||||
@@ -147,15 +160,15 @@ const handleTouchMove = e => {
|
||||
// 设置滑动状态
|
||||
isSliding.value = true
|
||||
|
||||
// 应用阻尼效果
|
||||
// 应用阻尼效果,使超过阈值后的滑动更加困难
|
||||
let offset = 0
|
||||
if (diffX <= SLIDE_THRESHOLD) {
|
||||
// 线性滑动
|
||||
// 线性滑动,在阈值内正常滑动
|
||||
offset = diffX
|
||||
} else {
|
||||
// 超过阈值后应用阻尼效果
|
||||
// 超过阈值后应用阻尼效果,增加滑动阻力
|
||||
const excess = diffX - SLIDE_THRESHOLD
|
||||
offset = SLIDE_THRESHOLD + excess * 0.03 // 0.3 为阻尼系数
|
||||
offset = SLIDE_THRESHOLD + excess * 0.03 // 0.03 为阻尼系数
|
||||
}
|
||||
|
||||
slideOffset.value = offset
|
||||
@@ -171,12 +184,14 @@ const handleTouchMove = e => {
|
||||
}
|
||||
}
|
||||
|
||||
// 触摸结束
|
||||
// 触摸结束事件处理函数
|
||||
// 根据滑动距离决定便签条的最终位置
|
||||
const handleTouchEnd = () => {
|
||||
if (!startX.value) return
|
||||
|
||||
// 如果滑动超过阈值,保持滑出状态;否则回弹
|
||||
if (slideOffset.value >= SLIDE_THRESHOLD) {
|
||||
// 保持滑出状态,显示删除按钮
|
||||
slideOffset.value = SLIDE_THRESHOLD
|
||||
isSlided.value = true
|
||||
} else {
|
||||
|
||||
@@ -50,42 +50,43 @@ onMounted(() => {
|
||||
}
|
||||
})
|
||||
|
||||
// 工具配置
|
||||
// 工具栏配置
|
||||
// 定义富文本编辑器的所有工具按钮及其功能
|
||||
const tools = ref([
|
||||
{
|
||||
name: 'bold',
|
||||
name: 'bold', // 加粗工具
|
||||
icon: '/assets/icons/drawable-xxhdpi/rtf_bold_normal.9.png',
|
||||
action: () => formatText('bold'),
|
||||
active: false,
|
||||
action: () => formatText('bold'), // 执行加粗格式化
|
||||
active: false, // 工具是否处于激活状态
|
||||
},
|
||||
{
|
||||
name: 'center',
|
||||
name: 'center', // 居中对齐工具
|
||||
icon: '/assets/icons/drawable-xxhdpi/rtf_center_normal.9.png',
|
||||
action: () => formatText('justifyCenter'),
|
||||
action: () => formatText('justifyCenter'), // 执行居中对齐格式化
|
||||
active: false,
|
||||
},
|
||||
{
|
||||
name: 'todo',
|
||||
name: 'todo', // 待办事项工具
|
||||
icon: '/assets/icons/drawable-xxhdpi/rtf_gtasks_normal.9.png',
|
||||
action: () => formatText('insertTodoList'),
|
||||
action: () => formatText('insertTodoList'), // 插入待办事项列表
|
||||
active: false,
|
||||
},
|
||||
{
|
||||
name: 'list',
|
||||
name: 'list', // 无序列表工具
|
||||
icon: '/assets/icons/drawable-xxhdpi/rtf_list_normal.9.png',
|
||||
action: () => formatText('insertUnorderedList'),
|
||||
action: () => formatText('insertUnorderedList'), // 插入无序列表
|
||||
active: false,
|
||||
},
|
||||
{
|
||||
name: 'header',
|
||||
name: 'header', // 标题工具
|
||||
icon: '/assets/icons/drawable-xxhdpi/rtf_header_normal.9.png',
|
||||
action: () => formatText('formatBlock', 'h2'),
|
||||
action: () => formatText('formatBlock', 'h2'), // 格式化为二级标题
|
||||
active: false,
|
||||
},
|
||||
{
|
||||
name: 'quote',
|
||||
name: 'quote', // 引用工具
|
||||
icon: '/assets/icons/drawable-xxhdpi/rtf_quot_normal.9.png',
|
||||
action: () => insertQuote(),
|
||||
action: () => insertQuote(), // 插入引用格式
|
||||
active: false,
|
||||
},
|
||||
])
|
||||
@@ -112,6 +113,7 @@ const handleInput = () => {
|
||||
}
|
||||
|
||||
// 检查当前选区是否已经在某种格式中
|
||||
// 用于防止重复应用相同的格式,例如重复加粗
|
||||
const isAlreadyInFormat = formatType => {
|
||||
const selection = window.getSelection()
|
||||
if (selection.rangeCount > 0) {
|
||||
@@ -121,10 +123,15 @@ const isAlreadyInFormat = formatType => {
|
||||
// 向上查找父元素,检查是否已经在指定格式中
|
||||
let current = container.nodeType === Node.TEXT_NODE ? container.parentElement : container
|
||||
while (current && current !== editorRef.value) {
|
||||
// 检查加粗格式
|
||||
if (formatType === 'bold' && current.tagName === 'B') return true
|
||||
// 检查居中对齐格式
|
||||
if (formatType === 'center' && current.style.textAlign === 'center') 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 === 'list' && (current.tagName === 'UL' || current.tagName === 'OL' || current.tagName === 'LI')) return true
|
||||
current = current.parentElement
|
||||
}
|
||||
@@ -133,6 +140,7 @@ const isAlreadyInFormat = formatType => {
|
||||
}
|
||||
|
||||
// 检查是否在列表、引用或待办事项中(用于嵌套限制)
|
||||
// 防止在已有的列表、引用或待办事项中再次插入相同类型的元素
|
||||
const isInListOrQuote = () => {
|
||||
const selection = window.getSelection()
|
||||
if (selection.rangeCount > 0) {
|
||||
@@ -142,7 +150,10 @@ const isInListOrQuote = () => {
|
||||
// 向上查找父元素,检查是否在列表、引用或待办事项中
|
||||
let current = container.nodeType === Node.TEXT_NODE ? container.parentElement : container
|
||||
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
|
||||
}
|
||||
current = current.parentElement
|
||||
@@ -152,8 +163,10 @@ const isInListOrQuote = () => {
|
||||
}
|
||||
|
||||
// 格式化文本
|
||||
// 根据指定的命令和值对选中文本应用格式
|
||||
const formatText = (command, value = null) => {
|
||||
// 检查是否已经应用了相同的格式,如果已应用则取消格式
|
||||
// 例如,如果文本已经是加粗的,再次点击加粗按钮会取消加粗
|
||||
if (command === 'bold' && isAlreadyInFormat('bold')) {
|
||||
document.execCommand('bold', false, null)
|
||||
updateToolbarState()
|
||||
@@ -161,6 +174,7 @@ const formatText = (command, value = null) => {
|
||||
return
|
||||
}
|
||||
|
||||
// 处理居中对齐切换:如果已居中则取消居中
|
||||
if (command === 'justifyCenter' && isAlreadyInFormat('center')) {
|
||||
document.execCommand('justifyLeft', false, null)
|
||||
updateToolbarState()
|
||||
@@ -168,6 +182,7 @@ const formatText = (command, value = null) => {
|
||||
return
|
||||
}
|
||||
|
||||
// 处理标题格式切换:如果已是标题则转为普通段落
|
||||
if (command === 'formatBlock' && value === 'h2' && isAlreadyInFormat('header')) {
|
||||
document.execCommand('formatBlock', false, '<p>')
|
||||
updateToolbarState()
|
||||
@@ -175,6 +190,7 @@ const formatText = (command, value = null) => {
|
||||
return
|
||||
}
|
||||
|
||||
// 处理列表格式切换:如果已是列表则取消列表
|
||||
if (command === 'insertUnorderedList' && isAlreadyInFormat('list')) {
|
||||
document.execCommand('insertUnorderedList', false, null)
|
||||
updateToolbarState()
|
||||
@@ -189,13 +205,16 @@ const formatText = (command, value = null) => {
|
||||
return
|
||||
}
|
||||
|
||||
// 检查嵌套限制
|
||||
// 检查嵌套限制,防止在列表、引用中再次插入列表或引用
|
||||
if ((command === 'insertUnorderedList' || (command === 'formatBlock' && value === 'blockquote')) && isInListOrQuote()) {
|
||||
return
|
||||
}
|
||||
|
||||
// 执行格式化命令
|
||||
document.execCommand(command, false, value)
|
||||
// 更新工具栏状态以反映当前格式
|
||||
updateToolbarState()
|
||||
// 触发输入事件以更新内容
|
||||
handleInput()
|
||||
}
|
||||
|
||||
@@ -283,39 +302,40 @@ const insertQuote = () => {
|
||||
}
|
||||
|
||||
// 插入待办事项列表
|
||||
// 创建一个可交互的待办事项元素,包含复选框图标和可编辑内容区域
|
||||
const insertTodoList = () => {
|
||||
const selection = window.getSelection()
|
||||
if (selection.rangeCount > 0) {
|
||||
const range = selection.getRangeAt(0)
|
||||
|
||||
// 检查嵌套限制
|
||||
// 检查嵌套限制,防止在列表或引用中插入待办事项
|
||||
if (isInListOrQuote()) return
|
||||
|
||||
// 创建待办事项容器
|
||||
const todoContainer = document.createElement('div')
|
||||
todoContainer.contentEditable = false
|
||||
todoContainer.contentEditable = false // 容器本身不可编辑
|
||||
todoContainer.className = 'todo-container'
|
||||
|
||||
// 创建图标元素
|
||||
// 创建图标元素(复选框)
|
||||
const icon = document.createElement('img')
|
||||
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 = '待办事项'
|
||||
|
||||
// 创建内容容器
|
||||
// 创建内容容器(可编辑区域)
|
||||
const contentSpan = document.createElement('div')
|
||||
contentSpan.contentEditable = true
|
||||
contentSpan.contentEditable = true // 内容区域可编辑
|
||||
contentSpan.className = 'todo-content'
|
||||
contentSpan.textContent = '待办事项'
|
||||
contentSpan.textContent = '待办事项' // 默认文本
|
||||
|
||||
// 组装元素
|
||||
// 组装元素:将图标和内容区域添加到容器中
|
||||
todoContainer.appendChild(icon)
|
||||
todoContainer.appendChild(contentSpan)
|
||||
|
||||
// 插入到当前光标位置
|
||||
range.insertNode(todoContainer)
|
||||
|
||||
// 添加换行
|
||||
// 添加换行,确保待办事项下方有空白行
|
||||
const br = document.createElement('br')
|
||||
todoContainer.parentNode.insertBefore(br, todoContainer.nextSibling)
|
||||
|
||||
@@ -338,19 +358,21 @@ const insertTodoList = () => {
|
||||
}
|
||||
}, 0)
|
||||
|
||||
// 添加事件监听器到图标
|
||||
// 添加事件监听器到图标,实现待办事项完成状态切换
|
||||
icon.addEventListener('click', function () {
|
||||
// 根据当前状态切换图标
|
||||
// 根据当前状态切换图标和样式
|
||||
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)'
|
||||
contentSpan.style.textDecoration = 'line-through'
|
||||
// 切换到完成状态
|
||||
this.src = '/assets/icons/drawable-xxhdpi/rtf_icon_gtasks_light.png' // 完成状态图标
|
||||
contentSpan.style.color = 'var(--text-tertiary)' // 灰色文字
|
||||
contentSpan.style.textDecoration = 'line-through' // 添加删除线
|
||||
} else {
|
||||
this.src = '/assets/icons/drawable-xxhdpi/rtf_icon_gtasks.png'
|
||||
contentSpan.style.color = 'var(--note-content)'
|
||||
contentSpan.style.textDecoration = 'none'
|
||||
// 切换到未完成状态
|
||||
this.src = '/assets/icons/drawable-xxhdpi/rtf_icon_gtasks.png' // 未完成状态图标
|
||||
contentSpan.style.color = 'var(--note-content)' // 正常文字颜色
|
||||
contentSpan.style.textDecoration = 'none' // 移除删除线
|
||||
}
|
||||
handleInput()
|
||||
handleInput() // 触发内容更新
|
||||
})
|
||||
|
||||
// 添加事件监听器到内容区域,监听内容变化和按键事件
|
||||
@@ -365,8 +387,8 @@ const insertTodoList = () => {
|
||||
}, 0)
|
||||
}
|
||||
|
||||
contentSpan.addEventListener('input', checkContent)
|
||||
contentSpan.addEventListener('blur', checkContent)
|
||||
contentSpan.addEventListener('input', checkContent) // 内容输入时检查
|
||||
contentSpan.addEventListener('blur', checkContent) // 失去焦点时检查
|
||||
|
||||
// 添加焦点事件监听器,确保工具栏在待办事项获得焦点时保持可见
|
||||
contentSpan.addEventListener('focus', () => {
|
||||
|
||||
24
src/main.js
24
src/main.js
@@ -3,30 +3,48 @@ import { createRouter, createWebHashHistory } from 'vue-router'
|
||||
import { createPinia } from 'pinia'
|
||||
import App from './App.vue'
|
||||
|
||||
// Pages
|
||||
// 导入页面组件
|
||||
// 便签列表页面
|
||||
import NoteListPage from './pages/NoteListPage.vue'
|
||||
// 便签编辑页面(用于新建和编辑便签)
|
||||
import NoteEditorPage from './pages/NoteEditorPage.vue'
|
||||
// 文件夹管理页面
|
||||
import FolderPage from './pages/FolderPage.vue'
|
||||
// 设置页面
|
||||
import SettingsPage from './pages/SettingsPage.vue'
|
||||
|
||||
// Router
|
||||
// 配置路由规则
|
||||
// 定义应用的所有路由路径和对应的组件
|
||||
const routes = [
|
||||
// 根路径重定向到便签列表页面
|
||||
{ path: '/', redirect: '/notes' },
|
||||
// 便签列表页面路由
|
||||
{ path: '/notes', component: NoteListPage },
|
||||
// 编辑便签页面路由(带便签ID参数)
|
||||
{ path: '/notes/:id', component: NoteEditorPage, props: true },
|
||||
// 新建便签页面路由
|
||||
{ path: '/editor', component: NoteEditorPage },
|
||||
// 编辑便签页面路由(带便签ID参数)
|
||||
{ path: '/editor/:id', component: NoteEditorPage, props: true },
|
||||
// 文件夹管理页面路由
|
||||
{ path: '/folders', component: FolderPage },
|
||||
// 设置页面路由
|
||||
{ path: '/settings', component: SettingsPage }
|
||||
]
|
||||
|
||||
// 创建路由实例
|
||||
// 使用Hash模式以支持静态文件部署
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes
|
||||
})
|
||||
|
||||
// App
|
||||
// 创建并挂载Vue应用实例
|
||||
// 配置Pinia状态管理和Vue Router路由
|
||||
const app = createApp(App)
|
||||
// 使用Pinia进行状态管理
|
||||
app.use(createPinia())
|
||||
// 使用Vue Router进行路由管理
|
||||
app.use(router)
|
||||
// 挂载应用到DOM元素
|
||||
app.mount('#app')
|
||||
@@ -29,12 +29,14 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useAppStore } from '../stores/useAppStore'
|
||||
import { search, closeCircle } from 'ionicons/icons'
|
||||
import FolderManage from '../components/FolderManage.vue'
|
||||
import Header from '../components/Header.vue'
|
||||
|
||||
const store = useAppStore()
|
||||
const router = useRouter()
|
||||
|
||||
// 加载初始数据
|
||||
onMounted(() => {
|
||||
@@ -44,7 +46,8 @@ onMounted(() => {
|
||||
const searchQuery = ref('')
|
||||
const selectedFolder = ref('all')
|
||||
|
||||
// Calculate note count for each folder
|
||||
// 计算每个文件夹中的便签数量
|
||||
// 遍历所有自定义文件夹,统计每个文件夹中的便签数量
|
||||
const foldersWithCount = computed(() => {
|
||||
return store.folders.map(folder => {
|
||||
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 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 archiveCount = 0
|
||||
|
||||
// 合并默认文件夹和自定义文件夹
|
||||
const foldersWithAllNotes = computed(() => {
|
||||
return [
|
||||
{ id: 'all', name: '全部便签', noteCount: allNotesCount.value, createdAt: new Date() },
|
||||
@@ -74,26 +81,28 @@ const foldersWithAllNotes = computed(() => {
|
||||
const handleFolderPress = folderId => {
|
||||
// 更新选中的文件夹状态
|
||||
selectedFolder.value = folderId
|
||||
// 在实际应用中,这里会将选中的文件夹传递回NoteListScreen
|
||||
// 通过导航参数传递选中的文件夹ID
|
||||
window.location.hash = `#/notes?folder=${folderId}`
|
||||
// 使用vue-router导航回便签列表页面,并传递文件夹参数
|
||||
router.push(`/notes?folder=${folderId}`)
|
||||
}
|
||||
|
||||
// 处理添加文件夹按钮点击事件
|
||||
// 在完整实现中,这里会打开文件夹创建对话框
|
||||
const handleAddFolder = () => {
|
||||
// In a full implementation, this would open a folder creation dialog
|
||||
console.log('Add folder pressed')
|
||||
}
|
||||
|
||||
// 处理搜索功能
|
||||
// 在完整实现中,这里会根据搜索关键词过滤文件夹
|
||||
const handleSearch = () => {
|
||||
// In a full implementation, this would filter folders based on searchQuery
|
||||
console.log('Search for:', searchQuery.value)
|
||||
}
|
||||
|
||||
const handleBackPress = () => {
|
||||
window.history.back()
|
||||
router.back()
|
||||
}
|
||||
|
||||
// Filter folders based on search query
|
||||
// 根据搜索关键词过滤文件夹
|
||||
// 将文件夹名称转换为小写进行模糊匹配
|
||||
const filteredFolders = computed(() => {
|
||||
return foldersWithAllNotes.value.filter(folder => folder.name.toLowerCase().includes(searchQuery.value.toLowerCase()))
|
||||
})
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, nextTick, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useAppStore } from '../stores/useAppStore'
|
||||
import Header from '../components/Header.vue'
|
||||
import RichTextEditor from '../components/RichTextEditor.vue'
|
||||
@@ -53,31 +54,35 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
// 为了保持向后兼容性,我们也支持noteId属性
|
||||
// 通过计算属性确保无论使用id还是noteId都能正确获取便签ID
|
||||
const noteId = computed(() => props.id || props.noteId)
|
||||
|
||||
const store = useAppStore()
|
||||
const router = useRouter()
|
||||
const editorRef = ref(null)
|
||||
|
||||
// 设置便签内容的函数
|
||||
// 用于在编辑器中加载指定便签的内容
|
||||
const setNoteContent = async (noteId) => {
|
||||
// 确保store数据已加载
|
||||
// 确保store数据已加载,如果便签列表为空则先加载数据
|
||||
if (store.notes.length === 0) {
|
||||
await store.loadData()
|
||||
console.log('Store loaded, notes count:', store.notes.length)
|
||||
}
|
||||
|
||||
// 从store中查找指定ID的便签
|
||||
const note = store.notes.find(n => n.id === noteId)
|
||||
console.log('Found note:', note)
|
||||
|
||||
// 确保编辑器已经初始化
|
||||
// 确保编辑器已经初始化完成
|
||||
await nextTick()
|
||||
console.log('Editor ref:', editorRef.value)
|
||||
|
||||
if (note) {
|
||||
console.log('Setting content:', note.content)
|
||||
// 无论editorRef是否可用,都先设置content的值
|
||||
// 无论editorRef是否可用,都先设置content的值作为备份
|
||||
content.value = note.content || ''
|
||||
// 如果editorRef可用,直接设置内容
|
||||
// 如果editorRef可用,直接设置编辑器内容
|
||||
if (editorRef.value) {
|
||||
editorRef.value.setContent(note.content || '')
|
||||
}
|
||||
@@ -113,11 +118,12 @@ watch(() => store.notes, async (newNotes) => {
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
// Check if we're editing an existing note
|
||||
// 检查是否正在编辑现有便签
|
||||
// 如果noteId存在则表示是编辑模式,否则是新建模式
|
||||
const isEditing = !!noteId.value
|
||||
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 || '')
|
||||
|
||||
// 当组件挂载时,确保编辑器初始化为空内容(针对新建便签)
|
||||
@@ -137,6 +143,7 @@ watch(() => store.notes, async (newNotes) => {
|
||||
const showAlert = ref(false)
|
||||
|
||||
// 防抖函数
|
||||
// 用于避免函数在短时间内被频繁调用,提高性能
|
||||
const debounce = (func, delay) => {
|
||||
let timeoutId
|
||||
return function (...args) {
|
||||
@@ -146,18 +153,21 @@ const debounce = (func, delay) => {
|
||||
}
|
||||
|
||||
// 防抖处理内容变化
|
||||
// 延迟300ms更新内容,避免用户输入时频繁触发更新
|
||||
const debouncedHandleContentChange = debounce((newContent) => {
|
||||
content.value = newContent
|
||||
console.log('Content updated:', newContent)
|
||||
}, 300)
|
||||
|
||||
// 监听编辑器内容变化
|
||||
// 当编辑器内容发生变化时调用此函数
|
||||
const handleContentChange = (newContent) => {
|
||||
console.log('Editor content changed:', newContent)
|
||||
debouncedHandleContentChange(newContent)
|
||||
}
|
||||
|
||||
// 计算属性
|
||||
// 计算属性 - 格式化时间显示
|
||||
// 如果是编辑现有便签则显示便签的更新时间,否则显示当前时间
|
||||
const formattedTime = computed(() => {
|
||||
if (existingNote?.updatedAt) {
|
||||
return formatNoteEditorDate(existingNote.updatedAt)
|
||||
@@ -165,8 +175,10 @@ const formattedTime = computed(() => {
|
||||
return formatNoteEditorDate(new Date())
|
||||
})
|
||||
|
||||
// 计算属性 - 计算字数
|
||||
// 移除HTML标签后计算纯文本字数
|
||||
const wordCount = computed(() => {
|
||||
// 移除HTML标签计算字数
|
||||
// 使用正则表达式移除HTML标签,只保留纯文本内容
|
||||
const textContent = content.value.replace(/<[^>]*>/g, '')
|
||||
return textContent.length || 0
|
||||
})
|
||||
@@ -191,7 +203,7 @@ const handleSave = async () => {
|
||||
}
|
||||
|
||||
// Navigate back to the previous screen
|
||||
window.location.hash = '#/notes'
|
||||
router.push('/notes')
|
||||
} catch (error) {
|
||||
// In a full implementation, show an alert or toast
|
||||
console.log('Save error: Failed to save note. Please try again.')
|
||||
@@ -206,7 +218,7 @@ const handleCancel = () => {
|
||||
if (hasUnsavedChanges) {
|
||||
showAlert.value = true
|
||||
} else {
|
||||
window.location.hash = '#/notes'
|
||||
router.push('/notes')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,7 +235,7 @@ const handleCreate = async () => {
|
||||
})
|
||||
|
||||
// Navigate back to the previous screen
|
||||
window.location.hash = '#/notes'
|
||||
router.push('/notes')
|
||||
} catch (error) {
|
||||
// In a full implementation, show an alert or toast
|
||||
console.log('Create error: Failed to create note. Please try again.')
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
<template>
|
||||
<ion-app>
|
||||
<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
|
||||
v-if="isFolderExpanded"
|
||||
class="folder-list">
|
||||
<div v-if="isFolderExpanded" class="folder-list">
|
||||
<FolderManage
|
||||
:allCount="allNotesCount"
|
||||
:starredCount="starredNotesCount"
|
||||
@@ -60,6 +66,7 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useAppStore } from '../stores/useAppStore'
|
||||
import NoteItem from '../components/NoteItem.vue'
|
||||
import Header from '../components/Header.vue'
|
||||
@@ -68,22 +75,25 @@ import SearchBar from '../components/SearchBar.vue'
|
||||
import { formatNoteListDate } from '../utils/dateUtils'
|
||||
|
||||
const store = useAppStore()
|
||||
const router = useRouter()
|
||||
|
||||
// 加载初始数据
|
||||
// 页面挂载时加载初始数据
|
||||
onMounted(() => {
|
||||
// 检查URL参数是否包含mock数据加载指令
|
||||
// 检查URL参数是否包含mock数据加载指令,用于开发和演示
|
||||
const urlParams = new URLSearchParams(window.location.search)
|
||||
if (urlParams.get('mock') === 'true') {
|
||||
// 加载预设的模拟数据
|
||||
store.loadMockData()
|
||||
} else {
|
||||
// 从localStorage加载用户数据
|
||||
store.loadData()
|
||||
}
|
||||
})
|
||||
|
||||
const searchQuery = ref('')
|
||||
const sortBy = ref('date') // 'date', 'title', 'starred'
|
||||
const isFolderExpanded = ref(false)
|
||||
const currentFolder = ref('all') // 默认文件夹是"全部便签"
|
||||
const sortBy = ref('date') // 排序方式:'date'(按日期)、'title'(按标题)、'starred'(按星标)
|
||||
const isFolderExpanded = ref(false) // 文件夹列表是否展开
|
||||
const currentFolder = ref('all') // 当前选中的文件夹,默认是"全部便签"
|
||||
const showAlert = ref(false)
|
||||
const noteToDelete = ref(null)
|
||||
|
||||
@@ -104,9 +114,7 @@ const filteredNotes = computed(() => {
|
||||
|
||||
return store.notes.filter(note => {
|
||||
// 先检查搜索条件
|
||||
const matchesSearch = !lowerCaseQuery ||
|
||||
note.title.toLowerCase().includes(lowerCaseQuery) ||
|
||||
note.content.toLowerCase().includes(lowerCaseQuery)
|
||||
const matchesSearch = !lowerCaseQuery || note.title.toLowerCase().includes(lowerCaseQuery) || note.content.toLowerCase().includes(lowerCaseQuery)
|
||||
|
||||
if (!matchesSearch) return false
|
||||
|
||||
@@ -128,7 +136,9 @@ const filteredNotes = computed(() => {
|
||||
})
|
||||
})
|
||||
|
||||
// Filter and sort notes
|
||||
// 过滤并排序便签列表
|
||||
// 首先按置顶状态排序,置顶的便签排在前面
|
||||
// 然后根据sortBy的值进行二次排序
|
||||
const filteredAndSortedNotes = computed(() => {
|
||||
return [...filteredNotes.value].sort((a, b) => {
|
||||
// 置顶的便签排在前面
|
||||
@@ -138,9 +148,10 @@ const filteredAndSortedNotes = computed(() => {
|
||||
// 根据排序方式排序
|
||||
switch (sortBy.value) {
|
||||
case 'title':
|
||||
// 按标题字母顺序排序
|
||||
return a.title.localeCompare(b.title)
|
||||
case 'starred':
|
||||
// 加星的便签排在前面
|
||||
// 按星标状态排序,加星的便签排在前面
|
||||
return (b.isStarred ? 1 : 0) - (a.isStarred ? 1 : 0)
|
||||
case 'date':
|
||||
default:
|
||||
@@ -170,17 +181,17 @@ const allNotesCount = computed(() => {
|
||||
})
|
||||
|
||||
const handleNotePress = noteId => {
|
||||
// 导航到编辑页面的逻辑将在路由中处理
|
||||
window.location.hash = `#/editor/${noteId}`
|
||||
// 使用vue-router导航到编辑页面
|
||||
router.push(`/editor/${noteId}`)
|
||||
}
|
||||
|
||||
const handleAddNote = () => {
|
||||
// 导航到编辑页面的逻辑将在路由中处理
|
||||
window.location.hash = '#/editor'
|
||||
// 使用vue-router导航到新建便签页面
|
||||
router.push('/editor')
|
||||
}
|
||||
|
||||
// 处理Header组件的操作按钮点击事件
|
||||
const handleHeaderAction = (actionType) => {
|
||||
const handleHeaderAction = actionType => {
|
||||
if (actionType === 'create') {
|
||||
handleAddNote()
|
||||
}
|
||||
@@ -236,13 +247,14 @@ const confirmDeleteNote = async () => {
|
||||
showAlert.value = false
|
||||
}
|
||||
|
||||
// 处理排序方式切换
|
||||
// 循环切换排序选项:按日期 -> 按标题 -> 按星标 -> 按日期...
|
||||
const handleSort = () => {
|
||||
// In a full implementation, this would cycle through sort options
|
||||
const sortOptions = ['date', 'title', 'starred']
|
||||
const currentIndex = sortOptions.indexOf(sortBy.value)
|
||||
const nextIndex = (currentIndex + 1) % sortOptions.length
|
||||
sortBy.value = sortOptions[nextIndex]
|
||||
console.log('Sort by:', sortOptions[nextIndex])
|
||||
console.log('当前排序方式:', sortOptions[nextIndex])
|
||||
}
|
||||
|
||||
const handleAllNotesClick = () => {
|
||||
@@ -261,13 +273,13 @@ const handleTrashNotesClick = () => {
|
||||
}
|
||||
|
||||
const handleFolderPress = () => {
|
||||
// 导航到文件夹页面的逻辑将在路由中处理
|
||||
window.location.hash = '#/folders'
|
||||
// 使用vue-router导航到文件夹页面
|
||||
router.push('/folders')
|
||||
}
|
||||
|
||||
const handleSettingsPress = () => {
|
||||
// 导航到设置页面的逻辑将在路由中处理
|
||||
window.location.hash = '#/settings'
|
||||
// 使用vue-router导航到设置页面
|
||||
router.push('/settings')
|
||||
}
|
||||
|
||||
const handleFolderToggle = () => {
|
||||
@@ -303,7 +315,8 @@ const handleSearchBlur = () => {
|
||||
// 可以在这里添加失去焦点时的特殊处理
|
||||
}
|
||||
|
||||
// 防抖搜索函数,避免频繁触发搜索
|
||||
// 防抖函数,用于避免频繁触发搜索
|
||||
// 通过延迟执行函数,只在最后一次调用后执行
|
||||
const debounceSearch = (func, delay) => {
|
||||
let timeoutId
|
||||
return function (...args) {
|
||||
@@ -312,8 +325,8 @@ const debounceSearch = (func, delay) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 防抖搜索处理
|
||||
const debouncedHandleSearch = debounceSearch((query) => {
|
||||
// 防抖搜索处理函数,延迟300ms执行搜索
|
||||
const debouncedHandleSearch = debounceSearch(query => {
|
||||
handleSearch(query)
|
||||
}, 300)
|
||||
|
||||
|
||||
@@ -81,61 +81,75 @@
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useAppStore } from '../stores/useAppStore';
|
||||
import Header from '../components/Header.vue';
|
||||
|
||||
const store = useAppStore();
|
||||
const router = useRouter();
|
||||
|
||||
// 加载初始数据
|
||||
// 页面挂载时加载初始数据
|
||||
// 从localStorage加载用户设置和便签数据
|
||||
onMounted(() => {
|
||||
store.loadData();
|
||||
});
|
||||
|
||||
// 切换云同步设置
|
||||
// 调用store中的方法更新云同步状态
|
||||
const toggleCloudSync = () => {
|
||||
store.toggleCloudSync();
|
||||
};
|
||||
|
||||
// 切换深色模式设置
|
||||
// 调用store中的方法更新深色模式状态
|
||||
const toggleDarkMode = () => {
|
||||
store.toggleDarkMode();
|
||||
};
|
||||
|
||||
// 处理登录云同步按钮点击事件
|
||||
// 在完整实现中,这里会打开登录界面
|
||||
const handleLogin = () => {
|
||||
// In a full implementation, this would open a login screen
|
||||
console.log('Login to cloud');
|
||||
};
|
||||
|
||||
// 处理隐私政策按钮点击事件
|
||||
// 在完整实现中,这里会显示隐私政策内容
|
||||
const handlePrivacyPolicy = () => {
|
||||
// In a full implementation, this would show the privacy policy
|
||||
console.log('Privacy policy');
|
||||
};
|
||||
|
||||
// 处理服务条款按钮点击事件
|
||||
// 在完整实现中,这里会显示服务条款内容
|
||||
const handleTermsOfService = () => {
|
||||
// In a full implementation, this would show the terms of service
|
||||
console.log('Terms of service');
|
||||
};
|
||||
|
||||
// 处理备份便签按钮点击事件
|
||||
// 在完整实现中,这里会执行便签备份操作
|
||||
const handleBackup = () => {
|
||||
// In a full implementation, this would backup notes
|
||||
console.log('Backup notes');
|
||||
};
|
||||
|
||||
// 处理恢复便签按钮点击事件
|
||||
// 在完整实现中,这里会执行便签恢复操作
|
||||
const handleRestore = () => {
|
||||
// In a full implementation, this would restore notes
|
||||
console.log('Restore notes');
|
||||
};
|
||||
|
||||
// 处理导出便签按钮点击事件
|
||||
// 在完整实现中,这里会执行便签导出操作
|
||||
const handleExport = () => {
|
||||
// In a full implementation, this would export notes
|
||||
console.log('Export notes');
|
||||
};
|
||||
|
||||
// 处理导入便签按钮点击事件
|
||||
// 在完整实现中,这里会执行便签导入操作
|
||||
const handleImport = () => {
|
||||
// In a full implementation, this would import notes
|
||||
console.log('Import notes');
|
||||
};
|
||||
|
||||
const handleBackPress = () => {
|
||||
window.history.back();
|
||||
router.back();
|
||||
};
|
||||
|
||||
const settings = computed(() => store.settings);
|
||||
|
||||
@@ -2,27 +2,59 @@ import { defineStore } from 'pinia'
|
||||
import * as storage from '../utils/storage'
|
||||
import { getCurrentDateTime, getPastDate } from '../utils/dateUtils'
|
||||
|
||||
/**
|
||||
* 应用状态管理Store
|
||||
* 使用Pinia进行状态管理,包含便签、文件夹和设置数据
|
||||
*/
|
||||
export const useAppStore = defineStore('app', {
|
||||
/**
|
||||
* 状态定义
|
||||
* 包含应用的核心数据:便签列表、文件夹列表和设置
|
||||
*/
|
||||
state: () => ({
|
||||
notes: [],
|
||||
folders: [],
|
||||
settings: { cloudSync: false, darkMode: false },
|
||||
notes: [], // 便签列表
|
||||
folders: [], // 文件夹列表
|
||||
settings: { cloudSync: false, darkMode: false }, // 应用设置
|
||||
}),
|
||||
|
||||
/**
|
||||
* 计算属性
|
||||
* 基于状态派生的计算值
|
||||
*/
|
||||
getters: {
|
||||
/**
|
||||
* 计算加星便签数量
|
||||
* @param {Object} state - 当前状态对象
|
||||
* @returns {number} 加星便签的数量
|
||||
*/
|
||||
starredNotesCount: state => {
|
||||
return state.notes.filter(note => note.isStarred).length
|
||||
},
|
||||
|
||||
/**
|
||||
* 计算所有便签数量
|
||||
* @param {Object} state - 当前状态对象
|
||||
* @returns {number} 所有便签的数量
|
||||
*/
|
||||
allNotesCount: state => {
|
||||
return state.notes.length
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* 状态变更操作
|
||||
* 包含所有修改状态的方法
|
||||
*/
|
||||
actions: {
|
||||
// 初始化数据
|
||||
/**
|
||||
* 初始化数据
|
||||
* 从localStorage加载便签、文件夹和设置数据
|
||||
* 如果没有数据则加载预设的mock数据
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async loadData() {
|
||||
try {
|
||||
// 从localStorage加载数据
|
||||
const loadedNotes = await storage.getNotes()
|
||||
const loadedFolders = await storage.getFolders()
|
||||
const loadedSettings = await storage.getSettings()
|
||||
@@ -31,6 +63,7 @@ export const useAppStore = defineStore('app', {
|
||||
if (loadedNotes.length === 0 && loadedFolders.length === 0) {
|
||||
this.loadMockData()
|
||||
} else {
|
||||
// 否则使用加载的数据
|
||||
this.notes = loadedNotes
|
||||
this.folders = loadedFolders
|
||||
this.settings = loadedSettings
|
||||
@@ -40,7 +73,11 @@ export const useAppStore = defineStore('app', {
|
||||
}
|
||||
},
|
||||
|
||||
// 加载mock数据
|
||||
/**
|
||||
* 加载预设的mock数据
|
||||
* 用于开发和演示目的,提供示例便签、文件夹和设置
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async loadMockData() {
|
||||
// Mock notes - 使用固定的日期值以避免每次运行时变化
|
||||
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 fixedFiveDaysAgo = '2025-10-07T10:00:00.000Z';
|
||||
|
||||
// 预设的便签示例数据
|
||||
const mockNotes = [
|
||||
{
|
||||
id: '1',
|
||||
@@ -58,10 +96,10 @@ export const useAppStore = defineStore('app', {
|
||||
createdAt: fixedCurrentDate,
|
||||
updatedAt: fixedCurrentDate,
|
||||
folderId: null,
|
||||
isStarred: true,
|
||||
isTop: true,
|
||||
hasImage: false,
|
||||
isDeleted: false,
|
||||
isStarred: true, // 加星便签
|
||||
isTop: true, // 置顶便签
|
||||
hasImage: false, // 不包含图片
|
||||
isDeleted: false, // 未删除
|
||||
deletedAt: null,
|
||||
},
|
||||
{
|
||||
@@ -71,10 +109,10 @@ export const useAppStore = defineStore('app', {
|
||||
createdAt: fixedYesterday,
|
||||
updatedAt: fixedYesterday,
|
||||
folderId: null,
|
||||
isStarred: true,
|
||||
isTop: false,
|
||||
hasImage: true,
|
||||
isDeleted: false,
|
||||
isStarred: true, // 加星便签
|
||||
isTop: false, // 非置顶
|
||||
hasImage: true, // 包含图片
|
||||
isDeleted: false, // 未删除
|
||||
deletedAt: null,
|
||||
},
|
||||
{
|
||||
@@ -84,10 +122,10 @@ export const useAppStore = defineStore('app', {
|
||||
createdAt: fixedTwoDaysAgo,
|
||||
updatedAt: fixedTwoDaysAgo,
|
||||
folderId: null,
|
||||
isStarred: false,
|
||||
isTop: false,
|
||||
hasImage: false,
|
||||
isDeleted: false,
|
||||
isStarred: false, // 非加星
|
||||
isTop: false, // 非置顶
|
||||
hasImage: false, // 不包含图片
|
||||
isDeleted: false, // 未删除
|
||||
deletedAt: null,
|
||||
},
|
||||
{
|
||||
@@ -97,10 +135,10 @@ export const useAppStore = defineStore('app', {
|
||||
createdAt: fixedThreeDaysAgo,
|
||||
updatedAt: fixedThreeDaysAgo,
|
||||
folderId: null,
|
||||
isStarred: false,
|
||||
isTop: false,
|
||||
hasImage: false,
|
||||
isDeleted: false,
|
||||
isStarred: false, // 非加星
|
||||
isTop: false, // 非置顶
|
||||
hasImage: false, // 不包含图片
|
||||
isDeleted: false, // 未删除
|
||||
deletedAt: null,
|
||||
},
|
||||
{
|
||||
@@ -110,10 +148,10 @@ export const useAppStore = defineStore('app', {
|
||||
createdAt: fixedFourDaysAgo,
|
||||
updatedAt: fixedFourDaysAgo,
|
||||
folderId: null,
|
||||
isStarred: false,
|
||||
isTop: false,
|
||||
hasImage: false,
|
||||
isDeleted: false,
|
||||
isStarred: false, // 非加星
|
||||
isTop: false, // 非置顶
|
||||
hasImage: false, // 不包含图片
|
||||
isDeleted: false, // 未删除
|
||||
deletedAt: null,
|
||||
},
|
||||
{
|
||||
@@ -123,15 +161,16 @@ export const useAppStore = defineStore('app', {
|
||||
createdAt: fixedFiveDaysAgo,
|
||||
updatedAt: fixedFiveDaysAgo,
|
||||
folderId: null,
|
||||
isStarred: false,
|
||||
isTop: false,
|
||||
hasImage: false,
|
||||
isDeleted: true,
|
||||
isStarred: false, // 非加星
|
||||
isTop: false, // 非置顶
|
||||
hasImage: false, // 不包含图片
|
||||
isDeleted: true, // 已删除
|
||||
deletedAt: fixedYesterday,
|
||||
},
|
||||
]
|
||||
|
||||
// Mock folders - 使用固定的日期值
|
||||
// 预设的文件夹示例数据
|
||||
const mockFolders = [
|
||||
{
|
||||
id: 'folder1',
|
||||
@@ -151,11 +190,13 @@ export const useAppStore = defineStore('app', {
|
||||
]
|
||||
|
||||
// Mock settings
|
||||
// 预设的设置示例数据
|
||||
const mockSettings = {
|
||||
cloudSync: false,
|
||||
darkMode: false,
|
||||
cloudSync: false, // 云同步关闭
|
||||
darkMode: false, // 深色模式关闭
|
||||
}
|
||||
|
||||
// 更新store状态
|
||||
this.notes = mockNotes
|
||||
this.folders = mockFolders
|
||||
this.settings = mockSettings
|
||||
@@ -166,7 +207,10 @@ export const useAppStore = defineStore('app', {
|
||||
await storage.saveSettings(mockSettings)
|
||||
},
|
||||
|
||||
// 保存notes到localStorage
|
||||
/**
|
||||
* 保存便签数据到localStorage
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async saveNotes() {
|
||||
try {
|
||||
await storage.saveNotes(this.notes)
|
||||
@@ -175,7 +219,10 @@ export const useAppStore = defineStore('app', {
|
||||
}
|
||||
},
|
||||
|
||||
// 保存folders到localStorage
|
||||
/**
|
||||
* 保存文件夹数据到localStorage
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async saveFolders() {
|
||||
try {
|
||||
await storage.saveFolders(this.folders)
|
||||
@@ -184,7 +231,10 @@ export const useAppStore = defineStore('app', {
|
||||
}
|
||||
},
|
||||
|
||||
// 保存settings到localStorage
|
||||
/**
|
||||
* 保存设置数据到localStorage
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async saveSettings() {
|
||||
try {
|
||||
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) {
|
||||
try {
|
||||
const newNote = await storage.addNote(note)
|
||||
this.notes.push(newNote)
|
||||
|
||||
return newNote
|
||||
} catch (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) {
|
||||
try {
|
||||
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) {
|
||||
try {
|
||||
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) {
|
||||
try {
|
||||
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) {
|
||||
try {
|
||||
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) {
|
||||
try {
|
||||
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) {
|
||||
try {
|
||||
const updatedSettings = { ...this.settings, ...newSettings }
|
||||
@@ -289,12 +385,20 @@ export const useAppStore = defineStore('app', {
|
||||
}
|
||||
},
|
||||
|
||||
// 切换云同步设置
|
||||
/**
|
||||
* 切换云同步设置
|
||||
* 开启或关闭云同步功能
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async toggleCloudSync() {
|
||||
await this.updateSettings({ cloudSync: !this.settings.cloudSync })
|
||||
},
|
||||
|
||||
// 切换深色模式设置
|
||||
/**
|
||||
* 切换深色模式设置
|
||||
* 开启或关闭深色模式
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async toggleDarkMode() {
|
||||
await this.updateSettings({ darkMode: !this.settings.darkMode })
|
||||
},
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
import { getCurrentDateTime, getTimestamp } from './dateUtils'
|
||||
|
||||
// Storage keys
|
||||
const NOTES_KEY = 'notes';
|
||||
const FOLDERS_KEY = 'folders';
|
||||
const SETTINGS_KEY = 'settings';
|
||||
// 本地存储键名常量
|
||||
// 用于在localStorage中标识不同类型的数据
|
||||
const NOTES_KEY = 'notes'; // 便签数据键名
|
||||
const FOLDERS_KEY = 'folders'; // 文件夹数据键名
|
||||
const SETTINGS_KEY = 'settings'; // 设置数据键名
|
||||
|
||||
// Notes functions
|
||||
// 便签操作函数
|
||||
// 提供便签的增删改查功能
|
||||
|
||||
/**
|
||||
* 获取所有便签数据
|
||||
* 从localStorage中读取便签数据并解析为JavaScript对象
|
||||
* @returns {Promise<Array>} 便签数组,如果读取失败则返回空数组
|
||||
*/
|
||||
export const getNotes = async () => {
|
||||
try {
|
||||
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) => {
|
||||
try {
|
||||
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) => {
|
||||
// 创建新的便签对象,添加必要的属性
|
||||
const newNote = {
|
||||
...note,
|
||||
id: getTimestamp().toString(),
|
||||
createdAt: getCurrentDateTime(),
|
||||
updatedAt: getCurrentDateTime(),
|
||||
isStarred: note.isStarred || false,
|
||||
isTop: note.isTop || false,
|
||||
hasImage: note.hasImage || false,
|
||||
isDeleted: note.isDeleted || false,
|
||||
deletedAt: note.deletedAt || null
|
||||
id: getTimestamp().toString(), // 使用时间戳生成唯一ID
|
||||
createdAt: getCurrentDateTime(), // 创建时间
|
||||
updatedAt: getCurrentDateTime(), // 更新时间
|
||||
isStarred: note.isStarred || false, // 是否加星
|
||||
isTop: note.isTop || false, // 是否置顶
|
||||
hasImage: note.hasImage || false, // 是否包含图片
|
||||
isDeleted: note.isDeleted || false, // 是否已删除
|
||||
deletedAt: note.deletedAt || null // 删除时间
|
||||
};
|
||||
|
||||
// 获取现有便签列表,添加新便签并保存
|
||||
const notes = await getNotes();
|
||||
notes.push(newNote);
|
||||
await saveNotes(notes);
|
||||
@@ -44,35 +66,62 @@ export const addNote = async (note) => {
|
||||
return newNote;
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新便签
|
||||
* 根据ID查找并更新便签信息
|
||||
* @param {string} id - 便签ID
|
||||
* @param {Object} updates - 要更新的属性对象
|
||||
* @returns {Promise<Object|null>} 更新后的便签对象,如果未找到则返回null
|
||||
*/
|
||||
export const updateNote = async (id, updates) => {
|
||||
// 获取所有便签并查找要更新的便签
|
||||
const notes = await getNotes();
|
||||
const index = notes.findIndex(note => note.id === id);
|
||||
|
||||
// 如果未找到指定ID的便签,返回null
|
||||
if (index === -1) return null;
|
||||
|
||||
// 创建更新后的便签对象
|
||||
const updatedNote = {
|
||||
...notes[index],
|
||||
...updates,
|
||||
updatedAt: getCurrentDateTime(),
|
||||
updatedAt: getCurrentDateTime(), // 更新最后修改时间
|
||||
};
|
||||
|
||||
// 更新便签列表并保存
|
||||
notes[index] = updatedNote;
|
||||
await saveNotes(notes);
|
||||
|
||||
return updatedNote;
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除便签
|
||||
* 根据ID从便签列表中移除便签
|
||||
* @param {string} id - 要删除的便签ID
|
||||
* @returns {Promise<boolean>} 删除成功返回true,未找到便签返回false
|
||||
*/
|
||||
export const deleteNote = async (id) => {
|
||||
// 获取所有便签并过滤掉要删除的便签
|
||||
const notes = await getNotes();
|
||||
const filteredNotes = notes.filter(note => note.id !== id);
|
||||
|
||||
// 如果便签数量没有变化,说明未找到要删除的便签
|
||||
if (notes.length === filteredNotes.length) return false;
|
||||
|
||||
// 保存更新后的便签列表
|
||||
await saveNotes(filteredNotes);
|
||||
return true;
|
||||
};
|
||||
|
||||
// Folders functions
|
||||
// 文件夹操作函数
|
||||
// 提供文件夹的增删改查功能
|
||||
|
||||
/**
|
||||
* 获取所有文件夹数据
|
||||
* 从localStorage中读取文件夹数据并解析为JavaScript对象
|
||||
* @returns {Promise<Array>} 文件夹数组,如果读取失败则返回空数组
|
||||
*/
|
||||
export const getFolders = async () => {
|
||||
try {
|
||||
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) => {
|
||||
try {
|
||||
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) => {
|
||||
// 创建新的文件夹对象,添加必要的属性
|
||||
const newFolder = {
|
||||
...folder,
|
||||
id: getTimestamp().toString(),
|
||||
createdAt: getCurrentDateTime(),
|
||||
id: getTimestamp().toString(), // 使用时间戳生成唯一ID
|
||||
createdAt: getCurrentDateTime(), // 创建时间
|
||||
};
|
||||
|
||||
// 获取现有文件夹列表,添加新文件夹并保存
|
||||
const folders = await getFolders();
|
||||
folders.push(newFolder);
|
||||
await saveFolders(folders);
|
||||
@@ -105,17 +168,32 @@ export const addFolder = async (folder) => {
|
||||
return newFolder;
|
||||
};
|
||||
|
||||
// Settings functions
|
||||
// 设置操作函数
|
||||
// 提供应用设置的读取和保存功能
|
||||
|
||||
/**
|
||||
* 获取应用设置
|
||||
* 从localStorage中读取设置数据并解析为JavaScript对象
|
||||
* @returns {Promise<Object>} 设置对象,如果读取失败则返回默认设置
|
||||
*/
|
||||
export const getSettings = async () => {
|
||||
try {
|
||||
const settingsJson = localStorage.getItem(SETTINGS_KEY);
|
||||
// 如果没有保存的设置,返回默认设置
|
||||
return settingsJson ? JSON.parse(settingsJson) : { cloudSync: false, darkMode: false };
|
||||
} catch (error) {
|
||||
console.error('Error getting settings:', error);
|
||||
// 出错时返回默认设置
|
||||
return { cloudSync: false, darkMode: false };
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 保存应用设置
|
||||
* 将设置对象转换为JSON字符串并保存到localStorage
|
||||
* @param {Object} settings - 设置对象
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export const saveSettings = async (settings) => {
|
||||
try {
|
||||
localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings));
|
||||
|
||||
Reference in New Issue
Block a user