diff --git a/src/app.wpy b/src/app.wpy index bf0fdde..c6a7eda 100644 --- a/src/app.wpy +++ b/src/app.wpy @@ -20,6 +20,7 @@ import promisify from '@wepy/use-promisify' import {wx_login} from './utils/util' import {IM} from './utils/im' import {initEid} from './mp_ecard_sdk/main' +import {invitationFromOptions} from './utils/invitation' // import {service} from './config' wepy.use(promisify) @@ -50,10 +51,11 @@ wepy.app({ onLaunch(e) { let vm = this + const invitationID = invitationFromOptions(e) + if (invitationID) wx.setStorageSync('from_user_id', invitationID) console.log(e, 'uuu=') vm.appLogin() console.log(e, 'e=') - console.log(decodeURIComponent(e.query.scene), 'e=') // if (e.path === 'pages/home/informationV2') { // wx.redirectTo({ url: `/pages/home/information?${decodeURIComponent(e.query.scene)}` }) // } @@ -95,15 +97,19 @@ wepy.app({ if (options.path === 'pages/tabBar/home1') { wx.switchTab({url: `/pages/tabBar/home?${decodeURIComponent(options.query.scene)}`}) } - if (options.query.scene) { - let sceneList = decodeURIComponent(options.query.scene).split(/[= &]/) + if (options.query && options.query.scene) { + let sceneList = [] + try { sceneList = decodeURIComponent(options.query.scene).split(/[= &]/) } catch (_) {} sceneList.forEach((item, index) => { if (item == 'userId') { wx.setStorageSync('agent_user_id', sceneList[index + 1]) } }) } - if (options.query && options.query.from_user_id) { + const invitationScene = invitationFromOptions(options) + if (invitationScene) { + wx.setStorageSync('from_user_id', invitationScene) + } else if (options.query && options.query.from_user_id) { wx.setStorageSync('from_user_id', options.query.from_user_id) } else { if (options.path && options.path === 'pages/sub_mall/confirmOrder') { @@ -235,6 +241,9 @@ subPackages: [ 'orderDetails', 'exclusiveService', 'myDynamic', +'more', +'myShare', +'shareCard', 'logoutDefault', 'ImageCropper', 'myDynamicDetail', diff --git a/src/images/ambassador-contact.jpg b/src/images/ambassador-contact.jpg new file mode 100644 index 0000000..e59ceea Binary files /dev/null and b/src/images/ambassador-contact.jpg differ diff --git a/src/images/fulian-logo.png b/src/images/fulian-logo.png new file mode 100644 index 0000000..4077cf1 Binary files /dev/null and b/src/images/fulian-logo.png differ diff --git a/src/pages/tabBar/user.wpy b/src/pages/tabBar/user.wpy index b2c0268..7cc2114 100644 --- a/src/pages/tabBar/user.wpy +++ b/src/pages/tabBar/user.wpy @@ -358,12 +358,6 @@ wepy.page({ type: 'navigateTo', path: '/pages/users/upgradeVIP' }, - { - icon: 'https://image.fulllinkai.com/202305/18/986abe4366a59ebc8c5437a63d4f957f.png', - title: '专属客服', - type: 'navigateTo', - path: '/pages/users/exclusiveService' - }, { icon: 'https://image.fulllinkai.com/202201/13/0c8d92e86946c9a4244a92cacd4bd8d0.png', title: '我的发布', @@ -371,22 +365,22 @@ wepy.page({ path: '/pages/users/myDynamic' }, { - icon: 'https://image.fulllinkai.com/202202/17/0cb2fe566b98d607136bde08e7f27f4d.png', - title: '福恋指南', + icon: 'https://image.fulllinkai.com/202302/06/4092bc34b48ef2b8b95e9d3a5954f803.png', + title: '我的分享', type: 'navigateTo', - path: 'https://love.ufutx.com/h5/#/guide' - }, - { - icon: 'https://image.fulllinkai.com/202202/21/c982ff4f02b9f834e6f4cd6886547f71.png', - title: '意见反馈', - type: 'navigateTo', - path: '/pages/users/report?type=user' + path: '/pages/users/myShare' }, { icon: 'https://image.fulllinkai.com/202302/06/4092bc34b48ef2b8b95e9d3a5954f803.png', title: '分享我的卡片', - type: 'share', - path: '' + type: 'navigateTo', + path: '/pages/users/shareCard' + }, + { + icon: 'https://image.fulllinkai.com/202202/17/0cb2fe566b98d607136bde08e7f27f4d.png', + title: '更多', + type: 'navigateTo', + path: '/pages/users/more' }, { icon: 'https://image.fulllinkai.com/202201/13/0b3e132b03ee70e1325229ef8885ba29.png', @@ -776,7 +770,7 @@ wepy.page({ return } if (text === '分享我的卡片') { - vm.hide = false + wx.navigateTo({url: '/pages/users/shareCard'}) return } if (url && url.includes('page')) { diff --git a/src/pages/users/more.wpy b/src/pages/users/more.wpy new file mode 100644 index 0000000..6a98e48 --- /dev/null +++ b/src/pages/users/more.wpy @@ -0,0 +1,37 @@ + + + +{navigationBarTitleText:'更多',navigationBarBackgroundColor:'#FFF9F5'} diff --git a/src/pages/users/myShare.wpy b/src/pages/users/myShare.wpy new file mode 100644 index 0000000..a27c9e0 --- /dev/null +++ b/src/pages/users/myShare.wpy @@ -0,0 +1,139 @@ + + + +{navigationBarTitleText:'我的分享',navigationBarBackgroundColor:'#FFF9F5'} diff --git a/src/pages/users/shareCard.wpy b/src/pages/users/shareCard.wpy new file mode 100644 index 0000000..56151b9 --- /dev/null +++ b/src/pages/users/shareCard.wpy @@ -0,0 +1,74 @@ + + + +{navigationBarTitleText:'分享我的卡片',navigationBarBackgroundColor:'#FFF9F5'} diff --git a/src/styles/sharing.less b/src/styles/sharing.less new file mode 100644 index 0000000..8114e33 --- /dev/null +++ b/src/styles/sharing.less @@ -0,0 +1,29 @@ +.sharing-page { min-height:100vh; background:#FFF9F5; color:#4E4145; padding:36rpx 32rpx 64rpx; box-sizing:border-box; } +.eyebrow { color:#D92554; font-size:24rpx; margin-bottom:16rpx; } +.sharing-title { color:#A3294D; font-size:40rpx; font-weight:600; margin-bottom:16rpx; } +.sharing-subtitle { font-size:26rpx; color:#97717D; line-height:1.7; margin-bottom:32rpx; } +.sharing-poster { display:block; width:100%; border-radius:20rpx; } +.sharing-preview { border-radius:24rpx; background:#FFF1F4; padding:28rpx; } +.sharing-photo { width:100%; height:400rpx; border-radius:16rpx; margin:24rpx 0; } +.sharing-logo { width:64rpx; height:64rpx; } +.sharing-intro { margin:28rpx 0; font-size:28rpx; line-height:1.8; } +.sharing-actions { display:flex; flex-wrap:wrap; gap:16rpx; margin-top:28rpx; } +.sharing-page button { background:#D92554; color:#FFF9F5; border-radius:16rpx; font-size:28rpx; margin:0; } +.sharing-page button[disabled] { background:#EED3DB; color:#4E4145; } +.sharing-actions button { flex:1; min-width:240rpx; } +.sharing-state { font-size:26rpx; color:#97717D; padding:32rpx 0; line-height:1.8; } +.sharing-state button { margin-top:20rpx; } +.sharing-section { margin-top:36rpx; font-size:32rpx; color:#A3294D; font-weight:600; } +.sharing-stats { display:flex; gap:20rpx; padding:30rpx; background:#FFF1F4; border-radius:24rpx; } +.sharing-stat { flex:1; font-size:26rpx; } +.sharing-number { display:block; font-size:48rpx; color:#A3294D; margin-bottom:12rpx; } +.sharing-row { display:flex; justify-content:space-between; gap:20rpx; padding:28rpx 0; border-bottom:1rpx solid #EED3DB; font-size:28rpx; } +.sharing-row text:first-child { overflow-wrap:anywhere; } +.sharing-status { color:#97717D; flex-shrink:0; } +.application-mask { position:fixed; inset:0; z-index:1000; background:rgba(78,65,69,.6); display:flex; align-items:center; justify-content:center; padding:24rpx; box-sizing:border-box; } +.application-dialog { width:100%; max-width:640rpx; max-height:90vh; overflow-y:auto; background:#FFF9F5; border-radius:28rpx; padding:28rpx; box-sizing:border-box; } +.application-title { color:#A3294D; font-size:34rpx; font-weight:600; text-align:center; } +.application-note { color:#4E4145; font-size:28rpx; line-height:1.7; margin:18rpx 0; } +.application-code { display:block; width:100%; } +.application-hint { font-size:24rpx; color:#97717D; line-height:1.6; margin:20rpx 0; } +.sharing-page .application-close { margin-top:16rpx; color:#A3294D; background:#FFF1F4; } diff --git a/src/utils/invitation.js b/src/utils/invitation.js new file mode 100644 index 0000000..2396d24 --- /dev/null +++ b/src/utils/invitation.js @@ -0,0 +1,10 @@ +export function invitationFromOptions(options) { + const query = (options && options.query) || {} + let sceneID = '' + try { + const match = decodeURIComponent(query.scene || '').match(/(?:^|&)from_user_id=([1-9][0-9]*)(?:&|$)/) + if (match) sceneID = match[1] + } catch (_) {} + const value = String(sceneID || query.from_user_id || '') + return /^[1-9][0-9]*$/.test(value) && Number.isSafeInteger(Number(value)) ? value : '' +} diff --git a/src/utils/sharing.js b/src/utils/sharing.js new file mode 100644 index 0000000..2ac2c3f --- /dev/null +++ b/src/utils/sharing.js @@ -0,0 +1,116 @@ +// Sharing destinations stay separate: inviting someone never advertises the +// inviter as a person seeking a relationship. +export function sharingPath(id, personal) { + const userID = Number(id) + if (!Number.isSafeInteger(userID) || userID <= 0) throw new Error('请先登录后分享') + return personal + ? `/pages/home/information?id=${userID}` + : `/pages/tabBar/welcome?from_user_id=${userID}` +} + +export function storeSharingCode(data, kind) { + const extension = data.mime === 'image/png' ? 'png' : data.mime === 'image/jpeg' ? 'jpg' : '' + if (!extension || !data.image) return Promise.reject(new Error('分享码格式无效')) + const filePath = `${wx.env.USER_DATA_PATH}/fulian-${kind}.${extension}` + return new Promise((resolve,reject) => wx.getFileSystemManager().writeFile({filePath,data:data.image,encoding:'base64',success:()=>resolve(filePath),fail:reject})) +} + +function imageInfo(src) { + return new Promise((resolve, reject) => wx.getImageInfo({src, success: resolve, fail: reject})) +} + +function textLines(ctx, text, x, y, width, lineHeight, maxLines) { + let line = '', row = 0 + const chars = Array.from(String(text || '').replace(/\s+/g, ' ').trim()) + for (let i = 0; i < chars.length; i++) { + if (ctx.measureText(line + chars[i]).width > width) { + if (row === maxLines - 1) { + while (line && ctx.measureText(line + '…').width > width) line = line.slice(0, -1) + ctx.fillText(line + '…', x, y + row * lineHeight) + return + } + // Keep closing punctuation with the preceding character on the next line. + const carry = /[,。!?;:、)】》」』]/.test(chars[i]) ? line.slice(-1) : '' + if (carry) line = line.slice(0, -1) + ctx.fillText(line, x, y + row * lineHeight) + row++ + line = carry + } + line += chars[i] + } + ctx.fillText(line, x, y + row * lineHeight) +} + +export async function renderSharingPoster(page, profile, personal) { + if (!profile.qrcode) throw new Error('分享码暂未准备好,请稍后重试') + const sources = ['/images/fulian-logo.png', profile.qrcode] + if (personal && profile.photo) sources.push(profile.photo) + const images = await Promise.all(sources.map(imageInfo)) + const ctx = wx.createCanvasContext('fulianPoster', page.$wx) + ctx.setFillStyle('#FFF9F5'); ctx.fillRect(0, 0, 750, 1120) + ctx.drawImage(images[0].path, 48, 42, 64, 64) + ctx.setFillStyle('#A3294D'); ctx.setFontSize(30); ctx.fillText('福恋', 126, 84) + ctx.setFontSize(22); ctx.fillText('上福恋认真找对象', 440, 82) + if (personal) { + ctx.setFillStyle('#FFF1F4'); ctx.fillRect(48, 138, 654, 430) + if (images[2]) { + const im = images[2], ratio = Math.max(654 / im.width, 430 / im.height) + const sw = 654 / ratio, sh = 430 / ratio + ctx.drawImage(im.path, (im.width - sw) / 2, (im.height - sh) / 2, sw, sh, 48, 138, 654, 430) + } + ctx.setFillStyle('#A3294D'); ctx.setFontSize(40) + textLines(ctx, profile.nickname || '我的个人卡片', 48, 628, 654, 48, 1) + ctx.setFillStyle('#97717D'); ctx.setFontSize(24) + textLines(ctx, profile.details, 48, 674, 654, 32, 1) + ctx.setFillStyle('#4E4145'); ctx.setFontSize(28) + textLines(ctx, profile.introduction || '很高兴在福恋与你相遇。', 48, 730, 654, 38, 2) + if (profile.idealMate) { + ctx.setFontSize(24) + textLines(ctx, '期待的你:' + profile.idealMate, 48, 824, 654, 32, 1) + } + } else { + ctx.setFillStyle('#FFF1F4'); ctx.fillRect(48, 160, 654, 640) + ctx.setFillStyle('#A3294D'); ctx.setFontSize(52) + ctx.fillText('把福恋分享给', 88, 300); ctx.fillText('认真寻找爱情的人', 88, 380) + ctx.setFillStyle('#4E4145'); ctx.setFontSize(28) + textLines(ctx, '认识新朋友,开启认真交往的第一步。', 88, 470, 560, 46, 2) + ctx.setFontSize(25) + textLines(ctx, `${profile.nickname || '一位朋友'} 邀请你加入福恋`, 88, 690, 560, 38, 2) + } + ctx.setFillStyle('#EED3DB'); ctx.fillRect(48, 860, 654, 1) + ctx.drawImage(images[1].path, 48, 906, 164, 164) + ctx.setFillStyle('#A3294D'); ctx.setFontSize(28) + ctx.fillText(personal ? '扫码认识我' : '扫码加入福恋', 244, 965) + ctx.setFillStyle('#97717D'); ctx.setFontSize(23) + ctx.fillText(personal ? '查看我的完整个人资料' : '上福恋认真找对象', 244, 1010) + await new Promise(resolve => ctx.draw(false, resolve)) + return new Promise((resolve, reject) => wx.canvasToTempFilePath({ + canvasId: 'fulianPoster', width:750, height:1120, destWidth:750, destHeight:1120, + success: result => resolve(result.tempFilePath), fail: reject + }, page.$wx)) +} + +export function saveSharingPoster(filePath) { + return new Promise((resolve, reject) => wx.saveImageToPhotosAlbum({filePath, success:resolve, fail:reject})) +} + +export async function renderSharingCover(page, profile, personal) { + const logo=await imageInfo('/images/fulian-logo.png') + const photo=personal && profile.photo ? await imageInfo(profile.photo) : null + const ctx=wx.createCanvasContext('fulianPoster',page.$wx) + ctx.setFillStyle('#FFF9F5'); ctx.fillRect(0,0,750,600) + ctx.drawImage(logo.path,40,32,64,64) + ctx.setFillStyle('#A3294D'); ctx.setFontSize(28); ctx.fillText('上福恋认真找对象',124,74) + if (photo) { + const ratio=Math.max(290/photo.width,360/photo.height),sw=290/ratio,sh=360/ratio + ctx.drawImage(photo.path,(photo.width-sw)/2,(photo.height-sh)/2,sw,sh,420,150,290,360) + } + ctx.setFillStyle('#A3294D');ctx.setFontSize(42) + textLines(ctx,personal ? profile.nickname || '我的个人卡片' : '邀请你加入福恋',40,210,personal ? 350 : 670,56,2) + ctx.setFillStyle('#4E4145');ctx.setFontSize(28) + textLines(ctx,personal ? profile.introduction || '期待与你认真相识' : '把福恋分享给认真寻找爱情的人',40,350,personal ? 350 : 670,44,3) + ctx.setFillStyle('#97717D');ctx.setFontSize(25) + ctx.fillText(personal ? '点击认识我' : '点击开启认真相识',40,550) + await new Promise(resolve=>ctx.draw(false,resolve)) + return new Promise((resolve,reject)=>wx.canvasToTempFilePath({canvasId:'fulianPoster',width:750,height:600,destWidth:750,destHeight:600,success:res=>resolve(res.tempFilePath),fail:reject},page.$wx)) +} diff --git a/tools/sharing/check-contact.cjs b/tools/sharing/check-contact.cjs new file mode 100644 index 0000000..c7d4999 --- /dev/null +++ b/tools/sharing/check-contact.cjs @@ -0,0 +1,27 @@ +const fs=require('fs'),path=require('path'),vm=require('vm'),assert=require('assert') +const source=fs.readFileSync(path.join(__dirname,'../../src/pages/users/myShare.wpy'),'utf8').match(/