删除冗余代码

This commit is contained in:
陈善美 2025-08-28 14:54:05 +08:00
parent fe61729cb4
commit 860ce21b52
27 changed files with 0 additions and 6201 deletions

View File

@ -1,219 +0,0 @@
<script setup lang="ts">
import { getOrderEvaluateListApi, getOrderEvaluateTabsApi } from '@/api/order'
import { ref } from 'vue'
// 使使
import { onPageScroll, onReachBottom } from '@dcloudio/uni-app'
import useZPaging from '@/uni_modules/z-paging/components/z-paging/js/hooks/useZPaging.js'
import type { orderEvaluateListResult } from '@/types/order'
import type { TabItem } from '@/types/global'
import { fullUrl, onShowRefreshData, previewImg } from '@/utils/common'
const props = defineProps<{
goods_id: string
}>()
const paging = ref()
// mixinsrefpagingpagingpaging.value
useZPaging(paging)
const tabList = ref<TabItem[]>([])
const tabIndex = ref(0)
const tabsChange = (index: number) => {
tabIndex.value = index
paging.value.reload()
}
const dataList = ref<orderEvaluateListResult[]>([])
const queryList = async (page: number, page_size: number) => {
const res = await getOrderEvaluateListApi({
page,
page_size,
goods_id: props.goods_id,
type: tabList.value[tabIndex.value]?.value ?? '',
})
paging.value.complete(res.result.data)
}
const getTabList = async () => {
const res = await getOrderEvaluateTabsApi({
goods_id: props.goods_id,
})
tabList.value = res.result.map((item) => ({
...item,
disabled: false,
badge: { count: item.count },
}))
}
const groupedImages = (imageList: string[]) => {
if (imageList.length <= 0) return []
const groups = []
const images = imageList.map((item) => fullUrl(item))
for (let i = 0; i < images.length; i += 3) {
groups.push(images.slice(i, i + 3))
}
return groups
}
onShowRefreshData(() => {
getTabList()
})
</script>
<template>
<z-paging ref="paging" v-model="dataList" @query="queryList" :safe-area-inset-bottom="true">
<template #top>
<z-tabs
:list="tabList"
@change="tabsChange"
:current="tabIndex"
:badge-style="{ backgroundColor: '#c3c3c3' }"
/>
</template>
<view class="evaluate panel" v-for="(item, index) in dataList" :key="index">
<view class="top">
<shop-user-avatar class="portrait" :url="item.user_avatar" width="70" />
<text class="name">{{ item.user_name }}</text>
<text class="time">{{ item.create_time }}</text>
</view>
<view class="rate">
<uni-rate :value="item.score" :size="18" readonly />
<text class="spec">{{ item.goods_spec }}</text>
</view>
<text class="content">{{ item.content }}</text>
<view class="content-img">
<view class="image-group" v-for="(group, index) in groupedImages(item.images)" :key="index">
<image
v-for="(img, imgIndex) in group"
:key="imgIndex"
:src="img"
mode="aspectFill"
@click="previewImg(img, item.images)"
:class="{
'first-image': imgIndex === 0 && group.length > 1,
'last-image': imgIndex === group.length - 1 && group.length > 1,
'single-image': group.length === 1,
'middle-image': imgIndex !== 0 && imgIndex !== group.length - 1,
}"
/>
</view>
</view>
<view class="bot"> </view>
</view>
</z-paging>
</template>
<style lang="scss">
page {
height: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
background-color: #f4f4f4;
}
.panel {
margin-top: 10rpx;
background-color: #fff;
padding: 20rpx;
margin: 20rpx;
border-radius: 20rpx;
.top {
display: flex;
align-items: center;
margin-bottom: 10rpx;
.portrait {
width: 70rpx;
height: 70rpx;
border-radius: 50%;
margin-right: 10px;
}
.name {
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-size: 28rpx;
}
.time {
font-size: 24rpx;
}
}
.rate {
display: flex;
align-items: center;
margin-bottom: 10rpx;
.spec {
::before {
content: '|';
margin: 0 10rpx;
}
color: #666;
font-size: 20rpx;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
}
}
.content {
display: flex;
padding-left: 7rpx;
font-size: 26rpx;
margin: 20rpx 0rpx;
}
.content-img {
display: flex;
flex-direction: column;
margin-top: 10rpx;
}
.image-group {
display: flex;
width: 100%;
}
.image-group image {
height: 200rpx;
width: 33%;
margin-right: 1.5%;
margin-bottom: 10rpx;
object-fit: cover;
border-radius: 0;
}
.image-group image.single-image {
border-radius: 20rpx;
}
.image-group image.first-image {
border-top-left-radius: 20rpx;
border-bottom-left-radius: 20rpx;
}
.image-group image.last-image {
border-top-right-radius: 20rpx;
border-bottom-right-radius: 20rpx;
}
.image-group image.middle-image {
border-radius: 0;
}
.image-group image:last-child {
margin-right: 0;
}
}
</style>

View File

@ -1,162 +0,0 @@
<script setup lang="ts">
import { useAddressStore } from '@/stores/modules/address'
import type { addressItem } from '@/types/address'
import { pageUrl } from '@/utils/constants'
const emit = defineEmits<{
(event: 'close'): void
}>()
//
const props = defineProps<{
list?: addressItem[]
}>()
const addressStore = useAddressStore()
//
const onSelectAddress = (item: addressItem) => {
addressStore.changeSelectedAddress(item)
emit('close')
}
const toAddressList = () => {
uni.navigateTo({
url: `${pageUrl['address-form']}`,
})
emit('close')
}
useAddressStore().changeScene('order')
</script>
<template>
<view class="address-panel">
<text class="close icon-close" @tap="emit('close')"></text>
<view class="title">配送至</view>
<view class="content">
<view v-if="props.list!.length > 0">
<view class="item" v-for="item in props.list" :key="item.id" @tap="onSelectAddress(item)">
<view class="user">{{ item.name }} {{ item.phone }}</view>
<view class="address">{{ item.full_location }} {{ item.address }}</view>
<text
class="icon"
:class="item.id === addressStore.selectedAddress?.id ? 'icon-checked' : 'icon-check'"
></text>
</view>
</view>
<view v-else>
<z-paging-empty-view
empty-view-text="暂无收货地址"
:empty-view-style="{
top: '50rpx',
}"
/>
</view>
</view>
</view>
<view class="footer">
<view class="button primary">
<view open-type="navigate" hover-class="navigator-hover" @tap="toAddressList">
新增收货地址
</view>
</view>
</view>
</template>
<style lang="scss">
.address-panel {
padding: 0 30rpx;
border-radius: 10rpx 10rpx 0 0;
position: relative;
background-color: #fff;
}
.title {
line-height: 1;
padding: 40rpx 0;
text-align: center;
font-size: 32rpx;
font-weight: normal;
border-bottom: 1rpx solid #ddd;
color: #444;
}
.close {
position: absolute;
right: 24rpx;
top: 24rpx;
font-size: 40rpx;
}
.content {
min-height: 300rpx;
max-height: 540rpx;
overflow: auto;
padding: 20rpx;
.item {
padding: 30rpx 50rpx 30rpx 60rpx;
background-size: 40rpx;
background-repeat: no-repeat;
background-position: 0 center;
background-image: url('~@/static/images/locate.png');
position: relative;
}
.icon {
color: #999;
font-size: 40rpx;
transform: translateY(-50%);
position: absolute;
top: 50%;
right: 0;
}
.icon-checked {
color: #ff5f3c;
}
.icon-ring {
color: #444;
}
.user {
font-size: 28rpx;
color: #444;
font-weight: 500;
}
.address {
font-size: 26rpx;
color: #666;
}
.empty-text {
text-align: center;
}
}
.footer {
display: flex;
justify-content: space-between;
padding: 20rpx 0 40rpx;
font-size: 28rpx;
color: #444;
.button {
flex: 1;
height: 72rpx;
text-align: center;
line-height: 72rpx;
margin: 0 20rpx;
color: #fff;
border-radius: 72rpx;
}
.primary {
color: #fff;
background-color: #ff5f3c;
}
.secondary {
background-color: #ffa868;
}
}
</style>

View File

@ -1,87 +0,0 @@
<script setup lang="ts">
import type { goodsServiceItem } from '@/types/goods'
const emit = defineEmits<{
(event: 'close'): void
}>()
//
const props = defineProps<{
list?: goodsServiceItem[]
}>()
</script>
<template>
<view class="service-panel">
<text class="close icon-close" @tap="emit('close')"></text>
<view class="title">服务说明</view>
<view class="content">
<view class="item" v-for="item in props.list" :key="item.id">
<view class="dt">{{ item.name }}</view>
<view class="dd">
{{ item.content }}
</view>
</view>
</view>
</view>
</template>
<style lang="scss">
.service-panel {
padding: 0 30rpx;
border-radius: 10rpx 10rpx 0 0;
position: relative;
background-color: #fff;
}
.title {
line-height: 1;
padding: 40rpx 0;
text-align: center;
font-size: 32rpx;
font-weight: normal;
border-bottom: 1rpx solid #ddd;
color: #444;
}
.close {
position: absolute;
right: 24rpx;
top: 24rpx;
font-size: 40rpx;
}
.content {
padding: 20rpx 20rpx 150rpx 20rpx;
.item {
margin-top: 20rpx;
}
.dt {
margin-bottom: 10rpx;
font-size: 28rpx;
color: #333;
font-weight: 500;
position: relative;
&::before {
content: '';
width: 10rpx;
height: 10rpx;
border-radius: 50%;
background-color: #eaeaea;
transform: translateY(-50%);
position: absolute;
top: 50%;
left: -20rpx;
}
}
.dd {
line-height: 1.6;
font-size: 26rpx;
color: #999;
}
}
</style>

View File

@ -1,153 +0,0 @@
<script setup lang="ts">
import type { goodsListItem } from '@/types/goods'
import { getShareUrl } from './sharePoster'
const emits = defineEmits<{
(event: 'close'): void
(event: 'showPoster'): void
}>()
const props = defineProps<{
goods: goodsListItem
}>()
const onCpoyLink = () => {
const shareUrl = getShareUrl(props.goods.id)
uni.setClipboardData({
data: shareUrl,
success: function () {
uni.showToast({
title: '复制成功',
icon: 'none',
})
},
})
emits('close')
}
const onShowSharePoster = () => {
emits('close')
emits('showPoster')
}
</script>
<template v-if="path == ''">
<view class="panel">
<view class="content">
<!-- #ifdef MP-WEIXIN -->
<button class="content-item" open-type="share">
<image class="icon" src="/static/icons/wechat.svg" />
<view class="text">微信好友</view>
</button>
<!-- #endif -->
<button class="content-item" @tap="onShowSharePoster">
<image class="icon" src="/static/icons/poster.svg" />
<view class="text">生成海报</view>
</button>
<button class="content-item" @tap="onCpoyLink">
<image class="icon" src="/static/icons/link.svg" />
<view class="text">复制链接</view>
</button>
</view>
<view class="footer">
<view class="button" @tap="emits('close')"> 取消分享 </view>
</view>
</view>
</template>
<style lang="scss">
.panel {
padding: 0 30rpx;
border-radius: 10rpx 10rpx 0 0;
position: relative;
background-color: #fff;
}
.title {
line-height: 1;
padding: 40rpx 0;
text-align: center;
font-size: 32rpx;
font-weight: normal;
border-bottom: 1rpx solid #ddd;
color: #444;
}
.close {
position: absolute;
right: 24rpx;
top: 24rpx;
font-size: 40rpx;
}
.content {
display: flex;
justify-content: cneter;
.content-item {
margin-top: 20rpx;
background-color: transparent;
border: none;
}
::after {
border: none;
}
.icon {
width: 80rpx;
height: 80rpx;
}
.text {
font-size: 30rpx;
}
}
.footer {
display: flex;
justify-content: space-between;
padding: 20rpx 0 20px;
font-size: 28rpx;
color: #444;
.button {
flex: 1;
height: 72rpx;
text-align: center;
line-height: 72rpx;
margin: 0 20rpx;
border-radius: 72rpx;
color: #fff;
background-color: #ff5f3c;
}
}
.action {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx;
background: #f5f5f5;
.button {
padding: 16rpx 32rpx;
margin: 0 10rpx;
background: #ff784d;
color: #ffffff;
border-radius: 40rpx;
&:active {
opacity: 0.8;
}
}
.cancel {
background: #b3b3b3;
color: #ffffff;
}
}
</style>

View File

@ -1,9 +0,0 @@
import { useMemberStore, useSettingStore } from '@/stores'
import { pageUrl } from '@/utils/constants'
export const getShareUrl = (goodsId: number | string) => {
const settingData = useSettingStore().data
const profileData = useMemberStore().profile
return `${settingData.h5_domain}${pageUrl['goods-detail']}?id=${goodsId}&sharer=${profileData?.id}`
}

View File

@ -1,231 +0,0 @@
<script setup lang="ts">
import { useMemberStore } from '@/stores'
import type { goodsListItem } from '@/types/goods'
import { ref, reactive } from 'vue'
import { fullUrl, checkImage, getUserDefaultAvatar } from '@/utils/common'
import { getShareInfoApi } from '@/api/goods'
import { getShareUrl } from './sharePoster'
import { usePopupStore } from '@/stores'
const props = defineProps<{
goods: goodsListItem
currentPrice: string
currentOriginPrice: string
goodsImage: string
}>()
const sharePosterRef = ref()
const painter = ref()
const state = reactive({
posterImgPath: '',
showPoster: false,
userAvatarImg: useMemberStore().profile!.avatar,
qrcodeUrl: '',
})
const userImage = ref('')
const open = async () => {
userImage.value = fullUrl(state.userAvatarImg)
await checkImage(userImage.value)
.then((url) => {
userImage.value = url
})
.catch(() => {
userImage.value = getUserDefaultAvatar()
})
// #ifdef MP-WEIXIN
const shareInfo = await getShareInfoApi(props.goods.id)
state.qrcodeUrl = shareInfo.result.qrcode_url
// #endif
await sharePosterRef.value?.open()
state.showPoster = true
state.posterImgPath = ''
await uni.showLoading({
title: '海报生成中…',
mask: true,
})
}
const close = () => {
sharePosterRef.value.close()
state.showPoster = false
}
const onDone = () => {}
const onSuccess = (imgPath: string) => {
state.posterImgPath = imgPath
uni.hideLoading()
}
const onSave = () => {
// #ifdef MP-WEIXIN
painter.value.canvasToTempFilePathSync({
fileType: 'jpg',
pathType: 'url',
quality: 1,
success: (res: any) => {
console.log(res.tempFilePath)
uni.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success: () => {
uni.showToast({
title: '保存成功',
icon: 'none',
})
},
fail: (err) => {
console.log(err)
if (err.errMsg == 'saveImageToPhotosAlbum:fail cancel') {
return uni.showToast({
title: '取消保存',
icon: 'none',
})
} else {
uni.showToast({
title: '保存失败',
icon: 'none',
})
}
},
complete: () => {
close()
},
})
},
})
// #endif
}
defineExpose({ sharePosterRef, open, close })
</script>
<template>
<uni-popup
ref="sharePosterRef"
type="center"
:animation="false"
:mask-click="false"
@change="usePopupStore().onChangePopupProps"
>
<image :src="state.posterImgPath" mode="widthFix" style="width: 600rpx" />
<view>
<view class="poster">
<l-painter
ref="painter"
isCanvasToTempFilePath
@success="onSuccess"
css="width: 750rpx; padding-bottom: 40rpx; background: #edca88; object-fit: contain;"
hidden
@done="onDone"
:after-delay="500"
>
<l-painter-image
:src="userImage"
css="margin-left: 40rpx; margin-top: 40rpx; width: 100rpx; height: 100rpx; border-radius: 50%;"
/>
<l-painter-view css="margin-top: 40rpx; padding-left: 20rpx; display: inline-block">
<l-painter-text
:text="useMemberStore().profile?.nickname || ''"
css="display: block; padding-bottom: 20rpx; color: #000; font-size: 32rpx; fontWeight: bold"
/>
<l-painter-text text="推荐了一个好物给您" css="color: #000; font-size: 24rpx" />
</l-painter-view>
<l-painter-view
css="margin-left: 40rpx; margin-top: 30rpx; padding: 32rpx; box-sizing: border-box; background: #fff; border-radius: 16rpx; width: 670rpx; box-shadow: 0 20rpx 58rpx rgba(0,0,0,.15)"
>
<l-painter-image
:src="fullUrl(props.goodsImage)"
css="object-fit: fill; width: 600rpx; max-height: 550rpx; border-radius: 12rpx;"
/>
<l-painter-view
css="margin-top: 32rpx; color: #FF0000; font-weight: bold; font-size: 28rpx; line-height: 1em;"
>
<l-painter-text text="¥" css="vertical-align: bottom" />
<l-painter-text
:text="`${props.currentPrice.split('.')[0]}`"
css="vertical-align: bottom; font-size: 58rpx"
/>
<l-painter-text
:text="`.${props.currentPrice.split('.')[1]}`"
css="vertical-align: bottom"
/>
<l-painter-text
:text="`¥${props.currentOriginPrice}`"
css="vertical-align: bottom; padding-left: 10rpx; font-weight: normal; text-decoration: line-through; color: #999999"
/>
</l-painter-view>
<l-painter-view css="margin-top: 30rpx">
<l-painter-text
:text="props.goods.name"
css="line-clamp: 2; color: #333333; line-height: 1.5em; width: 620rpx; font-size: 30rpx; padding-right:32rpx; box-sizing: border-box"
></l-painter-text>
</l-painter-view>
<l-painter-view css="margin-top: 30rpx; display: flex; flex-wrap: nowrap;">
<l-painter-text
text="长按或扫一扫识别二维码"
css="float: left; padding-bottom: 10rpx; color: #999999; font-size: 26rpx; margin-top: 60rpx; margin-right: 160rpx"
/>
<!-- #ifdef MP-WEIXIN -->
<l-painter-image
v-if="state.qrcodeUrl"
:src="state.qrcodeUrl"
css="float: right; width: 150rpx; height: 150rpx;"
/>
<!-- #endif -->
<!-- #ifdef WEB -->
<l-painter-qrcode
:text="getShareUrl(props.goods.id)"
css="float: right; width: 150rpx; height: 150rpx;"
/>
<!-- #endif -->
</l-painter-view>
</l-painter-view>
</l-painter>
</view>
<view class="action" v-if="state.posterImgPath">
<view class="button cancel" @tap="close()">取消分享</view>
<!-- #ifdef WEB -->
<view>长按图片进行保存</view>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
<view class="button" @tap="onSave()">保存图片</view>
<!-- #endif -->
</view>
</view>
</uni-popup>
</template>
<style lang="scss" scoped>
.action {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx;
background: #f5f5f5;
.button {
padding: 16rpx 32rpx;
margin: 0 10rpx;
background: #ff784d;
color: #ffffff;
border-radius: 40rpx;
&:active {
opacity: 0.8;
}
}
.cancel {
background: #b3b3b3;
color: #ffffff;
}
}
</style>

