wangfukang 19 hours ago
parent
commit
aed1d1c8db
  1. 16
      package2/group/groupBuyDetail.vue
  2. 127
      package2/group/groupBuyList.vue
  3. 467
      package2/group/groupBuySingle.vue
  4. 100
      package2/group/studentStoreList.vue

16
package2/group/groupBuyDetail.vue

@ -113,6 +113,22 @@
computed: { computed: {
businessTimeText() { businessTimeText() {
const takeaway = this.shopItem.shopTakeaway || {}; const takeaway = this.shopItem.shopTakeaway || {};
let list = takeaway.businessHourList;
if ((!list || !list.length) && takeaway.businessHours) {
try {
list = typeof takeaway.businessHours === 'string' ? JSON.parse(takeaway.businessHours) : takeaway.businessHours;
} catch (e) {
list = [];
}
}
if (list && list.length) {
const parts = list.filter(item => item && item.begin && item.end).map(item => {
const begin = String(item.begin).substring(0, 5);
const end = String(item.end).substring(0, 5);
return begin + '-' + end;
});
if (parts.length) return '周一到周日 ' + parts.join('、');
}
if (!takeaway.businessHourBegin || !takeaway.businessHourEnd) return '营业时间暂无'; if (!takeaway.businessHourBegin || !takeaway.businessHourEnd) return '营业时间暂无';
return '周一到周日 ' + takeaway.businessHourBegin + '-' + takeaway.businessHourEnd; return '周一到周日 ' + takeaway.businessHourBegin + '-' + takeaway.businessHourEnd;
}, },

127
package2/group/groupBuyList.vue

@ -108,6 +108,10 @@
<view class="shop-list"> <view class="shop-list">
<view class="shop-member" :class="{'shop-member-disabled': item.status != 1}" v-for="(item,index) in shopList" :key="item._renderKey" @tap="goDetail('shop',item)"> <view class="shop-member" :class="{'shop-member-disabled': item.status != 1}" v-for="(item,index) in shopList" :key="item._renderKey" @tap="goDetail('shop',item)">
<view class="shop-disabled-mask" @tap.stop="buyingye" v-if="item.status != 1"> <view class="shop-disabled-mask" @tap.stop="buyingye" v-if="item.status != 1">
<view class="closed-badge">
<text class="closed-title">休息中</text>
<text class="closed-hours" v-if="getBusinessHoursText(item)">营业时间 {{getBusinessHoursText(item)}}</text>
</view>
</view> </view>
<view class="shop-card-bg"></view> <view class="shop-card-bg"></view>
<view class="shop-top"> <view class="shop-top">
@ -149,7 +153,10 @@
</view> </view>
<view class="shop-content-bottom"> <view class="shop-content-bottom">
<view class="shop-deal"> <view class="shop-deal">
<text v-if="item.subtitle != null"> <text v-if="hasStartingPrice(item)">
{{formatAmountText(item.startingPrice)}}元起送
</text>
<text v-else-if="item.subtitle != null">
{{item.subtitle}} {{item.subtitle}}
</text> </text>
<text v-else>{{item._groupTip}}</text> <text v-else>{{item._groupTip}}</text>
@ -795,6 +802,10 @@
if (isNaN(amount)) return '0' if (isNaN(amount)) return '0'
return amount.toFixed(2).replace(/\.?0+$/, '') return amount.toFixed(2).replace(/\.?0+$/, '')
}, },
hasStartingPrice(item) {
const amount = item ? parseFloat(item.startingPrice) : 0
return !isNaN(amount) && amount > 0
},
hasOpenKitchenVideo(item) { hasOpenKitchenVideo(item) {
return !!(item && item.openKitchenVideoUrl && String(item.openKitchenVideoUrl).trim()) return !!(item && item.openKitchenVideoUrl && String(item.openKitchenVideoUrl).trim())
}, },
@ -820,34 +831,82 @@
for (let i = 0; i < this.shopList.length; i++) { for (let i = 0; i < this.shopList.length; i++) {
if (this.shopList[i].shopTakeaway != '' && this.shopList[i].shopTakeaway != null) { if (this.shopList[i].shopTakeaway != '' && this.shopList[i].shopTakeaway != null) {
if (this.shopList[i].status == 1) { if (this.shopList[i].status == 1) {
let isEndTime = this.isWithinBusinessHours(this.shopList[i].shopTakeaway.businessHourBegin,this.shopList[i].shopTakeaway.businessHourEnd) let isEndTime = this.isShopWithinBusinessHours(this.shopList[i].shopTakeaway)
this.shopList[i].status = isEndTime ? 1 : 0 this.shopList[i].status = isEndTime ? 1 : 0
} }
} }
} }
}, },
// //
isWithinBusinessHours(begin, end) { isShopWithinBusinessHours(takeaway) {
// if (!takeaway) return false
const now = new Date(); const periods = this.parseBusinessHourPeriods(takeaway)
const currentMinutes = now.getHours() * 60 + now.getMinutes(); if (!periods.length) return false
const now = new Date()
// "HH:MM" const currentMinutes = now.getHours() * 60 + now.getMinutes()
for (let i = 0; i < periods.length; i++) {
if (this.isWithinBusinessHours(periods[i].begin, periods[i].end, currentMinutes)) {
return true
}
}
return false
},
parseBusinessHourPeriods(takeaway) {
let list = takeaway.businessHourList
if ((!list || !list.length) && takeaway.businessHours) {
try {
list = typeof takeaway.businessHours === 'string' ? JSON.parse(takeaway.businessHours) : takeaway.businessHours
} catch (e) {
list = []
}
}
if (list && list.length) {
return list.filter(item => item && item.begin && item.end).map(item => ({
begin: String(item.begin).substring(0, 5),
end: String(item.end).substring(0, 5)
}))
}
if (takeaway.businessHourBegin && takeaway.businessHourEnd) {
return [{
begin: String(takeaway.businessHourBegin).substring(0, 5),
end: String(takeaway.businessHourEnd).substring(0, 5)
}]
}
return []
},
getBusinessHoursText(item) {
const takeaway = item && item.shopTakeaway
if (!takeaway) return ''
const periods = this.parseBusinessHourPeriods(takeaway)
if (!periods.length) return ''
return periods.map(p => p.begin + '-' + p.end).join('、')
},
isWithinBusinessHours(begin, end, currentMinutes) {
const nowMinutes = currentMinutes != null ? currentMinutes : (() => {
const now = new Date()
return now.getHours() * 60 + now.getMinutes()
})()
const parseTime = (timeStr) => { const parseTime = (timeStr) => {
const [hours, minutes] = timeStr.split(':').map(Number); const parts = String(timeStr || '00:00').split(':').map(Number)
return hours * 60 + minutes; return (parts[0] || 0) * 60 + (parts[1] || 0)
}; }
const startMinutes = parseTime(begin)
const startMinutes = parseTime(begin); const endMinutes = parseTime(end)
const endMinutes = parseTime(end); if (startMinutes === endMinutes) return true
if (startMinutes < endMinutes) {
// return nowMinutes >= startMinutes && nowMinutes <= endMinutes
return currentMinutes >= startMinutes && currentMinutes <= endMinutes; }
//
return nowMinutes >= startMinutes || nowMinutes <= endMinutes
}, },
goDetail(type, item) { goDetail(type, item) {
console.log('数据',item) console.log('数据',item)
if (type == 'shop') { if (type == 'shop') {
if (item && item.status != 1) {
this.buyingye()
return
}
if (!requireLoginToCurrentPage()) return if (!requireLoginToCurrentPage()) return
uni.navigateTo({ uni.navigateTo({
url: '/package2/group/groupBuySingle?type=shop&item=' + encodeURIComponent(JSON.stringify(item)) url: '/package2/group/groupBuySingle?type=shop&item=' + encodeURIComponent(JSON.stringify(item))
@ -1524,6 +1583,38 @@
z-index: 20; z-index: 20;
border-radius: inherit; border-radius: inherit;
background: rgba(255, 255, 255, 0.58); background: rgba(255, 255, 255, 0.58);
display: flex;
align-items: center;
justify-content: center;
padding: 24rpx;
box-sizing: border-box;
}
.closed-badge {
max-width: 86%;
padding: 16rpx 28rpx;
border-radius: 24rpx;
background: rgba(255, 255, 255, 0.94);
box-shadow: 0 8rpx 24rpx rgba(19, 91, 70, 0.12);
display: flex;
flex-direction: column;
align-items: center;
}
.closed-title {
color: #19be6b;
font-size: 28rpx;
font-weight: 700;
line-height: 40rpx;
}
.closed-hours {
margin-top: 6rpx;
color: #666;
font-size: 22rpx;
line-height: 32rpx;
text-align: center;
word-break: break-all;
} }
.shop-member-disabled { .shop-member-disabled {

467
package2/group/groupBuySingle.vue

@ -36,8 +36,13 @@
style="width:30rpx;height:30rpx;position: absolute;top: 0;left: 0;background-size: 100%;" /> style="width:30rpx;height:30rpx;position: absolute;top: 0;left: 0;background-size: 100%;" />
</view> </view>
<view class="shop-content" @tap="goDetail('shopDetail')"> <view class="shop-content" @tap="goDetail('shopDetail')">
<view class="shop-name"> <view class="shop-name-row">
{{shopItem.shopName}} <view class="shop-name">
{{shopItem.shopName}}
</view>
<view class="shop-action-btn shop-share-action" @tap.stop="openShopSharePoster">
分享商家
</view>
</view> </view>
<view class="shop-subtitle"> <view class="shop-subtitle">
{{shopSubtitle}} {{shopSubtitle}}
@ -132,8 +137,7 @@
<view class="catalog-menu-wrap" :style="{ top: navBarHeight + 'px' }"> <view class="catalog-menu-wrap" :style="{ top: navBarHeight + 'px' }">
<scroll-view scroll-y id="menuList" class="menu-scroll" <scroll-view scroll-y id="menuList" class="menu-scroll"
:style="{ height: stickyInnerHeight + 'px' }"> :style="{ height: stickyInnerHeight + 'px' }">
<view class="menu1" @tap="checkTab(index)" v-for="(item,index) in menuList" :key="index" <view class="menu1" :class="{'menu1-active': item.checked}" @tap="checkTab(index)" v-for="(item,index) in menuList" :key="index">
:style="{'border-top-right-radius':item.checked?'20rpx':'','border-bottom-right-radius':item.checked?'20rpx':'','color':item.checked ? (isStoreGroupOrder ? '#f0642f' : 'rgba(0, 35, 28, 1)') : (isStoreGroupOrder ? '#9b6a4c' : '#777'),'background':item.checked ? (isStoreGroupOrder ? 'linear-gradient(90deg, #fff4e8, #fff)' : '#fff') : ''}">
<!-- <image class="menu-active-dot" v-if="item.checked" src="https://jewel-shop.oss-cn-beijing.aliyuncs.com/f7227759d693497a8beab87ff437a875.png" <!-- <image class="menu-active-dot" v-if="item.checked" src="https://jewel-shop.oss-cn-beijing.aliyuncs.com/f7227759d693497a8beab87ff437a875.png"
mode="aspectFit"></image> --> mode="aspectFit"></image> -->
<view class="menu-name">{{item.categoryName}}</view> <view class="menu-name">{{item.categoryName}}</view>
@ -207,12 +211,12 @@
</view> </view>
</view> </view>
<uni-load-more :status="loadStatus" @change="onChange" /> <uni-load-more :status="loadStatus" @change="onChange" />
<view style="width: 100%;height: 160rpx;"></view> <view :style="{width: '100%', height: showStartingPriceTip ? '190rpx' : '160rpx'}"></view>
</view> </view>
</view> </view>
</view> </view>
<view style="width: 100%;height: 160rpx;"></view> <view :style="{width: '100%', height: showStartingPriceTip ? '190rpx' : '160rpx'}"></view>
<view class="bottom checkout-bar"> <view class="bottom checkout-bar" :class="{'checkout-bar--with-starting-price': showStartingPriceTip}">
<view class="bottom-left"> <view class="bottom-left">
<view class="cart-bag-wrap" style="position: relative;width: 60rpx;height: 80rpx;margin-top: 20rpx;" <view class="cart-bag-wrap" style="position: relative;width: 60rpx;height: 80rpx;margin-top: 20rpx;"
@tap="openPopup('car','','')"> @tap="openPopup('car','','')">
@ -226,6 +230,9 @@
<view class="package-fee-tip" v-if="cartTotalPackageFeeNumber > 0"> <view class="package-fee-tip" v-if="cartTotalPackageFeeNumber > 0">
餐盒费{{cartTotalPackageFee}}(堂食会去掉) 餐盒费{{cartTotalPackageFee}}(堂食会去掉)
</view> </view>
<view class="delivery-start-tip" v-if="showStartingPriceTip">
外卖{{startingPriceText}}元起送
</view>
</view> </view>
</view> </view>
<view class="bottom-right checkout-btn" @tap="submitCartCheckout"> <view class="bottom-right checkout-btn" @tap="submitCartCheckout">
@ -302,6 +309,9 @@
<view class="package-fee-tip" v-if="cartTotalPackageFeeNumber > 0"> <view class="package-fee-tip" v-if="cartTotalPackageFeeNumber > 0">
含餐盒费{{cartTotalPackageFee}} 含餐盒费{{cartTotalPackageFee}}
</view> </view>
<view class="delivery-start-tip" v-if="showStartingPriceTip">
外卖{{startingPriceText}}元起送
</view>
</view> </view>
</view> </view>
<view class="bottom-right" @tap="submitCartCheckout"> <view class="bottom-right" @tap="submitCartCheckout">
@ -795,7 +805,23 @@
</view> </view>
</view> </view>
</uni-popup> </uni-popup>
<uni-popup ref="shopSharePopup" background-color="transparent" @change="onBottomPopupChange">
<view class="share-poster-popup" @tap.stop>
<view class="share-popup-close" @tap="$refs.shopSharePopup.close()">
<uni-icons type="closeempty" size="22" color="#26433d"></uni-icons>
</view>
<view class="share-popup-title">商家分享码</view>
<view class="share-popup-sub">保存图片或让朋友扫码直接进入当前商家</view>
<view class="share-poster-preview">
<view class="share-loading" v-if="sharePosterLoading">海报生成中...</view>
<image v-else-if="sharePosterImage" class="share-poster-image" :src="sharePosterImage" mode="widthFix" show-menu-by-longpress></image>
</view>
<view class="share-save-btn" @tap="saveSharePoster">保存图片</view>
</view>
</uni-popup>
<common-loading /> <common-loading />
<canvas canvas-id="shopShareCanvas" id="shopShareCanvas" class="share-canvas"
:style="{width: posterCanvasWidth + 'px', height: posterCanvasHeight + 'px'}"></canvas>
<view class="free-order-mask" v-if="freeOrderEffectVisible" @tap.stop="closeFreeOrderEffect"> <view class="free-order-mask" v-if="freeOrderEffectVisible" @tap.stop="closeFreeOrderEffect">
<view class="free-order-card" @tap.stop> <view class="free-order-card" @tap.stop>
<view class="free-order-rays"></view> <view class="free-order-rays"></view>
@ -908,7 +934,12 @@
bottomPopupOpenCount: 0, bottomPopupOpenCount: 0,
autoWaitRuleKnown: false, autoWaitRuleKnown: false,
hasAutoOpenedWaitPopup: false, hasAutoOpenedWaitPopup: false,
areaKind: 1 areaKind: 1,
sharePosterLoading: false,
sharePosterImage: '',
shareQrcodeUrl: '',
posterCanvasWidth: 320,
posterCanvasHeight: 450
} }
}, },
components: { components: {
@ -921,6 +952,12 @@
isStoreGroupOrder() { isStoreGroupOrder() {
return this.orderScene === 'storeGroup' || this.shopItem.merchantType == 2; return this.orderScene === 'storeGroup' || this.shopItem.merchantType == 2;
}, },
showStartingPriceTip() {
return !this.isStoreGroupOrder && this.hasStartingPrice(this.shopItem);
},
startingPriceText() {
return this.formatMoney(this.shopItem.startingPrice).replace(/\.?0+$/, '');
},
shopSubtitle() { shopSubtitle() {
if (this.isStoreGroupOrder) { if (this.isStoreGroupOrder) {
return this.isSocialArea return this.isSocialArea
@ -2137,6 +2174,10 @@
let amount = parseFloat(value); let amount = parseFloat(value);
return (isNaN(amount) ? 0 : amount).toFixed(2); return (isNaN(amount) ? 0 : amount).toFixed(2);
}, },
hasStartingPrice(item) {
let amount = item ? parseFloat(item.startingPrice) : 0;
return !isNaN(amount) && amount > 0;
},
getSpecDisplayString(specs) { getSpecDisplayString(specs) {
if (!specs) return ''; if (!specs) return '';
let arr = []; let arr = [];
@ -2449,6 +2490,236 @@
}); });
} }
}, },
openShopSharePoster() {
if (!this.shopItem || !this.shopItem.id) {
this.tui.toast('商家信息加载中,请稍后再试');
return;
}
this.sharePosterLoading = true;
this.sharePosterImage = '';
this.$refs.shopSharePopup.open('center');
this.generateShopSharePoster().catch((err) => {
this.sharePosterLoading = false;
this.tui.toast((err && err.message) || '生成分享图失败');
});
},
getShopShareQrcode() {
if (this.shareQrcodeUrl) return Promise.resolve(this.shareQrcodeUrl);
const scene = 's' + String(this.shopItem.id || '');
if (scene.length > 32) {
throw new Error('商家码参数过长,暂时无法生成');
}
return this.tui.request('/order/ow/getWechatQrcode', 'POST', {
scene: scene,
page: 'pages/index/index',
envVersion: 'release'
}, false, true).then((res) => {
if (res && res.code == 200 && res.result) {
this.shareQrcodeUrl = res.result;
return res.result;
}
throw new Error((res && res.message) || '小程序码生成失败');
});
},
getPosterImageInfo(src, required) {
if (!src) {
return required ? Promise.reject(new Error('图片加载失败')) : Promise.resolve(null);
}
return new Promise((resolve, reject) => {
uni.getImageInfo({
src: src,
success: resolve,
fail: () => {
if (required) {
reject(new Error('图片加载失败'));
} else {
resolve(null);
}
}
});
});
},
createRoundedPath(ctx, x, y, width, height, radius) {
const r = Math.min(radius, width / 2, height / 2);
ctx.beginPath();
ctx.moveTo(x + r, y);
ctx.lineTo(x + width - r, y);
ctx.arc(x + width - r, y + r, r, Math.PI * 1.5, Math.PI * 2);
ctx.lineTo(x + width, y + height - r);
ctx.arc(x + width - r, y + height - r, r, 0, Math.PI * 0.5);
ctx.lineTo(x + r, y + height);
ctx.arc(x + r, y + height - r, r, Math.PI * 0.5, Math.PI);
ctx.lineTo(x, y + r);
ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 1.5);
ctx.closePath();
},
fillRoundRect(ctx, x, y, width, height, radius, color) {
this.createRoundedPath(ctx, x, y, width, height, radius);
ctx.setFillStyle(color);
ctx.fill();
},
drawRoundImage(ctx, imagePath, x, y, width, height, radius) {
if (!imagePath) return;
ctx.save();
this.createRoundedPath(ctx, x, y, width, height, radius);
ctx.clip();
ctx.drawImage(imagePath, x, y, width, height);
ctx.restore();
},
drawWrappedText(ctx, text, x, y, maxWidth, lineHeight, maxLines) {
const chars = String(text || '').split('');
let line = '';
let lines = [];
for (let i = 0; i < chars.length; i++) {
const testLine = line + chars[i];
const metrics = ctx.measureText ? ctx.measureText(testLine) : { width: testLine.length * 14 };
if (metrics.width > maxWidth && line) {
lines.push(line);
line = chars[i];
} else {
line = testLine;
}
if (lines.length >= maxLines) break;
}
if (line && lines.length < maxLines) lines.push(line);
if (lines.length > maxLines) lines = lines.slice(0, maxLines);
if (lines.length === maxLines && chars.join('').length > lines.join('').length) {
lines[maxLines - 1] = lines[maxLines - 1].slice(0, Math.max(0, lines[maxLines - 1].length - 1)) + '...';
}
for (let i = 0; i < lines.length; i++) {
ctx.fillText(lines[i], x, y + i * lineHeight);
}
return lines.length;
},
generateShopSharePoster() {
return this.getShopShareQrcode().then((qrcodeUrl) => {
return Promise.all([
this.getPosterImageInfo(qrcodeUrl, true),
this.getPosterImageInfo(this.shopItem.shopIcon, false)
]);
}).then(([qrcodeInfo, shopIconInfo]) => {
const width = uni.upx2px(640);
const height = uni.upx2px(900);
this.posterCanvasWidth = width;
this.posterCanvasHeight = height;
return new Promise((resolve, reject) => {
this.$nextTick(() => {
setTimeout(() => {
try {
const canvasScope = this.$scope || this;
const ctx = uni.createCanvasContext('shopShareCanvas', canvasScope);
const bg = ctx.createLinearGradient(0, 0, width, height);
bg.addColorStop(0, '#f8fff2');
bg.addColorStop(0.52, '#e8fff7');
bg.addColorStop(1, '#fff7df');
ctx.setFillStyle(bg);
ctx.fillRect(0, 0, width, height);
ctx.setFillStyle('rgba(227, 255, 150, 0.58)');
ctx.beginPath();
ctx.arc(width - uni.upx2px(88), uni.upx2px(112), uni.upx2px(104), 0, Math.PI * 2);
ctx.fill();
ctx.setFillStyle('rgba(166, 255, 234, 0.52)');
ctx.beginPath();
ctx.arc(uni.upx2px(74), height - uni.upx2px(120), uni.upx2px(130), 0, Math.PI * 2);
ctx.fill();
ctx.save();
ctx.translate(width - uni.upx2px(130), height - uni.upx2px(72));
ctx.rotate(-0.38);
ctx.setFillStyle('rgba(255, 184, 84, 0.22)');
this.fillRoundRect(ctx, -uni.upx2px(150), -uni.upx2px(34), uni.upx2px(300), uni.upx2px(68), uni.upx2px(34), 'rgba(255, 184, 84, 0.22)');
ctx.restore();
ctx.setFillStyle('rgba(12, 75, 62, 0.08)');
for (let i = 0; i < 9; i++) {
ctx.beginPath();
ctx.arc(uni.upx2px(70 + i * 56), uni.upx2px(760 + (i % 3) * 22), uni.upx2px(5), 0, Math.PI * 2);
ctx.fill();
}
this.fillRoundRect(ctx, uni.upx2px(28), uni.upx2px(28), width - uni.upx2px(56), height - uni.upx2px(56), uni.upx2px(36), '#ffffff');
this.fillRoundRect(ctx, uni.upx2px(54), uni.upx2px(54), uni.upx2px(172), uni.upx2px(54), uni.upx2px(27), '#e3ff96');
ctx.setFillStyle('#0c4b3e');
ctx.setFontSize(uni.upx2px(26));
ctx.setTextAlign('center');
ctx.fillText('半径里', uni.upx2px(140), uni.upx2px(91));
ctx.setTextAlign('left');
ctx.setFillStyle('#153a35');
ctx.setFontSize(uni.upx2px(58));
ctx.fillText('拼团更省钱', uni.upx2px(54), uni.upx2px(184));
ctx.setFillStyle('#6a7a75');
ctx.setFontSize(uni.upx2px(26));
ctx.fillText(this.isSocialArea ? '同城好友一起拼,优惠一起拿' : '约上同学室友一起拼,越拼越划算', uni.upx2px(58), uni.upx2px(236));
const iconX = uni.upx2px(58);
const iconY = uni.upx2px(288);
const iconSize = uni.upx2px(118);
this.fillRoundRect(ctx, iconX, iconY, iconSize, iconSize, uni.upx2px(24), '#f1faf6');
if (shopIconInfo && shopIconInfo.path) {
this.drawRoundImage(ctx, shopIconInfo.path, iconX, iconY, iconSize, iconSize, uni.upx2px(24));
}
ctx.setFillStyle('#173a34');
ctx.setFontSize(uni.upx2px(34));
const shopNameLines = this.drawWrappedText(ctx, this.shopItem.shopName || '半径里商家', uni.upx2px(198), uni.upx2px(328), uni.upx2px(350), uni.upx2px(42), 2);
ctx.setFillStyle('#ff6a2a');
ctx.setFontSize(uni.upx2px(24));
ctx.fillText('评分 ' + (this.shopItem.shopScore || 5), uni.upx2px(198), uni.upx2px(328 + shopNameLines * 42 + 26));
this.fillRoundRect(ctx, uni.upx2px(66), uni.upx2px(480), width - uni.upx2px(132), uni.upx2px(288), uni.upx2px(34), '#f7fffb');
this.fillRoundRect(ctx, uni.upx2px(106), uni.upx2px(518), uni.upx2px(212), uni.upx2px(212), uni.upx2px(30), '#ffffff');
this.drawRoundImage(ctx, qrcodeInfo.path, uni.upx2px(126), uni.upx2px(538), uni.upx2px(172), uni.upx2px(172), uni.upx2px(18));
ctx.setFillStyle('#153a35');
ctx.setFontSize(uni.upx2px(34));
ctx.fillText('扫码立即省钱', uni.upx2px(344), uni.upx2px(586));
ctx.setFillStyle('#70807b');
ctx.setFontSize(uni.upx2px(24));
this.drawWrappedText(ctx, '打开半径里小程序,直接查看本店拼团优惠。', uni.upx2px(344), uni.upx2px(636), uni.upx2px(190), uni.upx2px(34), 3);
this.fillRoundRect(ctx, uni.upx2px(112), uni.upx2px(798), width - uni.upx2px(224), uni.upx2px(58), uni.upx2px(29), '#153a35');
ctx.setFillStyle('#ffffff');
ctx.setFontSize(uni.upx2px(24));
ctx.setTextAlign('center');
ctx.fillText('长按保存,分享给朋友一起省', width / 2, uni.upx2px(836));
ctx.draw(false, () => {
setTimeout(() => {
uni.canvasToTempFilePath({
canvasId: 'shopShareCanvas',
width: width,
height: height,
destWidth: width * 2,
destHeight: height * 2,
success: (res) => {
this.sharePosterImage = res.tempFilePath;
this.sharePosterLoading = false;
resolve(res.tempFilePath);
},
fail: reject
}, canvasScope);
}, 120);
});
} catch (e) {
reject(e);
}
}, 80);
});
});
});
},
saveSharePoster() {
if (!this.sharePosterImage) {
this.tui.toast('分享图还在生成中');
return;
}
uni.saveImageToPhotosAlbum({
filePath: this.sharePosterImage,
success: () => {
this.tui.toast('已保存到相册', 1200, true);
},
fail: () => {
this.tui.toast('保存失败,请检查相册权限');
}
});
},
fetchCoupons() { fetchCoupons() {
let userId = uni.getStorageSync('id'); let userId = uni.getStorageSync('id');
if (!userId) return Promise.resolve([]); if (!userId) return Promise.resolve([]);
@ -2636,9 +2907,20 @@
} }
.shop-name-row {
display: flex;
align-items: center;
gap: 14rpx;
}
.shop-name { .shop-name {
flex: 1;
min-width: 0;
font-size: 32rpx; font-size: 32rpx;
font-weight: 900; font-weight: 900;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} }
.shop-content-center { .shop-content-center {
@ -2718,6 +3000,99 @@
opacity: 0.86; opacity: 0.86;
} }
.shop-share-action {
background: linear-gradient(90deg, #fff5d6 0%, #e3ff96 100%);
color: #8a4b10;
border-color: rgba(255, 209, 108, 0.72);
}
.share-poster-popup {
position: relative;
width: 650rpx;
padding: 34rpx 26rpx 30rpx;
border-radius: 38rpx;
background:
radial-gradient(circle at 12% 4%, rgba(227, 255, 150, 0.48), rgba(227, 255, 150, 0) 190rpx),
linear-gradient(180deg, #ffffff 0%, #f7fffb 100%);
box-shadow: 0 24rpx 78rpx rgba(0, 35, 28, 0.18);
box-sizing: border-box;
text-align: center;
}
.share-popup-close {
position: absolute;
top: 20rpx;
right: 20rpx;
width: 52rpx;
height: 52rpx;
line-height: 52rpx;
border-radius: 50%;
background: rgba(243, 251, 247, 0.96);
}
.share-popup-title {
color: #153a35;
font-size: 34rpx;
font-weight: 900;
line-height: 46rpx;
}
.share-popup-sub {
margin-top: 8rpx;
color: #758681;
font-size: 22rpx;
font-weight: 700;
line-height: 32rpx;
}
.share-poster-preview {
width: 520rpx;
min-height: 660rpx;
margin: 26rpx auto 0;
border-radius: 32rpx;
background: #edf8f4;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
.share-loading {
color: #61736e;
font-size: 26rpx;
font-weight: 800;
}
.share-poster-image {
width: 520rpx;
display: block;
}
.share-save-btn {
width: 440rpx;
height: 82rpx;
line-height: 82rpx;
margin: 28rpx auto 0;
border-radius: 999rpx;
background: linear-gradient(90deg, #e3ff96 0%, #a6ffea 100%);
color: #153a35;
font-size: 28rpx;
font-weight: 900;
box-shadow: 0 14rpx 28rpx rgba(98, 229, 190, 0.24);
}
.share-save-btn:active {
transform: scale(0.97);
}
.share-canvas {
position: fixed;
left: -9999px;
top: -9999px;
z-index: -1;
pointer-events: none;
}
.shop-deal1 { .shop-deal1 {
flex-shrink: 0; flex-shrink: 0;
margin-left: 10rpx; margin-left: 10rpx;
@ -3390,25 +3765,33 @@
.menu1 { .menu1 {
width: 160rpx; width: 160rpx;
height: 90rpx; min-height: 88rpx;
position: relative; position: relative;
text-align: center; text-align: center;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 0 16rpx; padding: 10rpx 10rpx;
box-sizing: border-box; box-sizing: border-box;
line-height: 1.2; color: #6f7b77;
background: transparent;
line-height: 1.18;
overflow: hidden; overflow: hidden;
} }
.menu-name { .menu-name {
width: 100%; width: 138rpx;
max-height: 68rpx; max-height: 64rpx;
line-height: 34rpx; color: inherit;
font-size: 23rpx;
font-weight: 700;
line-height: 31rpx;
letter-spacing: 0;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: normal;
word-break: break-all; word-break: break-all;
overflow-wrap: break-word;
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: 2; -webkit-line-clamp: 2;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
@ -3815,9 +4198,11 @@
z-index: 50; z-index: 50;
flex-shrink: 0; flex-shrink: 0;
width: 160rpx; width: 160rpx;
background: rgba(247, 255, 251, 0.95); background: #f5f7f6;
border-right: 1rpx solid #eee; border-right: 0;
border-top-left-radius: 34rpx; border-top-left-radius: 34rpx;
border-bottom-left-radius: 34rpx;
overflow: hidden;
} }
.catalog-goods { .catalog-goods {
@ -3827,17 +4212,38 @@
} }
.menu-scroll { .menu-scroll {
background: rgba(247, 255, 251, 0.9); background: #f5f7f6;
border-right: 0 !important; border-right: 0 !important;
flex-shrink: 0; flex-shrink: 0;
width: 160rpx; width: 160rpx;
font-weight: 700;
font-size: 28rpx;
} }
.menu1 { .menu1 {
transition: transform 0.18s ease, background 0.18s ease, color 0.18s ease;
}
.menu1-active {
color: #173a34;
background: #fff;
border-radius: 0 24rpx 24rpx 0;
box-shadow: 0 10rpx 24rpx rgba(0, 35, 28, 0.05);
}
.menu1-active::before {
content: '';
position: absolute;
left: 0;
top: 50%;
width: 7rpx;
height: 36rpx;
border-radius: 0 8rpx 8rpx 0;
background: linear-gradient(180deg, #ffb84d 0%, #ff6a2a 100%);
transform: translateY(-50%);
}
.menu1-active .menu-name {
font-size: 24rpx;
font-weight: 900; font-weight: 900;
transition: transform 0.18s ease, background 0.18s ease;
} }
.menu1:active { .menu1:active {
@ -3986,6 +4392,10 @@
overflow: hidden; overflow: hidden;
} }
.checkout-bar--with-starting-price {
min-height: 142rpx;
}
.cart-bag-wrap { .cart-bag-wrap {
animation: bagBounce 2.2s ease-in-out infinite; animation: bagBounce 2.2s ease-in-out infinite;
} }
@ -4109,6 +4519,14 @@
line-height: 24rpx; line-height: 24rpx;
} }
.delivery-start-tip {
margin-top: 2rpx;
color: #ff6f2c;
font-size: 20rpx;
font-weight: 900;
line-height: 26rpx;
}
.cart-item-price { .cart-item-price {
flex: 1; flex: 1;
display: flex; display: flex;
@ -5233,6 +5651,15 @@
color: #9b6a4c; color: #9b6a4c;
} }
.store-group-page .menu1-active {
color: #f0642f;
background: #fff;
}
.store-group-page .menu1-active::before {
background: linear-gradient(180deg, #ffbf6b 0%, #f0642f 100%);
}
.store-group-page .menu-active-dot { .store-group-page .menu-active-dot {
filter: hue-rotate(148deg) saturate(1.5); filter: hue-rotate(148deg) saturate(1.5);
opacity: 0.9; opacity: 0.9;

100
package2/group/studentStoreList.vue

@ -83,7 +83,10 @@
<view class="shop-card community-shop-card" :class="{'shop-card-disabled': item.status != 1}" <view class="shop-card community-shop-card" :class="{'shop-card-disabled': item.status != 1}"
v-for="(item,index) in shopList" :key="index" @tap="goShop(item)"> v-for="(item,index) in shopList" :key="index" @tap="goShop(item)">
<view @tap.stop="buyingye" v-if="item.status != 1" class="closed-mask"> <view @tap.stop="buyingye" v-if="item.status != 1" class="closed-mask">
<view class="closed-text">休息中</view> <view class="closed-badge">
<text class="closed-title">休息中</text>
<text class="closed-hours" v-if="getBusinessHoursText(item)">营业时间 {{getBusinessHoursText(item)}}</text>
</view>
</view> </view>
<view class="shop-card-bg"></view> <view class="shop-card-bg"></view>
<view class="shop-top"> <view class="shop-top">
@ -452,19 +455,70 @@
judgeBusinessStatus() { judgeBusinessStatus() {
for (let i = 0; i < this.shopList.length; i++) { for (let i = 0; i < this.shopList.length; i++) {
const takeaway = this.shopList[i].shopTakeaway const takeaway = this.shopList[i].shopTakeaway
if (takeaway && takeaway.businessHourBegin && takeaway.businessHourEnd && this.shopList[i].status == 1) { if (takeaway && this.shopList[i].status == 1) {
this.shopList[i].status = this.isWithinBusinessHours(takeaway.businessHourBegin, takeaway.businessHourEnd) ? 1 : 0 this.shopList[i].status = this.isShopWithinBusinessHours(takeaway) ? 1 : 0
} }
} }
}, },
isWithinBusinessHours(begin, end) { isShopWithinBusinessHours(takeaway) {
if (!takeaway) return false
const periods = this.parseBusinessHourPeriods(takeaway)
if (!periods.length) return false
const now = new Date() const now = new Date()
const currentMinutes = now.getHours() * 60 + now.getMinutes() const currentMinutes = now.getHours() * 60 + now.getMinutes()
for (let i = 0; i < periods.length; i++) {
if (this.isWithinBusinessHours(periods[i].begin, periods[i].end, currentMinutes)) {
return true
}
}
return false
},
parseBusinessHourPeriods(takeaway) {
let list = takeaway.businessHourList
if ((!list || !list.length) && takeaway.businessHours) {
try {
list = typeof takeaway.businessHours === 'string' ? JSON.parse(takeaway.businessHours) : takeaway.businessHours
} catch (e) {
list = []
}
}
if (list && list.length) {
return list.filter(item => item && item.begin && item.end).map(item => ({
begin: String(item.begin).substring(0, 5),
end: String(item.end).substring(0, 5)
}))
}
if (takeaway.businessHourBegin && takeaway.businessHourEnd) {
return [{
begin: String(takeaway.businessHourBegin).substring(0, 5),
end: String(takeaway.businessHourEnd).substring(0, 5)
}]
}
return []
},
getBusinessHoursText(item) {
const takeaway = item && item.shopTakeaway
if (!takeaway) return ''
const periods = this.parseBusinessHourPeriods(takeaway)
if (!periods.length) return ''
return periods.map(p => p.begin + '-' + p.end).join('、')
},
isWithinBusinessHours(begin, end, currentMinutes) {
const nowMinutes = currentMinutes != null ? currentMinutes : (() => {
const now = new Date()
return now.getHours() * 60 + now.getMinutes()
})()
const parseTime = (timeStr) => { const parseTime = (timeStr) => {
const parts = String(timeStr || '00:00').split(':').map(Number) const parts = String(timeStr || '00:00').split(':').map(Number)
return (parts[0] || 0) * 60 + (parts[1] || 0) return (parts[0] || 0) * 60 + (parts[1] || 0)
} }
return currentMinutes >= parseTime(begin) && currentMinutes <= parseTime(end) const startMinutes = parseTime(begin)
const endMinutes = parseTime(end)
if (startMinutes === endMinutes) return true
if (startMinutes < endMinutes) {
return nowMinutes >= startMinutes && nowMinutes <= endMinutes
}
return nowMinutes >= startMinutes || nowMinutes <= endMinutes
}, },
switchTab(item) { switchTab(item) {
this.checkedTab = item.key this.checkedTab = item.key
@ -551,6 +605,10 @@
}) })
}, },
goShop(item) { goShop(item) {
if (item && item.status != 1) {
this.buyingye()
return
}
if (!requireLoginToCurrentPage()) return if (!requireLoginToCurrentPage()) return
uni.navigateTo({ uni.navigateTo({
url: '/package2/group/groupBuySingle?type=shop&orderScene=storeGroup&item=' + encodeURIComponent(JSON.stringify(item)) url: '/package2/group/groupBuySingle?type=shop&orderScene=storeGroup&item=' + encodeURIComponent(JSON.stringify(item))
@ -1705,15 +1763,35 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 24rpx;
box-sizing: border-box;
} }
.closed-text { .closed-badge {
padding: 12rpx 30rpx; max-width: 86%;
border-radius: 999rpx; padding: 16rpx 28rpx;
background: rgba(255, 255, 255, 0.92); border-radius: 24rpx;
background: rgba(255, 255, 255, 0.94);
box-shadow: 0 8rpx 24rpx rgba(92, 48, 21, 0.1);
display: flex;
flex-direction: column;
align-items: center;
}
.closed-title {
color: #ff8352; color: #ff8352;
font-size: 26rpx; font-size: 28rpx;
font-weight: 900; font-weight: 700;
line-height: 40rpx;
}
.closed-hours {
margin-top: 6rpx;
color: #666;
font-size: 22rpx;
line-height: 32rpx;
text-align: center;
word-break: break-all;
} }
.rank-tag { .rank-tag {

Loading…
Cancel
Save