first commit
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
<script setup lang="ts">
|
||||
import {UserAddressType} from "@/constant/enums";
|
||||
import {useAddressStore} from "@/pages/address/store/address";
|
||||
const addressStore = useAddressStore()
|
||||
const { t } = useI18n()
|
||||
|
||||
const typeList = ref([
|
||||
{
|
||||
label: t('pages.address.choose-type.house'),
|
||||
desc: t('pages.address.choose-type.houseDescription'),
|
||||
value: UserAddressType.HOUSE,
|
||||
icon: '/static/images/chef/147.png',
|
||||
},
|
||||
{
|
||||
label: t('pages.address.choose-type.apartment'),
|
||||
desc: t('pages.address.choose-type.apartmentDescription'),
|
||||
value: UserAddressType.APARTMENT,
|
||||
icon: '/static/images/chef/148.png',
|
||||
},
|
||||
{
|
||||
label: t('pages.address.choose-type.office'),
|
||||
desc: t('pages.address.choose-type.officeDescription'),
|
||||
value: UserAddressType.OFFICE,
|
||||
icon: '/static/images/chef/149.png',
|
||||
},
|
||||
{
|
||||
label: t('pages.address.choose-type.hotel'),
|
||||
desc: t('pages.address.choose-type.hotelDescription'),
|
||||
value: UserAddressType.HOTEL,
|
||||
icon: '/static/images/chef/150.png',
|
||||
},
|
||||
{
|
||||
label: t('pages.address.choose-type.other'),
|
||||
desc: t('pages.address.choose-type.otherDescription'),
|
||||
value: UserAddressType.OTHER,
|
||||
icon: '/static/images/chef/151.png',
|
||||
},
|
||||
])
|
||||
|
||||
function chooseType(item: any) {
|
||||
addressStore.addressInfo.type = item.value
|
||||
switch (item.value) {
|
||||
case UserAddressType.HOUSE:
|
||||
uni.redirectTo({
|
||||
url: '/pages/address/save-address/house',
|
||||
})
|
||||
break;
|
||||
case UserAddressType.APARTMENT:
|
||||
uni.redirectTo({
|
||||
url: '/pages/address/save-address/apartment',
|
||||
})
|
||||
break;
|
||||
case UserAddressType.OFFICE:
|
||||
uni.redirectTo({
|
||||
url: '/pages/address/save-address/office',
|
||||
})
|
||||
break;
|
||||
case UserAddressType.HOTEL:
|
||||
uni.redirectTo({
|
||||
url: '/pages/address/save-address/hotel',
|
||||
})
|
||||
break;
|
||||
case UserAddressType.OTHER:
|
||||
uni.redirectTo({
|
||||
url: '/pages/address/save-address/other',
|
||||
})
|
||||
break;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view>
|
||||
<navbar :title="t('pages.address.choose-type.navTitle')">
|
||||
<template #right>
|
||||
<text @click="chooseType({
|
||||
value: UserAddressType.OTHER
|
||||
})" class="text-#CE7138 text-36rpx">{{ t('common.skip') }}</text>
|
||||
</template>
|
||||
</navbar>
|
||||
<view class="px-30rpx pt-64rpx">
|
||||
<view class="text-40rpx lh-40rpx text-#333 font-500">{{ t('pages.address.choose-type.title') }}</view>
|
||||
<view class="mt-28rpx text-28rpx lh-32rpx text-#666 mb-52rpx">
|
||||
{{ t('pages.address.choose-type.description') }}
|
||||
</view>
|
||||
|
||||
<template v-for="(item, index) in typeList">
|
||||
<view @click="chooseType(item)" :class="[index === 0 ? '' : 'mt-36rpx']" class="w-full border-#DFDFDF border-solid border-1px rounded-16rpx px-28rpx py-34rpx flex-center-sb">
|
||||
<view class="flex items-center">
|
||||
<image :src="item.icon" class="w-48rpx h-48rpx shrink-0 mr-28rpx"></image>
|
||||
<view>
|
||||
<view class="text-36rpx lh-36rpx text-#333 font-500">{{ item.label }}</view>
|
||||
<view class="text-28rpx lh-28rpx text-#6D6D6D mt-18rpx">{{ item.desc }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<image src="@img/chef/142.png" class="w-32rpx h-32rpx shrink-0"></image>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
page {
|
||||
background-color: #fff;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,46 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
const {t} = useI18n();
|
||||
const emit = defineEmits(['confirm'])
|
||||
|
||||
const show = ref(false)
|
||||
const stateList = ref<Record<string, any>>([])
|
||||
const stateListValue = ref('')
|
||||
|
||||
const handleClickStatePicker = () => {
|
||||
show.value = false
|
||||
emit('confirm', {
|
||||
id: stateListValue.value,
|
||||
name: stateList.value.find(item => item.id === stateListValue.value)?.name || ''
|
||||
})
|
||||
}
|
||||
|
||||
function init(data: any, id: any) {
|
||||
stateList.value = data
|
||||
stateListValue.value = id
|
||||
show.value = true
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
init
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<wd-popup v-model="show" :safe-area-inset-bottom="true" custom-style="border-radius:20rpx 20rpx 0 0;"
|
||||
position="bottom">
|
||||
<view class="px-30rpx">
|
||||
<view class="flex-center-sb h-102rpx">
|
||||
<view class="text-30rpx text-#999" @click="show = false">{{ t('common.cancel') }}</view>
|
||||
<view class="text-34rpx text-#333">{{ t('common.select') }}</view>
|
||||
<view class="text-30rpx text-#FF6106" @click="handleClickStatePicker">{{ t('common.confirm') }}</view>
|
||||
</view>
|
||||
|
||||
<wd-picker-view v-model="stateListValue" :columns="stateList" label-key="name" value-key="id"/>
|
||||
</view>
|
||||
</wd-popup>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,9 @@
|
||||
<template>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// 骨架屏组件
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,224 @@
|
||||
<script setup lang="ts">
|
||||
import Search from "@/pages/home/components/tabbar-home/components/search.vue";
|
||||
import {useUserStore} from "@/store";
|
||||
import useEventEmit from "@/hooks/useEventEmit";
|
||||
import {EventEnum, UserAddressType} from "@/constant/enums";
|
||||
import {appAppointmentTimeUpdateAppointmentTimePost, appUserAddressListPost, appUserAddressRemovePost} from "@/service";
|
||||
const { t } = useI18n();
|
||||
const userStore = useUserStore();
|
||||
import {useAddressStore} from "./store/address";
|
||||
const addressStore = useAddressStore()
|
||||
import {useMessage} from "wot-design-uni";
|
||||
const message = useMessage();
|
||||
|
||||
function handleClickSearch() {
|
||||
uni.navigateTo({
|
||||
url: '/pages-user/pages/search-address/index',
|
||||
});
|
||||
}
|
||||
|
||||
useEventEmit(EventEnum.CHOOSE_ADDRESS, (data) => {
|
||||
console.log('搜索的地址信息', data)
|
||||
if(data) {
|
||||
// 从addressComponents中提取州名/省名
|
||||
// let stateName = '';
|
||||
// if (data.addressComponents && Array.isArray(data.addressComponents)) {
|
||||
// // 先判断是否为中国地址
|
||||
// const countryComponent = data.addressComponents.find(component =>
|
||||
// component.types && component.types.includes('country')
|
||||
// );
|
||||
// const isChina = countryComponent && (countryComponent.shortText === 'CN' || countryComponent.longText === '中国');
|
||||
//
|
||||
// if (isChina) {
|
||||
// // 中国地址:优先取市级(locality),如果没有则取省级(administrative_area_level_1)
|
||||
// const cityComponent = data.addressComponents.find(component =>
|
||||
// component.types && component.types.includes('locality')
|
||||
// );
|
||||
// const provinceComponent = data.addressComponents.find(component =>
|
||||
// component.types && component.types.includes('administrative_area_level_1')
|
||||
// );
|
||||
//
|
||||
// if (cityComponent) {
|
||||
// stateName = cityComponent.longText || cityComponent.shortText || '';
|
||||
// } else if (provinceComponent) {
|
||||
// stateName = provinceComponent.longText || provinceComponent.shortText || '';
|
||||
// }
|
||||
// } else {
|
||||
// // 美国等其他国家:取州级(administrative_area_level_1)
|
||||
// const stateComponent = data.addressComponents.find(component =>
|
||||
// component.types && component.types.includes('administrative_area_level_1')
|
||||
// );
|
||||
// if (stateComponent) {
|
||||
// stateName = stateComponent.longText || stateComponent.shortText || '';
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
addressStore.setAddressLocation({
|
||||
displayName: data.displayName,
|
||||
formattedAddress: data.formattedAddress,
|
||||
longitude: data.location.lng,
|
||||
latitude: data.location.lat
|
||||
})
|
||||
setTimeout(()=> {
|
||||
uni.navigateTo({
|
||||
url: '/pages/address/choose-type'
|
||||
})
|
||||
}, 300)
|
||||
}
|
||||
})
|
||||
|
||||
function reservationTime() {
|
||||
uni.navigateTo({ url: '/pages/address/reservation-time' })
|
||||
}
|
||||
|
||||
// 获取用户地址列表
|
||||
const addressesList = ref([])
|
||||
function getAddressList() {
|
||||
appUserAddressListPost({
|
||||
params: {
|
||||
pageNum: 1,
|
||||
pageSize: 100,
|
||||
}
|
||||
}).then(res => {
|
||||
console.log('appAddressListGet', res)
|
||||
addressesList.value = res.rows
|
||||
})
|
||||
}
|
||||
|
||||
onShow(()=> {
|
||||
getAddressList()
|
||||
})
|
||||
|
||||
function chooseType(item: any) {
|
||||
addressStore.addressInfo = item
|
||||
switch (item.type) {
|
||||
case UserAddressType.HOUSE:
|
||||
uni.navigateTo({
|
||||
url: '/pages/address/save-address/house',
|
||||
})
|
||||
break;
|
||||
case UserAddressType.APARTMENT:
|
||||
uni.navigateTo({
|
||||
url: '/pages/address/save-address/apartment',
|
||||
})
|
||||
break;
|
||||
case UserAddressType.OFFICE:
|
||||
uni.navigateTo({
|
||||
url: '/pages/address/save-address/office',
|
||||
})
|
||||
break;
|
||||
case UserAddressType.HOTEL:
|
||||
uni.navigateTo({
|
||||
url: '/pages/address/save-address/hotel',
|
||||
})
|
||||
break;
|
||||
case UserAddressType.OTHER:
|
||||
uni.navigateTo({
|
||||
url: '/pages/address/save-address/other',
|
||||
})
|
||||
break;
|
||||
}
|
||||
}
|
||||
function deleteAddress(item: any) {
|
||||
message
|
||||
.confirm({
|
||||
title: t("common.prompt.system-prompt"),
|
||||
msg: `${t("common.prompt.system-prompt-delete")}`,
|
||||
confirmButtonText: t("common.yes"),
|
||||
cancelButtonText: t("common.no"),
|
||||
cancelButtonProps: {
|
||||
customClass:
|
||||
"!h-88rpx !w-258rpx !min-w-auto !text-30rpx !lh-42rpx !font-bold !border-#666666 !rounded-20rpx",
|
||||
},
|
||||
confirmButtonProps: {
|
||||
customClass:
|
||||
"!h-88rpx !w-258rpx !min-w-auto !text-30rpx !lh-42rpx !font-bold !bg-primary !rounded-20rpx",
|
||||
},
|
||||
})
|
||||
.then(async () => {
|
||||
appUserAddressRemovePost({
|
||||
body: [item.id]
|
||||
}).then((res) => {
|
||||
console.log('删除地址', res)
|
||||
uni.showToast({
|
||||
title: t('toast.deleteSuccess'),
|
||||
icon: 'none'
|
||||
})
|
||||
getAddressList()
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
});
|
||||
}
|
||||
|
||||
useEventEmit(EventEnum.CHOOSE_APPOINTMENT_TIME, (data) => {
|
||||
console.log('CHOOSE_APPOINTMENT_TIME', data)
|
||||
if(data) {
|
||||
appAppointmentTimeUpdateAppointmentTimePost({
|
||||
body: {
|
||||
endTime: data.endTime,
|
||||
startTime: data.startTime,
|
||||
}
|
||||
}).then(res=> {
|
||||
userStore.getAppointmentTime()
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view>
|
||||
<navbar :title="t('pages.address.title')" />
|
||||
<view class="mt-32rpx px-30rpx">
|
||||
<search :is-auto-jump="false" @clickSearch="handleClickSearch" />
|
||||
</view>
|
||||
<view class="mt-64rpx text-40rpx lh-40rpx text-#333 font-bold pl-30rpx pb-24rpx">
|
||||
{{ t('pages.address.savedAddresses') }}
|
||||
</view>
|
||||
<template v-for="item in addressesList">
|
||||
<wd-swipe-action>
|
||||
<!--:class="item === 1 ? 'bg-#F3F3F3' : ''" -->
|
||||
<view @click="chooseType(item)" class="w-full h-156rpx flex-center-sb px-30rpx">
|
||||
<view class="flex items-center">
|
||||
<!-- <image v-if="item === 1" src="@img/chef/143.png" class="w-44rpx h-44rpx shrink-0 mr-28rpx"></image>-->
|
||||
<image src="@img/chef/145.png" class="w-44rpx h-44rpx shrink-0 mr-28rpx"></image>
|
||||
<view class="flex-1 h-156rpx pt-40rpx">
|
||||
<view class="text-32rpx lh-32rpx text-#333 font-500 mb-16rpx line-clamp-1">{{ item.formattedAddress }}</view>
|
||||
<view class="text-28rpx lh-28rpx text-#6D6D6D">{{ item.displayName || '' }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<image src="@img/chef/144.png" class="w-44rpx h-44rpx shrink-0 pl-30rpx"></image>
|
||||
</view>
|
||||
<template #right>
|
||||
<view class="action flex items-center text-30rpx lh-30rpx text-#fff">
|
||||
<view class="w-152rpx h-156rpx bg-#FF2828 center" @click="deleteAddress(item)">{{ t('common.delete') }}</view>
|
||||
</view>
|
||||
</template>
|
||||
</wd-swipe-action>
|
||||
</template>
|
||||
<template v-if="addressesList.length === 0">
|
||||
<view class="py-100rpx center">
|
||||
<image class="w-250rpx h-250rpx" src="@img/chef/100.png"></image>
|
||||
</view>
|
||||
</template>
|
||||
<view class="mt-44rpx text-40rpx lh-40rpx text-#333 font-bold pl-30rpx">
|
||||
{{ t('pages.address.appointmentTime') }}
|
||||
</view>
|
||||
<view @click="reservationTime" class="flex-center-sb px-30rpx mt-70rpx">
|
||||
<view class="flex items-center">
|
||||
<image src="@img/chef/146.png" class="w-44rpx h-44rpx shrink-0 mr-28rpx"></image>
|
||||
<text class="text-32rpx lh-32rpx text-#333 font-500">{{ t('pages.address.immediateDelivery') }}</text>
|
||||
</view>
|
||||
<view class="h-56rpx px-22rpx bg-#F2F2F2 rounded-56rpx center text-28rpx text-#333">
|
||||
{{ userStore.appointmentTimeShow || t('pages.address.reservation') }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
page {
|
||||
background-color: #fff;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,691 @@
|
||||
<script setup lang="ts">
|
||||
import {EventEnum} from "@/constant/enums";
|
||||
|
||||
const { t } = useI18n();
|
||||
import dayjs from "dayjs";
|
||||
|
||||
// 店铺的营业时间(示例:周一到周五营业,周六周日不营业)
|
||||
// 测试用的营业时间字符串
|
||||
// MONDAY/TUESDAY/WEDNESDAY 09:00-18:00;THURSDAY/FRIDAY 08:00-09:00; // 周一到周五9点到18点,周四到周五8点到9点
|
||||
// MONDAY/TUESDAY/WEDNESDAY 09:00-18:00;THURSDAY/FRIDAY 08:00-12:00;SATURDAY/SUNDAY 10:00-20:00 // 周六周日10点到20点
|
||||
// MONDAY/TUESDAY/WEDNESDAY 09:00-16:00
|
||||
// MONDAY 09:00-18:00;TUESDAY 09:00-10:00;WEDNESDAY 09:00-18:00;THURSDAY 09:00-18:00;FRIDAY 08:00-09:00
|
||||
const storeBusinessHours = ref('');
|
||||
// 是否仅选择日期(当进入页面传递了 storeBusinessHours 时开启)
|
||||
const onlySelectDay = ref(false);
|
||||
|
||||
// 解析商家营业时间的接口
|
||||
interface BusinessHours {
|
||||
days: string[]; // 营业的星期几
|
||||
startTime: string; // 开始时间 HH:mm
|
||||
endTime: string; // 结束时间 HH:mm
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析商家营业时间字符串
|
||||
* @param businessHoursStr 营业时间字符串,支持多种格式:
|
||||
* - MONDAY/TUESDAY/WEDNESDAY 09:00-18:00;THURSDAY/FRIDAY/SATURDAY/SUNDAY 10:00-20:00
|
||||
* - MONDAY/TUESDAY/WEDNESDAY 09:00-18:00
|
||||
* - MONDAY 09:00-18:00;TUESDAY 09:00-10:00;WEDNESDAY 09:00-18:00
|
||||
* @returns 解析后的营业时间数组
|
||||
*/
|
||||
const parseBusinessHours = (businessHoursStr: string): BusinessHours[] => {
|
||||
if (!businessHoursStr) return [];
|
||||
|
||||
const businessHours: BusinessHours[] = [];
|
||||
// 使用分号分割不同的营业时间段
|
||||
const segments = businessHoursStr.split(";");
|
||||
|
||||
segments.forEach((segment) => {
|
||||
const trimmedSegment = segment.trim();
|
||||
if (!trimmedSegment) return;
|
||||
|
||||
// 使用空格分割星期几和时间
|
||||
const parts = trimmedSegment.split(" ");
|
||||
if (parts.length !== 2) return;
|
||||
|
||||
const dayStr = parts[0].trim().toUpperCase();
|
||||
const timeStr = parts[1];
|
||||
|
||||
// 解析时间范围
|
||||
const timeRange = timeStr.split("-");
|
||||
if (timeRange.length !== 2) return;
|
||||
|
||||
const startTime = timeRange[0].trim();
|
||||
const endTime = timeRange[1].trim();
|
||||
|
||||
// 按斜杠分割星期几,支持 MONDAY/TUESDAY/WEDNESDAY 格式
|
||||
const days = dayStr
|
||||
.split("/")
|
||||
.map((day) => day.trim())
|
||||
.filter((day) => day);
|
||||
|
||||
businessHours.push({
|
||||
days, // 支持多个星期几共享同一营业时间
|
||||
startTime,
|
||||
endTime,
|
||||
});
|
||||
});
|
||||
|
||||
return businessHours;
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取指定日期的营业时间
|
||||
* @param date 日期
|
||||
* @returns 营业时间对象,如果不营业返回null
|
||||
*/
|
||||
const getBusinessHoursForDate = (date: Date): BusinessHours | null => {
|
||||
if (!storeBusinessHours.value) return null;
|
||||
|
||||
const businessHours = parseBusinessHours(storeBusinessHours.value);
|
||||
|
||||
// 获取星期几的英文名称(确保是大写)(不要国际化导致判断失效)
|
||||
const dayNames = [
|
||||
"SUNDAY",
|
||||
"MONDAY",
|
||||
"TUESDAY",
|
||||
"WEDNESDAY",
|
||||
"THURSDAY",
|
||||
"FRIDAY",
|
||||
"SATURDAY",
|
||||
];
|
||||
const dayName = dayNames[date.getDay()];
|
||||
|
||||
// 查找包含当前星期几的营业时间
|
||||
return businessHours.find((hours) => hours.days.includes(dayName)) || null;
|
||||
};
|
||||
|
||||
/**
|
||||
* 检查日期是否营业
|
||||
* @param date 日期
|
||||
* @returns 是否营业
|
||||
*/
|
||||
const isDateOpen = (date: Date): boolean => {
|
||||
if (!storeBusinessHours.value) return true; // 如果没有营业时间限制,默认营业
|
||||
return getBusinessHoursForDate(date) !== null;
|
||||
};
|
||||
|
||||
/**
|
||||
* 检查日期是否应该显示为可选择状态(营业且有可用时间段)
|
||||
* @param date 日期
|
||||
* @returns 是否可选择
|
||||
*/
|
||||
const isDateSelectable = (date: Date): boolean => {
|
||||
// 如果只选日期模式,营业即可选择
|
||||
if (onlySelectDay.value) {
|
||||
if (!storeBusinessHours.value) return true;
|
||||
return isDateOpen(date);
|
||||
}
|
||||
|
||||
// 原逻辑:需要营业且有可用时间段
|
||||
if (!storeBusinessHours.value) return true; // 如果没有营业时间限制,默认可选择
|
||||
if (!isDateOpen(date)) return false;
|
||||
return hasAvailableTimeSlots(date);
|
||||
};
|
||||
|
||||
// 生成未来的日期(显示所有日期,但标记营业状态)
|
||||
const dateOptions = computed(() => {
|
||||
const dates: Date[] = [];
|
||||
const today = new Date();
|
||||
|
||||
// 生成连续的5天日期(包括不营业的日期)
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const date = new Date(today);
|
||||
date.setDate(today.getDate() + i);
|
||||
dates.push(date);
|
||||
}
|
||||
|
||||
return dates;
|
||||
});
|
||||
|
||||
// 状态管理 - 初始化为第一个营业日期
|
||||
const selectedDate = ref<Date>();
|
||||
|
||||
// 检查指定时间是否在营业时间内
|
||||
const isTimeInBusinessHours = (
|
||||
hour: number,
|
||||
minute: number,
|
||||
businessHours: BusinessHours
|
||||
): boolean => {
|
||||
const timeStr = `${hour.toString().padStart(2, "0")}:${minute
|
||||
.toString()
|
||||
.padStart(2, "0")}`;
|
||||
return timeStr >= businessHours.startTime && timeStr <= businessHours.endTime;
|
||||
};
|
||||
|
||||
// 检查指定日期是否有可用时间段
|
||||
const hasAvailableTimeSlots = (date: Date): boolean => {
|
||||
const now = new Date();
|
||||
const currentHour = now.getHours();
|
||||
const currentMinute = now.getMinutes();
|
||||
|
||||
// 获取指定日期的营业时间
|
||||
const businessHours = getBusinessHoursForDate(date);
|
||||
|
||||
// 如果没有营业时间,返回false
|
||||
if (!businessHours) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 解析营业时间的开始和结束时间
|
||||
const [startHour, startMinute] = businessHours.startTime
|
||||
.split(":")
|
||||
.map(Number);
|
||||
const [endHour, endMinute] = businessHours.endTime.split(":").map(Number);
|
||||
|
||||
// 检查是否有可用时间段
|
||||
for (let hour = startHour; hour <= endHour; hour++) {
|
||||
for (let minute = 0; minute < 60; minute += 30) {
|
||||
// 检查时间段开始时间是否在营业时间内
|
||||
if (!isTimeInBusinessHours(hour, minute, businessHours)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 计算时间段结束时间
|
||||
let nextHour = hour;
|
||||
let nextMinute = minute + 30;
|
||||
if (nextMinute >= 60) {
|
||||
nextHour++;
|
||||
nextMinute = 0;
|
||||
}
|
||||
|
||||
// 检查时间段结束时间是否超出营业时间
|
||||
if (!isTimeInBusinessHours(nextHour, nextMinute, businessHours)) {
|
||||
if (
|
||||
nextHour > endHour ||
|
||||
(nextHour === endHour && nextMinute > endMinute)
|
||||
) {
|
||||
nextHour = endHour;
|
||||
nextMinute = endMinute;
|
||||
}
|
||||
}
|
||||
|
||||
// 避免生成开始时间和结束时间相同的无效时间段
|
||||
if (hour === nextHour && minute === nextMinute) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 如果是今天,过滤掉已经过去的时间
|
||||
if (date.toDateString() === now.toDateString()) {
|
||||
if (
|
||||
hour < currentHour ||
|
||||
(hour === currentHour && minute <= currentMinute)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果能到这里,说明有可用时间段
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
// 初始化选中日期为第一个有可用时间段的营业日期
|
||||
const initializeSelectedDate = () => {
|
||||
if (onlySelectDay.value) {
|
||||
// 仅选日期模式:选择第一个营业日期(或第一个日期)
|
||||
const firstOpen = dateOptions.value.find((d) => isDateOpen(d));
|
||||
selectedDate.value = firstOpen || dateOptions.value[0];
|
||||
nextTick(() => updateScrollPosition());
|
||||
return;
|
||||
}
|
||||
|
||||
// 非仅选日期模式:保留原逻辑
|
||||
for (const date of dateOptions.value) {
|
||||
if (isDateSelectable(date)) {
|
||||
selectedDate.value = date;
|
||||
nextTick(() => {
|
||||
updateScrollPosition();
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const today = new Date();
|
||||
const nextBusinessDate = findNextBusinessDate(today);
|
||||
if (nextBusinessDate) {
|
||||
selectedDate.value = nextBusinessDate;
|
||||
nextTick(() => {
|
||||
updateScrollPosition();
|
||||
uni.showToast({
|
||||
title: t('pages.address.reservationTime.currentTimeExpired'),
|
||||
icon: "none",
|
||||
duration: 2000,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
selectedDate.value = dateOptions.value[0];
|
||||
nextTick(() => {
|
||||
updateScrollPosition();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 监听dateOptions变化,初始化选中日期
|
||||
watch(
|
||||
dateOptions,
|
||||
() => {
|
||||
if (!selectedDate.value) {
|
||||
initializeSelectedDate();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// 监听营业时间字符串变化,重新初始化选中日期
|
||||
watch(
|
||||
storeBusinessHours,
|
||||
() => {
|
||||
if (storeBusinessHours.value) {
|
||||
initializeSelectedDate();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
const selectedTimeSlot = ref<string>("");
|
||||
|
||||
// 横向滚动距离
|
||||
const scrollLeft = ref<number>(0);
|
||||
|
||||
// 计算并设置横向滚动距离
|
||||
const updateScrollPosition = () => {
|
||||
if (!selectedDate.value) return;
|
||||
|
||||
// 找到选中日期在 dateOptions 中的索引
|
||||
const selectedIndex = dateOptions.value.findIndex(date =>
|
||||
dayjs(date).isSame(dayjs(selectedDate.value), 'day')
|
||||
);
|
||||
|
||||
if (selectedIndex === -1) return;
|
||||
|
||||
// 每个日期卡片的宽度:240rpx + 28rpx 间距 = 268rpx
|
||||
// 但第一个卡片没有左边距,所以需要特殊处理
|
||||
const cardWidth = 240; // rpx
|
||||
const cardMargin = 28; // rpx
|
||||
|
||||
// 计算滚动距离,让选中的卡片尽量居中显示
|
||||
let scrollDistance = 0;
|
||||
if (selectedIndex > 0) {
|
||||
// 第一个卡片没有左边距,从第二个开始每个卡片占用 240 + 28 = 268rpx
|
||||
scrollDistance = selectedIndex * (cardWidth + cardMargin);
|
||||
|
||||
// 减去一些距离让选中项更居中(可根据屏幕宽度调整)
|
||||
scrollDistance = Math.max(0, scrollDistance - 100);
|
||||
}
|
||||
|
||||
scrollLeft.value = scrollDistance;
|
||||
};
|
||||
|
||||
// 格式化日期显示
|
||||
const formatDateDisplay = (date: Date) => {
|
||||
const today = dayjs();
|
||||
const targetDate = dayjs(date);
|
||||
|
||||
if (targetDate.isSame(today, "day")) {
|
||||
return "Today";
|
||||
} else if (targetDate.isSame(today.add(1, "day"), "day")) {
|
||||
return "Tomorrow";
|
||||
} else {
|
||||
// 返回星期几
|
||||
return targetDate.format("dddd");
|
||||
}
|
||||
};
|
||||
|
||||
// 格式化日期为月份和日期(不包含年份),不可选择日期显示"不营业"
|
||||
const formatDateOnly = (date: Date) => {
|
||||
if (!isDateSelectable(date)) {
|
||||
return t('pages.address.reservationTime.notAvailable')
|
||||
}
|
||||
return dayjs(date).format('MMMM D')
|
||||
};
|
||||
|
||||
/**
|
||||
* 检查时间是否在营业时间内
|
||||
* @param hour 小时
|
||||
* @param minute 分钟
|
||||
* @param businessHours 营业时间对象
|
||||
* @returns 是否在营业时间内
|
||||
*/
|
||||
|
||||
// 生成时间段选项(根据商家营业时间过滤)
|
||||
const timeSlots = computed(() => {
|
||||
const slots: string[] = [];
|
||||
const now = new Date();
|
||||
const currentHour = now.getHours();
|
||||
const currentMinute = now.getMinutes();
|
||||
|
||||
// 如果还没有选中日期,返回空数组
|
||||
if (!selectedDate.value) {
|
||||
return slots;
|
||||
}
|
||||
|
||||
// 获取选中日期的营业时间
|
||||
const businessHours = getBusinessHoursForDate(selectedDate.value);
|
||||
|
||||
// 如果没有营业时间限制,使用原有逻辑(0-24小时)
|
||||
if (!businessHours) {
|
||||
for (let hour = 0; hour < 24; hour++) {
|
||||
for (let minute = 0; minute < 60; minute += 30) {
|
||||
// 如果是今天,过滤掉已经过去的时间
|
||||
if (selectedDate.value.toDateString() === now.toDateString()) {
|
||||
if (
|
||||
hour < currentHour ||
|
||||
(hour === currentHour && minute <= currentMinute)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const startHour = hour.toString().padStart(2, "0");
|
||||
const startMinute = minute.toString().padStart(2, "0");
|
||||
const endHour =
|
||||
minute === 30
|
||||
? (hour + 1).toString().padStart(2, "0")
|
||||
: hour.toString().padStart(2, "0");
|
||||
const endMinute = minute === 30 ? "00" : "30";
|
||||
|
||||
const timeSlot = `${startHour}:${startMinute} - ${endHour}:${endMinute}`;
|
||||
slots.push(timeSlot);
|
||||
}
|
||||
}
|
||||
return slots;
|
||||
}
|
||||
|
||||
// 解析营业时间的开始和结束时间
|
||||
const [startHour, startMinute] = businessHours.startTime
|
||||
.split(":")
|
||||
.map(Number);
|
||||
const [endHour, endMinute] = businessHours.endTime.split(":").map(Number);
|
||||
|
||||
// 生成营业时间内的时间段
|
||||
for (let hour = startHour; hour <= endHour; hour++) {
|
||||
for (let minute = 0; minute < 60; minute += 30) {
|
||||
// 检查时间段开始时间是否在营业时间内
|
||||
if (!isTimeInBusinessHours(hour, minute, businessHours)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 计算时间段结束时间
|
||||
let nextHour = hour;
|
||||
let nextMinute = minute + 30;
|
||||
if (nextMinute >= 60) {
|
||||
nextHour++;
|
||||
nextMinute = 0;
|
||||
}
|
||||
|
||||
// 检查时间段结束时间是否超出营业时间
|
||||
if (!isTimeInBusinessHours(nextHour, nextMinute, businessHours)) {
|
||||
// 如果结束时间超出营业时间,但开始时间在营业时间内,则调整结束时间为营业结束时间
|
||||
if (
|
||||
nextHour > endHour ||
|
||||
(nextHour === endHour && nextMinute > endMinute)
|
||||
) {
|
||||
nextHour = endHour;
|
||||
nextMinute = endMinute;
|
||||
}
|
||||
}
|
||||
|
||||
// 避免生成开始时间和结束时间相同的无效时间段(如 18:00 - 18:00)
|
||||
if (hour === nextHour && minute === nextMinute) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 如果是今天,过滤掉已经过去的时间
|
||||
if (selectedDate.value.toDateString() === now.toDateString()) {
|
||||
if (
|
||||
hour < currentHour ||
|
||||
(hour === currentHour && minute <= currentMinute)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const startHourStr = hour.toString().padStart(2, "0");
|
||||
const startMinuteStr = minute.toString().padStart(2, "0");
|
||||
const endHourStr = nextHour.toString().padStart(2, "0");
|
||||
const endMinuteStr = nextMinute.toString().padStart(2, "0");
|
||||
|
||||
const timeSlot = `${startHourStr}:${startMinuteStr} - ${endHourStr}:${endMinuteStr}`;
|
||||
slots.push(timeSlot);
|
||||
}
|
||||
}
|
||||
|
||||
return slots;
|
||||
});
|
||||
|
||||
// 监听时间段变化,如果当前选中日期没有可用时间段,自动选择下一个营业日期
|
||||
watch(timeSlots, (newSlots) => {
|
||||
if (onlySelectDay.value) return; // 仅选日期模式不需要处理时间段
|
||||
if (selectedDate.value && newSlots.length === 0) {
|
||||
// 当前选中日期没有可用时间段,寻找下一个营业日期
|
||||
const nextBusinessDate = findNextBusinessDate(selectedDate.value);
|
||||
if (nextBusinessDate) {
|
||||
selectedDate.value = nextBusinessDate;
|
||||
selectedTimeSlot.value = ""; // 清空已选择的时间段
|
||||
uni.showToast({
|
||||
title: t('pages.address.reservationTime.noAvailableTime'),
|
||||
icon: "none",
|
||||
duration: 2000,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 寻找下一个有可用时间段的营业日期
|
||||
const findNextBusinessDate = (currentDate: Date): Date | null => {
|
||||
const maxDays = 30; // 最多向前查找30天
|
||||
for (let i = 1; i <= maxDays; i++) {
|
||||
const nextDate = new Date(currentDate);
|
||||
nextDate.setDate(currentDate.getDate() + i);
|
||||
|
||||
// 检查是否在dateOptions范围内
|
||||
const isInRange = dateOptions.value.some((date) =>
|
||||
dayjs(date).isSame(dayjs(nextDate), "day")
|
||||
);
|
||||
|
||||
if (isInRange && isDateOpen(nextDate) && hasAvailableTimeSlots(nextDate)) {
|
||||
return nextDate;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// 选择日期
|
||||
const selectDate = (date: Date) => {
|
||||
// 检查日期是否可选择,如果不可选择则不允许选择
|
||||
if (!isDateSelectable(date)) {
|
||||
uni.showToast({
|
||||
title: t('pages.address.reservationTime.dateNotSelectable'),
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
selectedDate.value = date;
|
||||
// 选择新日期后,清空已选择的时间段
|
||||
selectedTimeSlot.value = "";
|
||||
};
|
||||
|
||||
// 选择时间段
|
||||
const selectTimeSlot = (timeSlot: string) => {
|
||||
selectedTimeSlot.value = timeSlot;
|
||||
};
|
||||
|
||||
// 提交预约
|
||||
const submitReservation = () => {
|
||||
// 非仅选日期模式,需要选择时间段
|
||||
if (!onlySelectDay.value) {
|
||||
if (!selectedTimeSlot.value) {
|
||||
uni.showToast({
|
||||
title: t('pages.address.reservationTime.selectTimeSlot'),
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 计算开始/结束时间
|
||||
const selectedDateDayjs = dayjs(selectedDate.value);
|
||||
let startTime: dayjs.Dayjs;
|
||||
let endTime: dayjs.Dayjs;
|
||||
|
||||
if (onlySelectDay.value) {
|
||||
// 仅选日期:优先使用营业时间范围;若无营业时间限制,则使用当天起止
|
||||
const bh = getBusinessHoursForDate(selectedDate.value);
|
||||
if (bh) {
|
||||
const [startHour, startMinute] = bh.startTime.split(':').map(Number);
|
||||
const [endHour, endMinute] = bh.endTime.split(':').map(Number);
|
||||
startTime = selectedDateDayjs
|
||||
.hour(startHour)
|
||||
.minute(startMinute)
|
||||
.second(0)
|
||||
.millisecond(0);
|
||||
endTime = selectedDateDayjs
|
||||
.hour(endHour)
|
||||
.minute(endMinute)
|
||||
.second(0)
|
||||
.millisecond(0);
|
||||
} else {
|
||||
startTime = selectedDateDayjs.startOf('day');
|
||||
endTime = selectedDateDayjs.endOf('day');
|
||||
}
|
||||
} else {
|
||||
// 选择了时间段:解析并生成起止时间
|
||||
const [startTimeStr, endTimeStr] = selectedTimeSlot.value.split(' - ');
|
||||
const [startHour, startMinute] = startTimeStr.split(':').map(Number);
|
||||
const [endHour, endMinute] = endTimeStr.split(':').map(Number);
|
||||
startTime = selectedDateDayjs
|
||||
.hour(startHour)
|
||||
.minute(startMinute)
|
||||
.second(0)
|
||||
.millisecond(0);
|
||||
endTime = selectedDateDayjs
|
||||
.hour(endHour)
|
||||
.minute(endMinute)
|
||||
.second(0)
|
||||
.millisecond(0);
|
||||
}
|
||||
|
||||
console.log("预约信息:", {
|
||||
date: selectedDate.value,
|
||||
timeSlot: onlySelectDay.value ? '' : selectedTimeSlot.value,
|
||||
startTime: startTime.valueOf(),
|
||||
endTime: endTime.valueOf(),
|
||||
});
|
||||
|
||||
uni.$emit(EventEnum.CHOOSE_APPOINTMENT_TIME, {
|
||||
date: selectedDate.value,
|
||||
timeSlot: onlySelectDay.value ? '' : selectedTimeSlot.value,
|
||||
startTime: startTime.valueOf(),
|
||||
endTime: endTime.valueOf(),
|
||||
});
|
||||
|
||||
uni.showToast({
|
||||
title: t('pages.address.reservationTime.reservationSuccess'),
|
||||
icon: "none",
|
||||
});
|
||||
|
||||
uni.navigateBack();
|
||||
};
|
||||
|
||||
const storeId = ref(null);
|
||||
|
||||
// 页面加载时处理参数
|
||||
onLoad((options: any) => {
|
||||
if (options.storeId) {
|
||||
storeId.value = options.storeId;
|
||||
}
|
||||
if (options.storeBusinessHours) {
|
||||
storeBusinessHours.value = options.storeBusinessHours;
|
||||
// 如果传递了该参数,进入仅选日期模式
|
||||
onlySelectDay.value = true;
|
||||
}
|
||||
// 无论是否传参,统一初始化选中日期,避免首屏未选导致不显示时间段
|
||||
nextTick(() => {
|
||||
initializeSelectedDate();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="">
|
||||
<navbar />
|
||||
<view class="mt-20rpx px-30rpx text-46rpx lh-46rpx text-#333 font-bold">
|
||||
{{ t("pages.address.appTime") }}
|
||||
</view>
|
||||
<view class="px-30rpx pt-52rpx pb-50rpx w-screen bg-white">
|
||||
<scroll-view class="w-full whitespace-nowrap" scroll-x="true" :scroll-left="scrollLeft">
|
||||
<template v-for="(item, index) in dateOptions" :key="index">
|
||||
<view
|
||||
@click="selectDate(item)"
|
||||
:class="[
|
||||
index === 0 ? '' : 'ml-28rpx',
|
||||
selectedDate && dayjs(selectedDate).isSame(dayjs(item), 'day')
|
||||
? 'border-#333'
|
||||
: 'border-#D8D8D8',
|
||||
!isDateSelectable(item)
|
||||
? 'opacity-50 cursor-not-allowed'
|
||||
: 'cursor-pointer',
|
||||
]"
|
||||
class="inline-block border-solid border-1px w-240rpx h-140rpx rounded-20rpx px-32rpx py-36rpx"
|
||||
>
|
||||
<view
|
||||
:class="!isDateSelectable(item) ? 'text-#999' : 'text-#333'"
|
||||
class="text-28rpx lh-28rpx mb-12rpx"
|
||||
>
|
||||
{{ formatDateDisplay(item) }}
|
||||
</view>
|
||||
<view
|
||||
:class="!isDateSelectable(item) ? 'text-#CCC' : 'text-#7D7D7D'"
|
||||
class="text-28rpx"
|
||||
>
|
||||
{{ formatDateOnly(item) }}
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<!-- 时间段选择区域:在仅选日期模式下隐藏 -->
|
||||
<view v-if="!onlySelectDay" class="pb-138rpx">
|
||||
<view
|
||||
v-for="(timeSlot, index) in timeSlots"
|
||||
:key="index"
|
||||
class="h-108rpx flex-center-sb px-30rpx"
|
||||
:class="[
|
||||
index === 0 ? '' : 'border-top',
|
||||
timeSlots.length - 1 === index ? 'border-bottom' : '',
|
||||
]"
|
||||
@click="selectTimeSlot(timeSlot)"
|
||||
>
|
||||
<text class="text-32rpx font-regular">{{ timeSlot }}</text>
|
||||
<!-- 单选按钮 -->
|
||||
<image
|
||||
:src="
|
||||
selectedTimeSlot === timeSlot
|
||||
? '/static/images/chef/133.png'
|
||||
: '/static/images/chef/134.png'
|
||||
"
|
||||
class="w-48rpx h-48rpx shrink-0"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<fixed-bottom-large-btn
|
||||
class="z-100"
|
||||
fixed
|
||||
:text="`${t('common.submit')}`"
|
||||
@click="submitReservation"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
page {
|
||||
background-color: #fff;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,317 @@
|
||||
<script setup lang="ts">
|
||||
import ChooseImage from "@/components/choose-image/choose-image.vue";
|
||||
import chooseState from "../components/choose-state.vue";
|
||||
import { getStateListApi } from '@/pages-user/service'
|
||||
|
||||
|
||||
const { t } = useI18n()
|
||||
import VisitMethod from '@/components/visit-method/index.vue'
|
||||
import BuildingType from './components/building-type.vue'
|
||||
import {useAddressStore} from "../store/address";
|
||||
import {appUserAddressAddPost, appUserAddressEditPost} from "@/service";
|
||||
import {UserAddressType} from "@/constant/enums";
|
||||
const addressStore = useAddressStore()
|
||||
|
||||
const visitMethodRef = ref<InstanceType<typeof VisitMethod>>()
|
||||
const buildingTypeRef = ref<InstanceType<typeof BuildingType>>()
|
||||
function openVisitMethod() {
|
||||
visitMethodRef.value?.onOpen()
|
||||
}
|
||||
|
||||
const visitMethod = ref(t('components.visit.leaveItToMePersonally')) // 默认选择亲自送达
|
||||
function visitMethodConfirm(data: any) {
|
||||
console.log('visitMethodConfirm', data)
|
||||
visitMethod.value = data.label;
|
||||
addressStore.addressInfo.deliveryType = data.value + 1; // 更新地址信息中的送达方式
|
||||
}
|
||||
|
||||
function openBuildingType() {
|
||||
buildingTypeRef.value?.onOpen()
|
||||
}
|
||||
|
||||
|
||||
// 选择图片 - 添加安全检查
|
||||
const chooseImageRef = ref<InstanceType<typeof ChooseImage>>()
|
||||
const images = ref<string>('')
|
||||
function chooseImage() {
|
||||
if (chooseImageRef.value?.init) {
|
||||
chooseImageRef.value.init();
|
||||
}
|
||||
}
|
||||
|
||||
// 图片选择回调 - 修复类型问题
|
||||
function onImageChange(files: string[]) {
|
||||
if (files.length > 0) {
|
||||
// 确保 images 数组不为空
|
||||
images.value = files[0]; // 只取第一个图片
|
||||
} else {
|
||||
images.value = ''; // 如果没有选择图片,清空
|
||||
}
|
||||
}
|
||||
|
||||
function removeImage() {
|
||||
images.value = ''
|
||||
}
|
||||
|
||||
function saveAddress() {
|
||||
console.log('保存地址信息', addressStore.addressInfo);
|
||||
if(addressStore.addressInfo.id) {
|
||||
appUserAddressEditPost({
|
||||
body: {
|
||||
...addressStore.addressInfo,
|
||||
deliveryImage: images.value
|
||||
}
|
||||
}).then(res=> {
|
||||
uni.navigateBack()
|
||||
addressStore.clearAddressInfo()
|
||||
})
|
||||
} else {
|
||||
appUserAddressAddPost({
|
||||
body: {
|
||||
...addressStore.addressInfo,
|
||||
deliveryImage: images.value
|
||||
}
|
||||
}).then(res=> {
|
||||
uni.navigateBack()
|
||||
addressStore.clearAddressInfo()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onLoad(()=> {
|
||||
isSwitch.value = false
|
||||
if(addressStore.addressInfo.deliveryImage) {
|
||||
images.value = addressStore.addressInfo.deliveryImage
|
||||
}
|
||||
// 回显送达偏好
|
||||
if(+addressStore.addressInfo.deliveryType === 1) {
|
||||
visitMethod.value = t('components.visit.leaveItToMePersonally')
|
||||
}
|
||||
if(+addressStore.addressInfo.deliveryType === 2) {
|
||||
visitMethod.value = t('components.visit.putItAtTheDoor')
|
||||
}
|
||||
|
||||
getStateListApi().then(res=> {
|
||||
stateList.value = res.data
|
||||
if(addressStore.addressInfo.state) {
|
||||
addressStore.addressInfo.stateName = res.data.find(item => item.id === addressStore.addressInfo.state)?.name || ''
|
||||
}
|
||||
})
|
||||
})
|
||||
onUnload(()=> {
|
||||
if(!isSwitch.value) {
|
||||
addressStore.clearAddressInfo()
|
||||
}
|
||||
})
|
||||
|
||||
const isSwitch = ref(false)
|
||||
function submitType(value: string) {
|
||||
addressStore.addressInfo.type = value
|
||||
isSwitch.value = true
|
||||
switch (value) {
|
||||
case UserAddressType.HOUSE:
|
||||
uni.redirectTo({
|
||||
url: '/pages/address/save-address/house',
|
||||
})
|
||||
break;
|
||||
case UserAddressType.APARTMENT:
|
||||
uni.redirectTo({
|
||||
url: '/pages/address/save-address/apartment',
|
||||
})
|
||||
break;
|
||||
case UserAddressType.OFFICE:
|
||||
uni.redirectTo({
|
||||
url: '/pages/address/save-address/office',
|
||||
})
|
||||
break;
|
||||
case UserAddressType.HOTEL:
|
||||
uni.redirectTo({
|
||||
url: '/pages/address/save-address/hotel',
|
||||
})
|
||||
break;
|
||||
case UserAddressType.OTHER:
|
||||
uni.redirectTo({
|
||||
url: '/pages/address/save-address/other',
|
||||
})
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const chooseStateRef = ref()
|
||||
const stateList = ref([])
|
||||
function openState() {
|
||||
chooseStateRef.value?.init(stateList.value, addressStore.addressInfo.state)
|
||||
}
|
||||
function chooseStateConfirm(data: any) {
|
||||
addressStore.addressInfo.state = data.id
|
||||
addressStore.addressInfo.stateName = data.name
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view>
|
||||
<navbar :title="t('pages.address.titleDetail')" />
|
||||
<!-- 地址搜索结果名字 -->
|
||||
<view class="px-30rpx pt-38rpx border-bottom pb-52rpx">
|
||||
<view class="text-32rpx lh-32rpx text-#333 mb-62rpx">
|
||||
{{ addressStore.addressInfo.displayName }}
|
||||
{{ addressStore.addressInfo.formattedAddress }}
|
||||
</view>
|
||||
|
||||
|
||||
<!-- State -->
|
||||
<view class="mb-44rpx">
|
||||
<view class="mb-20rpx text-36rpx lh-36rpx text-#333 font-500">{{ t('common.state') }}</view>
|
||||
<view @click="openState" class="bg-#F6F6F6 rounded-16rpx px-24rpx h-98rpx flex-center-sb">
|
||||
<text class="text-30rpx lh-30rpx text-#333">
|
||||
{{ addressStore.addressInfo.stateName ? addressStore.addressInfo.stateName : t('common.placeholder.pleaseSelect') }}
|
||||
</text>
|
||||
<image src="@img/chef/142.png" class="w-32rpx h-32rpx shrink-0"></image>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 房屋类型 -->
|
||||
<view>
|
||||
<view class="mb-20rpx text-36rpx lh-36rpx text-#333 font-500">{{ t('pages.address.choose-type.navTitle') }}</view>
|
||||
<view @click="openBuildingType" class="bg-#F6F6F6 rounded-16rpx px-24rpx h-98rpx flex-center-sb">
|
||||
<text class="text-30rpx lh-30rpx text-#333">
|
||||
{{ `${t(`pages.address.choose-type.${addressStore.addressInfo.type}`)}` }}
|
||||
</text>
|
||||
<image src="@img/chef/142.png" class="w-32rpx h-32rpx shrink-0 rotate-90"></image>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Apartment/Unit/Floor (required) -->
|
||||
<view class="mt-44rpx">
|
||||
<view class="mb-20rpx text-36rpx lh-36rpx text-#333 font-500">{{ t('pages.address.apartment.titleApartment') }}</view>
|
||||
<view class="bg-#F6F6F6 rounded-16rpx px-24rpx h-98rpx flex items-center">
|
||||
<wd-input
|
||||
v-model.trim="addressStore.addressInfo.apartmentUnitFloor"
|
||||
:cursorSpacing="20"
|
||||
:focus-when-clear="false"
|
||||
:maxlength="20"
|
||||
:placeholder="t('pages.address.apartment.titleApartmentTips')"
|
||||
clearable
|
||||
custom-class="flex-1 !text-30rpx lh-30rpx !text-#333 !bg-transparent"
|
||||
no-border
|
||||
placeholderStyle="font-size: 30rpx;color: #999;"
|
||||
>
|
||||
</wd-input>
|
||||
</view>
|
||||
</view>
|
||||
<!-- Building Name -->
|
||||
<view class="mt-44rpx">
|
||||
<view class="mb-20rpx text-36rpx lh-36rpx text-#333 font-500">{{ t('pages.address.apartment.titleBuilding') }}</view>
|
||||
<view class="bg-#F6F6F6 rounded-16rpx px-24rpx h-98rpx flex items-center">
|
||||
<wd-input
|
||||
v-model.trim="addressStore.addressInfo.buildingName"
|
||||
:cursorSpacing="20"
|
||||
:focus-when-clear="false"
|
||||
:maxlength="20"
|
||||
:placeholder="t('pages.address.apartment.titleBuildingTips')"
|
||||
clearable
|
||||
custom-class="flex-1 !text-30rpx lh-30rpx !text-#333 !bg-transparent"
|
||||
no-border
|
||||
placeholderStyle="font-size: 30rpx;color: #999;"
|
||||
>
|
||||
</wd-input>
|
||||
</view>
|
||||
</view>
|
||||
<!-- Entry Code -->
|
||||
<view class="mt-44rpx">
|
||||
<view class="mb-20rpx text-36rpx lh-36rpx text-#333 font-500">{{ t('pages.address.apartment.titleEntry') }}</view>
|
||||
<view class="bg-#F6F6F6 rounded-16rpx px-24rpx h-98rpx flex items-center">
|
||||
<wd-input
|
||||
v-model.trim="addressStore.addressInfo.entranceCodeApartment"
|
||||
:cursorSpacing="20"
|
||||
:focus-when-clear="false"
|
||||
:maxlength="20"
|
||||
:placeholder="t('pages.address.apartment.titleEntryTips')"
|
||||
clearable
|
||||
custom-class="flex-1 !text-30rpx lh-30rpx !text-#333 !bg-transparent"
|
||||
no-border
|
||||
placeholderStyle="font-size: 30rpx;color: #999;"
|
||||
>
|
||||
</wd-input>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="px-30rpx py-52rpx">
|
||||
<!-- Delivery Point Information -->
|
||||
<view class="mb-44rpx text-36rpx lh-36rpx text-#333 font-500">{{ t('pages.address.deliveryPointInfo') }}</view>
|
||||
|
||||
<view @click="openVisitMethod" class="flex-center-sb">
|
||||
<view>
|
||||
<view class="text-32rpx lh-32rpx text-#333 mb-12rpx">{{ visitMethod }}</view>
|
||||
<view class="text-32rpx lh-32rpx text-#999">{{ t('pages.address.moreOptions') }}</view>
|
||||
</view>
|
||||
<image src="@img/chef/142.png" class="w-32rpx h-32rpx shrink-0"></image>
|
||||
</view>
|
||||
|
||||
<view class="mt-52rpx">
|
||||
<view class="text-36rpx lh-36rpx text-#333 font-500 mb-24rpx">{{ t('pages.address.deliveryInstructions') }}</view>
|
||||
<view
|
||||
class="min-h-240rpx box-border bg-#F6F6F6 rounded-20rpx overflow-hidden p-20rpx"
|
||||
>
|
||||
<wd-textarea
|
||||
:maxlength="250"
|
||||
custom-class="!bg-#F6F6F6"
|
||||
custom-textarea-container-class="!bg-#F6F6F6"
|
||||
no-border
|
||||
auto-height
|
||||
v-model="addressStore.addressInfo.deliveryRemark"
|
||||
:placeholder="t('pages.address.pleaseTip')"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="mt-24rpx relative w-240rpx h-240rpx" @click="chooseImage">
|
||||
<image
|
||||
v-if="images"
|
||||
class="absolute top--10rpx right--10rpx z-1 w-36rpx h-36rpx"
|
||||
src="@img/chef/113.png"
|
||||
@click.stop="removeImage"
|
||||
></image>
|
||||
<view v-if="!images" class="flex flex-col items-center justify-center w-240rpx h-240rpx bg-#f6f6f6 rounded-20rpx">
|
||||
<wd-icon name="add" size="32px" color="#3d3d3d"></wd-icon>
|
||||
<text class="text-30rpx lh-30rpx text-#333 mt-22rpx">{{ t('common.addPicture') }}</text>
|
||||
</view>
|
||||
<image
|
||||
v-else
|
||||
:src="images"
|
||||
class="w-240rpx h-240rpx rounded-20rpx"
|
||||
mode="aspectFill"
|
||||
></image>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
<fixed-bottom-large-btn
|
||||
class="z-100"
|
||||
fixed
|
||||
:text="t('common.saveAndContinue')"
|
||||
@click="saveAddress"
|
||||
/>
|
||||
|
||||
<visit-method @confirm="visitMethodConfirm" ref="visitMethodRef" />
|
||||
<!-- 房屋类型 -->
|
||||
<building-type ref="buildingTypeRef" @submit="submitType" />
|
||||
|
||||
<ChooseImage ref="chooseImageRef" @change="onImageChange"/>
|
||||
|
||||
<!-- 所在州 -->
|
||||
<choose-state ref="chooseStateRef" @confirm="chooseStateConfirm" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
:deep(.wd-textarea) {
|
||||
padding: 0 !important;
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
page {
|
||||
background-color: #fff;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,93 @@
|
||||
<script setup lang="ts">
|
||||
import {UserAddressType} from "@/constant/enums";
|
||||
|
||||
const { t } = useI18n();
|
||||
const show = ref(false);
|
||||
const value = ref('house')
|
||||
|
||||
const emit = defineEmits(['submit'])
|
||||
|
||||
function onOpen() {
|
||||
show.value = true;
|
||||
}
|
||||
function handleClose() {
|
||||
show.value = false;
|
||||
}
|
||||
function handleSubmit() {
|
||||
console.log('value', value.value)
|
||||
emit('submit', value.value)
|
||||
handleClose()
|
||||
}
|
||||
|
||||
const columns = ref(
|
||||
[
|
||||
{
|
||||
label: t('pages.address.choose-type.house'),
|
||||
value: UserAddressType.HOUSE,
|
||||
},
|
||||
{
|
||||
label: t('pages.address.choose-type.apartment'),
|
||||
value: UserAddressType.APARTMENT,
|
||||
},
|
||||
{
|
||||
label: t('pages.address.choose-type.office'),
|
||||
value: UserAddressType.OFFICE,
|
||||
},
|
||||
{
|
||||
label: t('pages.address.choose-type.hotel'),
|
||||
value: UserAddressType.HOTEL,
|
||||
},
|
||||
{
|
||||
label: t('pages.address.choose-type.other'),
|
||||
value: UserAddressType.OTHER,
|
||||
},
|
||||
]
|
||||
)
|
||||
function onChange({picker, value, index}) {
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
onOpen,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<wd-popup
|
||||
v-model="show"
|
||||
position="bottom"
|
||||
@close="handleClose"
|
||||
>
|
||||
<view>
|
||||
<view class="flex-center-sb h-102rpx bg-#F7F7F7 px-30rpx">
|
||||
<view @click="handleClose" class="text-30rpx text-#999">{{ t('common.cancel') }}</view>
|
||||
<view class="text-34rpx text-#333">{{ t('common.buildingType') }}</view>
|
||||
<view @click="handleSubmit" class="text-30rpx text-#FF6106">{{ t('common.confirm') }}</view>
|
||||
</view>
|
||||
<view class="bg-#fff px-54rpx py-56rpx">
|
||||
<wd-picker-view :columns="columns" v-model="value" @change="onChange" label-key="label" value-key="value" />
|
||||
</view>
|
||||
</view>
|
||||
</wd-popup>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
:deep(.uni-picker-view-wrapper) {
|
||||
& > uni-picker-view-column:first-of-type .uni-picker-view-group {
|
||||
.uni-picker-view-indicator {
|
||||
border-radius: 20rpx 0 0 20rpx !important;
|
||||
&:after {
|
||||
border: none;
|
||||
}
|
||||
&:before {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
:deep(.wd-picker-view-column__item) {
|
||||
line-height: 94rpx !important;
|
||||
}
|
||||
:deep(.uni-picker-view-indicator) {
|
||||
height: 94rpx !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,295 @@
|
||||
<script setup lang="ts">
|
||||
import ChooseImage from "@/components/choose-image/choose-image.vue";
|
||||
import chooseState from "../components/choose-state.vue";
|
||||
import { getStateListApi } from '@/pages-user/service'
|
||||
|
||||
const { t } = useI18n()
|
||||
import VisitMethod from '@/components/visit-method/index.vue'
|
||||
import BuildingType from './components/building-type.vue'
|
||||
import {useAddressStore} from "../store/address";
|
||||
import {appUserAddressAddPost, appUserAddressEditPost} from "@/service";
|
||||
import {UserAddressType} from "@/constant/enums";
|
||||
const addressStore = useAddressStore()
|
||||
|
||||
const visitMethodRef = ref<InstanceType<typeof VisitMethod>>()
|
||||
const buildingTypeRef = ref<InstanceType<typeof BuildingType>>()
|
||||
function openVisitMethod() {
|
||||
visitMethodRef.value?.onOpen()
|
||||
}
|
||||
|
||||
const visitMethod = ref(t('components.visit.leaveItToMePersonally')) // 默认选择亲自送达
|
||||
function visitMethodConfirm(data: any) {
|
||||
console.log('visitMethodConfirm', data)
|
||||
visitMethod.value = data.label;
|
||||
addressStore.addressInfo.deliveryType = data.value + 1; // 更新地址信息中的送达方式
|
||||
}
|
||||
|
||||
function openBuildingType() {
|
||||
buildingTypeRef.value?.onOpen()
|
||||
}
|
||||
|
||||
|
||||
// 选择图片 - 添加安全检查
|
||||
const chooseImageRef = ref<InstanceType<typeof ChooseImage>>()
|
||||
const images = ref<string>('')
|
||||
function chooseImage() {
|
||||
if (chooseImageRef.value?.init) {
|
||||
chooseImageRef.value.init();
|
||||
}
|
||||
}
|
||||
|
||||
// 图片选择回调 - 修复类型问题
|
||||
function onImageChange(files: string[]) {
|
||||
if (files.length > 0) {
|
||||
// 确保 images 数组不为空
|
||||
images.value = files[0]; // 只取第一个图片
|
||||
} else {
|
||||
images.value = ''; // 如果没有选择图片,清空
|
||||
}
|
||||
}
|
||||
|
||||
function removeImage() {
|
||||
images.value = ''
|
||||
}
|
||||
|
||||
function saveAddress() {
|
||||
console.log('保存地址信息', addressStore.addressInfo);
|
||||
if(addressStore.addressInfo.id) {
|
||||
appUserAddressEditPost({
|
||||
body: {
|
||||
...addressStore.addressInfo,
|
||||
deliveryImage: images.value
|
||||
}
|
||||
}).then(res=> {
|
||||
uni.navigateBack()
|
||||
addressStore.clearAddressInfo()
|
||||
})
|
||||
} else {
|
||||
appUserAddressAddPost({
|
||||
body: {
|
||||
...addressStore.addressInfo,
|
||||
deliveryImage: images.value
|
||||
}
|
||||
}).then(res=> {
|
||||
uni.navigateBack()
|
||||
addressStore.clearAddressInfo()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onLoad(()=> {
|
||||
isSwitch.value = false
|
||||
if(addressStore.addressInfo.deliveryImage) {
|
||||
images.value = addressStore.addressInfo.deliveryImage
|
||||
}
|
||||
// 回显送达偏好
|
||||
if(+addressStore.addressInfo.deliveryType === 1) {
|
||||
visitMethod.value = t('components.visit.leaveItToMePersonally')
|
||||
}
|
||||
if(+addressStore.addressInfo.deliveryType === 2) {
|
||||
visitMethod.value = t('components.visit.putItAtTheDoor')
|
||||
}
|
||||
|
||||
getStateListApi().then(res=> {
|
||||
stateList.value = res.data
|
||||
if(addressStore.addressInfo.state) {
|
||||
addressStore.addressInfo.stateName = res.data.find(item => item.id === addressStore.addressInfo.state)?.name || ''
|
||||
}
|
||||
})
|
||||
})
|
||||
onUnload(()=> {
|
||||
if(!isSwitch.value) {
|
||||
addressStore.clearAddressInfo()
|
||||
}
|
||||
})
|
||||
|
||||
const isSwitch = ref(false)
|
||||
function submitType(value: string) {
|
||||
addressStore.addressInfo.type = value
|
||||
isSwitch.value = true
|
||||
switch (value) {
|
||||
case UserAddressType.HOUSE:
|
||||
uni.redirectTo({
|
||||
url: '/pages/address/save-address/house',
|
||||
})
|
||||
break;
|
||||
case UserAddressType.APARTMENT:
|
||||
uni.redirectTo({
|
||||
url: '/pages/address/save-address/apartment',
|
||||
})
|
||||
break;
|
||||
case UserAddressType.OFFICE:
|
||||
uni.redirectTo({
|
||||
url: '/pages/address/save-address/office',
|
||||
})
|
||||
break;
|
||||
case UserAddressType.HOTEL:
|
||||
uni.redirectTo({
|
||||
url: '/pages/address/save-address/hotel',
|
||||
})
|
||||
break;
|
||||
case UserAddressType.OTHER:
|
||||
uni.redirectTo({
|
||||
url: '/pages/address/save-address/other',
|
||||
})
|
||||
break;
|
||||
}
|
||||
}
|
||||
const chooseStateRef = ref()
|
||||
const stateList = ref([])
|
||||
function openState() {
|
||||
chooseStateRef.value?.init(stateList.value, addressStore.addressInfo.state)
|
||||
}
|
||||
function chooseStateConfirm(data: any) {
|
||||
addressStore.addressInfo.state = data.id
|
||||
addressStore.addressInfo.stateName = data.name
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view>
|
||||
<navbar :title="t('pages.address.titleDetail')" />
|
||||
<!-- 地址搜索结果名字 -->
|
||||
<view class="px-30rpx pt-38rpx border-bottom pb-52rpx">
|
||||
<view class="text-32rpx lh-32rpx text-#333 mb-62rpx">
|
||||
{{ addressStore.addressInfo.displayName }}
|
||||
{{ addressStore.addressInfo.formattedAddress }}
|
||||
</view>
|
||||
|
||||
<!-- State -->
|
||||
<view class="mb-44rpx">
|
||||
<view class="mb-20rpx text-36rpx lh-36rpx text-#333 font-500">{{ t('common.state') }}</view>
|
||||
<view @click="openState" class="bg-#F6F6F6 rounded-16rpx px-24rpx h-98rpx flex-center-sb">
|
||||
<text class="text-30rpx lh-30rpx text-#333">
|
||||
{{ addressStore.addressInfo.stateName ? addressStore.addressInfo.stateName : t('common.placeholder.pleaseSelect') }}
|
||||
</text>
|
||||
<image src="@img/chef/142.png" class="w-32rpx h-32rpx shrink-0"></image>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 房屋类型 -->
|
||||
<view>
|
||||
<view class="mb-20rpx text-36rpx lh-36rpx text-#333 font-500">{{ t('pages.address.choose-type.navTitle') }}</view>
|
||||
<view @click="openBuildingType" class="bg-#F6F6F6 rounded-16rpx px-24rpx h-98rpx flex-center-sb">
|
||||
<text class="text-30rpx lh-30rpx text-#333">
|
||||
{{ `${t(`pages.address.choose-type.${addressStore.addressInfo.type}`)}` }}
|
||||
</text>
|
||||
<image src="@img/chef/142.png" class="w-32rpx h-32rpx shrink-0 rotate-90"></image>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Hotel Name (required) -->
|
||||
<view class="mt-44rpx">
|
||||
<view class="mb-20rpx text-36rpx lh-36rpx text-#333 font-500">{{ t('pages.address.hotel.titleHotel') }}</view>
|
||||
<view class="bg-#F6F6F6 rounded-16rpx px-24rpx h-98rpx flex items-center">
|
||||
<wd-input
|
||||
v-model.trim="addressStore.addressInfo.hotelName"
|
||||
:cursorSpacing="20"
|
||||
:focus-when-clear="false"
|
||||
:maxlength="20"
|
||||
:placeholder="t('pages.address.hotel.titleHotelTips')"
|
||||
clearable
|
||||
custom-class="flex-1 !text-30rpx lh-30rpx !text-#333 !bg-transparent"
|
||||
no-border
|
||||
placeholderStyle="font-size: 30rpx;color: #999;"
|
||||
>
|
||||
</wd-input>
|
||||
</view>
|
||||
</view>
|
||||
<!-- Room/Floor -->
|
||||
<view class="mt-44rpx">
|
||||
<view class="mb-20rpx text-36rpx lh-36rpx text-#333 font-500">{{ t('pages.address.hotel.titleRoom') }}</view>
|
||||
<view class="bg-#F6F6F6 rounded-16rpx px-24rpx h-98rpx flex items-center">
|
||||
<wd-input
|
||||
v-model.trim="addressStore.addressInfo.hotelRoom"
|
||||
:cursorSpacing="20"
|
||||
:focus-when-clear="false"
|
||||
:maxlength="20"
|
||||
:placeholder="t('pages.address.hotel.titleRoomTips')"
|
||||
clearable
|
||||
custom-class="flex-1 !text-30rpx lh-30rpx !text-#333 !bg-transparent"
|
||||
no-border
|
||||
placeholderStyle="font-size: 30rpx;color: #999;"
|
||||
>
|
||||
</wd-input>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="px-30rpx py-52rpx">
|
||||
<!-- Delivery Point Information -->
|
||||
<view class="mb-44rpx text-36rpx lh-36rpx text-#333 font-500">{{ t('pages.address.deliveryPointInfo') }}</view>
|
||||
|
||||
<view @click="openVisitMethod" class="flex-center-sb">
|
||||
<view>
|
||||
<view class="text-32rpx lh-32rpx text-#333 mb-12rpx">{{ visitMethod }}</view>
|
||||
<view class="text-32rpx lh-32rpx text-#999">{{ t('pages.address.moreOptions') }}</view>
|
||||
</view>
|
||||
<image src="@img/chef/142.png" class="w-32rpx h-32rpx shrink-0"></image>
|
||||
</view>
|
||||
|
||||
<view class="mt-52rpx">
|
||||
<view class="text-36rpx lh-36rpx text-#333 font-500 mb-24rpx">{{ t('pages.address.deliveryInstructions') }}</view>
|
||||
<view
|
||||
class="min-h-240rpx box-border bg-#F6F6F6 rounded-20rpx overflow-hidden p-20rpx"
|
||||
>
|
||||
<wd-textarea
|
||||
:maxlength="250"
|
||||
custom-class="!bg-#F6F6F6"
|
||||
custom-textarea-container-class="!bg-#F6F6F6"
|
||||
no-border
|
||||
auto-height
|
||||
v-model="addressStore.addressInfo.deliveryRemark"
|
||||
:placeholder="t('pages.address.pleaseTip')"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="mt-24rpx relative w-240rpx h-240rpx" @click="chooseImage">
|
||||
<image
|
||||
v-if="images"
|
||||
class="absolute top--10rpx right--10rpx z-1 w-36rpx h-36rpx"
|
||||
src="@img/chef/113.png"
|
||||
@click.stop="removeImage"
|
||||
></image>
|
||||
<view v-if="!images" class="flex flex-col items-center justify-center w-240rpx h-240rpx bg-#f6f6f6 rounded-20rpx">
|
||||
<wd-icon name="add" size="32px" color="#3d3d3d"></wd-icon>
|
||||
<text class="text-30rpx lh-30rpx text-#333 mt-22rpx">{{ t('common.addPicture') }}</text>
|
||||
</view>
|
||||
<image
|
||||
v-else
|
||||
:src="images"
|
||||
class="w-240rpx h-240rpx rounded-20rpx"
|
||||
mode="aspectFill"
|
||||
></image>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
<fixed-bottom-large-btn
|
||||
class="z-100"
|
||||
fixed
|
||||
:text="t('common.saveAndContinue')"
|
||||
@click="saveAddress"
|
||||
/>
|
||||
|
||||
<visit-method @confirm="visitMethodConfirm" ref="visitMethodRef" />
|
||||
<!-- 房屋类型 -->
|
||||
<building-type ref="buildingTypeRef" @submit="submitType" />
|
||||
|
||||
<ChooseImage ref="chooseImageRef" @change="onImageChange"/>
|
||||
<!-- 所在州 -->
|
||||
<choose-state ref="chooseStateRef" @confirm="chooseStateConfirm" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
:deep(.wd-textarea) {
|
||||
padding: 0 !important;
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
page {
|
||||
background-color: #fff;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,277 @@
|
||||
<script setup lang="ts">
|
||||
import ChooseImage from "@/components/choose-image/choose-image.vue";
|
||||
import chooseState from "../components/choose-state.vue";
|
||||
import { getStateListApi } from '@/pages-user/service'
|
||||
|
||||
const { t } = useI18n()
|
||||
import VisitMethod from '@/components/visit-method/index.vue'
|
||||
import BuildingType from './components/building-type.vue'
|
||||
import {useAddressStore} from "../store/address";
|
||||
import {appUserAddressAddPost, appUserAddressEditPost} from "@/service";
|
||||
import {UserAddressType} from "@/constant/enums";
|
||||
const addressStore = useAddressStore()
|
||||
|
||||
const visitMethodRef = ref<InstanceType<typeof VisitMethod>>()
|
||||
const buildingTypeRef = ref<InstanceType<typeof BuildingType>>()
|
||||
function openVisitMethod() {
|
||||
visitMethodRef.value?.onOpen()
|
||||
}
|
||||
|
||||
const visitMethod = ref(t('components.visit.leaveItToMePersonally')) // 默认选择亲自送达
|
||||
function visitMethodConfirm(data: any) {
|
||||
console.log('visitMethodConfirm', data)
|
||||
visitMethod.value = data.label;
|
||||
addressStore.addressInfo.deliveryType = data.value + 1; // 更新地址信息中的送达方式
|
||||
}
|
||||
|
||||
function openBuildingType() {
|
||||
buildingTypeRef.value?.onOpen()
|
||||
}
|
||||
|
||||
|
||||
// 选择图片 - 添加安全检查
|
||||
const chooseImageRef = ref<InstanceType<typeof ChooseImage>>()
|
||||
const images = ref<string>('')
|
||||
function chooseImage() {
|
||||
if (chooseImageRef.value?.init) {
|
||||
chooseImageRef.value.init();
|
||||
}
|
||||
}
|
||||
|
||||
// 图片选择回调 - 修复类型问题
|
||||
function onImageChange(files: string[]) {
|
||||
if (files.length > 0) {
|
||||
// 确保 images 数组不为空
|
||||
images.value = files[0]; // 只取第一个图片
|
||||
} else {
|
||||
images.value = ''; // 如果没有选择图片,清空
|
||||
}
|
||||
}
|
||||
|
||||
function removeImage() {
|
||||
images.value = ''
|
||||
}
|
||||
|
||||
function saveAddress() {
|
||||
console.log('保存地址信息', addressStore.addressInfo);
|
||||
if(addressStore.addressInfo.id) {
|
||||
appUserAddressEditPost({
|
||||
body: {
|
||||
...addressStore.addressInfo,
|
||||
deliveryImage: images.value
|
||||
}
|
||||
}).then(res=> {
|
||||
uni.navigateBack()
|
||||
addressStore.clearAddressInfo()
|
||||
})
|
||||
} else {
|
||||
appUserAddressAddPost({
|
||||
body: {
|
||||
...addressStore.addressInfo,
|
||||
deliveryImage: images.value
|
||||
}
|
||||
}).then(res=> {
|
||||
uni.navigateBack()
|
||||
addressStore.clearAddressInfo()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onLoad(()=> {
|
||||
isSwitch.value = false
|
||||
if(addressStore.addressInfo.deliveryImage) {
|
||||
images.value = addressStore.addressInfo.deliveryImage
|
||||
}
|
||||
// 回显送达偏好
|
||||
if(+addressStore.addressInfo.deliveryType === 1) {
|
||||
visitMethod.value = t('components.visit.leaveItToMePersonally')
|
||||
}
|
||||
if(+addressStore.addressInfo.deliveryType === 2) {
|
||||
visitMethod.value = t('components.visit.putItAtTheDoor')
|
||||
}
|
||||
|
||||
getStateListApi().then(res=> {
|
||||
stateList.value = res.data
|
||||
if(addressStore.addressInfo.state) {
|
||||
addressStore.addressInfo.stateName = res.data.find(item => item.id === addressStore.addressInfo.state)?.name || ''
|
||||
}
|
||||
})
|
||||
})
|
||||
onUnload(()=> {
|
||||
if(!isSwitch.value) {
|
||||
addressStore.clearAddressInfo()
|
||||
}
|
||||
})
|
||||
|
||||
const isSwitch = ref(false)
|
||||
function submitType(value: string) {
|
||||
addressStore.addressInfo.type = value
|
||||
isSwitch.value = true
|
||||
switch (value) {
|
||||
case UserAddressType.HOUSE:
|
||||
uni.redirectTo({
|
||||
url: '/pages/address/save-address/house',
|
||||
})
|
||||
break;
|
||||
case UserAddressType.APARTMENT:
|
||||
uni.redirectTo({
|
||||
url: '/pages/address/save-address/apartment',
|
||||
})
|
||||
break;
|
||||
case UserAddressType.OFFICE:
|
||||
uni.redirectTo({
|
||||
url: '/pages/address/save-address/office',
|
||||
})
|
||||
break;
|
||||
case UserAddressType.HOTEL:
|
||||
uni.redirectTo({
|
||||
url: '/pages/address/save-address/hotel',
|
||||
})
|
||||
break;
|
||||
case UserAddressType.OTHER:
|
||||
uni.redirectTo({
|
||||
url: '/pages/address/save-address/other',
|
||||
})
|
||||
break;
|
||||
}
|
||||
}
|
||||
const chooseStateRef = ref()
|
||||
const stateList = ref([])
|
||||
function openState() {
|
||||
chooseStateRef.value?.init(stateList.value, addressStore.addressInfo.state)
|
||||
}
|
||||
function chooseStateConfirm(data: any) {
|
||||
addressStore.addressInfo.state = data.id
|
||||
addressStore.addressInfo.stateName = data.name
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view>
|
||||
<navbar :title="t('pages.address.titleDetail')" />
|
||||
<!-- 地址搜索结果名字 -->
|
||||
<view class="px-30rpx pt-38rpx border-bottom pb-52rpx">
|
||||
<view class="text-32rpx lh-32rpx text-#333 mb-62rpx">
|
||||
{{ addressStore.addressInfo.displayName }}
|
||||
{{ addressStore.addressInfo.formattedAddress }}
|
||||
</view>
|
||||
|
||||
<!-- State -->
|
||||
<view class="mb-44rpx">
|
||||
<view class="mb-20rpx text-36rpx lh-36rpx text-#333 font-500">{{ t('common.state') }}</view>
|
||||
<view @click="openState" class="bg-#F6F6F6 rounded-16rpx px-24rpx h-98rpx flex-center-sb">
|
||||
<text class="text-30rpx lh-30rpx text-#333">
|
||||
{{ addressStore.addressInfo.stateName ? addressStore.addressInfo.stateName : t('common.placeholder.pleaseSelect') }}
|
||||
</text>
|
||||
<image src="@img/chef/142.png" class="w-32rpx h-32rpx shrink-0"></image>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 房屋类型 -->
|
||||
<view>
|
||||
<view class="mb-20rpx text-36rpx lh-36rpx text-#333 font-500">{{ t('pages.address.choose-type.navTitle') }}</view>
|
||||
<view @click="openBuildingType" class="bg-#F6F6F6 rounded-16rpx px-24rpx h-98rpx flex-center-sb">
|
||||
<text class="text-30rpx lh-30rpx text-#333">
|
||||
{{ `${t(`pages.address.choose-type.${addressStore.addressInfo.type}`)}` }}
|
||||
</text>
|
||||
<image src="@img/chef/142.png" class="w-32rpx h-32rpx shrink-0 rotate-90"></image>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Other Details -->
|
||||
<view class="mt-44rpx">
|
||||
<view class="mb-20rpx text-36rpx lh-36rpx text-#333 font-500">{{ t('pages.address.otherDetails') }}</view>
|
||||
<view class="bg-#F6F6F6 rounded-16rpx px-24rpx h-98rpx flex items-center">
|
||||
<wd-input
|
||||
v-model.trim="addressStore.addressInfo.houseExtra"
|
||||
:cursorSpacing="20"
|
||||
:focus-when-clear="false"
|
||||
:maxlength="20"
|
||||
:placeholder="t('pages.address.house.tips')"
|
||||
clearable
|
||||
custom-class="flex-1 !text-30rpx lh-30rpx !text-#333 !bg-transparent"
|
||||
no-border
|
||||
placeholderStyle="font-size: 30rpx;color: #999;"
|
||||
>
|
||||
</wd-input>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="px-30rpx py-52rpx">
|
||||
<!-- Delivery Point Information -->
|
||||
<view class="mb-44rpx text-36rpx lh-36rpx text-#333 font-500">{{ t('pages.address.deliveryPointInfo') }}</view>
|
||||
|
||||
<view @click="openVisitMethod" class="flex-center-sb">
|
||||
<view>
|
||||
<view class="text-32rpx lh-32rpx text-#333 mb-12rpx">{{ visitMethod }}</view>
|
||||
<view class="text-32rpx lh-32rpx text-#999">{{ t('pages.address.moreOptions') }}</view>
|
||||
</view>
|
||||
<image src="@img/chef/142.png" class="w-32rpx h-32rpx shrink-0"></image>
|
||||
</view>
|
||||
|
||||
<view class="mt-52rpx">
|
||||
<view class="text-36rpx lh-36rpx text-#333 font-500 mb-24rpx">{{ t('pages.address.deliveryInstructions') }}</view>
|
||||
<view
|
||||
class="min-h-240rpx box-border bg-#F6F6F6 rounded-20rpx overflow-hidden p-20rpx"
|
||||
>
|
||||
<wd-textarea
|
||||
:maxlength="250"
|
||||
custom-class="!bg-#F6F6F6"
|
||||
custom-textarea-container-class="!bg-#F6F6F6"
|
||||
no-border
|
||||
auto-height
|
||||
v-model="addressStore.addressInfo.deliveryRemark"
|
||||
:placeholder="t('pages.address.pleaseTip')"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="mt-24rpx relative w-240rpx h-240rpx" @click="chooseImage">
|
||||
<image
|
||||
v-if="images"
|
||||
class="absolute top--10rpx right--10rpx z-1 w-36rpx h-36rpx"
|
||||
src="@img/chef/113.png"
|
||||
@click.stop="removeImage"
|
||||
></image>
|
||||
<view v-if="!images" class="flex flex-col items-center justify-center w-240rpx h-240rpx bg-#f6f6f6 rounded-20rpx">
|
||||
<wd-icon name="add" size="32px" color="#3d3d3d"></wd-icon>
|
||||
<text class="text-30rpx lh-30rpx text-#333 mt-22rpx">{{ t('common.addPicture') }}</text>
|
||||
</view>
|
||||
<image
|
||||
v-else
|
||||
:src="images"
|
||||
class="w-240rpx h-240rpx rounded-20rpx"
|
||||
mode="aspectFill"
|
||||
></image>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
<fixed-bottom-large-btn
|
||||
class="z-100"
|
||||
fixed
|
||||
:text="t('common.saveAndContinue')"
|
||||
@click="saveAddress"
|
||||
/>
|
||||
|
||||
<visit-method @confirm="visitMethodConfirm" ref="visitMethodRef" />
|
||||
<!-- 房屋类型 -->
|
||||
<building-type ref="buildingTypeRef" @submit="submitType" />
|
||||
|
||||
<ChooseImage ref="chooseImageRef" @change="onImageChange"/>
|
||||
<!-- 所在州 -->
|
||||
<choose-state ref="chooseStateRef" @confirm="chooseStateConfirm" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
:deep(.wd-textarea) {
|
||||
padding: 0 !important;
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
page {
|
||||
background-color: #fff;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,295 @@
|
||||
<script setup lang="ts">
|
||||
import ChooseImage from "@/components/choose-image/choose-image.vue";
|
||||
import chooseState from "../components/choose-state.vue";
|
||||
import { getStateListApi } from '@/pages-user/service'
|
||||
|
||||
const { t } = useI18n()
|
||||
import VisitMethod from '@/components/visit-method/index.vue'
|
||||
import BuildingType from './components/building-type.vue'
|
||||
import {useAddressStore} from "../store/address";
|
||||
import {appUserAddressAddPost, appUserAddressEditPost} from "@/service";
|
||||
import {UserAddressType} from "@/constant/enums";
|
||||
const addressStore = useAddressStore()
|
||||
|
||||
const visitMethodRef = ref<InstanceType<typeof VisitMethod>>()
|
||||
const buildingTypeRef = ref<InstanceType<typeof BuildingType>>()
|
||||
function openVisitMethod() {
|
||||
visitMethodRef.value?.onOpen()
|
||||
}
|
||||
|
||||
const visitMethod = ref(t('components.visit.leaveItToMePersonally')) // 默认选择亲自送达
|
||||
function visitMethodConfirm(data: any) {
|
||||
console.log('visitMethodConfirm', data)
|
||||
visitMethod.value = data.label;
|
||||
addressStore.addressInfo.deliveryType = data.value + 1; // 更新地址信息中的送达方式
|
||||
}
|
||||
|
||||
function openBuildingType() {
|
||||
buildingTypeRef.value?.onOpen()
|
||||
}
|
||||
|
||||
|
||||
// 选择图片 - 添加安全检查
|
||||
const chooseImageRef = ref<InstanceType<typeof ChooseImage>>()
|
||||
const images = ref<string>('')
|
||||
function chooseImage() {
|
||||
if (chooseImageRef.value?.init) {
|
||||
chooseImageRef.value.init();
|
||||
}
|
||||
}
|
||||
|
||||
// 图片选择回调 - 修复类型问题
|
||||
function onImageChange(files: string[]) {
|
||||
if (files.length > 0) {
|
||||
// 确保 images 数组不为空
|
||||
images.value = files[0]; // 只取第一个图片
|
||||
} else {
|
||||
images.value = ''; // 如果没有选择图片,清空
|
||||
}
|
||||
}
|
||||
|
||||
function removeImage() {
|
||||
images.value = ''
|
||||
}
|
||||
|
||||
function saveAddress() {
|
||||
console.log('保存地址信息', addressStore.addressInfo);
|
||||
if(addressStore.addressInfo.id) {
|
||||
appUserAddressEditPost({
|
||||
body: {
|
||||
...addressStore.addressInfo,
|
||||
deliveryImage: images.value
|
||||
}
|
||||
}).then(res=> {
|
||||
uni.navigateBack()
|
||||
addressStore.clearAddressInfo()
|
||||
})
|
||||
} else {
|
||||
appUserAddressAddPost({
|
||||
body: {
|
||||
...addressStore.addressInfo,
|
||||
deliveryImage: images.value
|
||||
}
|
||||
}).then(res=> {
|
||||
uni.navigateBack()
|
||||
addressStore.clearAddressInfo()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onLoad(()=> {
|
||||
isSwitch.value = false
|
||||
if(addressStore.addressInfo.deliveryImage) {
|
||||
images.value = addressStore.addressInfo.deliveryImage
|
||||
}
|
||||
// 回显送达偏好
|
||||
if(+addressStore.addressInfo.deliveryType === 1) {
|
||||
visitMethod.value = t('components.visit.leaveItToMePersonally')
|
||||
}
|
||||
if(+addressStore.addressInfo.deliveryType === 2) {
|
||||
visitMethod.value = t('components.visit.putItAtTheDoor')
|
||||
}
|
||||
|
||||
getStateListApi().then(res=> {
|
||||
stateList.value = res.data
|
||||
if(addressStore.addressInfo.state) {
|
||||
addressStore.addressInfo.stateName = res.data.find(item => item.id === addressStore.addressInfo.state)?.name || ''
|
||||
}
|
||||
})
|
||||
})
|
||||
onUnload(()=> {
|
||||
if(!isSwitch.value) {
|
||||
addressStore.clearAddressInfo()
|
||||
}
|
||||
})
|
||||
|
||||
const isSwitch = ref(false)
|
||||
function submitType(value: string) {
|
||||
addressStore.addressInfo.type = value
|
||||
isSwitch.value = true
|
||||
switch (value) {
|
||||
case UserAddressType.HOUSE:
|
||||
uni.redirectTo({
|
||||
url: '/pages/address/save-address/house',
|
||||
})
|
||||
break;
|
||||
case UserAddressType.APARTMENT:
|
||||
uni.redirectTo({
|
||||
url: '/pages/address/save-address/apartment',
|
||||
})
|
||||
break;
|
||||
case UserAddressType.OFFICE:
|
||||
uni.redirectTo({
|
||||
url: '/pages/address/save-address/office',
|
||||
})
|
||||
break;
|
||||
case UserAddressType.HOTEL:
|
||||
uni.redirectTo({
|
||||
url: '/pages/address/save-address/hotel',
|
||||
})
|
||||
break;
|
||||
case UserAddressType.OTHER:
|
||||
uni.redirectTo({
|
||||
url: '/pages/address/save-address/other',
|
||||
})
|
||||
break;
|
||||
}
|
||||
}
|
||||
const chooseStateRef = ref()
|
||||
const stateList = ref([])
|
||||
function openState() {
|
||||
chooseStateRef.value?.init(stateList.value, addressStore.addressInfo.state)
|
||||
}
|
||||
function chooseStateConfirm(data: any) {
|
||||
addressStore.addressInfo.state = data.id
|
||||
addressStore.addressInfo.stateName = data.name
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view>
|
||||
<navbar :title="t('pages.address.titleDetail')" />
|
||||
<!-- 地址搜索结果名字 -->
|
||||
<view class="px-30rpx pt-38rpx border-bottom pb-52rpx">
|
||||
<view class="text-32rpx lh-32rpx text-#333 mb-62rpx">
|
||||
{{ addressStore.addressInfo.displayName }}
|
||||
{{ addressStore.addressInfo.formattedAddress }}
|
||||
</view>
|
||||
|
||||
<!-- State -->
|
||||
<view class="mb-44rpx">
|
||||
<view class="mb-20rpx text-36rpx lh-36rpx text-#333 font-500">{{ t('common.state') }}</view>
|
||||
<view @click="openState" class="bg-#F6F6F6 rounded-16rpx px-24rpx h-98rpx flex-center-sb">
|
||||
<text class="text-30rpx lh-30rpx text-#333">
|
||||
{{ addressStore.addressInfo.stateName ? addressStore.addressInfo.stateName : t('common.placeholder.pleaseSelect') }}
|
||||
</text>
|
||||
<image src="@img/chef/142.png" class="w-32rpx h-32rpx shrink-0"></image>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 房屋类型 -->
|
||||
<view>
|
||||
<view class="mb-20rpx text-36rpx lh-36rpx text-#333 font-500">{{ t('pages.address.choose-type.navTitle') }}</view>
|
||||
<view @click="openBuildingType" class="bg-#F6F6F6 rounded-16rpx px-24rpx h-98rpx flex-center-sb">
|
||||
<text class="text-30rpx lh-30rpx text-#333">
|
||||
{{ `${t(`pages.address.choose-type.${addressStore.addressInfo.type}`)}` }}
|
||||
</text>
|
||||
<image src="@img/chef/142.png" class="w-32rpx h-32rpx shrink-0 rotate-90"></image>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Corporate Name (required) -->
|
||||
<view class="mt-44rpx">
|
||||
<view class="mb-20rpx text-36rpx lh-36rpx text-#333 font-500">{{ t('pages.address.office.titleCorporate') }}</view>
|
||||
<view class="bg-#F6F6F6 rounded-16rpx px-24rpx h-98rpx flex items-center">
|
||||
<wd-input
|
||||
v-model.trim="addressStore.addressInfo.companyName"
|
||||
:cursorSpacing="20"
|
||||
:focus-when-clear="false"
|
||||
:maxlength="20"
|
||||
:placeholder="t('pages.address.office.titleCorporateTips')"
|
||||
clearable
|
||||
custom-class="flex-1 !text-30rpx lh-30rpx !text-#333 !bg-transparent"
|
||||
no-border
|
||||
placeholderStyle="font-size: 30rpx;color: #999;"
|
||||
>
|
||||
</wd-input>
|
||||
</view>
|
||||
</view>
|
||||
<!-- Suite/Floor -->
|
||||
<view class="mt-44rpx">
|
||||
<view class="mb-20rpx text-36rpx lh-36rpx text-#333 font-500">{{ t('pages.address.office.titleSuite') }}</view>
|
||||
<view class="bg-#F6F6F6 rounded-16rpx px-24rpx h-98rpx flex items-center">
|
||||
<wd-input
|
||||
v-model.trim="addressStore.addressInfo.officeFloor"
|
||||
:cursorSpacing="20"
|
||||
:focus-when-clear="false"
|
||||
:maxlength="20"
|
||||
:placeholder="t('pages.address.office.titleSuiteTips')"
|
||||
clearable
|
||||
custom-class="flex-1 !text-30rpx lh-30rpx !text-#333 !bg-transparent"
|
||||
no-border
|
||||
placeholderStyle="font-size: 30rpx;color: #999;"
|
||||
>
|
||||
</wd-input>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="px-30rpx py-52rpx">
|
||||
<!-- Delivery Point Information -->
|
||||
<view class="mb-44rpx text-36rpx lh-36rpx text-#333 font-500">{{ t('pages.address.deliveryPointInfo') }}</view>
|
||||
|
||||
<view @click="openVisitMethod" class="flex-center-sb">
|
||||
<view>
|
||||
<view class="text-32rpx lh-32rpx text-#333 mb-12rpx">{{ visitMethod }}</view>
|
||||
<view class="text-32rpx lh-32rpx text-#999">{{ t('pages.address.moreOptions') }}</view>
|
||||
</view>
|
||||
<image src="@img/chef/142.png" class="w-32rpx h-32rpx shrink-0"></image>
|
||||
</view>
|
||||
|
||||
<view class="mt-52rpx">
|
||||
<view class="text-36rpx lh-36rpx text-#333 font-500 mb-24rpx">{{ t('pages.address.deliveryInstructions') }}</view>
|
||||
<view
|
||||
class="min-h-240rpx box-border bg-#F6F6F6 rounded-20rpx overflow-hidden p-20rpx"
|
||||
>
|
||||
<wd-textarea
|
||||
:maxlength="250"
|
||||
custom-class="!bg-#F6F6F6"
|
||||
custom-textarea-container-class="!bg-#F6F6F6"
|
||||
no-border
|
||||
auto-height
|
||||
v-model="addressStore.addressInfo.deliveryRemark"
|
||||
:placeholder="t('pages.address.pleaseTip')"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="mt-24rpx relative w-240rpx h-240rpx" @click="chooseImage">
|
||||
<image
|
||||
v-if="images"
|
||||
class="absolute top--10rpx right--10rpx z-1 w-36rpx h-36rpx"
|
||||
src="@img/chef/113.png"
|
||||
@click.stop="removeImage"
|
||||
></image>
|
||||
<view v-if="!images" class="flex flex-col items-center justify-center w-240rpx h-240rpx bg-#f6f6f6 rounded-20rpx">
|
||||
<wd-icon name="add" size="32px" color="#3d3d3d"></wd-icon>
|
||||
<text class="text-30rpx lh-30rpx text-#333 mt-22rpx">{{ t('common.addPicture') }}</text>
|
||||
</view>
|
||||
<image
|
||||
v-else
|
||||
:src="images"
|
||||
class="w-240rpx h-240rpx rounded-20rpx"
|
||||
mode="aspectFill"
|
||||
></image>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
<fixed-bottom-large-btn
|
||||
class="z-100"
|
||||
fixed
|
||||
:text="t('common.saveAndContinue')"
|
||||
@click="saveAddress"
|
||||
/>
|
||||
|
||||
<visit-method @confirm="visitMethodConfirm" ref="visitMethodRef" />
|
||||
<!-- 房屋类型 -->
|
||||
<building-type ref="buildingTypeRef" @submit="submitType" />
|
||||
|
||||
<ChooseImage ref="chooseImageRef" @change="onImageChange"/>
|
||||
<!-- 所在州 -->
|
||||
<choose-state ref="chooseStateRef" @confirm="chooseStateConfirm" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
:deep(.wd-textarea) {
|
||||
padding: 0 !important;
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
page {
|
||||
background-color: #fff;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,295 @@
|
||||
<script setup lang="ts">
|
||||
import ChooseImage from "@/components/choose-image/choose-image.vue";
|
||||
import chooseState from "../components/choose-state.vue";
|
||||
import { getStateListApi } from '@/pages-user/service'
|
||||
|
||||
const { t } = useI18n()
|
||||
import VisitMethod from '@/components/visit-method/index.vue'
|
||||
import BuildingType from './components/building-type.vue'
|
||||
import {useAddressStore} from "../store/address";
|
||||
import {appUserAddressAddPost, appUserAddressEditPost} from "@/service";
|
||||
import {UserAddressType} from "@/constant/enums";
|
||||
const addressStore = useAddressStore()
|
||||
|
||||
const visitMethodRef = ref<InstanceType<typeof VisitMethod>>()
|
||||
const buildingTypeRef = ref<InstanceType<typeof BuildingType>>()
|
||||
function openVisitMethod() {
|
||||
visitMethodRef.value?.onOpen()
|
||||
}
|
||||
|
||||
const visitMethod = ref(t('components.visit.leaveItToMePersonally')) // 默认选择亲自送达
|
||||
function visitMethodConfirm(data: any) {
|
||||
console.log('visitMethodConfirm', data)
|
||||
visitMethod.value = data.label;
|
||||
addressStore.addressInfo.deliveryType = data.value + 1; // 更新地址信息中的送达方式
|
||||
}
|
||||
|
||||
function openBuildingType() {
|
||||
buildingTypeRef.value?.onOpen()
|
||||
}
|
||||
|
||||
|
||||
// 选择图片 - 添加安全检查
|
||||
const chooseImageRef = ref<InstanceType<typeof ChooseImage>>()
|
||||
const images = ref<string>('')
|
||||
function chooseImage() {
|
||||
if (chooseImageRef.value?.init) {
|
||||
chooseImageRef.value.init();
|
||||
}
|
||||
}
|
||||
|
||||
// 图片选择回调 - 修复类型问题
|
||||
function onImageChange(files: string[]) {
|
||||
if (files.length > 0) {
|
||||
// 确保 images 数组不为空
|
||||
images.value = files[0]; // 只取第一个图片
|
||||
} else {
|
||||
images.value = ''; // 如果没有选择图片,清空
|
||||
}
|
||||
}
|
||||
|
||||
function removeImage() {
|
||||
images.value = ''
|
||||
}
|
||||
|
||||
function saveAddress() {
|
||||
console.log('保存地址信息', addressStore.addressInfo);
|
||||
if(addressStore.addressInfo.id) {
|
||||
appUserAddressEditPost({
|
||||
body: {
|
||||
...addressStore.addressInfo,
|
||||
deliveryImage: images.value
|
||||
}
|
||||
}).then(res=> {
|
||||
uni.navigateBack()
|
||||
addressStore.clearAddressInfo()
|
||||
})
|
||||
} else {
|
||||
appUserAddressAddPost({
|
||||
body: {
|
||||
...addressStore.addressInfo,
|
||||
deliveryImage: images.value
|
||||
}
|
||||
}).then(res=> {
|
||||
uni.navigateBack()
|
||||
addressStore.clearAddressInfo()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onLoad(()=> {
|
||||
isSwitch.value = false
|
||||
if(addressStore.addressInfo.deliveryImage) {
|
||||
images.value = addressStore.addressInfo.deliveryImage
|
||||
}
|
||||
// 回显送达偏好
|
||||
if(+addressStore.addressInfo.deliveryType === 1) {
|
||||
visitMethod.value = t('components.visit.leaveItToMePersonally')
|
||||
}
|
||||
if(+addressStore.addressInfo.deliveryType === 2) {
|
||||
visitMethod.value = t('components.visit.putItAtTheDoor')
|
||||
}
|
||||
|
||||
getStateListApi().then(res=> {
|
||||
stateList.value = res.data
|
||||
if(addressStore.addressInfo.state) {
|
||||
addressStore.addressInfo.stateName = res.data.find(item => item.id === addressStore.addressInfo.state)?.name || ''
|
||||
}
|
||||
})
|
||||
})
|
||||
onUnload(()=> {
|
||||
if(!isSwitch.value) {
|
||||
addressStore.clearAddressInfo()
|
||||
}
|
||||
})
|
||||
|
||||
const isSwitch = ref(false)
|
||||
function submitType(value: string) {
|
||||
addressStore.addressInfo.type = value
|
||||
isSwitch.value = true
|
||||
switch (value) {
|
||||
case UserAddressType.HOUSE:
|
||||
uni.redirectTo({
|
||||
url: '/pages/address/save-address/house',
|
||||
})
|
||||
break;
|
||||
case UserAddressType.APARTMENT:
|
||||
uni.redirectTo({
|
||||
url: '/pages/address/save-address/apartment',
|
||||
})
|
||||
break;
|
||||
case UserAddressType.OFFICE:
|
||||
uni.redirectTo({
|
||||
url: '/pages/address/save-address/office',
|
||||
})
|
||||
break;
|
||||
case UserAddressType.HOTEL:
|
||||
uni.redirectTo({
|
||||
url: '/pages/address/save-address/hotel',
|
||||
})
|
||||
break;
|
||||
case UserAddressType.OTHER:
|
||||
uni.redirectTo({
|
||||
url: '/pages/address/save-address/other',
|
||||
})
|
||||
break;
|
||||
}
|
||||
}
|
||||
const chooseStateRef = ref()
|
||||
const stateList = ref([])
|
||||
function openState() {
|
||||
chooseStateRef.value?.init(stateList.value, addressStore.addressInfo.state)
|
||||
}
|
||||
function chooseStateConfirm(data: any) {
|
||||
addressStore.addressInfo.state = data.id
|
||||
addressStore.addressInfo.stateName = data.name
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view>
|
||||
<navbar :title="t('pages.address.titleDetail')" />
|
||||
<!-- 地址搜索结果名字 -->
|
||||
<view class="px-30rpx pt-38rpx border-bottom pb-52rpx">
|
||||
<view class="text-32rpx lh-32rpx text-#333 mb-62rpx">
|
||||
{{ addressStore.addressInfo.displayName }}
|
||||
{{ addressStore.addressInfo.formattedAddress }}
|
||||
</view>
|
||||
|
||||
<!-- State -->
|
||||
<view class="mb-44rpx">
|
||||
<view class="mb-20rpx text-36rpx lh-36rpx text-#333 font-500">{{ t('common.state') }}</view>
|
||||
<view @click="openState" class="bg-#F6F6F6 rounded-16rpx px-24rpx h-98rpx flex-center-sb">
|
||||
<text class="text-30rpx lh-30rpx text-#333">
|
||||
{{ addressStore.addressInfo.stateName ? addressStore.addressInfo.stateName : t('common.placeholder.pleaseSelect') }}
|
||||
</text>
|
||||
<image src="@img/chef/142.png" class="w-32rpx h-32rpx shrink-0"></image>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 房屋类型 -->
|
||||
<view>
|
||||
<view class="mb-20rpx text-36rpx lh-36rpx text-#333 font-500">{{ t('pages.address.choose-type.navTitle') }}</view>
|
||||
<view @click="openBuildingType" class="bg-#F6F6F6 rounded-16rpx px-24rpx h-98rpx flex-center-sb">
|
||||
<text class="text-30rpx lh-30rpx text-#333">
|
||||
{{ `${t(`pages.address.choose-type.${addressStore.addressInfo.type}`)}` }}
|
||||
</text>
|
||||
<image src="@img/chef/142.png" class="w-32rpx h-32rpx shrink-0 rotate-90"></image>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Hotel Name (required) -->
|
||||
<view class="mt-44rpx">
|
||||
<view class="mb-20rpx text-36rpx lh-36rpx text-#333 font-500">{{ t('pages.address.other.titleApartment') }}</view>
|
||||
<view class="bg-#F6F6F6 rounded-16rpx px-24rpx h-98rpx flex items-center">
|
||||
<wd-input
|
||||
v-model.trim="addressStore.addressInfo.hotelName"
|
||||
:cursorSpacing="20"
|
||||
:focus-when-clear="false"
|
||||
:maxlength="20"
|
||||
:placeholder="t('pages.address.other.titleApartmentTips')"
|
||||
clearable
|
||||
custom-class="flex-1 !text-30rpx lh-30rpx !text-#333 !bg-transparent"
|
||||
no-border
|
||||
placeholderStyle="font-size: 30rpx;color: #999;"
|
||||
>
|
||||
</wd-input>
|
||||
</view>
|
||||
</view>
|
||||
<!-- Room/Floor -->
|
||||
<view class="mt-44rpx">
|
||||
<view class="mb-20rpx text-36rpx lh-36rpx text-#333 font-500">{{ t('pages.address.other.titleCompany') }}</view>
|
||||
<view class="bg-#F6F6F6 rounded-16rpx px-24rpx h-98rpx flex items-center">
|
||||
<wd-input
|
||||
v-model.trim="addressStore.addressInfo.hotelRoom"
|
||||
:cursorSpacing="20"
|
||||
:focus-when-clear="false"
|
||||
:maxlength="20"
|
||||
:placeholder="t('pages.address.other.titleCompanyTips')"
|
||||
clearable
|
||||
custom-class="flex-1 !text-30rpx lh-30rpx !text-#333 !bg-transparent"
|
||||
no-border
|
||||
placeholderStyle="font-size: 30rpx;color: #999;"
|
||||
>
|
||||
</wd-input>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="px-30rpx py-52rpx">
|
||||
<!-- Delivery Point Information -->
|
||||
<view class="mb-44rpx text-36rpx lh-36rpx text-#333 font-500">{{ t('pages.address.deliveryPointInfo') }}</view>
|
||||
|
||||
<view @click="openVisitMethod" class="flex-center-sb">
|
||||
<view>
|
||||
<view class="text-32rpx lh-32rpx text-#333 mb-12rpx">{{ visitMethod }}</view>
|
||||
<view class="text-32rpx lh-32rpx text-#999">{{ t('pages.address.moreOptions') }}</view>
|
||||
</view>
|
||||
<image src="@img/chef/142.png" class="w-32rpx h-32rpx shrink-0"></image>
|
||||
</view>
|
||||
|
||||
<view class="mt-52rpx">
|
||||
<view class="text-36rpx lh-36rpx text-#333 font-500 mb-24rpx">{{ t('pages.address.deliveryInstructions') }}</view>
|
||||
<view
|
||||
class="min-h-240rpx box-border bg-#F6F6F6 rounded-20rpx overflow-hidden p-20rpx"
|
||||
>
|
||||
<wd-textarea
|
||||
:maxlength="250"
|
||||
custom-class="!bg-#F6F6F6"
|
||||
custom-textarea-container-class="!bg-#F6F6F6"
|
||||
no-border
|
||||
auto-height
|
||||
v-model="addressStore.addressInfo.deliveryRemark"
|
||||
:placeholder="t('pages.address.pleaseTip')"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="mt-24rpx relative w-240rpx h-240rpx" @click="chooseImage">
|
||||
<image
|
||||
v-if="images"
|
||||
class="absolute top--10rpx right--10rpx z-1 w-36rpx h-36rpx"
|
||||
src="@img/chef/113.png"
|
||||
@click.stop="removeImage"
|
||||
></image>
|
||||
<view v-if="!images" class="flex flex-col items-center justify-center w-240rpx h-240rpx bg-#f6f6f6 rounded-20rpx">
|
||||
<wd-icon name="add" size="32px" color="#3d3d3d"></wd-icon>
|
||||
<text class="text-30rpx lh-30rpx text-#333 mt-22rpx">{{ t('common.addPicture') }}</text>
|
||||
</view>
|
||||
<image
|
||||
v-else
|
||||
:src="images"
|
||||
class="w-240rpx h-240rpx rounded-20rpx"
|
||||
mode="aspectFill"
|
||||
></image>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
<fixed-bottom-large-btn
|
||||
class="z-100"
|
||||
fixed
|
||||
:text="t('common.saveAndContinue')"
|
||||
@click="saveAddress"
|
||||
/>
|
||||
|
||||
<visit-method @confirm="visitMethodConfirm" ref="visitMethodRef" />
|
||||
<!-- 房屋类型 -->
|
||||
<building-type ref="buildingTypeRef" @submit="submitType" />
|
||||
|
||||
<ChooseImage ref="chooseImageRef" @change="onImageChange"/>
|
||||
<!-- 所在州 -->
|
||||
<choose-state ref="chooseStateRef" @confirm="chooseStateConfirm" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
:deep(.wd-textarea) {
|
||||
padding: 0 !important;
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
page {
|
||||
background-color: #fff;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,59 @@
|
||||
<script setup lang="ts">
|
||||
const { t } = useI18n();
|
||||
const keyword = ref('');
|
||||
function handleSearch(){
|
||||
console.log("1111111111");
|
||||
|
||||
}
|
||||
function handleClickItem(){
|
||||
uni.navigateTo({
|
||||
url: '/pages/address/choose-type'
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<z-paging>
|
||||
<template #top>
|
||||
<navbar :title="t('pages.address.title')" />
|
||||
<view class="px-30rpx mt-32rpx">
|
||||
<view
|
||||
class="px-36rpx h-88rpx bg-#F2F3F6 flex items-center rounded-44rpx "
|
||||
>
|
||||
<image src="@img/chef/100222.png" class="w-28rpx h-28rpx mr-18rpx"></image>
|
||||
<wd-input
|
||||
no-border
|
||||
clearable
|
||||
:focus-when-clear="false"
|
||||
:focus="true"
|
||||
confirm-type="search"
|
||||
use-prefix-slot
|
||||
custom-class="flex items-center !text-30rpx !bg-transparent flex-1"
|
||||
placeholderStyle="font-size: 30rpx;color: #6D6D6D; font-weight: 500;"
|
||||
:modelValue="keyword"
|
||||
:placeholder="t('components.search.placeholder')"
|
||||
@confirm="handleSearch"
|
||||
>
|
||||
</wd-input>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<view class="pl-30rpx mt-24rpx">
|
||||
<template v-for="item in 6">
|
||||
<view @click="handleClickItem" class="flex items-center">
|
||||
<image src="@img/chef/145.png" class="w-44rpx h-44rpx shrink-0 mr-28rpx"></image>
|
||||
<view class="flex-1 h-156rpx border-bottom pt-40rpx">
|
||||
<view class="text-32rpx lh-32rpx text-#333 font-500 mb-16rpx">818 Avenue du Mont-Royal E</view>
|
||||
<view class="text-28rpx lh-28rpx text-#6D6D6D">Montreal,QC</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</z-paging>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
page {
|
||||
background-color: #fff;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,108 @@
|
||||
import { defineStore } from "pinia";
|
||||
import type { UserAddressBo } from '@/service/types';
|
||||
export const useAddressStore = defineStore('store-address', () => {
|
||||
const addressInfo = ref<UserAddressBo>({
|
||||
type: '',
|
||||
/** 配送类型 1 2 */
|
||||
deliveryType: '1',
|
||||
/** 配送说明 */
|
||||
deliveryRemark: '',
|
||||
/** 配送图片 */
|
||||
deliveryImage: '',
|
||||
/** 门牌号(住宅专用) */
|
||||
houseNumber: '',
|
||||
/** 其他信息(住宅专用) */
|
||||
houseExtra: '',
|
||||
/** 楼宇名称(公寓专用) */
|
||||
buildingName: '',
|
||||
/** 公寓单元楼层(公寓专用) */
|
||||
apartmentUnitFloor: '',
|
||||
/** 入口代码(公寓专用) */
|
||||
entranceCodeApartment: '',
|
||||
/** 公司名称(公司专用) */
|
||||
companyName: '',
|
||||
/** 办公楼楼层(公司专用) */
|
||||
officeFloor: '',
|
||||
/** 酒店名称(酒店专用) */
|
||||
hotelName: '',
|
||||
/** 酒店房间号(酒店专用) */
|
||||
hotelRoom: '',
|
||||
/** 入口代码(酒店专用) */
|
||||
entranceCodeHotel: '',
|
||||
/** 公寓/套房/楼层(其他专用) */
|
||||
otherLocation: '',
|
||||
/** 公司/楼宇名称(其他专用) */
|
||||
otherBuilding: '',
|
||||
longitude: '',
|
||||
latitude: '',
|
||||
displayName: '',
|
||||
formattedAddress: '',
|
||||
state: '', // 州信息
|
||||
stateName: '', // 州名称
|
||||
})
|
||||
|
||||
// 地图经纬度信息
|
||||
const addressLocation = ref({
|
||||
location: '',
|
||||
longitude: '',
|
||||
latitude: '',
|
||||
});
|
||||
|
||||
function setAddressLocation(data: any) {
|
||||
addressLocation.value = data
|
||||
addressInfo.value.longitude = data.longitude
|
||||
addressInfo.value.latitude = data.latitude
|
||||
addressInfo.value.displayName = data.displayName
|
||||
addressInfo.value.formattedAddress = data.formattedAddress
|
||||
}
|
||||
|
||||
function clearAddressInfo() {
|
||||
addressInfo.value = {
|
||||
id: '',
|
||||
type: '',
|
||||
/** 配送类型 1 2 */
|
||||
deliveryType: '1',
|
||||
/** 配送说明 */
|
||||
deliveryRemark: '',
|
||||
/** 配送图片 */
|
||||
deliveryImage: '',
|
||||
/** 门牌号(住宅专用) */
|
||||
houseNumber: '',
|
||||
/** 其他信息(住宅专用) */
|
||||
houseExtra: '',
|
||||
/** 楼宇名称(公寓专用) */
|
||||
buildingName: '',
|
||||
/** 公寓单元楼层(公寓专用) */
|
||||
apartmentUnitFloor: '',
|
||||
/** 入口代码(公寓专用) */
|
||||
entranceCodeApartment: '',
|
||||
/** 公司名称(公司专用) */
|
||||
companyName: '',
|
||||
/** 办公楼楼层(公司专用) */
|
||||
officeFloor: '',
|
||||
/** 酒店名称(酒店专用) */
|
||||
hotelName: '',
|
||||
/** 酒店房间号(酒店专用) */
|
||||
hotelRoom: '',
|
||||
/** 入口代码(酒店专用) */
|
||||
entranceCodeHotel: '',
|
||||
/** 公寓/套房/楼层(其他专用) */
|
||||
otherLocation: '',
|
||||
/** 公司/楼宇名称(其他专用) */
|
||||
otherBuilding: '',
|
||||
longitude: '',
|
||||
latitude: '',
|
||||
displayName: '',
|
||||
formattedAddress: '',
|
||||
state: '', // 州信息
|
||||
stateName: '', // 州名称
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
addressInfo,
|
||||
addressLocation,
|
||||
setAddressLocation,
|
||||
clearAddressInfo
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user