create-fullstack-scaffold 0.4.9 → 0.4.11

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 (139) 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/cli/modules/admin/index.ts +200 -0
  50. package/template/src/cli/modules/captcha/index.ts +40 -0
  51. package/template/src/cli/modules/chat/index.ts +21 -0
  52. package/template/src/cli/modules/content/index.ts +144 -0
  53. package/template/src/cli/modules/dispute/index.ts +150 -0
  54. package/template/src/cli/modules/file/index.ts +55 -0
  55. package/template/src/cli/modules/index.ts +30 -5
  56. package/template/src/cli/modules/order/index.ts +170 -0
  57. package/template/src/cli/modules/permission/index.ts +195 -0
  58. package/template/src/cli/modules/tenant/index.ts +142 -0
  59. package/template/src/cli/modules/ticket/index.ts +167 -0
  60. package/template/src/cli/rpc/client.ts +2 -2
  61. package/template/src/client/index.css +73 -0
  62. package/template/src/merchant/App.tsx +2 -0
  63. package/template/src/merchant/components/MerchantGuard.tsx +2 -6
  64. package/template/src/merchant/components/__tests__/MerchantGuard.test.tsx +117 -0
  65. package/template/src/merchant/layouts/Header.tsx +2 -2
  66. package/template/src/merchant/pages/DashboardPage.tsx +2 -2
  67. package/template/src/merchant/pages/DisputesPage.tsx +14 -10
  68. package/template/src/merchant/pages/LoginPage.tsx +49 -0
  69. package/template/src/merchant/pages/OrdersPage.tsx +17 -10
  70. package/template/src/merchant/pages/ProductsPage.tsx +31 -8
  71. package/template/src/merchant/pages/SettingsPage.tsx +3 -7
  72. package/template/src/merchant/stores/__tests__/merchantStore.test.ts +317 -0
  73. package/template/src/merchant/stores/merchantStore.ts +24 -27
  74. package/template/src/server/db/schema/index.ts +2 -0
  75. package/template/src/server/db/schema/merchants.ts +26 -0
  76. package/template/src/server/db/schema/products.ts +25 -0
  77. package/template/src/server/db/test-setup.ts +237 -0
  78. package/template/src/server/middleware/__tests__/audit-log.test.ts +144 -0
  79. package/template/src/server/middleware/__tests__/cors.test.ts +84 -0
  80. package/template/src/server/middleware/__tests__/logger.test.ts +117 -0
  81. package/template/src/server/middleware/__tests__/rate-limit.test.ts +73 -0
  82. package/template/src/server/middleware/__tests__/realtime-env.test.ts +56 -0
  83. package/template/src/server/middleware/__tests__/tenant-isolation.test.ts +150 -0
  84. package/template/src/server/module-admin/__tests__/admin-routes.test.ts +1 -1
  85. package/template/src/server/module-admin/module.ts +4 -0
  86. package/template/src/server/module-admin/routes/admin-notification-routes.ts +3 -3
  87. package/template/src/server/module-admin/routes/user-management-routes.ts +2 -2
  88. package/template/src/server/module-auth/__tests__/auth-routes.test.ts +315 -0
  89. package/template/src/server/module-auth/__tests__/profile-routes.test.ts +83 -0
  90. package/template/src/server/module-auth/module.ts +2 -0
  91. package/template/src/server/module-content/module.ts +1 -0
  92. package/template/src/server/module-content/routes/content-routes.ts +2 -2
  93. package/template/src/server/module-dispute/module.ts +1 -0
  94. package/template/src/server/module-dispute/routes/dispute-routes.ts +2 -2
  95. package/template/src/server/module-merchant/__tests__/merchant-routes.test.ts +218 -0
  96. package/template/src/server/module-merchant/__tests__/merchant-service.test.ts +183 -0
  97. package/template/src/server/module-merchant/index.ts +10 -0
  98. package/template/src/server/module-merchant/module.ts +33 -0
  99. package/template/src/server/module-merchant/routes/merchant-routes.ts +138 -0
  100. package/template/src/server/module-merchant/services/merchant-service.ts +292 -0
  101. package/template/src/server/module-notifications/index.ts +18 -0
  102. package/template/src/server/module-notifications/module.ts +2 -0
  103. package/template/src/server/module-order/module.ts +1 -0
  104. package/template/src/server/module-order/routes/order-routes.ts +2 -2
  105. package/template/src/server/module-permission/routes/role-routes.ts +3 -3
  106. package/template/src/server/module-plugin/__tests__/admin-category-service.test.ts +181 -0
  107. package/template/src/server/module-plugin/__tests__/admin-plugin-service.test.ts +277 -0
  108. package/template/src/server/module-plugin/__tests__/admin-stats-service.test.ts +170 -0
  109. package/template/src/server/module-plugin/__tests__/plugin-review-service.test.ts +260 -0
  110. package/template/src/server/module-plugin/__tests__/plugin-seed-service.test.ts +170 -0
  111. package/template/src/server/module-plugin/module.ts +3 -0
  112. package/template/src/server/module-tenant/__tests__/tenant-routes.test.ts +142 -0
  113. package/template/src/server/module-tenant/__tests__/tenant-service.test.ts +152 -0
  114. package/template/src/server/module-tenant/module.ts +1 -0
  115. package/template/src/server/module-tenant/services/tenant-service.ts +1 -37
  116. package/template/src/server/module-ticket/module.ts +1 -0
  117. package/template/src/server/module-ticket/routes/ticket-routes.ts +2 -2
  118. package/template/src/server/module-todos/module.ts +3 -0
  119. package/template/src/server/route-registry.ts +4 -0
  120. package/template/src/server/utils/__tests__/date.test.ts +130 -0
  121. package/template/src/server/utils/__tests__/env.test.ts +25 -0
  122. package/template/src/server/utils/__tests__/generate.test.ts +82 -0
  123. package/template/src/server/utils/__tests__/id-helpers.test.ts +29 -0
  124. package/template/src/server/utils/__tests__/json.test.ts +49 -0
  125. package/template/src/server/utils/__tests__/permission-utils.test.ts +26 -0
  126. package/template/src/server/utils/__tests__/uuid.test.ts +25 -0
  127. package/template/src/shared/core/module-manifest.ts +15 -0
  128. package/template/src/shared/modules/admin/schemas.ts +1 -1
  129. package/template/src/shared/modules/content/schemas.ts +2 -2
  130. package/template/src/shared/modules/dispute/schemas.ts +2 -2
  131. package/template/src/shared/modules/index.ts +181 -1
  132. package/template/src/shared/modules/merchant/index.ts +12 -0
  133. package/template/src/shared/modules/merchant/schemas.ts +63 -0
  134. package/template/src/shared/modules/order/schemas.ts +2 -2
  135. package/template/src/shared/modules/permission/index.ts +1 -1
  136. package/template/src/shared/modules/permission/schemas.ts +1 -1
  137. package/template/src/shared/modules/role/schemas.ts +2 -2
  138. package/template/src/shared/modules/ticket/schemas.ts +2 -2
  139. package/template/src/shared/schemas/index.ts +74 -2
