feat: 足迹 Footprints 个人留痕系统

桌面应用(Tauri+React+SQLite):时间线/记录/学年学科成绩梯度/统计/类型与字段模板/附件内容寻址/详情视图/PWA

同步服务端(sync-server):多用户+邀请码/LWW推拉/seq游标/附件去重/Docker部署

多端同步地基:uuid v7/HLC混合逻辑时钟/软删除墓碑/sync_oplog,Node与Rust双端一致并通过自动化测试
This commit is contained in:
root
2026-10-16 13:53:15 +08:00
commit 42ca57abfd
107 changed files with 234676 additions and 0 deletions
+50
View File
@@ -0,0 +1,50 @@
import { useEffect, useRef, useState } from 'react';
import * as echarts from 'echarts/core';
import type { EChartsType } from 'echarts/core';
export function useThemeTick() {
const [tick, setTick] = useState(0);
useEffect(() => {
const handler = () => setTick((t) => t + 1);
window.addEventListener('fp-theme-change', handler);
return () => window.removeEventListener('fp-theme-change', handler);
}, []);
return tick;
}
export function inkColor(name: '1' | '2' | '3'): string {
return getComputedStyle(document.documentElement).getPropertyValue('--text-' + name).trim() || '#454e5c';
}
// 图表 hook:回调式 ref —— 容器挂载(含条件渲染后出现)即初始化并立即绘制当前数据;
// deps 变化时增量更新;容器卸载时销毁实例。
export function useChart(build: () => echarts.EChartsCoreOption, deps: unknown[]) {
const chartRef = useRef<EChartsType | null>(null);
const [el, setEl] = useState<HTMLDivElement | null>(null);
const buildRef = useRef(build);
buildRef.current = build;
// 挂载/重挂载:初始化实例 + 首次绘制
useEffect(() => {
if (!el) return;
const chart = echarts.init(el);
chartRef.current = chart;
chart.setOption(buildRef.current(), { notMerge: true });
const ro = new ResizeObserver(() => chart.resize());
ro.observe(el);
return () => {
ro.disconnect();
chart.dispose();
chartRef.current = null;
};
}, [el]);
// 数据/主题变化:增量更新
useEffect(() => {
chartRef.current?.setOption(buildRef.current(), { notMerge: true });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [el, ...deps]);
const setRef = (node: HTMLDivElement | null) => setEl(node);
return setRef;
}