当前位置: 首页 > news >正文

uni-app——uni-app 小程序 操作后功能未生效问题的排查与解决

操作后功能未生效问题的排查与解决

问题背景

在开发审批流程功能时,遇到一个常见问题:审批状态的数据,点击撤回后功能没有生效。具体表现为:

  1. 点击"撤回到草稿箱"按钮后,操作没有生效
  2. 点击"彻底删除"后,数据没有被删除,反而出现在草稿箱中

问题复现

操作步骤:

  1. 提交一条审批申请
  2. 在待审批状态下,点击"撤回"按钮
  3. 期望数据回到草稿箱,但实际没有生效
  4. 点击"彻底删除",数据没有删除,反而出现在草稿箱

问题分析

通过代码分析,发现问题主要出在以下几个方面:

1. 操作后页面未刷新

从编辑/操作页面返回列表页时,列表页的onLoad生命周期不会重新触发,导致数据未刷新。

2. API 调用后缺少状态同步

撤回和删除操作调用 API 成功后,前端状态没有正确更新。

3. 接口参数传递错误

删除和撤回接口需要的参数格式不正确,导致后端处理失败但前端显示成功。

解决方案

方案一:添加 onShow 生命周期刷新数据

问题:小程序/App 中页面返回时不触发onLoad,只触发onShow

<script setup> import { ref } from 'vue'; import { onLoad, onShow } from '@dcloudio/uni-app'; const listData = ref([]); const isFirstLoad = ref(true); const currentStatus = ref('pending'); // pending/draft/deleted // 加载列表数据 const loadListData = async () => { try { const response = await getApprovalList({ status: currentStatus.value, pageNum: 1, pageSize: 20, }); listData.value = response.list || []; } catch (error) { console.error('加载失败:', error); } }; // 页面首次加载 onLoad((options) => { if (options.status) { currentStatus.value = options.status; } loadListData(); }); // 页面显示时刷新(关键修复点) onShow(() => { // 跳过首次加载,避免重复请求 if (isFirstLoad.value) { isFirstLoad.value = false; return; } // 从其他页面返回时重新加载数据 loadListData(); }); </script>

方案二:撤回功能的正确实现

<script setup> import { ref } from 'vue'; // 撤回到草稿箱 const handleWithdraw = async (item) => { try { // 显示确认弹窗 const confirmed = await showConfirmDialog({ title: '确认撤回', content: '撤回后将移至草稿箱,确定要撤回吗?', }); if (!confirmed) return; // 显示加载状态 showLoading('撤回中...'); // 调用撤回接口 - 注意参数格式 const result = await withdrawApproval({ instanceId: item.id, reason: '用户主动撤回', }); hideLoading(); if (result.success) { showToast('撤回成功'); // 方式1:从列表中移除该项(推荐) const index = listData.value.findIndex((i) => i.id === item.id); if (index > -1) { listData.value.splice(index, 1); } // 方式2:或者重新加载列表 // await loadListData(); } else { showToast(result.message || '撤回失败'); } } catch (error) { hideLoading(); showToast('操作失败,请重试'); console.error('撤回失败:', error); } }; </script>

方案三:删除功能的正确实现

<script setup> // 彻底删除 const handleDelete = async (item) => { try { const confirmed = await showConfirmDialog({ title: '确认删除', content: '删除后将无法恢复,确定要删除吗?', }); if (!confirmed) return; showLoading('删除中...'); // 关键:确保传递正确的参数 const result = await deleteApproval({ instanceId: item.id, deleteType: 'permanent', // 彻底删除标识 }); hideLoading(); if (result.success) { showToast('删除成功'); // 从列表中移除 listData.value = listData.value.filter((i) => i.id !== item.id); // 如果列表为空,可以显示空状态或返回上一页 if (listData.value.length === 0) { showEmptyState(); } } else { // 关键:正确处理失败情况 showToast(result.message || '删除失败'); } } catch (error) { hideLoading(); // 不要静默失败,要给用户反馈 showToast('删除失败,请重试'); console.error('删除失败:', error); } }; </script>

方案四:统一的操作结果处理

