mall-components 1.0.0

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 (122) hide show
  1. package/README.md +128 -0
  2. package/build/_components-raw.css +791 -0
  3. package/build/_shims/antd.js +1 -0
  4. package/build/_shims/icons.js +1 -0
  5. package/build/_shims/moment.js +1 -0
  6. package/build/_shims/react-dom.js +1 -0
  7. package/build/_shims/react.js +1 -0
  8. package/build/adapters/DataSourceAdapter.d.ts +46 -0
  9. package/build/components/AdminLayout/AdminLayout.d.ts +5 -0
  10. package/build/components/AdminLayout/Breadcrumb.d.ts +8 -0
  11. package/build/components/AdminLayout/MainContent.d.ts +17 -0
  12. package/build/components/AdminLayout/Navbar.d.ts +10 -0
  13. package/build/components/AdminLayout/Sidebar.d.ts +14 -0
  14. package/build/components/AdminLayout/TabBar.d.ts +13 -0
  15. package/build/components/AdminLayout/TabPane.d.ts +4 -0
  16. package/build/components/AdminLayout/index.d.ts +3 -0
  17. package/build/components/AdminLayout/types.d.ts +42 -0
  18. package/build/components/CouponCard/CouponCard.d.ts +20 -0
  19. package/build/components/CouponCard/index.d.ts +1 -0
  20. package/build/components/OrderForm/OrderForm.d.ts +18 -0
  21. package/build/components/OrderForm/index.d.ts +1 -0
  22. package/build/components/OrderList/OrderList.d.ts +29 -0
  23. package/build/components/OrderList/index.d.ts +1 -0
  24. package/build/components/ProductForm/ProductForm.d.ts +18 -0
  25. package/build/components/ProductForm/index.d.ts +3 -0
  26. package/build/components/ProductList/ProductList.d.ts +47 -0
  27. package/build/components/ProductList/index.d.ts +3 -0
  28. package/build/components/PromotionCard/PromotionCard.d.ts +22 -0
  29. package/build/components/PromotionCard/index.d.ts +1 -0
  30. package/build/components/RoleCard/RoleCard.d.ts +18 -0
  31. package/build/components/RoleCard/index.d.ts +1 -0
  32. package/build/components/UserCard/UserCard.d.ts +17 -0
  33. package/build/components/UserCard/index.d.ts +1 -0
  34. package/build/entry-meta.d.ts +603 -0
  35. package/build/index.css +1 -0
  36. package/build/index.js +1 -0
  37. package/build/mall-components-meta.js +2563 -0
  38. package/build/mall-components.cdn.umd.css +1 -0
  39. package/build/mall-components.cdn.umd.js +8 -0
  40. package/build/mall-components.codesandbox.combined.js +1094 -0
  41. package/build/mall-components.codesandbox.css +401 -0
  42. package/build/mall-components.codesandbox.js +1080 -0
  43. package/build/mall-components.umd.css +1 -0
  44. package/build/mall-components.umd.js +8 -0
  45. package/build/meta/adminLayoutMeta.d.ts +3 -0
  46. package/build/meta/couponCardMeta.d.ts +128 -0
  47. package/build/meta/icons.d.ts +10 -0
  48. package/build/meta/orderFormMeta.d.ts +111 -0
  49. package/build/meta/orderListMeta.d.ts +170 -0
  50. package/build/meta/productFormMeta.d.ts +3 -0
  51. package/build/meta/productListMeta.d.ts +200 -0
  52. package/build/meta/promotionCardMeta.d.ts +129 -0
  53. package/build/meta/roleCardMeta.d.ts +3 -0
  54. package/build/meta/tabPaneMeta.d.ts +3 -0
  55. package/build/meta/userCardMeta.d.ts +3 -0
  56. package/build/meta.d.ts +605 -0
  57. package/build/setters/RestApiTester.d.ts +11 -0
  58. package/build/types/common.d.ts +17 -0
  59. package/build/types/marketing.d.ts +128 -0
  60. package/build/types/order.d.ts +174 -0
  61. package/build/types/permission.d.ts +101 -0
  62. package/build/types/product.d.ts +47 -0
  63. package/package.json +1 -0
  64. package/src/adapters/DataSourceAdapter.ts +445 -0
  65. package/src/components/AdminLayout/AdminLayout.scss +447 -0
  66. package/src/components/AdminLayout/AdminLayout.tsx +681 -0
  67. package/src/components/AdminLayout/Breadcrumb.tsx +60 -0
  68. package/src/components/AdminLayout/MainContent.tsx +54 -0
  69. package/src/components/AdminLayout/Navbar.tsx +76 -0
  70. package/src/components/AdminLayout/Sidebar.tsx +256 -0
  71. package/src/components/AdminLayout/TabBar.tsx +177 -0
  72. package/src/components/AdminLayout/TabPane.tsx +29 -0
  73. package/src/components/AdminLayout/index.ts +3 -0
  74. package/src/components/AdminLayout/types.ts +46 -0
  75. package/src/components/CouponCard/CouponCard.scss +55 -0
  76. package/src/components/CouponCard/CouponCard.tsx +687 -0
  77. package/src/components/CouponCard/index.ts +1 -0
  78. package/src/components/OrderForm/OrderForm.scss +148 -0
  79. package/src/components/OrderForm/OrderForm.tsx +503 -0
  80. package/src/components/OrderForm/index.ts +1 -0
  81. package/src/components/OrderList/OrderList.scss +160 -0
  82. package/src/components/OrderList/OrderList.tsx +885 -0
  83. package/src/components/OrderList/index.ts +1 -0
  84. package/src/components/ProductForm/ProductForm.scss +23 -0
  85. package/src/components/ProductForm/ProductForm.tsx +442 -0
  86. package/src/components/ProductForm/index.ts +3 -0
  87. package/src/components/ProductList/ProductList.scss +293 -0
  88. package/src/components/ProductList/ProductList.tsx +454 -0
  89. package/src/components/ProductList/index.ts +3 -0
  90. package/src/components/PromotionCard/PromotionCard.scss +71 -0
  91. package/src/components/PromotionCard/PromotionCard.tsx +579 -0
  92. package/src/components/PromotionCard/index.ts +1 -0
  93. package/src/components/RoleCard/RoleCard.scss +77 -0
  94. package/src/components/RoleCard/RoleCard.tsx +463 -0
  95. package/src/components/RoleCard/index.ts +1 -0
  96. package/src/components/UserCard/UserCard.scss +51 -0
  97. package/src/components/UserCard/UserCard.tsx +432 -0
  98. package/src/components/UserCard/index.ts +1 -0
  99. package/src/entry-components.ts +39 -0
  100. package/src/entry-meta.ts +23 -0
  101. package/src/index.scss +4 -0
  102. package/src/index.ts +36 -0
  103. package/src/index.tsx +17 -0
  104. package/src/meta/adminLayoutMeta.ts +154 -0
  105. package/src/meta/couponCardMeta.ts +287 -0
  106. package/src/meta/icons.ts +41 -0
  107. package/src/meta/orderFormMeta.ts +279 -0
  108. package/src/meta/orderListMeta.ts +443 -0
  109. package/src/meta/productFormMeta.ts +253 -0
  110. package/src/meta/productListMeta.ts +434 -0
  111. package/src/meta/promotionCardMeta.ts +276 -0
  112. package/src/meta/roleCardMeta.ts +142 -0
  113. package/src/meta/tabPaneMeta.ts +69 -0
  114. package/src/meta/userCardMeta.ts +128 -0
  115. package/src/meta.ts +25 -0
  116. package/src/setters/RestApiTester.tsx +219 -0
  117. package/src/shims/require.js +8 -0
  118. package/src/types/common.ts +19 -0
  119. package/src/types/marketing.ts +124 -0
  120. package/src/types/order.ts +169 -0
  121. package/src/types/permission.ts +102 -0
  122. package/src/types/product.ts +49 -0
