first commit

This commit is contained in:
2026-02-26 09:25:47 +08:00
commit 40665dda67
708 changed files with 100122 additions and 0 deletions
@@ -0,0 +1,134 @@
<template>
<view class="order-skeleton bg-#F6F6F6">
<view class="px-30rpx py-32rpx">
<!-- 订单状态骨架屏 -->
<view class="mb-20rpx">
<view class="flex items-center mb-18rpx">
<view class="w-42rpx h-42rpx rounded-full mr-12rpx skeleton-item"></view>
<view class="w-150rpx h-36rpx skeleton-item"></view>
</view>
<view class="w-200rpx h-24rpx skeleton-item"></view>
</view>
<!-- 警告提示框骨架屏 -->
<view class="bg-white rounded-16rpx p-24rpx mb-20rpx">
<view class="w-100rpx h-28rpx mb-10rpx skeleton-item"></view>
<view class="w-full h-20rpx skeleton-item"></view>
</view>
<!-- 配送地址卡片骨架屏 -->
<view class="bg-white rounded-16rpx pt-34rpx mb-20rpx">
<view class="px-24rpx border-bottom pb-36rpx">
<!-- 配送地址标题 -->
<view class="w-120rpx h-28rpx mb-22rpx skeleton-item"></view>
<!-- 地址信息区域 -->
<view class="flex items-center mb-6rpx">
<view class="w-40rpx h-40rpx rounded-full mr-10rpx skeleton-item"></view>
<view class="w-250rpx h-28rpx skeleton-item"></view>
</view>
<!-- 地址详情 -->
<view class="w-full h-26rpx skeleton-item"></view>
</view>
<!-- 操作按钮区域 -->
<view class="flex justify-between h-90rpx">
<view class="center h-full w-full border-r-1rpx border-r-solid border-r-#DEDEDE">
<view class="w-40rpx h-40rpx rounded-full mr-16rpx skeleton-item"></view>
<view class="w-80rpx h-24rpx skeleton-item"></view>
</view>
<view class="center w-full">
<view class="w-40rpx h-40rpx rounded-full mr-16rpx skeleton-item"></view>
<view class="w-60rpx h-24rpx skeleton-item"></view>
</view>
</view>
</view>
<!-- 送达时间骨架屏 -->
<view class="mb-20rpx bg-white px-24rpx h-160rpx rounded-16rpx flex flex-col justify-center">
<view class="w-120rpx h-28rpx mb-32rpx skeleton-item"></view>
<view class="w-300rpx h-28rpx skeleton-item"></view>
</view>
<!-- 商品信息卡片骨架屏 -->
<view class="w-690rpx bg-white rounded-16rpx p-24rpx mb-30rpx">
<!-- 商品信息标题 -->
<view class="w-120rpx h-28rpx mb-34rpx skeleton-item"></view>
<!-- 商品列表骨架屏 -->
<template v-for="item in 3" :key="item">
<view class="flex items-center gap-24rpx mb-34rpx last:mb-0">
<view class="w-120rpx h-120rpx rounded-16rpx shrink-0 skeleton-item"></view>
<view class="flex-1 h-120rpx flex flex-col justify-between">
<view class="w-200rpx h-28rpx skeleton-item"></view>
<view class="w-150rpx h-24rpx skeleton-item"></view>
<view class="flex justify-between items-center">
<view class="w-80rpx h-30rpx skeleton-item"></view>
<view class="w-60rpx h-28rpx skeleton-item"></view>
</view>
</view>
</view>
</template>
<!-- 展开收起按钮骨架屏 -->
<view class="mt-34rpx center">
<view class="w-262rpx h-50rpx rounded-4rpx skeleton-item"></view>
</view>
<!-- 订单总计骨架屏 -->
<view class="mt-32rpx border-t-1rpx pt-16rpx">
<view class="flex justify-between mb-16rpx">
<view class="w-80rpx h-30rpx skeleton-item"></view>
<view class="w-100rpx h-30rpx skeleton-item"></view>
</view>
<view class="flex justify-between mb-16rpx">
<view class="flex items-center">
<view class="w-120rpx h-30rpx skeleton-item"></view>
<view class="w-28rpx h-28rpx ml-10rpx skeleton-item"></view>
</view>
<view class="w-80rpx h-30rpx skeleton-item"></view>
</view>
<view class="flex justify-between">
<view class="w-60rpx h-30rpx skeleton-item"></view>
<view class="w-100rpx h-30rpx skeleton-item"></view>
</view>
</view>
</view>
<!-- 订单信息卡片骨架屏 -->
<view class="bg-white rounded-16rpx pt-32rpx mb-30rpx">
<!-- 订单信息标题 -->
<view class="flex items-center mb-30rpx">
<view class="bg-#FF7112 w-10rpx h-30rpx mr-14rpx"></view>
<view class="w-120rpx h-28rpx skeleton-item"></view>
</view>
<!-- 订单详情骨架屏 -->
<view class="px-24rpx">
<template v-for="item in 4" :key="item">
<view class="flex justify-between items-center h-88rpx border-bottom">
<view class="w-120rpx h-28rpx skeleton-item"></view>
<view class="w-180rpx h-28rpx skeleton-item"></view>
</view>
</template>
</view>
</view>
</view>
<!-- 底部按钮区域骨架屏 -->
<view class="fixed bottom-0 left-0 right-0 z-1 bg-white shadow-[0rpx_-6rpx_24rpx_rgba(0,0,0,0.16)]">
<view class="h-118rpx p-[10rpx+30rpx] box-border flex gap-22rpx">
<view class="w-full h-98rpx rounded-20rpx skeleton-item"></view>
<view class="w-full h-98rpx rounded-20rpx skeleton-item"></view>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
// 订单详情页面骨架屏组件
</script>
<style lang="scss" scoped>
</style>
@@ -0,0 +1,69 @@
<script lang="ts" setup>
import {useConfigStore} from "@/store";
const {t} = useI18n();
const configStore = useConfigStore();
// 用户会员状态是否已开通
const show = ref(false);
const priceData = ref({})
function onOpen(data: any, num: number) {
priceData.value = data
show.value = true;
}
function handleClose() {
show.value = false;
}
defineExpose({
onOpen,
});
</script>
<template>
<wd-popup
v-model="show"
position="bottom"
@close="handleClose"
>
<view>
<view class="center h-102rpx bg-#F7F7F7 text-40rpx lh-40rpx font-bold text-#333">
{{ t('pages-user.order.checkout.priceDetail.title') }}?
</view>
<view class="border-bottom text-32rpx lh-32rpx font-500 text-#333 py-36rpx px-30rpx">
<view class="flex-center-sb mb-20rpx">
<view>{{ t('pages-user.order.checkout.priceDetail.serviceFees') }}</view>
<view>${{
(Number(priceData?.tip) + Number(priceData?.deliveryFee)).toFixed(2)
}}
</view>
</view>
<view class="text-24rpx lh-28rpx text-#9E9E9E">
{{ t('pages-user.order.checkout.priceDetail.desc') }}
</view>
</view>
<view class="flex-center-sb border-bottom h-104rpx text-32rpx lh-32rpx font-500 text-#333 px-30rpx">
<view>{{ t('pages-user.order.checkout.priceDetail.taxation') }}</view>
<view>${{ Number(priceData?.tax) }}</view>
</view>
<view class="px-30rpx pt-40rpx">
<wd-button
block
custom-class="!h-108rpx !text-36rpx !rounded-16rpx !bg-#14181B"
@click="handleClose"
>
{{ t('common.gotIt') }}
</wd-button>
</view>
<view :style="[configStore.iosSafeBottomPlaceholder]"></view>
</view>
</wd-popup>
</template>
<style lang="scss" scoped>
</style>
@@ -0,0 +1,87 @@
<script lang="ts" setup>
import {appMerchantOrderRejectRefundPost} from "@/service";
import {useConfigStore} from "@/store";
const {t} = useI18n();
const emit = defineEmits(['close'])
const configStore = useConfigStore()
const show = ref(false);
const formData = reactive({
rejectReason: '',
orderId: ''
})
function onOpen(orderId: string) {
formData.orderId = orderId
show.value = true;
}
function handleClose() {
show.value = false;
}
function handleSubmit() {
if (!formData.rejectReason) {
uni.showToast({
title: t('pages-user.order.toast.refuseReasonRequired'),
icon: 'none'
})
return
}
appMerchantOrderRejectRefundPost({
body: {
...formData
}
}).then(res => {
uni.showToast({
title: t('pages-user.order.toast.refuseSuccess'),
icon: 'none'
})
emit('close')
handleClose()
})
}
defineExpose({
onOpen,
});
</script>
<template>
<wd-popup v-model="show" custom-style="border-radius:32rpx 32rpx 0 0;" position="bottom" @close="handleClose">
<view class="px-30rpx pt-40rpx relative">
<view class="text-34rpx text-#333 font-bold text-center mb-37rpx">{{ t('pages-user.order.refuseReason') }}</view>
<image class="w-28rpx h-28rpx absolute top-40rpx right-30rpx" src="@img/chef/100404.png"
@click="handleClose"></image>
<view
class="min-h-234rpx box-border bg-#F7F7F7 rounded-14rpx overflow-hidden px-18rpx py-10rpx mb-36rpx"
>
<wd-textarea
v-model="formData.rejectReason"
:maxlength="100"
:placeholder="t('pages-user.order.toast.refuseReasonRequired')"
auto-height
custom-class="!bg-#F7F7F7"
custom-textarea-container-class="!bg-#F7F7F7"
no-border
/>
</view>
<wd-button block custom-class="!h-98rpx !text-30rpx !font-bold !rounded-16rpx !bg-#14181B"
@click="handleSubmit">
{{ t('common.submit') }}
</wd-button>
<view :style="[configStore.iosSafeBottomPlaceholder]"></view>
</view>
</wd-popup>
</template>
<style lang="scss" scoped>
</style>
@@ -0,0 +1,105 @@
<script setup lang="ts">
interface Props {
// 当前评分值
modelValue: number
// 最大星级数量
maxStars?: number
// 星星大小
starSize?: number
// 激活颜色
activeColor?: string
// 未激活颜色
inactiveColor?: string
// 是否显示评分数值
showScore?: boolean
// 是否禁用交互
disabled?: boolean
// 自定义类名
customClass?: string
}
interface Emits {
(e: 'update:modelValue', value: number): void
(e: 'change', value: number): void
}
const props = withDefaults(defineProps<Props>(), {
maxStars: 5,
starSize: 44,
activeColor: '#F86F1F',
inactiveColor: '#D1D1D1',
showScore: true,
disabled: false,
customClass: ''
})
const emit = defineEmits<Emits>()
// 点击星星设置评分
const setRating = (rating: number) => {
if (props.disabled) return
emit('update:modelValue', rating)
emit('change', rating)
}
</script>
<template>
<view :class="['star-rating flex items-center', customClass]">
<!-- 星级评分 -->
<view class="flex items-center gap-10rpx">
<view
v-for="star in maxStars"
:key="star"
:class="[
'mr-8rpx last:mr-0 flex-center',
disabled ? '' : 'cursor-pointer'
]"
@click="setRating(star)"
>
<image
v-show="star <= modelValue"
src="@img/chef/155.png"
mode="aspectFill"
class="w-24rpx h-24rpx"
/>
<image
v-show="star > modelValue"
src="@img/chef/154.png"
mode="aspectFill"
class="w-24rpx h-24rpx"
/>
</view>
</view>
<!-- 评分数值 -->
<text
v-if="showScore"
class="text-28rpx lh-28rpx text-#333 font-500 ml-80rpx"
>
{{ modelValue }}.0
</text>
</view>
</template>
<style scoped lang="scss">
.star-rating {
.flex-center {
display: flex;
align-items: center;
justify-content: center;
}
.cursor-pointer {
cursor: pointer;
}
.bg-active {
transition: background-color 0.2s ease;
}
.bg-inactive {
transition: background-color 0.2s ease;
}
}
</style>
@@ -0,0 +1,243 @@
<script lang="ts" setup>
import {appMerchantOrderDeliverOrderPost, appMerchantOrderDeliveryListPost} from "@/service";
import {useConfigStore} from "@/store";
import ChooseImage from "@/components/choose-image/choose-image.vue";
import {z} from 'zod';
const {t} = useI18n();
const emits = defineEmits(['submit'])
const configStore = useConfigStore()
const show = ref(false);
const formData = reactive({
deliveryPhone: '',
orderId: '',
deliveryAvatar: '',
deliverySurname: '',
deliveryFirstName: '',
})
// Zod 校验 schema
const formSchema = z.object({
deliveryPhone: z.string().min(1, t('pages-user.order.startDeliveryForm.validation.deliveryPhoneRequired')),
orderId: z.string().min(1, t('pages-user.order.startDeliveryForm.validation.orderIdRequired')),
deliverySurname: z.string().min(1, t('pages-user.order.startDeliveryForm.validation.deliverySurnameRequired')),
deliveryFirstName: z.string().min(1, t('pages-user.order.startDeliveryForm.validation.deliveryFirstNameRequired')),
})
function onOpen(orderId: string) {
formData.orderId = orderId
show.value = true;
// 获取最新配送订单的配送员信息
getLatestDeliveryOrderInfo()
}
const userList = ref([])
// 当前选中的配送员信息下标
const currentSort = ref(null)
function getLatestDeliveryOrderInfo() {
appMerchantOrderDeliveryListPost({}).then(res => {
console.log('获取最新配送订单的配送员信息', res)
userList.value = res.data
isShowAdd.value = res.data.length <= 0;
})
}
function changeUser(item: any, index: number) {
currentSort.value = index
formData.deliveryAvatar = item.deliveryAvatar
formData.deliverySurname = item.deliverySurname
formData.deliveryFirstName = item.deliveryFirstName
formData.deliveryPhone = item.deliveryPhone
}
function handleClose() {
show.value = false;
}
function handleSubmit() {
try {
// 使用 zod 校验表单数据
formSchema.parse(formData)
// 校验通过,调用接口
appMerchantOrderDeliverOrderPost({
body: {
...formData
}
}).then(res => {
emits('submit')
handleClose()
})
} catch (error) {
// 校验失败,显示错误信息
if (error instanceof z.ZodError) {
const firstError = error.errors[0]
uni.showToast({
title: firstError.message,
icon: 'none'
})
}
}
}
const chooseImageRef = ref()
function chooseImage() {
if (chooseImageRef.value?.init) {
chooseImageRef.value.init();
}
}
function onImageChange(file: any) {
if (file.length > 0) {
formData.deliveryAvatar = file[0]
}
}
const isShowAdd = ref(false)
defineExpose({
onOpen,
});
</script>
<template>
<wd-popup v-model="show" custom-style="border-radius:32rpx 32rpx 0 0;" position="bottom"
@close="handleClose">
<view>
<view class="flex-center-sb h-102rpx bg-#F7F7F7 px-30rpx">
<view class="text-30rpx text-#999" @click="handleClose">{{ t('common.cancel') }}</view>
<view class="text-34rpx text-#333">{{ t('pages-user.order.startDeliveryForm.title') }}</view>
<view class="text-30rpx text-#FF6106" @click="handleSubmit">{{ t('common.confirm') }}</view>
</view>
<!--已经有配送员信息-->
<template v-if="!isShowAdd">
<scroll-view class="h-400rpx" scroll-y="true">
<view class="px-30rpx">
<template v-for="(item, index) in userList">
<view class="w-full py-24rpx bg-white flex-center-sb" @click="changeUser(item, index)">
<view class="flex items-center">
<image
:src="item.deliveryAvatar"
class="w-76rpx h-76rpx rounded-50%"
mode="aspectFill"
/>
<view class="ml-20rpx">
<view>{{ item.deliverySurname || '' }} {{ item.deliveryFirstName || '' }}</view>
<view>{{ item.deliveryPhone || '' }}</view>
</view>
</view>
<image
:src="
index === currentSort
? '/static/images/chef/152.png'
: '/static/images/chef/134.png'
"
class="w-40rpx h-40rpx"
mode="aspectFit"
/>
</view>
</template>
</view>
</scroll-view>
<view class="px-30rpx w-full" @click="isShowAdd = true">
<wd-button class="w-full !h-98rpx">{{ t('pages-user.recipe.index.add') }}</wd-button>
</view>
</template>
<template v-else>
<view class="px-30rpx py-20rpx">
<view class="mt-20rpx">
<view class="text-28rpx lh-28rpx text-#333 font-500 mb-20rpx">
{{ t('pages-user.order.startDeliveryForm.deliveryAvatar') }}
</view>
<view class="h-98rpx" @click="chooseImage">
<image
v-if="formData.deliveryAvatar"
:src="formData.deliveryAvatar"
class="w-76rpx h-76rpx rounded-50%"
mode="aspectFill"
/>
<image
v-else
class="w-76rpx h-76rpx rounded-50%"
mode="aspectFill"
src="@img/chef/default_avatar.png"
/>
</view>
</view>
<view class="mt-20rpx">
<view class="text-28rpx lh-28rpx text-#333 font-500 mb-20rpx">
{{ t('pages-user.order.startDeliveryForm.deliveryPhone') }}
</view>
<view class="flex-center-sb h-98rpx px-24rpx rounded-16rpx bg-#F6F6F6">
<wd-input
v-model="formData.deliveryPhone"
:focus-when-clear="false"
:placeholder="t('common.enter')"
clearable
confirm-type="done"
custom-class="!text-30rpx !bg-transparent flex-1"
no-border
placeholderStyle="font-size: 30rpx;color: #999;"
use-prefix-slot
>
</wd-input>
</view>
</view>
<view class="mt-20rpx">
<view class="text-28rpx lh-28rpx text-#333 font-500 mb-20rpx">
{{ t('pages-user.order.startDeliveryForm.deliverySurname') }}
</view>
<view class="flex-center-sb h-98rpx px-24rpx rounded-16rpx bg-#F6F6F6">
<wd-input
v-model="formData.deliverySurname"
:focus-when-clear="false"
:placeholder="t('common.enter')"
clearable
confirm-type="done"
custom-class="!text-30rpx !bg-transparent flex-1"
no-border
placeholderStyle="font-size: 30rpx;color: #999;"
use-prefix-slot
>
</wd-input>
</view>
</view>
<view class="mt-20rpx">
<view class="text-28rpx lh-28rpx text-#333 font-500 mb-20rpx">
{{ t('pages-user.order.startDeliveryForm.deliveryFirstName') }}
</view>
<view class="flex-center-sb h-98rpx px-24rpx rounded-16rpx bg-#F6F6F6">
<wd-input
v-model="formData.deliveryFirstName"
:focus-when-clear="false"
:placeholder="t('common.enter')"
clearable
confirm-type="done"
custom-class="!text-30rpx !bg-transparent flex-1"
no-border
placeholderStyle="font-size: 30rpx;color: #999;"
use-prefix-slot
>
</wd-input>
</view>
</view>
</view>
</template>
<view :style="[configStore.iosSafeBottomPlaceholder]"></view>
</view>
</wd-popup>
<ChooseImage ref="chooseImageRef" @change="onImageChange"/>
</template>
<style lang="scss" scoped>
:deep(.wd-input__clear) {
background-color: transparent !important;
}
</style>