到家服务

This commit is contained in:
Daniel Kou 2026-06-12 11:02:01 +08:00
parent fa8d4cc38d
commit 3ed8bd5327
14 changed files with 1309 additions and 9 deletions

26
api/homeService.js Normal file
View File

@ -0,0 +1,26 @@
export const apiArr = {
// 到家服务
categoryList: "/api/v2/wechat/service/category/list", // 服务分类列表
serviceList: "/api/v2/wechat/service/list", // 服务商品列表
serviceInfo: "/api/v2/wechat/service/info", // 服务详情(含SKU+师傅)
orderCreate: "/api/v2/wechat/service/order/create", // 下单(定金单)
orderList: "/api/v2/wechat/service/order/list", // 我的服务订单列表
orderInfo: "/api/v2/wechat/service/order/info", // 服务订单详情
orderCancel: "/api/v2/wechat/service/order/cancel", // 取消服务订单
preorder: "/api/v2/wechat/service/order/preorder", // 预下单(拉卡拉)
tradeQuery: "/api/v2/wechat/service/order/trade-query", // 查单
// 师傅端
workerOrderList: "/api/v2/wechat/service/worker/order-list", // 师傅服务单列表
workerOrderStatus: "/api/v2/wechat/service/worker/order-status", // 师傅更新状态
workerExtraOrder: "/api/v2/wechat/service/worker/extra-order", // 师傅代客补差单
workerMyInfo: "/api/v2/wechat/service/worker/my-info", // 解析当前用户的师傅信息
// 家政合同(客户端)
contractList: "/api/v2/wechat/service/contract/list", // 我的家政合同列表
contractInfo: "/api/v2/wechat/service/contract/info", // 家政合同详情
// 月账单
workerMonthlyBillList: "/api/v2/wechat/service/worker/monthly-bill-list", // 师傅月账单列表
workerPushBill: "/api/v2/wechat/service/worker/push-bill", // 师傅推送/拒绝月账单
userMonthlyBillList: "/api/v2/wechat/service/user/monthly-bill-list", // 用户月账单列表
userMonthlyBillPreOrder: "/api/v2/wechat/service/user/monthly-bill/preorder", // 用户月账单预下单
userMonthlyBillTradeQuery: "/api/v2/wechat/service/user/monthly-bill/trade-query", // 用户月账单查单
}

View File

@ -91,6 +91,9 @@ export default {
this.currentCommunity = e
uni.setStorageSync('changeCommData', { id: e.community_id, name: e.name });
uni.setStorageSync('currentCommunityAddr', e.addr);
// room_name111103
const owner = (e.room_owner_list && e.room_owner_list.length) ? e.room_owner_list[0] : null
uni.setStorageSync('currentRoomNo', owner && owner.room_name ? owner.room_name : '');
NavgateTo("1")
},
},

View File

