vue3的基础用法

# vue3Step

### 创建项目
create -vue 是vue官方新的脚手架工具 底层切换到vite (下一代构建工具) 为开发提高急速相应
vuecli 底层是webpack
create -vue 底层是vite

node.js 版本环境:16.0 以上
创建vue应用
npm init vue@latest
Need to install the following packages:
create-vue@3.10.4
Ok to proceed? (y) y

Vue.js - The Progressive JavaScript Framework

√ 请输入项目名称: ... vue3Step
√ 请输入包名称: ... vue3step
√ 是否使用 TypeScript 语法? ... 否 / 是
√ 是否启用 JSX 支持? ... 否 / 是
√ 是否引入 Vue Router 进行单页面应用开发? ... 否 / 是
√ 是否引入 Pinia 用于状态管理? ... 否 / 是
√ 是否引入 Vitest 用于单元测试? ... 否 / 是
√ 是否要引入一款端到端(End to End)测试工具? » 不需要
√ 是否引入 ESLint 用于代码质量检测? ... 否 / 是
√ 是否引入 Prettier 用于代码格式化? ... 否 / 是
√ 是否引入 Vue DevTools 7 扩展用于调试? (试验阶段) ... 否 / 是

项目初始化完成,可执行以下命令:

cd vue3Step
npm install
npm run dev

### 熟悉项目目录和关键文件

Vetur vue2的插件禁用 使用vue3插件 volar 已禁用 可使用 Vue - Official

<!-- 加上setup 容许在script中直接编写组合式api -->
<script setup>
import HelloWorld from './components/HelloWorld.vue'
import TheWelcome from './components/TheWelcome.vue'
</script>

<template>
<!-- 不在要求唯一根元素 -->
<header>
<img alt="Vue logo" class="logo" src="./assets/logo.svg" width="125" height="125" />

<div class="wrapper">
<HelloWorld msg="You did it!" />
</div>
</header>

<main>
<TheWelcome />
</main>
</template>
main.js里
// new Vue() 创建一个应用实例 => createApp()
// createRouter() createStore()
// 将创建实例进行了封装,保证每个实例的独立封闭性

import { createApp } from 'vue'
import App from './App.vue'

// mount 设置挂载点 #app (id为app的盒子)
createApp(App).mount('#app')

### vue3-setup选项组合式api
setup 比beforecreate早
原始版本
<!-- 加上setup 容许在script中直接编写组合式api -->
<script>
export default {
setup() {
const message = 'msg vue3'
const logmsg=()=>{
console.log('vue3')
}
console.log('setip')
return {
message,
logmsg

}

}

}

</script>
<template>
<div class="div">
{{message }}
</div>
<button @click="logmsg">按钮</button>
</template>

 

<style scoped>


</style>

现在版本:

<!-- 加上setup 容许在script中直接编写组合式api -->
<script setup>
const message = 'msg vue3'
const logmsg=()=>{
console.log('vue3')
}

</script>
<template>
<div class="div">
{{message }}
</div>
<button @click="logmsg">按钮</button>
</template>


### 组合式api -reactive和ref函数
reactive接收对象数据的参数传入 并返回一个响应式的对象

核心步骤
导入
import{reactive} from 'vue'
执行函数 传入参数 变量接收
cinst state = reactive(对象类型数据)

<script setup>
// 1. reactive: 接收一个对象类型的数据,返回一个响应式的对象
// 问题:如果是简单类型,怎么办呢?
// import { reactive } from 'vue'
// const state = reactive({
// count: 100
// })
// const setCount = () => {
// state.count++
// }

// 2. ref: 接收简单类型 或 复杂类型,返回一个响应式的对象
// 本质:是在原有传入数据的基础上,外层包了一层对象,包成了复杂类型
// 底层,包成复杂类型之后,再借助 reactive 实现的响应式
// 注意点:
// 1. 脚本中访问数据,需要通过 .value
// 2. 在template中,.value不需要加 (帮我们扒了一层)

// 推荐:以后声明数据,统一用 ref => 统一了编码规范
import { ref } from 'vue'
const count = ref(0)
const setCount = () => {
count.value++
}
</script>

<template>
<div>
<div>{{ count }}</div>
<button @click="setCount">+1</button>
</div>
</template>

