wangfukang 3 days ago
parent
commit
ade847f50b
  1. 306
      package2/components/delivery-time-op/delivery-time-op.vue
  2. 99
      package2/group/groupBuyList.vue
  3. 384
      package2/group/groupBuySingle.vue
  4. 139
      package2/group/studentStoreList.vue
  5. 18
      package2/login/login.vue

306
package2/components/delivery-time-op/delivery-time-op.vue

@ -0,0 +1,306 @@
<!-- 弹窗组件小龙开发所有 -->
<template>
<view>
<!-- 模态框 -->
<view v-if="model" @tap="Modal" class="mask"></view>
<!-- 弹窗主体 -->
<view
:style="{ height: barHidth + 'rpx' }"
class="active"
:class="{ add: model }"
@tap.stop
>
<view class="title-model">
{{ title }}
<text class="title-close" @tap.stop="close">x</text>
</view>
<view class="cont" :style="{ height: barHidth - 80 + 'rpx' }">
<!-- 天 -->
<scroll-view class="day" :scroll-y="true">
<view
:class="index === isIndex ? 'active_copy' : ''"
v-for="(item, index) in normalizedContent"
:key="item.key"
@tap="dataCallback(index, item)"
>{{ item.timezh }}</view
>
</scroll-view>
<!-- 时 -->
<scroll-view class="content-model" :scroll-y="true" :scroll-top="scrollTop">
<view
class="appoint"
:class="index === Indexes ? 'longActive' : ''"
@tap="timeCallback(index, item)"
v-for="(item, index) in Days"
:key="index"
>{{ item.timestr
}}<text :class="index === Indexes ? 'cuIcon-check' : ''"></text
></view>
</scroll-view>
</view>
</view>
</view>
</template>
<script>
export default {
props: {
//弹窗组件标题
title: {
type: String,
default: "请传入title",
},
//弹窗组件数据
content: {
type: Array,
// 默认测试数据
default: () => [
{
timezh: "今天 (周三)",
timeformatter: "8-10",
id: 108,
timelist: [
{
timestr: "立即送达",
},
{
timestr: "15:35",
},
{
timestr: "16:05",
},
],
},
],
},
//弹窗 窗口高度
barHidth: {
type: Number,
default: 400,
},
//点击模态框是否能关闭弹窗
dodge: {
type: Boolean,
default: false,
},
},
data() {
return {
scrollTop: 0,
isIndex: 0,
Indexes: 0,
Days: [],
model: false,
};
},
computed: {
normalizedContent() {
return (this.content || []).map((item, index) => ({
...item,
key: item.id || index,
}));
},
},
//初始化数据
watch: {
content: {
immediate: true,
handler(newValue) {
this.Days =
newValue && newValue[0] && Array.isArray(newValue[0].timelist)
? newValue[0].timelist
: [];
},
},
},
methods: {
// 关闭窗口
close() {
this.model = false;
},
// 打开窗口
open() {
this.model = true;
},
// 点击模态框关闭窗口
Modal() {
if (this.dodge) {
this.close();
}
},
//时间切换回顶
gotop() {
this.scrollTop = 1;
this.$nextTick(function () {
this.scrollTop = 0;
});
},
//切换日期
dataCallback(index, item) {
this.isIndex = index;
this.Days = this.content[index].timelist;
this.Indexes = null;
this.gotop();
this.$emit("dataCallback", item);
},
//选择时间
timeCallback(index, item) {
this.Indexes = index;
this.modalName = null;
this.$emit("timeCallback", item);
},
},
};
</script>
<style scoped>
.mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #000;
animation: getInto 0.5s 1;
opacity: 0.5;
z-index: 999;
}
@keyframes getInto {
0% {
opacity: 0;
}
100% {
opacity: 0.5;
}
}
.active {
position: fixed;
bottom: 0;
left: 0;
z-index: 1000;
width: 100%;
height: 400rpx;
border-top-left-radius: 16rpx;
border-top-right-radius: 16rpx;
overflow: hidden;
transform: translateY(100%);
transition: 0.4s;
}
.add {
transform: translateY(0);
}
.title-model {
position: relative;
text-align: center;
background-color: #fff;
padding: 20rpx 0;
border-bottom: 2rpx solid #eee;
}
.title-close {
position: absolute;
top: 50%;
right: 24rpx;
display: flex;
align-items: center;
justify-content: center;
width: 48rpx;
height: 48rpx;
background-color: #f2f5f3;
border-radius: 50%;
color: #9aa5a0;
font-size: 32rpx;
line-height: 1;
transform: translateY(-50%);
}
.cont {
display: flex;
background-color: #fff;
overflow-y: scroll;
}
.day {
flex: 2;
background-color: #fff;
border-right: 2rpx solid #f8f8f8;
text-align: center;
border-bottom: 20px solid #fff;
}
.day view {
padding: 30rpx 12rpx;
font-size: 28rpx;
box-sizing: border-box;
}
.content-model {
flex: 4;
font-size: 28rpx;
border-bottom: 40rpx solid #fff;
background-color: #fff;
}
.appoint {
text-align: left;
padding: 30rpx;
border-bottom: 2rpx solid #f8f8f8;
}
.appoint text {
margin-left: 30rpx;
}
.active_copy {
position: relative;
background-color: #d7f7e3;
color: #27c866;
box-sizing: border-box;
}
.active_copy::after {
content: "";
width: 5rpx;
height: 94rpx;
background: #27c866;
position: absolute;
top: 0;
right: 0;
}
.longActive {
position: relative;
color: #27c866;
}
.cuIcon-check {
position: absolute;
width: 30rpx;
left: 210rpx;
height: 16rpx;
border-bottom: 4rpx solid #27c866;
border-left: 4rpx solid #27c866;
transform: rotate(-45deg);
}
scroll-view ::-webkit-scrollbar {
display: none;
width: 0;
height: 0;
color: transparent;
}
</style>

99
package2/group/groupBuyList.vue

