开始完善便签新建、编辑逻辑

This commit is contained in:
2025-10-12 18:32:25 +08:00
parent 3957a7d3b2
commit 1bb9b4a79e
13 changed files with 696 additions and 1039 deletions

View File

@@ -1,29 +1,17 @@
<template>
<ion-page>
<Header
title="文件夹"
:onBack="() => window.history.back()"
/>
<div style="padding: 10px; background-color: var(--background)">
<div style="display: flex; align-items: center; background-color: var(--search-bar-background); border-radius: 8px; padding: 0 10px">
<ion-icon :icon="search" style="font-size: 20px; color: var(--text-tertiary)"></ion-icon>
<ion-input
placeholder="搜索文件夹..."
:value="searchQuery"
@ionChange="e => setSearchQuery(e.detail.value)"
style="--padding-start: 10px; --padding-end: 10px; flex: 1; font-size: 16px; color: var(--text-primary)"
></ion-input>
<ion-button
v-if="searchQuery.length > 0"
fill="clear"
@click="() => setSearchQuery('')"
>
<ion-icon :icon="closeCircle" style="font-size: 20px; color: var(--text-tertiary)"></ion-icon>
<Header title="文件夹" :onBack="() => window.history.back()" />
<div class="folder-page-container">
<div class="search-container">
<ion-icon :icon="search" class="search-icon"></ion-icon>
<ion-input placeholder="搜索文件夹..." :value="searchQuery" @ionChange="e => setSearchQuery(e.detail.value)" class="search-input"></ion-input>
<ion-button v-if="searchQuery.length > 0" fill="clear" @click="() => setSearchQuery('')">
<ion-icon :icon="closeCircle" class="clear-icon"></ion-icon>
</ion-button>
</div>
</div>
<ion-content>
<ion-list style="background-color: var(--background); padding: 0 16px; --ion-item-background: var(--background)">
<ion-list class="folder-list">
<FolderManage
:allCount="allNotesCount"
:starredCount="starredNotesCount"
@@ -33,47 +21,46 @@
:onAllClick="() => handleFolderPress('all')"
:onStarredClick="() => handleFolderPress('starred')"
:onTrashClick="() => handleFolderPress('trash')"
:onArchiveClick="() => handleFolderPress('archive')"
/>
:onArchiveClick="() => handleFolderPress('archive')" />
</ion-list>
</ion-content>
</ion-page>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
import { useAppStore } from '../stores/useAppStore';
import { search, closeCircle } from 'ionicons/icons';
import FolderManage from '../components/FolderManage.vue';
import Header from '../components/Header.vue';
import { ref, computed, onMounted } from 'vue'
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 store = useAppStore()
// 加载初始数据
onMounted(() => {
store.loadData();
});
store.loadData()
})
const searchQuery = ref('');
const selectedFolder = ref('all');
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;
const noteCount = store.notes.filter(note => note.folderId === folder.id).length
return {
...folder,
noteCount,
};
});
});
}
})
})
// Add default folders at the beginning
const allNotesCount = computed(() => store.notes.length);
const starredNotesCount = computed(() => store.notes.filter(note => note.isStarred).length);
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 trashNotesCount = 0
const archiveCount = 0
const foldersWithAllNotes = computed(() => {
return [
@@ -81,39 +68,76 @@ const foldersWithAllNotes = computed(() => {
{ id: 'starred', name: '加星便签', noteCount: starredNotesCount.value, createdAt: new Date() },
{ id: 'trash', name: '回收站', noteCount: trashNotesCount, createdAt: new Date() },
...foldersWithCount.value,
];
});
]
})
const handleFolderPress = (folderId) => {
const handleFolderPress = folderId => {
// 更新选中的文件夹状态
selectedFolder.value = folderId;
selectedFolder.value = folderId
// 在实际应用中这里会将选中的文件夹传递回NoteListScreen
// 通过导航参数传递选中的文件夹ID
window.location.hash = `#/notes?folder=${folderId}`;
};
window.location.hash = `#/notes?folder=${folderId}`
}
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 = () => {
// 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 = () => {
window.history.back();
};
window.history.back()
}
// Filter folders based on search query
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()))
})
const setSearchQuery = (value) => {
searchQuery.value = value;
};
</script>
const setSearchQuery = value => {
searchQuery.value = value
}
</script>
<style scoped>
.folder-page-container {
padding: 10px;
background-color: var(--background);
}
.search-container {
display: flex;
align-items: center;
background-color: var(--search-bar-background);
border-radius: 8px;
padding: 0 10px;
}
.search-icon {
font-size: 20px;
color: var(--text-tertiary);
}
.search-input {
--padding-start: 10px;
--padding-end: 10px;
flex: 1;
font-size: 16px;
color: var(--text-primary);
}
.clear-icon {
font-size: 20px;
color: var(--text-tertiary);
}
.folder-list {
background-color: var(--background);
padding: 0 16px;
--ion-item-background: var(--background);
}
</style>

