官网:https://vitejs.cn/
Vue3 vite官网:https://cn.vitejs.dev/
Vite下一代的前端工具链,为开发者提供急速响应
# 安装
$ cnpm i vite -g
$ vite -v
vite/4.0.3 darwin-x64 node-v16.13.1
windows注意处理 ****/vite.psl文件
Vite(法语意为 “快速的”,发音 /vit/
,发音同 “veet”)是一种新型前端构建工具,能够显著提升前端开发体验。它主要由两部分组成:
# 如果选择npm创建项目再执行
$ npm create vite@latest
cnpm i yarn -g
$ yarn create vite
cnpm i pnpm -g
$ pnpm create vite
对比 | yarn | npm | pnpm |
---|---|---|---|
初始化 | yarn init | npm init | 利用硬链接和符号连接来避免复制所有本地缓存资源文件 |
安装依赖 | yarn install 或者yarn | npm install 或 npm i | pnpm install |
新增依赖 | yarn add vant | npm i vant -S | pnpm i vant |
删除依赖 | yarn remove vant | npm uninstall vant -S | |
删除devDependencies 依赖 | npm uninstall vant -D | ||
更新依赖 | yarn upgrade | npm update | pnpm update |
全局安装或者删除 | yarn global remove vue-cli | npm uninstall vue-cli -g | |
同时下载多个 | yarn add axios vue-axios | npm i axis vue-axios -S |
$ npm init vue@latest
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cSvR3gWm-1672888918411)(assets/image-20221226093812541.png)]
|- vue3-admin-app # 项目名称
|- node_modules # 项目依赖包
|- public
favicon.ico # 网页图标
|- src # 写代码的主场
|- assets # 资源文件
base.css # 基础样式
logo.svg # logo
main.css # 项目样式
|- components # 自定义组件
|- icons # 图标组件
HelloWorld.vue # 自定义组件
TheWelcome.vue # 自定义组件
welcomeItem.vue # 自定义组件
|-router # 路由文件夹
index.ts # 路由配置
|- stores # 状态管理器文件夹
counter.ts # 状态管理器模块
|- views # 项目页面组件
AboutView.vue # 页面
HomeView.vue # 页面
App.vue # 项目跟组件
main.ts # 项目入口文件
.eslintrc.cjs # 代码格式化说明
.gitignore # git上传忽略文件
.prettierrc.json # 格式化配置
env.d.ts # 环境配置声明文件 - ts 中
index.html # 页面模版
package.json # 项目依赖说明以及运行命令
README.md # 说明文档
tsconfig.config.json # ts的配置文件说明 - 本项目部分
tsconfig.json # ts的配置文件说明 - 公共部分
vite.config.ts # vite的配置文件
一个 Vue 单文件组件 (SFC),通常使用 *.vue
作为文件扩展名,它是一种使用了类似 HTML 语法的自定义文件格式,用于定义 Vue 组件。一个 Vue 单文件组件在语法上是兼容 HTML 的。
每一个 *.vue
文件都由三种顶层语言块构成:、
和
{{ msg }}
打开src/mian.ts
发现在引入 App.vue
中画红线,说明项目中没有.vue
的声明文件,需要自行补充一下
// env.d.ts
///
// 简单版本
// declare module '*.vue'
// 推荐
declare module '*.vue' {
// 引入vue模块中ts的方法
import type { DefineComponent } from 'vue'
// 定义vue组件以及类型注解
const component: DefineComponent<{}, {}, any>
export default component
}
再次打开 src/main.ts
发现红线丢失
复制src文件夹,保留App.vue
以及main.ts
基本内容
// src/main.ts
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.mount('#app')
如何使用选项式API
{{ msg }} - {{ count }}
如果使用组合式API
<!-- src/App.vue -->
<template>
<div class="example">
{{ msg }} - {{ count }}
<button @click="count++">加1</button>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
export default defineComponent({
setup () {
const msg = ref('hello sfc!!!!')
const count = ref(10)
return {
msg, count
}
}
})
</script>
<!-- 如果样式只针对当前组件是有效的,只要给style 添加 scoped 属性即可 -->
<style>
.example {
color: #f66
}
</style>
{{ msg }} - {{ count }}
预习:https://cn.vuejs.org/guide/typescript/composition-api.html
?样式处理 css、sass、less 、stylus
sass、less、stylus 为 css 预处理器
.contianer {} .container .box {} .container .box .header {} .container .box .content {} .container .box .footer {}
// scss .contianer { width: 100% .box { width: 100% .header { width: 100% } .content { width: 100% } .footer { width: 100% } } }
// .less .contianer { width: 100% .box { width: 100% .header { width: 100% } .content { width: 100% } .footer { width: 100% } } }
// stylus .container width 100% .box width 100% .header width 100% .content width 100% .footer width 100%
?? 本项目用什么,建议大家先选择UI组件库(element-plus / ant-design-vue)
样式 css - sass - 可以重新创建项目
# 需要安装
$ cnpm i sass -D
使用重置样式表
# 需要安装
$ cnpm i normalize.css -D
// src/main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
// 重置样式表
import 'normalize.css/normalize.css'
// import './assets/main.css' // 不使用默认样式
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.mount('#app')
路由 vue-router
# 创建项目时已经选择了,此处不需要安装
$ cnpm i vue-router -S
状态管理 pinia / vuex
# 创建项目时已经选择了,此处不需要安装
$ cnpm i pinia -S
数据请求方案 axios / fetch
# 需要安装
$ cnpm i axios -S
组件库 element plus
# 需要安装(如果安装失败, 更新cnpm)
# npm install -g cnpm --registry=https://registry.npmmirror.com
$ cnpm i element-plus -S
Element-plus 默认使用英文,如果需要使用中文包,如下操作
// src/main.ts import { createApp } from 'vue' import { createPinia } from 'pinia' // 引入UI库 ++++++ import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' import zhCn from 'element-plus/dist/locale/zh-cn.mjs' import App from './App.vue' import router from './router' // 重置样式表 import 'normalize.css/normalize.css' // import './assets/main.css' // 不使用默认样式 const app = createApp(App) // 配置UI库 ++++++ app.use(ElementPlus, { locale: zhCn }) app.use(createPinia()) app.use(router) app.mount('#app')
// env.d.ts ///
// declare module '*.vue' declare module '*.vue' { import type { DefineComponent, defineComponent } from 'vue' const component: DefineComponent<{}, {}, any> export default component } declare module 'element-plus/dist/locale/zh-cn.mjs'
本地存储 localStorage / store2 /
# 安装
$ cnpm i store2 -S
数据可视化 ECharts
# 安装
$ cnpm i echarts -S
https://element-plus.gitee.io/zh-CN/component/container.html#%E5%B8%B8%E8%A7%81%E9%A1%B5%E9%9D%A2%E5%B8%83%E5%B1%80
Aside
Header
Main
Footer
https://panjiachen.gitee.io/vue-element-admin/#/login?redirect=%2Fdashboard
// src/settings.ts
// 参考项目链接
// https://panjiachen.gitee.io/vue-element-admin/#/login?redirect=%2Fdashboard
export default {
title: '嗨购管理系统',
// 主题等的设置
showSetting: true,
// 快捷导航的提示
tagsView: true,
// 头部是否固定
fixedHeader: true,
// 左侧菜单logo显示
sideBarLogo: true
}
// src/utils/get-page-title.ts
import defaultSettings from '../settings'
const title = defaultSettings.title || '嗨购后台管理系统'
// 系统首页 - 嗨购后台管理系统
// 添加轮播图 - 嗨购后台管理系统
export default function getPageTitle (pageTitle: string) {
if (pageTitle) {
// 当前页面标识 + 应用的标题
return `${ pageTitle } - ${ title }`
} else {
return `${ title }`
}
}
页面标题的修改需要配合路由使用
Aside
Header
Main
Footer
系统首页
轮播图列表
轮播图列表
首页轮播图列表
分类轮播图列表
添加轮播图
添加轮播图
产品列表
筛选列表
管理员列表
用户列表
登录页面
https://router.vuejs.org/zh/introduction.html
左侧菜单渲染结构(这些页面应该都是主界面结构)
|- 系统首页 # 只有一级路由
|- 轮播图管理 # 三级路由
|- 轮播图列表
|- 首页轮播图
|- 分类页轮播图
|- 添加轮播图
|- 产品管理 # 二级路由
|- 产品列表
|- 筛选列表
|- 账户管理 # 二级路由
|- 管理员列表
|- 用户列表
真实项目开发时,可能 轮播图 产品 账户是不同的人开发以及维护的,所以建议将这几个路由的配置分离开来
然后再统一整合管理即可
// src/router/modules/account.ts
// @符号代表 src 目录结构
import Layout from '@/layout/index.vue'
import User from '@/views/account/user.vue'
import Admin from '@/views/account/Admin.vue'
export default {
path: '/account', // 地址栏显示的路径
redirect: '/account/user', // 当用户输入 /account 路由时,自动跳转到 /account/user 路由
component: Layout, // 账户管理使用主界面布局结构
name: 'account', // 给路由起名字 - 命名路由 - 具有唯一性 - 后期会使用
meta: {
title: '账户管理', // 左侧菜单栏显示的名字
icon: 'User' // 左侧菜单栏显示的图标
},
children: [ // 代表账户管理下有二级路由
{
path: '/account/user',
component: User,
meta: {
title: '用户列表'
}
},
{
path: '/account/admin',
component: Admin,
meta: {
title: '管理员列表'
}
}
]
}
// src/router/modules/banner.ts
import Layout from '@/layout/index.vue'
import BannerAdd from '@/views/banner/add.vue'
import BannerList from '@/views/banner/list.vue'
import BannerHomeList from '@/views/banner/components/home.vue'
import BannerKindList from '@/views/banner/components/kind.vue'
export default {
path: '/banner',
name: 'banner',
redirect: '/banner/list',
component: Layout,
meta: {
title: '轮播图管理',
icon: 'PictureFilled'
},
children: [
{
path: '/banner/list',
component: BannerList,
redirect: '/banner/list/home',
meta: {
title: '轮播图列表'
},
children: [
{
path: '/banner/list/home',
component: BannerHomeList,
meta: {
title: '首页轮播图'
},
},
{
path: '/banner/list/kind',
component: BannerKindList,
meta: {
title: '分类页轮播图'
},
}
]
},
{
path: '/banner/add',
component: BannerAdd,
meta: {
title: '添加轮播图'
}
}
]
}
// src/router/modules/pro.ts
import Layout from '@/layout/index.vue'
import ProList from '@/views/pro/list.vue'
import ProSearch from '@/views/pro/search.vue'
export default {
path: '/pro',
redirect: '/pro/list',
component: Layout,
meta: {
title: '产品管理',
icon: 'Fries'
},
children: [
{
path: '/pro/list',
component: ProList,
meta: {
title: '产品列表'
}
},
{
path: '/pro/search',
component: ProSearch,
meta: {
title: '筛选列表'
}
}
]
}
// src/router/index.ts
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router'
// 1.引入自定义分模块的路由
import bannerRoutes from './modules/banner'
import proRoutes from './modules/pro'
import accountRoutes from './modules/account'
// 2.引入主界面布局,生成 首页和登录的路由 -- 常规路由 -- 任何人都可以访问路由
import Layout from '@/layout/index.vue'
import Home from '@/views/home/index.vue'
import Login from '@/views/login/index.vue'
// 3.定义常规路由-任何人可以访问的路由
export const constantRoutes: RouteRecordRaw[] = [
{ // 地址栏输入 /login, 整个页面展示登录页面
path: '/login',
component: Login
},
{
path: '/',
redirect: '/home',
component: Layout,
children: [
{
path: '/home',
component: Home,
meta: {
title: '系统首页',
icon: 'HomeFilled'
}
}
]
}
]
// 4.整合模块路由
export const asyncRoutes = [
bannerRoutes,
proRoutes,
accountRoutes
]
// 5.合并路由
const router = createRouter({
// createWebHistory http://localhost:5173/home
// createWebHashHistory http://localhost:5173/#/home
history: createWebHistory(import.meta.env.BASE_URL),
// routes: constantRoutes.concat(asyncRoutes)
routes: [...constantRoutes, ...asyncRoutes]
})
export default router
// src/main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
// 引入UI库
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
import App from './App.vue'
import router from './router'
// 重置样式表
import 'normalize.css/normalize.css'
// import './assets/main.css' // 不使用默认样式
const app = createApp(App)
// 配置UI库
app.use(ElementPlus, { locale: zhCn })
app.use(createPinia())
app.use(router)
app.mount('#app')
Aside
Header
Footer
轮播图列表
页面展示靠路由,但是左侧菜单并不见得所有的路由都得出现
将不需要出现在左侧菜单栏的数据 做一个标识hidden: true
扩展了类型注解
// src/router/index.ts
// type RouteRecordRaw 定义路由规则的变量的类型注解
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router'
// 1.引入自定义分模块的路由
import bannerRoutes from './modules/banner'
import proRoutes from './modules/pro'
import accountRoutes from './modules/account'
// 2.引入主界面布局,生成 首页和登录的路由 -- 常规路由 -- 任何人都可以访问路由
import Layout from '@/layout/index.vue'
import Home from '@/views/home/index.vue'
import Login from '@/views/login/index.vue'
// 扩展 类型注解
type MyRouteRecordRaw = RouteRecordRaw & {
hidden?: boolean
}
// 3.定义常规路由-任何人可以访问的路由
export const constantRoutes: MyRouteRecordRaw[] = [
{ // 地址栏输入 /login, 整个页面展示登录页面
path: '/login',
component: Login,
hidden: true
},
{
path: '/',
redirect: '/home',
component: Layout,
children: [
{
path: '/home',
component: Home,
meta: {
title: '系统首页',
icon: 'HomeFilled'
}
}
]
}
]
// 4.整合模块路由
export const asyncRoutes: MyRouteRecordRaw[] = [
bannerRoutes,
proRoutes,
accountRoutes
]
// 5.合并路由
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: constantRoutes.concat(asyncRoutes)
})
export default router
// src/router/modules/banner.ts
import Layout from '@/layout/index.vue'
import BannerAdd from '@/views/banner/add.vue'
import BannerList from '@/views/banner/list.vue'
import BannerHomeList from '@/views/banner/components/home.vue'
import BannerKindList from '@/views/banner/components/kind.vue'
export default {
path: '/banner',
name: 'banner',
redirect: '/banner/list',
component: Layout,
meta: {
title: '轮播图管理',
icon: 'PictureFilled'
},
children: [
{
path: '/banner/list',
component: BannerList,
redirect: '/banner/list/home',
meta: {
title: '轮播图列表'
},
children: [
{
path: '/banner/list/home',
component: BannerHomeList,
meta: {
title: '首页轮播图'
},
},
{
path: '/banner/list/kind',
component: BannerKindList,
meta: {
title: '分类页轮播图'
},
}
]
},
{
path: '/banner/add',
component: BannerAdd,
meta: {
title: '添加轮播图'
},
hidden: true
}
]
}
左侧菜单
Header
Footer
左侧菜单对应的每个数据的菜单项
左侧菜单选项
渲染子组件
分析数据结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-itlMnTal-1672888918413)(assets/image-20221227160845689.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p8CRy9Iq-1672888918414)(assets/image-20221227160905511.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LQBUyTgw-1672888918414)(assets/image-20221227161017853.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ryAgzwzg-1672888918415)(assets/image-20221227161042917.png)]
说明:需要使用图标
$ cnpm install @element-plus/icons-vue
// src/main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
// 引入UI库
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
// 引入图标
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import App from './App.vue'
import router from './router'
// 重置样式表
import 'normalize.css/normalize.css'
// import './assets/main.css' // 不使用默认样式
const app = createApp(App)
// 配置图标
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
// 配置UI库
app.use(ElementPlus, { locale: zhCn })
app.use(createPinia())
app.use(router)
app.mount('#app')
{{ item.children[0].meta.title }}
{{ item.meta.title }}
{{ item.meta.title }}
{{ item.children[0].meta.title }}
{{ item.meta.title }}
{{ item.meta.title }}
{{ title }}
logo组件接收变量,控制文件的显示
{{ title }}
面包屑
// src/router/index.ts
// type RouteRecordRaw 定义路由规则的变量的类型注解
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router'
// 1.引入自定义分模块的路由
import bannerRoutes from './modules/banner'
import proRoutes from './modules/pro'
import accountRoutes from './modules/account'
// 2.引入主界面布局,生成 首页和登录的路由 -- 常规路由 -- 任何人都可以访问路由
import Layout from '@/layout/index.vue'
import Home from '@/views/home/index.vue'
import Login from '@/views/login/index.vue'
// 扩展 类型注解(interface) 暴露出去,供需要的使用 ++++++++++++++++++++++++++++++++++++++++
// export declare 暴露类型
export declare type MyRouteRecordRaw = RouteRecordRaw & {
hidden?: boolean
}
// 3.定义常规路由-任何人可以访问的路由
export const constantRoutes: MyRouteRecordRaw[] = [
{ // 地址栏输入 /login, 整个页面展示登录页面
path: '/login',
component: Login,
hidden: true
},
// {
// path: '/setting',
// component: Setting,
// meta: {
// title: '设置',
// icon: 'Setting'
// }
// },
{
path: '/',
redirect: '/home',
component: Layout,
children: [
{
path: '/home',
component: Home,
meta: {
title: '系统首页',
icon: 'HomeFilled'
}
}
]
}
]
// 4.整合模块路由
export const asyncRoutes: MyRouteRecordRaw[] = [
bannerRoutes,
proRoutes,
accountRoutes
]
// 5.合并路由
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: constantRoutes.concat(asyncRoutes)
})
export default router
获取需要的对象数据
面包屑
根据地址栏获取路由
系统首页
{{ breadcrumbNameMap[item] }}
垂直居中显示面包屑
Footer
axios https://www.axios-http.cn/docs/intro
// restful api
// get
// post
// put 更新
// patch 更新
// delete 删除
// get
fetch('url?a=1&b=2').then(res => res.json()).then(res => { console.log(res) })
axios.get('url?a=1&b=2').then(res => { console.log(res) })
axios.get('url', { params: { a: 1, b: 2} }).then(res => { console.log(res) })
axios({
url: 'url?a=1&b=2',
method: 'get'
}).then(res => { console.log(res) })
axios({
url: 'url',
method: 'get',
params: { a: 1, b: 2}
}).then(res => { console.log(res) })
// post
axios.post('url', { a: 1, b: 2 }).then(res => { console.log(res) })
axios({
url: 'url',
method: 'post',
data: { a: 1, b: 2}
}).then(res => { console.log(res) })
// axios 拦截器
// 请求拦截器
// 响应拦截器
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
// 显示loading动画效果
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
axios.interceptors.response.use(function (response) {
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据做点什么
// loading动画消失
return response;
}, function (error) {
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
return Promise.reject(error);
});
一般项目中都需要重新定义axios,需要对axios进行配置
// 创建实例时配置默认值
const instance = axios.create({
baseURL: 'https://api.example.com'
});
// 创建实例后修改默认值
instance.defaults.headers.common['Authorization'] = AUTH_TOKEN;
// https://api.example.com/api/pro/list
instance.get('/api/pro/list')
接口文档:http://121.89.205.189:3000/admindoc/
// src/utils/request.ts
// 1.引入axios
import axios from 'axios'
// 因为router 是单独暴露的,此处可以直接引入使用
import router from '@/router'
// 2.自定义axios
// 接口文档: http://121.89.205.189:3000/admindoc/
const ins = axios.create({
baseURL: 'http://121.89.205.189:3000/admin'
})
// 3.配置拦截器
// 请求拦截器
ins.interceptors.request.use((config) => {
// 请求之前干什么
// 后台管理系统所有的页面都需要从服务器获取数据
// 思考:要数据就必须是登录才可以
// 请求数据时需要将 是否 登录的标识传递给 服务器,服务器判断登录标识 是否可用,
// 如果可以使用,返回你要的数据,如果不可以使用,告诉用户,登录失效,用户重新登录
// 登录标识:token 登录以后后端返回给前端,前端一般会将其存入到本地存储
// 如何将 token 传递给服务器,一般从本地存储提取,然后在 头信息(请求头) 中传递
config.headers!.token = localStorage.getItem('token') || ''
return config
}, (error) => {
return Promise.reject(error)
})
// 响应拦截器
ins.interceptors.response.use((response) => {
// 判断登录标识 是否有效,如果无效跳转至登录页面,如果有效,不做操作
if (response.data.code === '10119') {
// 没有传递token 或者 token过期
// 跳转到登录页面 重新登录
router.push('/login')
}
return response
}, (error) => {
return Promise.reject(error)
})
// 暴露自定义的 axios
export default ins
所有的数据请求都要先登录,登录状态在非常多的页面都需要使用
https://pinia.vuejs.org/zh/
// src/main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
// 引入UI库
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
// 引入图标
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import App from './App.vue'
import router from './router'
// 重置样式表
import 'normalize.css/normalize.css'
// import './assets/main.css' // 不使用默认样式
const app = createApp(App)
// 配置图标
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
// 配置UI库
app.use(ElementPlus, { locale: zhCn })
app.use(createPinia())
app.use(router)
app.mount('#app')
// src/store/counter.ts
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
// 选项式API
// export const useCounterStore = defineStore('counter', {
// state () { // 初始化数据 -- data
// return {
// count: 99
// }
// },
// getters: { // 计算属性
// doubleCount: state => { // state 即为 当前状态管理器中的 所有的状态
// return state.count * 2
// }
// },
// actions: { // 事件 ---- methods
// increment () {
// this.count += 10
// },
// decrement () {
// this.count -= 10
// }
// }
// })
// 组合式API
export const useCounterStore = defineStore('counter', () => {
// 初始化
const count = ref(0)
// 计算属性
const doubleCount = computed(() => count.value * 2)
// 方法
function increment() {
count.value += 10
}
function decrement() {
count.value -= 10
}
// 返回˙状态和方法
return { count, doubleCount, increment, decrement }
})
系统首页
{{ count }} - {{ counter.count }} - {{ counter.doubleCount }} - {{ doubleCount }}
用户列表
{{ counter.count }} - {{ counter.doubleCount }}
系统登录
登录
默认账户名:admin 默认密码:123456
// src/api/admin.ts
import request from '@/utils/request'
// interface IAdminLoginParams {
// adminname: string
// password: string
// }
type TAdminLoginParams = {
adminname: string
password: string
}
export function adminLogin (params: TAdminLoginParams) {
return request.post('/admin/login', params)
}
系统登录
登录
默认账户名:admin 默认密码:123456
欢迎您,{{ adminname }}
个人中心
退出
Footer
// src/store/admins.ts
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
// 组合式API
export const useAdminStore = defineStore('admin', () => {
// 初始化
const adminname = ref(localStorage.getItem('adminname') || '')
const token = ref(localStorage.getItem('token') || '')
const changeAdminname = (val: string) => {
adminname.value = val
}
const changeToken = (val: string) => {
token.value = val
}
// 返回˙状态和方法
return { adminname, token, changeAdminname, changeToken }
})
系统登录
登录
默认账户名:admin 默认密码:123456
欢迎您1,{{ adminname }}
个人中心
退出
Footer
封装获取管理员信息的接口
// src/api/admin.ts
import request from '@/utils/request'
// interface IAdminLoginParams {
// adminname: string
// password: string
// }
type TAdminLoginParams = {
adminname: string
password: string
}
export function adminLogin (params: TAdminLoginParams) {
return request.post('/admin/login', params)
}
export function getAdminList () {
return request.get('/admin/list')
}
{{ scope.$index + 1 }}
超级管理员
管理员
编辑
删除
{{ scope.$index + 1 }}
超级管理员
管理员
编辑
删除
filter处理数据
const adminList = computed(() => {
const newArr = originalList.value.filter((item, index) => {
return index >= (currentPage.value - 1) * pageSize.value && index < (currentPage.value - 1) * pageSize.value + pageSize.value
})
return newArr
})
// src/api/admin.ts
import request from '@/utils/request'
// interface IAdminLoginParams {
// adminname: string
// password: string
// }
type TAdminLoginParams = {
adminname: string
password: string
}
export function adminLogin (params: TAdminLoginParams) {
return request.post('/admin/login', params)
}
export function getAdminList () {
return request.get('/admin/list')
}
export function deleteAdmin (params: { adminid: string }) {
return request.post('/admin/delete', params)
}
{{ (currentPage - 1) * pageSize + scope.$index + 1 }}
超级管理员
管理员
编辑
删除
// src/api/admin.ts
import request from '@/utils/request'
// interface IAdminLoginParams {
// adminname: string
// password: string
// }
type TAdminLoginParams = {
adminname: string
password: string
}
export function adminLogin (params: TAdminLoginParams) {
return request.post('/admin/login', params)
}
export function getAdminList () {
return request.get('/admin/list')
}
export function deleteAdmin (params: { adminid: string }) {
return request.post('/admin/delete', params)
}
interface IAdminAddParams {
adminname: string
password: string
role: number
checkedKeys: any // 因为开放,索引设置为any
}
export function addAdmin (params: IAdminAddParams) {
return request.post('/admin/add', params)
}
// src/utils/get-routes.ts
import type { MyRouteRecordRaw } from "@/router";
interface Tree {
id: string
label: string
children?: Tree[]
}
export function getRoutes (menuList: MyRouteRecordRaw[]) {
const arr: Tree[] = []
menuList.forEach((item) => {
let obj: Tree = {
id: '',
label: ''
}
if (item.meta) {
if (item.children) { // 动态那些数据
obj = {
id: item.path,
label: String(item.meta.title),
children: getRoutes(item.children)
}
} else {
obj = {
id: item.path,
label: String(item.meta.title)
}
}
} else {
if (item.children) { // 系统首页
obj = {
id: item.children[0].path,
label: String(item.children[0].meta!.title)
}
}
}
// arr.push(obj)
obj.id !== '' && arr.push(obj)
});
return arr
}
// getRoutes(menuList)
添加管理员
{{ (currentPage - 1) * pageSize + scope.$index + 1 }}
超级管理员
管理员
编辑
删除
添加管理员
添加
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Py3yNhxo-1672888918417)(assets/image-20221230153508095.png)]
// src/api/admin.ts
import request from '@/utils/request'
// interface IAdminLoginParams {
// adminname: string
// password: string
// }
type TAdminLoginParams = {
adminname: string
password: string
}
export function adminLogin (params: TAdminLoginParams) {
return request.post('/admin/login', params)
}
export function getAdminList () {
return request.get('/admin/list')
}
export function deleteAdmin (params: { adminid: string }) {
return request.post('/admin/delete', params)
}
interface IAdminAddParams {
adminname: string
password: string
role: number
checkedKeys: any // 因为开放,索引设置为any
}
export function addAdmin (params: IAdminAddParams) {
return request.post('/admin/add', params)
}
interface IAdminUpdateParams {
adminname: string
role: number
checkedKeys: any // 因为开放,索引设置为any
}
export function updateAdmin (params: IAdminUpdateParams) {
return request.post('/admin/update', params)
}t
添加管理员
{{ (currentPage - 1) * pageSize + scope.$index + 1 }}
超级管理员
管理员
编辑
删除
添加管理员
添加
此算法建议直接拿来使用
// src/utils/get-menu-list.ts
import type { MyRouteRecordRaw } from "@/router";
export function getMenuList (menus: MyRouteRecordRaw[], checkedkeys: string[]) {
if (checkedkeys.length === 0) { // 如果没有设置权限,那么即将拥有所有的权限
checkedkeys = ['/home', '/banner', '/banner/list', '/banner/list/home', '/banner/list/kind', '/banner/add', '/pro', '/pro/list', '/pro/search', '/account', '/account/user', '/account/admin']
}
let result = getResult(menus)
function getResult(menus: MyRouteRecordRaw[]) {
let arr: MyRouteRecordRaw[]= []
menus.forEach(item => {
if (item.meta) {
if (checkedkeys.includes(item.path)) {
arr.push({...item})
if (item.children) {
arr[arr.length - 1].children = getResult(item.children)
}
}
} else { // 系统首页
arr.push(item)
}
});
return arr
}
return result
}
<!-- src/layout/components/Sidebar/index.vue -->
<script lang="ts" setup>
import { ref } from 'vue'
import SidebarItem from './SidebarItem.vue'
import Logo from './../logo/index.vue'
import { getMenuList } from '@/utils/get-menu-list'
// 整合路由
import { constantRoutes, asyncRoutes } from '@/router'
import { useRoute } from 'vue-router';
// /banner,/banner/add,/banner/list,/banner/list/home,/banner/list/kind,/home
const checkedkeys = localStorage.getItem('adminname') !== 'admin' ? localStorage.getItem('checkedkeys')!.split(',') : []
console.log('checkedkeys', checkedkeys)
// const menuList = constantRoutes.concat(asyncRoutes) // 所有数据
const menuList = getMenuList(constantRoutes.concat(asyncRoutes), checkedkeys) // 真实数据
// console.log(menuList)
// 获取路由地址
// 选项式API this.$route
const route = useRoute()
// console.log(route) // route.path.value
const collapse = ref(false)
</script>
<template>
<el-aside :width="collapse ? '50px' : '200px'">
<!-- <div class="logo"></div> -->
<Logo :collapse="collapse"/>
<!-- 菜单组件
https://element-plus.gitee.io/zh-CN/component/menu.html#%E4%BE%A7%E6%A0%8F
-->
<!-- <div>左侧菜单</div> -->
<el-menu
:collapse="collapse"
:unique-opened="true"
:default-active="route.path"
background-color="#001529"
text-color="#fff"
:collapse-transition="false"
>
<!-- 路由中的每一个选项都需要单独去渲染菜单项
有的不需要出现在左侧的菜单栏
有的只有一个菜单项
有的有二级菜单和三级菜单
建议把每个菜单项单独封装成一个组件 SidebarItem
登录 系统首页 产品管理 账户管理 轮播图管理
将每一项数据 item 传输到子组件,子组件接收数据,并且渲染
-->
<SidebarItem
v-for="item of menuList"
:key="item.path"
:item = "item"
></SidebarItem>
</el-menu>
<div class="changeIcon" :style="{ width: collapse ? '50px' : '200px'}">
<el-icon color="#fff" v-if="collapse" @click="collapse = !collapse"><ArrowRight /></el-icon>
<el-icon color="#fff" v-else @click="collapse=!collapse"><ArrowLeft /></el-icon>
</div>
</el-aside>
</template>
<style lang="scss">
.changeIcon {
position: absolute;
bottom: 10px;
display: flex;
justify-content: center;
}
</style>
ckbox
node-key=“id”
@check=“onTreeCheck”
/>
# 11.左侧菜单栏动态渲染
## 11.1 处理左侧菜单数据
此算法建议直接拿来使用
```ts
// src/utils/get-menu-list.ts
import type { MyRouteRecordRaw } from "@/router";
export function getMenuList (menus: MyRouteRecordRaw[], checkedkeys: string[]) {
if (checkedkeys.length === 0) { // 如果没有设置权限,那么即将拥有所有的权限
checkedkeys = ['/home', '/banner', '/banner/list', '/banner/list/home', '/banner/list/kind', '/banner/add', '/pro', '/pro/list', '/pro/search', '/account', '/account/user', '/account/admin']
}
let result = getResult(menus)
function getResult(menus: MyRouteRecordRaw[]) {
let arr: MyRouteRecordRaw[]= []
menus.forEach(item => {
if (item.meta) {
if (checkedkeys.includes(item.path)) {
arr.push({...item})
if (item.children) {
arr[arr.length - 1].children = getResult(item.children)
}
}
} else { // 系统首页
arr.push(item)
}
});
return arr
}
return result
}
<!-- src/layout/components/Sidebar/index.vue -->
<script lang="ts" setup>
import { ref } from 'vue'
import SidebarItem from './SidebarItem.vue'
import Logo from './../logo/index.vue'
import { getMenuList } from '@/utils/get-menu-list'
// 整合路由
import { constantRoutes, asyncRoutes } from '@/router'
import { useRoute } from 'vue-router';
// /banner,/banner/add,/banner/list,/banner/list/home,/banner/list/kind,/home
const checkedkeys = localStorage.getItem('adminname') !== 'admin' ? localStorage.getItem('checkedkeys')!.split(',') : []
console.log('checkedkeys', checkedkeys)
// const menuList = constantRoutes.concat(asyncRoutes) // 所有数据
const menuList = getMenuList(constantRoutes.concat(asyncRoutes), checkedkeys) // 真实数据
// console.log(menuList)
// 获取路由地址
// 选项式API this.$route
const route = useRoute()
// console.log(route) // route.path.value
const collapse = ref(false)
</script>
<template>
<el-aside :width="collapse ? '50px' : '200px'">
<!-- <div class="logo"></div> -->
<Logo :collapse="collapse"/>
<!-- 菜单组件
https://element-plus.gitee.io/zh-CN/component/menu.html#%E4%BE%A7%E6%A0%8F
-->
<!-- <div>左侧菜单</div> -->
<el-menu
:collapse="collapse"
:unique-opened="true"
:default-active="route.path"
background-color="#001529"
text-color="#fff"
:collapse-transition="false"
>
<!-- 路由中的每一个选项都需要单独去渲染菜单项
有的不需要出现在左侧的菜单栏
有的只有一个菜单项
有的有二级菜单和三级菜单
建议把每个菜单项单独封装成一个组件 SidebarItem
登录 系统首页 产品管理 账户管理 轮播图管理
将每一项数据 item 传输到子组件,子组件接收数据,并且渲染
-->
<SidebarItem
v-for="item of menuList"
:key="item.path"
:item = "item"
></SidebarItem>
</el-menu>
<div class="changeIcon" :style="{ width: collapse ? '50px' : '200px'}">
<el-icon color="#fff" v-if="collapse" @click="collapse = !collapse"><ArrowRight /></el-icon>
<el-icon color="#fff" v-else @click="collapse=!collapse"><ArrowLeft /></el-icon>
</div>
</el-aside>
</template>
<style lang="scss">
.changeIcon {
position: absolute;
bottom: 10px;
display: flex;
justify-content: center;
}
</style>