完成理财

This commit is contained in:
tangxinyue 2026-01-27 18:31:45 +08:00
parent 2147da46ac
commit 4da4c4de84
25 changed files with 1098 additions and 248 deletions

View File

@ -6,7 +6,8 @@
"type" : "uni-app:app-ios" "type" : "uni-app:app-ios"
}, },
{ {
"playground" : "custom", "customPlaygroundType" : "device",
"playground" : "standard",
"type" : "uni-app:app-android" "type" : "uni-app:app-android"
} }
] ]

View File

@ -1,95 +1,111 @@
{ {
"name" : "alipay-emulator", "name": "alipay-emulator",
"appid" : "__UNI__D535736", "appid": "__UNI__D535736",
"description" : "", "description": "",
"versionName" : "1.0.0", "versionName": "1.0.0",
"versionCode" : "100", "versionCode": "100",
"transformPx" : false, "transformPx": false,
/* 5+App */ /* 5+App */
"app-plus" : { "app-plus": {
"darkmode" : false, "confusion": {
"usingComponents" : true, "resources": {
"nvueStyleCompiler" : "uni-app", "app-service.js": {}
"compilerVersion" : 3, }
"splashscreen" : { },
"alwaysShowBeforeRender" : true, "encryption": {
"waiting" : true, "wgt": true,
"autoclose" : true, "files": [
"delay" : 0 "app-service.js"
}, ]
"optimization" : { },
"subPackages" : true "darkmode": false,
}, "usingComponents": true,
"runmode" : "liberate", // "nvueStyleCompiler": "uni-app",
"compilerVersion": 3,
/* */ "splashscreen": {
"modules" : { "alwaysShowBeforeRender": true,
"Camera" : {}, "waiting": true,
"Payment" : {} "autoclose": true,
}, "delay": 0
/* */ },
"distribute" : { "optimization": {
/* android */ "subPackages": false
"android" : { },
"permissions" : [ "runmode": "liberate", //
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>", /* */
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>", "modules": {
"<uses-permission android:name=\"android.permission.VIBRATE\"/>", "Camera": {},
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>", "Payment": {}
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>", },
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>", /* */
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>", "distribute": {
"<uses-permission android:name=\"android.permission.CAMERA\"/>", /* android */
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>", "android": {
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>", "permissions": [
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>", "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>", "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>", "<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-feature android:name=\"android.hardware.camera\"/>", "<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>" "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
] "<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
}, "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
/* ios */ "<uses-permission android:name=\"android.permission.CAMERA\"/>",
"ios" : { "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
"dSYMs" : false "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
}, "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
/* SDK */ "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"sdkConfigs" : { "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"payment" : { "<uses-feature android:name=\"android.hardware.camera\"/>",
"weixin" : { "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
"__platform__" : [ "ios", "android" ], ]
"appid" : "123456", },
"UniversalLinks" : "https://hhhhh.com/apple-app-site-association/" /* ios */
}, "ios": {
"alipay" : { "dSYMs": false
"__platform__" : [ "ios", "android" ] },
} /* SDK */
} "sdkConfigs": {
} "payment": {
}, "weixin": {
"nvueLaunchMode" : "" "__platform__": [
}, "ios",
/* */ "android"
"quickapp" : {}, ],
/* */ "appid": "123456",
"mp-weixin" : { "UniversalLinks": "https://hhhhh.com/apple-app-site-association/"
"appid" : "", },
"setting" : { "alipay": {
"urlCheck" : false "__platform__": [
}, "ios",
"usingComponents" : true "android"
}, ]
"mp-alipay" : { }
"usingComponents" : true }
}, }
"mp-baidu" : { },
"usingComponents" : true "nvueLaunchMode": ""
}, },
"mp-toutiao" : { /* */
"usingComponents" : true "quickapp": {},
}, /* */
"uniStatistics" : { "mp-weixin": {
"enable" : false "appid": "",
}, "setting": {
"vueVersion" : "3" "urlCheck": false
} },
"usingComponents": true
},
"mp-alipay": {
"usingComponents": true
},
"mp-baidu": {
"usingComponents": true
},
"mp-toutiao": {
"usingComponents": true
},
"uniStatistics": {
"enable": false
},
"vueVersion": "3"
}

