优化京东购物模块

This commit is contained in:
tangxinyue 2026-04-27 09:19:48 +08:00
parent 8c39d28ff0
commit e6aa5f29ed
11 changed files with 232 additions and 63 deletions

View File

@ -3,8 +3,14 @@
<!-- Header -->
<view class="card-header">
<view class="shop-info">
<view class="tag self-tag" v-if="item.shopType === 'self'">自营</view>
<view class="tag waimai-tag" v-if="item.shopType === 'waimai'">外卖</view>
<!-- <view class="">
</view> -->
<image v-if="item.shopType === 'self'" style="width: 50rpx;margin-right: 12rpx;border-radius: 4rpx;"
src="/static/image/shopping/jingdong/detail/ziyin.png" mode="widthFix"></image>
<image v-if="item.shopType === 'waimai'" style="width: 50rpx;margin-right: 8rpx;border-radius: 4rpx;"
src="/static/image/shopping/jingdong/waimai/waimai.png" mode="widthFix"></image>
<!-- <view class="tag waimai-tag">外卖</view> -->
<image class="jd-tag" v-if="item.shopType === 'jd'" src="/static/image/shopping/jingdong/JD.png">
</image>
<text class="shop-name">{{ item.shopName }}</text>
@ -72,7 +78,8 @@
<template v-else>
<image class="product-img" :src="item.image" mode="aspectFill"></image>
<view class="product-info">
<text class="product-title">{{ item.title }}</text>
<text class="product-title" :class="{ 'shopping-title': item.shopType !== 'waimai' }">{{
truncatedTitle }}</text>
<text class="product-desc" v-if="item.desc">{{ item.desc }}</text>
<view class="product-tags" v-if="item.tags && item.tags.length">
<text class="tag" v-for="(tag, index) in item.tags" :key="index">{{ tag }}</text>
@ -83,7 +90,8 @@
:class="{ 'flex-1': item.products && item.products.length === 1 }">
<view class="product-info flex-1">
<template v-if="item.products && item.products.length === 1">
<text class="product-title">{{ item.products[0].title }}</text>
<text class="product-title" :class="{ 'shopping-title': item.shopType !== 'waimai' }">{{
truncatedTitle }}</text>
<text class="product-desc" v-if="item.products[0].desc">{{ item.products[0].desc }}</text>
<view class="product-tags" v-if="item.products[0].service">
<text class="tag">{{ item.products[0].service }}</text>
@ -157,7 +165,7 @@
</template>
<script setup>
import { defineProps, computed, defineEmits } from 'vue';
import { defineProps, defineEmits, ref, onMounted, watch, getCurrentInstance } from 'vue';
const props = defineProps({
item: {
@ -173,6 +181,62 @@ const onLongPress = (e) => {
emit('longpress', { event: e, item: props.item });
};
// --- JS ---
const instance = getCurrentInstance();
const truncatedTitle = ref('');
const calculateFitTitle = () => {
const originalTitle = props.item.products?.[0]?.title || props.item.title || '';
if (props.item.shopType === 'waimai') {
truncatedTitle.value = originalTitle;
return;
}
setTimeout(() => {
const query = uni.createSelectorQuery().in(instance);
//
query.select('.product-info').boundingClientRect(res => {
if (res && res.width) {
const containerWidth = res.width;
const fontSize = uni.upx2px(26); // 26rpx
let currentWidth = 0;
let cutIndex = 0;
let found = false;
for (let i = 0; i < originalTitle.length; i++) {
const char = originalTitle[i];
const charCode = char.charCodeAt(0);
let weight = 1;
if (charCode <= 255) {
if (/[A-Z]/.test(char)) weight = 0.7;
else if (/[a-z]/.test(char)) weight = 0.5;
else if (/[0-9]/.test(char)) weight = 0.55;
else if (char === ' ') weight = 0.25;
else weight = 0.5;
}
currentWidth += weight * fontSize;
if (currentWidth > containerWidth - uni.upx2px(4)) {
cutIndex = i;
found = true;
break;
}
}
truncatedTitle.value = found ? originalTitle.slice(0, cutIndex - 1) : originalTitle;
} else {
truncatedTitle.value = originalTitle;
}
}).exec();
}, 200);
};
onMounted(calculateFitTitle);
watch(() => props.item, calculateFitTitle, { deep: true, immediate: true });
// --- JS ---
const formatWaimaiStatusDesc = (desc) => {
if (!desc || !desc.includes(':')) return desc;
const parts = desc.split(':');
@ -533,8 +597,13 @@ const getButtons = (shopType, status, item) => {
color: #1A1A1A;
line-height: 36rpx;
// white-space: nowrap;
text-overflow: hidden;
text-overflow: clip;
overflow: hidden;
&.shopping-title {
width: 100%;
white-space: nowrap;
}
}
.product-desc {

14
main.js
View File

@ -16,18 +16,20 @@ app.$mount()
import {
createSSRApp
} from 'vue'
import {
store,
useStore
} from './store'
export function createApp() {
const app = createSSRApp(App)
// 从缓存读取系统信息已在App.vue中获取
app.config.globalProperties.$pageData = pageData
const systemInfo = uni.getStorageSync('systemInfo') || {}
let systemInfo = uni.getStorageSync('systemInfo') || {}
if (!systemInfo.platform) {
systemInfo = uni.getSystemInfoSync() || {}
}
app.config.globalProperties.$system = systemInfo.platform == 'ios' ? 'iOS' : 'Android'
// #ifdef APP-PLUS
app.config.globalProperties.$system = plus.os.name;
// #endif
app.config.globalProperties.$systemInfo = systemInfo
uni.setStorageSync('version', '1.0.4.sp13')
uni.setStorageSync('version', '1.0.4.sp17')
app.config.globalProperties.$version = uni.getStorageSync('version')
app.use(globalMethods);
return {

View File

@ -1036,7 +1036,7 @@ async function onConfirm() {
});
setTimeout(() => {
uni.navigateBack();
}, 1500);
}, 500);
}
const onShopTypeChange = (e) => {

View File

@ -1186,7 +1186,7 @@ const onConfirm = () => {
});
setTimeout(() => {
uni.navigateBack();
}, 1500);
}, 500);
}
</script>

