中文亚洲精品无码_熟女乱子伦免费_人人超碰人人爱国产_亚洲熟妇女综合网

當(dāng)前位置: 首頁 > news >正文

怎樣免費建立自己網(wǎng)站長春做網(wǎng)站推薦選吉網(wǎng)傳媒好

怎樣免費建立自己網(wǎng)站,長春做網(wǎng)站推薦選吉網(wǎng)傳媒好,牛商網(wǎng)做的包裝盒網(wǎng)站,長沙三日游詳細(xì)攻略本項目分為二部分 1、后臺管理系統(tǒng)(用戶管理,角色管理,視頻管理等) 2、客戶端(登錄注冊、發(fā)布視頻) Vue3做出B站【bilibili】 Vue3TypeScript【快速入門一篇文章精通系列(一)前端項目…

本項目分為二部分
1、后臺管理系統(tǒng)(用戶管理,角色管理,視頻管理等)
2、客戶端(登錄注冊、發(fā)布視頻)

在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

Vue3做出B站【bilibili】 Vue3+TypeScript【快速入門一篇文章精通系列(一)前端項目案例】

  • 一、前言
  • 二、項目創(chuàng)建基本頁面搭建
    • (一)創(chuàng)建Vue3 + TypeScript項目
      • 1、新建Vue3 項目
      • 2、用WebStorm打開項目
        • 1)打開項目以后執(zhí)行 `npm install`
        • 2)安裝TypeScript
        • 3)設(shè)置一下WebStorm配置
      • 3、配置項目
        • 1)安裝依賴
        • 2)路由配置
        • 3)pinia配置
        • 4)vite.config.ts配置
        • 5)按需引入ant-design-vue
        • 6)安裝axios
      • 4、設(shè)置頁面路由
    • (二)實現(xiàn)登錄頁面
      • 1、設(shè)置登陸頁面
      • 2、設(shè)置登錄請求
      • 3、創(chuàng)建mock.ts
      • 4、顯示驗證碼
      • 5、在store當(dāng)中的store.ts設(shè)置SET_TOKEN
      • 6、實現(xiàn)登錄請求相關(guān)內(nèi)容
      • 7、完善登錄
        • (1)在main.ts當(dāng)中引入antd的全局樣式
        • (2)完善request.ts當(dāng)中響應(yīng)的內(nèi)容
  • 三、后臺管理界面開發(fā)
    • (一)創(chuàng)建index頁面
      • 1、新建index頁面
      • 2、設(shè)置路由
      • 3、完善菜單頁面內(nèi)容
      • 4、Vue代碼抽取
        • (1)抽取菜單
        • (2)index
      • 5、設(shè)置子路由
      • 6、編寫導(dǎo)航欄路由
        • 1)創(chuàng)建需要路由跳轉(zhuǎn)的頁面
        • 2)設(shè)置頁面路由
    • (二)用戶登錄信息展示
      • 1、完善用戶接口
      • 2、設(shè)置mock.js
      • 3、設(shè)置個人中心的路由
      • 4、設(shè)置退出登錄
    • (三)動態(tài)菜單開發(fā)
      • 1、修改一下路由規(guī)則
      • 2、設(shè)置動態(tài)菜單的數(shù)據(jù)
        • (1)自定義Icon組件
        • (2)SideMenu.vue菜單頁面
        • (3)創(chuàng)建保存菜單的狀態(tài)信息的內(nèi)容
        • (4)設(shè)置mockjs
        • (5)完善SideMenu.vue菜單頁面,設(shè)置請求并渲染菜單
      • 3、設(shè)置動態(tài)路由加載一次以后無需二次加載
      • 4、實現(xiàn)動態(tài)導(dǎo)航
      • 5、設(shè)置側(cè)欄和頁面進(jìn)行動態(tài)綁定
      • 6、完善Tabs標(biāo)簽頁
    • (四)菜單管理界面開發(fā)
      • 1、在Menu當(dāng)中設(shè)置表格樣式
      • 2、在Menu當(dāng)中設(shè)置新增和編輯
    • (五)角色管理
      • 1、設(shè)置角色信息的增刪改查-權(quán)限分配
    • (六)用戶管理

一、前言

在前端方面我們使用的技術(shù)棧包括

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

二、項目創(chuàng)建基本頁面搭建