View File

@ -73,12 +73,27 @@
}, { }, {
"root": "pages/finance-management", "root": "pages/finance-management",
"pages": [{ "pages": [{
"path": "index", "path": "index",
"style": { "style": {
"navigationBarTitleText": "理财首页", "navigationBarTitleText": "理财首页",
"navigationStyle": "custom" "navigationStyle": "custom"
}
},
{
"path": "select-image/select-image",
"style": {
"navigationBarTitleText": "选择理财图片",
"navigationStyle": "custom"
}
},
{
"path": "edit/edit",
"style": {
"navigationBarTitleText": "编辑理财数据",
"navigationStyle": "custom"
}
} }
}] ]
}, },
{ {
"root": "pages/common", "root": "pages/common",
@ -119,7 +134,9 @@
"packages": [ "packages": [
"pages/balance", "pages/balance",
"pages/bill", "pages/bill",
"pages/common" "pages/common",
"pages/finance-management",
"pages/ant-credit-pay"
] ]
} }
}, },

View File

@ -507,13 +507,25 @@ const chooseImage = () => {
// //
let longPressTimer = null let longPressTimer = null
const handleTouchStart = () => { const handleTouchStart = (e) => {
// iOSHOME
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(() => { longPressTimer = setTimeout(() => {
uni.vibrateShort() uni.vibrateShort()
chooseImage() chooseImage()
}, 1200) // 1s }, 1200) // 1s
} }
const handleTouchEnd = () => { const handleTouchEnd = () => {
if (longPressTimer) { if (longPressTimer) {
clearTimeout(longPressTimer) clearTimeout(longPressTimer)

View File

@ -38,7 +38,7 @@
<image class="img" src="/static/image/balance/eye.png" mode=""></image> <image class="img" src="/static/image/balance/eye.png" mode=""></image>
</view> </view>
<view class="balance flex-cneter" @click="editBalance"> <view class="balance flex-cneter" @click="editBalance">
<text class="text alipay-font">{{ Number(balance).toFixed(2) }}</text> <text class="text alipay-font">{{ numberUtil.formatMoneyWithThousand(balance) }}</text>
</view> </view>
<view class="button-group"> <view class="button-group">
<view class="flex-1 btn-box"> <view class="flex-1 btn-box">
@ -116,7 +116,8 @@ import BalanceList from '@/components/balance-list/balance-list.vue'
import { fastEntranceList } from '@/static/json/initial.json' import { fastEntranceList } from '@/static/json/initial.json'
import { import {
util, util,
deviceUtil deviceUtil,
numberUtil
} from '@/utils/common'; } from '@/utils/common';
import { import {
storage storage
@ -212,7 +213,9 @@ onLoad(async () => {
onShow(() => { onShow(() => {
// #ifdef APP-PLUS // #ifdef APP-PLUS
util.setAndroidSystemBarColor('#F0F3F8') util.setAndroidSystemBarColor('#F0F3F8')
plus.navigator.setStatusBarStyle("light"); setTimeout(() => {
plus.navigator.setStatusBarStyle("light");
}, 500)
// #endif // #endif
// //

View File

@ -0,0 +1,222 @@
<template>
<view class="container">
<NavBar title="修改理财数据" bgColor="#F5F5F5" isRightButton @right-click="handleRightButtonClick"></NavBar>
<scroll-view scroll-y class="form-content">
<!-- 基础信息 -->
<view class="section-title">基础信息</view>
<view class="card">
<view class="form-item">
<text class="label">持卡人姓名</text>
<input class="input" v-model="financeInfo.cardHolderName" />
</view>
<picker mode="selector" :range="vipLevels" @change="onVipLevelChange">
<view class="form-item">
<text class="label">财富等级</text>
<view class="picker-value">{{ financeInfo.vipLevel }} </view>
</view>
</picker>
<view class="form-item">
<text class="label">权益文案</text>
<input class="input" v-model="financeInfo.benefitsText" />
</view>
</view>
<!-- 财富收益 -->
<view class="section-title">财富收益</view>
<view class="card" v-for="(item, key) in financeInfo.fortune" :key="key">
<view class="card-header">{{ getFortuneName(key) }}</view>
<view class="form-item">
<text class="label">总金额</text>
<input class="input" type="digit" v-model="item.labelSub" @input="onTotalAmountInput(item)" />
</view>
<view class="form-item">
<text class="label">昨日收益</text>
<input class="input" type="digit" v-model="item.value" />
</view>
</view>
<!-- 资产信息 -->
<view class="section-title">资产信息</view>
<view class="card">
<view class="form-item">
<text class="label">保险信息</text>
<input class="input" v-model="financeInfo.myAssets.baoxian.labelSub" />
</view>
</view>
<!-- 额度信息 -->
<view class="section-title">额度信息</view>
<view class="card">
<view class="form-item" v-for="(item, key) in financeInfo.myQuota" :key="key">
<text class="label">{{ getQuotaName(key) }}</text>
<input class="input" v-model="item.labelSub" />
</view>
</view>
<view class="placeholder"></view>
</scroll-view>
</view>
</template>
<script setup>
import NavBar from '@/components/nav-bar/nav-bar.vue'
import { ref, reactive, toRefs, onMounted } from 'vue';
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" }
}
}
const data = reactive({
financeInfo: JSON.parse(JSON.stringify(defualtData))
})
const { financeInfo } = toRefs(data)
const fortuneMap = {
yuebao: '余额宝',
dingqi: '定期',
jijin: '基金',
huangjin: '黄金',
yulingbao: '余利宝'
}
const quotaMap = {
huabei: '花呗',
jubei: '借呗',
beizhi: '备用金',
wangshangdai: '网商贷'
}
const getFortuneName = (key) => fortuneMap[key] || key
const getQuotaName = (key) => quotaMap[key] || key
const vipLevels = ['v1', 'v2', 'v3']
const onVipLevelChange = (e) => {
financeInfo.value.vipLevel = vipLevels[e.detail.value]
}
const onTotalAmountInput = (item) => {
// 使 setTimeout v-model value
//
if (item.labelSub && item.labelSub.toString().includes('-')) {
item.labelSub = item.labelSub.replace('-', '')
}
}
onMounted(() => {
const stored = uni.getStorageSync('financeInfo')
if (stored) {
// Deep merge to ensure structure integrity
Object.assign(data.financeInfo, stored)
}
})
const handleRightButtonClick = () => {
uni.setStorageSync('financeInfo', data.financeInfo)
uni.navigateBack()
}
</script>
<style>
@import "@/common/main.css";
page {
background-color: #F8F8F8;
height: 100vh;
overflow: hidden;
}
</style>
<style lang="less" scoped>
.container {
display: flex;
flex-direction: column;
height: 100vh;
}
.form-content {
flex: 1;
height: 0;
padding: 24rpx;
box-sizing: border-box;
}
.section-title {
font-size: 28rpx;
color: #666;
margin: 24rpx 0 16rpx 12rpx;
font-weight: 500;
}
.card {
background-color: #fff;
border-radius: 16rpx;
padding: 0 24rpx;
margin-bottom: 24rpx;
.card-header {
padding: 24rpx 0 12rpx;
font-size: 30rpx;
font-weight: bold;
color: #333;
border-bottom: 1rpx solid #f5f5f5;
margin-bottom: 12rpx;
}
}
.form-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 0;
border-bottom: 1rpx solid #F5F5F5;
&:last-child {
border-bottom: none;
}
.label {
font-size: 30rpx;
color: #333;
width: 240rpx;
}
.input {
flex: 1;
font-size: 30rpx;
color: #333;
text-align: right;
}
.picker-value {
flex: 1;
font-size: 30rpx;
color: #333;
text-align: right;
}
}
.placeholder {
height: 60rpx;
}
</style>

View File

@ -8,10 +8,12 @@
</view> </view>
<view class="container flex flex-column" @touchstart="handleTouchStart" @touchmove.stop.prevent="handleTouchMove" <view class="container flex flex-column" @touchstart="handleTouchStart" @touchmove.stop.prevent="handleTouchMove"
@touchend="handleTouchEnd"> @touchend="handleTouchEnd">
<!-- 下层信息盒子 -->
<view class="main-info-container" :class="{ 'open': isOpen }" <view class="main-info-container" :class="{ 'open': isOpen }"
:style="{ 'padding-bottom': `${cardArrowHeight}px` }"> :style="{ 'padding-bottom': `${cardArrowHeight}px` }">
<!-- 导航栏 placeholder --> <!-- 导航栏 placeholder -->
<NavBar bgColor="transparent"> <NavBar v-if="!selectedImage" bgColor="transparent" :buttonGroup="buttonGroup"
@button-click="clickTitlePopupButton">
<view class="nav-bar flex-align-center h100"> <view class="nav-bar flex-align-center h100">
<view class="left flex-align-center"> <view class="left flex-align-center">
<image src="/static/image/finance-management/search/search-left.png"></image> <image src="/static/image/finance-management/search/search-left.png"></image>
@ -27,8 +29,12 @@
</view> </view>
</view> </view>
</NavBar> </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="total-money-box flex-align-center flex-justify-between">
<!-- 总资产信息 -->
<view class="revenue-box flex-1"> <view class="revenue-box flex-1">
<view class="total-money flex-align-center"> <view class="total-money flex-align-center">
<view class="total-money-title flex-align-center"> <view class="total-money-title flex-align-center">
@ -65,12 +71,14 @@
</view> </view>
</view> </view>
</view> </view>
<!-- 财富信息 -->
<view class="data-box"> <view class="data-box">
<view v-for="item in myAssets" :key="item.title"> <view v-for="item in myAssets" :key="item.title">
<view v-if="item.title" class="title second-color">{{ item.title }}</view> <view v-if="item.title" class="title second-color">{{ item.title }}</view>
<view class="group-data flex-justify-between"> <view class="group-data flex-justify-between">
<template v-for="subItem in item.items" :key="subItem.label"> <template v-for="subItem in item.items" :key="subItem.label">
<view v-if="financeInfo[item.key][subItem.key].labelSub" <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"> class="item flex-align-center flex-justify-between">
<view class="flex-align-center"> <view class="flex-align-center">
<text class="label primary-color">{{ subItem.label }}</text> <text class="label primary-color">{{ subItem.label }}</text>
@ -102,8 +110,10 @@
</view> </view>
</view> </view>
</view> </view>
<!-- 图片盒子 -->
<view class="image-service-box flex-column" <view class="image-service-box flex-column"
:style="{ top: topHeight + 'px', 'min-height': `calc(100vh - ${imageCardBoxtopHeight}px)` }"> :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="card-arrow-box">
<view class="arrow-box"> <view class="arrow-box">
<image @click="data.isOpen = !data.isOpen" class="arrow-icon" <image @click="data.isOpen = !data.isOpen" class="arrow-icon"
@ -153,18 +163,35 @@
</view> </view>
</view> </view>
<view class="content flex-1 flex-align-center flex-justify-center"> <view v-if="!selectedImage" class="content flex-1 flex-column flex" @touchstart="clickHandleTouchStart"
<view class="flex-align-center flex-column"> @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;" <image style="width:92rpx; height: 92rpx;margin-top: 16rpx;"
src="/static/image/common/upload-screenshot.png"> src="/static/image/common/upload-screenshot.png">
</image> </image>
<text style="font-size: 36rpx;color: #1777FF;">长按替换真实截图</text> <text style="font-size: 36rpx;color: #1777FF;">长按替换真实截图</text>
</view> </view>
</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>
<!-- 底部导航栏 -->
<view class="bottom-box" <view class="bottom-box"
:class="`navigation-menu-${financeInfo.navigationMenuStyle}`, { 'ios-bottom-box': $system == 'iOS' }"> :class="`navigation-menu-${financeInfo.navigationMenuStyle}`, { 'ios-bottom-box': $system == 'iOS' }">
<view class="bottom-item" v-for="item in navigationMenu" :key="item.name"> <view class="bottom-item" v-for="item in navigationMenu" :key="item.name">
@ -175,33 +202,142 @@
</view> </view>
</view> </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> </template>
<script setup> <script setup>
import NavBar from '@/components/nav-bar/nav-bar.vue' import NavBar from '@/components/nav-bar/nav-bar.vue'
import { ref, toRefs, reactive, onMounted, watch, computed } from 'vue' import {
import { onLoad, onReady } from '@dcloudio/uni-app' ref,
import { serviceIcons, navigationMenu, myAssets } from '@/static/json/fortune.json' toRefs,
import { numberUtil } from '@/utils/common.js' 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(() => { onLoad(() => {
// //
// proxy.$apiUserEvent('all', { proxy.$apiUserEvent('all', {
// type: 'click', type: 'click',
// key: 'fortune', key: 'fortune',
// value: "" value: "理财"
// }) })
}) })
onReady(() => { onReady(() => {
statusBarHeight.value = uni.getSystemInfoSync().statusBarHeight; statusBarHeight.value = uni.getSystemInfoSync().statusBarHeight;
uni.createSelectorQuery().select('.total-money-box').boundingClientRect((data) => { uni.createSelectorQuery().select('.total-money-box').boundingClientRect((data) => {
console.log("total-money-box height: " + (data ? data.height : "null")); console.log("total-money-box height: " + (data ? data.height : "null"));
topHeight.value = data ? data.height + 44 + statusBarHeight.value : 44 + statusBarHeight.value; const initialH = data ? data.height + 44 + statusBarHeight.value : 44 + statusBarHeight.value;
imageCardBoxtopHeight.value = data ? data.height + 44 + statusBarHeight.value : 44 + statusBarHeight.value; topHeight.value = initialH;
minTop = initialH;
imageCardBoxtopHeight.value = initialH;
setTimeout(() => {
isReady.value = true
}, 200)
}).exec() }).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 = () => { const updateTopHeight = () => {
setTimeout(() => { setTimeout(() => {
uni.createSelectorQuery().select('.card-arrow-box').boundingClientRect((data) => { uni.createSelectorQuery().select('.card-arrow-box').boundingClientRect((data) => {
@ -215,7 +351,7 @@ const updateTopHeight = () => {
const totalMoney = computed(() => { const totalMoney = computed(() => {
let totalMoney = 0; let totalMoney = 0;
for (const key in financeInfo.value.fortune) { for (const key in financeInfo.value.fortune) {
if (!Object.hasOwn(financeInfo.value.fortune, key)) continue; if (!Object.prototype.hasOwnProperty.call(financeInfo.value.fortune, key)) continue;
const element = financeInfo.value.fortune[key]; const element = financeInfo.value.fortune[key];
totalMoney += Number(element.labelSub) totalMoney += Number(element.labelSub)
} }
@ -226,7 +362,7 @@ const totalMoney = computed(() => {
const yesterdayIncome = computed(() => { const yesterdayIncome = computed(() => {
let yesterdayIncome = 0; let yesterdayIncome = 0;
for (const key in financeInfo.value.fortune) { for (const key in financeInfo.value.fortune) {
if (!Object.hasOwn(financeInfo.value.fortune, key)) continue; if (!Object.prototype.hasOwnProperty.call(financeInfo.value.fortune, key)) continue;
const element = financeInfo.value.fortune[key]; const element = financeInfo.value.fortune[key];
yesterdayIncome += Number(element.value) yesterdayIncome += Number(element.value)
} }
@ -238,68 +374,38 @@ const familyProtection = computed(() => {
return parseInt(financeInfo.value.myAssets.baoxian.labelSub) return parseInt(financeInfo.value.myAssets.baoxian.labelSub)
}) })
const scrollTop = ref(0)
const onImageScroll = (e) => {
scrollTop.value = e.detail.scrollTop
}
const data = reactive({ const data = reactive({
financeInfo: { financeInfo: { ...defualtData },
benefitsText: '尊享礼遇18000元/年',
cardHolderName: 'LiXing',
vipLevel: "v3",
navigationMenuStyle: "style-1",
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"
}
}
},
topHeight: 0, topHeight: 0,
statusBarHeight: 0, statusBarHeight: 0,
cardArrowHeight: 0, cardArrowHeight: 0,
imageCardBoxtopHeight: 0, imageCardBoxtopHeight: 0,
isOpen: false isOpen: false,
isDragging: false,
isReady: false,
selectedImage: '',
showMask: false
}) })
let { financeInfo, topHeight, statusBarHeight, cardArrowHeight, imageCardBoxtopHeight, isOpen } = toRefs(data) let {
financeInfo,
topHeight,
statusBarHeight,
cardArrowHeight,
imageCardBoxtopHeight,
isOpen,
isDragging,
isReady,
selectedImage,
showMask
} = toRefs(data)
const formatTextWithNumber = (str) => { const formatTextWithNumber = (str) => {
@ -320,6 +426,8 @@ watch(() => isOpen.value, () => {
}) })
const handleTouchStart = (e) => { const handleTouchStart = (e) => {
if (selectedImage.value) return
isDragging.value = true;
startY = e.touches[0].clientY; startY = e.touches[0].clientY;
startTop = topHeight.value; startTop = topHeight.value;
@ -337,37 +445,12 @@ const handleTouchStart = (e) => {
// total-money-box + + - // total-money-box + + -
minTop = totalMoneyBox.height + 44 + statusBar; minTop = totalMoneyBox.height + 44 + statusBar;
} }
if (mainInfoContainer) {
// main-info-container -
// isOpen /
// /
//
// ~160px
// ~400px
// 使 'isOpen=true' maxTop
// data.height (main-info-container )
// main-info-container
// 'isOpen=true' topHeight main-info-container
// /使
// minTop topHeight
// maxTop topHeight
// **
// onReady/updateTopHeight
//
// DOM 使
// minTop 44 + statusBar + totalMoneyBoxHeight
// maxTop mainContainerHeight
}
// 使 handleTouchEnd
// handleTouchEnd init
}); });
} }
} }
const handleTouchMove = (e) => { const handleTouchMove = (e) => {
if (selectedImage.value) return
let currentY = e.touches[0].clientY; let currentY = e.touches[0].clientY;
let delta = currentY - startY; let delta = currentY - startY;
let newTop = startTop + delta; let newTop = startTop + delta;
@ -377,10 +460,8 @@ const handleTouchMove = (e) => {
if (maxTop === 0 && isOpen.value) maxTop = startTop; if (maxTop === 0 && isOpen.value) maxTop = startTop;
// //
if (minTop && maxTop) { if (minTop && newTop < minTop) newTop = minTop;
if (newTop < minTop) newTop = minTop; if (maxTop && newTop > maxTop) newTop = maxTop;
if (newTop > maxTop) newTop = maxTop;
}
// //
topHeight.value = newTop; topHeight.value = newTop;
@ -409,7 +490,10 @@ const updateHeightByState = () => {
} }
} }
const handleTouchEnd = (e) => { const handleTouchEnd = (e) => {
if (selectedImage.value) return
isDragging.value = false;
let endY = e.changedTouches[0].clientY; let endY = e.changedTouches[0].clientY;
let diff = endY - startY; let diff = endY - startY;
@ -432,7 +516,212 @@ const handleTouchEnd = (e) => {
} }
//
let longPressTimer = null
const clickHandleTouchStart = (e) => {
// iOSHOME
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> </script>
<style> <style>
@ -592,6 +881,10 @@ const handleTouchEnd = (e) => {
margin-right: 18rpx; margin-right: 18rpx;
flex-shrink: 0; flex-shrink: 0;
} }
.label-sub {
white-space: nowrap;
}
} }
} }
@ -610,6 +903,8 @@ const handleTouchEnd = (e) => {
background: linear-gradient(146deg, #0B1028 49%, #08112E 71.26%, #18336C 83.18%, #18336C 96%) background: linear-gradient(146deg, #0B1028 49%, #08112E 71.26%, #18336C 83.18%, #18336C 96%)
} }
.image-service-box { .image-service-box {
position: absolute; position: absolute;
display: flex; display: flex;
@ -701,6 +996,7 @@ const handleTouchEnd = (e) => {
margin-top: -1px; margin-top: -1px;
background: linear-gradient(180deg, #FFFFFF 56.37%, #F0F4F9 100%); background: linear-gradient(180deg, #FFFFFF 56.37%, #F0F4F9 100%);
padding: 20rpx; padding: 20rpx;
padding-bottom: 12rpx;
.service-icons { .service-icons {
display: flex; display: flex;
@ -730,11 +1026,70 @@ const handleTouchEnd = (e) => {
} }
.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 { .content {
background-color: #ffffff; background-color: #ffffff;
padding: 20rpx; // padding: 20rpx;
margin-top: -1px; margin-top: -1px;
} }
@ -742,7 +1097,7 @@ const handleTouchEnd = (e) => {
position: fixed; position: fixed;
bottom: 0; bottom: 0;
width: 100%; width: 100%;
height: 100rpx; // height: 100rpx;
background-color: #ffffff; background-color: #ffffff;
display: flex; display: flex;
justify-content: space-around; justify-content: space-around;
@ -768,11 +1123,7 @@ const handleTouchEnd = (e) => {
} }
} }
.ios-bottom-box {
padding-bottom: constant(safe-area-inset-bottom); // IOS<11.2
padding-bottom: env(safe-area-inset-bottom); // IOS>11.2
}
.navigation-menu-style-1 { .navigation-menu-style-1 {
background-color: #ffffff; background-color: #ffffff;
@ -795,4 +1146,64 @@ const handleTouchEnd = (e) => {
} }
} }
} }
.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>
<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>

View File

@ -0,0 +1,162 @@
<template>
<view>
<NavBar title="选择理财图片样式" bgColor="#F5F5F5" isRightButton @right-click="handleRightButtonClick"></NavBar>
<view class="conatiner flex flex-justify-between">
<view v-for="index in 6" :key="index" class="cover-item flex flex-column flex-align-center"
@click="data.selectedImage = index">
<view class="image-box" :class="{ 'active': index == data.selectedImage }">
<image class="w100 h100" :src="`/static/image/finance-management/covers/cover-${index}.png`"
mode="widthFix"></image>
</view>
<view class="text">样式{{ index }}</view>
</view>
</view>
</view>
</template>
<script setup>
import NavBar from '@/components/nav-bar/nav-bar.vue'
import { ref, reactive, toRefs, onMounted } from 'vue';
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"
}
}
}
const data = reactive({
financeInfo: {
...defualtData
},
bgImage: "",
selectedImage: 0
})
const { bgImage, selectedImage, financeInfo } = toRefs(data)
onMounted(() => {
financeInfo.value = uni.getStorageSync('financeInfo') || defualtData
})
const handleRightButtonClick = () => {
const financeInfoData = {
...financeInfo.value
}
financeInfoData.bgImage = `/static/image/finance-management/bg-style/style-${data.selectedImage}.png`
uni.setStorageSync('financeInfo', financeInfoData)
uni.navigateBack()
}
</script>
<style>
@import "@/common/main.css";
page {
background-color: #F5F5F5;
}
</style>
<style lang="less" scoped>
.conatiner {
background-color: #ffffff;
margin: 16rpx 24rpx;
border-radius: 16rpx;
padding: 36rpx;
flex-wrap: wrap;
.image-box {
width: 100%;
overflow: hidden;
border-radius: 24rpx;
}
.cover-item {
width: calc(50% - 13rpx);
flex-shrink: 0;
.text {
font-size: 28rpx;
color: #767676;
margin-top: 8rpx;
margin-bottom: 24rpx;
}
}
.active {
position: relative;
border: 6rpx solid #1A7CFF;
border-radius: 24rpx;
}
.active::before {
content: '';
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
border-radius: 24rpx;
background-color: #1A7CFF;
opacity: 0.1;
z-index: 1;
}
.active::after {
content: '';
position: absolute;
bottom: -1px;
right: -1px;
width: 52rpx;
height: 44rpx;
background-image: url(/static/image/finance-management/active.png);
background-size: 100% 100%;
z-index: 2;
}
}
</style>

View File

@ -42,7 +42,7 @@
</view> </view>
</view> </view>
<view class="notice-box" @click="clickNotice"> <view v-if="noticeInfo.enable" class="notice-box" @click="clickNotice">
<view class="sound-box"> <view class="sound-box">
<uni-icons type="sound" size="18" color="#D8D8D8"></uni-icons> <uni-icons type="sound" size="18" color="#D8D8D8"></uni-icons>
</view> </view>
@ -306,13 +306,16 @@ const setUserData = () => {
// 配置信息 - 安全访问 // 配置信息 - 安全访问
const configData = storage.get("config") const configData = storage.get("config")
if (configData && configData.config) { if (configData && configData.config) {
console.log("公告牌配置信息", configData.config['client.uniapp.notice'])
data.noticeInfo = configData.config['client.uniapp.notice'] || { data.noticeInfo = configData.config['client.uniapp.notice'] || {
text: '加载中...', text: '加载中...',
url: '' url: '',
enable: false
} }
data.noticeInfo = configData.config['client.uniapp.notice'] || { data.noticeInfo = configData.config['client.uniapp.notice'] || {
text: '加载中...', text: '加载中...',
url: '' url: '',
enable: false
} }
// 启动走马灯 // 启动走马灯
startMarquee(); startMarquee();
@ -320,7 +323,8 @@ const setUserData = () => {
} else { } else {
data.noticeInfo = { data.noticeInfo = {
text: '加载中...', text: '加载中...',
url: '' url: '',
enable: false
} }
data.videoHelpList = [] data.videoHelpList = []
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 634 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 327 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 692 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

View File

@ -70,34 +70,6 @@ export const useStore = () => {
return JSON.parse(JSON.stringify(store.billList)) return JSON.parse(JSON.stringify(store.billList))
} }
// 优化后的监听:使用防抖减少存储频率
const debouncedSaveSettings = debounce((newValue) => {
storage.set('settings', newValue)
}, 500)
watch(
() => store.userInfo,
(newValue) => storage.set('userInfo', newValue),
{ deep: true }
);
watch(
() => store.settings,
(newValue) => debouncedSaveSettings(newValue),
{ deep: true }
);
// 监听billList改变自动保存
watch(
() => store.billList,
(newValue) => {
console.log("store.billList")
storage.set('bill_list', newValue)
},
{ deep: true }
);
return { return {
store, store,
addBill, addBill,
@ -106,3 +78,31 @@ export const useStore = () => {
getBillList getBillList
}; };
}; };
// 优化后的监听:使用防抖减少存储频率
const debouncedSaveSettings = debounce((newValue) => {
storage.set('settings', newValue)
}, 500)
watch(
() => store.userInfo,
(newValue) => storage.set('userInfo', newValue),
{ deep: true }
);
watch(
() => store.settings,
(newValue) => debouncedSaveSettings(newValue),
{ deep: true }
);
// 监听billList改变自动保存
watch(
() => store.billList,
(newValue) => {
console.log("store.billList changed, saving...")
storage.set('bill_list', newValue)
},
{ deep: true }
);

View File

@ -393,9 +393,11 @@ export const util = {
* @returns {void} * @returns {void}
*/ */
setAndroidSystemBarColor(backgroundColor, frontColor = "#000000") { setAndroidSystemBarColor(backgroundColor, frontColor = "#000000") {
if (deviceUtil.isAndroid()) { // 使用同步方式判断或直接通过编译宏/plus判断
const isAndroid = uni.getSystemInfoSync().platform === 'android';
if (isAndroid) {
try { try {
// #ifndef APP-IOS // #ifdef APP-PLUS
if (plus.os.name === 'Android') { if (plus.os.name === 'Android') {
let color = plus.android.newObject("android.graphics.Color"); let color = plus.android.newObject("android.graphics.Color");
let activity = plus.android.runtimeMainActivity(); let activity = plus.android.runtimeMainActivity();