### 组合式api -computed

<script setup>
// const 计算属性 = computed(() => {
// return 计算返回的结果
// })

import { computed, ref } from 'vue'

// 声明数据
const list = ref([1, 2, 3, 4, 5, 6, 7, 8])

// 基于list派生一个计算属性,从list中过滤出 > 2
const computedList = computed(() => {
return list.value.filter(item => item > 2)
})

// 定义一个修改数组的方法
const addFn = () => {
list.value.push(666)
}
</script>

<template>
<div>
<div>原始数据: {{ list }}</div>
<div>计算后的数据: {{ computedList }}</div>
<button @click="addFn" type="button">修改</button>
</div>
</template>

### 组合式api -watch
<script setup>
import { ref, watch } from 'vue'
const count = ref(0)
const nickname = ref('张三')

const changeCount = () => {
count.value++
}
const changeNickname = () => {
nickname.value = '李四'
}

// 1. 监视单个数据的变化
// watch(ref对象, (newValue, oldValue) => { ... })
// watch(count, (newValue, oldValue) => {
// console.log(newValue, oldValue)
// })

// 2. 监视多个数据的变化
// watch([ref对象1, ref对象2], (newArr, oldArr) => { ... })
// watch([count, nickname], (newArr, oldArr) => {
// console.log(newArr, oldArr)
// })

// 3. immediate 立刻执行
// watch(count, (newValue, oldValue) => {
// console.log(newValue, oldValue)
// }, {
// immediate: true
// })
// --------------------------------------------
// 4. deep 深度监视, 默认 watch 进行的是 浅层监视
// const ref1 = ref(简单类型) 可以直接监视
// const ref2 = ref(复杂类型) 监视不到复杂类型内部数据的变化
const userInfo = ref({
name: 'zs',
age: 18
})
const setUserInfo = () => {
// 修改了 userInfo.value 修改了对象的地址,才能监视到
// userInfo.value = { name: 'ls', age: 50 }
userInfo.value.age++
}

// deep 深度监视
// watch(userInfo, (newValue) => {
// console.log(newValue)
// }, {
// deep: true
// })

// 5. 对于对象中的单个属性,进行监视
watch(() => userInfo.value.age, (newValue, oldValue) => {
console.log(newValue, oldValue)
})
</script>

<template>
<div>{{ count }}</div>
<button @click="changeCount">改数字</button>
<div>{{ nickname }}</div>
<button @click="changeNickname">改昵称</button>
<div>-----------------------</div>
<div>{{ userInfo }}</div>
<button @click="setUserInfo">修改userInfo</button>
</template>

### 组合式API -生命周期函数

父页面
<script setup>
// 父传子
// 1. 给子组件,添加属性的方式传值
// 2. 在子组件,通过props接收

// 局部组件(导入进来就能用)
import { ref } from 'vue'
import SonCom from '@/components/son-com.vue'

const money = ref(100)
const getMoney = () => {
money.value += 10
}
</script>

<template>
<div>
<h3>
父组件 - {{ money }}
<button @click="getMoney">挣钱</button>
</h3>
<!-- 给子组件,添加属性的方式传值 -->
<SonCom car="宝马车" :money="money"></SonCom>
</div>
</template>

子页面
<script setup>
// 注意:由于写了 setup,所以无法直接配置 props 选项
// 所以:此处需要借助于 “编译器宏” 函数接收子组件传递的数据
const props = defineProps({
car: String,
money: Number
})
const emit = defineEmits(['changeMoney'])
console.log(props.car)
console.log(props.money)

const buy = () => {
// 需要 emit 触发事件
emit('changeMoney', 5)
}
</script>

<template>
<!-- 对于props传递过来的数据,模板中可以直接使用 -->
<div class="son">
我是子组件 - {{ car }} - {{ money }}
<button @click="buy">花钱</button>
</div>
</template>

<style scoped>
.son {
border: 1px solid #000;
padding: 30px;
}
</style>


子传父
<script setup>
// 父传子
// 1. 给子组件,添加属性的方式传值
// 2. 在子组件,通过props接收

// 子传父
// 1. 在子组件内部,emit触发事件 (编译器宏获取)
// 2. 在父组件,通过 @ 监听

