Skip to content

Commit b1f42af

Browse files
docs: ✏️ 优化演示demo支持在顶部显示对应页面微信小程序的二维码
1 parent 7ed7dd3 commit b1f42af

83 files changed

Lines changed: 253 additions & 16 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

build/qrcode.js

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// 调用微信api生成每个页面的小程序码,使用axios发起请求
2+
3+
const fs = require('fs')
4+
const path = require('path')
5+
const axios = require('axios')
6+
const JSON5 = require('json5') // 引入 json5 库
7+
8+
const appID = process.argv[process.argv.indexOf('--APP_ID') + 1] // 在 --APP_ID 后面
9+
const appSecret = process.argv[process.argv.indexOf('--APP_SECRET') + 1] // --APP_SECRET 后面
10+
11+
// 获取 access_token 的函数
12+
async function getAccessToken() {
13+
const url = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appID}&secret=${appSecret}`
14+
15+
try {
16+
const response = await axios.get(url)
17+
return response.data.access_token
18+
} catch (error) {
19+
console.error(`获取 access_token 失败: ${error.response ? error.response.data : error.message}`)
20+
throw error // 抛出错误以便后续处理
21+
}
22+
}
23+
24+
// 读取 pages.json 文件
25+
const pagesJsonPath = path.join(__dirname, '../src/pages.json') // 计算 pages.json 的路径
26+
let pagesJson
27+
28+
try {
29+
const jsonData = fs.readFileSync(pagesJsonPath, 'utf8')
30+
pagesJson = JSON5.parse(jsonData) // 使用 json5 解析带注释的 JSON
31+
} catch (error) {
32+
console.error(`读取或解析 pages.json 失败: ${error.message}`)
33+
process.exit(1) // 退出程序
34+
}
35+
36+
const pages = pagesJson.pages.filter((page) => page.path.endsWith('Index'))
37+
38+
// 将驼峰命名法转换为小写短横线连接的函数
39+
function camelToKebabCase(str) {
40+
return str
41+
.replace(/([a-z])([A-Z])/g, '$1-$2') // 在小写字母和大写字母之间插入短横线
42+
.replace(/([A-Z])/g, '-$1') // 在大写字母前插入短横线
43+
.replace(/--+/g, '-') // 替换多个短横线为一个短横线
44+
.replace(/^-|-$/g, '') // 去掉开头和结尾的短横线
45+
.toLowerCase() // 转换为小写
46+
}
47+
48+
// 删除 wxqrcode 目录及其内容
49+
function clearWxqrcodeDirectory(outputDir) {
50+
if (fs.existsSync(outputDir)) {
51+
fs.rmSync(outputDir, { recursive: true, force: true }) // 递归删除目录及其内容
52+
console.log(`已删除目录: ${outputDir}`)
53+
}
54+
}
55+
56+
// 生成小程序码的函数
57+
async function generateMiniProgramCode(accessToken, pagePath, retries = 3) {
58+
const url = `https://api.weixin.qq.com/wxa/getwxacode?access_token=${accessToken}`
59+
const data = {
60+
path: pagePath,
61+
width: 430 // 小程序码的宽度,可以根据需要调整
62+
}
63+
64+
for (let attempt = 1; attempt <= retries; attempt++) {
65+
try {
66+
const response = await axios.post(url, data, {
67+
responseType: 'arraybuffer' // 以二进制形式接收图片数据
68+
})
69+
70+
// 确保输出目录存在
71+
const outputDir = path.join(__dirname, '../docs/public/wxqrcode')
72+
if (!fs.existsSync(outputDir)) {
73+
fs.mkdirSync(outputDir, { recursive: true }) // 创建目录及其父目录
74+
}
75+
76+
// 提取组件名并转换格式
77+
const componentName = pagePath.split('/')[1] // 假设路径格式为 pages/组件名/Index
78+
const formattedName = camelToKebabCase(componentName) // 转换为小写短横线连接
79+
80+
// 将返回的图片数据保存为文件
81+
const fileName = path.join(outputDir, `${formattedName}.png`) // 生成文件名
82+
fs.writeFileSync(fileName, response.data)
83+
console.log(`小程序码已生成并保存为 ${fileName}`)
84+
return // 成功后退出函数
85+
} catch (error) {
86+
console.error(`生成小程序码失败: ${error.response ? error.response.data : error.message}`)
87+
if (attempt < retries) {
88+
console.log(`重试 ${attempt}/${retries}...`)
89+
} else {
90+
console.error(`所有重试均失败,无法生成小程序码: ${pagePath}`)
91+
}
92+
}
93+
}
94+
}
95+
96+
// 遍历每个页面并生成小程序码
97+
async function generateCodesForAllPages(accessToken) {
98+
for (const page of pages) {
99+
await generateMiniProgramCode(accessToken, page.path)
100+
}
101+
}
102+
103+
// 生成二维码图片
104+
async function genrateQRCodeImage() {
105+
const outputDir = path.join(__dirname, '../docs/public/wxqrcode')
106+
107+
// 在开始生成小程序码之前清空 wxqrcode 目录
108+
clearWxqrcodeDirectory(outputDir)
109+
110+
try {
111+
const accessToken = await getAccessToken()
112+
await generateCodesForAllPages(accessToken)
113+
} catch (error) {
114+
console.error('程序执行失败:', error.message)
115+
}
116+
}
117+
118+
// 生成小程序二维码图片 pnpm qrcode -- --APP_ID xxx --APP_SECRET xxx
119+
genrateQRCodeImage()
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<template>
2+
<div @mouseenter="showQRCode = true" @mouseleave="showQRCode = false">
3+
<el-tooltip
4+
placement="bottom"
5+
effect="light"
6+
:visible="showQRCode"
7+
:popper-options="{ modifiers: [{ name: 'offset', options: { offset: [0, 20] } }] }"
8+
>
9+
<svg
10+
xmlns="http://www.w3.org/2000/svg"
11+
width="1em"
12+
height="1em"
13+
viewBox="0 0 24 24"
14+
>
15+
<path fill="currentColor" d="M2 2h9v9H2zm2 2v5h5V4zm9-2h9v9h-9zm2 2v5h5V4zM5.5 5.5h2.004v2.004H5.5zm11 0h2.004v2.004H16.5zm-3.504 7.496H15V15h-2.004zm7 0H22V15h-2.004zM2 13h9v9H2zm2 2v5h5v-5zm11.996.996H18v2h2v2h2V22h-2.004v-2h-2v-2h-2zM5.5 16.5h2.004v2.004H5.5zm7.496 3.496H15V22h-2.004z"></path>
16+
</svg>
17+
<template #content>
18+
<div class="qr-code">
19+
<img :src="src" alt="二维码" />
20+
</div>
21+
</template>
22+
</el-tooltip>
23+
</div>
24+
</template>
25+
26+
<script lang="ts" setup>
27+
import { ref } from 'vue';
28+
import { ElTooltip } from 'element-plus';
29+
interface Props {
30+
/** 二维码资源 */
31+
src: string
32+
}
33+
34+
const props = withDefaults(defineProps<Props>(), {
35+
src: ''
36+
})
37+
38+
const showQRCode = ref(false);
39+
</script>
40+
41+
<style scoped>
42+
43+
.qr-code {
44+
width: 130px; /* 设置二维码的宽度 */
45+
height: auto; /* 高度自适应 */
46+
transition: opacity 0.3s ease; /* 添加过渡效果 */
47+
}
48+
</style>

