还原了搜索栏布局、样式;

还原了文件夹管理布局、样式;
还原了头部布局、样式;
This commit is contained in:
User
2025-10-10 15:01:36 +08:00
parent e40288e8ef
commit f68cd4e0fd
10 changed files with 469 additions and 420 deletions

View File

@@ -24,14 +24,16 @@
</div>
<ion-content>
<ion-list style="background-color: var(--background); padding: 0 16px; --ion-item-background: var(--background)">
<FolderItem
v-for="folder in filteredFolders"
:key="folder.id"
:id="folder.id"
:name="folder.name"
:noteCount="folder.noteCount"
:onPress="() => handleFolderPress(folder.id)"
:isSelected="folder.id === selectedFolder"
<FolderManage
:allCount="allNotesCount"
:starredCount="starredNotesCount"
:trashCount="trashNotesCount"
:archiveCount="archiveCount"
:selectedFolder="selectedFolder"
:onAllClick="() => handleFolderPress('all')"
:onStarredClick="() => handleFolderPress('starred')"
:onTrashClick="() => handleFolderPress('trash')"
:onArchiveClick="() => handleFolderPress('archive')"
/>
</ion-list>
</ion-content>
@@ -42,7 +44,7 @@
import { ref, computed, onMounted } from 'vue';
import { useAppStore } from '../stores/useAppStore';
import { search, closeCircle } from 'ionicons/icons';
import FolderItem from '../components/FolderItem.vue';
import FolderManage from '../components/FolderManage.vue';
import Header from '../components/Header.vue';
const store = useAppStore();
@@ -71,6 +73,7 @@ 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 [

View File

@@ -1,109 +1,60 @@
<template>
<ion-page>
<Header
:title="headerTitle"
:onAction="handleAddNote"
actionIcon="create"
leftType="settings"
:onLeftAction="handleSettingsPress"
:onFolderToggle="handleFolderToggle"
:isFolderExpanded="isFolderExpanded"
:onTitlePress="handleFolderToggle"
/>
<!-- 悬浮文件夹列表 - 使用绝对定位实现 -->
<div
v-if="isFolderExpanded"
style="position: absolute; top: 50px; left: 10%; right: 10%; z-index: 1000; background-color: var(--background-card); border-radius: 8px; box-shadow: 0 2px 4px var(--shadow); border: 1px solid var(--border); overflow: hidden"
>
<FolderItem
id="all"
name="全部便签"
:noteCount="notes.length"
:isSelected="currentFolder === 'all'"
:onPress="() => {
setCurrentFolder('all');
setIsFolderExpanded(false);
}"
/>
<FolderItem
id="starred"
name="加星便签"
:noteCount="starredNotesCount"
:isSelected="currentFolder === 'starred'"
:onPress="() => {
setCurrentFolder('starred');
setIsFolderExpanded(false);
}"
/>
<FolderItem
id="trash"
name="回收站"
:noteCount="0"
:isSelected="currentFolder === 'trash'"
:onPress="() => {
setCurrentFolder('trash');
setIsFolderExpanded(false);
}"
/>
</div>
<div style="flex-direction: row; align-items: center; padding: 8px 16px; background-color: var(--background-card); border-bottom: 1px solid var(--border)">
<div style="flex: 1; flex-direction: row; align-items: center; background-color: var(--background-card); height: 36px; padding: 0 8px; padding-vertical: 0">
<div style="flex: 1; flex-direction: row; align-items: center; background-color: #f0f0f0; border-radius: 4; height: 36px; padding: 0 8px; padding-vertical: 0">
<img :src="'/assets/icons/drawable-xxhdpi/search_bar_left_icon.png'" style="width: 20px; height: 20px; tint-color: var(--text-tertiary)" />
<input
placeholder="搜索便签..."
:value="searchQuery"
@input="e => setSearchQuery(e.target.value)"
@keydown.enter="handleSearch"
style="flex: 1; font-size: 16px; color: var(--text-primary); margin-left: 8px; margin-right: 8px; padding: 0; border: none; background: transparent; outline: none"
/>
<ion-button
v-if="searchQuery.length > 0"
fill="clear"
@click="() => setSearchQuery('')"
style="--padding-start: 0; --padding-end: 0; width: 20px; height: 20px; margin: 0; padding: 0"
>
<img :src="'/assets/icons/drawable-xxhdpi/search_bar_clear_btn.png'" style="width: 20px; height: 20px; tint-color: var(--text-tertiary)" />
</ion-button>
<ion-app>
<div class="container">
<Header :title="headerTitle" :onAction="handleAddNote" actionIcon="create" leftType="settings" :onLeftAction="handleSettingsPress" :onFolderToggle="handleFolderToggle" :isFolderExpanded="isFolderExpanded" :onTitlePress="handleFolderToggle" />
<!-- 悬浮文件夹列表 - 使用绝对定位实现 -->
<div
v-if="isFolderExpanded"
style="position: absolute; top: 50px; left: 10%; right: 10%; z-index: 1000; background-color: var(--background-card); border-radius: 8px; box-shadow: 0 2px 4px var(--shadow); border: 1px solid #f0ece7; overflow: hidden">
<FolderManage
:allCount="notes.length"
:starredCount="starredNotesCount"
:trashCount="0"
:archiveCount="0"
:selectedFolder="currentFolder"
:onAllClick="
() => {
setCurrentFolder('all')
setIsFolderExpanded(false)
}
"
:onStarredClick="
() => {
setCurrentFolder('starred')
setIsFolderExpanded(false)
}
"
:onTrashClick="
() => {
setCurrentFolder('trash')
setIsFolderExpanded(false)
}
" />
</div>
<div style="padding: 0.8rem 0.5rem">
<SearchBar v-model="searchQuery" @search="handleSearch" @clear="handleClearSearch" @focus="handleSearchFocus" @blur="handleSearchBlur" />
</div>
<div v-if="filteredAndSortedNotes.length === 0" style="flex: 1; justify-content: center; align-items: center; padding: 16px">
<ion-text style="font-size: 18px; font-weight: 600; color: var(--text-tertiary); margin-bottom: 8px"> 未找到便签 </ion-text>
<ion-text style="font-size: 14px; color: var(--text-tertiary); text-align: center; line-height: 20px">
{{ searchQuery ? '尝试其他搜索词' : '点击 + 按钮创建您的第一条便签' }}
</ion-text>
</div>
<div v-else style="flex: 1">
<ion-text style="font-size: 13px; color: var(--text-tertiary); padding-horizontal: 16px; padding-vertical: 8px; display: block"> {{ filteredAndSortedNotes.length }} 条便签 </ion-text>
<div style="border-radius: 6px; overflow: hidden; box-shadow: 0 1px 2px var(--shadow); background-color: var(--background-card); margin: 0 16px">
<div v-for="note in filteredAndSortedNotes" :key="note.id">
<NoteItem :title="note.title" :content="note.content" :date="formatDate(note.updatedAt)" :isStarred="note.isStarred" :onPress="() => handleNotePress(note.id)" :onDelete="() => handleDeleteNote(note.id)" />
</div>
</div>
</div>
<!-- 点击外部区域收起文件夹列表的覆盖层 -->
<div v-if="isFolderExpanded" @click="() => setIsFolderExpanded(false)" style="position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: transparent; z-index: 99"></div>
</div>
<div v-if="filteredAndSortedNotes.length === 0" style="flex: 1; justify-content: center; align-items: center; padding: 16px; background-color: var(--background)">
<ion-text style="font-size: 18px; font-weight: 600; color: var(--text-tertiary); margin-bottom: 8px">
未找到便签
</ion-text>
<ion-text style="font-size: 14px; color: var(--text-tertiary); text-align: center; line-height: 20px">
{{ searchQuery ? '尝试其他搜索词' : '点击 + 按钮创建您的第一条便签' }}
</ion-text>
</div>
<div v-else style="flex: 1; background-color: var(--background)">
<ion-text style="font-size: 13px; color: var(--text-tertiary); padding-horizontal: 16px; padding-vertical: 8px; display: block">
{{ filteredAndSortedNotes.length }} 条便签
</ion-text>
<div style="border-radius: 6px; overflow: hidden; box-shadow: 0 1px 2px var(--shadow); background-color: var(--background-card); margin: 0 16px">
<div v-for="note in filteredAndSortedNotes" :key="note.id">
<NoteItem
:title="note.title"
:content="note.content"
:date="formatDate(note.updatedAt)"
:isStarred="note.isStarred"
:onPress="() => handleNotePress(note.id)"
:onDelete="() => handleDeleteNote(note.id)"
/>
</div>
</div>
</div>
<!-- 点击外部区域收起文件夹列表的覆盖层 -->
<div
v-if="isFolderExpanded"
@click="() => setIsFolderExpanded(false)"
style="position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: transparent; z-index: 99"
></div>
<ion-alert
:is-open="showAlert"
@didDismiss="() => setShowAlert(false)"
@@ -112,165 +63,183 @@
:buttons="[
{
text: '取消',
role: 'cancel'
role: 'cancel',
},
{
text: '删除',
handler: confirmDeleteNote
}
]"
></ion-alert>
</ion-page>
handler: confirmDeleteNote,
},
]"></ion-alert>
</ion-app>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
import { useAppStore } from '../stores/useAppStore';
import { create, settings } from 'ionicons/icons';
import NoteItem from '../components/NoteItem.vue';
import Header from '../components/Header.vue';
import FolderItem from '../components/FolderItem.vue';
import { ref, computed, onMounted } from 'vue'
import { useAppStore } from '../stores/useAppStore'
import { create, settings } from 'ionicons/icons'
import NoteItem from '../components/NoteItem.vue'
import Header from '../components/Header.vue'
import FolderManage from '../components/FolderManage.vue'
import SearchBar from '../components/SearchBar.vue'
const store = useAppStore();
const store = useAppStore()
// 加载初始数据
onMounted(() => {
store.loadData();
});
store.loadData()
})
const searchQuery = ref('');
const sortBy = ref('date'); // 'date', 'title', 'starred'
const isFolderExpanded = ref(false);
const currentFolder = ref('all'); // 默认文件夹是"全部便签"
const showAlert = ref(false);
const noteToDelete = ref(null);
const searchQuery = ref('')
const sortBy = ref('date') // 'date', 'title', 'starred'
const isFolderExpanded = ref(false)
const currentFolder = ref('all') // 默认文件夹是"全部便签"
const showAlert = ref(false)
const noteToDelete = ref(null)
// 计算加星便签数量
const starredNotesCount = computed(() => {
return store.notes.filter(note => note.isStarred).length;
});
return store.notes.filter(note => note.isStarred).length
})
// 根据当前文件夹过滤便签
const filteredNotes = computed(() => {
return store.notes.filter(note => {
switch (currentFolder.value) {
case 'all':
return true;
return true
case 'starred':
return note.isStarred;
return note.isStarred
case 'trash':
// 假设我们有一个isDeleted属性来标识已删除的便签
return note.isDeleted || false;
return note.isDeleted || false
default:
return note.folderId === currentFolder.value;
return note.folderId === currentFolder.value
}
});
});
})
})
// Filter and sort notes
const filteredAndSortedNotes = computed(() => {
return filteredNotes.value
.filter(note =>
note.title.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
note.content.toLowerCase().includes(searchQuery.value.toLowerCase())
)
.filter(note => note.title.toLowerCase().includes(searchQuery.value.toLowerCase()) || note.content.toLowerCase().includes(searchQuery.value.toLowerCase()))
.sort((a, b) => {
if (sortBy.value === 'title') {
return a.title.localeCompare(b.title);
return a.title.localeCompare(b.title)
} else if (sortBy.value === 'starred') {
return (b.isStarred ? 1 : 0) - (a.isStarred ? 1 : 0);
return (b.isStarred ? 1 : 0) - (a.isStarred ? 1 : 0)
} else {
return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime();
return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
}
});
});
})
})
// 计算头部标题
const headerTitle = computed(() => {
switch (currentFolder.value) {
case 'all':
return '全部便签';
return '全部便签'
case 'starred':
return '加星便签';
return '加星便签'
case 'trash':
return '回收站';
return '回收站'
default:
return '文件夹';
return '文件夹'
}
});
})
const handleNotePress = (noteId) => {
const handleNotePress = noteId => {
// 导航到详情页面的逻辑将在路由中处理
window.location.hash = `#/notes/${noteId}`;
};
window.location.hash = `#/notes/${noteId}`
}
const handleAddNote = () => {
// 导航到编辑页面的逻辑将在路由中处理
window.location.hash = '#/editor';
};
window.location.hash = '#/editor'
}
const handleDeleteNote = (noteId) => {
noteToDelete.value = noteId;
showAlert.value = true;
};
const handleDeleteNote = noteId => {
noteToDelete.value = noteId
showAlert.value = true
}
const confirmDeleteNote = () => {
if (noteToDelete.value) {
store.deleteNote(noteToDelete.value);
noteToDelete.value = null;
store.deleteNote(noteToDelete.value)
noteToDelete.value = null
}
showAlert.value = false;
};
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]);
};
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])
}
const handleFolderPress = () => {
// 导航到文件夹页面的逻辑将在路由中处理
window.location.hash = '#/folders';
};
window.location.hash = '#/folders'
}
const handleSettingsPress = () => {
// 导航到设置页面的逻辑将在路由中处理
window.location.hash = '#/settings';
};
window.location.hash = '#/settings'
}
const handleFolderToggle = () => {
// 在实际应用中,这里会触发文件夹列表的展开/收起
isFolderExpanded.value = !isFolderExpanded.value;
console.log('Folder expanded:', !isFolderExpanded.value);
};
isFolderExpanded.value = !isFolderExpanded.value
console.log('Folder expanded:', !isFolderExpanded.value)
}
const handleSearch = () => {
// In a full implementation, this would filter notes based on searchQuery
console.log('Search for:', searchQuery.value);
};
const handleSearch = query => {
// 搜索功能已在computed属性filteredAndSortedNotes中实现
console.log('Search for:', query)
}
const formatDate = (dateString) => {
return new Date(dateString).toLocaleDateString();
};
const handleClearSearch = () => {
// 清除搜索已在v-model中处理
console.log('Search cleared')
}
const setCurrentFolder = (folder) => {
currentFolder.value = folder;
};
const handleSearchFocus = () => {
console.log('Search bar focused')
}
const setIsFolderExpanded = (expanded) => {
isFolderExpanded.value = expanded;
};
const handleSearchBlur = () => {
console.log('Search bar blurred')
}
const setSearchQuery = (query) => {
searchQuery.value = query;
};
const formatDate = dateString => {
return new Date(dateString).toLocaleDateString()
}
const setShowAlert = (show) => {
showAlert.value = show;
};
const setCurrentFolder = folder => {
currentFolder.value = folder
}
const notes = computed(() => store.notes);
</script>
const setIsFolderExpanded = expanded => {
isFolderExpanded.value = expanded
}
const setSearchQuery = query => {
searchQuery.value = query
}
const setShowAlert = show => {
showAlert.value = show
}
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;
}
</style>