// 局部组件(导入进来就能用)
import { ref } from 'vue'
import SonCom from '@/components/son-com.vue'

const money = ref(100)
const getMoney = () => {
money.value += 10
}
const changeFn = (newMoney) => {
money.value = newMoney
}
</script>

<template>
<div>
<h3>
父组件 - {{ money }}
<button @click="getMoney">挣钱</button>
</h3>
<!-- 给子组件,添加属性的方式传值 -->
<SonCom
@changeMoney="changeFn"
car="宝马车"
:money="money">
</SonCom>
</div>
</template>
子页面

<!-- 加上setup 容许在script中直接编写组合式api -->
<script setup>
// 注意:由于写了 setup,所以无法直接配置 props 选项
// 所以:此处需要借助于 “编译器宏” 函数接收子组件传递的数据

const props = defineProps({
car:String,
money:Number
})
const emit=defineEmits(['changeMoney'])
console.log(props.car)
console.log(props.money)
const changeBtn=()=>{
emit('changeMoney',5)
}


</script>
<template>
<div class="son"> 我是子组件
{{car }}==={{money }}
<button @click="changeBtn">花钱</button>
</div>

</template>

 


<style scoped>
.son{
border: 1px solid #000;
padding: 30px;
}
</style>


### vue3 模板引用和defaineExpose宏函数
父页面
<script setup>
import TestCom from '@/components/test-com.vue'
import { onMounted, ref } from 'vue'

// 模板引用(可以获取dom,也可以获取组件)
// 1. 调用ref函数,生成一个ref对象
// 2. 通过ref标识,进行绑定
// 3. 通过ref对象.value即可访问到绑定的元素(必须渲染完成后,才能拿到)
const inp = ref(null)

// 生命周期钩子 onMounted
onMounted(() => {
// console.log(inp.value)
// inp.value.focus()
})
const clickFn = () => {
inp.value.focus()
}

// --------------------------------------
const testRef = ref(null)
const getCom = () => {
console.log(testRef.value.count)
testRef.value.sayHi()
}
</script>

<template>
<div>
<input ref="inp" type="text">
<button @click="clickFn">点击让输入框聚焦</button>
</div>
<TestCom ref="testRef"></TestCom>
<button @click="getCom">获取组件</button>
</template>

子页面
<script setup>
const count = 999
const sayHi = () => {
console.log('打招呼')
}

defineExpose({
count,
sayHi
})
</script>

<template>
<div>
我是用于测试的组件 - {{ count }}
</div>
</template>


### 组合式api provide inject

顶层页面
<script setup>
import CenterCom from '@/components/center-com.vue'
import { provide, ref } from 'vue'

// 1. 跨层传递普通数据
provide('theme-color', 'pink')

// 2. 跨层传递响应式数据
const count = ref(100)
provide('count', count)

setTimeout(() => {
count.value = 500
}, 2000)

// 3. 跨层传递函数 => 给子孙后代传递可以修改数据的方法
provide('changeCount', (newCount) => {
count.value = newCount
})

</script>

<template>
<div>
<h1>我是顶层组件</h1>
<CenterCom></CenterCom>
</div>
</template>

 

中层页面
<script setup>
import BottomCom from './bottom-com.vue'
</script>

<template>
<div>
<h2>我是中间组件</h2>
<BottomCom></BottomCom>
</div>
</template>
底层页面
<script setup>
import { inject } from 'vue'
const themeColor = inject('theme-color')
const count = inject('count')
const changeCount = inject('changeCount')
const clickFn = () => {
changeCount(1000)
}
</script>

<template>
<div>
<h3>我是底层组件-{{ themeColor }} - {{ count }}</h3>
<button @click="clickFn">更新count</button>
</div>
</template>

### vue新特性 defineOptions

在多个页面中vue3 不支持index 所以
<script setup>
defineOptions({
name:'AppIndex'
})
</script>
### vue3 -defineModel

<!-- 加上setup 容许在script中直接编写组合式api -->
<script setup>

App 主页面:
import myInput from '@/components/my-input.vue'
// import { provide } from 'vue';
import {ref }from 'vue';
const txt= ref('123456')
</script>
<template>
<div class="div">
<myInput v-model="txt"></myInput>
{{txt }}

