前言
- 通过制作管理系统学习vue3
流程
- 需要4.5以上
npm i -g @vue/cli
#查看版本
vue -V
vue create vue3-element-admin
- 选手动配置
- 除了pwa和下面2个测试,其他都选上。
- Vue版本选3.x
- 关于TypeScript两个选项 都选no
- 路由模式 使用Hash模式
- 选择 dart-sass 预处理器
- 选择 eslint配置 Standard
- 选择lint to save
- 生独立配置文件
- 最后一项不保存为preset 选择n
- 回车创建
vue add element-plus
- 安装会提示让你选是否要按需导入,语言包。
- 会自动创建src/plugins/element.js
- 将src/plugins/element.js修改为src/plugins/element.ts
- 文件改为:
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import installElementPlus from './plugins/element'
const app = createApp(App)
app
.use(store)
.use(router)
.use(installElementPlus)
.mount('#app')
- 入口修改:
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import installElementPlus from './plugins/element'
const app = createApp(App)
app
.use(store)
.use(router)
.use(installElementPlus)
.mount('#app')
- app.vue导入btn试一下:
<template>
<el-button type="primary">el-button</el-button>
</template>
<script>
export default {
name: 'App'
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
- 看见elemet 的btn即可。
- 修改eslint规则,关闭一些:
module.exports = {
root: true,
env: {
node: true
},
extends: [
'plugin:vue/vue3-essential',
'@vue/standard',
'@vue/typescript/recommended'
],
parserOptions: {
ecmaVersion: 2020
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'space-before-function-paren': 'off',
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/interface-name-prefixed': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/ban-ts-ignore': 'off'
}
}
- 初始化css
npm install --save normalize.css
- 导入:
import 'normalize.css/normalize.css'
- 创建layout布局
- src/layout/index.vue:
<template>
<div class="app-wrapper">
<div class="sidebar-container">sidebar</div>
<div class="main-container">
<div class="header">
<div class="navbar">navbar</div>
<div class="tags-view">tagsview</div>
</div>
<div class="app-main">
<h2>app main</h2>
<router-view></router-view>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.app-wrapper {
display: flex;
width: 100%;
height: 100%;
.main-container {
flex: 1;
display: flex;
flex-direction: column;
.header {
background: cyan;
.navbar {
height: 50px;
background: #1890ff;
}
.tags-view {
height: 34px;
background: #12efff;
}
}
.app-main {
/* 50= navbar 50 如果有tagsview + 34 */
min-height: calc(100vh - 84px);
background: red;
}
}
}
</style>
- src/views/创建dashboard:
<template>
<div>
<h1>Dashboard page</h1>
</div>
</template>
<script>
export default {
name: 'Dashboard'
}
</script>
- 配置路由 router/index.ts
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
import Layout from '@/layout/index.vue'
const routes: Array<RouteRecordRaw> = [
{
path: '/',
component: Layout,
redirect: '/dashboard',
children: [
{
path: 'dashboard',
name: 'Dashboard',
component: () => import(/* webpackChunkName: "dashboard" */ '@/views/dashboard/index.vue'),
meta: {
title: 'Dashboard'
}
}
]
}
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router
- src下创建 src/styles目录存放全局样式文件
- style下的sidebar.scss
#app {
.sidebar-container {
width: $sideBarWidth !important;
height: 100%;
background-color: pink;
}
}
- variable.scss
// base color
$blue:#324157;
$light-blue:#3A71A8;
$red:#C03639;
$pink: #E65D6E;
$green: #30B08F;
$tiffany: #4AB7BD;
$yellow:#FEC171;
$panGreen: #30B08F;
// sidebar
$menuText:#bfcbd9;
$menuActiveText:#409EFF;
$subMenuActiveText:#f4f4f5; // https://github.com/ElemeFE/element/issues/12951
$menuBg:#304156;
$menuHover:#263445;
$subMenuBg:#1f2d3d;
$subMenuHover:#001528;
$sideBarWidth: 210px;
// The :export directive is the magic sauce for webpack
// https://mattferderer.com/use-sass-variables-in-typescript-and-javascript
:export {
menuText: $menuText;
menuActiveText: $menuActiveText;
subMenuActiveText: $subMenuActiveText;
menuBg: $menuBg;
menuHover: $menuHover;
subMenuBg: $subMenuBg;
subMenuHover: $subMenuHover;
sideBarWidth: $sideBarWidth;
}
- 这个导出写法上面有个链接,告诉你可以在js ts中使用他们。
- ts中写个声明文件:
export interface ScssVariables {
menuText: string;
menuActiveText: string;
subMenuActiveText: string;
menuBg: string;
menuHover: string;
subMenuBg: string;
subMenuHover: string;
sideBarWidth: string;
}
export const variables: ScssVariables
export default variables
- main中引入:
import '@/styles/index.scss'
- app.vue中引入路由:
<template>
<div id="app">
<router-view />
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
<style>
#app {
height: 100%;
}
</style>
- 此时,能看见布局ok即可。
- 配置SVG
- 先准备好一些svg
- 安装loder
npm install svg-sprite-loader -D
- vue.config.js中加入配置:
'use strict'
// eslint-disable-next-line @typescript-eslint/no-var-requires
const path = require('path')
const resolve = dir => path.join(__dirname, dir)
function chainWebpack(config) {
// 在已有的svg loader配置中 排除掉对src/icons里svg进行转换
config.module
.rule('svg')
.exclude.add(resolve('src/icons')) // 排除掉src/icons目录
.end()
// svg icon工作原理 https://segmentfault.com/a/1190000015367490
// 配置svg-sprite-loader
config.module
.rule('icons')
.test(/\.svg$/)
.include.add(resolve('src/icons')) // 指定src/icons要处理svg的文件目录
.end()
.use('svg-sprite-loader')
.loader('svg-sprite-loader') // 用svg-sprite-loader解析
.options({
symbolId: 'icon-[name]' // symbol id命名格式 icon-图标名称
})
.end()
}
module.exports = {
chainWebpack,
devServer: {
port: 8080,
open: true,
overlay: {
warnings: false,
errors: true
}
}
}
- 制作svg组件:
<template>
<!-- 如果iconClass是带协议的图标链接 则通过style属性方式渲染-->
<div
class="svg-icon svg-external-icon"
v-if="isExt"
:style="styleExternalIcon"
v-bind="$attrs"
></div>
<!-- SVG icon 通过名称使用 -->
<svg v-else :class="svgClass" aria-hidden="true" v-bind="$attrs">
<!--
SVG中的use元素可以调用其他SVG文件的元素,<use xlink:href="#symbolId"></use>
-->
<use :xlink:href="iconName" />
</svg>
</template>
<script lang="ts">
import { isExternal } from '@/utils/validate'
import { computed, defineComponent } from 'vue'
/**
* v-bind="$attrs" 组件$attrs属性透传绑定到元素上
* vue3.0中$lietens已被移除 现在事件监听器是 $attrs 的一部分
* 文档说明:
* https://v3.cn.vuejs.org/guide/migration/listeners-removed.html#_3-x-%E8%AF%AD%E6%B3%95
*/
export default defineComponent({
name: 'SvgIcon',
inheritAttrs: false, // 组件上的$attrs不自动添加到组件根元素上 默认添加到组件根元素上
props: {
iconClass: {
type: String,
require: true
},
className: {
// 我们也可以自定义类名
type: String,
default: ''
}
},
setup(props) {
// 是否是带协议的图片链接
const isExt = computed(() => isExternal(props.iconClass || ''))
// 拼接成symbolId 在loader配置中指定了symbolId格式 icon-图标名称
const iconName = computed(() => `#icon-${props.iconClass}`)
// 添加类名 props.className外部传入自定义类名
const svgClass = computed(() =>
props.className ? `svg-icon ${props.className}` : 'svg-icon'
)
// 如果iconClass是带协议的图标链接 则通过style css属性方式渲染
const styleExternalIcon = computed(() => ({
mask: `url(${props.iconClass}) no-repeat 50% 50%`,
'-webkit-mask': `url(${props.iconClass}) no-repeat 50% 50%`
}))
return {
isExt,
iconName,
svgClass,
styleExternalIcon
}
}
})
</script>
<style scoped>
.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>
- 注册进app
import { App } from 'vue'
import SvgIcon from '@/components/SvgIcon/index.vue'
const req = require.context('./svg', false, /\.svg$/)
const requireAll = (requireContext: ReturnType<typeof require.context>) => requireContext.keys().map(requireContext)
requireAll(req)
export default (app: App): void => {
app.component('svg-icon', SvgIcon)
}
- 入口导入:
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
// 初始化css
import 'normalize.css/normalize.css'
// element-plus
import installElementPlus from './plugins/element'
// 全局 css
import '@/styles/index.scss'
// svg icons
import initSvgIcon from '@/icons/index'
const app = createApp(App)
app
.use(store)
.use(router)
.use(installElementPlus)
.use(initSvgIcon)
.mount('#app')
- 在dashboard引用下:
<template>
<div>
<h1>Dashboard page</h1>
<svg-icon icon-class="bug"></svg-icon>
<!-- icon-class svg图标名称 class-name 额外的自定义类名 @click绑定事件 -->
<svg-icon icon-class="404" class-name="custom-class" @click="sayHi"></svg-icon>
</div>
</template>
<script>
export default {
name: 'Dashboard',
setup() {
const sayHi = () => {
alert('hi svg')
}
return {
sayHi
}
}
}
</script>
<style lang="scss">
.custom-class { // 自定义样式404
font-size: 200px;
color: green;
}
</style>
- 有svg出现即ok。
- 下面使用svgo进行svg优化:
- 全局安装svgo https://github.com/svg/svgo
- 写入yml配置:
# replace default config
# multipass: true
# full: true
plugins:
# - name
#
# or:
# - name: false
# - name: true
#
# or:
# - name:
# param1: 1
# param2: 2
- removeAttrs:
attrs:
- 'fill'
- 'fill-rule'
- 增加命令即可:
"svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml"
- 全局命令挂载
- 注意,全局命令的声明不要写道shims.vue.d.ts里,因为里面如果有import之类整个项目编译会有问题。
- 所以声明放到main.ts下:
declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
$message: typeof ElMessage
$notify: typeof ElNotification
$confirm: typeof ElMessageBox.confirm
$alert: typeof ElMessageBox.alert
$prompt: typeof ElMessageBox.prompt
}
}
- app.config.globalProperties代替原来的vue.prototype
import { App } from 'vue'
import {
locale,
ElButton,
ElMessage,
ElNotification,
ElMessageBox
} from 'element-plus'
import 'element-plus/lib/theme-chalk/index.css'
// Element Plus 组件内部默认使用英语
// https://element-plus.gitee.io/#/zh-CN/component/i18n
import lang from 'element-plus/lib/locale/lang/zh-cn'
// Element Plus 直接使用了 Day.js 项目的时间日期国际化设置, 并且会自动全局设置已经导入的 Day.js 国际化配置。
import 'dayjs/locale/zh-cn'
// $ELEMENT size属性类型
export type Size = 'default' | 'medium' | 'small' | 'mini'
export default (app: App): void => {
locale(lang)
// 按需导入组件列表
const components = [ElButton, ElMessage, ElNotification, ElMessageBox]
components.forEach(component => {
app.component(component.name, component)
})
// Vue.prototype 替换为 config.globalProperties
// 文档说明 https://v3.cn.vuejs.org/guide/migration/global-api.html#vue-prototype-%E6%9B%BF%E6%8D%A2%E4%B8%BA-config-globalproperties
app.config.globalProperties.$message = ElMessage
app.config.globalProperties.$notify = ElNotification
app.config.globalProperties.$confirm = ElMessageBox.confirm
app.config.globalProperties.$alert = ElMessageBox.alert
app.config.globalProperties.$prompt = ElMessageBox.prompt
// element-plus全局配置
// 说明文档:https://element-plus.gitee.io/#/zh-CN/component/quickstart#quan-ju-pei-zhi
// 该对象目前支持 size 与 zIndex 字段。size 用于改变组件的默认尺寸 small,zIndex 设置弹框的初始 z-index(默认值:2000)。
app.config.globalProperties.$ELEMENT = {
size: 'medium'
// zIndex: 2000 弹框zIndex默认值:2000
}
}
- 用dashboard试验下:
<template>
<div>
<h1>Dashboard page</h1>
<svg-icon icon-class="bug"></svg-icon>
<!-- icon-class svg图标名称 class-name 额外的自定义类名 @click绑定事件 -->
<svg-icon
icon-class="404"
class-name="custom-class"
@click="sayHi"
></svg-icon>
</div>
</template>
<script lang="ts">
import { getCurrentInstance, defineComponent } from 'vue'
export default defineComponent({
name: 'Dashboard',
setup() {
// 无法使用ctx 使用proxy来代替
// https://blog.csdn.net/qq_39115469/article/details/113817592
const { proxy } = getCurrentInstance()!
const sayHi = () => {
proxy?.$message.success('恭喜你,这是一条成功消息')
}
return {
sayHi
}
}
})
</script>
<style lang="scss">
.custom-class {
// 自定义样式404
font-size: 200px;
color: green;
}
</style>
点击svg弹出提示则正常。