Browse Source

feat(群组功能): 实现同城群功能模块

- 新增群组相关页面:创建群组、群组详情、群组聊天、群组管理
- 添加群组列表组件和API接口
- 修改活动页为群组列表页并实现搜索和分类功能
- 更新配置文件和路由配置支持群组功能
- 完善文件上传组件支持视频类型
- 添加群组功能文档和项目规则说明
master
主管理员 3 weeks ago
parent
commit
e2b171c7a5
18 changed files with 2714 additions and 94 deletions
  1. +6
    -0
      .trae/rules/project_rules.md
  2. +85
    -1
      api/api.js
  3. +2
    -2
      components/base/tabbar.vue
  4. +71
    -3
      components/list/dynamic/daynamicInfo.vue
  5. +150
    -0
      components/list/group/groupItem.vue
  6. +70
    -9
      components/list/square/waterfallItem.vue
  7. +2
    -1
      config.js
  8. +117
    -0
      doc/group_feature.md
  9. +28
    -0
      pages.json
  10. +445
    -60
      pages/index/activity.vue
  11. +3
    -0
      pages/index/index.vue
  12. +48
    -12
      pages/index/product.vue
  13. +300
    -0
      pages_order/group/createGroup.vue
  14. +304
    -0
      pages_order/group/groupChat.vue
  15. +601
    -0
      pages_order/group/groupDetail.vue
  16. +456
    -0
      pages_order/group/groupManage.vue
  17. +25
    -5
      pages_order/post/addPost.vue
  18. +1
    -1
      pages_order/post/postDetail.vue

+ 6
- 0
.trae/rules/project_rules.md View File

@ -0,0 +1,6 @@
功能模块
同城群:显示各个地区同城群聊的基本信息,不需要实时聊天、不需要加入群聊、点击进行播放广告看完广告显示群聊二维码

+ 85
- 1
api/api.js View File

@ -12,7 +12,7 @@ const config = {
// limit : 1000
// },
getConfig : {url : '/login/getConfigInfo', method : 'GET', limit : 500},
getConfig : {url : '/login/getConfigInfo', method : 'GET', auth: false, limit : 500},
// 微信登录接口
@ -63,21 +63,25 @@ const config = {
getClassInfo: {
url: '/city/getClassInfo',
method: 'GET',
auth: false,
},
//获取首页头部信息
getIndexHeaderInfo: {
url: '/city/getIndexHeaderInfo',
method: 'GET',
auth: false,
},
//获取banner列表
getBannerList: {
url: '/city/getBannerList',
method: 'GET',
auth: false,
},
//获取分类类型列表
getClassifyList: {
url: '/city/getClassifyList',
method: 'GET',
auth: false,
},
//获取工作信息列表
getJobPage: {
@ -441,6 +445,86 @@ const config = {
auth : true,
showLoading : true,
},
// 获取群组列表
getGroupList : {
url: '/group/getGroupList',
method: 'GET',
auth : false,
limit : 1000,
},
// 获取群组详情
getGroupDetail : {
url: '/group/getGroupDetail',
method: 'GET',
auth : true,
limit : 1000,
},
// 加入群组
joinGroup : {
url: '/group/joinGroup',
method: 'POST',
auth : true,
showLoading : true,
},
// 退出群组
quitGroup : {
url: '/group/quitGroup',
method: 'POST',
auth : true,
showLoading : true,
},
// 创建群组
createGroup : {
url: '/group/createGroup',
method: 'POST',
auth : true,
showLoading : true,
},
// 发送群组消息
sendGroupMessage : {
url: '/group/sendMessage',
method: 'POST',
auth : true,
showLoading : false,
},
// 获取群组消息列表
getGroupMessages : {
url: '/group/getMessages',
method: 'GET',
auth : true,
limit : 1000,
},
// 更新群组公告
updateGroupAnnouncement : {
url: '/group/updateAnnouncement',
method: 'POST',
auth : true,
showLoading : true,
},
// 解散群组
dissolveGroup : {
url: '/group/dissolveGroup',
method: 'POST',
auth : true,
showLoading : true,
},
// 转让群主
transferGroup : {
url: '/group/transferGroup',
method: 'POST',
auth : true,
showLoading : true,
},
}
const models = ['order']


+ 2
- 2
components/base/tabbar.vue View File

@ -54,8 +54,8 @@
"selectedIconPath": "/static/image/tabbar/order-a.png",
"iconPath": "/static/image/tabbar/order.png",
"pagePath": "/pages/index/activity",
"title": "活动",
icon : 'gift',
"title": "同城群",
icon : 'chat',
},
// {
// "selectedIconPath": "/static/image/tabbar/cart-a.png",


+ 71
- 3
components/list/dynamic/daynamicInfo.vue View File

@ -4,8 +4,15 @@
</view>
<view class="Artworkimages">
<view class="wrokimg" @click.stop="previewImage(images, i)" :key="i" v-for="(img, i) in images">
<image :src="img" mode="aspectFill"></image>
<view class="wrokimg" @click.stop="previewMedia(media, i)" :key="i" v-for="(media, i) in mediaList">
<image v-if="media.type === 'image'" :src="media.url" mode="aspectFill"></image>
<video v-else-if="media.type === 'video'" :src="media.url"
controls
:poster="media.poster || ''"
style="width: 190rpx; height: 190rpx; border-radius: 20rpx;"></video>
<!-- <view v-if="media.type === 'video'" class="video-overlay">
<uv-icon name="play-circle-fill" size="60rpx" color="#fff"></uv-icon>
</view> -->
</view>
</view>
@ -36,6 +43,33 @@
}
return arr
},
mediaList(){
let mediaArray = []
//
if(this.item.wxImage){
mediaArray.push({
url: this.item.wxImage,
type: 'image'
})
}
// image
if(this.item.image){
this.item.image.split(',').forEach(url => {
if(url.trim()){
//
const isVideo = this.isVideoFile(url.trim())
mediaArray.push({
url: url.trim(),
type: isVideo ? 'video' : 'image'
})
}
})
}
return mediaArray
}
},
data() {
@ -44,7 +78,25 @@
}
},
methods: {
previewMedia(media, index){
if(media.type === 'image'){
//
const imageUrls = this.mediaList.filter(item => item.type === 'image').map(item => item.url)
const imageIndex = this.mediaList.slice(0, index).filter(item => item.type === 'image').length
uni.previewImage({
urls: imageUrls,
current: imageIndex
})
} else if(media.type === 'video'){
// controls
console.log('播放视频:', media.url)
}
},
isVideoFile(url) {
const videoExtensions = ['.mp4', '.avi', '.mov', '.wmv', '.flv', '.webm', '.m4v']
const lowerUrl = url.toLowerCase()
return videoExtensions.some(ext => lowerUrl.includes(ext))
}
}
}
</script>
@ -63,12 +115,28 @@
.wrokimg {
margin: 10rpx;
position: relative;
image {
height: 190rpx;
width: 190rpx;
border-radius: 20rpx;
}
video {
height: 190rpx;
width: 190rpx;
border-radius: 20rpx;
}
.video-overlay {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
pointer-events: none;
z-index: 1;
}
}
}
</style>

+ 150
- 0
components/list/group/groupItem.vue View File

@ -0,0 +1,150 @@
<template>
<view class="group-item" @click="$emit('click')">
<view class="group-header">
<view class="group-avatar">
<image :src="item.avatar || '/static/image/logo.jpg'" mode="aspectFill"></image>
</view>
<view class="group-info">
<view class="group-name">{{ item.name || '群组名称' }}</view>
<view class="group-desc">{{ item.description || '群组描述' }}</view>
<view class="group-stats">
<text class="member-count">{{ item.memberCount || 0 }}</text>
<text class="separator">·</text>
<text class="group-type">{{ item.type || '同城群' }}</text>
</view>
</view>
<view class="group-status">
<view v-if="item.isJoined" class="joined-tag">已加入</view>
<view v-else class="join-btn">加入</view>
</view>
</view>
<view class="group-footer" v-if="item.lastMessage">
<view class="last-message">
<text class="message-prefix">最新消息</text>
<text class="message-content">{{ item.lastMessage }}</text>
</view>
<view class="message-time">{{ item.lastMessageTime || '' }}</view>
</view>
</view>
</template>
<script>
export default {
props: {
item: {
type: Object,
default: () => ({})
}
},
data() {
return {
}
},
methods: {
}
}
</script>
<style scoped lang="scss">
.group-item {
margin: 20rpx;
background-color: #fff;
padding: 30rpx;
border-radius: 20rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
.group-header {
display: flex;
align-items: center;
.group-avatar {
width: 120rpx;
height: 120rpx;
margin-right: 20rpx;
image {
width: 100%;
height: 100%;
border-radius: 20rpx;
}
}
.group-info {
flex: 1;
.group-name {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 8rpx;
}
.group-desc {
font-size: 26rpx;
color: #666;
margin-bottom: 8rpx;
line-height: 1.4;
}
.group-stats {
display: flex;
align-items: center;
font-size: 24rpx;
color: #999;
.separator {
margin: 0 8rpx;
}
}
}
.group-status {
.joined-tag {
background-color: #5baaff;
color: #fff;
padding: 8rpx 16rpx;
border-radius: 20rpx;
font-size: 24rpx;
}
.join-btn {
background-color: #f0f0f0;
color: #5baaff;
padding: 8rpx 16rpx;
border-radius: 20rpx;
font-size: 24rpx;
border: 1rpx solid #5baaff;
}
}
}
.group-footer {
margin-top: 20rpx;
padding-top: 20rpx;
border-top: 1rpx solid #f0f0f0;
display: flex;
justify-content: space-between;
align-items: center;
.last-message {
flex: 1;
font-size: 24rpx;
color: #999;
.message-prefix {
color: #666;
}
.message-content {
color: #999;
}
}
.message-time {
font-size: 22rpx;
color: #ccc;
}
}
}
</style>

