Vite+Vue3+Ts项目搭建(全套解决方案Vuex、VueRouter、elementplus、NaiveUI、全局深色模式、中英文切换)

Vite+Vue3+Ts项目搭建(全套解决方案Vuex、VueRouter、elementplus、NaiveUI、全局深色适配、国际化)

使用Vite创建vue3+ts项目

# 创建vite项目
npm init @vitejs/app
# 输入项目名称
? Project name: » vite-devil-control
# 然后选择vue (通过上下箭头切换,回车确定)
# 然后选择vue或vue+ts

集成 Vue-Router

安装(两个任选其一)

# router4.x版本
npm install vue-router@4 
# router3.x版本
npm install vue-router 

创建文件:src\router\index.ts·

import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'

// 仅做示例,根据业务修改路径等
const Login = () => import('../views/Login/Login.vue')

const routes: RouteRecordRaw[] = [
  {
    path: '/login',
    component: Login,
    name: 'Login',
    meta: {
      title: '登录页'
    }
  }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

// 前置路由
router.beforeEach((to, from, next) => {
})

export default router

main.ts做出如下修改,引入router

import {createApp} from 'vue'
import router from './router'
import App from './App.vue'

const app = createApp(App)
app.use(router)
app.mount('#app')



集成Vuex

安装vuex:

npm install vuex@next

创建store对象src\store\index.ts·

import { createStore } from 'vuex'

const store = createStore({
  state() {
    return {
      name: 'coderwhy'
    }
  }
})

export default store

安装store:

import {createApp} from 'vue'
import router from './router'
import store from './store'
import App from './App.vue'

const app = createApp(App)
app.use(router)
app.use(store)
app.mount('#app')

安装scss依赖

# 下载scss,sass-loader,sass
npm install --save-dev sass-loader
npm install --save-dev node-sass
npm install --save-dev sass



集成element-plus

安装:npm install element-plus

全局引入

全局引入很简单,官方写的很详细,在 main.ts

import ElementPlus from 'element-plus'
import 'element-plus/lib/theme-chalk/index.css'

import router from './router'
import store from './store'

createApp(App).use(router).use(store).use(ElementPlus).mount('#app')

局部引入

首先是全局非常常用的组件,例如按钮等……

安装babel的插件:npm install babel-plugin-import -D

vite.config.ts

import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'
// @ts-ignore
import styleImport from 'vite-plugin-style-import';

// https://vitejs.dev/config/
export default defineConfig({
    plugins: [
        vue(),
        styleImport({
            libs: [
                {
                    libraryName: 'element-plus',
                    esModule: true,
                    ensureStyleFile: true,
                    resolveStyle: (name) => {
                        name: name.slice(3);
                        return `element-plus/packages/theme-chalk/src/${name}.scss`;
                    },
                    resolveComponent: (name) => {
                        return `element-plus/lib/${name}`;
                    },
                },
            ],
        }),
    ]
})

上述是为了引入对应的样式,如果出现问题,可采用引入全部css的方式:import 'element-plus/lib/theme-chalk/index.css',引入全部css就不需要上述在 vite.config.ts 中的操作

新建 src/global/register-element.ts 文件

import 'element-plus/lib/theme-chalk/base.css'
import {App} from "vue";
import {
    ElButton,
    ElTable,
    ElAlert,
    ElAside,
    ElAutocomplete,
    ElAvatar,
    ElBacktop,
    ElBadge,
} from 'element-plus'

const components = [
    ElButton,
    ElTable,
    ElAlert,
    ElAside,
    ElAutocomplete,
    ElAvatar,
    ElBacktop,
    ElBadge
]


export function registerElement(app:App):void {
    for (const cpn of components) {
        app.component(cpn.name, cpn)
    }
}

src/global/index.ts

import {App} from "vue";
import {registerElement} from "./register-element";

export function registerApp(app: App): void {
    registerElement(app)
}

在 main.ts中引入

import {createApp} from 'vue'
import router from './router'
import {registerElement} from "./plugins/ElementPlus";
import App from './App.vue'


const app = createApp(App)
registerElement(app)
app.use(router)
app.mount('#app')

其次还有一些不常用的,可能只在某一两个页面使用的组件,可以使用这种方式,注册为组件后即可在当前页面正常使用。

import { ElButton } from 'element-plus'

export default defineComponent({
    components: {
        ElButton
    }
})



集成NaiveUI

安装:npm i -D naive-ui

直接引入的方式很简单,这里就不做说明;

另外 NaiveUI 组件多,非常不建议全局引入

目录说明:

image-20210814114706467

按需引入

src/global/register-naive.ts

import {
    create,
    NButton,
    NCard,
    NConfigProvider,
    NInput,
} from 'naive-ui'

const naive = create({
    components: [
        NButton,
        NCard,
        NConfigProvider,
        NInput,
    ]
})

export function registerNaive(app: any) {
    app.use(naive)
}

src/global/index.ts

import {App} from "vue";
import {registerElement} from "./register-element";
import {registerNaive} from "./register-naive";

export function registerApp(app: App): void {
    registerElement(app)
    registerNaive(app)
}

main.ts不做更改,引入

import {createApp} from 'vue'
import router from './router'
import {registerApp} from "./global";
import App from './App.vue'


const app = createApp(App)
registerApp(app)
app.use(router)
app.mount('#app')



集成Axios

安装:npm install axios

接口代理

vite.config.ts 配置接口代理,拦截所有api开头的请求,请求时删除api

import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'
// @ts-ignore
import styleImport from 'vite-plugin-style-import';

// https://vitejs.dev/config/
export default defineConfig({
    plugins: [……],
    server: {
        host: '0.0.0.0',
        proxy: {
            '^/api/.*': {
                target: 'http://localhost:8080',
                changeOrigin: true,
                rewrite: (path) => path.replace(/^\/api/, '')
            },
        }
    }
})

如果不清楚接口的代理,可以忽略此选项,跳过即可



Axios深度封装

先来看一下目录结构,service在src目录下面

image-20210814155240674

config.ts

let BASE_URL = ''
const TIME_OUT = 8000

// 生产
if (process.env.NODE_ENV === 'development') {
    // 如果没有接口代理,这里可以使用真实的接口地址,例如:https://192.168.22.1:8899
  BASE_URL = '/api'
  // 上线环境
} else if (process.env.NODE_ENV === 'production') {
  BASE_URL = '/prod'
} else {
  // 测试环境
  BASE_URL = '/'
}

export { BASE_URL, TIME_OUT }

type.ts

import type { AxiosRequestConfig, AxiosResponse } from 'axios'

export interface CVRequestInterceptors {
  requestInterceptors: (config: AxiosRequestConfig) => AxiosRequestConfig
  requestInterceptorsCatch: (error: any) => any
  responseInterceptors: (config: AxiosResponse) => AxiosResponse
  responseInterceptorsCatch: (error: any) => any
}

export interface CVRequestConfig extends AxiosRequestConfig {
  // 自定义拦截器
  interceptors?: CVRequestInterceptors,
  // 是否显示Loading
  showLoading?: boolean
}

src/service/request/index.ts

import axios from 'axios'
import type { AxiosInstance } from 'axios'
import type { CVRequestConfig, CVRequestInterceptors } from './type'
import { ElLoading } from 'element-plus'
import { ILoadingInstance } from 'element-plus/lib/el-loading/src/loading.type'

class CVRequest {
  instance: AxiosInstance
  interceptors?: CVRequestInterceptors
  showLoading: boolean
  loading?: ILoadingInstance

  // 类的构造器
  constructor(config: CVRequestConfig) {
    this.instance = axios.create(config)
    this.showLoading = config.showLoading ?? true
    this.interceptors = config.interceptors

    // request拦截器,这里解释一下->这里是接收外界传递过来的拦截器,为了增强我们Axios灵活度设定的
    this.instance.interceptors.request.use(
      this.interceptors?.requestInterceptors,
      this.interceptors?.requestInterceptorsCatch
    )

    // response拦截器,这里解释一下->这里是接收外界传递过来的拦截器,为了增强我们Axios灵活度设定的
    this.instance.interceptors.response.use(
      this.interceptors?.responseInterceptors,
      this.interceptors?.responseInterceptorsCatch
    )

    // 添加所有实例都有的拦截器
    this.instance.interceptors.request.use(
      (config) => {
        // TODO 这里使用了ElmentPlus的组件,可以自行更换
        this.loading = ElLoading.service({
          lock: true,
          text: '数据请求中...'
        })
        return config
      }, (err) => {
        return err
      }
    )

    this.instance.interceptors.response.use(
      (config) => {
        // 移除loading
        this.loading?.close()

        // 说明,后端提供的接口,如果经过规范化处理,一般包含 状态码,提示信息,核心内容
        // 这里的意思,根据后端设定,如果状态码不等于20000则说明错误,我们在页面显示错误信息,具体的显示未做实现,自行解决
        const data = res.data()
        if (data.code !== 20000) {
          // TODO 页面显示错误信息
          console.log(data.msg)
        } else {
          return data
        }
        return config
      }, (err) => {
        // 移除loading
        this.loading?.close()
        let msg = showStatus(status)
        response.data.msg = msg
        return err
      }
    )
  }

  request<T>(config: CVRequestConfig): Promise<T> {
    return new Promise((resolve, reject) => {
      if (config.interceptors?.requestInterceptors(config)) {
        config = config.interceptors?.requestInterceptors(config)
      }

      if (config.showLoading === false) {
        this.showLoading = config.showLoading
      }

      this.instance.request(config).then((res) => {
        if (config.interceptors?.requestInterceptors) {
          res = config.interceptors.responseInterceptors(res)
        }

        // 将 showLoading 值重置,不影响下一次请求
        this.showLoading = true
      }).catch(err => {
        this.showLoading = true
        return err
      })
    })
  }
}

const showStatus = (status: number) => {
  let message = ''
  switch (status) {
    case 400:
      message = '请求错误(400)'
      break
    case 401:
      message = '未授权,请重新登录(401)'
      break
    case 403:
      message = '拒绝访问(403)'
      break
    case 404:
      message = '请求出错(404)'
      break
    case 408:
      message = '请求超时(408)'
      break
    case 500:
      message = '服务器错误(500)'
      break
    case 501:
      message = '服务未实现(501)'
      break
    case 502:
      message = '网络错误(502)'
      break
    case 503:
      message = '服务不可用(503)'
      break
    case 504:
      message = '网络超时(504)'
      break
    case 505:
      message = 'HTTP版本不受支持(505)'
      break
    default:
      message = `连接出错(${status})!`
  }
  return `${message},请检查网络或联系管理员!`
}
export default CVRequest

上面的3个文件不是很清楚?没有问题

  1. config是基础的请求前缀,如果做了接口代理,则使用 api,或自行设计。
  2. 如果没有使用接口代理,则将其换成真实接口地址 https://192.168.22.1:8899
  3. type.ts 无需改动
  4. index.ts 的数据请求中,如果不需要或使用了别的UI库,可自行更换
  5. index.ts 需要自行根据后端状态码的设定进行更改,如果不需要可直接删除
  6. TODO 页面显示错误信息 这里需要自行完善,根据UI库直接展示,或不做处理都行

src/service/index.ts 这里是对我们上面的封装进行使用,暴露给最外层使用

// service统一出口
import CVRequest from './request'
import { BASE_URL, TIME_OUT } from './request/config'

// 常规接口,如果项目涉及到多个接口地址,可以创建多个,使用命名区分即可
const cvRequest = new CVRequest({
  baseURL: BASE_URL,
  timeout: TIME_OUT,
  // 创建实例的时候传递拦截器,可根据业务需求自行更改
  interceptors: {
    requestInterceptors: (config) => {
      const token = localStorage.getItem("token")
      if (token){
        config.headers.token = token
      }
      return config
    },
    requestInterceptorsCatch: (err) => {
      return err
    },
    responseInterceptors: (res) => {
      return res.data
    },
    responseInterceptorsCatch: (err) => {
      return err
    }
  }
})

export default cvRequest

使用封装后的Axios

上面我们封装了Axios,接下来我们使用Axios,当然具体的使用是非常灵活的,这里仅提供一种解决思路

创建:src/service/devil/type.ts,

注意:devil代表的是一个类型的接口,如果是登录就换成登录,以此类推

type.ts存放接口接收类型的参数

// 查询队列
export interface QueryQueuePage {
    page: number,
    size: number,
    name: string
}

// 方式消息
export interface SendMessageReq {
    content: string,
    queueName: string
}

src/service/devil/devil.ts 负责具体的接口

import cvRequest from "../index";
import {QueryQueuePage, SendMessageReq} from "./type";

/**
 * 查询所有队列名称
 */
export function findAllQueueRequest() {
    return cvRequest.get({
        url: '/broker/count/queue/name'
    })
}

/**
 * 查询队列内容
 * @param query 队列页数、每页条数、队列名称
 */
export function findQueueByNameRequest(query: QueryQueuePage) {
    return cvRequest.get({
        url: `/broker/queue/content/${query.page}/${query.size}?name=${query.name}`
    })
}

/**
 * 发送消息
 * @param message 队列名称 消息内容
 */
export function sendMsgRequest(message: SendMessageReq) {
    return cvRequest.post({
        url: `/broker/add/message`,
        data: message
    })
}

上述应该可以满足绝大多数需求了。



去除浏览器默认样式

例如默认的间距等等

安装normalize.css:npm install --save normalize.css

main.ts 中引入:import ‘normalize.css/normalize.css’

全局黑夜模式

本质上设置了两个色系的配色

新建:src/style/seeting/variable.scss,下面的配色仅做参考,是我个人喜欢的颜色,具体的根据项目进行配色

:root {
  --bg-contrast: #ffffff;
  --color-default: rgb(51, 54, 57); //文本默认颜色
  --color-light: #797675; //浅色调文本
  --color-title: rgb(34, 33, 33); //文本默认颜色
  --bg-primary: #ffffff; // 左侧菜单等颜色
  --bg-minor: #f1f5ff; // 主要部分颜色
  --font-color-light: #777c88; // 左侧/网站 浅色调字体
  --bg-hover: #f1f5ff; // 默认hover背景色
  --bg-active: #e0e8f9; // 默认active背景色
  --color-primary: #4ba9ff; // 文字主题色
  --color-primary-active: #279bfe; // 文字主题色
  --a-color: #2c98f9; // a标签颜色
  --a-color-hover: #0a7be2; // a标签hover
  --icon-color: #cccccc; // 图标color
  --border-bg-active: rgba(0, 0, 0, .1); //边框阴影
  --hr-bg: #eeeeee; // hr颜色
  --webkit-scrollbar-thumb: rgba(189, 189, 189, 0.5); // 滑块颜色
  --nav-bar-shadow: rgba(57, 66, 60, 0.2); // 左侧菜单阴影
  --tab-bar-shadow: rgba(78, 87, 79, 0.1); // 顶部菜单阴影
  --color-disappear: rgba(0, 0, 0, .16); //非常浅的颜色
  --m-btn-border: rgba(193, 191, 191, 0.69); // 自定义按钮边框颜色
  --m-btn-color: rgb(79, 77, 77); // 自定义按钮字体颜色
  --m-btn-dot: rgb(113, 109, 109); // 自定义按钮点颜色
  --bg-layer: rgba(255, 255, 255, .9); // 公司项目遮罩层背景颜色
  --md-top-bg: #F3F5F6; //md顶部背景色
  --md-line-bg: #E8F2FF; //md当前选中行背景色
  --toolbar-item-active-hover: #e8e8e8; //顶部导航栏选中样式
  --md-pre-wrapper-bg: #f8fafc;// 代码块背景颜色
  --md-hr-bg: #ddd;// md中间的分割线
  --md-box-bg: rgba(28,31,33,0.15); // md最外层盒子阴影
  --hr-color-char: #E5E5E5; //聊天页面分割线
  --char-cus-card: #f5f5f5; //聊天页面单个卡片背景色
  --char-cus-card-bg-hover: #eaffee; //聊天页面单个卡片选中背景色
}

:root[theme='dark'] {
  --bg-contrast: #000000;
  --color-default: rgba(255, 255, 255, 0.75);
  --color-light: #797675;
  --color-title: rgba(255, 255, 255, 0.85);
  --bg-primary: #18181C;
  --bg-minor: #22242c;
  --font-color-light: #d8d8d8;
  --bg-hover: #222228;
  --bg-active: rgba(255, 255, 255, 0.07);
  --color-primary: #7FE7C4;
  --color-primary-active: #65e8b0;
  --a-color: #04d220;
  --a-color-hover: #029a02;
  --icon-color: #757575;
  --border-bg-active: rgba(255, 255, 255, .2);
  --hr-bg: #363636;
  --nav-bar-shadow: rgba(219, 221, 219, 0.2);
  --tab-bar-shadow: rgba(212, 215, 212, 0.1);
  --color-disappear: rgba(255, 255, 255, .15);
  --m-btn-border: rgba(216, 216, 216, 0.7);
  --m-btn-color: rgb(211, 208, 208);
  --m-btn-dot: rgb(222, 218, 218);
  --bg-layer: rgba(210, 210, 210, 0.7);
  --md-top-bg: rgb(15, 15, 15);
  --md-line-bg: #0f0e0e;
  --toolbar-item-active-hover: rgba(0,0,0,.8);
  --md-pre-wrapper-bg: #060606;
  --md-hr-bg: #232323;
  --md-box-bg: rgba(227,224,222,0.1);
  --hr-color-char: #515151;
  --char-cus-card: #161619;
  --char-cus-card-bg-hover: #002805;
}

App.Vue中引入

<style lang="scss">
@import "style/seeting/variable.scss";
</style>

使用

例如:

html {
  background-color: var(--bg-primary);
}

配合NaiveUI切换深色模式

将切换浅色/深色的按钮绑定changeTheme方法即可

<script lang='ts'>
import {darkTheme, createTheme} from 'naive-ui'
import {computed, defineComponent, ref} from 'vue'

export default defineComponent({
  name: "Devil",
  setup() {
    let flag: any = ref(true)
    let theme: any = ref(createTheme)
    if (localStorage.getItem('theme') && localStorage.getItem('theme') === '0') {
      flag = false
      theme.value = darkTheme
      document.documentElement.setAttribute('theme', 'dark')
    }

    const changeTheme = function () {
      flag = !flag
      if (flag) {
        localStorage.setItem('theme', '1')
        theme.value = createTheme
        document.documentElement.removeAttribute('theme')
      } else {
        localStorage.setItem('theme', '0')
        theme.value = darkTheme
        document.documentElement.setAttribute('theme', 'dark')
      }
    }
    return {
      theme,
    }

  }
})
</script>

国际化

通过Vue i18n实现,官方文档:Vue I18n

在vue3中引入,注意官网的教程引入暂不支持vue3,请使用我这里的方式:npm install vue-i18n@next

创建中英文存储文件

创建存放英文的ts文件,src/i18n/en.ts,具体的内容根据自身业务自行补充

const en = {
  tab: {
    control: 'console'
  }
}
export default en

创建存放中文的ts文件,src/i18n/zh.ts

const zh = {
  tab: {
    control: '中控室'
  }
}
export default zh

创建index统一管理语言文件,src/i18n/index.ts

import en from './en'
import zh from './zh'

export default {
  en,
  zh
}

配置 i18n

创建配置文件 src/i18n/i18n.ts

import {createI18n} from "vue-i18n";
import messages from './index'

const i18n = createI18n({
  locale: localStorage.lang || 'zh', //默认中文
  messages
})

export default i18n

main.ts中引入

import {createApp} from 'vue'
import router from './router'
import 'normalize.css/normalize.css'
import {registerApp} from "./global";
import i18n from "./i18n/i18n";
import App from './App.vue'


const app = createApp(App)
registerApp(app)
app.use(i18n)
app.use(router)
app.mount('#app')

核心代码

import i18n from "./i18n/i18n";
app.use(i18n)

语言切换

找到你需要切换语言的页面

<script lang='ts'>
import {useI18n} from "vue-i18n";

export default defineComponent({
  setup() {

    const { locale } = useI18n()
    const setLangCondition = () =>{
      let lang = localStorage.getItem('lang')
      if (lang === 'en'){
        locale.value = 'zh';
        localStorage.setItem('lang', locale.value);
      }else {
        locale.value = 'en';
        localStorage.setItem('lang', locale.value);
      }
    }
    
    return {
      setLangCondition
    }
  }
})
</script>

按钮绑定 setLangCondition 方法即可,如果设置多个语言也是类似的

如果有疑问,或者不清楚的欢迎评论区留言,如果感觉对你有帮助,请点个赞再走吧

  • 11
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CV大魔王

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值