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

news2024/11/19 1:45:51

本项目分为二部分
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>

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ASEMI高压MOS管20N60参数,20N60尺寸,20N60体积

编辑-Z ASEMI高压MOS管20N60参数&#xff1a; 型号&#xff1a;20N60 漏极-源极电压&#xff08;VDS&#xff09;&#xff1a;600V 栅源电压&#xff08;VGS&#xff09;&#xff1a;30V 漏极电流&#xff08;ID&#xff09;&#xff1a;20A 功耗&#xff08;PD&#xff…

项目最后一刻发生范围变更该怎么处理?

不管是项目需求发生了变化&#xff0c;还是第一轮可交付成果没有完全达到预期&#xff0c;在项目范围定义的初始阶段之后可能发生变化的原因有很多。当这种情况发生时&#xff0c;你需要准备好一个计划来处理最后一刻的范围变更和调整。 什么是范围变更&#xff1f; 范围变更是…

浪潮 KaiwuDB x 山东重工 | 打造离散制造业 IIoT 标杆解决方案

近日&#xff0c;浪潮 KaiwuDB 携手山东重工集团有限公司&#xff08;以下简称&#xff1a;山东重工&#xff09;重磅发布“离散制造业 IIoT 解决方案”。该 IIoT 方案以 KaiwuDB 就地运算专利技术为底座&#xff0c;搭建了”多快优智”的“13N”方案体系&#xff0c;目前已率先…

南京、西安集成电路企业和高校分布一览(附产业链主要厂商及高校名录)

前言 3月2日&#xff0c;国务院副总理刘鹤在北京调研集成电路企业发展&#xff0c;并主持召开座谈会。刘鹤指出&#xff0c;集成电路是现代化产业体系的核心枢纽&#xff0c;关系国家安全和中国式现代化进程。他表示&#xff0c;我国已形成较完整的集成电路产业链&#xff0c;也…

视频理解论文串讲——学习笔记

文章目录DeepVideoTwo-StreamBeyond-short-SmippetsConvolutional FusionTSNC3DI3DNon-localR&#xff08;21&#xff09;DSlowFastTimesformer本文是对视频理解领域论文串讲的笔记记录。 一篇相关综述&#xff1a;Yi Zhu, Xinyu Li, Chunhui Liu, Mohammadreza Zolfaghari, Yu…

【YOLO】YOLOv8训练自定义数据集

1. 运行环境 windows11 和 Ubuntu20.04&#xff08;建议使用 Linux 系统&#xff09; 首先切换到自己建立的虚拟环境安装 pytorch torch 1.12.0cu116&#xff08;根据自身设备而定&#xff09; torchvision 0.13.0cu116&#xff08;根据自身设备而定&…

详解JAVA枚举类

目录 1.概述 2.常用API 2.1.清单 2.2.代码示例 2.2.1.ordinal 2.2.2.compareTo 2.2.3.toString 2.2.4.valueOf 2.2.5.values 3.成员变量和带参构造 1.概述 枚举变量指的是变量的取值只在一个有限的集合内&#xff0c;如性别、星期几、颜色等。从JDK5开始&#xff0…

超详细CentOS7 NAT模式(有图形化界面)网络配置

在此附上CentOS7&#xff08;有可视化界面版&#xff09;安装教程 超详细VMware CentOS7&#xff08;有可视化界面版&#xff09;安装教程 打开VMware—>点击编辑---->选择虚拟网络编辑器 打开虚拟网络编辑器后如下图所示&#xff1a; 从下图中我们看到最下面子网IP为…

软测入门(九)unit test

unit test 核心概念 TestCase:测试用例&#xff1a;用类的方式 组织对一个功能的多项测试Fixture : 夹具&#xff0c;用来固定测试环境TestSuite:测试套件:组织多个TestCaseTestRunner:测试执行:用来执行TestSuit&#xff0c;可以导出测试结果 入门 类需要继承unittest.Tes…