View File

@ -1,864 +0,0 @@
<script setup lang="ts">
import { getGoodsByIdApi } from '@/api/goods'
import { onLoad } from '@dcloudio/uni-app'
import { ref, watchEffect, reactive, computed } from 'vue'
import type { goodsResult, goodsServiceItem, skuItem } from '@/types/goods'
import type {
SkuPopupEvent,
SkuPopupInstance,
SkuPopupLocaldata,
} from '@/components/vk-data-goods-sku-popup/vk-data-goods-sku-popup'
import { addCartApi } from '@/api/cart'
import { useAddressStore } from '@/stores/modules/address'
import { previewImg, fullUrl, arrayFullUrl, onShowRefreshData } from '@/utils/common'
import { pageUrl, safeBottom, pageMateStyle, isIOSWithHomeIndicator } from '@/utils/constants'
import { useCartStore } from '@/stores'
import { postAddFavoriteApi, postCancelFavoriteApi } from '@/api/favorite'
import _, { debounce } from 'lodash'
import { onShareAppMessage } from '@dcloudio/uni-app'
import { useMemberStore } from '@/stores'
import { getAddressApi } from '@/api/address'
import type { addressItem } from '@/types/address'
import { computeConversion } from '@/utils/common'
import servicePanel from '@/pagesGoods/pages/goods/components/servicePanel.vue'
import addressPanel from '@/pagesGoods/pages/goods/components//addressPanel.vue'
import sharePanel from '@/pagesGoods/pages/goods/components/sharePanel.vue'
import sharePoster from '@/pagesGoods/pages/goods/components/sharePoster.vue'
import { usePopupStore } from '@/stores'
const props = defineProps<{
id: string | number
scene?: string
}>()
//
const query = reactive({
id: props.id,
scene: props.scene,
})
const cartStore = useCartStore()
//sku
const isShowSku = ref(false)
//sku
const skuData = ref({} as SkuPopupLocaldata)
//sku
enum skuskuModeType {
both = 1,
cart = 2,
buy = 3,
}
const skuMode = ref<skuskuModeType>(skuskuModeType.both)
//skuref
const skuPopupRef = ref<SkuPopupInstance>()
//sku
const selectAttrText = computed(() => {
return skuPopupRef.value?.selectArr?.join(' ').trim() || '请选择商品规格'
})
//
const selectedPrice = ref(0)
const currentPrice = computed(() => selectedPrice.value || goods.value?.min_price || 0)
// 线
const selectedOriginPrice = ref(0)
const currentOriginPrice = computed(
() => selectedOriginPrice.value || goods.value?.max_origin_price || 0,
)
//sku
const openSkuPopup = (mode: skuskuModeType) => {
isShowSku.value = true
skuMode.value = mode
}
const goodsServiceList = ref<goodsServiceItem[]>([])
const goodsServiceText = ref('')
//
const goods = ref<goodsResult>()
const getGoodsDataById = async () => {
const res = await getGoodsByIdApi(Number(query.id))
goods.value = res.result
//sku
skuData.value = {
_id: res.result.id.toString(),
name: res.result.name,
goods_thumb: fullUrl(res.result.images[0]),
spec_list: res.result.spec_list.map((spec) => {
return {
name: spec.name,
list: spec.value,
}
}),
sku_list: res.result.sku_list.map((sku) => {
return {
_id: sku.id.toString(),
goods_id: res.result.id.toString(),
goods_name: res.result.name,
image: fullUrl(sku.image),
price: sku.price,
origin_price: sku.origin_price,
sku_name_arr: sku.spec?.map((sku_name) => sku_name.value),
stock: sku.stock,
}
}),
}
//
goodsServiceList.value = goods.value.goods_service
goodsServiceText.value = goods.value.goods_service.map((item) => item.name).join(' ')
}
//
const currentIndex = ref(0)
const updateIndex: UniHelper.SwiperOnChange = (event) => {
currentIndex.value = event.detail.current
}
//ref
const popup = ref<{
open: (type?: UniHelper.UniPopupType) => void
close: () => void
}>()
//
const popupName = ref<'address' | 'service' | 'share' | 'sharePoster'>()
const openPopup = (name: typeof popupName.value) => {
popupName.value = name
popup.value?.open()
}
//
const onAddCart = (event: SkuPopupEvent) => {
addCartApi({
sku_id: event._id,
num: event.buy_num,
}).then((res) => {
cartStore.setCartTotalNum(res.result.total_num)
cartInfo.value = res.result.total_num
isShowSku.value = false
uni.showToast({
title: '加入购物车成功',
icon: 'success',
mask: true,
})
})
}
//
const onBuyNow = (event: SkuPopupEvent) => {
console.log(event)
isShowSku.value = false
uni.navigateTo({
url: `${pageUrl['order-create']}?sku_id=${event._id}&count=${event.buy_num}`,
})
}
//
const onSpecSelected = (skuData: skuItem) => {
selectedPrice.value = skuData.price
selectedOriginPrice.value = skuData.origin_price
}
//
const addressStore = useAddressStore()
const selectAddressText = computed(() => {
return addressStore.selectedAddress
? addressStore.selectedAddress.full_location
: '请选择收货地址'
})
const addressList = ref<addressItem[]>([])
const openAddressPopup = () => {
getAddressApi().then((res) => {
addressList.value = res.result.data
openPopup('address')
})
}
const getMoreEvaluate = () => {
uni.navigateTo({
url: `${pageUrl['goods-evaluate']}?goods_id=${query.id}`,
})
}
const cartInfo = ref(0)
const menuButtons = computed<UniHelper.UniGoodsNavOption[]>(() => [
{
icon: 'shop',
text: '首页',
},
{
icon: 'cart',
text: '购物车',
info: cartInfo.value,
},
{
icon: 'redo',
text: '分享',
},
])
const menuClick = (event: UniHelper.UniGoodsNavOnClickEvent) => {
switch (event.index) {
case 0:
uni.switchTab({
url: `${pageUrl['index']}`,
})
break
case 1:
uni.switchTab({
url: `${pageUrl['cart']}`,
})
break
case 2:
showSharePopup()
break
}
}
const operateButtons: UniHelper.UniGoodsNavButton[] = [
{
text: '加入购物车',
backgroundColor: '#ffa200',
color: '#fff',
},
{
text: '立即购买',
backgroundColor: '#ff0000',
color: '#fff',
},
]
const buttonClick = (event: UniHelper.UniGoodsNavOnButtonClickEvent) => {
switch (event.index) {
case 0:
openSkuPopup(skuskuModeType.cart)
break
case 1:
openSkuPopup(skuskuModeType.buy)
break
}
}
const changeFavoriteStatus = debounce((goods) => {
if (goods.favorite === true) {
postCancelFavoriteApi({
goods_id: goods.id,
}).then((res) => {
if (res.result) {
goods.favorite = false
uni.showToast({
title: '商品取消收藏',
icon: 'none',
})
}
})
} else {
postAddFavoriteApi({
goods_id: goods.id,
})
.then((res) => {
if (res.result) {
goods.favorite = true
uni.showToast({
title: '商品收藏成功',
icon: 'none',
})
}
})
.catch((err) => {
console.log(err)
})
}
}, 500)
watchEffect(() => {
cartInfo.value = useCartStore().cartTotalNum
})
const sharePopupRef = ref()
//
const showSharePopup = () => {
if (!useMemberStore().profile?.id) {
return uni.showModal({
title: '温馨提示',
content: '登录解锁更多精彩,是否继续?',
confirmText: '去登录',
cancelText: '再看看',
success: function (res) {
if (res.confirm) {
uni.navigateTo({ url: pageUrl['login'] })
}
},
})
}
openPopup('share')
}
const sharePosterRef = ref()
const goodsImage = ref('')
const onShowPoster = () => {
sharePosterRef.value.open()
goodsImage.value = goods.value!.images[currentIndex.value] || goods.value!.images[0]
}
onShareAppMessage(() => {
sharePopupRef.value.close()
const sharePath = `${pageUrl['goods-detail']}?id=${goods.value?.id}&sharer=${
useMemberStore().profile?.id
}`
console.log(sharePath)
return {
title: goods.value?.name,
path: sharePath,
imageUrl: fullUrl(goods.value!.images[0]),
}
})
onShowRefreshData(() => {
if (props.scene) {
const params = decodeURIComponent(props.scene)
.split('&')
.reduce((acc: { [key: string]: any }, curr) => {
const [key, value] = curr.split('=')
acc[key] = value
return acc
}, {})
console.log('解析scene', params)
if (params.id) {
query.id = params.id
}
}
getGoodsDataById()
})
onLoad(() => {
// #ifdef WEB
// IOS
if (isIOSWithHomeIndicator()) {
document.documentElement.classList.add('ios-device')
}
// #endif
// #ifdef MP-WEIXIN
uni.showShareMenu({
withShareTicket: true,
menus: ['shareAppMessage'],
})
// #endif
})
</script>
<template>
<!-- #ifdef MP-WEIXIN -->
<page-meta :page-style="pageMateStyle">
<!-- #endif -->
<scroll-view scroll-y class="viewport" :style="safeBottom" v-if="goods">
<!-- 基本信息 -->
<view class="goods">
<!-- 商品主图 -->
<view class="preview">
<swiper circular @change="updateIndex" autoplay>
<swiper-item v-for="item in goods?.images" :key="item">
<image
class="image"
:src="fullUrl(item)"
@tap="previewImg(item, arrayFullUrl(goods!.images))"
/>
</swiper-item>
</swiper>
<view class="indicator">
<text class="current">{{ currentIndex + 1 }}</text>
<text class="split">/</text>
<text class="total">{{ goods?.images.length }}</text>
</view>
</view>
</view>
<view class="panel">
<view class="meta">
<view class="meta-top">
<view class="price">
<view class="origin">¥{{ currentOriginPrice }} </view>
<view class="current">
<text class="symbol">¥</text>
<text class="number">{{ currentPrice }}</text>
</view>
</view>
<view class="operate">
<view class="operate-item" @tap="changeFavoriteStatus(goods!)" v-if="goods.favorite">
<button class="operate-item-btn">
<text class="icon icon-favorite-checked" />
</button>
<text class="icon-text">已收藏</text>
</view>
<view class="operate-item" @tap="changeFavoriteStatus(goods!)" v-else>
<button class="operate-item-btn">
<text class="icon icon-favorite-default" />
</button>
<text class="icon-text">收藏</text>
</view>
<!-- #ifdef MP-WEIXIN -->
<view class="operate-item">
<button open-type="contact" class="operate-item-btn">
<text class="icon-handset"></text>
</button>
<view class="icon-text">
<text>客服</text>
</view>
</view>
<!-- #endif -->
</view>
</view>
<view class="name-box">
<view class="name ellipsis">{{ goods?.name }} </view>
<view class="sales">
已售{{ computeConversion(goods.sales_init + goods.sales_real) }}</view
>
</view>
</view>
</view>
<view class="panel">
<view class="action">
<view class="item arrow" @tap="openSkuPopup(skuskuModeType.both)">
<text class="label">已选</text>
<text class="text ellipsis">{{ selectAttrText }}</text>
</view>
<view class="item arrow" @tap="openAddressPopup">
<text class="label">送至</text>
<text class="text ellipsis">{{ selectAddressText }}</text>
</view>
<view class="item arrow" @tap="openPopup('service')">
<text class="label">服务</text>
<text class="text ellipsis">{{ goodsServiceText }}</text>
</view>
</view>
<!-- 弹出层 -->
<uni-popup
ref="popup"
type="bottom"
background-color="#fff"
@change="usePopupStore().onChangePopupProps"
>
<address-panel
v-if="popupName === 'address'"
@close="popup?.close()"
:list="addressList"
/>
<service-panel
v-if="popupName === 'service'"
:list="goodsServiceList"
@close="popup?.close()"
/>
<share-panel
v-if="popupName === 'share'"
@close="popup?.close()"
@showPoster="onShowPoster"
:goods="goods"
/>
</uni-popup>
</view>
<!-- 商品评价 -->
<view class="panel">
<view v-if="goods?.evaluate_count > 0" @click="getMoreEvaluate">
<uni-section :title="`评价 (${computeConversion(goods?.evaluate_count)})`" type="line">
<template #right>
<view class="tip">
<text> 好评率 {{ goods?.positive_rate }}%</text>
<uni-icons type="forward" size="14" color="#909399" class="icon"></uni-icons>
</view>
</template>
<view class="eva-box" v-for="(evaluate, index) in goods?.evaluate" :key="index">
<view class="top">
<shop-user-avatar
:url="evaluate.user_avatar"
width="50"
style="padding-right: 10rpx"
/>
<text class="name">{{ evaluate.user_name }}</text>
<view class="rate">
<uni-rate :value="evaluate.score" :size="20" readonly />
</view>
</view>
<text class="content">{{ evaluate.content }}</text>
<scroll-view class="scroll-view-container" :scroll-x="true">
<image
class="content-img"
mode="aspectFill"
v-for="(item, index) in arrayFullUrl(evaluate.images)"
:key="index"
:src="item"
@click.stop="previewImg(item, evaluate.images)"
/>
</scroll-view>
<view class="bot"> </view>
</view>
</uni-section>
</view>
<view v-else>
<uni-section title="评价 (0)" type="line">
<z-paging-empty-view empty-view-text="期待您的评价" :empty-view-fixed="false" />
</uni-section>
</view>
</view>
<view class="panel">
<uni-section title="详情" type="line">
<view class="properties">
<view class="item" v-for="item in goods?.params" :key="item.key">
<text class="label">{{ item.key }}</text>
<text class="value">{{ item.value }}</text>
</view>
</view>
</uni-section>
<view v-if="goods.detail_images.length > 0">
<shop-image :src="goods?.detail_images" />
</view>
</view>
<view :style="{ paddingBottom: '5rpx' }"> </view>
<!-- sku弹窗 -->
<vk-data-goods-sku-popup
v-model="isShowSku"
:localdata="skuData"
:mode="skuMode"
buy-now-background-color="#ff0000"
add-cart-background-color="#ffa200"
:amountType="0"
ref="skuPopupRef"
:actived-style="{
color: '#ff0000',
backgroundColor: '#fee8e6',
borderColor: '#ff0000',
}"
@add-cart="onAddCart"
@buy-now="onBuyNow"
@spec-selected="onSpecSelected"
class="sku-popup"
/>
<!-- 海报弹窗 -->
<sharePoster
ref="sharePosterRef"
:goods="goods"
:currentPrice="currentPrice"
:currentOriginPrice="currentOriginPrice"
:goodsImage="goodsImage"
/>
<!-- 用户操作 -->
<view class="goods-nav" :style="safeBottom" v-show="goods">
<uni-goods-nav
:fill="true"
:options="menuButtons"
:buttonGroup="operateButtons"
@click="menuClick"
@buttonClick="buttonClick"
/>
</view>
</scroll-view>
<!-- #ifdef MP-WEIXIN -->
</page-meta>
<!-- #endif -->
</template>
<style lang="scss">
swiper {
height: 100%;
}
page {
height: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
}
.viewport {
background-color: #f4f4f4;
z-index: 999;
margin-bottom: 40px;
}
.panel {
background-color: #fff;
border-radius: 10rpx;
margin: 15rpx 15rpx 20rpx;
padding: 10rpx;
}
.arrow {
&::after {
position: absolute;
top: 50%;
right: 30rpx;
content: '\e6c2';
color: #ccc;
font-family: 'iconfont' !important;
font-size: 32rpx;
transform: translateY(-50%);
}
}
/* 商品信息 */
.goods {
background-color: #fff;
margin: 15rpx 15rpx 20rpx;
.preview {
height: 750rpx;
position: relative;
.image {
width: 750rpx;
height: 750rpx;
}
.indicator {
height: 40rpx;
padding: 0 24rpx;
line-height: 40rpx;
border-radius: 30rpx;
color: #fff;
font-family: Arial, Helvetica, sans-serif;
background-color: rgba(0, 0, 0, 0.3);
position: absolute;
bottom: 30rpx;
right: 30rpx;
.current {
font-size: 26rpx;
}
.split {
font-size: 24rpx;
margin: 0 1rpx 0 2rpx;
}
.total {
font-size: 24rpx;
}
}
}
}
.meta {
.meta-top {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.operate {
display: flex;
.operate-item {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.operate-item-btn {
width: 80rpx;
height: 80rpx;
background-color: transparent;
&::after {
border: none;
}
}
.icon {
text-align: center;
}
.icon-text {
font-size: 20rpx;
color: #9a9a9a;
}
.icon-favorite-checked {
color: red;
}
}
}
.price {
height: 130rpx;
padding: 0rpx 20rpx;
color: red;
font-size: 34rpx;
box-sizing: border-box;
background-color: #fff;
display: flex;
flex-direction: column;
.origin {
margin-top: 15rpx;
color: #888;
text-decoration: line-through;
::after {
content: ;
}
}
}
.number {
font-size: 50rpx;
}
.brand {
width: 160rpx;
height: 80rpx;
overflow: hidden;
position: absolute;
top: 26rpx;
right: 30rpx;
}
.name {
max-height: 88rpx;
line-height: 1.4;
margin: 20rpx;
font-size: 32rpx;
color: #333;
}
.desc {
line-height: 1;
padding: 0 20rpx 30rpx;
font-size: 24rpx;
color: #cf4444;
}
.sales {
margin: 20rpx;
font-size: 24rpx;
color: #888;
}
}
.action {
padding-left: 10rpx;
background: #fff;
.item {
height: 90rpx;
padding-right: 60rpx;
border-bottom: 1rpx solid #eaeaea;
font-size: 26rpx;
color: #333;
position: relative;
display: flex;
align-items: center;
&:last-child {
border-bottom: 0 none;
}
}
.label {
width: 60rpx;
color: #898b94;
margin: 0 16rpx 0 10rpx;
}
.text {
flex: 1;
-webkit-line-clamp: 1;
}
}
.tip {
font-size: 24rpx;
color: #9a9a9a;
.icon {
padding-left: 10rpx;
}
}
.eva-box {
padding: 10rpx;
.top {
display: flex;
align-items: center;
padding-bottom: 20rpx;
.name {
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-size: 26rpx;
padding-bottom: 10rpx;
}
}
.rate {
margin-right: 10rpx;
}
.content {
margin: 0rpx 10rpx 10rpx;
max-height: 100rpx;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
font-size: 24rpx;
}
.scroll-view-container {
width: 100%;
white-space: nowrap;
overflow-x: scroll;
-webkit-overflow-scrolling: touch;
.content-img {
margin-left: 10rpx;
height: 150rpx;
width: 150rpx;
margin-bottom: 10rpx;
margin-right: 10rpx;
border-radius: 10rpx;
margin-top: 10rpx;
scroll-snap-align: start;
}
}
}
.properties {
padding: 0 20rpx;
.item {
display: flex;
line-height: 2;
padding: 10rpx;
font-size: 26rpx;
color: #333;
border-bottom: 1rpx dashed #ccc;
}
.label {
width: 200rpx;
}
.value {
flex: 1;
}
}
/* 底部工具栏 */
.goods-nav {
position: fixed;
bottom: 0;
width: 100%;
background-color: #fff;
}
/* #ifdef WEB */
// ios-device
.ios-device {
.viewport {
padding-bottom: 34px !important;
}
.goods-nav {
padding-bottom: 34px !important;
}
}
/* #endif */
</style>

View File

@ -1,107 +0,0 @@
<script setup lang="ts">
import { reactive, ref } from 'vue'
import type { shopGoodsListInsatnce } from '@/types/component'
//
const props = withDefaults(
defineProps<{
classify_id?: string
name?: string
coupon_id?: string
empty_text?: string
}>(),
{
classify_id: '',
name: '',
coupon_id: '',
empty_text: '暂无商品数据',
},
)
const query = reactive({
classify_id: props.classify_id,
name: props.name,
coupon_id: props.coupon_id,
sort_field: '',
sort_by: '',
})
const shopGoodsListRef = ref<shopGoodsListInsatnce>()
const queryOptions = ref([
{ title: '综合排序', value: 'all', type: 'click' },
{ title: '销量优先', value: 'sales', type: 'click' },
{
title: '价格排序',
value: '',
type: 'sort',
options: [{ value: 'asc' }, { value: 'desc' }],
},
])
const onChange = (data: any, index: number) => {
console.log(data, index)
}
const onConfirm = (data: any) => {
const { value, type } = data
if (value === 'all') {
Object.assign(query, { sort_field: data.value })
} else if (value === 'sales') {
Object.assign(query, { sort_field: data.value, sort_by: 'desc' })
} else if (type === 'sort') {
Object.assign(query, { sort_field: 'price', sort_by: value })
}
shopGoodsListRef.value?.reload()
}
</script>
<template>
<shop-goods-list
:query="query"
:safe-area-inset-bottom="true"
ref="shopGoodsListRef"
:options="{
emptyViewText: empty_text,
backGroundColor: '#fff',
}"
>
<template #top>
<view v-if="query">
<le-dropdown
v-model:menuList="queryOptions"
themeColor="#3185FF"
:duration="300"
:isCeiling="false"
@onConfirm="onConfirm"
@onChange="onChange"
></le-dropdown>
</view>
</template>
</shop-goods-list>
</template>
<style lang="scss" scoped>
page {
background-color: #ececec;
}
.toolbar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 1;
background-color: #fff;
height: 100rpx;
padding: 0 20rpx var(--window-bottom);
border-top: 1rpx solid #eaeaea;
display: flex;
justify-content: space-between;
align-items: center;
box-sizing: content-box;
}
//
.toolbar-height {
height: 190rpx;
}
</style>

