spa/frontend/technician-miniprogram/pages/index/index.vue

368 lines
6.9 KiB
Vue

<template>
<view class="container">
<!-- 技师状态卡片 -->
<view class="status-card">
<view class="status-info">
<view class="avatar">
<image :src="technicianInfo.avatar || '/static/default-avatar.png'" mode="aspectFill"></image>
<view class="status-dot" :class="{ online: isOnline }"></view>
</view>
<view class="info">
<text class="name">{{ technicianInfo.name || '未认证技师' }}</text>
<text class="level">{{ technicianInfo.level || '初级技师' }}</text>
</view>
</view>
<button class="status-btn" :class="{ online: isOnline }" @click="toggleStatus">
{{ isOnline ? '上线中' : '已下线' }}
</button>
</view>
<!-- 今日数据 -->
<view class="data-grid">
<view class="data-item">
<text class="number">{{ todayData.orders }}</text>
<text class="label">今日订单</text>
</view>
<view class="data-item">
<text class="number">¥{{ todayData.earnings }}</text>
<text class="label">今日收入</text>
</view>
<view class="data-item">
<text class="number">{{ todayData.rating }}</text>
<text class="label">服务评分</text>
</view>
</view>
<!-- 待处理订单 -->
<view class="section">
<view class="section-title">
<text>待处理订单</text>
<text class="more" @click="goToOrders">查看全部</text>
</view>
<view class="order-list">
<view class="order-item" v-for="(order, index) in pendingOrders" :key="index" @click="handleOrder(order)">
<view class="order-info">
<text class="service">{{ order.serviceName }}</text>
<text class="address">{{ order.address }}</text>
<text class="time">{{ order.appointmentTime }}</text>
</view>
<view class="order-action">
<text class="price">¥{{ order.price }}</text>
<button class="accept-btn" @click.stop="acceptOrder(order)">接单</button>
</view>
</view>
</view>
</view>
<!-- 快捷功能 -->
<view class="quick-actions">
<view class="action-item" @click="goToCertification">
<image src="/static/icon-cert.png" mode="aspectFit"></image>
<text>技师认证</text>
</view>
<view class="action-item" @click="goToEarnings">
<image src="/static/icon-money.png" mode="aspectFit"></image>
<text>收入明细</text>
</view>
<view class="action-item" @click="goToService">
<image src="/static/icon-service.png" mode="aspectFit"></image>
<text>服务记录</text>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
isOnline: false,
technicianInfo: {},
todayData: {
orders: 0,
earnings: 0,
rating: 5.0
},
pendingOrders: []
}
},
onLoad() {
this.loadData()
},
onShow() {
this.loadPendingOrders()
},
methods: {
loadData() {
// 加载技师信息
this.technicianInfo = uni.getStorageSync('technicianInfo') || {}
// 加载今日数据
this.loadTodayData()
},
loadTodayData() {
this.$api.get('/api/technician/today-data').then(res => {
this.todayData = res.data
})
},
loadPendingOrders() {
this.$api.get('/api/technician/pending-orders').then(res => {
this.pendingOrders = res.data
})
},
toggleStatus() {
this.isOnline = !this.isOnline
this.$api.post('/api/technician/status', { online: this.isOnline }).then(res => {
uni.showToast({
title: this.isOnline ? '已上线' : '已下线',
icon: 'success'
})
})
},
acceptOrder(order) {
uni.showModal({
title: '确认接单',
content: `确定接受这个订单吗?`,
success: (res) => {
if (res.confirm) {
this.$api.post('/api/technician/accept-order', { orderId: order.id }).then(res => {
uni.showToast({
title: '接单成功',
icon: 'success'
})
this.loadPendingOrders()
})
}
}
})
},
handleOrder(order) {
uni.navigateTo({
url: `/pages/order-detail/order-detail?id=${order.id}`
})
},
goToOrders() {
uni.switchTab({
url: '/pages/orders/orders'
})
},
goToCertification() {
uni.navigateTo({
url: '/pages/certification/certification'
})
},
goToEarnings() {
uni.switchTab({
url: '/pages/earnings/earnings'
})
},
goToService() {
uni.navigateTo({
url: '/pages/service-record/service-record'
})
}
}
}
</script>
<style scoped>
.container {
padding: 0;
}
.status-card {
background: linear-gradient(135deg, #ff6600 0%, #ff8533 100%);
padding: 20px;
color: white;
display: flex;
justify-content: space-between;
align-items: center;
}
.status-info {
display: flex;
align-items: center;
}
.avatar {
position: relative;
margin-right: 15px;
}
.avatar image {
width: 60px;
height: 60px;
border-radius: 30px;
border: 2px solid white;
}
.status-dot {
position: absolute;
bottom: 0;
right: 0;
width: 16px;
height: 16px;
border-radius: 8px;
background: #ccc;
border: 2px solid white;
}
.status-dot.online {
background: #4cd964;
}
.info .name {
display: block;
font-size: 18px;
font-weight: bold;
}
.info .level {
display: block;
font-size: 14px;
opacity: 0.8;
}
.status-btn {
padding: 8px 20px;
border-radius: 20px;
background: rgba(255, 255, 255, 0.2);
color: white;
border: 1px solid white;
font-size: 14px;
}
.status-btn.online {
background: rgba(255, 255, 255, 0.9);
color: #ff6600;
}
.data-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
background: white;
margin: 15px;
border-radius: 10px;
overflow: hidden;
}
.data-item {
padding: 20px;
text-align: center;
border-right: 1px solid #f0f0f0;
}
.data-item:last-child {
border-right: none;
}
.data-item .number {
display: block;
font-size: 20px;
font-weight: bold;
color: #333;
}
.data-item .label {
display: block;
font-size: 12px;
color: #999;
margin-top: 5px;
}
.section {
margin: 15px;
}
.section-title {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
font-size: 16px;
font-weight: bold;
}
.more {
font-size: 14px;
color: #ff6600;
}
.order-item {
background: white;
border-radius: 8px;
padding: 15px;
margin-bottom: 10px;
display: flex;
justify-content: space-between;
align-items: center;
}
.order-info .service {
display: block;
font-size: 16px;
font-weight: bold;
margin-bottom: 5px;
}
.order-info .address,
.order-info .time {
display: block;
font-size: 12px;
color: #999;
margin-bottom: 3px;
}
.order-action {
text-align: right;
}
.price {
display: block;
font-size: 18px;
font-weight: bold;
color: #ff6600;
margin-bottom: 8px;
}
.accept-btn {
padding: 6px 15px;
background: #ff6600;
color: white;
border-radius: 15px;
font-size: 12px;
}
.quick-actions {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 15px;
margin: 15px;
}
.action-item {
background: white;
border-radius: 8px;
padding: 20px;
text-align: center;
}
.action-item image {
width: 30px;
height: 30px;
margin-bottom: 8px;
}
.action-item text {
display: block;
font-size: 14px;
}
</style>