|
@@ -3,16 +3,14 @@ import { onLoad } from '@dcloudio/uni-app';
|
|
|
import { ref } from 'vue';
|
|
import { ref } from 'vue';
|
|
|
import globalConfig from '@/config/global';
|
|
import globalConfig from '@/config/global';
|
|
|
import * as ww from '@wecom/jssdk';
|
|
import * as ww from '@wecom/jssdk';
|
|
|
-import { checkExternalUserInGroup, syncExternalVisitorProfile } from '@/api/wecom';
|
|
|
|
|
-import { showNotify } from 'vant';
|
|
|
|
|
-import 'vant/es/notify/style'
|
|
|
|
|
|
|
+import {
|
|
|
|
|
+ checkExternalUserInGroup,
|
|
|
|
|
+ getCachedWecomVisitorProfile,
|
|
|
|
|
+ syncExternalVisitorProfile,
|
|
|
|
|
+} from '@/api/wecom';
|
|
|
|
|
+import type { WecomVisitorProfile } from '@/api/wecom/types';
|
|
|
|
|
|
|
|
-const codeParams = ref('');
|
|
|
|
|
-const authInfoPopupVisible = ref(false);
|
|
|
|
|
-const authInfoText = ref('');
|
|
|
|
|
-const shouldShowAuthInfoPopup = ref(false);
|
|
|
|
|
-const externalContactPopupVisible = ref(false);
|
|
|
|
|
-const externalContactPopupText = ref('');
|
|
|
|
|
|
|
+const AUTH_PAGE = '/pages/index/index'
|
|
|
|
|
|
|
|
const getCodeFromLocation = () => {
|
|
const getCodeFromLocation = () => {
|
|
|
if (typeof window === 'undefined') return ''
|
|
if (typeof window === 'undefined') return ''
|
|
@@ -21,126 +19,116 @@ const getCodeFromLocation = () => {
|
|
|
return searchParams.get('code') || ''
|
|
return searchParams.get('code') || ''
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-onLoad(async (option) => {
|
|
|
|
|
- if (option && option.code) {
|
|
|
|
|
- const { code } = option;
|
|
|
|
|
- codeParams.value = code;
|
|
|
|
|
- shouldShowAuthInfoPopup.value = true;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (!codeParams.value) {
|
|
|
|
|
- const codeFromLocation = getCodeFromLocation()
|
|
|
|
|
- if (codeFromLocation) {
|
|
|
|
|
- codeParams.value = codeFromLocation
|
|
|
|
|
- shouldShowAuthInfoPopup.value = true;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (!codeParams.value) {
|
|
|
|
|
- showNotify({ type: 'warning', message: '缺少企业微信授权参数,请重新进入页面' });
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- try {
|
|
|
|
|
- await initPageData();
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- console.error('初始化企业微信信息失败:', error);
|
|
|
|
|
- }
|
|
|
|
|
-})
|
|
|
|
|
|
|
+const stripOAuthCodeFromUrl = () => {
|
|
|
|
|
+ if (typeof window === 'undefined') return
|
|
|
|
|
+ const url = new URL(window.location.href)
|
|
|
|
|
+ const hadOAuthParams = url.searchParams.has('code') || url.searchParams.has('state')
|
|
|
|
|
+ if (!hadOAuthParams) return
|
|
|
|
|
+ url.searchParams.delete('code')
|
|
|
|
|
+ url.searchParams.delete('state')
|
|
|
|
|
+ const nextSearch = url.searchParams.toString()
|
|
|
|
|
+ const next = `${url.pathname}${nextSearch ? `?${nextSearch}` : ''}${url.hash}`
|
|
|
|
|
+ window.history.replaceState({}, '', next)
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
const externalUserid = ref('');
|
|
const externalUserid = ref('');
|
|
|
const unionidValue = ref('');
|
|
const unionidValue = ref('');
|
|
|
-const isQuestionEntry = ref(false); // 是否显示问题上报入口
|
|
|
|
|
-const ownerId = ref(''); // 群主id
|
|
|
|
|
|
|
+const ownerId = ref('');
|
|
|
|
|
|
|
|
-const openAuthInfoPopup = (data: Record<string, any>) => {
|
|
|
|
|
- if (!shouldShowAuthInfoPopup.value) return;
|
|
|
|
|
- authInfoText.value = JSON.stringify(data, null, 2);
|
|
|
|
|
- authInfoPopupVisible.value = true;
|
|
|
|
|
- shouldShowAuthInfoPopup.value = false;
|
|
|
|
|
|
|
+const applyVisitorFromSyncResult = (result: Awaited<ReturnType<typeof syncExternalVisitorProfile>>) => {
|
|
|
|
|
+ externalUserid.value = result.externalUserId
|
|
|
|
|
+ unionidValue.value = result.profile?.unionid || ''
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const openExternalContactPopup = (data: Record<string, any> | null | undefined) => {
|
|
|
|
|
- if (!data) return;
|
|
|
|
|
- externalContactPopupText.value = JSON.stringify(data, null, 2);
|
|
|
|
|
- externalContactPopupVisible.value = true;
|
|
|
|
|
|
|
+const applyVisitorFromCachedProfile = (profile: WecomVisitorProfile) => {
|
|
|
|
|
+ externalUserid.value = profile.externalUserId || ''
|
|
|
|
|
+ unionidValue.value = profile.unionid || ''
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const initPageData = async () => {
|
|
|
|
|
- const { externalUserId, profile, userInfo, externalContact } = await syncExternalVisitorProfile(codeParams.value);
|
|
|
|
|
|
|
+const resolveExternalChatId = () =>
|
|
|
|
|
+ new Promise<string>((resolve, reject) => {
|
|
|
|
|
+ ww.register({
|
|
|
|
|
+ corpId: globalConfig.corpid,
|
|
|
|
|
+ agentId: globalConfig.agentId,
|
|
|
|
|
+ jsApiList: ['getCurExternalChat'],
|
|
|
|
|
+ });
|
|
|
|
|
|
|
|
- externalUserid.value = externalUserId;
|
|
|
|
|
- if (!externalUserId) {
|
|
|
|
|
- openAuthInfoPopup({
|
|
|
|
|
- callbackParams: {
|
|
|
|
|
- code: codeParams.value,
|
|
|
|
|
|
|
+ ww.getCurExternalChat({
|
|
|
|
|
+ success(res) {
|
|
|
|
|
+ if (res?.chatId) {
|
|
|
|
|
+ resolve(res.chatId);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ reject(new Error('chatId missing'));
|
|
|
|
|
+ },
|
|
|
|
|
+ fail(err) {
|
|
|
|
|
+ reject(err);
|
|
|
},
|
|
},
|
|
|
- getuserinfo: userInfo,
|
|
|
|
|
- externalcontact: externalContact,
|
|
|
|
|
- message: '当前授权结果中未返回 external_userid',
|
|
|
|
|
});
|
|
});
|
|
|
- isQuestionEntry.value = false;
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- unionidValue.value = profile?.unionid || '';
|
|
|
|
|
- openAuthInfoPopup({
|
|
|
|
|
- callbackParams: {
|
|
|
|
|
- code: codeParams.value,
|
|
|
|
|
- },
|
|
|
|
|
- getuserinfo: userInfo,
|
|
|
|
|
- externalcontact: externalContact,
|
|
|
|
|
});
|
|
});
|
|
|
- openExternalContactPopup(externalContact);
|
|
|
|
|
|
|
|
|
|
- if (!unionidValue.value) {
|
|
|
|
|
- isQuestionEntry.value = false;
|
|
|
|
|
- return;
|
|
|
|
|
|
|
+const tryPrefetchOwnerInBackground = async () => {
|
|
|
|
|
+ if (!unionidValue.value) return
|
|
|
|
|
+ try {
|
|
|
|
|
+ const chatId = await resolveExternalChatId();
|
|
|
|
|
+ const outcome = await checkExternalUserInGroup(chatId, unionidValue.value);
|
|
|
|
|
+ ownerId.value = outcome.ownerUserId;
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ ownerId.value = '';
|
|
|
}
|
|
}
|
|
|
|
|
+};
|
|
|
|
|
|
|
|
- await getGroupChatStatus();
|
|
|
|
|
|
|
+const bootstrapWithCode = async (code: string) => {
|
|
|
|
|
+ const result = await syncExternalVisitorProfile(code)
|
|
|
|
|
+ if (!result.profile) {
|
|
|
|
|
+ throw new Error('identity incomplete')
|
|
|
|
|
+ }
|
|
|
|
|
+ applyVisitorFromSyncResult(result)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const getCurrentExternalChatId = () =>
|
|
|
|
|
- new Promise<string>((resolve, reject) => {
|
|
|
|
|
- ww.register({
|
|
|
|
|
- corpId: globalConfig.corpid, // 必填,从企业微信后台获取
|
|
|
|
|
- agentId: globalConfig.agentId, // 必填,自建应用或授权应用的AgentID
|
|
|
|
|
- jsApiList: ['getCurExternalChat'], // 声明需调用的API
|
|
|
|
|
- });
|
|
|
|
|
|
|
+const cacheLooksUsable = (cached: WecomVisitorProfile | null): cached is WecomVisitorProfile =>
|
|
|
|
|
+ !!cached &&
|
|
|
|
|
+ !!(cached.name || cached.mobile || cached.internalUserid || cached.externalUserId || cached.unionid)
|
|
|
|
|
|
|
|
- ww.getCurExternalChat({
|
|
|
|
|
- success(res) {
|
|
|
|
|
- if (res?.chatId) {
|
|
|
|
|
- resolve(res.chatId);
|
|
|
|
|
- return;
|
|
|
|
|
|
|
+const goReauthorize = () => {
|
|
|
|
|
+ uni.reLaunch({ url: AUTH_PAGE })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+onLoad(async (option) => {
|
|
|
|
|
+ const code = (option && option.code) || getCodeFromLocation()
|
|
|
|
|
+
|
|
|
|
|
+ if (code) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ await bootstrapWithCode(code)
|
|
|
|
|
+ void tryPrefetchOwnerInBackground()
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ const cached = getCachedWecomVisitorProfile()
|
|
|
|
|
+ if (cacheLooksUsable(cached)) {
|
|
|
|
|
+ applyVisitorFromCachedProfile(cached)
|
|
|
|
|
+ void tryPrefetchOwnerInBackground()
|
|
|
|
|
+ } else {
|
|
|
|
|
+ goReauthorize()
|
|
|
}
|
|
}
|
|
|
- reject(new Error('chatId missing'));
|
|
|
|
|
- },
|
|
|
|
|
- fail(err) {
|
|
|
|
|
- showNotify({ type: 'warning', message: '当前会话信息获取失败,请稍后重试' });
|
|
|
|
|
- console.error('获取失败:', err.errMsg);
|
|
|
|
|
- reject(err);
|
|
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ stripOAuthCodeFromUrl()
|
|
|
}
|
|
}
|
|
|
- });
|
|
|
|
|
-});
|
|
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
-// 获取客户群的群ID
|
|
|
|
|
-const getGroupChatStatus = async () => {
|
|
|
|
|
- const chatId = await getCurrentExternalChatId();
|
|
|
|
|
- const { inGroup, ownerUserId } = await checkExternalUserInGroup(chatId, unionidValue.value);
|
|
|
|
|
- ownerId.value = ownerUserId;
|
|
|
|
|
- isQuestionEntry.value = inGroup;
|
|
|
|
|
- return {
|
|
|
|
|
- chatId,
|
|
|
|
|
- inGroup,
|
|
|
|
|
- ownerUserId,
|
|
|
|
|
- };
|
|
|
|
|
-}
|
|
|
|
|
|
|
+ const cached = getCachedWecomVisitorProfile()
|
|
|
|
|
+ if (cacheLooksUsable(cached)) {
|
|
|
|
|
+ applyVisitorFromCachedProfile(cached)
|
|
|
|
|
+ void tryPrefetchOwnerInBackground()
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ goReauthorize()
|
|
|
|
|
+})
|
|
|
|
|
|
|
|
const getQuestionPage = () => {
|
|
const getQuestionPage = () => {
|
|
|
|
|
+ const ownerQuery = ownerId.value ? `?owner=${encodeURIComponent(ownerId.value)}` : ''
|
|
|
uni.navigateTo({
|
|
uni.navigateTo({
|
|
|
- url: `/subPages/pages/reportProblems/index?owner=${ownerId.value}`
|
|
|
|
|
|
|
+ url: `/subPages/pages/reportProblems/index${ownerQuery}`
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
|
</script>
|
|
</script>
|
|
@@ -149,7 +137,7 @@ const getQuestionPage = () => {
|
|
|
<view class="box">
|
|
<view class="box">
|
|
|
<img class="box_bg_top" src="@/assets/bg_top.png" alt="" srcset="">
|
|
<img class="box_bg_top" src="@/assets/bg_top.png" alt="" srcset="">
|
|
|
<view class="container">
|
|
<view class="container">
|
|
|
- <div v-if="isQuestionEntry" class="shadow question-box" @click="getQuestionPage">
|
|
|
|
|
|
|
+ <div class="shadow question-box" @click="getQuestionPage">
|
|
|
<img class="box_img" src="@/assets/1.png" alt="" srcset="">
|
|
<img class="box_img" src="@/assets/1.png" alt="" srcset="">
|
|
|
<div class="box_title">问题上报</div>
|
|
<div class="box_title">问题上报</div>
|
|
|
</div>
|
|
</div>
|
|
@@ -161,24 +149,8 @@ const getQuestionPage = () => {
|
|
|
<img class="box_img" src="@/assets/3.png" alt="" srcset="">
|
|
<img class="box_img" src="@/assets/3.png" alt="" srcset="">
|
|
|
<div class="box_title">问答库</div>
|
|
<div class="box_title">问答库</div>
|
|
|
</navigator>
|
|
</navigator>
|
|
|
- <!-- <navigator url="/subPages/pages/inspectionResults/index" class="shadow">店铺/企业检查结果</navigator>
|
|
|
|
|
- <navigator url="/subPages/pages/my/index" class="shadow">我的管理</navigator> -->
|
|
|
|
|
</view>
|
|
</view>
|
|
|
<img class="box_bg_bottom" src="@/assets/bg_bottom.png" alt="" srcset="">
|
|
<img class="box_bg_bottom" src="@/assets/bg_bottom.png" alt="" srcset="">
|
|
|
-
|
|
|
|
|
- <van-popup v-model:show="authInfoPopupVisible" round closeable class="auth-popup">
|
|
|
|
|
- <view class="auth-popup__header">授权回跳信息</view>
|
|
|
|
|
- <scroll-view scroll-y class="auth-popup__content">
|
|
|
|
|
- <text selectable class="auth-popup__json">{{ authInfoText }}</text>
|
|
|
|
|
- </scroll-view>
|
|
|
|
|
- </van-popup>
|
|
|
|
|
-
|
|
|
|
|
- <van-popup v-model:show="externalContactPopupVisible" round closeable class="auth-popup">
|
|
|
|
|
- <view class="auth-popup__header">外部联系人接口返回</view>
|
|
|
|
|
- <scroll-view scroll-y class="auth-popup__content">
|
|
|
|
|
- <text selectable class="auth-popup__json">{{ externalContactPopupText }}</text>
|
|
|
|
|
- </scroll-view>
|
|
|
|
|
- </van-popup>
|
|
|
|
|
</view>
|
|
</view>
|
|
|
|
|
|
|
|
</template>
|
|
</template>
|
|
@@ -264,36 +236,4 @@ const getQuestionPage = () => {
|
|
|
width: 100%;
|
|
width: 100%;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
-.auth-popup {
|
|
|
|
|
- width: 88vw;
|
|
|
|
|
- max-height: 75vh;
|
|
|
|
|
- padding: 20px 16px 16px;
|
|
|
|
|
- box-sizing: border-box;
|
|
|
|
|
-
|
|
|
|
|
- &__header {
|
|
|
|
|
- margin-bottom: 12px;
|
|
|
|
|
- padding-right: 24px;
|
|
|
|
|
- font-size: 16px;
|
|
|
|
|
- font-weight: 600;
|
|
|
|
|
- color: #333;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- &__content {
|
|
|
|
|
- max-height: 60vh;
|
|
|
|
|
- background: #f7f8fa;
|
|
|
|
|
- border-radius: 8px;
|
|
|
|
|
- padding: 12px;
|
|
|
|
|
- box-sizing: border-box;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- &__json {
|
|
|
|
|
- display: block;
|
|
|
|
|
- white-space: pre-wrap;
|
|
|
|
|
- word-break: break-all;
|
|
|
|
|
- font-size: 12px;
|
|
|
|
|
- line-height: 1.6;
|
|
|
|
|
- color: #333;
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
</style>
|
|
</style>
|