+ 70
- 9
components/list/square/waterfallItem.vue View File

@ -1,8 +1,16 @@
<template>
<view class="waterfall-item" @click="handleClick">
<!-- 主图片 -->
<view class="main-image" v-if="mainImage">
<image :src="mainImage" mode="aspectFill" @click.stop="previewImage([mainImage])"></image>
<!-- 主媒体 -->
<view class="main-image" v-if="mainMedia">
<!-- 图片 -->
<image v-if="mainMedia.type === 'image'" :src="mainMedia.url" mode="aspectFill"></image>
<!-- 视频 -->
<view v-else-if="mainMedia.type === 'video'" class="video-container">
<video :src="mainMedia.url" :poster="mainMedia.url" :show-center-play-btn="false" :controls="false" muted></video>
<!-- <view class="video-overlay">
<uv-icon name="play-circle" size="60rpx" color="rgba(255,255,255,0.8)"></uv-icon>
</view> -->
</view>
<!-- 分类标签 -->
<view class="category-tag" v-if="item.classId_dictText">
#{{ item.classId_dictText }}
@ -78,14 +86,23 @@
}
},
computed: {
// -
mainImage() {
// -
mainMedia() {
if (this.item.wxImage) {
return this.item.wxImage
return {
url: this.item.wxImage,
type: 'image'
}
}
if (this.item.image) {
const images = this.item.image.split(',').filter(img => img.trim())
return images.length > 0 ? images[0] : null
const files = this.item.image.split(',').filter(file => file.trim())
if (files.length > 0) {
const firstFile = files[0].trim()
return {
url: firstFile,
type: this.isVideoFile(firstFile) ? 'video' : 'image'
}
}
}
return null
}
@ -102,6 +119,19 @@
this.$emit('like', this.item)
},
//
previewMedia(media) {
if (media.type === 'image') {
//
this.previewImage([media.url])
} else if (media.type === 'video') {
//
uni.navigateTo({
url: `/pages/video/videoPlayer?url=${encodeURIComponent(media.url)}`
})
}
},
//
previewImage(urls, current = 0) {
if (!urls || urls.length === 0) return
@ -112,6 +142,13 @@
})
},
//
isVideoFile(url) {
const videoExtensions = ['.mp4', '.avi', '.mov', '.wmv', '.flv', '.webm', '.m4v']
const lowerUrl = url.toLowerCase()
return videoExtensions.some(ext => lowerUrl.includes(ext))
},
//
formatContent(content) {
if (!content) return ''
@ -175,6 +212,30 @@
object-fit: cover;
}
.video-container {
position: relative;
width: 100%;
height: 400rpx;
video {
width: 100%;
height: 100%;
object-fit: cover;
}
.video-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.3);
}
}
.category-tag {
position: absolute;
top: 16rpx;
@ -294,4 +355,4 @@
}
}
}
</style>
</style>

+ 2
- 1
config.js View File

@ -13,7 +13,8 @@ const type = 'prod'
// 环境配置
const config = {
dev : {
baseUrl : 'http://youyi-test.natapp1.cc/api',
baseUrl : 'http://mac.natapp1.cc/api',//http://mac.natapp1.cc
// baseUrl : 'http://youyi-test.natapp1.cc/api',
},
prod : {
baseUrl : 'https://admin.hhlm1688.com/api',


+ 117
- 0
doc/group_feature.md View File

@ -0,0 +1,117 @@
# 同城群功能说明
## 功能概述
将原有的活动页面改造为同城群展示列表,提供完整的群组社交功能。
## 主要功能
### 1. 群组列表页面 (`pages/index/activity.vue`)
- 搜索群组功能
- 分类标签切换(推荐群组、同城群、兴趣群、工作群)
- 群组列表展示
- 创建群组按钮
### 2. 群组详情页面 (`pages_order/group/groupDetail.vue`)
- 群组基本信息展示
- 群组公告
- 成员列表
- 最近消息
- 加入/退出群组
- 进入聊天
- 群组管理(仅群主可见)
### 3. 群组聊天页面 (`pages_order/group/groupChat.vue`)
- 实时消息列表
- 发送消息功能
- 消息时间显示
- 自动滚动到底部
### 4. 创建群组页面 (`pages_order/group/createGroup.vue`)
- 群组头像上传
- 群组信息填写
- 群组类型选择
- 群组公告设置
- 加入方式设置
### 5. 群组管理页面 (`pages_order/group/groupManage.vue`)
- 编辑群组信息
- 成员管理
- 群组公告编辑
- 群组设置
- 转让群主
- 解散群组
## 组件结构
### 群组列表项组件 (`components/list/group/groupItem.vue`)
- 群组头像
- 群组名称和描述
- 成员数量
- 群组类型
- 加入状态
- 最新消息预览
## API接口
### 群组相关API
- `getGroupList` - 获取群组列表
- `getGroupDetail` - 获取群组详情
- `joinGroup` - 加入群组
- `quitGroup` - 退出群组
- `createGroup` - 创建群组
- `sendGroupMessage` - 发送群组消息
- `getGroupMessages` - 获取群组消息列表
- `updateGroupAnnouncement` - 更新群组公告
- `dissolveGroup` - 解散群组
- `transferGroup` - 转让群主
## 页面路由
- `/pages/index/activity` - 群组列表页
- `/pages_order/group/groupDetail` - 群组详情页
- `/pages_order/group/groupChat` - 群组聊天页
- `/pages_order/group/createGroup` - 创建群组页
- `/pages_order/group/groupManage` - 群组管理页
## 样式特点
- 采用现代化的卡片式设计
- 统一的蓝色主题色 (#5baaff)
- 响应式布局
- 良好的用户体验
## 技术实现
- 使用 Vue.js 框架
- 采用 uv-ui 组件库
- 支持微信小程序
- 模块化组件设计
- 统一的API调用方式
- 使用模拟数据,无需后台接口
## 数据说明
当前版本使用模拟数据,包含:
- 20个不同类型的群组(同城群、兴趣群、工作群、学习群等)
- 群组详情信息(成员列表、最近消息等)
- 聊天消息模拟
- 群组管理功能模拟
### 群组分类统计
**推荐群组**:显示所有20个群组
**同城群**:6个群组(江华同城交流群、江华租房信息群、江华二手交易群、江华宝妈交流群、江华医疗健康群、江华法律咨询群)
**兴趣群**:8个群组(江华美食分享群、江华旅游攻略群、江华宠物交流群、江华汽车交流群、江华健身运动群、江华摄影爱好者群、江华音乐爱好者群、江华园艺爱好者群)
**工作群**:4个群组(江华求职招聘群、江华IT技术交流群、江华创业交流群、江华电商交流群)
**学习群**:2个群组(江华学习交流群、江华读书会)
### 群组数据特点
- **成员数量**:从234人到2341人不等
- **加入状态**:部分群组已加入,部分未加入
- **群主权限**:部分群组拥有群主权限
- **最后消息**:包含真实的时间戳和消息内容
- **群组公告**:每个群组都有相应的公告内容
所有功能都可以正常演示,无需连接真实后台接口。

+ 28
- 0
pages.json View File

@ -218,6 +218,34 @@
"enablePullDownRefresh": true,
"navigationStyle": "custom"
}
},
{
"path": "group/groupDetail",
"style": {
"navigationBarTitleText": "群组详情",
"enablePullDownRefresh": false
}
},
{
"path": "group/groupChat",
"style": {
"navigationBarTitleText": "群组聊天",
"enablePullDownRefresh": false
}
},
{
"path": "group/createGroup",
"style": {
"navigationBarTitleText": "创建群组",
"enablePullDownRefresh": false
}
},
{
"path": "group/groupManage",
"style": {
"navigationBarTitleText": "群组管理",
"enablePullDownRefresh": false
}
}
]
}],


