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]
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值