feat: complete SOP execution workflow
This commit is contained in:
1
web/dist/assets/DeleteOutlined-Bt9-1HZZ.js
vendored
Normal file
1
web/dist/assets/DeleteOutlined-Bt9-1HZZ.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import{F as e,a as t}from"./client-Bk7n1O1W.js";var n={icon:{tag:`svg`,attrs:{viewBox:`64 64 896 896`,focusable:`false`},children:[{tag:`path`,attrs:{d:`M360 184h-8c4.4 0 8-3.6 8-8v8h304v-8c0 4.4 3.6 8 8 8h-8v72h72v-80c0-35.3-28.7-64-64-64H352c-35.3 0-64 28.7-64 64v80h72v-72zm504 72H160c-17.7 0-32 14.3-32 32v32c0 4.4 3.6 8 8 8h60.4l24.7 523c1.6 34.1 29.8 61 63.9 61h454c34.2 0 62.3-26.8 63.9-61l24.7-523H888c4.4 0 8-3.6 8-8v-32c0-17.7-14.3-32-32-32zM731.3 840H292.7l-24.2-512h487l-24.2 512z`}}]},name:`delete`,theme:`outlined`};function r(e){for(var t=1;t<arguments.length;t++){var n=arguments[t]==null?{}:Object(arguments[t]),r=Object.keys(n);typeof Object.getOwnPropertySymbols==`function`&&(r=r.concat(Object.getOwnPropertySymbols(n).filter(function(e){return Object.getOwnPropertyDescriptor(n,e).enumerable}))),r.forEach(function(t){i(e,t,n[t])})}return e}function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}var a=function(i,a){var o=r({},i,a.attrs);return e(t,r({},o,{icon:n}),null)};a.displayName=`DeleteOutlined`,a.inheritAttrs=!1;export{a as t};
|
||||
37
web/dist/assets/DeleteOutlined-CRNedxr9.js
vendored
37
web/dist/assets/DeleteOutlined-CRNedxr9.js
vendored
File diff suppressed because one or more lines are too long
1
web/dist/assets/ExecuteView--myIljT9.css
vendored
1
web/dist/assets/ExecuteView--myIljT9.css
vendored
@@ -1 +0,0 @@
|
||||
.execution-shell[data-v-e2671b1e]{max-width:1220px}.sop-catalog[data-v-e2671b1e]{grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;display:grid}.sop-entry[data-v-e2671b1e]{text-align:left;cursor:pointer;grid-template-columns:40px 1fr 42px;align-items:start;gap:14px;min-height:150px;padding:22px;display:grid}.sop-entry[data-v-e2671b1e]:hover{border-color:#9ac8b8;box-shadow:0 8px 24px #20282512}.entry-seq[data-v-e2671b1e]{color:#8c9893;font-family:monospace;font-size:12px}.sop-entry small[data-v-e2671b1e]{color:var(--green);font-weight:650}.sop-entry h2[data-v-e2671b1e]{margin:8px 0 7px;font-family:Noto Serif SC,serif;font-size:20px}.sop-entry p[data-v-e2671b1e]{color:var(--muted);margin:0;line-height:1.6}.play[data-v-e2671b1e]{color:#fff;background:#202825;border-radius:4px;place-items:center;width:38px;height:38px;font-size:18px;display:grid}.run-workspace[data-v-e2671b1e]{grid-template-columns:270px minmax(0,1fr);gap:18px;display:grid}.run-context[data-v-e2671b1e]{color:#fff;background:#202825;border-radius:6px;align-self:start;padding:24px;position:sticky;top:86px}.run-label[data-v-e2671b1e]{color:#72c9ab;font-size:11px;font-weight:700}.run-context h2[data-v-e2671b1e]{margin:12px 0 8px;font-family:Noto Serif SC,serif}.run-context>p[data-v-e2671b1e]{color:#aebdb7;margin:0 0 28px;line-height:1.6}.context-meta[data-v-e2671b1e]{border-top:1px solid #3a4742;justify-content:space-between;align-items:center;padding:13px 0;display:flex}.context-meta span[data-v-e2671b1e]{color:#9eada7;font-size:12px}.privacy-note[data-v-e2671b1e]{color:#889b93;gap:8px;margin-top:28px;font-size:11px;display:flex}.conversation[data-v-e2671b1e]{min-height:590px;padding:30px 34px}.conversation-progress[data-v-e2671b1e]{color:#66736d;align-items:center;gap:8px;font-size:12px;display:flex}.conversation-progress span[data-v-e2671b1e]{background:#d08736;border-radius:50%;width:8px;height:8px;box-shadow:0 0 0 4px #faefe1}.conversation-progress span.done[data-v-e2671b1e]{background:var(--green);box-shadow:0 0 0 4px #e4f1ec}.node-content[data-v-e2671b1e]{padding:44px 0 26px}.node-content small[data-v-e2671b1e]{color:var(--green);font-size:10px;font-weight:800}.node-content h1[data-v-e2671b1e]{margin:8px 0 22px;font-family:Noto Serif SC,serif;font-size:28px}.node-content blockquote[data-v-e2671b1e]{color:#29342f;border-left:3px solid var(--green);background:#f2f6f4;margin:0;padding:18px 20px;font-size:17px;line-height:1.8}.answer-form[data-v-e2671b1e]{max-width:680px}.choice-group[data-v-e2671b1e]{flex-wrap:wrap;display:flex}.run-actions[data-v-e2671b1e]{border-top:1px solid var(--line);color:var(--muted);justify-content:space-between;align-items:center;gap:16px;margin:24px -34px -30px;padding:18px 34px;font-size:12px;display:flex}.completed-state[data-v-e2671b1e]{text-align:center;padding:40px 0}.completed-state>span[data-v-e2671b1e]{color:var(--green);font-size:50px}.completed-state h3[data-v-e2671b1e]{margin:14px 0 8px;font-family:Noto Serif SC,serif;font-size:24px}.completed-state p[data-v-e2671b1e]{color:var(--muted);margin:0 0 24px}.quick-feedback[data-v-e2671b1e]{text-align:left;border-top:1px solid var(--line);border-bottom:1px solid var(--line);background:#f5f8f6;grid-template-columns:180px 1fr auto;align-items:center;gap:12px;max-width:620px;margin:0 auto 18px;padding:16px;display:grid}.quick-feedback>span[data-v-e2671b1e]{font-size:12px;font-weight:650}.quick-feedback[data-v-e2671b1e] .ant-rate{font-size:18px}.quick-feedback[data-v-e2671b1e] .ant-input{grid-column:1/3}.feedback-thanks[data-v-e2671b1e]{max-width:520px;color:var(--green);background:#edf6f2;margin:0 auto 18px;padding:12px;font-size:12px}@media (width<=800px){.sop-catalog[data-v-e2671b1e],.run-workspace[data-v-e2671b1e]{grid-template-columns:1fr}.run-context[data-v-e2671b1e]{position:static}.conversation[data-v-e2671b1e]{padding:22px}.run-actions[data-v-e2671b1e]{margin:24px -22px -22px;padding:16px 22px}.run-actions span[data-v-e2671b1e]{display:none}.quick-feedback[data-v-e2671b1e]{grid-template-columns:1fr}.quick-feedback[data-v-e2671b1e] .ant-input{grid-column:auto}}
|
||||
1
web/dist/assets/ExecuteView-BrNPNrQz.js
vendored
Normal file
1
web/dist/assets/ExecuteView-BrNPNrQz.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
web/dist/assets/ExecuteView-CJavC20H.js
vendored
1
web/dist/assets/ExecuteView-CJavC20H.js
vendored
File diff suppressed because one or more lines are too long
1
web/dist/assets/ExecuteView-CTC6GSK6.css
vendored
Normal file
1
web/dist/assets/ExecuteView-CTC6GSK6.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.execution-shell[data-v-937f7610]{max-width:1220px}.sop-catalog[data-v-937f7610]{grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;display:grid}.sop-entry[data-v-937f7610]{text-align:left;cursor:pointer;grid-template-columns:40px 1fr 42px;align-items:start;gap:14px;min-height:150px;padding:22px;display:grid}.sop-entry[data-v-937f7610]:hover{border-color:#9ac8b8;box-shadow:0 8px 24px #20282512}.entry-seq[data-v-937f7610]{color:#8c9893;font-family:monospace;font-size:12px}.sop-entry small[data-v-937f7610]{color:var(--green);font-weight:650}.sop-entry h2[data-v-937f7610]{margin:8px 0 7px;font-family:Noto Serif SC,serif;font-size:20px}.sop-entry p[data-v-937f7610]{color:var(--muted);margin:0;line-height:1.6}.play[data-v-937f7610]{color:#fff;background:#202825;border-radius:4px;place-items:center;width:38px;height:38px;font-size:18px;display:grid}.run-workspace[data-v-937f7610]{grid-template-columns:270px minmax(0,1fr);gap:18px;display:grid}.run-context[data-v-937f7610]{color:#fff;background:#202825;border-radius:6px;align-self:start;padding:24px;position:sticky;top:86px}.run-label[data-v-937f7610]{color:#72c9ab;font-size:11px;font-weight:700}.run-context h2[data-v-937f7610]{margin:12px 0 8px;font-family:Noto Serif SC,serif}.run-context>p[data-v-937f7610]{color:#aebdb7;margin:0 0 28px;line-height:1.6}.context-meta[data-v-937f7610]{border-top:1px solid #3a4742;justify-content:space-between;align-items:center;padding:13px 0;display:flex}.context-meta span[data-v-937f7610]{color:#9eada7;font-size:12px}.privacy-note[data-v-937f7610]{color:#889b93;gap:8px;margin-top:28px;font-size:11px;display:flex}.conversation[data-v-937f7610]{min-height:590px;padding:30px 34px}.conversation-progress[data-v-937f7610]{color:#66736d;align-items:center;gap:8px;font-size:12px;display:flex}.conversation-progress span[data-v-937f7610]{background:#d08736;border-radius:50%;width:8px;height:8px;box-shadow:0 0 0 4px #faefe1}.conversation-progress span.done[data-v-937f7610]{background:var(--green);box-shadow:0 0 0 4px #e4f1ec}.node-content[data-v-937f7610]{padding:44px 0 26px}.node-content small[data-v-937f7610]{color:var(--green);font-size:10px;font-weight:800}.node-content h1[data-v-937f7610]{margin:8px 0 22px;font-family:Noto Serif SC,serif;font-size:28px}.node-content blockquote[data-v-937f7610]{color:#29342f;border-left:3px solid var(--green);background:#f2f6f4;margin:0;padding:18px 20px;font-size:17px;line-height:1.8}.answer-form[data-v-937f7610]{max-width:680px}.choice-group[data-v-937f7610]{flex-wrap:wrap;display:flex}.run-actions[data-v-937f7610]{border-top:1px solid var(--line);color:var(--muted);justify-content:space-between;align-items:center;gap:16px;margin:24px -34px -30px;padding:18px 34px;font-size:12px;display:flex}.run-actions>div[data-v-937f7610]{align-items:center;gap:9px;display:flex}.completed-state[data-v-937f7610]{text-align:center;padding:40px 0}.completed-state>span[data-v-937f7610]{color:var(--green);font-size:50px}.completed-state h3[data-v-937f7610]{margin:14px 0 8px;font-family:Noto Serif SC,serif;font-size:24px}.completed-state p[data-v-937f7610]{color:var(--muted);margin:0 0 24px}.quick-feedback[data-v-937f7610]{text-align:left;border-top:1px solid var(--line);border-bottom:1px solid var(--line);background:#f5f8f6;grid-template-columns:180px 1fr auto;align-items:center;gap:12px;max-width:620px;margin:0 auto 18px;padding:16px;display:grid}.quick-feedback>span[data-v-937f7610]{font-size:12px;font-weight:650}.quick-feedback[data-v-937f7610] .ant-rate{font-size:18px}.quick-feedback[data-v-937f7610] .ant-input{grid-column:1/3}.feedback-thanks[data-v-937f7610]{max-width:520px;color:var(--green);background:#edf6f2;margin:0 auto 18px;padding:12px;font-size:12px}.knowledge-snapshot[data-v-937f7610]{border-top:1px solid var(--line);border-bottom:1px solid var(--line);margin:0 0 24px}.knowledge-head[data-v-937f7610]{justify-content:space-between;align-items:center;padding:12px 0;display:flex}.knowledge-head span[data-v-937f7610]{color:var(--muted);font-size:11px}.standard-copy[data-v-937f7610],.knowledge-note[data-v-937f7610]{border-left:3px solid var(--green);background:#f2f7f5;padding:14px 16px}.standard-copy small[data-v-937f7610],.knowledge-note small[data-v-937f7610]{font-size:10px;font-weight:750}.standard-copy p[data-v-937f7610],.knowledge-note p[data-v-937f7610]{margin:6px 0 0;line-height:1.7}.knowledge-note[data-v-937f7610]{background:#fbf3f0;border-left-color:#c4573f;margin-top:8px}.knowledge-note.risk[data-v-937f7610]{background:#fbf6eb;border-left-color:#bd7b24}@media (width<=800px){.sop-catalog[data-v-937f7610],.run-workspace[data-v-937f7610]{grid-template-columns:1fr}.run-context[data-v-937f7610]{position:static}.conversation[data-v-937f7610]{padding:22px}.run-actions[data-v-937f7610]{margin:24px -22px -22px;padding:16px 22px}.run-actions span[data-v-937f7610]{display:none}.quick-feedback[data-v-937f7610]{grid-template-columns:1fr}.quick-feedback[data-v-937f7610] .ant-input{grid-column:auto}}
|
||||
1
web/dist/assets/KnowledgeView-BZbzCYjc.js
vendored
Normal file
1
web/dist/assets/KnowledgeView-BZbzCYjc.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
web/dist/assets/KnowledgeView-D2bZOSSf.js
vendored
1
web/dist/assets/KnowledgeView-D2bZOSSf.js
vendored
File diff suppressed because one or more lines are too long
1
web/dist/assets/ReviewCenterView-Cyi3NnR4.js
vendored
1
web/dist/assets/ReviewCenterView-Cyi3NnR4.js
vendored
@@ -1 +0,0 @@
|
||||
import{A as e,Bt as t,F as n,I as r,O as i,P as a,Q as o,Tt as s,Y as c,_t as l,a as u,j as d,k as f,lt as p,n as m,r as h,t as g,tt as _,vt as v}from"./client-Bk7n1O1W.js";import{a as y}from"./config-provider-CxhqvDI_.js";import{n as b,r as x,t as S}from"./EyeOutlined-C1VmLQCG.js";import{h as C}from"./useApi-CROJJdhE-C8LJOf_8.js";var w={icon:{tag:`svg`,attrs:{viewBox:`64 64 896 896`,focusable:`false`},children:[{tag:`path`,attrs:{d:`M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372 0-89 31.3-170.8 83.5-234.8l523.3 523.3C682.8 852.7 601 884 512 884zm288.5-137.2L277.2 223.5C341.2 171.3 423 140 512 140c205.4 0 372 166.6 372 372 0 89-31.3 170.8-83.5 234.8z`}}]},name:`stop`,theme:`outlined`};function T(e){for(var t=1;t<arguments.length;t++){var n=arguments[t]==null?{}:Object(arguments[t]),r=Object.keys(n);typeof Object.getOwnPropertySymbols==`function`&&(r=r.concat(Object.getOwnPropertySymbols(n).filter(function(e){return Object.getOwnPropertyDescriptor(n,e).enumerable}))),r.forEach(function(t){E(e,t,n[t])})}return e}function E(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}var D=function(e,t){var r=T({},e,t.attrs);return n(u,T({},r,{icon:w}),null)};D.displayName=`StopOutlined`,D.inheritAttrs=!1;var O={class:`page-shell review-page`},k={class:`page-heading`},A={class:`queue-count`},j={class:`surface review-table`},M={key:0,class:`sop-cell`},N={key:2,class:`waiting`},P={key:3,class:`row-actions`},F={class:`review-empty`},I={class:`modal-copy`},L=h(r({__name:`ReviewCenterView`,setup(r){let u=C(),h=v(!1),w=v([]),T=v(null),E=v(!1),L=l({reason:``}),R=[{title:`SOP / 场景`,key:`sop`},{title:`版本`,dataIndex:`version`,width:90},{title:`提交人`,dataIndex:`creator_name`,width:140},{title:`等待时间`,dataIndex:`submitted_at`,width:180},{title:``,key:`action`,width:250}];async function z(){h.value=!0;try{let e=await g.get(`/reviews`);w.value=e.items||[]}catch(e){y.error(m(e))}finally{h.value=!1}}async function B(e){E.value=!0;try{await g.post(`/sops/${e.sop_id}/publish`),y.success(`“${e.sop_name}”已通过并发布`),await z()}catch(e){y.error(m(e))}finally{E.value=!1}}function V(e){T.value=e,L.reason=``}async function H(){if(!T.value||!L.reason.trim())return y.warning(`请填写退回原因`);E.value=!0;try{await g.post(`/sops/${T.value.sop_id}/reject`,{reason:L.reason}),y.success(`已退回编辑者修改`),T.value=null,await z()}catch(e){y.error(m(e))}finally{E.value=!1}}function U(e){let t=Math.max(1,Math.round((Date.now()-new Date(e).getTime())/6e4));if(t<60)return`${t} 分钟`;let n=Math.round(t/60);return n<48?`${n} 小时`:`${Math.round(n/24)} 天`}return c(z),(r,c)=>{let l=_(`a-tag`),m=_(`a-button`),g=_(`a-table`),v=_(`a-textarea`),y=_(`a-modal`);return o(),d(`div`,O,[i(`div`,k,[c[3]||=i(`div`,null,[i(`h1`,null,`审核中心`),i(`p`,null,`检查流程完整性、风险分支和话术口径后再决定发布。`)],-1),i(`div`,A,[n(s(b)),i(`b`,null,t(w.value.length),1),c[2]||=i(`span`,null,`待审核`,-1)])]),i(`section`,j,[c[9]||=i(`div`,{class:`review-guideline`},[i(`span`,null,`审核关注`),i(`b`,null,`字段完整`),i(`i`),i(`b`,null,`条件可达`),i(`i`),i(`b`,null,`高风险有出口`),i(`i`),i(`b`,null,`知识卡已发布`)],-1),n(g,{"row-key":`version_id`,loading:h.value,"data-source":w.value,columns:R,pagination:!1,scroll:{x:850}},{bodyCell:p(({column:r,record:h})=>[r.key===`sop`?(o(),d(`div`,M,[i(`b`,null,t(h.sop_name),1),i(`span`,null,t(h.scenario_name)+` · `+t(h.description||`暂无说明`),1)])):r.dataIndex===`version`?(o(),f(l,{key:1},{default:p(()=>[a(`V`+t(h.version),1)]),_:2},1024)):r.dataIndex===`submitted_at`?(o(),d(`span`,N,t(U(h.submitted_at)),1)):r.key===`action`?(o(),d(`div`,P,[n(m,{onClick:e=>s(u).push(`/sops/${h.sop_id}`)},{default:p(()=>[n(s(S)),c[4]||=a(`查看`,-1)]),_:1},8,[`onClick`]),n(m,{danger:``,onClick:e=>V(h)},{default:p(()=>[n(s(D)),c[5]||=a(`退回`,-1)]),_:1},8,[`onClick`]),n(m,{type:`primary`,loading:E.value,onClick:e=>B(h)},{default:p(()=>[n(s(x)),c[6]||=a(`通过`,-1)]),_:1},8,[`loading`,`onClick`])])):e(``,!0)]),emptyText:p(()=>[i(`div`,F,[n(s(x)),c[7]||=i(`b`,null,`审核队列已清空`,-1),c[8]||=i(`span`,null,`新提交的 SOP 会出现在这里。`,-1)])]),_:1},8,[`loading`,`data-source`])]),n(y,{open:!!T.value,title:`退回修改`,"ok-text":`确认退回`,"confirm-loading":E.value,onOk:H,onCancel:c[1]||=e=>T.value=null},{default:p(()=>[i(`p`,I,`请说明“`+t(T.value?.sop_name)+`”需要调整的具体内容,编辑者可据此修改后重新提交。`,1),n(v,{value:L.reason,"onUpdate:value":c[0]||=e=>L.reason=e,rows:5,maxlength:1e3,"show-count":``,placeholder:`例如:急症判断缺少默认路径,请补充非急症分支。`},null,8,[`value`])]),_:1},8,[`open`,`confirm-loading`])])}}}),[[`__scopeId`,`data-v-88d0e1a8`]]);export{L as default};
|
||||
1
web/dist/assets/ReviewCenterView-DanGoVob.js
vendored
Normal file
1
web/dist/assets/ReviewCenterView-DanGoVob.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import{A as e,Bt as t,F as n,I as r,O as i,P as a,Q as o,Tt as s,Y as c,_t as l,j as u,k as d,lt as f,n as p,r as m,t as h,tt as g,vt as _}from"./client-Bk7n1O1W.js";import{a as v}from"./config-provider-CxhqvDI_.js";import{n as y,r as b,t as x}from"./EyeOutlined-C1VmLQCG.js";import{t as S}from"./StopOutlined-X-yne_Zl.js";import{h as C}from"./useApi-CROJJdhE-C8LJOf_8.js";var w={class:`page-shell review-page`},T={class:`page-heading`},E={class:`queue-count`},D={class:`surface review-table`},O={key:0,class:`sop-cell`},k={key:2,class:`waiting`},A={key:3,class:`row-actions`},j={class:`review-empty`},M={class:`modal-copy`},N=m(r({__name:`ReviewCenterView`,setup(r){let m=C(),N=_(!1),P=_([]),F=_(null),I=_(!1),L=l({reason:``}),R=[{title:`SOP / 场景`,key:`sop`},{title:`版本`,dataIndex:`version`,width:90},{title:`提交人`,dataIndex:`creator_name`,width:140},{title:`等待时间`,dataIndex:`submitted_at`,width:180},{title:``,key:`action`,width:250}];async function z(){N.value=!0;try{let e=await h.get(`/reviews`);P.value=e.items||[]}catch(e){v.error(p(e))}finally{N.value=!1}}async function B(e){I.value=!0;try{await h.post(`/sops/${e.sop_id}/publish`),v.success(`“${e.sop_name}”已通过并发布`),await z()}catch(e){v.error(p(e))}finally{I.value=!1}}function V(e){F.value=e,L.reason=``}async function H(){if(!F.value||!L.reason.trim())return v.warning(`请填写退回原因`);I.value=!0;try{await h.post(`/sops/${F.value.sop_id}/reject`,{reason:L.reason}),v.success(`已退回编辑者修改`),F.value=null,await z()}catch(e){v.error(p(e))}finally{I.value=!1}}function U(e){let t=Math.max(1,Math.round((Date.now()-new Date(e).getTime())/6e4));if(t<60)return`${t} 分钟`;let n=Math.round(t/60);return n<48?`${n} 小时`:`${Math.round(n/24)} 天`}return c(z),(r,c)=>{let l=g(`a-tag`),p=g(`a-button`),h=g(`a-table`),_=g(`a-textarea`),v=g(`a-modal`);return o(),u(`div`,w,[i(`div`,T,[c[3]||=i(`div`,null,[i(`h1`,null,`审核中心`),i(`p`,null,`检查流程完整性、风险分支和话术口径后再决定发布。`)],-1),i(`div`,E,[n(s(y)),i(`b`,null,t(P.value.length),1),c[2]||=i(`span`,null,`待审核`,-1)])]),i(`section`,D,[c[9]||=i(`div`,{class:`review-guideline`},[i(`span`,null,`审核关注`),i(`b`,null,`字段完整`),i(`i`),i(`b`,null,`条件可达`),i(`i`),i(`b`,null,`高风险有出口`),i(`i`),i(`b`,null,`知识卡已发布`)],-1),n(h,{"row-key":`version_id`,loading:N.value,"data-source":P.value,columns:R,pagination:!1,scroll:{x:850}},{bodyCell:f(({column:r,record:h})=>[r.key===`sop`?(o(),u(`div`,O,[i(`b`,null,t(h.sop_name),1),i(`span`,null,t(h.scenario_name)+` · `+t(h.description||`暂无说明`),1)])):r.dataIndex===`version`?(o(),d(l,{key:1},{default:f(()=>[a(`V`+t(h.version),1)]),_:2},1024)):r.dataIndex===`submitted_at`?(o(),u(`span`,k,t(U(h.submitted_at)),1)):r.key===`action`?(o(),u(`div`,A,[n(p,{onClick:e=>s(m).push(`/sops/${h.sop_id}`)},{default:f(()=>[n(s(x)),c[4]||=a(`查看`,-1)]),_:1},8,[`onClick`]),n(p,{danger:``,onClick:e=>V(h)},{default:f(()=>[n(s(S)),c[5]||=a(`退回`,-1)]),_:1},8,[`onClick`]),n(p,{type:`primary`,loading:I.value,onClick:e=>B(h)},{default:f(()=>[n(s(b)),c[6]||=a(`通过`,-1)]),_:1},8,[`loading`,`onClick`])])):e(``,!0)]),emptyText:f(()=>[i(`div`,j,[n(s(b)),c[7]||=i(`b`,null,`审核队列已清空`,-1),c[8]||=i(`span`,null,`新提交的 SOP 会出现在这里。`,-1)])]),_:1},8,[`loading`,`data-source`])]),n(v,{open:!!F.value,title:`退回修改`,"ok-text":`确认退回`,"confirm-loading":I.value,onOk:H,onCancel:c[1]||=e=>F.value=null},{default:f(()=>[i(`p`,M,`请说明“`+t(F.value?.sop_name)+`”需要调整的具体内容,编辑者可据此修改后重新提交。`,1),n(_,{value:L.reason,"onUpdate:value":c[0]||=e=>L.reason=e,rows:5,maxlength:1e3,"show-count":``,placeholder:`例如:急症判断缺少默认路径,请补充非急症分支。`},null,8,[`value`])]),_:1},8,[`open`,`confirm-loading`])])}}}),[[`__scopeId`,`data-v-88d0e1a8`]]);export{N as default};
|
||||
1
web/dist/assets/RunDetailView-BcBBNOh1.js
vendored
1
web/dist/assets/RunDetailView-BcBBNOh1.js
vendored
File diff suppressed because one or more lines are too long
1
web/dist/assets/RunDetailView-CnZ1R0eC.css
vendored
1
web/dist/assets/RunDetailView-CnZ1R0eC.css
vendored
@@ -1 +0,0 @@
|
||||
.run-detail-page[data-v-65fb6a52]{max-width:1320px}.back-link[data-v-65fb6a52]{height:auto;margin:0 0 14px;padding:0}.detail-heading[data-v-65fb6a52]{justify-content:space-between;align-items:flex-end;gap:24px;margin-bottom:20px;display:flex}.run-code[data-v-65fb6a52]{color:var(--green);font-family:monospace;font-size:12px;font-weight:750}.detail-heading h1[data-v-65fb6a52]{margin:7px 0 5px;font-family:Noto Serif SC,serif;font-size:28px}.detail-heading p[data-v-65fb6a52]{color:var(--muted);margin:0}.result-block[data-v-65fb6a52]{border-left:3px solid;align-items:center;gap:12px;min-width:210px;padding:15px 17px;display:flex}.result-block>span[data-v-65fb6a52]:first-child{font-size:25px}.result-block>span[data-v-65fb6a52]:last-child{flex-direction:column;display:flex}.result-block small[data-v-65fb6a52]{font-size:10px}.result-block b[data-v-65fb6a52]{margin-top:3px}.result-block.success[data-v-65fb6a52]{color:#17664f;background:#eaf4f0;border-color:#25866a}.result-block.warning[data-v-65fb6a52]{color:#8d5b16;background:#fbf2e3;border-color:#c08026}.detail-layout[data-v-65fb6a52]{grid-template-columns:minmax(0,1fr) 330px;gap:16px;display:grid}.timeline-panel[data-v-65fb6a52]{min-height:560px;padding:22px 24px}.section-title[data-v-65fb6a52]{justify-content:space-between;align-items:baseline;gap:12px;margin-bottom:22px;display:flex}.section-title span[data-v-65fb6a52]{font-weight:700}.section-title small[data-v-65fb6a52]{color:var(--muted);font-size:11px}.run-timeline[data-v-65fb6a52]{padding-top:5px}.event-head[data-v-65fb6a52]{align-items:center;gap:9px;min-height:27px;display:flex}.event-head time[data-v-65fb6a52]{color:#8b9691;margin-left:auto;font-size:11px}.run-timeline p[data-v-65fb6a52]{color:#56635d;margin:8px 0 0;line-height:1.65}.event-answers[data-v-65fb6a52]{grid-template-columns:repeat(2,minmax(0,1fr));gap:7px;margin-top:10px;display:grid}.event-answers span[data-v-65fb6a52]{background:#f4f7f5;border-left:2px solid #a6cbbd;justify-content:space-between;gap:10px;padding:8px 10px;display:flex}.event-answers small[data-v-65fb6a52]{color:var(--muted)}.event-answers b[data-v-65fb6a52]{font-size:12px}.detail-aside[data-v-65fb6a52]{flex-direction:column;gap:16px;display:flex}.summary-panel[data-v-65fb6a52],.answer-panel[data-v-65fb6a52]{padding:20px}.summary-panel dl[data-v-65fb6a52]{margin:0}.summary-panel dl>div[data-v-65fb6a52]{border-bottom:1px solid #edf0ee;justify-content:space-between;gap:15px;padding:10px 0;display:flex}.summary-panel dl>div[data-v-65fb6a52]:last-child{border-bottom:0}.summary-panel dt[data-v-65fb6a52]{color:var(--muted);font-size:12px}.summary-panel dd[data-v-65fb6a52]{text-align:right;margin:0;font-size:12px}.answer-list>div[data-v-65fb6a52]{border-bottom:1px solid #edf0ee;flex-direction:column;padding:10px 0;display:flex}.answer-list>div[data-v-65fb6a52]:last-child{border-bottom:0}.answer-list span[data-v-65fb6a52]{color:var(--muted);font-size:11px}.answer-list b[data-v-65fb6a52]{margin-top:4px;font-size:13px}.aside-empty[data-v-65fb6a52]{color:#929d98;text-align:center;padding:24px 0;font-size:12px}.feedback-band[data-v-65fb6a52]{margin-top:16px;padding:22px 24px}.feedback-list[data-v-65fb6a52]{grid-template-columns:repeat(2,minmax(0,1fr));gap:16px;display:grid}.feedback-list article[data-v-65fb6a52]{border-top:1px solid var(--line);padding:14px 0}.feedback-list article>div[data-v-65fb6a52]{justify-content:space-between;align-items:center;display:flex}.feedback-list[data-v-65fb6a52] .ant-rate{font-size:15px}.feedback-list p[data-v-65fb6a52]{color:#54615b;margin:9px 0}.feedback-list time[data-v-65fb6a52]{color:#929d98;font-size:11px}.feedback-form[data-v-65fb6a52]{border-top:1px solid var(--line);grid-template-columns:220px minmax(0,1fr) auto;align-items:center;gap:14px;padding-top:16px;display:grid}.feedback-form>div[data-v-65fb6a52]{flex-direction:column;gap:6px;display:flex}.feedback-form>div>span[data-v-65fb6a52]{font-size:12px;font-weight:650}.feedback-form[data-v-65fb6a52] .ant-rate{font-size:20px}@media (width<=900px){.detail-layout[data-v-65fb6a52]{grid-template-columns:1fr}.detail-aside[data-v-65fb6a52]{grid-template-columns:1fr 1fr;display:grid}.feedback-form[data-v-65fb6a52],.feedback-list[data-v-65fb6a52]{grid-template-columns:1fr}}@media (width<=620px){.detail-heading[data-v-65fb6a52]{flex-direction:column;align-items:flex-start}.result-block[data-v-65fb6a52]{width:100%}.detail-aside[data-v-65fb6a52]{grid-template-columns:1fr}.timeline-panel[data-v-65fb6a52]{padding:18px}.event-head[data-v-65fb6a52]{flex-wrap:wrap;align-items:flex-start}.event-head time[data-v-65fb6a52]{width:100%;margin:0}.event-answers[data-v-65fb6a52]{grid-template-columns:1fr}}
|
||||
1
web/dist/assets/RunDetailView-W8qPXltZ.js
vendored
Normal file
1
web/dist/assets/RunDetailView-W8qPXltZ.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
web/dist/assets/RunDetailView-z649jgwe.css
vendored
Normal file
1
web/dist/assets/RunDetailView-z649jgwe.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
web/dist/assets/RunHistoryView-BHDOSNhY.css
vendored
Normal file
1
web/dist/assets/RunHistoryView-BHDOSNhY.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.history-toolbar[data-v-d3f4eec1]{border-bottom:1px solid var(--line);justify-content:space-between;align-items:center;gap:12px;min-height:58px;padding:10px 14px;display:flex}.filter-set[data-v-d3f4eec1]{color:#7a8781;flex-wrap:wrap;align-items:center;gap:9px;display:flex}.filter-set[data-v-d3f4eec1] .ant-select{width:150px}.filter-set[data-v-d3f4eec1] .ant-picker{width:240px}.run-sop[data-v-d3f4eec1]{flex-direction:column;display:flex}.run-sop small[data-v-d3f4eec1]{color:var(--muted);margin-top:4px}.surface[data-v-d3f4eec1] .ant-table-row{cursor:pointer}.surface[data-v-d3f4eec1] .ant-table-row:hover .row-arrow{color:var(--green);transform:translate(2px)}code[data-v-d3f4eec1]{color:#17654f;background:#eef5f2;border-radius:3px;padding:2px 5px}.row-arrow[data-v-d3f4eec1]{color:#9ba6a1;transition:all .16s}@media (width<=700px){.history-toolbar[data-v-d3f4eec1]{align-items:flex-start}.filter-set[data-v-d3f4eec1]{width:100%}.filter-set[data-v-d3f4eec1] .ant-select{width:calc(50% - 18px)}.filter-set[data-v-d3f4eec1] .ant-picker{width:100%}}
|
||||
1
web/dist/assets/RunHistoryView-CZZ4hEox.css
vendored
1
web/dist/assets/RunHistoryView-CZZ4hEox.css
vendored
@@ -1 +0,0 @@
|
||||
.history-toolbar[data-v-681d0563]{border-bottom:1px solid var(--line);justify-content:space-between;align-items:center;gap:12px;min-height:58px;padding:10px 14px;display:flex}.filter-set[data-v-681d0563]{color:#7a8781;align-items:center;gap:9px;display:flex}.filter-set[data-v-681d0563] .ant-select{width:160px}.surface[data-v-681d0563] .ant-table-row{cursor:pointer}.surface[data-v-681d0563] .ant-table-row:hover .row-arrow{color:var(--green);transform:translate(2px)}code[data-v-681d0563]{color:#17654f;background:#eef5f2;border-radius:3px;padding:2px 5px}.row-arrow[data-v-681d0563]{color:#9ba6a1;transition:all .16s}@media (width<=600px){.history-toolbar[data-v-681d0563]{align-items:flex-start}.filter-set[data-v-681d0563]{flex-wrap:wrap}.filter-set[data-v-681d0563] .ant-select{width:138px}}
|
||||
1
web/dist/assets/RunHistoryView-D0sKlaWk.js
vendored
Normal file
1
web/dist/assets/RunHistoryView-D0sKlaWk.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
web/dist/assets/RunHistoryView-vkkaahbp.js
vendored
1
web/dist/assets/RunHistoryView-vkkaahbp.js
vendored
@@ -1 +0,0 @@
|
||||
import{A as e,Bt as t,F as n,I as r,O as i,P as a,Q as o,S as s,Tt as c,Y as l,_t as u,a as d,j as f,k as p,lt as m,n as h,r as g,t as _,tt as v,vt as y}from"./client-Bk7n1O1W.js";import{a as b}from"./config-provider-CxhqvDI_.js";import{n as x,t as S}from"./ReloadOutlined-Cih9hKpY.js";import{h as C}from"./useApi-CROJJdhE-C8LJOf_8.js";var w={icon:{tag:`svg`,attrs:{viewBox:`64 64 896 896`,focusable:`false`},children:[{tag:`path`,attrs:{d:`M880.1 154H143.9c-24.5 0-39.8 26.7-27.5 48L349 597.4V838c0 17.7 14.2 32 31.8 32h262.4c17.6 0 31.8-14.3 31.8-32V597.4L907.7 202c12.2-21.3-3.1-48-27.6-48zM603.4 798H420.6V642h182.9v156zm9.6-236.6l-9.5 16.6h-183l-9.5-16.6L212.7 226h598.6L613 561.4z`}}]},name:`filter`,theme:`outlined`};function T(e){for(var t=1;t<arguments.length;t++){var n=arguments[t]==null?{}:Object(arguments[t]),r=Object.keys(n);typeof Object.getOwnPropertySymbols==`function`&&(r=r.concat(Object.getOwnPropertySymbols(n).filter(function(e){return Object.getOwnPropertyDescriptor(n,e).enumerable}))),r.forEach(function(t){E(e,t,n[t])})}return e}function E(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}var D=function(e,t){var r=T({},e,t.attrs);return n(d,T({},r,{icon:w}),null)};D.displayName=`FilterOutlined`,D.inheritAttrs=!1;var O={class:`page-shell`},k={class:`surface`},A={class:`history-toolbar`},j={class:`filter-set`},M={key:0},N=g(r({__name:`RunHistoryView`,setup(r){let d=C(),g=y(!1),w=y([]),T=y(0),E=y(1),N=y(20),P=u({status:``,result:``}),F=[{title:`执行编号`,dataIndex:`id`,width:130},{title:`SOP`,dataIndex:`sop_name`},{title:`状态`,dataIndex:`status`,width:110},{title:`结果`,dataIndex:`result`,width:130},{title:`开始时间`,dataIndex:`started_at`,width:190},{title:`完成时间`,dataIndex:`completed_at`,width:190},{title:``,key:`action`,width:54}];async function I(e=!1){e&&(E.value=1),g.value=!0;try{let e=await _.get(`/runs`,{params:{page:E.value,page_size:N.value,status:P.status||void 0,result:P.result||void 0}});w.value=e.items||[],T.value=e.total}catch(e){b.error(h(e))}finally{g.value=!1}}function L(e){return e===`completed`?`已完成`:`进行中`}function R(e){return{finish:`正常结束`,escalate:`转人工 / 转诊`,manual:`人工结束`}[e]||e||`-`}function z(e,t){E.value=e,N.value=t,I()}function B(e){return{onClick:()=>d.push(`/runs/${e.id}`)}}function V(e){z(e.current||1,e.pageSize||20)}return l(I),(r,l)=>{let u=v(`a-select`),d=v(`a-button`),h=v(`a-tag`),_=v(`a-table`);return o(),f(`div`,O,[l[6]||=i(`div`,{class:`page-heading`},[i(`div`,null,[i(`h1`,null,`执行记录`),i(`p`,null,`按结果筛选实际执行,进入详情复盘每一步回答和分流。`)])],-1),i(`section`,k,[i(`div`,A,[i(`div`,j,[n(c(D)),n(u,{value:P.status,"onUpdate:value":l[0]||=e=>P.status=e,"allow-clear":``,placeholder:`全部状态`,options:[{value:`running`,label:`进行中`},{value:`completed`,label:`已完成`}],onChange:l[1]||=e=>I(!0)},null,8,[`value`]),n(u,{value:P.result,"onUpdate:value":l[2]||=e=>P.result=e,"allow-clear":``,placeholder:`全部结果`,options:[{value:`finish`,label:`正常结束`},{value:`escalate`,label:`转人工 / 转诊`},{value:`manual`,label:`人工结束`}],onChange:l[3]||=e=>I(!0)},null,8,[`value`])]),n(d,{type:`text`,"aria-label":`刷新执行记录`,onClick:l[4]||=e=>I()},{default:m(()=>[n(c(S))]),_:1})]),n(_,{"row-key":`id`,loading:g.value,"data-source":w.value,columns:F,pagination:{current:E.value,pageSize:N.value,total:T.value,showSizeChanger:!0,showTotal:e=>`共 ${e} 条`},scroll:{x:980},"custom-row":B,onChange:V},{bodyCell:m(({column:n,record:r})=>[n.dataIndex===`id`?(o(),f(`code`,M,`RUN-`+t(String(r.id).padStart(5,`0`)),1)):n.dataIndex===`status`?(o(),p(h,{key:1,color:r.status===`completed`?`green`:`orange`},{default:m(()=>[a(t(L(r.status)),1)]),_:2},1032,[`color`])):n.dataIndex===`result`?(o(),f(s,{key:2},[a(t(R(r.result)),1)],64)):n.dataIndex===`started_at`||n.dataIndex===`completed_at`?(o(),f(s,{key:3},[a(t(r[n.dataIndex]?new Date(r[n.dataIndex]).toLocaleString(`zh-CN`):`-`),1)],64)):n.key===`action`?(o(),p(c(x),{key:4,class:`row-arrow`})):e(``,!0)]),emptyText:m(()=>[...l[5]||=[i(`div`,{class:`empty-copy`},`当前筛选条件下没有执行记录。`,-1)]]),_:1},8,[`loading`,`data-source`,`pagination`])])])}}}),[[`__scopeId`,`data-v-681d0563`]]);export{N as default};
|
||||
1
web/dist/assets/SOPEditorView-C-aX-vfz.css
vendored
1
web/dist/assets/SOPEditorView-C-aX-vfz.css
vendored
File diff suppressed because one or more lines are too long
1
web/dist/assets/SOPEditorView-CnsIl3I7.css
vendored
Normal file
1
web/dist/assets/SOPEditorView-CnsIl3I7.css
vendored
Normal file
File diff suppressed because one or more lines are too long
3
web/dist/assets/SOPEditorView-D6Kadvbx.js
vendored
3
web/dist/assets/SOPEditorView-D6Kadvbx.js
vendored
File diff suppressed because one or more lines are too long
3
web/dist/assets/SOPEditorView-DSYlIMXk.js
vendored
Normal file
3
web/dist/assets/SOPEditorView-DSYlIMXk.js
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
web/dist/assets/StopOutlined-X-yne_Zl.js
vendored
Normal file
1
web/dist/assets/StopOutlined-X-yne_Zl.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import{F as e,a as t}from"./client-Bk7n1O1W.js";var n={icon:{tag:`svg`,attrs:{viewBox:`64 64 896 896`,focusable:`false`},children:[{tag:`path`,attrs:{d:`M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372 0-89 31.3-170.8 83.5-234.8l523.3 523.3C682.8 852.7 601 884 512 884zm288.5-137.2L277.2 223.5C341.2 171.3 423 140 512 140c205.4 0 372 166.6 372 372 0 89-31.3 170.8-83.5 234.8z`}}]},name:`stop`,theme:`outlined`};function r(e){for(var t=1;t<arguments.length;t++){var n=arguments[t]==null?{}:Object(arguments[t]),r=Object.keys(n);typeof Object.getOwnPropertySymbols==`function`&&(r=r.concat(Object.getOwnPropertySymbols(n).filter(function(e){return Object.getOwnPropertyDescriptor(n,e).enumerable}))),r.forEach(function(t){i(e,t,n[t])})}return e}function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}var a=function(i,a){var o=r({},i,a.attrs);return e(t,r({},o,{icon:n}),null)};a.displayName=`StopOutlined`,a.inheritAttrs=!1;export{a as t};
|
||||
File diff suppressed because one or more lines are too long
37
web/dist/assets/modal-C5Vo2Ug5.js
vendored
Normal file
37
web/dist/assets/modal-C5Vo2Ug5.js
vendored
Normal file
File diff suppressed because one or more lines are too long
5
web/dist/index.html
vendored
5
web/dist/index.html
vendored
@@ -5,17 +5,18 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="theme-color" content="#202825" />
|
||||
<title>销冠 SOP 平台</title>
|
||||
<script type="module" crossorigin src="/assets/index-BeO8QXu8.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-DpE4cxr4.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/assets/client-Bk7n1O1W.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/config-provider-CxhqvDI_.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/auth-D6Tj4g1X.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/DeleteOutlined-CRNedxr9.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/modal-C5Vo2Ug5.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/EyeOutlined-C1VmLQCG.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/SearchOutlined-D6c8vQZ_.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/ReloadOutlined-Cih9hKpY.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/PlusOutlined-CosM7zFe.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/ArrowLeftOutlined-B1KoeknA.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/AppstoreOutlined-BD2QJkU0.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/DeleteOutlined-Bt9-1HZZ.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/EditOutlined-BIUW-zYI.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/BookOutlined-Canuw-q-.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/HistoryOutlined-ABEqcIfz.js">
|
||||
|
||||
@@ -4,13 +4,14 @@ export interface Scenario extends BaseEntity { tenant_id: number; name: string;
|
||||
export interface ScenarioField extends BaseEntity { scenario_id: number; field_key: string; field_name: string; field_type: string; required: boolean; options: unknown[]; validation: Record<string, unknown>; sort_order: number }
|
||||
export interface SOP extends BaseEntity { scenario_id: number; name: string; description: string; status: string }
|
||||
export interface SOPVersion extends BaseEntity { sop_id: number; version: number; status: string; start_node_key: string; published_at?: string; created_by?:number; reviewed_by?:number; creator_name?:string; reviewer_name?:string }
|
||||
export interface SOPNode extends BaseEntity { node_key: string; type: string; title: string; content: string; config: Record<string, any>; position_x: number; position_y: number }
|
||||
export interface KnowledgeSnapshot { card_id:number; card_version_id:number; version:number; title:string; standard_copy:string; forbidden_copy:string; risk_note:string }
|
||||
export interface SOPNode extends BaseEntity { node_key: string; type: string; title: string; content: string; config: Record<string, any>; position_x: number; position_y: number; knowledge?:KnowledgeSnapshot }
|
||||
export interface SOPEdge extends BaseEntity { source_node_key: string; target_node_key: string; condition: Record<string, any>; priority: number }
|
||||
export interface SOPRun extends BaseEntity { sop_id: number; sop_version_id: number; current_node_key: string; status: string; answers: Record<string, any>; result: string; started_at: string; completed_at?: string; sop_name?: string }
|
||||
export interface SOPRun extends BaseEntity { sop_id: number; sop_version_id: number; operator_id:number; current_node_key: string; status: string; answers: Record<string, any>; result: string; started_at: string; completed_at?: string; sop_name?: string; scenario_name?:string; version?:number; operator_name?:string }
|
||||
export interface KnowledgeCard extends BaseEntity { scenario_id: number; title: string; status: string; version?: number; content?: Record<string, any> }
|
||||
export interface KnowledgeCardVersion extends BaseEntity { knowledge_card_id:number; version:number; status:string; content:Record<string,any> }
|
||||
export interface ReviewItem { sop_id:number; sop_name:string; description:string; scenario_id:number; scenario_name:string; version_id:number; version:number; creator_id:number; creator_name:string; submitted_at:string }
|
||||
export interface RunEvent extends BaseEntity { run_id:number; node_key:string; action:string; payload:Record<string,any>; node_title:string; node_type:string; node_content:string }
|
||||
export interface RunEvent extends BaseEntity { run_id:number; node_key:string; action:string; payload:Record<string,any>; node_title:string; node_type:string; node_content:string; knowledge?:KnowledgeSnapshot }
|
||||
export interface RunFeedback extends BaseEntity { run_id:number; user_id:number; score:number; comment:string; user_name:string }
|
||||
export interface RunDetail extends SOPRun { scenario_name:string; version:number; operator_name:string }
|
||||
export interface Role extends BaseEntity { name:string; code:string; permissions:string[] }
|
||||
|
||||
@@ -1,22 +1,60 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { CheckCircleOutlined, PlayCircleOutlined, SafetyCertificateOutlined } from '@ant-design/icons-vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { computed, nextTick, onMounted, reactive, ref } from 'vue'
|
||||
import { CheckCircleOutlined, PlayCircleOutlined, SafetyCertificateOutlined, StopOutlined } from '@ant-design/icons-vue'
|
||||
import { message, Modal } from 'ant-design-vue'
|
||||
import type { FormInstance } from 'ant-design-vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { api, apiMessage } from '@/api/client'
|
||||
import type { ScenarioField, SOPNode, SOPRun } from '@/types'
|
||||
|
||||
interface PublishedSOP { id:number; name:string; description:string; scenario_id:number; scenario_name:string; version:number }
|
||||
interface RunView { run:SOPRun; node:SOPNode; fields:ScenarioField[] }
|
||||
const route=useRoute();const router=useRouter()
|
||||
const loading=ref(true);const sops=ref<PublishedSOP[]>([]);const active=ref<RunView|null>(null);const answers=reactive<Record<string,any>>({});const submitting=ref(false)
|
||||
const answerForm=ref<FormInstance>()
|
||||
const feedback=reactive({score:5,comment:''});const feedbackSent=ref(false)
|
||||
const currentConfig=computed(()=>active.value?.node.config||{})
|
||||
const currentFields=computed(()=>{if(!active.value)return[];const node=active.value.node;if(node.type==='question'||node.type==='choice')return active.value.fields.filter(f=>f.field_key===currentConfig.value.field_key);if(node.type==='form')return active.value.fields.filter(f=>(currentConfig.value.field_keys||[]).includes(f.field_key));return[]})
|
||||
async function load(){loading.value=true;try{sops.value=(await api.get<{items:PublishedSOP[]}>('/published-sops')).items}catch(error){message.error(apiMessage(error))}finally{loading.value=false}}
|
||||
async function start(sopID:number){try{active.value=await api.post<RunView>('/runs',{sop_id:sopID});Object.keys(answers).forEach(k=>delete answers[k]);Object.assign(feedback,{score:5,comment:''});feedbackSent.value=false}catch(error){message.error(apiMessage(error))}}
|
||||
async function next(){if(!active.value)return;submitting.value=true;try{active.value=await api.post<RunView>(`/runs/${active.value.run.id}/answer`,{node_key:active.value.node.node_key,answers:{...answers}});Object.keys(answers).forEach(k=>delete answers[k])}catch(error){message.error(apiMessage(error))}finally{submitting.value=false}}
|
||||
async function load(){loading.value=true;try{sops.value=(await api.get<{items:PublishedSOP[]}>('/published-sops')).items;const runID=Number(route.query.run||0);if(runID>0)active.value=await api.get<RunView>(`/runs/${runID}`)}catch(error){message.error(apiMessage(error));await router.replace({query:{}})}finally{loading.value=false}}
|
||||
async function clearAnswers(){Object.keys(answers).forEach(k=>delete answers[k]);await nextTick();answerForm.value?.clearValidate()}
|
||||
async function start(sopID:number){try{active.value=await api.post<RunView>('/runs',{sop_id:sopID});await router.replace({query:{run:String(active.value.run.id)}});await clearAnswers();Object.assign(feedback,{score:5,comment:''});feedbackSent.value=false}catch(error){message.error(apiMessage(error))}}
|
||||
async function next(){if(!active.value)return;try{await answerForm.value?.validate()}catch{return}submitting.value=true;try{active.value=await api.post<RunView>(`/runs/${active.value.run.id}/answer`,{node_key:active.value.node.node_key,answers:{...answers}});await clearAnswers()}catch(error){message.error(apiMessage(error))}finally{submitting.value=false}}
|
||||
function finishManually(){
|
||||
if(!active.value)return
|
||||
Modal.confirm({
|
||||
title:'结束本次执行?',
|
||||
content:'当前节点尚未提交的内容不会保存,执行记录将标记为“人工结束”。',
|
||||
okText:'确认结束',cancelText:'继续执行',okType:'danger',
|
||||
async onOk(){
|
||||
if(!active.value)return
|
||||
submitting.value=true
|
||||
try{active.value=await api.post<RunView>(`/runs/${active.value.run.id}/finish`,{result:'manual'});Object.keys(answers).forEach(k=>delete answers[k]);message.success('本次执行已结束')}
|
||||
catch(error){message.error(apiMessage(error));throw error}
|
||||
finally{submitting.value=false}
|
||||
},
|
||||
})
|
||||
}
|
||||
function inputFor(field:ScenarioField){return field.field_type}
|
||||
function validationNumber(field:ScenarioField,key:string){const value=field.validation?.[key];return typeof value==='number'?value:undefined}
|
||||
function fieldRequired(field:ScenarioField){return field.required||(['question','choice'].includes(active.value?.node.type||'')&¤tConfig.value.required===true)}
|
||||
function fieldRules(field:ScenarioField){
|
||||
const rules:any[]=[]
|
||||
if(fieldRequired(field))rules.push({required:true,message:`请填写${field.field_name}`,trigger:['change','blur']})
|
||||
if(['text','textarea'].includes(field.field_type)){
|
||||
const min=validationNumber(field,'min_length');const max=validationNumber(field,'max_length')
|
||||
if(min!==undefined)rules.push({min,message:`${field.field_name}不能少于 ${min} 个字符`,trigger:'blur'})
|
||||
if(max!==undefined)rules.push({max,message:`${field.field_name}不能超过 ${max} 个字符`,trigger:'blur'})
|
||||
}
|
||||
if(field.field_type==='number'){
|
||||
const min=validationNumber(field,'min');const max=validationNumber(field,'max')
|
||||
rules.push({type:'number',message:`${field.field_name}必须是数字`,trigger:'change'})
|
||||
if(min!==undefined)rules.push({type:'number',min,message:`${field.field_name}不能小于 ${min}`,trigger:'change'})
|
||||
if(max!==undefined)rules.push({type:'number',max,message:`${field.field_name}不能大于 ${max}`,trigger:'change'})
|
||||
}
|
||||
return rules
|
||||
}
|
||||
async function sendFeedback(){if(!active.value)return;submitting.value=true;try{await api.post(`/runs/${active.value.run.id}/feedback`,feedback);feedbackSent.value=true;message.success('反馈已提交')}catch(error){message.error(apiMessage(error))}finally{submitting.value=false}}
|
||||
function reset(){active.value=null;load()}
|
||||
async function reset(){active.value=null;await router.replace({query:{}});await load()}
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
@@ -31,29 +69,35 @@ onMounted(load)
|
||||
<div v-else class="run-workspace">
|
||||
<aside class="run-context">
|
||||
<span class="run-label">RUN-{{ String(active.run.id).padStart(5,'0') }}</span>
|
||||
<h2>执行进行中</h2><p>客户回答会自动保存在当前执行记录中。</p>
|
||||
<h2>{{ active.run.status==='completed'?'执行已结束':'执行进行中' }}</h2><p>客户回答会自动保存在当前执行记录中。</p>
|
||||
<div class="context-meta"><span>当前节点</span><b>{{ active.node.title }}</b></div><div class="context-meta"><span>已采集字段</span><b>{{ Object.keys(active.run.answers || {}).length }}</b></div>
|
||||
<div class="privacy-note"><SafetyCertificateOutlined /><span>敏感信息请遵循企业数据规范</span></div>
|
||||
</aside>
|
||||
<main class="conversation surface">
|
||||
<div class="conversation-progress"><span :class="{done:active.run.status==='completed'}"></span><b>{{ active.run.status==='completed'?'流程已完成':'正在执行' }}</b></div>
|
||||
<div class="node-content"><small>{{ active.node.type.toUpperCase() }}</small><h1>{{ active.node.title }}</h1><blockquote v-if="active.node.content">{{ active.node.content }}</blockquote></div>
|
||||
<div v-if="active.run.status==='completed'" class="completed-state"><CheckCircleOutlined /><h3>{{ active.node.type==='escalate'?'已转交处理':'本次执行已完成' }}</h3><p>{{ active.node.content }}</p><div v-if="!feedbackSent" class="quick-feedback"><span>这套 SOP 是否清晰好用?</span><a-rate v-model:value="feedback.score"/><a-textarea v-model:value="feedback.comment" :rows="2" :maxlength="2000" placeholder="可填写需要改进的步骤"/><a-button type="primary" :loading="submitting" @click="sendFeedback">提交反馈</a-button></div><div v-else class="feedback-thanks">反馈已记录,将用于后续优化。</div><a-button @click="reset">执行另一套 SOP</a-button></div>
|
||||
<section v-if="active.node.knowledge" class="knowledge-snapshot">
|
||||
<div class="knowledge-head"><b>{{ active.node.knowledge.title }}</b><span>知识卡 V{{ active.node.knowledge.version }}</span></div>
|
||||
<div class="standard-copy"><small>标准话术</small><p>{{ active.node.knowledge.standard_copy }}</p></div>
|
||||
<div v-if="active.node.knowledge.forbidden_copy" class="knowledge-note forbidden"><small>禁用表达</small><p>{{ active.node.knowledge.forbidden_copy }}</p></div>
|
||||
<div v-if="active.node.knowledge.risk_note" class="knowledge-note risk"><small>风险提醒</small><p>{{ active.node.knowledge.risk_note }}</p></div>
|
||||
</section>
|
||||
<div v-if="active.run.status==='completed'" class="completed-state"><CheckCircleOutlined /><h3>{{ active.run.result==='manual'?'已人工结束':active.node.type==='escalate'?'已转交处理':'本次执行已完成' }}</h3><p>{{ active.run.result==='manual'?'本次执行由操作人员在当前节点结束。':active.node.content }}</p><div v-if="!feedbackSent" class="quick-feedback"><span>这套 SOP 是否清晰好用?</span><a-rate v-model:value="feedback.score"/><a-textarea v-model:value="feedback.comment" :rows="2" :maxlength="2000" placeholder="可填写需要改进的步骤"/><a-button type="primary" :loading="submitting" @click="sendFeedback">提交反馈</a-button></div><div v-else class="feedback-thanks">反馈已记录,将用于后续优化。</div><a-button @click="reset">执行另一套 SOP</a-button></div>
|
||||
<template v-else>
|
||||
<a-form layout="vertical" class="answer-form">
|
||||
<a-form-item v-for="field in currentFields" :key="field.id" :label="field.field_name" :required="field.required">
|
||||
<a-input v-if="inputFor(field)==='text'" v-model:value="answers[field.field_key]" />
|
||||
<a-textarea v-else-if="inputFor(field)==='textarea'" v-model:value="answers[field.field_key]" :rows="3" />
|
||||
<a-input-number v-else-if="inputFor(field)==='number'" v-model:value="answers[field.field_key]" style="width:100%" />
|
||||
<a-form ref="answerForm" :model="answers" layout="vertical" class="answer-form">
|
||||
<a-form-item v-for="field in currentFields" :key="field.id" :name="field.field_key" :label="field.field_name" :required="fieldRequired(field)" :rules="fieldRules(field)">
|
||||
<a-radio-group v-if="active.node.type==='choice'" v-model:value="answers[field.field_key]" class="choice-group"><a-radio-button v-for="option in currentConfig.options||[]" :key="option" :value="option">{{ option }}</a-radio-button></a-radio-group>
|
||||
<a-input v-else-if="inputFor(field)==='text'" v-model:value="answers[field.field_key]" :maxlength="validationNumber(field,'max_length')" show-count />
|
||||
<a-textarea v-else-if="inputFor(field)==='textarea'" v-model:value="answers[field.field_key]" :rows="3" :maxlength="validationNumber(field,'max_length')" show-count />
|
||||
<a-input-number v-else-if="inputFor(field)==='number'" v-model:value="answers[field.field_key]" :min="validationNumber(field,'min')" :max="validationNumber(field,'max')" style="width:100%" />
|
||||
<a-radio-group v-else-if="inputFor(field)==='boolean'" v-model:value="answers[field.field_key]"><a-radio :value="true">是</a-radio><a-radio :value="false">否</a-radio></a-radio-group>
|
||||
<a-select v-else-if="inputFor(field)==='select'" v-model:value="answers[field.field_key]" :options="(field.options||[]).map(value=>({value,label:value}))" />
|
||||
<a-select v-else-if="inputFor(field)==='multiselect'" v-model:value="answers[field.field_key]" mode="multiple" :options="(field.options||[]).map(value=>({value,label:value}))" />
|
||||
<a-date-picker v-else-if="inputFor(field)==='date'" v-model:value="answers[field.field_key]" style="width:100%" />
|
||||
<a-date-picker v-else-if="inputFor(field)==='date'" v-model:value="answers[field.field_key]" value-format="YYYY-MM-DD" style="width:100%" />
|
||||
<a-input v-else v-model:value="answers[field.field_key]" />
|
||||
</a-form-item>
|
||||
<a-form-item v-if="active.node.type==='choice'&¤tFields.length===0"><a-radio-group v-model:value="answers[currentConfig.field_key]" class="choice-group"><a-radio-button v-for="option in currentConfig.options||[]" :key="option" :value="option">{{ option }}</a-radio-button></a-radio-group></a-form-item>
|
||||
</a-form>
|
||||
<div class="run-actions"><span>{{ currentFields.length ? '填写后继续下一步' : '确认当前话术已完成' }}</span><a-button type="primary" size="large" :loading="submitting" @click="next">继续下一步</a-button></div>
|
||||
<div class="run-actions"><span>{{ currentFields.length ? '填写后继续下一步' : '确认当前话术已完成' }}</span><div><a-button danger :disabled="submitting" @click="finishManually"><StopOutlined />人工结束</a-button><a-button type="primary" size="large" :loading="submitting" @click="next">继续下一步</a-button></div></div>
|
||||
</template>
|
||||
</main>
|
||||
</div>
|
||||
@@ -62,6 +106,7 @@ onMounted(load)
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.execution-shell{max-width:1220px}.sop-catalog{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px}.sop-entry{min-height:150px;display:grid;grid-template-columns:40px 1fr 42px;align-items:start;gap:14px;padding:22px;text-align:left;cursor:pointer}.sop-entry:hover{border-color:#9ac8b8;box-shadow:0 8px 24px rgba(32,40,37,.07)}.entry-seq{color:#8c9893;font-family:monospace;font-size:12px}.sop-entry small{color:var(--green);font-weight:650}.sop-entry h2{margin:8px 0 7px;font-family:"Noto Serif SC",serif;font-size:20px}.sop-entry p{margin:0;color:var(--muted);line-height:1.6}.play{width:38px;height:38px;display:grid;place-items:center;color:white;background:#202825;border-radius:4px;font-size:18px}.run-workspace{display:grid;grid-template-columns:270px minmax(0,1fr);gap:18px}.run-context{align-self:start;padding:24px;color:white;background:#202825;border-radius:6px;position:sticky;top:86px}.run-label{color:#72c9ab;font-size:11px;font-weight:700}.run-context h2{margin:12px 0 8px;font-family:"Noto Serif SC",serif}.run-context>p{margin:0 0 28px;color:#aebdb7;line-height:1.6}.context-meta{display:flex;align-items:center;justify-content:space-between;padding:13px 0;border-top:1px solid #3a4742}.context-meta span{color:#9eada7;font-size:12px}.privacy-note{display:flex;gap:8px;margin-top:28px;color:#889b93;font-size:11px}.conversation{min-height:590px;padding:30px 34px}.conversation-progress{display:flex;align-items:center;gap:8px;color:#66736d;font-size:12px}.conversation-progress span{width:8px;height:8px;border-radius:50%;background:#d08736;box-shadow:0 0 0 4px #faefe1}.conversation-progress span.done{background:var(--green);box-shadow:0 0 0 4px #e4f1ec}.node-content{padding:44px 0 26px}.node-content small{color:var(--green);font-size:10px;font-weight:800}.node-content h1{margin:8px 0 22px;font-family:"Noto Serif SC",serif;font-size:28px}.node-content blockquote{margin:0;padding:18px 20px;color:#29342f;background:#f2f6f4;border-left:3px solid var(--green);font-size:17px;line-height:1.8}.answer-form{max-width:680px}.choice-group{display:flex;flex-wrap:wrap}.run-actions{display:flex;align-items:center;justify-content:space-between;gap:16px;margin:24px -34px -30px;padding:18px 34px;border-top:1px solid var(--line);color:var(--muted);font-size:12px}.completed-state{padding:40px 0;text-align:center}.completed-state>span{color:var(--green);font-size:50px}.completed-state h3{margin:14px 0 8px;font-family:"Noto Serif SC",serif;font-size:24px}.completed-state p{margin:0 0 24px;color:var(--muted)}.quick-feedback{max-width:620px;display:grid;grid-template-columns:180px 1fr auto;align-items:center;gap:12px;margin:0 auto 18px;padding:16px;text-align:left;background:#f5f8f6;border-top:1px solid var(--line);border-bottom:1px solid var(--line)}.quick-feedback>span{font-size:12px;font-weight:650}.quick-feedback :deep(.ant-rate){font-size:18px}.quick-feedback :deep(.ant-input){grid-column:1/3}.feedback-thanks{max-width:520px;margin:0 auto 18px;padding:12px;color:var(--green);background:#edf6f2;font-size:12px}
|
||||
.execution-shell{max-width:1220px}.sop-catalog{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px}.sop-entry{min-height:150px;display:grid;grid-template-columns:40px 1fr 42px;align-items:start;gap:14px;padding:22px;text-align:left;cursor:pointer}.sop-entry:hover{border-color:#9ac8b8;box-shadow:0 8px 24px rgba(32,40,37,.07)}.entry-seq{color:#8c9893;font-family:monospace;font-size:12px}.sop-entry small{color:var(--green);font-weight:650}.sop-entry h2{margin:8px 0 7px;font-family:"Noto Serif SC",serif;font-size:20px}.sop-entry p{margin:0;color:var(--muted);line-height:1.6}.play{width:38px;height:38px;display:grid;place-items:center;color:white;background:#202825;border-radius:4px;font-size:18px}.run-workspace{display:grid;grid-template-columns:270px minmax(0,1fr);gap:18px}.run-context{align-self:start;padding:24px;color:white;background:#202825;border-radius:6px;position:sticky;top:86px}.run-label{color:#72c9ab;font-size:11px;font-weight:700}.run-context h2{margin:12px 0 8px;font-family:"Noto Serif SC",serif}.run-context>p{margin:0 0 28px;color:#aebdb7;line-height:1.6}.context-meta{display:flex;align-items:center;justify-content:space-between;padding:13px 0;border-top:1px solid #3a4742}.context-meta span{color:#9eada7;font-size:12px}.privacy-note{display:flex;gap:8px;margin-top:28px;color:#889b93;font-size:11px}.conversation{min-height:590px;padding:30px 34px}.conversation-progress{display:flex;align-items:center;gap:8px;color:#66736d;font-size:12px}.conversation-progress span{width:8px;height:8px;border-radius:50%;background:#d08736;box-shadow:0 0 0 4px #faefe1}.conversation-progress span.done{background:var(--green);box-shadow:0 0 0 4px #e4f1ec}.node-content{padding:44px 0 26px}.node-content small{color:var(--green);font-size:10px;font-weight:800}.node-content h1{margin:8px 0 22px;font-family:"Noto Serif SC",serif;font-size:28px}.node-content blockquote{margin:0;padding:18px 20px;color:#29342f;background:#f2f6f4;border-left:3px solid var(--green);font-size:17px;line-height:1.8}.answer-form{max-width:680px}.choice-group{display:flex;flex-wrap:wrap}.run-actions{display:flex;align-items:center;justify-content:space-between;gap:16px;margin:24px -34px -30px;padding:18px 34px;border-top:1px solid var(--line);color:var(--muted);font-size:12px}.run-actions>div{display:flex;align-items:center;gap:9px}.completed-state{padding:40px 0;text-align:center}.completed-state>span{color:var(--green);font-size:50px}.completed-state h3{margin:14px 0 8px;font-family:"Noto Serif SC",serif;font-size:24px}.completed-state p{margin:0 0 24px;color:var(--muted)}.quick-feedback{max-width:620px;display:grid;grid-template-columns:180px 1fr auto;align-items:center;gap:12px;margin:0 auto 18px;padding:16px;text-align:left;background:#f5f8f6;border-top:1px solid var(--line);border-bottom:1px solid var(--line)}.quick-feedback>span{font-size:12px;font-weight:650}.quick-feedback :deep(.ant-rate){font-size:18px}.quick-feedback :deep(.ant-input){grid-column:1/3}.feedback-thanks{max-width:520px;margin:0 auto 18px;padding:12px;color:var(--green);background:#edf6f2;font-size:12px}
|
||||
.knowledge-snapshot{margin:0 0 24px;border-top:1px solid var(--line);border-bottom:1px solid var(--line)}.knowledge-head{display:flex;align-items:center;justify-content:space-between;padding:12px 0}.knowledge-head span{color:var(--muted);font-size:11px}.standard-copy,.knowledge-note{padding:14px 16px;border-left:3px solid var(--green);background:#f2f7f5}.standard-copy small,.knowledge-note small{font-size:10px;font-weight:750}.standard-copy p,.knowledge-note p{margin:6px 0 0;line-height:1.7}.knowledge-note{margin-top:8px;border-left-color:#c4573f;background:#fbf3f0}.knowledge-note.risk{border-left-color:#bd7b24;background:#fbf6eb}
|
||||
@media(max-width:800px){.sop-catalog{grid-template-columns:1fr}.run-workspace{grid-template-columns:1fr}.run-context{position:static}.conversation{padding:22px}.run-actions{margin:24px -22px -22px;padding:16px 22px}.run-actions span{display:none}.quick-feedback{grid-template-columns:1fr}.quick-feedback :deep(.ant-input){grid-column:auto}}
|
||||
</style>
|
||||
|
||||
@@ -13,7 +13,7 @@ const fieldMap=computed(()=>new Map((detail.value?.fields||[]).map(field=>[field
|
||||
const answers=computed(()=>Object.entries(detail.value?.run.answers||{}))
|
||||
const canFeedback=computed(()=>auth.can('runs.feedback')&&detail.value?.run.status==='completed'&&!detail.value.feedback.some(item=>item.user_id===auth.user?.user_id))
|
||||
function resultText(v:string){return{finish:'正常结束',escalate:'转人工 / 转诊',manual:'人工结束'}[v]||v||'进行中'}
|
||||
function actionText(v:string){return{start:'开始执行',enter:'进入节点',answer:'提交回答'}[v]||v}
|
||||
function actionText(v:string){return{start:'开始执行',enter:'进入节点',answer:'提交回答',finish:'人工结束'}[v]||v}
|
||||
function valueText(value:any){if(value===true)return'是';if(value===false)return'否';if(Array.isArray(value))return value.join('、');if(value===null||value===undefined||value==='')return'-';return String(value)}
|
||||
function answerEntries(event:RunEvent){const values=event.payload?.answers||{};return Object.entries(values)}
|
||||
async function load(){loading.value=true;try{detail.value=await api.get<DetailResponse>(`/runs/${route.params.id}/detail`)}catch(error){message.error(apiMessage(error));router.replace('/runs')}finally{loading.value=false}}
|
||||
@@ -35,9 +35,10 @@ onMounted(load)
|
||||
<main class="surface timeline-panel">
|
||||
<div class="section-title"><span>执行轨迹</span><small>按实际发生顺序记录</small></div>
|
||||
<a-timeline class="run-timeline">
|
||||
<a-timeline-item v-for="event in detail.events" :key="event.id" :color="event.action==='answer'?'blue':event.node_type==='escalate'?'red':'green'">
|
||||
<a-timeline-item v-for="event in detail.events" :key="event.id" :color="event.action==='finish'||event.node_type==='escalate'?'red':event.action==='answer'?'blue':'green'">
|
||||
<div class="event-head"><b>{{ event.node_title||event.node_key }}</b><a-tag>{{ actionText(event.action) }}</a-tag><time>{{ new Date(event.created_at).toLocaleString('zh-CN') }}</time></div>
|
||||
<p v-if="event.action==='enter'&&event.node_content">{{ event.node_content }}</p>
|
||||
<div v-if="event.action==='enter'&&event.knowledge" class="event-knowledge"><div><b>{{ event.knowledge.title }}</b><small>V{{ event.knowledge.version }}</small></div><p>{{ event.knowledge.standard_copy }}</p><p v-if="event.knowledge.risk_note" class="risk-note">{{ event.knowledge.risk_note }}</p></div>
|
||||
<div v-if="answerEntries(event).length" class="event-answers"><span v-for="[key,value] in answerEntries(event)" :key="key"><small>{{ fieldMap.get(key)||key }}</small><b>{{ valueText(value) }}</b></span></div>
|
||||
</a-timeline-item>
|
||||
</a-timeline>
|
||||
@@ -62,4 +63,5 @@ onMounted(load)
|
||||
|
||||
<style scoped>
|
||||
.run-detail-page{max-width:1320px}.back-link{height:auto;margin:0 0 14px;padding:0}.detail-heading{display:flex;align-items:flex-end;justify-content:space-between;gap:24px;margin-bottom:20px}.run-code{color:var(--green);font-family:monospace;font-size:12px;font-weight:750}.detail-heading h1{margin:7px 0 5px;font-family:"Noto Serif SC",serif;font-size:28px}.detail-heading p{margin:0;color:var(--muted)}.result-block{min-width:210px;display:flex;align-items:center;gap:12px;padding:15px 17px;border-left:3px solid}.result-block>span:first-child{font-size:25px}.result-block>span:last-child{display:flex;flex-direction:column}.result-block small{font-size:10px}.result-block b{margin-top:3px}.result-block.success{color:#17664f;background:#eaf4f0;border-color:#25866a}.result-block.warning{color:#8d5b16;background:#fbf2e3;border-color:#c08026}.detail-layout{display:grid;grid-template-columns:minmax(0,1fr) 330px;gap:16px}.timeline-panel{min-height:560px;padding:22px 24px}.section-title{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin-bottom:22px}.section-title span{font-weight:700}.section-title small{color:var(--muted);font-size:11px}.run-timeline{padding-top:5px}.event-head{display:flex;align-items:center;gap:9px;min-height:27px}.event-head time{margin-left:auto;color:#8b9691;font-size:11px}.run-timeline p{margin:8px 0 0;color:#56635d;line-height:1.65}.event-answers{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:7px;margin-top:10px}.event-answers span{display:flex;justify-content:space-between;gap:10px;padding:8px 10px;background:#f4f7f5;border-left:2px solid #a6cbbd}.event-answers small{color:var(--muted)}.event-answers b{font-size:12px}.detail-aside{display:flex;flex-direction:column;gap:16px}.summary-panel,.answer-panel{padding:20px}.summary-panel dl{margin:0}.summary-panel dl>div{display:flex;justify-content:space-between;gap:15px;padding:10px 0;border-bottom:1px solid #edf0ee}.summary-panel dl>div:last-child{border-bottom:0}.summary-panel dt{color:var(--muted);font-size:12px}.summary-panel dd{margin:0;text-align:right;font-size:12px}.answer-list>div{display:flex;flex-direction:column;padding:10px 0;border-bottom:1px solid #edf0ee}.answer-list>div:last-child{border-bottom:0}.answer-list span{color:var(--muted);font-size:11px}.answer-list b{margin-top:4px;font-size:13px}.aside-empty{padding:24px 0;color:#929d98;text-align:center;font-size:12px}.feedback-band{margin-top:16px;padding:22px 24px}.feedback-list{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px}.feedback-list article{padding:14px 0;border-top:1px solid var(--line)}.feedback-list article>div{display:flex;align-items:center;justify-content:space-between}.feedback-list :deep(.ant-rate){font-size:15px}.feedback-list p{margin:9px 0;color:#54615b}.feedback-list time{color:#929d98;font-size:11px}.feedback-form{display:grid;grid-template-columns:220px minmax(0,1fr) auto;align-items:center;gap:14px;padding-top:16px;border-top:1px solid var(--line)}.feedback-form>div{display:flex;flex-direction:column;gap:6px}.feedback-form>div>span{font-size:12px;font-weight:650}.feedback-form :deep(.ant-rate){font-size:20px}@media(max-width:900px){.detail-layout{grid-template-columns:1fr}.detail-aside{display:grid;grid-template-columns:1fr 1fr}.feedback-form{grid-template-columns:1fr}.feedback-list{grid-template-columns:1fr}}@media(max-width:620px){.detail-heading{align-items:flex-start;flex-direction:column}.result-block{width:100%}.detail-aside{grid-template-columns:1fr}.timeline-panel{padding:18px}.event-head{align-items:flex-start;flex-wrap:wrap}.event-head time{width:100%;margin:0}.event-answers{grid-template-columns:1fr}}
|
||||
.event-knowledge{margin-top:10px;padding:12px 14px;background:#f2f7f5;border-left:3px solid var(--green)}.event-knowledge>div{display:flex;justify-content:space-between;gap:12px}.event-knowledge small{color:var(--muted)}.event-knowledge .risk-note{padding-top:8px;border-top:1px solid #dce9e4;color:#8d5b16}
|
||||
</style>
|
||||
|
||||
@@ -1,22 +1,32 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { FilterOutlined, ReloadOutlined, RightOutlined } from '@ant-design/icons-vue'
|
||||
import { FilterOutlined, PlayCircleOutlined, ReloadOutlined, RightOutlined } from '@ant-design/icons-vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { api, apiMessage } from '@/api/client'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import type { SOPRun } from '@/types'
|
||||
|
||||
const router=useRouter()
|
||||
const auth=useAuthStore()
|
||||
const loading=ref(false);const items=ref<SOPRun[]>([]);const total=ref(0);const page=ref(1);const pageSize=ref(20)
|
||||
const filters=reactive({status:'',result:''})
|
||||
const columns=[{title:'执行编号',dataIndex:'id',width:130},{title:'SOP',dataIndex:'sop_name'},{title:'状态',dataIndex:'status',width:110},{title:'结果',dataIndex:'result',width:130},{title:'开始时间',dataIndex:'started_at',width:190},{title:'完成时间',dataIndex:'completed_at',width:190},{title:'',key:'action',width:54}]
|
||||
async function load(reset=false){if(reset)page.value=1;loading.value=true;try{const data=await api.get<{items:SOPRun[];total:number}>('/runs',{params:{page:page.value,page_size:pageSize.value,status:filters.status||undefined,result:filters.result||undefined}});items.value=data.items||[];total.value=data.total}catch(error){message.error(apiMessage(error))}finally{loading.value=false}}
|
||||
interface FilterOption{value:number;label:string}
|
||||
const filters=reactive({status:'',result:'',sop_id:undefined as number|undefined,operator_id:undefined as number|undefined})
|
||||
const dateRange=ref<any[]>([])
|
||||
const filterOptions=reactive<{sops:FilterOption[];operators:FilterOption[]}>({sops:[],operators:[]})
|
||||
const columns=[{title:'执行编号',dataIndex:'id',width:130},{title:'SOP / 场景',key:'sop',width:260},{title:'执行人',dataIndex:'operator_name',width:130},{title:'状态',dataIndex:'status',width:100},{title:'结果',dataIndex:'result',width:130},{title:'开始时间',dataIndex:'started_at',width:180},{title:'完成时间',dataIndex:'completed_at',width:180},{title:'',key:'action',width:54}]
|
||||
function queryParams(){return{page:page.value,page_size:pageSize.value,status:filters.status||undefined,result:filters.result||undefined,sop_id:filters.sop_id,operator_id:filters.operator_id,started_from:dateRange.value?.[0]?.startOf('day').toISOString(),started_to:dateRange.value?.[1]?.endOf('day').toISOString()}}
|
||||
async function load(reset=false){if(reset)page.value=1;loading.value=true;try{const data=await api.get<{items:SOPRun[];total:number}>('/runs',{params:queryParams()});items.value=data.items||[];total.value=data.total}catch(error){message.error(apiMessage(error))}finally{loading.value=false}}
|
||||
async function loadOptions(){try{const data=await api.get<{sops:FilterOption[];operators:FilterOption[]}>('/runs/options');filterOptions.sops=data.sops||[];filterOptions.operators=data.operators||[]}catch(error){message.error(apiMessage(error))}}
|
||||
function resetFilters(){Object.assign(filters,{status:'',result:'',sop_id:undefined,operator_id:undefined});dateRange.value=[];load(true)}
|
||||
function statusText(v:string){return v==='completed'?'已完成':'进行中'}
|
||||
function resultText(v:string){return{finish:'正常结束',escalate:'转人工 / 转诊',manual:'人工结束'}[v]||v||'-'}
|
||||
function changePage(next:number,size:number){page.value=next;pageSize.value=size;load()}
|
||||
function rowProps(record:SOPRun){return{onClick:()=>router.push(`/runs/${record.id}`)}}
|
||||
function canContinue(record:SOPRun){return record.status==='running'&&auth.can('sop.execute')&&(record.operator_id===auth.user?.user_id||auth.can('*'))}
|
||||
function continueRun(record:SOPRun){router.push({path:'/execute',query:{run:String(record.id)}})}
|
||||
function tableChange(pager:{current?:number;pageSize?:number}){changePage(pager.current||1,pager.pageSize||20)}
|
||||
onMounted(load)
|
||||
onMounted(()=>{load();loadOptions()})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -24,11 +34,11 @@ onMounted(load)
|
||||
<div class="page-heading"><div><h1>执行记录</h1><p>按结果筛选实际执行,进入详情复盘每一步回答和分流。</p></div></div>
|
||||
<section class="surface">
|
||||
<div class="history-toolbar">
|
||||
<div class="filter-set"><FilterOutlined /><a-select v-model:value="filters.status" allow-clear placeholder="全部状态" :options="[{value:'running',label:'进行中'},{value:'completed',label:'已完成'}]" @change="load(true)"/><a-select v-model:value="filters.result" allow-clear placeholder="全部结果" :options="[{value:'finish',label:'正常结束'},{value:'escalate',label:'转人工 / 转诊'},{value:'manual',label:'人工结束'}]" @change="load(true)"/></div>
|
||||
<div class="filter-set"><FilterOutlined /><a-select v-model:value="filters.status" allow-clear placeholder="全部状态" :options="[{value:'running',label:'进行中'},{value:'completed',label:'已完成'}]" @change="load(true)"/><a-select v-model:value="filters.result" allow-clear placeholder="全部结果" :options="[{value:'finish',label:'正常结束'},{value:'escalate',label:'转人工 / 转诊'},{value:'manual',label:'人工结束'}]" @change="load(true)"/><a-select v-model:value="filters.sop_id" allow-clear show-search option-filter-prop="label" placeholder="全部 SOP" :options="filterOptions.sops" @change="load(true)"/><a-select v-model:value="filters.operator_id" allow-clear show-search option-filter-prop="label" placeholder="全部执行人" :options="filterOptions.operators" @change="load(true)"/><a-range-picker v-model:value="dateRange" :allow-clear="true" @change="load(true)"/><a-button type="link" @click="resetFilters">重置</a-button></div>
|
||||
<a-button type="text" aria-label="刷新执行记录" @click="load()"><ReloadOutlined /></a-button>
|
||||
</div>
|
||||
<a-table row-key="id" :loading="loading" :data-source="items" :columns="columns" :pagination="{current:page,pageSize,total,showSizeChanger:true,showTotal:(value:number)=>`共 ${value} 条`}" :scroll="{x:980}" :custom-row="rowProps" @change="tableChange">
|
||||
<template #bodyCell="{column,record}"><template v-if="column.dataIndex==='id'"><code>RUN-{{ String(record.id).padStart(5,'0') }}</code></template><template v-else-if="column.dataIndex==='status'"><a-tag :color="record.status==='completed'?'green':'orange'">{{ statusText(record.status) }}</a-tag></template><template v-else-if="column.dataIndex==='result'">{{ resultText(record.result) }}</template><template v-else-if="column.dataIndex==='started_at'||column.dataIndex==='completed_at'">{{ record[column.dataIndex]?new Date(record[column.dataIndex]).toLocaleString('zh-CN'):'-' }}</template><template v-else-if="column.key==='action'"><RightOutlined class="row-arrow" /></template></template>
|
||||
<template #bodyCell="{column,record}"><template v-if="column.dataIndex==='id'"><code>RUN-{{ String(record.id).padStart(5,'0') }}</code></template><template v-else-if="column.key==='sop'"><div class="run-sop"><b>{{ record.sop_name }}</b><small>{{ record.scenario_name }} · V{{ record.version }}</small></div></template><template v-else-if="column.dataIndex==='status'"><a-tag :color="record.status==='completed'?'green':'orange'">{{ statusText(record.status) }}</a-tag></template><template v-else-if="column.dataIndex==='result'">{{ resultText(record.result) }}</template><template v-else-if="column.dataIndex==='started_at'||column.dataIndex==='completed_at'">{{ record[column.dataIndex]?new Date(record[column.dataIndex]).toLocaleString('zh-CN'):'-' }}</template><template v-else-if="column.key==='action'"><a-tooltip v-if="canContinue(record)" title="继续执行"><a-button type="text" aria-label="继续执行" @click.stop="continueRun(record)"><PlayCircleOutlined /></a-button></a-tooltip><RightOutlined v-else class="row-arrow" /></template></template>
|
||||
<template #emptyText><div class="empty-copy">当前筛选条件下没有执行记录。</div></template>
|
||||
</a-table>
|
||||
</section>
|
||||
@@ -36,5 +46,5 @@ onMounted(load)
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.history-toolbar{min-height:58px;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 14px;border-bottom:1px solid var(--line)}.filter-set{display:flex;align-items:center;gap:9px;color:#7a8781}.filter-set :deep(.ant-select){width:160px}.surface :deep(.ant-table-row){cursor:pointer}.surface :deep(.ant-table-row:hover) .row-arrow{color:var(--green);transform:translateX(2px)}code{padding:2px 5px;color:#17654f;background:#eef5f2;border-radius:3px}.row-arrow{color:#9ba6a1;transition:all .16s ease}@media(max-width:600px){.history-toolbar{align-items:flex-start}.filter-set{flex-wrap:wrap}.filter-set :deep(.ant-select){width:138px}}
|
||||
.history-toolbar{min-height:58px;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 14px;border-bottom:1px solid var(--line)}.filter-set{display:flex;align-items:center;flex-wrap:wrap;gap:9px;color:#7a8781}.filter-set :deep(.ant-select){width:150px}.filter-set :deep(.ant-picker){width:240px}.run-sop{display:flex;flex-direction:column}.run-sop small{margin-top:4px;color:var(--muted)}.surface :deep(.ant-table-row){cursor:pointer}.surface :deep(.ant-table-row:hover) .row-arrow{color:var(--green);transform:translateX(2px)}code{padding:2px 5px;color:#17654f;background:#eef5f2;border-radius:3px}.row-arrow{color:#9ba6a1;transition:all .16s ease}@media(max-width:700px){.history-toolbar{align-items:flex-start}.filter-set{width:100%}.filter-set :deep(.ant-select){width:calc(50% - 18px)}.filter-set :deep(.ant-picker){width:100%}}
|
||||
</style>
|
||||
|
||||
@@ -70,6 +70,7 @@ function removeEdge(edge:SOPEdge){edges.value=edges.value.filter(item=>item!==ed
|
||||
function addConditionRule(){edgeDraft.rules.push(newConditionRule())}
|
||||
function removeConditionRule(index:number){if(edgeDraft.rules.length>1)edgeDraft.rules.splice(index,1)}
|
||||
function parseValue(value:string){if(value==='true')return true;if(value==='false')return false;if(value!==''&&!Number.isNaN(Number(value)))return Number(value);return value}
|
||||
function knowledgeCardChanged(){if(selected.value)delete selected.value.config.knowledge_card_version_id}
|
||||
function conditionText(condition:Record<string,any>):string{if(!condition||Object.keys(condition).length===0)return '默认路径';if(Array.isArray(condition.any))return `任一:${condition.any.map(conditionText).join(';')}`;if(Array.isArray(condition.all))return `全部:${condition.all.map(conditionText).join(';')}`;if(!condition.field)return '未识别条件';const names:Record<string,string>={equals:'等于',not_equals:'不等于',contains:'包含',greater_than:'大于',less_than:'小于',in:'属于列表',exists:'已填写',not_exists:'未填写'};const fieldName=fields.value.find(field=>field.field_key===condition.field)?.field_name||condition.field;const value=Array.isArray(condition.value)?condition.value.join('、'):(condition.value??'');return `${fieldName} ${names[condition.operator]||condition.operator} ${value}`}
|
||||
async function save():Promise<boolean>{saving.value=true;try{await api.put(`/sops/${id}/draft`,{start_node_key:version.value?.start_node_key||'start',nodes:nodes.value.map(({node_key,type,title,content,config,position_x,position_y})=>({node_key,type,title,content,config,position_x,position_y})),edges:edges.value.map(({source_node_key,target_node_key,condition,priority})=>({source_node_key,target_node_key,condition,priority}))});message.success('草稿已保存');await load();return true}catch(error){message.error(apiMessage(error));return false}finally{saving.value=false}}
|
||||
async function validate(){try{const suffix=route.query.version?`?version=${encodeURIComponent(String(route.query.version))}`:'';const data=await api.post<{valid:boolean;problems:string[]}>(`/sops/${id}/validate${suffix}`);data.valid?message.success('流程校验通过'):Modal.warning({title:'流程还不能发布',content:data.problems.join(';')})}catch(error){message.error(apiMessage(error))}}
|
||||
@@ -105,7 +106,7 @@ onMounted(load)
|
||||
<template v-if="['question','choice'].includes(selected.type)"><div class="property-grid"><a-form-item label="写入场景字段"><a-select v-model:value="selected.config.field_key" :disabled="!canEdit" show-search option-filter-prop="label" placeholder="选择需要采集的字段" :options="fields.map(field=>({value:field.field_key,label:`${field.field_name} (${field.field_key})`}))" /></a-form-item><a-form-item label="是否必填"><a-switch v-model:checked="selected.config.required" :disabled="!canEdit" /></a-form-item></div></template>
|
||||
<a-form-item v-if="selected.type==='form'" label="表单采集字段"><a-select v-model:value="selected.config.field_keys" mode="multiple" :disabled="!canEdit" show-search option-filter-prop="label" placeholder="选择本步骤需要采集的字段" :options="fields.map(field=>({value:field.field_key,label:`${field.field_name} (${field.field_key})`}))" /></a-form-item>
|
||||
<a-form-item v-if="selected.type==='choice'" label="可选项(每行一个)"><a-textarea :value="(selected.config.options||[]).join('\n')" :disabled="!canEdit" :rows="4" @change="selected.config.options=($event.target as HTMLTextAreaElement).value.split('\n').filter(Boolean)" /></a-form-item>
|
||||
<a-form-item v-if="selected.type==='knowledge'" label="关联知识卡"><a-select v-model:value="selected.config.knowledge_card_id" :disabled="!canEdit" placeholder="选择已发布知识卡" :options="knowledgeCards.map(card=>({value:card.id,label:card.title}))" /></a-form-item>
|
||||
<a-form-item v-if="selected.type==='knowledge'" label="关联知识卡"><a-select v-model:value="selected.config.knowledge_card_id" :disabled="!canEdit" placeholder="选择已发布知识卡" :options="knowledgeCards.map(card=>({value:card.id,label:card.title}))" @change="knowledgeCardChanged" /><div v-if="selected.config.knowledge_card_version_id" class="snapshot-note">已发布快照:知识卡版本 ID {{ selected.config.knowledge_card_version_id }}</div></a-form-item>
|
||||
</a-form>
|
||||
<div class="transition-head"><div><b>下一步路径</b><span>条件路径优先匹配,默认路径始终排在最后</span></div></div>
|
||||
<div class="transition-list"><div v-for="edge in outgoing(selectedKey)" :key="`${edge.source_node_key}-${edge.target_node_key}-${edge.priority}`" class="transition-row"><span class="route-line"></span><span class="route-target">{{ nodes.find(n=>n.node_key===edge.target_node_key)?.title || edge.target_node_key }}</span><a-tag>{{ conditionText(edge.condition) }}</a-tag><a-button v-if="canEdit" type="text" danger aria-label="删除路径" @click="removeEdge(edge)"><DeleteOutlined /></a-button></div><div v-if="!outgoing(selectedKey).length" class="route-empty">还没有下一步路径</div></div>
|
||||
@@ -135,5 +136,6 @@ onMounted(load)
|
||||
|
||||
<style scoped>
|
||||
.editor-page{min-height:calc(100vh - 62px);background:#eef1ef}.editor-header{min-height:66px;display:flex;align-items:center;justify-content:space-between;gap:16px;padding:10px 24px;background:white;border-bottom:1px solid var(--line);position:sticky;top:62px;z-index:10}.editor-title{display:flex;align-items:center;gap:8px}.editor-title>div{display:flex;flex-direction:column}.editor-title b{font-size:15px}.editor-title span{margin-top:3px;color:var(--muted);font-size:11px}.page-actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}.editor-workbench{display:grid;grid-template-columns:280px minmax(0,1fr);gap:16px;max-width:1500px;margin:0 auto;padding:18px}.node-rail{align-self:start;overflow:hidden;position:sticky;top:146px}.rail-title{height:50px;display:flex;align-items:center;justify-content:space-between;padding:0 14px;border-bottom:1px solid var(--line);font-weight:650}.node-list{padding:8px}.node-list button{width:100%;min-height:58px;display:grid;grid-template-columns:34px 1fr auto;align-items:center;gap:9px;padding:7px 8px;text-align:left;background:transparent;border:1px solid transparent;border-radius:4px;cursor:pointer}.node-list button:hover{background:#f5f8f6}.node-list button.active{background:#edf6f2;border-color:#b9d8cc}.node-index{color:#88958f;font-family:monospace;font-size:11px}.node-meta{min-width:0;display:flex;flex-direction:column}.node-meta b{overflow:hidden;font-size:13px;text-overflow:ellipsis;white-space:nowrap}.node-meta small{margin-top:3px;color:var(--muted);font-size:11px}.node-move{display:flex;gap:4px;color:#87938e}.node-move>*:hover{color:var(--green)}.node-editor{align-self:start;min-height:600px;padding:24px}.node-editor-head{display:flex;align-items:flex-start;justify-content:space-between;padding-bottom:18px;border-bottom:1px solid var(--line)}.node-editor-head h2{margin:6px 0 0;font-family:"Noto Serif SC",serif;font-size:22px}.node-type-mark{color:var(--green);font-size:10px;font-weight:750}.property-form{padding-top:22px}.property-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px}.transition-head{display:flex;justify-content:space-between;margin:8px -24px 0;padding:18px 24px 10px;border-top:1px solid var(--line)}.transition-head>div{display:flex;flex-direction:column}.transition-head span{margin-top:4px;color:var(--muted);font-size:11px}.transition-row{min-height:48px;display:grid;grid-template-columns:20px minmax(120px,1fr) auto 36px;align-items:center;gap:10px;border-bottom:1px solid #edf0ee}.route-line{width:16px;height:1px;background:var(--green)}.route-target{font-weight:600}.route-empty{padding:20px 0;color:var(--muted);font-size:12px}.edge-builder{padding-top:16px}.edge-main{display:grid;grid-template-columns:minmax(220px,1fr) auto auto;align-items:center;gap:10px}.condition-toggle{display:flex;align-items:center;gap:7px;color:var(--muted);font-size:12px}.condition-builder{margin-top:12px;padding:14px;background:#f7f9f8;border:1px solid var(--line);border-radius:4px}.condition-mode{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px;color:var(--muted);font-size:12px}.condition-rule{display:grid;grid-template-columns:1.2fr 130px 1fr 34px;gap:8px;margin-top:8px}.editor-empty{min-height:400px;display:grid;place-items:center}.version-row{display:grid;grid-template-columns:54px 1fr auto;align-items:center;gap:12px;padding:16px 8px;border-bottom:1px solid var(--line)}.version-row.current{background:#f1f7f4}.version-number{font-weight:750;color:#245f4d}.version-copy>div{display:flex;align-items:center;gap:6px}.version-copy time{display:block;margin-top:5px;color:var(--muted);font-size:11px}.version-actions{display:flex;gap:6px}
|
||||
.snapshot-note{margin-top:6px;color:var(--muted);font-size:11px}
|
||||
@media(max-width:1050px){.editor-workbench{grid-template-columns:230px minmax(0,1fr)}.condition-rule{grid-template-columns:1fr 120px 1fr 34px}}@media(max-width:760px){.editor-header{top:62px;align-items:flex-start;flex-direction:column;padding:12px}.page-actions{justify-content:flex-start}.editor-workbench{grid-template-columns:1fr;padding:10px}.node-rail{position:static}.property-grid{grid-template-columns:1fr}.node-editor{padding:16px}.transition-head{margin:8px -16px 0;padding:16px}.edge-main,.condition-rule{grid-template-columns:1fr}.condition-rule .ant-btn{justify-self:end}.version-row{grid-template-columns:46px 1fr}.version-actions{grid-column:2}}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user