ui: responsive proportions - implicit column overflow fixes, meta grid single-col on mobile, consistent page max-widths, admin rows wrap

This commit is contained in:
root
2026-10-17 11:28:32 +08:00
parent 5ad9e8db64
commit 58db8652c8
21 changed files with 55 additions and 55 deletions
+1 -1
View File
@@ -109,7 +109,7 @@ export function DynamicForm({ fields, value, onChange, taxonomies }: {
return ( return (
<div className="grid grid-cols-1 sm:grid-cols-2 gap-x-3 gap-y-3.5"> <div className="grid grid-cols-1 sm:grid-cols-2 gap-x-3 gap-y-3.5">
{fields.map(f => ( {fields.map(f => (
<div key={f.key} className={f.widget === 'textarea' ? 'col-span-2' : ''}> <div key={f.key} className={f.widget === 'textarea' ? 'sm:col-span-2' : ''}>
{renderField(f)} {renderField(f)}
</div> </div>
))} ))}
@@ -147,7 +147,7 @@ export function RecordDetailDialog({ open, onClose, footprintId, onEdit }: {
{/* 事项要素:时间/组织方 + 类型专属字段(按模板定义顺序 = 重要性顺序) */} {/* 事项要素:时间/组织方 + 类型专属字段(按模板定义顺序 = 重要性顺序) */}
<Section title="事项要素"> <Section title="事项要素">
<div className="grid grid-cols-2 gap-x-4 gap-y-2 rounded-md border border-line bg-surface-2 px-3 py-2.5 text-[12.5px]"> <div className="grid grid-cols-1 gap-x-4 gap-y-2 rounded-md border border-line bg-surface-2 px-3 py-2.5 text-[12.5px] sm:grid-cols-2">
<MetaItem label="时间" value={fmtRange(d.start_date, d.end_date) || '—'} /> <MetaItem label="时间" value={fmtRange(d.start_date, d.end_date) || '—'} />
<MetaItem label="组织方" value={d.organizer || '—'} /> <MetaItem label="组织方" value={d.organizer || '—'} />
{fields.map(f => <MetaItem key={f.label} label={f.label} value={f.value} />)} {fields.map(f => <MetaItem key={f.label} label={f.label} value={f.value} />)}
+3 -3
View File
@@ -79,7 +79,7 @@ export function AdminPage() {
}; };
return ( return (
<div className="mx-auto max-w-[1080px] px-6 py-5"> <div className="mx-auto max-w-[1080px] px-3 py-4 sm:px-6 sm:py-5">
<div className="mb-4 flex items-center justify-between"> <div className="mb-4 flex items-center justify-between">
<h2 className="text-[15px] font-semibold text-ink-1">管理</h2> <h2 className="text-[15px] font-semibold text-ink-1">管理</h2>
<Button variant="secondary" onClick={() => { void qc.invalidateQueries(); }}> <Button variant="secondary" onClick={() => { void qc.invalidateQueries(); }}>
@@ -139,7 +139,7 @@ export function AdminPage() {
{filtered.length ? ( {filtered.length ? (
<div className="divide-y divide-line max-h-[430px] overflow-y-auto"> <div className="divide-y divide-line max-h-[430px] overflow-y-auto">
{filtered.map((i) => ( {filtered.map((i) => (
<div key={i.id} className="flex items-center gap-2 px-4 py-2.5 text-[12.5px]"> <div key={i.id} className="flex flex-wrap items-center gap-2 px-4 py-2.5 text-[12.5px]">
<button <button
className="num shrink-0 cursor-pointer rounded-sm bg-surface-3 px-1.5 py-0.5 font-mono text-[12px] text-ink-1 transition-colors hover:bg-brand-subtle hover:text-brand-text" className="num shrink-0 cursor-pointer rounded-sm bg-surface-3 px-1.5 py-0.5 font-mono text-[12px] text-ink-1 transition-colors hover:bg-brand-subtle hover:text-brand-text"
title="点击复制" title="点击复制"
@@ -218,7 +218,7 @@ export function AdminPage() {
{users.length ? ( {users.length ? (
<div className="divide-y divide-line max-h-[520px] overflow-y-auto"> <div className="divide-y divide-line max-h-[520px] overflow-y-auto">
{users.map((u) => ( {users.map((u) => (
<div key={u.id} className="flex items-center gap-2 px-4 py-2.5 text-[12.5px]"> <div key={u.id} className="flex flex-wrap items-center gap-2 px-4 py-2.5 text-[12.5px]">
<span className="font-medium text-ink-1">{u.username}</span> <span className="font-medium text-ink-1">{u.username}</span>
{u.is_admin && <Badge color="var(--brand-text)">管理员</Badge>} {u.is_admin && <Badge color="var(--brand-text)">管理员</Badge>}
<span className="num ml-auto text-ink-3">{u.devices} 设备 · {u.records} 记录 · {fmtSize(u.attachment_bytes)}</span> <span className="num ml-auto text-ink-3">{u.devices} 设备 · {u.records} 记录 · {fmtSize(u.attachment_bytes)}</span>
+1 -1
View File
@@ -124,7 +124,7 @@ export function DashboardPage() {
<CardHeader title="类型分布" /> <CardHeader title="类型分布" />
<CardBody><div ref={typeRef} className="h-[220px]" /></CardBody> <CardBody><div ref={typeRef} className="h-[220px]" /></CardBody>
</Card> </Card>
<Card className="col-span-2"> <Card className="lg:col-span-2">
<CardHeader title="级别分布" /> <CardHeader title="级别分布" />
<CardBody><div ref={levelRef} className="h-[200px]" /></CardBody> <CardBody><div ref={levelRef} className="h-[200px]" /></CardBody>
</Card> </Card>
+1 -1
View File
@@ -85,7 +85,7 @@ export function RecordsPage() {
const openDetail = (id: number) => { setDetailId(id); setDetailOpen(true); }; const openDetail = (id: number) => { setDetailId(id); setDetailOpen(true); };
return ( return (
<div className="px-3 py-4 sm:px-6 sm:py-5"> <div className="mx-auto max-w-[1280px] px-3 py-4 sm:px-6 sm:py-5">
<div className="mb-3 flex flex-wrap items-center gap-2"> <div className="mb-3 flex flex-wrap items-center gap-2">
<Input <Input
className="w-[220px]" className="w-[220px]"
+1 -1
View File
@@ -168,7 +168,7 @@ function TypeEditor({ source, onClose, onSaved }: { source: FootprintType | null
} }
> >
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div className="grid grid-cols-[1fr_120px_100px] gap-3"> <div className="grid grid-cols-1 sm:grid-cols-[1fr_120px_100px] gap-3">
<Field label="名称" required> <Field label="名称" required>
<Input value={name} onChange={e => setName(e.target.value)} placeholder="如:学科竞赛" /> <Input value={name} onChange={e => setName(e.target.value)} placeholder="如:学科竞赛" />
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-D1QXTKzW.js";import{C as c,a as d,b as x}from"./card-6oyQXitQ.js";import{u as N,i as u,a as m}from"./useChart-QAOCakHi.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:"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-Dw4WDG05.js";import{C as c,a as d,b as x}from"./card-TX22Y0l9.js";import{u as N,i as u,a as m}from"./useChart-CjbsPbyQ.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 A,f as I,j as e,E as O,w as $,u as k,d as S,r as u,g as y,x as H,y as W,B as g,P as q,C as Q,z as J,A as T,D as U,m as N,l as G,T as E,I as j,F as Y,H as C,J as B,K as V}from"./index-D1QXTKzW.js";import{C as f,a as z,b as _}from"./card-6oyQXitQ.js";import{u as X,i as M,a as L}from"./useChart-QAOCakHi.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 A,f as I,j as e,E as O,w as $,u as k,d as S,r as u,g as y,x as H,y as W,B as g,P as q,C as Q,z as J,A as T,D as U,m as N,l as G,T as E,I as j,F as Y,H as C,J as B,K as V}from"./index-Dw4WDG05.js";import{C as f,a as z,b as _}from"./card-TX22Y0l9.js";import{u as X,i as M,a as L}from"./useChart-CjbsPbyQ.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 * @license lucide-react v0.475.0 - ISC
* *
* This source code is licensed under the ISC license. * This source code is licensed under the ISC license.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
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{j as s,a}from"./index-D1QXTKzW.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-Dw4WDG05.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
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-D1QXTKzW.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-Dw4WDG05.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};
+2 -2
View File
@@ -8,8 +8,8 @@
<link rel="icon" href="/icons/pwa-192.png" /> <link rel="icon" href="/icons/pwa-192.png" />
<link rel="apple-touch-icon" href="/icons/pwa-192.png" /> <link rel="apple-touch-icon" href="/icons/pwa-192.png" />
<title>足迹 Footprints</title> <title>足迹 Footprints</title>
<script type="module" crossorigin src="/assets/index-D1QXTKzW.js"></script> <script type="module" crossorigin src="/assets/index-Dw4WDG05.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-7hm-Qv_H.css"> <link rel="stylesheet" crossorigin href="/assets/index-DMRuYB8j.css">
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>