欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

vue中使用雙token機(jī)制

 更新時(shí)間:2025年04月27日 08:29:26   作者:小天吶  
雙Token機(jī)制是一種增強(qiáng)身份驗(yàn)證安全性和提升用戶(hù)體驗(yàn)的技術(shù)方案,本文主要介紹了vue中使用雙token機(jī)制,具有一定的參考價(jià)值,感興趣的可以了解一下

一、什么是雙token機(jī)制?

雙 Token 機(jī)制是一種增強(qiáng)身份驗(yàn)證安全性(主)和提升用戶(hù)體驗(yàn)(次)的技術(shù)方案,常用于處理用戶(hù)登錄和會(huì)話管理,主要包含訪問(wèn)令牌(Access Token)和刷新令牌(Refresh Token)

二、基本概念

  • 訪問(wèn)令牌(Access Token):短token
    • 這是用于訪問(wèn)受保護(hù)資源的短期令牌,通常有效期較短,比如 15 分鐘到 1 小時(shí)不等。
    • 它包含了用戶(hù)的身份信息和權(quán)限聲明,服務(wù)器通過(guò)驗(yàn)證該令牌來(lái)確認(rèn)用戶(hù)是否有權(quán)限訪問(wèn)特定資源。
    • 由于有效期短,即使被竊取,攻擊者利用它進(jìn)行惡意操作的時(shí)間窗口也有限。
  • 刷新令牌(Refresh Token):長(zhǎng)token
    • 刷新令牌的有效期較長(zhǎng),可能是幾天甚至幾周。
    • 作用是在訪問(wèn)令牌過(guò)期后,用于獲取新的訪問(wèn)令牌,而無(wú)需用戶(hù)重新登錄。
    • 刷新令牌通常存儲(chǔ)在更安全的位置,如 HTTP - Only Cookie 中,以降低被竊取的風(fēng)險(xiǎn)。

三、工作流程

  • 用戶(hù)登錄:用戶(hù)在客戶(hù)端輸入用戶(hù)名和密碼等憑據(jù)進(jìn)行登錄。服務(wù)器驗(yàn)證這些憑據(jù),若驗(yàn)證通過(guò),會(huì)生成一個(gè)訪問(wèn)令牌和一個(gè)刷新令牌,并將它們返回給客戶(hù)端。
  • 訪問(wèn)資源:客戶(hù)端在后續(xù)請(qǐng)求中攜帶訪問(wèn)令牌,服務(wù)器驗(yàn)證該令牌的有效性。如果令牌有效,服務(wù)器處理請(qǐng)求并返回響應(yīng)。
  • 訪問(wèn)令牌過(guò)期:當(dāng)訪問(wèn)令牌過(guò)期后,客戶(hù)端在下次請(qǐng)求時(shí)會(huì)收到服務(wù)器返回的 “令牌過(guò)期” 錯(cuò)誤。
  • 刷新令牌:客戶(hù)端使用刷新令牌向服務(wù)器發(fā)起刷新請(qǐng)求。服務(wù)器驗(yàn)證刷新令牌的有效性,如果有效,會(huì)生成一個(gè)新的訪問(wèn)令牌,并返回給客戶(hù)端。
  • 新的訪問(wèn)資源:客戶(hù)端使用新的訪問(wèn)令牌繼續(xù)訪問(wèn)受保護(hù)資源。
  • 刷新令牌過(guò)期:當(dāng)刷新令牌也過(guò)期時(shí),用戶(hù)需要重新登錄以獲取新的訪問(wèn)令牌和刷新令牌。

因?yàn)?access_token 如果有效期太短,用戶(hù)就需要頻繁地進(jìn)行身份驗(yàn)證,用戶(hù)體驗(yàn)差。設(shè)置得太長(zhǎng)呢,一旦 access token 被獲取之后被冒用的可能性大。所以使用 refresh token 就可以把a(bǔ)ccess token 的有效期縮短,在提高安全性的同時(shí)還保證了用戶(hù)體驗(yàn)。

四、具體實(shí)現(xiàn)

這里前端采用 vue3 + axios,后端采用 nest.js 實(shí)現(xiàn)

1. 創(chuàng)建后端登錄接口

要求:登錄成功返回兩個(gè) token,一個(gè)用于刷新 token(refresh token),一個(gè)用于訪問(wèn) token(access token)。