@@ -0,0 +1,687 @@
1
+ import React, { useState, useEffect, useMemo } from 'react'
2
+ import {
3
+ Card,
4
+ Button,
5
+ Tag,
6
+ Space,
7
+ Modal,
8
+ Form,
9
+ Input,
10
+ InputNumber,
11
+ Select,
12
+ DatePicker,
13
+ Radio,
14
+ message,
15
+ Table,
16
+ Tooltip,
17
+ Badge,
18
+ Divider,
19
+ } from 'antd'
20
+ import type { ColumnsType } from 'antd/es/table'
21
+ import {
22
+ PlusOutlined,
23
+ EditOutlined,
24
+ DeleteOutlined,
25
+ GiftOutlined,
26
+ ClockCircleOutlined,
27
+ } from '@ant-design/icons'
28
+ import type {
29
+ SmsCoupon,
30
+ CouponQueryParam,
31
+ } from '../../types/marketing'
32
+ import {
33
+ COUPON_TYPE,
34
+ COUPON_PLATFORM,
35
+ COUPON_USE_TYPE,
36
+ COUPON_TYPE_OPTIONS,
37
+ COUPON_PLATFORM_OPTIONS,
38
+ COUPON_USE_TYPE_OPTIONS,
39
+ } from '../../types/marketing'
40
+ import { DataSourceAdapterFactory } from '../../adapters/DataSourceAdapter'
41
+ import type { DataSourceConfig } from '../../adapters/DataSourceAdapter'
42
+ import './CouponCard.scss'
43
+
44
+ const { RangePicker } = DatePicker
45
+
46
+ interface CouponCardProps {
47
+ dataSourceType?: 'rest' | 'mock' | 'variable'
48
+ api?: string
49
+ method?: 'GET' | 'POST'
50
+ mockData?: string
51
+ variableName?: string
52
+ showCreateButton?: boolean
53
+ showFilter?: boolean
54
+ showStatistics?: boolean
55
+ onCreateCoupon?: (coupon: Partial<SmsCoupon>) => void
56
+ onEditCoupon?: (id: number, coupon: Partial<SmsCoupon>) => void
57
+ onDeleteCoupon?: (id: number) => void
58
+ style?: React.CSSProperties
59
+ className?: string
60
+ }
61
+
62
+ const defaultMockData = {
63
+ code: 200,
64
+ message: 'success',
65
+ data: {
66
+ pageNum: 1,
67
+ pageSize: 10,
68
+ total: 3,
69
+ list: [
70
+ {
71
+ id: 1,
72
+ name: '新用户专享券',
73
+ type: 0,
74
+ platform: 0,
75
+ count: 1000,
76
+ amount: 50,
77
+ perLimit: 1,
78
+ minPoint: 200,
79
+ startTime: '2024-01-01',
80
+ endTime: '2024-12-31',
81
+ useType: 0,
82
+ note: '新用户首单满200减50',
83
+ publishCount: 500,
84
+ useCount: 320,
85
+ receiveCount: 450,
86
+ enableTime: '7',
87
+ code: 'NEWUSER50',
88
+ memberLevel: 0,
89
+ },
90
+ {
91
+ id: 2,
92
+ name: '限时折扣券',
93
+ type: 1,
94
+ platform: 3,
95
+ count: 500,
96
+ amount: 8,
97
+ perLimit: 2,
98
+ minPoint: 100,
99
+ startTime: '2024-01-15',
100
+ endTime: '2024-02-15',
101
+ useType: 1,
102
+ note: 'APP专享8折券',
103
+ publishCount: 300,
104
+ useCount: 180,
105
+ receiveCount: 280,
106
+ enableTime: '3',
107
+ code: 'APPDISCOUNT',
108
+ memberLevel: 1,
109
+ },
110
+ {
111
+ id: 3,
112
+ name: '会员专享券',
113
+ type: 0,
114
+ platform: 0,
115
+ count: 200,
116
+ amount: 100,
117
+ perLimit: 1,
118
+ minPoint: 500,
119
+ startTime: '2024-01-01',
120
+ endTime: '2024-06-30',
121
+ useType: 2,
122
+ note: '会员专享满500减100',
123
+ publishCount: 150,
124
+ useCount: 80,
125
+ receiveCount: 120,
126
+ enableTime: '15',
127
+ code: 'VIP100',
128
+ memberLevel: 2,
129
+ },
130
+ ],
131
+ },
132
+ }
133
+
134
+ const CouponCard: React.FC<CouponCardProps> = ({
135
+ dataSourceType = 'mock',
136
+ api,
137
+ method = 'GET',
138
+ mockData,
139
+ variableName,
140
+ showCreateButton = true,
141
+ showFilter = true,
142
+ showStatistics = true,
143
+ onCreateCoupon,
144
+ onEditCoupon,
145
+ onDeleteCoupon,
146
+ style,
147
+ className,
148
+ }) => {
149
+ const [loading, setLoading] = useState(false)
150
+ const [data, setData] = useState<SmsCoupon[]>([])
151
+ const [total, setTotal] = useState(0)
152
+ const [currentPage, setCurrentPage] = useState(1)
153
+ const [pageSize, setPageSize] = useState(10)
154
+ const [filterName, setFilterName] = useState('')
155
+ const [filterType, setFilterType] = useState<number>()
156
+ const [filterPlatform, setFilterPlatform] = useState<number>()
157
+ const [modalVisible, setModalVisible] = useState(false)
158
+ const [editingCoupon, setEditingCoupon] = useState<SmsCoupon | null>(null)
159
+ const [form] = Form.useForm()
160
+
161
+ const internalMockData: SmsCoupon[] = [
162
+ {
163
+ id: 1,
164
+ name: '新用户专享券',
165
+ type: COUPON_TYPE.FULL_REDUCTION,
166
+ platform: COUPON_PLATFORM.ALL,
167
+ count: 1000,
168
+ amount: 50,
169
+ perLimit: 1,
170
+ minPoint: 200,
171
+ startTime: '2024-01-01',
172
+ endTime: '2024-12-31',
173
+ useType: COUPON_USE_TYPE.ALL,
174
+ note: '新用户首单满200减50',
175
+ publishCount: 500,
176
+ useCount: 320,
177
+ receiveCount: 450,
178
+ enableTime: '7',
179
+ code: 'NEWUSER50',
180
+ memberLevel: 0,
181
+ },
182
+ {
183
+ id: 2,
184
+ name: '限时折扣券',
185
+ type: COUPON_TYPE.DISCOUNT,
186
+ platform: COUPON_PLATFORM.APP,
187
+ count: 500,
188
+ amount: 8,
189
+ perLimit: 2,
190
+ minPoint: 100,
191
+ startTime: '2024-01-15',
192
+ endTime: '2024-02-15',
193
+ useType: COUPON_USE_TYPE.CATEGORY,
194
+ note: 'APP专享8折券',
195
+ publishCount: 300,
196
+ useCount: 180,
197
+ receiveCount: 280,
198
+ enableTime: '3',
199
+ code: 'APPDISCOUNT',
200
+ memberLevel: 1,
201
+ },
202
+ {
203
+ id: 3,
204
+ name: '会员专享券',
205
+ type: COUPON_TYPE.FULL_REDUCTION,
206
+ platform: COUPON_PLATFORM.ALL,
207
+ count: 200,
208
+ amount: 100,
209
+ perLimit: 1,
210
+ minPoint: 500,
211
+ startTime: '2024-01-01',
212
+ endTime: '2024-06-30',
213
+ useType: COUPON_USE_TYPE.PRODUCT,
214
+ note: '会员专享满500减100',
215
+ publishCount: 150,
216
+ useCount: 80,
217
+ receiveCount: 120,
218
+ enableTime: '15',
219
+ code: 'VIP100',
220
+ memberLevel: 2,
221
+ },
222
+ ]
223
+
224
+ const dataSourceConfig: DataSourceConfig = useMemo(() => ({
225
+ type: dataSourceType,
226
+ api,
227
+ method,
228
+ mockData: mockData ? (() => {
229
+ try {
230
+ return typeof mockData === 'string' ? JSON.parse(mockData) : mockData
231
+ } catch {
232
+ return defaultMockData
233
+ }
234
+ })() : defaultMockData,
235
+ variableName,
236
+ }), [dataSourceType, api, method, mockData, variableName])
237
+
238
+ const adapter = useMemo(() => {
239
+ return DataSourceAdapterFactory.create(dataSourceConfig)
240
+ }, [dataSourceConfig])
241
+
242
+ useEffect(() => {
243
+ fetchData()
244
+ }, [currentPage, pageSize, adapter])
245
+
246
+ const fetchData = async () => {
247
+ setLoading(true)
248
+ try {
249
+ const result = await adapter.fetch({
250
+ pageNum: currentPage,
251
+ pageSize,
252
+ name: filterName,
253
+ type: filterType,
254
+ platform: filterPlatform,
255
+ })
256
+
257
+ let list: SmsCoupon[] = []
258
+ let totalCount = 0
259
+
260
+ if (result?.data?.list) {
261
+ list = result.data.list
262
+ totalCount = result.data.total || list.length
263
+ } else if (Array.isArray(result?.data)) {
264
+ list = result.data
265
+ totalCount = list.length
266
+ } else if (Array.isArray(result)) {
267
+ list = result
268
+ totalCount = list.length
269
+ }
270
+
271
+ if (filterName || filterType !== undefined || filterPlatform !== undefined) {
272
+ list = list.filter((item: SmsCoupon) => {
273
+ if (filterName && !item.name.toLowerCase().includes(filterName.toLowerCase())) {
274
+ return false
275
+ }
276
+ if (filterType !== undefined && item.type !== filterType) {
277
+ return false
278
+ }
279
+ if (filterPlatform !== undefined && item.platform !== filterPlatform) {
280
+ return false
281
+ }
282
+ return true
283
+ })
284
+ totalCount = list.length
285
+ }
286
+
287
+ setData(list)
288
+ setTotal(totalCount)
289
+ } catch (error) {
290
+ console.error('Failed to fetch coupon list:', error)
291
+ message.error('获取优惠券列表失败')
292
+ setData(internalMockData)
293
+ setTotal(internalMockData.length)
294
+ } finally {
295
+ setLoading(false)
296
+ }
297
+ }
298
+
299
+ const handleSearch = () => {
300
+ setCurrentPage(1)
301
+ fetchData()
302
+ }
303
+
304
+ const handleReset = () => {
305
+ setFilterName('')
306
+ setFilterType(undefined)
307
+ setFilterPlatform(undefined)
308
+ setCurrentPage(1)
309
+ fetchData()
310
+ }
311
+
312
+ const handleCreate = () => {
313
+ setEditingCoupon(null)
314
+ form.resetFields()
315
+ setModalVisible(true)
316
+ }
317
+
318
+ const handleEdit = (record: SmsCoupon) => {
319
+ setEditingCoupon(record)
320
+ form.setFieldsValue({
321
+ ...record,
322
+ dateRange: [record.startTime, record.endTime],
323
+ })
324
+ setModalVisible(true)
325
+ }
326
+
327
+ const handleDelete = (id: number) => {
328
+ Modal.confirm({
329
+ title: '确认删除',
330
+ content: '确定要删除该优惠券吗?',
331
+ onOk: () => {
332
+ onDeleteCoupon?.(id)
333
+ message.success('删除成功')
334
+ fetchData()
335
+ },
336
+ })
337
+ }
338
+
339
+ const handleSubmit = async () => {
340
+ try {
341
+ const values = await form.validateFields()
342
+ const couponData = {
343
+ ...values,
344
+ startTime: values.dateRange?.[0],
345
+ endTime: values.dateRange?.[1],
346
+ }
347
+ if (editingCoupon) {
348
+ onEditCoupon?.(editingCoupon.id, couponData)
349
+ message.success('编辑成功')
350
+ } else {
351
+ onCreateCoupon?.(couponData)
352
+ message.success('创建成功')
353
+ }
354
+ setModalVisible(false)
355
+ fetchData()
356
+ } catch (error) {
357
+ console.error('Validation failed:', error)
358
+ }
359
+ }
360
+
361
+ const getCouponTypeTag = (type: number) => {
362
+ const typeMap: Record<number, { color: string; text: string }> = {
363
+ [COUPON_TYPE.FULL_REDUCTION]: { color: 'red', text: '满减券' },
364
+ [COUPON_TYPE.DISCOUNT]: { color: 'blue', text: '折扣券' },
365
+ }
366
+ const { color, text } = typeMap[type] || { color: 'default', text: '未知' }
367
+ return <Tag color={color}>{text}</Tag>
368
+ }
369
+
370
+ const getPlatformTag = (platform: number) => {
371
+ const platformMap: Record<number, { color: string; text: string }> = {
372
+ [COUPON_PLATFORM.ALL]: { color: 'green', text: '全平台' },
373
+ [COUPON_PLATFORM.MOBILE]: { color: 'purple', text: '移动端' },
374
+ [COUPON_PLATFORM.PC]: { color: 'geekblue', text: 'PC端' },
375
+ [COUPON_PLATFORM.APP]: { color: 'cyan', text: 'APP' },
376
+ }
377
+ const { color, text } = platformMap[platform] || {
378
+ color: 'default',
379
+ text: '未知',
380
+ }
381
+ return <Tag color={color}>{text}</Tag>
382
+ }
383
+
384
+ const columns: ColumnsType<SmsCoupon> = [
385
+ {
386
+ title: '优惠券名称',
387
+ dataIndex: 'name',
388
+ key: 'name',
389
+ width: 150,
390
+ render: (text: string) => (
391
+ <Tooltip title={text}>
392
+ <span className="coupon-name">{text}</span>
393
+ </Tooltip>
394
+ ),
395
+ },
396
+ {
397
+ title: '类型',
398
+ dataIndex: 'type',
399
+ key: 'type',
400
+ width: 80,
401
+ render: (type: number) => getCouponTypeTag(type),
402
+ },
403
+ {
404
+ title: '优惠内容',
405
+ key: 'content',
406
+ width: 120,
407
+ render: (_, record) => (
408
+ <span>
409
+ {record.type === COUPON_TYPE.FULL_REDUCTION
410
+ ? `满${record.minPoint}减${record.amount}`
411
+ : `${record.amount}折`}
412
+ </span>
413
+ ),
414
+ },
415
+ {
416
+ title: '适用平台',
417
+ dataIndex: 'platform',
418
+ key: 'platform',
419
+ width: 80,
420
+ render: (platform: number) => getPlatformTag(platform),
421
+ },
422
+ {
423
+ title: '发行量',
424
+ dataIndex: 'count',
425
+ key: 'count',
426
+ width: 80,
427
+ render: (count: number) => <Badge count={count} showZero color="blue" />,
428
+ },
429
+ {
430
+ title: '已领取',
431
+ dataIndex: 'receiveCount',
432
+ key: 'receiveCount',
433
+ width: 80,
434
+ },
435
+ {
436
+ title: '已使用',
437
+ dataIndex: 'useCount',
438
+ key: 'useCount',
439
+ width: 80,
440
+ },
441
+ {
442
+ title: '有效期',
443
+ key: 'validity',
444
+ width: 180,
445
+ render: (_, record) => (
446
+ <Space>
447
+ <ClockCircleOutlined />
448
+ <span>
449
+ {record.startTime} ~ {record.endTime}
450
+ </span>
451
+ </Space>
452
+ ),
453
+ },
454
+ {
455
+ title: '操作',
456
+ key: 'action',
457
+ width: 150,
458
+ render: (_, record) => (
459
+ <Space>
460
+ <Button
461
+ type="link"
462
+ size="small"
463
+ icon={<EditOutlined />}
464
+ onClick={() => handleEdit(record)}
465
+ >
466
+ 编辑
467
+ </Button>
468
+ <Button
469
+ type="link"
470
+ size="small"
471
+ danger
472
+ icon={<DeleteOutlined />}
473
+ onClick={() => handleDelete(record.id)}
474
+ >
475
+ 删除
476
+ </Button>
477
+ </Space>
478
+ ),
479
+ },
480
+ ]
481
+
482
+ const statistics = {
483
+ totalCoupons: data.length,
484
+ activeCoupons: data.filter((c) => c.useCount > 0).length,
485
+ totalUsed: data.reduce((sum, c) => sum + c.useCount, 0),
486
+ totalReceived: data.reduce((sum, c) => sum + c.receiveCount, 0),
487
+ }
488
+
489
+ return (
490
+ <div className={`mall-coupon-card ${className || ''}`} style={style}>
491
+ <Card>
492
+ {showStatistics && (
493
+ <div className="statistics-section">
494
+ <Space size="large">
495
+ <div className="stat-item">
496
+ <div className="stat-value">{statistics.totalCoupons}</div>
497
+ <div className="stat-label">优惠券总数</div>
498
+ </div>
499
+ <Divider type="vertical" style={{ height: 40 }} />
500
+ <div className="stat-item">
501
+ <div className="stat-value">{statistics.activeCoupons}</div>
502
+ <div className="stat-label">活跃优惠券</div>
503
+ </div>
504
+ <Divider type="vertical" style={{ height: 40 }} />
505
+ <div className="stat-item">
506
+ <div className="stat-value">{statistics.totalReceived}</div>
507
+ <div className="stat-label">已领取</div>
508
+ </div>
509
+ <Divider type="vertical" style={{ height: 40 }} />
510
+ <div className="stat-item">
511
+ <div className="stat-value">{statistics.totalUsed}</div>
512
+ <div className="stat-label">已使用</div>
513
+ </div>
514
+ </Space>
515
+ </div>
516
+ )}
517
+
518
+ {showFilter && (
519
+ <div className="filter-section">
520
+ <Space wrap>
521
+ <Input
522
+ placeholder="优惠券名称"
523
+ value={filterName}
524
+ onChange={(e) => setFilterName(e.target.value)}
525
+ style={{ width: 200 }}
526
+ />
527
+ <Select
528
+ placeholder="优惠券类型"
529
+ value={filterType}
530
+ onChange={setFilterType}
531
+ style={{ width: 120 }}
532
+ allowClear
533
+ >
534
+ {COUPON_TYPE_OPTIONS.map((option) => (
535
+ <Select.Option key={option.value} value={option.value}>
536
+ {option.label}
537
+ </Select.Option>
538
+ ))}
539
+ </Select>
540
+ <Select
541
+ placeholder="适用平台"
542
+ value={filterPlatform}
543
+ onChange={setFilterPlatform}
544
+ style={{ width: 120 }}
545
+ allowClear
546
+ >
547
+ {COUPON_PLATFORM_OPTIONS.map((option) => (
548
+ <Select.Option key={option.value} value={option.value}>
549
+ {option.label}
550
+ </Select.Option>
551
+ ))}
552
+ </Select>
553
+ <Button type="primary" onClick={handleSearch}>
554
+ 查询
555
+ </Button>
556
+ <Button onClick={handleReset}>重置</Button>
557
+ </Space>
558
+ </div>
559
+ )}
560
+
561
+ {showCreateButton && (
562
+ <div className="action-section">
563
+ <Button type="primary" icon={<PlusOutlined />} onClick={handleCreate}>
564
+ 创建优惠券
565
+ </Button>
566
+ </div>
567
+ )}
568
+
569
+ <Table
570
+ columns={columns}
571
+ dataSource={data}
572
+ rowKey="id"
573
+ loading={loading}
574
+ pagination={{
575
+ current: currentPage,
576
+ pageSize,
577
+ total,
578
+ showSizeChanger: true,
579
+ showQuickJumper: true,
580
+ showTotal: (total) => `共 ${total} 条`,
581
+ onChange: (page, pageSize) => {
582
+ setCurrentPage(page)
583
+ setPageSize(pageSize)
584
+ },
585
+ }}
586
+ />
587
+ </Card>
588
+
589
+ <Modal
590
+ title={editingCoupon ? '编辑优惠券' : '创建优惠券'}
591
+ visible={modalVisible}
592
+ onOk={handleSubmit}
593
+ onCancel={() => setModalVisible(false)}
594
+ width={600}
595
+ >
596
+ <Form form={form} layout="vertical">
597
+ <Form.Item
598
+ name="name"
599
+ label="优惠券名称"
600
+ rules={[{ required: true, message: '请输入优惠券名称' }]}
601
+ >
602
+ <Input placeholder="请输入优惠券名称" />
603
+ </Form.Item>
604
+ <Form.Item
605
+ name="type"
606
+ label="优惠券类型"
607
+ rules={[{ required: true, message: '请选择优惠券类型' }]}
608
+ >
609
+ <Radio.Group>
610
+ <Radio value={COUPON_TYPE.FULL_REDUCTION}>满减券</Radio>
611
+ <Radio value={COUPON_TYPE.DISCOUNT}>折扣券</Radio>
612
+ </Radio.Group>
613
+ </Form.Item>
614
+ <Form.Item
615
+ name="amount"
616
+ label="优惠额度"
617
+ rules={[{ required: true, message: '请输入优惠额度' }]}
618
+ >
619
+ <InputNumber
620
+ placeholder="满减金额或折扣"
621
+ min={0}
622
+ style={{ width: '100%' }}
623
+ />
624
+ </Form.Item>
625
+ <Form.Item
626
+ name="minPoint"
627
+ label="最低消费金额"
628
+ rules={[{ required: true, message: '请输入最低消费金额' }]}
629
+ >
630
+ <InputNumber
631
+ placeholder="最低消费金额"
632
+ min={0}
633
+ style={{ width: '100%' }}
634
+ />
635
+ </Form.Item>
636
+ <Form.Item
637
+ name="platform"
638
+ label="适用平台"
639
+ rules={[{ required: true, message: '请选择适用平台' }]}
640
+ >
641
+ <Select placeholder="请选择适用平台">
642
+ {COUPON_PLATFORM_OPTIONS.map((option) => (
643
+ <Select.Option key={option.value} value={option.value}>
644
+ {option.label}
645
+ </Select.Option>
646
+ ))}
647
+ </Select>
648
+ </Form.Item>
649
+ <Form.Item
650
+ name="count"
651
+ label="发行数量"
652
+ rules={[{ required: true, message: '请输入发行数量' }]}
653
+ >
654
+ <InputNumber
655
+ placeholder="发行数量"
656
+ min={1}
657
+ style={{ width: '100%' }}
658
+ />
659
+ </Form.Item>
660
+ <Form.Item
661
+ name="perLimit"
662
+ label="每人限领"
663
+ rules={[{ required: true, message: '请输入每人限领数量' }]}
664
+ >
665
+ <InputNumber
666
+ placeholder="每人限领数量"
667
+ min={1}
668
+ style={{ width: '100%' }}
669
+ />
670
+ </Form.Item>
671
+ <Form.Item
672
+ name="dateRange"
673
+ label="有效期"
674
+ rules={[{ required: true, message: '请选择有效期' }]}
675
+ >
676
+ <RangePicker style={{ width: '100%' }} />
677
+ </Form.Item>
678
+ <Form.Item name="note" label="备注">
679
+ <Input.TextArea rows={3} placeholder="请输入备注" />
680
+ </Form.Item>
681
+ </Form>
682
+ </Modal>
683
+ </div>
684
+ )
685
+ }
686
+
687
+ export default CouponCard
@@ -0,0 +1 @@
1
+ export { default } from './CouponCard'