初始化提交

This commit is contained in:
2025-10-10 08:13:38 +08:00
commit 0d4c7353f4
1361 changed files with 13945 additions and 0 deletions

132
src/pages/FolderPage.vue Normal file
View File

@@ -0,0 +1,132 @@
<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>
</ion-button>
</div>
</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"
/>
</ion-list>
</ion-content>
</ion-page>
</template>
<script>
import { ref, computed } from 'vue';
import { useAppData } from '../utils/AppDataContext';
import { search, closeCircle } from 'ionicons/icons';
import FolderItem from '../components/FolderItem.vue';
import Header from '../components/Header.vue';
export default {
name: 'FolderPage',
components: {
FolderItem,
Header
},
setup() {
const { state } = useAppData();
const searchQuery = ref('');
const selectedFolder = ref('all');
// Calculate note count for each folder
const foldersWithCount = computed(() => {
return state.folders.map(folder => {
const noteCount = state.notes.filter(note => note.folderId === folder.id).length;
return {
...folder,
noteCount,
};
});
});
// Add default folders at the beginning
const allNotesCount = computed(() => state.notes.length);
const starredNotesCount = computed(() => state.notes.filter(note => note.isStarred).length);
// Assuming we have a way to track deleted notes in the future
const trashNotesCount = 0;
const foldersWithAllNotes = computed(() => {
return [
{ id: 'all', name: '全部便签', noteCount: allNotesCount.value, createdAt: new Date() },
{ id: 'starred', name: '加星便签', noteCount: starredNotesCount.value, createdAt: new Date() },
{ id: 'trash', name: '回收站', noteCount: trashNotesCount, createdAt: new Date() },
...foldersWithCount.value,
];
});
const handleFolderPress = (folderId) => {
// 更新选中的文件夹状态
selectedFolder.value = folderId;
// 在实际应用中这里会将选中的文件夹传递回NoteListScreen
// 通过导航参数传递选中的文件夹ID
window.location.hash = `#/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();
};
// Filter folders based on search query
const filteredFolders = computed(() => {
return foldersWithAllNotes.value.filter(folder =>
folder.name.toLowerCase().includes(searchQuery.value.toLowerCase())
);
});
const setSearchQuery = (value) => {
searchQuery.value = value;
};
return {
searchQuery,
selectedFolder,
filteredFolders,
handleFolderPress,
handleAddFolder,
handleSearch,
handleBackPress,
setSearchQuery,
search,
closeCircle
};
}
};
</script>

View File

@@ -0,0 +1,139 @@
<template>
<ion-page>
<Header
:title="note ? note.title : '未找到便签'"
:onBack="() => window.history.back()"
:onAction="note ? handleEdit : null"
actionIcon="settings"
/>
<ion-content v-if="!note">
<div style="display: flex; justify-content: center; align-items: center; height: 100%; background-color: var(--background)">
<ion-text>未找到该便签</ion-text>
</div>
</ion-content>
<ion-content v-else>
<div style="padding: 16px; background-color: var(--background-card)">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; padding-bottom: 16px; border-bottom: 1px solid var(--border)">
<ion-text style="color: var(--note-date); font-size: 14px">
最后更新: {{ formatDate(note.updatedAt) }}
</ion-text>
<ion-button fill="clear" @click="handleStar">
<ion-icon
:icon="note.isStarred ? star : starOutline"
:style="{ fontSize: '24px', color: note.isStarred ? 'var(--note-star)' : 'var(--text-tertiary)' }"
></ion-icon>
</ion-button>
</div>
<div style="white-space: pre-wrap; line-height: 1.6; font-size: 16px; color: var(--note-content)">
{{ note.content }}
</div>
</div>
<div style="position: absolute; bottom: 0; left: 0; right: 0; display: flex; justify-content: space-around; padding: 10px; background-color: var(--background); border-top: 1px solid var(--border)">
<div style="display: flex; flex-direction: column; align-items: center">
<ion-button fill="clear" @click="handleShare">
<ion-icon :icon="share" style="font-size: 24px; color: var(--text-primary)"></ion-icon>
</ion-button>
<ion-text style="font-size: 12px; color: var(--text-primary)">分享</ion-text>
</div>
<div style="display: flex; flex-direction: column; align-items: center">
<ion-button fill="clear" @click="handleMoveToFolder">
<ion-icon :icon="folder" style="font-size: 24px; color: var(--text-primary)"></ion-icon>
</ion-button>
<ion-text style="font-size: 12px; color: var(--text-primary)">文件夹</ion-text>
</div>
<div style="display: flex; flex-direction: column; align-items: center">
<ion-button fill="clear" @click="handleReminder">
<ion-icon :icon="alarm" style="font-size: 24px; color: var(--text-primary)"></ion-icon>
</ion-button>
<ion-text style="font-size: 12px; color: var(--text-primary)">提醒</ion-text>
</div>
<div style="display: flex; flex-direction: column; align-items: center">
<ion-button fill="clear" @click="handleDelete">
<ion-icon :icon="trash" style="font-size: 24px; color: var(--text-primary)"></ion-icon>
</ion-button>
<ion-text style="font-size: 12px; color: var(--text-primary)">删除</ion-text>
</div>
</div>
</ion-content>
</ion-page>
</template>
<script>
import { computed } from 'vue';
import { useAppData } from '../utils/AppDataContext';
import { star, starOutline, share, folder, alarm, trash } from 'ionicons/icons';
import Header from '../components/Header.vue';
export default {
name: 'NoteDetailPage',
components: {
Header
},
props: {
noteId: {
type: String,
required: true
}
},
setup(props) {
const { state, updateNote } = useAppData();
// Find the note by ID
const note = computed(() => state.notes.find(n => n.id === props.noteId));
const handleEdit = () => {
// 导航到编辑页面的逻辑将在路由中处理
window.location.hash = `#/editor/${props.noteId}`;
};
const handleShare = () => {
// In a full implementation, this would share the note
console.log('Share note');
};
const handleMoveToFolder = () => {
// In a full implementation, this would move the note to a folder
console.log('Move to folder');
};
const handleDelete = () => {
// In a full implementation, this would delete the note
console.log('Delete note');
};
const handleStar = async () => {
// Toggle star status
if (note.value) {
await updateNote(props.noteId, { isStarred: !note.value.isStarred });
}
};
const handleReminder = () => {
// In a full implementation, this would set a reminder
console.log('Set reminder');
};
const formatDate = (dateString) => {
return new Date(dateString).toLocaleDateString();
};
return {
note: note.value,
handleEdit,
handleShare,
handleMoveToFolder,
handleDelete,
handleStar,
handleReminder,
formatDate,
star,
starOutline,
share,
folder,
alarm,
trash
};
}
};
</script>

