alipay-emulator/pages/ant-credit-pay/index.vue

802 lines
18 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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="main-container">
<NavBar v-if="!selectedImage" title="花呗" :bgColor="data.navBar.bgColor" :buttonGroup="buttonGroup"
@button-click="clickTitlePopupButton">
<view class="nav-bar flex-between w100" :class="{ 'ios-nav-bar': $system == 'iOS' }">
<view class="flex-align-center flex-1">
<view class="left" @click.stop="goBack">
<image class=" back-icon" src="/static/image/nav-bar/back-white.png" mode="">
</image>
</view>
<view class="title ">
花呗
</view>
</view>
<view class="right">
<image class="icon" src="/static/image/ant-credit-pay/service.png"></image>
<image class="icon" src="/static/image/ant-credit-pay/setting.png"></image>
</view>
</view>
</NavBar>
<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">
<text class="money alipay-font">{{ numberUtil.formatMoneyWithThousand(huabeiInfo.money) }}</text>
<uni-icons type="right" size="16" color="#B9D6FF"></uni-icons>
</view>
<view v-if="huabeiInfo.styleType == 1 || !huabeiInfo.styleType" class="style-1 button-group">
<view class="button-item second-button" :class="{ 'ios-button': $system == 'iOS' }">立即还款</view>
<view v-if="!huabeiInfo.isInstallment" class="button-item primary-button"
:class="{ 'ios-button': $system == 'iOS' }">
分期还款
<view v-if="huabeiInfo.installmentBadgeText" class="badge">{{ huabeiInfo.installmentBadgeText }}
</view>
</view>
</view>
<view v-if="huabeiInfo.styleType == 2" class="style-2 bubble-container">
<view class="bubble-box">
<view class="arrow"></view>
<text class="text">{{ huabeiInfo.descText }}</text>
</view>
</view>
<view v-if="huabeiInfo.styleType == 3" class="style-3 bubble-container">
<view class="bubble-box">
<view class="arrow"></view>
<text class="text flex-align-center">{{ huabeiInfo.descText }}
<uni-icons type="right" size="16" color="#B9D6FF"></uni-icons>
</text>
</view>
</view>
<view class="total-info-box flex-align-center">
<view class="info-item">
<view class="label">总计账单</view>
<view class="value">还款日每月{{ huabeiInfo.dueDate }}日</view>
</view>
<view class="info-item">
<view class="label">总计额度</view>
<view class="value">{{
numberUtil.formatMoneyWithThousand(Number(huabeiInfo.totalAmount) - Number(huabeiInfo.money))
}}可用
</view>
</view>
</view>
</view>
<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;"
src="/static/image/common/upload-screenshot.png"></image>
<text style="font-size: 36rpx;color: #1777FF;">长按替换真实截图</text>
</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">
<view class="popup-content">
<view class="popup-title">编辑花呗数据</view>
<view class="form-item">
<text class="label">还款月份</text>
<picker :range="monthRange" :value="editHuabeiInfo.mouth - 1" @change="onMonthChange"
style="flex:1">
<view class="input">{{ editHuabeiInfo.mouth ? editHuabeiInfo.mouth + '月' : '请选择月份' }}</view>
</picker>
</view>
<view class="form-item">
<text class="label">本月应还</text>
<input class="input" type="digit" v-model="editHuabeiInfo.money" placeholder="请输入金额" />
</view>
<view class="form-item">
<text class="label">还款日</text>
<input class="input" type="number" v-model="editHuabeiInfo.dueDate" placeholder="请输入日期" />
</view>
<view class="form-item">
<text class="label">总计额度</text>
<input class="input" type="digit" v-model="editHuabeiInfo.totalAmount" placeholder="请输入总计额度" />
</view>
<view class="form-item">
<text class="label">气泡文本</text>
<input class="input" type="text" v-model="editHuabeiInfo.descText" placeholder="请输入描述文本" />
</view>
<view class="form-item">
<text class="label">分期气泡</text>
<input class="input" type="text" v-model="editHuabeiInfo.installmentBadgeText"
placeholder="请输入分期还款气泡文案" />
</view>
<view class="form-item flex-between">
<text class="label">是否分期</text>
<switch :checked="editHuabeiInfo.isInstallment" style="transform: scale(0.8);"
@change="e => editHuabeiInfo.isInstallment = e.detail.value" />
</view>
<view class="popup-btns">
<view class="btn cancel" @click="closeDialog">取消</view>
<view class="btn confirm" @click="confirmDialog">确定</view>
</view>
</view>
</uni-popup>
<!-- 样式选择弹窗 -->
<uni-popup ref="stylePopup" type="center">
<view class="popup-content">
<view class="popup-title">选择展示样式</view>
<view class="style-list">
<view class="style-item" v-for="(item, index) in styleList" :key="index"
@click="confirmStyleDialog(item.value)">
<text>{{ item.label }}</text>
<uni-icons v-if="huabeiInfo.styleType == item.value" type="checkmarkempty" size="20"
color="#1777FF"></uni-icons>
</view>
</view>
<view class="popup-btns">
<view class="btn cancel" @click="closeStyleDialog">取消</view>
</view>
</view>
</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>
</template>
<script setup>
import NavBar from '@/components/nav-bar/nav-bar'
import {
numberUtil
} from '@/utils/common.js'
import {
ref,
toRefs,
getCurrentInstance,
reactive
} from 'vue';
import {
onLoad
} from '@dcloudio/uni-app';
const instance = getCurrentInstance();
const buttonGroup = [{
name: "编辑花呗数据",
click: () => {
openDialog()
}
}, {
name: "切换展示样式",
click: () => {
openStyleDialog()
}
}, {
name: "删除当前底部图片",
click: () => {
data.huabeiInfo.image = ""
uni.setStorageSync(data.huabeiInfoStorageKey, data.huabeiInfo)
}
}]
const data = reactive({
navBar: {
bgColor: "#1777FF",
textColor: "#fff"
},
huabeiInfoStorageKey: 'huabei_info_storage',
huabeiInfo: {
mouth: 1,
money: 100,
dueDate: 15,
totalAmount: 200000,
descText: "当前账单进度已超出预期,花超了",
isInstallment: false,
styleType: 1,
installmentBadgeText: '4折起',
image: "",
isOverdue: false
},
selectedImage: '',
showMask: false
})
let {
huabeiInfo,
selectedImage,
showMask
} = toRefs(data)
// 编辑表单数据
const editHuabeiInfo = ref({})
const popup = ref(null)
const stylePopup = ref(null)
const scrollTop = ref(0)
const onImageScroll = (e) => {
scrollTop.value = e.detail.scrollTop
}
const styleList = [{
label: '样式 1 (默认)',
value: 1
},
{
label: '样式 2 (纯气泡)',
value: 2
},
{
label: '样式 3 (带箭头气泡)',
value: 3
}
]
const monthRange = Array.from({
length: 12
}, (_, i) => i + 1)
const onMonthChange = (e) => {
editHuabeiInfo.value.mouth = monthRange[e.detail.value]
}
onLoad((option) => {
console.log(option)
// 读取缓存
let savedInfo = uni.getStorageSync(data.huabeiInfoStorageKey)
// savedInfo.image = ""
// uni.setStorageSync(data.huabeiInfoStorageKey, savedInfo)
console.log("savedInfo====", savedInfo)
if (savedInfo) {
// 合并默认值,防止旧数据缺少新字段
data.huabeiInfo = {
...data.huabeiInfo,
...savedInfo
}
}
})
// 打开弹窗
const openDialog = () => {
// 深拷贝当前数据到编辑表单
editHuabeiInfo.value = JSON.parse(JSON.stringify(data.huabeiInfo))
popup.value.open()
}
// 关闭弹窗
const closeDialog = () => {
popup.value.close()
}
// 确认修改
const confirmDialog = () => {
data.huabeiInfo = JSON.parse(JSON.stringify(editHuabeiInfo.value))
// 保存到缓存
uni.setStorageSync(data.huabeiInfoStorageKey, data.huabeiInfo)
popup.value.close()
uni.showToast({
title: '保存成功',
icon: 'success'
})
}
// 打开样式选择弹窗
const openStyleDialog = () => {
stylePopup.value.open()
}
// 关闭样式选择弹窗
const closeStyleDialog = () => {
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) => {
data.huabeiInfo.styleType = type
// 保存到缓存
uni.setStorageSync(data.huabeiInfoStorageKey, data.huabeiInfo)
stylePopup.value.close()
}
const clickTitlePopupButton = (button) => {
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>
<style>
@import "/common/main.css";
</style>
<style lang="less" scoped>
.page-container {
position: relative;
display: flex;
flex-direction: column;
background-color: #ffffff;
height: 100vh;
overflow: hidden;
.nav-bar {
height: 100%;
display: flex;
align-items: center;
padding: 0 20rpx;
.title {
color: #ffffff;
font-size: 17px;
font-weight: 500;
text-align: center;
}
.right {
width: 80px;
}
.icon {
margin-left: 18rpx;
margin-right: 10rpx;
width: 24px;
height: 24px;
}
}
.ios-nav-bar {
.left {
width: 80px;
}
.title {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
}
.right {
width: 80px;
}
}
.main-container {
background-color: #1777FF;
padding-bottom: 32rpx;
// position: absolute;
// top: 0;
// left: 0;
// right: 0;
// z-index: 99;
.current-month {
margin-top: 12rpx;
color: #B9D6FF;
font-size: 24rpx;
text-align: center;
}
.money-box {
text-align: center;
justify-content: center;
.money {
font-size: 64rpx;
color: #ffffff;
}
}
.button-group {
margin-top: 18rpx;
display: flex;
justify-content: center;
justify-content: center;
.button-item {
height: 64rpx;
line-height: 64rpx;
border-radius: 8rpx;
color: #ffffff;
font-size: 30rpx;
padding: 0 24rpx;
}
.second-button {
border: 2rpx solid #66B2FD;
}
.primary-button {
background-color: #66B2FD;
margin: 0 22rpx;
position: relative;
.badge {
position: absolute;
top: -16rpx;
right: -10rpx;
background-color: #F34624;
color: #fff;
font-size: 20rpx;
padding: 0 10rpx;
height: 32rpx;
line-height: 32rpx;
border-radius: 16rpx 16rpx 16rpx 0;
z-index: 1;
}
}
.ios-button {
border-radius: 33rpx !important;
}
}
.style-1 {
margin-top: 18rpx;
display: flex;
justify-content: center;
justify-content: center;
}
.bubble-container {
margin-top: 10rpx;
display: flex;
justify-content: center;
.bubble-box {
position: relative;
border: 1px solid #75bcff;
border-radius: 30rpx;
padding: 12rpx 24rpx;
display: flex;
align-items: center;
justify-content: center;
.text {
color: #ffffff;
font-size: 26rpx;
line-height: 1.2;
}
.arrow {
position: absolute;
top: -5px;
left: 50%;
transform: translateX(-50%) rotate(45deg);
width: 8px;
height: 8px;
background-color: #1777FF;
border-left: 1px solid #75bcff;
border-top: 1px solid #75bcff;
z-index: 1;
}
}
}
.total-info-box {
margin-top: 48rpx;
.info-item {
width: 50%;
text-align: center;
.label {
color: #ffffff;
font-size: 30rpx;
line-height: 42rpx;
}
.value {
color: #ffffff;
font-size: 26rpx;
line-height: 36rpx;
}
}
}
}
.image-box {
width: 100%;
overflow: hidden; // scroll-view 需要
}
.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;
}
}
}
.back-icon {
width: 24px;
height: 24px;
}
</style>
<style lang="scss" scoped>
.popup-content {
background-color: #fff;
width: 600rpx;
border-radius: 16rpx;
padding: 30rpx;
.popup-title {
font-size: 32rpx;
font-weight: bold;
text-align: center;
margin-bottom: 30rpx;
}
.form-item {
display: flex;
align-items: center;
margin-bottom: 24rpx;
.label {
width: 140rpx;
font-size: 28rpx;
color: #333;
}
.input {
flex: 1;
height: 72rpx;
line-height: 72rpx;
background-color: #f5f5f5;
border-radius: 8rpx;
padding: 0 20rpx;
font-size: 28rpx;
}
}
.flex-between {
justify-content: space-between;
}
.popup-btns {
display: flex;
margin-top: 40rpx;
.btn {
flex: 1;
height: 80rpx;
line-height: 80rpx;
text-align: center;
border-radius: 12rpx;
font-size: 30rpx;
&.cancel {
background-color: #f5f5f5;
color: #666;
margin-right: 20rpx;
}
&.confirm {
background-color: #1777FF;
color: #fff;
}
}
}
}
.style-list {
.style-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx 0;
border-bottom: 1px solid #f5f5f5;
font-size: 30rpx;
color: #333;
&:last-child {
border-bottom: none;
}
}
}
</style>