@ -0,0 +1,79 @@
<template>
<view class="hcd-page" v-if="info.id">
<view class="hcd-card">
<view class="hcd-status" :class="'st' + info.status">{{ statusText(info.status) }}</view>
<view class="hcd-name">{{ info.contract_name }}</view>
<view class="hcd-no">合同编号{{ info.contract_no }}</view>
</view>
<view class="hcd-block">
<view class="hcd-item"><text class="hcd-label">月薪</text><text class="hcd-val amount">¥{{ info.month_amount }}</text></view>
<view class="hcd-item"><text class="hcd-label">账单日</text><text class="hcd-val">每月 {{ info.pay_day }} </text></view>
<view class="hcd-item"><text class="hcd-label">结算方式</text><text class="hcd-val">{{ info.settle_mode === 1 ? '平台月账单' : '线下自付' }}</text></view>
<view class="hcd-item"><text class="hcd-label">合同开始</text><text class="hcd-val">{{ fmtDate(info.start_date) }}</text></view>
<view class="hcd-item"><text class="hcd-label">合同结束</text><text class="hcd-val">{{ fmtDate(info.end_date) }}</text></view>
<view class="hcd-item"><text class="hcd-label">联系人</text><text class="hcd-val">{{ info.contact_name || '-' }}</text></view>
<view class="hcd-item"><text class="hcd-label">联系电话</text><text class="hcd-val">{{ info.contact_phone || '-' }}</text></view>
<view class="hcd-item"><text class="hcd-label">服务地址</text><text class="hcd-val">{{ info.service_address || '-' }}</text></view>
<view class="hcd-item" v-if="info.remark"><text class="hcd-label">备注</text><text class="hcd-val">{{ info.remark }}</text></view>
</view>
<view class="hcd-block" v-if="info.file_url">
<view class="hcd-file" @tap="openFile">查看已签合同</view>
</view>
</view>
</template>
<script>
import { request, picUrl } from '@/utils'
import { apiArr } from '@/api/homeService'
export default {
data() {
return { picUrl, id: 0, info: {} }
},
onLoad(options) {
this.id = Number(options.id)
this.loadInfo()
},
methods: {
loadInfo() {
const userId = uni.getStorageSync('userId')
request(apiArr.contractInfo, 'POST', { id: this.id, user_id: userId }, {}, false).then(res => {
this.info = res || {}
})
},
statusText(s) {
const map = { 1: '待签署', 2: '生效中', 3: '已到期', 4: '已解除', 5: '线下结算' }
return map[s] || ''
},
fmtDate(v) {
return v ? String(v).slice(0, 10) : '-'
},
openFile() {
const url = this.info.file_url.startsWith('http') ? this.info.file_url : this.picUrl + this.info.file_url
uni.downloadFile({
url,
success: (d) => {
uni.openDocument({ filePath: d.tempFilePath, showMenu: true })
},
fail: () => uni.showToast({ title: '打开失败', icon: 'none' })
})
}
}
}
</script>
<style scoped>
.hcd-page { background: #f5f5f5; min-height: 100vh; padding: 20rpx; }
.hcd-card { background: #fff; border-radius: 16rpx; padding: 28rpx; margin-bottom: 20rpx; }
.hcd-status { font-size: 26rpx; color: #FF370B; margin-bottom: 12rpx; }
.hcd-status.st2 { color: #07c160; }
.hcd-status.st3, .hcd-status.st4 { color: #999; }
.hcd-name { font-size: 34rpx; color: #222; font-weight: 600; }
.hcd-no { font-size: 26rpx; color: #888; margin-top: 10rpx; }
.hcd-block { background: #fff; border-radius: 16rpx; padding: 8rpx 28rpx; margin-bottom: 20rpx; }
.hcd-item { display: flex; justify-content: space-between; padding: 22rpx 0; border-bottom: 1rpx solid #f2f2f2; }
.hcd-label { font-size: 28rpx; color: #888; }
.hcd-val { font-size: 28rpx; color: #222; max-width: 460rpx; text-align: right; }
.hcd-val.amount { color: #FF370B; font-weight: 600; }
.hcd-file { text-align: center; color: #2b6cff; font-size: 28rpx; padding: 24rpx 0; }
</style>

View File

@ -0,0 +1,152 @@
<template>
<view class="hsd-page">
<swiper class="hsd-swiper" autoplay circular v-if="carousel.length">
<swiper-item v-for="(p, i) in carousel" :key="i">
<image :src="p" mode="aspectFill" class="hsd-swiper-img" />
</swiper-item>
</swiper>
<image v-else :src="picUrl + (info.cover_pic || '')" class="hsd-swiper-img" mode="aspectFill" />
<view class="hsd-main">
<view class="hsd-name">{{ info.service_name }}</view>
<view class="hsd-pricedesc">{{ info.price_desc }}</view>
</view>
<!-- SKU 选择 -->
<view class="hsd-block" v-if="skuList.length">
<view class="hsd-block-tit">选择规格</view>
<view class="hsd-sku-list">
<view class="hsd-sku" :class="{ active: currentSku && currentSku.id === sku.id }"
v-for="sku in skuList" :key="sku.id" @tap="selectSku(sku)">
<view class="hsd-sku-name">{{ sku.sku_name }}</view>
<view class="hsd-sku-price" v-if="isHousekeeping">月薪 ¥{{ sku.month_salary }}</view>
<view class="hsd-sku-price" v-else>定金 ¥{{ sku.deposit }}</view>
</view>
</view>
</view>
<!-- 师傅选择 -->
<view class="hsd-block" v-if="workerList.length">
<view class="hsd-block-tit">选择师傅</view>
<view class="hsd-worker-list">
<view class="hsd-worker" :class="{ active: currentWorker && currentWorker.employee_id === w.employee_id }"
v-for="w in workerList" :key="w.employee_id" @tap="selectWorker(w)">
<image class="hsd-worker-img" :src="w.employee_image || defaultAvatar" mode="aspectFill" />
<view class="hsd-worker-name">{{ w.employee_name }}</view>
<view class="hsd-worker-exp">{{ w.expertise }}</view>
</view>
</view>
</view>
<!-- 详情图 -->
<view class="hsd-block" v-if="detailPics.length">
<view class="hsd-block-tit">服务详情</view>
<image v-for="(p, i) in detailPics" :key="i" :src="p" mode="widthFix" class="hsd-detail-img" />
</view>
<view class="hsd-bottom">
<template v-if="isHousekeeping">
<view class="hsd-bottom-price">月薪 <text>¥{{ currentSku ? currentSku.month_salary : '--' }}</text></view>
<view class="hsd-bottom-btn" @tap="goConsult">咨询预约</view>
</template>
<template v-else>
<view class="hsd-bottom-price">定金 <text>¥{{ currentSku ? currentSku.deposit : '--' }}</text></view>
<view class="hsd-bottom-btn" @tap="goOrder">立即预约</view>
</template>
</view>
</view>
</template>
<script>
import { request, picUrl, NavgateTo } from '@/utils'
import { apiArr } from '@/api/homeService'
export default {
data() {
return {
picUrl,
defaultAvatar: 'https://static.hshuishang.com/defaultTx.png',
id: 0,
info: {},
carousel: [],
detailPics: [],
skuList: [],
workerList: [],
currentSku: null,
currentWorker: null
}
},
onLoad(options) {
this.id = Number(options.id)
this.loadInfo()
},
computed: {
// ( service_type=2)
isHousekeeping() {
return Number(this.info.service_type) === 2
}
},
methods: {
loadInfo() {
request(apiArr.serviceInfo, 'POST', { id: this.id }, {}, false).then(res => {
this.info = res || {}
this.carousel = (res.carousel ? res.carousel.split(',') : []).filter(Boolean).map(p => p.startsWith('http') ? p : this.picUrl + p)
this.detailPics = (res.detail_pic ? res.detail_pic.split(',') : []).filter(Boolean).map(p => p.startsWith('http') ? p : this.picUrl + p)
this.skuList = res.sku_list || []
this.workerList = res.worker_list || []
if (this.skuList.length) this.currentSku = this.skuList[0]
if (this.workerList.length) this.currentWorker = this.workerList[0]
})
},
selectSku(sku) { this.currentSku = sku },
selectWorker(w) { this.currentWorker = w },
goOrder() {
if (!this.currentSku) { uni.showToast({ title: '请选择规格', icon: 'none' }); return }
if (!this.currentWorker) { uni.showToast({ title: '请选择师傅', icon: 'none' }); return }
const params = {
service_info_id: this.id,
sku_id: this.currentSku.id,
employee_id: this.currentWorker.employee_id,
service_name: this.info.service_name,
deposit: this.currentSku.deposit
}
NavgateTo('/packages/homeService/order/index?params=' + encodeURIComponent(JSON.stringify(params)))
},
//
goConsult() {
const supplierId = this.info.supplier_id
if (!supplierId) { uni.showToast({ title: '商家信息缺失', icon: 'none' }); return }
// merchantInfo.id
const mi = uni.getStorageSync('merchantInfo') || {}
mi.id = supplierId
uni.setStorageSync('merchantInfo', mi)
NavgateTo('/packages/customerService/changeService/index')
}
}
}
</script>
<style scoped>
.hsd-page { background: #f5f5f5; min-height: 100vh; padding-bottom: 130rpx; }
.hsd-swiper, .hsd-swiper-img { width: 100%; height: 520rpx; }
.hsd-main { background: #fff; padding: 24rpx; }
.hsd-name { font-size: 36rpx; font-weight: 600; color: #222; }
.hsd-pricedesc { font-size: 26rpx; color: #FF370B; margin-top: 14rpx; }
.hsd-block { background: #fff; margin-top: 16rpx; padding: 24rpx; }
.hsd-block-tit { font-size: 30rpx; font-weight: 600; color: #222; margin-bottom: 18rpx; }
.hsd-sku-list, .hsd-worker-list { display: flex; flex-wrap: wrap; gap: 18rpx; }
.hsd-sku { border: 1rpx solid #eee; border-radius: 10rpx; padding: 16rpx 24rpx; }
.hsd-sku.active { border-color: #FF370B; background: #FFF1ED; }
.hsd-sku-name { font-size: 28rpx; color: #222; }
.hsd-sku-price { font-size: 24rpx; color: #FF370B; margin-top: 8rpx; }
.hsd-worker { width: 160rpx; display: flex; flex-direction: column; align-items: center; border: 1rpx solid #eee; border-radius: 10rpx; padding: 16rpx 8rpx; }
.hsd-worker.active { border-color: #FF370B; background: #FFF1ED; }
.hsd-worker-img { width: 100rpx; height: 100rpx; border-radius: 50%; background: #f0f0f0; }
.hsd-worker-name { font-size: 26rpx; color: #222; margin-top: 10rpx; }
.hsd-worker-exp { font-size: 20rpx; color: #999; margin-top: 4rpx; }
.hsd-detail-img { width: 100%; display: block; }
.hsd-bottom { position: fixed; left: 0; right: 0; bottom: 0; background: #fff; display: flex; align-items: center; padding: 16rpx 24rpx calc(16rpx + env(safe-area-inset-bottom)); box-shadow: 0 -4rpx 20rpx rgba(0,0,0,0.04); }
.hsd-bottom-price { flex: 1; font-size: 26rpx; color: #333; }
.hsd-bottom-price text { color: #FF370B; font-size: 36rpx; font-weight: 600; }
.hsd-bottom-btn { background: linear-gradient(91deg, #FF7658, #FF370B); color: #fff; font-size: 30rpx; padding: 20rpx 60rpx; border-radius: 44rpx; }
</style>

View File

@ -0,0 +1,124 @@
<template>
<view class="hs-page">
<!-- 到家/家政 Tab -->
<view class="hs-tabs">
<view class="hs-tab" :class="{ active: serviceType === 1 }" @tap="switchType(1)">到家服务</view>
<view class="hs-tab" :class="{ active: serviceType === 2 }" @tap="switchType(2)">家政服务</view>
</view>
<view class="hs-body">
<!-- 左侧分类 -->
<scroll-view class="hs-cate" scroll-y>
<view class="hs-cate-item" :class="{ active: currentCate === 0 }" @tap="selectCate(0)">全部</view>
<view class="hs-cate-item" :class="{ active: currentCate === item.id }"
v-for="item in categoryList" :key="item.id" @tap="selectCate(item.id)">
{{ item.category_name }}
</view>
</scroll-view>
<!-- 右侧服务列表 -->
<scroll-view class="hs-list" scroll-y @scrolltolower="loadMore">
<view class="hs-card" v-for="item in serviceList" :key="item.id" @tap="toDetail(item)">
<image class="hs-card-pic" :src="picUrl + item.cover_pic" mode="aspectFill" />
<view class="hs-card-info">
<view class="hs-card-name">{{ item.service_name }}</view>
<view class="hs-card-desc">{{ item.price_desc }}</view>
<view class="hs-card-foot">
<text class="hs-card-tag">{{ serviceType === 1 ? '到家' : '家政' }}</text>
<text class="hs-card-btn">查看</text>
</view>
</view>
</view>
<view v-if="serviceList.length === 0" class="hs-empty">暂无服务</view>
</scroll-view>
</view>
</view>
</template>
<script>
import { request, picUrl, NavgateTo } from '@/utils'
import { apiArr } from '@/api/homeService'
export default {
data() {
return {
picUrl,
serviceType: 1,
categoryList: [],
currentCate: 0,
serviceList: [],
pageNum: 1,
pageSize: 10,
hasMore: true
}
},
onLoad() {
this.loadCategory()
this.loadList(true)
},
methods: {
switchType(t) {
if (this.serviceType === t) return
this.serviceType = t
this.currentCate = 0
this.loadCategory()
this.loadList(true)
},
selectCate(id) {
this.currentCate = id
this.loadList(true)
},
loadCategory() {
request(apiArr.categoryList, 'POST', { service_type: this.serviceType }, {}, false).then(res => {
this.categoryList = res.rows || []
})
},
loadList(reset) {
if (reset) {
this.pageNum = 1
this.hasMore = true
this.serviceList = []
}
if (!this.hasMore) return
request(apiArr.serviceList, 'POST', {
service_type: this.serviceType,
category_id: this.currentCate,
page_num: this.pageNum,
page_size: this.pageSize
}, {}, false).then(res => {
const rows = res.rows || []
this.serviceList = this.serviceList.concat(rows)
this.hasMore = rows.length === this.pageSize
this.pageNum++
})
},
loadMore() {
this.loadList(false)
},
toDetail(item) {
NavgateTo('/packages/homeService/detail/index?id=' + item.id)
}
}
}
</script>
<style scoped>
.hs-page { display: flex; flex-direction: column; height: 100vh; background: #f5f5f5; }
.hs-tabs { display: flex; background: #fff; }
.hs-tab { flex: 1; text-align: center; padding: 26rpx 0; font-size: 28rpx; color: #666; }
.hs-tab.active { color: #FF370B; font-weight: 600; border-bottom: 4rpx solid #FF370B; }
.hs-body { flex: 1; display: flex; overflow: hidden; }
.hs-cate { width: 180rpx; background: #fafafa; height: 100%; }
.hs-cate-item { padding: 28rpx 16rpx; font-size: 26rpx; color: #555; text-align: center; }
.hs-cate-item.active { background: #fff; color: #FF370B; font-weight: 600; }
.hs-list { flex: 1; padding: 20rpx; box-sizing: border-box; }
.hs-card { display: flex; background: #fff; border-radius: 12rpx; padding: 16rpx; margin-bottom: 16rpx; }
.hs-card-pic { width: 200rpx; height: 200rpx; border-radius: 8rpx; flex-shrink: 0; background: #f0f0f0; }
.hs-card-info { flex: 1; margin-left: 18rpx; display: flex; flex-direction: column; min-width: 0; }
.hs-card-name { font-size: 30rpx; color: #222; font-weight: 600; }
.hs-card-desc { font-size: 24rpx; color: #999; margin-top: 12rpx; flex: 1; }
.hs-card-foot { display: flex; align-items: center; justify-content: space-between; }
.hs-card-tag { font-size: 20rpx; color: #FF370B; border: 1rpx solid #FFD9CD; border-radius: 6rpx; padding: 2rpx 10rpx; }
.hs-card-btn { background: linear-gradient(91deg, #FF7658, #FF370B); color: #fff; font-size: 24rpx; padding: 8rpx 28rpx; border-radius: 30rpx; }
.hs-empty { text-align: center; color: #999; font-size: 26rpx; padding: 80rpx 0; }
</style>

View File

@ -0,0 +1,66 @@
<template>
<view class="hsc-page">
<view class="hsc-list">
<view class="hsc-card" v-for="c in contracts" :key="c.id" @tap="toDetail(c)">
<view class="hsc-head">
<text class="hsc-no">{{ c.contract_no }}</text>
<text class="hsc-status" :class="'st' + c.status">{{ statusText(c.status) }}</text>
</view>
<view class="hsc-body">
<view class="hsc-name">{{ c.contract_name }}</view>
<view class="hsc-row">月薪<text class="hsc-amount">¥{{ c.month_amount }}</text></view>
<view class="hsc-row">结算{{ c.settle_mode === 1 ? '平台月账单' : '线下自付' }}</view>
<view class="hsc-row">合同期{{ fmtDate(c.start_date) }} ~ {{ fmtDate(c.end_date) }}</view>
</view>
</view>
<view v-if="contracts.length === 0" class="hsc-empty">暂无家政合同</view>
</view>
</view>
</template>
<script>
import { request, NavgateTo } from '@/utils'
import { apiArr } from '@/api/homeService'
export default {
data() {
return { contracts: [] }
},
onShow() {
this.loadContracts()
},
methods: {
loadContracts() {
const userId = uni.getStorageSync('userId')
request(apiArr.contractList, 'POST', { user_id: userId, page_num: 1, page_size: 50 }, {}, false).then(res => {
this.contracts = res.rows || []
})
},
statusText(s) {
const map = { 1: '待签署', 2: '生效中', 3: '已到期', 4: '已解除', 5: '线下结算' }
return map[s] || ''
},
fmtDate(v) {
return v ? String(v).slice(0, 10) : ''
},
toDetail(c) {
NavgateTo('/packages/homeService/contractDetail/index?id=' + c.id)
}
}
}
</script>
<style scoped>
.hsc-page { background: #f5f5f5; min-height: 100vh; padding: 20rpx; }
.hsc-card { background: #fff; border-radius: 16rpx; padding: 24rpx; margin-bottom: 20rpx; }
.hsc-head { display: flex; justify-content: space-between; align-items: center; border-bottom: 1rpx solid #f2f2f2; padding-bottom: 16rpx; }
.hsc-no { font-size: 26rpx; color: #888; }
.hsc-status { font-size: 26rpx; color: #FF370B; }
.hsc-status.st2 { color: #07c160; }
.hsc-status.st3, .hsc-status.st4 { color: #999; }
.hsc-body { padding-top: 16rpx; }
.hsc-name { font-size: 30rpx; color: #222; font-weight: 600; margin-bottom: 12rpx; }
.hsc-row { font-size: 26rpx; color: #555; margin-top: 8rpx; }
.hsc-amount { color: #FF370B; font-weight: 600; }
.hsc-empty { text-align: center; color: #999; font-size: 28rpx; padding-top: 120rpx; }
</style>

View File

@ -0,0 +1,95 @@
<template>
<view class="hmb-page">
<view class="hmb-list">
<view class="hmb-card" v-for="b in bills" :key="b.id">
<view class="hmb-head">
<text class="hmb-no">{{ b.bill_no }}</text>
<text class="hmb-status" :class="b.pay_status === 1 ? 'status-warn' : b.pay_status === 2 ? 'status-ok' : ''">{{ statusText(b.pay_status) }}</text>
</view>
<view class="hmb-body" @tap="toContract(b)">
<image class="hmb-pic" :src="picUrl + (b.service_pic || '')" mode="aspectFill" />
<view class="hmb-info">
<view class="hmb-name">{{ b.service_name }}</view>
<view class="hmb-month">{{ b.bill_month }} 月度账单</view>
<view class="hmb-amount">¥{{ b.amount }}</view>
</view>
</view>
<view class="hmb-foot">
<view class="hmb-link" @tap.stop="toContract(b)">查看合同 </view>
<view class="hmb-pay" v-if="b.pay_status === 1" @tap.stop="payBill(b)">去支付</view>
</view>
</view>
<view v-if="bills.length === 0" class="hmb-empty">暂无待支付月账单</view>
</view>
</view>
</template>
<script>
import { request, picUrl, NavgateTo } from '@/utils'
import { apiArr } from '@/api/homeService'
export default {
data() {
return { picUrl, bills: [] }
},
onShow() {
this.loadBills()
},
methods: {
loadBills() {
const userId = uni.getStorageSync('userId')
request(apiArr.userMonthlyBillList, 'POST', { user_id: userId, page_num: 1, page_size: 50 }, {}, false).then(res => {
this.bills = res.rows || []
})
},
statusText(payStatus) {
return payStatus === 1 ? '待支付' : payStatus === 2 ? '已支付' : payStatus === 3 ? '已退款' : ''
},
toContract(b) {
NavgateTo('/packages/homeService/contractDetail/index?id=' + b.contract_id)
},
payBill(b) {
const userId = uni.getStorageSync('userId')
request(apiArr.userMonthlyBillPreOrder, 'POST', { bill_id: b.id, user_id: userId, trans_type: '71' }).then(pay => {
uni.requestPayment({
provider: 'wxpay',
timeStamp: pay.timeStamp,
nonceStr: pay.nonceStr,
package: pay.package,
signType: pay.signType || 'RSA',
paySign: pay.paySign,
success: () => {
request(apiArr.userMonthlyBillTradeQuery, 'POST', { bill_id: b.id }, {}, false).finally(() => {
uni.showToast({ title: '支付成功', icon: 'none' })
this.loadBills()
})
},
fail: () => { uni.showToast({ title: '支付已取消', icon: 'none' }) }
})
}).catch(err => {
uni.showToast({ title: err.errMsg || '发起支付失败', icon: 'none' })
})
}
}
}
</script>
<style scoped>
.hmb-page { background: #f5f5f5; min-height: 100vh; padding: 20rpx; box-sizing: border-box; }
.hmb-list {}
.hmb-card { background: #fff; border-radius: 12rpx; padding: 20rpx; margin-bottom: 18rpx; border-left: 6rpx solid #FF370B; }
.hmb-head { display: flex; justify-content: space-between; align-items: center; font-size: 24rpx; color: #999; margin-bottom: 16rpx; }
.hmb-status { font-size: 26rpx; }
.status-warn { color: #FF9800; }
.status-ok { color: #4CAF50; }
.hmb-body { display: flex; }
.hmb-pic { width: 150rpx; height: 150rpx; border-radius: 8rpx; background: #f0f0f0; flex-shrink: 0; }
.hmb-info { flex: 1; margin-left: 18rpx; display: flex; flex-direction: column; }
.hmb-name { font-size: 30rpx; color: #222; font-weight: 600; }
.hmb-month { font-size: 24rpx; color: #666; margin-top: 8rpx; }
.hmb-amount { font-size: 32rpx; color: #FF370B; font-weight: 600; margin-top: auto; }
.hmb-foot { display: flex; justify-content: space-between; align-items: center; margin-top: 16rpx; }
.hmb-link { font-size: 24rpx; color: #666; }
.hmb-pay { background: linear-gradient(91deg, #FF7658, #FF370B); color: #fff; font-size: 26rpx; padding: 12rpx 40rpx; border-radius: 40rpx; margin-left: auto; }
.hmb-empty { text-align: center; color: #999; font-size: 26rpx; padding: 80rpx 0; }
</style>

View File

@ -0,0 +1,108 @@
<template>
<view class="hsm-page">
<view class="hsm-list">
<view class="hsm-card" v-for="o in orders" :key="o.id">
<view class="hsm-head">
<text class="hsm-no">{{ o.order_no }}</text>
<text class="hsm-status">{{ statusText(o) }}</text>
</view>
<view class="hsm-body" @tap="toDetail(o)">
<image class="hsm-pic" :src="picUrl + (o.service_pic || '')" mode="aspectFill" />
<view class="hsm-info">
<view class="hsm-name">{{ o.service_name }}</view>
<view class="hsm-kind">{{ kindText(o.order_kind) }}</view>
<view class="hsm-amount">¥{{ o.amount }}</view>
</view>
</view>
<view class="hsm-foot" v-if="o.pay_status === 1 && o.status !== 5 && o.status !== 4">
<view class="hsm-cancel" @tap="cancelOrder(o)">取消订单</view>
<view class="hsm-pay" @tap="payOrder(o)">去支付</view>
</view>
</view>
<view v-if="orders.length === 0" class="hsm-empty">暂无服务订单</view>
</view>
</view>
</template>
<script>
import { request, picUrl, NavgateTo } from '@/utils'
import { apiArr } from '@/api/homeService'
export default {
data() {
return { picUrl, orders: [] }
},
onShow() {
this.loadOrders()
},
methods: {
loadOrders() {
const userId = uni.getStorageSync('userId')
request(apiArr.orderList, 'POST', { user_id: userId, page_num: 1, page_size: 50 }, {}, false).then(res => {
this.orders = res.rows || []
})
},
kindText(k) {
return k === 1 ? '定金/上门费' : k === 2 ? '补差/尾款' : k === 3 ? '月账单' : ''
},
statusText(o) {
if (o.status === 5) return '已取消'
if (o.status === 4) return '已完成'
if (o.pay_status === 1) return '待支付'
const map = { 1: '待上门', 2: '服务中', 3: '待补款' }
return map[o.status] || ''
},
toDetail(o) {
NavgateTo('/packages/homeService/orderDetail/index?id=' + o.id)
},
cancelOrder(o) {
uni.showModal({
title: '提示',
content: '确定取消该订单吗?',
success: (r) => {
if (!r.confirm) return
const userId = uni.getStorageSync('userId')
request(apiArr.orderCancel, 'POST', { id: o.id, user_id: Number(userId) || 0 }).then(() => {
uni.showToast({ title: '已取消', icon: 'none' })
this.loadOrders()
})
}
})
},
payOrder(o) {
const userId = uni.getStorageSync('userId')
request(apiArr.preorder, 'POST', { order_id: o.id, user_id: userId, trans_type: '71' }).then(pay => {
uni.requestPayment({
provider: 'wxpay',
timeStamp: pay.timeStamp,
nonceStr: pay.nonceStr,
package: pay.package,
signType: pay.signType || 'RSA',
paySign: pay.paySign,
success: () => {
request(apiArr.tradeQuery, 'POST', { order_id: o.id }, {}, false).finally(() => this.loadOrders())
},
fail: () => { uni.showToast({ title: '支付已取消', icon: 'none' }) }
})
})
}
}
}
</script>
<style scoped>
.hsm-page { background: #f5f5f5; min-height: 100vh; padding: 20rpx; box-sizing: border-box; }
.hsm-card { background: #fff; border-radius: 12rpx; padding: 20rpx; margin-bottom: 18rpx; }
.hsm-head { display: flex; justify-content: space-between; align-items: center; font-size: 24rpx; color: #999; margin-bottom: 16rpx; }
.hsm-status { color: #FF370B; }
.hsm-body { display: flex; }
.hsm-pic { width: 150rpx; height: 150rpx; border-radius: 8rpx; background: #f0f0f0; flex-shrink: 0; }
.hsm-info { flex: 1; margin-left: 18rpx; display: flex; flex-direction: column; }
.hsm-name { font-size: 30rpx; color: #222; font-weight: 600; }
.hsm-kind { font-size: 24rpx; color: #999; margin-top: 10rpx; flex: 1; }
.hsm-amount { font-size: 32rpx; color: #FF370B; font-weight: 600; }
.hsm-foot { display: flex; justify-content: flex-end; align-items: center; margin-top: 16rpx; }
.hsm-cancel { border: 1rpx solid #ccc; color: #666; font-size: 26rpx; padding: 11rpx 36rpx; border-radius: 40rpx; margin-right: 20rpx; }
.hsm-pay { background: linear-gradient(91deg, #FF7658, #FF370B); color: #fff; font-size: 26rpx; padding: 12rpx 40rpx; border-radius: 40rpx; }
.hsm-empty { text-align: center; color: #999; font-size: 26rpx; padding: 80rpx 0; }
</style>

View File

@ -0,0 +1,221 @@
<template>
<view class="hso-page">
<view class="hso-card">
<view class="hso-svc">{{ params.service_name }}</view>
<view class="hso-deposit">定金 ¥{{ params.deposit }}</view>
</view>
<view class="hso-form">
<view class="hso-row">
<text class="hso-label">联系人</text>
<input class="hso-input" v-model="contactName" placeholder="请输入联系人" />
</view>
<view class="hso-row">
<text class="hso-label">联系电话</text>
<input class="hso-input" type="number" v-model="contactPhone" placeholder="请输入联系电话" maxlength="11" />
</view>
<view class="hso-row">
<text class="hso-label">小区地址</text>
<input class="hso-input" v-model="serviceAddress" placeholder="请选择或输入小区地址" />
<text class="hso-map-btn" @tap="chooseLocation">地图选点</text>
</view>
<view class="hso-row">
<text class="hso-label">门牌号</text>
<input class="hso-input" v-model="houseNumber" placeholder="如1栋1单元103室" />
</view>
<view class="hso-row" @tap="showDate = true">
<text class="hso-label">上门时间</text>
<text class="hso-input" :class="{ ph: !serviceTime }">{{ serviceTime || '请选择上门时间' }}</text>
</view>
<view class="hso-row hso-row--col">
<text class="hso-label">备注</text>
<textarea class="hso-textarea" v-model="remark" placeholder="选填" />
</view>
</view>
<view class="hso-bottom">
<view class="hso-bottom-price">定金 <text>¥{{ params.deposit }}</text></view>
<view class="hso-bottom-btn" @tap="submit">提交并支付</view>
</view>
<u-datetime-picker :show="showDate" :value="dateValue" mode="datetime" @confirm="onDate" @cancel="showDate = false" @close="showDate = false" />
</view>
</template>
<script>
import { request, NavgateTo, isPhone } from '@/utils'
import { apiArr } from '@/api/homeService'
import { apiArr as commApi } from '@/api/community'
export default {
data() {
return {
params: {},
contactName: '',
contactPhone: '',
serviceAddress: '',
houseNumber: '',
serviceTime: '',
dateValue: Date.now(),
remark: '',
showDate: false,
submitting: false
}
},
onLoad(options) {
if (options.params) {
try { this.params = JSON.parse(decodeURIComponent(options.params)) } catch (e) { this.params = {} }
}
//
this.fillFromStorage()
this.loadBoundHouse()
// 3
this.initDefaultTime()
},
methods: {
// +
fillFromStorage() {
const addr = uni.getStorageSync('currentCommunityAddr')
const comm = uni.getStorageSync('changeCommData')
const commName = (comm && comm.name) ? comm.name : ''
if (addr) {
this.serviceAddress = (addr + ' ' + commName).trim()
} else if (commName) {
this.serviceAddress = commName
}
const roomNo = uni.getStorageSync('currentRoomNo')
if (roomNo) this.houseNumber = roomNo
},
// + room_name
loadBoundHouse() {
const loc = uni.getStorageSync('location') || {}
const comm = uni.getStorageSync('changeCommData')
request(commApi.commInfo, 'POST', {
user_id: uni.getStorageSync('userId'),
longitude: loc.lng,
latitude: loc.lat,
page_num: 1,
page_size: 20
}, { silent: false }).then(res => {
const rows = (res && res.rows) ? res.rows : []
if (!rows.length) return
//
let target = null
if (comm && comm.id) target = rows.find(r => r.community_id === comm.id)
if (!target) target = rows[0]
const commName = target.name || ''
if (target.addr) {
this.serviceAddress = (target.addr + ' ' + commName).trim()
} else if (commName) {
this.serviceAddress = commName
}
const owner = (target.room_owner_list && target.room_owner_list.length) ? target.room_owner_list[0] : null
if (owner && owner.room_name) this.houseNumber = owner.room_name
}).catch(() => {})
},
// = + 3
initDefaultTime() {
const d = new Date()
d.setHours(d.getHours() + 3)
// //
if (d.getMinutes() > 0 || d.getSeconds() > 0 || d.getMilliseconds() > 0) {
d.setHours(d.getHours() + 1)
}
d.setMinutes(0, 0, 0)
this.dateValue = d.getTime()
this.serviceTime = this.formatTime(d)
},
formatTime(d) {
const p = n => String(n).padStart(2, '0')
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`
},
// /
chooseLocation() {
const loc = uni.getStorageSync('location') || {}
uni.chooseLocation({
latitude: loc.lat,
longitude: loc.lng,
success: (res) => {
const detail = res.name ? (res.address + ' ' + res.name) : res.address
this.serviceAddress = (detail || '').trim()
},
fail: (err) => {
if (err && err.errMsg && err.errMsg.indexOf('cancel') === -1) {
uni.showToast({ title: '地图选点失败,请检查定位权限', icon: 'none' })
}
}
})
},
onDate(e) {
const d = new Date(e.value)
this.dateValue = d.getTime()
this.serviceTime = this.formatTime(d)
this.showDate = false
},
submit() {
if (!this.contactName) { uni.showToast({ title: '请输入联系人', icon: 'none' }); return }
if (!isPhone(this.contactPhone)) { uni.showToast({ title: '联系电话格式不正确', icon: 'none' }); return }
if (!this.serviceAddress) { uni.showToast({ title: '请选择或输入小区地址', icon: 'none' }); return }
if (!this.houseNumber) { uni.showToast({ title: '请输入门牌号', icon: 'none' }); return }
if (this.submitting) return
this.submitting = true
const userId = uni.getStorageSync('userId')
const fullAddress = (this.serviceAddress + ' ' + this.houseNumber).trim()
request(apiArr.orderCreate, 'POST', {
user_id: userId,
service_info_id: this.params.service_info_id,
sku_id: this.params.sku_id,
employee_id: this.params.employee_id,
service_address: fullAddress,
contact_name: this.contactName,
contact_phone: this.contactPhone,
service_time: this.serviceTime,
remark: this.remark
}).then(res => {
this.payOrder(res.id, userId)
}).catch(() => { this.submitting = false })
},
payOrder(orderId, userId) {
request(apiArr.preorder, 'POST', { order_id: orderId, user_id: userId, trans_type: '71' }).then(pay => {
uni.requestPayment({
provider: 'wxpay',
timeStamp: pay.timeStamp,
nonceStr: pay.nonceStr,
package: pay.package,
signType: pay.signType || 'RSA',
paySign: pay.paySign,
success: () => {
//
request(apiArr.tradeQuery, 'POST', { order_id: orderId }, {}, false).finally(() => {
uni.redirectTo({ url: '/packages/homeService/myOrders/index' })
})
},
fail: () => {
this.submitting = false
uni.showToast({ title: '支付已取消', icon: 'none' })
uni.redirectTo({ url: '/packages/homeService/myOrders/index' })
}
})
}).catch(() => { this.submitting = false })
}
}
}
</script>
<style scoped>
.hso-page { background: #f5f5f5; min-height: 100vh; padding-bottom: 130rpx; }
.hso-card { background: #fff; padding: 28rpx 24rpx; display: flex; align-items: center; justify-content: space-between; }
.hso-svc { font-size: 30rpx; color: #222; font-weight: 600; }
.hso-deposit { font-size: 30rpx; color: #FF370B; font-weight: 600; }
.hso-form { background: #fff; margin-top: 16rpx; padding: 0 24rpx; }
.hso-row { display: flex; align-items: center; padding: 26rpx 0; border-bottom: 1rpx solid #f2f2f2; }
.hso-row--col { flex-direction: column; align-items: flex-start; }
.hso-label { width: 160rpx; font-size: 28rpx; color: #333; flex-shrink: 0; }
.hso-input { flex: 1; font-size: 28rpx; color: #222; }
.hso-input.ph { color: #b7b7b7; }
.hso-map-btn { flex-shrink: 0; margin-left: 16rpx; font-size: 26rpx; color: #FF370B; padding: 6rpx 16rpx; border: 1rpx solid #FF370B; border-radius: 28rpx; }
.hso-textarea { width: 100%; height: 120rpx; font-size: 28rpx; margin-top: 12rpx; }
.hso-bottom { position: fixed; left: 0; right: 0; bottom: 0; background: #fff; display: flex; align-items: center; padding: 16rpx 24rpx calc(16rpx + env(safe-area-inset-bottom)); box-shadow: 0 -4rpx 20rpx rgba(0,0,0,0.04); }
.hso-bottom-price { flex: 1; font-size: 26rpx; color: #333; }
.hso-bottom-price text { color: #FF370B; font-size: 36rpx; font-weight: 600; }
.hso-bottom-btn { background: linear-gradient(91deg, #FF7658, #FF370B); color: #fff; font-size: 30rpx; padding: 20rpx 60rpx; border-radius: 44rpx; }
</style>

View File

@ -0,0 +1,66 @@
<template>
<view class="hsod-page" v-if="order.id">
<view class="hsod-status">{{ statusText }}</view>
<view class="hsod-card">
<image class="hsod-pic" :src="picUrl + (order.service_pic || '')" mode="aspectFill" />
<view class="hsod-info">
<view class="hsod-name">{{ order.service_name }}</view>
<view class="hsod-kind">{{ kindText(order.order_kind) }}</view>
<view class="hsod-amount">¥{{ order.amount }}</view>
</view>
</view>
<view class="hsod-block">
<view class="hsod-row"><text>订单号</text><text>{{ order.order_no }}</text></view>
<view class="hsod-row"><text>联系人</text><text>{{ order.contact_name }}</text></view>
<view class="hsod-row"><text>联系电话</text><text>{{ order.contact_phone }}</text></view>
<view class="hsod-row"><text>服务地址</text><text>{{ order.service_address }}</text></view>
<view class="hsod-row" v-if="order.service_time"><text>上门时间</text><text>{{ order.service_time }}</text></view>
<view class="hsod-row" v-if="order.remark"><text>备注</text><text>{{ order.remark }}</text></view>
<view class="hsod-row"><text>支付状态</text><text>{{ order.pay_status === 2 ? '已支付' : '待支付' }}</text></view>
</view>
</view>
</template>
<script>
import { request, picUrl } from '@/utils'
import { apiArr } from '@/api/homeService'
export default {
data() {
return { picUrl, order: {} }
},
onLoad(options) {
request(apiArr.orderInfo, 'POST', { id: Number(options.id) }, {}, false).then(res => {
this.order = res || {}
})
},
computed: {
statusText() {
const o = this.order
if (o.pay_status === 1) return '待支付'
const map = { 1: '待上门', 2: '服务中', 3: '待补款', 4: '已完成', 5: '已取消' }
return map[o.status] || ''
}
},
methods: {
kindText(k) {
return k === 1 ? '定金/上门费' : k === 2 ? '补差/尾款' : k === 3 ? '月账单' : ''
}
}
}
</script>
<style scoped>
.hsod-page { background: #f5f5f5; min-height: 100vh; }
.hsod-status { background: linear-gradient(91deg, #FF7658, #FF370B); color: #fff; font-size: 34rpx; font-weight: 600; padding: 40rpx 24rpx; }
.hsod-card { background: #fff; display: flex; padding: 24rpx; margin-top: 16rpx; }
.hsod-pic { width: 150rpx; height: 150rpx; border-radius: 8rpx; background: #f0f0f0; flex-shrink: 0; }
.hsod-info { flex: 1; margin-left: 18rpx; display: flex; flex-direction: column; }
.hsod-name { font-size: 30rpx; color: #222; font-weight: 600; }
.hsod-kind { font-size: 24rpx; color: #999; margin-top: 10rpx; flex: 1; }
.hsod-amount { font-size: 32rpx; color: #FF370B; font-weight: 600; }
.hsod-block { background: #fff; margin-top: 16rpx; padding: 8rpx 24rpx; }
.hsod-row { display: flex; justify-content: space-between; font-size: 26rpx; color: #333; padding: 22rpx 0; border-bottom: 1rpx solid #f5f5f5; }
.hsod-row text:first-child { color: #999; }
.hsod-row:last-child { border-bottom: none; }
</style>

View File

@ -0,0 +1,307 @@
<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>

View File

@ -134,6 +134,47 @@
}
]
},
{
"root": "packages/homeService",
"pages": [
{
"path": "index/index",
"style": { "navigationBarTitleText": "到家服务", "navigationBarBackgroundColor": "#fff" }
},
{
"path": "detail/index",
"style": { "navigationBarTitleText": "服务详情", "navigationBarBackgroundColor": "#fff" }
},
{
"path": "order/index",
"style": { "navigationBarTitleText": "确认预约", "navigationBarBackgroundColor": "#fff" }
},
{
"path": "myOrders/index",
"style": { "navigationBarTitleText": "我的服务订单", "navigationBarBackgroundColor": "#fff" }
},
{
"path": "orderDetail/index",
"style": { "navigationBarTitleText": "订单详情", "navigationBarBackgroundColor": "#fff" }
},
{
"path": "worker/index",
"style": { "navigationBarTitleText": "我的接单", "navigationBarBackgroundColor": "#fff" }
},
{
"path": "myContracts/index",
"style": { "navigationBarTitleText": "我的家政合同", "navigationBarBackgroundColor": "#fff" }
},
{
"path": "contractDetail/index",
"style": { "navigationBarTitleText": "合同详情", "navigationBarBackgroundColor": "#fff" }
},
{
"path": "myMonthlyBills/index",
"style": { "navigationBarTitleText": "我的月账单", "navigationBarBackgroundColor": "#fff" }
}
]
},
{
"root": "packages/customerService",
"pages": [

View File

@ -92,7 +92,7 @@
</view>
</view>
<!--view class="main margin20">
<view class="main margin20">
<view class="main_title">到家服务</view>
<view class="item1 padding_bottom0">
<u-grid col="4" :border="false">
@ -102,7 +102,7 @@
</u-grid-item>
</u-grid>
</view>
</view-->
</view>
<view class="main margin20">
<view class="main_title">门店服务</view>
<view class="item1 padding_bottom0">
@ -192,18 +192,18 @@ export default {
],
visitList: [{
image: "https://static.hshuishang.com/property-img-file/page_user_Group_1572.png",
name: "服务工单",
url: "",
name: "到家服务订单",
url: "/packages/homeService/myOrders/index",
},
{
image: "https://static.hshuishang.com/property-img-file/page_user_Group_1573.png",
name: "服务地址",
url: "",
name: "我的月账单",
url: "/packages/homeService/myMonthlyBills/index",
},
{
image: "https://static.hshuishang.com/property-img-file/page_user_Group_1574.png",
name: "服务卡",
url: "",
name: "我的家政合同",
url: "/packages/homeService/myContracts/index",
}
],
shopList: [{
@ -240,6 +240,12 @@ export default {
name: "物业端",
url: "/packages/workOrderDashboard/guide/index",
isShow: uni.getStorageSync("is_worker"),
},
{
image: "https://static.hshuishang.com/property-img-file/userPageWuye.png",
name: "师傅端",
url: "/packages/homeService/worker/index",
isShow: uni.getStorageSync("is_merchant"),
}
],
parkList: [
@ -347,6 +353,12 @@ export default {
name: "物业端",
url: "/packages/workOrderDashboard/guide/index",
isShow: loginRes.is_worker,
},
{
image: "https://static.hshuishang.com/property-img-file/userPageWuye.png",
name: "师傅端",
url: "/packages/homeService/worker/index",
isShow: loginRes.is_merchant,
}
];

View File

@ -1,7 +1,7 @@
{
"appid": "wx1addb25675dd8e70",
"compileType": "miniprogram",
"libVersion": "3.9.1",
"libVersion": "3.16.0",
"packOptions": {
"ignore": [],
"include": []