diff --git a/package-lock.json b/package-lock.json index eb0ac12..d22c4e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 0de50b6..1997e9e 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/App.vue b/src/App.vue index f6ac6bc..b087e95 100644 --- a/src/App.vue +++ b/src/App.vue @@ -6,5 +6,4 @@ \ No newline at end of file + diff --git a/src/components/Header.vue b/src/components/Header.vue index f4e711e..4a1397f 100644 --- a/src/components/Header.vue +++ b/src/components/Header.vue @@ -5,8 +5,8 @@ -
- {{ title }} +
+ {{ title }}
@@ -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 { diff --git a/src/components/NoteItem.vue b/src/components/NoteItem.vue index 57de9d6..fa64fb4 100644 --- a/src/components/NoteItem.vue +++ b/src/components/NoteItem.vue @@ -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 { diff --git a/src/components/RichTextEditor.vue b/src/components/RichTextEditor.vue index 45b335d..f7c8c12 100644 --- a/src/components/RichTextEditor.vue +++ b/src/components/RichTextEditor.vue @@ -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, '

') 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', () => { diff --git a/src/main.js b/src/main.js index 39e2da9..a08a540 100644 --- a/src/main.js +++ b/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') \ No newline at end of file diff --git a/src/pages/FolderPage.vue b/src/pages/FolderPage.vue index d881412..e78f698 100644 --- a/src/pages/FolderPage.vue +++ b/src/pages/FolderPage.vue @@ -29,12 +29,14 @@