docs/.vitepress/theme/components/VPIframe.vue

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@
88
<div class="demo-header">
99
<ExternalLink :href="href" class="demo-link" :style="`${expanded ? '' : 'height:0;width:0;opacity:0'}`">
1010
</ExternalLink>
11+
<QrCode class="demo-qrcode" :src="qrcode" v-if="expanded&&qrcode"></QrCode>
1112
<el-icon class="expand-icon" style="cursor: pointer;" @click="toggleExpand">
1213
<component :is="expanded ? Fold : Expand" />
1314
</el-icon>
1415
</div>
1516
<!-- iframe 容器 -->
1617
<div class="iframe-container">
17-
<iframe v-if="expanded" ref="iframe" id="demo" class="iframe" scrolling="auto" frameborder="0" :src="href" />
18+
<iframe v-if="expanded&&transitionEnd" ref="iframe" id="demo" class="iframe" scrolling="auto" frameborder="0" :src="href" />
1819
</div>
1920
</div>
2021
</template>
@@ -23,6 +24,8 @@
2324
import { Expand, Fold } from '@element-plus/icons-vue'
2425
import { useRoute, useData } from 'vitepress'
2526
import { computed, onMounted, ref, watch } from 'vue'
27+
import QrCode from './QrCode.vue'
28+
2629
interface Props {
2730
/** 是否展开状态 */
2831
expanded?: boolean
@@ -35,7 +38,7 @@ const props = withDefaults(defineProps<Props>(), {
3538
// 状态管理
3639
const baseUrl = ref('')
3740
const iframe = ref<HTMLIFrameElement | null>(null)
38-
const transitionEnd = ref(false)
41+
const transitionEnd = ref(true)
3942
4043
const emit = defineEmits<{
4144
'update:expanded': [boolean] // 更新展开状态
@@ -54,6 +57,13 @@ const href = computed(() => {
5457
return baseUrl.value + `pages/${kebabToCamel(paths[paths.length - 1])}/Index`
5558
})
5659
60+
const qrcode = computed(() => {
61+
const path = route.path
62+
const paths = path ? path.split('.')[0].split('/') : []
63+
if (!paths.length) return ''
64+
return `/wxqrcode/${kebabToCamel(paths[paths.length - 1])}.png`
65+
})
66+
5767
// 工具函数:转换 kebab-case 为 camelCase
5868
function kebabToCamel(input: string): string {
5969
return input.replace(/-([a-z])/g, (match, group) => group.toUpperCase())
@@ -74,17 +84,13 @@ function toggleExpand() {
7484
// 触发事件通知父组件
7585
emit('update:expanded', !props.expanded)
7686
emit('state-change', !props.expanded)
77-
78-
if (props.expanded) {
79-
transitionEnd.value = false
80-
}
87+
transitionEnd.value = false
8188
}
8289
8390
// 过渡结束处理
8491
function onTransitionEnd() {
85-
if (!props.expanded) {
86-
transitionEnd.value = true
87-
}
92+
transitionEnd.value = true
93+
8894
}
8995
9096
// iframe 消息通信
@@ -180,6 +186,16 @@ watch(
180186
color: var(--color);
181187
}
182188
189+
.demo-qrcode{
190+
font-size: 28px !important;
191+
transition: all 0.3s ease-in-out;
192+
position: absolute;
193+
left: calc(50% - 14px);
194+
--color: inherit;
195+
fill: currentColor;
196+
color: var(--color);
197+
}
198+
183199
.expand-icon {
184200
position: absolute;
185201
right: 8px;
89.5 KB

docs/public/wxqrcode/backtop.png

88.8 KB

docs/public/wxqrcode/badge.png

91.9 KB

docs/public/wxqrcode/button.png

92 KB
90.5 KB

docs/public/wxqrcode/calendar.png

87.6 KB

docs/public/wxqrcode/card.png

92.9 KB

0 commit comments

Comments
 (0)