View File

@@ -0,0 +1,197 @@
<template>
<ion-page>
<Header
:title="isEditing ? '编辑便签' : '新建便签'"
:onBack="handleCancel"
:onAction="handleSave"
actionText="保存"
/>
<div style="display: flex; border-bottom: 1px solid var(--border); padding: 8px 16px; background-color: var(--background-card)">
<ion-button fill="clear" @click="handleBold" style="padding: 8px; margin-right: 8px">
<img src="/assets/icons/drawable-xxhdpi/rtf_bold_normal.9.png" style="width: 24px; height: 24px" />
</ion-button>
<ion-button fill="clear" @click="handleItalic" style="padding: 8px; margin-right: 8px">
<img src="/assets/icons/drawable-xxhdpi/rtf_gtasks_normal.9.png" style="width: 24px; height: 24px" />
</ion-button>
<ion-button fill="clear" @click="handleUnderline" style="padding: 8px; margin-right: 8px">
<img src="/assets/icons/drawable-xxhdpi/rtf_list_normal.9.png" style="width: 24px; height: 24px" />
</ion-button>
<ion-button fill="clear" @click="handleList" style="padding: 8px; margin-right: 8px">
<img src="/assets/icons/drawable-xxhdpi/rtf_header_normal.9.png" style="width: 24px; height: 24px" />
</ion-button>
<ion-button fill="clear" @click="handleHeader" style="padding: 8px; margin-right: 8px">
<img src="/assets/icons/drawable-xxhdpi/rtf_quot_normal.9.png" style="width: 24px; height: 24px" />
</ion-button>
<ion-button fill="clear" @click="handleQuote" style="padding: 8px">
<img src="/assets/icons/drawable-xxhdpi/rtf_center_normal.9.png" style="width: 24px; height: 24px" />
</ion-button>
</div>
<ion-content>
<div style="padding: 16px; background-color: var(--background-card)">
<textarea
placeholder="标题"
:value="title"
@input="e => setTitle(e.target.value)"
style="font-size: 22px; font-weight: 600; color: var(--note-title); margin-bottom: 16px; padding: 8px 0; border-bottom: 1px solid var(--border); width: 100%; background-color: transparent; border: none; outline: none; resize: none; font-family: inherit"
></textarea>
<textarea
placeholder="开始写作..."
:value="content"
@input="e => setContent(e.target.value)"
style="font-size: 16px; color: var(--note-content); line-height: 24px; width: 100%; height: calc(100vh - 200px); background-color: transparent; border: none; outline: none; resize: none; font-family: inherit"
></textarea>
</div>
</ion-content>
<ion-alert
:is-open="showAlert"
@didDismiss="() => setShowAlert(false)"
header="未保存的更改"
message="您有未保存的更改,确定要丢弃吗?"
:buttons="[
{
text: '取消',
role: 'cancel'
},
{
text: '丢弃',
handler: () => window.history.back()
}
]"
></ion-alert>
</ion-page>
</template>
<script>
import { ref, computed } from 'vue';
import { useAppData } from '../utils/AppDataContext';
import { save, text, list } from 'ionicons/icons';
import Header from '../components/Header.vue';
export default {
name: 'NoteEditorPage',
components: {
Header
},
props: {
noteId: {
type: String,
default: null
}
},
setup(props) {
const { state, addNote, updateNote } = useAppData();
// Check if we're editing an existing note
const isEditing = !!props.noteId;
const existingNote = isEditing ? state.notes.find(n => n.id === props.noteId) : null;
// Initialize state with existing note data or empty strings
const title = ref(existingNote?.title || '');
const content = ref(existingNote?.content || '');
const showAlert = ref(false);
const handleSave = async () => {
// Validate input
if (!title.value.trim()) {
// In a full implementation, show an alert or toast
console.log('Validation error: Please enter a note title.');
return;
}
try {
if (isEditing && existingNote) {
// Update existing note
await updateNote(props.noteId, { title: title.value, content: content.value });
} else {
// Create new note
await addNote({ title: title.value, content: content.value, isStarred: false });
}
// Navigate back to the previous screen
window.history.back();
} catch (error) {
// In a full implementation, show an alert or toast
console.log('Save error: Failed to save note. Please try again.');
}
};
const handleCancel = () => {
// Check if there are unsaved changes
const hasUnsavedChanges =
title.value !== (existingNote?.title || '') ||
content.value !== (existingNote?.content || '');
if (hasUnsavedChanges) {
showAlert.value = true;
} else {
window.history.back();
}
};
// Toolbar actions
const handleBold = () => {
// In a full implementation, this would apply bold formatting
console.log('Bold pressed');
};
const handleItalic = () => {
// In a full implementation, this would apply italic formatting
console.log('Italic pressed');
};
const handleUnderline = () => {
// In a full implementation, this would apply underline formatting
console.log('Underline pressed');
};
const handleList = () => {
// In a full implementation, this would insert a list
console.log('List pressed');
};
const handleHeader = () => {
// In a full implementation, this would apply header formatting
console.log('Header pressed');
};
const handleQuote = () => {
// In a full implementation, this would apply quote formatting
console.log('Quote pressed');
};
const setTitle = (value) => {
title.value = value;
};
const setContent = (value) => {
content.value = value;
};
const setShowAlert = (value) => {
showAlert.value = value;
};
return {
isEditing,
title,
content,
showAlert,
handleSave,
handleCancel,
handleBold,
handleItalic,
handleUnderline,
handleList,
handleHeader,
handleQuote,
setTitle,
setContent,
setShowAlert,
save,
text,
list
};
}
};
</script>

