timeout: 8000,
});
+// 生成中国时间(Asia/Shanghai, UTC+8)时间戳 "YYYY-MM-DD HH:MM:SS"。
+// 注意: 必须用固定 +8h 偏移,不能用 toISOString()(那是 UTC),否则
+// 凌晨学习会被记到前一天(时区 bug)。与服务器 Asia/Shanghai 保持一致。
+export function localTimestamp() {
+ const sh = new Date(Date.now() + 8 * 3600 * 1000);
+ const p = (n) => String(n).padStart(2, '0');
+ return `${sh.getUTCFullYear()}-${p(sh.getUTCMonth() + 1)}-${p(sh.getUTCDate())} `
+ + `${p(sh.getUTCHours())}:${p(sh.getUTCMinutes())}:${p(sh.getUTCSeconds())}`;
+}
+
// 统一 API 出口:所有组件应通过 api.xxx 调用,不再各自硬编码 API_BASE 或裸用 axios
export const api = {
getHome: () => http.get('/api/home'),
import React, { useState, useEffect, useCallback, useRef } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
-import { api } from '../api';
+import { api, localTimestamp } from '../api';
import { useOnlineStatus } from '../hooks/useOnlineStatus';
import { getWordsByGroup, putProgress, getLearnableGroups } from '../db';
import { triggerAutoSync } from '../sync';
} else {
if (submittedRef.current) return; // 防重入(按住Enter等场景)
submittedRef.current = true;
- const now = new Date().toISOString().slice(0, 19).replace('T', ' ');
+ const now = localTimestamp();
// 本地优先: 先写本地 dirty (必定保存, 不丢记录)
await putProgress(groupId, 'study', now, true);
// 在线则后台自动同步 (成功清 dirty, 失败保留)
import React, { useState, useEffect, useCallback, useRef } from 'react';
import { useParams, useNavigate, useSearchParams } from 'react-router-dom';
-import { api } from '../api';
+import { api, localTimestamp } from '../api';
import { useOnlineStatus } from '../hooks/useOnlineStatus';
import { getWordsByGroup, putProgress, getLearnableGroups } from '../db';
import { triggerAutoSync } from '../sync';
const finishTest = useCallback(async () => {
if (submittedRef.current) return; // 防重入(按住Enter等场景)
submittedRef.current = true;
- const now = new Date().toISOString().slice(0, 19).replace('T', ' ');
+ const now = localTimestamp();
// 本地优先: 先写本地 dirty (必定保存, 不丢记录)
await putProgress(groupId, activityType, now, true);
// 在线则后台自动同步 (成功清 dirty, 失败保留)