feat:新增微信扫码登录
This commit is contained in:
parent
fc69f53dc9
commit
c0f4b7db9b
@ -1,5 +1,5 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/order.svg" />
|
||||
|
24
package-lock.json
generated
24
package-lock.json
generated
@ -23,6 +23,7 @@
|
||||
"vite": "^6.3.5",
|
||||
"vite-plugin-vue-devtools": "^7.7.6",
|
||||
"vite-plugin-windicss": "^1.9.4",
|
||||
"vue-next-wxlogin": "^1.0.4",
|
||||
"windicss": "^3.5.6"
|
||||
}
|
||||
},
|
||||
@ -2392,6 +2393,18 @@
|
||||
"url": "https://github.com/sponsors/mesqueeb"
|
||||
}
|
||||
},
|
||||
"node_modules/core-js": {
|
||||
"version": "3.42.0",
|
||||
"resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.42.0.tgz",
|
||||
"integrity": "sha512-Sz4PP4ZA+Rq4II21qkNqOEDTDrCvcANId3xpIgB34NDkWc3UduWj2dqEtN9yZIq8Dk3HyPI33x9sqqU5C8sr0g==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/core-js"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
@ -4507,6 +4520,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vue-next-wxlogin": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmmirror.com/vue-next-wxlogin/-/vue-next-wxlogin-1.0.4.tgz",
|
||||
"integrity": "sha512-gJR8Zyp0tDWGcHPDkIJmTD50oblPbu7kPODmucj5d/sHYJw3VegD7xDsgRCSmT6+sT2lPBXFr8OTy2TQuMMmNw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"core-js": "^3.6.5",
|
||||
"vue": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-router": {
|
||||
"version": "4.5.1",
|
||||
"resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.5.1.tgz",
|
||||
|
@ -24,6 +24,7 @@
|
||||
"vite": "^6.3.5",
|
||||
"vite-plugin-vue-devtools": "^7.7.6",
|
||||
"vite-plugin-windicss": "^1.9.4",
|
||||
"vue-next-wxlogin": "^1.0.4",
|
||||
"windicss": "^3.5.6"
|
||||
}
|
||||
}
|
||||
|
@ -1,189 +1,188 @@
|
||||
<template>
|
||||
<div class="wx-login-container">
|
||||
<!-- 模态框模式 -->
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
title="微信扫码登录"
|
||||
width="380px"
|
||||
:close-on-click-modal="false"
|
||||
v-if="isDialog"
|
||||
>
|
||||
<login-content />
|
||||
</el-dialog>
|
||||
|
||||
<!-- 嵌入式模式 -->
|
||||
<div class="inline-login" v-else>
|
||||
<login-content />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const LoginContent = {
|
||||
template: `
|
||||
<div class="login-content">
|
||||
<div class="qrcode-container">
|
||||
<el-image
|
||||
v-loading="loading"
|
||||
:src="qrcodeUrl"
|
||||
fit="contain"
|
||||
class="qrcode-image"
|
||||
>
|
||||
<template #error>
|
||||
<div class="image-error">二维码加载失败</div>
|
||||
</template>
|
||||
</el-image>
|
||||
|
||||
<div class="status-message">
|
||||
<el-alert
|
||||
v-if="errorMessage"
|
||||
:title="errorMessage"
|
||||
type="error"
|
||||
show-icon
|
||||
:closable="false"
|
||||
/>
|
||||
<div v-else-if="!loading" class="scan-tip">
|
||||
微信扫一扫登录
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-skeleton :rows="3" animated v-if="loading" />
|
||||
</div>
|
||||
|
||||
<div class="action-area">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
@click="refreshQrcode"
|
||||
:loading="refreshing"
|
||||
>
|
||||
刷新二维码
|
||||
</el-button>
|
||||
<div class="qrcode-container">
|
||||
<el-image
|
||||
v-loading="loading"
|
||||
:src="qrcodeUrl"
|
||||
fit="contain"
|
||||
class="qrcode-image"
|
||||
>
|
||||
<template #error>
|
||||
<div class="image-error">二维码加载失败</div>
|
||||
</template>
|
||||
</el-image>
|
||||
|
||||
<div class="status-message">
|
||||
<el-alert
|
||||
v-if="errorMessage"
|
||||
:title="errorMessage"
|
||||
type="error"
|
||||
show-icon
|
||||
:closable="false"
|
||||
/>
|
||||
<div v-else-if="!loading" class="scan-tip">
|
||||
请使用微信扫一扫登录
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
props: ['getQrcodeUrl', 'checkLoginStatus'],
|
||||
setup(props, { emit }) {
|
||||
const loading = ref(true)
|
||||
const refreshing = ref(false)
|
||||
const qrcodeUrl = ref('')
|
||||
const errorMessage = ref('')
|
||||
let pollTimer = null
|
||||
|
||||
// 获取二维码
|
||||
const fetchQrcode = async () => {
|
||||
try {
|
||||
errorMessage.value = ''
|
||||
const response = await fetch(props.getQrcodeUrl)
|
||||
const data = await response.json()
|
||||
qrcodeUrl.value = data.qrcodeUrl
|
||||
startPolling(data.ticket)
|
||||
} catch (e) {
|
||||
errorMessage.value = '二维码获取失败,请重试'
|
||||
} finally {
|
||||
loading.value = false
|
||||
refreshing.value = false
|
||||
|
||||
<el-skeleton :rows="3" animated v-if="loading" />
|
||||
</div>
|
||||
|
||||
<div class="action-area">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
@click="refreshQrcode"
|
||||
:loading="refreshing"
|
||||
>
|
||||
刷新二维码
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import axios from 'axios'
|
||||
|
||||
const props = defineProps({
|
||||
// 微信公众号appid
|
||||
appid: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
// 微信公众号appsecret
|
||||
appsecret: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['success'])
|
||||
|
||||
const loading = ref(true)
|
||||
const refreshing = ref(false)
|
||||
const qrcodeUrl = ref('')
|
||||
const errorMessage = ref('')
|
||||
let pollTimer = null
|
||||
let ticket = ref('')
|
||||
|
||||
// 获取微信接口访问令牌
|
||||
const getAccessToken = async () => {
|
||||
try {
|
||||
const response = await axios.get(`/api/wechat/token?appid=${props.appid}&secret=${props.appsecret}`)
|
||||
return response.data.access_token
|
||||
} catch (error) {
|
||||
throw new Error('获取访问令牌失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 生成临时二维码
|
||||
const generateQrcode = async (access_token) => {
|
||||
try {
|
||||
const response = await axios.post(`/api/wechat/qrcode/create?access_token=${access_token}`, {
|
||||
expire_seconds: 600,
|
||||
action_name: 'QR_STR_SCENE',
|
||||
action_info: {
|
||||
scene: {
|
||||
scene_str: 'login_' + Date.now()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 开始轮询检查状态
|
||||
const startPolling = (ticket) => {
|
||||
clearInterval(pollTimer)
|
||||
pollTimer = setInterval(async () => {
|
||||
try {
|
||||
const response = await fetch(`${props.checkLoginStatus}?ticket=${ticket}`)
|
||||
const res = await response.json()
|
||||
|
||||
if (res.status === 'success') {
|
||||
})
|
||||
return response.data
|
||||
} catch (error) {
|
||||
throw new Error('生成二维码失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 获取二维码
|
||||
const fetchQrcode = async () => {
|
||||
try {
|
||||
errorMessage.value = ''
|
||||
loading.value = true
|
||||
|
||||
// 获取访问令牌
|
||||
const access_token = await getAccessToken()
|
||||
|
||||
// 生成临时二维码
|
||||
const qrcodeData = await generateQrcode(access_token)
|
||||
ticket.value = qrcodeData.ticket
|
||||
qrcodeUrl.value = `https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=${qrcodeData.ticket}`
|
||||
|
||||
// 开始轮询检查扫码状态
|
||||
startPolling()
|
||||
} catch (error) {
|
||||
errorMessage.value = error.message || '二维码获取失败,请重试'
|
||||
} finally {
|
||||
loading.value = false
|
||||
refreshing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 开始轮询检查状态
|
||||
const startPolling = () => {
|
||||
clearInterval(pollTimer)
|
||||
pollTimer = setInterval(async () => {
|
||||
try {
|
||||
const response = await axios.get(`/api/wechat/scan/check?ticket=${ticket.value}`)
|
||||
const data = response.data
|
||||
|
||||
if (data.status === 'success') {
|
||||
clearInterval(pollTimer)
|
||||
emit('success', res.data)
|
||||
} else if (res.status === 'expired') {
|
||||
emit('success', data)
|
||||
} else if (data.status === 'expired') {
|
||||
errorMessage.value = '二维码已过期,请刷新'
|
||||
clearInterval(pollTimer)
|
||||
}
|
||||
} catch (e) {
|
||||
errorMessage.value = '网络异常,请检查连接'
|
||||
clearInterval(pollTimer)
|
||||
}
|
||||
}, 2000)
|
||||
} catch (error) {
|
||||
errorMessage.value = '网络异常,请检查连接'
|
||||
clearInterval(pollTimer)
|
||||
}
|
||||
|
||||
// 刷新二维码
|
||||
const refreshQrcode = () => {
|
||||
refreshing.value = true
|
||||
fetchQrcode()
|
||||
}
|
||||
|
||||
onMounted(fetchQrcode)
|
||||
onBeforeUnmount(() => clearInterval(pollTimer))
|
||||
|
||||
return {
|
||||
loading,
|
||||
refreshing,
|
||||
qrcodeUrl,
|
||||
errorMessage,
|
||||
refreshQrcode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 主组件逻辑
|
||||
const props = defineProps({
|
||||
// 获取二维码的API地址
|
||||
getQrcodeUrl: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
// 检查登录状态的API地址
|
||||
checkLoginStatus: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
// 是否以对话框形式显示
|
||||
isDialog: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['success'])
|
||||
const dialogVisible = ref(true)
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
// 刷新二维码
|
||||
const refreshQrcode = () => {
|
||||
refreshing.value = true
|
||||
fetchQrcode()
|
||||
}
|
||||
|
||||
onMounted(fetchQrcode)
|
||||
onBeforeUnmount(() => clearInterval(pollTimer))
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
.wx-login-container {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.qrcode-container {
|
||||
width: 300px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.qrcode-image {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
border: 1px solid #eee;
|
||||
}
|
||||
|
||||
.scan-tip {
|
||||
color: #666;
|
||||
margin: 15px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.action-area {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.image-error {
|
||||
height: 300px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #999;
|
||||
}
|
||||
.wx-login-container {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.qrcode-container {
|
||||
width: 300px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.qrcode-image {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
border: 1px solid #eee;
|
||||
}
|
||||
|
||||
.scan-tip {
|
||||
color: #666;
|
||||
margin: 15px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.action-area {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.image-error {
|
||||
height: 300px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
@ -21,7 +21,7 @@
|
||||
<div class="flex flex-col items-center justify-center">
|
||||
<el-input
|
||||
v-model="username"
|
||||
class="w-240px mt-3"
|
||||
class="w-240px mt-4"
|
||||
size="large"
|
||||
placeholder="请输入用户名"
|
||||
>
|
||||
@ -32,7 +32,7 @@
|
||||
<el-input
|
||||
type="password"
|
||||
v-model="password"
|
||||
class="w-240px mt-3"
|
||||
class="w-240px mt-4"
|
||||
size="large"
|
||||
placeholder="请输入密码..."
|
||||
>
|
||||
@ -58,7 +58,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<div class="flex flex-col justify-center items-center">
|
||||
<div class="flex items-center w-240px mt-3">
|
||||
<div class="flex items-center w-240px mt-4">
|
||||
<el-input
|
||||
v-model="mobile"
|
||||
class="w-160px"
|
||||
@ -83,7 +83,7 @@
|
||||
style="width: 240px"
|
||||
size="large"
|
||||
placeholder="短信验证码"
|
||||
class="mt-3"
|
||||
class="mt-4"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon><Grid /></el-icon>
|
||||
@ -107,7 +107,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<div class="flex flex-col justify-center items-center">
|
||||
<div class="flex items-center w-240px mt-3">
|
||||
<div class="flex items-center w-240px mt-4">
|
||||
<el-input
|
||||
type="email"
|
||||
v-model="email"
|
||||
@ -133,7 +133,7 @@
|
||||
style="width: 240px"
|
||||
size="large"
|
||||
placeholder="邮件验证码"
|
||||
class="mt-3"
|
||||
class="mt-4"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon><Grid /></el-icon>
|
||||
@ -149,7 +149,30 @@
|
||||
</el-button>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="" name="fourth">
|
||||
<template #label>
|
||||
<div class="flex items-center">
|
||||
<i class="fa fa-qrcode"></i>
|
||||
<span class="font-bold ml-2">微信扫码</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="flex flex-col justify-center items-center">
|
||||
<wxlogin
|
||||
:appid="appId"
|
||||
:scope="scope"
|
||||
:redirect_uri="redirect_uri"
|
||||
:state="state"
|
||||
:self_redirect="self_redirect"
|
||||
:href="style_href"
|
||||
class='w-300px h-300px'
|
||||
>
|
||||
</wxlogin>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<div class="w-full flex items-center justify-center mt-4">
|
||||
<el-link type="primary">找回密码</el-link>
|
||||
</div>
|
||||
<el-divider class="mt-6">
|
||||
<span class="text-xs text-gray-400">成都机联云维科技有限公司 ©版权所有</span>
|
||||
</el-divider>
|
||||
@ -160,6 +183,10 @@
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import wxlogin from 'vue-next-wxlogin'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
const activeName = ref('first')
|
||||
const username = ref('')
|
||||
const password = ref('')
|
||||
@ -167,6 +194,22 @@
|
||||
const code = ref('')
|
||||
const email = ref('')
|
||||
const ecode = ref('')
|
||||
const appId = ref('wxa0a92798871387ba')
|
||||
// wxd18da60b377a9b40
|
||||
// wxa0a92798871387ba
|
||||
const impower_style = `
|
||||
.impowerBox qrcode {width: 200px;margin-top:10px;border: 0;}
|
||||
.impowerBox .title {display: none;}
|
||||
.status_icon {display: none;}
|
||||
.impowerBox .info {width: 200px;margin: -10px auto;display: none;}
|
||||
.impowerBox .status {text-align: center;padding: 0;}
|
||||
`
|
||||
const style_href = ref(`data:text/css;base64,${window.btoa(unescape(encodeURIComponent(impower_style)))}`)
|
||||
const self_redirect = ref('false')
|
||||
const scope = ref("snsapi_login")
|
||||
const redirect_uri = encodeURIComponent("https://api.jifuyun.cn/")
|
||||
const state = ref(`${parseInt(new Date().getTime() / 1000)}`)
|
||||
|
||||
const handleClick = (tab, event) => {
|
||||
console.log(tab, event)
|
||||
}
|
||||
@ -179,6 +222,15 @@
|
||||
const email_login = () => {
|
||||
console.log(mobile.value, code.value)
|
||||
}
|
||||
|
||||
const get_code = () => {
|
||||
console.log('获取验证码')
|
||||
}
|
||||
|
||||
const handleWechatLoginSuccess = (data) => {
|
||||
console.log('微信登录成功:', data)
|
||||
router.push('/home')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
Loading…
Reference in New Issue
Block a user