View File

@ -1070,7 +1070,7 @@
"name": "五粮液",
"title": "五粮液 普五 第八代 浓香型白酒 52度 500ml*2瓶",
"desc": "500ml*2礼盒装浓香型",
"service": "211限时达",
"service": "七天无理由退货 ",
"price": "1099.00",
"count": "2"
}

View File

@ -25,9 +25,10 @@
<view class="tab-list">
<view class="tab-item" v-for="(item, index) in tabList" :key="index"
:class="{ active: currentTab === index }" @click="switchTab(index)">
<text class="title">{{ item.name }}</text>
<view class="title">{{ item.name }}</view>
<view class="line" v-if="currentTab === index"></view>
<image v-if="item.icon" class="badge-icon" :src="item.icon" mode="heightFix"></image>
<image v-if="item.icon" class="badge-icon" :class="{ 'android-m-top': $system == 'Android' }"
:src="item.icon" mode="heightFix"></image>
</view>
</view>
<view class="filter-wrapper">
@ -756,14 +757,14 @@ const mockOrderList = ref([]);
.tab-item {
position: relative;
display: flex;
align-items: center;
align-items: flex-start;
margin-right: 58rpx;
.title {
font-size: 28rpx;
color: #1A1A1A;
font-weight: 700;
line-height: 28rpx;
line-height: 16px !important;
}
&.active {
@ -790,10 +791,13 @@ const mockOrderList = ref([]);
}
.badge-icon {
position: relative;
top: 0;
margin-left: 2rpx;
height: 32rpx;
width: 30px;
height: 16px;
&.android-m-top {
margin-top: -3rpx;
}
}
}
}

