statistics.vue 25 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000
  1. <template>
  2. <view class="container">
  3. <!-- 统计类型切换 -->
  4. <view class="stat-tabs">
  5. <view
  6. class="stat-tab"
  7. :class="{ active: currentStatType === 'task' }"
  8. @click="switchStatType('task')"
  9. >
  10. 任务统计
  11. </view>
  12. <view
  13. class="stat-tab"
  14. :class="{ active: currentStatType === 'service' }"
  15. @click="switchStatType('service')"
  16. >
  17. 服务单统计
  18. </view>
  19. </view>
  20. <!-- 日期范围和筛选 -->
  21. <view class="date-filter-bar mb16">
  22. <view class="date-range" @click="showDatePicker = true">
  23. <image class="w32 h32" src="https://ysrw-1395926010.cos.ap-chengdu.myqcloud.com/image/icon_time.png" mode=""></image>
  24. <text class="date-text">{{ dateRangeText }}</text>
  25. <text class="arrow-down">▼</text>
  26. </view>
  27. <view class="filter-btn" @click="showFilter = true">
  28. <image class="w32 h32" src="https://ysrw-1395926010.cos.ap-chengdu.myqcloud.com/image/icon_select.png" mode=""></image>
  29. <text>筛选</text>
  30. </view>
  31. </view>
  32. <!-- 内容区域 - 动态显示不同的统计表格 -->
  33. <template v-if="currentStatType === 'task'">
  34. <!-- 任务完成统计表格 -->
  35. <statistics-table
  36. :summary-title="summaryTitle.task[0]"
  37. :summary-stats="summaryStats.taskComplete"
  38. :columns="tableColumns.task"
  39. :table-data="tableData.taskComplete"
  40. :loading="loading.task"
  41. height="600rpx"
  42. @load-more="loadMore('taskComplete')"
  43. >
  44. </statistics-table>
  45. <!-- 任务创建统计表格 -->
  46. <statistics-table
  47. :summary-title="summaryTitle.task[1]"
  48. :summary-stats="summaryStats.taskCreate"
  49. :columns="tableColumns.task"
  50. :table-data="tableData.taskCreate"
  51. :loading="loading.task"
  52. height="600rpx"
  53. @load-more="loadMore('taskCreate')"
  54. >
  55. </statistics-table>
  56. </template>
  57. <template v-else-if="currentStatType === 'service'">
  58. <!-- 服务单统计表格 -->
  59. <statistics-table
  60. :summary-title="summaryTitle.service[0]"
  61. :summary-stats="summaryStats.service"
  62. :columns="tableColumns.service"
  63. :table-data="tableData.service"
  64. :loading="loading.service"
  65. height="600rpx"
  66. @load-more="loadMore('service')"
  67. >
  68. </statistics-table>
  69. <!-- <statistics-table
  70. :summary-title="summaryTitle.service[1]"
  71. :summary-stats="summaryStats.service"
  72. :columns="tableColumns.service"
  73. :table-data="tableData.service"
  74. :loading="loading.service"
  75. @load-more="loadMore('service')"
  76. >
  77. </statistics-table> -->
  78. </template>
  79. <!-- 日期选择弹窗 -->
  80. <view class="date-picker-popup" v-if="showDatePicker" @click="showDatePicker = false">
  81. <view class="date-picker-content" @click.stop>
  82. <view class="picker-header">
  83. <view class="picker-cancel" @click="showDatePicker = false">取消</view>
  84. <view class="picker-title">选择日期范围</view>
  85. <view class="picker-confirm" @click="confirmDateRange">确定</view>
  86. </view>
  87. <view class="picker-body">
  88. <view class="date-item">
  89. <text class="date-label">开始日期</text>
  90. <picker mode="date" :value="tempDateRange.startDate" @change="onStartDateChange">
  91. <view class="date-value">{{ tempDateRange.startDate || '请选择' }}</view>
  92. </picker>
  93. </view>
  94. <view class="date-item">
  95. <text class="date-label">结束日期</text>
  96. <picker mode="date" :value="tempDateRange.endDate" @change="onEndDateChange">
  97. <view class="date-value">{{ tempDateRange.endDate || '请选择' }}</view>
  98. </picker>
  99. </view>
  100. </view>
  101. </view>
  102. </view>
  103. <!-- 筛选弹窗 -->
  104. <view class="filter-popup" v-if="showFilter" @click="closeFilter">
  105. <view class="filter-content" @click.stop>
  106. <view class="filter-header">
  107. <view class="filter-title">筛选</view>
  108. <image class="filter-close-btn" src="https://ysrw-1395926010.cos.ap-chengdu.myqcloud.com/image/icon_cross.png" @click="closeFilter"></image>
  109. </view>
  110. <!-- 根据统计类型显示不同的筛选条件 -->
  111. <template v-if="currentStatType === 'task'">
  112. <!-- 任务类型筛选 -->
  113. <view class="filter-group">
  114. <view class="group-label">任务类型</view>
  115. <view class="filter-tags">
  116. <view class="filter-tag" :class="{ active: tempSelectedTaskType.includes(item.dictValue) }"
  117. v-for="(item, index) in taskTypeDict" :key="index" @click="toggleTaskType(item.dictValue)">
  118. {{ item.dictLabel }}
  119. </view>
  120. </view>
  121. </view>
  122. <!-- 任务状态筛选 -->
  123. <view class="filter-group">
  124. <view class="group-label">任务状态</view>
  125. <view class="filter-tags">
  126. <view class="filter-tag" :class="{ active: tempSelectedTaskStatus.includes(item.dictValue) }"
  127. v-for="(item, index) in taskStatusDict" :key="index" @click="toggleTaskStatus(item.dictValue)">
  128. {{ item.dictLabel }}
  129. </view>
  130. </view>
  131. </view>
  132. </template>
  133. <template v-else-if="currentStatType === 'service'">
  134. <view class="filter-group">
  135. <view class="group-label">服务单结算状态</view>
  136. <view class="filter-tags">
  137. <view class="filter-tag" :class="{ active: tempSelectedSettlementStatus === item.dictValue }"
  138. v-for="(item, index) in serviceOrderSettlementStatusDict" :key="index" @click="selectSettlementStatus(item.dictValue)">
  139. {{ item.dictLabel }}
  140. </view>
  141. </view>
  142. </view>
  143. <view class="filter-group">
  144. <view class="group-label">服务单状态</view>
  145. <view class="filter-tags">
  146. <view class="filter-tag" :class="{ active: tempSelectedServiceStatus === item.dictValue }"
  147. v-for="(item, index) in serviceOrderAuditStatusDict" :key="index"
  148. @click="selectServiceStatus(item.dictValue)">
  149. {{ item.dictLabel }}
  150. </view>
  151. </view>
  152. </view>
  153. </template>
  154. <!-- 操作按钮 -->
  155. <view class="filter-actions">
  156. <view class="reset-btn" @click="resetFilters">重置</view>
  157. <view class="confirm-btn" @click="confirmFilters">确定</view>
  158. </view>
  159. </view>
  160. </view>
  161. </view>
  162. </template>
  163. <script>
  164. import utils from '@/utils/common.js'
  165. import StatisticsTable from '@/components/StatisticsTable.vue'
  166. import { getTaskCompleteStats, getTaskCreateStats, getStatisticsSummary } from '@/api/task.js'
  167. export default {
  168. components: {
  169. StatisticsTable
  170. },
  171. data() {
  172. const d = new Date();
  173. const y = d.getFullYear();
  174. const m = String(d.getMonth() + 1).padStart(2, '0');
  175. const dd = String(d.getDate()).padStart(2, '0');
  176. const todayStr = `${y}-${m}-${dd}`;
  177. const weekAgo = new Date(d);
  178. weekAgo.setDate(weekAgo.getDate() - 7);
  179. const y2 = weekAgo.getFullYear();
  180. const m2 = String(weekAgo.getMonth() + 1).padStart(2, '0');
  181. const dd2 = String(weekAgo.getDate()).padStart(2, '0');
  182. const weekAgoStr = `${y2}-${m2}-${dd2}`;
  183. return {
  184. userInfo: uni.getStorageSync("userInfo")||'',
  185. statusBarHeight: uni.getSystemInfoSync().statusBarHeight + 'px',
  186. showDatePicker: false,
  187. showFilter: false,
  188. currentStatType: 'task', // 当前统计类型: task-任务统计, service-服务单统计
  189. // 日期范围
  190. dateRange: {
  191. startDate: weekAgoStr,
  192. endDate: todayStr
  193. },
  194. tempDateRange: {
  195. startDate: weekAgoStr,
  196. endDate: todayStr
  197. },
  198. // 任务统计筛选
  199. selectedTaskType: [],
  200. selectedTaskStatus: [],
  201. tempSelectedTaskType: [],
  202. tempSelectedTaskStatus: [],
  203. // 服务单统计筛选
  204. selectedServiceStatus: '',
  205. tempSelectedServiceStatus: '',
  206. selectedSettlementStatus: '',
  207. tempSelectedSettlementStatus: '',
  208. serviceOrderAuditStatusDict: [],
  209. serviceOrderSettlementStatusDict: [],
  210. // 统计标题(分开存储)
  211. summaryTitle: {
  212. service: [ '汇总统计', '明细统计'],
  213. task: ['任务完成统计', '任务创建统计']
  214. },
  215. // 统计数据
  216. summaryStats: {
  217. taskComplete: [{
  218. label: '总积分',
  219. value:0
  220. }],
  221. taskCreate: [{
  222. label: '总积分',
  223. value: 0
  224. }],
  225. service: [{
  226. label: '总积分',
  227. value: 0
  228. }]
  229. },
  230. // 表格列定义
  231. tableColumns: {
  232. task: [{
  233. title: '归属',
  234. key: 'deptName',
  235. width: '24%'
  236. },
  237. {
  238. title: '类型',
  239. key: 'taskType',
  240. width: '12%'
  241. },
  242. {
  243. title: '数量',
  244. key: 'count',
  245. width: '10%'
  246. },
  247. {
  248. title: '客户',
  249. key: 'doctorName',
  250. width: '24%'
  251. },{
  252. title: "业务员",
  253. key: "companyUserName",
  254. width: "18%"
  255. },
  256. {
  257. title: '积分',
  258. key: 'totalPoints',
  259. width: '12%'
  260. }],
  261. service: [{
  262. title: '归属部门',
  263. key: 'deptName',
  264. width: '24%'
  265. },
  266. {
  267. title: '服务单数',
  268. key: 'serviceOrderCount',
  269. width: '12%'
  270. },
  271. {
  272. title: '任务数',
  273. key: 'taskCount',
  274. width: '10%'
  275. },
  276. {
  277. title: '客服数',
  278. key: 'companyUserCount',
  279. width: '12%'
  280. },
  281. {
  282. title: '医生数',
  283. key: 'doctorCount',
  284. width: '12%'
  285. },
  286. {
  287. title: '任务金额',
  288. key: 'taskAmount',
  289. width: '15%'
  290. },
  291. {
  292. title: '总金额',
  293. key: 'totalAmount',
  294. width: '15%'
  295. }
  296. ]
  297. },
  298. // 表格数据
  299. tableData: {
  300. taskComplete: [
  301. ],
  302. taskCreate: [
  303. ],
  304. service: []
  305. },
  306. // 分页与加载更多控制
  307. pageSize: 10,
  308. page: {
  309. taskComplete: 1,
  310. taskCreate: 1
  311. },
  312. hasMore: {
  313. taskComplete: true,
  314. taskCreate: true
  315. },
  316. taskTypeDict: [],//任务类型
  317. taskStatusDict: [],//任务状态
  318. // 加载状态
  319. loading: {
  320. task: false,
  321. service: false
  322. }
  323. }
  324. },
  325. computed: {
  326. dateRangeText() {
  327. const d = new Date()
  328. const y = d.getFullYear()
  329. const m = String(d.getMonth() + 1).padStart(2, '0')
  330. const dd = String(d.getDate()).padStart(2, '0')
  331. const today = `${y}-${m}-${dd}`
  332. const start = this.dateRange && this.dateRange.startDate ? this.dateRange.startDate : today
  333. const end = this.dateRange && this.dateRange.endDate ? this.dateRange.endDate : today
  334. return `${start} 至 ${end}`
  335. }
  336. },
  337. watch: {
  338. showFilter(newVal) {
  339. if (newVal) {
  340. // 打开弹窗时,同步临时选择值
  341. if (this.currentStatType === 'task') {
  342. this.tempSelectedTaskType = [...this.selectedTaskType]
  343. this.tempSelectedTaskStatus = [...this.selectedTaskStatus]
  344. } else {
  345. this.tempSelectedServiceStatus = this.selectedServiceStatus
  346. this.tempSelectedSettlementStatus = this.selectedSettlementStatus
  347. }
  348. }
  349. },
  350. currentStatType(newVal) {
  351. // 切换统计类型时,重置筛选条件(可选)
  352. this.resetFilters()
  353. // 重新加载数据
  354. if (newVal === 'task') {
  355. this.loadData()
  356. } else {
  357. this.getStatisticsSummary()
  358. }
  359. }
  360. },
  361. onLoad: async function(options) {
  362. try {
  363. this.taskTypeDict = await utils.getDicts("task_type");//任务类型
  364. this.taskStatusDict = await utils.getDicts("task_status");//任务状态
  365. this.serviceOrderAuditStatusDict = await utils.getDicts("service_order_audit_status");//服务单状态
  366. this.serviceOrderSettlementStatusDict = await utils.getDicts("service_order_settlement_status");//服务单结算状态
  367. } catch (e) {
  368. console.log('获取字典数据失败:', e)
  369. }
  370. this.loadData()
  371. },
  372. onReachBottom() {
  373. this.loadMore(this.currentStatType)
  374. },
  375. methods: {
  376. //服务单统计汇总
  377. getStatisticsSummary(){
  378. let params = {
  379. companyId: this.userInfo.companyId,
  380. deptId: this.userInfo.deptId,
  381. startTime: this.dateRange.startDate,
  382. endTime: this.dateRange.endDate,
  383. auditStatus: String(this.selectedServiceStatus || ''),
  384. settlementStatus: String(this.selectedSettlementStatus || '')
  385. }
  386. getStatisticsSummary(params).then(res => {
  387. if (res.code === 200) {
  388. const d = res.data || {}
  389. const list = Array.isArray(d) ? d : (Array.isArray(d.rows) ? d.rows : [])
  390. this.tableData.service = list
  391. const totalAmount = d.totalAmount != null
  392. ? d.totalAmount
  393. : list.reduce((sum, cur) => sum + Number(cur.totalAmount || 0), 0)
  394. this.summaryStats.service = [
  395. { label: '总金额', value: totalAmount }
  396. ]
  397. }
  398. })
  399. },
  400. // 构造任务统计公共请求参数
  401. buildTaskParams(pageNum = 1) {
  402. const userInfo = uni.getStorageSync('userInfo') || {}
  403. const params = {
  404. companyId: userInfo.companyId || 1,
  405. pageNum,
  406. pageSize: this.pageSize,
  407. startDate: this.dateRange.startDate,
  408. endDate: this.dateRange.endDate
  409. }
  410. if (this.selectedTaskType && this.selectedTaskType.length > 0) {
  411. params.taskTypes = this.selectedTaskType.join(',')
  412. }
  413. if (this.selectedTaskStatus && this.selectedTaskStatus.length > 0) {
  414. params.taskStatuses = this.selectedTaskStatus.join(',')
  415. }
  416. return params
  417. },
  418. // 解析任务统计响应,返回 rows/totalCount/totalPoints
  419. parseTaskStats(res) {
  420. const d = (res && res.data) || {}
  421. const rows = Array.isArray(d.rows) ? d.rows : (res.rows || [])
  422. const totalCount = (d.totalCount != null) ? d.totalCount : (res.total || rows.length || 0)
  423. const totalPoints = (d.totalPoints != null)
  424. ? d.totalPoints
  425. : rows.reduce((sum, item) => sum + (item.totalPoints || 0), 0)
  426. const normalized = rows.map(item => ({
  427. ...item,
  428. taskType: item.taskType || '无类型'
  429. }))
  430. return { rows: normalized, totalCount, totalPoints }
  431. },
  432. // 切换统计类型
  433. switchStatType(type) {
  434. if (this.currentStatType !== type) {
  435. this.currentStatType = type
  436. }
  437. },
  438. goBack() {
  439. uni.navigateBack()
  440. },
  441. onStartDateChange(e) {
  442. this.tempDateRange.startDate = e.detail.value
  443. },
  444. onEndDateChange(e) {
  445. this.tempDateRange.endDate = e.detail.value
  446. },
  447. confirmDateRange() {
  448. if (!this.tempDateRange.startDate || !this.tempDateRange.endDate) {
  449. uni.showToast({
  450. icon: 'none',
  451. title: '请选择完整的日期范围'
  452. })
  453. return
  454. }
  455. if (new Date(this.tempDateRange.startDate) > new Date(this.tempDateRange.endDate)) {
  456. uni.showToast({
  457. icon: 'none',
  458. title: '开始日期不能大于结束日期'
  459. })
  460. return
  461. }
  462. this.dateRange = {
  463. ...this.tempDateRange
  464. }
  465. this.showDatePicker = false
  466. if (this.currentStatType === 'task') {
  467. this.loadData()
  468. } else {
  469. this.getStatisticsSummary()
  470. }
  471. },
  472. closeFilter() {
  473. // 关闭弹窗时,恢复临时选择值为当前选择值
  474. if (this.currentStatType === 'task') {
  475. this.tempSelectedTaskType = this.selectedTaskType
  476. this.tempSelectedTaskStatus = this.selectedTaskStatus
  477. } else {
  478. this.tempSelectedServiceStatus = this.selectedServiceStatus
  479. this.tempSelectedSettlementStatus = this.selectedSettlementStatus
  480. }
  481. this.showFilter = false
  482. },
  483. // 任务筛选相关方法
  484. toggleTaskType(value) {
  485. const index = this.tempSelectedTaskType.indexOf(value);
  486. if (index > -1) {
  487. // 已选中,取消选择
  488. this.tempSelectedTaskType.splice(index, 1);
  489. } else {
  490. // 未选中,添加选择
  491. this.tempSelectedTaskType.push(value);
  492. }
  493. },
  494. toggleTaskStatus(value) {
  495. const index = this.tempSelectedTaskStatus.indexOf(value);
  496. if (index > -1) {
  497. // 已选中,取消选择
  498. this.tempSelectedTaskStatus.splice(index, 1);
  499. } else {
  500. // 未选中,添加选择
  501. this.tempSelectedTaskStatus.push(value);
  502. }
  503. },
  504. // 服务单筛选相关方法
  505. selectServiceType(value) {
  506. if (this.tempSelectedServiceType === value) {
  507. this.tempSelectedServiceType = ''
  508. } else {
  509. this.tempSelectedServiceType = value
  510. }
  511. },
  512. selectServiceStatus(value) {
  513. if (this.tempSelectedServiceStatus === value) {
  514. this.tempSelectedServiceStatus = ''
  515. } else {
  516. this.tempSelectedServiceStatus = value
  517. }
  518. },
  519. selectSettlementStatus(value) {
  520. if (this.tempSelectedSettlementStatus === value) {
  521. this.tempSelectedSettlementStatus = ''
  522. } else {
  523. this.tempSelectedSettlementStatus = value
  524. }
  525. },
  526. resetFilters() {
  527. if (this.currentStatType === 'task') {
  528. this.tempSelectedTaskType = []
  529. this.tempSelectedTaskStatus = []
  530. } else {
  531. this.tempSelectedServiceStatus = ''
  532. this.tempSelectedSettlementStatus = ''
  533. }
  534. },
  535. confirmFilters() {
  536. if (this.currentStatType === 'task') {
  537. this.selectedTaskType = [...this.tempSelectedTaskType]
  538. this.selectedTaskStatus = [...this.tempSelectedTaskStatus]
  539. } else {
  540. this.selectedServiceStatus = this.tempSelectedServiceStatus
  541. this.selectedSettlementStatus = this.tempSelectedSettlementStatus
  542. }
  543. this.showFilter = false
  544. if (this.currentStatType === 'task') {
  545. this.loadData()
  546. } else {
  547. this.getStatisticsSummary()
  548. }
  549. },
  550. async loadData() {
  551. try {
  552. uni.showLoading({
  553. title: '加载中...'
  554. })
  555. // 根据当前统计类型加载数据
  556. if (this.currentStatType === 'task') {
  557. // 加载任务统计数据
  558. // 获取用户信息
  559. const userInfo = uni.getStorageSync('userInfo')
  560. // 重置分页
  561. this.page.taskComplete = 1
  562. this.page.taskCreate = 1
  563. // 并发获取完成与创建统计
  564. const params = this.buildTaskParams(1)
  565. const [completeStatsRes, createStatsRes] = await Promise.all([
  566. getTaskCompleteStats(params),
  567. getTaskCreateStats(params)
  568. ])
  569. // 处理返回的数据
  570. if (completeStatsRes.code === 200) {
  571. const comp = this.parseTaskStats(completeStatsRes)
  572. this.tableData.taskComplete = comp.rows
  573. this.hasMore.taskComplete = comp.rows.length >= this.pageSize
  574. this.summaryStats.taskComplete = [
  575. { label: '总任务数', value: comp.totalCount },
  576. { label: '总积分', value: comp.totalPoints }
  577. ]
  578. }
  579. if (createStatsRes.code === 200) {
  580. const cre = this.parseTaskStats(createStatsRes)
  581. this.tableData.taskCreate = cre.rows
  582. this.hasMore.taskCreate = cre.rows.length >= this.pageSize
  583. this.summaryStats.taskCreate = [
  584. { label: '总任务数', value: cre.totalCount },
  585. { label: '总积分', value: cre.totalPoints }
  586. ]
  587. }
  588. uni.hideLoading()
  589. } else {
  590. // 加载服务单统计数据
  591. // 模拟数据
  592. setTimeout(() => {
  593. uni.hideLoading()
  594. // 这里可以更新数据
  595. }, 500)
  596. }
  597. } catch (e) {
  598. uni.hideLoading()
  599. console.error('加载数据失败', e)
  600. }
  601. },
  602. async loadMore(which) {
  603. // 仅处理两个任务表格的加载更多;无更多时拦截
  604. if (which === 'taskComplete' && !this.hasMore.taskComplete) return
  605. if (which === 'taskCreate' && !this.hasMore.taskCreate) return
  606. this.loading.task = true
  607. try {
  608. if (which === 'taskComplete') {
  609. const nextPage = this.page.taskComplete + 1
  610. const res = await getTaskCompleteStats(this.buildTaskParams(nextPage))
  611. if (res.code === 200) {
  612. const comp = this.parseTaskStats(res)
  613. if (comp.rows.length > 0) {
  614. this.tableData.taskComplete = this.tableData.taskComplete.concat(comp.rows)
  615. this.page.taskComplete = nextPage
  616. }
  617. this.hasMore.taskComplete = comp.rows.length >= this.pageSize
  618. }
  619. } else if (which === 'taskCreate') {
  620. const nextPage2 = this.page.taskCreate + 1
  621. const res2 = await getTaskCreateStats(this.buildTaskParams(nextPage2))
  622. if (res2.code === 200) {
  623. const cre = this.parseTaskStats(res2)
  624. if (cre.rows.length > 0) {
  625. this.tableData.taskCreate = this.tableData.taskCreate.concat(cre.rows)
  626. this.page.taskCreate = nextPage2
  627. }
  628. this.hasMore.taskCreate = cre.rows.length >= this.pageSize
  629. }
  630. }
  631. } finally {
  632. this.loading.task = false
  633. }
  634. },
  635. // 去掉本地“更多任务”占位数据与服务单模拟追加,保持真实接口分页
  636. // 根据taskType获取任务类型名称
  637. getTaskTypeName(taskType) {
  638. // 任务类型映射
  639. const taskTypeMap = {
  640. '1': '医生坐诊',
  641. '2': '科普讲座',
  642. '3': '学术讲座',
  643. '4': '科普文章',
  644. '5': '科普短视频',
  645. '6': '科普长视频',
  646. '7': '空中课堂',
  647. '8': '用药调研',
  648. '9': '问卷调研',
  649. '10': '社群咨询',
  650. '11': '健康问答'
  651. }
  652. return taskTypeMap[taskType] || '未知类型'
  653. }
  654. }
  655. }
  656. </script>
  657. <style lang="scss" scoped>
  658. .container {
  659. min-height: 100vh;
  660. background: #f7f8fa;
  661. padding-bottom: 20rpx;
  662. }
  663. /* 统计类型切换样式 */
  664. .stat-tabs {
  665. display: flex;
  666. background: #fff;
  667. padding: 24rpx;
  668. .stat-tab {
  669. flex: 1;
  670. text-align: center;
  671. padding: 20rpx 0;
  672. font-size: 32rpx;
  673. color: #999;
  674. position: relative;
  675. &.active {
  676. color: #333333;
  677. font-weight: bold;
  678. &::after {
  679. content: '';
  680. position: absolute;
  681. bottom: 0;
  682. left: 50%;
  683. transform: translateX(-50%);
  684. width: 60rpx;
  685. height: 6rpx;
  686. background: #388BFF;
  687. border-radius: 3rpx;
  688. }
  689. }
  690. }
  691. }
  692. .date-filter-bar {
  693. display: flex;
  694. align-items: center;
  695. justify-content: space-between;
  696. padding: 24rpx;
  697. background: #fff;
  698. .date-range {
  699. display: flex;
  700. align-items: center;
  701. gap: 8rpx;
  702. .date-text {
  703. font-family: PingFang SC, PingFang SC;
  704. font-weight: 500;
  705. font-size: 28rpx;
  706. color: #333333;
  707. }
  708. .arrow-down {
  709. font-size: 20rpx;
  710. color: #333333;
  711. }
  712. }
  713. .filter-btn {
  714. display: flex;
  715. align-items: center;
  716. gap: 8rpx;
  717. font-size: 28rpx;
  718. color: #999999;
  719. }
  720. }
  721. .w32 {
  722. width: 32rpx;
  723. }
  724. .h32 {
  725. height: 32rpx;
  726. }
  727. .mb16 {
  728. margin-bottom: 16rpx;
  729. }
  730. .status-tag {
  731. padding: 4rpx 12rpx;
  732. border-radius: 4rpx;
  733. font-size: 24rpx;
  734. &.uncompleted,
  735. &.pending {
  736. background: #FFF3E0;
  737. color: #FF9800;
  738. }
  739. &.completed {
  740. background: #E8F5E9;
  741. color: #4CAF50;
  742. }
  743. &.pendingReview {
  744. background: #E3F2FD;
  745. color: #2196F3;
  746. }
  747. &.rejected {
  748. background: #FFEBEE;
  749. color: #F44336;
  750. }
  751. &.processing {
  752. background: #E3F2FD;
  753. color: #2196F3;
  754. }
  755. &.closed,
  756. &.cancelled {
  757. background: #F5F5F5;
  758. color: #9E9E9E;
  759. }
  760. }
  761. /* 日期选择弹窗样式 */
  762. .date-picker-popup {
  763. position: fixed;
  764. top: 0;
  765. left: 0;
  766. right: 0;
  767. bottom: 0;
  768. background: rgba(0, 0, 0, 0.5);
  769. z-index: 999;
  770. display: flex;
  771. align-items: flex-end;
  772. }
  773. .date-picker-content {
  774. width: 100%;
  775. background: #fff;
  776. border-radius: 24rpx 24rpx 0 0;
  777. .picker-header {
  778. display: flex;
  779. align-items: center;
  780. justify-content: space-between;
  781. padding: 24rpx;
  782. .picker-cancel {
  783. font-size: 30rpx;
  784. color: #666;
  785. }
  786. .picker-title {
  787. font-size: 32rpx;
  788. font-weight: bold;
  789. color: #333;
  790. }
  791. .picker-confirm {
  792. font-size: 30rpx;
  793. color: #388BFF;
  794. }
  795. }
  796. .picker-body {
  797. padding: 24rpx;
  798. .date-item {
  799. display: flex;
  800. align-items: center;
  801. justify-content: space-between;
  802. padding: 24rpx 0;
  803. &:last-child {
  804. border-bottom: none;
  805. }
  806. .date-label {
  807. font-size: 30rpx;
  808. color: #333;
  809. }
  810. .date-value {
  811. font-size: 30rpx;
  812. color: #388BFF;
  813. }
  814. }
  815. }
  816. }
  817. /* 筛选弹窗样式 */
  818. .filter-popup {
  819. position: fixed;
  820. top: 0;
  821. left: 0;
  822. right: 0;
  823. bottom: 0;
  824. background: rgba(0, 0, 0, 0.5);
  825. z-index: 999;
  826. display: flex;
  827. align-items: flex-end;
  828. }
  829. .filter-content {
  830. width: 100%;
  831. background: #fff;
  832. border-radius: 24rpx 24rpx 0 0;
  833. padding: 24rpx;
  834. max-height: 80vh;
  835. overflow-y: auto;
  836. .filter-header {
  837. display: flex;
  838. align-items: center;
  839. justify-content: center;
  840. margin-bottom: 32rpx;
  841. position: relative;
  842. .filter-title {
  843. font-size: 32rpx;
  844. font-weight: bold;
  845. color: #333;
  846. }
  847. .filter-close-btn {
  848. width: 44rpx;
  849. height: 44rpx;
  850. position: absolute;
  851. right: 0;
  852. }
  853. }
  854. .filter-group {
  855. margin-bottom: 40rpx;
  856. .group-label {
  857. font-size: 28rpx;
  858. font-weight: bold;
  859. color: #333;
  860. margin-bottom: 20rpx;
  861. }
  862. .filter-tags {
  863. display: flex;
  864. flex-wrap: wrap;
  865. gap: 20rpx;
  866. .filter-tag {
  867. padding: 12rpx 24rpx;
  868. font-size: 28rpx;
  869. color: #333;
  870. background: #F7F8FA;
  871. border-radius: 70rpx;
  872. &.active {
  873. background: rgba(56, 139, 255, 0.15);
  874. color: #388BFF;
  875. }
  876. }
  877. }
  878. }
  879. .filter-actions {
  880. display: flex;
  881. gap: 24rpx;
  882. margin-top: 40rpx;
  883. padding-top: 24rpx;
  884. border-top: 1rpx solid #f0f0f0;
  885. .reset-btn,
  886. .confirm-btn {
  887. flex: 1;
  888. height: 88rpx;
  889. line-height: 88rpx;
  890. text-align: center;
  891. border-radius: 200rpx;
  892. font-size: 28rpx;
  893. }
  894. .reset-btn {
  895. background: #fff;
  896. color: #388BFF;
  897. border: 2rpx solid #388BFF;
  898. }
  899. .confirm-btn {
  900. background: #388BFF;
  901. color: #fff;
  902. }
  903. }
  904. }
  905. </style>