1.1 創(chuàng)建 access、refresh token 模塊

Config 模塊用來(lái)讀取 .env 文件配置

// src/module/jwt-access.module.ts
import { Module } from '@nestjs/common'
import { JwtModule, JwtService } from '@nestjs/jwt'
import { ConfigModule, ConfigService } from '@nestjs/config'
import {
  JWT_ACCESS_EXPIRES_IN,
  JWT_ACCESS_SECRET
} from '../common/constant/env'

@Module({
  imports: [
    JwtModule.registerAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: async (configService: ConfigService) => ({
        secret: configService.get<string>(JWT_ACCESS_SECRET),
        signOptions: {
          expiresIn: configService.get<string>(JWT_ACCESS_EXPIRES_IN)
        }
      })
    })
  ],
  providers: [
    {
      // 創(chuàng)建一個(gè)別名,方便在 auth.service.ts 中注入
      provide: 'JWT_ACCESS',
      useExisting: JwtService
    }
  ],
  exports: [JwtModule, 'JWT_ACCESS']
})
export class JwtAccessModule {}
// src/module/jwt-refresh.module.ts
import { Module } from '@nestjs/common'
import { JwtModule, JwtService } from '@nestjs/jwt'
import { ConfigModule, ConfigService } from '@nestjs/config'
import {
  JWT_REFRESH_EXPIRES_IN,
  JWT_REFRESH_SECRET
} from '../common/constant/env'

@Module({
  imports: [
    JwtModule.registerAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: async (configService: ConfigService) => ({
        secret: configService.get<string>(JWT_REFRESH_SECRET),
        signOptions: {
          expiresIn: configService.get<string>(JWT_REFRESH_EXPIRES_IN)
        }
      })
    })
  ],
  providers: [
    {
      provide: 'JWT_REFRESH',
      useExisting: JwtService
    }
  ],
  exports: [JwtModule, 'JWT_REFRESH']
})
export class JwtRefreshModule {}
# .env
DB_TYPE=mysql
DB_DATABASE=code-blocks-DB
JWT_ACCESS_SECRET=code-blocks-server-access-secret
JWT_ACCESS_EXPIRES_IN=30m
JWT_REFRESH_SECRET=code-blocks-server-refresh-secret
JWT_REFRESH_EXPIRES_IN=7d

1.2 在 auth.module.ts 中注入兩個(gè)模塊

// src/module/auth.module.ts
import { Module } from '@nestjs/common'
import { AuthController } from '../controllers/auth.controller'
import { AuthService } from '../services/auth.service'
import { TypeOrmModule } from '@nestjs/typeorm'
import { User } from '../entities/user.entity'
import { JwtAccessModule } from './jwt-access.module'
import { JwtRefreshModule } from './jwt-refresh.module'

@Module({
  imports: [
    TypeOrmModule.forFeature([User]),
    JwtAccessModule,
    JwtRefreshModule
  ],
  controllers: [AuthController],
  providers: [AuthService]
})
export class AuthModule {}

1.3 創(chuàng)建兩個(gè) jwt 的校驗(yàn)策略

這一步是結(jié)合后續(xù)創(chuàng)建的管道,來(lái)對(duì)接口的請(qǐng)求進(jìn)行校驗(yàn),例如:@UseGuards(JwtRefreshGuard)

在 PassportStrategy 第二個(gè)參數(shù)傳入 name,為了后續(xù) guard 中進(jìn)行區(qū)分

jwtFromRequest 來(lái)判斷 jwt 從什么地方獲?。?/p>

  • 從請(qǐng)求頭中 Authorization 獲取 :jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
  • 自定義:從請(qǐng)求頭中獲取自定義的 Refresh-Token 字段
// src/strategy/jwt-access.strategy.ts
import { ExtractJwt, Strategy } from 'passport-jwt'
import { PassportStrategy } from '@nestjs/passport'
import { Injectable } from '@nestjs/common'
import { ConfigService } from '@nestjs/config'
import { JWT_ACCESS_SECRET } from '../common/constant/env'
import { InjectRepository } from '@nestjs/typeorm'
import { User } from '../entities/user.entity'
import { Repository } from 'typeorm'

