index.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. <template>
  2. <Theme>
  3. <view class="wrap">
  4. <Navbar title="网购助手" fixed border leftShow id="navbar" />
  5. <ConsultCard
  6. class="topcard"
  7. id="top_card"
  8. :style="{ '--navbarHeight': navbarHeight + 'px' }"
  9. ></ConsultCard>
  10. <view
  11. class="content"
  12. :style="{
  13. '--topHeight': topHeight + 'px',
  14. '--height': bottomHeight + 'px',
  15. '--keyboardHeight': keyboardHeight + 'px',
  16. '--contHeight': contHeight + 'px',
  17. '--navbarHeight': navbarHeight + 'px',
  18. '--tabbarHeight': tabbarHeight + 'px',
  19. }"
  20. >
  21. <scroll-view
  22. class="chat_content"
  23. scroll-anchoring
  24. :scroll-y="true"
  25. :scroll-top="scrollTop"
  26. :lower-threshold="lowerThreshold"
  27. :refresher-triggered="refresherTriggered"
  28. :refresher-enabled="refresherEnabled"
  29. @refresherrefresh="refresherrefresh"
  30. refresher-default-style="none"
  31. refresher-background="var(--bg)"
  32. @scroll="onScroll"
  33. @scrolltolower="onScrollLower"
  34. >
  35. <view class="refresher" v-if="refresherTriggered">
  36. <up-loadmore :status="'loading'" :loadingText="$t('正在加载')" />
  37. </view>
  38. <view class="scroll" id="scroll_items">
  39. <Message :list="msgList" />
  40. </view>
  41. </scroll-view>
  42. </view>
  43. <view
  44. class="new_count"
  45. :style="{ bottom: bottomHeight + tabbarHeight + 5 + 'px' }"
  46. v-show="newMessageCount > 0"
  47. @click="getContHeight"
  48. >
  49. <text>{{ newMessageCount }} {{ $t("条新消息") }}</text>
  50. </view>
  51. <footer-input
  52. v-model="message"
  53. :bottomHeight="tabbarHeight"
  54. @heights="getFooterHeight"
  55. @submit="send"
  56. @linechange="inputHeightChange"
  57. @imageUp="imageUp"
  58. ref="footerInputRef"
  59. />
  60. <Tabbar page="chat" @getTabbarHeight="getTabbarHeight" />
  61. </view>
  62. </Theme>
  63. </template>
  64. <script setup>
  65. import { onUnload, onLoad, onShow } from "@dcloudio/uni-app";
  66. import { ref, nextTick, onMounted, reactive, watch, provide } from "vue";
  67. import Navbar from "@/components/navbar";
  68. import footerInput from "@/components/footer_input";
  69. import Message from "@/components/message";
  70. import Tabbar from "@/components/tabbar";
  71. import ConsultCard from "@/components/message/components/consultCard.vue";
  72. import { query, systemInfo, useGlobal } from "@/utils";
  73. import { RONGIMCALL_HISTORY } from "@/api";
  74. import { useSocketStore, useMessageStore } from "@/store";
  75. import { storeToRefs } from "pinia";
  76. const useSocket = useSocketStore();
  77. const useMessage = useMessageStore();
  78. const { messageMap } = storeToRefs(useMessage);
  79. const bottomHeight = ref(0);
  80. const scrollTop = ref(0);
  81. const keyboardHeight = ref(0);
  82. const contHeight = ref(0);
  83. const newHeight = ref(0);
  84. const message = ref("");
  85. const footerInputRef = ref(null);
  86. const refresherTriggered = ref(false);
  87. const refresherEnabled = ref(true);
  88. const msgList = ref([]);
  89. const page = ref(1);
  90. const pagesize = ref(10);
  91. const total = ref(0);
  92. const channel = ref("");
  93. const firstMsg = ref({});
  94. const lowerThreshold = ref(0);
  95. const distanceFromBottom = ref(0);
  96. const newMessageCount = ref(0);
  97. const bottomSafeDistanceHeight = ref(0);
  98. const tabbarHeight = ref(0);
  99. const topHeight = ref(0);
  100. const navbarHeight = ref(0);
  101. watch(
  102. () => messageMap.value,
  103. (newVal, oldVal) => {
  104. const info = newVal[channel.value];
  105. if (channel.value && info) {
  106. msgList.value = info.messageList;
  107. newMessageCount.value =
  108. distanceFromBottom.value > lowerThreshold.value ? info.unreadCount : 0;
  109. }
  110. },
  111. { deep: true, immediate: true }
  112. );
  113. provide("onQuestionSend", send);
  114. function onScroll(e) {
  115. const { detail } = e;
  116. const distanceForBottom =
  117. detail.scrollHeight - detail.scrollTop - contHeight.value;
  118. distanceFromBottom.value = distanceForBottom > 0 ? distanceForBottom : 0;
  119. if (distanceFromBottom.value <= lowerThreshold.value) {
  120. useMessage.updateUnreadCount(channel.value, -1);
  121. }
  122. }
  123. function onScrollLower() {
  124. useMessage.updateUnreadCount(channel.value, -1);
  125. }
  126. const refresherrefresh = () => {
  127. refresherTriggered.value = true;
  128. if (total.value <= page.value * pagesize.value) {
  129. nextTick(() => {
  130. refresherTriggered.value = false;
  131. });
  132. return;
  133. }
  134. page.value += 1;
  135. nextTick(() => {
  136. getHistory(true);
  137. });
  138. };
  139. const getCont = () => {
  140. nextTick(() => {
  141. const { windowHeight, statusBarHeight } = systemInfo();
  142. contHeight.value = windowHeight - statusBarHeight - navbarHeight.value;
  143. });
  144. };
  145. const getContHeight = (isLoading) => {
  146. try {
  147. nextTick(async () => {
  148. const res = await query("#scroll_items");
  149. let info = uni.getSystemInfoSync();
  150. if (isLoading) {
  151. scrollTop.value = res.height - newHeight.value - navbarHeight.value;
  152. } else {
  153. if (info.platform === "ios") {
  154. scrollTop.value =
  155. res.height +
  156. bottomHeight.value -
  157. keyboardHeight.value +
  158. tabbarHeight.value;
  159. } else {
  160. scrollTop.value =
  161. res.height -
  162. bottomHeight.value -
  163. keyboardHeight.value -
  164. tabbarHeight.value;
  165. }
  166. }
  167. newHeight.value = res.height;
  168. });
  169. } catch (error) {}
  170. };
  171. const getFooterHeight = (res) => {
  172. nextTick(() => {
  173. bottomHeight.value = res.height || 0;
  174. keyboardHeight.value = res.keyboardHeight || 0;
  175. bottomSafeDistanceHeight.value = res.bottomSafeDistance || 0;
  176. getContHeight();
  177. });
  178. };
  179. function send(text) {
  180. sendMessage({ type: "text", text: text || message.value });
  181. }
  182. const imageUp = (url) => {
  183. sendMessage({ type: "image", url: url });
  184. };
  185. const sendMessage = (msgObject) => {
  186. let messageData = reactive({
  187. type: "service",
  188. msg: JSON.stringify(msgObject),
  189. send: 1,
  190. loading: true,
  191. status: -1,
  192. });
  193. useMessage.updateMessageList(channel.value, messageData, "push");
  194. getContHeight();
  195. useMessage
  196. .sendMessage(msgObject, "service")
  197. .then((data) => {
  198. messageData.loading = false;
  199. messageData.status = 1;
  200. for (let key in data) {
  201. if (key != "msg") {
  202. messageData[key] = data[key];
  203. }
  204. }
  205. useMessage.updateGlobalMapLasttime(`serviceChannel`, data.indate);
  206. })
  207. .catch(() => {
  208. messageData.loading = false;
  209. messageData.status = 0;
  210. });
  211. nextTick(() => {
  212. footerInputRef.value && footerInputRef.value.clear();
  213. });
  214. };
  215. const getTabbarHeight = (height) => {
  216. tabbarHeight.value = height;
  217. };
  218. const inputHeightChange = () => {
  219. // console.log("输入框高度发送变化");
  220. };
  221. const open = () => {
  222. useMessage
  223. .openChannel({
  224. type: "service",
  225. socket_id: useSocket.socketId,
  226. })
  227. .then(async (res) => {
  228. firstMsg.value = res.data;
  229. channel.value = res.data.auth.channel;
  230. await getHistory();
  231. await clearCount();
  232. });
  233. };
  234. const getHistory = async (isLoading = false) => {
  235. try {
  236. let para = {
  237. page: page.value,
  238. pagesize: pagesize.value,
  239. type: "service",
  240. };
  241. const res = await RONGIMCALL_HISTORY(para);
  242. const lists = res.data.lists;
  243. for (let i = 0; i < lists.length; i++) {
  244. const msgInfo = {
  245. ...lists[i],
  246. msg: JSON.parse(lists[i].msg),
  247. };
  248. useMessage.updateMessageList(channel.value, msgInfo, "unshift");
  249. }
  250. if (lists.length > 0 && page.value == 1) {
  251. let result = lists[0];
  252. let lastTime = result
  253. ? result.indate
  254. : (Date.now() / 1000).toFixed(0) - 0;
  255. useMessage.updateGlobalMapLasttime(`serviceChannel`, lastTime);
  256. }
  257. total.value = res.data.total;
  258. !total.value &&
  259. useMessage.updateMessageList(channel.value, firstMsg.value, "unshift");
  260. if (total.value <= page.value * pagesize.value) {
  261. nextTick(() => {
  262. refresherEnabled.value = false;
  263. });
  264. }
  265. nextTick(() => {
  266. getContHeight(isLoading);
  267. refresherTriggered.value = false;
  268. });
  269. } catch (error) {}
  270. };
  271. const clearCount = () => {
  272. useMessage.updateUnreadCount(channel.value, -1);
  273. useMessage.updateGlobalMapUnreadCount("serviceChannel", -1);
  274. };
  275. const getHeight = async () => {
  276. try {
  277. const res = await query("#top_card");
  278. topHeight.value = res.height;
  279. const resNav = await query("#navbar");
  280. navbarHeight.value = resNav.height;
  281. } catch (error) {}
  282. };
  283. const openAndGetHistory = async () => {
  284. try {
  285. await getHeight();
  286. await open();
  287. await getCont();
  288. await getContHeight();
  289. } catch (error) {
  290. console.error(error);
  291. }
  292. };
  293. onMounted(() => {
  294. nextTick(() => {
  295. openAndGetHistory();
  296. });
  297. });
  298. uni.hideTabBar();
  299. onUnload(() => {
  300. useSocket.send({
  301. event: "pusher:unsubscribe",
  302. data: { channel: channel.value },
  303. });
  304. clearCount();
  305. });
  306. </script>
  307. <style lang="less" scoped>
  308. @import url("@/style.less");
  309. .wrap {
  310. background: var(--bg);
  311. height: 100vh;
  312. padding-bottom: constant(safe-area-inset-bottom);
  313. padding-bottom: env(safe-area-inset-bottom);
  314. .topcard {
  315. position: sticky;
  316. top: var(--navbarHeight);
  317. padding: 0 30rpx;
  318. background: var(--bg);
  319. z-index: 10;
  320. }
  321. .content {
  322. width: 100%;
  323. // padding: 0 30rpx;
  324. .chat_content {
  325. height: calc(
  326. var(--contHeight) - var(--keyboardHeight) - var(--height) -
  327. var(--tabbarHeight)
  328. );
  329. /* #ifdef APP-PLUS */
  330. padding-bottom: constant(safe-area-inset-bottom);
  331. padding-bottom: env(safe-area-inset-bottom);
  332. /* #endif */
  333. /* #ifdef H5 */
  334. padding-bottom: calc(
  335. var(--tabbarHeight) + var(--height) + constant(safe-area-inset-bottom)
  336. );
  337. padding-bottom: calc(
  338. var(--tabbarHeight) + var(--height) + env(safe-area-inset-bottom)
  339. );
  340. // padding-bottom: calc(var(--tabbarHeight) + var(--height));
  341. /* #endif */
  342. .scroll {
  343. overflow: hidden;
  344. }
  345. }
  346. }
  347. .new_count {
  348. position: fixed;
  349. bottom: 0;
  350. right: 16rpx;
  351. z-index: 20;
  352. height: 52rpx;
  353. padding: 0 20rpx;
  354. border-radius: 26rpx;
  355. background-color: rgba(0, 0, 0, 1);
  356. color: #fff;
  357. font-size: 24rpx;
  358. line-height: 52rpx;
  359. }
  360. }
  361. </style>