This commit is contained in:
2022-02-11 15:29:12 +08:00
78 changed files with 4192 additions and 3872 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@@ -14,6 +14,10 @@
{ {
"launchtype" : "local" "launchtype" : "local"
}, },
"mp-weixin" :
{
"launchtype" : "local"
},
"type" : "uniCloud" "type" : "uniCloud"
} }
] ]

View File

@@ -11,6 +11,13 @@ import {
// 获取好友列表 // 获取好友列表
const getFriends = () => { const getFriends = () => {
return request({
url: 'im/friends',
}, true)
}
// 获取好友列表
const getFriendsLetter = () => {
return request({ return request({
url: 'im/friends/letter', url: 'im/friends/letter',
}, true) }, true)
@@ -85,62 +92,98 @@ const pedingFriend = (recipient) => {
}) })
} }
/** // 以下是群组相关业务的接口
* 好友申请数量 const getMyGroups = () => {
*/
const getPendingCount = () => {
return request({ return request({
url: 'im/friends/pending/count' url: 'im/groups'
}) })
} }
/** /**
* 上传聊天附件 * 获取群信息,包含基础信息和 14 个用户
* 图片
* 语音
* 视频
*/ */
// 基础配置 const getGroupInfo = (groupId) => {
const config = { return request({
apiUrl: 'http://api.zh.shangkelian.cn/api/', // 正式环境 url: 'im/groups/' + groupId
timeout: 60000
}
const uploadMessageFile = (file, type) => {
config.header = {
'Accept': 'application/json',
'Authorization': store.getters.getToken || ''
}
return new Promise((resolve, reject) => {
uni.uploadFile({
url: config.apiUrl + 'im/upload',
filePath: file,
name: 'upload',
formData: {
type
},
header: config.header || {},
success: (res) => {
if (res.statusCode === 200) {
let updData = JSON.parse(res.data)
if (updData.status_code === 200) {
resolve(updData.data)
} else {
reject(updData)
}
}
},
fail: (err) => {
console.log('ERR', err);
}
})
}) })
} }
const getGroupUsers = (groupId) => {
return request({
url: 'im/groups/' + groupId + '/users'
})
}
const getGroupAnnouncements = (groupId) => {
return request({
url: 'im/groups/' + groupId + '/announcements'
})
}
const createGroupAnnouncement = (groupId, content) => {
return request({
method: 'POST',
url: 'im/groups/' + groupId + '/announcements',
data: {
content: content
}
})
}
const deleteGroupAnnouncement = (groupId, announcementId) => {
return request({
method: 'DELETE',
url: 'im/groups/' + groupId + '/announcements/' + announcementId
})
}
/**
* 创建群聊
*/
const createGroup = (data) => {
return request({
method: 'POST',
url: 'im/groups',
data: data
})
}
const updateGroup = (groupId, data) => {
return request({
method: 'PUT',
url: 'im/groups/' + groupId,
data: data
})
}
/**
* 搜索群聊
*/
const searchGroup = (name) => {
return request({
url: 'im/groups/search?name=' + name
})
}
const joinGroup = (groupId) => {
return request({
method: 'POST',
url: 'im/groups/' + groupId + '/join'
})
}
const quitGroup = (groupId) => {
return request({
method: 'POST',
url: 'im/groups/' + groupId + '/quit'
})
}
export { export {
getImToken, getImToken,
deleteFriend, deleteFriend,
getFriends, getFriends,
getFriendsLetter,
getUserInfo, getUserInfo,
getFriendInfo, getFriendInfo,
getPedings, getPedings,
@@ -148,6 +191,15 @@ export {
rejectFriend, rejectFriend,
searchFriend, searchFriend,
pedingFriend, pedingFriend,
getPendingCount, getMyGroups,
uploadMessageFile createGroup,
updateGroup,
getGroupInfo,
getGroupUsers,
getGroupAnnouncements,
createGroupAnnouncement,
deleteGroupAnnouncement,
searchGroup,
joinGroup,
quitGroup
} }

33
lib/emoji.js Normal file
View File

@@ -0,0 +1,33 @@
export default [
"😀", "😁", "😃", "😄", "😅", "😆", "😉", "😊", "😋", "😎", "😍",
"😘", "😗", "😙", "😚", "☺", "😇", "😐", "😑", "😶", "😏", "😣", "😥", "😮", "😯", "😪",
"😫", "😴", "😌", "😛", "😜", "😝", "😒", "😓", "😔", "😕", "😲", "😷", "😖", "😞", "😟",
"😤", "😢", "😭", "😦", "😧", "😨", "😬", "😰", "😱", "😳", "😵", "😡", "😠",
"👦", "👧", "👨", "👩", "👴", "👵", "👶", "👱", "👮", "👲", "👳", "👷", "👸", "💂", "🎅", "👰", "👼",
"💆", "💇", "🙍", "🙎", "🙅", "🙆", "💁", "🙋", "🙇", "🙌", "🙏", "👤", "👥", "🚶", "🏃", "👯",
"💃", "👫", "👬", "👭", "💏", "💑", "👪", "💪", "👈", "👉", "☝", "👆", "👇", "✌", "✋", "👌",
"👍", "👎", "✊", "👊", "👋", "👏", "👐", "✍", "👣", "👀", "👂", "👃", "👅", "👄", "💋", "👓",
"👔", "👙", "👛", "👜", "👝", "🎒", "💼", "👞", "👟", "👠", "👡", "👢", "👑",
"👒", "🎩", "🎓", "💄", "💅", "💍", "🌂", "📶", "📳", "📴", "♻", "🏧","🚮", "🚰", "♿", "🚹", "🚺",
"🚻", "🚼", "🚾", "⚠", "🚸", "⛔", "🚫", "🚳", "🚭", "🚯", "🚱", "🚷", "🔞", "💈",
"🙈", "🐒", "🐶", "🐕", "🐩", "🐺", "🐱","🐈", "🐯", "🐅", "🐆", "🐴", "🐎", "🐮", "🐂",
"🐃","🐄","🐷","🐖","🐗","🐽","🐏","🐑","🐐","🐪","🐫","🐘","🐭",
"🐁","🐀","🐹","🐰","🐇","🐻","🐨","🐼","🐾","🐔","🐓","🐣","🐤","🐥",
"🐦", "🐧", "🐸", "🐊","🐢", "🐍", "🐲", "🐉", "🐳", "🐋", "🐬", "🐟", "🐠", "🐡",
"🐙", "🐚", "🐌", "🐛", "🐜", "🐝", "🐞", "🦋", "💐", "🌸", "💮", "🌹", "🌺",
"🌻", "🌼", "🌷", "🌱", "🌲", "🌳", "🌴", "🌵", "🌾", "🌿", "🍀", "🍁", "🍂", "🍃",
"🌍","🌎","🌏","🌐","🌑","🌒","🌓","🌔","🌕","🌖","🌗","🌘","🌙","🌚",
"🌛","🌜","☀","🌝","🌞","⭐","🌟","🌠","☁","⛅","☔","⚡","❄","🔥","💧","🌊",
"🏀", "🏈", "🏉", "🎾", "🎱", "🎳", "⛳", "🎣", "🎽", "🎿",
"😈", "👿", "👹", "👺", "💀", "☠", "👻", "👽", "👾", "💣",
"🌋", "🗻", "🏠", "🏡", "🏢", "🏣", "🏤", "🏥", "🏦", "🏨",
"⛲", "🌁", "🌃", "🌆", "🌇", "🎠", "🎡", "🎢", "🚂",
"🚌", "🚍", "🚎", "🚏", "🚐", "🚑", "🚒", "🚓", "🚔", "🚕", "🚖", "🚗", "🚘",
"💌", "💎", "🔪", "💈", "🚪", "🚽", "🚿", "🛁", "⌛", "⏳", "⌚", "⏰", "🎈", "🎉",
"💤", "💢", "💬", "💭", "♨", "🌀", "🔔", "🔕", "✡", "✝", "🔯", "📛", "🔰", "🔱", "⭕", "✅",
"☑", "✔", "✖", "❌", "❎", "", "", "➗", "➰", "➿", "〽", "✳", "✴", "❇", "‼", "⁉", "❓", "❔", "❕", "❗",
"🕛", "🕧", "🕐", "🕜", "🕑", "🕝", "🕒", "🕞", "🕓", "🕟", "🕔", "🕠", "🕕", "🕡",
"🕖", "🕢", "🕗", "🕣", "🕘", "🕤", "🕙", "🕥", "🕚", "🕦", "⏱", "⏲", "🕰",
"💘", "❤", "💓", "💔", "💕", "💖", "💗", "💙", "💚", "💛", "💜", "💝", "💞", "💟❣",
"🍇", "🍈", "🍉", "🍊", "🍋", "🍌", "🍍", "🍎", "🍏", "🍐", "🍑", "🍒", "🍓",
]

View File

@@ -2,8 +2,8 @@
"name" : "ZH-HEALTH", "name" : "ZH-HEALTH",
"appid" : "__UNI__C29473D", "appid" : "__UNI__C29473D",
"description" : "ZH-HEALTH您手上的健康管理专家", "description" : "ZH-HEALTH您手上的健康管理专家",
"versionName" : "1.0.7", "versionName" : "1.0.8",
"versionCode" : 107, "versionCode" : 108,
"transformPx" : false, "transformPx" : false,
/* 5+App */ /* 5+App */
"app-plus" : { "app-plus" : {
@@ -22,7 +22,9 @@
"Payment" : {}, "Payment" : {},
"Share" : {}, "Share" : {},
"SQLite" : {}, "SQLite" : {},
"VideoPlayer" : {} "VideoPlayer" : {},
"Geolocation" : {},
"Fingerprint" : {}
}, },
/* */ /* */
"distribute" : { "distribute" : {
@@ -78,7 +80,17 @@
} }
}, },
"ad" : {}, "ad" : {},
"push" : {} "push" : {},
"geolocation" : {
"amap" : {
"__platform__" : [ "android" ],
"appkey_ios" : "",
"appkey_android" : "05b7f32ca9c897c8b63c505d92cd654b"
},
"system" : {
"__platform__" : [ "android" ]
}
}
}, },
"icons" : { "icons" : {
"android" : { "android" : {

View File

@@ -4,8 +4,6 @@
"description": "ZH健康", "description": "ZH健康",
"main": "main.js", "main": "main.js",
"dependencies": { "dependencies": {
"bitcore-lib": "^8.25.25",
"bitcore-mnemonic": "^8.25.25",
"moment": "^2.29.1", "moment": "^2.29.1",
"uni-read-pages": "^1.0.5", "uni-read-pages": "^1.0.5",
"uni-simple-router": "^2.0.7", "uni-simple-router": "^2.0.7",

View File

@@ -266,7 +266,7 @@
"path": "pages/sign/index", "path": "pages/sign/index",
"name": "signIndex", "name": "signIndex",
"style": { "style": {
"navigationBarTitleText": "每日签到", "navigationBarTitleText": "每日打卡",
"navigationBarBackgroundColor": "#FFFFFF" "navigationBarBackgroundColor": "#FFFFFF"
} }
}, },
@@ -367,28 +367,16 @@
"path": "pages/im/index", "path": "pages/im/index",
"name": "IM", "name": "IM",
"style": { "style": {
"navigationBarBackgroundColor": "#FFFFFF",
"navigationStyle": "custom" "navigationStyle": "custom"
} }
}, },
{ {
"path": "pages/im/private/index", "path": "pages/im/private/chat",
"name": "imPrivate",
"style": { "style": {
"navigationBarTitleText": "聊天",
"navigationBarBackgroundColor": "#F3F6FB",
"disableScroll": true, "disableScroll": true,
"app-plus": { "navigationBarTitleText": "聊天",
"titleNView": { "enablePullDownRefresh": false,
"type": "default", "navigationBarBackgroundColor": "#F3F6FB"
"buttons": [{
"float": "right",
"fontSrc": "/static/iconfont.ttf",
"text": "\ue607",
"fontSize": "20px"
}]
}
}
} }
}, },
{ {
@@ -420,24 +408,21 @@
"path": "pages/im/friends/pending", "path": "pages/im/friends/pending",
"name": "imFriendsPending", "name": "imFriendsPending",
"style": { "style": {
"navigationBarTitleText": "新朋友", "navigationBarTitleText": "新朋友"
"navigationBarBackgroundColor": "#F3F6FB"
} }
}, },
{ {
"path": "pages/im/friends/search", "path": "pages/im/friends/search",
"name": "SearchFriend", "name": "SearchFriend",
"style": { "style": {
"navigationBarTitleText": "好友搜索", "navigationBarTitleText": "好友搜索"
"navigationBarBackgroundColor": "#F3F6FB"
} }
}, },
{ {
"path": "pages/im/friends/info", "path": "pages/im/friends/info",
"name": "imFriendsInfo", "name": "imFriendsInfo",
"style": { "style": {
"navigationBarTitleText": "好友资料", "navigationBarTitleText": "用户资料"
"navigationBarBackgroundColor": "#FFFFFF"
} }
}, },
{ {
@@ -451,9 +436,84 @@
}, },
{ {
"path": "pages/im/group/index", "path": "pages/im/group/index",
"name": "imGroup", "name": "imGroups",
"style": { "style": {
"navigationBarTitleText": "我的群聊" "navigationBarTitleText": "我的群聊",
"app-plus": {
"titleNView": {
"type": "default",
"buttons": [{
"float": "right",
"fontSrc": "/static/iconfont.ttf",
"text": "\ue60a",
"fontSize": "20px"
}]
}
}
}
},
{
"path": "pages/im/group/chat",
"name": "imGroupChat",
"style": {
"navigationBarTitleText": "群聊",
"app-plus": {
"titleNView": {
"type": "default",
"buttons": [{
"float": "right",
"fontSrc": "/static/iconfont.ttf",
"text": "\ue607",
"fontSize": "20px"
}]
}
}
}
},
{
"path": "pages/im/group/info",
"name": "imGroupInfo",
"style": {
"navigationBarTitleText": "群信息"
}
},
{
"path": "pages/im/group/create",
"name": "imGroupCreate",
"style": {
"navigationBarTitleText": "创建群聊"
}
},
{
"path": "pages/im/group/users",
"name": "imGroupUsers",
"style": {
"navigationBarTitleText": "群成员"
}
},
{
"path": "pages/im/group/announcement",
"name": "imGroupAnnouncement",
"style": {
"navigationBarTitleText": "群公告",
"app-plus": {
"titleNView": {
"type": "default",
"buttons": [{
"float": "right",
"fontSrc": "/static/iconfont.ttf",
"text": "\ue60a",
"fontSize": "20px"
}]
}
}
}
},
{
"path": "pages/im/group/announceCreate",
"name": "imGroupAnnouncementCreate",
"style": {
"navigationBarTitleText": "发布群公告"
} }
}, },
{ {
@@ -566,14 +626,6 @@
"navigationBarBackgroundColor": "#34CE98", "navigationBarBackgroundColor": "#34CE98",
"navigationBarTextStyle": "white" "navigationBarTextStyle": "white"
} }
}, {
"path": "pages/im/private/chat",
"style": {
"disableScroll": true,
"navigationBarTitleText": "聊天",
"enablePullDownRefresh": false,
"navigationBarBackgroundColor": "#F3F6FB"
}
} }
], ],
"tabBar": { "tabBar": {

BIN
pages/.DS_Store vendored

Binary file not shown.

View File

@@ -0,0 +1,163 @@
<template>
<view>
<view v-for="(item, index) in conversations" :key="index" :class="['message', { 'is-top': item.isTop }]"
:data-item="item" @longpress="onLongPress" @click="toDetail(item)">
<message-cell :item="item" />
</view>
<view class="shade" @click="hidePop" v-show="showPop">
<view class="pop" :style="popStyle" :class="{'show':showPop}">
<view v-for="(item, index) in popButton" :key="index" @click="pickerMenu" :data-index="index">
{{item}}
</view>
</view>
</view>
</view>
</template>
<script>
import * as RongIMLib from '@/uni_modules/RongCloud-IMWrapper/js_sdk/index'
import im from '@/utils/im/index.js'
import messageCell from './messageCell'
export default {
props: {
conversations: {
type: Array,
default: function() {
return []
}
}
},
components: {
messageCell
},
data() {
return {
/* 窗口尺寸 */
winSize: {},
/* 显示操作弹窗 */
showPop: false,
/* 弹窗按钮列表 */
popButton: ['置顶聊天', '删除该聊天'],
/* 弹窗定位样式 */
popStyle: "",
pickedItem: {},
}
},
methods: {
// 隐藏功能菜单
hidePop() {
this.showPop = false
this.pickedItem = {}
setTimeout(() => {
this.showShade = false
}, 250)
},
// 点击会话功能菜单
pickerMenu(e) {
const index = Number(e.currentTarget.dataset.index)
if (index == 0) {
RongIMLib.setConversationToTop(this.pickedItem.conversationType, this.pickedItem.targetId, !this
.pickedItem.isTop)
} else {
RongIMLib.removeConversation(this.pickedItem.conversationType, this.pickedItem.targetId)
RongIMLib.deleteMessages(this.pickedItem.conversationType, this.pickedItem.targetId)
}
this.$emit('refresh')
im.setNotifyBadge()
this.hidePop()
},
// 长按会话,展示功能菜单
onLongPress(e) {
let [touches, style, item] = [e.touches[0], "", e.currentTarget.dataset.item]
if (touches.clientY > (this.winSize.height / 2)) {
style = `bottom:${this.winSize.height-touches.clientY}px;`
} else {
style = `top:${touches.clientY}px;`
}
if (touches.clientX > (this.winSize.witdh / 2)) {
style += `right:${this.winSize.witdh-touches.clientX}px`
} else {
style += `left:${touches.clientX}px`
}
this.popButton[0] = item.isTop ? '取消置顶' : '置顶聊天'
this.popStyle = style
this.pickedItem = item
this.$nextTick(() => {
setTimeout(() => {
this.showPop = true;
}, 10)
})
},
toDetail(item) {
if (item.conversationType == 1) {
uni.navigateTo({
url: '/pages/im/private/chat?targetId=' + item.targetId
})
} else if (item.conversationType == 3) {
uni.navigateTo({
url: '/pages/im/group/chat?targetId=' + item.targetId
})
}
this.hidePop()
}
}
}
</script>
<style lang="scss" scoped>
.message {
background: white;
&.is-top {
background: $window-color;
}
}
/* 遮罩 */
.shade {
position: fixed;
width: 100%;
height: 100%;
.pop {
position: fixed;
z-index: 101;
width: 200rpx;
box-sizing: border-box;
font-size: 28rpx;
text-align: left;
color: #333;
background-color: #fff;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
line-height: 80rpx;
transition: transform 0.15s ease-in-out 0s;
user-select: none;
-webkit-touch-callout: none;
transform: scale(0, 0);
&.show {
transform: scale(1, 1);
}
&>view {
padding: 0 20rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
user-select: none;
-webkit-touch-callout: none;
&:active {
background-color: #f3f3f3;
}
}
}
}
</style>

View File

@@ -0,0 +1,112 @@
<template>
<view class="apply--cell u-border-bottom">
<view class="avatar">
<u-avatar :src="user.portraitUrl" shape="square" size="46" />
</view>
<view class="info">
<view class="name">
{{ user.name }}
</view>
<view class="message">
{{ message.message }}
</view>
</view>
<view class="action">
<u-button type="success" size="mini" @click="resolve">通过</u-button>
<u-button type="warning" size="mini" @click="reject">拒绝</u-button>
</view>
</view>
</template>
<script>
import * as RongIMLib from '@/uni_modules/RongCloud-IMWrapper/js_sdk/index'
import {
resolveFriend,
rejectFriend
} from '@/apis/interfaces/im.js'
export default {
props: {
message: {
type: Object,
default: {}
}
},
computed: {
user() {
return JSON.parse(this.message.extra)
}
},
methods: {
resolve() {
resolveFriend(this.message.sourceUserId).then(res => {
this.clearMessages()
uni.showToast({
icon: 'none',
title: '通过好友申请'
})
}).catch(err => {
uni.showToast({
icon: 'none',
title: err.message
})
})
},
reject() {
uni.showModal({
title: '拒绝申请',
success: (res) => {
if (res.confirm) {
rejectFriend(this.message.sourceUserId).then(res => {
this.clearMessages()
}).catch(err => {
uni.showToast({
icon: 'none',
title: err.message
})
})
}
}
})
},
// 不管是通过还是拒绝,都要把相关的信息清理
clearMessages() {
RongIMLib.deleteMessages(RongIMLib.ConversationType.SYSTEM, this.message.sourceUserId)
this.$emit('success')
uni.$emit('onContactNotification')
}
}
}
</script>
<style lang="scss" scoped>
.apply--cell {
display: flex;
padding: $padding;
align-items: center;
.info {
flex: 1;
margin-left: $padding;
.name {
font-size: $title-size + 2;
}
.message {
color: $text-gray-m;
font-size: $title-size-m;
margin-top: 10rpx;
}
}
.action {
justify-content: space-between;
.u-button+.u-button {
margin-top: 10rpx;
}
}
}
</style>

View File

@@ -1,15 +1,14 @@
<template> <template>
<view class="friend-apply"> <view class="friend-apply">
<block v-for="item in lists" v-if="lists.length > 0" :key="item.userId"> <block v-for="(item, index) in lists" v-if="lists.length > 0" :key="index">
<view class="lists"> <view class="lists">
<view class="" style="width: 100rpx;height: 100rpx;"> <view class="" style="width: 100rpx;height: 100rpx;">
<u-image class="cover" radius="4" width="100rpx" height="100rpx" :src="item.portraitUrl || require('@/static/user/cover.png')" :lazy-load="true" /> <u-avatar :src="JSON.parse(item.latestMessage.extra).portraitUrl" shape="square" size="44" />
</view> </view>
<view class="right"> <view class="right">
<view class="title"> <view class="title">
<view class="name">{{ item.name }}</view> <view class="name">{{ item.name }}</view>
<view class="des" v-if="isApply">{{ item.address || '这家伙很懒什么都没有添加~' }}</view> <view class="des">{{ item.latestMessage.message }}</view>
<view class="des" v-else>{{ item.remark || '你好,听说你很优秀想认识~' }}</view>
</view> </view>
<view class="agress-btn"> <view class="agress-btn">
<span v-if="isAgree" @click="action('agree', item)">通过</span> <span v-if="isAgree" @click="action('agree', item)">通过</span>
@@ -44,8 +43,8 @@ export default {
default: false default: false
} }
}, },
data() { created() {
return {}; console.log(this.lists);
}, },
methods: { methods: {
action(type, item) { action(type, item) {

View File

@@ -0,0 +1,85 @@
<template>
<view class="message--cell">
<view class="avatar">
<u-badge max="99" shape="horn" absolute :offset="[-5, -8]" :value="item.unreadMessageCount" />
<u-avatar :src="contact(item.targetId).portraitUrl" shape="square" size="44" />
</view>
<view class="content">
<view class="header">
<view class="name">{{ contact(item.targetId).name }}</view>
<view class="time">{{ item.sentTime|timeCustomCN }}</view>
</view>
<message-preview class="preview" :msg="item.latestMessage" :conversationType="item.conversationType"
:user="item.latestMessage.userInfo" />
</view>
</view>
</template>
<script>
import messagePreview from './messagePreview'
export default {
props: {
item: {
type: Object,
default: function() {
return {}
}
}
},
computed: {
contact() {
return function(targetId) {
return this.$store.getters.contactInfo(targetId)
}
}
},
components: {
messagePreview
}
}
</script>
<style lang="scss" scoped>
.message--cell {
display: flex;
padding: 20rpx 0 0 20rpx;
.avatar {
position: relative;
.u-badge {
z-index: 998;
}
}
.content {
margin-left: 30rpx;
box-sizing: border-box;
position: relative;
flex: 1;
border-bottom-width: 0.5px !important;
border-color: $u-border-color !important;
border-bottom-style: solid;
.header {
display: flex;
justify-content: space-between;
.name {
font-size: $title-size + 2;
color: #454545;
color: #454545;
}
.time {
font-size: $title-size-sm;
color: $text-gray-m;
position: absolute;
right: 30rpx;
}
}
}
}
</style>

View File

@@ -0,0 +1,65 @@
<template>
<view>
<view class="preview" v-if="msg.objectName=='RC:TxtMsg'">
<text v-if="conversationType == 3">{{ user.name }}: </text>{{ msg.content || '' }}
</view>
<view class="preview" v-if="msg.objectName=='RC:HQVCMsg'">
<text v-if="conversationType == 3">{{ user.name }}: </text>[语音]
</view>
<view class="preview" v-if="msg.objectName=='RC:ImgMsg'">
<text v-if="conversationType == 3">{{ user.name }}: </text>[图片]
</view>
<view class="preview" v-if="msg.objectName=='RC:GIFMsg'">
<text v-if="conversationType == 3">{{ user.name }}: </text>[表情]
</view>
<view class="preview" v-if="msg.objectName=='RC:FileMsg'">
<text v-if="conversationType == 3">{{ user.name }}: </text>[文件]
</view>
<view class="preview" v-if="msg.objectName=='RC:LBSMsg'">
<text v-if="conversationType == 3">{{ user.name }}: </text>[位置]
</view>
<view class="preview" v-if="msg.objectName=='RC:AudioMsg'">
<text v-if="conversationType == 3">{{ user.name }}: </text>[语音通话]
</view>
<view class="preview" v-if="msg.objectName=='RC:VideoMsg'">
<text v-if="conversationType == 3">{{ user.name }}: </text>[视频通话]
</view>
</view>
</template>
<script>
export default {
props: {
msg: {
type: Object,
default: {}
},
conversationType: {
type: Number,
default: 0
},
user: {
type: Object,
default: function() {
return {
name: ''
}
}
}
}
}
</script>
<style lang="scss" scoped>
.preview {
word-break: break-all;
color: $text-gray-m;
padding-top: $padding - 20;
padding-bottom: $padding;
font-size: $title-size-m;
height: 32rpx;
line-height: 32rpx;
width: 520rpx;
@extend .nowrap;
}
</style>

View File

@@ -0,0 +1,83 @@
<template>
<view class="">
<!-- footer -->
<view class="footer">
<view class="msg-type" @click="changeMessageType">
<image class="icon" src="@/static/icon/key-icon.png" v-if="chatType === 0" mode="widthFix">
</image>
<image class="icon" src="@/static/icon/msg-icon.png" v-if="chatType === 1" mode="widthFix">
</image>
</view>
<sent-voice v-if="chatType === 0" :conversationType="conversationType" :targetId="targetId"
@success="onSuccess" />
<sent-text v-if="chatType === 1" :conversationType="conversationType" :targetId="targetId"
@success="onSuccess" />
<view class="msg-type msg-popups" @click="showPopups = !showPopups">
<image class="icon" src="@/static/icon/popups-icon.png"></image>
</view>
</view>
<!-- 弹出层 -->
<sent-popups :show="showPopups" :conversationType="conversationType" :targetId="targetId"
@success="() => {showPopups = false, onSuccess()}"></sent-popups>
</view>
</template>
<script>
import sentText from '../components/sentText'
import sentVoice from '../components/sentVoice'
import sentPopups from '../components/sentPopups'
export default {
props: {
conversationType: {
type: Number,
default: 0
},
targetId: {
type: String,
default: ''
}
},
components: {
sentText,
sentVoice,
sentPopups
},
data() {
return {
chatType: 1, // 0 语音1 文本
showPopups: false
}
},
methods: {
// 切换聊天类型,语音/文本
changeMessageType() {
this.chatType = this.chatType === 1 ? 0 : 1
},
onSuccess() {
this.$emit('onSuccess')
}
}
}
</script>
<style lang="scss" scoped>
.footer {
background: white;
padding: 20rpx 30rpx;
display: flex;
justify-content: space-between;
flex-direction: row;
.msg-type {
width: 70rpx;
height: 70rpx;
.icon {
margin: 5rpx;
width: 60rpx;
height: 60rpx;
}
}
}
</style>

View File

@@ -0,0 +1,173 @@
<template>
<view class="sent--popups u-border-top" v-if="show">
<view class="item" @click="onPopupsItem('picture')">
<image class="icon" src="@/static/icon/popups-icon-00.png" mode="widthFix"></image>
<text class="text">相册</text>
</view>
<view class="item" @click="onPopupsItem('camera')">
<image class="icon" src="@/static/icon/popups-icon-01.png" mode="widthFix"></image>
<text class="text">拍摄</text>
</view>
<view class="item" @click="onPopupsItem('video')">
<image class="icon" src="@/static/icon/popups-icon-02.png" mode="widthFix"></image>
<text class="text">视频通话</text>
</view>
<view class="item" @click="onPopupsItem('location')">
<image class="icon" src="@/static/icon/popups-icon-03.png" mode="widthFix"></image>
<text class="text">位置</text>
</view>
<view class="item" @click="onPopupsItem('redpacket')">
<image class="icon" src="@/static/icon/popups-icon-04.png" mode="widthFix"></image>
<text class="text">红包</text>
</view>
<view class="item" @click="onPopupsItem('file')">
<image class="icon" src="@/static/icon/popups-icon-05.png" mode="widthFix"></image>
<text class="text">文件</text>
</view>
<u-action-sheet :actions="callActions" cancelText="取消" @close="callShow = false" @select="singleCall"
:show="callShow">
</u-action-sheet>
</view>
</template>
<script>
import im from '@/utils/im/index.js'
export default {
data() {
return {
callActions: [{
type: 0,
name: '语音通话'
},
{
type: 1,
name: '视频通话'
}
],
callShow: false
}
},
props: {
show: {
type: Boolean,
default: false
},
conversationType: {
type: Number,
default: 0
},
targetId: {
type: String,
default: ''
}
},
computed: {
user() {
return this.$store.getters.sender
}
},
methods: {
singleCall(e) {
uni.showToast({
icon: 'none',
title: '功能正在开发中'
})
// CallLib.startSingleCall(this.targetId, e.type, '');
// uni.redirectTo({
// url: '/pages/im/private/call?targetId=' + this.targetId + '&mediaType=' + e.type
// })
},
onPopupsItem(type) {
switch (type) {
case 'picture':
uni.chooseImage({
count: 9,
sourceType: ['album'],
success: res => {
im.sentImage(this.conversationType, this.targetId, res.tempFilePaths[0],
this.user, (
res) => {
this.success()
})
}
})
break;
case 'camera':
uni.chooseImage({
sourceType: ['camera'],
success: res => {
im.sentImage(this.conversationType, this.targetId, res.tempFilePaths[0],
this.user, (
res) => {
this.success()
})
}
})
break;
case 'video':
this.callShow = true
break;
case 'location':
uni.showToast({
icon: 'none',
title: '功能正在开发中'
})
// uni.chooseLocation({
// success: res => {
// console.log(res);
// this.success()
// }
// })
break;
case 'redpacket':
uni.showToast({
icon: 'none',
title: '功能正在开发中'
})
break;
case 'file':
uni.showToast({
icon: 'none',
title: '功能正在开发中'
})
break;
}
},
// 处理返回
success() {
this.$emit('success')
}
}
}
</script>
<style lang="scss" scoped>
.sent--popups {
background: white;
padding: 30rpx 15rpx;
flex-wrap: wrap;
flex-direction: row;
.item {
width: 150rpx;
margin: 15rpx;
}
.text {
text-align: center;
font-size: 26rpx;
color: #555;
padding-top: 15rpx;
}
.icon {
width: 110rpx;
height: 110rpx;
margin: 0 20rpx;
border-radius: 20rpx;
background: #F3F6FB;
}
}
</style>

View File

@@ -0,0 +1,84 @@
<template>
<view class="sent--text">
<input class="input" type="text" @focus="focus" @blur="blur" v-model="inputTxt" confirm-type="send"
@confirm="sent" cursor-spacing="10" />
<!-- <button class="button" size="mini" :disabled="disabled" @click="sent">发送</button> -->
</view>
</template>
<script>
import im from '@/utils/im/index.js'
import * as RongIMLib from '@/uni_modules/RongCloud-IMWrapper/js_sdk/index'
export default {
props: {
conversationType: {
type: Number,
default: 0
},
targetId: {
type: String,
default: ''
},
inputTxt: {
type: String,
default: ''
}
},
computed: {
disabled() {
return this.inputTxt.length === 0
},
user() {
return this.$store.getters.sender
}
},
created() {
RongIMLib.getTextMessageDraft(this.conversationType, this.targetId, ({
draft
}) => {
draft ? this.inputTxt = draft : ''
})
},
beforeDestroy() {
RongIMLib.saveTextMessageDraft(this.conversationType, this.targetId, this.inputTxt, (res) => {
console.log('销毁组件之前,保存草稿信息,但是没有执行', res);
})
},
methods: {
// 发送文本消息
sent() {
if (!this.disabled) {
RongIMLib.clearTextMessageDraft(this.conversationType, this.targetId)
im.sentText(this.conversationType, this.targetId, this.inputTxt, this.user, () => {
this.$emit('success')
this.inputTxt = ''
})
}
},
focus() {
this.$emit('focus')
},
blur() {
this.$emit('blur')
}
}
}
</script>
<style scoped lang="scss">
.sent--text {
display: flex;
flex-direction: row;
justify-content: space-between;
.input {
background: #F3F6FB;
height: 70rpx;
width: 500rpx;
border-radius: 10rpx;
margin-right: 15rpx;
padding: 0 20rpx;
}
}
</style>

View File

@@ -0,0 +1,144 @@
<template>
<view class="send--voice">
<view class="voice" hover-class="chat-hover" @touchstart="startRecord" @touchend="stopRecord">
<text class="button">按住说话</text>
</view>
<!-- 录音中提示 -->
<view class="modal" v-if="showRecordTip">
<image class="icon" src="@/static/icon/record-icon.png" mode="widthFix"></image>
<text class="text">录音中 {{recordTime}} s</text>
</view>
</view>
</template>
<script>
import im from '@/utils/im/index.js'
import permision from '@/utils/permission.js'
export default {
props: {
conversationType: {
type: Number,
default: 0
},
targetId: {
type: String,
default: ''
}
},
data() {
return {
showRecordTip: false,
recordTime: 60,
interval: 0,
maxRecordTime: 60,
recorderManager: null
}
},
computed: {
user() {
return this.$store.getters.sender
}
},
created() {
this.recorderManager = uni.getRecorderManager()
},
methods: {
// 检查安卓录制权限
async getAndroidPermission() {
return await permision.requestAndroidPermission('android.permission.RECORD_AUDIO')
},
// 录制语音消息
startRecord() {
this.getAndroidPermission().then(code => {
switch (code) {
case 1:
this.showRecordTip = true
this.recorderManager.start()
this.interval = setInterval(() => {
this.recordTime -= 1
if (this.recordTime === 0) {
this.stopRecord()
}
}, 1000)
break;
case 0:
uni.showToast({
title: '暂无麦克风权限,请前往应用设置开启麦克风',
icon: 'none'
})
break;
case -1:
uni.showToast({
title: '应用权限错误',
icon: 'none'
})
break;
}
})
},
// 结束录音
stopRecord(e) {
if (!this.showRecordTip) return
this.recorderManager.stop()
clearInterval(this.interval)
// 监听录音结束
this.recorderManager.onStop(res => {
im.sentVoice(this.conversationType, this.targetId, res.tempFilePath, (this.maxRecordTime -
this
.recordTime), this.user, () => {
setTimeout(() => {
this.$emit('success')
}, 500)
})
this.recordTime = this.maxRecordTime
this.showRecordTip = false
})
},
}
}
</script>
<style scoped lang="scss">
.send--voice {
.voice {
background: $window-color;
height: 70rpx;
line-height: 70rpx;
justify-content: center;
align-items: center;
width: 500rpx;
border-radius: 10rpx;
margin-right: 15rpx;
.button {
font-size: 30rpx;
color: #333;
}
}
.modal {
background: rgba(0, 0, 0, .6);
position: fixed;
height: 200rpx;
width: 300rpx;
border-radius: 10rpx;
z-index: 99;
top: 550rpx;
left: 225rpx;
flex-direction: column;
align-items: center;
justify-content: center;
.icon {
width: 88rpx;
height: 88rpx;
}
.text {
font-size: 28rpx;
color: #FFFFFF;
}
}
}
</style>

View File

@@ -0,0 +1,73 @@
<template>
<view class="">
<text class="name" v-if="!guest && name">{{ name }}</text>
<view class="msg--image" :class="guest ? 'right': 'left'">
<image class="img" :src="msg.thumbnail" @click="previewImage" mode="widthFix"></image>
</view>
</view>
</template>
<script>
export default {
name: 'showImage',
props: {
msg: {
type: Object,
default: () => {
return {
local: '',
remote: '',
objectName: '',
thumbnail: '',
isFull: false
}
}
},
guest: {
type: Boolean,
default: true
},
name: {
type: String,
default: ''
}
},
methods: {
previewImage() {
uni.previewImage({
urls: [
this.msg.remote
],
current: 1
})
}
}
}
</script>
<style scoped lang="scss">
.name {
font-size: 24rpx;
line-height: 34rpx;
color: $text-gray-m;
}
.msg--image {
padding: 20rpx;
&.left {
border-radius: 0 20rpx 20rpx 20rpx;
background: white;
}
&.right {
border-radius: 20rpx 0 20rpx 20rpx;
background: #34CE98;
}
.img {
width: 150rpx;
border-radius: 10rpx;
}
}
</style>

View File

@@ -0,0 +1,54 @@
<template>
<view class="msg--text">
<text class="name" v-if="!guest && name">{{ name }}</text>
<text class="im--text" :class="guest ? 'right': 'left'">{{ msg.content }}</text>
</view>
</template>
<script>
export default {
name: 'showText',
props: {
msg: {
type: Object,
default: {}
},
name: {
type: String,
default: ''
},
guest: {
type: Boolean,
default: true
}
}
}
</script>
<style scoped lang="scss">
.msg--text {
.name {
font-size: 24rpx;
line-height: 34rpx;
color: $text-gray-m;
}
.im--text {
max-width: 500rpx;
padding: 20rpx;
line-height: 44rpx;
font-size: 32rpx;
&.left {
border-radius: 0 20rpx 20rpx 20rpx;
background: white;
}
&.right {
border-radius: 20rpx 0 20rpx 20rpx;
background: $main-color;
color: white;
}
}
}
</style>

View File

@@ -1,9 +1,10 @@
<template> <template>
<view> <view class="">
<view class="im--audio" :class="guest ? 'right': 'left'" @click="onPlayMsg"> <text class="name" v-if="!guest && name">{{ name }}</text>
<image v-if="!guest" class="audio-mp3" src="@/static/icon/audio_green.png" mode="widthFix"></image> <view class="msg--voice" :class="guest ? 'right': 'left'" @click="onPlayMsg">
<text class="audio-text">"{{msg.duration}}"</text> <image v-if="!guest" class="icon" src="@/static/icon/audio_green.png" mode="widthFix"></image>
<image v-if="guest" class="audio-mp3" src="@/static/icon/audio_white.png" mode="widthFix"></image> <text class="duration">{{msg.duration}}"</text>
<image v-if="guest" class="icon" src="@/static/icon/audio_white.png" mode="widthFix"></image>
</view> </view>
</view> </view>
</template> </template>
@@ -23,6 +24,10 @@
} }
} }
}, },
name: {
type: String,
default: ''
},
guest: { guest: {
type: Boolean, type: Boolean,
default: true default: true
@@ -57,43 +62,45 @@
} }
</script> </script>
<style scoped> <style scoped lang="scss">
.im--audio { .name {
font-size: 24rpx;
line-height: 34rpx;
color: $text-gray-m;
}
.msg--voice {
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
height: 79rpx; height: 79rpx;
width: 170rpx; width: 170rpx;
padding: 0 20rpx; padding: 0 20rpx;
box-sizing:border-box; box-sizing: border-box;
/* box-sizing: border-box; */
}
, .icon {
.im--audio.left { width: 38rpx;
border-radius: 0 20rpx 20rpx 20rpx; height: 38rpx;
background: white; }
}
.im--audio.right { &.left {
border-radius: 20rpx 0 20rpx 20rpx; border-radius: 0 20rpx 20rpx 20rpx;
background: #34CE98; background: white;
}
.audio-mp3 { .duration {
width: 38rpx; color: #333;
height: 38rpx; font-size: 30rpx;
} }
}
.audio-text { &.right {
font-size: 30rpx; border-radius: 20rpx 0 20rpx 20rpx;
} background: $main-color;
.im--audio.left .audio-text { .duration {
color: #333; color: white;
} font-size: 30rpx;
}
.im--audio.right .audio-text { }
color: white;
} }
</style> </style>

