|
|
@@ -1,13 +1,15 @@
|
|
|
<template>
|
|
|
<view class="order-apply-page">
|
|
|
<nav-bar title="下单预约"></nav-bar>
|
|
|
+
|
|
|
<view class="apply-content">
|
|
|
<!-- 01 服务类型 -->
|
|
|
<text class="section-title">01 服务类型</text>
|
|
|
<view class="card service-info-card">
|
|
|
<view class="service-type-display">
|
|
|
<view :class="['service-icon-box', activeService]">
|
|
|
- <uni-icons :type="serviceIcon" size="22" color="#fff"></uni-icons>
|
|
|
+ <!-- CSS 手绘服务图标 @Author: Antigravity -->
|
|
|
+ <view class="pure-css-icon" :class="serviceIconClass"></view>
|
|
|
</view>
|
|
|
<view class="service-info-text">
|
|
|
<text class="main-name">{{ currentServiceName }}</text>
|
|
|
@@ -19,39 +21,39 @@
|
|
|
<!-- 02 基础信息 -->
|
|
|
<text class="section-title">02 基础信息</text>
|
|
|
<view class="card basic-info-card">
|
|
|
- <view class="field-row" @click="showShopPicker = true">
|
|
|
- <text class="field-label">服务门店</text>
|
|
|
- <text :class="['field-value', !formData.shopName ? 'placeholder' : '']">{{ formData.shopName ||
|
|
|
- '请选择商户门店' }}</text>
|
|
|
- <uni-icons type="right" size="14" color="#ccc"></uni-icons>
|
|
|
+ <view class="field-item" @click="showShopSelect = true">
|
|
|
+ <text class="field-label require">服务门店</text>
|
|
|
+ <text :class="['field-value', !formData.shopName ? 'placeholder' : '']">{{ formData.shopName || '请选择商户门店' }}</text>
|
|
|
+ <view class="right-arrow"></view>
|
|
|
</view>
|
|
|
- <view class="field-row" @click="showUserPopup = true">
|
|
|
- <text class="field-label">宠主用户</text>
|
|
|
+ <view class="field-item" @click="showUserSelect = true">
|
|
|
+ <text class="field-label require">宠主用户</text>
|
|
|
<view class="field-value-wrap">
|
|
|
<template v-if="selectedUser">
|
|
|
<text class="selected-name">{{ selectedUser.name }}</text>
|
|
|
- <text class="selected-phone">{{ selectedUser.phone }}</text>
|
|
|
+ <text class="selected-phone">{{ selectedUser.phone || selectedUser.phoneNumber }}</text>
|
|
|
</template>
|
|
|
- <text v-else class="placeholder">搜索手机号/姓名</text>
|
|
|
+ <text v-else class="placeholder">点击搜索</text>
|
|
|
</view>
|
|
|
+ <view class="right-arrow"></view>
|
|
|
</view>
|
|
|
- <view class="field-row" @click="openPetPicker">
|
|
|
- <text class="field-label">选择宠物</text>
|
|
|
- <text :class="['field-value', !formData.petName ? 'placeholder' : '']">{{ formData.petName ||
|
|
|
- '点击选择宠物档案' }}</text>
|
|
|
- <uni-icons type="right" size="14" color="#ccc"></uni-icons>
|
|
|
+ <view class="field-item" @click="openPetPicker">
|
|
|
+ <text class="field-label require">选择宠物</text>
|
|
|
+ <text :class="['field-value', !formData.petName ? 'placeholder' : '']">{{ formData.petName || '选择宠物档案' }}</text>
|
|
|
+ <view class="right-arrow"></view>
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
<!-- 03 业务表单 - 宠物接送 -->
|
|
|
<template v-if="activeService === 'transport'">
|
|
|
- <text class="section-title">03 填写接送路线与时间</text>
|
|
|
+ <text class="section-title">03 接送路线与时间</text>
|
|
|
<view class="card transport-card">
|
|
|
- <view class="field-row">
|
|
|
+ <view class="field-item">
|
|
|
<text class="field-label">团购套餐</text>
|
|
|
<input class="field-input" v-model="formData.packageName" placeholder="请输入套餐名称(选填)" />
|
|
|
</view>
|
|
|
- <text class="form-item-label">接送模式</text>
|
|
|
+
|
|
|
+ <text class="form-item-label require">接送模式</text>
|
|
|
<view class="mode-select">
|
|
|
<view v-for="mode in transportModes" :key="mode.value"
|
|
|
:class="['mode-btn', { active: formData.transportMode === mode.value }]"
|
|
|
@@ -60,156 +62,124 @@
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
- <!-- 接宠路线 (起点=用户家, 终点=门店) @Author: Antigravity -->
|
|
|
+ <!-- 接宠路线 @Author: Antigravity -->
|
|
|
<view class="route-box" v-if="formData.transportMode !== 'return_home'">
|
|
|
- <view class="route-icon pick"><text>接</text></view>
|
|
|
+ <view class="route-icon pick">接</view>
|
|
|
<view class="route-fields">
|
|
|
- <text class="addr-label">起点</text>
|
|
|
- <uni-data-picker :localdata="regionTree" v-model="formData.pickArea"
|
|
|
- :map="{ text: 'name', value: 'code' }" @change="onRegionChange('pick', $event)">
|
|
|
- <view class="premium-cascader-display">
|
|
|
- <text :class="['display-text', !formData.pickArea ? 'placeholder' : '']">
|
|
|
- {{ pickAreaLabel || '选择省/市/区' }}
|
|
|
- </text>
|
|
|
- <uni-icons type="right" size="12" color="#ccc"></uni-icons>
|
|
|
- </view>
|
|
|
- </uni-data-picker>
|
|
|
- <input class="route-input" v-model="formData.pickAddress" placeholder="详细地址 (街道/门牌号)" />
|
|
|
- <text class="addr-label">终点</text>
|
|
|
- <uni-data-picker :localdata="regionTree" v-model="formData.pickEndArea"
|
|
|
- :map="{ text: 'name', value: 'code' }" @change="onRegionChange('pickEnd', $event)">
|
|
|
- <view class="premium-cascader-display">
|
|
|
- <text :class="['display-text', !formData.pickEndArea ? 'placeholder' : '']">
|
|
|
- {{ pickEndAreaLabel || '选择省/市/区' }}
|
|
|
- </text>
|
|
|
- <uni-icons type="right" size="12" color="#ccc"></uni-icons>
|
|
|
- </view>
|
|
|
- </uni-data-picker>
|
|
|
- <input class="route-input" v-model="formData.pickEndAddress" placeholder="详细地址 (街道/门牌号)" />
|
|
|
+ <text class="addr-label require">起点 (用户家)</text>
|
|
|
+ <view class="route-picker-trigger" @click="openRegionSelect('pick')">
|
|
|
+ <text :class="['display-text', !formData.pickArea ? 'placeholder' : '']">{{ pickAreaLabel || '选择省/市/区' }}</text>
|
|
|
+ <view class="right-arrow"></view>
|
|
|
+ </view>
|
|
|
+ <input class="route-input" v-model="formData.pickAddress" placeholder="详细地址" />
|
|
|
+
|
|
|
+ <text class="addr-label require">终点 (门店)</text>
|
|
|
+ <view class="route-picker-trigger" @click="openRegionSelect('pickEnd')">
|
|
|
+ <text :class="['display-text', !formData.pickEndArea ? 'placeholder' : '']">{{ pickEndAreaLabel || '选择省/市/区' }}</text>
|
|
|
+ <view class="right-arrow"></view>
|
|
|
+ </view>
|
|
|
+ <input class="route-input" v-model="formData.pickEndAddress" placeholder="详细地址" />
|
|
|
+
|
|
|
<view class="contact-row">
|
|
|
<input class="route-input half" v-model="formData.pickContact" placeholder="联系人" />
|
|
|
- <input class="route-input half" v-model="formData.pickPhone" placeholder="电话"
|
|
|
- type="tel" />
|
|
|
+ <input class="route-input half" v-model="formData.pickPhone" placeholder="电话" type="tel" />
|
|
|
</view>
|
|
|
- <view class="time-picker-row">
|
|
|
- <uni-datetime-picker type="datetime" v-model="formData.pickTime" placeholder="选择接宠时间"
|
|
|
- :border="false" :hide-second="true">
|
|
|
- </uni-datetime-picker>
|
|
|
+ <view class="route-time-trigger" @click="openTimeModal('pick')">
|
|
|
+ <text :class="!formData.pickTime ? 'placeholder' : ''">{{ pickTimeDisplay || '设置接宠时间' }}</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
- <!-- 送宠路线 (起点=门店, 终点=用户家) @Author: Antigravity -->
|
|
|
+ <!-- 送宠路线 @Author: Antigravity -->
|
|
|
<view class="route-box" v-if="formData.transportMode !== 'pick_up'">
|
|
|
- <view class="route-icon send"><text>送</text></view>
|
|
|
+ <view class="route-icon send">送</view>
|
|
|
<view class="route-fields">
|
|
|
- <text class="addr-label">起点</text>
|
|
|
- <uni-data-picker :localdata="regionTree" v-model="formData.sendStartArea"
|
|
|
- :map="{ text: 'name', value: 'code' }" @change="onRegionChange('sendStart', $event)">
|
|
|
- <view class="premium-cascader-display">
|
|
|
- <text :class="['display-text', !formData.sendStartArea ? 'placeholder' : '']">
|
|
|
- {{ sendStartAreaLabel || '选择省/市/区' }}
|
|
|
- </text>
|
|
|
- <uni-icons type="right" size="12" color="#ccc"></uni-icons>
|
|
|
- </view>
|
|
|
- </uni-data-picker>
|
|
|
- <input class="route-input" v-model="formData.sendStartAddress"
|
|
|
- placeholder="详细地址 (街道/门牌号)" />
|
|
|
- <text class="addr-label">终点</text>
|
|
|
- <uni-data-picker :localdata="regionTree" v-model="formData.sendArea"
|
|
|
- :map="{ text: 'name', value: 'code' }" @change="onRegionChange('send', $event)">
|
|
|
- <view class="premium-cascader-display">
|
|
|
- <text :class="['display-text', !formData.sendArea ? 'placeholder' : '']">
|
|
|
- {{ sendAreaLabel || '选择省/市/区' }}
|
|
|
- </text>
|
|
|
- <uni-icons type="right" size="12" color="#ccc"></uni-icons>
|
|
|
- </view>
|
|
|
- </uni-data-picker>
|
|
|
- <input class="route-input" v-model="formData.sendAddress" placeholder="详细地址 (街道/门牌号)" />
|
|
|
+ <text class="addr-label require">起点 (门店)</text>
|
|
|
+ <view class="route-picker-trigger" @click="openRegionSelect('sendStart')">
|
|
|
+ <text :class="['display-text', !formData.sendStartArea ? 'placeholder' : '']">{{ sendStartAreaLabel || '选择省/市/区' }}</text>
|
|
|
+ <view class="right-arrow"></view>
|
|
|
+ </view>
|
|
|
+ <input class="route-input" v-model="formData.sendStartAddress" placeholder="详细地址" />
|
|
|
+
|
|
|
+ <text class="addr-label require">终点 (用户家)</text>
|
|
|
+ <view class="route-picker-trigger" @click="openRegionSelect('send')">
|
|
|
+ <text :class="['display-text', !formData.sendArea ? 'placeholder' : '']">{{ sendAreaLabel || '选择省/市/区' }}</text>
|
|
|
+ <view class="right-arrow"></view>
|
|
|
+ </view>
|
|
|
+ <input class="route-input" v-model="formData.sendAddress" placeholder="详细地址" />
|
|
|
+
|
|
|
<view class="contact-row">
|
|
|
<input class="route-input half" v-model="formData.sendContact" placeholder="联系人" />
|
|
|
- <input class="route-input half" v-model="formData.sendPhone" placeholder="电话"
|
|
|
- type="tel" />
|
|
|
+ <input class="route-input half" v-model="formData.sendPhone" placeholder="电话" type="tel" />
|
|
|
</view>
|
|
|
- <view class="time-picker-row">
|
|
|
- <uni-datetime-picker type="datetime" v-model="formData.sendTime"
|
|
|
- placeholder="预计送还时间(可选)" :border="false" :hide-second="true">
|
|
|
- </uni-datetime-picker>
|
|
|
+ <view class="route-time-trigger" @click="openTimeModal('send')">
|
|
|
+ <text :class="!formData.sendTime ? 'placeholder' : ''">{{ sendTimeDisplay || '设置送宠时间' }}</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
</template>
|
|
|
|
|
|
- <!-- 03 业务表单 - 上门喂遛/洗护 -->
|
|
|
+ <!-- 03 业务表单 - 上门 -->
|
|
|
<template v-else>
|
|
|
- <text class="section-title">03 选择套餐与服务细则</text>
|
|
|
+ <text class="section-title">03 服务细则</text>
|
|
|
<view class="card feed-card">
|
|
|
- <view class="field-row">
|
|
|
+ <view class="field-item">
|
|
|
<text class="field-label">团购套餐</text>
|
|
|
<input class="field-input" v-model="formData.packageName" placeholder="请输入套餐名称(选填)" />
|
|
|
</view>
|
|
|
- <text class="address-title">上门服务地址</text>
|
|
|
- <uni-data-picker :localdata="regionTree" v-model="formData.serviceArea"
|
|
|
- :map="{ text: 'name', value: 'code' }" @change="onRegionChange('service', $event)">
|
|
|
- <view class="premium-full-picker">
|
|
|
- <text :class="['display-text', !formData.serviceArea ? 'placeholder' : '']">
|
|
|
- {{ serviceAreaLabel || '请选择省/市/区' }}
|
|
|
- </text>
|
|
|
- <uni-icons type="right" size="14" color="#ccc"></uni-icons>
|
|
|
+ <view class="route-box">
|
|
|
+ <view class="route-icon service">服</view>
|
|
|
+ <view class="route-fields">
|
|
|
+ <text class="addr-label require">上门服务地址</text>
|
|
|
+ <view class="route-picker-trigger" @click="openRegionSelect('service')">
|
|
|
+ <text :class="['display-text', !formData.serviceArea ? 'placeholder' : '']">{{ serviceAreaLabel || '请选择省/市/区' }}</text>
|
|
|
+ <view class="right-arrow"></view>
|
|
|
+ </view>
|
|
|
+ <input class="route-input" v-model="formData.serviceAddress" placeholder="详细地址 (街道/路名/门牌号)" />
|
|
|
</view>
|
|
|
- </uni-data-picker>
|
|
|
- <input class="full-input" v-model="formData.serviceAddress" placeholder="详细地址 (街道/路名/门牌号)" />
|
|
|
+ </view>
|
|
|
|
|
|
<view class="booking-section">
|
|
|
<view class="booking-header">
|
|
|
- <text class="label">预约服务时间</text>
|
|
|
- <text class="count-tag">共 {{ formData.feedTimes.length }} 次</text>
|
|
|
+ <text class="label require">预约服务时间</text>
|
|
|
+ <view class="count-tag">共 {{ formData.feedTimes.length }} 次</view>
|
|
|
</view>
|
|
|
<view class="time-item-row" v-for="(time, index) in formData.feedTimes" :key="index">
|
|
|
<text class="index">{{ index + 1 }}.</text>
|
|
|
- <view class="flex-picker-box">
|
|
|
- <uni-datetime-picker type="datetime" v-model="time.start" placeholder="开始"
|
|
|
- :border="false" class="inline-picker" :hide-second="true"></uni-datetime-picker>
|
|
|
+ <view class="flex-time-box" @click="openTimeModal('feed', index, 'start')">
|
|
|
+ <text :class="['time-text', !time.start ? 'placeholder' : '']">{{ truncateTime(time.start) || '开始' }}</text>
|
|
|
</view>
|
|
|
<text class="to-line">~</text>
|
|
|
- <view class="flex-picker-box">
|
|
|
- <uni-datetime-picker type="datetime" v-model="time.end" placeholder="结束" :border="false"
|
|
|
- class="inline-picker" :hide-second="true"></uni-datetime-picker>
|
|
|
+ <view class="flex-time-box" @click="openTimeModal('feed', index, 'end')">
|
|
|
+ <text :class="['time-text', !time.end ? 'placeholder' : '']">{{ truncateTime(time.end) || '结束' }}</text>
|
|
|
</view>
|
|
|
<view class="action-buttons">
|
|
|
- <view class="circle-btn add" v-if="index === formData.feedTimes.length - 1"
|
|
|
- @click="addFeedTime">
|
|
|
- <text>+</text>
|
|
|
- </view>
|
|
|
- <view class="circle-btn remove" v-if="formData.feedTimes.length > 1"
|
|
|
- @click="removeFeedTime(index)">
|
|
|
- <text>-</text>
|
|
|
- </view>
|
|
|
+ <view class="circle-btn add" v-if="index === formData.feedTimes.length - 1" @click="addFeedTime">+</view>
|
|
|
+ <view class="circle-btn remove" v-if="formData.feedTimes.length > 1" @click="removeFeedTime(index)">-</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
-
|
|
|
<text class="remarks-title">备注信息</text>
|
|
|
- <textarea class="remarks-textarea" v-model="formData.otherNote" placeholder="其他注意事项"></textarea>
|
|
|
+ <textarea class="remarks-textarea" v-model="formData.otherNote" placeholder="如有其他注意事项请备注"></textarea>
|
|
|
</view>
|
|
|
</template>
|
|
|
|
|
|
<!-- 04 报价信息 -->
|
|
|
<text class="section-title">04 报价信息</text>
|
|
|
<view class="card quote-card">
|
|
|
- <view class="field-row">
|
|
|
- <text class="field-label">报价金额</text>
|
|
|
- <input class="field-input" v-model="formData.quoteAmount" type="digit" placeholder="请输入报价金额" />
|
|
|
+ <view class="field-item">
|
|
|
+ <text class="field-label require">报价金额</text>
|
|
|
+ <input class="field-input quote-input" v-model="formData.quoteAmount" type="digit" placeholder="填入数字" />
|
|
|
<text class="unit-text">元</text>
|
|
|
</view>
|
|
|
- <text class="quote-tips">注:此报价为预估费用,最终费用以实际结算为准。</text>
|
|
|
+ <text class="quote-tips">注:此价格将作为订单最终结算金额。</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
<!-- 底部操作栏 -->
|
|
|
<view class="footer-bar safe-bottom">
|
|
|
- <view class="quotation-fulfillmentCommission-box">
|
|
|
+ <view class="quotation-box">
|
|
|
<text class="p-label">总计报价:</text>
|
|
|
<text class="p-symbol">¥</text>
|
|
|
<text class="p-amount">{{ totalFulfillmentCommission }}</text>
|
|
|
@@ -217,436 +187,331 @@
|
|
|
<button class="submit-btn" @click="onSubmit">立即下单</button>
|
|
|
</view>
|
|
|
|
|
|
- <!-- 门店选择弹窗 -->
|
|
|
- <view class="popup-mask" v-if="showShopPicker" @click="showShopPicker = false">
|
|
|
- <view class="popup-content" @click.stop>
|
|
|
- <text class="popup-title">选择服务门店</text>
|
|
|
- <scroll-view scroll-y="true" class="popup-scroll">
|
|
|
- <view class="popup-item" v-for="shop in shopList" :key="shop.id" @click="onShopSelect(shop)">
|
|
|
- <text>{{ shop.name }}</text>
|
|
|
+ <!-- 居中联动选择弹窗群 @Author: Antigravity -->
|
|
|
+
|
|
|
+ <!-- 宠主搜索弹窗 -->
|
|
|
+ <view class="center-modal-mask" v-if="showUserSelect" @click="showUserSelect = false">
|
|
|
+ <view class="center-modal-content user-search-modal" @click.stop>
|
|
|
+ <view class="modal-header">
|
|
|
+ <view class="search-box">
|
|
|
+ <view class="search-icon"></view>
|
|
|
+ <input class="search-input" v-model="userSearchKey" placeholder="搜索宠主姓名/手机号" @confirm="fetchUsers" confirm-type="search" />
|
|
|
+ <view class="search-btn" @click="fetchUsers">查询</view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <scroll-view scroll-y class="modal-list-scroll">
|
|
|
+ <view class="list-item" v-for="user in userList" :key="user.id" @click="onUserSelect(user)">
|
|
|
+ <view class="user-info">
|
|
|
+ <text class="name">{{ user.name }}</text>
|
|
|
+ <text class="phone">{{ user.phone || user.phoneNumber }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="checkmark" v-if="formData.customerId === user.id"></view>
|
|
|
</view>
|
|
|
+ <view class="empty-tip" v-if="userList.length === 0">未找到相关宠主</view>
|
|
|
</scroll-view>
|
|
|
</view>
|
|
|
</view>
|
|
|
-
|
|
|
- <!-- 用户选择弹窗 -->
|
|
|
- <view class="popup-mask" v-if="showUserPopup" @click="showUserPopup = false">
|
|
|
- <view class="popup-content user-popup" @click.stop>
|
|
|
- <text class="popup-title">选择宠主</text>
|
|
|
- <view class="search-bar">
|
|
|
- <input class="search-input" v-model="userSearchKey" placeholder="输入姓名或手机号搜索"
|
|
|
- @confirm="fetchUsers" />
|
|
|
- <uni-icons type="search" size="18" color="#999" @click="fetchUsers"></uni-icons>
|
|
|
+
|
|
|
+ <!-- 区域选择器 (Cascader) @Author: Antigravity -->
|
|
|
+ <view class="center-modal-mask" v-if="showRegionModal" @click="showRegionModal = false">
|
|
|
+ <view class="center-modal-content region-modal" @click.stop>
|
|
|
+ <view class="modal-header"><text class="modal-title">选择区域</text><view class="close-btn" @click="showRegionModal = false"></view></view>
|
|
|
+ <view class="cascade-indicator">
|
|
|
+ <text v-for="(node, idx) in regionPath" :key="idx" class="path-node" @click="backToLevel(idx)">{{ node.name }}</text>
|
|
|
+ <text class="path-node active" v-if="regionPath.length < 3">请选择</text>
|
|
|
</view>
|
|
|
- <scroll-view scroll-y="true" class="popup-scroll">
|
|
|
- <view class="popup-item" v-for="user in userList" :key="user.id" @click="onUserSelect(user)">
|
|
|
- <text class="user-item-name">{{ user.name }}</text>
|
|
|
- <text class="user-item-phone">{{ user.phone || user.phoneNumber }}</text>
|
|
|
+ <scroll-view scroll-y class="modal-list-scroll">
|
|
|
+ <view class="list-item" v-for="item in currentRegionList" :key="item.code" @click="onRegionStepSelect(item)">
|
|
|
+ <text class="item-text">{{ item.name }}</text>
|
|
|
+ <view class="checkmark" v-if="isRegionSelected(item)"></view>
|
|
|
</view>
|
|
|
</scroll-view>
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
- <!-- 宠物选择弹窗 -->
|
|
|
- <view class="popup-mask" v-if="showPetPopup" @click="showPetPopup = false">
|
|
|
- <view class="popup-content" @click.stop>
|
|
|
- <text class="popup-title">选择宠物</text>
|
|
|
- <scroll-view scroll-y="true" class="popup-scroll">
|
|
|
- <view class="popup-item" v-for="pet in petList" :key="pet.id" @click="onPetSelect(pet)">
|
|
|
- <view class="pet-item-cell">
|
|
|
- <image :src="pet.avatar" class="pet-avatar-mini" mode="aspectFill"></image>
|
|
|
- <text>{{ pet.name }} ({{ pet.breed }})</text>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
- <view v-if="petList.length === 0" class="empty-tips">该用户下暂无宠物档案</view>
|
|
|
- </scroll-view>
|
|
|
+ <!-- 门店选择 -->
|
|
|
+ <center-select v-model="showShopSelect" title="选择服务门店" :options="shopList" labelKey="name" valueKey="id" :value="formData.merchantId" @select="onShopSelect" />
|
|
|
+
|
|
|
+ <!-- 宠物选择 -->
|
|
|
+ <center-select v-model="showPetPopup" title="选择指定宠物" :options="petOptions" labelKey="_label" valueKey="id" :value="formData.petId" @select="onPetSelect" />
|
|
|
+
|
|
|
+ <!-- 日期时间选择弹窗 @Author: Antigravity -->
|
|
|
+ <view class="center-modal-mask" v-if="showTimeModal" @click="showTimeModal = false">
|
|
|
+ <view class="center-modal-content time-modal" @click.stop>
|
|
|
+ <view class="modal-header"><text class="modal-title">选择预约时间</text></view>
|
|
|
+ <view class="datetime-picker-body">
|
|
|
+ <picker-view class="picker-view" :value="tempTimeIdx" @change="onTempTimeChange">
|
|
|
+ <picker-view-column><view class="picker-item" v-for="d in timeRanges[0]" :key="d">{{ d }}</view></picker-view-column>
|
|
|
+ <picker-view-column><view class="picker-item" v-for="h in timeRanges[1]" :key="h">{{ h }}时</view></picker-view-column>
|
|
|
+ <picker-view-column><view class="picker-item" v-for="m in timeRanges[2]" :key="m">{{ m }}分</view></picker-view-column>
|
|
|
+ </picker-view>
|
|
|
+ </view>
|
|
|
+ <view class="modal-footer">
|
|
|
+ <view class="modal-cancel" @click="showTimeModal = false">取消</view>
|
|
|
+ <view class="modal-confirm" @click="confirmTime">确定</view>
|
|
|
+ </view>
|
|
|
</view>
|
|
|
</view>
|
|
|
+
|
|
|
</view>
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
+/**
|
|
|
+ * @Author: Antigravity
|
|
|
+ */
|
|
|
import { ref, reactive, computed, watch } from 'vue'
|
|
|
import { onLoad } from '@dcloudio/uni-app'
|
|
|
import navBar from '@/components/nav-bar/index.vue'
|
|
|
+import centerSelect from '@/components/center-select/index.vue'
|
|
|
import { listStoreOnOrder } from '@/api/system/store'
|
|
|
import { listCustomerOnOrder } from '@/api/archieves/customer'
|
|
|
import { listPetByUser } from '@/api/archieves/pet'
|
|
|
import { createOrder } from '@/api/order/order'
|
|
|
import { listRegionTree } from '@/api/system/region'
|
|
|
|
|
|
-const showPetPopup = ref(false)
|
|
|
const activeService = ref('transport')
|
|
|
-const showShopPicker = ref(false)
|
|
|
-const showUserPopup = ref(false)
|
|
|
-const selectedUser = ref(null)
|
|
|
-const selectedShop = ref(null)
|
|
|
-const selectedPet = ref(null)
|
|
|
+const serviceInfo = ref(null)
|
|
|
const shopList = ref([])
|
|
|
const userList = ref([])
|
|
|
const petList = ref([])
|
|
|
-const userSearchKey = ref('')
|
|
|
-const serviceInfo = ref(null)
|
|
|
const regionTree = ref([])
|
|
|
+
|
|
|
+// 弹窗控制
|
|
|
+const showShopSelect = ref(false)
|
|
|
+const showUserSelect = ref(false)
|
|
|
+const showPetPopup = ref(false)
|
|
|
+const showRegionModal = ref(false)
|
|
|
+const showTimeModal = ref(false)
|
|
|
+
|
|
|
+const userSearchKey = ref('')
|
|
|
+const selectedUser = ref(null)
|
|
|
+const selectedShop = ref(null)
|
|
|
+
|
|
|
const pickAreaLabel = ref('')
|
|
|
const pickEndAreaLabel = ref('')
|
|
|
const sendStartAreaLabel = ref('')
|
|
|
const sendAreaLabel = ref('')
|
|
|
const serviceAreaLabel = ref('')
|
|
|
|
|
|
-const currentServiceName = computed(() => {
|
|
|
- // @Author: Antigravity
|
|
|
- if (serviceInfo.value) return serviceInfo.value.name
|
|
|
- const map = { transport: '宠物接送', feed: '上门喂遛', wash: '上门洗护' }
|
|
|
- return map[activeService.value]
|
|
|
-})
|
|
|
-const serviceIcon = computed(() => {
|
|
|
- const map = { transport: 'car', feed: 'shop', wash: 'color' }
|
|
|
- return map[activeService.value]
|
|
|
-})
|
|
|
-const serviceDesc = computed(() => {
|
|
|
- // @Author: Antigravity
|
|
|
- if (serviceInfo.value) return serviceInfo.value.remark
|
|
|
- const map = { transport: '专车接送 · 全程监护', feed: '喂食添水 · 陪玩遛狗', wash: '专业设备 · 深度清洁' }
|
|
|
- return map[activeService.value]
|
|
|
+const formData = reactive({
|
|
|
+ merchantId: '', shopName: '', customerId: '', customerName: '', petId: '', petName: '',
|
|
|
+ packageName: '', transportMode: 'round_trip',
|
|
|
+ pickArea: '', pickAddress: '', pickEndArea: '', pickEndAddress: '', pickContact: '', pickPhone: '', pickTime: '',
|
|
|
+ sendStartArea: '', sendStartAddress: '', sendArea: '', sendAddress: '', sendContact: '', sendPhone: '', sendTime: '',
|
|
|
+ serviceArea: '', serviceAddress: '', feedTimes: [{ start: '', end: '' }],
|
|
|
+ otherNote: '', quoteAmount: ''
|
|
|
})
|
|
|
|
|
|
+// 时间选择器逻辑 (5分钟一个间隔 @Author: Antigravity)
|
|
|
+const timeRanges = ref([[], [], []])
|
|
|
+const tempTimeIdx = ref([0, 0, 0])
|
|
|
+const timeCtx = reactive({ type: '', index: 0, field: '' })
|
|
|
+
|
|
|
onLoad((options) => {
|
|
|
if (options.service) activeService.value = options.service
|
|
|
- // @Author: Antigravity
|
|
|
const stored = uni.getStorageSync('currentService')
|
|
|
- if (stored) {
|
|
|
- serviceInfo.value = stored
|
|
|
+ if (stored) serviceInfo.value = stored
|
|
|
+ initTimeRanges()
|
|
|
+ fetchShops(); fetchUsers(); fetchRegionTree()
|
|
|
+})
|
|
|
+
|
|
|
+const initTimeRanges = () => {
|
|
|
+ const dates = []
|
|
|
+ const now = new Date()
|
|
|
+ for (let i = 0; i < 30; i++) {
|
|
|
+ const d = new Date(now); d.setDate(d.getDate() + i)
|
|
|
+ dates.push(`${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`)
|
|
|
+ }
|
|
|
+ timeRanges.value = [
|
|
|
+ dates,
|
|
|
+ Array.from({ length: 24 }, (_, i) => String(i).padStart(2, '0')),
|
|
|
+ // 五分钟间隔生成 @Author: Antigravity
|
|
|
+ Array.from({ length: 12 }, (_, i) => String(i * 5).padStart(2, '0'))
|
|
|
+ ]
|
|
|
+}
|
|
|
+
|
|
|
+const openTimeModal = (type, index = 0, field = '') => {
|
|
|
+ timeCtx.type = type; timeCtx.index = index; timeCtx.field = field
|
|
|
+ tempTimeIdx.value = [0, 0, 0]
|
|
|
+ showTimeModal.value = true
|
|
|
+}
|
|
|
+
|
|
|
+const onTempTimeChange = (e) => { tempTimeIdx.value = e.detail.value }
|
|
|
+
|
|
|
+const confirmTime = () => {
|
|
|
+ const [di, hi, mi] = tempTimeIdx.value
|
|
|
+ const val = `${new Date().getFullYear()}-${timeRanges.value[0][di]} ${timeRanges.value[1][hi]}:${timeRanges.value[2][mi]}:00`
|
|
|
+ if (timeCtx.type === 'pick') formData.pickTime = val
|
|
|
+ else if (timeCtx.type === 'send') formData.sendTime = val
|
|
|
+ else if (timeCtx.type === 'feed') {
|
|
|
+ if (timeCtx.field === 'start') formData.feedTimes[timeCtx.index].start = val
|
|
|
+ else formData.feedTimes[timeCtx.index].end = val
|
|
|
+ }
|
|
|
+ showTimeModal.value = false
|
|
|
+}
|
|
|
+
|
|
|
+const truncateTime = (t) => t ? t.substring(5, 16) : ''
|
|
|
+const pickTimeDisplay = computed(() => truncateTime(formData.pickTime))
|
|
|
+const sendTimeDisplay = computed(() => truncateTime(formData.sendTime))
|
|
|
+
|
|
|
+// 区域选择逻辑
|
|
|
+const regionPath = ref([])
|
|
|
+const activeRegionType = ref('')
|
|
|
+const currentRegionList = computed(() => {
|
|
|
+ let list = regionTree.value
|
|
|
+ for (let node of regionPath.value) {
|
|
|
+ const found = list.find(l => l.code === node.code)
|
|
|
+ if (found && found.children) list = found.children
|
|
|
+ else list = []
|
|
|
}
|
|
|
- // 初始化获取数据
|
|
|
- fetchShops()
|
|
|
- fetchUsers()
|
|
|
- fetchRegionTree()
|
|
|
+ return list
|
|
|
})
|
|
|
|
|
|
-const fetchRegionTree = () => {
|
|
|
- listRegionTree().then(res => {
|
|
|
- console.log('移动端获取到的地区树数据:', res)
|
|
|
- regionTree.value = res || []
|
|
|
- }).catch(err => {
|
|
|
- console.error('获取地区树异常:', err)
|
|
|
- })
|
|
|
+const openRegionSelect = (type) => {
|
|
|
+ activeRegionType.value = type
|
|
|
+ regionPath.value = []
|
|
|
+ showRegionModal.value = true
|
|
|
+}
|
|
|
+
|
|
|
+const backToLevel = (idx) => { regionPath.value = regionPath.value.slice(0, idx) }
|
|
|
+
|
|
|
+const onRegionStepSelect = (item) => {
|
|
|
+ regionPath.value.push({ code: item.code, name: item.name })
|
|
|
+ if (!item.children || item.children.length === 0 || regionPath.value.length >= 3) {
|
|
|
+ const fullLabel = regionPath.value.map(p => p.name).join(' / ')
|
|
|
+ const finalCode = item.code
|
|
|
+ if (activeRegionType.value === 'pick') { formData.pickArea = finalCode; pickAreaLabel.value = fullLabel }
|
|
|
+ else if (activeRegionType.value === 'pickEnd') { formData.pickEndArea = finalCode; pickEndAreaLabel.value = fullLabel }
|
|
|
+ else if (activeRegionType.value === 'sendStart') { formData.sendStartArea = finalCode; sendStartAreaLabel.value = fullLabel }
|
|
|
+ else if (activeRegionType.value === 'send') { formData.sendArea = finalCode; sendAreaLabel.value = fullLabel }
|
|
|
+ else if (activeRegionType.value === 'service') { formData.serviceArea = finalCode; serviceAreaLabel.value = fullLabel }
|
|
|
+ showRegionModal.value = false
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-const onRegionChange = (type, e) => {
|
|
|
- // @Author: Antigravity
|
|
|
- const text = e.detail.value.map(v => v.text).join(' / ')
|
|
|
- if (type === 'pick') pickAreaLabel.value = text
|
|
|
- else if (type === 'pickEnd') pickEndAreaLabel.value = text
|
|
|
- else if (type === 'sendStart') sendStartAreaLabel.value = text
|
|
|
- else if (type === 'send') sendAreaLabel.value = text
|
|
|
- else if (type === 'service') serviceAreaLabel.value = text
|
|
|
-}
|
|
|
+const isRegionSelected = (item) => {
|
|
|
+ const level = regionPath.value.length
|
|
|
+ return regionPath.value[level]?.code === item.code
|
|
|
+}
|
|
|
+
|
|
|
+// 核心回填逻辑修正 @Author: Antigravity
|
|
|
+watch([selectedShop, selectedUser, regionTree], ([shop, user, tree]) => {
|
|
|
+ if (!shop && !user) return
|
|
|
+
|
|
|
+ // 处理门店信息
|
|
|
+ const storeAreaCode = (shop?.areaCode || '').replace(/,/g, '/')
|
|
|
+ const storeLeaf = storeAreaCode.split('/').pop() || ''
|
|
|
+ const storePath = findRegionLabel(storeAreaCode, tree)
|
|
|
+
|
|
|
+ // 处理用户信息
|
|
|
+ const userAreaCode = (user?.regionCode || '')
|
|
|
+ const userLeaf = userAreaCode.split('/').pop() || ''
|
|
|
+ const userPath = findRegionLabel(userAreaCode, tree)
|
|
|
+
|
|
|
+ if (shop) {
|
|
|
+ formData.merchantId = shop.id; formData.shopName = shop.name
|
|
|
+ // 接送单终点 = 门店
|
|
|
+ formData.pickEndArea = storeLeaf; formData.pickEndAddress = shop.address || ''
|
|
|
+ pickEndAreaLabel.value = storePath
|
|
|
+ // 接送单起点 = 门店
|
|
|
+ formData.sendStartArea = storeLeaf; formData.sendStartAddress = shop.address || ''
|
|
|
+ sendStartAreaLabel.value = storePath
|
|
|
+ }
|
|
|
+ if (user) {
|
|
|
+ formData.customerId = user.id; formData.customerName = user.name
|
|
|
+ // 接宠单起点 = 宠主家
|
|
|
+ formData.pickArea = userLeaf; formData.pickAddress = user.address || ''
|
|
|
+ pickAreaLabel.value = userPath
|
|
|
+ // 送宠单终点 = 宠主家
|
|
|
+ formData.sendArea = userLeaf; formData.sendAddress = user.address || ''
|
|
|
+ sendAreaLabel.value = userPath
|
|
|
+ // 服务单地址 = 宠主家
|
|
|
+ formData.serviceArea = userLeaf; formData.serviceAddress = user.address || ''
|
|
|
+ serviceAreaLabel.value = userPath
|
|
|
+
|
|
|
+ formData.pickContact = user.name; formData.pickPhone = user.phoneNumber || user.phone || ''
|
|
|
+ formData.sendContact = user.name; formData.sendPhone = formData.pickPhone
|
|
|
+ }
|
|
|
+}, { deep: true })
|
|
|
|
|
|
-// 根据 code 递归查找地区名称全路径 @Author: Antigravity
|
|
|
const findRegionLabel = (code, list) => {
|
|
|
if (!code || !list || list.length === 0) return ''
|
|
|
- // 如果是路径格式,取最后一位
|
|
|
- const targetCode = code.includes('/') ? code.split('/').pop() : code
|
|
|
-
|
|
|
- const find = (nodes, target) => {
|
|
|
- for (let item of nodes) {
|
|
|
- if (item.code === target) return item.name
|
|
|
- if (item.children && item.children.length > 0) {
|
|
|
- const childMatch = find(item.children, target)
|
|
|
- if (childMatch) return item.name + ' / ' + childMatch
|
|
|
+ const target = code.split('/').pop()
|
|
|
+ const find = (nodes, t) => {
|
|
|
+ for (let n of nodes) {
|
|
|
+ if (n.code === t) return n.name
|
|
|
+ if (n.children) {
|
|
|
+ const res = find(n.children, t)
|
|
|
+ if (res) return n.name + ' / ' + res
|
|
|
}
|
|
|
}
|
|
|
return null
|
|
|
}
|
|
|
- return find(list, targetCode) || ''
|
|
|
-}
|
|
|
-
|
|
|
-const formData = reactive({
|
|
|
- merchantId: '', shopName: '',
|
|
|
- customerId: '', customerName: '',
|
|
|
- petId: '', petName: '',
|
|
|
- packageName: '',
|
|
|
- transportMode: 'round_trip',
|
|
|
- pickArea: '', pickAddress: '', pickEndArea: '', pickEndAddress: '', pickContact: '', pickPhone: '', pickTime: '',
|
|
|
- sendStartArea: '', sendStartAddress: '', sendArea: '', sendAddress: '', sendContact: '', sendPhone: '', sendTime: '',
|
|
|
- serviceArea: '', serviceAddress: '',
|
|
|
- feedTimes: [{ start: '', end: '' }],
|
|
|
- otherNote: '',
|
|
|
- quoteAmount: ''
|
|
|
-})
|
|
|
-
|
|
|
-const fetchShops = () => {
|
|
|
- // @Author: Antigravity
|
|
|
- const query = { pageNum: 1, pageSize: 50 }
|
|
|
- if (serviceInfo.value && serviceInfo.value.id) {
|
|
|
- query.serviceId = serviceInfo.value.id
|
|
|
- }
|
|
|
- listStoreOnOrder(query).then(res => {
|
|
|
- shopList.value = res.rows || []
|
|
|
- })
|
|
|
+ return find(list, target) || ''
|
|
|
}
|
|
|
|
|
|
-const fetchUsers = () => {
|
|
|
- listCustomerOnOrder({ pageNum: 1, pageSize: 20, content: userSearchKey.value }).then(res => {
|
|
|
- userList.value = res.rows || []
|
|
|
- })
|
|
|
-}
|
|
|
-
|
|
|
-const fetchPets = (userId) => {
|
|
|
- listPetByUser(userId).then(res => {
|
|
|
- // @Author: Antigravity
|
|
|
- // 移动端 request.js 自动解构 data,res 即为宠物列表数组
|
|
|
- petList.value = Array.isArray(res) ? res : (res.rows || [])
|
|
|
- })
|
|
|
-}
|
|
|
-
|
|
|
-const onShopSelect = (shop) => {
|
|
|
- // @Author: Antigravity
|
|
|
- selectedShop.value = shop
|
|
|
- formData.merchantId = shop.id
|
|
|
- formData.shopName = shop.name
|
|
|
- showShopPicker.value = false
|
|
|
-
|
|
|
-}
|
|
|
+const fetchShops = () => listStoreOnOrder({ pageNum: 1, pageSize: 50, serviceId: serviceInfo.value?.id }).then(res => { shopList.value = res.rows || [] })
|
|
|
+const fetchUsers = () => listCustomerOnOrder({ pageNum: 1, pageSize: 20, content: userSearchKey.value }).then(res => { userList.value = res.rows || [] })
|
|
|
+const fetchPets = (uid) => listPetByUser(uid).then(res => { petList.value = Array.isArray(res) ? res : (res.rows || []) })
|
|
|
+const fetchRegionTree = () => listRegionTree().then(res => { regionTree.value = res || [] })
|
|
|
|
|
|
+const onShopSelect = (shop) => { selectedShop.value = shop; showShopSelect.value = false }
|
|
|
const onUserSelect = (user) => {
|
|
|
- // @Author: Antigravity
|
|
|
- selectedUser.value = user
|
|
|
- formData.customerId = user.id
|
|
|
- formData.customerName = user.name
|
|
|
-
|
|
|
- // 重置宠物
|
|
|
- formData.petId = ''
|
|
|
- formData.petName = ''
|
|
|
- selectedPet.value = null
|
|
|
- fetchPets(user.id)
|
|
|
- showUserPopup.value = false
|
|
|
-}
|
|
|
-
|
|
|
-// 核心回填逻辑:watch 响应门店/用户/地区树任一变化,对齐 Web 端 watch([store, user]) @Author: Antigravity
|
|
|
-watch(
|
|
|
- [selectedShop, selectedUser, regionTree],
|
|
|
- ([shop, user, tree]) => {
|
|
|
- // 门店区域码与地址 (areaCode 为逗号分隔) @Author: Antigravity
|
|
|
- const storeArea = shop?.areaCode || ''
|
|
|
- const storeAddr = shop?.address || ''
|
|
|
- const storeLeaf = storeArea.includes(',') ? storeArea.split(',').pop() : storeArea
|
|
|
-
|
|
|
- // 用户区域码与地址 (regionCode 为斜杠分隔) @Author: Antigravity
|
|
|
- const userArea = user?.regionCode || ''
|
|
|
- const userAddr = user?.address || ''
|
|
|
- const userPhone = user?.phoneNumber || user?.phone || ''
|
|
|
- const userLeaf = userArea.includes('/') ? userArea.split('/').pop() : userArea
|
|
|
-
|
|
|
- // 回填接宠路线:起点=用户家,终点=门店 @Author: Antigravity
|
|
|
- formData.pickArea = userLeaf
|
|
|
- formData.pickAddress = userAddr
|
|
|
- formData.pickEndArea = storeLeaf
|
|
|
- formData.pickEndAddress = storeAddr
|
|
|
- formData.pickContact = user?.name || ''
|
|
|
- formData.pickPhone = userPhone
|
|
|
- pickAreaLabel.value = findRegionLabel(userArea, tree)
|
|
|
- pickEndAreaLabel.value = findRegionLabel(storeArea.replace(/,/g, '/'), tree)
|
|
|
-
|
|
|
- // 回填送宠路线:起点=门店,终点=用户家 @Author: Antigravity
|
|
|
- formData.sendStartArea = storeLeaf
|
|
|
- formData.sendStartAddress = storeAddr
|
|
|
- formData.sendArea = userLeaf
|
|
|
- formData.sendAddress = userAddr
|
|
|
- formData.sendContact = user?.name || ''
|
|
|
- formData.sendPhone = userPhone
|
|
|
- sendStartAreaLabel.value = findRegionLabel(storeArea.replace(/,/g, '/'), tree)
|
|
|
- sendAreaLabel.value = findRegionLabel(userArea, tree)
|
|
|
-
|
|
|
- // 回填上门服务地址 @Author: Antigravity
|
|
|
- formData.serviceArea = userLeaf
|
|
|
- formData.serviceAddress = userAddr
|
|
|
- serviceAreaLabel.value = findRegionLabel(userArea, tree)
|
|
|
- },
|
|
|
- { deep: true }
|
|
|
-)
|
|
|
-
|
|
|
-const openPetPicker = () => {
|
|
|
- if (!formData.customerId) {
|
|
|
- uni.showToast({ title: '请先选择宠主', icon: 'none' })
|
|
|
- return
|
|
|
- }
|
|
|
- showPetPopup.value = true
|
|
|
-}
|
|
|
-
|
|
|
-const onPetSelect = (pet) => {
|
|
|
- // @Author: Antigravity
|
|
|
- selectedPet.value = pet
|
|
|
- formData.petId = pet.id
|
|
|
- formData.petName = pet.name
|
|
|
- showPetPopup.value = false
|
|
|
-}
|
|
|
-
|
|
|
-const totalFulfillmentCommission = computed(() => {
|
|
|
- if (formData.quoteAmount && !isNaN(parseFloat(formData.quoteAmount))) return parseFloat(formData.quoteAmount).toFixed(2)
|
|
|
- return '0.00'
|
|
|
-})
|
|
|
-
|
|
|
-const transportModes = [
|
|
|
- { label: '往返接送', value: 'round_trip' },
|
|
|
- { label: '单程接', value: 'pick_up' },
|
|
|
- { label: '单程送', value: 'return_home' }
|
|
|
-]
|
|
|
-
|
|
|
-const addFeedTime = () => { formData.feedTimes.push({ start: '', end: '' }) }
|
|
|
-const removeFeedTime = (index) => { formData.feedTimes.splice(index, 1) }
|
|
|
+ selectedUser.value = user; formData.customerId = user.id;
|
|
|
+ formData.petId = ''; formData.petName = ''; petList.value = []; fetchPets(user.id)
|
|
|
+ showUserSelect.value = false
|
|
|
+}
|
|
|
+const openPetPicker = () => { if (!formData.customerId) return uni.showToast({ title: '先选择宠主', icon: 'none' }); showPetPopup.value = true }
|
|
|
+const petOptions = computed(() => petList.value.map(p => ({ ...p, _label: `${p.name} (${p.breed || '未知'})` })))
|
|
|
+const onPetSelect = (pet) => { formData.petId = pet.id; formData.petName = pet.name; showPetPopup.value = false }
|
|
|
+
|
|
|
+const currentServiceName = computed(() => serviceInfo.value?.name || (activeService.value === 'transport' ? '宠物接送' : '上门喂遛'))
|
|
|
+const serviceIconClass = computed(() => activeService.value)
|
|
|
+const serviceDesc = computed(() => serviceInfo.value?.remark || '专人专项 · 贴心呵护')
|
|
|
+const transportModes = [{ label: '往返', value: 'round_trip' }, { label: '单程接', value: 'pick_up' }, { label: '单程送', value: 'return_home' }]
|
|
|
+const addFeedTime = () => formData.feedTimes.push({ start: '', end: '' })
|
|
|
+const removeFeedTime = (idx) => formData.feedTimes.splice(idx, 1)
|
|
|
+const totalFulfillmentCommission = computed(() => formData.quoteAmount ? parseFloat(formData.quoteAmount).toFixed(2) : '0.00')
|
|
|
|
|
|
const onSubmit = async () => {
|
|
|
- // @Author: Antigravity
|
|
|
- if (!formData.merchantId) { uni.showToast({ title: '请选择门店', icon: 'none' }); return }
|
|
|
- if (!formData.customerId) { uni.showToast({ title: '请选择宠主', icon: 'none' }); return }
|
|
|
- if (!formData.petId) { uni.showToast({ title: '请选择宠物', icon: 'none' }); return }
|
|
|
- if (!formData.quoteAmount) { uni.showToast({ title: '请输入报价金额', icon: 'none' }); return }
|
|
|
-
|
|
|
+ if (!formData.merchantId || !formData.customerId || !formData.petId || !formData.quoteAmount) return uni.showToast({ title: '请完善红星必填项', icon: 'none' })
|
|
|
uni.showLoading({ title: '提交中...', mask: true })
|
|
|
-
|
|
|
try {
|
|
|
const subOrders = []
|
|
|
const baseMode = serviceInfo.value?.mode || 0
|
|
|
- const defaultContact = selectedUser.value?.name || ''
|
|
|
- const defaultPhone = selectedUser.value?.phone || selectedUser.value?.phoneNumber || ''
|
|
|
-
|
|
|
+ const defC = selectedUser.value?.name; const defP = selectedUser.value?.phone || selectedUser.value?.phoneNumber
|
|
|
if (activeService.value === 'transport') {
|
|
|
- // 接送逻辑:使用表单中回填/编辑后的起终点地址 @Author: Antigravity
|
|
|
- if (formData.transportMode === 'round_trip' || formData.transportMode === 'pick_up') {
|
|
|
- subOrders.push({
|
|
|
- mode: baseMode,
|
|
|
- type: formData.transportMode === 'round_trip' ? 0 : 2,
|
|
|
- contact: formData.pickContact || defaultContact,
|
|
|
- contactPhoneNumber: formData.pickPhone || defaultPhone,
|
|
|
- serviceTime: formData.pickTime || '',
|
|
|
- endServiceTime: formData.pickTime || '',
|
|
|
- fromCode: formData.pickArea || '',
|
|
|
- fromAddress: formData.pickAddress || '',
|
|
|
- toCode: formData.pickEndArea || '',
|
|
|
- toAddress: formData.pickEndAddress || ''
|
|
|
- })
|
|
|
- }
|
|
|
- if (formData.transportMode === 'round_trip' || formData.transportMode === 'return_home') {
|
|
|
- subOrders.push({
|
|
|
- mode: baseMode,
|
|
|
- type: formData.transportMode === 'round_trip' ? 1 : 3,
|
|
|
- contact: formData.sendContact || defaultContact,
|
|
|
- contactPhoneNumber: formData.sendPhone || defaultPhone,
|
|
|
- serviceTime: formData.sendTime || '',
|
|
|
- endServiceTime: formData.sendTime || '',
|
|
|
- fromCode: formData.sendStartArea || '',
|
|
|
- fromAddress: formData.sendStartAddress || '',
|
|
|
- toCode: formData.sendArea || '',
|
|
|
- toAddress: formData.sendAddress || ''
|
|
|
- })
|
|
|
- }
|
|
|
+ if (formData.transportMode !== 'return_home') subOrders.push({ mode: baseMode, type: formData.transportMode === 'round_trip' ? 0 : 2, contact: formData.pickContact || defC, contactPhoneNumber: formData.pickPhone || defP, serviceTime: formData.pickTime, endServiceTime: formData.pickTime, fromCode: formData.pickArea, fromAddress: formData.pickAddress, toCode: formData.pickEndArea, toAddress: formData.pickEndAddress })
|
|
|
+ if (formData.transportMode !== 'pick_up') subOrders.push({ mode: baseMode, type: formData.transportMode === 'round_trip' ? 1 : 3, contact: formData.sendContact || defC, contactPhoneNumber: formData.sendPhone || defP, serviceTime: formData.sendTime, endServiceTime: formData.sendTime, fromCode: formData.sendStartArea, fromAddress: formData.sendStartAddress, toCode: formData.sendArea, toAddress: formData.sendAddress })
|
|
|
} else {
|
|
|
- // 上门喂遛或洗护逻辑,补全 fromCode/toCode/toAddress @Author: Antigravity
|
|
|
- formData.feedTimes.forEach(time => {
|
|
|
- subOrders.push({
|
|
|
- mode: baseMode,
|
|
|
- contact: defaultContact,
|
|
|
- contactPhoneNumber: defaultPhone,
|
|
|
- serviceTime: time.start,
|
|
|
- endServiceTime: time.end || time.start,
|
|
|
- fromCode: formData.serviceArea || '',
|
|
|
- fromAddress: formData.serviceAddress,
|
|
|
- toCode: formData.serviceArea || '',
|
|
|
- toAddress: formData.serviceAddress
|
|
|
- })
|
|
|
- })
|
|
|
+ formData.feedTimes.forEach(t => subOrders.push({ mode: baseMode, contact: defC, contactPhoneNumber: defP, serviceTime: t.start, endServiceTime: t.end || t.start, fromCode: formData.serviceArea, fromAddress: formData.serviceAddress, toCode: formData.serviceArea, toAddress: formData.serviceAddress }))
|
|
|
}
|
|
|
-
|
|
|
- const payload = {
|
|
|
- store: formData.merchantId,
|
|
|
- storeSite: selectedShop.value?.site,
|
|
|
- customer: formData.customerId,
|
|
|
- pet: formData.petId,
|
|
|
- groupPurchasePackageName: formData.packageName || '',
|
|
|
- service: serviceInfo.value?.id,
|
|
|
- orderCommission: Math.round(Number(formData.quoteAmount) * 100),
|
|
|
- remark: formData.otherNote,
|
|
|
- tenantId: selectedShop.value?.tenantId,
|
|
|
- subOrders: subOrders
|
|
|
- }
|
|
|
-
|
|
|
+ const payload = { store: formData.merchantId, storeSite: selectedShop.value?.site, customer: formData.customerId, pet: formData.petId, groupPurchasePackageName: formData.packageName, service: serviceInfo.value?.id, orderCommission: Math.round(Number(formData.quoteAmount) * 100), remark: formData.otherNote, tenantId: selectedShop.value?.tenantId, subOrders }
|
|
|
await createOrder(payload)
|
|
|
- uni.hideLoading()
|
|
|
- uni.showToast({ title: '下单成功', icon: 'success' })
|
|
|
- setTimeout(() => {
|
|
|
- uni.reLaunch({ url: '/pages/order/list/index' })
|
|
|
- }, 1500)
|
|
|
- } catch (error) {
|
|
|
- uni.hideLoading()
|
|
|
- console.error('下单失败:', error)
|
|
|
- }
|
|
|
+ uni.showToast({ title: '成功', icon: 'success' })
|
|
|
+ setTimeout(() => uni.reLaunch({ url: '/pages/order/list/index' }), 1000)
|
|
|
+ } catch (e) { } finally { uni.hideLoading() }
|
|
|
}
|
|
|
</script>
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
-.order-apply-page {
|
|
|
- background: #f7f8fa;
|
|
|
- min-height: 100vh;
|
|
|
- padding-bottom: 200rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.apply-content {
|
|
|
- padding: 0 32rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.section-title {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- font-size: 30rpx;
|
|
|
- font-weight: bold;
|
|
|
- color: #333;
|
|
|
- margin: 32rpx 0 20rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.section-title::before {
|
|
|
- content: '';
|
|
|
- width: 8rpx;
|
|
|
- height: 28rpx;
|
|
|
- background: #f7ca3e;
|
|
|
- margin-right: 16rpx;
|
|
|
- border-radius: 4rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.card {
|
|
|
- background: #fff;
|
|
|
- border-radius: 24rpx;
|
|
|
- padding: 24rpx;
|
|
|
- margin-bottom: 24rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.service-type-display {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- gap: 24rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.service-icon-box {
|
|
|
- width: 88rpx;
|
|
|
- height: 88rpx;
|
|
|
- border-radius: 20rpx;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
-}
|
|
|
-
|
|
|
-.service-icon-box.transport {
|
|
|
- background: linear-gradient(135deg, #64b5f6, #2196f3);
|
|
|
-}
|
|
|
-
|
|
|
-.service-icon-box.feed {
|
|
|
- background: linear-gradient(135deg, #ffb74d, #ff9800);
|
|
|
-}
|
|
|
-
|
|
|
-.service-icon-box.wash {
|
|
|
- background: linear-gradient(135deg, #81c784, #4caf50);
|
|
|
-}
|
|
|
+/* 统一页面字体栈 @Author: Antigravity */
|
|
|
+.order-apply-page {
|
|
|
+ background: #f7f8fa;
|
|
|
+ min-height: 100vh;
|
|
|
+ padding-bottom: 220rpx;
|
|
|
+ font-family: 'PingFang SC', 'Helvetica Neue', Helvetica, 'STHeitiSTXihei', 'Microsoft YaHei', Arial, sans-serif;
|
|
|
+}
|
|
|
+.apply-content { padding: 0 28rpx; }
|
|
|
+.section-title { display: flex; align-items: center; font-size: 28rpx; font-weight: bold; color: #333; margin: 32rpx 0 20rpx; &::before { content: ''; width: 8rpx; height: 26rpx; background: #f7ca3e; margin-right: 16rpx; border-radius: 4rpx; } }
|
|
|
+.card { background: #fff; border-radius: 24rpx; padding: 24rpx; margin-bottom: 24rpx; }
|
|
|
+
|
|
|
+.service-type-display { display: flex; align-items: center; gap: 24rpx; }
|
|
|
+.service-icon-box { width: 88rpx; height: 88rpx; border-radius: 20rpx; display: flex; align-items: center; justify-content: center; }
|
|
|
+.service-icon-box.transport { background: linear-gradient(135deg, #64b5f6, #2196f3); }
|
|
|
+.service-icon-box.feed { background: linear-gradient(135deg, #ffb74d, #ff9800); }
|
|
|
+.service-icon-box.wash { background: linear-gradient(135deg, #81c784, #4caf50); }
|
|
|
|
|
|
.main-name {
|
|
|
display: block;
|
|
|
@@ -662,482 +527,70 @@ const onSubmit = async () => {
|
|
|
margin-top: 4rpx;
|
|
|
}
|
|
|
|
|
|
-.field-row {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- padding: 24rpx 0;
|
|
|
- border-bottom: 1rpx solid #f5f5f5;
|
|
|
-}
|
|
|
-
|
|
|
-.field-row:last-child {
|
|
|
- border-bottom: none;
|
|
|
-}
|
|
|
-
|
|
|
-.field-label {
|
|
|
- width: 160rpx;
|
|
|
- font-size: 28rpx;
|
|
|
- color: #333;
|
|
|
- flex-shrink: 0;
|
|
|
-}
|
|
|
-
|
|
|
-.field-value {
|
|
|
- flex: 1;
|
|
|
- font-size: 28rpx;
|
|
|
- color: #333;
|
|
|
- text-align: right;
|
|
|
-}
|
|
|
-
|
|
|
-.field-value.placeholder {
|
|
|
- color: #ccc;
|
|
|
-}
|
|
|
-
|
|
|
-.field-value-wrap {
|
|
|
- flex: 1;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: flex-end;
|
|
|
- gap: 12rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.selected-name {
|
|
|
- font-size: 28rpx;
|
|
|
- font-weight: bold;
|
|
|
- color: #333;
|
|
|
-}
|
|
|
-
|
|
|
-.selected-phone {
|
|
|
- font-size: 24rpx;
|
|
|
- color: #666;
|
|
|
-}
|
|
|
-
|
|
|
-.placeholder {
|
|
|
- color: #ccc;
|
|
|
- font-size: 28rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.field-input {
|
|
|
- flex: 1;
|
|
|
- font-size: 28rpx;
|
|
|
- color: #333;
|
|
|
- text-align: right;
|
|
|
-}
|
|
|
-
|
|
|
-.unit-text {
|
|
|
- font-size: 28rpx;
|
|
|
- color: #999;
|
|
|
- margin-left: 8rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.form-item-label {
|
|
|
- display: block;
|
|
|
- font-size: 28rpx;
|
|
|
- color: #333;
|
|
|
- margin: 24rpx 0 16rpx;
|
|
|
- font-weight: 500;
|
|
|
-}
|
|
|
-
|
|
|
-.mode-select {
|
|
|
- display: flex;
|
|
|
- gap: 16rpx;
|
|
|
- margin-bottom: 32rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.mode-btn {
|
|
|
- flex: 1;
|
|
|
- height: 64rpx;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- border: 1rpx solid #ddd;
|
|
|
- border-radius: 12rpx;
|
|
|
- font-size: 24rpx;
|
|
|
- color: #666;
|
|
|
-}
|
|
|
-
|
|
|
-.mode-btn.active {
|
|
|
- background: #fef8e5;
|
|
|
- border-color: #f7ca3e;
|
|
|
- color: #f7ca3e;
|
|
|
- font-weight: bold;
|
|
|
-}
|
|
|
-
|
|
|
-.route-box {
|
|
|
- display: flex;
|
|
|
- gap: 24rpx;
|
|
|
- margin-bottom: 24rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.route-icon {
|
|
|
- width: 48rpx;
|
|
|
- height: 48rpx;
|
|
|
- border-radius: 8rpx;
|
|
|
- color: #fff;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- font-size: 24rpx;
|
|
|
- font-weight: bold;
|
|
|
- flex-shrink: 0;
|
|
|
- margin-top: 16rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.route-icon.pick {
|
|
|
- background: #5bb7ff;
|
|
|
-}
|
|
|
-
|
|
|
-.route-icon.send {
|
|
|
- background: #64cf5c;
|
|
|
-}
|
|
|
-
|
|
|
-.route-fields {
|
|
|
- flex: 1;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- gap: 8rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.route-input {
|
|
|
- width: 100%;
|
|
|
- height: 72rpx;
|
|
|
- font-size: 26rpx;
|
|
|
- color: #333;
|
|
|
- border-bottom: 1rpx solid #f0f0f0;
|
|
|
- padding: 0 8rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.contact-row {
|
|
|
- display: flex;
|
|
|
- gap: 16rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.route-input.half {
|
|
|
- flex: 1;
|
|
|
-}
|
|
|
-
|
|
|
-/* 起点/终点标签 @Author: Antigravity */
|
|
|
-.addr-label {
|
|
|
- font-size: 24rpx;
|
|
|
- color: #606266;
|
|
|
- font-weight: bold;
|
|
|
- margin-top: 16rpx;
|
|
|
- margin-bottom: 4rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.address-title {
|
|
|
- display: block;
|
|
|
- font-size: 28rpx;
|
|
|
- color: #333;
|
|
|
- font-weight: 500;
|
|
|
- margin: 24rpx 0 16rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.full-input {
|
|
|
- width: 100%;
|
|
|
- height: 72rpx;
|
|
|
- font-size: 26rpx;
|
|
|
- color: #333;
|
|
|
- border-bottom: 1rpx solid #f0f0f0;
|
|
|
- padding: 0 8rpx;
|
|
|
- margin-bottom: 8rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.premium-cascader-display {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- padding: 16rpx 8rpx;
|
|
|
- border-bottom: 1rpx solid #f0f0f0;
|
|
|
-
|
|
|
- .display-text {
|
|
|
- flex: 1;
|
|
|
- font-size: 26rpx;
|
|
|
- color: #333;
|
|
|
-
|
|
|
- &.placeholder {
|
|
|
- color: #ccc;
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-.premium-full-picker {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- padding: 24rpx 12rpx;
|
|
|
- background: #f9f9f9;
|
|
|
- border-radius: 12rpx;
|
|
|
- margin-bottom: 16rpx;
|
|
|
-
|
|
|
- .display-text {
|
|
|
- flex: 1;
|
|
|
- font-size: 28rpx;
|
|
|
- color: #333;
|
|
|
-
|
|
|
- &.placeholder {
|
|
|
- color: #ccc;
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-.booking-section {
|
|
|
- margin-top: 24rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.time-picker-row {
|
|
|
- border-bottom: 1rpx solid #f0f0f0;
|
|
|
- padding: 4rpx 0;
|
|
|
-}
|
|
|
-
|
|
|
-.flex-picker-box {
|
|
|
- flex: 1;
|
|
|
- background: #fcfcfc;
|
|
|
- border-radius: 12rpx;
|
|
|
- border: 1rpx solid #f0f0f0;
|
|
|
- height: 64rpx;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
-
|
|
|
- .inline-picker {
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-.booking-header {
|
|
|
- display: flex;
|
|
|
- justify-content: space-between;
|
|
|
- align-items: center;
|
|
|
- margin-bottom: 16rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.booking-header .label {
|
|
|
- font-size: 28rpx;
|
|
|
- color: #333;
|
|
|
- font-weight: 500;
|
|
|
-}
|
|
|
-
|
|
|
-.count-tag {
|
|
|
- font-size: 22rpx;
|
|
|
- color: #ff9500;
|
|
|
- background: #fff3e0;
|
|
|
- padding: 4rpx 16rpx;
|
|
|
- border-radius: 8rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.time-item-row {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- gap: 12rpx;
|
|
|
- margin-bottom: 16rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.index {
|
|
|
- font-size: 26rpx;
|
|
|
- color: #999;
|
|
|
- width: 40rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.time-input {
|
|
|
- flex: 1;
|
|
|
- height: 64rpx;
|
|
|
- font-size: 24rpx;
|
|
|
- border: 1rpx solid #f0f0f0;
|
|
|
- border-radius: 12rpx;
|
|
|
- padding: 0 16rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.to-line {
|
|
|
- color: #999;
|
|
|
-}
|
|
|
-
|
|
|
-.action-buttons {
|
|
|
- display: flex;
|
|
|
- gap: 8rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.circle-btn {
|
|
|
- width: 48rpx;
|
|
|
- height: 48rpx;
|
|
|
- border-radius: 50%;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- font-size: 28rpx;
|
|
|
- font-weight: bold;
|
|
|
-}
|
|
|
-
|
|
|
-.circle-btn.add {
|
|
|
- background: #e3f2fd;
|
|
|
- color: #2196f3;
|
|
|
-}
|
|
|
-
|
|
|
-.circle-btn.remove {
|
|
|
- background: #fde2e2;
|
|
|
- color: #f56c6c;
|
|
|
-}
|
|
|
-
|
|
|
-.remarks-title {
|
|
|
- display: block;
|
|
|
- font-size: 28rpx;
|
|
|
- color: #333;
|
|
|
- font-weight: 500;
|
|
|
- margin: 24rpx 0 16rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.remarks-textarea {
|
|
|
- width: 100%;
|
|
|
- height: 160rpx;
|
|
|
- font-size: 26rpx;
|
|
|
- color: #333;
|
|
|
- background: #f9f9f9;
|
|
|
- border-radius: 16rpx;
|
|
|
- padding: 20rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.quote-tips {
|
|
|
- display: block;
|
|
|
- font-size: 22rpx;
|
|
|
- color: #999;
|
|
|
- margin-top: 12rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.footer-bar {
|
|
|
- position: fixed;
|
|
|
- bottom: 0;
|
|
|
- left: 0;
|
|
|
- right: 0;
|
|
|
- background: #fff;
|
|
|
- padding: 20rpx 32rpx;
|
|
|
- padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.05);
|
|
|
- z-index: 10;
|
|
|
-}
|
|
|
-
|
|
|
-.quotation-fulfillmentCommission-box {
|
|
|
- flex: 1;
|
|
|
- display: flex;
|
|
|
- align-items: baseline;
|
|
|
-}
|
|
|
-
|
|
|
-.p-label {
|
|
|
- font-size: 26rpx;
|
|
|
- color: #333;
|
|
|
-}
|
|
|
-
|
|
|
-.p-symbol {
|
|
|
- font-size: 28rpx;
|
|
|
- color: #f44336;
|
|
|
- font-weight: bold;
|
|
|
- margin-left: 8rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.p-amount {
|
|
|
- font-size: 44rpx;
|
|
|
- font-weight: 900;
|
|
|
- color: #f44336;
|
|
|
-}
|
|
|
-
|
|
|
-.submit-btn {
|
|
|
- width: 280rpx;
|
|
|
- height: 88rpx;
|
|
|
- background: linear-gradient(90deg, #ffd53f, #ff9500);
|
|
|
- color: #333;
|
|
|
- border: none;
|
|
|
- border-radius: 44rpx;
|
|
|
- font-size: 30rpx;
|
|
|
- font-weight: bold;
|
|
|
- line-height: 88rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.popup-mask {
|
|
|
- position: fixed;
|
|
|
- top: 0;
|
|
|
- left: 0;
|
|
|
- right: 0;
|
|
|
- bottom: 0;
|
|
|
- background: rgba(0, 0, 0, 0.5);
|
|
|
- z-index: 999;
|
|
|
- display: flex;
|
|
|
- align-items: flex-end;
|
|
|
-}
|
|
|
-
|
|
|
-.popup-content {
|
|
|
- width: 100%;
|
|
|
- background: #fff;
|
|
|
- border-radius: 32rpx 32rpx 0 0;
|
|
|
- padding: 40rpx 32rpx;
|
|
|
- max-height: 70vh;
|
|
|
-}
|
|
|
-
|
|
|
-.popup-title {
|
|
|
- display: block;
|
|
|
- font-size: 32rpx;
|
|
|
- font-weight: bold;
|
|
|
- color: #333;
|
|
|
- margin-bottom: 24rpx;
|
|
|
- text-align: center;
|
|
|
-}
|
|
|
-
|
|
|
-.popup-item {
|
|
|
- padding: 28rpx 0;
|
|
|
- border-bottom: 1rpx solid #f5f5f5;
|
|
|
- font-size: 28rpx;
|
|
|
- color: #333;
|
|
|
-}
|
|
|
-
|
|
|
-.user-popup .popup-item {
|
|
|
- display: flex;
|
|
|
- justify-content: space-between;
|
|
|
-}
|
|
|
-
|
|
|
-.user-item-name {
|
|
|
- font-weight: bold;
|
|
|
-}
|
|
|
-
|
|
|
-.user-item-phone {
|
|
|
- color: #999;
|
|
|
-}
|
|
|
-
|
|
|
-.popup-scroll {
|
|
|
- max-height: 600rpx;
|
|
|
- margin-top: 20rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.search-bar {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- background: #f5f5f5;
|
|
|
- border-radius: 40rpx;
|
|
|
- padding: 0 30rpx;
|
|
|
- margin-bottom: 20rpx;
|
|
|
- height: 72rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.search-input {
|
|
|
- flex: 1;
|
|
|
- font-size: 26rpx;
|
|
|
- color: #333;
|
|
|
-}
|
|
|
-
|
|
|
-.pet-item-cell {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- gap: 20rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.pet-avatar-mini {
|
|
|
- width: 60rpx;
|
|
|
- height: 60rpx;
|
|
|
- border-radius: 10rpx;
|
|
|
- background: #f0f0f0;
|
|
|
-}
|
|
|
-
|
|
|
-.empty-tips {
|
|
|
- text-align: center;
|
|
|
- color: #999;
|
|
|
- font-size: 24rpx;
|
|
|
- padding: 40rpx 0;
|
|
|
-}
|
|
|
+/* CSS 手绘图标 @Author: Antigravity */
|
|
|
+.pure-css-icon { width: 40rpx; height: 40rpx; border: 4rpx solid #fff; border-radius: 8rpx; position: relative; &::after { content: ''; position: absolute; top: 10rpx; left: 10rpx; width: 12rpx; height: 12rpx; background: #fff; border-radius: 50%; } }
|
|
|
+
|
|
|
+.field-item { display: flex; align-items: center; padding: 28rpx 0; border-bottom: 2rpx solid #f5f5f5; height: 44rpx; &:last-child { border-bottom: none; } }
|
|
|
+.field-label { width: 180rpx; font-size: 28rpx; color: #333; flex-shrink: 0; }
|
|
|
+.require::before { content: '*'; color: #f56c6c; margin-right: 4rpx; }
|
|
|
+.field-value { flex: 1; font-size: 28rpx; color: #333; text-align: right; margin-right: 16rpx; }
|
|
|
+.field-value.placeholder { color: #ccc; }
|
|
|
+.field-value-wrap { flex: 1; display: flex; flex-direction: column; align-items: flex-end; margin-right: 16rpx; .selected-name { font-size: 28rpx; font-weight: bold; color: #333; } .selected-phone { font-size: 22rpx; color: #999; } }
|
|
|
+.placeholder { color: #ccc; font-size: 28rpx; }
|
|
|
+
|
|
|
+.mode-select { display: flex; gap: 16rpx; margin: 20rpx 0 32rpx; }
|
|
|
+.mode-btn { flex: 1; height: 60rpx; display: flex; align-items: center; justify-content: center; border: 2rpx solid #f0f0f0; border-radius: 30rpx; font-size: 24rpx; color: #666; &.active { background: #fef8e5; border-color: #f7ca3e; color: #f7ca3e; font-weight: bold; } }
|
|
|
+
|
|
|
+.route-box { display: flex; gap: 20rpx; margin-bottom: 30rpx; }
|
|
|
+.route-icon { width: 44rpx; height: 44rpx; border-radius: 8rpx; color: #fff; display: flex; align-items: center; justify-content: center; font-size: 22rpx; font-weight: bold; flex-shrink: 0; margin-top: 10rpx; }
|
|
|
+.route-icon.pick { background: #5bb7ff; }
|
|
|
+.route-icon.send { background: #64cf5c; }
|
|
|
+.route-icon.service { background: #ff9500; }
|
|
|
+.route-fields { flex: 1; display: flex; flex-direction: column; gap: 6rpx; }
|
|
|
+.addr-label { font-size: 22rpx; color: #999; margin-top: 10rpx; }
|
|
|
+.route-picker-trigger { height: 64rpx; border-bottom: 2rpx solid #f5f5f5; display: flex; align-items: center; justify-content: space-between; .display-text { font-size: 26rpx; color: #333; &.placeholder { color: #ccc; } } }
|
|
|
+.route-input { height: 72rpx; font-size: 26rpx; border-bottom: 2rpx solid #f5f5f5; &.half { flex: 1; } }
|
|
|
+.contact-row { display: flex; gap: 16rpx; }
|
|
|
+.route-time-trigger { height: 72rpx; background: #f9f9f9; border-radius: 12rpx; display: flex; align-items: center; padding: 0 20rpx; font-size: 26rpx; color: #333; margin-top: 10rpx; .placeholder { color: #ccc; } }
|
|
|
+
|
|
|
+.address-title, .form-item-label, .booking-header .label, .remarks-title { display: block; font-size: 26rpx; color: #666; margin: 20rpx 0 10rpx; }
|
|
|
+.booking-section { margin-top: 24rpx; }
|
|
|
+.count-tag { font-size: 20rpx; color: #ff9500; background: #fff3e0; padding: 4rpx 12rpx; border-radius: 6rpx; }
|
|
|
+.time-item-row { display: flex; align-items: center; gap: 12rpx; margin-bottom: 16rpx; }
|
|
|
+.index { font-size: 24rpx; color: #999; width: 30rpx; }
|
|
|
+.flex-time-box { flex: 1; height: 64rpx; background: #fcfcfc; border: 2rpx solid #eee; border-radius: 10rpx; display: flex; align-items: center; justify-content: center; .time-text { font-size: 24rpx; color: #333; &.placeholder { color: #ccc; } } }
|
|
|
+.action-buttons { display: flex; gap: 12rpx; margin-left: 8rpx; }
|
|
|
+.circle-btn { width: 44rpx; height: 44rpx; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 32rpx; font-weight: bold; &.add { background: #e3f2fd; color: #2196f3; } &.remove { background: #fde2e2; color: #f56c6c; } }
|
|
|
+.remarks-textarea { width: 100%; height: 140rpx; font-size: 26rpx; background: #f9f9f9; border-radius: 16rpx; padding: 16rpx; box-sizing: border-box; }
|
|
|
+
|
|
|
+.quote-input { flex: 1; font-size: 36rpx; color: #f44336; font-weight: bold; text-align: right; }
|
|
|
+.unit-text { font-size: 28rpx; color: #333; margin-left: 8rpx; }
|
|
|
+.quote-tips { display: block; font-size: 22rpx; color: #999; margin-top: 20rpx; }
|
|
|
+
|
|
|
+.footer-bar { position: fixed; bottom: 0; left: 0; right: 0; background: #fff; padding: 24rpx 32rpx; display: flex; align-items: center; justify-content: space-between; box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.05); z-index: 100; }
|
|
|
+.quotation-box { display: flex; align-items: baseline; .p-label { font-size: 24rpx; color: #333; } .p-symbol { font-size: 28rpx; color: #f44336; font-weight: bold; margin-left: 8rpx; } .p-amount { font-size: 40rpx; font-weight: 900; color: #f44336; } }
|
|
|
+.submit-btn { width: 280rpx; height: 84rpx; background: linear-gradient(90deg, #ffd53f, #ff9500); color: #fff; border-radius: 42rpx; font-size: 28rpx; font-weight: bold; line-height: 84rpx; &::after { border: none; } }
|
|
|
+
|
|
|
+/* CSS Common Modal UI @Author: Antigravity */
|
|
|
+.center-modal-mask { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.6); z-index: 10000; display: flex; align-items: center; justify-content: center; backdrop-filter: blur(4rpx); }
|
|
|
+.center-modal-content { width: 620rpx; background: #fff; border-radius: 32rpx; display: flex; flex-direction: column; overflow: hidden; animation: popIn 0.3s ease-out; }
|
|
|
+@keyframes popIn { from { transform: scale(0.9); opacity: 0; } to { transform: scale(1); opacity: 1; } }
|
|
|
+
|
|
|
+.modal-header { padding: 32rpx; border-bottom: 2rpx solid #f2f2f2; position: relative; text-align: center; .modal-title { font-size: 30rpx; font-weight: bold; color: #333; } }
|
|
|
+.close-btn { position: absolute; right: 24rpx; top: 24rpx; width: 44rpx; height: 44rpx; &::before, &::after { content: ''; position: absolute; top: 20rpx; left: 8rpx; width: 28rpx; height: 4rpx; background: #999; transform: rotate(45deg); border-radius: 4rpx; } &::after { transform: rotate(-45deg); } }
|
|
|
+
|
|
|
+.search-box { display: flex; align-items: center; background: #f5f5f5; border-radius: 36rpx; padding: 0 24rpx; height: 72rpx; margin: 0 4rpx; .search-icon { width: 20rpx; height: 20rpx; border: 3rpx solid #999; border-radius: 50%; margin-right: 12rpx; position: relative; &::after { content: ''; width: 10rpx; height: 3rpx; background: #999; position: absolute; bottom: -4rpx; right: -4rpx; transform: rotate(45deg); } } .search-input { flex: 1; font-size: 26rpx; } .search-btn { font-size: 26rpx; color: #ff9500; font-weight: bold; margin-left: 20rpx; } }
|
|
|
+.modal-list-scroll { flex: 1; max-height: 55vh; padding: 0 32rpx; }
|
|
|
+.list-item { display: flex; align-items: center; justify-content: space-between; padding: 30rpx 0; border-bottom: 2rpx solid #f9f9f9; .user-info { display: flex; flex-direction: column; .name { font-size: 28rpx; font-weight: bold; color: #333; } .phone { font-size: 22rpx; color: #999; margin-top: 4rpx; } } }
|
|
|
+.checkmark { width: 12rpx; height: 22rpx; border-right: 4rpx solid #ff9500; border-bottom: 4rpx solid #ff9500; transform: rotate(45deg); }
|
|
|
+
|
|
|
+.cascade-indicator { display: flex; padding: 20rpx 32rpx; background: #fafafa; border-bottom: 2rpx solid #f2f2f2; flex-wrap: wrap; gap: 12rpx; .path-node { font-size: 24rpx; color: #666; &.active { color: #ff9500; font-weight: bold; } } }
|
|
|
+
|
|
|
+.datetime-picker-body { height: 400rpx; padding: 20rpx 0; }
|
|
|
+.picker-view { width: 100%; height: 100%; }
|
|
|
+.picker-item { line-height: 80rpx; text-align: center; font-size: 28rpx; }
|
|
|
+.modal-footer { display: flex; border-top: 2rpx solid #f2f2f2; .modal-cancel, .modal-confirm { flex: 1; height: 96rpx; line-height: 96rpx; text-align: center; font-size: 28rpx; } .modal-confirm { color: #ff9500; font-weight: bold; border-left: 2rpx solid #f2f2f2; } }
|
|
|
+
|
|
|
+.right-arrow { width: 12rpx; height: 12rpx; border-right: 3rpx solid #ccc; border-top: 3rpx solid #ccc; transform: rotate(45deg); flex-shrink: 0; }
|
|
|
+.empty-tip { padding: 80rpx 0; text-align: center; color: #ccc; font-size: 24rpx; }
|
|
|
</style>
|