Vue:Vue3-TypeScript-Pinia-Vite-pnpm / 基础项目 / 20240807

一、项目技术栈 / 依赖

序号技术栈版本解释
1node20.14.0
2vue

3.4.31

3vite

5.3.4

4TypeScript

5.2.2

5

@types/node

22.0.2

解决TypeScript项目中缺少对应模块的类型定义文件的问题
6

element-plus

2.7.8

ui组建
7

@types/js-cookie
js-cookie

3.0.6
3.0.5

8

sass

1.77.8

9

husky

8.0.0

10chalk

5.3.0

11axios1.7.3
12

vue-router

4.4.2

............

二、创建项目

2.1、创建项目

pnpm create vite

2.2、输入项目名字

2.3、选择TypeScript 

2.4、Enter/回车后 

2.5、项目创建成功-目录

2.6、安装依赖

pnpm install

2.7、启动项目

pnpm run dev

2.8、启动成功 

三、public目录下的文件可以直接访问

四、vite.config.ts 配置alias

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': resolve(__dirname, 'src'),
      '@assets': resolve(__dirname, 'src/assets')
    }
  }
})

@

@assets 

经测试,@、@assets 两个 alias 均使用成功。

五、Element-plus

pnpm add element-plus

安装后在src/main.ts引入并使用

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'

const app = createApp(App)
app.use(ElementPlus)
app.mount('#app')

 

 六、使用pinia

 nuxt3:使用pinia_nuxt3使用pinia-CSDN博客

七、使用cookie

pnpm add @types/js-cookie
pnpm add js-cookie
// 页面引入
import cookie from 'js-cookie'
<script setup lang="ts">
import cookie from 'js-cookie'
import HelloWorld from './components/HelloWorld.vue'

cookie.set('name', 'snow')
const name: string | undefined = cookie.get('name')
console.log('10name', name)
</script>

测试成功: 

八、使用 Tailwind CSS

Tailwind CSS:基础使用/vue3+ts+Tailwind_tailwind中文文档-CSDN博客

九、使用sass

9.1、安装sass

pnpm add sass

9.2、变量文件 assets/scss/variables.scss

// 颜色变量
$primary-color: #42b983; // 主题色
$text-color: #333; // 文本颜色
$border-color: #ddd; // 边框颜色

// 字体样式变量
$font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
$font-size-base: 16px;
$line-height-base: 1.5;

// 间距变量
$padding-base: 10px;
$margin-base: 20px;

// 布局变量
$container-max-widths: (
  sm: 540px,
  md: 720px,
  lg: 960px,
  xl: 1140px,
  xxl: 1320px
);

// 组件样式变量
$button-padding: 10px 15px;
$button-border-radius: 4px;
$button-background-color: $primary-color;

// 图标和图像变量
$logo-url: '/images/logo.png';

// 媒体查询断点
$screen-xs-min: 600px;
$screen-sm-min: 992px;
$screen-md-min: 1200px;
$screen-lg-min: 1920px;

9.3、vite.config.ts配置

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': resolve(__dirname, 'src'),
      '@assets': resolve(__dirname, 'src/assets')
    }
  },
  css: {
    // 这里的配置将应用于所有CSS/Sass/Less等预处理器文件
    preprocessorOptions: {
      scss: {
        additionalData: `@import "@/assets/scss/variables.scss";` // 全局引入变量
        // 或者你可以通过Vue的<style scoped src="...">来引入全局样式
        // 如果你不需要进行特别的Sass配置,这个对象可以留空
      }
    }
  }
})

9.4、App.vue文件使用全局变量,进行测试

<template>
    <div class="name">snow</div>
</template>

<style scoped lang="scss">
.name {
  color: $primary-color;
}
</style>

测试成功

十、使用ESLint

配置文件 - ESLint - 插件化的 JavaScript 代码检查工具

Configuration Migration Guide - ESLint - Pluggable JavaScript Linter

工程化-vue3+ts:代码检测工具 ESLint_expected to return a value at the end of arrow fun-CSDN博客

十一、使用husky / V.9.1.4  +  ESLint

