Vue3做出B站【bilibili】 Vue3+TypeScript+ant-design-vue【快速入门一篇文章精通系列(一)前端项目案例】

本项目分为二部分
1、后台管理系统(用户管理,角色管理,视频管理等)
2、客户端(登录注册、发布视频)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Vue3做出B站【bilibili】 Vue3+TypeScript+ant-design-vue【快速入门一篇文章精通系列(一)前端项目案例】

  • 一、前言
  • 二、项目创建基本页面搭建
    • (一)创建Vue3 + TypeScript项目
      • 1、新建Vue3 项目
      • 2、用WebStorm打开项目
        • 1)打开项目以后执行 `npm install`
        • 2)安装TypeScript
        • 3)设置一下WebStorm配置
      • 3、配置项目
        • 1)安装依赖
        • 2)路由配置
        • 3)pinia配置
        • 4)vite.config.ts配置
        • 5)按需引入ant-design-vue
        • 6)安装axios
      • 4、设置页面路由
    • (二)实现登录页面
      • 1、设置登陆页面
      • 2、设置登录请求
      • 3、创建mock.ts
      • 4、显示验证码
      • 5、在store当中的store.ts设置SET_TOKEN
      • 6、实现登录请求相关内容
      • 7、完善登录
        • (1)在main.ts当中引入antd的全局样式
        • (2)完善request.ts当中响应的内容
  • 三、后台管理界面开发
    • (一)创建index页面
      • 1、新建index页面
      • 2、设置路由
      • 3、完善菜单页面内容
      • 4、Vue代码抽取
        • (1)抽取菜单
        • (2)index
      • 5、设置子路由
      • 6、编写导航栏路由
        • 1)创建需要路由跳转的页面
        • 2)设置页面路由
    • (二)用户登录信息展示
      • 1、完善用户接口
      • 2、设置mock.js
      • 3、设置个人中心的路由
      • 4、设置退出登录
    • (三)动态菜单开发
      • 1、修改一下路由规则
      • 2、设置动态菜单的数据
        • (1)自定义Icon组件
        • (2)SideMenu.vue菜单页面
        • (3)创建保存菜单的状态信息的内容
        • (4)设置mockjs
        • (5)完善SideMenu.vue菜单页面,设置请求并渲染菜单
      • 3、设置动态路由加载一次以后无需二次加载
      • 4、实现动态导航
      • 5、设置侧栏和页面进行动态绑定
      • 6、完善Tabs标签页
    • (四)菜单管理界面开发
      • 1、在Menu当中设置表格样式
      • 2、在Menu当中设置新增和编辑
    • (五)角色管理
      • 1、设置角色信息的增删改查-权限分配
    • (六)用户管理

一、前言

在前端方面我们使用的技术栈包括

TypeScript
Vue3
ant Design Vue
axios
echarts
highcharts
mockjs
pinia
vue-router

二、项目创建基本页面搭建

(一)创建Vue3 + TypeScript项目

1、新建Vue3 项目

npm create vite@latest bilibili-vue3-ts -- --template vue

在这里插入图片描述
将生成的js文件都修改为ts文件
在这里插入图片描述
在这里插入图片描述

2、用WebStorm打开项目

1)打开项目以后执行 npm install

在这里插入图片描述
执行成功
在这里插入图片描述

2)安装TypeScript

安装TypeScript

npm install -g typescript

在这里插入图片描述
安装完成后,在控制台运行如下命令,检查安装是否成功(3.x):

tsc -v

在这里插入图片描述

3)设置一下WebStorm配置

在这里插入图片描述
设置自动编译
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

$FileNameWithoutExtension$.js:$FileNameWithoutExtension$.js.map
$FileDir$

3、配置项目

1)安装依赖

作为前端项目我们使用一些场景的依赖
这里我们只需要将以下依赖复制到package.json,重新运行npm install
将package-lock.json文件夹删除
在这里插入图片描述
在package.json当中
在这里插入图片描述

"dependencies": {
   "ant-design-vue": "^3.3.0-beta.4",
    "axios": "^0.27.2",
    "echarts": "^5.3.3",
    "echarts-gl": "^2.0.9",
    "highcharts": "^10.2.1",
    "pinia": "^2.0.23",
    "pinia-plugin-persist": "^1.0.0",
    "sass": "^1.54.9",
    "swiper": "^8.4.5",
    "vue": "^3.2.37",
    "vue-router": "^4.1.5",
    "vue3-audio-player": "^1.0.5",
    "vue3-seamless-scroll": "^2.0.1"
  },
  "devDependencies": {
    "less": "^4.1.3",
    "unplugin-auto-import": "^0.11.2",
    "unplugin-vue-components": "^0.22.4",
    "@vitejs/plugin-vue": "^4.0.0",
    "vite": "^4.0.0"
  }

执行npm install

除了以上安装方式以外,
你也可以自己找到对应依赖的官方网站,
一个一个手动安装

2)路由配置

创建router文件夹
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

import { createRouter,createWebHashHistory } from 'vue-router';
import Home from "../views/Home.vue";
import About from "../views/About.vue";
//2、定义一些路由
//每个路由都需要映射到一个组件
//我们后面再讨论嵌套路由
const routes = [
    {path:"/",component:Home,name:"Home"},
    {path:"/About",component:About,name:"About"},
];
//3、创建路由实例并传递‘routes’配置
//你可以在这里输入更多的配置,但是我们在这里
const router = createRouter({
    //4、内部提供了 history 模式的实现。为了简单起见,我们在这里使用hash模式
    history:createWebHashHistory(),
    routes, //routes:routes 的缩写
})
export default router

创建Home.vue和About.vue
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

<template>
 <h1>Home</h1>
</template>

<script lang="ts" setup name="">

</script>

<style scoped>

</style>

在这里插入图片描述
修改App.vue
在这里插入图片描述

<script setup lang="ts">
</script>
<template>
  <router-view></router-view>
</template>
<style scoped>
</style>

3)pinia配置

在这里插入图片描述
在这里插入图片描述

import { createPinia } from 'pinia'
import piniaPluginPersist from 'pinia-plugin-persist'
const store = createPinia()
store.use(piniaPluginPersist)
export default store

在这里插入图片描述
在这里插入图片描述

import { defineStore } from 'pinia'
export const userStore = defineStore({
    id: 'user',
    state: () => {
        return {
            title: '',
            token:''
        }
    },
    getters: {
        getTitle: (state) => state.title,
    },
    actions: {
        setTitle(title:string) {
            this.title= title
        }
    },
    // 开启数据缓存
    // @ts-ignore
    persist: { //数据默认存在 sessionStorage 里,并且会以 store 的 id 作为 key
        enabled: true
    }
})

在main.ts当中引入如上内容
在这里插入图片描述

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router/index'
import store  from './store/index'
import { createPinia } from 'pinia'
import * as echarts from 'echarts'

let app = createApp(App)
app.config.globalProperties.$echarts = echarts
app.use(router)
app.use(store)
app.use(createPinia)
app.mount('#app')

4)vite.config.ts配置

在这里插入图片描述

//vite.config.js
import { defineConfig } from 'vite'
import {resolve} from 'path'
import vue from '@vitejs/plugin-vue'
import Components from "unplugin-vue-components/vite"
import AutoImport from "unplugin-auto-import/vite"
export default defineConfig({
  plugins: [
    vue(),
    AutoImport({
    }),
    Components({
    }),
  ],
  // ...
  resolve: {
    alias: {
      '@': resolve(__dirname, './src')
    }
  },
  server: {
    port: 80,
    host: true,
    open: true,
    proxy: {
      '/api': {
        target: 'http://api.cpengx.cn/metashop/api',
        changeOrigin: true,
        rewrite: (p) => p.replace(/^\/api/, '')
      },
    }
  },
  // 开启less支持
  css: {
    preprocessorOptions: {
      less: {
        javascriptEnabled: true
      }
    }
  }
})

运行测试

npm run dev

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5)按需引入ant-design-vue

在这里插入图片描述

//vite.config.js
import { defineConfig } from 'vite'
import {resolve} from 'path'
import vue from '@vitejs/plugin-vue'
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers'
import Components from "unplugin-vue-components/vite"
import AutoImport from "unplugin-auto-import/vite"
export default defineConfig({
  plugins: [
    vue(),
    AutoImport({
      resolvers: [AntDesignVueResolver() ],
    }),
    Components({
      resolvers: [
        AntDesignVueResolver({
          importStyle: 'less', // 一定要开启这个配置项
        }),
      ],
    }),
  ],
  // ...
  resolve: {
    alias: {
      '@': resolve(__dirname, './src')
    }
  },
  server: {
    port: 80,
    host: true,
    open: true,
    proxy: {
      '/api': {
        target: 'http://api.cpengx.cn/metashop/api',
        changeOrigin: true,
        rewrite: (p) => p.replace(/^\/api/, '')
      },
    }
  },
  // 开启less支持
  css: {
    preprocessorOptions: {
      less: {
        modifyVars: { // 在这里自定义主题色等样式
          'primary-color': '#fb7299',
          'link-color': '#fb7299',
          'border-radius-base': '2px',
        },
        javascriptEnabled: true,
      }
    }
  }
})

在Home当中放置一个按钮
在这里插入图片描述

<template>
 <h1>Home</h1>
  <a-button type="primary">Primary Button</a-button>
</template>
<script lang="ts" setup name="">
</script>
<style scoped>
</style>

重新运行并访问
在这里插入图片描述

6)安装axios

安装axios:一个基于promise的HTTP库,类ajax

npm install axios

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

import axios from 'axios'
// @ts-ignore
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
// 创建axios实例

const service = axios.create({
    // axios中请求配置有baseURL选项,表示请求URL公共部分
    baseURL: "/bilibili-api",
    //baseURL: "/",
    // 超时
    timeout: 10000
})
export default service

配置请求

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

import request from '../utils/request'
/* 有参  */
export const  getXqInfo  = (params:any) => {
    return request({
        method: "GET",
        url: "/grid/openApi/screen/getXqInfo",
        params,
    });
};
/* 无参  */
export const getCommunityOverview = ( ) => {
    return request({
        method: "GET",
        url: "/grid/openApi/screen/getCommunityOverview",
    });
};

4、设置页面路由

删除页面的自动创建好的页面
在这里插入图片描述
在这里插入图片描述
设置路由
在这里插入图片描述

import { createRouter,createWebHashHistory } from 'vue-router';
import Home from "../views/Home.vue";
import Login from "../views/Login.vue";
//2、定义一些路由
//每个路由都需要映射到一个组件
//我们后面再讨论嵌套路由
const routes = [
    {path:"/",component:Home,name:"Home"},
    {path:"/login",component:Login,name:"Login"},
];
//3、创建路由实例并传递‘routes’配置
//你可以在这里输入更多的配置,但是我们在这里
const router = createRouter({
    //4、内部提供了 history 模式的实现。为了简单起见,我们在这里使用hash模式
    history:createWebHashHistory(),
    routes, //routes:routes 的缩写
})
export default router

(二)实现登录页面

1、设置登陆页面

我们找到From表单的内容
https://www.antdv.com/components/form-cn
在这里插入图片描述

在这里插入图片描述

复制上述代码,但是我们并不会直接使用期内容

在这里插入图片描述

<template>
  <a-form
      ref="formRef"
      name="custom-validation"
      :model="formState"
      :rules="rules"
      v-bind="layout"
      @finish="handleFinish"
      @validate="handleValidate"
      @finishFailed="handleFinishFailed"
  >
    <a-form-item has-feedback label="Password" name="pass">
      <a-input v-model:value="formState.pass" type="password" autocomplete="off" />
    </a-form-item>
    <a-form-item has-feedback label="Confirm" name="checkPass">
      <a-input v-model:value="formState.checkPass" type="password" autocomplete="off" />
    </a-form-item>
    <a-form-item has-feedback label="Age" name="age">
      <a-input-number v-model:value="formState.age" />
    </a-form-item>
    <a-form-item :wrapper-col="{ span: 14, offset: 4 }">
      <a-button type="primary" html-type="submit">Submit</a-button>
      <a-button style="margin-left: 10px" @click="resetForm">Reset</a-button>
    </a-form-item>
  </a-form>
</template>

<script lang="ts" setup>
import type { Rule } from 'ant-design-vue/es/form';
import {  reactive, ref } from 'vue';
import type { FormInstance } from 'ant-design-vue';

interface FormState {
  pass: string;
  checkPass: string;
  age: number | undefined;
}

const formRef = ref<FormInstance>();

const formState = reactive<FormState>({
  pass: '',
  checkPass: '',
  age: undefined,
});

let checkAge = async (_rule: Rule, value: number) => {
  if (!value) {
    return Promise.reject('Please input the age');
  }
  if (!Number.isInteger(value)) {
    return Promise.reject('Please input digits');
  } else {
    if (value < 18) {
      return Promise.reject('Age must be greater than 18');
    } else {
      return Promise.resolve();
    }
  }
};

let validatePass = async (_rule: Rule, value: string) => {
  if (value === '') {
    return Promise.reject('Please input the password');
  } else {
    if (formState.checkPass !== '') {
      formRef.value!.validateFields('checkPass');
    }
    return Promise.resolve();
  }
};

let validatePass2 = async (_rule: Rule, value: string) => {
  if (value === '') {
    return Promise.reject('Please input the password again');
  } else if (value !== formState.pass) {
    return Promise.reject("Two inputs don't match!");
  } else {
    return Promise.resolve();
  }
};

const rules: Record<string, Rule[]> = {
  pass: [{ required: true, validator: validatePass, trigger: 'change' }],
  checkPass: [{ validator: validatePass2, trigger: 'change' }],
  age: [{ validator: checkAge, trigger: 'change' }],
};

const layout = {
  labelCol: { span: 4 },
  wrapperCol: { span: 14 },
};

const handleFinish = (values: FormState) => {
  console.log(values, formState);
};

const handleFinishFailed = (errors: any) => {
  console.log(errors);
};

const resetForm = () => {
  formRef.value!.resetFields();
};

const handleValidate = (...args: any[]) => {
  console.log(args);
};
</script>
<style scoped>

</style>

访问页面http://localhost/#/login
在这里插入图片描述
删除style.css当中样式
在这里插入图片描述
在这里插入图片描述
调整一下页面
在这里插入图片描述

<template>

  <a-card style="width: 800px;margin:10% auto;border-radius: 15px;">
    <div style="width: 200px;margin: auto">
      <a-image
          style="margin: auto"
          :width="200"
          :preview="false"
          src="src/assets/bilibili.png"
      />
    </div>
    <div class="from-item">

      <a-form
          :model="formState"
          name="normal_login"
          class="login-form"
          @finish="onFinish"
          @finishFailed="onFinishFailed"
      >
        <a-form-item
            label="账号"
            name="username"
            :rules="[{ required: true, message: '请输入账号!' }]"
        >
          <a-input v-model:value="formState.username">
            <template #prefix>
              <UserOutlined class="site-form-item-icon" />
            </template>
          </a-input>
        </a-form-item>
        <a-form-item
            label="密码"
            name="password"
            :rules="[{ required: true, message: '请输入密码!' }]"
        >
          <a-input-password v-model:value="formState.password">
            <template #prefix>
              <LockOutlined class="site-form-item-icon" />
            </template>
          </a-input-password>
        </a-form-item>
        <a-row>
          <a-col :span="12">
            <a-form-item
                label="验证码"
                name="code"
                :rules="[{ required: true, message: '请输入验证码!' }]"
            >
              <a-input  v-model:value="formState.code" placeholder="请输入验证码" >

              </a-input>
            </a-form-item>
          </a-col>
          <a-col :span="12">
            <a-image
                :width="60"
                style="height: 30px;margin-left: 10%"
                :preview="false"
                src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png"
            />
          </a-col>
        </a-row>
        <a-form-item>
          <a-row>
            <a-col :span="6"></a-col>
            <a-col :span="12">
              <a-button  type="primary" block html-type="submit" class="login-form-button">
                登录
              </a-button>
            </a-col>
            <a-col :span="6"></a-col>
          </a-row>

        </a-form-item>
      </a-form>
    </div>

  </a-card>