(一)創(chuàng)建Vue3 + TypeScript項目

1、新建Vue3 項目

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

在這里插入圖片描述
將生成的js文件都修改為ts文件
在這里插入圖片描述
在這里插入圖片描述

2、用WebStorm打開項目

1)打開項目以后執(zhí)行 npm install

在這里插入圖片描述
執(zhí)行成功
在這里插入圖片描述

2)安裝TypeScript

安裝TypeScript

npm install -g typescript

在這里插入圖片描述
安裝完成后,在控制臺運行如下命令,檢查安裝是否成功(3.x):

tsc -v

在這里插入圖片描述

3)設(shè)置一下WebStorm配置

在這里插入圖片描述
設(shè)置自動編譯
在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

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

3、配置項目

1)安裝依賴

作為前端項目我們使用一些場景的依賴
這里我們只需要將以下依賴復(fù)制到package.json,重新運行npm install
將package-lock.json文件夾刪除
在這里插入圖片描述
在package.json當(dāng)中
在這里插入圖片描述

"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"}

執(zhí)行npm install

除了以上安裝方式以外,
你也可以自己找到對應(yīng)依賴的官方網(wǎng)站,
一個一個手動安裝

2)路由配置

創(chuàng)建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、創(chuàng)建路由實例并傳遞‘routes’配置
//你可以在這里輸入更多的配置,但是我們在這里
const router = createRouter({//4、內(nèi)部提供了 history 模式的實現(xiàn)。為了簡單起見,我們在這里使用hash模式history:createWebHashHistory(),routes, //routes:routes 的縮寫
})
export default router

創(chuàng)建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}},// 開啟數(shù)據(jù)緩存// @ts-ignorepersist: { //數(shù)據(jù)默認(rèn)存在 sessionStorage 里,并且會以 store 的 id 作為 keyenabled: true}
})

在main.ts當(dāng)中引入如上內(nèi)容
在這里插入圖片描述

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當(dāng)中放置一個按鈕
在這里插入圖片描述

<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'
// 創(chuàng)建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、設(shè)置頁面路由

刪除頁面的自動創(chuàng)建好的頁面
在這里插入圖片描述
在這里插入圖片描述
設(shè)置路由
在這里插入圖片描述

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、創(chuàng)建路由實例并傳遞‘routes’配置
//你可以在這里輸入更多的配置,但是我們在這里
const router = createRouter({//4、內(nèi)部提供了 history 模式的實現(xiàn)。為了簡單起見,我們在這里使用hash模式history:createWebHashHistory(),routes, //routes:routes 的縮寫
})
export default router

(二)實現(xiàn)登錄頁面

1、設(shè)置登陸頁面

我們找到From表單的內(nèi)容
https://www.antdv.com/components/form-cn
在這里插入圖片描述

在這里插入圖片描述

復(fù)制上述代碼,但是我們并不會直接使用期內(nèi)容

在這里插入圖片描述

<template><a-formref="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當(dāng)中樣式
在這里插入圖片描述
在這里插入圖片描述
調(diào)整一下頁面
在這里插入圖片描述

<template><a-card style="width: 800px;margin:10% auto;border-radius: 15px;"><div style="width: 200px;margin: auto"><a-imagestyle="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-itemlabel="賬號"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-itemlabel="密碼"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-itemlabel="驗證碼"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、設(shè)置登錄請求

在這里插入圖片描述

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

3、創(chuàng)建mock.ts

安裝qs
qs:查詢參數(shù)序列化和解析庫

npm install qs

安裝mockjs
mockjs:為我們生成隨機(jī)數(shù)據(jù)的工具庫

npm install mockjs

在這里插入圖片描述
在main.ts當(dāng)中引入mock.ts
在這里插入圖片描述

import "@/mock"

完善mock.ts
在這里插入圖片描述

// @ts-ignore
import Mock from "mockjs";const Random = Mock.Randomlet Result = {code: 200,msg: '操作成功',data: null
}Mock.mock('/bilibili-api/captcha','get',()=>{// @ts-ignoreResult.data = {token: Random.string(32),captchaImg:Random.dataImage('120x40','p7n5w')}return Result;
})

4、顯示驗證碼

完善request.ts,設(shè)置發(fā)起請求的內(nèi)容
在這里插入圖片描述

