# Conflicts:
#	property-uniapp-project/pageSubPack/api/apiSub.js
This commit is contained in:
wangyuxin
2026-05-13 17:31:31 +08:00
27 changed files with 1579 additions and 1914 deletions

View File

@@ -0,0 +1,7 @@
{
"permissions": {
"allow": [
"WebFetch(domain:app.apifox.com)"
]
}
}

View File

@@ -176,7 +176,14 @@ const narBack = () => {
*/
const switchTab = (tab) => {
activeTab.value = tab
resetAndLoad()
if (tab === 'list') {
resetAndLoad()
} else {
// 暂无审核功能,"我的活动"默认显示暂无数据
displayedActivities.value = []
hasMoreData.value = false
currentPage.value = 1
}
}
/**
@@ -185,7 +192,7 @@ const switchTab = (tab) => {
*/
const switchFilter = (filter) => {
myFilter.value = filter
resetAndLoad()
// resetAndLoad()
}
/**
@@ -287,7 +294,9 @@ const viewActivityDetail = (activity) => {
* 页面显示时刷新数据
*/
onShow(() => {
getActivityListData(1) // 强制刷新第一页
if (activeTab.value === 'list') {
getActivityListData(1) // 强制刷新第一页
}
})
/**
@@ -296,7 +305,7 @@ onShow(() => {
onLoad(() => {
// 监听详情页报名/取消报名后的刷新通知
uni.$on('refreshActivityList', (data) => {
console.log('收到活动列表刷新通知:', data)
// console.log('收到活动列表刷新通知:', data)
getActivityListData(1)
})
})
@@ -312,9 +321,7 @@ onUnload(() => {
* 监听我的活动筛选条件变化
*/
watch(() => myFilter.value, () => {
if (activeTab.value === 'my') {
resetAndLoad()
}
// 暂无审核功能,筛选条件暂不触发加载
})
</script>

View File

@@ -21,8 +21,9 @@
<view class="upload-area">
<!-- 已上传图片 -->
<view class="uploaded-img" v-for="(img, index) in imgList" :key="index">
<image :src="img" mode="aspectFill"></image>
<text class="del-img" @click="deleteImg(index)">×</text>
<image :src="img" mode="aspectFill" class="upload-img"></image>
<image src="http://47.104.199.163:9090/images/imgs/delete.png" class="upload-delete" @click="deleteImage(index)"></image>
<!-- <text class="del-img" @click="deleteImg(index)">×</text> -->
</view>
<!-- 上传按钮最多3张 -->
<view class="upload-btn" @click="chooseImg" v-if="imgList.length < 3">
@@ -55,7 +56,7 @@
<script setup>
import { ref } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import { addActivityLive, signinActivityLive, uploadImage } from '../api/api.js';
import { addActivityLive, signinActivityLive, uploadFile } from '../api/api.js';
// 活动ID
const activityId = ref('');
@@ -98,7 +99,7 @@ const chooseImg = () => {
};
// 删除图片
const deleteImg = (index) => {
const deleteImage = (index) => {
imgList.value.splice(index, 1);
};
@@ -147,9 +148,9 @@ const reLocate = () => {
// 上传所有图片
const uploadAllImages = async () => {
if (imgList.value.length === 0) return [];
const uploadPromises = imgList.value.map(img => uploadImage(img));
const uploadPromises = imgList.value.map(img => uploadFile(img));
const results = await Promise.all(uploadPromises);
return results.map(res => res.data?.url || res.data);
return results.map(res => res.url);
};
// 提交实况
@@ -258,11 +259,20 @@ const submitStatus = async () => {
height: 80px;
position: relative;
}
.uploaded-img image {
.uploaded-img .upload-img{
width: 100%;
height: 100%;
border-radius: 4px;
}
.uploaded-img .upload-delete{
position: absolute;
top: -5px;
right: -5px;
width: 32rpx;
height: 32rpx;
border-radius: 3rpx;
padding: 2px;
}
.del-img {
position: absolute;
top: -5px;

View File

@@ -22,6 +22,10 @@ export const getResetPassword = (data) =>{
return post('/api/resident/auth/resetPassword',data)
}
// =========首页=======
// 获取bannar图
export const getBannarList = (data) =>{
return get('/resident/banner',data)
}
// 首页最新公告
export const getNoticeLatest = () =>{
return get('/api/resident/notice/latest')
@@ -131,11 +135,7 @@ export const addVisitorData = (data) =>{
return post("/api/visitor", data)
}
// 通用文件上传
export const uploadImage = () => {
return upload('');
}
// 上传单张图片
export const uploadFile = (filePath) => {
return upload(filePath, '');
return upload(filePath,'/api/resident/file/uploadImage');
}

View File

@@ -86,6 +86,42 @@ export const authLogout = (data) => {
// 编辑房屋
export const upDateMyHouse = (data) => {
return putMethod(`/api/resident/my/house/${data.id}`, data)
}
// =========问卷调查=========
// 问卷列表
export const getQuestionnaireList = (data) => {
return get('/api/resident/questionnaire/list',data)
}
// 问卷详情
export const getQuestionnaireDetail = (data) => {
return get('/api/resident/questionnaire/myAnswers',data)
}
// 提交问卷
export const submitQuestionnair = (id,data) => {
return post(`/api/resident/questionnaire/${id}/submit`, data)
}
// =====投诉意见=======
// 获取投诉类型
export const getComplainType = () => {
return get('/complain/getComplainType')
}
// 获取投诉列表
export const getComplainList = (data) => {
return get('/complain/getComplainList',data)
}
// 提交投诉建议
export const addComplain = (data) => {
return post('/complain/add',data)
}
// 投诉详情
export const getComplainDetail = (data) => {
return get('/complain/detail',data)
}
// 联系物业
export const getManagePhoneData = (data) => {
return get('/api/manage/banner/getManagePhone',data)
}
// 修改身份证照片

View File

@@ -1,7 +1,5 @@
let baseUrl = '';
if (process.env.NODE_ENV === 'development') {
// http://192.168.1.215
// http://192.168.0.224
baseUrl = 'http://192.168.0.239:8080'; // 开发环境
} else {
baseUrl = 'http://192.168.0.224:8080'; // 生产环境
@@ -9,7 +7,6 @@ if (process.env.NODE_ENV === 'development') {
export default function request(url, data = {}, method = "GET") {
return new Promise((resolve, reject) => {
// 加载中提示(提升用户体验)
uni.showLoading({
title: '请求中...',
mask: true
@@ -24,87 +21,51 @@ export default function request(url, data = {}, method = "GET") {
"clientid": "resident",
'content-type': 'application/json'
},
// header: {
// "token": uni.getStorageSync("token") || '',
// 'content-type': 'application/json'
// },
success: (res) => {
uni.hideLoading(); // 关闭加载提示
uni.hideLoading();
// 处理HTTP状态码(网络层面)
// HTTP 失败
if (res.statusCode !== 200) {
const errMsg = `请求失败(${res.statusCode}`;
uni.showToast({
title: errMsg,
icon: "none",
duration: 2000
});
reject(errMsg);
reject({ msg: `请求异常 ${res.statusCode}`, code: res.statusCode });
return;
}
// 处理业务状态码后端返回的data里
const resData = res.data || {}; // 兼容后端返回空的情况
const resData = res.data || {};
// 优先处理token过期401
// token 过期
if (resData.code === 401) {
uni.showToast({
title: '登录过期,请重新登录',
icon: 'none',
duration: 2000
});
uni.showToast({ title: '登录过期,请重新登录', icon: 'none' });
uni.clearStorageSync('token');
setTimeout(() => {
uni.reLaunch({
url: '/pageSubPack/loginSub/pwd-login'
});
uni.reLaunch({ url: '/pageSubPack/loginSub/pwd-login' });
}, 1500);
reject('token 过期');
reject({ msg: '登录过期', code: 401 });
return;
}
// 业务成功code=200
// 成功
if (resData.code === 200) {
resolve(resData); // 只返回核心数据,简化页面调用
resolve(resData);
}
// 业务失败code=500
else if (resData.code === 500) {
const errMsg = resData.msg || '请求失败';
uni.showToast({
title: errMsg,
icon: "none",
duration: 2000
});
// resolve(null)
reject(errMsg);
} else {
const errMsg = resData.msg || '业务异常';
uni.showToast({
title: errMsg,
icon: "none",
duration: 2000
});
reject(errMsg);
// 业务失败
else {
reject({ msg: resData.msg || '请求失败', code: resData.code, data: resData });
}
},
fail: (err) => {
uni.hideLoading(); // 关闭加载提示
const errMsg = err.msg || '网络请求失败';
uni.showToast({
title: errMsg,
icon: "none",
duration: 2000
});
reject(err);
uni.hideLoading();
reject({ msg: '网络异常', code: -1, raw: err });
}
});
});
}
export const get = (url, data) => request(url, data, 'GET');
export const post = (url, data) => request(url, data, 'POST');
export const deleteMethod = (url, data) => request(url, data, 'DELETE');
export const putMethod = (url, data) => request(url, data, 'PUT');
// 通用文件上传
// 上传
export const upload = (filePath, url) => {
return new Promise((resolve, reject) => {
uni.uploadFile({
@@ -116,16 +77,12 @@ export const upload = (filePath, url) => {
"clientid": "resident"
},
success: (res) => {
if (res.statusCode === 200) {
const data = JSON.parse(res.data);
if (data.code === 200) {
resolve(data);
} else {
reject(data.msg || '上传失败');
try {
const data = JSON.parse(res.data);
resolve(data.data || ''); // 直接返回图片地址
} catch (e) {
resolve('');
}
} else {
reject('上传失败');
}
},
fail: (err) => {
reject(err);

View File

@@ -100,7 +100,7 @@ const handleConfirm = () => {
}
}).catch(err =>{
uni.showToast({
title: err.msg,
title: err.msg || '网络异常',
icon: 'error'
});
})

View File

@@ -92,7 +92,7 @@ const getVerifyCode = () => {
}
}).catch(err =>{
uni.showToast({
title: err.msg,
title: err.msg || '网络异常',
icon: 'error'
});
})

View File

@@ -62,7 +62,6 @@
<!-- 登录按钮 -->
<button
class="login-btn"
:disabled="!canLogin"
@click="handleLogin"
>
登录
@@ -166,7 +165,26 @@ const getVerifyCode = () => {
// 登录处理
const handleLogin = () => {
if (!canLogin.value) return;
if (!phoneNumber.value) {
uni.showToast({ title: '请输入手机号', icon: 'none' });
return;
}
if (!/^1[3-9]\d{9}$/.test(phoneNumber.value)) {
uni.showToast({ title: '请输入正确的手机号', icon: 'none' });
return;
}
if (!verifyCode.value) {
uni.showToast({ title: '请输入验证码', icon: 'none' });
return;
}
if (!/^\d{6}$/.test(verifyCode.value)) {
uni.showToast({ title: '验证码为 6 位数字', icon: 'none' });
return;
}
if (!agreeAgreement.value) {
uni.showToast({ title: '请勾选协议', icon: 'none' });
return;
}
uni.showLoading({ title: '登录中...', mask: true });
let params = {
phone:phoneNumber.value,
@@ -199,7 +217,7 @@ const handleLogin = () => {
}).catch(err => {
uni.showToast({
title: '网络异常,请重试',
title: err.msg || '网络异常,请重试',
icon: 'error'
});
})

View File

@@ -55,7 +55,6 @@
<!-- 登录按钮 -->
<button
class="login-btn"
:disabled="!canLogin"
@click="handleLogin"
>
登录
@@ -109,30 +108,37 @@ onLoad(() =>{
// 登录逻辑
const handleLogin = () => {
// if (!canLogin.value) {
// uni.showToast({ title: '请输入账号和密码', icon: 'none' });
// return
// }
if (!phoneNumber.value) {
uni.showToast({ title: '请输入账号', icon: 'none' });
return;
}
if (phoneNumber.value.length < 4 || phoneNumber.value.length > 16) {
uni.showToast({ title: '账号长度需为 4-16 位', icon: 'none' });
return;
}
if (!password.value) {
uni.showToast({ title: '请输入密码', icon: 'none' });
return;
}
if (password.value.length < 6) {
uni.showToast({ title: '密码至少输入 6 位', icon: 'none' });
return;
}
if (password.value.length > 16) {
uni.showToast({ title: '密码不能超过 16 位', icon: 'none' });
return;
}
if(!agreeAgreement.value){
uni.showToast({ title: '请勾选协议', icon: 'none' });
return;
}
// uni.showLoading({ title: '登录中...', mask: true });
uni.showLoading({ title: '登录中...', mask: true });
let params = {
username:phoneNumber.value,
password:password.value
}
getLoginPassword(params).then(res =>{
// console.log('33333',res)
if(res.code === 200){
uni.setStorageSync('token', res.data.token);
// 存用户ID
@@ -155,21 +161,11 @@ const handleLogin = () => {
}).catch(err => {
uni.showToast({
title: err.msg,
title: err.msg || '网络异常',
icon: 'error'
});
})
// 测试
// uni.showLoading({
// title: '登录中...',
// mask:true
// });
// setTimeout(() => {
// uni.switchTab({ url: '/pages/index/index' });
// }, 1500);
// uni.hideLoading()
};
</script>

View File

@@ -74,7 +74,7 @@
<!-- 立即注册按钮 -->
<button class="register-btn" :disabled="!canRegister" @click="handleRegister">
<button class="register-btn" @click="handleRegister">
立即注册
</button>
@@ -181,7 +181,7 @@
}
}).catch(err => {
uni.showToast({
title: err.msg,
title: err.msg || '网络异常',
icon: 'error'
});
})
@@ -191,7 +191,46 @@
// 处理注册逻辑
const handleRegister = () => {
if (!canRegister.value) return;
if (!phoneNumber.value) {
uni.showToast({ title: '请输入手机号', icon: 'none' });
return;
}
if (!/^1[3-9]\d{9}$/.test(phoneNumber.value)) {
uni.showToast({ title: '请输入正确的手机号', icon: 'none' });
return;
}
if (!verifyCode.value) {
uni.showToast({ title: '请输入验证码', icon: 'none' });
return;
}
if (!/^\d{6}$/.test(verifyCode.value)) {
uni.showToast({ title: '验证码为 6 位数字', icon: 'none' });
return;
}
if (!account.value) {
uni.showToast({ title: '请输入账号', icon: 'none' });
return;
}
if (account.value.length < 4 || account.value.length > 20) {
uni.showToast({ title: '账号长度需为 4-20 位', icon: 'none' });
return;
}
if (!password.value) {
uni.showToast({ title: '请输入密码', icon: 'none' });
return;
}
if (password.value.length < 6) {
uni.showToast({ title: '密码至少输入 6 位', icon: 'none' });
return;
}
if (password.value.length > 16) {
uni.showToast({ title: '密码不能超过 16 位', icon: 'none' });
return;
}
if (!agreeAgreement.value) {
uni.showToast({ title: '请勾选协议', icon: 'none' });
return;
}
uni.showLoading({
title: '注册中...',
mask: true
@@ -223,7 +262,7 @@
}
}).catch((err) => {
uni.showToast({
title: err.msg,
title: err.msg || '网络异常',
icon: 'error'
});
})

View File

@@ -1,296 +1,256 @@
<template>
<view class="contentStyle" style="">
<view class="contentStyle">
<view class="status-bar"></view>
<uni-nav-bar title="更改手机号码" left-icon="left" @clickLeft="goBack" backgroundColor="transparent" :border="false">
<uni-nav-bar
title="更改手机号码"
left-icon="left"
@click-left="goBack"
background-color="transparent"
:border="false"
>
</uni-nav-bar>
<scroll-view class="page" scroll-y>
<!-- 表单区域 -->
<view class="form-container">
<!-- 手机号输入框+86前缀 -->
<view class="input-group phone-input">
<image class="iconStyle" src="http://47.104.199.163:9090/images/propertyImgs/phoneIcon.png"></image>
<input class="input-item" type="number" v-model="phone" placeholder="请输入手机号码"
placeholder-class="input-placeholder" maxlength="11" />
<input
class="input-item"
type="number"
v-model="phone"
placeholder="请输入手机号码"
placeholder-class="input-placeholder"
maxlength="11"
/>
</view>
<!-- 验证码输入框带获取验证码按钮 -->
<view class="input-group code-input" style="padding: 0 0 0 20rpx;">
<image class="iconStyle" src="http://47.104.199.163:9090/images/propertyImgs/codeIcon.png"></image>
<input class="input-item code-item" type="number" v-model="code" placeholder="请输入验证码"
placeholder-class="input-placeholder" maxlength="6" />
<button class="code-btn" :disabled="countdown > 0" @click="getCode">
<image class="iconStyle" src="http://47.104.199.163:9090/images/propertyImgs/codeIcon.png"></image>
<input
class="input-item code-item"
type="number"
v-model="code"
placeholder="请输入验证码"
placeholder-class="input-placeholder"
maxlength="6"
/>
<button
class="code-btn"
:disabled="countdown > 0"
@click="getCode"
>
{{ countdown > 0 ? `${countdown}s后重发` : '获取验证码' }}
</button>
</view>
</view>
</scroll-view>
<view class="bottom-btn">
<button class="submit-btn" @click="onSubmit">确定</button>
</view>
</view>
</template>
<script>
export default {
onReady: function(e) {},
components: {
},
computed: {
},
created() {
},
onShow() {
},
onUnload() {
// 页面卸载时清除定时器
if (this.timer) clearInterval(this.timer)
},
mounted() {},
data() {
return {
/* 设定状态栏默认高度 */
statusBarHeight: 20,
phone: '',
code: '',
countdown: 0,
timer: null,
}
},
methods: {
// 获取验证码
getCode() {
// 1. 手机号校验
const phoneReg = /^1[3-9]\d{9}$/
if (!this.phone) {
uni.showToast({
title: '请输入手机号',
icon: 'none'
})
return
}
if (!phoneReg.test(this.phone)) {
uni.showToast({
title: '手机号格式不正确',
icon: 'none'
})
return
}
// 2. 模拟发送验证码(替换为实际接口)
uni.showLoading({
title: '发送中...'
})
setTimeout(() => {
uni.hideLoading()
uni.showToast({
title: '验证码已发送',
icon: 'none'
})
// 3. 开启60秒倒计时
this.countdown = 60
this.timer = setInterval(() => {
if (this.countdown <= 1) {
clearInterval(this.timer)
this.countdown = 0
return
}
this.countdown--
}, 1000)
}, 800)
},
// 提交修改
onSubmit() {
// 1. 基础校验
const phoneReg = /^1[3-9]\d{9}$/
if (!this.phone) {
uni.showToast({
title: '请输入手机号',
icon: 'none'
})
return
}
if (!phoneReg.test(this.phone)) {
uni.showToast({
title: '手机号格式不正确',
icon: 'none'
})
return
}
if (!this.code) {
uni.showToast({
title: '请输入验证码',
icon: 'none'
})
return
}
uni.showLoading({
title: '更换中...',
mask: true
});
setTimeout(() => {
uni.showToast({
title: '更换成功',
icon: 'success'
});
}, 1000);
setTimeout(() => {
// 提交成功后跳转列表页
uni.hideLoading();
this.goBack();
}, 2500);
},
goBack() {
uni.redirectTo({
url: '/pageSubPack/my/settingIndex',
success: res => {},
fail: () => {},
complete: () => {}
});
},
},
<script setup>
import { ref, onUnmounted } from 'vue'
import { getAuthSendSmsCode, getUserChangePhone } from '../api/apiSub.js'
// 响应式数据
const statusBarHeight = ref(20)
const phone = ref('')
const code = ref('')
const countdown = ref(0)
let timer = null // 定时器无需响应式,用普通变量
// 获取验证码
const getCode = () => {
const phoneReg = /^1[3-9]\d{9}$/
if (!phone.value) {
uni.showToast({ title: '请输入手机号', icon: 'none' })
return
}
if (!phoneReg.test(phone.value)) {
uni.showToast({ title: '手机号格式不正确', icon: 'none' })
return
}
uni.showLoading({ title: '发送中...' })
getAuthSendSmsCode({ phone: phone.value, scene: 'register' }).then(res => {
uni.hideLoading()
if (res.code === 200) {
uni.showToast({ title: '验证码已发送', icon: 'none' })
// 60秒倒计时
countdown.value = 60
timer = setInterval(() => {
if (countdown.value <= 1) {
clearInterval(timer)
countdown.value = 0
return
}
countdown.value--
}, 1000)
} else {
uni.showToast({ title: res.msg || '发送失败', icon: 'none' })
}
}).catch(() => {
uni.hideLoading()
uni.showToast({ title: '发送失败', icon: 'none' })
})
}
// 提交修改
const onSubmit = () => {
const phoneReg = /^1[3-9]\d{9}$/
if (!phone.value) {
uni.showToast({ title: '请输入手机号', icon: 'none' })
return
}
if (!phoneReg.test(phone.value)) {
uni.showToast({ title: '手机号格式不正确', icon: 'none' })
return
}
if (!code.value) {
uni.showToast({ title: '请输入验证码', icon: 'none' })
return
}
uni.showLoading({ title: '更换中...', mask: true })
getUserChangePhone({ newPhone: phone.value, smsCode: code.value,scene:'change_phone' }).then(res => {
uni.hideLoading()
if (res.code === 200) {
uni.showToast({ title: '更换成功', icon: 'success' })
setTimeout(() => goBack(), 1500)
} else {
uni.showToast({ title: res.msg || '更换失败', icon: 'none' })
}
}).catch(() => {
uni.hideLoading()
uni.showToast({ title: '更换失败', icon: 'none' })
})
}
// 返回
const goBack = () => {
uni.redirectTo({
url: '/pageSubPack/my/settingIndex'
})
}
// 页面卸载清除定时器
onUnmounted(() => {
if (timer) clearInterval(timer)
})
</script>
<style lang="scss">
page {
background: #f9f9f9;
}
page {
background: #f9f9f9;
}
</style>
<style scoped>
.contentStyle {
display: flex;
flex-direction: column;
height: 100vh;
/* 关键:占满屏幕高度 */
background-color: #f9f9f9;
}
.contentStyle {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #f9f9f9;
}
.page {
flex: 1;
overflow-y: auto;
padding: 0px 40rpx;
box-sizing: border-box;
/* background-color: #fff; */
}
.page {
flex: 1;
overflow-y: auto;
padding: 0px 40rpx;
box-sizing: border-box;
}
/* 底部按钮 */
.bottom-btn {
padding: 20rpx 30rpx 40rpx;
}
/* 底部按钮 */
.bottom-btn {
padding: 20rpx 30rpx 40rpx;
}
/* 表单容器 */
.form-container {
/* padding: 60rpx 30rpx; */
}
/* 表单容器 */
.form-container {}
/* 输入框组(手机号+验证码行) */
.input-group {
display: flex;
align-items: center;
height: 88rpx;
/* border: 1rpx solid #dcdcdc; */
border-radius: 8rpx;
margin-bottom: 30rpx;
padding: 0 20rpx;
box-sizing: border-box;
background-color: #fff;
}
/* 输入框组(手机号+验证码行) */
.input-group {
display: flex;
align-items: center;
height: 88rpx;
border-radius: 8rpx;
margin-bottom: 30rpx;
padding: 0 20rpx;
box-sizing: border-box;
background-color: #fff;
}
.iconStyle {
width:50rpx;
height: 50rpx;
}
.iconStyle {
width: 50rpx;
height: 50rpx;
}
.phone-input .input-item {
flex: 1;
height: 100%;
line-height: 88rpx;
border: none;
padding-left: 20rpx;
}
.phone-input .input-item {
flex: 1;
height: 100%;
line-height: 88rpx;
border: none;
padding-left: 20rpx;
}
/* 验证码输入框 */
.code-input .code-item {
flex: 1;
height: 100%;
line-height: 88rpx;
border: none;
font-size: 30rpx;
}
/* 验证码输入框 */
.code-input .code-item {
flex: 1;
height: 100%;
line-height: 88rpx;
border: none;
font-size: 30rpx;
}
.code-btn {
font-size: 24rpx;
color: #fff;
background: #2F77FD;
border: none;
padding: 0;
line-height: 88rpx;
width: 100px;
}
.code-btn {
font-size: 24rpx;
color: #fff;
background: #2F77FD;
border: none;
padding: 0;
line-height: 88rpx;
width: 100px;
}
.code-btn[disabled] {
color: #999;
}
.code-btn[disabled] {
color: #999;
}
/* 通用输入框样式 */
.input-item {
font-size: 30rpx;
color: #333;
background-color: #fff;
padding-left: 20rpx;
}
/* 通用输入框样式 */
.input-item {
font-size: 30rpx;
color: #333;
background-color: #fff;
padding-left: 20rpx;
}
.input-placeholder {
color: #999;
font-size: 30rpx;
}
.input-placeholder {
color: #999;
font-size: 30rpx;
}
/* 确定按钮 */
.submit-btn {
width: 100%;
height: 88rpx;
line-height: 88rpx;
background-color: #1677ff;
color: #fff;
font-size: 32rpx;
border-radius: 8rpx;
border: none;
margin-top: 20rpx;
}
/* 确定按钮 */
.submit-btn {
width: 100%;
height: 88rpx;
line-height: 88rpx;
background-color: #1677ff;
color: #fff;
font-size: 32rpx;
border-radius: 8rpx;
border: none;
margin-top: 20rpx;
}
.status-bar {
width: 100vw;
height: 46px;
}
.status-bar {
width: 100vw;
height: 46px;
}
</style>

View File

@@ -34,98 +34,57 @@
</template>
<script>
export default {
onReady: function(e) {},
components: {
},
computed: {
},
created() {
},
onShow() {
},
onLoad() {
},
mounted() {},
data() {
return {
/* 设定状态栏默认高度 */
statusBarHeight: 20,
oldPwd: '',
newPwd: '',
confirmPwd: '',
<script setup>
import { ref } from 'vue'
import { authChangePassword } from '../api/apiSub.js'
const statusBarHeight = ref(20);
const oldPwd = ref('');
const newPwd = ref('');
const confirmPwd = ref('')
// 方法
const onSubmit = () => {
if (!oldPwd.value) {
uni.showToast({ title: '请输入原密码', icon: 'none' })
return
}
if (!newPwd.value) {
uni.showToast({ title: '请输入新密码', icon: 'none' })
return
}
if (newPwd.value !== confirmPwd.value) {
uni.showToast({ title: '两次密码不一致', icon: 'none' })
return
}
uni.showLoading({ title: '修改中...', mask: true })
authChangePassword({
oldPassword: oldPwd.value,
newPassword: newPwd.value,
confirmPassword: confirmPwd.value
}).then(res => {
uni.hideLoading()
if (res.code === 200) {
uni.showToast({ title: '修改成功', icon: 'success' })
setTimeout(() => goBack(), 1500)
} else {
uni.showToast({ title: res.msg || '修改失败', icon: 'none' })
}
},
methods: {
// 提交修改
onSubmit() {
// 1. 基础校验
if (!this.oldPwd) {
uni.showToast({
title: '请输入原密码',
icon: 'none'
})
return
}
if (!this.newPwd) {
uni.showToast({
title: '请输入新密码',
icon: 'none'
})
return
}
if (this.newPwd !== this.confirmPwd) {
uni.showToast({
title: '两次密码不一致',
icon: 'none'
})
return
}
uni.showLoading({
title: '修改中...',
mask: true
});
setTimeout(() => {
uni.showToast({
title: '修改成功',
icon: 'success'
});
}, 1000);
setTimeout(() => {
// 提交成功后跳转列表页
uni.hideLoading();
this.goBack();
}, 2500);
},
goBack() {
uni.redirectTo({
url: '/pageSubPack/my/settingIndex',
success: res => {},
fail: () => {},
complete: () => {}
});
},
},
}).catch((err) => {
uni.hideLoading()
uni.showToast({ title: err.msg || '修改失败', icon: 'none' })
})
}
const goBack = () =>{
uni.redirectTo({
url: '/pageSubPack/my/settingIndex',
});
}
</script>
<style lang="scss">
page {

View File

@@ -39,7 +39,7 @@
onPullDownRefresh
} from '@dcloudio/uni-app'
import {
authLogout
authLogout, getUserProfile, getUserNotify, changeUserNotify
} from '/pageSubPack/api/apiSub.js'
//
@@ -47,7 +47,8 @@
const cacheSize = ref(0)
const settingList = ref([{
title: '更改手机号码',
type: 'arrow'
type: 'text',
value: ''
},
{
title: '修改密码',
@@ -144,7 +145,19 @@
}
const onSwitchChange = (item, e) => {
item.checked = e.detail.value
const checked = e.detail.value
const notifyEnabled = checked ? '1' : '0'
changeUserNotify({ notifyEnabled }).then(res => {
if (res.code === 200) {
item.checked = checked
} else {
uni.showToast({ title: res.msg || '更新失败', icon: 'none' })
item.checked = !checked
}
}).catch(() => {
uni.showToast({ title: '更新失败', icon: 'none' })
item.checked = !checked
})
}
const goBack = () => {
@@ -152,6 +165,20 @@
url: '/pages/myIndex/myIndex'
});
}
onMounted(() => {
getUserProfile().then(res => {
if (res.code === 200 && res.data) {
const phone = res.data.phone || ''
settingList.value[0].value = phone
}
})
getUserNotify().then(res => {
if (res.code === 200 && res.data) {
settingList.value[2].checked = res.data.notifyEnabled === '1'
}
})
})
</script>
<style lang="scss">

View File

@@ -9,7 +9,7 @@
@clickLeft="narBack"
/>
<!-- 滚动区域能正常滚动的核心 -->
<!-- 滚动区域 -->
<scroll-view
scroll-y
class="scroll-box"

View File

@@ -165,7 +165,7 @@ const getCommunityList = async (pageNum = 1, keyword = '') => {
displayedCommunities.value = []
hasMoreData.value = false
uni.showToast({
title: "网络异常",
title: err.msg || "网络异常",
icon: 'error'
})
} finally {
@@ -267,7 +267,7 @@ const handleConfirm = async () => {
} catch (err) {
uni.hideLoading()
uni.showToast({
title: '网络异常,请重试',
title: err.msg || '网络异常,请重试',
icon: 'error',
duration: 2000
})

View File

@@ -55,11 +55,11 @@
<view class="upload-wrap">
<!-- 已上传图片 -->
<view class="upload-item" v-for="(img, index) in form.images" :key="index">
<image :src="img" mode="aspectFill" @click="previewImage(img)"></image>
<uni-icons type="close" size="16" @click="deleteImage(index)"></uni-icons>
<image :src="img" mode="aspectFill" class="upload-img" @click="previewImage(img)"></image>
<image src="http://47.104.199.163:9090/images/imgs/delete.png" class="upload-delete" @click="deleteImage(index)"></image>
</view>
<!-- 上传按钮 -->
<view class="upload-btn" @click="chooseImage">
<view class="upload-btn" @click="chooseImage" v-if="form.images.length < 3">
<uni-icons type="plus" size="24"></uni-icons>
<text>上传照片</text>
</view>
@@ -298,17 +298,28 @@ const goToSelectProject = () => {
uni.navigateTo({ url: '/pageSubPack/online-repair/personalSelectPoject' })
}
// 图片
// 选择图片
const chooseImage = () => {
// 计算还能选几张
const remainCount = 3 - form.value.images.length;
if (remainCount <= 0) {
uni.showToast({
title: '最多只能上传3张图片',
icon: 'none'
})
return;
}
uni.chooseImage({
count: 3,
count: remainCount, // 动态限制可选数量
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
form.value.images = [...form.value.images, ...res.tempFilePaths]
const tempFilePaths = res.tempFilePaths
form.value.images = [...form.value.images, ...tempFilePaths]
}
})
}
// 预览图片
const previewImage = (url) => {
uni.previewImage({ urls: form.value.images, current: url })
}
@@ -326,7 +337,7 @@ const uploadImages = async () => {
}
try {
const res = await uploadFile(path)
urls.push(res.data || res.msg || '')
urls.push(res.url || '')
} catch (e) {
console.error('上传失败', e)
}
@@ -469,24 +480,25 @@ textarea {
width: 80px;
height: 80px;
}
.upload-item image {
.upload-item .upload-img{
width: 100%;
height: 100%;
border-radius: 4px;
}
.upload-item uni-icons {
.upload-item .upload-delete{
position: absolute;
top: -5px;
right: -5px;
background: #ff3b30;
color: #fff;
border-radius: 50%;
width: 32rpx;
height: 32rpx;
border-radius: 3rpx;
padding: 2px;
}
.upload-btn {
width: 80px;
height: 80px;
border: 1px dashed #ccc;
/* border: 1px dashed #ccc; */
background-color: #f3f3f3;
border-radius: 4px;
display: flex;
flex-direction: column;

View File

@@ -38,11 +38,12 @@
<view class="upload-wrap">
<!-- 已上传图片 -->
<view class="upload-item" v-for="(img, index) in form.images" :key="index">
<image :src="img" mode="aspectFill"></image>
<uni-icons type="close" size="16" @click="deleteImage(index)"></uni-icons>
<image :src="img" mode="aspectFill" class="upload-img"></image>
<image src="http://47.104.199.163:9090/images/imgs/delete.png" class="upload-delete" @click="deleteImage(index)"></image>
<!-- <uni-icons type="close" size="16" @click="deleteImage(index)"></uni-icons> -->
</view>
<!-- 上传按钮 -->
<view class="upload-btn" @click="chooseImage">
<view class="upload-btn" @click="chooseImage" v-if="form.images.length < 3">
<uni-icons type="plus" size="24"></uni-icons>
<text>上传照片</text>
</view>
@@ -93,15 +94,22 @@ const goBackSelect = () => {
// 选择图片
const chooseImage = () => {
// 计算还能选几张
const remainCount = 3 - form.value.images.length;
if (remainCount <= 0) {
uni.showToast({
title: '最多只能上传3张图片',
icon: 'none'
})
return;
}
uni.chooseImage({
count: 3, // 最多选3张
count: remainCount, // 动态限制可选数量
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
// 临时文件路径
const tempFilePaths = res.tempFilePaths
form.value.images = [...form.value.images, ...tempFilePaths]
}
})
}
@@ -111,7 +119,7 @@ const deleteImage = (index) => {
form.value.images.splice(index, 1)
}
// 上传图片到服务器
// 上传图片
const uploadImages = async () => {
const uploadedUrls = []
for (const path of form.value.images) {
@@ -122,9 +130,12 @@ const uploadImages = async () => {
try {
const res = await uploadFile(path)
// 根据后端实际返回结构调整字段名
uploadedUrls.push(res.data || res.msg || '')
} catch (e) {
console.error('图片上传失败', e)
uploadedUrls.push(res.url || '')
} catch (err) {
uni.showToast({
title:e.msg || '图片上传失败',
icon:'error'
})
}
}
return uploadedUrls
@@ -250,24 +261,25 @@ const submitForm = async () => {
width: 80px;
height: 80px;
}
.upload-item image {
.upload-item .upload-img{
width: 100%;
height: 100%;
border-radius: 4px;
}
.upload-item uni-icons {
.upload-item .upload-delete{
position: absolute;
top: -5px;
right: -5px;
background: #ff3b30;
color: #fff;
border-radius: 50%;
width: 32rpx;
height: 32rpx;
border-radius: 3rpx;
padding: 2px;
}
.upload-btn {
width: 80px;
height: 80px;
border: 1px dashed #ccc;
/* border: 1px dashed #ccc; */
background-color: #f3f3f3;
border-radius: 4px;
display: flex;
flex-direction: column;

View File

@@ -267,11 +267,12 @@ const previewImage = (url) => {
.photo-item {
width: 80px;
height: 80px;
border: 1px dashed #ccc;
/* border: 1px solid #ccc; */
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
background-color: #f5f5f5;
}
.btn-container {

View File

@@ -1,13 +1,11 @@
<template>
<view class="contentStyle" style="">
<view class="contentStyle">
<view class="status-bar"></view>
<uni-nav-bar title="投诉建议" backgroundColor="transparent" :border="false" left-icon="left" @clickLeft="goBack">
</uni-nav-bar>
<!-- 表单区域 -->
<scroll-view class="page" scroll-y="true">
<scroll-view class="page" scroll-y>
<!-- 表单主体 -->
<view class="form-content">
<text class="form-tip">请填写反馈信息</text>
@@ -19,9 +17,10 @@
<text class="label-text">投诉类型</text>
</view>
<view class="row-right">
<text class="value-text"
:class="{placeholderStyle:selectedType}">{{ selectedType || '请选择投诉类型' }}</text>
<text class="arrow-icon">></text>
<text class="value-text" :class="{placeholderStyle:selectedType}">
{{ selectedType || '请选择投诉类型' }}
</text>
<uni-icons type="arrowright" size="16"></uni-icons>
</view>
</view>
@@ -32,9 +31,11 @@
<text class="label-text">投诉物业</text>
</view>
<view class="row-right">
<text class="value-text"
:class="{placeholderStyle:selectedProperty}">{{ selectedProperty || '请选择' }}</text>
<text class="arrow-icon">></text>
<text class="value-text" :class="{placeholderStyle:selectedProperty}">
{{ selectedProperty || '请选择' }}
</text>
<uni-icons type="arrowright" size="16"></uni-icons>
<!-- <text class="arrow-icon"> > </text> -->
</view>
</view>
@@ -45,8 +46,7 @@
<text class="label-text">问题描述</text>
</view>
<view class="row-right desc-right">
<textarea v-model="description" placeholder="请描述您的问题" class="desc-input" auto-height
maxlength="500"></textarea>
<textarea v-model="description" placeholder="请描述您的问题" class="desc-input" auto-height maxlength="500"></textarea>
</view>
</view>
@@ -93,15 +93,13 @@
</view>
</view>
</view>
</scroll-view>
<!-- 底部下一步按钮 -->
<view class="bottom-btn-wrap">
<button class="next-btn" @click="submitForm">提交</button>
</view>
<!-- ================== 自定义Popup弹窗纯view实现 ================== -->
<!-- 蒙层 -->
<view class="mask" v-if="showTypePopup || showPropertyPopup" @click="closeAllPopup"></view>
@@ -113,195 +111,188 @@
<text class="title">选择投诉类型</text>
<text class="close" @click="showTypePopup = false"></text>
</view>
<view class="list">
<view :class="{ active: selectedType === item }" class="item" v-for="(item, index) in typeOptions"
:key="index" @click="selectType(item)">
{{ item }}
<view :class="{ active: selectedType === item }" class="item" v-for="(item, index) in typeOptions" :key="index" @click="selectType(item)">
{{ item.name }}
</view>
</view>
</view>
<!-- 投诉物业弹窗 -->
<view v-if="showPropertyPopup" class="mask" @click="showPropertyPopup=false"></view>
<view v-if="showPropertyPopup" class="popup">
<view class="popup-header">
<text class="title">选择投诉类型</text>
<text class="title">选择投诉物业</text>
<text class="close" @click="showPropertyPopup = false"></text>
</view>
<view class="list">
<view :class="{ active: selectedProperty === item }" class="item"
v-for="(item, index) in propertyOptions" :key="index" @click="selectProperty(item)">
<view :class="{ active: selectedProperty === item }" class="item" v-for="(item, index) in propertyOptions" :key="index" @click="selectProperty(item)">
{{ item }}
</view>
</view>
</view>
</view>
</template>
<script>
export default {
onReady: function(e) {},
components: {
<script setup>
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { getComplainType, addComplain } from '../api/apiSub.js'
import { uploadFile } from '../api/api.js'
},
// 弹窗控制
const showTypePopup = ref(false)
const showPropertyPopup = ref(false)
computed: {
// 下拉选项
const typeOptions = ref([])
const propertyOptions = ref(['XX物业A', 'XX物业B', 'XX物业C'])
// 表单数据
const selectedType = ref('')
const selectedTypeId = ref('')
const selectedProperty = ref('')
const description = ref('')
const name = ref('')
const phone = ref('')
const villageId = ref('')
// 多图上传
const tempFilePaths = ref([])
const maxCount = ref(9)
// 关闭所有弹窗
const closeAllPopup = () => {
showTypePopup.value = false
showPropertyPopup.value = false
}
},
created() {
},
onShow() {
},
mounted() {},
onReady() {
this.$nextTick(() => {
})
},
data() {
return {
/* 设定状态栏默认高度 */
statusBarHeight: 20,
// 弹窗控制
showTypePopup: false,
showPropertyPopup: false,
// 下拉选项
typeOptions: ['物业服务', '社区服务', '其他'],
propertyOptions: ['XX物业A', 'XX物业B', 'XX物业C'], // 替换为实际数据
// 表单数据
selectedType: '',
selectedProperty: '',
description: '',
name: '',
phone: '',
// 多图上传
tempFilePaths: [],
maxCount: 9 // 最大上传9张
}
},
methods: {
// 返回上一页
navigateBack() {
uni.navigateBack();
},
// 关闭所有弹窗
closeAllPopup() {
this.showTypePopup = false;
this.showPropertyPopup = false;
},
// 选择投诉类型
selectType(item) {
this.selectedType = item;
this.showTypePopup = false;
// 切换类型时清空物业选择
if (item !== '物业服务') {
this.selectedProperty = '';
}
},
// 选择物业
selectProperty(item) {
this.selectedProperty = item;
this.showPropertyPopup = false;
},
// 多图选择
chooseImages() {
uni.chooseImage({
count: this.maxCount - this.tempFilePaths.length,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
this.tempFilePaths = [...this.tempFilePaths, ...res.tempFilePaths];
}
});
},
// 删除图片
deletePhoto(index) {
this.tempFilePaths.splice(index, 1);
},
// 表单提交
submitForm() {
// 基础校验
if (!this.selectedType) {
return uni.showToast({
title: '请选择投诉类型',
icon: 'none'
});
}
if (this.selectedType === '物业服务' && !this.selectedProperty) {
return uni.showToast({
title: '请选择物业',
icon: 'none'
});
}
if (!this.description.trim()) {
return uni.showToast({
title: '请输入问题描述',
icon: 'none'
});
}
uni.showLoading({
title: '提交中...',
mask: true
});
setTimeout(() => {
uni.showToast({
title: '提交成功',
icon: 'success'
});
}, 1000);
setTimeout(() => {
// 提交成功后跳转列表页
uni.hideLoading();
this.goBack();
}, 2500);
},
goBack() {
uni.redirectTo({
url: '/pageSubPack/service-list/complaintsSuggestionsIndex',
success: res => {},
fail: () => {},
complete: () => {}
});
},
},
onLoad(() => {
getSelectType()
villageId.value = uni.getStorageSync('currentVillageId') || ''
})
// 获取投诉类型
const getSelectType = async () => {
try {
const res = await getComplainType()
if (res.code === 200) {
typeOptions.value = res.data || []
} else {
uni.showToast({ title: res.msg || '获取失败', icon: 'none' })
}
} catch (err) {
uni.showToast({ title: '网络异常', icon: 'none' })
}
}
// 选择投诉类型
const selectType = (item) => {
selectedType.value = item.name
selectedTypeId.value = item.id || ''
showTypePopup.value = false
// 切换类型时清空物业选择
if (item.name !== '物业服务') {
selectedProperty.value = ''
}
}
// 选择物业
const selectProperty = (item) => {
selectedProperty.value = item
showPropertyPopup.value = false
}
// 多图选择
const chooseImages = () => {
uni.chooseImage({
count: maxCount.value - tempFilePaths.value.length,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
tempFilePaths.value = [...tempFilePaths.value, ...res.tempFilePaths]
}
})
}
// 上传单张图片
const uploadImageData = async (filePath) => {
try {
const res = await uploadFile(filePath)
return res.url || ''
} catch (err) {
console.error('单张图片上传失败', err)
return ''
}
}
// 删除图片
const deletePhoto = (index) => {
tempFilePaths.value.splice(index, 1)
}
// 表单提交
const submitForm = async () => {
// 校验
if (!selectedTypeId.value) {
return uni.showToast({ title: '请选择投诉类型', icon: 'none' })
}
if (selectedType.value === '物业服务' && !selectedProperty.value) {
return uni.showToast({ title: '请选择物业', icon: 'none' })
}
if (!description.value.trim()) {
return uni.showToast({ title: '请输入问题描述', icon: 'none' })
}
uni.showLoading({ title: '提交中...', mask: true })
try {
// 上传图片
let imageUrls = []
for (let path of tempFilePaths.value) {
let url = await uploadImageData(path)
if (url) imageUrls.push(url)
}
// 构造提交参数
const params = {
typeId: selectedTypeId.value,
problemDes: description.value.trim(),
complaintName: name.value || '',
phone: phone.value || '',
villageId: villageId.value || '',
problemImageUrl: imageUrls.join(',')
}
// 调用提交接口
const res = await addComplain(params)
uni.hideLoading()
if (res.code === 200) {
uni.showToast({ title: '提交成功', icon: 'success' })
setTimeout(() => {
uni.redirectTo({
url: '/pageSubPack/service-list/complaintsSuggestionsIndex'
})
}, 1500)
} else {
uni.showToast({ title: res.msg || '提交失败', icon: 'none' })
}
} catch (err) {
uni.hideLoading()
uni.showToast({
title: err.msg || '提交失败,请重试',
icon: 'none'
})
}
}
// 返回
const goBack = () => {
uni.redirectTo({
url: '/pageSubPack/service-list/complaintsSuggestionsIndex'
})
}
</script>
<style lang="scss">
page {
@@ -314,8 +305,6 @@
display: flex;
flex-direction: column;
height: 100vh;
/* 关键:占满屏幕高度 */
/* background: linear-gradient(to left bottom, #e2efff 0%, #f9f9f9 50%); */
}
.page {
@@ -397,7 +386,6 @@
/* 问题描述行 */
.desc-row {
align-items: flex-start;
padding-top: 32rpx;
padding-bottom: 32rpx;
}
@@ -486,7 +474,6 @@
line-height: 1;
}
/* 底部按钮 */
.bottom-btn-wrap {
padding: 20rpx 30rpx 40rpx;
@@ -532,7 +519,6 @@
.popup-header {
padding: 30rpx;
text-align: center;
/* border-bottom: 1rpx solid #f0f0f0; */
position: relative;
}

View File

@@ -1,12 +1,11 @@
<template>
<view class="contentStyle" style="">
<view class="contentStyle">
<view class="status-bar"></view>
<uni-nav-bar title="投诉详情" backgroundColor="transparent" :border="false" left-icon="left" @clickLeft="goBack">
</uni-nav-bar>
<!-- 表单区域 -->
<scroll-view class="page" scroll-y="true">
<scroll-view class="page" scroll-y>
<view class="page-container">
<!-- 投诉状态 -->
<view class="status-section">
@@ -14,10 +13,12 @@
<text class="title-line"></text>
<text class="title-text">投诉状态</text>
</view>
<text class="status-value"
:class="status=='pending'?'pending':status=='processing'?'processing':status=='completed'?'completed':''">{{status=='pending'?'待处理':status=='processing'?'处理中':status=='completed'?'已完成':''}}</text>
<text class="status-value" :class="statusClass">{{ statusText }}</text>
</view>
<view class="completedDesc" v-if="status=='completed'">已完成处理已联系工作人员粘贴标语提醒并派人定时巡视</view>
<view class="completedDesc" v-if="detail.status === '2' && detail.handleContent">
{{ detail.handleContent }}
</view>
<!-- 投诉详情 -->
<view class="detail-section">
<view class="section-title">
@@ -25,122 +26,106 @@
<text class="title-text">投诉详情</text>
</view>
<!-- 投诉类型 -->
<view class="info-item">
<text class="info-label">投诉类型</text>
<text class="info-value">社区服务</text>
<text class="info-value">{{ detail.typeName}}</text>
</view>
<!-- 问题描述 -->
<view class="info-item desc-item">
<text class="info-label">问题描述</text>
<text class="info-value desc-value">
投诉内容投诉内容投诉内容投诉内容投诉内容投诉内容投诉内容投诉内容投诉内容投诉内容
</text>
<text class="info-value desc-value">{{ detail.problemDes }}</text>
</view>
<!-- 投诉人姓名 -->
<view class="info-item">
<text class="info-label">投诉人姓名</text>
<text class="info-value">匿名</text>
<text class="info-value">{{ detail.complaintName }}</text>
</view>
<!-- 手机号码 -->
<view class="info-item">
<text class="info-label">手机号码</text>
<text class="info-value"></text>
<text class="info-value">{{ detail.phone}}</text>
</view>
<!-- 问题照片 -->
<view class="photo-section">
<view class="photo-section" v-if="photoList.length > 0">
<text class="photo-label">问题照片</text>
<view class="photo-list">
<image v-for="(img, index) in photoList" :key="index" :src="img" mode="aspectFill"
class="photo-item"></image>
class="photo-item" @click="previewImage(index)"></image>
</view>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script>
export default {
onReady: function(e) {},
components: {
<script setup>
import { ref, computed } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { getComplainDetail } from '../api/apiSub.js'
},
const id = ref('')
computed: {
const detail = ref({})
const photoList = ref([])
// 状态映射
const statusMap = {
'0': { text: '待处理', class: 'pending' },
'1': { text: '处理中', class: 'processing' },
'2': { text: '已完成', class: 'completed' }
}
const statusText = computed(() => {
return statusMap[detail.value.status]?.text || '待处理'
})
const statusClass = computed(() => {
return statusMap[detail.value.status]?.class || 'pending'
})
},
created() {
},
onShow() {
},
onLoad(option) {
this.id = option.id;
this.status = option.status;
},
mounted() {},
onReady() {
this.$nextTick(() => {
})
},
data() {
return {
id: '',
status: '',
/* 设定状态栏默认高度 */
statusBarHeight: 20,
// 问题照片列表,替换为你的实际图片路径
photoList: [
"http://47.104.199.163:9090/images/propertyImgs/community1.png",
"http://47.104.199.163:9090/images/propertyImgs/community2.png",
"http://47.104.199.163:9090/images/propertyImgs/community3.png"
]
}
},
methods: {
goBack() {
uni.redirectTo({
url: '/pageSubPack/service-list/complaintsSuggestionsIndex',
success: res => {},
fail: () => {},
complete: () => {}
});
},
},
onLoad((option) => {
id.value = option.id || ''
if (id.value) {
detailData()
}
})
// 获取详情
const detailData = async () => {
try {
const res = await getComplainDetail({ id: id.value })
if (res.code === 200 && res.data) {
detail.value = res.data
// 图片URL逗号分隔转数组
if (res.data.problemImageUrl) {
photoList.value = res.data.problemImageUrl.split(',').filter(url => url.trim())
} else {
photoList.value = []
}
} else {
uni.showToast({ title: res.msg || '获取失败', icon: 'none' })
}
} catch (err) {
uni.showToast({ title: typeof err === 'string' ? err.msg : '网络异常', icon: 'none' })
}
}
// 预览图片
const previewImage = (index) => {
uni.previewImage({
current: photoList.value[index],
urls: photoList.value
})
}
// 返回
const goBack = () => {
uni.redirectTo({
url: '/pageSubPack/service-list/complaintsSuggestionsIndex'
})
}
</script>
<style lang="scss">
page {
background: #fff;
@@ -152,8 +137,6 @@
display: flex;
flex-direction: column;
height: 100vh;
/* 关键:占满屏幕高度 */
/* background: linear-gradient(to left bottom, #e2efff 0%, #f9f9f9 50%); */
}
.page {
@@ -161,12 +144,10 @@
overflow-y: auto;
padding: 0px 40rpx;
box-sizing: border-box;
/* background-color: #fff; */
}
.page-container {}
/* 通用标题样式(带蓝色竖线) */
.section-title {
display: flex;
align-items: center;
@@ -187,7 +168,6 @@
color: #333333;
}
/* 投诉状态区域 */
.status-section {
display: flex;
justify-content: space-between;
@@ -210,7 +190,6 @@
font-weight: 500;
}
/* 详情信息项通用样式 */
.info-item {
display: flex;
padding: 24rpx 0;
@@ -233,7 +212,6 @@
line-height: 1.5;
}
/* 问题描述特殊样式(多行文本) */
.desc-item {
align-items: flex-start;
}
@@ -243,7 +221,6 @@
word-break: break-all;
}
/* 问题照片区域 */
.photo-section {
padding: 24rpx 0;
}
@@ -258,9 +235,7 @@
.photo-list {
display: flex;
flex-wrap: wrap;
/* 自动换行 */
gap: 20rpx;
/* 图片之间的间距(可改) */
margin: 10rpx 0px 20rpx 0px;
}
@@ -279,7 +254,6 @@
}
.completed {
color: #999999;
}
@@ -287,4 +261,4 @@
width: 100vw;
height: 46px;
}
</style>
</style>

View File

@@ -1,228 +1,151 @@
<template>
<view class="contentStyle" style="">
<view class="contentStyle">
<view class="status-bar"></view>
<uni-nav-bar title="投诉建议" backgroundColor="transparent" :border="false" left-icon="left" @clickLeft="goBack">
</uni-nav-bar>
<!-- 1. 顶部分段控制器 -->
<!-- 顶部分段控制器 -->
<view style="width: 60vw;">
<uni-segmented-control :current="currentTab" :values="tabs" @clickItem="onTabClick" style-type="text"
active-color="#007AFF" inactive-color="#999" class="seg-control"></uni-segmented-control>
</view>
<!-- 表单区域 -->
<scroll-view class="page" scroll-y="true">
<!-- 列表区域 -->
<scroll-view class="page" scroll-y>
<view class="page-container">
<view v-if="dataList.length === 0" class="empty-state">
<uni-icons type="info" size="60" color="#ccc"></uni-icons>
<text class="empty-text">
{{ '暂无数据'}}
</text>
<text class="empty-text">暂无数据</text>
</view>
<!-- 2. 投诉卡片主体 -->
<view class="card-wrapper" v-for="item,index in dataList" :key="index">
<!-- 卡片头部标题 + 标签 -->
<!-- 投诉卡片 -->
<view class="card-wrapper" v-for="(item, index) in dataList" :key="index">
<view class="card-header">
<text class="card-title">{{item.title}}</text>
<text class="card-title">{{ item.title }}</text>
<text class="card-tag"
:class="item.status=='pending'?'pending':item.status=='processing'?'processing':item.status=='completed'?'completed':''">{{item.status=='pending'?'待处理':item.status=='processing'?'处理中':item.status=='completed'?'已完成':''}}</text>
:class="item.status === 'pending' ? 'pending' : item.status === 'processing' ? 'processing' : 'completed'">
{{ item.status === 'pending' ? '待处理' : item.status === 'processing' ? '处理中' : '已完成' }}
</text>
</view>
<!-- 卡片内容描述 + 横向图片 -->
<view class="card-body">
<text class="card-desc">
{{item.desc}}
</text>
<text class="card-desc">{{ item.desc }}</text>
<!-- 横向图片滚动区 -->
<view class="img-scroll-container">
<view v-for="(img, imgIndex) in item.imgList" :key="imgIndex" class="img-item">
<image :src="img" mode="widthFix" class="card-img"></image>
</view>
</view>
<!-- 时间 & 详情 -->
<view class="card-footer">
<text class="card-time">{{item.time}}</text>
<text class="card-time">{{ item.time }}</text>
<text class="card-detail" @click="turnToDetail(item)">投诉详情</text>
</view>
</view>
</view>
</view>
</scroll-view>
<!-- 底部按钮 -->
<view class="bottom-btn-wrap">
<button class="next-btn" @click="addComplaintsSuggestions">投诉建议</button>
</view>
</view>
</template>
<script>
export default {
onReady: function(e) {},
components: {
<script setup>
import { ref, onMounted } from 'vue'
import {onLoad} from '@dcloudio/uni-app'
import {getComplainList} from '../api/apiSub.js'
},
// 分段器
const tabs = ref(["待处理", "处理中", "已完成"])
const currentTab = ref(0)
const dataList = ref([])
computed: {
},
created() {
},
onShow() {
},
mounted() {
this.getDataList()
},
onReady() {
this.$nextTick(() => {
})
},
data() {
return {
/* 设定状态栏默认高度 */
statusBarHeight: 20,
// 分段器数据
tabs: ["待处理", "处理中", "已完成"],
currentTab: 0,
dataList: [],
pendingList: [{
id: 1,
title: '社区服务',
status: 'pending',
desc: '1栋楼下道路路灯不亮并且有的亮度不够请物业公司及时更换防止发生意外事故。',
time: '2026-08-01 10:05:46',
imgList: [
"http://47.104.199.163:9090/images/propertyImgs/community1.png",
"http://47.104.199.163:9090/images/propertyImgs/community2.png",
"http://47.104.199.163:9090/images/propertyImgs/community3.png"
]
},
{
id: 1,
title: '社区服务',
status: 'pending',
desc: '1栋楼下道路路灯不亮并且有的亮度不够请物业公司及时更换防止发生意外事故。',
time: '2026-08-01 10:05:46',
imgList: [
"http://47.104.199.163:9090/images/propertyImgs/community1.png",
"http://47.104.199.163:9090/images/propertyImgs/community2.png",
"http://47.104.199.163:9090/images/propertyImgs/community3.png"
]
},
{
id: 1,
title: '社区服务',
status: 'pending',
desc: '1栋楼下道路路灯不亮并且有的亮度不够请物业公司及时更换防止发生意外事故。',
time: '2026-08-01 10:05:46',
imgList: [
"http://47.104.199.163:9090/images/propertyImgs/community1.png",
"http://47.104.199.163:9090/images/propertyImgs/community2.png",
"http://47.104.199.163:9090/images/propertyImgs/community3.png"
]
},
{
id: 1,
title: '社区服务',
status: 'pending',
desc: '1栋楼下道路路灯不亮并且有的亮度不够请物业公司及时更换防止发生意外事故。',
time: '2026-08-01 10:05:46',
imgList: [
"http://47.104.199.163:9090/images/propertyImgs/community1.png",
"http://47.104.199.163:9090/images/propertyImgs/community2.png",
"http://47.104.199.163:9090/images/propertyImgs/community3.png"
]
}
],
processingList: [{
id: 2,
title: '社区服务',
status: 'processing',
desc: '1栋楼下道路路灯不亮并且有的亮度不够请物业公司及时更换防止发生意外事故。',
time: '2026-08-01 10:05:46',
imgList: []
}],
completedList: [{
id: 3,
title: '社区服务',
status: 'completed',
desc: '1栋楼下道路路灯不亮并且有的亮度不够请物业公司及时更换防止发生意外事故。',
time: '2026-08-01 10:05:46',
imgList: [
"http://47.104.199.163:9090/images/propertyImgs/community1.png",
"http://47.104.199.163:9090/images/propertyImgs/community2.png",
"http://47.104.199.163:9090/images/propertyImgs/community3.png"
]
}],
}
},
methods: {
turnToDetail(item) {
uni.redirectTo({
url: '/pageSubPack/service-list/complaintsDetail?id=' + item.id + '&status=' + item.status,
success: res => {},
fail: () => {},
complete: () => {}
});
},
getDataList() {
if (this.currentTab == 0) {
this.dataList = this.pendingList
} else if (this.currentTab == 1) {
this.dataList = this.processingList
} else if (this.currentTab == 2) {
this.dataList = this.completedList
}
},
addComplaintsSuggestions() {
uni.redirectTo({
url: '/pageSubPack/service-list/addComplaints',
success: res => {},
fail: () => {},
complete: () => {}
});
},
onTabClick(e) {
this.currentTab = e.currentIndex;
this.getDataList()
},
goBack() {
uni.switchTab({
url: '/pages/serviceIndex/serviceIndex',
success: res => {},
fail: () => {},
complete: () => {}
});
},
},
// 模拟数据
const pendingList = ref([])
const processingList = ref([])
const completedList = ref([])
// 状态映射
const statusMap = {
"0":'pending',
'1':'processing',
'2':'completed'
}
// 接口数据映射为模板所需格式
const mapItem = (item) => {
// 图片:单 URL 转数组
let imgList = []
if (item.problemImageUrl) {
imgList = [item.problemImageUrl]
}
return {
id: item.id,
title: item.typeName,
status: statusMap[item.status] || 'pending',
desc: item.problemDes || '',
time: item.createTime || '',
imgList
}
}
// 切换tab
const onTabClick = (e) => {
currentTab.value = e.currentIndex
getComplainListData()
}
// 获取列表数据
const getComplainListData = async () =>{
try{
let params ={
status:currentTab.value,
villageId:uni.getStorageSync('currentVillageId')
}
const res = await getComplainList(params)
if(res.code === 200){
dataList.value = res.data.map(mapItem)
}else{
dataList.value = []
uni.showToast({title:res.msg || '获取失败',icon:'error'})
}
}catch(err){
// console.log('dess',err)
uni.showToast({title:err.msg || '网络异常',icon:'error'})
}
}
onLoad(() =>{
getComplainListData()
})
// 跳转到详情
const turnToDetail = (item) => {
uni.navigateTo({
url: `/pageSubPack/service-list/complaintsDetail?id=${item.id}&status=${item.status}`
})
}
// 新增投诉
const addComplaintsSuggestions = () => {
uni.navigateTo({
url: '/pageSubPack/service-list/addComplaints'
})
}
// 返回
const goBack = () => {
uni.switchTab({
url: '/pages/serviceIndex/serviceIndex'
})
}
// 页面挂载后加载数据
// onMounted(() => {
// getDataList()
// })
</script>
<style lang="scss">
page {
background: #f2f1f6;
@@ -234,7 +157,6 @@
display: flex;
flex-direction: column;
height: 100vh;
/* 关键:占满屏幕高度 */
background: linear-gradient(to left bottom, #e2efff 0%, #f9f9f9 50%);
}
@@ -243,14 +165,12 @@
overflow-y: auto;
padding: 0px 40rpx;
box-sizing: border-box;
/* background-color: #fff; */
}
.page-container {
padding-top: 20rpx;
}
/* 空状态 */
.empty-state {
display: flex;
flex-direction: column;
@@ -267,27 +187,18 @@
margin-bottom: 10rpx;
}
/* --- 分段控制器样式 --- */
.seg-control {
margin-bottom: 20rpx;
}
.cardScroll {
padding: 0rpx 0rpx 0px 0rpx;
overflow-y: scroll;
}
/* --- 卡片主体样式 --- */
.card-wrapper {
background: #ffffff;
border-radius: 16rpx;
padding: 30rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
margin-bottom: 20rpx;
}
/* 卡片头部:标题 & 标签 */
.card-header {
display: flex;
align-items: center;
@@ -322,14 +233,11 @@
color: #FFFFFF;
}
/* 卡片内容区 */
.card-body {
display: flex;
flex-direction: column;
}
/* 描述文本 */
.card-desc {
font-size: 28rpx;
color: #666666;
@@ -337,31 +245,24 @@
margin-bottom: 20rpx;
}
/* 图片容器 */
.img-scroll-container {
display: flex;
flex-wrap: wrap;
/* 自动换行 */
gap: 20rpx;
/* 图片之间的间距(可改) */
margin: 10rpx 0px 20rpx 0px;
}
/* 单个图片项 */
.img-item {
width: calc(33.333% - 14rpx);
height: 52px;
}
/* 图片样式 */
.card-img {
width: 100%;
height: 52px;
object-fit: cover;
}
/* 底部栏:时间 & 详情 */
.card-footer {
display: flex;
justify-content: space-between;
@@ -378,12 +279,10 @@
color: #2F77FD;
}
/* 底部按钮 */
.bottom-btn-wrap {
padding: 20rpx 30rpx 40rpx;
display: flex;
gap: 20rpx;
/* background-color: #fff; */
}
.next-btn {
@@ -392,7 +291,7 @@
line-height: 96rpx;
background-color: #1677ff;
color: #fff;
font-size: 36rpx;
font-size: 28rpx;
border-radius: 12rpx;
border: none;
}

View File

@@ -28,7 +28,7 @@
</view>
</view>
<!-- 值班电话 -->
<view class="phone-item" v-for="phoneItems,phoneIndex in item.phoneList" :key="phoneIndex">
<view class="phone-item" v-for="phoneItems,phoneIndex in item.phoneList" :key="phoneIndex" @click="callPhone(phoneItems.phoneNum)">
<text class="phone-label">值班电话</text>
<text class="phone-num">{{phoneItems.phoneNum}}</text>
<!-- <text class="phone-icon"></text> -->
@@ -43,74 +43,54 @@
</template>
<script>
export default {
onReady: function(e) {},
components: {
},
computed: {
},
created() {
},
onShow() {
},
mounted() {},
onReady() {
this.$nextTick(() => {
})
},
data() {
return {
/* 设定状态栏默认高度 */
statusBarHeight: 20,
dataList: [{
src: 'http://47.104.199.163:9090/images/propertyImgs/community1.png',
propertyName: '北境碧桂园小区物业中心',
propertyAddress: 'A栋3单元1801',
phoneList: [{
phoneNum: '080-12312312312',
},
{
phoneNum: '080-12312312312',
}
],
}],
<script setup>
import { ref, onMounted } from 'vue';
import { getManagePhoneData } from '../api/apiSub.js'
const statusBarHeight = ref(20)
const dataList = ref([])
// ====方法=====
function callPhone(phone) {
uni.makePhoneCall({
phoneNumber: phone.replace(/-/g, ""),
fail: (err) => {
console.log('用户取消拨号', err)
}
},
methods: {
// 电话点击拨号
callPhone(phone) {
uni.makePhoneCall({
phoneNumber: phone.replace(/-/g, "")
});
},
goBack() {
uni.switchTab({
url: '/pages/serviceIndex/serviceIndex',
success: res => {},
fail: () => {},
complete: () => {}
});
},
},
});
}
function goBack() {
uni.switchTab({
url: '/pages/serviceIndex/serviceIndex',
});
}
onMounted(() => {
const villageId = uni.getStorageSync('currentVillageId')
if (!villageId) {
uni.showToast({ title: '请先选择小区', icon: 'none' })
return
}
uni.showLoading({ title: '加载中...' })
getManagePhoneData({ villageId }).then(res => {
uni.hideLoading()
if (res.code === 200 && res.data) {
const d = res.data
dataList.value = [{
src: d.villageImageUrl || 'http://47.104.199.163:9090/images/propertyImgs/community1.png',
propertyName: d.villageName || '',
propertyAddress: d.address || '',
phoneList: d.managePhone ? [{ phoneNum: d.managePhone }] : []
}]
} else {
uni.showToast({ title: res.msg || '获取失败', icon: 'none' })
}
}).catch(() => {
uni.hideLoading()
uni.showToast({ title: '获取失败', icon: 'none' })
})
})
</script>
<style lang="scss">
page {

View File

@@ -1,239 +1,221 @@
<template>
<view class="contentStyle" style="">
<view class="contentStyle">
<view class="status-bar"></view>
<uni-nav-bar title="问卷调查" backgroundColor="transparent" :border="false" left-icon="left" @clickLeft="goBack">
</uni-nav-bar>
<!-- 表单区域 -->
<scroll-view class="page" scroll-y="true">
<!-- 问卷标题 -->
<view class="survey-title">关于用户体验反馈的调查</view>
<view class="survey-title">{{ detailItem.title || '' }}</view>
<view class="survey-desc">{{ detailItem.description || '' }}</view>
<!-- 问卷说明 -->
<view class="survey-desc">
为了给您提供更好的服务希望您能抽出几分钟时间将进的感受和建议告诉我们我们非常重视每位用户的宝贵意见期待您的参与
<view v-if="questionList.length === 0" class="empty-tip">
<text>暂无问卷内容</text>
</view>
<!-- 问题1社区大门数量 -->
<view class="question-item">
<view v-for="(q, idx) in questionList" :key="q.cid" class="question-item">
<view class="question-title">
<text class="required">*</text>1.您社区的大门数量
<text v-if="q.required" class="required">*</text>
<text>{{ idx + 1 }}.{{ q.title || '未命名问题' }}</text>
</view>
<view class="answer-text">1</view>
</view>
<!-- 问题2消防演练符合项 -->
<view class="question-item">
<view class="question-title">
<text class="required">*</text>2.您所在的社区关于消防演练下列符合几项
</view>
<view class="answer-text">一周2~3</view>
</view>
<!-- 问题3健康医疗服务 -->
<view class="question-item">
<view class="question-title">
<text class="required">*</text>3.您所在的社区是否有健康医疗服务
</view>
<view class="answer-text"></view>
</view>
<!-- 问题4综合评分星级 -->
<view class="question-item">
<view class="question-title">
<text class="required">*</text>4.您对所在社区的综合评分
</view>
<view class="star-container">
<view v-for="(item, index) in 5" :key="index" class="star-item" :class="{ active: index < score }">
<text class="star-icon"></text>
<!-- radio / checkbox: 展示选项及选中状态 -->
<view v-if="q.type === 'radio' || q.type === 'checkbox'" class="option-list">
<view
v-for="(opt, oIdx) in q.options"
:key="oIdx"
class="option-item"
:class="{ active: isOptionSelected(q, opt.label) }"
>
<text class="option-icon">{{ q.type === 'radio' ? '●' : '■' }}</text>
<text class="option-label">{{ opt.label }}</text>
</view>
</view>
</view>
<!-- 问题5建议 -->
<view class="question-item">
<view class="question-title">
<text class="required">*</text>5.您还有哪些建议
<!-- input / textarea: 展示文本答案 -->
<view v-if="q.type === 'input' || q.type === 'textarea'" class="answer-text">
{{ getAnswerText(q.cid) || '未填写' }}
</view>
<view class="answer-text">没有其他建议</view>
<!-- 如需可编辑输入框替换为下方textarea -->
<!-- <textarea class="suggest-input" placeholder="请输入您的建议" v-model="suggestion" /> -->
</view>
</scroll-view>
</view>
</template>
<script>
export default {
onReady: function(e) {},
components: {
},
computed: {
},
created() {
},
onShow() {
},
mounted() {},
onReady() {
this.$nextTick(() => {
})
},
data() {
return {
/* 设定状态栏默认高度 */
statusBarHeight: 20,
score: 3, // 默认3星对应原图
suggestion: ''
}
},
methods: {
goBack() {
uni.redirectTo({
url: '/pageSubPack/service-list/questionnaireSurveyIndex',
success: res => {},
fail: () => {},
complete: () => {}
});
},
},
<script setup>
import { ref, computed } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { getQuestionnaireDetail } from '../api/apiSub.js'
const currentId = ref('')
const detailItem = ref({})
const questionList = computed(() => {
const content = detailItem.value?.content
if (!content) return []
try {
const list = JSON.parse(content)
return Array.isArray(list) ? list : []
} catch (e) {
return []
}
})
const answerMap = computed(() => {
const answerContent = detailItem.value?.answerContent
if (!answerContent) return {}
try {
const list = JSON.parse(answerContent)
if (!Array.isArray(list)) return {}
const map = {}
for (const item of list) {
map[item.cid] = item.value
}
return map
} catch (e) {
return {}
}
})
const getDetailData = async () => {
try {
const params = { questionnairedId: currentId.value }
const res = await getQuestionnaireDetail(params)
if (res.code === 200) {
const data = Array.isArray(res.data) ? res.data[0] : res.data
detailItem.value = data || {}
} else {
detailItem.value = {}
uni.showToast({ title: res.msg || '获取失败', icon: 'none' })
}
} catch (err) {
uni.showToast({ title: err.msg || '网络异常', icon: 'none' })
}
}
onLoad((option) => {
if (option.id) {
currentId.value = option.id
getDetailData()
}
})
function isOptionSelected(question, label) {
const val = answerMap.value[question.cid]
if (val === undefined || val === null) return false
if (Array.isArray(val)) {
return val.includes(label)
}
return val === label
}
function getAnswerText(cid) {
return answerMap.value[cid]
}
function goBack() {
uni.navigateBack()
}
</script>
<style lang="scss">
page {
background: #fff;
}
page {
background: #fff;
}
</style>
<style scoped>
.contentStyle {
display: flex;
flex-direction: column;
height: 100vh;
/* 关键:占满屏幕高度 */
/* background: linear-gradient(to left bottom, #e2efff 0%, #f9f9f9 50%); */
}
.contentStyle {
display: flex;
flex-direction: column;
height: 100vh;
}
.page {
flex: 1;
overflow-y: auto;
padding: 0px 40rpx;
box-sizing: border-box;
/* background-color: #fff; */
}
.page {
flex: 1;
overflow-y: auto;
padding: 0px 40rpx;
box-sizing: border-box;
margin-bottom: 48rpx;
}
/* 滚动内容区 */
.content-scroll {
flex: 1;
padding: 0 30rpx;
}
.survey-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
text-align: center;
margin: 0px 0 20rpx 0;
}
/* 标题与描述 */
.survey-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
text-align: center;
margin: 0px 0 20rpx 0;
;
}
.survey-desc {
font-size: 24rpx;
color: #4A4A4A;
line-height: 1.6;
margin-bottom: 20rpx;
text-align: justify;
}
.survey-desc {
font-size: 24rpx;
color: #4A4A4A;
line-height: 1.6;
margin-bottom: 20rpx;
text-align: justify;
}
.empty-tip {
text-align: center;
padding: 100rpx 0;
font-size: 28rpx;
color: #999;
}
.question-item {
margin-bottom: 30rpx;
}
.question-item {
margin-bottom: 30rpx;
}
.question-title {
font-size: 32rpx;
color: #000;
margin-bottom: 10rpx;
font-weight: 600;
}
.question-title {
font-size: 32rpx;
color: #000;
margin-bottom: 10rpx;
font-weight: 600;
}
.required {
color: #ff0000;
font-size: 34rpx;
margin-right: 8rpx;
}
.required {
color: #ff0000;
font-size: 34rpx;
margin-right: 8rpx;
}
.answer-text {
font-size: 32rpx;
color: #333;
line-height: 1.5;
padding-left: 30rpx;
}
.answer-text {
font-size: 32rpx;
color: #333;
line-height: 1.5;
padding-left: 30rpx;
}
/* 星级评分样式 */
.star-container {
display: flex;
align-items: center;
gap: 20rpx;
padding-left: 30rpx;
}
.option-list {
padding-left: 30rpx;
}
.star-item {
font-size: 48rpx;
color: #dcdcdc;
cursor: pointer;
transition: color 0.2s;
}
.option-item {
display: flex;
align-items: center;
gap: 16rpx;
padding: 16rpx 0;
font-size: 32rpx;
color: #333;
}
.star-item.active {
color: #409eff;
/* 蓝色,和原图一致 */
}
.option-item.active {
color: #409eff;
}
.star-icon {
font-family: monospace;
}
.option-icon {
font-size: 24rpx;
color: #ccc;
}
/* 建议输入框样式(如需可编辑) */
.suggest-input {
width: 100%;
min-height: 200rpx;
padding: 20rpx;
border: 1rpx solid #e5e5e5;
border-radius: 8rpx;
background: #fff;
font-size: 32rpx;
color: #333;
box-sizing: border-box;
}
.option-item.active .option-icon {
color: #409eff;
}
.status-bar {
width: 100vw;
height: 46px;
}
</style>
.option-label {
font-size: 32rpx;
}
.status-bar {
width: 100vw;
height: 46px;
}
</style>

View File

@@ -1,498 +1,402 @@
<template>
<view class="contentStyle" style="">
<view class="contentStyle">
<view class="status-bar"></view>
<uni-nav-bar title="问卷调查" backgroundColor="transparent" :border="false" left-icon="left" @clickLeft="goBack">
</uni-nav-bar>
<!-- 表单区域 -->
<scroll-view class="page" scroll-y="true">
<scroll-view class="page" scroll-y>
<view class="content-wrap">
<!-- 问卷标题 -->
<view class="survey-title">关于用户体验反馈的调查</view>
<view class="survey-title">{{ detailItem.title || '' }}</view>
<!-- 问卷说明 -->
<view class="survey-desc">
为了给您提供更好的服务希望您能抽出几分钟时间将进的感受和建议告诉我们我们非常重视每位用户的宝贵意见期待您的参与
<view class="survey-desc">{{ detailItem.description || '' }}</view>
<!-- 空状态 -->
<view v-if="questionList.length === 0" class="empty-tip">
<text>问卷内容加载中请稍后...</text>
</view>
<!-- 问题1单选 -->
<view class="question-item">
<!-- 动态问题列表 -->
<view v-for="(q, idx) in questionList" :key="q.cid" class="question-item">
<view class="question-title">
<text class="required">*</text>1.您社区的大门数量
<text v-if="q.required" class="required">*</text>
<text>{{ idx + 1 }}.{{ q.title || '未命名问题' }}</text>
</view>
<view class="radio-group">
<view class="radio-item" v-for="(item, index) in q1Options" :key="index"
@click="selectRadio(1, index)">
<view class="radio-circle" :class="{ checked: q1Selected === index }">
<text v-if="q1Selected === index" class="check-icon"></text>
<!-- 单选 -->
<view v-if="q.type === 'radio'" class="radio-group">
<view
v-for="(opt, oIdx) in q.options"
:key="oIdx"
class="radio-item"
:class="{ checked: answers[q.cid] === opt.label }"
@click="selectRadio(q.cid, opt.label)"
>
<view class="radio-circle" :class="{ active: answers[q.cid] === opt.label }">
<text v-if="answers[q.cid] === opt.label" class="check-icon"></text>
</view>
<text class="radio-label">{{ item }}</text>
<text class="radio-label">{{ opt.label }}</text>
</view>
</view>
</view>
<!-- 问题2 -->
<view class="question-item">
<view class="question-title">
<text class="required">*</text>2.您所在的社区关于消防演练下列符合几项
</view>
<view class="radio-group">
<view class="radio-item" v-for="(item, index) in q2Options" :key="index"
@click="selectRadio(2, index)">
<view class="radio-circle" :class="{ checked: q2Selected === index }">
<text v-if="q2Selected === index" class="check-icon"></text>
<!-- -->
<view v-if="q.type === 'checkbox'" class="checkbox-group">
<view
v-for="(opt, oIdx) in q.options"
:key="oIdx"
class="checkbox-item"
:class="{ checked: isChecked(q.cid, opt.label) }"
@click="toggleCheckbox(q.cid, opt.label)"
>
<view class="checkbox-box" :class="{ active: isChecked(q.cid, opt.label) }">
<text v-if="isChecked(q.cid, opt.label)" class="check-icon"></text>
</view>
<text class="radio-label">{{ item }}</text>
<text class="checkbox-label">{{ opt.label }}</text>
</view>
</view>
</view>
<!-- 问题3自定义 Select (核心部分不使用 picker) -->
<view class="question-item">
<view class="question-title">
<text class="required">*</text>3.您所在的社区是否有健康医疗服务
</view>
<!-- 模拟输入框点击展开下拉 -->
<view class="select-wrapper" @click="toggleDropdown">
<text class="select-text" v-if="q3Selected">
{{ q3Selected }}
</text>
<text class="select-placeholder" v-else>
请选择是否有该项服务
</text>
<text class="select-arrow"></text>
<!-- 单行输入 -->
<view v-if="q.type === 'input'">
<input
class="text-input"
v-model="answers[q.cid]"
placeholder="请输入"
/>
</view>
<!-- 下拉面板 (遮罩 + 列表) -->
<view class="dropdown-mask" v-if="showDropdown" @click="hideDropdown"></view>
<view class="dropdown-panel" v-if="showDropdown">
<view class="dropdown-item" v-for="(item, index) in q3Options" :key="index"
@click="selectQ3(index)" :class="{ active: index === q3Index }">
{{ item }}
</view>
<!-- 多行输入 -->
<view v-if="q.type === 'textarea'">
<textarea
class="textarea-input"
v-model="answers[q.cid]"
placeholder="请输入"
maxlength="500"
/>
</view>
</view>
<!-- 问题4星级评分 -->
<view class="question-item">
<view class="question-title">
<text class="required">*</text>4.您对所在社区的综合评分
</view>
<view class="star-group">
<view class="star-item" v-for="(item, index) in 5" :key="index" @click="selectStar(index + 1)">
<text class="star-icon" :class="{ active: index < q4Score }"></text>
</view>
</view>
</view>
<!-- 问题5文本输入 -->
<view class="question-item">
<view class="question-title">
<text class="required">*</text>5.您还有哪些建议
</view>
<textarea class="textarea-input" v-model="q5Content" placeholder="请输入您还有哪些建议" maxlength="500" />
</view>
</view>
</scroll-view>
<!-- 固定底部提交按钮 -->
<view class="submit-btn-wrap">
<button class="submit-btn" @click="handleSubmit">提交</button>
</view>
</view>
</template>
<script>
export default {
onReady: function(e) {},
components: {
<script setup>
import { ref, computed } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { getQuestionnaireDetail, submitQuestionnair } from '../api/apiSub.js'
},
const detailItem = ref({})
computed: {
const questionList = computed(() => {
const content = detailItem.value?.content
if (!content) return []
try {
const list = JSON.parse(content)
return Array.isArray(list) ? list : []
} catch (e) {
return []
}
})
const answers = ref({})
// 获取问卷调查
const getDetailData = async (id) => {
try {
const res = await getQuestionnaireDetail({ id })
// console.log('接口返回:', JSON.stringify(res))
if (res.code === 200) {
const data = Array.isArray(res.data) ? res.data[0] : res.data
detailItem.value = data || {}
} else {
uni.showToast({ title: res.msg || '获取失败', icon: 'none' })
}
} catch (err) {
uni.showToast({ title: err.msg || '网络异常', icon: 'none' })
}
}
onLoad((option) => {
if (option.id) {
getDetailData(option.id)
} else {
uni.showToast({ title: '缺少问卷ID', icon: 'none' })
}
})
const selectRadio = (cid, label) => {
answers.value[cid] = label
}
},
created() {
},
onShow() {
},
mounted() {},
onReady() {
this.$nextTick(() => {
})
},
data() {
return {
/* 设定状态栏默认高度 */
statusBarHeight: 20,
// 问题1
q1Options: ['1', '2', '3', '4'],
q1Selected: -1,
// 问题2
q2Options: ['一周2~3次', '一月2~3次', '一年2~3次', '基本不买'],
q2Selected: -1,
// 问题3 (自定义Select)
q3Options: ['有', '无'], // 选项列表
q3Selected: '', // 绑定显示值
q3Index: -1, // 绑定索引
showDropdown: false, // 控制下拉面板显示
// 问题4
q4Score: -1,
// 问题5
q5Content: ''
const isChecked = (cid, label) => {
const arr = answers.value[cid]
if (!Array.isArray(arr)) return false
return arr.includes(label)
}
const toggleCheckbox = (cid, label) => {
if (!Array.isArray(answers.value[cid])) {
answers.value[cid] = []
}
const arr = answers.value[cid]
const pos = arr.indexOf(label)
if (pos > -1) {
arr.splice(pos, 1)
} else {
arr.push(label)
}
}
const handleSubmit = async () => {
for (const q of questionList.value) {
if (q.required) {
const ans = answers.value[q.cid]
if (ans === undefined || ans === null || ans === '' || (Array.isArray(ans) && ans.length === 0)) {
uni.showToast({ title: `请填写第${questionList.value.indexOf(q) + 1}`, icon: 'none' })
return
}
},
methods: {
selectRadio(type, index) {
if (type === 1) this.q1Selected = index
else this.q2Selected = index
},
// 问题3切换下拉显示
toggleDropdown() {
this.showDropdown = !this.showDropdown
},
// 问题3隐藏下拉
hideDropdown() {
this.showDropdown = false
},
// 问题3选择选项
selectQ3(index) {
this.q3Index = index
this.q3Selected = this.q3Options[index]
this.hideDropdown() // 选择后自动关闭
},
// 问题4
selectStar(score) {
this.q4Score = score
},
// 提交
handleSubmit() {
if (this.q1Selected === -1) {
uni.showToast({
title: '请选择社区大门数量',
icon: 'none'
})
return
}
if (this.q2Selected === -1) {
uni.showToast({
title: '请选择社区消防演练频率',
icon: 'none'
})
return
}
if (this.q3Index === -1) {
uni.showToast({
title: '请选择社区是否有健康医疗服务',
icon: 'none'
})
return
}
if (this.q4Score === -1) {
uni.showToast({
title: '请选择对社区的综合评分',
icon: 'none'
})
return
}
if (!this.q5Content.trim()) {
uni.showToast({
title: '请输入建议',
icon: 'none'
})
return
}
uni.showLoading({
title: '提交中...',
mask: true
});
setTimeout(() => {
uni.showToast({
title: '提交成功',
icon: 'success'
});
}, 1000);
setTimeout(() => {
// 提交成功后跳转列表页
uni.hideLoading();
this.goBack();
}, 2500);
},
goBack() {
uni.redirectTo({
url: '/pageSubPack/service-list/questionnaireSurveyIndex',
success: res => {},
fail: () => {},
complete: () => {}
});
},
},
}
}
const answerList = []
for (const q of questionList.value) {
const val = answers.value[q.cid]
if (val !== undefined && val !== null && val !== '' && !(Array.isArray(val) && val.length === 0)) {
answerList.push({ cid: q.cid, value: val })
}
}
uni.showLoading({ title: '提交中...', mask: true })
try {
const res = await submitQuestionnair(detailItem.value.questionnaireId, answerList)
uni.hideLoading()
if (res.code === 200) {
uni.showToast({ title: '提交成功', icon: 'success' })
setTimeout(() => { goBack() }, 1500)
} else {
uni.showToast({ title: res.msg || '提交失败', icon: 'none' })
}
} catch (err) {
console.log('errrr',err)
uni.hideLoading()
uni.showToast({ title: err.msg || '网络异常', icon: 'none' })
}
}
const goBack = () => {
uni.navigateBack()
}
</script>
<style lang="scss">
page {
background: #f2f1f6;
}
page {
background: #f2f1f6;
}
</style>
<style scoped lang="scss">
.contentStyle {
display: flex;
flex-direction: column;
height: 100vh;
/* 关键:占满屏幕高度 */
background: linear-gradient(to left bottom, #e2efff 0%, #f9f9f9 50%);
}
.contentStyle {
display: flex;
flex-direction: column;
height: 100vh;
background: linear-gradient(to left bottom, #e2efff 0%, #f9f9f9 50%);
}
.page {
flex: 1;
overflow-y: auto;
padding: 0px 40rpx;
box-sizing: border-box;
/* background-color: #fff; */
}
.page {
flex: 1;
overflow-y: auto;
padding: 0px 40rpx;
box-sizing: border-box;
}
/* 滚动内容区 */
.content-scroll {}
.content-wrap {
padding-bottom: 40rpx;
}
.content-wrap {
// padding-bottom: 40rpx;
}
.survey-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
text-align: center;
margin: 0px 0 20rpx 0;
}
/* 标题与描述 */
.survey-title {
.survey-desc {
font-size: 24rpx;
color: #4A4A4A;
line-height: 1.6;
margin-bottom: 20rpx;
text-align: justify;
}
.empty-tip {
text-align: center;
padding: 100rpx 0;
font-size: 28rpx;
color: #999;
}
.question-item {
margin-bottom: 30rpx;
.question-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
text-align: center;
margin: 0px 0 20rpx 0;
;
}
margin-bottom: 10rpx;
line-height: 1.4;
.survey-desc {
font-size: 24rpx;
color: #4A4A4A;
line-height: 1.6;
margin-bottom: 20rpx;
text-align: justify;
}
/* 问题项 */
.question-item {
margin-bottom: 30rpx;
.question-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
margin-bottom:10rpx;
line-height: 1.4;
.required {
color: #ff3b30;
font-size: 36rpx;
margin-right: 8rpx;
}
.required {
color: #ff3b30;
font-size: 36rpx;
margin-right: 8rpx;
}
}
}
/* 单选按钮 */
.radio-group {
display: flex;
flex-direction: column;
gap: 30rpx;
padding-left: 30rpx;
}
/* 单选 */
.radio-group {
display: flex;
flex-direction: column;
gap: 20rpx;
padding-left: 10rpx;
}
.radio-item {
.radio-item {
display: flex;
align-items: center;
gap: 16rpx;
padding: 12rpx 0;
.radio-circle {
width: 40rpx;
height: 40rpx;
border-radius: 50%;
border: 2rpx solid #ccc;
display: flex;
align-items: center;
gap: 20rpx;
.radio-circle {
width: 40rpx;
height: 40rpx;
border-radius: 50%;
border: 2rpx solid #ccc;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
&.checked {
background-color: #007aff;
border-color: #007aff;
.check-icon {
color: #fff;
font-size: 24rpx;
font-weight: bold;
}
}
}
.radio-label {
font-size: 34rpx;
color: #333;
}
}
/* 核心:自定义 Select 样式 (模拟输入框) */
.select-wrapper {
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
height: 80rpx;
padding: 0 20rpx;
border: 2rpx solid #e5e5e5;
border-radius: 8rpx;
background-color: #fff;
font-size: 32rpx;
color: #333;
margin-left: 30rpx;
}
.select-placeholder {
color: #999;
}
.select-arrow {
font-size: 24rpx;
color: #999;
margin-left: 10rpx;
}
/* 下拉遮罩层 */
.dropdown-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.1);
z-index: 99;
}
/* 下拉面板 */
.dropdown-panel {
position: absolute;
left: 0;
right: 0;
max-height: 400rpx;
background-color: #fff;
border: 2rpx solid #e5e5e5;
border-radius: 8rpx;
z-index: 100;
margin-top: 10rpx;
overflow: hidden;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
}
.dropdown-item {
height: 80rpx;
line-height: 80rpx;
padding: 0 20rpx;
font-size: 32rpx;
color: #333;
&:active {
background-color: #f5f5f5;
}
justify-content: center;
flex-shrink: 0;
transition: all 0.2s;
&.active {
color: #007aff;
font-weight: 500;
background-color: #007aff;
border-color: #007aff;
}
}
/* 星级评分 */
.star-group {
display: flex;
align-items: center;
gap: 16rpx;
margin-left: 30rpx;
.star-icon {
font-size: 56rpx;
color: #e5e5e5;
transition: all 0.2s ease;
&.active {
color: #007aff;
}
}
.check-icon {
color: #fff;
font-size: 22rpx;
font-weight: bold;
}
/* 文本输入框 */
.textarea-input {
width: calc(100% - 30rpx);
min-height: 240rpx;
padding: 20rpx;
border: 2rpx solid #e5e5e5;
border-radius: 8rpx;
background-color: #fff;
.radio-label {
font-size: 32rpx;
color: #333;
box-sizing: border-box;
margin-left: 30rpx;
}
}
/* 底部占位 & 提交按钮 */
.bottom-placeholder {
height: 120rpx; // 高度要大于等于按钮高度
}
/* 多选 */
.checkbox-group {
display: flex;
flex-direction: column;
gap: 20rpx;
padding-left: 10rpx;
}
.submit-btn-wrap {
padding: 20rpx 30rpx 40rpx;
.checkbox-item {
display: flex;
align-items: center;
gap: 16rpx;
padding: 12rpx 0;
.checkbox-box {
width: 40rpx;
height: 40rpx;
border-radius: 8rpx;
border: 2rpx solid #ccc;
display: flex;
gap: 20rpx;
// background-color: #fff;
align-items: center;
justify-content: center;
flex-shrink: 0;
transition: all 0.2s;
.submit-btn {
width: 100%;
height: 96rpx;
line-height: 96rpx;
&.active {
background-color: #007aff;
color: #fff;
font-size: 36rpx;
font-weight: 500;
border-radius: 12rpx;
border: none;
padding: 0;
margin: 0;
&::after {
border: none;
}
border-color: #007aff;
}
}
.status-bar {
width: 100vw;
height: 46px;
.check-icon {
color: #fff;
font-size: 22rpx;
font-weight: bold;
}
.checkbox-label {
font-size: 32rpx;
color: #333;
}
}
/* 文本输入 */
.text-input {
width: calc(100% - 30rpx);
height: 80rpx;
padding: 0 20rpx;
border: 2rpx solid #e5e5e5;
border-radius: 8rpx;
background-color: #fff;
font-size: 32rpx;
color: #333;
box-sizing: border-box;
margin-left: 30rpx;
}
.textarea-input {
width: calc(100% - 30rpx);
min-height: 240rpx;
padding: 20rpx;
border: 2rpx solid #e5e5e5;
border-radius: 8rpx;
background-color: #fff;
font-size: 32rpx;
color: #333;
box-sizing: border-box;
margin-left: 30rpx;
}
/* 提交按钮 */
.submit-btn-wrap {
padding: 20rpx 30rpx 40rpx;
.submit-btn {
width: 100%;
height: 96rpx;
line-height: 96rpx;
background-color: #007aff;
color: #fff;
font-size: 36rpx;
font-weight: 500;
border-radius: 12rpx;
border: none;
padding: 0;
margin: 0;
&::after {
border: none;
}
}
}
.status-bar {
width: 100vw;
height: 46px;
}
</style>

View File

@@ -1,240 +1,152 @@
<template>
<view class="contentStyle" style="">
<view class="contentStyle">
<view class="status-bar"></view>
<uni-nav-bar title="问卷调查" backgroundColor="transparent" :border="false" left-icon="left" @clickLeft="goBack">
</uni-nav-bar>
<uni-nav-bar title="问卷调查" backgroundColor="transparent" :border="false" left-icon="left" @click-left="goBack" />
<view style="width: 40vw;">
<uni-segmented-control :current="currentTab" :values="tabs" @clickItem="switchTab" style-type="text"
active-color="#007AFF" inactive-color="#999" class="seg-control"></uni-segmented-control>
<uni-segmented-control :current="currentTab" :values="tabs" @click-item="switchTab" style-type="text"
active-color="#007AFF" inactive-color="#999" class="seg-control" />
</view>
<!-- 表单区域 -->
<scroll-view class="page" scroll-y="true">
<scroll-view class="page" scroll-y>
<view class="list-container">
<view v-if="currentList.length === 0" class="empty-state">
<uni-icons type="info" size="60" color="#ccc"></uni-icons>
<text class="empty-text">
{{ '暂无数据'}}
</text>
<text class="empty-text">暂无数据</text>
</view>
<view v-for="(item, index) in currentList" :key="index" class="list-item" :class="{
disabled: currentTab === 1,
selected: currentTab === 0 && item.status === '已参与'
}" @click="handleItemClick(item)">
<!-- 列表项点击高亮 -->
<view
v-for="(item, index) in currentList"
:key="index"
class="list-item"
:class="{
disabled: currentTab === 1,
selected: selectedIndex === index // 点击才高亮
}"
@click="handleItemClick(item, index)"
>
<view class="item-content">
<view class="item-title">{{ item.title }}</view>
<view class="item-info">
<text class="info-time">{{ item.time }}</text>
<text class="info-community">{{ item.community }}</text>
<text class="info-time">{{ item.createTime }}</text>
<text class="info-community">{{ item.villageName }}</text>
</view>
</view>
<view class="item-status"
:class="{joined: currentTab === 0 &&item.status === '已参与',unjoined: item.status === '未参与'||currentTab === 1, }">
{{ item.status }}
<view
class="item-status"
:class="{
joined: item.answerStatus === '1',
unjoined: item.answerStatus === '0'
}"
>
{{ item.statusName}}
</view>
</view>
</view>
<!-- 占位 -->
<view style="width: 100%;height:2rpx;"> </view>
<view style="width: 100%;height:2rpx;"></view>
</scroll-view>
</view>
</template>
<script>
export default {
onReady: function(e) {},
components: {
<script setup>
import { ref, computed, onMounted } from 'vue'
import {getQuestionnaireList} from '../api/apiSub.js'
},
// 状态栏高度
const statusBarHeight = ref(20)
// 当前选中tab
const currentTab = ref(0)
// tab标题
const tabs = ["收集中", "已结束"]
computed: {
// 计算当前显示的列表
currentList() {
return this.currentTab === 0 ? this.collectingList : this.endedList;
},
},
created() {
},
onShow() {
},
mounted() {
this.getDataList()
},
onReady() {
this.$nextTick(() => {
})
},
data() {
return {
/* 设定状态栏默认高度 */
statusBarHeight: 20,
currentTab: 0, // 0=收集中1=已结束
tabs: ["收集中", "已结束"],
// 收集中列表数据
collectingList: [{
title: '问卷调查名称问卷调查名称问卷调查...',
time: '2026-09-01 11:00:56',
community: '阳光海岸社区',
status: '已参与',
},
{
title: '问卷调查名称问卷调查名称问卷调查...',
time: '2026-09-01 11:00:56',
community: '阳光海岸社区',
status: '未参与',
},
{
title: '问卷调查名称问卷调查名称问卷调查...',
time: '2026-09-01 11:00:56',
community: '阳光海岸社区',
status: '未参与',
},
{
title: '问卷调查名称问卷调查名称问卷调查...',
time: '2026-09-01 11:00:56',
community: '阳光海岸社区',
status: '未参与',
}, {
title: '问卷调查名称问卷调查名称问卷调查...',
time: '2026-09-01 11:00:56',
community: '阳光海岸社区',
status: '未参与',
},
{
title: '问卷调查名称问卷调查名称问卷调查...',
time: '2026-09-01 11:00:56',
community: '阳光海岸社区',
status: '未参与',
},
{
title: '问卷调查名称问卷调查名称问卷调查...',
time: '2026-09-01 11:00:56',
community: '阳光海岸社区',
status: '未参与',
}, {
title: '问卷调查名称问卷调查名称问卷调查...',
time: '2026-09-01 11:00:56',
community: '阳光海岸社区',
status: '未参与',
},
{
title: '问卷调查名称问卷调查名称问卷调查...',
time: '2026-09-01 11:00:56',
community: '阳光海岸社区',
status: '未参与',
},
{
title: '问卷调查名称问卷调查名称问卷调查...',
time: '2026-09-01 11:00:56',
community: '阳光海岸社区',
status: '未参与',
},
],
// 已结束列表数据
endedList: [{
title: '问卷调查名称问卷调查名称问卷调查...',
time: '2023-09-01 11:00:56',
community: '阳光海岸社区',
status: '已参与',
},
{
title: '问卷调查名称问卷调查名称调查...',
time: '2023-09-01 11:00:56',
community: '阳光海岸社区',
status: '未参与',
},
{
title: '问卷调查名称问卷调查名称调查...',
time: '2023-09-01 11:00:56',
community: '阳光海岸社区',
status: '未参与',
},
],
}
},
methods: {
// Tab切换
switchTab(e) {
this.currentTab = e.currentIndex;
this.getDataList()
},
// 列表项点击
handleItemClick(item) {
if (item.status == '已参与') {
this.turnToDetail(item)
} else {
if (this.currentTab === 0) {
this.turnToFillout(item)
} else if (this.currentTab === 1) {
// 未参与的已结束问卷
}
}
},
turnToFillout(item) {
// 跳转到问卷填写
uni.redirectTo({
url: `/pageSubPack/service-list/questionnaireFillout`,
success: res => {},
fail: () => {},
complete: () => {}
});
},
turnToDetail(item) {
// 跳转到问卷详情
uni.redirectTo({
url: `/pageSubPack/service-list/questionnaireDetail`,
success: res => {},
fail: () => {},
complete: () => {}
});
},
getDataList() {
if (this.currentTab == 0) {
this.dataList = this.collectingList
} else if (this.currentTab == 1) {
this.dataList = this.endedList
}
},
onTabClick(e) {
this.currentTab = e.currentIndex;
this.getDataList()
},
goBack() {
uni.switchTab({
url: '/pages/serviceIndex/serviceIndex',
success: res => {},
fail: () => {},
complete: () => {}
});
},
},
// 收集中列表
const collectingList = ref([])
// 已结束列表
const endedList = ref([])
// 点击选中的索引(控制高亮)
const selectedIndex = ref(-1)
// 计算属性:当前显示列表
const currentList = computed(() => {
return currentTab.value === 0 ? collectingList.value : endedList.value
})
// Tab切换
const switchTab = (e) => {
currentTab.value = e.currentIndex
selectedIndex.value = -1 // 切换tab清空选中
getDataList()
}
// 获取列表数据
const getDataList = async () => {
try{
let params ={
status: currentTab.value === 0 ? 1 : 2
}
const res = await getQuestionnaireList(params)
if(res.code === 200){
const list = (res.data || []).map(item => {
return {
...item,
statusName: item.answerStatus == "0" ? '未参与' : '已参与'
}
})
if(currentTab.value === 0){
collectingList.value = list
}else{
endedList.value = list
}
}else{
uni.showToast({ title: res.msg || '获取失败', icon: 'none' })
}
}catch(err){
uni.showToast({ title: err.msg ||'网络异常', icon: 'none' })
}
}
// 列表项点击index 控制高亮)
const handleItemClick = (item, index) => {
selectedIndex.value = index //点击哪个,哪个高亮
if (item.answerStatus === '1') {
turnToDetail(item)
} else {
// if (currentTab.value === 0) {
turnToFillout(item)
// }
}
}
// 跳转到填写问卷
const turnToFillout = (item) => {
uni.navigateTo({
url: `/pageSubPack/service-list/questionnaireFillout?id=${item.id}`
})
}
// 跳转到详情
const turnToDetail = (item) => {
uni.navigateTo({
url: `/pageSubPack/service-list/questionnaireDetail?id=${item.id}`
})
}
// 返回
const goBack = () => {
uni.switchTab({
url: '/pages/serviceIndex/serviceIndex'
})
}
// 生命周期:页面挂载完成
onMounted(() => {
getDataList()
})
</script>
<style lang="scss">
page {
background: #f2f1f6;
@@ -246,7 +158,6 @@
display: flex;
flex-direction: column;
height: 100vh;
/* 关键:占满屏幕高度 */
background: linear-gradient(to left bottom, #e2efff 0%, #f9f9f9 50%);
}
@@ -255,30 +166,12 @@
overflow-y: auto;
padding: 0px 40rpx;
box-sizing: border-box;
/* background-color: #fff; */
}
.page-container {
padding: 40rpx;
}
/* --- 分段控制器样式 --- */
.seg-control {
margin-bottom: 20rpx;
}
.cardScroll {
padding: 0rpx 0rpx 0px 0rpx;
overflow-y: scroll;
}
/* 列表区域 */
.list-scroll {
flex: 1;
}
.list-container {
padding-top: 20rpx;
}
@@ -289,35 +182,29 @@
display: flex;
align-items: center;
justify-content: space-between;
border:2rpx solid #E6E6E6;
border: 2rpx solid #E6E6E6;
background-color: #fff;
&.disabled {
opacity: 0.6;
.item-title,
.item-info {}
}
&.selected {
border:2rpx solid #2F77FD;
border: 2rpx solid #2F77FD;
background-color: #F5FAFF;
}
}
.item-content {
// flex: 1;
width: calc(100% - 100rpx);
// margin-right: 20rpx;
}
.item-title {
font-size: 32rpx;
font-weight: 600;
color: #222222;
color: #222;
line-height: 44rpx;
margin-bottom:10rpx;
/* 超长文本省略 */
margin-bottom: 10rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@@ -334,7 +221,6 @@
font-size: 28rpx;
color: #666;
line-height: 32rpx;
/* 超长文本省略 */
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@@ -344,8 +230,7 @@
.item-status {
font-size: 28rpx;
font-weight: 500;
padding: 0px 0px;
border-radius:8rpx;
border-radius: 8rpx;
&.joined {
color: #2F77FD;
@@ -360,7 +245,7 @@
width: 100vw;
height: 46px;
}
/* 空状态 */
.empty-state {
display: flex;
flex-direction: column;
@@ -369,7 +254,7 @@
padding: 100rpx 0;
color: #ccc;
}
.empty-text {
font-size: 32rpx;
color: #999;

View File

@@ -8,26 +8,25 @@
<text class="nav-title">首页</text>
</view>
<view class="tabbarClass">
<view class="header-container" @click="navigateToPage">
<view class="header-container" @click="navigateToPage">
<!-- 左侧小区选择区域 -->
<view class="location-area">
<!-- 定位图标 -->
<view class="location-icon">
<!-- <uni-icons type="location-filled" size="14" color="#333333"></uni-icons> -->
<image src="http://47.104.199.163:9090/images/home/adress.png" style="height: 100%;width: 100%;"></image>
</view>
<!-- 小区名称和下拉箭头 -->
<view class="community-selector">
<text class="community-name">{{ currentCommunity }}</text>
<u-icon
name="arrow-down-fill"
size="16"
:color="isDropdownOpen ? '#2979ff' : '#222222'"
:class="['dropdown-arrow', { 'rotate': isDropdownOpen }]"
></u-icon>
</view>
</view>
</view>
<view class="location-icon">
<image src="http://47.104.199.163:9090/images/home/adress.png" style="height: 100%;width: 100%;"></image>
</view>
<!-- 小区名称和下拉箭头 -->
<view class="community-selector">
<text class="community-name">{{ currentCommunity }}</text>
<u-icon
name="arrow-down-fill"
size="16"
:color="isDropdownOpen ? '#2979ff' : '#222222'"
:class="['dropdown-arrow', { 'rotate': isDropdownOpen }]"
></u-icon>
</view>
</view>
</view>
</view>
</view>
@@ -166,7 +165,7 @@
import { ref } from 'vue'
import {onLoad,onUnload,onShow} from '@dcloudio/uni-app'
import moment from 'moment'
import {getNoticeLatest,getCurrentVillage,getActivitylatest} from "/pageSubPack/api/api.js"
import {getNoticeLatest,getCurrentVillage,getActivitylatest,getBannarList} from "/pageSubPack/api/api.js"
// 列表数据状态
@@ -177,7 +176,7 @@ const currentCommunity = ref('请认证小区')
const currentVillageId = ref('')
const bannerList = ref([
// 'http://47.104.199.163:9090/images/home/banner.png',
"http://47.104.199.163:9090/images/propertyImgs/serviceImg2.png"
// "http://47.104.199.163:9090/images/propertyImgs/serviceImg2.png"
])
const noticeText = ref('暂无公告')
@@ -196,6 +195,20 @@ const getNoticeMore = () =>{
url:"/pageSubPack/notice-list/noticeList"
})
}
// 获取bannar列表
const getbannarData = async () =>{
try{
const res = await getBannarList({villageId:uni.getStorageSync('currentVillageId')})
if(res.code === 200){
bannerList.value = res.data.map(item =>item.url)
}else{
uni.showToast({title:'获取失败',icon:'error'})
}
}catch (err){
const msg = err?.msg || (typeof err === 'string' ? err : '获取失败')
uni.showToast({title: msg,icon:'error'})
}
}
// 接收选择小区返回的参数
const slelctHomeData = (data) =>{
// console.log('收到B页面返回的数据', data)
@@ -222,7 +235,7 @@ const getCurrentCommunityData = () => {
}
}).catch(err =>{
uni.showToast({
title: '网络异常',
title: err.msg || '网络异常',
icon: 'error'
});
})
@@ -252,7 +265,7 @@ const getNoticeData = async() =>{
} catch (err) {
// console.error('获取通知数据异常:', err)
uni.showToast({
title: err.msg,
title: err.msg || '网络异常',
icon: 'error',
duration: 1000
})
@@ -277,6 +290,7 @@ onLoad(() => {
}
getNoticeData()
getNewActivityList()
getbannarData()
})
// 页面销毁时移除监听(防止内存泄漏)
@@ -388,7 +402,7 @@ const moreClick = () =>{
width: 100%;
background: repeating-linear-gradient(to right, #EDF4FA , #C0E4FB);
position: absolute;
}
.status_bar {
height: 46px;