order.uvue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  1. <template>
  2. <view class="page-container">
  3. <!-- 1. 顶部标签栏 (Scroll View) -->
  4. <scroll-view scroll-x class="tab-scroll" show-scrollbar="false" :enable-flex="true">
  5. <view class="tab-wrapper">
  6. <view v-for="(tab, index) in tabs" :key="index"
  7. :class="['tab-item', currentTab === index ? 'active' : '']" @click="currentTab = index">
  8. <text>
  9. {{ tab }}
  10. </text>
  11. <!-- 激活下的黄色短线 -->
  12. <view v-if="currentTab === index" class="tab-indicator">
  13. </view>
  14. </view>
  15. </view>
  16. </scroll-view>
  17. <!-- 2. 订单列表 -->
  18. <view class="order-list">
  19. <view v-for="(order, idx) in orders" :key="order['id']" class="order-card">
  20. <!-- Card Header: 时间 & 状态 -->
  21. <view class="card-header">
  22. <text class="time-text">
  23. 预约时间:{{ order['time'] }}
  24. </text>
  25. <text class="status-badge paid">
  26. 已支付
  27. </text>
  28. </view>
  29. <!-- Card Body: 服务信息 (左图右文) -->
  30. <view class="service-section">
  31. <image :src="order['image']" class="service-image" mode="aspectFill" />
  32. <view class="service-details">
  33. <view class="service-title-row">
  34. <text class="service-name">
  35. {{ order['serviceName'] }}
  36. </text>
  37. <text v-for="(tag, tIdx) in tagList(order)" :key="tIdx" :class="['tag-pill', tag.type]">
  38. {{ tag.text }}
  39. </text>
  40. </view>
  41. <view class="contact-info-row">
  42. <text class="contact-info">
  43. 联系人:{{ order['contact'] }}
  44. </text>
  45. <text class="tag-pill green ">
  46. 新客
  47. </text>
  48. </view>
  49. </view>
  50. <text class="service-price">
  51. ¥{{ order.price }}
  52. </text>
  53. </view>
  54. <!-- Card Address: 地址 & 距离 -->
  55. <view class="address-section">
  56. <u-icon type="location" size="16" color="#999999">
  57. </u-icon>
  58. <text class="address-content">
  59. {{ order.address }}
  60. </text>
  61. <text class="distance-text">
  62. {{ order.distance }}km
  63. </text>
  64. </view>
  65. <!-- Card Income: 预估收入 -->
  66. <view class="income-section">
  67. <text class="income-label">
  68. 预估收入
  69. </text>
  70. <view class="income-value-group">
  71. <text class="income-main">
  72. ¥{{ order.income }}
  73. </text>
  74. <text class="income-sub">
  75. (含路费)
  76. </text>
  77. </view>
  78. </view>
  79. <!-- Card Actions: 按钮组 -->
  80. <view class="action-section">
  81. <view class="btn btn-nav" @click="onNavigate(order.address)">
  82. <u-icon type="navigation" size="14" color="#333333">
  83. </u-icon>
  84. <text>
  85. 地址导航
  86. </text>
  87. </view>
  88. <text class="btn btn-transfer" @click="onTransfer(order.id)">
  89. 我要转单
  90. </text>
  91. <text class="btn btn-confirm" @click="onConfirm(order.id)">
  92. 确认接单
  93. </text>
  94. </view>
  95. </view>
  96. </view>
  97. </view>
  98. </template>
  99. <script setup lang="ts">
  100. import { ref } from 'vue';
  101. import { transRecords } from '@/utils/api/order';
  102. // --- 数据 ---
  103. const tabs = ['新订单',
  104. '进行中',
  105. '取消/售后',
  106. '已完成',
  107. '全部'];
  108. const currentTab = ref(0);
  109. type OrderTag = {
  110. text : string;
  111. type : 'orange' | 'green' | 'red';
  112. }
  113. type OrderItem = {
  114. id : number;
  115. time : string;
  116. serviceName : string;
  117. tags : OrderTag[];
  118. contact : string;
  119. price : string;
  120. address : string;
  121. distance : string;
  122. income : string;
  123. image : string;
  124. };
  125. // orders data – no need for reactivity, use a plain typed array
  126. const orders : OrderItem[] = [
  127. {
  128. id: 1,
  129. time: '2025-06-18 4:00',
  130. serviceName: '润养SPA',
  131. tags: [
  132. { text: '上门', type: 'orange' } as OrderTag,
  133. { text: '首单', type: 'orange' } as OrderTag,
  134. ],
  135. contact: '刘',
  136. price: '286.6',
  137. address: '烟台 芝罘区楚风一街楚凤花园(烟台吾悦)广场附近',
  138. distance: '2.24',
  139. income: '186.6',
  140. image: '/static/testInfo/demo.png'
  141. },
  142. {
  143. id: 2,
  144. time: '2025-06-18 8:00',
  145. serviceName: '润养SPA',
  146. tags: [
  147. { text: '加钟', type: 'red' } as OrderTag
  148. ],
  149. contact: '刘',
  150. price: '286.6',
  151. address: '烟台 芝罘区楚风一街楚凤花园(烟台吾悦)广场附近',
  152. distance: '2.24',
  153. income: '186.6',
  154. image: '/static/testInfo/demo.png'
  155. }
  156. ] as OrderItem[];
  157. // --- 方法 ---
  158. const onNavigate = (addr : string) => {
  159. uni.showToast({ title: '启动导航', icon: 'none' });
  160. };
  161. // helper used in template to give v-for a typed array source
  162. function tagList(order : OrderItem) : OrderTag[] {
  163. return order.tags;
  164. }
  165. const httpGetOrderList = async () => {
  166. const res = await transRecords({});
  167. console.log(res,'res');
  168. }
  169. const onTransfer = (id : number) => {
  170. uni.showModal({
  171. title: '转单确认',
  172. content: '确定将此订单转给其他技师吗?',
  173. success: (res) => {
  174. if (res.confirm) uni.showToast({ title: '转单成功', icon: 'success' });
  175. }
  176. });
  177. };
  178. const onConfirm = (id : number) => {
  179. uni.showLoading({ title: '处理中...' });
  180. setTimeout(() => {
  181. uni.hideLoading();
  182. uni.showToast({ title: '接单成功', icon: 'success' });
  183. }, 600);
  184. };
  185. onLoad(() => {
  186. httpGetOrderList()
  187. })
  188. </script>
  189. <style scoped>
  190. .page-container {
  191. background-color: #f5f6f8;
  192. /* min-height: 100vh; unsupported by uvue, replace with fixed value or remove */
  193. min-height: 1000rpx;
  194. /* width: 100%; */
  195. /* 默认块级元素已撑满父容器 */
  196. /* 确保内部元素不溢出 */
  197. box-sizing: border-box;
  198. }
  199. /* --- Tab 区域 --- */
  200. .tab-scroll {
  201. /* width: 100%; */
  202. /* 不使用百分比 */
  203. background-color: #ffffff;
  204. /* 固定高度或自适应 */
  205. height: 88rpx;
  206. }
  207. .tab-wrapper {
  208. display: flex;
  209. /* 内部横向排列 */
  210. flex-direction: row;
  211. align-items: center;
  212. /* 直接使用固定高度匹配 .tab-scroll */
  213. height: 88rpx;
  214. padding: 0 20rpx;
  215. white-space: nowrap;
  216. }
  217. .tab-item {
  218. position: relative;
  219. padding: 0 30rpx;
  220. /* 高度与容器一致,避免百分比 */
  221. height: 88rpx;
  222. display: flex;
  223. align-items: center;
  224. justify-content: center;
  225. font-size: 30rpx;
  226. color: #666666;
  227. }
  228. .tab-item.active {
  229. color: #333333;
  230. font-weight: bold;
  231. }
  232. .tab-indicator {
  233. position: absolute;
  234. bottom: 16rpx;
  235. left: 0;
  236. right: 0;
  237. margin: 0 auto;
  238. width: 40rpx;
  239. height: 6rpx;
  240. background-color: #ffc107;
  241. border-radius: 3rpx;
  242. }
  243. /* --- 列表区域 --- */
  244. .order-list {
  245. padding: 20rpx;
  246. /* 垂直排列卡片 */
  247. display: flex;
  248. flex-direction: column;
  249. /* gap: 20rpx; */
  250. }
  251. .order-card {
  252. background-color: #ffffff;
  253. border-radius: 16rpx;
  254. padding: 30rpx;
  255. /* 卡片内部也是垂直流 */
  256. display: flex;
  257. flex-direction: column;
  258. /* gap: 20rpx; */
  259. }
  260. /* 1. 头部 */
  261. .card-header {
  262. display: flex;
  263. flex-direction: row;
  264. /* 左右布局 */
  265. justify-content: space-between;
  266. align-items: center;
  267. }
  268. .time-text {
  269. font-size: 28rpx;
  270. color: #333333;
  271. font-weight: 400;
  272. /* use supported weight */
  273. }
  274. .status-badge {
  275. font-size: 24rpx;
  276. padding: 6rpx 16rpx;
  277. border-radius: 20rpx;
  278. }
  279. .status-badge.paid {
  280. background-color: #fff7e6;
  281. color: #ff9900;
  282. }
  283. /* 2. 服务信息 (左图右文) */
  284. .service-section {
  285. display: flex;
  286. flex-direction: row;
  287. /* 关键:横向排列图和文 */
  288. align-items: flex-start;
  289. /* gap: 20rpx; */
  290. }
  291. .service-image {
  292. width: 110rpx;
  293. height: 110rpx;
  294. border-radius: 12rpx;
  295. background-color: #f0f0f0;
  296. flex-shrink: 0;
  297. /* 防止图片被压缩 */
  298. }
  299. .service-details {
  300. flex: 1;
  301. display: flex;
  302. /* flex-direction: column; */
  303. /* 文字内部垂直排列 */
  304. justify-content: space-between;
  305. /* height: 110rpx; */
  306. }
  307. .service-title-row {
  308. display: flex;
  309. flex-direction: row;
  310. /* justify-content: space-between; */
  311. align-items: center;
  312. }
  313. .service-name {
  314. font-size: 32rpx;
  315. font-weight: bold;
  316. color: #333333;
  317. }
  318. .service-price {
  319. font-size: 34rpx;
  320. font-weight: bold;
  321. color: #333333;
  322. }
  323. .tags-container {
  324. display: flex;
  325. flex-direction: row;
  326. /* gap: 12rpx; */
  327. flex-wrap: wrap;
  328. }
  329. .tag-pill {
  330. font-size: 22rpx;
  331. padding: 4rpx 12rpx;
  332. border-radius: 20rpx;
  333. border-width: 1rpx;
  334. border-style: solid;
  335. line-height: 1.2;
  336. }
  337. .tag-pill.orange {
  338. color: #ff9900;
  339. border-color: #ff9900;
  340. background-color: #fffaf0;
  341. }
  342. .tag-pill.green {
  343. color: #52c41a;
  344. border-color: #52c41a;
  345. background-color: #f6ffed;
  346. }
  347. .tag-pill.red {
  348. color: #ff4d4f;
  349. border-color: #ff4d4f;
  350. background-color: #fff1f0;
  351. }
  352. .contact-info-row {
  353. display: flex;
  354. flex-direction: row;
  355. align-items: center;
  356. /* gap: 20rpx; */
  357. }
  358. .contact-info {
  359. font-size: 26rpx;
  360. color: #999999;
  361. }
  362. /* 3. 地址栏 */
  363. .address-section {
  364. display: flex;
  365. flex-direction: row;
  366. align-items: flex-start;
  367. padding-bottom: 20rpx;
  368. border-bottom-width: 1rpx;
  369. border-bottom-color: #f5f5f5;
  370. border-bottom-style: solid;
  371. /* gap: 10rpx; */
  372. }
  373. .address-content {
  374. flex: 1;
  375. font-size: 26rpx;
  376. color: #666666;
  377. line-height: 1.4;
  378. /* multi-line ellipsis removed; not supported by uvue */
  379. /* display: -webkit-box;
  380. -webkit-line-clamp: 2;
  381. -webkit-box-orient: vertical;
  382. overflow: hidden;
  383. */
  384. }
  385. .distance-text {
  386. font-size: 24rpx;
  387. color: #999999;
  388. white-space: nowrap;
  389. margin-left: 10rpx;
  390. }
  391. /* 4. 收入栏 */
  392. .income-section {
  393. display: flex;
  394. flex-direction: row;
  395. justify-content: space-between;
  396. align-items: center;
  397. }
  398. .income-label {
  399. font-size: 28rpx;
  400. color: #666666;
  401. }
  402. .income-value-group {
  403. display: flex;
  404. flex-direction: row;
  405. align-items: center;
  406. /* baseline not supported */
  407. /* */
  408. }
  409. .income-main {
  410. font-size: 36rpx;
  411. font-weight: bold;
  412. color: #ff4d4f;
  413. }
  414. .income-sub {
  415. font-size: 24rpx;
  416. color: #999999;
  417. }
  418. /* 5. 按钮组 */
  419. .action-section {
  420. display: flex;
  421. flex-direction: row;
  422. justify-content: space-between;
  423. /* gap: 20rpx; */
  424. }
  425. .btn {
  426. flex: 1;
  427. height: 72rpx;
  428. border-radius: 36rpx;
  429. font-size: 28rpx;
  430. font-weight: 400;
  431. display: flex;
  432. flex-direction: row;
  433. /* 按钮内图标和文字横向 */
  434. justify-content: center;
  435. align-items: center;
  436. /* */
  437. }
  438. .btn-nav {
  439. background-color: #f5f5f5;
  440. color: #333333;
  441. }
  442. .btn-transfer {
  443. background-color: #ffffff;
  444. color: #ff9900;
  445. border-width: 1rpx;
  446. border-color: #ff9900;
  447. border-style: solid;
  448. }
  449. .btn-confirm {
  450. background-color: #ffc107;
  451. color: #333333;
  452. border-width: 1rpx;
  453. border-color: #ffc107;
  454. border-style: solid;
  455. }
  456. </style>