// utils/approval.tsinterfaceOperationResult{success:boolean;message?:string;data?:any;}// 统一的操作处理函数exportasyncfunctionhandleApprovalOperation(operationType:'withdraw'|'delete'|'submit',params:{instanceId:string|number;[key:string]:any},callbacks:{onSuccess?:(data:any)=>void;onError?:(error:any)=>void;onFinally?:()=>void;}={}):Promise<OperationResult>{constoperationMap={withdraw:{api:withdrawApproval,loadingText:'撤回中...',successText:'撤回成功',},delete:{api:deleteApproval,loadingText:'删除中...',successText:'删除成功',},submit:{api:submitApproval,loadingText:'提交中...',successText:'提交成功',},};constoperation=operationMap[operationType];if(!operation){return{success:false,message:'未知操作类型'};}try{showLoading(operation.loadingText);constresult=awaitoperation.api(params);hideLoading();if(result.success||result.code===200){showToast(operation.successText);callbacks.onSuccess?.(result.data);return{success:true,data:result.data};}else{consterrorMsg=result.message||'操作失败';showToast(errorMsg);callbacks.onError?.(newError(errorMsg));return{success:false,message:errorMsg};}}catch(error:any){hideLoading();consterrorMsg=error.message||'网络异常,请重试';showToast(errorMsg);callbacks.onError?.(error);return{success:false,message:errorMsg};}finally{callbacks.onFinally?.();}}// 使用示例consthandleWithdraw=async(item)=>{constresult=awaithandleApprovalOperation('withdraw',{instanceId:item.id},{onSuccess:()=>{// 从列表中移除listData.value=listData.value.filter((i)=>i.id!==item.id);},});};

方案五:API 接口层的错误处理

// api/approval.tsimport{http}from'@/utils/request';// 撤回审批exportfunctionwithdrawApproval(data:{instanceId:number|string;reason?:string}){returnhttp({url:'/approval/instance/withdraw',method:'POST',data:{instanceId:Number(data.instanceId),// 确保类型正确reason:data.reason||'',},});}// 删除审批exportfunctiondeleteApproval(data:{instanceId:number|string;deleteType?:string}){returnhttp({url:'/approval/instance/delete',method:'POST',data:{instanceId:Number(data.instanceId),// 确保类型正确deleteType:data.deleteType||'permanent',},});}// 请求拦截器中添加响应处理http.interceptors.response.use((response)=>{const{code,data,message}=response.data;// 统一处理业务错误码if(code!==200&&code!==0){// 不要静默处理,返回错误信息returnPromise.reject({success:false,code,message:message||'请求失败',data:null,});}return{success:true,code,message,data,};},(error)=>{// 网络错误处理console.error('请求错误:',error);returnPromise.reject({success:false,code:-1,message:error.message||'网络异常',data:null,});});

方案六:完整的列表页面示例