</template>

<script lang="ts" setup>
import { defineComponent, reactive, computed } from 'vue';
import { UserOutlined, LockOutlined } from '@ant-design/icons-vue';
interface FormState {
  username: string;
  password: string;
  code: string;
}
const formState = reactive<FormState>({
  username: '',
  password: '',
  code: '',
});
const onFinish = (values: any) => {
  console.log('Success:', values);
};

const onFinishFailed = (errorInfo: any) => {
  console.log('Failed:', errorInfo);
};
const disabled = computed(() => {
  return !(formState.username && formState.password);
});
</script>

<style scoped>
.from-item{
  padding-top: 10%;
  margin: auto;
  width: 60%;
}

#components-form-demo-normal-login .login-form {
  max-width: 300px;
}
#components-form-demo-normal-login .login-form-forgot {
  float: right;
}
#components-form-demo-normal-login .login-form-button {
  width: 100%;
}
</style>

在这里插入图片描述

2、设置登录请求

在这里插入图片描述

import request from '@/utils/request'
/* 无参  */
export const getCaptchaImg = ( ) => {
    return request({
        method: "GET",
        url: "/captcha",
    });
};

3、创建mock.ts

安装qs
qs:查询参数序列化和解析库

npm install qs

安装mockjs
mockjs:为我们生成随机数据的工具库

npm install mockjs

在这里插入图片描述
在main.ts当中引入mock.ts
在这里插入图片描述

import "@/mock"

完善mock.ts
在这里插入图片描述

// @ts-ignore
import Mock from "mockjs";

const Random = Mock.Random

let Result = {
    code: 200,
    msg: '操作成功',
    data: null
}

Mock.mock('/bilibili-api/captcha','get',()=>{
    // @ts-ignore
    Result.data = {
        token: Random.string(32),
        captchaImg:Random.dataImage('120x40','p7n5w')
    }
    return Result;
})

4、显示验证码

完善request.ts,设置发起请求的内容
在这里插入图片描述

import axios from 'axios'
// @ts-ignore
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
// 创建axios实例

const service = axios.create({
    // axios中请求配置有baseURL选项,表示请求URL公共部分
    baseURL: "/bilibili-api",
    //baseURL: "/",
    // 超时
    timeout: 10000
})
export default service

完善vite.config.ts,设置发起请求的路径和地址
在这里插入图片描述

proxy: {
      '/bilibili-api': {
        target: 'http://localhost:8081',
        changeOrigin: true,
        rewrite: (p) => p.replace(/^\/bilibili-api/, '')
      },
}

完善src/api/index.ts设置请求
在这里插入图片描述

import request from '@/utils/request'
/*无参*/
export const getCaptchaImg = () => {
    return request({
        method: "GET",
        url: "/captcha",
    });
};

设置登录页面完善请求内容
在这里插入图片描述

 <a-image
                :width="60"
                style="height: 30px;margin-left: 10%"
                :preview="false"
                :src="captchaImg"
            />

在这里插入图片描述
在这里插入图片描述

import {getCaptchaImg} from "@/api";
const getCaptcha = () => {
  getCaptchaImg().then(res => {
    formState.token = res.data.data.token;
    captchaImg.value = res.data.data.captchaImg;
  })
}
onMounted(()=>{
  getCaptcha();
})

访问http://localhost/#/login
在这里插入图片描述

5、在store当中的store.ts设置SET_TOKEN

在这里插入图片描述

SET_TOKEN(token:string ){
            this.token = token
            localStorage.setItem("token",token)
 },

6、实现登录请求相关内容

在这里插入图片描述

export const userLogin = (data:any) => {
    return request({
        url: '/login',
        method: 'post',
        data: data
    })
};

在这里插入图片描述

const router = useRouter();
import { useRouter } from "vue-router";
const user = userStore()

const router = useRouter();
const onFinish = (values: any) => {
  userLogin(formState).then(res => {
    const jwt = res.headers['authorization']
    user.SET_TOKEN(jwt);
    router.push("/");
  })
};

完善mock.ts
在这里插入图片描述

Mock.mock('/bilibili-api/login','post',()=>{
    Result.code = 404
    Result.msg = "验证码错误"
    return Result;
})

7、完善登录

(1)在main.ts当中引入antd的全局样式

在这里插入图片描述

import 'ant-design-vue/dist/antd.css';

(2)完善request.ts当中响应的内容

在这里插入图片描述

import axios from 'axios'
import {  message as Message, notification } from 'ant-design-vue';
import { useRouter } from "vue-router";
// @ts-ignore
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
// 创建axios实例

const service = axios.create({
    // axios中请求配置有baseURL选项,表示请求URL公共部分
    baseURL: "/bilibili-api",
    // 超时
    timeout: 10000
})
service.interceptors.request.use(config => {
    // @ts-ignore
    config.headers['Authorization'] = localStorage.getItem("token")
    return config;
});
service.interceptors.response.use(response => {
        let res = response.data
        if (res.code === 200) {
            return response
        } else {
            Message.error(!res.msg ? '系统异常' : res.msg)
            return Promise.reject(response.data.msg)
        }
    }, error => {
        if (error.response.data) {
            error.message = error.response.data.msg
        }
        if (error.response.status === 401) {
            useRouter().push("/login")
        }
        Message.error(error.message)
        return Promise.reject(error)
    }
)
export default service

运行测试
http://localhost/#/login
在这里插入图片描述

三、后台管理界面开发

(一)创建index页面

一般来说,管理系统的页面我们都是头部是一个简单的信息展示系统名称和登录用户信息,然后中间的左边是菜单导航栏,右边是内容,对应到ant Design Vue的组件中,我们可以找到这个Layout 布局容器用于布局,方便快速搭建页面的基本结构。

而我们采用这个布局:
在这里插入图片描述

1、新建index页面

在这里插入图片描述
在这里插入图片描述

<template>
  <div>
    <a-layout>
      <a-layout-sider>Sider</a-layout-sider>
      <a-layout>
        <a-layout-header>Header</a-layout-header>
        <a-layout-content>Content</a-layout-content>
        <a-layout-footer>Footer</a-layout-footer>
      </a-layout>
    </a-layout>
  </div>
</template>
<script name="index" setup lang="ts">
</script>
<style scoped>
#components-layout-demo-basic .code-box-demo {
  text-align: center;
}
#components-layout-demo-basic .ant-layout-header,
#components-layout-demo-basic .ant-layout-footer {
  color: #fff;
  background: #fa81a3;
}
[data-theme='dark'] #components-layout-demo-basic .ant-layout-header {
  background: #fb7299;
}
[data-theme='dark'] #components-layout-demo-basic .ant-layout-footer {
  background: #fb7299;
}
#components-layout-demo-basic .ant-layout-footer {
  line-height: 1.5;
}
#components-layout-demo-basic .ant-layout-sider {
  color: #fff;
  line-height: 120px;
  background: #fd4c7e;
}
[data-theme='dark'] #components-layout-demo-basic .ant-layout-sider {
  background: #d9456f;
}
#components-layout-demo-basic .ant-layout-content {
  min-height: 120px;
  color: #fff;
  line-height: 120px;
  background: #b64665;
}
[data-theme='dark'] #components-layout-demo-basic .ant-layout-content {
  background: #cc889b;
}
#components-layout-demo-basic > .code-box-demo > .ant-layout + .ant-layout {
  margin-top: 48px;
}
</style>

2、设置路由

在这里插入图片描述

router.push("/index");

在这里插入图片描述
设置一下状态码使其跳转成功
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3、完善菜单页面内容

在这里插入图片描述

<template>
  <a-layout has-sider>
    <a-layout-sider v-model:collapsed="collapsed" :trigger="null" collapsible>
      <div class="logo" />
      <a-menu v-model:selectedKeys="selectedKeys" theme="dark" style="height: 100vh" mode="inline">
        <a-menu-item key="app" disabled>
          <div >
            bilibili后台管理系统
          </div>
        </a-menu-item>
        <a-menu-item key="1">
          <template #icon>
            <MailOutlined />
          </template>
         主页
        </a-menu-item>
        <a-sub-menu key="sub1">
          <template #icon>
            <AppstoreOutlined />
          </template>
          <template #title>系统管理</template>
          <a-menu-item key="3">用户管理</a-menu-item>
          <a-menu-item key="4">角色管理</a-menu-item>
          <a-menu-item key="5">菜单管理</a-menu-item>
        </a-sub-menu>
        <a-sub-menu key="sub2">
          <template #icon>
            <SettingOutlined />
          </template>
          <template #title>系统工具</template>
          <a-menu-item key="7">数字字典</a-menu-item>
        </a-sub-menu>
      </a-menu>
    </a-layout-sider><a-layout-content :style="{  marginTop: '0' }">
      <a-menu
          v-model:selectedKeys="selectedKeysTop"
          theme="dark"
          mode="horizontal"
          :style="{ lineHeight: '64px',marginLeft:'-15px' }"
      >
        <a-sub-menu key="sub2" >
          <template #title>
            <div >
              <menu-unfold-outlined
                  v-if="collapsed"
                  class="trigger"
                  @click="() => (collapsed = !collapsed)"
              />
              <menu-fold-outlined v-else class="trigger" @click="() => (collapsed = !collapsed)" />
            </div>
          </template>
        </a-sub-menu>
        <a-sub-menu key="sub1" style="margin-left: 85%" >
          <template #title>
            <a-avatar style="background-color: #87d068">
              <template #icon>
                <UserOutlined />
              </template>
            </a-avatar>
            <a class="ant-dropdown-link" @click.prevent>
              admin
              <DownOutlined />
            </a>
          </template>
          <a-menu-item key="setting:1">Option 1</a-menu-item>
          <a-menu-item key="setting:2">Option 2</a-menu-item>
          <a-menu-item key="setting:3">Option 3</a-menu-item>
          <a-menu-item key="setting:4">Option 4</a-menu-item>
        </a-sub-menu>
      </a-menu>
        <a-breadcrumb :style="{ margin: '16px 0' }">
          <a-breadcrumb-item>Home</a-breadcrumb-item>
          <a-breadcrumb-item>List</a-breadcrumb-item>
          <a-breadcrumb-item>App</a-breadcrumb-item>
        </a-breadcrumb>
        <div :style="{ background: '#fff', padding: '24px', minHeight: '800px' }">Content</div>
    </a-layout-content>
  </a-layout>
</template>
<script lang="ts" setup>
import {
  DownOutlined,
  UserOutlined,
  VideoCameraOutlined,
  UploadOutlined,
  MenuUnfoldOutlined,
  MenuFoldOutlined,
} from '@ant-design/icons-vue';
import { defineComponent, ref } from 'vue';
let selectedKeys =ref<string[]>(['1'])
let selectedKeysTop =ref<string[]>(['1'])
let collapsed =ref<boolean>(false)
</script>
<style>
#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: #fb7299;
}
#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>

在这里插入图片描述

4、Vue代码抽取

(1)抽取菜单

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

<template>
  <a-menu v-model:selectedKeys="selectedKeys" theme="dark" style="height: 100vh" mode="inline">
    <a-menu-item key="app" disabled>
      <template #icon>
        <img src="../../assets/bilibilitoum.png" style="height: 15px;width: 35px">
      </template>
      <div >
        后台管理系统
      </div>
    </a-menu-item>
    <a-menu-item key="1">
      <template #icon>
        <MailOutlined />
      </template>
      主页
    </a-menu-item>
    <a-sub-menu key="sub1">
      <template #icon>
        <AppstoreOutlined />
      </template>
      <template #title>系统管理</template>
      <a-menu-item key="3">用户管理</a-menu-item>
      <a-menu-item key="4">角色管理</a-menu-item>
      <a-menu-item key="5">菜单管理</a-menu-item>
    </a-sub-menu>
    <a-sub-menu key="sub2">
      <template #icon>
        <SettingOutlined />
      </template>
      <template #title>系统工具</template>
      <a-menu-item key="7">数字字典</a-menu-item>
    </a-sub-menu>
  </a-menu>
</template>
<script setup lang="ts">
import {ref} from "vue";
import {
  AppstoreOutlined,
  MailOutlined,
  SettingOutlined,
} from '@ant-design/icons-vue';
let selectedKeys =ref<string[]>(['1'])
let selectedKeysTop =ref<string[]>(['1'])
let collapsed =ref<boolean>(false)
</script>
<style scoped>
</style>

在这里插入图片描述
在这里插入图片描述

<SideMenu></SideMenu>
import SideMenu from "./inc/SideMenu.vue"

在这里插入图片描述

(2)index

将index.vue的内容全部抽取到Home
在这里插入图片描述

<template>
  <a-layout has-sider>
    <a-layout-sider v-model:collapsed="collapsed" :trigger="null" collapsible>
      <div class="logo" />
      <SideMenu></SideMenu>
    </a-layout-sider><a-layout-content :style="{  marginTop: '0' }">
      <a-menu
          v-model:selectedKeys="selectedKeysTop"
          theme="dark"
          mode="horizontal"
          :style="{ lineHeight: '64px',marginLeft:'-15px' }"
      >
        <a-sub-menu key="sub2" >
          <template #title>
            <div >
              <menu-unfold-outlined
                  v-if="collapsed"
                  class="trigger"
                  @click="() => (collapsed = !collapsed)"
              />
              <menu-fold-outlined v-else class="trigger" @click="() => (collapsed = !collapsed)" />
            </div>
          </template>
        </a-sub-menu>
        <a-sub-menu key="sub1" style="margin-left: 85%" >
          <template #title>
            <a-avatar style="background-color: #87d068">
              <template #icon>
                <UserOutlined />
              </template>
            </a-avatar>
            <a class="ant-dropdown-link" @click.prevent>
              admin
              <DownOutlined />
            </a>
          </template>
          <a-menu-item key="setting:1">Option 1</a-menu-item>
          <a-menu-item key="setting:2">Option 2</a-menu-item>
          <a-menu-item key="setting:3">Option 3</a-menu-item>
          <a-menu-item key="setting:4">Option 4</a-menu-item>
        </a-sub-menu>
      </a-menu>
      <a-breadcrumb :style="{ margin: '16px 0' }">
        <a-breadcrumb-item>Home</a-breadcrumb-item>
        <a-breadcrumb-item>List</a-breadcrumb-item>
        <a-breadcrumb-item>App</a-breadcrumb-item>
      </a-breadcrumb>
      <div :style="{ background: '#fff', padding: '24px', minHeight: '800px' }">Content</div>
    </a-layout-content>
  </a-layout>
</template>
<script lang="ts" setup>
import {
  DownOutlined,
  UserOutlined,
  VideoCameraOutlined,
  UploadOutlined,
  MenuUnfoldOutlined,
  MenuFoldOutlined,
} from '@ant-design/icons-vue';
import SideMenu from "./inc/SideMenu.vue"
import { defineComponent, ref } from 'vue';
let selectedKeys =ref<string[]>(['1'])
let selectedKeysTop =ref<string[]>(['1'])
let collapsed =ref<boolean>(false)
</script>
<style>
#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: #fb7299;
}
#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>

5、设置子路由

在这里插入图片描述

		children:[
            {
                path:'/index',
                name:'Index',
                component:Index
            }
        ]

