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,662 @@
1
+ <template>
2
+ <div class="team-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="showAddMemberDialog">添加成员</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-row :gutter="20">
26
+ <el-col
27
+ v-for="member in filteredMembers"
28
+ :key="member.id"
29
+ :span="6"
30
+ style="margin-bottom: 20px"
31
+ >
32
+ <el-card
33
+ class="member-card"
34
+ shadow="hover"
35
+ @click="viewMember(member)"
36
+ >
37
+ <div class="member-avatar">
38
+ <el-avatar :size="64" :src="member.avatar" />
39
+ </div>
40
+ <div class="member-info">
41
+ <h3>{{ member.name }}</h3>
42
+ <p class="member-role">{{ member.role }}</p>
43
+ <p class="member-department">{{ member.department }}</p>
44
+ <div class="member-status">
45
+ <el-tag :type="getStatusType(member.status)">
46
+ {{ getStatusText(member.status) }}
47
+ </el-tag>
48
+ </div>
49
+ </div>
50
+ </el-card>
51
+ </el-col>
52
+ </el-row>
53
+ </el-tab-pane>
54
+
55
+ <el-tab-pane label="在线成员" name="online">
56
+ <el-row :gutter="20">
57
+ <el-col
58
+ v-for="member in onlineMembers"
59
+ :key="member.id"
60
+ :span="6"
61
+ style="margin-bottom: 20px"
62
+ >
63
+ <el-card
64
+ class="member-card"
65
+ shadow="hover"
66
+ @click="viewMember(member)"
67
+ >
68
+ <div class="member-avatar">
69
+ <el-avatar :size="64" :src="member.avatar" />
70
+ <div class="online-indicator"></div>
71
+ </div>
72
+ <div class="member-info">
73
+ <h3>{{ member.name }}</h3>
74
+ <p class="member-role">{{ member.role }}</p>
75
+ <p class="member-department">{{ member.department }}</p>
76
+ </div>
77
+ </el-card>
78
+ </el-col>
79
+ </el-row>
80
+ </el-tab-pane>
81
+
82
+ <el-tab-pane label="部门" name="departments">
83
+ <el-row :gutter="20">
84
+ <el-col
85
+ v-for="department in departments"
86
+ :key="department.id"
87
+ :span="8"
88
+ style="margin-bottom: 20px"
89
+ >
90
+ <el-card class="department-card" shadow="hover">
91
+ <div class="department-header">
92
+ <h3>{{ department.name }}</h3>
93
+ <el-tag>{{ department.memberCount }} 人</el-tag>
94
+ </div>
95
+ <div class="department-description">
96
+ {{ department.description }}
97
+ </div>
98
+ <div class="department-actions">
99
+ <el-button type="text" @click="viewDepartment(department)">查看详情</el-button>
100
+ </div>
101
+ </el-card>
102
+ </el-col>
103
+ </el-row>
104
+ </el-tab-pane>
105
+ </el-tabs>
106
+ </el-card>
107
+
108
+ <!-- 添加/编辑成员对话框 -->
109
+ <el-dialog
110
+ v-model="memberDialogVisible"
111
+ :title="editingMember ? '编辑成员' : '添加成员'"
112
+ width="600px"
113
+ >
114
+ <el-form
115
+ ref="memberFormRef"
116
+ :model="memberForm"
117
+ :rules="memberRules"
118
+ label-width="100px"
119
+ >
120
+ <el-row :gutter="20">
121
+ <el-col :span="12">
122
+ <el-form-item label="姓名" prop="name">
123
+ <el-input v-model="memberForm.name" />
124
+ </el-form-item>
125
+
126
+ <el-form-item label="邮箱" prop="email">
127
+ <el-input v-model="memberForm.email" />
128
+ </el-form-item>
129
+
130
+ <el-form-item label="部门" prop="department">
131
+ <el-select v-model="memberForm.department" placeholder="请选择部门" style="width: 100%">
132
+ <el-option
133
+ v-for="dept in departments"
134
+ :key="dept.id"
135
+ :label="dept.name"
136
+ :value="dept.name"
137
+ />
138
+ </el-select>
139
+ </el-form-item>
140
+
141
+ <el-form-item label="职位" prop="role">
142
+ <el-input v-model="memberForm.role" />
143
+ </el-form-item>
144
+ </el-col>
145
+
146
+ <el-col :span="12">
147
+ <el-form-item label="头像">
148
+ <el-upload
149
+ class="avatar-uploader"
150
+ action="https://jsonplaceholder.typicode.com/posts/"
151
+ :show-file-list="false"
152
+ :on-success="handleAvatarSuccess"
153
+ :before-upload="beforeAvatarUpload"
154
+ >
155
+ <img
156
+ v-if="memberForm.avatar"
157
+ :src="memberForm.avatar"
158
+ class="avatar"
159
+ alt="Avatar"
160
+ />
161
+ <el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
162
+ </el-upload>
163
+ </el-form-item>
164
+
165
+ <el-form-item label="手机号" prop="phone">
166
+ <el-input v-model="memberForm.phone" />
167
+ </el-form-item>
168
+
169
+ <el-form-item label="状态" prop="status">
170
+ <el-select v-model="memberForm.status" placeholder="请选择状态" style="width: 100%">
171
+ <el-option label="在线" value="online"></el-option>
172
+ <el-option label="忙碌" value="busy"></el-option>
173
+ <el-option label="离开" value="away"></el-option>
174
+ <el-option label="离线" value="offline"></el-option>
175
+ </el-select>
176
+ </el-form-item>
177
+ </el-col>
178
+ </el-row>
179
+ </el-form>
180
+
181
+ <template #footer>
182
+ <span class="dialog-footer">
183
+ <el-button @click="memberDialogVisible = false">取消</el-button>
184
+ <el-button
185
+ type="primary"
186
+ @click="saveMember"
187
+ >
188
+ 保存
189
+ </el-button>
190
+ </span>
191
+ </template>
192
+ </el-dialog>
193
+
194
+ <!-- 成员详情对话框 -->
195
+ <el-dialog
196
+ v-model="detailDialogVisible"
197
+ title="成员详情"
198
+ width="600px"
199
+ >
200
+ <el-row :gutter="20">
201
+ <el-col :span="8">
202
+ <div class="detail-avatar">
203
+ <el-avatar :size="80" :src="detailMember.avatar" />
204
+ </div>
205
+ </el-col>
206
+ <el-col :span="16">
207
+ <el-descriptions :column="1" border>
208
+ <el-descriptions-item label="姓名">{{ detailMember.name }}</el-descriptions-item>
209
+ <el-descriptions-item label="邮箱">{{ detailMember.email }}</el-descriptions-item>
210
+ <el-descriptions-item label="部门">{{ detailMember.department }}</el-descriptions-item>
211
+ <el-descriptions-item label="职位">{{ detailMember.role }}</el-descriptions-item>
212
+ <el-descriptions-item label="手机号">{{ detailMember.phone }}</el-descriptions-item>
213
+ <el-descriptions-item label="状态">
214
+ <el-tag :type="getStatusType(detailMember.status)">
215
+ {{ getStatusText(detailMember.status) }}
216
+ </el-tag>
217
+ </el-descriptions-item>
218
+ </el-descriptions>
219
+ </el-col>
220
+ </el-row>
221
+
222
+ <el-tabs v-model="memberActiveTab" style="margin-top: 20px">
223
+ <el-tab-pane label="项目参与" name="projects">
224
+ <el-table :data="memberProjects" style="width: 100%">
225
+ <el-table-column prop="name" label="项目名称" />
226
+ <el-table-column prop="role" label="担任角色" width="120" />
227
+ <el-table-column label="进度" width="150">
228
+ <template #default="{ row }">
229
+ <el-progress :percentage="row.progress" :status="getProgressStatus(row.progress)" />
230
+ </template>
231
+ </el-table-column>
232
+ </el-table>
233
+ </el-tab-pane>
234
+ <el-tab-pane label="任务分配" name="tasks">
235
+ <el-table :data="memberTasks" style="width: 100%">
236
+ <el-table-column prop="title" label="任务标题" />
237
+ <el-table-column prop="project" label="所属项目" width="120" />
238
+ <el-table-column label="状态" width="100">
239
+ <template #default="{ row }">
240
+ <el-tag :type="getTaskStatusType(row.status)">
241
+ {{ getTaskStatusText(row.status) }}
242
+ </el-tag>
243
+ </template>
244
+ </el-table-column>
245
+ <el-table-column prop="dueDate" label="截止日期" width="120" />
246
+ </el-table>
247
+ </el-tab-pane>
248
+ </el-tabs>
249
+
250
+ <template #footer>
251
+ <span class="dialog-footer">
252
+ <el-button @click="detailDialogVisible = false">关闭</el-button>
253
+ <el-button type="primary" @click="editMember(detailMember)">编辑</el-button>
254
+ </span>
255
+ </template>
256
+ </el-dialog>
257
+ </div>
258
+ </template>
259
+
260
+ <script>
261
+ import { Search, Plus } from "@element-plus/icons-vue";
262
+
263
+ export default {
264
+ name: "Team",
265
+ components: {
266
+ Search,
267
+ Plus
268
+ },
269
+ data() {
270
+ return {
271
+ activeTab: "all",
272
+ memberActiveTab: "projects",
273
+ searchText: "",
274
+ memberDialogVisible: false,
275
+ detailDialogVisible: false,
276
+ editingMember: null,
277
+ members: [
278
+ {
279
+ id: 1,
280
+ name: "张三",
281
+ email: "zhangsan@example.com",
282
+ department: "技术部",
283
+ role: "前端工程师",
284
+ phone: "13800138001",
285
+ status: "online",
286
+ avatar: "https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"
287
+ },
288
+ {
289
+ id: 2,
290
+ name: "李四",
291
+ email: "lisi@example.com",
292
+ department: "技术部",
293
+ role: "后端工程师",
294
+ phone: "13800138002",
295
+ status: "busy",
296
+ avatar: "https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"
297
+ },
298
+ {
299
+ id: 3,
300
+ name: "王五",
301
+ email: "wangwu@example.com",
302
+ department: "设计部",
303
+ role: "UI设计师",
304
+ phone: "13800138003",
305
+ status: "away",
306
+ avatar: "https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"
307
+ },
308
+ {
309
+ id: 4,
310
+ name: "赵六",
311
+ email: "zhaoliu@example.com",
312
+ department: "产品部",
313
+ role: "产品经理",
314
+ phone: "13800138004",
315
+ status: "online",
316
+ avatar: "https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"
317
+ },
318
+ {
319
+ id: 5,
320
+ name: "钱七",
321
+ email: "qianqi@example.com",
322
+ department: "市场部",
323
+ role: "市场专员",
324
+ phone: "13800138005",
325
+ status: "offline",
326
+ avatar: "https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"
327
+ },
328
+ {
329
+ id: 6,
330
+ name: "孙八",
331
+ email: "sunba@example.com",
332
+ department: "技术部",
333
+ role: "测试工程师",
334
+ phone: "13800138006",
335
+ status: "online",
336
+ avatar: "https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"
337
+ }
338
+ ],
339
+ departments: [
340
+ {
341
+ id: 1,
342
+ name: "技术部",
343
+ description: "负责产品研发和技术支持",
344
+ memberCount: 3
345
+ },
346
+ {
347
+ id: 2,
348
+ name: "设计部",
349
+ description: "负责产品设计和用户体验",
350
+ memberCount: 1
351
+ },
352
+ {
353
+ id: 3,
354
+ name: "产品部",
355
+ description: "负责产品规划和项目管理",
356
+ memberCount: 1
357
+ },
358
+ {
359
+ id: 4,
360
+ name: "市场部",
361
+ description: "负责市场推广和客户关系",
362
+ memberCount: 1
363
+ }
364
+ ],
365
+ memberForm: {
366
+ name: "",
367
+ email: "",
368
+ department: "",
369
+ role: "",
370
+ phone: "",
371
+ status: "online",
372
+ avatar: ""
373
+ },
374
+ detailMember: {},
375
+ memberRules: {
376
+ name: [
377
+ { required: true, message: "请输入姓名", trigger: "blur" }
378
+ ],
379
+ email: [
380
+ { required: true, message: "请输入邮箱", trigger: "blur" },
381
+ { type: "email", message: "请输入正确的邮箱地址", trigger: "blur" }
382
+ ],
383
+ department: [
384
+ { required: true, message: "请选择部门", trigger: "change" }
385
+ ],
386
+ role: [
387
+ { required: true, message: "请输入职位", trigger: "blur" }
388
+ ]
389
+ },
390
+ memberProjects: [
391
+ {
392
+ id: 1,
393
+ name: "电商平台开发",
394
+ role: "前端开发",
395
+ progress: 75
396
+ },
397
+ {
398
+ id: 2,
399
+ name: "移动端App",
400
+ role: "前端开发",
401
+ progress: 40
402
+ }
403
+ ],
404
+ memberTasks: [
405
+ {
406
+ id: 1,
407
+ title: "首页改版",
408
+ project: "电商平台开发",
409
+ status: "in-progress",
410
+ dueDate: "2023-06-30"
411
+ },
412
+ {
413
+ id: 2,
414
+ title: "商品列表优化",
415
+ project: "电商平台开发",
416
+ status: "completed",
417
+ dueDate: "2023-06-15"
418
+ }
419
+ ]
420
+ };
421
+ },
422
+ computed: {
423
+ filteredMembers() {
424
+ if (!this.searchText) {
425
+ return this.members;
426
+ }
427
+ return this.members.filter(member =>
428
+ member.name.toLowerCase().includes(this.searchText.toLowerCase()) ||
429
+ member.role.toLowerCase().includes(this.searchText.toLowerCase()) ||
430
+ member.department.toLowerCase().includes(this.searchText.toLowerCase())
431
+ );
432
+ },
433
+ onlineMembers() {
434
+ return this.members.filter(member => member.status === "online");
435
+ }
436
+ },
437
+ methods: {
438
+ handleTabChange(tab) {
439
+ this.activeTab = tab;
440
+ },
441
+ getStatusText(status) {
442
+ const statusMap = {
443
+ "online": "在线",
444
+ "busy": "忙碌",
445
+ "away": "离开",
446
+ "offline": "离线"
447
+ };
448
+ return statusMap[status] || status;
449
+ },
450
+ getStatusType(status) {
451
+ const typeMap = {
452
+ "online": "success",
453
+ "busy": "warning",
454
+ "away": "info",
455
+ "offline": "danger"
456
+ };
457
+ return typeMap[status] || "info";
458
+ },
459
+ getProgressStatus(progress) {
460
+ if (progress === 100) {
461
+ return "success";
462
+ } else if (progress < 30) {
463
+ return "exception";
464
+ }
465
+ return "";
466
+ },
467
+ getTaskStatusText(status) {
468
+ const statusMap = {
469
+ "pending": "待办",
470
+ "in-progress": "进行中",
471
+ "completed": "已完成"
472
+ };
473
+ return statusMap[status] || status;
474
+ },
475
+ getTaskStatusType(status) {
476
+ const typeMap = {
477
+ "pending": "info",
478
+ "in-progress": "warning",
479
+ "completed": "success"
480
+ };
481
+ return typeMap[status] || "info";
482
+ },
483
+ showAddMemberDialog() {
484
+ this.editingMember = null;
485
+ this.memberForm = {
486
+ name: "",
487
+ email: "",
488
+ department: "",
489
+ role: "",
490
+ phone: "",
491
+ status: "online",
492
+ avatar: ""
493
+ };
494
+ this.memberDialogVisible = true;
495
+ this.$nextTick(() => {
496
+ this.$refs.memberFormRef.resetFields();
497
+ });
498
+ },
499
+ editMember(member) {
500
+ this.editingMember = member;
501
+ this.memberForm = { ...member };
502
+ this.memberDialogVisible = true;
503
+ this.detailDialogVisible = false;
504
+ },
505
+ viewMember(member) {
506
+ this.detailMember = { ...member };
507
+ this.detailDialogVisible = true;
508
+ },
509
+ viewDepartment(department) {
510
+ this.$message.info(`查看部门: ${department.name}`);
511
+ },
512
+ saveMember() {
513
+ this.$refs.memberFormRef.validate((valid) => {
514
+ if (valid) {
515
+ if (this.editingMember) {
516
+ // 编辑成员
517
+ const index = this.members.findIndex(m => m.id === this.editingMember.id);
518
+ if (index !== -1) {
519
+ this.members[index] = { ...this.editingMember, ...this.memberForm };
520
+ this.$message.success("成员信息更新成功");
521
+ }
522
+ } else {
523
+ // 添加成员
524
+ const newMember = {
525
+ id: Date.now(),
526
+ ...this.memberForm
527
+ };
528
+ this.members.push(newMember);
529
+ this.$message.success("成员添加成功");
530
+ }
531
+ this.memberDialogVisible = false;
532
+ }
533
+ });
534
+ },
535
+ handleAvatarSuccess(response, file) {
536
+ this.memberForm.avatar = URL.createObjectURL(file.raw);
537
+ },
538
+ beforeAvatarUpload(file) {
539
+ const isJPG = file.type === "image/jpeg" || file.type === "image/png";
540
+ const isLt2M = file.size / 1024 / 1024 < 2;
541
+
542
+ if (!isJPG) {
543
+ this.$message.error("头像图片只能是 JPG 或 PNG 格式!");
544
+ }
545
+ if (!isLt2M) {
546
+ this.$message.error("头像图片大小不能超过 2MB!");
547
+ }
548
+ return isJPG && isLt2M;
549
+ }
550
+ }
551
+ };
552
+ </script>
553
+
554
+ <style lang="scss" scoped>
555
+ .team-container {
556
+ padding: 20px;
557
+
558
+ .card-header {
559
+ display: flex;
560
+ justify-content: space-between;
561
+ align-items: center;
562
+ font-weight: bold;
563
+ }
564
+
565
+ .member-card {
566
+ cursor: pointer;
567
+ transition: transform 0.2s;
568
+
569
+ &:hover {
570
+ transform: translateY(-5px);
571
+ }
572
+
573
+ .member-avatar {
574
+ text-align: center;
575
+ position: relative;
576
+
577
+ .online-indicator {
578
+ position: absolute;
579
+ width: 16px;
580
+ height: 16px;
581
+ background-color: #67C23A;
582
+ border-radius: 50%;
583
+ border: 2px solid white;
584
+ bottom: 0;
585
+ right: 8px;
586
+ }
587
+ }
588
+
589
+ .member-info {
590
+ text-align: center;
591
+ margin-top: 15px;
592
+
593
+ h3 {
594
+ margin: 10px 0 5px;
595
+ font-size: 16px;
596
+ }
597
+
598
+ .member-role {
599
+ color: #999;
600
+ margin: 5px 0;
601
+ font-size: 14px;
602
+ }
603
+
604
+ .member-department {
605
+ color: #666;
606
+ font-size: 13px;
607
+ margin: 5px 0;
608
+ }
609
+ }
610
+ }
611
+
612
+ .department-card {
613
+ .department-header {
614
+ display: flex;
615
+ justify-content: space-between;
616
+ align-items: center;
617
+ margin-bottom: 10px;
618
+
619
+ h3 {
620
+ margin: 0;
621
+ }
622
+ }
623
+
624
+ .department-description {
625
+ color: #666;
626
+ margin: 10px 0;
627
+ min-height: 40px;
628
+ }
629
+ }
630
+
631
+ .detail-avatar {
632
+ text-align: center;
633
+ }
634
+
635
+ .avatar-uploader .avatar {
636
+ width: 120px;
637
+ height: 120px;
638
+ display: block;
639
+ }
640
+ }
641
+
642
+ .avatar-uploader .el-upload {
643
+ border: 1px dashed #d9d9d9;
644
+ border-radius: 6px;
645
+ cursor: pointer;
646
+ position: relative;
647
+ overflow: hidden;
648
+ transition: var(--el-transition-duration-fast);
649
+ }
650
+
651
+ .avatar-uploader .el-upload:hover {
652
+ border-color: var(--el-color-primary);
653
+ }
654
+
655
+ .el-icon.avatar-uploader-icon {
656
+ font-size: 28px;
657
+ color: #8c939d;
658
+ width: 120px;
659
+ height: 120px;
660
+ text-align: center;
661
+ }
662
+ </style>
@@ -0,0 +1,44 @@
1
+ <template>
2
+ <div class="tree-container">
3
+ <el-card shadow="never">
4
+ <el-tree
5
+ :data="treeData"
6
+ :props="defaultProps"
7
+ default-expand-all
8
+ node-key="id"
9
+ />
10
+ </el-card>
11
+ </div>
12
+ </template>
13
+
14
+ <script>
15
+ import { getTreeList } from "@/api/tree";
16
+
17
+ export default {
18
+ name: "Tree",
19
+ data() {
20
+ return {
21
+ treeData: [],
22
+ defaultProps: {
23
+ children: "children",
24
+ label: "title",
25
+ },
26
+ };
27
+ },
28
+ created() {
29
+ this.getTreeData();
30
+ },
31
+ methods: {
32
+ async getTreeData() {
33
+ const { data } = await getTreeList();
34
+ this.treeData = data;
35
+ },
36
+ },
37
+ };
38
+ </script>
39
+
40
+ <style lang="scss" scoped>
41
+ .tree-container {
42
+ padding: 20px;
43
+ }
44
+ </style>