nx-sk 0.6.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- import{u as q,r,a as p,j as e,b as w,T as A,E as H,C as _,M as z}from"./index-B7CtqgoG.js";import{f as K,r as f}from"./files-BNh48qeo.js";function S(t){return t<1024?`${t} B`:t<1024*1024?`${(t/1024).toFixed(1)} KB`:`${(t/(1024*1024)).toFixed(2)} MB`}const Q=t=>`${t.sectionTitle} / ${t.entryTitle||"(无名)"} / ${t.fieldLabel}`;function X(){const{toast:t,guard:l,dialog:m,reload:n}=q(),[i,k]=r.useState(null),[C,$]=r.useState(null),[u,N]=r.useState(""),[g,E]=r.useState(""),[D,v]=r.useState(!1),[d,h]=r.useState(null),T=r.useRef(null),x=r.useRef(0),o=r.useCallback(async()=>{try{const a=new URLSearchParams;u&&a.set("section",u),g&&a.set("q",g);const s=a.toString(),c=await p(`/api/files${s?`?${s}`:""}`);k(c),$(null)}catch(a){$(a.message),k(null)}},[u,g]);r.useEffect(()=>{o()},[o]);const b=r.useCallback(async a=>{const s=Array.from(a||[]);if(!s.length)return;let c=0;for(const j of s)try{const y=await K(j);await p("/api/files",{method:"POST",body:{content:y,name:j.name}}),c+=1}catch(y){t(`${j.name} 上传失败:${y.message}`,"bad")}c&&t(`已导入 ${c} 个附件(到栏目条目里选它即可链接)`),await f(),await o(),await n()},[o,n,t]),P=r.useCallback(a=>{a.preventDefault(),x.current=0,v(!1),b(a.dataTransfer.files)},[b]),I=l(async()=>{h({kind:"path",form:{path:"",copy:!1}})}),R=l(async a=>{if(!a.path.trim()){t("请填本机文件的绝对路径","bad");return}const s=await p("/api/files",{method:"POST",body:{path:a.path.trim(),copy:a.copy}});h(null),t(s.duplicated?"库中已有相同文件":s.moved?"已移动进附件库(可还原)":"已复制进附件库"),await f(),await o(),await n()}),B=l(async a=>{h({kind:"edit",id:a.id,form:{name:a.name,tags:a.tags.join("、"),note:a.note}})}),F=l(async(a,s)=>{await p(`/api/files/${encodeURIComponent(a)}`,{method:"PATCH",body:{name:s.name,tags:s.tags.split(/[,,、;;]+/).map(c=>c.trim()).filter(Boolean),note:s.note}}),h(null),t("附件已更新"),await f(),await o()}),U=l(async a=>{const s=await m({message:`把「${a.name}」还原到哪?`,detail:a.sourceDisplay?`默认移回原始位置并撤销台账:${a.sourceDisplay}`:"该附件没有原始位置记录,请在下一步填写目标目录。",input:a.sourceDisplay?void 0:{placeholder:"目标目录绝对路径,例如 D:\\资料"},confirmText:"移回并撤销台账",cancelText:"取消"});if(s===null)return;const c={};s&&typeof s=="string"&&(c.to=s.trim());const j=await p(`/api/files/${encodeURIComponent(a.id)}/restore`,{method:"POST",body:c});t(`已还原到 ${j.displayPath}`),await f(),await o(),await n()}),O=l(async a=>{const s=await m({message:`导出「${a.name}」的副本到哪个目录?库中原件保留。`,input:{placeholder:"目标目录绝对路径,例如 D:\\资料"},confirmText:"导出副本"});if(!s)return;const c=await p(`/api/files/${encodeURIComponent(a.id)}/restore`,{method:"POST",body:{to:String(s).trim(),copy:!0}});t(`已导出到 ${c.displayPath}`)}),L=l(async a=>{await m({message:`删除附件「${a.name}」?物理原件会一起删除,删除前自动留台账快照。`,detail:"想保留原件、只撤销台账,请改用「还原」。",danger:!0,confirmText:"删除"})&&(await p(`/api/files/${encodeURIComponent(a.id)}`,{method:"DELETE"}),t("已删除"),await f(),await o(),await n())});if(C)return e.jsxs("div",{className:"pad stack",children:[e.jsx("div",{className:"bad",children:e.jsx("strong",{children:"附件库读不到"})}),e.jsx("pre",{className:"block",children:C})]});if(!i)return e.jsx("div",{className:"pad muted",children:"加载中…"});const M=i.sections||[];return e.jsxs("div",{className:`files-page ${D?"drag-over":""}`,onDragEnter:a=>{a.preventDefault(),x.current+=1,v(!0)},onDragOver:a=>a.preventDefault(),onDragLeave:a=>{a.preventDefault(),x.current-=1,x.current<=0&&(x.current=0,v(!1))},onDrop:P,children:[e.jsxs("div",{className:"toolbar rowgap pad",children:[e.jsx("strong",{children:"附件"}),e.jsxs("span",{className:"muted",children:[i.total," 个 · ",S(i.totalBytes)]}),e.jsxs("span",{className:"hint",title:"被栏目条目的 file 字段引用的附件数",children:["被引用 ",i.total-i.unlinked,"/",i.total]}),e.jsx(w,{className:"hint",text:i.rootRaw,title:"点击复制附件库目录",children:i.root}),e.jsx("span",{className:"grow"}),e.jsx("input",{style:{width:180},placeholder:"搜索名称 / 备注 / 标签 / 引用它的条目",value:g,onChange:a=>E(a.target.value)}),e.jsx("button",{className:"btn small ghost",onClick:o,children:"刷新"}),e.jsx("button",{className:"btn small ghost",onClick:I,children:"本机路径导入"}),e.jsx("button",{className:"btn small",onClick:()=>{var a;return(a=T.current)==null?void 0:a.click()},children:"上传文件"}),e.jsx("input",{ref:T,type:"file",multiple:!0,style:{display:"none"},onChange:a=>{b(a.target.files),a.target.value=""}})]}),e.jsxs("div",{className:"rowgap pad file-filters",children:[e.jsxs("button",{className:`tab small ${u?"":"active"}`,onClick:()=>N(""),children:["全部",e.jsx("span",{className:"count",children:i.total})]}),M.map(a=>e.jsxs("button",{className:`tab small ${u===a.id?"active":""}`,onClick:()=>N(a.id),title:`被「${a.title}」栏目引用的附件`,children:[a.title,e.jsx("span",{className:"count",children:a.count})]},a.id)),e.jsxs("button",{className:`tab small ${u==="none"?"active":""}`,onClick:()=>N("none"),children:["未引用",e.jsx("span",{className:"count",children:i.unlinked})]})]}),D?e.jsx("div",{className:"drop-hint",children:"松开鼠标,文件直接导入附件库"}):null,i.files.length?e.jsx("div",{className:"file-grid pad",children:i.files.map(a=>{var s;return e.jsxs("div",{className:"file-card",children:[e.jsx("a",{className:"file-thumb",href:a.url,target:"_blank",rel:"noreferrer",title:"在浏览器中预览",children:a.isImage?e.jsx("img",{src:a.url,alt:a.name,loading:"lazy"}):e.jsx("span",{className:"ext-badge",children:a.ext?a.ext.slice(1).toUpperCase():"FILE"})}),e.jsxs("div",{className:"file-meta",children:[e.jsx("a",{className:"file-name",href:a.url,target:"_blank",rel:"noreferrer",title:a.name,children:a.name}),e.jsxs("div",{className:"rowgap",style:{gap:4},children:[e.jsx("span",{className:"hint",children:S(a.bytes)}),a.mode==="move"?e.jsx(A,{children:"移动导入"}):null]}),(s=a.usedBy)!=null&&s.length?e.jsxs("div",{className:"hint",title:"条目的 file 字段引用了本附件",children:["被引用:",a.usedBy.map(Q).join("、")]}):e.jsx("div",{className:"hint",children:"未被任何条目引用(到栏目条目里选它即可链接)"})]}),e.jsxs("div",{className:"file-acts",children:[e.jsx("a",{className:"btn small ghost",href:a.downloadUrl,children:"下载"}),e.jsx("button",{className:"btn small ghost",onClick:()=>O(a),children:"导出副本"}),e.jsx("button",{className:"btn small ghost",onClick:()=>U(a),children:"还原"}),e.jsx("button",{className:"btn small ghost",onClick:()=>B(a),children:"编辑"}),e.jsx("button",{className:"btn small ghost danger-ghost",onClick:()=>L(a),children:"删除"}),e.jsx("span",{className:"grow"}),e.jsx(w,{className:"hint",text:a.path,title:"点击复制物理路径",children:"路径"}),e.jsx(w,{className:"hint",text:a.url,title:"点击复制链接",children:"链接"})]})]},a.id)})}):e.jsxs("div",{className:"pad",children:[e.jsx(H,{children:"还没有附件。把文件拖到这里、点「上传文件」,或用「本机路径导入」把原件移动进库;随后到栏目的条目里选它。"}),e.jsx("div",{className:"hint pad",children:'CLI:nx-sk file add --path "D:\\简历\\张三-简历.pdf"(默认移动;--copy 仅复制)'})]}),e.jsx(_,{module:"files"}),d?e.jsx(z,{title:d.kind==="edit"?"编辑附件":"从本机路径导入",onClose:()=>h(null),children:d.kind==="path"?e.jsx(V,{form:d.form,onChange:a=>h(s=>({...s,form:{...s.form,...a}})),onSubmit:()=>R(d.form)}):e.jsx(G,{form:d.form,onChange:a=>h(s=>({...s,form:{...s.form,...a}})),onSubmit:()=>F(d.id,d.form)})}):null]})}function V({form:t,onChange:l,onSubmit:m}){return e.jsxs("div",{className:"stack",children:[e.jsxs("div",{className:"field wide",children:[e.jsx("div",{className:"flabel",children:e.jsx("span",{children:"本机文件的绝对路径"})}),e.jsx("div",{className:"fctrl",children:e.jsx("input",{value:t.path,placeholder:"例如 D:\\简历\\张三-简历.pdf",onChange:n=>l({path:n.target.value})})})]}),e.jsxs("div",{className:"field wide",children:[e.jsx("div",{className:"flabel",children:e.jsx("span",{children:"导入方式"})}),e.jsx("div",{className:"fctrl",children:e.jsxs("label",{className:"inline",children:[e.jsx("input",{type:"checkbox",checked:t.copy,onChange:n=>l({copy:n.target.checked})}),e.jsx("span",{className:"hint",children:"仅复制、保留原件(默认把原件移动进库,可用「还原」撤销)"})]})})]}),e.jsx("div",{className:"rowgap",children:e.jsx("button",{className:"btn",onClick:m,children:"导入"})})]})}function G({form:t,onChange:l,onSubmit:m}){return e.jsxs("div",{className:"stack",children:[e.jsxs("div",{className:"field wide",children:[e.jsx("div",{className:"flabel",children:e.jsx("span",{children:"文件名"})}),e.jsx("div",{className:"fctrl",children:e.jsx("input",{value:t.name,onChange:n=>l({name:n.target.value})})})]}),e.jsxs("div",{className:"field wide",children:[e.jsxs("div",{className:"flabel",children:[e.jsx("span",{children:"标签"}),e.jsx("span",{className:"fkey",children:"多个用「、」分隔"})]}),e.jsx("div",{className:"fctrl",children:e.jsx("input",{value:t.tags,onChange:n=>l({tags:n.target.value})})})]}),e.jsxs("div",{className:"field wide",children:[e.jsx("div",{className:"flabel",children:e.jsx("span",{children:"备注"})}),e.jsx("div",{className:"fctrl",children:e.jsx("textarea",{rows:2,value:t.note,onChange:n=>l({note:n.target.value})})})]}),e.jsxs("div",{className:"rowgap",children:[e.jsx("button",{className:"btn",onClick:m,children:"保存"}),e.jsx("span",{className:"hint",children:"改文件名时,物理原件会随之移动。要链接到条目,请在栏目条目的 file 字段里选本附件。"})]})]})}export{X as default};
1
+ import{u as q,r,a as p,j as e,b as w,T as A,E as H,C as _,M as z}from"./index-BygHBMGo.js";import{f as K,r as f}from"./files-DcDiKjwc.js";function S(t){return t<1024?`${t} B`:t<1024*1024?`${(t/1024).toFixed(1)} KB`:`${(t/(1024*1024)).toFixed(2)} MB`}const Q=t=>`${t.sectionTitle} / ${t.entryTitle||"(无名)"} / ${t.fieldLabel}`;function X(){const{toast:t,guard:l,dialog:m,reload:n}=q(),[i,k]=r.useState(null),[C,$]=r.useState(null),[u,N]=r.useState(""),[g,E]=r.useState(""),[D,v]=r.useState(!1),[d,h]=r.useState(null),T=r.useRef(null),x=r.useRef(0),o=r.useCallback(async()=>{try{const a=new URLSearchParams;u&&a.set("section",u),g&&a.set("q",g);const s=a.toString(),c=await p(`/api/files${s?`?${s}`:""}`);k(c),$(null)}catch(a){$(a.message),k(null)}},[u,g]);r.useEffect(()=>{o()},[o]);const b=r.useCallback(async a=>{const s=Array.from(a||[]);if(!s.length)return;let c=0;for(const j of s)try{const y=await K(j);await p("/api/files",{method:"POST",body:{content:y,name:j.name}}),c+=1}catch(y){t(`${j.name} 上传失败:${y.message}`,"bad")}c&&t(`已导入 ${c} 个附件(到栏目条目里选它即可链接)`),await f(),await o(),await n()},[o,n,t]),P=r.useCallback(a=>{a.preventDefault(),x.current=0,v(!1),b(a.dataTransfer.files)},[b]),I=l(async()=>{h({kind:"path",form:{path:"",copy:!1}})}),R=l(async a=>{if(!a.path.trim()){t("请填本机文件的绝对路径","bad");return}const s=await p("/api/files",{method:"POST",body:{path:a.path.trim(),copy:a.copy}});h(null),t(s.duplicated?"库中已有相同文件":s.moved?"已移动进附件库(可还原)":"已复制进附件库"),await f(),await o(),await n()}),B=l(async a=>{h({kind:"edit",id:a.id,form:{name:a.name,tags:a.tags.join("、"),note:a.note}})}),F=l(async(a,s)=>{await p(`/api/files/${encodeURIComponent(a)}`,{method:"PATCH",body:{name:s.name,tags:s.tags.split(/[,,、;;]+/).map(c=>c.trim()).filter(Boolean),note:s.note}}),h(null),t("附件已更新"),await f(),await o()}),U=l(async a=>{const s=await m({message:`把「${a.name}」还原到哪?`,detail:a.sourceDisplay?`默认移回原始位置并撤销台账:${a.sourceDisplay}`:"该附件没有原始位置记录,请在下一步填写目标目录。",input:a.sourceDisplay?void 0:{placeholder:"目标目录绝对路径,例如 D:\\资料"},confirmText:"移回并撤销台账",cancelText:"取消"});if(s===null)return;const c={};s&&typeof s=="string"&&(c.to=s.trim());const j=await p(`/api/files/${encodeURIComponent(a.id)}/restore`,{method:"POST",body:c});t(`已还原到 ${j.displayPath}`),await f(),await o(),await n()}),O=l(async a=>{const s=await m({message:`导出「${a.name}」的副本到哪个目录?库中原件保留。`,input:{placeholder:"目标目录绝对路径,例如 D:\\资料"},confirmText:"导出副本"});if(!s)return;const c=await p(`/api/files/${encodeURIComponent(a.id)}/restore`,{method:"POST",body:{to:String(s).trim(),copy:!0}});t(`已导出到 ${c.displayPath}`)}),L=l(async a=>{await m({message:`删除附件「${a.name}」?物理原件会一起删除,删除前自动留台账快照。`,detail:"想保留原件、只撤销台账,请改用「还原」。",danger:!0,confirmText:"删除"})&&(await p(`/api/files/${encodeURIComponent(a.id)}`,{method:"DELETE"}),t("已删除"),await f(),await o(),await n())});if(C)return e.jsxs("div",{className:"pad stack",children:[e.jsx("div",{className:"bad",children:e.jsx("strong",{children:"附件库读不到"})}),e.jsx("pre",{className:"block",children:C})]});if(!i)return e.jsx("div",{className:"pad muted",children:"加载中…"});const M=i.sections||[];return e.jsxs("div",{className:`files-page ${D?"drag-over":""}`,onDragEnter:a=>{a.preventDefault(),x.current+=1,v(!0)},onDragOver:a=>a.preventDefault(),onDragLeave:a=>{a.preventDefault(),x.current-=1,x.current<=0&&(x.current=0,v(!1))},onDrop:P,children:[e.jsxs("div",{className:"toolbar rowgap pad",children:[e.jsx("strong",{children:"附件"}),e.jsxs("span",{className:"muted",children:[i.total," 个 · ",S(i.totalBytes)]}),e.jsxs("span",{className:"hint",title:"被栏目条目的 file 字段引用的附件数",children:["被引用 ",i.total-i.unlinked,"/",i.total]}),e.jsx(w,{className:"hint",text:i.rootRaw,title:"点击复制附件库目录",children:i.root}),e.jsx("span",{className:"grow"}),e.jsx("input",{style:{width:180},placeholder:"搜索名称 / 备注 / 标签 / 引用它的条目",value:g,onChange:a=>E(a.target.value)}),e.jsx("button",{className:"btn small ghost",onClick:o,children:"刷新"}),e.jsx("button",{className:"btn small ghost",onClick:I,children:"本机路径导入"}),e.jsx("button",{className:"btn small",onClick:()=>{var a;return(a=T.current)==null?void 0:a.click()},children:"上传文件"}),e.jsx("input",{ref:T,type:"file",multiple:!0,style:{display:"none"},onChange:a=>{b(a.target.files),a.target.value=""}})]}),e.jsxs("div",{className:"rowgap pad file-filters",children:[e.jsxs("button",{className:`tab small ${u?"":"active"}`,onClick:()=>N(""),children:["全部",e.jsx("span",{className:"count",children:i.total})]}),M.map(a=>e.jsxs("button",{className:`tab small ${u===a.id?"active":""}`,onClick:()=>N(a.id),title:`被「${a.title}」栏目引用的附件`,children:[a.title,e.jsx("span",{className:"count",children:a.count})]},a.id)),e.jsxs("button",{className:`tab small ${u==="none"?"active":""}`,onClick:()=>N("none"),children:["未引用",e.jsx("span",{className:"count",children:i.unlinked})]})]}),D?e.jsx("div",{className:"drop-hint",children:"松开鼠标,文件直接导入附件库"}):null,i.files.length?e.jsx("div",{className:"file-grid pad",children:i.files.map(a=>{var s;return e.jsxs("div",{className:"file-card",children:[e.jsx("a",{className:"file-thumb",href:a.url,target:"_blank",rel:"noreferrer",title:"在浏览器中预览",children:a.isImage?e.jsx("img",{src:a.url,alt:a.name,loading:"lazy"}):e.jsx("span",{className:"ext-badge",children:a.ext?a.ext.slice(1).toUpperCase():"FILE"})}),e.jsxs("div",{className:"file-meta",children:[e.jsx("a",{className:"file-name",href:a.url,target:"_blank",rel:"noreferrer",title:a.name,children:a.name}),e.jsxs("div",{className:"rowgap",style:{gap:4},children:[e.jsx("span",{className:"hint",children:S(a.bytes)}),a.mode==="move"?e.jsx(A,{children:"移动导入"}):null]}),(s=a.usedBy)!=null&&s.length?e.jsxs("div",{className:"hint",title:"条目的 file 字段引用了本附件",children:["被引用:",a.usedBy.map(Q).join("、")]}):e.jsx("div",{className:"hint",children:"未被任何条目引用(到栏目条目里选它即可链接)"})]}),e.jsxs("div",{className:"file-acts",children:[e.jsx("a",{className:"btn small ghost",href:a.downloadUrl,children:"下载"}),e.jsx("button",{className:"btn small ghost",onClick:()=>O(a),children:"导出副本"}),e.jsx("button",{className:"btn small ghost",onClick:()=>U(a),children:"还原"}),e.jsx("button",{className:"btn small ghost",onClick:()=>B(a),children:"编辑"}),e.jsx("button",{className:"btn small ghost danger-ghost",onClick:()=>L(a),children:"删除"}),e.jsx("span",{className:"grow"}),e.jsx(w,{className:"hint",text:a.path,title:"点击复制物理路径",children:"路径"}),e.jsx(w,{className:"hint",text:a.url,title:"点击复制链接",children:"链接"})]})]},a.id)})}):e.jsxs("div",{className:"pad",children:[e.jsx(H,{children:"还没有附件。把文件拖到这里、点「上传文件」,或用「本机路径导入」把原件移动进库;随后到栏目的条目里选它。"}),e.jsx("div",{className:"hint pad",children:'CLI:nx-sk file add --path "D:\\简历\\张三-简历.pdf"(默认移动;--copy 仅复制)'})]}),e.jsx(_,{module:"files"}),d?e.jsx(z,{title:d.kind==="edit"?"编辑附件":"从本机路径导入",onClose:()=>h(null),children:d.kind==="path"?e.jsx(V,{form:d.form,onChange:a=>h(s=>({...s,form:{...s.form,...a}})),onSubmit:()=>R(d.form)}):e.jsx(G,{form:d.form,onChange:a=>h(s=>({...s,form:{...s.form,...a}})),onSubmit:()=>F(d.id,d.form)})}):null]})}function V({form:t,onChange:l,onSubmit:m}){return e.jsxs("div",{className:"stack",children:[e.jsxs("div",{className:"field wide",children:[e.jsx("div",{className:"flabel",children:e.jsx("span",{children:"本机文件的绝对路径"})}),e.jsx("div",{className:"fctrl",children:e.jsx("input",{value:t.path,placeholder:"例如 D:\\简历\\张三-简历.pdf",onChange:n=>l({path:n.target.value})})})]}),e.jsxs("div",{className:"field wide",children:[e.jsx("div",{className:"flabel",children:e.jsx("span",{children:"导入方式"})}),e.jsx("div",{className:"fctrl",children:e.jsxs("label",{className:"inline",children:[e.jsx("input",{type:"checkbox",checked:t.copy,onChange:n=>l({copy:n.target.checked})}),e.jsx("span",{className:"hint",children:"仅复制、保留原件(默认把原件移动进库,可用「还原」撤销)"})]})})]}),e.jsx("div",{className:"rowgap",children:e.jsx("button",{className:"btn",onClick:m,children:"导入"})})]})}function G({form:t,onChange:l,onSubmit:m}){return e.jsxs("div",{className:"stack",children:[e.jsxs("div",{className:"field wide",children:[e.jsx("div",{className:"flabel",children:e.jsx("span",{children:"文件名"})}),e.jsx("div",{className:"fctrl",children:e.jsx("input",{value:t.name,onChange:n=>l({name:n.target.value})})})]}),e.jsxs("div",{className:"field wide",children:[e.jsxs("div",{className:"flabel",children:[e.jsx("span",{children:"标签"}),e.jsx("span",{className:"fkey",children:"多个用「、」分隔"})]}),e.jsx("div",{className:"fctrl",children:e.jsx("input",{value:t.tags,onChange:n=>l({tags:n.target.value})})})]}),e.jsxs("div",{className:"field wide",children:[e.jsx("div",{className:"flabel",children:e.jsx("span",{children:"备注"})}),e.jsx("div",{className:"fctrl",children:e.jsx("textarea",{rows:2,value:t.note,onChange:n=>l({note:n.target.value})})})]}),e.jsxs("div",{className:"rowgap",children:[e.jsx("button",{className:"btn",onClick:m,children:"保存"}),e.jsx("span",{className:"hint",children:"改文件名时,物理原件会随之移动。要链接到条目,请在栏目条目的 file 字段里选本附件。"})]})]})}export{X as default};
@@ -1,4 +1,4 @@
1
- import{j as e,u as K,r as d,a as N,b as B,E as R,C as M,T as _,M as V}from"./index-B7CtqgoG.js";import{l as Y,f as z,r as G}from"./files-BNh48qeo.js";function Q({def:c,value:b,onChange:m}){const{toast:p,guard:i,patchUi:o}=K(),[j,s]=d.useState(null),u=d.useRef(null),h=b||"",C=d.useCallback(async()=>{try{s(await Y())}catch{}},[]);d.useEffect(()=>{C()},[C]);const x=((j==null?void 0:j.files)||[]).find(n=>n.id===h)||null,$=i(async n=>{const g=n==null?void 0:n[0];if(!g)return;const y=await z(g),S=await N("/api/files",{method:"POST",body:{content:y,name:g.name}});m(S.id),p(S.duplicated?"库中已有相同文件,已直接引用":"已上传并关联"),s(await G())});return e.jsxs("div",{className:"file-picker",children:[x?e.jsxs("div",{className:"file-preview",children:[x.isImage?e.jsx("img",{src:x.url,alt:x.name}):e.jsx("span",{className:"ext-badge",style:{fontSize:13,width:40,textAlign:"center"},children:x.ext?x.ext.slice(1).toUpperCase():"FILE"}),e.jsx("a",{className:"file-preview-name grow",href:x.url,target:"_blank",rel:"noreferrer",title:x.displayPath,children:x.name}),e.jsx("a",{className:"btn small ghost",href:x.downloadUrl,children:"下载"}),e.jsx(B,{className:"hint",text:x.path,title:"点击复制物理路径",children:"路径"}),e.jsx("button",{type:"button",className:"btn small ghost",onClick:()=>m(""),children:"清除"})]}):null,e.jsxs("div",{className:"file-pick-row",children:[e.jsxs("select",{value:h,onChange:n=>m(n.target.value),children:[e.jsx("option",{value:"",children:"(未选择:从附件库选一个)"}),((j==null?void 0:j.files)||[]).map(n=>e.jsx("option",{value:n.id,children:n.name},n.id))]}),e.jsx("button",{type:"button",className:"btn small",onClick:()=>{var n;return(n=u.current)==null?void 0:n.click()},children:"上传文件"}),e.jsx("button",{type:"button",className:"btn small ghost",onClick:()=>o({view:"files"}),children:"附件中心"}),e.jsx("input",{ref:u,type:"file",style:{display:"none"},onChange:n=>{$(n.target.files),n.target.value=""}})]}),h&&!x?e.jsxs("span",{className:"hint bad",children:["引用的附件已不存在(",h,")—— 重新上传,或清除。"]}):null,c.hint?e.jsx("span",{className:"hint",children:c.hint}):null]})}function W({def:c,value:b,onChange:m,disabled:p}){const i=c.type||"text",o=b??"";if(i==="file")return e.jsx(Q,{def:c,value:o,onChange:m});if(i==="bool"){const s=o===!0||o==="true"||o==="是";return e.jsxs("label",{className:"inline",children:[e.jsx("input",{type:"checkbox",checked:s,onChange:u=>m(u.target.checked)}),e.jsx("span",{className:"muted",children:s?"是":"否"})]})}if(i==="select"){const s=c.options||[],u=o!==""&&!s.includes(o);return e.jsxs("select",{value:u?"__custom":String(o),onChange:h=>m(h.target.value==="__custom"?"":h.target.value),children:[e.jsx("option",{value:"",children:"(未填写)"}),s.map(h=>e.jsx("option",{value:h,children:h},h)),u?e.jsxs("option",{value:"__custom",children:[String(o),"(当前值,不在候选里)"]}):null]})}if(i==="textarea")return e.jsx("textarea",{value:String(o),onChange:s=>m(s.target.value),rows:3});if(i==="tags"){const s=Array.isArray(o)?o.join("、"):String(o);return e.jsx("input",{value:s,placeholder:c.maxItems?`多个用「、」分隔,最多 ${c.maxItems} 个`:"多个用「、」分隔",onChange:u=>m(u.target.value.split(/[,,、;;]+/).map(h=>h.trim()).filter(Boolean))})}const j=i==="number"?"number":i==="date"?"date":i==="month"?"month":"text";return i==="secret"?e.jsx("input",{type:"text",value:String(o),placeholder:"密文落盘;原样保留占位则视为不修改",disabled:p,onChange:s=>m(s.target.value)}):e.jsx("input",{type:j,value:String(o),onChange:s=>m(s.target.value)})}function J({title:c,fields:b,draft:m,onPatch:p}){return e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"group-title",children:c}),e.jsx("div",{className:"pad",children:e.jsx("div",{className:"grid",children:b.map(i=>e.jsxs("div",{className:`field ${i.type==="textarea"||i.type==="file"?"wide":""}`,children:[e.jsxs("div",{className:"flabel",children:[e.jsx("span",{children:i.label}),i.sensitive?e.jsx("span",{className:"tag bad",children:"密文"}):null,i.fill&&i.fill!=="normal"?e.jsx("span",{className:"tag",children:i.fillLabel||i.fill}):null,e.jsx("span",{className:"fkey",children:i.key})]}),e.jsx("div",{className:"fctrl",children:e.jsx(W,{def:i,value:m[i.key],onChange:o=>p(i.key,o)})})]},i.key))})})]})}function X({sectionId:c,sectionTitle:b}){const{ui:m,patchUi:p,toast:i,guard:o,dialog:j,reload:s}=K(),[u,h]=d.useState(null),[C,x]=d.useState({}),[$,n]=d.useState({name:"",value:""}),g=!!m.mask,y=d.useCallback(async()=>{try{const l=`section=${encodeURIComponent(c)}${g?"&mask=1":""}`,a=await N(`/api/keys?${l}`);h(a);const v={};for(const E of a.keys)v[E.name]=E.value??"";x(v)}catch(l){i(l.message,"bad"),h(null)}},[c,g,i]);d.useEffect(()=>{y()},[y]);const S=o(async l=>{const a=C[l.name];(l.value??"")!==a&&(await N(`/api/keys/${encodeURIComponent(l.name)}`,{method:"PUT",body:{value:a,section:c}}),i(`已保存 ${l.name}`),await y())}),T=o(async(l,a)=>{const v=String(a||"").trim();if(!(!v||v===l.name)){if(u.keys.some(E=>E.name===v)){i(`已经有叫「${v}」的键了`,"bad"),await y();return}await N(`/api/entries/${encodeURIComponent(l.id)}`,{method:"PATCH",body:{title:v}}),x(E=>{const k={};for(const[D,F]of Object.entries(E))k[D===l.name?v:D]=F;return k}),i(`已重命名为 ${v}`),await y(),await s()}}),U=o(async()=>{const l=$.name.trim();if(!l){i("先填键名,例如 OPENAI_KEY","bad");return}if(u.keys.some(a=>a.name===l)){i(`已经有叫「${l}」的键了——直接在上面那行改值即可`,"bad");return}await N(`/api/keys/${encodeURIComponent(l)}`,{method:"PUT",body:{value:$.value,section:c}}),n({name:"",value:""}),i(`已新增 ${l}`),await y(),await s()}),A=o(async l=>{await j({message:`删除「${l.name}」?删除前会自动留一份整库快照在 ~/nx-sk/backup。`,danger:!0,confirmText:"删除"})&&(await N(`/api/keys/${encodeURIComponent(l.name)}`,{method:"DELETE",body:{section:c}}),i(`已删除 ${l.name}`),await y(),await s())});return u?e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"toolbar rowgap pad",children:[e.jsx("strong",{children:b}),e.jsxs("span",{className:"muted",children:[u.count," 个键 · 名称 → 值"]}),e.jsx("span",{className:"grow"}),e.jsxs("label",{className:"inline hint",children:[e.jsx("input",{type:"checkbox",checked:g,onChange:()=>p({mask:!g})}),"打码显示(投屏/截图时用)"]}),e.jsx("button",{className:"btn small ghost",onClick:y,children:"刷新"})]}),e.jsxs("div",{className:"card",children:[e.jsxs("div",{className:"colhead",children:[e.jsx("span",{style:{width:200},children:"名称"}),e.jsx("span",{className:"grow",children:"值"})]}),e.jsxs("div",{className:"colbody",children:[u.keys.length?u.keys.map(l=>e.jsxs("div",{className:"row",children:[e.jsx("input",{className:"mono",style:{width:200},defaultValue:l.name,title:"改完按回车或点开别处即重命名",onBlur:a=>T(l,a.target.value),onKeyDown:a=>{a.key==="Enter"&&a.currentTarget.blur()}}),e.jsx("input",{className:"mono grow",value:C[l.name]??"",onChange:a=>x(v=>({...v,[l.name]:a.target.value})),onBlur:()=>S(l),onKeyDown:a=>{a.key==="Enter"&&a.currentTarget.blur()}}),e.jsxs("span",{className:"acts",children:[e.jsx(B,{className:"tag",text:C[l.name]??"",title:"点击复制值",children:"复制"}),e.jsx("button",{className:"btn small ghost",onClick:()=>A(l),children:"删除"})]})]},l.id)):e.jsx(R,{children:"还没有密钥。在下面一行填「名称 / 值」按添加,或 CLI:nx-sk key set OPENAI_KEY sk-xxx"}),e.jsxs("div",{className:"row",children:[e.jsx("input",{className:"mono",style:{width:200},placeholder:"新键名,例如 OPENAI_KEY",value:$.name,onChange:l=>n(a=>({...a,name:l.target.value})),onKeyDown:l=>{l.key==="Enter"&&U()}}),e.jsx("input",{className:"mono grow",placeholder:"值(留空也行,之后再填)",value:$.value,onChange:l=>n(a=>({...a,value:l.target.value})),onKeyDown:l=>{l.key==="Enter"&&U()}}),e.jsx("span",{className:"acts",children:e.jsx("button",{className:"btn small",onClick:U,children:"添加"})})]})]})]}),e.jsx("div",{className:"pad hint",children:"改值:直接在「值」列改完按回车(或点开别处)即保存。改键名同理。 值以密文落盘,这里显示的是原文;要打码勾上方那个框。删除前自动整库快照。"}),e.jsx(M,{module:"entries"})]}):e.jsx("div",{className:"pad muted",children:"加载中…"})}const O=c=>c==null?"":Array.isArray(c)?c.join("、"):String(c);function se({sectionId:c}){var I;const{ui:b,patchUi:m,toast:p,guard:i,dialog:o,reload:j}=K(),[s,u]=d.useState(null),[h,C]=d.useState(null),[x,$]=d.useState(!1),[n,g]=d.useState(null),[y,S]=d.useState(null),[T,U]=d.useState(""),A=!!b.mask,l=d.useCallback(async t=>{$(!0),C(null);try{const r=await N(`/api/sections/${encodeURIComponent(c)}/dump${A?"?mask=1":""}`);u(r);const w=t||b.entry,f=r.entries.find(P=>P.id===w)||r.entries[0]||null;g(f?{id:f.id,title:f.title,values:{...f.values}}:null),f&&f.id!==b.entry&&m({entry:f.id})}catch(r){C(r.message),u(null),g(null)}finally{$(!1)}},[c,A]);d.useEffect(()=>{l()},[l]);const a=d.useMemo(()=>(s==null?void 0:s.entries.find(t=>t.id===(n==null?void 0:n.id)))||null,[s,n]),v=d.useMemo(()=>s?s.groups.map(t=>({...t,fields:s.fields.filter(r=>r.group===t.id)})).filter(t=>t.fields.length):[],[s]),E=(t,r)=>g(w=>w&&{...w,values:{...w.values,[t]:r}}),k=d.useMemo(()=>{if(!n||!a)return{set:{},titleChanged:!1,count:0};const t={};for(const r of s.fields)O(a.values[r.key])!==O(n.values[r.key])&&(t[r.key]=n.values[r.key]);return{set:t,titleChanged:n.title!==a.title,count:Object.keys(t).length+(n.title!==a.title?1:0)}},[n,a,s]),D=i(async()=>{if(!n||!a)return;if(!k.count){p("没有改动");return}const t={set:k.set};k.titleChanged&&(t.title=n.title),await N(`/api/entries/${encodeURIComponent(n.id)}`,{method:"PATCH",body:t}),p(`已保存 ${k.count} 处改动`),await l(n.id),await j()}),F=i(async()=>{var P;if(!n){p("先新建一条条目","bad");return}const t=T.indexOf("=");if(t<=0){p("用 字段=值 的格式,例如 微信=zhangsan","bad");return}const r=T.slice(0,t).trim(),w=T.slice(t+1),f=await N(`/api/entries/${encodeURIComponent(n.id)}`,{method:"PATCH",body:{set:{[r]:w}}});U(""),p(`已写入 ${r}${(P=f.newFields)!=null&&P.length?"(新字段已登记)":""}`),await l(n.id),await j()}),H=i(async()=>{const t=(s==null?void 0:s.section.titleLabel)||"名称",r=await o({message:`新建「${s==null?void 0:s.section.title}」条目,${t}是?`,input:{placeholder:`例如 ${(s==null?void 0:s.section.titleField)||"名称"}`}});if(!r)return;const w={section:c,title:r};s!=null&&s.section.titleField&&(w.set={[s.section.titleField]:r});const f=await N("/api/entries",{method:"POST",body:w});p(`已新建:${f.title}`),await l(f.id),await j()}),L=i(async()=>{!a||!await o({message:`删除条目「${a.title}」?删除前会自动留一份快照在 ~/nx-sk/backup。`,danger:!0,confirmText:"删除"})||(await N(`/api/entries/${encodeURIComponent(a.id)}`,{method:"DELETE"}),p("已删除"),await l(""),await j())}),q=i(async()=>{const t=await N("/api/export",{method:"POST",body:{section:c,format:"both"}});S({title:"导出结果",body:e.jsx("pre",{className:"block",children:`目录: ${t.dir}
1
+ import{j as e,u as K,r as d,a as N,b as B,E as R,C as M,T as _,M as V}from"./index-BygHBMGo.js";import{l as Y,f as z,r as G}from"./files-DcDiKjwc.js";function Q({def:c,value:b,onChange:m}){const{toast:p,guard:i,patchUi:o}=K(),[j,s]=d.useState(null),u=d.useRef(null),h=b||"",C=d.useCallback(async()=>{try{s(await Y())}catch{}},[]);d.useEffect(()=>{C()},[C]);const x=((j==null?void 0:j.files)||[]).find(n=>n.id===h)||null,$=i(async n=>{const g=n==null?void 0:n[0];if(!g)return;const y=await z(g),S=await N("/api/files",{method:"POST",body:{content:y,name:g.name}});m(S.id),p(S.duplicated?"库中已有相同文件,已直接引用":"已上传并关联"),s(await G())});return e.jsxs("div",{className:"file-picker",children:[x?e.jsxs("div",{className:"file-preview",children:[x.isImage?e.jsx("img",{src:x.url,alt:x.name}):e.jsx("span",{className:"ext-badge",style:{fontSize:13,width:40,textAlign:"center"},children:x.ext?x.ext.slice(1).toUpperCase():"FILE"}),e.jsx("a",{className:"file-preview-name grow",href:x.url,target:"_blank",rel:"noreferrer",title:x.displayPath,children:x.name}),e.jsx("a",{className:"btn small ghost",href:x.downloadUrl,children:"下载"}),e.jsx(B,{className:"hint",text:x.path,title:"点击复制物理路径",children:"路径"}),e.jsx("button",{type:"button",className:"btn small ghost",onClick:()=>m(""),children:"清除"})]}):null,e.jsxs("div",{className:"file-pick-row",children:[e.jsxs("select",{value:h,onChange:n=>m(n.target.value),children:[e.jsx("option",{value:"",children:"(未选择:从附件库选一个)"}),((j==null?void 0:j.files)||[]).map(n=>e.jsx("option",{value:n.id,children:n.name},n.id))]}),e.jsx("button",{type:"button",className:"btn small",onClick:()=>{var n;return(n=u.current)==null?void 0:n.click()},children:"上传文件"}),e.jsx("button",{type:"button",className:"btn small ghost",onClick:()=>o({view:"files"}),children:"附件中心"}),e.jsx("input",{ref:u,type:"file",style:{display:"none"},onChange:n=>{$(n.target.files),n.target.value=""}})]}),h&&!x?e.jsxs("span",{className:"hint bad",children:["引用的附件已不存在(",h,")—— 重新上传,或清除。"]}):null,c.hint?e.jsx("span",{className:"hint",children:c.hint}):null]})}function W({def:c,value:b,onChange:m,disabled:p}){const i=c.type||"text",o=b??"";if(i==="file")return e.jsx(Q,{def:c,value:o,onChange:m});if(i==="bool"){const s=o===!0||o==="true"||o==="是";return e.jsxs("label",{className:"inline",children:[e.jsx("input",{type:"checkbox",checked:s,onChange:u=>m(u.target.checked)}),e.jsx("span",{className:"muted",children:s?"是":"否"})]})}if(i==="select"){const s=c.options||[],u=o!==""&&!s.includes(o);return e.jsxs("select",{value:u?"__custom":String(o),onChange:h=>m(h.target.value==="__custom"?"":h.target.value),children:[e.jsx("option",{value:"",children:"(未填写)"}),s.map(h=>e.jsx("option",{value:h,children:h},h)),u?e.jsxs("option",{value:"__custom",children:[String(o),"(当前值,不在候选里)"]}):null]})}if(i==="textarea")return e.jsx("textarea",{value:String(o),onChange:s=>m(s.target.value),rows:3});if(i==="tags"){const s=Array.isArray(o)?o.join("、"):String(o);return e.jsx("input",{value:s,placeholder:c.maxItems?`多个用「、」分隔,最多 ${c.maxItems} 个`:"多个用「、」分隔",onChange:u=>m(u.target.value.split(/[,,、;;]+/).map(h=>h.trim()).filter(Boolean))})}const j=i==="number"?"number":i==="date"?"date":i==="month"?"month":"text";return i==="secret"?e.jsx("input",{type:"text",value:String(o),placeholder:"密文落盘;原样保留占位则视为不修改",disabled:p,onChange:s=>m(s.target.value)}):e.jsx("input",{type:j,value:String(o),onChange:s=>m(s.target.value)})}function J({title:c,fields:b,draft:m,onPatch:p}){return e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"group-title",children:c}),e.jsx("div",{className:"pad",children:e.jsx("div",{className:"grid",children:b.map(i=>e.jsxs("div",{className:`field ${i.type==="textarea"||i.type==="file"?"wide":""}`,children:[e.jsxs("div",{className:"flabel",children:[e.jsx("span",{children:i.label}),i.sensitive?e.jsx("span",{className:"tag bad",children:"密文"}):null,i.fill&&i.fill!=="normal"?e.jsx("span",{className:"tag",children:i.fillLabel||i.fill}):null,e.jsx("span",{className:"fkey",children:i.key})]}),e.jsx("div",{className:"fctrl",children:e.jsx(W,{def:i,value:m[i.key],onChange:o=>p(i.key,o)})})]},i.key))})})]})}function X({sectionId:c,sectionTitle:b}){const{ui:m,patchUi:p,toast:i,guard:o,dialog:j,reload:s}=K(),[u,h]=d.useState(null),[C,x]=d.useState({}),[$,n]=d.useState({name:"",value:""}),g=!!m.mask,y=d.useCallback(async()=>{try{const l=`section=${encodeURIComponent(c)}${g?"&mask=1":""}`,a=await N(`/api/keys?${l}`);h(a);const v={};for(const E of a.keys)v[E.name]=E.value??"";x(v)}catch(l){i(l.message,"bad"),h(null)}},[c,g,i]);d.useEffect(()=>{y()},[y]);const S=o(async l=>{const a=C[l.name];(l.value??"")!==a&&(await N(`/api/keys/${encodeURIComponent(l.name)}`,{method:"PUT",body:{value:a,section:c}}),i(`已保存 ${l.name}`),await y())}),T=o(async(l,a)=>{const v=String(a||"").trim();if(!(!v||v===l.name)){if(u.keys.some(E=>E.name===v)){i(`已经有叫「${v}」的键了`,"bad"),await y();return}await N(`/api/entries/${encodeURIComponent(l.id)}`,{method:"PATCH",body:{title:v}}),x(E=>{const k={};for(const[D,F]of Object.entries(E))k[D===l.name?v:D]=F;return k}),i(`已重命名为 ${v}`),await y(),await s()}}),U=o(async()=>{const l=$.name.trim();if(!l){i("先填键名,例如 OPENAI_KEY","bad");return}if(u.keys.some(a=>a.name===l)){i(`已经有叫「${l}」的键了——直接在上面那行改值即可`,"bad");return}await N(`/api/keys/${encodeURIComponent(l)}`,{method:"PUT",body:{value:$.value,section:c}}),n({name:"",value:""}),i(`已新增 ${l}`),await y(),await s()}),A=o(async l=>{await j({message:`删除「${l.name}」?删除前会自动留一份整库快照在 ~/nx-sk/backup。`,danger:!0,confirmText:"删除"})&&(await N(`/api/keys/${encodeURIComponent(l.name)}`,{method:"DELETE",body:{section:c}}),i(`已删除 ${l.name}`),await y(),await s())});return u?e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"toolbar rowgap pad",children:[e.jsx("strong",{children:b}),e.jsxs("span",{className:"muted",children:[u.count," 个键 · 名称 → 值"]}),e.jsx("span",{className:"grow"}),e.jsxs("label",{className:"inline hint",children:[e.jsx("input",{type:"checkbox",checked:g,onChange:()=>p({mask:!g})}),"打码显示(投屏/截图时用)"]}),e.jsx("button",{className:"btn small ghost",onClick:y,children:"刷新"})]}),e.jsxs("div",{className:"card",children:[e.jsxs("div",{className:"colhead",children:[e.jsx("span",{style:{width:200},children:"名称"}),e.jsx("span",{className:"grow",children:"值"})]}),e.jsxs("div",{className:"colbody",children:[u.keys.length?u.keys.map(l=>e.jsxs("div",{className:"row",children:[e.jsx("input",{className:"mono",style:{width:200},defaultValue:l.name,title:"改完按回车或点开别处即重命名",onBlur:a=>T(l,a.target.value),onKeyDown:a=>{a.key==="Enter"&&a.currentTarget.blur()}}),e.jsx("input",{className:"mono grow",value:C[l.name]??"",onChange:a=>x(v=>({...v,[l.name]:a.target.value})),onBlur:()=>S(l),onKeyDown:a=>{a.key==="Enter"&&a.currentTarget.blur()}}),e.jsxs("span",{className:"acts",children:[e.jsx(B,{className:"tag",text:C[l.name]??"",title:"点击复制值",children:"复制"}),e.jsx("button",{className:"btn small ghost",onClick:()=>A(l),children:"删除"})]})]},l.id)):e.jsx(R,{children:"还没有密钥。在下面一行填「名称 / 值」按添加,或 CLI:nx-sk key set OPENAI_KEY sk-xxx"}),e.jsxs("div",{className:"row",children:[e.jsx("input",{className:"mono",style:{width:200},placeholder:"新键名,例如 OPENAI_KEY",value:$.name,onChange:l=>n(a=>({...a,name:l.target.value})),onKeyDown:l=>{l.key==="Enter"&&U()}}),e.jsx("input",{className:"mono grow",placeholder:"值(留空也行,之后再填)",value:$.value,onChange:l=>n(a=>({...a,value:l.target.value})),onKeyDown:l=>{l.key==="Enter"&&U()}}),e.jsx("span",{className:"acts",children:e.jsx("button",{className:"btn small",onClick:U,children:"添加"})})]})]})]}),e.jsx("div",{className:"pad hint",children:"改值:直接在「值」列改完按回车(或点开别处)即保存。改键名同理。 值以密文落盘,这里显示的是原文;要打码勾上方那个框。删除前自动整库快照。"}),e.jsx(M,{module:"entries"})]}):e.jsx("div",{className:"pad muted",children:"加载中…"})}const O=c=>c==null?"":Array.isArray(c)?c.join("、"):String(c);function se({sectionId:c}){var I;const{ui:b,patchUi:m,toast:p,guard:i,dialog:o,reload:j}=K(),[s,u]=d.useState(null),[h,C]=d.useState(null),[x,$]=d.useState(!1),[n,g]=d.useState(null),[y,S]=d.useState(null),[T,U]=d.useState(""),A=!!b.mask,l=d.useCallback(async t=>{$(!0),C(null);try{const r=await N(`/api/sections/${encodeURIComponent(c)}/dump${A?"?mask=1":""}`);u(r);const w=t||b.entry,f=r.entries.find(P=>P.id===w)||r.entries[0]||null;g(f?{id:f.id,title:f.title,values:{...f.values}}:null),f&&f.id!==b.entry&&m({entry:f.id})}catch(r){C(r.message),u(null),g(null)}finally{$(!1)}},[c,A]);d.useEffect(()=>{l()},[l]);const a=d.useMemo(()=>(s==null?void 0:s.entries.find(t=>t.id===(n==null?void 0:n.id)))||null,[s,n]),v=d.useMemo(()=>s?s.groups.map(t=>({...t,fields:s.fields.filter(r=>r.group===t.id)})).filter(t=>t.fields.length):[],[s]),E=(t,r)=>g(w=>w&&{...w,values:{...w.values,[t]:r}}),k=d.useMemo(()=>{if(!n||!a)return{set:{},titleChanged:!1,count:0};const t={};for(const r of s.fields)O(a.values[r.key])!==O(n.values[r.key])&&(t[r.key]=n.values[r.key]);return{set:t,titleChanged:n.title!==a.title,count:Object.keys(t).length+(n.title!==a.title?1:0)}},[n,a,s]),D=i(async()=>{if(!n||!a)return;if(!k.count){p("没有改动");return}const t={set:k.set};k.titleChanged&&(t.title=n.title),await N(`/api/entries/${encodeURIComponent(n.id)}`,{method:"PATCH",body:t}),p(`已保存 ${k.count} 处改动`),await l(n.id),await j()}),F=i(async()=>{var P;if(!n){p("先新建一条条目","bad");return}const t=T.indexOf("=");if(t<=0){p("用 字段=值 的格式,例如 微信=zhangsan","bad");return}const r=T.slice(0,t).trim(),w=T.slice(t+1),f=await N(`/api/entries/${encodeURIComponent(n.id)}`,{method:"PATCH",body:{set:{[r]:w}}});U(""),p(`已写入 ${r}${(P=f.newFields)!=null&&P.length?"(新字段已登记)":""}`),await l(n.id),await j()}),H=i(async()=>{const t=(s==null?void 0:s.section.titleLabel)||"名称",r=await o({message:`新建「${s==null?void 0:s.section.title}」条目,${t}是?`,input:{placeholder:`例如 ${(s==null?void 0:s.section.titleField)||"名称"}`}});if(!r)return;const w={section:c,title:r};s!=null&&s.section.titleField&&(w.set={[s.section.titleField]:r});const f=await N("/api/entries",{method:"POST",body:w});p(`已新建:${f.title}`),await l(f.id),await j()}),L=i(async()=>{!a||!await o({message:`删除条目「${a.title}」?删除前会自动留一份快照在 ~/nx-sk/backup。`,danger:!0,confirmText:"删除"})||(await N(`/api/entries/${encodeURIComponent(a.id)}`,{method:"DELETE"}),p("已删除"),await l(""),await j())}),q=i(async()=>{const t=await N("/api/export",{method:"POST",body:{section:c,format:"both"}});S({title:"导出结果",body:e.jsx("pre",{className:"block",children:`目录: ${t.dir}
2
2
  格式: ${t.format}
3
3
  条目: ${t.entries}
4
4
  凭据: ${t.withSecrets?"含明文":"已打码"}
@@ -1 +1 @@
1
- import{u as b,r as d,a as k,j as s,b as r,E as F,C as I,T as $}from"./index-B7CtqgoG.js";function D(){var N,v,u,g,f,w,y,S;const{boot:e,reload:t,toast:n,guard:C}=b(),[o,h]=d.useState(null),[l,i]=d.useState(null),j=typeof window>"u"?"":window.location.origin,p=typeof window>"u"?"":window.location.port||"80",m=d.useCallback(async()=>{try{const a=await k("/api/settings");h(a),i({...a.settings})}catch(a){n(a.message,"bad")}},[n]);d.useEffect(()=>{m()},[m]);const E=C(async()=>{const a={exportDir:l.exportDir,exportFormat:l.exportFormat,includeSecretsInExport:!!l.includeSecretsInExport,defaultSection:l.defaultSection},x=await k("/api/settings",{method:"PATCH",body:a});n("设置已保存"),h({...o,settings:x.set}),await t()});return!o||!l?s.jsx("div",{className:"pad muted",children:"加载中…"}):s.jsxs(s.Fragment,{children:[s.jsxs("div",{className:"toolbar rowgap pad",children:[s.jsx("strong",{children:"设置"}),s.jsx("span",{className:"muted",children:"本机配置与运行状态"}),s.jsx("span",{className:"grow"}),s.jsx("button",{className:"btn small ghost",onClick:m,children:"重新读取"})]}),s.jsxs("div",{className:"card",children:[s.jsx("div",{className:"colhead",children:"存储与运行"}),s.jsxs("div",{className:"colbody",children:[s.jsx(c,{label:"数据目录",value:e==null?void 0:e.home,copy:e==null?void 0:e.home}),s.jsx(c,{label:"store.json",value:e==null?void 0:e.storePath,copy:e==null?void 0:e.storePathRaw}),s.jsx(c,{label:"栏目 / 条目",value:e?`${e.counts.sections} / ${e.counts.entries}`:""}),s.jsx(c,{label:"命令条数",value:e?String(e.counts.commands):""}),s.jsx(c,{label:"app / node",value:e?`v${e.app.version} · node ${e.app.node} · ${e.app.platform}`:""}),s.jsx(c,{label:"环境变量覆盖",value:e?Object.entries(e.envOverrides).filter(([,a])=>a).map(([a,x])=>`${a}=${x}`).join(" ")||"(无)":""})]})]}),s.jsxs("div",{className:"card",children:[s.jsx("div",{className:"colhead",children:"密钥(密文落盘)"}),s.jsxs("div",{className:"colbody",children:[s.jsx(c,{label:"密钥来源",value:((N=e==null?void 0:e.vault)==null?void 0:N.source)==="env"?"环境变量 NX_SK_PASSPHRASE(口令不落盘)":"本机密钥文件"}),s.jsx(c,{label:"密钥文件",value:((v=e==null?void 0:e.vault)==null?void 0:v.keyFile)||((u=e==null?void 0:e.vault)==null?void 0:u.saltFile)||"",copy:((g=e==null?void 0:e.vault)==null?void 0:g.keyFile)||((f=e==null?void 0:e.vault)==null?void 0:f.saltFile)}),s.jsx("div",{className:"row",children:s.jsx("span",{className:"desc",children:(w=e==null?void 0:e.vault)==null?void 0:w.note})}),s.jsx("div",{className:"row",children:s.jsx("span",{className:"desc bad",children:"删掉密钥文件或换掉口令后,已存的密文都解不开了——导出前先确认密钥可用。"})})]})]}),s.jsxs("div",{className:"card",children:[s.jsx("div",{className:"colhead",children:"面板与 CLI"}),s.jsxs("div",{className:"colbody",children:[s.jsxs("div",{className:"row",children:[s.jsx("span",{className:"name",children:"面板地址"}),s.jsx("span",{className:"desc mono",children:j}),s.jsx("span",{className:"acts",children:s.jsx(r,{className:"mono",text:j,children:"复制"})})]}),s.jsxs("div",{className:"row",children:[s.jsx("span",{className:"name",children:"重启面板"}),s.jsx("span",{className:"desc",children:"面板本身就是 serve 提供的;改代码后重启它即可"}),s.jsx("span",{className:"acts",children:s.jsx(r,{className:"mono",text:`nx-sk serve --port ${p}`,children:`nx-sk serve --port ${p}`})})]})]})]}),s.jsxs("div",{className:"card",children:[s.jsx("div",{className:"colhead",children:"默认行为"}),s.jsxs("div",{className:"pad",children:[s.jsxs("div",{className:"grid",children:[s.jsxs("div",{className:"field",children:[s.jsx("div",{className:"flabel",children:s.jsx("span",{children:"默认导出格式"})}),s.jsx("div",{className:"fctrl",children:s.jsxs("select",{value:l.exportFormat,onChange:a=>i({...l,exportFormat:a.target.value}),children:[s.jsx("option",{value:"both",children:"JSON + Markdown"}),s.jsx("option",{value:"json",children:"仅 JSON"}),s.jsx("option",{value:"md",children:"仅 Markdown"})]})})]}),s.jsxs("div",{className:"field",children:[s.jsxs("div",{className:"flabel",children:[s.jsx("span",{children:"默认导出目录"}),s.jsx("span",{className:"fkey",children:"留空 = ~/nx-sk/export"})]}),s.jsx("div",{className:"fctrl",children:s.jsx("input",{value:l.exportDir,onChange:a=>i({...l,exportDir:a.target.value})})})]}),s.jsxs("div",{className:"field",children:[s.jsxs("div",{className:"flabel",children:[s.jsx("span",{children:"默认栏目"}),s.jsx("span",{className:"fkey",children:"CLI 未指定 --section 时的首选"})]}),s.jsx("div",{className:"fctrl",children:s.jsx("input",{value:l.defaultSection,onChange:a=>i({...l,defaultSection:a.target.value})})})]}),s.jsxs("div",{className:"field",children:[s.jsxs("div",{className:"flabel",children:[s.jsx("span",{children:"导出默认带明文密钥"}),s.jsx("span",{className:"fkey",children:"默认开;要分享出去时关掉"})]}),s.jsx("div",{className:"fctrl",children:s.jsxs("label",{className:"inline",children:[s.jsx("input",{type:"checkbox",checked:!!l.includeSecretsInExport,onChange:a=>i({...l,includeSecretsInExport:a.target.checked})}),s.jsx("span",{className:l.includeSecretsInExport?"muted":"bad",children:l.includeSecretsInExport?"开(导出文件含真实凭据,注意别外发)":"关(凭据打码后再导出)"})]})})]})]}),s.jsx("div",{className:"rowgap",style:{marginTop:8},children:s.jsx("button",{className:"btn",onClick:E,children:"保存设置"})})]})]}),s.jsxs("div",{className:"card",children:[s.jsxs("div",{className:"colhead",children:["自动快照",s.jsx("span",{className:"right muted",children:((y=e==null?void 0:e.snapshots)==null?void 0:y.length)??0})]}),s.jsxs("div",{className:"colbody",children:[s.jsx("div",{className:"row",children:s.jsx("span",{className:"desc",children:"每次删除条目 / 栏目、修改设置前,都会在 ~/nx-sk/backup 留一份可回滚的整库快照。"})}),(S=e==null?void 0:e.snapshots)!=null&&S.length?e.snapshots.map(a=>s.jsxs("div",{className:"row",children:[s.jsx("span",{className:"mono name",children:a.name}),s.jsx("span",{className:"desc",children:a.at}),s.jsx("span",{className:"acts",children:s.jsxs("span",{className:"tag",children:[Math.ceil(a.bytes/1024)," KB"]})})]},a.name)):s.jsx(F,{children:"还没有快照。"})]})]}),e!=null&&e.migration?s.jsxs("div",{className:"card",children:[s.jsx("div",{className:"colhead",children:"数据迁移"}),s.jsxs("div",{className:"colbody",children:[s.jsx("div",{className:"row",children:s.jsxs("span",{className:"desc",children:["store 已从 v",e.migration.from," 迁到 v",e.migration.to,":密钥栏目字段 ",e.migration.fieldsBefore," → ",e.migration.fieldsAfter,",",e.migration.entries," 条条目"]})}),s.jsxs("div",{className:"row",children:[s.jsx("span",{className:"desc",children:"迁移前的原始文件"}),s.jsx("span",{className:"acts",children:s.jsx(r,{className:"mono",text:e.migration.snapshotRaw||"",children:e.migration.snapshot})})]}),e.migration.legacyValuesKept?s.jsx("div",{className:"row",children:s.jsxs("span",{className:"desc",children:["另保留了 ",e.migration.legacyValuesKept," 个旧字段的值(不再显示,但没删)"]})}):null]})]}):null,e!=null&&e.corrupt?s.jsxs("div",{className:"card",children:[s.jsx("div",{className:"colhead",children:"上次读取异常"}),s.jsx("div",{className:"colbody",children:s.jsx("div",{className:"row",children:s.jsxs("span",{className:"desc bad",children:["store.json 解析失败,已挪到 ",e.corrupt.to,"(原文件没有被覆盖)"]})})})]}):null,s.jsx(I,{module:"settings"})]})}function c({label:e,value:t,copy:n}){return s.jsxs("div",{className:"row",children:[s.jsx("span",{className:"name",children:e}),s.jsx("span",{className:"desc mono",children:t||"(空)"}),n?s.jsx("span",{className:"acts",children:s.jsx(r,{className:"mono",text:n,children:"复制"})}):s.jsx($,{children:" "})]})}export{D as default};
1
+ import{u as b,r as d,a as k,j as s,b as r,E as F,C as I,T as $}from"./index-BygHBMGo.js";function D(){var N,v,u,g,f,w,y,S;const{boot:e,reload:t,toast:n,guard:C}=b(),[o,h]=d.useState(null),[l,i]=d.useState(null),j=typeof window>"u"?"":window.location.origin,p=typeof window>"u"?"":window.location.port||"80",m=d.useCallback(async()=>{try{const a=await k("/api/settings");h(a),i({...a.settings})}catch(a){n(a.message,"bad")}},[n]);d.useEffect(()=>{m()},[m]);const E=C(async()=>{const a={exportDir:l.exportDir,exportFormat:l.exportFormat,includeSecretsInExport:!!l.includeSecretsInExport,defaultSection:l.defaultSection},x=await k("/api/settings",{method:"PATCH",body:a});n("设置已保存"),h({...o,settings:x.set}),await t()});return!o||!l?s.jsx("div",{className:"pad muted",children:"加载中…"}):s.jsxs(s.Fragment,{children:[s.jsxs("div",{className:"toolbar rowgap pad",children:[s.jsx("strong",{children:"设置"}),s.jsx("span",{className:"muted",children:"本机配置与运行状态"}),s.jsx("span",{className:"grow"}),s.jsx("button",{className:"btn small ghost",onClick:m,children:"重新读取"})]}),s.jsxs("div",{className:"card",children:[s.jsx("div",{className:"colhead",children:"存储与运行"}),s.jsxs("div",{className:"colbody",children:[s.jsx(c,{label:"数据目录",value:e==null?void 0:e.home,copy:e==null?void 0:e.home}),s.jsx(c,{label:"store.json",value:e==null?void 0:e.storePath,copy:e==null?void 0:e.storePathRaw}),s.jsx(c,{label:"栏目 / 条目",value:e?`${e.counts.sections} / ${e.counts.entries}`:""}),s.jsx(c,{label:"命令条数",value:e?String(e.counts.commands):""}),s.jsx(c,{label:"app / node",value:e?`v${e.app.version} · node ${e.app.node} · ${e.app.platform}`:""}),s.jsx(c,{label:"环境变量覆盖",value:e?Object.entries(e.envOverrides).filter(([,a])=>a).map(([a,x])=>`${a}=${x}`).join(" ")||"(无)":""})]})]}),s.jsxs("div",{className:"card",children:[s.jsx("div",{className:"colhead",children:"密钥(密文落盘)"}),s.jsxs("div",{className:"colbody",children:[s.jsx(c,{label:"密钥来源",value:((N=e==null?void 0:e.vault)==null?void 0:N.source)==="env"?"环境变量 NX_SK_PASSPHRASE(口令不落盘)":"本机密钥文件"}),s.jsx(c,{label:"密钥文件",value:((v=e==null?void 0:e.vault)==null?void 0:v.keyFile)||((u=e==null?void 0:e.vault)==null?void 0:u.saltFile)||"",copy:((g=e==null?void 0:e.vault)==null?void 0:g.keyFile)||((f=e==null?void 0:e.vault)==null?void 0:f.saltFile)}),s.jsx("div",{className:"row",children:s.jsx("span",{className:"desc",children:(w=e==null?void 0:e.vault)==null?void 0:w.note})}),s.jsx("div",{className:"row",children:s.jsx("span",{className:"desc bad",children:"删掉密钥文件或换掉口令后,已存的密文都解不开了——导出前先确认密钥可用。"})})]})]}),s.jsxs("div",{className:"card",children:[s.jsx("div",{className:"colhead",children:"面板与 CLI"}),s.jsxs("div",{className:"colbody",children:[s.jsxs("div",{className:"row",children:[s.jsx("span",{className:"name",children:"面板地址"}),s.jsx("span",{className:"desc mono",children:j}),s.jsx("span",{className:"acts",children:s.jsx(r,{className:"mono",text:j,children:"复制"})})]}),s.jsxs("div",{className:"row",children:[s.jsx("span",{className:"name",children:"重启面板"}),s.jsx("span",{className:"desc",children:"面板本身就是 serve 提供的;改代码后重启它即可"}),s.jsx("span",{className:"acts",children:s.jsx(r,{className:"mono",text:`nx-sk serve --port ${p}`,children:`nx-sk serve --port ${p}`})})]})]})]}),s.jsxs("div",{className:"card",children:[s.jsx("div",{className:"colhead",children:"默认行为"}),s.jsxs("div",{className:"pad",children:[s.jsxs("div",{className:"grid",children:[s.jsxs("div",{className:"field",children:[s.jsx("div",{className:"flabel",children:s.jsx("span",{children:"默认导出格式"})}),s.jsx("div",{className:"fctrl",children:s.jsxs("select",{value:l.exportFormat,onChange:a=>i({...l,exportFormat:a.target.value}),children:[s.jsx("option",{value:"both",children:"JSON + Markdown"}),s.jsx("option",{value:"json",children:"仅 JSON"}),s.jsx("option",{value:"md",children:"仅 Markdown"})]})})]}),s.jsxs("div",{className:"field",children:[s.jsxs("div",{className:"flabel",children:[s.jsx("span",{children:"默认导出目录"}),s.jsx("span",{className:"fkey",children:"留空 = ~/nx-sk/export"})]}),s.jsx("div",{className:"fctrl",children:s.jsx("input",{value:l.exportDir,onChange:a=>i({...l,exportDir:a.target.value})})})]}),s.jsxs("div",{className:"field",children:[s.jsxs("div",{className:"flabel",children:[s.jsx("span",{children:"默认栏目"}),s.jsx("span",{className:"fkey",children:"CLI 未指定 --section 时的首选"})]}),s.jsx("div",{className:"fctrl",children:s.jsx("input",{value:l.defaultSection,onChange:a=>i({...l,defaultSection:a.target.value})})})]}),s.jsxs("div",{className:"field",children:[s.jsxs("div",{className:"flabel",children:[s.jsx("span",{children:"导出默认带明文密钥"}),s.jsx("span",{className:"fkey",children:"默认开;要分享出去时关掉"})]}),s.jsx("div",{className:"fctrl",children:s.jsxs("label",{className:"inline",children:[s.jsx("input",{type:"checkbox",checked:!!l.includeSecretsInExport,onChange:a=>i({...l,includeSecretsInExport:a.target.checked})}),s.jsx("span",{className:l.includeSecretsInExport?"muted":"bad",children:l.includeSecretsInExport?"开(导出文件含真实凭据,注意别外发)":"关(凭据打码后再导出)"})]})})]})]}),s.jsx("div",{className:"rowgap",style:{marginTop:8},children:s.jsx("button",{className:"btn",onClick:E,children:"保存设置"})})]})]}),s.jsxs("div",{className:"card",children:[s.jsxs("div",{className:"colhead",children:["自动快照",s.jsx("span",{className:"right muted",children:((y=e==null?void 0:e.snapshots)==null?void 0:y.length)??0})]}),s.jsxs("div",{className:"colbody",children:[s.jsx("div",{className:"row",children:s.jsx("span",{className:"desc",children:"每次删除条目 / 栏目、修改设置前,都会在 ~/nx-sk/backup 留一份可回滚的整库快照。"})}),(S=e==null?void 0:e.snapshots)!=null&&S.length?e.snapshots.map(a=>s.jsxs("div",{className:"row",children:[s.jsx("span",{className:"mono name",children:a.name}),s.jsx("span",{className:"desc",children:a.at}),s.jsx("span",{className:"acts",children:s.jsxs("span",{className:"tag",children:[Math.ceil(a.bytes/1024)," KB"]})})]},a.name)):s.jsx(F,{children:"还没有快照。"})]})]}),e!=null&&e.migration?s.jsxs("div",{className:"card",children:[s.jsx("div",{className:"colhead",children:"数据迁移"}),s.jsxs("div",{className:"colbody",children:[s.jsx("div",{className:"row",children:s.jsxs("span",{className:"desc",children:["store 已从 v",e.migration.from," 迁到 v",e.migration.to,":密钥栏目字段 ",e.migration.fieldsBefore," → ",e.migration.fieldsAfter,",",e.migration.entries," 条条目"]})}),s.jsxs("div",{className:"row",children:[s.jsx("span",{className:"desc",children:"迁移前的原始文件"}),s.jsx("span",{className:"acts",children:s.jsx(r,{className:"mono",text:e.migration.snapshotRaw||"",children:e.migration.snapshot})})]}),e.migration.legacyValuesKept?s.jsx("div",{className:"row",children:s.jsxs("span",{className:"desc",children:["另保留了 ",e.migration.legacyValuesKept," 个旧字段的值(不再显示,但没删)"]})}):null]})]}):null,e!=null&&e.corrupt?s.jsxs("div",{className:"card",children:[s.jsx("div",{className:"colhead",children:"上次读取异常"}),s.jsx("div",{className:"colbody",children:s.jsx("div",{className:"row",children:s.jsxs("span",{className:"desc bad",children:["store.json 解析失败,已挪到 ",e.corrupt.to,"(原文件没有被覆盖)"]})})})]}):null,s.jsx(I,{module:"settings"})]})}function c({label:e,value:t,copy:n}){return s.jsxs("div",{className:"row",children:[s.jsx("span",{className:"name",children:e}),s.jsx("span",{className:"desc mono",children:t||"(空)"}),n?s.jsx("span",{className:"acts",children:s.jsx(r,{className:"mono",text:n,children:"复制"})}):s.jsx($,{children:" "})]})}export{D as default};
@@ -1 +1 @@
1
- import{u as R,r as j,a as h,j as e,T as N,E as T,C as I,M as U}from"./index-B7CtqgoG.js";const P=["text","textarea","number","date","month","bool","select","tags","secret","file"];function M(){const{reload:d,toast:n,guard:o,dialog:u}=R(),[t,b]=j.useState(null),[f,r]=j.useState([]),[x,i]=j.useState(null),[c,m]=j.useState(null),a=j.useCallback(async()=>{try{const[s,l]=await Promise.all([h("/api/sections"),h("/api/sections/templates")]);b(s),r(l.templates),i(null)}catch(s){i(s.message)}},[]);j.useEffect(()=>{a()},[a]);const p=o(async s=>{const l=await h(`/api/sections/${encodeURIComponent(s.id)}/dump`);m({kind:"dump",title:`${s.title}(${s.id})的全部信息`,body:e.jsx("pre",{className:"block",children:JSON.stringify(l,null,2)})})}),C=o(async s=>{const l=await h(`/api/sections/${encodeURIComponent(s.id)}`);m({kind:"edit",id:s.id,form:{title:l.title,description:l.description,order:String(l.order),titleLabel:l.titleLabel,titleField:l.titleField||""},full:l})}),w=o(async s=>{const l={id:s.id.trim(),title:s.title.trim()||s.id.trim(),template:s.template||void 0,description:s.description,order:Number(s.order)||50};await h("/api/sections",{method:"POST",body:l}),n("栏目已创建"),m(null),await a(),await d()}),$=o(async s=>{const l={title:s.form.title,description:s.form.description,order:Number(s.form.order)||50,titleLabel:s.form.titleLabel,titleField:s.form.titleField};await h(`/api/sections/${encodeURIComponent(s.id)}`,{method:"PATCH",body:l}),n("栏目已更新");const v=await h(`/api/sections/${encodeURIComponent(s.id)}`);m({...s,full:v}),await a(),await d()}),S=o(async(s,l)=>{await h(`/api/sections/${encodeURIComponent(s.id)}`,{method:"PATCH",body:{"add-field":l}}),n(`已新增字段 ${l.key}`);const v=await h(`/api/sections/${encodeURIComponent(s.id)}`);m({...s,full:v}),await a()}),F=o(async s=>{var y,k;const l=await h(`/api/sections/${encodeURIComponent(s.id)}/sync`,{method:"POST"});if(l.upToDate){n("已是最新,模板里没有需要补的字段");return}const v=[(y=l.addedGroups)!=null&&y.length?`分组 ${l.addedGroups.join("、")}`:"",(k=l.addedFields)!=null&&k.length?`字段 ${l.addedFields.join("、")}`:""].filter(Boolean).join(";");n(`已按模板补齐:${v||"无变化"}`);const g=await h(`/api/sections/${encodeURIComponent(s.id)}`);m({...s,full:g}),await a(),await d()}),E=o(async(s,l)=>{if(!await u({message:`从字段字典里删掉「${l}」?已填的值会留在条目里(只是不再显示/校验)。`,danger:!0,confirmText:"删除字段"}))return;await h(`/api/sections/${encodeURIComponent(s.id)}`,{method:"PATCH",body:{"remove-field":l}}),n(`已删字段 ${l}`);const g=await h(`/api/sections/${encodeURIComponent(s.id)}`);m({...s,full:g}),await a()}),L=o(async s=>{await u({message:`删除栏目「${s.title}」,连带其中 ${s.entries} 条条目?删除前会自动留快照。`,danger:!0,confirmText:"连带删除"})&&(await h(`/api/sections/${encodeURIComponent(s.id)}`,{method:"DELETE",body:{force:!0}}),n("栏目已删除"),await a(),await d())});return x?e.jsxs("div",{className:"pad stack",children:[e.jsx("div",{className:"bad",children:e.jsx("strong",{children:"读不到栏目"})}),e.jsx("pre",{className:"block",children:x})]}):t?e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"toolbar rowgap pad",children:[e.jsx("strong",{children:"栏目"}),e.jsxs("span",{className:"muted",children:["共 ",t.count," 个 · 条目 ",t.totalEntries," 条"]}),e.jsx("span",{className:"grow"}),e.jsx("button",{className:"btn small ghost",onClick:a,children:"刷新"}),e.jsx("button",{className:"btn small",onClick:()=>{var s;return m({kind:"create",form:{id:"",title:"",template:((s=f[0])==null?void 0:s.id)||"",description:"",order:"50"}})},children:"新建栏目"})]}),e.jsxs("div",{className:"card",children:[e.jsx("div",{className:"colhead",children:"全部栏目"}),e.jsx("div",{className:"colbody",children:t.sections.length?t.sections.map(s=>e.jsxs("div",{className:"row",children:[e.jsx("span",{className:"name",children:s.title}),e.jsx("span",{className:"mono muted",children:s.id}),s.template?e.jsx(N,{children:s.template}):e.jsx(N,{children:"自定义"}),e.jsx("span",{className:"desc",children:s.description}),e.jsxs("span",{className:"acts",children:[e.jsxs("span",{className:"tag",children:[s.fields," 字段"]}),e.jsxs("span",{className:"tag",children:[s.entries," 条目"]}),e.jsx("button",{className:"btn small ghost",onClick:()=>p(s),children:"全部信息"}),e.jsx("button",{className:"btn small ghost",onClick:()=>C(s),children:"编辑"}),e.jsx("button",{className:"btn small ghost",onClick:()=>L(s),children:"删除"})]})]},s.id)):e.jsx(T,{children:"还没有栏目。点「新建栏目」,用内置模板(求职 / 密钥)起步。"})})]}),e.jsx("div",{className:"pad hint",children:"栏目 = 一组同构条目 + 一份字段字典。字典是数据,所以这里加一个字段,CLI 与面板立刻就都能填它。"}),e.jsx(I,{module:"sections"}),c?e.jsxs(U,{title:z(c),onClose:()=>m(null),children:[c.kind==="dump"?c.body:null,c.kind==="create"?e.jsx(A,{templates:f,form:c.form,onChange:s=>m(l=>({...l,form:{...l.form,...s}})),onSubmit:()=>{if(!/^[a-z][a-z0-9_-]{0,63}$/.test(c.form.id.trim())){n("栏目 id 必须是小写字母开头,仅含 a-z 0-9 _ -","bad");return}w(c.form)}}):null,c.kind==="edit"?e.jsx(D,{modal:c,onChange:s=>m(l=>({...l,form:{...l.form,...s}})),onSave:()=>$(c),onSync:()=>F(c),onAddField:s=>S(c,s),onRemoveField:s=>E(c,s),onClose:()=>m(null)}):null]}):null]}):e.jsx("div",{className:"pad muted",children:"加载中…"})}function z(d){return d.kind==="dump"?d.title:d.kind==="create"?"新建栏目":`编辑栏目 ${d.id}`}function A({templates:d,form:n,onChange:o,onSubmit:u}){return e.jsxs("div",{className:"stack",children:[e.jsxs("div",{className:"grid",children:[e.jsxs("div",{className:"field",children:[e.jsxs("div",{className:"flabel",children:[e.jsx("span",{children:"栏目 id"}),e.jsx("span",{className:"fkey",children:"小写字母开头,仅 a-z 0-9 _ -"})]}),e.jsx("div",{className:"fctrl",children:e.jsx("input",{value:n.id,onChange:t=>o({id:t.target.value}),placeholder:"例如 projects"})})]}),e.jsxs("div",{className:"field",children:[e.jsx("div",{className:"flabel",children:e.jsx("span",{children:"标题"})}),e.jsx("div",{className:"fctrl",children:e.jsx("input",{value:n.title,onChange:t=>o({title:t.target.value}),placeholder:"例如 项目档案"})})]}),e.jsxs("div",{className:"field",children:[e.jsx("div",{className:"flabel",children:e.jsx("span",{children:"从模板起步"})}),e.jsx("div",{className:"fctrl",children:e.jsxs("select",{value:n.template,onChange:t=>o({template:t.target.value}),children:[e.jsx("option",{value:"",children:"(空栏目,自定义字段)"}),d.map(t=>e.jsxs("option",{value:t.id,children:[t.label,"(",t.fields," 字段)"]},t.id))]})})]}),e.jsxs("div",{className:"field",children:[e.jsxs("div",{className:"flabel",children:[e.jsx("span",{children:"排序"}),e.jsx("span",{className:"fkey",children:"越小越靠前"})]}),e.jsx("div",{className:"fctrl",children:e.jsx("input",{type:"number",value:n.order,onChange:t=>o({order:t.target.value})})})]})]}),e.jsxs("div",{className:"field wide",children:[e.jsx("div",{className:"flabel",children:e.jsx("span",{children:"描述"})}),e.jsx("div",{className:"fctrl",children:e.jsx("input",{value:n.description,onChange:t=>o({description:t.target.value})})})]}),e.jsxs("div",{className:"rowgap",children:[e.jsx("button",{className:"btn",onClick:u,children:"创建"}),e.jsx("span",{className:"hint",children:"同 id 已存在会直接报冲突,不会静默覆盖。"})]})]})}function D({modal:d,onChange:n,onSave:o,onSync:u,onAddField:t,onRemoveField:b,onClose:f}){var m;const[r,x]=j.useState({key:"",label:"",type:"text",group:""}),i=d.full,c=(i==null?void 0:i.groupList)||[];return e.jsxs("div",{className:"stack",children:[e.jsxs("div",{className:"grid",children:[e.jsxs("div",{className:"field",children:[e.jsx("div",{className:"flabel",children:e.jsx("span",{children:"标题"})}),e.jsx("div",{className:"fctrl",children:e.jsx("input",{value:d.form.title,onChange:a=>n({title:a.target.value})})})]}),e.jsxs("div",{className:"field",children:[e.jsx("div",{className:"flabel",children:e.jsx("span",{children:"排序"})}),e.jsx("div",{className:"fctrl",children:e.jsx("input",{type:"number",value:d.form.order,onChange:a=>n({order:a.target.value})})})]}),e.jsxs("div",{className:"field",children:[e.jsxs("div",{className:"flabel",children:[e.jsx("span",{children:"条目名字段"}),e.jsx("span",{className:"fkey",children:"列表里显示哪个字段"})]}),e.jsx("div",{className:"fctrl",children:e.jsxs("select",{value:d.form.titleField,onChange:a=>n({titleField:a.target.value}),children:[e.jsx("option",{value:"",children:"(用条目 title)"}),((i==null?void 0:i.fieldList)||[]).map(a=>e.jsxs("option",{value:a.key,children:[a.label,"(",a.key,")"]},a.key))]})})]}),e.jsxs("div",{className:"field",children:[e.jsxs("div",{className:"flabel",children:[e.jsx("span",{children:"名称标签"}),e.jsx("span",{className:"fkey",children:"新建条目时问什么"})]}),e.jsx("div",{className:"fctrl",children:e.jsx("input",{value:d.form.titleLabel,onChange:a=>n({titleLabel:a.target.value})})})]})]}),e.jsxs("div",{className:"field wide",children:[e.jsx("div",{className:"flabel",children:e.jsx("span",{children:"描述"})}),e.jsx("div",{className:"fctrl",children:e.jsx("input",{value:d.form.description,onChange:a=>n({description:a.target.value})})})]}),e.jsxs("div",{className:"rowgap",children:[e.jsx("button",{className:"btn",onClick:o,children:"保存栏目属性"}),i!=null&&i.template?e.jsx("button",{className:"btn ghost",onClick:u,title:`按模板「${i.template}」补齐新增的分组与字段(只增不改不删)`,children:"同步模板字段"}):null,e.jsx("button",{className:"btn ghost",onClick:f,children:"关闭"})]}),i!=null&&i.template?e.jsxs("div",{className:"hint",children:["本栏目由模板「",i.template,"」创建。模板后来新增的字段**不会自动**出现在这里, 点「同步模板字段」补齐(例如求职模板新增的「附件资料」组 / 简历、头像等 file 字段)。"]}):null,e.jsxs("div",{className:"card",style:{border:"1px solid var(--soft-2)",borderRadius:4},children:[e.jsxs("div",{className:"colhead",children:["字段字典(",((m=i==null?void 0:i.fieldList)==null?void 0:m.length)||0,")"]}),e.jsxs("div",{className:"colbody",children:[c.map(a=>e.jsxs("div",{children:[e.jsxs("div",{className:"group-title",children:[a.title,"(",a.id,")"]}),(i.fieldList||[]).filter(p=>p.group===a.id).map(p=>e.jsxs("div",{className:"row",children:[e.jsx("span",{className:"name",children:p.label}),e.jsx("span",{className:"mono muted",children:p.key}),e.jsx(N,{children:p.type}),p.sensitive?e.jsx(N,{kind:"bad",children:"密文"}):null,e.jsx("span",{className:"desc",children:p.hint||""}),e.jsx("span",{className:"acts",children:e.jsx("button",{className:"btn small ghost",onClick:()=>b(p.key),children:"删除"})})]},p.key))]},a.id)),((i==null?void 0:i.fieldList)||[]).filter(a=>!c.some(p=>p.id===a.group)).map(a=>e.jsxs("div",{className:"row",children:[e.jsx("span",{className:"name",children:a.label}),e.jsx("span",{className:"mono muted",children:a.key}),e.jsx(N,{children:a.type}),e.jsxs("span",{className:"desc",children:["分组 ",a.group,"(不在 groups 列表里)"]}),e.jsx("span",{className:"acts",children:e.jsx("button",{className:"btn small ghost",onClick:()=>b(a.key),children:"删除"})})]},a.key))]})]}),e.jsxs("div",{className:"card",style:{border:"1px solid var(--soft-2)",borderRadius:4},children:[e.jsx("div",{className:"colhead",children:"新增字段"}),e.jsxs("div",{className:"pad rowgap",children:[e.jsx("input",{style:{width:150},placeholder:"key(英文字母开头)",value:r.key,onChange:a=>x({...r,key:a.target.value})}),e.jsx("input",{style:{width:150},placeholder:"标签(中文名)",value:r.label,onChange:a=>x({...r,label:a.target.value})}),e.jsx("select",{style:{width:110},value:r.type,onChange:a=>x({...r,type:a.target.value}),children:P.map(a=>e.jsx("option",{value:a,children:a},a))}),e.jsxs("select",{style:{width:150},value:r.group,onChange:a=>x({...r,group:a.target.value}),children:[e.jsx("option",{value:"",children:"(首个分组)"}),c.map(a=>e.jsx("option",{value:a.id,children:a.title},a.id))]}),e.jsx("button",{className:"btn small",onClick:()=>{const a={key:r.key.trim(),label:r.label.trim()||r.key.trim(),type:r.type};r.group&&(a.group=r.group),t(a),x({key:"",label:"",type:"text",group:r.group})},children:"加入字典"}),e.jsx("span",{className:"hint",children:"加完立刻生效:CLI 与面板都能填它。"})]})]})]})}export{M as default};
1
+ import{u as R,r as j,a as h,j as e,T as N,E as T,C as I,M as U}from"./index-BygHBMGo.js";const P=["text","textarea","number","date","month","bool","select","tags","secret","file"];function M(){const{reload:d,toast:n,guard:o,dialog:u}=R(),[t,b]=j.useState(null),[f,r]=j.useState([]),[x,i]=j.useState(null),[c,m]=j.useState(null),a=j.useCallback(async()=>{try{const[s,l]=await Promise.all([h("/api/sections"),h("/api/sections/templates")]);b(s),r(l.templates),i(null)}catch(s){i(s.message)}},[]);j.useEffect(()=>{a()},[a]);const p=o(async s=>{const l=await h(`/api/sections/${encodeURIComponent(s.id)}/dump`);m({kind:"dump",title:`${s.title}(${s.id})的全部信息`,body:e.jsx("pre",{className:"block",children:JSON.stringify(l,null,2)})})}),C=o(async s=>{const l=await h(`/api/sections/${encodeURIComponent(s.id)}`);m({kind:"edit",id:s.id,form:{title:l.title,description:l.description,order:String(l.order),titleLabel:l.titleLabel,titleField:l.titleField||""},full:l})}),w=o(async s=>{const l={id:s.id.trim(),title:s.title.trim()||s.id.trim(),template:s.template||void 0,description:s.description,order:Number(s.order)||50};await h("/api/sections",{method:"POST",body:l}),n("栏目已创建"),m(null),await a(),await d()}),$=o(async s=>{const l={title:s.form.title,description:s.form.description,order:Number(s.form.order)||50,titleLabel:s.form.titleLabel,titleField:s.form.titleField};await h(`/api/sections/${encodeURIComponent(s.id)}`,{method:"PATCH",body:l}),n("栏目已更新");const v=await h(`/api/sections/${encodeURIComponent(s.id)}`);m({...s,full:v}),await a(),await d()}),S=o(async(s,l)=>{await h(`/api/sections/${encodeURIComponent(s.id)}`,{method:"PATCH",body:{"add-field":l}}),n(`已新增字段 ${l.key}`);const v=await h(`/api/sections/${encodeURIComponent(s.id)}`);m({...s,full:v}),await a()}),F=o(async s=>{var y,k;const l=await h(`/api/sections/${encodeURIComponent(s.id)}/sync`,{method:"POST"});if(l.upToDate){n("已是最新,模板里没有需要补的字段");return}const v=[(y=l.addedGroups)!=null&&y.length?`分组 ${l.addedGroups.join("、")}`:"",(k=l.addedFields)!=null&&k.length?`字段 ${l.addedFields.join("、")}`:""].filter(Boolean).join(";");n(`已按模板补齐:${v||"无变化"}`);const g=await h(`/api/sections/${encodeURIComponent(s.id)}`);m({...s,full:g}),await a(),await d()}),E=o(async(s,l)=>{if(!await u({message:`从字段字典里删掉「${l}」?已填的值会留在条目里(只是不再显示/校验)。`,danger:!0,confirmText:"删除字段"}))return;await h(`/api/sections/${encodeURIComponent(s.id)}`,{method:"PATCH",body:{"remove-field":l}}),n(`已删字段 ${l}`);const g=await h(`/api/sections/${encodeURIComponent(s.id)}`);m({...s,full:g}),await a()}),L=o(async s=>{await u({message:`删除栏目「${s.title}」,连带其中 ${s.entries} 条条目?删除前会自动留快照。`,danger:!0,confirmText:"连带删除"})&&(await h(`/api/sections/${encodeURIComponent(s.id)}`,{method:"DELETE",body:{force:!0}}),n("栏目已删除"),await a(),await d())});return x?e.jsxs("div",{className:"pad stack",children:[e.jsx("div",{className:"bad",children:e.jsx("strong",{children:"读不到栏目"})}),e.jsx("pre",{className:"block",children:x})]}):t?e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"toolbar rowgap pad",children:[e.jsx("strong",{children:"栏目"}),e.jsxs("span",{className:"muted",children:["共 ",t.count," 个 · 条目 ",t.totalEntries," 条"]}),e.jsx("span",{className:"grow"}),e.jsx("button",{className:"btn small ghost",onClick:a,children:"刷新"}),e.jsx("button",{className:"btn small",onClick:()=>{var s;return m({kind:"create",form:{id:"",title:"",template:((s=f[0])==null?void 0:s.id)||"",description:"",order:"50"}})},children:"新建栏目"})]}),e.jsxs("div",{className:"card",children:[e.jsx("div",{className:"colhead",children:"全部栏目"}),e.jsx("div",{className:"colbody",children:t.sections.length?t.sections.map(s=>e.jsxs("div",{className:"row",children:[e.jsx("span",{className:"name",children:s.title}),e.jsx("span",{className:"mono muted",children:s.id}),s.template?e.jsx(N,{children:s.template}):e.jsx(N,{children:"自定义"}),e.jsx("span",{className:"desc",children:s.description}),e.jsxs("span",{className:"acts",children:[e.jsxs("span",{className:"tag",children:[s.fields," 字段"]}),e.jsxs("span",{className:"tag",children:[s.entries," 条目"]}),e.jsx("button",{className:"btn small ghost",onClick:()=>p(s),children:"全部信息"}),e.jsx("button",{className:"btn small ghost",onClick:()=>C(s),children:"编辑"}),e.jsx("button",{className:"btn small ghost",onClick:()=>L(s),children:"删除"})]})]},s.id)):e.jsx(T,{children:"还没有栏目。点「新建栏目」,用内置模板(求职 / 密钥)起步。"})})]}),e.jsx("div",{className:"pad hint",children:"栏目 = 一组同构条目 + 一份字段字典。字典是数据,所以这里加一个字段,CLI 与面板立刻就都能填它。"}),e.jsx(I,{module:"sections"}),c?e.jsxs(U,{title:z(c),onClose:()=>m(null),children:[c.kind==="dump"?c.body:null,c.kind==="create"?e.jsx(A,{templates:f,form:c.form,onChange:s=>m(l=>({...l,form:{...l.form,...s}})),onSubmit:()=>{if(!/^[a-z][a-z0-9_-]{0,63}$/.test(c.form.id.trim())){n("栏目 id 必须是小写字母开头,仅含 a-z 0-9 _ -","bad");return}w(c.form)}}):null,c.kind==="edit"?e.jsx(D,{modal:c,onChange:s=>m(l=>({...l,form:{...l.form,...s}})),onSave:()=>$(c),onSync:()=>F(c),onAddField:s=>S(c,s),onRemoveField:s=>E(c,s),onClose:()=>m(null)}):null]}):null]}):e.jsx("div",{className:"pad muted",children:"加载中…"})}function z(d){return d.kind==="dump"?d.title:d.kind==="create"?"新建栏目":`编辑栏目 ${d.id}`}function A({templates:d,form:n,onChange:o,onSubmit:u}){return e.jsxs("div",{className:"stack",children:[e.jsxs("div",{className:"grid",children:[e.jsxs("div",{className:"field",children:[e.jsxs("div",{className:"flabel",children:[e.jsx("span",{children:"栏目 id"}),e.jsx("span",{className:"fkey",children:"小写字母开头,仅 a-z 0-9 _ -"})]}),e.jsx("div",{className:"fctrl",children:e.jsx("input",{value:n.id,onChange:t=>o({id:t.target.value}),placeholder:"例如 projects"})})]}),e.jsxs("div",{className:"field",children:[e.jsx("div",{className:"flabel",children:e.jsx("span",{children:"标题"})}),e.jsx("div",{className:"fctrl",children:e.jsx("input",{value:n.title,onChange:t=>o({title:t.target.value}),placeholder:"例如 项目档案"})})]}),e.jsxs("div",{className:"field",children:[e.jsx("div",{className:"flabel",children:e.jsx("span",{children:"从模板起步"})}),e.jsx("div",{className:"fctrl",children:e.jsxs("select",{value:n.template,onChange:t=>o({template:t.target.value}),children:[e.jsx("option",{value:"",children:"(空栏目,自定义字段)"}),d.map(t=>e.jsxs("option",{value:t.id,children:[t.label,"(",t.fields," 字段)"]},t.id))]})})]}),e.jsxs("div",{className:"field",children:[e.jsxs("div",{className:"flabel",children:[e.jsx("span",{children:"排序"}),e.jsx("span",{className:"fkey",children:"越小越靠前"})]}),e.jsx("div",{className:"fctrl",children:e.jsx("input",{type:"number",value:n.order,onChange:t=>o({order:t.target.value})})})]})]}),e.jsxs("div",{className:"field wide",children:[e.jsx("div",{className:"flabel",children:e.jsx("span",{children:"描述"})}),e.jsx("div",{className:"fctrl",children:e.jsx("input",{value:n.description,onChange:t=>o({description:t.target.value})})})]}),e.jsxs("div",{className:"rowgap",children:[e.jsx("button",{className:"btn",onClick:u,children:"创建"}),e.jsx("span",{className:"hint",children:"同 id 已存在会直接报冲突,不会静默覆盖。"})]})]})}function D({modal:d,onChange:n,onSave:o,onSync:u,onAddField:t,onRemoveField:b,onClose:f}){var m;const[r,x]=j.useState({key:"",label:"",type:"text",group:""}),i=d.full,c=(i==null?void 0:i.groupList)||[];return e.jsxs("div",{className:"stack",children:[e.jsxs("div",{className:"grid",children:[e.jsxs("div",{className:"field",children:[e.jsx("div",{className:"flabel",children:e.jsx("span",{children:"标题"})}),e.jsx("div",{className:"fctrl",children:e.jsx("input",{value:d.form.title,onChange:a=>n({title:a.target.value})})})]}),e.jsxs("div",{className:"field",children:[e.jsx("div",{className:"flabel",children:e.jsx("span",{children:"排序"})}),e.jsx("div",{className:"fctrl",children:e.jsx("input",{type:"number",value:d.form.order,onChange:a=>n({order:a.target.value})})})]}),e.jsxs("div",{className:"field",children:[e.jsxs("div",{className:"flabel",children:[e.jsx("span",{children:"条目名字段"}),e.jsx("span",{className:"fkey",children:"列表里显示哪个字段"})]}),e.jsx("div",{className:"fctrl",children:e.jsxs("select",{value:d.form.titleField,onChange:a=>n({titleField:a.target.value}),children:[e.jsx("option",{value:"",children:"(用条目 title)"}),((i==null?void 0:i.fieldList)||[]).map(a=>e.jsxs("option",{value:a.key,children:[a.label,"(",a.key,")"]},a.key))]})})]}),e.jsxs("div",{className:"field",children:[e.jsxs("div",{className:"flabel",children:[e.jsx("span",{children:"名称标签"}),e.jsx("span",{className:"fkey",children:"新建条目时问什么"})]}),e.jsx("div",{className:"fctrl",children:e.jsx("input",{value:d.form.titleLabel,onChange:a=>n({titleLabel:a.target.value})})})]})]}),e.jsxs("div",{className:"field wide",children:[e.jsx("div",{className:"flabel",children:e.jsx("span",{children:"描述"})}),e.jsx("div",{className:"fctrl",children:e.jsx("input",{value:d.form.description,onChange:a=>n({description:a.target.value})})})]}),e.jsxs("div",{className:"rowgap",children:[e.jsx("button",{className:"btn",onClick:o,children:"保存栏目属性"}),i!=null&&i.template?e.jsx("button",{className:"btn ghost",onClick:u,title:`按模板「${i.template}」补齐新增的分组与字段(只增不改不删)`,children:"同步模板字段"}):null,e.jsx("button",{className:"btn ghost",onClick:f,children:"关闭"})]}),i!=null&&i.template?e.jsxs("div",{className:"hint",children:["本栏目由模板「",i.template,"」创建。模板后来新增的字段**不会自动**出现在这里, 点「同步模板字段」补齐(例如求职模板新增的「附件资料」组 / 简历、头像等 file 字段)。"]}):null,e.jsxs("div",{className:"card",style:{border:"1px solid var(--soft-2)",borderRadius:4},children:[e.jsxs("div",{className:"colhead",children:["字段字典(",((m=i==null?void 0:i.fieldList)==null?void 0:m.length)||0,")"]}),e.jsxs("div",{className:"colbody",children:[c.map(a=>e.jsxs("div",{children:[e.jsxs("div",{className:"group-title",children:[a.title,"(",a.id,")"]}),(i.fieldList||[]).filter(p=>p.group===a.id).map(p=>e.jsxs("div",{className:"row",children:[e.jsx("span",{className:"name",children:p.label}),e.jsx("span",{className:"mono muted",children:p.key}),e.jsx(N,{children:p.type}),p.sensitive?e.jsx(N,{kind:"bad",children:"密文"}):null,e.jsx("span",{className:"desc",children:p.hint||""}),e.jsx("span",{className:"acts",children:e.jsx("button",{className:"btn small ghost",onClick:()=>b(p.key),children:"删除"})})]},p.key))]},a.id)),((i==null?void 0:i.fieldList)||[]).filter(a=>!c.some(p=>p.id===a.group)).map(a=>e.jsxs("div",{className:"row",children:[e.jsx("span",{className:"name",children:a.label}),e.jsx("span",{className:"mono muted",children:a.key}),e.jsx(N,{children:a.type}),e.jsxs("span",{className:"desc",children:["分组 ",a.group,"(不在 groups 列表里)"]}),e.jsx("span",{className:"acts",children:e.jsx("button",{className:"btn small ghost",onClick:()=>b(a.key),children:"删除"})})]},a.key))]})]}),e.jsxs("div",{className:"card",style:{border:"1px solid var(--soft-2)",borderRadius:4},children:[e.jsx("div",{className:"colhead",children:"新增字段"}),e.jsxs("div",{className:"pad rowgap",children:[e.jsx("input",{style:{width:150},placeholder:"key(英文字母开头)",value:r.key,onChange:a=>x({...r,key:a.target.value})}),e.jsx("input",{style:{width:150},placeholder:"标签(中文名)",value:r.label,onChange:a=>x({...r,label:a.target.value})}),e.jsx("select",{style:{width:110},value:r.type,onChange:a=>x({...r,type:a.target.value}),children:P.map(a=>e.jsx("option",{value:a,children:a},a))}),e.jsxs("select",{style:{width:150},value:r.group,onChange:a=>x({...r,group:a.target.value}),children:[e.jsx("option",{value:"",children:"(首个分组)"}),c.map(a=>e.jsx("option",{value:a.id,children:a.title},a.id))]}),e.jsx("button",{className:"btn small",onClick:()=>{const a={key:r.key.trim(),label:r.label.trim()||r.key.trim(),type:r.type};r.group&&(a.group=r.group),t(a),x({key:"",label:"",type:"text",group:r.group})},children:"加入字典"}),e.jsx("span",{className:"hint",children:"加完立刻生效:CLI 与面板都能填它。"})]})]})]})}export{M as default};
@@ -0,0 +1,5 @@
1
+ import{u as P,r,a as v,j as e,E as A,C as B,M as L}from"./index-BygHBMGo.js";const O=28*1024*1024,F=20;function I(){const s=new Date,t=a=>String(a).padStart(2,"0");return`${s.getFullYear()}${t(s.getMonth()+1)}${t(s.getDate())}-${t(s.getHours())}${t(s.getMinutes())}${t(s.getSeconds())}`}function U(s,t){const a=new Blob([t],{type:"application/json;charset=utf-8"}),d=URL.createObjectURL(a),l=document.createElement("a");l.href=d,l.download=s,document.body.appendChild(l),l.click(),l.remove(),setTimeout(()=>URL.revokeObjectURL(d),1e3)}function x({title:s,items:t,render:a,total:d}){if(!t||!t.length)return null;const l=d!=null&&d>t.length;return e.jsxs("div",{children:[e.jsxs("div",{className:"flabel",children:[e.jsx("span",{children:s}),l?e.jsxs("span",{className:"fkey",children:["共 ",d," 处"]}):null]}),e.jsx("ul",{className:"stack",style:{margin:"4px 0 0",paddingLeft:18},children:t.slice(0,F).map((p,h)=>e.jsx("li",{children:a(p)},h))})]})}function V({r:s}){const t=s.files||{};return e.jsxs("div",{className:"stack",children:[e.jsxs("div",{children:["新增栏目 ",(s.addedSections||[]).length," 个 / 新增条目 ",s.addedEntriesTotal||0," 条; 补空 ",s.filledTotal||0," 处;冲突 ",s.conflictsTotal||0," 处 (",s.counts?`栏目 ${s.counts.sectionsBefore} → ${s.counts.sectionsAfter},条目 ${s.counts.entriesBefore} → ${s.counts.entriesAfter}`:"",")"]}),e.jsx(x,{title:"补空明细(本地留空,已用备份值填上)",items:s.filled,total:s.filledTotal,render:a=>e.jsxs("span",{children:[a.section," / ",a.entry," · ",a.label," = ",a.value||"(空)"]})}),e.jsx(x,{title:"冲突(两边都有值且不同,已保留本地,请手动核对)",items:s.conflicts,total:s.conflictsTotal,render:a=>e.jsxs("span",{children:[a.section," / ",a.entry," · ",a.label,": 本地 ",a.localValue||"(空)"," | 备份 ",a.backupValue||"(空)"]})}),e.jsx(x,{title:"结构补齐(只增:备份多出的分组/字段已补进本地栏目)",items:s.structureAdded,render:a=>e.jsxs("span",{children:[a.id,": +",a.fields.length," 字段 [",a.fields.join(", "),"]",a.groups.length?`,+分组 [${a.groups.join(", ")}]`:""]})}),e.jsx(x,{title:"本地独有字段(备份里没有,已原样保留)",items:s.structureOnlyLocal,render:a=>e.jsxs("span",{children:[a.id,": [",a.fields.join(", "),"]"]})}),(s.kvMismatch||[]).length?e.jsxs("div",{className:"muted",children:["kv 标记与备份不一致、未合并结构的栏目: ",s.kvMismatch.join(", ")]}):null,(s.settingsFilled||[]).length?e.jsxs("div",{children:["设置补空: ",s.settingsFilled.join(", ")]}):null,e.jsx("div",{children:S(t)}),s.exportsSkipped?e.jsxs("div",{className:"muted",children:["备份里的导出历史 ",s.exportsSkipped," 条未并入(保留本机记录)"]}):null,s.resealed?e.jsxs("div",{className:"muted",children:["用本机密钥库重新加密的密钥字段: ",s.resealed," 个"]}):null,e.jsxs("div",{className:"muted",children:["备份生成于 ",s.backupCreatedAt||"未知"]}),e.jsxs("pre",{className:"block",children:["导入前的安全快照:",s.safetySnapshot]})]})}function S(s){const t=[];return s.toAdd!=null&&t.push(`台账新增 ${s.toAdd} 条、沿用本地 ${s.kept||0} 条`),s.written&&t.push(`落盘原件 ${s.written} 个`),s.healed&&t.push(`补齐缺失原件 ${s.healed} 个`),s.skippedExisting&&t.push(`磁盘已有但内容不同、保留磁盘 ${s.skippedExisting} 个`),s.checksumMismatch&&t.push(`校验不符被拒 ${s.checksumMismatch} 个`),s.missingBinaryTotal&&t.push(`${s.missingBinaryTotal} 条台账无原件(备份未携带)`),t.length?`附件: ${t.join(";")}${s.bytes?`(共 ${s.bytes} 字节)`:""}`:"附件: 台账已并入,原件均已就位"}function _({r:s}){const t=s.filesStats||{};return e.jsxs("div",{className:"stack",children:[e.jsxs("div",{children:["已整份替换为备份内容:",s.sections," 个栏目 / ",s.entries," 条条目(附件台账 ",s.filesInBackup||0," 条)"]}),e.jsx("div",{children:S({...t})}),s.resealed?e.jsxs("div",{className:"muted",children:["用本机密钥库重新加密的密钥字段: ",s.resealed," 个"]}):null,e.jsxs("div",{className:"muted",children:["备份生成于 ",s.backupCreatedAt||"未知"]}),e.jsxs("pre",{className:"block",children:["恢复前的安全快照:",s.safetySnapshot]})]})}function H(){const{toast:s,guard:t,dialog:a,reload:d}=P(),[l,p]=r.useState(""),[h,b]=r.useState(""),[f,C]=r.useState(!1),[m,g]=r.useState(""),[c,N]=r.useState(null),[u,w]=r.useState(null),k=r.useRef(null),R=t(async()=>{if(!l){s("请先设置备份口令","bad");return}if(l!==h){s("两次输入的口令不一致","bad");return}const n=await v("/api/backup",{method:"POST",body:{password:l,"with-files":f}}),i=`nx-sk-backup-${I()}.nxskbak`;U(i,JSON.stringify(n.archive,null,2)+`
2
+ `);const o=n.counts,j=f?`;嵌入原件 ${o.filesEmbedded} 个`:`;附件台账 ${o.files||0} 条未嵌原件`;s(`已生成加密备份:${o.sections} 栏目 / ${o.entries} 条目${j}`),p(""),b("")}),T=async n=>{var j;const i=(j=n.target.files)==null?void 0:j[0];if(!i)return;const o=await i.text();N({name:i.name,bytes:i.size,text:o})},y=()=>c?m?c.bytes>O?(s("备份文件超过 28MB,可能超过接口上限;请改用 CLI 导入:nx-sk backup import","bad"),!1):!0:(s("请输入备份口令","bad"),!1):(s("请先选择备份文件","bad"),!1),$=n=>(w(n),g(""),N(null),k.current&&(k.current.value=""),d()),E=t(async()=>{if(!y()||!await a({confirmText:"解密并合并导入",message:"合并导入:只把备份里有、而当前缺失或留空的内容补进来,不会覆盖你已填过的值,也不会删改本地栏目结构。",detail:`备份文件:${c.name}
3
+ 冲突(两边都有值且不同)会保留本地、列在导入报告里。
4
+ 导入前会自动给当前数据留一份安全快照(可回滚)。`}))return;const i=await v("/api/backup/restore",{method:"POST",body:{password:m,content:c.text}});await $(i)}),M=t(async()=>{if(!y()||!await a({danger:!0,confirmText:"解密并整份替换",message:"整份替换:当前数据会被备份内容整体覆盖,包括删掉备份里没有的条目。",detail:`备份文件:${c.name}
5
+ 替换前会自动给当前数据留一份安全快照(可回滚)。`}))return;const i=await v("/api/backup/restore",{method:"POST",body:{password:m,content:c.text,replace:!0}});await $(i)});return e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"toolbar rowgap pad",children:[e.jsx("strong",{children:"加密备份"}),e.jsx("span",{className:"muted",children:"整份数据用口令对称加密(AES-256-GCM);导出设口令、恢复输口令,口令不落盘"})]}),e.jsxs("div",{className:"card",children:[e.jsx("div",{className:"colhead",children:"加密导出"}),e.jsxs("div",{className:"pad stack",children:[e.jsxs("div",{className:"grid",children:[e.jsxs("div",{className:"field",children:[e.jsxs("div",{className:"flabel",children:[e.jsx("span",{children:"设置备份口令"}),e.jsx("span",{className:"fkey",children:"建议至少 6 位"})]}),e.jsx("div",{className:"fctrl",children:e.jsx("input",{type:"password",autoComplete:"new-password",value:l,onChange:n=>p(n.target.value),placeholder:"备份口令"})})]}),e.jsxs("div",{className:"field",children:[e.jsx("div",{className:"flabel",children:e.jsx("span",{children:"再输入一次"})}),e.jsx("div",{className:"fctrl",children:e.jsx("input",{type:"password",autoComplete:"new-password",value:h,onChange:n=>b(n.target.value),placeholder:"确认口令"})})]})]}),e.jsxs("label",{className:"row",children:[e.jsx("input",{type:"checkbox",checked:f,onChange:n=>C(n.target.checked)}),e.jsx("span",{children:"连附件原件一起打包(体积显著变大;附件台账始终随包)"})]}),e.jsx("div",{className:"hint",children:"导出为单个 .nxskbak 文件并在浏览器下载;文件内含随机盐,密钥由口令经 scrypt 派生。恢复时需要输入同一口令。"}),e.jsx("div",{className:"rowgap",children:e.jsx("button",{className:"btn",onClick:R,children:"加密导出并下载"})})]})]}),e.jsxs("div",{className:"card",children:[e.jsx("div",{className:"colhead",children:"解密导入"}),e.jsxs("div",{className:"pad stack",children:[e.jsxs("div",{className:"grid",children:[e.jsxs("div",{className:"field",children:[e.jsxs("div",{className:"flabel",children:[e.jsx("span",{children:"备份文件"}),e.jsx("span",{className:"fkey",children:".nxskbak"})]}),e.jsx("div",{className:"fctrl",children:e.jsx("input",{ref:k,type:"file",accept:".nxskbak,application/json",onChange:T})})]}),e.jsxs("div",{className:"field",children:[e.jsx("div",{className:"flabel",children:e.jsx("span",{children:"输入备份口令"})}),e.jsx("div",{className:"fctrl",children:e.jsx("input",{type:"password",autoComplete:"off",value:m,onChange:n=>g(n.target.value),placeholder:"导出时设置的口令"})})]})]}),c?e.jsxs("div",{className:"row",children:[e.jsx("span",{className:"mono name",children:c.name}),e.jsx("span",{className:"acts",children:e.jsxs("span",{className:"tag",children:[c.bytes," 字节"]})})]}):e.jsx(A,{children:"还没有选择备份文件。"}),e.jsx("div",{className:"hint",children:"缺省「合并」:只增补、不覆盖你已填过的值,冲突保留本地并列入报告。口令错误或文件损坏会明确报错、不会改动当前数据;导入前会先留安全快照。"}),e.jsxs("div",{className:"rowgap",children:[e.jsx("button",{className:"btn",onClick:E,children:"解密并导入(合并)"}),e.jsx("button",{className:"btn danger",onClick:M,children:"整份替换…"})]})]})]}),e.jsx(B,{module:"backup"}),u?e.jsx(L,{title:u.mode==="replace"?"导入完成(整份替换)":"导入完成(合并)",onClose:()=>w(null),children:u.mode==="replace"?e.jsx(_,{r:u}):e.jsx(V,{r:u})}):null]})}export{H as default};
@@ -1,4 +1,4 @@
1
- import{u as C,r as i,a as h,j as s,b as f,T as j,E as b,C as y,M as E}from"./index-B7CtqgoG.js";function W(){var u,N,v;const{boot:r,toast:d,guard:w}=C(),[g,S]=i.useState([]),[a,k]=i.useState(null),[c,n]=i.useState({format:"both",section:"",out:"",withSecrets:!1}),[o,m]=i.useState(null),x=i.useCallback(async()=>{try{const[e,l]=await Promise.all([h("/api/sections"),h("/api/exports")]);S(e.sections),k(l)}catch(e){d(e.message,"bad")}},[d]);i.useEffect(()=>{x()},[x]),i.useEffect(()=>{const e=r==null?void 0:r.settings;e&&n(l=>({...l,format:e.exportFormat||"both",withSecrets:!!e.includeSecretsInExport,out:e.exportDir||""}))},[r]);const $=()=>({format:c.format,out:c.out.trim()||void 0,section:c.section||void 0,...c.withSecrets?{"with-secrets":!0}:{"no-secrets":!0}}),p=w(async e=>{const l=await h("/api/export",{method:"POST",body:{...$(),"dry-run":!!e}});if(e){m({title:"试运行(没有写盘)",body:s.jsx("pre",{className:"block",children:`目录: ${l.wouldWrite.dir}
1
+ import{u as C,r as i,a as h,j as s,b as f,T as j,E as b,C as y,M as E}from"./index-BygHBMGo.js";function W(){var u,N,v;const{boot:r,toast:d,guard:w}=C(),[g,S]=i.useState([]),[a,k]=i.useState(null),[c,n]=i.useState({format:"both",section:"",out:"",withSecrets:!1}),[o,m]=i.useState(null),x=i.useCallback(async()=>{try{const[e,l]=await Promise.all([h("/api/sections"),h("/api/exports")]);S(e.sections),k(l)}catch(e){d(e.message,"bad")}},[d]);i.useEffect(()=>{x()},[x]),i.useEffect(()=>{const e=r==null?void 0:r.settings;e&&n(l=>({...l,format:e.exportFormat||"both",withSecrets:!!e.includeSecretsInExport,out:e.exportDir||""}))},[r]);const $=()=>({format:c.format,out:c.out.trim()||void 0,section:c.section||void 0,...c.withSecrets?{"with-secrets":!0}:{"no-secrets":!0}}),p=w(async e=>{const l=await h("/api/export",{method:"POST",body:{...$(),"dry-run":!!e}});if(e){m({title:"试运行(没有写盘)",body:s.jsx("pre",{className:"block",children:`目录: ${l.wouldWrite.dir}
2
2
  栏目: ${l.wouldWrite.sections} 条目: ${l.wouldWrite.entries}
3
3
  凭据: ${l.wouldWrite.withSecrets?"含明文":"已打码"}
4
4
 
@@ -8,7 +8,7 @@
8
8
  <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16.png" />
9
9
  <link rel="icon" type="image/x-icon" href="/favicon.ico" />
10
10
  <link rel="apple-touch-icon" href="/logo-rounded.png" />
11
- <script type="module" crossorigin src="/assets/index-B7CtqgoG.js"></script>
11
+ <script type="module" crossorigin src="/assets/index-BygHBMGo.js"></script>
12
12
  <link rel="stylesheet" crossorigin href="/assets/index-DLtjhflt.css">
13
13
  </head>
14
14
  <body>
@@ -1,3 +0,0 @@
1
- import{u as C,r as i,a as b,j as s,E as S,C as R,M as $}from"./index-B7CtqgoG.js";function P(){const e=new Date,t=r=>String(r).padStart(2,"0");return`${e.getFullYear()}${t(e.getMonth()+1)}${t(e.getDate())}-${t(e.getHours())}${t(e.getMinutes())}${t(e.getSeconds())}`}function E(e,t){const r=new Blob([t],{type:"application/json;charset=utf-8"}),o=URL.createObjectURL(r),n=document.createElement("a");n.href=o,n.download=e,document.body.appendChild(n),n.click(),n.remove(),setTimeout(()=>URL.revokeObjectURL(o),1e3)}function O(){const{toast:e,guard:t,dialog:r,reload:o}=C(),[n,x]=i.useState(""),[h,m]=i.useState(""),[p,j]=i.useState(""),[c,f]=i.useState(null),[d,v]=i.useState(null),u=i.useRef(null),k=t(async()=>{if(!n){e("请先设置备份口令","bad");return}if(n!==h){e("两次输入的口令不一致","bad");return}const a=await b("/api/backup",{method:"POST",body:{password:n}}),l=`nx-sk-backup-${P()}.nxskbak`;E(l,JSON.stringify(a.archive,null,2)+`
2
- `),e(`已生成加密备份:${a.counts.sections} 栏目 / ${a.counts.entries} 条目`),x(""),m("")}),g=async a=>{var N;const l=(N=a.target.files)==null?void 0:N[0];if(!l)return;const y=await l.text();f({name:l.name,bytes:l.size,text:y})},w=t(async()=>{if(!c){e("请先选择备份文件","bad");return}if(!p){e("请输入备份口令","bad");return}if(!await r({danger:!0,confirmText:"解密并整份恢复",message:"恢复会用备份内容整份替换当前数据。",detail:`备份文件:${c.name}
3
- 恢复前会自动给当前数据留一份安全快照(可回滚)。`}))return;const l=await b("/api/backup/restore",{method:"POST",body:{password:p,content:c.text}});v(l),j(""),f(null),u.current&&(u.current.value=""),await o()});return s.jsxs(s.Fragment,{children:[s.jsxs("div",{className:"toolbar rowgap pad",children:[s.jsx("strong",{children:"加密备份"}),s.jsx("span",{className:"muted",children:"整份数据用口令对称加密(AES-256-GCM);导出设口令、恢复输口令,口令不落盘"})]}),s.jsxs("div",{className:"card",children:[s.jsx("div",{className:"colhead",children:"加密导出"}),s.jsxs("div",{className:"pad stack",children:[s.jsxs("div",{className:"grid",children:[s.jsxs("div",{className:"field",children:[s.jsxs("div",{className:"flabel",children:[s.jsx("span",{children:"设置备份口令"}),s.jsx("span",{className:"fkey",children:"建议至少 6 位"})]}),s.jsx("div",{className:"fctrl",children:s.jsx("input",{type:"password",autoComplete:"new-password",value:n,onChange:a=>x(a.target.value),placeholder:"备份口令"})})]}),s.jsxs("div",{className:"field",children:[s.jsx("div",{className:"flabel",children:s.jsx("span",{children:"再输入一次"})}),s.jsx("div",{className:"fctrl",children:s.jsx("input",{type:"password",autoComplete:"new-password",value:h,onChange:a=>m(a.target.value),placeholder:"确认口令"})})]})]}),s.jsx("div",{className:"hint",children:"导出为单个 .nxskbak 文件并在浏览器下载;文件内含随机盐,密钥由口令经 scrypt 派生。恢复时需要输入同一口令。"}),s.jsx("div",{className:"rowgap",children:s.jsx("button",{className:"btn",onClick:k,children:"加密导出并下载"})})]})]}),s.jsxs("div",{className:"card",children:[s.jsx("div",{className:"colhead",children:"解密恢复(导入)"}),s.jsxs("div",{className:"pad stack",children:[s.jsxs("div",{className:"grid",children:[s.jsxs("div",{className:"field",children:[s.jsxs("div",{className:"flabel",children:[s.jsx("span",{children:"备份文件"}),s.jsx("span",{className:"fkey",children:".nxskbak"})]}),s.jsx("div",{className:"fctrl",children:s.jsx("input",{ref:u,type:"file",accept:".nxskbak,application/json",onChange:g})})]}),s.jsxs("div",{className:"field",children:[s.jsx("div",{className:"flabel",children:s.jsx("span",{children:"输入备份口令"})}),s.jsx("div",{className:"fctrl",children:s.jsx("input",{type:"password",autoComplete:"off",value:p,onChange:a=>j(a.target.value),placeholder:"导出时设置的口令"})})]})]}),c?s.jsxs("div",{className:"row",children:[s.jsx("span",{className:"mono name",children:c.name}),s.jsx("span",{className:"acts",children:s.jsxs("span",{className:"tag",children:[c.bytes," 字节"]})})]}):s.jsx(S,{children:"还没有选择备份文件。"}),s.jsx("div",{className:"hint",children:"口令错误或文件损坏会明确报错、不会改动当前数据;确认恢复前会先留安全快照。"}),s.jsx("div",{className:"rowgap",children:s.jsx("button",{className:"btn danger",onClick:w,children:"解密并恢复"})})]})]}),s.jsx(R,{module:"backup"}),d?s.jsx($,{title:"恢复完成",onClose:()=>v(null),children:s.jsxs("div",{className:"stack",children:[s.jsxs("div",{children:["已从加密备份恢复:",d.sections," 个栏目 / ",d.entries," 条条目"]}),s.jsxs("div",{className:"muted",children:["备份生成于 ",d.backupCreatedAt||"未知"]}),s.jsxs("pre",{className:"block",children:["恢复前的安全快照:",d.safetySnapshot]})]})}):null]})}export{O as default};