+ 445
- 60
pages/index/activity.vue View File

@ -1,37 +1,63 @@
<template>
<view class="page">
<navbar title="活动"/>
<navbar title="同城群"/>
<view class="swipe">
<uv-swiper
:list="bannerList"
indicator
height="320rpx"
keyName="image"></uv-swiper>
<!-- 顶部操作栏 -->
<view class="top-actions">
<view class="search-container">
<uv-search
v-model="searchKeyword"
placeholder="搜索群组名称"
@search="onSearch"
@clear="onClear"
bgColor="#f5f5f5"
:showAction="false"
></uv-search>
</view>
<view class="create-btn" @click="createGroup">
<uv-icon name="plus" size="30rpx" color="#fff"></uv-icon>
<text>创建群组</text>
</view>
</view>
<!-- 分类 -->
<view class="LabelOptions">
<uv-tabs :list="category"
:activeStyle="{color : '#000', fontWeight : 900}"
lineColor="#5baaff"
lineHeight="8rpx"
lineWidth="50rpx"
:scrollable="false"
@click="tabsClick"></uv-tabs>
<!-- 城市分类标签 -->
<view class="city-tabs-container">
<uv-tabs
:list="categoryList"
:current="currentCityTabIndex"
:activeStyle="{color: '#5baaff', fontWeight: 600}"
lineColor="#5baaff"
lineHeight="6rpx"
lineWidth="40rpx"
keyName="name"
@click="onCategoryClick"
/>
</view>
<!-- 群组列表 -->
<view class="group-list">
<groupItem
:key="index"
v-for="(item, index) in groupList"
:item="item"
@click="onGroupClick(item)"
/>
</view>
<!-- 空状态 -->
<view v-if="groupList.length === 0 && !loading" class="empty-state">
<uv-empty
mode="list"
text="暂无群组"
iconSize="120"
></uv-empty>
</view>
<view class="activityList">
<!-- <activityList :list="list"/> -->
<activityItem
:key="index"
v-for="(item, index) in list"
:item="item"
@click="$utils.navigateTo('/pages_order/activity/activityDetail?id=' + item.id)"
/>
<!-- 加载状态 -->
<view v-if="loading" class="loading-state">
<uv-loading-icon size="40"></uv-loading-icon>
<text>加载中...</text>
</view>
<tabber select="2" />
@ -40,64 +66,423 @@
<script>
import tabber from '@/components/base/tabbar.vue'
import activityList from '@/components/list/activityList.vue'
import activityItem from '@/components/list/activity/activityItem.vue'
import mixinsList from '@/mixins/list.js'
import groupItem from '@/components/list/group/groupItem.vue'
import { mapState } from 'vuex'
export default {
mixins : [mixinsList],
components : {
tabber,
activityList,
activityItem,
groupItem,
},
data() {
return {
mixinsListApi : 'getActivityPage',
bannerList : [],
category : [
{
name : '近期活动',
value : 0,
},
{
name : '往期活动',
value : 1,
}
],
searchKeyword: '',
loading: false,
currentCityTabIndex: 0, // uv-tabs
queryParams: {
cityId: null
},
groupList: [],
}
},
onShow() {
this.getBannerList()
computed: {
...mapState(['cityList']),
// ""
categoryList() {
const allTab = { name: '全部', value: null }
const cityTabs = this.cityList.map(city => ({
name: city.name || city.cityName || city.title,
value: city.id || city.cityId
}))
return [allTab, ...cityTabs]
}
},
onLoad() {
this.queryParams.className = 0
//
this.$store.commit('getCityList')
//
this.loadStaticGroupData()
},
onShow() {
//
if (this.groupList.length === 0) {
this.loadStaticGroupData()
}
},
methods: {
// banner
getBannerList(){
this.$api('getBannerList', res => {
if(res.code == 200){
this.bannerList = res.result
//
loadStaticGroupData(){
console.log('开始加载静态群组数据')
this.loading = true
//
setTimeout(() => {
this.loading = false
console.log('加载完成,开始筛选数据')
//
this.filterGroupListByCategory()
}, 500)
},
//
filterGroupListByCategory() {
const allGroups = [
{
id: 1,
name: '江华同城交流群',
description: '江华本地生活交流,分享美食、租房、工作信息',
avatar: '/static/image/logo.jpg',
memberCount: 1280,
type: '同城群',
category: 'local',
qrCode: '/static/image/qr/group1.jpg',
lastMessage: '有人知道江华哪里有好的租房信息吗?',
lastMessageTime: '2小时前'
},
{
id: 2,
name: '江华美食分享群',
description: '发现江华本地美食,分享美食攻略',
avatar: '/static/image/logo.jpg',
memberCount: 856,
type: '兴趣群',
category: 'interest',
qrCode: '/static/image/qr/group2.jpg',
lastMessage: '推荐一家新开的火锅店,味道不错',
lastMessageTime: '1小时前'
},
{
id: 3,
name: '江华租房信息群',
description: '江华租房信息发布与交流',
avatar: '/static/image/logo.jpg',
memberCount: 2341,
type: '同城群',
category: 'local',
qrCode: '/static/image/qr/group3.jpg',
lastMessage: '有单间出租,位置好,价格实惠',
lastMessageTime: '30分钟前'
},
{
id: 4,
name: '江华求职招聘群',
description: '江华本地求职招聘信息发布',
avatar: '/static/image/logo.jpg',
memberCount: 1567,
type: '工作群',
category: 'work',
qrCode: '/static/image/qr/group4.jpg',
lastMessage: '招聘销售员,待遇优厚',
lastMessageTime: '1天前'
},
{
id: 5,
name: '江华旅游攻略群',
description: '江华旅游景点推荐,攻略分享',
avatar: '/static/image/logo.jpg',
memberCount: 623,
type: '兴趣群',
category: 'interest',
qrCode: '/static/image/qr/group5.jpg',
lastMessage: '周末去瑶族文化园玩,有一起的吗?',
lastMessageTime: '3小时前'
},
{
id: 6,
name: '江华学习交流群',
description: '江华本地学习交流,分享学习资源',
avatar: '/static/image/logo.jpg',
memberCount: 445,
type: '学习群',
category: 'study',
qrCode: '/static/image/qr/group6.jpg',
lastMessage: '有人一起学习英语吗?',
lastMessageTime: '2天前'
},
{
id: 7,
name: '江华二手交易群',
description: '江华本地二手物品交易',
avatar: '/static/image/logo.jpg',
memberCount: 1890,
type: '同城群',
category: 'local',
qrCode: '/static/image/qr/group7.jpg',
lastMessage: '出售一台九成新的洗衣机',
lastMessageTime: '1小时前'
},
{
id: 8,
name: '江华宠物交流群',
description: '江华宠物爱好者交流群',
avatar: '/static/image/logo.jpg',
memberCount: 567,
type: '兴趣群',
category: 'interest',
qrCode: '/static/image/qr/group8.jpg',
lastMessage: '有人知道江华哪里有好的宠物医院吗?',
lastMessageTime: '4小时前'
},
{
id: 9,
name: '江华汽车交流群',
description: '江华汽车爱好者交流,分享用车心得',
avatar: '/static/image/logo.jpg',
memberCount: 892,
type: '兴趣群',
category: 'interest',
qrCode: '/static/image/qr/group9.jpg',
lastMessage: '有人知道江华哪里有好的汽车维修店吗?',
lastMessageTime: '5小时前'
},
{
id: 10,
name: '江华宝妈交流群',
description: '江华宝妈育儿经验分享',
avatar: '/static/image/logo.jpg',
memberCount: 1234,
type: '同城群',
category: 'local',
qrCode: '/static/image/qr/group10.jpg',
lastMessage: '推荐江华最好的儿童医院',
lastMessageTime: '6小时前'
},
{
id: 11,
name: '江华IT技术交流群',
description: '江华IT从业者技术交流',
avatar: '/static/image/logo.jpg',
memberCount: 345,
type: '工作群',
category: 'work',
qrCode: '/static/image/qr/group11.jpg',
lastMessage: '有前端开发的工作机会吗?',
lastMessageTime: '1天前'
},
{
id: 12,
name: '江华健身运动群',
description: '江华健身爱好者交流群',
avatar: '/static/image/logo.jpg',
memberCount: 678,
type: '兴趣群',
category: 'interest',
qrCode: '/static/image/qr/group12.jpg',
lastMessage: '有人一起去健身房吗?',
lastMessageTime: '2小时前'
},
{
id: 13,
name: '江华摄影爱好者群',
description: '江华摄影爱好者交流群',
avatar: '/static/image/logo.jpg',
memberCount: 456,
type: '兴趣群',
category: 'interest',
qrCode: '/static/image/qr/group13.jpg',
lastMessage: '分享一张江华夜景照片',
lastMessageTime: '1天前'
},
{
id: 14,
name: '江华创业交流群',
description: '江华创业者交流平台',
avatar: '/static/image/logo.jpg',
memberCount: 789,
type: '工作群',
category: 'work',
qrCode: '/static/image/qr/group14.jpg',
lastMessage: '寻找创业合作伙伴',
lastMessageTime: '3天前'
},
{
id: 15,
name: '江华医疗健康群',
description: '江华医疗健康咨询交流',
avatar: '/static/image/logo.jpg',
memberCount: 1123,
type: '同城群',
category: 'local',
qrCode: '/static/image/qr/group15.jpg',
lastMessage: '推荐江华最好的牙科医院',
lastMessageTime: '4小时前'
},
{
id: 16,
name: '江华读书会',
description: '江华读书爱好者交流群',
avatar: '/static/image/logo.jpg',
memberCount: 234,
type: '学习群',
category: 'study',
qrCode: '/static/image/qr/group16.jpg',
lastMessage: '推荐一本好书《江华瑶族文化》',
lastMessageTime: '1周前'
},
{
id: 17,
name: '江华音乐爱好者群',
description: '江华音乐爱好者交流群',
avatar: '/static/image/logo.jpg',
memberCount: 567,
type: '兴趣群',
category: 'interest',
qrCode: '/static/image/qr/group17.jpg',
lastMessage: '有人会弹吉他吗?想学',
lastMessageTime: '2天前'
},
{
id: 18,
name: '江华电商交流群',
description: '江华电商从业者交流群',
avatar: '/static/image/logo.jpg',
memberCount: 890,
type: '工作群',
category: 'work',
qrCode: '/static/image/qr/group18.jpg',
lastMessage: '分享电商运营经验',
lastMessageTime: '1周前'
},
{
id: 19,
name: '江华园艺爱好者群',
description: '江华园艺爱好者交流群',
avatar: '/static/image/logo.jpg',
memberCount: 345,
type: '兴趣群',
category: 'interest',
qrCode: '/static/image/qr/group19.jpg',
lastMessage: '推荐江华最好的花店',
lastMessageTime: '3天前'
},
{
id: 20,
name: '江华法律咨询群',
description: '江华法律咨询服务交流',
avatar: '/static/image/logo.jpg',
memberCount: 678,
type: '同城群',
category: 'local',
qrCode: '/static/image/qr/group20.jpg',
lastMessage: '免费法律咨询服务',
lastMessageTime: '5天前'
}
})
]
this.groupList = allGroups
//
this.$forceUpdate()
},
//
onSearch(keyword) {
this.searchKeyword = keyword
this.filterGroups()
},
//
onClear() {
this.searchKeyword = ''
this.filterGroups()
},
//
onCategoryClick(item) {
this.currentCityTabIndex = item.index
if (item.value) {
// 使ID
this.queryParams.cityId = item.value
delete this.queryParams.category //
} else {
// ""
delete this.queryParams.cityId
delete this.queryParams.category
}
this.loadStaticGroupData()
},
tabsClick(item) {
this.queryParams.className = item.value
this.getData()
//
onGroupClick(item) {
//
this.$utils.navigateTo('/pages_order/group/groupDetail?id=' + item.id)
},
//
filterGroups() {
if (!this.searchKeyword) {
return
}
//
},
//
createGroup() {
this.$utils.navigateTo('/pages_order/group/createGroup')
}
}
}
</script>
<style scoped lang="scss">
.page{
.activityList{
// padding: 0 20rpx;
background-color: #f5f5f5;
min-height: 100vh;
.top-actions {
background-color: #fff;
padding: 20rpx;
display: flex;
align-items: center;
gap: 20rpx;
.search-container {
flex: 1;
}
.create-btn {
display: flex;
align-items: center;
background-color: #5baaff;
color: #fff;
padding: 15rpx 20rpx;
border-radius: 30rpx;
font-size: 26rpx;
text {
margin-left: 8rpx;
}
}
}
.category-tabs {
background-color: #fff;
margin-bottom: 20rpx;
}
.swipe{
overflow: hidden;
border-radius: 20rpx;
margin: 20rpx;
.group-list {
padding-bottom: 120rpx;
}
.empty-state {
padding: 100rpx 0;
text-align: center;
}
.loading-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 0;
text {
margin-top: 20rpx;
color: #999;
font-size: 28rpx;
}
}
}
</style>

+ 3
- 0
pages/index/index.vue View File

@ -205,6 +205,7 @@
mixinsListApi: 'getPostPage',
bannerList: [],
// onShowData: false,
templateIds: [
'uXZnHWrjtcX9JHlnMpdlWmzgJp71sKxCRiMn3TrE-EE',
'gTzGpOfJcYxtbvPG9OHnhbureKz5XLG8NPyECUGb2lw',
@ -616,5 +617,7 @@
.dynamicList {
padding-top: 10rpx;
}
}
</style>

+ 48
- 12
pages/index/product.vue View File

@ -27,14 +27,17 @@
</view>
<!-- 分类 -->
<view class="LabelOptions">
<uv-tabs :list="productCategory"
:activeStyle="{color : '#000', fontWeight : 900}"
lineColor="#5baaff"
lineHeight="8rpx"
lineWidth="50rpx"
:scrollable="false"
@click="tabsClick"></uv-tabs>
<view class="city-tabs-container">
<uv-tabs
:list="productCategory"
:current="currentCityTabIndex"
:activeStyle="{color: '#5baaff', fontWeight: 600}"
lineColor="#5baaff"
lineHeight="6rpx"
lineWidth="40rpx"
keyName="title"
@click="tabsClick"
/>
</view>
<view class="productList">
@ -107,7 +110,18 @@
productList,
},
computed: {
...mapState(['city', 'userInfo', 'headInfo']),
...mapState(['city', 'userInfo', 'headInfo', 'cityList']),
// ""
productCategory() {
const allTab = { id: null, title: '全部', value: null }
const cityTabs = this.cityList.map(city => ({
id: city.id || city.cityId,
title: city.name || city.cityName || city.title,
value: city.id || city.cityId
}))
return [allTab, ...cityTabs]
}
// imageStyle(item) {
// return item => {
// const v = uni.upx2px(750) - this.leftGap - this.rightGap - this.columnGap;
@ -124,6 +138,7 @@
data() {
return {
mixinsListApi: 'getShopPingPage',
currentCityTabIndex: 0, // uv-tabs
// list1: [], //
// list2: [], //
// leftGap: 10,
@ -158,7 +173,11 @@
this.getBannerList()
},
onLoad() {
this.queryParams.className = 0
//
this.$store.commit('getCityList')
//
this.queryParams.cityId = null
},
methods: {
// e.namelist1list2
@ -174,8 +193,18 @@
})
},
tabsClick(item) {
this.queryParams.className = item.value
this.getData()
this.currentCityTabIndex = item.index
if (item.value) {
// 使ID
this.queryParams.cityId = item.value
delete this.queryParams.className //
} else {
// ""
delete this.queryParams.cityId
delete this.queryParams.className
}
this.refreshList()
},
}
@ -196,6 +225,13 @@
.productList{
padding: 0 20rpx;
}
//
.city-tabs-container {
background-color: #fff;
border-bottom: 1rpx solid #eee;
padding: 0 20rpx;
}
}


+ 300
- 0
pages_order/group/createGroup.vue View File

@ -0,0 +1,300 @@
<template>
<view class="page">
<navbar title="创建群组" bgColor="#5baaff" color="#fff" leftClick @leftClick="$utils.navigateBack" />
<view class="form-container">
<!-- 群组头像 -->
<view class="avatar-section">
<view class="avatar-title">群组头像</view>
<view class="avatar-upload" @click="chooseAvatar">
<image v-if="groupForm.avatar" :src="groupForm.avatar" class="avatar-preview" mode="aspectFill"></image>
<view v-else class="avatar-placeholder">
<uv-icon name="camera" size="60rpx" color="#ccc"></uv-icon>
<text>点击上传头像</text>
</view>
</view>
</view>
<!-- 群组信息表单 -->
<view class="form-section">
<view class="form-item">
<view class="form-label">群组名称</view>
<uv-input
v-model="groupForm.name"
placeholder="请输入群组名称"
:border="false"
:clearable="true"
></uv-input>
</view>
<view class="form-item">
<view class="form-label">群组描述</view>
<uv-textarea
v-model="groupForm.description"
placeholder="请输入群组描述"
:border="false"
:maxlength="200"
height="120"
></uv-textarea>
</view>
<view class="form-item">
<view class="form-label">群组类型</view>
<uv-picker
:list="groupTypes"
v-model="groupForm.type"
@confirm="onTypeConfirm"
>
<view class="picker-trigger">
<text>{{ getTypeName(groupForm.type) }}</text>
<uv-icon name="arrow-right" size="30rpx" color="#ccc"></uv-icon>
</view>
</uv-picker>
</view>
<view class="form-item">
<view class="form-label">群组公告</view>
<uv-textarea
v-model="groupForm.announcement"
placeholder="请输入群组公告(可选)"
:border="false"
:maxlength="500"
height="120"
></uv-textarea>
</view>
<view class="form-item">
<view class="form-label">加入方式</view>
<uv-radio-group v-model="groupForm.joinType">
<view class="radio-item">
<uv-radio name="free" label="自由加入"></uv-radio>
</view>
<view class="radio-item">
<uv-radio name="approve" label="需要审核"></uv-radio>
</view>
</uv-radio-group>
</view>
</view>
<!-- 创建按钮 -->
<view class="submit-section">
<uv-button
type="primary"
text="创建群组"
:loading="submitting"
@click="createGroup"
></uv-button>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
submitting: false,
groupForm: {
name: '',
description: '',
avatar: '',
type: 'local',
announcement: '',
joinType: 'free'
},
groupTypes: [
{
label: '同城群',
value: 'local'
},
{
label: '兴趣群',
value: 'interest'
},
{
label: '工作群',
value: 'work'
},
{
label: '学习群',
value: 'study'
}
]
}
},
methods: {
//
chooseAvatar() {
uni.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
this.groupForm.avatar = res.tempFilePaths[0]
//
this.uploadAvatar(res.tempFilePaths[0])
}
})
},
//
uploadAvatar(filePath) {
//
console.log('上传头像:', filePath)
},
//
onTypeConfirm(e) {
this.groupForm.type = e.value
},
//
getTypeName(type) {
const typeItem = this.groupTypes.find(item => item.value === type)
return typeItem ? typeItem.label : '请选择群组类型'
},
//
createGroup() {
if (!this.groupForm.name.trim()) {
uni.showToast({
title: '请输入群组名称',
icon: 'none'
})
return
}
if (!this.groupForm.description.trim()) {
uni.showToast({
title: '请输入群组描述',
icon: 'none'
})
return
}
this.submitting = true
//
setTimeout(() => {
this.submitting = false
uni.showToast({
title: '创建成功',
icon: 'success'
})
//
setTimeout(() => {
this.$utils.navigateTo('/pages_order/group/groupDetail?id=999')
}, 1500)
}, 1000)
}
}
}
</script>
<style scoped lang="scss">
.page {
background-color: #f5f5f5;
min-height: 100vh;
.form-container {
padding: 20rpx;
.avatar-section {
background-color: #fff;
padding: 30rpx;
border-radius: 20rpx;
margin-bottom: 20rpx;
.avatar-title {
font-size: 30rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
}
.avatar-upload {
display: flex;
justify-content: center;
.avatar-preview {
width: 120rpx;
height: 120rpx;
border-radius: 20rpx;
}
.avatar-placeholder {
width: 120rpx;
height: 120rpx;
border: 2rpx dashed #ddd;
border-radius: 20rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text {
font-size: 20rpx;
color: #999;
margin-top: 10rpx;
}
}
}
}
.form-section {
background-color: #fff;
padding: 30rpx;
border-radius: 20rpx;
margin-bottom: 20rpx;
.form-item {
margin-bottom: 30rpx;
&:last-child {
margin-bottom: 0;
}
.form-label {
font-size: 28rpx;
color: #333;
margin-bottom: 15rpx;
font-weight: bold;
}
.picker-trigger {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 0;
border-bottom: 1rpx solid #f0f0f0;
text {
font-size: 28rpx;
color: #333;
}
}
.radio-item {
margin-bottom: 15rpx;
&:last-child {
margin-bottom: 0;
}
}
}
}
.submit-section {
padding: 40rpx 0;
.uv-button {
width: 100%;
height: 80rpx;
border-radius: 40rpx;
font-size: 32rpx;
}
}
}
}
</style>