View File

@ -167,7 +167,7 @@
</image>
<text class="title">{{ order.shopName }}</text>
</view>
<uni-icons class="right-icon" size="14" color="#1A1A1A" type="right"></uni-icons>
<uni-icons class="right-icon" size="12" color="#1A1A1A" type="right"></uni-icons>
</view>
<view class="product-info">
<view class="image-box">
@ -176,13 +176,30 @@
</view>
<view class="info-box">
<view class="name">{{ order.products && order.products[0]?.title }}</view>
<view class="desc">{{ order.products && order.products[0]?.desc }}</view>
<view style="margin-top: 16rpx;" class=" w100 flex flex-justify-between">
<view class="flex-1" style="position: relative; overflow: hidden;">
<text id="desc-visible" class="desc"
:style="{ 'max-height': isDescExpanded ? 'none' : '32rpx' }"
style="display: block; text-overflow: clip;">
{{ order.products && order.products[0]?.desc }}
</text>
<!-- 测量层必须继承 desc 类以同步样式 -->
<view id="desc-measure" class="desc desc-measure"
style="position: absolute; top: 0; left: 0; visibility: hidden; pointer-events: none; width: 100%; max-height: none;">
{{ order.products && order.products[0]?.desc }}
</view>
</view>
<uni-icons v-if="hasDescOverflow" :type="isDescExpanded ? 'up' : 'down'" size="12"
color="#87868E" style="flex-shrink: 0;"
@click="isDescExpanded = !isDescExpanded"></uni-icons>
</view>
<view class="tag">{{ order.products && order.products[0]?.service }}</view>
</view>
<view class="price-box">
<view class="flex-align-center">
<view v-if="(order.status != '等待付款' && order.status != '已取消') && order.discount > 0"
class="flex-center" style="display: inline-block;">
<image style="width: 22rpx;height: 22rpx;margin-right: 4rpx;"
class="flex-center" style="display: inline-flex;">
<image style="width: 11px;height: 11px;margin-right: 4rpx;"
src="/static/image/shopping/jingdong/detail/help.png">
</image>
<text
@ -193,9 +210,11 @@
Number(order.products[0]?.price).toFixed(2) :
Number(order.products[0]?.price - order.discount).toFixed(2)
}}</text>
</view>
<view v-if="(order.status != '等待付款' && order.status != '已取消') && order.discount > 0"
class="wx-font-medium"
style="margin-top: 2rpx;text-align: right;color: #87868E;font-weight: 500;">
style="margin-top: 6rpx;text-align: right;color: #87868E;font-weight: 500;">
<text style="font-size: 24rpx;">{{ Number(order.products[0]?.price).toFixed(2) }}</text>
</view>
</view>
@ -218,7 +237,7 @@
</view>
</view>
<view v-else class="btn-box">
<view class="btn" :class="{ red: btn == '送朋友' }" v-for="btn in btnList">
<view class="btn" :class="{ red: btn == '送朋友' }" v-for="(btn, index) in btnList" :key="index">
<image v-if="btn == '送朋友'" class="icon" src="/static/image/shopping/jingdong/detail/gift.png">
</image>
{{ btn }}
@ -249,7 +268,7 @@
</view>
<view class="order-info">
<view class="item flex-justify-between" v-for="item in order.orderInfo"
<view class="item flex-justify-between" v-for="(item, index) in order.orderInfo" :key="index"
v-show="isOrderInfoExpanded || ['订单编号', '支付方式'].includes(item.label)"
@click="onClickItemInfo(item)">
<text class="label shrink-0 flex-align-center" style="color: #87868E;font-size: 26rpx;">{{
@ -271,7 +290,7 @@
</view>
</view>
<view class="delivery-info" v-if="isOrderInfoExpanded">
<view class="item flex-justify-between" v-for="item in order.sendType"
<view class="item flex-justify-between" v-for="(item, index) in order.sendType" :key="index"
@click="onClickItemInfo(item)">
<text class="label shrink-0" style="color: #87868E;font-size: 26rpx;">{{ item.label }}</text>
<view class="flex-1 flex-align-center"
@ -315,7 +334,7 @@
</view>
</view>
<view class="order-info">
<view class="item flex-justify-between" v-for="item in order.orderInfo"
<view class="item flex-justify-between" v-for="(item, index) in order.orderInfo" :key="index"
v-show="isOrderInfoExpanded || ['订单编号', '支付方式', '发票类型'].includes(item.label)"
@click="onClickItemInfo(item)">
<text class="label shrink-0" style="color: #87868E;font-size: 26rpx;">{{ item.label }}</text>
@ -334,7 +353,7 @@
</view>
</view>
<view class="delivery-info" v-if="isOrderInfoExpanded">
<view class="item flex-justify-between" v-for="item in order.sendType" @click="onClickItemInfo(item)">
<view class="item flex-justify-between" v-for="(item, index) in order.sendType" :key="index" @click="onClickItemInfo(item)">
<text class="label shrink-0" style="color: #87868E;font-size: 26rpx;">{{ item.label }}</text>
<view class="flex-1 flex-align-center"
style="justify-content: flex-end;width: 300rpx;overflow-x: auto;margin-left: 20rpx;">
@ -433,10 +452,12 @@
</view>
</template>
<view class="fixed-bottom flex-align-center flex-justify-between">
<view class="fixed-bottom ">
<view class="flex-align-center flex-justify-between w100">
<text>{{ bottomText }}</text>
<view class="btn-box">
<view v-if="order.status == '等待付款'" class="big-btn ">立即支付<text class="wx-font-medium money">¥69</text>
<view v-if="order.status == '等待付款'" class="big-btn ">立即支付<text
class="wx-font-medium money">¥69</text>
</view>
<view v-else class="flex-align-center w00">
<view v-for="btn in bottomBtns" :key="btn" class="bottom-btn " :class="{ red: btn == '再次购买' }">
@ -445,6 +466,8 @@
</view>
</view>
</view>
</view>
<view class="placeholder"></view>
<!-- 水印 -->
<view v-if="$isVip()">
@ -458,7 +481,7 @@
</template>
<script setup>
import { ref, computed, onUnmounted, watch } from 'vue';
import { ref, computed, onUnmounted, watch, onMounted, getCurrentInstance, reactive } from 'vue';
import { onLoad, onShow } from '@dcloudio/uni-app';
import { util } from '@/utils/common.js';
@ -473,6 +496,10 @@ const buttonGroup = [
},
]
const data = reactive({
id: 1
})
/**
* 模拟订单列表
*/
@ -491,6 +518,7 @@ const bottomBtns = computed(() => {
} else if (order.value.status == '已取消') {
return ['再次购买']
}
return [];
})
const bottomText = computed(() => {
@ -521,6 +549,7 @@ const order = ref({});
onLoad((options) => {
console.log('详情页参数:', options);
const id = options.id;
data.id = id
if (!id) return;
// 1.
@ -612,6 +641,27 @@ onShow(() => {
plus.navigator.setStatusBarStyle("dark");
}, 500);
// #endif
// 1.
const cachedList = uni.getStorageSync('jingdongShopping') || [];
let target = cachedList.find(item => String(item.id) === String(data.id));
// 2. mock
if (!target) {
target = mockOrderList.value.find(item => String(item.id) === String(data.id));
}
if (target) {
order.value = target;
console.log('成功匹配订单数据:', order.value);
//
if (order.value.status === '等待付款' && order.value.statusDesc) {
initCountdown();
}
} else {
console.error('未找到指定的订单数据, ID:', data.id);
}
})
// ()
@ -628,6 +678,45 @@ const formattedPhone = computed(() => {
// ()
const isOrderInfoExpanded = ref(false);
const isDescExpanded = ref(false);
const hasDescOverflow = ref(false);
const instance = getCurrentInstance();
const checkDescOverflow = () => {
//
setTimeout(() => {
const query = uni.createSelectorQuery().in(instance);
query.select('#desc-visible').boundingClientRect();
query.select('#desc-measure').boundingClientRect();
query.exec(res => {
if (res && res[0] && res[1]) {
const visibleH = res[0].height;
const measureH = res[1].height;
console.log('溢出检测高度对比:', visibleH, measureH);
//
if (measureH > visibleH + 1) {
hasDescOverflow.value = true;
} else {
hasDescOverflow.value = false;
}
}
});
}, 100);
};
onMounted(() => {
if (order.value?.products?.[0]?.desc) {
checkDescOverflow();
}
});
//
watch(() => order.value?.products?.[0]?.desc, (newVal) => {
if (newVal) {
checkDescOverflow();
}
});
</script>
<style>
@ -707,6 +796,7 @@ const isOrderInfoExpanded = ref(false);
color: #8C8C8C;
font-size: 26rpx;
line-height: 42rpx;
margin-left: 42rpx;
}
.edit-btn {
@ -1043,12 +1133,12 @@ const isOrderInfoExpanded = ref(false);
.title-box {
display: flex;
align-items: center;
justify-content: space-between;
// justify-content: space-between;
.left-box {
display: flex;
align-items: center;
flex: 1;
// flex: 1;
.img {
width: 50rpx;
@ -1071,7 +1161,7 @@ const isOrderInfoExpanded = ref(false);
.right-icon {
margin-left: 12rpx;
// margin-left: 12rpx;
}
}
@ -1104,13 +1194,11 @@ const isOrderInfoExpanded = ref(false);
}
.desc {
margin-top: 16rpx;
font-size: 22rpx;
color: #87868E;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
line-height: 32rpx;
overflow: hidden;
text-overflow: clip;
}
.tag {
@ -1124,6 +1212,7 @@ const isOrderInfoExpanded = ref(false);
font-weight: 500;
font-size: 28rpx;
color: #1A1A1A;
line-height: 28rpx;
.price {
font-size: 700;
@ -1253,10 +1342,14 @@ const isOrderInfoExpanded = ref(false);
.tag-box {
margin-top: 32rpx;
overflow: hidden;
overflow-x: scroll;
scrollbar-width: none;
-ms-overflow-style: none;
.tag {
background-color: #F5F6FA;
border-radius: 16rpx;
white-space: nowrap;
height: 68rpx;
line-height: 68rpx;
font-size: 26rpx;
@ -1417,6 +1510,7 @@ const isOrderInfoExpanded = ref(false);
min-height: 90rpx;
min-height: calc(90rpx + env(safe-area-inset-bottom));
min-height: calc(90rpx + constant(safe-area-inset-bottom));
padding-bottom: 16rpx;
background-color: #FFFFFF;
border-top: 0.5px solid #F2F2F2;

View File

@ -200,7 +200,7 @@
</image>
<text class="title">{{ order.shopName }}</text>
</view>
<uni-icons class="right-icon" size="14" color="#1A1A1A" type="right"></uni-icons>
<uni-icons class="right-icon" size="12" color="#1A1A1A" type="right"></uni-icons>
</view>
<view class="product-info flex-align-center" v-for="(item, i) in order.products" :key="i">
<view class="image-box shrink-0">
@ -657,7 +657,7 @@ const loadOrderDetail = (id) => {
.right-icon-box {
margin-right: 12rpx;
// margin-right: 12rpx;
.icon {
width: 40rpx;
@ -1025,12 +1025,12 @@ page {
.title-box {
display: flex;
align-items: center;
justify-content: space-between;
// justify-content: space-between;
.left-box {
display: flex;
align-items: center;
flex: 1;
// flex: 1;
.img {
width: 50rpx;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 802 B

After

Width:  |  Height:  |  Size: 769 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB