This commit is contained in:
浅念
2025-12-12 18:34:44 +08:00
parent 2c10e621aa
commit 5112803313
49 changed files with 3677 additions and 2571 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
// const baseUrl = "https://api.collect.hs.hnzhwlkj.club/"; //这里放置url域名32044 // const baseUrl = "https://api.collect.hs.hnzhwlkj.club/"; //这里放置url域名32044
// const baseUrl = "https://api.collect.xyh.muzili.top/"; //这里放置url域名32044 // const baseUrl = "https://api.collect.xyh.muzili.top/"; //这里放置url域名32044
const baseUrl = "https://bsh.info.hb.hnzhwlkj.cn/"; //这里放置url域名32044 const baseUrl = "https://cb.hnxinyouhe.com/"; //这里放置url域名32044
// const baseUrl = "https://114.215.24.240:8106/api/v1/"; //这里放置url域名32044 // const baseUrl = "https://114.215.24.240:8106/api/v1/"; //这里放置url域名32044
export function request(options){//封装的requset export function request(options){//封装的requset
let { url,data,method } = options; let { url,data,method } = options;
+1 -1
View File
@@ -4,7 +4,7 @@ export default {
home: 'home', home: 'home',
square: 'square', square: 'square',
publish: 'publish', publish: 'publish',
message: 'message', classify: 'classify',
mine: 'mine' mine: 'mine'
}, },
navbar: { navbar: {
+1 -1
View File
@@ -4,7 +4,7 @@ export default {
home: '首页', home: '首页',
square: '广场', square: '广场',
publish: '发布', publish: '发布',
message: '消息', classify: '分类',
mine: '我的' mine: '我的'
}, },
navbar: { navbar: {
+111
View File
@@ -0,0 +1,111 @@
<template>
<view class="card">
<view class="card-left" @click="$emit('view', item)">
<image class="card-img" :src="thumb" mode="aspectFit"></image>
</view>
<view class="card-right">
<view class="card-title line2" @click="$emit('view', item)">{{ item.content}}</view>
<view class="sku" @click="$emit('view', item)">{{ item.sku.model }}</view>
<view class="meta" @click="$emit('view', item)">
<text class="tag">#{{ item.age}}</text>
</view>
<view class="price-row">
<view @click="$emit('view', item)">
<text class="price">¥{{ item.price}}</text>
<text class="origin">¥{{ item.sku.price }}</text>
</view>
<uni-icons @click="$emit('start', item)" :type="item.is_favorite ? 'star-filled' : 'star'" :color="item.is_favorite ? '#05b3a8' : '#333'" size="18"></uni-icons>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'ClassifyCard',
props: {
item: {
type: Object,
required: true,
default: () => ({})
}
},
computed: {
thumb() {
const img = this.item.media?.[0]?.url_pull || this.item.cover || this.item.image?.[0]?.url_pull
return img || '/static/image/cp.png'
}
}
}
</script>
<style lang="scss" scoped>
.card {
display: flex;
gap: 16rpx;
padding: 16rpx;
background: #fff;
margin-bottom: 20rpx;
border-radius: 20rpx;
}
.card-left {
width: 180rpx;
position: relative;
}
.card-img {
width: 180rpx;
height: 180rpx;
background: #f5f5f5;
border-radius: 12rpx;
}
.card-right {
flex: 1;
display: flex;
flex-direction: column;
gap: 10rpx;
}
.card-title {
font-size: 26rpx;
color: #111;
line-height: 1.5;
}
.line2 {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.sku {
font-size: 22rpx;
color: #999;
}
.price-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10rpx;
}
.price {
font-size: 38rpx;
color: #00bfb2;
font-weight: 700;
margin-right: 10rpx;
}
.origin {
font-size: 22rpx;
color: #ccc;
text-decoration: line-through;
}
.meta {
display: flex;
gap: 10rpx;
}
.tag {
font-size: 22rpx;
color: #3BC6BD;
padding: 4rpx 28rpx;
background: rgb(59 198 189 / 0.1);
border-radius: 6rpx;
}
</style>
+257
View File
@@ -0,0 +1,257 @@
<template>
<view class="card-item">
<view class="user" @click="handleUserClick">
<image :src="item.user.avatar_pull || '/static/image/user.png'" class="avatar"></image>
<view class="info">
<view class="name">{{ item.user.nickname || '用户名' }}</view>
<view class="time">{{ item.created_at }}</view>
</view>
<button v-if="!ismysq" class="chat-btn" @click.stop="handleContact">联系门店</button>
<button
v-else
class="chat-btn"
:class="item.status==1 ? 'chat-btn1' : item.status==2 ? 'chat-btn2' : item.status==3 ? 'chat-btn3' : 'chat-btn4'"
@click.stop="handleContact"
>
{{ item.status==1 ? '已发布' : item.status==2 ? '审核中' : item.status==3 ? '未通过' : '已过期' }}
</button>
</view>
<view class="content" @click="handleView">
<u-read-more :showHeight="80" ref="uReadMore" color="#999999" closeText="查看更多">
<rich-text :nodes="item.content" @load="handleContentLoad"></rich-text>
</u-read-more>
</view>
<!-- <view class="tags" v-if="item.tag && item.tag.length">
<text v-for="(tag, index) in item.tag" :key="index">{{ tag }}</text>
</view> -->
<view class="tags">
<text>#{{ item.age}}</text>
</view>
<view class="imgs" v-if="item.media && item.media.length">
<image
v-for="(img, index) in item.media"
:key="index"
:src="getImgUrl(img)"
class="img"
mode="aspectFill"
@click.stop="previewImage(index)"
@longpress.stop="downloadImage(img)"
/>
</view>
<view class="location" v-if="item.location_name">
<uni-icons type="location" color="#999" size="18"></uni-icons>
<text>{{ item.location_name }}</text>
</view>
<view class="comment" @click="handleComment">
评论({{ item.comment || 0 }})
</view>
</view>
</template>
<script>
import uReadMore from '@/uni_modules/uview-ui/components/u-read-more/u-read-more.vue'
export default {
name: 'PostCard',
components: {
uReadMore
},
props: {
item: {
type: Object,
required: true,
default: () => ({})
},
ismysq:{
type: Boolean,
default: false
}
},
methods: {
getImgUrl(img) {
if (!img) return '';
if (typeof img === 'string') return img;
return img.img_src || img.url_pull || img.url || '';
},
handleContentLoad() {
this.$nextTick(() => {
if (this.$refs.uReadMore) {
this.$refs.uReadMore.init();
}
});
},
handleUserClick() {
this.$emit('userClick', this.item);
},
handleContact() {
this.$emit('contact', this.item);
},
handleView() {
this.$emit('view', this.item);
},
previewImage(index) {
if (!this.item.media || !this.item.media.length) return;
const urls = this.item.media.map(m => this.getImgUrl(m)).filter(Boolean);
uni.previewImage({
current: index,
urls
});
},
downloadImage(img) {
const url = this.getImgUrl(img);
if (!url) return;
uni.showActionSheet({
itemList: ['保存图片'],
success: (res) => {
if (res.tapIndex === 0) {
uni.showLoading({ title: '下载中...', mask: true });
uni.downloadFile({
url,
success: (downloadRes) => {
if (downloadRes.statusCode === 200) {
uni.saveImageToPhotosAlbum({
filePath: downloadRes.tempFilePath,
success: () => {
uni.hideLoading();
uni.showToast({ title: '保存成功', icon: 'success' });
},
fail: () => {
uni.hideLoading();
uni.showToast({ title: '保存失败', icon: 'none' });
}
});
} else {
uni.hideLoading();
uni.showToast({ title: '下载失败', icon: 'none' });
}
},
fail: () => {
uni.hideLoading();
uni.showToast({ title: '下载失败', icon: 'none' });
}
});
}
}
});
},
handleComment() {
this.$emit('comment', this.item);
}
}
}
</script>
<style lang="scss" scoped>
.card-item {
margin-bottom: 40rpx;
background: #fff;
padding: 20rpx;
border-radius: 10rpx;
}
.user {
flex-direction: row;
display: flex;
align-items: center;
.avatar {
width: 110rpx;
height: 110rpx;
border-radius: 50%;
}
.info {
margin-left: 20rpx;
flex: 1;
.name {
font-size: 30rpx;
font-weight: bold;
}
.time {
font-size: 24rpx;
color: #777;
margin-top: 6rpx;
}
}
.chat-btn {
padding: 0 26rpx;
height: 45rpx;
line-height: 45rpx;
border-radius: 10rpx;
background: #3BC6BD;
color: #fff;
font-size: 22rpx;
border: none;
}
.chat-btn1{
background:rgb(59 198 189 / 20%);
color: #3BC6BD;
}
.chat-btn2{
background: rgb(255 0 0 / 20%);
color: #fff;
}
.chat-btn3{
background: rgb(62 94 215 / 20%);
color: #3E5ED7;
}
.chat-btn4{
background: rgb(105 105 105 / 20%);
color: #696969;
}
}
.content {
margin-top: 20rpx;
font-size: 28rpx;
color: #444;
}
.tags {
margin-top: 20rpx;
text {
margin-right: 20rpx;
padding: 10rpx 20rpx;
border-radius: 30rpx;
font-size: 24rpx;
color: #3BC6BD;
}
}
.imgs {
display: flex;
flex-wrap: wrap;
margin-top: 20rpx;
.img {
width: 30%;
margin-right: 3%;
margin-bottom: 3%;
border-radius: 12rpx;
height: 190rpx;
}
}
.location {
display: flex;
align-items: center;
color: #666666;
font-size: 22rpx;
margin-top: 20rpx;
gap: 8rpx;
}
.comment {
border: 1px solid #ccc;
text-align: center;
line-height: 66rpx;
border-radius: 34rpx;
margin: 20rpx 0;
font-size: 26rpx;
color: #666;
}
</style>
+124
View File
@@ -0,0 +1,124 @@
<template>
<view class="danmu-container" v-if="visible">
<view
v-for="item in danmuItems"
:key="item.id"
class="danmu-item"
:style="{
top: item.top + 'rpx',
animationDuration: item.speed + 's',
color: item.color
}"
>
{{ item.text }}
</view>
</view>
</template>
<script>
export default {
props: {
list: Array,
visible: { type: Boolean, default: true },
row: { type: Number, default: 5 } // 轨道数
},
data() {
return {
danmuItems: [],
laneBusy: [] // 轨道是否被占用
}
},
mounted() {
this.laneBusy = Array(this.row).fill(false)
this.startDanmu()
},
methods: {
startDanmu() {
let index = 0
setInterval(() => {
const text = this.list[index].content
this.createDanmu(text)
index = (index + 1) % this.list.length
}, 1500)
},
createDanmu(text) {
const lane = this.getFreeLane()
const id = Date.now() + Math.random()
const item = {
id,
text,
top: lane * 100, // 每条轨道高度 60rpx
speed: 6 + Math.random()*2,
color: this.randomColor(),
lane
}
this.danmuItems.push(item)
setTimeout(() => {
this.finishDanmu(id, lane)
}, item.speed * 1000)
},
getFreeLane() {
// 找到第一个空轨道
for (let i = 0; i < this.row; i++) {
if (!this.laneBusy[i]) {
this.laneBusy[i] = true
return i
}
}
// 如果都满了,就随机一个
return Math.floor(Math.random() * this.row)
},
finishDanmu(id, lane) {
this.danmuItems = this.danmuItems.filter(i => i.id !== id)
// 整个动画跑完后释放轨道
this.laneBusy[lane] = false
},
randomColor() {
const colors = ["#fff", "#00F6FF", "#FFEA00", "#FF66C4"]
return colors[Math.floor(Math.random() * colors.length)]
}
}
}
</script>
<style>
.danmu-container {
position: absolute;
left: 0;
top: 88rpx;
width: 100%;
height: 600rpx;
overflow: hidden;
pointer-events: none;
z-index: 100;
}
.danmu-item {
position: absolute;
right: -400rpx;
font-size: 26rpx;
white-space: nowrap;
animation-name: danmu-move;
animation-timing-function: linear;
animation-fill-mode: forwards;
background: rgb(0 0 0 / 60%);
padding: 5px 14px;
border-radius: 20px;
}
@keyframes danmu-move {
from { transform: translateX(0); }
to { transform: translateX(-1200rpx); }
}
</style>
+10 -1
View File
@@ -100,8 +100,17 @@
"minified" : true, "minified" : true,
"postcss" : true "postcss" : true
}, },
"permission": {
"scope.userFuzzyLocation": {
"desc": "用于为你推荐附近内容/门店"
}
},
"requiredPrivateInfos": [
"getFuzzyLocation",
"chooseLocation",
"chooseAddress"
],
"usingComponents" : true, "usingComponents" : true,
"permission" : {},
"uniStatistics" : { "uniStatistics" : {
"enable" : true "enable" : true
} }
+30 -5
View File
@@ -83,7 +83,17 @@
"enablePullDownRefresh": false, "enablePullDownRefresh": false,
"navigationStyle":"custom"// 去掉微信小程序顶部导航 "navigationStyle":"custom"// 去掉微信小程序顶部导航
} }
},
{
"path" : "pages/classify/classify",
"style" :
{
"navigationBarTitleText" : "分类",
"enablePullDownRefresh": false,
"navigationStyle":"custom"// 去掉微信小程序顶部导航
}
} }
], ],
"subPackages":[ "subPackages":[
{ {
@@ -124,6 +134,15 @@
"enablePullDownRefresh": false, "enablePullDownRefresh": false,
"navigationStyle":"custom"// 去掉微信小程序顶部导航 "navigationStyle":"custom"// 去掉微信小程序顶部导航
} }
},
{
"path" : "my_reply",
"style" :
{
"navigationBarTitleText" : "回复我的",
"enablePullDownRefresh": false,
"navigationStyle":"custom"// 去掉微信小程序顶部导航
}
} }
] ]
} }
@@ -147,7 +166,7 @@
}, },
"tabBar": { "tabBar": {
"color": "#7A7E83", "color": "#7A7E83",
"selectedColor": "#0071FC", "selectedColor": "#3BC6BD",
"borderStyle": "white", "borderStyle": "white",
"backgroundColor": "#fff", "backgroundColor": "#fff",
"list": [ "list": [
@@ -158,10 +177,10 @@
"text": "首页" "text": "首页"
}, },
{ {
"pagePath": "pages/square/index", "pagePath": "pages/classify/classify",
"iconPath": "/static/image/square1.png", "iconPath": "/static/image/classify1.png",
"selectedIconPath": "/static/image/square.png", "selectedIconPath": "/static/image/classify.png",
"text": "广场" "text": "分类"
}, },
{ {
"pagePath": "pages/publish/index", "pagePath": "pages/publish/index",
@@ -169,6 +188,12 @@
"selectedIconPath": "/static/image/publish.png", "selectedIconPath": "/static/image/publish.png",
"text": "发布" "text": "发布"
}, },
{
"pagePath": "pages/square/index",
"iconPath": "/static/image/square1.png",
"selectedIconPath": "/static/image/square.png",
"text": "广场"
},
{ {
"pagePath": "pages/index/mine", "pagePath": "pages/index/mine",
"iconPath": "/static/image/mine1.png", "iconPath": "/static/image/mine1.png",
+630
View File
@@ -0,0 +1,630 @@
<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>
+322 -464
View File
@@ -1,489 +1,347 @@
<template> <template>
<view class="home-page"> <view class="page">
<z-paging ref="paging" :auto="false" v-model="contList" @query="queryList" empty-view-text="暂无数据!"> <z-paging ref="paging" :auto="false" v-model="list" @query="queryList" empty-view-text="暂无数据!">
<!-- <template #top> --> <!-- 顶部背景 -->
<!-- 轮播Banner --> <view class="header">
<swiper class="banner-swiper" circular autoplay :interval="4000" indicator-dots indicator-color="rgba(255,255,255,.4)" <image src="/static/image/homebg.png" mode="aspectFill" class="bg"></image>
indicator-active-color="#3F435B">
<swiper-item v-for="(banner,index) in bannerList" :key="index"> <!-- 弹幕组件 -->
<image class="banner-img" :src="banner.image_url_pull" mode="aspectFill"></image> <danmu :list="danmuList" :row="5" :visible="danmuVisible"></danmu>
<!-- <view class="banner-desc">
<text class="slogan-text">{{banner.slogan}}</text> <!-- 标题 -->
</view> --> <view class="title">
</swiper-item> <view>西门子样机</view>
</swiper> <view>福利平台</view>
<!-- </template> --> </view>
<!-- 内容网格 -->
<view class="content-grid"> <!-- 右侧按钮 -->
<square-post-card <view class="right-btns">
class="grid-item" <view class="circle-btn" :class="{ active: danmuVisible }" @click="toggleDanmu">
v-for="sitem in contList" <image v-if="danmuVisible" src="/static/image/dan1.png" mode=""></image>
:key="sitem.id" <image v-else src="/static/image/danm.png" mode=""></image>
:item="sitem" </view>
:isshow="false" <view class="circle-btn">
@view="navToList" <image src="/static/image/shares.png" class="share" mode=""></image>
@comment="viewPost" </view>
@like="viewPost" /> </view>
</view>
<!-- 主体内容 -->
<view class="main-box">
<view class="titles">
样机广场
</view>
<view class="tabs">
<view :class="['tab',activeTab==0?'on':'']" @tap="tabclick(0)">
<text>最新发布</text>
</view>
<view class="divider"></view>
<view :class="['tab',activeTab==1?'on':'']" @tap="tabclick(1)">
<text>距离最近</text>
</view>
</view>
<!-- 用户卡片 -->
<view class="card">
<post-card
v-for="sitem in list"
:key="sitem.id || sitem.username"
:item="sitem"
@userClick="goto"
@view="goto"
@contact="handleContact"
@comment="handleComment">
</post-card>
</view>
</view> </view>
</z-paging> </z-paging>
<u-popup :show="show" :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="show = !show">{{$t('cancel')}}</button>
<button v-if="selfChecked" class="btn" open-type="getPhoneNumber" @click="show = !show"
@getphonenumber="getPhoneNumber">{{$t('login')}}</button>
<button v-else class="btn" @click="loginbtn">{{$t('login')}}</button>
</view>
</u-popup>
<u-picker :immediateChange="true" :cancel-text="$t('cancel')" :confirm-text="$t('confirm')" @close="close('showlang')"
:show="showlang" :columns="columns" @cancel="cancellang" @confirm="confirmlang" :title="$t('home.langbtn')" keyName="label"></u-picker>
</view> </view>
</template> </template>
<script> <script>
import { request,showToast } from '@/api/apiT.js' import danmu from '@/components/danmu/danmu.vue';
import SquarePostCard from '@/components/SquarePostCard.vue' import PostCard from '@/components/PostCard.vue'
export default { import { request,showToast } from '@/api/apiT.js'
components: { export default {
SquarePostCard components: { danmu, PostCard },
}, data() {
data() { return {
return { activeTab: 0,
title: 'Hello', danmuVisible:true,
selfChecked: false, content:'',
home: this.$t('tabbar.home'), list:[],
is_login:uni.getStorageSync('is_login'), danmuList: [],
show: false, latitude:'',
showlang: false, longitude:''
searchKeyword: '', };
bannerList: [], },
contList: [], onLoad() {
columns: [ // 数据加载逻辑
[{ if (!uni.getStorageSync('token')) {
label: this.$t('zh'), this.mini_login().then(() => {
id: 'zh' this.barragelist()
}, {
label: this.$t('en'),
id: 'en'
},{
label: this.$t('ru'),
id: 'ru'
}]
],
}
},
onLoad() {
// if(uni.getStorageSync('token')){
// // this.$refs.paging && this.$refs.paging.reload()
// }else{
// this.mini_login()
// }
if (!uni.getStorageSync('token')) {
this.mini_login().then(() => {
this.bannerListfn()
this.$refs.paging && this.$refs.paging.reload()
})
}
},
onShow() {
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')
})
if(uni.getStorageSync('token')){
this.bannerListfn()
this.$refs.paging && this.$refs.paging.reload() this.$refs.paging && this.$refs.paging.reload()
} })
}else{
this.barragelist()
this.$refs.paging && this.$refs.paging.reload()
}
},
onShow() {
this.getFuzzy().then(res => {
this.latitude = res.latitude
this.longitude = res.longitude
console.log(res);
}).catch(err => console.error(err));
},
methods: {
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
});
});
}, },
methods: { tabclick(item){
async initPage(){ this.activeTab = item;
await this.mini_login() this.$refs.paging && this.$refs.paging.reload()
await this.bannerListfn() },
}, //获取弹幕
bannerListfn(){ barragelist(){
request({ request({
url:'common/banner/list', url: 'admin/barrage/list',
method:'get', method: 'GET',
data:{ }).then(res => {
module:'home_module' if (res.data.code === 200) {
} this.danmuList = res.data.data.records
}).then(res => { }
this.bannerList = res.data.data })
}) },
}, toggleDanmu() {
// 点击商品项 this.danmuVisible = !this.danmuVisible;
navToList(item) { },
uni.navigateTo({ goto(item){
url: '/pages/square/detiles?id='+item.id uni.navigateTo({
}); url: '/pages/square/detiles?id='+item.id
}, });
viewPost(item){ },
if(uni.getStorageSync('is_login')){ handleContact(item) {
uni.showToast({
title: '联系门店功能',
icon: 'none'
});
},
handleComment(item) {
uni.navigateTo({
url: '/pages/square/detiles?id=' + (item?.id || 1)
});
},
queryList(pageNo, pageSize, from) {
this.loading = true;
var that = this;
const params = {
current:pageNo,
size:10
};
const collectUrl = this.activeTab==0 ? 'common/post/list' : 'common/post/nearby'
// 添加排序参数(如果后端支持)
if(this.activeTab==1){
params.lng = this.longitude;
params.lat = this.latitude;
} else {
}
request({
url:collectUrl,
method:'get',
data: params
}).then(res => {
if(res.data.code ==200){
var datas = res.data.data.records;
this.$refs.paging.complete(datas);
}else{ }else{
this.show = true }
} uni.stopPullDownRefresh();
}, //
cancellang() { })
this.showlang = false },
}, mini_login() {
close(value){ return new Promise((resolve, reject) => {
this[value] = false uni.login({
}, provider: 'weixin',
queryList(pageNo, pageSize) { success: loginRes => {
this.loading = true;
var that = this; // 一定要 return,不然 Promise 不会等待
request({ return request({
url:'common/post/list', url:'miniapp/login',
method:'get', method:'POST',
data: { data:{ code: loginRes.code }
current:pageNo, }).then(res => {
size:10 if (res.data.code === 200) {
}
}).then(res => { uni.setStorageSync('openid', res.data.data.openid);
if(res.data.code ==200){ uni.setStorageSync('token', res.data.data.token);
var datas = res.data.data.records;
this.$refs.paging.complete(datas); if (res.data.data.mobile) {
}else{ uni.setStorageSync('is_login', true);
// this.$refs.paging.complete(null); } else {
// that.listlength=0 uni.setStorageSync('is_login', false);
} }
uni.stopPullDownRefresh();
// resolve(true); // 👍 一定在 Promise 中
}) } else {
}, uni.showToast({
//语言切换事件 title: res.data.msg,
confirmlang(e) { icon: 'none'
var that = this });
this.$i18n.locale = e.value[0].id reject(res);
uni.setLocale(e.value[0].id) }
uni.setStorageSync('i18locale', e.value[0].id); })
this.columns =[ },
[{ fail: (err) => {
label: this.$t('zh'), uni.showToast({
id: 'zh' title: '获取 code 失败',
}, { icon: 'none'
label: this.$t('en'), });
id: 'en' reject(err); // 👍 必须 reject
},{ }
label: this.$t('ru'), });
id: 'ru' });
}]
];
uni.setTabBarItem({
index: 0,
text: this.$t('tabbar.square')
})
uni.setTabBarItem({
index: 1,
text: this.$t('tabbar.publish')
})
uni.setTabBarItem({
index: 2,
text: this.$t('tabbar.mine')
})
this.showlang = false
var languages = e.value[0].id
if(e.value[0].id=='zh'){
languages= 'zh_CN'
}
request({
url:'user/update',
method:'POST',
data:{
language:languages
}
}).then(res => {
uni.showToast({
title: res.data.message,
icon: 'none'
});
})
},
goprivacy(){
uni.navigateTo({
url: '/pages/login/agreement',// 替换为你要跳转的页面路径
});
},
gouser(){
uni.navigateTo({
url: '/pages/login/agreementuser',// 替换为你要跳转的页面路径
});
},
addCar(){
if(uni.getStorageSync('is_login')){
uni.removeStorageSync('draftid')
uni.removeStorageSync('carinfo')
uni.navigateTo({
url: '/pages/carinfo/index'
});
}else{
this.show = true
}
},
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);
}
})
},
mini_login() {
return new Promise((resolve, reject) => {
uni.login({
provider: 'weixin',
success: loginRes => {
// 一定要 return,不然 Promise 不会等待
return request({
url:'miniapp/login',
method:'POST',
data:{ code: loginRes.code }
}).then(res => {
if (res.data.code === 200) {
uni.setStorageSync('openid', res.data.data.openid);
uni.setStorageSync('token', res.data.data.token);
if (res.data.data.mobile) {
uni.setStorageSync('is_login', true);
} else {
uni.setStorageSync('is_login', false);
}
resolve(true); // 👍 一定在 Promise 中
} else {
uni.showToast({
title: res.data.msg,
icon: 'none'
});
reject(res);
}
})
},
fail: (err) => {
uni.showToast({
title: '获取 code 失败',
icon: 'none'
});
reject(err); // 👍 必须 reject
}
});
});
},
} }
} }
}
</script> </script>
<style lang="scss"> <style lang="scss" scoped>
.home-page{ .page {
min-height: 100vh; min-height: 100vh;
background: #F5F6FA; }
box-sizing: border-box;
padding-bottom: 120rpx; .header {
} position: relative;
.brand-header{ height: 720rpx;
display: flex; }
justify-content: space-between; .bg {
align-items: center; width: 100%;
padding: 20rpx 30rpx; height: 100%;
background: #F5F6FA; border-bottom-left-radius: 30rpx;
} border-bottom-right-radius: 30rpx;
.brand-left{ }
display: flex;
align-items: center; .title {
} position: absolute;
.brand-text{ left: 40rpx;
font-size: 28rpx; top: 300rpx;
color: #333; font-size: 68rpx;
margin-right: 10rpx; line-height: 1.2;
} color: #fff;
.sun-icon{ font-weight: bold;
width: 24rpx; }
height: 24rpx;
background: #FFB800; .right-btns {
position: absolute;
right: 40rpx;
bottom: 100rpx;
.circle-btn {
margin-top: 20rpx;
width: 80rpx;
height: 80rpx;
border-radius: 50%; border-radius: 50%;
}
.brand-right{
font-size: 32rpx;
font-weight: 600;
color: #3F435B;
}
.banner-swiper{
width: 100%;
height: 430rpx;
}
.banner-img{
width: 100%;
height: 100%;
}
.banner-desc{
position: absolute;
left: 30rpx;
bottom: 60rpx;
right: 30rpx;
}
.slogan-text{
display: block;
font-size: 48rpx;
font-weight: 600;
color: #51B6FF;
text-shadow: 0 2rpx 8rpx rgba(0,0,0,0.2);
}
.content-grid{
display: flex;
flex-wrap: wrap;
padding: 20rpx;
justify-content: space-between;
background: #fff; background: #fff;
}
.grid-item{
width: 48%;
overflow: hidden;
margin-bottom: 20rpx;
}
.grid-image{
width: 100%;
height: 320rpx;
background: #E5E5E5;
border-radius: 10rpx;
}
.grid-footer{
padding: 20rpx;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center;
image{
width: 48rpx;
height: 48rpx;
}
.share{
width: 36rpx;
height: 36rpx;
}
} }
.grid-avatar{ }
width: 48rpx; /* 激活状态(弹幕开启) */
height: 48rpx; .circle-btn.active {
border-radius: 50%; background: #00c28a;
margin-right: 12rpx; color: #fff;
flex-shrink: 0; }
object-fit: cover; .main-box {
display: block; margin: 20rpx 30rpx;
} margin-top: -60rpx;
.grid-info{ position: relative;
flex: 1; background: #fff;
display: flex; border-radius: 30rpx;
flex-direction: column; .titles{
width: 87%; font-size: 34rpx;
}
.grid-tag{
font-size: 24rpx;
color: #3F435B;
margin-bottom: 4rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 95%;
}
.grid-name{
font-size: 22rpx;
color: #8A8D97;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 88%;
}
.popupcontent{
background-size: 100% 100%;
padding: 26px 15px;
text-align: center; text-align: center;
background-repeat: no-repeat; color: #000;
.title{ font-weight: bold;
color: #333333; line-height: 88rpx;
font-size: 15px;
margin-bottom: 15px;
}
} }
.log1{ }
width: 180px;
height: 180px; .tabs {
display: flex;
align-items: center;
justify-content: space-around;
gap: 20rpx;
margin-bottom: 30rpx;
.tab {
position: relative;
padding: 12rpx 24rpx;
font-size: 28rpx;
color: #000;
} }
.yhxy{ .tab::after {
color: #999999; content: '';
font-size: 12px; position: absolute;
margin-bottom: 5px; left: 50%;
.xy{ bottom: -10rpx;
color: #297EFF; width: 60rpx;
} height: 4rpx;
background: #3BC6BD;
border-radius: 4rpx;
transform: translateX(-50%);
opacity: 0;
transition: opacity 0.2s;
} }
.cent{ .tab.on {
display: flex; color: #3BC6BD;
align-items: center; font-weight: 600;
justify-content: space-around;
padding: 0px 10px 20px;
.btn1{
background-color: #fff;
border: 1px solid #206ff4;
color: #206ff4;
border-radius: 50px;
width: 46%;
margin: 10px 0px 20px;
height: 50px;
line-height: 50px;
}
.btn{
background-color: #206ff4;
color: white;
border-radius: 50px;
width: 46%;
margin: 10px 0px 20px;
height: 50px;
line-height: 50px;
}
} }
</style> .tab.on::after {
opacity: 1;
}
.divider {
width: 1px;
height: 24rpx;
background: #dcdcdc;
}
}
.card {
padding: 10rpx;
}
</style>
+311 -373
View File
File diff suppressed because one or more lines are too long
+163 -6
View File
@@ -13,10 +13,41 @@
<view class="cell1"> <view class="cell1">
<view>{{$t('mine.nickname')}}</view> <view>{{$t('mine.nickname')}}</view>
<view class="right select"> <view class="right select">
<input :placeholder="$t('mine.nickinput')" v-model="userinfo.nickname"></input> <input :placeholder="$t('mine.nickinput')" v-model="userinfo.nickname" />
<uni-icons type="right" size="18"></uni-icons> <uni-icons type="right" size="18"></uni-icons>
</view> </view>
</view> </view>
<view class="cell1">
<view>店铺名称</view>
<view class="right select">
<input placeholder="请填写名称" v-model="userinfo.shop_name" />
<uni-icons type="right" size="18"></uni-icons>
</view>
</view>
<view class="cell1">
<view>店铺电话</view>
<view class="right select">
<input placeholder="请填写店铺电话" v-model="userinfo.mobile" />
<uni-icons type="right" size="18"></uni-icons>
</view>
</view>
<view class="cell1">
<view>店铺位置</view>
<view class="right select">
<input placeholder="请选择店铺位置" v-model="userinfo.location_name" disabled="disabled" @focus="showMap = true" @click="showMap = true" />
<uni-icons type="right" size="18"></uni-icons>
</view>
</view>
<view v-show="showMap" style="height: 300px; margin: 0 20px 20px 20px;">
<map
style="width: 100%; height: 100%;"
:latitude="centerLat"
:longitude="centerLng"
:markers="markers"
scale="14"
@tap="onMapTap"
/>
</view>
</view> </view>
<view class="foot"> <view class="foot">
<button class="btn" @click="bcFn">{{$t('button.save')}}</button> <button class="btn" @click="bcFn">{{$t('button.save')}}</button>
@@ -28,6 +59,12 @@
export default { export default {
data() { data() {
return { return {
centerLat: 39.9042,
centerLng: 116.4074,
markers: [],
// 若需逆地理,请配置腾讯地图 key(可存储在此或通过 storage 注入)
qqMapKey: 'HQBBZ-SC76W-YUNRZ-34IAD-ARN6K-TZBZ4',
showMap: false,
imageStyles: { imageStyles: {
width: 40, width: 40,
height: 40, height: 40,
@@ -38,8 +75,12 @@
userinfo:{ userinfo:{
nickname:'', nickname:'',
avatar:'', avatar:'',
company:'',
phone:'', phone:'',
shop_name:'',
mobile:'',
location_name:'',
latitude: null,
longitude: null
}, },
} }
@@ -48,13 +89,124 @@
this.getinfo() this.getinfo()
}, },
methods: { methods: {
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
});
});
},
// 地图点选:更新“选点位置”marker
onMapTap(e) {
const { latitude, longitude } = e.detail || {};
if (typeof latitude !== 'number' || typeof longitude !== 'number') return;
// 用 id 2 表示“选点”
const picked = {
id: 2,
latitude,
longitude,
title: '选点位置'
};
// 只保留当前选点作为唯一标记
this.markers = [picked];
// 也可以把地图中心移动到选点
this.centerLat = latitude;
this.centerLng = longitude;
this.userinfo.latitude = latitude
this.userinfo.longitude = longitude
this.reverseGeocode(latitude, longitude)
},
getinfo(){ getinfo(){
var that= this;
request({ request({
url:'common/user/info', url:'common/user/info',
method:'POST', method:'POST',
}).then(res => { }).then(res => {
this.userinfo=res.data.data.user this.userinfo = res.data.data.user || {}
const lat = parseFloat(this.userinfo.latitude)
const lng = parseFloat(this.userinfo.longitude)
if (!isNaN(lat) && !isNaN(lng)) {
this.centerLat = lat
this.centerLng = lng
this.markers = [{
id: 1,
latitude: lat,
longitude: lng,
title: this.userinfo.location_name || '店铺位置'
}]
} else {
this.initFuzzyLocation()
}
}).catch(() => {
this.initFuzzyLocation()
})
},
// 优先使用接口位置,没有时回退到模糊定位
initFuzzyLocation() {
try {
this.getFuzzy().then(res => {
this.centerLat = res.latitude;
this.centerLng = res.longitude;
console.log(res)
// 默认给一个“当前位置(模糊)”标记
this.markers = [{
id: 1,
latitude: res.latitude,
longitude: res.longitude,
title: '当前位置(模糊)'
}];
}).catch(err => console.error(err));
} catch (e) {
console.error(e);
}
},
// 逆地理:将经纬度转为位置文本,失败则回退为经纬度字符串
reverseGeocode(lat, lng) {
const key = this.qqMapKey || uni.getStorageSync('qqMapKey') || ''
if (!key) {
this.userinfo.location_name = `${lat.toFixed(6)}, ${lng.toFixed(6)}`
return
}
uni.request({
url: 'https://apis.map.qq.com/ws/geocoder/v1/',
method: 'GET',
data: {
location: `${lat},${lng}`,
key
},
success: (res) => {
if (res.data && res.data.status === 0) {
const result = res.data.result || {}
const formatted = (result.formatted_addresses && result.formatted_addresses.recommend) || result.address
this.userinfo.location_name = formatted || `${lat.toFixed(6)}, ${lng.toFixed(6)}`
} else {
this.userinfo.location_name = `${lat.toFixed(6)}, ${lng.toFixed(6)}`
}
},
fail: () => {
this.userinfo.location_name = `${lat.toFixed(6)}, ${lng.toFixed(6)}`
}
}) })
}, },
uploadfn(imgUrl){ uploadfn(imgUrl){
@@ -71,7 +223,7 @@
}); });
}, },
delfn(){ delfn(){
that.userinfo.avatar ='' this.userinfo.avatar =''
}, },
select(e){ select(e){
const tempFilePaths = e.tempFilePaths; const tempFilePaths = e.tempFilePaths;
@@ -87,7 +239,12 @@
method:'POST', method:'POST',
data:{ data:{
nickname:this.userinfo.nickname, nickname:this.userinfo.nickname,
avatar:this.userinfo.avatar avatar:this.userinfo.avatar,
mobile:this.userinfo.mobile,
shop_name:this.userinfo.shop_name,
location_name:this.userinfo.location_name,
latitude:this.userinfo.latitude,
longitude:this.userinfo.longitude
} }
}).then(res => { }).then(res => {
uni.showToast({ uni.showToast({
+221 -396
View File
@@ -1,407 +1,232 @@
<template> <template>
<view class="content"> <view class="page">
<uni-nav-bar leftText=" " :fixed="true" :border="false" leftIcon="back" @clickLeft="leftClick" color="#000000" backgroundColor="#fff" :statusBar="true" title="我的发布"></uni-nav-bar> <uni-nav-bar leftText=" " :fixed="true" :border="false" leftIcon="back" @clickLeft="leftClick" color="#000000" backgroundColor="#fff" :statusBar="true" title="我的发布"></uni-nav-bar>
<view>
<view class="year-selector" @click="showYearPicker"> <view class="tabs">
<text class="year-text">{{selectedYear}}年</text> <u-tabs :list="list1" @click="click" lineColor="#3BC6BD" :scrollable="false"></u-tabs>
<uni-icons type="arrowdown" size="14" color="#666"></uni-icons> </view>
</view> <scroll-view
scroll-y
<scroll-view scroll-y class="scroll-container" class="list-scroll"
refresher-enabled refresher-enabled
:refresher-triggered="refreshing" :refresher-triggered="refreshing"
@refresherrefresh="onRefresh" @refresherrefresh="onRefresh"
:lower-threshold="100" :lower-threshold="100"
@scrolltolower="onLoadMore"> @scrolltolower="onLoadMore"
<view class="post-list"> >
<view v-for="(group, dateKey) in groupedPosts" :key="dateKey" class="date-group"> <view class="feed-list">
<view class="date-item"> <post-card
<view class="date-label"> v-for="sitem in slist"
<text class="date-day">{{group.day}}</text> :key="sitem.id"
<text class="date-month">{{group.month}}月</text> :item="sitem"
</view> :ismysq="true"
<view class="posts-container"> @userClick="goto"
<view v-for="post in group.posts" :key="post.id" class="post-item" @click="viewPost(post)"> @view="goto"
<image class="post-image" :src="post.media && post.media[0] ? post.media[0].url_pull : '/static/image/placeholder.png'" mode="aspectFill"></image> @contact="handleContact"
<text class="post-tag">{{post.content || ''}}</text> @comment="handleComment">
</view> </post-card>
</view>
</view> <!-- 状态提示 -->
</view> <view v-if="hasMore" class="load-more">
<text v-if="loadingMore">加载中...</text>
<!-- 加载更多提示 --> <text v-else>上拉加载更多</text>
<view v-if="hasMore && postList.length > 0" class="load-more"> </view>
<text v-if="loadingMore">加载中...</text> <view v-else-if="slist.length > 0" class="load-more">
<text v-else>上拉加载更多</text> <text>没有更多了</text>
</view> </view>
<view v-else-if="!hasMore && postList.length > 0" class="load-more"> <view class="empty" v-if="slist.length === 0 && !loading">
<text>没有更多了</text> <image src="/static/image/empty.png" mode="aspectFit"></image>
</view> <view>暂无数据!</view>
</view>
<view v-if="postList.length === 0 && !loading && !loadingMore" class="empty-view"> </view>
<text>暂无发布内容</text> </scroll-view>
</view>
<!-- 加载提示 -->
<view v-if="loading" class="loading-mask">
<view class="loading-text">加载中...</view>
</view> </view>
</scroll-view>
<!-- 年份选择器 -->
<u-picker
:show="showYearSelect"
:columns="yearColumns"
:defaultIndex="defaultYearIndex"
keyName="label"
@confirm="onYearConfirm"
@cancel="showYearSelect = false"
title="选择年份">
</u-picker>
<!-- 加载提示 -->
<view v-if="loading" class="loading-mask">
<view class="loading-text">加载中...</view>
</view> </view>
</view> </view>
</template> </template>
<script> <script>
import { request } from '@/api/apiT.js' import { request } from '@/api/apiT.js'
export default { import PostCard from '@/components/PostCard.vue'
data() { import uTabs from '@/uni_modules/uview-ui/components/u-tabs/u-tabs.vue'
// 生成年份列表(当前年份前后5年) export default {
const currentYear = new Date().getFullYear(); components: { PostCard,uTabs },
const years = []; data() {
for (let i = currentYear - 5; i <= currentYear + 5; i++) { return {
years.push({ label: i.toString(), value: i }); list1: [{
} name: '全部',
value:''
return { }, {
selectedYear: currentYear, name: '已发布',
showYearSelect: false, value:'1'
yearColumns: [years], }, {
postList: [], name: '审核中',
groupedPosts: {}, value:'2'
loading: false, }, {
refreshing: false, name: '未通过',
loadingMore: false, // 上拉加载状态 value:'3'
page: 1, // 当前页码 }, {
pageSize: 10, // 每页数量 name: '已过期',
hasMore: true, // 是否还有更多数据 value:'4'
total: 0, // 总数据量 }],
} status:'',
}, slist:[],
computed: { loading: false,
// 计算当前年份在选择器中的默认索引 refreshing: false,
defaultYearIndex() { loadingMore: false,
const currentYear = new Date().getFullYear(); page: 1,
const startYear = currentYear - 5; pageSize: 10,
return [currentYear - startYear]; // 当前年份在数组中的索引 hasMore: true,
} defaultTags: ['样机机', '9成新']
}, }
onLoad() { },
this.loadPostList(); onShow() {
}, this.loadList()
methods: { // this.syncTabbar()
leftClick() { },
// uni.navigateBack() methods: {
uni.reLaunch({ leftClick() {
url: '/pages/index/mine',// 替换为你要跳转的页面路径 // uni.navigateBack()
}); uni.reLaunch({
}, url: '/pages/index/mine',// 替换为你要跳转的页面路径
showYearPicker() { });
this.showYearSelect = true; },
}, goto(item){
onYearConfirm(e) { uni.navigateTo({
if (e.value && e.value[0]) { url: '/pages/square/detiles?id='+item.id
const selectedItem = typeof e.value[0] === 'object' ? e.value[0] : { value: e.value[0], label: e.value[0].toString() }; });
this.selectedYear = selectedItem.value || parseInt(selectedItem.label); },
this.showYearSelect = false; click(item) {
// 切换年份时重置分页 this.page = 1
this.page = 1; this.hasMore = true
this.hasMore = true; this.slist = []
this.postList = []; this.status=item.value
this.groupedPosts = {}; this.loadList()
this.loadPostList(false); },
} loadList(isLoadMore = false) {
}, if (isLoadMore) {
// 加载帖子列表 if (this.loadingMore || this.loading || this.refreshing || !this.hasMore) return
loadPostList(isLoadMore = false) { this.loadingMore = true
// 防止重复请求 this.page += 1
if (isLoadMore) { } else {
if (this.loadingMore || this.loading || this.refreshing || !this.hasMore) { if (this.loading || this.loadingMore) return
return; this.loading = true
} this.page = 1
this.loadingMore = true; this.hasMore = true
this.page += 1; this.slist = []
} else { }
if (this.loading && !this.refreshing) { request({
return; url: 'common/post/list',
} method: 'GET',
this.loading = true; data: {
this.page = 1; current: this.page,
this.hasMore = true; size: this.pageSize,
if (!isLoadMore && !this.refreshing) { status: this.status
this.postList = []; }
this.groupedPosts = {}; }).then(res => {
} this.loading = false
} this.loadingMore = false
this.refreshing = false
// 获取当前用户发布的帖子列表 if (res.data.code === 200) {
request({ const records = res.data.data.records || []
url: 'common/post/list', const total = res.data.data.total || 0
method: 'GET', if (isLoadMore) {
data: { this.slist = [...this.slist, ...records]
// 只获取当前用户发布的帖子,后端应该根据token自动过滤 } else {
current: this.page, this.slist = records
size: this.pageSize, }
scene:'self', this.hasMore = this.slist.length < total
year:this.selectedYear }
} }).catch(() => {
}).then(res => { this.loading = false
this.loading = false; this.loadingMore = false
this.loadingMore = false; this.refreshing = false
this.refreshing = false; if (isLoadMore) {
this.page -= 1
if (res.data.code == 200) { }
const records = res.data.data.records || []; })
const total = res.data.data.total || 0; },
this.total = total; onRefresh() {
if (this.loading || this.loadingMore) {
// 过滤出当前年份的数据 this.refreshing = false
const yearFilteredRecords = records.filter(post => { return
if (!post.created_at) return false; }
const dateStr = post.created_at; this.refreshing = true
const postYear = new Date(dateStr).getFullYear(); this.loadList(false)
return postYear === this.selectedYear; },
}); onLoadMore() {
this.loadList(true)
if (isLoadMore) { },
// 上拉加载更多,追加数据并去重 navToList(item) {
const existingIds = new Set(this.postList.map(item => item.id)); uni.navigateTo({
const newRecords = yearFilteredRecords.filter(item => !existingIds.has(item.id)); url: '/pages/square/detiles?id=' + item.id
this.postList = [...this.postList, ...newRecords]; })
} else { },
// 下拉刷新或初次加载,替换数据 leftClick() {
this.postList = yearFilteredRecords; uni.navigateBack()
} }
}
// 重新分组 }
this.groupPostsByDate();
// 判断是否还有更多数据
// 如果当前页返回的原始数据少于pageSize,说明没有更多了
// 或者已经加载到最后一页
if (records.length < this.pageSize) {
this.hasMore = false;
} else {
// 计算理论上最多能加载多少页
const maxPages = Math.ceil(total / this.pageSize);
this.hasMore = this.page < maxPages;
}
}
}).catch(() => {
this.loading = false;
this.loadingMore = false;
this.refreshing = false;
if (isLoadMore) {
this.page -= 1; // 加载失败,页码回退
}
});
},
// 按日期分组帖子
groupPostsByDate() {
const grouped = {};
this.postList.forEach(post => {
const dateStr = post.created_at || post.create_time || post.createdAt;
if (!dateStr) return;
const date = new Date(dateStr);
const month = date.getMonth() + 1; // 月份从0开始,需要+1
const day = date.getDate();
// 生成日期key,格式:MM-DD,用于排序
const dateKey = `${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
if (!grouped[dateKey]) {
grouped[dateKey] = {
day: day,
month: month,
posts: []
};
}
grouped[dateKey].posts.push(post);
});
// 按日期倒序排序(最新的在前)
const sortedKeys = Object.keys(grouped).sort((a, b) => {
// 按完整日期排序(年-月-日)
const dateA = new Date(this.selectedYear, parseInt(a.split('-')[0]) - 1, parseInt(a.split('-')[1]));
const dateB = new Date(this.selectedYear, parseInt(b.split('-')[0]) - 1, parseInt(b.split('-')[1]));
return dateB - dateA; // 倒序:最新的在前
});
// 重新构建分组对象,保持顺序
const sortedGrouped = {};
sortedKeys.forEach(key => {
sortedGrouped[key] = grouped[key];
});
this.groupedPosts = sortedGrouped;
},
// 下拉刷新
onRefresh() {
// 如果正在加载中,不执行刷新
if (this.loading || this.loadingMore) {
this.refreshing = false;
return;
}
this.refreshing = true;
this.page = 1;
this.hasMore = true;
this.loadPostList(false);
},
// 上拉加载更多
onLoadMore() {
if (!this.hasMore || this.loadingMore || this.loading || this.refreshing) {
return;
}
this.loadPostList(true);
},
// 查看帖子详情
viewPost(post) {
// TODO: 跳转到帖子详情页
uni.navigateTo({
url: '/pages/square/detiles?id='+post.id
});
}
}
}
</script> </script>
<style lang="scss"> <style lang="scss">
page, page,
.content { .page {
height: 100%; height: 100%;
background-color: #fff; background: #f8f8f8;
} }
.year-selector { .tabs{
display: flex; background: #fff;
align-items: center; }
justify-content: space-between;
padding: 20rpx 30rpx; .list-scroll {
background-color: #F5F5F5; height: calc(100vh - 120rpx);
}
.year-text {
font-size: 28rpx; .feed-list {
color: #333; // padding: 20rpx;
} margin: 20rpx 30rpx;
}
.arrow-icon { .load-more {
width: 24rpx; width: 100%;
height: 24rpx; text-align: center;
} padding: 30rpx 0;
} font-size: 26rpx;
color: #999;
.scroll-container { }
height: calc(100% - 168rpx); // 减去导航栏和年份选择器高度 .empty {
} width: 100%;
text-align: center;
.post-list { padding: 100rpx 0;
padding: 20rpx 0; }
} .empty image {
width: 252rpx;
.date-group { height: 164rpx;
margin-bottom: 40rpx; margin-bottom: 20rpx;
} }
.date-item { .loading-mask {
display: flex; position: fixed;
padding: 0 30rpx; top: 0;
} left: 0;
right: 0;
.date-label { bottom: 0;
display: flex; background-color: rgba(0, 0, 0, 0.3);
// flex-direction: column; display: flex;
// align-items: center; align-items: center;
margin-right: 30rpx; justify-content: center;
min-width: 120rpx; z-index: 999;
.date-day { .loading-text {
font-size: 48rpx; color: #fff;
font-weight: bold; font-size: 28upx;
color: #333; background-color: rgba(0, 0, 0, 0.7);
line-height: 1; padding: 20upx 40upx;
} border-radius: 8upx;
}
.date-month { }
font-size: 24rpx;
color: #666;
margin-top: 8rpx;
}
}
.posts-container {
flex: 1;
display: flex;
flex-direction: column;
gap: 20rpx;
}
.post-item {
display: flex;
// align-items: center;
margin-bottom: 30rpx;
.post-image {
width: 200rpx;
height: 200rpx;
background-color: #E5E5E5;
border-radius: 10rpx;
margin-right: 30rpx;
flex-shrink: 0;
}
.post-tag {
font-size: 28rpx;
color: #333;
flex: 1;
}
}
.empty-view {
text-align: center;
padding: 100rpx 0;
color: #999;
font-size: 28rpx;
}
.load-more {
width: 100%;
text-align: center;
padding: 30rpx 0;
font-size: 26rpx;
color: #999;
}
.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: 28rpx;
background-color: rgba(0, 0, 0, 0.7);
padding: 20rpx 40rpx;
border-radius: 8rpx;
}
}
</style> </style>
+20 -15
View File
@@ -2,21 +2,23 @@
<view class="home-page"> <view class="home-page">
<z-paging ref="paging" v-model="contList" @query="queryList" empty-view-text="暂无消息"> <z-paging ref="paging" v-model="contList" @query="queryList" empty-view-text="暂无消息">
<template #top> <template #top>
<uni-nav-bar leftText=" " :fixed="true" :border="false" leftIcon="back" @clickLeft="leftClick" color="#000000" backgroundColor="#fff" :statusBar="true" title="最新消息"></uni-nav-bar> <uni-nav-bar leftText=" " :fixed="true" :border="false" leftIcon="back" @clickLeft="leftClick" color="#000000" backgroundColor="#fff" :statusBar="true" title="我的信息"></uni-nav-bar>
</template> </template>
<view class="msg-list"> <view class="msg-list">
<view class="msg-item" v-for="item in contList" :key="item.id"> <view class="msg-item" v-for="item in contList" :key="item.id">
<image class="msg-avatar" :src="item.sender_user.avatar_pull" mode="aspectFill"></image> <view class="msg-cont">
<view class="msg-body"> <image class="msg-avatar" :src="item.sender_user.avatar_pull" mode="aspectFill"></image>
<view class="msg-header"> <view class="msg-body">
<text class="msg-name">{{item.sender_user.nickname}}</text> <view class="msg-header">
<text class="msg-time">{{item.created_at}}</text> <text class="msg-name">{{item.sender_user.nickname}}</text>
<!-- <text class="msg-time">{{item.created_at}}</text> -->
</view>
</view> </view>
<text class="msg-text">
<text>{{ item.type==1?'点赞了': item.type==2?'收藏了':'评论了' }} </text>
<text>{{ formatContent(item.post && item.post.content) }}</text>
</text>
</view> </view>
<view class="msg-text">
<text>{{ formatContent(item.post && item.post.content) }}</text>
</view>
</view> </view>
</view> </view>
</z-paging> </z-paging>
@@ -80,15 +82,18 @@
padding: 20rpx; padding: 20rpx;
} }
.msg-item{ .msg-item{
display: flex;
align-items: center;
padding: 24rpx 20rpx; padding: 24rpx 20rpx;
margin-bottom: 20rpx; margin-bottom: 20rpx;
border-bottom: 1px solid #e4e4e4; background: #fff;
border-radius: 10rpx;
}
.msg-cont{
display: flex;
align-items: center;
} }
.msg-avatar{ .msg-avatar{
width: 96rpx; width: 60rpx;
height: 96rpx; height: 60rpx;
border-radius: 50%; border-radius: 50%;
margin-right: 20rpx; margin-right: 20rpx;
} }
+9 -16
View File
@@ -6,15 +6,12 @@
</template> </template>
<!-- 内容网格 --> <!-- 内容网格 -->
<view class="content-grid"> <view class="content-grid">
<square-post-card <classify-card
class="grid-item" v-for="item in contList"
v-for="sitem in contList" :key="item.id"
:key="sitem.id" :item="item.post"
:item="sitem.post" @view="navToList"
:isshow="false" />
@view="navToList"
@comment="viewPost"
@like="viewPost" />
</view> </view>
</z-paging> </z-paging>
</view> </view>
@@ -22,10 +19,10 @@
<script> <script>
import { request,showToast } from '@/api/apiT.js' import { request,showToast } from '@/api/apiT.js'
import SquarePostCard from '@/components/SquarePostCard.vue' import ClassifyCard from '@/components/ClassifyCard.vue'
export default { export default {
components: { components: {
SquarePostCard ClassifyCard
}, },
data() { data() {
return { return {
@@ -87,11 +84,7 @@
padding-bottom: 120rpx; padding-bottom: 120rpx;
} }
.content-grid{ .content-grid{
display: flex; margin-top: 20rpx;
flex-wrap: wrap;
padding: 20rpx;
justify-content: space-between;
background: #fff;
} }
.grid-item{ .grid-item{
width: 48%; width: 48%;
+154
View File
@@ -0,0 +1,154 @@
<template>
<view class="container">
<uni-nav-bar leftText=" " :fixed="true" :border="false" leftIcon="back" @clickLeft="leftClick" color="#000000" backgroundColor="#fff" :statusBar="true" title="回复我的"></uni-nav-bar>
<view class="reply-list">
<view class="reply-card" v-for="(reply, index) in replyList" :key="index">
<!-- 主回复 -->
<view class="reply-item">
<view class="reply-heard">
<image :src="reply.avatar" class="avatar"></image>
<view class="reply-content">
<view class="username">{{ reply.username }}</view>
<view class="time">{{ reply.time }}</view>
</view>
</view>
<view class="reply-content">
<text class="reply-text">{{ reply.content }}</text>
</view>
</view>
<!-- 子回复 -->
<view class="sub-reply" v-for="(subReply, subIndex) in reply.subReplies" :key="subIndex">
<view class="reply-heard">
<image :src="subReply.avatar" class="avatar"></image>
<view class="reply-header">
<view class="username">{{ subReply.username }}</view>
</view>
</view>
<view class="reply-content">
<text class="reply-text">{{ subReply.content }}</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
replyList: [
{
avatar: '/static/image/user.png',
username: '用户名用户名',
time: '2025年12月04日10:56:44',
content: '这是回复这是回复这是回复这是回复这是回复这是回复这是回复这是回复这是回复这是回复这是回复这是回复',
subReplies: [
{
avatar: '/static/image/user.png',
username: '田户名田户名',
time: '2025年12月04日10:56:44',
content: '西门子全新款饮机品牌西门子全新款饮机品牌西门子全新...'
}
]
},
{
avatar: '/static/image/user.png',
username: '用户名用户名',
time: '2025年12月04日10:56:44',
content: '这是回复这是回复这是回复这是回复这是回复这是回复这是回复这是回复这是回复这是回复这是回复这是回复',
subReplies: [
{
avatar: '/static/image/user.png',
username: '用户名用户名',
time: '2025年12月04日10:56:44',
content: '西门子全新款饮机品牌西门子全新款饮机品牌西门子全新...'
}
]
}
]
}
},
methods: {
leftClick() {
uni.reLaunch({
url: '/pages/index/mine',// 替换为你要跳转的页面路径
});
}
}
}
</script>
<style scoped>
.container {
min-height: 100vh;
}
.reply-list {
padding: 0;
}
.reply-card {
background-color: #fff;
margin: 10px;
border-radius: 8px;
padding: 12px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.reply-item {
/* display: flex; */
margin-bottom: 12px;
}
.reply-heard{
display: flex;
gap: 10px;
}
.sub-reply {
/* margin-left: 30px; */
padding:8px 12px;
background-color: #F5F6FA;
border-radius: 6px;
}
.avatar {
width: 40px;
height: 40px;
border-radius: 50%;
flex-shrink: 0;
}
.reply-content {
flex: 1;
min-width: 0;
}
.reply-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 6px;
}
.username {
font-size: 13px;
color: #333;
font-weight: 500;
}
.time {
font-size: 12px;
color: #999;
}
.reply-text {
font-size: 13px;
color: #666;
line-height: 1.5;
word-break: break-all;
display: block;
}
</style>
+225 -81
View File
@@ -1,68 +1,87 @@
<template> <template>
<view class="publish-page"> <view class="publish-page">
<uni-nav-bar leftText=" " :fixed="true" :border="false" @clickLeft="leftClick" color="#000000" backgroundColor="#fff" :statusBar="true" title="发布页"></uni-nav-bar> <uni-nav-bar leftText=" " :fixed="true" :border="false" @clickLeft="leftClick" color="#000000" backgroundColor="#fff" :statusBar="true" title="样机发布"></uni-nav-bar>
<view class="form-card"> <view class="form-card">
<view class="section-label">选择发布标签</view> <view class="select-input gradient-bg" @click="showSelect1 = true">
<picker mode="selector" :range="categoryOptions" range-key="name" @change="onCategoryChange"> <view class="category-content">
<view class="select-input gradient-bg"> <text class="category-text">{{form.category || '请输入/选择型号'}}</text>
<view class="category-content">
<view class="fridge-icon">
<image :src="iconurl" mode=""></image>
</view>
<text class="category-text">{{form.category || '请选择发布标签'}}</text>
</view>
<u-icon name="arrow-down" color="#fff" size="20"></u-icon>
</view> </view>
</picker> <u-icon name="arrow-down" color="#3BC8BE" size="20"></u-icon>
<view class="mag-top">
<textarea v-model="form.content" class="content-textarea" placeholder="请输入文字内容"
:maxlength="maxWords" @input="handleContentInput" />
</view> </view>
<view class="conts mag-top">
<view class="media-upload-wrapper mag-top"> <view class="">
<uni-file-picker fileMediatype="image" limit="9" @select="selectMedia" @delete="delfn" :imageStyles="imageStyles"> <textarea v-model="form.content" class="content-textarea" placeholder="请输入文字内容"
<view class="upload-icon-wrapper"> :maxlength="maxWords" @input="handleContentInput" />
<image src="/static/image/photo.png" mode="" class="addimg"></image> </view>
<text class="upload-text">添加图片</text>
</view> <view class="media-upload-wrapper">
</uni-file-picker> <uni-file-picker fileMediatype="image" limit="9" @select="selectMedia" @delete="delfn" :imageStyles="imageStyles">
<!-- <view class="upload-area" @click="selectMedia" v-if="form.media.length < maxMedia"> <view class="upload-icon-wrapper">
<view class="upload-icon-wrapper"> <image src="/static/image/photo.png" mode="" class="addimg"></image>
<view class="camera-icon">
<image src="/static/image/photo.png" mode=""></image>
</view> </view>
<text class="upload-text">添加图片</text> </uni-file-picker>
</view> </view>
</view> --> <view class="help-text">
<!-- <view class="media-preview" v-for="(item, index) in form.media" :key="index"> <u-icon name="info-circle" size="18" color="#A6ABB7"></u-icon>
<image :src="item.url" mode="aspectFill" class="preview-image"></image> <text>文字不多于2000字,图片不多于9张</text>
<view class="delete-btn" @click.stop="deleteMedia(index)"> </view>
<u-icon name="close" size="18" color="#fff"></u-icon>
</view>
</view> -->
</view> </view>
<view class="help-text"> <view class="mag-top">
<u-icon name="info-circle" size="18" color="#A6ABB7"></u-icon> <picker mode="selector" :range="conditionOptions" range-key="name" @change="onCategoryChange">
<text>文字不多于2000字,图片不多于9张</text> <view class="select-input gradient-bg">
<view class="category-content">
<text class="category-text">{{form.agename || '请选择样机新旧程度'}}</text>
</view>
<u-icon name="arrow-down" color="#3BC8BE" size="20"></u-icon>
</view>
</picker>
</view> </view>
</view> <view class="mag-top">
<input class="uni-input" focus v-model="form.price" placeholder="请输入折扣价格" />
<view class="agreement"> </view>
<label class="agree-label" @click="toggleAgreement"> <view class="detils">
<checkbox :checked="agree" style="transform:scale(0.8)"></checkbox> <view v-html="productcontents">
<text>我已阅读并同意</text>
<text class="link" @click.stop="viewAgreement">《发布协议》</text>
</label>
</view>
<!-- <button class="primary-btn gradient-btn" :class="{disabled: !canSubmit}" :disabled="!canSubmit" @click="submitForm">立即发布</button> -->
<button class="primary-btn gradient-btn" @click="submitForm">立即发布</button>
<u-popup :show="show" :closeOnClickOverlay="false" :round="10" mode="center">
<view class="cont-text" >
<view class="" v-html="viewAgreementcont">
</view> </view>
</view> </view>
</view>
<!-- 型号选择弹层 -->
<view v-if="showSelect1" class="select-popup">
<view class="popup-mask" @click="showSelect1=false"></view>
<view class="popup-panel">
<view class="popup-header">
<input class="popup-search" v-model="searchKey1" placeholder="搜索型号" />
<text class="popup-close" @click="showSelect1=false">取消</text>
</view>
<scroll-view scroll-y class="popup-list">
<view
v-for="item in filteredCategories"
:key="item.id"
class="popup-item"
@click="selectCategory(item)"
>
{{ item.model }}
</view>
<view v-if="!filteredCategories.length" class="popup-empty">暂无数据</view>
</scroll-view>
</view>
</view>
<view class="foot">
<view class="agreement">
<label class="agree-label" @click="toggleAgreement">
<checkbox :checked="agree" style="transform:scale(0.8)"></checkbox>
<text>我已阅读并同意</text>
<text class="link" @click.stop="viewAgreement">《发布协议》</text>
</label>
<button class="primary-btn gradient-btn" @click="submitForm">立即发布</button>
</view>
</view>
<u-popup :show="show" :closeOnClickOverlay="false" :round="10" mode="center">
<view class="cont-text" >
<rich-text :nodes="viewAgreementcont"></rich-text>
</view>
<view class="cent"> <view class="cent">
<button class="btn1" @click="show = !show">取消</button> <button class="btn1" @click="show = !show">取消</button>
<button class="btn" @click="agreementbtn">确定</button> <button class="btn" @click="agreementbtn">确定</button>
@@ -94,17 +113,36 @@
</template> </template>
<script> <script>
import { request,showToast } from '@/api/apiT.js' import { request,showToast } from '@/api/apiT.js'
export default { export default {
name: 'PublishPage', name: 'PublishPage',
data(){ data(){
return { return {
showSelect1:false,
showSelect2:false,
searchKey1:'',
productcontents:'',
categoryOptions:[], categoryOptions:[],
conditionOptions:[
{name:'全新',value:'10'},
{name:'9成新',value:'9'},
{name:'8成新',value:'8'},
{name:'7成新',value:'7'},
{name:'6成新',value:'6'},
{name:'5成新',value:'5'},
{name:'4成新',value:'4'},
{name:'3成新',value:'3'},
{name:'2成新',value:'2'},
{name:'1成新',value:'1'},
],
form:{ form:{
agename:'',
age:'',
category:'', category:'',
category_id:'', sku_id:'',
content:'', content:'',
media:[] media:[],
price:''
}, },
selfChecked: false, selfChecked: false,
showlogin:false, showlogin:false,
@@ -119,16 +157,38 @@ export default {
computed:{ computed:{
canSubmit(){ canSubmit(){
return this.agree && this.form.content.trim().length>0 && this.form.media.length>0 return this.agree && this.form.content.trim().length>0 && this.form.media.length>0
} },
filteredCategories(){
if(!this.searchKey1) return this.categoryOptions
return this.categoryOptions.filter(i => (i.model || '').toLowerCase().includes(this.searchKey1.toLowerCase()))
}
}, },
onLoad() { onLoad() {
this.getcategory() this.getcategory()
}, },
onShow() { onShow() {
// this.getset()
this.syncTabbar() // this.syncTabbar()
}, },
methods:{ methods:{
getlocation(){
wx.getFuzzyLocation({
type: 'wgs84',
success(res) {
console.log("模糊定位:", res)
},
fail(err) {
console.log("模糊定位失败:", err)
}
})
},
selectCategory(item){
this.form.category = item.model || ''
this.form.sku_id = item.id || ''
this.productcontents = item.desc || ''
console.log(this.productcontents)
this.showSelect1 = false
},
goprivacy(){ goprivacy(){
uni.navigateTo({ uni.navigateTo({
url: '/pages/login/agreement',// 替换为你要跳转的页面路径 url: '/pages/login/agreement',// 替换为你要跳转的页面路径
@@ -176,16 +236,14 @@ export default {
} }
}) })
}, },
//商品下拉
getcategory(){ getcategory(){
request({ request({
url:'common/post/category/list', url:'common/sku/preset',
method:'GET' method:'GET'
}).then(res => { }).then(res => {
if (res.data.code == 200) { if (res.data.code == 200) {
this.categoryOptions = res.data.data.records || []; this.categoryOptions = res.data.data|| [];
this.form.category = res.data.data.records[0].name
this.form.category_id = res.data.data.records[0].id
this.iconurl = res.data.data.records[0].icon_preset_pull
} }
}) })
}, },
@@ -201,10 +259,9 @@ export default {
onCategoryChange(e){ onCategoryChange(e){
console.log(e) console.log(e)
const index = e.detail.value; // picker 返回的 index const index = e.detail.value; // picker 返回的 index
const selectCity = this.categoryOptions[index]; // 直接取整条数据 const selectCity = this.conditionOptions[index]; // 直接取整条数据
this.iconurl = selectCity.icon_preset_pull this.form.agename = selectCity.name
this.form.category = selectCity.name this.form.age = selectCity.name
this.form.category_id = selectCity.id
}, },
handleContentInput(e){ handleContentInput(e){
if(e.detail.value.length>this.maxWords){ if(e.detail.value.length>this.maxWords){
@@ -214,7 +271,7 @@ export default {
uploadfn(imgUrl){ uploadfn(imgUrl){
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
uni.uploadFile({ uni.uploadFile({
url: 'https://bsh.info.hb.hnzhwlkj.cn/upload/image?scene=postlist', url: 'https://cb.hnxinyouhe.com/upload/image?scene=postlist',
filePath: imgUrl, filePath: imgUrl,
name: 'image', name: 'image',
header:{'content-type': 'application/x-www-form-urlencoded','Fdesk-Auth-Token': 'Bearer ' + uni.getStorageSync('token'),'Fdesk-Language': uni.getStorageSync('i18locale')?uni.getStorageSync('i18locale'):'zh','Fdesk-Login-Type': 'wechat','Fdesk-System-Code':'wechat'}, header:{'content-type': 'application/x-www-form-urlencoded','Fdesk-Auth-Token': 'Bearer ' + uni.getStorageSync('token'),'Fdesk-Language': uni.getStorageSync('i18locale')?uni.getStorageSync('i18locale'):'zh','Fdesk-Login-Type': 'wechat','Fdesk-System-Code':'wechat'},
@@ -314,6 +371,9 @@ export default {
</script> </script>
<style lang="scss"> <style lang="scss">
.detils{
margin-top: 40rpx;
}
.cent{ .cent{
display: flex; display: flex;
align-items: center; align-items: center;
@@ -356,7 +416,7 @@ export default {
.form-card{ .form-card{
background: #fff; background: #fff;
padding: 30rpx; padding: 30rpx;
margin-bottom: 24rpx; margin-bottom: 400rpx;
box-shadow: 0 12rpx 30rpx rgba(0,0,0,0.04); box-shadow: 0 12rpx 30rpx rgba(0,0,0,0.04);
} }
.section-label{ .section-label{
@@ -374,7 +434,19 @@ export default {
justify-content: space-between; justify-content: space-between;
} }
.gradient-bg{ .gradient-bg{
background: linear-gradient(90deg, #206ff4 0%, #42e0a2 100%); background: #F4F4F4;
color: #999999;
}
.conts{
background: #F4F4F4;
color: #999999;
padding: 20rpx;
border-radius: 10rpx;
}
.uni-input{
background: #F4F4F4;
padding: 20rpx 25rpx;
border-radius: 10rpx;
} }
.category-content{ .category-content{
display: flex; display: flex;
@@ -418,7 +490,7 @@ export default {
} }
.category-text{ .category-text{
font-size: 28rpx; font-size: 28rpx;
color: #fff; color: #999999;
font-weight: 500; font-weight: 500;
} }
.mag-top{ .mag-top{
@@ -458,8 +530,8 @@ export default {
margin-bottom: 12rpx; margin-bottom: 12rpx;
} }
.addimg{ .addimg{
width: 26px; width: 215rpx;
height: 22px; height: 215rpx;
} }
.upload-text{ .upload-text{
font-size: 26rpx; font-size: 26rpx;
@@ -496,10 +568,14 @@ export default {
align-items: center; align-items: center;
gap: 8rpx; gap: 8rpx;
} }
.agreement{ .foot{
margin: 20rpx 0 40rpx;
padding: 20px 12px;
background: #fff; background: #fff;
position: fixed;
bottom: 1rpx;
width: 100%;
}
.agreement{
margin: 20px 12px;
} }
.agree-label{ .agree-label{
display: flex; display: flex;
@@ -515,15 +591,83 @@ export default {
} }
.primary-btn{ .primary-btn{
color: #fff; color: #fff;
border-radius: 999px; border-radius: 20rpx;
height: 88rpx; height: 88rpx;
line-height: 88rpx; line-height: 88rpx;
font-size: 32rpx; font-size: 32rpx;
border: none; border: none;
margin: 40px 12px;
} }
.gradient-btn{ .gradient-btn{
background: linear-gradient(180deg, #206ff4 0%, #42e0a2 100%); background: #3BC8BE;
margin-top: 20rpx;
}
.select-popup{
position: fixed;
inset: 0;
z-index: 999;
}
.popup-mask{
position: absolute;
inset: 0;
background: rgba(0,0,0,0.35);
}
.popup-panel{
position: absolute;
left: 0;
right: 0;
bottom: 0;
background: #fff;
border-top-left-radius: 20rpx;
border-top-right-radius: 20rpx;
max-height: 40vh; /* 控制整体可视高度 */
display: flex;
flex-direction: column;
padding: 20rpx;
box-sizing: border-box;
}
.popup-header{
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16rpx;
}
.popup-search{
flex: 1;
height: 72rpx;
border-radius: 10rpx;
background: #f4f4f4;
padding: 0 20rpx;
font-size: 28rpx;
color: #333;
margin-right: 12rpx;
box-sizing: border-box;
}
.popup-title{
font-size: 28rpx;
color: #333;
}
.popup-close{
font-size: 28rpx;
color: #3BC8BE;
}
.popup-list{
flex: 1;
max-height: calc(40vh - 140rpx); /* 预留头部高度,保证可滚动 */
}
.popup-item{
padding: 20rpx 12rpx;
border-bottom: 1px solid #f0f0f0;
font-size: 28rpx;
color: #333;
}
.popup-item:last-child{
border-bottom: none;
}
.popup-empty{
text-align: center;
color: #999;
font-size: 26rpx;
padding: 30rpx 0;
} }
.cont-text{ .cont-text{
max-width: 350px; max-width: 350px;
+789 -771
View File
File diff suppressed because it is too large Load Diff
+298 -440
View File
@@ -1,449 +1,307 @@
<template> <template>
<view class="content"> <view class="page">
<uni-nav-bar leftText=" " :fixed="true" :border="false" @clickLeft="leftClick" color="#000000" backgroundColor="#fff" :statusBar="true" title="广场"></uni-nav-bar> <uni-nav-bar :fixed="true" :border="false" color="#000" backgroundColor="#fff" :statusBar="true" title="广场" />
<view class="content-con"> <!-- Tabs -->
<scroll-view scroll-y class="left-aside"> <view class="tabs">
<view v-for="item in flist" :key="item.id" class="f-item b-b" :class="{active: item.id === currentCategoryId}" <view :class="['tab', currentTab==='latest'?'on':'']" @click="changeTab('latest')">最新发布</view>
@click="onCategoryChange(item.id)"> <view class="divider"></view>
<view class="category-icon" v-if="item.icon_active_pull"> <view :class="['tab', currentTab==='near'?'on':'']" @click="changeTab('near')">距离最近</view>
<image :src="item.id === currentCategoryId?item.icon_active_pull:item.icon_inactive_pull" mode=""></image>
️</view>
{{item.name}}
</view>
</scroll-view>
<scroll-view scroll-y class="right-aside"
refresher-enabled
:refresher-triggered="refreshing"
@refresherrefresh="onRefresh"
:lower-threshold="100"
@scrolltolower="onLoadMore">
<view class="t-lists">
<square-post-card
class="t-item"
v-for="sitem in slist"
:key="sitem.id"
:item="sitem"
:isshow="true"
@view="navToList"
@comment="navTomsg"
@like="navTozan" />
<!-- 加载更多提示 -->
<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">
<image src="/static/image/empty.png" mode=""></image>
<view class="">
暂无数据!
</view>
</view>
</view>
</scroll-view>
</view>
<!-- 加载提示 -->
<view v-if="loading" class="loading-mask">
<view class="loading-text">加载中...</view>
</view>
</view> </view>
</template>
<!-- Feed list -->
<scroll-view
scroll-y
class="list-scroll"
refresher-enabled
:refresher-triggered="refreshing"
@refresherrefresh="onRefresh"
:lower-threshold="100"
@scrolltolower="onLoadMore"
>
<view class="feed-list">
<post-card
v-for="sitem in slist"
:key="sitem.id || sitem.username"
:item="sitem"
@userClick="goto"
@view="goto"
@contact="handleContact"
@comment="handleComment">
</post-card>
<!-- 状态提示 -->
<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 v-if="loading" class="loading-mask">
<view class="loading-text">加载中...</view>
</view>
</view>
</template>
<script> <script>
import { request,showToast } from '@/api/apiT.js' import { request } from '@/api/apiT.js'
import SquarePostCard from '@/components/SquarePostCard.vue' import PostCard from '@/components/PostCard.vue'
export default { export default {
components: { components: { PostCard },
SquarePostCard data() {
}, return {
data() { tabs: [
return { { key: 'latest', label: '最新发布' },
flist: [], { key: 'near', label: '距离最近' }
slist: [], ],
currentCategoryId: '', // 当前选中的分类id currentTab: 'latest',
loading: false, // 加载状态 slist:[
refreshing: false, // 下拉刷新状态 {
loadingMore: false, // 上拉加载状态 username:'用户名用户名',
page: 1, // 当前页码 time:'2025 年 12 月 4 日 10:56',
pageSize: 10, // 每页数量 content:`西门子全新嵌饮机品牌西门子全新嵌饮机品牌西门子全新嵌饮机品牌西门子全新嵌饮机品牌西门子全新嵌饮机品牌西门子全新嵌饮机品牌西门子
hasMore: true, // 是否还有更多数据 西门子全新嵌饮机品牌西门子全新嵌饮机品牌西门子全新嵌饮机品牌西门子全新嵌饮机品牌西门子全新嵌饮机品牌西门子全新嵌饮机品牌西门子西门子全新嵌饮机品牌
categoriesLoaded: false // 分类是否已加载完成 西门子全新嵌饮机品牌西门子全新嵌饮机品牌西门子全新嵌饮机品牌西门子全新嵌饮机品牌西门子全新嵌饮机品牌西门子`,
} tag:['#散饮机','#9成新'],
}, location:'门店位置',
onLoad() { image:[
// 加载第一级分类列表 {img_src:'/static/image/bx.jpg'},
{img_src:'/static/image/bx.jpg'},
}, {img_src:'/static/image/bx.jpg'},
onShow() { {img_src:'/static/image/bx.jpg'},
this.loadCategoryList(); {img_src:'/static/image/bx.jpg'},
this.syncTabbar() {img_src:'/static/image/bx.jpg'}
// // 从详情页返回后,重新拉取当前分类数据 ]
// if (this.categoriesLoaded) { },{
// this.refreshCurrentCategory() username:'用户名用户名',
// } time:'2025 年 12 月 4 日 10:56',
}, content:`西门子全新嵌饮机品牌西门子全新嵌饮机品牌西门子全新嵌饮机品牌西门子全新嵌饮机品牌西门子全新嵌饮机品牌西门子全新嵌饮机品牌西门子
methods: { 西门子全新嵌饮机品牌西门子全新嵌饮机品牌西门子全新嵌饮机品牌西门子全新嵌饮机品牌西门子全新嵌饮机品牌西门子全新嵌饮机品牌西门子西门子全新嵌饮机品牌
// 加载第一级分类列表 西门子全新嵌饮机品牌西门子全新嵌饮机品牌西门子全新嵌饮机品牌西门子全新嵌饮机品牌西门子全新嵌饮机品牌西门子`,
loadCategoryList() { tag:['#散饮机','#9成新'],
// TODO: 替换为实际的后端接口 location:'门店位置',
let that = this; image:[
{img_src:'/static/image/bx.jpg'},
request({ {img_src:'/static/image/bx.jpg'},
url:'common/post/category/list', {img_src:'/static/image/bx.jpg'}
method:'GET' ]
}).then(res => { },{
if (res.data.code == 200) { username:'用户名用户名',
const records = res.data.data.records || []; time:'2025 年 12 月 4 日 10:56',
this.flist = [{ content:`西门子全新嵌饮机品牌西门子全新嵌饮机品牌西门子全新嵌饮机品牌西门子全新嵌饮机品牌西门子全新嵌饮机品牌西门子全新嵌饮机品牌西门子
id: '', 西门子全新嵌饮机品牌西门子全新嵌饮机品牌西门子全新嵌饮机品牌西门子全新嵌饮机品牌西门子全新嵌饮机品牌西门子全新嵌饮机品牌西门子西门子全新嵌饮机品牌
name: '全部分类' 西门子全新嵌饮机品牌西门子全新嵌饮机品牌西门子全新嵌饮机品牌西门子全新嵌饮机品牌西门子全新嵌饮机品牌西门子`,
}, ...records]; tag:['#散饮机','#9成新'],
this.categoriesLoaded = true; location:'门店位置',
// 默认加载第一个分类的数据 image:[
if (this.flist.length > 0) { {img_src:'/static/image/bx.jpg'},
this.onCategoryChange(this.flist[0].id); {img_src:'/static/image/bx.jpg'}
} ]
}
})
},
// 重新加载当前分类数据
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);
},
// 点击商品项
navToList(item) {
uni.navigateTo({
url: '/pages/square/detiles?id='+item.id
});
},
//点赞
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') })
}
} }
} ],
loading: false,
refreshing: false,
loadingMore: false,
page: 1,
pageSize: 10,
hasMore: true,
defaultTags: ['样机机', '9成新']
}
},
onShow() {
this.loadList()
// this.syncTabbar()
},
methods: {
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') })
},
changeTab(key) {
if (this.currentTab === key) return
this.currentTab = key
this.page = 1
this.hasMore = true
this.slist = []
this.loadList()
},
loadList(isLoadMore = false) {
if (isLoadMore) {
if (this.loadingMore || this.loading || this.refreshing || !this.hasMore) return
this.loadingMore = true
this.page += 1
} else {
if (this.loading || this.loadingMore) return
this.loading = true
this.page = 1
this.hasMore = true
this.slist = []
}
const sortKey = this.currentTab === 'near' ? 'distance' : 'latest'
request({
url: 'common/post/list',
method: 'GET',
data: {
current: this.page,
size: this.pageSize,
sort: sortKey
}
}).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) {
this.slist = [...this.slist, ...records]
} 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.loadList(false)
},
onLoadMore() {
this.loadList(true)
},
navToList(item) {
uni.navigateTo({
url: '/pages/square/detiles?id=' + item.id
})
},
leftClick() {
uni.navigateBack()
}
},
computed: {
underlineTransform() {
const index = this.tabs.findIndex(t => t.key === this.currentTab)
return `translateX(${index * 50}%)`
}
}
}
</script> </script>
<style lang='scss'> <style lang="scss">
.empty{ page,
text-align: center; .page {
margin-top: 30%; height: 100%;
width: 100%; background: #f8f8f8;
} }
.empty image{
width: 252px;
height: 164px;
}
page,
.content {
height: 100%;
background-color: #f8f8f8;
position: relative;
}
.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;
}
}
.content-con {
height: calc(100% - 128rpx);
background-color: #f8f8f8;
display: flex;
}
.left-aside {
flex-shrink: 0;
width: 200upx;
height: 100%;
background-color: #F7F7F7;
}
.f-item {
display: flex;
align-items: center;
justify-content: flex-start;
width: 100%;
height: 90rpx;
font-size: 26rpx;
color: #666;
position: relative;
padding-left: 24rpx;
&.active {
color: #0071FC;
background: #fff;
&:before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
height: 36upx;
width: 8upx;
background-color: #0071FC;
border-radius: 0 4px 4px 0;
opacity: .8;
}
}
}
.iconview{
margin-right: 10px;
display: inline-block;
font-size: 22rpx;
}
.icons {
width: 16px;
height: 16px;
margin-right: 5px;
}
.right-aside {
flex: 1;
overflow: hidden;
/* padding-left: 20upx; */
/* padding-top: 20upx; */
background: #fff;
}
.t-lists {
display: flex;
flex-wrap: wrap;
padding: 20rpx;
justify-content: space-between;
}
.t-item{
width: 48%;
/* background: #fff; */
/* border-radius: 24rpx; */
overflow: hidden;
margin-bottom: 20rpx;
/* box-shadow: 0 8rpx 24rpx rgba(0,0,0,0.06); */
}
.grid-image{
width: 100%;
height: 320rpx;
background: #E5E5E5;
border-radius: 10rpx;
}
.grid-footer{
padding: 20rpx;
display: flex;
align-items: center;
}
.grid-avatar{
width: 48rpx;
height: 48rpx;
border-radius: 50%;
margin-right: 12rpx;
}
.grid-info{
flex: 1;
display: flex;
flex-direction: column;
width: 70%;
}
.grid-tag{
font-size: 24rpx;
color: #3F435B;
margin-bottom: 4rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 87%;
}
.category-icon image{
height: 16px;
width: 16px;
margin-right: 10rpx;
}
.load-more{
width: 100%;
text-align: center;
padding: 30rpx 0;
font-size: 26rpx;
color: #999;
}
.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;
}
.list-scroll {
height: calc(100vh - 120rpx);
}
.feed-list {
// padding: 20rpx;
margin: 20rpx 30rpx;
// background: #fff;
}
.load-more {
width: 100%;
text-align: center;
padding: 30rpx 0;
font-size: 26rpx;
color: #999;
}
.empty {
width: 100%;
text-align: center;
padding: 100rpx 0;
}
.empty image {
width: 252rpx;
height: 164rpx;
margin-bottom: 20rpx;
}
.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;
}
}
</style> </style>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 894 B

After

Width:  |  Height:  |  Size: 726 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 618 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 881 B

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 953 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 640 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1013 B

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB