From 25df88d0ab9d7444ed1d162b9efa1259ac2f6f66 Mon Sep 17 00:00:00 2001
From: longz <longzhaoz@qq.com>
Date: 星期一, 27 四月 2026 10:13:53 +0800
Subject: [PATCH] 小程序对接finish

---
 yuyueMiniProgram/src/pages/record/index.vue |  260 +++++++++++++++++++++++++++++-----------------------
 1 files changed, 145 insertions(+), 115 deletions(-)

diff --git a/yuyueMiniProgram/src/pages/record/index.vue b/yuyueMiniProgram/src/pages/record/index.vue
index ad15f1a..27d8a86 100644
--- a/yuyueMiniProgram/src/pages/record/index.vue
+++ b/yuyueMiniProgram/src/pages/record/index.vue
@@ -1,5 +1,6 @@
 <script setup lang="ts">
-import { ref } from 'vue'
+import { fetchMyAppointmentOrders } from '@/api/appointment'
+import type { MiniAppointmentListItem } from '@/types/appointment'
 
 definePage({
   name: 'record',
@@ -9,6 +10,11 @@
   },
 })
 
+interface RecordTabItem {
+  title: string
+  status?: string
+}
+
 const router = useRouter()
 const sysInfo = uni.getSystemInfoSync()
 const statusBarHeight = sysInfo.statusBarHeight || 0
@@ -16,193 +22,218 @@
 
 const searchKeyword = ref('')
 const activeTab = ref(0)
+const loading = ref(false)
+const records = ref<MiniAppointmentListItem[]>([])
+const page = ref(1)
+const size = 10
+const total = ref(0)
+const finished = ref(false)
 
