Files
wagoo-douy3/src/pageHome/service/feeding.vue
2026-03-27 10:15:51 +08:00

1692 lines
52 KiB
Vue
Raw 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="feeding-container">
<scroll-view class="feeding-scroll" scroll-y>
<view class="feeding-content">
<!-- Tab切换 -->
<!-- <view class="order-tab-list">
<view :class="activeTab === 'reservation' ? 'activeItem' : 'tabItem'"
@click.stop="switchTab('reservation')">
预约单
</view>
<view :class="activeTab === 'instant' ? 'activeItem' : 'tabItem'"
@click.stop="switchTab('instant')">
即时单
</view>
</view> -->
<!-- 表单内容 -->
<view class="form-wrapper">
<!-- 选择宠物 -->
<view class="form-section">
<view class="form-label-row">
<text class="required">*</text>
<text class="form-label">选择宠物</text>
</view>
<view class="add-pet-wrapper">
<view v-for="(pet, index) in selectedPets" :key="pet.id || index"
class="selected-pet-avatar">
<image class="pet-avatar-img" :src="pet.avatar || `${imgPrefix}record_avator.png`"
mode="aspectFill" />
<view class="remove-pet-btn" @click.stop="removePet(index)">×</view>
</view>
<view class="add-pet-btn" @click="addPet">
<text class="plus-icon">+</text>
</view>
</view>
<text class="add-pet-text">添加宠物</text>
</view>
<!-- 选择套餐 -->
<view class="form-section package-section">
<view class="package-header">
<text class="required">*</text>
<text class="form-label">选择宠物套餐</text>
</view>
<scroll-view class="package-scroll" scroll-x>
<view class="package-list">
<view class="package-card"
:class="{ 'package-card-selected': formData.selectedPackageIndex === index }"
v-for="(item, index) in packageList" :key="index" @click="selectPackage(index)">
<view class="package-info">
<view class="package-name-row">
<text class="package-name">{{ item.service_name }}</text>
<image class="info-icon" :src="imgPrefix + 'trainingTips.png'"
mode="widthFix" @click.stop="showPackageInfo(item)" />
</view>
<text class="package-duration">{{ item.service_desc }}</text>
<view class="package-price-row">
<view class="price-content">
<text class="package-price">¥{{ item.service_price }}</text>
<text class="package-unit">/</text>
</view>
<image v-if="formData.selectedPackageIndex === index" class="package-radio"
src="@/static/images/cart_checked.png" mode="widthFix" />
<image v-else class="package-radio"
:src="imgPrefix + 'packageUnchecked.png'" mode="widthFix" />
</view>
</view>
</view>
</view>
</scroll-view>
</view>
<!-- 选择服务时间 -->
<view class="form-section" v-if="activeTab === 'reservation'" @click="selectTime">
<view class="service-time-header">
<text class="required">*</text>
<text class="service-time-title">选择服务时间</text>
<view class="service-time-count" v-if="selectedDatesCount > 0">
<text class="count-text">{{ selectedDatesCount }}</text>
<image class="right-icon" :src="imgPrefix + 'right-arrow.png'" mode="widthFix" />
</view>
<view class="service-time-count" v-else>
<text class="placeholder-text">请选择</text>
<image class="right-icon" :src="imgPrefix + 'right-arrow.png'" mode="widthFix" />
</view>
</view>
<view class="service-time-content" v-if="formData.serviceTime && selectedDatesCount > 0">
<view class="service-time-item">
<text class="service-time-label">已选择日期:</text>
<text class="service-time-value">{{ selectedDatesList.join(',') }}</text>
</view>
<view class="service-time-item">
<text class="service-time-label">已选择时间段:</text>
<text class="service-time-value">{{ selectedTimeSlot }}</text>
</view>
</view>
</view>
<!-- 选择服务地址 -->
<view class="form-section" @click="selectAddress">
<view class="info-top-view">
<text class="required">*</text>
<view class="title-view">
<text class="form-label">选择服务地址</text>
</view>
<view class="info-view" v-if="addressInfo">
<view class="address-display">
<view class="address-name-phone">
<text class="address-name-text">{{ addressInfo.recipient_name || '' }}</text>
<text class="address-phone-text">{{ addressInfo.phone || '' }}</text>
</view>
<text class="address-detail-text">{{ formData.serviceAddress }}</text>
</view>
</view>
<view class="info-view" v-else>
<text class="placeholder-text">请选择</text>
</view>
<image class="right-icon" :src="imgPrefix + 'right-arrow.png'" mode="widthFix" />
</view>
<!-- <text class="nightFee">以下区域需收取调度费奉贤区嘉定区青浦区松江区崇明县金山区</text> -->
</view>
<!-- Wi-Fi密码 -->
<view class="form-section">
<view class="info-top-view">
<view class="title-view">
<text class="form-label">Wi-Fi密码</text>
</view>
<view class="info-view">
<input class="form-input" type="text" v-model="formData.wifiPassword"
placeholder="以便工作人员拍摄宠物视频" placeholder-class="placeholder-class" />
</view>
<view class="right-icon" />
</view>
</view>
<!-- 服务备注 -->
<view class="form-section" @click="inputServiceNotes">
<view class="info-top-view">
<view class="title-view">
<text class="form-label">服务备注</text>
</view>
<view class="info-view" v-if="formData.serviceNotes">
<text class="form-label">{{ formData.serviceNotes }}</text>
</view>
<view class="info-view" v-else>
<text class="placeholder-text">请补充宠物注意事项</text>
</view>
<image class="right-icon" :src="imgPrefix + 'right-arrow.png'" mode="widthFix" />
</view>
</view>
<!-- 钥匙交接备注 -->
<view class="form-section" @click="inputKeyNotes">
<view class="info-top-view">
<text class="required">*</text>
<view class="title-view">
<text class="form-label">钥匙交接备注</text>
</view>
<view class="info-view" v-if="formData.keyNotes">
<text class="form-label">{{ formData.keyNotes }}</text>
</view>
<view class="info-view" v-else>
<text class="placeholder-text">请补充钥匙交接备注</text>
</view>
<image class="right-icon" :src="imgPrefix + 'right-arrow.png'" mode="widthFix" />
</view>
</view>
<!-- 停车状况 -->
<view class="form-section form-section-park">
<view class="info-top-view">
<text class="required">*</text>
<view class="title-view">
<text class="form-label">停车状况</text>
</view>
</view>
<view class="park-list">
<view class="park-card" v-for="item in parkConditions" :key="item"
:class="{ 'park-card-selected': formData.parkState === item }"
@click="selectParkState(item)">
<text class="park-card-text"
:class="{ 'park-card-text-selected': formData.parkState === item }">{{ item
}}</text>
</view>
</view>
<view class="park-input-wrap" v-if="formData.parkState === '其他'">
<textarea class="park-input" v-model="formData.otherParkState" placeholder="点击输入停车信息"
placeholder-class="placeholder-class" />
</view>
</view>
</view>
<!-- 说明图片 -->
<view class="explain-image-section">
<image class="explain-image"
:src="imgPrefix + (serviceType === 'feeding' ? 'feedPetLong.png' : 'trainingExplain.png')"
mode="widthFix" />
</view>
</view>
</scroll-view>
<!-- 底部固定栏 tip 时上方展示说明条 -->
<view class="bottom-fixed-wrap">
<view v-if="tip" class="tip-notice-bar">
<image class="tip-notice-icon" :src="imgPrefix + 'reservationTime-notice.png'" mode="aspectFit" />
<text class="tip-notice-text">{{ tip }}</text>
</view>
<view class="bottom-action-bar">
<view class="price-info">
<view class="estimate-price">
<text class="estimate-label">预估</text>
<text class="estimate-amount">¥{{ estimatedPrice }}</text>
</view>
</view>
<view class="next-step-btn" @click="goToNextStep">
<text class="next-step-text">下一步</text>
</view>
</view>
</view>
<!-- 服务备注弹窗 -->
<view class="key-notes-modal" v-if="showServiceNotesModal" @click.stop="closeServiceNotesModal">
<view class="modal-content" @click.stop="">
<!-- 标题栏 -->
<view class="modal-header">
<view class="close-btn-placeholder"></view>
<text class="modal-title">服务备注</text>
<view class="close-btn" @click="closeServiceNotesModal">
<text class="close-icon">×</text>
</view>
</view>
<!-- 内容区域 -->
<view class="content-view">
<!-- 输入框 -->
<view class="input-wrapper">
<textarea class="notes-input" v-model="serviceNotesInput" placeholder="请补充宠物注意事项"
placeholder-class="input-placeholder" maxlength="100" @input="onServiceNotesInput" />
<view class="char-count">{{ serviceNotesInput.length }}/100</view>
</view>
<!-- 快捷输入 -->
<view class="quick-input-section">
<text class="quick-input-label">快捷输入</text>
<view class="quick-tags">
<view class="quick-tag" v-for="(tag, index) in serviceNotesQuickTags" :key="index"
@click="selectServiceNotesTag(tag)">
<text class="tag-text">{{ tag }}</text>
</view>
</view>
</view>
<!-- 提交按钮 -->
<view class="submit-btn" @click="submitServiceNotes">
<text class="submit-btn-text">提交</text>
</view>
</view>
</view>
</view>
<!-- 服务介绍弹窗 -->
<view class="service-info-modal" v-if="showServiceInfoModal" @click.stop="closeServiceInfoModal">
<view class="service-info-modal-content" @click.stop="">
<view class="service-info-header">
<text class="service-info-title">服务介绍</text>
</view>
<view class="service-info-body">
<text class="service-info-text">{{ currentServiceInfo }}</text>
</view>
<view class="service-info-footer">
<view class="service-info-btn" @click="closeServiceInfoModal">
<text class="service-info-btn-text">我知道了</text>
</view>
</view>
</view>
</view>
<!-- 钥匙交接备注弹窗 -->
<view class="key-notes-modal" v-if="showKeyNotesModal" @click.stop="closeKeyNotesModal">
<view class="modal-content" @click.stop="">
<!-- 标题栏 -->
<view class="modal-header">
<view class="close-btn-placeholder"></view>
<text class="modal-title">钥匙交接备注</text>
<view class="close-btn" @click="closeKeyNotesModal">
<text class="close-icon">×</text>
</view>
</view>
<!-- 内容区域 -->
<view class="content-view">
<!-- 输入框 -->
<view class="input-wrapper">
<textarea class="notes-input" v-model="keyNotesInput" placeholder="请补充宠物注意事项"
placeholder-class="input-placeholder" maxlength="100" @input="onKeyNotesInput" />
<view class="char-count">{{ keyNotesInput.length }}/100</view>
</view>
<!-- 快捷输入 -->
<view class="quick-input-section">
<text class="quick-input-label">快捷输入</text>
<view class="quick-tags">
<view class="quick-tag" v-for="(tag, index) in keyNotesQuickTags" :key="index"
@click="selectQuickTag(tag)">
<text class="tag-text">{{ tag }}</text>
</view>
</view>
</view>
<!-- 提交按钮 -->
<view class="submit-btn" @click="submitKeyNotes">
<text class="submit-btn-text">提交</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import { imgPrefix } from '@/utils/common';
import { getHomeServices } from '@/api/common';
import { createFeedOrder, createWalkOrder, checkHolidayFee } from '@/api/order';
import { PET_TYPE_CAT, PET_TYPE_DOG } from '@/constants/app.business';
import moment from 'moment';
export default {
name: 'Feeding',
data() {
return {
imgPrefix,
serviceType: 'feeding', // 服务类型feeding(上门喂宠) 或 walking(上门遛宠)
activeTab: 'reservation',
formData: {
pet: null,
selectedPackageIndex: null,
serviceTime: null, // 改为存储完整的timeData对象 {dates: [], timeSlot: ''}
serviceAddress: '',
parkState: '',
otherParkState: '',
wifiPassword: '',
serviceNotes: '',
keyNotes: ''
},
parkConditions: ['小区', '地库', '路边', '其他'],
selectedPets: [], // 选中的宠物列表
addressInfo: null, // 地址信息对象
packageList: [
{
name: '单只小型犬(≤10kg)',
duration: '服务时长约30分钟',
price: 128
},
{
name: '单只中型犬(10-25kg)',
duration: '服务时长约60分钟',
price: 158
},
{
name: '单只大型犬(>25kg)',
duration: '服务时长约90分钟',
price: 188
}
],
showKeyNotesModal: false,
keyNotesInput: '',
showServiceNotesModal: false,
serviceNotesInput: '',
serviceNotesQuickTags: [
'怕生怕冷',
'胆小怕生',
'攻击性',
'不可抱',
'皮肤病',
'术后恢复',
'呼吸道敏感',
'需要安抚',
'护食',
'咬人',
'抓人'
],
keyNotesQuickTags: [
'存于快递柜',
'放于指定位置',
'家里有人在'
],
showServiceInfoModal: false,
currentServiceInfo: '',
holidayFee: 0,
tip: ''
};
},
computed: {
// 已选择日期数量
selectedDatesCount() {
return this.formData.serviceTime?.dates?.length || 0;
},
// 已选择日期列表格式YYYY-MM-DD
selectedDatesList() {
return this.formData.serviceTime?.dates || [];
},
// 已选择时间段
selectedTimeSlot() {
return this.formData.serviceTime?.timeSlot || '';
},
// 预估价格(根据选中的套餐计算)
estimatedPrice() {
if (this.formData.selectedPackageIndex !== null && this.formData.selectedPackageIndex !== undefined) {
const selectedPackage = this.packageList[this.formData.selectedPackageIndex];
if (selectedPackage && selectedPackage.service_price) {
// 基础价格包含1只体重最小的宠物1天的费用
let basePrice = parseFloat(selectedPackage.service_price) || 0;
// 宠物费用:找出体重最小的宠物(套餐已包含),计算其他宠物的额外费用
let petsFee = 0;
if (this.selectedPets.length > 0) {
// 找出体重最小的宠物weight_id最小的
let minWeightId = Infinity;
let minWeightPetIndex = 0;
this.selectedPets.forEach((pet, index) => {
const weightId = pet.weight_id || pet.weight?.weight_id || 0;
if (weightId < minWeightId) {
minWeightId = weightId;
minWeightPetIndex = index;
}
});
// 遍历所有宠物,计算额外费用
this.selectedPets.forEach((pet, index) => {
// 跳过体重最小的那只(套餐已包含)
if (index === minWeightPetIndex) {
return;
}
if (this.serviceType === 'feeding') {
// 上门喂宠每只额外宠物固定加30元
petsFee += 30;
} else if (this.serviceType === 'walking') {
// 上门遛宠:根据每只狗的体重判断
const weightId = pet.weight_id || pet.weight?.weight_id || 0;
// 根据体重ID判断费用
if (weightId < 7) {
petsFee += 40; // weight_id < 7加40元
} else if (weightId >= 7) {
petsFee += 50; // weight_id >= 7加50元
} else {
// 如果没有体重信息默认加40元
petsFee += 40;
}
}
});
}
let day = this.selectedDatesCount === 0 ? 1 : this.selectedDatesCount;
// 总价格 = 基础价格 + 附加价格 * 天数
let totalPrice = (basePrice + petsFee) * day;
return (totalPrice + this.holidayFee).toFixed(2);
}
}
return '0.00';
},
// 会员价格预估价格的折扣价假设9折
// memberPrice() {
// const price = parseFloat(this.estimatedPrice);
// if (price > 0) {
// return (price * 0.8).toFixed(2);
// }
// return '0.00';
// }
},
onLoad(options) {
// 获取服务类型参数feeding(上门喂宠) 或 walking(上门遛宠)
if (options.serviceType) {
this.serviceType = options.serviceType;
}
// 根据服务类型动态设置导航栏标题
const title = this.serviceType === 'walking' ? '上门遛宠' : '上门喂宠';
uni.setNavigationBarTitle({
title: title
});
// 调用套餐列表接口
this.getPackageList();
// 监听地址选择事件
uni.$on("selectAddress", this.addressChange);
},
onUnload() {
// 移除事件监听
uni.$off("selectAddress", this.addressChange);
},
methods: {
getPackageList() {
// 根据服务类型设置 service_type上门喂宠=1上门遛宠=2
const serviceType = this.serviceType === 'feeding' ? 1 : 2;
getHomeServices({ service_type: serviceType }).then((res) => {
// 兼容不同的返回结构
const list = res?.data || res?.info || res || [];
if (Array.isArray(list) && list.length > 0) {
this.packageList = list;
}
}).catch((err) => {
console.error('获取套餐列表失败:', err);
// 如果接口调用失败,保持使用默认的套餐列表
});
},
switchTab(tab) {
this.activeTab = tab;
// 清除所有选择的数据
this.selectedPets = [];
this.formData.pet = null;
this.formData.selectedPackageIndex = null;
this.formData.serviceTime = null;
this.addressInfo = null;
this.formData.serviceAddress = '';
},
addPet() {
uni.navigateTo({
url: `/pageHome/selectPet/index?serviceType=${this.serviceType}`,
events: {
changePet: (pet) => {
// 检查是否已经添加过这个宠物
const isExist = this.selectedPets.some(p => p.id === pet.id);
if (!isExist) {
this.selectedPets.push(pet);
// 如果只有一个宠物,也更新 formData.pet
if (this.selectedPets.length === 1) {
this.formData.pet = pet;
}
} else {
uni.showToast({
title: '该宠物已添加',
icon: 'none',
duration: 1500
});
}
}
}
});
},
removePet(index) {
this.selectedPets.splice(index, 1);
// 如果删除后没有宠物了,清空 formData.pet
if (this.selectedPets.length === 0) {
this.formData.pet = null;
} else if (this.selectedPets.length === 1) {
// 如果只有一个了,更新 formData.pet
this.formData.pet = this.selectedPets[0];
}
},
selectTime() {
// 构建URL参数传递已选择的数据
let url = '/pageHome/service/select-service-time';
if (this.formData.serviceTime && this.formData.serviceTime.dates && this.formData.serviceTime.dates.length > 0) {
const dates = encodeURIComponent(JSON.stringify(this.formData.serviceTime.dates));
const timeSlot = encodeURIComponent(this.formData.serviceTime.timeSlot || '');
url += `?dates=${dates}&timeSlot=${timeSlot}`;
}
uni.navigateTo({
url: url,
events: {
changeServiceTime: (timeData) => {
// timeData 包含 dates 和 timeSlot
// 直接存储完整的timeData对象
if (timeData && timeData.dates && timeData.dates.length > 0 && timeData.timeSlot) {
this.formData.serviceTime = timeData;
// 选择完日期后调用节假日费用校验接口,传递日期数组
checkHolidayFee({ date: timeData.dates }).then((res) => {
// 可根据返回结果处理节假日费用展示或价格更新
if (res && res.data) {
console.log('check_holiday_fee', res.data);
this.holidayFee = res.data.fee
this.tip = res.data.tip
}
}).catch((err) => {
console.error('check_holiday_fee error', err);
});
}
}
}
});
},
selectPackage(index) {
this.formData.selectedPackageIndex = index;
},
showPackageInfo(item) {
this.currentServiceInfo = item.service_info || '';
this.showServiceInfoModal = true;
},
closeServiceInfoModal() {
this.showServiceInfoModal = false;
},
selectAddress() {
uni.navigateTo({
url: `/pageHome/selectAddress/index?typeSelect=1&addressId=${this.addressInfo?.id || this.addressInfo?.address_id || ""}`
});
},
addressChange(addressInfo) {
this.addressInfo = addressInfo;
// 格式化地址信息:省市区 + 详细地址(用于显示)
const region = [addressInfo.province, addressInfo.city, addressInfo.district].filter(Boolean).join('');
this.formData.serviceAddress = region ? `${region}${addressInfo.full_address}` : addressInfo.full_address;
},
selectParkState(item) {
this.formData.parkState = item;
if (item !== '其他') {
this.formData.otherParkState = '';
}
},
inputServiceNotes() {
this.serviceNotesInput = this.formData.serviceNotes || '';
this.showServiceNotesModal = true;
},
closeServiceNotesModal() {
this.showServiceNotesModal = false;
},
onServiceNotesInput(e) {
this.serviceNotesInput = e.detail.value;
},
selectServiceNotesTag(tag) {
if (this.serviceNotesInput.length + tag.length <= 100) {
this.serviceNotesInput = this.serviceNotesInput ? this.serviceNotesInput + ' ' + tag : tag;
} else {
uni.showToast({
title: '字数超出限制',
icon: 'none'
});
}
},
submitServiceNotes() {
this.formData.serviceNotes = this.serviceNotesInput;
this.closeServiceNotesModal();
},
inputKeyNotes() {
this.keyNotesInput = this.formData.keyNotes || '';
this.showKeyNotesModal = true;
},
closeKeyNotesModal() {
this.showKeyNotesModal = false;
},
onKeyNotesInput(e) {
this.keyNotesInput = e.detail.value;
},
selectQuickTag(tag) {
if (this.keyNotesInput.length + tag.length <= 100) {
this.keyNotesInput = this.keyNotesInput ? this.keyNotesInput + ' ' + tag : tag;
} else {
uni.showToast({
title: '字数超出限制',
icon: 'none'
});
}
},
submitKeyNotes() {
this.formData.keyNotes = this.keyNotesInput;
this.closeKeyNotesModal();
},
validateForm() {
// 校验选择宠物
if (!this.selectedPets || this.selectedPets.length === 0) {
uni.showToast({
title: '请选择宠物',
icon: 'none'
});
return false;
}
// 校验选择宠物套餐
if (this.formData.selectedPackageIndex === null || this.formData.selectedPackageIndex === undefined) {
uni.showToast({
title: '请选择宠物套餐',
icon: 'none'
});
return false;
}
// 校验选择服务时间(仅在预约单时)
if (this.activeTab === 'reservation') {
if (!this.formData.serviceTime || !this.formData.serviceTime.dates || this.formData.serviceTime.dates.length === 0 || !this.formData.serviceTime.timeSlot) {
uni.showToast({
title: '请选择服务时间',
icon: 'none'
});
return false;
}
}
// 校验选择服务地址
if (!this.addressInfo || !this.addressInfo.id) {
uni.showToast({
title: '请选择服务地址',
icon: 'none'
});
return false;
}
// 校验停车状况
if (!this.formData.parkState) {
uni.showToast({
title: '请选择停车状况',
icon: 'none'
});
return false;
}
if (this.formData.parkState === '其他' && !(this.formData.otherParkState || '').trim()) {
uni.showToast({
title: '请输入停车信息',
icon: 'none'
});
return false;
}
// 校验钥匙交接备注
if (!this.formData.keyNotes || !this.formData.keyNotes.trim()) {
uni.showToast({
title: '请输入钥匙交接备注',
icon: 'none'
});
return false;
}
return true;
},
goToNextStep() {
// 校验表单
if (!this.validateForm()) {
return;
}
// 构建接口参数
const selectedPackage = this.packageList[this.formData.selectedPackageIndex];
if (!selectedPackage) {
uni.showToast({
title: '请选择套餐',
icon: 'none'
});
return;
}
// 构建宠物数组
const pets = this.selectedPets.map(pet => ({
pet_id: pet.id,
pet_name: pet.name || pet.pet_nickname || ''
}));
// 构建时间段数组
let slots = [];
if (this.activeTab === 'instant') {
// 即时单(现场单):直接设置为今天,格式 YYYY-MM-DD
const todayStr = moment().format('YYYY-MM-DD');
slots = [{
date: todayStr,
period_name: '' // 现场单不需要 period_name
}];
} else {
// 预约单:将时间段字符串转换为 period_name 格式
// 例如 "12:00-15:00" 转换为 "12:00:00 ~ 15:00:00"
const timeSlot = this.formData.serviceTime.timeSlot;
const periodName = timeSlot.replace(/-/g, ':00 ~ ') + ':00';
slots = this.formData.serviceTime.dates.map(date => ({
date: date,
period_name: periodName
}));
}
// 校验 park_desc 字段
const parkDesc = this.formData.parkState === '其他' ? (this.formData.otherParkState || '').trim() : '';
if (this.formData.parkState === '其他' && !parkDesc) {
uni.showToast({
title: '请输入停车信息',
icon: 'none'
});
return;
}
const orderData = {
pets: pets,
service_type: this.serviceType === 'feeding' ? 1 : 2, // 上门喂宠=1, 上门遛宠=2
service_id: selectedPackage.service_id,
service_name: selectedPackage.service_name || '',
address_id: this.addressInfo.id || this.addressInfo.address_id,
address: this.formData.serviceAddress || this.addressInfo.full_address || '',
park_desc: (this.formData.otherParkState || '').trim() || this.formData.parkState,
wifi_password: this.formData.wifiPassword || '',
remark: this.formData.serviceNotes || '',
key_handover_remark: this.formData.keyNotes || '',
slots: slots,
service_price: +selectedPackage.service_price,
service_actual_price: +this.estimatedPrice
};
uni.showLoading({
title: '提交中...',
mask: true
});
// 根据服务类型调用不同的接口
const createOrderAPI = this.serviceType === 'walking' ? createWalkOrder : createFeedOrder;
createOrderAPI(orderData)
.then((res) => {
uni.hideLoading();
uni.showToast({
title: '订单创建成功',
icon: 'success'
});
// 获取订单ID兼容不同的响应结构
const orderId = res?.data?.order_id;
// 跳转到支付确认页面
setTimeout(() => {
uni.navigateTo({
url: `/pageHome/service/payment-confirm?order_id=${orderId}`
});
}, 800);
})
.catch((err) => {
uni.hideLoading();
console.error('创建订单失败:', err);
uni.showToast({
title: err?.msg || err?.message || '创建订单失败',
icon: 'none'
});
});
}
}
};
</script>
<style lang="scss" scoped>
.feeding-container {
width: 100%;
height: 100vh;
background-color: #faf7f8;
display: flex;
flex-direction: column;
}
.feeding-scroll {
flex: 1;
height: 100%;
padding-bottom: calc(140rpx + env(safe-area-inset-bottom));
box-sizing: border-box;
}
.feeding-content {
padding: 0 20rpx;
padding-top: 20rpx;
}
.order-tab-list {
display: flex;
align-items: flex-end;
margin-top: 20rpx;
.tabItem {
flex: 1;
text-align: center;
background-color: #ffd8e6;
border-radius: 16rpx 16rpx 0px 0px;
height: 92rpx;
line-height: 92rpx;
font-size: 24rpx;
color: #333;
}
.activeItem {
background-color: #fff;
flex: 1;
text-align: center;
height: 92rpx;
line-height: 92rpx;
border-radius: 16rpx 16rpx 0px 0px;
font-size: 28rpx;
font-weight: 500;
color: #333;
}
}
.form-wrapper {
background-color: #fff;
padding: 0rpx 20rpx;
border-radius: 0px 0px 16rpx 16rpx;
}
.form-section {
width: 100%;
background-color: #fff;
display: flex;
flex-direction: column;
padding: 36rpx 0;
box-sizing: border-box;
border-bottom: 1px solid #ececec;
&:last-child {
border-bottom: none;
}
}
.form-section-park {
.park-list {
margin-top: 16rpx;
width: 100%;
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: center;
gap: 24rpx;
}
.park-card {
padding: 12rpx 20rpx;
border-radius: 182rpx;
background-color: #f5f5f5;
color: #000;
}
.park-card-selected {
background-color: #fee9f3;
}
.park-card-text {
font-size: 24rpx;
color: #333;
}
.park-card-text-selected {
color: #FF19A0;
}
.park-input-wrap {
width: 100%;
height: 200rpx;
margin-top: 32rpx;
box-sizing: border-box;
}
.park-input {
width: 100%;
height: 100%;
padding: 32rpx;
border-radius: 30rpx;
color: #333;
box-sizing: border-box;
background-color: #f9f7f9;
font-size: 24rpx;
}
}
.form-label-row {
display: flex;
flex-direction: row;
align-items: center;
box-sizing: border-box;
margin-bottom: 0;
}
.form-label {
font-size: 24rpx;
color: #333;
display: flex;
flex: 1;
align-items: center;
}
.required-star {
font-size: 24rpx;
color: #FF19A0;
margin-right: 4rpx;
}
.add-pet-wrapper {
display: flex;
align-items: center;
gap: 16rpx;
margin-top: 20rpx;
flex-wrap: wrap;
padding-left: 12rpx;
}
.selected-pet-avatar {
position: relative;
width: 64rpx;
height: 64rpx;
}
.pet-avatar-img {
width: 64rpx;
height: 64rpx;
border-radius: 50%;
background-color: #f0f0f0;
}
.remove-pet-btn {
position: absolute;
top: -8rpx;
right: -8rpx;
width: 32rpx;
height: 32rpx;
border-radius: 50%;
background-color: #FF19A0;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-size: 24rpx;
line-height: 1;
border: 2rpx solid #fff;
}
.add-pet-btn {
width: 64rpx;
height: 64rpx;
border: 1rpx dashed #3D3D3D;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
background: #EBEBEB;
}
.plus-icon {
font-size: 60rpx;
color: #999;
line-height: 1;
}
.add-pet-text {
font-size: 24rpx;
color: #666;
margin-top: 8rpx;
}
.info-top-view {
width: 100%;
display: flex;
flex-direction: row;
align-items: center;
box-sizing: border-box;
}
.required {
color: #FF19A0;
font-size: 24rpx;
margin-right: 4rpx;
}
.title-view {
display: flex;
flex: 1;
align-items: center;
}
.info-view {
max-width: 60%;
}
.address-display {
display: flex;
flex-direction: column;
align-items: flex-start;
width: 100%;
}
.address-name-phone {
display: flex;
align-items: center;
gap: 16rpx;
margin-bottom: 8rpx;
}
.address-name-text {
font-size: 24rpx;
color: #333;
}
.address-phone-text {
font-size: 24rpx;
color: #333;
}
.address-detail-text {
font-size: 24rpx;
color: #333;
text-align: left;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;
}
.placeholder-text {
color: #9B939A;
font-size: 24rpx;
}
.form-input {
text-align: right;
font-size: 24rpx;
color: #333;
width: 100%;
}
.placeholder-class {
color: #9B939A;
}
.right-icon {
margin-left: 16rpx;
width: 10rpx;
height: 18rpx;
}
.service-time-header {
display: flex;
align-items: center;
}
.service-time-title {
font-size: 24rpx;
color: #333;
margin-left: 4rpx;
}
.service-time-count {
display: flex;
align-items: center;
margin-left: auto;
}
.count-text {
font-size: 24rpx;
color: #666;
margin-right: 8rpx;
}
.service-time-content {
display: flex;
flex-direction: column;
gap: 16rpx;
margin-top: 16rpx;
}
.service-time-item {
display: flex;
align-items: flex-start;
}
.service-time-label {
color: #3D3D3D;
flex-shrink: 0;
margin-right: 8rpx;
font-size: 24rpx;
line-height: 36rpx;
}
.service-time-value {
color: #3D3D3D;
flex: 1;
word-break: break-all;
font-size: 24rpx;
line-height: 36rpx;
}
.form-selector {
width: 100%;
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-end;
box-sizing: border-box;
margin-top: 0;
}
.selector-text {
font-size: 28rpx;
color: #333;
flex: 1;
}
.selector-text.placeholder {
color: #999;
}
.selector-desc {
font-size: 24rpx;
}
.arrow-icon {
width: 10rpx;
height: 18rpx;
flex-shrink: 0;
margin-left: 8rpx;
}
.dispatch-fee-notice {
margin-top: 12rpx;
}
.nightFee {
color: #ff19a0;
font-family: PingFangSC;
font-size: 20rpx;
font-weight: normal;
margin-top: 20rpx;
}
.notice-text {
font-size: 24rpx;
color: #FF19A0;
line-height: 1.5;
}
.explain-image-section {
width: 100%;
margin-top: 20rpx;
box-sizing: border-box;
}
.explain-image {
width: 100%;
height: auto;
display: block;
}
.key-notes-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0);
display: flex;
flex-direction: column;
justify-content: flex-end;
z-index: 1000;
animation: fadeIn 0.3s ease forwards;
}
.key-notes-modal .modal-content {
width: 100%;
background-color: #fff;
border-radius: 24rpx 24rpx 0 0;
padding: 40rpx;
padding-bottom: calc(40rpx + env(safe-area-inset-bottom));
box-sizing: border-box;
max-height: 80vh;
overflow-y: auto;
transform: translateY(100%);
animation: slideUp 0.3s ease forwards;
}
.key-notes-modal .modal-header {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
margin-bottom: 40rpx;
position: relative;
}
.key-notes-modal .modal-title {
font-size: 36rpx;
font-weight: 600;
color: #333;
flex: 1;
text-align: center;
}
.key-notes-modal .close-btn-placeholder {
width: 48rpx;
height: 48rpx;
}
.key-notes-modal .close-btn {
width: 48rpx;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
}
.key-notes-modal .close-icon {
font-size: 48rpx;
color: #666;
line-height: 1;
}
.key-notes-modal .content-view {
width: 100%;
display: flex;
flex-direction: column;
}
.key-notes-modal .input-wrapper {
position: relative;
margin-bottom: 20rpx;
}
.key-notes-modal .notes-input {
width: 100%;
min-height: 216rpx;
background-color: #F5F5F5;
border-radius: 12rpx;
padding: 24rpx;
box-sizing: border-box;
font-size: 28rpx;
color: #333;
line-height: 1.6;
}
.key-notes-modal .input-placeholder {
color: #999;
}
.key-notes-modal .char-count {
position: absolute;
bottom: 16rpx;
right: 24rpx;
font-size: 24rpx;
color: #999;
}
.key-notes-modal .quick-input-section {
margin-bottom: 32rpx;
}
.key-notes-modal .quick-input-label {
font-size: 28rpx;
color: #333;
margin-bottom: 16rpx;
display: block;
}
.key-notes-modal .quick-tags {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 16rpx;
}
.key-notes-modal .quick-tag {
padding: 12rpx 24rpx;
background-color: #fff;
border: 1px solid #E0E0E0;
border-radius: 8rpx;
}
.key-notes-modal .tag-text {
font-size: 24rpx;
color: #333;
}
.key-notes-modal .submit-btn {
width: 100%;
height: 96rpx;
background-color: #FF19A0;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 100px;
}
.key-notes-modal .submit-btn-text {
font-size: 32rpx;
color: #fff;
font-weight: 500;
}
@keyframes fadeIn {
from {
background-color: rgba(0, 0, 0, 0);
}
to {
background-color: rgba(0, 0, 0, 0.5);
}
}
@keyframes slideUp {
from {
transform: translateY(100%);
}
to {
transform: translateY(0);
}
}
/* 服务介绍弹窗样式 */
.service-info-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 1000;
animation: fadeIn 0.3s ease forwards;
}
.service-info-modal-content {
width: 600rpx;
background-color: #fff;
border-radius: 24rpx;
box-sizing: border-box;
overflow: hidden;
animation: scaleIn 0.3s ease forwards;
}
.service-info-header {
padding: 40rpx 40rpx 24rpx;
text-align: center;
}
.service-info-title {
font-size: 32rpx;
font-weight: 600;
color: #333333;
}
.service-info-body {
padding: 0rpx 40rpx;
min-height: 120rpx;
max-height: 400rpx;
overflow-y: auto;
padding-bottom: 40rpx;
}
.service-info-text {
font-size: 28rpx;
color: #333333;
line-height: 1.8;
word-break: break-all;
}
.service-info-footer {
padding: 0 40rpx 40rpx;
}
.service-info-btn {
width: 100%;
height: 88rpx;
background-color: #FF19A0;
border-radius: 44rpx;
display: flex;
align-items: center;
justify-content: center;
}
.service-info-btn-text {
font-size: 32rpx;
color: #fff;
}
@keyframes scaleIn {
from {
transform: scale(0.9);
opacity: 0;
}
to {
transform: scale(1);
opacity: 1;
}
}
.package-section {
padding: 32rpx 0;
border-bottom: 1px solid #ececec;
}
.package-header {
display: flex;
flex-direction: row;
align-items: center;
margin-bottom: 24rpx;
}
.package-header .required {
color: #FF19A0;
font-size: 28rpx;
margin-right: 4rpx;
}
.package-header .form-label {
font-size: 24rpx;
}
.package-scroll {
width: 100%;
white-space: nowrap;
}
.package-list {
display: flex;
flex-direction: row;
gap: 16rpx;
}
.package-card {
flex-shrink: 0;
width: 300rpx;
background-color: #F6F6F6;
border-radius: 16rpx;
padding: 16rpx;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
border: 1px solid transparent;
}
.package-card-selected {
border: 1px solid #FF19A0;
background: rgba(231, 64, 152, 0.1);
}
.package-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 8rpx;
}
.package-name-row {
display: flex;
flex-direction: row;
align-items: center;
gap: 8rpx;
}
.package-name {
font-size: 24rpx;
color: #222222;
}
.info-icon {
width: 32rpx;
height: 32rpx;
flex-shrink: 0;
}
.package-duration {
font-size: 22rpx;
color: #3D3D3D;
}
.package-price-row {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
margin-top: 4rpx;
}
.price-content {
display: flex;
flex-direction: row;
align-items: baseline;
gap: 4rpx;
}
.package-price {
font-size: 24rpx;
color: #FF19A0;
font-weight: 500;
}
.package-unit {
font-size: 24rpx;
color: #FF19A0;
}
.package-radio {
width: 36rpx;
height: 36rpx;
flex-shrink: 0;
}
.bottom-fixed-wrap {
position: fixed;
bottom: 0;
left: 0;
right: 0;
display: flex;
flex-direction: column;
z-index: 100;
}
.tip-notice-bar {
background-color: #FFE5F0;
padding: 12rpx 20rpx;
display: flex;
flex-direction: row;
align-items: center;
gap: 12rpx;
flex-shrink: 0;
}
.tip-notice-icon {
width: 30rpx;
height: 30rpx;
flex-shrink: 0;
}
.tip-notice-text {
color: #FF19A0;
font-size: 24rpx;
flex: 1;
}
.bottom-action-bar {
background-color: #fff;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 24rpx 32rpx;
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
box-sizing: border-box;
border-top: 1px solid #f0f0f0;
}
.price-info {
display: flex;
flex-direction: column;
gap: 16rpx;
flex: 1;
}
.estimate-price {
display: flex;
flex-direction: row;
align-items: baseline;
gap: 8rpx;
}
.estimate-label {
font-size: 24rpx;
color: #666;
}
.estimate-amount {
font-size: 48rpx;
color: #FF19A0;
}
.member-price-row {
display: flex;
flex-direction: row;
align-items: center;
gap: 4rpx;
}
.vip-price-icon {
width: 86rpx;
height: 28rpx;
}
.member-price-amount {
font-size: 28rpx;
color: #3D3D3D;
}
.next-step-btn {
background-color: #FF19A0;
border-radius: 50rpx;
padding: 20rpx 60rpx;
margin-left: 32rpx;
}
.next-step-text {
font-size: 32rpx;
color: #fff;
font-weight: 500;
}
</style>