</div>
</template>
<style scoped>


</style>

引用页面:
<!-- 加上setup 容许在script中直接编写组合式api -->
<script setup>
defineOptions({
modelValue:String
})
const emit=defineEmits(['update:modelValue'])


</script>
<template>
<div class="div">
<input type="text"
:value="modelValue"
@input="e=>emit('update:modelValue',e.target.value)"
>

</div>

</template>
<style scoped>
</style>

使用更好的方式:
<!-- 加上setup 容许在script中直接编写组合式api -->
<!-- 加上setup 容许在script中直接编写组合式api -->
<script setup>
import { defineModel} from 'vue'
const modelValue = defineModel()
</script>
<template>
<div class="div">
<input type="text"
:value="modelValue"
@input="e=> modelValue=e.target.value"
>
</div>

</template>
<style scoped>
</style>


在vite。config。js

---- vue({
script:{
defineModel:true
}------
}),


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

// https://vitejs.dev/config/
export default defineConfig({
plugins: [
---- vue({
script:{
defineModel:true
}------
}),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})

并重新启动:
nnpm run dev

### 什么是pinia

安装
npm install pinia
在main.js里

import { createApp } from 'vue'
import { createPinia } from 'pinia'--
import App from './App.vue'

const pinia = createPinia()--
// mount 设置挂载点 #app (id为app的盒子)
createApp(App).use(pinia).mount('#app')--

pinia 基本用法

1.在sore的count.js中
import { defineStore } from 'pinia'
import { computed, ref } from 'vue'
// 定义store
// defineStore(仓库唯一标识,()=》{})
export const userCounterStore=defineStore('counter',()=>{
// 声明数据
const count = ref(100)
// 声明操作数据的方法action
const addCount =()=>{ count.value ++}
const subCount =()=>{ count.value--}
// 声明基于数据派生的计算属性 getters
const doublecount = computed(()=>count.value*2)

const msg=ref('hello')

return {
count,
msg,
addCount,
subCount,
doublecount
}

})

在app.vue页面中

<script setup>

import Son1Com from '@/components/Son1Com.vue'
import Son2Com from '@/components/Son2Com.vue'

 

import { userCounterStore } from '@/store/counter'
const counterStore = userCounterStore()
</script>
<template>
<div class="div">
<h3>
App.vue根组件-{{ counterStore.count}}=={{counterStore.msg}}
</h3>
<Son1Com></Son1Com>
<Son2Com></Son2Com>

</div>
</template>
<style scoped>


</style>

在其他son1.vue
<script setup>
import { userCounterStore } from '@/store/counter'
const counterStore = userCounterStore()

</script>
<template>
<div class="div">
<h3>
son1根组件-{{ counterStore.count}}=== {{ counterStore.doublecount }}
<button @click="counterStore.addCount">+</button>
</h3>
</div>
</template>
<style scoped>


</style>

与son2.vue中
<!-- 加上setup 容许在script中直接编写组合式api -->
<script setup>
import { userCounterStore } from '@/store/counter'
const counterStore = userCounterStore()

</script>
<template>
<div class="div">
<h3>
son2根组件-{{counterStore.count }}
<button @click="counterStore.subCount">-</button>
</h3>


</div>
</template>
<style scoped>


</style>

### action的用法

npm install axios --save-dev --legacy-peer-deps

在store中的channel.js中

import { defineStore } from 'pinia'
import { computed, ref } from 'vue'
import axios from 'axios'
// 定义store
// defineStore(仓库唯一标识,()=》{})
export const userChannelStore=defineStore('channel',()=>{
// 声明数据
const channeList = ref([])
// 声明操作数据的方法action
const getList= async ()=> {

// 支持异步
const {data:{data}}= await axios.get('http://geek.itheima.net/v1_0/channels')
channeList.value=data.channels
console.log('channel',channeList.value)
}
// 声明基于数据派生的计算属性 getters


return {
getList,
channeList,
userChannelStore,

}

})
在app.vue中
import { userChannelStore } from '@/store/channel'
const ChannelStore = userChannelStore()

<button @click="ChannelStore.getList">获取频道数据</button>
<ul>
<li v-for=" (item,index) in ChannelStore.channeList" :key="item.id">{{ item.name}}</li>
</ul>


<!-- 不支持结构 -->
### pinia-storeToRefs方法

import { storeToRefs } from 'pinia'

const {channeList } =storeToRefs(ChannelStore)
const { getList } =ChannelStore

对vuex里面的东西进行解构
<button @click="getList">获取频道数据</button>
<ul>
<li v-for=" (item,index) in channeList" :key="item.id">{{ item.name}}</li>
</ul>

### vue3 - pinia 持久化
官方文档:https:prazdevs.github.io/pinia-plugin-persistedstate/zh/
1.安装插件
npm i pinia-plugin-persistedstate
2.main.js使用
import persist from 'pinia-plugin-persistedstate'
...
app.use(createPinia().use(persist))
3.store仓库中 在count.js中

return {
count,
msg,
addCount,
subCount,
doublecount
}

},{
persist:true
})
persist:true开启