配置文件 - ESLint - 插件化的 JavaScript 代码检查工具

vue3+ts:约定式提交(git husky + gitHooks)_vue husky hook-CSDN博客

项目中使用 Husky 可以帮助你自动地在 Git 钩子(如 pre-commit、pre-push 等)上执行一些脚本,比如代码格式化、linting、测试等,从而确保代码质量。 同时,如果你打算在提交前运行 ESLint 或 Prettier,也需要安装husky。

pnpm add husky
npx husky-init

.husky/pre-commit 文件,增加 pnpm run eslint:fix,这样在提交代码前会先进行ESLint检查。

十二、使用router

12.1、安装router

pnpm add vue-router

12.2、在src目录下创建目录和文件routers/index.ts

// 引入创建路由管理器 引入创建路由模式 history模式
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import Snow from '../views/snow/index.vue';

// 引入路由各页面配置
const routes:Array<RouteRecordRaw> = [
  {
    path: '/',
    redirect: '/snow'
  },
  {
    path: '/index',
    redirect: '/snow'
  },
  {
    path: '/snow',
    name: 'snow',
    component: Snow
  },
  {
    path: '/star',
    component: ()=>import('../views/star/index.vue')
  },
]
// 创建路由管理器 模式和路由
const router = createRouter({
  history: createWebHistory(),
  routes
})

export default router

12.3、src/views/snow/index.vue

<template>
  <div class="container">
    <!-- 水平、垂直 居中 -->
    <!-- <div class="flex">
      <div class="flex_item"></div>
    </div> -->

    <div class="flex">
      <div class="flex_item">1</div>
      <div class="flex_item">2</div>
      <div class="flex_item">3</div>
      <div class="flex_item">4</div>
      <div class="flex_item">5</div>
      <div class="flex_item">6</div>
      <div class="flex_item">7</div>
    </div>
  </div>
</template>

<script setup lang="ts">
import cookie from 'js-cookie'
const name:string | undefined = cookie.get('name');
console.log('10name', name)
</script>

<style scoped lang="scss">
.container{
  // .flex{
  //   display: flex;
  //   justify-content: center; // 水平居中
  //   align-items: center; // 垂直居中
  //   width: 200px;
  //   height: 200px;
  //   background: #ff0000;
  //   &_item{
  //     width: 50px;
  //     height: 50px;
  //     background: #b3de1b;
  //   }
  // }
  .flex{
    display: flex;
    width: 200px;
    height: 200px;
    background: #ff0000;
    &_item{
      width: 50px;
      height: 50px;
      background: #b3de1b;
      flex-shrink: 0; // 表示Flex项目在空间不足时的缩小比例。flex-shrink的默认值为1,数值越大,缩小比例越多,设置为 0 不缩放 。
    }
  }
}
</style>

12.4、src/main.ts

12.5、src/App.vue

<template>
  <router-view></router-view>
</template>
<script setup lang="ts">
</script>
<style scoped>
</style>

12.6、测试成功

十三、本地代理,配置proxy,配置多环境 / env

vue3+vite:本地代理,配置proxy_vue3代理服务器proxy配置-CSDN博客

13.1、vite.config.ts

import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': resolve(__dirname, 'src'),
      '@assets': resolve(__dirname, 'src/assets')
    }
  },
  // 开发服务器配置
  server: {
    host: '0.0.0.0', // 允许本机
    port: 3022, // 设置端口
    open: false, // 设置服务启动时自动打开浏览器
    hmr: true, // 开启热更新
    // cors: true, // 允许跨域
    // 请求代理
    proxy: {
      '/m-staff-center': { // 匹配请求路径,localhost:3000/m-staff-center,如果只是匹配/那么就访问到网站首页了
          target: loadEnv(process.argv[process.argv.length-1], './env').VITE_SERVER_NAME, // 代理的目标地址
          changeOrigin: true, // 开发模式,默认的origin是真实的 origin:localhost:3000 代理服务会把origin修改为目标地址
      }
    }
  },
  css: {
    // 这里的配置将应用于所有CSS/Sass/Less等预处理器文件
    preprocessorOptions: {
      scss: {
        additionalData: `@import "@/assets/scss/variables.scss";` // 全局引入变量
        // 或者你可以通过Vue的<style scoped src="...">来引入全局样式
        // 如果你不需要进行特别的Sass配置,这个对象可以留空
      }
    }
  },
})

