@ -0,0 +1,455 @@ | |||
<template> | |||
<uv-popup | |||
ref="popup" | |||
:overlayOpacity="0.6" | |||
mode="center" | |||
bgColor="none" | |||
:zIndex="1000000" | |||
@change="onPopupChange" | |||
> | |||
<view class="popup__view"> | |||
<view class="canvas" style="width: 566rpx; height: 1060rpx; overflow: hidden;"> | |||
<canvas id="myCanvas" canvas-id="firstCanvas1" type="2d" style="width: 100%; height: 100%;"></canvas> | |||
</view> | |||
<button class="btn" @click="saveImg"> | |||
<view class="content">保存到相册</view> | |||
</button> | |||
</view> | |||
</uv-popup> | |||
</template> | |||
<script> | |||
export default { | |||
data() { | |||
return { | |||
wxCodeImage: '', | |||
baseUrl: 'https://image.hhlm1688.com/', | |||
canvas: {}, | |||
retry: 10, | |||
} | |||
}, | |||
async onLoad() { | |||
}, | |||
methods: { | |||
open() { | |||
this.retry = 10 | |||
this.$refs.popup.open(); | |||
}, | |||
close() { | |||
this.$refs.popup.close(); | |||
}, | |||
async fetchQrCode(path) { | |||
// todo: delete | |||
this.wxCodeImage = 'https://uploadfile.bizhizu.cn/up/e3/64/e0/e364e0f7f6af11f11abdafc22d17b15c.jpg' | |||
return | |||
try { | |||
this.wxCodeImage = (await this.$fetch('getInviteCode', { path }))?.url | |||
} catch (err) { | |||
} | |||
}, | |||
// 生成有圆角的矩形 | |||
drawBg(ctx, x, y, width, height, radius) { | |||
ctx.beginPath(); | |||
ctx.arc(x + radius, y + radius, radius, Math.PI, Math.PI * 3 / 2); | |||
ctx.lineTo(width - radius + x, y); | |||
ctx.arc(width - radius + x, radius + y, radius, Math.PI * 3 / 2, Math.PI * 2); | |||
ctx.lineTo(width + x, height + y - radius); | |||
ctx.arc(width - radius + x, height - radius + y, radius, 0, Math.PI * 1 / 2); | |||
ctx.lineTo(radius + x, height + y); | |||
ctx.arc(radius + x, height - radius + y, radius, Math.PI * 1 / 2, Math.PI); | |||
ctx.closePath(); | |||
ctx.fillStyle = '#fff' | |||
ctx.fill() | |||
}, | |||
drawCoverImg(canvas, ctx, x, y, width, height, radius, lineWidth) { | |||
return new Promise(resolve => { | |||
// 海报图片 | |||
const paperImage = canvas.createImage() | |||
console.log('paperImage', paperImage) | |||
// todo: fetch | |||
paperImage.src = 'https://i1.hdslb.com/bfs/archive/c0101b4ce06e6bdda803728408e79c8f8b8d0725.jpg' | |||
paperImage.onload = () => { | |||
console.log('paperImage onload') | |||
ctx.beginPath(); | |||
ctx.arc(x + radius, y + radius, radius, Math.PI, Math.PI * 3 / 2); | |||
ctx.lineTo(width - radius + x, y); | |||
ctx.arc(width - radius + x, radius + y, radius, Math.PI * 3 / 2, Math.PI * 2); | |||
ctx.lineTo(width + x, height + y); | |||
ctx.lineTo(x, height + y); | |||
ctx.closePath(); | |||
ctx.lineWidth = lineWidth; | |||
ctx.strokeStyle = '#F8F8F8'; | |||
ctx.stroke(); | |||
ctx.clip(); | |||
ctx.drawImage(paperImage, x, y, width, height) | |||
resolve() | |||
} | |||
}) | |||
}, | |||
drawContentBg(ctx, x, y, width, height, radius) { | |||
ctx.beginPath(); | |||
// ctx.arc(x + radius, y + radius, radius, Math.PI, Math.PI * 3 / 2); | |||
ctx.moveTo(x, y); | |||
ctx.lineTo(width + x, y); | |||
ctx.lineTo(width + x, height + y - radius); | |||
ctx.arc(width - radius + x, height - radius + y, radius, 0, Math.PI * 1 / 2); | |||
ctx.lineTo(radius + x, height + y); | |||
ctx.arc(radius + x, height - radius + y, radius, Math.PI * 1 / 2, Math.PI); | |||
ctx.closePath(); | |||
let gradient = ctx.createLinearGradient(x, y, x, y + height); | |||
gradient.addColorStop('0', '#DAF3FF'); | |||
gradient.addColorStop('0.2', '#FBFEFF'); | |||
gradient.addColorStop('1.0', '#FBFEFF'); | |||
ctx.fillStyle = gradient | |||
ctx.fill() | |||
}, | |||
drawAvatar(canvas, ctx, x, y, r) { | |||
console.log('drawAvatar', 'x', x, 'y', y, 'r', r) | |||
return new Promise(resolve => { | |||
// 头像图片 | |||
const avatarImage = canvas.createImage() | |||
// todo: fetch | |||
avatarImage.src = 'https://i1.hdslb.com/bfs/archive/c0101b4ce06e6bdda803728408e79c8f8b8d0725.jpg' | |||
avatarImage.onload = () => { | |||
console.log('avatarImage onload') | |||
ctx.beginPath(); | |||
console.log('arc', 'x', x + r, 'y', y + r, 'r', r) | |||
ctx.arc(x + r, y + r, r, 0, 2 * Math.PI); | |||
ctx.clip(); | |||
const size = r*2 | |||
console.log('size', size) | |||
ctx.drawImage(avatarImage, x, y, size, size) | |||
resolve() | |||
} | |||
}) | |||
}, | |||
drawMultilineText(ctx, text, x, y, maxWidth, lineHeight) { | |||
console.log('drawMultilineText', 'x', x, 'y', y) | |||
let line = ''; | |||
for (let n = 0; n < text.length; n++) { | |||
const testLine = line + text[n]; | |||
const metrics = ctx.measureText(testLine); | |||
const testWidth = metrics.width; | |||
if (testWidth > maxWidth && n > 0) { | |||
ctx.fillText(line, x, y); | |||
console.log(n, 'line', line, 'x', x, 'y', y) | |||
line = text[n]; | |||
y += lineHeight; | |||
} else { | |||
line = testLine; | |||
} | |||
} | |||
console.log('line', line, 'x', x, 'y', y) | |||
ctx.fillText(line, x, y); | |||
}, | |||
drawQrCodeImg(canvas, ctx, x, y, size) { | |||
return new Promise(resolve => { | |||
//二维码图片 | |||
const coderImage = canvas.createImage() | |||
coderImage.src = this.wxCodeImage | |||
coderImage.onload = () => { | |||
console.log('coderImage onload') | |||
ctx.drawImage(coderImage, x, y, size, size) | |||
resolve() | |||
} | |||
}) | |||
}, | |||
draw() { | |||
wx.createSelectorQuery().in(this) | |||
.select('#myCanvas') // 绘制的canvas的id | |||
.fields({ | |||
node: true, | |||
size: true | |||
}) | |||
.exec(async (res) => { | |||
console.log('res', res) | |||
if (!res?.[0]?.node) { | |||
if (!this.retry) { | |||
console.log('retry fail') | |||
return | |||
} | |||
console.log('retry') | |||
this.retry -= 1 | |||
setTimeout(() => { | |||
this.draw() | |||
}, 200) | |||
return | |||
} | |||
const canvas = res[0].node | |||
// 渲染上下文 | |||
const ctx = canvas.getContext('2d') | |||
// Canvas 画布的实际绘制宽高 | |||
const width = res[0].width | |||
const height = res[0].height | |||
// 初始化画布大小 | |||
const dpr = wx.getWindowInfo().pixelRatio | |||
//根据dpr调整 | |||
// dpr 2 4 | |||
// 3 6 | |||
console.log("--dpr", dpr) | |||
canvas.width = width * dpr | |||
canvas.height = height * dpr | |||
let Ratio = canvas.width / 566 | |||
this.canvas = canvas | |||
ctx.scale(dpr, dpr) | |||
ctx.clearRect(0, 0, width, height) | |||
ctx.fillStyle = 'transparent' | |||
ctx.fillRect(0, 0, canvas.width, canvas.height) | |||
ctx.save() | |||
let radius = 48 * Ratio / dpr | |||
let w = 566 * Ratio / dpr | |||
let h = 1060 * Ratio / dpr | |||
this.drawBg(ctx, 0, 0, w, h, radius) | |||
ctx.restore(); | |||
ctx.save() | |||
let lineWidth = 2 * Ratio / dpr | |||
let x = lineWidth | |||
let y = lineWidth | |||
w = 566 * Ratio / dpr - lineWidth * 2 | |||
h = 400 * Ratio / dpr - lineWidth * 2 | |||
await this.drawCoverImg(canvas, ctx, x, y, w, h, radius, lineWidth) | |||
ctx.restore(); | |||
ctx.save() | |||
x = lineWidth | |||
y = 400 * Ratio / dpr + lineWidth | |||
h = 660 * Ratio / dpr - lineWidth * 2 | |||
this.drawContentBg(ctx, x, y, w, h, radius, lineWidth) | |||
ctx.restore(); | |||
ctx.save() | |||
radius = Math.floor(27 * Ratio / dpr) | |||
x = Math.floor(40 * Ratio / dpr) | |||
y = Math.floor(440 * Ratio / dpr) | |||
await this.drawAvatar(canvas, ctx, x, y, radius) | |||
ctx.restore(); | |||
ctx.save() | |||
let text = '战斗世界' | |||
let maxWidth = 220 * Ratio / dpr | |||
let lineHeight = 17 * Ratio / dpr | |||
x = 100 * Ratio / dpr | |||
y = 474 * Ratio / dpr | |||
ctx.font = "normal normal normal 11px normal"; | |||
ctx.fillStyle = "#7D7D7D"; | |||
this.drawMultilineText(ctx, text, x, y, maxWidth, lineHeight) | |||
ctx.restore(); | |||
ctx.save() | |||
maxWidth = 540 * Ratio / dpr | |||
lineHeight = 60 * Ratio / dpr | |||
ctx.font = "normal normal 600 17px normal"; | |||
ctx.fillStyle = "#181818"; | |||
text = '邀请您' | |||
x = 40 * Ratio / dpr | |||
y = 564 * Ratio / dpr | |||
this.drawMultilineText(ctx, text, x, y, maxWidth, lineHeight) | |||
y += lineHeight | |||
text = '探索新世界,开启研学之旅!' | |||
this.drawMultilineText(ctx, text, x, y, maxWidth, lineHeight) | |||
ctx.restore(); | |||
ctx.save() | |||
text = '是否渴望一场充满知识与乐趣的冒险?现在,我们诚挚邀请你加入我们的研学小程序,开启一场别开生面的学习之旅!' | |||
maxWidth = 486 * Ratio / dpr | |||
lineHeight = 30 * Ratio / dpr | |||
ctx.font = "normal normal normal 10px normal"; | |||
ctx.fillStyle = "#7D7D7D"; | |||
x = 40 * Ratio / dpr | |||
y = 690 * Ratio / dpr | |||
this.drawMultilineText(ctx, text, x, y, maxWidth, lineHeight) | |||
ctx.restore(); | |||
ctx.save() | |||
// 设置虚线样式 | |||
ctx.setLineDash([4, 4]); // 第一个参数是实线长度, 第二个参数是间隔长度 | |||
ctx.lineDashOffset = 2; // 设置虚线的起始偏移量 | |||
ctx.lineWidth = lineWidth; | |||
ctx.strokeStyle = '#DADADA'; | |||
// 开始绘制 | |||
ctx.beginPath(); | |||
x = 40 * Ratio / dpr | |||
y = 778 * Ratio / dpr | |||
ctx.moveTo(x, y); | |||
x += 486 * Ratio / dpr | |||
ctx.lineTo(x, y); | |||
ctx.stroke(); | |||
ctx.restore(); | |||
ctx.save() | |||
maxWidth = 250 * Ratio / dpr | |||
lineHeight = 44 * Ratio / dpr | |||
ctx.font = "normal normal 600 13px normal"; | |||
ctx.fillStyle = "#181818"; | |||
text = '立即加入我们,' | |||
x = 40 * Ratio / dpr | |||
y = 879 * Ratio / dpr | |||
this.drawMultilineText(ctx, text, x, y, maxWidth, lineHeight) | |||
text = '开启你的研学之旅!' | |||
y += lineHeight | |||
this.drawMultilineText(ctx, text, x, y, maxWidth, lineHeight) | |||
ctx.restore(); | |||
ctx.save() | |||
x = 316 * Ratio / dpr | |||
y = 810 * Ratio / dpr | |||
let size = 210 * Ratio / dpr | |||
await this.drawQrCodeImg(canvas, ctx, x, y, size) | |||
uni.hideLoading() | |||
return | |||
// 海报图片 | |||
const paperImage = canvas.createImage() | |||
console.log('paperImage', paperImage) | |||
// todo: fetch | |||
paperImage.src = 'https://i1.hdslb.com/bfs/archive/c0101b4ce06e6bdda803728408e79c8f8b8d0725.jpg' | |||
paperImage.onload = () => { | |||
console.log('paperImage onload') | |||
const w = 566 * Ratio / dpr | |||
const h = 400 * Ratio / dpr | |||
ctx.drawImage(paperImage, 0, 0, w, h) | |||
//二维码图片 | |||
const coderImage = canvas.createImage() | |||
coderImage.src = this.wxCodeImage | |||
coderImage.onload = () => { | |||
console.log('coderImage onload') | |||
const x = 316 * Ratio / dpr | |||
const y = 910 * Ratio / dpr | |||
const size = 210 * Ratio / dpr | |||
ctx.drawImage(coderImage, x, y, size, size) | |||
uni.hideLoading() | |||
} | |||
} | |||
}) | |||
}, | |||
async init() { | |||
uni.showLoading({ | |||
title: '加载中...' | |||
}); | |||
await this.fetchQrCode() | |||
uni.hideLoading(); | |||
uni.showLoading({ | |||
title: "拼命绘画中..." | |||
}) | |||
this.draw() | |||
}, | |||
saveImg() { | |||
this.$authorize('scope.writePhotosAlbum').then((res) => { | |||
this.imgApi() | |||
}) | |||
}, | |||
imgApi() { | |||
uni.showLoading({ | |||
title: '保存中...' | |||
}); | |||
wx.canvasToTempFilePath({ | |||
x: 0, | |||
y: 0, | |||
width: this.canvas.width, | |||
height: this.canvas.height, | |||
canvas: this.canvas, | |||
success: (res) => { | |||
let tempFilePath = res.tempFilePath; | |||
this.saveImgToPhone(tempFilePath) | |||
}, | |||
fail: (err) => { | |||
console.log('--canvasToTempFilePath--fail', err) | |||
uni.hideLoading(); | |||
} | |||
}, this); | |||
}, | |||
saveImgToPhone(image) { | |||
/* 获取图片的信息 */ | |||
uni.getImageInfo({ | |||
src: image, | |||
success: function(image) { | |||
/* 保存图片到手机相册 */ | |||
uni.saveImageToPhotosAlbum({ | |||
filePath: image.path, | |||
success: function() { | |||
uni.showModal({ | |||
title: '保存成功', | |||
content: '图片已成功保存到相册', | |||
showCancel: false | |||
}); | |||
}, | |||
complete(res) { | |||
console.log(res); | |||
uni.hideLoading(); | |||
} | |||
}); | |||
} | |||
}); | |||
}, | |||
onPopupChange(e) { | |||
if (!e.show) { | |||
return | |||
} | |||
this.init() | |||
}, | |||
}, | |||
} | |||
</script> | |||
<style scoped lang="scss"> | |||
.canvas { | |||
border-radius: 48rpx; | |||
} | |||
.btn { | |||
margin-top: 32rpx; | |||
width: 100%; | |||
padding: 22rpx 0; | |||
box-sizing: border-box; | |||
font-family: PingFang SC; | |||
font-weight: 500; | |||
font-size: 36rpx; | |||
line-height: 1; | |||
color: #FFFFFF; | |||
background: linear-gradient(to right, #21FEEC, #019AF9); | |||
border: 2rpx solid #00A9FF; | |||
border-radius: 41rpx; | |||
} | |||
</style> |
@ -1,21 +1,453 @@ | |||
<template> | |||
<view class="page__view"> | |||
<navbar> | |||
<image class="icon-nav" src="@/static/image/partner/icon-nav.png" mode="widthFix"></image> | |||
</navbar> | |||
<view class="main"> | |||
<view class="advantage"> | |||
<view class="flex advantage-content"> | |||
<view class="flex advantage-item" v-for="(item, aIdx) in advantages" :key="aIdx"> | |||
<image class="icon" src="@/static/image/icon-checkmark-circle-fill.png" mode="widthFix"></image> | |||
<view>{{ item }}</view> | |||
</view> | |||
</view> | |||
</view> | |||
<view class="card"> | |||
<view class="flex user"> | |||
<view class="avatar"> | |||
<image class="img" src="@/static/image/temp-30.png" mode="scaleToFill"></image> | |||
<view :class="['tag', `tag-1`]">家长</view> | |||
</view> | |||
<view class="flex summary"> | |||
<view class="flex flex-column summary-item name"> | |||
<view class="summary-item-content">战斗世界</view> | |||
<view class="summary-item-label">ID:5625354</view> | |||
</view> | |||
<template v-if="isPartner"> | |||
<view class="flex flex-column summary-item" @click="jumpToAchievement"> | |||
<view class="summary-item-content">888</view> | |||
<view class="summary-item-label">推广人数</view> | |||
</view> | |||
<view class="flex flex-column summary-item"> | |||
<view class="summary-item-content">341</view> | |||
<view class="summary-item-label">总佣金</view> | |||
</view> | |||
</template> | |||
<template v-else> | |||
<view class="flex summary-item operate"> | |||
<button class="btn" @click="onApplyPartner">成为合伙人</button> | |||
</view> | |||
</template> | |||
</view> | |||
</view> | |||
<view class="flex bar" v-if="isPartner"> | |||
<button class="flex col btn" @click="jumpToTeam"> | |||
<image class="icon" src="@/static/image/partner/icon-team.png" mode="widthFix"></image> | |||
<view>我的团队</view> | |||
</button> | |||
<view class="flex divider"> | |||
<view class="line"></view> | |||
</view> | |||
<button class="flex col btn" @click="openPosterPopup"> | |||
<image class="icon" src="@/static/image/partner/icon-qrcode.png" mode="widthFix"></image> | |||
<view>邀请二维码</view> | |||
</button> | |||
<view class="flex divider"> | |||
<view class="line"></view> | |||
</view> | |||
<button class="flex col btn" @click="jumpToWithdraw"> | |||
<image class="icon" src="@/static/image/partner/icon-cash.png" mode="widthFix"></image> | |||
<view>提现</view> | |||
</button> | |||
</view> | |||
<!-- todo: check --> | |||
<button class="btn-apply" @click="onApplyPartner"> | |||
<image class="bg" src="@/static/image/partner/apply.png" mode="widthFix"></image> | |||
</button> | |||
<view class="list" v-if="isPartner && list.length"> | |||
<view class="flex list-item" v-for="item in list" :key="item.id"> | |||
<view class="flex col info"> | |||
<view class="avatar"> | |||
<image class="img" :src="item.avatar" mode="scaleToFill"></image> | |||
</view> | |||
<view>{{ item.name }}</view> | |||
</view> | |||
<view class="col price">{{ `+¥${item.price}` }}</view> | |||
<view class="col desc">{{ item.createTime }}</view> | |||
</view> | |||
</view> | |||
</view> | |||
</view> | |||
<posterPopup ref="posterPopup"></posterPopup> | |||
<tabber select="partner" /> | |||
</view> | |||
</template> | |||
<script> | |||
import mixinsList from '@/mixins/list.js' | |||
import tabber from '@/components/base/tabbar.vue' | |||
import posterPopup from '@/components/partner/posterPopup.vue' | |||
export default { | |||
mixins: [mixinsList], | |||
components: { | |||
tabber, | |||
posterPopup, | |||
}, | |||
data() { | |||
return { | |||
advantages: ['收益高', '品类全', '到账快', '城市多'], | |||
// todo: fetch | |||
isPartner: true, | |||
// todo | |||
mixinsListApi: '', | |||
} | |||
}, | |||
onShow() { | |||
// todo: refresh is partner? | |||
}, | |||
methods: { | |||
// todo: delete | |||
getData() { | |||
this.list = [ | |||
{ | |||
id: '001', | |||
avatar: '/static/image/temp-30.png', | |||
name: '李世海', | |||
price: 10, | |||
createTime: '2025-07-15', | |||
}, | |||
{ | |||
id: '002', | |||
avatar: '/static/image/temp-30.png', | |||
name: '周静', | |||
price: 10, | |||
createTime: '2025-07-15', | |||
}, | |||
{ | |||
id: '003', | |||
avatar: '/static/image/temp-30.png', | |||
name: '周海', | |||
price: 10, | |||
createTime: '2025-07-15', | |||
}, | |||
{ | |||
id: '004', | |||
avatar: '/static/image/temp-30.png', | |||
name: '冯启彬', | |||
price: 10, | |||
createTime: '2025-07-15', | |||
}, | |||
{ | |||
id: '005', | |||
avatar: '/static/image/temp-30.png', | |||
name: '李娉', | |||
price: 10, | |||
createTime: '2025-07-15', | |||
}, | |||
{ | |||
id: '006', | |||
avatar: '/static/image/temp-30.png', | |||
name: '李书萍', | |||
price: 10, | |||
createTime: '2025-07-15', | |||
}, | |||
{ | |||
id: '007', | |||
avatar: '/static/image/temp-30.png', | |||
name: '李世海', | |||
price: 10, | |||
createTime: '2025-07-15', | |||
}, | |||
{ | |||
id: '008', | |||
avatar: '/static/image/temp-30.png', | |||
name: '周静', | |||
price: 10, | |||
createTime: '2025-07-15', | |||
}, | |||
{ | |||
id: '009', | |||
avatar: '/static/image/temp-30.png', | |||
name: '周海', | |||
price: 10, | |||
createTime: '2025-07-15', | |||
}, | |||
{ | |||
id: '010', | |||
avatar: '/static/image/temp-30.png', | |||
name: '冯启彬', | |||
price: 10, | |||
createTime: '2025-07-15', | |||
}, | |||
{ | |||
id: '011', | |||
avatar: '/static/image/temp-30.png', | |||
name: '李娉', | |||
price: 10, | |||
createTime: '2025-07-15', | |||
}, | |||
{ | |||
id: '012', | |||
avatar: '/static/image/temp-30.png', | |||
name: '李书萍', | |||
price: 10, | |||
createTime: '2025-07-15', | |||
}, | |||
] | |||
}, | |||
onApplyPartner() { | |||
this.$utils.navigateTo(`/pages_order/partner/apply`) | |||
}, | |||
jumpToTeam() { | |||
this.$utils.navigateTo(`/pages_order/partner/team`) | |||
}, | |||
openPosterPopup() { | |||
this.$refs.posterPopup.open() | |||
}, | |||
jumpToWithdraw() { | |||
this.$utils.navigateTo(`/pages_order/partner/withdraw`) | |||
}, | |||
}, | |||
} | |||
</script> | |||
<style scoped lang="scss"> | |||
@import '/components/member/styles/tag.scss'; | |||
@import '../../components/member/styles/tag.scss'; | |||
.page__view { | |||
min-height: 100vh; | |||
background: linear-gradient(to right, #21FEEC, #019AF9); | |||
/deep/ .nav-bar__view { | |||
position: fixed; | |||
top: 0; | |||
left: 0; | |||
} | |||
.icon-nav { | |||
width: 168rpx; | |||
height: auto; | |||
} | |||
/deep/ .tabbar-box { | |||
height: auto; | |||
padding-bottom: 0; | |||
} | |||
} | |||
.main { | |||
// min-height: 100vh; | |||
// padding: calc(var(--status-bar-height) + 130rpx) 0 calc(120rpx + env(safe-area-inset-bottom)) 0; | |||
padding-top: calc(var(--status-bar-height) + 130rpx); | |||
box-sizing: border-box; | |||
} | |||
.advantage { | |||
padding: 0 40rpx 32rpx 40rpx; | |||
&-content { | |||
justify-content: space-between; | |||
padding: 16rpx; | |||
background: #1FB2FD99; | |||
border: 2rpx solid #FFFFFF4D; | |||
border-radius: 16rpx; | |||
} | |||
&-item { | |||
column-gap: 8rpx; | |||
padding-right: 16rpx; | |||
font-size: 26rpx; | |||
color: #FFFFFF; | |||
.icon { | |||
width: 40rpx; | |||
height: auto; | |||
} | |||
} | |||
} | |||
.card { | |||
width: 100%; | |||
// height: 100%; | |||
$advantage-height: 54px; | |||
// min-height: calc(100vh - #{$advantage-height} - (var(--status-bar-height) + 130rpx) - (120rpx + env(safe-area-inset-bottom))); | |||
min-height: calc(100vh - #{$advantage-height} - (var(--status-bar-height) + 130rpx)); | |||
padding: 40rpx; | |||
padding-bottom: calc(40rpx + 120rpx + env(safe-area-inset-bottom)); | |||
box-sizing: border-box; | |||
font-family: PingFang SC; | |||
font-weight: 400; | |||
line-height: 1.4; | |||
background: linear-gradient(#DAF3FF, #FBFEFF 400rpx, #FBFEFF); | |||
border: 2rpx solid #FFFFFF; | |||
border-top-left-radius: 48rpx; | |||
border-top-right-radius: 48rpx; | |||
} | |||
.user { | |||
justify-content: space-between; | |||
padding: 32rpx 40rpx; | |||
background: linear-gradient(#DAF3FF, #FBFEFF 70%, #FBFEFF); | |||
border: 2rpx solid #FFFFFF; | |||
border-radius: 48rpx; | |||
column-gap: 24rpx; | |||
.avatar { | |||
flex: none; | |||
position: relative; | |||
width: 128rpx; | |||
height: 128rpx; | |||
border-radius: 24rpx; | |||
overflow: hidden; | |||
.img { | |||
width: 100%; | |||
height: 100%; | |||
} | |||
} | |||
.summary { | |||
flex: 1; | |||
column-gap: 26rpx; | |||
&-item { | |||
flex: 1; | |||
row-gap: 8rpx; | |||
&.name { | |||
flex: none; | |||
align-items: flex-start; | |||
} | |||
&.operate { | |||
justify-content: flex-end; | |||
} | |||
&-content { | |||
font-size: 32rpx; | |||
font-weight: 600; | |||
color: #000000; | |||
} | |||
&-label { | |||
font-size: 24rpx; | |||
color: #939393; | |||
} | |||
} | |||
} | |||
.btn { | |||
padding: 8rpx 24rpx; | |||
font-size: 28rpx; | |||
font-weight: 500; | |||
line-height: 1.4; | |||
color: #FFFFFF; | |||
background: linear-gradient(to right, #21FEEC, #019AF9); | |||
border-radius: 28rpx; | |||
} | |||
} | |||
.btn-apply { | |||
margin-top: 32rpx; | |||
width: 100%; | |||
height: auto; | |||
padding: 0; | |||
background: none; | |||
font-size: 0; | |||
.bg { | |||
width: 100%; | |||
height: auto; | |||
} | |||
} | |||
.bar { | |||
margin-top: 24rpx; | |||
flex-wrap: nowrap; | |||
padding: 16rpx 24rpx; | |||
.col { | |||
flex: none; | |||
} | |||
.divider { | |||
flex: 1; | |||
.line { | |||
width: 2rpx; | |||
height: 44rpx; | |||
background: #00A9FF; | |||
} | |||
} | |||
.btn { | |||
column-gap: 8rpx; | |||
font-size: 24rpx; | |||
color: #181818; | |||
.icon { | |||
width: 64rpx; | |||
height: auto; | |||
} | |||
} | |||
} | |||
.list { | |||
margin-top: 32rpx; | |||
background: #FFFFFF; | |||
border: 2rpx solid #F0F0F0; | |||
border-radius: 24rpx; | |||
overflow: hidden; | |||
&-item { | |||
margin-top: 16rpx; | |||
padding: 16rpx 32rpx; | |||
font-size: 28rpx; | |||
color: #333333; | |||
background: #FFFFFF; | |||
border-bottom: 2rpx solid #F1F1F1; | |||
&:last-child { | |||
border: none; | |||
} | |||
.col { | |||
flex: 1; | |||
text-align: center; | |||
} | |||
.info { | |||
justify-content: flex-start; | |||
column-gap: 24rpx; | |||
.avatar { | |||
width: 72rpx; | |||
height: 72rpx; | |||
border-radius: 50%; | |||
overflow: hidden; | |||
.img { | |||
width: 100%; | |||
height: 100%; | |||
} | |||
} | |||
} | |||
.desc { | |||
font-size: 26rpx; | |||
color: #A3A3A3; | |||
} | |||
} | |||
} | |||
</style> |
@ -0,0 +1,252 @@ | |||
<template> | |||
<view class="page__view"> | |||
<navbar leftClick @leftClick="$utils.navigateBack"> | |||
<image class="icon-nav" src="@/static/image/partner/icon-nav.png" mode="widthFix"></image> | |||
</navbar> | |||
<view class="main"> | |||
<view class="advantage"> | |||
<view class="flex advantage-content"> | |||
<view class="flex advantage-item" v-for="(item, aIdx) in advantages" :key="aIdx"> | |||
<image class="icon" src="@/static/image/icon-checkmark-circle-fill.png" mode="widthFix"></image> | |||
<view>{{ item }}</view> | |||
</view> | |||
</view> | |||
</view> | |||
<view class="card"> | |||
<view class="card-header">申请合伙人</view> | |||
<view class="form"> | |||
<uv-form | |||
ref="form" | |||
:model="form" | |||
:rules="rules" | |||
errorType="toast" | |||
> | |||
<view class="form-item"> | |||
<uv-form-item prop="name" :customStyle="formItemStyle"> | |||
<view class="form-item-label"> | |||
<image class="icon" src="@/static/image/icon-require.png" mode="widthFix"></image> | |||
姓名 | |||
</view> | |||
<view class="form-item-content"> | |||
<formInput v-model="form.name"></formInput> | |||
</view> | |||
</uv-form-item> | |||
</view> | |||
<view class="form-item"> | |||
<uv-form-item prop="phone" :customStyle="formItemStyle"> | |||
<view class="form-item-label"> | |||
<image class="icon" src="@/static/image/icon-require.png" mode="widthFix"></image> | |||
电话 | |||
</view> | |||
<view class="form-item-content"> | |||
<formInput v-model="form.phone"></formInput> | |||
</view> | |||
</uv-form-item> | |||
</view> | |||
<view class="form-item"> | |||
<uv-form-item prop="recommend" :customStyle="formItemStyle"> | |||
<view class="form-item-label">推荐人</view> | |||
<view class="form-item-content"> | |||
<formInput v-model="form.recommend"></formInput> | |||
</view> | |||
</uv-form-item> | |||
</view> | |||
</uv-form> | |||
</view> | |||
</view> | |||
</view> | |||
<view class="bottom"> | |||
<view class="flex btn" @click="onSubmit">提交</view> | |||
</view> | |||
</view> | |||
</template> | |||
<script> | |||
import formInput from '@/pages_order/components/formInput.vue' | |||
export default { | |||
components: { | |||
formInput, | |||
}, | |||
data() { | |||
return { | |||
advantages: ['收益高', '品类全', '到账快', '城市多'], | |||
form: { | |||
name: null, | |||
phone: null, | |||
recommend: null, | |||
}, | |||
rules: { | |||
'name': { | |||
type: 'string', | |||
required: true, | |||
message: '请输入姓名', | |||
}, | |||
'phone': { | |||
type: 'string', | |||
required: true, | |||
message: '请输入电话', | |||
}, | |||
}, | |||
formItemStyle: { padding: 0 }, | |||
} | |||
}, | |||
methods: { | |||
async onSubmit() { | |||
try { | |||
await this.$refs.form.validate() | |||
const { | |||
} = this.form | |||
const params = { | |||
} | |||
// todo: fetch | |||
// await this.$fetch('updateAddress', params) | |||
uni.showToast({ | |||
icon: 'success', | |||
title: '提交成功', | |||
}); | |||
setTimeout(() => { | |||
this.$utils.navigateBack() | |||
}, 800) | |||
} catch (err) { | |||
console.log('onSave err', err) | |||
} | |||
}, | |||
}, | |||
} | |||
</script> | |||
<style scoped lang="scss"> | |||
.page__view { | |||
min-height: 100vh; | |||
background: linear-gradient(to right, #21FEEC, #019AF9); | |||
/deep/ .nav-bar__view { | |||
position: fixed; | |||
top: 0; | |||
left: 0; | |||
} | |||
.icon-nav { | |||
width: 168rpx; | |||
height: auto; | |||
} | |||
} | |||
.main { | |||
// min-height: 100vh; | |||
// padding: calc(var(--status-bar-height) + 130rpx) 0 calc(120rpx + env(safe-area-inset-bottom)) 0; | |||
padding-top: calc(var(--status-bar-height) + 130rpx); | |||
box-sizing: border-box; | |||
} | |||
.advantage { | |||
padding: 0 40rpx 32rpx 40rpx; | |||
&-content { | |||
justify-content: space-between; | |||
padding: 16rpx; | |||
background: #1FB2FD99; | |||
border: 2rpx solid #FFFFFF4D; | |||
border-radius: 16rpx; | |||
} | |||
&-item { | |||
column-gap: 8rpx; | |||
padding-right: 16rpx; | |||
font-size: 26rpx; | |||
color: #FFFFFF; | |||
.icon { | |||
width: 40rpx; | |||
height: auto; | |||
} | |||
} | |||
} | |||
.card { | |||
width: 100%; | |||
// height: 100%; | |||
$advantage-height: 54px; | |||
// min-height: calc(100vh - #{$advantage-height} - (var(--status-bar-height) + 130rpx) - (120rpx + env(safe-area-inset-bottom))); | |||
min-height: calc(100vh - #{$advantage-height} - (var(--status-bar-height) + 130rpx)); | |||
padding: 40rpx; | |||
box-sizing: border-box; | |||
font-family: PingFang SC; | |||
font-weight: 400; | |||
line-height: 1.4; | |||
background: linear-gradient(#DAF3FF, #FBFEFF 400rpx, #FBFEFF); | |||
border: 2rpx solid #FFFFFF; | |||
border-top-left-radius: 48rpx; | |||
border-top-right-radius: 48rpx; | |||
&-header { | |||
font-family: PingFang SC; | |||
font-weight: 500; | |||
font-size: 36rpx; | |||
line-height: 1.4; | |||
color: #191919; | |||
} | |||
} | |||
.form { | |||
&-item { | |||
margin-top: 32rpx; | |||
border-bottom: 2rpx solid #EEEEEE; | |||
&-label { | |||
font-family: PingFang SC; | |||
font-weight: 400; | |||
font-size: 26rpx; | |||
line-height: 1.4; | |||
color: #181818; | |||
.icon { | |||
margin-right: 8rpx; | |||
width: 16rpx; | |||
height: auto; | |||
} | |||
} | |||
&-content { | |||
} | |||
} | |||
} | |||
.bottom { | |||
position: fixed; | |||
left: 0; | |||
bottom: 0; | |||
width: 100vw; | |||
background: #FFFFFF; | |||
box-sizing: border-box; | |||
padding: 32rpx 40rpx; | |||
padding-bottom: calc(env(safe-area-inset-bottom) + 32rpx); | |||
box-sizing: border-box; | |||
.btn { | |||
width: 100%; | |||
padding: 14rpx 0; | |||
font-family: PingFang SC; | |||
font-weight: 500; | |||
font-size: 36rpx; | |||
line-height: 1; | |||
color: #FFFFFF; | |||
background: linear-gradient(to right, #21FEEC, #019AF9); | |||
border: 2rpx solid #00A9FF; | |||
border-radius: 41rpx; | |||
} | |||
} | |||
</style> |
@ -0,0 +1,242 @@ | |||
<template> | |||
<view class="page__view"> | |||
<navbar title="我的团队" leftClick @leftClick="$utils.navigateBack" color="#191919" bgColor="#FFFFFF" /> | |||
<view class="main"> | |||
<view class="tabs"> | |||
<uv-tabs | |||
:list="tabs" | |||
:current="current" | |||
:scrollable="false" | |||
lineColor="#00A9FF" | |||
lineWidth="48rpx" | |||
lineHeight="4rpx" | |||
:activeStyle="{ | |||
'font-family': 'PingFang SC', | |||
'font-weight': 500, | |||
'font-size': '32rpx', | |||
'line-height': 1.4, | |||
'color': '#00A9FF', | |||
}" | |||
:inactiveStyle="{ | |||
'font-family': 'PingFang SC', | |||
'font-weight': 400, | |||
'font-size': '32rpx', | |||
'line-height': 1.4, | |||
'color': '#181818', | |||
}" | |||
@click="clickTabs" | |||
></uv-tabs> | |||
</view> | |||
<view class="list"> | |||
<view class="flex list-item" v-for="item in list" :key="item.id"> | |||
<view class="avatar"> | |||
<image class="img" :src="item.avatar" mode="scaleToFill"></image> | |||
</view> | |||
<view>{{ item.name }}</view> | |||
</view> | |||
</view> | |||
</view> | |||
</view> | |||
</template> | |||
<script> | |||
import mixinsList from '@/mixins/list.js' | |||
export default { | |||
mixins: [mixinsList], | |||
data() { | |||
return { | |||
tabs: [ | |||
{ name: '直推用户列表' }, | |||
{ name: '间推用户列表' }, | |||
], | |||
mixinsListApi: '', | |||
current: 0, | |||
} | |||
}, | |||
onShow() { | |||
console.log('onShow') | |||
}, | |||
onLoad(arg) { | |||
this.clickTabs({ index: arg.index || 0 }) | |||
}, | |||
methods: { | |||
// todo: delete | |||
getData() { | |||
this.list = [ | |||
{ | |||
id: '001', | |||
avatar: '/static/image/temp-30.png', | |||
name: '李世海', | |||
price: 10, | |||
createTime: '2025-07-15', | |||
}, | |||
{ | |||
id: '002', | |||
avatar: '/static/image/temp-30.png', | |||
name: '周静', | |||
price: 10, | |||
createTime: '2025-07-15', | |||
}, | |||
{ | |||
id: '003', | |||
avatar: '/static/image/temp-30.png', | |||
name: '周海', | |||
price: 10, | |||
createTime: '2025-07-15', | |||
}, | |||
{ | |||
id: '004', | |||
avatar: '/static/image/temp-30.png', | |||
name: '冯启彬', | |||
price: 10, | |||
createTime: '2025-07-15', | |||
}, | |||
{ | |||
id: '005', | |||
avatar: '/static/image/temp-30.png', | |||
name: '李娉', | |||
price: 10, | |||
createTime: '2025-07-15', | |||
}, | |||
{ | |||
id: '006', | |||
avatar: '/static/image/temp-30.png', | |||
name: '李书萍', | |||
price: 10, | |||
createTime: '2025-07-15', | |||
}, | |||
{ | |||
id: '007', | |||
avatar: '/static/image/temp-30.png', | |||
name: '李世海', | |||
price: 10, | |||
createTime: '2025-07-15', | |||
}, | |||
{ | |||
id: '008', | |||
avatar: '/static/image/temp-30.png', | |||
name: '周静', | |||
price: 10, | |||
createTime: '2025-07-15', | |||
}, | |||
{ | |||
id: '009', | |||
avatar: '/static/image/temp-30.png', | |||
name: '周海', | |||
price: 10, | |||
createTime: '2025-07-15', | |||
}, | |||
{ | |||
id: '010', | |||
avatar: '/static/image/temp-30.png', | |||
name: '冯启彬', | |||
price: 10, | |||
createTime: '2025-07-15', | |||
}, | |||
{ | |||
id: '011', | |||
avatar: '/static/image/temp-30.png', | |||
name: '李娉', | |||
price: 10, | |||
createTime: '2025-07-15', | |||
}, | |||
{ | |||
id: '012', | |||
avatar: '/static/image/temp-30.png', | |||
name: '李书萍', | |||
price: 10, | |||
createTime: '2025-07-15', | |||
}, | |||
] | |||
}, | |||
//点击tab栏 | |||
clickTabs({ index }) { | |||
console.log('clickTabs') | |||
this.current = index | |||
if (index == 0) { | |||
delete this.queryParams.status | |||
} else { | |||
this.queryParams.status = index - 1 | |||
} | |||
this.getData() | |||
}, | |||
}, | |||
} | |||
</script> | |||
<style scoped lang="scss"> | |||
.page__view { | |||
width: 100vw; | |||
min-height: 100vh; | |||
background-color: $uni-bg-color; | |||
position: relative; | |||
/deep/ .nav-bar__view { | |||
position: fixed; | |||
top: 0; | |||
left: 0; | |||
} | |||
} | |||
.main { | |||
padding: calc(var(--status-bar-height) + 244rpx) 32rpx 40rpx 32rpx; | |||
.tabs { | |||
position: fixed; | |||
top: calc(var(--status-bar-height) + 120rpx); | |||
left: 0; | |||
width: 100%; | |||
height: 84rpx; | |||
background: #FFFFFF; | |||
z-index: 1; | |||
/deep/ .uv-tabs__wrapper__nav__line { | |||
border-radius: 2rpx; | |||
} | |||
} | |||
} | |||
.list { | |||
background: #FFFFFF; | |||
border-radius: 32rpx; | |||
overflow: hidden; | |||
&-item { | |||
margin-top: 16rpx; | |||
padding: 16rpx 32rpx; | |||
font-size: 28rpx; | |||
color: #333333; | |||
background: #FFFFFF; | |||
border-bottom: 2rpx solid #F1F1F1; | |||
justify-content: flex-start; | |||
column-gap: 24rpx; | |||
&:last-child { | |||
border: none; | |||
} | |||
.avatar { | |||
width: 72rpx; | |||
height: 72rpx; | |||
border-radius: 50%; | |||
overflow: hidden; | |||
.img { | |||
width: 100%; | |||
height: 100%; | |||
} | |||
} | |||
} | |||
} | |||
</style> |
@ -0,0 +1,233 @@ | |||
<template> | |||
<view class="page__view"> | |||
<navbar title="提现" leftClick @leftClick="$utils.navigateBack" color="#191919" bgColor="#FFFFFF" /> | |||
<view class="main"> | |||
<view class="card"> | |||
<view class="card-header">微信提现</view> | |||
<view class="form"> | |||
<uv-form | |||
ref="form" | |||
:model="form" | |||
:rules="rules" | |||
errorType="toast" | |||
> | |||
<view class="form-item"> | |||
<uv-form-item prop="name" :customStyle="formItemStyle"> | |||
<view class="form-item-label">真实姓名</view> | |||
<view class="form-item-content"> | |||
<formInput v-model="form.name"></formInput> | |||
</view> | |||
</uv-form-item> | |||
</view> | |||
<view class="form-item"> | |||
<uv-form-item prop="amount" :customStyle="formItemStyle"> | |||
<view class="form-item-label">提现金额</view> | |||
<view class="form-item-content"> | |||
<formInput v-model="form.amount"></formInput> | |||
</view> | |||
</uv-form-item> | |||
</view> | |||
</uv-form> | |||
</view> | |||
</view> | |||
<view class="notice"> | |||
请仔细检查并确认相关信息,因用户个人疏忽导致的充值错误。需由用户自行承担。 | |||
<!-- todo: 替换配置项key --> | |||
<text class="highlight" @click="$refs.modal.open('user_ys', '提现须知')">《提现须知》</text> | |||
</view> | |||
<agreementModal ref="modal"></agreementModal> | |||
</view> | |||
<view class="bottom"> | |||
<button class="btn" @click="onSubmit">提现</button> | |||
</view> | |||
</view> | |||
</template> | |||
<script> | |||
import formInput from '@/pages_order/components/formInput.vue' | |||
import agreementModal from '@/pages_order/components/agreementModal.vue' | |||
export default { | |||
components: { | |||
formInput, | |||
agreementModal, | |||
}, | |||
data() { | |||
return { | |||
form: { | |||
name: null, | |||
amount: null, | |||
}, | |||
rules: { | |||
'name': { | |||
type: 'string', | |||
required: true, | |||
message: '请输入真实姓名', | |||
}, | |||
'amount': { | |||
type: 'string', | |||
required: true, | |||
message: '请输入提现金额', | |||
}, | |||
}, | |||
formItemStyle: { padding: 0 }, | |||
} | |||
}, | |||
methods: { | |||
async onSubmit() { | |||
try { | |||
await this.$refs.form.validate() | |||
const { | |||
} = this.form | |||
const params = { | |||
} | |||
// todo: fetch | |||
// await this.$fetch('updateAddress', params) | |||
uni.showToast({ | |||
icon: 'success', | |||
title: '提交成功', | |||
}); | |||
setTimeout(() => { | |||
this.$utils.navigateBack() | |||
}, 800) | |||
} catch (err) { | |||
console.log('onSave err', err) | |||
} | |||
}, | |||
}, | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
.page__view { | |||
width: 100vw; | |||
min-height: 100vh; | |||
background: unset; | |||
position: relative; | |||
/deep/ .nav-bar__view { | |||
position: fixed; | |||
top: 0; | |||
left: 0; | |||
} | |||
} | |||
.main { | |||
padding: calc(var(--status-bar-height) + 144rpx) 32rpx 224rpx 32rpx; | |||
} | |||
.card { | |||
padding: 32rpx; | |||
background: #FFFFFF; | |||
border: 2rpx solid #FFFFFF; | |||
border-radius: 32rpx; | |||
& + & { | |||
margin-top: 40rpx; | |||
} | |||
&-header { | |||
font-family: PingFang SC; | |||
font-weight: 500; | |||
font-size: 36rpx; | |||
line-height: 1.4; | |||
color: #252545; | |||
margin-bottom: 32rpx; | |||
} | |||
} | |||
.form { | |||
padding: 8rpx 0 0 0; | |||
&-item { | |||
border-bottom: 2rpx solid #EEEEEE; | |||
&:last-child { | |||
border: none; | |||
} | |||
& + & { | |||
margin-top: 40rpx; | |||
} | |||
&-label { | |||
font-family: PingFang SC; | |||
font-weight: 400; | |||
font-size: 26rpx; | |||
line-height: 1.4; | |||
color: #181818; | |||
} | |||
&-content { | |||
margin-top: 14rpx; | |||
padding: 6rpx 0; | |||
.text { | |||
padding: 2rpx 0; | |||
font-family: PingFang SC; | |||
font-weight: 400; | |||
font-size: 32rpx; | |||
line-height: 1.4; | |||
&.placeholder { | |||
color: #C6C6C6; | |||
} | |||
} | |||
} | |||
} | |||
} | |||
.notice { | |||
margin-top: 40rpx; | |||
font-size: 24rpx; | |||
line-height: 1.4; | |||
color: #BABABA; | |||
.highlight { | |||
color: #F79400; | |||
} | |||
} | |||
.bottom { | |||
position: fixed; | |||
left: 0; | |||
bottom: 0; | |||
width: 100vw; | |||
// height: 200rpx; | |||
padding: 24rpx 40rpx; | |||
padding-bottom: calc(env(safe-area-inset-bottom) + 24rpx); | |||
background: #FFFFFF; | |||
box-sizing: border-box; | |||
.btn { | |||
width: 100%; | |||
padding: 14rpx 0; | |||
box-sizing: border-box; | |||
font-family: PingFang SC; | |||
font-weight: 500; | |||
font-size: 36rpx; | |||
line-height: 1; | |||
color: #FFFFFF; | |||
background: linear-gradient(to right, #21FEEC, #019AF9); | |||
border: 2rpx solid #00A9FF; | |||
border-radius: 41rpx; | |||
} | |||
} | |||
</style> |