159 项目介绍和pnpm创建项目

Vue3 compositionAPI
Pinia / Pinia 持久化处理
Element Plus (表单校验,表格处理,组件封装)
ppm a41742
Eslint + prettier 更规范的配置
husky (Git hooks工具) 代码提交之前,进行校验
Al 大模型开发一整个项目模块(掌握最新的开发方式)
1请求模块设计
IVueRouter4 路由设计

vue3 大事件管理系统
在线演示:https://fe-bigevent-web.itheima.net/login
接口文档:https:1/apifox.com/apidoc/shared-26c67aee-0233-4d23-aab7-08448fdf95ff/api-93850835
基地址:http://big-event-vue-api-t.itheima.net

cmd 安装pnpm
PS D:\360Downloads\web\vue3Code> npm install -g pnpm

PS D:\360Downloads\web\vue3Code> pnpm create vue

Vue.js - The Progressive JavaScript Framework

? 请输入项目名称: » Vue3-event-admin

npm install -g pnpm

changed 1 package in 7s

1 package is looking for funding
run `npm fund` for details
PS D:\360Downloads\web\vue3Code> pnpm create vue

Vue.js - The Progressive JavaScript Framework

√ 请输入项目名称: ... Vue3-event-admin
√ 请输入包名称: ... vue3-event-admin
√ 是否使用 TypeScript 语法? ... 否 / 是
√ 是否启用 JSX 支持? ... 否 / 是
√ 是否引入 Vue Router 进行单页面应用开发? ... 否 / 是
√ 是否引入 Pinia 用于状态管理? ... 否 / 是
√ 是否引入 Vitest 用于单元测试? ... 否 / 是
√ 是否要引入一款端到端(End to End)测试工具? » 不需要
√ 是否引入 ESLint 用于代码质量检测? ... 否 / 是
√ 是否引入 Prettier 用于代码格式化? ... 否 / 是
√ 是否引入 Vue DevTools 7 扩展用于调试? (试验阶段) ... 否 / 是

正在初始化项目 D:\360Downloads\web\vue3Code\Vue3-event-admin...

项目初始化完成,可执行以下命令:

cd Vue3-event-admin
pnpm install
pnpm format
pnpm dev

PS D:\360Downloads\web\vue3Code> cd Vue3-event-admin
PS D:\360Downloads\web\vue3Code\Vue3-event-admin> pnpm install
 PS D:\360Downloads\web\vue3Code\Vue3-event-admin> pnpm dev

> vue3-event-admin@0.0.0 dev D:\360Downloads\web\vue3Code\Vue3-event-admin
> vite


VITE v5.4.3 ready in 1587 ms

➜ Local: http://localhost:5173/
➜ Network: use --host to expose
➜ Vue DevTools: Open http://localhost:5173/__devtools__/ as a separate window
➜ Vue DevTools: Press Alt(⌥)+Shift(⇧)+D in App to toggle the Vue DevTools

➜ press h + enter to show help

 