+ 304
- 0
pages_order/group/groupChat.vue View File

@ -0,0 +1,304 @@
<template>
<view class="page">
<navbar title="群组聊天" bgColor="#5baaff" color="#fff" leftClick @leftClick="$utils.navigateBack" />
<!-- 消息列表 -->
<scroll-view
class="message-list"
scroll-y="true"
:scroll-top="scrollTop"
@scrolltoupper="loadMoreMessages"
>
<view class="message-container">
<view
v-for="(message, index) in messageList"
:key="message.id || index"
class="message-item"
:class="{ 'message-mine': message.isMine }"
>
<image
:src="message.avatar || '/static/image/logo.jpg'"
class="message-avatar"
mode="aspectFill"
></image>
<view class="message-content">
<view class="message-info">
<text class="sender-name">{{ message.senderName }}</text>
<text class="message-time">{{ message.time }}</text>
</view>
<view class="message-text">{{ message.content }}</view>
</view>
</view>
</view>
</scroll-view>
<!-- 输入框 -->
<view class="input-container">
<view class="input-box">
<uv-input
v-model="inputMessage"
placeholder="输入消息..."
:border="false"
:clearable="true"
@confirm="sendMessage"
></uv-input>
</view>
<view class="send-btn" @click="sendMessage">
<uv-icon name="paperplane" size="40rpx" color="#fff"></uv-icon>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
groupId: '',
groupInfo: {},
inputMessage: '',
scrollTop: 0,
messageList: [
{
id: 1,
senderName: '张三',
avatar: '/static/image/logo.jpg',
content: '大家好,欢迎加入群组!',
time: '10:30',
isMine: false
},
{
id: 2,
senderName: '我',
avatar: '/static/image/logo.jpg',
content: '谢谢,很高兴认识大家',
time: '10:32',
isMine: true
},
{
id: 3,
senderName: '李四',
avatar: '/static/image/logo.jpg',
content: '有人知道江华哪里有好的租房信息吗?',
time: '10:35',
isMine: false
},
{
id: 4,
senderName: '王五',
avatar: '/static/image/logo.jpg',
content: '我这边有个单间出租,位置不错',
time: '10:38',
isMine: false
}
]
}
},
onLoad(options) {
if (options.id) {
this.groupId = options.id
this.getGroupInfo()
}
},
onShow() {
this.scrollToBottom()
},
methods: {
//
getGroupInfo() {
//
const mockGroupInfo = {
1: { id: 1, name: '江华同城交流群' },
2: { id: 2, name: '江华美食分享群' },
3: { id: 3, name: '江华租房信息群' },
4: { id: 4, name: '江华求职招聘群' },
5: { id: 5, name: '江华旅游攻略群' },
6: { id: 6, name: '江华学习交流群' },
7: { id: 7, name: '江华二手交易群' },
8: { id: 8, name: '江华宠物交流群' },
9: { id: 9, name: '江华汽车交流群' },
10: { id: 10, name: '江华宝妈交流群' },
11: { id: 11, name: '江华IT技术交流群' },
12: { id: 12, name: '江华健身运动群' },
13: { id: 13, name: '江华摄影爱好者群' },
14: { id: 14, name: '江华创业交流群' },
15: { id: 15, name: '江华医疗健康群' },
16: { id: 16, name: '江华读书会' },
17: { id: 17, name: '江华音乐爱好者群' },
18: { id: 18, name: '江华电商交流群' },
19: { id: 19, name: '江华园艺爱好者群' },
20: { id: 20, name: '江华法律咨询群' }
}
setTimeout(() => {
this.groupInfo = mockGroupInfo[this.groupId] || { name: '群组聊天' }
}, 300)
},
//
sendMessage() {
if (!this.inputMessage.trim()) {
return
}
const message = {
id: Date.now(),
senderName: '我',
avatar: '/static/image/logo.jpg',
content: this.inputMessage,
time: this.getCurrentTime(),
isMine: true
}
this.messageList.push(message)
this.inputMessage = ''
//
this.$nextTick(() => {
this.scrollToBottom()
})
// API
// this.$api('sendGroupMessage', {
// groupId: this.groupId,
// content: message.content
// }, res => {
// if (res.code == 200) {
// console.log('')
// }
// })
},
//
loadMoreMessages() {
//
console.log('加载更多消息')
},
//
scrollToBottom() {
this.scrollTop = 999999
},
//
getCurrentTime() {
const now = new Date()
const hours = now.getHours().toString().padStart(2, '0')
const minutes = now.getMinutes().toString().padStart(2, '0')
return `${hours}:${minutes}`
}
}
}
</script>
<style scoped lang="scss">
.page {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #f5f5f5;
.message-list {
flex: 1;
padding: 20rpx;
.message-container {
.message-item {
display: flex;
margin-bottom: 30rpx;
&.message-mine {
flex-direction: row-reverse;
.message-content {
margin-left: 0;
margin-right: 20rpx;
align-items: flex-end;
.message-info {
flex-direction: row-reverse;
.sender-name {
margin-left: 10rpx;
margin-right: 0;
}
}
.message-text {
background-color: #5baaff;
color: #fff;
border-radius: 20rpx 20rpx 4rpx 20rpx;
}
}
}
.message-avatar {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
}
.message-content {
flex: 1;
margin-left: 20rpx;
display: flex;
flex-direction: column;
.message-info {
display: flex;
align-items: center;
margin-bottom: 8rpx;
.sender-name {
font-size: 24rpx;
color: #666;
margin-right: 10rpx;
}
.message-time {
font-size: 22rpx;
color: #999;
}
}
.message-text {
background-color: #fff;
padding: 20rpx;
border-radius: 20rpx 20rpx 20rpx 4rpx;
font-size: 28rpx;
color: #333;
line-height: 1.4;
word-break: break-all;
}
}
}
}
}
.input-container {
background-color: #fff;
padding: 20rpx;
border-top: 1rpx solid #f0f0f0;
display: flex;
align-items: center;
gap: 20rpx;
.input-box {
flex: 1;
background-color: #f5f5f5;
border-radius: 30rpx;
padding: 0 20rpx;
}
.send-btn {
width: 80rpx;
height: 80rpx;
background-color: #5baaff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
}
}
</style>

+ 601
- 0
pages_order/group/groupDetail.vue View File

@ -0,0 +1,601 @@
<template>
<view class="page">
<navbar title="群组详情" bgColor="#5baaff" color="#fff" leftClick @leftClick="$utils.navigateBack" />
<!-- 群组信息 -->
<view class="group-info">
<view class="group-header">
<image :src="groupInfo.avatar || '/static/image/logo.jpg'" class="group-avatar" mode="aspectFill"></image>
<view class="group-details">
<view class="group-name">{{ groupInfo.name || '群组名称' }}</view>
<view class="group-desc">{{ groupInfo.description || '群组描述' }}</view>
<view class="group-stats">
<text class="member-count">{{ groupInfo.memberCount || 0 }}</text>
<text class="separator">·</text>
<text class="group-type">{{ groupInfo.type || '同城群' }}</text>
</view>
</view>
</view>
<view class="group-actions">
<view v-if="showQrCode" class="qr-code-section">
<view class="qr-code-title">扫码加入群聊</view>
<image :src="groupInfo.qrCode || '/static/image/qr/default.jpg'" class="qr-code-image" mode="aspectFit"></image>
<view class="qr-code-tip">长按保存二维码微信扫码加入</view>
</view>
<view v-else class="watch-ad-btn" @click="watchAd">
<uv-icon name="play-circle" size="30rpx" color="#fff"></uv-icon>
<text>观看广告获取二维码</text>
</view>
<view v-if="groupInfo.isOwner" class="manage-btn" @click="manageGroup">
<uv-icon name="setting" size="30rpx" color="#fff"></uv-icon>
<text>编辑群组信息</text>
</view>
</view>
</view>
<!-- 群组公告 -->
<view v-if="groupInfo.announcement" class="announcement">
<view class="announcement-title">
<uv-icon name="volume" size="30rpx" color="#5baaff"></uv-icon>
<text>群组公告</text>
</view>
<view class="announcement-content">{{ groupInfo.announcement }}</view>
</view>
<!-- 成员列表 -->
<view class="member-section">
<view class="section-header">
<text class="section-title">群组成员</text>
<text class="member-count-text">({{ groupInfo.memberCount || 0 }})</text>
</view>
<view class="member-list">
<view
v-for="(member, index) in memberList"
:key="index"
class="member-item"
@click="viewMemberProfile(member)"
>
<image :src="member.avatar || '/static/image/logo.jpg'" class="member-avatar" mode="aspectFill"></image>
<view class="member-info">
<view class="member-name">{{ member.name || '用户' }}</view>
<view class="member-role">{{ member.role || '成员' }}</view>
</view>
<view class="member-status">
<view v-if="member.isOnline" class="online-status">在线</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
groupId: '',
showQrCode: false, //
groupInfo: {
id: 1,
name: '江华同城交流群',
description: '江华本地生活交流,分享美食、租房、工作信息',
avatar: '/static/image/logo.jpg',
memberCount: 1280,
type: '同城群',
qrCode: '/static/image/qr/group1.jpg',
isOwner: true,
announcement: '欢迎加入江华同城交流群!请遵守群规,文明交流。'
},
memberList: [
{
id: 1,
name: '张三',
avatar: '/static/image/logo.jpg',
role: '群主',
isOnline: true
},
{
id: 2,
name: '李四',
avatar: '/static/image/logo.jpg',
role: '管理员',
isOnline: true
},
{
id: 3,
name: '王五',
avatar: '/static/image/logo.jpg',
role: '成员',
isOnline: false
}
],
}
},
onLoad(options) {
if (options.id) {
this.groupId = options.id
this.getGroupDetail()
}
},
methods: {
//
getGroupDetail() {
//
const mockGroupData = {
1: {
id: 1,
name: '江华同城交流群',
description: '江华本地生活交流,分享美食、租房、工作信息',
avatar: '/static/image/logo.jpg',
memberCount: 1280,
type: '同城群',
isJoined: true,
isOwner: true,
announcement: '欢迎加入江华同城交流群!请遵守群规,文明交流。'
},
2: {
id: 2,
name: '江华美食分享群',
description: '发现江华本地美食,分享美食攻略',
avatar: '/static/image/logo.jpg',
memberCount: 856,
type: '兴趣群',
isJoined: false,
isOwner: false,
announcement: '欢迎美食爱好者加入!请分享美食照片和推荐。'
},
3: {
id: 3,
name: '江华租房信息群',
description: '江华租房信息发布与交流',
avatar: '/static/image/logo.jpg',
memberCount: 2341,
type: '同城群',
isJoined: true,
isOwner: false,
announcement: '租房信息请详细说明位置、价格、联系方式。'
},
4: {
id: 4,
name: '江华求职招聘群',
description: '江华本地求职招聘信息发布',
avatar: '/static/image/logo.jpg',
memberCount: 1567,
type: '工作群',
isJoined: false,
isOwner: false,
announcement: '招聘信息请详细说明职位要求、薪资待遇、联系方式。'
},
5: {
id: 5,
name: '江华旅游攻略群',
description: '江华旅游景点推荐,攻略分享',
avatar: '/static/image/logo.jpg',
memberCount: 623,
type: '兴趣群',
isJoined: true,
isOwner: false,
announcement: '欢迎分享江华旅游攻略和景点推荐!'
},
6: {
id: 6,
name: '江华学习交流群',
description: '江华本地学习交流,分享学习资源',
avatar: '/static/image/logo.jpg',
memberCount: 445,
type: '学习群',
isJoined: false,
isOwner: false,
announcement: '欢迎学习爱好者加入!请分享学习心得和资源。'
},
7: {
id: 7,
name: '江华二手交易群',
description: '江华本地二手物品交易',
avatar: '/static/image/logo.jpg',
memberCount: 1890,
type: '同城群',
isJoined: true,
isOwner: false,
announcement: '二手交易请详细说明物品状况、价格、联系方式。'
},
8: {
id: 8,
name: '江华宠物交流群',
description: '江华宠物爱好者交流群',
avatar: '/static/image/logo.jpg',
memberCount: 567,
type: '兴趣群',
isJoined: false,
isOwner: false,
announcement: '欢迎宠物爱好者加入!请分享宠物照片和养护经验。'
},
9: {
id: 9,
name: '江华汽车交流群',
description: '江华汽车爱好者交流,分享用车心得',
avatar: '/static/image/logo.jpg',
memberCount: 892,
type: '兴趣群',
isJoined: true,
isOwner: false,
announcement: '欢迎汽车爱好者加入!请分享用车心得和维修经验。'
},
10: {
id: 10,
name: '江华宝妈交流群',
description: '江华宝妈育儿经验分享',
avatar: '/static/image/logo.jpg',
memberCount: 1234,
type: '同城群',
isJoined: false,
isOwner: false,
announcement: '欢迎宝妈加入!请分享育儿经验和心得。'
},
11: {
id: 11,
name: '江华IT技术交流群',
description: '江华IT从业者技术交流',
avatar: '/static/image/logo.jpg',
memberCount: 345,
type: '工作群',
isJoined: true,
isOwner: true,
announcement: '欢迎IT从业者加入!请分享技术心得和项目经验。'
},
12: {
id: 12,
name: '江华健身运动群',
description: '江华健身爱好者交流群',
avatar: '/static/image/logo.jpg',
memberCount: 678,
type: '兴趣群',
isJoined: false,
isOwner: false,
announcement: '欢迎健身爱好者加入!请分享健身心得和运动计划。'
},
13: {
id: 13,
name: '江华摄影爱好者群',
description: '江华摄影爱好者交流群',
avatar: '/static/image/logo.jpg',
memberCount: 456,
type: '兴趣群',
isJoined: true,
isOwner: false,
announcement: '欢迎摄影爱好者加入!请分享摄影作品和技巧。'
},
14: {
id: 14,
name: '江华创业交流群',
description: '江华创业者交流平台',
avatar: '/static/image/logo.jpg',
memberCount: 789,
type: '工作群',
isJoined: false,
isOwner: false,
announcement: '欢迎创业者加入!请分享创业心得和项目经验。'
},
15: {
id: 15,
name: '江华医疗健康群',
description: '江华医疗健康咨询交流',
avatar: '/static/image/logo.jpg',
memberCount: 1123,
type: '同城群',
isJoined: true,
isOwner: false,
announcement: '欢迎医疗健康从业者加入!请分享健康知识和医疗经验。'
},
16: {
id: 16,
name: '江华读书会',
description: '江华读书爱好者交流群',
avatar: '/static/image/logo.jpg',
memberCount: 234,
type: '学习群',
isJoined: false,
isOwner: false,
announcement: '欢迎读书爱好者加入!请分享读书心得和好书推荐。'
},
17: {
id: 17,
name: '江华音乐爱好者群',
description: '江华音乐爱好者交流群',
avatar: '/static/image/logo.jpg',
memberCount: 567,
type: '兴趣群',
isJoined: true,
isOwner: false,
announcement: '欢迎音乐爱好者加入!请分享音乐作品和演奏技巧。'
},
18: {
id: 18,
name: '江华电商交流群',
description: '江华电商从业者交流群',
avatar: '/static/image/logo.jpg',
memberCount: 890,
type: '工作群',
isJoined: false,
isOwner: false,
announcement: '欢迎电商从业者加入!请分享电商运营经验和技巧。'
},
19: {
id: 19,
name: '江华园艺爱好者群',
description: '江华园艺爱好者交流群',
avatar: '/static/image/logo.jpg',
memberCount: 345,
type: '兴趣群',
isJoined: true,
isOwner: false,
announcement: '欢迎园艺爱好者加入!请分享园艺心得和植物养护经验。'
},
20: {
id: 20,
name: '江华法律咨询群',
description: '江华法律咨询服务交流',
avatar: '/static/image/logo.jpg',
memberCount: 678,
type: '同城群',
isJoined: false,
isOwner: false,
announcement: '欢迎法律从业者加入!请提供专业的法律咨询服务。'
}
}
// API
setTimeout(() => {
this.groupInfo = mockGroupData[this.groupId] || this.groupInfo
}, 500)
},
// 广
watchAd() {
uni.showLoading({
title: '加载广告中...'
})
// 广
setTimeout(() => {
uni.hideLoading()
uni.showModal({
title: '广告播放完成',
content: '恭喜您获得群聊二维码!',
showCancel: false,
success: () => {
this.showQrCode = true
}
})
}, 2000)
},
//
viewMemberProfile(member) {
this.$utils.navigateTo('/pages_order/profile/userProfile?id=' + member.id)
},
//
manageGroup() {
this.$utils.navigateTo('/pages_order/group/createGroup?id=' + this.groupId)
}
}
}
</script>
<style scoped lang="scss">
.page {
background-color: #f5f5f5;
min-height: 100vh;
.group-info {
background-color: #fff;
padding: 30rpx;
margin-bottom: 20rpx;
.group-header {
display: flex;
align-items: center;
margin-bottom: 30rpx;
.group-avatar {
width: 120rpx;
height: 120rpx;
margin-right: 20rpx;
image {
width: 100%;
height: 100%;
border-radius: 20rpx;
}
}
.group-details {
flex: 1;
.group-name {
font-size: 36rpx;
font-weight: bold;
color: #333;
margin-bottom: 10rpx;
}
.group-desc {
font-size: 28rpx;
color: #666;
margin-bottom: 10rpx;
line-height: 1.4;
}
.group-stats {
display: flex;
align-items: center;
font-size: 24rpx;
color: #999;
.separator {
margin: 0 8rpx;
}
}
}
}
.group-actions {
display: flex;
flex-direction: column;
gap: 20rpx;
.qr-code-section {
display: flex;
flex-direction: column;
align-items: center;
padding: 30rpx;
background-color: #f8f9fa;
border-radius: 20rpx;
.qr-code-title {
font-size: 30rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
}
.qr-code-image {
width: 300rpx;
height: 300rpx;
border-radius: 10rpx;
margin-bottom: 20rpx;
}
.qr-code-tip {
font-size: 24rpx;
color: #666;
text-align: center;
}
}
.watch-ad-btn, .manage-btn {
display: flex;
align-items: center;
justify-content: center;
padding: 20rpx 30rpx;
border-radius: 30rpx;
font-size: 28rpx;
text {
margin-left: 10rpx;
}
}
.watch-ad-btn {
background-color: #ff6b35;
color: #fff;
}
.manage-btn {
background-color: #5baaff;
color: #fff;
}
}
}
.announcement {
background-color: #fff;
padding: 30rpx;
margin-bottom: 20rpx;
.announcement-title {
display: flex;
align-items: center;
margin-bottom: 20rpx;
font-size: 30rpx;
font-weight: bold;
color: #333;
text {
margin-left: 10rpx;
}
}
.announcement-content {
font-size: 28rpx;
color: #666;
line-height: 1.5;
}
}
.member-section {
background-color: #fff;
padding: 30rpx;
margin-bottom: 20rpx;
.section-header {
display: flex;
align-items: center;
margin-bottom: 30rpx;
.section-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.member-count-text {
font-size: 24rpx;
color: #999;
margin-left: 10rpx;
}
}
}
.member-list {
.member-item {
display: flex;
align-items: center;
padding: 20rpx 0;
border-bottom: 1rpx solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.member-avatar {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
margin-right: 20rpx;
}
.member-info {
flex: 1;
.member-name {
font-size: 28rpx;
color: #333;
margin-bottom: 5rpx;
}
.member-role {
font-size: 24rpx;
color: #999;
}
}
.member-status {
.online-status {
background-color: #52c41a;
color: #fff;
padding: 4rpx 12rpx;
border-radius: 10rpx;
font-size: 20rpx;
}
}
}
}
}
</style>

