feat(mp, web, calendar): 新增微信收藏夹与日历批量生成功能
- 新增微信小程序版个人收藏夹全套功能,包含聚合首页、收藏列表、编辑、详情页面与配套后端API - 新增web版时光助手门户与个人收藏夹工具,支持与小程序共享后端数据 - 优化日历提醒工具:替换本地bootstrap资源为公共CDN版本,新增批量生成循环提醒的前端界面与后端逻辑,新增数据库batch_id字段,更新鉴权配置文件 - 补充项目配置、站点地图、调试诊断脚本等配套文件 - 完善各模块的页面配置与样式文件
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
(function () {
|
||||
const isHttps = location.protocol === 'https:'
|
||||
const API_BASE = isHttps ? location.origin : 'http://localhost:8976'
|
||||
|
||||
function buildUrl(path) {
|
||||
if (/^https?:/.test(path)) return path
|
||||
const p = path.startsWith('/') ? path : '/' + path
|
||||
return API_BASE + p
|
||||
}
|
||||
|
||||
async function request(method, path, body, opts) {
|
||||
opts = opts || {}
|
||||
const url = buildUrl(path)
|
||||
const init = {
|
||||
method: method || 'GET',
|
||||
headers: opts.headers || {},
|
||||
credentials: 'same-origin'
|
||||
}
|
||||
if (body !== null && body !== undefined) {
|
||||
if (body instanceof FormData) {
|
||||
init.body = body
|
||||
} else {
|
||||
init.headers['Content-Type'] = 'application/json'
|
||||
init.body = JSON.stringify(body)
|
||||
}
|
||||
}
|
||||
try {
|
||||
const res = await fetch(url, init)
|
||||
const text = await res.text()
|
||||
let data = null
|
||||
try { data = text ? JSON.parse(text) : null } catch { data = { ok: res.ok, raw: text } }
|
||||
if (!res.ok) {
|
||||
const msg = (data && data.error) || ('HTTP ' + res.status)
|
||||
const err = new Error(msg)
|
||||
err.status = res.status
|
||||
err.data = data
|
||||
throw err
|
||||
}
|
||||
return { ok: true, status: res.status, data }
|
||||
} catch (e) {
|
||||
const err = new Error(e.message || '请求失败')
|
||||
err.data = e.data || null
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
const hub = {
|
||||
async meta() {
|
||||
const res = await request('GET', '/api/mp_time_assistant/meta')
|
||||
const payload = (res && res.data) || {}
|
||||
return (payload && payload.ok) ? payload.data || {} : (payload || {})
|
||||
},
|
||||
async getState() {
|
||||
const res = await request('GET', '/api/mp_time_assistant/state')
|
||||
const payload = (res && res.data) || {}
|
||||
return (payload && payload.ok) ? payload.data || {} : (payload || {})
|
||||
},
|
||||
async setActiveKit(kitId) {
|
||||
const res = await request('PUT', '/api/mp_time_assistant/state/active-kit', { kitId })
|
||||
const payload = (res && res.data) || {}
|
||||
return (payload && payload.ok) ? payload.data || {} : (payload || {})
|
||||
}
|
||||
}
|
||||
|
||||
const personalCollect = {
|
||||
async list(keyword) {
|
||||
const q = new URLSearchParams()
|
||||
if (keyword) q.set('keyword', keyword)
|
||||
const suffix = q.toString() ? '?' + q.toString() : ''
|
||||
const res = await request('GET', '/api/mp_time_assistant/kits/personal_collect/items' + suffix)
|
||||
const payload = (res && res.data) || {}
|
||||
if (payload && payload.ok) return payload.data || []
|
||||
return Array.isArray(payload) ? payload : []
|
||||
},
|
||||
async get(id) {
|
||||
const res = await request('GET', '/api/mp_time_assistant/kits/personal_collect/items/' + encodeURIComponent(id))
|
||||
const payload = (res && res.data) || {}
|
||||
return (payload && payload.ok) ? (payload.data || null) : (payload || null)
|
||||
},
|
||||
async create(payload) {
|
||||
const res = await request('POST', '/api/mp_time_assistant/kits/personal_collect/items', payload)
|
||||
const body = (res && res.data) || {}
|
||||
if (body && body.ok) return body.data || body
|
||||
throw new Error((body && body.error) || '创建失败')
|
||||
},
|
||||
async update(payload) {
|
||||
const res = await request('PUT', '/api/mp_time_assistant/kits/personal_collect/items', payload)
|
||||
const body = (res && res.data) || {}
|
||||
if (body && body.ok) return body.data || body
|
||||
throw new Error((body && body.error) || '更新失败')
|
||||
},
|
||||
async remove(id) {
|
||||
const q = new URLSearchParams({ id })
|
||||
const res = await request('DELETE', '/api/mp_time_assistant/kits/personal_collect/items?' + q.toString())
|
||||
const body = (res && res.data) || {}
|
||||
if (body && body.ok) return body
|
||||
throw new Error((body && body.error) || '删除失败')
|
||||
},
|
||||
async uploadFile(file, groupId, onProgress) {
|
||||
const form = new FormData()
|
||||
form.append('file', file)
|
||||
if (groupId) form.append('groupId', groupId)
|
||||
const url = buildUrl('/api/mp_time_assistant/kits/personal_collect/upload')
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const xhr = new XMLHttpRequest()
|
||||
xhr.open('POST', url)
|
||||
xhr.withCredentials = true
|
||||
if (onProgress && typeof onProgress === 'function' && xhr.upload) {
|
||||
xhr.upload.addEventListener('progress', (e) => {
|
||||
if (e.lengthComputable) {
|
||||
onProgress({ loaded: e.loaded, total: e.total, percent: Math.round(e.loaded * 100 / Math.max(1, e.total)) })
|
||||
}
|
||||
})
|
||||
}
|
||||
xhr.onload = () => {
|
||||
try {
|
||||
const data = JSON.parse(xhr.responseText || '{}')
|
||||
if (xhr.status >= 200 && xhr.status < 300 && data && data.ok) {
|
||||
resolve({ ok: true, status: xhr.status, data })
|
||||
} else {
|
||||
const err = new Error((data && data.error) || ('HTTP ' + xhr.status))
|
||||
err.data = data
|
||||
reject(err)
|
||||
}
|
||||
} catch (e) {
|
||||
reject(new Error('响应解析失败: ' + e.message))
|
||||
}
|
||||
}
|
||||
xhr.onerror = () => reject(new Error('网络错误:上传失败'))
|
||||
xhr.ontimeout = () => reject(new Error('上传超时'))
|
||||
xhr.send(form)
|
||||
} catch (e) {
|
||||
reject(e)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const fileUrl = (relPath) => {
|
||||
if (!relPath) return ''
|
||||
if (/^https?:/.test(relPath)) return relPath
|
||||
const p = relPath.startsWith('/') ? relPath : '/' + relPath
|
||||
return buildUrl(p)
|
||||
}
|
||||
|
||||
window.__MPTA_API__ = {
|
||||
API_BASE,
|
||||
request,
|
||||
hub,
|
||||
personalCollect,
|
||||
fileUrl
|
||||
}
|
||||
})()
|
||||
Reference in New Issue
Block a user