staff/pagesB/questionnaire/list.vue

747 lines
20 KiB
Vue

<template>
<view class="questionnaire-page">
<!-- 自定义导航栏 -->
<wd-navbar :bordered="false"
custom-style="background: transparent !important; backdrop-filter: blur(10px) !important; -webkit-backdrop-filter: blur(10px) !important;"
safeAreaInsetTop fixed placeholder>
<template #left>
<view class="li-ml-15 li-mt-10 li-flex li-items-center">
<text v-if="hasMultiplePages" class="ri-arrow-left-s-line li-text-70"
@click="toPages({type:'nav'})"></text>
<text v-if="!hasMultiplePages" class="ri-home-5-line li-text-55 li-mb-8 li-mr-10"
@click="toPages({type:'home'})"></text>
<text class="li-text-42">问卷记录</text>
</view>
</template>
<!-- #ifdef MP-WEIXIN -->
<template #right>
<view class="li-flex-center li-mr-200 li-pt-6" @click="showFilter">
<text class="ri-filter-line li-text-48"></text>
</view>
</template>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<template #right>
<view class="li-flex-center li-mr-25 li-pt-6" @click="showFilter">
<text class="ri-filter-line li-text-48"></text>
</view>
</template>
<!-- #endif -->
</wd-navbar>
<!-- 导航栏背景 -->
<view class="nav-bg-layer"></view>
<!-- 整页背景 -->
<view class="page-bg"></view>
<!-- 筛选区域 -->
<!-- <view class="filter-section li-w-92% li-mx-auto li-mt-30">
<view class="li-flex li-items-center li-flex-wrap">
<text v-for="(item, index) in statusList" :key="index"
:class="activeStatus === item.value ? 'status-tag active' : 'status-tag'"
@click="handleStatusChange(item.value)">
{{item.label}}
</text>
</view>
</view> -->
<!-- 小区选择器 -->
<!-- <view class="li-px-15">
<wd-picker size="large" v-model="showVillagePicker" :columns="pickerVillageList" title="选择小区"
v-model:value="selectedVillage" @confirm="handleVillageConfirm" label-key="label" value-key="value" />
</view> -->
<!-- 问卷列表 -->
<view class="li-w-92% li-mx-auto li-mt-30 li-pb-30">
<view v-if="questionnaireList.length > 0">
<view v-for="(item, index) in questionnaireList" :key="index"
class="questionnaire-card li-bg-white li-rd-20 li-p-30 li-mb-20 li-shadow-sm"
@click="toPages({type:'detail', value:item})">
<!-- 问卷标题 -->
<view class="li-flex li-items-center li-justify-between li-pb-20 li-bottom-border2">
<view class="li-flex li-items-center">
<text class="ri-questionnaire-line li-text-40 li-mr-15 li-text-#0070F0"></text>
<text class="li-text-32 li-font-bold">{{item.title}}</text>
</view>
<view class="status-badge" :style="{
backgroundColor: getStatusBgColor(item.status),
color: getStatusColor(item.status)
}">
{{getStatusText(item.status)}}
</view>
</view>
<!-- 问卷信息 -->
<view class="li-mt-20">
<view class="li-flex li-items-center li-mb-15">
<text class="content-label li-text-28 li-text-#9a9a9a li-w-160">提交人</text>
<view class="li-flex li-items-center">
<text class="content-text li-text-28 li-text-#333">{{item.user_name}}</text>
<view class="user-badge" v-if="item.is_anonymous">匿名</view>
</view>
</view>
<view class="li-flex li-items-center li-mb-15">
<text class="content-label li-text-28 li-text-#9a9a9a li-w-160">所属小区</text>
<text class="content-text li-text-28 li-text-#333">{{item.village_name}}</text>
</view>
<view class="li-flex li-items-center li-mb-15">
<text class="content-label li-text-28 li-text-#9a9a9a li-w-160">问卷类型</text>
<wd-tag color="#0083ff" bg-color="#d0e8ff">{{item.type}}</wd-tag>
</view>
<view class="li-flex li-items-center li-mb-15">
<text class="content-label li-text-28 li-text-#9a9a9a li-w-160">提交时间</text>
<text class="content-text li-text-28 li-text-#333">{{item.submit_time}}</text>
</view>
<view class="li-flex li-items-center li-justify-between">
<view class="li-flex li-items-center">
<text class="content-label li-text-28 li-text-#9a9a9a li-w-160">问题数量</text>
<text class="content-text li-text-28 li-text-#333">{{item.question_count}}题</text>
</view>
<view class="action-wrapper">
<wd-button type="primary" size="small" custom-class="view-btn"
@click.stop="toPages({type:'detail', value:item})">查看详情</wd-button>
</view>
</view>
</view>
</view>
</view>
<!-- 空状态 -->
<view v-else class="li-mt-200">
<wd-status-tip :image="uni.$globalData?.RESOURCE_URL+'tip/search.png'" tip="暂无问卷记录" />
</view>
<!-- 加载状态 -->
<view v-if="loading" class="li-flex-center li-mt-30">
<wd-loading color="#0070F0" />
</view>
<!-- 底部提示 -->
<view v-if="!loading && finished && questionnaireList.length > 0"
class="li-text-center li-text-26 li-text-#999 li-py-20">
-- 没有更多数据了 --
</view>
</view>
<!-- 筛选弹窗 -->
<wd-popup v-model="showFilterPopup" position="right" custom-class="filter-popup">
<view class="filter-popup-content">
<view class="filter-popup-header li-flex li-justify-between li-items-center li-p-30 ">
<text class="li-text-36 li-font-bold">筛选类型</text>
<text class="ri-close-line li-text-40" @click="showFilterPopup = false"></text>
</view>
<view class="filter-popup-body li-p-30">
<view class="filter-section-title li-mb-20">提交时间</view>
<view class="date-range li-mb-30">
<view class="li-flex li-items-center li-justify-between li-mb-20">
<wd-button size="small" custom-class="date-btn"
:class="{'date-btn-active': dateRange === 'today'}"
@click="setDateRange('today')">今天</wd-button>
<wd-button size="small" custom-class="date-btn"
:class="{'date-btn-active': dateRange === 'yesterday'}"
@click="setDateRange('yesterday')">昨天</wd-button>
<wd-button size="small" custom-class="date-btn"
:class="{'date-btn-active': dateRange === 'week'}"
@click="setDateRange('week')">近7天</wd-button>
<wd-button size="small" custom-class="date-btn"
:class="{'date-btn-active': dateRange === 'month'}"
@click="setDateRange('month')">近30天</wd-button>
</view>
<view class="li-flex li-items-center li-justify-between">
<wd-datetime-picker v-model="showStartPicker" title="开始日期" v-model:value="startDate"
type="date" @confirm="handleDateChange">
<wd-input size="large" placeholder="开始日期" class="date-input"
:value="formatDate(startDate)" disabled></wd-input>
</wd-datetime-picker>
<text class="li-text-30 li-mx-20">至</text>
<wd-datetime-picker v-model="showEndPicker" title="结束日期" v-model:value="endDate" type="date"
@confirm="handleDateChange">
<wd-input size="large" placeholder="结束日期" class="date-input"
:value="formatDate(endDate)" disabled></wd-input>
</wd-datetime-picker>
</view>
</view>
<view class="filter-section-title li-mb-20">问卷类型</view>
<view class="type-filter li-flex li-flex-wrap li-mb-30">
<wd-tag v-for="(type, index) in typeList" :key="index"
:color="selectedTypes.includes(type.value) ? '#0070F0' : '#666'"
:bg-color="selectedTypes.includes(type.value) ? '#e8f4ff' : '#f5f5f5'"
custom-class="type-tag" @click="toggleTypeSelection(type.value)">
{{type.label}}
</wd-tag>
</view>
<view class="filter-section-title li-mb-20">其他筛选</view>
<view class="other-filters li-mb-30">
<view class="li-flex li-items-center li-justify-between li-mb-20">
<text class="li-text-30">只看匿名提交</text>
<wd-switch v-model="filterAnonymous" active-color="#0070F0" />
</view>
</view>
</view>
<view class="filter-popup-footer li-p-30 li-flex li-justify-between">
<wd-button size="large" custom-class="reset-btn" @click="resetFilters">重置</wd-button>
<wd-button size="large" type="primary" custom-class="confirm-btn"
@click="applyFilters">确定</wd-button>
</view>
</view>
</wd-popup>
</view>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, watch } from 'vue';
import { onLoad, onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app';
import { useNavigation } from '@/hooks/useNavigation';
import { useToast } from '@/uni_modules/wot-design-uni';
const Toast = useToast();
// 使用导航composable
const {
hasMultiplePages,
isTabBarPage,
checkRouteStack
} = useNavigation();
// 状态管理
const selectedVillage = ref('ALL'); // 设置默认为全部小区
const activeStatus = ref('ALL');
const loading = ref(false);
const finished = ref(false);
const page = ref(1);
const pageSize = ref(10);
// 筛选相关
const showFilterPopup = ref(false);
const dateRange = ref('');
const startDate = ref('');
const endDate = ref('');
const showStartPicker = ref(false);
const showEndPicker = ref(false);
const selectedTypes = ref([]);
const filterAnonymous = ref(false);
// 小区列表
const villageList = ref([
{ value: '1', label: '阳光花园小区' },
{ value: '2', label: '翠湖庭院' },
{ value: '3', label: '金色家园' }
]);
// 问卷类型列表
const typeList = ref([
{ value: '1', label: '满意度调查' },
{ value: '2', label: '意见征集' },
{ value: '3', label: '投票选举' },
{ value: '4', label: '需求调研' },
{ value: '5', label: '活动报名' }
]);
// 状态筛选选项
const statusList = ref([
{ label: '全部', value: 'ALL' },
{ label: '已查看', value: 1 },
{ label: '未查看', value: 0 }
]);
// 模拟问卷列表数据
const questionnaireList = ref([
// {
// id: 1,
// title: '2024年度物业服务满意度调查',
// village_name: '阳光花园小区',
// type: '满意度调查',
// user_name: '张三',
// is_anonymous: false,
// submit_time: '2024-06-15 14:23',
// question_count: 15,
// status: 1 // 已查看
// },
// {
// id: 2,
// title: '小区环境改造意见征集',
// village_name: '翠湖庭院',
// type: '意见征集',
// user_name: '李四',
// is_anonymous: true,
// submit_time: '2024-06-12 09:45',
// question_count: 8,
// status: 0 // 未查看
// },
// {
// id: 3,
// title: '业主委员会换届选举投票',
// village_name: '金色家园',
// type: '投票选举',
// user_name: '王五',
// is_anonymous: false,
// submit_time: '2024-06-10 18:30',
// question_count: 12,
// status: 1 // 已查看
// },
// {
// id: 4,
// title: '小区健身设施需求调研',
// village_name: '阳光花园小区',
// type: '需求调研',
// user_name: '赵六',
// is_anonymous: false,
// submit_time: '2024-06-08 11:20',
// question_count: 10,
// status: 1 // 已查看
// },
// {
// id: 5,
// title: '端午节活动满意度调查',
// village_name: '翠湖庭院',
// type: '满意度调查',
// user_name: '匿名用户',
// is_anonymous: true,
// submit_time: '2024-06-05 16:40',
// question_count: 6,
// status: 0 // 未查看
// }
]);
// 状态颜色配置
const getStatusColor = (status) => {
const colorMap = {
0: '#ff9d00', // 未查看
1: '#07c160' // 已查看
};
return colorMap[status] || '#666666';
};
// 状态背景色配置
const getStatusBgColor = (status) => {
const bgColorMap = {
0: '#fff6e9', // 未查看
1: '#e8fff1' // 已查看
};
return bgColorMap[status] || '#f5f5f5';
};
// 状态文字配置
const getStatusText = (status) => {
const textMap = {
0: '未查看',
1: '已查看'
};
return textMap[status] || '未知状态';
};
// 处理状态切换
const handleStatusChange = (status) => {
activeStatus.value = status;
resetList();
loadQuestionnaireList();
};
// 重置列表
const resetList = () => {
page.value = 1;
finished.value = false;
questionnaireList.value = [];
};
// 加载问卷列表数据
const loadQuestionnaireList = async () => {
if (loading.value || finished.value) return;
try {
loading.value = true;
// 实际项目中这里应该调用API
await new Promise(resolve => setTimeout(resolve, 1000));
if (page.value > 1) {
finished.value = true;
} else {
page.value++;
}
loading.value = false;
} catch (error) {
console.error('加载问卷记录失败', error);
Toast.fail('加载失败,请重试');
loading.value = false;
}
};
// 页面跳转
const toPages = (item) => {
switch (item.type) {
case 'nav':
uni.navigateBack({
delta: 1
});
break;
case 'home':
uni.switchTab({
url: '/pages/index/index'
});
break;
case 'detail':
// 更新记录状态为已查看
const record = questionnaireList.value.find(q => q.id === item.value.id);
if (record && record.status === 0) {
record.status = 1;
}
uni.navigateTo({
url: `/pagesB/questionnaire/detail?id=${item.value.id}`
});
break;
default:
break;
}
};
// 生命周期钩子
onLoad(() => {
checkRouteStack();
// loadQuestionnaireList();
});
// 下拉刷新
onPullDownRefresh(() => {
resetList();
loadQuestionnaireList().then(() => {
uni.stopPullDownRefresh();
});
});
// 上拉加载
onReachBottom(() => {
loadQuestionnaireList();
});
// 监听小区选择变化
watch(selectedVillage, (newVal) => {
resetList();
loadQuestionnaireList();
});
// 小区选择相关
const showVillagePicker = ref(false);
const pickerVillageList = ref([
[
{ value: 'ALL', label: '全部小区' },
{ value: '1', label: '阳光花园小区' },
{ value: '2', label: '翠湖庭院' },
{ value: '3', label: '金色家园' }
]
]);
const handleVillageConfirm = (selectedItem) => {
selectedVillage.value = selectedItem[0].value;
resetList();
loadQuestionnaireList();
};
// 筛选相关方法
const showFilter = () => {
showFilterPopup.value = true;
};
const setDateRange = (range) => {
dateRange.value = range;
const now = new Date();
switch (range) {
case 'today':
startDate.value = formatDate(now);
endDate.value = formatDate(now);
break;
case 'yesterday':
const yesterday = new Date(now);
yesterday.setDate(now.getDate() - 1);
startDate.value = formatDate(yesterday);
endDate.value = formatDate(yesterday);
break;
case 'week':
const weekStart = new Date(now);
weekStart.setDate(now.getDate() - 7);
startDate.value = formatDate(weekStart);
endDate.value = formatDate(now);
break;
case 'month':
const monthStart = new Date(now);
monthStart.setDate(now.getDate() - 30);
startDate.value = formatDate(monthStart);
endDate.value = formatDate(now);
break;
}
};
const formatDate = (date) => {
if (!date) return '';
if (typeof date === 'string') return date;
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
};
const handleDateChange = () => {
dateRange.value = '';
};
const toggleTypeSelection = (typeValue) => {
const index = selectedTypes.value.indexOf(typeValue);
if (index > -1) {
selectedTypes.value.splice(index, 1);
} else {
selectedTypes.value.push(typeValue);
}
};
const resetFilters = () => {
dateRange.value = '';
startDate.value = '';
endDate.value = '';
selectedTypes.value = [];
filterAnonymous.value = false;
};
const applyFilters = () => {
showFilterPopup.value = false;
resetList();
loadQuestionnaireList();
Toast.success('筛选条件已应用');
};
</script>
<style lang="scss">
page {
background-color: #F7F8FA;
}
.questionnaire-page {
min-height: 100vh;
position: relative;
z-index: 0;
}
.nav-bg-layer {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: calc(var(--status-bar-height) + 88rpx);
background: linear-gradient(180deg, rgba(255, 255, 255, 0.95) 0%, rgba(255, 255, 255, 0.9) 100%);
z-index: 10;
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
.page-bg {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(180deg, rgba(247, 248, 250, 0.9) 0%, rgba(247, 248, 250, 1) 100%);
z-index: -2;
}
.filter-section {
padding: 20rpx 0 0;
position: sticky;
/* #ifdef MP-WEIXIN */
top: calc(var(--status-bar-height) + 110rpx);
/* #endif */
/* #ifndef MP-WEIXIN */
top: calc(var(--status-bar-height) + 85rpx);
/* #endif */
z-index: 9;
background: rgba(255, 255, 255, 0.85);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
.status-tag {
height: 60rpx;
padding: 0 30rpx;
border-radius: 30rpx;
font-size: 28rpx;
color: #666;
background: rgba(255, 255, 255, 0.8);
margin-right: 20rpx;
margin-bottom: 16rpx;
transition: all 0.3s;
display: inline-flex;
align-items: center;
justify-content: center;
border: 2rpx solid transparent;
&:active {
transform: scale(0.95);
opacity: 0.8;
}
}
.status-tag.active {
color: #0070F0;
background: rgba(255, 255, 255, 0.95);
font-weight: 500;
border-color: #0070F0;
box-shadow: 0 2rpx 12rpx rgba(0, 112, 240, 0.15);
}
.questionnaire-card {
transition: all 0.3s;
position: relative;
z-index: 1;
&:active {
transform: scale(0.98);
opacity: 0.9;
}
.status-badge {
padding: 4rpx 20rpx;
border-radius: 20rpx;
font-size: 24rpx;
}
}
.li-bottom-border2 {
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.empty-image {
width: 240rpx;
height: 240rpx;
}
}
.view-btn {
width: 160rpx !important;
font-size: 26rpx !important;
height: 60rpx !important;
border-radius: 30rpx !important;
}
.user-badge {
display: inline-block;
font-size: 22rpx;
background-color: #f5f5f5;
color: #999;
padding: 2rpx 12rpx;
border-radius: 16rpx;
margin-left: 16rpx;
}
::v-deep .wd-navbar__placeholder {
height: calc(var(--status-bar-height) + 88rpx) !important;
padding-top: constant(safe-area-inset-top) !important;
padding-top: env(safe-area-inset-top) !important;
}
::v-deep .wd-select {
background-color: transparent !important;
margin-bottom: 0 !important;
.wd-select__value {
color: #333 !important;
}
.wd-select__label {
margin-right: 20rpx !important;
}
}
::v-deep .wd-select__right-icon {
color: #666 !important;
}
.filter-popup {
width: 80%;
height: 100%;
.filter-popup-content {
display: flex;
flex-direction: column;
height: 100%;
}
.filter-popup-header {
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
/* #ifdef MP-WEIXIN */
margin-top: 200rpx;
/* #endif */
/* #ifndef MP-WEIXIN */
margin-top: 100rpx;
/* #endif */
}
.filter-popup-body {
flex: 1;
overflow-y: auto;
}
.filter-popup-footer {
border-top: 1px solid rgba(0, 0, 0, 0.05);
.reset-btn {
width: 45% !important;
background-color: #f5f5f5 !important;
color: #666 !important;
}
.confirm-btn {
width: 45% !important;
}
}
}
.filter-section-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.date-btn {
width: 22% !important;
height: 70rpx !important;
background-color: #f5f5f5 !important;
color: #666 !important;
border: none !important;
&-active {
background-color: #e8f4ff !important;
color: #0070F0 !important;
font-weight: 500;
}
}
.date-input {
width: 45% !important;
background-color: #f5f5f5 !important;
}
.type-tag {
margin: 0 16rpx 16rpx 0 !important;
padding: 16rpx 24rpx !important;
border-radius: 8rpx !important;
font-size: 26rpx !important;
}
.village-filter {
.select-box {
background: rgba(255, 255, 255, 0.9);
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
}
}
</style>