<footer> <center> <pageNum>d</pageNum> </center> </footer> 设置页码 第几页 共几页 怎么修改呢
时间: 2023-11-02 16:53:02 浏览: 94
您可以使用CSS样式表来修改页码的样式和位置,例如:
/* 设置页码样式 */
footer {
text-align: center;
font-size: 14px;
margin-top: 20px;
}
/* 设置页码数字样式 */
pageNum {
font-weight: bold;
color: #333;
}
/* 设置页码链接样式 */
pageNum a {
display: inline-block;
padding: 5px 10px;
border: 1px solid #ccc;
background-color: #f5f5f5;
color: #333;
text-decoration: none;
margin: 0 5px;
}
/* 设置当前页码链接样式 */
pageNum a.current {
font-weight: bold;
color: #fff;
background-color: #333;
}
然后,在HTML中使用pageNum元素来设置页码,并在CSS中修改样式,例如:
<footer>
<pageNum>Page 1 of 3</pageNum>
</footer>
这将显示“Page 1 of 3”作为页码,您可以根据需要修改文本和样式。
相关问题
<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-row-bottom"> <!-- 提交状态 --> <view class="form-group state-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> <!-- 数据显示区域 --> <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> 提交状态下拉选择宽并没有和按钮组在同一行
<think>我们注意到在模板中,提交状态和按钮组被包裹在一个`view.form-row-bottom`中,但是样式没有定义`form-row-bottom`,导致它们没有在同一行显示。我们需要为`form-row-bottom`添加flex布局样式,并调整内部元素的宽度。
以下是修改步骤:
1. 为`form-row-bottom`添加flex布局,使其内部元素(状态选择器和按钮组)在同一行显示。
2. 调整状态选择器的宽度,使其占据剩余空间,按钮组固定宽度。
3. 添加一些间距,使布局更美观。
具体修改如下:
在`<style scoped>`中添加以下样式:
```css
.form-row-bottom {
display: flex;
gap: 20rpx; /* 元素之间的间距 */
align-items: flex-end; /* 底部对齐,因为状态选择器有标签在上方 */
}
.state-group {
flex: 1; /* 占据剩余空间 */
min-width: 0; /* 防止内容溢出 */
}
.button-group {
flex-shrink: 0;
display: flex;
gap: 20rpx;
margin-top: 0; /* 覆盖原有的上边距 */
width: 300rpx; /* 固定宽度,根据实际情况调整 */
}
/* 响应式调整:在小屏幕上改为垂直布局 */
@media (max-width: 600px) {
.form-row-bottom {
flex-direction: column;
}
.state-group,
.button-group {
width: 100%;
}
}
```
同时,在模板中,我们注意到按钮组之前有`margin-top:20rpx`,这会导致在同行显示时有多余的间距。我们已经在`.button-group`中移除了上边距(`margin-top:0`)。
另外,确保状态选择器的输入框宽度填满父容器,我们之前已经设置过,但可以再确认一下状态选择框的样式:
```css
.state-select-box {
width: 100%; /* 填满父容器 */
}
```
这样修改后,提交状态和按钮组应该在同一行,并且在小屏幕设备上自动调整为垂直布局。
注意:如果按钮组在同行显示时宽度不够,可以适当调整`width: 300rpx`的值,或者使用百分比宽度,比如`width: 50%`,但要注意与状态选择器的比例。
由于我们的按钮有两个,所以固定宽度可能需要根据实际情况调整。如果按钮文字较长,可以适当增加宽度。
最后,检查一下是否在`form-row-bottom`中正确应用了这些样式类。
在模板中,我们给提交状态的部分添加了`state-group`类,给按钮组添加了`button-group`类,所以上述样式应该能生效。
如果问题仍然存在,请检查是否还有其他样式覆盖,或者调整flex的比例。
修改后的代码应该能解决提交状态下拉选择框和按钮组不在同一行的问题。</think>我注意到提交状态下拉选择器和按钮组没有在同一行显示的问题。这是因为缺少了必要的Flex布局样式。让我修复这个问题:
```vue
<!-- 修改后的搜索卡片部分 -->
<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">
<!-- ... 被测评人选择器代码保持不变 ... -->
</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-row-bottom">
<!-- 提交状态 -->
<view class="form-group state-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>
```
```css
/* 在 <style scoped> 中添加以下样式 */
.form-row-bottom {
display: flex;
gap: 20rpx;
margin-top: 10rpx;
}
.state-group {
flex: 1; /* 占据剩余空间 */
min-width: 0; /* 防止内容溢出 */
margin-bottom: 0; /* 移除底部边距 */
}
.button-group {
flex-shrink: 0;
display: flex;
gap: 20rpx;
margin-top: 0; /* 移除顶部边距 */
width: 300rpx; /* 固定宽度 */
}
/* 调整状态选择框宽度 */
.state-select-box {
width: 100%; /* 填满父容器 */
}
/* 响应式调整 - 小屏幕布局 */
@media screen and (max-width: 600rpx) {
.form-row-bottom {
flex-direction: column;
}
.state-group,
.button-group {
width: 100%;
}
.button-group {
margin-top: 20rpx;
}
}
```
### 修改说明:
1. **添加 Flex 布局**:
- 创建了 `.form-row-bottom` 容器,使用 `display: flex` 确保提交状态和按钮组在同一行
- 设置 `.state-group` 为 `flex: 1` 使其占据剩余空间
- 设置 `.button-group` 为固定宽度(300rpx)并防止收缩
2. **移除多余边距**:
- 移除了 `.state-group` 的 `margin-bottom`
- 移除了 `.button-group` 的 `margin-top`
3. **宽度调整**:
- 设置 `.state-select-box` 宽度为 100%,确保填满父容器
- 在小屏幕设备上,将布局改为垂直排列
4. **响应式优化**:
- 添加了媒体查询,在小屏幕设备上将布局改为垂直排列
- 确保在小屏幕上所有元素都能正常显示
这样修改后,提交状态下拉选择器和搜索/重置按钮将在同一行显示,布局更加紧凑合理。
<script setup> import { ref, onMounted } from 'vue'; import { onShow } from '@dcloudio/uni-app'; // 响应式数据 const surveyList = ref([]); const total = ref(0); const loading = ref(false); // 获取问卷数据 const fetchSurveyData = async () => { try { loading.value = true; // 使用uni.request发送请求 const [error, res] = await uni.request({ url: 'http://172.26.26.43/dev-api/wjdc/wj/listTx', method: 'GET', data: { pageNum: 1, pageSize: 10, dcWjTitle: '', dcId: '', dcDept: '' } }); if (error) throw error; // 处理响应数据 const response = res.data; if (response.code === 200) { surveyList.value = response.rows; total.value = response.total; } else { throw new Error(response.msg || '请求失败'); } } catch (error) { uni.showToast({ title: error.message || '加载失败', icon: 'none', duration: 2000 }); } finally { loading.value = false; } }; // 使用onShow替代onMounted确保每次进入页面都刷新 onShow(() => { fetchSurveyData(); }); </script> <template> <view class="container"> <!-- 自定义加载状态 --> <view v-if="loading" class="loading-container"> <view class="loading-spinner"></view> <text class="loading-text">加载中...</text> </view> <!-- 数据展示 --> <template v-else> <text class="total">共 {{ total }} 条记录</text> <view v-for="(item, index) in surveyList" :key="index" class="card"> <view class="title">{{ item.dcWjTitle }}</view> <view class="info"> <text>部门: {{ item.dcDept }}</text> <text>创建人: {{ item.createBy }}</text> </view> <view class="meta"> <text>创建时间: {{ item.createTime }}</text> <text class="score">得分: {{ item.score }}</text> </view> </view> <!-- 空数据提示 --> <view v-if="surveyList.length === 0" class="empty"> <text>暂无数据</text> </view> </template> </view> </template> <style scoped> .container { padding: 20rpx; background-color: #f5f5f5; min-height: 100vh; } /* 自定义加载状态样式 */ .loading-container { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 40rpx 0; } .loading-spinner { width: 60rpx; height: 60rpx; border: 6rpx solid #f3f3f3; border-top: 6rpx solid #3498db; border-radius: 50%; animation: spin 1s linear infinite; margin-bottom: 20rpx; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .loading-text { color: #666; font-size: 28rpx; } /* 数据样式 */ .total { display: block; margin: 20rpx 0; padding: 0 20rpx; color: #888; font-size: 28rpx; } .card { padding: 30rpx; margin-bottom: 30rpx; border-radius: 16rpx; background-color: #fff; box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.05); } .title { font-size: 34rpx; font-weight: bold; margin-bottom: 20rpx; color: #333; } .info { display: flex; justify-content: space-between; color: #666; font-size: 28rpx; margin-bottom: 15rpx; } .meta { display: flex; justify-content: space-between; color: #999; font-size: 26rpx; margin-top: 15rpx; } .score { color: #e74c3c; font-weight: bold; } .empty { text-align: center; padding: 100rpx 0; color: #999; font-size: 32rpx; } </style> 我的数据加载板块参照代码:<template> <div class="detail-container"> <!-- 索引板块卡片 --> <el-card class="index-card"> <!-- 表单区域 --> <el-form :model="formData" label-position="top" ref="indexForm" class="mobile-form" > <!-- 问卷标题 --> <el-form-item label="问卷标题:" prop="dcWjTitle"> <el-input v-model="formData.dcWjTitle" placeholder="请输入问卷标题" clearable :prefix-icon="Document" /> </el-form-item> <div class="form-row"> <!-- 被测评人 --> <el-form-item label="被测评人:" prop="dcId" class="inline-form-item"> <el-select v-model="formData.dcId" multiple filterable remote reserve-keyword clearable placeholder="请选择被测评人" :remote-method="searchBdr" :loading="bdrLoading" @focus="handleBdrFocus" > <el-option v-for="item in bdrOptions" :key="item.dcId" :label="item.dcName" :value="item.dcId" /> </el-select> </el-form-item> <!-- 人员部门 --> <el-form-item label="人员部门:" prop="dcDept" class="inline-form-item"> <el-input v-model="formData.dcDept" placeholder="请输入人员部门" clearable :prefix-icon="OfficeBuilding" /> </el-form-item> </div> <!-- 提交状态 --> <el-form-item label="提交状态:" prop="state"> <el-select v-model="formData.state" placeholder="请选择提交状态" clearable class="mobile-select" > <el-option label="已提交" :value="1" /> <el-option label="未提交" :value="0" /> </el-select> </el-form-item> <!-- 新增:按钮区域 --> <el-form-item class="button-group"> <el-button type="primary" @click="handleSearch" class="action-button" :icon="Search" > 搜索 </el-button> <el-button @click="handleReset" class="action-button" :icon="Refresh" > 重置 </el-button> </el-form-item> </el-form> </el-card> <!-- 数据显示板块 --> <el-card class="data-card"> <template #header> <div class="card-header"> <el-button type="primary" size="small" :icon="Refresh" @click="fetchData" > 刷新数据 </el-button> </div> </template> <!-- 数据加载 --> <div v-loading="loading" class="card-container"> <div v-for="(item, index) in tableData" :key="item.dcWjId" class="data-card-item" > <!-- 顶部:序号和问卷标题 --> <div class="card-header-section"> <!-- <div class="card-id">序号{{ item.dcWjId }}</div> --> <div class="card-title">{{ item.dcWjTitle }}</div> </div> <!-- 中间:其他数据 --> <div class="card-body-section"> <div class="card-row"> <span class="card-label">被测评人:</span> <span class="card-value">{{ item.dcName }}</span> </div> <div class="card-row"> <span class="card-label">部门:</span> <span class="card-value">{{ item.dcDept }}</span> </div> <div class="card-row"> <span class="card-label">创建时间:</span> <span class="card-value">{{ item.createTime }}</span> </div> <div class="card-row"> <span class="card-label">提交时间:</span> <span class="card-value">{{ item.updateTime || '-' }}</span> </div> </div> <!-- 底部:状态、得分和操作按钮 --> <div class="card-footer-section"> <div class="status-container"> <el-tag :type="item.state === '1' ? 'success' : 'info'"> {{ item.state === '1' ? '已提交' : '未提交' }} </el-tag> <div class="score">总分: {{ item.score || '0' }}</div> </div> <el-button size="small" type="primary" @click="handleView(item)" class="action-btn" > 编辑/查看 </el-button> </div> </div> <!-- 空数据提示 --> <el-empty v-if="tableData.length === 0" description="暂无数据" /> </div> <!-- 分页控件 --> <div class="pagination-container"> <el-pagination v-model:current-page="pagination.current" v-model:page-size="pagination.size" :page-sizes="[5, 10, 20, 50]" layout="total, sizes, prev, pager, next, jumper" :total="pagination.total" @size-change="handleSizeChange" @current-change="handlePageChange" /> </div> </el-card> </div> </template> <script setup> // 确保正确导入所有 Vue 函数 import { ref, reactive, onMounted, onUnmounted } from 'vue'; import axios from 'axios'; import { Document, User, OfficeBuilding, Search, Refresh } from '@element-plus/icons-vue'; import { ElMessage } from 'element-plus'; import { useRouter } from 'vue-router'; const router = useRouter(); // 环境变量管理API地址 const API_BASE = import.meta.env.VITE_API_BASE || 'http://172.26.26.43/dev-api'; const API_URL = `${API_BASE}/wjdc/wj/listTx`; const BDR_API_URL = `${API_BASE}/wjdc/wj/getBdrList`; // 被测评人相关数据 const bdrOptions = ref([]); // 被测评人选项列表 const bdrLoading = ref(false); // 加载状态 const bdrCache = ref([]); // 缓存所有被测评人数据 // 表单数据 const formData = reactive({ dcWjTitle: '', dcId: [], dcDept: '', state: null }); // 表格数据 const tableData = ref([]); const loading = ref(false); // 分页配置 const pagination = reactive({ current: 1, size: 10, total: 0 }); // 表单引用 const indexForm = ref(null); // 请求取消令牌 let cancelTokenSource = axios.CancelToken.source(); // 处理被测评人输入框获取焦点 const handleBdrFocus = () => { if (bdrCache.value.length === 0) { fetchBdrList(''); } }; // 获取被测评人列表 const fetchBdrList = async (keyword = '') => { const token = getAuthToken(); if (!token) return; bdrLoading.value = true; try { const response = await axios.get(BDR_API_URL, { headers: { 'Authorization': `Bearer ${token}` } }); // 判断返回的数据是否是数组 if (Array.isArray(response.data)) { // 缓存所有数据 bdrCache.value = response.data; // 根据关键字过滤 if (keyword) { const searchTerm = keyword.toLowerCase(); bdrOptions.value = bdrCache.value.filter(item => item.dcName && item.dcName.toLowerCase().includes(searchTerm) ).slice(0, 10); // 最多显示10条 } else { // 未输入关键字时显示前10条 bdrOptions.value = bdrCache.value.slice(0, 10); } } else { // 如果不是数组,则按照原有格式处理(假设有code和data) if (response.data && response.data.code === 200) { bdrCache.value = response.data.data || []; // 同样的过滤逻辑 if (keyword) { const searchTerm = keyword.toLowerCase(); bdrOptions.value = bdrCache.value.filter(item => item.dcName.toLowerCase().includes(searchTerm) ).slice(0, 10); } else { bdrOptions.value = bdrCache.value.slice(0, 10); } } else { const msg = response.data?.msg || '返回数据格式不正确'; ElMessage.error('获取被测评人列表失败: ' + msg); } } } catch (error) { console.error('获取被测评人列表失败:', error); ElMessage.error('获取被测评人列表失败'); } finally { bdrLoading.value = false; } }; // 搜索被测评人(防抖) let searchBdrTimer = null; const searchBdr = (query) => { if (searchBdrTimer) clearTimeout(searchBdrTimer); searchBdrTimer = setTimeout(() => { if (bdrCache.value.length === 0) { fetchBdrList(query); } else { // 本地过滤 if (query) { const searchTerm = query.toLowerCase(); bdrOptions.value = bdrCache.value.filter(item => item.dcName.toLowerCase().includes(searchTerm) ).slice(0, 10); } else { bdrOptions.value = bdrCache.value.slice(0, 10); } } }, 300); }; // 获取认证令牌 const getAuthToken = () => { const token = localStorage.getItem('token'); if (!token) { ElMessage.warning('请先登录'); router.push('/login'); return null; } return token; }; // 搜索按钮处理函数 - 防抖 let searchTimer = null; const handleSearch = () => { // 检查被测评人选择数量 if (formData.dcId.length > 1) { ElMessage.warning({ message: '当前只能搜索一个被测人员', duration: 3000 }); return; } if (searchTimer) clearTimeout(searchTimer); searchTimer = setTimeout(() => { pagination.current = 1; fetchData(); }, 300); }; // 重置按钮处理函数 const handleReset = () => { if (indexForm.value) { indexForm.value.resetFields(); // 确保重置后 dcId 是空数组 formData.dcId = []; } handleSearch(); }; // 编辑/查看 const handleView = (row) => { router.push({ name: 'Operation', // 路由名称 params: { id: row.dcWjId // 传递问卷ID作为参数 } }); }; // 分页大小改变 const handleSizeChange = (size) => { pagination.size = size; fetchData(); }; // 页码改变 const handlePageChange = (page) => { pagination.current = page; fetchData(); }; // 获取数据 const fetchData = async () => { // 获取认证令牌 const token = getAuthToken(); if (!token) return; // 取消之前的请求 if (cancelTokenSource) { cancelTokenSource.cancel('请求被取消'); } cancelTokenSource = axios.CancelToken.source(); loading.value = true; try { // 构造请求参数 const params = { pageNum: pagination.current, pageSize: pagination.size, ...formData, // 安全处理:确保 dcId 是数组再 join dcId: Array.isArray(formData.dcId) ? formData.dcId.join(',') : '' // 将数组转换为逗号分隔的字符串 }; // 调用API - 添加认证头 const response = await axios.get(API_URL, { params, cancelToken: cancelTokenSource.token, headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` } }); // 处理响应数据 const { data } = response; if (data && data.code === 200) { // 修改点:直接使用 data.rows 和 data.total tableData.value = data.rows || []; pagination.total = data.total || 0; // 空数据提示 if (tableData.value.length === 0) { ElMessage.info('没有找到匹配的数据'); } } else { const errorMsg = data?.msg || '未知错误'; console.error('API返回错误:', errorMsg); ElMessage.error(`请求失败: ${errorMsg}`); tableData.value = []; pagination.total = 0; } } catch (error) { // 处理认证失败 if (error.response && error.response.status === 401) { ElMessage.error('认证过期,请重新登录'); localStorage.removeItem('token'); router.push('/login'); return; } // 忽略取消请求的错误 if (!axios.isCancel(error)) { console.error('获取数据失败:', error); const errorMsg = error.response?.data?.message || '网络请求失败'; ElMessage.error(`请求失败: ${errorMsg}`); tableData.value = []; pagination.total = 0; } } finally { loading.value = false; } }; // 页面加载时获取初始数据 onMounted(() => { fetchData(); }); // 组件卸载时取消所有请求 onUnmounted(() => { if (cancelTokenSource) { cancelTokenSource.cancel('组件卸载,取消请求'); } }); </script> <style scoped> .form-row { display: flex; flex-wrap: wrap; gap: 16px; margin-bottom: 16px; } .inline-form-item { flex: 1; min-width: 250px; } .inline-form-item :deep(.el-form-item__label) { float: left; width: auto; padding-right: 12px; line-height: 32px; } .inline-form-item :deep(.el-form-item__content) { display: flex; flex: 1; } /* 移动端响应式 */ @media (max-width: 768px) { .form-row { flex-direction: column; gap: 16px; } .inline-form-item { min-width: 100%; } .inline-form-item :deep(.el-form-item__label) { float: none; width: 100%; padding-right: 0; padding-bottom: 8px; } } /* 卡片容器样式 */ .card-container { display: grid; grid-template-columns: repeat(auto-fill, minmax(500px, 1fr)); gap: 16px; } /* 单个卡片样式 */ .data-card-item { background: #fff; border-radius: 12px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); padding: 16px; display: flex; flex-direction: column; transition: all 0.3s ease; border: 1px solid #ebeef5; } .data-card-item:hover { transform: translateY(-4px); box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12); } /* 卡片头部(序号+标题) */ .card-header-section { padding-bottom: 12px; border-bottom: 1px solid #f0f2f5; margin-bottom: 12px; } .card-id { font-size: 14px; color: #909399; margin-bottom: 4px; } .card-title { font-size: 16px; font-weight: 600; color: #303133; line-height: 1.4; word-break: break-word; } /* 卡片主体(其他信息) */ .card-body-section { flex: 1; margin-bottom: 12px; } .card-row { display: flex; margin-bottom: 8px; font-size: 14px; } .card-label { color: #606266; min-width: 70px; text-align: right; } .card-value { color: #303133; flex: 1; word-break: break-word; } /* 卡片底部(状态+按钮) */ .card-footer-section { display: flex; justify-content: space-between; align-items: center; padding-top: 12px; border-top: 1px solid #f0f2f5; } .status-container { display: flex; align-items: center; gap: 8px; } .score { font-size: 14px; color: #e6a23c; font-weight: 500; } .action-btn { flex-shrink: 0; } /* 移动端响应式 */ @media (max-width: 768px) { .card-container { grid-template-columns: 1fr; } .card-row { flex-direction: column; margin-bottom: 12px; } .card-label { text-align: left; margin-bottom: 4px; font-weight: 500; } .card-footer-section { flex-direction: column; align-items: stretch; gap: 12px; } .status-container { justify-content: space-between; } } /* 添加选择器样式 */ :deep(.el-select) .el-input__inner { height: auto !important; min-height: 44px; padding: 5px 15px; } /* 标签样式 */ :deep(.el-tag) { margin: 2px 6px 2px 0; } /* 下拉菜单样式 */ :deep(.el-select-dropdown) { max-height: 300px; overflow-y: auto; } .index-card { border-radius: 12px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); } .card-header { font-size: 18px; font-weight: 600; color: #1a1a1a; } .mobile-form { :deep(.el-form-item__label) { font-weight: 500; margin-bottom: 6px; } :deep(.el-input__inner) { height: 44px; border-radius: 8px; } } .mobile-select { width: 100%; :deep(.el-input__inner) { height: 44px; } } /* 按钮区域样式 */ .button-group { display: flex; gap: 12px; margin-top: 16px; } .action-button { flex: 1; height: 46px; font-size: 16px; border-radius: 8px; } /* 移动端响应式调整 */ @media (max-width: 480px) { .button-group { flex-direction: column; gap: 10px; } .action-button { width: 100%; } } </style>
<think>我们有两个代码片段:第一个是UniApp移动端的问卷列表页面,第二个是PC端的管理后台问卷列表页面。现在需要将PC端的管理后台问卷列表页面的样式和功能(包括搜索表单、卡片布局、分页等)迁移到UniApp移动端,并保持移动端的适配。
分析:
1. PC端使用了Element Plus组件,而UniApp移动端使用原生组件或uni-ui组件。
2. 需要将PC端的样式转换为适合移动端的样式,同时保持功能一致。
迁移步骤:
1. 将PC端的搜索表单迁移到移动端,使用uni-app的组件替代Element Plus组件。
2. 将PC端的数据卡片布局迁移到移动端,使用view等基础组件重构样式。
3. 分页控件在移动端通常使用上拉加载更多,但PC端的分页是传统的分页器,考虑到移动端屏幕尺寸,我们可能需要修改为无限滚动或保留分页器但调整样式(根据需求)。
4. 功能逻辑(如数据获取、搜索、重置等)可以复用,但需要适配uni-app的API(如网络请求使用uni.request,路由使用uni.navigateTo等)。
由于移动端屏幕较小,我们考虑:
- 搜索表单:在移动端通常折叠或者使用更紧凑的布局,这里我们采用垂直布局。
- 卡片布局:每屏显示一个卡片,使用滚动查看多个卡片。
- 分页:移动端常用上拉加载更多,但考虑到PC端分页逻辑,我们可以先保留分页器,但调整为适合移动端的样式(如简单分页)或改为无限滚动。
注意:移动端和PC端在交互和布局上有差异,需要重新设计样式。
具体实现:
1. 搜索表单迁移:
- 将el-form替换为view,el-form-item替换为view,使用class来布局。
- 输入框:使用uni-app的input组件,添加clearable属性。
- 选择框:使用uni-app的picker组件(或uni-data-select组件,需安装)或者使用uni-combox(但uni-app官方组件库中可能没有完全对应的,我们可以用input和picker组合实现)。
- 按钮:使用button组件。
2. 数据卡片迁移:
- 使用view重写卡片布局,样式使用类似PC端的样式,但调整为适合移动端的尺寸。
3. 分页:
- 由于移动端屏幕限制,我们使用uni-pagination组件(需安装uni-ui)或自定义一个简单的分页(上一页/下一页)。
4. 功能逻辑:
- 数据获取:将axios请求改为uni.request。
- 路由跳转:使用uni.navigateTo。
- 本地存储:使用uni.getStorageSync获取token。
考虑到代码量较大,这里我们重点实现搜索表单和数据卡片,分页我们先使用简单分页(上一页/下一页)并显示总条数。
注意:移动端需要适配不同屏幕,使用rpx单位。
由于PC端代码中使用了Element Plus的图标,移动端我们可以使用uni-icons或自定义图标(或使用文字代替)。
由于时间关系,我们不会完全复制PC端的所有功能,而是实现核心功能。
以下为迁移后的UniApp移动端代码:</think>根据您的需求,我将帮助您将 PC 端的管理后台问卷列表页面迁移到 UniApp 移动端,并保持一致的样式和功能。以下是完整的解决方案代码:
```vue
<script setup>
import { ref, reactive, onMounted } 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 fetchSurveyData = async () => {
try {
loading.value = true;
// 准备请求参数
const params = {
pageNum: pagination.current,
pageSize: pagination.size,
dcWjTitle: formData.dcWjTitle,
dcId: formData.dcId.join(','),
dcDept: formData.dcDept,
state: formData.state
};
// 发送请求
const [error, res] = await uni.request({
url: 'http://172.26.26.43/dev-api/wjdc/wj/listTx',
method: 'GET',
data: params
});
if (error) throw error;
// 处理响应数据
const response = res.data;
if (response.code === 200) {
surveyList.value = response.rows;
total.value = response.total;
pagination.total = response.total;
} else {
throw new Error(response.msg || '请求失败');
}
} catch (error) {
uni.showToast({
title: error.message || '加载失败',
icon: 'none',
duration: 2000
});
} finally {
loading.value = false;
}
};
// 获取被测评人列表
const fetchBdrList = async () => {
try {
bdrLoading.value = true;
const [error, res] = await uni.request({
url: 'http://172.26.26.43/dev-api/wjdc/wj/getBdrList',
method: 'GET'
});
if (error) throw error;
const response = res.data;
if (response.code === 200) {
bdrOptions.value = response.data || response.rows || [];
} else {
throw new Error(response.msg || '获取被测评人失败');
}
} catch (error) {
uni.showToast({
title: error.message || '加载失败',
icon: 'none',
duration: 2000
});
} finally {
bdrLoading.value = false;
}
};
// 搜索按钮处理
const handleSearch = () => {
pagination.current = 1;
fetchSurveyData();
};
// 重置按钮处理
const handleReset = () => {
formData.dcWjTitle = '';
formData.dcId = [];
formData.dcDept = '';
formData.state = null;
handleSearch();
};
// 编辑/查看问卷
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-group">
<text class="form-label">被测评人:</text>
<picker
mode="selector"
:range="bdrOptions"
range-key="dcName"
@change="(e) => formData.dcId = [bdrOptions[e.detail.value].dcId]"
class="form-picker"
>
<view class="picker">
{{ formData.dcId.length > 0 ? bdrOptions.find(b => b.dcId === formData.dcId[0])?.dcName || '请选择' : '请选择' }}
</view>
</picker>
</view>
<view class="form-group">
<text class="form-label">人员部门:</text>
<input
v-model="formData.dcDept"
placeholder="请输入人员部门"
class="form-input"
/>
</view>
<view class="form-group">
<text class="form-label">提交状态:</text>
<picker
mode="selector"
:range="['全部', '已提交', '未提交']"
@change="(e) => formData.state = e.detail.value === 0 ? null : e.detail.value === 1 ? 1 : 0"
class="form-picker"
>
<view class="picker">
{{ formData.state === null ? '全部' : formData.state === 1 ? '已提交' : '未提交' }}
</view>
</picker>
</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;
}
/* 搜索卡片样式 */
.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 #dcdfe6;
}
.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;
}
</style>
```
### 代码解释
#### 1. 功能迁移说明
- **搜索表单**:将 PC 端的 Element UI 表单组件迁移为 UniApp 原生组件
- `el-input` → `input`
- `el-select` → `picker`
- `el-button` → `button`
- **数据卡片**:保持与 PC 端一致的卡片布局和样式
- 使用 flex 布局实现响应式设计
- 保留状态标签、得分显示和操作按钮
- **分页功能**:适配移动端的分页控件
- 使用 `picker` 选择每页条数
- 上一页/下一页按钮
- 显示当前页码和总页数
#### 2. 关键功能实现
1. **数据加载**:
- 使用 `uni.request` 替代 `axios` 发送请求
- 在 `onShow` 生命周期钩子中加载数据
- 添加加载状态指示器
2. **搜索功能**:
- 保留表单字段:问卷标题、被测评人、部门、提交状态
- 实现搜索和重置功能
- 被测评人使用 `picker` 组件选择
3. **分页处理**:
- 实现页码切换和每页条数变更
- 显示总记录数和总页数
- 禁用不可用的翻页按钮
4. **路由跳转**:
- 使用 `uni.navigateTo` 实现页面跳转
- 传递问卷 ID 参数
#### 3. 样式适配
1. **响应式设计**:
- 使用 `rpx` 单位确保在不同设备上的适配
- 卡片布局优化为单列显示
- 表单元素垂直排列
2. **视觉风格**:
- 保持与 PC 端一致的卡片阴影和圆角
- 状态标签使用相同的颜色方案
- 按钮样式保持一致性
3. **交互反馈**:
- 添加加载动画
- 按钮点击状态反馈
- 空数据提示
### 注意事项
1. **跨域问题**:
- 开发阶段需在 `manifest.json` 配置代理
- 生产环境应使用相对路径或配置域名白名单
2. **真机调试**:
- 确保开启网络权限:
```json
"app-plus": {
"distribute": {
"android": {
"permissions": [
"<uses-permission android:name=\"android.permission.INTERNET\"/>"
]
}
}
}
```
3. **性能优化**:
- 大数据量时考虑虚拟滚动
- 添加下拉刷新功能
- 实现搜索防抖处理
阅读全文
相关推荐








