flat: 添加微信分享卡片

This commit is contained in:
史典卓
2025-07-14 15:38:39 +08:00
parent 645c2552f6
commit ec2dc5f659
22 changed files with 232 additions and 106 deletions

BIN
.DS_Store vendored

Binary file not shown.

32
App.vue
View File

@@ -3,7 +3,9 @@ import { reactive, inject, onMounted } from 'vue';
import { onLaunch, onShow, onHide } from '@dcloudio/uni-app'; import { onLaunch, onShow, onHide } from '@dcloudio/uni-app';
import useUserStore from './stores/useUserStore'; import useUserStore from './stores/useUserStore';
import useDictStore from './stores/useDictStore'; import useDictStore from './stores/useDictStore';
import { setupWechatShare, generateShareLink } from '@/utils/wechatShare.js';
const { $api, navTo, appendScriptTagElement } = inject('globalFunction'); const { $api, navTo, appendScriptTagElement } = inject('globalFunction');
import config from '@/config.js';
onLaunch((options) => { onLaunch((options) => {
useDictStore().getDictData(); useDictStore().getDictData();
@@ -26,15 +28,10 @@ onLaunch((options) => {
onMounted(() => { onMounted(() => {
// #ifndef MP-WEIXIN // #ifndef MP-WEIXIN
if (process.env.NODE_ENV === 'development') { appendScriptTagElement('https://qd.zhaopinzao8dian.com/file/csn/jweixin-1.4.0.js').then(() => {
appendScriptTagElement('./static/js/jweixin-1.4.0.js').then(() => { console.log('✅ 微信 JSSDK 加载完成');
console.log('✅ 微信 JSSDK 加载完成'); signatureFn();
}); });
} else {
appendScriptTagElement('/static/js/jweixin-1.4.0.js').then(() => {
console.log('✅ 微信 JSSDK 加载完成');
});
}
// #endif // #endif
}); });
@@ -45,6 +42,17 @@ onShow(() => {
onHide(() => { onHide(() => {
console.log('App Hide'); console.log('App Hide');
}); });
function signatureFn() {
const link = generateShareLink();
// console.log('首页link', link);
setupWechatShare({
title: config.shareConfig.title,
desc: config.shareConfig.desc,
link: link,
imgUrl: config.shareConfig.imgUrl,
});
}
</script> </script>
<style> <style>
@@ -81,19 +89,19 @@ uni-modal,
@font-face { @font-face {
font-family: PingFangSC-Regular; font-family: PingFangSC-Regular;
src: url('/static/font/PingFangSC-Regular.woff2') format('woff2'); src: url('https://qd.zhaopinzao8dian.com/file/csn/PingFangSC-Regular.woff2') format('woff2');
font-display: swap; font-display: swap;
} }
@font-face { @font-face {
font-family: PingFangSC-Medium; font-family: PingFangSC-Medium;
src: url('/static/font/PingFangSC-Medium.woff2') format('woff2'); src: url('https://qd.zhaopinzao8dian.com/file/csn/PingFangSC-Medium.woff2') format('woff2');
font-display: swap; font-display: swap;
} }
@font-face { @font-face {
font-family: DIN-Medium; font-family: DIN-Medium;
src: url('/static/font/DIN-Medium.woff2') format('woff2'); src: url('https://qd.zhaopinzao8dian.com/file/csn/DIN-Medium.woff2') format('woff2');
font-display: swap; font-display: swap;
} }

View File

@@ -543,6 +543,7 @@ function isEmptyObject(obj) {
return obj && typeof obj === 'object' && !Array.isArray(obj) && Object.keys(obj).length === 0; return obj && typeof obj === 'object' && !Array.isArray(obj) && Object.keys(obj).length === 0;
} }
export const $api = { export const $api = {
msg, msg,
prePage, prePage,
@@ -584,5 +585,5 @@ export default {
appendScriptTagElement, appendScriptTagElement,
insertSortData, insertSortData,
isInWechatMiniProgramWebview, isInWechatMiniProgramWebview,
isEmptyObject isEmptyObject,
} }

View File

@@ -63,5 +63,11 @@ export default {
experience: 0.3, //经验 experience: 0.3, //经验
salary: 0.5, // 薪资 salary: 0.5, // 薪资
areas: 0.5 // 区域 areas: 0.5 // 区域
},
shareConfig: {
baseUrl: 'https://qd.zhaopinzao8dian.com',
title: '找工作,用 AI 更高效|青岛市智能求职平台',
desc: '融合海量岗位、智能简历匹配、竞争力分析,助你精准锁定理想职位!',
imgUrl: 'https://qd.zhaopinzao8dian.com/file/csn/qd_shareLogo.jpg',
} }
} }

View File

@@ -18,14 +18,14 @@
</script> </script>
<title></title> <title></title>
<!-- vconsole --> <!-- vconsole -->
<!-- <script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script> <!-- <script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>
<script> <script>
var vConsole = new window.VConsole(); var vConsole = new window.VConsole();
vConsole.destroy(); vConsole.destroy();
</script> --> </script> -->
</head> </head>
<body> <!-- <body> -->
<div id="app"><!--app-html--></div> <div id="app"><!--app-html--></div>
<script type="module" src="/main.js"></script> <script type="module" src="/main.js"></script>
</body> </body>
</html> </html>

View File

@@ -1,7 +1,7 @@
<template> <template>
<AppLayout title="" :use-scroll-view="false"> <AppLayout title="" :use-scroll-view="false">
<template #headerleft> <template #headerleft>
<view class="btn"> <view class="btnback">
<image src="@/static/icon/back.png" @click="navBack"></image> <image src="@/static/icon/back.png" @click="navBack"></image>
</view> </view>
</template> </template>
@@ -143,12 +143,16 @@ function expand() {
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
.btnback{
width: 64rpx;
height: 64rpx;
}
.btn { .btn {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
width: 60rpx; width: 52rpx;
height: 60rpx; height: 52rpx;
} }
image { image {
height: 100%; height: 100%;

View File

@@ -1,7 +1,7 @@
<template> <template>
<AppLayout title="我的浏览" :show-bg-image="false" :use-scroll-view="false"> <AppLayout title="我的浏览" :show-bg-image="false" :use-scroll-view="false">
<template #headerleft> <template #headerleft>
<view class="btn"> <view class="btnback">
<image src="@/static/icon/back.png" @click="navBack"></image> <image src="@/static/icon/back.png" @click="navBack"></image>
</view> </view>
</template> </template>
@@ -152,12 +152,16 @@ function getPreviousDay(dateStr) {
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
.btnback{
width: 64rpx;
height: 64rpx;
}
.btn { .btn {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
width: 60rpx; width: 52rpx;
height: 60rpx; height: 52rpx;
} }
image { image {
height: 100%; height: 100%;

View File

@@ -1,7 +1,7 @@
<template> <template>
<AppLayout :title="title" :show-bg-image="false" @onScrollBottom="getDataList('add')"> <AppLayout :title="title" :show-bg-image="false" @onScrollBottom="getDataList('add')">
<template #headerleft> <template #headerleft>
<view class="btn"> <view class="btnback">
<image src="@/static/icon/back.png" @click="navBack"></image> <image src="@/static/icon/back.png" @click="navBack"></image>
</view> </view>
</template> </template>
@@ -101,12 +101,16 @@ function getDataList(type = 'add') {
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
.btnback{
width: 64rpx;
height: 64rpx;
}
.btn { .btn {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
width: 60rpx; width: 52rpx;
height: 60rpx; height: 52rpx;
} }
image { image {
height: 100%; height: 100%;

View File

@@ -1,7 +1,7 @@
<template> <template>
<AppLayout title="" :use-scroll-view="false"> <AppLayout title="" :use-scroll-view="false">
<template #headerleft> <template #headerleft>
<view class="btn"> <view class="btnback">
<image src="@/static/icon/back.png" @click="navBack"></image> <image src="@/static/icon/back.png" @click="navBack"></image>
</view> </view>
</template> </template>
@@ -219,12 +219,16 @@ function getHoursBetween(startTimeStr, endTimeStr) {
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
.btnback{
width: 64rpx;
height: 64rpx;
}
.btn { .btn {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
width: 60rpx; width: 52rpx;
height: 60rpx; height: 52rpx;
} }
image { image {
height: 100%; height: 100%;

View File

@@ -1,11 +1,14 @@
<template> <template>
<AppLayout title="" backGorundColor="#F4F4F4"> <AppLayout title="" backGorundColor="#F4F4F4">
<template #headerleft> <template #headerleft>
<view class="btn"> <view class="btnback">
<image src="@/static/icon/back.png" @click="navBack"></image> <image src="@/static/icon/back.png" @click="navBack"></image>
</view> </view>
</template> </template>
<template #headerright> <template #headerright>
<!-- <view class="btnshare">
<image src="@/static/icon/share.png" @click="shareJob"></image>
</view> -->
<view class="btn mar_ri10"> <view class="btn mar_ri10">
<image src="@/static/icon/collect3.png" v-if="!jobInfo.isCollection" @click="jobCollection"></image> <image src="@/static/icon/collect3.png" v-if="!jobInfo.isCollection" @click="jobCollection"></image>
<image src="@/static/icon/collect2.png" v-else @click="jobCollection"></image> <image src="@/static/icon/collect2.png" v-else @click="jobCollection"></image>
@@ -137,10 +140,12 @@
import point from '@/static/icon/point.png'; import point from '@/static/icon/point.png';
import VideoPlayer from './component/videoPlayer.vue'; import VideoPlayer from './component/videoPlayer.vue';
import { reactive, inject, watch, ref, onMounted, computed } from 'vue'; import { reactive, inject, watch, ref, onMounted, computed } from 'vue';
import { onLoad, onShow } from '@dcloudio/uni-app'; import { onLoad, onShow, onHide } from '@dcloudio/uni-app';
import dictLabel from '@/components/dict-Label/dict-Label.vue'; import dictLabel from '@/components/dict-Label/dict-Label.vue';
const { $api, navTo, getLenPx, parseQueryParams, navBack, isEmptyObject } = inject('globalFunction');
import RadarMap from './component/radarMap.vue'; import RadarMap from './component/radarMap.vue';
import { updateWechatShare, generateShareLink } from '@/utils/wechatShare.js';
const { $api, navTo, getLenPx, parseQueryParams, navBack, isEmptyObject } = inject('globalFunction');
import config from '@/config.js';
const matchingDegree = ref(['一般', '良好', '优秀', '极好']); const matchingDegree = ref(['一般', '良好', '优秀', '极好']);
const currentStep = ref(1); const currentStep = ref(1);
const companyCount = ref(0); const companyCount = ref(0);
@@ -165,11 +170,31 @@ onShow(() => {
} }
}); });
onHide(() => {
const link = generateShareLink();
// console.log('首页link', link);
updateWechatShare({
title: config.shareConfig.title,
desc: config.shareConfig.desc,
link: link,
imgUrl: config.shareConfig.imgUrl,
});
});
function initLoad(option) { function initLoad(option) {
const jobId = atob(option.jobId); const jobId = atob(option.jobId);
if (jobId !== jobIdRef.value) { if (jobId !== jobIdRef.value) {
jobIdRef.value = jobId; jobIdRef.value = jobId;
getDetail(jobId); getDetail(jobId).then((data) => {
const link = generateShareLink(btoa(data.jobId));
// console.log('详情', link);
updateWechatShare({
title: '职位推荐:' + data.jobTitle,
desc: data.description,
link: link,
imgUrl: 'https://qd.zhaopinzao8dian.com/file/csn/qd_shareLogo.jpg',
});
});
} }
} }
@@ -182,30 +207,33 @@ function seeExplain() {
} }
function getDetail(jobId) { function getDetail(jobId) {
$api.createRequest(`/app/job/${jobId}`).then((resData) => { return new Promise((reslove, reject) => {
const { latitude, longitude, companyName, companyId } = resData.data; $api.createRequest(`/app/job/${jobId}`).then((resData) => {
jobInfo.value = resData.data; const { latitude, longitude, companyName, companyId } = resData.data;
getCompanyIsAJobs(companyId); jobInfo.value = resData.data;
getCompetivetuveness(jobId); reslove(resData.data);
if (latitude && longitude) { getCompanyIsAJobs(companyId);
mapCovers.value = [ getCompetivetuveness(jobId);
{ if (latitude && longitude) {
latitude: latitude, mapCovers.value = [
longitude: longitude, {
iconPath: point, latitude: latitude,
label: { longitude: longitude,
content: companyName, iconPath: point,
textAlign: 'center', label: {
padding: 3, content: companyName,
fontSize: 12, textAlign: 'center',
bgColor: '#FFFFFF', padding: 3,
anchorX: getTextWidth(companyName), // X 轴调整,负数向左 fontSize: 12,
borderRadius: 5, bgColor: '#FFFFFF',
anchorX: getTextWidth(companyName), // X 轴调整,负数向左
borderRadius: 5,
},
width: 34,
}, },
width: 34, ];
}, }
]; });
}
}); });
} }
@@ -278,12 +306,21 @@ function getClass(index) {
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
.btnback{
width: 64rpx;
height: 64rpx;
}
.btn { .btn {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
width: 60rpx; width: 52rpx;
height: 60rpx; height: 52rpx;
}
.btnshare {
width: 48rpx;
height: 48rpx;
margin-right: 46rpx;
} }
image { image {
height: 100%; height: 100%;

View File

@@ -1,7 +1,7 @@
<template> <template>
<AppLayout title="附近" :use-scroll-view="false"> <AppLayout title="附近" :use-scroll-view="false">
<template #headerleft> <template #headerleft>
<view class="btn"> <view class="btnback">
<image src="@/static/icon/back.png" @click="navBack"></image> <image src="@/static/icon/back.png" @click="navBack"></image>
</view> </view>
</template> </template>
@@ -83,12 +83,16 @@ function handleTabChange(index) {
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
.btnback{
width: 64rpx;
height: 64rpx;
}
.btn { .btn {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
width: 60rpx; width: 52rpx;
height: 60rpx; height: 52rpx;
} }
image { image {
height: 100%; height: 100%;

BIN
static/.DS_Store vendored

Binary file not shown.

BIN
static/font/.DS_Store vendored

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
static/icon/share.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 B

36
static/share.html Normal file
View File

@@ -0,0 +1,36 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<title>找工作,用 AI 更高效|青岛市智能求职平台</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!-- 微信分享卡片标签(动态填充) -->
<meta property="og:title" content="找工作,用 AI 更高效|青岛市智能求职平台" />
<meta property="og:description" content="融合海量岗位、智能简历匹配、竞争力分析,助你精准锁定理想职位!" />
<meta property="og:image" content="https://qd.zhaopinzao8dian.com/file/csn/qd_shareLogo.jpg" />
<meta property="og:url" content="https://qd.zhaopinzao8dian.com" />
<meta name="description" content="融合海量岗位、智能简历匹配、竞争力分析,助你精准锁定理想职位!">
<script>
const params = new URLSearchParams(location.search)
const jobId = params.get('jobId')
// document.querySelector('meta[property="og:url"]').setAttribute('content', location.href)
// 延迟跳转到 Vue 页面
setTimeout(() => {
if (jobId) {
window.location.href = `/#/packageA/pages/post/post?jobId=${jobId}`
} else {
window.location.href = '/#/'
}
}, 300)
// 测试使用 分享等形式打开
// http://localhost:5173/app/static/share.html?jobId=MTE4MzQ4NTE4
// http://localhost:5173/app/static/share.html?jobId=MTE4MzQ4NTE4&_t=1752221704007#/
</script>
</head>
<body>
<p>正在加载中...</p>
</body>
</html>

BIN
unpackage/.DS_Store vendored

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,5 +1,7 @@
import wx from 'weixin-js-sdk'
import config from "@/config.js" import config from "@/config.js"
import {
$api
} from '../common/globalFunction';
export function setupWechatShare({ export function setupWechatShare({
title, title,
@@ -8,56 +10,72 @@ export function setupWechatShare({
imgUrl imgUrl
}) { }) {
// 通过后端接口获取签名(必须) // 通过后端接口获取签名(必须)
fetch(`${config.baseUrl}/wechat-signature?url=${encodeURIComponent(location.href.split('#')[0])}`) $api.createRequest('/app/job/getWechatUrl', {
.then(res => res.json()) imgUrl: location.href.split('#')[0]
.then(({ }, 'POST').then((resData) => {
const {
appId, appId,
timestamp, timestamp,
nonceStr, nonceStr,
signature signature
}) => { } = resData.data
wx.config({
debug: false,
appId,
timestamp,
nonceStr,
signature,
jsApiList: ['updateAppMessageShareData', 'updateTimelineShareData']
})
wx.ready(() => { wx.config({
// 分享给好友 debug: false,
wx.updateAppMessageShareData({ appId,
title, timestamp,
desc, nonceStr,
link, signature,
imgUrl, jsApiList: ['updateAppMessageShareData', 'updateTimelineShareData']
success: () => {
console.log('分享配置成功')
}
})
// 分享到朋友圈
wx.updateTimelineShareData({
title,
link,
imgUrl,
success: () => {
console.log('朋友圈分享配置成功')
}
})
})
}) })
wx.ready(() => {
// 分享给好友
wx.updateAppMessageShareData({
title,
desc,
link,
imgUrl,
success: () => {
$api.msg('分享配置成功')
}
})
// 分享到朋友圈
wx.updateTimelineShareData({
title,
link,
imgUrl,
success: () => {
$api.msg('朋友圈分享配置成功')
}
})
}).catch((err) => {
$api.msg('获取微信签名失败')
console.error('获取微信签名失败:', err);
});
})
} }
// 使用 export function updateWechatShare({
// import { setupWechatShare } from '@/utils/wechatShare.js' title,
desc,
link,
imgUrl
}) {
if (!window.wx) return;
wx.updateAppMessageShareData({
title,
desc,
link,
imgUrl,
success: () => console.log('分享配置成功'),
fail: (err) => console.warn('分享配置失败', err)
});
}
// onMounted(() => {
// setupWechatShare({ // tools
// title: '职位推荐:高级前端工程师', export function generateShareLink(jobId) {
// desc: '某知名互联网公司年薪40W点击查看详情', const base = location.origin + '/app/static/share.html';
// link: location.href, const query = jobId ? `?jobId=${jobId}&_t=${Date.now()}` : `?_t=${Date.now()}`;
// imgUrl: 'https://yourcdn.com/job-thumbnail.png' return `${base}${query}`;
// }) }
// })