serviceOrder.vue 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  1. <template>
  2. <view class="container">
  3. <!-- 标签页 -->
  4. <view class="tabs">
  5. <view class="tab-item" :class="{ active: activeTab === 'pending' }" @click="switchTab('pending')">
  6. <text>待确认</text>
  7. <view class="tab-indicator" v-if="activeTab === 'pending'"></view>
  8. </view>
  9. <view class="tab-item" :class="{ active: activeTab === 'confirmed' }" @click="switchTab('confirmed')">
  10. <text>已确认</text>
  11. <view class="tab-indicator" v-if="activeTab === 'confirmed'"></view>
  12. </view>
  13. </view>
  14. <!-- 内容区域 -->
  15. <scroll-view class="content" scroll-y @scrolltolower="loadMore">
  16. <view class="order-list">
  17. <view class="month-group" v-for="(group, groupIndex) in groupedOrders" :key="group.month">
  18. <view class="month-header">
  19. <!-- <view class="month-icon">○</view> -->
  20. <text class="month-text">{{ formatMonth(group.month) }}</text>
  21. </view>
  22. <view class="order-items">
  23. <view class="order-item" v-for="(item, index) in group.items" :key="getItemKey(item, index)" @click.stop="(e) => goToConfirm(item, e)">
  24. <view class="order-content">
  25. <view class="order-title">服务确认单</view>
  26. <view class="order-time">{{ item.createTime }}</view>
  27. </view>
  28. <view class="order-action" v-if="activeTab === 'pending'">
  29. <view class="confirm-btn">去确认</view>
  30. </view>
  31. </view>
  32. </view>
  33. </view>
  34. </view>
  35. <!-- 空状态 -->
  36. <view class="empty-state y-bc" v-if="groupedOrders.length === 0">
  37. <image class="w300 h300" src="@/static/image/img_blank_nodata.png" mode=""></image>
  38. <text>暂无数据</text>
  39. </view>
  40. </scroll-view>
  41. </view>
  42. </template>
  43. <script>
  44. import { getServiceOrderList, getServiceOrderStatusList} from '@/api/serviceOrder'
  45. export default {
  46. data() {
  47. return {
  48. activeTab: 'pending', // pending: 待确认, confirmed: 已确认
  49. orderData: {}, // 存储接口返回的按月份分组的数据 { "2026-01": [...], "2026-02": [...] }
  50. page: 1,
  51. pageSize: 10,
  52. hasMore: true
  53. }
  54. },
  55. computed: {
  56. // 将接口返回的月份分组数据转换为页面需要的格式
  57. groupedOrders() {
  58. const groups = []
  59. // 根据当前 tab 确定 doctorConfirm 值
  60. const doctorConfirm = this.activeTab === 'pending' ? 0 : 1
  61. // 遍历接口返回的数据对象
  62. Object.keys(this.orderData).forEach(month => {
  63. const items = (this.orderData[month] || []).filter(item => {
  64. // 根据 doctorConfirm 过滤数据
  65. return item.doctorConfirm === doctorConfirm
  66. })
  67. if (items.length > 0) {
  68. groups.push({
  69. month: month,
  70. items: items
  71. })
  72. }
  73. })
  74. // 按月份倒序排列(最新的月份在前)
  75. return groups.sort((a, b) => {
  76. return b.month.localeCompare(a.month)
  77. })
  78. }
  79. },
  80. onLoad() {
  81. // this.loadStatusList()
  82. //this.loadData(true)
  83. },
  84. onShow() {
  85. this.loadData(true)
  86. },
  87. methods: {
  88. async loadData(refresh = false) {
  89. if (refresh) {
  90. this.page = 1
  91. this.hasMore = true
  92. this.orderData = {}
  93. }
  94. if (!this.hasMore) return
  95. try {
  96. uni.showLoading({ title: '加载中...' })
  97. // 根据当前 tab 设置 doctorConfirm 参数
  98. const doctorConfirm = this.activeTab === 'pending' ? 0 : 1
  99. const res = await getServiceOrderList({
  100. doctorConfirm: doctorConfirm, // 0: 待确认, 1: 已确认
  101. pageNum: this.page,
  102. pageSize: this.pageSize
  103. })
  104. uni.hideLoading()
  105. if (res.code === 200 && res.data) {
  106. // 接口返回的数据结构:{ "2026-01": [...], "2026-02": [...] }
  107. const monthData = res.data || {}
  108. if (refresh) {
  109. // 刷新时直接替换
  110. this.orderData = { ...monthData }
  111. } else {
  112. // 加载更多时合并数据
  113. Object.keys(monthData).forEach(month => {
  114. if (this.orderData[month]) {
  115. // 如果该月份已存在,合并数组
  116. this.orderData[month] = [...this.orderData[month], ...monthData[month]]
  117. } else {
  118. // 如果该月份不存在,直接添加
  119. this.orderData[month] = [...monthData[month]]
  120. }
  121. })
  122. }
  123. // 判断是否还有更多数据(根据返回的月份数量判断)
  124. const monthCount = Object.keys(monthData).length
  125. this.hasMore = monthCount > 0
  126. if (this.hasMore) {
  127. this.page++
  128. }
  129. } else {
  130. // 如果接口失败,使用空对象
  131. uni.showToast({
  132. icon: 'none',
  133. title: res.msg
  134. })
  135. if (refresh) {
  136. this.orderData = {}
  137. }
  138. }
  139. } catch (e) {
  140. uni.hideLoading()
  141. console.error('加载服务单列表失败', e)
  142. if (refresh) {
  143. this.orderData = {}
  144. }
  145. }
  146. },
  147. // async loadStatusList() {
  148. // // 加载服务单状态列表(如果需要)
  149. // try {
  150. // const res = await getServiceOrderStatusList()
  151. // if (res.code === 200 && res.data) {
  152. // // 可以用于状态筛选等功能
  153. // console.log('服务单状态列表', res.data)
  154. // }
  155. // } catch (e) {
  156. // console.error('加载服务单状态列表失败', e)
  157. // }
  158. // },
  159. getDefaultData() {
  160. uni.hideLoading()
  161. // 示例数据
  162. const list = []
  163. const currentDate = new Date()
  164. for (let i = 0; i < 3; i++) {
  165. const date1 = new Date(currentDate.getFullYear(), currentDate.getMonth() - 1, 23, 16, 0, 0)
  166. list.push({
  167. id: `order_${i + 1}`,
  168. title: '服务确认单',
  169. createTime: this.formatDate(date1),
  170. status: this.activeTab === 'pending' ? 0 : 1
  171. })
  172. }
  173. for (let i = 0; i < 3; i++) {
  174. const date2 = new Date(currentDate.getFullYear(), currentDate.getMonth() - 2, 23, 16, 0, 0)
  175. list.push({
  176. id: `order_${i + 4}`,
  177. title: '服务确认单',
  178. createTime: this.formatDate(date2),
  179. status: this.activeTab === 'pending' ? 0 : 1
  180. })
  181. }
  182. return list
  183. },
  184. formatDate(date) {
  185. const year = date.getFullYear()
  186. const month = String(date.getMonth() + 1).padStart(2, '0')
  187. const day = String(date.getDate()).padStart(2, '0')
  188. const hours = String(date.getHours()).padStart(2, '0')
  189. const minutes = String(date.getMinutes()).padStart(2, '0')
  190. const seconds = String(date.getSeconds()).padStart(2, '0')
  191. return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
  192. },
  193. switchTab(tab) {
  194. if (this.activeTab === tab) return
  195. this.activeTab = tab
  196. this.loadData(true)
  197. },
  198. loadMore() {
  199. this.loadData(false)
  200. },
  201. goBack() {
  202. uni.navigateBack()
  203. },
  204. goToDetail(item) {
  205. uni.navigateTo({
  206. url: `/pages_user/serviceOrderDetail?id=`+item.id+`&status=1`
  207. })
  208. },
  209. goToConfirm(item, e) {
  210. // 阻止事件冒泡
  211. if (e) {
  212. e.stopPropagation()
  213. }
  214. if (!item) {
  215. console.error('goToConfirm: item is undefined')
  216. return
  217. }
  218. console.log('item', item)
  219. const orderId = item.id
  220. if (!orderId) {
  221. uni.showToast({
  222. icon: 'none',
  223. title: '订单ID不存在'
  224. })
  225. return
  226. }
  227. uni.navigateTo({
  228. url: `/pages_user/serviceOrderDetail?id=${orderId}&status=0`
  229. })
  230. },
  231. // 格式化月份显示(可选:将 2026-01 格式化为 2026年01月)
  232. formatMonth(month) {
  233. if (!month) return '未知'
  234. const parts = month.split('-')
  235. if (parts.length === 2) {
  236. return `${parts[0]}年${parts[1]}月`
  237. }
  238. return month
  239. },
  240. // 获取列表项的 key(用于 v-for)
  241. getItemKey(item, index) {
  242. return item.id || item.serviceTicketId || item.ticketId || `item_${index}`
  243. }
  244. }
  245. }
  246. </script>
  247. <style lang="scss" scoped>
  248. .container {
  249. min-height: 100vh;
  250. display: flex;
  251. flex-direction: column;
  252. }
  253. .navbar {
  254. display: flex;
  255. align-items: center;
  256. justify-content: space-between;
  257. padding: 20rpx 24rpx;
  258. background: #fff;
  259. border-bottom: 1rpx solid #f0f0f0;
  260. .nav-left {
  261. width: 60rpx;
  262. .back-icon {
  263. font-size: 36rpx;
  264. color: #333;
  265. font-weight: bold;
  266. }
  267. }
  268. .nav-title {
  269. flex: 1;
  270. text-align: center;
  271. font-size: 36rpx;
  272. font-weight: bold;
  273. color: #333;
  274. }
  275. .nav-right {
  276. display: flex;
  277. align-items: center;
  278. gap: 24rpx;
  279. width: 60rpx;
  280. justify-content: flex-end;
  281. .more-icon {
  282. font-size: 32rpx;
  283. color: #333;
  284. }
  285. .eye-icon {
  286. font-size: 32rpx;
  287. color: #333;
  288. }
  289. }
  290. }
  291. .tabs {
  292. display: flex;
  293. background: #fff;
  294. //border-bottom: 1rpx solid #f0f0f0;
  295. .tab-item {
  296. flex: 1;
  297. display: flex;
  298. flex-direction: column;
  299. align-items: center;
  300. justify-content: center;
  301. padding: 24rpx 0;
  302. position: relative;
  303. text {
  304. font-size: 30rpx;
  305. color: #999;
  306. }
  307. &.active {
  308. text {
  309. color: #333;
  310. font-weight: 500;
  311. }
  312. .tab-indicator {
  313. position: absolute;
  314. bottom: 0;
  315. left: 50%;
  316. transform: translateX(-50%);
  317. width: 80rpx;
  318. height: 6rpx;
  319. background: #388BFF;
  320. border-radius: 3rpx 3rpx 3rpx 3rpx;
  321. }
  322. }
  323. }
  324. }
  325. .content {
  326. flex: 1;
  327. }
  328. .order-list {
  329. padding: 24rpx;
  330. }
  331. .month-group {
  332. margin-bottom: 32rpx;
  333. &:last-child {
  334. margin-bottom: 0;
  335. }
  336. .month-header {
  337. display: flex;
  338. align-items: center;
  339. margin-bottom: 16rpx;
  340. .month-icon {
  341. width: 16rpx;
  342. height: 16rpx;
  343. border-radius: 50%;
  344. background: #ccc;
  345. margin-right: 16rpx;
  346. }
  347. .month-text {
  348. font-size: 28rpx;
  349. color: #666;
  350. }
  351. }
  352. .order-items {
  353. .order-item {
  354. background: #fff;
  355. border-radius: 16rpx;
  356. padding: 32rpx 24rpx;
  357. margin-bottom: 20rpx;
  358. display: flex;
  359. align-items: center;
  360. justify-content: space-between;
  361. &:last-child {
  362. margin-bottom: 0;
  363. }
  364. .order-content {
  365. flex: 1;
  366. .order-title {
  367. font-family: PingFang SC, PingFang SC;
  368. font-weight: 500;
  369. font-size: 28rpx;
  370. color: #333333;
  371. margin-bottom: 12rpx;
  372. }
  373. .order-time {
  374. font-family: PingFang SC, PingFang SC;
  375. font-weight: 400;
  376. font-size: 24rpx;
  377. color: #666666;
  378. }
  379. }
  380. .order-action {
  381. .confirm-btn {
  382. padding: 12rpx 32rpx;
  383. background: #388BFF;
  384. border-radius: 44rpx;
  385. font-family: PingFang SC, PingFang SC;
  386. font-weight: 500;
  387. font-size: 24rpx;
  388. color: #FFFFFF;
  389. }
  390. }
  391. }
  392. }
  393. }
  394. .empty-state {
  395. padding: 120rpx 24rpx;
  396. text-align: center;
  397. font-size: 28rpx;
  398. color: #999;
  399. }
  400. </style>