upload project

This commit is contained in:
unknown 2025-12-27 17:16:03 +08:00
commit 06961cae04
422 changed files with 110626 additions and 0 deletions

View file

@ -0,0 +1,262 @@
import jsQR from '../utils/jsQR';
Page({
data: {
// 相机相关配置
cameraContext: null,
devicePosition: 'back', // 后置摄像头, 'front' : 'back';
flashMode: 'off', // 闪光灯默认关闭,'off' : 'torch'
// 扫码框相关配置
scanBoxWidth: 580, // 扫码框宽度rpx
scanBoxHeight: 580, // 扫码框高度rpx
scanLineTop: 0, // 扫描线初始位置
scanLineSpeed: 2, // 扫描线移动速度
// 屏幕适配相关
windowWidth: 375, // 屏幕宽度rpx
windowHeight: 667, // 屏幕高度rpx
scanBoxLeft: 0, // 扫码框左侧偏移
scanBoxTop: 0, // 扫码框顶部偏移
isScanning: false, // 扫描状态标记,防止重复扫描
},
// 页面每次显示时(包括跳转返回后)触发
onShow() {
// 强制重置扫描状态,避免返回后无法扫码
this.setData({
isScanning: false
});
this.scanLock = false; // 同步释放全局锁
},
onLoad(options) {
const sis = wx.getWindowInfo();
// 初始化相机上下文
this.setData({
cameraContext: wx.createCameraContext(this),
// 获取屏幕尺寸用于适配
windowWidth: 750,
windowHeight: sis.windowHeight / sis.windowWidth * 750,
}, () => {
// 计算扫码框位置(居中显示)
this.calcScanBoxPosition();
// 启动扫描线动画
this.startScanLineAnimation();
});
},
// 计算扫码框居中位置
calcScanBoxPosition() {
const { windowWidth, windowHeight, scanBoxWidth, scanBoxHeight } = this.data;
const scanBoxLeft = (windowWidth - scanBoxWidth) / 2;
const scanBoxTop = (windowHeight - scanBoxHeight) / 2 - 100; // 向上偏移100rpx
this.setData({ scanBoxLeft, scanBoxTop });
},
// 扫描线动画
startScanLineAnimation() {
const { scanBoxHeight, scanLineSpeed } = this.data;
let scanLineTop = 0;
this.animationInterval = setInterval(() => {
scanLineTop += scanLineSpeed;
if (scanLineTop >= scanBoxHeight) {
scanLineTop = 0;
}
this.setData({ scanLineTop });
}, 30);
},
// 打开/关闭闪光灯
toggleFlash() {
this.setData({
flashMode: this.data.flashMode === 'torch' ? 'off' : 'torch',
});
},
/**
* 扫码结果
* @param {*} e
*/
handleScanCode(e) {
// 记录当前时间戳
const now = Date.now();
// 限制 2 秒内只能处理一次扫码
if (this.lastScanTime && now - this.lastScanTime < 2000) {
return;
}
this.lastScanTime = now;
// 双重锁机制确保不会重复处理
if (this.data.isScanning || this.scanLock) {
return;
}
this.setData({ isScanning: true });
this.scanLock = true;
console.log("扫码结果123-------", e);
// 处理扫码逻辑
this.triggerEvent('scancode', e.detail);
this.onCode(e.detail.result)
},
/**
* 点击跳转到我的二维码页面
* @param {*} e
*/
goCode(e) {
wx.showToast({ title: "跳转到我的二维码页面" });
wx.reLaunch({
url: '/subpackages/qr/qr-code/qr-code',
})
},
/**
* 手动选择照片后扫码
*/
chooseImageFirst() {
const that = this;
wx.chooseMedia({
count: 1,
mediaType: ['image'],
sourceType: ['album'],
maxDuration: 30,
camera: 'back',
fail(){
wx.showToast({ title: "请选择二维码" });
},
success(res) {
const src = res.tempFiles[0].tempFilePath
that.setData({
imgPath :src
})
wx.showLoading({
title: '识别中...',
})
console.log("图片地址",src);
wx.getImageInfo({
src: src,
success(res) {
console.log("图片信息",res);
// 转Canvas像素数据
const canvas = wx.createOffscreenCanvas({ type: '2d', width: res.width, height: res.height });
const canvasCtx = canvas.getContext('2d');
const img = canvas.createImage();
img.onload = () => {
canvasCtx.drawImage(img, 0, 0, res.width, res.height);
const imageData = canvasCtx.getImageData(0, 0, res.width, res.height);
// jsQR识别
const code = jsQR(imageData.data, res.width, res.height);
console.log('识别结果', code);
if(code){
wx.hideLoading()
that.onCode(code.data)
}else{
wx.showToast({ title: "未识别到二维码" });
}
};
img.src = src;
}
})
}
})
},
/**
* 扫码后获得的二维码内容
* @param {*} data
*/
onCode(data){
try {
console.log("扫码结果:", data);
if(!data || !data.startsWith('FINDME:')){
wx.showToast({
title: '无效的二维码这不是FindMe的用户二维码',
icon:"none"
})
setTimeout(()=>this.setData({imgPath : "" })
,1500)
this.setData({isScanning:false});
this.scanLock = false; // 同步释放全局锁
return
}
const customId = data.split(':')[1]
console.log('dataJson',customId);
this.handleCode(customId)
} catch (error) {
wx.showToast({ title: "无法解析二维码内容"});
console.log("无法解析二维码内容",error);
}finally{
this.setData({isScanning:false});
this.scanLock = false; // 同步释放全局锁
}
},
async handleCode(customId){
let userInfo = wx.getStorageSync('userInfo');
if(customId == userInfo.customId){
wx.showToast({
title: '不能添加自己为好友',
icon:'none'
})
setTimeout(()=>this.setData({imgPath : "" })
,1500)
this.setData({isScanning:false});
this.scanLock = false; // 同步释放全局锁
return
}
// 检查是否是好友关系
await this.checkFriendRelationFromScan(customId);
},
// 检查好友关系(从扫码调用)
async checkFriendRelationFromScan(customId) {
const friendAPI = require('../../../utils/friend-api.js');
wx.showLoading({ title: '加载中...', mask: true });
const friendDetailResponse = await friendAPI.getFriendDetail(customId).catch(() => null);
wx.hideLoading();
const isFriend = friendDetailResponse?.code === 0 && friendDetailResponse?.data;
this.navigateToUserDetail(customId, isFriend);
},
// 跳转到用户详情页面
navigateToUserDetail(customId, isFriend = false) {
const url = isFriend
? `/subpackages/social/friend-detail/friend-detail?customId=${customId}`
: `/subpackages/social/user-preview/user-preview?customId=${customId}`;
wx.navigateTo({
url: url,
success: () => {
this.setData({ imgPath: "" });
},
fail: (err) => {
console.error('跳转失败:', err);
wx.showToast({ title: '跳转失败,请重试', icon: 'none' });
this.setData({ imgPath: "",isScanning: false});
this.scanLock = false; // 同步释放全局锁
}
});
},
onUnload() {
// 页面卸载时停止扫码和动画
if (this.animationInterval) {
clearInterval(this.animationInterval);
}
},
// 授权回调
handleGetUserInfo(e) {
if (e.detail.userInfo) {
// 授权成功后重新初始化
this.onLoad();
} else {
wx.showToast({ title: '需要授权相机权限才能使用扫码功能', icon: 'none' });
}
}
});

