Commit baf785a2 by 王立鹏

Merge branch 'feature/22261-需求申请:YAYA文化岛链接支持富文本(可通过学习日历点击跳转)-&-新增转发按钮' into 'master'

Feature/22261 需求申请:yaya文化岛链接支持富文本(可通过学习日历点击跳转) & 新增转发按钮

See merge request !32
parents 39f07557 82101895
...@@ -20,11 +20,12 @@ import Progress from '@/components/common/Progress/index.vue' ...@@ -20,11 +20,12 @@ import Progress from '@/components/common/Progress/index.vue'
const locale = ref(zhCn) const locale = ref(zhCn)
onMounted(() => { onMounted(() => {
console.log(import.meta.env.MODE)
// console.table(__CORE_LIB_VERSION__) // console.table(__CORE_LIB_VERSION__)
if (import.meta.env.MODE === 'production') { if (import.meta.env.PROD) {
setTimeout(() => { initWxConfig().catch((error) => {
initWxConfig() console.warn('企业微信 SDK 初始化失败', error)
}, 3000) })
} }
}) })
</script> </script>
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1780627614555" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5302" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M868.2 712.3c-53.8 0-101.3 27.4-129.3 69l-440-207.7c8.2-18.9 12.7-39.7 12.7-61.6 0-22-4.6-42.9-12.8-61.9L615 255.6c28.6 34.2 71.6 56.1 119.6 56.1 85.9 0 155.8-69.9 155.8-155.8S820.5 0 734.6 0 578.8 69.9 578.8 155.8c0 22 4.6 42.9 12.8 61.9L275.4 412.3c-28.6-34.2-71.6-56.1-119.6-56.1C69.9 356.2 0 426.1 0 512s69.9 155.8 155.8 155.8c48 0 91-21.8 119.6-56.1l444.1 209.7c-4.6 14.8-7.2 30.4-7.2 46.7 0 85.9 69.9 155.8 155.8 155.8S1024 954.1 1024 868.2s-69.9-155.9-155.8-155.9zM734.6 44.5c61.4 0 111.3 49.9 111.3 111.3S796 267.1 734.6 267.1s-111.3-49.9-111.3-111.3S673.2 44.5 734.6 44.5zM155.8 623.3c-61.4 0-111.3-49.9-111.3-111.3s49.9-111.3 111.3-111.3S267.1 450.6 267.1 512s-49.9 111.3-111.3 111.3z m712.4 356.2c-61.4 0-111.3-49.9-111.3-111.3s49.9-111.3 111.3-111.3 111.3 49.9 111.3 111.3-50 111.3-111.3 111.3z" p-id="5303" fill="currentColor"></path></svg>
...@@ -194,7 +194,7 @@ const formattedDuration = computed(() => formatTime(videoDuration.value)) ...@@ -194,7 +194,7 @@ const formattedDuration = computed(() => formatTime(videoDuration.value))
let progressTimer: ReturnType<typeof setInterval> | null = null let progressTimer: ReturnType<typeof setInterval> | null = null
const SKIP_TIME = import.meta.env.MODE === 'production' ? 60 : 0 const SKIP_TIME = import.meta.env.PROD ? 60 : 0
const startTimers = () => { const startTimers = () => {
progressTimer = setInterval(() => { progressTimer = setInterval(() => {
if (videoRef.value) { if (videoRef.value) {
......
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
</h3> </h3>
<p class="mt-2 text-center text-13px text-gray-500 leading-5"> <p class="mt-2 text-center text-13px text-gray-500 leading-5">
{{ messgae }} {{ message }}
</p> </p>
</div> </div>
</div> </div>
...@@ -51,17 +51,18 @@ ...@@ -51,17 +51,18 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
const { title = '登录异常' } = defineProps<{ const { title = "登录异常" } = defineProps<{
title?: string title?: string;
}>() }>();
const messgae = const message =
import.meta.env.MODE === '检测到未登录或者登录错误,' + 'production' "检测到未登录或者登录错误," +
? '请关闭标签页,从企微工作台重新打开应用' (import.meta.env.PROD
: '请关闭网页或重新打开网页携带code' ? "请关闭标签页,从企微工作台或分享/消息链接重新打开应用"
: "请关闭网页或重新打开网页携带code");
const visible = defineModel<boolean>({ default: false }) const visible = defineModel<boolean>({ default: false });
onMounted(() => { onMounted(() => {
visible.value = true visible.value = true;
}) });
</script> </script>
...@@ -18,7 +18,7 @@ import 'notivue/animations.css' ...@@ -18,7 +18,7 @@ import 'notivue/animations.css'
import { vParseComment } from '@/directives' import { vParseComment } from '@/directives'
if (import.meta.env.MODE === 'production') { if (import.meta.env.PROD) {
import('@/utils/version').then(({ loopGetVersion }) => loopGetVersion()) import('@/utils/version').then(({ loopGetVersion }) => loopGetVersion())
} }
......
...@@ -28,6 +28,12 @@ export function handleBackendError<T>(backendServiceResult: BackendServiceResult ...@@ -28,6 +28,12 @@ export function handleBackendError<T>(backendServiceResult: BackendServiceResult
msg: backendServiceResult.message, msg: backendServiceResult.message,
} }
showErrorMsg(error) showErrorMsg(error)
// 处理code过期的bug 继续换取新的 code
if(backendServiceResult.message.includes('企业微信ID为空')){
const cleanUrl = window.location.origin + window.location.pathname
location.href = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=ww42a2d34b42b8d721&redirect_uri=${cleanUrl}&response_type=code&scope=snsapi_base#wechat_redirect`
}
return Promise.reject(backendServiceResult) return Promise.reject(backendServiceResult)
} }
......
export * from './wxLogin' export * from './wxLogin'
export * from './initWXConfig' export * from './initWXConfig'
import * as ww from '@wecom/jssdk' import * as ww from '@wecom/jssdk'
import { initWxConfig } from './initWXConfig'
const DEFAULT_SHARE_TITLE = '企业文化'
const DEFAULT_SHARE_DESC = '点击查看详情'
const DEFAULT_SHARE_IMAGE =
'https://res.mail.qq.com/node/ww/wwmng/style/images/index_share_logo$13c64306.png'
const getCurrentShareUrl = () =>
`https://open.weixin.qq.com/connect/oauth2/authorize?appid=ww42a2d34b42b8d721&redirect_uri=${location.href}&response_type=code&scope=snsapi_base#wechat_redirect`
/**
* 分享
*/
interface IShareWxOption { interface IShareWxOption {
/** 分享标题 */
title: string title: string
/** 分享链接 */ link?: string
link: string
/** 分享描述 */
desc?: string desc?: string
/** 分享封面 */
imgUrl?: string imgUrl?: string
} }
// export function wxShare(option: IShareWxOption) {
// const url = location.href.split('#')[0] interface IShareWxResult {
// option.link = url + '#' + option.link err_msg?: string
// wx.invoke('shareAppMessage', option, function (res: any) { errMsg?: string
// if (res.err_msg == 'openExistedChatWithMsg:ok') { }
// }
// }) export async function wxShare(option: IShareWxOption): Promise<IShareWxResult> {
// } await initWxConfig()
export async function wxShare(option: IShareWxOption) { return new Promise((resolve, reject) => {
const url = location.href.split('#')[0] ww.invoke(
await ww.shareAppMessage({ 'shareAppMessage',
title: '测试一下', {
desc: '让每个企业都有自己的微信', title: option.title || DEFAULT_SHARE_TITLE,
link: url + '#' + option.link, desc: option.desc || DEFAULT_SHARE_DESC,
imgUrl: 'https://res.mail.qq.com/node/ww/wwmng/style/images/index_share_logo$13c64306.png', link: option.link || getCurrentShareUrl(),
imgUrl: option.imgUrl || DEFAULT_SHARE_IMAGE,
},
function (res: IShareWxResult) {
const errMsg = res.err_msg || res.errMsg
if (errMsg === 'shareAppMessage:ok' || errMsg === 'openExistedChatWithMsg:ok') {
resolve(res)
} else {
reject(res)
}
},
)
}) })
} }
/** export function isWxShareCancel(error: unknown) {
* 企业微信获取部门人员SDK const err = error as { errMsg?: string; err_msg?: string; message?: string }
* @params wxOption 企业微信 selectEnterpriseContact 的选项 return [err.errMsg, err.err_msg, err.message].some((msg) =>
* @params wxOption.fromDepartmentId 必填,表示打开的通讯录从指定的部门开始展示,-1表示自己所在部门开始, 0表示从最上层开始 msg?.includes('shareAppMessage:cancel'),
* @params wxOption.mode 必填,选择模式,single表示单选,multi表示多选 )
* @params wxOption.type 必填,选择限制类型,指定department、user中的一个或者多个 }
* @params wxOption.selectedDepartmentIds 非必填,已选部门ID列表。用于多次选人时可重入,single模式下请勿填入多个id
* @params wxOption.selectedUserIds 非必填,已选用户ID列表。用于多次选人时可重入,single模式下请勿填入多个id
* @returns
*/
interface ISelectDepOrUser { interface ISelectDepOrUser {
err_msg: string err_msg: string
result: IResult result: IResult
} }
interface IResult { interface IResult {
userList: ISelectUser[] userList: ISelectUser[]
departmentList: ISelectDept[] departmentList: ISelectDept[]
} }
export interface ISelectUser { export interface ISelectUser {
id: string id: string
name: string name: string
avatar: string avatar: string
} }
export interface ISelectDept { export interface ISelectDept {
id: string id: string
name: string name: string
} }
export function selectDepOrUser(wxOption = {}): Promise<IResult> {
export async function selectDepOrUser(wxOption = {}): Promise<IResult> {
await initWxConfig()
const defaultOption = { const defaultOption = {
fromDepartmentId: -1, fromDepartmentId: -1,
mode: 'single', // single multi mode: 'single',
type: ['user', 'department'], // 'user' 'department' type: ['user', 'department'],
// "selectedDepartmentIds": ['2', '3'],
// "selectedUserIds": ['lisi', 'lisi2']
} }
const option = { const option = {
...defaultOption, ...defaultOption,
......
/* 企业微信js配置 */
//注意:如果要在页面调用企业微信内部方法,请现在下面的jsApiList数组中添加方法
import { getWxSignature } from '@/api' import { getWxSignature } from '@/api'
import * as ww from '@wecom/jssdk' import * as ww from '@wecom/jssdk'
// export async function initWxConfig() { let wxConfigPromise: Promise<void> | null = null
// const url = location.href.split('#')[0]
// const response = await getWxSignature(url) export function initWxConfig() {
// const timestamp = response.data.timestamp //时间戳 if (wxConfigPromise) return wxConfigPromise
// const nonceStr = response.data.nonceStr //随机字符串
// const signature = response.data.signature //签名 wxConfigPromise = new Promise((resolve, reject) => {
// const appId = response.data.appId //企业id ;(async () => {
// wx.config({ try {
// beta: true, // 必须这么写,否则wx.invoke调用形式的jsapi会有问题
// debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
// appId: appId, // 必填,企业微信的corpID
// timestamp: timestamp, // 必填,生成签名的时间戳
// nonceStr: nonceStr, // 必填,生成签名的随机串
// signature: signature, // 必填,签名,见附录1
// jsApiList: [
// // 所有要调用的 API 都要加到这个列表中
// 'shareAppMessage',
// 'selectEnterpriseContact',
// ],
// // success: function (result) {
// // // 回调
// // }
// })
// }
export async function initWxConfig() {
const url = location.href.split('#')[0] const url = location.href.split('#')[0]
const response = await getWxSignature(url!) const { data } = await getWxSignature(url!)
const timestamp = response.data.timestamp //时间戳 let resolved = false
const nonceStr = response.data.nonceStr //随机字符串 const timer = window.setTimeout(() => {
const signature = response.data.signature //签名 if (!resolved) {
// const appId = response.data.appId //企业id resolved = true
const corpId = response.data.corpid //企业id console.warn('企业微信 SDK 初始化未返回成功回调,继续执行后续操作')
const agentId = response.data.agentid //应用id resolve()
ww.register({ }
corpId, // 必填,当前用户企业所属企业ID }, 3000)
agentId, // 必填,当前应用的AgentID
jsApiList: ['shareAppMessage', 'selectEnterpriseContact'], // 必填,需要使用的JSAPI列表 const handleFail = (error: unknown) => {
getConfigSignature: function () { window.clearTimeout(timer)
return { wxConfigPromise = null
timestamp: timestamp, reject(error)
nonceStr: nonceStr, }
signature: signature,
const handleConfigSuccess = () => {
if (!resolved) {
resolved = true
window.clearTimeout(timer)
resolve()
} }
}, // 必填,根据url生成企业签名的回调函数
getAgentConfigSignature: function () {
return {
timestamp: timestamp,
nonceStr: nonceStr,
signature: signature,
} }
}, // 必填,根据url生成应用签名的回调函数
const signature = {
timestamp: data.timestamp,
nonceStr: data.nonceStr,
signature: data.signature,
}
ww.register({
corpId: data.corpid,
agentId: data.agentid,
jsApiList: ['shareAppMessage', 'selectEnterpriseContact'],
getConfigSignature() {
return signature
},
getAgentConfigSignature() {
return signature
},
onConfigSuccess: handleConfigSuccess,
onAgentConfigSuccess: handleConfigSuccess,
onConfigFail: handleFail,
onAgentConfigFail(error) {
console.warn(error)
},
}) })
} catch (error) {
console.error(error)
wxConfigPromise = null
reject(error)
}
})()
})
return wxConfigPromise
} }
...@@ -36,6 +36,8 @@ import type { Component } from 'vue' ...@@ -36,6 +36,8 @@ import type { Component } from 'vue'
import { useScrollTop } from '@/hooks' import { useScrollTop } from '@/hooks'
import { addOrCanceArticlelCollect, addOrCanceArticlelLike } from '@/api' import { addOrCanceArticlelCollect, addOrCanceArticlelLike } from '@/api'
import { push } from 'notivue' import { push } from 'notivue'
import { isWxShareCancel, wxShare } from '@/utils/wxUtil'
const modelValue = defineModel<ArticleItemDto>('modelValue', { required: true }) const modelValue = defineModel<ArticleItemDto>('modelValue', { required: true })
const emit = defineEmits<{ const emit = defineEmits<{
...@@ -46,7 +48,7 @@ const { ScrollTopComp } = useScrollTop(window) ...@@ -46,7 +48,7 @@ const { ScrollTopComp } = useScrollTop(window)
interface StatItem { interface StatItem {
icon: Component icon: Component
count: number count: number | string
label: string label: string
active?: boolean active?: boolean
actionFn?: () => Promise<void> actionFn?: () => Promise<void>
...@@ -103,6 +105,29 @@ const stats = computed(() => { ...@@ -103,6 +105,29 @@ const stats = computed(() => {
emit('scrollToCommentBox') emit('scrollToCommentBox')
}, },
}, },
{
icon: <SvgIcon name="share" size="20"></SvgIcon>,
count: '分享',
label: '分享',
async actionFn() {
try {
await wxShare({
title: modelValue.value.title,
desc: [modelValue.value.createUserName, modelValue.value.region].filter(Boolean).join('——'),
imgUrl: modelValue.value.faceUrl || modelValue.value.imgUrl?.split(',').find(Boolean),
})
push.success('分享成功')
} catch (error) {
if (isWxShareCancel(error)) {
console.warn(error)
push.warning('取消分享')
return
}
console.error(error)
push.error('分享失败,请刷新页面重新分享')
}
},
},
] ]
}) })
......
...@@ -195,33 +195,43 @@ ...@@ -195,33 +195,43 @@
</div> </div>
<!-- 右侧数据 --> <!-- 右侧数据 -->
<div class="flex items-center gap-6 text-slate-500 text-sm select-none"> <div class="flex items-center gap-1 text-slate-500 text-base select-none">
<span <el-button
text
class="!ml-0 flex items-center gap-1 transition-colors hover:text-blue-500"
:class="{ '!text-blue-500': questionDetail?.hasPraised }"
@click="handleLikeArticle" @click="handleLikeArticle"
class="hover:text-slate-600 cursor-pointer transition-colors flex items-center gap-1"
> >
<el-icon> <el-icon size="17">
<svg-icon <svg-icon
:name="questionDetail?.hasPraised ? 'praise_fill' : 'praise'" :name="questionDetail?.hasPraised ? 'praise_fill' : 'praise'"
></svg-icon> ></svg-icon>
</el-icon> </el-icon>
<span :class="{ 'text-blue-500': questionDetail?.hasPraised }">{{ <span class="text-15px">{{ questionDetail?.praiseCount || 0 }}</span>
questionDetail?.praiseCount || 0 </el-button>
}}</span> <el-button
</span> text
<span class="!ml-0 flex items-center gap-1 transition-colors hover:text-blue-500"
:class="{ '!text-blue-500': questionDetail?.hasCollect }"
@click="handleCollectArticle" @click="handleCollectArticle"
class="hover:text-slate-600 cursor-pointer transition-colors flex items-center gap-1"
> >
<el-icon> <el-icon size="17">
<svg-icon <svg-icon
:name="questionDetail?.hasCollect ? 'collection_fill' : 'collection'" :name="questionDetail?.hasCollect ? 'collection_fill' : 'collection'"
></svg-icon> ></svg-icon>
</el-icon> </el-icon>
<span :class="{ 'text-blue-500': questionDetail?.hasCollect }">{{ <span class="text-15px">{{ questionDetail?.collectionCount || 0 }}</span>
questionDetail?.collectionCount || 0 </el-button>
}}</span> <el-button
</span> text
class="!ml-0 flex items-center gap-2 transition-colors hover:text-blue-500"
@click="handleShareQuestion"
>
<el-icon size="17">
<svg-icon name="share" size="15"></svg-icon>
</el-icon>
<span class="text-15px">分享</span>
</el-button>
</div> </div>
</div> </div>
</template> </template>
...@@ -460,6 +470,7 @@ import { storeToRefs } from 'pinia' ...@@ -460,6 +470,7 @@ import { storeToRefs } from 'pinia'
import { useNavigation, useScrollTop } from '@/hooks' import { useNavigation, useScrollTop } from '@/hooks'
import { useMessageBox } from '@/hooks' import { useMessageBox } from '@/hooks'
import { parseEmoji } from '@/utils/emoji' import { parseEmoji } from '@/utils/emoji'
import { isWxShareCancel, wxShare } from '@/utils/wxUtil'
import { ArticleTypeEnum, BooleanFlag } from '@/constants' import { ArticleTypeEnum, BooleanFlag } from '@/constants'
import { push } from 'notivue' import { push } from 'notivue'
import { CommentSortTypeEnum } from '@/constants' import { CommentSortTypeEnum } from '@/constants'
...@@ -565,6 +576,27 @@ const handleCollectArticle = async () => { ...@@ -565,6 +576,27 @@ const handleCollectArticle = async () => {
push.success(`${questionDetail.value.hasCollect ? '收藏成功' : '取消收藏成功'}`) push.success(`${questionDetail.value.hasCollect ? '收藏成功' : '取消收藏成功'}`)
} }
const handleShareQuestion = async () => {
try {
await wxShare({
title: questionDetail.value.title,
desc: [questionDetail.value.createUserName, questionDetail.value.region]
.filter(Boolean)
.join('——'),
imgUrl: questionDetail.value.faceUrl || questionDetail.value.imgUrlList?.[0],
})
push.success('分享成功')
} catch (error) {
if (isWxShareCancel(error)) {
console.warn(error)
push.warning('取消分享')
return
}
console.error(error)
push.error('分享失败')
}
}
const handleLikeAnswer = async (answer: CommentItemDto) => { const handleLikeAnswer = async (answer: CommentItemDto) => {
await addOrCancelCommentLike(answer.id) await addOrCancelCommentLike(answer.id)
answer.hasPraise = answer.hasPraise === BooleanFlag.YES ? BooleanFlag.NO : BooleanFlag.YES answer.hasPraise = answer.hasPraise === BooleanFlag.YES ? BooleanFlag.NO : BooleanFlag.YES
......
...@@ -184,6 +184,14 @@ ...@@ -184,6 +184,14 @@
</el-icon> </el-icon>
<span class="text-base">{{ videoDetail?.replyCount || 0 }}</span> <span class="text-base">{{ videoDetail?.replyCount || 0 }}</span>
</el-button> </el-button>
<el-button
text
class="flex items-center gap-2 transition-colors hover:text-blue-500"
@click="handleShareVideo"
>
<el-icon size="17"><svg-icon name="share" /></el-icon>
<span class="text-15px">分享</span>
</el-button>
<!-- 打赏 --> <!-- 打赏 -->
<el-button <el-button
text text
...@@ -348,6 +356,7 @@ import ActionMore from '@/components/common/ActionMore/index.vue' ...@@ -348,6 +356,7 @@ import ActionMore from '@/components/common/ActionMore/index.vue'
import SendMessageDialog from '@/components/common/SendMessageDialog/index.vue' import SendMessageDialog from '@/components/common/SendMessageDialog/index.vue'
import BackButton from '@/components/common/BackButton/index.vue' import BackButton from '@/components/common/BackButton/index.vue'
import { useNavigation } from '@/hooks' import { useNavigation } from '@/hooks'
import { isWxShareCancel, wxShare } from '@/utils/wxUtil'
import { import {
ArticleTypeEnum, ArticleTypeEnum,
BooleanFlag, BooleanFlag,
...@@ -546,6 +555,25 @@ const handleReward = () => { ...@@ -546,6 +555,25 @@ const handleReward = () => {
rewardDialogRef.value?.open(videoDetail.value.id) rewardDialogRef.value?.open(videoDetail.value.id)
} }
const handleShareVideo = async () => {
try {
await wxShare({
title: videoDetail.value.title,
desc: [videoDetail.value.createUserName, videoDetail.value.region].filter(Boolean).join('——'),
imgUrl: videoDetail.value.faceUrl || videoDetail.value.imgUrl?.split(',').find(Boolean),
})
push.success('分享成功')
} catch (error) {
if (isWxShareCancel(error)) {
console.warn(error)
push.warning('取消分享')
return
}
console.error(error)
push.error('分享失败')
}
}
onMounted(async () => { onMounted(async () => {
const { data } = await getArticleDetail(videoId) const { data } = await getArticleDetail(videoId)
videoDetail.value = data videoDetail.value = data
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment