feat: 登录扫一扫
Some checks failed
构建并部署前端到生产环境 / build-and-deploy (push) Failing after 43s

This commit is contained in:
sexygoat
2026-04-16 17:31:46 +08:00
parent 102e6c0867
commit bd0bb149b8
4 changed files with 6313 additions and 826 deletions

6977
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -19,6 +19,7 @@
"vue": "^3.4.0",
"clipboard": "^2.0.11",
"dayjs": "^1.11.19",
"html5-qrcode": "^2.3.8",
"uview-plus": "^3.6.29"
}
}

View File

@@ -50,6 +50,22 @@
<text>扫一扫登录</text>
</button>
</view>
</view>
</view>
<view v-if="showScanner" class="scanner-overlay">
<view class="scanner-container">
<view class="scanner-header">
<text>扫一扫</text>
<text class="close-btn" @tap="closeScanner">×</text>
</view>
<view id="scanner-region" class="scanner-region"></view>
<view class="scanner-tip">将二维码放入框内即可自动扫描</view>
</view>
</view>
</view>
</button>
</view>
<view class="agreement">
<view class="agreement-text">
@@ -65,10 +81,11 @@
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { ref, onMounted, onUnmounted } from 'vue';
import { authApi } from '@/api/index.js';
import { useUserStore } from '@/store/index.js';
import { APP_ID, USE_WECHAT_AUTH } from '@/utils/env.js';
import { Html5Qrcode } from 'html5-qrcode';
const userStore = useUserStore();
@@ -81,11 +98,19 @@
getPathDeviceId();
});
onUnmounted(() => {
if (html5QrCode) {
html5QrCode.stop().catch(() => {});
}
});
const identifier = ref('');
const loading = ref(false);
const agreed = ref(true);
const inputFocus = ref(false);
const showError = ref(false);
const showScanner = ref(false);
let html5QrCode = null;
const getCode = () => {
const params = new URLSearchParams(window.location.search);
@@ -125,35 +150,44 @@
doLogin();
};
const handleScanLogin = () => {
// #ifdef H5
if (typeof wx !== 'undefined' && wx.scanQRCode) {
wx.scanQRCode({
needResult: 1,
scanType: ['qrCode', 'barCode'],
success: (res) => {
const result = res.resultStr;
if (result && result.includes('device_id')) {
const match = result.match(/device_id[=]([^&]*)/);
if (match && match[1]) {
identifier.value = match[1];
handleLogin();
}
const handleScanLogin = async () => {
showScanner.value = true;
try {
html5QrCode = new Html5Qrcode('scanner-region');
await html5QrCode.start(
{ facingMode: 'environment' },
{
fps: 10,
qrbox: { width: 250, height: 250 }
},
(decodedText) => {
const match = decodedText.match(/=([^=]+)$/);
if (match && match[1]) {
identifier.value = match[1];
closeScanner();
handleLogin();
} else {
uni.showToast({ title: '无效的二维码', icon: 'none' });
}
},
fail: () => {
uni.showToast({ title: '扫码失败,请重试', icon: 'none' });
}
});
} else {
uni.showToast({ title: '扫一扫功能暂不可用', icon: 'none' });
() => {}
);
} catch (err) {
console.error('扫码失败', err);
uni.showToast({ title: '无法访问摄像头', icon: 'none' });
closeScanner();
}
};
const closeScanner = async () => {
showScanner.value = false;
if (html5QrCode) {
try {
await html5QrCode.stop();
} catch (e) {}
html5QrCode = null;
}
// #endif
// #ifndef H5
uni.showToast({ title: '请在微信中打开', icon: 'none' });
// #endif
};
const getPathDeviceId = () => {
@@ -469,4 +503,52 @@
font-weight: 500;
white-space: nowrap;
}
.scanner-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.8);
z-index: 999;
display: flex;
align-items: center;
justify-content: center;
}
.scanner-container {
width: 600rpx;
background: var(--bg-primary);
border-radius: var(--radius-large);
overflow: hidden;
}
.scanner-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 32rpx;
font-size: 32rpx;
font-weight: 600;
border-bottom: 1rpx solid var(--border-light);
}
.close-btn {
font-size: 48rpx;
color: var(--text-tertiary);
line-height: 1;
}
.scanner-region {
width: 100%;
height: 600rpx;
}
.scanner-tip {
padding: 24rpx;
text-align: center;
font-size: 26rpx;
color: var(--text-tertiary);
}
</style>

29
utils/wxsdk.js Normal file
View File

@@ -0,0 +1,29 @@
import { BASE_URL, APP_ID } from './env.js';
export const initWxSdk = async () => {
if (typeof wx === 'undefined') return false;
try {
const res = await fetch(`${BASE_URL}/api/c/v1/wx/signature?url=${encodeURIComponent(window.location.href)}`);
const data = await res.json();
if (data.code === 200 || data.code === 0) {
wx.config({
debug: false,
appId: APP_ID,
timestamp: data.data.timestamp,
nonceStr: data.data.nonceStr,
signature: data.data.signature,
jsApiList: ['scanQRCode']
});
return new Promise((resolve) => {
wx.ready(() => resolve(true));
wx.error(() => resolve(false));
});
}
} catch (e) {
console.error('WX SDK init failed', e);
}
return false;
};