### pnpm 包管理器- 创建项目
- 些优势:比同类工具快2倍左右、节省磁盘空间…. https:l/ www.pnpm.cnl
安装方式:npm install -g pnpm
创建项目:pnpm create vue
多一句没有,少一句不行,田西后叶问
好公西字用的技术!
npm install -g pnpm
创建项目:pnpm create vue
ppm install
npm
npm install
npm install axios
pm install axios -D
npm uninstall axios
npm run dev
yarn
yarn
yarn add axios
yarn add axios -D
yarn remove axios
yarn dev
pnpm
pnpm install
pnpm add axios
pnpm add axios -D
pnpm remove axios
pnpm dev

### eslint配合prettier完成代码风格配置
ESLint 配置代码风格
环境同步:
1. 安装了插件 ESlint,开启保存自动修复
2.禁用了插件 Prettier,井关闭保存自动格式化
1ESlint插件十 vscode配置 实现自动格式化修复
"editor.codeActionsonsave": {
"source. fixAll": true
I,
"editor.formatonsave": false,
EZR#.eslintre.cjs
1. prettier /XLt&2 https://prettier.io
1. 单 1号
2.不使用分号
3. 每行宽度至多80字符
4. 不加对象|数组最后逗号
5. 换行符号不限制 (win mac不一致)
2. vue组件名称多单词组成(忽略index.vue)
3. props解构(关闭)
在.eslintrc.cjs里配置:

rules: {
// prettier专注于代码的美观度 (格式化工具)
// 前置:
// 1. 禁用格式化插件 prettier format on save 关闭
// 2. 安装Eslint插件, 并配置保存时自动修复
'prettier/prettier': [
'warn',
{
singleQuote: true, // 单引号
semi: false, // 无分号
printWidth: 80, // 每行宽度至多80字符
trailingComma: 'none', // 不加对象|数组最后逗号
endOfLine: 'auto' // 换行符号不限制(win mac 不一致)
}
],
// ESLint关注于规范, 如果不符合规范,报错
'vue/multi-word-component-names': [
'warn',
{
ignores: ['index'] // vue组件名称多单词组成(忽略index.vue)
}
],
'vue/no-setup-props-destructure': ['off'], // 关闭 props 解构的校验 (props解构丢失响应式)
// 添加未定义变量错误提示,create-vue@3.6.3 关闭,这里加上是为了支持下一个章节演示。
'no-undef': 'error'
}
### 基于husky代码检查工作流
1.在电脑上安装git
2.在vscode中想看到git code
shell.windows

2.

1.添加环境变量,略
2.在bash中查看path
echo $PATH
1
没有出现KaTeX parse error: Undefined control sequence: \Git at position 5: PATH\̲G̲i̲t̲\bin或PATH\Git\cmd

3.编辑.bashrc
vim ~/.bashrc
1
在最下方添加

"terminal.integrated.profiles.windows": {
"JavaScript Debug Terminal": null,
"Windows PowerShell": null,
"PowerShell": null,
"Command Prompt": null,

"git": {
"path":"D:\\360Downloads\\Software\\Git\\usr\\bin\\bash.exe",
},
"bash": {
"path": "D:\\360Downloads\\Software\\Git\\usr\\bin\\bash.exe",
},

},

重启vs code 与报错bash: uname: command not found

bash: git: command not found

在windows的命令行中运行git,一切正常。

查看PATH\usr\bin文件夹下,无git.exe。将PATH\bin\git.exe复制到上述路径后,在bash中运行git,报错BUG(fork bomb):PATH\bin\git.exe

解决思路:

1.添加环境变量,略
2.在bash中查看path
echo $PATH
1
没有出现KaTeX parse error: Undefined control sequence: \Git at position 5: PATH\̲G̲i̲t̲\bin或PATH\Git\cmd

3.编辑.bashrc
vim ~/.bashrc
1
在最下方添加

export PATH=$PATH:xxx/Git/bin:xxx/Git/cmd
1
其中xxx是Git的完整安装路径。例如笔者安装在C:\Program Files\Git,则添加

export PATH=$PATH:/c/Program\ Files/Git/bin:/c/Program\ Files/Git/cmd

找到.bashrc文件
在文件最后面加上

export PATH=$PATH:/d/360Downloads/Software/Git/bin:/d/360Downloads/Software/Git/cmd


输入bash:pnpm dlx husky-init && pnpm install 安装husky

 

posted @   竹雨禅月  阅读(25)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示