import axios from 'axios'
// @ts-ignore
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
// 創(chuàng)建axios實例const service = axios.create({// axios中請求配置有baseURL選項,表示請求URL公共部分baseURL: "/bilibili-api",//baseURL: "/",// 超時timeout: 10000
})
export default service

完善vite.config.ts,設(shè)置發(fā)起請求的路徑和地址
在這里插入圖片描述

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

完善src/api/index.ts設(shè)置請求
在這里插入圖片描述

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

設(shè)置登錄頁面完善請求內(nèi)容
在這里插入圖片描述

 <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當(dāng)中的store.ts設(shè)置SET_TOKEN

在這里插入圖片描述

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

6、實現(xiàn)登錄請求相關(guān)內(nèi)容

在這里插入圖片描述

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 = 404Result.msg = "驗證碼錯誤"return Result;
})

7、完善登錄

(1)在main.ts當(dāng)中引入antd的全局樣式

在這里插入圖片描述

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

(2)完善request.ts當(dāng)中響應(yīng)的內(nèi)容

在這里插入圖片描述

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'
// 創(chuàng)建axios實例const service = axios.create({// axios中請求配置有baseURL選項,表示請求URL公共部分baseURL: "/bilibili-api",// 超時timeout: 10000
})
service.interceptors.request.use(config => {// @ts-ignoreconfig.headers['Authorization'] = localStorage.getItem("token")return config;
});
service.interceptors.response.use(response => {let res = response.dataif (res.code === 200) {return response} else {Message.error(!res.msg ? '系統(tǒng)異常' : 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
在這里插入圖片描述

三、后臺管理界面開發(fā)

(一)創(chuàng)建index頁面

一般來說,管理系統(tǒng)的頁面我們都是頭部是一個簡單的信息展示系統(tǒng)名稱和登錄用戶信息,然后中間的左邊是菜單導(dǎo)航欄,右邊是內(nèi)容,對應(yīng)到ant Design Vue的組件中,我們可以找到這個Layout 布局容器用于布局,方便快速搭建頁面的基本結(jié)構(gòu)。

而我們采用這個布局:
在這里插入圖片描述

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、設(shè)置路由

在這里插入圖片描述

router.push("/index");

在這里插入圖片描述
設(shè)置一下狀態(tài)碼使其跳轉(zhuǎn)成功
在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

3、完善菜單頁面內(nèi)容

在這里插入圖片描述

<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后臺管理系統(tǒng)</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>系統(tǒng)管理</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>系統(tǒng)工具</template><a-menu-item key="7">數(shù)字字典</a-menu-item></a-sub-menu></a-menu></a-layout-sider><a-layout-content :style="{  marginTop: '0' }"><a-menuv-model:selectedKeys="selectedKeysTop"theme="dark"mode="horizontal":style="{ lineHeight: '64px',marginLeft:'-15px' }"><a-sub-menu key="sub2" ><template #title><div ><menu-unfold-outlinedv-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 >后臺管理系統(tǒng)</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>系統(tǒng)管理</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>系統(tǒng)工具</template><a-menu-item key="7">數(shù)字字典</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的內(nèi)容全部抽取到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-menuv-model:selectedKeys="selectedKeysTop"theme="dark"mode="horizontal":style="{ lineHeight: '64px',marginLeft:'-15px' }"><a-sub-menu key="sub2" ><template #title><div ><menu-unfold-outlinedv-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、設(shè)置子路由

在這里插入圖片描述

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

在Home.vue當(dāng)中設(shè)置路由
在這里插入圖片描述

 <router-view>
</router-view>

訪問頁面:http://localhost/#/index
在這里插入圖片描述

6、編寫導(dǎo)航欄路由

1)創(chuàng)建需要路由跳轉(zhuǎn)的頁面

在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

在這里插入圖片描述

防止手殘貼上全部代碼

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、創(chuàng)建路由實例并傳遞‘routes’配置
//你可以在這里輸入更多的配置,但是我們在這里
const router = createRouter({//4、內(nèi)部提供了 history 模式的實現(xiàn)。為了簡單起見,我們在這里使用hash模式history:createWebHashHistory(),routes, //routes:routes 的縮寫
})
export default router

訪問:http://localhost/#/roles
在這里插入圖片描述

訪問: http://localhost/#/users
在這里插入圖片描述
訪問:http://localhost/#/menus

2)設(shè)置頁面路由

在這里插入圖片描述

	<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>系統(tǒng)管理</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>系統(tǒng)工具</template><a-menu-item key="7"><template #icon><ContainerOutlined /></template>數(shù)字字典</a-menu-item></a-sub-menu>

點擊測試
在這里插入圖片描述
在這里插入圖片描述

(二)用戶登錄信息展示

管理界面的右上角的用戶信息現(xiàn)在是寫死的,
因為我們現(xiàn)在已經(jīng)登錄成功,所以我們可以通過接口去請求獲取到當(dāng)前的用戶信息了,
這樣我們就可以動態(tài)顯示用戶的信息,這個接口比較簡單,然后退出登錄的鏈接也一起完成,
就請求接口同時把瀏覽器中的緩存刪除就退出了哈。

1、完善用戶接口

在這里插入圖片描述

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

2、設(shè)置mock.js

在這里插入圖片描述


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

3、設(shè)置個人中心的路由

在這里插入圖片描述

<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
},

創(chuàng)建對應(yīng)的頁面
在這里插入圖片描述
在這里插入圖片描述

<template><a-formref="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、設(shè)置退出登錄

在這里插入圖片描述

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");});
}

