vue3-admin-gpt 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 (118) hide show
  1. package/.env.development +14 -0
  2. package/.env.production +14 -0
  3. package/LICENSE +21 -0
  4. package/README.en.md +106 -0
  5. package/README.md +104 -0
  6. package/build-zip.cjs +53 -0
  7. package/cli.js +110 -0
  8. package/jsconfig.json +9 -0
  9. package/package.json +92 -0
  10. package/public/index.html +20 -0
  11. package/public/robots.txt +2 -0
  12. package/rspack.config.js +282 -0
  13. package/rspack.js +162 -0
  14. package/src/App.vue +9 -0
  15. package/src/api/icon.js +9 -0
  16. package/src/api/router.js +9 -0
  17. package/src/api/table.js +25 -0
  18. package/src/api/tree.js +9 -0
  19. package/src/api/user.js +34 -0
  20. package/src/assets/error_images/401.png +0 -0
  21. package/src/assets/error_images/404.png +0 -0
  22. package/src/assets/error_images/cloud.png +0 -0
  23. package/src/assets/login_images/background.jpg +0 -0
  24. package/src/assets/logo.png +0 -0
  25. package/src/assets/qr_logo/lqr_logo.png +0 -0
  26. package/src/assets/vuejs-fill.svg +4 -0
  27. package/src/components/VabPageHeader/index.vue +133 -0
  28. package/src/config/index.js +7 -0
  29. package/src/config/net.config.js +20 -0
  30. package/src/config/permission.js +136 -0
  31. package/src/config/setting.config.js +62 -0
  32. package/src/config/settings.js +6 -0
  33. package/src/config/theme.config.js +14 -0
  34. package/src/layouts/EmptyLayout.vue +3 -0
  35. package/src/layouts/components/VabAppMain/index.vue +109 -0
  36. package/src/layouts/components/VabAvatar/index.vue +255 -0
  37. package/src/layouts/components/VabBreadcrumb/index.vue +61 -0
  38. package/src/layouts/components/VabFullScreen/index.vue +61 -0
  39. package/src/layouts/components/VabLogo/index.vue +94 -0
  40. package/src/layouts/components/VabNav/index.vue +176 -0
  41. package/src/layouts/components/VabSide/components/VabMenuItem.vue +80 -0
  42. package/src/layouts/components/VabSide/components/VabSideItem.vue +100 -0
  43. package/src/layouts/components/VabSide/components/VabSubmenu.vue +56 -0
  44. package/src/layouts/components/VabSide/index.vue +123 -0
  45. package/src/layouts/components/VabTabs/index.vue +500 -0
  46. package/src/layouts/components/VabTheme/index.vue +603 -0
  47. package/src/layouts/components/VabTop/index.vue +286 -0
  48. package/src/layouts/export.js +29 -0
  49. package/src/layouts/index.vue +339 -0
  50. package/src/main.js +40 -0
  51. package/src/plugins/echarts.js +4 -0
  52. package/src/plugins/index.js +44 -0
  53. package/src/plugins/support.js +16 -0
  54. package/src/router/index.js +400 -0
  55. package/src/store/index.js +26 -0
  56. package/src/store/modules/errorLog.js +27 -0
  57. package/src/store/modules/routes.js +60 -0
  58. package/src/store/modules/settings.js +73 -0
  59. package/src/store/modules/table.js +22 -0
  60. package/src/store/modules/tabsBar.js +109 -0
  61. package/src/store/modules/user.js +131 -0
  62. package/src/styles/element-variables.scss +13 -0
  63. package/src/styles/loading.scss +345 -0
  64. package/src/styles/nav-icons.scss +52 -0
  65. package/src/styles/normalize.scss +353 -0
  66. package/src/styles/spinner/dots.css +68 -0
  67. package/src/styles/spinner/gauge.css +104 -0
  68. package/src/styles/spinner/inner-circles.css +51 -0
  69. package/src/styles/spinner/plus.css +341 -0
  70. package/src/styles/themes/default.scss +1 -0
  71. package/src/styles/transition.scss +18 -0
  72. package/src/styles/vab.scss +476 -0
  73. package/src/styles/variables.scss +69 -0
  74. package/src/utils/accessToken.js +56 -0
  75. package/src/utils/eventBus.js +8 -0
  76. package/src/utils/handleRoutes.js +100 -0
  77. package/src/utils/index.js +231 -0
  78. package/src/utils/message.js +67 -0
  79. package/src/utils/pageTitle.js +11 -0
  80. package/src/utils/password.js +43 -0
  81. package/src/utils/permission.js +19 -0
  82. package/src/utils/request.js +187 -0
  83. package/src/utils/static.js +81 -0
  84. package/src/utils/vab.js +218 -0
  85. package/src/utils/validate.js +48 -0
  86. package/src/views/401.vue +302 -0
  87. package/src/views/404.vue +302 -0
  88. package/src/views/demo/index.vue +591 -0
  89. package/src/views/index/index.vue +1489 -0
  90. package/src/views/login/index.vue +456 -0
  91. package/src/views/register/index.vue +524 -0
  92. package/src/views/vab/calendar.vue +488 -0
  93. package/src/views/vab/campaign.vue +1006 -0
  94. package/src/views/vab/chart.vue +189 -0
  95. package/src/views/vab/customer.vue +666 -0
  96. package/src/views/vab/editor.vue +84 -0
  97. package/src/views/vab/form.vue +151 -0
  98. package/src/views/vab/help.vue +390 -0
  99. package/src/views/vab/icon.vue +113 -0
  100. package/src/views/vab/knowledge.vue +820 -0
  101. package/src/views/vab/nested/menu1/menu2/menu3.vue +29 -0
  102. package/src/views/vab/nested/menu1/menu2.vue +33 -0
  103. package/src/views/vab/nested/menu1.vue +33 -0
  104. package/src/views/vab/nested.vue +97 -0
  105. package/src/views/vab/notification.vue +416 -0
  106. package/src/views/vab/order.vue +507 -0
  107. package/src/views/vab/permissions.vue +214 -0
  108. package/src/views/vab/product.vue +724 -0
  109. package/src/views/vab/project.vue +559 -0
  110. package/src/views/vab/settings.vue +319 -0
  111. package/src/views/vab/statistics.vue +431 -0
  112. package/src/views/vab/table.vue +110 -0
  113. package/src/views/vab/task.vue +613 -0
  114. package/src/views/vab/team.vue +662 -0
  115. package/src/views/vab/tree.vue +44 -0
  116. package/src/views/vab/upload.vue +180 -0
  117. package/src/views/vab/vue3Demo/index.vue +103 -0
  118. package/src/views/vab/workflow.vue +863 -0