13.2、env文件 

env/env.dev 其他同理

# 请求接口地址
VITE_REQUEST_BASE_URL = '/m-abc-center/api/v1'
VITE_SERVER_NAME = 'https://md.abc.com.cn/'
# VITE开头的变量才会被暴露出去

env/index.d.ts

/** 扩展环境变量import.meta.env */
interface ImportMetaEnv {
    VITE_REQUEST_BASE_URL: string,
    VITE_SERVER_NAME: string
}

13.3、package.json/script配置

十四、 封装axios请求

14.1、安装axios

pnpm add axios

14.2、目录结构

14.3、src/api/http/axios.ts

import instance from "./index"
/**
 * @param {String} method  请求的方法:get、post、delete、put
 * @param {String} url     请求的url:
 * @param {Object} data    请求的参数
 * @param {Object} config  请求的配置
 * @returns {Promise}     返回一个promise对象,其实就相当于axios请求数据的返回值
 */
const axios = async ({
    method,
    url,
    data,
    config
}: any): Promise<any> => {
    method = method.toLowerCase();
    if (method == 'post') {
        return instance.post(url, data, { ...config })
    } else if (method == 'get') {
        return instance.get(url, {
            params: data,
            ...config
        })
    } else if (method == 'delete') {
        return instance.delete(url, {
            params: data,
            ...config
        })
    } else if (method == 'put') {
        return instance.put(url, data, { ...config })
    } else {
        console.error('未知的method' + method)
        return false
    }
}
export {
    axios
}

14.4、src/api/http/index.ts

    import axios from 'axios'
    import cookie from 'js-cookie'
    //创建axios的一个实例 
    const instance = axios.create({
        // baseURL: import.meta.env.VITE_RES_URL, //接口统一域名
        timeout: 6000, //设置超时
        headers: {
            'Content-Type': 'application/json;charset=UTF-8;',
        }
    })
    //请求拦截器 
    instance.interceptors.request.use((config: any) => {
        // 每次发送请求之前判断是否存在token,如果存在,则统一在http请求的header都加上token,不用每次请求都手动添加了
        const token = `Bearer ${cookie.get('token')}`
        console.log('16', token)
        token && (config.headers.Authorization = token)
        //若请求方式为post,则将data参数转为JSON字符串
        if (config.method === 'post') {
            config.data = JSON.stringify(config.data);
        } else if(config.method === 'get'){
            console.log('21', config)
        }
        return config;
    }, (error: any) =>
        // 对请求错误做些什么
        Promise.reject(error));

    //响应拦截器
    instance.interceptors.response.use((response: any) => {
        //响应成功
        console.log('响应成功');
        return response.data;
    }, (error: any) => {
        console.log(error)
        //响应错误
        if (error.response && error.response.status) {
            const status = error.response.status
            console.log(status);
            return Promise.reject(error);
        }
        return Promise.reject(error);
    });
    export default instance;

14.5、 src/api/modules/m-abc-center.ts

import { axios } from "../http/axios"
import * as T from '../types/types'

export const getUser = (data: T.userParams) => {
    return axios({
        method: "get",
        url: "/m-abc-center/api/v1/abc/abc",
        data,
        config: {
            timeout: 10000
        }
    })
}

14.6、src/api/types/types.ts

export interface userParams {
    keyword: string
}

14.7、页面使用及测试

<template>
  <div class="container">
  </div>
</template>

<script setup lang="ts">
import { getUser } from "@/api/modules/m-abc-center.ts";
let params = {
  keyword:"snow",
}
getUser(params).then((res: any)=>{
  console.log('4res', res)
})
</script>

<style scoped lang="scss">
</style>

14.8、测试成功

十五、接口挂载到全局属性上

15.1、 src/api/modules/m-abc-center.ts

