index.vue 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. <script setup lang="ts">
  2. import { onLoad } from '@dcloudio/uni-app';
  3. import { ref } from 'vue';
  4. import globalConfig from '@/config/global';
  5. import * as ww from '@wecom/jssdk';
  6. import {
  7. checkExternalUserInGroup,
  8. getCachedWecomVisitorProfile,
  9. syncExternalVisitorProfile,
  10. } from '@/api/wecom';
  11. import type { WecomVisitorProfile } from '@/api/wecom/types';
  12. const AUTH_PAGE = '/pages/index/index'
  13. const getCodeFromLocation = () => {
  14. if (typeof window === 'undefined') return ''
  15. const searchParams = new URLSearchParams(window.location.search)
  16. return searchParams.get('code') || ''
  17. }
  18. const stripOAuthCodeFromUrl = () => {
  19. if (typeof window === 'undefined') return
  20. const url = new URL(window.location.href)
  21. const hadOAuthParams = url.searchParams.has('code') || url.searchParams.has('state')
  22. if (!hadOAuthParams) return
  23. url.searchParams.delete('code')
  24. url.searchParams.delete('state')
  25. const nextSearch = url.searchParams.toString()
  26. const next = `${url.pathname}${nextSearch ? `?${nextSearch}` : ''}${url.hash}`
  27. window.history.replaceState({}, '', next)
  28. }
  29. const externalUserid = ref('');
  30. const unionidValue = ref('');
  31. const ownerId = ref('');
  32. const internalUserGetPopupVisible = ref(false);
  33. const internalUserGetJsonText = ref('');
  34. const applyVisitorFromSyncResult = (result: Awaited<ReturnType<typeof syncExternalVisitorProfile>>) => {
  35. externalUserid.value = result.externalUserId
  36. unionidValue.value = result.profile?.unionid || ''
  37. }
  38. const applyVisitorFromCachedProfile = (profile: WecomVisitorProfile) => {
  39. externalUserid.value = profile.externalUserId || ''
  40. unionidValue.value = profile.unionid || ''
  41. }
  42. const resolveExternalChatId = () =>
  43. new Promise<string>((resolve, reject) => {
  44. ww.register({
  45. corpId: globalConfig.corpid,
  46. agentId: globalConfig.agentId,
  47. jsApiList: ['getCurExternalChat'],
  48. });
  49. ww.getCurExternalChat({
  50. success(res) {
  51. if (res?.chatId) {
  52. resolve(res.chatId);
  53. return;
  54. }
  55. reject(new Error('chatId missing'));
  56. },
  57. fail(err) {
  58. reject(err);
  59. },
  60. });
  61. });
  62. const tryPrefetchOwnerInBackground = async () => {
  63. if (!unionidValue.value) return
  64. try {
  65. const chatId = await resolveExternalChatId();
  66. const outcome = await checkExternalUserInGroup(chatId, unionidValue.value);
  67. ownerId.value = outcome.ownerUserId;
  68. } catch {
  69. ownerId.value = '';
  70. }
  71. };
  72. const bootstrapWithCode = async (code: string) => {
  73. const result = await syncExternalVisitorProfile(code)
  74. if (!result.profile) {
  75. throw new Error('identity incomplete')
  76. }
  77. applyVisitorFromSyncResult(result)
  78. if (result.internalUserRaw) {
  79. internalUserGetJsonText.value = JSON.stringify(result.internalUserRaw, null, 2)
  80. internalUserGetPopupVisible.value = true
  81. }
  82. }
  83. const cacheLooksUsable = (cached: WecomVisitorProfile | null): cached is WecomVisitorProfile =>
  84. !!cached &&
  85. !!(cached.name || cached.mobile || cached.internalUserid || cached.externalUserId || cached.unionid)
  86. const goReauthorize = () => {
  87. uni.reLaunch({ url: AUTH_PAGE })
  88. }
  89. onLoad(async (option) => {
  90. const code = (option && option.code) || getCodeFromLocation()
  91. if (code) {
  92. try {
  93. await bootstrapWithCode(code)
  94. void tryPrefetchOwnerInBackground()
  95. } catch {
  96. const cached = getCachedWecomVisitorProfile()
  97. if (cacheLooksUsable(cached)) {
  98. applyVisitorFromCachedProfile(cached)
  99. void tryPrefetchOwnerInBackground()
  100. } else {
  101. goReauthorize()
  102. }
  103. } finally {
  104. stripOAuthCodeFromUrl()
  105. }
  106. return
  107. }
  108. const cached = getCachedWecomVisitorProfile()
  109. if (cacheLooksUsable(cached)) {
  110. applyVisitorFromCachedProfile(cached)
  111. void tryPrefetchOwnerInBackground()
  112. return
  113. }
  114. goReauthorize()
  115. })
  116. const getQuestionPage = () => {
  117. const ownerQuery = ownerId.value ? `?owner=${encodeURIComponent(ownerId.value)}` : ''
  118. uni.navigateTo({
  119. url: `/subPages/pages/reportProblems/index${ownerQuery}`
  120. })
  121. }
  122. </script>
  123. <template>
  124. <view class="box">
  125. <img class="box_bg_top" src="@/assets/bg_top.png" alt="" srcset="">
  126. <view class="container">
  127. <div class="shadow question-box" @click="getQuestionPage">
  128. <img class="box_img" src="@/assets/1.png" alt="" srcset="">
  129. <div class="box_title">问题上报</div>
  130. </div>
  131. <navigator url="/subPages/pages/reportServer/index" class="shadow">
  132. <img class="box_img" src="@/assets/2.png" alt="" srcset="">
  133. <div class="box_title">服务事项</div>
  134. </navigator>
  135. <navigator url="/subPages/pages/questions/index" class="shadow">
  136. <img class="box_img" src="@/assets/3.png" alt="" srcset="">
  137. <div class="box_title">问答库</div>
  138. </navigator>
  139. </view>
  140. <img class="box_bg_bottom" src="@/assets/bg_bottom.png" alt="" srcset="">
  141. <van-popup v-model:show="internalUserGetPopupVisible" round closeable class="user-get-popup">
  142. <view class="user-get-popup__header">getuserdetail 返回</view>
  143. <scroll-view scroll-y class="user-get-popup__content">
  144. <text selectable class="user-get-popup__json">{{ internalUserGetJsonText }}</text>
  145. </scroll-view>
  146. </van-popup>
  147. </view>
  148. </template>
  149. <style lang="scss" scoped>
  150. .box{
  151. position: relative;
  152. width: 100%;
  153. height: 100vh;
  154. background: #f6f6f6;
  155. &_bg_top{
  156. width: 100%;
  157. position: absolute;
  158. top: 0;
  159. }
  160. &_bg_bottom{
  161. position: absolute;
  162. bottom: 0;
  163. width: 100%;
  164. }
  165. &_title{
  166. font-size: 16px;
  167. font-weight: bold;
  168. color: #333;
  169. text-align: center;
  170. }
  171. &_img{
  172. width: 88px;
  173. height: 88px;
  174. }
  175. }
  176. .container {
  177. padding: 5%;
  178. display: grid;
  179. grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  180. gap: 20px;
  181. position: absolute;
  182. top: 130px;
  183. z-index: 1;
  184. }
  185. .container uni-navigator {
  186. border-radius: 8px;
  187. padding: 5%;
  188. height: 0;
  189. padding-bottom: 100%;
  190. position: relative;
  191. display: flex;
  192. justify-content: center;
  193. box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
  194. background: #fff;
  195. }
  196. .container uni-navigator::before {
  197. content: "";
  198. display: block;
  199. padding-top: 90%;
  200. }
  201. .container uni-navigator-content {
  202. position: absolute;
  203. top: 0;
  204. left: 0;
  205. right: 0;
  206. bottom: 0;
  207. display: flex;
  208. align-items: center;
  209. justify-content: center;
  210. }
  211. .question-box {
  212. border-radius: 8px;
  213. padding: 5%;
  214. height: 0;
  215. padding-bottom: 100%;
  216. position: relative;
  217. display: flex;
  218. justify-content: center;
  219. box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
  220. background: #fff;
  221. flex-wrap: wrap;
  222. .box_title {
  223. width: 100%;
  224. }
  225. }
  226. .user-get-popup {
  227. width: 88vw;
  228. max-height: 75vh;
  229. padding: 20px 16px 16px;
  230. box-sizing: border-box;
  231. &__header {
  232. margin-bottom: 12px;
  233. padding-right: 24px;
  234. font-size: 16px;
  235. font-weight: 600;
  236. color: #333;
  237. }
  238. &__content {
  239. max-height: 60vh;
  240. background: #f7f8fa;
  241. border-radius: 8px;
  242. padding: 12px;
  243. box-sizing: border-box;
  244. }
  245. &__json {
  246. display: block;
  247. white-space: pre-wrap;
  248. word-break: break-all;
  249. font-size: 12px;
  250. line-height: 1.6;
  251. color: #333;
  252. }
  253. }
  254. </style>