index.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. <template>
  2. <view class="container">
  3. <!-- 吸顶固定层:状态tab + 搜索 + 筛选 -->
  4. <view class="sticky-header">
  5. <!-- 顶部 Tab (待接送/服务中...) -->
  6. <view class="status-tabs">
  7. <view
  8. class="tab-item"
  9. v-for="(tab, index) in tabs"
  10. :key="index"
  11. :class="{ active: currentTab === index }"
  12. @click="currentTab = index"
  13. >
  14. <text>{{ tab }}</text>
  15. <view class="indicator" v-if="currentTab === index"></view>
  16. </view>
  17. </view>
  18. <!-- 搜索栏 -->
  19. <view class="search-bar">
  20. <view class="search-input-box">
  21. <input class="search-input" placeholder="搜索地址/电话/姓名" placeholder-class="ph-style" />
  22. </view>
  23. </view>
  24. <!-- 筛选栏 (支持自定义下拉) -->
  25. <view class="filter-wrapper">
  26. <view class="filter-bar">
  27. <!-- 订单类型下拉视图 -->
  28. <view class="filter-item" :class="{ 'active': activeDropdown === 1 }" @click="toggleDropdown(1)">
  29. <text :class="{ 'active-text': activeDropdown === 1 || currentTypeFilterIdx > 0 }">
  30. {{ currentTypeFilterIdx > 0 ? typeFilterOptions[currentTypeFilterIdx] : '全部类型' }}
  31. </text>
  32. <view class="triangle" :class="activeDropdown === 1 ? 'up' : 'down'"></view>
  33. </view>
  34. <!-- 服务时间下拉视图 -->
  35. <view class="filter-item" :class="{ 'active': activeDropdown === 2 }" @click="toggleDropdown(2)">
  36. <text :class="{ 'active-text': activeDropdown === 2 || hasTimeFilter }">服务时间</text>
  37. <view class="triangle" :class="activeDropdown === 2 ? 'up' : 'down'"></view>
  38. </view>
  39. </view>
  40. <!-- 下拉内容面板与遮罩 -->
  41. <view class="dropdown-mask" v-if="activeDropdown !== 0" @click="closeDropdown"></view>
  42. <view class="dropdown-panel" v-if="activeDropdown === 1">
  43. <view
  44. class="type-option"
  45. v-for="(item, index) in typeFilterOptions"
  46. :key="index"
  47. :class="{ 'selected': currentTypeFilterIdx === index }"
  48. @click="selectType(index)"
  49. >
  50. <text>{{ item }}</text>
  51. </view>
  52. </view>
  53. <view class="dropdown-panel calendar-panel" v-if="activeDropdown === 2">
  54. <view class="custom-calendar-container">
  55. <!-- 头部 -->
  56. <view class="cal-header">
  57. <uni-icons type="left" size="18" color="#666" @click="prevMonth"></uni-icons>
  58. <text class="cal-title">{{ currentMonth }}</text>
  59. <uni-icons type="right" size="18" color="#666" @click="nextMonth"></uni-icons>
  60. </view>
  61. <!-- 星期条 -->
  62. <view class="cal-weekdays">
  63. <text v-for="(wk, idx) in weekDays" :key="idx" class="wk-item">{{ wk }}</text>
  64. </view>
  65. <!-- 日期网格 -->
  66. <view class="cal-body">
  67. <!-- Feb 2026 starts on Sunday (index 0), so no empty padding needed for first row -->
  68. <view
  69. v-for="(day, idx) in calendarDays"
  70. :key="idx"
  71. class="cal-day-box"
  72. :class="getDateClass(day)"
  73. @click="selectDateItem(day)"
  74. >
  75. <view class="cal-day-text">{{ day }}</view>
  76. </view>
  77. </view>
  78. </view>
  79. <view class="calendar-actions">
  80. <button class="cal-btn reset" @click="resetTimeFilter">重置</button>
  81. <button class="cal-btn confirm" @click="confirmTimeFilter">确定</button>
  82. </view>
  83. </view>
  84. </view><!-- end filter-wrapper -->
  85. </view><!-- end sticky-header -->
  86. <!-- 订单列表 -->
  87. <scroll-view scroll-y class="order-list">
  88. <view class="order-card" v-for="(item, index) in orderList" :key="index" @click="goToDetail(item)">
  89. <view class="card-header">
  90. <view class="type-badge">
  91. <image class="type-icon" :src="item.typeIcon"></image>
  92. <text class="type-text">{{ item.typeText }}</text>
  93. </view>
  94. <text class="status-badge" :class="getStatusClass(item)">{{ getDisplayStatus(item) }}</text>
  95. </view>
  96. <view class="card-body">
  97. <view class="time-row">
  98. <view class="time-col">
  99. <text class="label">{{ item.timeLabel }}:</text>
  100. <text class="value">{{ item.time }}</text>
  101. </view>
  102. <text class="price">¥{{ item.price }}</text>
  103. </view>
  104. <!-- 宠物信息 -->
  105. <view class="pet-card">
  106. <image class="pet-avatar" :src="item.petAvatar" mode="aspectFill"></image>
  107. <view class="pet-info">
  108. <text class="pet-name">{{ item.petName }}</text>
  109. <text class="pet-breed">品种: {{ item.petBreed }}</text>
  110. </view>
  111. <view class="pet-profile-btn" @click.stop="showPetProfile(item)">宠物档案</view>
  112. </view>
  113. <!-- 路线信息 (完全复用 Home 样式) -->
  114. <view class="route-info">
  115. <template v-if="item.type === 1">
  116. <view class="route-item" @click.stop="openNavigation(item, 'start')">
  117. <view class="icon-circle start">取</view>
  118. <view class="route-line-vertical"></view>
  119. <view class="address-box">
  120. <text class="addr-title">{{ item.startLocation }}</text>
  121. <text class="addr-desc">{{ item.startAddress }}</text>
  122. </view>
  123. <view class="distance-tag">
  124. <text class="distance-text">{{ item.startDistance }}</text>
  125. <view class="nav-icon-circle">
  126. <image class="nav-arrow" src="/static/icons/nav_arrow.svg"></image>
  127. </view>
  128. </view>
  129. </view>
  130. <view class="route-item" @click.stop="openNavigation(item, 'end')">
  131. <view class="icon-circle end">送</view>
  132. <view class="address-box">
  133. <text class="addr-title">{{ item.endLocation }}</text>
  134. <text class="addr-desc">{{ item.endAddress }}</text>
  135. </view>
  136. <view class="distance-tag">
  137. <text class="distance-text">{{ item.endDistance }}</text>
  138. <view class="nav-icon-circle">
  139. <image class="nav-arrow" src="/static/icons/nav_arrow.svg"></image>
  140. </view>
  141. </view>
  142. </view>
  143. </template>
  144. <template v-else>
  145. <view class="route-item" @click.stop="openNavigation(item, 'end')">
  146. <view class="icon-circle service">服</view>
  147. <view class="address-box">
  148. <text class="addr-title">{{ item.endLocation }}</text>
  149. <text class="addr-desc">{{ item.endAddress }}</text>
  150. </view>
  151. <view class="distance-tag">
  152. <text class="distance-text">{{ item.endDistance }}</text>
  153. <view class="nav-icon-circle">
  154. <image class="nav-arrow" src="/static/icons/nav_arrow.svg"></image>
  155. </view>
  156. </view>
  157. </view>
  158. <view class="service-content" v-if="item.serviceContent">
  159. <text class="content-label">服务内容:</text>
  160. <text>{{ item.serviceContent }}</text>
  161. </view>
  162. </template>
  163. </view>
  164. <!-- 备注 -->
  165. <view class="remark-box" v-if="item.remark">
  166. <text>备注:{{ item.remark }}</text>
  167. </view>
  168. </view><!-- End of card-body -->
  169. <!-- 按钮组 -->
  170. <view class="action-btns" v-if="['接单', '到达', '出发', '开始', '送达', '结束'].includes(item.statusText)">
  171. <view class="action-left">
  172. <button class="btn normal" @click.stop="toggleCallMenu(item)">一键拨号</button>
  173. <view class="call-popover" v-if="activeCallItem === item" @click.stop>
  174. <view class="call-pop-item" @click="doCall('merchant')">联系商家</view>
  175. <view class="call-pop-item" @click="doCall('customer')">联系客户</view>
  176. </view>
  177. </view>
  178. <view class="action-right">
  179. <button class="btn normal" @click.stop="reportAbnormal(item)">异常上报</button>
  180. <button class="btn primary" @click.stop="mainAction(item)">{{ getMainActionText(item) }}</button>
  181. </view>
  182. </view>
  183. </view>
  184. <!-- 已加载完提示文字 -->
  185. <view class="loading-text">已加载完</view>
  186. <view style="height: 160rpx;"></view>
  187. </scroll-view>
  188. <view class="call-mask" v-if="activeCallItem" @click="closeCallMenu"></view>
  189. </view>
  190. <!-- 宠物档案弹窗 (复用Home) -->
  191. <view class="pet-modal-mask" v-if="showPetModal" @click="closePetProfile">
  192. <view class="pet-modal-content" @click.stop>
  193. <view class="pet-modal-header">
  194. <text class="pet-modal-title">宠物档案</text>
  195. <view class="pm-header-actions">
  196. <view class="pm-remark-btn" @click="openRemarkInput">备注</view>
  197. <view class="close-icon-btn" @click="closePetProfile">×</view>
  198. </view>
  199. </view>
  200. <scroll-view scroll-y class="pet-modal-scroll">
  201. <!-- Basic Info -->
  202. <view class="pet-base-info">
  203. <image class="pm-avatar" :src="currentPetInfo.petAvatar" mode="aspectFill"></image>
  204. <view class="pm-info-text">
  205. <view class="pm-name-row">
  206. <text class="pm-name">{{ currentPetInfo.petName }}</text>
  207. <view class="pm-gender" v-if="currentPetInfo.petGender === 'M'">
  208. <text class="gender-icon">♂</text>
  209. <text>公</text>
  210. </view>
  211. <view class="pm-gender female" v-else-if="currentPetInfo.petGender === 'F'">
  212. <text class="gender-icon">♀</text>
  213. <text>母</text>
  214. </view>
  215. </view>
  216. <text class="pm-breed">品种:{{ currentPetInfo.petBreed }}</text>
  217. </view>
  218. </view>
  219. <!-- Details Grid -->
  220. <view class="pm-detail-grid">
  221. <view class="pm-grid-item half">
  222. <text class="pm-label">年龄</text>
  223. <text class="pm-val">{{ currentPetInfo.petAge || '未知' }}</text>
  224. </view>
  225. <view class="pm-grid-item half">
  226. <text class="pm-label">体重</text>
  227. <text class="pm-val">{{ currentPetInfo.petWeight || '未知' }}</text>
  228. </view>
  229. <view class="pm-grid-item full">
  230. <text class="pm-label">性格</text>
  231. <text class="pm-val">{{ currentPetInfo.petPersonality || '无' }}</text>
  232. </view>
  233. <view class="pm-grid-item full">
  234. <text class="pm-label">爱好</text>
  235. <text class="pm-val">{{ currentPetInfo.petHobby || '无' }}</text>
  236. </view>
  237. <view class="pm-grid-item full">
  238. <text class="pm-label">备注</text>
  239. <text class="pm-val">{{ currentPetInfo.petRemark || '无特殊过敏史' }}</text>
  240. </view>
  241. </view>
  242. <!-- Tags -->
  243. <view class="pm-tags" v-if="currentPetInfo.petTags && currentPetInfo.petTags.length > 0">
  244. <view class="pm-tag" v-for="(tag, index) in currentPetInfo.petTags" :key="index">{{ tag }}</view>
  245. </view>
  246. <!-- Log Section -->
  247. <view class="pm-section-title">
  248. <view class="orange-bar"></view>
  249. <text>备注日志</text>
  250. </view>
  251. <view class="pm-log-list">
  252. <view class="pm-log-item" v-for="(log, lIndex) in currentPetInfo.petLogs" :key="lIndex">
  253. <text class="pm-log-date">{{ log.date }}</text>
  254. <text class="pm-log-text">{{ log.content }}</text>
  255. <text class="pm-log-recorder">{{ log.recorder === '系统记录' ? '' : '记录人: '}}{{ log.recorder }}</text>
  256. </view>
  257. </view>
  258. <view style="height: 30rpx;"></view>
  259. </scroll-view>
  260. </view>
  261. </view>
  262. <!-- 备注输入弹窗 -->
  263. <view class="remark-mask" v-if="showRemarkInput" @click="closeRemarkInput">
  264. <view class="remark-sheet" @click.stop>
  265. <view class="remark-sheet-header">
  266. <text class="remark-sheet-title">添加备注</text>
  267. <view class="close-icon-btn" @click="closeRemarkInput">×</view>
  268. </view>
  269. <textarea
  270. class="remark-textarea"
  271. v-model="remarkText"
  272. placeholder="请输入备注内容..."
  273. maxlength="500"
  274. auto-height
  275. />
  276. <view class="remark-submit-btn" @click="submitRemark">提交备注</view>
  277. </view>
  278. </view>
  279. <!-- 选择地图导航弹窗 (复用Home) -->
  280. <view class="nav-modal-mask" v-if="showNavModal" @click="closeNavModal">
  281. <view class="nav-action-sheet" @click.stop>
  282. <view class="nav-sheet-title">选择地图进行导航</view>
  283. <view class="nav-sheet-item" @click="chooseMap('高德')">高德地图</view>
  284. <view class="nav-sheet-item" @click="chooseMap('腾讯')">腾讯地图</view>
  285. <view class="nav-sheet-item" @click="chooseMap('百度')">百度地图</view>
  286. <view class="nav-sheet-gap"></view>
  287. <view class="nav-sheet-item cancel" @click="closeNavModal">取消</view>
  288. </view>
  289. </view>
  290. </template>
  291. <script>
  292. import logic from './logic.js';
  293. export default {
  294. ...logic
  295. }
  296. </script>
  297. <style>
  298. @import './style.css';
  299. </style>