+ 456
- 0
pages_order/group/groupManage.vue View File

@ -0,0 +1,456 @@
<template>
<view class="page">
<navbar title="群组管理" bgColor="#5baaff" color="#fff" leftClick @leftClick="$utils.navigateBack" />
<!-- 群组信息 -->
<view class="group-info">
<image :src="groupInfo.avatar || '/static/image/logo.jpg'" class="group-avatar" mode="aspectFill"></image>
<view class="group-details">
<view class="group-name">{{ groupInfo.name || '群组名称' }}</view>
<view class="member-count">{{ groupInfo.memberCount || 0 }}</view>
</view>
</view>
<!-- 管理选项 -->
<view class="manage-options">
<view class="option-item" @click="editGroupInfo">
<view class="option-left">
<uv-icon name="edit-pen" size="40rpx" color="#5baaff"></uv-icon>
<text>编辑群组信息</text>
</view>
<uv-icon name="arrow-right" size="30rpx" color="#ccc"></uv-icon>
</view>
<view class="option-item" @click="uploadQrCode">
<view class="option-left">
<uv-icon name="scan" size="40rpx" color="#5baaff"></uv-icon>
<text>上传群聊二维码</text>
</view>
<uv-icon name="arrow-right" size="30rpx" color="#ccc"></uv-icon>
</view>
<view class="option-item" @click="manageMembers">
<view class="option-left">
<uv-icon name="account" size="40rpx" color="#5baaff"></uv-icon>
<text>成员管理</text>
</view>
<uv-icon name="arrow-right" size="30rpx" color="#ccc"></uv-icon>
</view>
<view class="option-item" @click="editAnnouncement">
<view class="option-left">
<uv-icon name="volume" size="40rpx" color="#5baaff"></uv-icon>
<text>群组公告</text>
</view>
<uv-icon name="arrow-right" size="30rpx" color="#ccc"></uv-icon>
</view>
<view class="option-item" @click="groupSettings">
<view class="option-left">
<uv-icon name="setting" size="40rpx" color="#5baaff"></uv-icon>
<text>群组设置</text>
</view>
<uv-icon name="arrow-right" size="30rpx" color="#ccc"></uv-icon>
</view>
</view>
<!-- 危险操作 -->
<view class="danger-zone">
<view class="danger-title">危险操作</view>
<view class="danger-item" @click="transferGroup">
<view class="danger-left">
<uv-icon name="account" size="40rpx" color="#ff4757"></uv-icon>
<text>转让群主</text>
</view>
<uv-icon name="arrow-right" size="30rpx" color="#ccc"></uv-icon>
</view>
<view class="danger-item" @click="dissolveGroup">
<view class="danger-left">
<uv-icon name="close" size="40rpx" color="#ff4757"></uv-icon>
<text>解散群组</text>
</view>
<uv-icon name="arrow-right" size="30rpx" color="#ccc"></uv-icon>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
groupId: '',
groupInfo: {
id: 1,
name: '江华同城交流群',
avatar: '/static/image/logo.jpg',
memberCount: 1280
}
}
},
onLoad(options) {
if (options.id) {
this.groupId = options.id
this.getGroupInfo()
}
},
methods: {
//
getGroupInfo() {
//
const mockGroupInfo = {
1: {
id: 1,
name: '江华同城交流群',
avatar: '/static/image/logo.jpg',
memberCount: 1280
},
2: {
id: 2,
name: '江华美食分享群',
avatar: '/static/image/logo.jpg',
memberCount: 856
},
3: {
id: 3,
name: '江华租房信息群',
avatar: '/static/image/logo.jpg',
memberCount: 2341
},
4: {
id: 4,
name: '江华求职招聘群',
avatar: '/static/image/logo.jpg',
memberCount: 1567
},
5: {
id: 5,
name: '江华旅游攻略群',
avatar: '/static/image/logo.jpg',
memberCount: 623
},
6: {
id: 6,
name: '江华学习交流群',
avatar: '/static/image/logo.jpg',
memberCount: 445
},
7: {
id: 7,
name: '江华二手交易群',
avatar: '/static/image/logo.jpg',
memberCount: 1890
},
8: {
id: 8,
name: '江华宠物交流群',
avatar: '/static/image/logo.jpg',
memberCount: 567
},
9: {
id: 9,
name: '江华汽车交流群',
avatar: '/static/image/logo.jpg',
memberCount: 892
},
10: {
id: 10,
name: '江华宝妈交流群',
avatar: '/static/image/logo.jpg',
memberCount: 1234
},
11: {
id: 11,
name: '江华IT技术交流群',
avatar: '/static/image/logo.jpg',
memberCount: 345
},
12: {
id: 12,
name: '江华健身运动群',
avatar: '/static/image/logo.jpg',
memberCount: 678
},
13: {
id: 13,
name: '江华摄影爱好者群',
avatar: '/static/image/logo.jpg',
memberCount: 456
},
14: {
id: 14,
name: '江华创业交流群',
avatar: '/static/image/logo.jpg',
memberCount: 789
},
15: {
id: 15,
name: '江华医疗健康群',
avatar: '/static/image/logo.jpg',
memberCount: 1123
},
16: {
id: 16,
name: '江华读书会',
avatar: '/static/image/logo.jpg',
memberCount: 234
},
17: {
id: 17,
name: '江华音乐爱好者群',
avatar: '/static/image/logo.jpg',
memberCount: 567
},
18: {
id: 18,
name: '江华电商交流群',
avatar: '/static/image/logo.jpg',
memberCount: 890
},
19: {
id: 19,
name: '江华园艺爱好者群',
avatar: '/static/image/logo.jpg',
memberCount: 345
},
20: {
id: 20,
name: '江华法律咨询群',
avatar: '/static/image/logo.jpg',
memberCount: 678
}
}
setTimeout(() => {
this.groupInfo = mockGroupInfo[this.groupId] || this.groupInfo
}, 300)
},
//
editGroupInfo() {
this.$utils.navigateTo('/pages_order/group/editGroup?id=' + this.groupId)
},
//
uploadQrCode() {
uni.chooseImage({
count: 1,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
const tempFilePath = res.tempFilePaths[0]
//
uni.previewImage({
urls: [tempFilePath],
success: () => {
//
uni.showModal({
title: '确认上传',
content: '确定要上传这张二维码图片吗?',
success: (modalRes) => {
if (modalRes.confirm) {
this.uploadQrCodeImage(tempFilePath)
}
}
})
}
})
},
fail: (err) => {
uni.showToast({
title: '选择图片失败',
icon: 'none'
})
}
})
},
//
uploadQrCodeImage(filePath) {
uni.showLoading({
title: '上传中...'
})
//
setTimeout(() => {
uni.hideLoading()
uni.showToast({
title: '上传成功',
icon: 'success'
})
// API
// uni.uploadFile({
// url: 'your-upload-api',
// filePath: filePath,
// name: 'qrcode',
// formData: {
// groupId: this.groupId
// },
// success: (uploadRes) => {
// //
// }
// })
}, 2000)
},
//
manageMembers() {
this.$utils.navigateTo('/pages_order/group/memberManage?id=' + this.groupId)
},
//
editAnnouncement() {
uni.showModal({
title: '编辑群组公告',
content: '请输入新的群组公告',
editable: true,
placeholderText: '请输入公告内容',
success: (res) => {
if (res.confirm && res.content) {
this.updateAnnouncement(res.content)
}
}
})
},
//
updateAnnouncement(content) {
//
setTimeout(() => {
uni.showToast({
title: '公告更新成功',
icon: 'success'
})
}, 500)
},
//
groupSettings() {
this.$utils.navigateTo('/pages_order/group/groupSettings?id=' + this.groupId)
},
//
transferGroup() {
uni.showModal({
title: '转让群主',
content: '确定要转让群主身份吗?转让后将失去群主权限。',
success: (res) => {
if (res.confirm) {
//
this.$utils.navigateTo('/pages_order/group/transferGroup?id=' + this.groupId)
}
}
})
},
//
dissolveGroup() {
uni.showModal({
title: '解散群组',
content: '确定要解散该群组吗?解散后所有成员将被移出群组,且无法恢复。',
success: (res) => {
if (res.confirm) {
//
setTimeout(() => {
uni.showToast({
title: '群组已解散',
icon: 'success'
})
setTimeout(() => {
this.$utils.navigateBack()
}, 1500)
}, 500)
}
}
})
}
}
}
</script>
<style scoped lang="scss">
.page {
background-color: #f5f5f5;
min-height: 100vh;
.group-info {
background-color: #fff;
padding: 30rpx;
margin-bottom: 20rpx;
display: flex;
align-items: center;
.group-avatar {
width: 100rpx;
height: 100rpx;
border-radius: 20rpx;
margin-right: 20rpx;
}
.group-details {
flex: 1;
.group-name {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 8rpx;
}
.member-count {
font-size: 24rpx;
color: #999;
}
}
}
.manage-options, .danger-zone {
background-color: #fff;
margin-bottom: 20rpx;
.option-item, .danger-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.option-left, .danger-left {
display: flex;
align-items: center;
text {
margin-left: 20rpx;
font-size: 28rpx;
color: #333;
}
}
}
}
.danger-zone {
.danger-title {
padding: 20rpx 30rpx;
font-size: 28rpx;
color: #ff4757;
font-weight: bold;
border-bottom: 1rpx solid #f0f0f0;
}
.danger-item {
.danger-left {
text {
color: #ff4757;
}
}
}
}
}
</style>