export default ({axios}:any) => ({
    getUser(data: T.userParams) {
        return axios({
            url: '',
            data,
            method: "get"
        })
    },
    getUser2(data: T.userParams) {
        return axios({
            url: '',
            data,
            method: "get"
        })
    },
})

15.2、src/api/index.ts

import { axios } from './http/axios'
const files:any = import.meta.glob("./modules/*.ts", {eager: true}) // 导入文件
const api:any = {}
const apiGenerators:any = [] // modules目录下文件内容的数组,每一个文件是一个{}
for (const key in files) {
  if (Object.prototype.hasOwnProperty.call(files, key)) {
    apiGenerators.push(files[key].default || files[key])
  }
}
apiGenerators.forEach((generator:any) => {
  const apiInstance = generator({ // 创建axios实例
    axios
  })
  for (const apiName in apiInstance) {
    if (apiInstance.hasOwnProperty(apiName)) { // 通过名称找到定义的接口地址
      api[apiName] = apiInstance[apiName]
    }
  }
})

export { api }

15.3、src/main.ts

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import router from "./routers/index"
import { api } from './api/index'

const app = createApp(App)
app.use(ElementPlus)
app.use(router)
app.config.globalProperties.$api = api
app.mount('#app')

15.4、页面使用及测试

<template>
  <div class="container">
  </div>
</template>

<script setup lang="ts">
let params = {
  keyword:"snow",
}
import { getCurrentInstance } from 'vue'
let internalInstance = getCurrentInstance();
let Api = internalInstance && internalInstance.appContext.config.globalProperties.$api
Api.getUser(params).then((res: any)=>{
  console.log('17res', res)
})
</script>

<style scoped lang="scss">
</style>

测试成功 

十六、UnoCSS / 原子化CSS

Unocss(原子化css) 使用(vue3 + vite + ts)-CSDN博客

即时按需:UnoCSS的加载和渲染速度非常快,可以立即进行使用。它不需要预先编译,使得样式可以在需要时动态地生成。

原子级CSS:UnoCSS使用原子级CSS样式的概念,即通过将样式属性定义为独立的类来构建页面。每个类都只包含一项或几项样式属性,可以在组件中灵活地组合和应用这些类,以实现细粒度的样式控制。

轻量化和高性能:UnoCSS通过仅传递实际使用的样式属性,减小生成的CSS文件的体积,从而优化页面的加载速度,并减少不必要的网络传输和运行时的样式计算。

十七、vue-global-api

17.1、vue-global-api - npm-网址

17.2、使用前

<script setup>
import { ref, computed, watch } from 'vue'

const counter = ref(0)

const doubled = computed(() => counter.value * 2)

watch(doubled, (v) => {
  console.log('New value: ' + v)
})
</script>

17.3、使用后

<script setup>
const counter = ref(0)

const doubled = computed(() => counter.value * 2)

watch(doubled, (v) => {
  console.log('New value: ' + v)
})
</script>

十八、layout 布局

vue3 + ts: layout布局_vue3 layout-CSDN博客

十九、directives

vue3:自定义指令_vue3自定义指令-CSDN博客

二十、vue3 - ts - lerna

架构-单一代码库-monorepo-lerna(8.0.0):lerna-pnpm-vue3-vite-ts 实践 / 用于管理包含多个软件包(package)的 JavaScript 项目_lerna8-CSDN博客

二十一、rollup 插件

vue3-vite-ts:编写Rollup插件并使用 / 优化构建过程_vite 中 rollup-plugin-dts-CSDN博客

二十二、vue3 + three.js

WebGL-Vue3-TS-Threejs:基础练习 / Javascript 3D library / demo-CSDN博客

二十三、多语言

vue3-ts-vite:Google 多语言调试 / 网页中插入谷歌翻译元素 / 翻译_google-translate-element-CSDN博客

二十四、storybook

vue3-ts-storybook:理解storybook、实践 / 前端组件库-CSDN博客

二十五、多页面应用

vue3-ts-vite:vue 项目 配置 多页面应用_vite多页面配置-CSDN博客

二十六、TinyMCE

vue3.3-TinyMCE:TinyMCE富文本编辑器基础使用_tinymce中文文档-CSDN博客

