| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544 |
- <template>
- <view class="order-list-page">
- <!-- 顶部状态栏 -->
- <view class="sticky-header">
- <scroll-view scroll-x class="tabs-scroll" :show-scrollbar="false">
- <view class="tabs-row">
- <view v-for="tab in tabList" :key="tab.name"
- :class="['tab-item', { active: activeStatus === tab.name }]" @click="onTabClick(tab.name)">
- <text>{{ tab.title }}</text>
- </view>
- </view>
- </scroll-view>
- <!-- 搜索和过滤 -->
- <view class="filter-row">
- <picker :range="typeOptions" range-key="text" @change="onTypeChange">
- <view class="dropdown-btn">
- <text>{{ currentTypeName }}</text>
- <uni-icons type="bottom" size="12" color="#333"></uni-icons>
- </view>
- </picker>
- <view class="search-wrap">
- <uni-icons type="search" size="14" color="#999"></uni-icons>
- <input class="search-input" v-model="searchValue" placeholder="订单号/商户/宠主/手机号"
- placeholder-class="placeholder-style" />
- </view>
- </view>
- </view>
- <!-- 订单列表内容 -->
- <view class="list-container">
- <view class="order-card" v-for="order in filteredOrders" :key="order.id" @click="goToDetail(order)">
- <!-- 头部:订单号与状态 -->
- <view class="order-head">
- <text class="order-no">{{ order.id }}</text>
- <text :class="['status-text', order.statusClass]">{{ order.statusText }}</text>
- </view>
- <!-- 主体信息 -->
- <view class="order-body">
- <view class="service-row">
- <text class="service-name">{{ order.serviceType }}</text>
- <text class="service-tag tag-orange" v-if="order.serviceTags[0]">{{ order.serviceTags[0]
- }}</text>
- <text class="service-tag tag-blue" v-if="order.serviceTags[1] === '接'">接</text>
- <text class="service-tag tag-green" v-if="order.serviceTags[1] === '送'">送</text>
- </view>
- <view class="pet-row">
- <view class="pet-avatar-text">
- <text>{{ order.petName.substring(0, 1).toUpperCase() }}</text>
- </view>
- <view class="pet-desc">
- <text class="bold">{{ order.petName }}</text>
- <text class="sub">{{ order.petBreed }}</text>
- </view>
- <text class="user-desc">{{ order.userName }}</text>
- </view>
- <view class="info-list">
- <view class="info-item">
- <uni-icons type="location" size="14" color="#999"></uni-icons>
- <text>{{ order.address }}</text>
- </view>
- <view class="info-item">
- <uni-icons type="shop" size="14" color="#999"></uni-icons>
- <text>{{ order.shopName }} {{ order.userPhone }}</text>
- </view>
- <view class="info-item">
- <uni-icons type="calendar" size="14" color="#999"></uni-icons>
- <text>预约: {{ order.bookTime }}</text>
- </view>
- </view>
- </view>
- <!-- 履约与操作栏 -->
- <view class="order-foot">
- <view class="foot-left">
- <text class="create-time">下单: {{ order.createTime }}</text>
- <view class="assign-info">
- <text class="assign-label">履约信息:</text>
- <text class="assign-none" v-if="order.statusText === '待派单'">暂未派单</text>
- <text class="assign-none" v-else-if="order.statusText === '已取消'">订单已关闭</text>
- <text class="assign-name" v-else>{{ order.assigneeName }}</text>
- </view>
- <text class="cancel-time" v-if="order.statusText === '已取消' && order.cancelTime">取消: {{
- order.cancelTime }}</text>
- </view>
- <view class="actions">
- <template v-if="order.statusText === '待派单' || order.statusText === '待接单'">
- <button size="mini" class="action-btn btn-cancel"
- @click.stop="onCancelOrder(order)">取消</button>
- <button size="mini" class="action-btn btn-primary"
- @click.stop="goToDetail(order)">详情</button>
- </template>
- <template v-else-if="['服务中', '待商家确认', '已完成'].includes(order.statusText)">
- <button v-if="['服务中', '已完成'].includes(order.statusText)" size="mini"
- class="action-btn btn-cancel" @click.stop="onComplaint(order)">投诉</button>
- <button size="mini" class="action-btn btn-primary"
- @click.stop="goToDetail(order)">详情</button>
- </template>
- <template v-else>
- <button size="mini" class="action-btn btn-primary"
- @click.stop="goToDetail(order)">详情</button>
- </template>
- </view>
- </view>
- </view>
- <!-- 空状态 -->
- <view class="empty-state" v-if="filteredOrders.length === 0">
- <text class="empty-text">暂无相关订单</text>
- </view>
- </view>
- <custom-tabbar></custom-tabbar>
- </view>
- </template>
- <script setup>
- import { ref, computed } from 'vue'
- import customTabbar from '@/components/custom-tabbar/index.vue'
- import orderMockData from '@/mock/order.json'
- // 筛选与搜索
- const activeStatus = ref('all')
- const filterType = ref(0)
- const searchValue = ref('')
- const tabList = [
- { title: '全部订单', name: 'all' },
- { title: '待派单', name: 'wait_dispatch' },
- { title: '待接单', name: 'wait_accept' },
- { title: '服务中', name: 'serving' },
- { title: '已完成', name: 'done' },
- { title: '已取消', name: 'cancel' }
- ]
- const typeOptions = [
- { text: '全部类型', value: 0 },
- { text: '宠物接送', value: 1 },
- { text: '上门喂遛', value: 2 },
- { text: '上门洗护', value: 3 }
- ]
- const currentTypeName = computed(() => {
- return typeOptions[filterType.value].text
- })
- const statusMap = {
- all: '全部',
- wait_dispatch: '待派单',
- wait_accept: '待接单',
- serving: '服务中',
- done: '已完成',
- cancel: '已取消'
- }
- const statusKeyMap = {
- '待派单': 'wait_dispatch',
- '待接单': 'wait_accept',
- '服务中': 'serving',
- '已完成': 'done',
- '已取消': 'cancel'
- }
- const onTabClick = (name) => {
- activeStatus.value = name
- }
- const onTypeChange = (e) => {
- filterType.value = Number(e.detail.value)
- }
- // 搜索与过滤后的订单列表
- const filteredOrders = computed(() => {
- return orders.value.filter(order => {
- const statusMatch = activeStatus.value === 'all' || order.statusText === statusMap[activeStatus.value]
- const typeMap = { 1: '宠物接送', 2: '上门喂遛', 3: '上门洗护' }
- const typeMatch = filterType.value === 0 || order.serviceType === typeMap[filterType.value]
- const searchLower = searchValue.value.toLowerCase()
- const searchMatch = !searchValue.value ||
- order.id.toLowerCase().includes(searchLower) ||
- order.userName.toLowerCase().includes(searchLower) ||
- order.petName.toLowerCase().includes(searchLower) ||
- order.userPhone.includes(searchLower)
- return statusMatch && typeMatch && searchMatch
- })
- })
- const orders = ref(orderMockData)
- const onCancelOrder = (order) => {
- uni.showModal({
- title: '提示',
- content: `确定要取消订单 [${order.id}] 吗?`,
- success: (res) => {
- if (res.confirm) {
- uni.showToast({ title: '订单已取消', icon: 'success' })
- order.statusText = '已取消'
- order.statusClass = 'text-gray'
- activeStatus.value = 'cancel'
- }
- }
- })
- }
- const goToDetail = (order) => {
- const serviceKey = order.serviceType === '宠物接送' ? 'transport' : (order.serviceType === '上门喂遛' ? 'feed' : 'wash')
- const statusKey = statusKeyMap[order.statusText] || 'serving'
- const cancelTime = order.cancelTime ? encodeURIComponent(order.cancelTime) : ''
- uni.navigateTo({
- url: `/pages/order/detail/index?service=${serviceKey}&status=${statusKey}${cancelTime ? '&cancelTime=' + cancelTime : ''}`
- })
- }
- const onComplaint = (order) => {
- uni.navigateTo({
- url: `/pages/my/complaint-submit/index?orderId=${order.id}`
- })
- }
- </script>
- <style lang="scss" scoped>
- .order-list-page {
- background-color: #f2f2f2;
- min-height: 100vh;
- padding-bottom: 120rpx;
- }
- .sticky-header {
- position: sticky;
- top: 0;
- z-index: 99;
- background-color: #fff;
- }
- .tabs-scroll {
- white-space: nowrap;
- border-bottom: 1rpx solid #f5f5f5;
- }
- .tabs-row {
- display: flex;
- padding: 0 16rpx;
- }
- .tab-item {
- padding: 24rpx 24rpx;
- font-size: 28rpx;
- color: #666;
- position: relative;
- flex-shrink: 0;
- }
- .tab-item.active {
- color: #333;
- font-weight: bold;
- }
- .tab-item.active::after {
- content: '';
- position: absolute;
- bottom: 0;
- left: 50%;
- transform: translateX(-50%);
- width: 48rpx;
- height: 6rpx;
- background-color: #f7ca3e;
- border-radius: 6rpx;
- }
- .filter-row {
- display: flex;
- align-items: center;
- padding: 12rpx 16rpx;
- background-color: #fff;
- border-top: 1rpx solid #f9f9f9;
- gap: 16rpx;
- }
- .dropdown-btn {
- display: flex;
- align-items: center;
- gap: 8rpx;
- background: #f5f5f5;
- border-radius: 34rpx;
- padding: 12rpx 24rpx;
- font-size: 26rpx;
- color: #333;
- }
- .search-wrap {
- flex: 1;
- display: flex;
- align-items: center;
- background: #f5f5f5;
- border-radius: 34rpx;
- padding: 12rpx 20rpx;
- gap: 12rpx;
- }
- .search-input {
- flex: 1;
- font-size: 24rpx;
- }
- .placeholder-style {
- color: #999;
- font-size: 24rpx;
- }
- .list-container {
- padding: 24rpx;
- }
- .order-card {
- padding: 28rpx;
- margin-bottom: 24rpx;
- background: #fff;
- border-radius: 24rpx;
- box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
- }
- .order-head {
- display: flex;
- justify-content: space-between;
- align-items: center;
- border-bottom: 1rpx solid #f5f5f5;
- padding-bottom: 20rpx;
- margin-bottom: 20rpx;
- }
- .order-no {
- font-size: 26rpx;
- color: #666;
- }
- .status-text {
- font-size: 26rpx;
- font-weight: bold;
- }
- .text-red {
- color: #f44336;
- }
- .text-orange {
- color: #ff9800;
- }
- .text-blue {
- color: #2196f3;
- }
- .text-green {
- color: #4caf50;
- }
- .text-gray {
- color: #999;
- }
- .service-row {
- display: flex;
- align-items: center;
- margin-bottom: 20rpx;
- gap: 12rpx;
- }
- .service-name {
- font-size: 30rpx;
- font-weight: bold;
- color: #333;
- }
- .service-tag {
- font-size: 20rpx;
- padding: 2rpx 8rpx;
- border-radius: 8rpx;
- border: 1rpx solid;
- }
- .tag-orange {
- color: #ff9800;
- border-color: #ff9800;
- background: #fff3e0;
- }
- .tag-blue {
- color: #2196f3;
- border-color: #2196f3;
- background: #e3f2fd;
- }
- .tag-green {
- color: #4caf50;
- border-color: #4caf50;
- background: #e8f5e9;
- }
- .pet-row {
- display: flex;
- align-items: center;
- margin-bottom: 20rpx;
- background: #f7f8fa;
- padding: 16rpx 20rpx;
- border-radius: 16rpx;
- }
- .pet-avatar-text {
- width: 64rpx;
- height: 64rpx;
- border-radius: 50%;
- background-color: #e3f2fd;
- color: #2196f3;
- display: flex;
- align-items: center;
- justify-content: center;
- font-weight: bold;
- font-size: 32rpx;
- margin-right: 20rpx;
- }
- .pet-desc {
- display: flex;
- align-items: baseline;
- gap: 12rpx;
- flex: 1;
- }
- .pet-desc .bold {
- font-size: 28rpx;
- font-weight: bold;
- color: #333;
- }
- .pet-desc .sub {
- font-size: 24rpx;
- color: #666;
- }
- .user-desc {
- font-size: 26rpx;
- color: #333;
- }
- .info-list {
- display: flex;
- flex-direction: column;
- gap: 12rpx;
- }
- .info-item {
- display: flex;
- align-items: center;
- font-size: 24rpx;
- color: #666;
- gap: 12rpx;
- }
- .order-foot {
- display: flex;
- justify-content: space-between;
- align-items: flex-end;
- margin-top: 24rpx;
- padding-top: 20rpx;
- border-top: 1rpx solid #f5f5f5;
- }
- .foot-left {
- display: flex;
- flex-direction: column;
- gap: 12rpx;
- }
- .create-time,
- .cancel-time {
- font-size: 22rpx;
- color: #999;
- }
- .assign-info {
- display: flex;
- align-items: center;
- font-size: 24rpx;
- gap: 12rpx;
- }
- .assign-label {
- color: #999;
- }
- .assign-none {
- color: #ccc;
- }
- .assign-name {
- color: #333;
- font-weight: bold;
- }
- .actions {
- display: flex;
- gap: 16rpx;
- }
- .action-btn {
- height: 56rpx;
- line-height: 56rpx;
- min-width: 120rpx;
- font-size: 24rpx;
- font-weight: 600;
- padding: 0 28rpx;
- border-radius: 28rpx;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- }
- .btn-cancel {
- border: 1rpx solid #ddd;
- color: #666;
- background: transparent;
- }
- .btn-primary {
- background: linear-gradient(90deg, #ffd53f, #ff9500);
- border: none;
- color: #fff;
- box-shadow: 0 6rpx 16rpx rgba(255, 149, 0, 0.3);
- }
- .empty-state {
- text-align: center;
- padding: 100rpx 0;
- }
- .empty-text {
- font-size: 28rpx;
- color: #999;
- }
- </style>
|