Vue3+Vite框架搭建
1. 创建项目
npm init vue@latest
基于项目需求,选择需要安装的依赖包,比如:
2. 修改package.json
{
"name": "addr-www",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore",
"format": "prettier --write src/"
},
"dependencies": {
"vue": "^3.2.47",
"vue-router": "^4.1.6",
"@element-plus/icons-vue": "^2.1.0",
"@kangc/v-md-editor": "^2.3.15",
"axios": "^1.3.4",
"core-js": "^3.8.3",
"crypto-js": "^4.1.1",
"element-plus": "^2.3.1",
"js-cookie": "^3.0.1",
"vuex": "^4.0.0"
},
"devDependencies": {
"@babel/core": "^7.12.16",
"@babel/eslint-parser": "^7.12.16",
"@rushstack/eslint-patch": "^1.2.0",
"@vitejs/plugin-vue": "^4.0.0",
"@vue/eslint-config-prettier": "^7.1.0",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-plugin-router": "~5.0.0",
"@vue/cli-plugin-vuex": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"eslint": "^8.34.0",
"eslint-plugin-vue": "^9.9.0",
"prettier": "^2.8.4",
"rollup-plugin-visualizer": "^5.9.0",
"sass": "^1.32.7",
"sass-loader": "^12.0.0",
"unplugin-auto-import": "^0.15.2",
"unplugin-element-plus": "^0.7.0",
"unplugin-vue-components": "^0.22.7",
"vite": "^4.1.4",
"vite-plugin-vconsole": "^1.3.1",
"vite-plugin-vue-setup-extend": "^0.4.0"
}
}
根据项目需求,增删新建的package.json文件,以下将着重说几个重要的:
- Vite:一种新型前端构建工具,能够显著提升前端开发体验。
- Sass:专业级CSS扩展语言。
- Vue、Vue-Router、Vuex:Vue必备三件套。
- Element-Plus:适配Vue3的UI框架。
- Axios:一个基于 Promise 网络请求库,用于处理http请求(GET、POST等)。
- unplugin-auto-import:为 Vite、Webpack、Rollup 和 esbuild 按需自动导入 API,诸如Vue3中必须导入才能使用的ref、computed和Vuex中的mapActions、mapState等都可以通过全局调用来避免重复代码。
Q:为什么不用npm或yarn逐个安装?
A:麻烦。
3. 配置vite.config.js文件
import { defineConfig } from 'vite'
import path from 'path'
import vue from '@vitejs/plugin-vue'
import vueSetupExend from 'vite-plugin-vue-setup-extend'
import ElementPlus from 'unplugin-element-plus/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import AutoImport from 'unplugin-auto-import/vite'
import { visualizer } from 'rollup-plugin-visualizer'
export default defineConfig({
base: './',
plugins: [
vue(),
vueSetupExend(),
AutoImport({
resolvers: [ElementPlusResolver()],
imports: ['vue', 'vue-router', 'vuex'],
eslintrc: {
enabled: false,
globalsPropValue: true,
filepath: './.eslintrc-auto-import.json'
}
}),
visualizer({
emitFile: false,
file: 'stats.html',
open: false
}),
Components({
resolvers: [ElementPlusResolver()]
}),
ElementPlus()
],
resolve: {
alias: {
'@': path.resolve(__dirname, './src')
}
},
build: {
target: ['es2015'],
outDir: 'dist',
manifest: true,
sourcemap: false,
emptyOutDir: true,
rollupOptions: {
output: {
manualChunks: {
lodash: ['lodash']
}
}
}
},
server: {
open: true,
port: 3301,
hmr: { overlay: false }
},
css: {
preprocessorOptions: {
scss: {}
}
}
})
相关配置:
https://cn.vitejs.dev/config/
4. 配置.gitignore文件(无git忽略)
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
5. 配置auto-imports.d.ts文件(全局注册)
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// Generated by unplugin-auto-import
export {}
declare global {
const EffectScope: typeof import('vue')['EffectScope']
const computed: typeof import('vue')['computed']
const createApp: typeof import('vue')['createApp']
const createLogger: typeof import('vuex')['createLogger']
const createNamespacedHelpers: typeof import('vuex')['createNamespacedHelpers']
const createStore: typeof import('vuex')['createStore']
const customRef: typeof import('vue')['customRef']
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
const defineComponent: typeof import('vue')['defineComponent']
const effectScope: typeof import('vue')['effectScope']
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
const getCurrentScope: typeof import('vue')['getCurrentScope']
const h: typeof import('vue')['h']
const inject: typeof import('vue')['inject']
const isProxy: typeof import('vue')['isProxy']
const isReactive: typeof import('vue')['isReactive']
const isReadonly: typeof import('vue')['isReadonly']
const isRef: typeof import('vue')['isRef']
const mapActions: typeof import('vuex')['mapActions']
const mapGetters: typeof import('vuex')['mapGetters']
const mapMutations: typeof import('vuex')['mapMutations']
const mapState: typeof import('vuex')['mapState']
const markRaw: typeof import('vue')['markRaw']
const nextTick: typeof import('vue')['nextTick']
const onActivated: typeof import('vue')['onActivated']
const onBeforeMount: typeof import('vue')['onBeforeMount']
const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
const onDeactivated: typeof import('vue')['onDeactivated']
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
const onMounted: typeof import('vue')['onMounted']
const onRenderTracked: typeof import('vue')['onRenderTracked']
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
const onScopeDispose: typeof import('vue')['onScopeDispose']
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
const onUnmounted: typeof import('vue')['onUnmounted']
const onUpdated: typeof import('vue')['onUpdated']
const provide: typeof import('vue')['provide']
const reactive: typeof import('vue')['reactive']
const readonly: typeof import('vue')['readonly']
const ref: typeof import('vue')['ref']
const resolveComponent: typeof import('vue')['resolveComponent']
const shallowReactive: typeof import('vue')['shallowReactive']
const shallowReadonly: typeof import('vue')['shallowReadonly']
const shallowRef: typeof import('vue')['shallowRef']
const toRaw: typeof import('vue')['toRaw']
const toRef: typeof import('vue')['toRef']
const toRefs: typeof import('vue')['toRefs']
const triggerRef: typeof import('vue')['triggerRef']
const unref: typeof import('vue')['unref']
const useAttrs: typeof import('vue')['useAttrs']
const useCssModule: typeof import('vue')['useCssModule']
const useCssVars: typeof import('vue')['useCssVars']
const useLink: typeof import('vue-router')['useLink']
const useRoute: typeof import('vue-router')['useRoute']
const useRouter: typeof import('vue-router')['useRouter']
const useSlots: typeof import('vue')['useSlots']
const useStore: typeof import('vuex')['useStore']
const watch: typeof import('vue')['watch']
const watchEffect: typeof import('vue')['watchEffect']
const watchPostEffect: typeof import('vue')['watchPostEffect']
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
}
// for type re-export
declare global {
// @ts-ignore
export type { Component, ComponentPublicInstance, ComputedRef, InjectionKey, PropType, Ref, VNode } from 'vue'
}
6. 配置eslint相关文件
.eslintignore
/lib
/dist
/docs
.eslintrc-auto-import.json
{
"globals": {
"EffectScope": true,
"computed": true,
"createApp": true,
"createLogger": true,
"createNamespacedHelpers": true,
"createStore": true,
"customRef": true,
"defineAsyncComponent": true,
"defineComponent": true,
"defineProps": true,
"defineEmits": true,
"defineExpose": true,
"effectScope": true,
"getCurrentInstance": true,
"getCurrentScope": true,
"h": true,
"inject": true,
"isProxy": true,
"isReactive": true,
"isReadonly": true,
"isRef": true,
"mapActions": true,
"mapGetters": true,
"mapMutations": true,
"mapState": true,
"markRaw": true,
"nextTick": true,
"onActivated": true,
"onBeforeMount": true,
"onBeforeRouteLeave": true,
"onBeforeRouteUpdate": true,
"onBeforeUnmount": true,
"onBeforeUpdate": true,
"onDeactivated": true,
"onErrorCaptured": true,
"onMounted": true,
"onRenderTracked": true,
"onRenderTriggered": true,
"onScopeDispose": true,
"onServerPrefetch": true,
"onUnmounted": true,
"onUpdated": true,
"provide": true,
"reactive": true,
"readonly": true,
"ref": true,
"resolveComponent": true,
"resolveDirective": true,
"shallowReactive": true,
"shallowReadonly": true,
"shallowRef": true,
"toRaw": true,
"toRef": true,
"toRefs": true,
"triggerRef": true,
"unref": true,
"useAttrs": true,
"useCssModule": true,
"useCssVars": true,
"useHead": true,
"useLink": true,
"useRoute": true,
"useRouter": true,
"useSlots": true,
"useStore": true,
"watch": true,
"watchEffect": true,
"watchPostEffect": true,
"watchSyncEffect": true
}
}
.eslintrc.js
module.exports = {
'env': {
'browser': true,
'es2021': true,
'node': true
},
'extends': [
'eslint:recommended',
'plugin:vue/vue3-essential',
'./.eslintrc-auto-import.json'
],
'overrides': [
],
'parserOptions': {
'ecmaVersion': 12,
'sourceType': 'module',
'allowImportExportEverywhere': true
},
'plugins': [
'vue'
],
'rules': {
'no-console': 'off',
'no-debugger': 'off',
'vue/script-indent': ['off', 4],
'vue/html-indent': ['off', 4],
'vue/multi-word-component-names': 'off',
'@typescript-eslint/no-explicit-any': ['off'],
// 禁止使用拖尾逗号
'comma-dangle': [1, 'never'],
// 禁止使用分号
'semi': [1, 'never'],
// 使用单引号
'quotes': [1, 'single'],
// 禁用行尾空白
'no-trailing-spaces': [1],
// 强制一行的最大长度
'max-len': 0
}
}
7. 配置babel.config.js文件
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}
8. 常用方法(utils)
在src/下新建utils文件夹,http.js文件需要根据需求修改header
common.js
import cryptoJs, { enc } from 'crypto-js'
export function md5(str) {
return String(cryptoJs.MD5(str)).toUpperCase()
}
export function base64encode(raw) {
try {
return enc.Base64.stringify(enc.Utf8.parse(raw))
} catch (e) {
return ''
}
}
export function base64decode(str) {
try {
return enc.Base64.parse(str).toString(enc.Utf8)
} catch (e) {
return ''
}
}
export function getTime(time = new Date().getTime()) {
time = time.toString().length <= 10 ? time * 1000 : time
const date = new Date(time)
const year = date.getFullYear()
const month = date.getMonth() + 1
const day = date.getDate()
const h = date.getHours() < 10 ? '0' + date.getHours() : date.getHours()
const m = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()
const s = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds()
return `${year}/${month}/${day} ${h}:${m}:${s}`
}
cache.js
import { md5, base64encode, base64decode } from '@/utils/common'
function encodeKey(key) {
return md5(window.location.host + import.meta.env.VITE_APP_SOURCE + key)
}
export const session = {
set(key, val) {
sessionStorage.setItem(encodeKey(key), base64encode(val))
},
get(key) {
var val = sessionStorage.getItem(encodeKey(key)) || ''
return val ? base64decode(val) : ''
},
remove(key) {
sessionStorage.removeItem(encodeKey(key))
}
}
export const local = {
set(key, val) {
localStorage.setItem(encodeKey(key), base64encode(val))
},
get(key) {
var val = localStorage.getItem(encodeKey(key)) || ''
return val ? base64decode(val) : ''
},
remove(key) {
localStorage.removeItem(encodeKey(key))
}
}
export default { session, local }
http.js
import axios from 'axios'
import { session } from '@/utils/cache'
var instance = axios.create({
baseURL: import.meta.env.VITE_BASE_URL,
headers: {
'Content-Type': 'application/json'
},
responseType: 'json',
timeout: 8000
})
instance.interceptors.request.use(
http => {
http.headers.Authorization = session.get('__login_token__') || ''
return http
},
error => {
return Promise.reject(error)
}
)
instance.interceptors.response.use(
res => {
return res.data || {}
},
error => {
console.log(error)
return Promise.resolve('')
}
)
export function get(uri) {
return instance.get(uri).then(res => {
console.log('[GET]', uri, res)
return Promise.resolve(res)
})
}
export function post(uri, params) {
return instance.post(uri, params || {}).then(res => {
console.log('[POST]', uri, params || {}, res)
return Promise.resolve(res)
})
}
9. 配置Vuex相关文件
在src/下新建store文件夹,在store/下创建index.js和modules文件夹
index.js
import { createStore } from 'vuex'
const store = createStore({
state: {
aside: false
},
actions: {
inin({ state }) {
console.log(state)
}
},
mutations: {},
modules: {
...(() => {
const modules = {}
const files = import.meta.globEager('./modules/*.js')
Object.keys(files).forEach((key) => {
const module = files[key].default
const moduleName = key.replace(/^\.\/(.*)\/(.*)\.\w+$/, '$2')
modules[moduleName] = module
})
return modules
})()
}
})
export default store
modules文件夹下js文件模版
import { post } from '@/utils/http'
import { ElNotification } from 'element-plus'
const state = {
page: {
total: 0,
list: [],
num: 1,
size: 20,
sizes: [20, 50, 100, 300],
layout: 'total, sizes, prev, pager, next, jumper',
order_by: ''
},
search: {}
}
const getters = {
rules: () => {
return {
name: [
{ required: true, message: '必填', trigger: 'blur' }
],
age: [
{ required: true, message: '必填', trigger: 'blur' }
]
}
}
}
const actions = {
getPageList({ state }) {
state.loading = true
const data = {
...state.search,
page_num: state.page.num,
page_size: state.page.size
}
Object.keys(data).forEach((item) => {
if (!data[item]) delete data[item]
})
post('/marketing/ocean/account/list', data).then(res => {
state.loading = false
if (res.code) return
state.page.total = res.data.total || 0
state.page.list = res.data.list || []
state.adminUsers = res.data.admin_users || []
})
},
updatePageSize({ state, dispatch }, val) {
state.page.size = val
dispatch('getPageList')
},
updatePageNum({ state, dispatch }, val) {
state.page.num = val
dispatch('getPageList')
},
handleChangeStatus({ dispatch }, row) {
let reqData = {
id: row.id,
status: row.status
}
post('/marketing/ocean/account/status', reqData).then(res => {
if (res.code) return
dispatch('getPageList')
})
},
onCreateForm({ state, dispatch }, form) {
form['upstream'] = state.search['upstream']
post(`/marketing/ocean/account/${form.id ? 'update' : 'create'}`, form).then(res => {
if (res.code > 0) {
ElNotification.error(res.message || '操作失败')
return
}
ElNotification.success('操作成功')
dispatch('getPageList')
state.formDialogVisibale = false
})
}
}
const mutations = {
handleOpenFormDialog(state, params) {
state.formData = Object.assign({}, params)
state.formDialogVisibale = true
},
handleCloseFormDialog(state) {
state.formDialogVisibale = false
}
}
export default {
namespaced: true,
state,
getters,
actions,
mutations
}
在src/main.js中挂载
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
const app = createApp(App)
app.use(router).use(store)
app.mount('#app')
10. 设置全局css
在src/目录下创建style文件夹,新建index.scss文件
index.scss
html {
--main-background-color: #ffffff;
--main-button-background: #f5f5f7;
--star-background-color: #f5f5f7;
--star-item-text-color: #333333;
--star-tag-background-color: #ffffff;
--star-tag-text-color: #73737f;
--plus-first-text-color: #333333;
--plus-item-background-color: #f5f5f7;
--plus-item-text-color: #333333;
--plus-item-amount-color: #73737f;
--plus-item-check-color: #ffffff;
--view-active-background-color: #ededf1;
--wx-title-background: #ffffff;
--input-background-color: #ffffff;
--drawer-background-color: #ffffff;
--dialog-background-color: #ffffff;
--default-text-color: #b7bbc4;
--input-text-color: #b7bbc4;
--first-text-color: #2c2c34;
--second-text-color: #222222;
}
body,
#app {
min-width: 1280px;
width: 100%;
height: 100%;
min-height: 600px;
font-family: MicrosoftYaHei;
}
*,
::after,
::before {
margin: 0;
padding: 0;
box-sizing: border-box;
}
*:focus {
outline: 0 !important;
}
ul,
li {
list-style: none;
}
img {
display: block;
}
.clear::after {
content: '';
display: block;
clear: both;
}
// 轨道
::-webkit-scrollbar {
width: 6px;
height: 10px;
border-radius: 10px;
background: #e5e5e5;
}
// 滚动条
::-webkit-scrollbar-thumb {
background: #888888;
opacity: 0.2;
border-radius: 10px;
}
// 底部箭头
::-webkit-scrollbar-button {
display: none;
}
// 顶部箭头
::-webkit-scrollbar-track {
display: none;
}
.el-drawer {
background: var(--drawer-background-color) !important;
}
.el-drawer__title {
color: var(--first-text-color) !important;
}
.el-dialog {
background: var(--dialog-background-color) !important;
}
在App.vue中引入
<template>
<router-view></router-view>
</template>
<style lang="scss">
@import url('./style/index.scss');
</style>
11. 执行安装(国内可用cnpm)
npm install
12. 配置router相关文件
index.js
import { createRouter, createWebHashHistory } from 'vue-router'
import routes from './routes'
const router = createRouter({
history: createWebHashHistory(import.meta.env.BASE_URL),
routes
})
router.beforeEach((to, from, next) => {
window.document.title = to.meta.title
next()
})
export default router
routes.js
const common = [
{
path: '/:pathMatch(.*)', redirect: '/404'
},
{
path: '/401', name: 'no-access', meta: { title: '401' },
component: () => import('@/views/common/401.vue')
},
{
path: '/403', name: 'no-axios', meta: { title: '403' },
component: () => import('@/views/common/403.vue')
},
{
path: '/404', name: 'no-view', meta: { title: '404' },
component: () => import('@/views/common/404.vue')
}
]
const routes = [
{
path: '/',
component: () => import('@/views/layout/main.vue'),
redirect: { name: 'home' },
children: [
{
path: '/home', name: 'home',
meta: { title: '首页', icon: 'el-icon-house' },
component: () => import('@/views/home/home.vue')
},
{
path: 'star', name: 'star',
meta: { title: '收藏', icon: 'el-icon-star' },
component: () => import('@/views/star/star.vue')
},
{
path: 'plus', name: 'plus',
meta: { title: '会员', icon: 'el-icon-house' },
component: () => import('@/views/plus/plus.vue')
},
{
path: 'purview', name: 'purview',
meta: { title: '权限', icon: 'el-icon-purview' },
component: () => import('@/views/purview/purview.vue')
}
]
}
]
export default [...routes, ...common]