-const tabs = [
-  { title: '全部', badge: 0 },
-  { title: '待拜访', badge: 2 },
-  { title: '生效中', badge: 2 },
-  { title: '已失效', badge: 0 },
+const tabs: RecordTabItem[] = [
+  { title: '全部' },
+  { title: '待拜访', status: 'PENDING_VISIT' },
+  { title: '生效中', status: 'EFFECTIVE' },
+  { title: '已失效', status: 'INVALID' },
 ]
-
-const records = ref([
-  {
-    id: 1,
-    status: 'pending',
-    statusText: '待拜访',
-    interviewee: '员工A(管理部)',
-    area: 'xx区域/xx区域',
-    time: '2023-10-12 00:00 -2023-10-13 23:59',
-    reason: '业务洽谈',
-  },
-  {
-    id: 2,
-    status: 'active',
-    statusText: '生效中',
-    interviewee: '员工A(管理部)',
-    area: 'xx区域/xx区域',
-    time: '2023-10-12 00:00 -2023-10-13 23:59',
-    reason: '业务洽谈',
-  },
-  {
-    id: 3,
-    status: 'expired',
-    statusText: '已失效',
-    interviewee: '员工A(管理部)',
-    area: 'xx区域/xx区域',
-    time: '2023-10-12 00:00 -2023-10-13 23:59',
-    reason: '业务洽谈',
-  },
-])
 
 function handleBack() {
   router.back()
 }
 
-function handleTabClick(index: number) {
-  activeTab.value = index
+function statusTheme(item: MiniAppointmentListItem) {
+  switch (item.displayStatus) {
+    case 'EFFECTIVE':
+      return {
+        bar: 'bg-[#52C41A]',
+        tag: 'bg-[#E8F8E3] text-[#52C41A]',
+        text: '生效中',
+      }
+    case 'INVALID':
+      return {
+        bar: 'bg-[#D8D8D8]',
+        tag: 'bg-[#F5F5F5] text-[#999999]',
+        text: item.status === 'CANCELLED' ? '已取消' : '已失效',
+      }
+    default:
+      return {
+        bar: 'bg-[#0068FF]',
+        tag: 'bg-[#E6F0FF] text-[#0068FF]',
+        text: '待拜访',
+      }
+  }
 }
 
-function handleRecordClick(status: string) {
-  // 映射 record 状态到 review 页面的 appointmentStatus
-  let targetStatus = ''
-  switch (status) {
-    case 'pending':
-      targetStatus = 'reviewing'
-      break
-    case 'active':
-      targetStatus = 'effective'
-      break
-    case 'expired':
-      targetStatus = 'invalid'
-      break
-    default:
-      targetStatus = 'reviewing'
+async function loadRecords(reset = false) {
+  if (loading.value) {
+    return
   }
-  
+  loading.value = true
+  try {
+    const nextPage = reset ? 1 : page.value
+    const result = await fetchMyAppointmentOrders({
+      page: nextPage,
+      size,
+      status: tabs[activeTab.value]?.status,
+      keyword: searchKeyword.value.trim() || undefined,
+    })
+    records.value = reset ? result.records : [...records.value, ...result.records]
+    page.value = nextPage + 1
+    total.value = result.total
+    finished.value = records.value.length >= result.total || result.records.length < size
+  }
+  catch (error) {
+    uni.showToast({
+      title: error instanceof Error ? error.message : '加载预约记录失败',
+      icon: 'none',
+    })
+  }
+  finally {
+    loading.value = false
+  }
+}
+
+function reloadRecords() {
+  page.value = 1
+  finished.value = false
+  void loadRecords(true)
+}
+
+function handleTabClick(index: number) {
+  activeTab.value = index
+  reloadRecords()
+}
+
+function handleSearch() {
+  reloadRecords()
+}
+
+function handleRecordClick(item: MiniAppointmentListItem) {
   uni.navigateTo({
-    url: `/pages/review/index?status=${targetStatus}`
+    url: `/pages/review/index?orderId=${item.id}&orderUuid=${item.uuid}`,
   })
 }
+
+onShow(() => {
+  reloadRecords()
+})
+
+onReachBottom(() => {
+  if (!finished.value) {
+    void loadRecords()
+  }
+})
 </script>
 
 <template>
   <view class="min-h-screen bg-[#F6FBFF] w-full relative box-border flex flex-col">
-    <!-- 顶部渐变背景(只保留基础底色,具体遮挡由上面 fixed 区域的背景完成) -->
-    <view class="fixed left-0 top-0 z-0 h-[500rpx] w-full"
-      style="background: linear-gradient(180deg, #5398FF 0%, #A9D0FF 50%, #F6FBFF 100%);" />
+    <view class="fixed left-0 top-0 z-0 h-[500rpx] w-full" style="background: linear-gradient(180deg, #5398FF 0%, #A9D0FF 50%, #F6FBFF 100%);" />
 
-    <!-- 固定的顶部区域 (导航栏 + 搜索标签栏) -->
     <view class="fixed top-0 left-0 w-full z-50 flex flex-col">
-      <!-- 固定的背景图(限制在搜索框区域内) -->
       <view class="absolute left-0 top-0 z-[-1] w-full overflow-hidden" :style="{ height: (statusBarHeight + navBarHeight + uni.upx2px(36) + uni.upx2px(288) + uni.upx2px(24)) + 'px' }">
-        <view class="w-full h-[500rpx] absolute top-0 left-0" style="background: linear-gradient(180deg, #5398FF 0%, #A9D0FF 50%, #F6FBFF 100%);"></view>
+        <view class="w-full h-[500rpx] absolute top-0 left-0" style="background: linear-gradient(180deg, #5398FF 0%, #A9D0FF 50%, #F6FBFF 100%);" />
       </view>
-      <!-- 自定义导航栏 -->
+
       <view class="relative w-full shrink-0" :style="{ height: (statusBarHeight + navBarHeight) + 'px' }">
-        <view class="flex items-center justify-between w-full absolute left-0"
-          :style="{ top: statusBarHeight + 'px', height: navBarHeight + 'px' }">
+        <view class="flex items-center justify-between w-full absolute left-0" :style="{ top: statusBarHeight + 'px', height: navBarHeight + 'px' }">
           <view class="flex h-full w-[88rpx] items-center justify-center relative z-10" @click="handleBack">
             <wd-icon name="arrow-left" size="40rpx" color="#ffffff" />
           </view>
           <view class="absolute left-0 w-full text-center text-[34rpx] font-medium text-white z-0">
             预约记录
           </view>
-          <view class="w-[88rpx]"></view>
+          <view class="w-[88rpx]" />
         </view>
       </view>
 
-      <!-- 搜索和标签栏容器 -->
       <view class="px-[24rpx] pt-[36rpx] pb-[24rpx] w-full box-border">
         <view class="bg-white rounded-[16rpx] search-card shadow-[0_4rpx_8rpx_rgba(6,47,108,0.1608)] w-full shrink-0">
-          <!-- 搜索栏 -->
           <view class="flex justify-center pt-[32rpx] pb-[16rpx]">
             <view class="w-[622rpx] h-[88rpx] rounded-[44rpx] bg-[#FFFFFF] border-[4rpx] border-solid border-[#D4E6FF] box-border flex items-center px-[32rpx]">
-              <input v-model="searchKeyword" type="text" placeholder="搜索" placeholder-class="text-[#B3B3B3] text-[28rpx]"
-                class="flex-1 text-[28rpx] text-[#333333] h-full" />
-              <image src="/static/record/search@2x.png" class="w-[48rpx] h-[48rpx] ml-[16rpx] shrink-0" />
+              <input
+                v-model="searchKeyword"
+                type="text"
+                placeholder="搜索"
+                placeholder-class="text-[#B3B3B3] text-[28rpx]"
+                class="flex-1 text-[28rpx] text-[#333333] h-full"
+                confirm-type="search"
+                @confirm="handleSearch"
+              />
+              <image src="/static/record/search@2x.png" class="w-[48rpx] h-[48rpx] ml-[16rpx] shrink-0" @click="handleSearch" />
             </view>
           </view>
 
-          <!-- 标签栏 -->
           <view class="flex h-[88rpx] px-[16rpx]">
-            <view v-for="(tab, index) in tabs" :key="index"
-              class="flex-1 flex flex-col items-center justify-center relative" @click="handleTabClick(index)">
+            <view
+              v-for="(tab, index) in tabs"
+              :key="tab.title"
+              class="flex-1 flex flex-col items-center justify-center relative"
+              @click="handleTabClick(index)"
+            >
               <view class="flex items-center relative">
-                <text class="text-[28rpx] transition-colors duration-200"
-                  :class="activeTab === index ? 'text-[#0068FF] font-medium' : 'text-[#999999]'">
+                <text class="text-[28rpx] transition-colors duration-200" :class="activeTab === index ? 'text-[#0068FF] font-medium' : 'text-[#999999]'">
                   {{ tab.title }}
                 </text>
-                <view v-if="tab.badge > 0"
-                  class="absolute top-[-10rpx] right-[-28rpx] bg-[#FF4D4F] text-white text-[20rpx] px-[8rpx] h-[28rpx] rounded-full flex items-center justify-center min-w-[28rpx] box-border">
-                  {{ tab.badge }}
-                </view>
               </view>
-              <view class="absolute bottom-[16rpx] w-[40rpx] h-[4rpx] rounded-[2rpx] transition-all duration-200"
-                :class="activeTab === index ? 'bg-[#0068FF]' : 'bg-transparent'"></view>
+              <view class="absolute bottom-[16rpx] w-[40rpx] h-[4rpx] rounded-[2rpx] transition-all duration-200" :class="activeTab === index ? 'bg-[#0068FF]' : 'bg-transparent'" />
             </view>
           </view>
         </view>
       </view>
     </view>
 
-    <!-- 占位元素,用于撑开固定的顶部高度 -->
-    <view :style="{ height: (statusBarHeight + navBarHeight + uni.upx2px(36) + uni.upx2px(288) + uni.upx2px(24)) + 'px' }" class="shrink-0"></view>
+    <view :style="{ height: (statusBarHeight + navBarHeight + uni.upx2px(36) + uni.upx2px(288) + uni.upx2px(24)) + 'px' }" class="shrink-0" />
 
-    <!-- 内容区 -->
     <view class="relative z-10 px-[24rpx] flex-1 flex flex-col min-h-0 box-border">
-      <!-- 记录列表 -->
       <scroll-view scroll-y class="flex-1 min-h-0" :show-scrollbar="false">
         <view class="pb-[24rpx]">
-          <view v-for="item in records" :key="item.id"
+          <view
+            v-for="item in records"
+            :key="item.id"
             class="bg-white rounded-[16rpx] shadow-[0_4rpx_8rpx_rgba(6,47,108,0.1608)] mb-[24rpx] overflow-hidden"
-            @click="handleRecordClick(item.status)">
-            <!-- 顶部彩条 -->
-            <view class="w-full h-[12rpx]" :class="{
-              'bg-[#0068FF]': item.status === 'pending',
-              'bg-[#52C41A]': item.status === 'active',
-              'bg-[#D8D8D8]': item.status === 'expired'
-            }"></view>
+            @click="handleRecordClick(item)"
+          >
+            <view class="w-full h-[12rpx]" :class="statusTheme(item).bar" />
 
             <view class="pt-[28rpx] pb-[16rpx]">
-              <!-- 状态和箭头 -->
               <view class="flex justify-between items-center pb-[24rpx] px-[40rpx] status-border">
-                <view class="px-[16rpx] py-[4rpx] rounded-[4rpx]" :class="{
-                  'bg-[#E6F0FF] text-[#0068FF]': item.status === 'pending',
-                  'bg-[#E8F8E3] text-[#52C41A]': item.status === 'active',
-                  'bg-[#F5F5F5] text-[#999999]': item.status === 'expired'
-                }">
-                  <text class="text-[24rpx] font-medium">{{ item.statusText }}</text>
+                <view class="px-[16rpx] py-[4rpx] rounded-[4rpx]" :class="statusTheme(item).tag">
+                  <text class="text-[24rpx] font-medium">{{ statusTheme(item).text }}</text>
                 </view>
                 <wd-icon name="arrow-right" size="32rpx" color="#999999" />
               </view>
 
-              <!-- 详情字段 -->
               <view class="flex flex-col px-[40rpx]">
                 <view class="flex items-center h-[96rpx] relative">
-                  <text class="text-[#666666] text-[28rpx] w-[140rpx] shrink-0">被<text
-                      class="mx-[6rpx]">访</text>人:</text>
-                  <text class="text-[#333333] text-[28rpx] flex-1 truncate">{{ item.interviewee }}</text>
-                  <!-- 底部边框 (跳过 label 区域) -->
-                  <view class="absolute bottom-0 left-[140rpx] right-0 h-[2rpx] bg-[#F5F5F5]"></view>
+                  <text class="text-[#666666] text-[28rpx] w-[140rpx] shrink-0">被访人:</text>
+                  <text class="text-[#333333] text-[28rpx] flex-1 truncate">{{ item.visitedUserDisplay }}</text>
+                  <view class="absolute bottom-0 left-[140rpx] right-0 h-[2rpx] bg-[#F5F5F5]" />
                 </view>
                 <view class="flex items-center h-[96rpx] relative">
                   <text class="text-[#666666] text-[28rpx] w-[140rpx] shrink-0">访问区域:</text>
-                  <text class="text-[#333333] text-[28rpx] flex-1 truncate">{{ item.area }}</text>
-                  <!-- 底部边框 (跳过 label 区域) -->
-                  <view class="absolute bottom-0 left-[140rpx] right-0 h-[2rpx] bg-[#F5F5F5]"></view>
+                  <text class="text-[#333333] text-[28rpx] flex-1 truncate">{{ item.accessAreaText || '-' }}</text>
+                  <view class="absolute bottom-0 left-[140rpx] right-0 h-[2rpx] bg-[#F5F5F5]" />
                 </view>
                 <view class="flex items-center h-[96rpx] relative">
                   <text class="text-[#666666] text-[28rpx] w-[140rpx] shrink-0">拜访时间:</text>
-                  <text class="text-[#333333] text-[28rpx] flex-1 truncate">{{ item.time }}</text>
-                  <!-- 底部边框 (跳过 label 区域) -->
-                  <view class="absolute bottom-0 left-[140rpx] right-0 h-[2rpx] bg-[#F5F5F5]"></view>
+                  <text class="text-[#333333] text-[28rpx] flex-1 truncate">{{ item.visitTimeText }}</text>
+                  <view class="absolute bottom-0 left-[140rpx] right-0 h-[2rpx] bg-[#F5F5F5]" />
                 </view>
                 <view class="flex items-center h-[96rpx]">
                   <text class="text-[#666666] text-[28rpx] w-[140rpx] shrink-0">拜访事由:</text>
-                  <text class="text-[#333333] text-[28rpx] flex-1 truncate">{{ item.reason }}</text>
+                  <text class="text-[#333333] text-[28rpx] flex-1 truncate">{{ item.visitReasonText }}</text>
                 </view>
               </view>
             </view>
+          </view>
+
+          <view v-if="loading" class="text-center text-[#999999] text-[24rpx] py-[24rpx]">
+            加载中...
+          </view>
+          <view v-else-if="records.length === 0" class="text-center text-[#999999] text-[24rpx] py-[80rpx]">
+            暂无预约记录
+          </view>
+          <view v-else-if="finished" class="text-center text-[#999999] text-[24rpx] py-[24rpx]">
+            共 {{ total }} 条记录
           </view>
         </view>
       </scroll-view>
@@ -224,11 +255,10 @@
   overflow: hidden;
 }
 
-.status-border{
+.status-border {
   border-bottom: 2rpx solid #F5F5F5;
 }
 
-/* 隐藏滚动条 */
 ::-webkit-scrollbar {
   display: none;
   width: 0 !important;

--
Gitblit v1.7.1