index.vue 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649
  1. <template>
  2. <div class="member-statistics-page">
  3. <!-- 会员统计页面容器 -->
  4. <div class="member-statistics-container">
  5. <!-- =================== 顶部查询区域 =================== -->
  6. <el-form class="filter-container" :inline="true" :model="queryParams" ref="queryForm">
  7. <div class="filter-item">
  8. <!-- 按日/按月切换 -->
  9. <el-radio-group v-model="queryParams.dateType" size="small" @input="handleChangeDateType">
  10. <el-radio-button label="day">按每日</el-radio-button>
  11. <el-radio-button label="month">按每月</el-radio-button>
  12. </el-radio-group>
  13. </div>
  14. <div class="filter-item">
  15. <!-- 日期选择器 -->
  16. <el-date-picker
  17. v-model="queryParams.dateRange"
  18. :key="queryParams.dateType"
  19. :type="queryParams.dateType === 'day' ? 'daterange' : 'monthrange'"
  20. size="small"
  21. range-separator="至"
  22. start-placeholder="开始日期"
  23. end-placeholder="结束日期"
  24. style="width: 260px;"
  25. :clearable="false"
  26. />
  27. </div>
  28. <!-- 经销商选择 -->
  29. <el-form-item class="filter-item" label="经销商" prop="dealerId">
  30. <!-- 经销商选择 -->
  31. <el-select
  32. v-model="queryParams.dealerId"
  33. placeholder="请选择经销商"
  34. size="small"
  35. clearable filterable remote
  36. :remote-method="loadCompanyOptions"
  37. v-select-load-more="loadMoreCompanyOptions"
  38. :loading="companyOptionsLoading"
  39. @change="handleChangeDealer"
  40. style="width: 140px;"
  41. >
  42. <el-option v-for="item in dealerOptions" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue" />
  43. </el-select>
  44. </el-form-item>
  45. <el-form-item class="filter-item" label="群管" prop="groupId">
  46. <!-- 分组选择 -->
  47. <el-select
  48. v-model="queryParams.groupId"
  49. placeholder="请选择群管"
  50. size="small"
  51. clearable filterable remote
  52. :remote-method="loadCompanyUserOptions"
  53. v-select-load-more="loadMoreCompanyUserOptions"
  54. :loading="companyUserOptionsLoading"
  55. style="width: 140px;"
  56. >
  57. <el-option v-for="item in groupOptions" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue" />
  58. </el-select>
  59. </el-form-item>
  60. <el-form-item class="filter-item" label="会员" prop="memberId">
  61. <!-- 会员选择 -->
  62. <el-select
  63. v-model="queryParams.memberId"
  64. placeholder="请选择会员"
  65. size="small"
  66. clearable filterable remote
  67. :remote-method="loadUserOptions"
  68. v-select-load-more="loadMoreUserOptions"
  69. :loading="userOptionsLoading"
  70. style="width: 140px;"
  71. >
  72. <el-option v-for="item in memberOptions" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue" />
  73. </el-select>
  74. </el-form-item>
  75. <el-form-item class="filter-item" label="训练营" prop="trainerId">
  76. <!-- 训练营选择 -->
  77. <el-select
  78. v-model="queryParams.trainerId"
  79. placeholder="请选择训练营"
  80. size="small"
  81. clearable filterable remote
  82. :remote-method="loadTrainerOptions"
  83. v-select-load-more="loadMoreTrainerOptions"
  84. :loading="trainerOptionsLoading"
  85. @change="changeTrainer"
  86. style="width: 140px;"
  87. >
  88. <el-option v-for="item in trainerOptions" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue" />
  89. </el-select>
  90. </el-form-item>
  91. <el-form-item class="filter-item" label="营期" prop="campPeriod">
  92. <!-- 营期选择 -->
  93. <el-select
  94. v-model="queryParams.campPeriod"
  95. placeholder="请选择营期"
  96. size="small"
  97. clearable filterable remote
  98. :remote-method="loadPeriodOptions"
  99. v-select-load-more="loadMorePeriodOptions"
  100. :loading="campPeriodOptionsLoading"
  101. @change="changeCampPeriod"
  102. style="width: 140px;"
  103. >
  104. <el-option v-for="item in campPeriodOptions" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue" />
  105. </el-select>
  106. </el-form-item>
  107. <el-form-item class="filter-item" label="课程" prop="courseId">
  108. <!-- 课程选择 -->
  109. <el-select
  110. v-model="queryParams.courseId"
  111. placeholder="请选择课程"
  112. size="small"
  113. clearable filterable remote
  114. :remote-method="loadCourseOptions"
  115. v-select-load-more="loadMoreCourseOptions"
  116. :loading="courseOptionsLoading"
  117. style="width: 140px;"
  118. >
  119. <el-option v-for="item in courseOptions" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue" />
  120. </el-select>
  121. </el-form-item>
  122. <el-form-item class="filter-item" label="手机号" prop="mobile">
  123. <!-- 手机号输入框 -->
  124. <el-input
  125. v-model="queryParams.mobile"
  126. placeholder="请输入手机号"
  127. size="small"
  128. clearable
  129. style="width: 200px;"
  130. />
  131. </el-form-item>
  132. <div class="filter-item">
  133. <!-- 操作按钮组 -->
  134. <el-button type="primary" icon="el-icon-search" size="small" @click="handleQuery">搜索</el-button>
  135. </div>
  136. <div class="filter-item" style="margin-left: auto; display: none;">
  137. <el-button size="small">更换所属群管</el-button>
  138. <el-button type="primary" size="small">导出全部</el-button>
  139. </div>
  140. </el-form>
  141. </div>
  142. <!-- =================== 表格数据展示区域 =================== -->
  143. <div class="table-container">
  144. <el-table
  145. v-loading="loading"
  146. :data="tableData"
  147. border
  148. style="width: 100%;"
  149. show-summary
  150. :summary-row-style="{ background: '#fafafa' }"
  151. >
  152. <!-- 选择列 -->
  153. <el-table-column type="selection" width="50" align="center" />
  154. <!-- 序号列 -->
  155. <el-table-column label="序号" type="index" width="60" align="center" />
  156. <!-- 统计日期列 -->
  157. <el-table-column prop="date" label="统计日期" min-width="100" align="center" />
  158. <!-- 会员名称列 -->
  159. <el-table-column prop="memberName" label="会员名称" min-width="100" align="center" />
  160. <!-- 性别列 -->
  161. <el-table-column prop="gender" label="性别" width="60" align="center" />
  162. <!-- 手机号列 -->
  163. <el-table-column prop="mobile" label="手机号" min-width="100" align="center" />
  164. <!-- 会员标签列 -->
  165. <el-table-column prop="memberTag" label="会员标签" min-width="100" align="center" />
  166. <!-- 所属群管列 -->
  167. <el-table-column prop="groupName" label="所属群管" min-width="100" align="center" />
  168. <!-- 所属经销商列 -->
  169. <el-table-column prop="dealerName" label="所属经销商" min-width="100" align="center" />
  170. <!-- 观看课程列 -->
  171. <el-table-column prop="viewCount" label="观看课程" min-width="80" align="center" />
  172. <!-- 完课课程列 -->
  173. <el-table-column prop="finishCount" label="完课课程" min-width="80" align="center" />
  174. <!-- 完课率列 -->
  175. <el-table-column prop="finishRate" label="完课率" min-width="80" align="center" />
  176. <!-- 观看次数列 -->
  177. <el-table-column prop="viewTimes" label="观看次数" min-width="80" align="center" />
  178. <!-- 完课次数列 -->
  179. <el-table-column prop="finishTimes" label="完课次数" min-width="80" align="center" />
  180. <!-- 视频率列 -->
  181. <el-table-column prop="videoRate" label="视频率" min-width="80" align="center" />
  182. <!-- 操作列 -->
  183. <el-table-column label="操作" fixed="right" min-width="160" align="center">
  184. <template slot-scope="scope">
  185. <!-- 会员分析按钮 -->
  186. <el-button type="text" size="small" style="color: #409EFF;">会员分析</el-button>
  187. <!-- 红包记录按钮 -->
  188. <el-button type="text" size="small" style="color: #409EFF;">红包记录</el-button>
  189. </template>
  190. </el-table-column>
  191. </el-table>
  192. <pagination
  193. v-show="queryParams.total > 0"
  194. :total="queryParams.total"
  195. :page.sync="queryParams.pageNum"
  196. :limit.sync="queryParams.pageSize"
  197. @pagination="getList"
  198. />
  199. </div>
  200. </div>
  201. </template>
  202. <script>
  203. import {dailyData} from '@/api/statistics/member'
  204. import {getCompanyListLikeName} from '@/api/company/company'
  205. import {getCompanyUserListLikeName} from '@/api/company/companyUser'
  206. import {getUserListLikeName} from '@/api/users/user'
  207. import {getCampListLikeName} from '@/api/course/userCourseCamp'
  208. import {getPeriodListLikeName} from '@/api/course/userCoursePeriod'
  209. import {getVideoListLikeName} from '@/api/course/userCourseVideo'
  210. import moment from "moment"
  211. export default {
  212. // 组件名称
  213. name: 'MemberStatistics',
  214. // 组件数据
  215. data() {
  216. return {
  217. // ============= 查询参数 =============
  218. queryParams: {
  219. dateType: 'day', // 日期类型:day-按日,month-按月
  220. dateRange: [new Date(), new Date()], // 日期范围
  221. dealerId: '', // 经销商ID
  222. groupId: '', // 群组ID
  223. memberId: '', // 会员ID
  224. trainerId: '', // 训练官ID
  225. campPeriod: '', // 营期
  226. courseId: '', // 课程ID
  227. mobile: '', // 手机号
  228. pageNum: 1, // 当前页码
  229. pageSize: 30, // 每页记录数
  230. total: 0, // 总记录数
  231. },
  232. // ============= 页面状态 =============
  233. loading: false, // 加载状态
  234. // ============= 数据相关 =============
  235. tableData: [], // 表格数据
  236. // ============= 下拉选项数据 =============
  237. companyOptionsParams: {
  238. name: undefined,
  239. hasNextPage: false,
  240. pageNum: 1,
  241. pageSize: 10
  242. },
  243. companyOptionsLoading: false,
  244. dealerOptions: [], // 经销商选项
  245. companyUserOptionsParams: {
  246. name: undefined,
  247. companyId: undefined,
  248. hasNextPage: false,
  249. pageNum: 1,
  250. pageSize: 10
  251. },
  252. companyUserOptionsLoading: false,
  253. groupOptions: [], // 群组选项
  254. userOptionsParams: {
  255. name: undefined,
  256. hasNextPage: false,
  257. pageNum: 1,
  258. pageSize: 10
  259. },
  260. userOptionsLoading: false,
  261. memberOptions: [], // 会员选项
  262. trainerOptionsParams: {
  263. name: undefined,
  264. hasNextPage: false,
  265. pageNum: 1,
  266. pageSize: 10
  267. },
  268. trainerOptionsLoading: false,
  269. trainerOptions: [], // 训练营选项
  270. campPeriodOptionsParams: {
  271. name: undefined,
  272. campId: undefined,
  273. hasNextPage: false,
  274. pageNum: 1,
  275. pageSize: 10
  276. },
  277. campPeriodOptionsLoading: false,
  278. campPeriodOptions: [], // 营期选项
  279. courseOptionsParams: {
  280. name: undefined,
  281. periodId: undefined,
  282. hasNextPage: false,
  283. pageNum: 1,
  284. pageSize: 10
  285. },
  286. courseOptionsLoading: false,
  287. courseOptions: [] // 课程选项
  288. }
  289. },
  290. // ============= 生命周期钩子 =============
  291. created() {
  292. // 页面创建时初始化数据
  293. this.getList() // 加载表格数据
  294. },
  295. // ============= 组件方法 =============
  296. methods: {
  297. /**
  298. * 获取表格数据
  299. * 根据查询参数从服务器获取会员统计数据
  300. */
  301. getList() {
  302. this.loading = true // 显示加载中状态
  303. // 模拟异步请求
  304. let query = {
  305. type: this.queryParams.dateType === 'day' ? 1 : 2,
  306. startDate: moment(this.queryParams.dateRange[0]).format('YYYY-MM-DD'),
  307. endDate: moment(this.queryParams.dateRange[1]).format('YYYY-MM-DD'),
  308. companyId: this.queryParams.dealerId,
  309. companyUserId: this.queryParams.groupId,
  310. userId: this.queryParams.memberId,
  311. phone: this.queryParams.mobile,
  312. trainCampId: this.queryParams.trainerId,
  313. periodId: this.queryParams.campPeriod,
  314. videoId: this.queryParams.courseId,
  315. pageNum: this.queryParams.pageNum,
  316. pageSize: this.queryParams.pageSize
  317. }
  318. dailyData(query).then(response => {
  319. if (response && response.code === 200) {
  320. this.tableData = response.data.list
  321. this.queryParams.total = response.data.total
  322. this.loading = false // 隐藏加载中状态
  323. }
  324. })
  325. },
  326. // 切换日期类型
  327. handleChangeDateType() {
  328. this.queryParams.dateRange = [new Date(), new Date()]
  329. // 重新请求数据
  330. this.getList()
  331. },
  332. /**
  333. * 查询按钮点击事件
  334. * 根据筛选条件重新查询数据
  335. */
  336. handleQuery() {
  337. this.queryParams.pageNum = 1 // 重置为第一页
  338. this.getList() // 重新获取数据
  339. },
  340. /**
  341. * 重置查询条件
  342. * 清空所有筛选条件并重新加载数据
  343. */
  344. resetQuery() {
  345. // 重置所有查询条件
  346. this.queryParams = {
  347. dateType: 'day', // 重置日期类型为按天
  348. dateRange: [], // 清空日期范围
  349. publicId: '', // 清空公众号ID
  350. dealerId: '', // 清空经销商ID
  351. groupId: '', // 清空群组ID
  352. memberId: '', // 清空会员ID
  353. trainerId: '', // 清空训练官ID
  354. campPeriod: '', // 清空营期
  355. courseId: '', // 清空课程ID
  356. mobile: '' // 清空手机号
  357. }
  358. this.getList() // 重新获取数据
  359. },
  360. // 加载经销商选项
  361. loadCompanyOptions(query) {
  362. this.dealerOptions = [];
  363. if (query === '') {
  364. return;
  365. }
  366. this.companyOptionsParams.pageNum = 1
  367. this.companyOptionsParams.name = query
  368. this.companyOptionsLoading = true;
  369. this.getCompanyListLikeName()
  370. },
  371. getCompanyListLikeName() {
  372. getCompanyListLikeName(this.companyOptionsParams).then(response => {
  373. this.dealerOptions = [...this.dealerOptions, ...response.data.list]
  374. this.companyOptionsParams.hasNextPage = response.data.hasNextPage
  375. this.companyOptionsLoading = false;
  376. });
  377. },
  378. loadMoreCompanyOptions() {
  379. if (!this.companyOptionsParams.hasNextPage) {
  380. return;
  381. }
  382. this.companyOptionsParams.pageNum += 1
  383. this.getCompanyListLikeName()
  384. },
  385. handleChangeDealer(val) {
  386. this.groupOptions = [];
  387. this.companyUserOptionsLoading = true;
  388. if (!val) {
  389. this.companyUserOptionsParams.companyId = undefined
  390. this.getCompanyUserListLikeName()
  391. return
  392. }
  393. this.companyUserOptionsParams.companyId = val
  394. this.companyUserOptionsParams.pageNum = 1
  395. this.getCompanyUserListLikeName()
  396. },
  397. // 群组选项
  398. loadCompanyUserOptions(query) {
  399. this.groupOptions = [];
  400. if (query === '') {
  401. return;
  402. }
  403. this.companyUserOptionsParams.pageNum = 1
  404. this.companyUserOptionsParams.name = query
  405. this.companyUserOptionsLoading = true;
  406. this.getCompanyUserListLikeName()
  407. },
  408. getCompanyUserListLikeName() {
  409. getCompanyUserListLikeName(this.companyUserOptionsParams).then(response => {
  410. this.groupOptions = [...this.groupOptions, ...response.data.list]
  411. this.companyUserOptionsParams.hasNextPage = response.data.hasNextPage
  412. this.companyUserOptionsLoading = false;
  413. });
  414. },
  415. loadMoreCompanyUserOptions() {
  416. if (!this.companyUserOptionsParams.hasNextPage) {
  417. return;
  418. }
  419. this.companyUserOptionsParams.pageNum += 1
  420. this.getCompanyUserListLikeName()
  421. },
  422. // 会员选项
  423. loadUserOptions(query) {
  424. this.memberOptions = [];
  425. if (query === '') {
  426. return;
  427. }
  428. this.userOptionsParams.pageNum = 1
  429. this.userOptionsParams.name = query
  430. this.userOptionsLoading = true;
  431. this.getUserListLikeName()
  432. },
  433. getUserListLikeName() {
  434. getUserListLikeName(this.userOptionsParams).then(response => {
  435. this.memberOptions = [...this.memberOptions, ...response.data.list]
  436. this.userOptionsParams.hasNextPage = response.data.hasNextPage
  437. this.userOptionsLoading = false;
  438. });
  439. },
  440. loadMoreUserOptions() {
  441. if (!this.userOptionsParams.hasNextPage) {
  442. return;
  443. }
  444. this.userOptionsParams.pageNum += 1
  445. this.getUserListLikeName()
  446. },
  447. // 训练营选项
  448. loadTrainerOptions(query) {
  449. this.trainerOptions = [];
  450. if (query === '') {
  451. return;
  452. }
  453. this.trainerOptionsParams.pageNum = 1
  454. this.trainerOptionsParams.name = query
  455. this.trainerOptionsLoading = true;
  456. this.getTrainerListLikeName()
  457. },
  458. getTrainerListLikeName() {
  459. getCampListLikeName(this.trainerOptionsParams).then(response => {
  460. this.trainerOptions = [...this.trainerOptions, ...response.data.list]
  461. this.trainerOptionsParams.hasNextPage = response.data.hasNextPage
  462. this.trainerOptionsLoading = false;
  463. });
  464. },
  465. loadMoreTrainerOptions() {
  466. if (!this.trainerOptionsParams.hasNextPage) {
  467. return;
  468. }
  469. this.trainerOptionsParams.pageNum += 1
  470. this.getTrainerListLikeName()
  471. },
  472. changeTrainer(val) {
  473. this.campPeriodOptions = [];
  474. this.campPeriodOptionsLoading = true;
  475. if (!val) {
  476. this.campPeriodOptionsParams.companyId = undefined
  477. this.getPeriodListLikeName()
  478. return
  479. }
  480. this.campPeriodOptionsParams.companyId = val
  481. this.campPeriodOptionsParams.pageNum = 1
  482. this.getPeriodListLikeName()
  483. },
  484. // 营期选项
  485. loadPeriodOptions(query) {
  486. this.campPeriodOptions = [];
  487. if (query === '') {
  488. return;
  489. }
  490. this.campPeriodOptionsParams.pageNum = 1
  491. this.campPeriodOptionsParams.name = query
  492. this.campPeriodOptionsLoading = true;
  493. this.getPeriodListLikeName()
  494. },
  495. getPeriodListLikeName() {
  496. getPeriodListLikeName(this.campPeriodOptionsParams).then(response => {
  497. this.campPeriodOptions = [...this.campPeriodOptions, ...response.data.list]
  498. this.campPeriodOptionsParams.hasNextPage = response.data.hasNextPage
  499. this.campPeriodOptionsLoading = false;
  500. });
  501. },
  502. loadMorePeriodOptions() {
  503. if (!this.campPeriodOptionsParams.hasNextPage) {
  504. return;
  505. }
  506. this.campPeriodOptionsParams.pageNum += 1
  507. this.getPeriodListLikeName()
  508. },
  509. changeCampPeriod(val) {
  510. this.courseOptions = []
  511. this.courseOptionsLoading = true;
  512. if (!val) {
  513. this.courseOptionsParams.periodId = undefined
  514. this.getCourseListLikeName()
  515. return
  516. }
  517. this.courseOptionsParams.periodId = val
  518. this.courseOptionsParams.pageNum = 1
  519. this.getCourseListLikeName()
  520. },
  521. // 课程选项
  522. loadCourseOptions(query) {
  523. this.courseOptions = [];
  524. if (query === '') {
  525. return;
  526. }
  527. this.courseOptionsParams.pageNum = 1
  528. this.courseOptionsParams.name = query
  529. this.courseOptionsLoading = true;
  530. this.getCourseListLikeName()
  531. },
  532. getCourseListLikeName() {
  533. getVideoListLikeName(this.courseOptionsParams).then(response => {
  534. this.courseOptions = [...this.courseOptions, ...response.data.list]
  535. this.courseOptionsParams.hasNextPage = response.data.hasNextPage
  536. this.courseOptionsLoading = false;
  537. });
  538. },
  539. loadMoreCourseOptions() {
  540. if (!this.courseOptionsParams.hasNextPage) {
  541. return;
  542. }
  543. this.courseOptionsParams.pageNum += 1
  544. this.getCourseListLikeName()
  545. },
  546. }
  547. }
  548. </script>
  549. <style scoped>
  550. .member-statistics-page {
  551. padding: 10px;
  552. }
  553. /* 会员统计页面容器样式 */
  554. .member-statistics-container {
  555. padding: 10px;
  556. background-color: #fff;
  557. }
  558. /* 筛选区域样式 */
  559. .filter-container {
  560. display: flex;
  561. flex-wrap: wrap;
  562. align-items: center;
  563. background-color: #fff;
  564. padding: 15px;
  565. border-radius: 4px;
  566. }
  567. .filter-item {
  568. margin-right: 15px;
  569. margin-bottom: 10px;
  570. }
  571. /* 表格容器样式 */
  572. .table-container {
  573. background-color: #FFF;
  574. height: 72vh;
  575. }
  576. /* 表格深度样式 */
  577. ::v-deep .el-table th {
  578. color: #606266;
  579. font-weight: bold;
  580. }
  581. ::v-deep .el-table--border th, ::v-deep .el-table--border td {
  582. border-right: 1px solid #EBEEF5;
  583. }
  584. ::v-deep .el-table {
  585. border: 1px solid #EBEEF5;
  586. border-bottom: none;
  587. }
  588. ::v-deep .el-input__inner {
  589. border-radius: 4px;
  590. }
  591. ::v-deep .el-button {
  592. border-radius: 4px;
  593. }
  594. </style>