创建vue3项目
在vue-cli的主页可以看到目前vue-cli处于维护状态,而且官方也推荐vite创建vue项目。但是总的来说我感觉vue-cli创建vue项目更加简单方便。
1.使用vue-cli创建
1.安装vue-cli
先查看是否安装vue-cli
在cmd窗口输入vue -V
查看版本,如果出现
则说明存在vue-cli,如果出现
则需要安装vue-cli
npm i -g @vue/cli
如果需要升级版本则可以输入
npm update -g @vue/cli
2.创建vue3项目
输入
vue create vue3-demo
即可进入模板选择
等待生成
创建成功后进入创建好的项目文件夹中,在cmd窗口输入npm run serve即可启动项目。
可以看到我们创建好的项目:
2.使用vite创建
1.创建vite项目
npm create vite@latest
选项很简单,只有名称,框架,js还是ts,我这里先用的js
创建好的项目目录:
输入:npm i
和npm run dev
启动
3.配置vue3项目
1.修改vite.config.js文件
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { resolve } from 'path';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
base: './', //打包后静态资源读取
server: {
host: '0.0.0.0',
// port: 8080,
open: true,
},
resolve: {
//别名配置,引用src路径下的东西可以通过@如:import Layout from '@/layout/index.vue'
alias: [
{
find: '@',
replacement: resolve(__dirname, 'src'),
},
],
},
});
2.配置vite环境变量
默认情况下,开发服务器 (dev 命令) 运行在 development (开发) 模式,而 build 命令则运行在 production (生产) 模式。
vite 提供了两种模式:开发模式(development)和生产模式(production)
在根目录创建.env.development
NODE_ENV=development
VITE_APP_WEB_URL= 'YOUR WEB URL'
在根目录创建.env.production
NODE_ENV=production
VITE_APP_WEB_URL= 'YOUR WEB URL'
使用:
const BaseUrl = import.meta.env.VITE_APP_WEB_URL
3.配置代理(处理跨域)
修改vite.config.js
文件中的server
server: {
// 配置前端服务地址和端口
//服务器主机名
host: '0.0.0.0',
//端口号
// port: 8080,
//设为 true 时若端口已被占用则会直接退出,而不是尝试下一个可用端口
strictPort: false,
//服务器启动时自动在浏览器中打开应用程序,当此值为字符串时,会被用作 URL 的路径名
open: false,
//自定义代理规则
proxy: {
// 选项写法
'/api': {
target: 'http://xxx.xxx.xxx.xxx:xxxx',
changeOrigin: true,
rewrite: path => path.replace(/^\/api/, '')
}
}
},
4.添加css预处理器sass
npm install -D sass sass-loader
5.添加Vue Router
npm install vue-router@4
在src
目录下创建router/index.js
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
const routes = [];
const router = createRouter({
history: createWebHistory(),
routes,
});
export default router;
在main.js
文件中引入
import { createApp } from 'vue';
import './style.css';
import App from './App.vue';
import router from './router/index';
const app = createApp(App);
app.use(router);
app.mount('#app');
6.封装axios
npm install axios
在src
创建utils/request.js
import axios from 'axios';
import { ElMessage } from 'element-plus';
import { useUserStore } from '@/store/user';
const service = axios.create({
baseURL: import.meta.env.VUE_APP_BASE_API,
timeout: 5000,
});
// 请求拦截器
service.interceptors.request.use(
(config) => {
// 注入token;
const userStore = useUserStore();
if (userStore.token) {
// 如果存在token则注入token
config.headers.Authorization = userStore.token;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 响应拦截器
service.interceptors.response.use(
(response) => {
if (response.status === 200) {
return response.data;
} else {
ElMessage.error(response.statusText);
return Promise.reject(new Error(response.statusText));
}
},
(error) => {
if (
error.response &&
error.response.data &&
error.response.status === 424
) {
store.dispatch('user/logout');
}
ElMessage.error(error.response.data.msg);
return Promise.reject(error);
}
);
export default service;
使用:创建src/api/index.js
import request from '@/utils/request';
export function DataRequest(userInfo) {
return request({
url: '/api/admin/oauth2/token',
method: 'POST',
headers: {
Authorization: '',
'Content-Type': 'application/x-www-form-urlencoded',
Accept: '*/*',
},
data: {
username: userInfo.username,
},
});
}
export function paramPost(userInfo) {
return request({
url: '/api/admin/oauth2/token',
method: 'POST',
headers: {
Authorization: '',
'Content-Type': 'application/x-www-form-urlencoded',
Accept: '*/*',
},
params: {
username: userInfo.username
},
});
}
7.Vue 存储库Pinia
其实pinia就是vuex的升级版,pina对vuex进行了升级,现在只有state,getters,actions
npm install pinia
创建文件夹src/store
在main.js
中引入:
import { createPinia } from 'pinia'
app.use(createPinia())
在store中创建想要存的,比如user.js
import { defineStore } from 'pinia';
// 第一个参数是应用程序中 store 的唯一 id
export const useUsersStore = defineStore('users', {
state: () => {
return {
name: 'userName',
age: 25,
sex: '男',
};
},
getters: {
getAddAge: (state) => {
return (num) => state.age + num;
},
getNameAndAge() {
return this.name + this.getAddAge; // 调用其它getter
},
},
actions: {
saveName(name) {
this.name = name;
},
},
});
8.配置svg
安装依赖
npm install vite-plugin-svg-icons -D
创建components/SvgIcon/index.vue
<template>
<svg :class="svgClass" aria-hidden="true">
<use :xlink:href="iconName" />
</svg>
</template>
<script setup lang='ts'>
import { computed } from 'vue'
const props = defineProps({
iconClass: {
type: String,
required: true,
},
className: {
type: String,
default: () => '',
},
})
const iconName = computed(() => {
console.log(`#icon-${props.iconClass}`)
return `#icon-${props.iconClass}`
})
const svgClass = computed(() => {
return props.className ? 'svg-icon ' + props.className : 'svg-icon'
})
</script>
<style scoped lang="scss">
.svg-icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
.svg-external-icon {
background-color: currentColor;
mask-size: cover !important;
display: inline-block;
}
</style>
创建src/icons/svg
文件夹存放svg文件
创建src/icons/index.js
注册svg
在main.js中引入
// 引入svg组件
import IconSvg from '@/icons/index'
import "virtual:svg-icons-register"
app.use(IconSvg)
在vite.config.js中引入
import path from 'path'
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
plugins: [
// 注册所有的svg文件生成svg雪碧图
createSvgIconsPlugin({
iconDirs: [path.resolve(process.cwd(), "src/icons/svg")], // icon存放的目录
symbolId: "icon-[name]", // symbol的id
inject: "body-last", // 插入的位置
customDomId: "__svg__icons__dom__" // svg的id
})
],
使用:
<svg-icon icon-class="login-user"></svg-icon>
9.引入elementplus
npm install element-plus --save
- 按需引入(推荐)
npm install -D unplugin-vue-components unplugin-auto-import
// vite.config.ts
import { defineConfig } from 'vite'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig({
// ...
plugins: [
// ...
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
],
})
- 全局引入(不推荐)
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
const app = createApp(App)
app.use(ElementPlus)
app.mount('#app')
10.axios封装后使用多个BASE_URL代理
例如我有两个baseUrl需要进行代理,但是axios封装好了默认使用其中一个,此时可以使用axios的baseURL的功能:
在.env.development
中写入两个URL:
NODE_ENV=development
VUE_APP_BASE_API="'api'"
VUE_APP_GD_API="'gdurl'"
在vite.config.js中:
proxy: {
// 选项写法
'/api': {
target: 'http://4xxx.xxx.xxx.xxx:20010',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
},
'/gdurl': {
target: 'https://restapi.amap.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/gdurl/, '')
},
},
使用:
在axios的封装文件中使用默认的url:
在需要使用不是BASE_API的接口处:
4.初始化项目结构
- App.vue初始化
<template>
<router-view />
</template>
<style lang="scss"></style>
- 删除views文件夹中所有.vue文件
- 删除components文件夹下所有.vue文件
- 初始化router/index.js中的代码
import { createRouter, createWebHashHistory } from 'vue-router'
const routes = [
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router
5.创建layout文件夹
1.创建src/layout文件夹
- layout
- components
- Sidebar
- Navbar
- AppMain
- index.vue
- components
index.vue导入组件
<template>
<div class="app-wrapper">
<div class="fixed-header">
<navbar class="navbar-container"></navbar>
</div>
<div class="main-container">
<sidebar
class="sidebar-container"
:style="{ backgroundColor: variables.menuBg }"
></sidebar>
<tags-view class="tags-container"></tags-view>
<el-scrollbar>
<app-main></app-main>
</el-scrollbar>
</div>
</div>
</template>
<script setup>
import Navbar from './components/Navbar/index.vue';
import Sidebar from './components/Sidebar/index.vue';
import AppMain from './components/AppMain/index.vue';
import variables from '@/styles/variables.module.scss';
import TagsView from './components/TagsView/index.vue';
</script>
<style lang="scss" scoped>
@import '../styles/mixin.scss';
@import '../styles/variables.module.scss';
.app-wrapper {
@include clearfix;
@include relative;
}
.fixed-header {
position: fixed;
top: 0;
right: 0;
left: 0;
z-index: 9;
width: 100%;
}
</style>
2.创建layout通用的css文件
1.variables.module.scss
在src/styles中创建variables.module.scss文件定义常用的常量并导出
// navbar
$navbarHeight: 50px;
// tags-view
$tagsHeight: 35px;
// sidebar
$menuText: #bfcbd9;
$menuActiveText: #ffffff;
$subMenuActiveText: #f4f4f5;
$menuBg: #304156;
$menuHover: #263445;
$subMenuBg: #1f2d3d;
$subMenuHover: #001528;
$sideBarWidth: 210px;
// JS 与 scss 共享变量,在 scss 中通过 :export 进行导出,在 js 中可通过 ESM 进行导入
:export {
menuText: $menuText ;
menuActiveText: $menuActiveText;
subMenuActiveText: $subMenuActiveText;
menuBg: $menuBg;
menuHover: $menuHover;
subMenuBg: $subMenuBg;
subMenuHover: $subMenuHover;
sideBarWidth: $sideBarWidth;
}
2.mixin.scss
定义通用样式
// 清除浮动
@mixin clearfix {
&:after {
content: "";
display: table;
clear: both;
}
}
// 滚动条
@mixin scrollBar {
&::-webkit-scrollbar-track-piece {
background: #d3dce6;
}
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
background: #99a9bf;
border-radius: 20px;
}
}
// position
@mixin relative {
position: relative;
width: 100%;
height: 100%;
}
3.layout.scss
#app {
.navbar-container {
height: $navbarHeight;
overflow: hidden;
position: relative;
background-color: #fff;
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
}
.main-container {
height: calc(100% - $navbarHeight);
transition: margin-left 0.28s;
padding-top: $navbarHeight;
position: relative;
overflow: hidden;
}
.app-main {
width: 100%;
min-height: calc(100vh - calc($navbarHeight + calc($tagsHeight + 1px)));
position: relative;
overflow: hidden;
padding: 10px 0 10px calc($sideBarWidth + 10px);
box-sizing: border-box;
background-color: var(--el-bg-color-page);
}
.tags-container {
width: 100%;
height: $tagsHeight;
background-color: var(--el-bg-color);
border: 1px solid var(--el-border-color-light);
box-shadow: 0 1px 1px var(--el-box-shadow-light);
padding-left: $sideBarWidth;
}
.sidebar-container {
transition: width 0.28s;
width: $sideBarWidth !important;
height: calc(100% - $navbarHeight);
position: fixed;
top: $navbarHeight;
bottom: 0;
left: 0;
z-index: 1001;
overflow: hidden;
// 重置 element-plus 的css
.horizontal-collapse-transition {
transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
}
.scrollbar-wrapper {
overflow-x: hidden !important;
}
.el-scrollbar__bar.is-vertical {
right: 0px;
}
.el-scrollbar {
height: 100%;
}
&.has-logoel-scrollbar {
height: calc(100% - 50px);
}
.is-horizontal {
display: none;
}
a {
display: inline-block;
width: 100%;
overflow: hidden;
}
.svg-icon {
margin-right: 16px;
}
.sub-el-icon {
margin-right: 12px;
margin-left: -2px;
}
.el-menu {
border: none;
height: 100%;
width: 100% !important;
}
.is-active>.el-submenu__title {
color: $subMenuActiveText !important;
}
& .nest-menu .el-submenu>.el-submenu__title,
& .el-submenu .el-menu-item {
min-width: $sideBarWidth !important;
}
}
.hideSidebar {
.sidebar-container {
width: 54px !important;
}
.main-container {
margin-left: 54px;
}
.submenu-title-noDropdown {
padding: 0 !important;
position: relative;
.el-tooltip {
padding: 0 !important;
.svg-icon {
margin-left: 20px;
}
.sub-el-icon {
margin-left: 19px;
}
}
}
.el-submenu {
overflow: hidden;
&>.el-submenu__title {
padding: 0 !important;
.svg-icon {
margin-left: 20px;
}
.sub-el-icon {
margin-left: 19px;
}
.el-submenu__icon-arrow {
display: none;
}
}
}
.el-menu--collapse {
.el-submenu {
&>.el-submenu__title {
&>span {
height: 0;
width: 0;
overflow: hidden;
visibility: hidden;
display: inline-block;
}
}
}
}
}
.el-menu--collapse .el-menu .el-submenu {
min-width: $sideBarWidth !important;
}
.withoutAnimetion {
.main-container,
.sidebar-container {
transition: none;
}
}
}
.el-menu--vertical {
&>.el-menu {
.svg-icon {
margin-right: 16px;
}
.sub-el-icon {
margin-right: 12px;
margin-left: -2px;
}
}
// 菜单项过长时
>.el-menu--popup {
max-height: 100vh;
overflow-y: auto;
&::-webkit-scrollbar-track-piece {
background: #d3dce6;
}
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
background: #99a9bf;
border-radius: 20px;
}
}
}
4.在index.scss中导入
@import './variables.module.scss';
@import './mixin.scss';
@import './layout.scss';
html,
body {
height: 100%;
margin: 0;
padding: 0;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei,Arial, sans-serif;
position: relative;
}
#app {
height: 100%;
}
*,
*:before,
*:after {
box-sizing: inherit;
margin: 0;
padding: 0;
}
a:focus,
a:active {
outline: none;
}
a,
a:focus,
a:hover {
cursor: pointer;
color: inherit;
text-decoration: none;
}
div:focus {
outline: none;
}
.clearfix {
&:after {
visibility: hidden;
display: block;
font-size: 0;
content: '';
clear: both;
height: 0;
}
}
在main.js中导入
import ‘@/styles/index.scss’
5.在layout/index.vue中引入并定义sidebar的动态背景颜色
<template>
<div class="app-wrapper">
<div class="fixed-header">
<navbar class="navbar-container"></navbar>
</div>
<div class="main-container">
<sidebar
class="sidebar-container"
:style="{ backgroundColor: variables.menuBg }"
></sidebar>
<tags-view class="tags-container"></tags-view>
<el-scrollbar>
<app-main></app-main>
</el-scrollbar>
</div>
</div>
</template>
<script setup>
import Navbar from './components/Navbar/index.vue';
import Sidebar from './components/Sidebar/index.vue';
import AppMain from './components/AppMain/index.vue';
import variables from '@/styles/variables.module.scss';
import TagsView from './components/TagsView/index.vue';
</script>
<style lang="scss" scoped>
@import '../styles/mixin.scss';
@import '../styles/variables.module.scss';
.app-wrapper {
@include clearfix;
@include relative;
}
.fixed-header {
position: fixed;
top: 0;
right: 0;
left: 0;
z-index: 9;
width: 100%;
}
</style>