本文已参与「新人创作礼」活动,一起开启掘金创作之路。
项目实战
1.Vue运行环境安装,为什么需要安装运行环境
预装环境 :node v8+ (Vue3需要node 版本8以上)
为什么需要运行环境? VUE项目==文件类型是.vue他是需要被编译成.js文件,才可以被浏览器识别 安装复杂度
安装node环境 下载 | Node.js 中文网 nodejs.cn/download/
查看node版本: node –v
2.npm镜像安装
3.VueCli脚手架安装
4.Vue-Cli基础
Vue CLI 和 Vue的区别
脚手架是一个基于 Vue.js 进行快速开发的完整系统,通过@vue/cli 实现快速搭建标准化项目的脚手架
Vue的版本和VueCLI的版本的关系
Vue版本不受脚手架版本的影响 使用VueCLI构建项目过程,可以根据需求选择相应版本的Vue
查看VueCLI版本号: vue -v
5.使用Vue-Cli构建Vue3项目
详见博主文章
同一台电脑 实现 vue-cli2和vue-cli3同时并存 及 常见命令 未完待续。。。_BMG-Princess的博客-CSDN博客

编辑
6.Vue3与Vue2 main.js对比
编辑
编辑
编辑
7.Vue3生命周期变化
与 2.x 版本生命周期相对应的组合式 API
1.beforeCreate -> 使用 setup()
2.created -> 使用 setup()
3.beforeMount -> onBeforeMount
4.mounted -> onMounted
5.beforeUpdate -> onBeforeUpdate
6.updated -> onUpdated
7.beforeDestroy -> onBeforeUnmount
8.destroyed -> onUnmounted
9.errorCaptured -> onErrorCaptured
onRenderTracked 检查依赖被追踪。当render函数被调用时,会检查哪个响应式数据被收集依赖 onRenderTriggered 当执行update操作时,会检查哪个响应式数据导致组件重新渲染。

