feat: 新增运动节拍训练工具套件与子工具页面

新增运动节拍RepClock全套小程序页面、后端API与数据库服务,新增subhub子工具汇总页面,优化全局主页样式与配置,为web静态资源添加版本缓存参数,补充配套调试脚本
This commit is contained in:
yangxiangyuan
2026-09-09 14:43:06 +08:00
parent db7648eb4d
commit d4bf3627fd
69 changed files with 5759 additions and 297 deletions
+50 -80
View File
@@ -5,8 +5,6 @@ Page({
loading: true,
kits: [],
appNameZh: 'MilesYang时光助手',
subtitle: '个人工具集合,记录时光点滴。',
error: '',
clipVisible: false,
clipText: '',
clipCount: 0,
@@ -14,18 +12,12 @@ Page({
clipPreview: '',
},
onLoad() {
this.loadMeta()
},
onLoad() { this.refresh() },
onShow() {
this.checkClipboard()
},
onShow() { this.checkClipboard() },
onPullDownRefresh() {
this.loadMeta().finally(() => {
wx.stopPullDownRefresh()
})
this.refresh().finally(() => wx.stopPullDownRefresh())
},
simpleHash(str) {
@@ -42,6 +34,50 @@ Page({
return h1.toString(36) + h2.toString(36)
},
async refresh() {
this.setData({ loading: true })
try {
const res = await api.core.getMeta()
if (res && res.appNameZh) this.setData({ appNameZh: res.appNameZh })
} catch (e) {}
this.setData({
kits: this.buildKits(),
loading: false,
})
},
buildKits() {
return [
{
id: 'personal_collect',
name: '微信收藏夹',
description: '聊天图片 / 文件 / 文字收藏,标签搜索,软删除可追溯。',
icon: '📁',
path: '/kits/personal_collect/pages/list/list',
meta: 'API: /api/mp_personal_collect/ · DB: mp_personal_collect.db',
},
{
id: 'rep_clock',
name: '运动节拍 RepClock',
description: 'HIIT / Yoga / 拉伸节奏倒计时,锚点时钟 + 女声播报。',
icon: '⏱️',
path: '/kits/rep_clock/pages/cards/cards',
meta: 'API: /api/mp_rep_clock/ · DB: mp_rep_clock.db',
},
]
},
async onKitTap(e) {
const id = (e.currentTarget.dataset.id || '').trim()
const path = (e.currentTarget.dataset.path || '').trim()
if (!path) { wx.showToast({ title: '功能开发中', icon: 'none' }); return }
try { await api.core.setActiveKit(id) } catch (e) {}
wx.navigateTo({
url: path,
fail: () => wx.showToast({ title: '打开失败', icon: 'none' }),
})
},
checkClipboard() {
try {
const enable = wx.getStorageSync('mp_ta_clip_enable')
@@ -72,15 +108,13 @@ Page({
})
},
onClipCollect(e) {
const mode = (e && e.currentTarget && e.currentTarget.dataset.mode) || 'new'
onClipCollect() {
const text = this.data.clipText || ''
if (!text) { this.setData({ clipVisible: false }); return }
try { wx.setStorageSync('mp_ta_clip_text', text) } catch (e) {}
try { wx.setStorageSync('mp_ta_clip_mode', mode) } catch (e) {}
this.setData({ clipVisible: false })
wx.navigateTo({
url: '/kits/personal_collect/pages/edit/edit',
url: '/kits/personal_collect/pages/edit/edit?mode=new',
fail: () => { wx.showToast({ title: '打开失败', icon: 'none' }) }
})
},
@@ -105,69 +139,5 @@ Page({
})
},
async loadMeta() {
this.setData({ loading: true, error: '' })
try {
const res = await api.core.getMeta()
if (res && res.appNameZh) {
this.setData({ appNameZh: res.appNameZh })
}
if (res && Array.isArray(res.kits) && res.kits.length) {
this.setData({ kits: this.decorateKits(res.kits) })
try { getApp().setMeta(res) } catch (e) {}
} else {
this.setData({ kits: this.fallbackKits() })
}
} catch (e) {
this.setData({
error: e && e.message ? e.message : '加载失败,请检查网络',
kits: this.fallbackKits(),
})
} finally {
this.setData({ loading: false })
}
},
fallbackKits() {
return this.decorateKits([{
id: 'personal_collect',
name: '微信收藏夹',
description: '聊天图片、文件收藏管理,标签搜索,软删除可追溯。',
}])
},
decorateKits(list) {
const icons = {
personal_collect: '📁',
}
return list.map(item => Object.assign({}, item, {
icon: icons[item.id] || '🛠️',
path: this.getKitPath(item.id),
}))
},
getKitPath(id) {
const map = {
personal_collect: '/kits/personal_collect/pages/list/list',
}
return map[id] || ''
},
async onKitTap(e) {
const id = e.currentTarget.dataset.id
const path = e.currentTarget.dataset.path
if (!path) {
wx.showToast({ title: '功能开发中', icon: 'none' })
return
}
try {
await api.core.setActiveKit(id)
} catch (e) {}
wx.navigateTo({
url: path,
fail: () => {
wx.showToast({ title: '打开失败', icon: 'none' })
},
})
},
noop() {},
})
@@ -1,5 +1,8 @@
{
"navigationBarTitleText": "MilesYang时光助手",
"navigationBarTitleText": "时光助手",
"navigationBarBackgroundColor": "#1a1a2e",
"navigationBarTextStyle": "white",
"backgroundColor": "#0f0f1a",
"enablePullDownRefresh": true,
"backgroundTextStyle": "light"
}
@@ -1,8 +1,7 @@
<view class="container">
<view class="page-header">
<view class="page-title">{{appNameZh}}</view>
<view class="page-subtitle">{{subtitle}}</view>
<view wx:if="{{error}}" class="muted small" style="margin-top: 16rpx; color: #e05561;">⚠️ {{error}}</view>
<view class="page-subtitle">小工具集合 · 完全独立 · 零共享</view>
</view>
<view wx:if="{{clipVisible}}" class="clip-card" catchtouchmove="noop">
@@ -14,12 +13,22 @@
<view class="clip-actions">
<view class="clip-btn clip-btn-ghost" bindtap="onClipDismissForever">不再提示</view>
<view class="clip-btn clip-btn-ghost" bindtap="onClipCancel">取消</view>
<view class="clip-btn clip-btn-primary" bindtap="onClipCollect" data-mode="new">📌 收藏为文字</view>
<view class="clip-btn clip-btn-primary" bindtap="onClipCollect">📌 收藏为文字</view>
</view>
</view>
<view wx:if="{{loading}}" class="empty">加载中...</view>
<view class="hero-card">
<view class="hero-icon">
<text class="hero-emoji">🧘</text>
</view>
<view class="hero-body">
<view class="hero-title">每一次节奏,都是一次呼吸。</view>
<view class="hero-desc">独立工具集合,互不干扰,可独立部署。</view>
</view>
</view>
<block wx:for="{{kits}}" wx:key="id">
<view class="kit-card" bindtap="onKitTap" data-id="{{item.id}}" data-path="{{item.path}}">
<view class="kit-icon">
@@ -28,8 +37,13 @@
<view class="kit-body">
<view class="kit-title">{{item.name}}</view>
<view class="kit-desc">{{item.description}}</view>
<view wx:if="{{item.meta}}" class="kit-meta">{{item.meta}}</view>
</view>
<view class="chevron">›</view>
</view>
</block>
<view class="footer muted small">
独立部署 · 独立数据库 · 独立运行锁
</view>
</view>
@@ -1,4 +1,26 @@
/* 首页独立样式,大部分复用 app.wxss 全局样式 */
.hero-card {
margin: 4rpx 24rpx 36rpx;
padding: 32rpx 30rpx;
background: linear-gradient(145deg, rgba(108, 122, 224, 0.22), rgba(176, 107, 224, 0.16));
border: 1px solid rgba(140, 150, 240, 0.24);
border-radius: 28rpx;
display: flex;
align-items: center;
gap: 26rpx;
box-shadow: 0 14rpx 36rpx rgba(0,0,0,0.28);
}
.hero-icon { width: 96rpx; height: 96rpx; flex: none; border-radius: 24rpx; background: rgba(255,255,255,0.08); display: flex; align-items: center; justify-content: center; }
.hero-emoji { font-size: 54rpx; }
.hero-title { font-size: 30rpx; font-weight: 700; color: #efeffe; letter-spacing: 0.5rpx; margin-bottom: 8rpx; }
.hero-desc { font-size: 24rpx; color: #a6a9d0; }
.footer {
margin: 80rpx 0 40rpx;
text-align: center;
letter-spacing: 1rpx;
color: #5a5e82;
font-size: 22rpx;
}
.clip-card {
margin: 20rpx 24rpx 12rpx;
@@ -59,11 +81,6 @@
color: #b8b8d8;
border: 1px solid rgba(255,255,255,0.08);
}
.clip-btn-secondary {
background: rgba(108, 122, 224, 0.16);
color: #cfd4ff;
border: 1px solid rgba(108, 122, 224, 0.32);
}
.clip-btn-primary {
background: linear-gradient(135deg, #6c7ae0, #b06be0);
color: #fff;
@@ -0,0 +1,60 @@
const api = require('../../api.js')
Page({
data: {
loading: true,
kits: [],
appNameZh: 'MilesYang时光助手',
subtitle: '小工具集合 · 完全独立 · 零共享',
},
onLoad() { this.refresh() },
onPullDownRefresh() {
this.refresh().finally(() => wx.stopPullDownRefresh())
},
async refresh() {
this.setData({ loading: true })
try {
const res = await api.core.getMeta()
if (res && res.appNameZh) this.setData({ appNameZh: res.appNameZh })
} catch (e) {}
this.setData({
kits: this.buildKits(),
loading: false,
})
},
buildKits() {
return [
{
id: 'personal_collect',
name: '微信收藏夹',
description: '聊天图片 / 文件 / 文字收藏,标签搜索,软删除可追溯。',
icon: '📁',
path: '/kits/personal_collect/pages/list/list',
meta: 'API: /api/mp_personal_collect/ · DB: mp_personal_collect.db',
},
{
id: 'rep_clock',
name: '运动节拍 RepClock',
description: 'HIIT / Yoga / 拉伸节奏倒计时,锚点时钟 + 女声播报。',
icon: '⏱️',
path: '/kits/rep_clock/pages/cards/cards',
meta: 'API: /api/mp_rep_clock/ · DB: mp_rep_clock.db',
},
]
},
async onKitTap(e) {
const id = (e.currentTarget.dataset.id || '').trim()
const path = (e.currentTarget.dataset.path || '').trim()
if (!path) { wx.showToast({ title: '功能开发中', icon: 'none' }); return }
try { await api.core.setActiveKit(id) } catch (e) {}
wx.navigateTo({
url: path,
fail: () => wx.showToast({ title: '打开失败', icon: 'none' }),
})
},
})
@@ -0,0 +1,8 @@
{
"navigationBarTitleText": "时光助手",
"navigationBarBackgroundColor": "#1a1a2e",
"navigationBarTextStyle": "white",
"backgroundColor": "#0f0f1a",
"enablePullDownRefresh": true,
"backgroundTextStyle": "light"
}
@@ -0,0 +1,36 @@
<view class="container">
<view class="page-header">
<view class="page-title">{{appNameZh}}</view>
<view class="page-subtitle">{{subtitle}}</view>
</view>
<view wx:if="{{loading}}" class="empty">加载中...</view>
<view class="hero-card">
<view class="hero-icon">
<text class="hero-emoji">🧘</text>
</view>
<view class="hero-body">
<view class="hero-title">每一次节奏,都是一次呼吸。</view>
<view class="hero-desc">独立工具集合,互不干扰,可独立部署。</view>
</view>
</view>
<block wx:for="{{kits}}" wx:key="id">
<view class="kit-card" bindtap="onKitTap" data-id="{{item.id}}" data-path="{{item.path}}">
<view class="kit-icon">
<text class="kit-icon-text">{{item.icon}}</text>
</view>
<view class="kit-body">
<view class="kit-title">{{item.name}}</view>
<view class="kit-desc">{{item.description}}</view>
<view wx:if="{{item.meta}}" class="kit-meta">{{item.meta}}</view>
</view>
<view class="chevron">›</view>
</view>
</block>
<view class="footer muted small">
独立部署 · 独立数据库 · 独立运行锁
</view>
</view>
@@ -0,0 +1,23 @@
.hero-card {
margin: 4rpx 24rpx 36rpx;
padding: 32rpx 30rpx;
background: linear-gradient(145deg, rgba(108, 122, 224, 0.22), rgba(176, 107, 224, 0.16));
border: 1px solid rgba(140, 150, 240, 0.24);
border-radius: 28rpx;
display: flex;
align-items: center;
gap: 26rpx;
box-shadow: 0 14rpx 36rpx rgba(0,0,0,0.28);
}
.hero-icon { width: 96rpx; height: 96rpx; flex: none; border-radius: 24rpx; background: rgba(255,255,255,0.08); display: flex; align-items: center; justify-content: center; }
.hero-emoji { font-size: 54rpx; }
.hero-title { font-size: 30rpx; font-weight: 700; color: #efeffe; letter-spacing: 0.5rpx; margin-bottom: 8rpx; }
.hero-desc { font-size: 24rpx; color: #a6a9d0; }
.footer {
margin: 80rpx 0 40rpx;
text-align: center;
letter-spacing: 1rpx;
color: #5a5e82;
font-size: 22rpx;
}