View File

@ -1,147 +0,0 @@
<script setup lang="ts">
import { pageUrl } from '@/utils/constants'
import { ref } from 'vue'
let name = ref('')
const history = ref<string[]>(uni.getStorageSync('search') || [])
const storeHistroy = () => {
name.value = name.value.trim()
if (name.value == '') return
//
const index = history.value.findIndex((item) => item === name.value)
if (index !== -1) {
history.value.splice(index, 1)
}
history.value.unshift(name.value)
uni.setStorageSync('search', history.value)
}
const search = async () => {
await uni.navigateTo({
url: `${pageUrl['goods-list']}?name=${name.value}`,
})
storeHistroy()
}
const onTapHistory = async (keyword: string) => {
await uni.navigateTo({
url: `${pageUrl['goods-list']}?name=${keyword}`,
})
name.value = keyword
storeHistroy()
}
const onCancel = () => {
uni.navigateBack()
}
const onClearHistory = () => {
uni.showModal({
title: '',
content: '确定要清空历史搜索吗',
showCancel: true,
success: ({ confirm, cancel }) => {
if (confirm) {
uni.removeStorageSync('search')
history.value = []
}
},
})
}
const onDeleteHistory = (name: string) => {
uni.showModal({
title: '',
content: '确定要删除该条历史搜索吗',
showCancel: true,
success: ({ confirm, cancel }) => {
if (confirm) {
const index = history.value.findIndex((item) => item === name)
if (index !== -1) {
history.value.splice(index, 1)
uni.setStorageSync('search', history.value)
}
}
},
})
}
</script>
<template>
<view class="search">
<uni-search-bar
@confirm="search"
:focus="true"
v-model="name"
radius="100"
@cancel="onCancel"
/>
</view>
<view class="search-history">
<text>历史搜索</text>
<view class="trash">
<uni-icons type="trash" color="" size="24" @tap="onClearHistory" />
</view>
</view>
<view class="keyword">
<button
v-for="(item, index) in history"
:key="index"
@tap="onTapHistory(item)"
class="keyword-button"
aria-readonly
@longtap="onDeleteHistory(item)"
>
{{ item }}
</button>
</view>
</template>
<style lang="scss">
page {
padding-left: 20rpx;
}
.search {
padding-right: 20rpx;
}
.search-history {
display: flex;
justify-content: space-between;
margin-top: 10px;
font-size: 14px;
.trash {
display: flex;
align-items: center;
margin-right: 50rpx;
}
}
.keyword {
display: flex;
flex-wrap: wrap;
margin-top: 20px;
float: left;
button {
background-color: #f2f2f2;
border-radius: 25rpx;
color: #333333;
font-size: 24rpx;
align-items: center;
margin: 0 10rpx 15rpx 15rpx;
justify-content: flex-start;
// padding-left: 10px;
display: flex;
.delete-icon {
margin-left: 5px;
margin-right: -10rpx;
}
}
}
</style>

View File

@ -1,40 +0,0 @@
<script setup lang="ts">
import { pageUrl } from '@/utils/constants'
const pageList = [
{
title: '商城订单',
icon: 'icon-shop-order',
to: `${pageUrl['order-list']}`,
},
{
title: '充值订单',
icon: 'icon-recharge-order',
to: `${pageUrl['recharge-order-list']}`,
},
]
</script>
<template>
<uni-list class="list">
<uni-list-item
class="item"
v-for="(item, index) in pageList"
:key="index"
showArrow
:title="item.title"
:to="item.to"
>
<template #header>
<text class="icon" :class="item.icon"></text>
</template>
</uni-list-item>
</uni-list>
</template>
<style lang="scss" scoped>
.item {
line-height: 2em;
}
.icon {
margin-right: 20rpx;
}
</style>

View File

