Files
SmartisanNote.Remake/src/pages/NoteListPage.vue

384 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<ion-page>
<div class="container">
<ion-content class="content">
<Header
:title="headerTitle"
:onAction="handleHeaderAction"
actionIcon="create"
leftType="settings"
:onLeftAction="handleSettingsPress"
:onFolderToggle="handleFolderToggle"
:isFolderExpanded="isFolderExpanded"
:onTitlePress="handleFolderToggle"
slot="fixed" />
<!-- 悬浮文件夹列表 - 使用绝对定位实现 -->
<div v-if="isFolderExpanded" class="folder-list" slot="fixed">
<FolderManage
:allCount="allNotesCount"
:starredCount="starredNotesCount"
:trashCount="trashNotesCount"
:archiveCount="0"
:selectedFolder="currentFolder"
:onAllClick="handleAllNotesClick"
:onStarredClick="handleStarredNotesClick"
:onTrashClick="handleTrashNotesClick" />
</div>
<!-- 点击外部区域收起文件夹列表的覆盖层 -->
<div v-if="isFolderExpanded" @click="() => setIsFolderExpanded(false)" class="folder-overlay" slot="fixed"></div>
<div class="search-container">
<SearchBar v-model="searchQuery" @search="handleSearch" @clear="handleClearSearch" @focus="handleSearchFocus" @blur="handleSearchBlur" />
</div>
<div class="notes-container">
<div v-for="note in filteredAndSortedNotes" :key="note.id" class="note-item">
<NoteItem
:title="note.title"
:content="note.content"
:date="formatDate(note.updatedAt)"
:isStarred="note.isStarred"
:isTop="note.isTop || false"
:hasImage="note.hasImage || false"
:onPress="() => handleNotePress(note.id)"
:onStarToggle="() => handleStarToggle(note.id)"
:onTopToggle="() => handleTopToggle(note.id)"
:onDelete="() => confirmDeleteNote(note.id)" />
</div>
</div>
</ion-content>
</div>
</ion-page>
</template>
<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'
import FolderManage from '../components/FolderManage.vue'
import SearchBar from '../components/Search.vue'
import { formatNoteListDate } from '../utils/dateUtils'
import { IonContent, IonPage } from '@ionic/vue'
const store = useAppStore()
const router = useRouter()
// 页面挂载时加载初始数据
onMounted(() => {
// 检查URL参数是否包含mock数据加载指令用于开发和演示
const urlParams = new URLSearchParams(window.location.search)
if (urlParams.get('mock') === 'true') {
// 加载预设的模拟数据
store.loadMockData()
} else {
// 从Storage加载用户数据
store.loadData()
}
})
const searchQuery = ref('')
const sortBy = ref('date') // 排序方式:'date'(按日期)、'title'(按标题)、'starred'(按星标)
const isFolderExpanded = ref(false) // 文件夹列表是否展开
const currentFolder = ref('all') // 当前选中的文件夹,默认是"全部便签"
const noteToDelete = ref(null)
// 计算加星便签数量(未删除的)
const starredNotesCount = computed(() => {
return store.notes.filter(note => note.isStarred && !note.isDeleted).length
})
// 计算回收站便签数量
const trashNotesCount = computed(() => {
return store.notes.filter(note => note.isDeleted).length
})
// 根据当前文件夹过滤便签
const filteredNotes = computed(() => {
// 预处理搜索查询,提高性能
const lowerCaseQuery = searchQuery.value?.toLowerCase().trim() || ''
return store.notes.filter(note => {
// 先检查搜索条件
const matchesSearch = !lowerCaseQuery ||
(note.title && typeof note.title === 'string' && note.title.toLowerCase().includes(lowerCaseQuery)) ||
(note.content && typeof note.content === 'string' && note.content.toLowerCase().includes(lowerCaseQuery))
if (!matchesSearch) return false
// 再检查文件夹条件
switch (currentFolder.value) {
case 'all':
// 全部便签中不显示已删除的便签
return !note.isDeleted
case 'starred':
// 加星便签中只显示未删除的加星便签
return note.isStarred && !note.isDeleted
case 'trash':
// 回收站中只显示已删除的便签
return note.isDeleted
default:
// 自定义文件夹中不显示已删除的便签
return note.folderId === currentFolder.value && !note.isDeleted
}
})
})
// 过滤并排序便签列表
// 首先按置顶状态排序,置顶的便签排在前面
// 然后根据sortBy的值进行二次排序
const filteredAndSortedNotes = computed(() => {
return [...filteredNotes.value].sort((a, b) => {
// 置顶的便签排在前面
if (a.isTop && !b.isTop) return -1
if (!a.isTop && b.isTop) return 1
// 根据排序方式排序
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:
// 按更新时间倒序排列(最新的在前)
return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
}
})
})
// 计算头部标题
const headerTitle = computed(() => {
switch (currentFolder.value) {
case 'all':
return '全部便签'
case 'starred':
return '加星便签'
case 'trash':
return '回收站'
default:
return '全部便签'
}
})
// 计算全部便签数量(未删除的)
const allNotesCount = computed(() => {
return store.notes.filter(note => !note.isDeleted).length
})
const handleNotePress = noteId => {
// 使用vue-router导航到编辑页面
router.push(`/editor/${noteId}`)
}
const handleAddNote = () => {
// 使用vue-router导航到新建便签页面
router.push('/editor')
}
// 处理Header组件的操作按钮点击事件
const handleHeaderAction = actionType => {
if (actionType === 'create') {
handleAddNote()
}
}
const handleStarToggle = async noteId => {
const note = store.notes.find(n => n.id === noteId)
if (note) {
try {
await store.updateNote(noteId, { isStarred: !note.isStarred })
console.log(`便签 ${noteId} 星标状态已更新`)
} catch (error) {
console.error('更新便签星标状态失败:', error)
}
}
}
const handleTopToggle = async noteId => {
const note = store.notes.find(n => n.id === noteId)
if (note) {
try {
await store.updateNote(noteId, { isTop: !note.isTop })
console.log(`便签 ${noteId} 置顶状态已更新`)
} catch (error) {
console.error('更新便签置顶状态失败:', error)
}
}
}
const confirmDeleteNote = async noteId => {
noteToDelete.value = noteId
if (noteToDelete.value) {
try {
// 检查当前是否在回收站中
if (currentFolder.value === 'trash') {
// 在回收站中删除便签,彻底删除
await store.permanentlyDeleteNote(noteToDelete.value)
console.log(`便签 ${noteToDelete.value} 已彻底删除`)
} else {
// 不在回收站中,将便签移至回收站
await store.moveToTrash(noteToDelete.value)
console.log(`便签 ${noteToDelete.value} 已移至回收站`)
}
noteToDelete.value = null
} catch (error) {
console.error('删除便签失败:', error)
}
}
}
// 处理排序方式切换
// 循环切换排序选项:按日期 -> 按标题 -> 按星标 -> 按日期...
const handleSort = () => {
const sortOptions = ['date', 'title', 'starred']
const currentIndex = sortOptions.indexOf(sortBy.value)
const nextIndex = (currentIndex + 1) % sortOptions.length
sortBy.value = sortOptions[nextIndex]
console.log('当前排序方式:', sortOptions[nextIndex])
}
const handleAllNotesClick = () => {
setCurrentFolder('all')
setIsFolderExpanded(false)
}
const handleStarredNotesClick = () => {
setCurrentFolder('starred')
setIsFolderExpanded(false)
}
const handleTrashNotesClick = () => {
setCurrentFolder('trash')
setIsFolderExpanded(false)
}
const handleFolderPress = () => {
// 使用vue-router导航到文件夹页面
router.push('/folders')
}
const handleSettingsPress = () => {
// 使用vue-router导航到设置页面
router.push('/settings')
}
const handleFolderToggle = () => {
// 在实际应用中,这里会触发文件夹列表的展开/收起
isFolderExpanded.value = !isFolderExpanded.value
}
const handleSearch = query => {
// 搜索功能已在computed属性filteredAndSortedNotes中实现
console.log('搜索:', query)
// 可以在这里添加搜索统计或其它功能
if (query && query.length > 0) {
console.log(`找到 ${filteredAndSortedNotes.value.length} 个匹配的便签`)
}
}
const handleClearSearch = () => {
// 清除搜索已在v-model中处理
console.log('搜索已清除')
// 清除搜索后可以重置一些状态
setSearchQuery('')
}
const handleSearchFocus = () => {
console.log('搜索栏获得焦点')
// 可以在这里添加获得焦点时的特殊处理
}
const handleSearchBlur = () => {
console.log('搜索栏失去焦点')
// 可以在这里添加失去焦点时的特殊处理
}
// 防抖函数,用于避免频繁触发搜索
// 通过延迟执行函数,只在最后一次调用后执行
const debounceSearch = (func, delay) => {
let timeoutId
return function (...args) {
clearTimeout(timeoutId)
timeoutId = setTimeout(() => func.apply(this, args), delay)
}
}
// 防抖搜索处理函数延迟300ms执行搜索
const debouncedHandleSearch = debounceSearch(query => {
handleSearch(query)
}, 300)
// 改进的日期格式化函数
const formatDate = dateString => {
return formatNoteListDate(dateString)
}
const setCurrentFolder = folder => {
currentFolder.value = folder
}
const setIsFolderExpanded = expanded => {
isFolderExpanded.value = expanded
}
const setSearchQuery = query => {
searchQuery.value = query
}
const notes = computed(() => store.notes)
</script>
<style lang="less" scoped>
.container {
width: 100vw;
height: 100vh;
background: url(/assets/icons/drawable-xxhdpi/note_background.png);
background-size: cover;
}
.folder-list {
position: absolute;
top: 3.125rem;
left: 10%;
right: 10%;
z-index: 1000;
background-color: var(--background-card);
border-radius: 0.5rem;
box-shadow: 0 0.125rem 0.25rem var(--shadow);
border: 1px solid #f0ece7;
overflow: hidden;
}
.folder-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: transparent;
z-index: 99;
}
.content {
--background: transparent;
--padding-top: 4.5rem;
--padding-bottom: 2rem;
}
.search-container {
padding: 0.8rem 0.5rem;
}
.notes-container {
flex: 1;
}
.note-item {
margin: 0.6rem 0;
}
</style>