@Injectable()
export class JwtAccessStrategy extends PassportStrategy(
  Strategy,
  'jwt-access'
) {
  constructor(
    protected configService: ConfigService,
    @InjectRepository(User)
    private usersRepository: Repository<User>
  ) {
    super({
      // 從請(qǐng)求頭中獲取token
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: configService.get<string>(JWT_ACCESS_SECRET)
    })
  }

  // 對(duì)token進(jìn)行校驗(yàn),會(huì)在req.user上添加信息
  async validate(payload: { userId: string; phone: string }) {
    const user_info = await this.usersRepository.findOne({
      where: { id: payload.userId },
      select: ['id', 'phone', 'is_status']
    })
    if (!user_info) return false
    if (!user_info.is_status) return false
    if (user_info.phone !== payload.phone && user_info.id !== payload.userId)
      return false
    return { userId: payload.userId, phone: payload.phone }
  }
}
// src/strategy/jwt-refresh.strategy.ts
import { Strategy } from 'passport-jwt'
import { PassportStrategy } from '@nestjs/passport'
import { Injectable, UnauthorizedException } from '@nestjs/common'
import { ConfigService } from '@nestjs/config'
import { JWT_REFRESH_SECRET } from '../common/constant/env'
import { InjectRepository } from '@nestjs/typeorm'
import { User } from '../entities/user.entity'
import { Repository } from 'typeorm'

@Injectable()
export class JwtRefreshStrategy extends PassportStrategy(
  Strategy,
  'jwt-refresh'
) {
  constructor(
    protected configService: ConfigService,
    @InjectRepository(User)
    private usersRepository: Repository<User>
  ) {
    super({
      // 從請(qǐng)求頭中獲取自定義的 Refresh-Token 字段
      jwtFromRequest: (req: Request) => {
        const refreshToken = req.headers['refresh-token']
        if (!refreshToken) {
          throw new UnauthorizedException('Refresh token is required')
        }
        return refreshToken
      },
      ignoreExpiration: false,
      secretOrKey: configService.get<string>(JWT_REFRESH_SECRET)
    })
  }

  // 對(duì)token進(jìn)行校驗(yàn),會(huì)在req.user上添加信息
  async validate(payload: { userId: string; phone: string }) {
    const user_info = await this.usersRepository.findOne({
      where: { id: payload.userId },
      select: ['id', 'phone', 'is_status']
    })
    if (!user_info) return false
    if (!user_info.is_status) return false
    if (user_info.id !== payload.userId) return false
    return { userId: payload.userId, phone: user_info.phone }
  }
}

1.4 全局注冊(cè)策略(Strategy)

nest 通過(guò)依賴(lài)注入的方式來(lái)實(shí)現(xiàn)模塊之間的使用,也可以局部注冊(cè)。只有注冊(cè)后,全局的守衛(wèi)才會(huì)生效。

// src/app.module.ts
import { Global, Logger, Module } from '@nestjs/common'
// 不同模塊
import { AuthModule } from './modules/auth.module'
import { EditModule } from './modules/edit.module'
import { UserModule } from './modules/user.module'
import { TypeOrmConfigModule } from './config/typeorm.module'
import { LogConfigModule } from './config/log.module'
import { ENV_Config_Module } from './config/config.module'
import { JwtAccessStrategy } from './strategy/jwt-access.strategy'
import { TypeOrmModule } from '@nestjs/typeorm'
import { User } from './entities/user.entity'
import { JwtRefreshStrategy } from './strategy/jwt-refresh.strategy'

@Global()
@Module({
  imports: [
    ENV_Config_Module,
    TypeOrmConfigModule,
    TypeOrmModule.forFeature([User]),
    LogConfigModule,
    AuthModule,
    EditModule,
    UserModule
  ],
  controllers: [],
  // 全局提供logger,從@nestjs/common進(jìn)行導(dǎo)入。因?yàn)樵趍ain.ts中重構(gòu)官方的logger實(shí)例
  // JwtAccessStrategy、JwtRefreshStrategy 策略使其在全局都能使用
  providers: [Logger, JwtAccessStrategy, JwtRefreshStrategy],
  exports: [Logger]
})
export class AppModule {}

1.5 創(chuàng)建兩個(gè) jwt 的守衛(wèi)

