chat.jsx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  1. import Taro, { Component } from '@tarojs/taro'
  2. import { View, ScrollView } from '@tarojs/components'
  3. import ListMore from '@/c/pageDataList/listMore'
  4. import { strTrim, arrToObj } from '@utils'
  5. const HLKEY = '654mca0l38b489d9'
  6. const CJ = require('crypto-js')
  7. import './chat.scss'
  8. class Index extends Component {
  9. constructor (props) {
  10. super(props)
  11. const {id: curId} = this.$router.params
  12. this.state = {
  13. page_size: 10,
  14. page: 1,
  15. isListEnd: false,
  16. isListLoading: false,
  17. isListEmpty: false,
  18. dataList: [],
  19. curId,
  20. curObj: {},
  21. commentVal: '',
  22. msgSocketList: [],
  23. uObj: {},
  24. chatUserObj: {},
  25. viewId: '',
  26. socketTimer: null,
  27. }
  28. }
  29. config = {
  30. navigationBarTitleText: '在线沟通',
  31. }
  32. componentWillUnmount () {
  33. this.closeSocket()
  34. let pages = getCurrentPages()
  35. let prevPage = pages[ pages.length - 2 ]
  36. if (prevPage.$component.getNotifycount) prevPage.$component.getNotifycount()
  37. // if (prevPage.$component.getNewData) prevPage.$component.getNewData()
  38. }
  39. closeSocket () {
  40. Taro.closeSocket()
  41. const { socketTimer } = this.state
  42. clearInterval(socketTimer)
  43. }
  44. componentDidMount () {
  45. const that = this
  46. let uObj = Taro.getStorageSync('APP_userInfo')
  47. const { to_user_id } = this.$router.params
  48. this.setState({
  49. uObj
  50. }, () => {
  51. let uArr = [uObj.user_id, to_user_id]
  52. Taro.api.base.apiuserinfolist({
  53. user_ids: uArr.join(',')
  54. }).then(res => {
  55. let chatUserObj = {}
  56. const list = res.list || []
  57. list.forEach(item => {
  58. if (item.id !== uObj.user_id) {
  59. Taro.setNavigationBarTitle({
  60. title: item.nickname
  61. })
  62. }
  63. chatUserObj[item.id] = item
  64. })
  65. that.setState({
  66. chatUserObj
  67. })
  68. })
  69. Taro.$AHU(this)
  70. this.getDataList()
  71. this.initSocket()
  72. })
  73. }
  74. getDataList () {
  75. const { to_user_id } = this.$router.params
  76. let { page_size, page, dataList, isListEmpty } = this.state
  77. Taro.api.room.apiuserchatlist({
  78. page,
  79. page_size,
  80. to_user_id,
  81. }).then(res => {
  82. let curData = res.list || []
  83. curData = curData.reverse()
  84. let isListEnd = false
  85. if (curData.length > 0) {
  86. if (page === 1) {
  87. dataList = curData
  88. } else {
  89. dataList = [].concat(curData, dataList)
  90. }
  91. if (curData.length === page_size && res.total !== curData.length) {
  92. isListEnd = false
  93. } else {
  94. isListEnd = true
  95. }
  96. }
  97. if (curData.length === 0 && page === 1) {
  98. isListEmpty = true
  99. dataList = []
  100. } else {
  101. isListEmpty = false
  102. }
  103. const that = this
  104. this.setState({
  105. dataList,
  106. isListEnd,
  107. isListEmpty,
  108. isListLoading: false
  109. }, () => {
  110. if (page > 1) {
  111. setTimeout(() => {
  112. that.setState({
  113. viewId: `history11`
  114. })
  115. }, 500)
  116. }
  117. })
  118. })
  119. }
  120. onScrollToUpper (e) {
  121. let { isListEnd, isListLoading, page } = this.state
  122. if (!isListEnd && !isListLoading) {
  123. page++
  124. this.setState({
  125. page,
  126. isListLoading: true
  127. }, () => {
  128. this.getDataList()
  129. })
  130. }
  131. }
  132. initSocket () {
  133. const { uObj } = this.state
  134. const that = this
  135. Taro.connectSocket({
  136. url: 'wss://chat.honglouplus.com/acc',
  137. success: function () {
  138. console.log('connect success')
  139. }
  140. }).then(task => {
  141. task.onOpen(function () {
  142. // console.log('onOpen')
  143. let token = Taro.getStorageSync('APP_token')
  144. task.send({ data: '{"seq":"' + that.sendId() + '","cmd":"login","data":{"userId":"' + uObj.user_id + '","appId":101,"serviceToken":"' + token + '"}}' })
  145. const socketTimer = setInterval(() => {
  146. task.send({data: '{"seq":"' + that.sendId() + '","cmd":"heartbeat","data":{}}'});
  147. }, 30000)
  148. that.setState({
  149. socketTimer
  150. })
  151. })
  152. task.onMessage(function (msg) {
  153. // console.log('onMessage: ', msg)
  154. let { msgSocketList } = that.state
  155. const data = JSON.parse(msg.data)
  156. // console.log(data)
  157. if (data.cmd === 'msg' || data.cmd === 'login') {
  158. const res = data.response || {}
  159. if (res.code === 200) {
  160. if (data.cmd === 'login') {
  161. msgSocketList.push({msg: '登录成功~'})
  162. } else {
  163. const { to_user_id } = that.$router.params
  164. if (to_user_id === res.data.from) {
  165. msgSocketList.push(res.data)
  166. } else {
  167. msgSocketList.push({msg: '有其他人给你发消息啦,返回消息列表查看'})
  168. }
  169. }
  170. that.setState({
  171. msgSocketList
  172. }, () => {
  173. const { qId } = that.$router.params
  174. if (qId) that.sendHandle('house')
  175. const { eTitle, eId } = that.$router.params
  176. if (eId) that.sendHandle('text', `我刚刚浏览了楼盘:${eTitle},想咨询你一下`)
  177. setTimeout(() => {
  178. that.setState({
  179. viewId: `item${msgSocketList.length - 1}`
  180. })
  181. }, 500)
  182. })
  183. }
  184. }
  185. if (data.cmd === 'heartbeat') {
  186. const res = data.response || {}
  187. if (res.code !== 200) {
  188. msgSocketList.push({msg: res.codeMsg + '|暂无法接收消息,请退出当前页面重新登录~'})
  189. that.setState({
  190. msgSocketList
  191. }, () => {
  192. setTimeout(() => {
  193. that.setState({
  194. viewId: `item${msgSocketList.length - 1}`
  195. }, () => {
  196. that.closeSocket()
  197. })
  198. }, 100)
  199. })
  200. }
  201. }
  202. // task.close()
  203. })
  204. task.onError(function () {
  205. console.log('onError')
  206. })
  207. task.onClose(function (e) {
  208. console.log('onClose: ', e)
  209. })
  210. })
  211. }
  212. sendId () {
  213. let timeStamp = +new Date()
  214. let randId = parseInt(Math.random() * 1000000)
  215. return `${timeStamp}-${randId}`
  216. }
  217. componentDidShow () { }
  218. componentDidHide () { }
  219. avatarHandle (item) {
  220. const { uObj } = this.state
  221. if (uObj.is_sale === '1') {
  222. Taro.navigateTo({
  223. url: `/pagesRoom/follow/user?uId=${item.user_id}`
  224. })
  225. }
  226. }
  227. renderList () {
  228. const { chatUserObj, uObj, viewId } = this.state
  229. const { dataList, isListEnd, isListLoading, isListEmpty } = this.state
  230. const itemList = dataList.map((item, index) => {
  231. let tObj = {}
  232. if (item.type === 'house') tObj = JSON.parse(item.content)
  233. return (
  234. <View className="sl-item" key={index} id={`history${index}`}>
  235. <View className={item.user_id === uObj.user_id ? 'sl-wrap t2' : 'sl-wrap'}>
  236. {
  237. item.user_id === uObj.user_id
  238. ?
  239. <View className="sl-img">
  240. <Image className="img" src={chatUserObj[item.user_id].avatar}></Image>
  241. </View>
  242. :
  243. <View className="sl-img" onClick={this.avatarHandle.bind(this, item)}>
  244. <Image className="img" src={chatUserObj[item.user_id].avatar}></Image>
  245. </View>
  246. }
  247. <View className="sl-right">
  248. <View className="sign"></View>
  249. {
  250. item.type === 'house'
  251. ?
  252. <View className="sl-text">你好,我想咨询一下 <Navigator url={`/pagesRoom/dtl?id=${tObj.id}`} className="v">#{tObj.name}#</Navigator></View>
  253. : ''
  254. }
  255. {
  256. item.type === 'image'
  257. ?
  258. <View className="sl-content-img" onClick={this.previewImageHandle.bind(this, item.content, [item.content])}>
  259. <Image src={item.content} className="img" mode="aspectFit"></Image>
  260. </View>
  261. : ''
  262. }
  263. {
  264. item.type === 'text'
  265. ?
  266. <View className="sl-text" onClick={this.copyHandle.bind(this, item.content)}>{item.content}</View>
  267. : ''
  268. }
  269. <View className="sl-time">{item.create_at}</View>
  270. </View>
  271. </View>
  272. </View>
  273. )
  274. })
  275. return (
  276. <ScrollView
  277. className='l-scroll-view'
  278. scrollY
  279. scrollWithAnimation
  280. upperThreshold="10"
  281. scrollIntoView={viewId}
  282. onScrollToUpper={this.onScrollToUpper.bind(this)}
  283. >
  284. <ListMore isListEnd={isListEnd} isListLoading={isListLoading} isListEmpty={isListEmpty} text="暂无历史消息" />
  285. <View className="scoped-list">
  286. {itemList}
  287. </View>
  288. {/* {
  289. dataList.length > 0
  290. &&
  291. <AtDivider content="以上为历史消息" fontColor='#ccc' lineColor='#ccc' />
  292. } */}
  293. {this.renderMsgList()}
  294. <View id="msgId"></View>
  295. </ScrollView>
  296. )
  297. }
  298. previewImageHandle (current, urls) {
  299. Taro.previewImage({
  300. current,
  301. urls
  302. })
  303. }
  304. renderMsgList () {
  305. const { msgSocketList } = this.state
  306. const { chatUserObj, uObj } = this.state
  307. const curItems = msgSocketList.map((item, index) => {
  308. let tObj = {}
  309. if (item.type === 'house') tObj = JSON.parse(item.msg)
  310. return (
  311. <View className="sl-item" key={index} id={`item${index}`}>
  312. {
  313. item.from
  314. ?
  315. <View className={item.from === uObj.user_id ? 'sl-wrap t2' : 'sl-wrap'}>
  316. <View className="sl-img" onClick={this.avatarHandle.bind(this, item)}>
  317. <Image className="img" src={chatUserObj[item.from].avatar}></Image>
  318. </View>
  319. <View className="sl-right">
  320. <View className="sign"></View>
  321. {
  322. item.type === 'house'
  323. ?
  324. <View className="sl-text">你好,我想咨询一下 <Navigator url={`/pagesRoom/dtl?id=${tObj.id}`} className="v">#{tObj.name}#</Navigator></View>
  325. : ''
  326. }
  327. {
  328. item.type === 'image'
  329. ?
  330. <View className="sl-content-img" onClick={this.previewImageHandle.bind(this, item.content, [item.content])}>
  331. <Image src={item.content} className="img" mode="aspectFit"></Image>
  332. </View>
  333. : ''
  334. }
  335. {
  336. item.type === 'text'
  337. ?
  338. <View className="sl-text" onClick={this.copyHandle.bind(this, item.msg)}>{item.msg}</View>
  339. : ''
  340. }
  341. </View>
  342. </View>
  343. :
  344. <View className="scoped-msg-tips">{item.msg}</View>
  345. }
  346. </View>
  347. )
  348. })
  349. return (
  350. <View className="scoped-list">
  351. {curItems}
  352. </View>
  353. )
  354. }
  355. copyHandle (msg) {
  356. Taro.setClipboardData({
  357. data: msg,
  358. success: function (res) {
  359. Taro.getClipboardData({
  360. success: function (res) {
  361. console.log(res.data)
  362. }
  363. })
  364. }
  365. })
  366. }
  367. sendHandle (type, curMsg) {
  368. const that = this
  369. const { uObj } = this.state
  370. const { commentVal } = this.state
  371. let { msgSocketList } = this.state
  372. const { to_user_id } = this.$router.params
  373. let apiStr = 'apiusersendmessage'
  374. let msg = commentVal
  375. if (type === 'house') {
  376. const { qTitle, qId } = that.$router.params
  377. msg = JSON.stringify({
  378. name: qTitle,
  379. id: qId,
  380. })
  381. }
  382. if (curMsg && type === 'image') msg = curMsg
  383. if (typeof(curMsg) === 'string' && type === 'text') msg = curMsg
  384. if (msg) {
  385. Taro.api.room[apiStr]({
  386. message: msg,
  387. type,
  388. to_user: to_user_id,
  389. }).then(res => {
  390. msgSocketList.push({
  391. from: uObj.user_id,
  392. type,
  393. msg
  394. })
  395. this.setState({
  396. msgSocketList,
  397. commentVal: '',
  398. }, () => {
  399. setTimeout(() => {
  400. that.setState({
  401. viewId: `item${msgSocketList.length - 1}`
  402. })
  403. }, 100)
  404. })
  405. })
  406. } else {
  407. Taro.$msg('请输入')
  408. }
  409. }
  410. changeInput (e) {
  411. const val = strTrim(e.target.value)
  412. this.setState({
  413. commentVal: val
  414. })
  415. }
  416. uploadImgHandle () {
  417. let token = Taro.getStorageSync('APP_token')
  418. const that = this
  419. Taro.chooseImage({
  420. count: 1, // 默认9
  421. sizeType: ['original', 'compressed'],
  422. sourceType: ['album', 'camera'],
  423. success: function (res) {
  424. const tempFilePaths = res.tempFilePaths
  425. Taro.uploadFile({
  426. url: `https://api.honglouplus.com/api/upload/cloud`,
  427. filePath: tempFilePaths[0],
  428. name: 'upload',
  429. formData: {
  430. 'token': token
  431. },
  432. success (res){
  433. const msg = res.data || ''
  434. const key = CJ.enc.Utf8.parse(HLKEY)
  435. const bytes = CJ.AES.decrypt(msg, key, {
  436. mode: CJ.mode.ECB,
  437. padding: CJ.pad.Pkcs7
  438. })
  439. const originalText = bytes.toString(CJ.enc.Utf8)
  440. const cData = JSON.parse(originalText)
  441. const curImg = cData.data.url || ''
  442. that.sendHandle('image', curImg)
  443. }
  444. })
  445. }
  446. })
  447. }
  448. renderFooter () {
  449. const { commentVal } = this.state
  450. const bgImg = require('./img/img_uploads.png')
  451. return (
  452. <View className="l-floor-footer">
  453. <View className="scoped-footer">
  454. <View className="sf-input">
  455. <Input className="input" placeholder={`请输入`} value={commentVal} onInput={this.changeInput.bind(this)} />
  456. <Image src={bgImg} className="img" onClick={this.uploadImgHandle.bind(this)}></Image>
  457. <View className="btn" onClick={this.sendHandle.bind(this, 'text')}>发送</View>
  458. </View>
  459. </View>
  460. </View>
  461. )
  462. }
  463. render () {
  464. return (
  465. <View className="l-box has-footer">
  466. {this.renderList()}
  467. {this.renderFooter()}
  468. </View>
  469. )
  470. }
  471. }
  472. export default Index