weixin_58412143 2025-08-18 16:23 采纳率: 83.7%
浏览 6
已结题

vue#+ts二次打开弹窗子组件报错且没有请求接口

父组件点击房间明细表弹窗出子组件表格,传值时间范围和时间和页面类型(组件复用)和房间id,第二次点开弹窗报错并且没有走子组件的接口请求,弹窗内容还是第一次请求的数据

img

img

  <el-form ref="queryFormRef" :model="queryParams" :inline="true">
        <el-form-item label="时间范围" prop="">
          <el-date-picker v-model="queryParams.dateRangeValue" type="daterange" range-separator="To"
            start-placeholder="开始时间" end-placeholder="结束时间" @change="handleDateRangeChange" />
          <div class="date-shortcut-buttons" style="margin-left: 10px;">
            <el-button size="small" :class="{ 'is-active': selectedDateShortcut === 'today' }"
              @click="handleDateShortcut('today')">
              今日
            </el-button>
  <el-table-column fixed="right" label="操作" width="220">
              <template #default="scope">
                <el-button type="primary" size="small" link @click="handleOpenDialog(scope.row)">
                  房间明细表</el-button>
              </template>
            </el-table-column>
<el-dialog v-model="dialog.visible" :title="dialog.title" width="70%" @close="handleCloseDialog">
      <RoomDetails :roomId="RoomDetailsId" :range="selectedDateShortcut" :startTime="queryParams.startTime"
        :endTime="queryParams.endTime" :type="'building'" />
