2024/6/30周报

摘要

本周阅读了一篇关于TCN和LSTM进行光伏功率预测的文章,本文提出了一种利用LSTM-TCN预测光伏功率的新模型。它由长短期记忆和时间卷积网络模型之间的组合组成。LSTM用于从输入数据中提取时态特征,然后与TCN结合,在特征和输出之间建立连接。与LSTM、TCN相比,平均绝对误差分别下降了、秋季8.47%,14.26%、冬季6.91%,15.18%、春季10.22%,实验结果表明,所提出的模型优于LSTM和TCN模型。与LSTM、TCN相比,平均绝对误差分别下降了、秋季8.47%,14.26%、冬季6.91%,15.18%、春季10.22%,14.26%和夏季14.26%,14.23%。 在多步预测方面,LSTM-TCN在2步到7步的光伏功率预测中均优于所有比较的模型。

ABSTRACT

This week, a paper on photovoltaic power prediction using Temporal Convolutional Network (TCN) and Long Short-Term Memory (LSTM) is readed. This paper puts forward a new model to predict photovoltaic power by using LSTM-TCN. It consists of a combination of long-term and short-term memory and time convolution network model. LSTM is used to extract temporal features from input data, and then combined with TCN to establish a connection between features and output. Compared with LSTM and TCN, the average absolute error decreased by 8.47% in autumn, 14.26% in winter, 6.91% in winter, 15.18% in spring and 10.22% in spring. The experimental results show that the proposed model is superior to LSTM and TCN models. Compared with LSTM and TCN, the average absolute error decreased by 8.47% in autumn, 14.26% in winter, 6.91% in winter, 15.18% in spring, 10.22% in spring, 14.26% in summer and 14.23% in summer. In the aspect of multi-step prediction, LSTM-TCN is superior to all the compared models in 2-step to 7-step photovoltaic power prediction.

文献阅读

题目

Accurate one step and multistep forecasting of very short-term PV power using LSTM-TCN model - ScienceDirect

问题

随着人口的增长和经济的发展,能源需求逐年增加。利用光伏发电能源作为清洁可再生能源可以满足这种能源需求的增加。光伏发电技术以其高性能、高效率、低成本的特点在世界范围内得到了广泛的应用。将光伏系统并入电网可以带来经济效益和环境效益。然而,由于太阳能的间歇性,这类能源的高渗透率也给电网系统带来了很多挑战。光伏发电预测是控制太阳能发电间歇性的最经济、最可行的解决方案之一。然而,由于光伏发电数据具有非线性和不稳定的特点,以及光伏发电所依赖的气象条件,准确预测是一项复杂的任务。光伏发电功率预测分为四个层次:长期预测为一个月至一年,中期预测为一周至一个月,短期预测为一周或一周以内,极短期或超短期预测为一分钟至几分钟。长期预测为光伏发电、输配电提供长期规划和决策,保证电力系统的可靠运行。中期预测为电力系统调度提供中期决策支持。短期预测为电力系统运行提供支持,提高了电力系统的可靠性。极短期预测用于电力平滑、实时电力调度和存储控制。

本文贡献

光伏发电预测主要有三种方法:物理方法、统计方法和混合方法。物理模型可以定义为具有描述系统输入和输出的数学方程的确定性模型。统计模型基于持久性的概念,通常依赖于机器学习和深度学习模型,这些模型可以从光伏发电历史数据中学习,并返回预测的光伏发电。对于混合模型,它是将前面的方法(物理模型和统计模型)相结合,以优化光伏功率预测的准确性。

本文提出的模型由长短时记忆和时态卷积网络模型结合而成。LSTM用于从输入数据中提取时间特征,然后结合TCN建立特征与输出之间的联系。所提出的模型已经使用包括测量的光伏功率的历史时间序列的数据集进行了测试。然后将该模式与LSTM和TCN模式在不同季节、时段预报、阴天、晴天和间歇日的预报精度进行了比较。

方法

本文提出了LSTM-TCN用于短期光伏功率预测,其中LSTM用于从输入数据中提取时间特征,而TCN用于将特征与输出联系起来用于光伏功率预测。这项工作所用的流程图如图2所示。
在这里插入图片描述

LSTM

ARIMA(自回归综合移动平均)是一种流行的时间序列预测模型,它基于这样一个事实,即未来预测是过去测量的线性函数。ARIMA模型的局限性在于它的线性度和它不依赖于旧值的事实,它只考虑了之前的状态。

传统的RNN模型存在梯度消失或梯度爆炸的问题。为了避免梯度消失问题,LSTM已被提出用于有效地处理时间序列数据。LSTM模型具有具有自连接的存储单元来存储时间状态。LSTM单元的结构如图3所示。Ct是内部单元状态。LSTM使用忘记门FT、输入门It和输出门Ot来维护、改变或删除单元状态信息。输入是每个时间戳t的序列向量xt、隐藏层输出ht−1和信元状态Ct−1。输出是LSTM隐藏层输出ht和信元状态Ct。忘记门、输入门和输出门使用公式(1)-(3)计算:
在这里插入图片描述

通过使用公式(4)计算当前候选状态Ct:
在这里插入图片描述

输入门和遗忘门确定Ct−1和̃Ct在当前信元状态Ct中分别占据的信息的比例。当前单元格状态可以通过公式(5)计算:
在这里插入图片描述
隐藏层输出通过公式(6)计算:
在这里插入图片描述
其中Wf、Wi和W0分别为遗忘门、输入门和输出门的权重矩阵。Bf、bi和b0分别是遗忘门、输入门和输出门偏置。
σ是激活函数。

TCN

TCN是一种用于具有因果约束的卷积神经网络,常用于时间序列预测任务。TCN的第一层是一维全卷积网络,其中每个中间层具有与输入层相同的大小,并且使用大小的零填充来获得与先前层相同的后续层的大小。与RNN类似,TCN体系结构可以处理任意长度的序列或时间序列,并将其映射到相同长度的输出数据。

TCN体系结构包括扩张的因果卷积,以尊重因果约束,提高各自的场,确保信息不会从未来泄漏到过去,并避免巨大的计算工作。
一维序列(x∈Rn)的元素S的扩张卷积运算F描述如下:
在这里插入图片描述

其中d是膨胀因子,i是滤波数,k是滤波片大小,∗是卷积运算符,S−d·i解释过去的方向。图4示出了具有扩张因子d=1、2、4和滤波器大小k=3的扩张因果卷积的例子。为了扩大网络的接收范围,增大了滤波器大小k和扩张因子d。接受范围越大,网络就越能充分考虑过去。

在这里插入图片描述

模型总体架构

