一、使用Visual Studio Code。
- vscode相较于webstorm
- 对ts有更加完善的提示和适配。
- 尤其是对于Vue3+Ts有比较好的开发体验。
- 所以在本章节开始调整使用vscode工具开发。
- 将项目用vscode打开。
- 开发中所必须的插件:
- Vue Language Features (Volar)
- Prettier - Code formatter
- ESLint
- 开发中要禁用的插件:
- Vetur
- vue
- 及其他代码格式化插件
- 修改.prettierrc解决换行ESLint报错的问题
{
"tabWidth": 4,
"singleQuote": true,
"semi": true,
"printWidth": 200,
"endOfLine": "auto"
}
- 执行格式化命令
npx prettier --write .
- 重启VScode
二、AntdUI组件库升级
-
AntdUI目前推出了最新版本3.2.3。
- 3.x 版本在性能、易用性、功能上都有了很大的提升
- 官网建议尽快升级 3.x 版本
-
修改package.json文件
{
"name": "vue3-admin",
"version": "0.0.0",
"scripts": {
"dev": "cross-env NODE_ENV=development vite",
"build": "vue-tsc --noEmit && vite build",
"serve": "vite preview"
},
"dependencies": {
"ant-design-vue": "^3.2.3",
......
},
"devDependencies": {
......
}
}
- 执行安装依赖命令
npm i
三、升级Vue版本到3.2.X以上
- 升级vue版本到3.2.X以上
- 修改package.json文件
{
"name": "vue3-admin",
"version": "0.0.0",
"scripts": {
"dev": "cross-env NODE_ENV=development vite",
"build": "vue-tsc --noEmit && vite build",
"serve": "vite preview"
},
"dependencies": {
"vue": "^3.2.36",
......
},
"devDependencies": {
......
}
}
- 执行安装依赖命令
npm i
四、使用setup语法糖
- 修改.eslintrc.js
module.exports = {
......
rules: {
// 解决v-model指令报红
'vue/no-v-model-argument': 'off',
......
},
};
- App.vue
<template>
<layout v-if="route.meta.layout" />
<a-spin :spinning="store.state.loading.loadingState" :delay="300" size="large" v-else>
<router-view />
</a-spin>
</template>
<script lang="ts" setup>
import layout from '@/layout/index.vue';
import { useRoute } from 'vue-router';
import { useStore } from 'vuex';
const route = useRoute();
const store = useStore();
</script>
<style lang="less">......</style>
- @/layout/index.vue
<template>
<a-layout class="layout">
<a-layout-sider v-model:collapsed="collapsed" :trigger="null" collapsible>
<div class="logo" />
<a-menu theme="dark" mode="inline" v-model:selectedKeys="selectedKeys">
<a-menu-item key="/">
<dashboard-outlined />
<span>首页</span>
</a-menu-item>
</a-menu>
</a-layout-sider>
<a-layout>
<a-layout-header style="background: #fff; padding: 0 20px">
<menu-unfold-outlined v-if="collapsed" class="trigger" @click="() => (collapsed = !collapsed)" />
<menu-fold-outlined v-else class="trigger" @click="() => (collapsed = !collapsed)" />
</a-layout-header>
<a-layout-content style="margin: 24px 16px; padding: 24px; background: #fff; min-height: 280px">
<a-spin :spinning="store.state.loading.loadingState" :delay="300" size="large">
<router-view />
</a-spin>
</a-layout-content>
</a-layout>
</a-layout>
</template>
<script lang="ts" setup>
import { DashboardOutlined, MenuUnfoldOutlined, MenuFoldOutlined } from '@ant-design/icons-vue';
import { ref } from 'vue';
import { useStore } from 'vuex';
const store = useStore();
const selectedKeys = ref<string[]>(['/']);
const collapsed = ref<boolean>(false);
</script>
<style lang="less">......</style>
- @/views/Home/index.vue
<template>
<div>
<h1>home</h1>
</div>
</template>
<script lang="ts" setup>
</script>
<style lang="less">......</style>
- @/views/Login/index.vue
<template>
<a-row class="layout" type="flex" justify="center" align="middle">
<a-card class="login_card" :bodyStyle="{ height: '100%', padding: 'unset' }" hoverable>
<div class="card_body">
<div class="login_img">
<img src="https://via.placeholder.com/320x180/fff.png?text=Tian-Admin" />
<p>Tian-Admin</p>
</div>
<div class="login_form">
<a-form class="form" ref="loginFormRef" layout="vertical" :rules="loginRules" :model="loginForm">
<a-form-item label="账号:" name="username">
<a-input v-model:value="loginForm.username" size="large" />
</a-form-item>
<a-form-item label="密码:" name="password">
<a-input-password v-model:value="loginForm.password" size="large" />
</a-form-item>
<a-form-item>
<a-button type="primary" size="large" block @click="onSubmit">提交</a-button>
</a-form-item>
</a-form>
</div>
</div>
</a-card>
</a-row>
</template>
<script lang="ts" setup>
import { reactive, ref, toRaw } from 'vue';
import { useRouter } from 'vue-router';
import { useStore } from 'vuex';
import LoginAPI from '@/request/api/loginAPI';
interface loginFormConfig {
username: string;
password: string;
}
interface loginResConfig {
token: string;
userInfo: any;
}
const router = useRouter();
const store = useStore();
const loginFormRef = ref();
const loginForm: loginFormConfig = reactive({
username: '',
password: '',
});
const loginRules = {
username: [
{
required: true,
message: '请输入用户名',
trigger: 'blur',
},
],
password: [
{
required: true,
message: '请输入密码',
trigger: 'blur',
},
],
};
const onSubmit = () => {
loginFormRef.value.validate().then(async () => {
const res = (await LoginAPI.setLogin(toRaw(loginForm))) as loginResConfig;
if (res) {
console.log(res);
store.commit('user/setToken', res.token);
store.commit('user/setUserInfo', res.userInfo);
router.push({ path: '/' });
}
});
};
</script>
<style lang="less">......</style>
五、使用Pinia替换Vuex
- Pinia介绍
- Pinia 是 Vue 的存储库,它允许您跨组件/页面共享状态。
- Pinia 提供了一个更简单的 API,具有更少的规范。
- 提供了 Composition-API 风格的 API。
- 与 TypeScript 一起使用时具有可靠的类型推断支持。
- 安装,执行命令
npm install pinia
- 安装Pinia持久化插件
- 安装,执行命令
npm install pinia-plugin-persist
- 在项目中引入
- 修改 @/main.ts
import { createApp } from 'vue';
import App from '@/App.vue';
import router from '@/router';
import store from '@/store';
import { createPinia } from 'pinia';
import piniaPluginPersist from 'pinia-plugin-persist';
import Antd from 'ant-design-vue';
import 'ant-design-vue/dist/antd.css';
const app = createApp(App);
const pinia = createPinia();
pinia.use(piniaPluginPersist);
app.use(router);
app.use(store);
app.use(pinia);
app.use(Antd);
app.mount('#app');
- 增加pinia-plugin-persist的types
- 修改tsconfig.json
{
"compilerOptions": {
......
"types": [
"pinia-plugin-persist"
]
},
......
}
- 先保留store目录,在src下新建pinia目录
- 在src/pinia目录下新建modules目录
- 在src/pinia目录下新建index.ts
- 在src/pinia/modules目录新建loading.ts和user.ts文件
- src/pinia/modules/loading.ts
import { defineStore } from 'pinia';
const useLoadingStore = defineStore('loading', {
state: () => {
return {
loadingState: false,
};
},
actions: {
showLoading() {
this.loadingState = true;
},
hideLoading() {
this.loadingState = false;
},
},
});
export default useLoadingStore;
- src/pinia/modules/user.ts
import { defineStore } from 'pinia';
const useUserStore = defineStore('user', {
state: () => {
return {
token:'',
userInfo: {},
};
},
actions: {
setToken(token: string) {
this.token = token;
},
setUserInfo(userInfo) {
this.userInfo = userInfo;
},
clearToken() {
this.token = '';
},
clearUser() {
this.userInfo = {};
},
},
persist: {
enabled: true,
strategies: [
{ key: 'token', storage: localStorage, paths: ['token'] },
{ key: 'userInfo', storage: localStorage, paths: ['userInfo'] },
],
},
});
export default useUserStore;
- src/pinia/index.ts
import useUserStore from './modules/user';
import useLoadingStore from './modules/loading';
export { useUserStore, useLoadingStore };
- 完成上述操作后
- 删除原本的/src/store目录
- 将pinia目录改为store目录
- 修改main.ts
import { createApp } from 'vue';
import App from '@/App.vue';
import router from '@/router';
import { createPinia } from 'pinia';
import piniaPluginPersist from 'pinia-plugin-persist';
import Antd from 'ant-design-vue';
import 'ant-design-vue/dist/antd.css';
const app = createApp(App);
const pinia = createPinia();
pinia.use(piniaPluginPersist);
app.use(router);
app.use(pinia);
app.use(Antd);
app.mount('#app');
六、修改项目中原本的store
- @/App.vue
<template>
<layout v-if="route.meta.layout" />
<a-spin :spinning="loadingStore.loadingState" :delay="300" size="large" v-else>
<router-view />
</a-spin>
</template>
<script lang="ts" setup>
import { useRoute } from 'vue-router';
import { useLoadingStore } from '@/store';
import layout from '@/layout/index.vue';
const route = useRoute();
const loadingStore = useLoadingStore();
</script>
<style lang="less">
......
</style>
- @/request/http.ts
......
import { useLoadingStore, useUserStore } from
......
const loadingStore = useLoadingStore();
const userStore = useUserStore();
......
const Fetch = ({
.....
}: AxiosConfig) => {
if (loading) {
loadingStore.showLoading();
}
if (userStore.token) {
Object.assign(headers, {
token: userStore.token,
});
}
return new Promise((resolve, reject) => {
instance({
url,
method,
data,
params,
headers,
})
.then((res) => {
loadingStore.hideLoading();
resolve(res.data.data);
})
.catch((err) => {
loadingStore.hideLoading();
message.error('请求失败');
reject(err);
});
});
};
export default Fetch;
- @/router/index.ts
// @/router/index.ts
import { createRouter, createWebHashHistory } from 'vue-router';
import { useUserStore } from '@/store';
......
router.beforeEach((to, from, next) => {
const userStore = useUserStore();
if (to.name !== 'Login' && !userStore.token) {
next({ name: 'Login' });
} else {
next();
}
});
export default router;
- @/layout/index.vue
<template>
<a-layout class="layout">
<a-layout-sider v-model:collapsed="collapsed" :trigger="null" collapsible>
......
</a-layout-sider>
<a-layout>
......
<a-layout-content style="margin: 24px 16px; padding: 24px; background: #fff; min-height: 280px">
<a-spin :spinning="loadingStore.loadingState" :delay="300" size="large">
<router-view />
</a-spin>
</a-layout-content>
</a-layout>
</a-layout>
</template>
<script lang="ts" setup>
......
import { useLoadingStore } from '@/store';
const loadingStore = useLoadingStore();
......
</script>
<style lang="less" scoped>
......
</style>
- @/views/Login/index
<template>
......
</template>
<script lang="ts" setup>
......
import { useUserStore } from '@/store';
......
const router = useRouter();
const userStore = useUserStore();
......
const onSubmit = () => {
loginFormRef.value.validate().then(async () => {
const res = (await LoginAPI.setLogin(toRaw(loginForm))) as loginResConfig;
if (res) {
console.log(res);
userStore.setToken(res.token);
userStore.setUserInfo(res.userInfo);
router.push({ path: '/' });
}
});
};
</script>
<style lang="less" scoped>
......
</style>
七、源代码地址
https://github.com/jiangzetian/vue3-admin-template
八、视频演示及源码
本文演示视频:点击浏览
更多前端内容欢迎关注公众号:天小天个人网