設(shè)置Store的狀態(tài)
在這里插入圖片描述

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

設(shè)置mock
在這里插入圖片描述

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

(三)動態(tài)菜單開發(fā)

1、修改一下路由規(guī)則

在這里插入圖片描述

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

2、設(shè)置動態(tài)菜單的數(shù)據(jù)

(1)自定義Icon組件

在這里插入圖片描述
目前先這樣。后期會對其進(jìn)行優(yōu)化
在這里插入圖片描述

<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 >后臺管理系統(tǒng)</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: '系統(tǒng)管理',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: '系統(tǒng)工具',name: 'SysTools',icon: 'menu-outlined',path: '',children: [{title: '數(shù)字字典',name: 'SysDict',icon: 'container-outlined',path: '/sys/dicts',children: []}]}]
})
</script>
<style scoped>
</style>

刷新并訪問頁面
在這里插入圖片描述

(3)創(chuàng)建保存菜單的狀態(tài)信息的內(nèi)容

在這里插入圖片描述

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

發(fā)送獲取菜單的請求
在這里插入圖片描述

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

在路由當(dāng)中獲取拿到menuList,
在這里插入圖片描述

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

設(shè)置在路由加載前拿到前,拿到菜單的內(nèi)容并添加到Store
在這里插入圖片描述

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

(4)設(shè)置mockjs

在這里插入圖片描述


Mock.mock('/bilibili-api/sys/menu/nav', 'get', () => {let nav = [{key:101,title: '系統(tǒng)管理',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: '系統(tǒng)工具',name: 'SysTools',icon: 'menu-outlined',path: '',children: [{title: '數(shù)字字典',name: 'SysDict',icon: 'container-outlined',path: '/sys/dicts',children: []}]}];// @ts-ignorelet authoritys = [];// @ts-ignoreResult.data = {nav: nav,// @ts-ignoreauthoritys: authoritys}return Result;
})

(5)完善SideMenu.vue菜單頁面,設(shè)置請求并渲染菜單

在這里插入圖片描述

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、設(shè)置動態(tài)路由加載一次以后無需二次加載

在這里插入圖片描述

hasRoutes:falsegetHasRoutes: (state) => state.hasRoutes,changeRouteStatus(hasRoutes:any){this.hasRoutes = hasRoutes;
}

在這里插入圖片描述

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

4、實現(xiàn)動態(tài)導(dǎo)航

在這里插入圖片描述
在這里插入圖片描述

<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當(dāng)中引入該內(nèi)容
在這里插入圖片描述

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

在這里插入圖片描述

5、設(shè)置側(cè)欄和頁面進(jìn)行動態(tài)綁定

  • 在store.ts當(dāng)中設(shè)置添加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;//設(shè)置高亮同步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標(biāo)簽頁

在這里插入圖片描述

