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,579 @@
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
+ Select,
11
+ DatePicker,
12
+ Switch,
13
+ message,
14
+ Table,
15
+ Tooltip,
16
+ Badge,
17
+ Divider,
18
+ Timeline,
19
+ Statistic,
20
+ Row,
21
+ Col,
22
+ } from 'antd'
23
+ import type { ColumnsType } from 'antd/es/table'
24
+ import {
25
+ PlusOutlined,
26
+ EditOutlined,
27
+ DeleteOutlined,
28
+ ThunderboltOutlined,
29
+ ClockCircleOutlined,
30
+ CheckCircleOutlined,
31
+ CloseCircleOutlined,
32
+ } from '@ant-design/icons'
33
+ import type {
34
+ SmsFlashPromotion,
35
+ PromotionQueryParam,
36
+ } from '../../types/marketing'
37
+ import { PROMOTION_STATUS, PROMOTION_STATUS_OPTIONS } from '../../types/marketing'
38
+ import { DataSourceAdapterFactory } from '../../adapters/DataSourceAdapter'
39
+ import type { DataSourceConfig } from '../../adapters/DataSourceAdapter'
40
+ import './PromotionCard.scss'
41
+
42
+ const { RangePicker } = DatePicker
43
+
44
+ interface PromotionCardProps {
45
+ dataSourceType?: 'rest' | 'mock' | 'variable'
46
+ api?: string
47
+ method?: 'GET' | 'POST'
48
+ mockData?: string
49
+ variableName?: string
50
+ showCreateButton?: boolean
51
+ showFilter?: boolean
52
+ showStatistics?: boolean
53
+ showTimeline?: boolean
54
+ onCreatePromotion?: (promotion: Partial<SmsFlashPromotion>) => void
55
+ onEditPromotion?: (id: number, promotion: Partial<SmsFlashPromotion>) => void
56
+ onDeletePromotion?: (id: number) => void
57
+ onToggleStatus?: (id: number, status: 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: 4,
69
+ list: [
70
+ {
71
+ id: 1,
72
+ title: '双十一大促',
73
+ startDate: '2024-11-01',
74
+ endDate: '2024-11-11',
75
+ status: 1,
76
+ createTime: '2024-10-15 10:00:00',
77
+ },
78
+ {
79
+ id: 2,
80
+ title: '618年中大促',
81
+ startDate: '2024-06-01',
82
+ endDate: '2024-06-18',
83
+ status: 1,
84
+ createTime: '2024-05-20 09:00:00',
85
+ },
86
+ {
87
+ id: 3,
88
+ title: '新春季促销',
89
+ startDate: '2024-01-20',
90
+ endDate: '2024-02-10',
91
+ status: 0,
92
+ createTime: '2024-01-10 08:00:00',
93
+ },
94
+ {
95
+ id: 4,
96
+ title: '会员日特惠',
97
+ startDate: '2024-03-15',
98
+ endDate: '2024-03-17',
99
+ status: 1,
100
+ createTime: '2024-03-01 10:00:00',
101
+ },
102
+ ],
103
+ },
104
+ }
105
+
106
+ const PromotionCard: React.FC<PromotionCardProps> = ({
107
+ dataSourceType = 'mock',
108
+ api,
109
+ method = 'GET',
110
+ mockData,
111
+ variableName,
112
+ showCreateButton = true,
113
+ showFilter = true,
114
+ showStatistics = true,
115
+ showTimeline = true,
116
+ onCreatePromotion,
117
+ onEditPromotion,
118
+ onDeletePromotion,
119
+ onToggleStatus,
120
+ style,
121
+ className,
122
+ }) => {
123
+ const [loading, setLoading] = useState(false)
124
+ const [data, setData] = useState<SmsFlashPromotion[]>([])
125
+ const [total, setTotal] = useState(0)
126
+ const [currentPage, setCurrentPage] = useState(1)
127
+ const [pageSize, setPageSize] = useState(10)
128
+ const [filterTitle, setFilterTitle] = useState('')
129
+ const [filterStatus, setFilterStatus] = useState<number>()
130
+ const [modalVisible, setModalVisible] = useState(false)
131
+ const [editingPromotion, setEditingPromotion] = useState<SmsFlashPromotion | null>(null)
132
+ const [form] = Form.useForm()
133
+
134
+ const internalMockData: SmsFlashPromotion[] = [
135
+ {
136
+ id: 1,
137
+ title: '双十一大促',
138
+ startDate: '2024-11-01',
139
+ endDate: '2024-11-11',
140
+ status: PROMOTION_STATUS.ENABLED,
141
+ createTime: '2024-10-15 10:00:00',
142
+ },
143
+ {
144
+ id: 2,
145
+ title: '618年中大促',
146
+ startDate: '2024-06-01',
147
+ endDate: '2024-06-18',
148
+ status: PROMOTION_STATUS.ENABLED,
149
+ createTime: '2024-05-20 09:00:00',
150
+ },
151
+ {
152
+ id: 3,
153
+ title: '新春季促销',
154
+ startDate: '2024-01-20',
155
+ endDate: '2024-02-10',
156
+ status: PROMOTION_STATUS.DISABLED,
157
+ createTime: '2024-01-10 08:00:00',
158
+ },
159
+ {
160
+ id: 4,
161
+ title: '会员日特惠',
162
+ startDate: '2024-03-15',
163
+ endDate: '2024-03-17',
164
+ status: PROMOTION_STATUS.ENABLED,
165
+ createTime: '2024-03-01 10:00:00',
166
+ },
167
+ ]
168
+
169
+ const dataSourceConfig: DataSourceConfig = useMemo(() => ({
170
+ type: dataSourceType,
171
+ api,
172
+ method,
173
+ mockData: mockData ? (() => {
174
+ try {
175
+ return typeof mockData === 'string' ? JSON.parse(mockData) : mockData
176
+ } catch {
177
+ return defaultMockData
178
+ }
179
+ })() : defaultMockData,
180
+ variableName,
181
+ }), [dataSourceType, api, method, mockData, variableName])
182
+
183
+ const adapter = useMemo(() => {
184
+ return DataSourceAdapterFactory.create(dataSourceConfig)
185
+ }, [dataSourceConfig])
186
+
187
+ useEffect(() => {
188
+ fetchData()
189
+ }, [currentPage, pageSize, adapter])
190
+
191
+ const fetchData = async () => {
192
+ setLoading(true)
193
+ try {
194
+ const result = await adapter.fetch({
195
+ pageNum: currentPage,
196
+ pageSize,
197
+ title: filterTitle,
198
+ status: filterStatus,
199
+ })
200
+
201
+ let list: SmsFlashPromotion[] = []
202
+ let totalCount = 0
203
+
204
+ if (result?.data?.list) {
205
+ list = result.data.list
206
+ totalCount = result.data.total || list.length
207
+ } else if (Array.isArray(result?.data)) {
208
+ list = result.data
209
+ totalCount = list.length
210
+ } else if (Array.isArray(result)) {
211
+ list = result
212
+ totalCount = list.length
213
+ }
214
+
215
+ if (filterTitle || filterStatus !== undefined) {
216
+ list = list.filter((item: SmsFlashPromotion) => {
217
+ if (filterTitle && !item.title.toLowerCase().includes(filterTitle.toLowerCase())) {
218
+ return false
219
+ }
220
+ if (filterStatus !== undefined && item.status !== filterStatus) {
221
+ return false
222
+ }
223
+ return true
224
+ })
225
+ totalCount = list.length
226
+ }
227
+
228
+ setData(list)
229
+ setTotal(totalCount)
230
+ } catch (error) {
231
+ console.error('Failed to fetch promotion list:', error)
232
+ message.error('获取促销活动列表失败')
233
+ setData(internalMockData)
234
+ setTotal(internalMockData.length)
235
+ } finally {
236
+ setLoading(false)
237
+ }
238
+ }
239
+
240
+ const handleSearch = () => {
241
+ setCurrentPage(1)
242
+ fetchData()
243
+ }
244
+
245
+ const handleReset = () => {
246
+ setFilterTitle('')
247
+ setFilterStatus(undefined)
248
+ setCurrentPage(1)
249
+ fetchData()
250
+ }
251
+
252
+ const handleCreate = () => {
253
+ setEditingPromotion(null)
254
+ form.resetFields()
255
+ setModalVisible(true)
256
+ }
257
+
258
+ const handleEdit = (record: SmsFlashPromotion) => {
259
+ setEditingPromotion(record)
260
+ form.setFieldsValue({
261
+ ...record,
262
+ dateRange: [record.startDate, record.endDate],
263
+ })
264
+ setModalVisible(true)
265
+ }
266
+
267
+ const handleDelete = (id: number) => {
268
+ Modal.confirm({
269
+ title: '确认删除',
270
+ content: '确定要删除该促销活动吗?',
271
+ onOk: () => {
272
+ onDeletePromotion?.(id)
273
+ message.success('删除成功')
274
+ fetchData()
275
+ },
276
+ })
277
+ }
278
+
279
+ const handleToggleStatus = (record: SmsFlashPromotion) => {
280
+ const newStatus =
281
+ record.status === PROMOTION_STATUS.ENABLED
282
+ ? PROMOTION_STATUS.DISABLED
283
+ : PROMOTION_STATUS.ENABLED
284
+ onToggleStatus?.(record.id, newStatus)
285
+ message.success(
286
+ newStatus === PROMOTION_STATUS.ENABLED ? '已启用' : '已禁用'
287
+ )
288
+ fetchData()
289
+ }
290
+
291
+ const handleSubmit = async () => {
292
+ try {
293
+ const values = await form.validateFields()
294
+ const promotionData = {
295
+ ...values,
296
+ startDate: values.dateRange?.[0],
297
+ endDate: values.dateRange?.[1],
298
+ }
299
+ if (editingPromotion) {
300
+ onEditPromotion?.(editingPromotion.id, promotionData)
301
+ message.success('编辑成功')
302
+ } else {
303
+ onCreatePromotion?.(promotionData)
304
+ message.success('创建成功')
305
+ }
306
+ setModalVisible(false)
307
+ fetchData()
308
+ } catch (error) {
309
+ console.error('Validation failed:', error)
310
+ }
311
+ }
312
+
313
+ const getStatusTag = (status: number) => {
314
+ const statusMap: Record<number, { color: string; text: string; icon: React.ReactNode }> = {
315
+ [PROMOTION_STATUS.ENABLED]: {
316
+ color: 'success',
317
+ text: '已启用',
318
+ icon: <CheckCircleOutlined />,
319
+ },
320
+ [PROMOTION_STATUS.DISABLED]: {
321
+ color: 'default',
322
+ text: '已禁用',
323
+ icon: <CloseCircleOutlined />,
324
+ },
325
+ }
326
+ const { color, text, icon } = statusMap[status] || {
327
+ color: 'default',
328
+ text: '未知',
329
+ icon: null,
330
+ }
331
+ return (
332
+ <Tag color={color} icon={icon}>
333
+ {text}
334
+ </Tag>
335
+ )
336
+ }
337
+
338
+ const columns: ColumnsType<SmsFlashPromotion> = [
339
+ {
340
+ title: '活动名称',
341
+ dataIndex: 'title',
342
+ key: 'title',
343
+ width: 200,
344
+ render: (text: string) => (
345
+ <Tooltip title={text}>
346
+ <span className="promotion-title">
347
+ <ThunderboltOutlined style={{ color: '#faad14', marginRight: 8 }} />
348
+ {text}
349
+ </span>
350
+ </Tooltip>
351
+ ),
352
+ },
353
+ {
354
+ title: '活动时间',
355
+ key: 'dateRange',
356
+ width: 220,
357
+ render: (_, record) => (
358
+ <Space>
359
+ <ClockCircleOutlined />
360
+ <span>
361
+ {record.startDate} ~ {record.endDate}
362
+ </span>
363
+ </Space>
364
+ ),
365
+ },
366
+ {
367
+ title: '状态',
368
+ dataIndex: 'status',
369
+ key: 'status',
370
+ width: 100,
371
+ render: (status: number) => getStatusTag(status),
372
+ },
373
+ {
374
+ title: '创建时间',
375
+ dataIndex: 'createTime',
376
+ key: 'createTime',
377
+ width: 180,
378
+ },
379
+ {
380
+ title: '操作',
381
+ key: 'action',
382
+ width: 200,
383
+ render: (_, record) => (
384
+ <Space>
385
+ <Button
386
+ type="link"
387
+ size="small"
388
+ icon={<EditOutlined />}
389
+ onClick={() => handleEdit(record)}
390
+ >
391
+ 编辑
392
+ </Button>
393
+ <Button
394
+ type="link"
395
+ size="small"
396
+ onClick={() => handleToggleStatus(record)}
397
+ >
398
+ {record.status === PROMOTION_STATUS.ENABLED ? '禁用' : '启用'}
399
+ </Button>
400
+ <Button
401
+ type="link"
402
+ size="small"
403
+ danger
404
+ icon={<DeleteOutlined />}
405
+ onClick={() => handleDelete(record.id)}
406
+ >
407
+ 删除
408
+ </Button>
409
+ </Space>
410
+ ),
411
+ },
412
+ ]
413
+
414
+ const statistics = {
415
+ totalPromotions: data.length,
416
+ activePromotions: data.filter((p) => p.status === PROMOTION_STATUS.ENABLED).length,
417
+ upcomingPromotions: data.filter((p) => {
418
+ const today = new Date()
419
+ const startDate = new Date(p.startDate)
420
+ return startDate > today
421
+ }).length,
422
+ }
423
+
424
+ const timelineData = data
425
+ .filter((p) => p.status === PROMOTION_STATUS.ENABLED)
426
+ .slice(0, 5)
427
+ .map((p) => ({
428
+ color: 'green',
429
+ children: (
430
+ <div>
431
+ <div className="timeline-title">{p.title}</div>
432
+ <div className="timeline-date">
433
+ {p.startDate} ~ {p.endDate}
434
+ </div>
435
+ </div>
436
+ ),
437
+ }))
438
+
439
+ return (
440
+ <div className={`mall-promotion-card ${className || ''}`} style={style}>
441
+ <Card>
442
+ {showStatistics && (
443
+ <div className="statistics-section">
444
+ <Row gutter={16}>
445
+ <Col span={8}>
446
+ <Statistic
447
+ title="促销活动总数"
448
+ value={statistics.totalPromotions}
449
+ prefix={<ThunderboltOutlined />}
450
+ />
451
+ </Col>
452
+ <Col span={8}>
453
+ <Statistic
454
+ title="进行中的活动"
455
+ value={statistics.activePromotions}
456
+ valueStyle={{ color: '#3f8600' }}
457
+ />
458
+ </Col>
459
+ <Col span={8}>
460
+ <Statistic
461
+ title="即将开始"
462
+ value={statistics.upcomingPromotions}
463
+ valueStyle={{ color: '#faad14' }}
464
+ />
465
+ </Col>
466
+ </Row>
467
+ </div>
468
+ )}
469
+
470
+ {showFilter && (
471
+ <div className="filter-section">
472
+ <Space wrap>
473
+ <Input
474
+ placeholder="活动名称"
475
+ value={filterTitle}
476
+ onChange={(e) => setFilterTitle(e.target.value)}
477
+ style={{ width: 200 }}
478
+ />
479
+ <Select
480
+ placeholder="活动状态"
481
+ value={filterStatus}
482
+ onChange={setFilterStatus}
483
+ style={{ width: 120 }}
484
+ allowClear
485
+ >
486
+ {PROMOTION_STATUS_OPTIONS.map((option) => (
487
+ <Select.Option key={option.value} value={option.value}>
488
+ {option.label}
489
+ </Select.Option>
490
+ ))}
491
+ </Select>
492
+ <Button type="primary" onClick={handleSearch}>
493
+ 查询
494
+ </Button>
495
+ <Button onClick={handleReset}>重置</Button>
496
+ </Space>
497
+ </div>
498
+ )}
499
+
500
+ {showCreateButton && (
501
+ <div className="action-section">
502
+ <Button type="primary" icon={<PlusOutlined />} onClick={handleCreate}>
503
+ 创建促销活动
504
+ </Button>
505
+ </div>
506
+ )}
507
+
508
+ <div className="content-section">
509
+ <div className="table-section">
510
+ <Table
511
+ columns={columns}
512
+ dataSource={data}
513
+ rowKey="id"
514
+ loading={loading}
515
+ pagination={{
516
+ current: currentPage,
517
+ pageSize,
518
+ total,
519
+ showSizeChanger: true,
520
+ showQuickJumper: true,
521
+ showTotal: (total) => `共 ${total} 条`,
522
+ onChange: (page, pageSize) => {
523
+ setCurrentPage(page)
524
+ setPageSize(pageSize)
525
+ },
526
+ }}
527
+ />
528
+ </div>
529
+
530
+ {showTimeline && timelineData.length > 0 && (
531
+ <div className="timeline-section">
532
+ <Card title="活动时间线" size="small">
533
+ <Timeline items={timelineData} />
534
+ </Card>
535
+ </div>
536
+ )}
537
+ </div>
538
+ </Card>
539
+
540
+ <Modal
541
+ title={editingPromotion ? '编辑促销活动' : '创建促销活动'}
542
+ visible={modalVisible}
543
+ onOk={handleSubmit}
544
+ onCancel={() => setModalVisible(false)}
545
+ width={500}
546
+ >
547
+ <Form form={form} layout="vertical">
548
+ <Form.Item
549
+ name="title"
550
+ label="活动名称"
551
+ rules={[{ required: true, message: '请输入活动名称' }]}
552
+ >
553
+ <Input placeholder="请输入活动名称" />
554
+ </Form.Item>
555
+ <Form.Item
556
+ name="dateRange"
557
+ label="活动时间"
558
+ rules={[{ required: true, message: '请选择活动时间' }]}
559
+ >
560
+ <RangePicker style={{ width: '100%' }} />
561
+ </Form.Item>
562
+ <Form.Item
563
+ name="status"
564
+ label="活动状态"
565
+ initialValue={PROMOTION_STATUS.ENABLED}
566
+ >
567
+ <Switch
568
+ checkedChildren="启用"
569
+ unCheckedChildren="禁用"
570
+ defaultChecked
571
+ />
572
+ </Form.Item>
573
+ </Form>
574
+ </Modal>
575
+ </div>
576
+ )
577
+ }
578
+
579
+ export default PromotionCard
@@ -0,0 +1 @@
1
+ export { default } from './PromotionCard'
@@ -0,0 +1,77 @@
1
+ .mall-role-card {
2
+ .statistics-section {
3
+ padding: 16px;
4
+ background: linear-gradient(135deg, #f0f5ff 0%, #f5f0ff 100%);
5
+ border-radius: 4px;
6
+ margin-bottom: 16px;
7
+
8
+ .stat-item {
9
+ text-align: center;
10
+
11
+ .stat-value {
12
+ font-size: 24px;
13
+ font-weight: 600;
14
+ color: #722ed1;
15
+ }
16
+
17
+ .stat-label {
18
+ font-size: 12px;
19
+ color: #8c8c8c;
20
+ margin-top: 4px;
21
+ }
22
+ }
23
+ }
24
+
25
+ .content-section {
26
+ display: flex;
27
+ gap: 16px;
28
+
29
+ .table-section {
30
+ flex: 1;
31
+ }
32
+
33
+ .permission-section {
34
+ width: 280px;
35
+
36
+ .ant-card {
37
+ height: 100%;
38
+ }
39
+
40
+ .ant-tree {
41
+ font-size: 13px;
42
+ }
43
+ }
44
+ }
45
+
46
+ .filter-section {
47
+ margin-bottom: 16px;
48
+ padding: 16px;
49
+ background: #fafafa;
50
+ border-radius: 4px;
51
+ }
52
+
53
+ .action-section {
54
+ margin-bottom: 16px;
55
+ }
56
+
57
+ .role-name {
58
+ font-weight: 500;
59
+ color: #262626;
60
+ }
61
+
62
+ .ant-table {
63
+ .ant-tag {
64
+ margin: 0;
65
+ }
66
+ }
67
+
68
+ @media (max-width: 1200px) {
69
+ .content-section {
70
+ flex-direction: column;
71
+
72
+ .permission-section {
73
+ width: 100%;
74
+ }
75
+ }
76
+ }
77
+ }