View file

@ -0,0 +1,3 @@
{
"usingComponents": {}
}

View file

@ -0,0 +1,52 @@
<!-- 扫码容器 -->
<view class="scan-container">
<!-- 相机组件 -->
<camera class="camera" mode="scanCode" device-position="{{devicePosition}}" flash="{{flashMode}}" binderror="handleCameraError" bindscancode="handleScanCode"></camera>
<view class="scan-box off" wx:if="{{!!(imgPath)}}">
<image class="tempFilePath scan-box"style="width:{{scanBoxWidth}}rpx;height:{{scanBoxHeight}}rpx;left:{{scanBoxLeft}}rpx;top:{{scanBoxTop}}rpx;" src="{{imgPath}}" mode="aspectFill" />
</view>
<!-- 扫码框 -->
<view class="scan-box"style="width:{{scanBoxWidth}}rpx;height:{{scanBoxHeight}}rpx;left:{{scanBoxLeft}}rpx;top:{{scanBoxTop}}rpx;">
<!-- 扫码框边框 -->
<view class="scan-box-border">
</view>
<!-- 扫描线 -->
<view class="scan-line"style="top:{{scanLineTop}}rpx;"></view>
<!-- 角落标记 -->
<view class="scan-corner top-left"></view>
<view class="scan-corner top-right"></view>
<view class="scan-corner bottom-left"></view>
<view class="scan-corner bottom-right"></view>
</view>
<view class="action-buttons">
<!-- 保存二维码按钮 -->
<view bindtap="goCode" class="action-btn-out">
<!-- 下载图标 -->
<view class="action-btn">
<image class="action-icon" src="../../../images/qr-code.svg" mode="aspectFit"></image>
</view>
<text class="action-icon-text" >二维码</text>
</view>
<!-- 扫描二维码按钮 -->
<view class="action-btn-out" >
<view class="action-btn" bindtap="chooseImageFirst" >
<!-- 扫描图标 -->
<image class="action-icon" src="../../../images/Album.svg" mode="aspectFit"></image>
</view>
<text class="action-icon-text" >相册</text>
</view>
<!-- 分享二维码按钮 -->
<view class="action-btn-out">
<view class="action-btn" bindtap="toggleFlash" >
<!-- 分享图标 -->
<image class="action-icon" src="../../../images/flashlight.svg" mode="aspectFit"></image>
</view>
<text class="action-icon-text" >手电筒</text>
</view>
</view>
</view>

