Files
product-uniapp/pages/classify/classify.vue
T

669 lines
16 KiB
Vue
Raw Normal View History

2025-12-12 18:34:44 +08:00
<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: '距离最近' }
],
2025-12-13 17:30:18 +08:00
latitude:'',
longitude:'',
currentTab:'latest',
2025-12-12 18:34:44 +08:00
flist: [],
slist: [],
selfChecked: false,
currentCategoryId: '', // 当前选中的分类id
loading: false, // 加载状态
refreshing: false, // 下拉刷新状态
loadingMore: false, // 上拉加载状态
page: 1, // 当前页码
pageSize: 10, // 每页数量
hasMore: true, // 是否还有更多数据
categoriesLoaded: false // 分类是否已加载完成
}
},
onLoad() {
// 加载第一级分类列表
},
onShow() {
this.loadCategoryList();
2025-12-13 17:30:18 +08:00
this.getFuzzy().then(res => {
this.latitude = res.latitude
this.longitude = res.longitude
console.log(res);
}).catch(err => console.error(err));
2025-12-12 18:34:44 +08:00
// this.syncTabbar()
// // 从详情页返回后,重新拉取当前分类数据
// if (this.categoriesLoaded) {
// this.refreshCurrentCategory()
// }
},
methods: {
2025-12-13 17:30:18 +08:00
getFuzzy() {
return new Promise((resolve, reject) => {
wx.getSetting({
success: (st) => {
const ok = st.authSetting['scope.userFuzzyLocation'];
const run = () => wx.getFuzzyLocation({
type: 'wgs84',
success: resolve,
fail: reject
});
if (ok) return run();
wx.authorize({
scope: 'scope.userFuzzyLocation',
success: run,
fail: () => wx.openSetting({ success: () => reject(new Error('未授权')) })
});
},
fail: reject
});
});
},
2025-12-12 18:34:44 +08:00
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) {
2025-12-13 17:30:18 +08:00
const collectUrl = this.currentTab=='latest' ? 'common/post/list' : 'common/post/nearby'
2025-12-12 18:34:44 +08:00
// 防止重复请求:如果是加载更多,需要检查所有加载状态
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 = []; // 清空之前的数据
}
2025-12-13 17:30:18 +08:00
var that = this;
const params = {
current:this.page,
size:10,
status:1,
category_id:categoryId
};
// 添加排序参数(如果后端支持)
if(this.currentTab=='near'){
params.lng = this.longitude;
params.lat = this.latitude;
} else {
}
2025-12-12 18:34:44 +08:00
request({
2025-12-13 17:30:18 +08:00
url:collectUrl,
2025-12-12 18:34:44 +08:00
method:'GET',
2025-12-13 17:30:18 +08:00
data:params
2025-12-12 18:34:44 +08:00
}).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>