feat(mp, web, calendar): 新增微信收藏夹与日历批量生成功能
- 新增微信小程序版个人收藏夹全套功能,包含聚合首页、收藏列表、编辑、详情页面与配套后端API - 新增web版时光助手门户与个人收藏夹工具,支持与小程序共享后端数据 - 优化日历提醒工具:替换本地bootstrap资源为公共CDN版本,新增批量生成循环提醒的前端界面与后端逻辑,新增数据库batch_id字段,更新鉴权配置文件 - 补充项目配置、站点地图、调试诊断脚本等配套文件 - 完善各模块的页面配置与样式文件
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
const api = require('../../api.js')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
loading: true,
|
||||
kits: [],
|
||||
appNameZh: 'MilesYang时光助手',
|
||||
subtitle: '个人工具集合,记录时光点滴。',
|
||||
error: '',
|
||||
clipVisible: false,
|
||||
clipText: '',
|
||||
clipCount: 0,
|
||||
clipLines: 0,
|
||||
clipPreview: '',
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadMeta()
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.checkClipboard()
|
||||
},
|
||||
|
||||
onPullDownRefresh() {
|
||||
this.loadMeta().finally(() => {
|
||||
wx.stopPullDownRefresh()
|
||||
})
|
||||
},
|
||||
|
||||
simpleHash(str) {
|
||||
const s = String(str || '')
|
||||
let h1 = 0x811c9dc5
|
||||
let h2 = 0xdeadbeef
|
||||
for (let i = 0; i < s.length; i++) {
|
||||
const c = s.charCodeAt(i)
|
||||
h1 = Math.imul(h1 ^ c, 16777619)
|
||||
h2 = Math.imul(h2 ^ c, 2246822519)
|
||||
}
|
||||
h1 = (h1 ^ h1 >>> 16) >>> 0
|
||||
h2 = (h2 ^ h2 >>> 15) >>> 0
|
||||
return h1.toString(36) + h2.toString(36)
|
||||
},
|
||||
|
||||
checkClipboard() {
|
||||
try {
|
||||
const enable = wx.getStorageSync('mp_ta_clip_enable')
|
||||
if (enable === false || enable === 'false') return
|
||||
} catch (e) {}
|
||||
wx.getClipboardData({
|
||||
success: (res) => {
|
||||
const text = (res.data || '').trim()
|
||||
if (text.length < 6 || text.length > 30000) return
|
||||
const hash = this.simpleHash(text)
|
||||
try {
|
||||
const last = wx.getStorageSync('mp_ta_clip_hash')
|
||||
if (last === hash) return
|
||||
wx.setStorageSync('mp_ta_clip_hash', hash)
|
||||
} catch (e) {}
|
||||
const count = text.length
|
||||
const lines = text.split('\n').length
|
||||
const preview = text.length > 220 ? text.slice(0, 220) + '\n\n……' : text
|
||||
this.setData({
|
||||
clipVisible: true,
|
||||
clipText: text,
|
||||
clipCount: count,
|
||||
clipLines: lines,
|
||||
clipPreview: preview,
|
||||
})
|
||||
},
|
||||
fail: () => {}
|
||||
})
|
||||
},
|
||||
|
||||
onClipCollect(e) {
|
||||
const mode = (e && e.currentTarget && e.currentTarget.dataset.mode) || 'new'
|
||||
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',
|
||||
fail: () => { wx.showToast({ title: '打开失败', icon: 'none' }) }
|
||||
})
|
||||
},
|
||||
|
||||
onClipCancel() {
|
||||
this.setData({ clipVisible: false })
|
||||
},
|
||||
|
||||
onClipDismissForever() {
|
||||
wx.showModal({
|
||||
title: '不再检测剪贴板?',
|
||||
content: '关闭后可在未来任意新增收藏页面时重新开启。',
|
||||
confirmText: '不再提示',
|
||||
cancelText: '保留',
|
||||
success: (r) => {
|
||||
if (r.confirm) {
|
||||
try { wx.setStorageSync('mp_ta_clip_enable', false) } catch (e) {}
|
||||
this.setData({ clipVisible: false })
|
||||
wx.showToast({ title: '已关闭检测', icon: 'none' })
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
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' })
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"navigationBarTitleText": "MilesYang时光助手",
|
||||
"enablePullDownRefresh": true,
|
||||
"backgroundTextStyle": "light"
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<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>
|
||||
|
||||
<view wx:if="{{clipVisible}}" class="clip-card" catchtouchmove="noop">
|
||||
<view class="clip-head">
|
||||
<view class="clip-title">📝 检测到剪贴板文字</view>
|
||||
<view class="clip-count">{{clipCount}} 字 · {{clipLines}} 行</view>
|
||||
</view>
|
||||
<view class="clip-preview">{{clipPreview}}</view>
|
||||
<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>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{loading}}" class="empty">加载中...</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>
|
||||
<view class="chevron">›</view>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
@@ -0,0 +1,72 @@
|
||||
/* 首页独立样式,大部分复用 app.wxss 全局样式 */
|
||||
|
||||
.clip-card {
|
||||
margin: 20rpx 24rpx 12rpx;
|
||||
padding: 24rpx 26rpx 22rpx;
|
||||
background: linear-gradient(155deg, rgba(108, 122, 224, 0.18), rgba(176, 107, 224, 0.10));
|
||||
border: 1px solid rgba(140, 150, 240, 0.22);
|
||||
border-radius: 24rpx;
|
||||
box-shadow: 0 10rpx 30rpx rgba(0,0,0,0.28);
|
||||
}
|
||||
.clip-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 14rpx;
|
||||
}
|
||||
.clip-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
color: #ececff;
|
||||
letter-spacing: 0.5rpx;
|
||||
}
|
||||
.clip-count {
|
||||
font-size: 22rpx;
|
||||
color: #a0a4d0;
|
||||
background: rgba(108, 122, 224, 0.22);
|
||||
padding: 4rpx 16rpx;
|
||||
border-radius: 999rpx;
|
||||
}
|
||||
.clip-preview {
|
||||
max-height: 260rpx;
|
||||
overflow: hidden;
|
||||
padding: 18rpx 20rpx;
|
||||
background: rgba(0,0,0,0.32);
|
||||
border-radius: 16rpx;
|
||||
font-size: 25rpx;
|
||||
line-height: 1.8;
|
||||
color: #d4d4ee;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
border: 1px solid rgba(255,255,255,0.06);
|
||||
margin-bottom: 18rpx;
|
||||
}
|
||||
.clip-actions {
|
||||
display: flex;
|
||||
gap: 14rpx;
|
||||
justify-content: flex-end;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.clip-btn {
|
||||
padding: 12rpx 24rpx;
|
||||
font-size: 25rpx;
|
||||
border-radius: 14rpx;
|
||||
text-align: center;
|
||||
min-width: 160rpx;
|
||||
}
|
||||
.clip-btn-ghost {
|
||||
background: rgba(255,255,255,0.04);
|
||||
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;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 8rpx 20rpx rgba(108, 122, 224, 0.32);
|
||||
}
|
||||
Reference in New Issue
Block a user