wx-shop/pages/index/index.vue
2025-12-09 22:57:49 +08:00

1052 lines
22 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view>
<view class="header flex justify-between">
<view class="header-left">
<image class="icon" src="/static/images/home/icon-1.png"></image>
<!-- <text>香氛团购</text> -->
</view>
<view class="header-right flex">
<view class="search-trigger" @click="toggleSearch">
<text class="iconfont icon-sousuo"></text>
</view>
<view class="search-slide" :class="{ active: searchVisible }">
<input
class="search-slide-input"
v-model="searchKeyword"
placeholder="搜索商品"
confirm-type="search"
@confirm="handleSearch"
/>
<view class="search-close" @click.stop="closeSearch">
<text class="iconfont icon-guanbi"></text>
</view>
</view>
<!-- <view>
<text class="iconfont icon-aixin">
</text>
</view> -->
<button class="icon-btn" open-type="share" data-share-type="app" @click.stop="currentShareItem = null">
<text class="iconfont icon-zhifeiji1"></text>
</button>
</view>
</view>
<view class="container">
<view class="search flex justify-between">
<view class="search-left flex">
<view class="recommend" :class="{ active: searchType == 1 }" @click="changeSearch(1)">
<text class="iconfont icon-tubiaoshangshengqushi"></text>
推荐产品
</view>
<view class="group-buying" :class="{ active: searchType == 2 }" @click="changeSearch(2)">
<text class="iconfont icon-aixin"></text>
拼团活动
</view>
</view>
<view class="search-right flex">
<view :class="{ active: searchType2 == 1 }" @click="changeSearch2(1)">
<text class="iconfont icon-fenlei">
</text>
</view>
<view :class="{ active: searchType2 == 2 }" @click="changeSearch2(2)">
<text class="iconfont icon-caidan">
</text>
</view>
<view :class="{ active: searchType2 == 3 }" @click="changeSearch2(3)">
<text class="iconfont icon-shaixuan1">
</text>
</view>
</view>
</view>
<view class="shop-list" :class="{ 'grid-mode': searchType2 == 1 }" v-if="searchType == 1">
<view class="shop-item" :class="{ 'grid-card': searchType2 == 1 }" v-for="(item, index) in shopList" :key="item.id" @click="goShopDetail(item)">
<view class="shop-image">
<image :src="item.main_image_url" :mode="searchType2 == 1 ? 'aspectFill' : 'aspectFit'"></image>
<view class="shop-tag flex" v-if="searchType2 != 1">
<view class="tag danger" v-if="item.is_hot_selling == 1">
<text class="iconfont icon-tubiaoshangshengqushi"></text>热销
</view>
<view class="tag warning">
-30%
</view>
</view>
<view class="grid-share" v-if="searchType2 == 1">
<button class="share-btn" open-type="share" :data-item="item" @click.stop="currentShareItem = item">
<text class="iconfont icon-zhifeiji1"></text>
</button>
</view>
</view>
<view class="shop-container" v-if="searchType2 != 1">
<view class="sku-title">
{{ item.name }}
</view>
<view class="sku-description">
{{ item.description }}
</view>
<view class="sku-rating">
<text class="iconfont icon-xingxing"></text>
<text class="sku-rating-num">{{ item.rating }}</text>
<text class="sku-rating-text">156条评价 </text>
<text class="sku-rating-text"> {{ item.like_count }} 人喜欢</text>
</view>
<view class="sku-price" v-if="item.skus && item.skus.length > 0">
<text>{{ formatPrice(item.skus[0].price) }}</text>
<text class="original-price">{{ formatPrice(item.skus[0].original_price) }}</text>
</view>
<view class="sku-operation flex" v-if="isLoggedIn">
<button class="sku-num" @click.stop="handelLike(item)">
<text v-if="item.is_liked" class="iconfont icon-xin heart-filled"></text>
<text v-else class="iconfont icon-xin1 heart-outline"></text>
{{ item.like_count }}
</button>
<button class="share-btn" open-type="share" :data-item="item"
@click.stop="currentShareItem = item">
<text class="iconfont icon-zhifeiji1"></text>
</button>
<button class="shop-cart" @click.stop="handleAddToCart(item)">
<text class="iconfont icon-gouwuche"></text> 加入购物车
</button>
</view>
</view>
<view class="grid-info" v-else>
<view class="grid-title">
{{ item.name }}
</view>
<view class="grid-bottom flex justify-between">
<view class="grid-price" v-if="item.skus && item.skus.length > 0">
{{ formatPrice(item.skus[0].price) }}
</view>
<view class="grid-cart" @click.stop="handleAddToCart(item)">
<text class="iconfont icon-gouwuche"></text>
</view>
</view>
</view>
</view>
</view>
<view class="shop-list" v-if="searchType == 2">
<view class="shop-item">
<view class="shop-image">
<image src=""></image>
<view class="shop-tag flex">
<view class="tag danger">
<text class="iconfont icon-shandian"></text>拼团中
</view>
<view class="tag danger">
<text class="iconfont icon-tubiaoshangshengqushi"></text>热销
</view>
<view class="tag warning">
-30%
</view>
</view>
<view class="shop-tag flex buttom">
<view class="tag danger">
<text class="iconfont icon-shijian"></text>即将结束
</view>
</view>
</view>
<view class="shop-container">
<view class="sku-title">
3人拼团 玫瑰香水
</view>
<view class="sku-group-buying flex justify-between">
<view class="group-img">
</view>
<view class="">
<view class="group-name">
团长香香公主
</view>
<view class="group-text">
<text class="iconfont icon-yonghu"></text>还差1人成团
</view>
</view>
<view class="sku-group-rating">
<view class="group-rating">
<text class="iconfont icon-xingxing"></text>
<text class="sku-rating-num">4.6</text>
</view>
<view class="group-text">
信誉团长
</view>
</view>
</view>
<view class="sku-group-price flex justify-between">
<view class="group-price">
<view class="group-price-num">
<text>89</text>
<text class="original-price">129</text>
</view>
<view class="group-price-text">
拼团价
</view>
</view>
<view class="group-price-save">
<view class="save-tag">
<view>
<text class="iconfont icon-lihe"></text>
63
</view>
</view>
<view class="save-num">
立省30%
</view>
</view>
</view>
<view class="group-progress">
<view class="group-progress-num flex justify-between">
<text class="group-progress-text">拼团进度</text>
<text class="number">2/3 </text>
</view>
<view class="group-progress-width">
<progress :percent="50" stroke-width="20rpx" border-radius="20"
activeColor="#9810fa" />
</view>
<view class="group-progress-num font-text flex justify-between">
<text class="group-progress-text">已参团2人</text>
<text class="group-progress-text">还需1人</text>
</view>
</view>
<view class="sku-operation flex sku-operation2">
<view class="sku-num">
<text class="iconfont icon-aixin"></text> 423
</view>
<view class="">
<text class="iconfont icon-zhifeiji1"></text> 45
</view>
<view class="shop-cart">
<text class="iconfont icon-yonghu"></text> 立即参团
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import request from '/api/request';
import { parseDescription } from '/tools/format';
export default {
data() {
return {
searchType: 1,
searchType2: 1,
page: 1,
page_size: 10,
category_id: '',
name: '',
is_hot_selling: '',
is_limited: '',
shopList: '',
currentShareItem: null,
isLoggedIn: false,
searchVisible: false,
searchKeyword: '',
}
},
onLoad() {
this.checkLoginStatus()
this.getShopList()
},
onShow() {
// 保存之前的登录状态
const previousLoginStatus = this.isLoggedIn
// 检查当前登录状态
this.checkLoginStatus()
// 如果从未登录变为已登录,重新加载商品列表
if (!previousLoginStatus && this.isLoggedIn) {
this.getShopList()
}
},
// 微信小程序分享
onShareAppMessage(res) {
const shareType = res && res.target && res.target.dataset && res.target.dataset.shareType
// 菜单或右上角按钮:分享小程序首页
if (shareType === 'app' || (res && res.from === 'menu')) {
return {
title: '香氛团购',
// imageUrl: '/static/images/home/icon-1.png',
path: '/pages/index/index'
}
}
// 商品卡片分享
const item = this.currentShareItem || (res.target && res.target.dataset && res.target.dataset.item) || {}
return {
title: item.name || '香氛团购',
imageUrl: item.main_image_url || '/static/images/home/icon-1.png',
path: item.id ? `/pages/shopDetail/index?id=${item.id}` : '/pages/index/index'
}
},
methods: {
// 检查登录状态
checkLoginStatus() {
const token = uni.getStorageSync('access_token')
this.isLoggedIn = !!token
},
// 格式化价格
formatPrice(value) {
if (!value) {
return 0.00;
}
// 分转换为元
value = value / 100;
// 保留两位小数
return value.toFixed(2);
},
async getShopList() {
// 检查用户是否登录
const token = await uni.getStorageSync('access_token')
// 根据登录状态选择不同的接口
const apiUrl = token ? 'xcx/auth_products' : 'xcx/products'
request(apiUrl, 'get', {
page: this.page,
page_size: this.page_size,
name: this.searchKeyword
}).then((res) => {
this.shopList = res.list
console.log(this.shopList )
}).catch((err) => {
// token 失效,降级到无需 token 的接口
if (err && (err.code == 10101 || err.code == 10103)) {
request('xcx/products', 'get', {
page: this.page,
page_size: this.page_size,
name: this.searchKeyword
}).then((res) => {
this.shopList = res.list
})
}
})
},
async userIsLogin() {
const token = await uni.getStorageSync('access_token')
if (token) {
return
} else {
uni.navigateTo({
url: '/pages/login/index'
})
}
},
changeSearch(index) {
this.searchType = index
},
changeSearch2(index) {
this.searchType2 = index
},
toggleSearch() {
this.searchVisible = !this.searchVisible
if (!this.searchVisible) {
this.searchKeyword = ''
this.page = 1
this.getShopList()
}
},
closeSearch() {
this.searchVisible = false
this.searchKeyword = ''
this.page = 1
this.getShopList()
},
handleSearch() {
this.page = 1
this.getShopList()
},
async handelLike(row) {
this.userIsLogin().then((res) => {
request('xcx/product/like', 'post', {
product_id: row.id,
type: row.is_liked ? 2 : 1
}).then((res) => {
const liked = !row.is_liked
row.is_liked = liked
if (liked) {
row.like_count += 1
} else if (row.like_count > 0) {
row.like_count -= 1
}
})
})
},
async goShopDetail(item) {
// 检查登录状态
const token = await uni.getStorageSync('access_token')
if (!token) {
// 未登录,保存商品信息和跳转路径
await uni.setStorageSync('product_info', item)
await uni.setStorageSync('redirect_url', `/pages/shopDetail/index?id=${item.id}`)
// 跳转到登录页
uni.navigateTo({
url: '/pages/login/index'
})
return
}
// 已登录,直接跳转详情页
await uni.setStorageSync('product_info', item)
uni.navigateTo({
url: `/pages/shopDetail/index?id=${item.id}`
})
},
// 加入购物车
async handleAddToCart(item) {
// 检查登录状态
const token = await uni.getStorageSync('access_token')
if (!token) {
uni.showToast({
title: '请先登录',
icon: 'none'
})
uni.navigateTo({
url: '/pages/login/index'
})
return
}
// 检查是否有SKU
if (!item.skus || item.skus.length === 0) {
uni.showToast({
title: '该商品暂无库存',
icon: 'none'
})
return
}
// 获取第一个SKU
const firstSku = item.skus[0]
if (!firstSku.sku_id && !firstSku.id) {
uni.showToast({
title: '商品信息不完整',
icon: 'none'
})
return
}
try {
// 调用加入购物车接口
await request('xcx/cart', 'POST', {
product_id: item.id,
sku_id: firstSku.sku_id || firstSku.id,
quantity: 1
})
uni.showToast({
title: '已加入购物车',
icon: 'success'
})
} catch (error) {
console.error('加入购物车失败:', error)
uni.showToast({
title: error.message || '加入购物车失败',
icon: 'none'
})
}
},
// handeShare(item) {
// // 在微信小程序中无法通过代码直接拉起分享面板,
// // 需要使用 open-type="share" + onShareAppMessage
// console.log(item)
// }
}
}
</script>
<style lang="scss" scoped>
.header {
padding: 30rpx;
box-shadow: 0 2rpx 6rpx 0 rgb(0 0 0 / 0.1), 0 2rpx 4rpx -2rpx rgb(0 0 0 / 0.1);
}
.header-left {
.icon {
width: 50rpx;
height: 50rpx;
vertical-align: middle;
margin-right: 20rpx;
}
text {
font-size: 30rpx;
color: #9810fa;
}
}
.header-right {
position: relative;
view {
box-shadow: none;
padding: 10rpx 10rpx;
height: 40rpx;
border: 0 solid transparent;
}
view+view {
margin-left: 20rpx;
}
}
.icon-btn {
border: none;
background: transparent;
padding: 0;
margin: 0 0 0 20rpx;
width: 60rpx;
height: 60rpx;
border-radius: 20rpx;
display: flex;
align-items: center;
justify-content: center;
box-shadow: none;
line-height: 1;
position: relative;
z-index: 30;
}
.icon-btn::after {
border: none !important;
}
.search-trigger {
display: flex;
align-items: center;
justify-content: center;
}
.search-slide {
position: absolute;
right: 56rpx; /* 留出分享按钮区域,避免遮挡 */
top: 0;
height: 72rpx;
display: flex;
align-items: center;
overflow: hidden;
width: 0;
opacity: 0;
transition: all 0.25s ease;
z-index: 10;
}
.search-slide.active {
width: 460rpx;
opacity: 1;
}
.search-slide-input {
flex: 1;
height: 72rpx;
padding: 0 20rpx;
background: #fff;
border-radius: 12rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
font-size: 28rpx;
color: #333;
box-sizing: border-box;
}
.search-close {
width: 64rpx;
height: 72rpx;
display: flex;
align-items: center;
justify-content: center;
color: #999;
background: #fff;
border-radius: 0 12rpx 12rpx 0;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
}
.container {
padding: 40rpx 32rpx;
.search {
.search-left {
padding: 6rpx;
border-radius: 24rpx;
box-shadow: 0 2rpx 6rpx 0 rgb(0 0 0 / 0.1), 0 2rpx 4rpx -2rpx rgb(0 0 0 / 0.1);
view {
border-radius: 20rpx;
padding: 12rpx 12rpx;
font-size: 28rpx;
text {
margin: 0 6rpx;
}
}
.recommend {
image {
width: 40rpx;
height: 40rpx;
vertical-align: middle;
}
}
.group-buying {}
.active {
background-image: var(--background-linear-gradient);
color: #fff;
}
}
.search-right {
padding-top: 10rpx;
view {
width: 60rpx;
height: 60rpx;
border-radius: 20rpx;
line-height: 60rpx;
text-align: center;
font-size: 40rpx;
display: flex;
align-items: center;
justify-content: center;
}
view+view {
margin-left: 10rpx;
}
.active {
background-color: #f3e8ff;
color: #9810fa;
}
}
}
.shop-list {
margin-top: 60rpx;
.shop-item {
margin-top: 30rpx;
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
border-radius: 0 0 36rpx 36rpx;
image {
display: block;
width: 100%;
height: 400rpx;
}
.shop-container {
padding: 80rpx 32rpx;
.shop-tags {
.tag {
font-size: 20rpx;
color: #9810fa;
border: 2rpx solid #e9d4ff;
padding: 4rpx 10rpx;
border-radius: 12rpx;
}
.tag+.tag {
margin-left: 14rpx;
}
}
.sku-title {
margin-top: 20rpx;
font-size: 36rpx;
font-weight: bold;
}
.sku-description {
margin-top: 12rpx;
font-size: 28rpx;
color: #4a5565;
}
.sku-rating {
margin-top: 40rpx;
.iconfont {
font-size: 32rpx;
color: #f0b100;
margin-right: 20rpx;
}
.sku-rating-text {
font-size: 28rpx;
color: #4a5565;
}
}
.sku-price {
margin-top: 30rpx;
font-size: 48rpx;
color: #e7000b;
font-weight: bold;
.original-price {
margin-left: 20rpx;
font-size: 28rpx;
color: #99a1af;
font-weight: 400;
text-decoration: line-through;
}
}
.sku-operation {
margin-top: 40rpx;
justify-content: space-between;
button {
flex: 1;
border-radius: 12rpx;
text-align: center;
box-shadow: 0rpx 0rpx 4rpx rgb(0 0 0 / 0.2);
height: 70rpx;
line-height: 70rpx;
font-size: 24rpx;
overflow: inherit;
}
.sku-num {
flex: 3;
margin-right: 30rpx;
.iconfont {
margin-right: 8rpx;
}
.heart-filled {
color: #e7000b;
// font-size: 28rpx;
}
.heart-outline {
color: #ccc;
// font-size: 24rpx;
// opacity: 0.8;
}
}
.shop-cart {
color: #fff;
flex: 3;
margin-left: 30rpx;
// color: #fff;
background-image: var(--background-linear-gradient);
.iconfont {
margin-right: 8rpx;
}
}
.share-btn {
font-size: 24rpx;
}
}
.sku-operation2 {
view {
flex: auto;
border: 0;
box-shadow: none;
}
.sku-num {
flex: auto;
}
.shop-cart {
flex: auto;
background-image: none;
background-color: var(--color-danger);
color: #fff;
border-radius: 60rpx;
}
}
}
.shop-image {
position: relative;
image {
z-index: 0;
}
.shop-tag {
position: absolute;
top: 40rpx;
left: 32rpx;
z-index: 1;
.tag {
color: #fff;
height: 48rpx;
line-height: 48rpx;
font-size: 20rpx;
border-radius: 48rpx;
padding: 0 16rpx;
box-shadow: var(--tw-shadow);
text {
vertical-align: middle;
margin-right: 10rpx;
}
}
.tag+.tag {
margin-left: 14rpx;
}
.danger {
background-color: var(--color-danger);
}
.warning {
background-color: var(--color-warning);
}
.success {
background-color: var(--color-success);
}
}
.buttom {
top: auto;
buttom: 40rpx;
text {
font-size: 28rpx;
}
}
}
}
}
.shop-list.grid-mode {
display: flex;
flex-wrap: wrap;
gap: 24rpx;
margin-top: 40rpx;
.shop-item {
width: calc(50% - 12rpx);
margin-top: 0;
border-radius: 12rpx;
overflow: hidden;
box-shadow: 0 6rpx 18rpx rgba(0, 0, 0, 0.06);
}
.shop-image image {
height: 320rpx;
object-fit: cover;
}
.shop-container {
padding: 32rpx 24rpx;
}
.grid-card {
position: relative;
}
.grid-share {
position: absolute;
top: 16rpx;
right: 16rpx;
z-index: 5;
}
.grid-share .share-btn {
width: 64rpx;
height: 64rpx;
border-radius: 50%;
padding: 0;
background: rgba(0,0,0,0.4);
color: #fff;
display: flex;
align-items: center;
justify-content: center;
border: none;
}
.grid-info {
padding: 20rpx;
display: flex;
flex-direction: column;
row-gap: 12rpx;
}
.grid-title {
font-size: 28rpx;
font-weight: 400;
color: #333;
line-height: 1.4;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.grid-bottom {
align-items: center;
}
.grid-price {
font-size: 30rpx;
color: #e7000b;
font-weight: 500;
}
.grid-cart {
width: 64rpx;
height: 64rpx;
border-radius: 50%;
border: none;
background: #f3e8ff;
color: #9810fa;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4rpx 10rpx rgba(0,0,0,0.08);
padding: 0;
}
}
.sku-group-buying {
margin-top: 16rpx;
padding: 24rpx;
border-radius: 16rpx;
background-color: #fbf9fa;
.group-img {
width: 70rpx;
height: 70rpx;
border-radius: 70rpx;
display: flex;
justify-content: center;
align-items: center;
color: #fff;
background-image: var(--background-linear-gradient);
}
.group-name {
font-weight: bold;
font-size: 28rpx;
}
.group-text {
margin-top: 4rpx;
font-size: 24rpx;
color: #6a7282;
text {
margin-right: 8rpx;
font-size: 28rpx;
}
}
.group-rating {
font-size: 28rpx;
color: #f0b100;
margin-right: 20rpx;
font-weight: bold;
text {
margin-right: 8rpx;
}
}
}
.sku-group-price {
margin-top: 32rpx;
padding: 24rpx;
border-radius: 16rpx;
background-color: #fff7ed;
.group-price-num {
font-size: 48rpx;
color: #e7000b;
font-weight: bold;
.original-price {
margin-left: 20rpx;
font-size: 28rpx;
color: #99a1af;
font-weight: 400;
text-decoration: line-through;
}
}
.group-price {
flex: 2;
}
.group-price-save {
flex: 1;
}
.group-price-text {
font-size: 24rpx;
color: #4a5565;
margin-left: 20rpx;
}
.save-tag {
// display: inline-block;
background-color: var(--color-danger);
color: #fff;
font-size: 24rpx;
.iconfont {
margin-right: 6rpx;
display: inline-block;
}
height: 48rpx;
line-height: 48rpx;
padding-left: 40rpx;
padding-right: 10rpx;
border-radius: 48rpx;
text-align: center;
}
.save-num {
margin-top: 16rpx;
font-size: 24rpx;
color: #4a5565;
text-align: right;
}
}
.group-progress {
margin-top: 32rpx;
font-size: 28rpx;
.group-progress-text {
font-size: 24rpx;
}
.number {
color: #9810fa;
font-weight: bold;
}
.group-progress-width {
margin-top: 26rpx;
margin-bottom: 26rpx;
}
.font-text {
color: #6a7282;
}
}
}
</style>