nx-sk 0.6.2 → 0.7.1
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.
- package/CHANGELOG.md +17 -0
- package/README.md +15 -8
- package/assets/nx-sk/references/30-export-backup.md +12 -4
- package/package.json +1 -1
- package/src/modules/backup/index.js +9 -4
- package/src/modules/backup/service.js +461 -51
- package/src/modules/backup/view.jsx +133 -22
- package/src/web/frontend/components/fieldEditor.jsx +31 -2
- package/src/web/public/assets/{files-BNh48qeo.js → files-2kd_BT_X.js} +1 -1
- package/src/web/public/assets/{index-B7CtqgoG.js → index-Dzp1e1hp.js} +2 -2
- package/src/web/public/assets/{view-Bu70Q6GO.js → view-C4H5NgGI.js} +1 -1
- package/src/web/public/assets/{view-D5LfHr7W.js → view-CVOS0EBs.js} +1 -1
- package/src/web/public/assets/view-CmCEE0EV.js +5 -0
- package/src/web/public/assets/{view-BIdVqIGe.js → view-D7cRKEwG.js} +1 -1
- package/src/web/public/assets/{view-BAYyPWdU.js → view-DRj3unSu.js} +1 -1
- package/src/web/public/assets/view-qZDNlGNu.js +7 -0
- package/src/web/public/index.html +1 -1
- package/src/web/public/assets/view-1kwWQYbz.js +0 -7
- package/src/web/public/assets/view-DXqI-4_F.js +0 -3
|
@@ -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-Dzp1e1hp.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-Dzp1e1hp.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-
|
|
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-Dzp1e1hp.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
|
|
|
@@ -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-
|
|
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-Dzp1e1hp.js";import{f as K,r as f}from"./files-2kd_BT_X.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};
|
|
@@ -0,0 +1,7 @@
|
|
|
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 z}from"./index-Dzp1e1hp.js";import{l as V,f as Y,r as G}from"./files-2kd_BT_X.js";function Q({value:c,onChange:g,minRows:u=2,placeholder:j,disabled:i,maxLength:o}){const x=d.useRef(null);return d.useLayoutEffect(()=>{const s=x.current;s&&(s.style.height="auto",s.style.height=`${s.scrollHeight}px`)},[c]),e.jsx("textarea",{ref:x,value:String(c??""),onChange:s=>g(s.target.value),rows:u,placeholder:j,disabled:i,maxLength:o,style:{resize:"none",overflow:"hidden",width:"100%"}})}function W({def:c,value:g,onChange:u}){const{toast:j,guard:i,patchUi:o}=K(),[x,s]=d.useState(null),m=d.useRef(null),h=g||"",C=d.useCallback(async()=>{try{s(await V())}catch{}},[]);d.useEffect(()=>{C()},[C]);const p=((x==null?void 0:x.files)||[]).find(a=>a.id===h)||null,$=i(async a=>{const y=a==null?void 0:a[0];if(!y)return;const v=await Y(y),S=await N("/api/files",{method:"POST",body:{content:v,name:y.name}});u(S.id),j(S.duplicated?"库中已有相同文件,已直接引用":"已上传并关联"),s(await G())});return e.jsxs("div",{className:"file-picker",children:[p?e.jsxs("div",{className:"file-preview",children:[p.isImage?e.jsx("img",{src:p.url,alt:p.name}):e.jsx("span",{className:"ext-badge",style:{fontSize:13,width:40,textAlign:"center"},children:p.ext?p.ext.slice(1).toUpperCase():"FILE"}),e.jsx("a",{className:"file-preview-name grow",href:p.url,target:"_blank",rel:"noreferrer",title:p.displayPath,children:p.name}),e.jsx("a",{className:"btn small ghost",href:p.downloadUrl,children:"下载"}),e.jsx(B,{className:"hint",text:p.path,title:"点击复制物理路径",children:"路径"}),e.jsx("button",{type:"button",className:"btn small ghost",onClick:()=>u(""),children:"清除"})]}):null,e.jsxs("div",{className:"file-pick-row",children:[e.jsxs("select",{value:h,onChange:a=>u(a.target.value),children:[e.jsx("option",{value:"",children:"(未选择:从附件库选一个)"}),((x==null?void 0:x.files)||[]).map(a=>e.jsx("option",{value:a.id,children:a.name},a.id))]}),e.jsx("button",{type:"button",className:"btn small",onClick:()=>{var a;return(a=m.current)==null?void 0:a.click()},children:"上传文件"}),e.jsx("button",{type:"button",className:"btn small ghost",onClick:()=>o({view:"files"}),children:"附件中心"}),e.jsx("input",{ref:m,type:"file",style:{display:"none"},onChange:a=>{$(a.target.files),a.target.value=""}})]}),h&&!p?e.jsxs("span",{className:"hint bad",children:["引用的附件已不存在(",h,")—— 重新上传,或清除。"]}):null,c.hint?e.jsx("span",{className:"hint",children:c.hint}):null]})}function J({def:c,value:g,onChange:u,disabled:j}){const i=c.type||"text",o=g??"";if(i==="file")return e.jsx(W,{def:c,value:o,onChange:u});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:m=>u(m.target.checked)}),e.jsx("span",{className:"muted",children:s?"是":"否"})]})}if(i==="select"){const s=c.options||[],m=o!==""&&!s.includes(o);return e.jsxs("select",{value:m?"__custom":String(o),onChange:h=>u(h.target.value==="__custom"?"":h.target.value),children:[e.jsx("option",{value:"",children:"(未填写)"}),s.map(h=>e.jsx("option",{value:h,children:h},h)),m?e.jsxs("option",{value:"__custom",children:[String(o),"(当前值,不在候选里)"]}):null]})}if(i==="textarea")return e.jsx(Q,{value:o,onChange:u});if(i==="tags"){const s=Array.isArray(o)?o.join("、"):String(o);return e.jsx("input",{value:s,placeholder:c.maxItems?`多个用「、」分隔,最多 ${c.maxItems} 个`:"多个用「、」分隔",onChange:m=>u(m.target.value.split(/[,,、;;]+/).map(h=>h.trim()).filter(Boolean))})}const x=i==="number"?"number":i==="date"?"date":i==="month"?"month":"text";return i==="secret"?e.jsx("input",{type:"text",value:String(o),placeholder:"密文落盘;原样保留占位则视为不修改",disabled:j,onChange:s=>u(s.target.value)}):e.jsx("input",{type:x,value:String(o),onChange:s=>u(s.target.value)})}function X({title:c,fields:g,draft:u,onPatch:j}){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:g.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(J,{def:i,value:u[i.key],onChange:o=>j(i.key,o)})})]},i.key))})})]})}function Z({sectionId:c,sectionTitle:g}){const{ui:u,patchUi:j,toast:i,guard:o,dialog:x,reload:s}=K(),[m,h]=d.useState(null),[C,p]=d.useState({}),[$,a]=d.useState({name:"",value:""}),y=!!u.mask,v=d.useCallback(async()=>{try{const l=`section=${encodeURIComponent(c)}${y?"&mask=1":""}`,n=await N(`/api/keys?${l}`);h(n);const f={};for(const E of n.keys)f[E.name]=E.value??"";p(f)}catch(l){i(l.message,"bad"),h(null)}},[c,y,i]);d.useEffect(()=>{v()},[v]);const S=o(async l=>{const n=C[l.name];(l.value??"")!==n&&(await N(`/api/keys/${encodeURIComponent(l.name)}`,{method:"PUT",body:{value:n,section:c}}),i(`已保存 ${l.name}`),await v())}),T=o(async(l,n)=>{const f=String(n||"").trim();if(!(!f||f===l.name)){if(m.keys.some(E=>E.name===f)){i(`已经有叫「${f}」的键了`,"bad"),await v();return}await N(`/api/entries/${encodeURIComponent(l.id)}`,{method:"PATCH",body:{title:f}}),p(E=>{const k={};for(const[D,F]of Object.entries(E))k[D===l.name?f:D]=F;return k}),i(`已重命名为 ${f}`),await v(),await s()}}),U=o(async()=>{const l=$.name.trim();if(!l){i("先填键名,例如 OPENAI_KEY","bad");return}if(m.keys.some(n=>n.name===l)){i(`已经有叫「${l}」的键了——直接在上面那行改值即可`,"bad");return}await N(`/api/keys/${encodeURIComponent(l)}`,{method:"PUT",body:{value:$.value,section:c}}),a({name:"",value:""}),i(`已新增 ${l}`),await v(),await s()}),A=o(async l=>{await x({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 v(),await s())});return m?e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"toolbar rowgap pad",children:[e.jsx("strong",{children:g}),e.jsxs("span",{className:"muted",children:[m.count," 个键 · 名称 → 值"]}),e.jsx("span",{className:"grow"}),e.jsxs("label",{className:"inline hint",children:[e.jsx("input",{type:"checkbox",checked:y,onChange:()=>j({mask:!y})}),"打码显示(投屏/截图时用)"]}),e.jsx("button",{className:"btn small ghost",onClick:v,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:[m.keys.length?m.keys.map(l=>e.jsxs("div",{className:"row",children:[e.jsx("input",{className:"mono",style:{width:200},defaultValue:l.name,title:"改完按回车或点开别处即重命名",onBlur:n=>T(l,n.target.value),onKeyDown:n=>{n.key==="Enter"&&n.currentTarget.blur()}}),e.jsx("input",{className:"mono grow",value:C[l.name]??"",onChange:n=>p(f=>({...f,[l.name]:n.target.value})),onBlur:()=>S(l),onKeyDown:n=>{n.key==="Enter"&&n.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=>a(n=>({...n,name:l.target.value})),onKeyDown:l=>{l.key==="Enter"&&U()}}),e.jsx("input",{className:"mono grow",placeholder:"值(留空也行,之后再填)",value:$.value,onChange:l=>a(n=>({...n,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 te({sectionId:c}){var I;const{ui:g,patchUi:u,toast:j,guard:i,dialog:o,reload:x}=K(),[s,m]=d.useState(null),[h,C]=d.useState(null),[p,$]=d.useState(!1),[a,y]=d.useState(null),[v,S]=d.useState(null),[T,U]=d.useState(""),A=!!g.mask,l=d.useCallback(async t=>{$(!0),C(null);try{const r=await N(`/api/sections/${encodeURIComponent(c)}/dump${A?"?mask=1":""}`);m(r);const w=t||g.entry,b=r.entries.find(P=>P.id===w)||r.entries[0]||null;y(b?{id:b.id,title:b.title,values:{...b.values}}:null),b&&b.id!==g.entry&&u({entry:b.id})}catch(r){C(r.message),m(null),y(null)}finally{$(!1)}},[c,A]);d.useEffect(()=>{l()},[l]);const n=d.useMemo(()=>(s==null?void 0:s.entries.find(t=>t.id===(a==null?void 0:a.id)))||null,[s,a]),f=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)=>y(w=>w&&{...w,values:{...w.values,[t]:r}}),k=d.useMemo(()=>{if(!a||!n)return{set:{},titleChanged:!1,count:0};const t={};for(const r of s.fields)O(n.values[r.key])!==O(a.values[r.key])&&(t[r.key]=a.values[r.key]);return{set:t,titleChanged:a.title!==n.title,count:Object.keys(t).length+(a.title!==n.title?1:0)}},[a,n,s]),D=i(async()=>{if(!a||!n)return;if(!k.count){j("没有改动");return}const t={set:k.set};k.titleChanged&&(t.title=a.title),await N(`/api/entries/${encodeURIComponent(a.id)}`,{method:"PATCH",body:t}),j(`已保存 ${k.count} 处改动`),await l(a.id),await x()}),F=i(async()=>{var P;if(!a){j("先新建一条条目","bad");return}const t=T.indexOf("=");if(t<=0){j("用 字段=值 的格式,例如 微信=zhangsan","bad");return}const r=T.slice(0,t).trim(),w=T.slice(t+1),b=await N(`/api/entries/${encodeURIComponent(a.id)}`,{method:"PATCH",body:{set:{[r]:w}}});U(""),j(`已写入 ${r}${(P=b.newFields)!=null&&P.length?"(新字段已登记)":""}`),await l(a.id),await x()}),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 b=await N("/api/entries",{method:"POST",body:w});j(`已新建:${b.title}`),await l(b.id),await x()}),L=i(async()=>{!n||!await o({message:`删除条目「${n.title}」?删除前会自动留一份快照在 ~/nx-sk/backup。`,danger:!0,confirmText:"删除"})||(await N(`/api/entries/${encodeURIComponent(n.id)}`,{method:"DELETE"}),j("已删除"),await l(""),await x())}),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
|
+
格式: ${t.format}
|
|
3
|
+
条目: ${t.entries}
|
|
4
|
+
凭据: ${t.withSecrets?"含明文":"已打码"}
|
|
5
|
+
|
|
6
|
+
${t.files.map(r=>`${r.path} ${r.bytes} 字节`).join(`
|
|
7
|
+
`)}`})})});return h?e.jsxs("div",{className:"pad stack",children:[e.jsx("div",{className:"bad",children:e.jsx("strong",{children:"栏目读不到"})}),e.jsx("pre",{className:"block",children:h}),e.jsx("div",{className:"hint",children:"如果这个栏目刚被删掉,切到「栏目」tab 看看现有栏目。"})]}):s?s.section.kv?e.jsx(Z,{sectionId:c,sectionTitle:s.section.title}):e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"toolbar rowgap pad",children:[e.jsx("strong",{children:s.section.title}),e.jsx(_,{children:s.section.id}),e.jsxs("span",{className:"muted",children:[s.count," 条 / ",s.section.fields," 字段"]}),s.section.description?e.jsx("span",{className:"desc muted nowrap grow",children:s.section.description}):e.jsx("span",{className:"grow"}),e.jsxs("label",{className:"inline hint",children:[e.jsx("input",{type:"checkbox",checked:A,onChange:()=>u({mask:!A})}),"打码显示(投屏/截图时用)"]}),e.jsx("button",{className:"btn small ghost",onClick:()=>l(),children:"刷新"}),e.jsx("button",{className:"btn small ghost",onClick:q,children:"导出本栏目"}),e.jsx("button",{className:"btn small",onClick:H,children:"新建条目"})]}),e.jsxs("div",{className:"cols",children:[e.jsx("div",{className:"col",style:{flex:"0 0 300px"},children:e.jsxs("div",{className:"card",children:[e.jsxs("div",{className:"colhead",children:["条目列表",e.jsx("span",{className:"right muted",children:s.count})]}),e.jsx("div",{className:"colbody",children:s.entries.length?s.entries.map(t=>e.jsxs("div",{className:`row selectable ${t.id===(a==null?void 0:a.id)?"selected":""}`,onClick:()=>{u({entry:t.id}),y({id:t.id,title:t.title,values:{...t.values}})},children:[e.jsx("span",{className:"name",children:t.title||"(无名)"}),e.jsx("span",{className:"acts",children:t.missing.length?e.jsxs("span",{className:"tag",children:[t.completeness.ratio,"%"]}):e.jsx("span",{className:"tag strong",children:"已填满"})})]},t.id)):e.jsxs(R,{children:["还没有条目。点右上角「新建条目」,或用 CLI:nx-sk entry add --section ",s.section.id," --set ..."]})})]})}),e.jsx("div",{className:"col",children:a?e.jsxs("div",{className:"card",children:[e.jsxs("div",{className:"colhead",children:[e.jsx("span",{className:"grow nowrap",children:(n==null?void 0:n.title)||a.title}),e.jsx("span",{className:"muted mono",children:a.id}),n?e.jsxs(_,{kind:n.missing.length?"":"strong",children:["完整度 ",n.completeness.ratio,"%"]}):null]}),e.jsxs("div",{className:"field wide pad",style:{borderBottom:"1px solid var(--soft-2)"},children:[e.jsxs("div",{className:"flabel",children:[e.jsx("span",{children:s.section.titleLabel||"名称"}),e.jsx("span",{className:"bad",children:"改动后必须保存"})]}),e.jsx("div",{className:"fctrl",children:e.jsx("input",{value:a.title,onChange:t=>y(r=>({...r,title:t.target.value}))})})]}),e.jsxs("div",{className:"rowgap",style:{padding:"6px 14px",borderBottom:"1px solid var(--soft-2)"},children:[e.jsx("input",{className:"grow",style:{minWidth:220},placeholder:"直接加字段:字段=值 (回车写入;没见过的键会自动登记)",value:T,onChange:t=>U(t.target.value),onKeyDown:t=>{t.key==="Enter"&&F()}}),e.jsx("button",{className:"btn small",onClick:F,disabled:!T.includes("="),children:"写入"})]}),f.map(t=>e.jsx(X,{title:t.title,fields:t.fields,draft:a.values,onPatch:E},t.id)),e.jsxs("div",{className:"rowgap pad",children:[e.jsxs("button",{className:"btn",onClick:D,disabled:!k.count,children:["保存",k.count?`(${k.count} 处改动)`:""]}),e.jsx("button",{className:"btn ghost",onClick:()=>l(a.id),disabled:!k.count,children:"放弃改动"}),e.jsx("button",{className:"btn ghost",onClick:L,children:"删除条目"}),n!=null&&n.missing.length?e.jsxs("span",{className:"hint",children:["模板建议里还有 ",n.missing.length," 项没填(不影响使用):",n.missing.slice(0,5).map(t=>t.label).join("、"),n.missing.length>5?"…":""]}):null,(I=n==null?void 0:n.excluded)!=null&&I.length?e.jsxs("span",{className:"hint",children:["另有 ",n.excluded.length," 项已标不填、不计入完整度:",n.excluded.slice(0,6).map(t=>t.label).join("、"),n.excluded.length>6?"…":""]}):null]})]}):e.jsx("div",{className:"card",children:e.jsx(R,{children:"左边选一条,或新建一条"})})})]}),e.jsx(M,{module:"entries"}),v?e.jsx(z,{title:v.title,onClose:()=>S(null),children:v.body}):null]}):e.jsx("div",{className:"pad muted",children:p?"加载中…":"暂无数据"})}export{te as default};
|
|
@@ -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-
|
|
11
|
+
<script type="module" crossorigin src="/assets/index-Dzp1e1hp.js"></script>
|
|
12
12
|
<link rel="stylesheet" crossorigin href="/assets/index-DLtjhflt.css">
|
|
13
13
|
</head>
|
|
14
14
|
<body>
|
|
@@ -1,7 +0,0 @@
|
|
|
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}
|
|
2
|
-
格式: ${t.format}
|
|
3
|
-
条目: ${t.entries}
|
|
4
|
-
凭据: ${t.withSecrets?"含明文":"已打码"}
|
|
5
|
-
|
|
6
|
-
${t.files.map(r=>`${r.path} ${r.bytes} 字节`).join(`
|
|
7
|
-
`)}`})})});return h?e.jsxs("div",{className:"pad stack",children:[e.jsx("div",{className:"bad",children:e.jsx("strong",{children:"栏目读不到"})}),e.jsx("pre",{className:"block",children:h}),e.jsx("div",{className:"hint",children:"如果这个栏目刚被删掉,切到「栏目」tab 看看现有栏目。"})]}):s?s.section.kv?e.jsx(X,{sectionId:c,sectionTitle:s.section.title}):e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"toolbar rowgap pad",children:[e.jsx("strong",{children:s.section.title}),e.jsx(_,{children:s.section.id}),e.jsxs("span",{className:"muted",children:[s.count," 条 / ",s.section.fields," 字段"]}),s.section.description?e.jsx("span",{className:"desc muted nowrap grow",children:s.section.description}):e.jsx("span",{className:"grow"}),e.jsxs("label",{className:"inline hint",children:[e.jsx("input",{type:"checkbox",checked:A,onChange:()=>m({mask:!A})}),"打码显示(投屏/截图时用)"]}),e.jsx("button",{className:"btn small ghost",onClick:()=>l(),children:"刷新"}),e.jsx("button",{className:"btn small ghost",onClick:q,children:"导出本栏目"}),e.jsx("button",{className:"btn small",onClick:H,children:"新建条目"})]}),e.jsxs("div",{className:"cols",children:[e.jsx("div",{className:"col",style:{flex:"0 0 300px"},children:e.jsxs("div",{className:"card",children:[e.jsxs("div",{className:"colhead",children:["条目列表",e.jsx("span",{className:"right muted",children:s.count})]}),e.jsx("div",{className:"colbody",children:s.entries.length?s.entries.map(t=>e.jsxs("div",{className:`row selectable ${t.id===(n==null?void 0:n.id)?"selected":""}`,onClick:()=>{m({entry:t.id}),g({id:t.id,title:t.title,values:{...t.values}})},children:[e.jsx("span",{className:"name",children:t.title||"(无名)"}),e.jsx("span",{className:"acts",children:t.missing.length?e.jsxs("span",{className:"tag",children:[t.completeness.ratio,"%"]}):e.jsx("span",{className:"tag strong",children:"已填满"})})]},t.id)):e.jsxs(R,{children:["还没有条目。点右上角「新建条目」,或用 CLI:nx-sk entry add --section ",s.section.id," --set ..."]})})]})}),e.jsx("div",{className:"col",children:n?e.jsxs("div",{className:"card",children:[e.jsxs("div",{className:"colhead",children:[e.jsx("span",{className:"grow nowrap",children:(a==null?void 0:a.title)||n.title}),e.jsx("span",{className:"muted mono",children:n.id}),a?e.jsxs(_,{kind:a.missing.length?"":"strong",children:["完整度 ",a.completeness.ratio,"%"]}):null]}),e.jsxs("div",{className:"field wide pad",style:{borderBottom:"1px solid var(--soft-2)"},children:[e.jsxs("div",{className:"flabel",children:[e.jsx("span",{children:s.section.titleLabel||"名称"}),e.jsx("span",{className:"bad",children:"改动后必须保存"})]}),e.jsx("div",{className:"fctrl",children:e.jsx("input",{value:n.title,onChange:t=>g(r=>({...r,title:t.target.value}))})})]}),e.jsxs("div",{className:"rowgap",style:{padding:"6px 14px",borderBottom:"1px solid var(--soft-2)"},children:[e.jsx("input",{className:"grow",style:{minWidth:220},placeholder:"直接加字段:字段=值 (回车写入;没见过的键会自动登记)",value:T,onChange:t=>U(t.target.value),onKeyDown:t=>{t.key==="Enter"&&F()}}),e.jsx("button",{className:"btn small",onClick:F,disabled:!T.includes("="),children:"写入"})]}),v.map(t=>e.jsx(J,{title:t.title,fields:t.fields,draft:n.values,onPatch:E},t.id)),e.jsxs("div",{className:"rowgap pad",children:[e.jsxs("button",{className:"btn",onClick:D,disabled:!k.count,children:["保存",k.count?`(${k.count} 处改动)`:""]}),e.jsx("button",{className:"btn ghost",onClick:()=>l(n.id),disabled:!k.count,children:"放弃改动"}),e.jsx("button",{className:"btn ghost",onClick:L,children:"删除条目"}),a!=null&&a.missing.length?e.jsxs("span",{className:"hint",children:["模板建议里还有 ",a.missing.length," 项没填(不影响使用):",a.missing.slice(0,5).map(t=>t.label).join("、"),a.missing.length>5?"…":""]}):null,(I=a==null?void 0:a.excluded)!=null&&I.length?e.jsxs("span",{className:"hint",children:["另有 ",a.excluded.length," 项已标不填、不计入完整度:",a.excluded.slice(0,6).map(t=>t.label).join("、"),a.excluded.length>6?"…":""]}):null]})]}):e.jsx("div",{className:"card",children:e.jsx(R,{children:"左边选一条,或新建一条"})})})]}),e.jsx(M,{module:"entries"}),y?e.jsx(V,{title:y.title,onClose:()=>S(null),children:y.body}):null]}):e.jsx("div",{className:"pad muted",children:x?"加载中…":"暂无数据"})}export{se as default};
|
|
@@ -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};
|