307
src/pages/NoteListPage.vue Normal file
View File

@@ -0,0 +1,307 @@
<template>
<ion-page>
<Header
:title="headerTitle"
:onAction="handleAddNote"
actionIcon="create"
leftIcon="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>
</div>
</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)"
header="删除便签"
message="确定要删除这个便签吗?"
:buttons="[
{
text: '取消',
role: 'cancel'
},
{
text: '删除',
handler: confirmDeleteNote
}
]"
></ion-alert>
</ion-page>
</template>
<script>
import { ref, computed } from 'vue';
import { useAppData } from '../utils/AppDataContext';
import { create, settings } from 'ionicons/icons';
import NoteItem from '../components/NoteItem.vue';
import Header from '../components/Header.vue';
import FolderItem from '../components/FolderItem.vue';
export default {
name: 'NoteListPage',
components: {
NoteItem,
Header,
FolderItem
},
setup() {
const { state, deleteNote } = useAppData();
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 state.notes.filter(note => note.isStarred).length;
});
// 根据当前文件夹过滤便签
const filteredNotes = computed(() => {
return state.notes.filter(note => {
switch (currentFolder.value) {
case 'all':
return true;
case 'starred':
return note.isStarred;
case 'trash':
// 假设我们有一个isDeleted属性来标识已删除的便签
return note.isDeleted || false;
default:
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())
)
.sort((a, b) => {
if (sortBy.value === 'title') {
return a.title.localeCompare(b.title);
} else if (sortBy.value === 'starred') {
return (b.isStarred ? 1 : 0) - (a.isStarred ? 1 : 0);
} else {
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 handleNotePress = (noteId) => {
// 导航到详情页面的逻辑将在路由中处理
window.location.hash = `#/notes/${noteId}`;
};
const handleAddNote = () => {
// 导航到编辑页面的逻辑将在路由中处理
window.location.hash = '#/editor';
};
const handleDeleteNote = (noteId) => {
noteToDelete.value = noteId;
showAlert.value = true;
};
const confirmDeleteNote = () => {
if (noteToDelete.value) {
deleteNote(noteToDelete.value);
noteToDelete.value = null;
}
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 handleFolderPress = () => {
// 导航到文件夹页面的逻辑将在路由中处理
window.location.hash = '#/folders';
};
const handleSettingsPress = () => {
// 导航到设置页面的逻辑将在路由中处理
window.location.hash = '#/settings';
};
const handleFolderToggle = () => {
// 在实际应用中,这里会触发文件夹列表的展开/收起
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 formatDate = (dateString) => {
return new Date(dateString).toLocaleDateString();
};
const setCurrentFolder = (folder) => {
currentFolder.value = folder;
};
const setIsFolderExpanded = (expanded) => {
isFolderExpanded.value = expanded;
};
const setSearchQuery = (query) => {
searchQuery.value = query;
};
const setShowAlert = (show) => {
showAlert.value = show;
};
return {
notes: state.notes,
searchQuery,
sortBy,
isFolderExpanded,
currentFolder,
showAlert,
noteToDelete,
starredNotesCount,
filteredAndSortedNotes,
headerTitle,
handleNotePress,
handleAddNote,
handleDeleteNote,
confirmDeleteNote,
handleSearch,
handleSort,
handleFolderPress,
handleSettingsPress,
handleFolderToggle,
formatDate,
setCurrentFolder,
setIsFolderExpanded,
setSearchQuery,
setShowAlert,
create,
settings
};
}
};
</script>

156
src/pages/SettingsPage.vue Normal file
View File

@@ -0,0 +1,156 @@
<template>
<ion-page>
<Header
title="设置"
: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">
账户
</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>
</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>
<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>
<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>
<ion-toggle
slot="end"
:checked="settings.darkMode"
@ion-change="toggleDarkMode"
></ion-toggle>
</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>
<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>
<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>
<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>
<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>
</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>
<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>
<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>
<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>
</div>
</ion-content>
</ion-page>
</template>
<script>
import { useAppData } from '../utils/AppDataContext';
import Header from '../components/Header.vue';
export default {
name: 'SettingsPage',
components: {
Header
},
setup() {
const { state, updateSettings } = useAppData();
const toggleCloudSync = () => {
updateSettings({ cloudSync: !state.settings.cloudSync });
};
const toggleDarkMode = () => {
updateSettings({ darkMode: !state.settings.darkMode });
};
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();
};
return {
settings: state.settings,
toggleCloudSync,
toggleDarkMode,
handleLogin,
handlePrivacyPolicy,
handleTermsOfService,
handleBackup,
handleRestore,
handleExport,
handleImport,
handleBackPress
};
}
};
</script>