<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-ignoreif (uStore.key === targetKey) {lastIndex = i - 1;}});// @ts-ignoreuStore = uStore.filter(pane => pane.key !== targetKey);if (uStore.length && activeKey.value === targetKey) {if (lastIndex >= 0) {// @ts-ignoreactiveKey.value = uStore[lastIndex].key;} else {// @ts-ignoreactiveKey.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>
  • 我們發(fā)現(xiàn)重復(fù)點擊會重復(fù)的添加到上面,現(xiàn)在設(shè)置重復(fù)點擊不會出現(xiàn)重復(fù)的信息
    在這里插入圖片描述
    在這里插入圖片描述
		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;},

多次點擊以后不會出現(xiàn)
在這里插入圖片描述

  • 設(shè)置點擊tab進(jìn)行內(nèi)容的切換
    在這里插入圖片描述
<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().getEditableTabslet 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>后臺管理系統(tǒng)</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");
};

設(shè)置通過ip路徑訪問的時候,設(shè)置對應(yīng)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

在這里插入圖片描述
設(shè)置退出登錄后清除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");});
}

(四)菜單管理界面開發(fā)

1、在Menu當(dāng)中設(shè)置表格樣式

在這里插入圖片描述

<template><div><a-formref="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: '請輸入權(quán)限編碼',},type: {required: true,message: '請選擇類型',},orderNum: {required: true,message: '請?zhí)钊肱判蛱?#39;,},statu: {required: true,message: '請選擇狀態(tài)',},
};
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: '權(quán)限編碼',dataIndex: 'code',key: 'code',},{title: '圖標(biāo)',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: '狀態(tài)',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: '系統(tǒng)管理',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>

在這里插入圖片描述

上面當(dāng)面數(shù)據(jù)超出頁面的時候頁面跟著滾動這樣不太好我們優(yōu)化一下,設(shè)置側(cè)邊欄和頭部不懂,設(shè)置內(nèi)容區(qū)域滾動
在這里插入圖片描述
在這里插入圖片描述

<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-menuv-model:selectedKeys="selectedKeysTop"theme="dark"mode="horizontal":style="{ lineHeight: '64px',marginLeft:'-15px' }"><a-sub-menu key="sub2" ><template #title><div ><menu-unfold-outlinedv-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.datauserInfo = 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>

內(nèi)容滾動頭部底部不滾
在這里插入圖片描述

2、在Menu當(dāng)中設(shè)置新增和編輯

在這里插入圖片描述
在這里插入圖片描述

<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-formref="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-drawertitle="添加菜單":width="600":visible="visible":body-style="{ paddingBottom: '80px' }":footer-style="{ textAlign: 'right' }"@close="onClose"><a-formref="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-cascaderv-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="權(quán)限編碼" name="perms"><a-input v-model:value="formState.perms" type="perms" autocomplete="off" /></a-form-item><a-form-item has-feedback label="圖標(biāo)" 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="狀態(tài)" 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="選擇圖標(biāo)" @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: '系統(tǒng)管理',label: '系統(tǒng)管理',children: [{value: '用戶管理',label: '用戶管理'},{value: '角色管理',label: '角色管理'},{value: '菜單管理',label: '菜單管理'},],},{value: '系統(tǒng)工具',label: '系統(tǒng)工具',children: [{value: '數(shù)據(jù)字典',label: '數(shù)據(jù)字典',},],},
]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('請輸入權(quán)限編碼');}
};let checkIcon= async (_rule: Rule, value: string) => {if (!value) {return Promise.reject('請選擇圖標(biāo)');}
};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('請選擇狀態(tài)');}
};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-ignoreif (formState.checkPass !== '') {// @ts-ignoreformRef.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-ignoreformRef.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: '權(quán)限編碼',dataIndex: 'code',key: 'code',},{title: '圖標(biāo)',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: '狀態(tài)',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: '系統(tǒng)管理',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、設(shè)置角色信息的增刪改查-權(quán)限分配

在這里插入圖片描述

<template><div><a-formref="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-drawertitle="添加菜單":width="600":visible="visible":body-style="{ paddingBottom: '80px' }":footer-style="{ textAlign: 'right' }"@close="onClose"><a-formref="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-cascaderv-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="權(quán)限編碼" name="perms"><a-input v-model:value="formState.perms" type="perms" autocomplete="off" /></a-form-item><a-form-item has-feedback label="圖標(biāo)" 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="狀態(tài)" 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="選擇圖標(biāo)" @ok="handleOk" okText="確認(rèn)" 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: '系統(tǒng)管理',label: '系統(tǒng)管理',children: [{value: '用戶管理',label: '用戶管理'},{value: '角色管理',label: '角色管理'},{value: '菜單管理',label: '菜單管理'},],},{value: '系統(tǒng)工具',label: '系統(tǒng)工具',children: [{value: '數(shù)據(jù)字典',label: '數(shù)據(jù)字典',},],},
]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('請輸入權(quán)限編碼');}
};let checkIcon= async (_rule: Rule, value: string) => {if (!value) {return Promise.reject('請選擇圖標(biāo)');}
};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('請選擇狀態(tài)');}
};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-ignoreif (formState.checkPass !== '') {// @ts-ignoreformRef.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-ignoreformRef.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: '權(quán)限編碼',dataIndex: 'code',key: 'code',},{title: '圖標(biāo)',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: '狀態(tài)',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: '系統(tǒng)管理',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>

在這里插入圖片描述

(六)用戶管理

用戶的增刪改查以及對應(yīng)的權(quán)限
在這里插入圖片描述

<template><div><a-formref="formRef":model="formState":rules="rules"><a-row><a-col :span="12"><a-form-item><a-input-searchv-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-tagv-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-drawertitle="添加用戶":width="600":visible="visible"@close="handleClose"><a-formref="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-uploadv-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="權(quán)限編碼" 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="狀態(tài)" name="statu"><a-select v-model:value="formState.statu" placeholder="請選擇狀態(tài)"><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('請選擇狀態(tài)');}
};
let checkCode= async (_rule: Rule, value: string) => {if (!value) {return Promise.reject('請輸入權(quán)限編碼');}
};
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-ignoreformRef.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.risenshineclean.com/news/49884.html