View File

@@ -1,47 +1,32 @@
<template> <template>
<view> <view>
<u-index-list :index-list="indexs" inactiveColor="#666" activeColor="#34CE98"> <u-index-list :index-list="indexs" inactiveColor="#666" activeColor="#34CE98">
<view> <view class="friend-flex u-border-bottom" @click="toPending">
<view class="friend-flex u-border-bottom" @click="toPending"> <u-avatar class="cover" size="40" shape="square" :src="require('@/static/im/im_01.png')"></u-avatar>
<u-avatar class="cover" size="40" shape="square" :src="require('@/static/im/im_01.png')"></u-avatar> <u-badge max="99" absolute :offset="[23, 20]" :value="pendingCount" />
<view class="name"> <view class="info">新的朋友</view>
新的朋友 ({{ pendingCount }}) </view>
</view> <view class="friend-flex" @click="toGroup">
</view> <u-avatar class="cover" size="40" shape="square" :src="require('@/static/im/im_00.png')"></u-avatar>
<view class="friend-flex" @click="showToast"> <view class="info">我的群聊</view>
<u-avatar class="cover" size="40" shape="square" :src="require('@/static/im/im_00.png')"></u-avatar>
<view class="name">我的群聊</view>
</view>
</view> </view>
<block v-if="friends.length > 0"> <block v-if="friends.length > 0">
<template v-for="(item, friend) in friends"> <u-index-item v-for="(item, fkey) in friends" :key="fkey">
<!-- #ifdef APP-NVUE --> <u-index-anchor :text="indexs[fkey]" bgColor="#F3F6FB" height="20" size="12" color="#666">
<u-index-anchor :text="indexs[friend]" bgColor="#F3F6FB" height="20" size="12" color="#666">
</u-index-anchor> </u-index-anchor>
<!-- #endif -->
<u-index-item> <view v-for="(friendItem, index) in item" :key="index" class="friend-flex u-border-bottom"
<!-- #ifndef APP-NVUE --> @click="toFriend(friendItem.targetId)">
<u-index-anchor :text="indexs[friend]" bgColor="#F3F6FB" height="20" size="12" color="#666"> <u-avatar size="40" shape="square" :src="contact(friendItem.targetId).portraitUrl" />
</u-index-anchor> <view class="info">
<!-- #endif --> <view class="name">{{ contact(friendItem.targetId).name }}</view>
<view v-for="(friendItem, index) in item" :key="friendItem.userId" <view class="address">{{ friendItem.address }}</view>
class="friend-flex u-border-bottom"
@click="$Router.push({ name: 'imFriendsInfo', params: { targetId: friendItem.userId } })">
<block v-if="friendItem.portraitUrl != ''">
<u-avatar class="cover" size="40" shape="square" :src="friendItem.portraitUrl || ''"
:default-url="require('@/static/user/cover.png')"></u-avatar>
</block>
<block v-else>
<u-avatar class="cover" size="40" shape="square"
:src="require('@/static/user/cover.png')"></u-avatar>
</block>
<view class="name">{{ friendItem.name }}</view>
</view> </view>
</u-index-item> </view>
</template> </u-index-item>
</block> </block>
<block v-else> <block v-else>
<u-empty class="pages-null" mode="data" icon="http://cdn.uviewui.com/uview/empty/data.png" text="暂无好友"> <u-empty class="pages-null" mode="data" text="暂无好友">
</u-empty> </u-empty>
</block> </block>
</u-index-list> </u-index-list>
@@ -50,9 +35,9 @@
<script> <script>
import { import {
getFriends, getFriendsLetter
getPendingCount
} from '@/apis/interfaces/im'; } from '@/apis/interfaces/im';
import * as RongIMLib from '@/uni_modules/RongCloud-IMWrapper/js_sdk/index'
export default { export default {
data() { data() {
@@ -62,22 +47,53 @@
pendingCount: 0 pendingCount: 0
}; };
}, },
onShow() { computed: {
getFriends().then(res => { contact() {
this.indexs = res.indexList return function(targetId) {
this.friends = res.itemArr return this.$store.getters.contactInfo(targetId)
}) }
getPendingCount().then(res => { }
console.log(res); },
this.pendingCount = res onLoad() {
this.getFriendList()
this.checkNewFriendPending()
uni.$on('onContactNotification', () => {
this.checkNewFriendPending()
this.getFriendList()
}) })
}, },
onUnload() {
uni.$off('onContactNotification')
},
methods: { methods: {
showToast() { getFriendList() {
uni.showToast({ getFriendsLetter().then(res => {
title: '群聊功能暂未开放,敬请期待', this.indexs = res.indexList
icon: 'none' this.friends = res.itemArr
}); })
},
checkNewFriendPending() {
// 获取是否有新的好友申请
RongIMLib.getConversationList([RongIMLib.ConversationType.SYSTEM], 1000, 0, (res) => {
if (res.code === 0) {
this.pendingCount = res.conversations.filter((item) => {
return item.objectName == RongIMLib.ObjectName.ContactNotification
}).length
}
})
},
toGroup() {
uni.navigateTo({
url: '/pages/im/group/index',
fail(err) {
console.log(err);
}
})
},
toFriend(targetId) {
uni.navigateTo({
url: '/pages/im/friends/info?targetId=' + targetId
})
}, },
// 新朋友 // 新朋友
toPending() { toPending() {
@@ -108,14 +124,21 @@
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
// .cover .info {
.name {
flex: 1; flex: 1;
padding-left: $padding; margin-left: $padding;
font-size: $title-size + 2;
font-size: $title-size + 2; .name {
color: #454545 !important; font-size: $title-size + 2;
@extend .nowrap; font-size: $title-size + 2;
color: #454545 !important;
@extend .nowrap;
}
.address {
color: $text-gray-m;
font-size: $title-size-m - 5;
}
} }
} }
</style> </style>

View File

@@ -2,10 +2,11 @@
<view class="content"> <view class="content">
<!-- 用户信息 --> <!-- 用户信息 -->
<view class="info-flex"> <view class="info-flex">
<u-avatar :src="userInfo.portraitUrl || require('@/static/user/cover.png')" shape="square" size="50" bg-color="#fff"></u-avatar> <u-avatar :src="userInfo.portraitUrl || require('@/static/user/cover.png')" shape="square" size="50"
bg-color="#fff"></u-avatar>
<view class="info-text"> <view class="info-text">
<view class="nickname">{{userInfo.name}}</view> <view class="nickname">{{ userInfo.name }}</view>
<view class="address" @longpress="copyAddress">地址{{userInfo.address}}</view> <view class="address" @longpress="copyAddress">地址{{ userInfo.address }}</view>
</view> </view>
</view> </view>
<!-- 用户资料 --> <!-- 用户资料 -->
@@ -105,10 +106,11 @@
} }
}, },
onLoad(e) { onLoad(e) {
console.log(e);
this.targetId = e.targetId this.targetId = e.targetId
getFriendInfo(e.targetId).then(res => { getFriendInfo(e.targetId).then(res => {
this.userInfo = res this.userInfo = res
// 获取到用户信息之后,去检查一下要不要更新
this.$store.dispatch('updateContact', res)
uni.setNavigationBarTitle({ uni.setNavigationBarTitle({
title: res.name title: res.name
}) })
@@ -125,7 +127,7 @@
if (code == 0) { if (code == 0) {
this.isTop = conversation.isTop this.isTop = conversation.isTop
} }
}); })
}, },
methods: { methods: {
copyAddress() { copyAddress() {
@@ -141,7 +143,7 @@
}, },
toPrivate() { toPrivate() {
uni.redirectTo({ uni.redirectTo({
url: '/pages/im/private/chat?conversationType=1&targetId=' + this.targetId url: '/pages/im/private/chat?targetId=' + this.targetId
}); });
}, },
setRemark() { setRemark() {
@@ -156,7 +158,8 @@
content: '确认删除后不可恢复', content: '确认删除后不可恢复',
success: e => { success: e => {
if (e.confirm) { if (e.confirm) {
deleteFriend(this.targetId).then(res => { deleteFriend(this.targetId).then(res => {
uni.$emit('onContactNotification')
// 删除聊天记录 // 删除聊天记录
RongIMLib.deleteMessages(1, this.targetId); RongIMLib.deleteMessages(1, this.targetId);
RongIMLib.removeConversation(1, this.targetId); RongIMLib.removeConversation(1, this.targetId);
@@ -313,14 +316,16 @@
background: white; background: white;
margin: $margin; margin: $margin;
border-radius: $radius; border-radius: $radius;
.u-border-bottom{
.u-border-bottom {
border-bottom: solid 1rpx #f9f9f9 !important; border-bottom: solid 1rpx #f9f9f9 !important;
} }
.item { .item {
line-height: 100rpx; line-height: 100rpx;
display: flex; display: flex;
align-items: center; align-items: center;
padding:10rpx $padding; padding: 10rpx $padding;
justify-content: space-between; justify-content: space-between;
font-size: $title-size-lg; font-size: $title-size-lg;

View File

@@ -1,11 +1,3 @@
<!--
* @Description:新朋友即新增好友申请列表 可以搜索跳转
* @Author: Aimee·Zhang
* @Date: 2022-01-24 10:49:15
* @LastEditors: Aimee·Zhang
* @LastEditTime: 2022-01-25 10:18:26
-->
<template> <template>
<view class="pending"> <view class="pending">
<u-sticky> <u-sticky>
@@ -14,67 +6,42 @@
:disabled="true" :show-action="false" /> :disabled="true" :show-action="false" />
</view> </view>
</u-sticky> </u-sticky>
<block v-if="pedings.length > 0"> <view v-for="(item, index) in pendings" :key="index">
<applyFriend :lists="pedings" :isAgree="true" :isReject="false" @action="action" /> <apply-cell :message="item.latestMessage" @success="getPendingList" />
</block>
<view class="no-lists" v-else>
<u-image class="cover" radius="4" width="400rpx" height="400rpx"
:src="require('@/static/imgs/no-friend.png')" :lazy-load="true" />
<span>暂无申请记录~</span>
</view> </view>
</view> </view>
</template> </template>
<script> <script>
import { import * as RongIMLib from '@/uni_modules/RongCloud-IMWrapper/js_sdk/index'
getPedings, import applyCell from '../components/friendApplyCell'
resolveFriend,
rejectFriend
} from '@/apis/interfaces/im.js'
import * as RongIMLib from "@/uni_modules/RongCloud-IMWrapper/js_sdk/index"
import applyFriend from '@/components/friend-apply-reject-agree'
export default { export default {
components: { components: {
applyFriend applyCell
}, },
data() { data() {
return { return {
pedings: [] pendings: []
} }
}, },
onLoad() { onLoad() {
this.getPeddingList() this.getPendingList()
uni.$on('onContactNotification', this.getPendingList)
},
onUnload() {
uni.$off('onContactNotification')
}, },
methods: { methods: {
// 操作同意或拒绝 getPendingList() {
action(e) { // 获取系统中的好友关系会话列表
let url = e.type === 'agree' ? resolveFriend : rejectFriend RongIMLib.getConversationList([RongIMLib.ConversationType.SYSTEM], 1000, 0, (res) => {
uni.showModal({ if (res.code === 0) {
title: e.type === 'agree' ? '通过' : '拒绝', this.pendings = res.conversations.filter((item) => {
content: e.type === 'agree' ? '通过后即可与该用户畅所欲言' : '拒绝后将不会收到该用户发来信息', return item.objectName == RongIMLib.ObjectName.ContactNotification
success: res => { })
if (res.confirm) {
url(e.item.userId).then(res => {
this.getPeddingList()
})
}
} }
}) })
},
getPeddingList() {
// 获取系统中的好友关系会话列表
// RongIMLib.getConversationList([RongIMLib.ConversationType.SYSTEM], 50, 0, (res) => {
// if (res.code === 0) {
// this.pedings = res.conversations.filter((item) => {
// return item.objectName == RongIMLib.ObjectName.ContactNotification
// })
// }
// })
getPedings().then(res => {
console.log(res)
this.pedings = res
})
} }
} }
}; };

View File

@@ -31,7 +31,7 @@
searchFriend, searchFriend,
pedingFriend pedingFriend
} from '@/apis/interfaces/im.js'; } from '@/apis/interfaces/im.js';
import applyFriend from '@/components/friend-apply-reject-agree'; import applyFriend from '../components/friendApplyList.vue';
export default { export default {
components: { components: {
applyFriend applyFriend

View File

@@ -0,0 +1,54 @@
<template>
<view class="create">
<u--textarea v-model="content" count height="200" maxlength="200" placeholder="请输入公告内容"></u--textarea>
<u-button type="primary" text="发布" @click="onCreate"></u-button>
</view>
</template>
<script>
import {
createGroupAnnouncement
} from '@/apis/interfaces/im.js'
export default {
data() {
return {
targetId: '',
content: ''
}
},
onLoad(e) {
this.targetId = e.targetId
},
methods: {
onCreate() {
createGroupAnnouncement(this.targetId, this.content).then(res => {
uni.showToast({
title: '发布成功',
success: () => {
setTimeout(() => {
uni.navigateBack()
}, 1000)
}
})
}).catch(err => {
uni.showToast({
icon: 'none',
title: err.message
})
})
}
}
}
</script>
<style lang="scss" scoped>
.create {
padding: $padding;
.u-button {
margin-top: $padding;
}
}
</style>

View File

@@ -0,0 +1,105 @@
<template>
<view class="announce">
<u-skeleton rows="2" :loading="loading" avatar :rows="5">
<view v-for="(item,index) in announcements" :key="index">
<view class="header">
<u-avatar :src="item.user.portraitUrl"></u-avatar>
<view class="user">
<view class="name">{{ item.user.name }}</view>
<view class="time">{{ item.created_at }}</view>
</view>
<view class="delete" v-if="isAdmin" @click="onDelete(item.announcement_id)">删除</view>
</view>
<view class="content">{{ item.content }}</view>
</view>
</u-skeleton>
</view>
</template>
<script>
import {
getGroupInfo,
getGroupAnnouncements,
deleteGroupAnnouncement
} from '@/apis/interfaces/im.js'
export default {
data() {
return {
targetId: '',
announcements: [],
loading: true,
isAdmin: false
}
},
onLoad(e) {
this.targetId = e.targetId
getGroupInfo(this.targetId).then(res => {
this.isAdmin = res.group.is_admin
})
this.initData()
},
onNavigationBarButtonTap() {
if (this.isAdmin) {
uni.navigateTo({
url: '/pages/im/group/announceCreate?targetId=' + this.targetId
})
} else {
uni.showToast({
icon: 'none',
title: '没有权限'
})
}
},
methods: {
initData() {
getGroupAnnouncements(this.targetId).then(res => {
this.announcements = res
console.log(res);
this.loading = false
})
},
onDelete(aId) {
deleteGroupAnnouncement(this.targetId, aId).then(res => {
this.initData()
})
}
}
}
</script>
<style lang="scss" scoped>
.announce {
padding: $padding;
.header {
display: flex;
flex-direction: row;
.user {
margin-left: $padding;
flex: 1;
.name {
line-height: 44rpx;
}
.time {
margin-top: 15rpx;
font-size: 24rpx;
color: $text-gray-m;
}
}
.delete {
color: $text-price;
font-size: 32rpx;
}
}
.content {
padding: $padding;
font-size: 34rpx;
}
}
</style>

186
pages/im/group/chat.nvue Normal file
View File

@@ -0,0 +1,186 @@
<template>
<view class="group--chat">
<list class="body" :show-scrollbar="false">
<cell class="cell" v-for="(item, index) in messages" :key="index">
<view class="cell-item" :class="item.messageDirection == 1 ? 'right' : 'left'">
<u-avatar class="avatar" @click="toUser(item)" size="36" shape="square" :src="item.content.userInfo.portraitUrl" />
<view class="msg">
<show-voice v-if="item.objectName === 'RC:HQVCMsg'" :guest="item.messageDirection == 1"
:msg="item.content" :name="item.content.userInfo.name" />
<show-image v-if="item.objectName === 'RC:ImgMsg'" :guest="item.messageDirection == 1"
:msg="item.content" :name="item.content.userInfo.name" />
<show-text v-if="item.objectName === 'RC:TxtMsg'" :guest="item.messageDirection == 1"
:msg="item.content" :name="item.content.userInfo.name" />
</view>
</view>
</cell>
<cell class="cell-footer" ref="chatBottom"></cell>
</list>
<sent-message-bar :conversationType="conversationType" :targetId="targetId" @onSuccess="getMessageList()" />
</view>
</template>
<script>
import {
timeCustomCN
} from '@/utils/filters.js'
import * as RongIMLib from '@/uni_modules/RongCloud-IMWrapper/js_sdk/index'
import im from '@/utils/im/index.js'
import showVoice from '../components/showVoice'
import showImage from '../components/showImage'
import showText from '../components/showText'
import sentMessageBar from '../components/sentMessageBar'
const ChatList = uni.requireNativePlugin('dom')
export default {
components: {
showVoice,
showImage,
showText,
sentMessageBar
},
data() {
return {
targetId: '',
conversationType: 3,
messages: [],
groupInfo: {
name: ''
}
}
},
onLoad(e) {
this.targetId = e.targetId
this.groupInfo = this.$store.getters.contactInfo(this.targetId)
uni.setNavigationBarTitle({
title: this.groupInfo.name
})
this.getMessageList()
uni.$on('onReceiveMessage', (msg) => {
if (msg.targetId == this.targetId) {
this.getMessageList()
}
})
},
onBackPress() {
uni.$off('onReceiveMessage')
},
onNavigationBarButtonTap() {
uni.navigateTo({
url: '/pages/im/group/info?targetId=' + this.targetId
})
},
methods: {
toUser(item) {
console.log(item);
uni.navigateTo({
url: '/pages/im/friends/info?targetId=' + item.senderUserId
})
},
// 获取消息列表
getMessageList() {
im.getMessageList(
this.conversationType,
this.targetId,
new Date().getTime(),
20,
true,
(messages) => {
this.messages = messages.reverse()
this.scrollBottom()
})
},
// 滚动到底部
scrollBottom(type) {
// 清理当前会话,未读消息数量
RongIMLib.clearMessagesUnreadStatus(this.conversationType, this.targetId, new Date().getTime() + 1100)
// 发送消息已读状态给对方
RongIMLib.sendReadReceiptMessage(this.conversationType, this.targetId, new Date().getTime())
// 更新badge提醒数量
im.setNotifyBadge()
setTimeout(() => {
let el = this.$refs.chatBottom
ChatList.scrollToElement(el, {
offset: 0,
animated: false
})
}, 50)
}
}
}
</script>
<style lang="scss" scoped>
.group--chat {
background: $window-color;
flex: 1;
.body {
flex: 1;
.cell {
padding: 10rpx 30rpx;
.cell-item {
width: 690rpx;
justify-content: flex-start;
&.left {
flex-direction: row;
}
&.right {
flex-direction: row-reverse;
.state {
flex-direction: row;
justify-content: flex-end;
}
}
.avatar {
width: 78rpx;
height: 78rpx;
background-color: white;
border-radius: 10rpx;
}
.msg {
margin: 0 20rpx;
.user {
font-size: 18rpx;
line-height: 40rpx;
}
}
}
.cell-footer {
height: 20rpx;
}
}
}
.footer {
background: white;
padding: 20rpx 30rpx;
display: flex;
justify-content: space-between;
flex-direction: row;
.msg-type {
width: 70rpx;
height: 70rpx;
.icon {
margin: 5rpx;
width: 60rpx;
height: 60rpx;
}
}
}
}
</style>

48
pages/im/group/create.vue Normal file
View File

@@ -0,0 +1,48 @@
<template>
<view>
<u--input placeholder="请输入内容" border="surround" v-model="group_name"></u--input>
<u-button type="primary" text="确定" @click="onCreate"></u-button>
</view>
</template>
<script>
import {
createGroup
} from '@/apis/interfaces/im.js'
export default {
data() {
return {
group_name: '',
}
},
onLoad() {
},
methods: {
onCreate() {
console.log('阿斯利康');
createGroup({
name: this.group_name
}).then(res => {
console.log(res);
uni.showToast({
title: '创建成功'
})
uni.navigateBack()
}).catch(err => {
console.log(err);
uni.showToast({
icon: 'none',
title: err
})
})
}
}
}
</script>
<style>
</style>

View File

@@ -1,11 +1,79 @@
<template> <template>
<view class=""> <view>
<view v-for="(item, index) in groups" :key="index" class="friend-flex u-border-bottom"
</view> @click="toGroup(item.targetId)">
</template> <u-avatar size="40" shape="square" :src="contact(item.targetId).portraitUrl" />
<view class="info">
<script> <view class="name">{{ item.name }}</view>
</script> </view>
</view>
<style> </view>
</template>
<script>
import {
getMyGroups
} from '@/apis/interfaces/im.js'
export default {
data() {
return {
groups: []
}
},
computed: {
contact() {
return function(targetId) {
return this.$store.getters.contactInfo(targetId)
}
}
},
onNavigationBarButtonTap() {
uni.navigateTo({
url: 'pages/im/group/create'
})
},
onLoad() {
getMyGroups().then((res) => {
this.groups = res
res.map(item => {
this.$store.dispatch('updateContact', item)
})
})
},
methods: {
toGroup(targetId) {
uni.navigateTo({
url: '/pages/im/group/chat?targetId=' + targetId
})
}
}
}
</script>
<style lang="scss" scoped>
// 好友列表
.friend-flex {
position: relative;
padding: 20rpx $padding;
display: flex;
flex-direction: row;
align-items: center;
.info {
flex: 1;
margin-left: $padding;
.name {
font-size: $title-size + 2;
font-size: $title-size + 2;
color: #454545 !important;
}
.address {
color: $text-gray-m;
font-size: $title-size-m - 5;
}
}
}
</style> </style>

213
pages/im/group/info.vue Normal file
View File

@@ -0,0 +1,213 @@
<template>
<view class="container">
<view class="members u-border-bottom">
<view class="users">
<view class="user" v-for="(item, index) in members" :key="index" @click="toUser(item)">
<u-avatar size="44" shape="square" :src="item.portraitUrl"></u-avatar>
<view class="name">{{ item.name }}</view>
</view>
<view class="user">
<u-avatar @click="inviteUser" size="44" shape="square" icon="plus" bg-color="#eeeeee"
color="#999999"></u-avatar>
<view class="name">邀请用户</view>
</view>
</view>
<view @click="loadMore" class="loadmore">查看更多群成员</view>
</view>
<u-cell-group class="cells">
<u-cell isLink title="群公告" :label="announcement" @click="toAnnouncement"></u-cell>
<u-cell title="聊天置顶">
<u-switch slot="value" size="20" v-model="isTop" activeColor="#34CE98" @change="setTop"></u-switch>
</u-cell>
<u-cell title="免打扰">
<u-switch slot="value" size="20" v-model="status" activeColor="#34CE98" @change="setStatus"></u-switch>
</u-cell>
</u-cell-group>
<u-cell-group class="cells" v-if="group.is_owner">
<u-cell isLink title="修改群聊名称" :value="group.name" @click="onGroupName"></u-cell>
<u-cell isLink title="修改群头像">
<u-avatar slot="value" size="24" shape="square" :src="group.cover"></u-avatar>
</u-cell>
</u-cell-group>
<view class="cells actions u-border-top">
<view class="action u-border-bottom" @click="onClean">清空聊天记录</view>
<view class="action u-border-bottom" v-if="group.is_owner" @click="onDismiss">解散群聊</view>
<view class="action u-border-bottom" v-else @click="onQuite">删除并退出</view>
</view>
</view>
</template>
<script>
import {
getGroupInfo
} from '@/apis/interfaces/im.js'
import * as RongIMLib from '@/uni_modules/RongCloud-IMWrapper/js_sdk/index'
export default {
data() {
return {
targetId: '',
group: {},
conversationType: 3,
announcement: '',
members: [],
status: false,
isTop: false,
}
},
onLoad(e) {
this.targetId = e.targetId
getGroupInfo(this.targetId).then(res => {
this.group = res.group
console.log(this.group);
this.announcement = res.announcement
this.members = res.members
})
RongIMLib.getConversationNotificationStatus(this.conversationType, this.targetId, ({
status
}) => {
this.status = !Boolean(status)
})
RongIMLib.getConversation(this.conversationType, this.targetId, ({
code,
conversation
}) => {
if (code == 0) {
this.isTop = conversation.isTop
}
})
},
methods: {
setStatus() {
RongIMLib.setConversationNotificationStatus(this.conversationType, this.targetId, this.status,
({
status
}) => {
this.status = !Boolean(status)
})
},
setTop() {
RongIMLib.setConversationToTop(this.conversationType, this.targetId, this.isTop, (res) => {
RongIMLib.getConversation(this.conversationType, this.targetId, ({
conversation
}) => {
this.isTop = conversation.isTop
})
})
},
toUser(item) {
uni.navigateTo({
url: '/pages/im/friends/info?targetId=' + item.targetId
})
},
inviteUser() {
},
loadMore() {
uni.navigateTo({
url: '/pages/im/group/users?targetId=' + this.targetId
})
},
toAnnouncement() {
uni.navigateTo({
url: '/pages/im/group/announcement?targetId=' + this.targetId
})
},
onGroupName() {
},
onClean() {
uni.showModal({
title: '清空聊天记录',
content: '清空聊天记录,只会清空本地的记录,其他成员不会变化',
success: (res) => {
if (res.confirm) {
RongIMLib.deleteMessages(3, this.targetId, () => {
uni.showToast({
icon: 'none',
title: '清空成功'
})
})
}
}
})
},
onDismiss() {
uni.showToast({
icon: 'none',
title: '开发中'
})
},
onQuite() {
uni.showToast({
icon: 'none',
title: '开发中'
})
}
}
}
</script>
<style lang="scss" scoped>
.container {
min-height: 100vh;
background: $window-color;
}
.cells {
margin-top: $padding;
background-color: white;
}
.members {
background-color: white;
padding-bottom: 40rpx;
.users {
flex-direction: row;
flex-wrap: wrap;
display: flex;
padding: 20rpx 0;
.user {
width: 126rpx;
margin-left: 20rpx;
margin-bottom: 20rpx;
justify-content: center;
align-items: center;
.name {
width: 126rpx;
text-align: center;
font-size: 28rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
word-break: break-word;
}
}
}
.loadmore {
font-size: 28rpx;
color: $text-gray-m;
text-align: center;
}
}
.actions {
margin-top: $padding;
text-align: center;
.action {
padding: $padding;
color: $text-price;
justify-content: center;
}
}
</style>

74
pages/im/group/users.vue Normal file
View File

@@ -0,0 +1,74 @@
<template>
<view class="members">
<view class="users">
<view class="user" v-for="(item, index) in members" :key="index" @click="toUser(item)">
<u-avatar size="44" shape="square" :src="item.portraitUrl"></u-avatar>
<view class="name">{{ item.name }}</view>
</view>
</view>
</view>
</template>
<script>
import {
getGroupInfo,
getGroupUsers
} from '@/apis/interfaces/im.js'
export default {
data() {
return {
targetId: '',
members: []
}
},
onLoad(e) {
this.targetId = e.targetId
getGroupInfo(this.targetId).then(res => {
})
getGroupUsers(this.targetId).then(res => {
this.members = res
})
},
methods: {
toUser(item) {
uni.navigateTo({
url: '/pages/im/friends/info?targetId=' + item.targetId
})
}
}
}
</script>
<style lang="scss" scoped>
.members {
background-color: white;
padding-bottom: 40rpx;
.users {
flex-direction: row;
flex-wrap: wrap;
display: flex;
padding: 20rpx 0;
.user {
width: 126rpx;
margin-left: 20rpx;
margin-bottom: 20rpx;
justify-content: center;
align-items: center;
.name {
width: 126rpx;
text-align: center;
font-size: 28rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
word-break: break-word;
}
}
}
}
</style>

View File

@@ -5,56 +5,23 @@
<view class="custom-header"> <view class="custom-header">
<view class="header-flex"> <view class="header-flex">
<view class="tabs"> <view class="tabs">
<view class="item show"></view> <view class="item active"></view>
<view class="item" @click="onNav('', {})">群聊</view>
</view> </view>
<view class="btns"> <view class="btns">
<view class="item" @click="onNav('imFriends', {})">
<uni-icons color="#555" custom-prefix="iconfont" type="icon-tuandui" size="22"></uni-icons>
</view>
<view class="item" @click="scanQrCode"> <view class="item" @click="scanQrCode">
<uni-icons color="#555" type="scan" size="22"></uni-icons> <uni-icons color="#555" type="scan" size="22" />
</view>
<view class="item" @click="toFriendList">
<u-badge absolute max="99" :offset="[-5, -5]" :value="hasNewFriends" />
<uni-icons color="#555" custom-prefix="iconfont" type="icon-tuandui" size="22" />
</view> </view>
<!-- <view class="item" @click="onNav('', {})">
<uni-icons color="#555" custom-prefix="iconfont" type="icon-gengduo2" size="22"></uni-icons>
</view> -->
</view> </view>
</view> </view>
</view> </view>
<u-alert type="warning" v-if="connection != 0" description="网络似乎断开了,请检查网络" :show-icon="true" />
<!-- content --> <!-- content -->
<view v-if="$store.state.token != ''"> <view v-if="$store.state.token !== ''">
<block v-if="conversations.length < 1"> <conversation-list @refresh="getConversationList()" :conversations="conversations" />
<view class="vertical null-list">
<u-empty mode="message" textColor="#999" text="暂无好友消息" />
</view>
</block>
<block v-else>
<u-alert type="warning" v-if="connection != 0" description="网络似乎断开了" :show-icon="true" />
<view v-for="(item, index) in conversations" :key="index" :class="['message', { 'is-top': item.isTop }]"
@tap="toDetail(item)" @longpress="onLongPress" :data-item="item">
<view class="avatar">
<u-badge numberType="ellipsis" max="99" shape="horn" absolute :offset="[-5, -5]"
:value="item.unreadMessageCount" />
<u-avatar :src="friend(item.targetId).portraitUrl || require('@/static/user/cover.png')"
shape="square" size="46" />
</view>
<view class="content">
<view class="header">
<view class="name">{{ friend(item.targetId).name || '未知用户' }}</view>
<view class="time">{{ item.sentTime|timeCustomCN }}</view>
</view>
<view class="preview">{{ item.latestMessage.content || '' }}</view>
</view>
</view>
<!-- TODO 长按的弹出框怎么点击隐藏没搞明白 -->
<view class="shade" @tap="hidePop">
<view class="pop" :style="popStyle" :class="{'show':showPop}">
<view v-for="(item, index) in popButton" :key="index" @tap="pickerMenu" :data-index="index">
{{item}}
</view>
</view>
</view>
</block>
</view> </view>
<!-- 未登录 --> <!-- 未登录 -->
<view v-else class="vertical null-list"> <view v-else class="vertical null-list">
@@ -68,113 +35,51 @@
</template> </template>
<script> <script>
import * as RongIMLib from "@/uni_modules/RongCloud-IMWrapper/js_sdk/index" import * as RongIMLib from '@/uni_modules/RongCloud-IMWrapper/js_sdk/index'
import im from '@/utils/im/index.js' import im from '@/utils/im/index.js'
import userAuth from '@/public/userAuth' import userAuth from '@/public/userAuth'
import { import conversationList from './components/conversationList'
getImToken
} from '@/apis/interfaces/im.js'
export default { export default {
data() { data() {
return { return {
isShown: true, // 当前页面显示状态
conversations: [], // 会话列表 conversations: [], // 会话列表
connection: 0, connection: 0,
/* 窗口尺寸 */ hasNewFriends: 0
winSize: {},
/* 显示操作弹窗 */
showPop: false,
/* 弹窗按钮列表 */
popButton: ['置顶聊天', '删除该聊天'],
/* 弹窗定位样式 */
popStyle: "",
pickedItem: {}
} }
}, },
computed: { components: {
friend() { conversationList
return function(targetId) {
return this.$store.getters.userInfo(targetId)
}
}
}, },
onLoad() { onLoad() {
uni.$on('onReceiveMessage', (msg) => { // 好友申请数量
this.getConversationList() this.checkNewFriendPending()
})
uni.$on('onConnectionStatusChange', (status) => { uni.$on('onConnectionStatusChange', (status) => {
this.connection = status this.connection = status
}) })
uni.$on('onContactNotification', this.checkNewFriendPending)
}, },
onShow() { onShow() {
if (this.$store.state.token !== '') { if (this.$store.state.token !== '') {
this.getConversationList() this.getConversationList()
} }
this.isShown = true // 监听新消息
uni.$on('onReceiveMessage', (msg) => {
this.getConversationList()
})
}, },
onHide() { onHide() {
this.isShown = false uni.$off('onReceiveMessage')
}, },
onNavigationBarButtonTap(e) { methods: {
if (e.index == 0) { checkNewFriendPending() {
uni.showToast({ // 获取是否有新的好友申请
title: '开发中暂未开放,敬请期待', RongIMLib.getConversationList([RongIMLib.ConversationType.SYSTEM], 1000, 0, (res) => {
icon: 'none' if (res.code === 0) {
}) this.hasNewFriends = res.conversations.filter((item) => {
} return item.objectName == RongIMLib.ObjectName.ContactNotification
if (e.index == 1) { }).length
if (this.toLogin()) { }
this.$Router.push({
name: 'imFriends'
})
}
}
},
methods: {
// 隐藏功能菜单
hidePop() {
this.showPop = false
this.pickedItem = {}
setTimeout(() => {
this.showShade = false
}, 250)
},
// 点击会话功能菜单
pickerMenu(e) {
const index = Number(e.currentTarget.dataset.index)
if (index == 0) {
RongIMLib.setConversationToTop(this.pickedItem.conversationType, this.pickedItem.targetId, !this
.pickedItem.isTop)
} else {
RongIMLib.removeConversation(this.pickedItem.conversationType, this.pickedItem.targetId)
}
im.setNotifyBadge()
this.getConversationList()
this.hidePop()
},
// 长按会话,展示功能菜单
onLongPress(e) {
let [touches, style, item] = [e.touches[0], "", e.currentTarget.dataset.item]
if (touches.clientY > (this.winSize.height / 2)) {
style = `bottom:${this.winSize.height-touches.clientY}px;`
} else {
style = `top:${touches.clientY}px;`
}
if (touches.clientX > (this.winSize.witdh / 2)) {
style += `right:${this.winSize.witdh-touches.clientX}px`
} else {
style += `left:${touches.clientX}px`
}
this.popButton[0] = item.isTop ? '取消置顶' : '置顶聊天'
this.popStyle = style
this.pickedItem = item
this.$nextTick(() => {
setTimeout(() => {
this.showPop = true;
}, 10)
}) })
}, },
// 检查登录 // 检查登录
@@ -186,41 +91,16 @@
} }
return true return true
}, },
// 获取私聊的会话列表
getConversationList() { getConversationList() {
const count = 1000 const count = 1000
const timestamp = 0 const timestamp = 0
RongIMLib.getConversationList([RongIMLib.ConversationType.PRIVATE], count, timestamp, (res) => { RongIMLib.getConversationList([1, 3], count, timestamp, (res) => {
if (res.code === 0) { if (res.code === 0) {
this.conversations = res.conversations this.conversations = res.conversations
} }
}) })
}, },
// 进入聊天的详情页面,清理未读消息数量
toDetail(item) {
this.hidePop()
uni.navigateTo({
url: '/pages/im/private/chat?targetId=' + item.targetId + '&conversationType=' + item
.conversationType
})
// url: '/pages/im/private/index?targetId=' + item.targetId + '&conversationType=' + item.conversationType
},
// 点击按钮
onNav(name, params) {
if (this.toLogin) {
if (name === '') {
uni.showToast({
title: '开发中,敬请期待',
icon: 'none'
})
return
}
this.$Router.push({
name,
params
})
}
},
// 调起扫码 // 调起扫码
scanQrCode() { scanQrCode() {
uni.scanCode({ uni.scanCode({
@@ -233,59 +113,17 @@
} }
} }
}) })
},
toFriendList() {
uni.navigateTo({
url: '/pages/im/friends/index'
})
} }
} }
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
// header
.custom-header {
@extend .ios-top;
background: $window-color;
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 9999;
.header-flex {
padding: 20rpx $padding;
display: flex;
justify-content: space-between;
height: 60rpx;
line-height: 60rpx;
.tabs {
.item {
margin-left: $margin;
display: inline-block;
font-size: $title-size-lg;
color: $text-gray;
padding: 0 ($padding - 10);
border-radius: 30rpx;
&:first-child {
margin: 0;
}
&.show {
background: rgba($color: $main-color, $alpha: .1);
color: $main-color;
font-weight: bold;
}
}
}
.btns {
.item {
display: inline-block;
margin-left: $margin;
}
}
}
}
// contents // contents
.contents { .contents {
background-color: $window-color; background-color: $window-color;
@@ -293,6 +131,50 @@
padding-top: 90rpx + 20rpx; padding-top: 90rpx + 20rpx;
box-sizing: border-box; box-sizing: border-box;
.custom-header {
@extend .ios-top;
background: $window-color;
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 9999;
.header-flex {
padding: 20rpx $padding;
display: flex;
justify-content: space-between;
height: 60rpx;
line-height: 60rpx;
.tabs {
.item {
position: relative;
display: inline-block;
font-size: $title-size-lg;
color: $text-gray;
padding: 0 ($padding - 10);
border-radius: 30rpx;
&.active {
background: rgba($color: $main-color, $alpha: .1);
color: $main-color;
font-weight: bold;
}
}
}
.btns {
.item {
position: relative;
display: inline-block;
margin-left: $margin;
}
}
}
}
.null-list { .null-list {
height: 100vh; height: 100vh;
text-align: center; text-align: center;
@@ -308,116 +190,6 @@
box-sizing: border-box; box-sizing: border-box;
} }
} }
.message {
background: white;
padding: 30rpx 0 0 30rpx;
position: relative;
display: flex;
&.is-top {
background: $window-color;
border-bottom: #e8e8e8;
// background-color: rgba($color: $main-color, $alpha: 0.02);
}
.avatar {
position: relative;
.u-badge {
z-index: 998;
}
}
.content {
margin-left: 30rpx;
width: calc(100% - 46px);
box-sizing: border-box;
border-bottom: solid 1rpx #f3f3f3;
position: relative;
.header {
display: flex;
justify-content: space-between;
.name {
font-size: $title-size + 2;
color: #454545;
color: #454545;
}
.time {
font-size: $title-size-sm;
color: $text-gray-m;
position: absolute;
right: 30rpx;
}
}
.preview {
word-break: break-all;
color: $text-gray-m;
padding-top: $padding - 20;
padding-bottom: $padding;
font-size: $title-size-m;
height: 40rpx;
line-height: 40rpx;
width: 500rpx;
@extend .nowrap;
}
}
}
// .message:not(:last-child) {
// &::after {
// position: absolute;
// left: calc(44px + #{$padding} + 30rpx);
// right: 0;
// bottom: 0;
// content: " ";
// height: 1rpx;
// background: $border-color;
// }
// }
}
/* 遮罩 */
.shade {
.pop {
position: fixed;
z-index: 101;
width: 200rpx;
box-sizing: border-box;
font-size: 28rpx;
text-align: left;
color: #333;
background-color: #fff;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
line-height: 80rpx;
transition: transform 0.15s ease-in-out 0s;
user-select: none;
-webkit-touch-callout: none;
transform: scale(0, 0);
&.show {
transform: scale(1, 1);
}
&>view {
padding: 0 20rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
user-select: none;
-webkit-touch-callout: none;
&:active {
background-color: #f3f3f3;
}
}
}
} }
.u-border-bottom { .u-border-bottom {

View File

@@ -1,5 +1,5 @@
<template> <template>
<view class="call-page"> <view class="call">
<view class="video"> <view class="video">
<RongCloud-Call-RCUniCallView ref="bigVideoView" :style="{width:windowWidth+'px',height:windowHeight+'px'}" <RongCloud-Call-RCUniCallView ref="bigVideoView" :style="{width:windowWidth+'px',height:windowHeight+'px'}"
class="bigVideoView"> class="bigVideoView">
@@ -9,9 +9,11 @@
</view> </view>
<view class="status" v-if="!connected || mediaType == 0"> <view class="status" v-if="!connected || mediaType == 0">
<view class="call-user"> <view class="remote">
<u-avatar v-if="userInfo.portraitUrl" :src="userInfo.portraitUrl" shape="square" size="96" bgColor="#fff" /> <u-avatar v-if="userInfo.portraitUrl" :src="userInfo.portraitUrl" shape="square" size="96"
<u-avatar size="80" v-if="!userInfo.portraitUrl" shape="square" :text="userInfo.name ? userInfo.name.substring(0,1) : '未'" font-size="44" randomBgColor /> bgColor="#fff" />
<u-avatar size="80" v-if="!userInfo.portraitUrl" shape="square"
:text="userInfo.name ? userInfo.name.substring(0,1) : '未'" font-size="44" randomBgColor />
<view><text class="nickname">{{userInfo.name}}</text></view> <view><text class="nickname">{{userInfo.name}}</text></view>
<view v-if="remoteRinging"><text class="mediaType">等待对方接听</text></view> <view v-if="remoteRinging"><text class="mediaType">等待对方接听</text></view>
<view v-if="connected"><text class="mediaType">已接通</text></view> <view v-if="connected"><text class="mediaType">已接通</text></view>
@@ -163,85 +165,88 @@
} }
</script> </script>
<style scoped> <style scoped lang="scss">
.call-page { .call {
display: flex; display: flex;
flex: 1; flex: 1;
flex-direction: column; flex-direction: column;
position: relative; position: relative;
}
.bigVideoView { .video {
background: #000; .bigVideoView {
} background: #000;
}
.smallVideoView { .smallVideoView {
background: blue; background: blue;
position: absolute; position: absolute;
right: 100rpx; right: 100rpx;
top: 100rpx; top: 100rpx;
width: 180rpx; width: 180rpx;
height: 320rpx; height: 320rpx;
} }
}
.buttons { .status {
position: fixed; flex: 1;
bottom: 100rpx; background: #666;
width: 750rpx; width: 750rpx;
display: flex; position: fixed;
flex-direction: row; bottom: 0;
justify-content: space-around; top: 0;
}
.btn { .remote {
align-items: center; flex: 1;
} width: 750rpx;
display: flex;
align-items: center;
top: 300rpx;
.icon { .mediaType {
background: #34CE98; font-size: 32rpx;
width: 132rpx; color: #aaa;
height: 132rpx; }
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.btn .text { .nickname {
margin-top: 16rpx; margin: 30rpx;
color: #FFFFFF; font-size: 44rpx;
font-size: 32rpx; color: #ffffff;
} }
}
}
.icon.hangup { .buttons {
background: #dd524d; position: fixed;
} bottom: 100rpx;
width: 750rpx;
display: flex;
flex-direction: row;
justify-content: space-around;
}
.status {
flex: 1;
background: #666;
width: 750rpx;
position: fixed;
bottom: 0;
top: 0;
}
.call-user { .btn {
flex: 1; align-items: center;
width: 750rpx;
display: flex;
align-items: center;
top: 300rpx;
}
.mediaType { &.hangup {
font-size: 32rpx; background: $text-price;
color: #aaa; }
}
.nickname { .icon {
margin: 30rpx; background: $main-color;
font-size: 44rpx; width: 132rpx;
color: #ffffff; height: 132rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.text {
margin-top: 16rpx;
color: #FFFFFF;
font-size: 32rpx;
}
}
} }
</style> </style>

View File

@@ -1,21 +1,20 @@
<template> <template>
<view class="chat"> <view class="chat">
<!-- chat --> <!-- chat -->
<list class="chat-scroll" :show-scrollbar="false"> <list class="body" :show-scrollbar="false">
<cell class="cell" v-for="(item, index) in messages" :key="index"> <cell class="cell" v-for="(item, index) in messages" :key="index">
<view class="cell-time"> <view class="time">
<text class="cell-time-text">{{ customCN(item.sentTime) }}</text> <text class="text">{{ customCN(item.sentTime) }}</text>
</view> </view>
<view class="cell-item" :class="item.messageDirection == 1 ? 'right' : 'left'"> <view class="cell-item" :class="item.messageDirection == 1 ? 'right' : 'left'">
<image class="active" :src="userInfo.portraitUrl" mode="aspectFill" <u-avatar class="avatar" size="36" shape="square" @click="showUser(targetId, item.messageDirection)" :src="userInfo.portraitUrl" />
@click="showFriend(targetId, item.messageDirection)"></image>
<view class="msg"> <view class="msg">
<show-voice v-if="item.objectName === 'RC:HQVCMsg'" :guest="item.messageDirection == 1" <show-voice v-if="item.objectName === 'RC:HQVCMsg'" :guest="item.messageDirection == 1"
:msg="item.content"></show-voice> :msg="item.content" />
<show-image v-if="item.objectName === 'RC:ImgMsg'" :guest="item.messageDirection == 1" <show-image v-if="item.objectName === 'RC:ImgMsg'" :guest="item.messageDirection == 1"
:msg="item.content.content"></show-image> :msg="item.content" />
<show-text v-if="item.objectName === 'RC:TxtMsg'" :guest="item.messageDirection == 1" <show-text v-if="item.objectName === 'RC:TxtMsg'" :guest="item.messageDirection == 1"
:msg="item.content.content"></show-text> :msg="item.content" />
<view class="state" v-if="item.messageDirection == 1"> <view class="state" v-if="item.messageDirection == 1">
<text class="state-text">{{ item.sentStatus == 50 ? '已读': '未读'}}</text> <text class="state-text">{{ item.sentStatus == 50 ? '已读': '未读'}}</text>
</view> </view>
@@ -24,30 +23,7 @@
</cell> </cell>
<cell class="cell-footer" ref="chatBottom"></cell> <cell class="cell-footer" ref="chatBottom"></cell>
</list> </list>
<!-- footer --> <sent-message-bar :conversationType="conversationType" :targetId="targetId" @onSuccess="getMessageList()" />
<view class="chat-footer">
<view class="msg-type" @click="msgType">
<image class="msg-type-icon" src="@/static/icon/key-icon.png" v-if="importTabs === 0" mode="widthFix">
</image>
<image class="msg-type-icon" src="@/static/icon/msg-icon.png" v-if="importTabs === 1" mode="widthFix">
</image>
</view>
<block v-if="importTabs === 0">
<view class="chat-mp3" hover-class="chat-hover" @touchstart="startAudio" @touchend="chendAudio">
<text class="chat-mp3-text">按住说话</text>
</view>
</block>
<block v-if="importTabs === 1">
<input class="chat-input" type="text" v-model="inputTxt" confirm-type="发送" @confirm="send"
cursor-spacing="10" />
</block>
<text class="chat-push" size="mini" @click="send">发送</text>
</view>
<!-- 录音中提示 -->
<view class="audio-transcribe" v-if="showAudioTranscribe">
<image class="audio-transcribe-src" src="@/static/icon/record-icon.png" mode="widthFix"></image>
<text class="audio-transcribe-text">录音中 {{transcribeTime}} s</text>
</view>
</view> </view>
</template> </template>
@@ -55,171 +31,94 @@
import { import {
timeCustomCN timeCustomCN
} from '@/utils/filters.js' } from '@/utils/filters.js'
import * as RongIMLib from "@/uni_modules/RongCloud-IMWrapper/js_sdk/index" import * as RongIMLib from '@/uni_modules/RongCloud-IMWrapper/js_sdk/index'
import im from '@/utils/im/index.js' import im from '@/utils/im/index.js'
import permision from "@/js_sdk/wa-permission/permission.js" import showVoice from '../components/showVoice'
import showVoice from './components/showVoice' import showImage from '../components/showImage'
import showImage from './components/showImage' import showText from '../components/showText'
import showText from './components/showText' import sentMessageBar from '../components/sentMessageBar'
var transcribe
const recorderManager = uni.getRecorderManager()
const ChatList = uni.requireNativePlugin('dom') const ChatList = uni.requireNativePlugin('dom')
export default { export default {
data() { data() {
return { return {
targetId: '', targetId: '',
inputTxt: '',
messages: [], messages: [],
conversationType: 1, conversationType: 1,
userInfo: { userInfo: {
name: '', name: '',
userId: '', userId: '',
portraitUrl: '' portraitUrl: ''
}, }
importTabs: 1,
showAudioTranscribe: false,
transcribeTime: 60,
audioContextPaused: true
} }
}, },
components: { components: {
sentMessageBar,
showVoice, showVoice,
showImage, showImage,
showText showText
}, },
onLoad(e) { onLoad(e) {
this.targetId = e.targetId this.targetId = e.targetId
this.conversationType = e.conversationType // 会话类型 this.userInfo = this.$store.getters.contactInfo(this.targetId)
this.userInfo = this.$store.getters.userInfo(this.targetId)
uni.setNavigationBarTitle({ uni.setNavigationBarTitle({
title: this.$store.getters.userInfo(this.targetId).name title: this.userInfo.name
}) })
// 获取消息列表
RongIMLib.clearMessagesUnreadStatus(this.conversationType, this.targetId, new Date().getTime())
im.setNotifyBadge()
RongIMLib.sendReadReceiptMessage(this.conversationType, this.targetId, new Date().getTime())
this.getMessageList() this.getMessageList()
// 监听消息已读状态
// 监听消息回执 uni.$on('onReadReceiptReceived', (data) => {
RongIMLib.addReadReceiptReceivedListener(({
data
}) => {
if (data.targetId == this.targetId) { if (data.targetId == this.targetId) {
this.getMessageList() this.getMessageList()
} }
}) })
// 监听收到新消息,判断是否是当前会话,更新会话内容 // 监听收到新消息,判断是否是当前会话,更新会话内容
uni.$on('onReceiveMessage', (msg) => { uni.$on('onReceiveMessage', (msg) => {
if (msg.targetId == this.targetId) { if (msg.targetId == this.targetId) {
RongIMLib.clearMessagesUnreadStatus(msg.conversationType, msg.targetId, msg.sentTime)
RongIMLib.sendReadReceiptMessage(msg.conversationType, msg.targetId, msg.sentTime)
this.getMessageList() this.getMessageList()
im.setNotifyBadge()
} }
}) })
}, },
beforeDestroy() { onBackPress() {
uni.$off('onReceiveMessage') uni.$off('onReceiveMessage')
}, },
onUnload() {
RongIMLib.clearReadReceiptReceivedListener()
},
computed: {
disabled() {
return this.inputTxt.length == 0
}
},
methods: { methods: {
customCN(val) { customCN(val) {
return timeCustomCN(val) return timeCustomCN(val)
}, },
// 检查安卓录制权限
async getAndroidPermission() {
return await permision.requestAndroidPermission('android.permission.RECORD_AUDIO')
},
// 切换聊天信息
msgType() {
this.importTabs = this.importTabs === 1 ? 0 : 1
},
// 录制语音消息
startAudio(e) {
this.getAndroidPermission().then(code => {
switch (code) {
case 1:
this.showAudioTranscribe = true
recorderManager.start()
transcribe = setInterval(() => {
this.transcribeTime -= 1
if (this.transcribeTime === 0) {
this.chendAudio()
}
}, 1000)
break;
case 0:
uni.showToast({
title: '暂无麦克风权限,请前往应用设置开启麦克风',
icon: 'none'
})
break;
case -1:
uni.showToast({
title: '应用权限错误',
icon: 'none'
})
break;
}
})
},
// 结束录音
chendAudio(e) {
if (!this.showAudioTranscribe) return
recorderManager.stop()
clearInterval(transcribe)
// 监听录音结束
recorderManager.onStop(res => {
im.sentVoice(this.conversationType, this.targetId, res.tempFilePath, (60 - this
.transcribeTime), () => {
setTimeout(() => {
this.getMessageList()
}, 500)
})
this.transcribeTime = 60
this.showAudioTranscribe = false
})
},
// 获取消息列表 // 获取消息列表
getMessageList() { getMessageList() {
im.getMessageList(this.conversationType, this.targetId, (messages) => { im.getMessageList(
this.messages = messages.reverse() this.conversationType,
this.scrollBottom() this.targetId,
}) new Date().getTime(),
10,
true,
(messages) => {
this.messages = messages.reverse()
this.scrollBottom()
})
}, },
// 发送文本消息 // 展示好友信息, type 1 是自己, 2 是对方
send() { showUser(targetId, type) {
if (this.inputTxt === '') return
im.sentText(this.conversationType, this.targetId, this.inputTxt, () => {
setTimeout(() => {
this.getMessageList()
}, 500)
this.inputTxt = ''
})
},
// 展示好友信息
showFriend(targetId, type) {
uni.navigateTo({ uni.navigateTo({
url: type === 1 ? '/pages/im/friends/mine?targetId=' + targetId : url: type === 1 ? '/pages/im/friends/mine?targetId=' + targetId :
'/pages/im/friends/info?targetId=' + targetId '/pages/im/friends/info?targetId=' + targetId
}) })
}, },
// 滚动到底部 // 滚动到底部
scrollBottom() { scrollBottom(type) {
// 清理当前会话,未读消息数量
RongIMLib.clearMessagesUnreadStatus(this.conversationType, this.targetId, new Date().getTime() + 1100)
// 发送消息已读状态给对方
RongIMLib.sendReadReceiptMessage(this.conversationType, this.targetId, new Date().getTime())
// 更新badge提醒数量
im.setNotifyBadge()
setTimeout(() => { setTimeout(() => {
let el = this.$refs.chatBottom let el = this.$refs.chatBottom
ChatList.scrollToElement(el, { ChatList.scrollToElement(el, {
offset: 0,
animated: false animated: false
}) })
}, 50) }, 50)
@@ -228,155 +127,69 @@
} }
</script> </script>
<style scoped> <style scoped lang="scss">
.audio-transcribe {
background: rgba(0, 0, 0, .6);
position: fixed;
height: 200rpx;
width: 300rpx;
border-radius: 10rpx;
z-index: 99;
top: 550rpx;
left: 225rpx;
flex-direction: column;
align-items: center;
justify-content: center;
}
.audio-transcribe-src {
width: 88rpx;
height: 88rpx;
}
.audio-transcribe-text {
font-size: 28rpx;
color: #FFFFFF;
}
/* 窗口 */ /* 窗口 */
.chat { .chat {
background: #F3F6FB; background: $window-color;
flex: 1; flex: 1;
}
.chat-scroll { .body {
flex: 1; flex: 1;
}
.cell { .cell {
padding: 10rpx 30rpx; padding: 10rpx 30rpx;
}
.cell-time { .time {
justify-content: center; justify-content: center;
align-items: center; align-items: center;
padding-bottom: 20rpx; padding-bottom: 20rpx;
}
.cell-time-text { .text {
background: #fff; background: #fff;
font-size: 24rpx; font-size: 24rpx;
color: #666; color: #666;
line-height: 40rpx; line-height: 40rpx;
padding: 0 20rpx; padding: 0 20rpx;
border-radius: 10rpx; border-radius: 10rpx;
} }
}
.cell-item { .cell-item {
width: 690rpx; width: 690rpx;
justify-content: flex-start; justify-content: flex-start;
}
.cell-footer {
height: 20rpx;
}
.active { &.left {
width: 78rpx; flex-direction: row;
height: 78rpx; }
background-color: white;
border-radius: 10rpx;
}
.msg { &.right {
margin: 0 20rpx; flex-direction: row-reverse;
}
.state { .state {
padding-top: 10rpx; flex-direction: row;
} justify-content: flex-end;
}
}
.state-text { .msg {
font-size: 24rpx; margin: 0 20rpx;
color: #666;
}
.cell-item.left { .state {
flex-direction: row; padding-top: 10rpx;
}
.cell-item.right { .state-text {
flex-direction: row-reverse; font-size: 24rpx;
} color: #666;
}
}
}
}
.cell-item.right .state { .cell-footer {
flex-direction: row; height: 20rpx;
justify-content: flex-end; }
} }
}
/* footer */
.chat-footer {
background: white;
padding: 20rpx 30rpx;
display: flex;
justify-content: space-between;
flex-direction: row;
}
.msg-type {
width: 70rpx;
height: 70rpx;
}
.msg-type>.msg-type-icon {
margin: 5rpx;
width: 60rpx;
height: 60rpx;
}
.chat-mp3 {
background: #F3F6FB;
height: 70rpx;
line-height: 70rpx;
justify-content: center;
align-items: center;
width: 460rpx;
border-radius: 10rpx;
margin-right: 15rpx;
}
.chat-mp3-text {
font-size: 30rpx;
color: #333;
}
.chat-input {
background: #F3F6FB;
height: 70rpx;
width: 460rpx;
border-radius: 10rpx;
margin-right: 15rpx;
padding: 0 20rpx;
}
.chat-push {
background: #34CE98;
color: white;
width: 120rpx;
line-height: 70rpx;
text-align: center;
border-radius: 10rpx;
font-size: 30rpx;
font-weight: bold;
} }
</style> </style>

View File

@@ -1,15 +0,0 @@
<template>
<view>
</view>
</template>
<script>
export default {
}
</script>
<style>
</style>

View File

@@ -1,15 +0,0 @@
<template>
<view>
</view>
</template>
<script>
export default {
}
</script>
<style>
</style>

View File

@@ -1,49 +0,0 @@
<template>
<view class="im--img" :class="guest ? 'right': 'left'">
<image class="src" :src="msg" @click="openImg" mode="widthFix"></image>
</view>
</template>
<script>
export default {
name: 'showImage',
props: {
msg: {
type: String,
default: 'https://images.pexels.com/photos/9024609/pexels-photo-9024609.jpeg'
},
guest: {
type: Boolean,
default: true
}
},
methods: {
openImg() {
uni.previewImage({
urls: [this.msg],
current: 1
})
}
}
}
</script>
<style scoped>
.im--img {
padding: 19rpx;
}
.im--img.left {
border-radius: 0 20rpx 20rpx 20rpx;
background: white;
}
.im--img.right {
border-radius: 20rpx 0 20rpx 20rpx;
background: #34CE98;
}
.src {
width: 240rpx;
}
</style>

View File

@@ -1,46 +0,0 @@
<template>
<view class="im--box">
<text class="im--text" :class="guest ? 'right': 'left'">{{msg}}</text>
</view>
</template>
<script>
export default {
name: 'showText',
data() {
return {
};
},
props: {
msg: {
type: String,
default: ''
},
guest: {
type: Boolean,
default: true
}
}
}
</script>
<style scoped>
.im--text {
max-width: 400rpx;
padding: 20rpx;
font-size: 28rpx;
line-height: 40rpx;
}
.im--text.left {
border-radius: 0 20rpx 20rpx 20rpx;
background: white;
}
.im--text.right {
border-radius: 20rpx 0 20rpx 20rpx;
background: #34CE98;
color: white;
}
</style>

View File

@@ -1,499 +0,0 @@
<template>
<view class="chat-content">
<scroll-view class="chat-scrool" :scroll-y="true" :scroll-into-view="scrollIntoID"
:scroll-with-animation="false">
<!-- 聊天窗口 -->
<view class="chat-item" v-for="(item,index) in messages" :key="index">
<view class="chat-item-article" :class="item.messageDirection == 1 ? 'right' : 'left'">
<view class="chat-msg">
<!-- 文字消息 -->
<view class="chat-msg-text">{{ item.content.content }}</view>
<!-- 语音消息 -->
<view class="chat-msg-audio" @click="onPlayMsg()">
<image v-if="item.messageDirection == 0" src="@/static/icon/audio_green.png"
mode="widthFix"></image>
10"
<image v-if="item.messageDirection == 1" src="@/static/icon/audio_white.png"
mode="widthFix"></image>
</view>
<!-- 预留一些图片,语音表情包等位置 -->
</view>
<view class="chat-status" :class="{'hide': item.sentStatus == 50}"
v-if="item.messageDirection == 1">{{targetId}}{{ item.sentStatus == 50 ? '已读': '未读'}}</view>
<view class="chat-avatar" @click="showFriend(targetId, item.messageDirection)">
<u-avatar v-if="item.messageDirection == 2" shape="square" bg-color="#ffffff"
:src="userInfo.portraitUrl"></u-avatar>
<u-avatar v-else shape="square" bg-color="#ffffff" :src="$store.getters.sender.portraitUrl" />
</view>
</view>
<view class="chat-item-time" :id="'chatId_'+index">
<text>{{ item.sentTime|timeCustomCN }}</text>
</view>
</view>
</scroll-view>
<view class="chat-footer">
<view class="msg-type" @click="msgType">
<image src="@/static/icon/key-icon.png" v-if="importTabs === 0" mode="widthFix"></image>
<image src="@/static/icon/msg-icon.png" v-if="importTabs === 1" mode="widthFix"></image>
</view>
<block v-if="importTabs === 0">
<button type="default" class="chat-mp3" hover-class="chat-hover" @touchstart="startAudio"
@touchend="chendAudio">按住 说话</button>
</block>
<block v-if="importTabs === 1">
<input class="chat-input" type="text" v-model="inputTxt" confirm-type="发送" @confirm="send"
cursor-spacing="10" />
</block>
<button class="chat-push" :disabled="disabled" size="mini" @click="send">发送</button>
</view>
<!-- 录音中提示 -->
<view class="audio-transcribe" v-if="showAudioTranscribe">
<image src="@/static/icon/record-icon.png" mode="widthFix"></image>
<view class="text">录音中 {{transcribeTime}} s</view>
</view>
</view>
</template>
<script>
import * as RongIMLib from "@/uni_modules/RongCloud-IMWrapper/js_sdk/index"
import im from '@/utils/im/index.js'
import permision from "@/js_sdk/wa-permission/permission.js"
var transcribe
var recorderManager = uni.getRecorderManager()
export default {
data() {
return {
targetId: '',
scrollIntoID: 'chatID_0',
inputTxt: '',
messages: [],
conversationType: 1,
totalCount: 0,
userInfo: {
name: '',
userId: '',
portraitUrl: ''
},
importTabs: 0,
showAudioTranscribe: false,
transcribeTime: 60,
audioSrc: '',
audioContextPaused: true
}
},
onLoad(e) {
this.targetId = e.targetId
this.conversationType = e.conversationType // 会话类型
// 消息总数量
RongIMLib.getMessageCount(this.conversationType, this.targetId, ({
code,
count
}) => {
if (code == 0) {
this.totalCount = count
}
})
this.userInfo = this.$store.getters.userInfo(this.targetId)
uni.setNavigationBarTitle({
title: this.$store.getters.userInfo(this.targetId).name
})
RongIMLib.clearMessagesUnreadStatus(this.conversationType, this.targetId, new Date().getTime())
im.setNotifyBadge()
RongIMLib.sendReadReceiptMessage(this.conversationType, this.targetId, new Date().getTime())
this.getMessageList()
// 监听消息回执
RongIMLib.addReadReceiptReceivedListener(({
data
}) => {
if (data.targetId == this.targetId) {
this.getMessageList()
}
})
// 监听录音结束
recorderManager.onStop(res => {
if (res.tempFilePath) this.audioSrc = res.tempFilePath
console.log('------------------获取到了录音的临时路径---------------')
console.log(res.tempFilePath)
})
// 简童收到新消息,判断是否是当前会话,更新会话内容
uni.$on('onReceiveMessage', (msg) => {
if (msg.targetId == this.targetId) {
RongIMLib.clearMessagesUnreadStatus(msg.conversationType, msg.targetId, msg.sentTime)
RongIMLib.sendReadReceiptMessage(msg.conversationType, msg.targetId, msg.sentTime)
this.getMessageList()
im.setNotifyBadge()
}
})
},
beforeDestroy() {
uni.$off('onReceiveMessage')
},
onUnload() {
RongIMLib.clearReadReceiptReceivedListener()
},
computed: {
disabled() {
return this.inputTxt.length == 0
}
},
onNavigationBarButtonTap(e) {
if (e.index == 0) {
uni.navigateTo({
url: '/pages/im/private/setting?targetId=' + this.targetId +
'&conversationType=' + this.conversationType,
events: {
messageClear: () => {
this.getMessageList()
console.log('聊天消息被清空');
}
}
})
}
},
methods: {
// 播放语音消息
onPlayMsg() {
let innerAudioContext = uni.createInnerAudioContext()
innerAudioContext.src = this.audioSrc
if (this.audioContextPaused) {
innerAudioContext.play()
this.audioContextPaused = false
return
}
innerAudioContext.stop()
innerAudioContext.onStop(resStop => {
this.audioContextPaused = true
})
},
// 检查安卓录制权限
async getAndroidPermission() {
return await permision.requestAndroidPermission('android.permission.RECORD_AUDIO')
},
// 切换聊天信息
msgType() {
this.importTabs = this.importTabs === 1 ? 0 : 1
},
// 录制语音消息
startAudio(e) {
this.getAndroidPermission().then(code => {
switch (code) {
case 1:
this.showAudioTranscribe = true
recorderManager.start()
transcribe = setInterval(() => {
this.transcribeTime -= 1
if (this.transcribeTime === 0) {
this.chendAudio()
}
}, 1000)
break;
case 0:
uni.showToast({
title: '暂无麦克风权限,请前往应用设置开启麦克风',
icon: 'none'
})
break;
case -1:
uni.showToast({
title: '应用权限错误',
icon: 'none'
})
break;
}
})
},
// 结束录音
chendAudio(e) {
if (!this.showAudioTranscribe) return
recorderManager.stop()
clearInterval(transcribe)
this.transcribeTime = 60
this.showAudioTranscribe = false
},
getMessageList() {
// 获取消息列表
const objectNames = [
'RC:TxtMsg',
'RC:VcMsg',
'RC:HQVCMsg',
'RC:ImgMsg',
'RC:GIFMsg',
'RC:ImgTextMsg',
'RC:FileMsg',
'RC:LBSMsg',
'RC:SightMsg',
'RC:ReferenceMsg',
'RC:CombineMsg'
]
const timeStamp = new Date().getTime()
const count = 20 // 获取的消息数量
const isForward = true // 是否向前获取
RongIMLib.getHistoryMessagesByTimestamp(this.conversationType, this.targetId, objectNames, timeStamp,
count,
isForward,
({
code,
messages
}) => {
if (code === 0) {
this.messages = messages.reverse()
this.scrollBottom()
}
}
)
},
send() {
im.sendMsg(this.conversationType, this.targetId, this.inputTxt, () => {
this.getMessageList()
this.inputTxt = ''
})
},
showFriend(targetId, type) {
this.$Router.push({
name: type === 1 ? 'imFriendsMine' : 'imFriendsInfo',
params: {
targetId
}
})
},
scrollBottom() {
this.$nextTick(function() {
setTimeout(() => {
let len = this.messages.length
if (len) {
if (this.scrollIntoID == "chatId_" + (len - 1)) {
this.scrollIntoID = "chatId_" + (len - 2)
this.scrollBottom()
} else if (this.scrollIntoID == "chatId_" + (len - 2)) {
this.scrollIntoID = "chatId_" + (len - 1)
} else {
this.scrollIntoID = "chatId_" + (len - 1)
}
} else {
this.scrollIntoID = "chatId_0";
}
}, 50)
})
}
}
}
</script>
<style lang="scss" scoped>
.chat-content {
height: 100vh;
background: $window-color;
.audio-transcribe {
background: rgba($color: #000000, $alpha: .6);
position: fixed;
height: 200rpx;
width: 300rpx;
border-radius: $radius;
color: white;
z-index: 99;
top: 50%;
left: 50%;
margin-top: -200rpx;
margin-left: -150rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
image {
width: 88rpx;
height: 88rpx;
}
.text {
font-size: $title-size-m;
}
}
.chat-scrool {
height: calc(100vh - 140rpx);
box-sizing: border-box;
padding-bottom: $padding;
.chat-item {
.chat-item-time {
text-align: center;
padding: $padding/2 $padding;
text {
background: rgba($color: #000000, $alpha: .2);
color: white;
font-size: $title-size-sm - 4;
line-height: 40rpx;
padding: 0 15rpx;
display: inline-block;
border-radius: $radius-lg;
}
}
.chat-item-article {
position: relative;
padding: 10rpx ($padding + 110) 0;
overflow: hidden;
min-height: 40px;
.chat-msg {
overflow: hidden;
.chat-msg-text {
display: inline-block;
padding: ($padding - 10) $padding;
color: $text-color;
box-sizing: border-box;
font-size: $title-size-lg;
}
.chat-msg-audio {
@extend .chat-msg-text;
width: 50%;
image {
width: 38rpx;
height: 38rpx;
margin: 0;
vertical-align: middle;
}
}
}
.chat-status {
color: $text-gray;
font-size: $title-size-sm;
text-align: right;
&.hide {
color: $text-gray-m;
}
}
.chat-avatar {
position: absolute;
top: 0;
background: white;
border-radius: $radius-m;
}
&.left {
.chat-avatar {
left: $margin;
}
.chat-msg {
.chat-msg-text {
background-color: white;
border-radius: 0 $radius*2 $radius*2 $radius*2;
}
}
}
&.right {
.chat-avatar {
right: $margin;
}
.chat-msg {
text-align: right;
.chat-msg-text {
border-radius: $radius*2 0 $radius*2 $radius*2;
background: $main-color;
color: white;
}
}
}
}
}
}
.chat-footer {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 140rpx;
padding: 20rpx ($padding + 150rpx) 40rpx ($padding + 94rpx);
background: white;
box-sizing: border-box;
z-index: 99;
.msg-type {
position: absolute;
top: 20rpx;
left: $padding;
line-height: 80rpx;
image {
width: 64rpx;
height: 64rpx;
vertical-align: middle;
margin-bottom: 10rpx;
}
}
.chat-input {
background: $window-color;
height: 80rpx;
border-radius: $radius-lg;
font-size: $title-size-m;
padding: 0 $padding;
box-sizing: border-box;
}
.chat-mp3 {
background: $window-color;
line-height: 80rpx;
text-align: center;
border-radius: $radius-lg;
font-weight: bold;
color: #333;
font-size: $title-size-m;
&::after {
display: none;
}
}
.chat-hover {
opacity: .5;
}
.chat-push[size='mini'] {
position: absolute;
right: $margin;
top: 20rpx;
height: 80rpx;
line-height: 80rpx;
padding: 0;
margin: 0;
width: 120rpx;
background: $main-color;
color: white;
border-radius: $radius-lg;
font-size: $title-size-m;
font-weight: bold;
&[disabled] {
background: rgba($color: $main-color, $alpha: .5);
}
&::after {
display: none;
}
}
}
}
</style>

View File

@@ -4,7 +4,15 @@
<view class="status-main"> <view class="status-main">
<view class="helloe">欢迎使用ZH-HEALTH健康</view> <view class="helloe">欢迎使用ZH-HEALTH健康</view>
<view class="btns"> <view class="btns">
<view class="btns-item" @click="onBtn('signIndex')"><image src="@/static/icon/sign-icon.gif" mode="widthFix"></image></view> <!-- 打卡图标gif -->
<!-- <view class="btns-item" @click="$Router.push({name: 'signIndex'})"><image src="@/static/icon/sign-icon.gif" mode="widthFix"></image></view> -->
<view class="btns-clock" @click="$Router.push({name: 'signIndex'})">
<image src="@/static/icon/clock_icon.png" mode="widthFix"></image>
<view class="btns-clock-text">
打卡
</view>
</view>
<view class="btns-item show" @click="onBtn('noticeIndex')"><uni-icons custom-prefix="iconfont" type="icon-pinglun" size="25"></uni-icons></view> <view class="btns-item show" @click="onBtn('noticeIndex')"><uni-icons custom-prefix="iconfont" type="icon-pinglun" size="25"></uni-icons></view>
</view> </view>
</view> </view>
@@ -101,12 +109,29 @@
align-items: center; align-items: center;
.helloe { .helloe {
line-height: 100rpx; line-height: 100rpx;
font-size: $title-size + 10; font-size: $title-size + 4;
font-weight: bold; font-weight: bold;
} }
.btns { .btns {
margin-left: $margin; margin-left: $margin;
display: flex; display: flex;
.btns-clock {
color: #885100;
height: 50rpx;
font-size: $title-size-sm - 2;
border-radius: 40rpx;
display: flex;
background-image: linear-gradient(to right, #fce938, #ffce36);
image {
width: 48rpx;
display: inline-block;
}
.btns-clock-text {
line-height: 50rpx;
font-weight: 600;
padding: 0 20rpx 0 8rpx;
}
}
.btns-item { .btns-item {
position: relative; position: relative;
text-align: right; text-align: right;

View File

@@ -9,7 +9,7 @@
<view class="number"> <view class="number">
<text>{{ dateData.total }}</text> <text>{{ dateData.total }}</text>
</view> </view>
本月签到 本月打卡
</view> </view>
</view> </view>
<view class="label"> <view class="label">
@@ -18,7 +18,7 @@
<view class="number"> <view class="number">
<text>{{ dateData.continue }}</text> <text>{{ dateData.continue }}</text>
</view> </view>
累计签到 累计打卡
</view> </view>
</view> </view>
</view> </view>
@@ -50,25 +50,28 @@
</view> </view>
</view> </view>
<view class="day" v-for="(item, index) in dateArr"> <view class="day" v-for="(item, index) in dateArr" :key="index">
<view class="day-label" v-for="(items, index) in item"> <view class="day-label" v-for="(items, index) in item" :key="index">
<view class="label-block" :class="{active : items.isSign}" v-if="!items.isHidden">{{ items.isSign ? '' : items.date }}</view> <view class="label-block" :class="{active : items.isSign}" v-if="!items.isHidden">
<uni-icons v-if="items.isSign" class="search-icon" custom-prefix="iconfont" type="icon-dui" color="#ffffff" size="18"></uni-icons>
<block v-else>{{ items.date }}</block>
</view>
</view> </view>
</view> </view>
</view> </view>
<view class="signBtn"> <view class="signBtn">
<view class="btn" @click="signClick" :class="{active : dateData.isSign}"> <view class="btn" @click="signClick" :class="{active : dateData.isSign}">
{{ dateData.isSign ? '日已' : '立即签到'}} {{ dateData.isSign ? '日已打卡' : '今日打卡'}}
</view> </view>
</view> </view>
</view> </view>
<view class="tipsText"> <view class="tipsText">
ZH大健康用户签到 ZH大健康用户打卡
</view> </view>
</view> </view>
</view> </view>
</template> </template>
<script> <script>
import { date, sign } from '@/apis/interfaces/sign' import { date, sign } from '@/apis/interfaces/sign'
export default { export default {
@@ -112,9 +115,9 @@
}) })
} }
} }
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
page { page {
background: $window-color; background: $window-color;
@@ -174,7 +177,9 @@
} }
} }
.signDate { .signDate {
padding: $padding; padding: $padding;
border-radius: $radius-m;
background-color: white;
.date { .date {
text-align: center; text-align: center;
font-size: $title-size + 6; font-size: $title-size + 6;
@@ -209,6 +214,12 @@
height: 56rpx; height: 56rpx;
line-height: 56rpx; line-height: 56rpx;
text-align: center; text-align: center;
position: relative;
.label-icon {
position: absolute;
left: 10rpx;
top: 0;
}
&.active { &.active {
background-color: $main-color; background-color: $main-color;
color: white; color: white;
@@ -232,7 +243,8 @@
color: $text-gray; color: $text-gray;
} }
} }
} }
}
.tipsText { .tipsText {
text-align: center; text-align: center;
@@ -241,4 +253,4 @@
color: $text-gray-m; color: $text-gray-m;
} }
} }
color: $text-gray-m; </style>

View File

@@ -155,12 +155,10 @@
if (this.$store.state.token === '') return; if (this.$store.state.token === '') return;
info() info()
.then(res => { .then(res => {
console.log(res);
uni.setNavigationBarTitle({ uni.setNavigationBarTitle({
title: res.nickname title: res.nickname
}); });
this.userInfo = res; this.userInfo = res;
console.log(res);
}) })
.catch(err => { .catch(err => {
uni.showToast({ uni.showToast({

View File

@@ -1,64 +1,68 @@
<template> <template>
<view class="content vertical"> <view class="content vertical">
<!-- logo --> <!-- logo -->
<image class="logo" src="@/static/wallet/logo.png" mode="widthFix"></image> <image class="logo" src="@/static/wallet/logo.png" mode="widthFix"></image>
<!-- 副标题 --> <!-- 副标题 -->
<view class="sub-title">激活您的ZH健康钱包获取钱包地址地址可以理解为您的个人银行卡卡号</view> <view class="sub-title">激活您的ZH健康钱包获取钱包地址地址可以理解为您的个人银行卡卡号</view>
<!-- 按钮 --> <!-- 按钮 -->
<view class="wallet-btn" @click="$Router.replace({name: 'WalletCreate'})">激活钱包</view> <view class="wallet-btn" @click="$Router.replace({name: 'WalletCreate'})">激活钱包</view>
</view> </view>
</template> </template>
<script> <script>
export default { export default {
data() { data() {
return { return {
} }
}, },
methods: { methods: {
} }
} }
</script> </script>
<style> <style>
page { page {
background-color: white; background-color: white;
} }
</style> </style>
<style lang="scss" scoped> <style lang="scss" scoped>
.content{ .content {
position: relative; position: relative;
height: 100vh; height: 100vh;
padding: 0 15vw; padding: 0 15vw;
text-align: center; text-align: center;
.logo{
width: 138rpx; .logo {
vertical-align: top; width: 138rpx;
margin-bottom: 20vh; vertical-align: top;
} margin-bottom: 20vh;
.sub-title{ }
font-size: $title-size-m;
color: $text-gray; .sub-title {
} font-size: $title-size-m;
.wallet-btn{ color: $text-gray;
width: 100%; }
background-color: $main-color;
height: 90rpx; .wallet-btn {
line-height: 90rpx; width: 100%;
margin-top: $margin * 2; background-color: $main-color;
border-radius: $radius-lg; height: 90rpx;
color: white; line-height: 90rpx;
font-weight: bold; margin-top: $margin * 2;
font-size: $title-size; border-radius: $radius-lg;
&.hollow{ color: white;
background-color: white; font-weight: bold;
color: $main-color; font-size: $title-size;
border:solid 2rpx $main-color;
box-sizing: border-box; &.hollow {
} background-color: white;
} color: $main-color;
} border: solid 2rpx $main-color;
box-sizing: border-box;
}
}
}
</style> </style>

View File

@@ -1,187 +1,193 @@
<template> <template>
<view> <view>
<!-- 设置钱包密码 --> <!-- 设置钱包密码 -->
<view class="password"> <view class="password">
<view class="prompt">请设置6位数字密码建议不要使用连续的数字</view> <view class="prompt">请设置6位数字密码建议不要使用连续的数字</view>
<view class="group"> <view class="group">
<view class="inputs" @click="onShowKet('password')"> <view class="inputs" @click="onShowKet('password')">
<block v-if="password.length > 0"> <block v-if="password.length > 0">
<text v-for="item in password.length" :key="item"></text> <text v-for="item in password.length" :key="item"></text>
</block> </block>
<block v-if="keyShow && valKye === 'password'"> <block v-if="keyShow && valKye === 'password'">
<text class="flicker-animation">|</text> <text class="flicker-animation">|</text>
</block> </block>
<block v-else> <block v-else>
<text v-if="password.length === 0" class="placeholder">请设置密码</text> <text v-if="password.length === 0" class="placeholder">请设置密码</text>
</block> </block>
</view> </view>
<view class="inputs" :class="{'hide': verify === ''}" @click="onShowKet('verify')"> <view class="inputs" :class="{'hide': verify === ''}" @click="onShowKet('verify')">
<block v-if="verify.length > 0"> <block v-if="verify.length > 0">
<text v-for="item in verify.length" :key="item"></text> <text v-for="item in verify.length" :key="item"></text>
</block> </block>
<block v-if="keyShow && valKye === 'verify'"> <block v-if="keyShow && valKye === 'verify'">
<text class="flicker-animation">|</text> <text class="flicker-animation">|</text>
</block> </block>
<block v-else> <block v-else>
<text v-if="verify.length === 0" class="placeholder">请确认密码</text> <text v-if="verify.length === 0" class="placeholder">请确认密码</text>
</block> </block>
</view> </view>
</view> </view>
</view> </view>
<!-- key键盘 --> <!-- key键盘 -->
<u-keyboard <u-keyboard mode="number" random dotDisabled :overlay="false" :show="keyShow" :showCancel="false"
mode="number" @change="keyValChange" @backspace="keyValBackspace" @confirm="keyShow = false"></u-keyboard>
random <!-- 按钮 -->
dotDisabled <view class="buttons">
:overlay="false" <button type="default" form-type="submit" @click="createWallet">确认激活</button>
:show="keyShow" </view>
:showCancel="false" </view>
@change="keyValChange" </template>
@backspace="keyValBackspace"
@confirm="keyShow = false" <script>
></u-keyboard> import {
<!-- 按钮 --> security
<view class="buttons"> } from '@/apis/interfaces/wallet';
<button type="default" form-type="submit" @click="createWallet">确认激活</button> export default {
</view> data() {
</view> return {
</template> password: '',
verify: '',
<script> valKye: '',
import { security } from '@/apis/interfaces/wallet'; keyShow: false
export default { };
data() { },
return { methods: {
password: '', // 唤起key
verify : '', onShowKet(key) {
valKye : '', this.valKye = key
keyShow : false this.keyShow = true
}; },
}, // 键盘输入
methods: { keyValChange(e) {
// 唤起key if (this[this.valKye].length >= 6) return
onShowKet(key){ this[this.valKye] += e
this.valKye = key },
this.keyShow = true // 键盘删除
}, keyValBackspace(e) {
// 键盘输入 if (this[this.valKye].length) this[this.valKye] = this[this.valKye].substr(0, this[this.valKye].length -
keyValChange(e){ 1)
if(this[this.valKye].length >= 6) return },
this[this.valKye] += e // 激活钱包
}, createWallet() {
// 键盘删除 if (this.password === '' || this.verify === '') {
keyValBackspace(e){ uni.showToast({
if(this[this.valKye].length) this[this.valKye] = this[this.valKye].substr(0, this[this.valKye].length - 1) icon: 'none',
}, title: '请设置密码'
// 激活钱包 });
createWallet() { return;
if (this.password === '' || this.verify === '') { }
uni.showToast({ if (this.password !== this.verify) {
icon: 'none', uni.showToast({
title: '请设置密码' icon: 'none',
}); title: '两次输入密码不一致'
return; });
} return;
if (this.password !== this.verify) { }
uni.showToast({
icon: 'none', security({
title: '两次输入密码不一致' code: Number(this.password)
}); }).then(res => {
return; this.$Router.replace({
} name: 'WalletProperty'
})
security({ }).catch(err => {
code: Number(this.password) uni.showToast({
}).then(res => { icon: 'none',
this.$Router.replace({ title: err.message
name: 'WalletProperty' });
}) });
}).catch(err => { }
uni.showToast({ }
icon: 'none', };
title: err.message </script>
});
}); <style>
} .flicker-animation {
} animation: flicker .8s infinite;
}; }
</script>
@keyframes flicker {
<style> 0% {
.flicker-animation{ opacity: 0;
animation: flicker .8s infinite; }
}
@keyframes flicker{ 100 {
0%{opacity: 0;} opacity: 1;
100{opacity: 1;} }
} }
</style> </style>
<style lang="scss" scoped> <style lang="scss" scoped>
// 副标题 // 副标题
.sub-title { .sub-title {
color: $text-gray; color: $text-gray;
text-align: center; text-align: center;
margin: $margin * 2 $margin; margin: $margin * 2 $margin;
font-size: $title-size-m; font-size: $title-size-m;
} }
// 设置密码 // 设置密码
.password { .password {
padding: 0 $padding * 2; padding: 0 $padding * 2;
.prompt {
margin-top: $margin * 2; .prompt {
font-size: $title-size-m; margin-top: $margin * 2;
color: $main-color; font-size: $title-size-m;
text-align: center; color: $main-color;
} text-align: center;
}
.group {
padding-top: $padding; .group {
.inputs { padding-top: $padding;
padding: 10rpx $padding + 10;
margin-top: $margin; .inputs {
border-radius: $radius-m; padding: 10rpx $padding + 10;
background-color: $window-color; margin-top: $margin;
height: 70rpx; border-radius: $radius-m;
line-height: 70rpx; background-color: $window-color;
font-size: $title-size-lg; height: 70rpx;
text-align: center; line-height: 70rpx;
text{ font-size: $title-size-lg;
padding: 0 10rpx; text-align: center;
text-align: center;
} text {
.placeholder{ padding: 0 10rpx;
color: $text-gray; text-align: center;
} }
}
} .placeholder {
} color: $text-gray;
}
// 按钮 }
.buttons { }
padding: $padding * 2; }
.text {
text-align: center; // 按钮
line-height: 90rpx; .buttons {
height: 90rpx; padding: $padding * 2;
margin-bottom: $margin * 2;
font-size: $title-size-lg; .text {
color: $main-color; text-align: center;
font-weight: bold; line-height: 90rpx;
} height: 90rpx;
margin-bottom: $margin * 2;
button { font-size: $title-size-lg;
height: 90rpx; color: $main-color;
line-height: 90rpx; font-weight: bold;
background-color: $main-color; }
border-radius: $radius-m;
color: white; button {
font-weight: bold; height: 90rpx;
font-size: $title-size; line-height: 90rpx;
&::after{ background-color: $main-color;
display: none; border-radius: $radius-m;
} color: white;
} font-weight: bold;
} font-size: $title-size;
&::after {
display: none;
}
}
}
</style> </style>

View File

@@ -1,112 +1,122 @@
<template> <template>
<view> <view>
<!-- 提示信息 --> <!-- 提示信息 -->
<view class="prompt">请按照顺序记录并确保正确备份助记词</view> <view class="prompt">请按照顺序记录并确保正确备份助记词</view>
<!-- 助记词 --> <!-- 助记词 -->
<ul class="mnemonic"> <ul class="mnemonic">
<li v-for="(item, index) in mnemonic" :key="index">{{item}}</li> <li v-for="(item, index) in mnemonic" :key="index">{{item}}</li>
</ul> </ul>
<!-- 按钮 --> <!-- 按钮 -->
<view class="buttons"> <view class="buttons">
<view class="text">助记词是用户账户的唯一标识不能分享给他人掌握该助记词即可控制该账户与钱包</view> <view class="text">助记词是用户账户的唯一标识不能分享给他人掌握该助记词即可控制该账户与钱包</view>
<!-- <button type="default" @click="goto">验证助记词</button> --> <!-- <button type="default" @click="goto">验证助记词</button> -->
</view> </view>
</view> </view>
</template> </template>
<script> <script>
import { seed } from "@/apis/interfaces/wallet" import {
export default { seed
data() { } from "@/apis/interfaces/wallet"
return { export default {
mnemonic: [] data() {
} return {
}, mnemonic: []
mounted() { }
seed({ },
code: this.$Route.query.password mounted() {
}).then(res => { seed({
this.mnemonic = res.seed.split(' ') code: this.$Route.query.password
}).catch(err => { }).then(res => {
uni.showToast({ this.mnemonic = res.seed.split(' ')
icon: 'none', }).catch(err => {
title: err.message uni.showToast({
}) icon: 'none',
}) title: err.message
}, })
methods: { })
goto(){ },
this.$Router.replace({ methods: {
name: 'WalletValidation', goto() {
params: { this.$Router.replace({
seed: this.mnemonic, name: 'WalletValidation',
sign: this.sign params: {
} seed: this.mnemonic,
}) sign: this.sign
} }
} })
} }
</script> }
}
<style lang="scss" scoped> </script>
// 提示信息
.prompt{ <style lang="scss" scoped>
color: $text-gray; // 提示信息
text-align: center; .prompt {
line-height: 90rpx; color: $text-gray;
font-size: $title-size-m; text-align: center;
} line-height: 90rpx;
// 跳过 font-size: $title-size-m;
.skip{ }
padding: $padding * 2;
text-align: center; // 跳过
color: $text-gray; .skip {
navigator{ padding: $padding * 2;
color: $main-color; text-align: center;
margin-left: $margin/2; color: $text-gray;
display: inline-block;
} navigator {
} color: $main-color;
// 助记词 margin-left: $margin/2;
.mnemonic{ display: inline-block;
margin: $margin $margin * 2; }
border-radius: $radius-m; }
box-shadow: 0 0 4rpx 4rpx rgba($color: $text-color, $alpha: .02);
background-color: white; // 助记词
padding: $padding; .mnemonic {
list-style: none; margin: $margin $margin * 2;
display: flex; border-radius: $radius-m;
flex-wrap: wrap; box-shadow: 0 0 4rpx 4rpx rgba($color: $text-color, $alpha: .02);
li{ background-color: white;
text-align: center; padding: $padding;
height: 58rpx; list-style: none;
padding: 0 $padding/2; display: flex;
line-height: 58rpx; flex-wrap: wrap;
margin: $margin / 2;
color: $text-color; li {
background: rgba($color: $border-color, $alpha: .4); text-align: center;
} height: 58rpx;
} padding: 0 $padding/2;
// 按钮 line-height: 58rpx;
.buttons{ margin: $margin / 2;
padding: $padding $padding * 2; color: $text-color;
.text{ background: rgba($color: $border-color, $alpha: .4);
text-align: center; }
margin-bottom: $margin * 2; }
font-size: $title-size-m;
color: $text-price; // 按钮
} .buttons {
button{ padding: $padding $padding * 2;
height: 90rpx;
line-height: 90rpx; .text {
background-color: $main-color; text-align: center;
border-radius: $radius-lg; margin-bottom: $margin * 2;
color: white; font-size: $title-size-m;
font-weight: bold; color: $text-price;
font-size: $title-size; }
&::after{
display: none; button {
} height: 90rpx;
} line-height: 90rpx;
} background-color: $main-color;
border-radius: $radius-lg;
color: white;
font-weight: bold;
font-size: $title-size;
&::after {
display: none;
}
}
}
</style> </style>

View File

@@ -1,119 +1,130 @@
<template> <template>
<view> <view>
<!-- 私钥 --> <!-- 私钥 -->
<view class="keys"> <view class="keys">
<view class="title">您的ZH托管钱包</view> <view class="title">您的ZH托管钱包</view>
<view class="key">{{key || '-'}}</view> <view class="key">{{key || '-'}}</view>
<view class="copykey" @click="copykey">复制我的私钥</view> <view class="copykey" @click="copykey">复制我的私钥</view>
</view> </view>
<!-- 疑问 --> <!-- 疑问 -->
<view class="doubt" v-if="rules.length > 0"> <view class="doubt" v-if="rules.length > 0">
<view class="doubt-item" v-for="(item, index) in rules" :key="index"> <view class="doubt-item" v-for="(item, index) in rules" :key="index">
<view class="title">{{item.title || '-'}}</view> <view class="title">{{item.title || '-'}}</view>
<view class="content">{{item.description || '-'}}</view> <view class="content">{{item.description || '-'}}</view>
</view> </view>
</view> </view>
<!-- 免责说明 --> <!-- 免责说明 -->
<!-- <view class="liability"> <!-- <view class="liability">
<navigator url="/pages/wallet/cmsWithDraw">免责条款</navigator> <navigator url="/pages/wallet/cmsWithDraw">免责条款</navigator>
</view> --> </view> -->
</view> </view>
</template> </template>
<script> <script>
import { privatekey, keyrules } from '@/apis/interfaces/wallet' import {
export default { privatekey,
data() { keyrules
return { } from '@/apis/interfaces/wallet'
key: "", export default {
rules: [ data() {
{ return {
title: "什么是托管钱包?", key: "",
description: "托管钱包顾名思义就是用户把私钥和数字资产委托给其他机构管理,也就是就是通过中心化的方式安全管理并保存资产,本质上是与区块链所追求的去中心化相背离的。" rules: [{
}, title: "什么是托管钱包?",
{ description: "托管钱包顾名思义就是用户把私钥和数字资产委托给其他机构管理,也就是就是通过中心化的方式安全管理并保存资产,本质上是与区块链所追求的去中心化相背离的。"
title: "什么是钱包私钥?", },
description: "作为管理和使用加密货币最关键的东西,私钥对所有数字货币用户而言具有所有权,拥有私钥才能支配相应的加密资产。" {
} title: "什么是钱包私钥?",
] description: "作为管理和使用加密货币最关键的东西,私钥对所有数字货币用户而言具有所有权,拥有私钥才能支配相应的加密资产。"
}; }
}, ]
mounted() { };
privatekey(this.$Route.query.password).then(res => { },
this.key = res.private_key mounted() {
}).catch(err => { privatekey(this.$Route.query.password).then(res => {
uni.showToast({ this.key = res.private_key
title: err.message, }).catch(err => {
icon : 'none' uni.showToast({
}) title: err.message,
}) icon: 'none'
}, })
methods:{ })
copykey(){ },
uni.setClipboardData({ methods: {
data: this.key copykey() {
}) uni.setClipboardData({
} data: this.key
} })
} }
</script> }
}
<style lang="scss"> </script>
.keys{
margin: $margin * 2; <style lang="scss">
background: white; .keys {
padding: $padding * 2; margin: $margin * 2;
box-shadow: 0 0 4rpx 4rpx rgba($color: #000000, $alpha: .02); background: white;
border-radius: $radius; padding: $padding * 2;
.title{ box-shadow: 0 0 4rpx 4rpx rgba($color: #000000, $alpha: .02);
text-align: center; border-radius: $radius;
font-weight: bold;
font-size: $title-size + 4; .title {
color: $text-color; text-align: center;
} font-weight: bold;
.key{ font-size: $title-size + 4;
padding: $padding * 2 0; color: $text-color;
text-align: center; }
color: $main-color;
word-wrap: break-word; .key {
} padding: $padding * 2 0;
.copykey{ text-align: center;
background-color: $main-color; color: $main-color;
color: white; word-wrap: break-word;
height: 95rpx; }
line-height: 95rpx;
text-align: center; .copykey {
font-size: $title-size; background-color: $main-color;
border-radius: $radius-m; color: white;
font-weight: bold; height: 95rpx;
} line-height: 95rpx;
} text-align: center;
.doubt{ font-size: $title-size;
margin: $margin $margin * 2; border-radius: $radius-m;
.doubt-item{ font-weight: bold;
padding: $padding 0; }
.title{ }
font-weight: bold;
color: $text-color; .doubt {
line-height: 50rpx; margin: $margin $margin * 2;
font-size: $title-size + 2;
} .doubt-item {
.content{ padding: $padding 0;
color: $text-gray;
font-size: $title-size-m; .title {
line-height: 40rpx; font-weight: bold;
} color: $text-color;
} line-height: 50rpx;
} font-size: $title-size + 2;
.liability{ }
text-align: center;
color: $text-gray; .content {
@extend .ios-bottom; color: $text-gray;
navigator{ font-size: $title-size-m;
font-size: $title-size-sm; line-height: 40rpx;
display: inline-block; }
line-height: 90rpx; }
padding: 0 ($padding * 2); }
}
} .liability {
text-align: center;
color: $text-gray;
@extend .ios-bottom;
navigator {
font-size: $title-size-sm;
display: inline-block;
line-height: 90rpx;
padding: 0 ($padding * 2);
}
}
</style> </style>

View File

@@ -1,359 +1,386 @@
<template> <template>
<view> <view>
<view class="propery"> <view class="propery">
<view class="propery-content"> <view class="propery-content">
<view class="currency">钱包余额</view> <view class="currency">钱包余额</view>
<view class="balance">{{ balance.balance || '0' }}</view> <view class="balance">{{ balance.balance || '0' }}</view>
<view class="frozen">{{ balance.frozen || '0' }} 冻结中</view> <view class="frozen">{{ balance.frozen || '0' }} 冻结中</view>
<view class="balance-flex"> <view class="balance-flex">
<view class="balance-flex-item" @click="showAddress">区块链地址</view> <view class="balance-flex-item" @click="showAddress">区块链地址</view>
<view class="balance-flex-item" @click="showPrivatekey('privatekey')">我的私钥</view> <view class="balance-flex-item" @click="showPrivatekey('privatekey')">我的私钥</view>
</view> </view>
</view> </view>
</view> </view>
<!-- 账户记录 --> <!-- 账户记录 -->
<view class="record"> <view class="record">
<view class="record-tabs"> <view class="record-tabs">
<view class="tabs-item" :class="logsType === 0 ? 'show': ''" @click="onLogsType(0)">全部</view> <view class="tabs-item" :class="logsType === 0 ? 'show': ''" @click="onLogsType(0)">全部</view>
<view class="tabs-item" :class="logsType === 2 ? 'show': ''" @click="onLogsType(2)">收入</view> <view class="tabs-item" :class="logsType === 2 ? 'show': ''" @click="onLogsType(2)">收入</view>
<view class="tabs-item" :class="logsType === 1 ? 'show': ''" @click="onLogsType(1)">支出</view> <view class="tabs-item" :class="logsType === 1 ? 'show': ''" @click="onLogsType(1)">支出</view>
</view> </view>
<record :list="logs" /> <record :list="logs" />
<!-- ios安全距离 --> <!-- ios安全距离 -->
<view class="ios-bottom"></view> <view class="ios-bottom"></view>
</view> </view>
<!-- 钱包密码 --> <!-- 钱包密码 -->
<u-popup :show="passwordShow" @close="resetPassword" mode="center" round="10" borderRadius="10"> <u-popup :show="passwordShow" @close="resetPassword" mode="center" round="10" borderRadius="10">
<view class="validationPassword"> <view class="validationPassword">
<view class="from"> <view class="from">
<view class="title">验证钱包密码</view> <view class="title">验证钱包密码</view>
<view class="inputs"> <view class="inputs">
<text v-for="item in password.length" :key="item"></text> <text v-for="item in password.length" :key="item"></text>
<text class="flicker-animation">|</text> <text class="flicker-animation">|</text>
</view> </view>
</view> </view>
</view> </view>
</u-popup> </u-popup>
<!-- key --> <!-- key -->
<u-keyboard <u-keyboard mode="number" random dotDisabled :overlay="false" :show="passwordShow" confirmText="验证"
mode="number" @change="keyValChange" @backspace="keyValBackspace" @confirm="payPassword('confirm', passwordPages)"
random @cancel="passwordShow = false"></u-keyboard>
dotDisabled </view>
:overlay="false"
:show="passwordShow"
confirmText="验证"
@change="keyValChange"
@backspace="keyValBackspace"
@confirm="payPassword('confirm', passwordPages)"
@cancel="passwordShow = false"
></u-keyboard>
</view>
</template> </template>
<script> <script>
import record from '@/components/property/record' import record from '@/components/property/record'
import { sum, logs, securityCheck } from '@/apis/interfaces/wallet' import {
export default { sum,
components: { logs,
record securityCheck
}, } from '@/apis/interfaces/wallet'
data() { export default {
return { components: {
balance : {}, record
logs : [], },
logsType : 0, data() {
password : '', return {
passwordShow : false, balance: {},
passwordPages: '' logs: [],
}; logsType: 0,
}, password: '',
onShow() { passwordShow: false,
Promise.all([ passwordPages: ''
sum(), };
logs() },
]).then(res => { onShow() {
this.balance = res[0] Promise.all([
this.logs = res[1] sum(),
}).catch(err => { logs()
uni.showToast({ ]).then(res => {
icon: 'none', this.balance = res[0]
title: err.message this.logs = res[1]
}) }).catch(err => {
}) uni.showToast({
}, icon: 'none',
methods: { title: err.message
// 键盘输入 })
keyValChange(e){ })
if(this.password.length >= 6) return },
this.password += e methods: {
}, // 键盘输入
// 键盘删除 keyValChange(e) {
keyValBackspace(e){ if (this.password.length >= 6) return
if(this.password.length) this.password = this.password.substr(0, this.password.length - 1) this.password += e
}, },
// 弹出私钥 // 键盘删除
showPrivatekey(pages){ keyValBackspace(e) {
this.passwordShow = true if (this.password.length) this.password = this.password.substr(0, this.password.length - 1)
this.passwordPages = pages },
}, // 弹出私钥
// 重置密码 showPrivatekey(pages) {
resetPassword(){ this.passwordShow = true
this.passwordShow = false this.passwordPages = pages
this.password = '' },
}, // 重置密码
// 验证私钥 resetPassword() {
payPassword(type){ this.passwordShow = false
if(type === 'confirm'){ this.password = ''
if(this.password === '') { },
uni.showToast({ // 验证私钥
title: '请输入安全密码', payPassword(type) {
icon : 'none' if (type === 'confirm') {
}) if (this.password === '') {
return uni.showToast({
} title: '请输入安全密码',
securityCheck(this.password).then(res => { icon: 'none'
switch (this.passwordPages){ })
case 'privatekey': return
this.$Router.push({name:'WalletPrivatekey', params: {password: this.password}}) }
break; securityCheck(this.password).then(res => {
case 'ResetPassword': switch (this.passwordPages) {
this.$Router.push({name:'ResetPassword', params: {password: this.password}}) case 'privatekey':
break; this.$Router.push({
case 'WalletMnemonic': name: 'WalletPrivatekey',
this.$Router.push({name:'WalletMnemonic', params: {password: this.password}}) params: {
break; password: this.password
} }
this.resetPassword() })
}).catch(err => { break;
uni.showToast({ case 'ResetPassword':
title: err.message, this.$Router.push({
icon: 'none', name: 'ResetPassword',
}) params: {
}) password: this.password
return }
} })
this.$refs.showPassword.close() break;
}, case 'WalletMnemonic':
// 交易记录 this.$Router.push({
onLogsType(index) { name: 'WalletMnemonic',
if (this.logsType === index) return params: {
this.logsType = index password: this.password
this.logs = [] }
logs({ })
flag: this.logsType break;
}).then(res => { }
this.logs = res this.resetPassword()
}) }).catch(err => {
}, uni.showToast({
// 区块地址 title: err.message,
showAddress() { icon: 'none',
uni.showModal({ })
title: '我的区块链地址', })
content: this.balance.address, return
cancelText: '复制', }
cancelColor: '#009B69', this.$refs.showPassword.close()
success: (res) => { },
if (res.cancel) { // 交易记录
uni.setClipboardData({ onLogsType(index) {
data: this.balance.address if (this.logsType === index) return
}) this.logsType = index
} this.logs = []
} logs({
}) flag: this.logsType
} }).then(res => {
}, this.logs = res
onNavigationBarButtonTap(e) { })
if (e.index === 0) { },
uni.showActionSheet({ // 区块地址
itemList: ['导出助记词', '修改密码'], showAddress() {
success: (res) => { uni.showModal({
switch (res.tapIndex) { title: '我的区块链地址',
case 0: content: this.balance.address,
this.showPrivatekey('WalletMnemonic') cancelText: '复制',
break; cancelColor: '#009B69',
case 1: success: (res) => {
this.showPrivatekey('ResetPassword') if (res.cancel) {
break; uni.setClipboardData({
} data: this.balance.address
uni.hideLoading() })
} }
}) }
} })
} }
} },
</script> onNavigationBarButtonTap(e) {
if (e.index === 0) {
<style> uni.showActionSheet({
.flicker-animation{ itemList: ['导出助记词', '修改密码'],
animation: flicker .8s infinite; success: (res) => {
} switch (res.tapIndex) {
@keyframes flicker{ case 0:
0%{opacity: 0;} this.showPrivatekey('WalletMnemonic')
100{opacity: 1;} break;
} case 1:
this.showPrivatekey('ResetPassword')
break;
}
uni.hideLoading()
}
})
}
}
}
</script>
<style>
.flicker-animation {
animation: flicker .8s infinite;
}
@keyframes flicker {
0% {
opacity: 0;
}
100 {
opacity: 1;
}
}
</style> </style>
<style lang="scss" scoped> <style lang="scss" scoped>
// 验证密码弹出层 // 验证密码弹出层
.validationPassword{ .validationPassword {
width: 80vw; width: 80vw;
.from{
padding: $padding*2;
text-align: center;
.title{
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: $title-size;
padding-bottom: $padding;
}
// input{
// background: $window-color;
// height: 90rpx;
// left: 90rpx;
// font-size: $title-size-lg;
// border-radius: 45rpx;
// }
.inputs {
background-color: $window-color;
height: 90rpx;
line-height: 90rpx;
border-radius: 45rpx;
font-size: $title-size-lg;
text-align: center;
text{
padding: 0 10rpx;
text-align: center;
}
.placeholder{
color: $text-gray;
}
}
}
.buttons{
text-align: center;
padding: 0 $padding*2;
.button{
height: 90rpx;
line-height: 90rpx;
margin-bottom: $margin;
&.cancel{
color: $text-gray;
}
&.confirm{
color: white;
background: $main-color;
border-radius: 45rpx;
}
}
}
//
//
}
// 账户
.propery {
position: relative;
padding-top: var(--status-bar-height);
background-image: linear-gradient(to right, $main-color, #22aa98);
&::before { .from {
position: absolute; padding: $padding*2;
left: 0; text-align: center;
top: 0;
width: 100%;
height: 100%;
content: " ";
// background-image: url(@/static/background/wallet-back.png);
background-size: 100%;
background-repeat: no-repeat;
}
.propery-content { .title {
position: relative; display: flex;
z-index: 1; flex-direction: column;
padding: $padding * 5 $padding * 2; align-items: center;
text-align: center; justify-content: center;
font-weight: bold;
font-size: $title-size;
padding-bottom: $padding;
}
.currency { // input{
font-size: $title-size-m; // background: $window-color;
color: rgba($color: white, $alpha: .8); // height: 90rpx;
} // left: 90rpx;
// font-size: $title-size-lg;
// border-radius: 45rpx;
// }
.inputs {
background-color: $window-color;
height: 90rpx;
line-height: 90rpx;
border-radius: 45rpx;
font-size: $title-size-lg;
text-align: center;
.balance { text {
font-size: $title-size * 2; padding: 0 10rpx;
padding: ($padding / 2) 0; text-align: center;
color: white; }
}
.frozen { .placeholder {
background: rgba($color: #000000, $alpha: .1); color: $text-gray;
color: rgba($color: white, $alpha: .7); }
display: inline-block; }
padding: 0 $padding; }
font-size: $title-size-m;
height: 50rpx;
line-height: 50rpx;
border-radius: $radius-m;
border: solid 1rpx rgba($color: white, $alpha: .4)
}
.balance-flex { .buttons {
display: flex; text-align: center;
justify-content: center; padding: 0 $padding*2;
margin-top: $margin * 3;
.balance-flex-item { .button {
background-color: white; height: 90rpx;
width: 200rpx; line-height: 90rpx;
height: 75rpx; margin-bottom: $margin;
line-height: 75rpx;
color: $main-color;
margin: 0 $margin;
border-radius: $radius-m;
font-size: $title-size-lg;
}
}
}
}
// 记录 &.cancel {
.record { color: $text-gray;
background-color: white; }
border-radius: $radius $radius 0 0;
padding: $padding ($padding * 2);
margin-top: -$margin;
position: relative;
z-index: 2;
.record-tabs { &.confirm {
display: flex; color: white;
justify-content: space-around; background: $main-color;
font-weight: bold; border-radius: 45rpx;
font-size: $title-size; }
color: $text-gray; }
line-height: 70rpx; }
margin-bottom: $margin;
.tabs-item { //
position: relative; //
padding: 0 $padding; }
&.show { // 账户
color: $main-color; .propery {
position: relative;
padding-top: var(--status-bar-height);
background-image: linear-gradient(to right, $main-color, #22aa98);
&::before { &::before {
position: absolute; position: absolute;
bottom: 0; left: 0;
left: $padding; top: 0;
right: $padding; width: 100%;
height: 4rpx; height: 100%;
content: " "; content: " ";
background-color: $main-color; // background-image: url(@/static/background/wallet-back.png);
} background-size: 100%;
} background-repeat: no-repeat;
} }
}
} .propery-content {
position: relative;
z-index: 1;
padding: $padding * 5 $padding * 2;
text-align: center;
.currency {
font-size: $title-size-m;
color: rgba($color: white, $alpha: .8);
}
.balance {
font-size: $title-size * 2;
padding: ($padding / 2) 0;
color: white;
}
.frozen {
background: rgba($color: #000000, $alpha: .1);
color: rgba($color: white, $alpha: .7);
display: inline-block;
padding: 0 $padding;
font-size: $title-size-m;
height: 50rpx;
line-height: 50rpx;
border-radius: $radius-m;
border: solid 1rpx rgba($color: white, $alpha: .4)
}
.balance-flex {
display: flex;
justify-content: center;
margin-top: $margin * 3;
.balance-flex-item {
background-color: white;
width: 200rpx;
height: 75rpx;
line-height: 75rpx;
color: $main-color;
margin: 0 $margin;
border-radius: $radius-m;
font-size: $title-size-lg;
}
}
}
}
// 记录
.record {
background-color: white;
border-radius: $radius $radius 0 0;
padding: $padding ($padding * 2);
margin-top: -$margin;
position: relative;
z-index: 2;
.record-tabs {
display: flex;
justify-content: space-around;
font-weight: bold;
font-size: $title-size;
color: $text-gray;
line-height: 70rpx;
margin-bottom: $margin;
.tabs-item {
position: relative;
padding: 0 $padding;
&.show {
color: $main-color;
&::before {
position: absolute;
bottom: 0;
left: $padding;
right: $padding;
height: 4rpx;
content: " ";
background-color: $main-color;
}
}
}
}
}
</style> </style>

View File

@@ -1,198 +1,203 @@
<template> <template>
<view> <view>
<!-- 设置钱包密码 --> <!-- 设置钱包密码 -->
<view class="password"> <view class="password">
<view class="prompt">请设置6位数字密码建议不要使用连续的数字</view> <view class="prompt">请设置6位数字密码建议不要使用连续的数字</view>
<view class="group"> <view class="group">
<view class="inputs" @click="onShowKet('password')"> <view class="inputs" @click="onShowKet('password')">
<block v-if="password.length > 0"> <block v-if="password.length > 0">
<text v-for="item in password.length" :key="item"></text> <text v-for="item in password.length" :key="item"></text>
</block> </block>
<block v-if="keyShow && valKye === 'password'"> <block v-if="keyShow && valKye === 'password'">
<text class="flicker-animation">|</text> <text class="flicker-animation">|</text>
</block> </block>
<block v-else> <block v-else>
<text v-if="password.length === 0" class="placeholder">请设置密码</text> <text v-if="password.length === 0" class="placeholder">请设置密码</text>
</block> </block>
</view> </view>
<view class="inputs" :class="{'hide': verify === ''}" @click="onShowKet('verify')"> <view class="inputs" :class="{'hide': verify === ''}" @click="onShowKet('verify')">
<block v-if="verify.length > 0"> <block v-if="verify.length > 0">
<text v-for="item in verify.length" :key="item"></text> <text v-for="item in verify.length" :key="item"></text>
</block> </block>
<block v-if="keyShow && valKye === 'verify'"> <block v-if="keyShow && valKye === 'verify'">
<text class="flicker-animation">|</text> <text class="flicker-animation">|</text>
</block> </block>
<block v-else> <block v-else>
<text v-if="verify.length === 0" class="placeholder">请确认密码</text> <text v-if="verify.length === 0" class="placeholder">请确认密码</text>
</block> </block>
</view> </view>
</view> </view>
</view> </view>
<!-- key键盘 --> <!-- key键盘 -->
<u-keyboard <u-keyboard mode="number" random dotDisabled :overlay="false" :show="keyShow" :showCancel="false"
mode="number" @change="keyValChange" @backspace="keyValBackspace" @confirm="keyShow = false"></u-keyboard>
random <!-- 按钮 -->
dotDisabled <view class="buttons">
:overlay="false" <button type="default" form-type="submit" @click="createWallet">确认修改</button>
:show="keyShow" </view>
:showCancel="false" </view>
@change="keyValChange"
@backspace="keyValBackspace"
@confirm="keyShow = false"
></u-keyboard>
<!-- 按钮 -->
<view class="buttons">
<button type="default" form-type="submit" @click="createWallet">确认修改</button>
</view>
</view>
</template> </template>
<script> <script>
import { securityReset } from '@/apis/interfaces/wallet' import {
export default { securityReset
data() { } from '@/apis/interfaces/wallet'
return { export default {
password: '', data() {
verify: '', return {
oldPassword: '', password: '',
verify: '',
valKye : '', oldPassword: '',
keyShow : false valKye: '',
} keyShow: false
}, }
onLoad() { },
this.oldPassword = this.$Route.query.password onLoad() {
}, this.oldPassword = this.$Route.query.password
methods: { },
// 唤起key methods: {
onShowKet(key){ // 唤起key
this.valKye = key onShowKet(key) {
this.keyShow = true this.valKye = key
}, this.keyShow = true
// 键盘输入 },
keyValChange(e){ // 键盘输入
if(this[this.valKye].length >= 6) return keyValChange(e) {
this[this.valKye] += e if (this[this.valKye].length >= 6) return
}, this[this.valKye] += e
// 键盘删除 },
keyValBackspace(e){ // 键盘删除
if(this[this.valKye].length) this[this.valKye] = this[this.valKye].substr(0, this[this.valKye].length - 1) keyValBackspace(e) {
}, if (this[this.valKye].length) this[this.valKye] = this[this.valKye].substr(0, this[this.valKye].length -
// 修改密码 1)
createWallet() { },
if (this.password === '' || this.verify === '') { // 修改密码
uni.showToast({ createWallet() {
icon: 'none', if (this.password === '' || this.verify === '') {
title: '请设置密码' uni.showToast({
}) icon: 'none',
return title: '请设置密码'
} })
if (this.password !== this.verify) { return
uni.showToast({ }
icon: 'none', if (this.password !== this.verify) {
title: '两次输入密码不一致' uni.showToast({
}) icon: 'none',
return title: '两次输入密码不一致'
} })
securityReset({ return
new_code: this.password, }
code: this.oldPassword securityReset({
}).then(res => { new_code: this.password,
uni.showModal({ code: this.oldPassword
title: '提示', }).then(res => {
content: '密码已重置', uni.showModal({
showCancel:false, title: '提示',
success: res=> { content: '密码已重置',
uni.navigateBack() showCancel: false,
} success: res => {
}) uni.navigateBack()
}).catch(err => { }
uni.showToast({ })
icon: 'none', }).catch(err => {
title: err.message uni.showToast({
}) icon: 'none',
}) title: err.message
} })
} })
} }
</script> }
}
</script>
<style>
.flicker-animation{
animation: flicker .8s infinite; <style>
} .flicker-animation {
@keyframes flicker{ animation: flicker .8s infinite;
0%{opacity: 0;} }
100{opacity: 1;}
} @keyframes flicker {
0% {
opacity: 0;
}
100 {
opacity: 1;
}
}
</style> </style>
<style lang="scss" scoped> <style lang="scss" scoped>
// 副标题 // 副标题
.sub-title { .sub-title {
color: $text-gray; color: $text-gray;
text-align: center; text-align: center;
margin: $margin * 2 $margin; margin: $margin * 2 $margin;
font-size: $title-size-m; font-size: $title-size-m;
} }
// 设置密码 // 设置密码
.password { .password {
padding: 0 $padding * 2; padding: 0 $padding * 2;
.prompt {
margin-top: $margin * 2; .prompt {
font-size: $title-size-m; margin-top: $margin * 2;
color: $main-color; font-size: $title-size-m;
text-align: center; color: $main-color;
} text-align: center;
}
.group {
padding-top: $padding; .group {
.inputs { padding-top: $padding;
padding: 10rpx $padding + 10;
margin-top: $margin; .inputs {
border-radius: $radius-m; padding: 10rpx $padding + 10;
background-color: $window-color; margin-top: $margin;
height: 70rpx; border-radius: $radius-m;
line-height: 70rpx; background-color: $window-color;
font-size: $title-size-lg; height: 70rpx;
text-align: center; line-height: 70rpx;
text{ font-size: $title-size-lg;
padding: 0 10rpx; text-align: center;
text-align: center;
} text {
.placeholder{ padding: 0 10rpx;
color: $text-gray; text-align: center;
} }
}
} .placeholder {
} color: $text-gray;
}
// 按钮 }
.buttons { }
padding: $padding * 2; }
.text {
text-align: center; // 按钮
line-height: 90rpx; .buttons {
height: 90rpx; padding: $padding * 2;
margin-bottom: $margin * 2;
font-size: $title-size-lg; .text {
color: $main-color; text-align: center;
font-weight: bold; line-height: 90rpx;
} height: 90rpx;
margin-bottom: $margin * 2;
button { font-size: $title-size-lg;
height: 90rpx; color: $main-color;
line-height: 90rpx; font-weight: bold;
background-color: $main-color; }
border-radius: $radius-m;
color: white; button {
font-weight: bold; height: 90rpx;
font-size: $title-size; line-height: 90rpx;
&::after{ background-color: $main-color;
display: none; border-radius: $radius-m;
} color: white;
} font-weight: bold;
} font-size: $title-size;
&::after {
display: none;
}
}
}
</style> </style>

View File

@@ -1,170 +1,178 @@
<template> <template>
<view> <view>
<!-- 提示信息 --> <!-- 提示信息 -->
<view class="prompt"> <view class="prompt">
验证您的钱包助记词 验证您的钱包助记词
</view> </view>
<!-- 助记词 --> <!-- 助记词 -->
<view class="mnemonic"> <view class="mnemonic">
<view <view class="item" v-for="(item, index) in validation" :key="index" :class="item === null ? 'hide': ''"
class="item" @click="onKeys('removeKey', index)">{{ item }}</view>
v-for="(item, index) in validation" </view>
:key="index" <!-- 选择助记词 -->
:class="item === null ? 'hide': ''" <block v-if="mnemonic.length > 0">
@click="onKeys('removeKey', index)" <view class="mnemonic-title">
>{{ item }}</view> 按顺序填写助记词
</view> </view>
<!-- 选择助记词 --> <view class="mnemonic-select">
<block v-if="mnemonic.length > 0"> <view class="item" v-for="(item, index) in mnemonic" :key="index" @click="onKeys('addKey', index)">
<view class="mnemonic-title"> {{ item }}</view>
按顺序填写助记词 </view>
</view> </block>
<view class="mnemonic-select"> <!-- 按钮 -->
<view class="item" v-for="(item, index) in mnemonic" :key="index" @click="onKeys('addKey', index)">{{ item }}</view> <view class="buttons">
</view> <button type="default" @click="verifyMnemonic">验证</button>
</block> </view>
<!-- 按钮 --> </view>
<view class="buttons"> </template>
<button type="default" @click="verifyMnemonic">验证</button>
</view> <script>
</view> import {
</template> hash
} from "@/apis/interfaces/wallet"
<script> export default {
import { hash } from "../../apis/interfaces/wallet" data() {
export default { return {
data() { validation: new Array(12).fill(null), // 验证key
return { mnemonic: [], // 助记词key
validation : new Array(12).fill(null), // 验证key sign: '', // 助记词校验签名
mnemonic : [], // 助记词key seedString: '', // 助记词原词
sign : '', // 助记词校验签名 }
seedString : '', // 助记词原词 },
} mounted() {
}, let seed = this.$Route.query.seed.split(',')
mounted() { seed.sort(() => {
let seed = this.$Route.query.seed.split(',') return Math.random() - .5
seed.sort(() => { });
return Math.random() - .5 console.log(seed)
}); // this.mnemonic = seed
console.log(seed) // this.sign = this.$Route.query.sign
// this.mnemonic = seed // this.seedString = this.$Route.query.seed
// this.sign = this.$Route.query.sign },
// this.seedString = this.$Route.query.seed methods: {
}, // 填写助记词
methods: { onKeys(type, index) {
// 填写助记词 if (type === 'addKey') {
onKeys(type, index){ this.$set(this.validation, this.validation.findIndex(val => val === null), this.mnemonic[index])
if(type === 'addKey') { this.$delete(this.mnemonic, index)
this.$set(this.validation, this.validation.findIndex(val => val === null), this.mnemonic[index]) return
this.$delete(this.mnemonic, index) }
return if (type === 'removeKey' && this.validation[index] !== null) {
} this.mnemonic.push(this.validation[index])
if(type === 'removeKey' && this.validation[index] !== null) { this.$delete(this.validation, index)
this.mnemonic.push(this.validation[index]) this.validation.push(null)
this.$delete(this.validation, index) }
this.validation.push(null) },
} // 验证助记词
}, verifyMnemonic() {
// 验证助记词 if (this.validation.findIndex(val => val === null) > -1) {
verifyMnemonic(){ uni.showToast({
if(this.validation.findIndex(val => val === null) > -1){ title: '请完整填写助记词',
uni.showToast({ icon: 'none'
title: '请完整填写助记词', })
icon : 'none' return
}) }
return let seed = this.validation.toString().replace(/,/g, ',')
} if (this.seedString !== seed) {
let seed = this.validation.toString().replace(/,/g, ',') uni.showToast({
if (this.seedString !== seed) { title: '验证失败,请确认您的助记词',
uni.showToast({ icon: 'none'
title: '验证失败,请确认您的助记词', })
icon : 'none' return
}) }
return uni.redirectTo({
} url: './create'
uni.redirectTo({ })
url: './create' }
}) }
} }
} </script>
}
</script> <style lang="scss" scoped>
// 提示信息
<style lang="scss" scoped> .prompt {
// 提示信息 color: $text-gray;
.prompt{ text-align: center;
color: $text-gray; line-height: 90rpx;
text-align: center; font-size: $title-size-m;
line-height: 90rpx; }
font-size: $title-size-m;
} // 选择助记词
// 选择助记词 .mnemonic-title {
.mnemonic-title{ padding-top: $padding * 2;
padding-top: $padding * 2; margin: 0 $margin * 2;
margin: 0 $margin * 2; font-size: $title-size-m;
font-size: $title-size-m; color: $main-color;
color: $main-color; }
}
.mnemonic-select{ .mnemonic-select {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
padding: $padding $padding + $padding / 2; padding: $padding $padding + $padding / 2;
.item{
background-color: white; .item {
line-height: 68rpx; background-color: white;
height: 68rpx; line-height: 68rpx;
width: 68rpx; height: 68rpx;
text-align: center; width: 68rpx;
margin: $margin / 2; text-align: center;
border-radius: $radius-m; margin: $margin / 2;
box-shadow: 0 0 4rpx 4rpx rgba($color: $text-color, $alpha: .02); border-radius: $radius-m;
} box-shadow: 0 0 4rpx 4rpx rgba($color: $text-color, $alpha: .02);
} }
// 助记词 }
.mnemonic{
margin: $margin ($margin * 2); // 助记词
border-radius: $radius-m; .mnemonic {
box-shadow: 0 0 4rpx 4rpx rgba($color: $text-color, $alpha: .02); margin: $margin ($margin * 2);
background-color: white; border-radius: $radius-m;
padding: $padding; box-shadow: 0 0 4rpx 4rpx rgba($color: $text-color, $alpha: .02);
display: flex; background-color: white;
flex-wrap: wrap; padding: $padding;
align-items: flex-start; display: flex;
.item{ flex-wrap: wrap;
background: rgba($color: $border-color, $alpha: .4); align-items: flex-start;
min-width: 58rpx;
height: 58rpx; .item {
line-height: 58rpx; background: rgba($color: $border-color, $alpha: .4);
text-align: center; min-width: 58rpx;
color: $text-color; height: 58rpx;
margin: $margin / 2; line-height: 58rpx;
&.hide{ text-align: center;
border:dashed 1px $border-color; color: $text-color;
box-sizing: border-box; margin: $margin / 2;
background-color: white;
} &.hide {
} border: dashed 1px $border-color;
} box-sizing: border-box;
// 按钮 background-color: white;
.buttons{ }
padding: $padding $padding * 2; }
.text{ }
text-align: center;
margin-bottom: $margin * 2; // 按钮
font-size: $title-size-lg; .buttons {
color: $text-price; padding: $padding $padding * 2;
}
button{ .text {
height: 90rpx; text-align: center;
line-height: 90rpx; margin-bottom: $margin * 2;
background-color: $main-color; font-size: $title-size-lg;
border-radius: $radius-lg; color: $text-price;
color: white; }
font-weight: bold;
font-size: $title-size; button {
&[disabled]{ height: 90rpx;
background: rgba($color: $main-color, $alpha: .8); line-height: 90rpx;
} background-color: $main-color;
} border-radius: $radius-lg;
} color: white;
font-weight: bold;
font-size: $title-size;
&[disabled] {
background: rgba($color: $main-color, $alpha: .8);
}
}
}
</style> </style>

View File

@@ -4,16 +4,16 @@ import {
} from 'uni-simple-router'; } from 'uni-simple-router';
import store from '@/store/index' import store from '@/store/index'
// #ifdef APP-NVUE // CALL 页面必须是nvue但是NVUE 不支持webpack的 ROUTES 注入
// CALL 页面必须是nvue但是NVUE 不支持webpack的 ROUTES 注入
// https://github.com/SilurianYang/uni-read-pages/issues/20 // https://github.com/SilurianYang/uni-read-pages/issues/20
// #ifdef APP-NVUE
const ROUTES = [{ const ROUTES = [{
'path': '/pages/im/private/call', "path": "/pages/im/private/call"
'name': 'imPrivateCall' }, {
},{ "path": "/pages/im/private/chat"
'path': '/pages/im/private/chat', }, {
'name': 'imPrivateChat' "path": "/pages/im/group/chat"
}] }];
// #endif // #endif
const router = createRouter({ const router = createRouter({

BIN
static/.DS_Store vendored

Binary file not shown.

BIN
static/icon/clock_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
static/icon/popups-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@@ -1,6 +1,6 @@
@font-face { @font-face {
font-family: "iconfont"; /* Project id 2869797 */ font-family: "iconfont"; /* Project id 2869797 */
src: url('@/static/iconfont.ttf') format('truetype'); src: url('@/static/iconfont.ttf') format('truetype'),
} }
.iconfont { .iconfont {
@@ -11,6 +11,14 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.icon-jia:before {
content: "\e60a";
}
.icon-dui:before {
content: "\e609";
}
.icon-gengduo2:before { .icon-gengduo2:before {
content: "\e608"; content: "\e608";
} }

Binary file not shown.

View File

@@ -2,98 +2,138 @@ import im from "@/utils/im/index.js"
export default { export default {
state: { state: {
friends: {}, contacts: {},
sender: {}, myInfo: {}
}, },
getters: { getters: {
friends(state) { contacts(state) {
return state.friends return state.contacts
}, },
userInfo: (state) => (targetId) => { contactInfo: (state) => (targetId) => {
if (state.friends[targetId]) { if (state.contacts[targetId]) {
return state.friends[targetId] const info = state.contacts[targetId]
return {
name: info.name,
hash: info.hash,
portraitUrl: info.localAvatar ? info.localAvatar : require('@/static/user/cover.png')
}
} else { } else {
console.log('没找到,死循环了,得处理 todo', targetId);
// im.syncUserInfo(targetId)
return { return {
name: '', name: '',
address: '',
hash: '', hash: '',
portraitUrl: '' portraitUrl: ''
} }
} }
}, },
hasUser: (state) => (targetId) => { // 联系人是否存在
return Boolean(state.friends[targetId]) contactIsExist: (state) => (targetId) => {
return Boolean(state.contacts[targetId])
}, },
sender(state) { sender(state) {
return state.sender return state.myInfo
} }
}, },
mutations: { mutations: {
updateFriends(state, userInfo) { updateContactInfo(state, contactInfo) {
Vue.set(state.friends, userInfo.userId, userInfo) Vue.set(state.contacts, contactInfo.targetId, contactInfo)
}, },
SET_state_sender(state, userInfo) { setSenderInfo(state, contactInfo) {
state.sender = userInfo state.myInfo = {
userId: contactInfo.targetId,
name: contactInfo.name,
portraitUrl: contactInfo.portraitUrl
}
} }
}, },
actions: { actions: {
setSenderInfo({ setSenderInfo({
commit commit
}, userInfo) { }, contactInfo) {
commit('SET_state_sender', userInfo) commit('setSenderInfo', contactInfo)
}, },
updateFriends({ // 载入好友信息
launchContact({
commit commit
}, userInfo) { }, data) {
commit('updateFriends', userInfo) commit('updateContactInfo', data)
const model = uni.model.friendModel },
model.find('userId=' + userInfo.userId, (err, user) => { // 更新好友信息这个时候要校验hash值了
if (!err && user.length == 0) { updateContact({
saveAvatar(userInfo, (savedFilePath) => { commit,
model.insert({ dispatch
userId: userInfo.userId, }, contactInfo) {
name: userInfo.name, const model = uni.model.contactModel
hash: userInfo.hash, model.find('targetId="' + contactInfo.targetId + '"', (err, result) => {
address: userInfo.address, if (result.length == 0) {
portraitUrl: savedFilePath, // 没有数据,直接新增一条
}, (err, result) => {}) dispatch('initContact', contactInfo)
userInfo.portraitUrl = savedFilePath } else if (contactInfo.hash != result[0].hash) {
commit('updateFriends', userInfo) commit('updateContactInfo', contactInfo)
}) if (contactInfo.portraitUrl && contactInfo.portraitUrl != result[0].portraitUrl) {
} else if (!err && user[0].hash != userInfo.hash) { saveAvatar(contactInfo, (savedFilePath) => {
saveAvatar(userInfo, (savedFilePath) => { const info = {
model.update('userId=' + userInfo.userId, { targetId: contactInfo.targetId,
name: userInfo.name, name: contactInfo.name,
hash: userInfo.hash, hash: contactInfo.hash,
portraitUrl: savedFilePath, portraitUrl: contactInfo.portraitUrl,
}, (err, result) => {}) localAvatar: savedFilePath
userInfo.portraitUrl = savedFilePath }
commit('updateFriends', userInfo) model.update('targetId=' + contactInfo.targetId, info, (err, res) => {})
}) commit('updateContactInfo', info)
} else if (!err && user[0].portraitUrl.length > 50) { })
saveAvatar(userInfo, (savedFilePath) => { } else {
model.update('userId=' + userInfo.userId, { const info = {
name: userInfo.name, targetId: contactInfo.targetId,
hash: userInfo.hash, name: contactInfo.name,
portraitUrl: savedFilePath, hash: contactInfo.hash,
}, (err, result) => {}) portraitUrl: contactInfo.portraitUrl,
localAvatar: result[0].localAvatar
userInfo.portraitUrl = savedFilePath }
commit('updateFriends', userInfo) model.update('targetId="' + contactInfo.targetId + '"', info, (err, res) => {})
}) }
} else { } else {}
console.log('不用操作', user[0]);
}
}) })
},
// 初始化好友信息
initContact({
commit
}, contactInfo) {
// 将好友信息保存到vuex的内存中方便立即使用
commit('updateContactInfo', contactInfo)
const model = uni.model.contactModel
// 用户头像,是否需要下载到本地
if (contactInfo.portraitUrl) {
saveAvatar(contactInfo, (savedFilePath) => {
const info = {
targetId: contactInfo.targetId,
name: contactInfo.name,
hash: contactInfo.hash,
portraitUrl: contactInfo.portraitUrl,
localAvatar: savedFilePath
}
model.insert(info, (err, res) => {})
// 保存头像后,更新信息
commit('updateContactInfo', info)
})
} else {
// 直接将信息,写入数据库
const info = {
targetId: contactInfo.targetId,
name: contactInfo.name,
hash: contactInfo.hash,
portraitUrl: contactInfo.portraitUrl,
localAvatar: ''
}
model.insert(info, (err, res) => {})
}
} }
} }
} }
const saveAvatar = (userInfo, callback) => { const saveAvatar = (contactInfo, callback) => {
uni.downloadFile({ uni.downloadFile({
url: userInfo.portraitUrl, url: contactInfo.portraitUrl,
success: ({ success: ({
tempFilePath tempFilePath
}) => { }) => {
@@ -106,8 +146,6 @@ const saveAvatar = (userInfo, callback) => {
} }
}) })
}, },
fail: (err) => { fail: (err) => {}
console.log('头像保存失败', err);
}
}) })
} }

16
utils/im/data.js Normal file
View File

@@ -0,0 +1,16 @@
// 获取新好友申请数量
const getPendingCount = (callback) => {
RongIMLib.getConversationList([RongIMLib.ConversationType.SYSTEM], 100, 0, (res) => {
if (res.code === 0) {
const pendingCount = res.conversations.filter((item) => {
return item.objectName == RongIMLib.ObjectName.ContactNotification
}).length
callback(pendingCount)
}
})
}
export default {
getPendingCount
}

View File

@@ -1,23 +0,0 @@
[{
"objectName": "RC:TxtMsg",
"receivedTime": 1643080237399,
"extra": "",
"messageUId": "BUFC-3FSU-OLE4-I31K",
"conversationType": 1,
"messageDirection": 2,
"senderUserId": "10041",
"content": {
"content": "你好,这是 1710 条消息条消息条消息条消息条消息条消息条消息条消息条消息0.97796900 1642741562",
"objectName": "RC:TxtMsg",
"userInfo": {
"userId": "10041",
"name": "我是eth",
"portraitUrl": "http://storage.zh.shangkelian.cn/images/2022/01/12/3d2a103386df6822db7e5290272e8bc2.png"
}
},
"targetId": "10041",
"sentTime": 1642741563003,
"messageId": 2,
"receivedStatus": 1,
"sentStatus": 30
}]

View File

@@ -2,6 +2,7 @@ import * as RongIMLib from '@/uni_modules/RongCloud-IMWrapper/js_sdk/index'
import * as CallLib from '@/uni_modules/RongCloud-CallWrapper/lib/index' import * as CallLib from '@/uni_modules/RongCloud-CallWrapper/lib/index'
import store from '@/store/index.js' import store from '@/store/index.js'
import message from './message.js' import message from './message.js'
import listeners from './listeners.js'
import { import {
getFriends, getFriends,
getUserInfo, getUserInfo,
@@ -10,7 +11,7 @@ import {
const initIm = (KEY) => { const initIm = (KEY) => {
RongIMLib.init(KEY) RongIMLib.init(KEY)
CallLib.init() // CallLib.init()
addListeners() addListeners()
// 初始化的时候 自动链接 // 初始化的时候 自动链接
if (store.getters.getToken !== '') { if (store.getters.getToken !== '') {
@@ -47,25 +48,37 @@ const setNotifyBadge = () => {
/** /**
* 连接IM服务 * 连接IM服务
* @param {string} token token * @param {string} token token
* @param {object} userInfo {userId: string, name: string, portraitUrl: string} * @param {object} userInfo {targetId: string, name: string, portraitUrl: string}
*/ */
const connect = (token, userInfo, callback) => { const connect = (token, userInfo, callback) => {
RongIMLib.connect(token, res => { RongIMLib.connect(token, res => {
console.log('连接结果', res);
callback(res) callback(res)
}) // 更新个人信息
store.dispatch('setSenderInfo', userInfo)
// 设置未读消息数量
setNotifyBadge()
// 首次运行获取好友列表
const FK = 'IFT_' + userInfo.targetId
store.dispatch('setSenderInfo', userInfo) uni.getStorage({
key: FK,
setNotifyBadge() success: () => {
const model = uni.model.contactModel
const model = uni.model.friendModel model.find((err, results) => {
results.map(item => {
model.find((err, results) => { store.dispatch('launchContact', item)
console.log('好友列表', results); })
results.map(item => { })
store.dispatch('updateFriends', item) },
fail: () => {
// 程序是首次运行,初始化加载好友信息
getFriends().then(res => {
res.map(item => {
store.dispatch('initContact', item)
})
uni.setStorageSync(FK, userInfo.targetId)
})
}
}) })
}) })
} }
@@ -102,63 +115,88 @@ const notifyMsgTypes = [
function inArray(search, array) { function inArray(search, array) {
for (var i in array) { for (var i in array) {
if (array[i] == search) { if (array[i] == search) {
return true; return true
} }
} }
return false; return false
} }
const addListeners = () => { const addListeners = () => {
// 添加连接状态监听函数 // 添加连接状态监听函数
RongIMLib.addConnectionStatusListener((res) => { RongIMLib.addConnectionStatusListener((res) => {
console.log('连接状态监听', res.data.status); console.log('连接状态监听', res.data.status)
uni.$emit('onConnectionStatusChange', res.data.status) uni.$emit('onConnectionStatusChange', res.data.status)
}) })
// 添加消息监听函数 // 添加消息监听函数
RongIMLib.addReceiveMessageListener((res) => { RongIMLib.addReceiveMessageListener((res) => {
console.log('收到消息', res.data.message);
const message = res.data.message const message = res.data.message
console.log('收到消息', message)
if (inArray(message.objectName, notifyMsgTypes)) { if (inArray(message.objectName, notifyMsgTypes)) {
console.log('new Message'); if (!store.getters.contactIsExist(message.targetId)) {
getUserInfo(message.targetId).then(res => {
store.dispatch('initContact', res)
}).catch(err => {
console.log('ERR', err)
})
}
newMessage(message) newMessage(message)
} else if (message.objectName === RongIMLib.ObjectName.ProfileNotification) {
store.dispatch('updateContact', JSON.parse(message.content.data))
// 调用完更新之后,删除这条消息
RongIMLib.deleteMessagesByIds([message.messageId], ({
code
}) => {
console.log('消息删除结果', code)
})
} else if (message.objectName === RongIMLib.ObjectName.ContactNotification) {
// 触发一个新好友的通知事件
uni.$emit('onContactNotification', message.content)
} }
}) })
// 音视频通话相关的
// 监听通话呼入 // 监听消息回执
CallLib.onCallReceived(({ RongIMLib.addReadReceiptReceivedListener(({
data data
}) => { }) => {
uni.navigateTo({ uni.$emit('onReadReceiptReceived', data)
url: '/pages/im/private/call?targetId=' + data.targetId + '&mediaType=' +
data.mediaType
})
})
// 通话建立成功
CallLib.onCallConnected(() => {
uni.$emit('onCallConnected');
})
// 外呼
CallLib.onCallOutgoing((res) => {
uni.$emit('onCallOutgoing');
})
// 远端响铃
CallLib.onRemoteUserRinging((res) => {
uni.$emit('onRemoteUserRinging');
})
// 远端加入
CallLib.onRemoteUserJoined((res) => {
uni.$emit('onRemoteUserJoined');
})
// 断开链接
CallLib.onCallDisconnected((res) => {
console.log('断开链接', res);
uni.$emit('onCallDisconnected');
})
// 远端挂断
CallLib.onRemoteUserLeft((res) => {
console.log('远端离开', res);
uni.$emit('onRemoteUserLeft');
}) })
// 音视频通话相关的
// 监听通话呼入
// CallLib.onCallReceived(({
// data
// }) => {
// uni.navigateTo({
// url: '/pages/im/private/call?targetId=' + data.targetId + '&mediaType=' +
// data.mediaType
// })
// })
// // 通话建立成功
// CallLib.onCallConnected(() => {
// uni.$emit('onCallConnected')
// })
// // 外呼
// CallLib.onCallOutgoing((res) => {
// uni.$emit('onCallOutgoing')
// })
// // 远端响铃
// CallLib.onRemoteUserRinging((res) => {
// uni.$emit('onRemoteUserRinging')
// })
// // 远端加入
// CallLib.onRemoteUserJoined((res) => {
// uni.$emit('onRemoteUserJoined')
// })
// // 断开链接
// CallLib.onCallDisconnected((res) => {
// console.log('断开链接', res)
// uni.$emit('onCallDisconnected')
// })
// // 远端挂断
// CallLib.onRemoteUserLeft((res) => {
// console.log('远端离开', res)
// uni.$emit('onRemoteUserLeft')
// })
} }
// 维护消息列表,检查是否需要通知声音,设置新消息提醒的数量 // 维护消息列表,检查是否需要通知声音,设置新消息提醒的数量
@@ -168,25 +206,14 @@ const newMessage = (msg) => {
status status
}) => { }) => {
if (code === 0) { if (code === 0) {
if (status) { if (status) {
uni.vibrateLong()
triTone() triTone()
} }
} }
});
setNotifyBadge()
if (!store.getters.hasUser(msg.targetId)) {
syncUserInfo(msg.targetId)
}
uni.$emit('onReceiveMessage', msg);
}
function syncUserInfo(targetId) {
getUserInfo(targetId).then(res => {
store.dispatch('updateFriends', res)
}) })
setNotifyBadge()
uni.$emit('onReceiveMessage', msg)
} }
// 播放状态 // 播放状态
@@ -201,29 +228,15 @@ const triTone = () => {
tipState = true tipState = true
}) })
innerAudioContext.onEnded(() => { innerAudioContext.onEnded(() => {
tipState = false tipState = false
innerAudioContext.destroy() innerAudioContext.destroy()
}) })
} }
} }
/**
* 同步好友信息,保存头像地址等
*/
const syncFriends = () => {
getFriends().then(res => {
res.map(item => {
console.log('item', item);
store.dispatch('updateFriends', item)
})
})
}
export default { export default {
initIm, initIm,
connect, connect,
setNotifyBadge, setNotifyBadge,
syncFriends,
syncUserInfo,
...message ...message
} }

0
utils/im/listeners.js Normal file
View File

View File

@@ -1,8 +1,9 @@
import store from '@/store/index.js' import store from '@/store/index.js'
import * as RongIMLib from '@/uni_modules/RongCloud-IMWrapper/js_sdk/index' import * as RongIMLib from '@/uni_modules/RongCloud-IMWrapper/js_sdk/index'
const getMessageList = (conversationType, targetId, callback) => { const getMessageList = (conversationType, targetId, timeStamp, count, isForward, callback) => {
// 获取消息列表 // 获取消息列表 https://doc.rongcloud.cn/imserver/server/v1/message/objectname#objectName
const objectNames = [ const objectNames = [
'RC:TxtMsg', 'RC:TxtMsg',
'RC:VcMsg', 'RC:VcMsg',
@@ -16,10 +17,12 @@ const getMessageList = (conversationType, targetId, callback) => {
'RC:ReferenceMsg', 'RC:ReferenceMsg',
'RC:CombineMsg' 'RC:CombineMsg'
] ]
const timeStamp = new Date().getTime()
const count = 10 // 获取的消息数量 RongIMLib.getHistoryMessagesByTimestamp(
const isForward = true // 是否向前获取 conversationType,
RongIMLib.getHistoryMessagesByTimestamp(conversationType, targetId, objectNames, timeStamp, targetId,
objectNames,
timeStamp + 1000,
count, count,
isForward, isForward,
({ ({
@@ -45,27 +48,28 @@ const getMessageList = (conversationType, targetId, callback) => {
* @param {string} content 消息内容 * @param {string} content 消息内容
* @param {function} callback 回调函数 * @param {function} callback 回调函数
*/ */
const sentText = (conversationType, targetId, content, callback) => { const sentText = (conversationType, targetId, content, user, callback) => {
console.log('发送');
const msg = { const msg = {
conversationType: conversationType, conversationType: conversationType,
targetId: String(targetId), targetId: String(targetId),
content: { content: {
objectName: 'RC:TxtMsg', objectName: 'RC:TxtMsg',
content: content, content: content,
user: store.getters.sender userInfo: user
} }
} }
RongIMLib.sendMessage(msg, ({ RongIMLib.sendMessage(msg, ({
code, code,
messageId messageId
}) => { }) => {
if (code === 0) { if (code === 0) {
callback(messageId) callback(messageId)
} else { } else {
console.log('发送失败', msg);
uni.showToast({ uni.showToast({
icon: 'none', icon: 'none',
title: '发送失败' title: '发送失败' + code
}) })
} }
}) })
@@ -79,14 +83,15 @@ const sentText = (conversationType, targetId, content, callback) => {
* @param {integer} time 录音时长 * @param {integer} time 录音时长
* @param {function} callback 录音时长 * @param {function} callback 录音时长
*/ */
const sentVoice = (conversationType, targetId, voiceUrl, time, callback) => { const sentVoice = (conversationType, targetId, voiceUrl, time, user, callback) => {
const msg = { const msg = {
conversationType: conversationType, conversationType: conversationType,
targetId: String(targetId), targetId: String(targetId),
content: { content: {
objectName: 'RC:HQVCMsg', objectName: 'RC:HQVCMsg',
local: 'file:///' + plus.io.convertLocalFileSystemURL(voiceUrl), local: 'file:///' + plus.io.convertLocalFileSystemURL(voiceUrl),
duration: time duration: time,
userInfo: user
} }
} }
RongIMLib.sendMediaMessage(msg, { RongIMLib.sendMediaMessage(msg, {
@@ -105,14 +110,14 @@ const sentVoice = (conversationType, targetId, voiceUrl, time, callback) => {
}) })
} }
const sentImage = (conversationType, targetId, imageUrl, time, callback) => { const sentImage = (conversationType, targetId, imageUrl, user, callback) => {
const msg = { const msg = {
conversationType: conversationType, conversationType: conversationType,
targetId: String(targetId), targetId: String(targetId),
content: { content: {
objectName: 'RC:ImgMsg', objectName: 'RC:ImgMsg',
local: 'file:///' + plus.io.convertLocalFileSystemURL(imageUrl), local: 'file:///' + plus.io.convertLocalFileSystemURL(imageUrl),
duration: time userInfo: user
} }
} }
RongIMLib.sendMediaMessage(msg, { RongIMLib.sendMediaMessage(msg, {

View File

@@ -2,21 +2,21 @@ import {
usqlite usqlite
} from '@/uni_modules/onemue-USQLite/js_sdk/usqlite.js' } from '@/uni_modules/onemue-USQLite/js_sdk/usqlite.js'
const friendModel = usqlite.model('friends', { const contactModel = usqlite.model('contacts', {
userId: { targetId: {
type: String, type: String,
primaryKey: true, primaryKey: true,
unique: true unique: true
}, },
name: String, name: String,
address: String,
hash: { hash: {
type: String, type: String,
unique: true unique: true
}, },
portraitUrl: String portraitUrl: String,
}) localAvatar: String
})
export default { export default {
friendModel contactModel
} }

View File

@@ -1,51 +0,0 @@
{
"type": "Engine:OnCallReceived",
"module": "RongCloud-Call-RCUniCall",
"data": {
"endTime": 0,
"users": [{
"userId": "10051",
"enableCamera": false,
"mediaId": "420111350",
"mediaType": 1,
"userType": 0,
"enableMicrophone": false
}, {
"userId": "10047",
"enableCamera": false,
"mediaType": 1,
"userType": 0,
"enableMicrophone": false
}],
"inviter": {
"userId": "10051",
"enableCamera": false,
"mediaId": "420111350",
"mediaType": 1,
"userType": 0,
"enableMicrophone": false
},
"caller": {
"userId": "10051",
"enableCamera": false,
"mediaId": "420111350",
"mediaType": 1,
"userType": 0,
"enableMicrophone": false
},
"connectedTime": 0,
"extra": "",
"startTime": 0,
"mediaType": 1,
"callId": "c28cb9d8-6581-474c-bfa5-9872a4824b65",
"targetId": "10051",
"callType": 0,
"mine": {
"userId": "10047",
"enableCamera": false,
"mediaType": 1,
"userType": 0,
"enableMicrophone": false
}
}
}

View File

@@ -1,51 +0,0 @@
{
"type": "Engine:OnCallReceived",
"module": "RongCloud-Call-RCUniCall",
"data": {
"endTime": 0,
"users": [{
"userId": "10051",
"enableCamera": false,
"mediaId": "420068630",
"mediaType": 0,
"userType": 0,
"enableMicrophone": false
}, {
"userId": "10047",
"enableCamera": true,
"mediaType": 0,
"userType": 0,
"enableMicrophone": false
}],
"inviter": {
"userId": "10051",
"enableCamera": false,
"mediaId": "420068630",
"mediaType": 0,
"userType": 0,
"enableMicrophone": false
},
"caller": {
"userId": "10051",
"enableCamera": false,
"mediaId": "420068630",
"mediaType": 0,
"userType": 0,
"enableMicrophone": false
},
"connectedTime": 0,
"extra": "",
"startTime": 0,
"mediaType": 0,
"callId": "1a1462b8-b63b-40a9-bf95-963e810ac49a",
"targetId": "10051",
"callType": 0,
"mine": {
"userId": "10047",
"enableCamera": true,
"mediaType": 0,
"userType": 0,
"enableMicrophone": false
}
}
}

View File

@@ -1,221 +1,183 @@
import sha1 from './sha1.js' import env from './conf/env.js'
import env from './conf/env.js'
import * as RongIMLib from '@rongcloud/imlib-uni'
export default { export default {
checkPhone: (phone) => { checkPhone: (phone) => {
let re = /^[0-9]+.?[0-9]*/; let re = /^[0-9]+.?[0-9]*/;
if (phone.length === 11) { if (phone.length === 11) {
return re.test(phone) return re.test(phone)
} }
return false; return false;
}, },
jumpUrl(path) { jumpUrl(path) {
uni.navigateTo({ uni.navigateTo({
url: path url: path
}) })
}, },
connectIM() { handleTimeCustom(val) {
if(uni.getStorageSync('userInfo')) { val = this.timeStamp(val, 'Y-m-d H:i:s')
let userInfo = JSON.parse(uni.getStorageSync('userInfo')); let currentDate = new Date();
const im = getApp().globalData.im; let currentD = currentDate.getDate();
im.connect({ let currentYear = currentDate.getFullYear();
token: userInfo.rongimToken let currentMonth = currentDate.getMonth() + 1;
}).then(user => { let date = val.substring(0, 19);
console.log('IM链接成功, 链接用户 id 为: ', user.id); date = date.replace(/-/g, '/');
}).catch(error => { let valDate = new Date(date);
uni.hideLoading(); let valD = valDate.getDate();
console.log('IM链接失败: ', error.code, error.msg); let valYear = valDate.getFullYear();
}); let valMonth = valDate.getMonth() + 1;
} // 判断是否属于今天,计算时分
}, let difftime = (currentDate - valDate) / 1000;
handleTimeCustom(val) { let hour = valDate.getHours();
val = this.timeStamp(val, 'Y-m-d H:i:s') hour = hour > 9 ? hour : '0' + hour;
let currentDate = new Date(); let minute = valDate.getMinutes();
let currentD = currentDate.getDate(); minute = minute > 9 ? minute : '0' + minute;
let currentYear = currentDate.getFullYear(); if (currentYear === valYear && currentMonth === valMonth && currentD === valD) {
let currentMonth = currentDate.getMonth() + 1; return hour + ':' + minute;
let date = val.substring(0,19); } else {
date = date.replace(/-/g,'/'); // 计算天
let valDate = new Date(date); if (currentYear === valYear && currentMonth === valMonth && currentD !== valD) {
let valD = valDate.getDate(); return valMonth + '月' + valD + '日 ' + hour + '时' + minute;
let valYear = valDate.getFullYear(); } else {
let valMonth = valDate.getMonth() + 1; return valYear + '年' + valMonth + '月' + valD + '日 ' + hour + ':' + minute;
// 判断是否属于今天,计算时分 }
let difftime = (currentDate - valDate) / 1000;
let hour = valDate.getHours();
hour = hour > 9 ? hour : '0' + hour;
let minute = valDate.getMinutes();
minute = minute > 9 ? minute : '0' + minute;
if(currentYear === valYear && currentMonth === valMonth && currentD === valD) {
return hour + ':' + minute;
} else {
// 计算天
if(currentYear === valYear && currentMonth === valMonth && currentD !== valD) {
return valMonth + '月' + valD + '日 ' + hour + '时' + minute;
} else {
return valYear + '年' + valMonth + '月' + valD + '日 ' + hour + ':' + minute;
}
}
},
handleTimeCustomCN(val) {
val = this.timeStamp(val, 'Y-m-d H:i:s')
console.log(val, '时间0000000');
let currentDate = new Date();
let currentD = currentDate.getDate();
let currentYear = currentDate.getFullYear();
let currentMonth = currentDate.getMonth() + 1;
let date = val.substring(0,19);
date = date.replace(/-/g,'/');
let valDate = new Date(date);
let valD = valDate.getDate();
let valYear = valDate.getFullYear();
let valMonth = valDate.getMonth() + 1;
// 判断是否属于今天,计算时分
let difftime = (currentDate - valDate) / 1000;
if(currentYear === valYear && currentMonth === valMonth && currentD === valD) {
let minute = parseInt(difftime % 3600 / 60);
if(minute <= 60) {
return minute === 0 ? '刚刚' : minute + '分钟前';
} else {
return (minute * 60).toFixed(0) + '小时前';
}
} else {
// 计算天
if(currentYear === valYear && currentMonth === valMonth && currentD - 1 === valD) {
return '昨天';
} else {
let days = Math.abs(currentDate.getTime() - valDate.getTime())/(1000*60*60*24);
return Math.ceil(days) + '天前';
}
}
},
timeStamp(timestamp, formats) {
/*
** 时间戳转换成指定格式日期
** eg.
** dateFormat(11111111111111, 'Y年m月d日 H时i分')
** → "2322年02月06日 03时45分"
*/
// formats格式包括
// 1. Y-m-d
// 2. Y-m-d H:i:s
// 3. Y年m月d日
// 4. Y年m月d日 H时i分
formats = formats || 'Y-m-d';
var zero = function(value) { }
if (value < 10) { },
return '0' + value; handleTimeCustomCN(val) {
} val = this.timeStamp(val, 'Y-m-d H:i:s')
return value; console.log(val, '时间0000000');
}; let currentDate = new Date();
var myDate = timestamp ? new Date(timestamp) : new Date(); let currentD = currentDate.getDate();
let currentYear = currentDate.getFullYear();
let currentMonth = currentDate.getMonth() + 1;
let date = val.substring(0, 19);
date = date.replace(/-/g, '/');
let valDate = new Date(date);
let valD = valDate.getDate();
let valYear = valDate.getFullYear();
let valMonth = valDate.getMonth() + 1;
// 判断是否属于今天,计算时分
let difftime = (currentDate - valDate) / 1000;
if (currentYear === valYear && currentMonth === valMonth && currentD === valD) {
let minute = parseInt(difftime % 3600 / 60);
if (minute <= 60) {
return minute === 0 ? '刚刚' : minute + '分钟前';
} else {
return (minute * 60).toFixed(0) + '小时前';
}
} else {
// 计算天
if (currentYear === valYear && currentMonth === valMonth && currentD - 1 === valD) {
return '昨天';
} else {
let days = Math.abs(currentDate.getTime() - valDate.getTime()) / (1000 * 60 * 60 * 24);
return Math.ceil(days) + '天前';
}
var year = myDate.getFullYear(); }
var month = zero(myDate.getMonth() + 1); },
var day = zero(myDate.getDate()); timeStamp(timestamp, formats) {
/*
** 时间戳转换成指定格式日期
** eg.
** dateFormat(11111111111111, 'Y年m月d日 H时i分')
** → "2322年02月06日 03时45分"
*/
// formats格式包括
// 1. Y-m-d
// 2. Y-m-d H:i:s
// 3. Y年m月d日
// 4. Y年m月d日 H时i分
formats = formats || 'Y-m-d';
var hour = zero(myDate.getHours()); var zero = function(value) {
var minite = zero(myDate.getMinutes()); if (value < 10) {
var second = zero(myDate.getSeconds()); return '0' + value;
}
return value;
};
var myDate = timestamp ? new Date(timestamp) : new Date();
return formats.replace(/Y|m|d|H|i|s/ig, function(matches) { var year = myDate.getFullYear();
return ({ var month = zero(myDate.getMonth() + 1);
Y: year, var day = zero(myDate.getDate());
m: month,
d: day,
H: hour,
i: minite,
s: second
})[matches];
});
},
// 时间字符串转换中文时间
timeToDate(str) {
let date = str.substring(0, 19);
date = date.replace(/-/g, '/');
date = new Date(str);
let dateObj = {
year: date.getFullYear(),
month: date.getMonth() + 1,
day: date.getDate(),
hour: date.getHours(),
minute: date.getMinutes(),
second: date.getSeconds()
}
let string = dateObj.year + '年' + dateObj.month + '月' + dateObj.day + '日 ' + dateObj.hour + '时' + dateObj
.minute + '分';
return string
},
getAge(strAge) {
let birArr = strAge.split("-");
let birYear = birArr[0];
let birMonth = birArr[1];
let birDay = birArr[2];
d = new Date(); var hour = zero(myDate.getHours());
let nowYear = d.getFullYear(); var minite = zero(myDate.getMinutes());
let nowMonth = d.getMonth() + 1; //记得加1 var second = zero(myDate.getSeconds());
let nowDay = d.getDate();
let returnAge;
if (birArr == null) { return formats.replace(/Y|m|d|H|i|s/ig, function(matches) {
return false return ({
}; Y: year,
let d = new Date(birYear, birMonth - 1, birDay); m: month,
if (d.getFullYear() == birYear && (d.getMonth() + 1) == birMonth && d.getDate() == birDay) { d: day,
if (nowYear == birYear) { H: hour,
returnAge = 0; i: minite,
} else { s: second
let ageDiff = nowYear - birYear; })[matches];
if (ageDiff > 0) { });
if (nowMonth == birMonth) { },
let dayDiff = nowDay - birDay; // 时间字符串转换中文时间
if (dayDiff < 0) { timeToDate(str) {
returnAge = ageDiff - 1; let date = str.substring(0, 19);
} else { date = date.replace(/-/g, '/');
returnAge = ageDiff; date = new Date(str);
} let dateObj = {
} else { year: date.getFullYear(),
let monthDiff = nowMonth - birMonth; month: date.getMonth() + 1,
if (monthDiff < 0) { day: date.getDate(),
returnAge = ageDiff - 1; hour: date.getHours(),
} else { minute: date.getMinutes(),
returnAge = ageDiff; second: date.getSeconds()
} }
} let string = dateObj.year + '年' + dateObj.month + '月' + dateObj.day + '日 ' + dateObj.hour + '时' + dateObj
} else { .minute + '分';
return "出生日期晚于今天,数据有误"; //返回-1 表示出生日期输入错误 晚于今天 return string
} },
} getAge(strAge) {
return returnAge; let birArr = strAge.split("-");
} else { let birYear = birArr[0];
return ("输入的日期格式错误!"); let birMonth = birArr[1];
} let birDay = birArr[2];
},
getImUserInfo(targetId) { d = new Date();
return new Promise((resolve, reject) => { let nowYear = d.getFullYear();
const Nonce = Date.now(); let nowMonth = d.getMonth() + 1; //记得加1
const Timestamp = Date.now() * 1000; let nowDay = d.getDate();
uni.request({ let returnAge;
url: 'https://api2-cn.ronghub.com/user/info.json', //仅为示例,并非真实接口地址。
data: { if (birArr == null) {
userId: targetId return false
}, };
method: 'POST', let d = new Date(birYear, birMonth - 1, birDay);
header: { if (d.getFullYear() == birYear && (d.getMonth() + 1) == birMonth && d.getDate() == birDay) {
'Content-Type': 'application/x-www-form-urlencoded', if (nowYear == birYear) {
'App-Key': '你的key', returnAge = 0;
'Nonce': Nonce, } else {
'Timestamp': Timestamp, let ageDiff = nowYear - birYear;
'Signature': sha1(env.IMsecret + Nonce + Timestamp) if (ageDiff > 0) {
}, if (nowMonth == birMonth) {
success: (res) => { let dayDiff = nowDay - birDay;
resolve(res.data) if (dayDiff < 0) {
} returnAge = ageDiff - 1;
}); } else {
}) returnAge = ageDiff;
} }
} else {
let monthDiff = nowMonth - birMonth;
if (monthDiff < 0) {
returnAge = ageDiff - 1;
} else {
returnAge = ageDiff;
}
}
} else {
return "出生日期晚于今天,数据有误"; //返回-1 表示出生日期输入错误 晚于今天
}
}
return returnAge;
} else {
return ("输入的日期格式错误!");
}
}
} }

View File

@@ -1,50 +0,0 @@
function encodeUTF8(s) {
var i, r = [], c, x;
for (i = 0; i < s.length; i++)
if ((c = s.charCodeAt(i)) < 0x80) r.push(c);
else if (c < 0x800) r.push(0xC0 + (c >> 6 & 0x1F), 0x80 + (c & 0x3F));
else {
if ((x = c ^ 0xD800) >> 10 == 0) //对四字节UTF-16转换为Unicode
c = (x << 10) + (s.charCodeAt(++i) ^ 0xDC00) + 0x10000,
r.push(0xF0 + (c >> 18 & 0x7), 0x80 + (c >> 12 & 0x3F));
else r.push(0xE0 + (c >> 12 & 0xF));
r.push(0x80 + (c >> 6 & 0x3F), 0x80 + (c & 0x3F));
};
return r;
}
// 字符串加密成 hex 字符串
function sha1(s) {
var data = new Uint8Array(encodeUTF8(s))
var i, j, t;
var l = ((data.length + 8) >>> 6 << 4) + 16, s = new Uint8Array(l << 2);
s.set(new Uint8Array(data.buffer)), s = new Uint32Array(s.buffer);
for (t = new DataView(s.buffer), i = 0; i < l; i++)s[i] = t.getUint32(i << 2);
s[data.length >> 2] |= 0x80 << (24 - (data.length & 3) * 8);
s[l - 1] = data.length << 3;
var w = [], f = [
function () { return m[1] & m[2] | ~m[1] & m[3]; },
function () { return m[1] ^ m[2] ^ m[3]; },
function () { return m[1] & m[2] | m[1] & m[3] | m[2] & m[3]; },
function () { return m[1] ^ m[2] ^ m[3]; }
], rol = function (n, c) { return n << c | n >>> (32 - c); },
k = [1518500249, 1859775393, -1894007588, -899497514],
m = [1732584193, -271733879, null, null, -1009589776];
m[2] = ~m[0], m[3] = ~m[1];
for (i = 0; i < s.length; i += 16) {
var o = m.slice(0);
for (j = 0; j < 80; j++)
w[j] = j < 16 ? s[i + j] : rol(w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16], 1),
t = rol(m[0], 5) + f[j / 20 | 0]() + m[4] + w[j] + k[j / 20 | 0] | 0,
m[1] = rol(m[1], 30), m.pop(), m.unshift(t);
for (j = 0; j < 5; j++)m[j] = m[j] + o[j] | 0;
};
t = new DataView(new Uint32Array(m).buffer);
for (var i = 0; i < 5; i++)m[i] = t.getUint32(i << 2);
var hex = Array.prototype.map.call(new Uint8Array(new Uint32Array(m).buffer), function (e) {
return (e < 16 ? "0" : "") + e.toString(16);
}).join("");
return hex;
}
export default sha1

View File

@@ -1,126 +0,0 @@
import Bitcore from "bitcore-lib"
import Mnemonic from "bitcore-mnemonic"
import secp256k1 from 'secp256k1'
import {
Address,
pubToAddress,
toBuffer,
toChecksumAddress,
intToBuffer
} from 'ethereumjs-util'
import coinType from './networks.js'
import basex from 'base-x'
export default class Wallet {
static coinType = coinType
/**
* 生成助记词
* @param {Object} lang
*/
static generateMnemonic(lang) {
if (lang) {
return (new Mnemonic(this.getLanguage(lang))).toString();
} else {
return (new Mnemonic()).toString();
}
}
/**
* 验证助记词
* @param {Object} code
* @param {Object} lang
*/
static validMnemonic(code, lang) {
if (lang) {
return Mnemonic.isValid(code, this.getLanguage(lang));
} else {
return Mnemonic.isValid(code);
}
}
/**
* 获取助记词字典
* @param {Object} lang
*/
static getLanguage(lang) {
return Mnemonic.Words[lang]
}
/**
* 转成硬钱包私钥
* @param {Object} code
*/
static toHDPrivateKey(code) {
return (new Mnemonic(code)).toHDPrivateKey()
}
/**
* 验证地址是否合法
* @param {Object} addr
*/
static isValidAddress(addr) {
return Bitcore.Address.isValid(addr)
}
/**
* 硬钱包私钥转成对应网络的 地址 和 私钥
* @param {Object} hdPrivateKey
* @param {Object} type
*/
static HDPrivateKeyToAddress(hdPrivateKey, type) {
const derived = hdPrivateKey.derive("m/44'/" + type.type + "'/0'/0/0");
if (type.type === 195) {
const ethAddr = this.getEthereumAddress(derived)
const addressBuffer = Buffer.concat([intToBuffer(0x41), ethAddr.buf], 21)
return {
address: Bitcore.encoding.Base58Check.encode(addressBuffer),
public_key: derived.privateKey.publicKey.toString(),
private_key: derived.privateKey.toString()
}
}
if (type.type === 144) {
let addr = derived.privateKey.toAddress(type.network).toString()
let deco = Bitcore.encoding.Base58.decode(addr)
return {
address: basex('rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz').encode(deco),
public_key: derived.privateKey.publicKey.toString(),
private_key: derived.privateKey.toString()
}
}
if (this.networkIsEthereum(type)) {
return {
address: toChecksumAddress(this.getEthereumAddress(derived).toString()),
public_key: derived.privateKey.publicKey.toString(),
private_key: derived.privateKey.toString()
}
}
return {
address: derived.privateKey.toAddress(type.network).toString(),
public_key: derived.privateKey.publicKey.toString(),
private_key: derived.privateKey.toString()
}
}
/**
* 以太坊地址格式转换
* @param {Object} derived
*/
static getEthereumAddress(derived) {
const publicKey = derived.hdPublicKey.publicKey.toBuffer()
const ethPublicKey = secp256k1.publicKeyConvert(publicKey, false)
.slice(1)
return Address.fromPublicKey(toBuffer(ethPublicKey))
}
/**
* 是否是以太坊网络
* @param {Object} type
*/
static networkIsEthereum(type) {
return type.isEthereum
}
}

View File

@@ -1,29 +0,0 @@
import {
Wallet
} from "./Wallet.js"
const code = Wallet.generateMnemonic(this.defaultLanguage)
this.mnemonicCode = code
const hdPrivateKey = Wallet.toHDPrivateKey(this.mnemonicCode)
// const derived = hdPrivateKey.derive("m/44'/61'/0'/0/0");
// const publicKey = derived.hdPublicKey.publicKey.toBuffer()
// const ethPublicKey = secp256k1.publicKeyConvert(publicKey, false)
// .slice(1)
// const ethAddr = Address.fromPublicKey(toBuffer(ethPublicKey)).toString();
// console.log(toChecksumAddress(ethAddr));
// console.log(Address.fromPrivateKey(hdPrivateKey.hdPublicKey.publicKey.toBuffer()));
var addr = []
for (var i in this.coinType) {
let whk = Wallet.HDPrivateKeyToAddress(hdPrivateKey, i)
let parmas = {
name: this.coinType[i],
address: whk.address,
private_key: whk.private_key,
}
addr.push(parmas)
}
this.address = addr

View File

@@ -1,24 +0,0 @@
import store from "@/store/index.js"
const USE_SOTER_AUTH_KEY = 'USE_SOTER_AUTH_KEY'
/**
* 初始化配置
*/
const initWalletConfigs = () => {
// 生物识别
const USE_SOTER = Boolean(uni.getStorageSync(USE_SOTER_AUTH_KEY))
store.dispatch('wallet/setSoterAuth', USE_SOTER)
// 获取默认钱包
}
const setSoterAuthStatus = (opt) => {
uni.setStorageSync(USE_SOTER_AUTH_KEY, opt)
store.dispatch('wallet/setSoterAuth', opt)
}
export default {
USE_SOTER_AUTH_KEY,
setSoterAuthStatus,
initWalletConfigs
}

View File

@@ -1,209 +0,0 @@
import Bitcore from "bitcore-lib"
export default [{
type: 0,
name: '比特币',
symbol: 'BTC',
code: 'btc',
isEthereum: false,
network: Bitcore.Networks.mainnet
},
{
type: 60,
name: '以太坊',
symbol: 'ETH',
code: 'eth',
isEthereum: true
},
{
type: 61,
name: '以太经典',
symbol: 'ETC',
code: 'etc',
isEthereum: true
},
{
type: 60,
name: '赤子心',
symbol: 'CZX',
code: 'eth_0x3a2a239b1bdaae768ffa06314d523e88e98d4d1f',
isEthereum: true
},
// {
// type: 2,
// name: '莱特币',
// symbol: 'LTC',
// isEthereum: false,
// network: Bitcore.Networks.add({
// name: 'LTC',
// alias: 'LTC',
// pubkeyhash: 0x30,
// privatekey: 0x32,
// scripthash: 0xb0,
// bech32prefix: 'ltc',
// xpubkey: 0x019da462,
// xprivkey: 0x019d9cfe,
// networkMagic: 0xdbb6c0fb
// })
// },
{
type: 3,
name: '狗狗币',
symbol: 'DOGE',
code: 'doge',
isEthereum: false,
network: Bitcore.Networks.add({
name: 'DOGE',
alias: 'DOGE',
pubkeyhash: 0x1e,
privatekey: 0x16,
scripthash: 0x9e,
bech32prefix: 'doge',
xpubkey: 0x02facafd,
xprivkey: 0x02fac398,
networkMagic: 0xc0c0c0c0
})
},
// {
// type: 133,
// name: '零币',
// symbol: 'ZEC',
// isEthereum: false,
// network: Bitcore.Networks.add({
// name: 'ZEC',
// alias: 'ZEC',
// pubkeyhash: 0x1e,
// privatekey: 0x16,
// scripthash: 0x9e,
// bech32prefix: 'doge',
// xpubkey: 0x02facafd,
// xprivkey: 0x02fac398,
// networkMagic: 0xc0c0c0c0
// })
// },
// {
// type: 144,
// name: 'XPR - 瑞波币',
// symbol: 'XPR',
// isEthereum: false,
// network: Bitcore.Networks.add({
// name: 'XPR',
// alias: 'XPR',
// pubkeyhash: 0x1e,
// privatekey: 0x16,
// scripthash: 0x9e,
// bech32prefix: 'doge',
// xpubkey: 0x02facafd,
// xprivkey: 0x02fac398,
// networkMagic: 0xc0c0c0c0
// })
// },
// {
// type: 145,
// name: '比特现金',
// symbol: 'BCH',
// isEthereum: false,
// network: Bitcore.Networks.add({
// name: 'BCH',
// alias: 'BCH',
// pubkeyhash: 0x00,
// privatekey: 0x05,
// scripthash: 0x80,
// bech32prefix: 'bitcoincash',
// xpubkey: 0x0488b21e,
// xprivkey: 0x0488ade4,
// networkMagic: 0xd9b4bef9
// })
// },
// {
// type: 195,
// name: '波场',
// symbol: 'TRX',
// isEthereum: false,
// network: Bitcore.Networks.add({
// name: 'TRX',
// alias: 'TRX',
// pubkeyhash: 0x41,
// privatekey: 0x05,
// scripthash: 0x80,
// bech32prefix: '',
// xpubkey: 0x0488b21e,
// xprivkey: 0x0488ade4
// })
// },
{
type: 195,
name: 'USDT(TRC20)',
symbol: 'USDT',
code: 'trx_TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t',
isEthereum: false,
network: Bitcore.Networks.add({
name: 'USDT',
alias: 'USDT',
pubkeyhash: 0x41,
privatekey: 0x05,
scripthash: 0x80,
bech32prefix: '',
xpubkey: 0x0488b21e,
xprivkey: 0x0488ade4
})
},
{
type: 195,
name: 'USDT(ERC20)',
symbol: 'USDT',
code: 'eth_0xdac17f958d2ee523a2206206994597c13d831ec7',
isEthereum: true
},
{
type: 0,
name: 'USDT(OMNI)',
symbol: 'USDT',
code: 'usdt',
isEthereum: false,
network: Bitcore.Networks.mainnet
},
{
type: 13107,
name: '比特元',
symbol: 'BTY',
code: 'bty',
isEthereum: false,
network: Bitcore.Networks.add({
name: 'BTY',
alias: 'BTY',
pubkeyhash: 0x00,
privatekey: 0x05,
scripthash: 0x80,
bech32prefix: 'bityuan',
xpubkey: 0x0488b21e,
xprivkey: 0x0488ade4,
networkMagic: 0xd9b4bef9
})
},
// {
// type: 60,
// name: '元链',
// symbol: 'YCC',
// isEthereum: true
// },
{
type: 13107,
name: 'JZC',
symbol: 'JZC',
code: 'bty',
isEthereum: false,
network: Bitcore.Networks.add({
name: 'JZC',
alias: 'JZC',
pubkeyhash: 0x00,
privatekey: 0x05,
scripthash: 0x80,
bech32prefix: 'bityuan',
xpubkey: 0x0488b21e,
xprivkey: 0x0488ade4,
networkMagic: 0xd9b4bef9
})
}
]

207
yarn.lock
View File

@@ -2,194 +2,27 @@
# yarn lockfile v1 # yarn lockfile v1
"base-x@^3.0.2": moment@^2.29.1:
"integrity" "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==" version "2.29.1"
"resolved" "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz" resolved "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz"
"version" "3.0.9" integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
dependencies:
"safe-buffer" "^5.0.1"
"bech32@=2.0.0": uni-read-pages@^1.0.5:
"integrity" "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==" version "1.0.5"
"resolved" "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz" resolved "https://registry.npmjs.org/uni-read-pages/-/uni-read-pages-1.0.5.tgz"
"version" "2.0.0" integrity sha512-GkrrZ0LX0vn9R5k6RKEi0Ez3Q3e2vUpjXQ8Z6/K/d28KudI9ajqgt8WEjQFlG5EPm1K6uTArN8LlqmZTEixDUA==
"bigi@^1.1.0", "bigi@^1.4.2": uni-simple-router@^2.0.7:
"integrity" "sha1-nGZalfiLiwj8Bc/XMfVhhZ1yWCU=" version "2.0.7"
"resolved" "https://registry.npmjs.org/bigi/-/bigi-1.4.2.tgz" resolved "https://registry.npmjs.org/uni-simple-router/-/uni-simple-router-2.0.7.tgz"
"version" "1.4.2" integrity sha512-8FKv5dw7Eoonm0gkO8udprrxzin0fNUI0+AvIphFkFRH5ZmP5ZWJ2pvnWzb2NiiqQSECTSU5VSB7HhvOSwD5eA==
"bip-schnorr@=0.6.4": uview-ui@^2.0.19:
"integrity" "sha512-dNKw7Lea8B0wMIN4OjEmOk/Z5qUGqoPDY0P2QttLqGk1hmDPytLWW8PR5Pb6Vxy6CprcdEgfJpOjUu+ONQveyg==" version "2.0.19"
"resolved" "https://registry.npmjs.org/bip-schnorr/-/bip-schnorr-0.6.4.tgz" resolved "https://registry.npmjs.org/uview-ui/-/uview-ui-2.0.19.tgz"
"version" "0.6.4" integrity sha512-ddZiaP7R9wsUxMzAuhuXgh5OswgCm2lKuulTqjnRXFr0uUWsgL1iBifU3GbOwpwP0LtTHKJOo9rYv1LP0WXmzA==
dependencies:
"bigi" "^1.4.2"
"ecurve" "^1.0.6"
"js-sha256" "^0.9.0"
"randombytes" "^2.1.0"
"safe-buffer" "^5.2.1"
"bitcore-lib@^8.25.25": vuex@^3.6.2:
"integrity" "sha512-H6qNCVl4M8/MglXhvc04mmeus1d6nrmqTJGQ+xezJLvL7hs7R3dyBPtOqSP3YSw0iq/GWspMd8f5OOlyXVipJQ==" version "3.6.2"
"resolved" "https://registry.npmjs.org/bitcore-lib/-/bitcore-lib-8.25.25.tgz" resolved "https://registry.npmjs.org/vuex/-/vuex-3.6.2.tgz"
"version" "8.25.25" integrity sha512-ETW44IqCgBpVomy520DT5jf8n0zoCac+sxWnn+hMe/CzaSejb/eVw2YToiXYX+Ex/AuHHia28vWTq4goAexFbw==
dependencies:
"bech32" "=2.0.0"
"bip-schnorr" "=0.6.4"
"bn.js" "=4.11.8"
"bs58" "^4.0.1"
"buffer-compare" "=1.1.1"
"elliptic" "^6.5.3"
"inherits" "=2.0.1"
"lodash" "^4.17.20"
"bitcore-mnemonic@^8.25.25":
"integrity" "sha512-7HvRxHrmd+Rh0Ohl0SEDMKQBAM+FoevXbCFnxGju6H+uZjtWMOToHA8vUg0+B91pfEMjdt9mQVB/wSA8GMqnCA=="
"resolved" "https://registry.npmjs.org/bitcore-mnemonic/-/bitcore-mnemonic-8.25.25.tgz"
"version" "8.25.25"
dependencies:
"bitcore-lib" "^8.25.25"
"unorm" "^1.4.1"
"bn.js@^4.11.9":
"integrity" "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
"resolved" "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz"
"version" "4.12.0"
"bn.js@=4.11.8":
"integrity" "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA=="
"resolved" "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz"
"version" "4.11.8"
"brorand@^1.1.0":
"integrity" "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8="
"resolved" "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz"
"version" "1.1.0"
"bs58@^4.0.1":
"integrity" "sha1-vhYedsNU9veIrkBx9j806MTwpCo="
"resolved" "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz"
"version" "4.0.1"
dependencies:
"base-x" "^3.0.2"
"buffer-compare@=1.1.1":
"integrity" "sha1-W+e+hTr4kZjR9N3AkNHWakiu9ZY="
"resolved" "https://registry.npmjs.org/buffer-compare/-/buffer-compare-1.1.1.tgz"
"version" "1.1.1"
"ecurve@^1.0.6":
"integrity" "sha512-/BzEjNfiSuB7jIWKcS/z8FK9jNjmEWvUV2YZ4RLSmcDtP7Lq0m6FvDuSnJpBlDpGRpfRQeTLGLBI8H+kEv0r+w=="
"resolved" "https://registry.npmjs.org/ecurve/-/ecurve-1.0.6.tgz"
"version" "1.0.6"
dependencies:
"bigi" "^1.1.0"
"safe-buffer" "^5.0.1"
"elliptic@^6.5.3":
"integrity" "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ=="
"resolved" "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz"
"version" "6.5.4"
dependencies:
"bn.js" "^4.11.9"
"brorand" "^1.1.0"
"hash.js" "^1.0.0"
"hmac-drbg" "^1.0.1"
"inherits" "^2.0.4"
"minimalistic-assert" "^1.0.1"
"minimalistic-crypto-utils" "^1.0.1"
"hash.js@^1.0.0", "hash.js@^1.0.3":
"integrity" "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA=="
"resolved" "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz"
"version" "1.1.7"
dependencies:
"inherits" "^2.0.3"
"minimalistic-assert" "^1.0.1"
"hmac-drbg@^1.0.1":
"integrity" "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE="
"resolved" "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz"
"version" "1.0.1"
dependencies:
"hash.js" "^1.0.3"
"minimalistic-assert" "^1.0.0"
"minimalistic-crypto-utils" "^1.0.1"
"inherits@^2.0.3":
"integrity" "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
"resolved" "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz"
"version" "2.0.4"
"inherits@^2.0.4":
"integrity" "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
"resolved" "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz"
"version" "2.0.4"
"inherits@=2.0.1":
"integrity" "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE="
"resolved" "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
"version" "2.0.1"
"js-sha256@^0.9.0":
"integrity" "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA=="
"resolved" "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz"
"version" "0.9.0"
"lodash@^4.17.20":
"integrity" "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
"resolved" "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz"
"version" "4.17.21"
"minimalistic-assert@^1.0.0", "minimalistic-assert@^1.0.1":
"integrity" "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
"resolved" "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz"
"version" "1.0.1"
"minimalistic-crypto-utils@^1.0.1":
"integrity" "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo="
"resolved" "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz"
"version" "1.0.1"
"moment@^2.29.1":
"integrity" "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ=="
"resolved" "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz"
"version" "2.29.1"
"randombytes@^2.1.0":
"integrity" "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ=="
"resolved" "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz"
"version" "2.1.0"
dependencies:
"safe-buffer" "^5.1.0"
"safe-buffer@^5.0.1", "safe-buffer@^5.1.0", "safe-buffer@^5.2.1":
"integrity" "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
"resolved" "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz"
"version" "5.2.1"
"uni-read-pages@^1.0.5":
"integrity" "sha512-GkrrZ0LX0vn9R5k6RKEi0Ez3Q3e2vUpjXQ8Z6/K/d28KudI9ajqgt8WEjQFlG5EPm1K6uTArN8LlqmZTEixDUA=="
"resolved" "https://registry.npmjs.org/uni-read-pages/-/uni-read-pages-1.0.5.tgz"
"version" "1.0.5"
"uni-simple-router@^2.0.7":
"integrity" "sha512-8FKv5dw7Eoonm0gkO8udprrxzin0fNUI0+AvIphFkFRH5ZmP5ZWJ2pvnWzb2NiiqQSECTSU5VSB7HhvOSwD5eA=="
"resolved" "https://registry.npmjs.org/uni-simple-router/-/uni-simple-router-2.0.7.tgz"
"version" "2.0.7"
"unorm@^1.4.1":
"integrity" "sha512-b2/KCUlYZUeA7JFUuRJZPUtr4gZvBh7tavtv4fvk4+KV9pfGiR6CQAQAWl49ZpR3ts2dk4FYkP7EIgDJoiOLDA=="
"resolved" "https://registry.npmjs.org/unorm/-/unorm-1.6.0.tgz"
"version" "1.6.0"
"uview-ui@^2.0.19":
"integrity" "sha512-ddZiaP7R9wsUxMzAuhuXgh5OswgCm2lKuulTqjnRXFr0uUWsgL1iBifU3GbOwpwP0LtTHKJOo9rYv1LP0WXmzA=="
"resolved" "https://registry.npmjs.org/uview-ui/-/uview-ui-2.0.19.tgz"
"version" "2.0.19"
"vuex@^3.6.2":
"integrity" "sha512-ETW44IqCgBpVomy520DT5jf8n0zoCac+sxWnn+hMe/CzaSejb/eVw2YToiXYX+Ex/AuHHia28vWTq4goAexFbw=="
"resolved" "https://registry.npmjs.org/vuex/-/vuex-3.6.2.tgz"
"version" "3.6.2"