第一次提交:微信小程序前端最新完整代码

This commit is contained in:
XingQue
2026-06-12 21:55:06 +08:00
commit 84be8489b4
330 changed files with 74263 additions and 0 deletions

View File

@@ -0,0 +1,356 @@
// pages/zuzhanghaibao/zuzhanghaibao.js
import request from '../../utils/request.js';
const app = getApp();
Page({
data: {
qrcodeUrl: '', // 二维码完整URL
yaoqingma: '', // 组长邀请码字符串
isLoading: false,
showPosterCanvas: false,
posterImageTemp: '', // 生成的海报临时路径
avatarUrl: '', // 用户头像完整URL
uid: '',
nickName: '',
},
onLoad() {
this.loadFromCache();
this.loadUserInfo();
},
onShow() {
this.registerNotificationComponent();
},
registerNotificationComponent() {
const notificationComp = this.selectComponent('#global-notification');
if (notificationComp && notificationComp.showNotification) {
app.globalData.globalNotification = {
show: (data) => notificationComp.showNotification(data),
hide: () => notificationComp.hideNotification()
};
}
},
/**
* 从缓存加载二维码URL和邀请码
* 缓存键名使用独特拼音,避免与管事页面冲突
*/
loadFromCache() {
try {
const relativeUrl = wx.getStorageSync('zuzhang_haibao_url');
if (relativeUrl) {
const fullUrl = this.getFullImageUrl(relativeUrl);
this.setData({ qrcodeUrl: fullUrl });
}
const yaoqingma = wx.getStorageSync('zuzhang_yaoqingma');
if (yaoqingma) {
this.setData({ yaoqingma });
}
} catch (error) {
console.error('读取缓存失败', error);
}
},
/**
* 加载用户头像、昵称、UID
*/
loadUserInfo() {
const touxiang = wx.getStorageSync('touxiang');
const ossUrl = app.globalData.ossImageUrl || '';
const defaultAvatar = ossUrl + (app.globalData.morentouxiang || 'avatar/default.jpg');
let avatarUrl = defaultAvatar;
if (touxiang && typeof touxiang === 'string' && touxiang.trim() !== '') {
avatarUrl = this.getFullImageUrl(touxiang);
}
const uid = wx.getStorageSync('uid') || '';
// 组长昵称可能从全局变量或缓存获取
let nickName = app.globalData.nicheng || wx.getStorageSync('nicheng') || '组长';
this.setData({ avatarUrl, uid, nickName });
},
/**
* 拼接完整图片URL
*/
getFullImageUrl(url) {
if (!url) return '';
const ossUrl = app.globalData.ossImageUrl || '';
return url.startsWith('http') ? url : ossUrl + url;
},
/**
* 刷新/获取二维码和邀请码
* 调用 /peizhi/zuzhanghb 接口
*/
refreshQRCode() {
if (this.data.isLoading) return;
this.setData({ isLoading: true });
request({
url: '/peizhi/zuzhanghb',
method: 'POST',
})
.then(res => {
if (res.data && res.data.code === 0) {
const { url: relativeUrl, yaoqingma } = res.data.data; // 假设返回字段为 url 和 yaoqingma
if (relativeUrl) {
wx.setStorageSync('zuzhang_haibao_url', relativeUrl);
const fullUrl = this.getFullImageUrl(relativeUrl);
this.setData({ qrcodeUrl: fullUrl });
}
if (yaoqingma) {
wx.setStorageSync('zuzhang_yaoqingma', yaoqingma);
this.setData({ yaoqingma });
}
wx.showToast({ title: '获取成功', icon: 'success' });
} else {
wx.showToast({ title: res.data.msg || '获取失败', icon: 'none' });
}
})
.catch(err => {
console.error('获取组长海报失败', err);
wx.showToast({ title: '网络错误', icon: 'none' });
})
.finally(() => {
this.setData({ isLoading: false });
});
},
/**
* 保存二维码到相册(先下载网络图片)
*/
saveToAlbum() {
if (!this.data.qrcodeUrl) {
wx.showToast({ title: '暂无二维码', icon: 'none' });
return;
}
wx.showLoading({ title: '保存中...', mask: true });
wx.downloadFile({
url: this.data.qrcodeUrl,
success: (res) => {
wx.hideLoading();
if (res.statusCode === 200) {
this.downloadAndSave(res.tempFilePath);
} else {
wx.showToast({ title: '下载失败', icon: 'none' });
}
},
fail: (err) => {
wx.hideLoading();
console.error('下载二维码失败', err);
wx.showToast({ title: '下载失败', icon: 'none' });
}
});
},
/**
* 复制邀请码到剪贴板
*/
copyInviteCode() {
if (!this.data.yaoqingma) {
wx.showToast({ title: '暂无邀请码', icon: 'none' });
return;
}
wx.setClipboardData({
data: this.data.yaoqingma,
success: () => {
wx.showToast({ title: '复制成功', icon: 'success' });
},
fail: () => {
wx.showToast({ title: '复制失败', icon: 'none' });
}
});
},
/**
* 生成专属海报(包含头像、二维码、昵称、标语)
*/
generateMyPoster() {
if (!this.data.qrcodeUrl) {
wx.showToast({ title: '请先生成二维码', icon: 'none' });
return;
}
wx.showLoading({ title: '生成海报中...', mask: true });
const ossUrl = app.globalData.ossImageUrl || '';
// 🔥 组长海报背景图路径beijing/zuzhangbeijing.jpg
const bgUrl = ossUrl + 'beijing/zuzhangbeijing.jpg';
const urls = [bgUrl, this.data.qrcodeUrl, this.data.avatarUrl];
// 并行加载所有图片
const promises = urls.map(url => this.loadImage(url));
Promise.all(promises)
.then((images) => {
wx.hideLoading();
this.setData({ showPosterCanvas: true }, () => {
// 确保 canvas 已渲染
setTimeout(() => {
this.drawPoster(images[0], images[1], images[2]);
}, 200);
});
})
.catch((err) => {
wx.hideLoading();
wx.showToast({ title: '图片加载失败', icon: 'none' });
console.error('图片加载失败', err);
});
},
/**
* 加载图片为本地路径(用于 canvas 绘制)
*/
loadImage(url) {
return new Promise((resolve, reject) => {
wx.getImageInfo({
src: url,
success: (res) => resolve(res.path),
fail: reject
});
});
},
/**
* 绘制海报
* @param {string} bgPath 背景图本地路径
* @param {string} qrPath 二维码本地路径
* @param {string} avatarPath 头像本地路径
*/
drawPoster(bgPath, qrPath, avatarPath) {
const ctx = wx.createCanvasContext('posterCanvas', this);
const query = wx.createSelectorQuery().in(this);
query.select('.poster-canvas').boundingClientRect(rect => {
if (!rect) {
wx.showToast({ title: '画布获取失败', icon: 'none' });
return;
}
const canvasWidth = rect.width;
const canvasHeight = rect.height;
// 1. 绘制背景
ctx.drawImage(bgPath, 0, 0, canvasWidth, canvasHeight);
// 🔥【位置调整】二维码向右上角移动一丢丢 (增加 margin 从 5 到 15)
const qrSize = 100;
const margin = 15; // 原来 5现在 15向右下各移一点但看起来更协调
const qrX = canvasWidth - qrSize - margin;
const qrY = canvasHeight - qrSize - margin;
ctx.drawImage(qrPath, qrX, qrY, qrSize, qrSize);
// 🔥【位置调整】头像也向右上移动一丢丢 (原来 margin 5现在 margin 15)
const avatarSize = 60;
const avatarMargin = 15;
const avatarX = avatarMargin + 10; // 原来 margin 5+20现在 15+20相当于右移10
const avatarY = canvasHeight - avatarSize - avatarMargin;
// 圆形头像裁剪
ctx.save();
ctx.beginPath();
ctx.arc(avatarX + avatarSize/2, avatarY + avatarSize/2, avatarSize/2, 0, 2 * Math.PI);
ctx.clip();
ctx.drawImage(avatarPath, avatarX, avatarY, avatarSize, avatarSize);
ctx.restore();
// 绘制昵称(右侧)
ctx.setFontSize(16);
ctx.setFillStyle('#333333'); // 根据背景图颜色可调整
ctx.setTextAlign('left');
let nick = this.data.nickName || '组长';
if (nick.length > 3) {
nick = nick.substring(0, 3) + '...';
}
const nameText = nick;
const textX = avatarX + avatarSize + 8;
const textY = avatarY + avatarSize/2 + 6;
ctx.fillText(nameText, textX, textY);
// 绘制邀请语
ctx.setFontSize(18);
ctx.setFillStyle('#ffaa00'); // 金黄色,呼应组长主题
ctx.setTextAlign('left');
const slogan = '加入我的团队 ✦';
const sloganX = avatarX;
const sloganY = avatarY - 12;
ctx.fillText(slogan, sloganX, sloganY);
// 异步绘制
ctx.draw(false, () => {
wx.canvasToTempFilePath({
canvasId: 'posterCanvas',
success: (res) => {
this.setData({ posterImageTemp: res.tempFilePath });
},
fail: (err) => {
console.error('生成海报临时文件失败', err);
}
}, this);
});
}).exec();
},
/**
* 保存生成的海报
*/
savePoster() {
if (!this.data.posterImageTemp) {
wx.showToast({ title: '海报尚未生成', icon: 'none' });
return;
}
this.downloadAndSave(this.data.posterImageTemp);
},
/**
* 隐藏海报画布,返回主界面
*/
hidePoster() {
this.setData({ showPosterCanvas: false, posterImageTemp: '' });
},
/**
* 通用保存图片到相册(带权限检查)
*/
downloadAndSave(filePath) {
wx.getSetting({
success: (res) => {
if (!res.authSetting['scope.writePhotosAlbum']) {
wx.authorize({
scope: 'scope.writePhotosAlbum',
success: () => {
this._saveImage(filePath);
},
fail: () => {
wx.showModal({
title: '提示',
content: '需要您授权保存到相册',
success: (modalRes) => {
if (modalRes.confirm) {
wx.openSetting();
}
}
});
}
});
} else {
this._saveImage(filePath);
}
}
});
},
_saveImage(filePath) {
wx.saveImageToPhotosAlbum({
filePath: filePath,
success: () => {
wx.showToast({ title: '保存成功', icon: 'success' });
},
fail: (err) => {
console.error('保存失败', err);
wx.showToast({ title: '保存失败', icon: 'none' });
}
});
},
});

