赞
踩
官网地址:Ant Design Vue — An enterprise-class UI components based on Ant Design and Vue.js
下载antd插件
yarn add ant-design-vue
在main.ts文件中引入antd
- import Antd from "ant-design-vue"
- import "ant-design-vue/dist/antd.css"
- app.use(Antd)
- <template>
- <div style="background: #001529; height:100vh">
- <a-card title="用户登录" :bordered="true" class="loginBox">
- <a-form
- :model="formState"
- name="basic"
- :label-col="{ span: 8 }"
- :wrapper-col="{ span: 16 }"
- autocomplete="off"
- @finish="onFinish"
- @finishFailed="onFinishFailed"
- >
- <a-form-item
- label="账号"
- name="account"
- :rules="[{ required: true, message: '请输入账号!' }]"
- >
- <a-input v-model:value="formState.account" />
- </a-form-item>
- <a-form-item
- label="密码"
- name="password"
- :rules="[{ required: true, message: '请输入密码!' }]"
- >
- <a-input-password v-model:value="formState.password" />
- </a-form-item>
- <a-form-item :wrapper-col="{ offset: 8, span: 16 }">
- <a-button type="primary" html-type="submit">登录</a-button>
- </a-form-item>
- </a-form>
- </a-card>
- </div>
- </template>
-
- <script lang="ts">
- interface FormState {
- account: string;
- password: string;
- }
- import { reactive } from "vue";
- export default {
- setup() {
- const formState = reactive<FormState>({
- account: "",
- password: "",
- });
- const onFinish = (values: any) => {
- console.log("Success:", values);
- };
-
- const onFinishFailed = (errorInfo: any) => {
- console.log("Failed:", errorInfo);
- };
- return {
- formState,
- onFinish,
- onFinishFailed,
- };
- },
- };
- </script>
- <style>
- .loginBox{
- width: 380px;
- position:absolute;
- top:50%;
- left: 50%;
- transform: translate(-50%,-50%);
- }
- </style>
安装axios
yarn add axios
定义UserType接口
- export default interface UserType{
- account: string,
- password:string
- }
编写登录接口
- import axios from 'axios'
- axios.defaults.baseURL="http://www.zhaijizhe.cn:3001"
- import UserType from '../../type/UserType'
- export default{
- loginApi:(data:UserType)=>axios.post("/users/login",data)
- }
汇总api
- import users from './moudules/users'
- export default{
- users
- }
在Login.vue组件的onFinish函数中调用登录api方法实现用户登录
- import { reactive } from "vue";
- import {useRouter} from 'vue-router'
- import { message } from 'ant-design-vue';
- import api from "../http/api";
- export default {
- setup() {
- const router=useRouter() //实例化路由对象
- const formState = reactive<FormState>({
- account: "",
- password: "",
- });
- const onFinish = async(values: any) => {
- const result=await api.users.loginApi(values)
- if(result.data.code){
- //将token保存到localStorage中
- localStorage.setItem('token',result.data.data.token)
- //将用户信息保存到状态机中
- //进行路由跳转
- router.replace({
- path:'/'
- })
- }else{
- message.error('用户名或密码有误!');
- }
- };
- }
在src/http下新建interceptor.js文件,将axios拦截器的代码编写在这里
- import axios from "axios";
- import { message } from 'ant-design-vue';
- axios.interceptors.request.use((config:any)=>{
- //携带token到请求头
- config.headers.Authorization=localStorage.getItem('token')
- return config;
- })
-
- axios.interceptors.response.use(res=>{
- console.log('------拦截器-------');
- if(res.data.code==1){
- return res;
- }else{
- message.error( res.data.msg);
- return Promise.reject(res);
- }
- },err=>{
- switch(err.response.status){
- case 500:
- message.error("服务端后台出现500错误");
- break;
- case 401:
- message.error("服务端后台出现401错误");
- break;
- case 404:
- message.error("没有找到服务端相应资源");
- break;
- default:
- }
- return Promise.reject(err);
- })
- <template>
- <a-layout>
- <a-layout-sider v-model:collapsed="collapsed" :trigger="null" collapsible>
- <div class="logo">
- <img src="../assets/logo.png" alt="">
- 企业管理系统
- </div>
- <a-menu v-model:selectedKeys="selectedKeys" theme="dark" mode="inline">
- <a-menu-item key="1">
- <user-outlined />
- <span>nav 1</span>
- </a-menu-item>
- <a-menu-item key="2">
- <video-camera-outlined />
- <span>nav 2</span>
- </a-menu-item>
- <a-menu-item key="3">
- <upload-outlined />
- <span>nav 3</span>
- </a-menu-item>
- </a-menu>
- </a-layout-sider>
- <a-layout>
- <a-layout-header style="background: #fff; padding: 2">
- <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', minHeight: '580px' }"
- >
- Content
- </a-layout-content>
- </a-layout>
- </a-layout>
- </template>
- <script lang="ts">
- import {
- UserOutlined,
- VideoCameraOutlined,
- UploadOutlined,
- MenuUnfoldOutlined,
- MenuFoldOutlined,
- } from '@ant-design/icons-vue';
- import { defineComponent, ref } from 'vue';
- export default defineComponent({
- components: {
- UserOutlined,
- VideoCameraOutlined,
- UploadOutlined,
- MenuUnfoldOutlined,
- MenuFoldOutlined,
- },
- setup() {
- return {
- selectedKeys: ref<string[]>(['1']),
- collapsed: ref<boolean>(false),
- };
- },
- });
- </script>
- <style>
- .logo{
- height: 50px;
- color: white;
- font-size: 18px;
- line-height: 50px;
- text-align: center;
- }
- .logo img{
- width: 40px;
- height: 40px;
- border-radius: 50%;
- }
- #components-layout-demo-custom-trigger .trigger {
- font-size: 18px;
- line-height: 64px;
- padding: 0 24px;
- cursor: pointer;
- transition: color 0.3s;
- }
-
- #components-layout-demo-custom-trigger .trigger:hover {
- color: #1890ff;
- }
-
- #components-layout-demo-custom-trigger .logo {
- height: 32px;
- background: rgba(255, 255, 255, 0.3);
- margin: 16px;
- }
-
- .site-layout .site-layout-background {
- background: #fff;
- }
- </style>
首先在<a-menu-item>
标签的key属性中写上二级路由的path路径保持一致
其次在<a-menu>
标签上绑定@click="handClick"方法
- <a-menu
- v-model:selectedKeys="selectedKeys"
- theme="dark"
- mode="inline"
- @click="handClick">
- <a-menu-item key="/userList">
- <user-outlined />
- <span>用户管理</span>
- </a-menu-item>
- <a-menu-item key="/categoryList">
- <video-camera-outlined />
- <span>分类管理</span>
- </a-menu-item>
- <a-menu-item key="/goodsList">
- <upload-outlined />
- <span>商品管理</span>
- </a-menu-item>
- </a-menu>
- import {useRouter} from 'vue-router'
- export default defineComponent({
- setup() {
- const router=useRouter()
- const handClick=(item:any)=>{
- router.push(item.key)
- }
- return {
- handClick
- };
- },
- })
设置二级路由出口
- <a-layout-content
- :style="{ margin: '24px 16px', padding: '24px', background: '#fff',
- minHeight: '580px' }">
- <!--二级路由出口 -->
- <router-view></router-view>
- </a-layout-content>
在src/router/index.ts中配置路由守卫
- router.beforeEach(to=>{
- const token=localStorage.getItem("token")
- if(!token&&to.path!=="/login"){
- return "/login"
- }
- })
首先通过创建pinia仓库
- //导入defineStore函数
- import {defineStore} from 'pinia'
- import api from '@/api'
- import IUserState from '@/types/IUserState'
- import {RouteRecordRaw} from 'vue-router'
- import {getViews} from '@/utils/routedync'
- //通过该函数创建store对象
- const useUserStore=defineStore('users',{
- state:():IUserState=>{
- return{
- token:'',
- permissionList:[]
- }
- },
- getters:{
- //返回首页路由对象
- getHomeRoute(state:IUserState){
- let routeObj:RouteRecordRaw={
- path:'/',
- component:()=>import('@/views/Home.vue'),
- children:[]
- }
- let ary:Array<RouteRecordRaw>=[]
- state.permissionList.forEach((item:any)=>{
- if(item.children){
- item.children.forEach((subItem:any)=>{
-
- ary.push({
- path:subItem.path,
- component:getViews(`../views${subItem.path}.vue`)
- })
- })
- }
- })
- routeObj.children=ary
- return routeObj
- }
- },
- actions:{
- setToken(token:string){
- this.token=token
- },
- async getAuthMenuAsync(){
- const result=await api.users.getAuthMenus()
- this.permissionList=result.data.data
- console.log('权限',result.data.data);
- }
- },
- persist:{
- enabled:true,
- strategies:[
- {
- key:'users',
- storage:localStorage
- }
- ]
- }
- })
- export default useUserStore
其中要注意,这里边不能使用路由懒加载,动态路由的组件地址全部获取,可以在utils文件夹下编写routedync.ts
- export function getViews(path:string){
- let modules=import.meta.glob('../**/*.vue')
- return modules[path]
- }
当然,还学要在store文件夹下创建index.ts
- import {createPinia} from 'pinia'
- import piniaPluginPersist from 'pinia-plugin-persist'
- const pinia=createPinia()
- pinia.use(piniaPluginPersist)
- export default pinia
然后在main.ts中引入
- import {createApp} from 'vue'
- import App from '@/App.vue'
- import Antd from "ant-design-vue"
- import "ant-design-vue/dist/antd.css"
- import router from './router'
- import pinia from './store'
- import * as icons from '@ant-design/icons-vue'
-
- const app=createApp(App)
- for (const i in icons) {
- app.component(i, icons[i])
- }
- app.use(pinia) //设置pinina插件到vue实例上
- app.use(Antd) //设置antd插件到vue实例上
- app.use(router) //设置router插件到vue实例上
- app.mount('#app')
在Home.vue中调用并渲染
- <template>
- <div class="container">
- <div class="sider">
- <!-- <ul>
- <li>
- <router-link to="/users">用户管理</router-link>
- </li>
- <li>
- <router-link to="/product">商品管理</router-link>
- </li>
- </ul> -->
- <a-menu
- mode="inline"
- theme="dark">
- <a-sub-menu v-for="item in permissionList" :key="item._id">
- <template #icon>
- <component :is="item.icon"></component>
- </template>
- <template #title>{{item.title}}</template>
- <a-menu-item :key="subItem._id" v-for="subItem in item.children" @click="go(subItem.path)">
- <template #icon>
- <component :is="subItem.icon"></component>
- </template>
- <span>{{ subItem.title}}</span>
- </a-menu-item>
- </a-sub-menu>
-
- </a-menu>
- <!-- {{ permissionList }} -->
- </div>
- <div class="content">
- <!-- 二级路由出口 -->
- <router-view></router-view>
- </div>
- </div>
- </template>
-
- <script lang='ts' setup>
- import {Menu} from 'ant-design-vue'
- import useUserStore from '@/store/users';
- import {useRouter} from 'vue-router'
- const store=useUserStore()
- const nav=useRouter()
- const permissionList=store.permissionList
- const go=(path:string)=>{
- console.log('path',path);
- nav.push({
- path
- })
- }
- </script>
下载完成后在 main.js 中添加
- import { createApp } from 'vue'
- import App from './App.vue'
- import * as Icons from '@ant-design/icons-vue'
- const app = createApp(App)
- // 注册图标组件
- for (const i in Icons) {
- app.component(i, Icons[i])
- }
- app.mount('#app)
在vue文件中使用 <component :is="icon">
- <a-sub-menu v-for="item in permissionList" :key="item._id">
- <template #icon>
- <component :is="item.icon"></component>
- </template>
- </a-sub-menu>
在router/index.ts中编写动态路由挂载方法
- import {RouteRecordRaw,createRouter,createWebHashHistory,createWebHistory} from 'vue-router'
- //定义路由规则对象集合
- import api from '@/api'
- import {message} from 'ant-design-vue'
- // import useUserStore from '@/store/users'
- const routes:Array<RouteRecordRaw>=[
- {
- path:'/login',
- name:'login',
- component:()=>import('@/views/Login.vue')
- },
- {
- path:'/',
- redirect:'/home'
- },
- {
- path:'/home',
- component:()=>import('@/views/Home.vue'),
- }
- ]
- //这里是关键代码
- const dyncRoute=()=>{
- const store=useUserStore()
- //完成向pinia发送请求,然后获取权限
- store.getAuthMenuAsync()
- //调用首页路由对象
- const homeRoute:RouteRecordRaw=store.getHomeRoute
- //添加到路由对象上
- router.addRoute(homeRoute)
- console.log('路由集合',router.getRoutes());
- }
-
- //定义router对象
- const router=createRouter({
- routes:routes,
- //history:createWebHashHistory() //hash模式,
- history:createWebHistory() //history模式
- })
- router.beforeEach(async(to,from,next)=>{
- //执行useUserStore方法
- if(to.path=="/login"){
- next()
- }else{
- //没有登录直接进入
- //获取token
- let token=localStorage.getItem('token')
- console.log('token是否',token);
- //如果没有token
- if(!token){
- //跳转到登录页面上去
- next("/login")
- }else{
- try {
- await api.users.getUserInfo()
- dyncRoute()
- //继续进行导航
- next()
- } catch (error) {
- message.error('token已经失效,请重新登录')
- next('/login')
- }
- }
- }
- })
- export default router
路由丢失的主要原因是因为执行顺序的问题,解决办法是将动态路由的添加移到main.ts中 ,删除掉router/index.ts中的动态路由添加,一定要注意动态路由的添加必须放在app.use(router)之前,app.use(pinia)之后
- import {createApp} from 'vue'
- import App from '@/App.vue'
- import Antd from "ant-design-vue"
- import "ant-design-vue/dist/antd.css"
- import router from './router'
- import pinia from './store'
- import * as icons from '@ant-design/icons-vue'
- import useUserStore from '@/store/users'
- import {RouteRecordRaw} from 'vue-router'
- const app=createApp(App)
- for (const i in icons) {
- app.component(i, icons[i])
- }
-
- const dyncRoute=()=>{
- const store=useUserStore()
- //完成向pinia发送请求,然后获取权限
- store.getAuthMenuAsync()
- //调用首页路由对象
- const homeRoute:RouteRecordRaw=store.getHomeRoute
- //添加到路由对象上
- router.addRoute(homeRoute)
- console.log('路由集合',router.getRoutes());
- }
-
-
- app.use(pinia) //设置pinina插件到vue实例上
- app.use(Antd) //设置antd插件到vue实例上
-
- dyncRoute()
- //在app.use(router)之前进行路由添加调用,可以解决页面刷新的问题
- app.use(router) //设置router插件到vue实例上
- app.mount('#app')
整体的思路就是封装一个选项卡组件
- import {defineStore} from 'pinia'
- import {RouteMeta} from 'vue-router'
- import {unique} from '@/utils/aryutils'
-
- interface IRouteType{
- tabAry:Array<RouteMeta>
- }
- const useTabStore=defineStore('tabs',{
- state:():IRouteType=>{
- return{
- tabAry:[]
- }
- },
- getters:{
- getTabAry(state){
- return state.tabAry
- }
- },
- actions:{
- addTabs(item:RouteMeta){
- this.tabAry.push(item)
- //删除掉数组中的空对象
- var filterNullAry = this.tabAry.filter(value => Object.keys(value).length !== 0);
- // let index=0
- // this.tabAry.forEach((value:RouteMeta,i:number)=>{
- // if( Object.keys(value).length==0){
- // index=i;
- // }
- // })
- // this.tabAry[index]={title:'工作台',path:'/home/workplace'}
- // //按照对象属性去重
- let uniqueAray= unique(this.tabAry,"title")
- this.tabAry=uniqueAray
- },
- removeTab(title:string){
- const ary=this.tabAry.filter((item:RouteMeta)=>{
- return item.title!=title
- })
- this.tabAry=ary
- }
- }
- })
- export default useTabStore
- 注意:这里封装一个按照对象进行数组中去重的方法
-
- export const unique=(arr:any,u_key:string)=> {
- let map = new Map()
- arr.forEach((item:any,index:number)=>{
- if (!map.has(item[u_key])){
- map.set(item[u_key],item)
- }
- })
- return [...map.values()]
- }
- router.beforeEach(async(to,from,next)=>{
- //执行useTabStore方法
- const store=useTabStore()
- if(to.path=="/login"){
- next()
- }else{
- //没有登录直接进入
- //获取token
- let token=localStorage.getItem('token')
- console.log('token是否',token);
- //如果没有token
- if(!token){
- //跳转到登录页面上去
- next("/login")
- }else{
- try {
- await api.users.getUserInfo()
- store.addTabs(to.meta) //关键代码
- next()
-
- } catch (error) {
- message.error('token已经失效,请重新登录')
- next('/login')
- }
- }
- }
- })
- <template>
- <div>
- <div class="tab" v-for="(item,index) in tabs"
- :key="index" @click="go(item.path)">{{item.title }}<span style="margin-left:10px" @click="removeTab(item.title)">×</span></div>
- </div>
- </template>
-
- <script lang='ts' setup>
- import { defineProps } from "vue";
- import { RouteMeta,useRouter } from "vue-router";
- import useTabStore from '@/store/tabs'
- defineProps<{ tabs: Array<RouteMeta> }>();
- const router=useRouter()
- const store=useTabStore()
- const go=(path:string)=>{
- router.push(path)
- }
- const removeTab=(title:string)=>{
- store.removeTab(title)
- }
- </script>
-
- <style lang='scss' scoped>
- .tab {
- cursor: pointer;
- display: inline-block;
- font-size: 14px;
- font-weight: 500;
- height: 40px;
- line-height: 40px;
- text-align: center;
- padding: 0 15px;
- }
- </style>
- </template>
- <div class="content">
- <TabComponet :tabs="tabs"></TabComponet>
- <!-- 二级路由出口 -->
- <router-view></router-view>
- </div>
- </template>
- <script lang='ts' setup>
- import { Menu } from "ant-design-vue";
- import useUserStore from "@/store/users";
- import { useRouter } from "vue-router";
- import TabComponet from "@/components/TabComponet.vue";
- import useTabStore from "@/store/tabs";
- import {storeToRefs} from 'pinia'
- const store1 = useTabStore();
- const tabs = storeToRefs(store1).getTabAry;
- const store = useUserStore();
- const nav = useRouter();
- const permissionList = store.permissionList;
- const go = (path: string) => {
- console.log("path", path);
- nav.push({
- path,
- });
- };
- </script>
- <template>
- <a-table
- :dataSource="list"
- :columns="columns">
- <template #bodyCell="{ column, record }">
- <template v-if="column.key === 'imgUrl'">
- <a-avatar :src="record.imgUrl" />
- </template>
- <template v-if="column.key === 'role'">
- {{record.role.name}}
- </template>
- <template v-if="column.key === 'state'">
- {{record.state==1?'正常':'禁用'}}
- </template>
- <template v-if="column.key === 'createDate'">
- {{record.createDate.substring(0,10)}}
- </template>
- </template>
- </a-table>
- </template>
-
- <script lang="ts">
- import { reactive, toRefs,onMounted} from "vue";
- import api from '../http/api'
- import UserType from '../type/UserType';
- export default {
- setup() {
- const columns = [
- {
- title: "用户名",
- dataIndex: "account",
- key: "account",
- },
- {
- title: "邮箱",
- dataIndex: "email",
- key: "email",
- },
- {
- title: "创建时间",
- dataIndex: "createDate",
- key: "createDate",
- },
- {
- title:'角色',
- dataIndex:'role',
- key:'role',
- },
- {
- title:'头像',
- dataIndex:'imgUrl',
- key:'imgUrl',
- },
- {
- title:'状态',
- dataIndex:'state',
- key:'state'
- }
- ];
- const data = reactive<{list:Array<UserType>}>({
- list:[]
-
- });
- onMounted(async()=>{
- let result=await api.users.getAccountListApi()
- console.log(result.data);
- data.list=result.data.data
- })
- return {
- columns,
- ...toRefs(data),
- };
- },
- };
- </script>
给columns列表增加操作项
- const columns = [
- {
- title: '操作',
- dataIndex: 'operation',
- key:'operation'
- },
- ];
增加template模板项
- <template v-if="column.key === 'operation'">
- <a-popconfirm
- v-if="list.length"
- title="您确定要删除吗?"
- @confirm="deleteUser(record._id)"
- >
- <a>删除</a>
- </a-popconfirm>
- </template>
编写deleteUser方法
- const deleteUser=async(id:number)=>{
- let result=await api.users.deleteAccountApi(id)
- if(result.data.code){
- console.log(result.data.data);
- findAccount()
- }
- }
- const findAccount=async()=>{
- let result=await api.users.getAccountListApi()
- data.list=result.data.data
- }
- onMounted(()=>{
- findAccount()
- })
- import axios from 'axios'
- axios.defaults.baseURL="http://www.zhaijizhe.cn:3001"
- export default{
- getAllCategroyApi:()=>axios.get("/categroy/findAllCategroy")
- }
- <template>
- <a-table
- :columns="columns"
- :data-source="list"
- :row-selection="rowSelection"
- />
- </template>
-
- <script lang="ts">
- import { reactive, toRefs, onMounted } from "vue";
- import CategoryType from "../type/CategoryType";
- import api from "../http/api";
- export default {
- setup() {
- const columns = [
- {
- title: "名称",
- dataIndex: "label",
- key: "label",
- },
- {
- title: "值",
- dataIndex: "value",
- key: "value",
- }
- ];
- const data = reactive<{ list: Array<CategoryType> }>({
- list: [],
- });
- onMounted(async () => {
- const result = await api.category.getAllCategroyApi();
- data.list = result.data.data;
- });
- return {
- ...toRefs(data),
- columns
- };
- },
- };
- </script>
-
- <style>
- </style>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。