system.vue 8.2 KB

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