需求变更进行调整

This commit is contained in:
2026-01-30 09:01:38 +08:00
parent 44a4b33502
commit 79a21ff0a5
30 changed files with 1482 additions and 1707 deletions

View File

@@ -30,7 +30,7 @@
<!-- 顶部tab切换 -->
<view class="tab-bar">
<view v-for="(tab, index) in tabs" :key="index"
:class="['tab-item', activeTab === tab.value ? 'active' : '']" @click="activeTab = tab.value">
:class="['tab-item', activeTab === tab.value ? 'active' : '']" @click="onTabClick(tab.value)">
{{ tab.label }}
</view>
</view>
@@ -48,8 +48,8 @@
<!-- 右侧金额 + 图标 -->
<view class="right">
<view class="amount" :class="item.inOutType === '' ? 'income' : 'expense'">
{{ item.inOutType === '' ? '+' : '-' }}{{ item.itemAmount.toFixed(2) }}
<view class="amount" :class="item.inOutType === '' ? 'income' : 'expense'">
{{ item.inOutType === '' ? '+' : '-' }}{{ formatMoney(item.itemAmount) }}
</view>
<u-icon name="arrow-right" size="32" color="#ccc"></u-icon>
</view>
@@ -71,14 +71,14 @@
pageSize:10,
flowList: [],
loadStatus: 'loadmore',
activeTab: 'in', // 默认显示“收”
activeTab: '', // 默认显示“收”
tabs: [{
label: '收入',
value: 'in'
value: ''
},
{
label: '支出',
value: 'out'
value: ''
},
],
// 年份筛选相关
@@ -109,6 +109,10 @@
computed: {
},
methods: {
onTabClick(val) {
this.activeTab = val
this.resetAndLoad()
},
fetchPayRecord() {
if (this.loadStatus !== 'loadmore') return;
this.loadStatus = 'loading';
@@ -122,7 +126,6 @@
WT:this.$getToken()
}).then(res => {
const rows = res.data.result || [];
console.log(rows)
if (this.pageNo === 1) this.flowList = [];
this.flowList = this.flowList.concat(rows);
if (rows.length < this.pageSize) {
@@ -146,10 +149,14 @@
this.showYearPicker = false;
this.resetAndLoad();
},
formatMoney(val) {
if (!val && val !== 0) return '—';
return '¥' + Number(val).toFixed(2);
},
resetAndLoad(){
// 重新加载数据
this.loadStatus = 'loadmore';
this.pageNo = 1;
this.flowList = [];
this.fetchPayRecord();
},
loadMore() {

View File

@@ -134,8 +134,8 @@ export default {
this.$u.post('/contract/queryPage', {
pageNo: this.pageNo,
pageSize: this.pageSize,
startTime: this.startDate,
endTime: this.endDate,
startDate: this.startDate,
endDate: this.endDate,
signStatus: this.statusFilter
}, {
WT: this.$getToken()

View File

@@ -13,8 +13,8 @@
<view class="title">当前定位</view>
<view class="body">
<view class="left">
<image src="../../static/navigate.png" mode="widthFix" class="img"></image>
{{locationCity}}
<image src="/static/navigate.png" mode="widthFix" class="img"></image>
{{locationCity || '宜昌市'}}
</view>
<view class="right">切换城市</view>
</view>
@@ -28,7 +28,7 @@
</view>
</template>
<script>
// import wxGetAddress from '@/common/utils/wxGetAddress.js'
import wxGetAddress from '@/common/utils/wxGetAddress.js'
export default {
data() {
return {

View File

@@ -1,8 +1,11 @@
<template>
<view class="login-container">
<!-- 顶部状态栏 -->
<view class="status-bar"> <u-icon :name="btn.icon" :color="btn.color || '#333'" :size="btn.size || 40"></u-icon>
</view>
<!-- <view class="status-bar">
<u-icon name="arrow-left" size="40" color="#333" @tap="goBack" class="nav-left"></u-icon>
<u-icon :name="btn.icon" :color="btn.color || '#333'" :size="btn.size || 40"></u-icon>
</view> -->
<customNavbar opacity="0" :show-home="true" :show-back="false" :is-transparent="true" />
<!-- 主体内容区 -->
<view class="content">
@@ -51,7 +54,8 @@
screenWidth: 0,
scale: 1,
loginType: "0",
agreeProtocol: false
agreeProtocol: false,
menuRect: {}
}
},
@@ -62,10 +66,12 @@
this.scale = this.screenWidth / this.designWidth
}
})
const menuRect = wx.getMenuButtonBoundingClientRect()
this.menuRect = menuRect
},
onUnload() {
},
computed: {
// title 样式
@@ -82,6 +88,15 @@
textAlign: 'center'
}
},
navStyle() {
const rect = this.menuRect
if (!rect.top) return {}
return {
paddingTop: rect.top + 'px',
height: rect.height + 'px',
lineHeight: rect.height + 'px'
}
},
staticHost() {
return this.$config.staticUrl
},
@@ -119,7 +134,13 @@
}
this.doLogin(code);
},
goBack() {
uni.switchTab({
url: '/pages/index/index'
})
},
/** 执行登录逻辑 */
async doLogin(phoneGetCode) {
if (!this.agreeProtocol) {
@@ -142,19 +163,17 @@
});
// 调用后端登录接口
const authRes = await this.$u.post(`/login/weChatLogin`, {
phoneGetCode: phoneGetCode,
loginCode: loginRes.code,
loginType: this.loginType
});
// let tempToken = 'asd5646'
const authRes = await this.$u.post(`/login/weChatLogin`, {
phoneGetCode: phoneGetCode,
loginCode: loginRes.code,
loginType: this.loginType
});
// 保存token
let token = authRes.data;
this.$u.vuex('vuex_token', token);
// this.$u.vuex('vuex_token', tempToken);
uni.hideLoading();
this.getUserOtherInfo(this.loginType,token)
this.getUserOtherInfo(this.loginType, token)
// 跳转首页
uni.switchTab({
url: '/pages/index/index'
@@ -172,24 +191,17 @@
'WT': token,
'USERTYPE': loginType
}).then(obj => {
if(obj.flag){
if (obj.flag) {
uni.setStorageSync('userInfo', {
userType: obj.data.userType,
oaAuth: obj.data.oaAuth,
cusNo: obj.data.cusNo,
userName: obj.data.userName,
openId: obj.data.openId
userType: obj.data.userType,
oaAuth: obj.data.oaAuth,
cusNo: obj.data.cusNo,
userName: obj.data.userName,
openId: obj.data.openId,
subscribe: obj.data.subscribeMsg
})
}
});
// let tempUser = {
// userType: '0',
// oaAuth: '1',
// cusNo: 'cus0001',
// openId: '65456asd'
// }
// uni.setStorageSync('userInfo', tempUser)
},
/** 触发手机号选择(子组件 → 父组件) */
showPhoneSelector(phoneList, openid) {
@@ -220,8 +232,18 @@
overflow: hidden;
.status-bar {
margin-top: 7.56%;
display: flex;
flex-direction: row;
margin-top: 11.56%;
height: var(--status-bar-height);
}
.nav-left {
width: 80rpx;
display: flex;
padding-left: 5%;
align-items: center;
}
.content {
@@ -267,8 +289,9 @@
}
.user-type {
display: flex; justify-content: center;
gap: 40rpx;
display: flex;
justify-content: center;
gap: 40rpx;
margin-bottom: 20rpx;
}

View File

@@ -7,6 +7,10 @@
:show-with-animation="true"
:selectable="true"></u-parse>
</view>
<u-swiper :list="imgList" @change="change"></u-swiper>
<view v-for="(item,index) in attachments" :key="index">
<text @click="download(item)">{{ item.fileName }}</text>
</view>
</view>
</template>
@@ -16,7 +20,9 @@
return {
id:null,
title:'资讯',
content: ``
content: ``,
imgList:[],
attachments:[]
}
},
onLoad(option) {
@@ -31,8 +37,25 @@
if(res.flag) {
this.title = res.data.noticeTitle;
this.content = res.data.noticeContent;
if(res.data.imgs) {
const _this = this;
res.data.imgs.forEach(img=>{
_this.imgList.push(_this.$config.staticUrl + img)
})
}
this.attachments = res.data.attachments;
}
})
},
download(item){
uni.downloadFile({
url: this.$config.staticUrl + item.url,
success(res) {
uni.openDocument({
filePath: res.tempFilePath
})
}
})
}
}
}