在Home.vue当中设置路由
在这里插入图片描述

 <router-view>
</router-view>

访问页面:http://localhost/#/index
在这里插入图片描述

6、编写导航栏路由

1)创建需要路由跳转的页面

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

防止手残贴上全部代码

import  { createRouter,createWebHashHistory } from 'vue-router';
import Home from "../views/Home.vue";
import Index from "../views/Index.vue";
import Login from "../views/Login.vue";
import Menu from '../views/sys/Menu.vue'
import Role from '../views/sys/Role.vue'
import User from '../views/sys/User.vue'
//2、定义一些路由
//每个路由都需要映射到一个组件
//我们后面再讨论嵌套路由
const routes = [
    {
        path:"/",
        component:Home,
        name:"Home",
        children:[
            {
                path:'',
                name:'Index',
                component:Index
            },
            {
                path:'/index',
                name:'Index',
                component:Index
            },
            {
                path:'/users',
                name:'SysUser',
                component:User
            },
            {
                path:'/roles',
                name:'SysRole',
                component:Role
            },
            {
                path:'/menus',
                name:'SysMenu',
                component:Menu
            }
        ]
    },
    {path:"/login",component:Login,name:"Login"},
];
//3、创建路由实例并传递‘routes’配置
//你可以在这里输入更多的配置,但是我们在这里
const router = createRouter({
    //4、内部提供了 history 模式的实现。为了简单起见,我们在这里使用hash模式
    history:createWebHashHistory(),
    routes, //routes:routes 的缩写
})
export default router

访问:http://localhost/#/roles
在这里插入图片描述

访问: http://localhost/#/users
在这里插入图片描述
访问:http://localhost/#/menus

2)设置页面路由

在这里插入图片描述

	<router-link to="/index">
      <a-menu-item key="1">
        <template #icon>
          <MailOutlined />
        </template>
        主页
      </a-menu-item>
    </router-link>
    <a-sub-menu key="sub1">
      <template #icon>
        <AppstoreOutlined />
      </template>
      <template #title>系统管理</template>
      <router-link to="/users">
        <a-menu-item key="3">
          <template #icon>
            <UserOutlined />
          </template>
          用户管理
        </a-menu-item>
      </router-link>
      <router-link to="/roles">
        <a-menu-item key="4">
          <template #icon>
            <TeamOutlined />
          </template>
          角色管理
        </a-menu-item>
      </router-link>
      <router-link to="/menus">
        <a-menu-item key="5">
          <template #icon>
            <MenuOutlined />
          </template>
          菜单管理
        </a-menu-item>
      </router-link>
    </a-sub-menu>
    <a-sub-menu key="sub2">
      <template #icon>
        <SettingOutlined />
      </template>
      <template #title>系统工具</template>
      <a-menu-item key="7">
        <template #icon>
          <ContainerOutlined />
        </template>
        数字字典
      </a-menu-item>
    </a-sub-menu>

点击测试
在这里插入图片描述
在这里插入图片描述

(二)用户登录信息展示

管理界面的右上角的用户信息现在是写死的,
因为我们现在已经登录成功,所以我们可以通过接口去请求获取到当前的用户信息了,
这样我们就可以动态显示用户的信息,这个接口比较简单,然后退出登录的链接也一起完成,
就请求接口同时把浏览器中的缓存删除就退出了哈。

1、完善用户接口

在这里插入图片描述

export const getUserInfo = () => {
    return request({
        url: '/sys/userInfo',
        method: 'get',
    })
};

2、设置mock.js

在这里插入图片描述


Mock.mock('/bilibili-api/sys/userInfo','get',()=>{
    // @ts-ignore
    Result.data = {
        id:"1",
        username:"itbluebox",
        avatar:"https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png"
    }
    return Result
})

3、设置个人中心的路由

在这里插入图片描述

<a-menu-item key="setting:1">
       <router-link to="/userCenter">
             个人中心
        </router-link>
</a-menu-item>

在这里插入图片描述

import UserCenter from '../views/UserCenter.vue'
{
    path:'/userCenter',
    name:'UserCenter',
    component:UserCenter
},

创建对应的页面
在这里插入图片描述
在这里插入图片描述

<template>
  <a-form
      ref="formRef"
      :model="formState"
      :label-col="labelCol"
      :wrapper-col="wrapperCol"
      :rules="rules"
  >
    <a-form-item ref="user" label="账号" name="user">
      <a-input v-model:value="formState.user" placeholder="Username">
        <template #prefix><UserOutlined style="color: rgba(0, 0, 0, 0.25)" /></template>
      </a-input>
    </a-form-item>
    <a-form-item ref="password" label="密码" name="password">
      <a-input v-model:value="formState.password" type="password" placeholder="Password">
        <template #prefix><LockOutlined style="color: rgba(0, 0, 0, 0.25)" /></template>
      </a-input>
    </a-form-item>
    <a-form-item ref="code" label="验证码" name="code">
      <a-row>
        <a-col :span="12">
          <a-input v-model:value="formState.code" type="textarea" />
        </a-col>
        <a-col :span="12">
          <a-image @click="getCaptcha"
                   :width="60"
                   style="height: 30px;margin-left: 10%"
                   :preview="false"
                   :src="captchaImg"
          />
        </a-col>
      </a-row>
    </a-form-item>
    <a-form-item :wrapper-col="{ span: 14, offset: 4 }">
      <a-button type="primary" @click="onSubmit">修改</a-button>
      <a-button style="margin-left: 10px" @click="onReSet">重置</a-button>
    </a-form-item>
  </a-form>
</template>

<script name="UserCenter" lang="ts" setup>
import { defineComponent, reactive, toRaw, UnwrapRef,ref,onMounted } from 'vue';
import { ValidateErrorEntity } from 'ant-design-vue/es/form/interface';
import {getCaptchaImg} from "@/api";
onMounted(()=>{
  getCaptcha()
})
const formRef = ref();
let captchaImg = ref('')
let labelCol  = reactive(
    { span: 4 },
)
let wrapperCol  = reactive(
    { span: 14  },
)
interface FormState {
  user: string;
  password: string | undefined;
  code:  undefined;
}
const formState: UnwrapRef<FormState> = reactive({
  user: '',
  password: undefined,
  code: undefined,
});
const rules = reactive({
  user: [
    {required: true, message: '请输入用户名', trigger: 'blur'},
  ],
  password: [{required: true, message: '请输入密码', trigger: 'blur'}],
  code: [{required: true, message: '请输入密码', trigger: 'blur'}],
})
const onSubmit = () => {
  formRef.value.validate()
      .then(() => {
        console.log('values', formState, toRaw(formState));
      })
      .catch((error: ValidateErrorEntity<FormState>) => {
        console.log('error', error);
      });
};
const onReSet = () => {
  console.log('submit!', toRaw(formState));
};
const getCaptcha = () => {
  getCaptchaImg().then(res => {
    captchaImg.value = res.data.data.captchaImg;
  })
}
</script>
<style scoped>
</style>

在这里插入图片描述
在这里插入图片描述

4、设置退出登录

在这里插入图片描述

export const logout = () => {
    return request({
        url: '/logout',
        method: 'get',
    })
};

在这里插入图片描述

<a-menu-item key="setting:2" @click.native="logOut">退出</a-menu-item>

在这里插入图片描述

import { userStore} from '@/store/store'
const user = userStore()
const logOut = () => {
  logout().then(response => {
    user.resetState()
    localStorage.clear();
    sessionStorage.clear();
    router.push("/login");
  });
}

设置Store的状态
在这里插入图片描述

 resetState(){
            this.token = ""
 },

设置mock
在这里插入图片描述

Mock.mock('/bilibili-api/logout','get',()=>{
    return Result;
})

(三)动态菜单开发

1、修改一下路由规则

在这里插入图片描述

{
                path:'/sys/users',
                name:'SysUser',
                component:User
            },
            {
                path:'/sys/roles',
                name:'SysRole',
                component:Role
            },
            {
                path:'/sys/menus',
                name:'SysMenu',
                component:Menu
            }

2、设置动态菜单的数据

(1)自定义Icon组件

在这里插入图片描述
目前先这样。后期会对其进行优化
在这里插入图片描述

<template>
  <div>
    <SettingOutlined v-if="iconName == 'setting-outlined'"></SettingOutlined>
    <UserOutlined v-if="iconName == 'user-outlined'"></UserOutlined>
    <MenuOutlined v-if="iconName == 'menu-outlined'"></MenuOutlined>
    <ContainerOutlined v-if="iconName == 'container-outlined'"></ContainerOutlined>
    <UsergroupAddOutlined v-if="iconName == 'user-group-add-outlined'"></UsergroupAddOutlined>
  </div>
</template>

<script setup lang="ts">
import {ref,reactive} from "vue";
import {
  UserOutlined,
  SettingOutlined,
  MenuOutlined,
  ContainerOutlined,
 UsergroupAddOutlined
} from '@ant-design/icons-vue';
const props = defineProps<{
  iconName: any;
}>();
</script>

<style scoped>

</style>

(2)SideMenu.vue菜单页面

在这里插入图片描述

在这里插入图片描述

<template>
  <a-menu v-model:selectedKeys="selectedKeys" theme="dark" style="height: 100vh" mode="inline">
    <a-menu-item key="app" disabled>
      <template #icon>
        <img src="../../assets/bilibilitoum.png" style="height: 15px;width: 35px">
      </template>
      <div >
        后台管理系统
      </div>
    </a-menu-item>
    <router-link to="/index">
      <a-menu-item key="1">
        <template #icon>
          <MailOutlined />
        </template>
        主页
      </a-menu-item>
    </router-link>
    <a-sub-menu :key="menu.name"  v-for="menu in menuList.menus">
      <template #icon>
        <Icon :icon-name="menu.icon" />
      </template>
      <template #title>{{menu.title}}</template>
      <router-link :to="item.path" v-for="item in menu.children">
        <a-menu-item :key="item.key">
          <template #icon>
            <Icon :icon-name="item.icon" />
          </template>
          {{item.title}}
        </a-menu-item>
      </router-link>
    </a-sub-menu>
  </a-menu>
</template>
<script setup lang="ts">
import {ref,reactive} from "vue";
import {
  AppstoreOutlined,
  MailOutlined,
  SettingOutlined,
  TeamOutlined,
  UserOutlined,
  MenuOutlined,
  ContainerOutlined
} from '@ant-design/icons-vue';
import Icon from "@/components/Icon.vue"
let selectedKeys =ref<string[]>(['1'])
let selectedKeysTop =ref<string[]>(['1'])
let collapsed =ref<boolean>(false)
let menuList = reactive({
  menus:  [{
        key:101,
        title: '系统管理',
        name: 'SysMange',
        icon: 'setting-outlined',
        path: '',
        children: [
          {
            key:102,
            title: '用户管理',
            name: 'SysUser',
            icon: 'user-outlined',
            path: '/sys/users',
            children: []
          },
          {
            key:103,
            title: '角色管理',
            name: 'SysUser',
            icon: 'user-group-add-outlined',
            path: '/sys/roles',
            children: []
          },
          {
            key:104,
            title: '菜单管理',
            name: 'SysMenu',
            icon: 'menu-outlined',
            path: '/sys/menus',
            children: []
          }
        ]
      },
        {
          key:201,
          title: '系统工具',
          name: 'SysTools',
          icon: 'menu-outlined',
          path: '',
          children: [{
            title: '数字字典',
            name: 'SysDict',
            icon: 'container-outlined',
            path: '/sys/dicts',
            children: []
          }]
        }
      ]
})
</script>
<style scoped>
</style>

刷新并访问页面
在这里插入图片描述

(3)创建保存菜单的状态信息的内容

在这里插入图片描述

menuList:[],
            authoritys:[]

 setMenuList(menuList:any) {
            this.menuList = menuList
        },
        setAuthoritys(authoritys:any) {
            this.authoritys = authoritys
        },

发送获取菜单的请求
在这里插入图片描述

export const nav = () => {
    return request({
        url: '/sys/menu/nav',
        method: 'get',
    })
};

在路由当中获取拿到menuList,
在这里插入图片描述

import {nav} from "@/api";
import { userStore} from '@/store/store'

设置在路由加载前拿到前,拿到菜单的内容并添加到Store
在这里插入图片描述

router.beforeEach((to,from,next)=>{
    nav().then(res => {
        //拿到menuList
        userStore().setMenuList(res.data.data.nav)
        userStore().setAuthoritys(res.data.data.authoritys)
    })
    next()
})

(4)设置mockjs

在这里插入图片描述


Mock.mock('/bilibili-api/sys/menu/nav', 'get', () => {
    let nav = [
        {
            key:101,
            title: '系统管理',
            name: 'SysMange',
            icon: 'setting-outlined',
            path: '',
            children: [
                {
                    key:102,
                    title: '用户管理',
                    name: 'SysUser',
                    icon: 'user-outlined',
                    path: '/sys/users',
                    children: []
                },
                {
                    key:103,
                    title: '角色管理',
                    name: 'SysUser',
                    icon: 'user-group-add-outlined',
                    path: '/sys/roles',
                    children: []
                },
                {
                    key:104,
                    title: '菜单管理',
                    name: 'SysMenu',
                    icon: 'menu-outlined',
                    path: '/sys/menus',
                    children: []
                }
            ]
        },
        {
            key:201,
            title: '系统工具',
            name: 'SysTools',
            icon: 'menu-outlined',
            path: '',
            children: [{
                title: '数字字典',
                name: 'SysDict',
                icon: 'container-outlined',
                path: '/sys/dicts',
                children: []
            }]
        }

    ];
    // @ts-ignore
    let authoritys = [];
    // @ts-ignore
    Result.data = {
        nav: nav,
        // @ts-ignore
        authoritys: authoritys
    }
    return Result;
})

(5)完善SideMenu.vue菜单页面,设置请求并渲染菜单

在这里插入图片描述

import {ref,reactive,onMounted} from "vue";
import {
  AppstoreOutlined,
  MailOutlined,
  SettingOutlined,
  TeamOutlined,
  UserOutlined,
  MenuOutlined,
  ContainerOutlined
} from '@ant-design/icons-vue';
import Icon from "@/components/Icon.vue"
import { userStore} from '@/store/store'
let selectedKeys =ref<string[]>(['1'])
let selectedKeysTop =ref<string[]>(['1'])
let collapsed =ref<boolean>(false)
let menuList = reactive({
     menus:  []
})
onMounted(()=>{
  menuList.menus = userStore().getMenuList
})

在这里插入图片描述

3、设置动态路由加载一次以后无需二次加载

在这里插入图片描述

hasRoutes:false

 getHasRoutes: (state) => state.hasRoutes,

 changeRouteStatus(hasRoutes:any){
            this.hasRoutes = hasRoutes;
}

在这里插入图片描述

router.beforeEach((to,from,next)=>{
    let hasRoutes = userStore().getHasRoutes;
    if(!hasRoutes){
        nav().then(res => {
            //拿到menuList
            userStore().setMenuList(res.data.data.nav)
            userStore().setAuthoritys(res.data.data.authoritys)
            hasRoutes = true
            userStore().changeRouteStatus(hasRoutes)
        })
    }
    next()
})

4、实现动态导航

在这里插入图片描述
在这里插入图片描述

<template>
  <div>
    <a-tabs v-model:activeKey="activeKey" type="editable-card" @edit="onEdit">
      <a-tab-pane v-for="pane in panes" :key="pane.key" :tab="pane.title" :closable="pane.closable">
      </a-tab-pane>
    </a-tabs>
  </div>
</template>

