Files
product-uniapp/pages/classify/classify.vue
T
2025-12-12 18:34:44 +08:00

630 lines
15 KiB
Vue
Raw 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 class="page">
<uni-nav-bar leftText=" " :fixed="true" :border="false" @clickLeft="leftClick" color="#000" backgroundColor="#fff" :statusBar="true" title="样机分类"></uni-nav-bar>
<!-- 顶部 tabs -->
<view class="tabs">
<view :class="['tab', currentTab==='latest'?'on':'']" @click="switchTab('latest')">最新发布</view>
<view class="divider"></view>
<view :class="['tab', currentTab==='near'?'on':'']" @click="switchTab('near')">距离最近</view>
</view>
<view class="content-con">
<!-- 左侧分类 -->
<scroll-view scroll-y class="left-aside">
<view
v-for="item in flist"
:key="item.id"
class="f-item"
:class="{active: item.id === currentCategoryId}"
@click="onCategoryChange(item.id)"
>
<view class="cat-text">{{ item.name }}</view>
</view>
</scroll-view>
<!-- 右侧列表 -->
<scroll-view
scroll-y
class="right-aside"
refresher-enabled
:refresher-triggered="refreshing"
@refresherrefresh="onRefresh"
:lower-threshold="100"
@scrolltolower="onLoadMore"
>
<view class="list">
<classify-card
v-for="item in slist"
:key="item.id"
:item="item"
@view="navToList"
@start="startLink"
/>
<view v-if="hasMore" class="load-more">
<text v-if="loadingMore">加载中...</text>
<text v-else>上拉加载更多</text>
</view>
<view v-else-if="slist.length > 0" class="load-more">
<text>没有更多了</text>
</view>
<view class="empty" v-if="slist.length === 0 && !loading">
<image src="/static/image/empty.png" mode="aspectFit"></image>
<view>暂无数据!</view>
</view>
</view>
</scroll-view>
</view>
<!-- 加载提示 -->
<view v-if="loading" class="loading-mask">
<view class="loading-text">加载中...</view>
</view>
<u-popup :show="showlogin" :closeOnClickOverlay="false" :round="20" mode="center">
<view class="popupcontent">
<image class="log1" src="/static/image/log1.png"></image>
<view class="title">{{$t('home.loginmsg')}}</view>
<view class="yhxy">
<checkbox-group name="" style="width: 28px;display: inline-block;">
<label @tap="selfChangde()">
<checkbox :checked="selfChecked" activeBackgroundColor="#3F435B"
style="transform:scale(0.7);border-radius: 50%" />
</label>
</checkbox-group>
{{$t('home.argin')}}<text class="xy" @click="gouser">{{$t('home.UserAgreement')}}</text>{{$t('home.and')}}<text
class="xy" @click="goprivacy">{{$t('home.Privacy')}}</text>
</view>
</view>
<view class="cent">
<button class="btn1" @click="showlogin = !showlogin">{{$t('cancel')}}</button>
<button v-if="selfChecked" class="btn" open-type="getPhoneNumber" @click="showlogin = !showlogin"
@getphonenumber="getPhoneNumber">{{$t('login')}}</button>
<button v-else class="btn" @click="loginbtn">{{$t('login')}}</button>
</view>
</u-popup>
</view>
</template>
<script>
import { request } from '@/api/apiT.js'
import SquarePostCard from '@/components/SquarePostCard.vue'
import ClassifyCard from '@/components/ClassifyCard.vue'
export default {
components: {
SquarePostCard,
ClassifyCard
},
data() {
return {
showlogin: false,
tabs: [
{ key: 'latest', label: '最新发布' },
{ key: 'near', label: '距离最近' }
],
currentTab: 'latest',
flist: [],
slist: [],
selfChecked: false,
currentCategoryId: '', // 当前选中的分类id
loading: false, // 加载状态
refreshing: false, // 下拉刷新状态
loadingMore: false, // 上拉加载状态
page: 1, // 当前页码
pageSize: 10, // 每页数量
hasMore: true, // 是否还有更多数据
categoriesLoaded: false // 分类是否已加载完成
}
},
onLoad() {
// 加载第一级分类列表
},
onShow() {
this.loadCategoryList();
// this.syncTabbar()
// // 从详情页返回后,重新拉取当前分类数据
// if (this.categoriesLoaded) {
// this.refreshCurrentCategory()
// }
},
methods: {
goprivacy(){
uni.navigateTo({
url: '/pages/login/agreement',// 替换为你要跳转的页面路径
});
},
selfChangde(e) {
this.selfChecked = !this.selfChecked;
},
loginbtn(){
if(!this.selfChecked){
uni.showToast({
title:this.$t('home.arginimg'),
icon: 'none'
});
return false
}
},
getPhoneNumber(e) {
let that = this;
request({
url:'miniapp/phone',
method:'POST',
data:{
phone_code: e.detail.code,
}
}).then(res => {
if (res.data.code == -1) {
uni.showToast({
title: res.data.msg,
icon: 'none'
});
} else if (res.data.code == 200) {
//返回数据接收成功
uni.setStorageSync('is_login', true);
}
})
},
// 加载第一级分类列表
loadCategoryList() {
// TODO: 替换为实际的后端接口
let that = this;
request({
url:'common/post/category/list',
method:'GET'
}).then(res => {
if (res.data.code == 200) {
const records = res.data.data.records || [];
this.flist = [{
id: '',
name: '全部分类'
}, ...records];
this.categoriesLoaded = true;
// 默认加载第一个分类的数据
if (this.flist.length > 0) {
this.onCategoryChange(this.flist[0].id);
}
}
})
},
// 重新加载当前分类数据
refreshCurrentCategory() {
const targetCategory = this.currentCategoryId !== undefined ? this.currentCategoryId : (this.flist[0] && this.flist[0].id) || '';
this.loadCategoryData(targetCategory, false);
},
// 分类切换事件
onCategoryChange(categoryId) {
// 如果点击的是当前分类且已有数据,不重复加载
if (this.currentCategoryId === categoryId && this.slist.length > 0) {
return;
}
// 切换分类时,重置所有状态
this.currentCategoryId = categoryId;
this.page = 1;
this.hasMore = true;
this.loading = false;
this.loadingMore = false;
this.refreshing = false;
this.slist = []; // 先清空数据
this.loadCategoryData(categoryId, false);
},
// 根据分类id加载分类下的内容
loadCategoryData(categoryId, isLoadMore = false) {
// 防止重复请求:如果是加载更多,需要检查所有加载状态
if (isLoadMore) {
if (this.loadingMore || this.loading || this.refreshing) {
return;
}
} else {
// 下拉刷新或初次加载,检查加载状态
if (this.loading || this.loadingMore) {
return;
}
}
if (isLoadMore) {
this.loadingMore = true;
this.page += 1;
} else {
this.loading = true;
this.page = 1;
this.hasMore = true;
this.slist = []; // 清空之前的数据
}
request({
url:'common/post/list',
method:'GET',
data:{
category_id:categoryId,
current: this.page,
size: this.pageSize
}
}).then(res => {
this.loading = false;
this.loadingMore = false;
this.refreshing = false;
if (res.data.code == 200) {
const records = res.data.data.records || [];
const total = res.data.data.total || 0;
if (isLoadMore) {
// 上拉加载更多,追加数据并去重
const existingIds = new Set(this.slist.map(item => item.id));
const newRecords = records.filter(item => !existingIds.has(item.id));
this.slist = [...this.slist, ...newRecords];
} else {
// 下拉刷新或初次加载,替换数据
this.slist = records;
}
// 判断是否还有更多数据
this.hasMore = this.slist.length < total;
}
}).catch(() => {
this.loading = false;
this.loadingMore = false;
this.refreshing = false;
if (isLoadMore) {
this.page -= 1; // 加载失败,页码回退
}
})
},
// 下拉刷新
onRefresh() {
// 如果正在加载中,不执行刷新
if (this.loading || this.loadingMore) {
this.refreshing = false;
return;
}
this.refreshing = true;
this.loadCategoryData(this.currentCategoryId, false);
},
// 上拉加载更多
onLoadMore() {
if (!this.hasMore || this.loadingMore || this.loading || this.refreshing) {
return;
}
this.loadCategoryData(this.currentCategoryId, true);
},
// tab 切换
switchTab(key) {
if (this.currentTab === key) return
this.currentTab = key
this.onRefresh()
},
// 点击商品项
navToList(item) {
uni.navigateTo({
url: '/pages/square/detiles?id='+item.id
});
},
islogin(){
if(uni.getStorageSync('is_login')){
return true
}else{
return false
}
},
//收藏
startLink(item){
if(!this.islogin()){
this.showlogin = true
return false
}
const targetIndex = this.slist.findIndex(post => post.id === item.id)
if(targetIndex === -1){
return
}
const target = this.slist[targetIndex]
const collectUrl = item.is_favorite ? 'common/post/favorite/destroy' : 'common/post/favorite/store'
const method = item.is_favorite ? 'GET' : 'POST'
const data = {
post_id: item.id
}
if (item.is_favorite) {
data.user_id = item.user_id
}
request({
url: collectUrl,
method,
data
}).then(res => {
if(res.data.code === 200){
const newTarget = { ...target }
if(newTarget.is_favorite){
newTarget.is_favorite = 0
uni.showToast({
title: '取消收藏成功',
icon: 'none'
});
}else{
newTarget.is_favorite = 1
uni.showToast({
title: '收藏成功',
icon: 'none'
});
}
this.$set(this.slist, targetIndex, newTarget)
}else{
uni.showToast({
title:res.data.message,
icon: 'none'
});
}
})
},
//点赞
navTozan(item){
const targetIndex = this.slist.findIndex(post => post.id === item.id)
if(targetIndex === -1){
return
}
const target = this.slist[targetIndex]
const isLiked = Number(target.is_like) === 1
const likeUrl = isLiked ? 'common/post/like/destroy' : 'common/post/like/store'
const method = isLiked ? 'GET' : 'POST'
const data = {
post_id: target.id
}
if (isLiked) {
data.user_id = target.user_id
}
request({
url: likeUrl,
method,
data
}).then(res => {
if(res.data.code === 200){
const newTarget = { ...target }
if(isLiked){
newTarget.is_like = 0
newTarget.like = Math.max(0, (Number(newTarget.like) || 0) - 1)
uni.showToast({
title: '取消成功',
icon: 'none'
});
}else{
newTarget.is_like = 1
newTarget.like = (Number(newTarget.like) || 0) + 1
uni.showToast({
title: '点赞成功',
icon: 'none'
});
}
this.$set(this.slist, targetIndex, newTarget)
}else{
uni.showToast({
title:res.data.message,
icon: 'none'
});
}
})
},
//消息
navTomsg(item){
request({
url:'common/like/store',
method:'GET',
data:{
post_id:item.id
}
}).then(res => {
})
},
leftClick(){
uni.navigateBack()
},
syncTabbar(){
uni.setTabBarItem({ index:0, text:this.$t('tabbar.home') })
uni.setTabBarItem({ index:1, text:this.$t('tabbar.square') })
uni.setTabBarItem({ index:2, text:this.$t('tabbar.publish') })
uni.setTabBarItem({ index:3, text:this.$t('tabbar.mine') })
}
}
}
</script>
<style lang='scss' scoped>
page,
.page {
height: 100%;
/* background: #f7f7f7; */
}
.tabs {
height: 80rpx;
background: #fff;
display: flex;
align-items: center;
justify-content: space-around;
gap: 24rpx;
position: relative;
font-size: 28rpx;
color: #666;
}
.tab {
position: relative;
padding: 12rpx 20rpx 18rpx;
color: #333;
}
.tab::after {
content: '';
position: absolute;
left: 50%;
bottom: 4rpx;
width: 50rpx;
height: 4rpx;
background: #05b3a8;
border-radius: 4rpx;
transform: translateX(-50%) scaleX(0);
transition: transform 0.2s;
}
.tab.on {
color: #05b3a8;
font-weight: 600;
}
.tab.on::after {
transform: translateX(-50%) scaleX(1);
}
.divider {
width: 1px;
height: 26rpx;
background: #dcdcdc;
}
.content-con {
height: calc(100% - 80rpx);
display: flex;
}
.left-aside {
width: 200rpx;
background: #3BC6BD;
color: #000;
}
.f-item {
height: 96rpx;
/* padding-left: 28rpx; */
display: flex;
align-items: center;
justify-content: center;
gap: 12rpx;
color: #000;
}
.category-icon image {
width: 32rpx;
height: 32rpx;
}
.cat-text {
font-size: 26rpx;
width: 80%;
text-align: center;
padding: 14rpx 0;
border-radius: 10rpx;
}
.f-item.active .cat-text{
background: #F5F6FA;
color: #000;
}
.right-aside {
flex: 1;
/* background: #fff; */
}
.list {
padding: 16rpx 16rpx 40rpx;
}
.load-more {
width: 100%;
text-align: center;
padding: 30rpx 0;
font-size: 24rpx;
color: #999;
}
.empty {
width: 100%;
text-align: center;
padding: 80rpx 0;
}
.empty image {
width: 200rpx;
height: 140rpx;
margin-bottom: 12rpx;
}
.loading-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.3);
display: flex;
align-items: center;
justify-content: center;
z-index: 999;
.loading-text {
color: #fff;
font-size: 28upx;
background-color: rgba(0, 0, 0, 0.7);
padding: 20upx 40upx;
border-radius: 8upx;
}
}
.popupcontent{
background-size: 100% 100%;
padding: 26px 15px;
text-align: center;
background-repeat: no-repeat;
.title{
color: #333333;
font-size: 15px;
margin-bottom: 15px;
}
}
.log1{
width: 180px;
height: 180px;
}
.yhxy{
color: #999999;
font-size: 12px;
margin-bottom: 5px;
.xy{
color: #3BC6BD;
}
}
.share{
background-color: #fff;
border: 1px solid #fff;
height: 34px;
line-height: 34px;
width: 30px;
position: absolute;
right: 0px;
padding: 0px;
top: 8px;
}
.share:after{
border: 1px solid #fff;
}
.share image{
width: 24px;
height: 24px;
}
.cent{
display: flex;
align-items: center;
justify-content: space-around;
padding: 0px 10px 20px;
.btn1{
background-color: #fff;
border: 1px solid #3BC6BD;
color: #3BC6BD;
border-radius: 50px;
width: 46%;
margin: 10px 0px 20px;
height: 50px;
line-height: 50px;
}
.btn{
background-color: #3BC6BD;
color: white;
border-radius: 50px;
width: 46%;
margin: 10px 0px 20px;
height: 50px;
line-height: 50px;
}
}
</style>