@@ -0,0 +1,1006 @@
1
+ <template>
2
+ <div class="campaign-container">
3
+ <el-card shadow="never">
4
+ <template #header>
5
+ <div class="card-header">
6
+ <span>营销活动</span>
7
+ <div class="header-actions">
8
+ <el-input
9
+ v-model="searchText"
10
+ placeholder="搜索活动..."
11
+ clearable
12
+ style="width: 200px; margin-right: 10px"
13
+ >
14
+ <template #prefix>
15
+ <el-icon><Search /></el-icon>
16
+ </template>
17
+ </el-input>
18
+ <el-button type="primary" @click="showAddCampaignDialog">创建活动</el-button>
19
+ </div>
20
+ </div>
21
+ </template>
22
+
23
+ <el-tabs v-model="activeTab" @tab-change="handleTabChange">
24
+ <el-tab-pane label="所有活动" name="all">
25
+ <el-table
26
+ :data="filteredCampaigns"
27
+ style="width: 100%"
28
+ row-key="id"
29
+ v-loading="loading"
30
+ >
31
+ <el-table-column prop="name" label="活动名称" min-width="200">
32
+ <template #default="{ row }">
33
+ <div class="campaign-name">
34
+ <el-avatar :size="32" :style="{ backgroundColor: getCampaignColor(row.type) }">
35
+ <el-icon><component :is="getCampaignIcon(row.type)" /></el-icon>
36
+ </el-avatar>
37
+ <span style="margin-left: 10px">{{ row.name }}</span>
38
+ </div>
39
+ </template>
40
+ </el-table-column>
41
+ <el-table-column prop="type" label="活动类型" width="120">
42
+ <template #default="{ row }">
43
+ <el-tag :type="getCampaignType(row.type)">
44
+ {{ getCampaignText(row.type) }}
45
+ </el-tag>
46
+ </template>
47
+ </el-table-column>
48
+ <el-table-column label="活动时间" min-width="200">
49
+ <template #default="{ row }">
50
+ <div>{{ row.startTime }} 至 {{ row.endTime }}</div>
51
+ </template>
52
+ </el-table-column>
53
+ <el-table-column label="状态" width="100">
54
+ <template #default="{ row }">
55
+ <el-tag :type="getStatusType(row.status)">
56
+ {{ getStatusText(row.status) }}
57
+ </el-tag>
58
+ </template>
59
+ </el-table-column>
60
+ <el-table-column label="参与人数" width="100">
61
+ <template #default="{ row }">
62
+ {{ row.participants }}
63
+ </template>
64
+ </el-table-column>
65
+ <el-table-column label="转化率" width="100">
66
+ <template #default="{ row }">
67
+ {{ row.conversionRate }}%
68
+ </template>
69
+ </el-table-column>
70
+ <el-table-column label="操作" width="200">
71
+ <template #default="{ row }">
72
+ <el-button type="text" @click="viewCampaign(row)">查看</el-button>
73
+ <el-button type="text" @click="editCampaign(row)">编辑</el-button>
74
+ <el-button
75
+ v-if="row.status === 'draft' || row.status === 'pending'"
76
+ type="text"
77
+ @click="startCampaign(row)"
78
+ >
79
+ 启动
80
+ </el-button>
81
+ <el-button
82
+ v-if="row.status === 'active'"
83
+ type="text"
84
+ @click="stopCampaign(row)"
85
+ >
86
+ 停止
87
+ </el-button>
88
+ </template>
89
+ </el-table-column>
90
+ </el-table>
91
+ </el-tab-pane>
92
+
93
+ <el-tab-pane label="进行中" name="active">
94
+ <el-table
95
+ :data="activeCampaigns"
96
+ style="width: 100%"
97
+ row-key="id"
98
+ >
99
+ <el-table-column prop="name" label="活动名称" min-width="200">
100
+ <template #default="{ row }">
101
+ <div class="campaign-name">
102
+ <el-avatar :size="32" :style="{ backgroundColor: getCampaignColor(row.type) }">
103
+ <el-icon><component :is="getCampaignIcon(row.type)" /></el-icon>
104
+ </el-avatar>
105
+ <span style="margin-left: 10px">{{ row.name }}</span>
106
+ </div>
107
+ </template>
108
+ </el-table-column>
109
+ <el-table-column prop="type" label="活动类型" width="120">
110
+ <template #default="{ row }">
111
+ <el-tag :type="getCampaignType(row.type)">
112
+ {{ getCampaignText(row.type) }}
113
+ </el-tag>
114
+ </template>
115
+ </el-table-column>
116
+ <el-table-column label="活动时间" min-width="200">
117
+ <template #default="{ row }">
118
+ <div>{{ row.startTime }} 至 {{ row.endTime }}</div>
119
+ </template>
120
+ </el-table-column>
121
+ <el-table-column label="参与人数" width="100">
122
+ <template #default="{ row }">
123
+ {{ row.participants }}
124
+ </template>
125
+ </el-table-column>
126
+ <el-table-column label="转化率" width="100">
127
+ <template #default="{ row }">
128
+ {{ row.conversionRate }}%
129
+ </template>
130
+ </el-table-column>
131
+ <el-table-column label="操作" width="150">
132
+ <template #default="{ row }">
133
+ <el-button type="text" @click="viewCampaign(row)">查看</el-button>
134
+ <el-button type="text" @click="stopCampaign(row)">停止</el-button>
135
+ </template>
136
+ </el-table-column>
137
+ </el-table>
138
+ </el-tab-pane>
139
+
140
+ <el-tab-pane label="已结束" name="ended">
141
+ <el-table
142
+ :data="endedCampaigns"
143
+ style="width: 100%"
144
+ row-key="id"
145
+ >
146
+ <el-table-column prop="name" label="活动名称" min-width="200">
147
+ <template #default="{ row }">
148
+ <div class="campaign-name">
149
+ <el-avatar :size="32" :style="{ backgroundColor: getCampaignColor(row.type) }">
150
+ <el-icon><component :is="getCampaignIcon(row.type)" /></el-icon>
151
+ </el-avatar>
152
+ <span style="margin-left: 10px">{{ row.name }}</span>
153
+ </div>
154
+ </template>
155
+ </el-table-column>
156
+ <el-table-column prop="type" label="活动类型" width="120">
157
+ <template #default="{ row }">
158
+ <el-tag :type="getCampaignType(row.type)">
159
+ {{ getCampaignText(row.type) }}
160
+ </el-tag>
161
+ </template>
162
+ </el-table-column>
163
+ <el-table-column label="活动时间" min-width="200">
164
+ <template #default="{ row }">
165
+ <div>{{ row.startTime }} 至 {{ row.endTime }}</div>
166
+ </template>
167
+ </el-table-column>
168
+ <el-table-column label="参与人数" width="100">
169
+ <template #default="{ row }">
170
+ {{ row.participants }}
171
+ </template>
172
+ </el-table-column>
173
+ <el-table-column label="转化率" width="100">
174
+ <template #default="{ row }">
175
+ {{ row.conversionRate }}%
176
+ </template>
177
+ </el-table-column>
178
+ <el-table-column label="操作" width="100">
179
+ <template #default="{ row }">
180
+ <el-button type="text" @click="viewCampaign(row)">查看</el-button>
181
+ </template>
182
+ </el-table-column>
183
+ </el-table>
184
+ </el-tab-pane>
185
+ </el-tabs>
186
+
187
+ <div class="pagination-container">
188
+ <el-pagination
189
+ v-model:current-page="currentPage"
190
+ v-model:page-size="pageSize"
191
+ :page-sizes="[10, 20, 50, 100]"
192
+ :total="totalCampaigns"
193
+ layout="total, sizes, prev, pager, next, jumper"
194
+ @size-change="handleSizeChange"
195
+ @current-change="handleCurrentChange"
196
+ />
197
+ </div>
198
+ </el-card>
199
+
200
+ <!-- 创建/编辑活动对话框 -->
201
+ <el-dialog
202
+ v-model="campaignDialogVisible"
203
+ :title="editingCampaign ? '编辑活动' : '创建活动'"
204
+ width="800px"
205
+ >
206
+ <el-form
207
+ ref="campaignFormRef"
208
+ :model="campaignForm"
209
+ :rules="campaignRules"
210
+ label-width="100px"
211
+ >
212
+ <el-row :gutter="20">
213
+ <el-col :span="12">
214
+ <el-form-item label="活动名称" prop="name">
215
+ <el-input v-model="campaignForm.name" />
216
+ </el-form-item>
217
+
218
+ <el-form-item label="活动类型" prop="type">
219
+ <el-select v-model="campaignForm.type" placeholder="请选择活动类型" style="width: 100%">
220
+ <el-option label="折扣活动" value="discount">
221
+ <el-icon><Discount /></el-icon>
222
+ <span style="margin-left: 10px">折扣活动</span>
223
+ </el-option>
224
+ <el-option label="满减活动" value="reduction">
225
+ <el-icon><Coin /></el-icon>
226
+ <span style="margin-left: 10px">满减活动</span>
227
+ </el-option>
228
+ <el-option label="赠品活动" value="gift">
229
+ <el-icon><Present /></el-icon>
230
+ <span style="margin-left: 10px">赠品活动</span>
231
+ </el-option>
232
+ <el-option label="秒杀活动" value="seckill">
233
+ <el-icon><Lightning /></el-icon>
234
+ <span style="margin-left: 10px">秒杀活动</span>
235
+ </el-option>
236
+ </el-select>
237
+ </el-form-item>
238
+
239
+ <el-form-item label="活动时间" prop="timeRange">
240
+ <el-date-picker
241
+ v-model="campaignForm.timeRange"
242
+ type="datetimerange"
243
+ range-separator="至"
244
+ start-placeholder="开始时间"
245
+ end-placeholder="结束时间"
246
+ format="YYYY-MM-DD HH:mm"
247
+ value-format="YYYY-MM-DD HH:mm"
248
+ style="width: 100%"
249
+ />
250
+ </el-form-item>
251
+
252
+ <el-form-item label="目标用户" prop="targetUsers">
253
+ <el-select
254
+ v-model="campaignForm.targetUsers"
255
+ multiple
256
+ placeholder="请选择目标用户"
257
+ style="width: 100%"
258
+ >
259
+ <el-option label="所有用户" value="all"></el-option>
260
+ <el-option label="新用户" value="new"></el-option>
261
+ <el-option label="老用户" value="old"></el-option>
262
+ <el-option label="VIP用户" value="vip"></el-option>
263
+ </el-select>
264
+ </el-form-item>
265
+ </el-col>
266
+
267
+ <el-col :span="12">
268
+ <el-form-item
269
+ v-if="campaignForm.type === 'discount'"
270
+ label="折扣率"
271
+ prop="discountRate"
272
+ >
273
+ <el-input-number
274
+ v-model="campaignForm.discountRate"
275
+ :min="0"
276
+ :max="100"
277
+ controls-position="right"
278
+ style="width: 100%"
279
+ >
280
+ <template #append>%</template>
281
+ </el-input-number>
282
+ </el-form-item>
283
+
284
+ <el-form-item
285
+ v-if="campaignForm.type === 'reduction'"
286
+ label="满减条件"
287
+ prop="reductionCondition"
288
+ >
289
+ <el-input v-model="campaignForm.reductionCondition" placeholder="例如:满300减50">
290
+ <template #prepend>满</template>
291
+ <template #append>元减</template>
292
+ </el-input>
293
+ </el-form-item>
294
+
295
+ <el-form-item
296
+ v-if="campaignForm.type === 'gift'"
297
+ label="赠品"
298
+ prop="gift"
299
+ >
300
+ <el-input v-model="campaignForm.gift" placeholder="请输入赠品信息" />
301
+ </el-form-item>
302
+
303
+ <el-form-item
304
+ v-if="campaignForm.type === 'seckill'"
305
+ label="秒杀价"
306
+ prop="seckillPrice"
307
+ >
308
+ <el-input-number
309
+ v-model="campaignForm.seckillPrice"
310
+ :min="0"
311
+ controls-position="right"
312
+ style="width: 100%"
313
+ >
314
+ <template #append>元</template>
315
+ </el-input-number>
316
+ </el-form-item>
317
+
318
+ <el-form-item label="适用商品">
319
+ <el-select
320
+ v-model="campaignForm.products"
321
+ multiple
322
+ filterable
323
+ remote
324
+ :remote-method="searchProducts"
325
+ :loading="searchingProducts"
326
+ placeholder="请选择适用商品"
327
+ style="width: 100%"
328
+ >
329
+ <el-option
330
+ v-for="product in availableProducts"
331
+ :key="product.id"
332
+ :label="product.name"
333
+ :value="product.id"
334
+ >
335
+ <span style="float: left">{{ product.name }}</span>
336
+ <span style="float: right; color: #8492a6; font-size: 13px">
337
+ ¥{{ product.price }}
338
+ </span>
339
+ </el-option>
340
+ </el-select>
341
+ </el-form-item>
342
+
343
+ <el-form-item label="活动预算" prop="budget">
344
+ <el-input-number
345
+ v-model="campaignForm.budget"
346
+ :min="0"
347
+ controls-position="right"
348
+ style="width: 100%"
349
+ >
350
+ <template #append>元</template>
351
+ </el-input-number>
352
+ </el-form-item>
353
+ </el-col>
354
+ </el-row>
355
+ </el-form>
356
+
357
+ <template #footer>
358
+ <span class="dialog-footer">
359
+ <el-button @click="campaignDialogVisible = false">取消</el-button>
360
+ <el-button
361
+ type="primary"
362
+ @click="saveCampaign"
363
+ >
364
+ 保存
365
+ </el-button>
366
+ </span>
367
+ </template>
368
+ </el-dialog>
369
+
370
+ <!-- 活动详情对话框 -->
371
+ <el-dialog
372
+ v-model="detailDialogVisible"
373
+ title="活动详情"
374
+ width="1000px"
375
+ >
376
+ <el-row :gutter="20">
377
+ <el-col :span="16">
378
+ <el-descriptions :column="1" border>
379
+ <el-descriptions-item label="活动名称">{{ detailCampaign.name }}</el-descriptions-item>
380
+ <el-descriptions-item label="活动类型">
381
+ <el-tag :type="getCampaignType(detailCampaign.type)">
382
+ {{ getCampaignText(detailCampaign.type) }}
383
+ </el-tag>
384
+ </el-descriptions-item>
385
+ <el-descriptions-item label="活动时间">
386
+ {{ detailCampaign.startTime }} 至 {{ detailCampaign.endTime }}
387
+ </el-descriptions-item>
388
+ <el-descriptions-item label="状态">
389
+ <el-tag :type="getStatusType(detailCampaign.status)">
390
+ {{ getStatusText(detailCampaign.status) }}
391
+ </el-tag>
392
+ </el-descriptions-item>
393
+ <el-descriptions-item label="目标用户">{{ getTargetUsersText(detailCampaign.targetUsers) }}</el-descriptions-item>
394
+ <el-descriptions-item label="活动规则">{{ getCampaignRule(detailCampaign) }}</el-descriptions-item>
395
+ <el-descriptions-item label="活动预算">¥{{ detailCampaign.budget }}</el-descriptions-item>
396
+ <el-descriptions-item label="创建时间">{{ detailCampaign.createTime }}</el-descriptions-item>
397
+ </el-descriptions>
398
+ </el-col>
399
+ <el-col :span="8">
400
+ <div class="campaign-icon-detail" :style="{ backgroundColor: getCampaignColor(detailCampaign.type) }">
401
+ <el-icon :size="60"><component :is="getCampaignIcon(detailCampaign.type)" /></el-icon>
402
+ </div>
403
+ </el-col>
404
+ </el-row>
405
+
406
+ <el-tabs v-model="campaignActiveTab" style="margin-top: 20px">
407
+ <el-tab-pane label="数据统计" name="statistics">
408
+ <el-row :gutter="20" style="margin-bottom: 20px">
409
+ <el-col :span="6">
410
+ <el-card class="stat-card" shadow="never">
411
+ <div class="stat-title">参与人数</div>
412
+ <div class="stat-value">{{ detailCampaign.participants }}</div>
413
+ </el-card>
414
+ </el-col>
415
+ <el-col :span="6">
416
+ <el-card class="stat-card" shadow="never">
417
+ <div class="stat-title">订单数量</div>
418
+ <div class="stat-value">{{ detailCampaign.orders }}</div>
419
+ </el-card>
420
+ </el-col>
421
+ <el-col :span="6">
422
+ <el-card class="stat-card" shadow="never">
423
+ <div class="stat-title">销售额</div>
424
+ <div class="stat-value">¥{{ detailCampaign.sales }}</div>
425
+ </el-card>
426
+ </el-col>
427
+ <el-col :span="6">
428
+ <el-card class="stat-card" shadow="never">
429
+ <div class="stat-title">转化率</div>
430
+ <div class="stat-value">{{ detailCampaign.conversionRate }}%</div>
431
+ </el-card>
432
+ </el-col>
433
+ </el-row>
434
+
435
+ <el-row :gutter="20">
436
+ <el-col :span="12">
437
+ <el-card shadow="never">
438
+ <div ref="conversionChart" style="height: 300px"></div>
439
+ </el-card>
440
+ </el-col>
441
+ <el-col :span="12">
442
+ <el-card shadow="never">
443
+ <div ref="salesChart" style="height: 300px"></div>
444
+ </el-card>
445
+ </el-col>
446
+ </el-row>
447
+ </el-tab-pane>
448
+ <el-tab-pane label="参与用户" name="participants">
449
+ <el-table :data="campaignParticipants" style="width: 100%">
450
+ <el-table-column prop="id" label="用户ID" width="100" />
451
+ <el-table-column prop="name" label="用户名" width="150" />
452
+ <el-table-column prop="phone" label="手机号" width="150" />
453
+ <el-table-column prop="orderTime" label="下单时间" width="180" />
454
+ <el-table-column prop="orderAmount" label="订单金额" width="120">
455
+ <template #default="{ row }">
456
+ ¥{{ row.orderAmount }}
457
+ </template>
458
+ </el-table-column>
459
+ </el-table>
460
+ </el-tab-pane>
461
+ </el-tabs>
462
+
463
+ <template #footer>
464
+ <span class="dialog-footer">
465
+ <el-button @click="detailDialogVisible = false">关闭</el-button>
466
+ <el-button
467
+ v-if="detailCampaign.status === 'draft' || detailCampaign.status === 'pending'"
468
+ type="primary"
469
+ @click="startCampaign(detailCampaign)"
470
+ >
471
+ 启动活动
472
+ </el-button>
473
+ <el-button
474
+ v-if="detailCampaign.status === 'active'"
475
+ type="danger"
476
+ @click="stopCampaign(detailCampaign)"
477
+ >
478
+ 停止活动
479
+ </el-button>
480
+ </span>
481
+ </template>
482
+ </el-dialog>
483
+ </div>
484
+ </template>
485
+
486
+ <script>
487
+ import { Search, Discount, Coin, Present, Lightning } from "@element-plus/icons-vue";
488
+ import * as echarts from "echarts";
489
+
490
+ export default {
491
+ name: "Campaign",
492
+ components: {
493
+ Search,
494
+ Discount,
495
+ Coin,
496
+ Present,
497
+ Lightning
498
+ },
499
+ data() {
500
+ return {
501
+ activeTab: "all",
502
+ campaignActiveTab: "statistics",
503
+ searchText: "",
504
+ currentPage: 1,
505
+ pageSize: 10,
506
+ totalCampaigns: 0,
507
+ loading: false,
508
+ searchingProducts: false,
509
+ campaignDialogVisible: false,
510
+ detailDialogVisible: false,
511
+ editingCampaign: null,
512
+ campaigns: [
513
+ {
514
+ id: 1,
515
+ name: "618年中大促",
516
+ type: "discount",
517
+ startTime: "2023-06-01 00:00",
518
+ endTime: "2023-06-18 23:59",
519
+ status: "active",
520
+ targetUsers: ["all"],
521
+ discountRate: 80,
522
+ participants: 1256,
523
+ orders: 342,
524
+ sales: 128500,
525
+ conversionRate: 27.3,
526
+ budget: 50000,
527
+ createTime: "2023-05-15 10:30:00"
528
+ },
529
+ {
530
+ id: 2,
531
+ name: "新品首发优惠",
532
+ type: "reduction",
533
+ startTime: "2023-05-20 00:00",
534
+ endTime: "2023-05-27 23:59",
535
+ status: "ended",
536
+ targetUsers: ["new"],
537
+ reductionCondition: "满200减30",
538
+ participants: 865,
539
+ orders: 210,
540
+ sales: 65420,
541
+ conversionRate: 24.3,
542
+ budget: 20000,
543
+ createTime: "2023-05-10 14:45:00"
544
+ },
545
+ {
546
+ id: 3,
547
+ name: "会员专享秒杀",
548
+ type: "seckill",
549
+ startTime: "2023-05-25 10:00",
550
+ endTime: "2023-05-25 12:00",
551
+ status: "pending",
552
+ targetUsers: ["vip"],
553
+ seckillPrice: 1999,
554
+ participants: 0,
555
+ orders: 0,
556
+ sales: 0,
557
+ conversionRate: 0,
558
+ budget: 10000,
559
+ createTime: "2023-05-20 09:15:00"
560
+ },
561
+ {
562
+ id: 4,
563
+ name: "开学季赠品活动",
564
+ type: "gift",
565
+ startTime: "2023-08-20 00:00",
566
+ endTime: "2023-09-10 23:59",
567
+ status: "draft",
568
+ targetUsers: ["all"],
569
+ gift: "购买指定商品即赠精美书签一套",
570
+ participants: 0,
571
+ orders: 0,
572
+ sales: 0,
573
+ conversionRate: 0,
574
+ budget: 5000,
575
+ createTime: "2023-05-25 16:20:00"
576
+ }
577
+ ],
578
+ campaignForm: {
579
+ name: "",
580
+ type: "discount",
581
+ timeRange: [],
582
+ targetUsers: [],
583
+ discountRate: 90,
584
+ reductionCondition: "",
585
+ gift: "",
586
+ seckillPrice: 0,
587
+ products: [],
588
+ budget: 0
589
+ },
590
+ detailCampaign: {},
591
+ availableProducts: [
592
+ { id: 1, name: "iPhone 14 Pro", price: 7999 },
593
+ { id: 2, name: "MacBook Pro 14英寸", price: 15999 },
594
+ { id: 3, name: "AirPods Pro", price: 1899 },
595
+ { id: 4, name: "iPad Air", price: 4399 },
596
+ { id: 5, name: "Apple Watch Series 8", price: 2999 }
597
+ ],
598
+ campaignRules: {
599
+ name: [
600
+ { required: true, message: "请输入活动名称", trigger: "blur" }
601
+ ],
602
+ type: [
603
+ { required: true, message: "请选择活动类型", trigger: "change" }
604
+ ],
605
+ timeRange: [
606
+ { required: true, message: "请选择活动时间", trigger: "change" }
607
+ ],
608
+ targetUsers: [
609
+ { required: true, message: "请选择目标用户", trigger: "change" }
610
+ ],
611
+ budget: [
612
+ { required: true, message: "请输入活动预算", trigger: "blur" }
613
+ ]
614
+ },
615
+ campaignParticipants: [
616
+ {
617
+ id: "U1001",
618
+ name: "张三",
619
+ phone: "138****8001",
620
+ orderTime: "2023-06-01 10:30:00",
621
+ orderAmount: 7999
622
+ },
623
+ {
624
+ id: "U1002",
625
+ name: "李四",
626
+ phone: "138****8002",
627
+ orderTime: "2023-06-01 14:45:00",
628
+ orderAmount: 15998
629
+ }
630
+ ],
631
+ conversionChart: null,
632
+ salesChart: null
633
+ };
634
+ },
635
+ computed: {
636
+ filteredCampaigns() {
637
+ let result = this.campaigns;
638
+
639
+ // 搜索过滤
640
+ if (this.searchText) {
641
+ result = result.filter(campaign =>
642
+ campaign.name.toLowerCase().includes(this.searchText.toLowerCase())
643
+ );
644
+ }
645
+
646
+ // 分页处理
647
+ const start = (this.currentPage - 1) * this.pageSize;
648
+ const end = start + this.pageSize;
649
+ return result.slice(start, end);
650
+ },
651
+ activeCampaigns() {
652
+ return this.campaigns.filter(campaign => campaign.status === "active");
653
+ },
654
+ endedCampaigns() {
655
+ return this.campaigns.filter(campaign => campaign.status === "ended");
656
+ }
657
+ },
658
+ mounted() {
659
+ // 初始化图表
660
+ this.initCharts();
661
+ },
662
+ beforeUnmount() {
663
+ // 销毁图表实例
664
+ if (this.conversionChart) this.conversionChart.dispose();
665
+ if (this.salesChart) this.salesChart.dispose();
666
+ },
667
+ methods: {
668
+ handleTabChange(tab) {
669
+ this.activeTab = tab;
670
+ this.currentPage = 1;
671
+ },
672
+ handleSizeChange(val) {
673
+ this.pageSize = val;
674
+ this.currentPage = 1;
675
+ },
676
+ handleCurrentChange(val) {
677
+ this.currentPage = val;
678
+ },
679
+ getCampaignText(type) {
680
+ const typeMap = {
681
+ "discount": "折扣活动",
682
+ "reduction": "满减活动",
683
+ "gift": "赠品活动",
684
+ "seckill": "秒杀活动"
685
+ };
686
+ return typeMap[type] || type;
687
+ },
688
+ getCampaignType(type) {
689
+ const typeMap = {
690
+ "discount": "primary",
691
+ "reduction": "success",
692
+ "gift": "warning",
693
+ "seckill": "danger"
694
+ };
695
+ return typeMap[type] || "info";
696
+ },
697
+ getCampaignIcon(type) {
698
+ const iconMap = {
699
+ "discount": "Discount",
700
+ "reduction": "Coin",
701
+ "gift": "Present",
702
+ "seckill": "Lightning"
703
+ };
704
+ return iconMap[type] || "Discount";
705
+ },
706
+ getCampaignColor(type) {
707
+ const colorMap = {
708
+ "discount": "#409EFF",
709
+ "reduction": "#67C23A",
710
+ "gift": "#E6A23C",
711
+ "seckill": "#F56C6C"
712
+ };
713
+ return colorMap[type] || "#909399";
714
+ },
715
+ getStatusText(status) {
716
+ const statusMap = {
717
+ "draft": "草稿",
718
+ "pending": "待启动",
719
+ "active": "进行中",
720
+ "ended": "已结束"
721
+ };
722
+ return statusMap[status] || status;
723
+ },
724
+ getStatusType(status) {
725
+ const typeMap = {
726
+ "draft": "info",
727
+ "pending": "warning",
728
+ "active": "success",
729
+ "ended": ""
730
+ };
731
+ return typeMap[status] || "info";
732
+ },
733
+ getTargetUsersText(targetUsers) {
734
+ const textMap = {
735
+ "all": "所有用户",
736
+ "new": "新用户",
737
+ "old": "老用户",
738
+ "vip": "VIP用户"
739
+ };
740
+
741
+ if (!targetUsers || targetUsers.length === 0) return "未设置";
742
+
743
+ return targetUsers.map(user => textMap[user] || user).join("、");
744
+ },
745
+ getCampaignRule(campaign) {
746
+ switch (campaign.type) {
747
+ case "discount":
748
+ return `全场${campaign.discountRate}折`;
749
+ case "reduction":
750
+ return campaign.reductionCondition;
751
+ case "gift":
752
+ return campaign.gift;
753
+ case "seckill":
754
+ return `秒杀价¥${campaign.seckillPrice}`;
755
+ default:
756
+ return "未设置";
757
+ }
758
+ },
759
+ showAddCampaignDialog() {
760
+ this.editingCampaign = null;
761
+ this.campaignForm = {
762
+ name: "",
763
+ type: "discount",
764
+ timeRange: [],
765
+ targetUsers: [],
766
+ discountRate: 90,
767
+ reductionCondition: "",
768
+ gift: "",
769
+ seckillPrice: 0,
770
+ products: [],
771
+ budget: 0
772
+ };
773
+ this.campaignDialogVisible = true;
774
+ this.$nextTick(() => {
775
+ this.$refs.campaignFormRef.resetFields();
776
+ });
777
+ },
778
+ editCampaign(campaign) {
779
+ this.editingCampaign = campaign;
780
+ this.campaignForm = {
781
+ name: campaign.name,
782
+ type: campaign.type,
783
+ timeRange: [campaign.startTime, campaign.endTime],
784
+ targetUsers: campaign.targetUsers,
785
+ discountRate: campaign.discountRate || 90,
786
+ reductionCondition: campaign.reductionCondition || "",
787
+ gift: campaign.gift || "",
788
+ seckillPrice: campaign.seckillPrice || 0,
789
+ products: campaign.products || [],
790
+ budget: campaign.budget
791
+ };
792
+ this.campaignDialogVisible = true;
793
+ },
794
+ viewCampaign(campaign) {
795
+ this.detailCampaign = { ...campaign };
796
+ this.detailDialogVisible = true;
797
+ this.$nextTick(() => {
798
+ this.updateCharts();
799
+ });
800
+ },
801
+ startCampaign(campaign) {
802
+ this.$confirm(`确定要启动活动"${campaign.name}"吗?`, "提示", {
803
+ confirmButtonText: "确定",
804
+ cancelButtonText: "取消",
805
+ type: "warning"
806
+ }).then(() => {
807
+ const index = this.campaigns.findIndex(c => c.id === campaign.id);
808
+ if (index !== -1) {
809
+ this.campaigns[index].status = "active";
810
+ this.$message.success("活动已启动");
811
+
812
+ // 如果在详情对话框中操作,更新详情活动状态
813
+ if (this.detailCampaign.id === campaign.id) {
814
+ this.detailCampaign.status = "active";
815
+ }
816
+ }
817
+ }).catch(() => {
818
+ this.$message.info("已取消操作");
819
+ });
820
+ },
821
+ stopCampaign(campaign) {
822
+ this.$confirm(`确定要停止活动"${campaign.name}"吗?`, "提示", {
823
+ confirmButtonText: "确定",
824
+ cancelButtonText: "取消",
825
+ type: "warning"
826
+ }).then(() => {
827
+ const index = this.campaigns.findIndex(c => c.id === campaign.id);
828
+ if (index !== -1) {
829
+ this.campaigns[index].status = "ended";
830
+ this.$message.success("活动已停止");
831
+
832
+ // 如果在详情对话框中操作,更新详情活动状态
833
+ if (this.detailCampaign.id === campaign.id) {
834
+ this.detailCampaign.status = "ended";
835
+ }
836
+ }
837
+ }).catch(() => {
838
+ this.$message.info("已取消操作");
839
+ });
840
+ },
841
+ saveCampaign() {
842
+ this.$refs.campaignFormRef.validate((valid) => {
843
+ if (valid) {
844
+ if (this.editingCampaign) {
845
+ // 编辑活动
846
+ const index = this.campaigns.findIndex(c => c.id === this.editingCampaign.id);
847
+ if (index !== -1) {
848
+ this.campaigns[index] = {
849
+ ...this.editingCampaign,
850
+ ...this.campaignForm,
851
+ startTime: this.campaignForm.timeRange[0],
852
+ endTime: this.campaignForm.timeRange[1],
853
+ status: "draft"
854
+ };
855
+ this.$message.success("活动更新成功");
856
+ }
857
+ } else {
858
+ // 创建活动
859
+ const newCampaign = {
860
+ id: Date.now(),
861
+ createTime: new Date().toLocaleString(),
862
+ status: "draft",
863
+ participants: 0,
864
+ orders: 0,
865
+ sales: 0,
866
+ conversionRate: 0,
867
+ ...this.campaignForm,
868
+ startTime: this.campaignForm.timeRange[0],
869
+ endTime: this.campaignForm.timeRange[1]
870
+ };
871
+ this.campaigns.push(newCampaign);
872
+ this.totalCampaigns = this.campaigns.length;
873
+ this.$message.success("活动创建成功");
874
+ }
875
+ this.campaignDialogVisible = false;
876
+ }
877
+ });
878
+ },
879
+ searchProducts(query) {
880
+ if (query !== '') {
881
+ this.searchingProducts = true;
882
+ setTimeout(() => {
883
+ this.searchingProducts = false;
884
+ }, 200);
885
+ } else {
886
+ this.availableProducts = [];
887
+ }
888
+ },
889
+ initCharts() {
890
+ // 转化率图表
891
+ this.conversionChart = echarts.init(this.$refs.conversionChart);
892
+ this.conversionChart.setOption(this.getConversionChartOption());
893
+
894
+ // 销售额图表
895
+ this.salesChart = echarts.init(this.$refs.salesChart);
896
+ this.salesChart.setOption(this.getSalesChartOption());
897
+ },
898
+ updateCharts() {
899
+ if (this.conversionChart) {
900
+ this.conversionChart.setOption(this.getConversionChartOption());
901
+ }
902
+ if (this.salesChart) {
903
+ this.salesChart.setOption(this.getSalesChartOption());
904
+ }
905
+ },
906
+ getConversionChartOption() {
907
+ return {
908
+ title: {
909
+ text: '转化率趋势'
910
+ },
911
+ tooltip: {
912
+ trigger: 'axis'
913
+ },
914
+ xAxis: {
915
+ type: 'category',
916
+ data: ['6.1', '6.2', '6.3', '6.4', '6.5', '6.6', '6.7']
917
+ },
918
+ yAxis: {
919
+ type: 'value'
920
+ },
921
+ series: [
922
+ {
923
+ data: [20, 25, 28, 30, 27, 32, 27.3],
924
+ type: 'line',
925
+ smooth: true
926
+ }
927
+ ]
928
+ };
929
+ },
930
+ getSalesChartOption() {
931
+ return {
932
+ title: {
933
+ text: '销售额趋势'
934
+ },
935
+ tooltip: {
936
+ trigger: 'axis'
937
+ },
938
+ xAxis: {
939
+ type: 'category',
940
+ data: ['6.1', '6.2', '6.3', '6.4', '6.5', '6.6', '6.7']
941
+ },
942
+ yAxis: {
943
+ type: 'value'
944
+ },
945
+ series: [
946
+ {
947
+ data: [15000, 22000, 28000, 32000, 29000, 35000, 32000],
948
+ type: 'line',
949
+ smooth: true
950
+ }
951
+ ]
952
+ };
953
+ }
954
+ }
955
+ };
956
+ </script>
957
+
958
+ <style lang="scss" scoped>
959
+ .campaign-container {
960
+ padding: 20px;
961
+
962
+ .card-header {
963
+ display: flex;
964
+ justify-content: space-between;
965
+ align-items: center;
966
+ font-weight: bold;
967
+ }
968
+
969
+ .campaign-name {
970
+ display: flex;
971
+ align-items: center;
972
+ }
973
+
974
+ .campaign-icon-detail {
975
+ width: 100px;
976
+ height: 100px;
977
+ border-radius: 50%;
978
+ display: flex;
979
+ align-items: center;
980
+ justify-content: center;
981
+ color: white;
982
+ margin: 0 auto;
983
+ }
984
+
985
+ .stat-card {
986
+ text-align: center;
987
+
988
+ .stat-title {
989
+ font-size: 14px;
990
+ color: #666;
991
+ margin-bottom: 10px;
992
+ }
993
+
994
+ .stat-value {
995
+ font-size: 24px;
996
+ font-weight: bold;
997
+ color: #333;
998
+ }
999
+ }
1000
+
1001
+ .pagination-container {
1002
+ margin-top: 20px;
1003
+ text-align: right;
1004
+ }
1005
+ }
1006
+ </style>