2026-06-12 11:02:01 +08:00

307 lines
12 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="hsw-page">
<!-- 顶部Tab全部/待上门/服务中/已完成/月账单 -->
<view class="hsw-tabs">
<view
class="hsw-tab"
:class="{ active: statusTab === item.v }"
v-for="item in orderTabs"
:key="item.v"
@tap="switchStatus(item.v)"
>{{ item.t }}</view>
</view>
<!-- 月账单子Tab -->
<view class="hsw-tabs hsw-tabs-bill" v-if="showMonthlyBill">
<view
class="hsw-tab"
:class="{ active: billTab === item.v }"
v-for="item in billTabs"
:key="item.v"
@tap="switchBillTab(item.v)"
>{{ item.t }}</view>
</view>
<!-- 列表区域 -->
<scroll-view class="hsw-list" scroll-y>
<!-- 普通服务单非月账单模式 -->
<block v-if="!showMonthlyBill">
<view class="hsw-card" v-for="o in orders" :key="o.id">
<view class="hsw-head">
<text class="hsw-no">{{ o.order_no }}</text>
<text class="hsw-kind">{{ kindText(o.order_kind) }}</text>
</view>
<view class="hsw-row"><text>服务</text><text>{{ o.service_name }}</text></view>
<view class="hsw-row"><text>客户</text><text>{{ o.contact_name }} {{ o.contact_phone }}</text></view>
<view class="hsw-row"><text>地址</text><text>{{ o.service_address }}</text></view>
<view class="hsw-row"><text>金额</text><text class="hsw-amount">¥{{ o.amount }}</text></view>
<view class="hsw-foot">
<view class="hsw-btn ghost" v-if="o.status === 1" @tap="updateStatus(o, 2)">开始服务</view>
<view class="hsw-btn" v-if="o.status === 2 && o.order_kind === 1" @tap="openExtra(o)">代客补差</view>
<view class="hsw-btn ghost" v-if="o.status === 2" @tap="updateStatus(o, 4)">完成服务</view>
</view>
</view>
<view v-if="orders.length === 0" class="hsw-empty">暂无服务单</view>
</block>
<!-- 月账单列表师傅可见所有状态 -->
<block v-else>
<view class="hsw-card hsw-bill-card" v-for="b in bills" :key="b.id">
<view class="hsw-head">
<text class="hsw-no">{{ b.bill_no }}</text>
<text class="hsw-kind" :class="b.push_status === 0 ? 'status-warn' : b.push_status === 1 ? 'status-info' : b.push_status === 2 ? 'status-ok' : 'status-cancel'">{{ pushStatusText(b.push_status) }}</text>
</view>
<view class="hsw-row"><text>服务</text><text>{{ b.service_name }}</text></view>
<view class="hsw-row"><text>客户</text><text>{{ b.contact_name }} {{ b.contact_phone }}</text></view>
<view class="hsw-row"><text>地址</text><text>{{ b.service_address }}</text></view>
<view class="hsw-row"><text>账单月份</text><text>{{ b.bill_month }}</text></view>
<view class="hsw-row"><text>金额</text><text class="hsw-amount">¥{{ b.amount }}</text></view>
<view class="hsw-foot">
<view class="hsw-btn" v-if="b.push_status === 0" @tap="openPush(b)">推送至用户支付</view>
<view class="hsw-btn ghost" v-if="b.push_status === 0" @tap="openReject(b)">拒绝</view>
<view class="hsw-btn ghost" v-if="b.push_status === 1">已推送待用户支付</view>
<view class="hsw-btn ghost" v-if="b.push_status === 2">用户已支付</view>
<view class="hsw-btn ghost" v-if="b.push_status === 3">已拒绝{{ b.remark ? '' + b.remark : '' }}</view>
</view>
</view>
<view v-if="bills.length === 0" class="hsw-empty">暂无月账单</view>
</block>
</scroll-view>
<!-- 代客补差弹层 -->
<view class="hsw-mask" v-if="showExtra" @tap.self="showExtra = false">
<view class="hsw-dialog">
<view class="hsw-dialog-tit">代客生成补差单</view>
<input class="hsw-dialog-input" type="digit" v-model="extraAmount" placeholder="请输入补差金额" />
<input class="hsw-dialog-input" v-model="extraRemark" placeholder="备注(选填)" />
<view class="hsw-dialog-btns">
<view class="hsw-dialog-btn cancel" @tap="showExtra = false">取消</view>
<view class="hsw-dialog-btn ok" @tap="submitExtra">确定</view>
</view>
</view>
</view>
<!-- 推送月账单弹层 -->
<view class="hsw-mask" v-if="showPush" @tap.self="showPush = false">
<view class="hsw-dialog">
<view class="hsw-dialog-tit">推送月账单</view>
<view class="hsw-dialog-info">确认推送至用户端用户支付后该账单生效</view>
<view class="hsw-dialog-btns">
<view class="hsw-dialog-btn cancel" @tap="showPush = false">取消</view>
<view class="hsw-dialog-btn ok" @tap="submitPush">确认推送</view>
</view>
</view>
</view>
<!-- 拒绝月账单弹层 -->
<view class="hsw-mask" v-if="showReject" @tap.self="showReject = false">
<view class="hsw-dialog">
<view class="hsw-dialog-tit">拒绝月账单</view>
<input class="hsw-dialog-input" v-model="rejectRemark" placeholder="请输入拒绝原因(选填)" />
<view class="hsw-dialog-btns">
<view class="hsw-dialog-btn cancel" @tap="showReject = false">取消</view>
<view class="hsw-dialog-btn ok" @tap="submitReject">确认拒绝</view>
</view>
</view>
</view>
</view>
</template>
<script>
import { request } from '@/utils'
import { apiArr } from '@/api/homeService'
export default {
data() {
return {
employeeId: 0,
statusTab: 0,
orderTabs: [
{ v: 0, t: '全部' },
{ v: 1, t: '待上门' },
{ v: 2, t: '服务中' },
{ v: 4, t: '已完成' },
{ v: -1, t: '月账单' }
],
showMonthlyBill: false,
billTab: 0,
billTabs: [
{ v: 0, t: '全部' },
{ v: 1, t: '待推送' },
{ v: 2, t: '已推送' }
],
orders: [],
bills: [],
showExtra: false,
extraOrder: null,
extraAmount: '',
extraRemark: '',
showPush: false,
showReject: false,
pushBill: null,
rejectBill: null,
rejectRemark: ''
}
},
onLoad(options) {
this.employeeId = Number(options.employeeId || uni.getStorageSync('serviceEmployeeId') || 0)
if (!this.employeeId) {
const userId = uni.getStorageSync('userId')
request(apiArr.workerMyInfo, 'POST', { user_id: userId }, {}, false).then(res => {
if (res && res.is_worker && res.employee_id) {
this.employeeId = res.employee_id
uni.setStorageSync('serviceEmployeeId', res.employee_id)
this.loadOrders()
} else {
uni.showModal({ title: '提示', content: '您还不是服务师傅,请联系商家添加', showCancel: false })
}
})
}
},
onShow() {
if (this.employeeId) {
if (this.showMonthlyBill) {
this.loadBills()
} else {
this.loadOrders()
}
}
},
methods: {
switchStatus(v) {
if (v === -1) {
this.showMonthlyBill = true
this.billTab = 0
this.loadBills()
} else {
this.showMonthlyBill = false
this.statusTab = v
this.loadOrders()
}
},
switchBillTab(v) {
this.billTab = v
this.loadBills()
},
loadOrders() {
request(apiArr.workerOrderList, 'POST', {
employee_id: this.employeeId,
status: this.statusTab,
page_num: 1,
page_size: 50
}, {}, false).then(res => {
this.orders = res.rows || []
})
},
loadBills() {
request(apiArr.workerMonthlyBillList, 'POST', {
employee_id: this.employeeId,
user_id: Number(uni.getStorageSync('userId')) || 0,
page_num: 1,
page_size: 50
}, {}, false).then(res => {
let rows = res.rows || []
if (this.billTab === 1) rows = rows.filter(b => b.push_status === 0)
if (this.billTab === 2) rows = rows.filter(b => b.push_status === 1 || b.push_status === 2)
this.bills = rows
})
},
kindText(k) {
return k === 1 ? '定金/上门费' : k === 2 ? '补差/尾款' : k === 3 ? '月账单' : ''
},
pushStatusText(s) {
return s === 0 ? '待推送' : s === 1 ? '已推送待支付' : s === 2 ? '已支付' : s === 3 ? '已拒绝' : ''
},
updateStatus(o, status) {
request(apiArr.workerOrderStatus, 'POST', { order_id: o.id, status }).then(() => {
uni.showToast({ title: '操作成功', icon: 'none' })
this.loadOrders()
})
},
openExtra(o) {
this.extraOrder = o
this.extraAmount = ''
this.extraRemark = ''
this.showExtra = true
},
submitExtra() {
const amt = Number(this.extraAmount)
if (!amt || amt <= 0) { uni.showToast({ title: '请输入补差金额', icon: 'none' }); return }
request(apiArr.workerExtraOrder, 'POST', {
order_id: this.extraOrder.id,
amount: amt,
remark: this.extraRemark
}).then(() => {
this.showExtra = false
uni.showToast({ title: '已生成补差单,待客户支付', icon: 'none' })
this.loadOrders()
})
},
openPush(b) {
this.pushBill = b
this.showPush = true
},
submitPush() {
request(apiArr.workerPushBill, 'POST', {
bill_id: this.pushBill.id,
action: 1
}).then(() => {
this.showPush = false
uni.showToast({ title: '已推送,用户可支付', icon: 'none' })
this.loadBills()
}).catch(err => {
uni.showToast({ title: err.errMsg || '操作失败', icon: 'none' })
})
},
openReject(b) {
this.rejectBill = b
this.rejectRemark = ''
this.showReject = true
},
submitReject() {
request(apiArr.workerPushBill, 'POST', {
bill_id: this.rejectBill.id,
action: 3,
remark: this.rejectRemark
}).then(() => {
this.showReject = false
uni.showToast({ title: '已拒绝', icon: 'none' })
this.loadBills()
}).catch(err => {
uni.showToast({ title: err.errMsg || '操作失败', icon: 'none' })
})
}
}
}
</script>
<style scoped>
.hsw-page { display: flex; flex-direction: column; height: 100vh; background: #f5f5f5; }
.hsw-tabs { display: flex; background: #fff; }
.hsw-tab { flex: 1; text-align: center; padding: 24rpx 0; font-size: 26rpx; color: #666; }
.hsw-tab.active { color: #FF370B; font-weight: 600; border-bottom: 4rpx solid #FF370B; }
.hsw-tabs-bill { border-top: 1rpx solid #f0f0f0; }
.hsw-list { flex: 1; padding: 20rpx; box-sizing: border-box; }
.hsw-card { background: #fff; border-radius: 12rpx; padding: 20rpx; margin-bottom: 18rpx; }
.hsw-bill-card { border-left: 6rpx solid #FF370B; }
.hsw-head { display: flex; justify-content: space-between; font-size: 24rpx; color: #999; margin-bottom: 14rpx; }
.hsw-kind { color: #FF370B; }
.status-warn { color: #FF9800; }
.status-info { color: #2196F3; }
.status-ok { color: #4CAF50; }
.status-cancel { color: #999; }
.hsw-row { display: flex; justify-content: space-between; font-size: 26rpx; color: #333; padding: 8rpx 0; }
.hsw-row text:first-child { color: #999; }
.hsw-amount { color: #FF370B; font-weight: 600; }
.hsw-foot { display: flex; justify-content: flex-end; gap: 16rpx; margin-top: 16rpx; }
.hsw-btn { background: linear-gradient(91deg, #FF7658, #FF370B); color: #fff; font-size: 26rpx; padding: 12rpx 36rpx; border-radius: 40rpx; }
.hsw-btn.ghost { background: #fff; color: #FF370B; border: 1rpx solid #FF370B; }
.hsw-empty { text-align: center; color: #999; font-size: 26rpx; padding: 80rpx 0; }
.hsw-mask { position: fixed; inset: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 999; }
.hsw-dialog { width: 600rpx; background: #fff; border-radius: 16rpx; padding: 32rpx; }
.hsw-dialog-tit { font-size: 30rpx; font-weight: 600; text-align: center; margin-bottom: 24rpx; }
.hsw-dialog-info { font-size: 26rpx; color: #666; text-align: center; margin-bottom: 24rpx; }
.hsw-dialog-input { border: 1rpx solid #eee; border-radius: 8rpx; height: 80rpx; padding: 0 20rpx; font-size: 28rpx; margin-bottom: 18rpx; }
.hsw-dialog-btns { display: flex; gap: 20rpx; margin-top: 10rpx; }
.hsw-dialog-btn { flex: 1; text-align: center; padding: 18rpx 0; border-radius: 44rpx; font-size: 28rpx; }
.hsw-dialog-btn.cancel { background: #f2f2f2; color: #666; }
.hsw-dialog-btn.ok { background: linear-gradient(91deg, #FF7658, #FF370B); color: #fff; }
</style>