news 2026/2/10 6:30:40

uni-app—— uni-app 页面角色权限校验失败问题的排查与解决

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
uni-app—— uni-app 页面角色权限校验失败问题的排查与解决

问题描述

在审批创建页面,用户选择审批模板后提交,后端返回"角色权限校验失败"的错误。但用户实际上是有该模板的使用权限的。

问题原因

问题出在templateOptions计算属性的逻辑缺陷,导致模板数据不完整传递给后端,从而触发权限校验失败。

原有代码问题

consttemplateOptions=computed(()=>{// 问题1: 这个判断逻辑有问题// 当 filteredTemplates.value 为空数组时,Object.keys({}).length 也为 0// 但空数组和空对象是不同的情况if(filteredTemplates.value&&Object.keys(filteredTemplates.value).length===0){return;// 返回 undefined,而不是空数组}constcurrentTemplates=filteredTemplates.value||[];if(!Array.isArray(currentTemplates)||currentTemplates.length===0){return[];}returncurrentTemplates.map((template)=>({value:template?.id??'',label:template?.name??'',}));});

核心问题分析:

  1. 类型判断混乱:使用Object.keys()来判断数组是否为空是不正确的做法
  2. 返回值不一致:有时返回undefined,有时返回空数组[],有时返回正常数组
  3. 缺少前置条件检查:没有检查依赖数据是否已加载完成

问题影响链

依赖数据未加载完成 ↓ filteredTemplates 计算结果不准确 ↓ templateOptions 返回 undefined 或错误数据 ↓ 选中的模板 ID 丢失或不完整 ↓ 提交时后端收到的模板信息不完整 ↓ 后端根据不完整信息校验权限失败

解决方案

修复后的代码

consttemplateOptions=computed(()=>{// 修复1: 添加前置条件检查// 确保依赖数据已加载完成再计算模板选项if(categories.length===0){returnfalse;// 明确返回 false 表示数据未就绪}// 修复2: 规范化数据处理constcurrentTemplates=filteredTemplates.value||[];// 修复3: 统一返回类型为数组if(!Array.isArray(currentTemplates)||currentTemplates.length===0){return[];}returncurrentTemplates.map((template)=>({value:template?.id??'',label:template?.name??'',}));});

关键改进点

  1. 添加依赖数据检查:在计算模板选项前,先检查依赖数据是否已加载
  2. 移除错误的类型判断:删除了Object.keys()的误用
  3. 统一返回值类型
    • false- 表示依赖数据未就绪
    • []- 表示数据已就绪但没有可用模板
    • Array- 正常的模板选项列表

完整 Demo 示例

<template> <div class="approval-create"> <div class="form-item"> <label>审批模板</label> <select v-model="selectedTemplate" :disabled="!isTemplateReady"> <option value="">请选择模板</option> <option v-for="option in safeTemplateOptions" :key="option.value" :value="option.value" > {{ option.label }} </option> </select> <span v-if="!isTemplateReady" class="loading-tip">加载中...</span> </div> <button @click="handleSubmit" :disabled="!canSubmit"> 提交审批 </button> </div> </template> <script setup> import { ref, computed, onMounted } from 'vue'; // 响应式数据 const selectedTemplate = ref(''); const categories = ref([]); const allTemplates = ref([]); const selectedCategory = ref(''); // 根据分类筛选模板 const filteredTemplates = computed(() => { if (!selectedCategory.value) { return allTemplates.value; } return allTemplates.value.filter( template => template.category === selectedCategory.value ); }); // 核心修复: templateOptions 计算属性 const templateOptions = computed(() => { // 关键检查: 确保依赖数据已加载 if (categories.value.length === 0) { return false; // 数据未就绪 } const currentTemplates = filteredTemplates.value || []; if (!Array.isArray(currentTemplates) || currentTemplates.length === 0) { return []; } return currentTemplates.map((template) => ({ value: template?.id ?? '', label: template?.name ?? '', })); }); // 模板数据是否就绪 const isTemplateReady = computed(() => { return templateOptions.value !== false; }); // 安全的模板选项(用于渲染) const safeTemplateOptions = computed(() => { if (templateOptions.value === false) { return []; } return templateOptions.value; }); // 是否可以提交 const canSubmit = computed(() => { return isTemplateReady.value && selectedTemplate.value; }); // 提交处理 const handleSubmit = async () => { if (!canSubmit.value) { console.warn('数据未就绪,无法提交'); return; } // 获取完整的模板信息 const template = allTemplates.value.find( t => t.id === selectedTemplate.value ); if (!template) { console.error('未找到选中的模板'); return; } // 构建提交数据,确保包含完整的模板信息 const submitData = { templateId: template.id, templateName: template.name, templateCategory: template.category, // 其他必要字段... }; try { await submitApproval(submitData); } catch (error) { console.error('提交失败:', error); } }; // 加载数据 onMounted(async () => { // 并行加载依赖数据 await Promise.all([ loadCategories(), loadTemplates() ]); }); // 模拟 API 调用 const loadCategories = async () => { // 模拟异步加载 const response = await fetch('/api/categories'); categories.value = await response.json(); }; const loadTemplates = async () => { const response = await fetch('/api/templates'); allTemplates.value = await response.json(); }; const submitApproval = async (data) => { const response = await fetch('/api/approval/create', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); if (!response.ok) { throw new Error('提交失败'); } return response.json(); }; </script> <style scoped> .approval-create { padding: 20px; } .form-item { margin-bottom: 16px; } .form-item label { display: block; margin-bottom: 8px; font-weight: bold; } .form-item select { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; } .form-item select:disabled { background-color: #f5f5f5; cursor: not-allowed; } .loading-tip { color: #999; font-size: 12px; margin-left: 8px; } button { padding: 10px 20px; background-color: #1890ff; color: white; border: none; border-radius: 4px; cursor: pointer; } button:disabled { background-color: #d9d9d9; cursor: not-allowed; } </style>

经验总结

1. 计算属性返回值要保持一致性

// 错误示范:返回值类型不一致constoptions=computed(()=>{if(condition1)return;// undefinedif(condition2)returnnull;// nullif(condition3)return[];// arrayreturndata.map(...);// array});// 正确示范:明确的返回值约定constoptions=computed(()=>{if(!isDataReady.value)returnfalse;// 明确:数据未就绪if(isEmpty.value)return[];// 明确:空数组returndata.map(...);// 正常数据});

2. 不要混用数组和对象的判断方法

// 错误:用 Object.keys() 判断数组if(Object.keys(arr).length===0){...}// 正确:用数组专用方法if(!Array.isArray(arr)||arr.length===0){...}

3. 计算属性需要考虑依赖数据的加载顺序

// 问题场景constresult=computed(()=>{// 如果 dataA 依赖 dataB,但 dataB 还没加载完// 这里的计算结果就会不准确returndataA.filter(item=>dataB.includes(item.id));});// 解决方案constresult=computed(()=>{// 先检查依赖数据是否就绪if(dataB.length===0){returnfalse;// 或其他标识未就绪的值}returndataA.filter(item=>dataB.includes(item.id));});

4. 提交数据前确保数据完整性

consthandleSubmit=async()=>{// 检查必要数据是否完整if(!isDataReady.value){showToast('数据加载中,请稍候');return;}// 验证选中的数据是否有效constselectedItem=findSelectedItem();if(!selectedItem){showToast('请选择有效的选项');return;}// 构建完整的提交数据constsubmitData=buildSubmitData(selectedItem);awaitsubmit(submitData);};

调试技巧

当遇到"权限校验失败"但用户确实有权限时,可以从以下角度排查:

  1. 检查请求参数:对比正常请求和异常请求的参数差异
  2. 检查计算属性:打印计算属性在不同时机的返回值
  3. 检查数据加载顺序:确认依赖数据是否都已加载完成
  4. 检查数据类型:确认传递的数据类型是否符合后端预期
// 调试示例watch(templateOptions,(newVal,oldVal)=>{console.log('templateOptions changed:',{old:oldVal,new:newVal,categoriesReady:categories.value.length>0,filteredTemplatesCount:filteredTemplates.value?.length});},{immediate:true});
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/8 9:42:47

1966-2025年低空经济企业数据库

低空经济通常指围绕“低空空域”的飞行活动及其配套产业形成的综合经济形态。它以各类航空器的低空飞行应用为牵引&#xff0c;向上游制造、向下游运营服务与治理体系延展&#xff0c;形成“制造—运营—服务—基础设施—监管保障”的产业链 团队参考李牧南&#xff08;2024&a…

作者头像 李华
网站建设 2026/2/10 8:06:47

基于Java的彩灯加工智慧管理系统的设计与实现全方位解析:附毕设论文+源代码

1. 为什么这个毕设项目值得你 pick ? 毕设不用从零敲&#xff01;基于Java的彩灯加工智慧管理系统的设计与实现。该系统主要功能模块包括供应商管理、加工单位管理、产品管理、业务登记管理、交货单管理和供应商付款管理&#xff0c;满足普通员工和部门领导的数据录入查阅执行…

作者头像 李华
网站建设 2026/2/10 8:38:14

基于Java的影视剪辑智慧管理系统的设计与实现全方位解析:附毕设论文+源代码

1. 为什么这个毕设项目值得你 pick ? 基于Java的影视剪辑智慧管理系统的设计与实现&#xff0c;提供了一种针对传统选题的优势、创新性和实用性的摆脱方案。该系统主要功能模块包括会员管理、视频管理、素材及任务日志等多个子系统&#xff0c;覆盖了从用户数据录入到项目导出…

作者头像 李华
网站建设 2026/2/9 20:46:27

强烈安利 10个降AIGC工具:专科生降AI率全攻略

在如今的学术写作中&#xff0c;AI 生成内容&#xff08;AIGC&#xff09;已经成为一个不可忽视的现象。对于专科生来说&#xff0c;论文写作不仅需要满足学术规范&#xff0c;还要避免被检测出 AI 生成痕迹&#xff0c;以免影响成绩或遭遇查重风险。这时候&#xff0c;AI 降重…

作者头像 李华