<script setup lang="ts">
/*
*
* <close-outlined />
* */
import { defineComponent, ref,onMounted } from 'vue'
const panes = ref<{ title: string; content: string; key: string; closable?: boolean }[]>([
  { title: 'Tab 1', content: 'Content of Tab 1', key: '1' },
  { title: 'Tab 2', content: 'Content of Tab 2', key: '2' },
  { title: 'Tab 3', content: 'Content of Tab 3', key: '3', closable: false },
]);
const activeKey = ref(panes.value[0].key);
const newTabIndex = ref(0);
onMounted(()=>{
})
const add = () => {
  activeKey.value = `newTab${++newTabIndex.value}`;
  panes.value.length = 1
};
const remove = (targetKey: string) => {
  let lastIndex = 0;
  panes.value.forEach((pane, i) => {
    if (pane.key === targetKey) {
      lastIndex = i - 1;
    }
  });
  panes.value = panes.value.filter(pane => pane.key !== targetKey);
  if (panes.value.length && activeKey.value === targetKey) {
    if (lastIndex >= 0) {
      activeKey.value = panes.value[lastIndex].key;
    } else {
      activeKey.value = panes.value[0].key;
    }
  }
};

const onEdit = (targetKey: string | MouseEvent, action: string) => {
  if (action === 'add') {
    add();
  } else {
    remove(targetKey as string);
  }
};
</script>
<style scoped>
::v-deep .ant-tabs-nav-list .ant-tabs-nav-add span {
   transform: rotate(-45deg);
}
</style>

查看效果
在Home当中引入该内容
在这里插入图片描述

<div style="margin-top: 15px;">
   <Tabs></Tabs>
</div>
import Tabs from "@/views/inc/Tabs.vue"

在这里插入图片描述

5、设置侧栏和页面进行动态绑定

  • 在store.ts当中设置添加tab 的功能
    在这里插入图片描述
			 editableTabsValue: 0,
            editableTabs: [
                {
                    title: '首页',
                    content: '/index',
                    key: 0,
                    closable: false,
                }
            ],

		 getEditableTabsValue: (state) => state.editableTabsValue,
         getEditableTabs: (state) => state.editableTabs,

在这里插入图片描述

addTab(tab:any) {
            this.editableTabs.push({
                title: tab.title,
                content: tab.path,
                key: tab.key,
                closable: true,
            });
        },
        setEditableTabs(tab:any){
            this.editableTabs = tab;
        },
        setEditableTabsIndex0(){
            this.editableTabsValue = 0;
        },
        setEditableTabsIndexClearALL(){
            this.editableTabs  = [
                {
                    title: '首页',
                    content: '/index',
                    key: 0,
                    closable: false,
                }
            ]
        },
        setEditableTabsValue(tabValue:number){
            this.editableTabsValue = tabValue;
        }

在这里插入图片描述

 <a-menu-item key="1" @click="selectMenuIndex0">
        <template #icon>
          <MailOutlined />
        </template>
        主页
 </a-menu-item>
<a-menu-item :key="item.key" @click="selectMenu(item)">
          <template #icon>
            <Icon :icon-name="item.icon" />
          </template>
          {{item.title}}
 </a-menu-item>

在这里插入图片描述

onMounted(()=>{
  var menus = userStore().getEditableTabsValue;
  //设置高亮同步
  selectedKeys.value.length = 0;
  selectedKeys.value.push(menus+"")
  menuList.menus = userStore().getMenuList;
});
const selectMenu = (item:any) => {
  userStore().addTab(item)
}
const selectMenuIndex0 = () => {
  userStore().setEditableTabsIndex0()
}

6、完善Tabs标签页

在这里插入图片描述

<template>
  <div>
    <a-tabs v-model:activeKey="editableTabsValue" type="editable-card" @edit="onEdit">
      <a-tab-pane v-for="pane in editableTabs" :key="pane.key" :tab="pane.title" :closable="pane.closable">
      </a-tab-pane>
    </a-tabs>
  </div>
</template>

<script setup lang="ts">
import { userStore} from '@/store/store'
import {ref, onMounted,computed} from 'vue'

let editableTabs = computed({
  get(){
    return userStore().getEditableTabs;
  },
  set(val){
    userStore().addTab(val);
  }
});
let editableTabsValue = computed({
  get(){
    return userStore().getEditableTabsValue;
  },
  set(val:number){
    userStore().setEditableTabsValue(val);
  }
});
// @ts-ignore
const activeKey = ref(editableTabs.value[0].key);
const newTabIndex = ref(0);
let panesList = ref();
onMounted(()=>{
  panesList.value = userStore().getEditableTabs;
})
const removeAll = () => {
  activeKey.value = `newTab${++newTabIndex.value}`;
  userStore().setEditableTabsIndexClearALL()
};
const remove = (targetKey: string) => {
  let lastIndex = 0;
  let uStore = userStore().getEditableTabs;
  uStore.forEach((pane, i) => {
    // @ts-ignore
    if (uStore.key === targetKey) {
      lastIndex = i - 1;
    }
  });
  // @ts-ignore
  uStore = uStore.filter(pane => pane.key !== targetKey);
  if (uStore.length && activeKey.value === targetKey) {
    if (lastIndex >= 0) {
      // @ts-ignore
      activeKey.value = uStore[lastIndex].key;
    } else {
      // @ts-ignore
      activeKey.value = uStore[0].key;
    }
  }
  userStore().setEditableTabs(uStore);
};
const onEdit = (targetKey: string | MouseEvent, action: string) => {
  if (action === 'add') {
    removeAll();
  } else {
    remove(targetKey as string);
  }
};
</script>
<style scoped>
::v-deep .ant-tabs-nav-list .ant-tabs-nav-add span {
   transform: rotate(-45deg);
}
</style>

  • 我们发现重复点击会重复的添加到上面,现在设置重复点击不会出现重复的信息
    在这里插入图片描述
    在这里插入图片描述
		addTab(tab:any) {
            let index = this.editableTabs.findIndex(e => e.title === tab.title )
            if(index === -1){
                this.editableTabs.push({
                    title: tab.title,
                    content: tab.path,
                    key: tab.key,
                    closable: true,
                });
            }
            this.editableTabsValue = tab.key;
        },

多次点击以后不会出现
在这里插入图片描述

  • 设置点击tab进行内容的切换
    在这里插入图片描述
<a-tabs v-model:activeKey="editableTabsValue" type="editable-card" @edit="onEdit" @tabClick="onTabClick">
      <a-tab-pane v-for="pane in editableTabs" :key="pane.key" :tab="pane.title" :closable="pane.closable">
      </a-tab-pane>
</a-tabs>

在这里插入图片描述

const onTabClick = (targetKey: string) => {
  let jsonArray = userStore().getEditableTabs
  let path = "";
  for(let i =0;i < jsonArray.length;i++){
    if(targetKey == jsonArray[i].key){
      path = jsonArray[i].content;
    }
  }
  userStore().setEditableTabsValue(targetKey)
  router.push(path);
}

在这里插入图片描述

 <div style="display: none"> {{editableTabsValue}}</div>
        后台管理系统
      </div>

在这里插入图片描述

let editableTabsValue = computed({
  get(){
    let key = userStore().getEditableTabsValue;
    selectedKeys.value.length = 0;
    selectedKeys.value.push(key)
    return key;
  },
  set(val:any){
    userStore().setMenuList(val);
  }
});

在这里插入图片描述
完善清除功能
在这里插入图片描述
在这里插入图片描述

const removeAll = () => {
  activeKey.value = `newTab${++newTabIndex.value}`;
  userStore().setEditableTabsIndexClearALL()
  userStore().setEditableTabsValue("1")
  router.push("/index");
};

设置通过ip路径访问的时候,设置对应tabs和menu

在这里插入图片描述

<template>
  <router-view></router-view>
</template>
<script setup lang="ts">
import {ref,reactive,watch} from "vue"
import {useRouter} from "vue-router";
import {userStore} from "@/store/store";
const router = useRouter();
watch(
    () => router.currentRoute.value,
    (newValue, oldValue) => {
      let uStore = userStore().getEditableTabs;
      uStore.forEach((pane) => {
        if(pane.content === newValue.fullPath ){
          userStore().setEditableTabsValue(pane.key)
          router.push(pane.content);
        }
      });
    },
    { immediate: true }
)
</script>
<style scoped>
</style>

访问:http://localhost/#/sys/menus

在这里插入图片描述
设置退出登录后清除tab
在这里插入图片描述

 <a-menu-item key="setting:2" @click.native="logOut">退出</a-menu-item>

在这里插入图片描述

const logOut = () => {
  logout().then(response => {
    user.resetState()
    user.setEditableTabsIndexClearALL()
    user.setEditableTabsIndex0()
    localStorage.clear();
    sessionStorage.clear();
    router.push("/login");
  });
}

(四)菜单管理界面开发

1、在Menu当中设置表格样式

在这里插入图片描述

<template>
  <div>
    <a-form
        ref="formRef"
        :model="formState"
        :rules="rules"
    >
      <a-form-item :wrapper-col="{ span:24 }">
        <a-button type="primary" @click="onSubmit">新建</a-button>
      </a-form-item>
    </a-form>
    <a-table :columns="columns" :data-source="data" :row-selection="rowSelection">
      <template #bodyCell="{ column, record }">
        <template v-if="column.key === 'type'">
          <a-tag color="blue" v-if="record.type == '目录'">{{ record.type }}</a-tag>
          <a-tag color="green" v-if="record.type == '菜单'">{{ record.type }}</a-tag>
          <a-tag color="orange" v-if="record.type == '按钮'">{{ record.type }}</a-tag>
        </template>
        <template v-if="column.key === 'statu'">
          <a-tag color="green" v-if="record.statu == '正常'">{{ record.statu }}</a-tag>
          <a-tag color="red" v-if="record.statu == '禁用'">{{ record.statu }}</a-tag>
        </template>
        <template v-if="column.key === 'operation'">
          <a-button type="text" size="small" style="color: blue">
            编辑
          </a-button>
          <a-button type="text" size="small" style="color: red">
            删除
          </a-button>
        </template>
      </template>
    </a-table>
  </div>
</template>

<script name="menu" lang="ts" setup>
import { defineComponent,ref,reactive,toRaw } from 'vue';
import { SearchOutlined } from '@ant-design/icons-vue';
const formRef = ref();
const formState = reactive({
  name: undefined,
  sub: { name: undefined },
});
const rules = {
  parentId: {
    required: true,
    message: '请选择上级菜单',
  },
  name: {
    required: true,
    message: '请输入您的姓名',
  },
  perms: {
    required: true,
    message: '请输入权限编码',
  },
  type: {
    required: true,
    message: '请选择类型',
  },
  orderNum: {
    required: true,
    message: '请填入排序号',
  },
  statu: {
    required: true,
    message: '请选择状态',
  },
};
const onSubmit = () => {
  formRef.value.validate().then(() => {
        console.log('values', formState, toRaw(formState));
  }).catch(error => {
        console.log('error', error);
  });
};
const resetForm = () => {
  formRef.value.resetFields();
};
const columns = [
  {
    title: '名称',
    dataIndex: 'name',
    key: 'name',
  },
  {
    title: '权限编码',
    dataIndex: 'code',
    key: 'code',
  },
  {
    title: '图标',
    dataIndex: 'icon',
    key: 'icon',
  },
  {
    title: '类型',
    dataIndex: 'type',
    key: 'type',
  },
  {
    title: '菜单path',
    dataIndex: 'path',
    key: 'path',
  },
  {
    title: '菜单组件',
    dataIndex: 'component',
    key: 'component',
  },
  {
    title: '排序号',
    dataIndex: 'sort',
    key: 'sort',
  },{
    title: '状态',
    dataIndex: 'statu',
    key: 'statu',
  },
  {
    title: '操作',
    dataIndex: 'operation',
    key: 'operation',
  },
];

interface DataItem {
  key: number;
  name: string;
  code: string;
  sort: string;
  icon:string;
  statu:string;
  type: string;
  path: string;
  component: string;
  operation: string;
  children?: DataItem[];
}

const data: DataItem[] = [
  {
    key: 1,
    name: '系统管理',
    code: 'sys:system:list',
    type: "目录",
    path: "/",
    component: "/",
    sort: '1',
    icon: 'step-forward-outlined',
    statu: '正常',
    operation: '操作',
    children: [
      {
        key: 12,
        name: '用户管理',
        code: 'sys:user:list',
        type: "菜单",
        path: "/sys/user/list",
        component: "sys/User",
        sort: '2',
        icon: 'swap-right-outlined',
        statu: '正常',
        operation: '操作',
        children: [
          {
            key: 121,
            name: '查询',
            code: 'sys:user:list',
            type: "按钮",
            path: "",
            component: "",
            sort: '3',
            icon: 'swap-right-outlined',
            statu: '禁用',
            operation: '操作',
          },
          {
            key: 121,
            name: '新增',
            code: 'sys:user:add',
            type: "按钮",
            path: "",
            component: "",
            sort: '4',
            icon: 'step-forward-outlined',
            statu: '正常',
            operation: '操作',
          },
          {
            key: 121,
            name: '修改',
            code: 'sys:user:edit',
            type: "按钮",
            path: "",
            component: "",
            sort: '5',
            icon: 'step-forward-outlined',
            statu: '禁用',
            operation: '操作',
          },
          {
            key: 121,
            name: '删除',
            code: 'sys:user:delete',
            type: "按钮",
            path: "",
            component: "",
            sort: '6',
            icon: 'step-forward-outlined',
            statu: '正常',
            operation: '操作',
          },
          {
            key: 121,
            name: '重置密码',
            code: 'sys:user:repass',
            type: "按钮",
            path: "",
            component: "",
            sort: '7',
            icon: 'step-forward-outlined',
            statu: '禁用',
            operation: '操作',
          },
        ],
      },
      {
        key: 122,
        name: '角色管理',
        code: 'sys:role:list',
        type: "目录",
        path: "/sys/role/list",
        component: "sys/Role",
        sort: '8',
        icon: 'step-forward-outlined',
        statu: '正常',
        operation: '操作',
        children: [
          {
            key: 1212,
            name: '查询',
            code: 'sys:role:list',
            type: "菜单",
            path: "",
            component: "",
            sort: '9',
            icon: 'step-forward-outlined',
            statu: '正常',
            operation: '操作',
          },
          {
            key: 1213,
            name: '新增',
            code: 'sys:role:add',
            type: "菜单",
            path: "",
            component: "",
            sort: '10',
            icon: 'step-forward-outlined',
            statu: '正常',
            operation: '操作',
          },
          {
            key: 1214,
            name: '修改',
            code: 'sys:role:edit',
            type: "菜单",
            path: "",
            component: "",
            sort: '11',
            icon: 'step-forward-outlined',
            statu: '正常',
            operation: '操作',
          },
          {
            key: 1215,
            name: '删除',
            code: 'sys:role:delete',
            type: "菜单",
            path: "",
            component: "",
            sort: '12',
            icon: 'step-forward-outlined',
            statu: '正常',
            operation: '操作',
          },
          {
            key: 121,
            name: '重置密码',
            code: 'sys:user:repass',
            type: "按钮",
            path: "",
            component: "",
            sort: '7',
            icon: 'step-forward-outlined',
            statu: '禁用',
            operation: '操作',
          },
        ],
      },
    ],
  },
];

const rowSelection = {
  onChange: (selectedRowKeys: (string | number)[], selectedRows: DataItem[]) => {
    console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
  },
  onSelect: (record: DataItem, selected: boolean, selectedRows: DataItem[]) => {
    console.log(record, selected, selectedRows);
  },
  onSelectAll: (selected: boolean, selectedRows: DataItem[], changeRows: DataItem[]) => {
    console.log(selected, selectedRows, changeRows);
  },
};

</script>