二十七、shims-vue.d.ts

vue3+ts:shims-vue.d.ts-CSDN博客

二十八、keepalive

vue3-ts:keepalive 列表页到详情,详情回到列表,列表恢复离开时的状态_vue3 从列表到详情-CSDN博客

二十九、qiankun

微前端-qiankun:vue3-vite 接入 vue3、nuxt3、vue2、nuxt2等子应用_qiankun.js-CSDN博客

三十、生命周期

vue3:生命周期(onErrorCaptured)-CSDN博客

三十一、vue2与vue3的区别

vue:vue2与vue3的区别_vue版本-CSDN博客

三十二、uuid

vue3 + ts:使用uuid_vue3 uuid-CSDN博客

三十三、watch

vue3 + ts:watch(immediate、deep、$watch)_vue3 watch immediate-CSDN博客

三十四、slot

vue:匿名slot、具名slot、作用域slot(技术栈Vue3 + TS)_具名插槽-CSDN博客

三十五、prettier 

vue3-ts:husky + prettier / 代码格式化工具-CSDN博客

三十六、过程记录

记录一、找不到模块“path”或其相应的类型声明

报错信息“找不到模块‘path’或其相应的类型声明”通常意味着你的TypeScript项目中缺少对应模块的类型定义文件。

解决:

pnpm add @types/node

安装后问题解决

记录二、Module '"c:/gkht/project/m-basic-vue3-pc/m-basic-vue3-pc/src/components/HelloWorld.vue"' has no default export.Vetur(1192)

新项目有一条红色的波浪线,很难受 

解决:

根据提示信息可以知道是Vetur的提示信息。

查找资料后,

viscode插件 Vetur(v0.35.0)不支持最新写法 卸载 并 安装 Volar 插件,使用Volar插件代替Vetur

但是,本文时间20240802 Volar被弃用了,改用了  Vue - Official

问题得到解决 

记录三、lint-staged干啥的

lint-staged是一个在git暂存文件上运行linters的工具,‌它的主要作用是提高代码质量和开发效率。‌lint-staged通过自动化代码检查过程,‌可以显著提升开发效率并维护代码一致性。‌这个工具特别适用于前端开发,‌因为它能够过滤出Git代码暂存区的文件,‌只对这些文件进行lint检查,‌避免了全量文件的检查,‌从而提高了性能并减少了误操作的可能性。‌此外,‌lint-staged的使用可以整合到开发流程中,‌为开发者提供更流畅、‌更可靠的编码体验

记录四、报错 error Parsing error: Unexpected token :

使用 ESLint 9.0 时遇到“Parsing error: Unexpected token :”这类错误,通常是因为 ESLint 配置不正确或者没有正确解析 TypeScript 文件。

确保已经安装了 @typescript-eslint/parser 和 @typescript-eslint/eslint-plugin。这两个包是 ESLint 对 TypeScript 支持的核心。

记录五、TypeError: (intermediate value).globEager is not a function

由 import.meta.globEager(参数)

改为 import.meta.glob(参数, {eager: true})

记录六、理解 import.meta.glob

import.meta.glob 是一个在 Vite、Snowpack 等现代前端构建工具中提供的特殊功能,它允许你基于 Glob 模式动态地导入多个模块。这个功能特别有用,当你需要基于文件系统的结构来动态地加载多个模块时,比如在一个 Vue、React 或其他前端框架的应用中,你可能需要导入一个目录下所有的组件或模块。 

import.meta.glob 返回一个对象,其键是匹配到的文件路径(相对于当前文件),值是动态导入的模块。但是,默认情况下,这些模块是懒加载的,即它们只会在被实际请求时才会被加载。

当你将 {eager: true} 作为第二个参数传递给 import.meta.glob 时,它会改变这个行为,使得所有匹配的模块在调用 import.meta.glob 时立即被加载(即非懒加载)。

// 假设你有一个目录 `./components/`,里面包含了多个 Vue 组件  
// 你想要在应用启动时立即加载这些组件  
  
