create-fullstack-scaffold 0.4.9 → 0.4.10
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/dist/cli/index.js +499 -100
- package/dist/cli/index.js.map +1 -1
- package/package.json +2 -2
- package/template/CLAUDE.md +18 -6
- package/template/drizzle/0004_add_merchants_products.sql +30 -0
- package/template/drizzle/meta/_journal.json +7 -0
- package/template/eslint-rules/module-boundary.js +6 -2
- package/template/eslint-rules/no-cross-module-service-import.js +105 -0
- package/template/eslint-rules/no-disable-type-safe-client.js +5 -0
- package/template/eslint.config.js +9 -0
- package/template/lint-scripts/config/project.config.ts +21 -0
- package/template/lint-scripts/validate-all.ts +48 -12
- package/template/lint-scripts/validators/index.ts +29 -0
- package/template/lint-scripts/validators/module-public-api.validator.ts +103 -0
- package/template/lint-scripts/validators/schema-uniqueness.validator.ts +147 -0
- package/template/modules.config.ts +2 -0
- package/template/package.json +2 -0
- package/template/playwright.config.ts +14 -0
- package/template/src/admin/App.tsx +40 -3
- package/template/src/admin/components/LanguageSwitcher.tsx +22 -0
- package/template/src/admin/components/NotificationDrawer.tsx +108 -38
- package/template/src/admin/components/StatsCard.tsx +3 -1
- package/template/src/admin/components/ThemeToggle.tsx +28 -0
- package/template/src/admin/hooks/useRoles.ts +2 -2
- package/template/src/admin/i18n/index.ts +18 -0
- package/template/src/admin/i18n/locales/en-US.json +381 -0
- package/template/src/admin/i18n/locales/zh-CN.json +381 -0
- package/template/src/admin/i18n/useLanguage.ts +21 -0
- package/template/src/admin/layouts/Header.tsx +37 -16
- package/template/src/admin/layouts/Layout.tsx +7 -3
- package/template/src/admin/layouts/Sidebar.tsx +70 -68
- package/template/src/admin/main.tsx +1 -0
- package/template/src/admin/pages/ContentPage.tsx +68 -56
- package/template/src/admin/pages/DashboardPage.tsx +82 -76
- package/template/src/admin/pages/DisputesPage.tsx +61 -53
- package/template/src/admin/pages/LoginPage.tsx +41 -33
- package/template/src/admin/pages/OrdersPage.tsx +69 -64
- package/template/src/admin/pages/PermissionsPage.tsx +10 -8
- package/template/src/admin/pages/PluginDashboardPage.tsx +3 -1
- package/template/src/admin/pages/PluginManagementPage.tsx +3 -1
- package/template/src/admin/pages/RegisterPage.tsx +18 -16
- package/template/src/admin/pages/RolesPage.tsx +51 -49
- package/template/src/admin/pages/SettingsPage.tsx +22 -22
- package/template/src/admin/pages/SystemLogsPage.tsx +32 -30
- package/template/src/admin/pages/TicketsPage.tsx +67 -59
- package/template/src/admin/pages/UsersPage.tsx +63 -53
- package/template/src/admin/pages/__tests__/RolesPage.test.tsx +2 -2
- package/template/src/admin/stores/themeStore.ts +32 -0
- package/template/src/client/index.css +73 -0
- package/template/src/merchant/App.tsx +2 -0
- package/template/src/merchant/components/MerchantGuard.tsx +2 -6
- package/template/src/merchant/components/__tests__/MerchantGuard.test.tsx +117 -0
- package/template/src/merchant/layouts/Header.tsx +2 -2
- package/template/src/merchant/pages/DashboardPage.tsx +2 -2
- package/template/src/merchant/pages/DisputesPage.tsx +14 -10
- package/template/src/merchant/pages/LoginPage.tsx +49 -0
- package/template/src/merchant/pages/OrdersPage.tsx +17 -10
- package/template/src/merchant/pages/ProductsPage.tsx +31 -8
- package/template/src/merchant/pages/SettingsPage.tsx +3 -7
- package/template/src/merchant/stores/__tests__/merchantStore.test.ts +317 -0
- package/template/src/merchant/stores/merchantStore.ts +24 -27
- package/template/src/server/db/schema/index.ts +2 -0
- package/template/src/server/db/schema/merchants.ts +26 -0
- package/template/src/server/db/schema/products.ts +25 -0
- package/template/src/server/db/test-setup.ts +237 -0
- package/template/src/server/middleware/__tests__/audit-log.test.ts +144 -0
- package/template/src/server/middleware/__tests__/cors.test.ts +84 -0
- package/template/src/server/middleware/__tests__/logger.test.ts +117 -0
- package/template/src/server/middleware/__tests__/rate-limit.test.ts +73 -0
- package/template/src/server/middleware/__tests__/realtime-env.test.ts +56 -0
- package/template/src/server/middleware/__tests__/tenant-isolation.test.ts +150 -0
- package/template/src/server/module-admin/__tests__/admin-routes.test.ts +1 -1
- package/template/src/server/module-admin/module.ts +4 -0
- package/template/src/server/module-admin/routes/admin-notification-routes.ts +3 -3
- package/template/src/server/module-admin/routes/user-management-routes.ts +2 -2
- package/template/src/server/module-auth/__tests__/auth-routes.test.ts +315 -0
- package/template/src/server/module-auth/__tests__/profile-routes.test.ts +83 -0
- package/template/src/server/module-auth/module.ts +2 -0
- package/template/src/server/module-content/module.ts +1 -0
- package/template/src/server/module-content/routes/content-routes.ts +2 -2
- package/template/src/server/module-dispute/module.ts +1 -0
- package/template/src/server/module-dispute/routes/dispute-routes.ts +2 -2
- package/template/src/server/module-merchant/__tests__/merchant-routes.test.ts +218 -0
- package/template/src/server/module-merchant/__tests__/merchant-service.test.ts +183 -0
- package/template/src/server/module-merchant/index.ts +10 -0
- package/template/src/server/module-merchant/module.ts +33 -0
- package/template/src/server/module-merchant/routes/merchant-routes.ts +138 -0
- package/template/src/server/module-merchant/services/merchant-service.ts +292 -0
- package/template/src/server/module-notifications/index.ts +18 -0
- package/template/src/server/module-notifications/module.ts +2 -0
- package/template/src/server/module-order/module.ts +1 -0
- package/template/src/server/module-order/routes/order-routes.ts +2 -2
- package/template/src/server/module-permission/routes/role-routes.ts +3 -3
- package/template/src/server/module-plugin/__tests__/admin-category-service.test.ts +181 -0
- package/template/src/server/module-plugin/__tests__/admin-plugin-service.test.ts +277 -0
- package/template/src/server/module-plugin/__tests__/admin-stats-service.test.ts +170 -0
- package/template/src/server/module-plugin/__tests__/plugin-review-service.test.ts +260 -0
- package/template/src/server/module-plugin/__tests__/plugin-seed-service.test.ts +170 -0
- package/template/src/server/module-plugin/module.ts +3 -0
- package/template/src/server/module-tenant/__tests__/tenant-routes.test.ts +142 -0
- package/template/src/server/module-tenant/__tests__/tenant-service.test.ts +152 -0
- package/template/src/server/module-tenant/module.ts +1 -0
- package/template/src/server/module-tenant/services/tenant-service.ts +1 -37
- package/template/src/server/module-ticket/module.ts +1 -0
- package/template/src/server/module-ticket/routes/ticket-routes.ts +2 -2
- package/template/src/server/module-todos/module.ts +3 -0
- package/template/src/server/route-registry.ts +4 -0
- package/template/src/server/utils/__tests__/date.test.ts +130 -0
- package/template/src/server/utils/__tests__/env.test.ts +25 -0
- package/template/src/server/utils/__tests__/generate.test.ts +82 -0
- package/template/src/server/utils/__tests__/id-helpers.test.ts +29 -0
- package/template/src/server/utils/__tests__/json.test.ts +49 -0
- package/template/src/server/utils/__tests__/permission-utils.test.ts +26 -0
- package/template/src/server/utils/__tests__/uuid.test.ts +25 -0
- package/template/src/shared/core/module-manifest.ts +15 -0
- package/template/src/shared/modules/admin/schemas.ts +1 -1
- package/template/src/shared/modules/content/schemas.ts +2 -2
- package/template/src/shared/modules/dispute/schemas.ts +2 -2
- package/template/src/shared/modules/index.ts +181 -1
- package/template/src/shared/modules/merchant/index.ts +12 -0
- package/template/src/shared/modules/merchant/schemas.ts +63 -0
- package/template/src/shared/modules/order/schemas.ts +2 -2
- package/template/src/shared/modules/permission/index.ts +1 -1
- package/template/src/shared/modules/permission/schemas.ts +1 -1
- package/template/src/shared/modules/role/schemas.ts +2 -2
- package/template/src/shared/modules/ticket/schemas.ts +2 -2
- package/template/src/shared/schemas/index.ts +74 -2
|
@@ -5,35 +5,43 @@ import { PermissionGuard } from '../components/PermissionGuard'
|
|
|
5
5
|
import { Permission } from '@shared/modules/permission'
|
|
6
6
|
import { apiClient } from '../services/apiClient'
|
|
7
7
|
import type { Dispute } from '@shared/modules/dispute'
|
|
8
|
+
import { useLanguage } from '../i18n/useLanguage'
|
|
8
9
|
|
|
9
|
-
const
|
|
10
|
-
refund: '退款争议',
|
|
11
|
-
product_quality: '商品质量',
|
|
12
|
-
service_quality: '服务质量',
|
|
13
|
-
delivery: '配送问题',
|
|
14
|
-
other: '其他',
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const STATUS_COLORS = {
|
|
10
|
+
const STATUS_COLORS: Record<string, string> = {
|
|
18
11
|
pending: 'orange',
|
|
19
12
|
investigating: 'blue',
|
|
20
13
|
resolved: 'green',
|
|
21
14
|
rejected: 'red',
|
|
22
15
|
}
|
|
23
16
|
|
|
24
|
-
const STATUS_LABELS = {
|
|
25
|
-
pending: '待处理',
|
|
26
|
-
investigating: '调查中',
|
|
27
|
-
resolved: '已解决',
|
|
28
|
-
rejected: '已驳回',
|
|
29
|
-
}
|
|
30
|
-
|
|
31
17
|
export const DisputesPage: React.FC = () => {
|
|
18
|
+
const { t, formatDate } = useLanguage()
|
|
32
19
|
const [disputes, setDisputes] = useState<Dispute[]>([])
|
|
33
20
|
const [loading, setLoading] = useState(false)
|
|
34
21
|
const [selectedDispute, setSelectedDispute] = useState<Dispute | null>(null)
|
|
35
22
|
const [detailVisible, setDetailVisible] = useState(false)
|
|
36
23
|
|
|
24
|
+
const getTypeLabel = (type: string) => {
|
|
25
|
+
const map: Record<string, string> = {
|
|
26
|
+
refund: t('disputes.typeRefund'),
|
|
27
|
+
product_quality: t('disputes.typeProductQuality'),
|
|
28
|
+
service_quality: t('disputes.typeServiceQuality'),
|
|
29
|
+
delivery: t('disputes.typeDelivery'),
|
|
30
|
+
other: t('disputes.typeOther'),
|
|
31
|
+
}
|
|
32
|
+
return map[type] || type
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const getStatusLabel = (status: string) => {
|
|
36
|
+
const map: Record<string, string> = {
|
|
37
|
+
pending: t('disputes.statusPending'),
|
|
38
|
+
investigating: t('disputes.statusInvestigating'),
|
|
39
|
+
resolved: t('disputes.statusResolved'),
|
|
40
|
+
rejected: t('disputes.statusRejected'),
|
|
41
|
+
}
|
|
42
|
+
return map[status] || status
|
|
43
|
+
}
|
|
44
|
+
|
|
37
45
|
useEffect(() => {
|
|
38
46
|
fetchDisputes()
|
|
39
47
|
}, [])
|
|
@@ -46,10 +54,10 @@ export const DisputesPage: React.FC = () => {
|
|
|
46
54
|
if (result.success) {
|
|
47
55
|
setDisputes(result.data)
|
|
48
56
|
} else {
|
|
49
|
-
message.error(result.error || '
|
|
57
|
+
message.error(result.error || t('disputes.loadFailed'))
|
|
50
58
|
}
|
|
51
59
|
} catch {
|
|
52
|
-
message.error('
|
|
60
|
+
message.error(t('disputes.loadFailed'))
|
|
53
61
|
} finally {
|
|
54
62
|
setLoading(false)
|
|
55
63
|
}
|
|
@@ -57,11 +65,11 @@ export const DisputesPage: React.FC = () => {
|
|
|
57
65
|
|
|
58
66
|
const handleResolve = (dispute: Dispute) => {
|
|
59
67
|
Modal.confirm({
|
|
60
|
-
title: '
|
|
68
|
+
title: t('disputes.resolveDispute'),
|
|
61
69
|
content: (
|
|
62
70
|
<div>
|
|
63
|
-
<p
|
|
64
|
-
<p
|
|
71
|
+
<p>{t('disputes.confirmResolve')}</p>
|
|
72
|
+
<p>¥{dispute.amount.toFixed(2)}</p>
|
|
65
73
|
</div>
|
|
66
74
|
),
|
|
67
75
|
onOk: async () => {
|
|
@@ -75,11 +83,11 @@ export const DisputesPage: React.FC = () => {
|
|
|
75
83
|
})
|
|
76
84
|
const result = await response.json()
|
|
77
85
|
if (result.success) {
|
|
78
|
-
message.success('
|
|
86
|
+
message.success(t('disputes.disputeResolved'))
|
|
79
87
|
fetchDisputes()
|
|
80
88
|
}
|
|
81
89
|
} catch {
|
|
82
|
-
message.error('
|
|
90
|
+
message.error(t('disputes.resolveFailed'))
|
|
83
91
|
}
|
|
84
92
|
},
|
|
85
93
|
})
|
|
@@ -92,18 +100,18 @@ export const DisputesPage: React.FC = () => {
|
|
|
92
100
|
|
|
93
101
|
const columns = [
|
|
94
102
|
{
|
|
95
|
-
title: '
|
|
103
|
+
title: t('disputes.disputeNo'),
|
|
96
104
|
dataIndex: 'disputeNo',
|
|
97
105
|
key: 'disputeNo',
|
|
98
106
|
render: (text: string) => <code className="text-sm">{text}</code>,
|
|
99
107
|
},
|
|
100
108
|
{
|
|
101
|
-
title: '
|
|
109
|
+
title: t('disputes.orderNo'),
|
|
102
110
|
dataIndex: 'orderNo',
|
|
103
111
|
key: 'orderNo',
|
|
104
112
|
},
|
|
105
113
|
{
|
|
106
|
-
title: '
|
|
114
|
+
title: t('disputes.customer'),
|
|
107
115
|
key: 'customer',
|
|
108
116
|
render: (_: unknown, record: Dispute) => (
|
|
109
117
|
<div>
|
|
@@ -113,13 +121,13 @@ export const DisputesPage: React.FC = () => {
|
|
|
113
121
|
),
|
|
114
122
|
},
|
|
115
123
|
{
|
|
116
|
-
title: '
|
|
124
|
+
title: t('disputes.type'),
|
|
117
125
|
dataIndex: 'type',
|
|
118
126
|
key: 'type',
|
|
119
|
-
render: (type:
|
|
127
|
+
render: (type: string) => getTypeLabel(type),
|
|
120
128
|
},
|
|
121
129
|
{
|
|
122
|
-
title: '
|
|
130
|
+
title: t('disputes.amount'),
|
|
123
131
|
dataIndex: 'amount',
|
|
124
132
|
key: 'amount',
|
|
125
133
|
render: (amount: number) => (
|
|
@@ -127,21 +135,21 @@ export const DisputesPage: React.FC = () => {
|
|
|
127
135
|
),
|
|
128
136
|
},
|
|
129
137
|
{
|
|
130
|
-
title: '
|
|
138
|
+
title: t('common.status'),
|
|
131
139
|
dataIndex: 'status',
|
|
132
140
|
key: 'status',
|
|
133
|
-
render: (status:
|
|
134
|
-
<Tag color={STATUS_COLORS[status]}>{
|
|
141
|
+
render: (status: string) => (
|
|
142
|
+
<Tag color={STATUS_COLORS[status]}>{getStatusLabel(status)}</Tag>
|
|
135
143
|
),
|
|
136
144
|
},
|
|
137
145
|
{
|
|
138
|
-
title: '
|
|
146
|
+
title: t('common.createdAt'),
|
|
139
147
|
dataIndex: 'createdAt',
|
|
140
148
|
key: 'createdAt',
|
|
141
|
-
render: (date: string) =>
|
|
149
|
+
render: (date: string) => formatDate(date),
|
|
142
150
|
},
|
|
143
151
|
{
|
|
144
|
-
title: '
|
|
152
|
+
title: t('common.actions'),
|
|
145
153
|
key: 'actions',
|
|
146
154
|
render: (_: unknown, record: Dispute) => (
|
|
147
155
|
<Space>
|
|
@@ -151,7 +159,7 @@ export const DisputesPage: React.FC = () => {
|
|
|
151
159
|
icon={<Eye className="w-4 h-4" />}
|
|
152
160
|
onClick={() => showDetail(record)}
|
|
153
161
|
>
|
|
154
|
-
|
|
162
|
+
{t('common.view')}
|
|
155
163
|
</Button>
|
|
156
164
|
<PermissionGuard permission={Permission.TICKET_CLOSE}>
|
|
157
165
|
{(record.status === 'pending' || record.status === 'investigating') && (
|
|
@@ -161,7 +169,7 @@ export const DisputesPage: React.FC = () => {
|
|
|
161
169
|
icon={<CheckCircle className="w-4 h-4" />}
|
|
162
170
|
onClick={() => handleResolve(record)}
|
|
163
171
|
>
|
|
164
|
-
|
|
172
|
+
{t('disputes.resolve')}
|
|
165
173
|
</Button>
|
|
166
174
|
)}
|
|
167
175
|
</PermissionGuard>
|
|
@@ -174,17 +182,17 @@ export const DisputesPage: React.FC = () => {
|
|
|
174
182
|
<div>
|
|
175
183
|
<div className="flex justify-between items-center mb-6">
|
|
176
184
|
<div>
|
|
177
|
-
<h1 className="text-2xl font-bold text-gray-900"
|
|
178
|
-
<p className="text-gray-600 mt-1"
|
|
185
|
+
<h1 className="text-2xl font-bold text-gray-900">{t('disputes.title')}</h1>
|
|
186
|
+
<p className="text-gray-600 mt-1">{t('disputes.subtitle')}</p>
|
|
179
187
|
</div>
|
|
180
|
-
<Button onClick={fetchDisputes}
|
|
188
|
+
<Button onClick={fetchDisputes}>{t('common.refresh')}</Button>
|
|
181
189
|
</div>
|
|
182
190
|
|
|
183
191
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
|
184
192
|
<Card>
|
|
185
193
|
<div className="flex items-center justify-between">
|
|
186
194
|
<div>
|
|
187
|
-
<div className="text-sm text-gray-500"
|
|
195
|
+
<div className="text-sm text-gray-500">{t('disputes.totalDisputes')}</div>
|
|
188
196
|
<div className="text-2xl font-bold">{disputes.length}</div>
|
|
189
197
|
</div>
|
|
190
198
|
<AlertTriangle className="w-8 h-8 text-orange-500" />
|
|
@@ -193,7 +201,7 @@ export const DisputesPage: React.FC = () => {
|
|
|
193
201
|
<Card>
|
|
194
202
|
<div className="flex items-center justify-between">
|
|
195
203
|
<div>
|
|
196
|
-
<div className="text-sm text-gray-500"
|
|
204
|
+
<div className="text-sm text-gray-500">{t('disputes.pending')}</div>
|
|
197
205
|
<div className="text-2xl font-bold text-orange-500">
|
|
198
206
|
{disputes.filter(d => d.status === 'pending').length}
|
|
199
207
|
</div>
|
|
@@ -204,7 +212,7 @@ export const DisputesPage: React.FC = () => {
|
|
|
204
212
|
<Card>
|
|
205
213
|
<div className="flex items-center justify-between">
|
|
206
214
|
<div>
|
|
207
|
-
<div className="text-sm text-gray-500"
|
|
215
|
+
<div className="text-sm text-gray-500">{t('disputes.resolved')}</div>
|
|
208
216
|
<div className="text-2xl font-bold text-green-500">
|
|
209
217
|
{disputes.filter(d => d.status === 'resolved').length}
|
|
210
218
|
</div>
|
|
@@ -225,7 +233,7 @@ export const DisputesPage: React.FC = () => {
|
|
|
225
233
|
</Card>
|
|
226
234
|
|
|
227
235
|
<Modal
|
|
228
|
-
title=
|
|
236
|
+
title={t('disputes.detail')}
|
|
229
237
|
open={detailVisible}
|
|
230
238
|
onCancel={() => setDetailVisible(false)}
|
|
231
239
|
footer={null}
|
|
@@ -233,27 +241,27 @@ export const DisputesPage: React.FC = () => {
|
|
|
233
241
|
>
|
|
234
242
|
{selectedDispute && (
|
|
235
243
|
<Descriptions column={1} bordered>
|
|
236
|
-
<Descriptions.Item label=
|
|
244
|
+
<Descriptions.Item label={t('disputes.disputeNo')}>
|
|
237
245
|
<code>{selectedDispute.disputeNo}</code>
|
|
238
246
|
</Descriptions.Item>
|
|
239
|
-
<Descriptions.Item label=
|
|
240
|
-
<Descriptions.Item label=
|
|
247
|
+
<Descriptions.Item label={t('disputes.orderNo')}>{selectedDispute.orderNo}</Descriptions.Item>
|
|
248
|
+
<Descriptions.Item label={t('disputes.customer')}>
|
|
241
249
|
{selectedDispute.customerName} ({selectedDispute.customerEmail})
|
|
242
250
|
</Descriptions.Item>
|
|
243
|
-
<Descriptions.Item label=
|
|
244
|
-
<Descriptions.Item label=
|
|
251
|
+
<Descriptions.Item label={t('disputes.type')}>{getTypeLabel(selectedDispute.type)}</Descriptions.Item>
|
|
252
|
+
<Descriptions.Item label={t('disputes.amount')}>
|
|
245
253
|
<span className="text-lg font-bold text-red-600">
|
|
246
254
|
¥{selectedDispute.amount.toFixed(2)}
|
|
247
255
|
</span>
|
|
248
256
|
</Descriptions.Item>
|
|
249
|
-
<Descriptions.Item label=
|
|
257
|
+
<Descriptions.Item label={t('common.status')}>
|
|
250
258
|
<Tag color={STATUS_COLORS[selectedDispute.status]}>
|
|
251
|
-
{
|
|
259
|
+
{getStatusLabel(selectedDispute.status)}
|
|
252
260
|
</Tag>
|
|
253
261
|
</Descriptions.Item>
|
|
254
|
-
<Descriptions.Item label=
|
|
262
|
+
<Descriptions.Item label={t('tickets.description')}>{selectedDispute.description}</Descriptions.Item>
|
|
255
263
|
{selectedDispute.resolution && (
|
|
256
|
-
<Descriptions.Item label=
|
|
264
|
+
<Descriptions.Item label={t('disputes.resolution')}>{selectedDispute.resolution}</Descriptions.Item>
|
|
257
265
|
)}
|
|
258
266
|
</Descriptions>
|
|
259
267
|
)}
|
|
@@ -4,75 +4,83 @@ import { useNavigate, Link } from 'react-router-dom'
|
|
|
4
4
|
import { useAdminStore } from '../stores/adminStore'
|
|
5
5
|
import { Role } from '@shared/modules/permission'
|
|
6
6
|
import type { LoginRequest } from '@shared/modules/admin'
|
|
7
|
+
import { useLanguage } from '../i18n/useLanguage'
|
|
7
8
|
|
|
8
9
|
interface QuickLoginAccount extends LoginRequest {
|
|
9
10
|
username: string
|
|
10
11
|
password: string
|
|
11
12
|
role: Role
|
|
12
|
-
|
|
13
|
+
labelKey: string
|
|
13
14
|
color: string
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
const QUICK_LOGIN_ACCOUNTS: QuickLoginAccount[] = [
|
|
17
|
-
{
|
|
18
|
-
username: 'superadmin',
|
|
19
|
-
password: '123456',
|
|
20
|
-
role: Role.SUPER_ADMIN,
|
|
21
|
-
label: '超级管理员',
|
|
22
|
-
color: 'red',
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
username: 'customerservice',
|
|
26
|
-
password: '123456',
|
|
27
|
-
role: Role.CUSTOMER_SERVICE,
|
|
28
|
-
label: '客服人员',
|
|
29
|
-
color: 'blue',
|
|
30
|
-
},
|
|
31
|
-
{ username: 'user1', password: '123456', role: Role.USER, label: '普通用户', color: 'green' },
|
|
32
|
-
]
|
|
33
|
-
|
|
34
17
|
export const LoginPage: React.FC = () => {
|
|
18
|
+
const { t } = useLanguage()
|
|
35
19
|
const navigate = useNavigate()
|
|
36
20
|
const [form] = Form.useForm<LoginRequest>()
|
|
37
21
|
const { login, loading } = useAdminStore()
|
|
38
22
|
|
|
23
|
+
const QUICK_LOGIN_ACCOUNTS: QuickLoginAccount[] = [
|
|
24
|
+
{
|
|
25
|
+
username: 'superadmin',
|
|
26
|
+
password: '123456',
|
|
27
|
+
role: Role.SUPER_ADMIN,
|
|
28
|
+
labelKey: 'login.superAdmin',
|
|
29
|
+
color: 'red',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
username: 'customerservice',
|
|
33
|
+
password: '123456',
|
|
34
|
+
role: Role.CUSTOMER_SERVICE,
|
|
35
|
+
labelKey: 'login.customerService',
|
|
36
|
+
color: 'blue',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
username: 'user1',
|
|
40
|
+
password: '123456',
|
|
41
|
+
role: Role.USER,
|
|
42
|
+
labelKey: 'login.normalUser',
|
|
43
|
+
color: 'green',
|
|
44
|
+
},
|
|
45
|
+
]
|
|
46
|
+
|
|
39
47
|
const handleSubmit = async (values: LoginRequest) => {
|
|
40
48
|
try {
|
|
41
49
|
await login(values.username, values.password)
|
|
42
|
-
message.success('
|
|
50
|
+
message.success(t('login.success'))
|
|
43
51
|
navigate('/dashboard')
|
|
44
52
|
} catch (error) {
|
|
45
|
-
message.error((error as Error).message || '
|
|
53
|
+
message.error((error as Error).message || t('login.failed'))
|
|
46
54
|
}
|
|
47
55
|
}
|
|
48
56
|
|
|
49
57
|
const handleQuickLogin = async (account: QuickLoginAccount) => {
|
|
50
58
|
try {
|
|
51
59
|
await login(account.username, account.password)
|
|
52
|
-
message.success(
|
|
60
|
+
message.success(t('login.quickLoginAs', { role: t(account.labelKey) }))
|
|
53
61
|
navigate('/dashboard')
|
|
54
62
|
} catch (error) {
|
|
55
|
-
message.error((error as Error).message || '
|
|
63
|
+
message.error((error as Error).message || t('login.failed'))
|
|
56
64
|
}
|
|
57
65
|
}
|
|
58
66
|
|
|
59
67
|
return (
|
|
60
68
|
<div className="min-h-screen flex items-center justify-center bg-gray-100">
|
|
61
|
-
<Card className="w-full max-w-md" title=
|
|
69
|
+
<Card className="w-full max-w-md" title={t('login.title')}>
|
|
62
70
|
<Spin spinning={loading}>
|
|
63
71
|
<Form form={form} onFinish={handleSubmit} layout="vertical" data-testid="admin-login-form">
|
|
64
|
-
<Form.Item name="username" rules={[{ required: true, message: '
|
|
72
|
+
<Form.Item name="username" rules={[{ required: true, message: t('login.usernameRequired') }]}>
|
|
65
73
|
<Input
|
|
66
74
|
prefix={<UserOutlined />}
|
|
67
|
-
placeholder=
|
|
75
|
+
placeholder={t('login.username')}
|
|
68
76
|
size="large"
|
|
69
77
|
data-testid="admin-login-username"
|
|
70
78
|
/>
|
|
71
79
|
</Form.Item>
|
|
72
|
-
<Form.Item name="password" rules={[{ required: true, message: '
|
|
80
|
+
<Form.Item name="password" rules={[{ required: true, message: t('login.passwordRequired') }]}>
|
|
73
81
|
<Input.Password
|
|
74
82
|
prefix={<LockOutlined />}
|
|
75
|
-
placeholder=
|
|
83
|
+
placeholder={t('login.password')}
|
|
76
84
|
size="large"
|
|
77
85
|
data-testid="admin-login-password"
|
|
78
86
|
/>
|
|
@@ -86,19 +94,19 @@ export const LoginPage: React.FC = () => {
|
|
|
86
94
|
loading={loading}
|
|
87
95
|
data-testid="admin-login-submit"
|
|
88
96
|
>
|
|
89
|
-
|
|
97
|
+
{t('login.submit')}
|
|
90
98
|
</Button>
|
|
91
99
|
</Form.Item>
|
|
92
100
|
<div className="text-center">
|
|
93
|
-
<span className="text-gray-500"
|
|
101
|
+
<span className="text-gray-500">{t('login.noAccount')}</span>
|
|
94
102
|
<Link to="/register" className="text-blue-500 hover:underline">
|
|
95
|
-
|
|
103
|
+
{t('login.register')}
|
|
96
104
|
</Link>
|
|
97
105
|
</div>
|
|
98
106
|
</Form>
|
|
99
107
|
</Spin>
|
|
100
108
|
|
|
101
|
-
<Divider
|
|
109
|
+
<Divider>{t('login.quickLogin')}</Divider>
|
|
102
110
|
|
|
103
111
|
<div className="space-y-2">
|
|
104
112
|
{QUICK_LOGIN_ACCOUNTS.map(account => (
|
|
@@ -110,7 +118,7 @@ export const LoginPage: React.FC = () => {
|
|
|
110
118
|
loading={loading}
|
|
111
119
|
className="flex items-center justify-between"
|
|
112
120
|
>
|
|
113
|
-
<span>{account.
|
|
121
|
+
<span>{t(account.labelKey)}</span>
|
|
114
122
|
<span className="text-gray-400 text-sm">{account.username}</span>
|
|
115
123
|
</Button>
|
|
116
124
|
))}
|