Files
WeReadBoard/static/js/weapi.js
T
admin 8af75454a4 feat(web): v2 看板前端接入真实数据接口
前端样式完全沿用 2.0 设计稿,仅替换数据来源。

- templates/index.html 改用设计稿 DOM 结构与类名,引用 styles.css + weapi.js + app.js
- static/css/styles.css 引入 2.0 设计稿样式
- static/js/weapi.js 新增,封装 /api/v2/* 调用为全局 WeApi
- static/js/app.js 重写:保留设计稿全部渲染函数与 CSS 类名,
  数据源由 demo-data.js 静态假数据改为真实接口,
  补充异步加载器、加载/错误状态机与重试按钮
- static/js/demo-data.js 保留为设计稿原始假数据对照
2026-09-11 18:21:06 +08:00

126 lines
3.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ==========================================================================
阅间 · 数据层(v2)
--------------------------------------------------------------------------
把后端 /api/v2/* 接口封装成前端可直接使用的数据结构。
设计稿(Design Demo)原本使用 demo-data.js 里的静态示例数据,
这里替换为真实接口调用,字段名与设计稿保持一致。
========================================================================== */
(function (global) {
'use strict';
const BASE = '/api/v2';
async function request(path, params) {
const url = new URL(BASE + path, global.location.origin);
Object.entries(params || {}).forEach(([key, value]) => {
if (value !== undefined && value !== null && value !== '') url.searchParams.set(key, value);
});
const response = await fetch(url.toString(), { headers: { Accept: 'application/json' } });
let payload = null;
try {
payload = await response.json();
} catch (error) {
payload = null;
}
if (!response.ok) {
const message = (payload && (payload.error || payload.detail)) || `请求失败(${response.status})`;
const err = new Error(message);
err.status = response.status;
err.payload = payload;
throw err;
}
return payload;
}
/* ------------------------------ 工具函数 ------------------------------ */
function durationText(seconds) {
const total = Math.max(0, Math.round(Number(seconds) || 0));
const hours = Math.floor(total / 3600);
const minutes = Math.round((total % 3600) / 60);
return hours ? `${hours}小时${minutes}分钟` : `${minutes}分钟`;
}
function percentText(value) {
const num = Number(value) || 0;
return `${num}%`;
}
/* ------------------------------ 接口封装 ------------------------------ */
const WeApi = {
durationText,
percentText,
status() {
return request('/status');
},
/**
* 书架数据
* @param {string} tab all|reading|done|wish
* @param {{keyword?:string, limit?:number, offset?:number, progress?:boolean}} options
*/
shelf(tab, options) {
const opts = options || {};
return request('/shelf', {
tab: tab || 'all',
keyword: opts.keyword || '',
limit: opts.limit,
offset: opts.offset,
progress: opts.progress ? 1 : 0,
});
},
dashboard(period) {
return request('/dashboard', { period: period || 'week' });
},
notebooks() {
return request('/notebooks');
},
/** bookId 传 'all' 时聚合笔记最多的前 limit 本 */
notes(bookId, limit) {
return request('/notes', { book_id: bookId, limit: limit });
},
book(bookId) {
return request('/book/' + encodeURIComponent(bookId));
},
reviews(bookId, type, count) {
return request(`/book/${encodeURIComponent(bookId)}/reviews`, { type: type || 0, count: count || 10 });
},
highlights(bookId, chapterUid) {
return request(`/book/${encodeURIComponent(bookId)}/highlights`, { chapter_uid: chapterUid || 0 });
},
underlineThoughts(bookId, chapterUid, range) {
return request(`/book/${encodeURIComponent(bookId)}/underline-thoughts`, {
chapter_uid: chapterUid,
range: range,
});
},
thought(reviewId) {
return request('/thought/' + encodeURIComponent(reviewId));
},
archives() {
return request('/archives');
},
recommend(maxIdx) {
return request('/recommend', { maxIdx: maxIdx || 0 });
},
search(keyword, scope) {
return request('/search', { keyword: keyword, scope: scope || 'books' });
},
};
global.WeApi = WeApi;
})(window);