@@ -5,30 +5,35 @@ import { PermissionGuard } from '../components/PermissionGuard'
5
5
  import { Permission } from '@shared/modules/permission'
6
6
  import { apiClient } from '../services/apiClient'
7
7
  import type { Order } from '@shared/modules/order'
8
-
9
- const STATUS_COLORS = {
10
- pending: 'orange',
11
- processing: 'blue',
12
- completed: 'green',
13
- cancelled: 'red',
14
- disputed: 'purple',
15
- }
16
-
17
- const STATUS_LABELS = {
18
- pending: '待处理',
19
- processing: '处理中',
20
- completed: '已完成',
21
- cancelled: '已取消',
22
- disputed: '争议中',
23
- }
8
+ import { useLanguage } from '../i18n/useLanguage'
24
9
 
25
10
  export const OrdersPage: React.FC = () => {
11
+ const { t, formatDate } = useLanguage()
26
12
  const [orders, setOrders] = useState<Order[]>([])
27
13
  const [loading, setLoading] = useState(false)
28
14
  const [selectedOrder, setSelectedOrder] = useState<Order | null>(null)
29
15
  const [detailVisible, setDetailVisible] = useState(false)
30
16
  const [filterStatus, setFilterStatus] = useState<string | undefined>()
31
17
 
18
+ const STATUS_COLORS: Record<string, string> = {
19
+ pending: 'orange',
20
+ processing: 'blue',
21
+ completed: 'green',
22
+ cancelled: 'red',
23
+ disputed: 'purple',
24
+ }
25
+
26
+ const getLabel = (status: string) => {
27
+ const map: Record<string, string> = {
28
+ pending: t('orders.pending'),
29
+ processing: t('orders.processing'),
30
+ completed: t('orders.completed'),
31
+ cancelled: t('orders.cancelled'),
32
+ disputed: t('orders.disputed'),
33
+ }
34
+ return map[status] || status
35
+ }
36
+
32
37
  const fetchOrders = useCallback(async () => {
33
38
  setLoading(true)
34
39
  try {
@@ -42,14 +47,14 @@ export const OrdersPage: React.FC = () => {
42
47
  if (result.success) {
43
48
  setOrders(result.data)
44
49
  } else {
45
- message.error(result.error || 'Failed to load orders')
50
+ message.error(result.error || t('orders.loadFailed'))
46
51
  }
47
52
  } catch {
48
- message.error('获取订单列表失败')
53
+ message.error(t('orders.loadFailed'))
49
54
  } finally {
50
55
  setLoading(false)
51
56
  }
52
- }, [filterStatus])
57
+ }, [filterStatus, t])
53
58
 