ENVI IDL学习笔记之基本操作

前言ENVI IDL&#xff08;交互式数据语言&#xff09;是一个通用的科学计算包&#xff0c;它提供了一套数学函数、数据分析工具&#xff0c;以及一些科学可视化和动画工具。IDL 是 ENVI 图像处理和分析软件的基础&#xff0c;可用于编写脚本并自动执行许多使用 ENVI 图形用户界…

【鲁棒优化】基于联合聚类和定价的鲁棒功率控制方法(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

12接口扩展无忧,存储显示充电都拉满,ORICO XDR扩展坞上手

扩展坞现在很多朋友都用&#xff0c;一般是配合笔记本使用&#xff0c;有些带有桌面模式的手机、平板装上扩展坞之后&#xff0c;也可以变身全能型的办公设备。现在市面上的扩展坞选择不少&#xff0c;我目前用的是一款功能比较全的12合1扩展坞&#xff0c;来自国产品牌ORICO。…

【机会约束、鲁棒优化】具有排放感知型经济调度中机会约束和鲁棒优化研究【IEEE6节点、IEEE118节点算例】(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Django/Vue实现在线考试系统-03-开发环境搭建-MySQL安装

1.概述 MySQL是一种关系型数据库管理系统,所使用的 SQL 语言是用于访问数据库的最常用标准化语言。MySQL 软件采用了双授权政策,分为社区版和商业版,由于其体积小、速度快、总体拥有成本低,尤其是开放源码这一特点,一般中小型和大型网站的开发都选择 MySQL 作为网站数据库…

Redis的持久化操作

目录 介绍 RDB(redis database) 是什么 备份如何执行 配置 优势 劣势 备份恢复 AOF(Append Only File) 是什么 数据恢复 正常恢复 异常恢复 同步频率设置 重写(压缩) 持久化流程 优势 劣势 总结 介绍 redis持久化操作方式有两种&#xff1a;RDB和AOF。 RDB(redis database) 是…

mysql数据库之触发器

触发器是与表有关的数据库对象&#xff0c;指在insert、update、delete之前或之后&#xff0c;触发并执行触发器中定义的SQL语句集合。触发器的这种特性可以协助应用在数据库端确保数据的完整性&#xff0c;日志记录&#xff0c;数据校验等操作。 使用别名old和new来引用触发器…

华为OD机试题,用 Java 解【子序列长度】问题

华为Od必看系列 华为OD机试 全流程解析+经验分享,题型分享,防作弊指南)华为od机试,独家整理 已参加机试人员的实战技巧华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典使用说明 参加华为od机试,一定要注意不…

力扣第99场双周赛题目记录(复盘)

第一题 2578.最小和分割 给你一个正整数 num &#xff0c;请你将它分割成两个非负整数 num1 和 num2 &#xff0c;满足&#xff1a; num1 和 num2 直接连起来&#xff0c;得到 num 各数位的一个排列。 换句话说&#xff0c;num1 和 num2 中所有数字出现的次数之和等于 num 中所…

DolphinScheduler理论知识以及手机、邮箱、钉钉、电话等多种告警部署实操

1、DolphinScheduler简介 1.1、DolphinScheduler概述 Apache DolphinScheduler是一个新一代分布式、易扩展的可视化大数据工作流任务调度平台&#xff0c;致力于“解决大数据任务之间错综复杂的依赖关系&#xff0c;整个数据处理开箱即用”。它以 DAG(有向无环图) 的方式将任…

IDEA插件开发.02之“异味”代码收集插件

前言许久没更新IDEA插件开发系列了。最近刚好在汇总日常开发中常见的代码“异味”&#xff0c;共享文档复制黏贴略显麻烦&#xff0c;所以想着是否可以搞一个IDEA插件来帮忙收集常见代码&#xff0c;毕竟IDEA作为后端程序员必备的开发工具&#xff0c;显然会方便很多。于是&…