實(shí)現(xiàn)接口注解,@UseGuards(JwtAccessGuard) 和 @UseGuards(JwtRefreshGuard) 來(lái)實(shí)現(xiàn)統(tǒng)一校驗(yàn)

例如:

// xxx.controller.ts
// 全局接口
@Controller('edit')
@UseGuards(JwtAccessGuard)
export class EditController {}

// 單獨(dú)接口
@Get('refresh-token')
@UseGuards(JwtRefreshGuard)
async refreshToken(@Headers('Refresh-Token') refreshToken: string) {}

實(shí)現(xiàn):

// src/guard/jwt-access.guard.ts
import { AuthGuard } from '@nestjs/passport'

/**
 * AuthGuard 默認(rèn)為 jwt,也可以在 strategy/jwt.strategy.ts 中修改為其他策略
 * JwtAccessStrategy 繼承的 PassportStrategy,在 PassportStrategy 第二個(gè)參數(shù)就是 name 值
 * */
export class JwtAccessGuard extends AuthGuard('jwt-access') {
  constructor() {
    super()
  }
}
// src/guard/jwt-refresh.guard.ts
import { AuthGuard } from '@nestjs/passport'

export class JwtRefreshGuard extends AuthGuard('jwt-refresh') {
  constructor() {
    super()
  }
}

1.6 實(shí)現(xiàn)刷新 token 接口

刷新 token 接口有兩種思路:

  • 接口返回兩個(gè) token,這樣后續(xù)就可以保證這個(gè) 長(zhǎng) token(refresh token)永遠(yuǎn)不會(huì)過(guò)期。
  • 接口只返回短 token,長(zhǎng) token 會(huì)過(guò)期,例如 7 天后過(guò)期用戶(hù)也會(huì)重新登錄。(這里采用這種方式)

token 存儲(chǔ)方式也有幾種:自行選擇

  • cookie(refresh token) + localStorage(access token)
  • localStorage(refresh token + access token)
// src/controller/auth.controller.ts
/**
 * 刷新token接口
 * @headers headers['Refresh-Token'] 刷新token
 */
@Get('refresh-token')
@UseGuards(JwtRefreshGuard)
    async refreshToken(@Headers('Refresh-Token') refreshToken: string) {
    const data = await this.authService.refreshToken(refreshToken);
    return {
        code: 200,
        message: '刷新token成功',
        data,
    };
}
// src/service/auth.service.ts
import {
  HttpException,
  HttpStatus,
  Inject,
  Injectable,
  NotFoundException
} from '@nestjs/common'
import { Repository } from 'typeorm'
import { InjectRepository } from '@nestjs/typeorm'
import { RegisterDto } from '../dto/user/register.dto'
import { JwtService } from '@nestjs/jwt'

@Injectable()
export class AuthService {
  constructor(
    @InjectRepository(User)
    private usersRepository: Repository<User>,
    // 使用 @Inject 手動(dòng)注入依賴(lài),通過(guò)在 1.1 中注入的內(nèi)容實(shí)現(xiàn)
    @Inject('JWT_ACCESS') private readonly jwtAccess: JwtService,
    @Inject('JWT_REFRESH') private readonly jwtRefresh: JwtService
  ) {}

  /**
   * 刷新token
   */
  async refreshToken(refreshToken: string): Promise<{ accessToken: string }> {
    const payload = this.jwtRefresh.decode(refreshToken)
    const user = await this.usersRepository.findOne({
      where: { id: payload.userId },
      select: ['id', 'phone']
    })
    if (!user) {
      throw new NotFoundException('用戶(hù)不存在')
    }
    const accessToken = await this.jwtAccess.signAsync({
      userId: user.id,
      phone: user.phone
    })
    return { accessToken }
  }
}

2. 前端登錄

2.1 新增刷新 token 接口

export const reqRefreshToken = () =>
  request<any, RefreshTokenResponse>({
    url: API.refreshToken,
    method: 'get',
    headers: { 'Refresh-Token': getRefreshToken() }
  })

2.2 配置請(qǐng)求響應(yīng)攔截器

需要注意的點(diǎn):

  • 在攜帶 access token 的接口,返回 401 時(shí),就需要發(fā)送 reqRefreshToken 來(lái)刷新 token
  • 在頁(yè)面多個(gè)并發(fā)請(qǐng)求時(shí),需要?jiǎng)?chuàng)建一個(gè)請(qǐng)求隊(duì)列,當(dāng) token 刷新后重新發(fā)送請(qǐng)求
import axios, { type AxiosRequestConfig } from 'axios'
import router from '@/router/index'
import { useUserStore } from '@/stores/user'
import { ElMessage } from 'element-plus'
import { baseUrl } from '@/common/baseUrl'
import { API as AuthAPI, reqRefreshToken } from './auth'

const user = useUserStore()

const request = axios.create({
  baseURL: baseUrl,
  timeout: 300000
})

request.interceptors.request.use(
  (config) => {
    const user = useUserStore()
    if (user.token) config.headers['Authorization'] = 'Bearer ' + user.token
    return config
  },
  (error) => {
    return Promise.reject(error)
  }
)

// 已經(jīng)處理過(guò)的錯(cuò)誤碼
const hasErrorCode = [401, 403]
// 在刷新token時(shí),如果頁(yè)面上有多個(gè)請(qǐng)求,當(dāng)token過(guò)期后,那這幾個(gè)請(qǐng)求都會(huì)觸發(fā) reqRefreshToken 來(lái)刷新token
/**
 * 1. 需要定義一個(gè)變量來(lái)標(biāo)記當(dāng)前是否刷新中,避免重復(fù)刷新token
 * 2. 創(chuàng)建一個(gè)請(qǐng)求隊(duì)列,當(dāng)刷新token成功后,需要把隊(duì)列中的請(qǐng)求重新發(fā)送
 */
let isRefreshing = false
interface PendingTask {
  config: AxiosRequestConfig
  resolve: (value?: any) => void
}
const requestQueue: PendingTask[] = []

request.interceptors.response.use(
  async (response) => {
    if (
      response.data?.code === 401 &&
      !response.config.url?.includes(AuthAPI.refreshToken)
    ) {
      if (!isRefreshing) {
        // 第一個(gè)觸發(fā) 401 的請(qǐng)求,刷新 token 并重新發(fā)送隊(duì)列中的請(qǐng)求
        isRefreshing = true
        const res = await reqRefreshToken()
        isRefreshing = false
        if (res.code === 200) {
          const accessToken = res.data.accessToken
          // 更新accessToken
          user.refresh(accessToken)
          // 重新請(qǐng)求
          requestQueue.forEach(({ config, resolve }) => {
            config.headers!['Authorization'] = 'Bearer ' + accessToken
            resolve(request(config))
          })
          requestQueue.length = 0
          /**
           * 如果同時(shí)多個(gè)請(qǐng)求,在其他幾個(gè)請(qǐng)求中,有一個(gè)先返回響應(yīng),先響應(yīng)的回執(zhí)行 requestQueue.forEach,
           * 此時(shí)這個(gè) requestQueue 沒(méi)有當(dāng)前請(qǐng)求,則需要返回當(dāng)前這個(gè)請(qǐng)求,重新去執(zhí)行
           * (返回一個(gè)Promise會(huì)直接去執(zhí)行)
           */
          return request(response.config)
        } else {
          // refreshToken過(guò)期
          user.clearInfo()
          router.replace('/login')
          ElMessage.error('登錄已過(guò)期,請(qǐng)重新登錄')
        }
      } else {
        // 當(dāng)前請(qǐng)求不是第一個(gè)觸發(fā) 401 的請(qǐng)求,則將當(dāng)前為401(token過(guò)期的請(qǐng)求)添加到請(qǐng)求隊(duì)列中
        return new Promise((resolve) => {
          requestQueue.push({
            config: response.config,
            resolve
          })
        })
      }
      return
    }

    if (response.data?.code === 403) {
      user.clearInfo()
      router.replace('/login')
      ElMessage.error('無(wú)權(quán)限')
      return
    }

    if (response.data?.code === 200) {
      response.data['status'] = true
    }

    if (
      !response.data['status'] &&
      !hasErrorCode.includes(response.data?.code)
    ) {
      ElMessage.error(response.data.message)
    }

    return response.data
  },
  (error) => {
    return Promise.reject(error)
  }
)

export default request

到此這篇關(guān)于vue中使用雙token機(jī)制的文章就介紹到這了,更多相關(guān)vue 雙token機(jī)制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 解決vue路由發(fā)生了跳轉(zhuǎn)但是界面沒(méi)有任何反應(yīng)問(wèn)題

    解決vue路由發(fā)生了跳轉(zhuǎn)但是界面沒(méi)有任何反應(yīng)問(wèn)題

    這篇文章主要介紹了解決vue路由發(fā)生了跳轉(zhuǎn)但是界面沒(méi)有任何反應(yīng)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-04-04
  • vue如何實(shí)現(xiàn)點(diǎn)擊選中取消切換

    vue如何實(shí)現(xiàn)點(diǎn)擊選中取消切換

    這篇文章主要介紹了vue實(shí)現(xiàn)點(diǎn)擊選中取消切換,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-05-05
  • vue+vuex+axios實(shí)現(xiàn)登錄、注冊(cè)頁(yè)權(quán)限攔截

    vue+vuex+axios實(shí)現(xiàn)登錄、注冊(cè)頁(yè)權(quán)限攔截

    下面小編就為大家分享一篇vue+vuex+axios實(shí)現(xiàn)登錄、注冊(cè)頁(yè)權(quán)限攔截,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2018-03-03
  • Vue條件判斷之循環(huán)舉例詳解

    Vue條件判斷之循環(huán)舉例詳解

    在Vue進(jìn)行前端開(kāi)發(fā)中,條件判斷主要用于根據(jù)不同的條件來(lái)決定顯示或隱藏,或者進(jìn)行視圖之間的切換,這篇文章主要給大家介紹了關(guān)于Vue條件判斷之循環(huán)舉例詳解的相關(guān)資料,需要的朋友可以參考下
    2024-07-07
  • Vue.js Ajax動(dòng)態(tài)參數(shù)與列表顯示實(shí)現(xiàn)方法

    Vue.js Ajax動(dòng)態(tài)參數(shù)與列表顯示實(shí)現(xiàn)方法

    Vue.js是一個(gè)輕巧、高性能、可組件化的MVVM庫(kù),同時(shí)擁有非常容易上手的API。下面通過(guò)本文給大家介紹vue.js ajax動(dòng)態(tài)參數(shù)與列表顯示實(shí)現(xiàn)方法,感興趣的朋友一起看看吧
    2016-10-10
  • vue為什么v-for的優(yōu)先級(jí)比v-if高原理解析

    vue為什么v-for的優(yōu)先級(jí)比v-if高原理解析

    這篇文章主要為大家介紹了vue為什么v-for的優(yōu)先級(jí)比v-if高原理解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-05-05
  • vue中sync語(yǔ)法糖的用法詳解

    vue中sync語(yǔ)法糖的用法詳解

    Vue的.sync語(yǔ)法糖是一個(gè)用于雙向數(shù)據(jù)綁定的指令,可以在子組件中用來(lái)監(jiān)聽(tīng)父組件傳遞下來(lái)的props的變化,本文給大家介紹了在Vue中,.sync語(yǔ)法糖的使用方法,感興趣的朋友跟著小編一起來(lái)學(xué)習(xí)吧
    2024-01-01
  • Vue對(duì)象的深層劫持詳細(xì)講解

    Vue對(duì)象的深層劫持詳細(xì)講解

    這篇文章主要介紹了vue2.x對(duì)象深層劫持的原理實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-01-01
  • vue項(xiàng)目如何實(shí)現(xiàn)前端預(yù)覽word與pdf格式文件

    vue項(xiàng)目如何實(shí)現(xiàn)前端預(yù)覽word與pdf格式文件

    最近項(xiàng)目中需要在線預(yù)覽WORD文檔,所以給大家總結(jié)下,這篇文章主要給大家介紹了關(guān)于vue項(xiàng)目如何實(shí)現(xiàn)前端預(yù)覽word與pdf格式文件的相關(guān)資料,需要的朋友可以參考下
    2023-03-03
  • Vue 實(shí)現(xiàn)列表動(dòng)態(tài)添加和刪除的兩種方法小結(jié)

    Vue 實(shí)現(xiàn)列表動(dòng)態(tài)添加和刪除的兩種方法小結(jié)

    今天小編就為大家分享一篇Vue 實(shí)現(xiàn)列表動(dòng)態(tài)添加和刪除的兩種方法小結(jié),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2018-09-09

最新評(píng)論