论文提出的模型结合LSTM和TCN,利用其历史和气象数据(如全球水平辐射)来预测PV功率。LSTM-TCN的流程图如下图所示。该模型由三层LSTM模型组成,用于从输入数据中提取时间特征。第一层有256个单元,第二层有128个单元,第三层有64个单元。在LSTM层中加入3个堆叠的TCN模型残差块。每个残差块具有相同数量的Filter和内核大小。残差块的膨胀系数分别为1、2和4。将TCN模型与LSTM相结合,提高了预测精度,并建立了特征与输出之间的联系。最后,使用Dense层进行维度变换。用于模型训练和验证的损失函数是均方误差(MSE),模型优化器是自适应矩估计(Adam)。值得注意的是,LSTM和TCN参数是在使用Keras调谐器函数搜索误差最小的最优参数后,测试了膨胀率、内核大小、Filter数量、LSTM单元和层数等几个值后选择的。
在这里插入图片描述

实验

作者设置的参数如下:
100个epoch,batch size为200,学习率为0.001,损失函数是均方误差,用ADAM优化算法来降低损失函数,模型的输入顺序采用12步回溯,相当于1小时。

为了在时间序列预测中使用深度学习模型,必须将数据集转换为监督回归问题,其中输入特征和输出(标签)被呈现和定义。滑动窗口方法通过分割数据集来将数据集转换为监督学习问题,以获得一个序列(固定窗口大小)的观测值作为模型的输入,并获得固定数量的后续观测值作为输出,这些观测值可以是一步预测的一个后续观测值,也可以是多步预测的一系列后续观测值。通过每次滑动窗口一个时间步来对整个数据集重复分割,以获得下一个输入和输出序列。下图显示了多步时间序列预测(输入序列大小= 5,输出序列大小= 3)的滑动窗口过程。数据集分为训练数据集,用于训练所提出的深度学习模型和测试数据集,以评估模型的准确性和性能。
在这里插入图片描述

实验结果

对于一步预测,结果表明作者提出的模型比LSTM和TCN模型具有更好的性能。与LSTM、TCN相比,秋季、冬季、春季和夏季的平均绝对误差分别降低了8.47%、14.26%、6.91%、15.18、10.22%、14.26%、14.23%。对于多步预测,从2步预测到7步预测,LSTM-TCN在不同时间段的预测均优于所有比较模型。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

深度学习

TCN-LSTM代码

import argparse
import time
 
import numpy as np
import pandas as pd
import torch
import torch.nn as nn
from matplotlib import pyplot as plt
from torch.nn.utils import weight_norm
from torch.utils.data import DataLoader
from torch.utils.data import Dataset
from tqdm import tqdm
 
# 随机数种子
np.random.seed(0)
 
 
class StandardScaler():
    def __init__(self):
        self.mean = 0.
        self.std = 1.
 
    def fit(self, data):
        self.mean = data.mean(0)
        self.std = data.std(0)
 
    def transform(self, data):
        mean = torch.from_numpy(self.mean).type_as(data).to(data.device) if torch.is_tensor(data) else self.mean
        std = torch.from_numpy(self.std).type_as(data).to(data.device) if torch.is_tensor(data) else self.std
        return (data - mean) / std
 
    def inverse_transform(self, data):
        mean = torch.from_numpy(self.mean).type_as(data).to(data.device) if torch.is_tensor(data) else self.mean
        std = torch.from_numpy(self.std).type_as(data).to(data.device) if torch.is_tensor(data) else self.std
        if data.shape[-1] != mean.shape[-1]:
            mean = mean[-1:]
            std = std[-1:]
        return (data * std) + mean
 
 
def plot_loss_data(data):
    # 使用Matplotlib绘制线图
    plt.figure()
 
    plt.plot(data, marker='o')
 
    # 添加标题
    plt.title("loss results Plot")
 
    # 显示图例
    plt.legend(["Loss"])
 
    plt.show()
 
 
class TimeSeriesDataset(Dataset):
    def __init__(self, sequences):
        self.sequences = sequences
 
    def __len__(self):
        return len(self.sequences)
 
    def __getitem__(self, index):
        sequence, label = self.sequences[index]
        return torch.Tensor(sequence), torch.Tensor(label)
 
 
def create_inout_sequences(input_data, tw, pre_len, config):
    # 创建时间序列数据专用的数据分割器
    inout_seq = []
    L = len(input_data)
    for i in range(L - tw):
        train_seq = input_data[i:i + tw]
        if (i + tw + pre_len) > len(input_data):
            break
        if config.feature == 'MS':
            train_label = input_data[:, -1:][i + tw:i + tw + pre_len]
        else:
            train_label = input_data[i + tw:i + tw + pre_len]
        inout_seq.append((train_seq, train_label))
    return inout_seq
 
 
def calculate_mae(y_true, y_pred):
    # 平均绝对误差
    mae = np.mean(np.abs(y_true - y_pred))
    return mae
 
 
def create_dataloader(config, device):
    print(">>>>>>>>>>>>>>>>>>>>>>>>>>>>创建数据加载器<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<")
    df = pd.read_csv(config.data_path)  # 填你自己的数据地址,自动选取你最后一列数据为特征列 # 添加你想要预测的特征列
    pre_len = config.pre_len  # 预测未来数据的长度
    train_window = config.window_size  # 观测窗口
 
    # 将特征列移到末尾
    target_data = df[[config.target]]
    df = df.drop(config.target, axis=1)
    df = pd.concat((df, target_data), axis=1)
 
    cols_data = df.columns[1:]
    df_data = df[cols_data]
 
    # 这里加一些数据的预处理, 最后需要的格式是pd.series
    true_data = df_data.values
 
    # 定义标准化优化器
    # 定义标准化优化器
    scaler = StandardScaler()
    scaler.fit(true_data)
 
    train_data = true_data[int(0.3 * len(true_data)):]
    valid_data = true_data[int(0.15 * len(true_data)):int(0.30 * len(true_data))]
    test_data = true_data[:int(0.15 * len(true_data))]
    print("训练集尺寸:", len(train_data), "测试集尺寸:", len(test_data), "验证集尺寸:", len(valid_data))
 
    # 进行标准化处理
    train_data_normalized = scaler.transform(train_data)
    test_data_normalized = scaler.transform(test_data)
    valid_data_normalized = scaler.transform(valid_data)
 
    # 转化为深度学习模型需要的类型Tensor
    train_data_normalized = torch.FloatTensor(train_data_normalized).to(device)
    test_data_normalized = torch.FloatTensor(test_data_normalized).to(device)
    valid_data_normalized = torch.FloatTensor(valid_data_normalized).to(device)
 
    # 定义训练器的的输入
    train_inout_seq = create_inout_sequences(train_data_normalized, train_window, pre_len, config)
    test_inout_seq = create_inout_sequences(test_data_normalized, train_window, pre_len, config)
    valid_inout_seq = create_inout_sequences(valid_data_normalized, train_window, pre_len, config)
 
    # 创建数据集
    train_dataset = TimeSeriesDataset(train_inout_seq)
    test_dataset = TimeSeriesDataset(test_inout_seq)
    valid_dataset = TimeSeriesDataset(valid_inout_seq)
 
    # 创建 DataLoader
    train_loader = DataLoader(train_dataset, batch_size=args.batch_size, shuffle=True, drop_last=True)
    test_loader = DataLoader(test_dataset, batch_size=args.batch_size, shuffle=False, drop_last=True)
    valid_loader = DataLoader(valid_dataset, batch_size=args.batch_size, shuffle=False, drop_last=True)
 
    print("通过滑动窗口共有训练集数据:", len(train_inout_seq), "转化为批次数据:", len(train_loader))
    print("通过滑动窗口共有测试集数据:", len(test_inout_seq), "转化为批次数据:", len(test_loader))
    print("通过滑动窗口共有验证集数据:", len(valid_inout_seq), "转化为批次数据:", len(valid_loader))
    print(">>>>>>>>>>>>>>>>>>>>>>>>>>>>创建数据加载器完成<<<<<<<<<<<<<<<<<<<<<<<<<<<")
    return train_loader, test_loader, valid_loader, scaler
 
 