<template> <view class="approval-list"> <!-- 状态标签页 --> <view class="tabs"> <view v-for="tab in tabs" :key="tab.value" :class="['tab-item', { active: currentStatus === tab.value }]" @click="switchTab(tab.value)" > {{ tab.label }} <text v-if="tab.count" class="count">{{ tab.count }}</text> </view> </view> <!-- 列表内容 --> <scroll-view scroll-y class="list-container" @scrolltolower="loadMore" refresher-enabled :refresher-triggered="isRefreshing" @refresherrefresh="onRefresh" > <view v-if="listData.length > 0"> <view v-for="item in listData" :key="item.id" class="approval-item"> <view class="item-header"> <text class="title">{{ item.title }}</text> <text :class="['status', item.status]">{{ getStatusText(item.status) }}</text> </view> <view class="item-content"> <text>申请时间:{{ item.createTime }}</text> <text>申请金额:¥{{ item.amount }}</text> </view> <!-- 操作按钮 --> <view class="item-actions"> <!-- 待审批状态可撤回 --> <button v-if="item.status === 'pending'" class="btn btn-withdraw" @click="handleWithdraw(item)" > 撤回 </button> <!-- 草稿状态可编辑和删除 --> <template v-if="item.status === 'draft'"> <button class="btn btn-edit" @click="handleEdit(item)">编辑</button> <button class="btn btn-delete" @click="handleDelete(item)">删除</button> </template> </view> </view> </view> <!-- 空状态 --> <view v-else class="empty-state"> <image src="/static/empty.png" class="empty-image" /> <text>暂无数据</text> </view> </scroll-view> </view> </template> <script setup> import { ref, computed } from 'vue'; import { onLoad, onShow } from '@dcloudio/uni-app'; import { getApprovalList, withdrawApproval, deleteApproval } from '@/api/approval'; const tabs = [ { label: '待审批', value: 'pending', count: 0 }, { label: '草稿箱', value: 'draft', count: 0 }, { label: '已完成', value: 'completed', count: 0 }, ]; const currentStatus = ref('pending'); const listData = ref([]); const isRefreshing = ref(false); const isFirstLoad = ref(true); const pageNum = ref(1); const hasMore = ref(true); // 加载列表 const loadListData = async (isLoadMore = false) => { try { if (!isLoadMore) { pageNum.value = 1; hasMore.value = true; } const response = await getApprovalList({ status: currentStatus.value, pageNum: pageNum.value, pageSize: 20, }); const newList = response.list || []; if (isLoadMore) { listData.value = [...listData.value, ...newList]; } else { listData.value = newList; } hasMore.value = newList.length >= 20; } catch (error) { console.error('加载失败:', error); uni.showToast({ title: '加载失败', icon: 'none' }); } }; // 切换标签 const switchTab = (status) => { if (currentStatus.value === status) return; currentStatus.value = status; loadListData(); }; // 下拉刷新 const onRefresh = async () => { isRefreshing.value = true; await loadListData(); isRefreshing.value = false; }; // 加载更多 const loadMore = () => { if (!hasMore.value) return; pageNum.value++; loadListData(true); }; // 撤回操作 const handleWithdraw = async (item) => { try { const [error] = await uni.showModal({ title: '确认撤回', content: '撤回后将移至草稿箱,确定吗?', }); if (error || !error.confirm) return; uni.showLoading({ title: '撤回中...' }); const result = await withdrawApproval({ instanceId: item.id }); uni.hideLoading(); if (result.success) { uni.showToast({ title: '撤回成功', icon: 'success' }); // 从当前列表移除 listData.value = listData.value.filter((i) => i.id !== item.id); } else { uni.showToast({ title: result.message || '撤回失败', icon: 'none' }); } } catch (error) { uni.hideLoading(); uni.showToast({ title: '操作失败', icon: 'none' }); } }; // 删除操作 const handleDelete = async (item) => { try { const [error, res] = await uni.showModal({ title: '确认删除', content: '删除后无法恢复,确定吗?', }); if (error || !res.confirm) return; uni.showLoading({ title: '删除中...' }); const result = await deleteApproval({ instanceId: item.id, deleteType: 'permanent', }); uni.hideLoading(); if (result.success) { uni.showToast({ title: '删除成功', icon: 'success' }); listData.value = listData.value.filter((i) => i.id !== item.id); } else { uni.showToast({ title: result.message || '删除失败', icon: 'none' }); } } catch (error) { uni.hideLoading(); uni.showToast({ title: '操作失败', icon: 'none' }); } }; // 编辑 const handleEdit = (item) => { uni.navigateTo({ url: `/pages/approval/edit?id=${item.id}`, }); }; // 获取状态文本 const getStatusText = (status) => { const map = { pending: '待审批', draft: '草稿', completed: '已完成', rejected: '已驳回', }; return map[status] || status; }; // 生命周期 onLoad(() => { loadListData(); }); // 关键:页面显示时刷新数据 onShow(() => { if (isFirstLoad.value) { isFirstLoad.value = false; return; } // 从编辑页返回时刷新 loadListData(); }); </script> <style lang="scss" scoped> .approval-list { min-height: 100vh; background: #f5f5f5; } .tabs { display: flex; background: #fff; padding: 20rpx; gap: 20rpx; .tab-item { flex: 1; text-align: center; padding: 16rpx; border-radius: 8rpx; background: #f0f0f0; font-size: 28rpx; &.active { background: #1890ff; color: #fff; } .count { margin-left: 8rpx; font-size: 24rpx; } } } .approval-item { background: #fff; margin: 20rpx; padding: 24rpx; border-radius: 12rpx; .item-header { display: flex; justify-content: space-between; margin-bottom: 16rpx; .title { font-size: 32rpx; font-weight: 500; } .status { font-size: 24rpx; padding: 4rpx 12rpx; border-radius: 4rpx; &.pending { background: #fff7e6; color: #fa8c16; } &.draft { background: #f0f0f0; color: #666; } } } .item-actions { display: flex; gap: 16rpx; margin-top: 20rpx; padding-top: 20rpx; border-top: 1rpx solid #eee; .btn { flex: 1; font-size: 28rpx; padding: 16rpx; border-radius: 8rpx; border: none; &.btn-withdraw { background: #fff7e6; color: #fa8c16; } &.btn-edit { background: #e6f7ff; color: #1890ff; } &.btn-delete { background: #fff1f0; color: #ff4d4f; } } } } .empty-state { display: flex; flex-direction: column; align-items: center; padding: 100rpx; color: #999; } </style>

