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.
- 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/cli/modules/admin/index.ts +200 -0
- package/template/src/cli/modules/captcha/index.ts +40 -0
- package/template/src/cli/modules/chat/index.ts +21 -0
- package/template/src/cli/modules/content/index.ts +144 -0
- package/template/src/cli/modules/dispute/index.ts +150 -0
- package/template/src/cli/modules/file/index.ts +55 -0
- package/template/src/cli/modules/index.ts +30 -5
- package/template/src/cli/modules/order/index.ts +170 -0
- package/template/src/cli/modules/permission/index.ts +195 -0
- package/template/src/cli/modules/tenant/index.ts +142 -0
- package/template/src/cli/modules/ticket/index.ts +167 -0
- package/template/src/cli/rpc/client.ts +2 -2
- 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,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 || '
|
|
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:
|
|
134
|
-
<Tag color={STATUS_COLORS[status]}>{
|
|
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) =>
|
|
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"
|
|
189
|
-
<p className="text-gray-600 mt-1"
|
|
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}
|
|
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"
|
|
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"
|
|
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"
|
|
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"
|
|
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 =>
|
|
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=
|
|
281
|
-
<Descriptions.Item label=
|
|
282
|
-
<Descriptions.Item label=
|
|
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
|
-
{
|
|
295
|
+
{getLabel(selectedOrder.status)}
|
|
291
296
|
</Tag>
|
|
292
297
|
</Descriptions.Item>
|
|
293
|
-
<Descriptions.Item label=
|
|
294
|
-
{
|
|
298
|
+
<Descriptions.Item label={t('common.createdAt')}>
|
|
299
|
+
{formatDate(selectedOrder.createdAt)}
|
|
295
300
|
</Descriptions.Item>
|
|
296
|
-
<Descriptions.Item label=
|
|
297
|
-
{
|
|
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"
|
|
61
|
+
<h1 className="text-2xl font-bold mb-6">{t('permissions.title')}</h1>
|
|
60
62
|
|
|
61
|
-
<Card title=
|
|
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
|
-
|
|
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).
|
|
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).
|
|
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('
|
|
31
|
+
message.success(t('register.success'))
|
|
30
32
|
navigate('/login')
|
|
31
33
|
} else {
|
|
32
|
-
message.error(result.error || '
|
|
34
|
+
message.error(result.error || t('register.failed'))
|
|
33
35
|
}
|
|
34
36
|
} catch (error) {
|
|
35
|
-
message.error((error as Error).message || '
|
|
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=
|
|
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: '
|
|
50
|
+
rules={[{ required: true, message: t('register.usernameRequired') }]}
|
|
49
51
|
>
|
|
50
|
-
<Input prefix={<UserOutlined />} placeholder=
|
|
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: '
|
|
56
|
+
rules={[{ required: true, type: 'email', message: t('register.emailRequired') }]}
|
|
55
57
|
>
|
|
56
|
-
<Input prefix={<MailOutlined />} placeholder=
|
|
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: '
|
|
63
|
+
{ required: true, min: 6, message: t('register.passwordMin') },
|
|
62
64
|
]}
|
|
63
65
|
>
|
|
64
|
-
<Input.Password prefix={<LockOutlined />} placeholder=
|
|
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: '
|
|
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('
|
|
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=
|
|
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
|
-
|
|
91
|
+
{t('register.submit')}
|
|
90
92
|
</Button>
|
|
91
93
|
</Form.Item>
|
|
92
94
|
<div className="text-center">
|
|
93
|
-
<span className="text-gray-500">
|
|
95
|
+
<span className="text-gray-500">{t('register.hasAccount')}</span>
|
|
94
96
|
<Link to="/login" className="text-blue-500 hover:underline">
|
|
95
|
-
|
|
97
|
+
{t('register.loginLink')}
|
|
96
98
|
</Link>
|
|
97
99
|
</div>
|
|
98
100
|
</Form>
|