活动介绍

const load=()=>{ request.get(‘/course/selectPage’,{//?pageNum=1&pageSize=10 params:{ pageNum:data.pageNum, pageSize:data.pageSize, name:data.name,//用的变量名大小写之类的要统一还有一致,千万不能随便改 …(data.user.role === ‘ADMIN’ ? { status: ‘通过’ } : {}) } }).then(res=>{ if(res.code===‘200’){ data.tableData=res.data.list data.total=res.data.total }else{ ElMessage.error(res.msg) } }) }要求不修改代码只通过添加代码的方式实现前端管理员的课程列表里只显示审核通过的课程,认真仔细的思考后给出可以实现要求的代码 给出全部代码

时间: 2025-07-12 21:32:06 浏览: 7
<think>好的,我需要帮助用户实现在前端管理员的课程列表中只显示审核通过的课程,而且不能修改现有代码,只能通过添加代码来实现。首先,我得仔细看看用户提供的代码。 用户给出的代码是一个名为load的函数,里面发送了一个GET请求到'/course/selectPage',参数包括pageNum、pageSize、name,以及根据用户角色是否为ADMIN来决定是否添加status: '通过'。现在的问题是,管理员应该只能看到审核通过的课程,但目前的代码中,当用户角色是ADMIN时,params里添加了status: '通过'。不过用户提到不能修改现有代码,只能添加代码,所以可能需要找到其他方式。 首先,我需要确认现有代码的逻辑。当用户是ADMIN时,请求参数中会包含status: '通过',这样后端应该只返回状态为通过的课程。但用户的需求是管理员看到的列表里只显示通过的课程,可能当前的实现有问题,或者后端没有正确处理这个参数? 或者,也许当前代码中的条件判断有问题。例如,当用户是ADMIN时,添加了status: '通过',但可能应该不管角色如何,管理员都应该强制加上这个条件。或者,可能现有的代码在管理员情况下并没有正确传递参数? 仔细看代码中的params部分,这里使用了展开运算符:...(data.user.role === ‘ADMIN’ ? { status: ‘通过’ } : {})。也就是说,当用户是ADMIN时,params会包含status: '通过',否则不包含。这可能意味着,管理员在请求数据时,会传递status参数为“通过”,而后端应该根据这个参数过滤数据。因此,现有的代码可能已经实现了管理员只能看到通过课程的功能,但用户可能遇到了问题,比如后端没有正确处理这个参数,或者前端有其他地方的代码覆盖了这个参数。 但用户要求不修改现有代码,只能通过添加代码来确保管理员列表只显示通过的课程。可能的解决方式是在获取数据后,对返回的数据进行过滤,即在前端处理时,只保留状态为“通过”的课程。但这样做的话,分页数据可能会有问题,因为总条数total可能包含未过滤的数据,导致分页显示不正确。因此,最好的方式还是在请求参数中加入status: '通过',确保后端返回正确的数据。 不过用户说不能修改现有代码,只能添加。那么可能需要检查现有代码中是否有其他地方影响了status参数。或者,是否有其他方式可以覆盖或添加status参数? 或者,用户可能希望无论管理员怎么操作,都强制加上status参数。但现有的代码已经根据角色添加了status,可能用户遇到的情况是,当管理员在查询时,可能name或其他参数被修改,导致status被覆盖?或者,可能用户在后端没有正确处理这个参数? 假设当前代码中,当用户是ADMIN时,确实正确传了status: '通过',但可能其他情况下没有处理,或者用户需要确保无论如何,管理员看到的都是审核通过的课程,不管其他参数如何。例如,管理员可能在搜索时,即使输入名称,也应该只返回通过的课程。此时,可能需要确保无论其他条件如何,status参数都被强制设置为“通过”。 但现有代码中的params已经包含这个条件,所以问题可能出在现有代码是否正确地传递了这个参数。比如,如果用户是ADMIN,但代码中的条件判断写错了,比如大小写问题?例如,用户的角色是'ADMIN',但代码中可能写成了'Admin'或者其他形式,导致条件不成立,没有添加status参数。 在用户提供的代码中,条件判断是data.user.role === ‘ADMIN’,这里需要注意引号是否正确,以及角色值是否确实是'ADMIN'。如果这里有错误,比如角色是'admin',那么大写的'ADMIN'会导致条件不成立,从而不会添加status参数,导致管理员看到所有状态的课程。 但用户无法修改现有代码,只能添加代码,所以可能需要通过其他方式来确保status参数被正确添加。例如,可以在发送请求前,检查用户角色,并强制添加status参数,无论原来的条件如何。 但是用户要求不能修改现有代码,只能添加新的代码。那么,是否有办法在现有代码之外,通过拦截请求或响应来修改参数或数据? 例如,使用axios的拦截器,在请求发出前,检查请求的URL是否是'/course/selectPage',然后如果是管理员用户,就添加或覆盖status参数为“通过”。这样,无论原来的代码如何设置,都能确保参数正确。 或者,在获取到数据后,过滤数据,只保留状态为“通过”的条目。但这样分页的总数可能不正确,因为后端返回的总数可能是未过滤的。 因此,更好的方法是在请求参数中添加status参数,让后端处理分页和过滤。所以,使用请求拦截器可能是一个可行的方案,不需要修改现有的load函数,而是通过添加拦截器代码来实现。 具体步骤可能是: 1. 在代码中添加axios的请求拦截器,检查当前请求是否是获取课程列表的请求(即URL是否为'/course/selectPage')。 2. 检查当前用户是否为管理员(即data.user.role === 'ADMIN')。 3. 如果是,则在请求的params中添加status: '通过',或者确保该参数存在。 但是要注意,现有的代码可能已经添加了这个参数,所以需要确保拦截器中的处理不会重复添加,或者覆盖原有参数。例如,如果原来的代码在管理员情况下已经添加了status,那么拦截器再次添加可能会导致参数重复,但HTTP请求中重复的参数处理方式可能因后端框架而异,通常后面的值会覆盖前面的。或者,如果原来的代码因为某些原因没有正确添加status参数,拦截器可以补充。 不过,用户的问题是不能修改现有代码,只能添加代码。因此,如果现有代码中的条件判断有问题,比如角色判断错误,导致status参数没有被正确添加,那么可以通过拦截器来强制添加。 假设现有代码中的条件判断正确,但用户需要确保无论如何管理员都只能看到通过的课程,可能拦截器的方式是可行的。 但需要确保拦截器只在管理员访问课程列表时生效。例如: 在axios的请求拦截器中: axios.interceptors.request.use(config => { if (config.url === '/course/selectPage' && data.user.role === 'ADMIN') { config.params = { ...config.params, status: '通过' }; } return config; }); 这样,无论原来的请求参数如何,都会强制添加status参数为“通过”。但需要注意,如果原来的代码已经添加了status参数,这里可能会覆盖它,或者合并。例如,如果原来的params中有status,那么这里的操作会将新的status合并进去,但可能需要用Object.assign或者展开运算符来确保正确合并。 不过,这种情况下,原来的代码中的条件判断可能已经添加了status,如果用户角色是ADMIN,那么拦截器再次添加可能会导致重复,但最终参数可能以最后一次设置为准。不过,这可能会导致问题,例如,如果原来的代码在某些情况下需要不同的status值,但用户的需求是管理员必须只能看到通过的,所以可能需要覆盖。 但是,这样处理的话,是否需要考虑其他情况?比如,管理员可能需要查看其他状态的课程,但用户的需求是强制只显示通过的。因此,拦截器的方式可能满足需求,而无需修改现有代码。 不过,在代码中添加拦截器需要确保data.user.role是可访问的,可能需要在拦截器中有权限访问data对象。但通常,data可能是一个组件内的变量,而拦截器是在全局或axios实例中设置的,可能无法直接访问data。因此,这种方法可能有局限性。 另一种方式是在调用load函数之前,或者在组件初始化时,对参数进行处理。例如,在Vue组件中,可能在created或mounted钩子中,或者在data中设置初始值,但需要确保在发送请求时,参数被正确添加。 或者,考虑在获取到数据后,对数据进行过滤。例如,在then(res => { ... })中,对res.data.list进行过滤,只保留status为“通过”的课程。但这样处理的话,分页的total值可能不正确,因为后端返回的总数是基于查询条件的,如果前端过滤,总数不会改变,导致分页显示错误。例如,总共有100条通过的课程,但后端可能返回了10条(因为每页10条),但total可能正确,但如果前端过滤,可能实际显示的条目数少于10,导致分页混乱。 因此,最好的方法还是在请求参数中添加status,让后端返回正确的数据。因此,需要确保status参数被正确添加到请求中。 回到用户提供的代码,现有代码在管理员情况下添加了status: '通过'。如果这没有生效,可能是条件判断有误,例如data.user.role的值不是大写的'ADMIN',或者data.user未正确获取。但用户无法修改现有代码,只能添加代码,所以需要找到另一种方式。 另一种可能性是,在现有代码中,当用户是管理员时,添加的status参数可能被其他参数覆盖,例如在params中存在其他同名的status参数。因此,可能需要确保status参数的正确性。 或者,用户可能希望管理员无论何时都强制使用status: '通过',而不管其他情况。例如,当管理员在搜索课程时,即使不选择状态,也应该默认加上“通过”的状态。这时候,现有的代码可能只在初始请求中添加了status,但在用户进行搜索或其他操作时,可能没有正确保留这个参数。 因此,可能需要通过添加代码,在每次请求时都强制添加status参数,无论现有参数如何。例如,在发送请求之前,检查用户角色,并设置status参数。 但用户不能修改现有代码,只能添加新的代码。因此,可能的解决方案是,在load函数外部,通过某种方式修改请求参数,例如使用Vue的全局混入(mixin)或者拦截器,或者在调用load之前修改data中的参数。 例如,在调用load函数之前,检查用户是否是管理员,如果是,则设置data.status = '通过',然后在params中自动包含这个参数。但现有代码的params中可能没有使用data.status,而是直接在请求参数中添加了status。 或者,在现有的params部分中,如果能够通过添加代码来影响data中的某个属性,从而间接影响请求参数。例如,在load函数外部,当用户是管理员时,设置data.status = '通过',然后在params中展开这个属性。 不过,现有的params部分已经包含了一个展开运算符:...(data.user.role === ‘ADMIN’ ? { status: ‘通过’ } : {})。这可能意味着,当用户是管理员时,status参数会被添加。如果这个条件判断正确,那么现有的代码应该已经实现了管理员只能看到通过的课程。但用户可能遇到的情况是这个条件没有正确触发,导致管理员没有带上status参数,因此需要确保这个条件成立。 如果问题在于现有的条件判断中的角色名称不正确,例如实际角色是'Admin'而不是'ADMIN',那么可能需要修改条件判断,但用户不能修改代码,只能添加代码。因此,可能需要通过其他方式覆盖这个条件。 例如,在data.user.role的值被正确设置为'ADMIN'的前提下,现有的代码应该可以正常工作。如果用户的数据中role的值正确,那么问题可能出在后端没有正确处理status参数,或者前端其他部分覆盖了status。 但用户的需求是不修改现有代码,只能添加代码。因此,可能的解决方案是,在获取到数据后,对数据进行过滤,只保留状态为“通过”的课程。尽管这可能导致分页信息不准确,但可能用户能接受。 例如,在then(res => { ... })部分,添加过滤: data.tableData = res.data.list.filter(item => item.status === '通过'); 但用户提供的现有代码中,已经将data.tableData设置为res.data.list。因此,可以在不修改现有代码的情况下,在then回调中添加过滤代码。但需要确保在添加代码时,不修改原有的then链,而是追加一个处理。 但用户提供的代码是: .then(res=>{ if(res.code===‘200’){ data.tableData=res.data.list data.total=res.data.total }else{ ElMessage.error(res.msg) } }) 因此,可以在else语句之后,添加一个过滤的步骤: .then(res=>{ if(res.code===‘200’){ data.tableData=res.data.list data.total=res.data.total // 添加过滤代码 if (data.user.role === 'ADMIN') { data.tableData = data.tableData.filter(item => item.status === '通过') // 同时需要更新total为过滤后的数量,否则分页会出错 data.total = data.tableData.length } }else{ ElMessage.error(res.msg) } }) 但这样处理的话,data.total会被设置为过滤后的列表长度,这会导致分页组件显示的总数不正确,因为后端返回的total是未过滤的总数。例如,如果一页10条,但过滤后剩下5条,那么分页会显示总数为5,而实际后端可能有更多的数据。因此,这种方法并不正确。 因此,正确的做法还是在请求参数中添加status,让后端返回正确的结果,这样分页信息也是正确的。 回到用户的问题,可能需要确保在管理员的情况下,status参数被正确添加。如果现有的代码已经正确添加了status参数,那么问题可能出在后端,但用户无法修改后端代码。因此,可能需要在前端确保status参数被正确添加。 如果现有的代码中,条件判断有误,例如data.user.role的值不是'ADMIN',导致没有添加status参数,那么可以通过添加代码来修正这个问题。例如,在代码的其他地方,确保data.user.role的值正确,或者在请求发送前,强制设置status参数。 例如,在调用load函数之前,或者在组件的created钩子中,添加: if (data.user.role === 'admin') { // 假设实际角色是小写 data.user.role = 'ADMIN'; // 转换为大写 } 但这属于修改现有数据,可能不符合用户“不修改代码”的要求,但属于添加代码。 或者,在params展开之前,添加代码来强制设置status: const originalLoad = load; load = () => { // 在调用原load函数前,修改data中的参数 if (data.user.role === 'ADMIN') { data.status = '通过'; } originalLoad(); }; 但这种方法可能需要修改load函数的定义,或者通过猴子补丁的方式覆盖load函数,这可能比较复杂。 另一种方法是使用JavaScript的Proxy来拦截data对象的访问,确保在params展开时,status被正确添加。但这种方法可能过于复杂。 综合考虑,最可行的方式是在then回调中对数据进行过滤,尽管这会导致分页信息不准确。或者,在请求拦截器中强制添加status参数。 假设用户使用的是axios,并且可以在全局添加拦截器,那么可以这样做: // 添加请求拦截器 axios.interceptors.request.use(config => { // 检查请求的URL是否是获取课程列表 if (config.url === '/course/selectPage') { // 获取当前用户角色,假设存在全局的data对象或可以通过某种方式获取 // 注意:这里可能需要根据实际情况获取用户角色,比如从Vuex store或localStorage const userRole = getCurrentUserRole(); // 假设存在这样的函数 if (userRole === 'ADMIN') { // 添加或覆盖status参数 config.params = { ...config.params, status: '通过' }; } } return config; }); 但问题是如何在拦截器中获取用户角色。如果用户角色存储在data对象中,而data是某个Vue组件的data,那么拦截器可能无法直接访问它。因此,可能需要通过其他方式,例如将用户角色存储在Vuex store或本地存储中,然后在拦截器中获取。 例如,假设用户角色存储在Vuex store中,可以这样做: import store from '@/store'; axios.interceptors.request.use(config => { if (config.url === '/course/selectPage') { const userRole = store.state.user.role; if (userRole === 'ADMIN') { config.params = { ...config.params, status: '通过' }; } } return config; }); 这样,每次请求/course/selectPage时,如果是管理员,都会自动添加status参数为“通过”,从而确保后端返回的数据是已通过的课程,分页信息也正确。 这种方法不需要修改现有的load函数,只需添加拦截器代码,符合用户的要求。 因此,最终的解决方案是添加一个axios的请求拦截器,在请求/course/selectPage时,如果是管理员角色,则添加status参数为“通过”。</think>可以通过添加**请求拦截器**实现管理员强制过滤`status=通过`,无需修改原代码逻辑。以下是具体实现: ```javascript // 添加axios请求拦截器(通常在axios实例化文件,如request.js) request.interceptors.request.use(config => { // 仅处理目标接口 if (config.url === '/course/selectPage') { // 获取用户信息(根据实际项目结构调整获取方式) const user = localStorage.getItem('user') ? JSON.parse(localStorage.getItem('user')) : {}; // 管理员强制注入status参数 if (user.role === 'ADMIN') { config.params = { ...config.params, // 保留原有参数 status: '通过' // 强制覆盖status }; } } return config; }); ``` **关键点解释:** 1. **不侵入原代码** 通过拦截器全局控制请求参数,完全无需修改`load()`函数内部逻辑 2. **精准条件过滤** 仅当同时满足: - 请求路径为`/course/selectPage` - 当前用户角色为`ADMIN` 时才会强制添加`status=通过` 3. **参数安全合并** `...config.params`保留原始参数(如分页、搜索词),再合并`status`确保不会被覆盖 4. **用户信息获取方式** 示例中使用`localStorage`,实际项目可根据状态管理(如Vuex/Pinia)或Cookie调整获取方式 **注意事项:** - 拦截器代码应添加在**axios实例创建后**,且确保在所有课程列表请求前执行 - 确保`localStorage`或状态管理中能正确获取用户角色信息 - 如果项目存在多环境,需确认接口路径`/course/selectPage`是否需动态配置
阅读全文

相关推荐

<script setup lang="ts"> import request from '@/utils/request'; import { ElMessage, ElMessageBox } from 'element-plus'; import type { FormInstance } from 'element-plus' import { reactive, ref,onMounted } from 'vue'; interface CategoryEntity { id?: number title: string parentId: number children?: CategoryEntity[] } const tableData = ref<CategoryEntity[]>([]) const formVisible = ref(false) const currentId = ref<number | null>(null) const formRef = ref<FormInstance>() const data=reactive({ user: JSON.parse(localStorage.getItem('xm-pro-user')||'{}'),//跟自己的文件名一致 title: '', parentId: '', pageNum:1, pageSize:5, total:0, tableData:[], form:{} as CategoryForm, formVisible:false, rules:{ title:[ {required:true,message:'请填写分类标题',trigger:'blur'} ], } }) const load=()=>{ request.get('/category/selectPage',{ params:{ pageNum:data.pageNum, pageSize:data.pageSize, title:data.title } }).then(res=>{ if(res.data.code==='200'){ data.tableData=res.data?.list data.total=res.data?.total }else{ ElMessage.error(res.data.msg) } }) } load() const handleAdd=()=>{ data.formVisible=true data.form={} } const save=()=>{//在一个保存方法里面做2个操作,一个是新增一个是编辑 data.form.id?update():add() } const add=()=>{ formRef.value.validate((valid)=>{ if(valid){//验证通过的话再新增 request.post('/category/add',data.form).then(res=>{ if(res.data.code==='200'){ data.formVisible=false ElMessage.success('新增成功') load()//新增后重新加载最新的数据 }else{ ElMessage.error(res.data.msg) } }) } }) } const update=()=>{ formRef.value.validate((valid)=>{ if(valid){ request.put('/category/update',data.form).then(res=>{ if(res.data.code==='200'){ data.formVisible=false ElMessage.success('操作成功') load()//更新后重新加载最新的数据 }else{ ElMessage.error(res.data.msg) } }) } }) } const handleEdit=(row)=>{ data.form=JSON.parse(JSON.stringify(row)) as CategoryForm//深拷贝一个新的对象用于编辑,就不会影响行对象 data.formVisible=true } const del=(id)=>{ //二次确认 ElMessageBox.confirm('删除后无法恢复,确认删除?','删除确认',{type:'warning'}).then(()=>{ request.delete('/category/deleteById/'+id).then(res=>{ if(res.data.code==='200'){ ElMessage.success('操作成功') load()//删除后重新加载最新的数据 }else{ ElMessage.error(res.data.msg) } }) }).catch() } const reset=()=>{ data.title=null load() } </script> 找不到名称“CategoryForm”。ts-plugin(2304) type CategoryForm = /*unresolved*/ any

<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> 我想要 数据显示板块里的每条数据以单独的卡片形式布局,

<template> <el-row style="padding: 15px 10px;background-color: rgba(244, 244, 245,.3)"> <el-input v-model="searchKey" style="width:50%" placeholder="输入关键词搜索" class="input-with-select" @keyup.enter="searchData" > <template #prepend> <el-select v-model="select" placeholder="请先选择档案" style="width: 200px" @change="selectChange"> <el-option v-for="(item,index) in options" :label="item.label" :value="item.value"/> </el-select> </template> <template #append> <el-button :icon="Search" @click="searchData"/> </template> </el-input> <el-button plain type="primary" :disabled="select ===''" @click="openInputDialog = true"> <el-text>{{ select }}</el-text>  数据导入 </el-button> </el-row> <el-row style="padding: 5px 15px;height: 89%;"> <el-table v-if="select !== ''" :data="tableData" v-loading="loading" :header-cell-style="{ background: '#FFFFFF' }" style="width: 100%;height: 100%" border stripe> <el-table-column fixed type="index" width="60" label="序号"></el-table-column> <el-table-column v-for="(col,index) in computedColumns" :fixed="['projectCode', 'projectName'].includes(col.prop)" :prop="col.prop" :label="col.label" :width="col.width"> <template #default="scope" v-if="col.dataType !== ''"> <el-text v-if="col.dataType === 'fee'">{{ method.NumberThousandths(scope.row[col.prop]) }}</el-text> <el-text v-else-if="col.dataType === 'date'">{{ method.DateFormat(scope.row[col.prop]) }}</el-text> <el-text v-else></el-text> </template> </el-table-column> </el-table> </el-row> <el-row> <el-pagination v-if="select !== ''" style="margin: 0;" v-model:current-page="pageInfo.pageNum" @current-change="currentPageChange" v-model:page-size="pageInfo.pageSize" @size-change="pageSizeChange" pager-count="7" :page-sizes="[10, 20, 50, 200]" :layout=pageLayout :total="pageInfo.total"/> </el-row> <el-dialog v-model="openInputDialog" width="90vw" style="height: 90vh;margin: 5vh 5vw"> <el-row> <el-col :span="8" style="text-align: left"> <el-upload ref="upload" v-loading="uploading" accept=".xlsx" :limit="1" :on-exceed="handleExceed" action="" :http-request="excelResolve" :before-upload="beforeAvatarUpload" :on-success="handleAvatarSuccess" > <el-button :disabled="uploading"> {{ uploading ? '解析中...' : '📁打开文件' }} </el-button> </el-upload> </el-col> <el-col :span="8"> <el-text size="large" type="info">{{ select }}( <el-text size="small" type="danger" v-show="inputData.length !== 0">已解析:{{ inputData.length }}</el-text> ) </el-text> </el-col> <el-col :span="8" style="text-align: right"> <el-button type="primary" @click="uploadFile"> <el-icon> <UploadFilled/> </el-icon> 上传数据 </el-button> </el-col> </el-row> <el-row> <el-table :data="inputData" :virtual-scroll="inputData.length > 100" v-loading="loading" :header-cell-style="{ background: '#FFFFFF' }" style="width: 100%;height: 73.5vh" border stripe> <el-table-column fixed type="index" width="60" label="序号"></el-table-column> <el-table-column v-for="(col,index) in computedColumns" :fixed="['projectCode', 'projectName'].includes(col.prop)" :prop="col.prop" :label="col.label" :width="col.width"> <template #default="scope" v-if="col.dataType !== ''"> <el-text v-if="col.dataType === 'fee'">{{ method.NumberThousandths(scope.row[col.prop]) }}</el-text> <el-text v-else-if="col.dataType === 'date'">{{ method.DateFormat(scope.row[col.prop]) }}</el-text> <el-text v-else></el-text> </template> </el-table-column> </el-table> </el-row> <el-row> <el-text v-if="errorMessage !==''" size="default" type="danger">{{ errorMessage }}</el-text> </el-row> </el-dialog> </template> <script setup> import {computed, reactive, ref, watch} from "vue"; import {Search} from "@element-plus/icons-vue"; import apiWorkHours from "@/api/workHours/apiWorkHours"; import qs from "qs"; import {ElMessage, genFileId} from "element-plus"; import method from "../../../util/js/method"; // region 属性 let tableColumns = [ { prop: "departmentCode", label: "部门编码", width: "300", belongTo: ["部门人工标准", "部门人工工时"], dataType: '' }, { prop: "departmentName", label: "部门名称", width: "", belongTo: ["部门人工标准", "部门人工工时"], dataType: '' }, { prop: "monthPeriod", label: "月份", width: "400", belongTo: ["部门人工标准", "部门人工工时"], dataType: '' }, { prop: "laborHours", label: "工时", width: "400", belongTo: ["部门人工工时"], dataType: '' }, { prop: "laborStandard", label: "人工标准", width: "150", belongTo: ["部门人工标准"], dataType: '' }, { prop: "projectCode", label: "项目编码", width: "200", belongTo: ["项目人工分摊", "项目档案底表"], dataType: '' }, { prop: "projectName", label: "项目名称", width: "400", belongTo: ["项目人工分摊", "项目档案底表"], dataType: '' }, { prop: "laborCost", label: "人工费", width: "150", belongTo: ["项目人工分摊"], dataType: '' }, { prop: "approvalStatus", label: "立项状态", width: "150", belongTo: ["项目档案底表"], dataType: '' }, { prop: "projectStatus", label: "项目状态", width: "150", belongTo: ["项目人工分摊", "项目档案底表"], dataType: '' }, { prop: "projectNature", label: "项目性质", width: "150", belongTo: ["项目人工分摊", "项目档案底表"], dataType: '' }, { prop: "projectCategory", label: "项目类别", width: "150", belongTo: ["项目人工分摊", "项目档案底表"], dataType: '' }, { prop: "productCategory", label: "产品类别", width: "150", belongTo: ["项目人工分摊", "项目档案底表"], dataType: '' }, { prop: "responsibleDepartment", label: "主管部门", width: "200", belongTo: ["项目人工分摊", "项目档案底表"], dataType: '' }, { prop: "projectManager", label: "项目主管", width: "150", belongTo: ["项目人工分摊", "项目档案底表"], dataType: '' }, { prop: "financeContact", label: "财务对接人", width: "150", belongTo: ["项目人工分摊", "项目档案底表"], dataType: '' }, { prop: "projectLeader", label: "项目负责人", width: "150", belongTo: ["项目档案底表"], dataType: '' }, { prop: "projectStartDate", label: "项目开始时间", width: "150", belongTo: ["项目人工分摊", "项目档案底表"], dataType: 'date' }, { prop: "expectedEndDate", label: "项目结束时间", width: "150", belongTo: ["项目人工分摊", "项目档案底表"], dataType: 'date' }, { prop: "currency", label: "单位", width: "80", belongTo: ["项目档案底表"], dataType: '' } ] //table列配置 let errorMessage = ref('') const upload = ref() const loading = ref(false) // 数据查询loading const uploading = ref(false) // 数据解析loading const openInputDialog = ref(false) // 数据导入窗口 let tableData = ref([]) // 查询数据 let inputData = ref([]) // excel解析的数据 const pageInfo = reactive({ pageNum: 1, pageSize: 10, total: 0 }) // 分页器 // endregion // region API const getWhDepartmentLaborPageApi = () => {// 部门档案page try { const param = { pageNum: pageInfo.pageNum, pageSize: pageInfo.pageSize } apiWorkHours.getWhDepartmentLaborPage(qs.stringify(param)).then(res => { if (res.code === 1000) { tableData.value = res.data.list; pageInfo.total = res.data.total } else { ElMessage.error('部门档案信息查询失败!') } loading.value = false }) } catch (e) { ElMessage.error('程序错误:', e) } } const getWhDepartmentLaborPageBySearchApi = () => {// 部门档案page try { const param = { searchKey: searchKey.value, pageNum: searchKey.value === '' ? 1 : null, pageSize: searchKey.value === '' ? 10 : null, } apiWorkHours.getWhDepartmentLaborPageBySearch(qs.stringify(param)).then(res => { if (res.code === 1000) { tableData.value = res.data.list; pageInfo.total = res.data.total ElMessage.success('共查询到:' + pageInfo.total + " 条记录") } else { ElMessage.error('部门档案信息查询失败!') } loading.value = false }) } catch (e) { ElMessage.error('程序错误:', e) } } const laborHoursUpdateBatchAPI = () => { const param = inputData.value apiWorkHours.laborHoursUpdateBatch(param).then(res => { console.log(res) if (res.code === 1000) { ElMessage.error("成功更新:" + res.data + "条") } else { ElMessage.error("更新失败") } }) } const laborStandardUpdateBatch = () => { const param = inputData.value apiWorkHours.laborStandardUpdateBatch(param).then(res => { console.log(res) if (res.code === 1000) { ElMessage.error("成功更新:" + res.data + "条") } else { ElMessage.error("更新失败") } }) } const laborStandardInsertBatch = () => { const param = inputData.value apiWorkHours.laborStandardInsertBatch(param).then(res => { console.log(res) if (res.code === 1000) { ElMessage.success(res.data) } else { ElMessage.error("插入失败") } }) } const laborHoursInsertBatch = () => { const param = inputData.value apiWorkHours.laborHoursInsertBatch(param).then(res => { console.log(res) if (res.code === 1000) { ElMessage.success(res.data) } else { ElMessage.error("插入失败") } }) } const projectInfoInsertBatch = () => { const param = inputData.value apiWorkHours.projectInfoInsertBatch(param).then(res => { console.log(res) if (res.code === 1000) { ElMessage.success(res.data) } else { ElMessage.error("插入失败") } }) } const laborShareInsertBatch = () => { const param = inputData.value apiWorkHours.laborShareInsertBatch(param).then(res => { console.log(res) if (res.code === 1000) { ElMessage.success(res.data) } else { ElMessage.error("插入失败") } }) } const getWhLaborSharePageApi = () => {// 人工分摊page try { const param = { pageNum: pageInfo.pageNum, pageSize: pageInfo.pageSize } apiWorkHours.getWhLaborSharePage(qs.stringify(param)).then(res => { if (res.code === 1000) { tableData.value = res.data.list; pageInfo.total = res.data.total } else { ElMessage.error('人工分摊信息查询失败!') } loading.value = false }) } catch (e) { ElMessage.error('程序错误:', e) } } const getWhLaborSharePageBySearchApi = () => {// 人工分摊page try { const param = { searchKey: searchKey.value, pageNum: searchKey.value === '' ? 1 : null, pageSize: searchKey.value === '' ? 10 : null, } apiWorkHours.getWhLaborSharePageBySearch(qs.stringify(param)).then(res => { if (res.code === 1000) { tableData.value = res.data.list; pageInfo.total = res.data.total ElMessage.success('共查询到:' + pageInfo.total + " 条记录") } else { ElMessage.error('人工分摊信息查询失败!') } loading.value = false }) } catch (e) { ElMessage.error('程序错误:', e) } } const getProjectInfoPageApi = () => {// 项目档案page try { const param = { pageNum: pageInfo.pageNum, pageSize: pageInfo.pageSize } apiWorkHours.getProjectInfoPage(qs.stringify(param)).then(res => { if (res.code === 1000) { tableData.value = res.data.list; pageInfo.total = res.data.total } else { ElMessage.error('项目档案信息查询失败!') } loading.value = false }) } catch (e) { ElMessage.error('程序错误:', e) } } const getProjectInfoPageBySearchApi = () => {// 项目档案page try { const param = { searchKey: searchKey.value, pageNum: searchKey.value === '' ? 1 : null, pageSize: searchKey.value === '' ? 10 : null, } apiWorkHours.getProjectInfoPageBySearch(qs.stringify(param)).then(res => { if (res.code === 1000) { tableData.value = res.data.list pageInfo.total = res.data.total ElMessage.success('共查询到:' + pageInfo.total + " 条记录") } else { ElMessage.error('项目档案信息查询失败!') } loading.value = false }) } catch (e) { ElMessage.error('程序错误:', e) } } const excelResolveWhDepartmentLaborAPI = (file) => { const param = { excelFile: file } apiWorkHours.excelResolveWhDepartmentLabor(param).then(res => { if (res.code === 1000) { inputData.value = res.data uploading.value = false ElMessage.success("数据解析完成") } else { ElMessage.error("数据解析失败") errorMessage.value = res.data } }) } const excelResolveWhLaborShareAPI = (file) => { const param = { excelFile: file } apiWorkHours.excelResolveWhLaborShare(param).then(res => { if (res.code === 1000) { inputData.value = res.data uploading.value = false ElMessage.success("数据解析完成") } else { ElMessage.error("数据解析失败") errorMessage.value = res.data } }) } const excelResolveWhProjectInfoAPI = (file) => { const param = { excelFile: file } apiWorkHours.excelResolveWhProjectInfo(param).then(res => { if (res.code === 1000) { inputData.value = res.data uploading.value = false ElMessage.success("数据解析完成") } else { ElMessage.error("数据解析失败") errorMessage.value = res.data } }) } // endregion // region 搜索栏 const select = ref('') const options = [ { value: '部门人工标准', label: '部门人工标准', }, { value: '部门人工工时', label: '部门人工工时', disabled: false, }, { value: '项目人工分摊', label: '项目人工分摊', }, { value: '项目档案底表', label: '项目档案底表', }, ] const searchKey = ref('') const searchData = () => { pageLayout = searchKey.value === '' ? "total, sizes, prev, pager, next, jumper" : "total" switch (select.value) { case "部门人工标准": getWhDepartmentLaborPageBySearchApi() break; case "部门人工工时": getWhDepartmentLaborPageBySearchApi() break; case "项目人工分摊": getWhLaborSharePageBySearchApi() break; case "项目档案底表": getProjectInfoPageBySearchApi() break; default: loading.value = false ElMessage.warning('请先选择档案!') } } // endregion // region 数据展示 // 增加计算属性 const computedColumns = computed(() => { return tableColumns.filter(column => column.belongTo.includes(select.value)) }) // 选项切换→更换数据 const selectChange = (value) => { loading.value = true tableData.value = [] switch (value) { case "部门人工标准": getWhDepartmentLaborPageApi() break; case "部门人工工时": getWhDepartmentLaborPageApi() break; case "项目人工分摊": getWhLaborSharePageApi() break; case "项目档案底表": getProjectInfoPageApi() break; default: loading.value = false ElMessage.warning('请先选择档案!') } } // endregion // region 分页 let pageLayout = ref("total, sizes, prev, pager, next, jumper") const currentPageChange = () => { selectChange(select.value) } const pageSizeChange = () => { selectChange(select.value) } // endregion // region 数据导入 // 添加watch监听对话框状态,重置上传状态 watch(openInputDialog, (newVal) => { if (newVal) { upload.value?.clearFiles(); inputData.value = []; uploading.value = false; }else { setTimeout(() => selectChange(select.value), 200) } }) const beforeAvatarUpload = (rawFile) => {// 上传前钩子 if (rawFile.size / 1024 / 1024 > 5) { ElMessage.error('文件较大 已超过5MB!') return false } return true } const handleAvatarSuccess = (response, uploadFile) => {// 文件上传成功 ElMessage.success("文件上传成功:", response) // console.table("uploadFile:", uploadFile) } const handleExceed = (files) => { }; const excelResolve = (file) => {// Excel解析,根据选择的上传数据类型选择调用的解析接口 uploading.value = true switch (select.value) { case "部门人工标准": file.name = "部门人工标准-导入模板" excelResolveWhDepartmentLaborAPI(file) break; case "部门人工工时": file.name = "部门人工工时-导入模板" excelResolveWhDepartmentLaborAPI(file) break; case "项目人工分摊": excelResolveWhLaborShareAPI(file) break; case "项目档案底表": excelResolveWhProjectInfoAPI(file) break; default: uploading.value = false ElMessage.warning('请先选择档案!') } } const uploadFile = () => { switch (select.value) { case "部门人工标准": laborStandardInsertBatch() break; case "部门人工工时": laborHoursInsertBatch() break; case "项目人工分摊": laborShareInsertBatch() break; case "项目档案底表": projectInfoInsertBatch() break; default: uploading.value = false ElMessage.warning('请先选择档案!') } } // endregion </script> <style scoped lang="scss"> .input-with-select .el-input-group__prepend { background-color: var(--el-fill-color-blank); } </style> 为什么点击upload打开文件选择时会卡死,页面无响应

<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> 报错:18:15:29.275 [plugin:vite:vue] [vue/compiler-sfc] Unexpected reserved word 'await'. (257:25) 18:15:29.275 D:/民意测评/vue3-uni-mycp/pages/detail/detail.vue 18:15:29.275 255| 18:15:29.275 256| try { 18:15:29.275 257| const response = await uni.request({ 18:15:29.275 | ^ 18:15:29.275 258| url: BDR_API_URL, 18:15:29.275 259| method: 'GET', 18:15:29.275 at pages/detail/detail.vue:257:25

最新推荐

recommend-type

微软解决方案面向服务的架构.doc

微软解决方案面向服务的架构.doc
recommend-type

Huawei S6780-H-V600R024SPH120

Huawei S6780-H_V600R024SPH120,里面包含补丁说明书和补丁安装指导书,该补丁支持哪些型号,支持哪些版本可以安装当前补丁,请参考补丁说明书和补丁安装指导书。
recommend-type

网络营销案例分析概述.pptx

网络营销案例分析概述.pptx
recommend-type

2025广西省道路路网矢量数据图层Shp数据最新版下载

2025广西省道路路网矢量数据图层,shp格式,包含多级道路分类属性,路名等属性,包含全省几十万条道路,坐标系为WGS1984坐标系统
recommend-type

最新中国移动通信年度报告样本 (1)(1).doc

最新中国移动通信年度报告样本 (1)(1).doc
recommend-type

VC图像编程全面资料及程序汇总

【标题】:"精通VC图像编程资料全览" 【知识点】: VC即Visual C++,是微软公司推出的一个集成开发环境(IDE),专门用于C++语言的开发。VC图像编程涉及到如何在VC++开发环境中处理和操作图像。在VC图像编程中,开发者通常会使用到Windows API中的GDI(图形设备接口)或GDI+来进行图形绘制,以及DirectX中的Direct2D或DirectDraw进行更高级的图形处理。 1. GDI(图形设备接口): - GDI是Windows操作系统提供的一套应用程序接口,它允许应用程序通过设备无关的方式绘制图形。 - 在VC图像编程中,主要使用CDC类(设备上下文类)来调用GDI函数进行绘制,比如绘制线条、填充颜色、显示文本等。 - CDC类提供了很多函数,比如`MoveTo`、`LineTo`、`Rectangle`、`Ellipse`、`Polygon`等,用于绘制基本的图形。 - 对于图像处理,可以使用`StretchBlt`、`BitBlt`、`TransparentBlt`等函数进行图像的位块传输。 2. GDI+: - GDI+是GDI的后继技术,提供了更丰富的图形处理功能。 - GDI+通过使用`Graphics`类来提供图像的绘制、文本的渲染、图像的处理和颜色管理等功能。 - GDI+引入了对矢量图形、渐变色、复杂的文本格式和坐标空间等更高级的图形处理功能。 - `Image`类是GDI+中用于图像操作的基础类,通过它可以进行图像的加载、保存、旋转、缩放等操作。 3. DirectX: - DirectX是微软推出的一系列API集合,用于在Windows平台上进行高性能多媒体编程。 - DirectX中的Direct2D是用于硬件加速的二维图形API,专门用于UI元素和简单的图形渲染。 - DirectDraw主要用于硬件加速的位图操作,比如全屏游戏开发中的画面渲染。 4. 位图操作: - 在VC图像编程中,位图操作是一个重要的部分。需要了解如何加载、保存和处理位图(BMP)文件。 - 可以使用位图文件格式的解析,来访问位图的像素数据,进行像素级别的图像处理和修改。 5. 高级图像处理技术: - 包括图像滤镜、图像转换、图像压缩和解压缩技术。 - 需要掌握一些图像处理算法,比如卷积、FFT(快速傅里叶变换)、DCT(离散余弦变换)等。 - 了解图像的色彩空间转换,比如RGB到YUV的转换,这在视频处理中非常重要。 6. 图像库的使用: - 除了直接使用API进行图像处理之外,还可以使用开源的图像处理库,如OpenCV。 - OpenCV是一个跨平台的计算机视觉和机器学习软件库,它提供了很多高级的图像处理功能。 【压缩包子文件的文件名称列表】: VC++ 文件名称列表显示为"VC++",这表明文件包中可能包含了Visual C++的项目、源代码文件、动态链接库(DLLs)、编译器设置、链接器设置等。如果要使用这些文件进行VC图像编程,需要确保Visual C++开发环境已经安装并且配置好,同时需要安装好所有依赖的库和工具。 要开始VC图像编程,开发者需要具备C++编程基础,熟悉Windows编程概念,并且对图形学有一定的了解。掌握VC图像编程技巧,对于进行桌面应用程序开发、图像处理软件开发以及游戏开发等都是至关重要的。
recommend-type

Pokemmo响应速度翻倍:多线程处理的高级技巧

# 摘要 多线程处理是提高软件并发性能和响应速度的关键技术,本文从基础概念与优势出发,深入探讨了多线程的实现机制,包括线程模型、调度算法、编程关键技术、线程安全性最佳实践、死锁预防与处理等方面。通
recommend-type

人名列表滚动抽奖

<think>嗯,用户想实现一个滚动抽奖功能,需要前端动画效果和随机选择逻辑。首先,我得考虑前端怎么实现滚动效果。可能用CSS动画或者JavaScript的requestAnimationFrame?比如,使用CSS的@keyframes来控制位移,或者用JS动态更新样式。然后,随机选择算法,可能需要确保公平性,比如用Fisher-Yates洗牌算法,或者用Math.random()来生成随机索引。然后,用户可能需要平滑的滚动动画,比如先快速滚动,然后逐渐减速,最后停在选中的人名上。这可能需要设置定时器,逐步改变位置,或者使用CSS过渡效果。另外,还要考虑性能,避免页面卡顿,可能需要使用硬件加
recommend-type

一站式JSF开发环境:即解压即用JAR包

标题:“jsf开发完整JAR包”所指的知识点: 1. JSF全称JavaServer Faces,是Java EE(现EE4J)规范之一,用于简化Java Web应用中基于组件的用户界面构建。JSF提供了一种模型-视图-控制器(MVC)架构的实现,使得开发者可以将业务逻辑与页面表示分离。 2. “开发完整包”意味着这个JAR包包含了JSF开发所需的所有类库和资源文件。通常来说,一个完整的JSF包会包含核心的JSF库,以及一些可选的扩展库,例如PrimeFaces、RichFaces等,这些扩展库提供了额外的用户界面组件。 3. 在一个项目中使用JSF,开发者无需单独添加每个必要的JAR文件到项目的构建路径中。因为打包成一个完整的JAR包后,所有这些依赖都被整合在一起,极大地方便了开发者的部署工作。 4. “解压之后就可以直接导入工程中使用”表明这个JAR包是一个可执行的归档文件,可能是一个EAR包或者一个可直接部署的Java应用包。解压后,开发者只需将其内容导入到他们的IDE(如Eclipse或IntelliJ IDEA)中,或者将其放置在Web应用服务器的正确目录下,就可以立即进行开发。 描述中所指的知识点: 1. “解压之后就可以直接导入工程中使用”说明这个JAR包是预先配置好的,它可能包含了所有必要的配置文件,例如web.xml、faces-config.xml等,这些文件是JSF项目运行所必需的。 2. 直接使用意味着减少了开发者配置环境和处理依赖的时间,有助于提高开发效率。 标签“jsf jar包”所指的知识点: 1. 标签指明了JAR包的内容是专门针对JSF框架的。因此,这个JAR包包含了JSF规范所定义的API以及可能包含的具体实现,比如Mojarra或MyFaces。 2. “jar包”是一种Java平台的归档文件格式,用于聚合多个文件到一个文件中。在JSF开发中,JAR文件经常被用来打包和分发库或应用程序。 文件名称列表“jsf”所指的知识点: 1. “jsf”文件名可能意味着这是JSF开发的核心库,它应该包含了所有核心的JavaServer Faces类文件以及资源文件。 2. 如果是使用特定版本的JSF,例如“jsf-2.2.jar”,则表明文件内包含了对应版本的JSF实现。这种情况下,开发者必须确认他们所使用的Web服务器或应用程序服务器支持该版本的JSF。 3. 文件名称也可能是“jsf-components.jar”、“jsf-impl.jar”等,表明这个JAR包是JSF的一个子模块或特定功能组件。例如,“jsf-components.jar”可能包含了一系列用于在JSF应用中使用的自定义组件。 4. 对于开发者而言,了解文件名称中所蕴含的信息非常重要,因为这将决定他们需要下载哪些JAR包来满足特定项目的需求。 综合以上信息,开发者在使用JSF进行Java Web应用开发时,会通过一个预先配置好的JAR包来快速地搭建和启动项目。这样做不仅简化了项目初始化的过程,也使得开发者能够更加聚焦于业务逻辑的实现和界面设计,而不必深究底层框架配置的细节。
recommend-type

Pokemmo内存优化揭秘:专家教你如何降低50%资源消耗

# 摘要 本文综述了Pokemmo游戏的内存优化方法,从内存管理基础出发,探讨内存使用效率的影响因素,并介绍了性能监控与分析工具。在内存优化实践技巧章节中,详细讨论了代码层面的优化、数据结构和算法选择对内存效率的影响,并通过案例分析展示了实际的优化过程。针对Pokemmo游戏特点,分析了内存消耗特性并提出了特定优化技术。最后,本文展望了未来内存管理技术的发展方向,以及游戏开发中面临的新挑战,为Pokemmo及类似游戏提供了优化建议。 # 关键字 内存优化;内存管理;性能监控;数据结构;算法效率;游戏开发 参考资源链接:[Pokemmo必备资源包:四种ROM与汉化补丁](https://we