晚上邀请逻辑.
This commit is contained in:
parent
18b5304119
commit
3257210c39
|
|
@ -1,13 +1,55 @@
|
|||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const axios = require('axios');
|
||||
const { authenticateUser } = require('../middleware/auth');
|
||||
const User = require('../models/User');
|
||||
const env = require('../config/env');
|
||||
|
||||
// 缓存 access_token
|
||||
let accessTokenCache = {
|
||||
token: null,
|
||||
expiresAt: 0
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取微信 access_token
|
||||
*/
|
||||
async function getAccessToken() {
|
||||
const now = Date.now();
|
||||
|
||||
// 如果缓存有效,直接返回
|
||||
if (accessTokenCache.token && accessTokenCache.expiresAt > now) {
|
||||
return accessTokenCache.token;
|
||||
}
|
||||
|
||||
// 调用微信API获取 access_token
|
||||
const url = 'https://api.weixin.qq.com/cgi-bin/token';
|
||||
const response = await axios.get(url, {
|
||||
params: {
|
||||
grant_type: 'client_credential',
|
||||
appid: env.wechat.appId,
|
||||
secret: env.wechat.appSecret
|
||||
}
|
||||
});
|
||||
|
||||
if (response.data.errcode) {
|
||||
throw new Error(`获取access_token失败: ${response.data.errmsg}`);
|
||||
}
|
||||
|
||||
// 缓存 token,提前5分钟过期
|
||||
accessTokenCache = {
|
||||
token: response.data.access_token,
|
||||
expiresAt: now + (response.data.expires_in - 300) * 1000
|
||||
};
|
||||
|
||||
return accessTokenCache.token;
|
||||
}
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/qrcode/miniprogram:
|
||||
* post:
|
||||
* summary: 获取小程序码信息
|
||||
* summary: 获取小程序码
|
||||
* tags: [QRCode]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
|
|
@ -17,18 +59,17 @@ const User = require('../models/User');
|
|||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* path:
|
||||
* type: string
|
||||
* width:
|
||||
* type: number
|
||||
* description: 二维码宽度,默认430
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 成功
|
||||
* description: 成功返回小程序码图片的base64
|
||||
*/
|
||||
router.post('/miniprogram', authenticateUser, async (req, res) => {
|
||||
try {
|
||||
const userId = req.user.id;
|
||||
const { path, width } = req.body;
|
||||
const { width = 430 } = req.body;
|
||||
|
||||
// 获取用户邀请码
|
||||
const user = await User.findByPk(userId, {
|
||||
|
|
@ -37,8 +78,9 @@ router.post('/miniprogram', authenticateUser, async (req, res) => {
|
|||
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: { code: 'USER_NOT_FOUND', message: 'User not found' }
|
||||
code: 404,
|
||||
message: 'User not found',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -49,20 +91,52 @@ router.post('/miniprogram', authenticateUser, async (req, res) => {
|
|||
await user.update({ invitationCode });
|
||||
}
|
||||
|
||||
// 目前返回邀请码信息,前端可以用 canvas 绘制
|
||||
// 获取 access_token
|
||||
const accessToken = await getAccessToken();
|
||||
|
||||
// 调用微信API生成小程序码(使用 getUnlimited 接口,无数量限制)
|
||||
const wxUrl = `https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=${accessToken}`;
|
||||
const wxResponse = await axios.post(wxUrl, {
|
||||
scene: `inviteCode=${invitationCode}`, // scene 参数,最长32字符
|
||||
page: 'pages/index/index', // 必须是已发布的小程序页面
|
||||
width: width,
|
||||
auto_color: false,
|
||||
line_color: { r: 0, g: 0, b: 0 },
|
||||
is_hyaline: false
|
||||
}, {
|
||||
responseType: 'arraybuffer' // 返回的是图片二进制数据
|
||||
});
|
||||
|
||||
// 检查是否返回错误(错误时返回JSON)
|
||||
const contentType = wxResponse.headers['content-type'];
|
||||
if (contentType && contentType.includes('application/json')) {
|
||||
const errorData = JSON.parse(wxResponse.data.toString());
|
||||
console.error('微信小程序码生成失败:', errorData);
|
||||
return res.status(200).json({
|
||||
code: 500,
|
||||
message: errorData.errmsg || '生成小程序码失败',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
// 转换为 base64
|
||||
const base64Image = Buffer.from(wxResponse.data).toString('base64');
|
||||
const dataUrl = `data:image/png;base64,${base64Image}`;
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
code: 0,
|
||||
message: 'success',
|
||||
data: {
|
||||
invitationCode,
|
||||
path: path || `pages/index/index?inviteCode=${invitationCode}`,
|
||||
message: '请使用邀请码分享给好友'
|
||||
qrcodeUrl: dataUrl // base64格式的小程序码图片
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Get miniprogram qrcode error:', error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
error: { code: 'QRCODE_ERROR', message: error.message }
|
||||
return res.status(200).json({
|
||||
code: 500,
|
||||
message: error.message || '生成小程序码失败',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
|||
4
miniprogram/dist/dev/mp-weixin/app.js
vendored
4
miniprogram/dist/dev/mp-weixin/app.js
vendored
|
|
@ -44,8 +44,10 @@ const _sfc_main = {
|
|||
user: null,
|
||||
shouldRefresh: false,
|
||||
loginTime: 0,
|
||||
config: null
|
||||
config: null,
|
||||
// 应用配置(从后端获取)
|
||||
inviteCode: ""
|
||||
// 邀请码(从分享链接获取)
|
||||
},
|
||||
onLaunch: function() {
|
||||
console.log("App Launch");
|
||||
|
|
|
|||
1864
miniprogram/dist/dev/mp-weixin/common/vendor.js
vendored
1864
miniprogram/dist/dev/mp-weixin/common/vendor.js
vendored
File diff suppressed because it is too large
Load Diff
|
|
@ -185,9 +185,13 @@ AppServer.prototype.deleteData = async function(url) {
|
|||
return err;
|
||||
});
|
||||
};
|
||||
AppServer.prototype.WechatLogin = async function(code) {
|
||||
AppServer.prototype.WechatLogin = async function(code, invitationCode) {
|
||||
var url = serverConfig.apiUrl_Auth_WechatLogin;
|
||||
return this.postData(url, { code }).then((data) => {
|
||||
var postData = { code };
|
||||
if (invitationCode) {
|
||||
postData.invitationCode = invitationCode;
|
||||
}
|
||||
return this.postData(url, postData).then((data) => {
|
||||
return data;
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,363 +0,0 @@
|
|||
"use strict";
|
||||
const common_vendor = require("../../../../common/vendor.js");
|
||||
let qrcode;
|
||||
const _sfc_main = {
|
||||
name: "u-qrcode",
|
||||
props: {
|
||||
cid: {
|
||||
type: String,
|
||||
default: () => `u-qrcode-canvas${Math.floor(Math.random() * 1e6)}`
|
||||
},
|
||||
size: {
|
||||
type: Number,
|
||||
default: 200
|
||||
},
|
||||
unit: {
|
||||
type: String,
|
||||
default: "px"
|
||||
},
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
val: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
background: {
|
||||
type: String,
|
||||
default: "#ffffff"
|
||||
},
|
||||
foreground: {
|
||||
type: String,
|
||||
default: "#000000"
|
||||
},
|
||||
pdground: {
|
||||
type: String,
|
||||
default: "#000000"
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
iconSize: {
|
||||
type: Number,
|
||||
default: 40
|
||||
},
|
||||
lv: {
|
||||
type: Number,
|
||||
default: 3
|
||||
},
|
||||
onval: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
loadMake: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
usingComponents: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
showLoading: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
loadingText: {
|
||||
type: String,
|
||||
default: "生成中"
|
||||
},
|
||||
allowPreview: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否使用根节点宽高
|
||||
useRootHeightAndWidth: {
|
||||
type: Boolean,
|
||||
default: () => false
|
||||
}
|
||||
},
|
||||
emits: ["result", "longpressCallback"],
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
result: "",
|
||||
popupShow: false,
|
||||
list: [
|
||||
{
|
||||
name: "保存二维码"
|
||||
}
|
||||
],
|
||||
rootId: `rootId${Number(Math.random() * 100).toFixed(0)}`,
|
||||
ganvas: null,
|
||||
context: "",
|
||||
canvasObj: {},
|
||||
sizeLocal: this.size,
|
||||
ctx: null,
|
||||
// ctx 在new Qrcode 时js文件内部设置
|
||||
canvas: null
|
||||
// ctx 在new Qrcode 时js文件内部设置
|
||||
};
|
||||
},
|
||||
async mounted() {
|
||||
if (this.useRootHeightAndWidth) {
|
||||
await this.setNewSize();
|
||||
}
|
||||
if (this.loadMake) {
|
||||
if (!this._empty(this.val)) {
|
||||
setTimeout(() => {
|
||||
setTimeout(() => {
|
||||
this._makeCode();
|
||||
});
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
_makeCode() {
|
||||
let that2 = this;
|
||||
if (!this._empty(this.val)) {
|
||||
this.loading = true;
|
||||
qrcode = new common_vendor.QRCode({
|
||||
context: that2,
|
||||
// 上下文环境
|
||||
canvasId: that2.cid,
|
||||
// canvas-id
|
||||
nvueContext: that2.context,
|
||||
usingComponents: that2.usingComponents,
|
||||
// 是否是自定义组件
|
||||
showLoading: false,
|
||||
// 是否显示loading
|
||||
loadingText: that2.loadingText,
|
||||
// loading文字
|
||||
text: that2.val,
|
||||
// 生成内容
|
||||
size: that2.sizeLocal,
|
||||
// 二维码大小
|
||||
background: that2.background,
|
||||
// 背景色
|
||||
foreground: that2.foreground,
|
||||
// 前景色
|
||||
pdground: that2.pdground,
|
||||
// 定位角点颜色
|
||||
correctLevel: that2.lv,
|
||||
// 容错级别
|
||||
image: that2.icon,
|
||||
// 二维码图标
|
||||
imageSize: that2.iconSize,
|
||||
// 二维码图标大小
|
||||
cbResult: function(res) {
|
||||
that2._result(res);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
common_vendor.index.showToast({
|
||||
title: "二维码内容不能为空",
|
||||
icon: "none",
|
||||
duration: 2e3
|
||||
});
|
||||
}
|
||||
},
|
||||
_clearCode() {
|
||||
this._result("");
|
||||
qrcode.clear();
|
||||
},
|
||||
_saveCode() {
|
||||
let that2 = this;
|
||||
if (this.result != "") {
|
||||
common_vendor.index.saveImageToPhotosAlbum({
|
||||
filePath: that2.result,
|
||||
success: function() {
|
||||
common_vendor.index.showToast({
|
||||
title: "二维码保存成功",
|
||||
icon: "success",
|
||||
duration: 2e3
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
preview(e) {
|
||||
if (this.allowPreview) {
|
||||
common_vendor.index.previewImage({
|
||||
urls: [this.result],
|
||||
longPressActions: {
|
||||
itemList: ["保存二维码图片"],
|
||||
success: function(data) {
|
||||
switch (data.tapIndex) {
|
||||
case 0:
|
||||
that._saveCode();
|
||||
break;
|
||||
}
|
||||
},
|
||||
fail: function(err) {
|
||||
console.log(err.errMsg);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
this.$emit("preview", {
|
||||
url: this.result
|
||||
}, e);
|
||||
},
|
||||
async toTempFilePath({ success, fail }) {
|
||||
if (this.context) {
|
||||
this.ctx.toTempFilePath(
|
||||
0,
|
||||
0,
|
||||
this.sizeLocal,
|
||||
this.sizeLocal,
|
||||
this.sizeLocal,
|
||||
this.sizeLocal,
|
||||
"",
|
||||
1,
|
||||
(res) => {
|
||||
success(res);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
const canvas = await this.getNode(this.cid, true);
|
||||
common_vendor.index.canvasToTempFilePath(
|
||||
{
|
||||
canvas,
|
||||
success: (res) => {
|
||||
success(res);
|
||||
},
|
||||
fail
|
||||
},
|
||||
this
|
||||
);
|
||||
}
|
||||
},
|
||||
async longpress() {
|
||||
this.toTempFilePath({
|
||||
success: (res) => {
|
||||
this.$emit("longpressCallback", res.tempFilePath);
|
||||
},
|
||||
fail: (err) => {
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 使用根节点宽高 设置新的size
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async setNewSize() {
|
||||
const rootNode = await this.getNode(this.rootId, false);
|
||||
const { width, height } = rootNode;
|
||||
if (width > height) {
|
||||
this.sizeLocal = height;
|
||||
} else {
|
||||
this.sizeLocal = width;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 获取节点
|
||||
* @param id 节点id
|
||||
* @param isCanvas 是否为Canvas节点
|
||||
* @return {Promise<unknown>}
|
||||
*/
|
||||
async getNode(id, isCanvas) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const query = common_vendor.index.createSelectorQuery().in(this);
|
||||
query.select(`#${id}`).fields({ node: true, size: true }).exec((res) => {
|
||||
if (isCanvas) {
|
||||
resolve(res[0].node);
|
||||
} else {
|
||||
resolve(res[0]);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("获取节点失败", e);
|
||||
}
|
||||
});
|
||||
},
|
||||
selectClick(index) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
alert("保存二维码");
|
||||
this._saveCode();
|
||||
break;
|
||||
}
|
||||
},
|
||||
_result(res) {
|
||||
this.loading = false;
|
||||
this.result = res;
|
||||
this.$emit("result", res);
|
||||
},
|
||||
_empty(v) {
|
||||
let tp = typeof v, rt = false;
|
||||
if (tp == "number" && String(v) == "") {
|
||||
rt = true;
|
||||
} else if (tp == "undefined") {
|
||||
rt = true;
|
||||
} else if (tp == "object") {
|
||||
if (JSON.stringify(v) == "{}" || JSON.stringify(v) == "[]" || v == null)
|
||||
rt = true;
|
||||
} else if (tp == "string") {
|
||||
if (v == "" || v == "undefined" || v == "null" || v == "{}" || v == "[]")
|
||||
rt = true;
|
||||
} else if (tp == "function") {
|
||||
rt = false;
|
||||
}
|
||||
return rt;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
size: function(n, o) {
|
||||
if (n != o && !this._empty(n)) {
|
||||
this.cSize = n;
|
||||
if (!this._empty(this.val)) {
|
||||
setTimeout(() => {
|
||||
this._makeCode();
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
},
|
||||
val: function(n, o) {
|
||||
if (this.onval) {
|
||||
if (n != o && !this._empty(n)) {
|
||||
setTimeout(() => {
|
||||
this._makeCode();
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {}
|
||||
};
|
||||
if (!Array) {
|
||||
const _easycom_up_loading_icon2 = common_vendor.resolveComponent("up-loading-icon");
|
||||
_easycom_up_loading_icon2();
|
||||
}
|
||||
const _easycom_up_loading_icon = () => "../u-loading-icon/u-loading-icon.js";
|
||||
if (!Math) {
|
||||
_easycom_up_loading_icon();
|
||||
}
|
||||
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
|
||||
return common_vendor.e({
|
||||
a: $props.cid,
|
||||
b: $props.cid,
|
||||
c: $data.sizeLocal + $props.unit,
|
||||
d: $data.sizeLocal + $props.unit,
|
||||
e: $props.showLoading && $data.loading
|
||||
}, $props.showLoading && $data.loading ? {
|
||||
f: common_vendor.p({
|
||||
vertical: true,
|
||||
text: $props.loadingText,
|
||||
textSize: "14px"
|
||||
}),
|
||||
g: $data.sizeLocal + $props.unit,
|
||||
h: $data.sizeLocal + $props.unit
|
||||
} : {}, {
|
||||
i: common_vendor.o((...args) => $options.preview && $options.preview(...args)),
|
||||
j: $data.rootId,
|
||||
k: $props.useRootHeightAndWidth ? "100%" : "auto",
|
||||
l: $props.useRootHeightAndWidth ? "100%" : "auto",
|
||||
m: common_vendor.o((...args) => $options.longpress && $options.longpress(...args))
|
||||
});
|
||||
}
|
||||
const Component = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-444ebaa9"], ["__file", "F:/gitCode/uniapp/appointment_system/miniprogram/node_modules/uview-plus/components/u-qrcode/u-qrcode.vue"]]);
|
||||
wx.createComponent(Component);
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"component": true,
|
||||
"usingComponents": {
|
||||
"up-loading-icon": "../u-loading-icon/u-loading-icon"
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
<view class="u-qrcode data-v-444ebaa9" id="{{j}}" style="{{'width:' + k + ';' + ('height:' + l)}}" bindlongpress="{{m}}"><view class="u-qrcode__content data-v-444ebaa9" bindtap="{{i}}"><block wx:if="{{r0}}"><canvas class="u-qrcode__canvas data-v-444ebaa9" id="{{a}}" canvas-id="{{b}}" type="2d" style="{{'width:' + c + ';' + ('height:' + d)}}"/></block><view wx:if="{{e}}" class="u-qrcode__loading data-v-444ebaa9" style="{{'width:' + g + ';' + ('height:' + h)}}"><up-loading-icon wx:if="{{f}}" class="data-v-444ebaa9" u-i="444ebaa9-0" bind:__l="__l" u-p="{{f}}"></up-loading-icon></view></view></view>
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
/**
|
||||
* 这里是uni-app内置的常用样式变量
|
||||
*
|
||||
* uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量
|
||||
* 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
|
||||
*
|
||||
* 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
|
||||
*/
|
||||
/* 颜色变量 */
|
||||
/* 行为相关颜色 */
|
||||
/* 文字基本颜色 */
|
||||
/* 背景颜色 */
|
||||
/* 边框颜色 */
|
||||
/* 尺寸变量 */
|
||||
/* 文字尺寸 */
|
||||
/* 图片尺寸 */
|
||||
/* Border Radius */
|
||||
/* 水平间距 */
|
||||
/* 垂直间距 */
|
||||
/* 透明度 */
|
||||
/* 文章场景相关 */
|
||||
.u-qrcode__loading.data-v-444ebaa9 {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #f7f7f7;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
.u-qrcode__content.data-v-444ebaa9 {
|
||||
position: relative;
|
||||
}
|
||||
.u-qrcode__content__canvas.data-v-444ebaa9 {
|
||||
position: fixed;
|
||||
top: -99999rpx;
|
||||
left: -99999rpx;
|
||||
z-index: -99999;
|
||||
}
|
||||
|
|
@ -17,7 +17,33 @@ const _sfc_main = {
|
|||
// 应用名称
|
||||
};
|
||||
},
|
||||
onLoad() {
|
||||
onLoad(options) {
|
||||
let inviteCode = "";
|
||||
if (options && options.inviteCode) {
|
||||
inviteCode = options.inviteCode;
|
||||
}
|
||||
if (options && options.scene) {
|
||||
const scene = decodeURIComponent(options.scene);
|
||||
console.log("小程序码 scene:", scene);
|
||||
const params = {};
|
||||
scene.split("&").forEach((item) => {
|
||||
const [key, value] = item.split("=");
|
||||
if (key && value) {
|
||||
params[key] = value;
|
||||
}
|
||||
});
|
||||
if (params.inviteCode) {
|
||||
inviteCode = params.inviteCode;
|
||||
}
|
||||
}
|
||||
if (inviteCode) {
|
||||
console.log("收到邀请码:", inviteCode);
|
||||
const app = getApp();
|
||||
if (app && app.globalData) {
|
||||
app.globalData.inviteCode = inviteCode;
|
||||
}
|
||||
common_vendor.index.setStorageSync("inviteCode", inviteCode);
|
||||
}
|
||||
this.loadConfig();
|
||||
this.loadBanners();
|
||||
this.loadHotServices();
|
||||
|
|
|
|||
|
|
@ -47,8 +47,18 @@ const _sfc_main = {
|
|||
this.isLoading = false;
|
||||
return;
|
||||
}
|
||||
let invitationCode = "";
|
||||
const app = getApp();
|
||||
if (app && app.globalData && app.globalData.inviteCode) {
|
||||
invitationCode = app.globalData.inviteCode;
|
||||
} else {
|
||||
invitationCode = common_vendor.index.getStorageSync("inviteCode") || "";
|
||||
}
|
||||
if (invitationCode) {
|
||||
console.log("使用邀请码登录:", invitationCode);
|
||||
}
|
||||
const appserver = new modules_api_AppServer.AppServer();
|
||||
const data = await appserver.WechatLogin(loginRes.code);
|
||||
const data = await appserver.WechatLogin(loginRes.code, invitationCode);
|
||||
console.log("登录接口返回:", data);
|
||||
if (!data) {
|
||||
console.error("登录接口无响应");
|
||||
|
|
@ -80,6 +90,14 @@ const _sfc_main = {
|
|||
console.log("登录成功,保存认证信息");
|
||||
const token = "Bearer " + data.data.token;
|
||||
utils_auth.saveAuthData(token, data.data.refreshToken, data.data.user);
|
||||
if (invitationCode) {
|
||||
const app2 = getApp();
|
||||
if (app2 && app2.globalData) {
|
||||
app2.globalData.inviteCode = "";
|
||||
}
|
||||
common_vendor.index.removeStorageSync("inviteCode");
|
||||
console.log("邀请码已清除");
|
||||
}
|
||||
common_vendor.index.showToast({
|
||||
title: this.$t("login.loginSuccess") || "登录成功",
|
||||
icon: "success",
|
||||
|
|
|
|||
|
|
@ -16,6 +16,12 @@ const _sfc_main = {
|
|||
qrcodeDataUrl: "",
|
||||
qrcodeValue: "",
|
||||
// 二维码内容
|
||||
wxQrcodeImage: "",
|
||||
// 微信小程序码图片(base64)
|
||||
qrcodeLoading: false,
|
||||
// 小程序码加载状态
|
||||
qrcodeError: "",
|
||||
// 小程序码错误信息
|
||||
appLogo: "",
|
||||
// 应用logo,用于分享图片
|
||||
applyStep: 1,
|
||||
|
|
@ -124,7 +130,7 @@ const _sfc_main = {
|
|||
this.commissionStats = res.data;
|
||||
this.updateWithdrawDisplay();
|
||||
const inviteCode = res.data.invitationCode || "";
|
||||
this.qrcodeValue = `https://your-domain.com/invite?code=${inviteCode}`;
|
||||
this.qrcodeValue = `/pages/index/index?inviteCode=${inviteCode}`;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取佣金统计失败:", error);
|
||||
|
|
@ -205,21 +211,30 @@ const _sfc_main = {
|
|||
showCancel: false
|
||||
});
|
||||
},
|
||||
generateQRCode() {
|
||||
const inviteCode = this.commissionStats.invitationCode || "";
|
||||
this.qrcodeValue = `https://your-domain.com/invite?code=${inviteCode}`;
|
||||
this.showQRCodeContent = false;
|
||||
async generateQRCode() {
|
||||
if (this.wxQrcodeImage) {
|
||||
this.showQRCodeModal = true;
|
||||
return;
|
||||
}
|
||||
this.showQRCodeModal = true;
|
||||
},
|
||||
onQRCodePopupOpen() {
|
||||
setTimeout(() => {
|
||||
this.showQRCodeContent = true;
|
||||
}, 100);
|
||||
this.qrcodeLoading = true;
|
||||
this.qrcodeError = "";
|
||||
try {
|
||||
const res = await appServer.GetMiniProgramQRCode({ width: 430 });
|
||||
if (res && res.code === 0 && res.data && res.data.qrcodeUrl) {
|
||||
this.wxQrcodeImage = res.data.qrcodeUrl;
|
||||
} else {
|
||||
this.qrcodeError = (res == null ? void 0 : res.message) || "生成小程序码失败";
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("生成小程序码失败:", error);
|
||||
this.qrcodeError = "网络错误,请重试";
|
||||
} finally {
|
||||
this.qrcodeLoading = false;
|
||||
}
|
||||
},
|
||||
closeQRCodeModal() {
|
||||
this.showQRCodeContent = false;
|
||||
this.showQRCodeModal = false;
|
||||
this.qrcodeDataUrl = "";
|
||||
},
|
||||
shareQRCodeToFriend() {
|
||||
common_vendor.index.showToast({
|
||||
|
|
@ -228,52 +243,58 @@ const _sfc_main = {
|
|||
});
|
||||
},
|
||||
saveQRCodeImage() {
|
||||
if (this.$refs.uQrcode) {
|
||||
this.$refs.uQrcode.toTempFilePath({
|
||||
success: (res) => {
|
||||
common_vendor.index.saveImageToPhotosAlbum({
|
||||
filePath: res.tempFilePath,
|
||||
success: () => {
|
||||
common_vendor.index.showToast({
|
||||
title: this.$t("invite.saveSuccess") || "保存成功",
|
||||
icon: "success"
|
||||
});
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error("保存图片失败:", err);
|
||||
if (err.errMsg && err.errMsg.includes("auth deny")) {
|
||||
common_vendor.index.showModal({
|
||||
title: "提示",
|
||||
content: "需要您授权保存图片到相册",
|
||||
success: (modalRes) => {
|
||||
if (modalRes.confirm) {
|
||||
common_vendor.index.openSetting();
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
common_vendor.index.showToast({
|
||||
title: "保存失败",
|
||||
icon: "none"
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error("获取二维码图片失败:", err);
|
||||
common_vendor.index.showToast({
|
||||
title: "获取二维码失败",
|
||||
icon: "none"
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (!this.wxQrcodeImage) {
|
||||
common_vendor.index.showToast({
|
||||
title: "二维码生成中,请稍候",
|
||||
title: "小程序码加载中,请稍候",
|
||||
icon: "none"
|
||||
});
|
||||
return;
|
||||
}
|
||||
const base64Data = this.wxQrcodeImage.replace(/^data:image\/\w+;base64,/, "");
|
||||
const filePath = `${common_vendor.wx$1.env.USER_DATA_PATH}/qrcode_${Date.now()}.png`;
|
||||
const fs = common_vendor.index.getFileSystemManager();
|
||||
fs.writeFile({
|
||||
filePath,
|
||||
data: base64Data,
|
||||
encoding: "base64",
|
||||
success: () => {
|
||||
common_vendor.index.saveImageToPhotosAlbum({
|
||||
filePath,
|
||||
success: () => {
|
||||
common_vendor.index.showToast({
|
||||
title: this.$t("invite.saveSuccess") || "保存成功",
|
||||
icon: "success"
|
||||
});
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error("保存图片失败:", err);
|
||||
if (err.errMsg && err.errMsg.includes("auth deny")) {
|
||||
common_vendor.index.showModal({
|
||||
title: "提示",
|
||||
content: "需要您授权保存图片到相册",
|
||||
success: (modalRes) => {
|
||||
if (modalRes.confirm) {
|
||||
common_vendor.index.openSetting();
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
common_vendor.index.showToast({
|
||||
title: "保存失败",
|
||||
icon: "none"
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error("写入文件失败:", err);
|
||||
common_vendor.index.showToast({
|
||||
title: "保存失败",
|
||||
icon: "none"
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
shareToFriend() {
|
||||
common_vendor.index.share({
|
||||
|
|
@ -439,13 +460,11 @@ const _sfc_main = {
|
|||
};
|
||||
if (!Array) {
|
||||
const _easycom_up_popup2 = common_vendor.resolveComponent("up-popup");
|
||||
const _easycom_u_qrcode2 = common_vendor.resolveComponent("u-qrcode");
|
||||
(_easycom_up_popup2 + _easycom_u_qrcode2)();
|
||||
_easycom_up_popup2();
|
||||
}
|
||||
const _easycom_up_popup = () => "../../node-modules/uview-plus/components/u-popup/u-popup.js";
|
||||
const _easycom_u_qrcode = () => "../../node-modules/uview-plus/components/u-qrcode/u-qrcode.js";
|
||||
if (!Math) {
|
||||
(_easycom_up_popup + _easycom_u_qrcode)();
|
||||
_easycom_up_popup();
|
||||
}
|
||||
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
|
||||
return common_vendor.e({
|
||||
|
|
@ -521,22 +540,17 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
|
|||
bgColor: "#ffffff"
|
||||
}),
|
||||
N: common_vendor.t(_ctx.$t("invite.qrcodeTitle") || "邀请好友,赢现金"),
|
||||
O: $data.showQRCodeContent
|
||||
}, $data.showQRCodeContent ? {
|
||||
P: common_vendor.sr("uQrcode", "830ba560-2,830ba560-1"),
|
||||
Q: common_vendor.p({
|
||||
val: $data.qrcodeValue,
|
||||
size: 200,
|
||||
unit: "px",
|
||||
["load-make"]: true,
|
||||
["using-components"]: true
|
||||
})
|
||||
} : {}, {
|
||||
R: common_vendor.t(_ctx.$t("invite.shareToFriend") || "分享给好友"),
|
||||
S: common_vendor.t(_ctx.$t("invite.saveImage") || "保存图片"),
|
||||
T: common_vendor.o((...args) => $options.saveQRCodeImage && $options.saveQRCodeImage(...args)),
|
||||
U: common_vendor.o($options.closeQRCodeModal),
|
||||
V: common_vendor.o($options.onQRCodePopupOpen),
|
||||
O: $data.wxQrcodeImage
|
||||
}, $data.wxQrcodeImage ? {
|
||||
P: $data.wxQrcodeImage
|
||||
} : $data.qrcodeLoading ? {} : {
|
||||
R: common_vendor.t($data.qrcodeError || "加载失败")
|
||||
}, {
|
||||
Q: $data.qrcodeLoading,
|
||||
S: common_vendor.t(_ctx.$t("invite.shareToFriend") || "分享给好友"),
|
||||
T: common_vendor.t(_ctx.$t("invite.saveImage") || "保存图片"),
|
||||
U: common_vendor.o((...args) => $options.saveQRCodeImage && $options.saveQRCodeImage(...args)),
|
||||
V: common_vendor.o($options.closeQRCodeModal),
|
||||
W: common_vendor.p({
|
||||
show: $data.showQRCodeModal,
|
||||
mode: "center",
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
"navigationBarTitleText": "",
|
||||
"navigationStyle": "custom",
|
||||
"usingComponents": {
|
||||
"up-popup": "../../node-modules/uview-plus/components/u-popup/u-popup",
|
||||
"u-qrcode": "../../node-modules/uview-plus/components/u-qrcode/u-qrcode"
|
||||
"up-popup": "../../node-modules/uview-plus/components/u-popup/u-popup"
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -480,19 +480,23 @@
|
|||
margin-bottom: 30rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
overflow: hidden;
|
||||
}
|
||||
.qrcode-modal-content .qrcode-box.data-v-830ba560 .u-qrcode {
|
||||
position: static !important;
|
||||
.qrcode-modal-content .qrcode-box .wx-qrcode-image.data-v-830ba560 {
|
||||
width: 400rpx;
|
||||
height: 400rpx;
|
||||
}
|
||||
.qrcode-modal-content .qrcode-box .qrcode-loading.data-v-830ba560 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
.qrcode-modal-content .qrcode-box.data-v-830ba560 .u-qrcode__content {
|
||||
position: static !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
.qrcode-modal-content .qrcode-box .qrcode-error.data-v-830ba560 {
|
||||
color: #ff4d4f;
|
||||
font-size: 26rpx;
|
||||
text-align: center;
|
||||
}
|
||||
.qrcode-modal-content .qrcode-box.data-v-830ba560 .u-qrcode__canvas {
|
||||
position: static !important;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
"compileType": "miniprogram",
|
||||
"libVersion": "",
|
||||
"appid": "wx34e8a031d0d78b81",
|
||||
"projectname": "appointment_system",
|
||||
"projectname": "一起飞Go Mundo",
|
||||
"condition": {
|
||||
"search": {
|
||||
"current": -1,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@
|
|||
user: null,
|
||||
shouldRefresh: false,
|
||||
loginTime: 0,
|
||||
config: null // 应用配置(从后端获取)
|
||||
config: null, // 应用配置(从后端获取)
|
||||
inviteCode: '' // 邀请码(从分享链接获取)
|
||||
},
|
||||
onLaunch: function() {
|
||||
console.log('App Launch')
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name" : "appointment_system",
|
||||
"name" : "一起飞Go Mundo",
|
||||
"appid" : "__UNI__C6C43F9",
|
||||
"description" : "",
|
||||
"versionName" : "1.0.0",
|
||||
|
|
|
|||
|
|
@ -269,10 +269,15 @@ AppServer.prototype.deleteData = async function(url) {
|
|||
/**
|
||||
* 微信登录
|
||||
* @param {String} code - 微信登录code
|
||||
* @param {String} invitationCode - 邀请码(可选)
|
||||
*/
|
||||
AppServer.prototype.WechatLogin = async function(code) {
|
||||
AppServer.prototype.WechatLogin = async function(code, invitationCode) {
|
||||
var url = serverConfig.apiUrl_Auth_WechatLogin
|
||||
return this.postData(url, { code }).then((data) => {
|
||||
var postData = { code }
|
||||
if (invitationCode) {
|
||||
postData.invitationCode = invitationCode
|
||||
}
|
||||
return this.postData(url, postData).then((data) => {
|
||||
return data;
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,7 +70,45 @@
|
|||
appName: '' // 应用名称
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
onLoad(options) {
|
||||
// 处理邀请码参数
|
||||
let inviteCode = ''
|
||||
|
||||
// 方式1:普通分享链接,直接从 options.inviteCode 获取
|
||||
if (options && options.inviteCode) {
|
||||
inviteCode = options.inviteCode
|
||||
}
|
||||
|
||||
// 方式2:小程序码扫码进入,从 scene 参数解析
|
||||
// 小程序码的 scene 格式为 "inviteCode=XXXXXX"
|
||||
if (options && options.scene) {
|
||||
const scene = decodeURIComponent(options.scene)
|
||||
console.log('小程序码 scene:', scene)
|
||||
// 解析 scene 参数
|
||||
const params = {}
|
||||
scene.split('&').forEach(item => {
|
||||
const [key, value] = item.split('=')
|
||||
if (key && value) {
|
||||
params[key] = value
|
||||
}
|
||||
})
|
||||
if (params.inviteCode) {
|
||||
inviteCode = params.inviteCode
|
||||
}
|
||||
}
|
||||
|
||||
// 保存邀请码
|
||||
if (inviteCode) {
|
||||
console.log('收到邀请码:', inviteCode)
|
||||
// 保存邀请码到全局数据,登录时使用
|
||||
const app = getApp()
|
||||
if (app && app.globalData) {
|
||||
app.globalData.inviteCode = inviteCode
|
||||
}
|
||||
// 同时保存到本地存储,防止丢失
|
||||
uni.setStorageSync('inviteCode', inviteCode)
|
||||
}
|
||||
|
||||
// 不在首页检查登录状态,允许游客浏览
|
||||
this.loadConfig()
|
||||
this.loadBanners()
|
||||
|
|
|
|||
|
|
@ -104,9 +104,22 @@
|
|||
return
|
||||
}
|
||||
|
||||
// 调用后端登录接口
|
||||
// 获取邀请码(从全局数据或本地存储)
|
||||
let invitationCode = ''
|
||||
const app = getApp()
|
||||
if (app && app.globalData && app.globalData.inviteCode) {
|
||||
invitationCode = app.globalData.inviteCode
|
||||
} else {
|
||||
invitationCode = uni.getStorageSync('inviteCode') || ''
|
||||
}
|
||||
|
||||
if (invitationCode) {
|
||||
console.log('使用邀请码登录:', invitationCode)
|
||||
}
|
||||
|
||||
// 调用后端登录接口(带邀请码)
|
||||
const appserver = new AppServer();
|
||||
const data = await appserver.WechatLogin(loginRes.code);
|
||||
const data = await appserver.WechatLogin(loginRes.code, invitationCode);
|
||||
|
||||
console.log('登录接口返回:', data);
|
||||
|
||||
|
|
@ -148,6 +161,16 @@
|
|||
// 保存认证信息
|
||||
const token = "Bearer " + data.data.token
|
||||
saveAuthData(token, data.data.refreshToken, data.data.user)
|
||||
|
||||
// 清除邀请码(已使用)
|
||||
if (invitationCode) {
|
||||
const app = getApp()
|
||||
if (app && app.globalData) {
|
||||
app.globalData.inviteCode = ''
|
||||
}
|
||||
uni.removeStorageSync('inviteCode')
|
||||
console.log('邀请码已清除')
|
||||
}
|
||||
|
||||
// 登录成功
|
||||
uni.showToast({
|
||||
|
|
|
|||
179
miniprogram/src/pages/me/INVITE_SYSTEM.md
Normal file
179
miniprogram/src/pages/me/INVITE_SYSTEM.md
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
# 邀请新人系统文档
|
||||
|
||||
## 功能概述
|
||||
|
||||
用户可以通过分享邀请链接或小程序码邀请新用户注册,新用户注册后会自动绑定邀请关系,邀请人可获得佣金奖励。
|
||||
|
||||
## 完整流程
|
||||
|
||||
```
|
||||
邀请者分享 → 新用户点击/扫码 → 进入小程序 → 保存邀请码 → 登录注册 → 绑定邀请关系
|
||||
```
|
||||
|
||||
## 分享方式
|
||||
|
||||
### 1. 分享给微信好友
|
||||
- 入口:邀请奖励页面 → 分享给好友按钮
|
||||
- 实现:`onShareAppMessage()`
|
||||
- 链接格式:`/pages/index/index?inviteCode=XXXXXX`
|
||||
|
||||
### 2. 分享到朋友圈
|
||||
- 入口:邀请奖励页面 → 右上角分享
|
||||
- 实现:`onShareTimeline()`
|
||||
- 参数格式:`inviteCode=XXXXXX`
|
||||
|
||||
### 3. 小程序码
|
||||
- 入口:邀请奖励页面 → 生成二维码按钮
|
||||
- 实现:调用后端 `/api/v1/qrcode/miniprogram` 接口
|
||||
- 参数格式:`scene=inviteCode=XXXXXX`
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 前端文件
|
||||
|
||||
| 文件 | 功能 |
|
||||
|------|------|
|
||||
| `App.vue` | 定义 `globalData.inviteCode` 存储邀请码 |
|
||||
| `pages/index/index.vue` | 接收并保存邀请码 |
|
||||
| `pages/login/login-page.vue` | 登录时传递邀请码 |
|
||||
| `pages/me/invite-reward-page.vue` | 邀请奖励页面,分享功能 |
|
||||
| `modules/api/AppServer.js` | API调用,`WechatLogin(code, invitationCode)` |
|
||||
|
||||
### 后端文件
|
||||
|
||||
| 文件 | 功能 |
|
||||
|------|------|
|
||||
| `services/authService.js` | 微信登录,处理邀请码绑定 |
|
||||
| `routes/qrcodeRoutes.js` | 生成小程序码 |
|
||||
| `models/User.js` | 用户模型,`invitedBy` 字段 |
|
||||
| `models/Invitation.js` | 邀请关系模型 |
|
||||
|
||||
### 邀请码接收逻辑 (index.vue)
|
||||
|
||||
```javascript
|
||||
onLoad(options) {
|
||||
let inviteCode = ''
|
||||
|
||||
// 方式1:普通分享链接
|
||||
if (options && options.inviteCode) {
|
||||
inviteCode = options.inviteCode
|
||||
}
|
||||
|
||||
// 方式2:小程序码扫码(scene参数)
|
||||
if (options && options.scene) {
|
||||
const scene = decodeURIComponent(options.scene)
|
||||
const params = {}
|
||||
scene.split('&').forEach(item => {
|
||||
const [key, value] = item.split('=')
|
||||
if (key && value) params[key] = value
|
||||
})
|
||||
if (params.inviteCode) inviteCode = params.inviteCode
|
||||
}
|
||||
|
||||
// 保存邀请码
|
||||
if (inviteCode) {
|
||||
getApp().globalData.inviteCode = inviteCode
|
||||
uni.setStorageSync('inviteCode', inviteCode)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 登录绑定逻辑 (login-page.vue)
|
||||
|
||||
```javascript
|
||||
async handleWechatLogin() {
|
||||
// 获取邀请码
|
||||
let invitationCode = ''
|
||||
const app = getApp()
|
||||
if (app?.globalData?.inviteCode) {
|
||||
invitationCode = app.globalData.inviteCode
|
||||
} else {
|
||||
invitationCode = uni.getStorageSync('inviteCode') || ''
|
||||
}
|
||||
|
||||
// 调用登录接口(带邀请码)
|
||||
const data = await appserver.WechatLogin(loginRes.code, invitationCode)
|
||||
|
||||
// 登录成功后清除邀请码
|
||||
if (invitationCode) {
|
||||
app.globalData.inviteCode = ''
|
||||
uni.removeStorageSync('inviteCode')
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 后端绑定逻辑 (authService.js)
|
||||
|
||||
```javascript
|
||||
const wechatLogin = async (code, userInfo, invitationCode, deviceInfo) => {
|
||||
// ... 获取openId
|
||||
|
||||
let user = await User.findOne({ where: { wechatOpenId: openId } })
|
||||
|
||||
if (!user) {
|
||||
// 新用户注册
|
||||
const userData = {
|
||||
wechatOpenId: openId,
|
||||
invitationCode: await generateUniqueInvitationCode(),
|
||||
}
|
||||
|
||||
// 处理邀请关系
|
||||
if (invitationCode) {
|
||||
const inviter = await User.findOne({ where: { invitationCode } })
|
||||
if (inviter) {
|
||||
userData.invitedBy = inviter.id
|
||||
// 创建邀请记录
|
||||
await Invitation.create({
|
||||
inviterId: inviter.id,
|
||||
inviteeId: user.id,
|
||||
invitationCode,
|
||||
registeredAt: new Date(),
|
||||
rewardStatus: 'pending',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
user = await User.create(userData)
|
||||
}
|
||||
// ... 返回token
|
||||
}
|
||||
```
|
||||
|
||||
## 数据模型
|
||||
|
||||
### User 表
|
||||
- `invitationCode`: 用户的邀请码(唯一)
|
||||
- `invitedBy`: 邀请人ID
|
||||
|
||||
### Invitation 表
|
||||
- `inviterId`: 邀请人ID
|
||||
- `inviteeId`: 被邀请人ID
|
||||
- `invitationCode`: 使用的邀请码
|
||||
- `registeredAt`: 注册时间
|
||||
- `firstPaymentAt`: 首次付款时间
|
||||
- `rewardAmount`: 奖励金额
|
||||
- `rewardStatus`: 奖励状态 (pending/credited/cancelled)
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **小程序码限制**:`page` 参数必须是已发布的小程序页面
|
||||
2. **开发环境**:开发版/体验版扫码可能无法正常跳转
|
||||
3. **邀请码有效性**:只对新用户有效,老用户登录不会重复绑定
|
||||
4. **scene参数限制**:最长32字符,格式为 `inviteCode=XXXXXX`
|
||||
|
||||
## API接口
|
||||
|
||||
### 生成小程序码
|
||||
```
|
||||
POST /api/v1/qrcode/miniprogram
|
||||
Authorization: Bearer {token}
|
||||
Body: { width: 430 }
|
||||
Response: { code: 0, data: { invitationCode, qrcodeUrl } }
|
||||
```
|
||||
|
||||
### 微信登录(带邀请码)
|
||||
```
|
||||
POST /api/v1/auth/wechat-login
|
||||
Body: { code, invitationCode }
|
||||
Response: { code: 0, data: { token, user } }
|
||||
```
|
||||
|
|
@ -159,11 +159,15 @@
|
|||
</up-popup>
|
||||
|
||||
<!-- 二维码弹窗 -->
|
||||
<up-popup :show="showQRCodeModal" mode="center" :round="20" :overlay="true" :closeOnClickOverlay="true" bgColor="#ffffff" @close="closeQRCodeModal" @open="onQRCodePopupOpen">
|
||||
<up-popup :show="showQRCodeModal" mode="center" :round="20" :overlay="true" :closeOnClickOverlay="true" bgColor="#ffffff" @close="closeQRCodeModal">
|
||||
<view class="qrcode-modal-content">
|
||||
<text class="qrcode-modal-title">{{ $t('invite.qrcodeTitle') || '邀请好友,赢现金' }}</text>
|
||||
<view class="qrcode-box">
|
||||
<u-qrcode v-if="showQRCodeContent" ref="uQrcode" :val="qrcodeValue" :size="200" unit="px" :load-make="true" :using-components="true"></u-qrcode>
|
||||
<image v-if="wxQrcodeImage" :src="wxQrcodeImage" class="wx-qrcode-image" mode="aspectFit"></image>
|
||||
<view v-else-if="qrcodeLoading" class="qrcode-loading">
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
<text v-else class="qrcode-error">{{ qrcodeError || '加载失败' }}</text>
|
||||
</view>
|
||||
<view class="qrcode-actions">
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
|
|
@ -291,6 +295,9 @@
|
|||
showQRCodeContent: false, // 控制二维码内容显示
|
||||
qrcodeDataUrl: '',
|
||||
qrcodeValue: '', // 二维码内容
|
||||
wxQrcodeImage: '', // 微信小程序码图片(base64)
|
||||
qrcodeLoading: false, // 小程序码加载状态
|
||||
qrcodeError: '', // 小程序码错误信息
|
||||
appLogo: '', // 应用logo,用于分享图片
|
||||
applyStep: 1,
|
||||
withdrawAmount: '',
|
||||
|
|
@ -404,9 +411,9 @@
|
|||
this.commissionStats = res.data
|
||||
// 更新提现记录显示
|
||||
this.updateWithdrawDisplay()
|
||||
// 预先设置二维码内容,用于分享
|
||||
// 预先设置二维码内容,用于分享(使用小程序路径)
|
||||
const inviteCode = res.data.invitationCode || ''
|
||||
this.qrcodeValue = `https://your-domain.com/invite?code=${inviteCode}`
|
||||
this.qrcodeValue = `/pages/index/index?inviteCode=${inviteCode}`
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取佣金统计失败:', error)
|
||||
|
|
@ -491,26 +498,36 @@
|
|||
showCancel: false
|
||||
})
|
||||
},
|
||||
generateQRCode() {
|
||||
// 构建邀请链接,包含邀请码
|
||||
const inviteCode = this.commissionStats.invitationCode || ''
|
||||
// 设置二维码内容为邀请链接
|
||||
this.qrcodeValue = `https://your-domain.com/invite?code=${inviteCode}`
|
||||
// 先隐藏二维码内容
|
||||
this.showQRCodeContent = false
|
||||
// 打开二维码弹窗
|
||||
async generateQRCode() {
|
||||
// 如果已有小程序码,直接显示
|
||||
if (this.wxQrcodeImage) {
|
||||
this.showQRCodeModal = true
|
||||
return
|
||||
}
|
||||
|
||||
// 显示弹窗并开始加载
|
||||
this.showQRCodeModal = true
|
||||
},
|
||||
onQRCodePopupOpen() {
|
||||
// 弹窗打开后延迟显示二维码
|
||||
setTimeout(() => {
|
||||
this.showQRCodeContent = true
|
||||
}, 100)
|
||||
this.qrcodeLoading = true
|
||||
this.qrcodeError = ''
|
||||
|
||||
try {
|
||||
// 调用后端API获取微信小程序码
|
||||
const res = await appServer.GetMiniProgramQRCode({ width: 430 })
|
||||
|
||||
if (res && res.code === 0 && res.data && res.data.qrcodeUrl) {
|
||||
this.wxQrcodeImage = res.data.qrcodeUrl
|
||||
} else {
|
||||
this.qrcodeError = res?.message || '生成小程序码失败'
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('生成小程序码失败:', error)
|
||||
this.qrcodeError = '网络错误,请重试'
|
||||
} finally {
|
||||
this.qrcodeLoading = false
|
||||
}
|
||||
},
|
||||
closeQRCodeModal() {
|
||||
this.showQRCodeContent = false
|
||||
this.showQRCodeModal = false
|
||||
this.qrcodeDataUrl = ''
|
||||
},
|
||||
shareQRCodeToFriend() {
|
||||
// 分享给好友
|
||||
|
|
@ -524,53 +541,62 @@
|
|||
})
|
||||
},
|
||||
saveQRCodeImage() {
|
||||
// 使用 u-qrcode 组件的 toTempFilePath 方法获取图片并保存
|
||||
if (this.$refs.uQrcode) {
|
||||
this.$refs.uQrcode.toTempFilePath({
|
||||
success: (res) => {
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath: res.tempFilePath,
|
||||
success: () => {
|
||||
uni.showToast({
|
||||
title: this.$t('invite.saveSuccess') || '保存成功',
|
||||
icon: 'success'
|
||||
})
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('保存图片失败:', err)
|
||||
if (err.errMsg && err.errMsg.includes('auth deny')) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '需要您授权保存图片到相册',
|
||||
success: (modalRes) => {
|
||||
if (modalRes.confirm) {
|
||||
uni.openSetting()
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '保存失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('获取二维码图片失败:', err)
|
||||
uni.showToast({
|
||||
title: '获取二维码失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
})
|
||||
} else {
|
||||
if (!this.wxQrcodeImage) {
|
||||
uni.showToast({
|
||||
title: '二维码生成中,请稍候',
|
||||
title: '小程序码加载中,请稍候',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 将 base64 图片保存到相册
|
||||
// 先将 base64 转为临时文件
|
||||
const base64Data = this.wxQrcodeImage.replace(/^data:image\/\w+;base64,/, '')
|
||||
const filePath = `${wx.env.USER_DATA_PATH}/qrcode_${Date.now()}.png`
|
||||
|
||||
const fs = uni.getFileSystemManager()
|
||||
fs.writeFile({
|
||||
filePath: filePath,
|
||||
data: base64Data,
|
||||
encoding: 'base64',
|
||||
success: () => {
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath: filePath,
|
||||
success: () => {
|
||||
uni.showToast({
|
||||
title: this.$t('invite.saveSuccess') || '保存成功',
|
||||
icon: 'success'
|
||||
})
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('保存图片失败:', err)
|
||||
if (err.errMsg && err.errMsg.includes('auth deny')) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '需要您授权保存图片到相册',
|
||||
success: (modalRes) => {
|
||||
if (modalRes.confirm) {
|
||||
uni.openSetting()
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '保存失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('写入文件失败:', err)
|
||||
uni.showToast({
|
||||
title: '保存失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
shareToFriend() {
|
||||
uni.share({
|
||||
|
|
@ -1295,21 +1321,25 @@
|
|||
margin-bottom: 30rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
overflow: hidden;
|
||||
|
||||
// 修复 u-qrcode 组件的 canvas 定位问题
|
||||
:deep(.u-qrcode) {
|
||||
position: static !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
.wx-qrcode-image {
|
||||
width: 400rpx;
|
||||
height: 400rpx;
|
||||
}
|
||||
|
||||
:deep(.u-qrcode__content) {
|
||||
position: static !important;
|
||||
.qrcode-loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.qrcode-error {
|
||||
color: #ff4d4f;
|
||||
font-size: 26rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
:deep(.u-qrcode__canvas) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user