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,666 @@
1
+ <template>
2
+ <div class="customer-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="showAddCustomerDialog">添加客户</el-button>
19
+ </div>
20
+ </div>
21
+ </template>
22
+
23
+ <el-table
24
+ :data="filteredCustomers"
25
+ style="width: 100%"
26
+ row-key="id"
27
+ v-loading="loading"
28
+ >
29
+ <el-table-column prop="name" label="客户名称" min-width="150">
30
+ <template #default="{ row }">
31
+ <div class="customer-name">
32
+ <el-avatar :size="32" :style="{ backgroundColor: getAvatarColor(row.name) }">
33
+ {{ row.name.charAt(0).toUpperCase() }}
34
+ </el-avatar>
35
+ <span style="margin-left: 10px">{{ row.name }}</span>
36
+ </div>
37
+ </template>
38
+ </el-table-column>
39
+ <el-table-column prop="contact" label="联系人" width="120" />
40
+ <el-table-column prop="phone" label="联系电话" width="150" />
41
+ <el-table-column prop="email" label="邮箱" width="200" />
42
+ <el-table-column label="客户等级" width="100">
43
+ <template #default="{ row }">
44
+ <el-tag :type="getLevelType(row.level)">
45
+ {{ getLevelText(row.level) }}
46
+ </el-tag>
47
+ </template>
48
+ </el-table-column>
49
+ <el-table-column prop="address" label="地址" min-width="200" />
50
+ <el-table-column prop="createTime" label="创建时间" width="180" />
51
+ <el-table-column label="操作" width="200">
52
+ <template #default="{ row }">
53
+ <el-button type="text" @click="viewCustomer(row)">查看</el-button>
54
+ <el-button type="text" @click="editCustomer(row)">编辑</el-button>
55
+ <el-button type="text" @click="deleteCustomer(row)">删除</el-button>
56
+ </template>
57
+ </el-table-column>
58
+ </el-table>
59
+
60
+ <div class="pagination-container">
61
+ <el-pagination
62
+ v-model:current-page="currentPage"
63
+ v-model:page-size="pageSize"
64
+ :page-sizes="[10, 20, 50, 100]"
65
+ :total="totalCustomers"
66
+ layout="total, sizes, prev, pager, next, jumper"
67
+ @size-change="handleSizeChange"
68
+ @current-change="handleCurrentChange"
69
+ />
70
+ </div>
71
+ </el-card>
72
+
73
+ <!-- 添加/编辑客户对话框 -->
74
+ <el-dialog
75
+ v-model="customerDialogVisible"
76
+ :title="editingCustomer ? '编辑客户' : '添加客户'"
77
+ width="600px"
78
+ >
79
+ <el-form
80
+ ref="customerFormRef"
81
+ :model="customerForm"
82
+ :rules="customerRules"
83
+ label-width="100px"
84
+ >
85
+ <el-row :gutter="20">
86
+ <el-col :span="12">
87
+ <el-form-item label="客户名称" prop="name">
88
+ <el-input v-model="customerForm.name" />
89
+ </el-form-item>
90
+
91
+ <el-form-item label="联系人" prop="contact">
92
+ <el-input v-model="customerForm.contact" />
93
+ </el-form-item>
94
+
95
+ <el-form-item label="联系电话" prop="phone">
96
+ <el-input v-model="customerForm.phone" />
97
+ </el-form-item>
98
+
99
+ <el-form-item label="邮箱" prop="email">
100
+ <el-input v-model="customerForm.email" />
101
+ </el-form-item>
102
+ </el-col>
103
+
104
+ <el-col :span="12">
105
+ <el-form-item label="客户等级" prop="level">
106
+ <el-select v-model="customerForm.level" placeholder="请选择客户等级" style="width: 100%">
107
+ <el-option label="普通客户" value="normal"></el-option>
108
+ <el-option label="重要客户" value="important"></el-option>
109
+ <el-option label="VIP客户" value="vip"></el-option>
110
+ </el-select>
111
+ </el-form-item>
112
+
113
+ <el-form-item label="所在地区" prop="region">
114
+ <el-cascader
115
+ v-model="customerForm.region"
116
+ :options="regionOptions"
117
+ :props="{ expandTrigger: 'hover' }"
118
+ style="width: 100%"
119
+ />
120
+ </el-form-item>
121
+
122
+ <el-form-item label="详细地址" prop="address">
123
+ <el-input
124
+ v-model="customerForm.address"
125
+ type="textarea"
126
+ :rows="2"
127
+ />
128
+ </el-form-item>
129
+ </el-col>
130
+ </el-row>
131
+ </el-form>
132
+
133
+ <template #footer>
134
+ <span class="dialog-footer">
135
+ <el-button @click="customerDialogVisible = false">取消</el-button>
136
+ <el-button
137
+ type="primary"
138
+ @click="saveCustomer"
139
+ >
140
+ 保存
141
+ </el-button>
142
+ </span>
143
+ </template>
144
+ </el-dialog>
145
+
146
+ <!-- 客户详情对话框 -->
147
+ <el-dialog
148
+ v-model="detailDialogVisible"
149
+ title="客户详情"
150
+ width="800px"
151
+ >
152
+ <el-row :gutter="20">
153
+ <el-col :span="16">
154
+ <el-descriptions :column="1" border>
155
+ <el-descriptions-item label="客户名称">{{ detailCustomer.name }}</el-descriptions-item>
156
+ <el-descriptions-item label="联系人">{{ detailCustomer.contact }}</el-descriptions-item>
157
+ <el-descriptions-item label="联系电话">{{ detailCustomer.phone }}</el-descriptions-item>
158
+ <el-descriptions-item label="邮箱">{{ detailCustomer.email }}</el-descriptions-item>
159
+ <el-descriptions-item label="客户等级">
160
+ <el-tag :type="getLevelType(detailCustomer.level)">
161
+ {{ getLevelText(detailCustomer.level) }}
162
+ </el-tag>
163
+ </el-descriptions-item>
164
+ <el-descriptions-item label="所在地区">{{ getRegionText(detailCustomer.region) }}</el-descriptions-item>
165
+ <el-descriptions-item label="详细地址">{{ detailCustomer.address }}</el-descriptions-item>
166
+ <el-descriptions-item label="创建时间">{{ detailCustomer.createTime }}</el-descriptions-item>
167
+ </el-descriptions>
168
+ </el-col>
169
+ <el-col :span="8">
170
+ <div class="customer-avatar-detail" :style="{ backgroundColor: getAvatarColor(detailCustomer.name) }">
171
+ {{ detailCustomer.name.charAt(0).toUpperCase() }}
172
+ </div>
173
+ </el-col>
174
+ </el-row>
175
+
176
+ <el-tabs v-model="activeTab" style="margin-top: 20px">
177
+ <el-tab-pane label="交易记录" name="transactions">
178
+ <el-table :data="customerTransactions" style="width: 100%">
179
+ <el-table-column prop="id" label="订单号" width="120" />
180
+ <el-table-column prop="product" label="产品" />
181
+ <el-table-column prop="amount" label="金额" width="120">
182
+ <template #default="{ row }">
183
+ ¥{{ row.amount.toLocaleString() }}
184
+ </template>
185
+ </el-table-column>
186
+ <el-table-column label="状态" width="100">
187
+ <template #default="{ row }">
188
+ <el-tag :type="getTransactionStatusType(row.status)">
189
+ {{ getTransactionStatusText(row.status) }}
190
+ </el-tag>
191
+ </template>
192
+ </el-table-column>
193
+ <el-table-column prop="date" label="交易时间" width="180" />
194
+ </el-table>
195
+ </el-tab-pane>
196
+ <el-tab-pane label="联系记录" name="contacts">
197
+ <el-table :data="customerContacts" style="width: 100%">
198
+ <el-table-column prop="contactPerson" label="联系人" width="120" />
199
+ <el-table-column prop="method" label="联系方式" width="120" />
200
+ <el-table-column prop="content" label="联系内容" />
201
+ <el-table-column prop="date" label="联系时间" width="180" />
202
+ </el-table>
203
+
204
+ <div style="margin-top: 20px; text-align: center">
205
+ <el-button type="primary" @click="showAddContactDialog">添加联系记录</el-button>
206
+ </div>
207
+ </el-tab-pane>
208
+ </el-tabs>
209
+
210
+ <template #footer>
211
+ <span class="dialog-footer">
212
+ <el-button @click="detailDialogVisible = false">关闭</el-button>
213
+ <el-button type="primary" @click="editCustomer(detailCustomer)">编辑</el-button>
214
+ </span>
215
+ </template>
216
+ </el-dialog>
217
+
218
+ <!-- 添加联系记录对话框 -->
219
+ <el-dialog
220
+ v-model="contactDialogVisible"
221
+ title="添加联系记录"
222
+ width="500px"
223
+ >
224
+ <el-form
225
+ ref="contactFormRef"
226
+ :model="contactForm"
227
+ :rules="contactRules"
228
+ label-width="80px"
229
+ >
230
+ <el-form-item label="联系人" prop="contactPerson">
231
+ <el-input v-model="contactForm.contactPerson" />
232
+ </el-form-item>
233
+
234
+ <el-form-item label="联系方式" prop="method">
235
+ <el-select v-model="contactForm.method" placeholder="请选择联系方式" style="width: 100%">
236
+ <el-option label="电话" value="电话"></el-option>
237
+ <el-option label="邮件" value="邮件"></el-option>
238
+ <el-option label="面谈" value="面谈"></el-option>
239
+ <el-option label="微信" value="微信"></el-option>
240
+ </el-select>
241
+ </el-form-item>
242
+
243
+ <el-form-item label="联系内容" prop="content">
244
+ <el-input
245
+ v-model="contactForm.content"
246
+ type="textarea"
247
+ :rows="3"
248
+ />
249
+ </el-form-item>
250
+ </el-form>
251
+
252
+ <template #footer>
253
+ <span class="dialog-footer">
254
+ <el-button @click="contactDialogVisible = false">取消</el-button>
255
+ <el-button
256
+ type="primary"
257
+ @click="saveContact"
258
+ >
259
+ 保存
260
+ </el-button>
261
+ </span>
262
+ </template>
263
+ </el-dialog>
264
+ </div>
265
+ </template>
266
+
267
+ <script>
268
+ import { Search } from "@element-plus/icons-vue";
269
+
270
+ export default {
271
+ name: "Customer",
272
+ components: {
273
+ Search
274
+ },
275
+ data() {
276
+ return {
277
+ searchText: "",
278
+ currentPage: 1,
279
+ pageSize: 10,
280
+ totalCustomers: 0,
281
+ loading: false,
282
+ customerDialogVisible: false,
283
+ detailDialogVisible: false,
284
+ contactDialogVisible: false,
285
+ activeTab: "transactions",
286
+ editingCustomer: null,
287
+ customers: [
288
+ {
289
+ id: 1,
290
+ name: "阿里巴巴集团",
291
+ contact: "张伟",
292
+ phone: "010-12345678",
293
+ email: "zhangwei@alibaba.com",
294
+ level: "vip",
295
+ region: ["北京市", "北京市", "朝阳区"],
296
+ address: "北京市朝阳区某某大厦101室",
297
+ createTime: "2023-01-15 10:30:00"
298
+ },
299
+ {
300
+ id: 2,
301
+ name: "腾讯科技",
302
+ contact: "李娜",
303
+ phone: "0755-87654321",
304
+ email: "lina@tencent.com",
305
+ level: "important",
306
+ region: ["广东省", "深圳市", "南山区"],
307
+ address: "深圳市南山区科技园南路1001号",
308
+ createTime: "2023-02-20 14:45:00"
309
+ },
310
+ {
311
+ id: 3,
312
+ name: "百度在线",
313
+ contact: "王强",
314
+ phone: "010-88886666",
315
+ email: "wangqiang@baidu.com",
316
+ level: "important",
317
+ region: ["北京市", "北京市", "海淀区"],
318
+ address: "北京市海淀区上地十街10号",
319
+ createTime: "2023-03-10 09:15:00"
320
+ },
321
+ {
322
+ id: 4,
323
+ name: "字节跳动",
324
+ contact: "赵敏",
325
+ phone: "010-56891234",
326
+ email: "zhaomin@bytedance.com",
327
+ level: "vip",
328
+ region: ["北京市", "北京市", "海淀区"],
329
+ address: "北京市海淀区知春路甲48号",
330
+ createTime: "2023-04-05 16:20:00"
331
+ },
332
+ {
333
+ id: 5,
334
+ name: "京东商城",
335
+ contact: "孙丽",
336
+ phone: "010-95068888",
337
+ email: "sunli@jd.com",
338
+ level: "important",
339
+ region: ["北京市", "北京市", "大兴区"],
340
+ address: "北京市大兴区亦庄经济技术开发区科创十一街18号",
341
+ createTime: "2023-05-12 11:30:00"
342
+ }
343
+ ],
344
+ regionOptions: [
345
+ {
346
+ value: "北京市",
347
+ label: "北京市",
348
+ children: [
349
+ {
350
+ value: "北京市",
351
+ label: "北京市",
352
+ children: [
353
+ { value: "东城区", label: "东城区" },
354
+ { value: "西城区", label: "西城区" },
355
+ { value: "朝阳区", label: "朝阳区" },
356
+ { value: "海淀区", label: "海淀区" },
357
+ { value: "大兴区", label: "大兴区" }
358
+ ]
359
+ }
360
+ ]
361
+ },
362
+ {
363
+ value: "广东省",
364
+ label: "广东省",
365
+ children: [
366
+ {
367
+ value: "深圳市",
368
+ label: "深圳市",
369
+ children: [
370
+ { value: "南山区", label: "南山区" },
371
+ { value: "福田区", label: "福田区" },
372
+ { value: "宝安区", label: "宝安区" }
373
+ ]
374
+ },
375
+ {
376
+ value: "广州市",
377
+ label: "广州市",
378
+ children: [
379
+ { value: "天河区", label: "天河区" },
380
+ { value: "越秀区", label: "越秀区" }
381
+ ]
382
+ }
383
+ ]
384
+ }
385
+ ],
386
+ customerForm: {
387
+ name: "",
388
+ contact: "",
389
+ phone: "",
390
+ email: "",
391
+ level: "normal",
392
+ region: [],
393
+ address: ""
394
+ },
395
+ detailCustomer: {},
396
+ contactForm: {
397
+ contactPerson: "",
398
+ method: "电话",
399
+ content: ""
400
+ },
401
+ customerRules: {
402
+ name: [
403
+ { required: true, message: "请输入客户名称", trigger: "blur" }
404
+ ],
405
+ contact: [
406
+ { required: true, message: "请输入联系人", trigger: "blur" }
407
+ ],
408
+ phone: [
409
+ { required: true, message: "请输入联系电话", trigger: "blur" }
410
+ ],
411
+ email: [
412
+ { required: true, message: "请输入邮箱", trigger: "blur" },
413
+ { type: "email", message: "请输入正确的邮箱地址", trigger: "blur" }
414
+ ],
415
+ level: [
416
+ { required: true, message: "请选择客户等级", trigger: "change" }
417
+ ],
418
+ region: [
419
+ { required: true, message: "请选择所在地区", trigger: "change" }
420
+ ]
421
+ },
422
+ contactRules: {
423
+ contactPerson: [
424
+ { required: true, message: "请输入联系人", trigger: "blur" }
425
+ ],
426
+ method: [
427
+ { required: true, message: "请选择联系方式", trigger: "change" }
428
+ ],
429
+ content: [
430
+ { required: true, message: "请输入联系内容", trigger: "blur" }
431
+ ]
432
+ },
433
+ customerTransactions: [
434
+ {
435
+ id: "T202305001",
436
+ product: "企业级软件服务",
437
+ amount: 50000,
438
+ status: "completed",
439
+ date: "2023-05-01 14:30:00"
440
+ },
441
+ {
442
+ id: "T202304015",
443
+ product: "云服务器租赁",
444
+ amount: 12000,
445
+ status: "completed",
446
+ date: "2023-04-15 10:15:00"
447
+ }
448
+ ],
449
+ customerContacts: [
450
+ {
451
+ contactPerson: "张伟",
452
+ method: "电话",
453
+ content: "确认合同细节,对方表示满意",
454
+ date: "2023-05-10 16:00:00"
455
+ },
456
+ {
457
+ contactPerson: "李娜",
458
+ method: "邮件",
459
+ content: "发送产品资料和报价单",
460
+ date: "2023-05-08 09:30:00"
461
+ }
462
+ ]
463
+ };
464
+ },
465
+ computed: {
466
+ filteredCustomers() {
467
+ let result = this.customers;
468
+
469
+ // 搜索过滤
470
+ if (this.searchText) {
471
+ result = result.filter(customer =>
472
+ customer.name.toLowerCase().includes(this.searchText.toLowerCase()) ||
473
+ customer.contact.toLowerCase().includes(this.searchText.toLowerCase()) ||
474
+ customer.phone.includes(this.searchText) ||
475
+ customer.email.toLowerCase().includes(this.searchText.toLowerCase())
476
+ );
477
+ }
478
+
479
+ // 分页处理
480
+ const start = (this.currentPage - 1) * this.pageSize;
481
+ const end = start + this.pageSize;
482
+ return result.slice(start, end);
483
+ }
484
+ },
485
+ methods: {
486
+ handleSizeChange(val) {
487
+ this.pageSize = val;
488
+ this.currentPage = 1;
489
+ },
490
+ handleCurrentChange(val) {
491
+ this.currentPage = val;
492
+ },
493
+ getLevelText(level) {
494
+ const levelMap = {
495
+ "normal": "普通客户",
496
+ "important": "重要客户",
497
+ "vip": "VIP客户"
498
+ };
499
+ return levelMap[level] || level;
500
+ },
501
+ getLevelType(level) {
502
+ const typeMap = {
503
+ "normal": "info",
504
+ "important": "warning",
505
+ "vip": "success"
506
+ };
507
+ return typeMap[level] || "info";
508
+ },
509
+ getTransactionStatusText(status) {
510
+ const statusMap = {
511
+ "pending": "待处理",
512
+ "processing": "处理中",
513
+ "completed": "已完成",
514
+ "cancelled": "已取消"
515
+ };
516
+ return statusMap[status] || status;
517
+ },
518
+ getTransactionStatusType(status) {
519
+ const typeMap = {
520
+ "pending": "info",
521
+ "processing": "warning",
522
+ "completed": "success",
523
+ "cancelled": "danger"
524
+ };
525
+ return typeMap[status] || "info";
526
+ },
527
+ getRegionText(region) {
528
+ return region ? region.join(" - ") : "";
529
+ },
530
+ getAvatarColor(name) {
531
+ const colors = ["#409EFF", "#67C23A", "#E6A23C", "#F56C6C", "#909399"];
532
+ let hash = 0;
533
+ for (let i = 0; i < name.length; i++) {
534
+ hash = name.charCodeAt(i) + ((hash << 5) - hash);
535
+ }
536
+ const index = Math.abs(hash) % colors.length;
537
+ return colors[index];
538
+ },
539
+ showAddCustomerDialog() {
540
+ this.editingCustomer = null;
541
+ this.customerForm = {
542
+ name: "",
543
+ contact: "",
544
+ phone: "",
545
+ email: "",
546
+ level: "normal",
547
+ region: [],
548
+ address: ""
549
+ };
550
+ this.customerDialogVisible = true;
551
+ this.$nextTick(() => {
552
+ this.$refs.customerFormRef.resetFields();
553
+ });
554
+ },
555
+ editCustomer(customer) {
556
+ this.editingCustomer = customer;
557
+ this.customerForm = { ...customer };
558
+ this.customerDialogVisible = true;
559
+ this.detailDialogVisible = false;
560
+ },
561
+ viewCustomer(customer) {
562
+ this.detailCustomer = { ...customer };
563
+ this.detailDialogVisible = true;
564
+ },
565
+ deleteCustomer(customer) {
566
+ this.$confirm(`确定要删除客户"${customer.name}"吗?`, "提示", {
567
+ confirmButtonText: "确定",
568
+ cancelButtonText: "取消",
569
+ type: "warning"
570
+ }).then(() => {
571
+ const index = this.customers.findIndex(c => c.id === customer.id);
572
+ if (index !== -1) {
573
+ this.customers.splice(index, 1);
574
+ this.totalCustomers = this.customers.length;
575
+ this.$message.success("客户删除成功");
576
+ }
577
+ }).catch(() => {
578
+ this.$message.info("已取消删除");
579
+ });
580
+ },
581
+ saveCustomer() {
582
+ this.$refs.customerFormRef.validate((valid) => {
583
+ if (valid) {
584
+ if (this.editingCustomer) {
585
+ // 编辑客户
586
+ const index = this.customers.findIndex(c => c.id === this.editingCustomer.id);
587
+ if (index !== -1) {
588
+ this.customers[index] = { ...this.editingCustomer, ...this.customerForm };
589
+ this.$message.success("客户信息更新成功");
590
+ }
591
+ } else {
592
+ // 添加客户
593
+ const newCustomer = {
594
+ id: Date.now(),
595
+ createTime: new Date().toLocaleString(),
596
+ ...this.customerForm
597
+ };
598
+ this.customers.push(newCustomer);
599
+ this.totalCustomers = this.customers.length;
600
+ this.$message.success("客户添加成功");
601
+ }
602
+ this.customerDialogVisible = false;
603
+ }
604
+ });
605
+ },
606
+ showAddContactDialog() {
607
+ this.contactForm = {
608
+ contactPerson: "",
609
+ method: "电话",
610
+ content: ""
611
+ };
612
+ this.contactDialogVisible = true;
613
+ this.$nextTick(() => {
614
+ this.$refs.contactFormRef.resetFields();
615
+ });
616
+ },
617
+ saveContact() {
618
+ this.$refs.contactFormRef.validate((valid) => {
619
+ if (valid) {
620
+ this.customerContacts.unshift({
621
+ ...this.contactForm,
622
+ date: new Date().toLocaleString()
623
+ });
624
+ this.$message.success("联系记录添加成功");
625
+ this.contactDialogVisible = false;
626
+ }
627
+ });
628
+ }
629
+ }
630
+ };
631
+ </script>
632
+
633
+ <style lang="scss" scoped>
634
+ .customer-container {
635
+ padding: 20px;
636
+
637
+ .card-header {
638
+ display: flex;
639
+ justify-content: space-between;
640
+ align-items: center;
641
+ font-weight: bold;
642
+ }
643
+
644
+ .customer-name {
645
+ display: flex;
646
+ align-items: center;
647
+ }
648
+
649
+ .customer-avatar-detail {
650
+ width: 100px;
651
+ height: 100px;
652
+ border-radius: 50%;
653
+ display: flex;
654
+ align-items: center;
655
+ justify-content: center;
656
+ font-size: 40px;
657
+ color: white;
658
+ margin: 0 auto;
659
+ }
660
+
661
+ .pagination-container {
662
+ margin-top: 20px;
663
+ text-align: right;
664
+ }
665
+ }
666
+ </style>