<style scoped>

</style>

在这里插入图片描述

上面当面数据超出页面的时候页面跟着滚动这样不太好我们优化一下,设置侧边栏和头部不懂,设置内容区域滚动
在这里插入图片描述
在这里插入图片描述

<template>
  <a-layout has-sider :style="{ position: 'fixed', zIndex: 1, width: '100%' }">
    <a-layout-sider v-model:collapsed="collapsed" :trigger="null" collapsible >
      <div class="logo" />
      <SideMenu></SideMenu>
    </a-layout-sider><a-layout-content :style="{  marginTop: '0' }">
          <a-menu
              v-model:selectedKeys="selectedKeysTop"
              theme="dark"
              mode="horizontal"
              :style="{ lineHeight: '64px',marginLeft:'-15px' }"
          >
            <a-sub-menu key="sub2" >
              <template #title>
                <div >
                  <menu-unfold-outlined
                      v-if="collapsed"
                      class="trigger"
                      @click="() => (collapsed = !collapsed)"
                  />
                  <menu-fold-outlined v-else class="trigger" @click="() => (collapsed = !collapsed)" />
                </div>
              </template>
            </a-sub-menu>
            <a-sub-menu key="sub1" style="margin-left: 85%" >
              <template #title>
                <a-avatar v-if="userInfo.avatar == null || userInfo.avatar == ''" style="background-color: #87d068">
                  <template #icon>
                    <UserOutlined />
                  </template>
                </a-avatar>
                <a-avatar style="margin-top: -10px" v-if="userInfo.avatar != null && userInfo.avatar != ''"  src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" />
                <a class="ant-dropdown-link" @click.prevent>
                  <view style="margin-top: 10%;margin-left: 10%">
                    {{userInfo.username}}
                  </view>
                  <DownOutlined />
                </a>
              </template>
              <a-menu-item key="setting:1">
                <router-link to="/userCenter">
                  个人中心
                </router-link>
              </a-menu-item>
              <a-menu-item key="setting:2" @click.native="logOut">退出</a-menu-item>
            </a-sub-menu>
          </a-menu>
          <div style="margin-top: 15px;">
            <Tabs></Tabs>
          </div>
          <div id="components-affix-demo-target" ref="containerRef" class="scrollable-container" :style="{ background: 'rgb(255,255,255)', padding: '15px', minHeight: '820px' }">
            <router-view>
            </router-view>
          </div>
    </a-layout-content>
  </a-layout>
</template>
<script lang="ts" setup>
import {
  DownOutlined,
  UserOutlined,
  MenuUnfoldOutlined,
  MenuFoldOutlined,
} from '@ant-design/icons-vue';
import { userStore} from '@/store/store'
import { useRoute, useRouter } from "vue-router";
import SideMenu from "@/views/inc/SideMenu.vue"
import Tabs from "@/views/inc/Tabs.vue"
import { ref,reactive } from 'vue';
import { getUserInfo,logout } from "@/api";
const user = userStore()
let selectedKeys =ref<string[]>(['1'])
let selectedKeysTop =ref<string[]>(['1'])
let collapsed =ref<boolean>(false)
let gridInfo = ref('')
// 获取路由信息
const router = useRouter();
let userInfo = reactive({
  id: '',
  username: 'admin',
  avatar: '',
});
getUserInfo().then(response => {
  gridInfo.value = response.data.data
  userInfo = Object.assign(userInfo,gridInfo.value);
});
const logOut = () => {
  logout().then(response => {
    user.resetState()
    user.setEditableTabsIndexClearALL()
    user.setEditableTabsIndex0()
    localStorage.clear();
    sessionStorage.clear();
    router.push("/login");
  });
}
</script>
<style>
#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: #fb7299;
}
#components-layout-demo-custom-trigger .logo {
  height: 32px;
  background: rgba(255, 255, 255, 0.3);
  margin: 16px;
}
.site-layout .site-layout-background {
  background: #fff;
}
#components-affix-demo-target.scrollable-container {
  height: 100px;
  overflow-y: scroll;
}
#components-affix-demo-target .background {
  padding-top: 60px;
  height: 300px;
}
</style>

内容滚动头部底部不滚
在这里插入图片描述

2、在Menu当中设置新增和编辑

在这里插入图片描述
在这里插入图片描述

<template>
  <div>
    <a-row>
      <a-col :span="2">
        <a-button @click="iconValue = 'step-backward-outlined'" >
          <template #icon>
            <step-backward-outlined />
          </template>
        </a-button>
      </a-col>
      <a-col :span="2">
        <a-button  @click="iconValue = 'step-forward-outlined'" >
          <template #icon>
            <step-forward-outlined />
          </template>
        </a-button>
      </a-col>
      <a-col :span="2">
        <a-button  @click="iconValue = 'fast-backward-outlined'" >
          <template #icon>
            <fast-backward-outlined />
          </template>
        </a-button>
      </a-col>

      <a-col :span="2">
        <a-button  @click="iconValue = 'fast-forward-outlined'" >
          <template #icon>
            <fast-forward-outlined />
          </template>
        </a-button>
      </a-col>

      <a-col :span="2">
        <a-button   @click="iconValue = 'shrink-outlined'" >
          <template #icon>
            <shrink-outlined />
          </template>
        </a-button>
      </a-col>

      <a-col :span="2">
        <a-button   @click="iconValue = 'arrows-alt-outlined'" >
          <template #icon>
            <arrows-alt-outlined />
          </template>
        </a-button>
      </a-col>

      <a-col :span="2">
        <a-button   @click="iconValue = 'down-outlined'" >
          <template #icon>
            <down-outlined />
          </template>
        </a-button>
      </a-col>


      <a-col :span="2">
        <a-button  @click="iconValue = 'up-outlined'" >
          <template #icon>
            <up-outlined />
          </template>
        </a-button>
      </a-col>

      <a-col :span="2">
        <a-button  @click="iconValue = 'left-outlined'" >
          <template #icon>
            <left-outlined />
          </template>
        </a-button>
      </a-col>

      <a-col :span="2">
        <a-button   @click="iconValue = 'right-outlined'" >
          <template #icon>
            <right-outlined />
          </template>
        </a-button>
      </a-col>

      <a-col :span="2">
        <a-button  @click="iconValue = 'caret-up-outlined'" >
          <template #icon>
            <caret-up-outlined />
          </template>
        </a-button>
      </a-col>

      <a-col :span="2">
        <a-button  @click="iconValue = 'caret-down-outlined'" >
          <template #icon>
            <caret-down-outlined />
          </template>
        </a-button>
      </a-col>

      <a-col :span="2">
        <a-button  @click="iconValue = 'caret-left-outlined'" >
          <template #icon>
            <caret-left-outlined />
          </template>
        </a-button>
      </a-col>

      <a-col :span="2">
        <a-button  @click="iconValue = 'caret-right-outlined'" >
          <template #icon>
            <caret-right-outlined />
          </template>
        </a-button>
      </a-col>

      <a-col :span="2">
        <a-button  @click="iconValue = 'up-circle-outlined'" >
          <template #icon>
            <up-circle-outlined />
          </template>
        </a-button>
      </a-col>

      <a-col :span="2">
        <a-button   @click="iconValue = 'down-circle-outlined'" >
          <template #icon>
            <down-circle-outlined />
          </template>
        </a-button>
      </a-col>

      <a-col :span="2">
        <a-button  @click="iconValue = 'down-circle-outlined'" >
          <template #icon>
            <down-circle-outlined />
          </template>
        </a-button>
      </a-col>

      <a-col :span="2">
        <a-button  @click="iconValue = 'left-circle-outlined'" >
          <template #icon>
            <left-circle-outlined />
          </template>
        </a-button>
      </a-col>

      <a-col :span="2">
        <a-button  @click="iconValue = 'right-circle-outlined'" >
          <template #icon>
            <right-circle-outlined />
          </template>
        </a-button>
      </a-col>

      <a-col :span="2">
        <a-button  @click="iconValue = 'right-circle-outlined'" >
          <template #icon>
            <right-circle-outlined />
          </template>
        </a-button>
      </a-col>
      <a-col :span="2">
        <a-button  @click="iconValue = 'double-left-outlined'" >
          <template #icon>
            <double-left-outlined />
          </template>
        </a-button>
      </a-col>
      <a-col :span="2">
        <a-button  @click="iconValue = 'vertical-left-outlined'" >
          <template #icon>
            <vertical-left-outlined />
          </template>
        </a-button>
      </a-col>
      <a-col :span="2">
        <a-button  @click="iconValue = 'vertical-right-outlined'" >
          <template #icon>
            <vertical-right-outlined />
          </template>
        </a-button>
      </a-col>
      <a-col :span="2">
        <a-button  @click="iconValue = 'vertical-align-top-outlined'" >
          <template #icon>
            <vertical-align-top-outlined />
          </template>
        </a-button>
      </a-col>

      <a-col :span="2">
        <a-button  @click="iconValue = 'vertical-align-middle-outlined'" >
          <template #icon>
            <vertical-align-middle-outlined />
          </template>
        </a-button>
      </a-col>

      <a-col :span="2">
        <a-button  @click="iconValue = 'vertical-align-middle-outlined'" >
          <template #icon>
            <vertical-align-middle-outlined />
          </template>
        </a-button>
      </a-col>

      <a-col :span="2">
        <a-button  @click="iconValue = 'vertical-align-bottom-outlined'" >
          <template #icon>
            <vertical-align-bottom-outlined />
          </template>
        </a-button>
      </a-col>

      <a-col :span="2">
        <a-button  @click="iconValue = 'forward-outlined'" >
          <template #icon>
            <forward-outlined />
          </template>
        </a-button>
      </a-col>

      <a-col :span="2">
        <a-button  @click="iconValue = 'backward-outlined'" >
          <template #icon>
            <backward-outlined />
          </template>
        </a-button>
      </a-col>

      <a-col :span="2">
        <a-button   @click="iconValue = 'rollback-outlined'" >
          <template #icon>
            <rollback-outlined />
          </template>
        </a-button>
      </a-col>
      <a-col :span="2">
        <a-button  @click="iconValue = 'enter-outlined'" >
          <template #icon>
            <enter-outlined />
          </template>
        </a-button>
      </a-col>
      <a-col :span="2">
        <a-button   @click="iconValue = 'retweet-outlined'" >
          <template #icon>
            <retweet-outlined />
          </template>
        </a-button>
      </a-col>
      <a-col :span="2">
        <a-button  @click="iconValue = 'step-backward-outlined'" >
          <template #icon>
            <menu-fold-outlined />
          </template>
        </a-button>
      </a-col>
      <a-col :span="2">
        <a-button  @click="iconValue = 'menu-unfold-outlined'" >
          <template #icon>
            <menu-unfold-outlined />
          </template>
        </a-button>
      </a-col>
      <a-col :span="2">
        <a-button  @click="iconValue = 'align-center-outlined'" >
          <template #icon>
            <align-center-outlined />
          </template>
        </a-button>
      </a-col><a-col :span="2">
      <a-button  @click="iconValue = 'align-left-outlined'" >
        <template #icon>
          <align-left-outlined />
        </template>
      </a-button>
    </a-col><a-col :span="2">
      <a-button  @click="iconValue = 'ordered-list-outlined'" >
        <template #icon>
          <ordered-list-outlined />
        </template>
      </a-button>
    </a-col>

      <a-col :span="2">
        <a-button  @click="iconValue = 'unordered-list-outlined'" >
          <template #icon>
            <unordered-list-outlined />
          </template>
        </a-button>
      </a-col>
      <a-col :span="2">
        <a-button  @click="iconValue = 'appstore-outlined'" >
          <template #icon>
            <appstore-outlined />
          </template>
        </a-button>
      </a-col>
      <a-col :span="2">
        <a-button  @click="iconValue = 'bars-outlined'" >
          <template #icon>
            <bars-outlined />
          </template>
        </a-button>
      </a-col>
      <a-col :span="2">
        <a-button  @click="iconValue = 'vertical-align-top-outlined'" >
          <template #icon>
            <vertical-align-top-outlined />
          </template>
        </a-button>
      </a-col>
      <a-col :span="2">
        <a-button  @click="iconValue = 'bulb-outlined'" >
          <template #icon>
            <bulb-outlined />
          </template>
        </a-button>
      </a-col>
      <a-col :span="2">
        <a-button  @click="iconValue = 'console-sql-outlined'" >
          <template #icon>
            <console-sql-outlined />
          </template>
        </a-button>
      </a-col>
      <a-col :span="2">
        <a-button  @click="iconValue = 'desktop-outlined'" >
          <template #icon>
            <desktop-outlined />
          </template>
        </a-button>
      </a-col>
      <a-col :span="2">
        <a-button   @click="iconValue = 'vertical-align-top-outlined'" >
          <template #icon>
            <vertical-align-top-outlined />
          </template>
        </a-button>
      </a-col>
      <a-col :span="2">
        <a-button   @click="iconValue = 'vertical-align-top-outlined'" >
          <template #icon>
            <vertical-align-top-outlined />
          </template>
        </a-button>
      </a-col>

      <a-col :span="2">
        <a-button  @click="iconValue = 'exception-outline'" >
          <template #icon>
            <exception-outlined />
          </template>
        </a-button>
      </a-col>

      <a-col :span="2">
        <a-button   @click="iconValue = 'file-word-outlined'" >
          <template #icon>
            <file-word-outlined />
          </template>
        </a-button>
      </a-col>

      <a-col :span="2">
        <a-button  @click="iconValue = 'file-markdown-outlined'" >
          <template #icon>
            <file-markdown-outlined />
          </template>
        </a-button>
      </a-col>

      <a-col :span="2">
        <a-button   @click="iconValue = 'file-search-outlined'" >
          <template #icon>
            <file-search-outlined />
          </template>
        </a-button>
      </a-col>
      <a-col :span="2">
        <a-button   @click="iconValue = 'file-protect-outlined'" >
          <template #icon>
            <file-protect-outlined />
          </template>
        </a-button>
      </a-col>
      <a-col :span="2">
        <a-button  @click="iconValue = 'hdd-outlined'" >
          <template #icon>
            <hdd-outlined />
          </template>
        </a-button>
      </a-col>
      <a-col :span="2">
        <a-button   @click="iconValue = 'insert-row-left-outlined'" >
          <template #icon>
            <insert-row-left-outlined />
          </template>
        </a-button>
      </a-col>
      <a-col :span="2">
        <a-button   @click="iconValue = 'merge-cells-outlined'" >
          <template #icon>
            <merge-cells-outlined />
          </template>
        </a-button>
      </a-col>
      <a-col :span="2">
        <a-button  @click="iconValue = 'printer-outlined'" >
          <template #icon>
            <printer-outlined />
          </template>
        </a-button>
      </a-col><a-col :span="2">
      <a-button  @click="iconValue = 'reconciliation-outlined'" >
        <template #icon>
          <reconciliation-outlined />
        </template>
      </a-button>
    </a-col><a-col :span="2">
      <a-button  @click="iconValue = 'shop-outlined'" >
        <template #icon>
          <shop-outlined />
        </template>
      </a-button>
    </a-col>

      <a-col :span="2">
        <a-button   @click="iconValue = 'split-cells-outlined'" >
          <template #icon>
            <split-cells-outlined />
          </template>
        </a-button>
      </a-col>
      <a-col :span="2">
        <a-button  @click="iconValue = 'usergroup-add-outlined'" >
          <template #icon>
            <usergroup-add-outlined />
          </template>
        </a-button>
      </a-col>
      <a-col :span="2">
        <a-button  @click="iconValue = 'woman-outlined'" >
          <template #icon>
            <woman-outlined />
          </template>
        </a-button>
      </a-col>
    </a-row>
  </div>
