1209 lines
29 KiB
Vue
1209 lines
29 KiB
Vue
<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="container flex flex-column" @touchstart="handleTouchStart" @touchmove.stop.prevent="handleTouchMove"
|
||
@touchend="handleTouchEnd">
|
||
<!-- 下层信息盒子 -->
|
||
<view class="main-info-container" :class="{ 'open': isOpen }"
|
||
:style="{ 'padding-bottom': `${cardArrowHeight}px` }">
|
||
<!-- 导航栏 placeholder -->
|
||
<NavBar v-if="!selectedImage" bgColor="transparent" :buttonGroup="buttonGroup"
|
||
@button-click="clickTitlePopupButton">
|
||
<view class="nav-bar flex-align-center h100">
|
||
<view class="left flex-align-center">
|
||
<image src="/static/image/finance-management/search/search-left.png"></image>
|
||
</view>
|
||
<view class="nav-bar-search flex-align-center flex-1">
|
||
<image class="search-icon" src="/static/image/bill/bill-list/search-black.png" mode=""></image>
|
||
<input type="text" class="search-input flex-1" placeholder="小而美的基金" />
|
||
<view class="line h100"></view>
|
||
<view class="search-button">搜索</view>
|
||
</view>
|
||
<view class="right flex-align-center">
|
||
<image src="/static/image/finance-management/search/search-right.png"></image>
|
||
</view>
|
||
</view>
|
||
</NavBar>
|
||
<NavBar v-else title="拼图" bgColor="#EFEFEF" noBack @back="closeImage" isRightButton
|
||
@right-click="confirmImage">
|
||
</NavBar>
|
||
|
||
<view class="total-money-box flex-align-center flex-justify-between">
|
||
<!-- 总资产信息 -->
|
||
<view class="revenue-box flex-1">
|
||
<view class="total-money flex-align-center">
|
||
<view class="total-money-title flex-align-center">
|
||
<text>总资产(元)</text>
|
||
<image class="eye-icon" src="/static/image/finance-management/eye.png"></image>
|
||
<view class="flex-align-center" style="color: #A39797;font-size: 22rpx;">
|
||
<image class="safe-icon" src="/static/image/finance-management/safe.png"></image>
|
||
<text>免费升级保障</text>
|
||
</view>
|
||
</view>
|
||
<view class="total-money-title flex-align-center">
|
||
<text>昨日收益</text>
|
||
</view>
|
||
</view>
|
||
<view class="total-money flex-align-center" style="margin-top: 10px;">
|
||
<view class="total-money-value flex-align-center">
|
||
<text class="alipay-font">{{ numberUtil.formatMoneyWithThousand(totalMoney) }}</text>
|
||
</view>
|
||
<view class="total-money-value flex-align-center">
|
||
<text class="alipay-font">{{ yesterdayIncome > 0 ? "+" +
|
||
numberUtil.formatMoneyWithThousand(yesterdayIncome) :
|
||
numberUtil.formatMoneyWithThousand(yesterdayIncome)
|
||
}}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="family-protection">
|
||
<view class="total-money-title flex-align-center">
|
||
<text>家庭保障</text>
|
||
</view>
|
||
<view class=" flex-align-end" style="margin-top: 10px;">
|
||
<text class="value alipay-font">{{ familyProtection }}</text>
|
||
<text class="text">份</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<!-- 财富信息 -->
|
||
<view class="data-box">
|
||
<view v-for="item in myAssets" :key="item.title">
|
||
<view v-if="item.title" class="title second-color">{{ item.title }}</view>
|
||
<view class="group-data flex-justify-between">
|
||
<template v-for="subItem in item.items" :key="subItem.label">
|
||
<view
|
||
v-if="financeInfo[item.key][subItem.key].labelSub && (financeInfo[item.key][subItem.key].labelSub > 0 || financeInfo[item.key][subItem.key].type == 'text')"
|
||
class="item flex-align-center flex-justify-between">
|
||
<view class="flex-align-center">
|
||
<text class="label primary-color">{{ subItem.label }}</text>
|
||
<text v-if="financeInfo[item.key][subItem.key].type == 'text'"
|
||
class="label-sub second-color">
|
||
{{ formatTextWithNumber(financeInfo[item.key][subItem.key].labelSub) }}
|
||
</text>
|
||
<text v-else class="label-sub second-color">
|
||
{{
|
||
numberUtil.formatMoneyWithThousand(financeInfo[item.key][subItem.key].labelSub)
|
||
}}</text>
|
||
</view>
|
||
|
||
<text v-if="financeInfo[item.key][subItem.key].value" class="value primary-color">{{
|
||
financeInfo[item.key][subItem.key].value > 0 ? "+" +
|
||
numberUtil.formatMoneyWithThousand(financeInfo[item.key][subItem.key].value) :
|
||
numberUtil.formatMoneyWithThousand(financeInfo[item.key][subItem.key].value)
|
||
}}</text>
|
||
</view>
|
||
</template>
|
||
|
||
<view v-if="item.groupId == '1'" class="item flex-align-center flex-justify-between">
|
||
<view class="flex-align-center">
|
||
<text class="label primary-color">更多资产服务</text>
|
||
</view>
|
||
<text class="value primary-color">去看看</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 图片盒子 -->
|
||
<view class="image-service-box flex-column"
|
||
:style="{ top: topHeight + 'px', 'height': `calc(100vh - ${imageCardBoxtopHeight}px)`, transition: isDragging || !isReady ? 'none' : 'top 0.3s ease-out' }">
|
||
<view class="card-arrow-box">
|
||
<view class="arrow-box">
|
||
<image @click="data.isOpen = !data.isOpen" class="arrow-icon"
|
||
:src="`/static/image/finance-management/arrow-${isOpen ? 'up' : 'down'}.png`">
|
||
</image>
|
||
</view>
|
||
|
||
<view class="card-box flex-column">
|
||
|
||
<view class="wealth-card-box flex">
|
||
<!-- 占位图片设置为透明 -->
|
||
<image class="wealth-card-opacity0" src="/static/image/finance-management/v1/bg.png"
|
||
mode="widthFix" @load="updateTopHeight" />
|
||
<!-- 背景图片高度自适应 -->
|
||
<image class="wealth-card"
|
||
:src="`/static/image/finance-management/${financeInfo.vipLevel}/bg.png`" mode="widthFix" />
|
||
|
||
<view class="card-info">
|
||
<image class="logo"
|
||
:src="`/static/image/finance-management/${financeInfo.vipLevel}/logo.png`"
|
||
mode="scaleToFill" />
|
||
<view class="name-box flex-align-center">
|
||
<image class="card-holder-icon"
|
||
:src="`/static/image/finance-management/${financeInfo.vipLevel}/card-holder.png`"
|
||
mode="scaleToFill" />
|
||
<view class="card-holder-name sacramento-font">{{ financeInfo.cardHolderName }}</view>
|
||
</view>
|
||
|
||
</view>
|
||
<view class="enjoy-benefits flex-align-center">
|
||
<text class="benefits-text">{{ financeInfo.benefitsText }}</text>
|
||
<image class="arrow-icon"
|
||
:src="`/static/image/finance-management/${financeInfo.vipLevel}/arrow.png`"></image>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="content-box">
|
||
<view class="service-icons">
|
||
<view class="service-icon" v-for="item in serviceIcons" :key="item.name">
|
||
<image class="icon"
|
||
:src="`/static/image/finance-management/service-icon/${item.icon}.png`"
|
||
mode="scaleToFill" />
|
||
<text class="name">{{ item.name }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="!selectedImage" class="content flex-1 flex-column flex" @touchstart="clickHandleTouchStart"
|
||
@touchend="clickHandleTouchEnd">
|
||
<view v-if="financeInfo.bgImage" class="w100 h100" style="margin-top: -1px;">
|
||
<image class="w100 h100" :src="financeInfo.bgImage" mode="widthFix">
|
||
</image>
|
||
</view>
|
||
<view v-else class="flex-align-center flex-justify-center flex-column flex-1 w100 h100">
|
||
<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>
|
||
<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>
|
||
|
||
</view>
|
||
|
||
<!-- 底部导航栏 -->
|
||
<view class="bottom-box"
|
||
:class="`navigation-menu-${financeInfo.navigationMenuStyle}`, { 'ios-bottom-box': $system == 'iOS' }">
|
||
<view class="bottom-item" v-for="item in navigationMenu" :key="item.name">
|
||
<image
|
||
:src="`/static/image/finance-management/navigation-menu/${financeInfo.navigationMenuStyle}/${item.icon}.png`">
|
||
</image>
|
||
<text>{{ item.name }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 样式选择弹窗 -->
|
||
<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="financeInfo.navigationMenuStyle == 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>
|
||
</template>
|
||
|
||
<script setup>
|
||
import NavBar from '@/components/nav-bar/nav-bar.vue'
|
||
import {
|
||
ref,
|
||
toRefs,
|
||
reactive,
|
||
onMounted,
|
||
watch,
|
||
computed,
|
||
getCurrentInstance
|
||
} from 'vue'
|
||
import {
|
||
onLoad,
|
||
onReady,
|
||
onShow
|
||
} from '@dcloudio/uni-app'
|
||
import {
|
||
serviceIcons,
|
||
navigationMenu,
|
||
myAssets
|
||
} from '@/static/json/fortune.json'
|
||
import {
|
||
numberUtil,
|
||
util
|
||
} from '@/utils/common.js'
|
||
const instance = getCurrentInstance();
|
||
const { proxy } = instance
|
||
|
||
const buttonGroup = [{
|
||
name: "编辑理财数据",
|
||
click: () => {
|
||
util.goPage('/pages/finance-management/edit/edit')
|
||
}
|
||
}, {
|
||
name: "切换导航栏样式",
|
||
click: () => {
|
||
openStyleDialog()
|
||
}
|
||
}, {
|
||
name: "选择理财图片样式",
|
||
click: () => {
|
||
util.goPage('/pages/finance-management/select-image/select-image')
|
||
}
|
||
}, {
|
||
name: "删除当前理财图片",
|
||
click: () => {
|
||
financeInfo.value.bgImage = ""
|
||
uni.setStorageSync('financeInfo', financeInfo.value)
|
||
}
|
||
}]
|
||
|
||
const defualtData = {
|
||
benefitsText: '尊享礼遇18000元/年',
|
||
cardHolderName: 'XiaoMing',
|
||
vipLevel: "v3",
|
||
navigationMenuStyle: "style-1",
|
||
bgImage: "",
|
||
fortune: {
|
||
yuebao: { value: 3.53, labelSub: "100000" },
|
||
dingqi: { value: 50, labelSub: "100000" },
|
||
jijin: { value: 5000, labelSub: "100000" },
|
||
huangjin: { value: -300, labelSub: "100000" },
|
||
yulingbao: { value: 20, labelSub: "100000" }
|
||
},
|
||
myAssets: {
|
||
baoxian: { labelSub: "3份保单保障中", type: "text" }
|
||
},
|
||
myQuota: {
|
||
huabei: { labelSub: "可用100000", type: "text" },
|
||
jubei: { labelSub: "可用100000", type: "text" },
|
||
beizhi: { labelSub: "可用100000", type: "text" },
|
||
wangshangdai: { labelSub: "预计可借100000", type: "text" }
|
||
}
|
||
}
|
||
|
||
onLoad(() => {
|
||
// 理财页面埋点
|
||
proxy.$apiUserEvent('all', {
|
||
type: 'click',
|
||
key: 'fortune',
|
||
value: "理财"
|
||
})
|
||
})
|
||
|
||
onReady(() => {
|
||
statusBarHeight.value = uni.getSystemInfoSync().statusBarHeight;
|
||
uni.createSelectorQuery().select('.total-money-box').boundingClientRect((data) => {
|
||
console.log("total-money-box height: " + (data ? data.height : "null"));
|
||
const initialH = data ? data.height + 44 + statusBarHeight.value : 44 + statusBarHeight.value;
|
||
topHeight.value = initialH;
|
||
minTop = initialH;
|
||
imageCardBoxtopHeight.value = initialH;
|
||
|
||
setTimeout(() => {
|
||
isReady.value = true
|
||
}, 200)
|
||
}).exec()
|
||
})
|
||
|
||
onShow(() => {
|
||
financeInfo.value = uni.getStorageSync('financeInfo') || defualtData
|
||
console.log("financeInfo.value", financeInfo.value)
|
||
// #ifdef APP-PLUS
|
||
util.setAndroidSystemBarColor(financeInfo.value.navigationMenuStyle == 'style-1' ? '#ffffff' : '#F8F8F8')
|
||
setTimeout(() => {
|
||
plus.navigator.setStatusBarStyle("light");
|
||
}, 500)
|
||
// #endif
|
||
})
|
||
|
||
const updateTopHeight = () => {
|
||
setTimeout(() => {
|
||
uni.createSelectorQuery().select('.card-arrow-box').boundingClientRect((data) => {
|
||
console.log("card-arrow-box height: " + (data ? data.height : "null"));
|
||
cardArrowHeight.value = data ? data.height - 20 : 0;
|
||
}).exec()
|
||
}, 500)
|
||
}
|
||
|
||
// 总资产
|
||
const totalMoney = computed(() => {
|
||
let totalMoney = 0;
|
||
for (const key in financeInfo.value.fortune) {
|
||
if (!Object.prototype.hasOwnProperty.call(financeInfo.value.fortune, key)) continue;
|
||
const element = financeInfo.value.fortune[key];
|
||
totalMoney += Number(element.labelSub)
|
||
}
|
||
return totalMoney
|
||
})
|
||
|
||
// 昨日收益
|
||
const yesterdayIncome = computed(() => {
|
||
let yesterdayIncome = 0;
|
||
for (const key in financeInfo.value.fortune) {
|
||
if (!Object.prototype.hasOwnProperty.call(financeInfo.value.fortune, key)) continue;
|
||
const element = financeInfo.value.fortune[key];
|
||
yesterdayIncome += Number(element.value)
|
||
}
|
||
return yesterdayIncome
|
||
})
|
||
|
||
// 家庭保障
|
||
const familyProtection = computed(() => {
|
||
return parseInt(financeInfo.value.myAssets.baoxian.labelSub)
|
||
})
|
||
|
||
|
||
const scrollTop = ref(0)
|
||
const onImageScroll = (e) => {
|
||
scrollTop.value = e.detail.scrollTop
|
||
}
|
||
|
||
const data = reactive({
|
||
financeInfo: { ...defualtData },
|
||
topHeight: 0,
|
||
statusBarHeight: 0,
|
||
cardArrowHeight: 0,
|
||
imageCardBoxtopHeight: 0,
|
||
isOpen: false,
|
||
isDragging: false,
|
||
isReady: false,
|
||
selectedImage: '',
|
||
showMask: false
|
||
})
|
||
|
||
|
||
let {
|
||
financeInfo,
|
||
topHeight,
|
||
statusBarHeight,
|
||
cardArrowHeight,
|
||
imageCardBoxtopHeight,
|
||
isOpen,
|
||
isDragging,
|
||
isReady,
|
||
selectedImage,
|
||
showMask
|
||
} = toRefs(data)
|
||
|
||
|
||
const formatTextWithNumber = (str) => {
|
||
if (!str) return '';
|
||
return str.replace(/(\d+(\.\d+)?)/g, (match) => {
|
||
return match > 100 ? numberUtil.formatMoneyWithThousand(match) : match;
|
||
});
|
||
}
|
||
|
||
let startY = 0;
|
||
let startTop = 0;
|
||
let minTop = 0;
|
||
let maxTop = 0;
|
||
|
||
// 监听 isOpen 变化来控制 topHeight
|
||
watch(() => isOpen.value, () => {
|
||
updateHeightByState();
|
||
})
|
||
|
||
const handleTouchStart = (e) => {
|
||
if (selectedImage.value) return
|
||
isDragging.value = true;
|
||
startY = e.touches[0].clientY;
|
||
startTop = topHeight.value;
|
||
|
||
// 如果未设置边界(或为了安全起见每次开始时),则计算边界
|
||
if (!minTop || !maxTop) {
|
||
const systemInfo = uni.getSystemInfoSync();
|
||
const statusBar = systemInfo.statusBarHeight;
|
||
|
||
// 创建查询以获取当前尺寸
|
||
const query = uni.createSelectorQuery();
|
||
query.select('.total-money-box').boundingClientRect();
|
||
query.select('.main-info-container').boundingClientRect();
|
||
query.exec(([totalMoneyBox, mainInfoContainer]) => {
|
||
if (totalMoneyBox) {
|
||
// 最小高度(关闭状态):total-money-box 高度 + 导航栏 + 状态栏 - 卡片箭头偏移量
|
||
minTop = totalMoneyBox.height + 44 + statusBar;
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
const handleTouchMove = (e) => {
|
||
if (selectedImage.value) return
|
||
let currentY = e.touches[0].clientY;
|
||
let delta = currentY - startY;
|
||
let newTop = startTop + delta;
|
||
|
||
// 如果为 0,让我们获取它们。
|
||
if (minTop === 0 && !isOpen.value) minTop = startTop;
|
||
if (maxTop === 0 && isOpen.value) maxTop = startTop;
|
||
|
||
// 如果有值,则进行简单钳位
|
||
if (minTop && newTop < minTop) newTop = minTop;
|
||
if (maxTop && newTop > maxTop) newTop = maxTop;
|
||
|
||
// 应用
|
||
topHeight.value = newTop;
|
||
}
|
||
|
||
const updateHeightByState = () => {
|
||
statusBarHeight.value = uni.getSystemInfoSync().statusBarHeight;
|
||
if (isOpen.value) {
|
||
// 打开状态:计算 main-info-container 高度
|
||
uni.createSelectorQuery().select('.main-info-container').boundingClientRect((data) => {
|
||
if (data) {
|
||
const targetH = data.height - cardArrowHeight.value;
|
||
topHeight.value = targetH;
|
||
maxTop = targetH; // 更新 maxTop
|
||
}
|
||
}).exec()
|
||
} else {
|
||
// 关闭状态:计算 total-money-box 位置
|
||
uni.createSelectorQuery().select('.total-money-box').boundingClientRect((data) => {
|
||
if (data) {
|
||
const targetH = data.height + 44 + statusBarHeight.value;
|
||
topHeight.value = targetH;
|
||
minTop = targetH; // 更新 minTop
|
||
}
|
||
}).exec()
|
||
}
|
||
}
|
||
|
||
|
||
const handleTouchEnd = (e) => {
|
||
if (selectedImage.value) return
|
||
isDragging.value = false;
|
||
let endY = e.changedTouches[0].clientY;
|
||
let diff = endY - startY;
|
||
|
||
let targetState = isOpen.value;
|
||
|
||
// 判断滑动意图
|
||
if (diff > 50) {
|
||
targetState = true;
|
||
} else if (diff < -50) {
|
||
targetState = false;
|
||
}
|
||
|
||
// 如果状态改变,watch 会处理动画/高度更新
|
||
if (targetState !== isOpen.value) {
|
||
isOpen.value = targetState;
|
||
} else {
|
||
// 如果状态未改变,手动恢复高度(吸附回原位)
|
||
updateHeightByState();
|
||
}
|
||
}
|
||
|
||
|
||
// 长按事件定时器
|
||
let longPressTimer = null
|
||
const clickHandleTouchStart = (e) => {
|
||
// 兼容iOS上滑HOME条,如果有底部安全区且触摸位置在底部安全区内,则不触发
|
||
const systemInfo = uni.getSystemInfoSync()
|
||
if (systemInfo.platform === 'ios' && systemInfo.safeAreaInsets?.bottom) {
|
||
const clientY = e.touches[0].clientY
|
||
const windowHeight = systemInfo.windowHeight
|
||
// 如果触摸点在底部安全区范围内(通常是34px),则忽略
|
||
if (clientY > windowHeight - systemInfo.safeAreaInsets.bottom) {
|
||
return
|
||
}
|
||
}
|
||
|
||
longPressTimer = setTimeout(() => {
|
||
uni.vibrateShort()
|
||
chooseImage()
|
||
}, 1200) // 长按时间大于1s
|
||
}
|
||
|
||
|
||
|
||
|
||
// 选择图片
|
||
const chooseImage = () => {
|
||
if (selectedImage.value) return
|
||
uni.chooseImage({
|
||
count: 1,
|
||
sourceType: ['album'],
|
||
success: (res) => {
|
||
selectedImage.value = res.tempFilePaths[0]
|
||
data.showMask = true
|
||
setTimeout(() => {
|
||
plus.navigator.setStatusBarStyle("dark");
|
||
}, 500)
|
||
}
|
||
})
|
||
}
|
||
|
||
// 确认图片裁剪
|
||
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: sWidth, // 使用原图实际宽度,保持原图清晰度
|
||
destHeight: sHeight, // 使用原图实际高度,保持原图清晰度
|
||
success: (res) => {
|
||
console.log('crop success (temp)', res
|
||
.tempFilePath)
|
||
|
||
// 将临时路径保存为永久路径
|
||
uni.saveFile({
|
||
tempFilePath: res.tempFilePath,
|
||
success: (saveRes) => {
|
||
console.log('save success (saved)', saveRes.savedFilePath)
|
||
financeInfo.value.bgImage = saveRes.savedFilePath
|
||
selectedImage.value = '' // 隐藏编辑模式
|
||
setTimeout(() => {
|
||
plus.navigator.setStatusBarStyle("light");
|
||
}, 200)
|
||
// 保存到缓存
|
||
uni.setStorageSync("financeInfo", financeInfo.value)
|
||
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 clickHandleTouchEnd = () => {
|
||
if (longPressTimer) {
|
||
clearTimeout(longPressTimer)
|
||
longPressTimer = null
|
||
}
|
||
}
|
||
|
||
const closeImage = () => {
|
||
selectedImage.value = ''
|
||
data.showMask = false
|
||
plus.navigator.setStatusBarStyle("light");
|
||
return false
|
||
}
|
||
|
||
const closeMask = () => {
|
||
data.showMask = false
|
||
}
|
||
|
||
|
||
|
||
const stylePopup = ref(null)
|
||
|
||
const styleList = [{
|
||
label: '样式 1 (默认)',
|
||
value: 'style-1'
|
||
},
|
||
{
|
||
label: '样式 2',
|
||
value: 'style-2'
|
||
}
|
||
]
|
||
|
||
// 打开样式选择弹窗
|
||
const openStyleDialog = () => {
|
||
stylePopup.value.open()
|
||
}
|
||
|
||
// 关闭样式选择弹窗
|
||
const closeStyleDialog = () => {
|
||
stylePopup.value.close()
|
||
}
|
||
|
||
// 确认样式选择
|
||
const confirmStyleDialog = (type) => {
|
||
financeInfo.value.navigationMenuStyle = type
|
||
uni.setStorageSync('financeInfo', financeInfo.value)
|
||
// #ifdef APP-PLUS
|
||
util.setAndroidSystemBarColor(financeInfo.value.navigationMenuStyle == 'style-1' ? '#ffffff' : '#F8F8F8')
|
||
// #endif
|
||
stylePopup.value.close()
|
||
}
|
||
|
||
const clickTitlePopupButton = (button) => {
|
||
button.click()
|
||
}
|
||
</script>
|
||
|
||
<style>
|
||
@import "@/common/main.css";
|
||
</style>
|
||
<style lang="less" scoped>
|
||
.container {
|
||
height: 100vh;
|
||
background-color: #ffffff;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.main-info-container {
|
||
background: linear-gradient(348deg, #0B1028 49%, #0B1028 76.26%, #18336C 86.18%, #18336C 100%);
|
||
|
||
.nav-bar {
|
||
padding: 20rpx;
|
||
|
||
.left {
|
||
image {
|
||
height: 48rpx;
|
||
width: 48rpx;
|
||
}
|
||
}
|
||
|
||
.right {
|
||
image {
|
||
height: 36rpx;
|
||
width: 36rpx;
|
||
}
|
||
}
|
||
|
||
.nav-bar-search {
|
||
background-color: #ffffff;
|
||
border-radius: 300rpx;
|
||
height: 56rpx;
|
||
padding: 4px 0;
|
||
margin: 0 26rpx 0 14rpx;
|
||
|
||
.search-icon {
|
||
width: 32rpx;
|
||
height: 32rpx;
|
||
margin-left: 4px;
|
||
margin-right: 6px;
|
||
}
|
||
|
||
.search-input {
|
||
font-size: 14px;
|
||
}
|
||
|
||
::v-deep .input-placeholder {
|
||
color: #767676;
|
||
}
|
||
|
||
.line {
|
||
width: 1px;
|
||
background-color: #D8D8D8;
|
||
}
|
||
|
||
.search-button {
|
||
font-size: 14px;
|
||
padding: 0 12px;
|
||
color: #8B634E;
|
||
font-weight: 500;
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
.total-money-box {
|
||
padding-top: 10rpx;
|
||
padding-bottom: 6rpx;
|
||
margin-bottom: 32rpx;
|
||
|
||
.revenue-box {
|
||
padding: 0 24rpx;
|
||
|
||
.total-money {
|
||
font-size: 24rpx;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
|
||
.total-money-value {
|
||
color: #F1CFAB;
|
||
}
|
||
|
||
}
|
||
|
||
.total-money-title {
|
||
color: #A39797;
|
||
|
||
.eye-icon {
|
||
width: 44rpx;
|
||
height: 44rpx;
|
||
margin: 0 32rpx 0 20rpx;
|
||
}
|
||
|
||
.safe-icon {
|
||
width: 24rpx;
|
||
height: 24rpx;
|
||
margin-right: 4rpx;
|
||
}
|
||
}
|
||
|
||
.total-money-value {
|
||
font-size: 50rpx;
|
||
font-weight: 500;
|
||
line-height: 64rpx;
|
||
}
|
||
|
||
.family-protection {
|
||
display: flex;
|
||
flex-direction: column;
|
||
flex-shrink: 0;
|
||
height: 100%;
|
||
padding: 0 28rpx;
|
||
border-left: 1rpx solid #172139;
|
||
align-items: center;
|
||
font-size: 24rpx;
|
||
|
||
.value {
|
||
color: #F1CFAB;
|
||
font-size: 56rpx;
|
||
font-weight: 500;
|
||
line-height: 64rpx;
|
||
}
|
||
|
||
.text {
|
||
color: #F1CFAB;
|
||
font-size: 24rpx;
|
||
margin-left: 8rpx;
|
||
|
||
}
|
||
}
|
||
}
|
||
|
||
.data-box {
|
||
padding: 0 26rpx 0;
|
||
font-size: 26rpx;
|
||
|
||
.title {
|
||
margin-bottom: 40rpx;
|
||
}
|
||
|
||
.group-data {
|
||
// margin-top: 8rpx;
|
||
flex-wrap: wrap;
|
||
|
||
.item {
|
||
width: calc(50% - 15rpx);
|
||
flex-shrink: 0;
|
||
margin-bottom: 40rpx;
|
||
|
||
.label {
|
||
margin-right: 18rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.label-sub {
|
||
white-space: nowrap;
|
||
}
|
||
}
|
||
}
|
||
|
||
.second-color {
|
||
color: #787174;
|
||
}
|
||
|
||
.primary-color {
|
||
color: #EAD2B8;
|
||
}
|
||
}
|
||
}
|
||
|
||
.open {
|
||
// background: #08112E;
|
||
background: linear-gradient(146deg, #0B1028 49%, #08112E 71.26%, #18336C 83.18%, #18336C 96%)
|
||
}
|
||
|
||
|
||
|
||
.image-service-box {
|
||
position: absolute;
|
||
display: flex;
|
||
flex-direction: column;
|
||
top: 20%;
|
||
width: 100%;
|
||
|
||
.arrow-box {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 4rpx;
|
||
|
||
.arrow-icon {
|
||
width: 28rpx;
|
||
height: 28rpx;
|
||
}
|
||
}
|
||
|
||
.card-box {
|
||
display: flex;
|
||
flex-direction: column;
|
||
|
||
.wealth-card-box {
|
||
position: relative;
|
||
|
||
.card-info {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: flex-start;
|
||
position: absolute;
|
||
width: 100%;
|
||
top: 12%;
|
||
padding: 0 5%;
|
||
|
||
.logo {
|
||
width: 188rpx;
|
||
height: 40rpx;
|
||
}
|
||
|
||
.name-box {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
}
|
||
|
||
.card-holder-icon {
|
||
width: 68rpx;
|
||
height: 28rpx;
|
||
margin-right: 8rpx;
|
||
}
|
||
|
||
.card-holder-name {
|
||
color: #F1CFAB;
|
||
font-size: 30rpx;
|
||
}
|
||
}
|
||
|
||
.enjoy-benefits {
|
||
position: absolute;
|
||
bottom: 25%;
|
||
right: 4%;
|
||
|
||
.benefits-text {
|
||
color: #F1CFAB;
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
.arrow-icon {
|
||
width: 28rpx;
|
||
height: 28rpx;
|
||
margin-left: 6rpx;
|
||
}
|
||
}
|
||
|
||
|
||
}
|
||
|
||
.wealth-card-opacity0 {
|
||
width: 100%;
|
||
opacity: 0;
|
||
}
|
||
|
||
.wealth-card {
|
||
position: absolute;
|
||
width: 100%;
|
||
}
|
||
|
||
.content-box {
|
||
margin-top: -1px;
|
||
background: linear-gradient(180deg, #FFFFFF 56.37%, #F0F4F9 100%);
|
||
padding: 20rpx;
|
||
padding-bottom: 12rpx;
|
||
|
||
.service-icons {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
justify-content: space-between;
|
||
|
||
.service-icon {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
width: 20%;
|
||
margin-top: 22rpx;
|
||
margin-bottom: 8rpx;
|
||
|
||
.icon {
|
||
width: 62rpx;
|
||
height: 62rpx;
|
||
}
|
||
|
||
.name {
|
||
font-size: 26rpx;
|
||
color: #5D5D5D;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
}
|
||
|
||
.image-box {
|
||
width: 100%;
|
||
overflow: hidden; // scroll-view 需要
|
||
}
|
||
|
||
.scroll-image-box {
|
||
background-color: #ffffff;
|
||
width: 100%;
|
||
min-height: 0; // 修复 flex一出问题
|
||
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;
|
||
}
|
||
}
|
||
|
||
.content {
|
||
background-color: #ffffff;
|
||
// padding: 20rpx;
|
||
margin-top: -1px;
|
||
}
|
||
|
||
.bottom-box {
|
||
position: fixed;
|
||
bottom: 0;
|
||
width: 100%;
|
||
// height: 100rpx;
|
||
background-color: #ffffff;
|
||
display: flex;
|
||
justify-content: space-around;
|
||
align-items: center;
|
||
padding: 4rpx 0;
|
||
|
||
.bottom-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
|
||
image {
|
||
width: 56rpx;
|
||
height: 56rpx;
|
||
}
|
||
|
||
text {
|
||
margin-top: 12rpx;
|
||
font-size: 20rpx;
|
||
line-height: 25rpx;
|
||
color: #333;
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
|
||
.navigation-menu-style-1 {
|
||
background-color: #ffffff;
|
||
|
||
.bottom-item:nth-child(2) {
|
||
text {
|
||
color: #A27140;
|
||
}
|
||
}
|
||
}
|
||
|
||
.navigation-menu-style-2 {
|
||
background-color: #F8F8F8;
|
||
border-top: 1rpx solid #C3CED1;
|
||
padding: 6rpx 0;
|
||
|
||
.bottom-item:nth-child(2) {
|
||
text {
|
||
color: #0075FF;
|
||
}
|
||
}
|
||
}
|
||
|
||
.ios-bottom-box {
|
||
padding-bottom: constant(safe-area-inset-bottom); // 兼容 IOS<11.2
|
||
padding-bottom: env(safe-area-inset-bottom); // 兼容 IOS>11.2
|
||
}
|
||
</style>
|
||
|
||
<style scoped>
|
||
.popup-content {
|
||
background-color: #fff;
|
||
border-radius: 12px;
|
||
width: 600rpx;
|
||
padding: 20px;
|
||
}
|
||
|
||
.popup-title {
|
||
text-align: center;
|
||
font-size: 18px;
|
||
font-weight: 500;
|
||
margin-bottom: 20px;
|
||
color: #333;
|
||
}
|
||
|
||
.style-list {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.style-item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 15px 10px;
|
||
border-bottom: 1px solid #f5f5f5;
|
||
font-size: 16px;
|
||
color: #333;
|
||
}
|
||
|
||
.style-item:active {
|
||
background-color: #f9f9f9;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.popup-btns {
|
||
display: flex;
|
||
justify-content: center;
|
||
}
|
||
|
||
.btn {
|
||
width: 100%;
|
||
height: 44px;
|
||
line-height: 44px;
|
||
text-align: center;
|
||
border-radius: 22px;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.btn.cancel {
|
||
background-color: #f5f5f5;
|
||
color: #666;
|
||
}
|
||
</style> |