@ -1,612 +0,0 @@
<script setup lang="ts">
import { fullUrl } from '@/utils/common'
import { orderType, pageUrl, safeBottom, pageMateStyle } from '@/utils/constants'
import {
getCartOrderPreApi,
getOrderPreApi,
getRepurchaseOrderPreByOrderIdApi,
postOrderApi,
} from '@/api/order'
import { useAddressStore } from '@/stores/modules/address'
import type { orderPreResult } from '@/types/order'
import { onShow } from '@dcloudio/uni-app'
import { computed, ref } from 'vue'
import shopCoupon from '@/components/shop-coupon/shop-coupon.vue'
import type { couponListItem } from '@/types/coupon'
import { debounce } from 'lodash'
import { usePopupStore } from '@/stores/modules/popup'
import type { InputNumberBoxEvent } from '@/components/vk-data-input-number-box/vk-data-input-number-box'
//store
const addressStore = useAddressStore()
addressStore.changeScene('order')
//
const buyerMessage = ref('')
const disabledSubmit = ref(false)
if (!addressStore.selectedAddress?.id) {
disabledSubmit.value = true
}
const selectCouponId = ref<string | number>('')
const couponText = ref('')
//
const query = defineProps<{
sku_id?: string
count?: string
orderId?: string
}>()
//
const orderPreData = ref<orderPreResult>()
const orderPreParams = ref<{
id: string
sku_id: string
num: string | number
address_id: string | number
coupon_id: string | number
}>({
id: query.orderId ?? '0',
sku_id: query.sku_id ?? '0',
num: query.count ?? 0,
address_id: addressStore.selectedAddress?.id ?? '0',
coupon_id: selectCouponId.value ?? '0',
})
const getOrderPreData = async () => {
orderPreParams.value = {
...orderPreParams.value,
coupon_id: selectCouponId.value ?? '0',
}
let res: any = []
try {
if (query.sku_id && query.count) {
//
res = await getOrderPreApi({
sku_id: orderPreParams.value.sku_id,
num: Number(orderPreParams.value.num),
address_id: orderPreParams.value.address_id,
coupon_id: orderPreParams.value.coupon_id,
})
} else if (query.orderId) {
//
res = await getRepurchaseOrderPreByOrderIdApi({
id: orderPreParams.value.id,
address_id: orderPreParams.value.address_id,
coupon_id: orderPreParams.value.coupon_id,
})
} else {
//
res = await getCartOrderPreApi({
address_id: orderPreParams.value.address_id,
coupon_id: orderPreParams.value.coupon_id,
})
}
disabledSubmit.value = false
} catch (error: any) {
uni.showModal({
title: '提示',
content: error.data.msg,
})
}
orderPreData.value = res.result
if (res.result.error) {
uni.showModal({
title: '提示',
content: res.result.error,
})
disabledSubmit.value = true
}
}
//
const addressData = computed(() => {
return addressStore.selectedAddress
})
//
const onOrderSubmit = async () => {
await checkOrder()
}
//
const checkOrder = () => {
if (!addressStore.selectedAddress?.id) {
return uni.showToast({
title: '请选择收货地址',
icon: 'none',
mask: true,
})
}
debouncedSubmitOrder()
}
//
const debouncedSubmitOrder = debounce(async () => {
const res = await postOrderApi({
address_id: addressStore.selectedAddress!.id,
remark: buyerMessage.value,
goods: orderPreData.value!.goods.map((item) => ({
sku_id: item.id,
num: item.num,
})),
type: orderType.default,
coupon_id: selectCouponId.value,
})
if (!res.result.id) {
return uni.showToast({
icon: 'error',
title: '订单提交失败',
})
}
//
uni.redirectTo({ url: `${pageUrl['order-detail']}?id=${res.result.id}` })
}, 1000)
const couponPopup = ref<{
open: (type?: UniHelper.UniPopupType) => void
close: () => void
}>()
const openCouponPopup = () => {
couponPopup.value?.open()
}
const closeCouponPopup = () => {
couponPopup.value?.close()
}
//
const selectCoupon = (item: couponListItem) => {
selectCouponId.value = item.id
getOrderPreData().then(() => {
couponText.value = orderPreData.value!.summary.coupon_discount
couponPopup.value?.close()
})
}
//
const clearSelectCoupon = () => {
selectCouponId.value = ''
getOrderPreData().then(() => {
couponPopup.value?.close()
})
}
const onChangeNum = debounce((event: InputNumberBoxEvent) => {
orderPreParams.value.num = event.value
selectCouponId.value = ''
getOrderPreData()
}, 500)
onShow(() => {
getOrderPreData().then(() => {
if (!orderPreData.value?.user_address) {
addressStore.clearSelectedAddress()
}
})
})
</script>
<template>
<!-- #ifdef MP-WEIXIN -->
<page-meta :page-style="pageMateStyle">
<!-- #endif -->
<view v-if="orderPreData">
<scroll-view scroll-y class="viewport">
<!-- 收货地址 -->
<navigator
v-if="addressData"
class="shipment"
hover-class="none"
:url="`${pageUrl['address']}?from=order`"
>
<view class="user"> {{ addressData.name }} {{ addressData.phone }} </view>
<view class="address"> {{ addressData.full_location }} {{ addressData.address }} </view>
<text class="icon icon-right"></text>
</navigator>
<navigator
v-else
class="shipment"
hover-class="none"
:url="`${pageUrl['address']}?from=order`"
>
<view class="address"> 请选择收货地址 </view>
<text class="icon icon-right"></text>
</navigator>
<!-- 商品信息 -->
<view class="goods" v-if="orderPreData">
<view v-for="item in orderPreData?.goods" :key="item.sku_id" class="item">
<navigator :url="`${pageUrl['goods-detail']}?id=${item.goods_id}`" hover-class="none">
<image class="picture" :src="fullUrl(item.image)" />
</navigator>
<view class="meta">
<view class="name ellipsis"> {{ item.goods.name }} </view>
<view class="attrs">{{ item.spec_text }}</view>
<view class="prices">
<view class="pay-price symbol">{{ item.price }}</view>
<view class="price symbol">{{ item.origin_price }}</view>
</view>
<view class="count">
<vk-data-input-number-box
v-if="query.sku_id && query.count"
:style="{ position: 'absolute', right: '0', bottom: '-10rpx' }"
v-model="item.num"
:index="item.id"
:min="1"
@plus="onChangeNum($event)"
@minus="onChangeNum($event)"
@blur="onChangeNum($event)"
/>
<view else="orderPreData?.goods.length > 1">x {{ item.num }}</view>
</view>
</view>
</view>
</view>
<view class="related">
<view class="item">
<text class="text">订单备注</text>
<input
class="input"
:cursor-spacing="30"
placeholder="建议留言前先与商家沟通确认"
v-model="buyerMessage"
/>
</view>
</view>
<!-- 支付金额 -->
<view class="settlement">
<view class="item">
<text class="text">商品总价: </text>
<text class="number symbol">{{ orderPreData?.summary.total_price }}</text>
</view>
<view class="item">
<text class="text">优惠券: </text>
<text class="coupon" @tap="openCouponPopup" v-if="orderPreData.coupon_list.length > 0">
<text v-if="selectCouponId" class="danger">-{{ couponText }}</text>
<text v-else>
<text class="danger">{{ orderPreData.coupon_list.length }}</text> 张优惠券可用
</text>
</text>
<text v-else>无优惠券可用</text>
</view>
<view class="item">
<text class="text">运费: </text>
<text class="number symbol">{{ orderPreData?.summary.freight }}</text>
</view>
</view>
</scroll-view>
<!-- 优惠券弹出层 -->
<uni-popup
ref="couponPopup"
type="share"
background-color="#fff"
class="coupon-popup"
@change="usePopupStore().onChangePopupProps"
>
<view class="coupon-panel">
<text class="close icon-close" @tap="closeCouponPopup"></text>
<view class="title">选择优惠券</view>
<view class="content">
<shop-coupon
:couponList="orderPreData.coupon_list"
mode="user"
buttonTitle="选择使用"
@buttonClicked="selectCoupon"
class="user-coupon-popup"
/>
</view>
</view>
<view class="coupon-panel-footer">
<view class="button primary" @tap="clearSelectCoupon"> 不使用优惠券</view>
</view>
</uni-popup>
<!-- 底部占位空盒子 -->
<view class="toolbar-height"></view>
<!-- 吸底工具栏 -->
<view class="toolbar" :style="safeBottom">
<view class="total-pay symbol">
<text class="number">{{ orderPreData?.summary.pay_price }}</text>
</view>
<view class="button" :class="{ disabled: disabledSubmit }" @tap="onOrderSubmit">
<button class="button" :disabled="disabledSubmit">提交订单</button>
</view>
</view>
</view>
<!-- #ifdef MP-WEIXIN -->
</page-meta>
<!-- #endif -->
</template>
<style lang="scss">
page {
display: flex;
flex-direction: column;
background-color: #f4f4f4;
}
.symbol::before {
content: '¥';
font-size: 80%;
margin-right: 5rpx;
}
.shipment {
margin: 20rpx;
padding: 30rpx 30rpx 30rpx 84rpx;
font-size: 26rpx;
border-radius: 10rpx;
background: url('~@/static/images/locate.png') 20rpx center / 50rpx no-repeat #fff;
position: relative;
.icon {
font-size: 36rpx;
color: #333;
transform: translateY(-50%);
position: absolute;
top: 50%;
right: 20rpx;
}
.user {
color: #333;
margin-bottom: 5rpx;
}
.address {
color: #666;
width: 94%;
}
}
.goods {
margin: 20rpx;
padding: 0 20rpx;
border-radius: 10rpx;
background-color: #fff;
.item {
display: flex;
padding: 30rpx 0;
border-top: 1rpx solid #eee;
&:first-child {
border-top: none;
}
.picture {
width: 170rpx;
height: 170rpx;
border-radius: 10rpx;
margin-right: 20rpx;
}
.meta {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-around;
position: relative;
}
.name {
font-size: 26rpx;
color: #444;
}
.attrs {
line-height: 1.8;
padding: 0 15rpx;
margin-top: 6rpx;
margin-bottom: 10rpx;
font-size: 24rpx;
align-self: flex-start;
border-radius: 4rpx;
color: #888;
background-color: #f7f7f8;
max-width: 90%;
}
.prices {
display: flex;
align-items: baseline;
margin-top: 6rpx;
font-size: 28rpx;
width: 50%;
flex-flow: wrap;
.pay-price {
margin-right: 10rpx;
color: #cf4444;
}
.price {
font-size: 24rpx;
color: #999;
text-decoration: line-through;
}
}
.count {
position: absolute;
right: 0;
bottom: 0;
}
}
}
.related {
margin: 20rpx;
padding: 0 20rpx;
border-radius: 10rpx;
background-color: #fff;
.item {
display: flex;
justify-content: space-between;
align-items: center;
min-height: 80rpx;
font-size: 26rpx;
color: #333;
}
.input {
flex: 1;
text-align: right;
margin: 20rpx 0;
padding-right: 20rpx;
font-size: 26rpx;
color: #999;
}
.item .text {
width: 125rpx;
}
.picker {
color: #666;
}
.picker::after {
content: '\e6c2';
}
}
/* 结算清单 */
.settlement {
margin: 20rpx;
padding: 0 20rpx;
border-radius: 10rpx;
background-color: #fff;
// padding-bottom: 200rpx;
.item {
display: flex;
align-items: center;
justify-content: space-between;
height: 80rpx;
font-size: 26rpx;
color: #333;
}
.coupon {
&::after {
font-family: 'iconfont' !important;
content: '\e6c2';
}
}
.danger {
color: #cf4444;
}
}
.coupon-panel {
.title {
line-height: 1;
padding: 40rpx 0;
text-align: center;
font-size: 32rpx;
font-weight: normal;
border-bottom: 1rpx solid #ddd;
color: #444;
}
.close {
position: absolute;
right: 24rpx;
top: 24rpx;
font-size: 40rpx;
}
.content {
min-height: 400rpx;
max-height: 650rpx;
overflow: auto;
-webkit-overflow-scrolling: touch;
}
}
.coupon-panel-footer {
display: flex;
justify-content: space-between;
padding: 20rpx 0 40rpx;
font-size: 28rpx;
color: #444;
.button {
flex: 1;
height: 72rpx;
text-align: center;
line-height: 72rpx;
margin: 0 20rpx;
color: #fff;
border-radius: 72rpx;
}
.primary {
color: #fff;
background-color: #ff5f3c;
}
.secondary {
background-color: #ffa868;
}
}
/* 吸底工具栏 */
.toolbar {
position: fixed;
left: 0;
right: 0;
bottom: calc(var(--window-bottom));
z-index: 1;
background-color: #fff;
height: 100rpx;
padding: 0 20rpx;
border-top: 1rpx solid #eaeaea;
display: flex;
justify-content: space-between;
align-items: center;
box-sizing: content-box;
.total-pay {
font-size: 40rpx;
color: #cf4444;
.decimal {
font-size: 75%;
}
}
.button {
width: 220rpx;
text-align: center;
line-height: 72rpx;
font-size: 26rpx;
color: #fff;
border-radius: 72rpx;
background-color: #ff5f3c;
}
.disabled {
opacity: 0.6;
}
}
//
.toolbar-height {
height: 190rpx;
}
</style>

View File

@ -1,261 +0,0 @@
<template>
<view v-if="deliveryList.length > 0">
<z-tabs
:list="deliveryList"
@change="onChangeTabs"
class="tabs"
v-if="deliveryInfo.length > 1"
:scroll-count="1"
/>
<view class="body">
<!-- 商品详情 -->
<view class="delivery">
<view class="delivery-goods-list">
<view class="goods-item" v-for="(goods, idx) in deliveryInfo[tabIndex].detail" :key="idx">
<image class="goods-img" :src="fullUrl(goods.order_detail.image)" alt="商品图片" />
<view class="title">{{ goods.delivery_num }}</view>
</view>
</view>
<!-- 物流信息 -->
<view class="delivery-info">
<view v-if="deliveryInfo[tabIndex].delivery_company">
<view class="info-item">
<view class="item-label">物流公司: </view>
<view class="item-content">{{ deliveryInfo[tabIndex].delivery_company.name }}</view>
</view>
<view class="info-item">
<view class="item-label">物流单号: </view>
<view class="item-content">{{ deliveryInfo[tabIndex].delivery_no }}</view>
<view class="item-content">
<button class="copy-button" @tap="onCopy(deliveryInfo[tabIndex].delivery_no)">
复制
</button>
</view>
</view>
<view class="info-item">
<view class="item-label address">收货地址: </view>
<view class="item-content">
{{ deliveryInfo[tabIndex].order_address.full_address }}
</view>
</view>
</view>
<view v-else>
<view class="info-item">
<view>无需物流</view>
</view>
</view>
</view>
</view>
</view>
<!-- 物流轨迹 -->
<view class="delivery-tracks" v-if="deliveryList[tabIndex].value.length > 0">
<view
v-for="(item, index) in deliveryList[tabIndex].value"
:key="index"
class="track-item"
:class="{ start: index === 0, end: index === deliveryList[tabIndex].value.length - 1 }"
>
<view class="track-item-content">
<view class="item-content">
<text>{{ item.context }}</text>
</view>
<view class="item-time">
<text>{{ item.time }}</text>
</view>
</view>
</view>
</view>
<view v-else>
<z-paging-empty-view :empty-view-fixed="false" empty-view-text="暂无物流轨迹信息" />
</view>
</view>
<view :style="safeBottom" class="bottom"></view>
</template>
<script setup lang="ts">
import { safeBottom } from '@/utils/constants'
import { getOrderDeliveryByIdApi } from '@/api/order'
import type { orderDeliveryResult } from '@/types/order'
import { ref } from 'vue'
import { fullUrl, onShowRefreshData } from '@/utils/common'
const props = defineProps<{
order_id: string
}>()
let deliveryInfo: orderDeliveryResult[] = []
const deliveryList = ref<any[]>([])
const tabIndex = ref(0)
const onChangeTabs = (index: number) => {
tabIndex.value = index
}
const onCopy = (deliveryNo: string) => {
uni.setClipboardData({ data: deliveryNo })
}
onShowRefreshData(async () => {
const res = await getOrderDeliveryByIdApi(props.order_id)
deliveryInfo = res.result
for (let i = 1; i <= deliveryInfo.length; i++) {
deliveryList.value.push({
name: '包裹' + i,
value: deliveryInfo[i - 1].delivery_tracks,
})
}
})
</script>
<style lang="scss">
page {
background-color: #f6f6f6;
}
.body {
padding-top: 20rpx;
padding-bottom: 20rpx;
}
.delivery {
padding: 10rpx;
background-color: #fff;
.delivery-goods-list {
display: flex;
flex-wrap: wrap;
margin-top: 20rpx;
margin-bottom: -30rpx;
.goods-item {
position: relative;
border-radius: 8rpx;
overflow: hidden;
width: 130rpx;
height: 130rpx;
margin-bottom: 30rpx;
margin-left: 18rpx;
}
.goods-img {
display: block;
width: 100%;
height: 100%;
}
.title {
position: absolute;
bottom: 0;
width: 100%;
text-align: center;
background: rgba(0, 0, 0, 0.6);
color: #fff;
padding: 4rpx 0;
font-size: 24rpx;
}
}
.delivery-info {
display: flex;
flex-direction: column;
line-height: 1.6;
padding: 20rpx;
}
.info-item {
display: flex;
font-size: 24rpx;
.copy-button {
display: flex;
height: 34rpx;
font-size: 24rpx;
align-items: center;
margin-left: 20rpx;
border-radius: 50rpx;
border: 1rpx solid #c1c1c1;
}
}
.item-label {
color: #9d9d9d;
margin-right: 20rpx;
white-space: nowrap;
}
.item-content {
display: flex;
justify-content: center;
flex-direction: column;
font-size: 26rpx;
}
}
.track {
font-size: 40rrpx;
}
.delivery-tracks {
padding: 30rpx;
background-color: #fff;
border-radius: 25rpx;
.track-item {
position: relative;
padding-left: 20rpx;
padding-bottom: 25rpx;
border-left: 4rpx solid #ccc;
&.start {
border-left: 5rpx solid red;
&:after {
background-color: red;
top: 0;
}
}
&.end {
padding-bottom: 0rpx;
border-left: none;
&::after {
left: -13rpx;
}
}
&:after {
content: ' ';
display: inline-block;
position: absolute;
left: -9px;
top: 0rpx;
width: 24rpx;
height: 24rpx;
border-radius: 40rpx;
background: #bdbdbd;
border: 2rpx solid #fff;
}
.track-item-content {
position: relative;
padding: 20rpx 20rpx;
background-color: #f4f4f4;
border-radius: 20rpx;
.item-content {
font-size: 27rpx;
padding-bottom: 12rpx;
}
.item-time {
font-size: 24rpx;
color: #999;
}
}
}
}
.bottom {
height: 50rpx;
}
</style>

View File