View File

@@ -0,0 +1,8 @@
{
"usingComponents": {
"global-notification": "/components/global-notification/global-notification"
},
"navigationBarTitleText": "组长海报",
"navigationBarBackgroundColor": "#0a0e17",
"navigationBarTextStyle": "white"
}

View File

@@ -0,0 +1,82 @@
<!-- pages/zuzhanghaibao/zuzhanghaibao.wxml - 组长推广海报(金黄色机甲风格) -->
<view class="page-container">
<!-- 背景网格 -->
<view class="grid-bg"></view>
<view class="mech-line line-h"></view>
<view class="mech-line line-v"></view>
<view class="content">
<!-- 标题区域 -->
<view class="title-section">
<text class="title">⚡ 组长专属海报 ⚡</text>
<text class="subtitle">邀请管事加入团队,自动填充您的邀请码</text>
</view>
<!-- 二维码展示区域 -->
<view class="qrcode-section">
<view class="qrcode-wrapper" wx:if="{{qrcodeUrl}}">
<image class="qrcode-img" src="{{qrcodeUrl}}" mode="widthFix" />
<view class="qrcode-glow"></view>
</view>
<view class="empty-qrcode" wx:else>
<text class="empty-icon">⚡</text>
<text class="empty-text">点击下方按钮生成您的专属二维码</text>
</view>
</view>
<!-- 🔥【新增】邀请码展示与复制区域 -->
<view class="invite-code-section" wx:if="{{yaoqingma}}">
<text class="code-label">邀请码:</text>
<text class="code-value">{{yaoqingma}}</text>
<view class="copy-btn" bindtap="copyInviteCode">
<text class="copy-icon">📋</text>
<text class="copy-text">复制</text>
</view>
</view>
<!-- 按钮区域(第一行) -->
<view class="button-section">
<view class="btn-group">
<view class="btn save-btn" bindtap="saveToAlbum" wx:if="{{qrcodeUrl}}">
<text class="btn-icon">📥</text>
<text class="btn-text">保存二维码</text>
</view>
<view class="btn refresh-btn" bindtap="refreshQRCode" wx:if="{{!isLoading}}">
<text class="btn-icon">⟳</text>
<text class="btn-text">{{qrcodeUrl ? '重新获取' : '获取二维码'}}</text>
</view>
<view class="btn loading-btn" wx:else>
<view class="loading-spinner-small"></view>
<text class="btn-text">生成中...</text>
</view>
<!-- 生成专属海报按钮(需要二维码存在) -->
<view class="btn poster-btn" bindtap="generateMyPoster" wx:if="{{qrcodeUrl && !showPosterCanvas}}">
<text class="btn-icon">🎨</text>
<text class="btn-text">生成专属海报</text>
</view>
</view>
</view>
<!-- 专属海报展示区 -->
<view class="poster-section" wx:if="{{showPosterCanvas}}">
<canvas canvas-id="posterCanvas" class="poster-canvas"></canvas>
<view class="poster-actions">
<view class="btn save-poster-btn" bindtap="savePoster">
<text class="btn-icon">📥</text>
<text class="btn-text">保存海报</text>
</view>
<view class="btn back-btn" bindtap="hidePoster">
<text class="btn-icon">↩️</text>
<text class="btn-text">返回</text>
</view>
</view>
</view>
<!-- 使用说明 -->
<view class="instruction">
<text class="instruction-text">* 管事扫码后自动进入注册页面,邀请码将自动填充</text>
</view>
</view>
<global-notification id="global-notification" />
</view>

