384 lines
8.7 KiB
Vue
384 lines
8.7 KiB
Vue
<template>
|
|
<view class="container">
|
|
<!-- 统计卡片 -->
|
|
<view class="statistics-card">
|
|
<view class="stat-item" @click="filterStatus(null)">
|
|
<text class="stat-value">{{ statistics.totalOrders || 0 }}</text>
|
|
<text class="stat-label">全部工单</text>
|
|
</view>
|
|
<view class="stat-item" @click="filterStatus(0)">
|
|
<text class="stat-value">{{ statistics.pendingOrders || 0 }}</text>
|
|
<text class="stat-label">待服务</text>
|
|
</view>
|
|
<view class="stat-item" @click="filterStatus(1)">
|
|
<text class="stat-value">{{ statistics.inProgressOrders || 0 }}</text>
|
|
<text class="stat-label">进行中</text>
|
|
</view>
|
|
<view class="stat-item" @click="filterStatus(2)">
|
|
<text class="stat-value">{{ statistics.completedOrders || 0 }}</text>
|
|
<text class="stat-label">已完成</text>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 工单列表 -->
|
|
<scroll-view
|
|
class="work-order-list"
|
|
scroll-y
|
|
@scrolltolower="loadMore"
|
|
>
|
|
<view
|
|
v-for="item in list"
|
|
:key="item.id"
|
|
class="work-order-item"
|
|
@click="goDetail(item.id)"
|
|
>
|
|
<view class="order-header">
|
|
<view class="order-no">工单号:{{ item.id }}</view>
|
|
<view class="status" :class="'status-' + item.status">
|
|
{{ getStatusText(item.status) }}
|
|
</view>
|
|
</view>
|
|
|
|
<view class="order-info">
|
|
<view class="info-row">
|
|
<text class="label">服务名称:</text>
|
|
<text class="value">{{ item.serviceName }}</text>
|
|
</view>
|
|
<view class="info-row">
|
|
<text class="label">陪伴员:</text>
|
|
<text class="value">{{ item.teacherName }}</text>
|
|
</view>
|
|
<view class="info-row">
|
|
<text class="label">服务时间:</text>
|
|
<text class="value">{{ item.serviceDate }} {{ item.timeSlot }}</text>
|
|
</view>
|
|
<view class="info-row">
|
|
<text class="label">服务地址:</text>
|
|
<text class="value">{{ item.serviceAddress }}</text>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="order-footer">
|
|
<text class="create-time">创建时间:{{ formatTime(item.createTime) }}</text>
|
|
<view class="actions">
|
|
<button
|
|
v-if="item.status === 0"
|
|
class="btn-action"
|
|
@click.stop="reassign(item)"
|
|
>
|
|
重新派单
|
|
</button>
|
|
<button
|
|
v-if="item.status === 1"
|
|
class="btn-action primary"
|
|
@click.stop="complete(item)"
|
|
>
|
|
完成工单
|
|
</button>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<view v-if="loading" class="loading">加载中...</view>
|
|
<view v-if="!hasMore && list.length > 0" class="no-more">没有更多了</view>
|
|
<view v-if="!loading && list.length === 0" class="empty">
|
|
<text class="empty-icon">📭</text>
|
|
<text>暂无工单</text>
|
|
</view>
|
|
</scroll-view>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
import { managerApi } from '@/api/index.js'
|
|
|
|
export default {
|
|
data() {
|
|
return {
|
|
managerId: null,
|
|
currentStatus: null,
|
|
list: [],
|
|
statistics: {},
|
|
loading: false,
|
|
hasMore: true,
|
|
page: 1
|
|
}
|
|
},
|
|
onLoad(options) {
|
|
this.managerId = options.managerId
|
|
this.loadStatistics()
|
|
this.loadList()
|
|
},
|
|
methods: {
|
|
async loadStatistics() {
|
|
try {
|
|
const res = await managerApi.getStatistics(this.managerId)
|
|
this.statistics = res
|
|
} catch (error) {
|
|
console.error('加载统计数据失败', error)
|
|
}
|
|
},
|
|
|
|
async loadList() {
|
|
if (this.loading) return
|
|
|
|
this.loading = true
|
|
try {
|
|
const res = await managerApi.getWorkOrders({
|
|
managerId: this.managerId,
|
|
status: this.currentStatus,
|
|
page: this.page,
|
|
size: 10
|
|
})
|
|
|
|
if (this.page === 1) {
|
|
this.list = res.records
|
|
} else {
|
|
this.list.push(...res.records)
|
|
}
|
|
|
|
this.hasMore = res.records.length >= 10
|
|
} catch (error) {
|
|
uni.showToast({ title: '加载失败', icon: 'none' })
|
|
} finally {
|
|
this.loading = false
|
|
}
|
|
},
|
|
|
|
loadMore() {
|
|
if (!this.hasMore || this.loading) return
|
|
this.page++
|
|
this.loadList()
|
|
},
|
|
|
|
filterStatus(status) {
|
|
this.currentStatus = status
|
|
this.page = 1
|
|
this.list = []
|
|
this.loadList()
|
|
},
|
|
|
|
goDetail(id) {
|
|
uni.navigateTo({
|
|
url: `/pages/manager/work-order-detail?id=${id}`
|
|
})
|
|
},
|
|
|
|
reassign(item) {
|
|
uni.navigateTo({
|
|
url: `/pages/manager/assign?orderId=${item.orderId}&managerId=${this.managerId}`
|
|
})
|
|
},
|
|
|
|
complete(item) {
|
|
const self = this
|
|
uni.showModal({
|
|
title: '确认完成',
|
|
content: '确认完成该工单吗?',
|
|
success: (res) => {
|
|
if (res.confirm) {
|
|
managerApi.updateWorkOrderStatus({
|
|
workOrderId: item.id,
|
|
status: 2,
|
|
remark: '工单已完成'
|
|
}).then(() => {
|
|
uni.showToast({ title: '操作成功', icon: 'success' })
|
|
self.loadStatistics()
|
|
self.loadList()
|
|
}).catch(error => {
|
|
uni.showToast({ title: '操作失败', icon: 'none' })
|
|
})
|
|
}
|
|
}
|
|
})
|
|
},
|
|
|
|
formatTime(time) {
|
|
if (!time) return ''
|
|
return time.replace('T', ' ').substring(0, 16)
|
|
},
|
|
|
|
getStatusText(status) {
|
|
const statusMap = {
|
|
0: '待派单',
|
|
1: '已派单',
|
|
2: '待服务',
|
|
3: '服务中',
|
|
4: '已完成',
|
|
'-1': '已取消',
|
|
'-2': '已退款'
|
|
}
|
|
return statusMap[status] || '未知'
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.container {
|
|
height: 100vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
background: #f8f8f8;
|
|
}
|
|
|
|
.statistics-card {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
padding: 40rpx 30rpx;
|
|
display: flex;
|
|
justify-content: space-around;
|
|
|
|
.stat-item {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
|
|
.stat-value {
|
|
font-size: 48rpx;
|
|
font-weight: bold;
|
|
color: #fff;
|
|
margin-bottom: 12rpx;
|
|
}
|
|
|
|
.stat-label {
|
|
font-size: 24rpx;
|
|
color: rgba(255, 255, 255, 0.8);
|
|
}
|
|
}
|
|
}
|
|
|
|
.work-order-list {
|
|
flex: 1;
|
|
padding: 20rpx 30rpx;
|
|
}
|
|
|
|
.work-order-item {
|
|
background: #fff;
|
|
border-radius: 16rpx;
|
|
padding: 30rpx;
|
|
margin-bottom: 20rpx;
|
|
|
|
.order-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 20rpx;
|
|
padding-bottom: 20rpx;
|
|
border-bottom: 1rpx solid #f0f0f0;
|
|
|
|
.order-no {
|
|
font-size: 28rpx;
|
|
color: #666;
|
|
}
|
|
|
|
.status {
|
|
padding: 8rpx 20rpx;
|
|
border-radius: 20rpx;
|
|
font-size: 24rpx;
|
|
|
|
&.status-0 {
|
|
background: #fff7e6;
|
|
color: #fa8c16;
|
|
}
|
|
|
|
&.status-1 {
|
|
background: #e6f7ff;
|
|
color: #1890ff;
|
|
}
|
|
|
|
&.status-2 {
|
|
background: #f6ffed;
|
|
color: #52c41a;
|
|
}
|
|
|
|
&.status-3 {
|
|
background: #f5f5f5;
|
|
color: #999;
|
|
}
|
|
}
|
|
}
|
|
|
|
.order-info {
|
|
margin-bottom: 20rpx;
|
|
|
|
.info-row {
|
|
display: flex;
|
|
margin-bottom: 16rpx;
|
|
font-size: 28rpx;
|
|
|
|
&:last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.label {
|
|
color: #999;
|
|
min-width: 160rpx;
|
|
}
|
|
|
|
.value {
|
|
flex: 1;
|
|
color: #333;
|
|
}
|
|
}
|
|
}
|
|
|
|
.order-footer {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding-top: 20rpx;
|
|
border-top: 1rpx solid #f0f0f0;
|
|
|
|
.create-time {
|
|
font-size: 24rpx;
|
|
color: #999;
|
|
}
|
|
|
|
.actions {
|
|
display: flex;
|
|
gap: 16rpx;
|
|
|
|
.btn-action {
|
|
padding: 12rpx 32rpx;
|
|
background: #f0f0f0;
|
|
color: #666;
|
|
border: none;
|
|
border-radius: 40rpx;
|
|
font-size: 26rpx;
|
|
|
|
&.primary {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
color: #fff;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.loading, .no-more {
|
|
text-align: center;
|
|
padding: 40rpx;
|
|
color: #999;
|
|
font-size: 26rpx;
|
|
}
|
|
|
|
.empty {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 120rpx 0;
|
|
|
|
image {
|
|
width: 300rpx;
|
|
height: 300rpx;
|
|
margin-bottom: 30rpx;
|
|
}
|
|
|
|
text {
|
|
font-size: 28rpx;
|
|
color: #999;
|
|
}
|
|
}
|
|
</style>
|