# 七、搜索用户
# 1. 搜索用户接口文档
搜索用户接口文档具体查看:四、搜索用户
# 2. 搜索用户界面开发
# ① 新建搜索用户页面 /pages/search/search.vue
在 pages.json配置页面
{
"path" : "pages/search/search",
"style" :
{
"navigationBarTitleText" : "搜索",
"navigationStyle": "custom"
}
}
# ② 在页面 /pages/wode/wode.nvue 跳转
<template>
<view>
<!-- 头像昵称 -->
...
<!-- 间隔槽 -->
...
<!-- cell 单元格 -->
<u-cell-group>
<u-cell v-for="(item,index) in uCellMenus" :key="index"
:icon="item.icon" :title="item.title"
:border="false" clickable isLink
:customStyle="customStyles"
:iconStyle="iconStyles"
:titleStyle="titleStyles"
@click="openCell(item,index)"></u-cell>
</u-cell-group>
<!-- 间隔槽 -->
...
</view>
</template>
<script>
...
export default {
data() {
return {
...
uCellMenus:[
{icon:'plus-people-fill',title:'添加朋友',url:'/pages/search/search'},
{icon:'chat',title:'发起群聊',url:''},
{icon:'scan',title:'扫一扫',url:''},
],
}
},
...
methods: {
// 点击栏目
openCell(item,index){
if(!item.url) return;
if(!this.user || this.user.role == 'visitor'){
return uni.navigateTo({
url: '/pages/loginCenter/loginCenter',
});
}
uni.navigateTo({
url: item.url,
});
},
...
}
}
</script>
# ③ 在页面 /pages/search/search.vue
<template>
<view class="page">
<!-- 导航栏 -->
<!-- #ifdef MP -->
<chat-navbar :fixed="true" :showPlus="false" :showUser="false" :showBack="true"
navbarClass="navbar-bgColor" :h5WeiXinNeedNavbar="false">
</chat-navbar>
<!-- #endif -->
<!-- 搜索框 -->
<view class="flex align-center p-3">
<u--input placeholder="搜 索" prefixIcon="search"
prefixIconStyle="font-size:22px;color:#909399;"
confirmType="search" v-model="keyword" clearable
:maxlength="50" showWordLimit focus
:adjustPosition="false"
:customStyle="inputStyle"
@input="searchData"></u--input>
<text class="font text-primary ml-2" @click="navigateBack">取消</text>
</view>
<!-- 搜索结果 -->
<view v-if="keyword">
<view v-if="searchResStatus">
<!-- 搜索用户的结果 -->
<view v-if="searchRes.length"
class="flex flex-column justify-center">
<u-list @scrolltolower="scrolltolower">
<u-list-item v-for="(item, index) in searchRes" :key="index">
<u-cell :title="item.nickname || item.username">
<u-avatar
slot="icon"
shape="square"
size="35"
:src="item.avatar"
customStyle="margin: -3px 5px -3px 0"
></u-avatar>
</u-cell>
</u-list-item>
</u-list>
</view>
<view v-else>
<u-empty mode="search"></u-empty>
</view>
</view>
<view v-else class="flex align-center justify-center mt-3">
<text class="font text-muted">正在搜索中...</text>
</view>
</view>
</view>
</template>
<script>
import toolJs from '@/common/mixins/tool.js';
import {requestUrl} from '@/common/mixins/configData.js';
import {mapState,mapGetters,mapMutations,mapActions} from 'vuex';
export default {
mixins:[toolJs],
data() {
return {
inputStyle:{
"background-color":"#ffffff",
},
chatuser_token: '',
keyword:'',
searchResStatus:false, // 搜索结果状态
searchRes:[],
}
},
computed:{
...mapState({
user : state => state.Chatuser.regloginUser,
}),
},
onLoad() {
this.chatuser_token = uni.getStorageSync('chatuser_token');
if (!this.chatuser_token || this.user.role == 'visitor') {
return uni.switchTab({
url: '/pages/wode/wode'
});
}
},
methods: {
searchData(e){
// console.log('搜索内容',e);
// console.log('搜索内容',this.keyword);
this.searchResStatus = false;
if(this.keyword.length >= 1) this.searchAjax();
},
searchAjax(){
uni.$u.http.post(requestUrl.http + `/api/chat/searchUser`, {
keyword:this.keyword,
}, {
header: {
token: this.chatuser_token,
},
}).then(res => {
console.log('服务器返回的结果', res);
this.searchResStatus = true;
this.searchRes = [];
if(res.data.data.length){
this.searchRes = res.data.data;
}
}).catch(err => {
console.log('出错', err);
if (err.data && err.data.data) {
uni.showToast({
title: err.data.data,
icon: 'none',
duration: 5000
});
}
});
},
//加载跟多
scrolltolower(){
console.log('滚动条滚动到底部了加载更多搜索内容');
},
}
}
</script>
<style>
/* 导航栏背景色 */
.navbar-bgColor {
background-color: #ededed;
}
</style>
# 八、查看用户信息
# 1. 查看用户资料接口文档
搜索用户接口文档具体查看:十六、查看用户资料
# 2. 查看用户资料界面开发
# ① 新建用户信息页面 /pages/userinfo/userinfo.vue
在 pages.json配置页面
{
"path" : "pages/userinfo/userinfo",
"style" :
{
"navigationBarTitleText" : "用户信息展示",
"navigationStyle": "custom"
}
}
# ② 在页面 /pages/search/search.vue 跳转
<template>
<view class="page">
...
<!-- 搜索结果 -->
<view v-if="keyword">
<view v-if="searchResStatus">
<!-- 搜索用户的结果 -->
<view ...>
<u-list ...>
<u-list-item ...>
<u-cell :title="item.nickname || item.username"
@click="openUserInfo(item, index)">
...
</u-cell>
</u-list-item>
</u-list>
</view>
<view v-else>
<u-empty mode="search"></u-empty>
</view>
</view>
<view ...>
...
</view>
</view>
</view>
</template>
<script>
...
export default {
...
methods: {
openUserInfo(item, index){
console.log('查看用户信息',item);
uni.navigateTo({
url: '/pages/userinfo/userinfo?uuid=' + encodeURIComponent(item.uuid),
});
},
...
}
}
</script>
# ③ 在页面 /pages/userinfo/userinfo.vue
<template>
<view class="page">
<!-- 导航栏 -->
<chat-navbar :fixed="true" :showPlus="false" :showUser="false" :showBack="true"
navbarClass="navbar-bgColor" :h5WeiXinNeedNavbar="false">
</chat-navbar>
<!-- 介绍 -->
<view class="p-3 flex align-center justify-between">
<!-- 头像 -->
<view class="ml-1 mr-3">
<u--image mode="widthFix"
:src="avatarShow"
width="120rpx" height="120rpx" radius="20rpx"></u--image>
</view>
<!-- 昵称 -->
<view class="flex flex-1 flex-column justify-center">
<text class="font-lg font-weight-bold">{{nicknameShow}}</text>
<view class="flex justify-between mt-2">
<text class="font text-light-muted">{{nicknameNextShow}}</text>
<!-- <text class="font-lg text-light-muted iconfont"></text> -->
</view>
</view>
</view>
<!-- 处理 -->
<view class="p-3 flex align-center">
<u-button :text="btn.text" :type="btn.type" @click="sendMessageFun"
:loading="btn.loading" :loadingText="btn.loadingText"
:loadingMode="btn.loadingMode" :disabled="btn.disabled"></u-button>
</view>
</view>
</template>
<script>
import toolJs from '@/common/mixins/tool.js';
import {requestUrl} from '@/common/mixins/configData.js';
import {mapState,mapGetters,mapMutations,mapActions} from 'vuex';
export default {
mixins:[toolJs],
data() {
return {
uuid:'',
user:null,
// 按钮
btn:{
text:'发消息',
type:'primary',
loading:false,
loadingText:'正在处理中',
loadingMode:'circle', // spinner 雪花
clicktype:'init', // 初始化
disabled:false,
},
}
},
onLoad(e) {
if(!e.uuid) return this.navigateBack();
this.uuid = e.uuid;
this.getUserInfo();
},
computed:{
...mapState({
me : state => state.Chatuser.regloginUser,
}),
//头像
avatarShow(){
let avatar = this.user && this.user.avatar;
avatar = avatar && avatar.startsWith('http') ? avatar : `${requestUrl.http}${avatar}`;
console.log(avatar);
return avatar;
},
// 账号
nicknameShow(){
return this.user && (this.user.nickname || this.user.username);
},
// 账号下面的小字
nicknameNextShow(){
return this.user && (this.user.userinfo.location || ``);
}
},
methods: {
// 获取用户信息
getUserInfo(){
uni.$u.http.get(requestUrl.http + `/api/userinfo/${this.uuid}`).then(res => {
console.log('服务器返回的结果', res);
this.user = res.data.data;
}).catch(err => {
console.log('出错', err);
if (err.data && err.data.data) {
uni.showToast({
title: err.data.data,
icon: 'none',
duration: 5000
});
}
});
},
// 发消息
sendMessageFun(){
console.log('发消息逻辑处理',this.me);
const role = this.me.role; // 'visitor'|'user'
const { sendCount, needFollow } = this.user.chatset[role];
// 1. 检查是否需要关注
// if (needFollow && !this.me.hasFollowed) {
// console.log('请先关注对方才能发消息');
// return; // 终止后续逻辑
// }
// 2. 根据发送权限处理
switch (sendCount) {
case 0:
if(role == 'visitor'){
console.log('游客无法发送消息,请登录');
this.btn.clicktype = 'needLogin';
}else if(role == 'user'){
console.log('请先添加对方为好友才能发消息');
this.btn.clicktype = 'applyFriend';
}
break;
case 1:
console.log('可以发一条消息');
this.btn.clicktype = 'sendOne';
break;
default: // sendCount >= 2
console.log('发消息不受限制');
// 实际发送操作...
}
// 3. 根据点击类型处理不同逻辑
if(this.btn.clicktype == 'applyFriend'){
this.btn.text = '对方设置为:需加为好友才能发消息';
uni.showModal({
content: '申请加对方为好友?',
success:(res)=> {
if (res.confirm) {
console.log('处理申请的逻辑');
this.btn.loading = true;
this.btn.loadingText = '申请提交中';
//和服务器提交申请数据
setTimeout(()=>{
this.btn.loading = false;
this.btn.text = '申请提交成功,等待对方同意';
this.btn.disabled = true;
this.btn.type = 'success';
},3000);
}
}
});
}else if(this.btn.clicktype == 'needLogin'){
this.btn.text = '请先登录才能发消息';
uni.showModal({
content: '是否去登录?',
success: function (res) {
if (res.confirm) {
console.log('去登录页');
}
}
});
}else if(this.btn.clicktype == 'sendOne'){
this.btn.text = '发消息';
console.log('跳转到聊天页');
}
},
}
}
</script>
<style>
/* 导航栏背景色 */
.navbar-bgColor {
background-color: #ededed;
}
</style>
# 九、申请添加好友
# 1. 申请添加好友接口文档
搜索用户接口文档具体查看:五、申请添加好友
# 2. 申请添加好友代码
在页面 /pages/userinfo/userinfo.vue
<template>
<view class="page">
...
<!-- 处理 -->
<view v-if="btn.clicktype == 'applyFriend'" class="p-3 flex align-center">
<u-textarea :placeholder="textarea.placeholder" v-model="textarea.nickname"
count :adjustPosition="false"
height="70"></u-textarea>
</view>
<view class="p-3 flex align-center">
<u-button :text="btn.text" :type="btn.type"
@click="sendMessageFun" :loading="btn.loading"
:loadingText="btn.loadingText" :loadingMode="btn.loadingMode"
:disabled="btn.disabled"></u-button>
</view>
</view>
</template>
<script>
...
export default {
...
data() {
return {
...,
//表单
textarea: {
nickname: '',
placeholder: '',
},
}
},
...,
methods: {
...,
//发消息
sendMessageFun() {
console.log('发消息的逻辑', this.me);
const role = this.me.role; //'visitor' | 'user'
const {sendCount,needFollow} = this.user.chatset[role];
// 1. 检查是否需要关注
// if(needFollow && this.me.hasFollowed){
// console.log('请先关注对方才能发消息');
// return; // 程序结束
// }
// 2. 根据对方设置的聊天权限处理
switch (sendCount) {
case 0:
if (role == 'visitor') {
console.log('游客无法发消息,要先登录');
this.btn.clicktype = 'needLogin';
this.btn.text = '请先登录才能发消息';
uni.showModal({
content: '是否去登录?',
success: res => {
if (res.confirm) {
console.log('去登录页');
}
},
});
} else if (role == 'user') {
console.log('请先添加对方为好友才能发消息',this.btn.clicktype);
if (this.btn.clicktype == 'init') {
this.btn.text = '加为好友';
this.textarea.placeholder = `对方设置为:需要加为好友才能发消息,请介绍一下自己或者说明申请加对方为好友原因,选填`;
this.btn.clicktype = 'applyFriend';
} else {
this.btn.loading = true;
this.btn.loadingText = '申请提交中';
uni.$u.http.post(requestUrl.http + `/api/chat/applyfriend`, {
friend_id: this.user.id,
nickname: this.textarea.nickname,
}, {
header: {
token: uni.getStorageSync('chatuser_token'),
}
}).then(res => {
console.log('服务器返回的结果', res);
this.btn.loading = false;
if(res.data.data == 'ok'){
this.btn.text = '申请提交成功,等待对方同意,请勿重复申请';
this.btn.clicktype = 'init';
this.btn.disabled = true;
this.btn.type = 'success';
}
}).catch(err => {
console.log('出错', err);
this.btn.loading = false;
if (err.data && err.data.data) {
uni.showToast({
title: err.data.data,
icon: 'none',
duration: 5000
});
this.btn.text = err.data.data;
this.btn.clicktype = 'init';
this.btn.disabled = true;
this.btn.type = 'error';
}
});
}
}
break;
case 1:
console.log('可以发一条消息');
this.btn.clicktype = 'sendOne';
this.btn.text = '发消息';
console.log('去聊天页');
// uni.navigateTo({
// url: '/pages/chat/chat',
// });
break;
default:
console.log('发消息不受限制');
break;
}
}
}
}
</script>
# 十、查看好友申请列表(获取别人申请我为好友的列表数据)
# 1. 查看好友申请列表接口文档
搜索用户接口文档具体查看:六、查看好友申请列表(获取别人申请我为好友的列表数据)
# 2. 好友申请信息可以在初始化的时候获取
在 /store/modules/chatuser.js 初始化或者登录之后获取,然后显示在页面上做提醒
import {requestUrl} from '@/common/mixins/configData.js';
export default{
// 对应的mapState,在computed中引用导入
// 类似于data,把全局或者公共部分放在这里
state:{
regloginUser:null, //注册登录后续的操作res结果存储
// 定义加我为好友的信息
goodfriendapply:{
alldata:[], // 所有申请加我为好友的用户信息
pendingCount:0, // 多少未处理
},
},
// 异步的方法,在methods引入
actions:{
//注册登录后续的操作
regloginAction({commit,state,dispatch}, regloginRes){
...
//获取好友申请信息
dispatch('getGoodfriendapply');
},
// 用户退出登录
...
// 初始化登录注册状态(避免刷新页面state没有数据)
initChatuserAction({commit,state,dispatch}){
console.log('初始化登录注册状态',state);
// 给state赋值
let user = uni.getStorageSync('chatuser') ?
JSON.parse(uni.getStorageSync('chatuser')) : '';
if(user){
state.regloginUser = user;
console.log('初始化登录注册状态',state);
//获取好友申请信息
dispatch('getGoodfriendapply');
}
},
//获取好友申请信息
getGoodfriendapply({commit,state}, page = 1, limit = 10){
console.log('看一下我的情况',state.regloginUser);
if(state.regloginUser && state.regloginUser.role == 'user'){
uni.$u.http.get(requestUrl.http + `/api/chat/listapplyfriend/${page}?limit=${limit}`, {
header: {
token: uni.getStorageSync('chatuser_token'),
}
}).then(res => {
console.log('服务器返回的好友申请结果', res);
state.goodfriendapply = res.data.data;
});
}
},
},
}
# 3. 显示在页面上做提示
如:可以显示在 /pages/friendsList/friendsList.nvue 朋友列表页面
<template>
<view>
<!-- 导航栏 -->
...
<!-- 朋友列表 -->
<u-index-list :indexList="indexList">
<view slot="header" class="list">
<view v-for="(item,index) in topMenus" :key="index">
<view class="flex align-center justify-between pr-3"
@click="opentopMenus(item,index)">
<view class="list__item">
<u-avatar shape="square" size="35"
:icon="item.icon" fontSize="26" randomBgColor></u-avatar>
<text class="list__item__user-name">{{item.title}}</text>
</view>
<!-- 提示待处理数量 -->
<view class="flex align-center">
<u-badge :max="99" :value="goodfriendapply.pendingCount"></u-badge>
</view>
</view>
<u-line></u-line>
</view>
</view>
<!-- 列表 -->
...
</u-index-list>
</view>
</template>
<script>
import toolJs from '@/common/mixins/tool.js';
import {mapState,mapGetters,mapMutations,mapActions} from 'vuex';
...
export default {
mixins: [toolJs],
data() {
return {
...
topMenus:[
{icon:'man-add-fill',title:'新的朋友',url:''},
// {icon:'tags-fill',title:'标签',url:''},
// {icon:'chrome-circle-fill',title:'朋友圈',url:''},
// {icon:'qq-fill',title:'QQ',url:''},
],
}
},
computed: {
...mapState({
me: state => state.Chatuser.regloginUser,
goodfriendapply: state => state.Chatuser.goodfriendapply,
}),
...
},
onLoad() {
console.log('我的信息',this.me);
console.log('好友申请',this.goodfriendapply);
if(!this.me || this.me.role == 'visitor') return this.navigateBack();
},
methods: {
opentopMenus(item,index){
console.log('点击某一项',item);
}
}
}
</script>
...
# 4. 新建查看好友申请列表页面
新建页面: /pages/applyMyfriend/applyMyfriend ,在 pages.json 中
{
"path" : "pages/applyMyfriend/applyMyfriend",
"style" :
{
"navigationBarTitleText" : "新的朋友",
"navigationStyle": "custom",
"enablePullDownRefresh": true
}
}
# 5. 从 /pages/friendsList/friendsList 朋友列表页面进入
methods: {
opentopMenus(item,index){
console.log('点击某一项',item);
uni.navigateTo({
url: item.url,
});
},
}
# 6. 查看好友申请列表页面代码
在页面 /pages/applyMyfriend/applyMyfriend代码
<template>
<view>
<!-- 导航栏 -->
<chat-navbar title="新的朋友" :fixed="true" :showPlus="false"
:showUser="false" :showBack="true"
navbarClass="bg-light" :h5WeiXinNeedNavbar="false">
</chat-navbar>
<!-- 新的朋友 -->
<view class="p-3" v-if="goodfriendapply.alldata.length > 0">
<u-cell-group title="申请加我为好友的用户" :border="false">
<u-cell v-for="(item,index) in goodfriendapply.alldata"
:key="index" :customStyle="{'height':'300px'}">
<view slot="title" class="flex align-center">
<u-avatar :src="item|avatarShow" shape="square"></u-avatar>
<!-- <u--image mode="widthFix"
:src="item|avatarShow"
width="40px" height="40px" radius="10rpx"></u--image> -->
<view class="flex flex-column justify-center ml-2 mr-1">
<text class="font-md">{{item.user.nickname || item.user.username}}</text>
<text class="font-sm text-light-muted">{{item.nickname}}</text>
</view>
</view>
<view slot="value">
<view v-if="item.status == 'pending'">
<u-button text="同意" type="success" size="small"></u-button>
</view>
<view v-else>
<text class="font-sm text-light-muted">{{item | statusText}}</text>
</view>
</view>
</u-cell>
</u-cell-group>
</view>
<view v-else>
<u-empty></u-empty>
</view>
</view>
</template>
<script>
import toolJs from '@/common/mixins/tool.js';
import {mapState,mapGetters,mapMutations,mapActions} from 'vuex';
import {requestUrl} from '@/common/mixins/configData.js';
export default {
mixins:[toolJs],
data() {
return {
}
},
onLoad() {
console.log('我的信息',this.me);
console.log('好友申请信息',this.goodfriendapply);
if(!this.me || this.me.role == 'visitor') return this.navigateBack();
},
computed: {
...mapState({
me : state => state.Chatuser.regloginUser,
goodfriendapply : state => state.Chatuser.goodfriendapply,
}),
},
filters:{
//头像
avatarShow(item){
let avatar = item && item.user.avatar;
avatar = avatar && avatar.startsWith('http') ? avatar :
`${requestUrl.http}${avatar}`;
return avatar;
},
// 状态显示
statusText(item){
let status = item && item.status;
let text = {
agree:'已同意',
refuse:'已拒绝',
ignore:'已忽略'
}
return text[status];
}
},
methods: {
}
}
</script>
<style>
</style>
# 7. 上拉刷新数据及下拉加载更多数据
- 在页面
/pages/applyMyfriend/applyMyfriend代码
<template>
<view>
<!-- 导航栏 -->
...
<!-- 新的朋友 -->
<view ...>
<u-cell-group ...>
<u-cell v-for="(item,index) in goodfriendapply.alldata"
:key="index" :customStyle="{'height':'500px'}">
<view slot="title" class="flex align-center">
...
</view>
<view slot="value">
...
</view>
</u-cell>
</u-cell-group>
<!-- 上拉加载更多 --><!-- 正在加载中 --><!-- 没有更多数据了 -->
<view>
<!-- 上拉加载更多 -->
<view v-if="!loadingIcon.show && moreData"
class="flex align-center justify-center py-3">
<text class="font-sm text-light-muted">上拉加载更多数据</text>
</view>
<!-- 正在加载中 -->
<u-loading-icon :text="loadingIcon.text" :textSize="loadingIcon.textSize"
:mode="loadingIcon.mode" :show="loadingIcon.show"></u-loading-icon>
<!-- 没有更多数据了 -->
<view v-if="!loadingIcon.show && !moreData"
class="w-100">
<u-divider text="我是有底线的" textColor="#909399" lineColor="#dcdfe6"></u-divider>
</view>
</view>
</view>
<view v-else>
<u-empty></u-empty>
</view>
</view>
</template>
<script>
...
export default {
...
data() {
return {
page:1,
loadingIcon:{
text:'数据加载中',
textSize:12,
mode:'circle',
show:false,
},
moreData:true, // 是否可加载更多数据
}
},
// 监听用户下拉
onPullDownRefresh() {
this.page = 1;
this.$store.dispatch('getGoodfriendapply',{page:this.page,limit:10})
.then(res=>{
uni.stopPullDownRefresh();
uni.showToast({
title: '刷新数据成功',
icon: 'none'
});
});
},
// 监听触底
onReachBottom() {
console.log('拉到底部了');
this.loadMore();
},
methods: {
//触底加载更多
loadMore(){
if(this.moreData){
this.loadingIcon.show = true;
const limit = 2;
this.page ++;
this.$store.dispatch('getGoodfriendapply',{page:this.page,limit:limit})
.then(res=>{
console.log('页面显示是否有下一页',this.goodfriendapply.moredata);
this.moreData = this.goodfriendapply.moredata;
console.log('看一下此时的加载状态', this.goodfriendapply.loadingStatus);
this.loadingIcon.show = this.goodfriendapply.loadingStatus;
if(!this.moreData){
this.loadingIcon.show = false;
}
});
}
},
}
}
</script>
- 在vuex中
/store/modules/chatuser.js
import {requestUrl} from '@/common/mixins/configData.js';
export default {
// 对应的mapState,在computed中引用导入
// 类似于data,把全局或者公共部分放在这里
state: {
regloginUser: null, //注册登录后续的操作res结果存储
//定义加我为好友的信息
goodfriendapply:{
alldata:[], // 所有申请我为好友的用户信息
pendingCount:0, // 多少还未处理
},
},
// 异步的方法,在methods引入
actions: {
//注册登录后续的操作
...,
// 用户退出登录
...,
// 初始化登录注册状态(避免刷新页面state没有数据)
...,
//获取好友申请的信息
getGoodfriendapply({commit,state}, payload = {}) {
if (state.regloginUser && state.regloginUser.role == 'user') {
let page = payload.page ? payload.page : 1;
let limit = payload.limit ? payload.limit : 2;
// 正在请求中
state.goodfriendapply.loadingStatus = true;
// 请求服务器
uni.$u.http.get(requestUrl.http + `/api/chat/listapplyfriend/${page}?limit=${limit}`, {
header: {
token: uni.getStorageSync('chatuser_token'),
}
}).then(res => {
console.log('服务器返回的好友申请结果', res);
if(page == 1){
// 得到数据赋值
state.goodfriendapply = res.data.data;
// 有没有下一页数据
state.goodfriendapply.moredata = true;
// 请求结束
state.goodfriendapply.loadingStatus = false;
}else{
// 加载更多数据:往当前数据里面添加数据
state.goodfriendapply.alldata = [...state.goodfriendapply.alldata,
...res.data.data.alldata];
console.log('加载更多数据得到的条数',state.goodfriendapply.alldata.length);
console.log('如果有下一页应该有几条',page * limit);
// 还有没有下一页
const moredata = page * limit > state.goodfriendapply.alldata.length ?
false : true;
state.goodfriendapply.moredata = moredata;
// 请求结束
state.goodfriendapply.loadingStatus = false;
}
})
}
},
},
}
# 十一、处理好友申请
# 1. 查看处理好友申请接口文档
搜索用户接口文档具体查看:七、处理好友的申请(对申请加我为好友的信息进行处理)
# 2. 从好友申请列表页面进入处理
在 /pages/applyMyfriend/applyMyfriend 好友申请列表页面
<template>
<view>
<!-- 导航栏 -->
...
<!-- 新的朋友 -->
<view class="p-3" v-if="goodfriendapply.alldata.length > 0">
<u-cell-group ...>
<u-cell v-for="(item,index) in goodfriendapply.alldata"
:key="index">
<view slot="title" class="flex align-center"
@click="openUserInfo(item,'info')">
...
</view>
<view slot="value">
<view v-if="item.status == 'pending'"
@click="openUserInfo(item,'doapply')">
...
</view>
<view v-else>
...
</view>
</view>
</u-cell>
</u-cell-group>
<!-- 上拉加载更多 --><!-- 正在加载中 --><!-- 没有更多数据了 -->
<view>
...
</view>
</view>
<view v-else>
<u-empty></u-empty>
</view>
</view>
</template>
<script>
...
export default {
...,
// 监听用户下拉
onPullDownRefresh() {
this.getNewData();
},
onShow() {
this.getNewData();
},
methods: {
//获取最新数据
getNewData(){
this.page = 1;
const limit = 20;
this.$store.dispatch('getGoodfriendapply',{ page:this.page, limit:limit }).then(res=>{
uni.stopPullDownRefresh();
// uni.showToast({title: '刷新数据成功',icon: 'none'});
});
},
//触底加载下一页
...,
// 打开用户详情
openUserInfo(item, action){
uni.navigateTo({
url: `/pages/userinfo/userinfo?uuid=${item.user.uuid}&action=${action}`,
});
},
}
}
</script>
# 3. 处理好友申请
在 /pages/userinfo/userinfo.vue 中处理
这里面涉及到一个接口:
- 查看用户是否申请加我为好友(登录用户有这个权限,游客无权限)
文档地址:十七、查看用户是否申请加我为好友(即我有没有权限处理这个申请)
<template>
<view class="page">
<!-- 导航栏 -->
...
<!-- 介绍 -->
...
<!-- 处理 -->
<view v-if="btn.clicktype == 'applyFriend'"
class="p-3 flex align-center">
<u-textarea :placeholder="textarea.placeholder" v-model="textarea.nickname"
count :adjustPosition="false" :height="70"></u-textarea>
</view>
<!-- 发消息 -->
<view v-if="action.show == 'sendMessage'"
class="p-3 flex align-center">
<u-button :text="btn.text" :type="btn.type" @click="sendMessageFun"
:loading="btn.loading" :loadingText="btn.loadingText"
:loadingMode="btn.loadingMode" :disabled="btn.disabled"></u-button>
</view>
<!-- doapply的情况 -->
<view v-if="action.show == 'doapply'"
class="p-3">
<!-- 用户留言 -->
<view v-if="action.tips1"
class="flex flex-column justify-center mb-3">
<text class="mb-1">用户留言备注</text>
<text class="font-sm text-light-muted"
style="text-align: justify;">{{action.tips1}}</text>
</view>
<!-- 备注用户名 -->
<view class="mb-3 bg-white">
<u-input placeholder="如果同意加为好友,可以备注一下用户名,选填"
v-model="action.tips1Value"></u-input>
</view>
<!-- 处理按钮 -->
<view class="flex align-center">
<view class="flex flex-1 mr-1">
<u-button text="同意加为好友" type="success" size="small"
@click="doApply('agree')"
:loading="btn.loading" :loadingText="btn.loadingText"
:loadingMode="btn.loadingMode" :disabled="btn.disabled"></u-button>
</view>
<view class="flex flex-1">
<u-button text="拒绝" type="warning" size="small"
@click="doApply('refuse')"
:loading="btn.loading" :loadingText="btn.loadingText"
:loadingMode="btn.loadingMode" :disabled="btn.disabled"></u-button>
</view>
<view class="flex flex-1 ml-1">
<u-button text="忽略" type="primary" size="small"
@click="doApply('ignore')"
:loading="btn.loading" :loadingText="btn.loadingText"
:loadingMode="btn.loadingMode" :disabled="btn.disabled"></u-button>
</view>
</view>
</view>
</view>
</template>
<script>
...
export default {
mixins:[toolJs],
data() {
return {
...,
// 页面处理多样化
action:{
show:'sendMessage',
//显示的内容1不限字段
tips1:'',
tips1Value:'',
},
}
},
onLoad(e) {
if(!e.uuid) return this.navigateBack();
this.uuid = e.uuid;
this.getUserInfo();
if(e.action && e.action == 'info') this.action.show = false;
if(e.action && e.action == 'doapply'){
// 处理好友申请必须是登录用户了
if(!this.me || this.me.role == 'visitor') return this.navigateBack();
this.action.show = 'doapply';
// 看一下当前用户是不是申请加我为好友
uni.$u.http.post(requestUrl.http + `/api/chat/isApplyfriend/${this.uuid}`,{},{
header:{
token: uni.getStorageSync('chatuser_token'),
}
}).then(res => {
console.log('服务器返回的结果', res);
const applyInfo = res.data.data;
this.action.tips1 = applyInfo.nickname;
this.action.applyInfo = applyInfo;
}).catch(err => {
return this.navigateBack();
});
}
},
methods: {
// 处理申请
doApply(type){
this.btn.loading = true;
this.btn.disabled = true;
uni.$u.http.post(requestUrl.http + `/api/chat/handleapply/${this.action.applyInfo.id}`,{
nickname:this.action.tips1Value,
status:type,
},{
header:{
token: uni.getStorageSync('chatuser_token'),
}
}).then(res => {
this.btn.loading = false;
console.log('服务器返回处理申请结果', res);
if(res.data.data == 'ok'){
uni.showToast({
title: '处理成功',
icon: 'none',
duration: 1000
});
setTimeout(()=>{
return this.navigateBack();
},1000);
}
}).catch(err => {
if (err.data && err.data.data) {
uni.showToast({
title: err.data.data,
icon: 'none',
duration: 5000
});
}
});
},
...
}
}
</script>
# 十二、好友列表(通讯录)展示
# 1. 获取好友列表数据
获取好友列表数据文档接口: 八、好友列表(联系人、通讯录)
在vuex中获取,在 /store/modules/chatuser.js中
import {requestUrl} from '@/common/mixins/configData.js';
export default {
// 对应的mapState,在computed中引用导入
// 类似于data,把全局或者公共部分放在这里
state: {
...
// 我的好友列表
goodfriendlist:[],
},
// 异步的方法,在methods引入
actions: {
...,
//获取好友列表
getGoodfriendlist({commit,state}, payload = {}){
if (state.regloginUser && state.regloginUser.role == 'user') {
let page = payload.page ? payload.page : 1;
let limit = payload.limit ? payload.limit : 200;
// 请求服务器
uni.$u.http.get(requestUrl.http + `/api/chat/goodfriendlist/${page}?limit=${limit}`, {
header: {
token: uni.getStorageSync('chatuser_token'),
}
}).then(res => {
console.log('服务器返回好友列表', res);
state.goodfriendlist = res.data.data;
});
}
}
},
}
# 2. 页面展示
在页面 /pages/friendsList/friendsList.nvue 中代码
<template>
<view>
<!-- 导航栏 -->
<chat-navbar title="好友列表" :fixed="true"
:showPlus="false" :showUser="false"
:showBack="true"></chat-navbar>
<!-- 朋友列表 -->
<u-index-list :indexList="indexList">
<view slot="header" class="list">
<view v-for="(item,index) in topMenus" :key="index"
@click="opentopMenus(item,index)">
<view class="flex align-center justify-between pr-3">
<view class="list__item">
<u-avatar shape="square" size="35"
:icon="item.icon" fontSize="26" randomBgColor></u-avatar>
<text class="list__item__user-name">{{item.title}}</text>
</view>
<!-- 提示待处理的数量 -->
<view class="flex align-center">
<u-badge :max="99" :value="goodfriendapply.pendingCount"></u-badge>
</view>
</view>
<u-line></u-line>
</view>
</view>
<!-- 列表 -->
<template v-for="(item, index) in itemArr">
<!-- #ifdef APP-NVUE -->
<u-index-anchor :text="indexList[index]" :key="index"></u-index-anchor>
<!-- #endif -->
<u-index-item :key="index">
<!-- #ifndef APP-NVUE -->
<u-index-anchor :text="indexList[index]"></u-index-anchor>
<!-- #endif -->
<view class="list" v-for="(item1, index1) in item" :key="index1">
<view class="list__item" @click="openUserInfo(item1,'info')">
<image class="list__item__avatar" :src="item1.url"></image>
<text class="list__item__user-name">{{item1.name}}</text>
</view>
<u-line></u-line>
</view>
</u-index-item>
</template>
<view slot="footer" class="u-safe-area-inset--bottom"
v-if="goodfriendlist && goodfriendlist.count">
<text class="list__footer">共{{goodfriendlist && goodfriendlist.count}}位好友</text>
</view>
</u-index-list>
</view>
</template>
<script>
import toolJs from '@/common/mixins/tool.js';
import {requestUrl} from '@/common/mixins/configData.js';
import {mapState,mapGetters,mapMutations,mapActions} from 'vuex';
/*
const indexList = () => {
const indexList = []
const charCodeOfA = 'A'.charCodeAt(0)
indexList.push("↑")
indexList.push("☆")
for (let i = 0; i < 26; i++) {
indexList.push(String.fromCharCode(charCodeOfA + i))
}
console.log('indexList的当前值',indexList);
indexList.push('#')
return indexList
}
*/
export default {
mixins:[toolJs],
data() {
return {
//indexList: indexList(),
urls: [
'https://docs-51yrc-com.oss-cn-hangzhou.aliyuncs.com/chat/avatar-01.png',
'https://docs-51yrc-com.oss-cn-hangzhou.aliyuncs.com/chat/avatar-02.png',
'https://docs-51yrc-com.oss-cn-hangzhou.aliyuncs.com/chat/avatar-03.png',
'https://docs-51yrc-com.oss-cn-hangzhou.aliyuncs.com/chat/avatar-04.png',
'https://docs-51yrc-com.oss-cn-hangzhou.aliyuncs.com/chat/avatar-05.png',
'https://docs-51yrc-com.oss-cn-hangzhou.aliyuncs.com/chat/avatar-06.png',
'https://docs-51yrc-com.oss-cn-hangzhou.aliyuncs.com/chat/avatar-07.png',
'https://docs-51yrc-com.oss-cn-hangzhou.aliyuncs.com/chat/avatar-08.png',
'https://docs-51yrc-com.oss-cn-hangzhou.aliyuncs.com/chat/avatar-09.png',
'https://docs-51yrc-com.oss-cn-hangzhou.aliyuncs.com/chat/avatar-10.png',
],
names: ["晓明哥", "热巴", "GIGI梁咏琪", "古巨基", "古力娜扎", "阿祖",
"力宏哥", "华仔", "黄奕", "刘涛", "林俊杰", "涛涛",
"阿力", "许文强", "周杰伦", "王心凌"
],
topMenus:[
{icon:'man-add-fill', title:'新的朋友', url:'/pages/applyMyfriend/applyMyfriend'},
// {icon:'tags-fill', title:'标签', url:''},
// {icon:'chrome-circle-fill', title:'朋友圈', url:''},
// {icon:'qq-fill', title:'QQ', url:''},
],
}
},
onLoad() {
console.log('我的信息',this.me);
console.log('好友申请信息',this.goodfriendapply);
if(!this.me || this.me.role == 'visitor') return this.navigateBack();
// 获取好友列表
this.$store.dispatch('getGoodfriendlist');
},
computed: {
...mapState({
me : state => state.Chatuser.regloginUser,
goodfriendapply : state => state.Chatuser.goodfriendapply,
goodfriendlist : state => state.Chatuser.goodfriendlist,
}),
itemArr() {
/*
return this.indexList && this.indexList.map(item => {
const arr = []
for (let i = 0; i < 10; i++) {
arr.push({
name: this.names[uni.$u.random(0, this.names.length - 1)],
url: this.urls[uni.$u.random(0, this.urls.length - 1)]
})
}
return arr
})
*/
let newList = (this.goodfriendlist && this.goodfriendlist.rows) &&
this.goodfriendlist.rows.newList;
return newList && newList.map(item => {
const arr = [];
for (let i = 0; i < item.list.length; i++) {
let avatar = item.list[i].avatar.startsWith('http') ?
item.list[i].avatar : requestUrl.http + item.list[i].avatar;
arr.push({
name: item.list[i].name,
url: avatar,
uuid: item.list[i].uuid,
});
}
return arr;
});
},
// 数据库获取indexList
indexList(){
return (this.goodfriendlist && this.goodfriendlist.rows) &&
this.goodfriendlist.rows.indexList;
}
},
methods: {
opentopMenus(item,index){
console.log('点击了某一项',item);
uni.navigateTo({
url: item.url,
});
},
openUserInfo(item1,action){
uni.navigateTo({
url: `/pages/userinfo/userinfo?uuid=${item1.uuid}`,
});
}
}
}
</script>
<style>
/* #ifdef H5 */
@import '/common/css/common.nvue.vue.css';
/* #endif */
</style>
<style lang="scss">
.list {
&__item {
@include flex;
padding: 6px 12px;
align-items: center;
&__avatar {
height: 35px;
width: 35px;
border-radius: 3px;
}
&__user-name {
font-size: 16px;
margin-left: 10px;
color: $u-main-color;
}
}
&__footer {
color: $u-tips-color;
font-size: 14px;
text-align: center;
margin: 15px 0;
}
}
</style>
# 十三、用户详情设置(当前主要开发的是聊天设置)
# 1. 新建设置详情页面 /pages/setpageInfo/setpageInfo.vue
在 /pages.json 中
{
"path" : "pages/setpageInfo/setpageInfo",
"style" :
{
"navigationBarTitleText" : "详细设置",
"navigationStyle": "custom"
}
}
# 2. 从设置页进入
在 /pages/setpage/setpage.vue 代码
<template>
<view>
<!-- 导航栏 -->
...
<!-- 内容 -->
<view >
<!-- 聊天相关 -->
<u-cell-group title="聊天相关" :border="false"
:customStyle="{'background-color':'#eeeeee'}">
<u-cell title="聊天设置"
:customStyle="{'background-color':'#ffffff'}"
isLink titleStyle="fontSize:18px;"
@click="setfun('chatset','可以和我聊天的设置')"></u-cell>
</u-cell-group>
<!-- 退出 -->
...
</view>
</view>
</template>
<script>
...
export default {
...
methods: {
// 设置
setfun(action,title){
uni.navigateTo({
url: `/pages/setpageInfo/setpageInfo?action=${action}&title=${encodeURIComponent(title)}`,
});
},
...
}
}
</script>
# 3. 设置详情页面代码
用到两个接口:
在页面 /pages/setpageInfo/setpageInfo.vue
<template>
<view>
<!-- 导航栏 -->
<chat-navbar :title="title" :fixed="true" :showPlus="false"
:showUser="false" :showBack="true"
navbarClass="navbar-bgColor" :h5WeiXinNeedNavbar="false">
</chat-navbar>
<!-- 内容 -->
<view >
<!-- 聊天相关 -->
<view v-if="setdata.action == 'chatset'">
<!-- 游客(未登录用户) -->
<u-cell-group title="游客(未登录用户)" :border="false"
:customStyle="{'background-color':'#eeeeee'}">
<u-cell
:customStyle="{'background-color':'#ffffff'}"
v-if="setdata.visitor.list.length">
<view slot="title"
class="flex align-center border">
<view class="flex justify-center py-2 flex-1"
v-for="(item,index) in setdata.visitor.list" :key="index"
:class="[setdata.visitor.current == index ?
'bg-success text-white':'bg-light']"
@click="setdata.visitor.current = index">
<text style="font-size: 24rpx;">{{item.title}}</text>
</view>
</view>
</u-cell>
</u-cell-group>
<!-- 登录用户(非好友) -->
<u-cell-group title="登录用户(非好友)" :border="false"
:customStyle="{'background-color':'#eeeeee'}">
<u-cell
:customStyle="{'background-color':'#ffffff'}"
v-if="setdata.user.list.length">
<view slot="title"
class="flex align-center border">
<view class="flex justify-center py-2 flex-1"
v-for="(item,index) in setdata.user.list" :key="index"
:class="[setdata.user.current == index ?
'bg-success text-white':'bg-light']"
@click="setdata.user.current = index">
<text style="font-size: 24rpx;">{{item.title}}</text>
</view>
</view>
</u-cell>
</u-cell-group>
<!-- 确定 -->
<view class="p-3">
<u-button type="primary" text="确 定" @click="submitSet"
:loading="btn.loading" :loadingText="btn.loadingText"
:disabled="btn.disabled"></u-button>
</view>
</view>
</view>
</view>
</template>
<script>
import {requestUrl,visitorSalt} from '@/common/mixins/configData.js';
import toolJs from '@/common/mixins/tool.js';
import {mapState,mapGetters,mapMutations,mapActions} from 'vuex';
export default {
mixins: [toolJs],
data() {
return {
title:'',
setdata:{
visitor : {},
user : {},
},
btn:{
loading:false,
loadingText:'正在提交中',
disabled:false,
}
}
},
onLoad(e) {
console.log(e);
if(!e.action || !this.me || this.me.role == 'visitor') return this.navigateBack();
if(e.title) {
this.title = decodeURIComponent(e.title);
uni.setNavigationBarTitle({
title:this.title,
});
};
//动态生成数据
if(e.action){
this.setdata.action = e.action;
if(this.setdata.action == 'chatset'){
this.setdata.visitor = {
list:[
{ title:'禁止聊天(先登录)', value:0 },
{ title:'可向我发一条消息', value:1},
{ title:'可以聊天', value:2 },
],
current: 1,
};
this.setdata.user = {
list:[
{ title:'禁止聊天(先加好友)', value:0 },
{ title:'可向我发一条消息', value:1},
{ title:'可以聊天', value:2 },
],
current: 1,
};
// 获取用户的设置信息
uni.$u.http.get(requestUrl.http + `/api/userinfo/${this.me.uuid}`).then(res => {
console.log('获取我的信息', res);
let userset = res.data.data.userset;
userset = userset && JSON.parse(userset);
this.setdata.visitor.current = userset.chatset.visitor;
this.setdata.user.current = userset.chatset.user;
});
}
}
},
computed:{
...mapState({
me : state => state.Chatuser.regloginUser,
}),
},
methods: {
submitSet(){
let userset = {};
//聊天设置
if(this.setdata.action == 'chatset'){
const chatset = {
visitor: this.setdata.visitor.list[this.setdata.visitor.current].value,
user: this.setdata.user.list[this.setdata.user.current].value,
}
console.log('提交聊天设置:', chatset);
userset.chatset = chatset;
}
console.log('提交给服务器',userset);
this.btn.loading = true;
this.btn.disabled = true;
uni.$u.http.post(requestUrl.http + `/api/chat/userset`, {
userset:JSON.stringify(userset),
}, {
header: {
token: this.me.token,
},
}).then(res => {
console.log('服务器返回的设置结果', res);
this.btn.loading = false;
if(res.data.data == 'ok'){
uni.showToast({
title: '设置成功',
icon: 'none',
duration: 1000
});
setTimeout(()=>{
return this.navigateBack();
},1000);
}
}).catch(err => {
this.btn.loading = false;
if (err.data && err.data.data) {
uni.showToast({
title: err.data.data,
icon: 'none',
duration: 5000
});
}
});
},
}
}
</script>
<style>
</style>
# 十四. 发消息前解决几个小问题
- 页面
/pages/setpageInfo/setpageInfo在小程序标题过程不好看,调整/pages/setpage/setpage.vue页面方法:@click="setfun('chatset','和我聊天设置')"标题;
# 1. 聊天设置信息提交数据完善
配合页面 /pages/userinfo/userinfo.vue 发消息的逻辑,我们需要调整:
① 页面
/pages/setpageInfo/setpageInfo.vue提交服务器的设置信息,如果登录用户还需增加一个条件才能和我聊天,如:需要关注才能发消息,因此我们提交数据的构造应该调整一下:
onLoad(e) {
...
// 动态生成数据
if(e.action){
this.setdata.action = e.action;
if(this.setdata.action == 'chatset'){
...
// 获取用户的设置信息
uni.$u.http.get(requestUrl.http + `/api/userinfo/${this.me.uuid}`).then(res => {
...
if(userset){
...
this.setdata.visitor.current = userset.chatset.visitor.sendCount;
this.setdata.user.current = userset.chatset.user.sendCount;
}
})
}
}
},
methods: {
submitSet(){
let userset = {};
// 聊天设置
if(this.setdata.action == 'chatset'){
const chatset = {
visitor: {
sendCount:this.setdata.visitor.list[this.setdata.visitor.current].value,
needFollow:false,
},
user: {
sendCount:this.setdata.user.list[this.setdata.user.current].value,
needFollow:false,
},
}
userset.chatset = chatset;
}
console.log('提交给服务器的设置数据',userset);
...
}
}
# 2. 发消息按钮的两种情况判断
- 从搜索页搜索用户进入,此时点击发消息,存在这个用户是否是你的好友的判断,如果不是好友,则按照用户的聊天设置进行处理;
- 从好友列表(通讯录)点击头像进入,此时已经是你的好友了,则直接进行聊天(除非将你拉黑了[拉黑功能后面再实现]);
基于以上情况,在点击发消息前,我们需要:
- 查询一下对方是否是我的好友
- 根据是否是好友的情况进行处理
- 很多用户没有进行聊天设置,字段
userset是空值,针对空值的情况的处理
# ① 查询一下对方是否是我的好友
# 1. 接口说明
具体查看接口说明: 十九、查询一下对方是否是我的好友
# 2. 处理发消息的逻辑
在页面 /pages/userinfo/userinfo.vue 代码
<template>
<view class="page">
...
</view>
</template>
<script>
...
export default {
...
methods: {
...,
// 获取用户信息
getUserInfo(){
uni.$u.http.get(requestUrl.http + `/api/userinfo/${this.uuid}`).then(res => {
console.log('获取用户信息结果', res);
...
// 是不是我的好友
this.ismygoodfriend();
}).catch(err => {
...
});
},
// 是不是我的好友
ismygoodfriend(){
uni.$u.http.post(requestUrl.http + `/api/chat/ismygoodfriend/${this.user.id}`,{},{
header:{
token:uni.getStorageSync('chatuser_token'),
}
}).then(res => {
console.log('是否是我的好友结果',res);
if(res.data.data == 'goodfriend') this.me.ismygoodfriend = true;
}).catch(err => {
// 不是好友
this.me.ismygoodfriend = false;
});
},
// 发消息
sendMessageFun(){
console.log('发消息的逻辑',this.me);
if(this.me.ismygoodfriend){
console.log('是好友,可以直接聊天了');
}else{
const role = this.me.role; //'visitor' | 'user'
// const { sendCount, needFollow } = this.user.chatset[role];
let userset = this.user.userset;
if(!userset){
// 用户没有聊天设置之前
userset = {
chatset:{
visitor: {
sendCount:1, // 发一条
needFollow:false, // 不需要关注
},
user: {
sendCount:1,
needFollow:false,
},
}
}
}else{
userset = JSON.parse(userset);
}
const { sendCount, needFollow } = userset.chatset[role];
// 1. 检查是否需要关注
// if(needFollow && this.me.hasFollowed){
// console.log('请先关注对方才能发消息');
// return; // 程序结束
// }
// 2. 根据对方设置的聊天权限处理
...
}
},
}
}
</script>
# 3. 处理完用户申请返回好友列表(通讯录)更新好友列表
在页面 /pages/friendsList/friendsList.nvue
onShow() {
//获取好友列表
this.$store.dispatch('getGoodfriendlist');
},