openxiangda 1.0.71 → 1.0.73
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/package.json +1 -1
- package/packages/sdk/dist/components/index.cjs +0 -1
- package/packages/sdk/dist/components/index.cjs.map +1 -1
- package/packages/sdk/dist/components/index.mjs +0 -1
- package/packages/sdk/dist/components/index.mjs.map +1 -1
- package/packages/sdk/dist/runtime/index.cjs +0 -1
- package/packages/sdk/dist/runtime/index.cjs.map +1 -1
- package/packages/sdk/dist/runtime/index.mjs +0 -1
- package/packages/sdk/dist/runtime/index.mjs.map +1 -1
- package/packages/sdk/dist/styles/tailwind-preset.cjs +45 -1
- package/packages/sdk/dist/styles/tailwind-preset.cjs.map +1 -1
- package/packages/sdk/dist/styles/tailwind-preset.mjs +45 -1
- package/packages/sdk/dist/styles/tailwind-preset.mjs.map +1 -1
- package/templates/openxiangda-react-spa/AGENTS.md +1 -1
- package/templates/openxiangda-react-spa/src/app/navigation.ts +6 -5
- package/templates/openxiangda-react-spa/src/app/starter-content.ts +1 -0
- package/templates/openxiangda-react-spa/src/layouts/AdminShell.tsx +136 -42
- package/templates/openxiangda-react-spa/src/main.tsx +1 -1
- package/templates/openxiangda-react-spa/src/resources/menus/menus.json +1 -1
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import type { ComponentType } from "react";
|
|
1
|
+
import type { ComponentType, SVGProps } from "react";
|
|
2
2
|
import { Home } from "lucide-react";
|
|
3
3
|
|
|
4
4
|
export type StarterNavigationItem = {
|
|
5
5
|
code?: string;
|
|
6
6
|
hint?: string;
|
|
7
|
-
icon: ComponentType<
|
|
7
|
+
icon: ComponentType<
|
|
8
|
+
SVGProps<SVGSVGElement> & { size?: string | number; strokeWidth?: string | number }
|
|
9
|
+
>;
|
|
8
10
|
name: string;
|
|
9
11
|
path: string;
|
|
10
12
|
routeCode?: string;
|
|
@@ -27,13 +29,12 @@ export function buildStarterAdminNavigation({
|
|
|
27
29
|
}: BuildStarterNavigationOptions): StarterNavigationGroup[] {
|
|
28
30
|
return [
|
|
29
31
|
{
|
|
30
|
-
title: "
|
|
32
|
+
title: "应用工作台",
|
|
31
33
|
items: [
|
|
32
34
|
{
|
|
33
35
|
code: "admin_dashboard",
|
|
34
|
-
hint: "默认首页",
|
|
35
36
|
icon: Home,
|
|
36
|
-
name: "
|
|
37
|
+
name: "工作台",
|
|
37
38
|
path: viewPath(appType, "admin"),
|
|
38
39
|
routeCode: "admin.dashboard",
|
|
39
40
|
},
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ChevronDown,
|
|
3
|
+
ChevronLeft,
|
|
3
4
|
ChevronRight,
|
|
4
5
|
LogOut,
|
|
5
6
|
Menu,
|
|
6
7
|
RefreshCw,
|
|
7
8
|
Shield,
|
|
8
|
-
UserCircle,
|
|
9
9
|
X,
|
|
10
10
|
} from "lucide-react";
|
|
11
11
|
import { useEffect, useMemo, useState } from "react";
|
|
@@ -49,6 +49,7 @@ export function AdminShell() {
|
|
|
49
49
|
const auth = useRuntimeAuth();
|
|
50
50
|
const [collapsedGroups, setCollapsedGroups] = useState<Record<string, boolean>>({});
|
|
51
51
|
const [mobileOpen, setMobileOpen] = useState(false);
|
|
52
|
+
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
|
52
53
|
const [userMenuOpen, setUserMenuOpen] = useState(false);
|
|
53
54
|
const [loggingOut, setLoggingOut] = useState(false);
|
|
54
55
|
|
|
@@ -59,6 +60,19 @@ export function AdminShell() {
|
|
|
59
60
|
bootstrap.data?.user?.id ||
|
|
60
61
|
"当前用户",
|
|
61
62
|
);
|
|
63
|
+
const userAvatar = String(
|
|
64
|
+
bootstrap.data?.user?.avatar ||
|
|
65
|
+
bootstrap.data?.user?.avatarUrl ||
|
|
66
|
+
bootstrap.data?.user?.photoUrl ||
|
|
67
|
+
"",
|
|
68
|
+
);
|
|
69
|
+
const userRole = String(
|
|
70
|
+
bootstrap.data?.user?.roleName ||
|
|
71
|
+
bootstrap.data?.user?.title ||
|
|
72
|
+
bootstrap.data?.user?.position ||
|
|
73
|
+
bootstrap.data?.user?.departmentName ||
|
|
74
|
+
"平台用户",
|
|
75
|
+
);
|
|
62
76
|
|
|
63
77
|
const menuCodes = useMemo(() => collectMenuCodes(menus.data), [menus.data]);
|
|
64
78
|
const groups = useMemo<StarterNavigationGroup[]>(
|
|
@@ -77,47 +91,60 @@ export function AdminShell() {
|
|
|
77
91
|
await auth.logoutAndRedirect({ replace: true });
|
|
78
92
|
};
|
|
79
93
|
|
|
80
|
-
const sidebar = (
|
|
81
|
-
<aside className="flex h-full min-h-0 flex-col border-r border-
|
|
82
|
-
<div className="flex h-
|
|
94
|
+
const sidebar = (compact = false) => (
|
|
95
|
+
<aside className="flex h-full min-h-0 flex-col border-r border-slate-200 bg-white">
|
|
96
|
+
<div className={cn("flex h-[68px] shrink-0 items-center gap-3", compact ? "justify-center px-3" : "px-5")}>
|
|
83
97
|
<Link
|
|
84
|
-
|
|
98
|
+
aria-label={starterBrand.name}
|
|
99
|
+
className="grid h-9 w-9 shrink-0 place-items-center text-blue-600"
|
|
85
100
|
to={`/view/${appType}/admin`}
|
|
86
101
|
>
|
|
87
|
-
|
|
102
|
+
<OpenXiangdaMark />
|
|
88
103
|
</Link>
|
|
89
|
-
<div className="min-w-0">
|
|
90
|
-
<Link className="block truncate text-
|
|
91
|
-
{
|
|
104
|
+
<div className={cn("min-w-0", compact && "hidden")}>
|
|
105
|
+
<Link className="block truncate text-[15px] font-semibold leading-5 text-slate-950" to={`/view/${appType}/admin`}>
|
|
106
|
+
{starterBrand.name}
|
|
92
107
|
</Link>
|
|
93
|
-
<div className="mt-0.5 truncate text-xs text-slate-500">{starterBrand.subtitle}</div>
|
|
108
|
+
<div className="mt-0.5 truncate text-xs leading-4 text-slate-500">{starterBrand.subtitle}</div>
|
|
94
109
|
</div>
|
|
95
110
|
</div>
|
|
96
111
|
|
|
97
|
-
<nav className="ox-scrollbar min-h-0 flex-1
|
|
112
|
+
<nav className={cn("ox-scrollbar min-h-0 flex-1 overflow-y-auto py-5", compact ? "px-2" : "space-y-7 px-3")}>
|
|
98
113
|
{groups.map(group => {
|
|
99
114
|
const collapsed = collapsedGroups[group.title];
|
|
100
115
|
return (
|
|
101
116
|
<section key={group.title}>
|
|
102
117
|
<button
|
|
103
|
-
|
|
118
|
+
aria-label={group.title}
|
|
119
|
+
className={cn(
|
|
120
|
+
"flex h-10 w-full items-center rounded-lg text-[13px] font-semibold text-slate-800 transition hover:bg-slate-50",
|
|
121
|
+
compact ? "justify-center px-0" : "justify-between px-2.5",
|
|
122
|
+
)}
|
|
104
123
|
onClick={() => setCollapsedGroups(prev => ({ ...prev, [group.title]: !collapsed }))}
|
|
105
124
|
type="button"
|
|
106
125
|
>
|
|
107
|
-
<span>{group.title}</span>
|
|
108
|
-
{
|
|
126
|
+
<span className={cn(compact && "sr-only")}>{group.title}</span>
|
|
127
|
+
{compact ? (
|
|
128
|
+
<Menu className="text-slate-500" size={16} strokeWidth={2} />
|
|
129
|
+
) : collapsed ? (
|
|
130
|
+
<ChevronRight className="text-slate-500" size={16} strokeWidth={2} />
|
|
131
|
+
) : (
|
|
132
|
+
<ChevronDown className="text-slate-500" size={16} strokeWidth={2} />
|
|
133
|
+
)}
|
|
109
134
|
</button>
|
|
110
135
|
{!collapsed ? (
|
|
111
|
-
<div className="mt-1 space-y-1
|
|
136
|
+
<div className={cn("mt-1 space-y-1", compact && "mt-2")}>
|
|
112
137
|
{group.items.map(item => {
|
|
113
138
|
const active = isMenuActive(location.pathname, location.search, item.path);
|
|
114
139
|
return (
|
|
115
140
|
<Link
|
|
141
|
+
aria-label={item.name}
|
|
116
142
|
className={cn(
|
|
117
|
-
"group relative flex items-center
|
|
143
|
+
"group relative flex h-10 items-center rounded-lg text-sm transition",
|
|
144
|
+
compact ? "justify-center px-0" : "gap-3 px-3",
|
|
118
145
|
active
|
|
119
|
-
? "bg-
|
|
120
|
-
: "text-slate-600 hover:bg-slate-
|
|
146
|
+
? "bg-[#eef6ff] text-[#1677ff]"
|
|
147
|
+
: "text-slate-600 hover:bg-slate-50 hover:text-slate-900",
|
|
121
148
|
)}
|
|
122
149
|
key={`${group.title}-${item.name}-${item.path}`}
|
|
123
150
|
onClick={() => setMobileOpen(false)}
|
|
@@ -125,24 +152,20 @@ export function AdminShell() {
|
|
|
125
152
|
>
|
|
126
153
|
<span
|
|
127
154
|
className={cn(
|
|
128
|
-
"absolute left-
|
|
129
|
-
active ? "bg-
|
|
155
|
+
"absolute -left-3 top-1 h-8 w-1 rounded-r-full transition",
|
|
156
|
+
active ? "bg-[#1677ff]" : "bg-transparent",
|
|
157
|
+
compact && "-left-2",
|
|
130
158
|
)}
|
|
131
159
|
/>
|
|
132
160
|
<span
|
|
133
161
|
className={cn(
|
|
134
|
-
"grid h-
|
|
135
|
-
active ? "
|
|
162
|
+
"grid h-5 w-5 shrink-0 place-items-center transition",
|
|
163
|
+
active ? "text-[#1677ff]" : "text-slate-500 group-hover:text-slate-700",
|
|
136
164
|
)}
|
|
137
165
|
>
|
|
138
|
-
<item.icon size={
|
|
139
|
-
</span>
|
|
140
|
-
<span className="min-w-0 flex-1">
|
|
141
|
-
<span className="block truncate font-medium">{item.name}</span>
|
|
142
|
-
{item.hint ? (
|
|
143
|
-
<span className="mt-0.5 block truncate text-xs text-slate-400">{item.hint}</span>
|
|
144
|
-
) : null}
|
|
166
|
+
<item.icon size={17} strokeWidth={2} />
|
|
145
167
|
</span>
|
|
168
|
+
<span className={cn("min-w-0 flex-1 truncate font-medium", compact && "sr-only")}>{item.name}</span>
|
|
146
169
|
</Link>
|
|
147
170
|
);
|
|
148
171
|
})}
|
|
@@ -152,6 +175,21 @@ export function AdminShell() {
|
|
|
152
175
|
);
|
|
153
176
|
})}
|
|
154
177
|
</nav>
|
|
178
|
+
<div className="shrink-0 px-3 py-4">
|
|
179
|
+
<button
|
|
180
|
+
className={cn(
|
|
181
|
+
"flex h-10 w-full items-center gap-3 rounded-lg text-sm font-medium text-slate-600 transition hover:bg-slate-50 hover:text-slate-950",
|
|
182
|
+
compact ? "justify-center px-0" : "px-2.5",
|
|
183
|
+
)}
|
|
184
|
+
onClick={() => setSidebarCollapsed(value => !value)}
|
|
185
|
+
type="button"
|
|
186
|
+
>
|
|
187
|
+
<span className="grid h-7 w-7 shrink-0 place-items-center rounded-lg bg-slate-50 text-slate-600 ring-1 ring-slate-200">
|
|
188
|
+
{compact ? <ChevronRight size={17} /> : <ChevronLeft size={17} />}
|
|
189
|
+
</span>
|
|
190
|
+
<span className={cn("truncate", compact && "sr-only")}>{compact ? "展开侧边栏" : "收起侧边栏"}</span>
|
|
191
|
+
</button>
|
|
192
|
+
</div>
|
|
155
193
|
</aside>
|
|
156
194
|
);
|
|
157
195
|
|
|
@@ -164,7 +202,9 @@ export function AdminShell() {
|
|
|
164
202
|
routeCode="admin.dashboard"
|
|
165
203
|
>
|
|
166
204
|
<div className="min-h-screen overflow-x-hidden bg-[linear-gradient(180deg,#edf4ff_0%,#f8fafc_42%,#f1f5f9_100%)] text-slate-950">
|
|
167
|
-
<div className="fixed inset-y-0 left-0 z-30 hidden w-
|
|
205
|
+
<div className={cn("fixed inset-y-0 left-0 z-30 hidden lg:block", sidebarCollapsed ? "w-[76px]" : "w-60")}>
|
|
206
|
+
{sidebar(sidebarCollapsed)}
|
|
207
|
+
</div>
|
|
168
208
|
|
|
169
209
|
{mobileOpen ? (
|
|
170
210
|
<div className="fixed inset-0 z-40 lg:hidden">
|
|
@@ -183,12 +223,12 @@ export function AdminShell() {
|
|
|
183
223
|
>
|
|
184
224
|
<X size={19} />
|
|
185
225
|
</button>
|
|
186
|
-
{sidebar}
|
|
226
|
+
{sidebar(false)}
|
|
187
227
|
</div>
|
|
188
228
|
</div>
|
|
189
229
|
) : null}
|
|
190
230
|
|
|
191
|
-
<main className="min-w-0 lg:pl-
|
|
231
|
+
<main className={cn("min-w-0 transition-[padding] duration-200", sidebarCollapsed ? "lg:pl-[76px]" : "lg:pl-60")}>
|
|
192
232
|
<header className="sticky top-0 z-20 border-b border-white/70 bg-white/[0.8] backdrop-blur-xl">
|
|
193
233
|
<div className="flex h-20 min-w-0 items-center justify-between gap-3 px-4 sm:px-6">
|
|
194
234
|
<div className="flex min-w-0 items-center gap-3">
|
|
@@ -209,25 +249,24 @@ export function AdminShell() {
|
|
|
209
249
|
<div className="flex shrink-0 items-center">
|
|
210
250
|
<div className="relative">
|
|
211
251
|
<button
|
|
212
|
-
className="flex h-
|
|
252
|
+
className="flex h-12 items-center gap-3 rounded-2xl px-2 py-1.5 text-left transition hover:bg-white/80"
|
|
213
253
|
onClick={() => setUserMenuOpen(open => !open)}
|
|
214
254
|
type="button"
|
|
215
255
|
>
|
|
216
|
-
<
|
|
217
|
-
|
|
256
|
+
<UserAvatar name={userName} src={userAvatar} />
|
|
257
|
+
<span className="hidden min-w-0 sm:block">
|
|
258
|
+
<span className="block max-w-28 truncate text-sm font-semibold leading-5 text-slate-950">{userName}</span>
|
|
259
|
+
<span className="block max-w-28 truncate text-xs leading-4 text-slate-500">{userRole}</span>
|
|
218
260
|
</span>
|
|
219
|
-
<
|
|
220
|
-
<ChevronDown size={15} />
|
|
261
|
+
<ChevronDown className="text-slate-500" size={16} />
|
|
221
262
|
</button>
|
|
222
263
|
{userMenuOpen ? (
|
|
223
264
|
<div className="absolute right-0 mt-3 w-72 overflow-hidden rounded-2xl border border-slate-200 bg-white p-2 shadow-[0_24px_70px_rgba(15,23,42,0.16)]">
|
|
224
|
-
<div className="flex gap-3 rounded-xl bg-slate-50 p-3">
|
|
225
|
-
<
|
|
226
|
-
<UserCircle size={21} />
|
|
227
|
-
</div>
|
|
265
|
+
<div className="flex items-center gap-3 rounded-xl bg-slate-50 p-3">
|
|
266
|
+
<UserAvatar name={userName} src={userAvatar} size="lg" />
|
|
228
267
|
<div className="min-w-0">
|
|
229
268
|
<div className="truncate text-sm font-semibold text-slate-950">{userName}</div>
|
|
230
|
-
<div className="mt-
|
|
269
|
+
<div className="mt-0.5 truncate text-xs text-slate-500">{userRole}</div>
|
|
231
270
|
</div>
|
|
232
271
|
</div>
|
|
233
272
|
<button
|
|
@@ -266,6 +305,61 @@ export function AdminShell() {
|
|
|
266
305
|
);
|
|
267
306
|
}
|
|
268
307
|
|
|
308
|
+
function UserAvatar({
|
|
309
|
+
name,
|
|
310
|
+
size = "md",
|
|
311
|
+
src,
|
|
312
|
+
}: {
|
|
313
|
+
name: string;
|
|
314
|
+
size?: "md" | "lg";
|
|
315
|
+
src?: string;
|
|
316
|
+
}) {
|
|
317
|
+
const className = cn(
|
|
318
|
+
"shrink-0 overflow-hidden rounded-full bg-[linear-gradient(135deg,#2563eb_0%,#38bdf8_100%)] text-white shadow-sm ring-2 ring-white",
|
|
319
|
+
size === "lg" ? "h-11 w-11" : "h-9 w-9",
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
if (src) {
|
|
323
|
+
return (
|
|
324
|
+
<img
|
|
325
|
+
alt=""
|
|
326
|
+
className={cn(className, "object-cover")}
|
|
327
|
+
referrerPolicy="no-referrer"
|
|
328
|
+
src={src}
|
|
329
|
+
/>
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return (
|
|
334
|
+
<span className={cn(className, "grid place-items-center text-sm font-semibold")}>
|
|
335
|
+
{name.slice(0, 1).toUpperCase()}
|
|
336
|
+
</span>
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function OpenXiangdaMark() {
|
|
341
|
+
return (
|
|
342
|
+
<svg
|
|
343
|
+
aria-hidden="true"
|
|
344
|
+
className="h-8 w-8"
|
|
345
|
+
fill="none"
|
|
346
|
+
viewBox="0 0 36 36"
|
|
347
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
348
|
+
>
|
|
349
|
+
<path
|
|
350
|
+
d="M18 3.5 32.5 31H26L18 15.7 10 31H3.5L18 3.5Z"
|
|
351
|
+
fill="currentColor"
|
|
352
|
+
/>
|
|
353
|
+
<path d="M18 20.2 23.4 31H12.6L18 20.2Z" fill="white" />
|
|
354
|
+
<path
|
|
355
|
+
d="M18 9.5 8.2 31H3.5L18 3.5l14.5 27.5h-4.7L18 9.5Z"
|
|
356
|
+
fill="currentColor"
|
|
357
|
+
opacity="0.86"
|
|
358
|
+
/>
|
|
359
|
+
</svg>
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
|
|
269
363
|
function AdminPermissionState({ state }: { state: PermissionBoundaryFallbackState }) {
|
|
270
364
|
const auth = useRuntimeAuth();
|
|
271
365
|
|
|
@@ -3,7 +3,7 @@ import ReactDOM from "react-dom/client";
|
|
|
3
3
|
import { RouterProvider } from "react-router-dom";
|
|
4
4
|
|
|
5
5
|
import { router } from "./app/router";
|
|
6
|
-
import "antd-mobile/
|
|
6
|
+
import "antd-mobile/bundle/style.css";
|
|
7
7
|
import "./styles/index.css";
|
|
8
8
|
|
|
9
9
|
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|