View File

@@ -0,0 +1,315 @@
/* pages/zuzhanghaibao/zuzhanghaibao.wxss - 金黄色机甲风格 */
page {
background: #0a0e17; /* 深色背景,与管事页面一致,但高亮色改为金黄色 */
min-height: 100vh;
font-family: 'Avenir', 'PingFang SC', 'Helvetica Neue', sans-serif;
color: #fff;
}
/* 背景网格(颜色调为金黄色调) */
.grid-bg {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
linear-gradient(rgba(255, 200, 0, 0.05) 1px, transparent 1px),
linear-gradient(90deg, rgba(255, 200, 0, 0.05) 1px, transparent 1px);
background-size: 50rpx 50rpx;
pointer-events: none;
z-index: 0;
}
.page-container {
position: relative;
z-index: 2;
min-height: 100vh;
padding: 30rpx 25rpx;
box-sizing: border-box;
}
/* 机械装饰线条(金黄色) */
.mech-line {
position: absolute;
pointer-events: none;
z-index: 1;
}
.line-h {
top: 200rpx;
left: 0;
width: 100%;
height: 2rpx;
background: linear-gradient(90deg, transparent, #ffaa00, transparent);
}
.line-v {
right: 50rpx;
top: 100rpx;
width: 2rpx;
height: 300rpx;
background: linear-gradient(180deg, transparent, #ffaa00, transparent);
}
.content {
position: relative;
z-index: 3;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
padding: 40rpx 0;
}
/* 标题区域(金黄色光效) */
.title-section {
text-align: center;
margin-bottom: 60rpx;
}
.title {
font-size: 44rpx;
font-weight: bold;
color: #fff;
text-shadow: 0 0 30rpx #ffaa00;
letter-spacing: 2rpx;
display: block;
margin-bottom: 20rpx;
}
.subtitle {
font-size: 28rpx;
color: #ffd966;
opacity: 0.9;
letter-spacing: 1rpx;
}
/* 二维码区域(金黄色边框) */
.qrcode-section {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 40rpx; /* 给邀请码留出空间 */
}
.qrcode-wrapper {
position: relative;
width: 400rpx;
height: 400rpx;
background: rgba(20, 20, 20, 0.8);
border: 4rpx solid #ffaa00;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 0 80rpx rgba(255, 170, 0, 0.4);
animation: qrcodePulse 2s infinite alternate;
}
.qrcode-img {
width: 90%;
height: 90%;
object-fit: contain;
}
.qrcode-glow {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: radial-gradient(circle at 30% 30%, rgba(255, 170, 0, 0.2), transparent 70%);
pointer-events: none;
}
.empty-qrcode {
width: 400rpx;
height: 400rpx;
background: rgba(20, 20, 20, 0.8);
border: 4rpx dashed #ffaa00;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.empty-icon {
font-size: 80rpx;
color: #ffaa00;
margin-bottom: 20rpx;
opacity: 0.5;
}
.empty-text {
font-size: 26rpx;
color: #ffd966;
text-align: center;
padding: 0 40rpx;
}
/* 邀请码展示区域 */
.invite-code-section {
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.5);
padding: 20rpx 30rpx;
border-radius: 60rpx;
border: 2rpx solid #ffaa00;
margin-bottom: 30rpx;
width: 90%;
box-sizing: border-box;
}
.code-label {
font-size: 28rpx;
color: #ffd966;
margin-right: 10rpx;
}
.code-value {
font-size: 32rpx;
color: #fff;
font-weight: bold;
letter-spacing: 2rpx;
flex: 1;
text-align: center;
}
.copy-btn {
display: flex;
align-items: center;
background: rgba(255, 170, 0, 0.2);
padding: 10rpx 20rpx;
border-radius: 40rpx;
margin-left: 20rpx;
border: 2rpx solid #ffaa00;
}
.copy-icon {
font-size: 28rpx;
color: #ffaa00;
margin-right: 6rpx;
}
.copy-text {
font-size: 24rpx;
color: #ffaa00;
}
.copy-btn:active {
background: rgba(255, 170, 0, 0.4);
transform: scale(0.96);
}
/* 按钮区域(金黄色斜切风格) */
.button-section {
width: 100%;
display: flex;
justify-content: center;
margin-bottom: 40rpx;
}
.btn-group {
display: flex;
gap: 20rpx;
flex-wrap: wrap;
justify-content: center;
}
.btn {
width: 220rpx;
height: 80rpx;
background: rgba(20, 20, 20, 0.8);
border: 2rpx solid #ffaa00;
border-right: none;
border-bottom: none;
clip-path: polygon(0 0, 100% 0, 90% 100%, 0 100%);
display: flex;
align-items: center;
justify-content: center;
position: relative;
transition: 0.2s;
cursor: pointer;
}
.btn:active {
transform: scale(0.96);
box-shadow: 0 0 40rpx #ffaa00;
}
.save-btn {
border-color: #ffaa00;
}
.refresh-btn {
border-color: #ffaa00;
}
.refresh-btn .btn-icon {
color: #ffaa00;
}
.poster-btn {
border-color: #ffaa00;
}
.poster-btn .btn-icon {
color: #ffaa00;
}
.loading-btn {
opacity: 0.7;
pointer-events: none;
}
.btn-icon {
font-size: 36rpx;
margin-right: 8rpx;
color: #fff;
}
.btn-text {
font-size: 26rpx;
font-weight: 500;
color: #fff;
}
.loading-spinner-small {
width: 30rpx;
height: 30rpx;
border: 4rpx solid rgba(255,255,255,0.2);
border-top: 4rpx solid #ffaa00;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-right: 10rpx;
}
/* 专属海报展示区 */
.poster-section {
margin-top: 40rpx;
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
}
.poster-canvas {
width: 600rpx;
height: 900rpx;
background: #1a1f2e;
border: 4rpx solid #ffaa00;
box-shadow: 0 0 60rpx #ffaa00;
}
.poster-actions {
display: flex;
gap: 30rpx;
margin-top: 30rpx;
justify-content: center;
}
.save-poster-btn {
background: linear-gradient(45deg, #cc8400, #ffaa00);
border-color: #ffaa00;
}
.back-btn {
background: rgba(20, 20, 20, 0.8);
border-color: #ffaa00;
}
/* 使用说明 */
.instruction {
margin-top: 30rpx;
padding: 20rpx 40rpx;
background: rgba(20, 20, 20, 0.5);
border-left: 4rpx solid #ffaa00;
clip-path: polygon(0 0, 100% 0, 98% 100%, 0 100%);
}
.instruction-text {
font-size: 24rpx;
color: #ffd966;
line-height: 1.6;
}
/* 动画 */
@keyframes qrcodePulse {
0% { box-shadow: 0 0 40rpx rgba(255, 170, 0, 0.3); }
100% { box-shadow: 0 0 100rpx rgba(255, 170, 0, 0.6); }
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}