花呗主页完成

This commit is contained in:
tangxinyue 2026-01-21 15:26:23 +08:00
parent c6e77c74a8
commit 44625d9e5d
3 changed files with 405 additions and 128 deletions

View File

@ -59,7 +59,7 @@
"weixin" : { "weixin" : {
"__platform__" : [ "ios", "android" ], "__platform__" : [ "ios", "android" ],
"appid" : "123456", "appid" : "123456",
"UniversalLinks": "123456" "UniversalLinks" : "https://hhhhh.com/apple-app-site-association/"
}, },
"alipay" : { "alipay" : {
"__platform__" : [ "ios", "android" ] "__platform__" : [ "ios", "android" ]

View File

@ -1,16 +1,23 @@
<template> <template>
<!-- 水印 -->
<view v-if="$isVip()">
<watermark :dark="data.dark" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark')">
<c-lottie ref="cLottieRef" :src='$watermark()' width="94px" height='74px' :loop="true"></c-lottie>
</liu-drag-button>
</view>
<view class="page-container"> <view class="page-container">
<view class="main-container"> <view class="main-container">
<NavBar title="花呗|信用购" :bgColor="data.navBar.bgColor" :buttonGroup="buttonGroup" <NavBar v-if="!selectedImage" title="花呗" :bgColor="data.navBar.bgColor" :buttonGroup="buttonGroup"
@button-click="clickTitlePopupButton"> @button-click="clickTitlePopupButton">
<view class="nav-bar flex-between w100" :class="{ 'ios-nav-bar': $system == 'iOS' }"> <view class="nav-bar flex-between w100" :class="{ 'ios-nav-bar': $system == 'iOS' }">
<view class="flex-align-center flex-1"> <view class="flex-align-center flex-1">
<view class="left"> <view class="left" @click.stop="goBack">
<image class=" back-icon" src="/static/image/nav-bar/back-white.png" mode=""> <image class=" back-icon" src="/static/image/nav-bar/back-white.png" mode="">
</image> </image>
</view> </view>
<view class="title "> <view class="title ">
花呗|信用购 花呗
</view> </view>
</view> </view>
@ -20,7 +27,10 @@
</view> </view>
</view> </view>
</NavBar> </NavBar>
<view class="current-month">{{ huabeiInfo.mouth }}月应还()</view> <NavBar v-else title="拼图" bgColor="#EFEFEF" isRightButton @right-click="confirmImage">
</NavBar>
<view v-if="huabeiInfo.styleType == 1" class="current-month">{{ huabeiInfo.mouth }}月应还()</view>
<view v-else class="current-month">{{ huabeiInfo.mouth }}月账单累计中()</view>
<view class="money-box flex-align-center"> <view class="money-box flex-align-center">
<text class="money alipay-font">{{ numberUtil.formatMoneyWithThousand(huabeiInfo.money) }}</text> <text class="money alipay-font">{{ numberUtil.formatMoneyWithThousand(huabeiInfo.money) }}</text>
<uni-icons type="right" size="16" color="#B9D6FF"></uni-icons> <uni-icons type="right" size="16" color="#B9D6FF"></uni-icons>
@ -56,16 +66,35 @@
<view class="info-item"> <view class="info-item">
<view class="label">总计额度</view> <view class="label">总计额度</view>
<view class="value">{{ <view class="value">{{
numberUtil.formatMoneyWithThousand(Number(huabeiInfo.totalAmount) - Number(huabeiInfo.money)) }}可用 numberUtil.formatMoneyWithThousand(Number(huabeiInfo.totalAmount) - Number(huabeiInfo.money))
}}可用
</view> </view>
</view> </view>
</view> </view>
</view> </view>
<view class="image-box flex-1"> <view v-if="!selectedImage" class="image-box flex-1 flex-align-center flex-column flex-justify-center"
@longpress="chooseImage">
<view v-if="!huabeiInfo.image" class="flex-align-center flex-column">
<image style="width:92rpx; height: 92rpx;margin-top: 16rpx;" <image style="width:92rpx; height: 92rpx;margin-top: 16rpx;"
src="/static/image/common/upload-screenshot.png"></image> src="/static/image/common/upload-screenshot.png"></image>
<text style="font-size: 36rpx;color: #1777FF;">长按替换真实截图</text> <text style="font-size: 36rpx;color: #1777FF;">长按替换真实截图</text>
</view> </view>
<view v-else class="w100 h100">
<image class="w100 h100" :src="huabeiInfo.image" mode="widthFix"></image>
</view>
</view>
<view v-else class="scroll-image-box flex-1">
<scroll-view class="image-box h100" style="width: 100%;" scroll-y :show-scrollbar="false"
@scroll="onImageScroll">
<image class="crop-image-target" style="width:100%;" :src="selectedImage" mode="widthFix"></image>
</scroll-view>
<view class="dashed-line-box">
<view class="dashed-line-text">我是分割线</view>
</view>
</view>
<canvas canvas-id="crop-canvas"
style="position: fixed; left: -9999px; width: 750rpx; height: 100vh; pointer-events: none;"></canvas>
<!-- 编辑弹窗 --> <!-- 编辑弹窗 -->
<uni-popup ref="popup" type="center" :mask-click="false"> <uni-popup ref="popup" type="center" :mask-click="false">
<view class="popup-content"> <view class="popup-content">
@ -90,7 +119,7 @@
<input class="input" type="digit" v-model="editHuabeiInfo.totalAmount" placeholder="请输入总计额度" /> <input class="input" type="digit" v-model="editHuabeiInfo.totalAmount" placeholder="请输入总计额度" />
</view> </view>
<view class="form-item"> <view class="form-item">
<text class="label">描述文本</text> <text class="label">气泡文本</text>
<input class="input" type="text" v-model="editHuabeiInfo.descText" placeholder="请输入描述文本" /> <input class="input" type="text" v-model="editHuabeiInfo.descText" placeholder="请输入描述文本" />
</view> </view>
<view class="form-item"> <view class="form-item">
@ -128,14 +157,31 @@
</view> </view>
</view> </view>
</uni-popup> </uni-popup>
<!-- 蒙层 -->
<view v-if="showMask" class="mask" @click="closeMask">
<image class="mask-icon" src="/static/image/common/mask-icon.png" mode="widthFix">
</image>
</view>
</view> </view>
</template> </template>
<script setup> <script setup>
import NavBar from '@/components/nav-bar/nav-bar' import NavBar from '@/components/nav-bar/nav-bar'
import { numberUtil } from '@/utils/common.js' import {
import { ref, toRefs } from 'vue'; numberUtil
import { onLoad } from '@dcloudio/uni-app'; } from '@/utils/common.js'
import {
ref,
toRefs,
getCurrentInstance,
reactive
} from 'vue';
import {
onLoad
} from '@dcloudio/uni-app';
const instance = getCurrentInstance();
const buttonGroup = [{ const buttonGroup = [{
name: "编辑花呗数据", name: "编辑花呗数据",
@ -147,13 +193,20 @@ const buttonGroup = [{
click: () => { click: () => {
openStyleDialog() openStyleDialog()
} }
}, {
name: "删除当前底部图片",
click: () => {
data.huabeiInfo.image = ""
uni.setStorageSync(data.huabeiInfoStorageKey, data.huabeiInfo)
}
}] }]
const data = ref({ const data = reactive({
navBar: { navBar: {
bgColor: "#1777FF", bgColor: "#1777FF",
textColor: "#fff" textColor: "#fff"
}, },
huabeiInfoStorageKey: 'huabei_info_storage',
huabeiInfo: { huabeiInfo: {
mouth: 1, mouth: 1,
money: 100, money: 100,
@ -162,25 +215,47 @@ const data = ref({
descText: "当前账单进度已超出预期,花超了", descText: "当前账单进度已超出预期,花超了",
isInstallment: false, isInstallment: false,
styleType: 1, styleType: 1,
installmentBadgeText: '4折起' installmentBadgeText: '4折起',
image: "",
isOverdue: false
}, },
huabeiInfoStorageKey: 'huabei_info_storage' selectedImage: '',
showMask: false
}) })
let { huabeiInfo } = toRefs(data.value) let {
huabeiInfo,
selectedImage,
showMask
} = toRefs(data)
// //
const editHuabeiInfo = ref({}) const editHuabeiInfo = ref({})
const popup = ref(null) const popup = ref(null)
const stylePopup = ref(null) const stylePopup = ref(null)
const styleList = [ const scrollTop = ref(0)
{ label: '样式 1 (默认)', value: 1 }, const onImageScroll = (e) => {
{ label: '样式 2 (纯气泡)', value: 2 }, scrollTop.value = e.detail.scrollTop
{ label: '样式 3 (带箭头气泡)', value: 3 } }
const styleList = [{
label: '样式 1 (默认)',
value: 1
},
{
label: '样式 2 (纯气泡)',
value: 2
},
{
label: '样式 3 (带箭头气泡)',
value: 3
}
] ]
const monthRange = Array.from({ length: 12 }, (_, i) => i + 1) const monthRange = Array.from({
length: 12
}, (_, i) => i + 1)
const onMonthChange = (e) => { const onMonthChange = (e) => {
editHuabeiInfo.value.mouth = monthRange[e.detail.value] editHuabeiInfo.value.mouth = monthRange[e.detail.value]
} }
@ -188,17 +263,23 @@ const onMonthChange = (e) => {
onLoad((option) => { onLoad((option) => {
console.log(option) console.log(option)
// //
const savedInfo = uni.getStorageSync(data.value.huabeiInfoStorageKey) let savedInfo = uni.getStorageSync(data.huabeiInfoStorageKey)
// savedInfo.image = ""
// uni.setStorageSync(data.huabeiInfoStorageKey, savedInfo)
console.log("savedInfo====", savedInfo)
if (savedInfo) { if (savedInfo) {
// //
data.value.huabeiInfo = { ...data.value.huabeiInfo, ...savedInfo } data.huabeiInfo = {
...data.huabeiInfo,
...savedInfo
}
} }
}) })
// //
const openDialog = () => { const openDialog = () => {
// //
editHuabeiInfo.value = JSON.parse(JSON.stringify(data.value.huabeiInfo)) editHuabeiInfo.value = JSON.parse(JSON.stringify(data.huabeiInfo))
popup.value.open() popup.value.open()
} }
@ -209,9 +290,9 @@ const closeDialog = () => {
// //
const confirmDialog = () => { const confirmDialog = () => {
data.value.huabeiInfo = JSON.parse(JSON.stringify(editHuabeiInfo.value)) data.huabeiInfo = JSON.parse(JSON.stringify(editHuabeiInfo.value))
// //
uni.setStorageSync(data.value.huabeiInfoStorageKey, data.value.huabeiInfo) uni.setStorageSync(data.huabeiInfoStorageKey, data.huabeiInfo)
popup.value.close() popup.value.close()
uni.showToast({ uni.showToast({
title: '保存成功', title: '保存成功',
@ -229,18 +310,158 @@ const closeStyleDialog = () => {
stylePopup.value.close() stylePopup.value.close()
} }
//
const confirmImage = () => {
uni.showLoading({
title: '处理中...'
})
const query = uni.createSelectorQuery().in(instance)
//
query.select('.image-box').boundingClientRect()
query.select('.crop-image-target').boundingClientRect()
query.exec(res => {
if (!res[0] || !res[1]) {
uni.hideLoading()
return
}
console.log('rects', res)
const container = res[0] //
const image = res[1] //
// ( / /?)
// canvas
// canvas drawImage : img, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight
//
uni.getImageInfo({
src: selectedImage.value,
success: (imgInfo) => {
const scale = imgInfo.width / image.width // 图片 原始宽 / 渲染宽
const sTop = scrollTop.value * scale // Y
const sHeight = container.height * scale //
// widthFix
const sWidth = imgInfo.width
// (使)
// canvasContext使 pixelRatio
// uni-app canvas-id (px)
// canvas
const canvasW = container.width
const canvasH = container.height
const ctx = uni.createCanvasContext('crop-canvas', instance)
//
ctx.clearRect(0, 0, canvasW, canvasH)
//
// drawImage(imageResource, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
ctx.drawImage(
imgInfo.path,
0, sTop, sWidth, sHeight, //
0, 0, canvasW, canvasH //
)
ctx.draw(false, () => {
uni.canvasToTempFilePath({
canvasId: 'crop-canvas',
width: canvasW,
height: canvasH,
destWidth: canvasW * 2, //
destHeight: canvasH * 2,
success: (res) => {
console.log('crop success (temp)', res
.tempFilePath)
//
uni.saveFile({
tempFilePath: res.tempFilePath,
success: (saveRes) => {
console.log(
'save success (saved)',
saveRes
.savedFilePath)
data.huabeiInfo.image =
saveRes.savedFilePath
selectedImage.value =
'' //
//
uni.setStorageSync(data
.huabeiInfoStorageKey,
data.huabeiInfo)
uni.hideLoading()
},
fail: (err) => {
console.error(
'saveFile fail',
err)
uni.hideLoading()
uni.showToast({
title: '保存失败',
icon: 'none'
})
}
})
},
fail: (err) => {
console.error(err)
uni.hideLoading()
uni.showToast({
title: '裁剪失败',
icon: 'none'
})
}
}, instance)
})
},
fail: () => {
uni.hideLoading()
uni.showToast({
title: '图片加载失败',
icon: 'none'
})
}
})
})
}
// //
const confirmStyleDialog = (type) => { const confirmStyleDialog = (type) => {
data.value.huabeiInfo.styleType = type data.huabeiInfo.styleType = type
// //
uni.setStorageSync(data.value.huabeiInfoStorageKey, data.value.huabeiInfo) uni.setStorageSync(data.huabeiInfoStorageKey, data.huabeiInfo)
stylePopup.value.close() stylePopup.value.close()
} }
//
const clickTitlePopupButton = (button) => { const clickTitlePopupButton = (button) => {
button.click() button.click()
} }
//
const chooseImage = () => {
if (selectedImage.value) return
uni.chooseImage({
count: 1,
sourceType: ['album'],
success: (res) => {
selectedImage.value = res.tempFilePaths[0]
data.showMask = true
}
})
}
const closeMask = () => {
data.showMask = false
}
const goBack = () => {
uni.navigateBack()
}
</script> </script>
<style> <style>
@ -249,6 +470,7 @@ const clickTitlePopupButton = (button) => {
<style lang="less" scoped> <style lang="less" scoped>
.page-container { .page-container {
position: relative;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
background-color: #ffffff; background-color: #ffffff;
@ -300,6 +522,11 @@ const clickTitlePopupButton = (button) => {
.main-container { .main-container {
background-color: #1777FF; background-color: #1777FF;
padding-bottom: 32rpx; padding-bottom: 32rpx;
// position: absolute;
// top: 0;
// left: 0;
// right: 0;
// z-index: 99;
.current-month { .current-month {
margin-top: 12rpx; margin-top: 12rpx;
@ -427,10 +654,60 @@ const clickTitlePopupButton = (button) => {
} }
.image-box { .image-box {
display: flex; width: 100%;
flex-direction: column; overflow: hidden; // scroll-view
align-items: center; }
justify-content: center;
.scroll-image-box {
width: 100%;
min-height: 0; // flex
// overflow: hidden; // scroll-view
position: relative;
}
.dashed-line-box {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
border: 4rpx dashed #ffffff;
pointer-events: none;
.dashed-line-text {
height: 44rpx;
line-height: 44rpx;
width: 180rpx;
padding: 0 20rpx;
border-radius: 8rpx;
color: #1777FF;
font-size: 24rpx;
font-weight: 500;
background-color: #fff;
position: absolute;
top: 0;
left: 50%;
transform: translate(-50%, -50%);
}
}
.mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.8);
z-index: 999;
.mask-icon {
position: absolute;
top: 50%;
right: 52rpx;
transform: translateY(-25%);
width: 360rpx;
height: 360rpx;
}
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB