From bca5170e229efe5b29d0228943a214a8708bba13 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=9D=8E=E5=BF=97=E5=BC=BA?= <357099073@qq.com>
Date: Wed, 15 Jul 2026 18:14:17 +0800
Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=97=A5=E7=A8=8B=E7=95=8C?=
=?UTF-8?q?=E9=9D=A2?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
uniapp/.mimocode/.cron-lock | 1 +
uniapp/App.vue | 4 +-
uniapp/api/note.js | 138 ++++++
uniapp/api/request.js | 24 +
uniapp/api/schedule.js | 242 ++++++++++
uniapp/components/AppTabbar.vue | 118 +----
uniapp/pages.json | 35 ++
uniapp/pages/dashboard/dashboard.vue | 185 ++++----
uniapp/pages/features/features.vue | 619 ++++++++++---------------
uniapp/pages/login/login.vue | 180 +++----
uniapp/pages/profile/profile.vue | 437 ++---------------
uniapp/pages/tools/notepad/edit.vue | 290 ++++++++++++
uniapp/pages/tools/notepad/index.vue | 332 +++++++++++++
uniapp/pages/tools/schedule/detail.vue | 321 +++++++++++++
uniapp/pages/tools/schedule/edit.vue | 375 +++++++++++++++
uniapp/pages/tools/schedule/index.vue | 428 +++++++++++++++++
uniapp/src/styles/page-common.scss | 40 ++
uniapp/src/styles/theme.scss | 33 +-
uniapp/utils/date.js | 73 +++
19 files changed, 2805 insertions(+), 1070 deletions(-)
create mode 100644 uniapp/.mimocode/.cron-lock
create mode 100644 uniapp/api/note.js
create mode 100644 uniapp/api/request.js
create mode 100644 uniapp/api/schedule.js
create mode 100644 uniapp/pages/tools/notepad/edit.vue
create mode 100644 uniapp/pages/tools/notepad/index.vue
create mode 100644 uniapp/pages/tools/schedule/detail.vue
create mode 100644 uniapp/pages/tools/schedule/edit.vue
create mode 100644 uniapp/pages/tools/schedule/index.vue
create mode 100644 uniapp/src/styles/page-common.scss
create mode 100644 uniapp/utils/date.js
diff --git a/uniapp/.mimocode/.cron-lock b/uniapp/.mimocode/.cron-lock
new file mode 100644
index 0000000..7817067
--- /dev/null
+++ b/uniapp/.mimocode/.cron-lock
@@ -0,0 +1 @@
+{"pid":4360,"startedAt":1784109827988}
\ No newline at end of file
diff --git a/uniapp/App.vue b/uniapp/App.vue
index be529b4..d3b8474 100644
--- a/uniapp/App.vue
+++ b/uniapp/App.vue
@@ -20,8 +20,8 @@
page {
height: 100%;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
- background-color: #f8fafc;
- color: #1e293b;
+ background-color: #f5f7fa;
+ color: #303133;
box-sizing: border-box;
}
diff --git a/uniapp/api/note.js b/uniapp/api/note.js
new file mode 100644
index 0000000..e5d96ee
--- /dev/null
+++ b/uniapp/api/note.js
@@ -0,0 +1,138 @@
+import { generateId } from '@/api/request.js'
+
+const STORAGE_KEY = 'app_notes'
+
+function readLocal() {
+ return uni.getStorageSync(STORAGE_KEY) || []
+}
+
+function writeLocal(list) {
+ uni.setStorageSync(STORAGE_KEY, list)
+}
+
+function seedIfEmpty() {
+ const list = readLocal()
+ if (list.length) return list
+ const now = Date.now()
+ const seeded = [
+ {
+ id: generateId(),
+ title: '欢迎使用记事本',
+ content: '在这里记录灵感、待办或任何想法。支持置顶、搜索与编辑,后续可一键接入云端同步。',
+ pinned: true,
+ createdAt: now - 86400000,
+ updatedAt: now - 3600000
+ },
+ {
+ id: generateId(),
+ title: '项目会议要点',
+ content: '1. 确认 Q3 目标\n2. 排期评审\n3. 接口联调时间',
+ pinned: false,
+ createdAt: now - 172800000,
+ updatedAt: now - 7200000
+ }
+ ]
+ writeLocal(seeded)
+ return seeded
+}
+
+function sortNotes(list) {
+ return [...list].sort((a, b) => {
+ if (a.pinned !== b.pinned) return a.pinned ? -1 : 1
+ return (b.updatedAt || 0) - (a.updatedAt || 0)
+ })
+}
+
+/**
+ * 获取笔记列表
+ * @param {{ keyword?: string }} params
+ * @returns {Promise<{ list: Array, total: number }>}
+ */
+export async function fetchNotes(params = {}) {
+ // TODO: return request({ url: '/api/notes', data: params })
+ let list = seedIfEmpty()
+ const keyword = (params.keyword || '').trim().toLowerCase()
+ if (keyword) {
+ list = list.filter(
+ item =>
+ item.title.toLowerCase().includes(keyword) ||
+ item.content.toLowerCase().includes(keyword)
+ )
+ }
+ list = sortNotes(list)
+ return { list, total: list.length }
+}
+
+/**
+ * 获取单条笔记
+ * @param {string} id
+ */
+export async function getNote(id) {
+ // TODO: return request({ url: `/api/notes/${id}` })
+ const list = readLocal()
+ return list.find(item => item.id === id) || null
+}
+
+/**
+ * 创建笔记
+ * @param {{ title: string, content: string, pinned?: boolean }} data
+ */
+export async function createNote(data) {
+ // TODO: return request({ url: '/api/notes', method: 'POST', data })
+ const now = Date.now()
+ const note = {
+ id: generateId(),
+ title: (data.title || '').trim() || '无标题',
+ content: (data.content || '').trim(),
+ pinned: !!data.pinned,
+ createdAt: now,
+ updatedAt: now
+ }
+ const list = readLocal()
+ list.unshift(note)
+ writeLocal(list)
+ return note
+}
+
+/**
+ * 更新笔记
+ * @param {string} id
+ * @param {{ title?: string, content?: string, pinned?: boolean }} data
+ */
+export async function updateNote(id, data) {
+ // TODO: return request({ url: `/api/notes/${id}`, method: 'PUT', data })
+ const list = readLocal()
+ const index = list.findIndex(item => item.id === id)
+ if (index === -1) throw new Error('笔记不存在')
+ const note = {
+ ...list[index],
+ ...data,
+ title: data.title !== undefined ? (data.title.trim() || '无标题') : list[index].title,
+ content: data.content !== undefined ? data.content.trim() : list[index].content,
+ updatedAt: Date.now()
+ }
+ list[index] = note
+ writeLocal(list)
+ return note
+}
+
+/**
+ * 删除笔记
+ * @param {string} id
+ */
+export async function deleteNote(id) {
+ // TODO: return request({ url: `/api/notes/${id}`, method: 'DELETE' })
+ const list = readLocal().filter(item => item.id !== id)
+ writeLocal(list)
+ return true
+}
+
+/**
+ * 切换置顶
+ * @param {string} id
+ */
+export async function toggleNotePin(id) {
+ const note = await getNote(id)
+ if (!note) throw new Error('笔记不存在')
+ return updateNote(id, { pinned: !note.pinned })
+}
diff --git a/uniapp/api/request.js b/uniapp/api/request.js
new file mode 100644
index 0000000..b71b174
--- /dev/null
+++ b/uniapp/api/request.js
@@ -0,0 +1,24 @@
+/**
+ * 统一请求封装,接入后端时在此配置 baseURL、token、错误处理。
+ *
+ * 示例:
+ * export function request({ url, method = 'GET', data }) {
+ * return new Promise((resolve, reject) => {
+ * uni.request({
+ * url: BASE_URL + url,
+ * method,
+ * data,
+ * header: { Authorization: 'Bearer ' + getToken() },
+ * success: (res) => {
+ * if (res.statusCode >= 200 && res.statusCode < 300) resolve(res.data)
+ * else reject(res)
+ * },
+ * fail: reject
+ * })
+ * })
+ * }
+ */
+
+export function generateId() {
+ return `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`
+}
diff --git a/uniapp/api/schedule.js b/uniapp/api/schedule.js
new file mode 100644
index 0000000..69cc182
--- /dev/null
+++ b/uniapp/api/schedule.js
@@ -0,0 +1,242 @@
+import { generateId } from '@/api/request.js'
+import { startOfDay, endOfDay } from '@/utils/date.js'
+
+const STORAGE_KEY = 'app_schedules'
+
+const PRIORITY = {
+ low: { value: 'low', label: '低', color: '#909399' },
+ medium: { value: 'medium', label: '中', color: '#3c9cff' },
+ high: { value: 'high', label: '高', color: '#f56c6c' }
+}
+
+export const PRIORITY_OPTIONS = Object.values(PRIORITY)
+
+export const REMIND_CHANNEL_OPTIONS = [
+ { value: 'sms', label: '短信' },
+ { value: 'email', label: '邮件' },
+ { value: 'bark', label: 'Bark推送' },
+ { value: 'site', label: '站内信' },
+ { value: 'app', label: 'APP' }
+]
+
+export const REMIND_MINUTES_OPTIONS = [
+ { label: '不提前', value: 0 },
+ { label: '提前 5 分钟', value: 5 },
+ { label: '提前 10 分钟', value: 10 },
+ { label: '提前 15 分钟', value: 15 },
+ { label: '提前 30 分钟', value: 30 },
+ { label: '提前 1 小时', value: 60 },
+ { label: '提前 2 小时', value: 120 },
+ { label: '提前 1 天', value: 1440 }
+]
+
+function readLocal() {
+ const list = uni.getStorageSync(STORAGE_KEY) || []
+ return list.map(normalizeSchedule)
+}
+
+function writeLocal(list) {
+ uni.setStorageSync(STORAGE_KEY, list)
+}
+
+export function normalizeSchedule(item) {
+ const content = (item.content || item.title || '').trim()
+ const title = (item.title || content.split('\n')[0] || '无标题').trim().slice(0, 60)
+ let remindChannels = item.remindChannels
+ if (!Array.isArray(remindChannels)) {
+ remindChannels = item.remindMinutes > 0 ? ['app'] : []
+ }
+ return {
+ ...item,
+ title,
+ content,
+ remindChannels,
+ remindMinutes: item.remindMinutes ?? 15
+ }
+}
+
+function buildTitle(content) {
+ const line = (content || '').split('\n')[0].trim()
+ return line.slice(0, 60) || '无标题'
+}
+
+function seedIfEmpty() {
+ const raw = uni.getStorageSync(STORAGE_KEY)
+ if (raw && raw.length) return raw.map(normalizeSchedule)
+ const now = Date.now()
+ const today = new Date()
+ const seeded = [
+ {
+ id: generateId(),
+ content: '团队周会\n汇报本周进度,讨论下周计划',
+ datetime: new Date(today.getFullYear(), today.getMonth(), today.getDate(), 10, 0).getTime(),
+ remindChannels: ['app', 'site'],
+ remindMinutes: 15,
+ priority: 'high',
+ completed: false,
+ createdAt: now - 86400000,
+ updatedAt: now - 86400000
+ },
+ {
+ id: generateId(),
+ content: '提交月报\n整理本月数据并发送给主管',
+ datetime: new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1, 18, 0).getTime(),
+ remindChannels: ['email', 'site'],
+ remindMinutes: 30,
+ priority: 'medium',
+ completed: false,
+ createdAt: now - 43200000,
+ updatedAt: now - 43200000
+ },
+ {
+ id: generateId(),
+ content: '健身打卡\n有氧运动 30 分钟',
+ datetime: new Date(today.getFullYear(), today.getMonth(), today.getDate() - 1, 19, 30).getTime(),
+ remindChannels: ['bark', 'app'],
+ remindMinutes: 10,
+ priority: 'low',
+ completed: true,
+ createdAt: now - 259200000,
+ updatedAt: now - 86400000
+ }
+ ].map(normalizeSchedule)
+ writeLocal(seeded)
+ return seeded
+}
+
+function sortSchedules(list) {
+ return [...list].sort((a, b) => {
+ if (a.completed !== b.completed) return a.completed ? 1 : -1
+ return (a.datetime || 0) - (b.datetime || 0)
+ })
+}
+
+/**
+ * 获取日程列表
+ */
+export async function fetchSchedules(params = {}) {
+ // TODO: return request({ url: '/api/schedules', data: params })
+ let list = seedIfEmpty()
+ const keyword = (params.keyword || '').trim().toLowerCase()
+ const status = params.status || 'all'
+
+ if (keyword) {
+ list = list.filter(item => {
+ const text = `${item.title} ${item.content}`.toLowerCase()
+ return text.includes(keyword)
+ })
+ }
+ if (status === 'pending') list = list.filter(item => !item.completed)
+ if (status === 'done') list = list.filter(item => item.completed)
+
+ if (params.date) {
+ const dayStart = startOfDay(params.date).getTime()
+ const dayEnd = endOfDay(params.date).getTime()
+ list = list.filter(item => item.datetime >= dayStart && item.datetime <= dayEnd)
+ }
+
+ list = sortSchedules(list)
+ return { list, total: list.length }
+}
+
+/**
+ * 获取单条日程
+ */
+export async function getSchedule(id) {
+ // TODO: return request({ url: `/api/schedules/${id}` })
+ const list = readLocal()
+ const item = list.find(entry => entry.id === id)
+ return item ? normalizeSchedule(item) : null
+}
+
+/**
+ * 创建日程
+ * @param {{ content: string, datetime: number, remindChannels?: string[], remindMinutes?: number }} data
+ */
+export async function createSchedule(data) {
+ // TODO: return request({ url: '/api/schedules', method: 'POST', data })
+ const now = Date.now()
+ const content = (data.content || '').trim()
+ const schedule = normalizeSchedule({
+ id: generateId(),
+ title: buildTitle(content),
+ content,
+ datetime: data.datetime,
+ remindChannels: data.remindChannels || [],
+ remindMinutes: data.remindMinutes ?? 15,
+ priority: data.priority || 'medium',
+ completed: false,
+ createdAt: now,
+ updatedAt: now
+ })
+ const list = readLocal()
+ list.push(schedule)
+ writeLocal(list)
+ return schedule
+}
+
+/**
+ * 更新日程
+ */
+export async function updateSchedule(id, data) {
+ // TODO: return request({ url: `/api/schedules/${id}`, method: 'PUT', data })
+ const list = readLocal()
+ const index = list.findIndex(item => item.id === id)
+ if (index === -1) throw new Error('日程不存在')
+
+ const prev = list[index]
+ const content = data.content !== undefined ? data.content.trim() : prev.content
+ const schedule = normalizeSchedule({
+ ...prev,
+ ...data,
+ title: data.content !== undefined ? buildTitle(content) : prev.title,
+ content,
+ updatedAt: Date.now()
+ })
+ list[index] = schedule
+ writeLocal(list)
+ return schedule
+}
+
+/**
+ * 删除日程
+ */
+export async function deleteSchedule(id) {
+ // TODO: return request({ url: `/api/schedules/${id}`, method: 'DELETE' })
+ const list = readLocal().filter(item => item.id !== id)
+ writeLocal(list)
+ return true
+}
+
+/**
+ * 切换完成状态
+ */
+export async function toggleScheduleComplete(id) {
+ const item = await getSchedule(id)
+ if (!item) throw new Error('日程不存在')
+ return updateSchedule(id, { completed: !item.completed })
+}
+
+export function getPriorityMeta(value) {
+ return PRIORITY[value] || PRIORITY.medium
+}
+
+export function getRemindChannelLabel(value) {
+ return REMIND_CHANNEL_OPTIONS.find(item => item.value === value)?.label || value
+}
+
+export function getRemindChannelLabels(channels = []) {
+ if (!channels.length) return '未设置'
+ return channels.map(getRemindChannelLabel).join('、')
+}
+
+export function getRemindMinutesLabel(minutes) {
+ const item = REMIND_MINUTES_OPTIONS.find(option => option.value === minutes)
+ return item?.label || (minutes ? `提前 ${minutes} 分钟` : '不提前')
+}
+
+export function getScheduleSummary(item) {
+ const text = (item?.content || item?.title || '').trim()
+ const line = text.split('\n')[0]
+ return line || '无内容'
+}
diff --git a/uniapp/components/AppTabbar.vue b/uniapp/components/AppTabbar.vue
index 6bb9267..0a91c15 100644
--- a/uniapp/components/AppTabbar.vue
+++ b/uniapp/components/AppTabbar.vue
@@ -1,179 +1,87 @@
-
-
-
-
+
-
-
{{ tab.text }}
-
-
-
-
-
-
-
-
-
-
-
diff --git a/uniapp/pages.json b/uniapp/pages.json
index 87e8015..037ed46 100644
--- a/uniapp/pages.json
+++ b/uniapp/pages.json
@@ -36,6 +36,41 @@
"navigationStyle": "custom",
"navigationBarTitleText": "我的"
}
+ },
+ {
+ "path": "pages/tools/notepad/index",
+ "style": {
+ "navigationBarTitleText": "记事本",
+ "navigationBarBackgroundColor": "#F8FAFC"
+ }
+ },
+ {
+ "path": "pages/tools/notepad/edit",
+ "style": {
+ "navigationBarTitleText": "新建笔记",
+ "navigationBarBackgroundColor": "#F8FAFC"
+ }
+ },
+ {
+ "path": "pages/tools/schedule/index",
+ "style": {
+ "navigationBarTitleText": "日程提醒",
+ "navigationBarBackgroundColor": "#F8FAFC"
+ }
+ },
+ {
+ "path": "pages/tools/schedule/detail",
+ "style": {
+ "navigationBarTitleText": "日程详情",
+ "navigationBarBackgroundColor": "#F8FAFC"
+ }
+ },
+ {
+ "path": "pages/tools/schedule/edit",
+ "style": {
+ "navigationBarTitleText": "新建日程",
+ "navigationBarBackgroundColor": "#F8FAFC"
+ }
}
],
"globalStyle": {
diff --git a/uniapp/pages/dashboard/dashboard.vue b/uniapp/pages/dashboard/dashboard.vue
index 8a12f8e..8349c84 100644
--- a/uniapp/pages/dashboard/dashboard.vue
+++ b/uniapp/pages/dashboard/dashboard.vue
@@ -11,7 +11,7 @@
-
+
搜索功能、数据...
@@ -19,17 +19,20 @@
- {{ item.value }}
{{ item.label }}
-
- {{ item.up ? '↑' : '↓' }} {{ item.trend }}
-
+ {{ item.value }}
+
+
+ {{ item.trend }}
+
- 快捷入口
-
+
+
-
+
{{ item.name }}
@@ -51,11 +54,14 @@
-
+
+
+
{{ item.title }}
{{ item.time }}
+
@@ -121,149 +127,145 @@ function onQuickTap(item) {
diff --git a/uniapp/pages/features/features.vue b/uniapp/pages/features/features.vue
index e736182..92d21e7 100644
--- a/uniapp/pages/features/features.vue
+++ b/uniapp/pages/features/features.vue
@@ -1,451 +1,304 @@
-
-
-
-
-
-
-
-
-
diff --git a/uniapp/pages/login/login.vue b/uniapp/pages/login/login.vue
index 9bc46b3..cab5be1 100644
--- a/uniapp/pages/login/login.vue
+++ b/uniapp/pages/login/login.vue
@@ -3,6 +3,9 @@
@@ -22,7 +25,8 @@
-
+
+
@@ -47,7 +51,7 @@
type="number"
maxlength="6"
border="none"
- color="#3c9cff"
+ color="#303133"
:customStyle="inputStyle"
:placeholderStyle="placeholderStyle"
/>
@@ -65,7 +69,7 @@
v-model="username"
placeholder="请输入账号"
border="none"
- color="#3c9cff"
+ color="#303133"
:customStyle="inputStyle"
:placeholderStyle="placeholderStyle"
/>
@@ -76,7 +80,7 @@
placeholder="请输入密码"
type="password"
border="none"
- color="#3c9cff"
+ color="#303133"
:customStyle="inputStyle"
:placeholderStyle="placeholderStyle"
/>
@@ -85,6 +89,7 @@
登 录
测试登录
+
@@ -139,7 +144,7 @@ const inputStyle = {
fontSize: '28rpx'
}
-const placeholderStyle = 'color: #9acafc; font-size: 28rpx'
+const placeholderStyle = 'color: #909399; font-size: 28rpx'
onMounted(() => {
if (isLoggedIn()) {
@@ -217,85 +222,85 @@ function socialLogin(type) {
diff --git a/uniapp/pages/profile/profile.vue b/uniapp/pages/profile/profile.vue
index 75d0a8a..5614945 100644
--- a/uniapp/pages/profile/profile.vue
+++ b/uniapp/pages/profile/profile.vue
@@ -1,649 +1,306 @@
-
-
-
-
-
-
-
-
退出登录
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/uniapp/pages/tools/notepad/edit.vue b/uniapp/pages/tools/notepad/edit.vue
new file mode 100644
index 0000000..cf662b6
--- /dev/null
+++ b/uniapp/pages/tools/notepad/edit.vue
@@ -0,0 +1,290 @@
+
+
+
+
+ 标题
+
+
+
+
+ 内容
+
+ {{ form.content.length }}/5000
+
+
+
+
+
+ 置顶笔记
+
+
+
+
+
+
+ 最近更新:{{ formatDate(meta.updatedAt, true) }}
+
+
+
+
+
+
+
+
+
diff --git a/uniapp/pages/tools/notepad/index.vue b/uniapp/pages/tools/notepad/index.vue
new file mode 100644
index 0000000..14dae10
--- /dev/null
+++ b/uniapp/pages/tools/notepad/index.vue
@@ -0,0 +1,332 @@
+
+
+
+
+
+
+
+
+
+
+
+ 共 {{ total }} 条
+ 置顶 {{ pinnedCount }} 条
+
+
+
+
+
+ 加载中...
+
+
+
+ {{ keyword ? '未找到相关笔记' : '暂无笔记,点击右下角新建' }}
+
+
+
+
+
+ 置顶
+ {{ item.title }}
+
+ {{ formatRelativeTime(item.updatedAt) }}
+
+ {{ preview(item.content) }}
+
+
+
+ {{ item.pinned ? '取消置顶' : '置顶' }}
+
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/uniapp/pages/tools/schedule/detail.vue b/uniapp/pages/tools/schedule/detail.vue
new file mode 100644
index 0000000..3096984
--- /dev/null
+++ b/uniapp/pages/tools/schedule/detail.vue
@@ -0,0 +1,321 @@
+
+
+
+ 加载中...
+
+
+
+
+
+ {{ item.content || '无内容' }}
+
+
+
+ 发生时间
+ {{ formatDate(item.datetime, true) }}
+
+
+
+ 提醒渠道
+
+ {{ getRemindChannelLabel(channel) }}
+ 未设置
+
+
+
+
+ 提前提醒时间
+ {{ remindText }}
+
+
+
+ 创建于 {{ formatDate(item.createdAt, true) }}
+ 更新于 {{ formatDate(item.updatedAt, true) }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/uniapp/pages/tools/schedule/edit.vue b/uniapp/pages/tools/schedule/edit.vue
new file mode 100644
index 0000000..2476eac
--- /dev/null
+++ b/uniapp/pages/tools/schedule/edit.vue
@@ -0,0 +1,375 @@
+
+
+
+
+ 该日程已完成,不可编辑
+
+
+
+
+ 日程内容 *
+
+ {{ form.content.length }}/1000
+
+
+
+ 发生日期 *
+
+
+ {{ form.date || '选择日期' }}
+
+
+
+
+
+
+ 发生时间 *
+
+
+ {{ form.time || '选择时间' }}
+
+
+
+
+
+
+ 提醒渠道
+ 可多选,接入接口后按渠道下发提醒
+
+
+ {{ channel.label }}
+
+
+
+
+
+ 提前提醒时间
+
+
+ {{ remindLabels[remindIndex] }}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/uniapp/pages/tools/schedule/index.vue b/uniapp/pages/tools/schedule/index.vue
new file mode 100644
index 0000000..75a323d
--- /dev/null
+++ b/uniapp/pages/tools/schedule/index.vue
@@ -0,0 +1,428 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ tab.label }}
+
+
+
+
+
+ 加载中...
+
+
+
+ {{ keyword ? '未找到相关日程' : '暂无日程,点击右下角新建' }}
+
+
+
+ {{ group.label }}
+
+
+
+
+ {{ getScheduleSummary(item) }}
+
+
+
+ {{ formatDate(item.datetime, true) }}
+
+
+ {{ getRemindChannelLabel(ch) }}
+ {{ getRemindMinutesLabel(item.remindMinutes) }}
+
+
+
+
+
+ {{ item.completed ? '已完成' : '标记完成' }}
+
+
+
+ 编辑
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/uniapp/src/styles/page-common.scss b/uniapp/src/styles/page-common.scss
new file mode 100644
index 0000000..e864fc9
--- /dev/null
+++ b/uniapp/src/styles/page-common.scss
@@ -0,0 +1,40 @@
+@import '@/src/styles/theme.scss';
+
+@mixin page-shell {
+ height: 100vh;
+ background: $color-bg-page;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+}
+
+@mixin scroll-body {
+ flex: 1;
+ min-height: 0;
+ overflow-y: auto;
+ -webkit-overflow-scrolling: touch;
+ padding: 24rpx $page-padding-x 0;
+}
+
+@mixin card {
+ background: $color-card;
+ border-radius: $radius-lg;
+ box-shadow: $shadow-card;
+}
+
+@mixin section-title {
+ font-size: 32rpx;
+ font-weight: 600;
+ color: $color-text;
+}
+
+@mixin icon-box($size: 72rpx) {
+ width: $size;
+ height: $size;
+ border-radius: $radius-md;
+ background: $color-primary-bg;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+}
diff --git a/uniapp/src/styles/theme.scss b/uniapp/src/styles/theme.scss
index 281baa4..aede43a 100644
--- a/uniapp/src/styles/theme.scss
+++ b/uniapp/src/styles/theme.scss
@@ -1,19 +1,18 @@
-// 简约潮流主题变量
-$color-primary: #6366f1;
-$color-primary-light: #818cf8;
-$color-primary-dark: #4f46e5;
-$color-accent: #ec4899;
-$color-bg: #f8fafc;
+// 标准商务主题(单色蓝 + 灰阶,无渐变)
+$color-primary: #3c9cff;
+$color-primary-dark: #2b8ae8;
+$color-primary-bg: #ecf5ff;
+$color-bg-page: #f5f7fa;
$color-card: #ffffff;
-$color-text: #1e293b;
-$color-text-secondary: #64748b;
-$color-text-muted: #94a3b8;
-$color-border: #e2e8f0;
-$gradient-primary: linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #ec4899 100%);
-$gradient-soft: linear-gradient(160deg, #eef2ff 0%, #faf5ff 50%, #fdf2f8 100%);
-$shadow-sm: 0 2rpx 12rpx rgba(99, 102, 241, 0.08);
-$shadow-md: 0 8rpx 32rpx rgba(99, 102, 241, 0.12);
-$radius-sm: 16rpx;
-$radius-md: 24rpx;
-$radius-lg: 32rpx;
+$color-text: #303133;
+$color-text-secondary: #606266;
+$color-text-muted: #909399;
+$color-border: #ebeef5;
+$color-divider: #f2f3f5;
+$shadow-card: 0 4rpx 24rpx rgba(0, 0, 0, 0.05);
+$shadow-header: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
+$radius-sm: 8rpx;
+$radius-md: 12rpx;
+$radius-lg: 16rpx;
$radius-full: 999rpx;
+$page-padding-x: 32rpx;
diff --git a/uniapp/utils/date.js b/uniapp/utils/date.js
new file mode 100644
index 0000000..27c5f25
--- /dev/null
+++ b/uniapp/utils/date.js
@@ -0,0 +1,73 @@
+export function pad(n) {
+ return String(n).padStart(2, '0')
+}
+
+export function toDate(value) {
+ if (!value) return null
+ const d = value instanceof Date ? value : new Date(value)
+ return Number.isNaN(d.getTime()) ? null : d
+}
+
+export function formatDate(value, withTime = false) {
+ const d = toDate(value)
+ if (!d) return ''
+ const date = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`
+ if (!withTime) return date
+ return `${date} ${pad(d.getHours())}:${pad(d.getMinutes())}`
+}
+
+export function formatRelativeTime(value) {
+ const d = toDate(value)
+ if (!d) return ''
+ const now = new Date()
+ const diff = now.getTime() - d.getTime()
+ const minute = 60 * 1000
+ const hour = 60 * minute
+ const day = 24 * hour
+
+ if (diff < minute) return '刚刚'
+ if (diff < hour) return `${Math.floor(diff / minute)} 分钟前`
+ if (diff < day) return `${Math.floor(diff / hour)} 小时前`
+ if (diff < 7 * day) return `${Math.floor(diff / day)} 天前`
+ return formatDate(d)
+}
+
+export function isSameDay(a, b) {
+ const da = toDate(a)
+ const db = toDate(b)
+ if (!da || !db) return false
+ return (
+ da.getFullYear() === db.getFullYear() &&
+ da.getMonth() === db.getMonth() &&
+ da.getDate() === db.getDate()
+ )
+}
+
+export function isToday(value) {
+ return isSameDay(value, new Date())
+}
+
+export function isTomorrow(value) {
+ const d = toDate(value)
+ if (!d) return false
+ const tomorrow = new Date()
+ tomorrow.setDate(tomorrow.getDate() + 1)
+ return isSameDay(d, tomorrow)
+}
+
+export function dayLabel(value) {
+ if (isToday(value)) return '今天'
+ if (isTomorrow(value)) return '明天'
+ return formatDate(value)
+}
+
+export function startOfDay(value = new Date()) {
+ const d = toDate(value) || new Date()
+ return new Date(d.getFullYear(), d.getMonth(), d.getDate())
+}
+
+export function endOfDay(value = new Date()) {
+ const d = startOfDay(value)
+ d.setHours(23, 59, 59, 999)
+ return d
+}