@yuku123/z-frontend-common 0.2.8 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +25 -0
- package/README.md +55 -42
- package/dist/index.js +1 -0
- package/package.json +49 -44
- package/src/components/ErrorBoundary/index.jsx +24 -77
- package/src/components/Layout/index.jsx +141 -38
- package/src/components/LoginPage/index.jsx +47 -21
- package/src/components/StatusTag/index.jsx +14 -12
- package/src/index.js +32 -38
- package/src/services/request.js +74 -4
- package/dist/z-frontend-common.css +0 -1
- package/dist/z-frontend-common.es.js +0 -51275
- package/dist/z-frontend-common.umd.js +0 -459
- package/src/components/Ctc/Layout.jsx +0 -330
- package/src/components/Ctc/Layout.module.css +0 -145
- package/src/components/Ctc/agentTeam/index.tsx +0 -308
- package/src/components/Ctc/login/AuthPage.module.css +0 -26
- package/src/components/Ctc/login/AuthPage.tsx +0 -235
- package/src/components/Ctc/login/index.less +0 -49
- package/src/components/Ctc/login/index.tsx +0 -142
- package/src/components/Ctc/uc/UCLogin.tsx +0 -31
- package/src/components/Ctc/uc/UCPhoneLogin.tsx +0 -62
- package/src/components/Ctc/uc/UCRegister.tsx +0 -37
- package/src/components/Ctc/uc/UCResetPassword.tsx +0 -69
- package/src/components/Ctc/uc/index.tsx +0 -15
- package/src/components/Ctc/userPanel/index.tsx +0 -999
- package/src/components/Ctc/webide/index.tsx +0 -272
- package/src/components/LowCode/LowCodeModel.jsx +0 -962
- package/src/components/LowCode/LowCodePage.jsx +0 -31
- package/src/components/LowCode/LowCodeRuntime.jsx +0 -335
- package/src/components/LowCode/MaterializePage.jsx +0 -235
- package/src/components/LowCode/index.js +0 -1
- package/src/components/MockPlatform/CurlImportModal.jsx +0 -362
- package/src/components/MockPlatform/EndpointsTab.jsx +0 -509
- package/src/components/MockPlatform/EnvironmentsTab.jsx +0 -212
- package/src/components/MockPlatform/MockTemplateHelper.jsx +0 -200
- package/src/components/MockPlatform/OpenApiImportModal.jsx +0 -305
- package/src/components/MockPlatform/RecordingsTab.jsx +0 -397
- package/src/components/MockPlatform/RequestLogsTab.jsx +0 -239
- package/src/components/MockPlatform/ScenariosTab.jsx +0 -236
- package/src/components/MockPlatform/TestCasesTab.jsx +0 -462
- package/src/components/MockPlatform/index.jsx +0 -127
- package/src/components/Overview.jsx +0 -74
- package/src/dev.jsx +0 -28
- package/src/services/agentTeam.js +0 -7
- package/src/services/api.js +0 -84
- package/src/services/ctcAc.js +0 -7
- package/src/services/ctcAcDomain.js +0 -7
- package/src/services/ctcAuthorization.js +0 -7
- package/src/services/ctcSurl.js +0 -7
- package/src/services/ctcUser.js +0 -7
- package/src/services/job.js +0 -7
- package/src/services/metaApp.js +0 -7
- package/src/services/privateConfig.js +0 -7
- package/src/services/webide.js +0 -6
- package/src/services/workspace.js +0 -21
- package/src/utils/request.ts +0 -102
package/LICENSE
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
<<<<<<< HEAD
|
|
4
|
+
Copyright (c) 2026 z-opc-foundation
|
|
5
|
+
=======
|
|
6
|
+
Copyright (c) 2026 z-opc-foundation
|
|
7
|
+
>>>>>>> 037d896 (feat: z-frontend-common 独立仓首版 1.0.0(lead 008 §1-§16 唯一承载仓))
|
|
8
|
+
|
|
9
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
10
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
11
|
+
in the Software without restriction, including without limitation the rights
|
|
12
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
13
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
14
|
+
furnished to do so, subject to the following conditions:
|
|
15
|
+
|
|
16
|
+
The above copyright notice and this permission notice shall be included in all
|
|
17
|
+
copies or substantial portions of the Software.
|
|
18
|
+
|
|
19
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
20
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
21
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
22
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
23
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
24
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
25
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,59 +1,72 @@
|
|
|
1
|
-
#
|
|
1
|
+
# z-frontend-common
|
|
2
2
|
|
|
3
|
-
> z-opc
|
|
3
|
+
> z-opc-foundation 全仓共享前端基座 —— lead 008《前端开发规范》§1-§16 的唯一承载仓。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
40+ 个 `z-*` 产品仓的 suit 通过同一份 AppLayout / LoginPage 获得一致的
|
|
6
|
+
左菜单架子、登录流程与右上角用户组件;21 个 `<repo>-component` 的 services
|
|
7
|
+
层消费同一份 axios 拦截器工厂。**改一处,全仓同步**。
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
npm install @yuku123/z-frontend-common
|
|
9
|
-
# 国内用户加速
|
|
10
|
-
# .npmrc: @yuku1231:registry=https://registry.npmmirror.com/
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
## 导出能力
|
|
9
|
+
---
|
|
14
10
|
|
|
15
|
-
|
|
16
|
-
- `AppLayout` — 统一布局(Sider + Header + Content)
|
|
17
|
-
- `StatusTag` — 状态标签
|
|
18
|
-
- `LoginPage` — 统一登录页
|
|
19
|
-
- `ErrorBoundary` — React 错误边界
|
|
11
|
+
## 基本信息
|
|
20
12
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
13
|
+
| 字段 | 值 |
|
|
14
|
+
|------|-----|
|
|
15
|
+
| 仓 | `z-opc-foundation/z-frontend-common`(本仓) |
|
|
16
|
+
| npm 包 | `@yuku123/z-frontend-common`(scope 沿用 yuku123,与 0.1.x/0.2.x 旧线连续) |
|
|
17
|
+
| 当前版本 | `1.0.0` —— 1.0 语义:§1-§16 规范化起点。0.1.x 是 pre-§16 形态;0.2.x 内嵌 react-dom 已废弃 |
|
|
18
|
+
| 技术栈 | React 19 + antd 6 + react-router-dom 7 + Vite 6(lead 005 §3.0 版本线单一尺子) |
|
|
19
|
+
| 产物 | ESM 单文件 `dist/index.js`;react / antd / @ant-design/icons / react-router-dom 全部 external |
|
|
20
|
+
| axios | `dependencies` 内联(`_shared/` vendoring 时期的决策保留:避免消费方从无 node_modules 的目录解析 axios) |
|
|
26
21
|
|
|
27
|
-
##
|
|
28
|
-
|
|
29
|
-
```jsx
|
|
30
|
-
import { AppLayout, LoginPage, request } from '@yuku123/z-frontend-common'
|
|
22
|
+
## 对外 API(与 0.1.1 完全兼容 + 三处升级)
|
|
31
23
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
24
|
+
| 导出 | 说明 |
|
|
25
|
+
|------|------|
|
|
26
|
+
| `AppLayout` | **升级**:§1 sider 双模式品牌区(暗主题显式白字 / 折叠 36×36 圆角图标 + Tooltip)、§11 selectedKey 最长菜单前缀、§12 sider 顶 `产品名@版本`、§15 Header 48px + 1px 下边线、§16 `appUser` 右上角下拉(profile + 退出登录,`loginPath` 按 `menuItems[0].key` 自动推导可覆盖) |
|
|
27
|
+
| `LoginPage` | **升级**:`mockAuth` 双模式 —— 传 `{username, password, user}` 走纯前端校验(CTC/SSO 接入前短期形态);缺省走 `loginApi` 真实端点 |
|
|
28
|
+
| `EmptyState`(占位) | 目录已建,组件下一波随 §2 列表页规范收口时进 |
|
|
29
|
+
| `StatusTag` / `defaultStatusMap` | 0.1.1 契约原样保留 |
|
|
30
|
+
| `ErrorBoundary` | 0.1.1 契约原样保留(antd Result 形态重绘) |
|
|
31
|
+
| `createRequest(options)` | axios 实例工厂:`{baseURL='/api', timeout=10000, tokenKey, userInfoKey, unauthorizedRedirect}`;21 个 component 仓 services 层消费 |
|
|
32
|
+
| `request` / `authRequest` | 预建实例(`createRequest()` 双份,0.1.1 契约) |
|
|
33
|
+
| `setupInterceptors` | 拦截器装配(Bearer token 注入 + 401 清凭跳登录 + 响应解包) |
|
|
34
|
+
| `unwrapResponse` | **新增显式导出**:`{code, data}` 包装解包 / 裸数据透传(z-opc Spring MVC 统一返回体兼容) |
|
|
35
|
+
| `default`(桶) | `{AppLayout, StatusTag, defaultStatusMap, LoginPage, ErrorBoundary, request, authRequest, createRequest}` —— 0.1.1 形状原样 |
|
|
37
36
|
|
|
38
|
-
##
|
|
37
|
+
## AppLayout props
|
|
39
38
|
|
|
40
|
-
```
|
|
41
|
-
|
|
39
|
+
```jsx
|
|
40
|
+
<AppLayout
|
|
41
|
+
menuItems={menuConfig} // antd Menu items,每项必须带 icon(§10)
|
|
42
|
+
appTitle="z-vector" // sider 展开态文字
|
|
43
|
+
appShort="ZV" // 无 appIcon 时折叠态缩写
|
|
44
|
+
appVersion="1.0.5" // §12:展开态渲染 z-vector@1.0.5(不要 "v" 前缀)
|
|
45
|
+
appIcon={{icon, color, label}} // §1:折叠态 36×36 圆角色板 + Tooltip
|
|
46
|
+
appUser={{name, role}} // §16:右上角 Avatar+name+DownOutlined 下拉
|
|
47
|
+
loginPath="/z-vector/login" // 可选,缺省按 menuItems[0].key 自动推导 /<产品>/login
|
|
48
|
+
headerExtra={<.../>} // Header 右侧追加内容(版本号别放这,§12)
|
|
49
|
+
loading={false} // 菜单区 loading 占位
|
|
50
|
+
/>
|
|
42
51
|
```
|
|
43
52
|
|
|
44
|
-
|
|
45
|
-
- 其他 code → reject
|
|
46
|
-
- 响应无 `code` 字段 → pass-through 透传
|
|
53
|
+
## suit 接入模板(lead 008 §16)
|
|
47
54
|
|
|
48
|
-
|
|
55
|
+
```jsx
|
|
56
|
+
import { AppLayout, LoginPage, ErrorBoundary } from '@yuku123/z-frontend-common'
|
|
57
|
+
import { menuConfig, routes, appMeta } from '@yuku123/<repo>-component/pages'
|
|
49
58
|
|
|
50
|
-
|
|
51
|
-
- 业务侧 `res.data.xxx` 写法统一
|
|
59
|
+
const MOCK_AUTH = { username: 'admin', password: '123456', user: { name: 'admin', role: '管理员' } }
|
|
52
60
|
|
|
53
|
-
|
|
61
|
+
// <Route path="/<repo>/login" element={<LoginPage title="<repo> 管理台" mockAuth={MOCK_AUTH} redirectUrl="/<repo>/home" />} />
|
|
62
|
+
// <Route element={<ProtectedShell/>}> 内挂 <AppLayout menuItems={menuConfig} appTitle={appMeta.title} appVersion="…" appUser={user} />
|
|
63
|
+
```
|
|
54
64
|
|
|
55
|
-
|
|
65
|
+
完整模板见 lead 008 §16;出厂自检清单见 §9。
|
|
56
66
|
|
|
57
|
-
##
|
|
67
|
+
## 历史
|
|
58
68
|
|
|
59
|
-
|
|
69
|
+
- `0.1.0 – 0.1.6`:早期形态(pre-§16),源码曾位于 `yuku123/z-opc` 仓的 `z-frontend-common/` 子目录
|
|
70
|
+
- `0.2.x`:内嵌 react-dom 的失败尝试线,已废弃
|
|
71
|
+
- 应急过渡:`z-opc-foundation/_shared/z-frontend-common-local/` vendoring(不入 git,1.0.0 发版后退役)
|
|
72
|
+
- `1.0.0`:独立仓首版 —— 源码自 vendoring 迁入,API 契约兼容 0.1.1,AppLayout/LoginPage 升级 §1-§16 规范版
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{Avatar as H,Button as j,Dropdown as W,Layout as R,Menu as N,Spin as J,Tooltip as A}from"antd";import{DownOutlined as G,LoginOutlined as Q,MenuFoldOutlined as X,MenuUnfoldOutlined as Y}from"@ant-design/icons";import{Outlet as Z,useLocation as _,useNavigate as U}from"react-router-dom";import{useState as V}from"react";import{jsx as o,jsxs as m}from"react/jsx-runtime";var{Header:ee,Sider:te,Content:re}=R;function oe(e,t=.85){let n=/^#?([0-9a-f]{6})$/i.exec(e||"");if(!n)return e;let l=parseInt(n[1],16),f=Math.max(0,Math.min(255,Math.round((l>>16&255)*t))),r=Math.max(0,Math.min(255,Math.round((l>>8&255)*t))),s=Math.max(0,Math.min(255,Math.round((l&255)*t)));return"#"+(f<<16|r<<8|s).toString(16).padStart(6,"0")}function L({menuItems:e=[],appTitle:t="One Company",appShort:n="OC",appVersion:l,appIcon:f,appUser:r,loginPath:s,headerExtra:v,loading:C=!1}){let[i,c]=V(!1),F=U(),$=_(),P=({key:d})=>{F(d)},M=()=>{if(s)return s;let d=e&&e[0],a=d&&d.key;return typeof a=="string"&&a.endsWith("/home")?a.slice(0,-4)+"login":"/login"},z=(d,a=[])=>{for(let u of d||[])u?.key&&a.push(u.key),u?.children&&z(u.children,a);return a},D=(()=>{let d=z(e),a=$.pathname;return d.filter(u=>a===u||a.startsWith(u+"/")).sort((u,b)=>b.length-u.length)[0]||a})();return m(R,{style:{minHeight:"100vh"},children:[m(te,{trigger:null,collapsible:!0,collapsed:i,children:[o("div",{style:{height:64,display:"flex",alignItems:"center",justifyContent:"center"},children:(()=>{if(i){if(f){let{icon:d,color:a="#7c3aed",label:u=t}=f,b=o("div",{style:{width:36,height:36,borderRadius:9,background:`linear-gradient(135deg, ${a}, ${oe(a,.7)})`,display:"flex",alignItems:"center",justifyContent:"center",color:"#fff",fontSize:20,boxShadow:"0 2px 6px rgba(0,0,0,0.25)",overflow:"hidden"},children:d});return o(A,{title:u,placement:"right",children:b})}return n}return m("span",{style:{color:"#fff",fontWeight:600,fontSize:16,whiteSpace:"nowrap"},children:[t,l&&m("span",{style:{color:"rgba(255,255,255,0.55)",fontWeight:400,fontSize:12,marginLeft:4},children:["@",l]})]})})()}),C?o("div",{style:{padding:16,textAlign:"center"},children:o(J,{size:"small"})}):o(N,{theme:"dark",mode:"inline",selectedKeys:[D],items:e,onClick:P})]}),m(R,{children:[m(ee,{style:{height:48,lineHeight:"48px",padding:"0 16px",background:"#fff",display:"flex",alignItems:"center",justifyContent:"space-between",borderBottom:"1px solid #f1f5f9"},children:[o("span",{onClick:()=>c(!i),style:{fontSize:18,cursor:"pointer"},children:i?o(Y,{}):o(X,{})}),m("div",{style:{display:"flex",alignItems:"center",gap:12},children:[r&&o(W,{menu:{items:[{key:"profile",label:`${r.name}\uFF08${r.role||"\u7528\u6237"}\uFF09`,disabled:!0},{type:"divider"},{key:"logout",label:"\u9000\u51FA\u767B\u5F55",onClick:()=>{localStorage.removeItem("token"),localStorage.removeItem("userInfo"),window.location.href=M()}}]},placement:"bottomRight",children:m("span",{style:{cursor:"pointer",display:"inline-flex",alignItems:"center",gap:6},children:[o(H,{size:28,style:{background:"#7c3aed"},children:(r.name||"?").slice(0,1).toUpperCase()}),o("span",{style:{fontSize:13,color:"#0f172a"},children:r.name}),o(G,{style:{fontSize:10,color:"#94a3b8"}})]})}),!r&&o(j,{type:"primary",size:"small",icon:o(Q,{}),onClick:()=>{window.location.href=M()},children:"\u767B\u5F55"}),v]})]}),o(re,{style:{margin:16,padding:24,background:"#fff",minHeight:280},children:o(Z,{})})]})]})}import{useState as se}from"react";import{Alert as ae,Button as ie,Card as le,Form as S,Input as K,message as h}from"antd";import{LockOutlined as fe,UserOutlined as ue}from"@ant-design/icons";import ne from"axios";import"antd";function B(e){return e&&typeof e=="object"&&"code"in e&&"data"in e?e.code!==200?Promise.reject(new Error(e.message||"\u8BF7\u6C42\u5931\u8D25")):e.data:e}function O(e,t={}){let{tokenKey:n="token",userInfoKey:l="userInfo",unauthorizedRedirect:f="/login"}=t;return e.interceptors.request.use(r=>{let s=localStorage.getItem(n);return s&&(r.headers.Authorization=`Bearer ${s}`),r}),e.interceptors.response.use(r=>B(r.data),r=>{let s=r.response?.status;return s===401?(localStorage.removeItem(n),localStorage.removeItem(l),window.location.href=f):s===403?console.error("\u6CA1\u6709\u6743\u9650\u8BBF\u95EE\u8BE5\u8D44\u6E90"):s===500&&console.error("\u670D\u52A1\u5668\u5185\u90E8\u9519\u8BEF"),Promise.reject(r)}),e}function y(e={}){let t=ne.create({baseURL:e.baseURL??"/api",timeout:e.timeout??1e4});return O(t,{tokenKey:e.tokenKey,userInfoKey:e.userInfoKey,unauthorizedRedirect:e.unauthorizedRedirect})}var x=y(),q=y();import{jsx as p,jsxs as T}from"react/jsx-runtime";function I({title:e="\u7EDF\u4E00\u7BA1\u7406\u5E73\u53F0",mockAuth:t,loginApi:n="/auth/login",redirectUrl:l="/",onSuccess:f}){let[r,s]=se(!1),v=i=>{localStorage.setItem("token",t?"mock-"+t.username:i?.token||"token"),localStorage.setItem("userInfo",JSON.stringify(i||{})),h.success("\u767B\u5F55\u6210\u529F"+(t?"\uFF08mock \u6A21\u5F0F\uFF0C\u5F85 CTC/SSO \u63A5\u5165\uFF09":"")),f?f(i):window.location.href=l},C=async i=>{s(!0);try{if(t){i.username===t.username&&i.password===t.password?v(t.user):h.error("\u7528\u6237\u540D\u6216\u5BC6\u7801\u9519\u8BEF");return}let c=await x.post(n,i);c?.token?(localStorage.setItem("token",c.token),localStorage.setItem("userInfo",JSON.stringify(c.user||c)),h.success("\u767B\u5F55\u6210\u529F"),f?f(c):window.location.href=l):h.error("\u767B\u5F55\u5931\u8D25\uFF1A\u672A\u83B7\u53D6\u5230 token")}catch(c){h.error(c.response?.data?.message||c.message||"\u767B\u5F55\u5931\u8D25")}finally{s(!1)}};return p("div",{style:{height:"100vh",display:"flex",justifyContent:"center",alignItems:"center",background:"#f0f2f5"},children:T(le,{title:e,style:{width:400},children:[t&&p(ae,{type:"info",showIcon:!0,style:{marginBottom:16},message:`Mock \u6A21\u5F0F\uFF1A\u7528\u6237\u540D ${t.username} / \u5BC6\u7801 ${t.password}`}),T(S,{onFinish:C,size:"large",children:[p(S.Item,{name:"username",rules:[{required:!0,message:"\u8BF7\u8F93\u5165\u7528\u6237\u540D"}],children:p(K,{prefix:p(ue,{}),placeholder:"\u7528\u6237\u540D"})}),p(S.Item,{name:"password",rules:[{required:!0,message:"\u8BF7\u8F93\u5165\u5BC6\u7801"}],children:p(K.Password,{prefix:p(fe,{}),placeholder:"\u5BC6\u7801"})}),p(S.Item,{children:p(ie,{type:"primary",htmlType:"submit",loading:r,block:!0,children:"\u767B \u5F55"})})]})]})})}import{Tag as ce}from"antd";import{jsx as de}from"react/jsx-runtime";var w={1:{color:"green",text:"\u6B63\u5E38"},0:{color:"red",text:"\u7981\u7528"},pending:{color:"orange",text:"\u5F85\u5904\u7406"},approved:{color:"blue",text:"\u5DF2\u901A\u8FC7"},rejected:{color:"red",text:"\u5DF2\u62D2\u7EDD"}};function k({status:e,map:t}){let n=t?.[e]||w[e]||{color:"default",text:String(e)};return de(ce,{color:n.color,children:n.text})}import{Component as pe}from"react";import{Button as me,Result as ge}from"antd";import{jsx as E}from"react/jsx-runtime";var g=class extends pe{constructor(t){super(t),this.state={hasError:!1,error:null,errorInfo:null},this.handleReset=()=>{this.setState({hasError:!1,error:null,errorInfo:null}),typeof this.props.onReset=="function"&&this.props.onReset()}}static getDerivedStateFromError(t){return{hasError:!0,error:t}}componentDidCatch(t,n){console.error("[ErrorBoundary] caught error:",t,n),this.setState({errorInfo:n})}render(){return this.state.hasError?E(ge,{status:"error",title:"\u9875\u9762\u51FA\u9519\u4E86",subTitle:String(this.state.error?.message||this.state.error),extra:E(me,{type:"primary",onClick:this.handleReset,children:"\u91CD\u8BD5"})}):this.props.children}};var Ae={AppLayout:L,StatusTag:k,defaultStatusMap:w,LoginPage:I,ErrorBoundary:g,request:x,authRequest:q,createRequest:y};export{L as AppLayout,g as ErrorBoundary,I as LoginPage,k as StatusTag,q as authRequest,y as createRequest,Ae as default,w as defaultStatusMap,x as request,O as setupInterceptors,B as unwrapResponse};
|
package/package.json
CHANGED
|
@@ -1,64 +1,69 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yuku123/z-frontend-common",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
"antd",
|
|
9
|
-
"vite",
|
|
10
|
-
"frontend",
|
|
11
|
-
"z-opc"
|
|
12
|
-
],
|
|
13
|
-
"license": "MIT",
|
|
14
|
-
"author": {
|
|
15
|
-
"name": "zifang",
|
|
16
|
-
"email": "1340947819@qq.com"
|
|
17
|
-
},
|
|
18
|
-
"homepage": "https://github.com/yuku123/z-opc-frontend/tree/main/packages/z-frontend-common#readme",
|
|
19
|
-
"repository": {
|
|
20
|
-
"type": "git",
|
|
21
|
-
"url": "https://github.com/yuku123/z-opc-frontend.git",
|
|
22
|
-
"directory": "packages/z-frontend-common"
|
|
23
|
-
},
|
|
24
|
-
"bugs": {
|
|
25
|
-
"url": "https://github.com/yuku123/z-opc-frontend/issues"
|
|
26
|
-
},
|
|
27
|
-
"main": "./dist/z-frontend-common.umd.js",
|
|
28
|
-
"module": "./dist/z-frontend-common.es.js",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "z-opc 全仓共享前端基座 — AppLayout(§1/§11/§12/§15/§16) + LoginPage(mockAuth 双模式) + EmptyState/StatusTag/ErrorBoundary + createRequest 工厂。lead 008 前端开发规范唯一承载仓",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
29
8
|
"types": "./dist/index.d.ts",
|
|
30
9
|
"exports": {
|
|
31
10
|
".": {
|
|
32
|
-
"
|
|
33
|
-
"
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"default": "./dist/index.js"
|
|
34
13
|
}
|
|
35
14
|
},
|
|
36
15
|
"files": [
|
|
37
16
|
"dist",
|
|
38
|
-
"src"
|
|
39
|
-
"README.md"
|
|
17
|
+
"src"
|
|
40
18
|
],
|
|
41
|
-
"publishConfig": {
|
|
42
|
-
"access": "public",
|
|
43
|
-
"registry": "https://registry.npmjs.org/"
|
|
44
|
-
},
|
|
45
19
|
"scripts": {
|
|
46
|
-
"dev": "vite --port 3099",
|
|
47
20
|
"build": "vite build",
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
"peerDependencies": {
|
|
51
|
-
"react": "^19.2.0",
|
|
52
|
-
"react-dom": "^19.2.0",
|
|
53
|
-
"antd": "^6.3.3",
|
|
54
|
-
"react-router-dom": "^7.13.1"
|
|
21
|
+
"dev": "vite build --watch",
|
|
22
|
+
"prepublishOnly": "vite build"
|
|
55
23
|
},
|
|
56
24
|
"dependencies": {
|
|
57
25
|
"axios": "^1.16.1"
|
|
58
26
|
},
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"@ant-design/icons": "^6.0.0",
|
|
29
|
+
"antd": "^6.0.0",
|
|
30
|
+
"react": "^19.0.0",
|
|
31
|
+
"react-dom": "^19.0.0",
|
|
32
|
+
"react-router-dom": "^7.0.0"
|
|
33
|
+
},
|
|
59
34
|
"devDependencies": {
|
|
35
|
+
"@ant-design/icons": "^6.1.0",
|
|
36
|
+
"@types/react": "^19.2.0",
|
|
37
|
+
"@types/react-dom": "^19.2.0",
|
|
60
38
|
"@vitejs/plugin-react": "^4.2.1",
|
|
61
|
-
"
|
|
39
|
+
"antd": "^6.3.3",
|
|
40
|
+
"axios": "^1.16.1",
|
|
41
|
+
"react": "^19.2.0",
|
|
42
|
+
"react-dom": "^19.2.0",
|
|
43
|
+
"react-router-dom": "^7.13.1",
|
|
44
|
+
"vite": "^6.2.6"
|
|
45
|
+
},
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "git+https://github.com/z-opc-foundation/z-frontend-common.git"
|
|
62
49
|
},
|
|
63
|
-
"
|
|
50
|
+
"bugs": {
|
|
51
|
+
"url": "https://github.com/z-opc-foundation/z-frontend-common/issues"
|
|
52
|
+
},
|
|
53
|
+
"homepage": "https://github.com/z-opc-foundation/z-frontend-common#readme",
|
|
54
|
+
"keywords": [
|
|
55
|
+
"react",
|
|
56
|
+
"antd",
|
|
57
|
+
"vite",
|
|
58
|
+
"z-opc",
|
|
59
|
+
"frontend-common",
|
|
60
|
+
"applayout",
|
|
61
|
+
"admin-shell"
|
|
62
|
+
],
|
|
63
|
+
"author": "yuku123",
|
|
64
|
+
"license": "MIT",
|
|
65
|
+
"publishConfig": {
|
|
66
|
+
"registry": "https://registry.npmjs.org/",
|
|
67
|
+
"access": "public"
|
|
68
|
+
}
|
|
64
69
|
}
|
|
@@ -1,93 +1,40 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {BugOutlined, ReloadOutlined} from '@ant-design/icons'
|
|
1
|
+
import { Component } from 'react'
|
|
2
|
+
import { Button, Result } from 'antd'
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* 用法:
|
|
10
|
-
* <ErrorBoundary fallbackTitle="流程编辑器加载失败">
|
|
11
|
-
* <WorkflowEditor />
|
|
12
|
-
* </ErrorBoundary>
|
|
5
|
+
* 兜底错误边界(0.1.1 契约保留):render 崩了显示错误 + 重置按钮。
|
|
6
|
+
* props: children + 可选 onReset(点「重试」时回调,缺省只清内部态)。
|
|
13
7
|
*/
|
|
14
|
-
export default class ErrorBoundary extends
|
|
8
|
+
export default class ErrorBoundary extends Component {
|
|
15
9
|
constructor(props) {
|
|
16
10
|
super(props)
|
|
17
|
-
this.state = {hasError: false, error: null, errorInfo: null}
|
|
11
|
+
this.state = { hasError: false, error: null, errorInfo: null }
|
|
12
|
+
this.handleReset = () => {
|
|
13
|
+
this.setState({ hasError: false, error: null, errorInfo: null })
|
|
14
|
+
typeof this.props.onReset === 'function' && this.props.onReset()
|
|
15
|
+
}
|
|
18
16
|
}
|
|
19
17
|
|
|
20
18
|
static getDerivedStateFromError(error) {
|
|
21
|
-
return {hasError: true, error}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
componentDidCatch(error, errorInfo) {
|
|
25
|
-
// eslint-disable-next-line no-console
|
|
26
|
-
console.error('[ErrorBoundary] caught error:', error, errorInfo)
|
|
27
|
-
this.setState({errorInfo})
|
|
19
|
+
return { hasError: true, error }
|
|
28
20
|
}
|
|
29
21
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
this.props.onReset()
|
|
34
|
-
}
|
|
22
|
+
componentDidCatch(error, info) {
|
|
23
|
+
console.error('[ErrorBoundary] caught error:', error, info)
|
|
24
|
+
this.setState({ errorInfo: info })
|
|
35
25
|
}
|
|
36
26
|
|
|
37
27
|
render() {
|
|
38
|
-
if (
|
|
39
|
-
return
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
return (
|
|
46
|
-
<div
|
|
47
|
-
style={{
|
|
48
|
-
padding: 24,
|
|
49
|
-
background: '#fff2f0',
|
|
50
|
-
border: '1px solid #ffccc7',
|
|
51
|
-
borderRadius: 8,
|
|
52
|
-
minHeight: 200,
|
|
53
|
-
}}
|
|
54
|
-
>
|
|
55
|
-
<Alert
|
|
56
|
-
type="error"
|
|
57
|
-
showIcon
|
|
58
|
-
icon={<BugOutlined/>}
|
|
59
|
-
message={fallbackTitle}
|
|
60
|
-
description={
|
|
61
|
-
<div>
|
|
62
|
-
<div style={{marginBottom: 8}}>
|
|
63
|
-
{fallbackDescription || '当前组件发生了异常,已被错误边界捕获。其他功能仍可正常使用。'}
|
|
64
|
-
</div>
|
|
65
|
-
<div
|
|
66
|
-
style={{
|
|
67
|
-
fontFamily: 'Menlo, Monaco, Consolas, monospace',
|
|
68
|
-
fontSize: 12,
|
|
69
|
-
color: '#cf1322',
|
|
70
|
-
background: '#fff',
|
|
71
|
-
padding: 8,
|
|
72
|
-
borderRadius: 4,
|
|
73
|
-
border: '1px solid #ffccc7',
|
|
74
|
-
wordBreak: 'break-word',
|
|
75
|
-
whiteSpace: 'pre-wrap',
|
|
76
|
-
}}
|
|
77
|
-
>
|
|
78
|
-
{errMsg}
|
|
79
|
-
</div>
|
|
80
|
-
</div>
|
|
81
|
-
}
|
|
28
|
+
if (this.state.hasError) {
|
|
29
|
+
return (
|
|
30
|
+
<Result
|
|
31
|
+
status="error"
|
|
32
|
+
title="页面出错了"
|
|
33
|
+
subTitle={String(this.state.error?.message || this.state.error)}
|
|
34
|
+
extra={<Button type="primary" onClick={this.handleReset}>重试</Button>}
|
|
82
35
|
/>
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
重试
|
|
87
|
-
</Button>
|
|
88
|
-
</Space>
|
|
89
|
-
)}
|
|
90
|
-
</div>
|
|
91
|
-
)
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
return this.props.children
|
|
92
39
|
}
|
|
93
40
|
}
|
|
@@ -1,69 +1,172 @@
|
|
|
1
|
-
import {Layout, Menu, Spin} from 'antd'
|
|
2
|
-
import {MenuFoldOutlined, MenuUnfoldOutlined} from '@ant-design/icons'
|
|
3
|
-
import {Outlet, useLocation, useNavigate} from 'react-router-dom'
|
|
4
|
-
import {useState} from 'react'
|
|
1
|
+
import { Avatar, Button, Dropdown, Layout, Menu, Spin, Tooltip } from 'antd'
|
|
2
|
+
import { DownOutlined, LoginOutlined, MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons'
|
|
3
|
+
import { Outlet, useLocation, useNavigate } from 'react-router-dom'
|
|
4
|
+
import { useState } from 'react'
|
|
5
5
|
|
|
6
|
-
const {Header, Sider, Content} = Layout
|
|
6
|
+
const { Header, Sider, Content } = Layout
|
|
7
|
+
|
|
8
|
+
function shade(hex, amount = 0.85) {
|
|
9
|
+
const m = /^#?([0-9a-f]{6})$/i.exec(hex || '')
|
|
10
|
+
if (!m) return hex
|
|
11
|
+
const n = parseInt(m[1], 16)
|
|
12
|
+
const r = Math.max(0, Math.min(255, Math.round(((n >> 16) & 0xff) * amount)))
|
|
13
|
+
const g = Math.max(0, Math.min(255, Math.round(((n >> 8) & 0xff) * amount)))
|
|
14
|
+
const b = Math.max(0, Math.min(255, Math.round((n & 0xff) * amount)))
|
|
15
|
+
return '#' + ((r << 16) | (g << 8) | b).toString(16).padStart(6, '0')
|
|
16
|
+
}
|
|
7
17
|
|
|
8
18
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
19
|
+
* 通用左菜单架子 AppLayout — lead 008 §1/§11/§12/§15/§16 全 46 仓 suit 强制落地。
|
|
20
|
+
*
|
|
21
|
+
* props:
|
|
22
|
+
* - menuItems / appTitle / appShort / appIcon / headerExtra / loading: 同 z-vector-component 原版
|
|
23
|
+
* - appVersion?: string — sider 展开态展示「z-vector@1.0.5」格式
|
|
24
|
+
* - appUser?: { name, role } — Header 右上角 avatar+name+下拉(profile/退出登录)
|
|
25
|
+
* - loginPath?: string — 退出登录跳转目标,缺省按 menuItems[0].key 自动推导(/home → /login)
|
|
16
26
|
*/
|
|
17
|
-
export
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
27
|
+
export function AppLayout({
|
|
28
|
+
menuItems = [],
|
|
29
|
+
appTitle = 'One Company',
|
|
30
|
+
appShort = 'OC',
|
|
31
|
+
appVersion,
|
|
32
|
+
appIcon,
|
|
33
|
+
appUser,
|
|
34
|
+
loginPath,
|
|
35
|
+
headerExtra,
|
|
36
|
+
loading = false,
|
|
37
|
+
}) {
|
|
24
38
|
const [collapsed, setCollapsed] = useState(false)
|
|
25
39
|
const navigate = useNavigate()
|
|
26
40
|
const location = useLocation()
|
|
27
41
|
|
|
28
|
-
const handleMenuClick = ({key}) => {
|
|
29
|
-
|
|
42
|
+
const handleMenuClick = ({ key }) => { navigate(key) }
|
|
43
|
+
|
|
44
|
+
// 退出登录跳转:loginPath 显式传优先;否则按 menuItems[0].key 自动推导 /<产品>/home → /<产品>/login
|
|
45
|
+
const computeLoginPath = () => {
|
|
46
|
+
if (loginPath) return loginPath
|
|
47
|
+
const first = menuItems && menuItems[0]
|
|
48
|
+
const k = first && first.key
|
|
49
|
+
if (typeof k === 'string' && k.endsWith('/home')) {
|
|
50
|
+
return k.slice(0, -4) + 'login'
|
|
51
|
+
}
|
|
52
|
+
return '/login'
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// URL 即状态(lead 008 §11):用 location.pathname 的最长菜单前缀作 selectedKey
|
|
56
|
+
const collectKeys = (items, acc = []) => {
|
|
57
|
+
for (const it of items || []) {
|
|
58
|
+
if (it?.key) acc.push(it.key)
|
|
59
|
+
if (it?.children) collectKeys(it.children, acc)
|
|
60
|
+
}
|
|
61
|
+
return acc
|
|
62
|
+
}
|
|
63
|
+
const selectedKey = (() => {
|
|
64
|
+
const keys = collectKeys(menuItems)
|
|
65
|
+
const p = location.pathname
|
|
66
|
+
return keys.filter((k) => p === k || p.startsWith(k + '/'))
|
|
67
|
+
.sort((a, b) => b.length - a.length)[0] || p
|
|
68
|
+
})()
|
|
69
|
+
|
|
70
|
+
const renderBrand = () => {
|
|
71
|
+
if (collapsed) {
|
|
72
|
+
if (appIcon) {
|
|
73
|
+
const { icon, color = '#7c3aed', label = appTitle } = appIcon
|
|
74
|
+
const node = (
|
|
75
|
+
<div style={{
|
|
76
|
+
width: 36, height: 36, borderRadius: 9,
|
|
77
|
+
background: `linear-gradient(135deg, ${color}, ${shade(color, 0.7)})`,
|
|
78
|
+
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
79
|
+
color: '#fff', fontSize: 20, boxShadow: '0 2px 6px rgba(0,0,0,0.25)',
|
|
80
|
+
overflow: 'hidden',
|
|
81
|
+
}}>{icon}</div>
|
|
82
|
+
)
|
|
83
|
+
return <Tooltip title={label} placement="right">{node}</Tooltip>
|
|
84
|
+
}
|
|
85
|
+
return appShort
|
|
86
|
+
}
|
|
87
|
+
return (
|
|
88
|
+
<span style={{ color: '#fff', fontWeight: 600, fontSize: 16, whiteSpace: 'nowrap' }}>
|
|
89
|
+
{appTitle}
|
|
90
|
+
{appVersion && (
|
|
91
|
+
<span style={{
|
|
92
|
+
color: 'rgba(255,255,255,0.55)', fontWeight: 400, fontSize: 12, marginLeft: 4,
|
|
93
|
+
}}>@{appVersion}</span>
|
|
94
|
+
)}
|
|
95
|
+
</span>
|
|
96
|
+
)
|
|
30
97
|
}
|
|
31
98
|
|
|
32
99
|
return (
|
|
33
|
-
<Layout style={{minHeight: '100vh'}}>
|
|
100
|
+
<Layout style={{ minHeight: '100vh' }}>
|
|
34
101
|
<Sider trigger={null} collapsible collapsed={collapsed}>
|
|
35
102
|
<div style={{
|
|
36
103
|
height: 64, display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
37
|
-
color: 'white', fontSize: collapsed ? 14 : 18, fontWeight: 'bold',
|
|
38
104
|
}}>
|
|
39
|
-
{
|
|
105
|
+
{renderBrand()}
|
|
40
106
|
</div>
|
|
41
107
|
{loading ? (
|
|
42
|
-
<div style={{padding: 16, textAlign: 'center'}}>
|
|
43
|
-
<Spin size="small"/>
|
|
44
|
-
</div>
|
|
108
|
+
<div style={{ padding: 16, textAlign: 'center' }}><Spin size="small" /></div>
|
|
45
109
|
) : (
|
|
46
|
-
<Menu
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
110
|
+
<Menu
|
|
111
|
+
theme="dark"
|
|
112
|
+
mode="inline"
|
|
113
|
+
selectedKeys={[selectedKey]}
|
|
114
|
+
items={menuItems}
|
|
115
|
+
onClick={handleMenuClick}
|
|
50
116
|
/>
|
|
51
117
|
)}
|
|
52
118
|
</Sider>
|
|
53
119
|
<Layout>
|
|
54
120
|
<Header style={{
|
|
55
|
-
padding: '0 16px', background: '#fff',
|
|
121
|
+
height: 48, lineHeight: '48px', padding: '0 16px', background: '#fff',
|
|
56
122
|
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
|
|
123
|
+
borderBottom: '1px solid #f1f5f9',
|
|
57
124
|
}}>
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
{
|
|
125
|
+
<span onClick={() => setCollapsed(!collapsed)} style={{ fontSize: 18, cursor: 'pointer' }}>
|
|
126
|
+
{collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
|
|
127
|
+
</span>
|
|
128
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
|
129
|
+
{appUser && (
|
|
130
|
+
<Dropdown
|
|
131
|
+
menu={{
|
|
132
|
+
items: [
|
|
133
|
+
{ key: 'profile', label: `${appUser.name}(${appUser.role || '用户'})`, disabled: true },
|
|
134
|
+
{ type: 'divider' },
|
|
135
|
+
{
|
|
136
|
+
key: 'logout', label: '退出登录', onClick: () => {
|
|
137
|
+
localStorage.removeItem('token')
|
|
138
|
+
localStorage.removeItem('userInfo')
|
|
139
|
+
window.location.href = computeLoginPath()
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
}}
|
|
144
|
+
placement="bottomRight"
|
|
145
|
+
>
|
|
146
|
+
<span style={{ cursor: 'pointer', display: 'inline-flex', alignItems: 'center', gap: 6 }}>
|
|
147
|
+
<Avatar size={28} style={{ background: '#7c3aed' }}>
|
|
148
|
+
{(appUser.name || '?').slice(0, 1).toUpperCase()}
|
|
149
|
+
</Avatar>
|
|
150
|
+
<span style={{ fontSize: 13, color: '#0f172a' }}>{appUser.name}</span>
|
|
151
|
+
<DownOutlined style={{ fontSize: 10, color: '#94a3b8' }} />
|
|
152
|
+
</span>
|
|
153
|
+
</Dropdown>
|
|
154
|
+
)}
|
|
155
|
+
{!appUser && (
|
|
156
|
+
<Button type="primary" size="small" icon={<LoginOutlined />}
|
|
157
|
+
onClick={() => { window.location.href = computeLoginPath() }}>
|
|
158
|
+
登录
|
|
159
|
+
</Button>
|
|
160
|
+
)}
|
|
161
|
+
{headerExtra}
|
|
162
|
+
</div>
|
|
62
163
|
</Header>
|
|
63
|
-
<Content style={{margin: 16, padding: 24, background: '#fff', minHeight: 280}}>
|
|
64
|
-
<Outlet/>
|
|
164
|
+
<Content style={{ margin: 16, padding: 24, background: '#fff', minHeight: 280 }}>
|
|
165
|
+
<Outlet />
|
|
65
166
|
</Content>
|
|
66
167
|
</Layout>
|
|
67
168
|
</Layout>
|
|
68
169
|
)
|
|
69
170
|
}
|
|
171
|
+
|
|
172
|
+
export default AppLayout
|