View File

@@ -4,7 +4,7 @@
<Header :onBack="handleCancel" :onAction="handleAction" actionIcon="save" />
<!-- 顶部信息栏 -->
<div class="header-info" v-if="isEditing">
<div class="header-info">
<span class="edit-time">{{ formattedTime }}</span>
<span>|</span>
<span class="word-count">{{ wordCount }}</span>
@@ -12,7 +12,11 @@
<!-- 富文本编辑器 -->
<div class="editor-container">
<RichTextEditor ref="editorRef" v-model="content" class="rich-text-editor" />
<RichTextEditor
ref="editorRef"
:modelValue="content"
@update:modelValue="handleContentChange"
class="rich-text-editor" />
</div>
<ion-alert
@@ -35,34 +39,123 @@
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { ref, computed, onMounted, nextTick, watch } from 'vue'
import { useAppStore } from '../stores/useAppStore'
import Header from '../components/Header.vue'
import RichTextEditor from '../components/RichTextEditor.vue'
const props = defineProps({
noteId: {
id: {
type: String,
default: null,
},
})
// 为了保持向后兼容性我们也支持noteId属性
const noteId = computed(() => props.id || props.noteId)
const store = useAppStore()
const editorRef = ref(null)
// 设置便签内容的函数
const setNoteContent = async (noteId) => {
// 确保store数据已加载
if (store.notes.length === 0) {
await store.loadData()
console.log('Store loaded, notes count:', store.notes.length)
}
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的值
content.value = note.content || ''
// 如果editorRef可用直接设置内容
if (editorRef.value) {
editorRef.value.setContent(note.content || '')
}
} else {
console.log('Note not available')
}
}
// 加载初始数据
onMounted(async () => {
console.log('NoteEditorPage mounted')
await store.loadData()
console.log('Store loaded, notes count:', store.notes.length)
// 如果是编辑现有便签,在组件挂载后设置内容
if (noteId.value) {
await setNoteContent(noteId.value)
}
})
// 监听noteId变化确保在编辑器准备好后设置内容
watch(noteId, async (newNoteId) => {
console.log('Note ID changed:', newNoteId)
if (newNoteId) {
await setNoteContent(newNoteId)
}
}, { immediate: true })
// 监听store变化确保在store加载后设置内容
watch(() => store.notes, async (newNotes) => {
if (noteId.value && newNotes.length > 0) {
await setNoteContent(noteId.value)
}
}, { immediate: true })
// Check if we're editing an existing note
const isEditing = !!props.noteId
const existingNote = isEditing ? store.notes.find(n => n.id === props.noteId) : null
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 || '')
// 当组件挂载时,确保编辑器初始化为空内容(针对新建便签)
onMounted(() => {
if (!isEditing && editorRef.value) {
console.log('Initializing editor for new note')
editorRef.value.setContent('')
}
})
// 监听store变化确保在store加载后设置内容
watch(() => store.notes, async (newNotes) => {
if (noteId.value && newNotes.length > 0) {
await setNoteContent(noteId.value)
}
}, { immediate: true })
const showAlert = ref(false)
// 防抖函数
const debounce = (func, delay) => {
let timeoutId
return function (...args) {
clearTimeout(timeoutId)
timeoutId = setTimeout(() => func.apply(this, args), delay)
}
}
// 防抖处理内容变化
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(() => {
const now = new Date()
@@ -93,15 +186,18 @@ const wordCount = computed(() => {
// 处理保存
const handleSave = async () => {
try {
// 获取编辑器中的实际内容
const editorContent = editorRef.value ? editorRef.value.getContent() : content.value
if (isEditing && existingNote) {
// Update existing note
await store.updateNote(props.noteId, {
content: content.value,
await store.updateNote(noteId.value, {
content: editorContent,
})
} else {
// Create new note
await store.addNote({
content: content.value,
content: editorContent,
isStarred: false,
})
}
@@ -129,9 +225,12 @@ const handleCancel = () => {
// 处理创建(用于新建便签)
const handleCreate = async () => {
try {
// 获取编辑器中的实际内容
const editorContent = editorRef.value ? editorRef.value.getContent() : content.value
// Create new note
await store.addNote({
content: content.value,
content: editorContent,
isStarred: false,
})
@@ -185,7 +284,7 @@ const setShowAlert = value => {
display: flex;
justify-content: flex-start;
gap: 10px;
padding: 8px 16px;
padding: 1.5rem 16px 0.7rem 16px;
background-color: var(--background-card);
border-bottom: 1px solid var(--border);
font-size: 0.7rem;

View File

@@ -6,41 +6,26 @@
<!-- 悬浮文件夹列表 - 使用绝对定位实现 -->
<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">
class="folder-list">
<FolderManage
:allCount="notes.length"
:allCount="allNotesCount"
:starredCount="starredNotesCount"
:trashCount="0"
:trashCount="trashNotesCount"
:archiveCount="0"
:selectedFolder="currentFolder"
:onAllClick="
() => {
setCurrentFolder('all')
setIsFolderExpanded(false)
}
"
:onStarredClick="
() => {
setCurrentFolder('starred')
setIsFolderExpanded(false)
}
"
:onTrashClick="
() => {
setCurrentFolder('trash')
setIsFolderExpanded(false)
}
" />
:onAllClick="handleAllNotesClick"
:onStarredClick="handleStarredNotesClick"
:onTrashClick="handleTrashNotesClick" />
</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 v-if="isFolderExpanded" @click="() => setIsFolderExpanded(false)" class="folder-overlay"></div>
<div style="padding: 0.8rem 0.5rem">
<div class="search-container">
<SearchBar v-model="searchQuery" @search="handleSearch" @clear="handleClearSearch" @focus="handleSearchFocus" @blur="handleSearchBlur" />
</div>
<div style="flex: 1">
<div v-for="note in filteredAndSortedNotes" :key="note.id" style="margin: 0.4rem 0">
<div class="notes-container">
<div v-for="note in filteredAndSortedNotes" :key="note.id" class="note-item">
<NoteItem
:title="note.title"
:content="note.content"
@@ -76,7 +61,6 @@
<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 FolderManage from '../components/FolderManage.vue'
@@ -102,50 +86,67 @@ 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 && !note.isDeleted).length
})
// 计算置顶便签数量
const topNotesCount = computed(() => {
return filteredAndSortedNotes.value.filter(note => note.isTop).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.toLowerCase().includes(lowerCaseQuery) ||
note.content.toLowerCase().includes(lowerCaseQuery)
if (!matchesSearch) return false
// 再检查文件夹条件
switch (currentFolder.value) {
case 'all':
return true
// 全部便签中不显示已删除的便签
return !note.isDeleted
case 'starred':
return note.isStarred
// 加星便签中只显示未删除的加星便签
return note.isStarred && !note.isDeleted
case 'trash':
// 假设我们有一个isDeleted属性来标识已删除的便签
return note.isDeleted || false
// 回收站中只显示已删除的便签
return note.isDeleted
default:
return note.folderId === currentFolder.value
// 自定义文件夹中不显示已删除的便签
return note.folderId === currentFolder.value && !note.isDeleted
}
})
})
// 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()))
.sort((a, b) => {
// 置顶的便签排在前面
if (a.isTop && !b.isTop) return -1
if (!a.isTop && b.isTop) return 1
return [...filteredNotes.value].sort((a, b) => {
// 置顶的便签排在前面
if (a.isTop && !b.isTop) return -1
if (!a.isTop && b.isTop) return 1
if (sortBy.value === 'title') {
// 根据排序方式排序
switch (sortBy.value) {
case 'title':
return a.title.localeCompare(b.title)
} else if (sortBy.value === 'starred') {
case 'starred':
// 加星的便签排在前面
return (b.isStarred ? 1 : 0) - (a.isStarred ? 1 : 0)
} else {
case 'date':
default:
// 按更新时间倒序排列(最新的在前)
return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
}
})
}
})
})
// 计算头部标题
@@ -158,10 +159,15 @@ const headerTitle = computed(() => {
case 'trash':
return '回收站'
default:
return '文件夹'
return '全部便签'
}
})
// 计算全部便签数量(未删除的)
const allNotesCount = computed(() => {
return store.notes.filter(note => !note.isDeleted).length
})
const handleNotePress = noteId => {
// 导航到编辑页面的逻辑将在路由中处理
window.location.hash = `#/editor/${noteId}`
@@ -187,21 +193,44 @@ const handleDeleteNote = noteId => {
const handleStarToggle = async noteId => {
const note = store.notes.find(n => n.id === noteId)
if (note) {
await store.updateNote(noteId, { isStarred: !note.isStarred })
try {
await store.updateNote(noteId, { isStarred: !note.isStarred })
console.log(`Note ${noteId} starred status updated`)
} catch (error) {
console.error('Failed to update note star status:', error)
}
}
}
const handleTopToggle = async noteId => {
const note = store.notes.find(n => n.id === noteId)
if (note) {
await store.updateNote(noteId, { isTop: !note.isTop })
try {
await store.updateNote(noteId, { isTop: !note.isTop })
console.log(`Note ${noteId} top status updated`)
} catch (error) {
console.error('Failed to update note top status:', error)
}
}
}
const confirmDeleteNote = () => {
const confirmDeleteNote = async () => {
if (noteToDelete.value) {
store.deleteNote(noteToDelete.value)
noteToDelete.value = null
try {
// 检查当前是否在回收站中
if (currentFolder.value === 'trash') {
// 在回收站中删除便签,彻底删除
await store.permanentlyDeleteNote(noteToDelete.value)
console.log(`Note ${noteToDelete.value} permanently deleted`)
} else {
// 不在回收站中,将便签移至回收站
await store.moveToTrash(noteToDelete.value)
console.log(`Note ${noteToDelete.value} moved to trash`)
}
noteToDelete.value = null
} catch (error) {
console.error('Failed to delete note:', error)
}
}
showAlert.value = false
}
@@ -215,6 +244,21 @@ const handleSort = () => {
console.log('Sort by:', sortOptions[nextIndex])
}
const handleAllNotesClick = () => {
setCurrentFolder('all')
setIsFolderExpanded(false)
}
const handleStarredNotesClick = () => {
setCurrentFolder('starred')
setIsFolderExpanded(false)
}
const handleTrashNotesClick = () => {
setCurrentFolder('trash')
setIsFolderExpanded(false)
}
const handleFolderPress = () => {
// 导航到文件夹页面的逻辑将在路由中处理
window.location.hash = '#/folders'
@@ -228,29 +272,77 @@ const handleSettingsPress = () => {
const handleFolderToggle = () => {
// 在实际应用中,这里会触发文件夹列表的展开/收起
isFolderExpanded.value = !isFolderExpanded.value
console.log('Folder expanded:', !isFolderExpanded.value)
}
const handleSearch = query => {
// 搜索功能已在computed属性filteredAndSortedNotes中实现
console.log('Search for:', query)
// 可以在这里添加搜索统计或其它功能
if (query && query.length > 0) {
console.log(`Found ${filteredAndSortedNotes.value.length} matching notes`)
}
}
const handleClearSearch = () => {
// 清除搜索已在v-model中处理
console.log('Search cleared')
// 清除搜索后可以重置一些状态
setSearchQuery('')
}
const handleSearchFocus = () => {
console.log('Search bar focused')
// 可以在这里添加获得焦点时的特殊处理
}
const handleSearchBlur = () => {
console.log('Search bar blurred')
// 可以在这里添加失去焦点时的特殊处理
}
// 防抖搜索函数,避免频繁触发搜索
const debounceSearch = (func, delay) => {
let timeoutId
return function (...args) {
clearTimeout(timeoutId)
timeoutId = setTimeout(() => func.apply(this, args), delay)
}
}
// 防抖搜索处理
const debouncedHandleSearch = debounceSearch((query) => {
handleSearch(query)
}, 300)
// 改进的日期格式化函数
const formatDate = dateString => {
return new Date(dateString).toLocaleDateString()
const date = new Date(dateString)
const now = new Date()
// 计算日期差
const diffTime = now - date
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24))
// 今天的便签显示时间
if (diffDays === 0) {
return `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`
}
// 昨天的便签显示"昨天"
if (diffDays === 1) {
return '昨天'
}
// 一周内的便签显示星期几
if (diffDays < 7) {
const weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
return weekdays[date.getDay()]
}
// 超过一周的便签显示月日
return `${date.getMonth() + 1}/${date.getDate()}`
}
const setCurrentFolder = folder => {
@@ -278,4 +370,39 @@ const notes = computed(() => store.notes)
background: url(/assets/icons/drawable-xxhdpi/note_background.png);
background-size: cover;
}
</style>
.folder-list {
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;
}
.folder-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: transparent;
z-index: 99;
}
.search-container {
padding: 0.8rem 0.5rem;
}
.notes-container {
flex: 1;
}
.note-item {
margin: 0.4rem 0;
}
</style>

View File

@@ -5,31 +5,31 @@
:onBack="handleBackPress"
/>
<ion-content style="background-color: var(--background)">
<div style="margin-bottom: 12px; background-color: var(--background-card)">
<div style="background-color: var(--background-secondary); font-size: 13px; font-weight: 600; color: var(--text-tertiary); padding: 10px 16px">
<ion-content class="settings-content">
<div class="settings-section">
<div class="section-header">
账户
</div>
<div button @click="handleLogin" style="display: flex; justify-content: space-between; align-items: center; background-color: var(--background-card); padding: 14px 16px; border-bottom: 1px solid var(--border); cursor: pointer">
<div style="font-size: 16px; color: var(--text-primary)">登录云同步</div>
<div style="font-size: 15px; color: var(--text-tertiary)">未登录</div>
<div button @click="handleLogin" class="settings-item settings-item-clickable">
<div class="item-text-primary">登录云同步</div>
<div class="item-text-tertiary">未登录</div>
</div>
</div>
<div style="margin-bottom: 12px; background-color: var(--background-card)">
<div style="background-color: var(--background-secondary); font-size: 13px; font-weight: 600; color: var(--text-tertiary); padding: 10px 16px">
<div class="settings-section">
<div class="section-header">
偏好设置
</div>
<div style="display: flex; justify-content: space-between; align-items: center; background-color: var(--background-card); padding: 14px 16px; border-bottom: 1px solid var(--border)">
<div style="font-size: 16px; color: var(--text-primary)">云同步</div>
<div class="settings-item settings-item-border">
<div class="item-text-primary">云同步</div>
<ion-toggle
slot="end"
:checked="settings.cloudSync"
@ion-change="toggleCloudSync"
></ion-toggle>
</div>
<div style="display: flex; justify-content: space-between; align-items: center; background-color: var(--background-card); padding: 14px 16px">
<div style="font-size: 16px; color: var(--text-primary)">深色模式</div>
<div class="settings-item">
<div class="item-text-primary">深色模式</div>
<ion-toggle
slot="end"
:checked="settings.darkMode"
@@ -38,41 +38,41 @@
</div>
</div>
<div style="margin-bottom: 12px; background-color: var(--background-card)">
<div style="background-color: var(--background-secondary); font-size: 13px; font-weight: 600; color: var(--text-tertiary); padding: 10px 16px">
<div class="settings-section">
<div class="section-header">
数据管理
</div>
<div button @click="handleBackup" style="display: flex; align-items: center; background-color: var(--background-card); padding: 14px 16px; border-bottom: 1px solid var(--border); cursor: pointer">
<img :src="'/assets/icons/drawable-xxhdpi/btn_save_pic.png'" style="width: 20px; height: 20px; color: var(--text-primary); margin-right: 12px" />
<div style="font-size: 16px; color: var(--text-primary)">备份便签</div>
<div button @click="handleBackup" class="settings-item settings-item-clickable settings-item-border">
<img :src="'/assets/icons/drawable-xxhdpi/btn_save_pic.png'" class="item-icon" />
<div class="item-text-primary">备份便签</div>
</div>
<div button @click="handleRestore" style="display: flex; align-items: center; background-color: var(--background-card); padding: 14px 16px; border-bottom: 1px solid var(--border); cursor: pointer">
<img :src="'/assets/icons/drawable-xxhdpi/btn_restore.png'" style="width: 20px; height: 20px; color: var(--text-primary); margin-right: 12px" />
<div style="font-size: 16px; color: var(--text-primary)">恢复便签</div>
<div button @click="handleRestore" class="settings-item settings-item-clickable settings-item-border">
<img :src="'/assets/icons/drawable-xxhdpi/btn_restore.png'" class="item-icon" />
<div class="item-text-primary">恢复便签</div>
</div>
<div button @click="handleExport" style="display: flex; align-items: center; background-color: var(--background-card); padding: 14px 16px; border-bottom: 1px solid var(--border); cursor: pointer">
<img :src="'/assets/icons/drawable-xxhdpi/btn_share.png'" style="width: 20px; height: 20px; color: var(--text-primary); margin-right: 12px" />
<div style="font-size: 16px; color: var(--text-primary)">导出便签</div>
<div button @click="handleExport" class="settings-item settings-item-clickable settings-item-border">
<img :src="'/assets/icons/drawable-xxhdpi/btn_share.png'" class="item-icon" />
<div class="item-text-primary">导出便签</div>
</div>
<div button @click="handleImport" style="display: flex; align-items: center; background-color: var(--background-card); padding: 14px 16px; cursor: pointer">
<img :src="'/assets/icons/drawable-xxhdpi/btn_load_error.png'" style="width: 20px; height: 20px; color: var(--text-primary); margin-right: 12px" />
<div style="font-size: 16px; color: var(--text-primary)">导入便签</div>
<div button @click="handleImport" class="settings-item settings-item-clickable">
<img :src="'/assets/icons/drawable-xxhdpi/btn_load_error.png'" class="item-icon" />
<div class="item-text-primary">导入便签</div>
</div>
</div>
<div style="margin-bottom: 12px; background-color: var(--background-card)">
<div style="background-color: var(--background-secondary); font-size: 13px; font-weight: 600; color: var(--text-tertiary); padding: 10px 16px">
<div class="settings-section">
<div class="section-header">
关于
</div>
<div style="display: flex; justify-content: space-between; align-items: center; background-color: var(--background-card); padding: 14px 16px; border-bottom: 1px solid var(--border)">
<div style="font-size: 16px; color: var(--text-primary)">版本</div>
<div style="font-size: 15px; color: var(--text-tertiary)">1.0.0</div>
<div class="settings-item settings-item-border">
<div class="item-text-primary">版本</div>
<div class="item-text-tertiary">1.0.0</div>
</div>
<div button @click="handlePrivacyPolicy" style="display: flex; justify-content: space-between; align-items: center; background-color: var(--background-card); padding: 14px 16px; border-bottom: 1px solid var(--border); cursor: pointer">
<div style="font-size: 16px; color: var(--text-primary)">隐私政策</div>
<div button @click="handlePrivacyPolicy" class="settings-item settings-item-clickable settings-item-border">
<div class="item-text-primary">隐私政策</div>
</div>
<div button @click="handleTermsOfService" style="display: flex; justify-content: space-between; align-items: center; background-color: var(--background-card); padding: 14px 16px; cursor: pointer">
<div style="font-size: 16px; color: var(--text-primary)">服务条款</div>
<div button @click="handleTermsOfService" class="settings-item settings-item-clickable">
<div class="item-text-primary">服务条款</div>
</div>
</div>
</ion-content>
@@ -139,4 +139,65 @@ const handleBackPress = () => {
};
const settings = computed(() => store.settings);
</script>
</script>
<style scoped>
.settings-content {
background-color: var(--background);
}
.settings-section {
margin-bottom: 12px;
background-color: var(--background-card);
}
.section-header {
background-color: var(--background-secondary);
font-size: 13px;
font-weight: 600;
color: var(--text-tertiary);
padding: 10px 16px;
}
.settings-item {
display: flex;
justify-content: space-between;
align-items: center;
background-color: var(--background-card);
padding: 14px 16px;
}
.settings-item-border {
border-bottom: 1px solid var(--border);
}
.settings-item-clickable {
cursor: pointer;
}
.item-text-primary {
font-size: 16px;
color: var(--text-primary);
}
.item-text-tertiary {
font-size: 15px;
color: var(--text-tertiary);
}
.item-icon {
width: 20px;
height: 20px;
color: var(--text-primary);
margin-right: 12px;
}
.settings-item-clickable .item-text-primary {
flex: 1;
}
.settings-item-clickable {
display: flex;
align-items: center;
}
</style>