const selectedDateShortcut = ref<string | null>('today');//时间范围
const queryParams = reactive<DeviceJobLogPageQuery>({
  pageNum: 1,
  pageSize: 10,
  startTime: formatDate(new Date()), // 默认设置为今天的日期
  endTime: formatDate(new Date()),   // 默认设置为今天的日期
  dateRangeValue: [new Date(), new Date()]
});
// 当日期选择器值变化时更新queryParams
const handleDateRangeChange = (dates) => {
  selectedDateShortcut.value = null
  isSelect.value = false
  if (dates && dates.length === 2) {
    queryParams.startTime = formatDate(dates[0]) // 开始日期,可能是 Date 对象
    queryParams.endTime = formatDate(dates[1])   // 结束日期
  } else {
    queryParams.startTime = ''
    queryParams.endTime = ''
  }
}
const handleDateShortcut = (type: string) => {
  // 设置选中的快捷按钮状态
  selectedDateShortcut.value = type

  const now = new Date()
  let start: Date, end: Date

  switch (type) {
    case 'today':
      // 今日
      start = new Date(now.getFullYear(), now.getMonth(), now.getDate())
      end = new Date(now.getFullYear(), now.getMonth(), now.getDate())
      break
  // 设置日期选择器的值,确保结束日期包含当天
  queryParams.dateRangeValue = [
    start,
    end
  ]
  queryParams.startTime = formatDate(start) // 开始日期,可能是 Date 对象
  queryParams.endTime = formatDate(end) // 结束日期
  handleQuery()
}
/** 打开楼宇汇总弹窗 */
function handleOpenDialog(row?: any) {
  dialog.title = "房间明细表";
  RoomDetailsId.value = row.roomId; // 设置要传递的 id
  console.log("房间明细表", row.roomId, typeof RoomDetailsId.value)
  // 关键:重置 queryParams(避免旧参数残留)
  queryParams.value = {
    startTime: queryParams.startTime,
    endTime: queryParams.endTime,
    roomId: row.roomId // 显式传递新 roomId
  };
  dialog.visible = true;
}

子组件

   <el-table ref="dataTableRef" v-loading="loading" :data="pageData" highlight-current-row border default-expand-all>
        <el-table-column label="所属楼宇" prop="buildingName" align="center" width="200" />
const props = defineProps({
  roomId: {
    type: String,
    required: false
  },
  range: {
    type: String,
    required: false
  },
  startTime: {
    type: String,
    required: false
  },
  endTime: {
    type: String,
    required: false
  },
  deptId: {
    type: String,
    required: false
  },
  type: {
    type: String,
    required: false
  }

})
// 添加一个变量来存储请求取消令牌
const abortController = ref<AbortController | null>(null);
function handleQuery() {
  // 取消之前的请求(如果有)
  if (abortController.value) {
    abortController.value.abort();
  }

  // 创建新的取消令牌
  abortController.value = new AbortController();
  const signal = abortController.value.signal;

  loading.value = true;
  console.log(queryParams)
  DepartmentAPI.getRoomDetail({
    ...queryParams,
    _t: Date.now() // 每次请求添加随机参数,避免缓存
  }, { signal }) // 传递信号到请求
    .then((data) => {
      pageData.value = [data]; // 替换而不是追加数据
      console.log("data", data, pageData.value)
    })
    .catch((error) => {
      if (error.name !== 'AbortError') {
        console.error('请求出错:', error);
        ElMessage.error('获取数据失败');
      }
    })
    .finally(() => {
      loading.value = false;
    });
}
onMounted(() => {
  console.log(" props.roomId", props.roomId, "deptIds", props.deptId)
  handleQuery();
});
// 在组件卸载时取消请求
onUnmounted(() => {
  if (abortController.value) {
    abortController.value.abort();
  }
});
  • 写回答

5条回答 默认 最新

  • 宝码香车 优质创作者: JavaScript技术领域 2025-08-18 16:29
    关注

    让【宝码香车】来帮您解答,本回答参考DeepSeek编写,并整理提供。

    如果答案让您满意,请采纳、点赞、关注,非常感谢!
    .  
    每日一句正能量,让它带给您好心情:学会和自己和解,是一生的修行。
    .  

    Vue+TS二次打开弹窗子组件问题分析

    根据您描述的问题和代码,我分析出以下原因和解决方案:

    问题原因

    1. 子组件未响应props变化:子组件只在onMounted时发起请求,当父组件传递的props变化时,子组件没有重新请求数据。
    1. 弹窗复用问题:Vue的v-model控制的弹窗在关闭时不会销毁组件实例,导致第二次打开时不会重新触发onMounted
    1. CORS错误:控制台显示存在跨域问题,可能影响请求发送。

    解决方案

    1. 监听props变化

    在子组件中添加对props变化的监听:

    // 子组件中
    watch(() => [props.roomId, props.startTime, props.endTime, props.range], () => {
      handleQuery();
    }, { immediate: true });
    

    2. 修改父组件的弹窗处理逻辑

    // 父组件中
    function handleOpenDialog(row?: any) {
      dialog.title = "房间明细表";
      RoomDetailsId.value = row.roomId;
      dialog.visible = true;
      
      // 强制重置子组件状态(如果需要)
      // 可以通过ref调用子组件方法
    }
    
    
    function handleCloseDialog() {
      // 可以在这里重置相关状态
      dialog.visible = false;
    }
    

    3. 解决CORS问题

    确保后端API配置了正确的CORS头:

    Access-Control-Allow-Origin: http://localhost:3000
    Access-Control-Allow-Headers: content-type
    

    4. 完整子组件优化代码

    const props = defineProps({
      roomId: { type: String, required: false },
      range: { type: String, required: false },
      startTime: { type: String, required: false },
      endTime: { type: String, required: false },
      deptId: { type: String, required: false },
      type: { type: String, required: false }
    });
    
    
    const queryParams = reactive({
      roomId: props.roomId,
      startTime: props.startTime,
      endTime: props.endTime,
      range: props.range,
      deptId: props.deptId
    });
    
    
    // 监听props变化
    watch(() => ({
      roomId: props.roomId,
      startTime: props.startTime,
      endTime: props.endTime,
      range: props.range,
      deptId: props.deptId
    }), (newVal) => {
      Object.assign(queryParams, newVal);
      handleQuery();
    }, { immediate: true });
    
    
    // 请求方法保持不变
    function handleQuery() {
      if (abortController.value) {
        abortController.value.abort();
      }
      
      abortController.value = new AbortController();
      
      loading.value = true;
      DepartmentAPI.getRoomDetail({
        ...queryParams,
        _t: Date.now()
      }, { signal: abortController.value.signal })
        .then((data) => {
          pageData.value = [data];
        })
        .catch((error) => {
          if (error.name !== 'AbortError') {
            console.error('请求出错:', error);
            ElMessage.error('获取数据失败');
          }
        })
        .finally(() => {
          loading.value = false;
        });
    }
    

    其他建议

    1. 添加错误边界处理:在父组件中捕获子组件可能抛出的错误。
    1. 添加加载状态:在父组件中也添加加载状态,避免用户多次点击。
    1. 考虑使用key强制重新渲染:如果问题仍然存在,可以尝试:
    <RoomDetails 
      :key="`${RoomDetailsId}-${Date.now()}`"
      :roomId="RoomDetailsId" 
      :range="selectedDateShortcut" 
      :startTime="queryParams.startTime"
      :endTime="queryParams.endTime" 
      :type="'building'" 
    />
    

    这样每次打开弹窗都会强制创建新的子组件实例。

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(4条)

报告相同问题?

问题事件

  • 系统已结题 8月26日
  • 已采纳回答 8月18日
  • 创建了问题 8月18日