54
59
  useEffect(() => {
55
60
  fetchOrders()
@@ -62,18 +67,18 @@ export const OrdersPage: React.FC = () => {
62
67
  })
63
68
  const result = await response.json()
64
69
  if (result.success) {
65
- message.success('订单已开始处理')
70
+ message.success(t('orders.orderProcessed'))
66
71
  fetchOrders()
67
72
  }
68
73
  } catch {
69
- message.error('处理订单失败')
74
+ message.error(t('orders.processFailed'))
70
75
  }
71
76
  }
72
77
 
73
78
  const handleCancel = async (orderId: string) => {
74
79
  Modal.confirm({
75
- title: '确认取消',
76
- content: '确定要取消这个订单吗?',
80
+ title: t('orders.confirmCancel'),
81
+ content: t('orders.confirmCancelContent'),
77
82
  onOk: async () => {
78
83
  try {
79
84
  const response = await apiClient.api.orders[':id'].cancel.$put({
@@ -81,11 +86,11 @@ export const OrdersPage: React.FC = () => {
81
86
  })
82
87
  const result = await response.json()
83
88
  if (result.success) {
84
- message.success('订单已取消')
89
+ message.success(t('orders.orderCancelled'))
85
90
  fetchOrders()
86
91
  }
87
92
  } catch {
88
- message.error('取消订单失败')
93
+ message.error(t('orders.cancelFailed'))
89
94
  }
90
95
  },
91
96
  })
@@ -98,13 +103,13 @@ export const OrdersPage: React.FC = () => {
98
103
 
99
104
  const columns = [
100
105
  {
101
- title: '订单号',
106
+ title: t('orders.orderNo'),
102
107
  dataIndex: 'orderNo',
103
108
  key: 'orderNo',
104
109
  render: (text: string) => <code className="text-sm">{text}</code>,
105
110
  },
106
111
  {
107
- title: '客户信息',
112
+ title: t('orders.customer'),
108
113
  key: 'customer',
109
114
  render: (_: unknown, record: Order) => (
110
115
  <div>
@@ -114,12 +119,12 @@ export const OrdersPage: React.FC = () => {
114
119
  ),
115
120
  },
116
121
  {
117
- title: '商品',
122
+ title: t('orders.product'),
118
123
  dataIndex: 'productName',
119
124
  key: 'productName',
120
125
  },
121
126
  {
122
- title: '金额',
127
+ title: t('orders.amount'),
123
128
  dataIndex: 'amount',
124
129
  key: 'amount',
125
130
  render: (amount: number) => (
@@ -127,21 +132,21 @@ export const OrdersPage: React.FC = () => {
127
132
  ),
128
133
  },
129
134
  {
130
- title: '状态',
135
+ title: t('orders.status'),
131
136
  dataIndex: 'status',
132
137
  key: 'status',
133
- render: (status: keyof typeof STATUS_COLORS) => (
134
- <Tag color={STATUS_COLORS[status]}>{STATUS_LABELS[status]}</Tag>
138
+ render: (status: string) => (
139
+ <Tag color={STATUS_COLORS[status]}>{getLabel(status)}</Tag>
135
140
  ),
136
141
  },
137
142
  {
138
- title: '创建时间',
143
+ title: t('common.createdAt'),
139
144
  dataIndex: 'createdAt',
140
145
  key: 'createdAt',
141
- render: (date: string) => new Date(date).toLocaleString('zh-CN'),
146
+ render: (date: string) => formatDate(date),
142
147
  },
143
148
  {
144
- title: '操作',
149
+ title: t('common.actions'),
145
150
  key: 'actions',
146
151
  render: (_: unknown, record: Order) => (
147
152
  <Space>
@@ -151,7 +156,7 @@ export const OrdersPage: React.FC = () => {
151
156
  icon={<Eye className="w-4 h-4" />}
152
157
  onClick={() => showDetail(record)}
153
158
  >
154
- 查看
159
+ {t('common.view')}
155
160
  </Button>
156
161
  <PermissionGuard permission={Permission.ORDER_PROCESS}>
157
162
  {record.status === 'pending' && (
@@ -161,7 +166,7 @@ export const OrdersPage: React.FC = () => {
161
166
  icon={<CheckCircle className="w-4 h-4" />}
162
167
  onClick={() => handleProcess(record.id)}
163
168
  >
164
- 处理
169
+ {t('orders.process')}
165
170
  </Button>
166
171
  )}
167
172
  {(record.status === 'pending' || record.status === 'processing') && (
@@ -172,7 +177,7 @@ export const OrdersPage: React.FC = () => {
172
177
  icon={<XCircle className="w-4 h-4" />}
173
178
  onClick={() => handleCancel(record.id)}
174
179
  >
175
- 取消
180
+ {t('orders.cancelOrder')}
176
181
  </Button>
177
182
  )}
178
183
  </PermissionGuard>
@@ -185,25 +190,25 @@ export const OrdersPage: React.FC = () => {
185
190
  <div>
186
191
  <div className="flex justify-between items-center mb-6">
187
192
  <div>
188
- <h1 className="text-2xl font-bold text-gray-900">订单管理</h1>
189
- <p className="text-gray-600 mt-1">管理和处理所有订单</p>
193
+ <h1 className="text-2xl font-bold text-gray-900">{t('orders.title')}</h1>
194
+ <p className="text-gray-600 mt-1">{t('orders.subtitle')}</p>
190
195
  </div>
191
196
  <Space>
192
197
  <Select
193
- placeholder="筛选状态"
198
+ placeholder={t('orders.filterStatus')}
194
199
  style={{ width: 150 }}
195
200
  allowClear
196
201
  value={filterStatus}
197
202
  onChange={setFilterStatus}
198
203
  options={[
199
- { value: 'pending', label: '待处理' },
200
- { value: 'processing', label: '处理中' },
201
- { value: 'completed', label: '已完成' },
202
- { value: 'cancelled', label: '已取消' },
203
- { value: 'disputed', label: '争议中' },
204
+ { value: 'pending', label: t('orders.pending') },
205
+ { value: 'processing', label: t('orders.processing') },
206
+ { value: 'completed', label: t('orders.completed') },
207
+ { value: 'cancelled', label: t('orders.cancelled') },
208
+ { value: 'disputed', label: t('orders.disputed') },
204
209
  ]}
205
210
  />
206
- <Button onClick={fetchOrders}>刷新</Button>
211
+ <Button onClick={fetchOrders}>{t('common.refresh')}</Button>
207
212
  </Space>
208
213
  </div>
209
214
 
@@ -211,7 +216,7 @@ export const OrdersPage: React.FC = () => {
211
216
  <Card>
212
217
  <div className="flex items-center justify-between">
213
218
  <div>
214
- <div className="text-sm text-gray-500">总订单</div>
219
+ <div className="text-sm text-gray-500">{t('orders.totalOrders')}</div>
215
220
  <div className="text-2xl font-bold">{orders.length}</div>
216
221
  </div>
217
222
  <DollarSign className="w-8 h-8 text-blue-500" />
@@ -220,7 +225,7 @@ export const OrdersPage: React.FC = () => {
220
225
  <Card>
221
226
  <div className="flex items-center justify-between">
222
227
  <div>
223
- <div className="text-sm text-gray-500">待处理</div>
228
+ <div className="text-sm text-gray-500">{t('orders.pending')}</div>
224
229
  <div className="text-2xl font-bold text-orange-500">
225
230
  {orders.filter(o => o.status === 'pending').length}
226
231
  </div>
@@ -231,7 +236,7 @@ export const OrdersPage: React.FC = () => {
231
236
  <Card>
232
237
  <div className="flex items-center justify-between">
233
238
  <div>
234
- <div className="text-sm text-gray-500">处理中</div>
239
+ <div className="text-sm text-gray-500">{t('orders.processing')}</div>
235
240
  <div className="text-2xl font-bold text-blue-500">
236
241
  {orders.filter(o => o.status === 'processing').length}
237
242
  </div>
@@ -242,7 +247,7 @@ export const OrdersPage: React.FC = () => {
242
247
  <Card>
243
248
  <div className="flex items-center justify-between">
244
249
  <div>
245
- <div className="text-sm text-gray-500">已完成</div>
250
+ <div className="text-sm text-gray-500">{t('orders.completed')}</div>
246
251
  <div className="text-2xl font-bold text-green-500">
247
252
  {orders.filter(o => o.status === 'completed').length}
248
253
  </div>
@@ -260,13 +265,13 @@ export const OrdersPage: React.FC = () => {
260
265
  loading={loading}
261
266
  pagination={{
262
267
  pageSize: 10,
263
- showTotal: total => `共 ${total} 条`,
268
+ showTotal: total => t('common.total', { count: total }),
264
269
  }}
265
270
  />
266
271
  </Card>
267
272
 
268
273
  <Modal
269
- title="订单详情"
274
+ title={t('orders.detail')}
270
275
  open={detailVisible}
271
276
  onCancel={() => setDetailVisible(false)}
272
277
  footer={null}
@@ -274,27 +279,27 @@ export const OrdersPage: React.FC = () => {
274
279
  >
275
280
  {selectedOrder && (
276
281
  <Descriptions column={1} bordered>
277
- <Descriptions.Item label="订单号">
282
+ <Descriptions.Item label={t('orders.orderNo')}>
278
283
  <code>{selectedOrder.orderNo}</code>
279
284
  </Descriptions.Item>
280
- <Descriptions.Item label="客户姓名">{selectedOrder.customerName}</Descriptions.Item>
281
- <Descriptions.Item label="客户邮箱">{selectedOrder.customerEmail}</Descriptions.Item>
282
- <Descriptions.Item label="商品名称">{selectedOrder.productName}</Descriptions.Item>
283
- <Descriptions.Item label="订单金额">
285
+ <Descriptions.Item label={t('orders.customerName')}>{selectedOrder.customerName}</Descriptions.Item>
286
+ <Descriptions.Item label={t('orders.customerEmail')}>{selectedOrder.customerEmail}</Descriptions.Item>
287
+ <Descriptions.Item label={t('orders.productName')}>{selectedOrder.productName}</Descriptions.Item>
288
+ <Descriptions.Item label={t('orders.orderAmount')}>
284
289
  <span className="text-lg font-bold text-green-600">
285
290
  ¥{selectedOrder.amount.toFixed(2)}
286
291
  </span>
287
292
  </Descriptions.Item>
288
- <Descriptions.Item label="订单状态">
293
+ <Descriptions.Item label={t('orders.orderStatus')}>
289
294
  <Tag color={STATUS_COLORS[selectedOrder.status]}>
290
- {STATUS_LABELS[selectedOrder.status]}
295
+ {getLabel(selectedOrder.status)}
291
296
  </Tag>
292
297
  </Descriptions.Item>
293
- <Descriptions.Item label="创建时间">
294
- {new Date(selectedOrder.createdAt).toLocaleString('zh-CN')}
298
+ <Descriptions.Item label={t('common.createdAt')}>
299
+ {formatDate(selectedOrder.createdAt)}
295
300
  </Descriptions.Item>
296
- <Descriptions.Item label="更新时间">
297
- {new Date(selectedOrder.updatedAt).toLocaleString('zh-CN')}
301
+ <Descriptions.Item label={t('orders.updatedAt')}>
302
+ {formatDate(selectedOrder.updatedAt)}
298
303
  </Descriptions.Item>
299
304
  </Descriptions>
300
305
  )}
@@ -2,8 +2,10 @@ import { Card, Table, Tag } from 'antd'
2
2
  import { usePermissions } from '../hooks/usePermissions'
3
3
  import { usePermissionCategories, useRoleLabels, usePermissionLabels } from '../hooks/useConfig'
4
4
  import type { PermissionInfo, Permission } from '@shared/modules/permission'
5
+ import { useLanguage } from '../i18n/useLanguage'
5
6
 
6
7
  export const PermissionsPage: React.FC = () => {
8
+ const { t } = useLanguage()
7
9
  const { allPermissions, roles, loading } = usePermissions()
8
10
  const { categories } = usePermissionCategories()
9
11
  const { roleLabels } = useRoleLabels()
@@ -20,13 +22,13 @@ export const PermissionsPage: React.FC = () => {
20
22
 
21
23
  const columns = [
22
24
  {
23
- title: '权限',
25
+ title: t('permissions.permission'),
24
26
  dataIndex: 'permission',
25
27
  key: 'permission',
26
28
  render: (permission: Permission) => permissionLabels[permission] || permission,
27
29
  },
28
30
  {
29
- title: '超级管理员',
31
+ title: t('permissions.superAdmin'),
30
32
  key: 'superAdmin',
31
33
  render: (_: unknown, record: PermissionInfo) => {
32
34
  const role = roles.find(r => r.role === 'super_admin')
@@ -35,7 +37,7 @@ export const PermissionsPage: React.FC = () => {
35
37
  },
36
38
  },
37
39
  {
38
- title: '客服人员',
40
+ title: t('permissions.customerService'),
39
41
  key: 'customerService',
40
42
  render: (_: unknown, record: PermissionInfo) => {
41
43
  const role = roles.find(r => r.role === 'customer_service')
@@ -44,7 +46,7 @@ export const PermissionsPage: React.FC = () => {
44
46
  },
45
47
  },
46
48
  {
47
- title: '普通用户',
49
+ title: t('permissions.normalUser'),
48
50
  key: 'user',
49
51
  render: (_: unknown, record: PermissionInfo) => {
50
52
  const role = roles.find(r => r.role === 'user')
@@ -56,9 +58,9 @@ export const PermissionsPage: React.FC = () => {
56
58
 
57
59
  return (
58
60
  <div className="p-6">
59
- <h1 className="text-2xl font-bold mb-6">权限管理</h1>
61
+ <h1 className="text-2xl font-bold mb-6">{t('permissions.title')}</h1>
60
62
 
61
- <Card title="角色列表" className="mb-6">
63
+ <Card title={t('permissions.roleList')} className="mb-6">
62
64
  <div className="grid grid-cols-3 gap-4">
63
65
  {roles.map(role => (
64
66
  <Card key={role.role} size="small">
@@ -66,14 +68,14 @@ export const PermissionsPage: React.FC = () => {
66
68
  {roleLabels[role.role] || role.role}
67
69
  </div>
68
70
  <div className="text-sm text-gray-500">
69
- 权限数量: {role.permissions.length}
71
+ {t('permissions.permissionCount', { count: role.permissions.length })}
70
72
  </div>
71
73
  </Card>
72
74
  ))}
73
75
  </div>
74
76
  </Card>
75
77
 
76
- <Card title="权限矩阵">
78
+ <Card title={t('permissions.permissionMatrix')}>
77
79
  {Object.entries(groupedPermissions).map(([category, permissions]) => (
78
80
  <div key={category} className="mb-6">
79
81
  <h3 className="text-lg font-semibold mb-3">
@@ -13,6 +13,7 @@ import {
13
13
  import type { ColumnsType } from 'antd/es/table'
14
14
  import { apiClient, api } from '../services/apiClient'
15
15
  import type { AdminDashboardStats, AdminPlugin } from '@shared/modules/plugins'
16
+ import { useLanguage } from '../i18n/useLanguage'
16
17
 
17
18
  const STATUS_CONFIG: Record<string, { color: string; label: string }> = {
18
19
  pending: { color: 'orange', label: '待审核' },
@@ -21,6 +22,7 @@ const STATUS_CONFIG: Record<string, { color: string; label: string }> = {
21
22
  }
22
23
 
23
24
  export const PluginDashboardPage: React.FC = () => {
25
+ const { formatDate } = useLanguage()
24
26
  const [stats, setStats] = useState<AdminDashboardStats | null>(null)
25
27
  const [recentSubmissions, setRecentSubmissions] = useState<AdminPlugin[]>([])
26
28
  const [loading, setLoading] = useState(true)
@@ -175,7 +177,7 @@ export const PluginDashboardPage: React.FC = () => {
175
177
  title: '提交时间',
176
178
  dataIndex: 'createdAt',
177
179
  key: 'createdAt',
178
- render: (ts: number) => new Date(ts).toLocaleString('zh-CN'),
180
+ render: (ts: number) => formatDate(new Date(ts).toISOString()),
179
181
  },
180
182
  {
181
183
  title: '操作',
@@ -4,6 +4,7 @@ import { CheckCircle, XCircle, Star, Download, Trash2, Search, RefreshCw } from
4
4
  import type { ColumnsType } from 'antd/es/table'
5
5
  import { apiClient, api } from '../services/apiClient'
6
6
  import type { Plugin } from '@shared/modules/plugins'
7
+ import { useLanguage } from '../i18n/useLanguage'
7
8
 
8
9
  type PluginStatus = 'pending' | 'approved' | 'rejected'
9
10
 
@@ -23,6 +24,7 @@ const STATUS_OPTIONS = [
23
24
  ]
24
25
 
25
26
  export const PluginManagementPage: React.FC = () => {
27
+ const { formatDate } = useLanguage()
26
28
  const [plugins, setPlugins] = useState<Plugin[]>([])
27
29
  const [total, setTotal] = useState(0)
28
30
  const [page, setPage] = useState(1)
@@ -201,7 +203,7 @@ export const PluginManagementPage: React.FC = () => {
201
203
  title: '更新时间',
202
204
  dataIndex: 'updatedAt',
203
205
  key: 'updatedAt',
204
- render: (ts: number) => new Date(ts).toLocaleString('zh-CN'),
206
+ render: (ts: number) => formatDate(new Date(ts).toISOString()),
205
207
  sorter: (a: Plugin, b: Plugin) => a.updatedAt - b.updatedAt,
206
208
  defaultSortOrder: 'descend',
207
209
  },
@@ -4,12 +4,14 @@ import { useNavigate, Link } from 'react-router-dom'
4
4
  import { useState } from 'react'
5
5
  import { apiClient } from '../services/apiClient'
6
6
  import type { RegisterRequest } from '@shared/modules/admin'
7
+ import { useLanguage } from '../i18n/useLanguage'
7
8
 
8
9
  interface RegisterForm extends RegisterRequest {
9
10
  confirmPassword: string
10
11
  }
11
12
 
12
13
  export const RegisterPage: React.FC = () => {
14
+ const { t } = useLanguage()
13
15
  const navigate = useNavigate()
14
16
  const [form] = Form.useForm<RegisterForm>()
15
17
  const [loading, setLoading] = useState(false)
@@ -26,13 +28,13 @@ export const RegisterPage: React.FC = () => {
26
28
  })
27
29
  const result = await response.json()
28
30
  if (result.success) {
29
- message.success('Registration successful! Please login.')
31
+ message.success(t('register.success'))
30
32
  navigate('/login')
31
33
  } else {
32
- message.error(result.error || 'Registration failed!')
34
+ message.error(result.error || t('register.failed'))
33
35
  }
34
36
  } catch (error) {
35
- message.error((error as Error).message || 'Registration failed!')
37
+ message.error((error as Error).message || t('register.failed'))
36
38
  } finally {
37
39
  setLoading(false)
38
40
  }
@@ -40,59 +42,59 @@ export const RegisterPage: React.FC = () => {
40
42
 
41
43
  return (
42
44
  <div className="min-h-screen flex items-center justify-center bg-gray-100">
43
- <Card className="w-full max-w-md" title="Admin Registration">
45
+ <Card className="w-full max-w-md" title={t('register.title')}>
44
46
  <Spin spinning={loading}>
45
47
  <Form form={form} onFinish={handleSubmit} layout="vertical">
46
48
  <Form.Item
47
49
  name="username"
48
- rules={[{ required: true, message: 'Please input username!' }]}
50
+ rules={[{ required: true, message: t('register.usernameRequired') }]}
49
51
  >
50
- <Input prefix={<UserOutlined />} placeholder="Username" size="large" />
52
+ <Input prefix={<UserOutlined />} placeholder={t('register.username')} size="large" />
51
53
  </Form.Item>
52
54
  <Form.Item
53
55
  name="email"
54
- rules={[{ required: true, type: 'email', message: 'Please input valid email!' }]}
56
+ rules={[{ required: true, type: 'email', message: t('register.emailRequired') }]}
55
57
  >
56
- <Input prefix={<MailOutlined />} placeholder="Email" size="large" />
58
+ <Input prefix={<MailOutlined />} placeholder={t('register.email')} size="large" />
57
59
  </Form.Item>
58
60
  <Form.Item
59
61
  name="password"
60
62
  rules={[
61
- { required: true, min: 6, message: 'Password must be at least 6 characters!' },
63
+ { required: true, min: 6, message: t('register.passwordMin') },
62
64
  ]}
63
65
  >
64
- <Input.Password prefix={<LockOutlined />} placeholder="Password" size="large" />
66
+ <Input.Password prefix={<LockOutlined />} placeholder={t('register.password')} size="large" />
65
67
  </Form.Item>
66
68
  <Form.Item
67
69
  name="confirmPassword"
68
70
  dependencies={['password']}
69
71
  rules={[
70
- { required: true, message: 'Please confirm password!' },
72
+ { required: true, message: t('register.confirmRequired') },
71
73
  ({ getFieldValue }) => ({
72
74
  validator(_, value) {
73
75
  if (!value || getFieldValue('password') === value) {
74
76
  return Promise.resolve()
75
77
  }
76
- return Promise.reject(new Error('Passwords do not match!'))
78
+ return Promise.reject(new Error(t('register.passwordMismatch')))
77
79
  },
78
80
  }),
79
81
  ]}
80
82
  >
81
83
  <Input.Password
82
84
  prefix={<LockOutlined />}
83
- placeholder="Confirm Password"
85
+ placeholder={t('register.confirmPassword')}
84
86
  size="large"
85
87
  />
86
88
  </Form.Item>
87
89
  <Form.Item>
88
90
  <Button type="primary" htmlType="submit" size="large" block loading={loading}>
89
- Register
91
+ {t('register.submit')}
90
92
  </Button>
91
93
  </Form.Item>
92
94
  <div className="text-center">
93
- <span className="text-gray-500">Already have an account? </span>
95
+ <span className="text-gray-500">{t('register.hasAccount')}</span>
94
96
  <Link to="/login" className="text-blue-500 hover:underline">
95
- Login
97
+ {t('register.loginLink')}
96
98
  </Link>
97
99
  </div>
98
100
  </Form>