View File

@@ -100,8 +100,7 @@
});
return;
}
let life = uni.getStorageSync('lifeData') || {}
let token = life.vuex_token
let token = this.$getToken()
let url = "/login/updateVerifyCode";
let encryptCode = rsaEncrypt(this.authCode);
this.$u.post(url, {
@@ -147,9 +146,9 @@
// #ifdef MP-WEIXIN
// 此处执行微信才执行
// #endif
uni.removeStorageSync('userInfo');
let token = this.$getToken();
let loginType = this.user.userType
let url = `/login/userInfo?loginType=${loginType}`;
let url = `/login/userInfo`;
this.$u.get(url, {}, {
'WT': token
}).then(obj => {
@@ -158,7 +157,8 @@
oaAuth: obj.data.oaAuth,
cusNo: obj.data.cusNo,
userName: obj.data.userName,
openId: obj.data.openId
openId: obj.data.openId,
subscribe: obj.data.subscribeMsg
})
});
}

View File

@@ -5,11 +5,11 @@
<view class="setting-content">
<u-cell-group>
<u-cell-item title="账号实名信息" @click="profile"></u-cell-item>
<!-- <u-cell-item title="订阅系统消息" :arrow="false">
<u-cell-item title="订阅系统消息" :arrow="false">
<u-switch v-model="longSubscribe" @change="toggleLongSubscribe" />
</u-cell-item> -->
</u-cell-item>
</u-cell-group>
</view>
@@ -30,6 +30,10 @@
},
onLoad() {
},
onShow(){
let user = uni.getStorageSync('userInfo');
this.longSubscribe = user.subscribe;
},
methods: {
profile() {
@@ -49,7 +53,7 @@
// 微信小程序专用
// #ifdef MP-WEIXIN
if (this.longSubscribe) {
const tmplIds = ['TEMPLATE_ID_1', 'TEMPLATE_ID_2'] // 在小程序后台配置的长期订阅模板
const tmplIds = ['ZVl9bkFv8Nzha2n_6wO36IBqe0H5VwJBvV-7OkVT5jo'] // 在小程序后台配置的长期订阅模板
wx.requestSubscribeMessage({
tmplIds,
success: (res) => {
@@ -89,6 +93,12 @@
saveLongSubscribeStatus(enable) {
// TODO: 后端保存用户长期订阅状态
console.log('保存长期订阅状态到后端', enable)
let token = this.$getToken();
let url = `/login/subscribeMsg?subscribe=${enable}`;
this.$u.get(url, {}, {
'WT': token
}).then(obj => {
});
}
}
}

View File

@@ -154,7 +154,13 @@ export default {
});
},
callManager(phone) {
if (!phone) return;
if (!phone) {
uni.showToast({
title: '暂无管家联系方式',
icon: 'none'
});
return;
}
uni.makePhoneCall({ phoneNumber: phone });
},
loadMore() {

View File

@@ -1,7 +1,7 @@
<template>
<view class="u-margin-left-20 u-margin-right-20">
<u-navbar :is-back="true" title="搜索" :border-bottom="false"></u-navbar>
<u-search placeholder="请输入资产名称/街道/位置等信息" v-model="keyword" @search="clickSearch(value)"
<u-search placeholder="请输入资产名称/位置等信息" v-model="keyword" @search="clickSearch(value)"
:focus="true" action-text="取消" @custom="cancelSearch"></u-search>
<!-- 搜索记录 -->
<template v-if="historyList.length > 0">

View File

@@ -18,7 +18,7 @@
<!-- 搜索栏 -->
<view class="search-bar-wrapper">
<view class="search-bar">
<view class="city-select" @click="location()">{{ city ||'宜昌'}} <text class="icon-down"></text>
<view class="city-select" @click="location()">{{ city ||'宜昌'}} <text class="icon-down"></text>
</view>
<view class="search-input" @click="search">
<text class="icon-search"></text>
@@ -42,12 +42,12 @@
<u-lazy-load threshold="750" border-radius="8" :image="item.coverImgUrl" :index="index"
@click="clickImage(item.assetsNo)" mode="aspectFill"></u-lazy-load>
<view class="item-title">{{ item.assetsName }}</view>
<view class="item-desc">{{ item.assetsType }} {{ item.footPrint }} {{ item.orientation }}
<view class="item-desc">{{ item.assetsType }} {{ item.footPrint }} {{ item.orientation ||'未知'}}
</view>
<view class="item-tags">
<view class="tag" v-for="(tag, tagIndex) in item.tags" :key="tagIndex">{{ tag }}</view>
</view>
<view class="item-price">¥{{ item.rentFee }}/<text></text></view>
<view class="item-price">¥{{ item.rentFee || '未知'}}/<text></text></view>
</view>
</template>
<template v-slot:right="{ rightList }">
@@ -55,11 +55,11 @@
<u-lazy-load threshold="750" border-radius="8" :image="item.coverImgUrl" :index="index"
@click="clickImage(item.assetsNo)" mode="aspectFill"></u-lazy-load>
<view class="item-title">{{ item.assetsName }}</view>
<view class="item-desc">{{ item.assetsType}} {{ item.footPrint }} {{ item.orientation }}</view>
<view class="item-desc">{{ item.assetsType}} {{ item.footPrint }} {{ item.orientation ||'未知'}}</view>
<view class="item-tags">
<view class="tag" v-for="(tag, tagIndex) in item.tags" :key="tagIndex">{{ tag }}</view>
</view>
<view class="item-price">¥{{ item.rentFee }}/<text></text></view>
<view class="item-price">¥{{ item.rentFee || '未知'}}/<text></text></view>
</view>
</template>
</u-waterfall>
@@ -81,9 +81,11 @@
},
data() {
return {
keyWord: null,
city: null,
streetList: [],
selectedBizZone: null,
selectedLayout: null,
selectedDecorationStatus: null,
selectedFeatures:[],
indexArr: [],
valueArr: [],
defaultSelected: [],
@@ -109,9 +111,6 @@
let villageName = option.villageName
let lifeData = uni.getStorageSync('lifeData');
let vuex_city = lifeData.vuex_city
if (option.keyword) {
this.keyWord = option.keyword
}
this.city = vuex_city
this.searchData = {}
if (type) {
@@ -120,9 +119,9 @@
if (villageName) {
this.searchData.villageName = villageName
}
this.resetAndLoad()
// 获取小区数据
this.findVillageList()
// 获取街道数据
this.findFilterTabData()
this.findHouseList(null,option.keyword)
},
onPageScroll(e) {
this.scrollTop = e.scrollTop;
@@ -167,13 +166,12 @@
this.pageNo = 1;
this.flowList = [];
this.loadStatus = 'loadmore';
this.$nextTick(() => {
this.$refs.uWaterfall && this.$refs.uWaterfall.clear();
this.findHouseList();
});
},
findHouseList(type = null) {
findHouseList(type = null,keyWord = null) {
if (this.loadStatus !== 'loadmore') return;
this.loadStatus = 'loading';
@@ -183,18 +181,16 @@
assetsType: type,
pageNo: this.pageNo,
pageSize: this.pageSize,
keyWord: this.keyWord
keyWord: this.keyWord,
bizZone: this.searchData.bizZone
}).then(result => {
const rows = result.data.result || [];
// 第一页无数据
if (this.pageNo === 1 && rows.length === 0) {
this.flowList = [];
this.loadStatus = 'nomore';
return;
}
rows.forEach(row => {
if (row.coverImgUrl) {
row.coverImgUrl = this.$config.staticUrl + row.coverImgUrl
@@ -204,7 +200,6 @@
})
// 追加数据
this.flowList = this.flowList.concat(rows);
// 判断是否还有下一页(核心)
if (rows.length < this.pageSize) {
this.loadStatus = 'nomore';
@@ -212,6 +207,7 @@
this.pageNo++; // ✅ 只有这里能 +1
this.loadStatus = 'loadmore';
}
this.keyWord = null
}).catch(err => {
console.log("获取资产信息失败:", err);
@@ -226,40 +222,48 @@
// 调用接口获取下一页
this.findHouseList();
},
findStreet() {
async findFilterTabData() {
// 填充商圈数据到筛选菜单
const bizZones = await this.getBizZone();
const features = await this.getFeatures();
let featureSubMenu = [];
for (let i = 0; i < bizZones.length; i++) {
searchData[0].submenu.push({
name: bizZones[i],
value: bizZones[i]
});
}
for (let i = 0; i < features.length; i++) {
featureSubMenu.push({
name: features[i],
value: features[i]
})
}
searchData[2].submenu.push({
name:"基础配套",
type:"radio-multi",
submenu: featureSubMenu
})
this.filterData = searchData;
},
findVillageList() {
// 前端写死的测试小区数据
// const villageData = [{
// name: '景秀天成'
// },
// {
// name: '锦绣花园'
// },
// {
// name: '阳光小区'
// },
// {
// name: '幸福家园'
// },
// {
// name: '碧水湾'
// }
// ];
// 添加测试小区数据到筛选菜单
// for (let i = 0; i < villageData.length; i++) {
// searchData[0].submenu.push({
// name: villageData[i].name,
// value: villageData[i].name
// });
// }
// this.filterData = searchData;
async getFeatures(){
const res = await this.$u.get('/assets/getFeatures');
if (res.flag) {
return res.data;
}
return [];
},
async getBizZone(){
const res = await this.$u.get('/assets/getBizZones');
if (res.flag) {
return res.data;
}
return [];
},
clickImage(houseId) {
this.$u.route({
url: '/pages-assets/assets/detail',
url: '/pages-assets/assets/assetsDetail',
params: {
assetsNo: houseId
}
@@ -267,25 +271,21 @@
},
//接收菜单结果
confirm(e) {
let type = e.value[1][0]
let villageName = e.value[0][0]
let price = e.value[2][0]
let combo = e.value[3]
let houseNum = combo[0]
let bizZone = e.value[0][0]
let price = e.value[1][0]
let combo = e.value[2]
let layout = combo[0]
let decoration = combo[1]
let feature = combo[2]
this.searchData = {}
if (type) {
this.searchData.type = type
}
if (villageName) {
this.searchData.villageName = villageName
if (bizZone) {
this.searchData.bizZone = bizZone
}
if (price) {
this.searchData.price = price
}
if (houseNum && houseNum.length > 0) {
this.searchData.houseNum = houseNum.toString()
if (layout && layout.length > 0) {
this.searchData.layout = layout.toString()
}
if (decoration && decoration.length > 0) {
this.searchData.decoration = decoration.toString()

View File

@@ -2,7 +2,7 @@
<view class="calculator">
<customNavbar title="计算器" :showHome="true" />
<input class="display" v-model="expression" disabled />
<textarea class="display" auto-height v-model="expression" disabled />
<view class="keys">
<view
@@ -36,8 +36,7 @@ export default {
'4','5','6','*',
'1','2','3','-',
'0','.','%','+',
'C','⌫',
'='
'C','⌫','='
]
}
},
@@ -186,9 +185,10 @@ export default {
}
.display {
height: 120rpx;
font-size: 44rpx;
font-size: 70rpx;
font-weight: 600;
min-height: 400rpx;
width: 95%;
border-radius: 12rpx;
background: #fff;
padding: 0 24rpx;
@@ -206,7 +206,7 @@ export default {
background: #ffffff;
text-align: center;
padding: 36rpx 0;
font-size: 36rpx;
font-size: 70rpx;
font-weight: 500;
border-radius: 16rpx;
box-shadow: 0 10rpx 20rpx rgba(0,0,0,0.08);
@@ -220,7 +220,7 @@ export default {
background: #ffe8d9;
color: #ff7a18;
box-shadow: 0 10rpx 20rpx rgba(255,122,24,0.35);
font-size: 40rpx;
font-size: 70rpx;
}
.control {
@@ -230,10 +230,10 @@ export default {
}
.equal {
grid-column: span 4;
width: 215%;
background: linear-gradient(135deg, #4facfe, #00f2fe);
color: #fff;
font-size: 44rpx;
font-size: 70rpx;
font-weight: 600;
box-shadow: 0 12rpx 28rpx rgba(79,172,254,0.5);
}

View File

@@ -130,7 +130,7 @@
this.selectedBills = this.selectedBills.filter(b => b.id !== item.id);
}
let sumFee = 0;
this.selectedBills.forEach(b => sumFee += b.amount);
this.selectedBills.forEach(b => sumFee += b.billAmount);
this.sumAmount = sumFee;
},
updateSelected() {

View File

@@ -1,438 +1,19 @@
<template>
<view class="vr-page">
<!-- 返回按钮 -->
<view class="back-btn" @click="back"> 返回</view>
<!-- Canvas -->
<canvas canvas-id="vrCanvas" class="vr-canvas" @touchstart="onTouchStart" @touchmove="onTouchMove"
@touchend="onTouchEnd"></canvas>
<!-- 左上角 3D mini preview -->
<canvas v-if="currentSpace.type==='model'" canvas-id="miniCanvas" class="mini-canvas"></canvas>
<!-- Hotspot 点击由 Three.js raycaster 处理 -->
<!-- 底栏 -->
<view class="bottom-bar">
<!-- 左侧空间切换 -->
<view class="left">
<button @click="showSpaceList = !showSpaceList">
<image src="/static/icons/space.png" />
</button>
</view>
<!-- 右侧工具 -->
<view class="right">
<button @click="showTools = !showTools">
<image src="/static/icons/tools.png" />
</button>
</view>
</view>
<!-- 空间缩略图弹窗 -->
<view v-if="showSpaceList" class="space-list">
<view v-for="space in spaces" :key="space.id" class="space-item" @click="switchSpace(space.id)">
<image :src="space.thumbnail" />
<text>{{ space.name }}</text>
</view>
</view>
<!-- 工具栏弹窗 -->
<view v-if="showTools" class="tools-panel">
<button @click="resetView">重置视角</button>
<button @click="toggleFullScreen">全屏</button>
<button @click="takePhoto">拍照</button>
</view>
<!-- 上一/下一空间按钮 -->
<view class="switch-btn left" @click="prevSpace"></view>
<view class="switch-btn right" @click="nextSpace"></view>
</view>
<web-view :src="src + id"></web-view>
</template>
<script>
import * as THREE from 'three'
import {
GLTFLoader
} from 'three/examples/jsm/loaders/GLTFLoader.js'
export default {
name: 'VRView',
data() {
return {
spaces: [], // VR 空间数组
currentIndex: 0,
currentSpace: null,
// Three.js 主场景
renderer: null,
scene: null,
camera: null,
sphere: null,
videoTexture: null,
// Mini 3D模型
miniRenderer: null,
miniScene: null,
miniCamera: null,
miniModel: null,
// Hotspot
raycaster: null,
mouse: new THREE.Vector2(),
// 触控
isDragging: false,
lastX: 0,
lastY: 0,
lon: 0,
lat: 0,
// UI
showSpaceList: false,
showTools: false
}
},
onLoad(options) {
if (options.vrList) {
try {
// JSON.parse(decodeURIComponent(options.spaces))
this.spaces = options.vrList
} catch (e) {
console.error('解析 VR 空间失败', e)
}
}
this.currentSpace = this.spaces[this.currentIndex]
this.initThree()
},
methods: {
back() {
const pages = getCurrentPages();
if (pages.length > 1) {
uni.navigateBack();
} else {
uni.switchTab({
url:'/pages/index/index'
})
}
},
// ==================== Three.js 主场景 ====================
initThree() {
const canvasQuery = uni.createSelectorQuery()
canvasQuery.select('#vrCanvas').node().exec(res => {
const canvasNode = res[0].node
const {
windowWidth: width,
windowHeight: height
} = uni.getSystemInfoSync()
// 主 renderer
this.renderer = new THREE.WebGLRenderer({
canvas: canvasNode,
antialias: true
})
this.renderer.setSize(width, height)
// scene & camera
this.scene = new THREE.Scene()
this.camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 2000)
this.camera.position.set(0, 0, 0.01)
// raycaster
this.raycaster = new THREE.Raycaster()
this.loadSpace(this.currentSpace)
this.animate()
})
},
// 加载空间
loadSpace(space) {
// 清空场景
while (this.scene.children.length) this.scene.remove(this.scene.children[0])
if (this.videoTexture) {
this.videoTexture = null
}
// 图片
if (space.type === 'image') {
const geometry = new THREE.SphereGeometry(500, 60, 40)
geometry.scale(-1, 1, 1)
const texture = new THREE.TextureLoader().load(space.src)
const material = new THREE.MeshBasicMaterial({
map: texture
})
this.sphere = new THREE.Mesh(geometry, material)
this.scene.add(this.sphere)
}
// CubeMap
else if (space.type === 'cubemap') {
const urls = [space.cubemap.px, space.cubemap.nx, space.cubemap.py, space.cubemap.ny, space.cubemap.pz,
space.cubemap.nz
]
const cubeTexture = new THREE.CubeTextureLoader().load(urls)
this.scene.background = cubeTexture
}
// 视频
else if (space.type === 'video') {
const video = document.createElement('video')
video.src = space.src
video.crossOrigin = 'anonymous'
video.loop = true
video.muted = false
video.autoplay = true
video.play()
this.videoTexture = new THREE.VideoTexture(video)
const geometry = new THREE.SphereGeometry(500, 60, 40)
geometry.scale(-1, 1, 1)
const material = new THREE.MeshBasicMaterial({
map: this.videoTexture
})
this.sphere = new THREE.Mesh(geometry, material)
this.scene.add(this.sphere)
}
// 模型
else if (space.type === 'model') {
const loader = new GLTFLoader()
loader.load(space.src, gltf => {
this.sphere = gltf.scene
this.sphere.scale.set(space.modelScale, space.modelScale, space.modelScale)
this.scene.add(this.sphere)
this.initMiniModel(gltf.scene.clone(), space.modelScale)
})
}
// Hotspots
if (space.hotspots && space.hotspots.length) {
space.hotspots.forEach(h => {
const spriteMap = new THREE.TextureLoader().load('/static/icons/hotspot.png')
const spriteMaterial = new THREE.SpriteMaterial({
map: spriteMap
})
const sprite = new THREE.Sprite(spriteMaterial)
const phi = THREE.MathUtils.degToRad(90 - h.position.lat)
const theta = THREE.MathUtils.degToRad(h.position.lon)
const radius = 500
sprite.position.set(
radius * Math.sin(phi) * Math.cos(theta),
radius * Math.cos(phi),
radius * Math.sin(phi) * Math.sin(theta)
)
sprite.userData.targetId = h.targetId
this.scene.add(sprite)
})
}
},
animate() {
requestAnimationFrame(this.animate)
// 相机旋转
const phi = THREE.MathUtils.degToRad(90 - this.lat)
const theta = THREE.MathUtils.degToRad(this.lon)
this.camera.target = new THREE.Vector3(
500 * Math.sin(phi) * Math.cos(theta),
500 * Math.cos(phi),
500 * Math.sin(phi) * Math.sin(theta)
)
this.camera.lookAt(this.camera.target)
this.renderer.render(this.scene, this.camera)
// mini 模型同步旋转
if (this.currentSpace.type === 'model' && this.miniModel) {
this.miniModel.rotation.y = this.camera.rotation.y
this.miniRenderer.render(this.miniScene, this.miniCamera)
}
},
// ==================== Mini 3D 模型 ====================
initMiniModel(model, scale) {
const canvasQuery = uni.createSelectorQuery()
canvasQuery.select('#miniCanvas').node().exec(res => {
const canvasNode = res[0].node
this.miniRenderer = new THREE.WebGLRenderer({
canvas: canvasNode,
antialias: true,
alpha: true
})
this.miniRenderer.setSize(150, 150)
this.miniScene = new THREE.Scene()
this.miniCamera = new THREE.PerspectiveCamera(75, 1, 0.1, 2000)
this.miniCamera.position.set(0, 50, 100)
this.miniModel = model
this.miniScene.add(this.miniModel)
})
},
// ==================== 空间切换 ====================
switchSpace(id) {
const index = this.spaces.findIndex(s => s.id === id)
if (index >= 0) {
this.currentIndex = index
this.currentSpace = this.spaces[index]
this.loadSpace(this.currentSpace)
this.showSpaceList = false
}
},
prevSpace() {
this.currentIndex = (this.currentIndex - 1 + this.spaces.length) % this.spaces.length;
this.switchSpace(this.spaces[this.currentIndex].id)
},
nextSpace() {
this.currentIndex = (this.currentIndex + 1) % this.spaces.length;
this.switchSpace(this.spaces[this.currentIndex].id)
},
// ==================== 手势 ====================
onTouchStart(e) {
this.isDragging = true;
const t = e.touches[0];
this.lastX = t.clientX;
this.lastY = t.clientY
},
onTouchMove(e) {
if (!this.isDragging) return;
const t = e.touches[0];
const dx = t.clientX - this.lastX;
const dy = t.clientY - this.lastY;
this.lastX = t.clientX;
this.lastY = t.clientY;
this.lon -= dx * 0.1;
this.lat += dy * 0.1;
this.lat = Math.max(-85, Math.min(85, this.lat))
},
onTouchEnd() {
this.isDragging = false
},
// ==================== 工具栏功能示例 ====================
resetView() {
this.lon = 0;
this.lat = 0
},
toggleFullScreen() {
uni.setScreenBrightness({
value: 1
})
}, // 示例
takePhoto() {
console.log('拍照功能可扩展')
}
}
}
</script>
<style scoped>
.vr-page {
width: 100%;
height: 100%;
background: #000;
position: relative
}
.vr-canvas {
width: 100%;
height: 100%;
display: block
}
.mini-canvas {
position: absolute;
top: 20rpx;
left: 20rpx;
width: 150rpx;
height: 150rpx;
z-index: 10;
border: 1px solid #ccc;
border-radius: 6rpx
}
.back-btn {
position: absolute;
top: 40rpx;
left: 20rpx;
color: #fff;
font-size: 28rpx;
z-index: 10
}
.bottom-bar {
position: absolute;
bottom: 0;
width: 100%;
height: 80rpx;
display: flex;
justify-content: space-between;
padding: 0 20rpx;
z-index: 10
}
.bottom-bar .left,
.bottom-bar .right {
display: flex;
align-items: center
}
.space-list {
position: absolute;
bottom: 80rpx;
left: 0;
width: 100%;
height: 150rpx;
background: rgba(0, 0, 0, 0.7);
display: flex;
overflow-x: scroll;
padding: 10rpx
}
.space-list .space-item {
margin-right: 10rpx;
display: flex;
flex-direction: column;
align-items: center
}
.space-list image {
width: 120rpx;
height: 80rpx;
border-radius: 6rpx
}
.tools-panel {
position: absolute;
bottom: 80rpx;
right: 0;
width: 200rpx;
background: rgba(0, 0, 0, 0.7);
display: flex;
flex-direction: column;
padding: 10rpx
}
.tools-panel button {
margin-bottom: 10rpx;
color: #fff
}
.switch-btn {
position: absolute;
top: 50%;
transform: translateY(-50%);
z-index: 10;
color: #fff;
font-size: 60rpx;
background: rgba(0, 0, 0, 0.3);
width: 80rpx;
height: 80rpx;
line-height: 80rpx;
text-align: center;
border-radius: 50%
}
.switch-btn.left {
left: 20rpx
}
.switch-btn.right {
right: 20rpx
}
</style>
export default {
data() {
return {
id: '' ,// WebView URL
src: this.$config.staticUrl + '/public/vr/vr.html?id='
}
},
computed: {
},
onLoad(options) {
this.id = options.id
}
}
</script>

View File

@@ -76,8 +76,8 @@
let url = '/bill/pageQueryWaeBill'
this.$u.post(url, {
pageNo: this.pageNo,
pageSize: this.pageSize
pageSize: this.pageSize,
billStatus: '待缴费'
}, {
WT: token
}).then(
@@ -129,7 +129,7 @@
this.selectedBills = this.selectedBills.filter(b => b.id !== item.id);
}
let sumFee = 0;
this.selectedBills.forEach(b => sumFee += b.amount);
this.selectedBills.forEach(b => sumFee += b.billAmount);
this.sumAmount = sumFee;
},
updateSelected() {