</template>


<script setup lang="ts">
import {ref,defineExpose} from "vue";
let iconValue = ref('')
const props = defineProps<{
  iconName: any;
}>();

const change = () => {
  console.log(iconValue.value)
}

defineExpose({
  change,
  iconValue
})
</script>

<style scoped>

</style>

在这里插入图片描述

<template>
  <div>
    <a-form
        ref="formRef"
        :model="formState"
        :rules="rules"
    >
      <a-form-item :wrapper-col="{ span:24 }">
        <a-button type="primary" @click="showDrawer">新建</a-button>
      </a-form-item>
    </a-form>
    <a-table :columns="columns" :data-source="data" :row-selection="rowSelection">
      <template #bodyCell="{ column, record }">
        <template v-if="column.key === 'type'">
          <a-tag color="blue" v-if="record.type == '目录'">{{ record.type }}</a-tag>
          <a-tag color="green" v-if="record.type == '菜单'">{{ record.type }}</a-tag>
          <a-tag color="orange" v-if="record.type == '按钮'">{{ record.type }}</a-tag>
        </template>
        <template v-if="column.key === 'statu'">
          <a-tag color="green" v-if="record.statu == '正常'">{{ record.statu }}</a-tag>
          <a-tag color="red" v-if="record.statu == '禁用'">{{ record.statu }}</a-tag>
        </template>
        <template v-if="column.key === 'operation'">
          <a-button type="text" size="small" style="color: blue">
            编辑
          </a-button>
          <a-button type="text" size="small" style="color: red">
            删除
          </a-button>
        </template>
      </template>
    </a-table>
    <a-drawer
        title="添加菜单"
        :width="600"
        :visible="visible"
        :body-style="{ paddingBottom: '80px' }"
        :footer-style="{ textAlign: 'right' }"
        @close="onClose"
    >
       <a-form
          ref="formRef"
          name="custom-validation"
          :model="formState"
          :rules="rules"
          v-bind="layout"
          @finish="handleFinish"
          @validate="handleValidate"
          @finishFailed="handleFinishFailed"
      >
        <a-form-item has-feedback label="上级菜单" name="parentId">
          <a-input-group compact>
            <a-cascader
                v-model:value="formState.parentId"
                :options="options"
                type="parentId"
                placeholder="选择上级菜单"
            />
          </a-input-group>
        </a-form-item>
        <a-form-item has-feedback label="菜单名称" name="name">
          <a-input v-model:value="formState.name" type="name" autocomplete="off" />
        </a-form-item>
        <a-form-item has-feedback label="权限编码" name="perms">
          <a-input v-model:value="formState.perms" type="perms" autocomplete="off" />
        </a-form-item>
        <a-form-item has-feedback label="图标" name="icon">
          <a-input  v-model:value="formState.icon" @click="showModal" type="icon" autocomplete="off" />
        </a-form-item>
        <a-form-item has-feedback label="菜单URL" name="path">
          <a-input v-model:value="formState.path" type="path" autocomplete="off" />
        </a-form-item>
        <a-form-item has-feedback label="菜单组件" name="component">
          <a-input v-model:value="formState.component" type="component" autocomplete="off" />
        </a-form-item>
        <a-form-item has-feedback label="类型" name="type" >
          <a-checkbox-group v-model:value="formState.type">
            <a-checkbox value="1" name="type">目录</a-checkbox>
            <a-checkbox value="2" name="type">菜单</a-checkbox>
            <a-checkbox value="3" name="type">按钮</a-checkbox>
          </a-checkbox-group>
        </a-form-item>
        <a-form-item has-feedback label="状态" name="statu">
          <a-checkbox-group v-model:value="formState.statu">
            <a-checkbox value="1" name="type">禁用</a-checkbox>
            <a-checkbox value="2" name="type">正常</a-checkbox>
          </a-checkbox-group>
        </a-form-item>
        <a-form-item has-feedback label="排序" name="orderNum">
          <a-input-number v-model:value="formState.orderNum" />
        </a-form-item>
        <a-form-item :wrapper-col="{ span: 20, offset: 4 }">
          <a-button type="primary" html-type="submit">提交</a-button>
          <a-button style="margin-left: 10px" @click="resetForm">重置</a-button>
        </a-form-item>
      </a-form>
    </a-drawer>
    <a-modal v-model:visible="visibleIcon" title="选择图标" @ok="handleOk">
      <IconTable ref="myIcons"></IconTable>
    </a-modal>
  </div>
</template>

<script name="menu" lang="ts" setup>
import { defineComponent,ref,reactive,toRaw } from 'vue';
import { SearchOutlined } from '@ant-design/icons-vue';
import type { DrawerProps } from 'ant-design-vue';
import type { Rule } from 'ant-design-vue/es/form';
import type { FormInstance } from 'ant-design-vue';

import IconTable from  '@/components/IconTable.vue'
//获取绑定的ref
const myIcons = ref();
const formRef = ref<FormInstance>();
const placement = ref<DrawerProps['placement']>('right');
const visible = ref<boolean>(false);
const value18 = ref<string[]>([]);
interface FormState {
  parentId: string;
  name: string;
  perms: string;
  icon: string;
  path: string;
  component: string;
  type: string;
  statu: number | undefined;
  orderNum: number | undefined;
}
const formState = reactive<FormState>({
  parentId:  '',
  name: '',
  perms:  '',
  icon: '',
  path:  '',
  component:  '',
  type: '',
  statu: 0,
  orderNum:  0,
});
const options = [
  {
    value: '主页',
    label: '主页',
  },
  {
    value: '系统管理',
    label: '系统管理',
    children: [
      {
        value: '用户管理',
        label: '用户管理'
      },
      {
        value: '角色管理',
        label: '角色管理'
      },
      {
        value: '菜单管理',
        label: '菜单管理'
      },
    ],
  },
  {
    value: '系统工具',
    label: '系统工具',
    children: [
      {
        value: '数据字典',
        label: '数据字典',
      },
    ],
  },
]


let checkName= async (_rule: Rule, value: string) => {
  if (!value) {
    return Promise.reject('请输入菜单名称');
  }
};

let checkPath= async (_rule: Rule, value: string) => {
  if (!value) {
    return Promise.reject('请输入路径');
  }
};


let checkParentId= async (_rule: Rule, value: string) => {
  if (!value) {
    return Promise.reject('请选择父目录');
  }
};

let checkPerms= async (_rule: Rule, value: string) => {
  if (!value) {
    return Promise.reject('请输入权限编码');
  }
};


let checkIcon= async (_rule: Rule, value: string) => {
  if (!value) {
    return Promise.reject('请选择图标');
  }
};



let checkComponent= async (_rule: Rule, value: string) => {
  if (!value) {
    return Promise.reject('请输入组件');
  }
};

let checkType= async (_rule: Rule, value: string) => {
  if (!value) {
    return Promise.reject('请选择类型');
  }
};

let checkStatu= async (_rule: Rule, value: string) => {
  if (!value) {
    return Promise.reject('请选择状态');
  }
};


let checkOrderNum= async (_rule: Rule, value: string) => {
  if (!value) {
    return Promise.reject('请输入排序');
  }
};




let checkAge = async (_rule: Rule, value: number) => {
  if (!value) {
    return Promise.reject('Please input the age');
  }
  if (!Number.isInteger(value)) {
    return Promise.reject('Please input digits');
  } else {
    if (value < 18) {
      return Promise.reject('Age must be greater than 18');
    } else {
      return Promise.resolve();
    }
  }
};

let validatePass = async (_rule: Rule, value: string) => {
  if (value === '') {
    return Promise.reject('Please input the password');
  } else {
    // @ts-ignore
    if (formState.checkPass !== '') {
      // @ts-ignore
      formRef.value.validateFields('checkPass');
    }
    return Promise.resolve();
  }
};
let validatePass2 = async (_rule: Rule, value: string) => {
  if (value === '') {
    return Promise.reject('Please input the password again');
    // @ts-ignore
  } else if (value !== formState.pass) {
    return Promise.reject("Two inputs don't match!");
  } else {
    return Promise.resolve();
  }
};
const rules: Record<string, Rule[]> = {
  pass: [{ required: true, validator: validatePass, trigger: 'change' }],
  checkPass: [{ validator: validatePass2, trigger: 'change' }],
  age: [{ validator: checkAge, trigger: 'change' }],
  parentId: [{ validator: checkParentId, trigger: 'change' }],
  name: [{ validator: checkName, trigger: 'change' }],
  perms: [{ validator: checkPerms, trigger: 'change' }],
  icon: [{ validator: checkIcon, trigger: 'change' }],
  path: [{ validator: checkPath, trigger: 'change' }],
  component: [{ validator: checkComponent, trigger: 'change' }],
  type: [{ validator: checkType, trigger: 'change' }],
  statu: [{ validator: checkStatu, trigger: 'change' }],
  orderNum: [{ validator: checkOrderNum, trigger: 'change' }],
};
const layout = {
  labelCol: { span: 4 },
  wrapperCol: { span: 14 },
};
const handleFinish = (values: FormState) => {
  console.log(values, formState);
};
const handleFinishFailed = (errors: any) => {
  console.log(errors);
};
const resetForm = () => {
  // @ts-ignore
  formRef.value.resetFields();
};
const handleValidate = (...args: any[]) => {
  console.log(args);
};

const showDrawer = () => {
  visible.value = true;
};
const onClose = () => {
  visible.value = false;
};
const visibleIcon = ref<boolean>(false);

const showModal = () => {
  visibleIcon.value = true;
};

const handleOk = (e: MouseEvent) => {
  console.log(e);
  visibleIcon.value = false;
  myIcons.value.change()
  //这里也可以通过ref获取到子组件暴露出来想要父组件获取到的值
  formState.icon = myIcons.value.iconValue
};
const columns = [
  {
    title: '名称',
    dataIndex: 'name',
    key: 'name',
  },
  {
    title: '权限编码',
    dataIndex: 'code',
    key: 'code',
  },
  {
    title: '图标',
    dataIndex: 'icon',
    key: 'icon',
  },
  {
    title: '类型',
    dataIndex: 'type',
    key: 'type',
  },
  {
    title: '菜单path',
    dataIndex: 'path',
    key: 'path',
  },
  {
    title: '菜单组件',
    dataIndex: 'component',
    key: 'component',
  },
  {
    title: '排序号',
    dataIndex: 'sort',
    key: 'sort',
  },{
    title: '状态',
    dataIndex: 'statu',
    key: 'statu',
  },
  {
    title: '操作',
    dataIndex: 'operation',
    key: 'operation',
  },
];

interface DataItem {
  key: number;
  name: string;
  code: string;
  sort: string;
  icon:string;
  statu:string;
  type: string;
  path: string;
  component: string;
  operation: string;
  children?: DataItem[];
}

const data: DataItem[] = [
  {
    key: 1,
    name: '系统管理',
    code: 'sys:system:list',
    type: "目录",
    path: "/",
    component: "/",
    sort: '1',
    icon: 'step-forward-outlined',
    statu: '正常',
    operation: '操作',
    children: [
      {
        key: 12,
        name: '用户管理',
        code: 'sys:user:list',
        type: "菜单",
        path: "/sys/user/list",
        component: "sys/User",
        sort: '2',
        icon: 'swap-right-outlined',
        statu: '正常',
        operation: '操作',
        children: [
          {
            key: 121,
            name: '查询',
            code: 'sys:user:list',
            type: "按钮",
            path: "",
            component: "",
            sort: '3',
            icon: 'swap-right-outlined',
            statu: '禁用',
            operation: '操作',
          },
          {
            key: 121,
            name: '新增',
            code: 'sys:user:add',
            type: "按钮",
            path: "",
            component: "",
            sort: '4',
            icon: 'step-forward-outlined',
            statu: '正常',
            operation: '操作',
          },
          {
            key: 121,
            name: '修改',
            code: 'sys:user:edit',
            type: "按钮",
            path: "",
            component: "",
            sort: '5',
            icon: 'step-forward-outlined',
            statu: '禁用',
            operation: '操作',
          },
          {
            key: 121,
            name: '删除',
            code: 'sys:user:delete',
            type: "按钮",
            path: "",
            component: "",
            sort: '6',
            icon: 'step-forward-outlined',
            statu: '正常',
            operation: '操作',
          },
          {
            key: 121,
            name: '重置密码',
            code: 'sys:user:repass',
            type: "按钮",
            path: "",
            component: "",
            sort: '7',
            icon: 'step-forward-outlined',
            statu: '禁用',
            operation: '操作',
          },
        ],
      },
      {
        key: 122,
        name: '角色管理',
        code: 'sys:role:list',
        type: "目录",
        path: "/sys/role/list",
        component: "sys/Role",
        sort: '8',
        icon: 'step-forward-outlined',
        statu: '正常',
        operation: '操作',
        children: [
          {
            key: 1212,
            name: '查询',
            code: 'sys:role:list',
            type: "菜单",
            path: "",
            component: "",
            sort: '9',
            icon: 'step-forward-outlined',
            statu: '正常',
            operation: '操作',
          },
          {
            key: 1213,
            name: '新增',
            code: 'sys:role:add',
            type: "菜单",
            path: "",
            component: "",
            sort: '10',
            icon: 'step-forward-outlined',
            statu: '正常',
            operation: '操作',
          },
          {
            key: 1214,
            name: '修改',
            code: 'sys:role:edit',
            type: "菜单",
            path: "",
            component: "",
            sort: '11',
            icon: 'step-forward-outlined',
            statu: '正常',
            operation: '操作',
          },
          {
            key: 1215,
            name: '删除',
            code: 'sys:role:delete',
            type: "菜单",
            path: "",
            component: "",
            sort: '12',
            icon: 'step-forward-outlined',
            statu: '正常',
            operation: '操作',
          },
          {
            key: 121,
            name: '重置密码',
            code: 'sys:user:repass',
            type: "按钮",
            path: "",
            component: "",
            sort: '13',
            icon: 'step-forward-outlined',
            statu: '禁用',
            operation: '操作',
          },
        ],
      },
    ],
  },
];

const rowSelection = {
  onChange: (selectedRowKeys: (string | number)[], selectedRows: DataItem[]) => {
    console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
  },
  onSelect: (record: DataItem, selected: boolean, selectedRows: DataItem[]) => {
    console.log(record, selected, selectedRows);
  },
  onSelectAll: (selected: boolean, selectedRows: DataItem[], changeRows: DataItem[]) => {
    console.log(selected, selectedRows, changeRows);
  },
};

</script>

<style scoped>

</style>

在这里插入图片描述

(五)角色管理

1、设置角色信息的增删改查-权限分配

在这里插入图片描述