@ -73,7 +73,7 @@
<img :src="item.url" alt="" />
<text class="menu-sticker">{{item.sticker}}</text>
</view>
<view class="menu-name">{{item.displayName || item.name}}</view>
<view class="menu-name">{{item.name}}</view>
</view>
</view>
<view class="menu-box menu-box-second" v-if="categoryExpanded">
@ -82,7 +82,7 @@
<img :src="item.url" alt="" />
<text class="menu-sticker">{{item.sticker}}</text>
</view>
<view class="menu-name">{{item.displayName || item.name}}</view>
<view class="menu-name">{{item.name}}</view>
</view>
</view>
</view>
@ -224,10 +224,11 @@
import { buildHomeSharePath } from '@/utils/sharePath.js'
import { hasLogin, requireLoginToCurrentPage } from '@/utils/authRedirect.js'
import { getSearchHistory, saveSearchHistory } from '@/utils/searchHistory.js'
import { resolveCampusTextEnabled, shouldUseSocialText } from '@/utils/areaTextMode.js'
const SHARE_TARGET_PATH = '/package2/group/groupBuyList'
const GROUP_META_CACHE_TTL = 5 * 60 * 1000
const GROUP_EAT_TYPE_CACHE_KEY = 'group_buy_eat_type_cache_v1'
const GROUP_EAT_TYPE_CACHE_KEY = 'group_buy_eat_type_cache_v3'
const GROUP_SHOP_AREA_CACHE_PREFIX = 'group_buy_shop_area_cache_v1_'
const GROUP_BUY_SEARCH_HISTORY_KEY = 'group_buy_list_search_history_v1'
@ -264,6 +265,7 @@
},
shopList: [],
areaKind: 1,
campusTextEnabled: 0,
searchHotWords: ['🍜 今晚吃啥', '🥤 奶茶续命拼团', '🔥 还差1人', '🍱 宿舍套餐热拼'],
liveSlogans: ['谁在偷吃夜宵', '差个饭搭子', '隔壁宿舍已开拼'],
liveFeeds: [{
@ -398,46 +400,32 @@
isSocialArea() {
return Number(this.areaKind) === 2
},
useSocialText() {
return shouldUseSocialText(this.areaKind, this.campusTextEnabled)
},
pageSubtitle() {
return this.isSocialArea ? '好友拼饭局' : '宿舍拼饭局'
return this.useSocialText ? '好友拼饭局' : '宿舍拼饭局'
},
interestSectionTitle() {
return this.isSocialArea ? '周边美食兴趣入口' : '校园美食兴趣入口'
return this.useSocialText ? '周边美食兴趣入口' : '校园美食兴趣入口'
},
scoreSortLabel() {
return this.isSocialArea ? '好友最爱' : '同学最爱'
return this.useSocialText ? '好友最爱' : '同学最爱'
},
areaSortLabel() {
return this.isSocialArea ? '附近区域' : '宿舍附近'
return this.useSocialText ? '附近区域' : '宿舍附近'
},
emptyOnlineText() {
return this.isSocialArea ? '好友群正在等一个饭搭子' : '宿舍群正在等一个饭搭子'
return this.useSocialText ? '好友群正在等一个饭搭子' : '宿舍群正在等一个饭搭子'
},
displayLiveFeeds() {
return this.isSocialArea ? this.socialLiveFeeds : this.liveFeeds
return this.useSocialText ? this.socialLiveFeeds : this.liveFeeds
},
displayMenuBtn() {
if (!this.isSocialArea) return this.menuBtn
return this.menuBtn.map((item) => {
const next = Object.assign({}, item)
if (item.name === '超市便利') {
next.displayName = '家里囤货'
} else if (item.name === '同学小店') {
next.displayName = '好友小店'
next.sticker = '友'
}
return next
})
return this.menuBtn
},
displayMenuBtn1() {
if (!this.isSocialArea) return this.menuBtn1
return this.menuBtn1.map((item) => {
const next = Object.assign({}, item)
if (item.name === '早点') {
next.displayName = '元气早餐'
}
return next
})
return this.menuBtn1
}
},
filters: {
@ -475,6 +463,7 @@
onLoad() {
this.syncAreaKind()
this.enableShareMenu()
this.getEatType()
this.getShopList();
},
onShow() {
@ -517,6 +506,7 @@
}
const kind = area ? Number(area.areaKind) : 1
this.areaKind = kind === 2 ? 2 : 1
this.campusTextEnabled = resolveCampusTextEnabled(area)
},
enableShareMenu() {
if (!uni.showShareMenu) return
@ -527,7 +517,7 @@
},
buildShareInfo() {
return {
title: this.isSocialArea
title: this.useSocialText
? '饭团好友拼饭局,约好友一起省钱吃'
: '饭团宿舍拼饭局,约同学一起省钱吃',
path: buildHomeSharePath(SHARE_TARGET_PATH),
@ -618,7 +608,6 @@
}, 300)
},
getEatType() {
if (!hasLogin()) return
const cache = uni.getStorageSync(GROUP_EAT_TYPE_CACHE_KEY)
if (this.isFreshCache(cache)) {
this.applyEatTypeList(cache.list || [])
@ -628,7 +617,10 @@
that.tui.request("/dictData/getByCondition", "GET", {
sort: 'sortOrder',
order: 'asc',
dictId: '1712359407444168704'
dictId: '1712359407444168704',
merchantType: 1,
pageNumber: 1,
pageSize: 1000
}, false, true, true).then((res) => {
if (res.code == 200) {
const list = res.result.content || []
@ -644,18 +636,25 @@
}).catch((res) => {})
},
applyEatTypeList(list) {
for (let i = 0; i < list.length; i++) {
for (let m = 0; m < this.menuBtn.length; m++) {
if (this.menuBtn[m].name == list[i].title) {
this.menuBtn[m].value = list[i].value
}
}
for (let m = 0; m < this.menuBtn1.length; m++) {
if (this.menuBtn1[m].name == list[i].title) {
this.menuBtn1[m].value = list[i].value
}
}
}
const source = this.menuBtn.concat(this.menuBtn1)
const dictList = Array.isArray(list) ? list.filter(item => item && item.title) : []
if (!dictList.length) return
const allMenus = dictList.map((item, index) => {
const exact = source.find(menu => menu.name == item.title || String(menu.value) == String(item.value))
const matched = exact
|| source[index % source.length]
|| {}
return Object.assign({}, matched, {
name: item.title,
displayName: '',
value: item.value,
sticker: matched.sticker || '拼',
url: matched.url || 'https://jewel-shop.oss-cn-beijing.aliyuncs.com/f7227759d693497a8beab87ff437a875.png',
isStudentOnly: (exact && exact.isStudentOnly) || Number(item.value) === 5
})
})
this.menuBtn = allMenus.slice(0, 5)
this.menuBtn1 = allMenus.slice(5)
},
getShopArea() {
if (!hasLogin()) return
@ -763,7 +762,7 @@
const selfDeliveryTime = Number(item.shopDeliveryDuration)
const transferTimeValue = Number(item.transferDeliveryDuration)
const transferTime = item.supportTransferDelivery == 1 && !isNaN(transferTimeValue) && transferTimeValue > 0 ? transferTimeValue : 0
item._groupTip = this.isSocialArea
item._groupTip = this.useSocialText
? ['还差1人就成团', '刚下班,大家都在拼', '这口热乎的很适合夜宵', '附近好友想一起点'][index % 4]
: ['还差1人就成团', '刚下课,大家都在拼', '这口热乎的很适合夜宵', '附近同学想一起点'][index % 4]
let baseDeliveryTime
@ -827,8 +826,8 @@
const fee = parseFloat(item.orderBkge || 0)
const feeText = fee > 0 ? `¥${fee.toFixed(1)}` : '免配送费'
const locationText = item.shopDeliveryLocation == 1
? (this.isSocialArea ? '送上门' : '送宿舍')
: (this.isSocialArea ? '送门口' : '送楼下')
? (this.useSocialText ? '送上门' : '送宿舍')
: (this.useSocialText ? '送门口' : '送楼下')
return `商家自配送 · ${feeText} · ${locationText}`
},
shouldShowBaseDeliveryFee(item) {
@ -836,7 +835,7 @@
return !!(item && item.supportShopDelivery != 1 && !isNaN(fee) && fee > 0)
},
formatOnlineWorkerText(item) {
const people = this.isSocialArea ? '位骑手' : '位同学'
const people = this.useSocialText ? '位骑手' : '位同学'
return `${item.areaName} 附近 ${item.onlineWorkerCount} ${people}在线接单`
},
formatAmountText(value) {
@ -1553,6 +1552,12 @@
.menu-box-second {
margin-top: 14rpx;
flex-wrap: wrap;
row-gap: 14rpx;
}
.menu-box-second .menu-list {
flex: 0 0 20%;
}
.menu-list {

384
package2/group/groupBuySingle.vue

@ -147,7 +147,7 @@
</view>
<view class="catalog-goods">
<view class="goods-member goods-card" :id="'category-' + item.categoryId"
v-for="(item,index) in productItem" :key="index" @tap="openGoodsDetail(item)">
v-for="(item,index) in productItem" :key="index" @tap="openGoodsDetailByIndex(index)">
<view class="goods-card-shine"></view>
<view @tap.stop="buyingye" v-if="item.status == 1"
style="width: 100%;height:100%;position: absolute;top: 0;background: rgba(0, 0, 0, 0.1);z-index: 999;border-radius: 20rpx;">
@ -180,13 +180,13 @@
</view>
</view>
<view class="shop-tag1">
<view class="goods-tag" @tap.stop="openOrderWait(item)"
<view class="goods-tag" @tap.stop="openOrderWaitByIndex(index)"
v-if="item.orderListWait != undefined && item.orderListWait.length > 0">
<view class="pintuan-right-img">
<img src="https://jewel-shop.oss-cn-beijing.aliyuncs.com/f7227759d693497a8beab87ff437a875.png" alt="" />
<view class="weipincheng">+</view>
</view>
<view class="pintuan-right-cha" @tap.stop="openOrderWait(item)">
<view class="pintuan-right-cha" @tap.stop="openOrderWaitByIndex(index)">
差<text style="color: red;">{{getMissingGroupMembers(item)}}人</text>成团
</view>
</view>
@ -197,7 +197,7 @@
<view v-if="isProductGroupBuyAvailable(item)"
class="goods-btn group-buy-btn" style="margin-left: 6%;"
:style="{'float':groupId != ''?'right':''}" @tap.stop="openPopup('xiadan','pintuan',item)">
:style="{'float':groupId != ''?'right':''}" @tap.stop="openProductPopup('pintuan', index)">
<view class="btn-spark">
<view class="spark-dot spark-dot-1"></view>
<view class="spark-dot spark-dot-2"></view>
@ -205,7 +205,7 @@
</view>
<text>拼团购买¥{{ getGroupPrice(item) }}</text>
</view>
<view class="goods-btn solo-buy-btn" v-if="!groupId" @tap.stop="openPopup('xiadan','dandu',item)"
<view class="goods-btn solo-buy-btn" v-if="!groupId" @tap.stop="openProductPopup('dandu', index)"
style="background: rgba(166, 255, 234, 0.3);border: 1px solid rgba(166, 255, 234, 0.5);height:68rpx;">
直接购买¥{{item.attributeListPrice | sliceMsg}}
</view>
@ -399,7 +399,7 @@
</view>
<view class="goods-content-center">
<view class="goods-deal1">
月售 <text> 100+</text>
<rich-text :nodes="getProductIntro(currentItem)"></rich-text>
</view>
</view>
<view class="goods-content-bottom">
@ -672,9 +672,27 @@
<text style="font-size: 30rpx;font-weight: 700;">立即出餐</text>
</view>
<view style="color: #777;width: 500rpx;">
拼团成功后立即出餐
{{nowMake === false ? '选择用餐时间,系统会在用餐前30分钟通知商家并打印小票' : '拼团成功后立即出餐'}}
</view>
<view class="pack-options" v-if="nowMake">
<view class="now-make-options">
<view class="now-make-option" @tap="checkNowMake(true)">
<view :class="nowMake === true ? 'radio-check' : 'radio-no-check'">
<uni-icons v-if="nowMake === true" type="checkmarkempty" size="12"></uni-icons>
</view>
是
</view>
<view class="now-make-option" @tap="checkNowMake(false)">
<view :class="nowMake === false ? 'radio-check' : 'radio-no-check'">
<uni-icons v-if="nowMake === false" type="checkmarkempty" size="12"></uni-icons>
</view>
否
</view>
</view>
<view class="pickup-time-row" v-if="nowMake === false" @tap="openPickupTimePicker">
<text>用餐时间</text>
<text :class="{'pickup-time-placeholder': !pickupTime}">{{pickupTime || '选择时间 >'}}</text>
</view>
<view class="pack-options">
<view class="pack-option" @tap="isPack = 0">
<view :class="isPack === 0 ? 'radio-check' : 'radio-no-check'">
<uni-icons v-if="isPack === 0" type="checkmarkempty" size="12"></uni-icons>
@ -689,11 +707,6 @@
</view>
</view>
</view>
<view style="width: 36rpx;padding-top: 20rpx;">
<view :class="nowMake ? 'radio-check' : 'radio-no-check'" @tap="nowMakeMethod">
<uni-icons v-if="nowMake" type="checkmarkempty" size="12"></uni-icons>
</view>
</view>
</view>
<view class="box1 pay-option-card" style="display: flex;">
<view style="flex: 1;">
@ -712,11 +725,27 @@
</view>
</view>
<view class="btn pay-confirm-btn" @tap="wxPayment">
确认付款{{nowMake && groupdeliveryType == 2 && !isStoreGroupOrder ? '-即刻出餐' : ''}}
确认付款{{nowMake === true && groupdeliveryType == 2 && !isStoreGroupOrder ? '-即刻出餐' : ''}}
</view>
</view>
</view>
</uni-popup>
<delivery-time-op @timeCallback="pickupTimeCallback" :dodge="false" ref="pickupTimeModel"
:content="pickupTimeContent" :barHidth="600" title="选择用餐时间">
</delivery-time-op>
<uni-popup ref="nowMakePopup" type="center" background-color="transparent">
<view class="now-make-popup">
<view class="now-make-popup-title">是否立即出餐</view>
<view class="now-make-popup-desc">请选择是否需要商家在成团后立即出餐。</view>
<view class="now-make-popup-tip">
选择“否”后需要预约用餐时间,系统会在用餐前30分钟通知商家并打印小票
</view>
<view class="now-make-popup-actions">
<view class="now-make-popup-cancel" @tap="confirmNowMake(false)">否</view>
<view class="now-make-popup-confirm" @tap="confirmNowMake(true)">是</view>
</view>
</view>
</uni-popup>
<!-- 参团成功提示弹出层 -->
<uni-popup ref="tishiPopup" background-color="transparent" @change="onTishiPopupChange">
<view v-if="!isPindan && isStoreGroupOrder" class="group-face-tip-popup" @tap.stop>
@ -872,8 +901,10 @@
</template>
<script>
import deliveryTimeOp from '@/package2/components/delivery-time-op/delivery-time-op.vue'
import { requireLoginToCurrentPage } from '@/utils/authRedirect.js'
import { rememberInviteParams } from '@/utils/sharePath.js'
import { resolveCampusTextEnabled, shouldUseSocialText } from '@/utils/areaTextMode.js'
export default {
data() {
return {
@ -894,7 +925,9 @@
storeConsumeOnly: 0,
},
productId: '',
nowMake: true,
nowMake: null,
pickupTime: '',
pickupTimeContent: [],
isPack: 0,
productItem: [],
tuanzhangOrder: {},
@ -907,6 +940,7 @@
checkMoreBuyIndex: '',
parsedSpecs: [],
specExpandedMap: [],
allMoreBuyList: [],
moreBuyList: [],
selectedGroupRule: {},
pageNum: 1,
@ -960,6 +994,7 @@
autoWaitRuleKnown: false,
hasAutoOpenedWaitPopup: false,
areaKind: 1,
campusTextEnabled: 0,
sharePosterLoading: false,
sharePosterImage: '',
shareQrcodeUrl: '',
@ -973,12 +1008,15 @@
}
},
components: {
deliveryTimeOp
},
computed: {
isSocialArea() {
return Number(this.areaKind) === 2
},
useSocialText() {
return shouldUseSocialText(this.areaKind, this.campusTextEnabled)
},
isStoreGroupOrder() {
return this.orderScene === 'storeGroup' || this.shopItem.merchantType == 2;
},
@ -990,11 +1028,11 @@
},
shopSubtitle() {
if (this.isStoreGroupOrder) {
return this.isSocialArea
return this.useSocialText
? '到店核销,任选商品即可参团'
: '到店核销,任选商品即可参团'
}
return this.isSocialArea
return this.useSocialText
? '约上好友一起拼,任选商品即可参团'
: '约上室友同学一起拼,任选商品即可参团'
},
@ -1005,25 +1043,25 @@
return '';
},
liveSlogan() {
return this.isSocialArea
return this.useSocialText
? '你知道吗?拼团和柴米油盐一样重要!'
: '你知道吗?拼团和期末不挂科一样重要!'
},
popupRallyMain() {
if (this.isStoreGroupOrder) {
return this.isSocialArea
return this.useSocialText
? '到店团购搭子哪里找-同城好友一起省!'
: '到店团购搭子哪里找-同校同学一起省!'
}
return this.isSocialArea
return this.useSocialText
? '拼团搭子哪里找-同城好友全是宝!'
: '拼团搭子哪里找-隔壁宿舍全是宝!'
},
regionSelectToast() {
return this.isSocialArea ? '请先选择区域' : '请先选择校区'
return this.useSocialText ? '请先选择区域' : '请先选择校区'
},
regionMismatchContent() {
return this.isSocialArea
return this.useSocialText
? '当前商家不在本区域范围,请切换区域后下单'
: '当前商家不在本校区范围,请切换校区后下单'
},
@ -1254,6 +1292,7 @@
}
const kind = area ? Number(area.areaKind) : 1
this.areaKind = kind === 2 ? 2 : 1
this.campusTextEnabled = resolveCampusTextEnabled(area)
},
normalizeRegionId(value) {
return value === undefined || value === null ? '' : String(value).trim();
@ -1320,6 +1359,7 @@
checked: true
}];
this.productItem = [];
this.allMoreBuyList = [];
this.moreBuyList = [];
},
loadShopContent() {
@ -1482,8 +1522,109 @@
this.goPayOrderDetail();
}
},
nowMakeMethod() {
this.nowMake = !this.nowMake
checkNowMake(value) {
this.nowMake = value;
if (value === false) {
this.openPickupTimePicker();
}
},
openNowMakePopup() {
this.$refs.nowMakePopup.open('center');
},
confirmNowMake(value) {
this.nowMake = value;
this.$refs.nowMakePopup.close();
this.$nextTick(() => {
if (value === false) {
this.openPickupTimePicker();
return;
}
this.wxPayment();
});
},
needsPickupAppointment() {
return this.groupdeliveryType == 2 && !this.isStoreGroupOrder && this.nowMake === false;
},
openPickupTimePicker() {
this.generatePickupTimes();
this.$refs.pickupTimeModel.open();
},
pickupTimeCallback(val) {
this.pickupTime = val.timestr;
this.$refs.pickupTimeModel.close();
},
getPickupLeadMinutes() {
const cookingTime = Number(this.shopItem.shopTakeaway && this.shopItem.shopTakeaway.cookingTime ? this.shopItem.shopTakeaway.cookingTime : 0);
return !isNaN(cookingTime) && cookingTime > 0 ? cookingTime : 30;
},
getNextPickupSlot(date) {
const interval = 20;
const slot = new Date(date);
slot.setSeconds(0, 0);
const remainder = slot.getMinutes() % interval;
if (remainder !== 0) {
slot.setMinutes(slot.getMinutes() + interval - remainder);
} else {
slot.setMinutes(slot.getMinutes() + interval);
}
return slot;
},
formatClockTime(date) {
return `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
},
formatDateTimeValue(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}`;
},
getPickupAppointmentEndTime(date) {
const endTime = new Date(date);
const fallbackEndTime = new Date(date);
fallbackEndTime.setHours(23, 0, 0, 0);
const endStr = this.shopItem.shopTakeaway && this.shopItem.shopTakeaway.businessHourEnd;
const parts = String(endStr || '').split(':').map(Number);
if (parts.length < 2 || isNaN(parts[0]) || isNaN(parts[1])) {
return fallbackEndTime;
}
endTime.setHours(parts[0], parts[1], 0, 0);
endTime.setMinutes(endTime.getMinutes() + 20);
return endTime > fallbackEndTime ? fallbackEndTime : endTime;
},
generatePickupTimes() {
const now = new Date();
const today = new Date(now);
const weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
const earliestTime = new Date(now);
earliestTime.setMinutes(earliestTime.getMinutes() + this.getPickupLeadMinutes());
const todayStartTime = this.getNextPickupSlot(earliestTime);
const todayEndTime = this.getPickupAppointmentEndTime(today);
const timelist = [];
const cursor = new Date(todayStartTime);
while (cursor <= todayEndTime) {
timelist.push({ timestr: this.formatClockTime(cursor) });
cursor.setMinutes(cursor.getMinutes() + 20);
}
this.pickupTimeContent = [{
id: 'today',
timezh: `今天 (${weekdays[today.getDay()]})`,
timelist: timelist
}];
},
getPickupMakeTime() {
if (!this.pickupTime) return null;
let [h, m] = this.pickupTime.split(':');
let targetHour = parseInt(h);
let targetMinute = parseInt(m);
let d = new Date();
if (targetHour < d.getHours() || (targetHour === d.getHours() && targetMinute <= d.getMinutes())) {
d.setDate(d.getDate() + 1);
}
d.setHours(targetHour, targetMinute, 0, 0);
return this.formatDateTimeValue(d);
},
toNumber(value) {
let amount = parseFloat(value);
@ -1515,6 +1656,12 @@
},
getMoreBuyAmount() {
let list = this.currentItem && this.currentItem.moreBuyData ? this.currentItem.moreBuyData : this.moreBuyData;
const allowedIds = this.parseMoreBuyProductIds(this.currentItem && this.currentItem.moreBuyProductIds);
if (allowedIds.length === 0) {
list = [];
} else {
list = (list || []).filter(item => allowedIds.indexOf(String(item.id)) !== -1);
}
let amount = 0;
for (let i = 0; i < list.length; i++) {
if (list[i].quantity > 0) {
@ -1562,10 +1709,42 @@
}
this.selectedGroupRule.groupPriceAndMore = amount > 0 ? amount : 0.01;
},
parseMoreBuyProductIds(value) {
if (Array.isArray(value)) {
return value.filter(item => item !== '' && item !== null && item !== undefined).map(String);
}
if (value === '' || value === null || value === undefined) {
return [];
}
return String(value).split(',').map(item => item.trim()).filter(item => item);
},
getCurrentMoreBuyList(item) {
const allowedIds = this.parseMoreBuyProductIds(item && item.moreBuyProductIds);
if (allowedIds.length === 0) {
return [];
}
return this.allMoreBuyList.filter(product => allowedIds.indexOf(String(product.id)) !== -1);
},
applyCurrentMoreBuyList(item) {
const selectedData = Array.isArray(this.moreBuyData) ? this.moreBuyData : [];
this.moreBuyList = this.getCurrentMoreBuyList(item).map(product => {
const selected = selectedData.find(data => data.id == product.id);
return Object.assign({}, product, {
isChecked: !!(selected && selected.quantity > 0),
quantity: selected && selected.quantity ? selected.quantity : 0
});
});
this.vModelValue = 0;
this.checkMoreBuyIndex = '';
},
syncCurrentItemMoreBuyData() {
this.currentItem.moreBuyData = [];
const allowedIds = this.parseMoreBuyProductIds(this.currentItem && this.currentItem.moreBuyProductIds);
if (allowedIds.length === 0) {
return;
}
for (let i = 0; i < this.moreBuyData.length; i++) {
if (this.moreBuyData[i].quantity != undefined && this.moreBuyData[i].quantity > 0) {
if (allowedIds.indexOf(String(this.moreBuyData[i].id)) !== -1 && this.moreBuyData[i].quantity != undefined && this.moreBuyData[i].quantity > 0) {
this.currentItem.moreBuyData.push(this.moreBuyData[i]);
}
}
@ -1681,6 +1860,7 @@
let tuijianList = []
let putongList = []
that.moreBuyList = []
that.allMoreBuyList = []
for (let i = 0; i < that.productItem.length; i++) {
if (this.productItem[i].sellBeginTime != '' && this.productItem[i].sellEndTime != '') {
let isEndTime = this.isWithinBusinessHours(this.productItem[i].sellBeginTime, this
@ -1691,7 +1871,7 @@
if (that.productItem[i].isMoreBuy == 1) {
//搭配的小料商品
that.productItem[i].isChecked = false
that.moreBuyList.push(that.productItem[i])
that.allMoreBuyList.push(that.productItem[i])
}
if (that.productItem[i].isPush == 1) {
that.productItem[i].categoryId = 'tuijian'
@ -1775,6 +1955,28 @@
});
});
},
getProductByIndex(index) {
const product = this.productItem && this.productItem[index];
return product || null;
},
openGoodsDetailByIndex(index) {
const product = this.getProductByIndex(index);
if (product) {
this.openGoodsDetail(product);
}
},
openOrderWaitByIndex(index) {
const product = this.getProductByIndex(index);
if (product) {
this.openOrderWait(product);
}
},
openProductPopup(type, index) {
const product = this.getProductByIndex(index);
if (product) {
this.openPopup('xiadan', type, product);
}
},
openOrderWait(item) {
const waitList = item && item != '' ? item.orderListWait : this.orderListWait;
if (!Array.isArray(waitList) || waitList.length == 0) {
@ -1786,6 +1988,7 @@
this.orderListWait = waitList
this.currentItem = item;
this.applyCurrentMoreBuyList(item);
// Parse specifications
this.parsedSpecs = [];
if (item.attributeList && item.attributeList !== '{}') {
@ -1932,6 +2135,7 @@
this.$refs.carPopup.open('bottom')
} else if (type == 'xiadan') {
this.currentItem = item;
this.applyCurrentMoreBuyList(item);
// Parse specifications
this.parsedSpecs = [];
if (item.attributeList && item.attributeList !== '{}') {
@ -2179,7 +2383,7 @@
return null;
}
return {
let payload = {
userId: uni.getStorageSync('id'),
shopId: this.shopItem.id,
deliveryType: this.tuanzhangOrder.deliveryType,
@ -2196,9 +2400,27 @@
userCouponId: this.selectedCoupon ? this.selectedCoupon.id : null,
regionId: regionId
};
if (payload.deliveryType == 2 && !this.isStoreGroupOrder) {
payload.isPack = this.isPack;
if (this.nowMake === true) {
payload.userRequireMake = 1;
} else if (this.nowMake === false) {
payload.userRequireMakeTime = this.getPickupMakeTime();
}
}
return payload;
},
createFTFJoinOrder(specChoices, autoPay) {
if (this.isCreatingOrder) return;
if (this.groupdeliveryType == 2 && !this.isStoreGroupOrder && this.nowMake === null) {
this.openNowMakePopup();
return;
}
if (this.needsPickupAppointment() && !this.pickupTime) {
this.tui.toast('请选择用餐时间');
this.openPickupTimePicker();
return;
}
this.isCreatingOrder = true;
this.syncCurrentItemMoreBuyData();
let payload = this.buildFTFJoinPayload(specChoices);
@ -2619,6 +2841,10 @@
wxPayment() {
if (!requireLoginToCurrentPage()) return;
let that = this;
if (this.groupdeliveryType == 2 && !this.isStoreGroupOrder && this.nowMake === null) {
this.openNowMakePopup();
return;
}
if (!this.currentOrderId || !this.backendTotalAmount) {
if (this.pendingPaySpecChoices) {
this.createFTFJoinOrder(this.pendingPaySpecChoices, true);
@ -2628,7 +2854,7 @@
let amountInCents = Math.round(this.backendTotalAmount * 100);
let userRequireMake = 0
if (this.nowMake && this.groupdeliveryType == 2 && !this.isStoreGroupOrder) {
if (this.nowMake === true && this.groupdeliveryType == 2 && !this.isStoreGroupOrder) {
userRequireMake = 1
}
@ -2855,7 +3081,7 @@
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));
ctx.fillText(this.useSocialText ? '同城好友一起拼,优惠一起拿' : '约上同学室友一起拼,越拼越划算', uni.upx2px(58), uni.upx2px(236));
const iconX = uni.upx2px(58);
const iconY = uni.upx2px(288);
@ -3140,6 +3366,102 @@
margin-right: 40rpx;
}
.now-make-options {
display: flex;
align-items: center;
margin-top: 18rpx;
}
.now-make-option {
display: flex;
align-items: center;
color: #00231C;
font-size: 28rpx;
font-weight: 800;
margin-right: 40rpx;
}
.pickup-time-row {
display: flex;
align-items: center;
justify-content: space-between;
width: 500rpx;
min-height: 56rpx;
margin-top: 18rpx;
padding: 0 18rpx;
border-radius: 16rpx;
background: #f6faf8;
color: #00231C;
font-size: 24rpx;
font-weight: 800;
box-sizing: border-box;
}
.pickup-time-placeholder {
color: #8b9993;
font-weight: normal;
}
.now-make-popup {
width: 620rpx;
padding: 44rpx 36rpx 34rpx;
box-sizing: border-box;
border-radius: 32rpx;
background: #fff;
box-shadow: 0 28rpx 70rpx rgba(0, 35, 28, 0.18);
}
.now-make-popup-title {
color: #00231C;
font-size: 34rpx;
font-weight: 900;
text-align: center;
}
.now-make-popup-desc {
margin-top: 12rpx;
color: #6f7f79;
font-size: 24rpx;
text-align: center;
}
.now-make-popup-tip {
margin-top: 18rpx;
padding: 14rpx 18rpx;
border-radius: 18rpx;
background: #fff8ef;
color: #ff7043;
font-size: 22rpx;
line-height: 34rpx;
}
.now-make-popup-actions {
display: flex;
margin-top: 34rpx;
gap: 18rpx;
}
.now-make-popup-cancel,
.now-make-popup-confirm {
flex: 1;
height: 76rpx;
border-radius: 999rpx;
text-align: center;
line-height: 76rpx;
font-size: 28rpx;
font-weight: 800;
}
.now-make-popup-cancel {
background: #f3f6f4;
color: #6f7f79;
}
.now-make-popup-confirm {
background: linear-gradient(135deg, #0b9b73, #20d6a4);
color: #fff;
}
.title-search {
display: flex;
flex: 1;
@ -3571,7 +3893,7 @@
}
.goods-deal1 {
width: 60%;
width: 98%;
}
.goods-tag {

139
package2/group/studentStoreList.vue

@ -74,6 +74,9 @@
<view class="interest-pulse"></view>
</view>
</view>
<view class="category-toggle" v-if="hasMoreTabs" @tap="toggleCategory">
{{categoryExpanded ? '收起' : '展开更多'}}
</view>
</view>
<view class="sort-strip">
@ -154,9 +157,13 @@
import { buildHomeSharePath } from '@/utils/sharePath.js'
import { hasLogin, requireLoginToCurrentPage } from '@/utils/authRedirect.js'
import { getSearchHistory, saveSearchHistory } from '@/utils/searchHistory.js'
import { resolveCampusTextEnabled, shouldUseSocialText } from '@/utils/areaTextMode.js'
const SHARE_TARGET_PATH = '/package2/group/studentStoreList'
const STUDENT_STORE_SEARCH_HISTORY_KEY = 'student_store_search_history_v1'
const STUDENT_STORE_TYPE_CACHE_KEY = 'student_store_type_cache_v3'
const STUDENT_STORE_TYPE_CACHE_TTL = 5 * 60 * 1000
const DEFAULT_CATEGORY_VISIBLE_COUNT = 10
export default {
data() {
@ -171,6 +178,7 @@
searchScore: false,
sortMode: 'all',
checkedTab: '',
categoryExpanded: false,
areaTitle: '区域',
shopArea: [],
totalPages: 1,
@ -272,6 +280,7 @@
}],
shopList: [],
areaKind: 1,
campusTextEnabled: 0,
searchHistory: [],
searchHistoryVisible: false
}
@ -280,38 +289,41 @@
isSocialArea() {
return Number(this.areaKind) === 2
},
useSocialText() {
return shouldUseSocialText(this.areaKind, this.campusTextEnabled)
},
searchPlaceholder() {
return this.isSocialArea ? '输入你想拼的周边快乐' : '输入你想拼的校园快乐'
return this.useSocialText ? '输入你想拼的周边快乐' : '输入你想拼的校园快乐'
},
pendingSubText() {
return this.isSocialArea ? '看看同城好友还有哪些拼团差人' : '看看同校还有哪些拼团差人'
return this.useSocialText ? '看看同城好友还有哪些拼团差人' : '看看同校还有哪些拼团差人'
},
scoreSortLabel() {
return this.isSocialArea ? '好友最爱' : '同学最爱'
return this.useSocialText ? '好友最爱' : '同学最爱'
},
emptySubText() {
return this.isSocialArea
return this.useSocialText
? '换个关键词或分类试试,新的周边店铺会出现在这里'
: '换个关键词或分类试试,新的校园店铺会出现在这里'
},
defaultShopSubtitle() {
return this.isSocialArea ? '约上好友一起拼' : '约上同学一起拼'
return this.useSocialText ? '约上好友一起拼' : '约上同学一起拼'
},
hasMoreTabs() {
const allTabCount = this.tabList.some(item => item && item.key === '') ? 1 : 0
const categoryCount = this.tabList.filter(item => item && item.key !== '').length
return categoryCount > DEFAULT_CATEGORY_VISIBLE_COUNT - allTabCount
},
displayTabList() {
if (!this.isSocialArea) return this.tabList
return this.tabList.map((item) => {
const next = Object.assign({}, item)
if (item.key === 'student') {
next.name = '好友小店'
next.icon = '友'
next.sub = '好友宝藏'
} else if (item.key === 'course') {
next.sub = '技能充电'
} else if (item.key === 'movie') {
next.sub = '周末约影'
}
return next
})
if (this.categoryExpanded || !this.hasMoreTabs) {
return this.tabList
}
const allTab = this.tabList.find(item => item && item.key === '')
const categoryTabs = this.tabList.filter(item => item && item.key !== '')
const visibleCategoryCount = DEFAULT_CATEGORY_VISIBLE_COUNT - (allTab ? 1 : 0)
return allTab
? [allTab, ...categoryTabs.slice(0, visibleCategoryCount)]
: categoryTabs.slice(0, DEFAULT_CATEGORY_VISIBLE_COUNT)
}
},
filters: {
@ -333,6 +345,7 @@
onLoad() {
this.syncAreaKind()
this.enableShareMenu()
this.loadShopTypeDict()
this.initRegion()
},
onShow() {
@ -372,7 +385,7 @@
},
buildShareInfo() {
return {
title: this.isSocialArea
title: this.useSocialText
? '团购杂货铺,同城好友好店一起拼'
: '团购杂货铺,同校好店一起拼',
path: buildHomeSharePath(SHARE_TARGET_PATH),
@ -391,6 +404,69 @@
}
const kind = area ? Number(area.areaKind) : 1
this.areaKind = kind === 2 ? 2 : 1
this.campusTextEnabled = resolveCampusTextEnabled(area)
},
isFreshTypeCache(cache) {
return !!(cache && cache.time && Date.now() - cache.time < STUDENT_STORE_TYPE_CACHE_TTL)
},
toggleCategory() {
this.categoryExpanded = !this.categoryExpanded
},
loadShopTypeDict() {
const cache = uni.getStorageSync(STUDENT_STORE_TYPE_CACHE_KEY)
if (this.isFreshTypeCache(cache)) {
this.applyShopTypeList(cache.list || [])
return
}
this.tui.request('/dictData/getByCondition', 'GET', {
sort: 'sortOrder',
order: 'asc',
dictId: '1712359407444168704',
merchantType: 2,
pageNumber: 1,
pageSize: 1000
}, false, true, true).then((res) => {
if (res.code == 200) {
const list = res.result.content || []
this.applyShopTypeList(list)
uni.setStorageSync(STUDENT_STORE_TYPE_CACHE_KEY, {
time: Date.now(),
list
})
}
}).catch(() => {})
},
applyShopTypeList(list) {
const allTab = this.tabList.find(item => item.key === '') || {
name: '全部',
key: '',
value: '',
icon: '逛',
sub: '随便看看'
}
const source = this.tabList.filter(item => item.key !== '')
const dictList = Array.isArray(list) ? list.filter(item => item && item.title) : []
if (!dictList.length) return
const tabs = dictList.map((item, index) => {
const exact = source.find(tab => tab.name == item.title || String(tab.value) == String(item.value))
const matched = exact
|| source[index % source.length]
|| {}
const title = String(item.title || '')
return Object.assign({}, matched, {
name: title,
key: exact && exact.key ? exact.key : ('type_' + (item.id || item.value || index)),
value: item.value,
icon: matched.icon || this.getShopTypeIcon(index),
sub: matched.sub || '更多美好',
isStudentOnly: (exact && exact.isStudentOnly) || Number(item.value) === 5 || title.indexOf('学生') > -1 || title.indexOf('同学') > -1
})
})
this.tabList = [Object.assign({}, allTab), ...tabs]
},
getShopTypeIcon(index) {
const icons = ['逛', '舍', '课', '吃', '玩', '美', '咖', '店', '演', '其']
return icons[index % icons.length]
},
backHome() {
uni.switchTab({
@ -404,7 +480,7 @@
this.syncAreaKind()
const area = uni.getStorageSync('area')
if (!area) {
this.tui.toast(this.isSocialArea ? '请先选择区域' : '请先选择校区')
this.tui.toast(this.useSocialText ? '请先选择区域' : '请先选择校区')
return
}
try {
@ -414,7 +490,7 @@
this.searchForm.regionId = ''
}
if (!this.searchForm.regionId) {
this.tui.toast(this.isSocialArea ? '请先选择区域' : '请先选择校区')
this.tui.toast(this.useSocialText ? '请先选择区域' : '请先选择校区')
return
}
if (hasLogin()) {
@ -667,7 +743,7 @@
getShopType(item) {
if (item.shopTypeTitle) return item.shopTypeTitle
if (item.shopTakeaway && item.shopTakeaway.type) return item.shopTakeaway.type
return this.isSocialArea ? '同城生活' : '校园生活'
return this.useSocialText ? '同城生活' : '校园生活'
},
getShopStatus(item) {
const status = Number(item && item.status)
@ -700,7 +776,7 @@
return count > 0 ? Math.min(99, count + 8 + index * 3) : 18 + index * 5
},
getDormText(index) {
const dorms = this.isSocialArea
const dorms = this.useSocialText
? ['小区门口正在拼', '商场旁刚下单', '公园边热拼', '地铁口有人约', '写字楼旁在凑']
: ['3号楼正在拼', '东区宿舍刚下单', '图书馆旁热拼', '操场边有人约', '北门寝室在凑']
return dorms[index % dorms.length]
@ -709,7 +785,7 @@
return index % 3 === 0 ? '还差1' : (index % 3 === 1 ? '还差2' : '满3')
},
getLiveText(index) {
const texts = this.isSocialArea
const texts = this.useSocialText
? ['刚成团', '同城热拼', '搭子在等', '附近爆款']
: ['刚成团', '同校热拼', '搭子在等', '附近爆款']
return texts[index % texts.length]
@ -1453,6 +1529,19 @@
padding: 0 0 8rpx;
}
.category-toggle {
width: 180rpx;
margin: 2rpx auto 12rpx;
padding: 10rpx 0;
border-radius: 999rpx;
background: linear-gradient(90deg, rgba(255, 231, 186, 0.92), rgba(255, 244, 220, 0.92));
color: #9a5a28;
font-size: 22rpx;
font-weight: 900;
text-align: center;
box-shadow: 0 10rpx 24rpx rgba(178, 102, 48, 0.1);
}
.interest-card {
width: 19%;
height: 118rpx;

18
package2/login/login.vue

@ -98,6 +98,7 @@
<script>
import { resolveHomeShareTarget, getEnterShareTarget, resolveMiniCodeSceneTarget, reopenShareEnter, rememberInviteParams, getStoredInviteParams, normalizeInviteParams, extractInviteParamsFromUrl, getInviteDeviceId } from '@/utils/sharePath.js'
import { resolveCampusTextEnabled, shouldUseSocialText } from '@/utils/areaTextMode.js'
export default {
data() {
return {
@ -110,6 +111,7 @@
saleId: '',
redirectKey: '',
areaKind: 1,
campusTextEnabled: 0,
inviteContext: { inviterId: '', regionId: '' },
inviteLat: '',
inviteLng: '',
@ -121,23 +123,26 @@
isSocialArea() {
return Number(this.areaKind) === 2
},
useSocialText() {
return shouldUseSocialText(this.areaKind, this.campusTextEnabled)
},
bubbleCText() {
return this.isSocialArea ? '附近好友正在拼饭' : '附近同学正在拼饭'
return this.useSocialText ? '附近好友正在拼饭' : '附近同学正在拼饭'
},
brandSubtitle() {
return this.isSocialArea ? '同城万物 · 皆可拼团' : '校园万物 · 皆可拼团'
return this.useSocialText ? '同城万物 · 皆可拼团' : '校园万物 · 皆可拼团'
},
cardTitle() {
return this.isSocialArea ? '进入你的同城半径' : '进入你的校园半径'
return this.useSocialText ? '进入你的同城半径' : '进入你的校园半径'
},
loginTipText() {
return this.isSocialArea ? '轻松进入同城生活世界' : '轻松进入校园生活世界'
return this.useSocialText ? '轻松进入同城生活世界' : '轻松进入校园生活世界'
},
defaultWechatName() {
return this.isSocialArea ? '半径里好友' : '半径里同学'
return this.useSocialText ? '半径里好友' : '半径里同学'
},
profileDesc() {
return this.isSocialArea ? '用于完善半径里同城身份' : '用于完善半径里校园身份'
return this.useSocialText ? '用于完善半径里同城身份' : '用于完善半径里校园身份'
}
},
onLoad(option) {
@ -253,6 +258,7 @@
}
const kind = area ? Number(area.areaKind) : 1
this.areaKind = kind === 2 ? 2 : 1
this.campusTextEnabled = resolveCampusTextEnabled(area)
},
wxLogin() {
if (!this.xieyidata) {

Loading…
Cancel
Save