最佳实践总结

1. 操作后必须更新前端状态

  • API 调用成功后,及时更新本地列表数据
  • 不要依赖页面刷新,主动移除或修改列表项

2. 正确处理页面生命周期

  • 小程序/App 使用onShow而非仅onLoad
  • 避免首次加载重复请求

3. 完善的错误处理

  • API 失败时给用户明确反馈
  • 不要静默失败

4. 参数类型检查

  • 确保instanceId等参数类型正确
  • 后端可能要求数字类型,前端传字符串会导致失败

5. 用户体验优化

  • 操作前显示确认弹窗
  • 操作中显示 loading
  • 操作后显示结果提示

关键词:审批撤回、删除功能、状态同步、页面刷新、onShow生命周期

适用场景:审批流程、工单系统、任务管理等需要状态操作的业务场景

http://www.jsqmd.com/news/340265/

相关文章:

  • YOLO26涨点改进 | 独家创新,特殊场景检测篇 | TGRS 2025 | 引入FAENet特征自适应增强网络,专注于恶劣天气条件下的目标检测(低光场景、雾天场景、雨雪场景、复杂环境等)即插即用
  • YOLO26涨点改进 | 独家创新,卷积改进篇 | TGRS 2025 | 引入RFEM感受野增强模块,增强特征的全局结构和上下文表达能力,含多种创新改进,助力恶劣天气条件目标检测任务有效涨点
  • uni-app—— uni-app小程序 移动端评分滑块组件的选择:自绘实现 vs 原生组件
  • YOLO26涨点改进 |全网独家、特征融合创新篇 | TGRS 2025 | 引入ERM边缘感知细化融合模块,解决红外小目标检测中常见的边界模糊、目标不完整、背景干扰问题,助力YOLO26有效涨点
  • YOLO26涨点改进 | 独家创新-注意力改进篇 | AAAI 2025 | YOLO26引入 SSA 稀疏自注意力创新模块,专注于非语义特征的提取,增强了模型对细节特征的捕捉能力,含多种创新改进
  • Win32 使用 MoveFileEx 延迟到重启后删除文件
  • 常用算法(下)---拷贝、替换、算术生成、集合算法
  • 前端技术经理:角色、职责与面试指南
  • Python 中的 click 框架
  • 2026年初至今,国内顶尖气力输送供应商综合评估报告 - 2026年企业推荐榜
  • 2026年秦皇岛开放式厨房定制指南:口碑厂家深度解析 - 2026年企业推荐榜
  • 2026年青海舟谱数据服务商综合评估与精选报告 - 2026年企业推荐榜
  • 2026年当下乌鲁木齐舟谱公司哪家好 - 2026年企业推荐榜
  • 2月3日面试题整理 字节跳动后端开发相关
  • 2026年Q1长沙玻璃胶实力厂家综合评选与选型指南 - 2026年企业推荐榜
  • Java 线程池线程数怎么定?从 IO / CPU / 混合型任务谈起
  • 长沙结构胶工厂盘点:五家本地优质服务商推荐 - 2026年企业推荐榜
  • 2026年贵州防撞板生产商综合评估与选购指南 - 2026年企业推荐榜
  • 2026年靠谱墙板厂家推荐榜单 - 2026年企业推荐榜
  • 2026年第一季度优质集成墙板定做厂家综合评估报告 - 2026年企业推荐榜
  • 五大热门远程控制软件全方位测评,2026年远控王者花落谁家?
  • 2026年陕西基本农田调整技术服务公司综合盘点 - 2026年企业推荐榜
  • 2026年国土空间规划调整技术服务商综合实力排行与选型指南 - 2026年企业推荐榜
  • Windows C盘清理—— Android Studio .gradle 文件夹迁移
  • 专业级模型 GLM-OCR
  • 谷歌发布 Project Genie:基于文本生成可互动 3D 虚拟世界
  • 2026年北海地区信誉优良的合成高温润滑脂厂家综合评估 - 2026年企业推荐榜
  • 福州合成高温润滑油厂商深度评测与选型指南 - 2026年企业推荐榜
  • 2026年斜管填料制造厂综合评估与优选指南 - 2026年企业推荐榜
  • 2026年成都石膏板厂家综合评估与优选指南 - 2026年企业推荐榜