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.
Files changed (127) hide show
  1. package/dist/cli/index.js +499 -100
  2. package/dist/cli/index.js.map +1 -1
  3. package/package.json +2 -2
  4. package/template/CLAUDE.md +18 -6
  5. package/template/drizzle/0004_add_merchants_products.sql +30 -0
  6. package/template/drizzle/meta/_journal.json +7 -0
  7. package/template/eslint-rules/module-boundary.js +6 -2
  8. package/template/eslint-rules/no-cross-module-service-import.js +105 -0
  9. package/template/eslint-rules/no-disable-type-safe-client.js +5 -0
  10. package/template/eslint.config.js +9 -0
  11. package/template/lint-scripts/config/project.config.ts +21 -0
  12. package/template/lint-scripts/validate-all.ts +48 -12
  13. package/template/lint-scripts/validators/index.ts +29 -0
  14. package/template/lint-scripts/validators/module-public-api.validator.ts +103 -0
  15. package/template/lint-scripts/validators/schema-uniqueness.validator.ts +147 -0
  16. package/template/modules.config.ts +2 -0
  17. package/template/package.json +2 -0
  18. package/template/playwright.config.ts +14 -0
  19. package/template/src/admin/App.tsx +40 -3
  20. package/template/src/admin/components/LanguageSwitcher.tsx +22 -0
  21. package/template/src/admin/components/NotificationDrawer.tsx +108 -38
  22. package/template/src/admin/components/StatsCard.tsx +3 -1
  23. package/template/src/admin/components/ThemeToggle.tsx +28 -0
  24. package/template/src/admin/hooks/useRoles.ts +2 -2
  25. package/template/src/admin/i18n/index.ts +18 -0
  26. package/template/src/admin/i18n/locales/en-US.json +381 -0
  27. package/template/src/admin/i18n/locales/zh-CN.json +381 -0
  28. package/template/src/admin/i18n/useLanguage.ts +21 -0
  29. package/template/src/admin/layouts/Header.tsx +37 -16
  30. package/template/src/admin/layouts/Layout.tsx +7 -3
  31. package/template/src/admin/layouts/Sidebar.tsx +70 -68
  32. package/template/src/admin/main.tsx +1 -0
  33. package/template/src/admin/pages/ContentPage.tsx +68 -56
  34. package/template/src/admin/pages/DashboardPage.tsx +82 -76
  35. package/template/src/admin/pages/DisputesPage.tsx +61 -53
  36. package/template/src/admin/pages/LoginPage.tsx +41 -33
  37. package/template/src/admin/pages/OrdersPage.tsx +69 -64
  38. package/template/src/admin/pages/PermissionsPage.tsx +10 -8
  39. package/template/src/admin/pages/PluginDashboardPage.tsx +3 -1
  40. package/template/src/admin/pages/PluginManagementPage.tsx +3 -1
  41. package/template/src/admin/pages/RegisterPage.tsx +18 -16
  42. package/template/src/admin/pages/RolesPage.tsx +51 -49
  43. package/template/src/admin/pages/SettingsPage.tsx +22 -22
  44. package/template/src/admin/pages/SystemLogsPage.tsx +32 -30
  45. package/template/src/admin/pages/TicketsPage.tsx +67 -59
  46. package/template/src/admin/pages/UsersPage.tsx +63 -53
  47. package/template/src/admin/pages/__tests__/RolesPage.test.tsx +2 -2
  48. package/template/src/admin/stores/themeStore.ts +32 -0
  49. package/template/src/client/index.css +73 -0
  50. package/template/src/merchant/App.tsx +2 -0
  51. package/template/src/merchant/components/MerchantGuard.tsx +2 -6
  52. package/template/src/merchant/components/__tests__/MerchantGuard.test.tsx +117 -0
  53. package/template/src/merchant/layouts/Header.tsx +2 -2
  54. package/template/src/merchant/pages/DashboardPage.tsx +2 -2
  55. package/template/src/merchant/pages/DisputesPage.tsx +14 -10
  56. package/template/src/merchant/pages/LoginPage.tsx +49 -0
  57. package/template/src/merchant/pages/OrdersPage.tsx +17 -10
  58. package/template/src/merchant/pages/ProductsPage.tsx +31 -8
  59. package/template/src/merchant/pages/SettingsPage.tsx +3 -7
  60. package/template/src/merchant/stores/__tests__/merchantStore.test.ts +317 -0
  61. package/template/src/merchant/stores/merchantStore.ts +24 -27
  62. package/template/src/server/db/schema/index.ts +2 -0
  63. package/template/src/server/db/schema/merchants.ts +26 -0
  64. package/template/src/server/db/schema/products.ts +25 -0
  65. package/template/src/server/db/test-setup.ts +237 -0
  66. package/template/src/server/middleware/__tests__/audit-log.test.ts +144 -0
  67. package/template/src/server/middleware/__tests__/cors.test.ts +84 -0
  68. package/template/src/server/middleware/__tests__/logger.test.ts +117 -0
  69. package/template/src/server/middleware/__tests__/rate-limit.test.ts +73 -0
  70. package/template/src/server/middleware/__tests__/realtime-env.test.ts +56 -0
  71. package/template/src/server/middleware/__tests__/tenant-isolation.test.ts +150 -0
  72. package/template/src/server/module-admin/__tests__/admin-routes.test.ts +1 -1
  73. package/template/src/server/module-admin/module.ts +4 -0
  74. package/template/src/server/module-admin/routes/admin-notification-routes.ts +3 -3
  75. package/template/src/server/module-admin/routes/user-management-routes.ts +2 -2
  76. package/template/src/server/module-auth/__tests__/auth-routes.test.ts +315 -0
  77. package/template/src/server/module-auth/__tests__/profile-routes.test.ts +83 -0
  78. package/template/src/server/module-auth/module.ts +2 -0
  79. package/template/src/server/module-content/module.ts +1 -0
  80. package/template/src/server/module-content/routes/content-routes.ts +2 -2
  81. package/template/src/server/module-dispute/module.ts +1 -0
  82. package/template/src/server/module-dispute/routes/dispute-routes.ts +2 -2
  83. package/template/src/server/module-merchant/__tests__/merchant-routes.test.ts +218 -0
  84. package/template/src/server/module-merchant/__tests__/merchant-service.test.ts +183 -0
  85. package/template/src/server/module-merchant/index.ts +10 -0
  86. package/template/src/server/module-merchant/module.ts +33 -0
  87. package/template/src/server/module-merchant/routes/merchant-routes.ts +138 -0
  88. package/template/src/server/module-merchant/services/merchant-service.ts +292 -0
  89. package/template/src/server/module-notifications/index.ts +18 -0
  90. package/template/src/server/module-notifications/module.ts +2 -0
  91. package/template/src/server/module-order/module.ts +1 -0
  92. package/template/src/server/module-order/routes/order-routes.ts +2 -2
  93. package/template/src/server/module-permission/routes/role-routes.ts +3 -3
  94. package/template/src/server/module-plugin/__tests__/admin-category-service.test.ts +181 -0
  95. package/template/src/server/module-plugin/__tests__/admin-plugin-service.test.ts +277 -0
  96. package/template/src/server/module-plugin/__tests__/admin-stats-service.test.ts +170 -0
  97. package/template/src/server/module-plugin/__tests__/plugin-review-service.test.ts +260 -0
  98. package/template/src/server/module-plugin/__tests__/plugin-seed-service.test.ts +170 -0
  99. package/template/src/server/module-plugin/module.ts +3 -0
  100. package/template/src/server/module-tenant/__tests__/tenant-routes.test.ts +142 -0
  101. package/template/src/server/module-tenant/__tests__/tenant-service.test.ts +152 -0
  102. package/template/src/server/module-tenant/module.ts +1 -0
  103. package/template/src/server/module-tenant/services/tenant-service.ts +1 -37
  104. package/template/src/server/module-ticket/module.ts +1 -0
  105. package/template/src/server/module-ticket/routes/ticket-routes.ts +2 -2
  106. package/template/src/server/module-todos/module.ts +3 -0
  107. package/template/src/server/route-registry.ts +4 -0
  108. package/template/src/server/utils/__tests__/date.test.ts +130 -0
  109. package/template/src/server/utils/__tests__/env.test.ts +25 -0
  110. package/template/src/server/utils/__tests__/generate.test.ts +82 -0
  111. package/template/src/server/utils/__tests__/id-helpers.test.ts +29 -0
  112. package/template/src/server/utils/__tests__/json.test.ts +49 -0
  113. package/template/src/server/utils/__tests__/permission-utils.test.ts +26 -0
  114. package/template/src/server/utils/__tests__/uuid.test.ts +25 -0
  115. package/template/src/shared/core/module-manifest.ts +15 -0
  116. package/template/src/shared/modules/admin/schemas.ts +1 -1
  117. package/template/src/shared/modules/content/schemas.ts +2 -2
  118. package/template/src/shared/modules/dispute/schemas.ts +2 -2
  119. package/template/src/shared/modules/index.ts +181 -1
  120. package/template/src/shared/modules/merchant/index.ts +12 -0
  121. package/template/src/shared/modules/merchant/schemas.ts +63 -0
  122. package/template/src/shared/modules/order/schemas.ts +2 -2
  123. package/template/src/shared/modules/permission/index.ts +1 -1
  124. package/template/src/shared/modules/permission/schemas.ts +1 -1
  125. package/template/src/shared/modules/role/schemas.ts +2 -2
  126. package/template/src/shared/modules/ticket/schemas.ts +2 -2
  127. 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 TYPE_LABELS = {
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 || 'Failed to load disputes')
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>确定要解决这个争议吗?</p>
64
- <p>金额:¥{dispute.amount.toFixed(2)}</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: keyof typeof TYPE_LABELS) => TYPE_LABELS[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: keyof typeof STATUS_COLORS) => (
134
- <Tag color={STATUS_COLORS[status]}>{STATUS_LABELS[status]}</Tag>
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) => new Date(date).toLocaleString('zh-CN'),
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">争议处理</h1>
178
- <p className="text-gray-600 mt-1">处理订单争议和客户投诉</p>
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}>刷新</Button>
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">总争议</div>
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">待处理</div>
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">已解决</div>
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="订单号">{selectedDispute.orderNo}</Descriptions.Item>
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="类型">{TYPE_LABELS[selectedDispute.type]}</Descriptions.Item>
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
- {STATUS_LABELS[selectedDispute.status]}
259
+ {getStatusLabel(selectedDispute.status)}
252
260
  </Tag>
253
261
  </Descriptions.Item>
254
- <Descriptions.Item label="描述">{selectedDispute.description}</Descriptions.Item>
262
+ <Descriptions.Item label={t('tickets.description')}>{selectedDispute.description}</Descriptions.Item>
255
263
  {selectedDispute.resolution && (
256
- <Descriptions.Item label="解决方案">{selectedDispute.resolution}</Descriptions.Item>
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
- label: string
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(`已以${account.label}身份登录!`)
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">还没有账号? </span>
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>快速登录</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.label}</span>
121
+ <span>{t(account.labelKey)}</span>
114
122
  <span className="text-gray-400 text-sm">{account.username}</span>
115
123
  </Button>
116
124
  ))}