TeacherTeam.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  1. <template>
  2. <div class="teacher-team admin-page">
  3. <!-- 页面标题区 -->
  4. <div class="page-header">
  5. <div class="header-left">
  6. <h2 class="page-title">我的团队</h2>
  7. <span class="page-desc">管理您的下级规划师和团队业绩</span>
  8. </div>
  9. <div class="header-actions">
  10. <el-button type="primary" class="primary-btn" @click="handleGenerateCode">
  11. <i class="el-icon-link"></i> 生成邀请码
  12. </el-button>
  13. </div>
  14. </div>
  15. <!-- 团队统计卡片 -->
  16. <el-row :gutter="24" class="stats-row">
  17. <el-col :span="6">
  18. <div class="stat-card">
  19. <div class="stat-icon blue">
  20. <i class="el-icon-user-solid"></i>
  21. </div>
  22. <div class="stat-info">
  23. <div class="stat-value">{{ teamStats.memberCount }}</div>
  24. <div class="stat-label">团队人数</div>
  25. </div>
  26. </div>
  27. </el-col>
  28. <el-col :span="6">
  29. <div class="stat-card">
  30. <div class="stat-icon green">
  31. <i class="el-icon-house"></i>
  32. </div>
  33. <div class="stat-info">
  34. <div class="stat-value">{{ teamStats.familyCount }}</div>
  35. <div class="stat-label">服务家庭</div>
  36. </div>
  37. </div>
  38. </el-col>
  39. <el-col :span="6">
  40. <div class="stat-card">
  41. <div class="stat-icon orange">
  42. <i class="el-icon-s-goods"></i>
  43. </div>
  44. <div class="stat-info">
  45. <div class="stat-value">{{ formatPriceWithSymbol(teamStats.totalSales) }}</div>
  46. <div class="stat-label">团队业绩</div>
  47. </div>
  48. </div>
  49. </el-col>
  50. <el-col :span="6">
  51. <div class="stat-card">
  52. <div class="stat-icon purple">
  53. <i class="el-icon-wallet"></i>
  54. </div>
  55. <div class="stat-info">
  56. <div class="stat-value">{{ formatPriceWithSymbol(teamStats.myCommission) }}</div>
  57. <div class="stat-label">我的佣金</div>
  58. </div>
  59. </div>
  60. </el-col>
  61. </el-row>
  62. <!-- 邀请码卡片 -->
  63. <el-card class="invite-code-card" v-if="inviteCode">
  64. <div class="invite-code-content">
  65. <div class="invite-code-info">
  66. <span class="invite-label">邀请码</span>
  67. <span class="invite-code">{{ inviteCode.code }}</span>
  68. <span class="invite-expire">有效期至 {{ inviteCode.expireAt }}</span>
  69. </div>
  70. <div class="invite-actions">
  71. <el-button size="small" @click="handleCopyCode">复制</el-button>
  72. <el-button size="small" type="text" @click="handleDeleteCode">删除</el-button>
  73. </div>
  74. </div>
  75. </el-card>
  76. <!-- 团队成员列表 -->
  77. <el-card class="table-card">
  78. <div slot="header" class="card-header">
  79. <span class="card-title">团队成员</span>
  80. <el-input v-model="keyword" placeholder="搜索成员..." prefix-icon="el-icon-search" style="width:200px" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" />
  81. <span class="card-count">共 {{ filteredMembers.length }} 人</span>
  82. </div>
  83. <div style="overflow:auto;max-height:calc(100vh - 300px);">
  84. <el-table :data="filteredMembers" stripe style="width: 100%; max-height:calc(100vh - 300px);" v-loading="loading">
  85. <el-table-column label="成员信息" min-width="200">
  86. <template slot-scope="scope">
  87. <div class="member-cell">
  88. <div class="member-avatar" :style="{ background: getAvatarGradient(scope.row.level) }">
  89. <i class="el-icon-user"></i>
  90. </div>
  91. <div class="member-detail">
  92. <div class="member-name">{{ scope.row.nickname || '规划师' + scope.row.id }}</div>
  93. <div class="member-id">ID: {{ scope.row.id }}</div>
  94. </div>
  95. </div>
  96. </template>
  97. </el-table-column>
  98. <el-table-column prop="level" label="级别" width="120">
  99. <template slot-scope="scope">
  100. <el-tag :type="getLevelType(scope.row.level)" effect="dark" size="small">
  101. {{ scope.row.level || '初级' }}
  102. </el-tag>
  103. </template>
  104. </el-table-column>
  105. <el-table-column prop="familyCount" label="服务家庭" width="100">
  106. <template slot-scope="scope">
  107. <span class="family-count">{{ scope.row.familyCount || 0 }}</span>
  108. </template>
  109. </el-table-column>
  110. <el-table-column prop="childrenCount" label="服务孩子" width="100">
  111. <template slot-scope="scope">
  112. <span class="children-count">{{ scope.row.childrenCount || 0 }}</span>
  113. </template>
  114. </el-table-column>
  115. <el-table-column prop="monthlySales" label="本月业绩" width="120">
  116. <template slot-scope="scope">
  117. <span class="sales-value">{{ formatPriceWithSymbol(scope.row.monthlySales || 0) }}</span>
  118. </template>
  119. </el-table-column>
  120. <el-table-column prop="totalSales" label="累计业绩" width="120">
  121. <template slot-scope="scope">
  122. <span class="total-value">{{ formatPriceWithSymbol(scope.row.totalSales || 0) }}</span>
  123. </template>
  124. </el-table-column>
  125. <el-table-column prop="commission" label="贡献佣金" width="100">
  126. <template slot-scope="scope">
  127. <span class="commission-value">+{{ formatPriceWithSymbol(Math.round((scope.row.monthlySales || 0) * 0.1)) }}</span>
  128. </template>
  129. </el-table-column>
  130. <el-table-column label="操作" width="200" fixed="right">
  131. <template slot-scope="{ row }">
  132. <el-button size="mini" type="primary" @click="handleEdit(row)">编辑</el-button>
  133. <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
  134. <el-button size="mini">
  135. 更多<i class="el-icon-arrow-down el-icon--right"></i>
  136. </el-button>
  137. <el-dropdown-menu slot="dropdown">
  138. <el-dropdown-item command="view" icon="el-icon-view">查看详情</el-dropdown-item>
  139. <el-dropdown-item command="delete" icon="el-icon-delete">删除</el-dropdown-item>
  140. </el-dropdown-menu>
  141. </el-dropdown>
  142. </template>
  143. </el-table-column>
  144. </el-table>
  145. </div>
  146. </el-card>
  147. <!-- 成员详情抽屉 -->
  148. <el-drawer :visible.sync="drawerVisible" title="成员详情" size="480px" direction="rtl">
  149. <div class="drawer-content" v-if="currentMember">
  150. <el-divider content-position="left">基本信息</el-divider>
  151. <div class="detail-row">
  152. <span class="detail-label">成员ID</span>
  153. <span class="detail-value">{{ currentMember.id }}</span>
  154. </div>
  155. <div class="detail-row">
  156. <span class="detail-label">昵称</span>
  157. <span class="detail-value">{{ currentMember.nickname || '-' }}</span>
  158. </div>
  159. <div class="detail-row">
  160. <span class="detail-label">级别</span>
  161. <el-tag :type="getLevelType(currentMember.level)" size="small">{{ currentMember.level || '初级' }}</el-tag>
  162. </div>
  163. <div class="detail-row">
  164. <span class="detail-label">注册时间</span>
  165. <span class="detail-value">{{ currentMember.createdAt || '-' }}</span>
  166. </div>
  167. <el-divider content-position="left">业绩统计</el-divider>
  168. <el-row :gutter="12">
  169. <el-col :span="12">
  170. <div class="mini-stat">
  171. <div class="mini-stat-value" style="color: #3B82F6">{{ formatPriceWithSymbol(currentMember.monthlySales || 0) }}</div>
  172. <div class="mini-stat-label">本月业绩</div>
  173. </div>
  174. </el-col>
  175. <el-col :span="12">
  176. <div class="mini-stat">
  177. <div class="mini-stat-value" style="color: #10B981">{{ formatPriceWithSymbol(currentMember.totalSales || 0) }}</div>
  178. <div class="mini-stat-label">累计业绩</div>
  179. </div>
  180. </el-col>
  181. </el-row>
  182. <el-divider content-position="left">服务概况</el-divider>
  183. <el-row :gutter="12">
  184. <el-col :span="8">
  185. <div class="mini-stat">
  186. <div class="mini-stat-value" style="color: #F59E0B">{{ currentMember.familyCount || 0 }}</div>
  187. <div class="mini-stat-label">服务家庭</div>
  188. </div>
  189. </el-col>
  190. <el-col :span="8">
  191. <div class="mini-stat">
  192. <div class="mini-stat-value" style="color: #8B5CF6">{{ currentMember.childrenCount || 0 }}</div>
  193. <div class="mini-stat-label">服务孩子</div>
  194. </div>
  195. </el-col>
  196. <el-col :span="8">
  197. <div class="mini-stat">
  198. <div class="mini-stat-value" style="color: #10B981">{{ currentMember.taskCount || 0 }}</div>
  199. <div class="mini-stat-label">完成任务</div>
  200. </div>
  201. </el-col>
  202. </el-row>
  203. </div>
  204. </el-drawer>
  205. </div>
  206. </template>
  207. <script>
  208. import { getTeamMembers, getTeamStats } from '@/api/guide'
  209. export default {
  210. name: 'TeacherTeam',
  211. data() {
  212. return {
  213. teamMembers: [],
  214. keyword: '',
  215. teamStats: {
  216. totalSales: 0,
  217. myCommission: 0,
  218. familyCount: 0,
  219. memberCount: 0
  220. },
  221. inviteCode: null,
  222. drawerVisible: false,
  223. currentMember: null,
  224. loading: false
  225. }
  226. },
  227. computed: {
  228. filteredMembers() {
  229. if (!this.keyword) return this.teamMembers
  230. const kw = this.keyword.toLowerCase()
  231. return this.teamMembers.filter(m =>
  232. (m.nickname || '').toLowerCase().includes(kw) ||
  233. String(m.id).includes(kw)
  234. )
  235. }
  236. },
  237. created() {
  238. this.loadData()
  239. },
  240. methods: {
  241. async loadData() {
  242. this.loading = true
  243. try {
  244. const [membersRes, statsRes] = await Promise.all([
  245. getTeamMembers(),
  246. getTeamStats()
  247. ])
  248. if (membersRes.data) {
  249. this.teamMembers = membersRes.data
  250. }
  251. if (statsRes.data) {
  252. this.teamStats = statsRes.data
  253. }
  254. } catch (e) {
  255. console.error(e)
  256. } finally {
  257. this.loading = false
  258. }
  259. },
  260. handleView(row) {
  261. this.currentMember = row
  262. this.drawerVisible = true
  263. },
  264. handleEdit(row) {
  265. this.handleView(row)
  266. },
  267. handleActionCmd(row, cmd) {
  268. switch (cmd) {
  269. case 'view': this.handleView(row); break
  270. case 'edit': this.handleEdit(row); break
  271. case 'delete': this.$confirm('确认删除该成员吗?', '提示', { type: 'warning' }).then(() => {}).catch(() => {}); break
  272. }
  273. },
  274. handleSearch() {
  275. },
  276. handleGenerateCode() {
  277. this.$prompt('请选择邀请码有效期', '生成邀请码', {
  278. confirmButtonText: '7天',
  279. cancelButtonText: '30天'
  280. }).then(({ value }) => {
  281. this.inviteCode = {
  282. code: Math.random().toString(36).substring(2, 10).toUpperCase(),
  283. expireAt: value ? new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toLocaleDateString() : new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toLocaleDateString()
  284. }
  285. this.$message.success('邀请码已生成')
  286. }).catch(() => {
  287. // 用户取消操作,不做任何处理
  288. })
  289. },
  290. handleCopyCode() {
  291. navigator.clipboard.writeText(this.inviteCode.code)
  292. this.$message.success('已复制到剪贴板')
  293. },
  294. handleDeleteCode() {
  295. this.inviteCode = null
  296. this.$message.success('已删除')
  297. },
  298. getLevelType(level) {
  299. const map = {
  300. '初级': '',
  301. '中级': 'success',
  302. '高级': 'warning'
  303. }
  304. return map[level] || ''
  305. },
  306. getAvatarGradient(level) {
  307. const gradients = {
  308. '初级': 'linear-gradient(135deg, #3B82F6 0%, #60A5FA 100%)',
  309. '中级': 'linear-gradient(135deg, #10B981 0%, #34D399 100%)',
  310. '高级': 'linear-gradient(135deg, #F59E0B 0%, #FBBF24 100%)'
  311. }
  312. return gradients[level] || gradients['初级']
  313. }
  314. }
  315. }
  316. </script>
  317. <style scoped>
  318. .teacher-team {
  319. padding: 24px;
  320. background: #F0F7FF;
  321. min-height: calc(100vh - 120px);
  322. }
  323. /* 页面标题 */
  324. .page-header {
  325. display: flex;
  326. justify-content: space-between;
  327. align-items: flex-start;
  328. margin-bottom: 24px;
  329. }
  330. .page-title {
  331. font-size: 24px;
  332. font-weight: 700;
  333. color: #1E293B;
  334. margin: 0 0 4px 0;
  335. }
  336. .page-desc {
  337. font-size: 14px;
  338. color: #64748B;
  339. }
  340. .primary-btn {
  341. background: linear-gradient(135deg, #3B82F6 0%, #60A5FA 100%);
  342. border: none;
  343. padding: 12px 24px;
  344. border-radius: 10px;
  345. }
  346. .primary-btn:hover {
  347. background: linear-gradient(135deg, #2563EB 0%, #3B82F6 100%);
  348. }
  349. /* 统计卡片 */
  350. .stats-row {
  351. margin-bottom: 24px;
  352. }
  353. .stat-card {
  354. display: flex;
  355. align-items: center;
  356. padding: 20px 24px;
  357. background: #FFFFFF;
  358. border-radius: 16px;
  359. box-shadow: 0 2px 12px rgba(59, 130, 246, 0.08);
  360. }
  361. .stat-icon {
  362. width: 52px;
  363. height: 52px;
  364. border-radius: 14px;
  365. display: flex;
  366. align-items: center;
  367. justify-content: center;
  368. margin-right: 16px;
  369. font-size: 22px;
  370. color: #FFFFFF;
  371. }
  372. .stat-icon.blue {
  373. background: linear-gradient(135deg, #3B82F6 0%, #60A5FA 100%);
  374. }
  375. .stat-icon.green {
  376. background: linear-gradient(135deg, #10B981 0%, #34D399 100%);
  377. }
  378. .stat-icon.orange {
  379. background: linear-gradient(135deg, #F59E0B 0%, #FBBF24 100%);
  380. }
  381. .stat-icon.purple {
  382. background: linear-gradient(135deg, #8B5CF6 0%, #A78BFA 100%);
  383. }
  384. .stat-value {
  385. font-size: 28px;
  386. font-weight: 700;
  387. color: #1E293B;
  388. }
  389. .stat-label {
  390. font-size: 14px;
  391. color: #64748B;
  392. margin-top: 2px;
  393. }
  394. /* 邀请码卡片 */
  395. .invite-code-card {
  396. border-radius: 16px;
  397. margin-bottom: 24px;
  398. border: none;
  399. box-shadow: 0 2px 12px rgba(59, 130, 246, 0.06);
  400. background: linear-gradient(135deg, #3B82F6 0%, #60A5FA 100%);
  401. }
  402. .invite-code-content {
  403. display: flex;
  404. justify-content: space-between;
  405. align-items: center;
  406. }
  407. .invite-label {
  408. display: block;
  409. font-size: 13px;
  410. color: rgba(255, 255, 255, 0.8);
  411. margin-bottom: 4px;
  412. }
  413. .invite-code {
  414. display: block;
  415. font-size: 28px;
  416. font-weight: 700;
  417. color: #FFFFFF;
  418. font-family: 'Monaco', 'Menlo', monospace;
  419. letter-spacing: 2px;
  420. }
  421. .invite-expire {
  422. display: block;
  423. font-size: 12px;
  424. color: rgba(255, 255, 255, 0.7);
  425. margin-top: 4px;
  426. }
  427. .invite-actions {
  428. display: flex;
  429. gap: 8px;
  430. }
  431. /* 表格卡片 */
  432. .table-card {
  433. border-radius: 16px;
  434. border: none;
  435. box-shadow: 0 2px 12px rgba(59, 130, 246, 0.06);
  436. }
  437. .card-header {
  438. display: flex;
  439. justify-content: space-between;
  440. align-items: center;
  441. }
  442. .card-title {
  443. font-size: 16px;
  444. font-weight: 600;
  445. color: #1E293B;
  446. }
  447. .card-count {
  448. font-size: 13px;
  449. color: #64748B;
  450. }
  451. .member-cell {
  452. display: flex;
  453. align-items: center;
  454. }
  455. .member-avatar {
  456. width: 40px;
  457. height: 40px;
  458. border-radius: 50%;
  459. display: flex;
  460. align-items: center;
  461. justify-content: center;
  462. color: #FFFFFF;
  463. margin-right: 12px;
  464. font-size: 16px;
  465. }
  466. .member-name {
  467. font-weight: 600;
  468. font-size: 15px;
  469. color: #1E293B;
  470. }
  471. .member-id {
  472. font-size: 12px;
  473. color: #94A3B8;
  474. margin-top: 2px;
  475. }
  476. .family-count, .children-count {
  477. font-weight: 600;
  478. color: #3B82F6;
  479. }
  480. .sales-value {
  481. font-weight: 600;
  482. color: #F59E0B;
  483. }
  484. .total-value {
  485. font-weight: 600;
  486. color: #1E293B;
  487. }
  488. .commission-value {
  489. font-weight: 700;
  490. color: #10B981;
  491. }
  492. /* 抽屉样式 */
  493. .drawer-content {
  494. padding: 0 24px;
  495. }
  496. .detail-row {
  497. display: flex;
  498. justify-content: space-between;
  499. align-items: center;
  500. padding: 10px 0;
  501. border-bottom: 1px solid #F1F5F9;
  502. }
  503. .detail-label {
  504. color: #64748B;
  505. font-size: 14px;
  506. }
  507. .detail-value {
  508. color: #1E293B;
  509. font-size: 14px;
  510. font-weight: 500;
  511. }
  512. .mini-stat {
  513. text-align: center;
  514. padding: 16px;
  515. background: #F8FAFC;
  516. border-radius: 12px;
  517. }
  518. .mini-stat-value {
  519. font-size: 20px;
  520. font-weight: 700;
  521. }
  522. .mini-stat-label {
  523. font-size: 12px;
  524. color: #64748B;
  525. margin-top: 4px;
  526. }
  527. </style>