View file

@ -0,0 +1,252 @@
/* 扫码容器 */
.scan-container {
position: relative;
width: 100vw;
height: 100vh;
background-color: #000;
position: absolute;
}
/* 相机组件 */
.camera {
width: 100%;
height: 100%;
}
/* 扫码框 */
.scan-box {
position: absolute;
display: flex;
flex-direction: column;
align-items: center;
}
.tempFilePath{
width: 100%;
height: 100%;
opacity: 50%;
}
/* 扫码框边框(半透明) */
.scan-box-border {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
/* border: 1px dashed rgba(0, 255, 0, 0.15); */
box-sizing: border-box;
/* border: 1px solid red; */
}
/* 扫描线 */
.scan-line {
position: absolute;
width: 100%;
height: 2rpx;
background: linear-gradient(90deg, transparent, #00ffaa, transparent);
/* background-color: #00ffaa; */
border-radius: 100%;
/* box-shadow: 100rpx 100rpx 1px 2px #0f0; */
}
/* 角落标记 */
.scan-corner {
position: absolute;
width: 20rpx;
height: 20rpx;
border: 3px solid rgba(255, 255, 255, 1);
opacity:0;
box-sizing: border-box;
}
.scan-corner.top-left {
top: 0;
left: 0;
border-right: none;
border-bottom: none;
}
.scan-corner.top-right {
top: 0;
right: 0;
border-left: none;
border-bottom: none;
}
.scan-corner.bottom-left {
bottom: 0;
left: 0;
border-right: none;
border-top: none;
}
.scan-corner.bottom-right {
bottom: 0;
right: 0;
border-left: none;
border-top: none;
}
/* 提示文字 */
.scan-tip {
position: absolute;
bottom: 200rpx;
width: 100%;
text-align: center;
color: #fff;
font-size: 28rpx;
text-shadow: 0 0 5px rgba(0,0,0,0.8);
}
/* 功能按钮区 */
.scan-controls {
position: absolute;
bottom: 60rpx;
width: 100%;
display: flex;
justify-content: space-around;
}
.control-btn {
display: flex;
flex-direction: column;
align-items: center;
color: #fff;
background: transparent;
border: none;
padding: 0;
margin: 0;
border: 1px solid red;
}
.btn-icon {
width: 50rpx;
height: 50rpx;
}
.btn-text {
font-size: 24rpx;
}
/* 结果弹窗 */
.result-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.7);
display: flex;
justify-content: center;
align-items: center;
z-index: 100;
}
.result-content {
width: 600rpx;
background-color: #fff;
border-radius: 20rpx;
padding: 40rpx;
box-sizing: border-box;
}
.result-title {
font-size: 32rpx;
font-weight: bold;
text-align: center;
margin-bottom: 30rpx;
color: #333;
}
.result-text {
font-size: 28rpx;
color: #666;
padding: 20rpx;
border: 1px solid #eee;
border-radius: 10rpx;
margin-bottom: 30rpx;
word-break: break-all;
max-height: 300rpx;
overflow-y: auto;
}
.result-btns {
display: flex;
justify-content: space-between;
}
.result-btn {
width: 240rpx;
height: 80rpx;
line-height: 80rpx;
font-size: 28rpx;
border-radius: 40rpx;
}
.copy-btn {
background-color: #07c160;
color: #fff;
}
.rescan-btn {
background-color: #f5f5f5;
color: #333;
}
/* 底部操作按钮区域样式 */
.action-buttons {
display: flex;
justify-content: space-around;
width: 100%;
/* padding: 0 60rpx; */
position: fixed;
bottom: 50rpx;
left: 0;
color: white;
font-size: 28rpx;
}
.action-btn-out{
/* border: 1px solid red; */
width: 120rpx;
height: 180rpx;
display: flex;
justify-content: space-between;
align-items: center;
flex-direction: column;
}
.action-btn {
width: 120rpx;
height: 120rpx;
background-color: #0a0a0a;
border-radius: 24rpx;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 1),
-3rpx -3rpx 2rpx rgba(255, 255, 255, 0.3),
inset -3rpx -3rpx 2rpx rgba(255, 255, 255, 0.3);
/* border-top-right-radius:; */
}
.action-icon {
width: 70rpx;
height:70rpx;
opacity: 0.9;
}
.imgPath{
height: 80%;
border: 1px solid red;
}
.off {
width: 100vw;
height: 100vh;
left: 0rpx;
top: 0rpx;
/* border: 1px solid rgb(0, 255, 0); */
background-color: rgba(0, 0, 0, 1);
}