相關(guān)文章:

  • 什么是網(wǎng)站app惠州seo管理
  • wordpress手機(jī)圖片站蘇州百度代理公司
  • 平面設(shè)計網(wǎng)站大全有哪些成人計算機(jī)培訓(xùn)機(jī)構(gòu)哪個最好
  • 一些好用的網(wǎng)站個人博客網(wǎng)站設(shè)計畢業(yè)論文
  • 做自媒體的網(wǎng)站谷歌推廣開戶多少費用
  • 自己做的網(wǎng)站如何加視頻長春seo排名公司
  • wordpress做的好的網(wǎng)站友情鏈接的形式有哪些
  • 深圳網(wǎng)站建設(shè)公司 交通如何推廣app更高效
  • 珠海網(wǎng)站優(yōu)化百度導(dǎo)航2023年最新版
  • 怎樣查看wordpress用的什么主題網(wǎng)站seo工具
  • 安徽六安郵政編碼seo快速排名軟件app
  • 關(guān)鍵詞的分類和優(yōu)化seo關(guān)鍵詞首頁排名代發(fā)
  • 企業(yè)宣傳片文案大全北京網(wǎng)站優(yōu)化排名
  • 得物app公司域名seo查詢
  • 做網(wǎng)站baidunongmin南寧seo優(yōu)化公司
  • 做網(wǎng)站怎么插音樂循環(huán)西安網(wǎng)站制作價格
  • 互聯(lián)網(wǎng)站備案登記表石家莊網(wǎng)站建設(shè)排名
  • 專業(yè)杭州網(wǎng)站建設(shè)今日頭條新聞最新事件
  • 網(wǎng)站的排版好看點擊進(jìn)入官方網(wǎng)站
  • 濰坊網(wǎng)站定制 優(yōu)幫云谷歌搜索引擎入口2023
  • 浙江昆侖建設(shè)集團(tuán)網(wǎng)站seo技術(shù)培訓(xùn)沈陽
  • 域名年費價格表新野seo公司
  • 代碼大全可復(fù)制武漢seo價格
  • 江蘇藝居建設(shè)有限公司網(wǎng)站如何做市場營銷推廣
  • 商城網(wǎng)站建設(shè)適合于哪類企業(yè)營業(yè)推廣促銷
  • 信息技術(shù)課做網(wǎng)站最近三天的新聞大事小學(xué)生
  • 用dw如何做網(wǎng)站首頁黑鋒網(wǎng)seo
  • 2024年1月流感情況百度網(wǎng)站怎么優(yōu)化排名
  • 長春網(wǎng)站seo報價廣告接單平臺app
  • 網(wǎng)站開發(fā)需要哪些知識南京seo按天計費