完成大体功能和样式
This commit is contained in:
313
pages-biz/bill/bill.vue
Normal file
313
pages-biz/bill/bill.vue
Normal file
@@ -0,0 +1,313 @@
|
||||
<template>
|
||||
<view class="bill-list-page">
|
||||
<!-- 自定义导航栏 -->
|
||||
<customNavbar title="我的账单"></customNavbar>
|
||||
|
||||
<!-- 筛选栏 -->
|
||||
<view class="filter-bar">
|
||||
<!-- 年份筛选点击区域 -->
|
||||
<view class="year-filter" @click="toggleYearPicker">
|
||||
<text class="year-text">{{ currentYear }}年</text>
|
||||
<u-icon name="arrow-down" size="24" color="#666"></u-icon>
|
||||
</view>
|
||||
|
||||
<!-- 自定义年份选择器弹窗 -->
|
||||
<u-popup v-model="showYearPicker" mode="bottom" border-radius="20rpx" :closeable="true">
|
||||
<view class="year-picker-popup">
|
||||
<!-- 弹窗标题 -->
|
||||
<view class="popup-header">
|
||||
<text class="popup-title">选择年份</text>
|
||||
</view>
|
||||
|
||||
<!-- 年份列表 -->
|
||||
<view class="year-list">
|
||||
<view class="year-item" v-for="year in years" :key="year"
|
||||
:class="{ active: currentYear === year }" @click="selectYear(year)">
|
||||
{{ year }}年
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</view>
|
||||
|
||||
<!-- 账单列表 -->
|
||||
<scroll-view scroll-y class="scroll-content" :scroll-top="0" :refresher-enabled="true"
|
||||
:refresher-triggered="isRefreshing" @refresherrefresh="refresh" @scrolltolower="loadMore">
|
||||
<view v-if="flowList.length > 0">
|
||||
<view class="bill-item" v-for="item in flowList" :key="item.id" @click="goDetail(item)">
|
||||
<!-- 左侧信息 -->
|
||||
<view class="bill-left">
|
||||
<text class="bill-name">{{ item.billName }}</text>
|
||||
<text class="bill-date" v-if="item.payTime">支付时间:{{ item.payTime }}</text>
|
||||
<text class="bill-date" v-if="!item.payTime">账单截止日期: {{ item.billEndDate }}</text>
|
||||
<text :class="['bill-status', (item.billStatus === '待缴费' ? 'unpay' : paid)]" >{{ item.billStatus }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 右侧金额 + 箭头 -->
|
||||
<view class="bill-right">
|
||||
<text class="amount">{{ formatMoney(item.billAmount) }}</text>
|
||||
<u-icon name="arrow-right" size="28" color="#ccc" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else class="empty">
|
||||
<u-empty mode="list" text="暂无账单" />
|
||||
</view>
|
||||
|
||||
<u-loadmore :status="loadStatus" />
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
bills: [],
|
||||
flowList: [],
|
||||
isRefreshing: false,
|
||||
loadStatus: 'more', // u-loadmore 状态
|
||||
// 年份筛选相关
|
||||
currentYear: null,
|
||||
years: [],
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
showYearPicker: false
|
||||
};
|
||||
},
|
||||
onLoad(options) {
|
||||
// 可以从 options 获取用户ID或合同ID
|
||||
let year = new Date().getFullYear();
|
||||
this.currentYear = year;
|
||||
this.years = [];
|
||||
for (let i = 5; i >= 1; i--) {
|
||||
this.years.push(year - i);
|
||||
}
|
||||
this.loadBills();
|
||||
},
|
||||
onReachBottom() {
|
||||
this.loadStatus = 'loading';
|
||||
// 获取数据
|
||||
this.loadBills();
|
||||
},
|
||||
onShow(){
|
||||
this.$checkToken(this.$getToken())
|
||||
},
|
||||
methods: {
|
||||
// 切换年份选择器显示
|
||||
toggleYearPicker() {
|
||||
this.showYearPicker = !this.showYearPicker;
|
||||
},
|
||||
|
||||
// 选择年份
|
||||
selectYear(year) {
|
||||
this.currentYear = year;
|
||||
this.showYearPicker = false;
|
||||
// 重新加载账单数据
|
||||
this.loadBills();
|
||||
},
|
||||
|
||||
loadBills() {
|
||||
let url = '/bill/pageQueryContractBill'
|
||||
this.$u.post(url, {
|
||||
pageNo: this.pageNo,
|
||||
pageSize: this.pageSize,
|
||||
year: this.currentYear,
|
||||
billStatus: '已缴费'
|
||||
},{
|
||||
WT: this.$getToken()
|
||||
}).then(result => {
|
||||
const data = result.data.result;
|
||||
this.bills = data;
|
||||
for (let i = 0; i < this.bills.length; i++) {
|
||||
let item = this.bills[i]
|
||||
this.flowList.push(item);
|
||||
}
|
||||
++this.pageNo
|
||||
}).catch(err => {
|
||||
console.log("获取合同账单信息失败:", err)
|
||||
})
|
||||
},
|
||||
|
||||
refresh() {
|
||||
this.isRefreshing = true;
|
||||
setTimeout(() => {
|
||||
this.loadBills();
|
||||
this.isRefreshing = false;
|
||||
}, 1000);
|
||||
},
|
||||
|
||||
loadMore() {
|
||||
// 可以调用接口分页加载更多数据
|
||||
this.loadStatus = 'noMore';
|
||||
},
|
||||
|
||||
formatMoney(val) {
|
||||
if (!val && val !== 0) return '—';
|
||||
return '¥' + Number(val).toFixed(2);
|
||||
},
|
||||
|
||||
goDetail(item) {
|
||||
uni.navigateTo({
|
||||
url: `/pages-biz/bill/billDetail?id=${item.id}`,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.bill-list-page {
|
||||
background: #f7f8fa;
|
||||
min-height: 100vh;
|
||||
padding-top: 160rpx;
|
||||
/* 给导航栏留空间 */
|
||||
}
|
||||
|
||||
.filter-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: #fff;
|
||||
padding: 24rpx 30rpx;
|
||||
margin: 20rpx;
|
||||
border-radius: 12rpx;
|
||||
box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
/* 年份筛选 */
|
||||
.year-filter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 支出/收入切换 */
|
||||
.tab-bar {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
padding: 12rpx 32rpx;
|
||||
font-size: 28rpx;
|
||||
border-radius: 24rpx;
|
||||
color: #333;
|
||||
background-color: #f5f5f5;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&.active {
|
||||
color: #fff;
|
||||
background: #ff3b30;
|
||||
}
|
||||
}
|
||||
|
||||
/* 自定义年份选择器样式 */
|
||||
.year-picker-popup {
|
||||
background: #fff;
|
||||
padding: 30rpx 0;
|
||||
}
|
||||
|
||||
.popup-header {
|
||||
text-align: center;
|
||||
padding: 20rpx 0;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.popup-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.year-list {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.year-item {
|
||||
font-size: 32rpx;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
padding: 24rpx;
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 16rpx;
|
||||
background: #f5f5f5;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: #ff3b30;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: #ff5252;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.scroll-content {
|
||||
margin-top: 20rpx;
|
||||
padding-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.bill-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
background: #fff;
|
||||
margin: 0 20rpx 20rpx 20rpx;
|
||||
border-radius: 12rpx;
|
||||
padding: 20rpx;
|
||||
box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.bill-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.bill-name {
|
||||
font-size: 28rpx;
|
||||
color: #2D2B2C;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.bill-status{
|
||||
font-size: 30rpx;
|
||||
font-weight: 800;
|
||||
&.paid {
|
||||
color: red;
|
||||
}
|
||||
|
||||
&.unpay {
|
||||
color: green;
|
||||
}
|
||||
}
|
||||
.bill-date,
|
||||
.bill-status {
|
||||
margin-top: 18rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.bill-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.amount {
|
||||
font-size: 28rpx;
|
||||
color: #F34038;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.empty {
|
||||
margin-top: 200rpx;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
136
pages-biz/bill/billDetail.vue
Normal file
136
pages-biz/bill/billDetail.vue
Normal file
@@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<view class="bill-detail-page">
|
||||
<!-- 顶部自定义导航栏 -->
|
||||
<custom-navbar title="账单详情" />
|
||||
|
||||
<scroll-view scroll-y class="scroll-content">
|
||||
<!-- 基本信息 -->
|
||||
<view class="section">
|
||||
<view class="section-title">账单信息</view>
|
||||
|
||||
<view class="info-item">
|
||||
<text class="label">费用名称:</text>
|
||||
<text class="value">{{ bill.feeName }}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-item">
|
||||
<text class="label">支付时间:</text>
|
||||
<text class="value">{{ bill.payTime || '未支付' }}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-item">
|
||||
<text class="label">支付状态:</text>
|
||||
<text class="value">{{ bill.status }}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-item">
|
||||
<text class="label">金额:</text>
|
||||
<text class="value">{{ formatMoney(bill.amount) }}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-item" v-if="bill.contractId">
|
||||
<text class="label">关联合同:</text>
|
||||
<text class="value" @click="goContractDetail(bill.contractId)" style="color:#007aff;">点击查看</text>
|
||||
</view>
|
||||
|
||||
<view class="info-item" v-if="bill.payMethod">
|
||||
<text class="label">支付方式:</text>
|
||||
<text class="value">{{ bill.payMethod }}</text>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
bill: {},
|
||||
};
|
||||
},
|
||||
onLoad(options) {
|
||||
const { id } = options; // 列表页传过来的账单ID
|
||||
this.loadBillDetail(id);
|
||||
},
|
||||
onShow() {
|
||||
this.$checkToken(this.$getToken())
|
||||
},
|
||||
methods: {
|
||||
loadBillDetail(id) {
|
||||
// 模拟请求接口
|
||||
this.bill = {
|
||||
id,
|
||||
feeName: '第一期租金(2024.12.01-2024.12.31)',
|
||||
payTime: '2024-12-05',
|
||||
status: '已支付',
|
||||
amount: 3000,
|
||||
contractId: 'c123',
|
||||
payMethod: '线上支付',
|
||||
};
|
||||
},
|
||||
formatMoney(val) {
|
||||
if (!val && val !== 0) return '—';
|
||||
return '¥' + Number(val).toFixed(2);
|
||||
},
|
||||
goDetail(contractId) {
|
||||
uni.navigateTo({
|
||||
url: `/pages-biz/contract/contractDetail?id=${contractId}`,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.bill-detail-page {
|
||||
background: #f7f8fa;
|
||||
min-height: 100vh;
|
||||
padding-top: 175rpx; // 自定义导航栏高度
|
||||
}
|
||||
|
||||
.scroll-content {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.section {
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.section-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
margin-bottom: 20rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 16rpx 0;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
margin-right: 16rpx; /* 增加右侧安全间距 */
|
||||
max-width: 70%; /* 防止过长文字挤到箭头或屏幕边缘 */
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
328
pages-biz/bill/payHistory.vue
Normal file
328
pages-biz/bill/payHistory.vue
Normal file
@@ -0,0 +1,328 @@
|
||||
<template>
|
||||
<view class="pay-history-page">
|
||||
<customNavbar title="账单" />
|
||||
|
||||
<!-- 筛选栏 -->
|
||||
<view class="filter-bar">
|
||||
<!-- 年份筛选点击区域 -->
|
||||
<view class="year-filter" @click="toggleYearPicker">
|
||||
<text class="year-text">{{ currentYear }}年</text>
|
||||
<u-icon name="arrow-down" size="24" color="#666"></u-icon>
|
||||
</view>
|
||||
|
||||
<!-- 自定义年份选择器弹窗 -->
|
||||
<u-popup v-model="showYearPicker" mode="bottom" border-radius="20rpx" :closeable="true">
|
||||
<view class="year-picker-popup">
|
||||
<!-- 弹窗标题 -->
|
||||
<view class="popup-header">
|
||||
<text class="popup-title">选择年份</text>
|
||||
</view>
|
||||
|
||||
<!-- 年份列表 -->
|
||||
<view class="year-list">
|
||||
<view class="year-item" v-for="year in years" :key="year"
|
||||
:class="{ active: currentYear === year }" @click="selectYear(year)">
|
||||
{{ year }}年
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
<!-- 顶部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">
|
||||
{{ tab.label }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 收支记录列表 -->
|
||||
<scroll-view scroll-y class="scroll-content" @scrolltolower="loadMore">
|
||||
<view v-if="flowList.length > 0" class="record-list">
|
||||
<view v-for="(item, index) in flowList" :key="index" class="record-item" @click="goDetail(item)">
|
||||
<!-- 左侧信息 -->
|
||||
<view class="left">
|
||||
<view class="title">{{ item.itemName }}</view>
|
||||
<view class="sub">{{ item.payDate || item.inDate || '未知'}}</view>
|
||||
</view>
|
||||
|
||||
<!-- 右侧金额 + 图标 -->
|
||||
<view class="right">
|
||||
<view class="amount" :class="item.inOutType === '收' ? 'income' : 'expense'">
|
||||
{{ item.inOutType === '收' ? '+' : '-' }}{{ item.itemAmount.toFixed(2) }}
|
||||
</view>
|
||||
<u-icon name="arrow-right" size="32" color="#ccc"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else class="empty">
|
||||
<u-empty mode="list" text="暂无记录" />
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
pageNo: 1,
|
||||
pageSize:10,
|
||||
flowList: [],
|
||||
loadStatus: 'loadmore',
|
||||
activeTab: 'in', // 默认显示“收”
|
||||
tabs: [{
|
||||
label: '收入',
|
||||
value: 'in'
|
||||
},
|
||||
{
|
||||
label: '支出',
|
||||
value: 'out'
|
||||
},
|
||||
],
|
||||
// 年份筛选相关
|
||||
currentYear: null,
|
||||
years: [],
|
||||
showYearPicker: false
|
||||
};
|
||||
},
|
||||
onLoad(options) {
|
||||
let year = new Date().getFullYear();
|
||||
this.currentYear = year;
|
||||
this.years = [];
|
||||
for (let i = 5; i >= 1; i--) {
|
||||
this.years.push(year - i);
|
||||
}
|
||||
this.fetchPayRecord();
|
||||
},
|
||||
onShow() {
|
||||
this.$checkToken(this.$getToken())
|
||||
},
|
||||
watch: {
|
||||
activeTab(newVal, oldVal) {
|
||||
if (newVal !== oldVal) {
|
||||
this.resetAndLoad()
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
methods: {
|
||||
fetchPayRecord() {
|
||||
if (this.loadStatus !== 'loadmore') return;
|
||||
this.loadStatus = 'loading';
|
||||
let url = '/bill/pageQueryPayRecord'
|
||||
this.$u.post(url, {
|
||||
pageNo: this.pageNo,
|
||||
pageSize: this.pageSize,
|
||||
year: this.currentYear,
|
||||
leType: this.activeTab
|
||||
},{
|
||||
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) {
|
||||
this.loadStatus = 'nomore';
|
||||
} else {
|
||||
this.pageNo++;
|
||||
this.loadStatus = 'loadmore';
|
||||
}
|
||||
}).catch(err => {
|
||||
console.log("获取账单记录失败:", err)
|
||||
this.loadStatus = 'loadmore';
|
||||
})
|
||||
},
|
||||
// 切换年份选择器显示
|
||||
toggleYearPicker() {
|
||||
this.showYearPicker = !this.showYearPicker;
|
||||
},
|
||||
// 选择年份
|
||||
selectYear(year) {
|
||||
this.currentYear = year;
|
||||
this.showYearPicker = false;
|
||||
this.resetAndLoad();
|
||||
},
|
||||
resetAndLoad(){
|
||||
// 重新加载数据
|
||||
this.pageNo = 1;
|
||||
this.flowList = [];
|
||||
this.fetchPayRecord();
|
||||
},
|
||||
loadMore() {
|
||||
// 只有在 loadStatus 为 'loadmore' 时才触发
|
||||
if (this.loadStatus !== 'loadmore') return;
|
||||
// 调用接口获取下一页
|
||||
this.fetchPayRecord();
|
||||
},
|
||||
goDetail(item) {
|
||||
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.pay-history-page {
|
||||
background: #f8f8f8;
|
||||
min-height: 100vh;
|
||||
padding-top: 175rpx;
|
||||
.filter-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: #fff;
|
||||
padding: 24rpx 30rpx;
|
||||
margin: 20rpx;
|
||||
margin-bottom: 0;
|
||||
border-radius: 12rpx;
|
||||
box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
/* 年份筛选 */
|
||||
.year-filter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 支出/收入切换 */
|
||||
.tab-bar {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
padding: 12rpx 32rpx;
|
||||
font-size: 28rpx;
|
||||
border-radius: 24rpx;
|
||||
color: #333;
|
||||
background-color: #f5f5f5;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&.active {
|
||||
color: #fff;
|
||||
background: #ff3b30;
|
||||
}
|
||||
}
|
||||
|
||||
/* 自定义年份选择器样式 */
|
||||
.year-picker-popup {
|
||||
background: #fff;
|
||||
padding: 30rpx 0;
|
||||
}
|
||||
|
||||
.popup-header {
|
||||
text-align: center;
|
||||
padding: 20rpx 0;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.popup-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.year-list {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.year-item {
|
||||
font-size: 32rpx;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
padding: 24rpx;
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 16rpx;
|
||||
background: #f5f5f5;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: #ff3b30;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: #ff5252;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.record-list {
|
||||
padding: 20rpx;
|
||||
|
||||
.record-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
margin-bottom: 20rpx;
|
||||
padding: 20rpx;
|
||||
box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.03);
|
||||
|
||||
.left {
|
||||
flex: 1;
|
||||
|
||||
.title {
|
||||
font-size: 28rpx;
|
||||
font-weight:5600;
|
||||
color: #2D2B2C;
|
||||
}
|
||||
|
||||
.sub {
|
||||
font-size: 24rpx;
|
||||
color: #86868C;
|
||||
margin-top: 18rpx;
|
||||
}
|
||||
|
||||
.status {
|
||||
font-size: 28rpx;
|
||||
|
||||
&.paid {
|
||||
color: #73B936;
|
||||
}
|
||||
|
||||
&.unpaid {
|
||||
color: #F34038;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.amount {
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
margin-right: 10rpx;
|
||||
|
||||
&.income {
|
||||
color: #73B936;
|
||||
}
|
||||
|
||||
&.expense {
|
||||
color: #F34038;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.empty {
|
||||
margin-top: 100rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
248
pages-biz/chooseAddress/cityList.vue
Normal file
248
pages-biz/chooseAddress/cityList.vue
Normal file
@@ -0,0 +1,248 @@
|
||||
<template>
|
||||
<view>
|
||||
<view class="list">
|
||||
<view class="keyList" v-for="(keyItem, keyIndex) in renderList" :key="keyIndex">
|
||||
<view :id="'keyword' + keyItem.key" class="keyword">
|
||||
<text>{{ keyItem.key === 'AAA' ? '热门地区' : keyItem.key }}</text>
|
||||
</view>
|
||||
<view class="item b-b" v-for="(item, index) in keyItem.list" :key="index" @click="chooseCity(item)">
|
||||
<text>{{ item.label }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="key-list column" @click.stop.prevent="stopPrevent">
|
||||
<view
|
||||
class="key-item"
|
||||
:class="{active: index === keywordCurrent}"
|
||||
v-for="(item, index) in renderList"
|
||||
:key="index"
|
||||
@click="onKeywordClick(index)"
|
||||
>
|
||||
<text>{{ item.key === 'AAA' ? '#' : item.key }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="key-show center" :class="{show: keyChanged}">
|
||||
<text>{{ keywordCurrentName }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import getFirstLetter from './js/getFirstLetter.js'
|
||||
import cityData from './js/city'
|
||||
const _anchorList = [];
|
||||
let _onKeywordClicking = false;
|
||||
let _timer = 0;
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
keyChanged: false,
|
||||
keywordCurrentName: '',
|
||||
keywordCurrent: 0, //当前索引
|
||||
renderList: [],
|
||||
list: [],
|
||||
}
|
||||
},
|
||||
onPageScroll(e) {
|
||||
if (_onKeywordClicking) {
|
||||
return;
|
||||
}
|
||||
const top = e.scrollTop;
|
||||
for (let i = 0; i < _anchorList.length; i++) {
|
||||
if (top > _anchorList[i]) {
|
||||
this.keywordCurrent = i;
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
keywordCurrent(val) {
|
||||
this.keywordCurrentName = this.renderList[val].key;
|
||||
if (_timer) {
|
||||
clearTimeout(_timer);
|
||||
}
|
||||
this.keyChanged = true;
|
||||
_timer = setTimeout(() => {
|
||||
this.keyChanged = false;
|
||||
}, 500)
|
||||
}
|
||||
},
|
||||
onLoad(options) {
|
||||
this.initList();
|
||||
},
|
||||
methods: {
|
||||
chooseCity(item){
|
||||
const pages = getCurrentPages();
|
||||
const prePage = (pages[pages.length - 2]).$vm;
|
||||
prePage.city = item.label;
|
||||
prePage.searchList();
|
||||
if (pages.length > 1) {
|
||||
uni.navigateBack();
|
||||
} else {
|
||||
uni.switchTab({
|
||||
url:'/pages/index/index'
|
||||
})
|
||||
}
|
||||
},
|
||||
//初始化列表
|
||||
initList() {
|
||||
const list = cityData;
|
||||
const tempData = {};
|
||||
list.forEach(item => {
|
||||
let key = getFirstLetter.getLetter(item.label).firstletter;
|
||||
if (!tempData[key]) {
|
||||
tempData[key] = [];
|
||||
}
|
||||
tempData[key].push(item);
|
||||
})
|
||||
//排序用
|
||||
const tempKeyList = [];
|
||||
for (let key in tempData) {
|
||||
tempKeyList.push(key);
|
||||
}
|
||||
tempKeyList.sort();
|
||||
const renderList = [];
|
||||
tempKeyList.forEach(keyword => {
|
||||
for (let key in tempData) {
|
||||
if (key == keyword) {
|
||||
renderList.push({
|
||||
key,
|
||||
list: tempData[key]
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
this.renderList = renderList;
|
||||
//生成右侧索引
|
||||
setTimeout(() => {
|
||||
this.calcAnchor();
|
||||
}, 500)
|
||||
},
|
||||
//计算锚点高度
|
||||
calcAnchor() {
|
||||
const list = this.renderList;
|
||||
let size = {};
|
||||
const placeFillHeight = this.systemInfo.navigationBarHeight + this.systemInfo.statusBarHeight;
|
||||
|
||||
list.forEach(async item => {
|
||||
let size = await this.getElSize('keyword' + item.key);
|
||||
item.top = size.top;
|
||||
_anchorList.push(size.top);
|
||||
})
|
||||
},
|
||||
//右侧索引点击
|
||||
onKeywordClick(index) {
|
||||
_onKeywordClicking = true;
|
||||
setTimeout(() => {
|
||||
_onKeywordClicking = false;
|
||||
}, 500)
|
||||
this.keywordCurrent = index;
|
||||
uni.pageScrollTo({
|
||||
scrollTop: this.renderList[index].top
|
||||
})
|
||||
},
|
||||
//获得元素的size
|
||||
getElSize(id) {
|
||||
return new Promise(res => {
|
||||
let el = uni.createSelectorQuery().select('#' + id);
|
||||
el.boundingClientRect(data => {
|
||||
res(data);
|
||||
}).exec();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
page {
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
</style>
|
||||
<style scoped lang="scss">
|
||||
.key-show {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
z-index: 95;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
background-color: #2979ff;
|
||||
border-radius: 100rpx;
|
||||
font-size: 40rpx;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
opacity: 0;
|
||||
|
||||
&.show {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.keyList {
|
||||
position: relative;
|
||||
|
||||
.keyword {
|
||||
position: sticky;
|
||||
left: 0;
|
||||
top: var(--window-top);
|
||||
z-index: 95;
|
||||
padding-top: 6rpx;
|
||||
padding-left: 30rpx;
|
||||
width: 100%;
|
||||
height: 66rpx;
|
||||
line-height: 60rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 700;
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
.item {
|
||||
padding-left: 30rpx;
|
||||
line-height: 80rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
background-color: #fff;
|
||||
|
||||
&:after {
|
||||
left: 30rpx;
|
||||
}
|
||||
|
||||
.cn-name {
|
||||
margin-left: 12rpx;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 右侧索引 */
|
||||
.key-list {
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
z-index: 96;
|
||||
padding-right: 20rpx;
|
||||
padding-left: 40rpx;
|
||||
transform: translateY(-50%);
|
||||
|
||||
.key-item {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
margin-bottom: 2rpx;
|
||||
text-align: center;
|
||||
line-height: 40rpx;
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
border-radius: 100rpx;
|
||||
}
|
||||
|
||||
.active {
|
||||
background-color: #2979ff;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
407
pages-biz/chooseAddress/index.vue
Normal file
407
pages-biz/chooseAddress/index.vue
Normal file
@@ -0,0 +1,407 @@
|
||||
<template>
|
||||
<view class='app'>
|
||||
<!-- 当前选择地址 -->
|
||||
<view class="current-address row b-b">
|
||||
<view class="left">
|
||||
<view class="row">
|
||||
<text class="red">[当前]</text>
|
||||
<text class="title">{{currentAddress.title}}</text>
|
||||
</view>
|
||||
<text class="addr clamp">{{currentAddress.address}}</text>
|
||||
</view>
|
||||
<!-- 搜索图标 -->
|
||||
<image
|
||||
class="ser-icon"
|
||||
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAENElEQVRoQ+2a/1XcMAzHlQkKExQmaJmAMgHtBJQJqkzQMsGpExQm6HWCHhMUJihMUJjAfd97znuucc6Sklxf4PwXjzixPtYPS/I19MJG88J4aQf83DW+0/BOw89sByY3aWbea5rmDREdhBAO0v1rmmYVQrgXkbtt7eskwMz8lojOiOgdEeHv2nggolXTNMvFYnFVmzzk+ajAbduehhA4gnrlArwQ0VcRwd+jjlGAo0YXA0FzsDW4iFyMSTwYmJk/E9GXDULdEtGSiOCnqa/uRXOHX78nolc937ghog9j+bkbGMGIiKDVjwVB7+MmLLVmGa0E7gDfL2n7REQAP2i4gCPsz0JAWoOKyKVXKmaGxmExJfDzId+GTF5gwCICp+MHtK3VaG1DmBlmjo1LTR1+PUjTZmBmRgT9lAl8ISKb/LjGV3wezRz+/zqZAOhD78aagOOuf8+kaxFKXUSKl6L7INilml6JyIni9SdT1MBx4V/ImJKvXIlIKWh5ZOl9J2p6lUG7/NkCDJPFEdSNWxHRZFGjwDMzIjhOhW64TFsFHLX7m4hwFHUDwQO7vrXBzFjvOFnQrGUtMMz2W7LQtYjkUXpycGbGmjghunEnIoeWhbXAiJSnyYePxkgCLIJ2c5kZyQeqr26YZKkCR3P+kyyAcu6fMs8juPedgi+bjkQNcG5GW4nMfRsSMzHEk26Y3EsDnEdnJPIw8f82MrN+EJF9rTAaYKR3aV679eicw+TRWkSqHN03qhMLR8G+N63TaqE2j5ndSjADW3azJrj3OTPnbqa2urkC5wUMiglVI1ADnJ/B6t30arD23tQ+7DafmuDe51MD52nlpOVgbRMKidDo5zCyqvSgvxGRo5pgUz0v1ORo56KSUo2qD+MrzIyAkHYd1EFCJYVh0pAjCctogXM/NuWvBp6NU2NaiSZEV6Y+ikhaslaX0gLnZo3iG1WK6iioSqGcUNCuOa9XAUezzrMb82JKruK0QtHwiHaTNeuzAOdahmDmjoMHuqcP7nIrNXDUcu7L+LepAHcCo1OKPnU3XNpVB61UyELHYXBzfNMmMDNaS3ln1L3JJg1HLcO00WbJbwSQkLivWHLoDdc5gxIfM3CERns27xPjEYAh0KB73bZtz0IIKBDyI8fctMs30gVcgXZfaMeuJHrfmzqilyJy7okFLh/O/BnmjWoq7SKmU5bxdxzId59cdcbffxyHEBCQAKltDrqh3RrOwEvRu08JAAeYJkO6jgGrdH3qgh4FOAlmffe6Vgt8cs9cyLLWMcNq3qMBd0QxI0L1AjNNCw4NNO6YcRlZvMIZA3p04IKPd75ZCkTd7z5w/am6pxoKPSmwRqWeOUOgZwkcY0ZezODf1WbAbIH7oGtt5FkDF6Cr/a3ZA0fodWDU5PLPAtgS+HbAlt2a49ydhueoNYvMOw1bdmuOc/8C4xLETHORTG0AAAAASUVORK5CYII="
|
||||
@click="navTo('search?city='+curCity)"
|
||||
></image>
|
||||
</view>
|
||||
<!-- 补充详细地址 -->
|
||||
<view class="confirm-wrap row">
|
||||
<!-- <input class="input" placeholder="补充详细地址:楼号、门牌等(选填)" placeholder-class="placeholder" v-model="room" @confirm='submit'></input> -->
|
||||
<view class="btn" @click="submit">确定</view>
|
||||
</view>
|
||||
<map
|
||||
id="map"
|
||||
class="map"
|
||||
:scale="15"
|
||||
:show-location="true"
|
||||
:longitude="map.longitude"
|
||||
:latitude="map.latitude"
|
||||
@regionchange="onRegionchange"
|
||||
>
|
||||
<cover-image class="map-center-icon" src="https://7478-tx-cloud-mix-mall-d6944c-1302673523.tcb.qcloud.la/5bfbd58b18200d8e16f6b17a02e6ed9.png"></cover-image>
|
||||
</map>
|
||||
<!-- 结果集 -->
|
||||
<scroll-view scroll-y class="addr-list-scroll">
|
||||
<view class="addr-list">
|
||||
<view class="addr-item b-b row" v-for="(item, index) in list" :key="index" @click="chooseAddress(index)">
|
||||
<view class="left">
|
||||
<text class="title">{{item.title}}</text>
|
||||
<text class="addr">{{item.address}}</text>
|
||||
</view>
|
||||
<icon type="success" color="#2979ff" size="18" class="icon_circle" v-if='checked === index' />
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const QQMapWX = require('./js/qqmap-wx-jssdk.min.js')
|
||||
const qqmapsdk = new QQMapWX({
|
||||
key: 'FALBZ-J2G3I-ZY5GX-5ATUZ-GHOOZ-YVFAR'
|
||||
})
|
||||
let _mapCtx = null;
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
curCity: '',
|
||||
mapStatus: 1,
|
||||
map: {
|
||||
longitude: 116.39742,
|
||||
latitude: 39.909,
|
||||
},
|
||||
room: '', //补充地址 门牌号、房间号
|
||||
list: [], //地址列表
|
||||
checked: 0, //当前选择选择地址下标
|
||||
tempAddress: null, //编辑或者搜索到地址时,需要手动将结果添加到poi集
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
currentAddress(){
|
||||
if(this.list.length === 0){
|
||||
return {};
|
||||
}
|
||||
return this.list[this.checked];
|
||||
}
|
||||
},
|
||||
created() {
|
||||
_mapCtx = uni.createMapContext('map');
|
||||
},
|
||||
onNavigationBarButtonTap() {
|
||||
this.submit();
|
||||
},
|
||||
async onLoad(options) {
|
||||
//编辑地址时参数
|
||||
let optionData = options.data;
|
||||
let lng, lat;
|
||||
if(optionData){
|
||||
//编辑地址时
|
||||
this.tempAddress = JSON.parse(optionData);
|
||||
this.room = this.tempAddress.room;
|
||||
lng = this.tempAddress.location.lng;
|
||||
lat = this.tempAddress.location.lat;
|
||||
}else{
|
||||
//没有传坐标时获取用户当前定位
|
||||
const userLocation = await this.getLocation();
|
||||
lng = +userLocation.longitude;
|
||||
lat = +userLocation.latitude;
|
||||
}
|
||||
this.map = {
|
||||
longitude: lng,
|
||||
latitude: lat
|
||||
}
|
||||
this.position = {
|
||||
longitude: lng,
|
||||
latitude: lat
|
||||
}
|
||||
this.getAddressList(1)
|
||||
},
|
||||
methods: {
|
||||
//确定选择
|
||||
submit() {
|
||||
const {currentAddress, room} = this;
|
||||
const {ad_info, address, location, title} = currentAddress;
|
||||
// this.$util.prePage().setAddress(Object.assign({}, {
|
||||
// ad_info, address, location, title,room
|
||||
// }));
|
||||
uni.$emit('changeAddressConfig', address,location.lng,location.lat);
|
||||
const pages = getCurrentPages();
|
||||
if (pages.length > 1) {
|
||||
uni.navigateBack();
|
||||
} else {
|
||||
uni.switchTab({
|
||||
url:'/pages/index/index'
|
||||
})
|
||||
}
|
||||
},
|
||||
//获取poi列表
|
||||
getAddressList(s = 0) {
|
||||
//在ios下防止搜索返回时多次加载地址列表的问题
|
||||
if(this.isSetTempAddress === 1){
|
||||
return;
|
||||
}
|
||||
qqmapsdk.reverseGeocoder({
|
||||
location: {
|
||||
latitude: this.position.latitude,
|
||||
longitude: this.position.longitude
|
||||
},
|
||||
get_poi: 1,
|
||||
poi_options: "page_size=30;page_index=1",
|
||||
success: res=> {
|
||||
res.result.pois.forEach(poi=>{
|
||||
if(!poi.ad_info){
|
||||
poi.ad_info = {
|
||||
adcode: poi.adcode,
|
||||
city: poi.city,
|
||||
district: poi.district,
|
||||
province: poi.province
|
||||
}
|
||||
}
|
||||
})
|
||||
//有搜索结果时,手动追加到列表顶部
|
||||
if(this.tempAddress){
|
||||
if(this.tempAddress.title != res.result.pois[0].title){
|
||||
if(!this.tempAddress.ad_info){
|
||||
this.tempAddress.ad_info = {
|
||||
adcode: this.tempAddress.adcode,
|
||||
city: this.tempAddress.city,
|
||||
district: this.tempAddress.district,
|
||||
province: this.tempAddress.province
|
||||
}
|
||||
}
|
||||
res.result.pois.unshift(this.tempAddress);
|
||||
}
|
||||
this.tempAddress = null;
|
||||
this.isSetTempAddress = 1;
|
||||
setTimeout(()=>{
|
||||
this.isSetTempAddress = 0;
|
||||
}, 500)
|
||||
}
|
||||
if (s) {
|
||||
const ad_info = res.result.pois[0].ad_info;
|
||||
this.curCity = ad_info.city || '';
|
||||
res.result.pois[0].select = 1
|
||||
this.list = res.result.pois;
|
||||
this.checked = 0;
|
||||
} else {
|
||||
this.list = res.result.pois;
|
||||
}
|
||||
},
|
||||
fail: err=> {
|
||||
console.log(err)
|
||||
}
|
||||
})
|
||||
},
|
||||
//地图区域改变
|
||||
onRegionchange(e) {
|
||||
clearTimeout(this.timer)
|
||||
this.timer = setTimeout(() => {
|
||||
//h5 end 安卓 regionchange
|
||||
if (e.type === 'end' || e.type === 'regionchange') {
|
||||
_mapCtx.getCenterLocation({
|
||||
success: res => {
|
||||
this.position = {
|
||||
latitude: res.latitude,
|
||||
longitude: res.longitude
|
||||
}
|
||||
if (this.mapStatus == 1) { // 防止地图点击时 进行多次加载
|
||||
this.getAddressList(1)
|
||||
}
|
||||
},
|
||||
fail: err=>{
|
||||
console.log(err);
|
||||
}
|
||||
})
|
||||
}
|
||||
}, 200)
|
||||
},
|
||||
//地址列表点击
|
||||
chooseAddress(index) {
|
||||
let list = this.list
|
||||
this.map = {
|
||||
longitude: list[index].location.lng,
|
||||
latitude: list[index].location.lat
|
||||
}
|
||||
this.checked = index;
|
||||
this.mapStatus = 0;
|
||||
//防止ios下触发多次加载列表的bug
|
||||
clearTimeout(this.mapStatusTimer);
|
||||
this.mapStatusTimer = setTimeout(()=>{
|
||||
this.mapStatus = 1;
|
||||
}, 1000)
|
||||
},
|
||||
//获取用户定位
|
||||
getLocation (){
|
||||
return new Promise(resolve=> {
|
||||
// #ifndef H5
|
||||
uni.getLocation({
|
||||
type: 'gcj02',
|
||||
success: res=> {
|
||||
resolve({
|
||||
longitude: res.longitude,
|
||||
latitude: res.latitude
|
||||
})
|
||||
},
|
||||
err: err=> {
|
||||
console.log(err)
|
||||
resolve({
|
||||
longitude: 116.39742,
|
||||
latitude: 39.909,
|
||||
})
|
||||
},
|
||||
complete(res) {
|
||||
console.log(res);
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
//h5的定位没有写,这里直接默认天安门,可以根据项目需求使用对应jssdk获取定位
|
||||
resolve({
|
||||
longitude: 116.39742,
|
||||
latitude: 39.909,
|
||||
})
|
||||
// #endif
|
||||
})
|
||||
},
|
||||
navTo(url){
|
||||
uni.navigateTo({
|
||||
url
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
page {
|
||||
height: 100%;
|
||||
background: #F6F6F6;
|
||||
}
|
||||
</style>
|
||||
<style scoped lang="scss">
|
||||
.app{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
.row{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.b-b{
|
||||
position: relative;
|
||||
|
||||
&:after{
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
left: 0;
|
||||
top: auto;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
height: 0;
|
||||
content: '';
|
||||
transform: scaleY(.5);
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
}
|
||||
}
|
||||
.clamp {
|
||||
/* #ifdef APP-PLUS-NVUE */
|
||||
lines: 1;
|
||||
/* #endif */
|
||||
/* #ifndef APP-PLUS-NVUE */
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
display: block;
|
||||
/* #endif */
|
||||
}
|
||||
.current-address{
|
||||
height: 120rpx;
|
||||
padding: 0 24rpx;
|
||||
background-color: #fff;
|
||||
|
||||
.left{
|
||||
width: 634rpx;
|
||||
}
|
||||
.red{
|
||||
flex-shrink: 0;
|
||||
margin-right: 6rpx;
|
||||
font-size: 28rpx;
|
||||
color: #2979ff;
|
||||
line-height: 36rpx;
|
||||
}
|
||||
.title{
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
line-height: 36rpx;
|
||||
}
|
||||
.addr{
|
||||
width: 700rpx;
|
||||
margin-top: 12rpx;
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.ser-icon{
|
||||
flex-shrink: 0;
|
||||
width: 66rpx;
|
||||
height: 66rpx;
|
||||
padding: 12rpx 4rpx 12rpx 20rpx;
|
||||
}
|
||||
}
|
||||
.confirm-wrap{
|
||||
height: 88rpx;
|
||||
padding: 0 24rpx;
|
||||
background-color: #fff;
|
||||
|
||||
.input{
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
color: #303133;
|
||||
}
|
||||
.btn{
|
||||
width: 700rpx;
|
||||
padding: 0 25rpx;
|
||||
font-size: 26rpx;
|
||||
color: #fff;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
background-color: #2979ff;
|
||||
border-radius: 100rpx;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
.map{
|
||||
width: 750rpx;
|
||||
height: 700rpx;
|
||||
|
||||
.map-center-icon{
|
||||
position: absolute;
|
||||
left: 339rpx;
|
||||
top: 264rpx;
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
}
|
||||
}
|
||||
.addr-list-scroll{
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
|
||||
.addr-list{
|
||||
background-color: #fff;
|
||||
}
|
||||
.addr-item{
|
||||
padding: 24rpx;
|
||||
}
|
||||
.left{
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-right: 50rpx;
|
||||
}
|
||||
.title{
|
||||
font-size: 28rpx;
|
||||
color: #303133;
|
||||
}
|
||||
.addr{
|
||||
margin-top: 10rpx;
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
1
pages-biz/chooseAddress/js/city.js
Normal file
1
pages-biz/chooseAddress/js/city.js
Normal file
File diff suppressed because one or more lines are too long
35
pages-biz/chooseAddress/js/getFirstLetter.js
Normal file
35
pages-biz/chooseAddress/js/getFirstLetter.js
Normal file
File diff suppressed because one or more lines are too long
26
pages-biz/chooseAddress/js/qqmap-wx-jssdk.min.js
vendored
Normal file
26
pages-biz/chooseAddress/js/qqmap-wx-jssdk.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
179
pages-biz/chooseAddress/search.vue
Normal file
179
pages-biz/chooseAddress/search.vue
Normal file
@@ -0,0 +1,179 @@
|
||||
<template>
|
||||
<view class='app'>
|
||||
<view class="search-wrap">
|
||||
<view class="city" @click="navTo('cityList?city='+city)">
|
||||
<image class="icon" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAC70lEQVRIS8VW7VXcMBDctQsAKshRQYRcAJcKIBUAFQAVhFSQowKggpAKuBTgRVcBRwWQAiTljZ/Ek235436hf3e2NTuzO7vL9EmHPwmXZgMrpRZlWZ4Q0dJ7v2TmfQTtvX9n5jURra21D8aY9zlkJoGVUvtFUVwS0VUEG7oYQRDRyjl3OxXAKLBSSpVleUdEKrDbENG9c84Q0TYEsCiKAs/Pmflr+M9Yay+MMXgvewaBAVoUxVNg+c9ae2qMgaSDp6qqUwRGRHtg75w7MsbEAFvfZYEhb1mWT2Dqvd8455ZT0sVbQ2rWgT2Yf8t9mwXWWt8w8w8iAtNF90NcHmVFYLnnZVmCKZj/FJGbrkw94BDxCyT23l+ICKRrTnj2i5nP04u898j7dRqA1ho5vwuSH3aD6wEnH2xEpCmqCBrlhxKwT3i0BDMi6smqtTZQpksA3/WAq6p6JKIT7/2tiFxFYK31ipkvvfd/nXMotMavQYVHZj4e+oaI/tR1jcL7ODnGKIzjUBQfVVxVFYD2rLUHAzl9g6wicpCotIRKCFZEoMwo8JaZv6TAoWu95C5IFIkBI5+NhZRSEbiVtqzUWuse42CvN+SxruujnJFjPlNFEuBZjBvgrg201j0lMpK+isgiUaGx5Vypo4cf6rr+sE3HHt9jFwOroih+5+xXVRWseJbzcs7HMS+tQgGLeFFOaiJqBRrejwWJ7tVqt0Odq5E15z8wD5OqGQjoXJhIaaPB/4lCLflj0KMt03u/FZHDAYajf8eamN0yky4Ve+21iKx2AZ/q9Vk7JRXZ9Fr8ttZivA3O1jSoMMOfQxpavX60gaQPk/a5DbN1dK0J7fOZmWGpXpucDTx3tibpmT3D5+xcWH9ghTiBeitNZ0XCDFdDm8doVXcLKVzcgIf5OtRAXsPkmqyHScZJW8S2GVca+LepdGZuRueuK9Js4CSPADxLVcEcds7dzN3LRu005ltskynjuq6xPOx0dmK8080TL38a8H+fVUE94KTW6AAAAABJRU5ErkJggg=="></image>
|
||||
<text>{{ city }}</text>
|
||||
<image class="c-icon" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAvElEQVQ4T+3TwQ3CMAwF0G9lAQbgwgRlhEgegBEQm3UEFrCUEWACLgzAAlaQpQa1EJxK9MChvaVKXp3vmrDwQwt7WMHfE31lGGPchBBuAM4icppDM3MP4KCqu5TSw85MmsLMFyLqcs59CzWMiI4556uI7EsBE3CoMrXQMaaqsVT3UaG9aKEeVgU9tIV9BWuo5V0ye7/muIHupJTrA+iICNYAD3MrLF8d0LutVXU7bkDt11pnec7A+Xv+P8Mn98h9FeV7S88AAAAASUVORK5CYII="></image>
|
||||
</view>
|
||||
<input class="input" maxlength="30" @input="bindConfirm" focus auto-focus placeholder="请输入地址关键字搜索" @confirm='bindConfirm'></input>
|
||||
</view>
|
||||
|
||||
<view class="addr-list">
|
||||
<view class="addr-item b-b" v-for="(item, index) in list" :key="index" @click="confirm(item)">
|
||||
<text class="title">{{item.title}}</text>
|
||||
<text class="addr">{{item.address}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const QQMapWX = require('./js/qqmap-wx-jssdk.min.js')
|
||||
var qqmapsdk = new QQMapWX({
|
||||
key: 'FALBZ-J2G3I-ZY5GX-5ATUZ-GHOOZ-YVFAR'
|
||||
})
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
city: '',
|
||||
keyword: '',
|
||||
list: []
|
||||
}
|
||||
},
|
||||
onLoad(options) {
|
||||
uni.setNavigationBarTitle({
|
||||
title: '搜索地址'
|
||||
})
|
||||
this.city = options.city || '';
|
||||
},
|
||||
methods: {
|
||||
//选择地址
|
||||
confirm(item) {
|
||||
const pages = getCurrentPages()
|
||||
const prePages = pages[pages.length - 2].$vm
|
||||
|
||||
prePages.tempAddress = item;
|
||||
prePages.position = {
|
||||
longitude: item.location.lng,
|
||||
latitude: item.location.lat,
|
||||
}
|
||||
prePages.map = {
|
||||
longitude: item.location.lng,
|
||||
latitude: item.location.lat,
|
||||
}
|
||||
// #ifdef H5 || MP-WEIXIN
|
||||
prePages.getAddressList(1); //h5没触发地图regionchange事件,需要手动调用获取新地址列表。微信小程序开发工具自动触发,真机不触发,同样需要调用一下。
|
||||
// #endif
|
||||
uni.navigateBack({
|
||||
delta: 1
|
||||
})
|
||||
},
|
||||
//搜索地址
|
||||
searchList() {
|
||||
qqmapsdk.getSuggestion({
|
||||
keyword: this.keyword,
|
||||
policy: 1, //默认0,常规策略 policy=1:本策略主要用于收货地址、上门服务地址的填写,
|
||||
page_size: 20, //每页条目数,最大限制为20条,默认值10
|
||||
page_index: 1,
|
||||
region: this.city || '全国',
|
||||
success: res=> {
|
||||
this.list = res.data;
|
||||
},
|
||||
fail: err => {
|
||||
this.list = [];
|
||||
}
|
||||
})
|
||||
},
|
||||
bindConfirm(e) {
|
||||
this.keyword = e.detail.value;
|
||||
this.searchList()
|
||||
},
|
||||
navTo(url){
|
||||
uni.navigateTo({
|
||||
url
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
view{
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.app{
|
||||
padding-top: 100rpx;
|
||||
}
|
||||
.search-wrap{
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: var(--window-top);
|
||||
z-index: 90;
|
||||
display: flex;
|
||||
background-color: #fff;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100rpx;
|
||||
padding: 0 30rpx;
|
||||
background-color: #fff;
|
||||
|
||||
.city{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
}
|
||||
.icon{
|
||||
width: 34rpx;
|
||||
height: 34rpx;
|
||||
margin-right: 4rpx;
|
||||
}
|
||||
.c-icon{
|
||||
width: 22rpx;
|
||||
height: 22rpx;
|
||||
margin-left: 4rpx;
|
||||
}
|
||||
.input{
|
||||
flex: 1;
|
||||
margin-left: 16rpx;
|
||||
padding: 0 28rpx;
|
||||
height: 70rpx;
|
||||
line-height: 70rpx;
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 100rpx;
|
||||
}
|
||||
}
|
||||
.addr-list{
|
||||
background-color: #fff;
|
||||
|
||||
.addr-item{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 24rpx 30rpx;
|
||||
position: relative;
|
||||
|
||||
&:after{
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
left: 0;
|
||||
top: auto;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
height: 0;
|
||||
content: '';
|
||||
transform: scaleY(.5);
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
}
|
||||
}
|
||||
.left{
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-right: 50rpx;
|
||||
}
|
||||
.title{
|
||||
font-size: 28rpx;
|
||||
color: #303133;
|
||||
}
|
||||
.addr{
|
||||
margin-top: 10rpx;
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
305
pages-biz/contract/contract.vue
Normal file
305
pages-biz/contract/contract.vue
Normal file
@@ -0,0 +1,305 @@
|
||||
<template>
|
||||
|
||||
<view class="contract-page">
|
||||
<!-- 顶部导航栏 -->
|
||||
<customNavbar title="我的合同" :showHome = "true"/>
|
||||
|
||||
<!-- Tabs -->
|
||||
<view class="tab-wrapper">
|
||||
<u-tabs :list="tabList" :current="currentTab" @change="onTabChange" lineColor="#EA4D3E" activeColor="#EA4D3E"
|
||||
itemStyle="padding: 0 30rpx;"></u-tabs>
|
||||
</view>
|
||||
|
||||
<DateFilter :start="startDate" :end="endDate" @update:start="startDate = $event" @update:end="endDate = $event"
|
||||
@change="onDateFilterChange" />
|
||||
|
||||
<!-- 合同列表 -->
|
||||
<scroll-view scroll-y class="scroll-content" @scrolltolower="loadMore" :refresher-enabled="true"
|
||||
:refresher-triggered="isRefreshing" @refresherrefresh="refresh">
|
||||
<view v-if="contracts.length > 0" class="contract-list">
|
||||
<view class="contract-card" v-for="item in contracts" :key="item.contractNo" @click="goDetail(item)">
|
||||
<image class="contract-cover" :src="staticHost + (item.coverImgUrl || defaultCover)" mode="aspectFill" />
|
||||
|
||||
<view class="contract-info">
|
||||
<view class="top-row">
|
||||
<text class="asset-name">{{ $ellipsis(item.assetName,12) }}</text>
|
||||
<u-icon name="arrow-right" size="28" color="#ccc" />
|
||||
</view>
|
||||
<!-- <view class="asset-room">{{ item.assetRoom || "—" }}</view> -->
|
||||
<view class="date-range">{{ item.startDate }} ~ {{ item.endDate }}</view>
|
||||
|
||||
<view :class="['status-tag', item.signStatus]">
|
||||
{{ getStatusText(item.signStatus) }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<u-loadmore :status="loadStatus" />
|
||||
</view>
|
||||
|
||||
<view v-else class="empty">
|
||||
<u-empty mode="list" text="暂无合同记录" />
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
|
||||
</view>
|
||||
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import DateFilter from '../../components/DatePicker/DateFilter.vue';
|
||||
export default {
|
||||
components: { DateFilter },
|
||||
data() {
|
||||
return {
|
||||
tabList: [{
|
||||
name: "全部",
|
||||
value: "all"
|
||||
},
|
||||
{
|
||||
name: "待签署",
|
||||
value: "pending"
|
||||
},
|
||||
{
|
||||
name: "已签署",
|
||||
value: "signed"
|
||||
},
|
||||
{
|
||||
name: "已过期",
|
||||
value: "expired"
|
||||
},
|
||||
],
|
||||
|
||||
currentTab: 0,
|
||||
statusFilter: "all",
|
||||
// --- 时间筛选 ---
|
||||
startDate: "",
|
||||
endDate: "",
|
||||
// 可选:限制日期范围
|
||||
minDate: "2000-01-01",
|
||||
maxDate: "2099-12-31",
|
||||
contracts: [],
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
loadStatus: "loadmore",
|
||||
isRefreshing: false,
|
||||
defaultCover: "/public/static/assets.jpg",
|
||||
};
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
this.loadContracts();
|
||||
},
|
||||
onShow() {
|
||||
this.$checkToken(this.$getToken())
|
||||
},
|
||||
computed: {
|
||||
staticHost() {
|
||||
return this.$config.staticUrl
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onTabChange(index) {
|
||||
this.currentTab = index;
|
||||
this.statusFilter = this.tabList[index].value;
|
||||
this.resetAndLoad();
|
||||
},
|
||||
onDateFilterChange({ start, end }) {
|
||||
this.resetAndLoad();
|
||||
},
|
||||
|
||||
formatDate(time) {
|
||||
const d = new Date(time);
|
||||
const m = String(d.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(d.getDate()).padStart(2, "0");
|
||||
return `${d.getFullYear()}-${m}-${day}`;
|
||||
},
|
||||
|
||||
resetAndLoad() {
|
||||
this.pageNo = 1;
|
||||
this.contracts = [];
|
||||
this.loadStatus = "loadmore";
|
||||
this.loadContracts();
|
||||
},
|
||||
|
||||
/** 你的 loadContracts() 保持不变,只需加:按时间筛选 */
|
||||
loadContracts() {
|
||||
if (this.loadStatus !== 'loadmore') return;
|
||||
|
||||
this.loadStatus = 'loading';
|
||||
|
||||
this.$u.post('/contract/queryPage', {
|
||||
pageNo: this.pageNo,
|
||||
pageSize: this.pageSize,
|
||||
startTime: this.startDate,
|
||||
endTime: this.endDate,
|
||||
signStatus: this.statusFilter
|
||||
}, {
|
||||
WT: this.$getToken()
|
||||
}).then(result => {
|
||||
|
||||
const rows = result.data.result || [];
|
||||
|
||||
// 第一页 & 无数据
|
||||
if (this.pageNo === 1 && rows.length === 0) {
|
||||
this.contracts = [];
|
||||
this.loadStatus = 'nomore';
|
||||
return;
|
||||
}
|
||||
|
||||
// 追加数据
|
||||
this.contracts = this.contracts.concat(rows);
|
||||
|
||||
// 是否还有下一页(核心判断)
|
||||
if (rows.length < this.pageSize) {
|
||||
this.loadStatus = 'nomore';
|
||||
} else {
|
||||
this.pageNo++; // ✅ 只有这里才能 +1
|
||||
this.loadStatus = 'loadmore';
|
||||
}
|
||||
|
||||
}).catch(err => {
|
||||
console.log("获取合同信息失败:", err);
|
||||
this.loadStatus = 'loadmore';
|
||||
}).finally(() => {
|
||||
this.isRefreshing = false;
|
||||
});
|
||||
},
|
||||
|
||||
refresh() {
|
||||
this.isRefreshing = true;
|
||||
this.resetAndLoad();
|
||||
},
|
||||
|
||||
loadMore() {
|
||||
if (this.loadStatus !== "loadmore") return;
|
||||
this.loadContracts();
|
||||
},
|
||||
|
||||
goDetail(item) {
|
||||
uni.navigateTo({
|
||||
url: `/pages-biz/contract/contractDetail?contractNo=${item.contractNo}`,
|
||||
});
|
||||
},
|
||||
|
||||
getStatusText(status) {
|
||||
switch (status) {
|
||||
case "pending":
|
||||
return "待签署";
|
||||
case "signed":
|
||||
return "已签署";
|
||||
case "expired":
|
||||
return "已过期";
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.contract-page {
|
||||
background-color: #f8f8f8;
|
||||
padding-top: 170rpx;
|
||||
/* 给导航栏留空间 */
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.scroll-content {
|
||||
height: calc(100vh - 100rpx);
|
||||
}
|
||||
|
||||
.contract-list {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.contract-card {
|
||||
display: flex;
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
margin-bottom: 24rpx;
|
||||
padding: 20rpx;
|
||||
box-shadow: 0 4rpx 8rpx rgba(0, 0, 0, 0.05);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.contract-cover {
|
||||
width: 193rpx;
|
||||
height: 145rpx;
|
||||
border-radius: 10rpx;
|
||||
background-color: #FCE5E0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.contract-info {
|
||||
flex: 1;
|
||||
margin-left: 20rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.top-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.asset-name {
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.asset-room {
|
||||
font-size: 26rpx;
|
||||
color: #555;
|
||||
margin-top: 18rpx;
|
||||
}
|
||||
|
||||
.date-range {
|
||||
font-size: 24rpx;
|
||||
color: #888;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.status-tag {
|
||||
position: absolute;
|
||||
bottom: 20rpx;
|
||||
right: 0rpx;
|
||||
padding: 12rpx 15rpx;
|
||||
border-radius: 6rpx;
|
||||
font-size: 26rpx;
|
||||
|
||||
&.pending {
|
||||
background: #FEEDDD;
|
||||
color: #EFA049;
|
||||
}
|
||||
|
||||
&.signed {
|
||||
background: #FCE5E0;
|
||||
color: #ED7748;
|
||||
}
|
||||
|
||||
&.expired {
|
||||
background: #CDCDCD;
|
||||
color: #969696;
|
||||
}
|
||||
}
|
||||
|
||||
.empty {
|
||||
margin-top: 200rpx;
|
||||
}
|
||||
|
||||
.tab-wrapper {
|
||||
background-color: #ffffff;
|
||||
padding: 10rpx 0;
|
||||
box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.06);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
</style>
|
||||
341
pages-biz/contract/contractDetail.vue
Normal file
341
pages-biz/contract/contractDetail.vue
Normal file
@@ -0,0 +1,341 @@
|
||||
<template>
|
||||
<view class="contract-detail-page">
|
||||
<customNavbar title="我的合同"></customNavbar>
|
||||
|
||||
<!-- 合同基本信息 -->
|
||||
<view class="section">
|
||||
<view class="section-title">合同基本信息</view>
|
||||
<view
|
||||
class="info-item"
|
||||
v-for="(item, index) in baseInfo"
|
||||
:key="index"
|
||||
@click="item.hasArrow && handleClick(item)"
|
||||
>
|
||||
<text class="label">{{ item.label }}</text>
|
||||
<view class="value-box">
|
||||
<text class="value">{{ item.value }}</text>
|
||||
<u-icon v-if="item.hasArrow" name="arrow-right" size="30" color="#ccc" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
<!-- 该部分仍保留 -->
|
||||
<!-- <view
|
||||
v-for="(asset, index) in assetList"
|
||||
:key="index"
|
||||
class="asset-block"
|
||||
>
|
||||
<view class="asset-header" @click="toggleAsset(index)">
|
||||
<view class="left">
|
||||
<image
|
||||
class="cover"
|
||||
:src="asset.coverUrl || '/static/default-cover.png'"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<view class="info">
|
||||
<text class="name">{{ asset.assetName }}</text>
|
||||
<text class="room">{{ asset.assetRoom || '—' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<u-icon
|
||||
:name="asset.expanded ? 'arrow-up' : 'arrow-down'"
|
||||
size="28"
|
||||
color="#999"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view v-show="asset.expanded">
|
||||
<view class="section">
|
||||
<view class="section-title">费用信息</view>
|
||||
<view
|
||||
class="info-item"
|
||||
v-for="(item, i) in feeItems(asset)"
|
||||
:key="i"
|
||||
>
|
||||
<text class="label">{{ item.label }}</text>
|
||||
<text class="value">{{ item.value }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view> -->
|
||||
|
||||
<!-- <view
|
||||
v-if="hasMoreAssets"
|
||||
class="load-more"
|
||||
@click="loadMoreAssets"
|
||||
> -->
|
||||
<!-- <text v-if="!loadingAsset">加载更多资产</text>
|
||||
<text v-else>加载中...</text>
|
||||
</view> -->
|
||||
|
||||
<!-- 底部操作栏(完全保留原逻辑和样式) -->
|
||||
<view v-if="contract.signStatus === '待签署'" class="bottom-bar">
|
||||
<u-button class="sign-btn" @click="goSign">
|
||||
去签署
|
||||
</u-button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
contractNo: null,
|
||||
contract: {},
|
||||
assetList: [],
|
||||
assetPageNo: 1,
|
||||
assetPageSize: 1,
|
||||
hasMoreAssets: true,
|
||||
loadingAsset: false
|
||||
}
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.$checkToken(this.$getToken())
|
||||
this.getContractDetail()
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
this.contractNo = options.contractNo
|
||||
},
|
||||
|
||||
onReachBottom() {
|
||||
this.loadMoreAssets()
|
||||
},
|
||||
|
||||
computed: {
|
||||
baseInfo() {
|
||||
return [
|
||||
{ label: '合同名称', value: this.contract.contractName || '—' },
|
||||
{ label: '签约状态', value: this.contract.signStatus || '—' },
|
||||
{
|
||||
label: '合同生效期',
|
||||
value: this.contract.startDate
|
||||
? `${this.contract.startDate} 至 ${this.contract.endDate}`
|
||||
: '—'
|
||||
},
|
||||
{
|
||||
label: '电子合同',
|
||||
value: '查看',
|
||||
hasArrow: true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
/** 合同详情 + 第一条资产 */
|
||||
getContractDetail() {
|
||||
this.$u.get(`/contract/detail?contractNo=${this.contractNo}`, {},{
|
||||
WT: this.$getToken()
|
||||
}).then(res => {
|
||||
console.log(res)
|
||||
this.contract = res.data || {}
|
||||
|
||||
// this.assetList = (res.assetsInfos.assets || []).map((item, index) => ({
|
||||
// ...item,
|
||||
// expanded: index === 0
|
||||
// }))
|
||||
|
||||
// this.hasMoreAssets =
|
||||
// this.assetList.length < (res.totalAssets || 0)
|
||||
})
|
||||
},
|
||||
|
||||
/** 加载更多资产 */
|
||||
loadMoreAssets() {
|
||||
if (this.loadingAsset || !this.hasMoreAssets) return
|
||||
|
||||
this.loadingAsset = true
|
||||
this.assetPageNo += 1
|
||||
|
||||
this.$u.get('/contract/assets', {
|
||||
contractNo: this.contractNo,
|
||||
pageNo: this.assetPageNo,
|
||||
pageSize: this.assetPageSize,
|
||||
WT: this.$getToken()
|
||||
}).then(res => {
|
||||
const list = (res.assets || []).map(item => ({
|
||||
...item,
|
||||
expanded: false
|
||||
}))
|
||||
|
||||
this.assetList = this.assetList.concat(list)
|
||||
this.hasMoreAssets = list.length === this.assetPageSize
|
||||
}).finally(() => {
|
||||
this.loadingAsset = false
|
||||
})
|
||||
},
|
||||
|
||||
/** 折叠 / 展开(手风琴效果) */
|
||||
toggleAsset(index) {
|
||||
this.assetList.forEach((item, i) => {
|
||||
item.expanded = i === index ? !item.expanded : false
|
||||
})
|
||||
},
|
||||
|
||||
feeItems(asset) {
|
||||
return [
|
||||
{ label: '押金/保证金', value: asset.fee?.deposit || '—' },
|
||||
{ label: '租金', value: asset.fee?.rent || '—' },
|
||||
{ label: '电费', value: asset.fee?.electricity || '—' },
|
||||
{ label: '水费', value: asset.fee?.water || '—' },
|
||||
{ label: '物业/增值服务', value: asset.fee?.property || '—' }
|
||||
]
|
||||
},
|
||||
|
||||
handleClick(item) {
|
||||
if (item.label === '电子合同') {
|
||||
uni.downloadFile({
|
||||
url: this.$config.staticUrl + this.contract.eContractUrl,
|
||||
header: {
|
||||
'X-Token': this.$getToken(),
|
||||
'USERTYPE': this.$getUserType()
|
||||
},
|
||||
success: res => {
|
||||
uni.openDocument({
|
||||
filePath: res.tempFilePath,
|
||||
fileType: 'pdf'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
goSign() {
|
||||
let url = `/contract/getSignLink?eFlowId=${this.contract.eContractFlowId}`
|
||||
let signLink
|
||||
this.$u.get(url,{},{
|
||||
WT: this.$getToken()
|
||||
}).then(res=>{
|
||||
if(res.flag) {
|
||||
signLink = res.data
|
||||
// 开发者微信小程序内通过WebView打开e签宝的认证授权/签署页面
|
||||
// 场景二:当前小程序内打开H5页面操作认证授权/签署
|
||||
wx.navigateTo({
|
||||
url: '/pages-biz/webview/webview?url=' + encodeURIComponent(signLink),
|
||||
})
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.contract-detail-page {
|
||||
background: #f7f8fa;
|
||||
min-height: 100vh;
|
||||
padding-top: 175rpx;
|
||||
padding-bottom: 150rpx;
|
||||
}
|
||||
|
||||
.asset-block {
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
margin: 20rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.asset-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
|
||||
.left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.cover {
|
||||
width: 120rpx;
|
||||
height: 96rpx;
|
||||
border-radius: 8rpx;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.name {
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.room {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.section {
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
margin: 20rpx;
|
||||
padding: 20rpx 24rpx;
|
||||
|
||||
.section-title {
|
||||
font-weight: 500;
|
||||
font-size: 40rpx;
|
||||
margin-bottom: 12rpx;
|
||||
color: #212121;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 40rpx 0;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 30rpx;
|
||||
color: #86868c;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 30rpx;
|
||||
color: #222;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.load-more {
|
||||
text-align: center;
|
||||
padding: 30rpx;
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 底部按钮样式保持不变 */
|
||||
.bottom-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: #fff;
|
||||
padding: 35rpx;
|
||||
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.06);
|
||||
z-index: 99;
|
||||
|
||||
.sign-btn {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
}
|
||||
|
||||
&::v-deep .u-btn {
|
||||
font-size: 30rpx;
|
||||
color: #fff;
|
||||
background: linear-gradient(90deg, #ff6f63 0%, #fb392a 100%);
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
134
pages-biz/face/faceAuth.vue
Normal file
134
pages-biz/face/faceAuth.vue
Normal file
@@ -0,0 +1,134 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<view class="loading-content">
|
||||
<view class="image-content">
|
||||
<image :src="staticHost + '/public' + '/static/loading.svg'" class="image"></image>
|
||||
</view>
|
||||
<view class="loading-tect-content">
|
||||
<text class="loading-text">加载中</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="btn-content">
|
||||
<text>如未成功跳转,</text>
|
||||
<text class="btn-click" bindtap="goFaceAuth">点击此处</text>
|
||||
<text>手动跳转</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
bizToken: '',
|
||||
redirectUrl: '',
|
||||
goFaceDone: false // 是否已跳转至公证签做人脸
|
||||
}
|
||||
},
|
||||
|
||||
onLoad(e) {
|
||||
console.log('---middle onLoad', e)
|
||||
|
||||
this.bizToken = e.bizToken || ''
|
||||
this.redirectUrl = e.redirectUrl
|
||||
? decodeURIComponent(e.redirectUrl)
|
||||
: ''
|
||||
|
||||
this.goFaceAuth()
|
||||
},
|
||||
computed: {
|
||||
staticHost() {
|
||||
return this.$config.staticUrl
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
goFaceAuth() {
|
||||
const { bizToken } = this
|
||||
|
||||
// ⚠️ 只有微信小程序支持
|
||||
// #ifdef MP-WEIXIN
|
||||
wx.navigateToMiniProgram({
|
||||
appId: 'wx1c9e1d0b916674dc', // 公证签小程序 APPID
|
||||
path: `/pages-biz/face/faceAuth?bizToken=${bizToken}`,
|
||||
success: () => {
|
||||
this.goFaceDone = true
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
|
||||
onShow() {
|
||||
console.log('---middle onShow')
|
||||
|
||||
const { goFaceDone, redirectUrl } = this
|
||||
|
||||
// 防止首次进入直接触发
|
||||
if (!goFaceDone) return
|
||||
|
||||
// 重置状态
|
||||
this.goFaceDone = false
|
||||
|
||||
// ⚠️ 微信小程序专有
|
||||
// #ifdef MP-WEIXIN
|
||||
if (!wx.getEnterOptionsSync) return
|
||||
|
||||
const options = wx.getEnterOptionsSync()
|
||||
console.log('---options', options)
|
||||
|
||||
// scene === 1038:从其他小程序返回
|
||||
if (
|
||||
options.scene === 1038 &&
|
||||
options.referrerInfo &&
|
||||
options.referrerInfo.extraData &&
|
||||
options.referrerInfo.extraData.faceResult
|
||||
) {
|
||||
const pages = getCurrentPages()
|
||||
const pre = pages[pages.length - 2]
|
||||
|
||||
// 调用上一个页面的 reloadPage 方法
|
||||
if (pre && typeof pre.reloadPage === 'function') {
|
||||
pre.reloadPage(
|
||||
`${redirectUrl}&timeStamp=${Date.now()}`
|
||||
)
|
||||
|
||||
wx.navigateBack({
|
||||
delta: 1
|
||||
})
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.loading-content {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
.image {
|
||||
width: 172rpx;
|
||||
height: 186rpx;
|
||||
}
|
||||
.loading-tect-content {
|
||||
font-size: 28rpx;
|
||||
margin-top: 48rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.btn-content {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-top: 24rpx;
|
||||
}
|
||||
.btn-click {
|
||||
color: #F34038;
|
||||
}
|
||||
|
||||
</style>
|
||||
203
pages-biz/location/location.vue
Normal file
203
pages-biz/location/location.vue
Normal file
@@ -0,0 +1,203 @@
|
||||
<template>
|
||||
<view class="location-page">
|
||||
<custom-navbar title="选择城市" />
|
||||
<view class="selected" >
|
||||
<view class="title">
|
||||
已选 :
|
||||
<text style="color: #FF2F31;margin-left: 12rpx;">
|
||||
{{vuex_city==''?'请选择城市':vuex_city}}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="location" @click="setLocation">
|
||||
<view class="title">当前定位</view>
|
||||
<view class="body">
|
||||
<view class="left">
|
||||
<image src="../../static/navigate.png" mode="widthFix" class="img"></image>
|
||||
{{locationCity}}
|
||||
</view>
|
||||
<view class="right">切换城市</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="hot" >
|
||||
<view class="title">热门城市</view>
|
||||
<view class="body">
|
||||
<view class="tag" v-for="(item,index) in hotList" :key="index" @click="clickCity(item)">{{item}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
// import wxGetAddress from '@/common/utils/wxGetAddress.js'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
locationCity:'',
|
||||
hotList:['杭州市','郑州市','北京市','上海市','广州市','深圳市','武汉市']
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
this.findLocation()
|
||||
},
|
||||
methods: {
|
||||
clickCity(item){
|
||||
this.$u.vuex('vuex_city', item);
|
||||
const pages = getCurrentPages();
|
||||
if (pages.length > 1) {
|
||||
uni.navigateBack();
|
||||
} else {
|
||||
uni.switchTab({
|
||||
url:'/pages/index/index'
|
||||
})
|
||||
}
|
||||
},
|
||||
setLocation(){
|
||||
this.$u.vuex('vuex_city', this.locationCity);
|
||||
const pages = getCurrentPages();
|
||||
if (pages.length > 1) {
|
||||
uni.navigateBack();
|
||||
} else {
|
||||
uni.switchTab({
|
||||
url:'/pages/index/index'
|
||||
})
|
||||
}
|
||||
},
|
||||
findLocation(){
|
||||
let that = this
|
||||
uni.showLoading({title:"定位中....",mask:true})
|
||||
this.$u.get("/location/getRealCity").then(obj => {
|
||||
let cityName = obj.msg
|
||||
if(cityName){
|
||||
this.locationCity = cityName
|
||||
let lifeData = uni.getStorageSync('lifeData');
|
||||
let vuex_city = lifeData.vuex_city
|
||||
if(!vuex_city){
|
||||
this.$u.vuex('vuex_city', cityName);
|
||||
}
|
||||
uni.hideLoading();
|
||||
}else{
|
||||
uni.request({
|
||||
//获取真实IP
|
||||
url:'https://tianqiapi.com/ip?version=v9&appid=23035354&appsecret=8YvlPNrz',
|
||||
success(resp) {
|
||||
let ip = resp.data.ip
|
||||
that.$u.get("/api/profile/getRealCityByIP?ip="+ip).then(obj => {
|
||||
let cityName = obj.msg
|
||||
if(cityName){
|
||||
that.locationCity = cityName
|
||||
let lifeData = uni.getStorageSync('lifeData');
|
||||
let vuex_city = lifeData.vuex_city
|
||||
if(!vuex_city){
|
||||
that.$u.vuex('vuex_city', cityName);
|
||||
}
|
||||
uni.hideLoading();
|
||||
}else{
|
||||
that.$mytip.toast('定位失败')
|
||||
}
|
||||
uni.hideLoading();
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
// 微信小程序定位审核太严厉,不使用了。扩展后端接口,使用ip定位
|
||||
// findLocation(){
|
||||
// uni.showLoading({title:"定位中....",mask:true})
|
||||
// uni.getLocation({
|
||||
// type: 'gcj02',
|
||||
// success: async res => {
|
||||
// let { longitude, latitude } = res;
|
||||
// let result = await wxGetAddress({ longitude, latitude });
|
||||
// let province = result.regeocodeData.addressComponent.province
|
||||
// let cityName = result.regeocodeData.addressComponent.city
|
||||
// this.locationCity = cityName
|
||||
// let lifeData = uni.getStorageSync('lifeData');
|
||||
// let vuex_city = lifeData.vuex_city
|
||||
// if(!vuex_city){
|
||||
// this.$u.vuex('vuex_city', cityName);
|
||||
// }
|
||||
// uni.hideLoading();
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.location-page{
|
||||
padding-top: 175rpx;
|
||||
}
|
||||
.selected{
|
||||
.title {
|
||||
font-size: 30rpx;
|
||||
color: $u-main-color;
|
||||
margin: 30rpx 20rpx;
|
||||
font-weight: 600;
|
||||
margin-left: 30rpx;
|
||||
}
|
||||
}
|
||||
.location{
|
||||
background: #FFFFFF;
|
||||
border-radius: 18rpx;
|
||||
padding: 3rpx 0;
|
||||
margin: 20rpx;
|
||||
.title {
|
||||
font-size: 22rpx;
|
||||
color: $u-tips-color;
|
||||
margin: 30rpx 20rpx;
|
||||
}
|
||||
|
||||
.body{
|
||||
margin: 30rpx 20rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
.left{
|
||||
font-size: 32rpx;
|
||||
font-weight: 800;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
.img{
|
||||
width: 35rpx;
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
}
|
||||
.right{
|
||||
color: #FF2F31;
|
||||
font-weight: 600;
|
||||
margin-right: 10rpx;
|
||||
};
|
||||
}
|
||||
}
|
||||
.hot{
|
||||
.title {
|
||||
font-size: 30rpx;
|
||||
color: $u-main-color;
|
||||
margin: 30rpx 20rpx;
|
||||
font-weight: 600;
|
||||
margin-left: 30rpx;
|
||||
}
|
||||
|
||||
.body{
|
||||
background: #FFFFFF;
|
||||
padding-bottom: 15rpx;
|
||||
border-radius: 18rpx;
|
||||
margin: 20rpx;
|
||||
.tag {
|
||||
display: inline-block;
|
||||
width: 137rpx;
|
||||
height: 75rpx;
|
||||
line-height: 75rpx;
|
||||
font-size: 26rpx;
|
||||
color: $u-content-color;
|
||||
margin: 20rpx 20rpx 5rpx 20rpx;
|
||||
padding: 5rpx 10rpx;
|
||||
text-align: center;
|
||||
background-color: $u-bg-color;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
308
pages-biz/login/login.vue
Normal file
308
pages-biz/login/login.vue
Normal file
@@ -0,0 +1,308 @@
|
||||
<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="content">
|
||||
<!-- 标题区域 -->
|
||||
<view class="title">登录</view>
|
||||
|
||||
<!-- 登录插图区域 -->
|
||||
<view class="illustration">
|
||||
<image :src="staticHost + '/public/static/login/bg.png'" mode="aspectFit" />
|
||||
</view>
|
||||
|
||||
<!-- 登录按钮 -->
|
||||
<button class="login-btn" open-type="getPhoneNumber" @getphonenumber="onGetPhone">
|
||||
使用微信授权登录
|
||||
</button>
|
||||
|
||||
<!-- 用户类型选择 -->
|
||||
<view class="user-type" shape="square">
|
||||
<u-radio-group v-model="loginType" active-color="#EA414A" shape="square">
|
||||
<u-radio name="0">个人</u-radio>
|
||||
<u-radio name="1">企业</u-radio>
|
||||
</u-radio-group>
|
||||
</view>
|
||||
|
||||
<!-- 协议勾选 -->
|
||||
<view class="agreement">
|
||||
<u-checkbox v-model="agreeProtocol" shape="square" active-color="#EA414A">
|
||||
<text class="agreement-text">
|
||||
我已阅读并同意
|
||||
<text class="link" @tap.stop="goPrivacy('user')">《用户协议》</text>
|
||||
和
|
||||
<text class="link" @tap.stop="goPrivacy('privacy')">《隐私政策》</text>
|
||||
</text>
|
||||
</u-checkbox>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
designWidth: 750,
|
||||
screenWidth: 0,
|
||||
scale: 1,
|
||||
loginType: "0",
|
||||
agreeProtocol: false
|
||||
}
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
wx.getWindowInfo({
|
||||
success: (res) => {
|
||||
this.screenWidth = res.windowWidth
|
||||
this.scale = this.screenWidth / this.designWidth
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
onUnload() {
|
||||
|
||||
},
|
||||
computed: {
|
||||
// title 样式
|
||||
titleStyle() {
|
||||
const s = this.scale;
|
||||
return {
|
||||
width: `${93 * s}rpx`,
|
||||
height: `${45 * s}rpx`,
|
||||
fontSize: `${48 * s}rpx`,
|
||||
lineHeight: `${40 * s}rpx`,
|
||||
fontWeight: 500,
|
||||
color: '#222222',
|
||||
fontFamily: 'Noto Sans S Chinese',
|
||||
textAlign: 'center'
|
||||
}
|
||||
},
|
||||
staticHost() {
|
||||
return this.$config.staticUrl
|
||||
},
|
||||
headerStyle() {
|
||||
const s = this.scale;
|
||||
return {
|
||||
marginTop: `${214 * s}rpx`,
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center', // 水平居中
|
||||
justifyContent: 'flex-start' // 从顶部开始
|
||||
}
|
||||
},
|
||||
// cover 样式
|
||||
coverStyle() {
|
||||
const s = this.scale;
|
||||
return {
|
||||
width: `${428 * s}rpx`,
|
||||
height: `${608 * s}rpx`
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
/** 微信手机号授权 */
|
||||
onGetPhone(e) {
|
||||
const {
|
||||
code
|
||||
} = e.detail;
|
||||
|
||||
if (!code) {
|
||||
this.$mytip.toast("授权失败");
|
||||
return;
|
||||
}
|
||||
this.doLogin(code);
|
||||
},
|
||||
|
||||
/** 执行登录逻辑 */
|
||||
async doLogin(phoneGetCode) {
|
||||
if (!this.agreeProtocol) {
|
||||
this.$mytip.toast('请先阅读并同意用户协议和隐私政策')
|
||||
return
|
||||
}
|
||||
uni.showLoading({
|
||||
title: "登录中...",
|
||||
mask: true
|
||||
});
|
||||
|
||||
try {
|
||||
// 微信登录
|
||||
const loginRes = await new Promise((resolve, reject) => {
|
||||
uni.login({
|
||||
provider: 'weixin',
|
||||
success: resolve,
|
||||
fail: reject
|
||||
});
|
||||
});
|
||||
|
||||
// 调用后端登录接口
|
||||
const authRes = await this.$u.post(`/login/weChatLogin`, {
|
||||
phoneGetCode: phoneGetCode,
|
||||
loginCode: loginRes.code,
|
||||
loginType: this.loginType
|
||||
});
|
||||
|
||||
// let tempToken = 'asd5646'
|
||||
// 保存token
|
||||
let token = authRes.data;
|
||||
this.$u.vuex('vuex_token', token);
|
||||
// this.$u.vuex('vuex_token', tempToken);
|
||||
uni.hideLoading();
|
||||
this.getUserOtherInfo(this.loginType,token)
|
||||
// 跳转首页
|
||||
uni.switchTab({
|
||||
url: '/pages/index/index'
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
uni.hideLoading();
|
||||
this.$mytip.toast("登录失败");
|
||||
console.error('Login failed:', err);
|
||||
}
|
||||
},
|
||||
getUserOtherInfo(loginType, token) {
|
||||
let url = `/login/userInfo`;
|
||||
this.$u.get(url, {}, {
|
||||
'WT': token,
|
||||
'USERTYPE': loginType
|
||||
}).then(obj => {
|
||||
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
|
||||
})
|
||||
}
|
||||
});
|
||||
// let tempUser = {
|
||||
// userType: '0',
|
||||
// oaAuth: '1',
|
||||
// cusNo: 'cus0001',
|
||||
// openId: '65456asd'
|
||||
// }
|
||||
// uni.setStorageSync('userInfo', tempUser)
|
||||
|
||||
},
|
||||
/** 触发手机号选择(子组件 → 父组件) */
|
||||
showPhoneSelector(phoneList, openid) {
|
||||
this.$emit("choosePhone", {
|
||||
phoneList,
|
||||
openid
|
||||
});
|
||||
},
|
||||
/** 查看协议 */
|
||||
goPrivacy(type) {
|
||||
console.log(type)
|
||||
const url = type === 'user' ?
|
||||
'/pages-biz/privacy/userAgreement' :
|
||||
'/pages-biz/privacy/privacyPolicy';
|
||||
|
||||
uni.navigateTo({
|
||||
url
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.login-container {
|
||||
height: 100vh;
|
||||
background-color: #fff;
|
||||
overflow: hidden;
|
||||
|
||||
.status-bar {
|
||||
margin-top: 7.56%;
|
||||
height: var(--status-bar-height);
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
height: calc(100vh - var(--status-bar-height));
|
||||
margin-top: 12.8%;
|
||||
}
|
||||
|
||||
.title {
|
||||
height: 45rpx;
|
||||
font-family: Noto Sans S Chinese;
|
||||
font-weight: 500;
|
||||
font-size: 48rpx;
|
||||
color: #222222;
|
||||
line-height: 40rpx;
|
||||
}
|
||||
|
||||
.illustration {
|
||||
width: 100%;
|
||||
height: 44.6%;
|
||||
padding: 0 21.47%;
|
||||
margin-bottom: 6%;
|
||||
}
|
||||
|
||||
.illustration image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
width: 78.6%;
|
||||
height: 7.12%;
|
||||
background: linear-gradient(90deg, #FF2F31 0%, #FF9379 100%);
|
||||
border-radius: 49rpx;
|
||||
font-size: 36rpx;
|
||||
color: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.user-type {
|
||||
display: flex; justify-content: center;
|
||||
gap: 40rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.agreement {
|
||||
margin-top: 6.24%;
|
||||
padding: 0 40rpx;
|
||||
}
|
||||
|
||||
.agreement-text {
|
||||
font-size: 26rpx;
|
||||
color: #cecece;
|
||||
}
|
||||
|
||||
.link {
|
||||
color: #334254;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* 响应式适配 */
|
||||
@media (max-width: 320rpx) {
|
||||
.title {
|
||||
font-size: 42rpx;
|
||||
margin-bottom: 100rpx;
|
||||
}
|
||||
|
||||
.illustration {
|
||||
height: 360rpx;
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
height: 80rpx;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
168
pages-biz/message/message.vue
Normal file
168
pages-biz/message/message.vue
Normal file
@@ -0,0 +1,168 @@
|
||||
<template>
|
||||
<view class="message-page">
|
||||
|
||||
<!-- 顶部导航栏 -->
|
||||
<u-navbar title="消息中心" bg-color="#fff" title-color="#111" :border-bottom="true" @leftClick="goBack">
|
||||
<view slot="left">
|
||||
<u-icon name="arrow-left" size="44" color="#111"></u-icon>
|
||||
</view>
|
||||
</u-navbar>
|
||||
|
||||
<!-- 消息列表 -->
|
||||
<scroll-view scroll-y class="message-list" @scrolltolower="loadMore" @refresherrefresh="refresh"
|
||||
:refresher-enabled="true" :refresher-triggered="isRefreshing">
|
||||
<view v-for="(msg,index) in flowList" :key="index" class="msg-item u-flex u-row-between" @click="viewMessage(msg)">
|
||||
<view class="u-flex">
|
||||
<image :src="staticHost + '/public' + msg.icon"></image>
|
||||
<view class="msg-content u-m-l-20">
|
||||
<view class="msg-title u-font-16">{{ msg.title }}</view>
|
||||
<view class="msg-desc u-tips-color u-font-12 u-line-2">{{ msg.desc }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="msg-right u-text-right">
|
||||
<view class="msg-time u-font-12 u-tips-color">{{ msg.time }}</view>
|
||||
<view v-if="!msg.read" class="unread-dot"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<u-loadmore :status="loadStatus" :loading-text="'加载中...'" :nomore-text="'没有更多消息了'"></u-loadmore>
|
||||
</scroll-view>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
flowList: [],
|
||||
pageNo:1,
|
||||
pageSize:10,
|
||||
isRefreshing: false,
|
||||
loadStatus: 'loadmore'
|
||||
}
|
||||
},
|
||||
onShow() {
|
||||
this.$checkToken(this.$getToken())
|
||||
},
|
||||
onload(){
|
||||
this.loadMore();
|
||||
},
|
||||
methods: {
|
||||
goBack() {
|
||||
const pages = getCurrentPages();
|
||||
if (pages.length > 1) {
|
||||
uni.navigateBack();
|
||||
} else {
|
||||
uni.switchTab({
|
||||
url:'/pages/index/index'
|
||||
})
|
||||
}
|
||||
},
|
||||
getIconUrl(type) {
|
||||
if(type === 'BILL') {
|
||||
return this.$config.staticUrl + '/static/icon/msg-1.png'
|
||||
}
|
||||
if(type === 'SIGN') {
|
||||
return this.$config.baseUrl + '/static/icon/msg-2.png'
|
||||
}
|
||||
},
|
||||
fetchMessageList(){
|
||||
if (this.loadStatus !== 'loadmore') return;
|
||||
this.loadStatus = 'loading';
|
||||
let url = '/message/queryPage'
|
||||
this.$u.post(url, {
|
||||
pageNo: this.pageNo,
|
||||
pageSize: this.pageSize
|
||||
}, {
|
||||
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) {
|
||||
this.loadStatus = 'nomore';
|
||||
} else {
|
||||
this.pageNo++;
|
||||
this.loadStatus = 'loadmore';
|
||||
}
|
||||
}).catch(err => {
|
||||
console.log("获取消息列表失败:", err)
|
||||
this.loadStatus = 'loadmore';
|
||||
})
|
||||
},
|
||||
loadMore() {
|
||||
// 只有在 loadStatus 为 'loadmore' 时才触发
|
||||
if (this.loadStatus !== 'loadmore') return;
|
||||
this.fetchMessageList()
|
||||
},
|
||||
viewMessage(msg) {
|
||||
this.$u.route({url:'/pages-biz/message/messageDetail',params:{
|
||||
id: msg.id,
|
||||
read: msg.read
|
||||
}});
|
||||
},
|
||||
refresh() {
|
||||
this.isRefreshing = true;
|
||||
setTimeout(() => {
|
||||
this.isRefreshing = false;
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.message-page {
|
||||
background-color: #ffffff;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.message-list {
|
||||
padding: 20rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.msg-item {
|
||||
padding: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
align-items: flex-start;
|
||||
position: relative;
|
||||
border-bottom: 1px solid #E6E6E6;
|
||||
|
||||
image{
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.msg-content {
|
||||
width: 500rpx;
|
||||
}
|
||||
|
||||
.msg-title {
|
||||
font-weight: bold;
|
||||
color: #111;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.msg-right {
|
||||
align-items: flex-end;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.msg-time{
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.unread-dot {
|
||||
width: 14rpx;
|
||||
height: 14rpx;
|
||||
background-color: #ff4d4f;
|
||||
border-radius: 50%;
|
||||
margin-top: 50rpx;
|
||||
}
|
||||
</style>
|
||||
73
pages-biz/message/messageDetail.vue
Normal file
73
pages-biz/message/messageDetail.vue
Normal file
@@ -0,0 +1,73 @@
|
||||
<template>
|
||||
<view class="msg-detail-page">
|
||||
<u-navbar title="消息详情" bg-color="#fff" title-color="#111" :border-bottom="true" @leftClick="goBack">
|
||||
<view slot="left">
|
||||
<u-icon name="arrow-left" size="44" color="#111"></u-icon>
|
||||
</view>
|
||||
</u-navbar>
|
||||
|
||||
<view class="msg-detail u-p-40">
|
||||
<view class="msg-title u-font-20 u-m-b-20">{{ title }}</view>
|
||||
<view class="msg-desc u-font-14 u-tips-color">{{ desc }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
id:null,
|
||||
title: '',
|
||||
desc: ''
|
||||
}
|
||||
},
|
||||
onShow() {
|
||||
this.$checkToken(this.$getToken())
|
||||
},
|
||||
onLoad(e) {
|
||||
this.id = e.id
|
||||
this.getDetail()
|
||||
},
|
||||
methods: {
|
||||
getDetail(){
|
||||
this.$u.get(`/message/detail?id=${this.id}`,{},{
|
||||
WT: this.$getToken()
|
||||
}).then(res=>{
|
||||
if(res.flag) {
|
||||
this.title = res.data.title;
|
||||
this.desc = res.data.messageContent;
|
||||
}
|
||||
})
|
||||
},
|
||||
read(){
|
||||
this.$u.get(`/message/read?id=${this.id}`,{},{
|
||||
WT: this.$getToken()
|
||||
})
|
||||
},
|
||||
goBack() {
|
||||
const pages = getCurrentPages();
|
||||
if (pages.length > 1) {
|
||||
uni.navigateBack();
|
||||
} else {
|
||||
uni.switchTab({
|
||||
url:'/pages/index/index'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.msg-detail-page {
|
||||
background: #fff;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.msg-detail {
|
||||
line-height: 1.8;
|
||||
}
|
||||
.msg-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
129
pages-biz/myrent/myLease.vue
Normal file
129
pages-biz/myrent/myLease.vue
Normal file
@@ -0,0 +1,129 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<!-- 顶部自定义导航栏 -->
|
||||
<customNavbar title="我的租赁资产" />
|
||||
|
||||
<!-- 筛选栏 -->
|
||||
|
||||
|
||||
<!-- 租赁资产列表组件 -->
|
||||
<scroll-view
|
||||
scroll-y
|
||||
class="scroll-content"
|
||||
@scrolltolower="loadMore"
|
||||
>
|
||||
<asset-list
|
||||
:list="assetList"
|
||||
@click="goDetail"
|
||||
/>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AssetList from '@/components/asset/assetList.vue'
|
||||
export default {
|
||||
name: 'MyLease',
|
||||
components: { AssetList},
|
||||
data() {
|
||||
return {
|
||||
assetList: [],
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
loadStatus: 'loadmore',
|
||||
};
|
||||
},
|
||||
onLoad(options) {
|
||||
this.cusNo = options.cusNo;
|
||||
this.loadAssets();
|
||||
},
|
||||
onShow() {
|
||||
this.$checkToken(this.$getToken())
|
||||
},
|
||||
staticHost() {
|
||||
return this.$config.staticUrl
|
||||
},
|
||||
methods: {
|
||||
changeFilter(value) {
|
||||
if (this.currentFilter === value) return;
|
||||
this.currentFilter = value;
|
||||
this.pageNo = 1;
|
||||
this.assetList = [];
|
||||
this.loadAssets();
|
||||
},
|
||||
|
||||
loadAssets() {
|
||||
if (this.loadStatus !== 'loadmore') return;
|
||||
this.loadStatus = 'loading';
|
||||
|
||||
this.$u.post('/assets/getMyAssetsList', {
|
||||
pageNo: this.pageNo,
|
||||
pageSize: this.pageSize
|
||||
},{
|
||||
WT: this.$getToken()
|
||||
}).then(res => {
|
||||
const rows = res.data.result || [];
|
||||
|
||||
if (this.pageNo === 1) this.assetList = [];
|
||||
|
||||
this.assetList = this.assetList.concat(rows);
|
||||
|
||||
if (rows.length < this.pageSize) {
|
||||
this.loadStatus = 'nomore';
|
||||
} else {
|
||||
this.pageNo++;
|
||||
this.loadStatus = 'loadmore';
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error(err);
|
||||
this.loadStatus = 'loadmore';
|
||||
});
|
||||
},
|
||||
|
||||
loadMore() {
|
||||
this.loadAssets();
|
||||
},
|
||||
|
||||
goDetail(item) {
|
||||
uni.navigateTo({
|
||||
url: `/pages-assets/assets/assetsDetail?assetsNo=${item.assetsNo}`,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
background: #f7f8fa;
|
||||
padding-top: 175rpx;
|
||||
}
|
||||
|
||||
.filter-bar {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
background-color: #fff;
|
||||
padding: 20rpx 0;
|
||||
margin-top: var(--custom-navbar-height);
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
|
||||
.filter-btn {
|
||||
min-width: 150rpx;
|
||||
text-align: center;
|
||||
font-size: 26rpx;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
border-radius: 40rpx;
|
||||
margin: 0 8rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.scroll-content {
|
||||
flex: 1;
|
||||
height: calc(100vh - 200rpx);
|
||||
}
|
||||
</style>
|
||||
161
pages-biz/notice/notice.vue
Normal file
161
pages-biz/notice/notice.vue
Normal file
@@ -0,0 +1,161 @@
|
||||
<template>
|
||||
<view>
|
||||
<u-navbar :is-back="true" title="招商公告" :border-bottom="false"></u-navbar>
|
||||
<view class="wrap">
|
||||
<scroll-view scroll-y style="height: 100%;width: 100%;">
|
||||
<view class="page-box">
|
||||
<view class="tabSwiper" v-for="(item, index) in flowList" :key="item.noticeId" @click="clickContent(item)">
|
||||
<u-icon name="bell" :size="35" color="#FF2F31" class="bell-icon"></u-icon>
|
||||
<view class="content-wrapper">
|
||||
<view class="top">
|
||||
<view class="title">{{ item.noticeTitle }}</view>
|
||||
<view class="date">{{ item.createTime }}</view>
|
||||
</view>
|
||||
<view class="item">
|
||||
<view class="content u-line-2">{{ item.noticeContent }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<u-icon name="arrow-right" color="rgb(203,203,203)" :size="26" class="arrow-icon"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
// 静态公告数据
|
||||
const staticNoticeList = [];
|
||||
return {
|
||||
pageNo: 1,
|
||||
pageSize: 20,
|
||||
flowList: [],
|
||||
loadStatus: 'loadmore',
|
||||
isRefreshing: false
|
||||
};
|
||||
},
|
||||
onLoad() {
|
||||
// 静态数据已在data中初始化,无需调用接口
|
||||
this.getNoticecList();
|
||||
},
|
||||
methods: {
|
||||
clickContent(item) {
|
||||
if (item.noticeId) {
|
||||
this.$u.route({
|
||||
url: '/pages-biz/notice/noticeDetail',
|
||||
params: {
|
||||
id: item.noticeId
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
getNoticecList() {
|
||||
if (this.loadStatus !== 'loadmore') return;
|
||||
this.loadStatus = 'loading';
|
||||
let url = "/notice/pageQuery";
|
||||
this.$u.post(url, {
|
||||
pageNo: this.pageNo,
|
||||
pageSize: this.pageSize
|
||||
}).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) {
|
||||
this.loadStatus = 'nomore';
|
||||
} else {
|
||||
this.pageNo++;
|
||||
this.loadStatus = 'loadmore';
|
||||
}
|
||||
}).catch(err => {
|
||||
console.log("获取招商公告失败:", err)
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.tabSwiper {
|
||||
width: 95%;
|
||||
background-color: #ffffff;
|
||||
margin: 16rpx auto;
|
||||
border-radius: 12rpx;
|
||||
box-sizing: border-box;
|
||||
padding: 32rpx;
|
||||
box-shadow: 0 1rpx 8rpx rgba(0, 0, 0, 0.04);
|
||||
transition: all 0.2s ease;
|
||||
border: 1rpx solid #f0f0f0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.bell-icon {
|
||||
margin-right: 32rpx;
|
||||
flex-shrink: 0;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.arrow-icon {
|
||||
margin-left: 24rpx;
|
||||
flex-shrink: 0;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-top: 20rpx;
|
||||
|
||||
.title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
line-height: 38rpx;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.date {
|
||||
color: #999999;
|
||||
font-size: 26rpx;
|
||||
flex-shrink: 0;
|
||||
position: absolute;
|
||||
right: 30rpx;
|
||||
top: 15rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.item {
|
||||
.content {
|
||||
font-size: 26rpx;
|
||||
line-height: 36rpx;
|
||||
color: #666666;
|
||||
letter-spacing: 0.5rpx;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - var(--window-top));
|
||||
width: 100%;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.page-box {
|
||||
padding: 16rpx 0 40rpx 0;
|
||||
}
|
||||
</style>
|
||||
56
pages-biz/notice/noticeDetail.vue
Normal file
56
pages-biz/notice/noticeDetail.vue
Normal file
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<view>
|
||||
<u-navbar :is-back="true" :title="title" :border-bottom="false"></u-navbar>
|
||||
<view class="u-content">
|
||||
<u-parse :html="content"
|
||||
:autosetTitle="true"
|
||||
:show-with-animation="true"
|
||||
:selectable="true"></u-parse>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
id:null,
|
||||
title:'资讯',
|
||||
content: ``
|
||||
}
|
||||
},
|
||||
onLoad(option) {
|
||||
this.id = option.id
|
||||
this.getDetail()
|
||||
},
|
||||
methods:{
|
||||
getDetail(){
|
||||
this.$u.get(`/notice/detail?id=${this.id}`,{},{
|
||||
WT: this.$getToken()
|
||||
}).then(res=>{
|
||||
if(res.flag) {
|
||||
this.title = res.data.noticeTitle;
|
||||
this.content = res.data.noticeContent;
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
page{
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
</style>
|
||||
<style lang="scss" scoped>
|
||||
.u-content{
|
||||
margin:0 10rpx;
|
||||
padding: 24rpx;
|
||||
font-size: 34rpx;
|
||||
color: $u-main-color;
|
||||
line-height: 1.8;
|
||||
white-space: pre-wrap !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
120
pages-biz/privacy/privacyPolicy.vue
Normal file
120
pages-biz/privacy/privacyPolicy.vue
Normal file
@@ -0,0 +1,120 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<view class="header">
|
||||
<view class="back" @click="goBack">
|
||||
<u-icon name="arrow-left" size="36" color="#333" />
|
||||
</view>
|
||||
<text class="title">隐私协议</text>
|
||||
</view>
|
||||
|
||||
<scroll-view class="content" scroll-y>
|
||||
<view class="text">
|
||||
<text class="section-title">一、我们如何收集信息</text>
|
||||
<text class="paragraph">
|
||||
在您使用本应用过程中,我们可能会收集与您相关的必要信息,
|
||||
包括但不限于设备信息、日志信息以及您主动提供的信息。
|
||||
</text>
|
||||
|
||||
<text class="section-title">二、信息的使用方式</text>
|
||||
<text class="paragraph">
|
||||
我们收集的信息仅用于提供和优化服务、保障账号与系统安全、
|
||||
以及履行法律法规规定的义务。
|
||||
</text>
|
||||
|
||||
<text class="section-title">三、信息的存储与保护</text>
|
||||
<text class="paragraph">
|
||||
我们将采取合理的技术与管理措施,保护您的个人信息不被泄露、
|
||||
篡改或非法访问。
|
||||
</text>
|
||||
|
||||
<text class="section-title">四、信息的共享与披露</text>
|
||||
<text class="paragraph">
|
||||
未经您的明确同意,我们不会向第三方共享或披露您的个人信息,
|
||||
法律法规另有规定的除外。
|
||||
</text>
|
||||
|
||||
<text class="section-title">五、您的权利</text>
|
||||
<text class="paragraph">
|
||||
您有权依法查询、更正或删除您的个人信息,并可随时联系我们行使相关权利。
|
||||
</text>
|
||||
|
||||
<text class="section-title">六、政策变更</text>
|
||||
<text class="paragraph">
|
||||
本隐私政策如有更新,我们将以合理方式向您告知。
|
||||
</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
methods: {
|
||||
goBack() {
|
||||
const pages = getCurrentPages();
|
||||
if (pages.length > 1) {
|
||||
uni.navigateBack();
|
||||
} else {
|
||||
uni.navigateTo({
|
||||
url:'/pages-biz/login/login'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
height: 100vh;
|
||||
padding-top:120rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.header {
|
||||
position: relative;
|
||||
height: 88rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.back {
|
||||
position: absolute;
|
||||
left: 30rpx;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 28rpx;
|
||||
color: #555;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
display: block;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-top: 30rpx;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.paragraph {
|
||||
display: block;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
</style>
|
||||
119
pages-biz/privacy/userAgreement.vue
Normal file
119
pages-biz/privacy/userAgreement.vue
Normal file
@@ -0,0 +1,119 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<view class="header">
|
||||
<view class="back" @click="goBack">
|
||||
<u-icon name="arrow-left" size="36" color="#333" />
|
||||
</view>
|
||||
<text class="title">用户协议</text>
|
||||
</view>
|
||||
|
||||
<scroll-view class="content" scroll-y>
|
||||
<view class="text">
|
||||
<text class="section-title">一、协议说明</text>
|
||||
<text class="paragraph">
|
||||
欢迎您使用本应用。请您在使用本应用前,认真阅读并充分理解本《用户协议》。
|
||||
一旦您开始使用本应用,即视为您已阅读、理解并同意本协议的全部内容。
|
||||
</text>
|
||||
|
||||
<text class="section-title">二、用户行为规范</text>
|
||||
<text class="paragraph">
|
||||
1. 用户应遵守中华人民共和国相关法律法规,不得利用本应用从事任何违法活动。
|
||||
</text>
|
||||
<text class="paragraph">
|
||||
2. 不得发布、传播违法、侵权、虚假、骚扰性或其他不当信息。
|
||||
</text>
|
||||
<text class="paragraph">
|
||||
3. 不得以任何形式干扰本应用的正常运行。
|
||||
</text>
|
||||
|
||||
<text class="section-title">三、服务变更与中断</text>
|
||||
<text class="paragraph">
|
||||
本应用有权根据业务发展需要,对服务内容进行调整、中断或终止,
|
||||
并尽可能提前向用户告知。
|
||||
</text>
|
||||
|
||||
<text class="section-title">四、免责声明</text>
|
||||
<text class="paragraph">
|
||||
因不可抗力或非本应用原因造成的服务中断或数据损失,本应用不承担责任。
|
||||
</text>
|
||||
|
||||
<text class="section-title">五、其他</text>
|
||||
<text class="paragraph">
|
||||
本协议的解释权及修改权归本应用所有。
|
||||
</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
methods: {
|
||||
goBack() {
|
||||
const pages = getCurrentPages();
|
||||
if (pages.length > 1) {
|
||||
uni.navigateBack();
|
||||
} else {
|
||||
uni.navigateTo({
|
||||
url:'/pages-biz/login/login'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
height: 100vh;
|
||||
padding-top:120rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.header {
|
||||
position: relative;
|
||||
height: 88rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.back {
|
||||
position: absolute;
|
||||
left: 30rpx;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 28rpx;
|
||||
color: #555;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
display: block;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-top: 30rpx;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.paragraph {
|
||||
display: block;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
</style>
|
||||
180
pages-biz/profile/profile.vue
Normal file
180
pages-biz/profile/profile.vue
Normal file
@@ -0,0 +1,180 @@
|
||||
<template>
|
||||
<view class="u-m-20">
|
||||
<u-navbar :is-back="true" title="账号实名信息" :border-bottom="false"></u-navbar>
|
||||
<view>
|
||||
<!-- <u-cell-group>
|
||||
<u-cell-item title="头像" :arrow="false" hover-class="none" @click="updateAvatar">
|
||||
<u-avatar :src="pic" size="100"></u-avatar>
|
||||
</u-cell-item>
|
||||
</u-cell-group> -->
|
||||
<!-- <u-cell-group>
|
||||
<u-cell-item title="昵称" :arrow="false" hover-class="none" @click="updateName">
|
||||
{{vuex_user.user.nickName}}
|
||||
</u-cell-item>
|
||||
</u-cell-group> -->
|
||||
<u-cell-group>
|
||||
<u-cell-item :title="getTitle" :arrow="false" hover-class="none" @click="updateName">
|
||||
{{(cardNo == null ? orgNo : cardNo) || ''}}
|
||||
</u-cell-item>
|
||||
</u-cell-group>
|
||||
</view>
|
||||
|
||||
<u-modal v-model="showModel" @confirm="confirmAuthCode" ref="uModal" :async-close="true"
|
||||
:title="'设置' + getTitle">
|
||||
<view class="slot-content">
|
||||
<u-input v-model="authCode" type="text" :border="false" :placeholder="'请输入' + getTitle" />
|
||||
</view>
|
||||
</u-modal>
|
||||
|
||||
<!-- <view class="u-m-t-20">
|
||||
<u-button type="primary" @click="subProfile">提交</u-button>
|
||||
</view> -->
|
||||
|
||||
<!-- 如果是微信登录小程序,则获取用户的昵称与头像 -->
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<!-- <u-button type="default">使用微信头像与昵称</u-button> -->
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import config from "@/common/config.js" // 全局配置文件
|
||||
import {
|
||||
rsaEncrypt
|
||||
} from "@/common/utils/ras.js"
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
user: {
|
||||
userType: null,
|
||||
oaAuth: null,
|
||||
cusNo: null,
|
||||
userName: null,
|
||||
openId: null
|
||||
},
|
||||
pic: null,
|
||||
authCode: '',
|
||||
showModel: false,
|
||||
cardNo: null,
|
||||
orgNo: null
|
||||
}
|
||||
},
|
||||
onLoad(options) {
|
||||
let userVo = uni.getStorageSync('userInfo');
|
||||
this.user = userVo;
|
||||
},
|
||||
methods: {
|
||||
updateName() {
|
||||
this.showModel = true;
|
||||
},
|
||||
// 简单身份证校验(18 位)
|
||||
checkIdCard(code) {
|
||||
return /^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/.test(
|
||||
code
|
||||
);
|
||||
},
|
||||
// 统一社会信用代码校验(18 位)
|
||||
checkCreditCode(code) {
|
||||
return /^[0-9A-Z]{18}$/.test(code);
|
||||
},
|
||||
confirmAuthCode() {
|
||||
let loginType = this.user.userType
|
||||
if (!this.authCode) {
|
||||
this.showModel = false;
|
||||
return this.$mytip.toast(loginType === '0' ? '请输入身份证号' : '请输入组织社会信用代码')
|
||||
}
|
||||
// 身份证校验
|
||||
if (this.loginType === '0' && !this.checkIdCard(this.authCode)) {
|
||||
uni.showToast({
|
||||
title: '身份证号格式不正确',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 企业社会信用代码校验
|
||||
if (this.loginType === '1' && !this.checkCreditCode(this.authCode)) {
|
||||
uni.showToast({
|
||||
title: '社会信用代码格式不正确',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
let life = uni.getStorageSync('lifeData') || {}
|
||||
let token = life.vuex_token
|
||||
let url = "/login/updateVerifyCode";
|
||||
let encryptCode = rsaEncrypt(this.authCode);
|
||||
this.$u.post(url, {
|
||||
userType: loginType,
|
||||
code: loginType === '0' ? encryptCode : this.authCode
|
||||
}, {
|
||||
'WT': token
|
||||
}).then(obj => {
|
||||
if (obj.flag) {
|
||||
if (loginType === '0') {
|
||||
this.cardNo = encryptCode
|
||||
this.cardNo = this.applyMask(this.authCode)
|
||||
} else {
|
||||
this.orgNo = this.authCode
|
||||
}
|
||||
this.showModel = false;
|
||||
this.$mytip.toast('修改成功');
|
||||
this.getUserProfile();
|
||||
uni.switchTab({
|
||||
url: '/pages/center/center'
|
||||
})
|
||||
} else {
|
||||
this.$mytip.toast('修改失败');
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
applyMask(idCard) {
|
||||
if (idCard.length !== 18) return idCard; // 非18位直接返回
|
||||
return idCard.replace(/(\d{6})(\d{8})(\d{4})/, '$1********$2****');
|
||||
},
|
||||
updateAvatar() {
|
||||
this.$u.route('/pages-biz/profile/avatar')
|
||||
},
|
||||
subProfile() {
|
||||
// 1.更新vuex中的用户信息
|
||||
this.$mytip.toast('修改成功')
|
||||
// 2.页面跳转
|
||||
|
||||
},
|
||||
getUserProfile() {
|
||||
// 如果是微信登录小程序,则获取用户的昵称与头像
|
||||
// #ifdef MP-WEIXIN
|
||||
// 此处执行微信才执行
|
||||
// #endif
|
||||
let token = this.$getToken();
|
||||
let loginType = this.user.userType
|
||||
let url = `/login/userInfo?loginType=${loginType}`;
|
||||
this.$u.get(url, {}, {
|
||||
'WT': token
|
||||
}).then(obj => {
|
||||
uni.setStorageSync('userInfo', {
|
||||
userType: obj.data.userType,
|
||||
oaAuth: obj.data.oaAuth,
|
||||
cusNo: obj.data.cusNo,
|
||||
userName: obj.data.userName,
|
||||
openId: obj.data.openId
|
||||
})
|
||||
});
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getTitle() {
|
||||
return this.user.userType === '0' ?
|
||||
'身份证号' :
|
||||
'企业社会信用代码'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.slot-content {
|
||||
padding: 40rpx;
|
||||
}
|
||||
</style>
|
||||
136
pages-biz/profile/setting.vue
Normal file
136
pages-biz/profile/setting.vue
Normal file
@@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<view>
|
||||
<u-navbar :is-back="true" title="设置" :border-bottom="false">
|
||||
</u-navbar>
|
||||
<view class="setting-content">
|
||||
<u-cell-group>
|
||||
<u-cell-item title="账号实名信息" @click="profile"></u-cell-item>
|
||||
<!-- <u-cell-item title="订阅系统消息" :arrow="false">
|
||||
|
||||
<u-switch v-model="longSubscribe" @change="toggleLongSubscribe" />
|
||||
|
||||
</u-cell-item> -->
|
||||
</u-cell-group>
|
||||
</view>
|
||||
|
||||
<view class="btn">
|
||||
<u-button @click="logout" plain>退出登录</u-button>
|
||||
</view>
|
||||
|
||||
<view class="version">Version {{vuex_version}}</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
longSubscribe: false // 开关状态
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
profile() {
|
||||
this.$u.route('/pages-biz/profile/profile')
|
||||
},
|
||||
logout() {
|
||||
// 登录成功修改token与用户信息
|
||||
this.$u.vuex('vuex_token', '');
|
||||
this.$u.vuex('vuex_user', {});
|
||||
uni.removeStorageSync('userInfo');
|
||||
return uni.reLaunch({
|
||||
url: '/pages/index/index'
|
||||
})
|
||||
},
|
||||
toggleLongSubscribe(e) {
|
||||
this.longSubscribe = e
|
||||
// 微信小程序专用
|
||||
// #ifdef MP-WEIXIN
|
||||
if (this.longSubscribe) {
|
||||
const tmplIds = ['TEMPLATE_ID_1', 'TEMPLATE_ID_2'] // 在小程序后台配置的长期订阅模板
|
||||
wx.requestSubscribeMessage({
|
||||
tmplIds,
|
||||
success: (res) => {
|
||||
// 用户同意才生效
|
||||
console.log('长期订阅授权结果', res)
|
||||
if (Object.values(res).some((v) => v === 'accept')) {
|
||||
uni.showToast({
|
||||
title: '长期订阅开启成功',
|
||||
icon: 'success'
|
||||
})
|
||||
// 可以把状态保存到后端
|
||||
this.saveLongSubscribeStatus(true)
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '未授权订阅',
|
||||
icon: 'none'
|
||||
})
|
||||
this.longSubscribe = false
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error(err)
|
||||
this.longSubscribe = false
|
||||
},
|
||||
})
|
||||
} else {
|
||||
// 取消订阅
|
||||
uni.showToast({
|
||||
title: '已关闭系统消息订阅',
|
||||
icon: 'none'
|
||||
})
|
||||
this.saveLongSubscribeStatus(false)
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
|
||||
saveLongSubscribeStatus(enable) {
|
||||
// TODO: 后端保存用户长期订阅状态
|
||||
console.log('保存长期订阅状态到后端', enable)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.setting-content {
|
||||
padding: 0rpx 20rpx;
|
||||
border-radius: 10rpx;
|
||||
background: #FFFFFF;
|
||||
width: 94%;
|
||||
margin: 20rpx auto;
|
||||
|
||||
&::v-deep .u-cell {
|
||||
color: #222222 !important;
|
||||
}
|
||||
|
||||
&::v-deep .u-cell_title {
|
||||
font-size: 30rpx !important;
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 94%;
|
||||
margin: 30rpx auto;
|
||||
|
||||
&::v-deep .u-btn {
|
||||
border-radius: 10rpx;
|
||||
font-size: 36rpx !important;
|
||||
color: #EA4047 !important;
|
||||
border: 0 !important;
|
||||
}
|
||||
|
||||
&::v-deep .u-hairline-border:after {
|
||||
border: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.version {
|
||||
position: absolute;
|
||||
bottom: 20rpx;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
291
pages-biz/reserve/reserveRecords.vue
Normal file
291
pages-biz/reserve/reserveRecords.vue
Normal file
@@ -0,0 +1,291 @@
|
||||
<template>
|
||||
<view class="reserve-records">
|
||||
|
||||
<!-- 顶部导航栏 -->
|
||||
<customNavbar title="我的预约" :is-transparent="navbarStyle.isTransparent" :bg-color="navbarStyle.bgColor"
|
||||
:text-color="navbarStyle.textColor" :opacity="navbarStyle.opacity"
|
||||
:show-home="true" />
|
||||
<!-- 内容部分 -->
|
||||
<scroll-view
|
||||
scroll-y
|
||||
class="scroll-content"
|
||||
@scrolltolower="loadMore"
|
||||
@refresherrefresh="refresh"
|
||||
:refresher-enabled="true"
|
||||
:refresher-triggered="isRefreshing"
|
||||
>
|
||||
<view v-if="flowList.length > 0" class="record-list">
|
||||
<view
|
||||
v-for="item in flowList"
|
||||
:key="item.id"
|
||||
class="record-card"
|
||||
>
|
||||
<!-- 资产名称和状态 -->
|
||||
<view class="card-header">
|
||||
<text class="asset-name">{{ item.assetName }}</text>
|
||||
<text
|
||||
:class="['record-status', item.status]"
|
||||
>
|
||||
{{ statusText(item.status) }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<!-- 内容部分 -->
|
||||
<view class="card-content">
|
||||
<!-- 预约时间 -->
|
||||
<view class="info-item">
|
||||
<text class="info-label">预约时间:</text>
|
||||
<text class="info-value">{{ formatDate(item.date) }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 地址 -->
|
||||
<view class="info-item">
|
||||
<text class="info-label">地址:</text>
|
||||
<text class="info-value">{{ item.assetAddress }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 管家 -->
|
||||
<view class="info-item">
|
||||
<text class="info-label">管家:</text>
|
||||
<text class="info-value">{{ item.managerName || '未知'}}({{ item.managerPhone || '未知'}})</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<view class="action-buttons">
|
||||
<button
|
||||
class="location-btn"
|
||||
@click="viewLocation(item)"
|
||||
>
|
||||
查看位置
|
||||
</button>
|
||||
<button
|
||||
class="call-btn"
|
||||
@click="callManager(item.managerPhone)"
|
||||
>
|
||||
拨打电话
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<u-loadmore :status="loadStatus" />
|
||||
</view>
|
||||
|
||||
<view v-else class="empty">
|
||||
<u-empty mode="list" text="暂无预约记录" />
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
pageNo: 1,
|
||||
pageSize:10,
|
||||
flowList: [],
|
||||
loadStatus: 'loadmore',
|
||||
isRefreshing: false
|
||||
}
|
||||
},
|
||||
onLoad(options){
|
||||
this.fetchReserve()
|
||||
},
|
||||
onShow() {
|
||||
this.$checkToken(this.$getToken())
|
||||
},
|
||||
methods: {
|
||||
statusText(status) {
|
||||
switch (status) {
|
||||
case 'pending':
|
||||
return '待确认';
|
||||
case 'done':
|
||||
return '已完成';
|
||||
default:
|
||||
return '未知';
|
||||
}
|
||||
},
|
||||
formatDate(dateStr) {
|
||||
// 格式化日期,例如:2025-11-10 周五 09:00
|
||||
if(!dateStr){return '-'}
|
||||
const date = new Date(dateStr);
|
||||
const weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
const weekday = weekdays[date.getDay()];
|
||||
const hours = String(date.getHours()).padStart(2, '0');
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
|
||||
return `${year}-${month}-${day} ${weekday} ${hours}:${minutes}`;
|
||||
},
|
||||
fetchReserve(){
|
||||
if (this.loadStatus !== 'loadmore') return;
|
||||
this.loadStatus = 'loading';
|
||||
let url = '/reservate/queryPage'
|
||||
this.$u.post(url, {
|
||||
pageNo: this.pageNo,
|
||||
pageSize: this.pageSize
|
||||
},{
|
||||
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) {
|
||||
this.loadStatus = 'nomore';
|
||||
} else {
|
||||
this.pageNo++;
|
||||
this.loadStatus = 'loadmore';
|
||||
}
|
||||
}).catch(err => {
|
||||
console.log("获取预约记录失败:", err)
|
||||
this.loadStatus = 'loadmore';
|
||||
})
|
||||
},
|
||||
viewLocation(item) {
|
||||
uni.openLocation({
|
||||
latitude: item.lat,
|
||||
longitude: item.lng,
|
||||
name: item.assetName,
|
||||
address: item.assetAddress
|
||||
});
|
||||
},
|
||||
callManager(phone) {
|
||||
if (!phone) return;
|
||||
uni.makePhoneCall({ phoneNumber: phone });
|
||||
},
|
||||
loadMore() {
|
||||
this.fetchReserve()
|
||||
},
|
||||
refresh() {
|
||||
this.isRefreshing = true;
|
||||
setTimeout(() => {
|
||||
this.isRefreshing = false;
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.reserve-records {
|
||||
min-height: 100vh;
|
||||
padding-top: 175rpx; /* 给导航栏留空间 */
|
||||
background: linear-gradient(0deg, #F3F1ED 43%, #F5E9DB 100%);
|
||||
}
|
||||
|
||||
.scroll-content {
|
||||
height: calc(100vh - -120rpx - 120rpx);
|
||||
}
|
||||
|
||||
.record-list {
|
||||
padding: 30rpx 40rpx;
|
||||
}
|
||||
|
||||
.record-card {
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
padding: 32rpx;
|
||||
margin-bottom: 30rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.asset-name {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
color: #2D2B2C;
|
||||
}
|
||||
|
||||
.record-status {
|
||||
font-size: 26rpx;
|
||||
padding: 10rpx 15rpx;
|
||||
border-radius: 4rpx;
|
||||
&.done {
|
||||
background: #FCE5E0;
|
||||
color: #ED7748;
|
||||
}
|
||||
&.pending {
|
||||
background: #F2F3F7;
|
||||
color: #86868C;
|
||||
}
|
||||
}
|
||||
|
||||
.card-content {
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.info-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
width: 120rpx;
|
||||
flex-shrink: 0;
|
||||
line-height: 44rpx;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
line-height: 44rpx;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 20rpx;
|
||||
margin-top: 32rpx;
|
||||
padding-top: 32rpx;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.location-btn {
|
||||
width: 220rpx;
|
||||
height: 72rpx;
|
||||
background: #fff;
|
||||
color: #2D2B2C;
|
||||
font-size: 28rpx;
|
||||
border-radius: 8rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #222222;
|
||||
}
|
||||
|
||||
.call-btn {
|
||||
width: 220rpx;
|
||||
height: 72rpx;
|
||||
background: #ff3b30;
|
||||
color: #fff;
|
||||
font-size: 28rpx;
|
||||
border-radius: 8rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.empty {
|
||||
margin-top: 200rpx;
|
||||
}
|
||||
</style>
|
||||
142
pages-biz/search/search.vue
Normal file
142
pages-biz/search/search.vue
Normal file
@@ -0,0 +1,142 @@
|
||||
<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)"
|
||||
:focus="true" action-text="取消" @custom="cancelSearch"></u-search>
|
||||
<!-- 搜索记录 -->
|
||||
<template v-if="historyList.length > 0">
|
||||
<view style="position: relative;">
|
||||
<u-card title="搜索记录" :border="false" :head-border-bottom="false" padding="0" title-size="28">
|
||||
<view slot="body">
|
||||
<u-cell-group :border="false">
|
||||
<u-cell-item v-for="(item,index) in historyList" :key="index" :title="item" @click="clickSearchTag(item)" :border-bottom="false" :arrow="false">
|
||||
<!-- <u-icon slot="icon" size="32" name="search"></u-icon> -->
|
||||
</u-cell-item>
|
||||
</u-cell-group>
|
||||
</view>
|
||||
</u-card>
|
||||
<view class="arrow-right" @click="clearHistory">
|
||||
<u-icon name="trash"></u-icon>
|
||||
清除
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
historyList:[],
|
||||
keyword:"",
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
// 加载历史记录
|
||||
let history = uni.getStorageSync('searchHistory')
|
||||
this.historyList = history ? JSON.parse(history) : []
|
||||
},
|
||||
onReady() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
clickSearch(){
|
||||
if(this.keyword === ''){
|
||||
return uni.showToast({
|
||||
title: '关键词不能为空',
|
||||
icon: 'none'
|
||||
});
|
||||
}else{
|
||||
uni.hideKeyboard()
|
||||
this.addHistory()
|
||||
this.$u.route({
|
||||
url: '/pages-biz/search/searchList',
|
||||
params: {
|
||||
keyword: this.keyword
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
// 自定义取消搜索事件
|
||||
cancelSearch(){
|
||||
uni.navigateBack({
|
||||
delta: 1
|
||||
})
|
||||
},
|
||||
clickSearchTag(item){
|
||||
this.keyword = item
|
||||
this.clickSearch()
|
||||
},
|
||||
// 加入搜索记录
|
||||
addHistory(){
|
||||
let index = this.historyList.indexOf(this.keyword)
|
||||
let history = this.historyList;
|
||||
if(index === -1){
|
||||
history.unshift(this.keyword)
|
||||
}else{
|
||||
history.unshift(history.splice(index,1)[0])
|
||||
}
|
||||
uni.setStorageSync('searchHistory',JSON.stringify(history))
|
||||
},
|
||||
clearHistory(){
|
||||
// 清除搜索记录
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '是否清除搜索历史?',
|
||||
cancelText: '取消',
|
||||
confirmText: '确认',
|
||||
success: res => {
|
||||
if(res.confirm){
|
||||
uni.removeStorageSync('searchHistory');
|
||||
this.historyList = []
|
||||
this.keyword = ''
|
||||
this.$mytip.toast('清除成功')
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
page {
|
||||
background: #FFFFFF;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.item-title {
|
||||
font-size: 28rpx;
|
||||
color: $u-main-color;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.item-price {
|
||||
font-weight: normal;
|
||||
font-size: 35rpx;
|
||||
color: $u-type-warning;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.item-desc {
|
||||
font-weight: normal;
|
||||
font-size: 26rpx;
|
||||
color: $u-tips-color;
|
||||
}
|
||||
|
||||
.item-tag {
|
||||
font-size: 24rpx;
|
||||
color: $u-tips-color;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.arrow-right{
|
||||
position: absolute;
|
||||
top: 0rpx;
|
||||
right: 28rpx;
|
||||
font-weight: normal;
|
||||
font-size: 28rpx;
|
||||
color: $u-tips-color;
|
||||
}
|
||||
</style>
|
||||
509
pages-biz/search/searchList.vue
Normal file
509
pages-biz/search/searchList.vue
Normal file
@@ -0,0 +1,509 @@
|
||||
<template>
|
||||
<view class="search-list-page">
|
||||
<!-- 顶部导航栏 -->
|
||||
<!-- <customNavbar title="搜索资产" /> -->
|
||||
<u-navbar title="搜索资产" :autoBack="true" :background="background" title-color="#2D2B2C"
|
||||
back-icon-color="#2D2B2C">
|
||||
</u-navbar>
|
||||
<view class="search-list-content">
|
||||
<!-- 筛选标签栏 -->
|
||||
<view class="filter-dropdown-wrapper">
|
||||
<u-sticky offset-top="0">
|
||||
<view class="sticky" style="width: 100vw;">
|
||||
<filterDropdown :menuTop="0" :filterData="filterData" :defaultSelected="defaultSelected"
|
||||
:updateMenuName="true" @confirm="confirm" dataFormat="Object"></filterDropdown>
|
||||
</view>
|
||||
</u-sticky>
|
||||
</view>
|
||||
<!-- 搜索栏 -->
|
||||
<view class="search-bar-wrapper">
|
||||
<view class="search-bar">
|
||||
<view class="city-select" @click="location()">{{ city ||'宜昌'}} <text class="icon-down"></text>
|
||||
</view>
|
||||
<view class="search-input" @click="search">
|
||||
<text class="icon-search"></text>
|
||||
<input type="text" placeholder="搜索商铺/住房/土地" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 标签组 -->
|
||||
<view class="tag-group">
|
||||
<view class="tag-item" :class="{ active: selectedTags.includes(tag) }"
|
||||
v-for="(tag, index) in tagGroupList" :key="index" @click="onTagClick(tag)">
|
||||
{{ tag }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="u-p-l-10 u-p-r-10 waterfall">
|
||||
<u-waterfall v-model="flowList" ref="uWaterfall">
|
||||
<template v-slot:left="{ leftList }">
|
||||
<view class="demo-warter" v-for="(item, index) in leftList" :key="index">
|
||||
<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-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>
|
||||
</template>
|
||||
<template v-slot:right="{ rightList }">
|
||||
<view class="demo-warter" v-for="(item, index) in rightList" :key="index">
|
||||
<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-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>
|
||||
</template>
|
||||
</u-waterfall>
|
||||
<u-loadmore bg-color="rgb(240, 240, 240)" :status="loadStatus" @loadmore="loadMore"
|
||||
style="height: 80rpx;line-height: 80rpx;"></u-loadmore>
|
||||
<u-back-top :scroll-top="scrollTop" top="1000"></u-back-top>
|
||||
<u-no-network></u-no-network>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import config from "@/common/config.js" // 全局配置文件
|
||||
import searchData from '@/common/utils/searchData.js'; //筛选菜单数据
|
||||
import filterDropdown from '@/components/zy/filterDropdown.vue';
|
||||
export default {
|
||||
components: {
|
||||
filterDropdown
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
keyWord: null,
|
||||
city: null,
|
||||
streetList: [],
|
||||
indexArr: [],
|
||||
valueArr: [],
|
||||
defaultSelected: [],
|
||||
filterData: [],
|
||||
searchData: {},
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
scrollTop: 0,
|
||||
houseList: [],
|
||||
loadStatus: 'loadmore',
|
||||
flowList: [],
|
||||
// 标签组数据和选中状态
|
||||
tagGroupList: ['全套家具', '配套齐全', '优选好房', '生活便利'],
|
||||
selectedTags: [],
|
||||
background: {
|
||||
// 渐变色
|
||||
backgroundImage: 'linear-gradient(-90deg, #F9DED9 0%, #F8DFC0 99%);'
|
||||
},
|
||||
}
|
||||
},
|
||||
onLoad(option) {
|
||||
let type = option.type
|
||||
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) {
|
||||
this.searchData.type = type
|
||||
}
|
||||
if (villageName) {
|
||||
this.searchData.villageName = villageName
|
||||
}
|
||||
this.resetAndLoad()
|
||||
// 获取小区数据
|
||||
this.findVillageList()
|
||||
},
|
||||
onPageScroll(e) {
|
||||
this.scrollTop = e.scrollTop;
|
||||
},
|
||||
// 下拉刷新
|
||||
onPullDownRefresh() {
|
||||
this.resetAndLoad();
|
||||
// 关闭刷新
|
||||
uni.stopPullDownRefresh();
|
||||
},
|
||||
computed: {
|
||||
staticHost() {
|
||||
return this.$config.staticUrl
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 标签点击事件
|
||||
onTagClick(tag) {
|
||||
// 检查标签是否已选中
|
||||
const isSelected = this.selectedTags.includes(tag);
|
||||
if (isSelected) {
|
||||
// 如果已选中,则移除
|
||||
this.selectedTags = this.selectedTags.filter(item => item !== tag);
|
||||
} else {
|
||||
// 如果未选中,则添加
|
||||
this.selectedTags.push(tag);
|
||||
}
|
||||
// 这里可以添加根据标签筛选数据的逻辑
|
||||
console.log('标签点击:', tag);
|
||||
console.log('当前选中的标签:', this.selectedTags);
|
||||
this.resetAndLoad()
|
||||
},
|
||||
location() {
|
||||
this.$u.route({
|
||||
url: '/pages-biz/location/location',
|
||||
})
|
||||
},
|
||||
search() {
|
||||
this.$u.route('/pages-biz/search/search');
|
||||
},
|
||||
resetAndLoad() {
|
||||
this.pageNo = 1;
|
||||
this.flowList = [];
|
||||
this.loadStatus = 'loadmore';
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.$refs.uWaterfall && this.$refs.uWaterfall.clear();
|
||||
this.findHouseList();
|
||||
});
|
||||
},
|
||||
findHouseList(type = null) {
|
||||
if (this.loadStatus !== 'loadmore') return;
|
||||
|
||||
this.loadStatus = 'loading';
|
||||
|
||||
this.$u.post('/assets/queryPage', {
|
||||
assetsStatus:'闲置中',
|
||||
assetsType: type,
|
||||
pageNo: this.pageNo,
|
||||
pageSize: this.pageSize,
|
||||
keyWord: this.keyWord
|
||||
}).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
|
||||
} else {
|
||||
row.coverImgUrl = this.$config.staticUrl + this.defaultImgUrl
|
||||
}
|
||||
})
|
||||
// 追加数据
|
||||
this.flowList = this.flowList.concat(rows);
|
||||
|
||||
// 判断是否还有下一页(核心)
|
||||
if (rows.length < this.pageSize) {
|
||||
this.loadStatus = 'nomore';
|
||||
} else {
|
||||
this.pageNo++; // ✅ 只有这里能 +1
|
||||
this.loadStatus = 'loadmore';
|
||||
}
|
||||
|
||||
}).catch(err => {
|
||||
console.log("获取资产信息失败:", err);
|
||||
this.loadStatus = 'loadmore';
|
||||
}).finally(() => {
|
||||
uni.stopPullDownRefresh();
|
||||
});
|
||||
},
|
||||
loadMore() {
|
||||
// 只有在 loadStatus 为 'loadmore' 时才触发
|
||||
if (this.loadStatus !== 'loadmore') return;
|
||||
// 调用接口获取下一页
|
||||
this.findHouseList();
|
||||
},
|
||||
findStreet() {
|
||||
|
||||
},
|
||||
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;
|
||||
},
|
||||
clickImage(houseId) {
|
||||
this.$u.route({
|
||||
url: '/pages-assets/assets/detail',
|
||||
params: {
|
||||
assetsNo: houseId
|
||||
}
|
||||
})
|
||||
},
|
||||
//接收菜单结果
|
||||
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 decoration = combo[1]
|
||||
let feature = combo[2]
|
||||
this.searchData = {}
|
||||
if (type) {
|
||||
this.searchData.type = type
|
||||
}
|
||||
if (villageName) {
|
||||
this.searchData.villageName = villageName
|
||||
}
|
||||
if (price) {
|
||||
this.searchData.price = price
|
||||
}
|
||||
if (houseNum && houseNum.length > 0) {
|
||||
this.searchData.houseNum = houseNum.toString()
|
||||
}
|
||||
if (decoration && decoration.length > 0) {
|
||||
this.searchData.decoration = decoration.toString()
|
||||
}
|
||||
if (feature && feature.length > 0) {
|
||||
this.searchData.feature = feature.toString()
|
||||
}
|
||||
this.resetAndLoad()
|
||||
},
|
||||
code() {
|
||||
this.$mytip.toast('请咨询技术支持')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.search-list-page {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.search-list-content {
|
||||
// padding-top: 20rpx;
|
||||
background: linear-gradient(-90deg, #F9DED9 0%, #F8DFC0 99%);
|
||||
border-radius: 0rpx;
|
||||
padding-bottom: 20rpx;
|
||||
}
|
||||
|
||||
// 搜索栏样式
|
||||
.search-bar-wrapper {
|
||||
padding: 10rpx 20rpx;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
display: flex;
|
||||
background-color: #fff;
|
||||
align-items: center;
|
||||
border-radius: 10rpx;
|
||||
padding: 20rpx 30rpx;
|
||||
}
|
||||
|
||||
.city-select {
|
||||
margin-right: 20rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.icon-down {
|
||||
display: inline-block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 8rpx solid transparent;
|
||||
border-right: 8rpx solid transparent;
|
||||
border-top: 8rpx solid #666;
|
||||
margin-left: 5rpx;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.icon-search {
|
||||
margin-right: 10rpx;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.search-input input {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
border: none;
|
||||
background: transparent;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.filter-dropdown-wrapper {
|
||||
// padding: 0 20rpx;
|
||||
border-radius: 10rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
// 标签组样式
|
||||
.tag-group {
|
||||
padding: 15rpx 20rpx;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 15rpx;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.tag-item {
|
||||
padding: 5rpx 10rpx;
|
||||
border-radius: 6rpx;
|
||||
font-size: 24rpx;
|
||||
color: #723324;
|
||||
transition: all 0.3s ease;
|
||||
border: 1px solid #DBB39D;
|
||||
}
|
||||
|
||||
.tag-item.active {
|
||||
background-color: #FCF2F3;
|
||||
border: 1px solid #ffffff;
|
||||
}
|
||||
|
||||
.waterfall {
|
||||
padding-top: 20rpx;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.nomore {
|
||||
background-color: $u-bg-color;
|
||||
}
|
||||
|
||||
.search {
|
||||
width: 54px;
|
||||
height: 44px;
|
||||
|
||||
&:active {
|
||||
background-color: $u-bg-color;
|
||||
}
|
||||
}
|
||||
|
||||
.rowClass {
|
||||
border-radius: 8px;
|
||||
background-color: rgb(255, 255, 255);
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
|
||||
.hoverClass {
|
||||
background-color: #E4E7ED;
|
||||
}
|
||||
|
||||
.tabName {
|
||||
font-size: 28rpx;
|
||||
color: $u-main-color;
|
||||
}
|
||||
|
||||
.demo-warter {
|
||||
border-radius: 8px;
|
||||
margin-top: 15rpx;
|
||||
margin-right: 10rpx;
|
||||
margin-left: 10rpx;
|
||||
background-color: #ffffff;
|
||||
padding: 0rpx;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.u-close {
|
||||
position: absolute;
|
||||
top: 20rpx;
|
||||
right: 20rpx;
|
||||
}
|
||||
|
||||
// 图片样式
|
||||
.u-lazy-load {
|
||||
width: 100%;
|
||||
height: 300rpx;
|
||||
}
|
||||
|
||||
.item-cover {
|
||||
font-size: 55rpx;
|
||||
color: $u-type-warning;
|
||||
}
|
||||
|
||||
.item-title {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
padding: 12rpx 15rpx 8rpx 15rpx;
|
||||
line-height: 40rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.item-price {
|
||||
font-size: 32rpx;
|
||||
color: #FF2F31;
|
||||
padding: 0 15rpx 15rpx 15rpx;
|
||||
}
|
||||
|
||||
.item-price text {
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.item-desc {
|
||||
font-weight: normal;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
padding: 0 15rpx 8rpx 15rpx;
|
||||
}
|
||||
|
||||
// 标签样式
|
||||
.item-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 0 15rpx 12rpx 15rpx;
|
||||
gap: 10rpx;
|
||||
}
|
||||
|
||||
.tag {
|
||||
padding: 4rpx 12rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 12rpx;
|
||||
font-size: 22rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.item-tag {
|
||||
font-size: 24rpx;
|
||||
color: $u-tips-color;
|
||||
margin-top: 3px;
|
||||
}
|
||||
</style>
|
||||
240
pages-biz/tool/calculator.vue
Normal file
240
pages-biz/tool/calculator.vue
Normal file
@@ -0,0 +1,240 @@
|
||||
<template>
|
||||
<view class="calculator">
|
||||
<customNavbar title="计算器" :showHome="true" />
|
||||
|
||||
<input class="display" v-model="expression" disabled />
|
||||
|
||||
<view class="keys">
|
||||
<view
|
||||
v-for="item in keys"
|
||||
:key="item"
|
||||
class="key"
|
||||
:class="{
|
||||
operator: operators.includes(item) || item === '%',
|
||||
equal: item === '=',
|
||||
control: ['C','⌫'].includes(item)
|
||||
}"
|
||||
@click="press(item)"
|
||||
@touchstart="onTouchStart(item)"
|
||||
@touchend="onTouchEnd"
|
||||
>
|
||||
{{ item }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
expression: '',
|
||||
timer: null,
|
||||
operators: ['+', '-', '*', '/'],
|
||||
keys: [
|
||||
'7','8','9','/',
|
||||
'4','5','6','*',
|
||||
'1','2','3','-',
|
||||
'0','.','%','+',
|
||||
'C','⌫',
|
||||
'='
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
press(key) {
|
||||
const ops = this.operators
|
||||
|
||||
if (key === 'C') {
|
||||
this.expression = ''
|
||||
return
|
||||
}
|
||||
|
||||
if (key === '⌫') {
|
||||
return
|
||||
}
|
||||
|
||||
if (key === '%') {
|
||||
this.applyPercent()
|
||||
return
|
||||
}
|
||||
|
||||
if (key === '=') {
|
||||
if (!this.expression) return
|
||||
|
||||
while (/[+\-*/.]$/.test(this.expression)) {
|
||||
this.expression = this.expression.slice(0, -1)
|
||||
}
|
||||
|
||||
if (!/^\d+(\.\d+)?([+\-*/]\d+(\.\d+)?)*$/.test(this.expression)) {
|
||||
uni.showToast({ title: '格式错误', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
this.expression = this.calculate(this.expression)
|
||||
return
|
||||
}
|
||||
|
||||
if (ops.includes(key)) {
|
||||
if (!this.expression) return
|
||||
if (ops.includes(this.expression.slice(-1))) {
|
||||
this.expression =
|
||||
this.expression.slice(0, -1) + key
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (key === '.') {
|
||||
const last = this.expression.split(/[+\-*/]/).pop()
|
||||
if (last.includes('.')) return
|
||||
}
|
||||
|
||||
this.expression += key
|
||||
},
|
||||
|
||||
/* 长按删除 */
|
||||
onTouchStart(key) {
|
||||
if (key !== '⌫') return
|
||||
|
||||
this.expression = this.expression.slice(0, -1)
|
||||
|
||||
this.timer = setInterval(() => {
|
||||
if (!this.expression) {
|
||||
clearInterval(this.timer)
|
||||
return
|
||||
}
|
||||
this.expression = this.expression.slice(0, -1)
|
||||
}, 120)
|
||||
},
|
||||
|
||||
onTouchEnd() {
|
||||
clearInterval(this.timer)
|
||||
this.timer = null
|
||||
},
|
||||
|
||||
/* 百分号处理(核心) */
|
||||
applyPercent() {
|
||||
if (!this.expression) return
|
||||
|
||||
const match = this.expression.match(/(\d+(\.\d+)?)([+\-*/])?(\d+(\.\d+)?)?$/)
|
||||
if (!match) return
|
||||
|
||||
const base = parseFloat(match[1])
|
||||
const op = match[3]
|
||||
const value = match[4] ? parseFloat(match[4]) : null
|
||||
|
||||
let percentValue
|
||||
|
||||
if (!op) {
|
||||
percentValue = base / 100
|
||||
this.expression = percentValue.toString()
|
||||
return
|
||||
}
|
||||
|
||||
if (op === '+' || op === '-') {
|
||||
percentValue = base * (value / 100)
|
||||
} else {
|
||||
percentValue = value / 100
|
||||
}
|
||||
|
||||
this.expression =
|
||||
this.expression.slice(0, match.index) +
|
||||
base +
|
||||
op +
|
||||
percentValue
|
||||
},
|
||||
|
||||
/* 计算(无 eval) */
|
||||
calculate(exp) {
|
||||
const nums = exp.split(/[\+\-\*\/]/).map(Number)
|
||||
const ops = exp.match(/[\+\-\*\/]/g) || []
|
||||
|
||||
for (let i = 0; i < ops.length; ) {
|
||||
if (ops[i] === '*' || ops[i] === '/') {
|
||||
const r =
|
||||
ops[i] === '*'
|
||||
? nums[i] * nums[i + 1]
|
||||
: nums[i] / nums[i + 1]
|
||||
|
||||
nums.splice(i, 2, r)
|
||||
ops.splice(i, 1)
|
||||
} else {
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
let result = nums[0]
|
||||
for (let i = 0; i < ops.length; i++) {
|
||||
result =
|
||||
ops[i] === '+'
|
||||
? result + nums[i + 1]
|
||||
: result - nums[i + 1]
|
||||
}
|
||||
|
||||
return Number(result.toFixed(10)).toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.calculator {
|
||||
padding: 30rpx;
|
||||
padding-top: 175rpx;
|
||||
background: #f7f8fa;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.display {
|
||||
height: 120rpx;
|
||||
font-size: 44rpx;
|
||||
font-weight: 600;
|
||||
border-radius: 12rpx;
|
||||
background: #fff;
|
||||
padding: 0 24rpx;
|
||||
margin-bottom: 30rpx;
|
||||
box-shadow: 0 8rpx 20rpx rgba(0,0,0,0.08);
|
||||
}
|
||||
|
||||
.keys {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.key {
|
||||
background: #ffffff;
|
||||
text-align: center;
|
||||
padding: 36rpx 0;
|
||||
font-size: 36rpx;
|
||||
font-weight: 500;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 10rpx 20rpx rgba(0,0,0,0.08);
|
||||
}
|
||||
|
||||
.key:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.operator {
|
||||
background: #ffe8d9;
|
||||
color: #ff7a18;
|
||||
box-shadow: 0 10rpx 20rpx rgba(255,122,24,0.35);
|
||||
font-size: 40rpx;
|
||||
}
|
||||
|
||||
.control {
|
||||
background: #ff4d4f;
|
||||
color: #fff;
|
||||
box-shadow: 0 10rpx 20rpx rgba(255,77,79,0.4);
|
||||
}
|
||||
|
||||
.equal {
|
||||
grid-column: span 4;
|
||||
background: linear-gradient(135deg, #4facfe, #00f2fe);
|
||||
color: #fff;
|
||||
font-size: 44rpx;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 12rpx 28rpx rgba(79,172,254,0.5);
|
||||
}
|
||||
</style>
|
||||
367
pages-biz/unpaid/unpaid.vue
Normal file
367
pages-biz/unpaid/unpaid.vue
Normal file
@@ -0,0 +1,367 @@
|
||||
<template>
|
||||
<view class="pending-bill-page">
|
||||
<!-- 自定义导航栏 -->
|
||||
<custom-navbar title="租金待付" />
|
||||
|
||||
<scroll-view scroll-y class="scroll-content" :refresher-enabled="true" :refresher-triggered="isRefreshing"
|
||||
@refresherrefresh="refresh" @scrolltolower="loadMore">
|
||||
<view v-if="flowList.length > 0">
|
||||
<view class="bill-item" v-for="item in flowList" :key="item.billNo">
|
||||
<!-- 左侧勾选 + 信息 -->
|
||||
<view class="bill-left">
|
||||
<u-checkbox v-model="item.checked" @change="toggleCheck(item)" size="32"
|
||||
active-color="#EA414A" />
|
||||
<view class="bill-info">
|
||||
<text class="bill-name">{{ $ellipsis(item.billName,27) }}</text>
|
||||
<text class="bill-date">缴费截止:<text class="date">{{ item.billPayEndDate }}</text></text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 右侧金额 + 支付按钮 -->
|
||||
<view class="bill-right">
|
||||
<text class="amount">{{ formatMoney(item.billAmount) }}</text>
|
||||
<button class="pay-btn" @click="goPayTest([item])">去支付
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else class="empty">
|
||||
<u-empty mode="list" text="暂无待支付账单" />
|
||||
</view>
|
||||
|
||||
<u-loadmore :status="loadStatus" />
|
||||
</scroll-view>
|
||||
|
||||
<!-- 底部批量支付栏 -->
|
||||
<view v-if="selectedBills.length > 0" class="batch-pay-bar">
|
||||
<view class="batch-pay-bar-left"><text>已选 {{ selectedBills.length }} 项</text></view>
|
||||
<view class="batch-pay-bar-center">
|
||||
合计:<text class="sumAmount">{{ formatMoney(sumAmount) }}</text>
|
||||
</view>
|
||||
<view class="batch-pay-bar-right">
|
||||
<button class="bottomPayBtn" color="linear-gradient(90deg, #007aff, #00aaff)" type="primary"
|
||||
size="small" @click="goPayTest(selectedBills)">
|
||||
批量支付
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
flowList: [],
|
||||
isRefreshing: false,
|
||||
loadStatus: 'loadmore',
|
||||
sumAmount: 0,
|
||||
selectedBills: [], // 勾选的账单集合
|
||||
};
|
||||
},
|
||||
onLoad(options) {
|
||||
this.loadBills();
|
||||
},
|
||||
onShow() {
|
||||
this.$checkToken(this.$getToken())
|
||||
},
|
||||
methods: {
|
||||
loadBills() {
|
||||
if (this.loadStatus !== 'loadmore') return;
|
||||
this.loadStatus = 'loading';
|
||||
const token = this.$getToken()
|
||||
let url = '/bill/pageQueryContractBill'
|
||||
this.$u.post(url, {
|
||||
pageNo: this.pageNo,
|
||||
pageSize: this.pageSize,
|
||||
billStatus: '待缴费'
|
||||
}, {
|
||||
WT: token
|
||||
}).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) {
|
||||
this.loadStatus = 'nomore';
|
||||
} else {
|
||||
this.pageNo++;
|
||||
this.loadStatus = 'loadmore';
|
||||
}
|
||||
}).catch(err => {
|
||||
console.log("获取待付合同账单记录失败:", err)
|
||||
this.loadStatus = 'loadmore';
|
||||
})
|
||||
this.updateSelected();
|
||||
},
|
||||
refresh() {
|
||||
this.isRefreshing = true;
|
||||
setTimeout(() => {
|
||||
this.loadBills();
|
||||
this.isRefreshing = false;
|
||||
}, 1000);
|
||||
},
|
||||
|
||||
loadMore() {
|
||||
this.loadBills();
|
||||
},
|
||||
|
||||
formatMoney(val) {
|
||||
if (val === null || val === undefined || isNaN(val)) return '—';
|
||||
|
||||
val = Number(val);
|
||||
|
||||
if (val >= 10000) {
|
||||
return '¥' + (val / 10000).toFixed(2) + '万';
|
||||
}
|
||||
|
||||
return '¥' + val.toFixed(2);
|
||||
},
|
||||
toggleCheck(item) {
|
||||
item.checked = !item.checked;
|
||||
if (item.checked && !this.selectedBills.some(b => b.id === item.id)) {
|
||||
this.selectedBills.push(item);
|
||||
} else {
|
||||
// 取消勾选时从已选数组移除
|
||||
this.selectedBills = this.selectedBills.filter(b => b.id !== item.id);
|
||||
}
|
||||
let sumFee = 0;
|
||||
this.selectedBills.forEach(b => sumFee += b.amount);
|
||||
this.sumAmount = sumFee;
|
||||
},
|
||||
updateSelected() {
|
||||
if (!Array.isArray(this.bills)) return;
|
||||
this.selectedBills = this.bills.filter(b => b.checked);
|
||||
},
|
||||
goPayTest(billList) {
|
||||
if (!billList || billList.length === 0) return;
|
||||
|
||||
// 模拟后台生成订单返回
|
||||
const order = {
|
||||
timeStamp: String(Date.now()), // 时间戳
|
||||
nonceStr: Math.random().toString(36).substr(2, 15), // 随机字符串
|
||||
package: 'prepay_id=TEST1234567890', // 模拟预支付ID
|
||||
signType: 'MD5',
|
||||
paySign: 'TEST_SIGN', // 模拟签名
|
||||
};
|
||||
|
||||
// 调用微信支付接口(测试)
|
||||
uni.requestPayment({
|
||||
...order,
|
||||
success: () => {
|
||||
uni.showToast({
|
||||
title: '支付成功(测试)',
|
||||
icon: 'success'
|
||||
});
|
||||
// 清空勾选状态
|
||||
billList.forEach(b => b.checked = false);
|
||||
this.updateSelected();
|
||||
// 刷新列表(可选)
|
||||
this.loadBills();
|
||||
},
|
||||
fail: () => {
|
||||
uni.showToast({
|
||||
title: '支付失败(测试)',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
async goPay(billList) {
|
||||
if (!billList || billList.length === 0) return;
|
||||
|
||||
try {
|
||||
// 1. 请求后台生成订单
|
||||
const res = await uni.request({
|
||||
url: 'https://api.example.com/payments/create',
|
||||
method: 'POST',
|
||||
data: {
|
||||
userId: this.userId,
|
||||
bills: billList.map(b => b.id),
|
||||
}
|
||||
});
|
||||
|
||||
if (res[1].data.code !== 0) {
|
||||
uni.showToast({
|
||||
title: res[1].data.msg,
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const order = res[1].data.data; // 后台返回的支付参数
|
||||
|
||||
// 2. 调用微信支付
|
||||
uni.requestPayment({
|
||||
timeStamp: order.timeStamp,
|
||||
nonceStr: order.nonceStr,
|
||||
package: order.package,
|
||||
signType: order.signType,
|
||||
paySign: order.paySign,
|
||||
success: () => {
|
||||
uni.showToast({
|
||||
title: '支付成功',
|
||||
icon: 'success'
|
||||
});
|
||||
// 更新勾选状态
|
||||
billList.forEach(b => b.checked = false);
|
||||
this.updateSelected();
|
||||
// 刷新列表
|
||||
this.loadBills();
|
||||
},
|
||||
fail: () => {
|
||||
uni.showToast({
|
||||
title: '支付失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('支付请求失败', error);
|
||||
uni.showToast({
|
||||
title: '支付请求失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.pending-bill-page {
|
||||
background: #f7f8fa;
|
||||
min-height: 100vh;
|
||||
padding-top: 175rpx;
|
||||
/* 自定义导航栏高度 */
|
||||
}
|
||||
|
||||
.scroll-content {
|
||||
padding: 20rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.bill-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
background: #fff;
|
||||
margin-bottom: 20rpx;
|
||||
border-radius: 12rpx;
|
||||
padding: 20rpx;
|
||||
box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.bill-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.bill-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.bill-name {
|
||||
font-size: 30rpx;
|
||||
color: #2D2B2C;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.bill-date {
|
||||
font-size: 26rpx;
|
||||
color: #86868C;
|
||||
margin-top: 6rpx;
|
||||
|
||||
.date {
|
||||
font-size: 24rpx;
|
||||
color: #2D2B2C;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bill-right {
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
width: 30%;
|
||||
|
||||
.amount {
|
||||
text-align: center;
|
||||
font-size: 30rpx;
|
||||
height: 28rpx;
|
||||
line-height: 28rpx;
|
||||
color: #EF8849;
|
||||
}
|
||||
|
||||
.pay-btn {
|
||||
margin-top: 12rpx;
|
||||
padding: 0 40rpx;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
border-radius: 6rpx; // 圆角
|
||||
font-size: 30rpx;
|
||||
color: #fff;
|
||||
background-color: #F34038;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.btn-text {
|
||||
padding-top: 13rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.batch-pay-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 170rpx;
|
||||
padding: 0 30rpx;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
box-shadow: 0 -4rpx 10rpx rgba(0, 0, 0, 0.08);
|
||||
z-index: 999;
|
||||
|
||||
.batch-pay-bar-left {
|
||||
font-size: 28rpx;
|
||||
color: #86868C;
|
||||
}
|
||||
|
||||
.batch-pay-bar-center {
|
||||
font-size: 28rpx;
|
||||
color: #222222;
|
||||
|
||||
.sumAmount {
|
||||
font-size: 28rpx;
|
||||
color: #ff7f00;
|
||||
}
|
||||
}
|
||||
|
||||
.batch-pay-bar-right {
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
|
||||
.bottomPayBtn {
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
padding: 0 40rpx;
|
||||
border-radius: 6rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
background: #F34038;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.empty {
|
||||
margin-top: 200rpx;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
438
pages-biz/vr/vr.vue
Normal file
438
pages-biz/vr/vr.vue
Normal file
@@ -0,0 +1,438 @@
|
||||
<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>
|
||||
</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>
|
||||
366
pages-biz/wae/wae.vue
Normal file
366
pages-biz/wae/wae.vue
Normal file
@@ -0,0 +1,366 @@
|
||||
<template>
|
||||
<view class="pending-bill-page">
|
||||
<!-- 自定义导航栏 -->
|
||||
<custom-navbar title="水电费待付" />
|
||||
|
||||
<scroll-view scroll-y class="scroll-content" :refresher-enabled="true" :refresher-triggered="isRefreshing"
|
||||
@refresherrefresh="refresh" @scrolltolower="loadMore">
|
||||
<view v-if="bills.length > 0">
|
||||
<view class="bill-item" v-for="item in bills" :key="item.billNo">
|
||||
<!-- 左侧勾选 + 信息 -->
|
||||
<view class="bill-left">
|
||||
<u-checkbox v-model="item.checked" @change="toggleCheck(item)" size="32"
|
||||
active-color="#EA414A" />
|
||||
<view class="bill-info">
|
||||
<text class="bill-name">{{ item.billName }}</text>
|
||||
<text class="bill-date">缴费截止:<text class="date">{{ item.billEndDate }}</text></text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 右侧金额 + 支付按钮 -->
|
||||
<view class="bill-right">
|
||||
<text class="amount">{{ formatMoney(item.billAmount) }}</text>
|
||||
<button class="pay-btn" @click="goPayTest([item])">去支付
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else class="empty">
|
||||
<u-empty mode="list" text="暂无水电费账单" />
|
||||
</view>
|
||||
|
||||
<u-loadmore :status="loadStatus" />
|
||||
</scroll-view>
|
||||
|
||||
<!-- 底部批量支付栏 -->
|
||||
<view v-if="selectedBills.length > 0" class="batch-pay-bar">
|
||||
<view class="batch-pay-bar-left"><text>已选 {{ selectedBills.length }} 项</text></view>
|
||||
<view class="batch-pay-bar-center">
|
||||
合计:<text class="sumAmount">{{ formatMoney(sumAmount) }}</text>
|
||||
</view>
|
||||
<view class="batch-pay-bar-right">
|
||||
<button class="bottomPayBtn" color="linear-gradient(90deg, #007aff, #00aaff)" type="primary"
|
||||
size="small" @click="goPayTest(selectedBills)">
|
||||
批量缴费
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
bills: [],
|
||||
isRefreshing: false,
|
||||
loadStatus: 'loadmore',
|
||||
sumAmount: 0,
|
||||
selectedBills: [], // 勾选的账单集合
|
||||
};
|
||||
},
|
||||
onLoad(options) {
|
||||
this.loadBills();
|
||||
},
|
||||
onShow() {
|
||||
this.$checkToken(this.$getToken())
|
||||
},
|
||||
methods: {
|
||||
loadBills() {
|
||||
if (this.loadStatus !== 'loadmore') return;
|
||||
this.loadStatus = 'loading';
|
||||
const token = this.$getToken();
|
||||
let url = '/bill/pageQueryWaeBill'
|
||||
this.$u.post(url, {
|
||||
pageNo: this.pageNo,
|
||||
pageSize: this.pageSize
|
||||
|
||||
}, {
|
||||
WT: token
|
||||
}).then(
|
||||
result => {
|
||||
const rows = result.data.result || [];
|
||||
|
||||
if (this.pageNo === 1) this.bills = [];
|
||||
|
||||
this.bills = this.bills.concat(rows);
|
||||
|
||||
if (rows.length < this.pageSize) {
|
||||
this.loadStatus = 'nomore';
|
||||
} else {
|
||||
this.pageNo++;
|
||||
this.loadStatus = 'loadmore';
|
||||
}
|
||||
})
|
||||
this.updateSelected();
|
||||
},
|
||||
refresh() {
|
||||
this.isRefreshing = true;
|
||||
setTimeout(() => {
|
||||
this.loadBills();
|
||||
this.isRefreshing = false;
|
||||
}, 1000);
|
||||
},
|
||||
|
||||
loadMore() {
|
||||
this.loadBills()
|
||||
},
|
||||
|
||||
formatMoney(val) {
|
||||
if (val === null || val === undefined || isNaN(val)) return '—';
|
||||
|
||||
val = Number(val);
|
||||
|
||||
if (val >= 10000) {
|
||||
return '¥' + (val / 10000).toFixed(2) + '万';
|
||||
}
|
||||
|
||||
return '¥' + val.toFixed(2);
|
||||
},
|
||||
toggleCheck(item) {
|
||||
item.checked = !item.checked;
|
||||
if (item.checked && !this.selectedBills.some(b => b.id === item.id)) {
|
||||
this.selectedBills.push(item);
|
||||
} else {
|
||||
// 取消勾选时从已选数组移除
|
||||
this.selectedBills = this.selectedBills.filter(b => b.id !== item.id);
|
||||
}
|
||||
let sumFee = 0;
|
||||
this.selectedBills.forEach(b => sumFee += b.amount);
|
||||
this.sumAmount = sumFee;
|
||||
},
|
||||
updateSelected() {
|
||||
if (!Array.isArray(this.bills)) return;
|
||||
this.selectedBills = this.bills.filter(b => b.checked);
|
||||
},
|
||||
goPayTest(billList) {
|
||||
if (!billList || billList.length === 0) return;
|
||||
|
||||
// 模拟后台生成订单返回
|
||||
const order = {
|
||||
timeStamp: String(Date.now()), // 时间戳
|
||||
nonceStr: Math.random().toString(36).substr(2, 15), // 随机字符串
|
||||
package: 'prepay_id=TEST1234567890', // 模拟预支付ID
|
||||
signType: 'MD5',
|
||||
paySign: 'TEST_SIGN', // 模拟签名
|
||||
};
|
||||
|
||||
// 调用微信支付接口(测试)
|
||||
uni.requestPayment({
|
||||
...order,
|
||||
success: () => {
|
||||
uni.showToast({
|
||||
title: '支付成功(测试)',
|
||||
icon: 'success'
|
||||
});
|
||||
// 清空勾选状态
|
||||
billList.forEach(b => b.checked = false);
|
||||
this.updateSelected();
|
||||
// 刷新列表(可选)
|
||||
this.loadBills();
|
||||
},
|
||||
fail: () => {
|
||||
uni.showToast({
|
||||
title: '支付失败(测试)',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
async goPay(billList) {
|
||||
if (!billList || billList.length === 0) return;
|
||||
|
||||
try {
|
||||
// 1. 请求后台生成订单
|
||||
const res = await uni.request({
|
||||
url: 'https://api.example.com/payments/create',
|
||||
method: 'POST',
|
||||
data: {
|
||||
userId: this.userId,
|
||||
bills: billList.map(b => b.id),
|
||||
}
|
||||
});
|
||||
|
||||
if (res[1].data.code !== 0) {
|
||||
uni.showToast({
|
||||
title: res[1].data.msg,
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const order = res[1].data.data; // 后台返回的支付参数
|
||||
|
||||
// 2. 调用微信支付
|
||||
uni.requestPayment({
|
||||
timeStamp: order.timeStamp,
|
||||
nonceStr: order.nonceStr,
|
||||
package: order.package,
|
||||
signType: order.signType,
|
||||
paySign: order.paySign,
|
||||
success: () => {
|
||||
uni.showToast({
|
||||
title: '支付成功',
|
||||
icon: 'success'
|
||||
});
|
||||
// 更新勾选状态
|
||||
billList.forEach(b => b.checked = false);
|
||||
this.updateSelected();
|
||||
// 刷新列表
|
||||
this.loadBills();
|
||||
},
|
||||
fail: () => {
|
||||
uni.showToast({
|
||||
title: '支付失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('支付请求失败', error);
|
||||
uni.showToast({
|
||||
title: '支付请求失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.pending-bill-page {
|
||||
background: #f7f8fa;
|
||||
min-height: 100vh;
|
||||
padding-top: 175rpx;
|
||||
/* 自定义导航栏高度 */
|
||||
}
|
||||
|
||||
.scroll-content {
|
||||
padding: 20rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.bill-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
background: #fff;
|
||||
margin-bottom: 20rpx;
|
||||
border-radius: 12rpx;
|
||||
padding: 20rpx;
|
||||
box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.bill-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.bill-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.bill-name {
|
||||
font-size: 30rpx;
|
||||
color: #2D2B2C;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.bill-date {
|
||||
font-size: 24rpx;
|
||||
color: #86868C;
|
||||
margin-top: 6rpx;
|
||||
|
||||
.date {
|
||||
font-size: 24rpx;
|
||||
color: #2D2B2C;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bill-right {
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
width: 30%;
|
||||
|
||||
.amount {
|
||||
text-align: center;
|
||||
height: 28rpx;
|
||||
line-height: 28rpx;
|
||||
font-size: 30rpx;
|
||||
color: #EF8849;
|
||||
}
|
||||
|
||||
.pay-btn {
|
||||
margin-top: 12rpx;
|
||||
padding: 0 40rpx;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
border-radius: 6rpx; // 圆角
|
||||
font-size: 30rpx;
|
||||
color: #fff;
|
||||
background-color: #F34038;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.btn-text {
|
||||
padding-top: 13rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.batch-pay-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 170rpx;
|
||||
padding: 0 30rpx;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
box-shadow: 0 -4rpx 10rpx rgba(0, 0, 0, 0.08);
|
||||
z-index: 999;
|
||||
|
||||
.batch-pay-bar-left {
|
||||
font-size: 28rpx;
|
||||
color: #86868C;
|
||||
}
|
||||
|
||||
.batch-pay-bar-center {
|
||||
font-size: 28rpx;
|
||||
color: #222222;
|
||||
|
||||
.sumAmount {
|
||||
font-size: 28rpx;
|
||||
color: #ff7f00;
|
||||
}
|
||||
}
|
||||
|
||||
.batch-pay-bar-right {
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
|
||||
.bottomPayBtn {
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
padding: 0 40rpx;
|
||||
border-radius: 6rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
background: #F34038;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.empty {
|
||||
margin-top: 200rpx;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
195
pages-biz/wae/waeRecords.vue
Normal file
195
pages-biz/wae/waeRecords.vue
Normal file
@@ -0,0 +1,195 @@
|
||||
<template>
|
||||
<view class="water-electric-page">
|
||||
<!-- 页面标题栏 -->
|
||||
<customNavbar title="水电费明细" />
|
||||
<!-- 空状态 -->
|
||||
|
||||
|
||||
<!-- 列表部分 -->
|
||||
<!-- 收支记录列表 -->
|
||||
<scroll-view scroll-y class="scroll-content" @scrolltolower="loadMore">
|
||||
<view v-if="flowList.length > 0" class="record-list">
|
||||
<view
|
||||
class="record-item"
|
||||
v-for="(item, index) in flowList"
|
||||
:key="index"
|
||||
@click="toDetail(item)"
|
||||
>
|
||||
<!-- <view class="record-icon">
|
||||
<image src="/static/icon/水费.png" class="icon-1" v-if="item.feeType=='水费'"></image>
|
||||
<image src="/static/icon/电费.png" class="icon-2" v-else></image>
|
||||
</view> -->
|
||||
<!-- 左侧信息 -->
|
||||
<view class="record-left">
|
||||
<text class="record-title">{{ item.itemName }}</text>
|
||||
<text class="record-time">支付时间:{{ item.payDate }}</text>
|
||||
</view>
|
||||
<!-- 右侧金额 + 箭头 -->
|
||||
<view class="record-right">
|
||||
<view class="amount">
|
||||
¥{{ item.itemAmount.toFixed(2) }}
|
||||
<u-icon name="arrow-right" size="20" color="#ccc"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="records.length === 0" class="empty">
|
||||
<image src="/static/empty.png" mode="aspectFit" />
|
||||
<text>暂明细记录</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
pageNo: 1,
|
||||
pageSize:10,
|
||||
flowList: [],
|
||||
loadStatus: 'loadmore'
|
||||
};
|
||||
},
|
||||
onShow() {
|
||||
this.$checkToken(this.$getToken())
|
||||
},
|
||||
onLoad() {
|
||||
this.fetchDataList();
|
||||
},
|
||||
methods: {
|
||||
fetchDataList(){
|
||||
if (this.loadStatus !== 'loadmore') return;
|
||||
this.loadStatus = 'loading';
|
||||
let url = '/bill/pageQueryPayRecord'
|
||||
this.$u.post(url, {
|
||||
pageNo: this.pageNo,
|
||||
pageSize: this.pageSize,
|
||||
recordType:'wae'
|
||||
},{
|
||||
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) {
|
||||
this.loadStatus = 'nomore';
|
||||
} else {
|
||||
this.pageNo++;
|
||||
this.loadStatus = 'loadmore';
|
||||
}
|
||||
}).catch(err => {
|
||||
console.log("获取水电费账单缴费记录失败:", err)
|
||||
this.loadStatus = 'loadmore';
|
||||
})
|
||||
},
|
||||
loadMore() {
|
||||
// 只有在 loadStatus 为 'loadmore' 时才触发
|
||||
if (this.loadStatus !== 'loadmore') return;
|
||||
// 调用接口获取下一页
|
||||
this.fetchDataList();
|
||||
},
|
||||
toDetail(item) {
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.water-electric-page {
|
||||
padding: 175rpx 20rpx 20rpx 20rpx;
|
||||
|
||||
.header {
|
||||
background-color: #fff;
|
||||
padding: 30rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
|
||||
text-align: center;
|
||||
|
||||
.title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.record-list {
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.record-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
margin-bottom: 20rpx;
|
||||
padding: 24rpx 30rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
|
||||
|
||||
.record-icon{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
.icon-1{
|
||||
width: 33rpx;
|
||||
height: 47rpx;
|
||||
}
|
||||
|
||||
.icon-2{
|
||||
width: 35rpx;
|
||||
height: 57rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.record-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10rpx;
|
||||
|
||||
.record-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
color: #2D2B2C;
|
||||
}
|
||||
|
||||
.record-time {
|
||||
font-size: 24rpx;
|
||||
color: #86868C;
|
||||
}
|
||||
}
|
||||
|
||||
.record-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 30rpx;
|
||||
font-weight: 400;
|
||||
color: #222222;
|
||||
|
||||
.amount {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.empty {
|
||||
margin-top: 200rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
color: #aaa;
|
||||
font-size: 28rpx;
|
||||
|
||||
image {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
72
pages-biz/webview/webview.vue
Normal file
72
pages-biz/webview/webview.vue
Normal file
@@ -0,0 +1,72 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<web-view v-if="src" :src="src" bindmessage="handleGetMessage"></web-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
src: '' // WebView URL
|
||||
}
|
||||
},
|
||||
|
||||
// 页面加载
|
||||
onLoad(e) {
|
||||
let url = e.url || ''
|
||||
try {
|
||||
url = decodeURIComponent(url)
|
||||
} catch (err) {
|
||||
console.warn('decode error', url)
|
||||
}
|
||||
console.log("签署链接:" + url)
|
||||
// 强制刷新 WebView
|
||||
this.src = ''
|
||||
this.$nextTick(() => {
|
||||
this.src = url
|
||||
console.log('webview src set:', this.src)
|
||||
})
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* 刷脸完成后重新加载页面
|
||||
*/
|
||||
reloadPage(redirectUrl) {
|
||||
console.log('---webview reloadPage', redirectUrl)
|
||||
this.src = redirectUrl || this.src
|
||||
},
|
||||
|
||||
/**
|
||||
* 消息通知回调处理
|
||||
* H5 / App / 小程序都可以调用
|
||||
*/
|
||||
handleGetMessage(e) {
|
||||
console.log('handleGetMessage', e)
|
||||
const data = e.detail?.data?.[0]
|
||||
if (data && data.result === 'success') {
|
||||
// 跳转中转页
|
||||
// #ifdef MP-WEIXIN
|
||||
wx.navigateBack()
|
||||
// #endif
|
||||
|
||||
// #ifdef H5
|
||||
if (window.history.length > 1) {
|
||||
window.history.go(-delta)
|
||||
} else {
|
||||
// 没有上一页就跳首页
|
||||
this.$router.replace('/')
|
||||
}
|
||||
return
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
// 这里根据你 APP 路由跳转方法
|
||||
// plus.webview.open('/redirect/bizpage')
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user