网站建设进度以及具体内容,东营seo网站推广费用,wordpress调整logo大小,新闻危机公关本项目分为二部分 1、后台管理系统#xff08;用户管理#xff0c;角色管理#xff0c;视频管理等#xff09; 2、客户端#xff08;登录注册、发布视频#xff09; Vue3做出B站【bilibili】 Vue3TypeScriptant-design-vue【快速入门一篇文章精通系列#xff08;一…本项目分为二部分 1、后台管理系统用户管理角色管理视频管理等 2、客户端登录注册、发布视频 Vue3做出B站【bilibili】 Vue3TypeScriptant-design-vue【快速入门一篇文章精通系列一前端项目案例】一、前言二、项目创建基本页面搭建一创建Vue3 TypeScript项目1、新建Vue3 项目2、用WebStorm打开项目1打开项目以后执行 npm install2安装TypeScript3设置一下WebStorm配置3、配置项目1安装依赖2路由配置3pinia配置4vite.config.ts配置5按需引入ant-design-vue6安装axios4、设置页面路由二实现登录页面1、设置登陆页面2、设置登录请求3、创建mock.ts4、显示验证码5、在store当中的store.ts设置SET_TOKEN6、实现登录请求相关内容7、完善登录1在main.ts当中引入antd的全局样式2完善request.ts当中响应的内容三、后台管理界面开发一创建index页面1、新建index页面2、设置路由3、完善菜单页面内容4、Vue代码抽取1抽取菜单2index5、设置子路由6、编写导航栏路由1创建需要路由跳转的页面2设置页面路由二用户登录信息展示1、完善用户接口2、设置mock.js3、设置个人中心的路由4、设置退出登录三动态菜单开发1、修改一下路由规则2、设置动态菜单的数据1自定义Icon组件2SideMenu.vue菜单页面3创建保存菜单的状态信息的内容4设置mockjs5完善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 vitelatest bilibili-vue3-ts -- --template vue将生成的js文件都修改为ts文件
2、用WebStorm打开项目
1打开项目以后执行 npm install 执行成功
2安装TypeScript
安装TypeScript
npm install -g typescript安装完成后在控制台运行如下命令检查安装是否成功(3.x):
tsc -v3设置一下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
templateh1Home/h1
/templatescript langts setup name/scriptstyle scoped/style修改App.vue
script setup langts
/script
templaterouter-view/router-view
/template
style scoped
/style
3pinia配置 import { createPinia } from pinia
import piniaPluginPersist from pinia-plugin-persist
const store createPinia()
store.use(piniaPluginPersist)
export default storeimport { 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-ignorepersist: { //数据默认存在 sessionStorage 里并且会以 store 的 id 作为 keyenabled: 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 echartslet app createApp(App)
app.config.globalProperties.$echarts echarts
app.use(router)
app.use(store)
app.use(createPinia)
app.mount(#app)4vite.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 dev5按需引入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当中放置一个按钮
templateh1Home/h1a-button typeprimaryPrimary Button/a-button
/template
script langts setup name
/script
style scoped
/style重新运行并访问
6安装axios
安装axios:一个基于promise的HTTP库类ajax
npm install axiosimport axios from axios
// ts-ignore
axios.defaults.headers[Content-Type] application/json;charsetutf-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 复制上述代码但是我们并不会直接使用期内容 templatea-formrefformRefnamecustom-validation:modelformState:rulesrulesv-bindlayoutfinishhandleFinishvalidatehandleValidatefinishFailedhandleFinishFaileda-form-item has-feedback labelPassword namepassa-input v-model:valueformState.pass typepassword autocompleteoff //a-form-itema-form-item has-feedback labelConfirm namecheckPassa-input v-model:valueformState.checkPass typepassword autocompleteoff //a-form-itema-form-item has-feedback labelAge nameagea-input-number v-model:valueformState.age //a-form-itema-form-item :wrapper-col{ span: 14, offset: 4 }a-button typeprimary html-typesubmitSubmit/a-buttona-button stylemargin-left: 10px clickresetFormReset/a-button/a-form-item/a-form
/templatescript langts 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 refFormInstance();const formState reactiveFormState({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 dont match!);} else {return Promise.resolve();}
};const rules: Recordstring, 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当中样式 调整一下页面
templatea-card stylewidth: 800px;margin:10% auto;border-radius: 15px;div stylewidth: 200px;margin: autoa-imagestylemargin: auto:width200:previewfalsesrcsrc/assets/bilibili.png//divdiv classfrom-itema-form:modelformStatenamenormal_loginclasslogin-formfinishonFinishfinishFailedonFinishFaileda-form-itemlabel账号nameusername:rules[{ required: true, message: 请输入账号! }]a-input v-model:valueformState.usernametemplate #prefixUserOutlined classsite-form-item-icon //template/a-input/a-form-itema-form-itemlabel密码namepassword:rules[{ required: true, message: 请输入密码! }]a-input-password v-model:valueformState.passwordtemplate #prefixLockOutlined classsite-form-item-icon //template/a-input-password/a-form-itema-rowa-col :span12a-form-itemlabel验证码namecode:rules[{ required: true, message: 请输入验证码! }]a-input v-model:valueformState.code placeholder请输入验证码 /a-input/a-form-item/a-cola-col :span12a-image:width60styleheight: 30px;margin-left: 10%:previewfalsesrchttps://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png//a-col/a-rowa-form-itema-rowa-col :span6/a-cola-col :span12a-button typeprimary block html-typesubmit classlogin-form-button登录/a-button/a-cola-col :span6/a-col/a-row/a-form-item/a-form/div/a-card
/templatescript langts 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 reactiveFormState({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);
});
/scriptstyle 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%;
}
/style2、设置登录请求 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.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设置发起请求的内容
import axios from axios
// ts-ignore
axios.defaults.headers[Content-Type] application/json;charsetutf-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:width60styleheight: 30px;margin-left: 10%:previewfalse:srccaptchaImg/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 tokenlocalStorage.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 404Result.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;charsetutf-8
// 创建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 ? 系统异常 : 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页面 templatediva-layouta-layout-siderSider/a-layout-sidera-layouta-layout-headerHeader/a-layout-headera-layout-contentContent/a-layout-contenta-layout-footerFooter/a-layout-footer/a-layout/a-layout/div
/template
script nameindex setup langts
/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-themedark] #components-layout-demo-basic .ant-layout-header {background: #fb7299;
}
[data-themedark] #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-themedark] #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-themedark] #components-layout-demo-basic .ant-layout-content {background: #cc889b;
}
#components-layout-demo-basic .code-box-demo .ant-layout .ant-layout {margin-top: 48px;
}
/style2、设置路由 router.push(/index);设置一下状态码使其跳转成功
3、完善菜单页面内容 templatea-layout has-sidera-layout-sider v-model:collapsedcollapsed :triggernull collapsiblediv classlogo /a-menu v-model:selectedKeysselectedKeys themedark styleheight: 100vh modeinlinea-menu-item keyapp disableddiv bilibili后台管理系统/div/a-menu-itema-menu-item key1template #iconMailOutlined //template主页/a-menu-itema-sub-menu keysub1template #iconAppstoreOutlined //templatetemplate #title系统管理/templatea-menu-item key3用户管理/a-menu-itema-menu-item key4角色管理/a-menu-itema-menu-item key5菜单管理/a-menu-item/a-sub-menua-sub-menu keysub2template #iconSettingOutlined //templatetemplate #title系统工具/templatea-menu-item key7数字字典/a-menu-item/a-sub-menu/a-menu/a-layout-sider、a-layout-content :style{ marginTop: 0 }a-menuv-model:selectedKeysselectedKeysTopthemedarkmodehorizontal:style{ lineHeight: 64px,marginLeft:-15px }a-sub-menu keysub2 template #titlediv menu-unfold-outlinedv-ifcollapsedclasstriggerclick() (collapsed !collapsed)/menu-fold-outlined v-else classtrigger click() (collapsed !collapsed) //div/template/a-sub-menua-sub-menu keysub1 stylemargin-left: 85% template #titlea-avatar stylebackground-color: #87d068template #iconUserOutlined //template/a-avatara classant-dropdown-link click.preventadminDownOutlined //a/templatea-menu-item keysetting:1Option 1/a-menu-itema-menu-item keysetting:2Option 2/a-menu-itema-menu-item keysetting:3Option 3/a-menu-itema-menu-item keysetting:4Option 4/a-menu-item/a-sub-menu/a-menua-breadcrumb :style{ margin: 16px 0 }a-breadcrumb-itemHome/a-breadcrumb-itema-breadcrumb-itemList/a-breadcrumb-itema-breadcrumb-itemApp/a-breadcrumb-item/a-breadcrumbdiv :style{ background: #fff, padding: 24px, minHeight: 800px }Content/div/a-layout-content/a-layout
/template
script langts setup
import {DownOutlined,UserOutlined,VideoCameraOutlined,UploadOutlined,MenuUnfoldOutlined,MenuFoldOutlined,
} from ant-design/icons-vue;
import { defineComponent, ref } from vue;
let selectedKeys refstring[]([1])
let selectedKeysTop refstring[]([1])
let collapsed refboolean(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抽取菜单 templatea-menu v-model:selectedKeysselectedKeys themedark styleheight: 100vh modeinlinea-menu-item keyapp disabledtemplate #iconimg src../../assets/bilibilitoum.png styleheight: 15px;width: 35px/templatediv 后台管理系统/div/a-menu-itema-menu-item key1template #iconMailOutlined //template主页/a-menu-itema-sub-menu keysub1template #iconAppstoreOutlined //templatetemplate #title系统管理/templatea-menu-item key3用户管理/a-menu-itema-menu-item key4角色管理/a-menu-itema-menu-item key5菜单管理/a-menu-item/a-sub-menua-sub-menu keysub2template #iconSettingOutlined //templatetemplate #title系统工具/templatea-menu-item key7数字字典/a-menu-item/a-sub-menu/a-menu
/template
script setup langts
import {ref} from vue;
import {AppstoreOutlined,MailOutlined,SettingOutlined,
} from ant-design/icons-vue;
let selectedKeys refstring[]([1])
let selectedKeysTop refstring[]([1])
let collapsed refboolean(false)
/script
style scoped
/style SideMenu/SideMenu
import SideMenu from ./inc/SideMenu.vue2index
将index.vue的内容全部抽取到Home
templatea-layout has-sidera-layout-sider v-model:collapsedcollapsed :triggernull collapsiblediv classlogo /SideMenu/SideMenu/a-layout-sider、a-layout-content :style{ marginTop: 0 }a-menuv-model:selectedKeysselectedKeysTopthemedarkmodehorizontal:style{ lineHeight: 64px,marginLeft:-15px }a-sub-menu keysub2 template #titlediv menu-unfold-outlinedv-ifcollapsedclasstriggerclick() (collapsed !collapsed)/menu-fold-outlined v-else classtrigger click() (collapsed !collapsed) //div/template/a-sub-menua-sub-menu keysub1 stylemargin-left: 85% template #titlea-avatar stylebackground-color: #87d068template #iconUserOutlined //template/a-avatara classant-dropdown-link click.preventadminDownOutlined //a/templatea-menu-item keysetting:1Option 1/a-menu-itema-menu-item keysetting:2Option 2/a-menu-itema-menu-item keysetting:3Option 3/a-menu-itema-menu-item keysetting:4Option 4/a-menu-item/a-sub-menu/a-menua-breadcrumb :style{ margin: 16px 0 }a-breadcrumb-itemHome/a-breadcrumb-itema-breadcrumb-itemList/a-breadcrumb-itema-breadcrumb-itemApp/a-breadcrumb-item/a-breadcrumbdiv :style{ background: #fff, padding: 24px, minHeight: 800px }Content/div/a-layout-content/a-layout
/template
script langts 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 refstring[]([1])
let selectedKeysTop refstring[]([1])
let collapsed refboolean(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/indexa-menu-item key1template #iconMailOutlined //template主页/a-menu-item/router-linka-sub-menu keysub1template #iconAppstoreOutlined //templatetemplate #title系统管理/templaterouter-link to/usersa-menu-item key3template #iconUserOutlined //template用户管理/a-menu-item/router-linkrouter-link to/rolesa-menu-item key4template #iconTeamOutlined //template角色管理/a-menu-item/router-linkrouter-link to/menusa-menu-item key5template #iconMenuOutlined //template菜单管理/a-menu-item/router-link/a-sub-menua-sub-menu keysub2template #iconSettingOutlined //templatetemplate #title系统工具/templatea-menu-item key7template #iconContainerOutlined //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-ignoreResult.data {id:1,username:itbluebox,avatar:https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png}return Result
})
3、设置个人中心的路由 a-menu-item keysetting:1router-link to/userCenter个人中心/router-link
/a-menu-itemimport UserCenter from ../views/UserCenter.vue
{path:/userCenter,name:UserCenter,component:UserCenter
},创建对应的页面
templatea-formrefformRef:modelformState:label-collabelCol:wrapper-colwrapperCol:rulesrulesa-form-item refuser label账号 nameusera-input v-model:valueformState.user placeholderUsernametemplate #prefixUserOutlined stylecolor: rgba(0, 0, 0, 0.25) //template/a-input/a-form-itema-form-item refpassword label密码 namepassworda-input v-model:valueformState.password typepassword placeholderPasswordtemplate #prefixLockOutlined stylecolor: rgba(0, 0, 0, 0.25) //template/a-input/a-form-itema-form-item refcode label验证码 namecodea-rowa-col :span12a-input v-model:valueformState.code typetextarea //a-cola-col :span12a-image clickgetCaptcha:width60styleheight: 30px;margin-left: 10%:previewfalse:srccaptchaImg//a-col/a-row/a-form-itema-form-item :wrapper-col{ span: 14, offset: 4 }a-button typeprimary clickonSubmit修改/a-buttona-button stylemargin-left: 10px clickonReSet重置/a-button/a-form-item/a-form
/templatescript nameUserCenter langts 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: UnwrapRefFormState 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: ValidateErrorEntityFormState) {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
/style4、设置退出登录 export const logout () {return request({url: /logout,method: get,})
};a-menu-item keysetting:2 click.nativelogOut退出/a-menu-itemimport { 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组件 目前先这样。后期会对其进行优化
templatedivSettingOutlined v-ificonName setting-outlined/SettingOutlinedUserOutlined v-ificonName user-outlined/UserOutlinedMenuOutlined v-ificonName menu-outlined/MenuOutlinedContainerOutlined v-ificonName container-outlined/ContainerOutlinedUsergroupAddOutlined v-ificonName user-group-add-outlined/UsergroupAddOutlined/div
/templatescript setup langts
import {ref,reactive} from vue;
import {UserOutlined,SettingOutlined,MenuOutlined,ContainerOutlined,UsergroupAddOutlined
} from ant-design/icons-vue;
const props defineProps{iconName: any;
}();
/scriptstyle scoped/style
2SideMenu.vue菜单页面 templatea-menu v-model:selectedKeysselectedKeys themedark styleheight: 100vh modeinlinea-menu-item keyapp disabledtemplate #iconimg src../../assets/bilibilitoum.png styleheight: 15px;width: 35px/templatediv 后台管理系统/div/a-menu-itemrouter-link to/indexa-menu-item key1template #iconMailOutlined //template主页/a-menu-item/router-linka-sub-menu :keymenu.name v-formenu in menuList.menustemplate #iconIcon :icon-namemenu.icon //templatetemplate #title{{menu.title}}/templaterouter-link :toitem.path v-foritem in menu.childrena-menu-item :keyitem.keytemplate #iconIcon :icon-nameitem.icon //template{{item.title}}/a-menu-item/router-link/a-sub-menu/a-menu
/template
script setup langts
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 refstring[]([1])
let selectedKeysTop refstring[]([1])
let collapsed refboolean(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 {//拿到menuListuserStore().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-ignorelet authoritys [];// ts-ignoreResult.data {nav: nav,// ts-ignoreauthoritys: 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 refstring[]([1])
let selectedKeysTop refstring[]([1])
let collapsed refboolean(false)
let menuList reactive({menus: []
})
onMounted((){menuList.menus userStore().getMenuList
})3、设置动态路由加载一次以后无需二次加载 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、实现动态导航 templatediva-tabs v-model:activeKeyactiveKey typeeditable-card editonEdita-tab-pane v-forpane in panes :keypane.key :tabpane.title :closablepane.closable/a-tab-pane/a-tabs/div
/templatescript setup langts
/*
*
* 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 stylemargin-top: 15px;Tabs/Tabs
/div
import Tabs from /views/inc/Tabs.vue5、设置侧栏和页面进行动态绑定
在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 key1 clickselectMenuIndex0template #iconMailOutlined //template主页/a-menu-item
a-menu-item :keyitem.key clickselectMenu(item)template #iconIcon :icon-nameitem.icon //template{{item.title}}/a-menu-itemonMounted((){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标签页 templatediva-tabs v-model:activeKeyeditableTabsValue typeeditable-card editonEdita-tab-pane v-forpane in editableTabs :keypane.key :tabpane.title :closablepane.closable/a-tab-pane/a-tabs/div
/templatescript setup langts
import { userStore} from /store/store
import {ref, onMounted,computed} from vuelet 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
我们发现重复点击会重复的添加到上面现在设置重复点击不会出现重复的信息 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:activeKeyeditableTabsValue typeeditable-card editonEdit tabClickonTabClicka-tab-pane v-forpane in editableTabs :keypane.key :tabpane.title :closablepane.closable/a-tab-pane
/a-tabsconst 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 styledisplay: none {{editableTabsValue}}/div后台管理系统/divlet 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 templaterouter-view/router-view
/template
script setup langts
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 keysetting:2 click.nativelogOut退出/a-menu-itemconst logOut () {logout().then(response {user.resetState()user.setEditableTabsIndexClearALL()user.setEditableTabsIndex0()localStorage.clear();sessionStorage.clear();router.push(/login);});
}四菜单管理界面开发
1、在Menu当中设置表格样式 templatediva-formrefformRef:modelformState:rulesrulesa-form-item :wrapper-col{ span:24 }a-button typeprimary clickonSubmit新建/a-button/a-form-item/a-forma-table :columnscolumns :data-sourcedata :row-selectionrowSelectiontemplate #bodyCell{ column, record }template v-ifcolumn.key typea-tag colorblue v-ifrecord.type 目录{{ record.type }}/a-taga-tag colorgreen v-ifrecord.type 菜单{{ record.type }}/a-taga-tag colororange v-ifrecord.type 按钮{{ record.type }}/a-tag/templatetemplate v-ifcolumn.key statua-tag colorgreen v-ifrecord.statu 正常{{ record.statu }}/a-taga-tag colorred v-ifrecord.statu 禁用{{ record.statu }}/a-tag/templatetemplate v-ifcolumn.key operationa-button typetext sizesmall stylecolor: blue编辑/a-buttona-button typetext sizesmall stylecolor: red删除/a-button/template/template/a-table/div
/templatescript namemenu langts 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);},
};/scriptstyle scoped/style 上面当面数据超出页面的时候页面跟着滚动这样不太好我们优化一下设置侧边栏和头部不懂设置内容区域滚动
templatea-layout has-sider :style{ position: fixed, zIndex: 1, width: 100% }a-layout-sider v-model:collapsedcollapsed :triggernull collapsible div classlogo /SideMenu/SideMenu/a-layout-sider、a-layout-content :style{ marginTop: 0 }a-menuv-model:selectedKeysselectedKeysTopthemedarkmodehorizontal:style{ lineHeight: 64px,marginLeft:-15px }a-sub-menu keysub2 template #titlediv menu-unfold-outlinedv-ifcollapsedclasstriggerclick() (collapsed !collapsed)/menu-fold-outlined v-else classtrigger click() (collapsed !collapsed) //div/template/a-sub-menua-sub-menu keysub1 stylemargin-left: 85% template #titlea-avatar v-ifuserInfo.avatar null || userInfo.avatar stylebackground-color: #87d068template #iconUserOutlined //template/a-avatara-avatar stylemargin-top: -10px v-ifuserInfo.avatar ! null userInfo.avatar ! srchttps://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png /a classant-dropdown-link click.preventview stylemargin-top: 10%;margin-left: 10%{{userInfo.username}}/viewDownOutlined //a/templatea-menu-item keysetting:1router-link to/userCenter个人中心/router-link/a-menu-itema-menu-item keysetting:2 click.nativelogOut退出/a-menu-item/a-sub-menu/a-menudiv stylemargin-top: 15px;Tabs/Tabs/divdiv idcomponents-affix-demo-target refcontainerRef classscrollable-container :style{ background: rgb(255,255,255), padding: 15px, minHeight: 820px }router-view/router-view/div/a-layout-content/a-layout
/template
script langts 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 refstring[]([1])
let selectedKeysTop refstring[]([1])
let collapsed refboolean(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
内容滚动头部底部不滚
2、在Menu当中设置新增和编辑 templatediva-rowa-col :span2a-button clickiconValue step-backward-outlined template #iconstep-backward-outlined //template/a-button/a-cola-col :span2a-button clickiconValue step-forward-outlined template #iconstep-forward-outlined //template/a-button/a-cola-col :span2a-button clickiconValue fast-backward-outlined template #iconfast-backward-outlined //template/a-button/a-cola-col :span2a-button clickiconValue fast-forward-outlined template #iconfast-forward-outlined //template/a-button/a-cola-col :span2a-button clickiconValue shrink-outlined template #iconshrink-outlined //template/a-button/a-cola-col :span2a-button clickiconValue arrows-alt-outlined template #iconarrows-alt-outlined //template/a-button/a-cola-col :span2a-button clickiconValue down-outlined template #icondown-outlined //template/a-button/a-cola-col :span2a-button clickiconValue up-outlined template #iconup-outlined //template/a-button/a-cola-col :span2a-button clickiconValue left-outlined template #iconleft-outlined //template/a-button/a-cola-col :span2a-button clickiconValue right-outlined template #iconright-outlined //template/a-button/a-cola-col :span2a-button clickiconValue caret-up-outlined template #iconcaret-up-outlined //template/a-button/a-cola-col :span2a-button clickiconValue caret-down-outlined template #iconcaret-down-outlined //template/a-button/a-cola-col :span2a-button clickiconValue caret-left-outlined template #iconcaret-left-outlined //template/a-button/a-cola-col :span2a-button clickiconValue caret-right-outlined template #iconcaret-right-outlined //template/a-button/a-cola-col :span2a-button clickiconValue up-circle-outlined template #iconup-circle-outlined //template/a-button/a-cola-col :span2a-button clickiconValue down-circle-outlined template #icondown-circle-outlined //template/a-button/a-cola-col :span2a-button clickiconValue down-circle-outlined template #icondown-circle-outlined //template/a-button/a-cola-col :span2a-button clickiconValue left-circle-outlined template #iconleft-circle-outlined //template/a-button/a-cola-col :span2a-button clickiconValue right-circle-outlined template #iconright-circle-outlined //template/a-button/a-cola-col :span2a-button clickiconValue right-circle-outlined template #iconright-circle-outlined //template/a-button/a-cola-col :span2a-button clickiconValue double-left-outlined template #icondouble-left-outlined //template/a-button/a-cola-col :span2a-button clickiconValue vertical-left-outlined template #iconvertical-left-outlined //template/a-button/a-cola-col :span2a-button clickiconValue vertical-right-outlined template #iconvertical-right-outlined //template/a-button/a-cola-col :span2a-button clickiconValue vertical-align-top-outlined template #iconvertical-align-top-outlined //template/a-button/a-cola-col :span2a-button clickiconValue vertical-align-middle-outlined template #iconvertical-align-middle-outlined //template/a-button/a-cola-col :span2a-button clickiconValue vertical-align-middle-outlined template #iconvertical-align-middle-outlined //template/a-button/a-cola-col :span2a-button clickiconValue vertical-align-bottom-outlined template #iconvertical-align-bottom-outlined //template/a-button/a-cola-col :span2a-button clickiconValue forward-outlined template #iconforward-outlined //template/a-button/a-cola-col :span2a-button clickiconValue backward-outlined template #iconbackward-outlined //template/a-button/a-cola-col :span2a-button clickiconValue rollback-outlined template #iconrollback-outlined //template/a-button/a-cola-col :span2a-button clickiconValue enter-outlined template #iconenter-outlined //template/a-button/a-cola-col :span2a-button clickiconValue retweet-outlined template #iconretweet-outlined //template/a-button/a-cola-col :span2a-button clickiconValue step-backward-outlined template #iconmenu-fold-outlined //template/a-button/a-cola-col :span2a-button clickiconValue menu-unfold-outlined template #iconmenu-unfold-outlined //template/a-button/a-cola-col :span2a-button clickiconValue align-center-outlined template #iconalign-center-outlined //template/a-button/a-cola-col :span2a-button clickiconValue align-left-outlined template #iconalign-left-outlined //template/a-button/a-cola-col :span2a-button clickiconValue ordered-list-outlined template #iconordered-list-outlined //template/a-button/a-cola-col :span2a-button clickiconValue unordered-list-outlined template #iconunordered-list-outlined //template/a-button/a-cola-col :span2a-button clickiconValue appstore-outlined template #iconappstore-outlined //template/a-button/a-cola-col :span2a-button clickiconValue bars-outlined template #iconbars-outlined //template/a-button/a-cola-col :span2a-button clickiconValue vertical-align-top-outlined template #iconvertical-align-top-outlined //template/a-button/a-cola-col :span2a-button clickiconValue bulb-outlined template #iconbulb-outlined //template/a-button/a-cola-col :span2a-button clickiconValue console-sql-outlined template #iconconsole-sql-outlined //template/a-button/a-cola-col :span2a-button clickiconValue desktop-outlined template #icondesktop-outlined //template/a-button/a-cola-col :span2a-button clickiconValue vertical-align-top-outlined template #iconvertical-align-top-outlined //template/a-button/a-cola-col :span2a-button clickiconValue vertical-align-top-outlined template #iconvertical-align-top-outlined //template/a-button/a-cola-col :span2a-button clickiconValue exception-outline template #iconexception-outlined //template/a-button/a-cola-col :span2a-button clickiconValue file-word-outlined template #iconfile-word-outlined //template/a-button/a-cola-col :span2a-button clickiconValue file-markdown-outlined template #iconfile-markdown-outlined //template/a-button/a-cola-col :span2a-button clickiconValue file-search-outlined template #iconfile-search-outlined //template/a-button/a-cola-col :span2a-button clickiconValue file-protect-outlined template #iconfile-protect-outlined //template/a-button/a-cola-col :span2a-button clickiconValue hdd-outlined template #iconhdd-outlined //template/a-button/a-cola-col :span2a-button clickiconValue insert-row-left-outlined template #iconinsert-row-left-outlined //template/a-button/a-cola-col :span2a-button clickiconValue merge-cells-outlined template #iconmerge-cells-outlined //template/a-button/a-cola-col :span2a-button clickiconValue printer-outlined template #iconprinter-outlined //template/a-button/a-cola-col :span2a-button clickiconValue reconciliation-outlined template #iconreconciliation-outlined //template/a-button/a-cola-col :span2a-button clickiconValue shop-outlined template #iconshop-outlined //template/a-button/a-cola-col :span2a-button clickiconValue split-cells-outlined template #iconsplit-cells-outlined //template/a-button/a-cola-col :span2a-button clickiconValue usergroup-add-outlined template #iconusergroup-add-outlined //template/a-button/a-cola-col :span2a-button clickiconValue woman-outlined template #iconwoman-outlined //template/a-button/a-col/a-row/div
/templatescript setup langts
import {ref,defineExpose} from vue;
let iconValue ref()
const props defineProps{iconName: any;
}();const change () {console.log(iconValue.value)
}defineExpose({change,iconValue
})
/scriptstyle scoped/style templatediva-formrefformRef:modelformState:rulesrulesa-form-item :wrapper-col{ span:24 }a-button typeprimary clickshowDrawer新建/a-button/a-form-item/a-forma-table :columnscolumns :data-sourcedata :row-selectionrowSelectiontemplate #bodyCell{ column, record }template v-ifcolumn.key typea-tag colorblue v-ifrecord.type 目录{{ record.type }}/a-taga-tag colorgreen v-ifrecord.type 菜单{{ record.type }}/a-taga-tag colororange v-ifrecord.type 按钮{{ record.type }}/a-tag/templatetemplate v-ifcolumn.key statua-tag colorgreen v-ifrecord.statu 正常{{ record.statu }}/a-taga-tag colorred v-ifrecord.statu 禁用{{ record.statu }}/a-tag/templatetemplate v-ifcolumn.key operationa-button typetext sizesmall stylecolor: blue编辑/a-buttona-button typetext sizesmall stylecolor: red删除/a-button/template/template/a-tablea-drawertitle添加菜单:width600:visiblevisible:body-style{ paddingBottom: 80px }:footer-style{ textAlign: right }closeonClosea-formrefformRefnamecustom-validation:modelformState:rulesrulesv-bindlayoutfinishhandleFinishvalidatehandleValidatefinishFailedhandleFinishFaileda-form-item has-feedback label上级菜单 nameparentIda-input-group compacta-cascaderv-model:valueformState.parentId:optionsoptionstypeparentIdplaceholder选择上级菜单//a-input-group/a-form-itema-form-item has-feedback label菜单名称 namenamea-input v-model:valueformState.name typename autocompleteoff //a-form-itema-form-item has-feedback label权限编码 namepermsa-input v-model:valueformState.perms typeperms autocompleteoff //a-form-itema-form-item has-feedback label图标 nameicona-input v-model:valueformState.icon clickshowModal typeicon autocompleteoff //a-form-itema-form-item has-feedback label菜单URL namepatha-input v-model:valueformState.path typepath autocompleteoff //a-form-itema-form-item has-feedback label菜单组件 namecomponenta-input v-model:valueformState.component typecomponent autocompleteoff //a-form-itema-form-item has-feedback label类型 nametype a-checkbox-group v-model:valueformState.typea-checkbox value1 nametype目录/a-checkboxa-checkbox value2 nametype菜单/a-checkboxa-checkbox value3 nametype按钮/a-checkbox/a-checkbox-group/a-form-itema-form-item has-feedback label状态 namestatua-checkbox-group v-model:valueformState.statua-checkbox value1 nametype禁用/a-checkboxa-checkbox value2 nametype正常/a-checkbox/a-checkbox-group/a-form-itema-form-item has-feedback label排序 nameorderNuma-input-number v-model:valueformState.orderNum //a-form-itema-form-item :wrapper-col{ span: 20, offset: 4 }a-button typeprimary html-typesubmit提交/a-buttona-button stylemargin-left: 10px clickresetForm重置/a-button/a-form-item/a-form/a-drawera-modal v-model:visiblevisibleIcon title选择图标 okhandleOkIconTable refmyIcons/IconTable/a-modal/div
/templatescript namemenu langts 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 refFormInstance();
const placement refDrawerProps[placement](right);
const visible refboolean(false);
const value18 refstring[]([]);
interface FormState {parentId: string;name: string;perms: string;icon: string;path: string;component: string;type: string;statu: number | undefined;orderNum: number | undefined;
}
const formState reactiveFormState({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-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 dont match!);} else {return Promise.resolve();}
};
const rules: Recordstring, 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 refboolean(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);},
};/scriptstyle scoped/style五角色管理
1、设置角色信息的增删改查-权限分配 templatediva-formrefformRef:modelformState:rulesrulesa-form-item :wrapper-col{ span:24 }a-button typeprimary clickshowDrawer新建/a-button/a-form-item/a-forma-table :columnscolumns :data-sourcedata :row-selectionrowSelectiontemplate #bodyCell{ column, record }template v-ifcolumn.key typea-tag colorblue v-ifrecord.type 目录{{ record.type }}/a-taga-tag colorgreen v-ifrecord.type 菜单{{ record.type }}/a-taga-tag colororange v-ifrecord.type 按钮{{ record.type }}/a-tag/templatetemplate v-ifcolumn.key statua-tag colorgreen v-ifrecord.statu 正常{{ record.statu }}/a-taga-tag colorred v-ifrecord.statu 禁用{{ record.statu }}/a-tag/templatetemplate v-ifcolumn.key operationa-button typetext sizesmall stylecolor: blue编辑/a-buttona-button typetext sizesmall stylecolor: red删除/a-button/template/template/a-tablea-drawertitle添加菜单:width600:visiblevisible:body-style{ paddingBottom: 80px }:footer-style{ textAlign: right }closeonClosea-formrefformRefnamecustom-validation:modelformState:rulesrulesv-bindlayoutfinishhandleFinishvalidatehandleValidatefinishFailedhandleFinishFaileda-form-item has-feedback label上级菜单 nameparentIda-input-group compacta-cascaderv-model:valueformState.parentId:optionsoptionstypeparentIdplaceholder选择上级菜单//a-input-group/a-form-itema-form-item has-feedback label菜单名称 namenamea-input v-model:valueformState.name typename autocompleteoff //a-form-itema-form-item has-feedback label权限编码 namepermsa-input v-model:valueformState.perms typeperms autocompleteoff //a-form-itema-form-item has-feedback label图标 nameicona-input v-model:valueformState.icon clickshowModal typeicon autocompleteoff //a-form-itema-form-item has-feedback label菜单URL namepatha-input v-model:valueformState.path typepath autocompleteoff //a-form-itema-form-item has-feedback label菜单组件 namecomponenta-input v-model:valueformState.component typecomponent autocompleteoff //a-form-itema-form-item has-feedback label类型 nametype a-checkbox-group v-model:valueformState.typea-checkbox value1 nametype目录/a-checkboxa-checkbox value2 nametype菜单/a-checkboxa-checkbox value3 nametype按钮/a-checkbox/a-checkbox-group/a-form-itema-form-item has-feedback label状态 namestatua-checkbox-group v-model:valueformState.statua-checkbox value1 nametype禁用/a-checkboxa-checkbox value2 nametype正常/a-checkbox/a-checkbox-group/a-form-itema-form-item has-feedback label排序 nameorderNuma-input-number v-model:valueformState.orderNum //a-form-itema-form-item :wrapper-col{ span: 20, offset: 4 }a-button typeprimary html-typesubmit提交/a-buttona-button stylemargin-left: 10px clickresetForm重置/a-button/a-form-item/a-form/a-drawera-modal v-model:visiblevisibleIcon title选择图标 okhandleOk okText确认 cancelText取消IconTable refmyIcons/IconTable/a-modal/div
/templatescript namemenu langts 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 refFormInstance();
const placement refDrawerProps[placement](right);
const visible refboolean(false);
const value18 refstring[]([]);
interface FormState {parentId: string;name: string;perms: string;icon: string;path: string;component: string;type: string;statu: number | undefined;orderNum: number | undefined;
}
const formState reactiveFormState({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-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 dont match!);} else {return Promise.resolve();}
};
const rules: Recordstring, 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 refboolean(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);},
};
/scriptstyle scoped/style六用户管理
用户的增删改查以及对应的权限
templatediva-formrefformRef:modelformState:rulesrulesa-rowa-col :span12a-form-itema-input-searchv-model:valuesearchValueplaceholder请输入用户名enter-button搜索searchonSearch//a-form-item/a-cola-col :span12a-form-item :wrapper-col{ span:6 }a-button typeprimary clickshowDrawer新建/a-button/a-form-item/a-col/a-row/a-forma-table :columnscolumns :data-sourcedatatemplate #avatar{ text }a-avatar :srctext //templatetemplate #name{ text }a{{ text }}/a/templatetemplate #customTitlespansmile-outlined /Name/span/templatetemplate #tags{ text: tags }spana-tagv-fortag in tags:keytag{{ tag.toUpperCase() }}/a-tag/span/templatetemplate #action{ record }spana分配角色/aa-divider typevertical /a重置密码/aa-divider typevertical /a clickedit(record) classant-dropdown-link编辑/aa-divider typevertical /a删除/aa-divider typevertical //span/template/a-tablea-drawertitle添加用户:width600:visiblevisibleclosehandleClosea-formrefformRefnamecustom-validation:modelformState:rulesrulesFromv-bindlayoutfinishhandleFinishvalidatehandleValidatefinishFailedhandleFinishFaileda-form-item has-feedback label菜单名称 namenamea-input v-model:valueformState.name typename autocompleteoff //a-form-itema-form-item has-feedback label头像 nameavatara-uploadv-model:file-listformState.avatarnameavatarlist-typepicture-cardclassavatar-uploader:show-upload-listfalseactionhttps://www.mocky.io/v2/5cc8019d300000980a055e76:before-uploadbeforeUploadchangehandleChangeimg v-ifimageUrl :srcimageUrl altavatar /div v-elseloading-outlined v-ifloading/loading-outlinedplus-outlined v-else/plus-outlineddiv classant-upload-textUpload/div/div/a-upload/a-form-itema-form-item has-feedback label权限编码 namecodea-input v-model:valueformState.code typecode autocompleteoff //a-form-itema-form-item has-feedback label电话 namephonea-input v-model:valueformState.phone typephone autocompleteoff //a-form-itema-form-item has-feedback label性别 namesexa-select v-model:valueformState.sex placeholder请选择性别a-select-option value1男/a-select-optiona-select-option value2女/a-select-option/a-select/a-form-itema-form-item has-feedback label状态 namestatua-select v-model:valueformState.statu placeholder请选择状态a-select-option value1正常/a-select-optiona-select-option value2停止/a-select-optiona-select-option value3注销/a-select-option/a-select/a-form-itema-form-item :wrapper-col{ span: 20, offset: 4 }a-button typeprimary html-typesubmit提交/a-buttona-button stylemargin-left: 10px clickresetForm重置/a-button/a-form-item/a-form/a-drawer/div
/templatescript nameuser langts 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 refFormInstance();
const visible refboolean(false);
const fileList ref([]);
const loading refboolean(false);
const imageUrl refstring();interface FormState {name: string;avatar: string[];code: string;email: string;phone: string;sex: string;statu: string;
}
const formState reactiveFormState({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: Recordstring, 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: 2800967183qq.com,phone: 18086256816,tags: [正常],},{key: 2,avatar: https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png,name: Jim Green,code: doctor,email: 2019967083qq.com,phone: 15024511186,tags: [注销],},{key: 3,avatar: https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png,name: Joe Black,code: admin,email: 2079901021qq.com,phone: 15748163055,tags: [正常],},
];
const onSearch () {}let checkRemark async (_rule: Rule, value: string) {if (!value) {return Promise.reject(请输入描述);}
};const rules: Recordstring, 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;
}/scriptstyle scoped/style