<script setup>
import { ref, reactive, onMounted, computed } from 'vue';
import { onShow } from '@dcloudio/uni-app';
// 响应式数据
const surveyList = ref([]);
const total = ref(0);
const loading = ref(false);
const formData = reactive({
dcWjTitle: '',
dcId: [],
dcDept: '',
state: null
});
const bdrOptions = ref([]);
const bdrLoading = ref(false);
const pagination = reactive({
current: 1,
size: 10,
total: 0
});
const showBdrPlaceholder = ref(true);
const handleBdrFocus = () => {
showBdrList.value = true;
showBdrPlaceholder.value = false; // 输入框获得焦点时隐藏占位文字
filterBdrOptions();
};
// 选择被测评人
const handleBdrSelect = (item) => {
formData.dcId = [item.value];
showBdrList.value = false;
bdrSearchKeyword.value = '';
showBdrPlaceholder.value = false; // 选择后不显示占位文字
};
// 新增的搜索选择器相关变量
const bdrSearchKeyword = ref('');
const showBdrList = ref(false);
const filteredBdrOptions = ref([]);
// 状态选择器相关变量
const stateOptions = ref([
{ label: '全部', value: null },
{ label: '已提交', value: 1 },
{ label: '未提交', value: 0 }
]);
const showStateList = ref(false);
// 计算选中的被测评人标签
const selectedBdrLabel = computed(() => {
if (formData.dcId.length === 0) return '';
const selected = bdrOptions.value.find(b => b.value === formData.dcId[0]);
return selected ? selected.label : '';
});
// 获取状态标签
const getStateLabel = (value) => {
const state = stateOptions.value.find(s => s.value === value);
return state ? state.label : '全部';
};
// 过滤选项
const filterBdrOptions = () => {
if (!bdrSearchKeyword.value) {
filteredBdrOptions.value = [...bdrOptions.value];
return;
}
const keyword = bdrSearchKeyword.value.toLowerCase();
filteredBdrOptions.value = bdrOptions.value.filter(item =>
item.label.toLowerCase().includes(keyword)
);
};
// 选择状态
const handleStateSelect = (state) => {
formData.state = state.value;
showStateList.value = false;
};
// 切换下拉列表显示
const toggleBdrList = () => {
showBdrList.value = !showBdrList.value;
if (showBdrList.value) {
filterBdrOptions();
}
};
// 切换状态列表显示
const toggleStateList = () => {
showStateList.value = !showStateList.value;
};
// 获取问卷数据
const fetchSurveyData = async () => {
try {
loading.value = true;
// 获取 token
const token = uni.getStorageSync('token');
if (!token) {
uni.showToast({ title: '请先登录', icon: 'none' });
uni.redirectTo({ url: '/pages/login/login' });
return;
}
// 准备请求参数
const params = {
pageNum: pagination.current,
pageSize: pagination.size,
dcWjTitle: formData.dcWjTitle,
dcId: formData.dcId.join(','),
dcDept: formData.dcDept,
state: formData.state
};
// 发送请求
const res = await uni.request({
url: 'http://172.26.26.43/dev-api/wjdc/wj/listTx',
method: 'GET',
data: params,
header: {
'Authorization': `Bearer ${token}`
}
});
// 错误处理
if (res.statusCode !== 200) {
throw new Error(`请求失败,状态码: ${res.statusCode}`);
}
// 处理响应数据
const response = res.data;
if (response.code === 200) {
surveyList.value = response.rows || [];
total.value = response.total || 0;
pagination.total = response.total || 0;
} else {
throw new Error(response.msg || 'API返回错误');
}
} catch (error) {
console.error('请求错误:', error);
uni.showToast({
title: error.message || '加载失败',
icon: 'none',
duration: 3000
});
} finally {
loading.value = false;
}
};
// 获取被测评人列表
const fetchBdrList = async () => {
try {
const token = uni.getStorageSync('token');
if (!token) {
uni.showToast({ title: '请先登录', icon: 'none' });
return;
}
const res = await uni.request({
url: 'http://172.26.26.43/dev-api/wjdc/wj/getBdrList',
method: 'GET',
header: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
timeout: 8000
});
if (res.statusCode !== 200) {
throw new Error(`HTTP错误: ${res.statusCode}`);
}
const response = res.data;
let data = [];
if (Array.isArray(response)) {
data = response;
} else if (response.data) {
data = response.data;
} else if (response.rows) {
data = response.rows;
} else {
throw new Error('无效的API响应格式');
}
if (!Array.isArray(data)) {
throw new Error('响应数据格式错误');
}
bdrOptions.value = data.map(item => ({
label: item.dcName || '未知名称',
value: item.dcId || item.id || null
}));
// 初始化过滤后的选项
filteredBdrOptions.value = [...bdrOptions.value];
} catch (error) {
console.error('获取被测评人失败:', error);
let errorMsg = '获取被测评人失败';
if (error.message.includes('401')) {
errorMsg = '认证失败,请重新登录';
uni.redirectTo({ url: '/pages/login/login' });
} else if (error.message.includes('timeout')) {
errorMsg = '请求超时,请检查网络';
} else if (error.message.includes('404')) {
errorMsg = 'API地址不存在';
}
uni.showToast({
title: errorMsg,
icon: 'none',
duration: 3000
});
}
};
// 搜索按钮处理
const handleSearch = () => {
pagination.current = 1;
fetchSurveyData();
};
// 重置按钮处理
const handleReset = () => {
formData.dcWjTitle = '';
formData.dcId = [];
formData.dcDept = '';
formData.state = null;
bdrSearchKeyword.value = '';
showBdrList.value = false;
showStateList.value = false;
handleSearch();
showBdrPlaceholder.value = true; // 重置后显示占位文字
};
// 编辑/查看问卷
const handleView = (item) => {
uni.navigateTo({
url: `/pages/operation/operation?id=${item.dcWjId}`
});
};
// 分页大小改变
const handleSizeChange = (e) => {
pagination.size = e.detail.value;
fetchSurveyData();
};
// 页码改变
const handlePageChange = (page) => {
pagination.current = page;
fetchSurveyData();
};
// 刷新数据
const refreshData = () => {
pagination.current = 1;
fetchSurveyData();
};
// 页面显示时加载数据
onShow(() => {
fetchSurveyData();
fetchBdrList();
});
</script>
<template>
<view class="container">
<!-- 搜索表单区域 -->
<view class="search-card">
<!-- 问卷标题单独一行 -->
<view class="form-group">
<text class="form-label">问卷标题:</text>
<input
v-model="formData.dcWjTitle"
placeholder="请输入问卷标题"
class="form-input"
/>
</view>
<!-- 被测评人和部门在同一行 -->
<view class="form-row">
<!-- 被测评人选择器 -->
<view class="form-group form-group-half">
<text class="form-label">被测评人:</text>
<view class="search-select-container">
<!-- 搜索输入框 -->
<input
:value="bdrSearchKeyword"
:placeholder="showBdrPlaceholder ? '搜索被测评人' : ''"
class="search-input"
@focus="handleBdrFocus"
@input="handleBdrInput"
/>
<!-- 下拉列表 -->
<view v-if="showBdrList" class="dropdown-list">
<scroll-view scroll-y="true" class="dropdown-scroll">
<view
v-for="(item, index) in filteredBdrOptions"
:key="index"
class="dropdown-item"
:class="{ 'selected': formData.dcId[0] === item.value }"
@click="handleBdrSelect(item)"
>
{{ item.label }}
</view>
<view v-if="filteredBdrOptions.length === 0" class="empty-option">
无匹配结果
</view>
</scroll-view>
</view>
<!-- 已选项显示 -->
<view v-if="formData.dcId.length > 0 && !showBdrList" class="selected-display">
{{ selectedBdrLabel }}
</view>
<!-- 下拉箭头 -->
<view class="dropdown-icon" @click="toggleBdrList">
<text>▼</text>
</view>
<!-- 遮罩层 -->
<view
v-if="showBdrList"
class="dropdown-mask"
@click="showBdrList = false"
></view>
</view>
</view>
<!-- 人员部门 -->
<view class="form-group form-group-half">
<text class="form-label">人员部门:</text>
<input
v-model="formData.dcDept"
placeholder="请输入部门"
class="form-input"
/>
</view>
</view>
<!-- 提交状态 -->
<view class="form-group">
<text class="form-label">提交状态:</text>
<view class="search-select-container">
<!-- 状态选择框 -->
<view
class="state-select-box"
@click="toggleStateList"
>
<text class="selected-state">
{{ getStateLabel(formData.state) }}
</text>
<view class="dropdown-icon">
<text>▼</text>
</view>
</view>
<!-- 状态下拉列表 -->
<view v-if="showStateList" class="dropdown-list state-dropdown">
<view
v-for="(state, index) in stateOptions"
:key="index"
class="dropdown-item"
:class="{ 'selected': formData.state === state.value }"
@click="handleStateSelect(state)"
>
{{ state.label }}
</view>
</view>
<!-- 遮罩层 -->
<view
v-if="showStateList"
class="dropdown-mask"
@click="showStateList = false"
></view>
</view>
</view>
<!-- 按钮组 -->
<view class="button-group">
<button class="search-button" @click="handleSearch">搜索</button>
<button class="reset-button" @click="handleReset">重置</button>
</view>
</view>
<!-- 数据显示区域 -->
<view class="data-card">
<view class="card-header">
<button class="refresh-button" @click="refreshData">刷新数据</button>
</view>
<!-- 加载状态 -->
<view v-if="loading" class="loading-container">
<view class="loading-spinner"></view>
<text class="loading-text">加载中...</text>
</view>
<!-- 数据展示 -->
<view v-else>
<view v-for="(item, index) in surveyList" :key="index" class="data-card-item">
<view class="card-header-section">
<view class="card-title">{{ item.dcWjTitle }}</view>
</view>
<view class="card-body-section">
<view class="card-row">
<text class="card-label">被测评人:</text>
<text class="card-value">{{ item.dcName }}</text>
</view>
<view class="card-row">
<text class="card-label">部门:</text>
<text class="card-value">{{ item.dcDept }}</text>
</view>
<view class="card-row">
<text class="card-label">创建时间:</text>
<text class="card-value">{{ item.createTime }}</text>
</view>
<view class="card-row">
<text class="card-label">提交时间:</text>
<text class="card-value">{{ item.updateTime || '-' }}</text>
</view>
</view>
<view class="card-footer-section">
<view class="status-container">
<view :class="['status-tag', item.state === '1' ? 'status-submitted' : 'status-not-submitted']">
{{ item.state === '1' ? '已提交' : '未提交' }}
</view>
<view class="score">总分: {{ item.score || '0' }}</view>
</view>
<button class="view-button" @click="handleView(item)">编辑/查看</button>
</view>
</view>
<!-- 空数据提示 -->
<view v-if="surveyList.length === 0" class="empty">
<text>暂无数据</text>
</view>
</view>
<!-- 分页控件 -->
<view class="pagination-container">
<picker
mode="selector"
:range="['5', '10', '20', '50']"
@change="handleSizeChange"
class="page-size-picker"
>
<view class="picker">
每页 {{ pagination.size }} 条
</view>
</picker>
<view class="pagination-buttons">
<button
:disabled="pagination.current === 1"
@click="handlePageChange(pagination.current - 1)"
class="page-button prev-button"
>
上一页
</button>
<text class="page-info">
第 {{ pagination.current }} 页 / 共 {{ Math.ceil(pagination.total / pagination.size) }} 页
</text>
<button
:disabled="pagination.current >= Math.ceil(pagination.total / pagination.size)"
@click="handlePageChange(pagination.current + 1)"
class="page-button next-button"
>
下一页
</button>
</view>
<text class="total-records">共 {{ pagination.total }} 条记录</text>
</view>
</view>
</view>
</template>
<style scoped>
.container {
padding: 20rpx;
background-color: #f5f5f5;
min-height: 100vh;
}
/* 新增样式 */
.form-row {
display: flex;
gap: 20rpx;
margin-bottom: 30rpx;
}
.form-group-half {
flex: 1;
min-width: 0; /* 防止内容溢出 */
}
/* 调整搜索选择器容器高度 */
.search-select-container {
position: relative;
height: 80rpx;
}
/* 调整搜索输入框高度 */
.search-input {
height: 80rpx;
}
/* 调整状态选择框高度 */
.state-select-box {
height: 80rpx;
}
/* 搜索卡片样式 */
.search-card {
background: #fff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 30rpx;
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.05);
}
.form-group {
margin-bottom: 30rpx;
}
.form-label {
display: block;
margin-bottom: 10rpx;
font-size: 28rpx;
color: #606266;
font-weight: 500;
}
.form-input, .form-picker {
width: 100%;
height: 80rpx;
padding: 0 20rpx;
border: 1px solid #dcdfe6;
border-radius: 8rpx;
font-size: 28rpx;
background-color: #fff;
box-sizing: border-box;
}
.picker {
height: 80rpx;
line-height: 80rpx;
color: #606266;
}
.button-group {
display: flex;
gap: 20rpx;
margin-top: 20rpx;
}
.search-button, .reset-button {
flex: 1;
height: 80rpx;
line-height: 80rpx;
font-size: 30rpx;
border-radius: 8rpx;
text-align: center;
}
.search-button {
background-color: #409eff;
color: #fff;
}
.reset-button {
background-color: #f5f7fa;
color: #606266;
border: 1px solid #dcdfe6;
}
/* 数据卡片样式 */
.data-card {
background: #fff;
border-radius: 16rpx;
padding: 30rpx;
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.05);
}
.card-header {
margin-bottom: 30rpx;
}
.refresh-button {
background-color: #409eff;
color: #fff;
height: 70rpx;
line-height: 70rpx;
font-size: 28rpx;
border-radius: 8rpx;
}
/* 数据卡片项 */
.data-card-item {
background: #fff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 30rpx;
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.05);
border: 1px solid #ebeef5;
}
.card-header-section {
padding-bottom: 20rpx;
border-bottom: 1px solid #f0f2f5;
margin-bottom: 20rpx;
}
.card-title {
font-size: 34rpx;
font-weight: bold;
color: #303133;
line-height: 1.4;
word-break: break-word;
}
.card-body-section {
margin-bottom: 20rpx;
}
.card-row {
display: flex;
margin-bottom: 15rpx;
font-size: 28rpx;
}
.card-label {
color: #606266;
width: 150rpx;
}
.card-value {
color: #303133;
flex: 1;
word-break: break-word;
}
.card-footer-section {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 20rpx;
border-top: 1px solid #f0f2f5;
}
.status-container {
display: flex;
align-items: center;
gap: 20rpx;
}
.status-tag {
padding: 8rpx 20rpx;
border-radius: 40rpx;
font-size: 24rpx;
font-weight: 500;
}
.status-submitted {
background-color: #f0f9eb;
color: #67c23a;
border: 1px solid #e1f3d8;
}
.status-not-submitted {
background-color: #fef0f0;
color: #f56c6c;
border: 1px solid #fde2e2;
}
.score {
font-size: 28rpx;
color: #e6a23c;
font-weight: 500;
}
.view-button {
background-color: #409eff;
color: #fff;
height: 60rpx;
line-height: 60rpx;
padding: 0 30rpx;
font-size: 26rpx;
border-radius: 40rpx;
}
/* 分页样式 */
.pagination-container {
margin-top: 40rpx;
display: flex;
flex-direction: column;
align-items: center;
gap: 20rpx;
}
.page-size-picker {
width: 200rpx;
height: 60rpx;
border: 1px solid #dcdfe6;
border-radius: 8rpx;
text-align: center;
line-height: 60rpx;
font-size: 26rpx;
}
.pagination-buttons {
display: flex;
align-items: center;
gap: 20rpx;
}
.page-button {
height: 60rpx;
line-height: 60rpx;
padding: 0 30rpx;
font-size: 26rpx;
border-radius: 8rpx;
background-color: #f5f7fa;
color: #606266;
border: 1px solid #dcdfe;
}
.page-button:disabled {
opacity: 0.5;
}
.prev-button::before {
content: "← ";
}
.next-button::after {
content: " →";
}
.page-info {
font-size: 26rpx;
color: #606266;
}
.total-records {
font-size: 26rpx;
color: #909399;
}
/* 加载状态样式 */
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80rpx 0;
}
.loading-spinner {
width: 80rpx;
height: 80rpx;
border: 8rpx solid #f3f3f3;
border-top: 8rpx solid #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 30rpx;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading-text {
color: #666;
font-size: 28rpx;
}
/* 空数据提示 */
.empty {
text-align: center;
padding: 100rpx 0;
color: #999;
font-size: 32rpx;
}
/* 新增的搜索选择器样式 */
.search-select-container {
position: relative;
width: 100%;
}
.search-input {
width: 100%;
height: 80rpx;
padding: 0 60rpx 0 20rpx;
border: 1px solid #dcdfe6;
border-radius: 8rpx;
font-size: 28rpx;
background-color: #fff;
box-sizing: border-box;
}
.dropdown-icon {
position: absolute;
right: 20rpx;
top: 50%;
transform: translateY(-50%);
pointer-events: none;
font-size: 24rpx;
color: #606266;
}
.dropdown-list {
position: absolute;
top: 100%;
left: 0;
right: 0;
max-height: 400rpx;
background: #fff;
border: 1px solid #dcdfe6;
border-radius: 8rpx;
z-index: 1000;
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.1);
margin-top: 10rpx;
}
.dropdown-scroll {
max-height: 400rpx;
}
.dropdown-item {
padding: 20rpx;
font-size: 28rpx;
color: #333;
border-bottom: 1px solid #f0f2f5;
}
.dropdown-item.selected {
background-color: #f5f7fa;
color: #409eff;
font-weight: 500;
}
.dropdown-item:last-child {
border-bottom: none;
}
.dropdown-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: transparent;
z-index: 999;
}
.empty-option {
padding: 20rpx;
text-align: center;
color: #999;
font-size: 28rpx;
}
.selected-display {
position: absolute;
top: 0;
left: 0;
right: 60rpx;
height: 80rpx;
padding: 0 20rpx;
line-height: 80rpx;
color: #303133;
pointer-events: none;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* 状态选择器样式 */
.state-select-box {
position: relative;
width: 100%;
height: 80rpx;
padding: 0 60rpx 0 20rpx;
border: 1px solid #dcdfe6;
border-radius: 8rpx;
font-size: 28rpx;
background-color: #fff;
display: flex;
align-items: center;
box-sizing: border-box;
}
.selected-state {
color: #303133;
}
.state-dropdown {
max-height: 300rpx;
}
</style>
我想要修改搜索板块的布局,我想要提交状态板块和搜索、重置按钮在同一行,不至于太宽