|
|
@@ -1,7 +1,7 @@
|
|
|
<template>
|
|
|
<view class="order-apply-page">
|
|
|
<nav-bar title="下单预约"></nav-bar>
|
|
|
-
|
|
|
+
|
|
|
<view class="apply-content">
|
|
|
<!-- 01 服务类型 -->
|
|
|
<text class="section-title">01 服务类型</text>
|
|
|
@@ -23,7 +23,8 @@
|
|
|
<view class="card basic-info-card">
|
|
|
<view class="field-item" @click="showShopSelect = true">
|
|
|
<text class="field-label require">服务门店</text>
|
|
|
- <text :class="['field-value', !formData.shopName ? 'placeholder' : '']">{{ formData.shopName || '请选择商户门店' }}</text>
|
|
|
+ <text :class="['field-value', !formData.shopName ? 'placeholder' : '']">{{ formData.shopName ||
|
|
|
+ '请选择商户门店' }}</text>
|
|
|
<view class="right-arrow"></view>
|
|
|
</view>
|
|
|
<view class="field-item" @click="showUserSelect = true">
|
|
|
@@ -39,7 +40,8 @@
|
|
|
</view>
|
|
|
<view class="field-item" @click="openPetPicker">
|
|
|
<text class="field-label require">选择宠物</text>
|
|
|
- <text :class="['field-value', !formData.petName ? 'placeholder' : '']">{{ formData.petName || '选择宠物档案' }}</text>
|
|
|
+ <text :class="['field-value', !formData.petName ? 'placeholder' : '']">{{ formData.petName ||
|
|
|
+ '选择宠物档案' }}</text>
|
|
|
<view class="right-arrow"></view>
|
|
|
</view>
|
|
|
</view>
|
|
|
@@ -52,7 +54,7 @@
|
|
|
<text class="field-label">团购套餐</text>
|
|
|
<input class="field-input" v-model="formData.packageName" placeholder="请输入套餐名称(选填)" />
|
|
|
</view>
|
|
|
-
|
|
|
+
|
|
|
<text class="form-item-label require">接送模式</text>
|
|
|
<view class="mode-select">
|
|
|
<view v-for="mode in transportModes" :key="mode.value"
|
|
|
@@ -68,24 +70,28 @@
|
|
|
<view class="route-fields">
|
|
|
<text class="addr-label require">起点 (用户家)</text>
|
|
|
<view class="route-picker-trigger" @click="openRegionSelect('pick')">
|
|
|
- <text :class="['display-text', !formData.pickArea ? 'placeholder' : '']">{{ pickAreaLabel || '选择省/市/区' }}</text>
|
|
|
+ <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>
|
|
|
+ <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="route-time-trigger" @click="openTimeModal('pick')">
|
|
|
- <text :class="!formData.pickTime ? 'placeholder' : ''">{{ pickTimeDisplay || '设置接宠时间' }}</text>
|
|
|
+ <text :class="!formData.pickStartTime ? 'placeholder' : ''">{{
|
|
|
+ truncateTime(formData.pickStartTime) || '设置接宠时间' }}</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
@@ -96,31 +102,36 @@
|
|
|
<view class="route-fields">
|
|
|
<text class="addr-label require">起点 (门店)</text>
|
|
|
<view class="route-picker-trigger" @click="openRegionSelect('sendStart')">
|
|
|
- <text :class="['display-text', !formData.sendStartArea ? 'placeholder' : '']">{{ sendStartAreaLabel || '选择省/市/区' }}</text>
|
|
|
+ <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>
|
|
|
+ <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="route-time-trigger" @click="openTimeModal('send')">
|
|
|
- <text :class="!formData.sendTime ? 'placeholder' : ''">{{ sendTimeDisplay || '设置送宠时间' }}</text>
|
|
|
+ <text :class="!formData.sendStartTime ? 'placeholder' : ''">{{
|
|
|
+ truncateTime(formData.sendStartTime) || '设置送宠时间' }}</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
<!-- 接送备注 -->
|
|
|
<text class="remarks-title">备注信息</text>
|
|
|
- <textarea class="remarks-textarea" v-model="formData.transportNote" placeholder="请添加接送备注 (如宠物性格、接送要求等)"></textarea>
|
|
|
+ <textarea class="remarks-textarea" v-model="formData.transportNote"
|
|
|
+ placeholder="请添加接送备注 (如宠物性格、接送要求等)"></textarea>
|
|
|
</view>
|
|
|
</template>
|
|
|
|
|
|
@@ -137,35 +148,36 @@
|
|
|
<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>
|
|
|
+ <text :class="['display-text', !formData.serviceArea ? 'placeholder' : '']">{{
|
|
|
+ serviceAreaLabel || '请选择省/市/区' }}</text>
|
|
|
<view class="right-arrow"></view>
|
|
|
</view>
|
|
|
- <input class="route-input" v-model="formData.serviceAddress" placeholder="详细地址 (街道/路名/门牌号)" />
|
|
|
+ <input class="route-input" v-model="formData.serviceAddress"
|
|
|
+ placeholder="详细地址 (街道/路名/门牌号)" />
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
<view class="booking-section">
|
|
|
<view class="booking-header">
|
|
|
<text class="label require">预约服务时间</text>
|
|
|
- <view class="count-tag">共 {{ formData.feedTimes.length }} 次</view>
|
|
|
+ <text class="count-tag">共 {{ formData.feedTimes.length }} 次</text>
|
|
|
</view>
|
|
|
<view class="time-item-row" v-for="(time, index) in formData.feedTimes" :key="index">
|
|
|
- <text class="index">{{ index + 1 }}.</text>
|
|
|
- <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-time-box" @click="openTimeModal('feed', index, 'end')">
|
|
|
- <text :class="['time-text', !time.end ? 'placeholder' : '']">{{ truncateTime(time.end) || '结束' }}</text>
|
|
|
+ <view class="flex-time-range" @click="openTimeModal('feed', index)">
|
|
|
+ <text :class="['time-text', !time.start && !time.end ? 'placeholder' : '']">{{
|
|
|
+ feedTimeDisplay(time) || '开始 ~ 结束' }}</text>
|
|
|
</view>
|
|
|
<view class="action-buttons">
|
|
|
- <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 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>
|
|
|
|
|
|
@@ -174,7 +186,8 @@
|
|
|
<view class="card quote-card">
|
|
|
<view class="field-item">
|
|
|
<text class="field-label require">报价金额</text>
|
|
|
- <input class="field-input quote-input" v-model="formData.quoteAmount" type="digit" placeholder="填入数字" />
|
|
|
+ <input class="field-input quote-input" v-model="formData.quoteAmount" type="digit"
|
|
|
+ placeholder="填入数字" />
|
|
|
<text class="unit-text">元</text>
|
|
|
</view>
|
|
|
<text class="quote-tips">注:此价格将作为订单最终结算金额。</text>
|
|
|
@@ -192,9 +205,12 @@
|
|
|
</view>
|
|
|
|
|
|
<!-- 居中联动选择弹窗群 @Author: Antigravity -->
|
|
|
-
|
|
|
+
|
|
|
<!-- 宠主搜索弹窗 -->
|
|
|
- <page-select v-model="showUserSelect" title="选择宠主用户" searchable :searchKey="userSearchKey" searchPlaceholder="搜索宠主姓名/手机号" :options="userList" labelKey="name" valueKey="id" :value="formData.customerId" :loading="userPage.loading" :finished="userPage.finished" emptyText="未找到相关宠主" @select="onUserSelect" @loadMore="fetchUsers(false)" @search="onUserSearch">
|
|
|
+ <page-select v-model="showUserSelect" title="选择宠主用户" searchable :searchKey="userSearchKey"
|
|
|
+ searchPlaceholder="搜索宠主姓名/手机号" :options="userList" labelKey="name" valueKey="id"
|
|
|
+ :value="formData.customerId" :loading="userPage.loading" :finished="userPage.finished" emptyText="未找到相关宠主"
|
|
|
+ @select="onUserSelect" @loadMore="fetchUsers(false)" @search="onUserSearch">
|
|
|
<template #item="{ item }">
|
|
|
<view class="user-info">
|
|
|
<text class="name">{{ item.name }}</text>
|
|
|
@@ -202,17 +218,22 @@
|
|
|
</view>
|
|
|
</template>
|
|
|
</page-select>
|
|
|
-
|
|
|
+
|
|
|
<!-- 区域选择器 (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="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 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 class="modal-list-scroll">
|
|
|
- <view class="list-item" v-for="item in currentRegionList" :key="item.code" @click="onRegionStepSelect(item)">
|
|
|
+ <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>
|
|
|
@@ -220,26 +241,65 @@
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
- <!-- 门店选择 -->
|
|
|
- <page-select v-model="showShopSelect" title="选择服务门店" searchable :searchKey="shopSearchKey" searchPlaceholder="搜索门店名称" :options="shopList" labelKey="name" valueKey="id" :value="formData.merchantId" :loading="shopPage.loading" :finished="shopPage.finished" @select="onShopSelect" @loadMore="fetchShops(true)" @search="onShopSearch" />
|
|
|
-
|
|
|
+ <!-- 门店选择 -->
|
|
|
+ <page-select v-model="showShopSelect" title="选择服务门店" searchable :searchKey="shopSearchKey"
|
|
|
+ searchPlaceholder="搜索门店名称" :options="shopList" labelKey="name" valueKey="id" :value="formData.merchantId"
|
|
|
+ :loading="shopPage.loading" :finished="shopPage.finished" @select="onShopSelect"
|
|
|
+ @loadMore="fetchShops(true)" @search="onShopSearch" />
|
|
|
+
|
|
|
<!-- 宠物选择 -->
|
|
|
- <center-select v-model="showPetPopup" title="选择指定宠物" :options="petOptions" labelKey="_label" valueKey="id" :value="formData.petId" @select="onPetSelect" />
|
|
|
+ <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>
|
|
|
+ <template v-if="isDualTimePicker">
|
|
|
+ <view class="time-slot-row">
|
|
|
+ <view :class="['time-slot', { active: activeSlot === 'start' }]"
|
|
|
+ @click="activeSlot = 'start'">
|
|
|
+ <text class="slot-label">开始时间</text>
|
|
|
+ <text :class="['slot-value', !tempStartDisplay ? 'placeholder' : '']">{{
|
|
|
+ tempStartDisplay || '请选择' }}</text>
|
|
|
+ </view>
|
|
|
+ <view :class="['time-slot', { active: activeSlot === 'end' }]" @click="activeSlot = 'end'">
|
|
|
+ <text class="slot-label">结束时间</text>
|
|
|
+ <text :class="['slot-value', !tempEndDisplay ? 'placeholder' : '']">{{ tempEndDisplay ||
|
|
|
+ '请选择' }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <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>
|
|
|
+ </template>
|
|
|
+ <template v-else>
|
|
|
+ <picker-view class="picker-view" :value="tempSingleIdx"
|
|
|
+ @change="(e) => tempSingleIdx = e.detail.value">
|
|
|
+ <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>
|
|
|
+ </template>
|
|
|
</view>
|
|
|
<view class="modal-footer">
|
|
|
<view class="modal-cancel" @click="showTimeModal = false">取消</view>
|
|
|
- <view class="modal-confirm" @click="confirmTime">确定</view>
|
|
|
+ <view class="modal-confirm" @click="confirmTime">确认</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
@@ -295,16 +355,24 @@ const serviceAreaLabel = ref('')
|
|
|
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: '',
|
|
|
+ pickArea: '', pickAddress: '', pickEndArea: '', pickEndAddress: '', pickContact: '', pickPhone: '',
|
|
|
+ pickStartTime: '', pickEndTime: '',
|
|
|
+ sendStartArea: '', sendStartAddress: '', sendArea: '', sendAddress: '', sendContact: '', sendPhone: '',
|
|
|
+ sendStartTime: '', sendEndTime: '',
|
|
|
serviceArea: '', serviceAddress: '', feedTimes: [{ start: '', end: '' }],
|
|
|
otherNote: '', transportNote: '', quoteAmount: ''
|
|
|
})
|
|
|
|
|
|
// 时间选择器逻辑 (5分钟一个间隔 @Author: Antigravity)
|
|
|
const timeRanges = ref([[], [], []])
|
|
|
+const activeSlot = ref('start')
|
|
|
+const tempStartIdx = ref([0, 0, 0])
|
|
|
+const tempEndIdx = ref([0, 0, 0])
|
|
|
const tempTimeIdx = ref([0, 0, 0])
|
|
|
-const timeCtx = reactive({ type: '', index: 0, field: '' })
|
|
|
+const tempSingleIdx = ref([0, 0, 0])
|
|
|
+const timeCtx = reactive({ type: '', index: 0 })
|
|
|
+
|
|
|
+const isDualTimePicker = computed(() => timeCtx.type === 'feed')
|
|
|
|
|
|
onLoad((options) => {
|
|
|
if (options.service) activeService.value = options.service
|
|
|
@@ -329,29 +397,100 @@ const initTimeRanges = () => {
|
|
|
]
|
|
|
}
|
|
|
|
|
|
-const openTimeModal = (type, index = 0, field = '') => {
|
|
|
- timeCtx.type = type; timeCtx.index = index; timeCtx.field = field
|
|
|
- tempTimeIdx.value = [0, 0, 0]
|
|
|
+const openTimeModal = (type, index = 0) => {
|
|
|
+ timeCtx.type = type; timeCtx.index = index
|
|
|
+ activeSlot.value = 'start'
|
|
|
+ const readTime = (t) => {
|
|
|
+ if (!t) return null
|
|
|
+ const match = t.match(/(\d{2})-(\d{2}) (\d{2}):(\d{2})/)
|
|
|
+ if (!match) return null
|
|
|
+ const monthDay = `${match[1]}-${match[2]}`
|
|
|
+ const di = timeRanges.value[0].findIndex(d => d === monthDay)
|
|
|
+ const hi = parseInt(match[3])
|
|
|
+ const mi = timeRanges.value[2].findIndex(m => parseInt(m) === parseInt(match[4]))
|
|
|
+ return [di < 0 ? 0 : di, hi < 0 ? 0 : hi, mi < 0 ? 0 : mi]
|
|
|
+ }
|
|
|
+ const nowIdx = () => {
|
|
|
+ const now = new Date()
|
|
|
+ const monthDay = `${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`
|
|
|
+ const di = timeRanges.value[0].findIndex(d => d === monthDay)
|
|
|
+ const hi = now.getHours()
|
|
|
+ const mi = timeRanges.value[2].findIndex(m => parseInt(m) >= now.getMinutes())
|
|
|
+ return [di < 0 ? 0 : di, hi < 0 ? 0 : hi, mi < 0 ? 0 : mi]
|
|
|
+ }
|
|
|
+ if (type === 'feed') {
|
|
|
+ tempStartIdx.value = readTime(formData.feedTimes[index].start) || nowIdx()
|
|
|
+ tempEndIdx.value = readTime(formData.feedTimes[index].end) || nowIdx()
|
|
|
+ tempTimeIdx.value = [...tempStartIdx.value]
|
|
|
+ } else if (type === 'pick') {
|
|
|
+ tempSingleIdx.value = readTime(formData.pickStartTime) || nowIdx()
|
|
|
+ } else if (type === 'send') {
|
|
|
+ tempSingleIdx.value = readTime(formData.sendStartTime) || nowIdx()
|
|
|
+ }
|
|
|
showTimeModal.value = true
|
|
|
}
|
|
|
|
|
|
-const onTempTimeChange = (e) => { tempTimeIdx.value = e.detail.value }
|
|
|
+const onTempTimeChange = (e) => {
|
|
|
+ tempTimeIdx.value = e.detail.value
|
|
|
+ if (activeSlot.value === 'start') {
|
|
|
+ tempStartIdx.value = [...e.detail.value]
|
|
|
+ } else {
|
|
|
+ tempEndIdx.value = [...e.detail.value]
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const buildTimeLabel = (idx) => {
|
|
|
+ const d = timeRanges.value[0][idx[0]]
|
|
|
+ const h = timeRanges.value[1][idx[1]]
|
|
|
+ const m = timeRanges.value[2][idx[2]]
|
|
|
+ return d && h !== undefined && m !== undefined ? `${d} ${h}:${m}` : ''
|
|
|
+}
|
|
|
+
|
|
|
+const tempStartDisplay = computed(() => buildTimeLabel(tempStartIdx.value))
|
|
|
+const tempEndDisplay = computed(() => buildTimeLabel(tempEndIdx.value))
|
|
|
+
|
|
|
+watch(activeSlot, (val) => {
|
|
|
+ tempTimeIdx.value = val === 'start' ? [...tempStartIdx.value] : [...tempEndIdx.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
|
|
|
+ const buildTime = (idx) => {
|
|
|
+ const [di, hi, mi] = idx
|
|
|
+ return `${new Date().getFullYear()}-${timeRanges.value[0][di]} ${timeRanges.value[1][hi]}:${timeRanges.value[2][mi]}:00`
|
|
|
+ }
|
|
|
+ if (timeCtx.type === 'feed') {
|
|
|
+ const startVal = buildTime(tempStartIdx.value)
|
|
|
+ const endVal = buildTime(tempEndIdx.value)
|
|
|
+ formData.feedTimes[timeCtx.index].start = startVal
|
|
|
+ formData.feedTimes[timeCtx.index].end = endVal
|
|
|
+ } else if (timeCtx.type === 'pick') {
|
|
|
+ const val = buildTime(tempSingleIdx.value)
|
|
|
+ formData.pickStartTime = val
|
|
|
+ formData.pickEndTime = val
|
|
|
+ } else if (timeCtx.type === 'send') {
|
|
|
+ const val = buildTime(tempSingleIdx.value)
|
|
|
+ formData.sendStartTime = val
|
|
|
+ formData.sendEndTime = 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 feedTimeDisplay = (time) => {
|
|
|
+ const s = truncateTime(time.start)
|
|
|
+ const e = truncateTime(time.end)
|
|
|
+ return s && e ? `${s} ~ ${e}` : (s || e || '')
|
|
|
+}
|
|
|
+const pickTimeDisplay = computed(() => {
|
|
|
+ const s = truncateTime(formData.pickStartTime)
|
|
|
+ const e = truncateTime(formData.pickEndTime)
|
|
|
+ return s && e ? `${s} ~ ${e}` : (s || e || '')
|
|
|
+})
|
|
|
+const sendTimeDisplay = computed(() => {
|
|
|
+ const s = truncateTime(formData.sendStartTime)
|
|
|
+ const e = truncateTime(formData.sendEndTime)
|
|
|
+ return s && e ? `${s} ~ ${e}` : (s || e || '')
|
|
|
+})
|
|
|
|
|
|
// 区域选择逻辑
|
|
|
const regionPath = ref([])
|
|
|
@@ -366,10 +505,10 @@ const currentRegionList = computed(() => {
|
|
|
return list
|
|
|
})
|
|
|
|
|
|
-const openRegionSelect = (type) => {
|
|
|
+const openRegionSelect = (type) => {
|
|
|
activeRegionType.value = type
|
|
|
regionPath.value = []
|
|
|
- showRegionModal.value = true
|
|
|
+ showRegionModal.value = true
|
|
|
}
|
|
|
|
|
|
const backToLevel = (idx) => { regionPath.value = regionPath.value.slice(0, idx) }
|
|
|
@@ -396,7 +535,7 @@ const isRegionSelected = (item) => {
|
|
|
// 核心回填逻辑修正 @Author: Antigravity
|
|
|
watch([selectedShop, selectedUser, regionTree], ([shop, user, tree]) => {
|
|
|
if (!shop && !user) return
|
|
|
-
|
|
|
+
|
|
|
// 处理门店信息
|
|
|
const storeAreaCode = (shop?.areaCode || '').replace(/,/g, '/')
|
|
|
const storeLeaf = storeAreaCode.split('/').pop() || ''
|
|
|
@@ -406,7 +545,7 @@ watch([selectedShop, selectedUser, regionTree], ([shop, user, 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
|
|
|
// 接送单终点 = 门店
|
|
|
@@ -499,7 +638,7 @@ const onShopSelect = (shop) => { selectedShop.value = shop; showShopSelect.value
|
|
|
const onShopSearch = (keyword) => { shopSearchKey.value = keyword; fetchShops(false) }
|
|
|
const onUserSearch = (keyword) => { userSearchKey.value = keyword; fetchUsers(true) }
|
|
|
const onUserSelect = (user) => {
|
|
|
- selectedUser.value = user; formData.customerId = user.id;
|
|
|
+ selectedUser.value = user; formData.customerId = user.id;
|
|
|
formData.petId = ''; formData.petName = ''; petList.value = []; fetchPets(user.id)
|
|
|
showUserSelect.value = false
|
|
|
}
|
|
|
@@ -523,8 +662,8 @@ const onSubmit = async () => {
|
|
|
const baseMode = serviceInfo.value?.mode || 0
|
|
|
const defC = selectedUser.value?.name; const defP = selectedUser.value?.phone || selectedUser.value?.phoneNumber
|
|
|
if (activeService.value === 'transport') {
|
|
|
- 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 })
|
|
|
+ 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.pickStartTime, endServiceTime: formData.pickEndTime, 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.sendStartTime, endServiceTime: formData.sendEndTime, fromCode: formData.sendStartArea, fromAddress: formData.sendStartAddress, toCode: formData.sendArea, toAddress: formData.sendAddress })
|
|
|
} else {
|
|
|
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 }))
|
|
|
}
|
|
|
@@ -542,21 +681,68 @@ const onSubmit = async () => {
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
/* 统一页面字体栈 @Author: Antigravity */
|
|
|
-.order-apply-page {
|
|
|
- background: #f7f8fa;
|
|
|
- min-height: 100vh;
|
|
|
- padding-bottom: 220rpx;
|
|
|
+.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); }
|
|
|
+.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;
|
|
|
@@ -573,70 +759,652 @@ const onSubmit = async () => {
|
|
|
}
|
|
|
|
|
|
/* 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; } }
|
|
|
+.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,
|
|
|
+.remarks-title {
|
|
|
+ display: block;
|
|
|
+ font-size: 26rpx;
|
|
|
+ color: #666;
|
|
|
+ margin: 20rpx 0 10rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.booking-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ margin: 20rpx 0 10rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.booking-header .label {
|
|
|
+ display: block;
|
|
|
+ font-size: 26rpx;
|
|
|
+ color: #666;
|
|
|
+}
|
|
|
+
|
|
|
+.booking-section {
|
|
|
+ margin-top: 24rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.count-tag {
|
|
|
+ font-size: 20rpx;
|
|
|
+ color: #f44336;
|
|
|
+ padding: 4rpx 0;
|
|
|
+}
|
|
|
+
|
|
|
+.time-item-row {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12rpx;
|
|
|
+ margin-bottom: 16rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.flex-time-range {
|
|
|
+ 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; } }
|
|
|
+.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;
|
|
|
|
|
|
-.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); } }
|
|
|
+ .path-node {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #666;
|
|
|
|
|
|
-.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); }
|
|
|
+ &.active {
|
|
|
+ color: #ff9500;
|
|
|
+ font-weight: bold;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.datetime-picker-body {
|
|
|
+ padding: 20rpx 0;
|
|
|
+}
|
|
|
|
|
|
-.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; } } }
|
|
|
+.time-slot-row {
|
|
|
+ display: flex;
|
|
|
+ padding: 0 20rpx;
|
|
|
+ margin-bottom: 20rpx;
|
|
|
+ gap: 16rpx;
|
|
|
+}
|
|
|
|
|
|
-.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; } }
|
|
|
+.time-slot {
|
|
|
+ flex: 1;
|
|
|
+ border: 2rpx solid #eee;
|
|
|
+ border-radius: 12rpx;
|
|
|
+ padding: 16rpx;
|
|
|
+ text-align: center;
|
|
|
|
|
|
-.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; }
|
|
|
-.user-info { display: flex; flex-direction: column; flex: 1; .name { font-size: 28rpx; font-weight: bold; color: #333; } .phone { font-size: 22rpx; color: #999; margin-top: 4rpx; } }
|
|
|
+ &.active {
|
|
|
+ border-color: #ff9500;
|
|
|
+ background: #fff8f0;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.slot-label {
|
|
|
+ display: block;
|
|
|
+ font-size: 22rpx;
|
|
|
+ color: #999;
|
|
|
+ margin-bottom: 6rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.slot-value {
|
|
|
+ display: block;
|
|
|
+ font-size: 26rpx;
|
|
|
+ color: #333;
|
|
|
+ font-weight: 500;
|
|
|
+
|
|
|
+ &.placeholder {
|
|
|
+ color: #ccc;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.picker-view {
|
|
|
+ width: 100%;
|
|
|
+ height: 360rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.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;
|
|
|
+}
|
|
|
+
|
|
|
+.user-info {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ flex: 1;
|
|
|
+
|
|
|
+ .name {
|
|
|
+ font-size: 28rpx;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #333;
|
|
|
+ }
|
|
|
+
|
|
|
+ .phone {
|
|
|
+ font-size: 22rpx;
|
|
|
+ color: #999;
|
|
|
+ margin-top: 4rpx;
|
|
|
+ }
|
|
|
+}
|
|
|
</style>
|