feat: 新增社区动态和消息页面及相关功能
5
.env
|
|
@ -5,7 +5,8 @@ SHOPRO_VERSION=v2.4.1
|
|||
SHOPRO_BASE_URL=http://api-dashboard.yudao.iocoder.cn
|
||||
|
||||
# 后端接口 - 测试环境(通过 process.env.NODE_ENV = development)
|
||||
SHOPRO_DEV_BASE_URL=http://127.0.0.1:48080
|
||||
#SHOPRO_DEV_BASE_URL=http://127.0.0.1:48080
|
||||
SHOPRO_DEV_BASE_URL=http://1.12.53.43:9999
|
||||
### SHOPRO_DEV_BASE_URL=http://10.171.1.188:48080
|
||||
### SHOPRO_DEV_BASE_URL = http://yunai.natapp1.cc
|
||||
|
||||
|
|
@ -32,4 +33,4 @@ SHOPRO_H5_URL=http://127.0.0.1:3000
|
|||
SHOPRO_MPLIVE_ON=0
|
||||
|
||||
# 租户ID 默认 1
|
||||
SHOPRO_TENANT_ID=1
|
||||
SHOPRO_TENANT_ID=162
|
||||
|
|
|
|||
25
pages.json
|
|
@ -116,6 +116,18 @@
|
|||
"group": "物业管理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/index/switch-house",
|
||||
"style": {
|
||||
"navigationBarTitleText": ""
|
||||
},
|
||||
"meta": {
|
||||
"auth": false,
|
||||
"sync": true,
|
||||
"title": "切换房号",
|
||||
"group": "物业管理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/index/auth-form",
|
||||
"style": {
|
||||
|
|
@ -636,7 +648,18 @@
|
|||
"group": "物业管理"
|
||||
}
|
||||
},
|
||||
{
|
||||
{
|
||||
"path": "income/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "收益公示"
|
||||
},
|
||||
"meta": {
|
||||
"sync": true,
|
||||
"title": "收益公示",
|
||||
"group": "物业管理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "knowledge/classroom",
|
||||
"style": {
|
||||
"navigationBarTitleText": "知识课堂"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,160 @@
|
|||
<!-- 消息中心 -->
|
||||
<template>
|
||||
<s-layout title="消息" navbar="inner">
|
||||
<view class="msg-page">
|
||||
<!-- 消息列表 -->
|
||||
<scroll-view scroll-y class="msg-scroll">
|
||||
<view class="msg-list">
|
||||
<view
|
||||
v-for="(item, index) in msgList"
|
||||
:key="item.id"
|
||||
class="msg-item"
|
||||
@tap="handleMsgTap(item)"
|
||||
>
|
||||
<view class="msg-avatar">
|
||||
<image :src="item.avatar" mode="aspectFill" />
|
||||
<view v-if="item.unread" class="unread-dot"></view>
|
||||
</view>
|
||||
<view class="msg-content">
|
||||
<view class="msg-header">
|
||||
<text class="msg-name">{{ item.name }}</text>
|
||||
<text class="msg-time">{{ item.time }}</text>
|
||||
</view>
|
||||
<view class="msg-body">
|
||||
<text class="msg-text">{{ item.content }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 空状态(无数据时显示) -->
|
||||
<view v-if="msgList.length === 0" class="empty-state">
|
||||
<text class="empty-text">暂无消息</text>
|
||||
</view>
|
||||
</view>
|
||||
</s-layout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
|
||||
// 模拟消息数据
|
||||
const msgList = ref([
|
||||
{ id: 1, name: '系统通知', avatar: '/static/img/login_img.png', content: '您的佣金已到账', time: '10:30', unread: true },
|
||||
{ id: 2, name: '推广助手', avatar: '/static/img/login_img.png', content: '您有新的团队成员加入', time: '昨天', unread: false },
|
||||
{ id: 3, name: '订单通知', avatar: '/static/img/login_img.png', content: '您推广的商品已成功下单', time: '01-08', unread: false }
|
||||
]);
|
||||
|
||||
// 点击消息
|
||||
function handleMsgTap(item) {
|
||||
console.log('查看消息:', item);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.msg-page {
|
||||
min-height: 100vh;
|
||||
background-color: #F5F5F5;
|
||||
}
|
||||
|
||||
/* 滚动区域 */
|
||||
.msg-scroll {
|
||||
height: calc(100vh - 88rpx);
|
||||
}
|
||||
|
||||
/* 消息列表 */
|
||||
.msg-list {
|
||||
padding: 16rpx 24rpx;
|
||||
}
|
||||
|
||||
/* 单条消息 */
|
||||
.msg-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
background-color: #FFFFFF;
|
||||
border-radius: 16rpx;
|
||||
|
||||
/* 头像 */
|
||||
.msg-avatar {
|
||||
position: relative;
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
|
||||
image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* 未读红点 */
|
||||
.unread-dot {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 18rpx;
|
||||
height: 18rpx;
|
||||
background-color: #FA7E49;
|
||||
border-radius: 50%;
|
||||
border: 4rpx solid #FFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
/* 内容 */
|
||||
.msg-content {
|
||||
flex: 1;
|
||||
margin-left: 20rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
/* 头部:名称 + 时间 */
|
||||
.msg-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12rpx;
|
||||
|
||||
.msg-name {
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.msg-time {
|
||||
font-size: 24rpx;
|
||||
color: #999999;
|
||||
flex-shrink: 0;
|
||||
margin-left: 16rpx;
|
||||
}
|
||||
}
|
||||
|
||||
/* 消息内容 */
|
||||
.msg-body {
|
||||
.msg-text {
|
||||
font-size: 26rpx;
|
||||
color: #888888;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-top: 200rpx;
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,290 @@
|
|||
<!-- 社区动态页面 -->
|
||||
<template>
|
||||
<s-layout title="社区动态" :bgStyle="{ backgroundColor:'#F8EDE8' }" color="#333333">
|
||||
|
||||
<view class="community-dynamics-page">
|
||||
<!-- Tab 切换栏 -->
|
||||
<view class="tab-bar">
|
||||
<view
|
||||
class="tab-item"
|
||||
v-for="(tab, index) in tabList"
|
||||
:key="index"
|
||||
:class="{ active: currentTab === index }"
|
||||
@tap="switchTab(index)"
|
||||
>
|
||||
<text class="tab-text">{{ tab }}</text>
|
||||
<view class="tab-indicator" v-if="currentTab === index"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 动态列表 - 独立滚动区域 -->
|
||||
<scroll-view class="dynamics-list" scroll-y :style="{ height: scrollViewHeight + 'px' }">
|
||||
<view class="list-inner">
|
||||
<view
|
||||
class="dynamics-item"
|
||||
v-for="(item, index) in dynamicsList"
|
||||
:key="index"
|
||||
@tap="goDetail(item)"
|
||||
>
|
||||
<!-- 封面图 -->
|
||||
<image class="item-cover" :src="item.cover" mode="aspectFill" />
|
||||
|
||||
<!-- 内容区 -->
|
||||
<view class="item-content">
|
||||
<text class="item-title">{{ item.title }}</text>
|
||||
<view class="item-footer">
|
||||
<text class="item-views">{{ item.views }}</text>
|
||||
<text class="item-date">{{ item.date }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-state" v-if="dynamicsList.length === 0">
|
||||
<text class="empty-text">暂无数据</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</s-layout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, nextTick, getCurrentInstance } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
|
||||
// 列表滚动区域高度(单位:px),通过JS计算后绑定到scroll-view
|
||||
const scrollViewHeight = ref(0);
|
||||
|
||||
// Tab 列表
|
||||
const tabList = ref(['全部', '物业', '业委会', '社区']);
|
||||
const currentTab = ref(0);
|
||||
|
||||
// 动态列表
|
||||
const dynamicsList = ref([
|
||||
{
|
||||
id: 1,
|
||||
title: '社区动态标题xxxx操作手册--如何邀请访客',
|
||||
cover: '/static/img/guest.png',
|
||||
views: '1.2万播放',
|
||||
date: '2021/02/21'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: '社区动态标题xxxx操作手册--如何邀请访客',
|
||||
cover: '/static/img/guest.png',
|
||||
views: '1.2万播放',
|
||||
date: '2021/02/21'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: '社区动态标题xxxx操作手册--如何邀请访客',
|
||||
cover: '/static/img/guest.png',
|
||||
views: '1.2万播放',
|
||||
date: '2021/02/21'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: '社区动态标题xxxx操作手册--如何邀请访客',
|
||||
cover: '/static/img/guest.png',
|
||||
views: '1.2万播放',
|
||||
date: '2021/02/21'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
title: '社区动态标题xxxx操作手册--如何邀请访客',
|
||||
cover: '/static/img/guest.png',
|
||||
views: '1.2万播放',
|
||||
date: '2021/02/21'
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
title: '社区动态标题xxxx操作手册--如何邀请访客',
|
||||
cover: '/static/img/guest.png',
|
||||
views: '1.2万播放',
|
||||
date: '2021/02/21'
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
title: '社区动态标题xxxx操作手册--如何邀请访客',
|
||||
cover: '/static/img/guest.png',
|
||||
views: '1.2万播放',
|
||||
date: '2021/02/21'
|
||||
}
|
||||
]);
|
||||
|
||||
// 页面加载
|
||||
onLoad((options) => {
|
||||
if (options.tab) {
|
||||
currentTab.value = parseInt(options.tab);
|
||||
}
|
||||
loadDynamicsList();
|
||||
});
|
||||
|
||||
// 页面渲染完成后,计算scroll-view的精确高度
|
||||
onMounted(() => {
|
||||
const instance = getCurrentInstance();
|
||||
nextTick(() => {
|
||||
const sysInfo = uni.getSystemInfoSync();
|
||||
// 获取tab栏的实际渲染高度
|
||||
uni.createSelectorQuery()
|
||||
.in(instance)
|
||||
.select('.tab-bar')
|
||||
.boundingClientRect((rect) => {
|
||||
if (rect) {
|
||||
// scroll-view高度 = 屏幕可用高度 - tab栏高度 - tab栏距离顶部的高度
|
||||
scrollViewHeight.value = sysInfo.windowHeight - rect.height - rect.top;
|
||||
}
|
||||
})
|
||||
.exec();
|
||||
});
|
||||
});
|
||||
|
||||
// 切换 Tab
|
||||
function switchTab(index) {
|
||||
currentTab.value = index;
|
||||
loadDynamicsList();
|
||||
}
|
||||
|
||||
// 加载动态列表
|
||||
async function loadDynamicsList() {
|
||||
// TODO: 调用API获取动态列表
|
||||
}
|
||||
|
||||
// 跳转详情
|
||||
function goDetail(item) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/community/dynamics-detail?id=${item.id}`
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* 页面容器 - 伪元素渐变向上覆盖到导航栏 */
|
||||
.community-dynamics-page {
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
// content: '';
|
||||
// position: absolute;
|
||||
// left: 0;
|
||||
// right: 0;
|
||||
// height: calc(100% + 176rpx);
|
||||
background: linear-gradient(180deg, #F8EDE8 0%, #FFFFFF 30%);
|
||||
// z-index: -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Tab 切换栏 */
|
||||
.tab-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
padding: 36rpx 50rpx;
|
||||
// background-color: #FFFFFF;
|
||||
|
||||
.tab-item {
|
||||
position: relative;
|
||||
padding: 8rpx 16rpx;
|
||||
|
||||
.tab-text {
|
||||
font-size: 30rpx;
|
||||
color: #999999;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
&.active .tab-text {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.tab-indicator {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 48rpx;
|
||||
height: 6rpx;
|
||||
background: linear-gradient(90deg, #FF7F69 0%, #FC5A5D 100%);
|
||||
border-radius: 3rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 动态列表 - scroll-view,高度由JS动态绑定 */
|
||||
.dynamics-list {
|
||||
/* 高度由 :style="{ height: scrollViewHeight + 'px' }" 控制 */
|
||||
}
|
||||
|
||||
/* 列表内容包裹层 - 负责padding间距 */
|
||||
.list-inner {
|
||||
padding: 24rpx 32rpx;
|
||||
}
|
||||
|
||||
/* 动态项 */
|
||||
.dynamics-item {
|
||||
display: flex;
|
||||
background-color: #FFFFFF;
|
||||
border-radius: 31rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 19rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
|
||||
|
||||
.item-cover {
|
||||
width: 305rpx;
|
||||
height: 168rpx;
|
||||
border-radius: 19rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.item-content {
|
||||
flex: 1;
|
||||
margin-left: 24rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
|
||||
.item-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
color: #333333;
|
||||
line-height: 1.5;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.item-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.item-views {
|
||||
font-size: 24rpx;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.item-date {
|
||||
font-size: 24rpx;
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 200rpx 0;
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -6,26 +6,26 @@
|
|||
<!-- 小区名称 -->
|
||||
<view class="form-item">
|
||||
<text class="form-label">小区名称</text>
|
||||
<view class="form-value">
|
||||
<text class="value-text">融侨馨苑</text>
|
||||
<view class="form-value" @tap="showCommunityPicker">
|
||||
<text class="value-text">{{ state.form.communityName || '请选择小区' }}</text>
|
||||
<image class="arrow-icon" src="/static/img/right-icon-black.png" mode="aspectFit" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 序号:楼号/单元/号 -->
|
||||
<view class="form-item form-row">
|
||||
<view class="form-item form-row" @tap="showHousePicker">
|
||||
<text class="form-label">序号</text>
|
||||
<view class="row-inputs">
|
||||
<view class="col-item">
|
||||
<input class="col-input" v-model="state.form.building" />
|
||||
<input class="col-input" v-model="state.form.buildingNo" placeholder="请选择" disabled />
|
||||
<text class="col-label">号楼</text>
|
||||
</view>
|
||||
<view class="col-item">
|
||||
<input class="col-input" v-model="state.form.unit" />
|
||||
<input class="col-input" v-model="state.form.unitNo" placeholder="请选择" disabled />
|
||||
<text class="col-label">单元</text>
|
||||
</view>
|
||||
<view class="col-item">
|
||||
<input class="col-input" v-model="state.form.room" />
|
||||
<input class="col-input" v-model="state.form.roomNo" placeholder="请选择" disabled />
|
||||
<text class="col-label">号</text>
|
||||
</view>
|
||||
</view>
|
||||
|
|
@ -54,7 +54,7 @@
|
|||
<view class="form-item">
|
||||
<text class="form-label">与产权人关系</text>
|
||||
<view class="form-value" @tap="showRelationPicker">
|
||||
<text class="value-text placeholder-color">{{ state.form.relation || '请输入' }}</text>
|
||||
<text class="value-text placeholder-color">{{ state.form.relationType || '请输入' }}</text>
|
||||
<image class="arrow-icon" src="/static/img/right-icon-black.png" mode="aspectFit" />
|
||||
</view>
|
||||
</view>
|
||||
|
|
@ -79,7 +79,7 @@
|
|||
<text class="form-label">手机号</text>
|
||||
<view class="form-value">
|
||||
<input
|
||||
v-model="state.form.phone"
|
||||
v-model="state.form.mobile"
|
||||
class="form-input"
|
||||
type="number"
|
||||
maxlength="11"
|
||||
|
|
@ -119,7 +119,7 @@
|
|||
<view class="form-item">
|
||||
<text class="form-label">性别</text>
|
||||
<view class="form-value" @tap="showGenderPicker">
|
||||
<text class="value-text placeholder-color">{{ state.form.gender || '请选择' }}</text>
|
||||
<text class="value-text placeholder-color">{{ state.form.sex === 1 ? '男' : state.form.sex === 2 ? '女' : '请选择' }}</text>
|
||||
<image class="arrow-icon" src="/static/img/right-icon-black.png" mode="aspectFit" />
|
||||
</view>
|
||||
</view>
|
||||
|
|
@ -145,13 +145,32 @@
|
|||
<text>上传附件</text>
|
||||
</view>
|
||||
<view class="upload-area">
|
||||
<view class="upload-btn" @tap="handleUpload">
|
||||
<view v-if="state.attachmentUrl" class="upload-preview" @tap="handleUploadAttachment">
|
||||
<image class="preview-img" :src="state.attachmentUrl" mode="aspectFill" />
|
||||
</view>
|
||||
<view v-else class="upload-btn" @tap="handleUploadAttachment">
|
||||
<view class="upload-icon-wrapper">
|
||||
<text class="upload-folder-icon">📁</text>
|
||||
</view>
|
||||
<text class="upload-text">选择文件</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 上传人脸照片 -->
|
||||
<view class="form-section-title">
|
||||
<text>上传人脸照片</text>
|
||||
</view>
|
||||
<view class="upload-area">
|
||||
<view v-if="state.facePhotoUrl" class="upload-preview" @tap="handleUploadFace">
|
||||
<image class="preview-img" :src="state.facePhotoUrl" mode="aspectFill" />
|
||||
</view>
|
||||
<view v-else class="upload-btn" @tap="handleUploadFace">
|
||||
<view class="upload-icon-wrapper">
|
||||
<text class="upload-folder-icon">📁</text>
|
||||
</view>
|
||||
<text class="upload-text">选择照片</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
|
|
@ -163,33 +182,152 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import { reactive, ref, computed } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import MemberHouseApi from '@/sheep/api/community/memberHouse';
|
||||
import CommunityApi from '@/sheep/api/community/community';
|
||||
import FileApi from '@/sheep/api/infra/file';
|
||||
import { tenantId } from '@/sheep/config';
|
||||
import sheep from '@/sheep';
|
||||
|
||||
// 用户信息
|
||||
const userStore = sheep.$store('user');
|
||||
const userInfo = computed(() => userStore.userInfo || {});
|
||||
|
||||
// 表单数据
|
||||
const state = reactive({
|
||||
form: {
|
||||
community: '融侨馨苑',
|
||||
building: '',
|
||||
unit: '',
|
||||
room: '',
|
||||
communityId: null, // 选中的小区ID
|
||||
communityName: '',
|
||||
houseId: null, // 选中的房屋ID
|
||||
buildingNo: '',
|
||||
unitNo: '',
|
||||
roomNo: '',
|
||||
isOwner: true,
|
||||
relation: '',
|
||||
relationType: '',
|
||||
name: '',
|
||||
phone: '',
|
||||
mobile: '',
|
||||
idType: '',
|
||||
idNumber: '',
|
||||
gender: '',
|
||||
sex: '',
|
||||
birthday: ''
|
||||
},
|
||||
communityList: [], // 小区列表
|
||||
houseTree: [], // 房屋树(楼号->单元->房号)
|
||||
attachmentUrl: '', // 附件URL
|
||||
facePhotoUrl: '', // 人脸照片URL
|
||||
});
|
||||
|
||||
onLoad(async () => {
|
||||
// 获取小区列表
|
||||
const { code, data } = await CommunityApi.getSimpleList();
|
||||
if (code === 0 && data && data.length > 0) {
|
||||
state.communityList = data;
|
||||
// 优先匹配 SHOPRO_TENANT_ID 配置的小区,没有则选第一个
|
||||
const target = data.find((item) => String(item.id) === String(tenantId)) || data[0];
|
||||
state.form.communityId = target.id;
|
||||
state.form.communityName = target.communityName;
|
||||
// 加载该小区的房屋树
|
||||
await loadHouseTree(target.id);
|
||||
} else {
|
||||
// 兜底:使用当前用户信息里的小区
|
||||
state.form.communityName = userInfo.value.currentCommunityName || '请选择小区';
|
||||
}
|
||||
});
|
||||
|
||||
// 加载房屋树
|
||||
const loadHouseTree = async (communityId) => {
|
||||
if (!communityId) return;
|
||||
const { code, data } = await CommunityApi.getHouseTree(communityId);
|
||||
if (code === 0) {
|
||||
state.houseTree = data || [];
|
||||
}
|
||||
};
|
||||
|
||||
// 显示小区选择器
|
||||
const showCommunityPicker = () => {
|
||||
if (!state.communityList.length) return;
|
||||
const itemList = state.communityList.map((item) => item.communityName);
|
||||
uni.showActionSheet({
|
||||
itemList,
|
||||
success: async (res) => {
|
||||
const selected = state.communityList[res.tapIndex];
|
||||
state.form.communityId = selected.id;
|
||||
state.form.communityName = selected.communityName;
|
||||
// 切换小区后加载对应房屋树
|
||||
await loadHouseTree(selected.id);
|
||||
// 清空已选房屋
|
||||
state.form.houseId = null;
|
||||
state.form.buildingNo = '';
|
||||
state.form.unitNo = '';
|
||||
state.form.roomNo = '';
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 级联选择房屋(楼号 -> 单元 -> 房号)
|
||||
const showHousePicker = () => {
|
||||
if (!state.houseTree.length) {
|
||||
uni.showToast({ title: '该小区暂无房屋数据', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
// 第一级:选择楼号
|
||||
const buildings = state.houseTree;
|
||||
const buildingLabels = buildings.map((item) => item.label);
|
||||
uni.showActionSheet({
|
||||
itemList: buildingLabels,
|
||||
success: (buildingRes) => {
|
||||
const building = buildings[buildingRes.tapIndex];
|
||||
|
||||
// 第二级:选择单元
|
||||
const units = building.children || [];
|
||||
if (!units.length) {
|
||||
state.form.houseId = building.value;
|
||||
state.form.buildingNo = building.label;
|
||||
state.form.unitNo = '';
|
||||
state.form.roomNo = '';
|
||||
return;
|
||||
}
|
||||
const unitLabels = units.map((item) => item.label);
|
||||
uni.showActionSheet({
|
||||
itemList: unitLabels,
|
||||
success: (unitRes) => {
|
||||
const unit = units[unitRes.tapIndex];
|
||||
|
||||
// 第三级:选择房号
|
||||
const rooms = unit.children || [];
|
||||
if (!rooms.length) {
|
||||
state.form.houseId = unit.value;
|
||||
state.form.buildingNo = building.label;
|
||||
state.form.unitNo = unit.label;
|
||||
state.form.roomNo = '';
|
||||
return;
|
||||
}
|
||||
const roomLabels = rooms.map((item) => item.label);
|
||||
uni.showActionSheet({
|
||||
itemList: roomLabels,
|
||||
success: (roomRes) => {
|
||||
const room = rooms[roomRes.tapIndex];
|
||||
state.form.houseId = room.value;
|
||||
state.form.buildingNo = building.label;
|
||||
state.form.unitNo = unit.label;
|
||||
state.form.roomNo = room.label;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 显示关系选择器
|
||||
const showRelationPicker = () => {
|
||||
uni.showActionSheet({
|
||||
itemList: ['本人', '配偶', '父母', '子女', '其他'],
|
||||
success: (res) => {
|
||||
const relations = ['本人', '配偶', '父母', '子女', '其他'];
|
||||
state.form.relation = relations[res.tapIndex];
|
||||
state.form.relationType = relations[res.tapIndex];
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
@ -210,7 +348,7 @@ const showGenderPicker = () => {
|
|||
uni.showActionSheet({
|
||||
itemList: ['男', '女'],
|
||||
success: (res) => {
|
||||
state.form.gender = res.tapIndex === 0 ? '男' : '女';
|
||||
state.form.sex = res.tapIndex === 0 ? 1 : 2;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
@ -224,7 +362,6 @@ const onDateChange = (e) => {
|
|||
const datePickerRef = ref(null);
|
||||
const triggerDatePicker = () => {
|
||||
if (datePickerRef.value) {
|
||||
// uni-datetime-picker 内部有 show 方法可以打开弹窗
|
||||
const picker = datePickerRef.value.$children?.[0] || datePickerRef.value;
|
||||
if (picker.show) {
|
||||
picker.show();
|
||||
|
|
@ -232,25 +369,79 @@ const triggerDatePicker = () => {
|
|||
}
|
||||
};
|
||||
|
||||
// 上传附件
|
||||
const handleUpload = () => {
|
||||
uni.chooseMessageFile({
|
||||
// 上传图片通用方法
|
||||
const uploadImage = (field) => {
|
||||
uni.chooseImage({
|
||||
count: 1,
|
||||
type: 'file',
|
||||
success: (res) => {
|
||||
console.log('选择文件:', res.tempFiles[0]);
|
||||
sourceType: ['album', 'camera'],
|
||||
success: async (res) => {
|
||||
const filePath = res.tempFilePaths[0];
|
||||
const result = await FileApi.uploadFile(filePath, 'member-house');
|
||||
if (result && result.data) {
|
||||
state[field] = result.data;
|
||||
uni.showToast({ title: '上传成功', icon: 'success' });
|
||||
} else {
|
||||
uni.showToast({ title: '上传失败', icon: 'none' });
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 上传附件
|
||||
const handleUploadAttachment = () => uploadImage('attachmentUrl');
|
||||
// 上传人脸照片
|
||||
const handleUploadFace = () => uploadImage('facePhotoUrl');
|
||||
|
||||
// 表单校验
|
||||
const validateForm = () => {
|
||||
const f = state.form;
|
||||
if (!f.communityName) return '请选择小区';
|
||||
if (!f.buildingNo) return '请输入楼号';
|
||||
if (!f.unitNo) return '请输入单元号';
|
||||
if (!f.roomNo) return '请输入门牌号';
|
||||
if (f.isOwner === null) return '请选择是否产权人';
|
||||
if (!f.isOwner && !f.relationType) return '请选择与产权人关系';
|
||||
if (!f.name) return '请输入姓名';
|
||||
if (!f.mobile) return '请输入手机号';
|
||||
if (!/^1[3-9]\d{9}$/.test(f.mobile)) return '手机号格式不正确';
|
||||
if (!f.idType) return '请选择证件类型';
|
||||
if (!f.idNumber) return '请输入证件号';
|
||||
if (!f.sex) return '请选择性别';
|
||||
if (!f.birthday) return '请选择出生日期';
|
||||
return '';
|
||||
};
|
||||
|
||||
// 提交表单
|
||||
const handleSubmit = () => {
|
||||
console.log('提交表单:', state.form);
|
||||
|
||||
// 跳转到提交成功页
|
||||
uni.redirectTo({
|
||||
url: '/pages/index/auth-success'
|
||||
});
|
||||
const handleSubmit = async () => {
|
||||
const errMsg = validateForm();
|
||||
if (errMsg) {
|
||||
uni.showToast({ title: errMsg, icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
const params = {
|
||||
communityId: state.form.communityId || userInfo.value.currentCommunityId || '',
|
||||
communityName: state.form.communityName,
|
||||
houseId: state.form.houseId || null,
|
||||
buildingNo: state.form.buildingNo,
|
||||
unitNo: state.form.unitNo,
|
||||
roomNo: state.form.roomNo,
|
||||
isOwner: state.form.isOwner,
|
||||
relationType: state.form.relationType,
|
||||
name: state.form.name,
|
||||
mobile: state.form.mobile,
|
||||
idType: state.form.idType,
|
||||
idNumber: state.form.idNumber,
|
||||
sex: state.form.sex,
|
||||
birthday: state.form.birthday,
|
||||
attachmentUrl: state.attachmentUrl,
|
||||
facePhotoUrl: state.facePhotoUrl,
|
||||
};
|
||||
|
||||
const { code } = await MemberHouseApi.create(params);
|
||||
if (code === 0) {
|
||||
uni.redirectTo({ url: '/pages/index/auth-success' });
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
@ -513,6 +704,18 @@ const handleSubmit = () => {
|
|||
color: #999999;
|
||||
}
|
||||
}
|
||||
|
||||
.upload-preview {
|
||||
width: 180rpx;
|
||||
height: 180rpx;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
|
||||
.preview-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 底部按钮 */
|
||||
|
|
|
|||
|
|
@ -246,13 +246,6 @@
|
|||
cart.delete(state.selectedIds);
|
||||
}
|
||||
|
||||
function getCartList() {
|
||||
cart.getList();
|
||||
}
|
||||
|
||||
onShow(() => {
|
||||
getCartList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@
|
|||
</view>
|
||||
<view class="house-info">
|
||||
<view class="house-name-row">
|
||||
<text class="house-name">{{ item.name }}</text>
|
||||
<text :class="['status-tag', `status-${item.status}`]">{{ getStatusText(item.status) }}</text>
|
||||
<text class="house-name">{{ item.communityName }}</text>
|
||||
<text :class="['status-tag', getStatusClass(item.status)]">{{ getStatusText(item.status) }}</text>
|
||||
</view>
|
||||
<text class="house-address">{{ item.address }}</text>
|
||||
</view>
|
||||
|
|
@ -31,17 +31,17 @@
|
|||
|
||||
<!-- 操作区(已认证/已驳回显示) -->
|
||||
<view
|
||||
v-if="item.status !== 'pending'"
|
||||
v-if="item.status !== 0"
|
||||
class="house-action"
|
||||
@tap.stop="handleItemTap(item)"
|
||||
>
|
||||
<view class="action-left">
|
||||
<image
|
||||
class="action-icon"
|
||||
:src="item.status === 'approved' ? '/static/img/yz-icon1.png' : '/static/img/yz-icon2.png'"
|
||||
:src="item.status === 1 ? '/static/img/yz-icon1.png' : '/static/img/yz-icon2.png'"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
<text class="action-text">{{ item.status === 'approved' ? '查看关联人员' : '查看原因' }}</text>
|
||||
<text class="action-text">{{ item.status === 1 ? '查看关联人员' : '查看原因' }}</text>
|
||||
</view>
|
||||
<image class="action-arrow-icon" src="/static/img/right-icon.png" mode="aspectFit" />
|
||||
</view>
|
||||
|
|
@ -82,52 +82,57 @@
|
|||
|
||||
<script setup>
|
||||
import { reactive } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import MemberHouseApi from '@/sheep/api/community/memberHouse';
|
||||
|
||||
// 页面状态
|
||||
const state = reactive({
|
||||
houseList: [
|
||||
{
|
||||
id: 1,
|
||||
name: '融侨馨苑',
|
||||
address: '12号楼1单元701',
|
||||
status: 'pending', // pending待审核, approved已认证, rejected已驳回
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '融侨馨苑',
|
||||
address: '12号楼1单元701',
|
||||
status: 'approved',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '融侨馨苑',
|
||||
address: '12号楼1单元701',
|
||||
status: 'rejected',
|
||||
rejectReason: '因房产合同照片模糊,该认证信息不予通过',
|
||||
},
|
||||
],
|
||||
houseList: [],
|
||||
showRejectModal: false,
|
||||
rejectReason: '',
|
||||
});
|
||||
|
||||
// 状态映射(0-待审核, 1-已认证, 2-驳回)
|
||||
const STATUS_MAP = {
|
||||
0: { text: '待审核', class: 'status-pending' },
|
||||
1: { text: '已认证', class: 'status-approved' },
|
||||
2: { text: '已驳回', class: 'status-rejected' },
|
||||
};
|
||||
|
||||
// 获取状态文字
|
||||
const getStatusText = (status) => {
|
||||
const map = {
|
||||
pending: '待审核',
|
||||
approved: '已认证',
|
||||
rejected: '已驳回'
|
||||
};
|
||||
return map[status] || '未知';
|
||||
return STATUS_MAP[status]?.text || '未知';
|
||||
};
|
||||
|
||||
// 获取状态样式类
|
||||
const getStatusClass = (status) => {
|
||||
return STATUS_MAP[status]?.class || '';
|
||||
};
|
||||
|
||||
// 获取我的房屋认证列表
|
||||
const fetchHouseList = async () => {
|
||||
const { code, data } = await MemberHouseApi.getMyList();
|
||||
if (code === 0) {
|
||||
state.houseList = (data || []).map((item) => ({
|
||||
...item,
|
||||
// 拼接完整地址
|
||||
address: `${item.buildingNo}${item.unitNo}${item.roomNo}`,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
onLoad(() => {
|
||||
fetchHouseList();
|
||||
});
|
||||
|
||||
// 点击房屋项
|
||||
const handleItemTap = (item) => {
|
||||
if (item.status === 'approved') {
|
||||
if (item.status === 1) {
|
||||
// 已认证 - 查看关联人员
|
||||
uni.navigateTo({
|
||||
url: '/pages/index/related-members'
|
||||
});
|
||||
} else if (item.status === 'rejected') {
|
||||
} else if (item.status === 2) {
|
||||
// 已驳回 - 显示驳回原因弹窗
|
||||
state.rejectReason = item.rejectReason || '认证信息不完整,请重新提交';
|
||||
state.showRejectModal = true;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,6 @@
|
|||
<template>
|
||||
<s-layout title="访客管理" navbar="custom">
|
||||
<s-layout title="访客管理" >
|
||||
<view class="guest-page">
|
||||
<!-- 自定义导航栏 -->
|
||||
<view class="navbar">
|
||||
<view class="navbar-content">
|
||||
<text class="navbar-title">访客管理</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态内容区 -->
|
||||
<view class="empty-state">
|
||||
|
|
@ -38,6 +32,7 @@
|
|||
min-height: 100vh;
|
||||
background: linear-gradient(180deg, #FFF5F0 0%, #FFFFFF 30%);
|
||||
padding-bottom: 120rpx;
|
||||
overflow:hidden;
|
||||
}
|
||||
|
||||
/* 导航栏 */
|
||||
|
|
@ -72,7 +67,7 @@
|
|||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 200rpx;
|
||||
padding-top: 200rpx;
|
||||
|
||||
/* 空状态图标 */
|
||||
.empty-icon {
|
||||
|
|
|
|||
|
|
@ -6,10 +6,15 @@
|
|||
|
||||
<!-- 顶部小区选择器 -->
|
||||
<view class="community-selector">
|
||||
<text class="community-name" @tap="goLogin">融侨馨苑</text>
|
||||
<text class="community-name" @tap="showCommunityPicker">{{ communityName }}</text>
|
||||
<text class="arrow-down">▼</text>
|
||||
|
||||
<text class="not-auth" @tap="goCheck">未认证!请点击认证房屋!</text>
|
||||
<!-- 未认证 -->
|
||||
<text v-if="!isHouseAuth" class="not-auth" @tap="goCheck">未认证!请点击认证房屋!</text>
|
||||
<!-- 已认证但未选择房屋 -->
|
||||
<text v-else-if="!hasSelectedHouse" class="not-auth" @tap="goCheck">请选择房屋</text>
|
||||
<!-- 已认证且已选择房屋 -->
|
||||
<text v-else class="house-address" @tap="goCheck">{{ houseAddress }}</text>
|
||||
</view>
|
||||
|
||||
<!-- Banner卡片 -->
|
||||
|
|
@ -103,12 +108,43 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import { ref, onMounted, computed } from 'vue';
|
||||
import { onLoad, onShow } from '@dcloudio/uni-app';
|
||||
import MemberHouseApi from '@/sheep/api/community/memberHouse';
|
||||
import sheep from '@/sheep';
|
||||
|
||||
// 状态栏高度
|
||||
const statusBarHeight = ref(0);
|
||||
|
||||
// 用户信息
|
||||
const userStore = sheep.$store('user');
|
||||
const userInfo = computed(() => userStore.userInfo || {});
|
||||
|
||||
// 小区房屋树
|
||||
const communityTree = ref([]);
|
||||
// 当前选中的小区名称(优先下拉选择,其次用户信息)
|
||||
const selectedCommunityName = ref('');
|
||||
|
||||
// 当前小区名称
|
||||
const communityName = computed(() => {
|
||||
return selectedCommunityName.value || userInfo.value.currentCommunityName || '请选择小区';
|
||||
});
|
||||
|
||||
// 是否已完成房屋认证(根据 currentHouseId 判断)
|
||||
const isHouseAuth = computed(() => {
|
||||
return !!userInfo.value.currentHouseId;
|
||||
});
|
||||
|
||||
// 是否已选择房屋(currentHouseId 不为空)
|
||||
const hasSelectedHouse = computed(() => {
|
||||
return !!userInfo.value.currentHouseId;
|
||||
});
|
||||
|
||||
// 当前房屋地址
|
||||
const houseAddress = computed(() => {
|
||||
return userInfo.value.currentHouseAddress || '';
|
||||
});
|
||||
|
||||
// Banner轮播数据
|
||||
const bannerList = ref([
|
||||
{
|
||||
|
|
@ -127,7 +163,7 @@ const bannerList = ref([
|
|||
const functionList = ref([
|
||||
{ label: '工作计划', icon: '/static/img/Group_1.png', bgGradient: 'linear-gradient(35deg, #FF7F69 0%, #FC5A5D 100%)', path: '/pages/sub/community/dynamics' },
|
||||
{ label: '业主投票', icon: '/static/img/Group_2.png', bgGradient: 'linear-gradient(35deg, #52C41A 0%, #36AD1A 100%)', path: '/pages/sub/community/daily' },
|
||||
{ label: '收益公示', icon: '/static/img/Group_3.png', bgGradient: 'linear-gradient(35deg, #FFA940 0%, #FA8C16 100%)', path: '' },
|
||||
{ label: '收益公示', icon: '/static/img/Group_3.png', bgGradient: 'linear-gradient(35deg, #FFA940 0%, #FA8C16 100%)', path: '/pages/sub/income/index' },
|
||||
{ label: '在线缴费', icon: '/static/img/Group_4.png', bgGradient: 'linear-gradient(35deg, #4096FF 0%, #1890FF 100%)', path: '' },
|
||||
{ label: '报事报修', icon: '/static/img/Group_5.png', bgGradient: 'linear-gradient(35deg, #69B1FF 0%, #4096FF 100%)', path: '' },
|
||||
{ label: '物业人员', icon: '/static/img/Group_6.png', bgGradient: 'linear-gradient(35deg, #9254DE 0%, #722ED1 100%)', path: '/pages/sub/staff/index' },
|
||||
|
|
@ -139,8 +175,68 @@ onLoad(() => {
|
|||
// 获取状态栏高度
|
||||
const systemInfo = uni.getSystemInfoSync();
|
||||
statusBarHeight.value = systemInfo.statusBarHeight || 0;
|
||||
// 加载小区房屋树
|
||||
fetchCommunityTree();
|
||||
});
|
||||
|
||||
onShow(() => {
|
||||
// 每次进入首页刷新用户信息,确保小区房屋信息最新
|
||||
if (userStore.isLogin) {
|
||||
userStore.getInfo();
|
||||
}
|
||||
});
|
||||
|
||||
// 获取小区房屋树
|
||||
const fetchCommunityTree = async () => {
|
||||
const { code, data } = await MemberHouseApi.getCommunityTree();
|
||||
if (code === 0 && data && data.length > 0) {
|
||||
communityTree.value = data;
|
||||
// 默认展示第一个小区名称
|
||||
selectedCommunityName.value = data[0].communityName;
|
||||
}
|
||||
};
|
||||
|
||||
// 级联选择小区和房屋
|
||||
const showCommunityPicker = () => {
|
||||
if (!communityTree.value.length) {
|
||||
uni.showToast({ title: '暂无小区数据', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
// 第一级:选择小区
|
||||
const communityNames = communityTree.value.map((item) => item.communityName);
|
||||
uni.showActionSheet({
|
||||
itemList: communityNames,
|
||||
success: (communityRes) => {
|
||||
const selectedCommunity = communityTree.value[communityRes.tapIndex];
|
||||
selectedCommunityName.value = selectedCommunity.communityName;
|
||||
|
||||
// 第二级:选择房屋
|
||||
const houses = selectedCommunity.houses || [];
|
||||
if (houses.length > 0) {
|
||||
const houseNames = houses.map((item) => item.fullAddress || `${item.buildingNo}${item.unitNo}${item.roomNo}`);
|
||||
uni.showActionSheet({
|
||||
itemList: houseNames,
|
||||
success: async (houseRes) => {
|
||||
const selectedHouse = houses[houseRes.tapIndex];
|
||||
// 调用切换房屋接口
|
||||
const switchRes = await MemberHouseApi.switchHouse({
|
||||
memberHouseId: selectedHouse.memberHouseId,
|
||||
houseId: selectedHouse.houseId,
|
||||
communityId: selectedCommunity.communityId,
|
||||
});
|
||||
if (switchRes.code === 0) {
|
||||
// 切换成功后刷新用户信息
|
||||
await userStore.getInfo();
|
||||
uni.showToast({ title: '切换成功', icon: 'success' });
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 功能入口点击
|
||||
const handleFunctionTap = (item) => {
|
||||
if (item.path) {
|
||||
|
|
@ -161,7 +257,13 @@ const goLogin = () => {
|
|||
};
|
||||
|
||||
const goCheck = () => {
|
||||
uni.navigateTo({ url: '/pages/index/check-page' });
|
||||
if (!isHouseAuth.value) {
|
||||
// 未认证/未选择房屋,跳转认证表单
|
||||
uni.navigateTo({ url: '/pages/index/check-page' });
|
||||
} else {
|
||||
// 已认证且已选择房屋,跳转切换房号页面
|
||||
uni.navigateTo({ url: '/pages/index/switch-house' });
|
||||
}
|
||||
};
|
||||
|
||||
// 法律小课堂
|
||||
|
|
@ -223,6 +325,18 @@ const goActivityDetail = () => {
|
|||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 24rpx;
|
||||
font-family: 'PingFang SC', sans-serif;
|
||||
}
|
||||
|
||||
.house-address {
|
||||
margin-left: 30rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 24rpx;
|
||||
color: #666666;
|
||||
font-family: 'PingFang SC', sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -57,9 +57,15 @@
|
|||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部微信登录 -->
|
||||
<view class="wechat-login" @tap="wechatLogin">
|
||||
<image class="wechat-icon" src="/static/img/wx-icon.png" mode="aspectFit"></image>
|
||||
<!-- 底部微信一键登录 -->
|
||||
<view class="wechat-login">
|
||||
<button
|
||||
class="phone-btn"
|
||||
open-type="getPhoneNumber"
|
||||
@getphonenumber="onMobileLogin"
|
||||
>
|
||||
<image class="wechat-icon" src="/static/img/wx-icon.png" mode="aspectFit"></image>
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
|
@ -185,25 +191,31 @@ const handleLogin = async () => {
|
|||
}
|
||||
};
|
||||
|
||||
// 微信登录
|
||||
const wechatLogin = async () => {
|
||||
// 微信手机号一键登录
|
||||
const onMobileLogin = async (e) => {
|
||||
if (!agreed.value) {
|
||||
uni.showToast({ title: '请先同意隐私政策和用户协议', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
// 微信小程序环境下使用微信登录
|
||||
if (sheep.$platform.name === 'WechatMiniProgram') {
|
||||
const result = await sheep.$platform.useProvider('wechat').login();
|
||||
if (result) {
|
||||
await sheep.$store('user').getInfo();
|
||||
uni.navigateBack();
|
||||
} else {
|
||||
uni.showToast({ title: '微信登录失败', icon: 'none' });
|
||||
}
|
||||
} else {
|
||||
uni.showToast({ title: '当前环境不支持微信登录', icon: 'none' });
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
if (e.detail.errMsg !== 'getPhoneNumber:ok') {
|
||||
uni.showToast({ title: '手机号授权失败', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await sheep.$platform.useProvider('wechat').mobileLogin(e.detail);
|
||||
if (result) {
|
||||
await sheep.$store('user').getInfo();
|
||||
uni.navigateBack();
|
||||
} else {
|
||||
uni.showToast({ title: '登录失败', icon: 'none' });
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifndef MP-WEIXIN
|
||||
uni.showToast({ title: '当前环境不支持微信登录', icon: 'none' });
|
||||
// #endif
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
@ -356,10 +368,28 @@ const wechatLogin = async () => {
|
|||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
padding-bottom: 80rpx;
|
||||
|
||||
.wechat-icon {
|
||||
|
||||
.phone-btn {
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 1;
|
||||
|
||||
&::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.wechat-icon {
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,260 @@
|
|||
<!-- 切换房号页面 -->
|
||||
<template>
|
||||
<s-layout title="切换房号">
|
||||
<view class="switch-house-page">
|
||||
<!-- 小区名称 -->
|
||||
<view class="section-label">小区名称</view>
|
||||
<picker
|
||||
mode="selector"
|
||||
:range="state.communityList"
|
||||
range-key="name"
|
||||
:value="state.communityIndex"
|
||||
@change="onCommunityChange"
|
||||
>
|
||||
<view class="card community-card">
|
||||
<text class="community-name">{{ state.currentCommunity }}</text>
|
||||
<!-- 下拉箭头 SVG -->
|
||||
<svg class="arrow-icon" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 9L12 15L18 9" stroke="#999999" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</view>
|
||||
</picker>
|
||||
|
||||
<!-- 房号 -->
|
||||
<view class="section-label">房号</view>
|
||||
<scroll-view class="house-scroll" scroll-y :style="{ height: scrollHeight + 'px' }">
|
||||
<view class="card house-card">
|
||||
<view
|
||||
v-for="(item, index) in state.houseList"
|
||||
:key="index"
|
||||
class="house-item"
|
||||
:class="{ active: state.selectedHouseId === item.id }"
|
||||
@tap="selectHouse(item)"
|
||||
>
|
||||
<!-- 单选按钮 -->
|
||||
<radio
|
||||
class="house-radio"
|
||||
:checked="state.selectedHouseId === item.id"
|
||||
color="#FF7B54"
|
||||
/>
|
||||
<text class="house-name">{{ item.name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 底部去认证 -->
|
||||
<view class="bottom-link-wrapper">
|
||||
<text class="auth-link" @tap="goAddHouse">房屋不存在,去认证</text>
|
||||
</view>
|
||||
</view>
|
||||
</s-layout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import MemberHouseApi from '@/sheep/api/community/memberHouse';
|
||||
import sheep from '@/sheep';
|
||||
|
||||
// scroll-view 动态高度
|
||||
const scrollHeight = ref(0);
|
||||
|
||||
// 页面状态
|
||||
const state = reactive({
|
||||
// 小区房屋树(原始数据)
|
||||
communityTree: [],
|
||||
// 小区列表(用于picker)
|
||||
communityList: [],
|
||||
// 当前选中的小区索引
|
||||
communityIndex: 0,
|
||||
// 当前小区名称
|
||||
currentCommunity: '',
|
||||
// 房号列表
|
||||
houseList: [],
|
||||
// 当前选中房号ID(对应 memberHouseId)
|
||||
selectedHouseId: '',
|
||||
});
|
||||
|
||||
// 获取小区房屋树
|
||||
const fetchCommunityTree = async () => {
|
||||
const { code, data } = await MemberHouseApi.getCommunityTree();
|
||||
if (code === 0 && data && data.length > 0) {
|
||||
state.communityTree = data;
|
||||
state.communityList = data.map((item) => ({
|
||||
id: item.communityId,
|
||||
name: item.communityName,
|
||||
}));
|
||||
// 默认选中第一个小区
|
||||
state.communityIndex = 0;
|
||||
state.currentCommunity = data[0].communityName;
|
||||
updateHouseListByIndex(0);
|
||||
}
|
||||
};
|
||||
|
||||
// 根据小区索引更新房屋列表
|
||||
const updateHouseListByIndex = (index) => {
|
||||
const community = state.communityTree[index];
|
||||
if (!community) return;
|
||||
const houses = community.houses || [];
|
||||
state.houseList = houses.map((item) => ({
|
||||
id: String(item.memberHouseId),
|
||||
name: item.fullAddress || `${item.buildingNo}${item.unitNo}${item.roomNo}`,
|
||||
// 保留原始字段,切换接口需要
|
||||
memberHouseId: item.memberHouseId,
|
||||
houseId: item.houseId,
|
||||
communityId: community.communityId,
|
||||
}));
|
||||
// 默认选中第一个房屋
|
||||
state.selectedHouseId = state.houseList[0]?.id || '';
|
||||
};
|
||||
|
||||
onLoad(() => {
|
||||
// 计算 scroll-view 可用高度(屏幕高度 - 状态栏 - 导航栏 - 小区区域高度 - 底部链接高度 - 安全区)
|
||||
const sys = uni.getSystemInfoSync();
|
||||
const safeBottom = sys.safeAreaInsets?.bottom || 0;
|
||||
// 估算:状态栏+导航栏约 88px,小区区域约 140px,底部链接约 60px
|
||||
scrollHeight.value = sys.windowHeight - 88 - 140 - 60 - safeBottom;
|
||||
fetchCommunityTree();
|
||||
});
|
||||
|
||||
// 选择房号并切换
|
||||
const selectHouse = async (item) => {
|
||||
state.selectedHouseId = item.id;
|
||||
const switchRes = await MemberHouseApi.switchHouse({
|
||||
memberHouseId: item.memberHouseId,
|
||||
houseId: item.houseId,
|
||||
communityId: item.communityId,
|
||||
});
|
||||
if (switchRes.code === 0) {
|
||||
// 切换成功后刷新用户信息
|
||||
await sheep.$store('user').getInfo();
|
||||
uni.showToast({ title: '已切换至 ' + item.name, icon: 'none' });
|
||||
}
|
||||
};
|
||||
|
||||
// 切换小区
|
||||
const onCommunityChange = (e) => {
|
||||
const index = e.detail.value;
|
||||
state.communityIndex = index;
|
||||
state.currentCommunity = state.communityList[index].name;
|
||||
updateHouseListByIndex(index);
|
||||
};
|
||||
|
||||
// 去认证房屋
|
||||
const goAddHouse = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/index/auth-form',
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* 页面容器 */
|
||||
.switch-house-page {
|
||||
height: 100%;
|
||||
background-color: #F5F5F5;
|
||||
padding: 24rpx 0 calc(40rpx + env(safe-area-inset-bottom));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 房号滚动区域 */
|
||||
.house-scroll {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 区块标签 */
|
||||
.section-label {
|
||||
font-size: 28rpx;
|
||||
font-weight: 400;
|
||||
color: #666666;
|
||||
margin: 24rpx 38rpx 16rpx;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* 白色卡片通用样式 */
|
||||
.card {
|
||||
background-color: #FFFFFF;
|
||||
border-radius: 24rpx;
|
||||
margin: 0 38rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
/* 小区选择卡片 */
|
||||
.community-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 30rpx 32rpx;
|
||||
|
||||
.community-name {
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.arrow-icon {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 房号卡片 */
|
||||
.house-card {
|
||||
padding: 8rpx 32rpx;
|
||||
|
||||
.house-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 28rpx 0;
|
||||
border-bottom: 1rpx solid #F0F0F0;
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&:active {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.house-radio {
|
||||
margin-right: 20rpx;
|
||||
flex-shrink: 0;
|
||||
transform: scale(0.85);
|
||||
}
|
||||
|
||||
.house-name {
|
||||
font-size: 30rpx;
|
||||
font-weight: 400;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
&.active .house-name {
|
||||
color: #FF7B54;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 底部认证链接 */
|
||||
.bottom-link-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: auto;
|
||||
padding: 40rpx 0 calc(40rpx + env(safe-area-inset-bottom));
|
||||
|
||||
.auth-link {
|
||||
font-size: 28rpx;
|
||||
font-weight: 400;
|
||||
color: #FF7B54;
|
||||
text-decoration: underline;
|
||||
line-height: 1.4;
|
||||
|
||||
&:active {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -95,6 +95,7 @@
|
|||
import { ref, onMounted } from 'vue';
|
||||
import { onShow } from '@dcloudio/uni-app';
|
||||
import sheep from '@/sheep';
|
||||
import AuthUtil from '@/sheep/api/member/auth';
|
||||
|
||||
// 用户信息
|
||||
const userInfo = ref({
|
||||
|
|
@ -131,10 +132,15 @@ const handleLogout = () => {
|
|||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要退出登录吗?',
|
||||
success: (res) => {
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
sheep.$store('user').logout();
|
||||
sheep.$router.go('/pages/index/login-page');
|
||||
const { code } = await AuthUtil.logout();
|
||||
if (code === 0) {
|
||||
sheep.$store('user').logout();
|
||||
sheep.$router.go('/pages/index/login-page');
|
||||
} else {
|
||||
uni.showToast({ title: '退出登录失败', icon: 'none' });
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,411 @@
|
|||
<!-- 通知公告详情页 -->
|
||||
<template>
|
||||
<s-layout title="通知公告" navbar="inner" color="#333333">
|
||||
<!-- 渐变背景 -->
|
||||
<view class="gradient-bg"></view>
|
||||
|
||||
<!-- 固定头部区域 -->
|
||||
<view class="detail-header">
|
||||
<view class="header-inner">
|
||||
<!-- 文章标题 -->
|
||||
<view class="article-title">
|
||||
<text class="title-text">{{ noticeInfo.title }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 发布信息 -->
|
||||
<view class="publish-info">
|
||||
<view class="publisher">
|
||||
<image class="publisher-avatar" src="/static/img/login_img.png" mode="aspectFill" />
|
||||
<text class="publisher-name">{{ noticeInfo.publisher }}</text>
|
||||
</view>
|
||||
<text class="publish-date">{{ noticeInfo.publishDate }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 分割线 -->
|
||||
<view class="divider"></view>
|
||||
</view>
|
||||
</view>paddi n g n
|
||||
|
||||
<!-- 富文本内容 - 独立滚动区域 -->
|
||||
<scroll-view class="article-scroll" scroll-y :style="{ height: contentScrollHeight + 'px', top: contentScrollTop + 'px' }">
|
||||
<view class="article-content">
|
||||
<view class="article-inner">
|
||||
<mp-html :content="noticeInfo.content"></mp-html>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 占位,防止内容被底部附件遮挡 -->
|
||||
<view class="bottom-placeholder" v-if="noticeInfo.attachments && noticeInfo.attachments.length > 0"></view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 附件列表 - 固定在底部 -->
|
||||
<view class="attachment-section" v-if="noticeInfo.attachments && noticeInfo.attachments.length > 0">
|
||||
<view class="attachment-inner">
|
||||
<view
|
||||
class="attachment-item"
|
||||
v-for="(item, index) in noticeInfo.attachments"
|
||||
:key="index"
|
||||
@tap="downloadAttachment(item)"
|
||||
>
|
||||
<view class="attachment-left">
|
||||
<image class="attachment-icon" :src="getAttachmentIcon(item.type)" mode="aspectFit" />
|
||||
<text class="attachment-name">{{ item.name }}</text>
|
||||
</view>
|
||||
<image class="download-icon" src="/static/img/right-icon.png" mode="aspectFit" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</s-layout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, nextTick, getCurrentInstance } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import sheep from '@/sheep';
|
||||
|
||||
// 富文本滚动区域高度(单位:px)
|
||||
const contentScrollHeight = ref(0);
|
||||
// 富文本滚动区域距离顶部的距离(单位:px)
|
||||
const contentScrollTop = ref(0);
|
||||
|
||||
// 公告信息
|
||||
const noticeInfo = ref({
|
||||
title: '',
|
||||
publisher: '',
|
||||
publishDate: '',
|
||||
content: '',
|
||||
attachments: []
|
||||
});
|
||||
|
||||
// 页面加载
|
||||
onLoad((options) => {
|
||||
if (options.id) {
|
||||
loadNoticeDetail(options.id);
|
||||
}
|
||||
});
|
||||
|
||||
// 渲染完成后计算scroll-view高度和位置
|
||||
onMounted(() => {
|
||||
const instance = getCurrentInstance();
|
||||
nextTick(() => {
|
||||
const sysInfo = uni.getSystemInfoSync();
|
||||
const query = uni.createSelectorQuery().in(instance);
|
||||
|
||||
// 获取头部高度和位置
|
||||
query.select('.detail-header').boundingClientRect();
|
||||
// 获取附件区域高度(如果存在)
|
||||
query.select('.attachment-section').boundingClientRect();
|
||||
|
||||
query.exec((res) => {
|
||||
const headerRect = res[0];
|
||||
const attachmentRect = res[1];
|
||||
|
||||
let attachmentHeight = 0;
|
||||
if (attachmentRect && attachmentRect.height > 0) {
|
||||
attachmentHeight = attachmentRect.height;
|
||||
}
|
||||
|
||||
if (headerRect) {
|
||||
// scroll-view距离顶部 = 头部高度 + 头部距离顶部距离(已包含状态栏和导航栏高度)
|
||||
contentScrollTop.value = headerRect.height + headerRect.top;
|
||||
// scroll-view高度 = 屏幕可用高度 - scroll-view距离顶部 - 附件高度
|
||||
contentScrollHeight.value = sysInfo.windowHeight - contentScrollTop.value - attachmentHeight;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// 加载公告详情
|
||||
async function loadNoticeDetail(id) {
|
||||
// TODO: 调用API获取公告详情
|
||||
// const { code, data } = await NoticeApi.getNoticeDetail(id);
|
||||
// if (code === 0) {
|
||||
// noticeInfo.value = data;
|
||||
// }
|
||||
|
||||
// 模拟数据
|
||||
noticeInfo.value = {
|
||||
title: '关于召开业主大会讨论物业费价格调整的通知',
|
||||
publisher: 'x小区物业',
|
||||
publishDate: '2026-01-20',
|
||||
content: `<p>各位业主:</p>
|
||||
<p>为进一步提升本小区物业服务质量,保障小区公共设施设备的正常运维、环境卫生整治、安保服务升级等工作有序开展,切实维护全体业主的共同利益,根据《物业管理条例》《业主大会和业主委员会指导规则》及本小区《管理规约》相关规定,经业主委员会研究决定,召开业主大会,专门讨论本小区物业费价格调整相关事宜。现将具体事项通知如下:</p>
|
||||
<p>一、会议基本信息</p>
|
||||
<p>会议时间:2026年2月15日(周六)上午9:00</p>
|
||||
<p>会议地点:小区活动中心一楼会议室</p>
|
||||
<p>参会人员:全体业主或业主代表</p>
|
||||
<p>参会人员:全体业主或业主代表</p>
|
||||
<p>参会人员:全体业主或业主代表</p>
|
||||
<p>参会人员:全体业主或业主代表</p>
|
||||
<p>参会人员:全体业主或业主代表</p>
|
||||
<p>参会人员:全体业主或业主代表</p>
|
||||
<p>参会人员:全体业主或业主代表</p>
|
||||
<p>参会人员:全体业主或业主代表</p>
|
||||
<p>参会人员:全体业主或业主代表</p>
|
||||
<p>参会人员:全体业主或业主代表</p>
|
||||
<p>参会人员:全体业主或业主代表</p>
|
||||
<p>参会人员:全体业主或业主代表</p>
|
||||
<p>参会人员:全体业主或业主代表</p>
|
||||
<p>参会人员:全体业主或业主代表</p>
|
||||
<p>参会人员:全体业主或业主代表</p>
|
||||
<p>参会人员:全体业主或业主代表</p>
|
||||
<p>参会人员:全体业主或业主代表</p>
|
||||
<p>参会人员:全体业主或业主代表</p>
|
||||
<p>参会人员:全体业主或业主代表</p>`,
|
||||
attachments: [
|
||||
{ name: 'IMG-2309.PNG', type: 'image', url: '' },
|
||||
{ name: 'EXCEL-2309.XLXS', type: 'excel', url: '' }
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
// 获取附件图标
|
||||
function getAttachmentIcon(type) {
|
||||
const iconMap = {
|
||||
image: '/static/img/eli-icon1.png',
|
||||
excel: '/static/img/eli-icon2.png',
|
||||
pdf: '/static/img/eli-icon2.png',
|
||||
word: '/static/img/eli-icon2.png'
|
||||
};
|
||||
return iconMap[type] || '/static/img/eli-icon2.png';
|
||||
}
|
||||
|
||||
// 下载附件
|
||||
function downloadAttachment(item) {
|
||||
if (!item.url) {
|
||||
uni.showToast({
|
||||
title: '附件链接不存在',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
uni.showLoading({
|
||||
title: '下载中...'
|
||||
});
|
||||
|
||||
uni.downloadFile({
|
||||
url: item.url,
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200) {
|
||||
// 保存到本地
|
||||
uni.saveFile({
|
||||
tempFilePath: res.tempFilePath,
|
||||
success: (saveRes) => {
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
title: '下载成功',
|
||||
icon: 'success'
|
||||
});
|
||||
|
||||
// 打开文件
|
||||
uni.openDocument({
|
||||
filePath: saveRes.savedFilePath,
|
||||
showMenu: true,
|
||||
success: () => {
|
||||
console.log('打开文档成功');
|
||||
},
|
||||
fail: () => {
|
||||
uni.showToast({
|
||||
title: '无法打开该文件',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
fail: () => {
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
title: '保存失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
title: '下载失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
title: '下载失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* 渐变背景 */
|
||||
.gradient-bg {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 660rpx;
|
||||
background: linear-gradient(180deg, #F8EDE8 0%, #F5F5F5 50%);
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* 页面容器 */
|
||||
.notice-detail-page {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* 固定头部区域(标题+发布信息) */
|
||||
.detail-header {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.header-inner {
|
||||
padding: 0 32rpx;
|
||||
}
|
||||
|
||||
/* 底部占位 */
|
||||
.bottom-placeholder {
|
||||
height: 300rpx;
|
||||
}
|
||||
|
||||
/* 文章标题 */
|
||||
.article-title {
|
||||
padding: 32rpx 0 24rpx;
|
||||
|
||||
.title-text {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
|
||||
/* 发布信息 */
|
||||
.publish-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-bottom: 24rpx;
|
||||
|
||||
.publisher {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.publisher-avatar {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
border-radius: 50%;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.publisher-name {
|
||||
font-size: 28rpx;
|
||||
color: #666666;
|
||||
}
|
||||
}
|
||||
|
||||
.publish-date {
|
||||
font-size: 26rpx;
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
|
||||
/* 分割线 */
|
||||
.divider {
|
||||
height: 1rpx;
|
||||
background-color: #E5E5E5;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
/* 富文本滚动区域 - 纯定位,不加padding */
|
||||
.article-scroll {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
/* 富文本内容 - 圆角背景卡片 */
|
||||
.article-content {
|
||||
margin: 0 32rpx; /* 左右间距 */
|
||||
border-radius: 24rpx;
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
/* 富文本内容内层 - 控制padding */
|
||||
.article-inner {
|
||||
padding: 32rpx;
|
||||
|
||||
:deep(p) {
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
line-height: 1.8;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
:deep(img) {
|
||||
width: 100%;
|
||||
border-radius: 16rpx;
|
||||
margin: 24rpx 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 附件区域 - 固定在底部 */
|
||||
.attachment-section {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 260rpx;
|
||||
background: #FFFFFF;
|
||||
box-shadow: 0rpx -8rpx 64rpx 0rpx rgba(0, 0, 0, 0.16);
|
||||
border-radius: 0;
|
||||
overflow-y: auto;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.attachment-inner {
|
||||
padding: 24rpx 32rpx;
|
||||
}
|
||||
|
||||
.attachment-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 24rpx 0;
|
||||
|
||||
&:not(:last-child) {
|
||||
border-bottom: 1rpx solid #F0F0F0;
|
||||
}
|
||||
|
||||
.attachment-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
|
||||
.attachment-icon {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.attachment-name {
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
}
|
||||
}
|
||||
|
||||
.download-icon {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,549 @@
|
|||
<!-- 收益公示页面 -->
|
||||
<template>
|
||||
<s-layout title="收益公示" :bgStyle="{ backgroundColor: '#EDDCD2' }" navbar="inner" color="#333333">
|
||||
<view class="income-page">
|
||||
<!-- 渐变背景装饰 -->
|
||||
<view class="gradient-bg"></view>
|
||||
|
||||
<!-- 固定头部:财务卡片 + Tab -->
|
||||
<view class="page-header">
|
||||
<!-- 财务总览卡片 -->
|
||||
<view class="finance-card">
|
||||
<!-- 背景图 -->
|
||||
<image class="finance-bg" src="/static/img/jr-icon3.png" mode="aspectFill" />
|
||||
|
||||
<!-- 右上角标签 -->
|
||||
<view class="corner-badge">公共财务</view>
|
||||
|
||||
<!-- 左侧内容区 -->
|
||||
<view class="card-left">
|
||||
<!-- 账单标题 -->
|
||||
<view class="card-top">
|
||||
<image class="bill-icon" src="/static/img/jr-icon2.png" mode="aspectFit" />
|
||||
<text class="bill-title">2025年1月财务账单</text>
|
||||
</view>
|
||||
|
||||
<!-- 余额 -->
|
||||
<view class="balance-row">
|
||||
<text class="balance-label">余额(元)</text>
|
||||
<text class="balance-value">{{ financeInfo.balance }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 收支统计 -->
|
||||
<view class="stats-row">
|
||||
<view class="stats-item">
|
||||
<text class="stats-label">收入(元)</text>
|
||||
<text class="stats-value">{{ financeInfo.income }}</text>
|
||||
</view>
|
||||
<view class="stats-item">
|
||||
<text class="stats-label">支出(元)</text>
|
||||
<text class="stats-value">{{ financeInfo.expense }}</text>
|
||||
</view>
|
||||
<view class="stats-item">
|
||||
<text class="stats-label">未收(元)</text>
|
||||
<text class="stats-value">{{ financeInfo.unreceived }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分类Tab栏 -->
|
||||
<scroll-view class="tab-scroll" scroll-x :show-scrollbar="false">
|
||||
<view class="tab-bar">
|
||||
<view
|
||||
class="tab-item"
|
||||
:class="{ active: currentTab === tab.key }"
|
||||
v-for="(tab, index) in tabList"
|
||||
:key="index"
|
||||
@tap="currentTab = tab.key"
|
||||
>
|
||||
<text class="tab-text">{{ tab.label }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 明细列表 - 独立滚动区域 -->
|
||||
<scroll-view class="income-scroll" scroll-y :style="{ height: scrollHeight + 'px' }">
|
||||
<view class="income-list">
|
||||
<!-- 分类项 -->
|
||||
<view
|
||||
class="category-block"
|
||||
v-for="(item, index) in filteredIncomeList"
|
||||
:key="item.id"
|
||||
>
|
||||
<!-- 分类标题行 -->
|
||||
<view class="category-header" @tap="toggleExpand(item)">
|
||||
<view class="header-left">
|
||||
<image class="category-icon" src="/static/img/jr-icon3.png" mode="aspectFit" />
|
||||
<text class="category-name">{{ item.name }}</text>
|
||||
</view>
|
||||
<view class="header-right">
|
||||
<text class="category-total">{{ item.total }}</text>
|
||||
<image
|
||||
class="arrow-icon"
|
||||
:class="{ 'arrow-up': item.expanded }"
|
||||
src="/static/img/jr-icon4.png"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 子项列表(展开时显示) -->
|
||||
<view class="sub-list" v-if="item.expanded">
|
||||
<view
|
||||
class="sub-item"
|
||||
v-for="(sub, subIndex) in item.children"
|
||||
:key="subIndex"
|
||||
>
|
||||
<text class="sub-name">{{ sub.name }}</text>
|
||||
<text class="sub-value">{{ sub.value }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-state" v-if="filteredIncomeList.length === 0">
|
||||
<text class="empty-text">暂无数据</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</s-layout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, nextTick, getCurrentInstance } from 'vue';
|
||||
|
||||
// scroll-view 滚动高度
|
||||
const scrollHeight = ref(0);
|
||||
|
||||
// 当前选中的分类Tab
|
||||
const currentTab = ref('income');
|
||||
|
||||
// Tab列表
|
||||
const tabList = ref([
|
||||
{ key: 'income', label: '收入' },
|
||||
{ key: 'expense', label: '支出' },
|
||||
{ key: 'unreceived', label: '未收' }
|
||||
]);
|
||||
|
||||
// 财务总览数据
|
||||
const financeInfo = ref({
|
||||
balance: '16,520.00',
|
||||
income: '19,700.00',
|
||||
expense: '3,180.00',
|
||||
unreceived: '0.00'
|
||||
});
|
||||
|
||||
// 收入明细数据
|
||||
const incomeData = ref([
|
||||
{
|
||||
id: 1,
|
||||
category: 'income',
|
||||
name: '物业费',
|
||||
total: '¥20000.50',
|
||||
expanded: true,
|
||||
children: [
|
||||
{ name: '物业费', value: '¥10000.50' },
|
||||
{ name: '物业费', value: '¥10000.00' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
category: 'income',
|
||||
name: '水电公摊费',
|
||||
total: '¥200.50',
|
||||
expanded: false,
|
||||
children: [
|
||||
{ name: '水费', value: '¥100.50' },
|
||||
{ name: '电费', value: '¥100.00' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
category: 'income',
|
||||
name: '广告创收',
|
||||
total: '¥200.50',
|
||||
expanded: true,
|
||||
children: [
|
||||
{ name: '电梯广告', value: '¥200.50' },
|
||||
{ name: '门闸广告', value: '¥200.50' },
|
||||
{ name: '公区场地广告', value: '¥200.50' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
category: 'income',
|
||||
name: '公共场地创收',
|
||||
total: '¥200.50',
|
||||
expanded: false,
|
||||
children: [
|
||||
{ name: '场地租赁', value: '¥200.50' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
category: 'income',
|
||||
name: '停车收费',
|
||||
total: '¥200.50',
|
||||
expanded: false,
|
||||
children: [
|
||||
{ name: '月租停车', value: '¥200.50' }
|
||||
]
|
||||
}
|
||||
]);
|
||||
|
||||
// 支出明细数据
|
||||
const expenseData = ref([
|
||||
{
|
||||
id: 6,
|
||||
category: 'expense',
|
||||
name: '保洁费用',
|
||||
total: '¥1,000.00',
|
||||
expanded: false,
|
||||
children: [
|
||||
{ name: '日常保洁', value: '¥500.00' },
|
||||
{ name: '垃圾清运', value: '¥500.00' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
category: 'expense',
|
||||
name: '维修费用',
|
||||
total: '¥1,500.00',
|
||||
expanded: false,
|
||||
children: [
|
||||
{ name: '电梯维修', value: '¥1,000.00' },
|
||||
{ name: '水管维修', value: '¥500.00' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
category: 'expense',
|
||||
name: '绿化养护',
|
||||
total: '¥680.00',
|
||||
expanded: false,
|
||||
children: [
|
||||
{ name: '绿植采购', value: '¥380.00' },
|
||||
{ name: '养护人工', value: '¥300.00' }
|
||||
]
|
||||
}
|
||||
]);
|
||||
|
||||
// 未收明细数据
|
||||
const unreceivedData = ref([]);
|
||||
|
||||
// 根据当前Tab筛选列表
|
||||
const filteredIncomeList = computed(() => {
|
||||
const allData = [...incomeData.value, ...expenseData.value, ...unreceivedData.value];
|
||||
return allData.filter(item => item.category === currentTab.value);
|
||||
});
|
||||
|
||||
// 展开/收起子项
|
||||
function toggleExpand(item) {
|
||||
item.expanded = !item.expanded;
|
||||
}
|
||||
|
||||
// 渲染完成后计算scroll-view高度
|
||||
onMounted(() => {
|
||||
const instance = getCurrentInstance();
|
||||
nextTick(() => {
|
||||
const sysInfo = uni.getSystemInfoSync();
|
||||
uni.createSelectorQuery()
|
||||
.in(instance)
|
||||
.select('.page-header')
|
||||
.boundingClientRect((rect) => {
|
||||
if (rect) {
|
||||
scrollHeight.value = sysInfo.windowHeight - rect.height - rect.top;
|
||||
}
|
||||
})
|
||||
.exec();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* ==================== 页面容器 ==================== */
|
||||
.income-page {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* ==================== 渐变背景 ==================== */
|
||||
.gradient-bg {
|
||||
position: absolute;
|
||||
top: -176rpx;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: calc(100% + 176rpx);
|
||||
background: linear-gradient(180deg, #F8EDE8 0%, #FFFFFF 30%);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
/* ==================== 固定头部区域 ==================== */
|
||||
.page-header {
|
||||
padding-bottom: 16rpx;
|
||||
}
|
||||
|
||||
/* ==================== 财务总览卡片 ==================== */
|
||||
.finance-card {
|
||||
margin: 24rpx 32rpx 24rpx;
|
||||
padding: 28rpx 0 24rpx 32rpx;
|
||||
border-radius: 24rpx;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
min-height: 300rpx;
|
||||
|
||||
/* 背景图 */
|
||||
.finance-bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
/* 右上角标签 */
|
||||
.corner-badge {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 30rpx;
|
||||
background: linear-gradient(135deg, #FA7E49 0%, #E86935 100%);
|
||||
color: #FFFFFF;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
padding: 12rpx 28rpx;
|
||||
border-radius: 0 24rpx 0 24rpx;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/* 左侧内容区 */
|
||||
.card-left {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* 卡片顶部:账单标题 */
|
||||
.card-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.bill-icon {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
margin-right: 12rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.bill-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
}
|
||||
}
|
||||
|
||||
/* 余额行 - 纵向排列 */
|
||||
.balance-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.balance-label {
|
||||
font-size: 26rpx;
|
||||
color: #666666;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.balance-value {
|
||||
font-size: 52rpx;
|
||||
font-weight: 700;
|
||||
color: #FA7E49;
|
||||
}
|
||||
}
|
||||
|
||||
/* 收支统计行 */
|
||||
.stats-row {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
gap: 48rpx;
|
||||
|
||||
.stats-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.stats-label {
|
||||
font-size: 24rpx;
|
||||
color: #666666;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.stats-value {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ==================== 分类Tab栏 ==================== */
|
||||
.tab-scroll {
|
||||
white-space: nowrap;
|
||||
margin: 0 0 8rpx;
|
||||
}
|
||||
|
||||
.tab-bar {
|
||||
display: inline-flex;
|
||||
padding: 0 32rpx;
|
||||
|
||||
.tab-item {
|
||||
padding: 20rpx 0;
|
||||
margin-right: 40rpx;
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.tab-text {
|
||||
font-size: 28rpx;
|
||||
color: #999999;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
/* 选中状态 */
|
||||
&.active {
|
||||
.tab-text {
|
||||
color: #FA7E49;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 底部橙色指示条 */
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 48rpx;
|
||||
height: 4rpx;
|
||||
background: #FA7E49;
|
||||
border-radius: 2rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ==================== 明细列表滚动区域 ==================== */
|
||||
.income-scroll {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* ==================== 明细列表内容 ==================== */
|
||||
.income-list {
|
||||
padding: 0 32rpx 160rpx;
|
||||
}
|
||||
|
||||
/* ==================== 分类块 ==================== */
|
||||
.category-block {
|
||||
background-color: #FFFFFF;
|
||||
border-radius: 16rpx;
|
||||
margin-bottom: 20rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
/* ==================== 分类标题行 ==================== */
|
||||
.category-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 28rpx 24rpx;
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.category-icon {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
margin-right: 16rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.category-name {
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
color: #333333;
|
||||
}
|
||||
}
|
||||
|
||||
.header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.category-total {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #FA7E49;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.arrow-icon {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
transition: transform 0.3s;
|
||||
|
||||
&.arrow-up {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ==================== 子项列表 ==================== */
|
||||
.sub-list {
|
||||
padding: 0 24rpx 16rpx;
|
||||
|
||||
.sub-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16rpx 0;
|
||||
border-top: 1rpx solid #F5F5F5;
|
||||
|
||||
.sub-name {
|
||||
font-size: 26rpx;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.sub-value {
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
color: #FA7E49;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ==================== 空状态 ==================== */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 200rpx 0;
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -9,34 +9,49 @@
|
|||
<view class="page-header">
|
||||
<!-- 统计卡片 -->
|
||||
<view class="stats-card">
|
||||
<!-- 小区名称 -->
|
||||
<view class="card-top">
|
||||
<view class="building-info">
|
||||
<text class="sicon-building building-icon"></text>
|
||||
<!-- 背景图 -->
|
||||
<image class="stats-bg" src="/static/img/wy-icon3.png" mode="aspectFill" />
|
||||
|
||||
<!-- 右上角标签 -->
|
||||
<view class="corner-badge">物业人员</view>
|
||||
|
||||
<!-- 左侧内容区 -->
|
||||
<view class="card-left">
|
||||
<!-- 小区名称 -->
|
||||
<view class="card-top">
|
||||
<image class="building-icon" src="/static/img/wy-icon2.png" mode="aspectFit" />
|
||||
<text class="building-name">{{ statsInfo.buildingName }}</text>
|
||||
</view>
|
||||
<view class="staff-badge">物业人员</view>
|
||||
</view>
|
||||
|
||||
<!-- 总人数 -->
|
||||
<view class="total-row">
|
||||
<text class="total-label">总人数</text>
|
||||
<text class="total-count">{{ statsInfo.total }}<text class="total-unit">人</text></text>
|
||||
</view>
|
||||
<!-- 总人数 -->
|
||||
<view class="total-row">
|
||||
<text class="total-label">总人数</text>
|
||||
<text class="total-count">{{ statsInfo.total }}<text class="total-unit">人</text></text>
|
||||
</view>
|
||||
|
||||
<!-- 分类统计 -->
|
||||
<view class="category-row">
|
||||
<view
|
||||
class="category-item"
|
||||
v-for="(cat, index) in statsInfo.categories"
|
||||
:key="index"
|
||||
>
|
||||
<text class="cat-name">{{ cat.name }}</text>
|
||||
<text class="cat-count">{{ cat.count }}</text>
|
||||
<!-- 分类统计 -->
|
||||
<view class="category-row">
|
||||
<view
|
||||
class="category-item"
|
||||
v-for="(cat, index) in statsInfo.categories"
|
||||
:key="index"
|
||||
>
|
||||
<text class="cat-name">{{ cat.name }}</text>
|
||||
<text class="cat-count">{{ cat.count }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 右侧叠加背景图 -->
|
||||
<image class="right-bg" src="/static/img/wy-icon1.png" mode="aspectFit" />
|
||||
|
||||
<!-- 右侧区域 -->
|
||||
<view class="card-right"></view>
|
||||
</view>
|
||||
|
||||
<!-- 人员信息标题 -->
|
||||
<view class="section-title">人员信息</view>
|
||||
|
||||
<!-- 分类Tab栏 -->
|
||||
<scroll-view class="tab-scroll" scroll-x :show-scrollbar="false">
|
||||
<view class="tab-bar">
|
||||
|
|
@ -51,9 +66,6 @@
|
|||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 人员信息标题 -->
|
||||
<view class="section-title">人员信息</view>
|
||||
</view>
|
||||
|
||||
<!-- 人员列表 - 独立滚动区域 -->
|
||||
|
|
@ -81,10 +93,7 @@
|
|||
<!-- 电话 -->
|
||||
<view class="phone-row">
|
||||
<text class="phone-label">电话:</text>
|
||||
<text class="phone-text">{{ item.phone }}</text>
|
||||
<view class="call-btn" @tap="callPhone(item.phone)">
|
||||
<text class="sicon-phone call-icon"></text>
|
||||
</view>
|
||||
<text class="phone-text" @tap="callPhone(item.phone)">{{ item.phone }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 职责描述 -->
|
||||
|
|
@ -236,27 +245,68 @@ function callPhone(phone) {
|
|||
/* ==================== 统计卡片 ==================== */
|
||||
.stats-card {
|
||||
margin: 24rpx 32rpx 24rpx;
|
||||
padding: 28rpx 32rpx 24rpx;
|
||||
background: linear-gradient(135deg, #EBF4FF 0%, #D6E7FF 100%);
|
||||
padding: 28rpx 0 24rpx 32rpx;
|
||||
border-radius: 24rpx;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
min-height: 320rpx;
|
||||
|
||||
/* 卡片顶部:小区名+标签 */
|
||||
/* 背景图 */
|
||||
.stats-bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
/* 右侧叠加背景图 */
|
||||
.right-bg {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 24rpx;
|
||||
transform: translateY(-50%);
|
||||
width: 160rpx;
|
||||
height: 180rpx;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* 右上角标签 */
|
||||
.corner-badge {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 30rpx;
|
||||
color: #FFFFFF;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
padding: 12rpx 28rpx;
|
||||
border-radius: 0 24rpx 0 24rpx;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/* 左侧内容区 */
|
||||
.card-left {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* 卡片顶部:小区名 */
|
||||
.card-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
/* 小区信息 */
|
||||
.building-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.building-icon {
|
||||
font-size: 36rpx;
|
||||
color: #333333;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
margin-right: 12rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.building-name {
|
||||
|
|
@ -266,35 +316,25 @@ function callPhone(phone) {
|
|||
}
|
||||
}
|
||||
|
||||
/* 物业人员标签 */
|
||||
.staff-badge {
|
||||
background: linear-gradient(135deg, #5B9BFF 0%, #3D7BD9 100%);
|
||||
color: #FFFFFF;
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
padding: 8rpx 24rpx;
|
||||
border-radius: 24rpx 0 24rpx 0;
|
||||
}
|
||||
|
||||
/* 总人数行 */
|
||||
/* 总人数行 - 纵向排列 */
|
||||
.total-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
margin-bottom: 24rpx;
|
||||
flex-direction: column;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.total-label {
|
||||
font-size: 26rpx;
|
||||
color: #666666;
|
||||
margin-right: 16rpx;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.total-count {
|
||||
font-size: 48rpx;
|
||||
font-size: 52rpx;
|
||||
font-weight: 700;
|
||||
color: #FA7E49; /* 橙色主题 */
|
||||
|
||||
.total-unit {
|
||||
font-size: 28rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: 400;
|
||||
color: #FA7E49;
|
||||
margin-left: 4rpx;
|
||||
|
|
@ -305,7 +345,8 @@ function callPhone(phone) {
|
|||
/* 分类统计行 */
|
||||
.category-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
justify-content: space-around;
|
||||
gap: 32rpx;
|
||||
|
||||
.category-item {
|
||||
display: flex;
|
||||
|
|
@ -313,9 +354,9 @@ function callPhone(phone) {
|
|||
align-items: center;
|
||||
|
||||
.cat-name {
|
||||
font-size: 26rpx;
|
||||
font-size: 24rpx;
|
||||
color: #666666;
|
||||
margin-bottom: 8rpx;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.cat-count {
|
||||
|
|
@ -325,6 +366,18 @@ function callPhone(phone) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 右侧区域 */
|
||||
.card-right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-right: 24rpx;
|
||||
padding-left: 16rpx;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* ==================== 分类Tab栏 ==================== */
|
||||
|
|
@ -341,6 +394,7 @@ function callPhone(phone) {
|
|||
padding: 20rpx 0;
|
||||
margin-right: 40rpx;
|
||||
position: relative;
|
||||
flex-shrink: 0; /* 防止Tab项被压缩 */
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
|
|
@ -397,13 +451,14 @@ function callPhone(phone) {
|
|||
.staff-item {
|
||||
display: flex;
|
||||
padding: 28rpx 0;
|
||||
position: relative;
|
||||
|
||||
/* 分割线(非第一项) */
|
||||
&:not(:last-child)::after {
|
||||
/* 分割线 */
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 112rpx;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 1rpx;
|
||||
background-color: #F0F0F0;
|
||||
|
|
@ -442,6 +497,9 @@ function callPhone(phone) {
|
|||
font-weight: 500;
|
||||
margin-right: 24rpx;
|
||||
}
|
||||
.name-text{
|
||||
margin-right:100rpx;
|
||||
}
|
||||
}
|
||||
|
||||
/* 电话行 */
|
||||
|
|
@ -478,17 +536,23 @@ function callPhone(phone) {
|
|||
}
|
||||
}
|
||||
|
||||
/* 职责描述行 */
|
||||
/* 职责描述行 - 标签与内容同行换行 */
|
||||
.duty-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
|
||||
.duty-label {
|
||||
font-size: 26rpx;
|
||||
color: #999999;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.duty-text {
|
||||
flex: 1;
|
||||
font-size: 26rpx;
|
||||
color: #666666;
|
||||
line-height: 1.6;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
import request from '@/sheep/request';
|
||||
|
||||
const CommunityApi = {
|
||||
// 获取小区简单列表
|
||||
getSimpleList: () => {
|
||||
return request({
|
||||
url: '/community/community/simple-list',
|
||||
method: 'GET',
|
||||
custom: {
|
||||
showLoading: true,
|
||||
auth: true,
|
||||
},
|
||||
});
|
||||
},
|
||||
// 获取小区房屋树
|
||||
getHouseTree: (communityId) => {
|
||||
return request({
|
||||
url: '/community/house/tree',
|
||||
method: 'GET',
|
||||
params: { communityId },
|
||||
custom: {
|
||||
showLoading: true,
|
||||
auth: true,
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default CommunityApi;
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
import request from '@/sheep/request';
|
||||
|
||||
const MemberHouseApi = {
|
||||
// 获取我的房屋认证列表
|
||||
getMyList: () => {
|
||||
return request({
|
||||
url: '/community/member-house/my-list',
|
||||
method: 'GET',
|
||||
custom: {
|
||||
showLoading: true,
|
||||
auth: true,
|
||||
},
|
||||
});
|
||||
},
|
||||
// 获取小区房屋树
|
||||
getCommunityTree: () => {
|
||||
return request({
|
||||
url: '/community/member-house/community-tree',
|
||||
method: 'GET',
|
||||
custom: {
|
||||
showLoading: true,
|
||||
auth: true,
|
||||
},
|
||||
});
|
||||
},
|
||||
// 切换当前房屋
|
||||
switchHouse: (data) => {
|
||||
return request({
|
||||
url: '/community/member-house/switch-house',
|
||||
method: 'POST',
|
||||
data,
|
||||
custom: {
|
||||
showLoading: true,
|
||||
auth: true,
|
||||
showSuccess: true,
|
||||
successMsg: '切换成功',
|
||||
},
|
||||
});
|
||||
},
|
||||
// 新增业主认证信息
|
||||
create: (data) => {
|
||||
return request({
|
||||
url: '/community/member-house/create',
|
||||
method: 'POST',
|
||||
data,
|
||||
custom: {
|
||||
showLoading: true,
|
||||
auth: true,
|
||||
showSuccess: true,
|
||||
successMsg: '提交成功',
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default MemberHouseApi;
|
||||
|
|
@ -8,7 +8,7 @@ let subscribeEventList = []
|
|||
|
||||
function load() {
|
||||
checkUpdate()
|
||||
getSubscribeTemplate()
|
||||
// getSubscribeTemplate() // 已移除:不需要获取订阅消息模板列表
|
||||
}
|
||||
|
||||
// ================= 登录相关逻辑===================
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ let subscribeEventList = [];
|
|||
// 加载微信小程序
|
||||
function load() {
|
||||
checkUpdate();
|
||||
getSubscribeTemplate();
|
||||
// getSubscribeTemplate(); // 已移除:不需要获取订阅消息模板列表
|
||||
}
|
||||
|
||||
// 微信小程序静默授权登陆
|
||||
|
|
|
|||
|
|
@ -67,12 +67,12 @@ const app = defineStore({
|
|||
await adaptTenant();
|
||||
|
||||
// 加载装修配置
|
||||
await adaptTemplate(this.template, templateId);
|
||||
// await adaptTemplate(this.template, templateId);
|
||||
|
||||
// TODO 芋艿:【初始化优化】未来支持管理后台可配;对应 https://api.shopro.sheepjs.com/shop/api/init
|
||||
if (true) {
|
||||
this.info = {
|
||||
name: '芋道商城',
|
||||
name: '物业管理',
|
||||
logo: 'https://static.iocoder.cn/ruoyi-vue-pro-logo.png',
|
||||
version: '2026.01',
|
||||
copyright: '全部开源,个人与企业可 100% 免费使用',
|
||||
|
|
@ -172,7 +172,7 @@ const adaptTenant = async () => {
|
|||
userStore.setToken();
|
||||
|
||||
// 设置新的 tenantId 到本地存储
|
||||
uni.setStorageSync('tenant-id', newTenantId);
|
||||
uni.setStorageSync('tenant-id', '162'||newTenantId);
|
||||
console.log('租户 ID 已更新:', `${oldTenantId} -> ${newTenantId}`);
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,11 @@ const defaultUserInfo = {
|
|||
gender: 0, // 性别
|
||||
mobile: '', // 手机号
|
||||
point: 0, // 积分
|
||||
currentCommunityId: null, // 当前小区ID
|
||||
currentCommunityName: '', // 当前小区名称
|
||||
currentHouseId: null, // 当前房屋ID
|
||||
currentHouseAddress: '', // 当前房屋完整地址
|
||||
isHouseAuth: false, // 是否已完成房屋认证(有认证记录)
|
||||
};
|
||||
|
||||
// 默认钱包信息
|
||||
|
|
@ -110,8 +115,8 @@ const user = defineStore({
|
|||
|
||||
// 获取最新信息
|
||||
await this.getInfo();
|
||||
this.getWallet();
|
||||
this.getNumData();
|
||||
// this.getWallet();
|
||||
// this.getNumData();
|
||||
return this.userInfo;
|
||||
},
|
||||
|
||||
|
|
@ -132,17 +137,17 @@ const user = defineStore({
|
|||
await this.updateUserData();
|
||||
|
||||
// 加载购物车
|
||||
cart().getList();
|
||||
// cart().getList();
|
||||
// 登录后设置全局分享参数
|
||||
$share.getShareInfo();
|
||||
|
||||
// 提醒绑定手机号
|
||||
if (app().platform.bind_mobile && !this.userInfo.mobile) {
|
||||
showAuthModal('changeMobile');
|
||||
}
|
||||
// if (app().platform.bind_mobile && !this.userInfo.mobile) {
|
||||
// showAuthModal('changeMobile');
|
||||
// }
|
||||
|
||||
// 绑定推广员
|
||||
$share.bindBrokerageUser();
|
||||
// $share.bindBrokerageUser();
|
||||
},
|
||||
|
||||
// 登出系统
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 202 B |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 396 B |
|
After Width: | Height: | Size: 899 B |
|
After Width: | Height: | Size: 369 B |
|
After Width: | Height: | Size: 16 KiB |