class Chomp1d(nn.Module):
    def __init__(self, chomp_size):
        super(Chomp1d, self).__init__()
        self.chomp_size = chomp_size
 
    def forward(self, x):
        return x[:, :, :-self.chomp_size].contiguous()
 
 
class TemporalBlock(nn.Module):
    def __init__(self, n_inputs, n_outputs, kernel_size, stride, dilation, padding, dropout=0.2):
        super(TemporalBlock, self).__init__()
        self.conv1 = weight_norm(nn.Conv1d(n_inputs, n_outputs, kernel_size,
                                           stride=stride, padding=padding, dilation=dilation))
        self.chomp1 = Chomp1d(padding)
        self.relu1 = nn.ReLU()
        self.dropout1 = nn.Dropout(dropout)
 
        self.conv2 = weight_norm(nn.Conv1d(n_outputs, n_outputs, kernel_size,
                                           stride=stride, padding=padding, dilation=dilation))
        self.chomp2 = Chomp1d(padding)
        self.relu2 = nn.ReLU()
        self.dropout2 = nn.Dropout(dropout)
 
        self.net = nn.Sequential(self.conv1, self.chomp1, self.relu1, self.dropout1,
                                 self.conv2, self.chomp2, self.relu2, self.dropout2)
        self.downsample = nn.Conv1d(n_inputs, n_outputs, 1) if n_inputs != n_outputs else None
        self.relu = nn.ReLU()
        self.init_weights()
 
    def init_weights(self):
        self.conv1.weight.data.normal_(0, 0.01)
        self.conv2.weight.data.normal_(0, 0.01)
        if self.downsample is not None:
            self.downsample.weight.data.normal_(0, 0.01)
 
    def forward(self, x):
        out = self.net(x)
        res = x if self.downsample is None else self.downsample(x)
        return self.relu(out + res)
 
 
class TemporalConvNet(nn.Module):
    def __init__(self, num_inputs, outputs, pre_len, num_channels, n_layers, kernel_size=2, dropout=0.2):
        super(TemporalConvNet, self).__init__()
        layers = []
        self.pre_len = pre_len
        self.n_layers = n_layers
        self.hidden_size = num_channels[-2]
        self.hidden = nn.Linear(num_channels[-1], num_channels[-2])
        self.relu = nn.ReLU()
        self.lstm = nn.LSTM(self.hidden_size, self.hidden_size, n_layers, bias=True,
                            batch_first=True)  # output (batch_size, obs_len, hidden_size)
        num_levels = len(num_channels)
        for i in range(num_levels):
            dilation_size = 2 ** i
            in_channels = num_inputs if i == 0 else num_channels[i - 1]
            out_channels = num_channels[i]
            layers += [TemporalBlock(in_channels, out_channels, kernel_size, stride=1, dilation=dilation_size,
                                     padding=(kernel_size - 1) * dilation_size, dropout=dropout)]
 
        self.network = nn.Sequential(*layers)
        self.linear = nn.Linear(num_channels[-2], outputs)
 
    def forward(self, x):
        x = x.permute(0, 2, 1)
        x = self.network(x)
        x = x.permute(0, 2, 1)
 
        batch_size, obs_len, features_size = x.shape  # (batch_size, obs_len, features_size)
        xconcat = self.hidden(x)  # (batch_size, obs_len, hidden_size)
        H = torch.zeros(batch_size, obs_len - 1, self.hidden_size).to(device)  # (batch_size, obs_len-1, hidden_size)
        ht = torch.zeros(self.n_layers, batch_size, self.hidden_size).to(
            device)  # (num_layers, batch_size, hidden_size)
        ct = ht.clone()
        for t in range(obs_len):
            xt = xconcat[:, t, :].view(batch_size, 1, -1)  # (batch_size, 1, hidden_size)
            out, (ht, ct) = self.lstm(xt, (ht, ct))  # ht size (num_layers, batch_size, hidden_size)
            htt = ht[-1, :, :]  # (batch_size, hidden_size)
            if t != obs_len - 1:
                H[:, t, :] = htt
        H = self.relu(H)  # (batch_size, obs_len-1, hidden_size)
 
        x = self.linear(H)
        return x[:, -self.pre_len:, :]
 
 
def train(model, args, scaler, device):
    start_time = time.time()  # 计算起始时间
    model = model
    loss_function = nn.MSELoss()
    optimizer = torch.optim.Adam(model.parameters(), lr=0.005)
    epochs = args.epochs
    model.train()  # 训练模式
    results_loss = []
    for i in tqdm(range(epochs)):
        losss = []
        for seq, labels in train_loader:
            optimizer.zero_grad()
 
            y_pred = model(seq)
 
            single_loss = loss_function(y_pred, labels)
 
            single_loss.backward()
 
            optimizer.step()
            losss.append(single_loss.detach().cpu().numpy())
        tqdm.write(f"\t Epoch {i + 1} / {epochs}, Loss: {sum(losss) / len(losss)}")
        results_loss.append(sum(losss) / len(losss))
 
 
        torch.save(model.state_dict(), 'save_model.pth')
        time.sleep(0.1)
 
    # valid_loss = valid(model, args, scaler, valid_loader)
    # 尚未引入学习率计划后期补上
    # 保存模型
 
    print(f">>>>>>>>>>>>>>>>>>>>>>模型已保存,用时:{(time.time() - start_time) / 60:.4f} min<<<<<<<<<<<<<<<<<<")
    plot_loss_data(results_loss)
 
 
def valid(model, args, scaler, valid_loader):
    lstm_model = model
    # 加载模型进行预测
    lstm_model.load_state_dict(torch.load('save_model.pth'))
    lstm_model.eval()  # 评估模式
    losss = []
 
    for seq, labels in valid_loader:
        pred = lstm_model(seq)
        mae = calculate_mae(pred.detach().numpy().cpu(), np.array(labels.detach().cpu()))  # MAE误差计算绝对值(预测值  - 真实值)
        losss.append(mae)
 
    print("验证集误差MAE:", losss)
    return sum(losss) / len(losss)
 
 