<template>
  <div>
    <a-form
        ref="formRef"
        :model="formState"
        :rules="rules"
    >
      <a-form-item :wrapper-col="{ span:24 }">
        <a-button type="primary" @click="showDrawer">新建</a-button>
      </a-form-item>
    </a-form>
    <a-table :columns="columns" :data-source="data" :row-selection="rowSelection">
      <template #bodyCell="{ column, record }">
        <template v-if="column.key === 'type'">
          <a-tag color="blue" v-if="record.type == '目录'">{{ record.type }}</a-tag>
          <a-tag color="green" v-if="record.type == '菜单'">{{ record.type }}</a-tag>
          <a-tag color="orange" v-if="record.type == '按钮'">{{ record.type }}</a-tag>
        </template>
        <template v-if="column.key === 'statu'">
          <a-tag color="green" v-if="record.statu == '正常'">{{ record.statu }}</a-tag>
          <a-tag color="red" v-if="record.statu == '禁用'">{{ record.statu }}</a-tag>
        </template>
        <template v-if="column.key === 'operation'">
          <a-button type="text" size="small" style="color: blue">
            编辑
          </a-button>
          <a-button type="text" size="small" style="color: red">
            删除
          </a-button>
        </template>
      </template>
    </a-table>
    <a-drawer
        title="添加菜单"
        :width="600"
        :visible="visible"
        :body-style="{ paddingBottom: '80px' }"
        :footer-style="{ textAlign: 'right' }"
        @close="onClose"
    >
       <a-form
          ref="formRef"
          name="custom-validation"
          :model="formState"
          :rules="rules"
          v-bind="layout"
          @finish="handleFinish"
          @validate="handleValidate"
          @finishFailed="handleFinishFailed"
      >
        <a-form-item has-feedback label="上级菜单" name="parentId">
          <a-input-group compact>
            <a-cascader
                v-model:value="formState.parentId"
                :options="options"
                type="parentId"
                placeholder="选择上级菜单"
            />
          </a-input-group>
        </a-form-item>
        <a-form-item has-feedback label="菜单名称" name="name">
          <a-input v-model:value="formState.name" type="name" autocomplete="off" />
        </a-form-item>
        <a-form-item has-feedback label="权限编码" name="perms">
          <a-input v-model:value="formState.perms" type="perms" autocomplete="off" />
        </a-form-item>
        <a-form-item has-feedback label="图标" name="icon">
          <a-input  v-model:value="formState.icon" @click="showModal" type="icon" autocomplete="off" />
        </a-form-item>
        <a-form-item has-feedback label="菜单URL" name="path">
          <a-input v-model:value="formState.path" type="path" autocomplete="off" />
        </a-form-item>
        <a-form-item has-feedback label="菜单组件" name="component">
          <a-input v-model:value="formState.component" type="component" autocomplete="off" />
        </a-form-item>
        <a-form-item has-feedback label="类型" name="type" >
          <a-checkbox-group v-model:value="formState.type">
            <a-checkbox value="1" name="type">目录</a-checkbox>
            <a-checkbox value="2" name="type">菜单</a-checkbox>
            <a-checkbox value="3" name="type">按钮</a-checkbox>
          </a-checkbox-group>
        </a-form-item>
        <a-form-item has-feedback label="状态" name="statu">
          <a-checkbox-group v-model:value="formState.statu">
            <a-checkbox value="1" name="type">禁用</a-checkbox>
            <a-checkbox value="2" name="type">正常</a-checkbox>
          </a-checkbox-group>
        </a-form-item>
        <a-form-item has-feedback label="排序" name="orderNum">
          <a-input-number v-model:value="formState.orderNum" />
        </a-form-item>
        <a-form-item :wrapper-col="{ span: 20, offset: 4 }">
          <a-button type="primary" html-type="submit">提交</a-button>
          <a-button style="margin-left: 10px" @click="resetForm">重置</a-button>
        </a-form-item>
      </a-form>
    </a-drawer>
    <a-modal v-model:visible="visibleIcon" title="选择图标" @ok="handleOk" okText="确认" cancelText="取消">
      <IconTable ref="myIcons"></IconTable>
    </a-modal>
  </div>
</template>

<script name="menu" lang="ts" setup>
import { defineComponent,ref,reactive,toRaw } from 'vue';
import { SearchOutlined } from '@ant-design/icons-vue';
import type { DrawerProps } from 'ant-design-vue';
import type { Rule } from 'ant-design-vue/es/form';
import type { FormInstance } from 'ant-design-vue';

import IconTable from  '@/components/IconTable.vue'
//获取绑定的ref
const myIcons = ref();
const formRef = ref<FormInstance>();
const placement = ref<DrawerProps['placement']>('right');
const visible = ref<boolean>(false);
const value18 = ref<string[]>([]);
interface FormState {
  parentId: string;
  name: string;
  perms: string;
  icon: string;
  path: string;
  component: string;
  type: string;
  statu: number | undefined;
  orderNum: number | undefined;
}
const formState = reactive<FormState>({
  parentId:  '',
  name: '',
  perms:  '',
  icon: '',
  path:  '',
  component:  '',
  type: '',
  statu: 0,
  orderNum:  0,
});

const options = [
  {
    value: '主页',
    label: '主页',
  },
  {
    value: '系统管理',
    label: '系统管理',
    children: [
      {
        value: '用户管理',
        label: '用户管理'
      },
      {
        value: '角色管理',
        label: '角色管理'
      },
      {
        value: '菜单管理',
        label: '菜单管理'
      },
    ],
  },
  {
    value: '系统工具',
    label: '系统工具',
    children: [
      {
        value: '数据字典',
        label: '数据字典',
      },
    ],
  },
]


let checkName= async (_rule: Rule, value: string) => {
  if (!value) {
    return Promise.reject('请输入菜单名称');
  }
};

let checkPath= async (_rule: Rule, value: string) => {
  if (!value) {
    return Promise.reject('请输入路径');
  }
};


let checkParentId= async (_rule: Rule, value: string) => {
  if (!value) {
    return Promise.reject('请选择父目录');
  }
};

let checkPerms= async (_rule: Rule, value: string) => {
  if (!value) {
    return Promise.reject('请输入权限编码');
  }
};


let checkIcon= async (_rule: Rule, value: string) => {
  if (!value) {
    return Promise.reject('请选择图标');
  }
};



let checkComponent= async (_rule: Rule, value: string) => {
  if (!value) {
    return Promise.reject('请输入组件');
  }
};

let checkType= async (_rule: Rule, value: string) => {
  if (!value) {
    return Promise.reject('请选择类型');
  }
};

let checkStatu= async (_rule: Rule, value: string) => {
  if (!value) {
    return Promise.reject('请选择状态');
  }
};


let checkOrderNum= async (_rule: Rule, value: string) => {
  if (!value) {
    return Promise.reject('请输入排序');
  }
};




let checkAge = async (_rule: Rule, value: number) => {
  if (!value) {
    return Promise.reject('Please input the age');
  }
  if (!Number.isInteger(value)) {
    return Promise.reject('Please input digits');
  } else {
    if (value < 18) {
      return Promise.reject('Age must be greater than 18');
    } else {
      return Promise.resolve();
    }
  }
};

let validatePass = async (_rule: Rule, value: string) => {
  if (value === '') {
    return Promise.reject('Please input the password');
  } else {
    // @ts-ignore
    if (formState.checkPass !== '') {
      // @ts-ignore
      formRef.value.validateFields('checkPass');
    }
    return Promise.resolve();
  }
};
let validatePass2 = async (_rule: Rule, value: string) => {
  if (value === '') {
    return Promise.reject('Please input the password again');
    // @ts-ignore
  } else if (value !== formState.pass) {
    return Promise.reject("Two inputs don't match!");
  } else {
    return Promise.resolve();
  }
};
const rules: Record<string, Rule[]> = {
  pass: [{ required: true, validator: validatePass, trigger: 'change' }],
  checkPass: [{ validator: validatePass2, trigger: 'change' }],
  age: [{ validator: checkAge, trigger: 'change' }],
  parentId: [{ validator: checkParentId, trigger: 'change' }],
  name: [{ validator: checkName, trigger: 'change' }],
  perms: [{ validator: checkPerms, trigger: 'change' }],
  icon: [{ validator: checkIcon, trigger: 'change' }],
  path: [{ validator: checkPath, trigger: 'change' }],
  component: [{ validator: checkComponent, trigger: 'change' }],
  type: [{ validator: checkType, trigger: 'change' }],
  statu: [{ validator: checkStatu, trigger: 'change' }],
  orderNum: [{ validator: checkOrderNum, trigger: 'change' }],
};
const layout = {
  labelCol: { span: 4 },
  wrapperCol: { span: 20 },
};
const handleFinish = (values: FormState) => {
  console.log(values, formState);
};
const handleFinishFailed = (errors: any) => {
  console.log(errors);
};
const resetForm = () => {
  // @ts-ignore
  formRef.value.resetFields();
};
const handleValidate = (...args: any[]) => {
  console.log(args);
};

const showDrawer = () => {
  visible.value = true;
};
const onClose = () => {
  visible.value = false;
};
const visibleIcon = ref<boolean>(false);

const showModal = () => {
  visibleIcon.value = true;
};

const handleOk = (e: MouseEvent) => {
  console.log(e);
  visibleIcon.value = false;
  myIcons.value.change()
  //这里也可以通过ref获取到子组件暴露出来想要父组件获取到的值
  formState.icon = myIcons.value.iconValue
};
const columns = [
  {
    title: '名称',
    dataIndex: 'name',
    key: 'name',
  },
  {
    title: '权限编码',
    dataIndex: 'code',
    key: 'code',
  },
  {
    title: '图标',
    dataIndex: 'icon',
    key: 'icon',
  },
  {
    title: '类型',
    dataIndex: 'type',
    key: 'type',
  },
  {
    title: '菜单path',
    dataIndex: 'path',
    key: 'path',
  },
  {
    title: '菜单组件',
    dataIndex: 'component',
    key: 'component',
  },
  {
    title: '排序号',
    dataIndex: 'sort',
    key: 'sort',
  },{
    title: '状态',
    dataIndex: 'statu',
    key: 'statu',
  },
  {
    title: '操作',
    dataIndex: 'operation',
    key: 'operation',
  },
];

interface DataItem {
  key: number;
  name: string;
  code: string;
  sort: string;
  icon:string;
  statu:string;
  type: string;
  path: string;
  component: string;
  operation: string;
  children?: DataItem[];
}

const data: DataItem[] = [
  {
    key: 1,
    name: '系统管理',
    code: 'sys:system:list',
    type: "目录",
    path: "/",
    component: "/",
    sort: '1',
    icon: 'step-forward-outlined',
    statu: '正常',
    operation: '操作',
    children: [
      {
        key: 12,
        name: '用户管理',
        code: 'sys:user:list',
        type: "菜单",
        path: "/sys/user/list",
        component: "sys/User",
        sort: '2',
        icon: 'swap-right-outlined',
        statu: '正常',
        operation: '操作',
        children: [
          {
            key: 121,
            name: '查询',
            code: 'sys:user:list',
            type: "按钮",
            path: "",
            component: "",
            sort: '3',
            icon: 'swap-right-outlined',
            statu: '禁用',
            operation: '操作',
          },
          {
            key: 121,
            name: '新增',
            code: 'sys:user:add',
            type: "按钮",
            path: "",
            component: "",
            sort: '4',
            icon: 'step-forward-outlined',
            statu: '正常',
            operation: '操作',
          },
          {
            key: 121,
            name: '修改',
            code: 'sys:user:edit',
            type: "按钮",
            path: "",
            component: "",
            sort: '5',
            icon: 'step-forward-outlined',
            statu: '禁用',
            operation: '操作',
          },
          {
            key: 121,
            name: '删除',
            code: 'sys:user:delete',
            type: "按钮",
            path: "",
            component: "",
            sort: '6',
            icon: 'step-forward-outlined',
            statu: '正常',
            operation: '操作',
          },
          {
            key: 121,
            name: '重置密码',
            code: 'sys:user:repass',
            type: "按钮",
            path: "",
            component: "",
            sort: '7',
            icon: 'step-forward-outlined',
            statu: '禁用',
            operation: '操作',
          },
        ],
      },
      {
        key: 122,
        name: '角色管理',
        code: 'sys:role:list',
        type: "目录",
        path: "/sys/role/list",
        component: "sys/Role",
        sort: '8',
        icon: 'step-forward-outlined',
        statu: '正常',
        operation: '操作',
        children: [
          {
            key: 1212,
            name: '查询',
            code: 'sys:role:list',
            type: "菜单",
            path: "",
            component: "",
            sort: '9',
            icon: 'step-forward-outlined',
            statu: '正常',
            operation: '操作',
          },
          {
            key: 1213,
            name: '新增',
            code: 'sys:role:add',
            type: "菜单",
            path: "",
            component: "",
            sort: '10',
            icon: 'step-forward-outlined',
            statu: '正常',
            operation: '操作',
          },
          {
            key: 1214,
            name: '修改',
            code: 'sys:role:edit',
            type: "菜单",
            path: "",
            component: "",
            sort: '11',
            icon: 'step-forward-outlined',
            statu: '正常',
            operation: '操作',
          },
          {
            key: 1215,
            name: '删除',
            code: 'sys:role:delete',
            type: "菜单",
            path: "",
            component: "",
            sort: '12',
            icon: 'step-forward-outlined',
            statu: '正常',
            operation: '操作',
          },
          {
            key: 121,
            name: '重置密码',
            code: 'sys:user:repass',
            type: "按钮",
            path: "",
            component: "",
            sort: '13',
            icon: 'step-forward-outlined',
            statu: '禁用',
            operation: '操作',
          },
        ],
      },
    ],
  },
];

const rowSelection = {
  onChange: (selectedRowKeys: (string | number)[], selectedRows: DataItem[]) => {
    console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
  },
  onSelect: (record: DataItem, selected: boolean, selectedRows: DataItem[]) => {
    console.log(record, selected, selectedRows);
  },
  onSelectAll: (selected: boolean, selectedRows: DataItem[], changeRows: DataItem[]) => {
    console.log(selected, selectedRows, changeRows);
  },
};
</script>

<style scoped>

</style>

在这里插入图片描述

(六)用户管理

用户的增删改查以及对应的权限
在这里插入图片描述

<template>
  <div>
    <a-form
        ref="formRef"
        :model="formState"
        :rules="rules"
    >
      <a-row>
        <a-col :span="12">
          <a-form-item>
            <a-input-search
                v-model:value="searchValue"
                placeholder="请输入用户名"
                enter-button="搜索"
                @search="onSearch"
            />
          </a-form-item>
        </a-col>
        <a-col :span="12">
          <a-form-item :wrapper-col="{ span:6 }">
            <a-button type="primary" @click="showDrawer">新建</a-button>
          </a-form-item>
        </a-col>
      </a-row>
    </a-form>
    <a-table :columns="columns" :data-source="data">
      <template #avatar="{ text }">
        <a-avatar :src="text" />
      </template>
      <template #name="{ text }">
        <a>{{ text }}</a>
      </template>
      <template #customTitle>
      <span>
        <smile-outlined />
        Name
      </span>
      </template>
      <template #tags="{ text: tags }">
      <span>
        <a-tag
            v-for="tag in tags"
            :key="tag"
        >
          {{ tag.toUpperCase() }}
        </a-tag>
      </span>
      </template>
      <template #action="{ record }">
        <span>
          <a>分配角色</a>
          <a-divider type="vertical" />
          <a>重置密码</a>
          <a-divider type="vertical" />
          <a @click="edit(record)" class="ant-dropdown-link">
            编辑
          </a>
          <a-divider type="vertical" />
           <a>删除</a>
          <a-divider type="vertical" />
        </span>
      </template>
    </a-table>
    <a-drawer
        title="添加用户"
        :width="600"
        :visible="visible"
        @close="handleClose"
    >
      <a-form
          ref="formRef"
          name="custom-validation"
          :model="formState"
          :rules="rulesFrom"
          v-bind="layout"
          @finish="handleFinish"
          @validate="handleValidate"
          @finishFailed="handleFinishFailed"
      >
        <a-form-item has-feedback label="菜单名称" name="name">
          <a-input v-model:value="formState.name" type="name" autocomplete="off" />
        </a-form-item>
        <a-form-item has-feedback label="头像" name="avatar">
          <a-upload
              v-model:file-list="formState.avatar"
              name="avatar"
              list-type="picture-card"
              class="avatar-uploader"
              :show-upload-list="false"
              action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
              :before-upload="beforeUpload"
              @change="handleChange"
          >
            <img v-if="imageUrl" :src="imageUrl" alt="avatar" />
            <div v-else>
              <loading-outlined v-if="loading"></loading-outlined>
              <plus-outlined v-else></plus-outlined>
              <div class="ant-upload-text">Upload</div>
            </div>
          </a-upload>
        </a-form-item>
        <a-form-item has-feedback label="权限编码" name="code">
          <a-input v-model:value="formState.code" type="code" autocomplete="off" />
        </a-form-item>
        <a-form-item has-feedback label="电话" name="phone">
          <a-input v-model:value="formState.phone" type="phone" autocomplete="off" />
        </a-form-item>
        <a-form-item has-feedback label="性别" name="sex">
          <a-select v-model:value="formState.sex" placeholder="请选择性别">
            <a-select-option value="1"></a-select-option>
            <a-select-option value="2"></a-select-option>
          </a-select>
        </a-form-item>
        <a-form-item has-feedback label="状态" name="statu">
          <a-select v-model:value="formState.statu" placeholder="请选择状态">
            <a-select-option value="1">正常</a-select-option>
            <a-select-option value="2">停止</a-select-option>
            <a-select-option value="3">注销</a-select-option>
          </a-select>
        </a-form-item>
        <a-form-item :wrapper-col="{ span: 20, offset: 4 }">
          <a-button type="primary" html-type="submit">提交</a-button>
          <a-button style="margin-left: 10px" @click="resetForm">重置</a-button>
        </a-form-item>
      </a-form>
    </a-drawer>
  </div>
