feat(admin): demo account rebuild button + user asset registry (photo keys cascade-deleted with user)

This commit is contained in:
root
2026-10-17 13:59:18 +08:00
parent 6a97590f90
commit b48c36fdb3
18 changed files with 156 additions and 88 deletions
+10
View File
@@ -40,6 +40,16 @@ CREATE TABLE IF NOT EXISTS tokens (
expires_at TEXT NOT NULL
);
-- 用户图床资产登记:头像等直传 COS 的文件 key 归账到用户,删除用户时级联清理
CREATE TABLE IF NOT EXISTS user_assets (
user_id TEXT NOT NULL,
kind TEXT NOT NULL,
key TEXT NOT NULL,
url TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now','localtime')),
PRIMARY KEY (user_id, kind, key)
);
-- 每行最新状态(按 user + table + row_uuid 唯一,LWW 胜者)
CREATE TABLE IF NOT EXISTS current (
user_id TEXT NOT NULL,
+51 -29
View File
@@ -28,6 +28,42 @@ async function lskyUpload(buf, fileName, mime) {
return { url, thumb_url: sanitizeImageUrl(j?.data?.thumbnail_url) || undefined, key: j.data.key };
}
async function deleteUserCascade(db, target) {
// 图床远端:附件 remote keys + 用户资产(头像等)
let remote = 0;
if (LSKY_API && LSKY_TOKEN) {
try {
const atts = commandsFor(target.id).getDb().prepare('SELECT remote_key, remote_thumb_key FROM attachment WHERE deleted_at IS NULL').all();
for (const a of atts) {
for (const k of [a.remote_key, a.remote_thumb_key]) {
if (k) { try { await lskyDelete(k); remote++; } catch {} }
}
}
} catch {}
try {
const assets = db.prepare('SELECT key FROM user_assets WHERE user_id=?').all(target.id);
for (const a of assets) { try { await lskyDelete(a.key); remote++; } catch {} }
} catch {}
}
try { db.prepare('DELETE FROM user_assets WHERE user_id=?').run(target.id); } catch {}
try { commandsFor(target.id).close(); } catch {}
userCommands.delete(target.id);
if (demoUserId === target.id) demoUserId = undefined;
for (const f of [target.id + '.db', target.id + '.db-wal', target.id + '.db-shm']) {
try { fs.rmSync(path.join(USERS_DIR, f), { force: true }); } catch {}
}
try { fs.rmSync(path.join(USERS_DIR, String(target.id)), { recursive: true, force: true }); } catch {}
db.exec('BEGIN');
try {
db.prepare('DELETE FROM tokens WHERE user_id=?').run(target.id);
db.prepare('DELETE FROM current WHERE user_id=?').run(target.id);
db.prepare('DELETE FROM attachments WHERE user_id=?').run(target.id);
db.prepare('DELETE FROM devices WHERE user_id=?').run(target.id);
db.prepare('DELETE FROM users WHERE id=?').run(target.id);
db.exec('COMMIT');
} catch (e) { db.exec('ROLLBACK'); throw e; }
return remote;
}
async function lskyDelete(key) {
const r = await fetch(LSKY_API + '/images/' + encodeURIComponent(key), {
method: 'DELETE', headers: { authorization: 'Bearer ' + LSKY_TOKEN, accept: 'application/json' },
@@ -159,7 +195,7 @@ const server = http.createServer(async (req, res) => {
}
// ---- 管理(仅 is_admin 登录会话) ----
if (route === '/admin/users' || route === '/admin/users/delete' || route === '/admin/invites' || route === '/admin/demo-seed' || route === '/admin/attachments/sync-lsky' || route.startsWith('/admin/invites/')) {
if (route === '/admin/users' || route === '/admin/users/delete' || route === '/admin/demo-rebuild' || route === '/admin/invites' || route === '/admin/demo-seed' || route === '/admin/attachments/sync-lsky' || route.startsWith('/admin/invites/')) {
let auth;
try { auth = authUser(req); }
catch (e) { return send(e.code || 401, false, e.message); }
@@ -187,36 +223,20 @@ const server = http.createServer(async (req, res) => {
if (target.is_admin && db.prepare('SELECT COUNT(*) AS c FROM users WHERE is_admin=1').get().c <= 1) {
return send(400, false, '不能删除最后一位管理员');
}
// 图床远端文件(尽力而为,不阻塞删除)
let remote = 0;
try {
const atts = commandsFor(target.id).getDb().prepare('SELECT remote_key, remote_thumb_key FROM attachment WHERE deleted_at IS NULL').all();
for (const a of atts) {
for (const k of [a.remote_key, a.remote_thumb_key]) {
if (k && LSKY_API && LSKY_TOKEN) { try { await lskyDelete(k); remote++; } catch {} }
}
}
} catch {}
// 关闭库句柄并删除用户库 + 附件目录
try { commandsFor(target.id).close(); } catch {}
userCommands.delete(target.id);
if (demoUserId === target.id) demoUserId = undefined;
for (const f of [target.id + '.db', target.id + '.db-wal', target.id + '.db-shm']) {
try { fs.rmSync(path.join(USERS_DIR, f), { force: true }); } catch {}
}
try { fs.rmSync(path.join(USERS_DIR, String(target.id)), { recursive: true, force: true }); } catch {}
// 认证库级联清理
db.exec('BEGIN');
try {
db.prepare('DELETE FROM tokens WHERE user_id=?').run(target.id);
db.prepare('DELETE FROM current WHERE user_id=?').run(target.id);
db.prepare('DELETE FROM attachments WHERE user_id=?').run(target.id);
db.prepare('DELETE FROM devices WHERE user_id=?').run(target.id);
db.prepare('DELETE FROM users WHERE id=?').run(target.id);
db.exec('COMMIT');
} catch (e) { db.exec('ROLLBACK'); throw e; }
const remote = await deleteUserCascade(db, target);
console.log('[footprints-sync] 用户已删除:', target.username, '(图床文件 ' + remote + ' 个)');
return send(200, true, { ok: true, username: target.username, remote_deleted: remote });
// 重建演示账号:级联删除现有 demo → 重新注册 → 重新播种
if (req.method === 'POST' && route === '/admin/demo-rebuild') {
const existing = db.prepare('SELECT id, username FROM users WHERE username=?').get('demo');
if (existing && existing.id !== auth.user_id) await deleteUserCascade(db, existing);
const { register } = await import('./auth.mjs');
const uid = register({ username: 'demo', password: 'demo1234', device_name: 'seed-demo', seed: true }).user_id;
const seeded = commandsFor(uid).handleCommand('demo_seed', {});
console.log('[footprints-sync] demo 账号已重建:', JSON.stringify(seeded));
return send(200, true, { ok: true, seeded });
}
}
if (req.method === 'GET' && route === '/admin/invites') {
@@ -369,6 +389,8 @@ const server = http.createServer(async (req, res) => {
const buf = Buffer.from(body.data_b64 || '', 'base64');
if (!buf.length) return send(400, false, '空文件');
const up = await lskyUpload(buf, body.file_name || 'profile-photo.png', body.mime || 'image/png');
try { getDb().prepare('INSERT OR REPLACE INTO user_assets (user_id, kind, key, url) VALUES (?,?,?,?)')
.run(auth.user_id, 'photo', up.key || sanitizeImageUrl(up.url), sanitizeImageUrl(up.url)); } catch {}
return send(200, true, { url: sanitizeImageUrl(up.url) });
} catch (e) {
return send(500, false, '证件照上传失败: ' + e.message);
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{r as g,f as v,j as t,S as j,t as S}from"./index-BfkfBvd0.js";import{C as c,a as d,b as x}from"./card-vt70dxDz.js";import{u as N,i as u,a as m}from"./useChart-DWYcRWwu.js";import{u as k,i as w,a as C,b as L,c as R,d as $,e as z,f as T}from"./echarts-BIWefrIc.js";k([w,C,L,R,$,z,T]);function q(){const[l,n]=g.useState(""),i=N(),e=v({queryKey:["stats",l||void 0],queryFn:()=>S(l?Number(l):void 0)}).data,p=u("2"),r=u("3"),h=m(()=>({grid:{left:34,right:12,top:14,bottom:24},tooltip:{trigger:"axis"},xAxis:{type:"category",data:(e==null?void 0:e.monthly.map(a=>a.ym.slice(2)))??[],axisLine:{lineStyle:{color:r}},axisTick:{show:!1},axisLabel:{color:r,fontSize:10}},yAxis:{type:"value",splitLine:{lineStyle:{color:"rgba(128,138,155,0.15)"}},axisLabel:{color:r,fontSize:10}},series:[{type:"bar",data:(e==null?void 0:e.monthly.map(a=>a.count))??[],barMaxWidth:18,itemStyle:{color:"#3056d3",borderRadius:[3,3,0,0]}}]}),[e,i]),b=m(()=>{const a=((e==null?void 0:e.by_type)??[]).filter(s=>s.count>0);return{tooltip:{trigger:"item"},legend:{orient:"vertical",right:4,top:"middle",textStyle:{color:p,fontSize:11},itemWidth:10,itemHeight:10},series:[{type:"pie",radius:["48%","72%"],center:["34%","50%"],label:{show:!1},data:a.map(s=>({name:s.label,value:s.count,itemStyle:{color:s.color||"#64748b"}}))}]}},[e,i]),y=m(()=>{const a=((e==null?void 0:e.by_level)??[]).filter(s=>s.count>0).slice().reverse();return{grid:{left:64,right:24,top:8,bottom:22},tooltip:{trigger:"axis"},xAxis:{type:"value",splitLine:{lineStyle:{color:"rgba(128,138,155,0.15)"}},axisLabel:{color:r,fontSize:10}},yAxis:{type:"category",data:a.map(s=>s.label),axisLine:{show:!1},axisTick:{show:!1},axisLabel:{color:p,fontSize:11}},series:[{type:"bar",data:a.map(s=>({value:s.count,itemStyle:{color:s.color||"#3056d3",borderRadius:[0,3,3,0]}})),barMaxWidth:14}]}},[e,i]),f=((e==null?void 0:e.metrics)??[]).slice(0,3);return t.jsxs("div",{className:"mx-auto max-w-[1080px] px-3 py-4 sm:px-6 sm:py-5",children:[t.jsxs("div",{className:"mb-4 flex flex-wrap items-center justify-between gap-3",children:[t.jsxs("div",{className:"flex flex-wrap gap-3",children:[t.jsx(o,{label:"总记录",value:(e==null?void 0:e.total)??0}),t.jsx(o,{label:l?l+" 年新增":"本年新增",value:(e==null?void 0:e.total_this_year)??0}),t.jsx(o,{label:"附件总数",value:(e==null?void 0:e.attachment_total)??0}),f.map(a=>t.jsx(o,{label:a.label,value:a.value,unit:a.unit??""},a.key))]}),t.jsx(j,{className:"w-[120px]",value:l,onChange:a=>n(a.target.value),options:[{value:"",label:"全部年份"},...((e==null?void 0:e.years)??[]).map(a=>({value:String(a),label:a+" 年"}))]})]}),!(e!=null&&e.metrics.length)&&t.jsx("div",{className:"mb-4 rounded-md border border-dashed border-line px-4 py-2 text-[12px] text-ink-3",children:"提示:在「统计」配置中注册指标(如志愿时长、读书本数),带单位的数值字段即可在此量化展示。"}),t.jsxs("div",{className:"grid grid-cols-1 lg:grid-cols-2 gap-4",children:[t.jsxs(c,{children:[t.jsx(d,{title:"近 12 个月活跃度"}),t.jsx(x,{children:t.jsx("div",{ref:h,className:"h-[220px]"})})]}),t.jsxs(c,{children:[t.jsx(d,{title:"类型分布"}),t.jsx(x,{children:t.jsx("div",{ref:b,className:"h-[220px]"})})]}),t.jsxs(c,{className:"lg:col-span-2",children:[t.jsx(d,{title:"级别分布"}),t.jsx(x,{children:t.jsx("div",{ref:y,className:"h-[200px]"})})]})]})]})}function o({label:l,value:n,unit:i}){return t.jsxs("div",{className:"min-w-[120px] rounded-lg border border-line bg-surface px-4 py-3 shadow-card",children:[t.jsx("div",{className:"text-[12px] text-ink-3",children:l}),t.jsxs("div",{className:"num mt-0.5 text-[22px] font-semibold leading-tight text-ink-1",children:[n,i&&t.jsx("span",{className:"ml-1 text-[12px] font-normal text-ink-3",children:i})]})]})}export{q as DashboardPage};
import{r as g,f as v,j as t,S as j,t as S}from"./index-CIeBS4lG.js";import{C as c,a as d,b as x}from"./card-_C0OnRIz.js";import{u as N,i as u,a as m}from"./useChart-Cowo5MEh.js";import{u as k,i as w,a as C,b as L,c as R,d as $,e as z,f as T}from"./echarts-BIWefrIc.js";k([w,C,L,R,$,z,T]);function q(){const[l,n]=g.useState(""),i=N(),e=v({queryKey:["stats",l||void 0],queryFn:()=>S(l?Number(l):void 0)}).data,p=u("2"),r=u("3"),h=m(()=>({grid:{left:34,right:12,top:14,bottom:24},tooltip:{trigger:"axis"},xAxis:{type:"category",data:(e==null?void 0:e.monthly.map(a=>a.ym.slice(2)))??[],axisLine:{lineStyle:{color:r}},axisTick:{show:!1},axisLabel:{color:r,fontSize:10}},yAxis:{type:"value",splitLine:{lineStyle:{color:"rgba(128,138,155,0.15)"}},axisLabel:{color:r,fontSize:10}},series:[{type:"bar",data:(e==null?void 0:e.monthly.map(a=>a.count))??[],barMaxWidth:18,itemStyle:{color:"#3056d3",borderRadius:[3,3,0,0]}}]}),[e,i]),b=m(()=>{const a=((e==null?void 0:e.by_type)??[]).filter(s=>s.count>0);return{tooltip:{trigger:"item"},legend:{orient:"vertical",right:4,top:"middle",textStyle:{color:p,fontSize:11},itemWidth:10,itemHeight:10},series:[{type:"pie",radius:["48%","72%"],center:["34%","50%"],label:{show:!1},data:a.map(s=>({name:s.label,value:s.count,itemStyle:{color:s.color||"#64748b"}}))}]}},[e,i]),y=m(()=>{const a=((e==null?void 0:e.by_level)??[]).filter(s=>s.count>0).slice().reverse();return{grid:{left:64,right:24,top:8,bottom:22},tooltip:{trigger:"axis"},xAxis:{type:"value",splitLine:{lineStyle:{color:"rgba(128,138,155,0.15)"}},axisLabel:{color:r,fontSize:10}},yAxis:{type:"category",data:a.map(s=>s.label),axisLine:{show:!1},axisTick:{show:!1},axisLabel:{color:p,fontSize:11}},series:[{type:"bar",data:a.map(s=>({value:s.count,itemStyle:{color:s.color||"#3056d3",borderRadius:[0,3,3,0]}})),barMaxWidth:14}]}},[e,i]),f=((e==null?void 0:e.metrics)??[]).slice(0,3);return t.jsxs("div",{className:"mx-auto max-w-[1080px] px-3 py-4 sm:px-6 sm:py-5",children:[t.jsxs("div",{className:"mb-4 flex flex-wrap items-center justify-between gap-3",children:[t.jsxs("div",{className:"flex flex-wrap gap-3",children:[t.jsx(o,{label:"总记录",value:(e==null?void 0:e.total)??0}),t.jsx(o,{label:l?l+" 年新增":"本年新增",value:(e==null?void 0:e.total_this_year)??0}),t.jsx(o,{label:"附件总数",value:(e==null?void 0:e.attachment_total)??0}),f.map(a=>t.jsx(o,{label:a.label,value:a.value,unit:a.unit??""},a.key))]}),t.jsx(j,{className:"w-[120px]",value:l,onChange:a=>n(a.target.value),options:[{value:"",label:"全部年份"},...((e==null?void 0:e.years)??[]).map(a=>({value:String(a),label:a+" 年"}))]})]}),!(e!=null&&e.metrics.length)&&t.jsx("div",{className:"mb-4 rounded-md border border-dashed border-line px-4 py-2 text-[12px] text-ink-3",children:"提示:在「统计」配置中注册指标(如志愿时长、读书本数),带单位的数值字段即可在此量化展示。"}),t.jsxs("div",{className:"grid grid-cols-1 lg:grid-cols-2 gap-4",children:[t.jsxs(c,{children:[t.jsx(d,{title:"近 12 个月活跃度"}),t.jsx(x,{children:t.jsx("div",{ref:h,className:"h-[220px]"})})]}),t.jsxs(c,{children:[t.jsx(d,{title:"类型分布"}),t.jsx(x,{children:t.jsx("div",{ref:b,className:"h-[220px]"})})]}),t.jsxs(c,{className:"lg:col-span-2",children:[t.jsx(d,{title:"级别分布"}),t.jsx(x,{children:t.jsx("div",{ref:y,className:"h-[200px]"})})]})]})]})}function o({label:l,value:n,unit:i}){return t.jsxs("div",{className:"min-w-[120px] rounded-lg border border-line bg-surface px-4 py-3 shadow-card",children:[t.jsx("div",{className:"text-[12px] text-ink-3",children:l}),t.jsxs("div",{className:"num mt-0.5 text-[22px] font-semibold leading-tight text-ink-1",children:[n,i&&t.jsx("span",{className:"ml-1 text-[12px] font-normal text-ink-3",children:i})]})]})}export{q as DashboardPage};
@@ -1,4 +1,4 @@
import{c as R,f as I,j as e,E as O,w as $,u as N,d as f,r as u,g as v,x as H,y as W,B as b,P as q,C as L,z as J,A as T,D as U,m as k,l as F,T as z,I as g,F as Y,H as S,J as E,K as V}from"./index-BfkfBvd0.js";import{C,a as M,b as _}from"./card-vt70dxDz.js";import{u as X,i as Q,a as K}from"./useChart-DWYcRWwu.js";import{u as Z,i as ee,b as se,a as te,c as ae,d as re,e as ne,f as le}from"./echarts-BIWefrIc.js";/**
import{c as R,f as I,j as e,E as O,w as $,u as N,d as f,r as u,g as v,x as H,y as W,B as b,P as q,C as L,z as J,A as T,D as U,m as k,l as F,T as z,I as g,F as Y,H as S,J as E,K as V}from"./index-CIeBS4lG.js";import{C,a as M,b as _}from"./card-_C0OnRIz.js";import{u as X,i as Q,a as K}from"./useChart-Cowo5MEh.js";import{u as Z,i as ee,b as se,a as te,c as ae,d as re,e as ne,f as le}from"./echarts-BIWefrIc.js";/**
* @license lucide-react v0.475.0 - ISC
*
* This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
import{c as E,u as P,d as S,r as p,f as F,g as q,j as e,B as f,l as B,aa as L,F as z,H as k,S as M,I as Q,ab as _,ac as K,ad as v,ae as T}from"./index-BfkfBvd0.js";/**
import{c as E,u as P,d as S,r as p,f as F,g as q,j as e,B as f,l as B,ab as L,F as z,H as k,S as M,I as Q,ac as _,ad as K,ae as v,af as T}from"./index-CIeBS4lG.js";/**
* @license lucide-react v0.475.0 - ISC
*
* This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
import{c as fe,j as e,a as N,r as s,b as he,u as ve,d as ge,e as je,f as L,g as ye,I as be,S as k,B as w,P as Q,h as te,L as se,G as ae,R as Ne,i as ne,k as ke,l as we,T as Ce,E as le,m as Ee,n as Se,o as _e,C as ze,p as qe,q as Le,s as Re,t as De,v as Ie}from"./index-BfkfBvd0.js";/**
import{c as fe,j as e,a as N,r as s,b as he,u as ve,d as ge,e as je,f as L,g as ye,I as be,S as k,B as w,P as Q,h as te,L as se,G as ae,R as Ne,i as ne,k as ke,l as we,T as Ce,E as le,m as Ee,n as Se,o as _e,C as ze,p as qe,q as Le,s as Re,t as De,v as Ie}from"./index-CIeBS4lG.js";/**
* @license lucide-react v0.475.0 - ISC
*
* This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
import{c as L,d as oe,f as Q,r as z,j as e,B as E,af as re,H as O,S as G,ab as Y,P as ee,a7 as ce,ad as U,I as H,T as de,ae as me,v as xe}from"./index-BfkfBvd0.js";import{C as Z,a as X,b as J}from"./card-vt70dxDz.js";/**
import{c as L,d as oe,f as Q,r as z,j as e,B as E,ag as re,H as O,S as G,ac as Y,P as ee,a8 as ce,ae as U,I as H,T as de,af as me,v as xe}from"./index-CIeBS4lG.js";import{C as Z,a as X,b as J}from"./card-_C0OnRIz.js";/**
* @license lucide-react v0.475.0 - ISC
*
* This source code is licensed under the ISC license.
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
import{c as D,u as K,d as F,f as L,r,g as I,j as e,B as p,P as q,M as O,m as _,l as H,T as P,C as R,N as G,F as U,H as i,I as j,O as W,S as z,Q as J,U as V,q as X}from"./index-BfkfBvd0.js";import{C as Y}from"./card-vt70dxDz.js";/**
import{c as D,u as K,d as F,f as L,r,g as I,j as e,B as p,P as q,M as O,m as _,l as H,T as P,C as R,N as G,F as U,H as i,I as j,O as W,S as z,Q as J,U as V,q as X}from"./index-CIeBS4lG.js";import{C as Y}from"./card-_C0OnRIz.js";/**
* @license lucide-react v0.475.0 - ISC
*
* This source code is licensed under the ISC license.
@@ -1 +1 @@
import{j as s,a}from"./index-BfkfBvd0.js";function i({className:r,...e}){return s.jsx("div",{className:a("rounded-lg border border-line bg-surface shadow-card",r),...e})}function t({title:r,extra:e,className:n}){return s.jsxs("div",{className:a("flex min-h-11 flex-wrap items-center justify-between gap-2 border-b border-line px-4 py-1.5",n),children:[s.jsx("h3",{className:"text-[13px] font-semibold text-ink-1",children:r}),e&&s.jsx("div",{className:"flex flex-wrap items-center gap-2",children:e})]})}function c({className:r,...e}){return s.jsx("div",{className:a("p-4",r),...e})}export{i as C,t as a,c as b};
import{j as s,a}from"./index-CIeBS4lG.js";function i({className:r,...e}){return s.jsx("div",{className:a("rounded-lg border border-line bg-surface shadow-card",r),...e})}function t({title:r,extra:e,className:n}){return s.jsxs("div",{className:a("flex min-h-11 flex-wrap items-center justify-between gap-2 border-b border-line px-4 py-1.5",n),children:[s.jsx("h3",{className:"text-[13px] font-semibold text-ink-1",children:r}),e&&s.jsx("div",{className:"flex flex-wrap items-center gap-2",children:e})]})}function c({className:r,...e}){return s.jsx("div",{className:a("p-4",r),...e})}export{i as C,t as a,c as b};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{r}from"./index-BfkfBvd0.js";import{g as f}from"./echarts-BIWefrIc.js";function p(){const[n,o]=r.useState(0);return r.useEffect(()=>{const s=()=>o(t=>t+1);return window.addEventListener("fp-theme-change",s),()=>window.removeEventListener("fp-theme-change",s)},[]),n}function d(n){return getComputedStyle(document.documentElement).getPropertyValue("--text-"+n).trim()||"#454e5c"}function h(n,o){const s=r.useRef(null),[t,i]=r.useState(null),u=r.useRef(n);return u.current=n,r.useEffect(()=>{if(!t)return;const e=f(t);s.current=e,e.setOption(u.current(),{notMerge:!0});const c=new ResizeObserver(()=>e.resize());return c.observe(t),()=>{c.disconnect(),e.dispose(),s.current=null}},[t]),r.useEffect(()=>{var e;(e=s.current)==null||e.setOption(u.current(),{notMerge:!0})},[t,...o]),e=>i(e)}export{h as a,d as i,p as u};
import{r}from"./index-CIeBS4lG.js";import{g as f}from"./echarts-BIWefrIc.js";function p(){const[n,o]=r.useState(0);return r.useEffect(()=>{const s=()=>o(t=>t+1);return window.addEventListener("fp-theme-change",s),()=>window.removeEventListener("fp-theme-change",s)},[]),n}function d(n){return getComputedStyle(document.documentElement).getPropertyValue("--text-"+n).trim()||"#454e5c"}function h(n,o){const s=r.useRef(null),[t,i]=r.useState(null),u=r.useRef(n);return u.current=n,r.useEffect(()=>{if(!t)return;const e=f(t);s.current=e,e.setOption(u.current(),{notMerge:!0});const c=new ResizeObserver(()=>e.resize());return c.observe(t),()=>{c.disconnect(),e.dispose(),s.current=null}},[t]),r.useEffect(()=>{var e;(e=s.current)==null||e.setOption(u.current(),{notMerge:!0})},[t,...o]),e=>i(e)}export{h as a,d as i,p as u};
+1 -1
View File
@@ -9,7 +9,7 @@
<link rel="icon" href="/icons/pwa-192.png" />
<link rel="apple-touch-icon" href="/icons/pwa-192.png" />
<title>足迹 Footprints</title>
<script type="module" crossorigin src="/assets/index-BfkfBvd0.js"></script>
<script type="module" crossorigin src="/assets/index-CIeBS4lG.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-tIg5eBzC.css">
</head>
<body>