@yuku123/z-frontend-common 0.1.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/README.md +59 -0
- package/dist/z-frontend-common.es.js +497 -0
- package/dist/z-frontend-common.umd.js +22 -0
- package/package.json +63 -0
- package/src/components/ErrorBoundary/index.jsx +93 -0
- package/src/components/Layout/index.jsx +69 -0
- package/src/components/LoginPage/index.jsx +67 -0
- package/src/components/StatusTag/index.jsx +16 -0
- package/src/dev.jsx +28 -0
- package/src/index.js +36 -0
- package/src/utils/request.ts +102 -0
package/README.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# @yuku123/z-frontend-common
|
|
2
|
+
|
|
3
|
+
> z-opc 共享前端组件库 · React 19 + Ant Design 6
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @yuku123/z-frontend-common
|
|
9
|
+
# 国内用户加速
|
|
10
|
+
# .npmrc: @yuku1231:registry=https://registry.npmmirror.com/
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## 导出能力
|
|
14
|
+
|
|
15
|
+
### 组件
|
|
16
|
+
- `AppLayout` — 统一布局(Sider + Header + Content)
|
|
17
|
+
- `StatusTag` — 状态标签
|
|
18
|
+
- `LoginPage` — 统一登录页
|
|
19
|
+
- `ErrorBoundary` — React 错误边界
|
|
20
|
+
|
|
21
|
+
### 请求工具
|
|
22
|
+
- `request` — 默认实例(`baseURL=/api`,识别 `{code, data, message}` 后端响应)
|
|
23
|
+
- `authRequest` — 同上(语义区分)
|
|
24
|
+
- `createRequest({baseURL, tokenKey, userInfoKey, unauthorizedRedirect, timeout})` — 工厂
|
|
25
|
+
- `setupInterceptors(instance, options)` — 自定义拦截器
|
|
26
|
+
|
|
27
|
+
## 使用示例
|
|
28
|
+
|
|
29
|
+
```jsx
|
|
30
|
+
import { AppLayout, LoginPage, request } from '@yuku123/z-frontend-common'
|
|
31
|
+
|
|
32
|
+
// 业务模块推荐:在 src/utils/request.js 写 2-10 行适配层
|
|
33
|
+
import { createRequest } from '@yuku123/z-frontend-common'
|
|
34
|
+
const request = createRequest({ baseURL: '/meta', tokenKey: 'token' })
|
|
35
|
+
export default request
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## 统一后端响应格式
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{ "code": 200, "data": T, "message": "ok" }
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
- `code=200` → 自动返回 `data`
|
|
45
|
+
- 其他 code → reject
|
|
46
|
+
- 响应无 `code` 字段 → pass-through 透传
|
|
47
|
+
|
|
48
|
+
## 长期方案
|
|
49
|
+
|
|
50
|
+
- 模板从 z-meta 拉取(数据库驱动)
|
|
51
|
+
- 业务侧 `res.data.xxx` 写法统一
|
|
52
|
+
|
|
53
|
+
## 仓库
|
|
54
|
+
|
|
55
|
+
https://github.com/yuku123/z-opc/tree/main/z-frontend-common
|
|
56
|
+
|
|
57
|
+
## License
|
|
58
|
+
|
|
59
|
+
MIT
|
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
var fe = Object.defineProperty;
|
|
2
|
+
var de = (r, s, a) => s in r ? fe(r, s, { enumerable: !0, configurable: !0, writable: !0, value: a }) : r[s] = a;
|
|
3
|
+
var D = (r, s, a) => de(r, typeof s != "symbol" ? s + "" : s, a);
|
|
4
|
+
import V, { useState as G } from "react";
|
|
5
|
+
import { Layout as P, Spin as me, Menu as pe, Tag as he, Card as ge, Form as v, Input as U, Button as H, message as A, Alert as xe, Space as be } from "antd";
|
|
6
|
+
import { MenuUnfoldOutlined as Ee, MenuFoldOutlined as Re, UserOutlined as ye, LockOutlined as ve, BugOutlined as _e, ReloadOutlined as je } from "@ant-design/icons";
|
|
7
|
+
import { useNavigate as ke, useLocation as Te, Outlet as Se } from "react-router-dom";
|
|
8
|
+
import we from "axios";
|
|
9
|
+
var _ = { exports: {} }, b = {};
|
|
10
|
+
/**
|
|
11
|
+
* @license React
|
|
12
|
+
* react-jsx-runtime.production.js
|
|
13
|
+
*
|
|
14
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
15
|
+
*
|
|
16
|
+
* This source code is licensed under the MIT license found in the
|
|
17
|
+
* LICENSE file in the root directory of this source tree.
|
|
18
|
+
*/
|
|
19
|
+
var W;
|
|
20
|
+
function Oe() {
|
|
21
|
+
if (W) return b;
|
|
22
|
+
W = 1;
|
|
23
|
+
var r = Symbol.for("react.transitional.element"), s = Symbol.for("react.fragment");
|
|
24
|
+
function a(f, i, o) {
|
|
25
|
+
var d = null;
|
|
26
|
+
if (o !== void 0 && (d = "" + o), i.key !== void 0 && (d = "" + i.key), "key" in i) {
|
|
27
|
+
o = {};
|
|
28
|
+
for (var p in i)
|
|
29
|
+
p !== "key" && (o[p] = i[p]);
|
|
30
|
+
} else o = i;
|
|
31
|
+
return i = o.ref, {
|
|
32
|
+
$$typeof: r,
|
|
33
|
+
type: f,
|
|
34
|
+
key: d,
|
|
35
|
+
ref: i !== void 0 ? i : null,
|
|
36
|
+
props: o
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
return b.Fragment = s, b.jsx = a, b.jsxs = a, b;
|
|
40
|
+
}
|
|
41
|
+
var E = {};
|
|
42
|
+
/**
|
|
43
|
+
* @license React
|
|
44
|
+
* react-jsx-runtime.development.js
|
|
45
|
+
*
|
|
46
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
47
|
+
*
|
|
48
|
+
* This source code is licensed under the MIT license found in the
|
|
49
|
+
* LICENSE file in the root directory of this source tree.
|
|
50
|
+
*/
|
|
51
|
+
var B;
|
|
52
|
+
function Ae() {
|
|
53
|
+
return B || (B = 1, process.env.NODE_ENV !== "production" && (function() {
|
|
54
|
+
function r(e) {
|
|
55
|
+
if (e == null) return null;
|
|
56
|
+
if (typeof e == "function")
|
|
57
|
+
return e.$$typeof === ie ? null : e.displayName || e.name || null;
|
|
58
|
+
if (typeof e == "string") return e;
|
|
59
|
+
switch (e) {
|
|
60
|
+
case j:
|
|
61
|
+
return "Fragment";
|
|
62
|
+
case ee:
|
|
63
|
+
return "Profiler";
|
|
64
|
+
case Q:
|
|
65
|
+
return "StrictMode";
|
|
66
|
+
case oe:
|
|
67
|
+
return "Suspense";
|
|
68
|
+
case se:
|
|
69
|
+
return "SuspenseList";
|
|
70
|
+
case le:
|
|
71
|
+
return "Activity";
|
|
72
|
+
}
|
|
73
|
+
if (typeof e == "object")
|
|
74
|
+
switch (typeof e.tag == "number" && console.error(
|
|
75
|
+
"Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."
|
|
76
|
+
), e.$$typeof) {
|
|
77
|
+
case Z:
|
|
78
|
+
return "Portal";
|
|
79
|
+
case te:
|
|
80
|
+
return e.displayName || "Context";
|
|
81
|
+
case re:
|
|
82
|
+
return (e._context.displayName || "Context") + ".Consumer";
|
|
83
|
+
case ne:
|
|
84
|
+
var t = e.render;
|
|
85
|
+
return e = e.displayName, e || (e = t.displayName || t.name || "", e = e !== "" ? "ForwardRef(" + e + ")" : "ForwardRef"), e;
|
|
86
|
+
case ae:
|
|
87
|
+
return t = e.displayName || null, t !== null ? t : r(e.type) || "Memo";
|
|
88
|
+
case k:
|
|
89
|
+
t = e._payload, e = e._init;
|
|
90
|
+
try {
|
|
91
|
+
return r(e(t));
|
|
92
|
+
} catch {
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
function s(e) {
|
|
98
|
+
return "" + e;
|
|
99
|
+
}
|
|
100
|
+
function a(e) {
|
|
101
|
+
try {
|
|
102
|
+
s(e);
|
|
103
|
+
var t = !1;
|
|
104
|
+
} catch {
|
|
105
|
+
t = !0;
|
|
106
|
+
}
|
|
107
|
+
if (t) {
|
|
108
|
+
t = console;
|
|
109
|
+
var l = t.error, c = typeof Symbol == "function" && Symbol.toStringTag && e[Symbol.toStringTag] || e.constructor.name || "Object";
|
|
110
|
+
return l.call(
|
|
111
|
+
t,
|
|
112
|
+
"The provided key is an unsupported type %s. This value must be coerced to a string before using it here.",
|
|
113
|
+
c
|
|
114
|
+
), s(e);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function f(e) {
|
|
118
|
+
if (e === j) return "<>";
|
|
119
|
+
if (typeof e == "object" && e !== null && e.$$typeof === k)
|
|
120
|
+
return "<...>";
|
|
121
|
+
try {
|
|
122
|
+
var t = r(e);
|
|
123
|
+
return t ? "<" + t + ">" : "<...>";
|
|
124
|
+
} catch {
|
|
125
|
+
return "<...>";
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function i() {
|
|
129
|
+
var e = T.A;
|
|
130
|
+
return e === null ? null : e.getOwner();
|
|
131
|
+
}
|
|
132
|
+
function o() {
|
|
133
|
+
return Error("react-stack-top-frame");
|
|
134
|
+
}
|
|
135
|
+
function d(e) {
|
|
136
|
+
if (M.call(e, "key")) {
|
|
137
|
+
var t = Object.getOwnPropertyDescriptor(e, "key").get;
|
|
138
|
+
if (t && t.isReactWarning) return !1;
|
|
139
|
+
}
|
|
140
|
+
return e.key !== void 0;
|
|
141
|
+
}
|
|
142
|
+
function p(e, t) {
|
|
143
|
+
function l() {
|
|
144
|
+
Y || (Y = !0, console.error(
|
|
145
|
+
"%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)",
|
|
146
|
+
t
|
|
147
|
+
));
|
|
148
|
+
}
|
|
149
|
+
l.isReactWarning = !0, Object.defineProperty(e, "key", {
|
|
150
|
+
get: l,
|
|
151
|
+
configurable: !0
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
function h() {
|
|
155
|
+
var e = r(this.type);
|
|
156
|
+
return F[e] || (F[e] = !0, console.error(
|
|
157
|
+
"Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release."
|
|
158
|
+
)), e = this.props.ref, e !== void 0 ? e : null;
|
|
159
|
+
}
|
|
160
|
+
function g(e, t, l, c, y, w) {
|
|
161
|
+
var u = l.ref;
|
|
162
|
+
return e = {
|
|
163
|
+
$$typeof: L,
|
|
164
|
+
type: e,
|
|
165
|
+
key: t,
|
|
166
|
+
props: l,
|
|
167
|
+
_owner: c
|
|
168
|
+
}, (u !== void 0 ? u : null) !== null ? Object.defineProperty(e, "ref", {
|
|
169
|
+
enumerable: !1,
|
|
170
|
+
get: h
|
|
171
|
+
}) : Object.defineProperty(e, "ref", { enumerable: !1, value: null }), e._store = {}, Object.defineProperty(e._store, "validated", {
|
|
172
|
+
configurable: !1,
|
|
173
|
+
enumerable: !1,
|
|
174
|
+
writable: !0,
|
|
175
|
+
value: 0
|
|
176
|
+
}), Object.defineProperty(e, "_debugInfo", {
|
|
177
|
+
configurable: !1,
|
|
178
|
+
enumerable: !1,
|
|
179
|
+
writable: !0,
|
|
180
|
+
value: null
|
|
181
|
+
}), Object.defineProperty(e, "_debugStack", {
|
|
182
|
+
configurable: !1,
|
|
183
|
+
enumerable: !1,
|
|
184
|
+
writable: !0,
|
|
185
|
+
value: y
|
|
186
|
+
}), Object.defineProperty(e, "_debugTask", {
|
|
187
|
+
configurable: !1,
|
|
188
|
+
enumerable: !1,
|
|
189
|
+
writable: !0,
|
|
190
|
+
value: w
|
|
191
|
+
}), Object.freeze && (Object.freeze(e.props), Object.freeze(e)), e;
|
|
192
|
+
}
|
|
193
|
+
function m(e, t, l, c, y, w) {
|
|
194
|
+
var u = t.children;
|
|
195
|
+
if (u !== void 0)
|
|
196
|
+
if (c)
|
|
197
|
+
if (ce(u)) {
|
|
198
|
+
for (c = 0; c < u.length; c++)
|
|
199
|
+
I(u[c]);
|
|
200
|
+
Object.freeze && Object.freeze(u);
|
|
201
|
+
} else
|
|
202
|
+
console.error(
|
|
203
|
+
"React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead."
|
|
204
|
+
);
|
|
205
|
+
else I(u);
|
|
206
|
+
if (M.call(t, "key")) {
|
|
207
|
+
u = r(e);
|
|
208
|
+
var x = Object.keys(t).filter(function(ue) {
|
|
209
|
+
return ue !== "key";
|
|
210
|
+
});
|
|
211
|
+
c = 0 < x.length ? "{key: someKey, " + x.join(": ..., ") + ": ...}" : "{key: someKey}", z[u + c] || (x = 0 < x.length ? "{" + x.join(": ..., ") + ": ...}" : "{}", console.error(
|
|
212
|
+
`A props object containing a "key" prop is being spread into JSX:
|
|
213
|
+
let props = %s;
|
|
214
|
+
<%s {...props} />
|
|
215
|
+
React keys must be passed directly to JSX without using spread:
|
|
216
|
+
let props = %s;
|
|
217
|
+
<%s key={someKey} {...props} />`,
|
|
218
|
+
c,
|
|
219
|
+
u,
|
|
220
|
+
x,
|
|
221
|
+
u
|
|
222
|
+
), z[u + c] = !0);
|
|
223
|
+
}
|
|
224
|
+
if (u = null, l !== void 0 && (a(l), u = "" + l), d(t) && (a(t.key), u = "" + t.key), "key" in t) {
|
|
225
|
+
l = {};
|
|
226
|
+
for (var O in t)
|
|
227
|
+
O !== "key" && (l[O] = t[O]);
|
|
228
|
+
} else l = t;
|
|
229
|
+
return u && p(
|
|
230
|
+
l,
|
|
231
|
+
typeof e == "function" ? e.displayName || e.name || "Unknown" : e
|
|
232
|
+
), g(
|
|
233
|
+
e,
|
|
234
|
+
u,
|
|
235
|
+
l,
|
|
236
|
+
i(),
|
|
237
|
+
y,
|
|
238
|
+
w
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
function I(e) {
|
|
242
|
+
N(e) ? e._store && (e._store.validated = 1) : typeof e == "object" && e !== null && e.$$typeof === k && (e._payload.status === "fulfilled" ? N(e._payload.value) && e._payload.value._store && (e._payload.value._store.validated = 1) : e._store && (e._store.validated = 1));
|
|
243
|
+
}
|
|
244
|
+
function N(e) {
|
|
245
|
+
return typeof e == "object" && e !== null && e.$$typeof === L;
|
|
246
|
+
}
|
|
247
|
+
var R = V, L = Symbol.for("react.transitional.element"), Z = Symbol.for("react.portal"), j = Symbol.for("react.fragment"), Q = Symbol.for("react.strict_mode"), ee = Symbol.for("react.profiler"), re = Symbol.for("react.consumer"), te = Symbol.for("react.context"), ne = Symbol.for("react.forward_ref"), oe = Symbol.for("react.suspense"), se = Symbol.for("react.suspense_list"), ae = Symbol.for("react.memo"), k = Symbol.for("react.lazy"), le = Symbol.for("react.activity"), ie = Symbol.for("react.client.reference"), T = R.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, M = Object.prototype.hasOwnProperty, ce = Array.isArray, S = console.createTask ? console.createTask : function() {
|
|
248
|
+
return null;
|
|
249
|
+
};
|
|
250
|
+
R = {
|
|
251
|
+
react_stack_bottom_frame: function(e) {
|
|
252
|
+
return e();
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
var Y, F = {}, $ = R.react_stack_bottom_frame.bind(
|
|
256
|
+
R,
|
|
257
|
+
o
|
|
258
|
+
)(), q = S(f(o)), z = {};
|
|
259
|
+
E.Fragment = j, E.jsx = function(e, t, l) {
|
|
260
|
+
var c = 1e4 > T.recentlyCreatedOwnerStacks++;
|
|
261
|
+
return m(
|
|
262
|
+
e,
|
|
263
|
+
t,
|
|
264
|
+
l,
|
|
265
|
+
!1,
|
|
266
|
+
c ? Error("react-stack-top-frame") : $,
|
|
267
|
+
c ? S(f(e)) : q
|
|
268
|
+
);
|
|
269
|
+
}, E.jsxs = function(e, t, l) {
|
|
270
|
+
var c = 1e4 > T.recentlyCreatedOwnerStacks++;
|
|
271
|
+
return m(
|
|
272
|
+
e,
|
|
273
|
+
t,
|
|
274
|
+
l,
|
|
275
|
+
!0,
|
|
276
|
+
c ? Error("react-stack-top-frame") : $,
|
|
277
|
+
c ? S(f(e)) : q
|
|
278
|
+
);
|
|
279
|
+
};
|
|
280
|
+
})()), E;
|
|
281
|
+
}
|
|
282
|
+
var J;
|
|
283
|
+
function Pe() {
|
|
284
|
+
return J || (J = 1, process.env.NODE_ENV === "production" ? _.exports = Oe() : _.exports = Ae()), _.exports;
|
|
285
|
+
}
|
|
286
|
+
var n = Pe();
|
|
287
|
+
const { Header: Ce, Sider: Ie, Content: Ne } = P;
|
|
288
|
+
function Le({
|
|
289
|
+
menuItems: r = [],
|
|
290
|
+
appTitle: s = "One Company",
|
|
291
|
+
appShort: a = "OC",
|
|
292
|
+
headerExtra: f,
|
|
293
|
+
loading: i = !1
|
|
294
|
+
}) {
|
|
295
|
+
const [o, d] = G(!1), p = ke(), h = Te(), g = ({ key: m }) => {
|
|
296
|
+
p(m);
|
|
297
|
+
};
|
|
298
|
+
return /* @__PURE__ */ n.jsxs(P, { style: { minHeight: "100vh" }, children: [
|
|
299
|
+
/* @__PURE__ */ n.jsxs(Ie, { trigger: null, collapsible: !0, collapsed: o, children: [
|
|
300
|
+
/* @__PURE__ */ n.jsx("div", { style: {
|
|
301
|
+
height: 64,
|
|
302
|
+
display: "flex",
|
|
303
|
+
alignItems: "center",
|
|
304
|
+
justifyContent: "center",
|
|
305
|
+
color: "white",
|
|
306
|
+
fontSize: o ? 14 : 18,
|
|
307
|
+
fontWeight: "bold"
|
|
308
|
+
}, children: o ? a : s }),
|
|
309
|
+
i ? /* @__PURE__ */ n.jsx("div", { style: { padding: 16, textAlign: "center" }, children: /* @__PURE__ */ n.jsx(me, { size: "small" }) }) : /* @__PURE__ */ n.jsx(
|
|
310
|
+
pe,
|
|
311
|
+
{
|
|
312
|
+
theme: "dark",
|
|
313
|
+
mode: "inline",
|
|
314
|
+
selectedKeys: [h.pathname],
|
|
315
|
+
items: r,
|
|
316
|
+
onClick: g
|
|
317
|
+
}
|
|
318
|
+
)
|
|
319
|
+
] }),
|
|
320
|
+
/* @__PURE__ */ n.jsxs(P, { children: [
|
|
321
|
+
/* @__PURE__ */ n.jsxs(Ce, { style: {
|
|
322
|
+
padding: "0 16px",
|
|
323
|
+
background: "#fff",
|
|
324
|
+
display: "flex",
|
|
325
|
+
alignItems: "center",
|
|
326
|
+
justifyContent: "space-between"
|
|
327
|
+
}, children: [
|
|
328
|
+
/* @__PURE__ */ n.jsx("span", { onClick: () => d(!o), style: { fontSize: 18, cursor: "pointer" }, children: o ? /* @__PURE__ */ n.jsx(Ee, {}) : /* @__PURE__ */ n.jsx(Re, {}) }),
|
|
329
|
+
f
|
|
330
|
+
] }),
|
|
331
|
+
/* @__PURE__ */ n.jsx(Ne, { style: { margin: 16, padding: 24, background: "#fff", minHeight: 280 }, children: /* @__PURE__ */ n.jsx(Se, {}) })
|
|
332
|
+
] })
|
|
333
|
+
] });
|
|
334
|
+
}
|
|
335
|
+
const K = {
|
|
336
|
+
1: { color: "green", text: "正常" },
|
|
337
|
+
0: { color: "red", text: "禁用" },
|
|
338
|
+
pending: { color: "orange", text: "待处理" },
|
|
339
|
+
approved: { color: "blue", text: "已通过" },
|
|
340
|
+
rejected: { color: "red", text: "已拒绝" }
|
|
341
|
+
};
|
|
342
|
+
function Me({ status: r, map: s }) {
|
|
343
|
+
const a = (s == null ? void 0 : s[r]) || K[r] || { color: "default", text: String(r) };
|
|
344
|
+
return /* @__PURE__ */ n.jsx(he, { color: a.color, children: a.text });
|
|
345
|
+
}
|
|
346
|
+
function Ye(r) {
|
|
347
|
+
if (r && typeof r == "object" && "code" in r && "data" in r) {
|
|
348
|
+
const s = r;
|
|
349
|
+
return s.code !== 200 ? Promise.reject(new Error(s.message || "请求失败")) : s.data;
|
|
350
|
+
}
|
|
351
|
+
return r;
|
|
352
|
+
}
|
|
353
|
+
function Fe(r, s = {}) {
|
|
354
|
+
const {
|
|
355
|
+
tokenKey: a = "token",
|
|
356
|
+
userInfoKey: f = "userInfo",
|
|
357
|
+
unauthorizedRedirect: i = "/login"
|
|
358
|
+
} = s;
|
|
359
|
+
r.interceptors.request.use((o) => {
|
|
360
|
+
const d = localStorage.getItem(a);
|
|
361
|
+
return d && (o.headers.Authorization = `Bearer ${d}`), o;
|
|
362
|
+
}), r.interceptors.response.use(
|
|
363
|
+
(o) => Ye(o.data),
|
|
364
|
+
(o) => {
|
|
365
|
+
var d, p, h;
|
|
366
|
+
return ((d = o.response) == null ? void 0 : d.status) === 401 ? (localStorage.removeItem(a), localStorage.removeItem(f), window.location.href = i) : ((p = o.response) == null ? void 0 : p.status) === 403 ? console.error("没有权限访问该资源") : ((h = o.response) == null ? void 0 : h.status) === 500 && console.error("服务器内部错误"), Promise.reject(o);
|
|
367
|
+
}
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
function C(r = {}) {
|
|
371
|
+
const s = we.create({
|
|
372
|
+
baseURL: r.baseURL ?? "/api",
|
|
373
|
+
timeout: r.timeout ?? 1e4
|
|
374
|
+
});
|
|
375
|
+
return Fe(s, {
|
|
376
|
+
tokenKey: r.tokenKey,
|
|
377
|
+
userInfoKey: r.userInfoKey,
|
|
378
|
+
unauthorizedRedirect: r.unauthorizedRedirect
|
|
379
|
+
}), s;
|
|
380
|
+
}
|
|
381
|
+
const X = C(), $e = C();
|
|
382
|
+
function qe({
|
|
383
|
+
title: r = "统一管理平台",
|
|
384
|
+
loginApi: s = "/auth/login",
|
|
385
|
+
redirectUrl: a = "/",
|
|
386
|
+
onSuccess: f
|
|
387
|
+
}) {
|
|
388
|
+
const [i, o] = G(!1), d = async (p) => {
|
|
389
|
+
var h, g;
|
|
390
|
+
o(!0);
|
|
391
|
+
try {
|
|
392
|
+
const m = await X.post(s, p);
|
|
393
|
+
m.token ? (localStorage.setItem("token", m.token), localStorage.setItem("userInfo", JSON.stringify(m.user || m)), A.success("登录成功"), f ? f(m) : window.location.href = a) : A.error("登录失败:未获取到 token");
|
|
394
|
+
} catch (m) {
|
|
395
|
+
A.error(((g = (h = m.response) == null ? void 0 : h.data) == null ? void 0 : g.message) || m.message || "登录失败");
|
|
396
|
+
} finally {
|
|
397
|
+
o(!1);
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
return /* @__PURE__ */ n.jsx("div", { style: {
|
|
401
|
+
height: "100vh",
|
|
402
|
+
display: "flex",
|
|
403
|
+
justifyContent: "center",
|
|
404
|
+
alignItems: "center",
|
|
405
|
+
background: "#f0f2f5"
|
|
406
|
+
}, children: /* @__PURE__ */ n.jsx(ge, { title: r, style: { width: 400 }, children: /* @__PURE__ */ n.jsxs(v, { onFinish: d, size: "large", children: [
|
|
407
|
+
/* @__PURE__ */ n.jsx(v.Item, { name: "username", rules: [{ required: !0, message: "请输入用户名" }], children: /* @__PURE__ */ n.jsx(U, { prefix: /* @__PURE__ */ n.jsx(ye, {}), placeholder: "用户名" }) }),
|
|
408
|
+
/* @__PURE__ */ n.jsx(v.Item, { name: "password", rules: [{ required: !0, message: "请输入密码" }], children: /* @__PURE__ */ n.jsx(U.Password, { prefix: /* @__PURE__ */ n.jsx(ve, {}), placeholder: "密码" }) }),
|
|
409
|
+
/* @__PURE__ */ n.jsx(v.Item, { children: /* @__PURE__ */ n.jsx(H, { type: "primary", htmlType: "submit", loading: i, block: !0, children: "登 录" }) })
|
|
410
|
+
] }) }) });
|
|
411
|
+
}
|
|
412
|
+
class ze extends V.Component {
|
|
413
|
+
constructor(a) {
|
|
414
|
+
super(a);
|
|
415
|
+
D(this, "handleReset", () => {
|
|
416
|
+
this.setState({ hasError: !1, error: null, errorInfo: null }), typeof this.props.onReset == "function" && this.props.onReset();
|
|
417
|
+
});
|
|
418
|
+
this.state = { hasError: !1, error: null, errorInfo: null };
|
|
419
|
+
}
|
|
420
|
+
static getDerivedStateFromError(a) {
|
|
421
|
+
return { hasError: !0, error: a };
|
|
422
|
+
}
|
|
423
|
+
componentDidCatch(a, f) {
|
|
424
|
+
console.error("[ErrorBoundary] caught error:", a, f), this.setState({ errorInfo: f });
|
|
425
|
+
}
|
|
426
|
+
render() {
|
|
427
|
+
if (!this.state.hasError)
|
|
428
|
+
return this.props.children;
|
|
429
|
+
const { fallbackTitle: a = "组件加载失败", fallbackDescription: f, showReload: i = !0 } = this.props, o = this.state.error && (this.state.error.message || String(this.state.error)) || "未知错误";
|
|
430
|
+
return /* @__PURE__ */ n.jsxs(
|
|
431
|
+
"div",
|
|
432
|
+
{
|
|
433
|
+
style: {
|
|
434
|
+
padding: 24,
|
|
435
|
+
background: "#fff2f0",
|
|
436
|
+
border: "1px solid #ffccc7",
|
|
437
|
+
borderRadius: 8,
|
|
438
|
+
minHeight: 200
|
|
439
|
+
},
|
|
440
|
+
children: [
|
|
441
|
+
/* @__PURE__ */ n.jsx(
|
|
442
|
+
xe,
|
|
443
|
+
{
|
|
444
|
+
type: "error",
|
|
445
|
+
showIcon: !0,
|
|
446
|
+
icon: /* @__PURE__ */ n.jsx(_e, {}),
|
|
447
|
+
message: a,
|
|
448
|
+
description: /* @__PURE__ */ n.jsxs("div", { children: [
|
|
449
|
+
/* @__PURE__ */ n.jsx("div", { style: { marginBottom: 8 }, children: f || "当前组件发生了异常,已被错误边界捕获。其他功能仍可正常使用。" }),
|
|
450
|
+
/* @__PURE__ */ n.jsx(
|
|
451
|
+
"div",
|
|
452
|
+
{
|
|
453
|
+
style: {
|
|
454
|
+
fontFamily: "Menlo, Monaco, Consolas, monospace",
|
|
455
|
+
fontSize: 12,
|
|
456
|
+
color: "#cf1322",
|
|
457
|
+
background: "#fff",
|
|
458
|
+
padding: 8,
|
|
459
|
+
borderRadius: 4,
|
|
460
|
+
border: "1px solid #ffccc7",
|
|
461
|
+
wordBreak: "break-word",
|
|
462
|
+
whiteSpace: "pre-wrap"
|
|
463
|
+
},
|
|
464
|
+
children: o
|
|
465
|
+
}
|
|
466
|
+
)
|
|
467
|
+
] })
|
|
468
|
+
}
|
|
469
|
+
),
|
|
470
|
+
i && /* @__PURE__ */ n.jsx(be, { style: { marginTop: 16 }, children: /* @__PURE__ */ n.jsx(H, { icon: /* @__PURE__ */ n.jsx(je, {}), onClick: this.handleReset, children: "重试" }) })
|
|
471
|
+
]
|
|
472
|
+
}
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
const Ge = {
|
|
477
|
+
AppLayout: Le,
|
|
478
|
+
StatusTag: Me,
|
|
479
|
+
defaultStatusMap: K,
|
|
480
|
+
LoginPage: qe,
|
|
481
|
+
ErrorBoundary: ze,
|
|
482
|
+
request: X,
|
|
483
|
+
authRequest: $e,
|
|
484
|
+
createRequest: C
|
|
485
|
+
};
|
|
486
|
+
export {
|
|
487
|
+
Le as AppLayout,
|
|
488
|
+
ze as ErrorBoundary,
|
|
489
|
+
qe as LoginPage,
|
|
490
|
+
Me as StatusTag,
|
|
491
|
+
$e as authRequest,
|
|
492
|
+
C as createRequest,
|
|
493
|
+
Ge as default,
|
|
494
|
+
K as defaultStatusMap,
|
|
495
|
+
X as request,
|
|
496
|
+
Fe as setupInterceptors
|
|
497
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
(function(s,p){typeof exports=="object"&&typeof module<"u"?p(exports,require("react"),require("antd"),require("@ant-design/icons"),require("react-router-dom"),require("axios")):typeof define=="function"&&define.amd?define(["exports","react","antd","@ant-design/icons","react-router-dom","axios"],p):(s=typeof globalThis<"u"?globalThis:s||self,p(s.ZFrontendCommon={},s.React,s.antd,s.icons,s.ReactRouterDOM,s.axios))})(this,(function(s,p,a,E,w,te){"use strict";var je=Object.defineProperty;var ke=(s,p,a)=>p in s?je(s,p,{enumerable:!0,configurable:!0,writable:!0,value:a}):s[p]=a;var re=(s,p,a)=>ke(s,typeof p!="symbol"?p+"":p,a);var j={exports:{}},v={};/**
|
|
2
|
+
* @license React
|
|
3
|
+
* react-jsx-runtime.production.js
|
|
4
|
+
*
|
|
5
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
6
|
+
*
|
|
7
|
+
* This source code is licensed under the MIT license found in the
|
|
8
|
+
* LICENSE file in the root directory of this source tree.
|
|
9
|
+
*/var F;function ne(){if(F)return v;F=1;var n=Symbol.for("react.transitional.element"),l=Symbol.for("react.fragment");function i(m,c,o){var h=null;if(o!==void 0&&(h=""+o),c.key!==void 0&&(h=""+c.key),"key"in c){o={};for(var R in c)R!=="key"&&(o[R]=c[R])}else o=c;return c=o.ref,{$$typeof:n,type:m,key:h,ref:c!==void 0?c:null,props:o}}return v.Fragment=l,v.jsx=i,v.jsxs=i,v}var _={};/**
|
|
10
|
+
* @license React
|
|
11
|
+
* react-jsx-runtime.development.js
|
|
12
|
+
*
|
|
13
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
14
|
+
*
|
|
15
|
+
* This source code is licensed under the MIT license found in the
|
|
16
|
+
* LICENSE file in the root directory of this source tree.
|
|
17
|
+
*/var q;function oe(){return q||(q=1,process.env.NODE_ENV!=="production"&&(function(){function n(e){if(e==null)return null;if(typeof e=="function")return e.$$typeof===xe?null:e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case P:return"Fragment";case me:return"Profiler";case de:return"StrictMode";case Re:return"Suspense";case ye:return"SuspenseList";case be:return"Activity"}if(typeof e=="object")switch(typeof e.tag=="number"&&console.error("Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."),e.$$typeof){case fe:return"Portal";case he:return e.displayName||"Context";case pe:return(e._context.displayName||"Context")+".Consumer";case ge:var r=e.render;return e=e.displayName,e||(e=r.displayName||r.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case Ee:return r=e.displayName||null,r!==null?r:n(e.type)||"Memo";case C:r=e._payload,e=e._init;try{return n(e(r))}catch{}}return null}function l(e){return""+e}function i(e){try{l(e);var r=!1}catch{r=!0}if(r){r=console;var u=r.error,f=typeof Symbol=="function"&&Symbol.toStringTag&&e[Symbol.toStringTag]||e.constructor.name||"Object";return u.call(r,"The provided key is an unsupported type %s. This value must be coerced to a string before using it here.",f),l(e)}}function m(e){if(e===P)return"<>";if(typeof e=="object"&&e!==null&&e.$$typeof===C)return"<...>";try{var r=n(e);return r?"<"+r+">":"<...>"}catch{return"<...>"}}function c(){var e=I.A;return e===null?null:e.getOwner()}function o(){return Error("react-stack-top-frame")}function h(e){if(H.call(e,"key")){var r=Object.getOwnPropertyDescriptor(e,"key").get;if(r&&r.isReactWarning)return!1}return e.key!==void 0}function R(e,r){function u(){K||(K=!0,console.error("%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)",r))}u.isReactWarning=!0,Object.defineProperty(e,"key",{get:u,configurable:!0})}function y(){var e=n(this.type);return X[e]||(X[e]=!0,console.error("Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release.")),e=this.props.ref,e!==void 0?e:null}function b(e,r,u,f,S,L){var d=u.ref;return e={$$typeof:G,type:e,key:r,props:u,_owner:f},(d!==void 0?d:null)!==null?Object.defineProperty(e,"ref",{enumerable:!1,get:y}):Object.defineProperty(e,"ref",{enumerable:!1,value:null}),e._store={},Object.defineProperty(e._store,"validated",{configurable:!1,enumerable:!1,writable:!0,value:0}),Object.defineProperty(e,"_debugInfo",{configurable:!1,enumerable:!1,writable:!0,value:null}),Object.defineProperty(e,"_debugStack",{configurable:!1,enumerable:!1,writable:!0,value:S}),Object.defineProperty(e,"_debugTask",{configurable:!1,enumerable:!1,writable:!0,value:L}),Object.freeze&&(Object.freeze(e.props),Object.freeze(e)),e}function g(e,r,u,f,S,L){var d=r.children;if(d!==void 0)if(f)if(ve(d)){for(f=0;f<d.length;f++)J(d[f]);Object.freeze&&Object.freeze(d)}else console.error("React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead.");else J(d);if(H.call(r,"key")){d=n(e);var x=Object.keys(r).filter(function(_e){return _e!=="key"});f=0<x.length?"{key: someKey, "+x.join(": ..., ")+": ...}":"{key: someKey}",ee[d+f]||(x=0<x.length?"{"+x.join(": ..., ")+": ...}":"{}",console.error(`A props object containing a "key" prop is being spread into JSX:
|
|
18
|
+
let props = %s;
|
|
19
|
+
<%s {...props} />
|
|
20
|
+
React keys must be passed directly to JSX without using spread:
|
|
21
|
+
let props = %s;
|
|
22
|
+
<%s key={someKey} {...props} />`,f,d,x,d),ee[d+f]=!0)}if(d=null,u!==void 0&&(i(u),d=""+u),h(r)&&(i(r.key),d=""+r.key),"key"in r){u={};for(var M in r)M!=="key"&&(u[M]=r[M])}else u=r;return d&&R(u,typeof e=="function"?e.displayName||e.name||"Unknown":e),b(e,d,u,c(),S,L)}function J(e){V(e)?e._store&&(e._store.validated=1):typeof e=="object"&&e!==null&&e.$$typeof===C&&(e._payload.status==="fulfilled"?V(e._payload.value)&&e._payload.value._store&&(e._payload.value._store.validated=1):e._store&&(e._store.validated=1))}function V(e){return typeof e=="object"&&e!==null&&e.$$typeof===G}var T=p,G=Symbol.for("react.transitional.element"),fe=Symbol.for("react.portal"),P=Symbol.for("react.fragment"),de=Symbol.for("react.strict_mode"),me=Symbol.for("react.profiler"),pe=Symbol.for("react.consumer"),he=Symbol.for("react.context"),ge=Symbol.for("react.forward_ref"),Re=Symbol.for("react.suspense"),ye=Symbol.for("react.suspense_list"),Ee=Symbol.for("react.memo"),C=Symbol.for("react.lazy"),be=Symbol.for("react.activity"),xe=Symbol.for("react.client.reference"),I=T.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,H=Object.prototype.hasOwnProperty,ve=Array.isArray,N=console.createTask?console.createTask:function(){return null};T={react_stack_bottom_frame:function(e){return e()}};var K,X={},Z=T.react_stack_bottom_frame.bind(T,o)(),Q=N(m(o)),ee={};_.Fragment=P,_.jsx=function(e,r,u){var f=1e4>I.recentlyCreatedOwnerStacks++;return g(e,r,u,!1,f?Error("react-stack-top-frame"):Z,f?N(m(e)):Q)},_.jsxs=function(e,r,u){var f=1e4>I.recentlyCreatedOwnerStacks++;return g(e,r,u,!0,f?Error("react-stack-top-frame"):Z,f?N(m(e)):Q)}})()),_}var Y;function se(){return Y||(Y=1,process.env.NODE_ENV==="production"?j.exports=ne():j.exports=oe()),j.exports}var t=se();const{Header:ae,Sider:ie,Content:le}=a.Layout;function z({menuItems:n=[],appTitle:l="One Company",appShort:i="OC",headerExtra:m,loading:c=!1}){const[o,h]=p.useState(!1),R=w.useNavigate(),y=w.useLocation(),b=({key:g})=>{R(g)};return t.jsxs(a.Layout,{style:{minHeight:"100vh"},children:[t.jsxs(ie,{trigger:null,collapsible:!0,collapsed:o,children:[t.jsx("div",{style:{height:64,display:"flex",alignItems:"center",justifyContent:"center",color:"white",fontSize:o?14:18,fontWeight:"bold"},children:o?i:l}),c?t.jsx("div",{style:{padding:16,textAlign:"center"},children:t.jsx(a.Spin,{size:"small"})}):t.jsx(a.Menu,{theme:"dark",mode:"inline",selectedKeys:[y.pathname],items:n,onClick:b})]}),t.jsxs(a.Layout,{children:[t.jsxs(ae,{style:{padding:"0 16px",background:"#fff",display:"flex",alignItems:"center",justifyContent:"space-between"},children:[t.jsx("span",{onClick:()=>h(!o),style:{fontSize:18,cursor:"pointer"},children:o?t.jsx(E.MenuUnfoldOutlined,{}):t.jsx(E.MenuFoldOutlined,{})}),m]}),t.jsx(le,{style:{margin:16,padding:24,background:"#fff",minHeight:280},children:t.jsx(w.Outlet,{})})]})]})}const O={1:{color:"green",text:"正常"},0:{color:"red",text:"禁用"},pending:{color:"orange",text:"待处理"},approved:{color:"blue",text:"已通过"},rejected:{color:"red",text:"已拒绝"}};function D({status:n,map:l}){const i=(l==null?void 0:l[n])||O[n]||{color:"default",text:String(n)};return t.jsx(a.Tag,{color:i.color,children:i.text})}function ue(n){if(n&&typeof n=="object"&&"code"in n&&"data"in n){const l=n;return l.code!==200?Promise.reject(new Error(l.message||"请求失败")):l.data}return n}function U(n,l={}){const{tokenKey:i="token",userInfoKey:m="userInfo",unauthorizedRedirect:c="/login"}=l;n.interceptors.request.use(o=>{const h=localStorage.getItem(i);return h&&(o.headers.Authorization=`Bearer ${h}`),o}),n.interceptors.response.use(o=>ue(o.data),o=>{var h,R,y;return((h=o.response)==null?void 0:h.status)===401?(localStorage.removeItem(i),localStorage.removeItem(m),window.location.href=c):((R=o.response)==null?void 0:R.status)===403?console.error("没有权限访问该资源"):((y=o.response)==null?void 0:y.status)===500&&console.error("服务器内部错误"),Promise.reject(o)})}function k(n={}){const l=te.create({baseURL:n.baseURL??"/api",timeout:n.timeout??1e4});return U(l,{tokenKey:n.tokenKey,userInfoKey:n.userInfoKey,unauthorizedRedirect:n.unauthorizedRedirect}),l}const A=k(),B=k();function W({title:n="统一管理平台",loginApi:l="/auth/login",redirectUrl:i="/",onSuccess:m}){const[c,o]=p.useState(!1),h=async R=>{var y,b;o(!0);try{const g=await A.post(l,R);g.token?(localStorage.setItem("token",g.token),localStorage.setItem("userInfo",JSON.stringify(g.user||g)),a.message.success("登录成功"),m?m(g):window.location.href=i):a.message.error("登录失败:未获取到 token")}catch(g){a.message.error(((b=(y=g.response)==null?void 0:y.data)==null?void 0:b.message)||g.message||"登录失败")}finally{o(!1)}};return t.jsx("div",{style:{height:"100vh",display:"flex",justifyContent:"center",alignItems:"center",background:"#f0f2f5"},children:t.jsx(a.Card,{title:n,style:{width:400},children:t.jsxs(a.Form,{onFinish:h,size:"large",children:[t.jsx(a.Form.Item,{name:"username",rules:[{required:!0,message:"请输入用户名"}],children:t.jsx(a.Input,{prefix:t.jsx(E.UserOutlined,{}),placeholder:"用户名"})}),t.jsx(a.Form.Item,{name:"password",rules:[{required:!0,message:"请输入密码"}],children:t.jsx(a.Input.Password,{prefix:t.jsx(E.LockOutlined,{}),placeholder:"密码"})}),t.jsx(a.Form.Item,{children:t.jsx(a.Button,{type:"primary",htmlType:"submit",loading:c,block:!0,children:"登 录"})})]})})})}class $ extends p.Component{constructor(i){super(i);re(this,"handleReset",()=>{this.setState({hasError:!1,error:null,errorInfo:null}),typeof this.props.onReset=="function"&&this.props.onReset()});this.state={hasError:!1,error:null,errorInfo:null}}static getDerivedStateFromError(i){return{hasError:!0,error:i}}componentDidCatch(i,m){console.error("[ErrorBoundary] caught error:",i,m),this.setState({errorInfo:m})}render(){if(!this.state.hasError)return this.props.children;const{fallbackTitle:i="组件加载失败",fallbackDescription:m,showReload:c=!0}=this.props,o=this.state.error&&(this.state.error.message||String(this.state.error))||"未知错误";return t.jsxs("div",{style:{padding:24,background:"#fff2f0",border:"1px solid #ffccc7",borderRadius:8,minHeight:200},children:[t.jsx(a.Alert,{type:"error",showIcon:!0,icon:t.jsx(E.BugOutlined,{}),message:i,description:t.jsxs("div",{children:[t.jsx("div",{style:{marginBottom:8},children:m||"当前组件发生了异常,已被错误边界捕获。其他功能仍可正常使用。"}),t.jsx("div",{style:{fontFamily:"Menlo, Monaco, Consolas, monospace",fontSize:12,color:"#cf1322",background:"#fff",padding:8,borderRadius:4,border:"1px solid #ffccc7",wordBreak:"break-word",whiteSpace:"pre-wrap"},children:o})]})}),c&&t.jsx(a.Space,{style:{marginTop:16},children:t.jsx(a.Button,{icon:t.jsx(E.ReloadOutlined,{}),onClick:this.handleReset,children:"重试"})})]})}}const ce={AppLayout:z,StatusTag:D,defaultStatusMap:O,LoginPage:W,ErrorBoundary:$,request:A,authRequest:B,createRequest:k};s.AppLayout=z,s.ErrorBoundary=$,s.LoginPage=W,s.StatusTag=D,s.authRequest=B,s.createRequest=k,s.default=ce,s.defaultStatusMap=O,s.request=A,s.setupInterceptors=U,Object.defineProperties(s,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}));
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@yuku123/z-frontend-common",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "z-opc 共享前端组件库 - AppLayout / StatusTag / LoginPage / ErrorBoundary + 统一 createRequest 工厂",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"react",
|
|
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/tree/main/z-frontend-common#readme",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "https://github.com/yuku123/z-opc.git",
|
|
22
|
+
"directory": "z-frontend-common"
|
|
23
|
+
},
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/yuku123/z-opc/issues"
|
|
26
|
+
},
|
|
27
|
+
"main": "./dist/z-frontend-common.umd.js",
|
|
28
|
+
"module": "./dist/z-frontend-common.es.js",
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"exports": {
|
|
31
|
+
".": {
|
|
32
|
+
"import": "./dist/z-frontend-common.es.js",
|
|
33
|
+
"require": "./dist/z-frontend-common.umd.js"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"dist",
|
|
38
|
+
"src",
|
|
39
|
+
"README.md"
|
|
40
|
+
],
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public",
|
|
43
|
+
"registry": "https://registry.npmjs.org/"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"dev": "vite --port 3099",
|
|
47
|
+
"build": "vite build",
|
|
48
|
+
"preview": "vite preview"
|
|
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"
|
|
55
|
+
},
|
|
56
|
+
"dependencies": {
|
|
57
|
+
"axios": "^1.16.1"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@vitejs/plugin-react": "^4.2.1",
|
|
61
|
+
"vite": "^6.2.0"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import {Alert, Button, Space} from 'antd'
|
|
3
|
+
import {BugOutlined, ReloadOutlined} from '@ant-design/icons'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* React Error Boundary — 捕获子组件的 render/lifecycle/effect 异常,
|
|
7
|
+
* 用 fallback UI 替代冒泡到整棵 React 树导致整体卸载。
|
|
8
|
+
*
|
|
9
|
+
* 用法:
|
|
10
|
+
* <ErrorBoundary fallbackTitle="流程编辑器加载失败">
|
|
11
|
+
* <WorkflowEditor />
|
|
12
|
+
* </ErrorBoundary>
|
|
13
|
+
*/
|
|
14
|
+
export default class ErrorBoundary extends React.Component {
|
|
15
|
+
constructor(props) {
|
|
16
|
+
super(props)
|
|
17
|
+
this.state = {hasError: false, error: null, errorInfo: null}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
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})
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
handleReset = () => {
|
|
31
|
+
this.setState({hasError: false, error: null, errorInfo: null})
|
|
32
|
+
if (typeof this.props.onReset === 'function') {
|
|
33
|
+
this.props.onReset()
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
render() {
|
|
38
|
+
if (!this.state.hasError) {
|
|
39
|
+
return this.props.children
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const {fallbackTitle = '组件加载失败', fallbackDescription, showReload = true} = this.props
|
|
43
|
+
const errMsg = (this.state.error && (this.state.error.message || String(this.state.error))) || '未知错误'
|
|
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
|
+
}
|
|
82
|
+
/>
|
|
83
|
+
{showReload && (
|
|
84
|
+
<Space style={{marginTop: 16}}>
|
|
85
|
+
<Button icon={<ReloadOutlined/>} onClick={this.handleReset}>
|
|
86
|
+
重试
|
|
87
|
+
</Button>
|
|
88
|
+
</Space>
|
|
89
|
+
)}
|
|
90
|
+
</div>
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
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'
|
|
5
|
+
|
|
6
|
+
const {Header, Sider, Content} = Layout
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 统一布局组件
|
|
10
|
+
* @param {Object} props
|
|
11
|
+
* @param {Array} props.menuItems - antd Menu items 格式
|
|
12
|
+
* @param {string} props.appTitle - 应用标题
|
|
13
|
+
* @param {string} props.appShort - 折叠时标题缩写
|
|
14
|
+
* @param {ReactNode} props.headerExtra - 头部右侧额外内容
|
|
15
|
+
* @param {boolean} props.loading - 是否加载中
|
|
16
|
+
*/
|
|
17
|
+
export default function AppLayout({
|
|
18
|
+
menuItems = [],
|
|
19
|
+
appTitle = 'One Company',
|
|
20
|
+
appShort = 'OC',
|
|
21
|
+
headerExtra,
|
|
22
|
+
loading = false,
|
|
23
|
+
}) {
|
|
24
|
+
const [collapsed, setCollapsed] = useState(false)
|
|
25
|
+
const navigate = useNavigate()
|
|
26
|
+
const location = useLocation()
|
|
27
|
+
|
|
28
|
+
const handleMenuClick = ({key}) => {
|
|
29
|
+
navigate(key)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<Layout style={{minHeight: '100vh'}}>
|
|
34
|
+
<Sider trigger={null} collapsible collapsed={collapsed}>
|
|
35
|
+
<div style={{
|
|
36
|
+
height: 64, display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
37
|
+
color: 'white', fontSize: collapsed ? 14 : 18, fontWeight: 'bold',
|
|
38
|
+
}}>
|
|
39
|
+
{collapsed ? appShort : appTitle}
|
|
40
|
+
</div>
|
|
41
|
+
{loading ? (
|
|
42
|
+
<div style={{padding: 16, textAlign: 'center'}}>
|
|
43
|
+
<Spin size="small"/>
|
|
44
|
+
</div>
|
|
45
|
+
) : (
|
|
46
|
+
<Menu theme="dark" mode="inline"
|
|
47
|
+
selectedKeys={[location.pathname]}
|
|
48
|
+
items={menuItems}
|
|
49
|
+
onClick={handleMenuClick}
|
|
50
|
+
/>
|
|
51
|
+
)}
|
|
52
|
+
</Sider>
|
|
53
|
+
<Layout>
|
|
54
|
+
<Header style={{
|
|
55
|
+
padding: '0 16px', background: '#fff',
|
|
56
|
+
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
|
|
57
|
+
}}>
|
|
58
|
+
<span onClick={() => setCollapsed(!collapsed)} style={{fontSize: 18, cursor: 'pointer'}}>
|
|
59
|
+
{collapsed ? <MenuUnfoldOutlined/> : <MenuFoldOutlined/>}
|
|
60
|
+
</span>
|
|
61
|
+
{headerExtra}
|
|
62
|
+
</Header>
|
|
63
|
+
<Content style={{margin: 16, padding: 24, background: '#fff', minHeight: 280}}>
|
|
64
|
+
<Outlet/>
|
|
65
|
+
</Content>
|
|
66
|
+
</Layout>
|
|
67
|
+
</Layout>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import {useState} from 'react'
|
|
2
|
+
import {Button, Card, Form, Input, message} from 'antd'
|
|
3
|
+
import {LockOutlined, UserOutlined} from '@ant-design/icons'
|
|
4
|
+
import request from '../../utils/request'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 统一登录页面组件
|
|
8
|
+
* @param {Object} props
|
|
9
|
+
* @param {string} props.title - 页面标题
|
|
10
|
+
* @param {string} props.loginApi - 登录 API 路径,默认 /auth/login
|
|
11
|
+
* @param {string} props.redirectUrl - 登录成功后跳转地址,默认 /
|
|
12
|
+
* @param {Function} props.onSuccess - 登录成功回调,覆盖 redirectUrl
|
|
13
|
+
*/
|
|
14
|
+
export default function LoginPage({
|
|
15
|
+
title = '统一管理平台',
|
|
16
|
+
loginApi = '/auth/login',
|
|
17
|
+
redirectUrl = '/',
|
|
18
|
+
onSuccess,
|
|
19
|
+
}) {
|
|
20
|
+
const [loading, setLoading] = useState(false)
|
|
21
|
+
|
|
22
|
+
const handleSubmit = async (values) => {
|
|
23
|
+
setLoading(true)
|
|
24
|
+
try {
|
|
25
|
+
const res = await request.post(loginApi, values)
|
|
26
|
+
if (res.token) {
|
|
27
|
+
localStorage.setItem('token', res.token)
|
|
28
|
+
localStorage.setItem('userInfo', JSON.stringify(res.user || res))
|
|
29
|
+
message.success('登录成功')
|
|
30
|
+
if (onSuccess) {
|
|
31
|
+
onSuccess(res)
|
|
32
|
+
} else {
|
|
33
|
+
window.location.href = redirectUrl
|
|
34
|
+
}
|
|
35
|
+
} else {
|
|
36
|
+
message.error('登录失败:未获取到 token')
|
|
37
|
+
}
|
|
38
|
+
} catch (err) {
|
|
39
|
+
message.error(err.response?.data?.message || err.message || '登录失败')
|
|
40
|
+
} finally {
|
|
41
|
+
setLoading(false)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<div style={{
|
|
47
|
+
height: '100vh', display: 'flex', justifyContent: 'center', alignItems: 'center',
|
|
48
|
+
background: '#f0f2f5',
|
|
49
|
+
}}>
|
|
50
|
+
<Card title={title} style={{width: 400}}>
|
|
51
|
+
<Form onFinish={handleSubmit} size="large">
|
|
52
|
+
<Form.Item name="username" rules={[{required: true, message: '请输入用户名'}]}>
|
|
53
|
+
<Input prefix={<UserOutlined/>} placeholder="用户名"/>
|
|
54
|
+
</Form.Item>
|
|
55
|
+
<Form.Item name="password" rules={[{required: true, message: '请输入密码'}]}>
|
|
56
|
+
<Input.Password prefix={<LockOutlined/>} placeholder="密码"/>
|
|
57
|
+
</Form.Item>
|
|
58
|
+
<Form.Item>
|
|
59
|
+
<Button type="primary" htmlType="submit" loading={loading} block>
|
|
60
|
+
登 录
|
|
61
|
+
</Button>
|
|
62
|
+
</Form.Item>
|
|
63
|
+
</Form>
|
|
64
|
+
</Card>
|
|
65
|
+
</div>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import {Tag} from 'antd'
|
|
2
|
+
|
|
3
|
+
const defaultStatusMap = {
|
|
4
|
+
1: {color: 'green', text: '正常'},
|
|
5
|
+
0: {color: 'red', text: '禁用'},
|
|
6
|
+
pending: {color: 'orange', text: '待处理'},
|
|
7
|
+
approved: {color: 'blue', text: '已通过'},
|
|
8
|
+
rejected: {color: 'red', text: '已拒绝'},
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default function StatusTag({status, map}) {
|
|
12
|
+
const config = map?.[status] || defaultStatusMap[status] || {color: 'default', text: String(status)}
|
|
13
|
+
return <Tag color={config.color}>{config.text}</Tag>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export {defaultStatusMap}
|
package/src/dev.jsx
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* z-frontend-common 开发调试入口
|
|
3
|
+
*/
|
|
4
|
+
import React from 'react'
|
|
5
|
+
import ReactDOM from 'react-dom/client'
|
|
6
|
+
import {LoginPage, StatusTag} from './index'
|
|
7
|
+
|
|
8
|
+
const DevApp = () => {
|
|
9
|
+
return (
|
|
10
|
+
<div style={{padding: 20}}>
|
|
11
|
+
<h1>z-frontend-common Dev</h1>
|
|
12
|
+
|
|
13
|
+
<h2>StatusTag 示例</h2>
|
|
14
|
+
<StatusTag status={1}/>
|
|
15
|
+
<StatusTag status={0}/>
|
|
16
|
+
<StatusTag status="pending"/>
|
|
17
|
+
<StatusTag status="approved"/>
|
|
18
|
+
<StatusTag status="rejected"/>
|
|
19
|
+
|
|
20
|
+
<h2>LoginPage 示例</h2>
|
|
21
|
+
<div style={{height: 400, border: '1px solid #ccc', borderRadius: 8, overflow: 'hidden'}}>
|
|
22
|
+
<LoginPage title="测试登录"/>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
ReactDOM.createRoot(document.getElementById('root')).render(<DevApp/>)
|
package/src/index.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* z-frontend-common 组件库 - 导出所有可复用组件
|
|
3
|
+
*
|
|
4
|
+
* 命名导出 + 默认导出双形式:
|
|
5
|
+
* import { AppLayout, LoginPage, request, createRequest } from '@yuku123/z-frontend-common'
|
|
6
|
+
* import common from '@yuku123/z-frontend-common'
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// 组件
|
|
10
|
+
export {default as AppLayout} from './components/Layout'
|
|
11
|
+
export {default as StatusTag, defaultStatusMap} from './components/StatusTag'
|
|
12
|
+
export {default as LoginPage} from './components/LoginPage'
|
|
13
|
+
export {default as ErrorBoundary} from './components/ErrorBoundary'
|
|
14
|
+
|
|
15
|
+
// 请求工具:默认实例(baseURL=/api)+ 工厂
|
|
16
|
+
export {default as request} from './utils/request'
|
|
17
|
+
export {authRequest, createRequest, setupInterceptors} from './utils/request'
|
|
18
|
+
|
|
19
|
+
// 默认聚合对象
|
|
20
|
+
import _AppLayout from './components/Layout'
|
|
21
|
+
import _StatusTag, {defaultStatusMap as _defaultStatusMap} from './components/StatusTag'
|
|
22
|
+
import _LoginPage from './components/LoginPage'
|
|
23
|
+
import _ErrorBoundary from './components/ErrorBoundary'
|
|
24
|
+
import _request, {authRequest as _authRequest, createRequest as _createRequest} from './utils/request'
|
|
25
|
+
|
|
26
|
+
const common = {
|
|
27
|
+
AppLayout: _AppLayout,
|
|
28
|
+
StatusTag: _StatusTag,
|
|
29
|
+
defaultStatusMap: _defaultStatusMap,
|
|
30
|
+
LoginPage: _LoginPage,
|
|
31
|
+
ErrorBoundary: _ErrorBoundary,
|
|
32
|
+
request: _request,
|
|
33
|
+
authRequest: _authRequest,
|
|
34
|
+
createRequest: _createRequest,
|
|
35
|
+
}
|
|
36
|
+
export default common
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import axios, {AxiosInstance, AxiosResponse} from 'axios'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 业务响应解包:识别后端约定的 {code, data, message} 格式。
|
|
5
|
+
*
|
|
6
|
+
* 标准(所有后端应遵守):
|
|
7
|
+
* - 成功:{code: 200, data: T, message: 'ok'} → 返回 data
|
|
8
|
+
* - 失败:{code: 4xx/5xx, data: null, message: '...'} → reject(new Error(message))
|
|
9
|
+
*
|
|
10
|
+
* 容错:响应不含 `code` 字段(旧的 Map / ResponseEntity 直返),按 pass-through 处理,
|
|
11
|
+
* 业务侧自行读取 res.data / res.success。
|
|
12
|
+
*/
|
|
13
|
+
function defaultUnwrap(data: unknown): unknown {
|
|
14
|
+
if (data && typeof data === 'object' && 'code' in data && 'data' in data) {
|
|
15
|
+
const wrapped = data as {code: number | string; data: unknown; message?: string}
|
|
16
|
+
if (wrapped.code !== 200) {
|
|
17
|
+
return Promise.reject(new Error(wrapped.message || '请求失败'))
|
|
18
|
+
}
|
|
19
|
+
return wrapped.data
|
|
20
|
+
}
|
|
21
|
+
return data
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 请求拦截器配置
|
|
26
|
+
* @param options.tokenKey 自定义 token 键名,默认 'token'
|
|
27
|
+
* @param options.userInfoKey 自定义 userInfo 键名,默认 'userInfo'
|
|
28
|
+
* @param options.unauthorizedRedirect 401 时跳转的地址,默认 '/login'
|
|
29
|
+
*/
|
|
30
|
+
export function setupInterceptors(
|
|
31
|
+
instance: AxiosInstance,
|
|
32
|
+
options: {
|
|
33
|
+
tokenKey?: string
|
|
34
|
+
userInfoKey?: string
|
|
35
|
+
unauthorizedRedirect?: string
|
|
36
|
+
} = {}
|
|
37
|
+
) {
|
|
38
|
+
const {
|
|
39
|
+
tokenKey = 'token',
|
|
40
|
+
userInfoKey = 'userInfo',
|
|
41
|
+
unauthorizedRedirect = '/login',
|
|
42
|
+
} = options
|
|
43
|
+
|
|
44
|
+
instance.interceptors.request.use((config) => {
|
|
45
|
+
const token = localStorage.getItem(tokenKey)
|
|
46
|
+
if (token) {
|
|
47
|
+
config.headers.Authorization = `Bearer ${token}`
|
|
48
|
+
}
|
|
49
|
+
return config
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
instance.interceptors.response.use(
|
|
53
|
+
(response: AxiosResponse) => defaultUnwrap(response.data),
|
|
54
|
+
(error) => {
|
|
55
|
+
if (error.response?.status === 401) {
|
|
56
|
+
localStorage.removeItem(tokenKey)
|
|
57
|
+
localStorage.removeItem(userInfoKey)
|
|
58
|
+
window.location.href = unauthorizedRedirect
|
|
59
|
+
} else if (error.response?.status === 403) {
|
|
60
|
+
console.error('没有权限访问该资源')
|
|
61
|
+
} else if (error.response?.status === 500) {
|
|
62
|
+
console.error('服务器内部错误')
|
|
63
|
+
}
|
|
64
|
+
return Promise.reject(error)
|
|
65
|
+
}
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 创建请求实例。所有实例共用同一种响应解包策略(见 defaultUnwrap 注释)。
|
|
71
|
+
*
|
|
72
|
+
* @param options.baseURL 接口前缀,默认 '/api'
|
|
73
|
+
* @param options.timeout 超时毫秒,默认 10000
|
|
74
|
+
* @param options.tokenKey token localStorage key,默认 'token'
|
|
75
|
+
* @param options.userInfoKey userInfo localStorage key,默认 'userInfo'
|
|
76
|
+
* @param options.unauthorizedRedirect 401 跳转地址,默认 '/login'
|
|
77
|
+
*/
|
|
78
|
+
export function createRequest(options: {
|
|
79
|
+
baseURL?: string
|
|
80
|
+
timeout?: number
|
|
81
|
+
tokenKey?: string
|
|
82
|
+
userInfoKey?: string
|
|
83
|
+
unauthorizedRedirect?: string
|
|
84
|
+
} = {}): AxiosInstance {
|
|
85
|
+
const instance = axios.create({
|
|
86
|
+
baseURL: options.baseURL ?? '/api',
|
|
87
|
+
timeout: options.timeout ?? 10000,
|
|
88
|
+
})
|
|
89
|
+
setupInterceptors(instance, {
|
|
90
|
+
tokenKey: options.tokenKey,
|
|
91
|
+
userInfoKey: options.userInfoKey,
|
|
92
|
+
unauthorizedRedirect: options.unauthorizedRedirect,
|
|
93
|
+
})
|
|
94
|
+
return instance
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// 默认实例:用于主应用,baseURL = /api
|
|
98
|
+
const request = createRequest()
|
|
99
|
+
const authRequest = createRequest()
|
|
100
|
+
|
|
101
|
+
export {authRequest}
|
|
102
|
+
export default request
|