@ -1,788 +0,0 @@
<script setup lang="ts">
import { orderState, orderStateActions, orderStateList, pageUrl } from '@/utils/constants'
import { deleteOrderApi, getOrderDetailByIdApi, putOrderCancelByIdApi } from '@/api/order'
import type { orderResult } from '@/types/order'
import { ref, computed } from 'vue'
import { fullUrl, getPayPageUrl, formatPrice, onShowRefreshData } from '@/utils/common'
import receiveButton from '@/pagesOrder/components/receiveButton.vue'
import cancelButton from '@/pagesOrder/components/cancelButton.vue'
import { safeBottom } from '@/utils/constants'
//
const detailData = ref<orderResult>()
const getDetailData = async () => {
const res = await getOrderDetailByIdApi(orderId)
detailData.value = res.result
}
//
const onCopy = () => {
//
uni.setClipboardData({ data: detailData.value!.order_no })
}
//
const query = defineProps<{
id: string
}>()
//ID
const orderId = Number(query.id)
//
const onTimeUp = () => {
if (detailData.value?.status != orderState.canceled) {
putOrderCancelByIdApi({
id: detailData.value!.id,
time_out: true,
})
}
detailData.value!.status = orderState.canceled
}
//
const onDeleteOrder = () => {
uni.showModal({
title: '提示',
content: '确定要删除订单吗',
showCancel: true,
success: async ({ confirm, cancel }) => {
if (confirm) {
await deleteOrderApi({
id: [orderId],
})
uni.showToast({
title: '删除成功',
icon: 'success',
mask: true,
})
setTimeout(() => {
uni.redirectTo({ url: `${pageUrl['order-list']}` })
}, 500)
}
},
})
}
onShowRefreshData(() => {
getDetailData()
}, true)
const showDelivery = computed(() => {
return (
[orderState.toReceived, orderState.toEvaluate, orderState.completed].includes(
detailData.value!.status,
) && detailData.value!.delivery.length > 0
)
})
const onCancel = () => {
if (detailData.value?.pay_time) {
detailData.value!.status = orderState.applyCancel
} else {
detailData.value!.status = orderState.canceled
}
}
</script>
<template>
<scroll-view scroll-y class="viewport" id="scroller">
<template v-if="detailData">
<!-- 订单状态 -->
<view class="overview" :style="{ paddingTop: '30rpx' }">
<!-- 待付款状态:展示去支付按钮和倒计时 -->
<template v-if="detailData.status === orderState.toPay">
<view class="status icon-clock">{{ orderStateList[detailData.status].text }}</view>
<view class="tips">
<view class="time">
<view>还剩</view>
<uni-countdown
:second="detailData.countdown"
@timeup.once="onTimeUp"
color="#fff"
splitor-color="#fff"
:show-day="false"
:show-colon="false"
/>
<view>自动取消</view>
</view>
</view>
<navigator
class="button primary"
:url="getPayPageUrl(orderId, detailData.pay_price)"
hover-class="none"
>
去支付
</navigator>
</template>
<!-- 其他订单状态:展示再次购买按钮 -->
<template v-else>
<!-- 订单状态文字 -->
<view class="status"> {{ orderStateList[detailData.status].text }} </view>
</template>
</view>
<view class="shipment" v-show="detailData.address">
<view class="locate">
<view class="user"> {{ detailData.address.name }} {{ detailData.address.phone }} </view>
<view class="address">{{ detailData.address.full_address }} </view>
</view>
<!-- 配送状态 -->
<view class="item" v-if="showDelivery">
<view class="delivery-card">
<template v-if="detailData.delivery.length > 1">
<navigator
class="delivery"
:url="`${pageUrl['order-delivery']}?order_id=${orderId}`"
hover-class="none"
>
<view v-if="detailData.status == orderState.toReceived" class="delivery-tip">
<text>订单拆分为 {{ detailData.delivery.length }} 个包裹发货</text>
</view>
<view v-else class="delivery-tip">
<text>订单拆分为多个包裹发货</text>
<text>已发货 {{ detailData.delivery.length }} 个包裹</text>
</view>
<view class="more">
<text class="icon-right"></text>
</view>
</navigator>
</template>
<template v-else>
<navigator
class="delivery"
:url="`${pageUrl['order-delivery']}?order_id=${orderId}`"
hover-class="none"
v-for="(item, idx) in detailData.delivery"
:key="idx"
>
<view class="delivery-info">
<view class="info-item">
<view class="item-label">物流公司</view>
<view class="item-content">
{{ item.delivery_company?.name || '无需物流' }}
</view>
</view>
<view class="info-item">
<view class="item-label">物流单号</view>
<view class="item-content">{{ item.delivery_no || '' }}</view>
</view>
</view>
<view class="more">
<text class="icon-right"></text>
</view>
</navigator>
</template>
</view>
</view>
</view>
<!-- 商品信息 -->
<view class="goods" v-if="detailData.detail.length > 0">
<view class="item" v-for="detail in detailData.detail" :key="detail.id">
<navigator
class="navigator"
:url="`${pageUrl['goods-detail']}?id=${detail.goods_id}`"
hover-class="none"
>
<view> <image class="cover" :src="fullUrl(detail.image)"></image> </view>
<view class="meta">
<view class="name ellipsis">{{ detail.goods_name }}</view>
<view class="type">{{ detail.spec_text }}</view>
<view class="price">
<view class="actual">
<text class="symbol">¥</text>
<text>{{ detail.price }}</text>
</view>
</view>
<view class="quantity">x{{ detail.num }}</view>
</view>
</navigator>
<view class="operate">
<view
v-if="
orderStateActions.evaluatable.includes(detailData.status) &&
detail.evaluate_time <= 0
"
>
<navigator
:url="`${pageUrl['order-evaluate']}?detail_id=${detail.id}`"
open-type="navigate"
hover-class="navigator-hover"
class="button"
>
去评价
</navigator>
</view>
<view v-if="detail.apply_refund">
<navigator
:url="`${pageUrl['order-refund']}?order_id=${
detailData.id
}&detail_data=${JSON.stringify(detail)}`"
open-type="navigate"
hover-class="navigator-hover"
class="button"
>
申请售后
</navigator>
</view>
</view>
</view>
</view>
<view class="goods">
<view class="total">
<view class="row">
<view class="text">商品总价</view>
<view class="symbol">{{ formatPrice(detailData.total_price) }}</view>
</view>
<view class="row">
<view class="text">商品运费</view>
<view class="symbol">{{ formatPrice(detailData.freight) }}</view>
</view>
<view class="row">
<view class="text">优惠折扣</view>
<view class="symbol discount">
{{ formatPrice(detailData.discount_data.coupon_discount) }}
</view>
</view>
<view class="row">
<view class="text">应付金额</view>
<view class="symbol">{{ formatPrice(detailData.pay_price) }}</view>
</view>
</view>
</view>
<!-- 订单信息 -->
<view class="detail">
<view class="row">
<view class="item">
<view class="item-title"> 订单编号</view>
<view>
{{ detailData.order_no }}
<text class="copy" @tap="onCopy">复制</text>
</view>
</view>
<view class="item">
<view class="item-title">下单时间</view>
<view>{{ detailData.create_time }}</view>
</view>
<view class="item" v-if="detailData.pay_time">
<view class="item-title">支付时间</view>
<view> {{ detailData.pay_time }}</view>
</view>
<view class="item">
<view class="item-title">买家留言</view>
<view style="width: 80%">{{ detailData.remark || '--' }}</view>
</view>
</view>
</view>
<!-- 底部操作栏 -->
<view class="toolbar-height" :style="safeBottom"></view>
<view class="toolbar" :style="safeBottom">
<!-- 待付款状态:展示支付按钮 -->
<template v-if="detailData.status === orderState.toPay">
<cancelButton
:status="detailData.status"
:id="detailData.id"
className="button secondary"
@cancel="onCancel"
/>
<navigator
class="button primary"
:url="getPayPageUrl(detailData.id, detailData.pay_price)"
hover-class="none"
>
去支付
</navigator>
</template>
<!-- 其他订单状态:按需展示按钮 -->
<template v-else>
<view v-if="orderStateActions.deletable.includes(detailData.status)">
<view class="button secondary" @tap="onDeleteOrder"> 删除订单 </view>
</view>
<cancelButton
:status="detailData.status"
:id="detailData.id"
className="button secondary"
@cancel="onCancel"
/>
<receiveButton
:status="detailData.status"
:id="detailData.id"
:payment-trade-info="detailData.payment_trade"
className="button secondary"
/>
<view v-if="orderStateActions.evaluatable.includes(detailData.status)">
<navigator
:url="`${pageUrl['order-evaluate']}?order_id=${orderId}`"
class="button secondary"
hover-class="none"
>
一键评价
</navigator>
</view>
<navigator
class="button primary"
:url="`${pageUrl['order-create']}?orderId=${orderId}`"
hover-class="none"
>
再次购买
</navigator>
</template>
</view>
</template>
</scroll-view>
</template>
<style lang="scss" scoped>
page {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
}
.card {
display: flex;
flex-direction: column;
line-height: 1.6;
padding: 20rpx;
margin: 20rpx;
border-radius: 30rpx;
background-color: #fff;
}
.navbar {
width: 750rpx;
color: #000;
position: fixed;
top: 0;
left: 0;
z-index: 9;
/* background-color: #f8f8f8; */
background-color: transparent;
.wrap {
position: relative;
.title {
height: 44px;
display: flex;
justify-content: center;
align-items: center;
font-size: 32rpx;
/* color: #000; */
color: transparent;
}
.back {
position: absolute;
left: 0;
height: 44px;
width: 44px;
font-size: 44rpx;
display: flex;
align-items: center;
justify-content: center;
/* color: #000; */
color: #fff;
}
}
}
.viewport {
background-color: #f7f7f8;
}
.overview {
display: flex;
flex-direction: column;
align-items: center;
padding-bottom: 35rpx;
color: #fff;
background-image: url('~@/static/images/bg.png');
background-size: cover;
.status {
font-size: 36rpx;
}
.status::before {
margin-right: 6rpx;
font-weight: 500;
}
.tips {
margin: 30rpx 0;
display: flex;
flex-direction: column;
font-size: 14px;
align-items: center;
.money {
margin-right: 30rpx;
}
.time {
display: flex;
align-items: center;
justify-content: center;
gap: 10rpx;
padding-top: 10rpx;
}
}
.button-group {
margin-top: 30rpx;
display: flex;
justify-content: center;
align-items: center;
}
.button {
width: 260rpx;
height: 64rpx;
margin: 0 10rpx;
text-align: center;
line-height: 64rpx;
font-size: 28rpx;
color: #ff5f3c;
border-radius: 68rpx;
background-color: #fff;
}
}
.shipment {
line-height: 1.4;
padding: 0 20rpx;
margin: 20rpx 20rpx 20rpx;
border-radius: 30rpx;
background-color: #fff;
.locate,
.item {
// min-height: 120rpx;
padding: 30rpx 30rpx 25rpx 75rpx;
background-size: 50rpx;
background-repeat: no-repeat;
background-position: 6rpx center;
}
.locate {
background-image: url('~@/static/images/locate.png');
border-bottom: 1rpx solid #eee;
.user {
font-size: 26rpx;
color: #444;
}
.address {
font-size: 24rpx;
color: #666;
}
}
.item {
background-image: url('~@/static/images/car.png');
border-bottom: 1rpx solid #eee;
position: relative;
.message {
font-size: 26rpx;
color: #444;
}
.date {
font-size: 24rpx;
color: #666;
}
}
.delivery {
display: flex;
align-items: center;
.delivery-tip {
font-size: 24rpx;
display: flex;
flex-direction: column;
}
.info-item {
display: flex;
font-size: 24rpx;
padding-bottom: 10rpx;
}
.item-label {
color: #999999;
margin-right: 20rpx;
}
.item-content {
font-size: 26rpx;
}
.more {
margin-left: auto;
}
}
}
.goods {
margin: 20rpx;
padding: 0 20rpx;
border-radius: 30rpx;
background-color: #fff;
.item {
padding: 30rpx 0;
border-bottom: 1rpx solid #eee;
.navigator {
display: flex;
// margin: 20rpx 0;
}
.cover {
width: 170rpx;
height: 170rpx;
border-radius: 10rpx;
margin-right: 20rpx;
}
.meta {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-around;
position: relative;
}
.name {
font-size: 26rpx;
color: #444;
}
.type {
line-height: 1.8;
padding: 0 15rpx;
margin-top: 6rpx;
font-size: 24rpx;
align-self: flex-start;
border-radius: 4rpx;
color: #888;
background-color: #f7f7f8;
max-width: 91%;
}
.price {
display: flex;
margin-top: 6rpx;
font-size: 24rpx;
}
.symbol {
font-size: 20rpx;
}
.original {
color: #999;
text-decoration: line-through;
}
.actual {
margin-left: 10rpx;
color: #444;
}
.text {
font-size: 22rpx;
}
.operate {
display: flex;
align-items: center;
flex-direction: row-reverse;
.button {
display: flex;
justify-content: center;
border-radius: 60rpx;
height: 50rpx;
width: 150rpx;
align-items: center;
border: 1rpx solid #ccc;
color: #444;
font-size: 20rpx;
margin-left: 20rpx;
}
}
.quantity {
position: absolute;
right: 0;
font-size: 24rpx;
color: #444;
}
.action {
display: flex;
flex-direction: row-reverse;
justify-content: flex-start;
padding: 30rpx 0 0;
.button {
width: 200rpx;
height: 60rpx;
text-align: center;
justify-content: center;
line-height: 60rpx;
margin-left: 20rpx;
border-radius: 60rpx;
border: 1rpx solid #ccc;
font-size: 26rpx;
color: #444;
}
.primary {
color: #ff5f3c;
border-color: #ff5f3c;
}
}
}
.total {
line-height: 1.4;
font-size: 26rpx;
padding: 20rpx 0;
color: #666;
.row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10rpx 0;
}
.symbol::before {
content: '¥';
font-size: 80%;
margin-right: 3rpx;
}
.discount::before {
content: '- ¥';
font-size: 80%;
margin-right: 3rpx;
}
.primary {
color: #cf4444;
font-size: 36rpx;
}
}
}
.detail {
line-height: 1.4;
padding: 20rpx;
margin: 20rpx;
font-size: 26rpx;
color: #666;
border-radius: 30rpx;
background-color: #fff;
.title {
font-size: 30rpx;
color: #444;
}
.row {
padding: 20rpx 0;
.item {
padding: 10rpx 0;
display: flex;
.item-title {
margin-right: 30rpx;
}
}
.copy {
border-radius: 20rpx;
font-size: 20rpx;
border: 1px solid #ccc;
padding: 5rpx 20rpx;
margin-left: 10rpx;
}
}
}
.toolbar-height {
height: 100rpx;
box-sizing: content-box;
}
.toolbar {
position: fixed;
left: 0;
right: 0;
bottom: calc(var(--window-bottom));
z-index: 1;
height: 100rpx;
padding: 0 20rpx;
display: flex;
align-items: center;
flex-direction: row;
justify-content: flex-end;
border-top: 1rpx solid #ededed;
border-bottom: 1rpx solid #ededed;
background-color: #fff;
box-sizing: content-box;
.button {
display: flex;
justify-content: center;
align-items: center;
width: 150rpx;
height: 60rpx;
margin-left: 15rpx;
font-size: 26rpx;
border-radius: 72rpx;
border: 1rpx solid #ccc;
color: #444;
}
.delete {
order: 4;
}
.button {
order: 3;
}
.secondary {
order: 2;
color: #ff5f3c;
border-color: #ff5f3c;
}
.primary {
order: 1;
color: #fff;
background-color: #ff5f3c;
}
}
</style>

View File

@ -1,221 +0,0 @@
<script setup lang="ts">
import { cleanContent } from '@/utils/common'
import { postOrderEvaluateApi } from '@/api/order'
import { ref } from 'vue'
import { uploadApi } from '@/api/common'
const props = defineProps<{
detail_id?: string
order_id?: string
}>()
const content = ref('')
const images = ref([])
const score = ref(5)
const rateArr = ['非常差', '差', '一般', '好', '非常好']
const rateStr = ref(rateArr[score.value - 1])
const onChange = (event: UniHelper.UniRateOnChangeEvent) => {
rateStr.value = rateArr[event.value - 1]
}
const tmpPathArr = ref<string[]>([])
const imagePathArr = ref<string[]>([])
const onSelect = (e: UniHelper.UniFilePickerOnSelectEvent) => {
tmpPathArr.value = tmpPathArr.value.concat(e.tempFilePaths)
}
const onDelete = (e: UniHelper.UniFilePickerOnDeleteEvent) => {
//
const index = tmpPathArr.value.findIndex((item) => item === e.tempFilePath)
tmpPathArr.value.splice(index, 1)
}
const onSubmit = async () => {
const uploadPromises = tmpPathArr.value.map((item) => {
return new Promise((resolve, reject) => {
uploadApi(item).then((res) => {
// console.log(res)
imagePathArr.value.push(res.result.url)
resolve(res)
})
})
})
try {
uni.showLoading({
title: '数据提交中...',
})
await Promise.all(uploadPromises)
const res = await postOrderEvaluateApi({
detail_id: props.detail_id ?? '',
order_id: props.order_id ?? '',
score: score.value,
content: cleanContent(content.value),
images: imagePathArr.value.join(','),
})
if (res.msg) {
uni.showToast({
title: res.msg,
icon: 'error',
})
return
}
uni.showToast({
title: '评价成功',
icon: 'success',
})
setTimeout(() => {
uni.navigateBack({
delta: 1,
})
}, 1500)
} catch (err) {
//
console.log(err)
uni.showToast({
title: '评价失败,请刷新重试',
icon: 'none',
})
}
}
</script>
<template>
<view class="body">
<view class="top">
<text class="text">商品评价</text>
<view class="rate">
<uni-rate v-model="score" @change="onChange" />
</view>
<text class="rate-text">{{ rateStr }}</text>
</view>
<view class="content">
<uni-easyinput
type="textarea"
autoHeight
v-model="content"
placeholder="请输入评价内容"
:inputBorder="false"
></uni-easyinput>
</view>
<view class="image">
<uni-file-picker
v-model="images"
file-mediatype="image"
mode="grid"
file-extname="png,jpg"
:limit="9"
return-type="array"
@select="onSelect"
@delete="onDelete"
:auto-upload="false"
>
<view class="upload">
<uni-icons type="camera" color="#ff0000" size="30"></uni-icons>
<text class="text">添加图片</text>
</view>
</uni-file-picker>
</view>
</view>
<shop-submit-button @tap="onSubmit">提交评价</shop-submit-button>
</template>
<style lang="scss">
page {
height: 100%;
overflow: auto;
display: flex;
flex-direction: column;
background-color: #f4f4f4;
}
.body {
display: flex;
flex-direction: column;
border-radius: 10rpx;
overflow: hidden;
border: 1rpx solid #ccc;
.top {
padding: 20rpx 15rpx 20rpx;
display: flex;
align-items: center;
background-color: #fff;
.rate {
margin: 0 20rpx 0 0rpx;
cursor: pointer;
}
.text {
font-size: 24rpx;
color: #333;
margin-right: 20rpx;
}
.rate-text {
font-size: 20rpx;
color: #666;
}
}
.content {
display: flex;
background-color: #fff;
margin-top: 10rpx;
padding: 15rpx;
.uni-easyinput {
width: 100%;
font-size: 24rpx;
line-height: 1.5;
color: #666;
}
}
.image {
display: flex;
background-color: #fff;
margin-top: 10rpx;
padding: 0;
align-items: flex-start;
.upload {
display: flex;
flex-direction: column;
text-align: center;
font-size: 24rpx;
.text {
color: #666;
font-size: 27rpx;
}
}
}
}
.sub-button {
display: flex;
justify-content: center;
align-items: center;
position: absolute;
bottom: 0;
width: 100%;
.button-container {
background-color: white;
padding: 10rpx;
border-radius: 20rpx;
width: 100%;
}
button {
width: 100%;
background-color: #ff5f3c;
color: white;
border-radius: 20rpx;
text-align: center;
cursor: pointer;
}
}
</style>

View File

@ -1,438 +0,0 @@
<script setup lang="ts">
import { deleteOrderApi, getOrderListApi } from '@/api/order'
import type { orderListResult } from '@/types/order'
import { ref, watch } from 'vue'
import {
orderState,
orderStateList,
orderType,
orderStateActions,
refundStateList,
pageUrl,
} from '@/utils/constants'
import { fullUrl, getPayPageUrl } from '@/utils/common'
import { onShow } from '@dcloudio/uni-app'
import cancelButton from '@/pagesOrder/components/cancelButton.vue'
import { usePopupStore } from '@/stores'
import ReceiveButton from '@/pagesOrder/components/receiveButton.vue'
// eslint-disable-next-line no-undef
const paging = ref<ZPagingInstance>()
const dataList = ref<orderListResult[]>([])
//
const firstLoaded = ref(false)
//
const isCurrentPage = ref(false)
const props = withDefaults(
defineProps<{
tabIndex: number
currentIndex: number
status: string
}>(),
{
tableIndex: 0,
currentIndex: 0,
status: '',
},
)
const queryList = (page: number, page_size: number) => {
const params = {
page,
page_size,
type: orderType.default,
status: props.status,
}
getOrderListApi(params).then((res) => {
res.result.data.map((item) => {
if (item.status == orderState.toPay && item.countdown < 0) {
item.status = orderState.canceled
}
})
paging.value?.complete(res.result.data)
})
}
//
const onDeleteOrder = (id: number) => {
uni.showModal({
content: '确定要删除订单吗?',
cancelText: '取消',
confirmText: '确定',
success: (res) => {
if (res.confirm) {
deleteOrderApi({
id: [id],
}).then(() => {
uni.showToast({
title: '删除成功',
icon: 'success',
})
setTimeout(() => {
paging.value?.refresh()
}, 1000)
})
}
},
})
}
const onCancel = (event: { orderId: number; reason: string }) => {
const index = dataList.value.findIndex((v) => v.id == event.orderId)
if (index > -1) {
dataList.value.splice(index, 1)
}
}
watch(
() => props.currentIndex,
(newVal) => {
if (newVal === props.tabIndex) {
// item
if (!firstLoaded.value) {
// z-paging
setTimeout(() => {
isCurrentPage.value = true
}, 100)
}
}
},
{
immediate: true,
},
)
onShow(() => {
if (props.currentIndex === props.tabIndex) {
paging.value?.refresh()
}
})
const backToTop = ref(true)
watch(
() => usePopupStore().show,
(newVal) => {
backToTop.value = !newVal
},
{ immediate: true },
)
</script>
<!-- 在这个文件对每个tab对应的列表进行渲染 -->
<template>
<view class="content">
<!-- 如果当前页已经加载过数据或者当前切换到的tab是当前页才展示当前页数据懒加载 -->
<z-paging
:enable-back-to-top="currentIndex === tabIndex"
v-if="firstLoaded || isCurrentPage"
ref="paging"
v-model="dataList"
@query="queryList"
:fixed="false"
:auto-show-back-to-top="backToTop"
:back-to-top-style="{
display: backToTop ? '' : 'none',
}"
bg-color="#f6f6f6"
>
<view class="orders" v-for="(item, index) in dataList" :key="index">
<view class="card">
<view class="status">
<text class="date">{{ item.create_time }}</text>
<text v-if="item.status === orderState.applyCancel">
{{ refundStateList.find((v) => v.id == item.refund_status)?.text ?? '' }}
</text>
<text v-else>{{ orderStateList[item.status].text ?? '' }}</text>
<text
class="icon-delete"
v-show="orderStateActions.deletable.includes(item.status)"
@tap="onDeleteOrder(item.id)"
/>
</view>
<navigator
v-for="sku in item.detail"
:key="sku.id"
class="goods"
:url="`${pageUrl['order-detail']}?id=${item.id}`"
hover-class="none"
>
<image class="cover" :src="fullUrl(sku.image)"></image>
<view class="meta">
<view class="name ellipsis">{{ sku.goods_name }}</view>
<view class="name meta-spec">{{ sku.spec }}</view>
<view class="meta-quantity">x{{ sku.num }}</view>
</view>
</navigator>
<view class="payment">
<text class="quantity">{{ item.total_num }}件商品</text>
<text v-if="orderStateActions.normalTransaction.includes(item.status)">实付</text>
<text v-else>应付</text>
<text class="amount"> <text class="symbol">¥</text>{{ item.pay_price }}</text>
</view>
<view class="action">
<template v-if="item.status === orderState.toPay && item.countdown > 0">
<text class="pay-time">支付剩余</text>
<countdown-timer :time="item.countdown * 1000" :autoStart="true">
<template v-slot="{ day, hour, minute, second }">
<view v-if="item.countdown > 86400">{{ day }}{{ hour }}{{ minute }}</view>
<view v-else-if="item.countdown < 60">{{ second }}</view>
<view v-else>{{ hour }}{{ minute }}</view>
</template>
</countdown-timer>
<cancelButton
:status="item.status"
:id="item.id"
className="button secondary"
@cancel="onCancel"
/>
<navigator
class="button primary"
:url="getPayPageUrl(item.id, item.pay_price)"
hover-class="none"
>
去支付
</navigator>
</template>
<template v-else>
<cancelButton
:status="item.status"
:id="item.id"
className="button secondary"
@cancel="onCancel"
/>
<ReceiveButton
:status="item.status"
:id="item.id"
:paymentTradeInfo="item.payment_trade"
className="button secondary"
/>
<view v-if="orderStateActions.evaluatable.includes(item.status)">
<navigator
:url="`${pageUrl['order-evaluate']}?order_id=${item.id}`"
class="button secondary"
hover-class="none"
>
一键评价
</navigator>
</view>
<navigator
class="button primary"
:url="`${pageUrl['order-create']}?orderId=${item.id}`"
hover-class="none"
>
再次购买
</navigator>
</template>
</view>
</view>
</view>
</z-paging>
</view>
</template>
<style scoped lang="scss">
/* 注意:父节点需要固定高度z-paging的height:100%才会生效 */
.content {
height: 100%;
}
page {
background-color: #f6f6f6;
}
.orders {
display: flex;
flex-direction: column;
background-color: #f6f6f6;
.card {
min-height: 100rpx;
padding: 20rpx;
margin: 20rpx;
border-radius: 10rpx;
background-color: #fff;
&:last-child {
padding-bottom: 40rpx;
}
}
.status {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 28rpx;
color: #999;
margin-bottom: 15rpx;
.date {
color: #666;
flex: 1;
}
.primary {
color: #ff9240;
}
.icon-delete {
line-height: 1;
margin-left: 10rpx;
padding-left: 10rpx;
border-left: 1rpx solid #e3e3e3;
}
}
.goods {
display: flex;
margin-bottom: 20rpx;
.cover {
width: 170rpx;
height: 170rpx;
margin-right: 20rpx;
border-radius: 10rpx;
overflow: hidden;
position: relative;
}
.quantity {
position: absolute;
bottom: 0;
right: 0;
line-height: 1;
padding: 6rpx 4rpx 6rpx 8rpx;
font-size: 24rpx;
color: #fff;
border-radius: 10rpx 0 0 0;
background-color: rgba(0, 0, 0, 0.6);
}
.name {
font-size: 26rpx;
color: #444;
max-width: 90%;
}
.meta {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-evenly;
.meta-spec {
font-size: 24rpx;
color: #999;
margin-top: 10rpx;
}
&-quantity {
position: absolute;
right: 20px;
font-size: 24rpx;
color: #9e9c9c;
}
}
.type {
line-height: 1.8;
padding: 0 15rpx;
margin-top: 10rpx;
font-size: 24rpx;
align-self: flex-start;
border-radius: 4rpx;
color: #888;
background-color: #f7f7f8;
}
.more {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
font-size: 22rpx;
color: #333;
}
}
.payment {
display: flex;
justify-content: flex-end;
align-items: center;
line-height: 1;
padding: 20rpx 0;
text-align: right;
color: #999;
font-size: 28rpx;
border-bottom: 1rpx solid #eee;
.quantity {
font-size: 24rpx;
margin-right: 16rpx;
}
.amount {
color: #444;
margin-left: 6rpx;
}
.symbol {
font-size: 20rpx;
}
}
.action {
display: flex;
justify-content: flex-end;
align-items: center;
padding-top: 20rpx;
.pay-time {
font-size: 24rpx;
color: #999;
margin-right: 20rpx;
}
.countdown {
font-size: 24rpx;
color: #999;
margin-left: auto;
display: flex;
}
.button {
width: 150rpx;
height: 60rpx;
display: flex;
justify-content: center;
align-items: center;
margin-left: 20rpx;
border-radius: 60rpx;
border: 1rpx solid #ccc;
font-size: 26rpx;
color: #444;
}
.secondary {
color: #ff5f3c;
border-color: #ff5f3c;
}
.primary {
color: #fff;
background-color: #ff5f3c;
}
}
.loading-text {
text-align: center;
font-size: 28rpx;
color: #666;
padding: 20rpx 0;
}
}
</style>

View File

@ -1,119 +0,0 @@
<script setup lang="ts">
import { ref, watch } from 'vue'
import orderListItem from '@/pagesOrder/pages/list/components/order-list-item.vue'
import { usePopupStore } from '@/stores'
import { pageMateStyle } from '@/utils/constants'
//
const query = withDefaults(
defineProps<{
status?: string
}>(),
{
status: '',
},
)
const tabs = ref()
const tabList = ref([
{
name: '全部',
value: '',
disabled: false,
},
{
name: '待付款',
value: '1',
disabled: false,
},
{
name: '待发货',
value: '2',
disabled: false,
},
{
name: '待收货',
value: '3',
disabled: false,
},
{
name: '待评价',
value: '4',
disabled: false,
},
])
// tab
const sub = tabList.value.findIndex((item) => item.value == query.status)
const current = ref(sub == -1 ? 0 : sub)
// tabsswiper
const tabsChange = (index: number) => {
current.value = index
}
// swiper
const swiperTransition = (e: UniHelper.SwiperOnTransitionEvent) => {
if (!current.value) {
tabs.value.setDx(e.detail.dx)
}
}
// swiper
const swiperAnimationfinish = (e: UniHelper.SwiperOnAnimationfinishEvent) => {
current.value = e.detail.current
if (current.value) {
tabs.value.unlockDx()
}
}
watch(
() => usePopupStore().show,
(newVal) => {
tabList.value.forEach((item) => (item.disabled = newVal))
},
{ immediate: true },
)
</script>
<template>
<!-- #ifdef MP-WEIXIN-->
<page-mate :pageStyle="pageMateStyle">
<!-- #endif -->
<z-paging-swiper>
<template #top>
<z-tabs
ref="tabs"
:list="tabList"
:current="current"
@change="tabsChange"
bg-color="#f6f6f6"
/>
</template>
<swiper
class="swiper"
:current="current"
@transition="swiperTransition"
@animationfinish="swiperAnimationfinish"
>
<swiper-item class="swiper-item" v-for="(item, index) in tabList" :key="index">
<order-list-item
:tabIndex="index"
:currentIndex="current"
:status="tabList[current].value"
></order-list-item>
</swiper-item>
</swiper>
</z-paging-swiper>
<!-- #ifdef MP-WEIXIN -->
</page-mate>
<!-- #endif -->
</template>
<style lang="scss">
page {
background-color: #f6f6f6;
}
.swiper {
height: 100%;
}
</style>

View File

@ -1,244 +0,0 @@
<script setup lang="ts">
import { pageUrl } from '@/utils/constants'
import { getMemberProfileApi } from '@/api/member'
import { payOrderApi } from '@/api/pay'
import _ from 'lodash'
import { computed, reactive, ref } from 'vue'
import { onShowRefreshData } from '@/utils/common'
//
const props = defineProps<{
orderId: string
payPrice: string
}>()
const state = reactive({
orderId: Number(props.orderId),
balance: '0.00',
})
const getBalance = async () => {
const res = await getMemberProfileApi()
state.balance = res.result.money || '0.00'
}
const payMethods = computed(() => {
return [
// #ifdef WEB
{
name: '支付宝支付',
value: 'alipay',
icon: '/static/icons/alipay.svg',
},
// #endif
{
name: '微信支付',
value: 'wxpay',
icon: '/static/icons/wechat-pay.svg',
},
{
name: `余额支付(可用¥${state.balance})`,
value: 'balance',
icon: '/static/icons/balance.svg',
},
]
})
const current = ref()
const payWay = ref('')
const radioChange = (event: UniHelper.RadioGroupOnChangeEvent) => {
payWay.value = event.detail.value
}
const onPayment = async () => {
if (!payWay.value) {
return uni.showToast({
title: '请选择支付方式',
icon: 'none',
mask: true,
})
}
if (payWay.value === 'balance' && parseFloat(props.payPrice) > parseFloat(state.balance)) {
return uni.showToast({
title: '很抱歉,您的余额不足',
icon: 'none',
mask: true,
})
}
try {
const res = await payOrderApi({
id: state.orderId,
pay_way: payWay.value,
})
switch (payWay.value) {
case 'wxpay':
if (uni.getSystemInfoSync().uniPlatform === 'web') {
doH5Wxpay(res)
} else {
doWxPay(res.result)
}
break
case 'alipay':
doAlipay(res)
break
case 'balance':
successHandle()
break
}
} catch (err: any) {
console.log(err)
//
uni.showModal({
title: '提示',
content: err.data.msg || '支付失败',
showCancel: false,
})
}
}
const doWxPay = async (result: WechatMiniprogram.RequestPaymentOption) => {
wx.requestPayment({
...result,
success: () => {
successHandle()
},
fail: (fail) => {
failHandle(fail)
},
})
}
const doH5Wxpay = (res: any) => {
wx.showLoading({
title: '正在支付…',
mask: true,
})
location.href = res.result.h5_url
}
const doAlipay = (res: any) => {
wx.showLoading({
title: '正在支付…',
mask: true,
})
location.href = res.result.url
}
const successHandle = () => {
setTimeout(() => {
uni.redirectTo({
url: `${pageUrl['order-payment']}?id=${state.orderId}&pay_way=${payWay.value}`,
})
}, 500)
}
const failHandle = (err: any) => {
console.warn('支付异常:')
console.warn(err)
if (err.errMsg == 'requestPayment:fail cancel') {
uni.showToast({
title: '订单取消支付~',
icon: 'none',
})
} else {
uni.showToast({
title: err.data.msg ? err.data.msg : '支付未完成',
icon: 'none',
})
}
}
onShowRefreshData(() => {
getBalance()
})
</script>
<template>
<view class="overview">
<view class="money">
<span class="currency"></span>
<span class="integer">{{ props.payPrice.split('.')[0] }}.</span>
<span class="decimal">{{ props.payPrice.split('.')[1] }}</span>
</view>
<view class="payment">
<radio-group @change="radioChange" class="card">
<label class="item" v-for="(item, index) in payMethods" :key="item.value">
<view class="method">
<span><image :src="item.icon" class="icon" /></span>
{{ item.name }}
</view>
<view>
<radio :value="item.value" :checked="current === index" />
</view>
</label>
</radio-group>
</view>
<shop-submit-button title="确认支付" @tap="onPayment"></shop-submit-button>
</view>
</template>
<style lang="scss">
page {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
background: #f0f0f0;
}
.overview {
background-color: #f0f0f0;
.money {
padding: 5%;
display: flex;
align-items: center;
justify-content: center;
position: relative;
color: red;
font-size: 50rpx;
.currency {
font-size: 35rpx;
vertical-align: sub;
align-self: flex-end;
}
.integer {
font-size: 60rpx;
}
.decimal {
font-size: 50rpx;
align-self: flex-end;
}
}
.payment {
margin: 20rpx 20rpx 0;
padding: auto;
border-radius: 10rpx;
background-color: #fff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
.item {
display: flex;
justify-content: space-between;
padding: 5%;
}
.method {
display: flex;
align-items: center;
}
.icon {
width: 40rpx;
height: 40rpx;
margin-right: 20rpx;
}
}
}
</style>

View File

@ -1,133 +0,0 @@
<script setup lang="ts">
import { pageUrl } from '@/utils/constants'
import { onShow } from '@dcloudio/uni-app'
import { postPayNotifyApi } from '@/api/pay'
import { ref } from 'vue'
//
const query = defineProps<{
id: number | string
pay_way: string
}>()
const isPay = ref(false)
const checkPay = () => {
if (query.pay_way === 'balance') {
isPay.value = true
return
}
postPayNotifyApi({
id: Number(query.id),
pay_way: query.pay_way,
})
.then(() => {
isPay.value = true
})
.catch((err) => {
console.warn(err)
if (err.errMsg == 'requestPayment:fail cancel') {
uni.showToast({
title: '订单取消支付~',
icon: 'none',
})
} else {
uni.showToast({
title: err.data.msg ? err.data.msg : '支付未完成',
icon: 'none',
})
}
setTimeout(() => {
uni.redirectTo({
url: `${pageUrl['order-list']}`,
})
}, 500)
})
}
onShow(() => {
checkPay()
})
</script>
<template>
<scroll-view enable-back-to-top class="viewport" scroll-y>
<!-- 订单状态 -->
<view class="overview" v-if="isPay">
<view class="status icon-checked">支付成功</view>
<view class="buttons">
<navigator
hover-class="none"
class="button navigator"
:url="`${pageUrl['index']}`"
open-type="switchTab"
>
返回首页
</navigator>
<navigator
hover-class="none"
class="button navigator"
:url="`${pageUrl['order-detail']}?id=${query.id}`"
open-type="redirect"
>
查看订单
</navigator>
</view>
</view>
</scroll-view>
</template>
<style lang="scss">
page {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
}
.viewport {
background-color: #f7f7f8;
}
.overview {
line-height: 1;
padding: 50rpx 0;
color: #fff;
background-color: #ff7c22;
.status {
font-size: 36rpx;
font-weight: 500;
text-align: center;
}
.status::before {
display: block;
font-size: 110rpx;
margin-bottom: 20rpx;
}
.buttons {
height: 60rpx;
line-height: 60rpx;
display: flex;
justify-content: center;
align-items: center;
margin-top: 60rpx;
}
.button {
text-align: center;
margin: 0 10rpx;
font-size: 28rpx;
color: #fff;
&:first-child {
width: 200rpx;
border-radius: 64rpx;
border: 1rpx solid #fff;
}
}
}
</style>

View File

@ -1,89 +0,0 @@
<script setup lang="ts">
import { ref } from 'vue'
import { pageUrl } from '@/utils/constants'
//
const query = defineProps<{
id: number
}>()
const back = () => {
uni.navigateBack({ delta: 1 })
}
const orderId = ref(query.id)
</script>
<template>
<scroll-view enable-back-to-top class="viewport" scroll-y>
<!-- 订单状态 -->
<view class="overview">
<view class="status icon-checked">收货成功</view>
<view class="buttons">
<view class="button navigator" @tap="back"> 返回 </view>
<navigator
hover-class="none"
class="button navigator"
:url="`${pageUrl['order-evaluate']}?order_id=${orderId}`"
open-type="redirect"
>
去评价
</navigator>
</view>
</view>
</scroll-view>
</template>
<style lang="scss">
page {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
}
.viewport {
background-color: #f7f7f8;
}
.overview {
line-height: 1;
padding: 50rpx 0;
color: #fff;
background-color: #ff7c22;
.status {
font-size: 36rpx;
font-weight: 500;
text-align: center;
}
.status::before {
display: block;
font-size: 110rpx;
margin-bottom: 20rpx;
}
.buttons {
height: 60rpx;
line-height: 60rpx;
display: flex;
justify-content: center;
align-items: center;
margin-top: 60rpx;
}
.button {
text-align: center;
margin: 0 10rpx;
font-size: 28rpx;
color: #fff;
&:first-child {
width: 200rpx;
border-radius: 64rpx;
border: 1rpx solid #fff;
}
}
}
</style>

View File

@ -1,77 +0,0 @@
<script setup lang="ts">
import { getRechargeOrderListApi } from '@/api/recharge'
import { onShowRefreshData } from '@/utils/common'
import type { rechargeOrderListItem } from '@/types/member'
import { ref } from 'vue'
// eslint-disable-next-line no-undef
const paging = ref<ZPagingInstance>()
const dataList = ref<rechargeOrderListItem[]>([])
const queryList = (page: number, page_size: number) => {
getRechargeOrderListApi({
page,
page_size,
}).then((res) => {
paging.value?.complete(res.result.data)
})
}
const getRechargeDesc = (item: rechargeOrderListItem) => {
let str = ''
if (parseFloat(item.gift_money) > 0) {
str += `充值${item.money}元,赠送${item.gift_money}`
} else {
str += `充值${item.money}`
}
if (item.pay_status == 1) {
str += '(充值中)'
} else if (item.pay_status == 2) {
str += '(已到账)'
}
return str
}
onShowRefreshData(() => {
paging.value?.refresh()
})
</script>
<template>
<shop-paginate-list ref="paging" v-model="dataList" @query="queryList">
<uni-list class="list">
<uni-list-item
v-for="(item, index) in dataList"
:key="index"
class="item"
:title="item.order_no"
:note="getRechargeDesc(item)"
>
<template #footer>
<view class="right">
<text class="time">{{ item.create_time }}</text>
<text class="money"> +{{ item.actual_money }} </text>
</view>
</template>
</uni-list-item>
</uni-list>
</shop-paginate-list>
</template>
<style lang="scss" scoped>
.item {
line-height: 1.5em;
}
.right {
display: flex;
flex-direction: column;
align-items: flex-end;
}
.time {
font-size: 24rpx;
}
.money {
color: red;
}
</style>

View File

@ -1,545 +0,0 @@
<script setup lang="ts">
import { fullUrl, previewImg } from '@/utils/common'
import { getDeliveryCompanyApi } from '@/api/delivery'
import { getOrderRefundDetailApi, postOrderRefundReturnApi } from '@/api/order'
import type { deliveryCompanyItem } from '@/types/delivery'
import type { orderRefundDetailResult } from '@/types/order'
import { onLoad } from '@dcloudio/uni-app'
import { computed } from 'vue'
import { ref } from 'vue'
import { pageUrl } from '@/utils/constants'
const props = defineProps<{
data: string
id: number
}>()
const orderData = ref<orderRefundDetailResult>()
const getOrderData = async () => {
const res = await getOrderRefundDetailApi({ id: props.id })
orderData.value = res.result
}
//
enum auditStatusEnum {
wait_audit = 1,
agree = 2,
refuse = 3,
cancel = 4,
}
//
const showHistory = computed(() => {
return orderData.value?.audit_status == auditStatusEnum.refuse
})
// 退
const showDelivery = computed(() => {
return orderData.value?.audit_status == auditStatusEnum.agree && orderData.value.refund_address
})
//
const copyAddress = () => {
const fullAddress = `收货人:${orderData.value?.refund_address.name}联系电话:${orderData.value?.refund_address.phone}详细地址:${orderData.value?.refund_address.full_address}`
uni.setClipboardData({
data: fullAddress,
success: () => {
uni.showToast({
title: '复制成功',
icon: 'none',
})
},
})
}
const deliveryCompanyOptions = ref<deliveryCompanyItem[]>([])
const companyIndex = ref(0)
const getDeliveryCompanyData = async () => {
//
if (deliverForm.value.delivery_company && deliverForm.value.delivery_no) return
const res = await getDeliveryCompanyApi()
res.result.unshift({ id: 0, name: '请选择物流公司', code: '' })
deliveryCompanyOptions.value = res.result
}
const deliverForm = ref({
delivery_company_id: 0,
delivery_company: '',
delivery_no: '',
})
const deliverFormRules = ref({
delivery_no: {
rules: [{ required: true, errorMessage: '请输入物流单号' }],
},
})
const onChnageCompanyIndex = (event: UniHelper.SelectorPickerOnChangeEvent) => {
companyIndex.value = event.detail.value
const deliveryCompany = deliveryCompanyOptions.value.find((item) => item.id == companyIndex.value)
deliverForm.value.delivery_company_id = deliveryCompany!.id
deliverForm.value.delivery_company = deliveryCompany!.name
}
const formRef = ref()
const onSubmit = async () => {
if (!deliverForm.value.delivery_company_id) {
uni.showToast({
title: '请选择物流公司',
icon: 'none',
})
return
}
await formRef.value.validate()
const params = {
id: props.id,
...deliverForm.value,
}
const res = await postOrderRefundReturnApi(params)
uni.showToast({
title: res.msg,
icon: 'none',
})
setTimeout(() => {
uni.navigateBack()
}, 1500)
}
const refundTypeText = computed(() => {
switch (orderData.value?.refund_type) {
case 1:
return '退货退款'
case 2:
return '换货'
default:
return ''
}
})
onLoad(async () => {
await getOrderData()
await getDeliveryCompanyData()
})
</script>
<template>
<view class="detail" v-if="orderData">
<view class="status">
<image class="image" src="/static/images/bg.png" />
<text>{{ orderData.status_text }}</text>
</view>
<view class="order">
<view class="card">
<text class="title">售后商品</text>
<navigator
class="goods"
:url="`${pageUrl['goods-detail']}?id=${orderData.detail.goods_id}`"
hover-class="none"
>
<image class="cover" :src="fullUrl(orderData.detail.image)"></image>
<view class="meta">
<view class="name ellipsis">{{ orderData.detail.goods_name }}</view>
<view class="meta-spec">{{ orderData.detail.spec }}</view>
<view class="meta-quantity">x{{ orderData!.refund_num }}</view>
</view>
</navigator>
<view class="detail">
<view class="detail-item" v-if="parseFloat(orderData.apply_money) > 0">
<text>申请金额</text>
<text class="amount">{{ orderData.apply_money }}</text>
</view>
<view class="detail-item" v-if="parseFloat(orderData.refund_money) > 0">
<text>退款金额</text>
<text class="amount">{{ orderData.refund_money }}</text>
</view>
<view class="detail-item" v-if="parseFloat(orderData.refund_freight) > 0">
<text>退款运费</text>
<text>{{ orderData.refund_freight }}</text>
</view>
<view class="detail-item">
<text>申请时间</text>
<text>{{ orderData.create_time }}</text>
</view>
</view>
</view>
</view>
<view class="block" v-if="showHistory">
<view class="history">
<view class="header">
<text class="title">协商历史</text>
</view>
<view class="history-item">
<text>售后类型{{ refundTypeText }}</text>
</view>
<view class="history-item">
<text>申请原因{{ orderData.remark }}</text>
</view>
<view class="history-item" v-if="orderData.remark_images">
<text>申请凭证</text>
<view class="images-container">
<image
v-for="(item, index) in orderData.remark_images"
:key="index"
:src="item"
mode="aspectFit"
@tap="previewImg(item, orderData!.remark_images)"
/>
</view>
</view>
<view class="history-item" v-if="orderData.refuse_reason">
<text>商家回复{{ orderData.refuse_reason }}</text>
</view>
</view>
</view>
<view class="block" v-if="showDelivery">
<view class="address">
<view class="header">
<text class="title">退货地址</text>
<button class="copy-button" @tap="copyAddress">复制</button>
</view>
<view>
<text>收货人{{ orderData.refund_address.name }}</text>
</view>
<view>
<text>联系电话{{ orderData.refund_address.phone }}</text>
</view>
<view>
<text>详细地址{{ orderData.refund_address.full_address }}</text>
</view>
<view class="remark">
<text>未与商家协商一致情况下请勿使用平邮或到付邮寄方式</text>
</view>
</view>
</view>
<view class="block" v-if="showDelivery">
<view class="delivery">
<view class="header">
<text class="title">退货物流</text>
</view>
<uni-list v-if="orderData.user_ship_delivery">
<uni-list-item
title="物流公司"
:rightText="orderData.user_ship_delivery.delivery_company"
>
</uni-list-item>
<uni-list-item title="物流单号" :rightText="orderData.user_ship_delivery.delivery_no">
</uni-list-item>
</uni-list>
<view v-else>
<uni-forms
ref="formRef"
label-width="100"
validate-trigger="blur"
:rules="deliverFormRules"
:model="deliverForm"
>
<uni-forms-item required label="物流公司" name="delivery_company">
<view style="display: flex; align-items: center; height: 35px">
<picker
mode="selector"
:range="deliveryCompanyOptions"
range-key="name"
:value="companyIndex"
@change="onChnageCompanyIndex"
>
<text>{{ deliveryCompanyOptions[companyIndex]?.name }}</text>
</picker>
</view>
</uni-forms-item>
<uni-forms-item required label="物流单号" name="delivery_no">
<uni-easyinput
v-model="deliverForm.delivery_no"
placeholder="请输入物流单号"
:disabled="Boolean(orderData.user_ship_status)"
/>
</uni-forms-item>
</uni-forms>
<button class="sub-button" @tap="onSubmit" v-if="!Boolean(orderData.user_ship_status)">
录入物流信息
</button>
</view>
</view>
</view>
<view class="block" v-if="orderData.exchange_delivery">
<view class="delivery">
<view class="header">
<text class="title">换货物流</text>
</view>
<uni-list>
<uni-list-item title="物流公司" :rightText="orderData.exchange_delivery.delivery_company">
</uni-list-item>
<uni-list-item title="物流单号" :rightText="orderData.exchange_delivery.delivery_no">
</uni-list-item>
</uni-list>
</view>
</view>
<view :style="{ paddingBottom: '100rpx' }"></view>
</view>
</template>
<style lang="scss">
page {
background-color: #f6f6f6;
}
.status {
display: flex;
justify-content: center;
align-items: center;
position: relative;
height: 150rpx;
/* 图片样式 */
.image {
width: 100%;
height: 150rpx;
position: absolute;
top: 0;
left: 0;
}
/* 文本样式 */
text {
z-index: 1;
font-size: 36rpx;
color: #fff;
}
}
.order {
height: 100%;
display: flex;
flex-direction: column;
background-color: #f6f6f6;
.card {
min-height: 100rpx;
padding: 20rpx;
margin: 20rpx 20rpx 0;
border-radius: 10rpx;
background-color: #fff;
&:last-child {
padding-bottom: 40rpx;
}
}
.status {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 28rpx;
color: #999;
margin-bottom: 15rpx;
.date {
color: #666;
flex: 1;
}
.text {
color: red;
}
.primary {
color: #ff9240;
}
.icon-delete {
line-height: 1;
margin-left: 10rpx;
padding-left: 10rpx;
border-left: 1rpx solid #e3e3e3;
}
}
.goods {
display: flex;
padding-top: 20rpx;
padding-bottom: 20rpx;
.cover {
width: 170rpx;
height: 170rpx;
margin-right: 20rpx;
border-radius: 10rpx;
overflow: hidden;
position: relative;
}
.quantity {
position: absolute;
bottom: 0;
right: 0;
line-height: 1;
padding: 6rpx 4rpx 6rpx 8rpx;
font-size: 24rpx;
color: #fff;
border-radius: 10rpx 0 0 0;
background-color: rgba(0, 0, 0, 0.6);
}
.meta {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-around;
&-spec {
font-size: 24rpx;
color: #999;
margin-top: 10rpx;
max-width: 90%;
}
&-quantity {
position: absolute;
right: 20px;
font-size: 24rpx;
color: #9e9c9c;
}
}
.name {
font-size: 26rpx;
color: #444;
max-width: 90%;
}
}
.detail {
display: flex;
flex-direction: column;
color: #999;
font-size: 28rpx;
}
.detail-item {
display: flex;
justify-content: space-between;
padding: 5rpx;
}
.amount {
color: red;
}
.action {
display: flex;
justify-content: flex-end;
align-items: center;
padding-top: 20rpx;
.pay-time {
font-size: 24rpx;
color: #999;
margin-right: 20rpx;
}
.countdown {
font-size: 24rpx;
color: #999;
margin-left: auto;
display: flex;
}
.button {
width: 130rpx;
height: 60rpx;
display: flex;
justify-content: center;
align-items: center;
margin-left: 20rpx;
border-radius: 60rpx;
font-size: 26rpx;
color: #444;
}
.secondary {
color: #ff5f3c;
border-color: #ff5f3c;
}
.primary {
color: #fff;
background-color: #ff5f3c;
}
}
.loading-text {
text-align: center;
font-size: 28rpx;
color: #666;
padding: 20rpx 0;
}
}
.block {
display: flex;
flex-direction: column;
padding: 20rpx;
margin: 20rpx 20rpx 0;
border-radius: 10rpx;
background-color: #fff;
}
.address view {
margin-bottom: 20rpx;
font-size: 24rpx;
}
.history view {
margin-bottom: 20rpx;
}
.images-container {
display: flex;
image {
width: 150rpx;
height: 120rpx;
}
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 20rpx;
}
.title {
font-size: 35rpx;
}
.copy-button {
display: flex;
height: 50rpx;
font-size: 24rpx;
margin-right: initial;
align-items: center;
}
.remark {
padding: 10rpx 0 10rpx 0;
font-size: 24rpx;
color: #999;
line-height: 1.5;
}
.sub-button {
background-color: #ff5f3c;
color: white;
border-radius: 20rpx;
text-align: center;
cursor: pointer;
margin: 20rpx;
}
</style>

View File

@ -1,293 +0,0 @@
<script setup lang="ts">
import { onLoad, onPageScroll, onReachBottom, onShow } from '@dcloudio/uni-app'
import useZPaging from '@/uni_modules/z-paging/components/z-paging/js/hooks/useZPaging.js'
import { nextTick, ref } from 'vue'
import type { TabItem } from '@/types/global'
import { getOrderRefundListApi } from '@/api/order'
import { fullUrl } from '@/utils/common'
import type { orderRefundListResult } from '@/types/order'
import { pageUrl } from '@/utils/constants'
const paging = ref()
useZPaging(paging)
const props = defineProps<{
status: string
}>()
const tabList = ref<TabItem[]>([
{ name: '全部', value: '' },
{ name: '待处理', value: 1 },
])
const dataList = ref<orderRefundListResult[]>([])
const queryList = (page: number, page_size: number) => {
const params = {
page,
page_size,
status: tabList.value[tabIndex.value].value,
}
getOrderRefundListApi(params).then((res) => {
paging.value.complete(res.result.data)
})
}
const tabIndex = ref(Number(props.status) || 0)
const tabsChange = (index: number) => {
tabIndex.value = index
paging.value.refresh()
}
onShow(() => {
nextTick(() => {
paging.value.refresh()
})
})
</script>
<template>
<view class="list">
<z-paging
ref="paging"
v-model="dataList"
use-page-scroll
@query="queryList"
:safe-area-inset-bottom="true"
:auto-show-back-to-top="true"
:auto="false"
>
<template #top>
<z-tabs
:list="tabList"
@change="tabsChange"
:current="tabIndex"
:badge-style="{ 'background-color': '#c3c3c3' }"
/>
</template>
<view class="order">
<view class="card" v-for="(item, index) in dataList" :key="index">
<view class="status">
<text class="date">{{ item.create_time }}</text>
<text class="text">{{ item.status_text }}</text>
</view>
<navigator
class="goods"
hover-class="none"
:url="`${pageUrl['order-refund-detail']}?id=${item.id}&data=${JSON.stringify(item)}`"
>
<image class="cover" :src="fullUrl(item.detail.image)"></image>
<view class="meta">
<view class="name ellipsis">{{ item.detail.goods_name }}</view>
<view class="meta-spec">{{ item.detail.spec }}</view>
<view class="meta-quantity">x{{ item.refund_num }}</view>
</view>
</navigator>
</view>
<!-- 占位符 -->
<view style="padding-bottom: 10rpx"></view>
</view>
</z-paging>
</view>
</template>
<style lang="scss">
.list {
background-color: #f6f6f6;
}
.order {
height: 100%;
display: flex;
flex-direction: column;
background-color: #f6f6f6;
.card {
min-height: 100rpx;
padding: 20rpx;
margin: 20rpx 20rpx 0;
border-radius: 10rpx;
background-color: #fff;
&:last-child {
padding-bottom: 40rpx;
}
}
.status {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 28rpx;
color: #999;
margin-bottom: 15rpx;
.date {
color: #666;
flex: 1;
}
.text {
color: red;
}
.primary {
color: #ff9240;
}
.icon-delete {
line-height: 1;
margin-left: 10rpx;
padding-left: 10rpx;
border-left: 1rpx solid #e3e3e3;
}
}
.goods {
display: flex;
margin-bottom: 20rpx;
.cover {
width: 170rpx;
height: 170rpx;
margin-right: 20rpx;
border-radius: 10rpx;
overflow: hidden;
position: relative;
}
.quantity {
position: absolute;
bottom: 0;
right: 0;
line-height: 1;
padding: 6rpx 4rpx 6rpx 8rpx;
font-size: 24rpx;
color: #fff;
border-radius: 10rpx 0 0 0;
background-color: rgba(0, 0, 0, 0.6);
}
.meta {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-around;
&-spec {
font-size: 24rpx;
color: #999;
margin-top: 10rpx;
max-width: 90%;
}
&-quantity {
position: absolute;
right: 20px;
font-size: 24rpx;
color: #9e9c9c;
}
}
.name {
font-size: 26rpx;
color: #444;
max-width: 90%;
}
.type {
line-height: 1.8;
padding: 0 15rpx;
margin-top: 10rpx;
font-size: 24rpx;
align-self: flex-start;
border-radius: 4rpx;
color: #888;
background-color: #f7f7f8;
}
.more {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
font-size: 22rpx;
color: #333;
}
}
.payment {
display: flex;
// justify-content: flex-end;
align-items: center;
line-height: 1;
padding: 20rpx 0;
text-align: right;
color: #999;
font-size: 28rpx;
.quantity {
font-size: 24rpx;
margin-right: 16rpx;
}
.amount {
color: #444;
margin-left: 6rpx;
}
.symbol {
font-size: 20rpx;
}
}
.action {
display: flex;
justify-content: flex-end;
align-items: center;
padding-top: 20rpx;
.pay-time {
font-size: 24rpx;
color: #999;
margin-right: 20rpx;
}
.countdown {
font-size: 24rpx;
color: #999;
margin-left: auto;
display: flex;
}
.button {
width: 130rpx;
height: 60rpx;
display: flex;
justify-content: center;
align-items: center;
margin-left: 20rpx;
border-radius: 60rpx;
font-size: 26rpx;
color: #444;
}
.secondary {
color: #ff5f3c;
border-color: #ff5f3c;
}
.primary {
color: #fff;
background-color: #ff5f3c;
}
}
.loading-text {
text-align: center;
font-size: 28rpx;
color: #666;
padding: 20rpx 0;
}
}
</style>

View File

@ -1,362 +0,0 @@
<script setup lang="ts">
import { fullUrl } from '@/utils/common'
import { safeBottom } from '@/utils/constants'
import { postOrderRefundApi } from '@/api/order'
import { computed, reactive } from 'vue'
import { ref } from 'vue'
import { pageUrl } from '@/utils/constants'
import { uploadApi } from '@/api/common'
const props = defineProps<{
order_id: number
detail_data?: string
}>()
const detailData = JSON.parse(props.detail_data ?? '{}')
const refundTypeArr = [
{ value: 1, text: '退货退款' },
{ value: 2, text: '换货' },
]
const tmpPathArr = ref<string[]>([])
const imagePathArr = ref<string[]>([])
const onSelect = (e: UniHelper.UniFilePickerOnSelectEvent) => {
tmpPathArr.value = tmpPathArr.value.concat(e.tempFilePaths)
}
const onDelete = (e: UniHelper.UniFilePickerOnDeleteEvent) => {
//
const index = tmpPathArr.value.findIndex((item) => item === e.tempFilePath)
tmpPathArr.value.splice(index, 1)
}
const applyForm = reactive({
refund_type: 1,
refund_num: 1,
apply_money: detailData.pay_price,
remark: '',
remark_images: [],
})
const maxApplyNum = computed(() => {
return parseFloat(detailData.num)
})
const maxApplyAmount = computed(() => {
return parseFloat(detailData.pay_price)
})
//
const onSubmit = async () => {
if (!applyForm.remark) {
uni.showToast({
title: '请填写申请说明',
icon: 'none',
})
return
}
const uploadPromises = tmpPathArr.value.map((item) => {
return new Promise((resolve) => {
uploadApi(item).then((res) => {
imagePathArr.value.push(res.result.url)
resolve(res)
})
})
})
try {
await Promise.all(uploadPromises)
// 退
const res = await postOrderRefundApi({
id: props.order_id,
detail_id: detailData.id,
refund_type: applyForm.refund_type,
refund_num: applyForm.refund_num,
remark: applyForm.remark,
apply_money: applyForm.apply_money,
remark_images: imagePathArr.value.join(','),
})
if (res.result) {
uni.showToast({
title: '申请成功,请等待商家处理',
icon: 'none',
mask: true,
})
}
setTimeout(() => {
uni.navigateBack()
}, 1500)
} catch (error) {
console.error(error)
}
}
</script>
<template>
<view class="panel goods">
<view class="item">
<navigator
class="navigator"
:url="`${pageUrl['goods-detail']}?id=${detailData.goods_id}`"
hover-class="none"
>
<image class="cover" :src="fullUrl(detailData.image)"></image>
<view class="meta">
<view class="name ellipsis">{{ detailData.goods_name }}</view>
<view class="type">{{ detailData.spec_text }}</view>
<view class="price">
<view class="actual">
<text class="symbol">¥</text>
<text>{{ detailData.price }}</text>
</view>
</view>
<view class="quantity">x{{ detailData.num }}</view>
</view>
</navigator>
</view>
</view>
<view class="panel">
<view class="panel-item">
<view class="item-title">
<text>售后类型:</text>
</view>
<view class="content">
<uni-data-checkbox mode="tag" v-model="applyForm.refund_type" :localdata="refundTypeArr" />
</view>
</view>
<view class="panel-item">
<view class="item-title">
<text>售后数量:</text>
</view>
<view class="content">
<uni-number-box v-model="applyForm.refund_num" :min="1" :max="maxApplyNum" />
</view>
</view>
<view class="panel-item" v-if="applyForm.refund_type == 1">
<view class="item-title">
<text>申请金额:</text>
</view>
<view class="content">
<uni-number-box v-model="applyForm.apply_money" :min="0.01" :max="maxApplyAmount" />
</view>
</view>
</view>
<view class="panel">
<view class="panel-item2">
<view class="item-title">申请说明</view>
<view class="content">
<uni-easyinput
type="textarea"
v-model="applyForm.remark"
placeholder="描述清晰有助于商家快速处理售后问题"
/>
</view>
</view>
<view class="panel-item2">
<view class="item-title">上传凭证(最多6张)</view>
<view class="content">
<uni-file-picker
class="image"
v-model="applyForm.remark_images"
file-mediatype="image"
mode="grid"
file-extname="png,jpg"
:limit="6"
return-type="array"
:auto-upload="false"
@select="onSelect"
@delete="onDelete"
/>
</view>
</view>
</view>
<view class="sub-button" :style="safeBottom">
<button @tap="onSubmit">提交申请</button>
</view>
</template>
<style lang="scss">
page {
background-color: #f7f7f8;
}
.panel {
margin: 20rpx 20rpx;
padding: 10rpx 20rpx;
border-radius: 20rpx;
background-color: #fff;
.panel-item {
display: flex; // 使flex
align-items: center; //
justify-content: space-between; //
padding: 10rpx 0;
margin-bottom: 20rpx;
}
.item-title {
font-size: 26rpx;
}
.content {
margin-bottom: 10rpx;
}
.price-value {
color: red;
}
.panel-item2 {
.item-title {
padding: 10rpx 0 30rpx;
}
}
.image {
padding: 10rpx 0 10rpx 0;
.uni-file-picker {
margin-top: 20rpx;
}
}
}
.goods {
.item {
.navigator {
display: flex;
margin: 20rpx 0;
}
.cover {
width: 170rpx;
height: 170rpx;
border-radius: 10rpx;
margin-right: 20rpx;
}
.meta {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-around;
position: relative;
}
.name {
font-size: 26rpx;
color: #444;
}
.type {
line-height: 1.8;
padding: 0 15rpx;
margin-top: 6rpx;
font-size: 24rpx;
align-self: flex-start;
border-radius: 4rpx;
color: #888;
background-color: #f7f7f8;
max-width: 90%;
}
.price {
display: flex;
margin-top: 6rpx;
font-size: 24rpx;
}
.symbol {
font-size: 20rpx;
}
.original {
color: #999;
text-decoration: line-through;
}
.actual {
margin-left: 10rpx;
color: #444;
}
.text {
font-size: 22rpx;
}
.evaluate {
position: absolute;
right: 0;
font-size: 24rpx;
bottom: 50rpx;
.button {
width: 80rpx;
text-align: center;
justify-content: center;
margin-left: 20rpx;
border-radius: 60rpx;
border: 1rpx solid #ccc;
color: #444;
}
}
.quantity {
position: absolute;
right: 0;
font-size: 24rpx;
color: #444;
}
.action {
display: flex;
flex-direction: row-reverse;
justify-content: flex-start;
padding: 30rpx 0 0;
.button {
width: 200rpx;
height: 60rpx;
text-align: center;
justify-content: center;
line-height: 60rpx;
margin-left: 20rpx;
border-radius: 60rpx;
border: 1rpx solid #ccc;
font-size: 26rpx;
color: #444;
}
.primary {
color: #ff5f3c;
border-color: #ff5f3c;
}
}
}
}
.sub-button {
display: flex;
justify-content: center;
align-items: center;
position: absolute;
bottom: 0;
width: 100%;
.button-container {
background-color: white;
padding: 10rpx;
border-radius: 30rpx;
width: 100%;
}
button {
width: 100%;
background-color: #ff5f3c;
color: white;
border-radius: 20rpx;
text-align: center;
cursor: pointer;
margin: 20rpx;
}
}
</style>