123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337 |
- const FATAL_REBUILD_TOLERANCE = 10
- const SETDATA_SCROLL_TO_BOTTOM = {
- scrollTop: 100000,
- scrollWithAnimation: true,
- }
- Component({
- properties: {
- envId: String,
- collection: String,
- groupId: String,
- groupName: String,
- userInfo: Object,
- onGetUserInfo: {
- type: Function,
- },
- getOpenID: {
- type: Function,
- },
- },
- data: {
- chats: [],
- textInputValue: '',
- openId: '',
- scrollTop: 0,
- scrollToMessage: '',
- hasKeyboard: false,
- },
- methods: {
- onGetUserInfo(e) {
- this.properties.onGetUserInfo(e)
- },
- getOpenID() {
- return this.properties.getOpenID()
- },
- mergeCommonCriteria(criteria) {
- return {
- groupId: this.data.groupId,
- ...criteria,
- }
- },
- async initRoom() {
- this.try(async () => {
- await this.initOpenID()
- const { envId, collection } = this.properties
- this.db = wx.cloud.database({
- env: envId,
- })
- const db = this.db
- const _ = db.command
- const { data: initList } = await db.collection(collection).where(this.mergeCommonCriteria()).orderBy('sendTimeTS', 'desc').get()
- console.log('init query chats', initList)
- this.setData({
- chats: initList.reverse(),
- scrollTop: 10000,
- })
- this.initWatch(initList.length ? {
- sendTimeTS: _.gt(initList[initList.length - 1].sendTimeTS),
- } : {})
- }, '初始化失败')
- },
- async initOpenID() {
- return this.try(async () => {
- const openId = await this.getOpenID()
- this.setData({
- openId,
- })
- }, '初始化 openId 失败')
- },
- async initWatch(criteria) {
- this.try(() => {
- const { collection } = this.properties
- const db = this.db
- const _ = db.command
- console.warn(`开始监听`, criteria)
- this.messageListener = db.collection(collection).where(this.mergeCommonCriteria(criteria)).watch({
- onChange: this.onRealtimeMessageSnapshot.bind(this),
- onError: e => {
- if (!this.inited || this.fatalRebuildCount >= FATAL_REBUILD_TOLERANCE) {
- this.showError(this.inited ? '监听错误,已断开' : '初始化监听失败', e, '重连', () => {
- this.initWatch(this.data.chats.length ? {
- sendTimeTS: _.gt(this.data.chats[this.data.chats.length - 1].sendTimeTS),
- } : {})
- })
- } else {
- this.initWatch(this.data.chats.length ? {
- sendTimeTS: _.gt(this.data.chats[this.data.chats.length - 1].sendTimeTS),
- } : {})
- }
- },
- })
- }, '初始化监听失败')
- },
- onRealtimeMessageSnapshot(snapshot) {
- console.warn(`收到消息`, snapshot)
- if (snapshot.type === 'init') {
- this.setData({
- chats: [
- ...this.data.chats,
- ...[...snapshot.docs].sort((x, y) => x.sendTimeTS - y.sendTimeTS),
- ],
- })
- this.scrollToBottom()
- this.inited = true
- } else {
- let hasNewMessage = false
- let hasOthersMessage = false
- const chats = [...this.data.chats]
- for (const docChange of snapshot.docChanges) {
- switch (docChange.queueType) {
- case 'enqueue': {
- hasOthersMessage = docChange.doc._openid !== this.data.openId
- const ind = chats.findIndex(chat => chat._id === docChange.doc._id)
- if (ind > -1) {
- if (chats[ind].msgType === 'image' && chats[ind].tempFilePath) {
- chats.splice(ind, 1, {
- ...docChange.doc,
- tempFilePath: chats[ind].tempFilePath,
- })
- } else chats.splice(ind, 1, docChange.doc)
- } else {
- hasNewMessage = true
- chats.push(docChange.doc)
- }
- break
- }
- }
- }
- this.setData({
- chats: chats.sort((x, y) => x.sendTimeTS - y.sendTimeTS),
- })
- if (hasOthersMessage || hasNewMessage) {
- this.scrollToBottom()
- }
- }
- },
- async onConfirmSendText(e) {
- this.try(async () => {
- if (!e.detail.value) {
- return
- }
- const { collection } = this.properties
- const db = this.db
- const _ = db.command
- const doc = {
- _id: `${Math.random()}_${Date.now()}`,
- groupId: this.data.groupId,
- avatar: this.data.userInfo.avatarUrl,
- nickName: this.data.userInfo.nickName,
- msgType: 'text',
- textContent: e.detail.value,
- sendTime: new Date(),
- sendTimeTS: Date.now(), // fallback
- }
- this.setData({
- textInputValue: '',
- chats: [
- ...this.data.chats,
- {
- ...doc,
- _openid: this.data.openId,
- writeStatus: 'pending',
- },
- ],
- })
- this.scrollToBottom(true)
- await db.collection(collection).add({
- data: doc,
- })
- this.setData({
- chats: this.data.chats.map(chat => {
- if (chat._id === doc._id) {
- return {
- ...chat,
- writeStatus: 'written',
- }
- } else return chat
- }),
- })
- }, '发送文字失败')
- },
- async onChooseImage(e) {
- wx.chooseImage({
- count: 1,
- sourceType: ['album', 'camera'],
- success: async res => {
- const { envId, collection } = this.properties
- const doc = {
- _id: `${Math.random()}_${Date.now()}`,
- groupId: this.data.groupId,
- avatar: this.data.userInfo.avatarUrl,
- nickName: this.data.userInfo.nickName,
- msgType: 'image',
- sendTime: new Date(),
- sendTimeTS: Date.now(), // fallback
- }
- this.setData({
- chats: [
- ...this.data.chats,
- {
- ...doc,
- _openid: this.data.openId,
- tempFilePath: res.tempFilePaths[0],
- writeStatus: 0,
- },
- ]
- })
- this.scrollToBottom(true)
- const uploadTask = wx.cloud.uploadFile({
- cloudPath: `${this.data.openId}/${Math.random()}_${Date.now()}.${res.tempFilePaths[0].match(/\.(\w+)$/)[1]}`,
- filePath: res.tempFilePaths[0],
- config: {
- env: envId,
- },
- success: res => {
- this.try(async () => {
- await this.db.collection(collection).add({
- data: {
- ...doc,
- imgFileID: res.fileID,
- },
- })
- }, '发送图片失败')
- },
- fail: e => {
- this.showError('发送图片失败', e)
- },
- })
- uploadTask.onProgressUpdate(({ progress }) => {
- this.setData({
- chats: this.data.chats.map(chat => {
- if (chat._id === doc._id) {
- return {
- ...chat,
- writeStatus: progress,
- }
- } else return chat
- })
- })
- })
- },
- })
- },
- onMessageImageTap(e) {
- wx.previewImage({
- urls: [e.target.dataset.fileid],
- })
- },
- scrollToBottom(force) {
- if (force) {
- console.log('force scroll to bottom')
- this.setData(SETDATA_SCROLL_TO_BOTTOM)
- return
- }
- this.createSelectorQuery().select('.body').boundingClientRect(bodyRect => {
- this.createSelectorQuery().select(`.body`).scrollOffset(scroll => {
- if (scroll.scrollTop + bodyRect.height * 3 > scroll.scrollHeight) {
- console.log('should scroll to bottom')
- this.setData(SETDATA_SCROLL_TO_BOTTOM)
- }
- }).exec()
- }).exec()
- },
- async onScrollToUpper() {
- if (this.db && this.data.chats.length) {
- const { collection } = this.properties
- const _ = this.db.command
- const { data } = await this.db.collection(collection).where(this.mergeCommonCriteria({
- sendTimeTS: _.lt(this.data.chats[0].sendTimeTS),
- })).orderBy('sendTimeTS', 'desc').get()
- this.data.chats.unshift(...data.reverse())
- this.setData({
- chats: this.data.chats,
- scrollToMessage: `item-${data.length}`,
- scrollWithAnimation: false,
- })
- }
- },
- async try(fn, title) {
- try {
- await fn()
- } catch (e) {
- this.showError(title, e)
- }
- },
- showError(title, content, confirmText, confirmCallback) {
- console.error(title, content)
- wx.showModal({
- title,
- content: content.toString(),
- showCancel: confirmText ? true : false,
- confirmText,
- success: res => {
- res.confirm && confirmCallback()
- },
- })
- },
- },
- ready() {
- global.chatroom = this
- this.initRoom()
- this.fatalRebuildCount = 0
- },
- })
|