You've already forked SmartisanNote.Remake
新增 离线web应用发布流程;
移除了便签详情页; 优化了若干逻辑; 新增 移动端、IOS兼容处理;
This commit is contained in:
@@ -13,13 +13,14 @@
|
||||
|
||||
<!-- 右侧操作按钮 -->
|
||||
<!-- 新建便签 -->
|
||||
<img v-if="actionIcon === 'create'" class="image_4" src="/assets/icons/drawable-xxhdpi/btn_create.png" @click="handleAction" />
|
||||
<!-- 新建便签 -->
|
||||
<img v-if="actionIcon === 'create'" class="image_4" src="/assets/icons/drawable-xxhdpi/btn_create.png" @click="handleAction('create')" />
|
||||
|
||||
<div v-else-if="actionIcon === 'save'" class="code-fun-flex-row code-fun-items-center right-group">
|
||||
<!-- 插入图片 -->
|
||||
<img class="image_4" src="/assets/icons/drawable-xxhdpi/btn_pic.png" @click="handleAction" />
|
||||
<img class="image_4" src="/assets/icons/drawable-xxhdpi/btn_pic.png" @click="handleAction('insertImage')" />
|
||||
<!-- 保存便签 -->
|
||||
<img class="image_4" src="/assets/icons/drawable-xxhdpi/btn_save_notes.png" @click="handleAction" />
|
||||
<img class="image_4" src="/assets/icons/drawable-xxhdpi/btn_save_notes.png" @click="handleAction('save')" />
|
||||
</div>
|
||||
<!-- 占位符 -->
|
||||
<div v-else class="image_4-placeholder"></div>
|
||||
@@ -117,10 +118,10 @@ const handleLeftAction = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const handleAction = () => {
|
||||
const handleAction = (actionType) => {
|
||||
// 处理右侧操作按钮点击事件
|
||||
if (props.onAction) {
|
||||
props.onAction()
|
||||
props.onAction(actionType)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -488,6 +488,68 @@ const insertTodoList = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 插入图片
|
||||
const insertImage = () => {
|
||||
// 创建文件输入元素
|
||||
const fileInput = document.createElement('input')
|
||||
fileInput.type = 'file'
|
||||
fileInput.accept = 'image/*'
|
||||
fileInput.style.display = 'none'
|
||||
|
||||
// 添加到文档中
|
||||
document.body.appendChild(fileInput)
|
||||
|
||||
// 监听文件选择事件
|
||||
fileInput.addEventListener('change', function (event) {
|
||||
const file = event.target.files[0]
|
||||
if (file && file.type.startsWith('image/')) {
|
||||
// 创建FileReader读取文件
|
||||
const reader = new FileReader()
|
||||
reader.onload = function (e) {
|
||||
// 获取图片数据URL
|
||||
const imageDataUrl = e.target.result
|
||||
|
||||
// 获取当前选区
|
||||
const selection = window.getSelection()
|
||||
if (selection.rangeCount > 0) {
|
||||
const range = selection.getRangeAt(0)
|
||||
|
||||
// 创建图片元素
|
||||
const img = document.createElement('img')
|
||||
img.src = imageDataUrl
|
||||
img.className = 'editor-image'
|
||||
img.style.maxWidth = '100%'
|
||||
img.style.height = 'auto'
|
||||
img.style.display = 'block'
|
||||
img.style.margin = '0 auto'
|
||||
|
||||
// 插入图片到当前光标位置
|
||||
range.insertNode(img)
|
||||
|
||||
// 添加换行
|
||||
const br = document.createElement('br')
|
||||
img.parentNode.insertBefore(br, img.nextSibling)
|
||||
|
||||
// 触发输入事件更新内容
|
||||
handleInput()
|
||||
|
||||
// 重新聚焦到编辑器
|
||||
if (editorRef.value) {
|
||||
editorRef.value.focus()
|
||||
}
|
||||
}
|
||||
}
|
||||
reader.readAsDataURL(file)
|
||||
}
|
||||
|
||||
// 清理文件输入元素
|
||||
document.body.removeChild(fileInput)
|
||||
})
|
||||
|
||||
// 触发文件选择对话框
|
||||
fileInput.click()
|
||||
}
|
||||
|
||||
// 处理输入事件
|
||||
const handleInput = () => {
|
||||
if (editorRef.value) {
|
||||
@@ -878,6 +940,13 @@ defineExpose({
|
||||
line-height: var(--editor-line-height, 1.6);
|
||||
}
|
||||
|
||||
.editor-content img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* 待办事项样式 */
|
||||
:deep(.todo-container) {
|
||||
display: flex;
|
||||
@@ -903,3 +972,71 @@ defineExpose({
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// 插入图片
|
||||
const insertImage = () => {
|
||||
// 创建文件输入元素
|
||||
const fileInput = document.createElement('input')
|
||||
fileInput.type = 'file'
|
||||
fileInput.accept = 'image/*'
|
||||
fileInput.style.display = 'none'
|
||||
|
||||
// 添加到文档中
|
||||
document.body.appendChild(fileInput)
|
||||
|
||||
// 监听文件选择事件
|
||||
fileInput.addEventListener('change', function (event) {
|
||||
const file = event.target.files[0]
|
||||
if (file && file.type.startsWith('image/')) {
|
||||
// 创建FileReader读取文件
|
||||
const reader = new FileReader()
|
||||
reader.onload = function (e) {
|
||||
// 获取图片数据URL
|
||||
const imageDataUrl = e.target.result
|
||||
|
||||
// 获取当前选区
|
||||
const selection = window.getSelection()
|
||||
if (selection.rangeCount > 0) {
|
||||
const range = selection.getRangeAt(0)
|
||||
|
||||
// 创建图片元素
|
||||
const img = document.createElement('img')
|
||||
img.src = imageDataUrl
|
||||
img.className = 'editor-image'
|
||||
img.style.maxWidth = '100%'
|
||||
img.style.height = 'auto'
|
||||
img.style.display = 'block'
|
||||
img.style.margin = '0 auto'
|
||||
|
||||
// 插入图片到当前光标位置
|
||||
range.insertNode(img)
|
||||
|
||||
// 添加换行
|
||||
const br = document.createElement('br')
|
||||
img.parentNode.insertBefore(br, img.nextSibling)
|
||||
|
||||
// 触发输入事件更新内容
|
||||
handleInput()
|
||||
|
||||
// 重新聚焦到编辑器
|
||||
if (editorRef.value) {
|
||||
editorRef.value.focus()
|
||||
}
|
||||
}
|
||||
}
|
||||
reader.readAsDataURL(file)
|
||||
}
|
||||
|
||||
// 清理文件输入元素
|
||||
document.body.removeChild(fileInput)
|
||||
})
|
||||
|
||||
// 触发文件选择对话框
|
||||
fileInput.click()
|
||||
}
|
||||
|
||||
export default {
|
||||
insertImage
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -5,7 +5,6 @@ import App from './App.vue'
|
||||
|
||||
// Pages
|
||||
import NoteListPage from './pages/NoteListPage.vue'
|
||||
import NoteDetailPage from './pages/NoteDetailPage.vue'
|
||||
import NoteEditorPage from './pages/NoteEditorPage.vue'
|
||||
import FolderPage from './pages/FolderPage.vue'
|
||||
import SettingsPage from './pages/SettingsPage.vue'
|
||||
@@ -14,7 +13,7 @@ import SettingsPage from './pages/SettingsPage.vue'
|
||||
const routes = [
|
||||
{ path: '/', redirect: '/notes' },
|
||||
{ path: '/notes', component: NoteListPage },
|
||||
{ path: '/notes/:id', component: NoteDetailPage, props: true },
|
||||
{ path: '/notes/:id', component: NoteEditorPage, props: true },
|
||||
{ path: '/editor', component: NoteEditorPage },
|
||||
{ path: '/editor/:id', component: NoteEditorPage, props: true },
|
||||
{ path: '/folders', component: FolderPage },
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
<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 setup>
|
||||
import { computed, onMounted } from 'vue';
|
||||
import { useAppStore } from '../stores/useAppStore';
|
||||
import { star, starOutline, share, folder, alarm, trash } from 'ionicons/icons';
|
||||
import Header from '../components/Header.vue';
|
||||
|
||||
const props = defineProps({
|
||||
noteId: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const store = useAppStore();
|
||||
|
||||
// 加载初始数据
|
||||
onMounted(() => {
|
||||
store.loadData();
|
||||
});
|
||||
|
||||
// Find the note by ID
|
||||
const note = computed(() => store.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 store.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();
|
||||
};
|
||||
</script>
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<ion-page>
|
||||
<div class="container">
|
||||
<Header :onBack="handleCancel" :onAction="handleSave" actionIcon="save" />
|
||||
<Header :onBack="handleCancel" :onAction="handleAction" actionIcon="save" />
|
||||
|
||||
<!-- 顶部信息栏 -->
|
||||
<div class="header-info">
|
||||
<div class="header-info" v-if="isEditing">
|
||||
<span class="edit-time">{{ formattedTime }}</span>
|
||||
<span>|</span>
|
||||
<span class="word-count">{{ wordCount }}</span>
|
||||
@@ -126,6 +126,38 @@ const handleCancel = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 处理创建(用于新建便签)
|
||||
const handleCreate = async () => {
|
||||
try {
|
||||
// Create new note
|
||||
await store.addNote({
|
||||
content: content.value,
|
||||
isStarred: false,
|
||||
})
|
||||
|
||||
// Navigate back to the previous screen
|
||||
window.location.hash = '#/notes'
|
||||
} catch (error) {
|
||||
// In a full implementation, show an alert or toast
|
||||
console.log('Create error: Failed to create note. Please try again.')
|
||||
}
|
||||
}
|
||||
|
||||
// 处理Header组件的操作按钮点击事件
|
||||
const handleAction = actionType => {
|
||||
if (actionType === 'save') {
|
||||
handleSave()
|
||||
} else if (actionType === 'create') {
|
||||
handleCreate()
|
||||
} else if (actionType === 'insertImage') {
|
||||
// 插入图片功能
|
||||
if (editorRef.value) {
|
||||
// 通过editorRef调用RichTextEditor组件的方法来插入图片
|
||||
editorRef.value.insertImage()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const setShowAlert = value => {
|
||||
showAlert.value = value
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<ion-app>
|
||||
<div class="container">
|
||||
<Header :title="headerTitle" :onAction="handleAddNote" actionIcon="create" leftType="settings" :onLeftAction="handleSettingsPress" :onFolderToggle="handleFolderToggle" :isFolderExpanded="isFolderExpanded" :onTitlePress="handleFolderToggle" />
|
||||
<Header :title="headerTitle" :onAction="handleHeaderAction" actionIcon="create" leftType="settings" :onLeftAction="handleSettingsPress" :onFolderToggle="handleFolderToggle" :isFolderExpanded="isFolderExpanded" :onTitlePress="handleFolderToggle" />
|
||||
|
||||
<!-- 悬浮文件夹列表 - 使用绝对定位实现 -->
|
||||
<div
|
||||
@@ -163,8 +163,8 @@ const headerTitle = computed(() => {
|
||||
})
|
||||
|
||||
const handleNotePress = noteId => {
|
||||
// 导航到详情页面的逻辑将在路由中处理
|
||||
window.location.hash = `#/notes/${noteId}`
|
||||
// 导航到编辑页面的逻辑将在路由中处理
|
||||
window.location.hash = `#/editor/${noteId}`
|
||||
}
|
||||
|
||||
const handleAddNote = () => {
|
||||
@@ -172,6 +172,13 @@ const handleAddNote = () => {
|
||||
window.location.hash = '#/editor'
|
||||
}
|
||||
|
||||
// 处理Header组件的操作按钮点击事件
|
||||
const handleHeaderAction = (actionType) => {
|
||||
if (actionType === 'create') {
|
||||
handleAddNote()
|
||||
}
|
||||
}
|
||||
|
||||
const handleDeleteNote = noteId => {
|
||||
noteToDelete.value = noteId
|
||||
showAlert.value = true
|
||||
|
||||
Reference in New Issue
Block a user