</template>

<script name="user" lang="ts" setup>
import {ref,reactive } from "vue";
import { SmileOutlined, DownOutlined } from '@ant-design/icons-vue';
import { PlusOutlined, LoadingOutlined } from '@ant-design/icons-vue';
import { message } from 'ant-design-vue';
import {Rule} from "ant-design-vue/es/form";
import {FormInstance} from "ant-design-vue";
const formRef = ref<FormInstance>();
const visible = ref<boolean>(false);
const fileList = ref([]);
const loading = ref<boolean>(false);
const imageUrl = ref<string>('');

interface FormState {
  name: string;
  avatar: string[];
  code: string;
  email: string;
  phone: string;
  sex: string;
  statu: string;
}
const formState = reactive<FormState>({
  name: '',
  avatar:  [],
  code:  '',
  email: '',
  phone:  '',
  sex:  '',
  statu:  '',
});
const layout = {
  labelCol: { span: 4 },
  wrapperCol: { span: 20 },
};
let checkName= async (_rule: Rule, value: string) => {
  if (!value) {
    return Promise.reject('请输入姓名');
  }
};
let checkEmail= async (_rule: Rule, value: string) => {
  if (!value) {
    return Promise.reject('请输入邮箱');
  }
};
let checkPhone= async (_rule: Rule, value: string) => {
  if (!value) {
    return Promise.reject('请输入电话');
  }
};
let checkSex= async (_rule: Rule, value: string) => {
  if (!value) {
    return Promise.reject('请输入菜单名称');
  }
};
let checkStatu= async (_rule: Rule, value: string) => {
  if (!value) {
    return Promise.reject('请选择状态');
  }
};
let checkCode= async (_rule: Rule, value: string) => {
  if (!value) {
    return Promise.reject('请输入权限编码');
  }
};
let checkAvatar= async (_rule: Rule, value: string) => {
  if (!value) {
    return Promise.reject('请选择头像');
  }
};
const rulesFrom: Record<string, Rule[]> = {
  name: [{ validator: checkName, trigger: 'change' }],
  avatar: [{ validator: checkAvatar, trigger: 'change' }],
  code: [{ validator: checkCode, trigger: 'change' }],
  email: [{ validator: checkEmail, trigger: 'change' }],
  phone: [{ validator: checkPhone, trigger: 'change' }],
  sex: [{ validator: checkSex, trigger: 'change' }],
  statu: [{ validator: checkStatu, trigger: 'change' }],
};
const onFinish = (values: any) => {
  console.log('Success:', values);
};
let searchValue = ref("")
const columns = [
  {
    title: '头像',
    dataIndex: 'avatar',
    key: 'avatar',
    slots: {
      title: 'customTitle',
      customRender: 'avatar'
    },
  },
  {
    title: '名称',
    dataIndex: 'name',
    key: 'name',
    slots: {
      title: 'customTitle',
      customRender: 'name'
    },
  },
  {
    title: '角色',
    dataIndex: 'code',
    key: 'code',
  },
  {
    title: '邮箱',
    dataIndex: 'email',
    key: 'email',
  },
  {
    title: '电话',
    dataIndex: 'phone',
    key: 'phone',
  },
  {
    title: 'Tags',
    key: 'tags',
    dataIndex: 'tags',
    slots: {
      customRender: 'tags'
    },
  },
  {
    title: 'Action',
    key: 'action',
    slots: {
      customRender: 'action'
    },
  },
];
const data = [
  {
    key: '1',
    avatar: 'https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png',
    name: 'John Brown',
    code: 'user',
    email: '2800967183@qq.com',
    phone: '18086256816',
    tags: ['正常'],
  },
  {
    key: '2',
    avatar: 'https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png',
    name: 'Jim Green',
    code: 'doctor',
    email: '2019967083@qq.com',
    phone: '15024511186',
    tags: ['注销'],
  },
  {
    key: '3',
    avatar: 'https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png',
    name: 'Joe Black',
    code: 'admin',
    email: '2079901021@qq.com',
    phone: '15748163055',
    tags: ['正常'],
  },
];
const onSearch = () => {

}

let checkRemark= async (_rule: Rule, value: string) => {
  if (!value) {
    return Promise.reject('请输入描述');
  }
};

const rules: Record<string, Rule[]> = {
  name: [{ validator: checkName, trigger: 'change' }],
  code: [{ validator: checkCode, trigger: 'change' }],
  remark: [{ validator: checkRemark, trigger: 'change' }],
  statu: [{ validator: checkStatu, trigger: 'change' }],
};

const showDrawer = (id:number) => {
  console.log(id)
  visible.value = true;
};
const handleClose = () => {
  visible.value = false;
};
const handleFinish = (values: FormState) => {
  console.log(values, formState);
};
const handleFinishFailed = (errors: any) => {
  console.log(errors);
};
const resetForm = () => {
  // @ts-ignore
  formRef.value.resetFields();
};
const handleValidate = (...args: any[]) => {
  console.log(args);
};
interface FileItem {
  uid: string;
  name?: string;
  status?: string;
  response?: string;
  url?: string;
  type?: string;
  size: number;
  originFileObj: any;
}
interface FileInfo {
  file: FileItem;
  fileList: FileItem[];
}
function getBase64(img: Blob, callback: (base64Url: string) => void) {
  const reader = new FileReader();
  reader.addEventListener('load', () => callback(reader.result as string));
  reader.readAsDataURL(img);
}
const handleChange = (info: FileInfo) => {
  if (info.file.status === 'uploading') {
    loading.value = true;
    return;
  }
  if (info.file.status === 'done') {
    // Get this url from response in real world.
    getBase64(info.file.originFileObj, (base64Url: string) => {
      imageUrl.value = base64Url;
      loading.value = false;
    });
  }
  if (info.file.status === 'error') {
    loading.value = false;
    message.error('upload error');
  }
};

const beforeUpload = (file: FileItem) => {
  const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
  if (!isJpgOrPng) {
    message.error('You can only upload JPG file!');
  }
  const isLt2M = file.size / 1024 / 1024 < 2;
  if (!isLt2M) {
    message.error('Image must smaller than 2MB!');
  }
  return isJpgOrPng && isLt2M;
};
const edit = (e:any) => {
  resetForm();
  console.log(e)
  formState.avatar = [e.avatar];
  formState.code = e.code;
  formState.email = e.email;
  formState.name = e.name;
  formState.phone = e.phone;
  formState.sex = e.sex;
  formState.statu = e.tags[0];
  visible.value = true;
}

</script>

<style scoped>

</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/143.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【C++】STL简介 及 string的使用

文章目录1. STL简介1.1 什么是STL1.2 STL的版本1.3 STL的六大组件2. string类的使用2.1 C语言中的字符串2.2 标准库中的string类2.3 string类的常用接口说明1. string类对象的常见构造2. string类对象的容量操作3. string类对象的修改操作4. resize和reserve5. 认识迭代器&…

产品研发项目进度管理软件工具有哪些推荐?整理10款最佳进度管理软件

项目进度管理是确保项目按时完成的关键过程&#xff0c;使用合适的项目进度管理工具能确保帮助项目管理者实时了解和控制项目的进展情况&#xff0c;及时发现和解决问题&#xff0c;减少项目风险&#xff0c;提高项目效率和管理水平。这里将整理出国内外最受欢迎的10款项目进度…

ASEMI低压MOS管SI2301参数,SI2301体积,SI2301尺寸

编辑-Z ASEMI低压MOS管SI2301参数&#xff1a; 型号&#xff1a;SI2301 漏极-源极电压&#xff08;VDS&#xff09;&#xff1a;20V 栅源电压&#xff08;VGS&#xff09;&#xff1a;8V 漏极电流&#xff08;ID&#xff09;&#xff1a;2.3A 功耗&#xff08;PD&#xf…

Simulink壁咚(一)——What and How

目录 一、前言 二、Simulink 知多少 三、滤波算法 四、Model Verification 五、Model Coverage 六、Simulink测试实例 七、Simulink Test 八、Test Manager 九、Test Harness 十、 学习 一、前言 Simulink从2017b以后更加工程化和实用化&#xff0c;基于MBD的功能日趋…

MATLAB绘制ROC曲线

ROC曲线(Receiver Operating Characteristic Curve) 1 简介 ROC曲线是用于评估二元分类模型&#xff08;如Logistic回归&#xff09;表现优劣的一种工具&#xff0c;其横轴表示假阳性率&#xff08;false positive rate&#xff0c;FPR&#xff09;&#xff0c;即实际为负例但…

MySQL事务详解

&#x1f3c6;今日学习目标&#xff1a; &#x1f340;Spring事务和MySQL事务详解 ✅创作者&#xff1a;林在闪闪发光 ⏰预计时间&#xff1a;30分钟 &#x1f389;个人主页&#xff1a;林在闪闪发光的个人主页 &#x1f341;林在闪闪发光的个人社区&#xff0c;欢迎你的加入: …

vue3 构建属于自己的组件库dxui

文章目录前言第一步&#xff0c;通过vue-cli搭建vue3框架第二步&#xff0c;构建一个入口&#xff0c;将所有的组件统一管理第三步 修改package.json &#xff0c;对组件进行单独打包第四步输入命令行开始打包第五步&#xff0c;修改package.json文件&#xff0c;为npm 发布做准…

[ vulnhub靶机通关篇 ] 渗透测试综合靶场 DC-1 通关详解 (附靶机搭建教程)

&#x1f36c; 博主介绍 &#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 _PowerShell &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 &#x1f389;点赞➕评论➕收藏 养成习…

Python 基础教程【1】:Python介绍、变量和数据类型、输入输出、运算符

本文已收录于专栏&#x1f33b;《Python 基础》文章目录1、Python 介绍2、变量和数据类型2.1 注释的使用2.2 变量以及数据类型2.2.1 什么是变量&#xff1f;2.2.2 怎么给变量起名&#xff1f;2.2.3 变量的类型&#x1f3a8; 整数 int&#x1f3a8; 浮点数&#xff08;小数&…

教你成为比卡卡西还牛逼的全能忍者,全拷贝与分割函数

如何成为一个集雷切&#xff0c;写轮眼侦查和拷贝与一身的卡卡西&#xff0c;下面教你&#xff01; 目录 第一式——雷切&#xff01; strtok 第二式——写轮眼侦查&#xff01; strerror函数 第三式——写轮眼拷贝&#xff01; memcpy 模拟实现memcpy函数 &#x1f60e;…

Hadoop集群搭建

文章目录一、运行环境配置(所有节点)1、基础配置2、配置Host二、依赖软件安装(101节点)1、安装JDK2、安装Hadoop(root)3、Hadoop目录结构三、本地运行模式&#xff08;官方WordCount&#xff09;1、简介2、本地运行模式&#xff08;官方WordCount&#xff09;四、完全分布式运行…

多线程的风险 --- 线程安全

✨个人主页&#xff1a;bit me&#x1f447; ✨当前专栏&#xff1a;Java EE初阶&#x1f447; ✨每日一语&#xff1a;低头赶路&#xff0c;敬事如仪&#xff1b;自知自心&#xff0c;其路则明。 目 录&#x1f378;一. 线程不安全&#x1f379;二. 线程不安全的原因&#x1f…

看完书上的栈不过瘾,为什么不动手试试呢?

一.栈的基本概念1.栈的定义栈&#xff08;Stack&#xff09;&#xff1a;是只允许在一端进行插入或删除的线性表。首先栈是一种线性表&#xff0c;但限定这种线性表只能在某一端进行插入和删除操作。其中注意几点&#xff1a;栈顶&#xff08;Top&#xff09;&#xff1a;线性表…

【C语言蓝桥杯每日一题】—— 单词分析

【C语言蓝桥杯每日一题】—— 单词分析&#x1f60e;前言&#x1f64c;单词分析&#x1f64c;总结撒花&#x1f49e;&#x1f60e;博客昵称&#xff1a;博客小梦 &#x1f60a;最喜欢的座右铭&#xff1a;全神贯注的上吧&#xff01;&#xff01;&#xff01; &#x1f60a;作者…

三天吃透MySQL面试八股文

本文已经收录到Github仓库&#xff0c;该仓库包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等核心知识点&#xff0c;欢迎star~ Github地址&#xff1a;https://github.com/…

C 语言编程 — 线程池设计与实现

目录 文章目录目录线程池&#xff08;Thread Pool&#xff09;tiny-threadpool数据结构设计Task / JobTask / Job QueueWorker / ThreadThread Pool ManagerPublic APIsPrivate Functions运行示例线程池&#xff08;Thread Pool&#xff09; 线程池&#xff08;Thread Pool&am…

Spring Cloud学习笔记【初识微服务基础框架搭建】

文章目录微服务架构介绍架构图核心组件Spring Cloud版本对应基础框架搭建1.建造父工程2.建造子工程user工程建造auth工程建造RestTemplate 实现微服务远程调用RestTemplate 介绍配置RestTemplate测试远程访问总结微服务架构 介绍 微服务架构是一种将应用程序拆分成小型、自治…

设计模式之工厂模式

工厂模式是设计模式中的经典模式&#xff0c;工厂模式又可分为以下三种类型&#xff1a; 简单工厂模式工厂方法模式抽象工厂模式 这三种模式可以理解为同一种编程思想的三个版本&#xff0c;从简单到高级不断升级。本文将着重介绍简单工厂模式。 简单工厂模式 简单工厂模式&…

哈佛与冯诺依曼结构

1. 下图是典型的冯诺依曼结构 2. CPU分为三部分&#xff1a;ALU运算单元&#xff0c;CU控制单元&#xff0c;寄存器组。 3. 分析51单片机为何能使用汇编进行编程 51指令集&#xff08;Instruction Set&#xff09;是单片机CPU能够执行的所有指令的集合。在编写51单片机程序时&a…

Python打包成exe,文件太大问题解决办法(比保姆级还保姆级)

首先我要说一下&#xff0c;如果你不在乎大小&#xff0c;此篇直接别看了&#xff0c;因为我写过直接打包的&#xff0c;就多20M而已&#xff0c;这篇就别看了&#xff0c;点击查看不在乎大小直接打包这篇我觉得简单的令人发指 不废话&#xff0c;照葫芦画瓢就好 第1步&#…