Files
FootPrint/sync-server/www/assets/GradesPage-Dbm013_S.js
T

7 lines
16 KiB
JavaScript

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-BpXGbBE-.js";import{C as f,a as z,b as _}from"./card-DMi0z8Hh.js";import{u as X,i as M,a as L}from"./useChart-D4kPoPsx.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.
* See the LICENSE file in the root directory of this source tree.
*/const ie=[["rect",{width:"16",height:"20",x:"4",y:"2",rx:"2",key:"1nb95v"}],["line",{x1:"8",x2:"16",y1:"6",y2:"6",key:"x4nwl0"}],["line",{x1:"16",x2:"16",y1:"14",y2:"18",key:"wjye3r"}],["path",{d:"M16 10h.01",key:"1m94wz"}],["path",{d:"M12 10h.01",key:"1nrarc"}],["path",{d:"M8 10h.01",key:"19clt8"}],["path",{d:"M12 14h.01",key:"1etili"}],["path",{d:"M8 14h.01",key:"6423bh"}],["path",{d:"M12 18h.01",key:"mhygvu"}],["path",{d:"M8 18h.01",key:"lrp35t"}]],D=A("Calculator",ie);Z([ee,se,te,ae,re,ne,le]);function oe(a){const s=/^(\d{4})-(\d{4})/.exec(a);return s?s[1]+"~"+s[2]+" 学年":"其他学期"}function ce(a){const s=a.filter(i=>i.grade_point!=null&&i.credit>0);if(!s.length)return null;const t=s.reduce((i,o)=>i+o.credit,0),l=s.reduce((i,o)=>i+o.grade_point*o.credit,0);return Math.round(l/t*100)/100}function ye(){const a=X(),s=I({queryKey:["grades"],queryFn:V}),t=s.data,l=M("2"),i=M("3"),o=L(()=>{const n=((t==null?void 0:t.overall.tier_dist)??[]).filter(r=>r.count>0);return{tooltip:{trigger:"item"},legend:{orient:"vertical",right:8,top:"middle",itemGap:10,textStyle:{color:l,fontSize:11},itemWidth:10,itemHeight:10},series:[{type:"pie",radius:["48%","72%"],center:["34%","50%"],label:{show:!1},data:n.map(r=>({name:r.label,value:r.count,itemStyle:{color:r.color}}))}]}},[t,a]),m=L(()=>{const n=(t==null?void 0:t.overall.gpa_trend)??[];return{grid:{left:8,right:8,top:36,bottom:6,containLabel:!0},tooltip:{trigger:"axis"},legend:{top:0,left:"center",itemGap:16,textStyle:{color:l,fontSize:11},itemWidth:14,itemHeight:8},xAxis:{type:"category",data:n.map(r=>r.term),axisLine:{lineStyle:{color:i}},axisTick:{show:!1},axisLabel:{color:i,fontSize:10}},yAxis:[{type:"value",min:0,max:100,splitLine:{lineStyle:{color:"rgba(128,138,155,0.15)"}},axisLabel:{color:i,fontSize:10}},{type:"value",min:0,max:5,splitLine:{show:!1},axisLabel:{color:i,fontSize:10}}],series:[{name:"加权平均分",type:"bar",barMaxWidth:18,itemStyle:{color:"#3056d3",borderRadius:[3,3,0,0]},data:n.map(r=>r.avg)},{name:"绩点",type:"line",yAxisIndex:1,smooth:!0,symbolSize:6,itemStyle:{color:"#d97706"},lineStyle:{width:2},data:n.map(r=>r.gpa)}]}},[t,a]);if(s.isLoading)return e.jsx("div",{className:"py-16 text-center text-[13px] text-ink-3",children:"加载中…"});const p=[...(t==null?void 0:t.semesters)??[]].sort((n,r)=>r.term.localeCompare(n.term));return e.jsxs("div",{className:"mx-auto max-w-[1080px] px-3 py-4 sm:px-6 sm:py-5",children:[e.jsxs("div",{className:"mb-4 flex flex-wrap gap-3",children:[e.jsx(w,{label:"总学分",value:(t==null?void 0:t.overall.credits)??0}),e.jsx(w,{label:"加权平均分",value:(t==null?void 0:t.overall.avg)!=null?t.overall.avg:"—"}),e.jsx(w,{label:"已录课程",value:(t==null?void 0:t.overall.courses)??0}),e.jsx(w,{label:"学期数",value:(t==null?void 0:t.semesters.length)??0})]}),e.jsxs("div",{className:"mb-4 grid grid-cols-1 lg:grid-cols-2 gap-4",children:[e.jsxs(f,{children:[e.jsx(z,{title:"单科成绩等级分布"}),e.jsx(_,{children:e.jsx("div",{ref:o,className:"h-[220px]"})})]}),e.jsxs(f,{children:[e.jsx(z,{title:"学期趋势(加权平均分 / 绩点)"}),e.jsx(_,{children:e.jsx("div",{ref:m,className:"h-[220px]"})})]})]}),e.jsx(xe,{bands:(t==null?void 0:t.bands)??[]}),e.jsxs("div",{className:"mb-3 mt-5 flex items-center justify-between",children:[e.jsx("h2",{className:"text-[14px] font-semibold text-ink-1",children:"学年学科成绩"}),e.jsx(he,{})]}),p.length?e.jsx("div",{className:"flex flex-col gap-5",children:de(p).map(([n,r])=>e.jsxs("section",{children:[e.jsx("div",{className:"mb-2 text-[12px] font-medium text-ink-3",children:n}),e.jsx("div",{className:"flex flex-col gap-3",children:r.map(h=>e.jsx(ue,{sem:h},h.id))})]},n))}):e.jsx(f,{children:e.jsx(O,{icon:e.jsx($,{size:36,strokeWidth:1.5}),title:"暂无学期成绩",description:"新增一个学期(如 2024-2025-1),再录入各科成绩。"})})]})}function de(a){const s=new Map;for(const t of a){const l=oe(t.term);s.has(l)||s.set(l,[]),s.get(l).push(t)}return[...s.entries()]}function w({label:a,value:s}){return e.jsxs("div",{className:"min-w-[128px] flex-1 max-w-[220px] rounded-lg border border-line bg-surface px-4 py-3 shadow-card",children:[e.jsx("div",{className:"text-[12px] text-ink-3",children:a}),e.jsx("div",{className:"num mt-0.5 text-[22px] font-semibold leading-tight text-ink-1",children:s})]})}function xe({bands:a}){const s=k(),t=S(),[l,i]=u.useState(null),[o,m]=u.useState(null),p=y({mutationFn:H,onSuccess:()=>{t("success","梯度已保存"),s.invalidateQueries({queryKey:["grades"]})},onError:r=>t("error",r.message)}),n=y({mutationFn:W,onSuccess:()=>{s.invalidateQueries({queryKey:["grades"]}),m(null)}});return e.jsxs(f,{children:[e.jsx(z,{title:"成绩梯度",extra:e.jsx("span",{className:"text-[11px] text-ink-3",children:"按 分数 ≥ min 且 < max 定级,满分档含上限"})}),e.jsxs(_,{className:"p-0",children:[e.jsx("div",{className:"divide-y divide-line",children:a.map(r=>e.jsx(P,{band:r,onSave:h=>p.mutate(h),onDelete:()=>m(r)},r.id))}),e.jsx("div",{className:"border-t border-line px-4 py-2.5",children:l?e.jsx(P,{band:l,isNew:!0,onChange:i,onSave:()=>{p.mutate(l),i(null)},onCancel:()=>i(null)}):e.jsxs(g,{size:"sm",variant:"secondary",onClick:()=>{var r;return i({id:0,label:"",color:"#3056d3",min:0,max:60,sort:(((r=a.at(-1))==null?void 0:r.sort)??0)+10})},children:[e.jsx(q,{size:13})," 添加等级"]})})]}),e.jsx(Q,{open:!!o,title:"删除等级",content:o?"确定删除「"+o.label+"」吗?已录成绩将不再显示该等级。":"",onCancel:()=>m(null),onOk:()=>o&&n.mutate(o.id)})]})}function P({band:a,onSave:s,onDelete:t,onChange:l,onCancel:i,isNew:o}){const[m,p]=u.useState(a),n=o&&l?m:a,r=d=>{const x={...n,...d};l?l(x):p(x)},h=o||JSON.stringify({...n,id:0})!==JSON.stringify({...a,id:0});return e.jsxs("div",{className:"flex flex-wrap items-center gap-2 px-4 py-2",children:[e.jsx("input",{type:"color",value:n.color,onChange:d=>r({color:d.target.value}),className:"size-5 shrink-0 cursor-pointer rounded border border-line bg-surface","aria-label":"颜色"}),e.jsx(j,{className:"w-[96px]",value:n.label,onChange:d=>r({label:d.target.value}),placeholder:"等级名"}),e.jsx("span",{className:"text-[12px] text-ink-3",children:"分数"}),e.jsx(j,{type:"number",className:"num w-[72px]",value:String(n.min),onChange:d=>r({min:Number(d.target.value)})}),e.jsx("span",{className:"text-[12px] text-ink-3",children:"至"}),e.jsx(j,{type:"number",className:"num w-[72px]",value:String(n.max),onChange:d=>r({max:Number(d.target.value)})}),o?e.jsxs(e.Fragment,{children:[e.jsx(g,{size:"sm",variant:"primary",onClick:()=>s==null?void 0:s(n),children:"添加"}),e.jsx(g,{size:"sm",variant:"ghost",onClick:()=>i==null?void 0:i(),children:"取消"})]}):e.jsxs("div",{className:"ml-auto flex gap-1.5",children:[e.jsx(g,{size:"sm",variant:h?"primary":"secondary",disabled:!h,onClick:()=>s==null?void 0:s(n),children:"保存"}),e.jsx(g,{size:"icon-sm",variant:"ghost","aria-label":"删除等级",onClick:()=>t==null?void 0:t(),children:e.jsx(E,{size:13})})]})]})}function ue({sem:a}){const s=k(),t=S(),[l,i]=u.useState(null),[o,m]=u.useState(null),[p,n]=u.useState(!1),r=()=>void s.invalidateQueries({queryKey:["grades"]}),h=y({mutationFn:J,onSuccess:()=>{r(),m(null)}}),d=y({mutationFn:()=>{const c=ce(a.courses);if(c==null)throw new Error("请先为课程填写绩点");return T({id:a.id,term:a.term,gpa:c,rank:a.rank??null,total_students:a.total_students??null})},onSuccess:()=>{t("success","绩点已按课程自动计算"),r()},onError:c=>t("error",c.message)}),x=y({mutationFn:()=>U(a.id),onSuccess:()=>{r(),n(!1),t("success","学期已删除")}});return e.jsxs(f,{children:[e.jsx(z,{title:a.term,extra:e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsxs(N,{color:"var(--brand-text)",children:["绩点 ",a.gpa??"—"]}),a.rank!=null&&e.jsxs(N,{color:"var(--success)",children:["排名 ",a.rank,"/",a.total_students??"—"]}),e.jsxs(N,{color:"var(--text-3)",children:["均分 ",a.stats.avg??"—"]}),e.jsxs(N,{color:"var(--text-3)",children:[a.stats.credits," 学分"]}),e.jsxs(g,{size:"sm",variant:"ghost",onClick:()=>d.mutate(),disabled:d.isPending,children:[e.jsx(D,{size:13})," 自动绩点"]}),e.jsx(g,{size:"icon-sm",variant:"ghost","aria-label":"编辑学期",onClick:()=>n(!0),children:e.jsx(G,{size:13})})]})}),e.jsx(_,{className:"p-0",children:e.jsx("div",{className:"overflow-x-auto",children:e.jsx("div",{className:"-mx-1 overflow-x-auto px-1",children:e.jsxs("table",{className:"w-full border-collapse text-[13px]",children:[e.jsx("thead",{children:e.jsx("tr",{children:["课程","学分","分数","等级","绩点",""].map(c=>e.jsx("th",{className:"border-b border-line bg-surface-2 px-3 py-2 text-left text-[12px] font-semibold text-ink-3",children:c},c))})}),e.jsxs("tbody",{children:[a.courses.map(c=>l===c.id?e.jsx(pe,{sem:a,course:c,onDone:()=>i(null)},c.id):e.jsxs("tr",{className:"transition-colors hover:bg-surface-2",children:[e.jsx("td",{className:"border-b border-line px-3 py-2 font-medium text-ink-1",children:c.course_name}),e.jsx("td",{className:"num border-b border-line px-3 py-2 text-ink-2",children:c.credit}),e.jsx("td",{className:"num border-b border-line px-3 py-2",children:c.score!=null?e.jsx("span",{className:"font-medium",children:c.score}):e.jsx("span",{className:"text-ink-3",children:"未录入"})}),e.jsx("td",{className:"border-b border-line px-3 py-2",children:c.band?e.jsx(N,{color:c.band.color,children:c.band.label}):e.jsx("span",{className:"text-ink-3",children:"—"})}),e.jsx("td",{className:"num border-b border-line px-3 py-2 text-ink-2",children:c.grade_point??"—"}),e.jsx("td",{className:"border-b border-line px-3 py-2",children:e.jsxs("div",{className:"flex justify-end gap-1",children:[e.jsx(g,{size:"icon-sm",variant:"ghost","aria-label":"编辑课程",onClick:()=>i(c.id),children:e.jsx(G,{size:13})}),e.jsx(g,{size:"icon-sm",variant:"ghost","aria-label":"删除课程",className:"text-ink-3 hover:text-danger",onClick:()=>m(c),children:e.jsx(E,{size:13})})]})})]},c.id)),e.jsx(me,{sem:a})]})]})})})}),e.jsx(Q,{open:!!o,title:"删除课程",content:o?"确定删除「"+o.course_name+"」吗?":"",onCancel:()=>m(null),onOk:()=>o&&h.mutate(o.id)}),e.jsx(K,{open:p,sem:a,onClose:()=>n(!1),deleteMut:x})]})}function me({sem:a}){const s=k(),t=S(),[l,i]=u.useState(""),[o,m]=u.useState(""),[p,n]=u.useState(""),[r,h]=u.useState(""),d=y({mutationFn:()=>B({semester_id:a.id,course_name:l,credit:Number(o)||0,score:p===""?null:Number(p),grade_point:r===""?null:Number(r)}),onSuccess:()=>{i(""),m(""),n(""),h(""),s.invalidateQueries({queryKey:["grades"]})},onError:x=>t("error",x.message)});return e.jsxs("tr",{className:"bg-surface-2",children:[e.jsx("td",{className:"px-3 py-2",children:e.jsx(j,{className:"h-7 w-[160px]",placeholder:"课程名称",value:l,onChange:x=>i(x.target.value)})}),e.jsx("td",{className:"px-3 py-2",children:e.jsx(j,{type:"number",step:"0.5",className:"num h-7 w-[64px]",placeholder:"0",value:o,onChange:x=>m(x.target.value)})}),e.jsx("td",{className:"px-3 py-2",children:e.jsx(j,{type:"number",className:"num h-7 w-[72px]",placeholder:"0-100",value:p,onChange:x=>n(x.target.value)})}),e.jsx("td",{className:"px-3 py-2 text-ink-3",children:"—"}),e.jsx("td",{className:"px-3 py-2",children:e.jsx(j,{type:"number",step:"0.1",className:"num h-7 w-[64px]",placeholder:"选填",value:r,onChange:x=>h(x.target.value)})}),e.jsx("td",{className:"px-3 py-2",children:e.jsxs(g,{size:"sm",variant:"secondary",disabled:!l.trim()||d.isPending,onClick:()=>d.mutate(),children:[e.jsx(q,{size:13})," 添加"]})})]})}function pe({sem:a,course:s,onDone:t}){const l=k(),i=S(),[o,m]=u.useState(s.course_name),[p,n]=u.useState(String(s.credit)),[r,h]=u.useState(s.score!=null?String(s.score):""),[d,x]=u.useState(s.grade_point!=null?String(s.grade_point):""),c=y({mutationFn:()=>B({id:s.id,semester_id:a.id,course_name:o,credit:Number(p)||0,score:r===""?null:Number(r),grade_point:d===""?null:Number(d)}),onSuccess:()=>{l.invalidateQueries({queryKey:["grades"]}),t()},onError:b=>i("error",b.message)});return e.jsxs("tr",{className:"bg-brand-subtle/40",children:[e.jsx("td",{className:"border-b border-line px-3 py-2",children:e.jsx(j,{className:"h-7 w-[160px]",value:o,onChange:b=>m(b.target.value)})}),e.jsx("td",{className:"border-b border-line px-3 py-2",children:e.jsx(j,{type:"number",step:"0.5",className:"num h-7 w-[64px]",value:p,onChange:b=>n(b.target.value)})}),e.jsx("td",{className:"border-b border-line px-3 py-2",children:e.jsx(j,{type:"number",className:"num h-7 w-[72px]",value:r,onChange:b=>h(b.target.value)})}),e.jsx("td",{className:"border-b border-line px-3 py-2 text-[12px] text-ink-3",children:"保存后按梯度定级"}),e.jsx("td",{className:"border-b border-line px-3 py-2",children:e.jsx(j,{type:"number",step:"0.1",className:"num h-7 w-[64px]",value:d,onChange:b=>x(b.target.value)})}),e.jsx("td",{className:"border-b border-line px-3 py-2",children:e.jsxs("div",{className:"flex justify-end gap-1.5",children:[e.jsx(g,{size:"sm",variant:"primary",onClick:()=>c.mutate(),disabled:c.isPending,children:"保存"}),e.jsx(g,{size:"sm",variant:"ghost",onClick:t,children:"取消"})]})})]})}function he(){const[a,s]=u.useState(!1);return e.jsxs(e.Fragment,{children:[e.jsxs(g,{variant:"primary",size:"md",onClick:()=>s(!0),children:[e.jsx(q,{size:14})," 新增学期"]}),e.jsx(K,{open:a,sem:null,onClose:()=>s(!1)})]})}function K({open:a,sem:s,onClose:t,deleteMut:l}){const i=k(),o=S(),[m,p]=u.useState((s==null?void 0:s.term)??""),[n,r]=u.useState((s==null?void 0:s.gpa)!=null?String(s.gpa):""),[h,d]=u.useState((s==null?void 0:s.rank)!=null?String(s.rank):""),[x,c]=u.useState((s==null?void 0:s.total_students)!=null?String(s.total_students):""),[b,R]=u.useState(""),F=y({mutationFn:()=>T({id:s==null?void 0:s.id,term:m,gpa:n===""?null:Number(n),rank:h===""?null:Number(h),total_students:x===""?null:Number(x)}),onSuccess:()=>{o("success",s?"学期已更新":"学期已新增"),i.invalidateQueries({queryKey:["grades"]}),t()},onError:v=>R(v.message)});return e.jsx(Y,{open:a,onClose:t,width:480,title:s?"编辑学期 · "+s.term:"新增学期",footer:e.jsxs(e.Fragment,{children:[b&&e.jsx("span",{className:"mr-auto text-[12px] text-danger",children:b}),s&&e.jsxs(g,{variant:"danger-ghost",className:"mr-auto",onClick:()=>l==null?void 0:l.mutate(),disabled:l==null?void 0:l.isPending,children:[e.jsx(E,{size:13})," 删除学期"]}),e.jsx(g,{variant:"secondary",onClick:t,children:"取消"}),e.jsx(g,{variant:"primary",onClick:()=>F.mutate(),disabled:F.isPending,children:"保存"})]}),children:e.jsxs("div",{className:"flex flex-col gap-3.5",children:[e.jsx(C,{label:"学期名称",required:!0,hint:"如 2024-2025-1",children:e.jsx(j,{value:m,onChange:v=>p(v.target.value),placeholder:"2024-2025-1"})}),e.jsxs("div",{className:"grid grid-cols-3 gap-3",children:[e.jsx(C,{label:"绩点",children:e.jsx(j,{type:"number",step:"0.01",className:"num",value:n,onChange:v=>r(v.target.value),placeholder:"选填"})}),e.jsx(C,{label:"排名",children:e.jsx(j,{type:"number",className:"num",value:h,onChange:v=>d(v.target.value),placeholder:"选填"})}),e.jsx(C,{label:"年级人数",children:e.jsx(j,{type:"number",className:"num",value:x,onChange:v=>c(v.target.value),placeholder:"选填"})})]}),e.jsxs("p",{className:"flex items-start gap-1.5 rounded-md bg-surface-2 px-3 py-2 text-[12px] text-ink-3",children:[e.jsx(D,{size:13,className:"mt-0.5 shrink-0"}),"绩点可手动填写,也可在卡片上点「自动绩点」按课程学分加权计算。"]})]})})}export{ye as GradesPage};