// 使用 Vite 或支持 import.meta.glob 的构建工具  
const modules = import.meta.glob('./components/*.vue', { eager: true });  
  
// modules 现在是一个对象,其键是组件文件的路径,值是已解析的模块  
// 你可以遍历这个对象来访问这些组件  
for (const path in modules) {  
  // 注意:由于模块是立即加载的,你可以直接访问 modules[path] 来获取模块导出的内容  
  // 例如,如果组件默认导出了一个 Vue 组件,你可以这样做:  
  const component = modules[path].default;  
  // 现在你可以将 component 用作 Vue 组件或其他用途  
}  
  
// 但是,请注意,由于所有组件都是立即加载的,这可能会影响你的应用的初始加载时间  
// 因此,请谨慎使用 {eager: true}

记录七、files[key].default为什么有的是命名导出有的是default

 解答

// 正确的默认导出  
export default function myFunction() {  
    // ...  
}  

// 只有命名导出  
export function myNamedFunction() {  
    // ...  
}

记录八、Cannot find module '@/api/modules/m-abc-center.ts'

解决:增加一个声明

src/vite-env.d.ts

declare module "@/api/modules/m-abc-center.ts"

记录九、vite-env.d.ts的作用是什么

vite-env.d.ts(或有时简称为env.d.ts)是一个TypeScript声明文件,它在Vite项目中扮演着重要的角色。以下是vite-env.d.ts的主要作用:

序号作用解释
1声明环境变量类型vite-env.d.ts`文件通常用于声明项目中使用的环境变量类型。在Vite项目中,环境变量是在构建过程中进行动态替换的,而`vite-env.d.ts`则为这些环境变量提供了类型定义,使得TypeScript能够理解这些变量的类型,并在代码中使用时提供类型检查和智能提示。
2提高代码安全性通过为环境变量提供明确的类型定义,vite-env.d.ts帮助开发者在编写代码时避免类型错误,从而提高代码的类型安全性和可维护性。例如,如果某个环境变量被声明为字符串类型,那么TypeScript就会在编译时检查该变量的使用是否符合字符串类型的规范。
3支持全局类型声明vite-env.d.ts文件还可以用于声明全局类型。在TypeScript中,全局类型声明通常用于定义那些在整个项目中都可用的类型,比如全局变量、全局函数等。通过在vite-env.d.ts中声明这些全局类型,开发者可以在项目的任何位置使用它们,而无需在每个文件中都进行重复声明。
4引入其他类型的声明文件vite-env.d.ts文件中,开发者还可以使用/// <reference types="..." />指令来引入其他类型声明文件。这有助于将多个类型声明文件组织在一起,使得项目的类型定义更加清晰和易于管理。

/// <reference types="vite/client" />  
  
interface ImportMetaEnv {  
  readonly VITE_API_URL: string;  
  // 其他环境变量...  
}  
  
interface ImportMeta {  
  readonly env: ImportMetaEnv;  
}

declare module "@/api/modules/m-abc-center.ts"

记录十、Vue 3项目中的.vue文件类型声明

declare module '*.vue' {  
  import type { DefineComponent } from 'vue'  
  const component: DefineComponent<{}, {}, any>  
  export default component  
}

在Vue 3与TypeScript结合使用时,由于.vue文件不是标准的TypeScript文件(它们包含模板、脚本和样式),因此TypeScript默认无法理解这些文件的结构。为了解决这个问题,我们需要通过声明文件来告诉TypeScript如何理解.vue文件中的组件。

三十七、欢迎交流指正

三十八、参考链接

从 vue 源码看问题 —— 你真的了解 Vue 全局 Api 吗?_vue-global-api-CSDN博客

Vue 开发必须知道的36个技巧(小结)_vue.js_脚本之家

https://juejin.cn/post/7354298118236864566

Documentation - ESLint - Pluggable JavaScript Linter

配置文件 - ESLint - 插件化的 JavaScript 代码检查工具

vue3+ts+vite集成eslint

vue3:vue3+vite+ts+pinia_vue3+ts+vite+pinia项目-CSDN博客

https://juejin.cn/post/7038597045644427295

  • 18
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值