9. 社交模块 - 添加好友,展示好友
目录
社交模块
处理用户与用户之间的关系
好友列表页面添加
客户端显示页面
- 用户中心点击好友列表同时进入好友列表主副页面,
html/user.html
,代码:
<!DOCTYPE html>
<html>
<head>
<title>用户中心</title>
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta charset="utf-8">
<link rel="stylesheet" href="../static/css/main.css">
<script src="../static/js/vue.js"></script>
<script src="../static/js/axios.js"></script>
<script src="../static/js/uuid.js"></script>
<!-- 引入用户头像处理js文件 -->
<script src="../static/js/v-avatar-2.0.3.min.js"></script>
<script src="../static/js/main.js"></script>
</head>
<body>
<div class="app user" id="app">
<div class="bg">
<img src="../static/images/bg0.jpg">
</div>
<img class="back" @click="back" src="../static/images/user_back.png" alt="">
<img class="setting" @click='to_settings' src="../static/images/setting.png" alt="">
<div class="header">
<div class="info">
<div class="avatar">
<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
<!-- 用户头像处理 -->
<div class="user_avatar">
<v-avatar v-if="user_data.avatar" :src="user_data.avatar" :size="55" :rounded="true"></v-avatar>
<v-avatar v-else-if="user_data.nickname" :username="user_data.nickname" :size="55" :rounded="true"></v-avatar>
<v-avatar v-else :username="user_data.id" :size="55" :rounded="true"></v-avatar>
</div>
<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
</div>
<p class="user_name">{{user_data.nickname}}</p>
</div>
<div class="wallet">
<div class="balance">
<p class="title"><img src="../static/images/money.png" alt="">钱包</p>
<p class="num">{{user_data.money_format}}</p>
</div>
<div class="balance">
<p class="title"><img src="../static/images/integral.png" alt="">果子</p>
<p class="num">{{user_data.credit_format}}</p>
</div>
</div>
<div class="invite">
<img class="invite_btn" src="../static/images/invite.png" alt="">
</div>
</div>
<div class="menu">
<div class="item">
<span class="title">我的主页</span>
<span class="value">查看</span>
</div>
<div class="item" @click='to_friend_list'>
<span class="title">好友列表</span>
<span class="value">查看</span>
</div>
<div class="item">
<span class="title">收益明细</span>
<span class="value">查看</span>
</div>
<div class="item">
<span class="title">实名认证</span>
<span class="value">未认证</span>
</div>
<div class="item">
<span class="title">问题反馈</span>
<span class="value">去反馈</span>
</div>
</ul>
</div>
</div>
<script>
apiready = function(){
var game = new Game("../static/mp3/bg4.mp3");
// 在 #app 标签下渲染一个按钮组件
Vue.prototype.game = game;
new Vue({
el:"#app",
data(){
return {
prev:{name:"",url:"",params:{}},
current:{name:"user",url:"user.html",params:{}},
user_data:{},
}
},
// 页面加载之前获取用户数据
created(){
// 获取用户数据
this.get_user_data()
// 监听事件变化
this.listen()
},
methods:{
// 监听事件
listen(){
// 监听头像更新的通知
this.listen_update_avatar();
this.listen_update_nickname();
},
// 监听头像更新的通知
listen_update_avatar(){
api.addEventListener({
name: 'update_avatar_success'
}, (ret, err) => {
// 更新用户数据
this.get_user_data()
});
},
// 监听昵称更新的通知
listen_update_nickname(){
api.addEventListener({
name: 'update_nickname_success'
}, (ret, err) => {
// 更新用户数据
this.get_user_data()
});
},
// 通过token值获取用户数据
get_user_data(){
// 获取token
let token = this.game.getfs('access_token') || this.game.getdata('access_token')
// 根据token获取用户数据
this.user_data = this.game.get_user_by_token(token)
// this.game.print(this.user_data)
// 格式化数字变成金钱格式,原始数据不变
this.user_data.money_format = this.game.number_format(this.user_data.money)
this.user_data.credit_format = this.game.number_format(this.user_data.credit)
},
back(){
// 返回首页
this.game.closeWin();
},
// 点击设置按钮,跳转到系统设置页面
to_settings(){
this.game.openFrame('settings', 'settings.html')
},
// 点击好友列表,跳转带好友列表页面
to_friend_list(){
this.game.openFrame('friends', 'friends.html')
this.game.openFrame('friend_list', 'friend_list.html', null, {
x: 0, // 左上角x轴坐标
y: 194, // 左上角y轴坐标
w: 'auto', // 当前帧页面的宽度, auto表示满屏
h: 'auto' // 当前帧页面的高度, auto表示满屏
})
},
}
});
}
</script>
</body>
</html>
修改static/js/main.js
中封装的打开帧页面方法
// 创建帧页面
openFrame(name,url,redirect='from_right',rect, pageParam){
let frame = {
name: name, // 帧页面的名称
url: url, // 帧页面打开的url地址
bounces:false, // 页面是否可以下拉拖动
reload: true, // 帧页面如果已经存在,是否重新刷新加载
useWKWebView:true, // 是否使用WKWebView来加载页面
historyGestureEnabled:true, // 是否可以通过手势来进行历史记录前进后退,只在useWKWebView参数为true时有效
vScrollBarEnabled: false, // 是否显示垂直滚动条
hScrollBarEnabled: false, // 是否显示水平滚动条
animation:{
type:"push", //动画类型(详见动画类型常量)
subType:redirect, //动画子类型(详见动画子类型常量)
duration:300 //动画过渡时间,默认300毫秒
},
rect: { // 当前帧的宽高范围
// 方式1,设置矩形大小宽高
x: 0, // 左上角x轴坐标
y: 0, // 左上角y轴坐标
w: 'auto', // 当前帧页面的宽度, auto表示满屏
h: 'auto' // 当前帧页面的高度, auto表示满屏
// 方式2,设置矩形大小宽高
// marginLeft:, //相对父页面左外边距的距离,数字类型
// marginTop:, //相对父页面上外边距的距离,数字类型
// marginBottom:, //相对父页面下外边距的距离,数字类型
// marginRight: //相对父页面右外边距的距离,数字类型
},
pageParam: {} // 要传递新建帧页面的参数,在新页面可通过 api.pageParam.name 获取
}
if(rect){
frame.rect = rect
}
if (pageParam) {
frame.pageParam = pageParam
}
// 打开帧页面
api.openFrame(frame);
}
- 好友列表主窗口页面
html/friends.html
,,代码:
<!DOCTYPE html>
<html>
<head>
<title>好友列表主窗口</title>
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta charset="utf-8">
<link rel="stylesheet" href="../static/css/main.css">
<script src="../static/js/vue.js"></script>
<script src="../static/js/axios.js"></script>
<script src="../static/js/uuid.js"></script>
<script src="../static/js/main.js"></script>
</head>
<body>
<div class="app user setting" id="app">
<div class="bg">
<img src="../static/images/friends_bg.png">
</div>
<img class="back" @click="back" src="../static/images/user_back.png" alt="">
<div class="add_friend_btn" @click="add_friend">
<img src="../static/images/add_friends.png" alt="">
</div>
<div class="friends_list">
</div>
</div>
<script>
apiready = function(){
var game = new Game("../static/mp3/bg1.mp3");
// 在 #app 标签下渲染一个按钮组件
Vue.prototype.game = game;
new Vue({
el:"#app",
data(){
return {
friends:[],
}
},
methods:{
back(){
this.game.closeFrame();
},
add_friend(){
// 添加好友
}
}
});
}
</script>
</body>
</html>
- 新添css样式
css/main.css
,并把素材中的图片添加到static/image
文件夹中 样式代码:
.add_friend_btn {
position: absolute;
top: 12rem;
left: 3.6rem;
width: 26rem;
height: 6rem;
}
.add_friend_btn img{
box-shadow: 2px 2px 5px rgba(9,9,9,0.1);
}
- 好友列表副窗口页面
friend_list.html
,设置打开好友列表页面的同时,也打开好友列表数据页面代码。
friend_list.html
,代码:
<!DOCTYPE html>
<html>
<head>
<title>好友列表</title>
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta charset="utf-8">
<link rel="stylesheet" href="../static/css/main.css">
<script src="../static/js/vue.js"></script>
<script src="../static/js/axios.js"></script>
<script src="../static/js/uuid.js"></script>
<script src="../static/js/main.js"></script>
</head>
<body>
<div class="app user setting" id="app">
<div class="friends_list">
<div class="item">
<div class="avatar">
<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
<img class="user_avatar" src="../static/images/avatar.png" alt="">
<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
</div>
<div class="info">
<p class="username">长昵称都很好</p>
<p class="fruit">果子:9,999.00</p>
</div>
<div class="behavior pick">摘</div>
<div class="goto"><img src="../static/images/arrow1.png" alt=""></div>
</div>
<div class="item">
<div class="avatar">
<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
<img class="user_avatar" src="../static/images/avatar.png" alt="">
<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
</div>
<div class="info">
<p class="username">长昵称都很好</p>
<p class="fruit">果子:9,999.00</p>
</div>
<div class="goto"><img src="../static/images/arrow1.png" alt=""></div>
</div>
<div class="item">
<div class="avatar">
<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
<img class="user_avatar" src="../static/images/avatar.png" alt="">
<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
</div>
<div class="info">
<p class="username">长昵称都很好</p>
<p class="fruit">果子:9,999.00</p>
</div>
<div class="behavior protect">护</div>
<div class="goto"><img src="../static/images/arrow1.png" alt=""></div>
</div>
<div class="item">
<div class="avatar">
<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
<img class="user_avatar" src="../static/images/avatar.png" alt="">
<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
</div>
<div class="info">
<p class="username">长昵称都很好</p>
<p class="fruit">果子:9,999.00</p>
</div>
<div class="behavior pick">摘</div>
<div class="goto"><img src="../static/images/arrow1.png" alt=""></div>
</div>
<div class="item">
<div class="avatar">
<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
<img class="user_avatar" src="../static/images/avatar.png" alt="">
<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
</div>
<div class="info">
<p class="username">长昵称都很好</p>
<p class="fruit">果子:9,999.00</p>
</div>
<div class="goto"><img src="../static/images/arrow1.png" alt=""></div>
</div>
<div class="item">
<div class="avatar">
<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
<img class="user_avatar" src="../static/images/avatar.png" alt="">
<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
</div>
<div class="info">
<p class="username">长昵称都很好</p>
<p class="fruit">果子:9,999.00</p>
</div>
<div class="goto"><img src="../static/images/arrow1.png" alt=""></div>
</div>
</div>
</div>
<script>
apiready = function(){
var game = new Game("../static/mp3/bg1.mp3");
// 在 #app 标签下渲染一个按钮组件
Vue.prototype.game = game;
new Vue({
el:"#app",
data(){
return {
friends:[],
page: 1,
prev:{name:"",url:"",params:{}},
current:{name:"friend_list",url:"friend_list.html",params:{}},
}
},
created(){
this.get_friends();
},
methods:{
get_friends(){
},
goto_home(){
// 退出当前页面
this.game.closeFrame();
},
}
});
}
</script>
</body>
</html>
- 新添css样式
css/main.css
,样式代码:
.friends_list .avatar{
width: 6.39rem;
height: 6.39rem;
position: relative;
}
.friends_list .avatar_bf{
position: absolute;
z-index: 1;
margin: auto;
width: 4.56rem;
height: 4.56rem;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.friends_list .user_avatar{
position: absolute;
z-index: 1;
width: 4.56rem;
height: 4.56rem;
margin: auto;
top: 0;
bottom: 0;
left: 0;
right: 0;
border-radius: 1rem;
}
.friends_list .avatar_border{
position: absolute;
z-index: 1;
margin: auto;
top: 0;
bottom: 0;
left: 0;
right: 0;
width: 6.1rem;
height: 6.1rem;
}
.friends_list{
position: absolute;
top: 0rem;
left: 3.6rem;
}
.friends_list .item{
position: relative;
background-color: rgba(196,81,9,0.1);
border-radius: 4px;
height: 7rem;
width: 25.8rem;
margin-bottom: 1rem;
box-shadow: 2px 2px 5px rgba(9,9,9,0.1);
}
.friends_list .item .avatar{
position: absolute;
left: 1rem;
top: 0;
bottom: 0;
margin: auto;
}
.friends_list .item .info{
position: absolute;
left: 8rem;
top: 2rem;
color: #fff;
width: 10rem;
}
.friends_list .item .behavior{
position: absolute;
left: 16rem;
font-size: 1.5rem;
text-align: center;
line-height: 4rem;
height: 4rem;
width: 4rem;
color: #fff;
top: 0;
bottom: 0;
margin: auto;
box-shadow: 2px 2px 5px #333333;
}
.friends_list .item .pick{
background: #336633;
border-radius: 50%;
}
.friends_list .item .protect{
background: #990000;
border-top-right-radius: 5px;
border-top-left-radius: 5px;
border-bottom-left-radius: 30px;
border-bottom-right-radius: 30px;
}
.friends_list .item .goto{
position: absolute;
left: 23rem;
top: 0;
bottom: 0;
margin: auto;
width: 0.96rem;
height: 1.8rem;
}
- 在页面退出的时候,好友页面的主窗口和列表页都要同时退出。
frients.html
,代码:
<!DOCTYPE html>
<html>
<head>
<title>好友列表主窗口</title>
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta charset="utf-8">
<link rel="stylesheet" href="../static/css/main.css">
<script src="../static/js/vue.js"></script>
<script src="../static/js/axios.js"></script>
<script src="../static/js/uuid.js"></script>
<script src="../static/js/main.js"></script>
</head>
<body>
<div class="app user setting" id="app">
<div class="bg">
<img src="../static/images/friends_bg.png">
</div>
<img class="back" @click="back" src="../static/images/user_back.png" alt="">
<div class="add_friend_btn" @click="add_friend">
<img src="../static/images/add_friends.png" alt="">
</div>
<div class="friends_list">
</div>
</div>
<script>
apiready = function(){
var game = new Game("../static/mp3/bg1.mp3");
// 在 #app 标签下渲染一个按钮组件
Vue.prototype.game = game;
new Vue({
el:"#app",
data(){
return {
friends:[],
prev:{name:"",url:"",params:{}},
current:{name:"friends",url:"friends.html",params:{}},
}
},
methods:{
back(){
this.game.closeFrame("friend_list");
this.game.closeFrame();
},
add_friend(){
// 添加好友
}
}
});
}
</script>
</body>
</html>
- 给好友列表的数据页面,添加拉下拉刷新效果,
html/friend_list.html
,代码:
<!DOCTYPE html>
<html>
<head>
<title>好友列表</title>
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta charset="utf-8">
<link rel="stylesheet" href="../static/css/main.css">
<script src="../static/js/vue.js"></script>
<script src="../static/js/axios.js"></script>
<script src="../static/js/uuid.js"></script>
<script src="../static/js/main.js"></script>
</head>
<body>
<div class="app user setting" id="app">
<div class="friends_list">
<div class="item">
<div class="avatar">
<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
<img class="user_avatar" src="../static/images/avatar.png" alt="">
<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
</div>
<div class="info">
<p class="username">长昵称都很好</p>
<p class="fruit">果子:9,999.00</p>
</div>
<div class="behavior pick">摘</div>
<div class="goto"><img src="../static/images/arrow1.png" alt=""></div>
</div>
<div class="item">
<div class="avatar">
<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
<img class="user_avatar" src="../static/images/avatar.png" alt="">
<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
</div>
<div class="info">
<p class="username">长昵称都很好</p>
<p class="fruit">果子:9,999.00</p>
</div>
<div class="goto"><img src="../static/images/arrow1.png" alt=""></div>
</div>
<div class="item">
<div class="avatar">
<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
<img class="user_avatar" src="../static/images/avatar.png" alt="">
<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
</div>
<div class="info">
<p class="username">长昵称都很好</p>
<p class="fruit">果子:9,999.00</p>
</div>
<div class="behavior protect">护</div>
<div class="goto"><img src="../static/images/arrow1.png" alt=""></div>
</div>
<div class="item">
<div class="avatar">
<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
<img class="user_avatar" src="../static/images/avatar.png" alt="">
<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
</div>
<div class="info">
<p class="username">长昵称都很好</p>
<p class="fruit">果子:9,999.00</p>
</div>
<div class="behavior pick">摘</div>
<div class="goto"><img src="../static/images/arrow1.png" alt=""></div>
</div>
<div class="item">
<div class="avatar">
<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
<img class="user_avatar" src="../static/images/avatar.png" alt="">
<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
</div>
<div class="info">
<p class="username">长昵称都很好</p>
<p class="fruit">果子:9,999.00</p>
</div>
<div class="goto"><img src="../static/images/arrow1.png" alt=""></div>
</div>
<div class="item">
<div class="avatar">
<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
<img class="user_avatar" src="../static/images/avatar.png" alt="">
<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
</div>
<div class="info">
<p class="username">长昵称都很好</p>
<p class="fruit">果子:9,999.00</p>
</div>
<div class="goto"><img src="../static/images/arrow1.png" alt=""></div>
</div>
</div>
</div>
<script>
apiready = function(){
var game = new Game("../static/mp3/bg1.mp3");
// 在 #app 标签下渲染一个按钮组件
Vue.prototype.game = game;
new Vue({
el:"#app",
data(){
return {
friends:[],
page: 1,
prev:{name:"",url:"",params:{}},
current:{name:"friend_list",url:"friend_list.html",params:{}},
}
},
created(){
this.get_friends();
},
methods:{
get_friends(){
// 下拉刷新好友列表
api.setRefreshHeaderInfo({
loadingImg: 'widget://image/refresh.png',
bgColor: null,
textColor: '#fff',
textDown: '下拉刷新...',
textUp: '松开刷新...'
}, (ret, err)=>{
// 在这里从服务器加载数据,加载完成后调用api.refreshHeaderLoadDone()方法恢复组件到默认状态
setTimeout(()=>{
api.refreshHeaderLoadDone();
},1500);
});
},
goto_home(){
// 退出当前页面
this.game.closeFrame();
},
}
});
}
</script>
</body>
</html>
添加好友显示页面
- 点击添加好友,跳转到条件好友页面
html/friends.html
,代码:
<!DOCTYPE html>
<html>
<head>
<title>好友列表主窗口</title>
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta charset="utf-8">
<link rel="stylesheet" href="../static/css/main.css">
<script src="../static/js/vue.js"></script>
<script src="../static/js/axios.js"></script>
<script src="../static/js/uuid.js"></script>
<script src="../static/js/main.js"></script>
</head>
<body>
<div class="app user setting" id="app">
<div class="bg">
<img src="../static/images/friends_bg.png">
</div>
<img class="back" @click="back" src="../static/images/user_back.png" alt="">
<div class="add_friend_btn" @click="add_friend">
<img src="../static/images/add_friends.png" alt="">
</div>
<div class="friends_list">
</div>
</div>
<script>
apiready = function(){
var game = new Game("../static/mp3/bg1.mp3");
// 在 #app 标签下渲染一个按钮组件
Vue.prototype.game = game;
new Vue({
el:"#app",
data(){
return {
friends:[],
prev:{name:"",url:"",params:{}},
current:{name:"friends",url:"friends.html",params:{}},
}
},
methods:{
back(){
this.game.closeFrame("friend_list");
this.game.closeFrame();
},
add_friend(){
// 添加好友
this.game.openFrame("add_friend","add_friend.html",null,null,{
type: "push", //动画类型(详见动画类型常量)
subType: "from_top", //动画子类型(详见动画子类型常量)
duration: 300 //动画过渡时间,默认300毫秒
});
},
}
});
}
</script>
</body>
</html>
- 添加好友页面
html/add_friend.html
,,页面代码:
<!DOCTYPE html>
<html>
<head>
<title>添加好友</title>
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta charset="utf-8">
<link rel="stylesheet" href="../static/css/main.css">
<script src="../static/js/vue.js"></script>
<script src="../static/js/axios.js"></script>
<script src="../static/js/uuid.js"></script>
<script src="../static/js/main.js"></script>
</head>
<body>
<div class="app frame avatar update_nickname add_friend" id="app">
<div class="box">
<p class="title">添加好友</p>
<img class="close" @click="back" src="../static/images/close_btn1.png" alt="">
<div class="content">
<input class="nickname" type="text" v-model="account" placeholder="输入昵称/手机/邮箱/魔方账号....">
</div>
<div class="friends_list">
<div class="item">
<div class="avatar">
<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
<img class="user_avatar" src="../static/images/avatar.png" alt="">
<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
</div>
<div class="info">
<p class="username">长昵称都很好</p>
<p class="time">刚刚搜索</p>
</div>
<div class="status">添加</div>
</div>
<div class="item">
<div class="avatar">
<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
<img class="user_avatar" src="../static/images/avatar.png" alt="">
<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
</div>
<div class="info">
<p class="username">长昵称都很好</p>
<p class="time">3小时前</p>
</div>
<div class="status" @click="change_status">等待通过</div>
</div>
<div class="item">
<div class="avatar">
<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
<img class="user_avatar" src="../static/images/avatar.png" alt="">
<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
</div>
<div class="info">
<p class="username">长昵称都很好</p>
<p class="time">1天前</p>
</div>
<div class="status">已通过</div>
</div>
<div class="item">
<div class="avatar">
<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
<img class="user_avatar" src="../static/images/avatar.png" alt="">
<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
</div>
<div class="info">
<p class="username">长昵称都很好</p>
<p class="time">7天前</p>
</div>
<div class="status">已超时</div>
</div>
<div class="item">
<div class="avatar">
<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
<img class="user_avatar" src="../static/images/avatar.png" alt="">
<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
</div>
<div class="info">
<p class="username">长昵称都很好</p>
<p class="time">7天前</p>
</div>
<div class="status">已拒绝</div>
</div>
</div>
</div>
</div>
<script>
apiready = function(){
var game = new Game("../static/mp3/bg1.mp3");
// 在 #app 标签下渲染一个按钮组件
Vue.prototype.game = game;
new Vue({
el:"#app",
data(){
return {
account:"",
}
},
methods:{
back(){
this.game.closeFrame();
},
add_friend_commit(){
// 提交搜索信息
},
change_status(){
// 状态修改
}
}
});
}
</script>
</body>
</html>
- 添加页面样式
css/main.css
,代码:
.add_friend input::-webkit-input-placeholder,
.add_friend textarea::-webkit-input-placeholder{
color: #fff;
}
.add_friend .box{
top: 4rem;
height: 55.56rem;
background: url("../images/long_bg1.png") no-repeat 0 0;
background-size: 100%;
}
.add_friend .nickname{
margin: 4rem 4.6rem 2rem;
width: 19rem;
height: 4rem;
line-height: 4rem;
background-color: #cc9966;
outline: none;
border: 1px solid #330000;
text-align: center;
font-size: 1rem;
color: #ffffcc;
}
.add_friend .friends_list{
position: absolute;
top: 15rem;
left: 3.6rem;
}
.add_friend .friends_list .item{
position: relative;
margin-left: 1rem;
background-color: rgba(196,81,9,0.1);
border-radius: 4px;
height: 4rem;
width: 19rem;
margin-bottom: 1rem;
box-shadow: 2px 2px 5px rgba(9,9,9,0.1);
}
.add_friend .friends_list .avatar{
width: 3.84rem;
height: 3.84rem;
position: absolute;
left: 1rem;
}
.add_friend .friends_list .avatar_bf{
position: absolute;
z-index: 1;
margin: auto;
width: 2.74rem;
height: 2.74rem;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.add_friend .friends_list .user_avatar{
position: absolute;
z-index: 1;
width: 2.74rem;
height: 2.74rem;
margin: auto;
top: 0;
bottom: 0;
left: 0;
right: 0;
border-radius: 1rem;
}
.add_friend .friends_list .avatar_border{
position: absolute;
z-index: 1;
margin: auto;
top: 0;
bottom: 0;
left: 0;
right: 0;
width: 3.66rem;
height: 3.66rem;
}
.add_friend .friends_list .item .info{
top: 0.6rem;
left: 6rem;
}
.add_friend .friends_list .item .time{
font-size: 0.6rem;
}
.add_friend .friends_list .item .status{
position: absolute;
left: 12rem;
top: 1.2rem;
width: 8rem;
text-align: center;
height: 2rem;
color: #fff;
}
.add_friend .friends_list{
height: 36rem;
overflow-y: auto;
overflow-x: hidden;
}
添加好友
服务端提供接口
模型创建
users/models.py
,代码:
class UserApplyFriendHistory(BaseModel):
"""申请好友历史"""
relration_chioce = (
(1,"已申请"),
(2,"已通过"),
(3,"已超时"),
(4,"已拒绝"),
(5,"已取消"),
)
__tablename__ = "mf_user_apply_friend_history"
apply_id = db.Column(db.Integer, comment="申请人ID")
applied_id = db.Column(db.Integer, comment="被申请人ID")
apply_user = db.relationship('User', primaryjoin='User.id == UserApplyFriendHistory.apply_id', foreign_keys='UserApplyFriendHistory.apply_id', backref=backref('apply_list', uselist=True), uselist=False)
applied_user = db.relationship('User', primaryjoin='User.id == UserApplyFriendHistory.applied_id', foreign_keys='UserApplyFriendHistory.applied_id', backref=backref('applied_user_list',uselist=True), uselist=False)
status = db.Column(db.Integer, default=1, comment="关系状态")
def __repr__(self):
return f"<UserApplyFriendHistory {self.apply_user.nickname}>"
@property
def get_status(self):
"""获取历史状态"""
return self.relration_chioce[self.status-1]
获取好友申请添加记录
- 获取好友申请历史记录
users.api
,代码:
# 获取当前用户的好友申请历史记录
@jwt_required()
@decorator.get_user_object
def get_apply_friend_history(user):
'''
获取当前用户的好友申请历史记录
:param user: 装饰器通过token获取的用户模型对象
:return: 列表
'''
# 获取好友申请记录列表
friend_history_list = services.get_apply_friend_history_by_user_id(user.id)
return {
'errno' : code.CODE_OK,
'errmsg' : message.ok,
'friend_history_list': friend_history_list
}
- 数据服务层 获取数据
users.services
,代码:
from sqlalchemy import or_, and_
from .models import db, User, UserApplyFriendHistory
# 获取当前用户的好友添加申请历史
def get_apply_friend_history_by_user_id(user_id):
'''
根据用户ID获取好友申请记录
:param user_id: 用户ID
:return: 历史记录列表
'''
# 获取当前对象申请与被申请的模型对象列表
history_record_list = UserApplyFriendHistory.query.filter(
or_(
UserApplyFriendHistory.apply_id == user_id,
and_(
# 如果用户主动申请,并主动取消,则被申请人看不到申请记录
UserApplyFriendHistory.applied_id == user_id,
UserApplyFriendHistory.status < 5,
)
)
).order_by(UserApplyFriendHistory.created_time.desc()).limit(20).all()
from .marshmallow import ApplyFriendHistorySchema
# 实例化构造器
afhs = ApplyFriendHistorySchema()
# 序列化输出数据
friend_history_list = afhs.dump(history_record_list, many=True)
return friend_history_list
- 好友申请历史构造器
users.marshmallow
,代码:
from marshmallow import Schema, fields, validate, validates, ValidationError, post_load, validates_schema, post_dump
from marshmallow_sqlalchemy import SQLAlchemyAutoSchema, auto_field
from .models import User, UserApplyFriendHistory
# 好友申请历史构造器
class ApplyFriendHistorySchema(SQLAlchemyAutoSchema):
'''好友申请历史构造器'''
# 序列化器嵌套
apply_user = fields.Nested(UserSchema)
applied_user = fields.Nested(UserSchema)
class Meta:
model = UserApplyFriendHistory
include_fk = False # 启用外键关系
include_relationships = True # 模型关系外部属性
fields = ["id", "apply_user", "applied_user", "get_status", "created_time"]
- 路由
users.urls
,代码:
from application import path, api_rpc
# 引入当前蓝图应用视图 , 引入rpc视图
from . import views, api
# 蓝图路径与函数映射列表
urlpatterns = []
# rpc方法与函数映射列表[rpc接口列表]
apipatterns = [
api_rpc('check_mobile', api.check_mobile),
api_rpc('register', api.register),
api_rpc('login', api.login),
api_rpc('refresh', api.refresh_token), # 刷新access_token值
api_rpc('update_avatar', api.update_avatar), # 更新头像
api_rpc('update_nickname', api.update_nickname), # 更新昵称
api_rpc('update_mobile', api.update_mobile), # 更新手机号
api_rpc('update_password', api.update_password), # 更新登录密码
api_rpc('update_pay_password', api.update_pay_password), # 更新交易密码
api_rpc('get_apply_friend_history', api.get_apply_friend_history), # 获取好友申请列表
]
客户端显示好友申请记录
html/add_friend.html
,代码:
<!DOCTYPE html>
<html>
<head>
<title>添加好友</title>
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta charset="utf-8">
<link rel="stylesheet" href="../static/css/main.css">
<script src="../static/js/vue.js"></script>
<script src="../static/js/axios.js"></script>
<script src="../static/js/uuid.js"></script>
<script src="../static/js/v-avatar-2.0.3.min.js"></script>
<script src="../static/js/main.js"></script>
</head>
<body>
<div class="app frame avatar update_nickname add_friend" id="app">
<div class="box">
<p class="title">添加好友</p>
<img class="close" @click="back" src="../static/images/close_btn1.png" alt="">
<div class="content">
<input class="nickname" type="text" v-model="account" placeholder="输入昵称/手机/邮箱/魔方账号....">
</div>
<div class="friends_list">
<div class="item" v-for='user in user_list'>
<div class="avatar">
<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
<div class="user_avatar">
<v-avatar v-if="user.avatar" :src="user.avatar" :size="33" :rounded="true"></v-avatar>
<v-avatar v-else-if="user.nickname" :username="user.nickname" :size="33" :rounded="true"></v-avatar>
<v-avatar v-else :username="user.id" :size="33" :rounded="true"></v-avatar>
</div>
<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
</div>
<div class="info">
<p class="username">{{user.nickname}}</p>
</div>
<div class="status" @click="apply_friend(user)">添加</div>
</div>
<div class="item" v-for='hfriend in friend_history_list'>
<!-- 自己申请好友 -->
<div v-if='hfriend.apply_user.id == user_info.id'>
<div class="avatar">
<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
<div class="user_avatar">
<v-avatar v-if="hfriend.applied_user.avatar" :src="hfriend.applied_user.avatar" :size="33" :rounded="true"></v-avatar>
<v-avatar v-else-if="hfriend.applied_user.nickname" :username="hfriend.applied_user.nickname" :size="33" :rounded="true"></v-avatar>
<v-avatar v-else :username="hfriend.applied_user.id" :size="33" :rounded="true"></v-avatar>
</div>
<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
</div>
<div class="info">
<p class="username">{{hfriend.applied_user.nickname}}</p>
<!-- 时间需要过滤显示 -->
<p class="time">{{hfriend.created_time|time_format}}</p>
</div>
<div class="status">{{hfriend.get_status[1]}}</div>
</div>
<!-- 用户被申请 -->
<div v-else>
<div class="avatar">
<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
<div class="user_avatar">
<v-avatar v-if="hfriend.apply_user.avatar" :src="hfriend.apply_user.avatar" :size="33" :rounded="true"></v-avatar>
<v-avatar v-else-if="hfriend.apply_user.nickname" :username="hfriend.apply_user.nickname" :size="33" :rounded="true"></v-avatar>
<v-avatar v-else :username="hfriend.apply_user.id" :size="33" :rounded="true"></v-avatar>
</div>
<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
</div>
<div class="info">
<p class="username">{{hfriend.apply_user.nickname}}</p>
<!-- 时间需要过滤显示 -->
<p class="time">{{hfriend.created_time|time_format}}</p>
</div>
<div class="status" v-if="hfriend.get_status[0]==1">等待审核</div>
<div class="status" v-if="hfriend.get_status[0]==2">已同意</div>
<div class="status" v-if="hfriend.get_status[0]==3">{{hfriend.get_status[1]}}</div>
<div class="status" v-if="hfriend.get_status[0]==4">{{hfriend.get_status[1]}}</div>
</div>
</div>
</div>
</div>
</div>
<script>
apiready = function(){
var game = new Game("../static/mp3/bg1.mp3");
// 在 #app 标签下渲染一个按钮组件
Vue.prototype.game = game;
new Vue({
el:"#app",
data(){
return {
account:"", // 搜索用户名
search_timer:null, // 搜索定时器标记符
user_list: [], // 搜索结果用户列表
user_info: {}, // 当前用户信息
friend_history_list: [], // 好友申请历史列表
}
},
// 监听事件
watch:{
account(){
// 节流防抖
if(this.account.length >= 1){
// 清除定时器
clearTimeout(this.search_timer)
// 停止输入后,两秒钟发送请求
this.search_timer = setTimeout(() => {
this.search_user_info();
},2000)
}
}
},
created(){
// this.token = this.game.getdata('access_token') || this.game.getfs('access_token')
// this.user_info = this.game.get_user_by_token(this.token)
this.get_user_info() // 获取当前用户信息
this.get_apply_friend_history() // 获取好友申请历史记录
},
// 过滤器
filters:{
// 时间过滤器
time_format(time){
// 计算时间距离,返回文本格式
his_time_obj = new Date(time)
now_time_obj = new Date()
duration = parseInt((now_time_obj - his_time_obj)/1000)
if(0 <= duration && duration < 60 * 5){
// 5分钟内
return '刚刚'
}
if(60 * 5 <= duration && duration < 60 * 60){
// 1小时内
return parseInt(duration/60) + '分前'
}
if(60 * 60 <= duration && duration < 60 * 60 * 24){
// 1天内
return parseInt(duration/60/60) + '小时前'
}
if(60 * 60 * 24 <= duration && duration < 60 * 60 * 24 * 30){
// 1月内
return parseInt(duration/60/60/24) + '天前'
}
// 判断月份和年份的时间距离
let year_duration = now_time_obj.getFullYear() - his_time_obj.getFullYear();
let month_duration = now_time_obj.getMonth() - his_time_obj.getMonth();
if( now_time_obj.getMonth() < his_time_obj.getMonth() ){
month_duration +=12;
}
if(year_duration > 1 && year_duration < 10){
return year_duration+"年前";
}
if(year_duration > 5){
return time.split("T")[0];
}
if( month_duration < 6){
return month_duration+"个月前";
}
if( month_duration < 12 ){
return "半年前";
}
},
},
methods:{
back(){
this.game.closeFrame();
},
// 获取当前用户信息
get_user_info(){
let self = this
self.game.check_user_login(self, () => {
let token = self.game.getdata('access_token') || self.game.getfs('access_token')
self.user_info = self.game.get_user_by_token(token)
})
},
// 获取好友申请历史记录
get_apply_friend_history(){
let self = this
self.game.check_user_login(self, () => {
let token = self.game.getdata('access_token') || self.game.getfs('access_token')
self.game.post(self, {
'method': 'Users.get_apply_friend_history',
'params': {},
'header': {
'Authorization': 'jwt ' + token
},
success(response){
let data = response.data;
if(data.result && data.result.errno === 1000){
self.friend_history_list = data.result.friend_history_list
} else {
self.game.tips(data.result.errmsg)
}
}
})
})
},
// 提交搜索信息,获取用户列表
search_user_info(){
let self = this
self.game.check_user_login(self, () => {
let token = self.game.getdata('access_token') || self.game.getfs('access_token')
self.game.post(self, {
'method': 'Users.search_user_info',
'params': {'account': self.account},
'header': {
'Authorization': 'jwt ' + token
},
success(response){
let data = response.data;
if(data.result && data.result.errno === 1000){
self.user_list = data.result.user_list
} else {
self.game.tips(data.result.errmsg)
}
}
})
})
},
// 用户申请添加好友
apply_friend(user){
let self = this
self.game.check_user_login(self, () => {
let token = self.game.getdata('access_token') || self.game.getfs('access_token')
self.game.post(self, {
'method': 'Users.apply_friend',
'params': {'applied_user_id': user.id},
'header': {
'Authorization': 'jwt ' + token
},
success(response){
let data = response.data;
if(data.result && data.result.errno === 1000){
self.game.tips("成功发起好友申请!请等待...");
} else {
self.game.tips(data.result.errmsg)
}
}
})
})
},
change_status(){
// 状态修改
}
}
});
}
</script>
</body>
</html>
搜索用户信息
服务端提供搜索用户的api接口
- 视图
users.api
,代码:
@jwt_required()
@get_user_object
def search_user_info(user, account):
"""
搜索用户信息
:param user: 当前登录用户
:param account: 搜索账号相关信息(昵称,手机,邮箱,账号)
:return:
"""
# 根据用户账户信息搜索用户列表,排除当前用户
user_list = services.search_user_info(user, account)
return {
"errno": code.CODE_OK,
"errmsg": message.ok,
"user_list": user_list,
}
- 数据服务层
users.services
,代码:
def search_user_info(user, account):
"""
根据用户搜索的账号条件搜索其他用户信息
:param user: 当前用户模型
:param account: 搜索账号相关信息(昵称,手机,邮箱,账号)
:return: 用户列表
"""
user_object_list = User.query.filter(or_(
User.mobile == account,
User.nickname.contains(account),
User.email == account,
User.name == account,
)).filter(
User.id!=user.id
).all()
from .marshmallow import SearchUserInfoSchema
us = SearchUserInfoSchema()
us.current_user = user
user_list = us.dump(user_object_list, many=True)
return user_list
- 添加显示搜索用户信息序列化器,
marshmallow
,代码:
class SearchUserInfoSchema(MA.SQLAlchemyAutoSchema):
"""搜索用户信息的构造器"""
class Meta:
model = User
include_fk = False # 启用外键关系
include_relationships = True # 模型关系外部属性
# 如果要返回客户端用户模型的全部字段,就不要声明fields或exclude字段即可
fields = ["id", "name", "nickname", "avatar", "mobile"]
@post_dump
def get_object(self, data, **kwargs):
# todo 判断搜索用户和当前用户的关系
# self.current_user
data["mobile"] = data["mobile"][:3] +"****" + data["mobile"][-4:]
return data
- 路由,
users.urls
,代码:
from application import path,api
from . import api as apiviews
urlpatterns = [
]
apipatterns = [
api("mobile", apiviews.check_mobile),
api("register",apiviews.register),
api("login",apiviews.login),
api("refresh",apiviews.refresh),
api("avatar.update",apiviews.update_avatar),
api("nickname.update",apiviews.update_nickname),
api("password.update",apiviews.update_password),
api("pay_password.update",apiviews.update_pay_password),
api("apply_friend_history",apiviews.get_apply_friend_history),
api("search_user_info", apiviews.search_user_info),
]
客户端展示搜索用户信息
html/add_friend.html
,代码:
<!DOCTYPE html>
<html>
<head>
<title>添加好友</title>
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta charset="utf-8">
<link rel="stylesheet" href="../static/css/main.css">
<script src="../static/js/vue.js"></script>
<script src="../static/js/axios.js"></script>
<script src="../static/js/uuid.js"></script>
<script src="../static/js/v-avatar-2.0.3.min.js"></script>
<script src="../static/js/main.js"></script>
</head>
<body>
<div class="app frame avatar update_nickname add_friend" id="app">
<div class="box">
<p class="title">添加好友</p>
<img class="close" @click="back" src="../static/images/close_btn1.png" alt="">
<div class="content">
<input class="nickname" type="text" v-model="account" placeholder="输入昵称/手机/邮箱/魔方账号....">
</div>
<div class="friends_list">
<div class="item" v-for='user in user_list'>
<div class="avatar">
<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
<div class="user_avatar">
<v-avatar v-if="user.avatar" :src="user.avatar" :size="33" :rounded="true"></v-avatar>
<v-avatar v-else-if="user.nickname" :username="user.nickname" :size="33" :rounded="true"></v-avatar>
<v-avatar v-else :username="user.id" :size="33" :rounded="true"></v-avatar>
</div>
<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
</div>
<div class="info">
<p class="username">{{user.nickname}}</p>
</div>
<div class="status" @click="change_status">添加</div>
</div>
<div class="item" v-for='hfriend in friend_history_list'>
<!-- 自己申请好友 -->
<div v-if='hfriend.apply_user.id == user_info.id'>
<div class="avatar">
<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
<div class="user_avatar">
<v-avatar v-if="hfriend.applied_user.avatar" :src="hfriend.applied_user.avatar" :size="33" :rounded="true"></v-avatar>
<v-avatar v-else-if="hfriend.applied_user.nickname" :username="hfriend.applied_user.nickname" :size="33" :rounded="true"></v-avatar>
<v-avatar v-else :username="hfriend.applied_user.id" :size="33" :rounded="true"></v-avatar>
</div>
<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
</div>
<div class="info">
<p class="username">{{hfriend.applied_user.nickname}}</p>
<!-- 时间需要过滤显示 -->
<p class="time">{{hfriend.created_time|time_format}}</p>
</div>
<div class="status">{{hfriend.get_status[1]}}</div>
</div>
<!-- 用户被申请 -->
<div v-else>
<div class="avatar">
<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
<div class="user_avatar">
<v-avatar v-if="hfriend.apply_user.avatar" :src="hfriend.apply_user.avatar" :size="33" :rounded="true"></v-avatar>
<v-avatar v-else-if="hfriend.apply_user.nickname" :username="hfriend.apply_user.nickname" :size="33" :rounded="true"></v-avatar>
<v-avatar v-else :username="hfriend.apply_user.id" :size="33" :rounded="true"></v-avatar>
</div>
<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
</div>
<div class="info">
<p class="username">{{hfriend.apply_user.nickname}}</p>
<!-- 时间需要过滤显示 -->
<p class="time">{{hfriend.created_time|time_format}}</p>
</div>
<div class="status" v-if="hfriend.get_status[0]==1">等待审核</div>
<div class="status" v-if="hfriend.get_status[0]==2">已同意</div>
<div class="status" v-if="hfriend.get_status[0]==3">{{hfriend.get_status[1]}}</div>
<div class="status" v-if="hfriend.get_status[0]==4">{{hfriend.get_status[1]}}</div>
</div>
</div>
</div>
</div>
</div>
<script>
apiready = function(){
var game = new Game("../static/mp3/bg1.mp3");
// 在 #app 标签下渲染一个按钮组件
Vue.prototype.game = game;
new Vue({
el:"#app",
data(){
return {
account:"", // 搜索用户名
search_timer:null, // 搜索定时器标记符
user_list: [], // 搜索结果用户列表
token: "",
user_info: {}, // 当前用户信息
friend_history_list: [], // 好友申请历史列表
}
},
// 监听事件
watch:{
account(){
// 节流防抖
if(this.account.length >= 1){
// 清除定时器
clearTimeout(this.search_timer)
// 停止输入后,两秒钟发送请求
this.search_timer = setTimeout(() => {
this.search_user_info();
},2000)
}
}
},
created(){
this.token = this.game.getdata('access_token') || this.game.getfs('access_token')
this.user_info = this.game.get_user_by_token(this.token)
this.get_apply_friend_history() // 获取好友申请历史记录
},
// 过滤器
filters:{
// 时间过滤器
time_format(time){
// 计算时间距离,返回文本格式
his_time_obj = new Date(time)
now_time_obj = new Date()
duration = parseInt((now_time_obj - his_time_obj)/1000)
if(0 <= duration && duration < 60 * 5){
// 5分钟内
return '刚刚'
}
if(60 * 5 <= duration && duration < 60 * 60){
// 1小时内
return parseInt(duration/60) + '分前'
}
if(60 * 60 <= duration && duration < 60 * 60 * 24){
// 1天内
return parseInt(duration/60/60) + '小时前'
}
if(60 * 60 * 24 <= duration && duration < 60 * 60 * 24 * 30){
// 1月内
return parseInt(duration/60/60/24) + '天前'
}
// 判断月份和年份的时间距离
let year_duration = now_time_obj.getFullYear() - his_time_obj.getFullYear();
let month_duration = now_time_obj.getMonth() - his_time_obj.getMonth();
if( now_time_obj.getMonth() < his_time_obj.getMonth() ){
month_duration +=12;
}
if(year_duration > 1 && year_duration < 10){
return year_duration+"年前";
}
if(year_duration > 5){
return time.split("T")[0];
}
if( month_duration < 6){
return month_duration+"个月前";
}
if( month_duration < 12 ){
return "半年前";
}
},
},
methods:{
back(){
this.game.closeFrame();
},
// 获取好友申请历史记录
get_apply_friend_history(){
let self = this
self.game.check_user_login(self, () => {
let token = self.game.getdata('access_token') || self.game.getfs('access_token')
self.game.post(self, {
'method': 'Users.get_apply_friend_history',
'params': {},
'header': {
'Authorization': 'jwt ' + token
},
success(response){
let data = response.data;
if(data.result && data.result.errno === 1000){
self.friend_history_list = data.result.friend_history_list
} else {
self.game.tips(data.result.errmsg)
}
}
})
})
},
// 提交搜索信息,获取用户列表
search_user_info(){
let self = this
self.game.check_user_login(self, () => {
let token = self.game.getdata('access_token') || self.game.getfs('access_token')
self.game.post(self, {
'method': 'Users.search_user_info',
'params': {'account': self.account},
'header': {
'Authorization': 'jwt ' + token
},
success(response){
let data = response.data;
if(data.result && data.result.errno === 1000){
self.user_list = data.result.user_list
} else {
self.game.tips(data.result.errmsg)
}
}
})
})
},
change_status(){
// 状态修改
}
}
});
}
</script>
</body>
</html>
申请添加好友
客户端提供用户添加申请好友的操作菜单
搜索用户信息, 显示用户列表, 点击添加操作, 向后台请求申请添加好友操作html/add_friend.html
,代码:
<!DOCTYPE html>
<html>
<head>
<title>添加好友</title>
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta charset="utf-8">
<link rel="stylesheet" href="../static/css/main.css">
<script src="../static/js/vue.js"></script>
<script src="../static/js/axios.js"></script>
<script src="../static/js/uuid.js"></script>
<script src="../static/js/v-avatar-2.0.3.min.js"></script>
<script src="../static/js/main.js"></script>
</head>
<body>
<div class="app frame avatar update_nickname add_friend" id="app">
<div class="box">
<p class="title">添加好友</p>
<img class="close" @click="back" src="../static/images/close_btn1.png" alt="">
<div class="content">
<input class="nickname" type="text" v-model="account" placeholder="输入昵称/手机/邮箱/魔方账号....">
</div>
<div class="friends_list">
<div class="item" v-for='user in user_list'>
<div class="avatar">
<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
<div class="user_avatar">
<v-avatar v-if="user.avatar" :src="user.avatar" :size="33" :rounded="true"></v-avatar>
<v-avatar v-else-if="user.nickname" :username="user.nickname" :size="33" :rounded="true"></v-avatar>
<v-avatar v-else :username="user.id" :size="33" :rounded="true"></v-avatar>
</div>
<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
</div>
<div class="info">
<p class="username">{{user.nickname}}</p>
</div>
<div class="status" @click="apply_friend(user)">添加</div>
</div>
<div class="item" v-for='hfriend in friend_history_list'>
<!-- 自己申请好友 -->
<div v-if='hfriend.apply_user.id == user_info.id'>
<div class="avatar">
<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
<div class="user_avatar">
<v-avatar v-if="hfriend.applied_user.avatar" :src="hfriend.applied_user.avatar" :size="33" :rounded="true"></v-avatar>
<v-avatar v-else-if="hfriend.applied_user.nickname" :username="hfriend.applied_user.nickname" :size="33" :rounded="true"></v-avatar>
<v-avatar v-else :username="hfriend.applied_user.id" :size="33" :rounded="true"></v-avatar>
</div>
<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
</div>
<div class="info">
<p class="username">{{hfriend.applied_user.nickname}}</p>
<!-- 时间需要过滤显示 -->
<p class="time">{{hfriend.created_time|time_format}}</p>
</div>
<div class="status">{{hfriend.get_status[1]}}</div>
</div>
<!-- 用户被申请 -->
<div v-else>
<div class="avatar">
<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
<div class="user_avatar">
<v-avatar v-if="hfriend.apply_user.avatar" :src="hfriend.apply_user.avatar" :size="33" :rounded="true"></v-avatar>
<v-avatar v-else-if="hfriend.apply_user.nickname" :username="hfriend.apply_user.nickname" :size="33" :rounded="true"></v-avatar>
<v-avatar v-else :username="hfriend.apply_user.id" :size="33" :rounded="true"></v-avatar>
</div>
<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
</div>
<div class="info">
<p class="username">{{hfriend.apply_user.nickname}}</p>
<!-- 时间需要过滤显示 -->
<p class="time">{{hfriend.created_time|time_format}}</p>
</div>
<div class="status" v-if="hfriend.get_status[0]==1">等待审核</div>
<div class="status" v-if="hfriend.get_status[0]==2">已同意</div>
<div class="status" v-if="hfriend.get_status[0]==3">{{hfriend.get_status[1]}}</div>
<div class="status" v-if="hfriend.get_status[0]==4">{{hfriend.get_status[1]}}</div>
</div>
</div>
</div>
</div>
</div>
<script>
apiready = function(){
var game = new Game("../static/mp3/bg1.mp3");
// 在 #app 标签下渲染一个按钮组件
Vue.prototype.game = game;
new Vue({
el:"#app",
data(){
return {
account:"", // 搜索用户名
search_timer:null, // 搜索定时器标记符
user_list: [], // 搜索结果用户列表
token: "",
user_info: {}, // 当前用户信息
friend_history_list: [], // 好友申请历史列表
}
},
// 监听事件
watch:{
account(){
// 节流防抖
if(this.account.length >= 1){
// 清除定时器
clearTimeout(this.search_timer)
// 停止输入后,两秒钟发送请求
this.search_timer = setTimeout(() => {
this.search_user_info();
},2000)
}
}
},
created(){
this.token = this.game.getdata('access_token') || this.game.getfs('access_token')
this.user_info = this.game.get_user_by_token(this.token)
this.get_apply_friend_history() // 获取好友申请历史记录
},
// 过滤器
filters:{
// 时间过滤器
time_format(time){
// 计算时间距离,返回文本格式
his_time_obj = new Date(time)
now_time_obj = new Date()
duration = parseInt((now_time_obj - his_time_obj)/1000)
if(0 <= duration && duration < 60 * 5){
// 5分钟内
return '刚刚'
}
if(60 * 5 <= duration && duration < 60 * 60){
// 1小时内
return parseInt(duration/60) + '分前'
}
if(60 * 60 <= duration && duration < 60 * 60 * 24){
// 1天内
return parseInt(duration/60/60) + '小时前'
}
if(60 * 60 * 24 <= duration && duration < 60 * 60 * 24 * 30){
// 1月内
return parseInt(duration/60/60/24) + '天前'
}
// 判断月份和年份的时间距离
let year_duration = now_time_obj.getFullYear() - his_time_obj.getFullYear();
let month_duration = now_time_obj.getMonth() - his_time_obj.getMonth();
if( now_time_obj.getMonth() < his_time_obj.getMonth() ){
month_duration +=12;
}
if(year_duration > 1 && year_duration < 10){
return year_duration+"年前";
}
if(year_duration > 5){
return time.split("T")[0];
}
if( month_duration < 6){
return month_duration+"个月前";
}
if( month_duration < 12 ){
return "半年前";
}
},
},
methods:{
back(){
this.game.closeFrame();
},
// 获取好友申请历史记录
get_apply_friend_history(){
let self = this
self.game.check_user_login(self, () => {
let token = self.game.getdata('access_token') || self.game.getfs('access_token')
self.game.post(self, {
'method': 'Users.get_apply_friend_history',
'params': {},
'header': {
'Authorization': 'jwt ' + token
},
success(response){
let data = response.data;
if(data.result && data.result.errno === 1000){
self.friend_history_list = data.result.friend_history_list
} else {
self.game.tips(data.result.errmsg)
}
}
})
})
},
// 提交搜索信息,获取用户列表
search_user_info(){
let self = this
self.game.check_user_login(self, () => {
let token = self.game.getdata('access_token') || self.game.getfs('access_token')
self.game.post(self, {
'method': 'Users.search_user_info',
'params': {'account': self.account},
'header': {
'Authorization': 'jwt ' + token
},
success(response){
let data = response.data;
if(data.result && data.result.errno === 1000){
self.user_list = data.result.user_list
} else {
self.game.tips(data.result.errmsg)
}
}
})
})
},
// 用户申请添加好友
apply_friend(user){
let self = this
self.game.check_user_login(self, () => {
let token = self.game.getdata('access_token') || self.game.getfs('access_token')
self.game.post(self, {
'method': 'Users.apply_friend',
'params': {'applied_user_id': user.id},
'header': {
'Authorization': 'jwt ' + token
},
success(response){
let data = response.data;
if(data.result && data.result.errno === 1000){
self.game.tips("成功发起好友申请!请等待...");
} else {
self.game.tips(data.result.errmsg)
}
}
})
})
},
change_status(){
// 状态修改
}
}
});
}
</script>
</body>
</html>
服务端接收并处理好友申请
- 视图:
users.api
,代码:
from . import tasks # 引入定时任务
# 用户申请添加好友
@jwt_required()
@decorator.get_user_object
def apply_friend(user, applied_user_id):
'''
申请添加好友
:param user: 装饰器通过token获取的用户模型对象
:param applied_user_id: 被申请添加的用户ID
:return:
'''
# todo 1.判断两者身份关系
# 2. 添加申请好友的记录
history = services.add_apply_friend_history(user, applied_user_id)
# 3.发送延时任务,7天后没人处理的申请,自动超时
from datetime import datetime, timedelta
# 延时时间
eta = datetime.utcnow() + timedelta(seconds=5)
# 发布异步定时任务
tasks.change_history_timeout.apply_async((history.id,), eta=eta)
return {
'errno': code.CODE_OK,
'errmsg': message.ok
}
- 数据服务层:
users.services
,代码:
# 添加申请好友的历史记录
def add_apply_friend_history(user, applied_user_id):
'''
添加申请好友的历史记录
:param user: 当前登录用户模型对象
:param applied_user_id: 被申请添加好友的用户ID
:return:
'''
history = UserApplyFriendHistory(
apply_id = user.id,
applied_id = applied_user_id,
status = 1
)
db.session.add(history)
db.session.commit()
return history
# 更改好友申请记录的状态
def change_history_status(history_id, status):
'''
更改好友申请记录的状态
:param history_id: 历史记录ID
:param status: 更改的状态数值
:return:
'''
# 获取历史记录
history = UserApplyFriendHistory.query.get(history_id)
# 如果状态是待审核,则修改
if history.status == 1:
history.status = status
db.session.commit()
return history
- 添加定时异步任务, 规定时间自动超时。
users.tasks
,代码:
from application import celery
from . import services
@celery.task(name="change_history_timeout",bind=True)
def change_history_timeout(self, history_id:int):
try:
with celery.app.app_context():
# 设置超时的好友申请记录自动改变状态为 已超时
services.change_history_status(history_id, 3)
except Exception as exc:
# 发生异常,每隔3秒尝试重新执行,一共5次
self.retry(exc=exc, countdown=3, max_retries=5)
终端启动celery
celery -A manage.celery worker -l info
- 路由,
users.urls
,代码:
from application import path, api_rpc
# 引入当前蓝图应用视图 , 引入rpc视图
from . import views, api
# 蓝图路径与函数映射列表
urlpatterns = []
# rpc方法与函数映射列表[rpc接口列表]
apipatterns = [
api_rpc('check_mobile', api.check_mobile),
api_rpc('register', api.register),
api_rpc('login', api.login),
api_rpc('refresh', api.refresh_token), # 刷新access_token值
api_rpc('update_avatar', api.update_avatar), # 更新头像
api_rpc('update_nickname', api.update_nickname), # 更新昵称
api_rpc('update_mobile', api.update_mobile), # 更新手机号
api_rpc('update_password', api.update_password), # 更新登录密码
api_rpc('update_pay_password', api.update_pay_password), # 更新交易密码
api_rpc('get_apply_friend_history', api.get_apply_friend_history), # 获取好友申请列表
api_rpc('search_user_info', api.search_user_info), # 搜索用户信息
api_rpc('apply_friend', api.apply_friend), # 添加用户好友申请记录
]
好友申请状态审核
服务端提供状态更新接口
- 声明用户之间好友关系的数据模型并创建数据表。
apps.users.models
,代码:
class UserFriendShip(BaseModel):
"""用户的好友关系"""
__tablename__ = "mf_user_friendship"
apply_id = db.Column(db.Integer, comment="主动添加好友的用户ID")
applied_id = db.Column(db.Integer, comment="被添加好友的用户的ID")
apply_user = db.relationship('User', primaryjoin='User.id == UserFriendShip.apply_id', foreign_keys='UserFriendShip.apply_id', backref=backref('apply_friend_list', uselist=True), uselist=False)
applied_user = db.relationship('User', primaryjoin='User.id == UserFriendShip.applied_id', foreign_keys='UserFriendShip.applied_id', backref=backref('applied_friend_list', uselist=True), uselist=False)
def __repr__(self):
return f"<UserFriendShip {self.apply_user.nickname} {self.applied_user.nickname}>"
- 编写视图:审核好友关系
apps.users.api
,代码:
# 审核好友关系(同意/拒绝添加好友)
@jwt_required()
@decorator.get_user_object
def add_friend(user, apply_user_id, history_id, status):
'''
审核好友关系(同意/拒绝添加好友)
:param user: 装饰器通过token获取的用户模型对象
:param apply_user_id: 主动申请添加好友的用户
:param history_id: 申请好友历史记录ID
:param status: 同意/拒绝添加好友(true/false)
:return:
'''
# 如果两个用户已经是好友关系了,则不能继续审核操作
res = services.get_friendship(user.id, apply_user_id)
if res:
return {
'errno': code.CODE_ADD_FRIEND_ERROR,
'errmsg': message.add_friend_error
}
# 同意(2)或拒绝(4)添加好友状态
status = 2 if status else 4
# 1. 修改申请好友记录的状态
history = services.change_history_status(history_id, status)
# 2. 当用户同意申请以后,添加好友关系
if status == 2:
services.add_friend(history.apply_user, history.applied_user)
return {
'errno': code.CODE_OK,
'errmsg': message.ok
}
- 数据服务层,
services.user.services
,, 代码:
# 查询2个用户是否存在好友关系
def get_friendship(apply_user_id, applied_user_id):
'''
判断2个用户是否存在好友关系
:param apply_user_id: 被添加用户ID
:param applied_user_id: 主动添加用户ID
:return:
'''
instance = UserFriendShip.query.filter(
or_(
and_(UserFriendShip.apply_id == apply_user_id, UserFriendShip.applied_id == applied_user_id),
and_(UserFriendShip.apply_id == applied_user_id, UserFriendShip.applied_id == apply_user_id),
)
).first()
return instance
# 添加用户好友关系记录
def add_friend(apply_user, applied_user):
'''
添加用户好友关系记录
:param apply_user: 申请添加好友的用户
:param applied_user: 被申请添加好友的用户
:return:
'''
# 添加用户好友关系记录
friendship = UserFriendShip(
apply_user = apply_user,
applied_user = applied_user,
)
db.session.add(friendship)
db.session.commit()
return friendship
- 路由
user/urls.py
代码:
from application import path, api_rpc
# 引入当前蓝图应用视图 , 引入rpc视图
from . import views, api
# 蓝图路径与函数映射列表
urlpatterns = []
# rpc方法与函数映射列表[rpc接口列表]
apipatterns = [
api_rpc('check_mobile', api.check_mobile),
api_rpc('register', api.register),
api_rpc('login', api.login),
api_rpc('refresh', api.refresh_token), # 刷新access_token值
api_rpc('update_avatar', api.update_avatar), # 更新头像
api_rpc('update_nickname', api.update_nickname), # 更新昵称
api_rpc('update_mobile', api.update_mobile), # 更新手机号
api_rpc('update_password', api.update_password), # 更新登录密码
api_rpc('update_pay_password', api.update_pay_password), # 更新交易密码
api_rpc('get_apply_friend_history', api.get_apply_friend_history), # 获取好友申请列表
api_rpc('search_user_info', api.search_user_info), # 搜索用户信息
api_rpc('apply_friend', api.apply_friend), # 添加用户好友申请记录
api_rpc('add_friend', api.add_friend), # 添加用户好友关系记录
]
- 提示码与提示信息
application/utils/message.py
,代码:
add_friend_error = '添加好友失败!'
application/utils/code.py
,代码:
CODE_ADD_FRIEND_ERROR = 1010 # 添加好友失败
- 完成用户对好友关系的状态操作接口以后,因为我们已经创建了好友关系表,所以前面针对添加申请好友记录时候,判断用户之间的关系这块也可以完成了。
apps.users.api
,代码:
from .tasks import change_history_timeout
# 用户申请添加好友
@jwt_required()
@decorator.get_user_object
def apply_friend(user, applied_user_id):
'''
申请添加好友
:param user: 装饰器通过token获取的用户模型对象
:param applied_user_id: 被申请添加的用户ID
:return:
'''
# 1.判断两者是否为好友关系,如果有,则不能继续添加好友
res = services.get_friendship(user.id, applied_user_id)
if res:
return {
'errno': code.CODE_ADD_FRIEND_ERROR,
'errmsg': message.add_friend_error
}
# 2. 添加申请好友的记录
history = services.add_apply_friend_history(user, applied_user_id)
# 3.发送延时任务,7天后没人处理的申请,自动超时
from datetime import datetime, timedelta
# 延时时间
eta = datetime.utcnow() + timedelta(seconds=5)
# 发布异步定时任务
tasks.change_history_timeout.apply_async((history.id,), eta=eta)
return {
'errno': code.CODE_OK,
'errmsg': message.ok
}
# 完成了好友关系查询以后,todo就可以去掉了。
- 还有之前搜索用户时,也需要在获取用户时判断被搜索用户与当前用户的关系。
apps.users.marshmallow
,代码:
# 搜索用户信息构造器
class SearchUserInfoSchema(SQLAlchemyAutoSchema):
"""搜索用户信息的构造器"""
class Meta:
model = User
include_fk = False # 启用外键关系
include_relationships = True # 模型关系外部属性
# 如果要返回客户端用户模型的全部字段,就不要声明fields或exclude字段即可
fields = ["id", "name", "nickname", "avatar", "mobile"]
# 修改序列化输出字段
@post_dump
def post_dump(self, data, **kwargs):
# todo 判断搜索用户和当前用户是否为好友关系
# self.current_user 当前用户 - 构造器初始化传过来的
from application.apps.users import services
friendship = services.get_friendship(self.current_user.id, data['id'])
# 有返回值是好友关系
data['is_friend'] = friendship is not None
data['mobile'] = data["mobile"][:3] +"****" + data["mobile"][-4:]
return data
# 完成了这块功能以后,就可以把todo去掉了。
客户端发送请求更改好友申请状态
在用户进入添加好友的页面中,用户当处于被申请添加好友时,显示"等待审核",当点击了"等待审核"以后弹出菜单,可以决定是同意添加好友还是拒绝添加。
html/add_friend.html
,代码:
<!DOCTYPE html>
<html>
<head>
<title>添加好友</title>
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta charset="utf-8">
<link rel="stylesheet" href="../static/css/main.css">
<script src="../static/js/vue.js"></script>
<script src="../static/js/axios.js"></script>
<script src="../static/js/uuid.js"></script>
<script src="../static/js/v-avatar-2.0.3.min.js"></script>
<script src="../static/js/main.js"></script>
</head>
<body>
<div class="app frame avatar update_nickname add_friend" id="app">
<div class="box">
<p class="title">添加好友</p>
<img class="close" @click="back" src="../static/images/close_btn1.png" alt="">
<div class="content">
<input class="nickname" type="text" v-model="account" placeholder="输入昵称/手机/邮箱/魔方账号....">
</div>
<div class="friends_list">
<div class="item" v-for='user in user_list'>
<div class="avatar">
<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
<div class="user_avatar">
<v-avatar v-if="user.avatar" :src="user.avatar" :size="33" :rounded="true"></v-avatar>
<v-avatar v-else-if="user.nickname" :username="user.nickname" :size="33" :rounded="true"></v-avatar>
<v-avatar v-else :username="user.id" :size="33" :rounded="true"></v-avatar>
</div>
<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
</div>
<div class="info">
<p class="username">{{user.nickname}}</p>
</div>
<div class="status" @click="apply_friend(user)">添加</div>
</div>
<div class="item" v-for='hfriend in friend_history_list'>
<!-- 自己申请好友 -->
<div v-if='hfriend.apply_user.id == user_info.id'>
<div class="avatar">
<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
<div class="user_avatar">
<v-avatar v-if="hfriend.applied_user.avatar" :src="hfriend.applied_user.avatar" :size="33" :rounded="true"></v-avatar>
<v-avatar v-else-if="hfriend.applied_user.nickname" :username="hfriend.applied_user.nickname" :size="33" :rounded="true"></v-avatar>
<v-avatar v-else :username="hfriend.applied_user.id" :size="33" :rounded="true"></v-avatar>
</div>
<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
</div>
<div class="info">
<p class="username">{{hfriend.applied_user.nickname}}</p>
<!-- 时间需要过滤显示 -->
<p class="time">{{hfriend.created_time|time_format}}</p>
</div>
<div class="status">{{hfriend.get_status[1]}}</div>
</div>
<!-- 用户被申请 -->
<div v-else>
<div class="avatar">
<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
<div class="user_avatar">
<v-avatar v-if="hfriend.apply_user.avatar" :src="hfriend.apply_user.avatar" :size="33" :rounded="true"></v-avatar>
<v-avatar v-else-if="hfriend.apply_user.nickname" :username="hfriend.apply_user.nickname" :size="33" :rounded="true"></v-avatar>
<v-avatar v-else :username="hfriend.apply_user.id" :size="33" :rounded="true"></v-avatar>
</div>
<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
</div>
<div class="info">
<p class="username">{{hfriend.apply_user.nickname}}</p>
<!-- 时间需要过滤显示 -->
<p class="time">{{hfriend.created_time|time_format}}</p>
</div>
<div class="status" v-if="hfriend.get_status[0]==1" @click="change_apply(hfriend)">等待审核</div>
<div class="status" v-if="hfriend.get_status[0]==2">已同意</div>
<div class="status" v-if="hfriend.get_status[0]==3">{{hfriend.get_status[1]}}</div>
<div class="status" v-if="hfriend.get_status[0]==4">{{hfriend.get_status[1]}}</div>
</div>
</div>
</div>
</div>
</div>
<script>
apiready = function(){
var game = new Game("../static/mp3/bg1.mp3");
// 在 #app 标签下渲染一个按钮组件
Vue.prototype.game = game;
new Vue({
el:"#app",
data(){
return {
account:"", // 搜索用户名
search_timer:null, // 搜索定时器标记符
user_list: [], // 搜索结果用户列表
user_info: {}, // 当前用户信息
friend_history_list: [], // 好友申请历史列表
}
},
// 监听事件
watch:{
account(){
// 节流防抖
if(this.account.length >= 1){
// 清除定时器
clearTimeout(this.search_timer)
// 停止输入后,两秒钟发送请求
this.search_timer = setTimeout(() => {
this.search_user_info();
},2000)
}
}
},
created(){
this.get_user_info() // 获取当前用户信息
this.get_apply_friend_history() // 获取好友申请历史记录
},
// 过滤器
filters:{
// 时间过滤器
time_format(time){
// 计算时间距离,返回文本格式
his_time_obj = new Date(time)
now_time_obj = new Date()
duration = parseInt((now_time_obj - his_time_obj)/1000)
if(0 <= duration && duration < 60 * 5){
// 5分钟内
return '刚刚'
}
if(60 * 5 <= duration && duration < 60 * 60){
// 1小时内
return parseInt(duration/60) + '分前'
}
if(60 * 60 <= duration && duration < 60 * 60 * 24){
// 1天内
return parseInt(duration/60/60) + '小时前'
}
if(60 * 60 * 24 <= duration && duration < 60 * 60 * 24 * 30){
// 1月内
return parseInt(duration/60/60/24) + '天前'
}
// 判断月份和年份的时间距离
let year_duration = now_time_obj.getFullYear() - his_time_obj.getFullYear();
let month_duration = now_time_obj.getMonth() - his_time_obj.getMonth();
if( now_time_obj.getMonth() < his_time_obj.getMonth() ){
month_duration +=12;
}
if(year_duration > 1 && year_duration < 10){
return year_duration+"年前";
}
if(year_duration > 5){
return time.split("T")[0];
}
if( month_duration < 6){
return month_duration+"个月前";
}
if( month_duration < 12 ){
return "半年前";
}
},
},
methods:{
back(){
this.game.closeFrame();
},
// 获取当前用户信息
get_user_info(){
let self = this
self.game.check_user_login(self, () => {
let token = self.game.getdata('access_token') || self.game.getfs('access_token')
self.user_info = self.game.get_user_by_token(token)
})
},
// 获取好友申请历史记录
get_apply_friend_history(){
let self = this
self.game.check_user_login(self, () => {
let token = self.game.getdata('access_token') || self.game.getfs('access_token')
self.game.post(self, {
'method': 'Users.get_apply_friend_history',
'params': {},
'header': {
'Authorization': 'jwt ' + token
},
success(response){
let data = response.data;
if(data.result && data.result.errno === 1000){
self.friend_history_list = data.result.friend_history_list
} else {
self.game.tips(data.result.errmsg)
}
}
})
})
},
// 提交搜索信息,获取用户列表
search_user_info(){
let self = this
self.game.check_user_login(self, () => {
let token = self.game.getdata('access_token') || self.game.getfs('access_token')
self.game.post(self, {
'method': 'Users.search_user_info',
'params': {'account': self.account},
'header': {
'Authorization': 'jwt ' + token
},
success(response){
let data = response.data;
if(data.result && data.result.errno === 1000){
self.user_list = data.result.user_list
} else {
self.game.tips(data.result.errmsg)
}
}
})
})
},
// 用户申请添加好友
apply_friend(user){
let self = this
self.game.check_user_login(self, () => {
let token = self.game.getdata('access_token') || self.game.getfs('access_token')
self.game.post(self, {
'method': 'Users.apply_friend',
'params': {'applied_user_id': user.id},
'header': {
'Authorization': 'jwt ' + token
},
success(response){
let data = response.data;
if(data.result && data.result.errno === 1000){
self.game.tips("成功发起好友申请!请等待...");
} else {
self.game.tips(data.result.errmsg)
}
}
})
})
},
// 审核好友申请
change_apply(history){
api.actionSheet({
title: `是否同意来自${history.apply_user.nickname}的好友申请?`,
cancelTitle: '忽略',
destructiveTitle: '同意',
buttons: ['拒绝']
}, (ret, err)=>{
if(ret.buttonIndex < 3){
this.change_apply_http(history, ret.buttonIndex)
}
});
},
// 发送审核好友申请结果到服务端
change_apply_http(history, buttonIndex){
let status = buttonIndex==1?true:false;
let self = this;
self.game.check_user_login(self, () => {
let token = self.game.getdata('access_token') || self.game.getfs('access_token')
self.game.post(self, {
'method': 'Users.add_friend',
'params': {
"apply_user_id": history.apply_user.id,
"history_id": history.id,
"status": status,
},
'header': {
'Authorization': 'jwt ' + token
},
success(response){
let data = response.data;
if(data.result && data.result.errno === 1000){
self.game.tips("审核操作成功~");
// 发起全局广播通知
self.game.sendEvent('add_friend_success')
// 更改页面好友状态
if(status){
history.get_status = [2,'已同意']
}else {
history.get_status = [4,'已拒绝']
}
} else {
self.game.tips(data.result.errmsg)
}
}
})
})
}
}
});
}
</script>
</body>
</html>
用户取消自己发起的好友申请。
服务端接口
- 视图:
users.api
,代码:
# 用户取消自己发起的好友申请
@jwt_required()
@decorator.get_user_object
def cancel_apply_friend(user, history_id):
'''
用户取消自己发起的好友申请
:param user: 装饰器通过token获取的用户模型对象
:param history_id: 好友申请历史记录ID
:return:
'''
# 获取好友申请历史记录
history = services.get_apply_friend_history_by_history_id(history_id)
if history is None: # 如果没有返回值
return {
'errno': code.CODE_NO_SUCH_HISTORY,
'errmsg': message.no_such_history
}
# 取消好友申请
try:
services.cancel_apply_friend(history)
except Exception:
# 当前记录已经被处理了,返回超时[1. 对方处理, 2. 系统处理..]
return {
'errno': code.CODE_TIMEOUT,
'errmsg': message.timeout
}
return {
'errno': code.CODE_OK,
'errmsg': message.ok
}
- 数据服务层
users.services
,代码:
# 根据历史记录ID获取好友申请记录
def get_apply_friend_history_by_history_id(history_id):
'''
根据历史记录ID获取好友申请记录
:param history_id: 历史记录ID
:return:
'''
try:
history = UserApplyFriendHistory.query.get(history_id)
return history
except Exception:
return None
# 取消好友申请记录
def cancel_apply_friend(history):
'''
取消好友申请记录
:param history: 好友申请记录模型对象
:return:
'''
# 1. 对方已经处理的情况下,无法取消,提示用户
if history.status != 1:
raise Exception
# 2. 对方没有处理的情况下,直接取消,5表示取消状态
history.status = 5
db.session.commit()
- 添加路由
users.urls
,代码:
from application import path, api_rpc
# 引入当前蓝图应用视图 , 引入rpc视图
from . import views, api
# 蓝图路径与函数映射列表
urlpatterns = []
# rpc方法与函数映射列表[rpc接口列表]
apipatterns = [
api_rpc('check_mobile', api.check_mobile),
api_rpc('register', api.register),
api_rpc('login', api.login),
api_rpc('refresh', api.refresh_token), # 刷新access_token值
api_rpc('update_avatar', api.update_avatar), # 更新头像
api_rpc('update_nickname', api.update_nickname), # 更新昵称
api_rpc('update_mobile', api.update_mobile), # 更新手机号
api_rpc('update_password', api.update_password), # 更新登录密码
api_rpc('update_pay_password', api.update_pay_password), # 更新交易密码
api_rpc('get_apply_friend_history', api.get_apply_friend_history), # 获取好友申请列表
api_rpc('search_user_info', api.search_user_info), # 搜索用户信息
api_rpc('apply_friend', api.apply_friend), # 添加用户好友申请记录
api_rpc('add_friend', api.add_friend), # 添加用户好友关系记录
api_rpc('cancel_apply_friend', api.cancel_apply_friend), # 用户取消自己申请的好友记录
]
- 提示码和提示信息
提示码; application/utils/code.py
,代码:
CODE_NO_SUCH_HISTORY = 1011 # 没有历史记录
CODE_TIMEOUT = 1012 # 超时
提示信息 ; application/utils/message
,代码:
no_such_history = '没有这条历史记录'
timeout = '超时'
客户端发起取消好友申请操作
html/add_friend.html
,代码:
<!DOCTYPE html>
<html>
<head>
<title>添加好友</title>
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta charset="utf-8">
<link rel="stylesheet" href="../static/css/main.css">
<script src="../static/js/vue.js"></script>
<script src="../static/js/axios.js"></script>
<script src="../static/js/uuid.js"></script>
<script src="../static/js/v-avatar-2.0.3.min.js"></script>
<script src="../static/js/main.js"></script>
</head>
<body>
<div class="app frame avatar update_nickname add_friend" id="app">
<div class="box">
<p class="title">添加好友</p>
<img class="close" @click="back" src="../static/images/close_btn1.png" alt="">
<div class="content">
<input class="nickname" type="text" v-model="account" placeholder="输入昵称/手机/邮箱/魔方账号....">
</div>
<div class="friends_list">
<div class="item" v-for='user in user_list'>
<div class="avatar">
<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
<div class="user_avatar">
<v-avatar v-if="user.avatar" :src="user.avatar" :size="33" :rounded="true"></v-avatar>
<v-avatar v-else-if="user.nickname" :username="user.nickname" :size="33" :rounded="true"></v-avatar>
<v-avatar v-else :username="user.id" :size="33" :rounded="true"></v-avatar>
</div>
<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
</div>
<div class="info">
<p class="username">{{user.nickname}}</p>
</div>
<div class="status" @click="apply_friend(user)">添加</div>
</div>
<div class="item" v-for='hfriend in friend_history_list'>
<!-- 自己申请好友 -->
<div v-if='hfriend.apply_user.id == user_info.id'>
<div class="avatar">
<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
<div class="user_avatar">
<v-avatar v-if="hfriend.applied_user.avatar" :src="hfriend.applied_user.avatar" :size="33" :rounded="true"></v-avatar>
<v-avatar v-else-if="hfriend.applied_user.nickname" :username="hfriend.applied_user.nickname" :size="33" :rounded="true"></v-avatar>
<v-avatar v-else :username="hfriend.applied_user.id" :size="33" :rounded="true"></v-avatar>
</div>
<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
</div>
<div class="info">
<p class="username">{{hfriend.applied_user.nickname}}</p>
<!-- 时间需要过滤显示 -->
<p class="time">{{hfriend.created_time|time_format}}</p>
</div>
<!-- 用户取消自己发起的好友申请 -->
<div class="status" v-if='hfriend.get_status[0] == 1' @click='cancel_apply_friend(hfriend)'>{{hfriend.get_status[1]}}</div>
<div class="status" v-else>{{hfriend.get_status[1]}}</div>
</div>
<!-- 用户被申请 -->
<div v-else>
<div class="avatar">
<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
<div class="user_avatar">
<v-avatar v-if="hfriend.apply_user.avatar" :src="hfriend.apply_user.avatar" :size="33" :rounded="true"></v-avatar>
<v-avatar v-else-if="hfriend.apply_user.nickname" :username="hfriend.apply_user.nickname" :size="33" :rounded="true"></v-avatar>
<v-avatar v-else :username="hfriend.apply_user.id" :size="33" :rounded="true"></v-avatar>
</div>
<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
</div>
<div class="info">
<p class="username">{{hfriend.apply_user.nickname}}</p>
<!-- 时间需要过滤显示 -->
<p class="time">{{hfriend.created_time|time_format}}</p>
</div>
<div class="status" v-if="hfriend.get_status[0]==1" @click="change_apply(hfriend)">等待审核</div>
<div class="status" v-if="hfriend.get_status[0]==2">已同意</div>
<div class="status" v-if="hfriend.get_status[0]==3">{{hfriend.get_status[1]}}</div>
<div class="status" v-if="hfriend.get_status[0]==4">{{hfriend.get_status[1]}}</div>
</div>
</div>
</div>
</div>
</div>
<script>
apiready = function(){
var game = new Game("../static/mp3/bg1.mp3");
// 在 #app 标签下渲染一个按钮组件
Vue.prototype.game = game;
new Vue({
el:"#app",
data(){
return {
account:"", // 搜索用户名
search_timer:null, // 搜索定时器标记符
user_list: [], // 搜索结果用户列表
user_info: {}, // 当前用户信息
friend_history_list: [], // 好友申请历史列表
}
},
// 监听事件
watch:{
account(){
// 节流防抖
if(this.account.length >= 1){
// 清除定时器
clearTimeout(this.search_timer)
// 停止输入后,两秒钟发送请求
this.search_timer = setTimeout(() => {
this.search_user_info();
},2000)
}
}
},
created(){
this.get_user_info() // 获取当前用户信息
this.get_apply_friend_history() // 获取好友申请历史记录
},
// 过滤器
filters:{
// 时间过滤器
time_format(time){
// 计算时间距离,返回文本格式
his_time_obj = new Date(time)
now_time_obj = new Date()
duration = parseInt((now_time_obj - his_time_obj)/1000)
if(0 <= duration && duration < 60 * 5){
// 5分钟内
return '刚刚'
}
if(60 * 5 <= duration && duration < 60 * 60){
// 1小时内
return parseInt(duration/60) + '分前'
}
if(60 * 60 <= duration && duration < 60 * 60 * 24){
// 1天内
return parseInt(duration/60/60) + '小时前'
}
if(60 * 60 * 24 <= duration && duration < 60 * 60 * 24 * 30){
// 1月内
return parseInt(duration/60/60/24) + '天前'
}
// 判断月份和年份的时间距离
let year_duration = now_time_obj.getFullYear() - his_time_obj.getFullYear();
let month_duration = now_time_obj.getMonth() - his_time_obj.getMonth();
if( now_time_obj.getMonth() < his_time_obj.getMonth() ){
month_duration +=12;
}
if(year_duration > 1 && year_duration < 10){
return year_duration+"年前";
}
if(year_duration > 5){
return time.split("T")[0];
}
if( month_duration < 6){
return month_duration+"个月前";
}
if( month_duration < 12 ){
return "半年前";
}
},
},
methods:{
back(){
this.game.closeFrame();
},
// 获取当前用户信息
get_user_info(){
let self = this
self.game.check_user_login(self, () => {
let token = self.game.getdata('access_token') || self.game.getfs('access_token')
self.user_info = self.game.get_user_by_token(token)
})
},
// 获取好友申请历史记录
get_apply_friend_history(){
let self = this
self.game.check_user_login(self, () => {
let token = self.game.getdata('access_token') || self.game.getfs('access_token')
self.game.post(self, {
'method': 'Users.get_apply_friend_history',
'params': {},
'header': {
'Authorization': 'jwt ' + token
},
success(response){
let data = response.data;
if(data.result && data.result.errno === 1000){
self.friend_history_list = data.result.friend_history_list
} else {
self.game.tips(data.result.errmsg)
}
}
})
})
},
// 提交搜索信息,获取用户列表
search_user_info(){
let self = this
self.game.check_user_login(self, () => {
let token = self.game.getdata('access_token') || self.game.getfs('access_token')
self.game.post(self, {
'method': 'Users.search_user_info',
'params': {'account': self.account},
'header': {
'Authorization': 'jwt ' + token
},
success(response){
let data = response.data;
if(data.result && data.result.errno === 1000){
self.user_list = data.result.user_list
} else {
self.game.tips(data.result.errmsg)
}
}
})
})
},
// 用户申请添加好友
apply_friend(user){
let self = this
self.game.check_user_login(self, () => {
let token = self.game.getdata('access_token') || self.game.getfs('access_token')
self.game.post(self, {
'method': 'Users.apply_friend',
'params': {'applied_user_id': user.id},
'header': {
'Authorization': 'jwt ' + token
},
success(response){
let data = response.data;
if(data.result && data.result.errno === 1000){
self.game.tips("成功发起好友申请!请等待...");
} else {
self.game.tips(data.result.errmsg)
}
}
})
})
},
// 审核好友申请
change_apply(history){
api.actionSheet({
title: `是否同意来自${history.apply_user.nickname}的好友申请?`,
cancelTitle: '忽略',
destructiveTitle: '同意',
buttons: ['拒绝']
}, (ret, err)=>{
if(ret.buttonIndex < 3){
this.change_apply_http(history, ret.buttonIndex)
}
});
},
// 发送审核好友申请结果到服务端
change_apply_http(history, buttonIndex){
let status = buttonIndex==1?true:false;
let self = this;
self.game.check_user_login(self, () => {
let token = self.game.getdata('access_token') || self.game.getfs('access_token')
self.game.post(self, {
'method': 'Users.add_friend',
'params': {
"apply_user_id": history.apply_user.id,
"history_id": history.id,
"status": status,
},
'header': {
'Authorization': 'jwt ' + token
},
success(response){
let data = response.data;
if(data.result && data.result.errno === 1000){
self.game.tips("审核操作成功~");
// 发起全局广播通知
self.game.sendEvent('add_friend_success')
// 更改页面好友状态
if(status){
history.get_status = [2,'已同意']
}else {
history.get_status = [4,'已拒绝']
}
} else {
self.game.tips(data.result.errmsg)
}
}
})
})
},
// 用户取消好友申请
cancel_apply_friend(history){
api.actionSheet({
title: `是否取消申请${history.applied_user.nickname}为好友?`,
cancelTitle: '忽略',
destructiveTitle: '取消',
}, (ret, err)=>{
// this.game.print(ret.buttonIndex)
if(ret.buttonIndex == 1){
// 取消申请发送请求
this.cancel_apply_friend_http(history)
}
});
},
// 用户取消自己好友申请请求到服务端
cancel_apply_friend_http(history){
let self = this;
self.game.check_user_login(self, () => {
let token = self.game.getdata('access_token') || self.game.getfs('access_token')
self.game.post(self, {
'method': 'Users.cancel_apply_friend',
'params': {
"history_id": history.id,
},
'header': {
'Authorization': 'jwt ' + token
},
success(response){
let data = response.data;
if(data.result && data.result.errno === 1000){
self.game.tips("取消好友申请成功~");
// 更改页面好友状态
history.get_status = [5,'已取消']
} else {
self.game.tips(data.result.errmsg)
}
}
})
})
},
}
});
}
</script>
</body>
</html>
展示好友列表
服务端提供当前用户好友列表功能
- 接口视图,
users.api
,代码:
# 获取用户好友列表
@jwt_required()
@decorator.get_user_object
def get_friend_list(user):
'''
获取用户好友列表
:param user: 装饰器通过token获取的用户模型对象
:return:
'''
friend_list = services.get_friend_list(user)
return {
'errno': code.CODE_OK,
'errmsg': message.ok,
'friend_list': friend_list
}
- 因为用户好友列表往往需要进行昵称排序,所以我们需要准备一个文字转拼音模块。
# 安装文字转拼音模块
pip install xpinyin
基本用法:
from xpinyin import Pinyin
pinyin = Pinyin() # 实例拼音转换对象
# 拼音转换
ret = pinyin.get_pinyin("汉语拼音转换", tone_marks='marks')
ret1 = pinyin.get_pinyin("汉语拼音转换", tone_marks='numbers')
print(ret) # hàn-yǔ-pīn-yīn-zhuǎn-huàn
print(ret1) # han4-yu3-pin1-yin1-zhuan3-huan4
在项目入口主文件进行初始化 application/__init__.py
from xpinyin import Pinyin
# 文字转拼音初始化
pinyin = Pinyin()
- 数据服务层:
users.services
,代码:
from application import pinyin
# 获取用户的好友列表
def get_friend_list(user):
'''
获取用户的好友列表
:param user: 用户模型对象
:return:
'''
# 用户申请好友或被申请为好友信息都查出来
friends = UserFriendShip.query.filter(
or_(
UserFriendShip.apply_id == user.id,
UserFriendShip.applied_id == user.id
)
).all()
from .marshmallow import FriendShipSchema
# 实力化好友关系构造器,通过context进行参数传递在构造器内部调用
fs = FriendShipSchema(context={'user':user})
# 序列化输出
friend_list = fs.dump(friends, many=True)
# 输出数据根据名字拼音排序
data = sorted(friend_list, key=lambda item: pinyin.get_pinyin(item['nickname'], tone_marks='number'), reverse=False)
return data
- 好友关系构造器,
users.marshmallow
,代码:
# 好友关系构造器
class FriendShipSchema(SQLAlchemyAutoSchema):
'''好友关系构造器'''
# 构造器嵌套
apply_user = fields.Nested(UserSchema)
applied_user = fields.Nested(UserSchema)
class Meta:
model = UserFriendShip
include_fk = False # 启用外键关系
include_relationships = True # 模型关系外检属性
# 如果要返回客户端用户模型的全部字段,就不要声明fields或exclude字段即可
fields = ["id", "apply_user", "applied_user"]
# 加工序列化输出数据
@post_dump
def post_dump(self, data, **kwargs):
# 当前用户模型对象 - 构造器初始化传过来的
current_user = self.context.get('user')
# 判断当前用户是属于主动添加还是被动添加
if data['apply_user']['id'] == current_user.id:
'''当前好友关系属于用户主动添加的,则返回被添加的用户'''
data = data['applied_user']
else:
# 被动添加
data = data['apply_user']
return data
- 路由,
users.urls
,代码:
from application import path, api_rpc
# 引入当前蓝图应用视图 , 引入rpc视图
from . import views, api
# 蓝图路径与函数映射列表
urlpatterns = []
# rpc方法与函数映射列表[rpc接口列表]
apipatterns = [
api_rpc('check_mobile', api.check_mobile),
api_rpc('register', api.register),
api_rpc('login', api.login),
api_rpc('refresh', api.refresh_token), # 刷新access_token值
api_rpc('update_avatar', api.update_avatar), # 更新头像
api_rpc('update_nickname', api.update_nickname), # 更新昵称
api_rpc('update_mobile', api.update_mobile), # 更新手机号
api_rpc('update_password', api.update_password), # 更新登录密码
api_rpc('update_pay_password', api.update_pay_password), # 更新交易密码
api_rpc('get_apply_friend_history', api.get_apply_friend_history), # 获取好友申请列表
api_rpc('search_user_info', api.search_user_info), # 搜索用户信息
api_rpc('apply_friend', api.apply_friend), # 添加用户好友申请记录
api_rpc('add_friend', api.add_friend), # 添加用户好友关系记录
api_rpc('cancel_apply_friend', api.cancel_apply_friend), # 用户取消自己申请的好友记录
api_rpc('get_friend_list', api.get_friend_list), # 获取好友列表
]
客户端在用户进入好友列表页展示好友列表数据
- 进入页面展示好友列表数据
friend_list.html
,代码:
<!DOCTYPE html>
<html>
<head>
<title>好友列表</title>
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta charset="utf-8">
<link rel="stylesheet" href="../static/css/main.css">
<script src="../static/js/vue.js"></script>
<script src="../static/js/axios.js"></script>
<script src="../static/js/uuid.js"></script>
<script src="../static/js/v-avatar-2.0.3.min.js"></script>
<script src="../static/js/main.js"></script>
</head>
<body>
<div class="app user setting" id="app">
<div class="friends_list">
<div class="item" v-for='friend in friend_list'>
<div class="avatar">
<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
<div class="user_avatar">
<v-avatar v-if="friend.avatar" :src="friend.avatar" :size="55" :rounded="true"></v-avatar>
<v-avatar v-else-if="friend.nickname" :username="friend.nickname" :size="55" :rounded="true"></v-avatar>
<v-avatar v-else :username="friend.id" :size="55" :rounded="true"></v-avatar>
</div>
<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
</div>
<div class="info">
<p class="username">{{friend.nickname}}</p>
<p class="fruit">果子: {{game.credit_format(friend.credit)}}</p>
</div>
<div class="behavior pick">摘</div>
<div class="goto"><img src="../static/images/arrow1.png" alt=""></div>
</div>
<div class="item">
<div class="avatar">
<img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
<img class="user_avatar" src="../static/images/avatar.png" alt="">
<img class="avatar_border" src="../static/images/avatar_border.png" alt="">
</div>
<div class="info">
<p class="username">长昵称都很好</p>
<p class="fruit">果子:9,999.00</p>
</div>
<div class="behavior protect">护</div>
<div class="goto"><img src="../static/images/arrow1.png" alt=""></div>
</div>
</div>
</div>
<script>
apiready = function() {
var game = new Game("../static/mp3/bg1.mp3");
// 在 #app 标签下渲染一个按钮组件
Vue.prototype.game = game;
new Vue({
el: "#app",
data() {
return {
friend_list: [], // 好友列表
page: 1,
prev: {
name: "",
url: "",
params: {}
},
current: {
name: "friend_list",
url: "friend_list.html",
params: {}
},
}
},
created() {
// 获取好友列表
this.get_friend_list();
// 刷新好友列表
this.refresh_friend_list();
// 监听事件
this.listen()
},
methods: {
// 监听事件
listen() {
// 监听其他页面进行的添加好友操作通知,获取最新好友列表
this.listen_add_friend();
},
listen_add_friend() {
api.addEventListener({
name: 'add_friend_success'
}, (ret, err) => {
this.get_friend_list()
});
},
// 获取好友列表
get_friend_list() {
let self = this
// 检测token是否过期,刷新token值
self.game.check_user_login(self, () => {
let token = self.game.getdata('access_token') || self.game.getfs('access_token')
// 向服务端发送请求,或好友列表
self.game.post(self, {
'method': 'Users.get_friend_list',
'params': {},
'header': {
'Authorization': 'jwt ' + token
},
success(response) {
let data = response.data
if (data.result && data.result.errno === 1000) {
self.game.print(data.result.friend_list)
// 请求成功,获取好友列表
self.friend_list = data.result.friend_list
} else {
self.game.tips(data.result.errmsg)
}
}
})
})
},
// 下拉刷新好友列表
refresh_friend_list() {
// 下拉刷新好友列表
api.setRefreshHeaderInfo({
loadingImg: 'widget://image/refresh.png',
bgColor: null,
textColor: '#fff',
textDown: '下拉刷新...',
textUp: '松开刷新...'
}, (ret, err) => {
// 在这里从服务器加载数据,刷新好友列表
this.get_friend_list()
// 加载完成后调用api.refreshHeaderLoadDone()方法恢复组件到默认状态
setTimeout(() => {
api.refreshHeaderLoadDone();
}, 1500);
});
},
goto_home() {
// 退出当前页面
this.game.closeFrame();
},
}
});
}
</script>
</body>
</html>
static/js/main.js
添加果子积分格式转换(英文格式数字 - 123,45.67)
class Game{
// ...省略
// 打印数据方法更改
print(data, st = false) {
// 打印数据
data = JSON.stringify(data);
if (st) {
api.alert({
"msg": data
});
} else {
console.log(data);
}
}
// ...省略
// 果子格式化
credit_format(credit){
return credit.toLocaleString('en-US')
}
}