def test(model, args, test_loader, scaler):
    # 加载模型进行预测
    losss = []
    model = model
    model.load_state_dict(torch.load('save_model.pth'))
    model.eval()  # 评估模式
    results = []
    labels = []
    for seq, label in test_loader:
        pred = model(seq)
        mae = calculate_mae(pred.detach().cpu().numpy(),
                            np.array(label.detach().cpu()))  # MAE误差计算绝对值(预测值  - 真实值)
        losss.append(mae)
        pred = pred[:, 0, :]
        label = label[:, 0, :]
        pred = scaler.inverse_transform(pred.detach().cpu().numpy())
        label = scaler.inverse_transform(label.detach().cpu().numpy())
        for i in range(len(pred)):
            results.append(pred[i][-1])
            labels.append(label[i][-1])
 
    print("测试集误差MAE:", losss)
    # 绘制历史数据
    plt.plot(labels, label='TrueValue')
 
    # 绘制预测数据
    # 注意这里预测数据的起始x坐标是历史数据的最后一个点的x坐标
    plt.plot(results, label='Prediction')
 
    # 添加标题和图例
    plt.title("test state")
    plt.legend()
    plt.show()
 
 
# 检验模型拟合情况
def inspect_model_fit(model, args, train_loader, scaler):
    model = model
    model.load_state_dict(torch.load('save_model.pth'))
    model.eval()  # 评估模式
    results = []
    labels = []
 
    for seq, label in train_loader:
        pred = model(seq)[:, 0, :]
        label = label[:, 0, :]
        pred = scaler.inverse_transform(pred.detach().cpu().numpy())
        label = scaler.inverse_transform(label.detach().cpu().numpy())
        for i in range(len(pred)):
            results.append(pred[i][-1])
            labels.append(label[i][-1])
 
    # 绘制历史数据
    plt.plot(labels, label='History')
 
    # 绘制预测数据
    # 注意这里预测数据的起始x坐标是历史数据的最后一个点的x坐标
    plt.plot(results, label='Prediction')
 
    # 添加标题和图例
    plt.title("inspect model fit state")
    plt.legend()
    plt.show()
 
 
def predict(model, args, device, scaler):
    # 预测未知数据的功能
    df = pd.read_csv(args.data_path)
    df = df.iloc[:, 1:][-args.window_size:].values  # 转换为nadarry
    pre_data = scaler.transform(df)
    tensor_pred = torch.FloatTensor(pre_data).to(device)
    tensor_pred = tensor_pred.unsqueeze(0)  # 单次预测 , 滚动预测功能暂未开发后期补上
    model = model
    model.load_state_dict(torch.load('save_model.pth'))
    model.eval()  # 评估模式
 
    pred = model(tensor_pred)[0]
 
    pred = scaler.inverse_transform(pred.detach().cpu().numpy())
 
    # 假设 df 和 pred 是你的历史和预测数据
 
    # 计算历史数据的长度
    history_length = len(df[:, -1])
 
    # 为历史数据生成x轴坐标
    history_x = range(history_length)
 
    # 为预测数据生成x轴坐标
    # 开始于历史数据的最后一个点的x坐标
    prediction_x = range(history_length - 1, history_length + len(pred[:, -1]) - 1)
 
    # 绘制历史数据
    plt.plot(history_x, df[:, -1], label='History')
 
    # 绘制预测数据
    # 注意这里预测数据的起始x坐标是历史数据的最后一个点的x坐标
    plt.plot(prediction_x, pred[:, -1], marker='o', label='Prediction')
    plt.axvline(history_length - 1, color='red')  # 在图像的x位置处画一条红色竖线
    # 添加标题和图例
    plt.title("History and Prediction")
    plt.legend()
 
 
if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Time Series forecast')
    parser.add_argument('-model', type=str, default='TCN-LSTM', help="模型持续更新")
    parser.add_argument('-window_size', type=int, default=126, help="时间窗口大小, window_size > pre_len")
    parser.add_argument('-pre_len', type=int, default=24, help="预测未来数据长度")
    # data
    parser.add_argument('-shuffle', action='store_true', default=True, help="是否打乱数据加载器中的数据顺序")
    parser.add_argument('-data_path', type=str, default='ETTh1-Test.csv', help="你的数据数据地址")
    parser.add_argument('-target', type=str, default='OT', help='你需要预测的特征列,这个值会最后保存在csv文件里')
    parser.add_argument('-input_size', type=int, default=7, help='你的特征个数不算时间那一列')
    parser.add_argument('-feature', type=str, default='MS', help='[M, S, MS],多元预测多元,单元预测单元,多元预测单元')
    parser.add_argument('-model_dim', type=list, default=[64, 128, 256], help='这个地方是这个TCN卷积的关键部分,它代表了TCN的层数我这里输'
                                                                              '入list中包含三个元素那么我的TCN就是三层,这个根据你的数据复杂度来设置'
                                                                              '层数越多对应数据越复杂但是不要超过5层')
 
    # learning
    parser.add_argument('-lr', type=float, default=0.001, help="学习率")
    parser.add_argument('-drop_out', type=float, default=0.05, help="随机丢弃概率,防止过拟合")
    parser.add_argument('-epochs', type=int, default=20, help="训练轮次")
    parser.add_argument('-batch_size', type=int, default=16, help="批次大小")
    parser.add_argument('-save_path', type=str, default='models')
 
    # model
    parser.add_argument('-hidden_size', type=int, default=64, help="隐藏层单元数")
    parser.add_argument('-kernel_sizes', type=int, default=3)
    parser.add_argument('-laryer_num', type=int, default=1)
    # device
    parser.add_argument('-use_gpu', type=bool, default=True)
    parser.add_argument('-device', type=int, default=0, help="只设置最多支持单个gpu训练")
 
    # option
    parser.add_argument('-train', type=bool, default=True)
    parser.add_argument('-test', type=bool, default=True)
    parser.add_argument('-predict', type=bool, default=True)
    parser.add_argument('-inspect_fit', type=bool, default=True)
    parser.add_argument('-lr-scheduler', type=bool, default=True)
    args = parser.parse_args()
 
    if isinstance(args.device, int) and args.use_gpu:
        device = torch.device("cuda:" + f'{args.device}')
    else:
        device = torch.device("cpu")
    print("使用设备:", device)
    train_loader, test_loader, valid_loader, scaler = create_dataloader(args, device)
 
    if args.feature == 'MS' or args.feature == 'S':
        args.output_size = 1
    else:
        args.output_size = args.input_size
 
    # 实例化模型
    try:
        print(f">>>>>>>>>>>>>>>>>>>>>>>>>开始初始化{args.model}模型<<<<<<<<<<<<<<<<<<<<<<<<<<<")
        model = TemporalConvNet(args.input_size, args.output_size, args.pre_len, args.model_dim, args.laryer_num,
                                args.kernel_sizes).to(device)
        print(f">>>>>>>>>>>>>>>>>>>>>>>>>开始初始化{args.model}模型成功<<<<<<<<<<<<<<<<<<<<<<<<<<<")
    except:
        print(f">>>>>>>>>>>>>>>>>>>>>>>>>开始初始化{args.model}模型失败<<<<<<<<<<<<<<<<<<<<<<<<<<<")
 
    # 训练模型
    if args.train:
        print(f">>>>>>>>>>>>>>>>>>>>>>>>>开始{args.model}模型训练<<<<<<<<<<<<<<<<<<<<<<<<<<<")
        train(model, args, scaler, device)
    if args.test:
        print(f">>>>>>>>>>>>>>>>>>>>>>>>>开始{args.model}模型测试<<<<<<<<<<<<<<<<<<<<<<<<<<<")
        test(model, args, test_loader, scaler)
    if args.inspect_fit:
        print(f">>>>>>>>>>>>>>>>>>>>>>>>>开始检验{args.model}模型拟合情况<<<<<<<<<<<<<<<<<<<<<<<<<<<")
        inspect_model_fit(model, args, train_loader, scaler)
    if args.predict:
        print(f">>>>>>>>>>>>>>>>>>>>>>>>>预测未来{args.pre_len}条数据<<<<<<<<<<<<<<<<<<<<<<<<<<<")
        predict(model, args, device, scaler)
    plt.show()