编辑 
编辑
8.ref,reactive响应式api
reactive与ref
被响应式api标记过的数据才可以成为响应式数据
ref--用来标记简单类型数据
reactive—标记复杂类型数据(深度响应式)
如果用ref对象/数组, 会自动将对象/数组转换为reactive的代理对象
ref的数据操作: 在js中要.value, 在模板中不需要(内部解析会自动添加.value)
编辑
9.引入http请求框架axios +封装
编辑
http.js说明
export function 可导出多个 ;此例只导出post;;后续补充get...
import axios from 'axios'
// import router from '@/router'
// import qs from "qs"
import { ElMessage } from 'element-plus'
//响应拦截器
axios.interceptors.response.use(
response => {
return response
},
error => {
if (error.response) {
switch (error.response.status) {
// 401: 未登录
// 只有在当前路由不是登录页面才跳转
case 401:
router.replace({
path: '/',
});
break;
// 403 token过期
// 清除本地token
// 跳转登录页面
case 403:
ElMessage.error('登录过期,请重新登录')
router.replace({
path: '/',
});
break;
// 404请求不存在
case 404:
ElMessage.error('网络请求不存在')
// router.replace({
// path: '/',
// });
break;
// 其他错误,直接抛出错误提示
default:
ElMessage.error(error.response.data.message)
}
}
})
//post
export function post(url, params) {
return new Promise((resolve, reject) => {
axios({
url: url,
method: 'post',
data: params,
timeout: 1000 * 60,
headers: {
'Content-Type': 'application/json;charset=utf-8',
// 'token': Dcookie.getCookie('token'),
}
})
.then(res => {
if (res.data.code == 1000) {
ElMessage.error(res.data.msg)
// localStorage.clear();
// Dcookie.setCookie('token', '', -1, '/', 'derunht.cn')
// router.push({
// path: "/"
// });
} else {
resolve(res.data);
}
})
.catch(err => {
reject(err)
});
});
}
api.js说明
//接口统一管理
import { post } from './http.js'
//角色列表
export const roleList = params => post('/power-dev/role/water/list', params);
10.Vue路由
import { createRouter, createWebHashHistory } from 'vue-router'
import Home from '../views/Home.vue'
import Login from '../views/login.vue'
const routes = [
{
path: '/Home',
name: 'Home',
component: Home,
meta:{
isShow:false,
},
children:[
{
path: '/courseList',
name: 'CourseListin',
meta:{
isShow:true,
title:'课程列表'
},
component: () => import(/* webpackChunkName: "login" */ '../views/courseList.vue')
},
{
path: '/teacherList',
name: 'TeacherList',
meta:{
isShow:true,
title:'讲师列表'
},
component: () => import(/* webpackChunkName: "login" */ '../views/teacherList.vue')
},
{
path: '/personal',
name: 'Personal',
meta:{
isShow:true,
title:'个人中心'
},
component: () => import(/* webpackChunkName: "login" */ '../views/personal.vue')
}
]
},
{
path: '/',
name: 'Login',
component: Login,
meta:{
isShow:false,
}
},
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router
11.Vue3 vs Vue2
编辑
12.emit 子调父
在 <script setup> 中必须使用 defineEmits API 来声明 emits ,它具备完整的类型推断并且无需引用、直接可用 。
<script setup>
import { reactive, ref } from 'vue'
//定义事件
const emit = defineEmits(["resetForm"])
//子调父方法
const click= () => {
emit("resetForm")
}
</script>
13. setup函数
setup是compositon Api在Vue3中的入口
beforeCreate和created会在setup中执行
无法使用this拿到当前组件的相关数据 setup只能同步执行,不可以异步。
<script setup> 是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖。相比于普通的 <script> 语法,它具有更多优势:
- 更少的样板内容,更简洁的代码。
- 能够使用纯 Typescript 声明 props 和抛出事件。
- 更好的运行时性能 (其模板会被编译成与其同一作用域的渲染函数,没有任何的中间代理)。
- 更好的 IDE 类型推断性能 (减少语言服务器从代码中抽离类型的工作)。
14.ts
因为使用的是typescript,所以标注lang="ts"。
<script lang="ts">
</script>
因为使用了ts语法,所以写明 index 为 number 类型。
setup(){
const list = ref(["1","2","3"]);
const aa = ref('');
const selectClick=(index:number)=>{
aa.value = list.value[index]
}
}
框架配置
1.vite.config.js
将文件夹vite.config.js修改为以下,直接复制粘贴即可,跨域地址自行修改
// 使用 vite 创建项目完成后会自动生成 一个 vite.config.js 代码如下
import {
defineConfig
} from 'vite' // 帮手函数,这样不用 jsdoc 注解也可以获取类型提示
import vue from '@vitejs/plugin-vue'
const {
resolve
} = require('path')
export default () => defineConfig({
plugins: [ //配置需要使用的插件列表
vue(),
],
// 强制预构建插件包
optimizeDeps: {
//检测需要预构建的依赖项
entries: [],
//默认情况下,不在 node_modules 中的,链接的包不会预构建
include: ['schart.js', 'axios'],
exclude: ['your-package-name'] //排除在优化之外
},
//静态资源服务的文件夹
publicDir: "public",
base: './',
//静态资源处理
assetsInclude: "",
//控制台输出的级别 info 、warn、error、silent
logLevel: "info",
// 设为false 可以避免 vite 清屏而错过在终端中打印某些关键信息
clearScreen: false,
resolve: {
alias: [ //配置别名
{
find: '@',
replacement: resolve(__dirname, 'src')
}
],
// 情景导出 package.json 配置中的exports字段
conditions: [],
// 导入时想要省略的扩展名列表
// 不建议使用 .vue 影响IDE和类型支持
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json']
},
// css
css: {
// 配置 css modules 的行为
modules: {},
// postCss 配置
postcss: {},
//指定传递给 css 预处理器的选项
preprocessorOptions: {
scss: {
additionalData: `$injectedColor:orange;`
}
}
},
json: {
//是否支持从 .json 文件中进行按名导入
namedExports: true,
//若设置为 true 导入的json会被转为 export default JSON.parse("..") 会比转译成对象字面量性能更好
stringify: false
},
//继承自 esbuild 转换选项,最常见的用例是自定义 JSX
esbuild: {
jsxFactory: "h",
jsxFragment: "Fragment",
jsxInject: `import Vue from 'vue'`
},
//本地运行配置,以及反向代理配置
server: {
overlay: {
warning: false,
err: false
},
hot: true,
hotOnly: true, // 是否热更新
host: 'localhost',
port: 8080,
autoOpenBrowser: false, //是否自动打开浏览器
errorOverlay: true,
notifyOnErrors: true,
poll: false,
devtool: 'cheap-module-eval-source-map',
cacheBusting: true,
cssSourceMap: true,
// 反向代理配置
proxy: {
//告诉node, 我接口只要是'/power-dev'开头的才用代理
'/power-dev': {
target: 'http://backstage-*****.****-vue',
changeOrigin: true, //是否允许跨域
// secure: true, //是否https接口
logLevel: 'debug',
pathRewrite: {
//'^/power-dev': ''的含义是将路径中/power-dev替换成空
'^/power-dev': '/power-dev'
}
},
},
disableHostCheck: true
},
//打包配置
build: {
//浏览器兼容性 "esnext"|"modules"
target: "modules",
//指定输出路径
outDir: "dist",
//生成静态资源的存放路径
assetsDir: "assets",
//小于此阈值的导入或引用资源将内联为 base64 编码,以避免额外的 http 请求。设置为 0 可以完全禁用此项
assetsInlineLimit: 4096,
//启用/禁用 CSS 代码拆分
cssCodeSplit: true,
//构建后是否生成 source map 文件
sourcemap: false,
//自定义底层的 Rollup 打包配置
rollupOptions: {},
//@rollup/plugin-commonjs 插件的选项
commonjsOptions: {},
//构建的库
lib: {},
//当设置为 true,构建后将会生成 manifest.json 文件
manifest: false,
// 设置为 false 可以禁用最小化混淆,
// 或是用来指定使用哪种混淆器
// boolean | 'terser' | 'esbuild'
minify: "terser", //terser 构建后文件体积更小
//传递给 Terser 的更多 minify 选项。
terserOptions: {},
//设置为 false 来禁用将构建后的文件写入磁盘
write: true,
//默认情况下,若 outDir 在 root 目录下,则 Vite 会在构建时清空该目录。
emptyOutDir: true,
//启用/禁用 brotli 压缩大小报告
brotliSize: true,
//chunk 大小警告的限制
chunkSizeWarningLimit: 500
},
ssr: {
// 列出的是要为 SSR 强制外部化的依赖
external: [],
//列出的是防止被 SSR 外部化依赖项
noExternal: []
}
})
ElementUI
1、使用element-plus框架
官方组件库:
Button 按钮 | Element Plus (gitee.io)
2、安装:
npm install element-plus
3、引用:
import { createApp } from 'vue'
// 全局引用
import ElementPlus from 'element-plus';
// 引用所有样式
import 'element-plus/lib/theme-chalk/index.css';
import App from './App.vue';
const app = createApp(App)
// 使用
app.use(ElementPlus)
app.mount('#app')
配置引入
- 只能引入插件的
css文件,但是base样式和icon还需要手动引入 - 下载插件:
npm install babel-plugin-import -D - 配置
babel.config.js
babel.config.js文件内容如下:
module.exports = {
presets: ['@vue/app'],
plugins: [
[
"import",
{
libraryName: 'element-plus',
customStyleName: (name) => {
name = name.slice(3)
return `element-plus/packages/theme-chalk/src/${name}.scss`;
},
},
],
],
}
子页面使用
<template>
<div class="login-wrap">
<div class="ms-login">
<div class="ms-title">后台管理系统</div>
<el-form :model="param" :rules="rules" ref="login" label-width="0px" class="ms-content">
<el-form-item prop="username">
<el-input v-model="param.username" placeholder="username">
<template #prepend>
<el-button icon="el-icon-user"></el-button>
</template>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input type="password" placeholder="password" v-model="param.password"
@keyup.enter="submitForm()">
<template #prepend>
<el-button icon="el-icon-lock"></el-button>
</template>
</el-input>
</el-form-item>
<div class="login-btn">
<el-button type="primary" @click="submitForm()">登录</el-button>
</div>
<p class="login-tips">Tips : 用户名和密码随便填。</p>
</el-form>
</div>
</div>
</template>
<script>
import {
ref,
reactive
} from "vue";
import {
useStore
} from "vuex";
import {
useRouter
} from "vue-router";
import {
ElMessage
} from "element-plus";
import {
roleList
} from '@/request/api.js'
export default {
setup() {
const router = useRouter();
const param = reactive({
username: "admin",
password: "123123",
});
const rules = {
username: [{
required: true,
message: "请输入用户名",
trigger: "blur",
}, ],
password: [{
required: true,
message: "请输入密码",
trigger: "blur"
}, ],
};// 一个普通对象,修改后不会被proxy拦截,进而页面也不会动态更新
const login = ref(null);
const submitForm = () => {
login.value.validate((valid) => {
if (valid) {
submitFormLink();
} else {
ElMessage.error("登录失败");
return false;
}
});
};
// 使用响应式函数reactive构建proxy响应式对象
const params = reactive({
***: 用户名,
***: 密码,
})
const submitFormLink = () => {
roleList(params).then(res => {
if (res.code == 0) {
ElMessage.success("登录成功");
localStorage.setItem("ms_username", param.username);
router.push("/");
} else if (res.code !== 0) {
this.$message.error(res.msg);
}
})
};
const store = useStore();
store.commit("clearTags");
// 使用时,要把对象return出去,才能在template中使用
return {
param,
rules,
login,
submitForm,
params,
submitFormLink
};
},
};
</script>
<style scoped>
.login-wrap {
position: relative;
width: 100%;
height: 100%;
background-image: url(../assets/img/login-bg.jpg);
background-size: 100%;
}
.ms-title {
width: 100%;
line-height: 50px;
text-align: center;
font-size: 20px;
color: #fff;
border-bottom: 1px solid #ddd;
}
.ms-login {
position: absolute;
left: 50%;
top: 50%;
width: 350px;
margin: -190px 0 0 -175px;
border-radius: 5px;
background: rgba(255, 255, 255, 0.3);
overflow: hidden;
}
.ms-content {
padding: 30px 30px;
}
.login-btn {
text-align: center;
}
.login-btn button {
width: 100%;
height: 36px;
margin-bottom: 10px;
}
.login-tips {
font-size: 12px;
line-height: 30px;
color: #fff;
}
</style>
4、首页布局实现 - 参考Element官网
<template>
<div class="home">
<el-container>
<el-header class="top-box">
<el-row :gutter="24">
<el-col :span="18">
<img class="logo" src="../assets/logo.png" alt="">
</el-col>
<el-col :span="6">
<div class="info-box">
<span>Tina</span>
<el-button class="out-btn" type="text">退出</el-button>
</div>
</el-col>
</el-row>
</el-header>
<el-container>
<el-aside width="200px">
<el-menu
router='true'
class="el-menu-vertical-demo">
<el-menu-item :route="i.path" v-for="i in tabList" :key="i.name" :index="i.name">
<!-- <i class="el-icon-document"></i> -->
<template #title>{{i.meta.title}}</template>
</el-menu-item>
</el-menu>
</el-aside>
<el-main>
<div class="content-box">
<router-view/>
</div>
</el-main>
</el-container>
</el-container>
</div>
</template>
<script>
// @ is an alias to /src
// import {reactive} from "vue";
import router from "../router/index";
export default {
name: "Home",
setup() {
let tabList = router.options.routes[0].children;
return {
tabList,
};
},
};
</script>
<style lang="scss" scoped>
.top-box {
background: #efefef;
padding: 5px;
.logo {
width: 50px;
}
.info-box {
text-align: right;
}
.out-btn {
margin-left: 10px;
}
}
.content-box{
padding:20px;
}
</style>
5、开发疑问+解答
1、dark
<script lang="ts" setup>
import { isDark } from '~/composables/dark'
</script>
<template>
<div class="flex">
<el-button color="#626aef" :dark="isDark">Default</el-button>
</div>
</template>
:dark="isDark" 目前调研此为错误
2、引用示例
- 注意script写法,v-model内值,定义时加入ref
<template>
<el-cascader :options="options" />
</template>
<script lang="ts" setup>
const options = [
{
value: 'guide',
label: 'Guide',
children: [
{
value: 'notice',
label: 'Notice',
children: [
{
value: 'message-box',
label: 'MessageBox',
},
],
},
],
},
]
</script>
<script lang="ts" setup>
import { ref } from 'vue'
const checked1 = ref(true)
const checked2 = ref(false)
</script>
<template>
<el-checkbox-group v-model="checkList">
<el-checkbox label="Option A" />
<el-checkbox label="Option B" />
<el-checkbox label="Option C" />
<el-checkbox label="disabled" disabled />
<el-checkbox label="selected and disabled" disabled />
</el-checkbox-group>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const checkList = ref(['selected and disabled', 'Option A'])
</script>
<template>
<el-checkbox
v-model="checkAll"
:indeterminate="isIndeterminate"
@change="handleCheckAllChange"
>Check all</el-checkbox
>
<el-checkbox-group
v-model="checkedCities"
@change="handleCheckedCitiesChange"
>
<el-checkbox v-for="city in cities" :key="city" :label="city">{{
city
}}</el-checkbox>
</el-checkbox-group>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const checkAll = ref(false)
const isIndeterminate = ref(true)
const checkedCities = ref(['Shanghai', 'Beijing'])
const cities = ['Shanghai', 'Beijing', 'Guangzhou', 'Shenzhen']
const handleCheckAllChange = (val: boolean) => {
checkedCities.value = val ? cities : []
isIndeterminate.value = false
}
const handleCheckedCitiesChange = (value: string[]) => {
const checkedCount = value.length
checkAll.value = checkedCount === cities.length
isIndeterminate.value = checkedCount > 0 && checkedCount < cities.length
}
</script>