卸载 vue – 安装 vue
npm uninstall vue-cli - g
vue install vue-cli -g
vue -v
vite 官网学习
开始构建越来越大型的应用时,需要处理的 JavaScript
代码量也呈指数级增长。包含数千个模块的大型项目相当普遍。我们开始遇到性能瓶颈 —— 使用 JavaScript
开发的工具通常需要很长时间(甚至是几分钟!)才能启动开发服务器,即使使用 HMR
,文件修改后的效果也需要几秒钟才能在浏览器中反映出来。如此循环往复,迟钝的反馈会极大地影响开发者的开发效率和幸福感。
1.Vite
旨在利用生态系统中的新进展解决上述问题:浏览器开始原生支持 ES
模块,且越来越多
2. JavaScript
工具使用编译型语言编写。
3. Vite
天然支持引入 .ts
文件。
构建vite + vueTs
npm init vite@latest
你还可以通过附加的命令行选项直接指定项目名称和你想要使用的模板。例如,要构建一个 Vite + Vue 项目,运行:
npm init vite@latest my-vue-app --template vue
# npm 7+, 需要额外的双横线:
npm init vite@latest my-vue-app -- --template vue
Vite
是什么:vite
是下一代前端开发构建工具,同时它的插件API
和JavaScript AP
I 带来了高度的可扩展性,并有完整的类型支持
.ts
文件Vite
通过在一开始将应用中的模块区分为 依赖 和 源码 两类,改进了开发服务器启动时间。使用缓慢的更新element-plus 中文官网
# NPM
$ npm install element-plus --save
element.ts
import ElementPlus from 'element-plus'
import * as ElementPlusIcons from '@element-plus/icons-vue'
import {App} from 'vue'
export default (app: App) => {
app.use(ElementPlus)
for (const [key, component] of Object.entries(ElementPlusIcons)) {
app.component(key, component)
}
}
App.vue
import { createApp } from 'vue'
import App from './App.vue'
import elementPlus from "./plugins/element";
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
const app = createApp(App)
// 注入element-plus
elementPlus(app)
app.use(ElementPlus)
app .mount('#app')
VueX 官网
Vuex
是一个专为 Vue.js
应用程序开发的状态管理模式 + 库
。它采用集中式存储管理应用的所有组件的状态
,并以相应的规则保证状态以一种可预测的方式发生变化。
白话文,中间缓存件
Vuex
的状态存储是响应式
的。当Vue
组件从store
中读取状态的时候,若 store
中的状态发生变化,那么相应的组件也会相应地得到高效更新
。
你不能直接改变 store
中的状态。改变 store
中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。
Vuex
可以帮助我们管理共享状态,并附带了更多的概念和框架。这需要对短期和长期效益进行权衡。
如果您不打算开发大型单页应用,使用 Vuex
可能是繁琐冗余
的。确实是如此——如果您的应用够简单,您最好不要使用 Vuex。一个简单的 store
模式就足够您所需了。但是,如果您需要构建一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态,Vuex
将会成为自然而然的选择。
Vuex
是一个状态存储响应式管理器,可以对于复杂应用提供一个很好的缓存中间件,方便抽出共享的变量响应式的存储进去,方便后期维护和开发维护
npm install vuex@next --save
store/index.ts
import { createStore } from 'vuex'
const store = createStore({
state () {
return {
count: 0
}
},
mutations: {
increment (state) {
state.count++
}
}
})
export default store
main.ts
import { createApp } from 'vue'
import App from './App.vue'
import elementPlus from "./plugins/element";
import store from "./store";
const app = createApp(App)
// 注入element-plus
elementPlus(app)
// 使用store
app.use(store)
app .mount('#app')
一般在使用的时候,会进行拆分成模块使用,配置全局配置,和一些特殊状态变量改变一下举例:
创建store/modules
app.ts 主目录配置
user.ts 用户权限状态配置
import {Module} from "vuex";
/**
* app.ts 主目录配置
*/
export interface AppModule {
config: any
}
const app: Module<AppModule, any> = {
namespaced: true,
state: {
config: {}
},
mutations: {
setConfig(state, data) {
state.config = data
}
}
}
import {Module} from "vuex";
/**
* user.ts
*/
export interface UserModule {
token: string
user: Record<string, any>
sidebar: any[]
permissions: string[]
}
const user: Module<UserModule, any> = {
namespaced: true,
state: {
token: '',
user: {},
// 菜单
sidebar: [],
// 权限
permissions: []
},
mutations: {
setToken(state, data) {
state.token = data
},
setUser(state, data) {
state.user = data
},
setSidebar(state, data) {
state.sidebar = data
},
setPermissions(state, data) {
state.permissions = data
}
}
}
export default user
import app, { AppModule } from './app'
import user, { UserModule } from './user'
/**
* index.ts
*/
export interface rootState {
app: AppModule
user: UserModule
}
export default {
app,
user
}
import { GetterTree } from 'vuex'
import { rootState } from './moudules'
/**
* getters.ts
*/
const getters: GetterTree<rootState, any> = {
// token
token: state => state.user.token,
// 管理员信息
userInfo: state => state.user.user,
// 通用配置
config: state => state.app.config,
// 权限列表
permissions: state => state.user.permissions,
sidebar: state => state.user.sidebar
}
export default getters
import { createStore, Store, useStore as baseUseStore } from 'vuex'
import getters from './getters'
import modules from './moudules'
/**
* index.ts
*/
const store = createStore({
modules: modules,
getters
})
export default store
Vue 路由
npm install vue-router@4
router/static.ts
import { RouteRecordRaw } from 'vue-router'
import Layout from '@/layout/index.vue'
// Symbol 其存在就是为了确保对象属性唯一,不会存在属性冲突。
export const constRoutes: RouteRecordRaw[] = [
{
path: '/login',
component: () => import('@/views/account/login.vue')
},
]
export const indexRouteName = Symbol()
export const indexRoute: RouteRecordRaw = {
path: '/',
component: Layout,
name: indexRouteName
}
index.ts
import axios from "axios";
import { ElMessage } from "element-plus";
import { throttle } from "echarts";
import store from "@/store";
const request = axios.create({
baseURL: `${import.meta.env.VITE_APP_BASE_URL}/api`,
timeout: 3000,
headers: {
"Content-Type": "application/json",
version: import.meta.env.version
}
});
/**
* 定义事件
*/
const eventResponse = {
// 失败
error: ({ msg }: any): Promise<any> => {
return Promise.reject(msg);
},
// 重定向
// throttle 函数节流的原理: 使用定时器做时间节流. 当触发一个事件时,
// 先用setTimeout让这个事件延迟一小段时间在执行.
// 如果在这个时间间隔内又触发了事件,
// 就clearTimeout原来的定时器,
// 在setTimeout一个新的定时器重复以上流程
redirect: throttle(() => {
store.dispatch("user/logout").then(() => {
location.reload();
});
return Promise.reject();
})
};
/**
* 设置响应拦截器 推送事件弹窗。 同时增加组件封装弹窗
*/
request.interceptors.response.use(
response => {
switch (response.data.code) {
// 成功
case 200:
return response.data
case 500:
return eventResponse.error(response.data);
// 权限过期
case -1:
return eventResponse.redirect();
}
},
error => {
console.log(error);
ElMessage({ type: "error", message: "系统错误联系开发" });
return Promise.reject(error);
}
);
export default request;
main.ts
import { createApp } from 'vue'
import App from './App.vue'
import elementPlus from "./plugins/element";
import store from "./store";
import router from "./router";
const app = createApp(App)
// 注入element-plus
elementPlus(app)
// 使用store
app.use(store)
app.use(router)
app .mount('#app')
创建登录页面
login.vue
<template>
<div>
你好
div>
template>
<script lang="ts">
export default {
name: ''
}
script>
<style scoped>
style>
整个项目搭建完成之后。开始书写管理后台
import axios from "axios";
const request = axios.create({
baseURL: `${import.meta.env.VITE_APP_BASE_URL}/api`,
timeout: 3000,
headers: {
"Content-Type": "application/json",
version: import.meta.env.version
}
});
export default request;
npm i mockjs -D
npm i vite-plugin-mock -D
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
// 解决@ 映射到全路径地址信息
import path from "path";
import { viteMockServe } from "vite-plugin-mock";
// https://vitejs.dev/config/
export default defineConfig({
// 解决编译@问题
resolve: {
alias: {
"@": path.resolve(__dirname, "src")
}
},
base: "/admin/",
plugins: [vue(),
viteMockServe({
mockPath: "./src/mock",
localEnabled: true, // 开发
prodEnabled: false,// 生产
injectFile: path.resolve("src/main.ts"), // 解决读取不到mock.ts 问题注入文件
})
],
server: {
host: "0.0.0.0",//ip地址
port: 10086, // 设置服务启动端口号
open: true // 设置服务启动时是否自动打开浏览器
}
});
建立包./src/mock 映射上文配置mockPath: “./src/mock”,扫描
书写mock请求
import { MockMethod } from 'vite-plugin-mock'
export default [
{
url: '/api/getConfig',
method: 'get',
response: () => {
return 'mock 成功'
}
}
] as MockMethod[] // ts 类型返回数组
请求测试
这里的请求axios请求的时候不能加api 因为在使用创建axios 实例的时候我们加了baseUrl,所以如果加了api 那么映射的 /api/api/getConfig
同时因为是mock 数据在本机需要修改本机的baseurl 不要打到后端, 需要修改打到本机
修改前是后端路由, 127.0.0.1:8080
修改是服务本机地址 127.0.0.1:10086 用来访问mock
配置页面点击试试
<template>
<button @click="getUrl">点击</button>
34324
</template>
<script lang='ts' setup>
import { apiConfig } from "@/api/app";
function getUrl(){
apiConfig()
}
</script>
<style scoped>
</style>
可以访问
进度条 官网
npm install --save nprogress
直接调用 start()或者done()来控制进度条。
vite-env.d.ts 配置
declare module 'nprogress' {
export function configure(options: any): void
export function start(): void
export function done(): void
}
import 'nprogress/nprogress.css'
import NProgress from 'nprogress
NProgress.start();
NProgress.done();
官网解释
可让您在不编写类的情况下使用 state(状态) 常用 为 useState useEffect
const [count, setCount] = useState(0)
// count 当前值
// setcount 当前参数自带一个设置方法
const [count, setCount] = useState(0);
// count 当前值
// setcount 当前参数自带一个设置方法
useEffect(() => {
let timer = setInterval(() => {
setCount(count + 1)
}, 1000);
// 当在 useEffect 的回调函数中返回一个函数时,这个函数会在组件卸载前被调用
return () => clearInterval(timer)
// count发生变化时再次执行
}, [count]);
// 组件调用 自动运行定时器,赋新值,容器销毁自动销毁,不会卡内存泄漏
<template>
<el-container class="heightMax">
<el-aside width="77%">
<div class="demo-image__lazy">
<el-image class="heightMax" v-for="url in urls" :key="url" :src="url" lazy />
div>
el-aside>
<el-main class="mainMax">
<div class="login-body">
<div class="login-container">
<div class="head">
<img class="logo" src="https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg" alt="" />
<div class="name">
<div class="title">商城div>
div>
div>
<el-form label-position="top"
:model="state.ruleForm"
ref="formRef"
:rules="rules"
class="login-form">
<el-form-item style="color:#545c64" prop="account">
<el-input type="text"
v-model.trim="state.ruleForm.account"
autocomplete="off"
placeholder="账号">
el-input>
el-form-item>
<el-form-item prop="password">
<el-input type="password"
v-model.trim="state.ruleForm.password"
autocomplete="off"
placeholder="密码">
el-input>
el-form-item>
<el-form-item>
<div style="color: rgb(67, 151, 84);">登录表示您已同意
<a @click="visible = true" style=" color: chocolate;">《服务条款》a>
div>
<el-button style="width: 100%" type="primary" @click="submitForm(formRef)">立即登录el-button>
<el-checkbox v-model="state.checked" @change="!state.checked">下次自动登录el-checkbox>
el-form-item>
el-form>
div>
div>
el-main>
el-container>
<el-dialog v-model="visible" :show-close="false">
<template #header="{ close, titleId, titleClass }">
<div class="my-header">
<h4 :id="titleId" :class="titleClass">服务条款!h4>
<el-button type="danger" @click="close">
<el-icon class="el-icon--left">
<CircleCloseFilled />
el-icon>
关闭
el-button>
div>
template>
服务条款文档
el-dialog>
template>
<script lang="ts" setup>
import type { FormInstance, FormRules } from "element-plus";
import { computed, onMounted, reactive, ref, Ref } from "vue";
import { ElForm, ElInput, ElMessage } from "element-plus";
import store from "@/store";
import { useAdmin } from "@/core/hooks/app";
const { route,router } = useAdmin()
const config = computed(() => store.getters.config);
const formRef = ref<FormInstance>();
const visible = ref(false);
const state = reactive({
ruleForm: {
account: "",
password: ""
},
checked: true,
});
const rules = reactive<FormRules>({
account: [
{ required: true, message: "账户不能为空", trigger: "blur" }
],
password: [
{ required: true, message: "密码不能为空", trigger: "blur" }
]
})
function submitForm(formEl: FormInstance | undefined) {
if (!formEl) return;
formEl.validate((valid) => {
if (valid) {
store.dispatch("user/login", state.ruleForm).then(() => {
// 增加搜索属性
const {
query: { redirect }
} = route
// 定位到搜索属性路由
const path = typeof redirect === "string" ? redirect : "/";
// 跳转连接
router.replace(path);
}).catch(err => {
state.ruleForm.password = "";
ElMessage.error("登录失败");
});
}
});
}
const urls = [
"https://fuss10.elemecdn.com/9/bb/e27858e973f5d7d3904835f46abbdjpeg.jpeg"
];
script>
<style scoped lang="scss">
@import "../src/styles/common";
style>
@import “…/src/styles/common”; 封装样式
.heightMax {
height: 100%;
}
.mainMax {
padding-top: 0;
padding-bottom: 0;
display: flex;
}
.demo-image__lazy {
height: 100%;
overflow-y: auto;
}
.demo-image__lazy .el-image {
display: block;
min-height: 200px;
margin-bottom: 10px;
}
.demo-image__lazy .el-image:last-child {
margin-bottom: 0;
}
.login-body {
display: flex;
width: 100%;
background-color: #fff;
}
.my-header {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.login-container {
width: 100%;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 21px 41px 0 rgba(0, 0, 0, 0.2);
}
.head {
display: flex;
justify-content: center;
align-items: center;
padding: 40px 0 20px 0;
}
.head img {
width: 100px;
height: 100px;
margin-right: 20px;
}
.head .title {
font-size: 28px;
color: #1BAEAE;
font-weight: bold;
}
.head .tips {
font-size: 12px;
color: #999;
}
.login-form {
width: 70%;
margin: 0 auto;
}
存储token
import { Module } from "vuex";
import cache from "@/utils/cache";
import { TOKEN } from "@/config/cachekey";
import { apiLogin, apiLogout, apiUserInfo } from "@/api/user";
export interface UserModule {
token: string;
user: Record<string, any>;
sidebar: any[];
permissions: string[];
}
const user: Module<UserModule, any> = {
namespaced: true,
state: {
token: cache.get(TOKEN) || "",
user: {},
// 菜
sidebar: [],
// 权限
permissions: []
},
mutations: {
setToken(state, data) {
state.token = data;
},
setUser(state, data) {
state.user = data;
},
setSidebar(state, data) {
state.sidebar = data;
},
setPermissions(state, data) {
state.permissions = data;
}
},
actions: {
//清除用户信息
clearUserCache({ commit }) {
commit("setToken", "");
commit("setUser", {});
commit("setPermissions", {});
},
// 登录
login({ commit }, payload: any) {
const { account, password } = payload;
return new Promise((resolve, reject) => {
apiLogin({
account: account.trim(),
password: password
}).then((data: any) => {
commit("setToken", data.token);
cache.set(TOKEN, data.token);
resolve(data);
})
.catch((error) => {
reject(error);
});
});
},
// 退出登录
logout({ dispatch }) {
return new Promise((resolve, reject) => {
apiLogout()
.then((data) => {
cache.remove(TOKEN);
resolve(data);
})
.catch((error) => {
reject(error);
});
});
}
}
};
export default user;
暴露 user
import { createStore, Module, Store, useStore as baseUseStore } from "vuex";
import app, { AppModule } from "@/store/modules/app";
import user, { UserModule } from "@/store/modules/user";
interface rootState {
app: AppModule;
user: UserModule;
}
const store = createStore<rootState>({
modules: { app, user },
getters: {
config: state => state.app.config,
token: state => state.user.token
}
});
export default store;
在tsConfig.json 编译选项添加
"paths": {
"@/*": ["./src/*"]
}
vite.config.ts 配置
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// @ts-ignore
import path from 'path'
// https://vitejs.dev/config/
export default defineConfig({
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
},
},
plugins: [vue()]
})
安装node-sass 或 sass
npm install node-sass
参考:VITE_APP_BASE_URL='http://127.0.0.1:8080'