运行结果

在这里插入图片描述

总结

在这里插入图片描述

时序卷积与普通的一维卷积存在以下两点不同:

  1. 使用因果卷积。特点在于网络输出长度等于输入长度,保证未来信息不会用于预测。
  2. 采用扩张卷积。特点在于可通过增加层数和扩张卷积核大小来增大感受野。越到上层,卷积核越大,输出的信息范围越广,从而确保滤波器能够获得更大范围输入特征,高效获取长时间跨度信息。
<template> <!--提报搜索条件--> <n-form ref="formInstRef" :model="dataSend" label-placement="left" label-align="left" label-width="auto" > <n-flex justify="space-between" :wrap="false" :size="128" > <n-grid :cols="32" :x-gap="24" > <n-form-item-gi :span="6" label="需求名称:" path="requireName" > <n-input v-model:value="dataSend.requireName" /> </n-form-item-gi> <n-form-item-gi :span="6" label="提报单位:" path="majorDecisionSubmitUnit" > <n-select v-model:value="dataSend.reportUnit" :options="submitDeparments" placeholder="全部" class="custom-select-color" /> </n-form-item-gi> <n-form-item-gi :span="6" label="提报时间:" path="majorDecisionSubmitTime" > <n-date-picker v-model:formatted-value="dataSend.reportTimespan" type="daterange" format="yyyy-MM-dd" clearable /> </n-form-item-gi> <n-form-item-gi :span="6" label="需求状态:" path="requireStatus" > <n-select v-model:value="dataSend.requireStatus" :options="requireStatusLabelMap" placeholder="全部" clearable /> </n-form-item-gi> </n-grid> </n-flex> <n-flex justify="space-between" :wrap="false" > <n-grid :cols="24" > <n-form-item-gi :offset="19" > <div class="button-container-flex"> <n-button type="primary" @click="querymajorDecisionSubmit" > <template #icon> <n-icon size="14"> <Search /> </n-icon> </template> 搜索 </n-button> <n-button type="default" @click="reset" > <template #icon> <n-icon size="14"> <Sync /> </n-icon> </template> 重置 </n-button> <n-button type="default" @click="exportExcel" > <template #icon> <n-icon size="14"> <Download /> </n-icon> </template> 导出 </n-button> </div> </n-form-item-gi> </n-grid> </n-flex> </n-form> <!--提报搜索结果--> <div class="card-table-wrapper" ref="cardTableWrapper" > <n-data-table :single-line="false" :columns="columns" :max-height="`${cardTableHeight}px`" :data="tableData" :scroll-x="1000" :loading="loading" :pagination="pagination" remote :row-key="(row: Record<string, unknown>) => row.id || row.key || row.requireId || row.requireName" @update:checked-row-keys="handleCheckedRowKeysChange" /> </div> </template> <script type="ts" lang="ts" setup> import { createDiscreteApi, FormInst, NIcon, } from 'naive-ui'; import { Search, Download, Sync, Plus, PencilAlt, Circle, } from '@vicons/fa'; import { ref, reactive, onMounted, h, } from 'vue'; import { NButton, useDialog, } from 'naive-ui'; import { fetchList, deleteItem, fetchAllSubmitDepartment, exportFile, } from '@/api/majorDecisionSubmit'; // ----------亮灯规则--------------------- const { message } = createDiscreteApi(['message']); import { fetchList as fetchLightingList } from '@/api/lightingProcessSetting'; import { fetchListMessage } from "@/api/holidaysSetting"; // ------------------------------- import { saveAs } from 'file-saver'; // 移除不再使用的导入 import { useRouter } from 'vue-router'; import { requireStatusEnum, handleError, handleSuccess, } from '@/utils/majorDecisionSubmit'; //todo remove import { getSubmitterInfo } from '@/utils/majorDecisionSubmit'; onMounted( async () => { await queryHolidays(); console.log("ssss"); await queryLightingRules(); console.log("ssss1"); await colorChange(tableData,thisType); }); const router = useRouter(); const dialog = useDialog(); const formInstRef = ref<FormInst | null>(null); const loading = ref(false); const cardTableHeight = ref(400); const cardTableWrapper = ref<HTMLElement | null>(null); const tableData = ref([ { requireType: '重大决策审核提报', requireName: '供应链管理系统对接电商平台', requireDescription: '实现订单实时同步与库存自动调拨', department: '商务部', createdBy: '王海涛', area: '华东区', createdTime: '2024-06-15 14:30:00', requireStatus: '待审核', consultStatus: '已回复', }, { requireType: '重大决策审核提报', requireName: '客户信息查重系统升级', requireDescription: '增加关键字匹配引擎,避免冗余客户数据', department: '信息技术部', createdBy: '李晓芸', area: '华南区', createdTime: '2024-06-18 09:15:00', requireStatus: '已审核', consultStatus: '未回复', }]); const submitDeparments = ref<{label: string, value: string}[]>([]); const checkedRowKeys = ref<string[]>([]); // 新增:存储选中的行数据 // ----------亮灯规则--------------------- // 定义颜色和图标映射 const statusConfig = { red: 'red', green: 'green', yellow: 'yellow', }; const lightingData = ref([]); const holidaysMessage = ref([]); const endDate = ref(null); const startDate = ref(null); const thisType = '重大决策审核提报'; // ------------------------------- // 搜索条件 const dataSend = ref({ requireName: null, //需求名称 reportUnit: null, //提报单位 reportTimespan: null, //提报发布时间字段 requireStatus: null, // 需求状态 }); // 页面信息 const pagination = reactive({ page: 1, pageSize: 10, itemCount: 0, pageCount: 0, showSizePicker: true, pageSizes: [10, 20, 50], showQuickJumper: true, prefix: ({ itemCount }: { itemCount: number }) => `共 ${itemCount} 条`, onChange: (page: number) => { pagination.page = page; querymajorDecisionSubmit(); }, onUpdatePageSize: (pageSize: number) => { pagination.pageSize = pageSize; pagination.page = 1; querymajorDecisionSubmit(); }, }); // 提报信息行 interface TableRow { checked: boolean; index: number; id: string; requireType: string; requireName: string; requireDescription: string; createdBy: number; department: string; area: string; createdTime: Date; requireStatus: number; consultStatus: string; } // 提报人单位信息 interface majorDecisionSubmitUnits{ name: string; remark: Date; } // 提报的所有状态文字描述 const requireStatusLabelMap = ([ { value: requireStatusEnum.NoneSubmit, label: '待提交', }, { value: requireStatusEnum.SearchExpert, label: '分配中', }, { value: requireStatusEnum.Checking, label: '审核中', }, { value: requireStatusEnum.Auditing, label: '评价中', }, { value: requireStatusEnum.Complete, label: '完结', }, ]); // 表列定义 const columns = ref([ { type: 'selection', fixed: 'left', key: 'checked', }, { title: '序号', key: 'index', fixed: 'left', align: 'center', }, { title: '需求类型', key: 'requireType', fixed: 'left', align: 'center', }, { title: '需求名称', fixed: 'left', key: 'requireName', align: 'center', width: 160, }, { title: '需求描述', key: 'requireDescription', align: 'center', }, { title: '提报单位', key: 'department', align: 'center', // to do remove render(row: TableRow) { return getSubmitterInfo(row.createdBy)?.department; }, }, { title: '提报人', key: 'createdBy', align: 'center', // to do remove render(row: TableRow) { return getSubmitterInfo(row.createdBy)?.creater; }, }, { title: '所属区域', key: 'area', align: 'center', // to do remove render(row: TableRow) { return getSubmitterInfo(row.createdBy)?.area; }, }, { title: '提报时间', key: 'createdTime', align: 'center', }, // { // title: '需求状态', // key: 'requireStatus', // align: 'center', // render(row: TableRow) { // return requireStatusLabelMap[row.requireStatus-1].label; // }, // }, { title: '咨询对话', key: 'consultStatus', align: 'center', }, { title: 'time1', key: 'time1', render(row : object) { const config = statusConfig[row.status] || statusConfig.inactive; return [ h( NIcon, { color: config, size: 'small', }, { default: () => h(Circle) }, ), ]; }, }, { title: 'time2', key: 'time2', render(row : object) { const config = statusConfig[row.status1] || statusConfig.inactive; return [ h( NIcon, { color: config, size: 'small', }, { default: () => h(Circle) }, ), ]; }, }, { title: '操作', key: 'id', fixed: 'right', align: 'center', render(row: TableRow) { if(row.requireStatus === requireStatusEnum.NoneSubmit) { return [h( NButton, { size: 'small', type: 'error', ghost: true, onClick: () => handleDelete(row), }, { default: () => '删除' }, ), h( NButton, { size: 'small', type: 'primary', ghost: true, onClick: () => handleEdit(row), }, { default: () => '编辑' }, )]; } if(row.requireStatus === requireStatusEnum.SearchExpert || row.requireStatus === requireStatusEnum.Checking || row.requireStatus === requireStatusEnum.Complete ) { return h( NButton, { size: 'small', type: 'primary', ghost: true, onClick: () => row.requireStatus === requireStatusEnum.Complete ? handleCheck(row) :handleReadonly(row), }, { default: () => '查看' }, ); } if(row.requireStatus === requireStatusEnum.Auditing) { return h( NButton, { size: 'small', type: 'primary', ghost: true, onClick: () => handleCheck(row), }, { default: () => '评价' }, ); } }, }, ]); //新建按钮的处理 - 跳转新建提报页面 const handleAdd = () => { router.push({ name: 'AddMajorDecisionSubmit' }); }; // 编辑按钮的处理 - 跳转编辑页面 const handleSelectRowEdit = () => { const selectedRows = getSelectedRows(); if(selectedRows.length != 1) { return; } const row = selectedRows[0] as TableRow; if(row.requireStatus != requireStatusEnum.NoneSubmit){ return; } router.push({ name: 'EditMajorDecisionSubmit', params: { status: row.requireStatus, id: row.id || 0, }, }); }; // 搜索按钮的处理 const querymajorDecisionSubmit = () => { loading.value = true; const data = { ...dataSend.value, pageNum: pagination.page, pageSize: pagination.pageSize, }; fetchList(data).then((response) => { loading.value = false; tableData.value = response.data.dataResult || response || []; pagination.itemCount = response.data.pageResult.totalCount || 0; pagination.pageCount = response.data.pageResult.totalPages || 0; tableData.value.forEach((item: TableRow, index) => { item.index = (pagination.page - 1)*pagination.pageSize + index + 1; }); }).catch(() => { loading.value = false; handleError('获取数据失败'); }); }; // 重置按钮的处理 const reset = () => { dataSend.value = { requireName: null, reportUnit: null, reportTimespan: null, requireStatus: null, }; pagination.page = 1; formInstRef.value?.restoreValidation(); querymajorDecisionSubmit(); }; // 提报单位取得 const fetchSubmitDepartment = () => { fetchAllSubmitDepartment().then((response: { data: majorDecisionSubmitUnits[] }) => { submitDeparments.value = response.data.map((item: majorDecisionSubmitUnits) => ({ label: item.name, value: item.name, })); }) .catch(() => { loading.value = false; handleError('获取提报单位失败'); }); }; // 表中按钮的处理 - 跳转编辑页面 const handleEdit = (row: TableRow) => { router.push({ name: 'EditMajorDecisionSubmit', params: { id: row.id || 0 }, }); }; // 表中按钮的处理 - 跳转只读页面 const handleReadonly = (row: TableRow) => { router.push({ name: 'viewMajorDecisionSubmit', params: { status: row.requireStatus, id: row.id || 0, }, }); }; //表中按钮的处理 - 跳转评价页面 const handleCheck = (row: TableRow) => { router.push({ name: 'checkMajorDecisionSubmit', params: { status: row.requireStatus, id: row.id || 0, }, }); }; //表中按钮的处理 - 跳转删除提报页面 const handleDelete = function (row: TableRow) { dialog.warning({ title: '提示', content: '此操作将删除数据参数,是否继续?', positiveText: '确定', negativeText: '取消', onPositiveClick: () => { deleteItem(row.id as string).then(() => { handleSuccess('删除成功'); querymajorDecisionSubmit(); }) .catch(() => { loading.value = false; handleError('删除失败'); }); }, onNegativeClick: () => { // 用户取消删除操作 }, }); }; // 处理选中行变化 const handleCheckedRowKeysChange = (keys: string[]) => { checkedRowKeys.value = keys; }; // 获取选中的行数据 const getSelectedRows = () => { return tableData.value.filter((row: TableRow) => checkedRowKeys.value.includes(row.id)); }; const exportExcel = async () => { // 检查是否有选中的行 const exportTitle=(checkedRowKeys.value.length > 0?'重要决策检查报告_我的提报_选中数据':'重要决策检查报告_我的提报'); // 调用导出API - 传递选中数据的ID列表或查询条件 const params = { ...dataSend.value, ids: checkedRowKeys.value, // 传递要导出的数据ID列表 exportType: checkedRowKeys.value.length > 0 ? 'selected' : 'all', // 标识导出类型 }; loading.value = true; exportFile(params).then((response: any) => { loading.value = false; // 生成文件名 const timestamp = new Date().toISOString().slice(0, 19).replace(/[-:]/g, '').replace('T', '_'); const filename = `${exportTitle}_${timestamp}.xlsx`; // 使用saveAs下载文件 saveAs(response, filename); const message = checkedRowKeys.value.length > 0 ? `导出成功,共导出 ${checkedRowKeys.value.length} 条选中数据` : '导出成功'; handleSuccess(message); }) .catch(() => { loading.value = false; handleError('导出失败'); }); }; // ----------亮灯规则--------------------- const queryLightingRules = async ()=>{ await fetchLightingList({}).then((res) => { if (res.state === 200) { lightingData.value = res.data; } }).catch(()=>{ message.error('获取亮灯规则失败'); }); }; const queryHolidays = async ()=>{ await fetchListMessage({}).then((res) => { if (res.state === 200) { holidaysMessage.value = res.data; } }).catch(()=>{ message.error('获取节假日设置失败'); }); }; // ---------计算日期方法------------ const colorChange = async function (data : object , type : string ) { const date = new Date(); let dateDiffInDays = 0; setTimeout( async ()=>{ data.value.forEach(item => { if (type == '重大决策审核提报' || type == '经济合同审核提报' || type == '规章制度审核提报' || type == '法律纠纷案件论证提报'){ lightingData.value.forEach(item1 => { if (item.requireType == item1.lightingProcess && item.requireStatus == item1.requirementName){ getDaysMessage(item.createdTime).then((res: number) => { dateDiffInDays = res; if (dateDiffInDays == 0) dateDiffInDays = getDateDiffInDays(item.createdTime, date, 'asc'); const arr = item1.lightingRules.split(','); const arr1 = item1.expertLightingRulesForAssistanceAreas.split(','); console.log("ssssssss"); if (dateDiffInDays >= Number(arr[0]) && dateDiffInDays < Number(arr[1])) item.status = statusConfig.green; if (dateDiffInDays >= Number(arr[1]) && dateDiffInDays < Number(arr[2])) item.status = statusConfig.yellow; if (dateDiffInDays >= Number(arr[2])) item.status = statusConfig.red; if (dateDiffInDays >= Number(arr1[0]) && dateDiffInDays <= Number(arr1[1])) item.status1 = statusConfig.green; if (dateDiffInDays >= Number(arr1[1]) && dateDiffInDays <= Number(arr1[2])) item.status1 = statusConfig.yellow; if (dateDiffInDays >= Number(arr1[2])) item.status1 = statusConfig.red; }); } }); }else { lightingData.value.forEach(item1 => { if (item.requireType == item1.lightingProcess && item.requireStatus == item1.requirementName){ getDaysMessage(item.createdTime).then((res : number)=>{ dateDiffInDays = res; if (dateDiffInDays==0) dateDiffInDays = getDateDiffInDays(item.createdTime, date,'desc'); const arr = item1.lightingRules.split(','); const arr1 = item1.expertLightingRulesForAssistanceAreas.split(','); if (dateDiffInDays > Number(arr[0]) && dateDiffInDays < Number(arr[1])) item.status = statusConfig.red; if (dateDiffInDays > Number(arr[1]) && dateDiffInDays < Number(arr[2])) item.status = statusConfig.yellow; if (dateDiffInDays >= Number(arr[2])) item.status = statusConfig.green; if (dateDiffInDays > Number(arr1[0]) && dateDiffInDays <= Number(arr1[1])) item.status1 = statusConfig.red; if (dateDiffInDays > Number(arr1[1]) && dateDiffInDays <= Number(arr1[2])) item.status1 = statusConfig.yellow; if (dateDiffInDays > Number(arr1[2])) item.status1 = statusConfig.green; }); } }); } }); },1000); }; /** * 计算两个日期之间的间隔天数(绝对值) * @param start 起始日期 * @param end 结束日期 * @returns 间隔天数(正数) */ const getDateDiffInDays = function (start : string, end : string ,status : string) { const startData = new Date(start); const endData = new Date(end); // 确保时间部分相同,避免时间差影响天数计算 const startUTC = Date.UTC( startData.getFullYear(), startData.getMonth(), startData.getDate(), ); const endUTC = Date.UTC( endData.getFullYear(), endData.getMonth(), endData.getDate(), ); // 计算毫秒差并转换为天数 const msPerDay = 24 * 60 * 60 * 1000; // 每天的毫秒数 console.log(Math.floor(Math.abs(endUTC - startUTC) / msPerDay)); if (status == 'asc') return Math.floor(Math.abs(endUTC - startUTC) / msPerDay); if (status == 'desc') return Math.floor(Math.abs(startUTC - endUTC ) / msPerDay); return 0; }; const getHolidaysMessage = ()=>{ fetchListMessage({}).then(res=>{ if (res.state == 200){ holidaysMessage.value = res.data; } }); }; const formatToDateInput = (date : Date) => { date = new Date(date); const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); return `${year}-${month}-${day}`; }; const getDaysMessage = async (startTime : Date) : Promise => { const today = new Date(); endDate.value = formatToDateInput(today); startDate.value = formatToDateInput(startTime); return await checkWeekends(); }; // 检测周末 const checkWeekends = async () : Promise => { return new Promise(resolve => { const totalDays = ref(0); const weekendDays = ref(0); const saturdays = ref(0); const sundays = ref(0); const sum = ref(0); const holidaySum = ref(0); const start = new Date(startDate.value); const end = new Date(endDate.value); // 确保开始日期不晚于结束日期 if (start > end) { [start, end] = [end, start]; startDate.value = formatToDateInput(start); endDate.value = formatToDateInput(end); } // 计算总天数 const timeDiff = end.getTime() - start.getTime(); totalDays.value = Math.ceil(timeDiff / (1000 * 3600 * 24)); // 检查每一天 let current = new Date(start); // 计算开始时间和结束时间内有几个周六周日 setTimeout(()=>{ while (current <= end) { const dayOfWeek = current.getDay(); if (dayOfWeek === 0 || dayOfWeek === 6) { weekendDays.value++; if (dayOfWeek === 6) saturdays.value++; if (dayOfWeek === 0) sundays.value++; } current.setDate(current.getDate() + 1); } // console.log(holidaysMessage.value); // 判断起止时间内是否包含节假日 , 开始计算节假日设置的时间段是否包含周末以及节假日的天数 holidaysMessage.value.forEach(item =>{ item.startTime = new Date(item.startTime); item.endTime = new Date(item.endTime); // console.log(start < item.startTime && end > item.endTime); if (start < item.startTime && end > item.endTime) { holidaySum.value = Math.ceil( (item.endTime.getTime() - item.startTime.getTime())/(1000 * 3600 * 24)); current = new Date(item.startTime); while (current <= item.endTime) { const dayOfWeek = current.getDay(); if (dayOfWeek === 0 || dayOfWeek === 6) { weekendDays.value++; if (dayOfWeek === 6) sum.value++; if (dayOfWeek === 0) sum.value++; } current.setDate(current.getDate() + 1); } } }); console.log(saturdays.value + sundays.value + holidaySum.value - sum.value - sum.value); const result = totalDays.value - (saturdays.value + sundays.value + holidaySum.value - sum.value); resolve(result); },500); }); }; // ------------------------------- // onMounted(() => { // // 所有提报单位取得 // fetchSubmitDepartment(); // // 刷新界面 todo 根据前画面传递的参数 // querymajorDecisionSubmit(); // }); </script> <style lang="scss" scoped> .button-container-flex { display: flex; gap: 17px; /* 设置按钮之间的间隙 */ } </style> 这个页面的onmounted不好用
最新发布
09-17
<think> 我们正在解决Vue中使用router.push跳转页面后onMounted方法不执行的问题。根据之前的讨论,常见原因有组件复用、异步跳转、keep-alive缓存、路径错误或路由守卫未调用next等。下面将结合引用内容详细说明解决方案。 ### 1. 组件复用导致onMounted不执行(最常见) 当跳转的目标组件与当前组件相同(仅参数变化),Vue会复用组件实例,此时不会触发`onMounted`。 **解决方案**:使用`watch`监听路由参数变化,或使用`onBeforeRouteUpdate`导航守卫。 参考引用[2]中接收参数的方式,我们可以这样监听: ```vue <script setup> import { watch } from 'vue' import { useRoute } from 'vue-router' const route = useRoute() // 监听整个route对象的变化或特定参数 watch(() => route.params.id, (newId) => { console.log('参数变化:', newId) // 这里执行原本在onMounted中的逻辑 }) // 或者使用onBeforeRouteUpdate import { onBeforeRouteUpdate } from 'vue-router' onBeforeRouteUpdate((to, from) => { console.log('导航到新参数:', to.params.id) // 无需调用next()(在setup中) }) </script> ``` ### 2. router.push异步跳转导致时机问题 引用[3]提到`router.push`是异步操作,如果跳转后立即执行逻辑,可能发生在组件挂载之前。 **解决方案**:在`router.push`的`then`回调中执行逻辑,确保跳转完成。 引用[1]中的跳转方式可以调整为: ```javascript router.push('/news').then(() => { // 此时目标组件的onMounted已执行 console.log('跳转完成,可执行后续操作') }) ``` ### 3. 组件被<keep-alive>缓存 如果目标组件被`<keep-alive>`包裹,再次访问时触发的是`onActivated`而不是`onMounted`。 **解决方案**:使用`onActivated`钩子替代。 ```vue <script setup> import { onActivated } from 'vue' onActivated(() => { console.log('组件被激活(包括初次加载和缓存激活)') // 执行数据加载等操作 }) </script> ``` ### 4. 路径错误或路由守卫拦截 - **路径错误**:检查`router.push`的路径是否拼写错误(需与路由配置完全匹配)。 - **路由守卫未调用next()**:确保全局守卫(如`beforeEach`)中调用了`next()`。 ```javascript // 全局路由守卫示例 router.beforeEach((to, from, next) => { // 必须调用next()否则导航中断 next() }) ``` ### 5. 组件未正确注册/渲染 - **路由未配置**:确认目标路径已在路由表中注册。 - **缺失<router-view>**:确保父组件中有`<router-view>`渲染出口。 ### 总结排查步骤: 1. **检查是否为同一组件复用** → 监听路由参数或使用导航守卫。 2. **确认跳转完成时机** → 在`router.push().then()`中执行后续操作。 3. **查看是否使用keep-alive** → 改用`onActivated`钩子。 4. **验证路径和守卫** → 修正路径,检查守卫是否调用`next()`。 5. **审查路由配置** → 确保路由注册且`<router-view>`存在。 > 若仍不执行,可在目标组件中添加其他生命周期钩子(如`onBeforeMount`)进行调试[^3][^4]。 --- §§ 相关问题 §§ 1. Vue Router中如何监听路由参数变化? 2. `<keep-alive>`对Vue组件生命周期有何影响? 3. 如何正确处理Vue Router的异步导航? 4. 全局路由守卫`beforeEach`有哪些常见使用场景? 5. Vue 3的组合式API中如何正确使用路由钩子? </think> ### Vue中使用`router.push`跳转后`onMounted`不执行的解决方案 #### 1. **组件复用导致生命周期不触发** 当跳转前后的组件相同时(仅参数变化),Vue会**复用组件实例**而非重新创建,此时不会触发`onMounted`[^1][^2]。 **解决方法**: ```vue <script setup> import { watch } from 'vue' import { useRoute } from 'vue-router' const route = useRoute() // 监听参数变化执行逻辑 watch(() => route.query.id, (newId) => { console.log('参数变化:', newId) // 替代onMounted中的逻辑 }) </script> ``` #### 2. **异步跳转未等待完成** `router.push`是**异步操作**,直接跳转后立即访问新组件可能导致时机问题[^3]。 **解决方法**: ```javascript router.push('/news').then(() => { console.log('跳转完成') // 此时新组件已挂载 }) ``` #### 3. **组件被`<keep-alive>`缓存** 若目标组件被缓存,会触发`onActivated`而非`onMounted`[^4]。 **解决方法**: ```vue <script setup> import { onActivated } from 'vue' onActivated(() => { console.log('组件激活') // 替代onMounted }) </script> ``` #### 4. **路径错误或路由守卫拦截** - **路径错误**:检查跳转路径是否匹配路由配置 ```javascript router.push('/valid-path') // 确保路径正确 ``` - **路由守卫未调用next()**: ```javascript router.beforeEach((to, from, next) => { // 必须调用next() next() }) ``` #### 5. **组件未正确注册/渲染** - 检查路由配置: ```javascript const routes = [ { path: '/news', component: () => import('./News.vue') } // 确认组件路径正确 ] ``` - 确保父组件包含`<router-view>`: ```vue <template> <router-view></router-view> </template> ``` --- ### 排查流程图 ```mermaid graph TD A[onMounted不执行] --> B{是否同一组件?} B -->|是| C[监听路由参数变化] B -->|否| D{router.push后是否立即操作?} D -->|是| E[使用.then()等待跳转完成] D -->|否| F{是否使用keep-alive?} F -->|是| G[改用onActivated] F -->|否| H[检查路径/路由守卫/组件注册] ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值