+ 25
- 5
pages_order/post/addPost.vue View File

@ -33,7 +33,10 @@
name="fileList"
@delete="deleteImage"
@afterRead="afterRead"
:previewFullImage="true"></uv-upload>
:previewFullImage="true"
:accept="'image,video'"
:sizeType="['original', 'compressed']"
:sourceType="['album', 'camera']"></uv-upload>
</view>
<view class="category">
@ -237,9 +240,14 @@
if(res.result.image){
res.result.image.split(',')
.forEach(url => {
self.fileList.push({
url
})
if(url.trim()){
//
const isVideo = this.isVideoFile(url.trim())
self.fileList.push({
url: url.trim(),
type: isVideo ? 'video' : 'image'
})
}
})
}
}
@ -251,14 +259,25 @@
afterRead(e){
let self = this
e.file.forEach(file => {
//
const isVideo = this.isVideoFile(file.url || file.path)
self.$Oss.ossUpload(file.url).then(url => {
self[e.name].push({
url
url,
type: isVideo ? 'video' : 'image'
})
})
})
},
//
isVideoFile(filePath) {
const videoExtensions = ['.mp4', '.avi', '.mov', '.wmv', '.flv', '.webm', '.m4v', '.3gp']
const fileName = filePath.toLowerCase()
return videoExtensions.some(ext => fileName.includes(ext))
},
//
submit(){
@ -284,6 +303,7 @@
delete this.form.shop
// image
this.form.image = this.fileList.map((item) => item.url).join(",")
this.form.wxImage = this.codeFileList.map((item) => item.url).join(",")


+ 1
- 1
pages_order/post/postDetail.vue View File

@ -26,7 +26,7 @@
</view>
<view class="content" v-if="tagIndex == 1">
哈哈哈哈哈
</view>
<commentList v-if="tagIndex == 0" @getData="getData" :list="list" :params="params" />


Loading…
Cancel
Save