You've already forked SmartisanNote.Remake
新增 离线web应用发布流程;
移除了便签详情页; 优化了若干逻辑; 新增 移动端、IOS兼容处理;
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -95,3 +95,4 @@ ehthumbs_vista.db
|
|||||||
|
|
||||||
# Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option)
|
# Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option)
|
||||||
|
|
||||||
|
ftp-config.json
|
||||||
144
index.html
144
index.html
@@ -2,89 +2,91 @@
|
|||||||
<html lang="zh-CN">
|
<html lang="zh-CN">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
||||||
<title>锤子便签</title>
|
<title>锤子便签</title>
|
||||||
<style>
|
<style>
|
||||||
/* Smartisan Notes Color Scheme - Based on Original Design */
|
/* Smartisan Notes Color Scheme - Based on Original Design */
|
||||||
:root {
|
:root {
|
||||||
/* Primary colors - Original Smartisan Notes brown/gold palette */
|
/* Primary colors - Original Smartisan Notes brown/gold palette */
|
||||||
--primary: #5c3c2a; /* Main brown color for UI elements */
|
--primary: #5c3c2a; /* Main brown color for UI elements */
|
||||||
--primary-dark: #4a3224; /* Darker shade of primary */
|
--primary-dark: #4a3224; /* Darker shade of primary */
|
||||||
--primary-light: #f5f0e6; /* Light background tone */
|
--primary-light: #f5f0e6; /* Light background tone */
|
||||||
|
|
||||||
/* Editor typography - Consistent font size and line height */
|
/* Editor typography - Consistent font size and line height */
|
||||||
--editor-font-size: 19px; /* Base font size for editor */
|
--editor-font-size: 19px; /* Base font size for editor */
|
||||||
--editor-line-height: 1.5; /* Line height for editor */
|
--editor-line-height: 1.5; /* Line height for editor */
|
||||||
|
|
||||||
/* Background colors - Warm paper-like tones */
|
/* Background colors - Warm paper-like tones */
|
||||||
--background: #fbf7ed; /* Main app background - warm off-white */
|
--background: #fbf7ed; /* Main app background - warm off-white */
|
||||||
--background-secondary: #f7f2e9; /* Slightly darker background */
|
--background-secondary: #f7f2e9; /* Slightly darker background */
|
||||||
--background-card: #ffffff; /* Pure white for cards/notes */
|
--background-card: #ffffff; /* Pure white for cards/notes */
|
||||||
--search-bar-background: #f0f0f0; /* Search bar background - light gray */
|
--search-bar-background: #f0f0f0; /* Search bar background - light gray */
|
||||||
|
|
||||||
/* Text colors - Brown/black tones for readability */
|
/* Text colors - Brown/black tones for readability */
|
||||||
--text-primary: #5c3c2a; /* Main text color - dark brown */
|
--text-primary: #5c3c2a; /* Main text color - dark brown */
|
||||||
--text-secondary: #6e482f; /* Secondary text - medium brown */
|
--text-secondary: #6e482f; /* Secondary text - medium brown */
|
||||||
--text-tertiary: #9e836c; /* Tertiary text - light brown/gray */
|
--text-tertiary: #9e836c; /* Tertiary text - light brown/gray */
|
||||||
--text-inverted: #ffffff; /* White text for dark backgrounds */
|
--text-inverted: #ffffff; /* White text for dark backgrounds */
|
||||||
|
|
||||||
/* Accent colors - Smartisan's signature colors */
|
/* Accent colors - Smartisan's signature colors */
|
||||||
--accent-blue: #5c89f2; /* Blue for links/actions */
|
--accent-blue: #5c89f2; /* Blue for links/actions */
|
||||||
--accent-green: #97cc4e; /* Green for success/positive actions */
|
--accent-green: #97cc4e; /* Green for success/positive actions */
|
||||||
--accent-red: #e65c53; /* Red for errors/dangerous actions */
|
--accent-red: #e65c53; /* Red for errors/dangerous actions */
|
||||||
--accent-orange: #f0880d; /* Orange for warnings/highlights */
|
--accent-orange: #f0880d; /* Orange for warnings/highlights */
|
||||||
--accent-yellow: #ffffdd33; /* Yellow for starred items/highlights (updated to match original) */
|
--accent-yellow: #ffffdd33; /* Yellow for starred items/highlights (updated to match original) */
|
||||||
|
|
||||||
/* Note specific colors */
|
/* Note specific colors */
|
||||||
--note-title: #5c3c2a; /* Note title color */
|
--note-title: #5c3c2a; /* Note title color */
|
||||||
--note-content: #6e482f; /* Note content color */
|
--note-content: #6e482f; /* Note content color */
|
||||||
--note-date: #705c3c2a; /* Date/time color */
|
--note-date: #705c3c2a; /* Date/time color */
|
||||||
--note-star: #ffffdd33; /* Star/favorite color (updated to match original) */
|
--note-star: #ffffdd33; /* Star/favorite color (updated to match original) */
|
||||||
|
|
||||||
/* Folder colors */
|
/* Folder colors */
|
||||||
--folder-name: #9b9b9b; /* Folder name color (60% black) */
|
--folder-name: #9b9b9b; /* Folder name color (60% black) */
|
||||||
--folder-count: #b4b4b4; /* Folder item count color (30% black) */
|
--folder-count: #b4b4b4; /* Folder item count color (30% black) */
|
||||||
--folder-item-selected: #f5f5f5; /* Folder item selected background color */
|
--folder-item-selected: #f5f5f5; /* Folder item selected background color */
|
||||||
|
|
||||||
/* Button colors - Based on Smartisan's button styles */
|
/* Button colors - Based on Smartisan's button styles */
|
||||||
--button-primary: #5c3c2a; /* Primary button - brown */
|
--button-primary: #5c3c2a; /* Primary button - brown */
|
||||||
--button-secondary: #97cc4e; /* Secondary button - green */
|
--button-secondary: #97cc4e; /* Secondary button - green */
|
||||||
--button-danger: #e65c53; /* Danger button - red */
|
--button-danger: #e65c53; /* Danger button - red */
|
||||||
--button-disabled: #d4d4d5; /* Disabled button - light gray */
|
--button-disabled: #d4d4d5; /* Disabled button - light gray */
|
||||||
|
|
||||||
/* Status colors */
|
/* Status colors */
|
||||||
--success: #79ad31; /* Success - green */
|
--success: #79ad31; /* Success - green */
|
||||||
--warning: #f0880d; /* Warning - orange */
|
--warning: #f0880d; /* Warning - orange */
|
||||||
--error: #e64746; /* Error - red */
|
--error: #e64746; /* Error - red */
|
||||||
--info: #5c89f2; /* Info - blue */
|
--info: #5c89f2; /* Info - blue */
|
||||||
|
|
||||||
/* UI elements - Borders, dividers, shadows */
|
/* UI elements - Borders, dividers, shadows */
|
||||||
--border: #e5ddca; /* Light brown border */
|
--border: #e5ddca; /* Light brown border */
|
||||||
--divider: #e5e5e5; /* Light gray divider */
|
--divider: #e5e5e5; /* Light gray divider */
|
||||||
--shadow: #00000014; /* Subtle shadow */
|
--shadow: #00000014; /* Subtle shadow */
|
||||||
|
|
||||||
/* Transparency variants */
|
/* Transparency variants */
|
||||||
--black-05: #0000000d; /* 5% black */
|
--black-05: #0000000d; /* 5% black */
|
||||||
--black-10: #0000001a; /* 10% black */
|
--black-10: #0000001a; /* 10% black */
|
||||||
--black-14: #00000024; /* 14% black */
|
--black-14: #00000024; /* 14% black */
|
||||||
--black-20: #00000033; /* 20% black */
|
--black-20: #00000033; /* 20% black */
|
||||||
--black-30: #0000004d; /* 30% black */
|
--black-30: #0000004d; /* 30% black */
|
||||||
--black-40: #00000066; /* 40% black */
|
--black-40: #00000066; /* 40% black */
|
||||||
--black-50: #00000080; /* 50% black */
|
--black-50: #00000080; /* 50% black */
|
||||||
--black-60: #00000099; /* 60% black */
|
--black-60: #00000099; /* 60% black */
|
||||||
--black-80: #000000cc; /* 80% black */
|
--black-80: #000000cc; /* 80% black */
|
||||||
--black-90: #000000e6; /* 90% black */
|
--black-90: #000000e6; /* 90% black */
|
||||||
|
|
||||||
--white-10: #ffffff1a; /* 10% white */
|
--white-10: #ffffff1a; /* 10% white */
|
||||||
--white-20: #ffffff33; /* 20% white */
|
--white-20: #ffffff33; /* 20% white */
|
||||||
--white-30: #ffffff4d; /* 30% white */
|
--white-30: #ffffff4d; /* 30% white */
|
||||||
--white-40: #ffffff66; /* 40% white */
|
--white-40: #ffffff66; /* 40% white */
|
||||||
--white-50: #ffffff80; /* 50% white */
|
--white-50: #ffffff80; /* 50% white */
|
||||||
--white-60: #ffffff99; /* 60% white */
|
--white-60: #ffffff99; /* 60% white */
|
||||||
--white-80: #ffffffcc; /* 80% white */
|
--white-80: #ffffffcc; /* 80% white */
|
||||||
--white-90: #ffffffe6; /* 90% white */
|
--white-90: #ffffffe6; /* 90% white */
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -92,11 +94,21 @@
|
|||||||
background-color: var(--background);
|
background-color: var(--background);
|
||||||
background-image: repeating-linear-gradient(0deg, transparent, transparent 10px, var(--background-secondary) 10px, var(--background-secondary) 20px);
|
background-image: repeating-linear-gradient(0deg, transparent, transparent 10px, var(--background-secondary) 10px, var(--background-secondary) 20px);
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
|
/* 适配iPhone X及更新机型的刘海屏 */
|
||||||
|
padding-top: env(safe-area-inset-top);
|
||||||
|
padding-right: env(safe-area-inset-right);
|
||||||
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
|
padding-left: env(safe-area-inset-left);
|
||||||
}
|
}
|
||||||
|
|
||||||
#app {
|
#app {
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
/* 适配iPhone X及更新机型的刘海屏 */
|
||||||
|
padding-top: env(safe-area-inset-top);
|
||||||
|
padding-right: env(safe-area-inset-right);
|
||||||
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
|
padding-left: env(safe-area-inset-left);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
@@ -104,4 +116,4 @@
|
|||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<script type="module" src="/src/main.js"></script>
|
<script type="module" src="/src/main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
10
package-lock.json
generated
10
package-lock.json
generated
@@ -15,6 +15,7 @@
|
|||||||
"@capacitor/ios": "^5.7.2",
|
"@capacitor/ios": "^5.7.2",
|
||||||
"@vue/cli-service": "^5.0.9",
|
"@vue/cli-service": "^5.0.9",
|
||||||
"@vue/compiler-sfc": "^3.5.22",
|
"@vue/compiler-sfc": "^3.5.22",
|
||||||
|
"basic-ftp": "^5.0.5",
|
||||||
"ionicons": "^7.4.0",
|
"ionicons": "^7.4.0",
|
||||||
"pinia": "^3.0.3",
|
"pinia": "^3.0.3",
|
||||||
"vue": "^3.5.22",
|
"vue": "^3.5.22",
|
||||||
@@ -4293,6 +4294,15 @@
|
|||||||
"baseline-browser-mapping": "dist/cli.js"
|
"baseline-browser-mapping": "dist/cli.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/basic-ftp": {
|
||||||
|
"version": "5.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz",
|
||||||
|
"integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/batch": {
|
"node_modules/batch": {
|
||||||
"version": "0.6.1",
|
"version": "0.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"android": "npx cap run android",
|
"android": "npx cap run android",
|
||||||
"build:all": "vite build && vite build --mode pwa",
|
"build:all": "vite build && vite build --mode pwa",
|
||||||
|
"deploy:pwa": "vite build --mode pwa && node upload-pwa.js",
|
||||||
"dev": "vite"
|
"dev": "vite"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -19,6 +20,7 @@
|
|||||||
"@capacitor/ios": "^5.7.2",
|
"@capacitor/ios": "^5.7.2",
|
||||||
"@vue/cli-service": "^5.0.9",
|
"@vue/cli-service": "^5.0.9",
|
||||||
"@vue/compiler-sfc": "^3.5.22",
|
"@vue/compiler-sfc": "^3.5.22",
|
||||||
|
"basic-ftp": "^5.0.5",
|
||||||
"ionicons": "^7.4.0",
|
"ionicons": "^7.4.0",
|
||||||
"pinia": "^3.0.3",
|
"pinia": "^3.0.3",
|
||||||
"vue": "^3.5.22",
|
"vue": "^3.5.22",
|
||||||
|
|||||||
@@ -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">
|
<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>
|
||||||
<!-- 占位符 -->
|
<!-- 占位符 -->
|
||||||
<div v-else class="image_4-placeholder"></div>
|
<div v-else class="image_4-placeholder"></div>
|
||||||
@@ -117,10 +118,10 @@ const handleLeftAction = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleAction = () => {
|
const handleAction = (actionType) => {
|
||||||
// 处理右侧操作按钮点击事件
|
// 处理右侧操作按钮点击事件
|
||||||
if (props.onAction) {
|
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 = () => {
|
const handleInput = () => {
|
||||||
if (editorRef.value) {
|
if (editorRef.value) {
|
||||||
@@ -878,6 +940,13 @@ defineExpose({
|
|||||||
line-height: var(--editor-line-height, 1.6);
|
line-height: var(--editor-line-height, 1.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.editor-content img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
/* 待办事项样式 */
|
/* 待办事项样式 */
|
||||||
:deep(.todo-container) {
|
:deep(.todo-container) {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -903,3 +972,71 @@ defineExpose({
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</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
|
// Pages
|
||||||
import NoteListPage from './pages/NoteListPage.vue'
|
import NoteListPage from './pages/NoteListPage.vue'
|
||||||
import NoteDetailPage from './pages/NoteDetailPage.vue'
|
|
||||||
import NoteEditorPage from './pages/NoteEditorPage.vue'
|
import NoteEditorPage from './pages/NoteEditorPage.vue'
|
||||||
import FolderPage from './pages/FolderPage.vue'
|
import FolderPage from './pages/FolderPage.vue'
|
||||||
import SettingsPage from './pages/SettingsPage.vue'
|
import SettingsPage from './pages/SettingsPage.vue'
|
||||||
@@ -14,7 +13,7 @@ import SettingsPage from './pages/SettingsPage.vue'
|
|||||||
const routes = [
|
const routes = [
|
||||||
{ path: '/', redirect: '/notes' },
|
{ path: '/', redirect: '/notes' },
|
||||||
{ path: '/notes', component: NoteListPage },
|
{ path: '/notes', component: NoteListPage },
|
||||||
{ path: '/notes/:id', component: NoteDetailPage, props: true },
|
{ path: '/notes/:id', component: NoteEditorPage, props: true },
|
||||||
{ path: '/editor', component: NoteEditorPage },
|
{ path: '/editor', component: NoteEditorPage },
|
||||||
{ path: '/editor/:id', component: NoteEditorPage, props: true },
|
{ path: '/editor/:id', component: NoteEditorPage, props: true },
|
||||||
{ path: '/folders', component: FolderPage },
|
{ 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>
|
<template>
|
||||||
<ion-page>
|
<ion-page>
|
||||||
<div class="container">
|
<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 class="edit-time">{{ formattedTime }}</span>
|
||||||
<span>|</span>
|
<span>|</span>
|
||||||
<span class="word-count">{{ wordCount }}</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 => {
|
const setShowAlert = value => {
|
||||||
showAlert.value = value
|
showAlert.value = value
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<ion-app>
|
<ion-app>
|
||||||
<div class="container">
|
<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
|
<div
|
||||||
@@ -163,8 +163,8 @@ const headerTitle = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const handleNotePress = noteId => {
|
const handleNotePress = noteId => {
|
||||||
// 导航到详情页面的逻辑将在路由中处理
|
// 导航到编辑页面的逻辑将在路由中处理
|
||||||
window.location.hash = `#/notes/${noteId}`
|
window.location.hash = `#/editor/${noteId}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleAddNote = () => {
|
const handleAddNote = () => {
|
||||||
@@ -172,6 +172,13 @@ const handleAddNote = () => {
|
|||||||
window.location.hash = '#/editor'
|
window.location.hash = '#/editor'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理Header组件的操作按钮点击事件
|
||||||
|
const handleHeaderAction = (actionType) => {
|
||||||
|
if (actionType === 'create') {
|
||||||
|
handleAddNote()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleDeleteNote = noteId => {
|
const handleDeleteNote = noteId => {
|
||||||
noteToDelete.value = noteId
|
noteToDelete.value = noteId
|
||||||
showAlert.value = true
|
showAlert.value = true
|
||||||
|
|||||||
33
upload-pwa.js
Normal file
33
upload-pwa.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import path from 'path';
|
||||||
|
import { Client } from 'basic-ftp';
|
||||||
|
import config from './ftp-config.json' with { type: 'json' };
|
||||||
|
|
||||||
|
async function uploadPWA() {
|
||||||
|
const client = new Client();
|
||||||
|
client.ftp.verbose = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await client.access({
|
||||||
|
host: config.host,
|
||||||
|
user: config.user,
|
||||||
|
password: config.password,
|
||||||
|
port: config.port,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 上传PWA构建目录中的所有文件
|
||||||
|
const localPath = path.resolve('./dist/offline');
|
||||||
|
const remotePath = config.remotePath;
|
||||||
|
|
||||||
|
await client.ensureDir(remotePath);
|
||||||
|
await client.clearWorkingDir();
|
||||||
|
await client.uploadFromDir(localPath);
|
||||||
|
|
||||||
|
console.log('PWA版本已成功上传到FTP服务器');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('FTP上传失败:', error);
|
||||||
|
} finally {
|
||||||
|
client.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadPWA();
|
||||||
Reference in New Issue
Block a user