硅谷甄选笔记(9-2开始学习中)

p24

1.let pro=defineProps(["width","fill","src"])在template直接使用width,在setup使用是pro.wid;
2.let pro = defineProps({ prefix: { type: String, default: "#icon-" }, name: { type: String }, color: { type: String, default: "#000" }, width: { type: String, default: "30px" }, height: { type: String, default: "30px" } })
两种方式都可以.

  1. 在开发项目的时候经常会用到svg矢量图,而且我们使用SVG以后,页面上加载的不再是图片资源,

这对页面性能来说是个很大的提升,而且我们SVG文件比img要小的很多,放在项目中几乎不占用资源。

安装SVG依赖插件

java pnpm install vite-plugin-svg-icons -D

测试SVG图标使用

svg:图标外层容器节点, 内部需要与use标签结合使用

 <svg>
    <!-- 测试SVG图标使用
    svg:图标外层容器节点,内部需要与use标签结合使用 -->
    <!-- xlink:href执行用哪一个图标,属性值务必#icon-图标名字 -->
    <!-- use标签fill属性可以设置图标的颜色 -->
    <use xlink:href="#icon-icon" fill="red"></use>
  </svg>
  • xlink:href=“#icon-icon” 的`#icon-xxx xxx是svg文件名字 ,文件夹在src/assets/icons.

在vite.config.ts中配置插件

import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
import path from 'path'
export default () => {
  return {
    plugins: [
      createSvgIconsPlugin({
        // Specify the icon folder to be cached
        iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')],
        // Specify symbolId format
        symbolId: 'icon-[dir]-[name]',
      }),
    ],
  }
}

p25

// 1.引入组件
import SvgIcon from '@/components/SvgIcon/index.vue'
import Pagination from '@/components/Pagination/index.vue'
// 每个组件都有名字和组件内容.需要是用app.component()注册.
console.log(SvgIcon,Pagination);
const GlobalComponent ={SvgIcon,Pagination};
console.log(Object.keys(GlobalComponent));


export default {
  install(app){
    Object.keys(GlobalComponent).forEach((item)=>{
      app.component(item,GlobalComponent[item]);
    })
  }
}
  • 这个引入组件 有组件名字和组件内容,需要使用app.component()引入.
import globalComponent from "@/components/index"
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'

import '@/styles/index.scss'
const app=createApp(App)
app.use(globalComponent)
  • main.ts可以在这里装插件.进入项目局自己运行.

p26

在使用scss语法!!!需要加上lang=“scss”

<style scoped lang="scss"></style>

接下来我们为项目添加一些全局的样式

在src/styles目录下创建一个index.scss文件,当然项目中需要用到清除默认样式,因此在index.scss引入reset.scss

@import reset.scss

在入口文件引入

import '@/styles'

但是你会发现在src/styles/index.scss全局样式文件中没有办法使用 变量 . 因此需要给项目中引入全局变量 变量.因此需要给项目中引入全局变量 变量.因此需要给项目中引入全局变量.

在style/variable.scss创建一个variable.scss文件!

在vite.config.ts文件配置如下:

export default defineConfig((config) => {
    css: {
      preprocessorOptions: {
        scss: {
          javascriptEnabled: true,
          additionalData: '@import "./src/styles/variable.scss";',
        },
      },
    },
    }
  • 在sass中可以在一个sass文件中,使用@import导入另一个sass文件.
  • 使用全局变量可以规定项目的颜色切换.
  • 在main.ts的东西会提前加载好.

27 mock接口

安装依赖: https://www.npmjs.com/package/vite-plugin-mock

pnpm install -D vite-plugin-mock mockjs

mock中配置文件会报错,是因为vite-plugin-mock版本匹配导致的,可以 pnpm i vite-plugin-mock@2.0.0
还有pinia最新版也不匹配 可以安装时候 pnpm i pinia@2.0.34

createUserList:次函数执行会返回一个数组,数组里面包含两个用户信息。

对外暴露一个数组:数组里面包含两个接口

登录假的接口
获取用户信息的假的接口

在 vite.config.js 配置文件启用插件。

mport { UserConfigExport, ConfigEnv } from 'vite'
import { viteMockServe } from 'vite-plugin-mock'
import vue from '@vitejs/plugin-vue'
export default ({ command })=> {
  return {
    plugins: [
      vue(),
      viteMockServe({
        localEnabled: command === 'serve',
      }),
    ],
  }
}

在根目录创建mock文件夹:去创建我们需要mock数据与接口!!!

在mock文件夹内部创建一个user.ts文件

axios二次封装

  1. import.meta.env 可以获得三种环境对应的文件信息.
    例如: import.meta.env.VITE_APP_BASE_API, //获得前缀
    api

  2. axios一次封装是使用 axios.create(baseurl,timeout)

  3. 对象的属性增加可以是

 config.headers.token="123";
 config.headers["token"]="123";
import axios from "axios"
import { ElMessage } from 'element-plus'

const request = axios.create({
  // 基础路径  api
  baseURL: import.meta.env.VITE_APP_BASE_API,
  timeout: 5000  //超时时间
})

request.interceptors.request.use((config) => {
  console.log(config);
  config.headers["token"]="123";
  // 请求拦截下来了,返回回去.
  return config;
})

request.interceptors.response.use((response) => {
  // 成功的回调。
  
  return response.data;
}, (error) => {
  // 状态码400 404 500错误
  let msg = ""
  const code = error.response.status;

  switch (code) {
    case 401:
      msg = "token过期"
      break;
    case 403:
      msg = "无权限访问";
      break;

    case 404:
      msg = "页面不存在";
      break;
    case 500:
      msg = "服务器出现问题";
      break;

    default:
      msg = "网络错误";
  }
  ElMessage({
    showClose: true,
    message: msg,
    type: 'error',
  })
  return Promise.reject(error);
  // 失败的回调
})


export default request;
  • 请求拦截器加上token
  • 响应拦截器 有成功回调和失败回调. 在失败回调中可以是获得code,之后进行判断,赋予msg.使用提示框返回.
  • api的第一次封装是在src/util/request

二次封装开发接口

开发接口中

// 与用户相关的接口
import request from "@/utils/request";
import { loginForm,loginResponseData,userInfoResponseData } from "./type"
enum UserApi {
  LOGIN_URL = "/user/login",
  USERINFO_URL = "/user/info"
}

// 形参使用接口约束.
export const LoginApi = (data: loginForm) => request.post<any,loginResponseData>(UserApi.LOGIN_URL, data)
export const UserInfoApi = () => request.get<any,userInfoResponseData>(UserApi.USERINFO_URL)
export interface loginForm{
  username: string,
  password: string
}

interface dataType{
  token: string
}


export interface loginResponseData{
  code:number,
  data:dataType
}

interface checkUser {
  userId: number,
  avatar:string,
  username: string,
  password: string,
  desc: string,
  roles: string[],
  buttons: string[],
  routes: string[],
  token: string,
}

interface InfoType{
  checkUser:checkUser
}

export interface userInfoResponseData{
  code:number,
  data:InfoType
}
  • 二次封装可以使用request.get() request.post() requets({})
  • 使用接口类型来约束对象.

路由

  • 组件分两种,一种是普通组件,写在components里的,还有一种是路由组件,这种就是路由组件
  • 哈希模式:简单易用,兼容性好,但 URL 中包含 # 符号。适用于不需要复杂服务器配置的情况。
    历史模式:支持干净的 URL,对 SEO 更友好,但需要服务器端支持。适用于需要更复杂路由管理的应用。(hash模式有#号.)
const routerData = [
  {
    // 登录成功后的页面
    path: "/",
    name: "home",
    component: () => import("@/views/home/index.vue"),
  },
  {
    // 登录
    path: "/login",
    name: "login",
    component: () => import("@/views/login/index.vue"),
  },
  {
    path: "/404",
    name: "404",
    component: () => import("@/views/404/index.vue")
  },
  {
    path: "/:pathMatch(.*)*",
    redirect: "/404",
    name:"Any"
  }
]
export default routerData;
  • 最后一个就是没有匹配的到这里,之后到404.
<template>
  <div>
    <router-view></router-view>
  </div>
</template>
<script setup lang="ts">
import { onMounted } from 'vue';
import { LoginApi } from "@/api/user/index";

onMounted(() => {
  LoginApi({ username: "admin", password: "111111" })
})
</script>
<style scoped lang="scss"></style>
  • 使用router-view标签.

p31 登录页面

.login_container{
  width:100%;
  height:100vh;
  background-color: #2b4b6b;
}
  • height:100%;是要依靠div里面的内容进行扩大. height:100vh;是整个屏幕占据
  • element框架有根据屏幕大小设置大小.

p32

在这里插入图片描述

  • 大仓库暴露后注册到main.ts

  • 小仓库直接暴露据可以使用了。

  • 信息需要存储到vuex的,就在action中请求接口,之后存储。

  • 在vuex中操作state需要通过mutations,在pinia中是直接操作,使用this获得数据.

  • 使用await async 里面根据结果返回return Promise的状态.

state: () => {
    return {
      token: localStorage.getItem("token")
    }
  },
------------------
   async userLogin(data: loginForm) {
      const result = await LoginApi(data);
      if (result.code === 200) {
        this.token = result.data.token;
        localStorage.setItem("token", this.token);
        return Promise.resolve()
      } else {
        return Promise.reject(new Error(result.data.message))
      }
    }

let submitForm = () => {
  loadingState.value = true
  let LoginResult = useStore.userLogin(form)
  console.log(LoginResult)

  LoginResult.then(() => {
    $router.push('/')
    ElNotification({
      message: '登陆成功',
      type: 'success'
    })
    loadingState.value = false

  }).catch(error => {
    ElNotification({
      message: error.message,
      type: 'error'
    })
    loadingState.value = false
  })
}
  • 存储到pinia,之后本地存储.
promise
  .then(result => {
    // 处理成功状态
    console.log('成功:', result);
  })
  .catch(error => {
    // 处理失败状态
    console.error('失败:', error.data.message);
  });

  • 获得promise失败的要有message,成功的不用.

  • 在 Vue 2 中,this.$router 是访问和操作路由的主要方式,而在 Vue 3 中,则使用 Composition API 和 useRouter

import { useRouter } from 'vue-router'
let $router = useRouter()

在组件的 setup 函数中调用 useRouter,它会返回 Vue Router 实例。

  • result中有成功的话没有messgae,失败的话有message.所以在失败返回promise.reject时候要返回信息.

  • let loadingState=ref(false)
    在template中是直接使用loadingState这个;在代码中是 loadingState.value=true. 才能进行赋值

ElNotification({
      message: error.message,
      type: 'error'
    })
  • 这是通知的通知栏.
<el-button type="primary" @click="submitForm(form)" class="btn" :loading="loadingState">
            登录
</el-button>
  • 这是可以加载动态的按钮

  • import type 用于仅导入类型,不会在最终的 JavaScript 代码中出现,而普通的 import 用于导入实际的值(如函数、对象等)

Ts的类型主要针对方法的形参和返回的数据。还有pinia的state。
封装localstorege

在这里插入图片描述
本来是没有封装,我们针对本地存储进行封装一下。

token.ts

export const SET_TOKEN = (token: string) => {
  localStorage.setItem("token", token as string);
}

export const GET_TOKEN = () => {
  localStorage.getItem("token")
}

p34 登录时间判断

根据时间判断,在这里插入图片描述返回消息,

  • 本来vue2是写在method中的,不可以引出来
  • vue3可以把所有的方法写出来变成工具文件.之后引入,在进行执行函数.

await和async

async userLogin(data: loginForm) {
      const result: loginResponseData = await LoginApi(data);
      if (result.code === 200) {
        this.token = result.data.token;
        SET_TOKEN(this.token)
        return Promise.resolve()
      } else {
        return Promise.reject(new Error(result.data.message))
      }
    }
  • await 加载异步操作前面,可以直接获得数据.
  • async修饰的方法一定会返回promise对象.这个对象有成功的状态和失败的状态.关于状态我们可以自己控制.

在这里插入图片描述

  • 在使用async后,获得promise状态就用then.

规则校验

  • 这是一个
  • props对应着rules的属性名. trigger有change和blur的.
  • 不符合条件的时候,会触发message

在这里插入图片描述

  • model是绑定表单数据
  • rules是规则校验
  • ref是一个关于校验的结果在这里插入图片描述

p37 layout布局

在这里插入图片描述

  .layout_tabbar {
    width: calc(100% - $base_menu_width);
    height: $base_tabbar_height;
    background-color: beige;
    position: fixed;
    top: 0;
    left: $base_menu_width;
  }
  • 使用css的calc属性可以动态计算值,
::-webkit-scrollbar{
  width: 10px;
}
::-webkit-scrollbar-track{
  background: $base_menu_color;
}
::-webkit-scrollbar-thumb{
  width: 10px;
  border: 10px;
  background-color: yellow;
}
  • 这是滚动条的css -webkit-
  • 超过父盒子的子盒子的使用overflow:auto
  • 使用饿了么ui的滚动跳也可以解决问题,

p38

  • 感觉关于h5很多部分的标题或者图片都可以另外封装处理.
<template>
  <div class="logo" :v-if="setting.hidden">
    <img :src="setting.logo_puture" alt="">
    <p>{{setting.logo_title}}</p>
  </div>
</template>

-----
**setting.ts**

export default {
  logo_title: 'xxx管理系统',
  logo_puture: '/public/logo.png',
  hidden:true
}
  • 路径可以从/开始代表从当前项目开始.
  • 先设计好宽高,
  • 弹性布局 横向是align-items;

p39

  • 教学了使用饿了么ui制作菜单
	 <el-sub-menu index="1">
            <template #title>
              <el-icon>
                <location />
              </el-icon>
              <span>首页</span>
            </template>
            <el-menu-item-group>
              <el-menu-item index="1-1">item one</el-menu-item>
              <el-menu-item index="1-2">item two</el-menu-item>
            </el-menu-item-group>
          </el-sub-menu>

          <!-- 第二个 -->
          <el-menu-item index="2">
            <el-icon><icon-menu /></el-icon>
            <span>Navigator Two</span>
          </el-menu-item>
  • 单个菜单是el-menu-item ;多个菜单是el-sub-menu
  • 但是项目菜单一多,我们要怎么办

p40

<template>
  <div>
    <template v-for="(item,index) in menuList" key="item.path">
      <!-- 没有下拉菜单 -->
      <template v-if="!item.children">
        <el-menu-item :index="index" v-if="!item.meta.hidden">
          <el-icon>
            <component :is="item.meta.icon"></component>
          </el-icon>
          <span>{{item.meta.title}}</span>
        </el-menu-item>
      </template>
      <!-- 有下拉 start -->
      <template v-if="item.children">
        <el-sub-menu :index="index" :key="item.path">
          <template #title>
            <el-icon>
              <component :is="item.meta.icon"></component>
            </el-icon>
            <span>{{item.meta.title}}</span>
          </template>
          <div v-for="i in item.children" :key="i.path">
            <!-- 没有子 -->
            <template v-if="!i.children && !i.meta.hidden">
              <el-menu-item :index="i.index"><el-icon>
                  <component :is="item.meta.icon"></component>

                </el-icon>
                <span>{{i.meta.title}}</span></el-menu-item>
            </template>
            <!-- 有子 -->
            <template v-if="i.children">
              <el-sub-menu :index="i.index" :key="i.index">
                <template #title>
                  <el-icon>
                    <component :is="item.meta.icon"></component>

                  </el-icon>
                  <span>{{i.meta.title}}</span>
                </template>
                <Menu :menuList="i.children" v-if="i.children">{{i.children}}</Menu>
              </el-sub-menu>
            </template>
          </div>
        </el-sub-menu>
      </template>

    </template>
  </div>
</template>

<script setup lang="ts">
defineProps(['menuList'])
import { Document, Menu as IconMenu, Location, Setting } from '@element-plus/icons-vue'
</script>
<script  lang="ts">
export default {
  name: 'Menu'
}
</script>

<style scoped  lang="scss">
</style>
  • 前端递归 主要是不断减少的量的数据格式还是要一样.
  • 在这里插入图片描述
 {
    // 登录
    path: "/login",
    name: "login",
    component: () => import("@/views/login/index.vue"),
    meta: {
      title: "登录",
      icon: "IconMenu",
      hidden: true
    }
  },
  -----------

  <template v-if="!i.children && !i.meta.hidden">
              <el-menu-item :index="i.index">{{i.name}}</el-menu-item>
            </template>
  • hidden:true 表示隐藏该路由,在路由标签上使用v-if实现

   <template #title>
                  <el-icon>
                    <component :is="item.meta.icon"></component>
                  </el-icon>
                  <span>{{i.meta.title}}</span>
                </template>
  • item.meta.icon 里面这是一个组件名字,component组件.

p40

在这里插入图片描述

  • index在一个单选 多选下拉中.并且加上@click就可以获得每一个的路由.
  • index是一个唯一标识符号,就是路径
    router-view 展示路由绑定组件的地方.
<template>
    <router-view v-slot="{ Component }">
      <transition name="fade">
        <component :is="Component" />
      </transition>
    </router-view>
</template>

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

<style scoped lang="scss">
.fade-enter-from {
  opacity: 0;
}
.fade-enter-active {
  transition: all 1s;
  background: red; // 添加背景颜色用于测试
}

.fade-enter-to {
  opacity: 1;
}
</style>

展示地方可以设置动效.

p43 刷新菜单关闭

  • url在刷新页面后路径不会改变,但是菜单会关闭我们要利用default-active来设置.
  • 在vue3使用路由对象需要先引入,在实例化.在vue2可以直接this.$router获得,感觉vue3节约来很多东西
import { useRoute } from 'vue-router'
let $router = useRoute()
console.log($router.fullPath)
<div class="layout_slider">
      <Logo></Logo>
      <el-scrollbar class="scrollbar">
        <el-menu :default-active="$router.fullPath" active-text-color="#ffd04b" background-color="##545c64" router class="el-menu-vertical-demo" text-color="#fff" @open="handleOpen" @close="handleClose">
          <Menu :menuList=userData.menuRoutes> </Menu>
        </el-menu>
      </el-scrollbar>
    </div>
    <!-- 顶部导航 -->
    <div class="layout_tabbar">456</div>
    <!-- 内容展示 -->
    <div class="layout_main">
      <Main></Main>
    </div>
  </div>
</template>

<script setup lang="ts">
import { Document, Menu as IconMenu, Location, Setting } from '@element-plus/icons-vue'
import Logo from './logo/index.vue'
import Menu from '@/layout/Menu/index.vue'
import useUserStore from '@/store/modules/user/index.ts'
import Main from '@/views/main/index.vue'
let userData = useUserStore()

import { useRoute } from 'vue-router'
let $router = useRoute()
console.log($router.fullPath)
  • 在layout的tabbar封装成为组件放到layout文件夹下.路由有关的放到views中.
  • 给组件命名可以可以在开发者查看该组件和使用递归标签.
.layout_slider {
  &.fold {
    width: 60px;
  }
}

  • &.fold: & 符号在 SCSS 中代表父选择器,即 .layout_slider。所以 &.fold 代表 .layout_slider.fold。
  • class的动态绑定 有 :class=“{fold? statas?true:false}” ;class"{类名:三元表达式}"
  • 菜单栏之前有设置宽度,但是如果需要缩小的话,可以利用css的选择器优先级来设置.我们可以用两个类名进行设置宽度.
  &.fold {
      width: $base_menu_minWidth;
      transition:all 0.3s;
    }
  • transition 就是用0.3s时间变化到类名的属性.
  • all就是所有可以变化的属性。

p44 菜单折叠

在这里插入图片描述

  • 这个是菜单的折叠面板。根据类名决定是否使用。
    在这里插入图片描述
  • 菜单栏需要设置最大的宽度和最小宽度。
  • 根据fold类名的true和false,根据类名,决定css属性。、

在这里插入图片描述

面包屑

  • 面包屑的数据我们通过
    获得路由实例$router.matched 这个api获得多级路由的信息。
 <el-breadcrumb-item v-for="(item,index) in $router.matched" :key="index" :to="{ path: item.path }">
          {{item.meta.title}}
        </el-breadcrumb-item>
  • to可以获得到达的多级路由的信息。之后在router.ts中可以设置redirect进行重定向。

nexttick用法

  • Vue.js 的响应式系统会在数据发生变化后对 DOM 进行更新。先更新数据刷新Dom,使用nextTick才可以操作新的dom.

全屏操作

let fullScreen = () => {
  console.log(document.fullscreenElement)
  let fullScreen = document.fullscreenElement
  if (!fullScreen) {
    document.documentElement.requestFullscreen()
  } else {
    document.exitFullscreen()
  }
}
  • document.fullscreenElement 这是html元素。

  • beforecreate --. create – beforemount—mount – beforeupdate —update – beforeUnmount —unmount生命周期,更新的可以执行多次.

  • 浏览器刷新全部周期执行一次.

  • 登录成功后,会根据生命周期拿到头像和用户名.保存在pinia.

//进行导航的
import { useRouter } from 'vue-router'
const $router = useRouter()


//查看当前路由信息
import { useRoute } from 'vue-router'
const $router = useRoute()
  • useRoute() 是用于访问当前的路由信息(如路由参数、查询参数等),而 useRouter() 是用于执行导航操作的。
  • 在 Vue 3 和 Pinia 中,使用 useRouter 直接在模块外部是无效的,因为 useRouter 必须在 Vue 组件的 setup 函数或其他类似的上下文中调用。你不能在定义 Pinia store 的模块外部直接调用 useRouter()。

为了修复这个问题,你可以将 useRouter 放到 actions 中,在需要使用 router 进行导航的地方动态调用它。

  • 这个route,router需要再setup函数里面使用或者在pinia函数里面使用.

如果退出登录后,记住上一次操作的url

这个场景是:你未登录的时候后在浏览一些页面, 但是接下来的操作需要登录, 当你登录后,在跳转到之前浏览的页面。如果第一次登录的话就需要用到判断是否有query.

  • 把当前操作的url,放在url上的query参数.

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

  • 路由导航的格式
$route.push({path:"",query:{key:value}})

query在url中是key=value

我们把获得用户信息的接口放在home的组件,会有用户信息,可是我们去到其他view,刷新一下就没有用户信息了.(主要是刷新).

onMounted(() => {
  userUseStore.requestUserInfo()
})
  • 我们之前是把获得用户信息的方法放在home中的(其中一个view页面),

  • 只有访问home页面才会调用这个方法,但是我们可以放在更大范围的layout中,这样就可以一种加载.因为所有组件都在layout组件里面.所以每次刷新都会加载这个获得用户方法.

  • 所以登录和获得用户信息是两回事是两个接口.

  • 第二种解决方法是把请求的接口放在全局前置路由.

路由鉴权

全局路由守卫 和前置后值守卫 在更换路由信息会触发这个.
还有路由鉴权,是需要什么样的条件才能访问这个路由.

全局路由守卫

  • 前置路由守卫和后置路由守卫
import router from "@/router"
import nprogress from "nprogress"
import "nprogress/nprogress.css"

router.beforeEach((to, from, next) => {
  console.log(from);
  console.log(to);
  nprogress.start(); 
  next();
  // 放行函数
})

// 全局后置守卫
router.afterEach((to, from) => {
  nprogress.done();
});

路由鉴权


  if (token) {
    console.log("有token");
    // 已经登录
    if (to.path != '/login') {
      next()
    } else {
      next({ path: "/" })
    }

  } else {
    console.log("无token");
    // 没有登录
    if (to.path == '/login') { 
      next()
    } else {
      next({ path: "/login" })
    }
  }
  • 分为登录成功和未登录的情况.
  • 登录成功有不可以访问登录页面和可以访问除了登录页外的.
  • 未登录有可以访问login.不可以访问login外的页面.

绝对定位和静态定位

在这里插入图片描述
绝对定位脱离了文档流.静态定位还在文档流中,所以绝对定位会挡住静态定位的.

  • 左边的使用静态,右边是绝对来控制left和top.这样就可以挡住左边的菜单栏.
token过期处理
async requestUserInfo() {
      // 获得用户api。 
      const userInfo = await UserInfoApi()
      console.log(userInfo);
      if (userInfo.code != 200) return Promise.reject(new Error(userInfo.message))
      const { username, avatar } = userInfo.data.checkUser
      this.username = username
      this.avatar = avatar
      return Promise.resolve()
    },
  • 请求接口返回promise
 if (!username) {
        try {
          await userStore.requestUserInfo()
          next()
        } catch (error) {
          console.log(error);
          userStore.logout()
          next({ path: "/login" })
        }
      } 
  • token过期或者被修改token,处理错误.
import router from "@/router"
import nprogress from "nprogress"
import "nprogress/nprogress.css"
import store from "@/store"
import userUserStore from "@/store/modules/user"
import path from "path"
import setting from "./setting"
const userStore = userUserStore(store)
console.log(userStore.token);
const token = userStore.token;
router.beforeEach(async (to, from, next) => {
  document.title = setting.logo_title + to.meta.title
  nprogress.start();
  console.log(to);
  const username = userStore.username
  if (token && localStorage.getItem("token")) {
    console.log("有token");

    if (to.path != '/login') {
      // 来到主页
      if (!username) {
        try {
          await userStore.requestUserInfo()
          next()
        } catch (error) {
          console.log(error);
          userStore.logout()
          next({ path: "/login" })
        }
      } else {
        // 有用户信息
        next()
      }

    } else {

      next({ path: "/" })
    }

  } else {
    console.log("无token");
    // 没有登录
    if (to.path == '/login') {
      next()
    } else {
      next({ path: "/login" })
    }
  }
  // 放行函数
})

// 全局后置守卫
router.afterEach((to, from) => {
  nprogress.done();
});




  • 没有用户名的话,请求用户名字
  • 有用户名放行
  • 检查token和用户名.可以的话放行.不符合的话进行处理.

引入变量

  • 设置一个文件setting.ts之后暴露出去,别人需要的时候就可以引入使用里面变量.

p51代理服务器

  • 我们的脚本命令控制使用那三个文件的, VITE_SERVE=“http://sph-api.atguigu.cn”
  • VITE_APP_BASE_API=“/api/” 写在

const request = axios.create({
  // 基础路径  api
  baseURL: import.meta.env.VITE_APP_BASE_API,
  timeout: 5000  //超时时间
})

/api/ 用来给代理服务器识别的.

  • 之后代理服务器把这一段重写 ,在前端的/api/user/add,中间api给服务器识别和重写了.在后端是/user/add

服务器代理

import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'

import { UserConfigExport, ConfigEnv } from 'vite'

import { viteMockServe } from 'vite-plugin-mock'
// 引入svg需要的插件.
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'

export default defineConfig(({ command, mode }) => {
  //获取各种环境下的对应的变量
  const env = loadEnv(mode, process.cwd(), "");
  console.log("环境");

  console.log(env);

  return {

    plugins: [
      vue(),
      viteMockServe({
        // default
        mockPath: 'mock',
        localEnabled: command === 'serve',//保证开发阶段可以使用mock接口.
      }),
      createSvgIconsPlugin({
        // Specify the icon folder to be cached
        iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')],
        // Specify symbolId format
        symbolId: 'icon-[dir]-[name]',
      }),
    ],
    resolve: {
      dedupe: [
        'vue'
      ],
      alias: {
        "@": path.resolve("./src") // 相对路径别名配置,使用 @ 代替 src
      }
    },
    css: {
      preprocessorOptions: {
        scss: {
          javascriptEnabled: true,
          additionalData: '@import "./src/styles/variable.scss";',
        },
      },
    },
    server: {
      proxy: {
        [env.VITE_APP_BASE_API]: {
          target: env.VITE_SERVE, // 正确使用环境变量
          changeOrigin: true,
          rewrite: (path) => path.replace(/^\/api/, ''),
        },
      }
    },
  }
})

TS设计

export interface userBaseData {
  code: number,
  message: string,
  ok: boolean
}


// 用户登录返回信息.
export interface userResponseData extends userBaseData{
  data: string,
}

// 登录所需表单.
export interface loginForm {
  username: string,
  password: string
}

// 用户详情的返回接口
export interface LoginInfoResponseData  extends userBaseData{
  data: {
    routes: string[],
    buttons: string[],
    roles: string[],
    name: string,
    avatar: string
  }
}

在这里插入图片描述

export interface ResponseData {
  code: number
  message: string
  ok: boolean
}

//已有的品牌的ts数据类型
export interface TradeMark {
  id?: number
  tmName: string
  logoUrl: string
}

//包含全部品牌数据的ts类型
export type Records = TradeMark[]

//获取的已有全部品牌的数据ts类型
export interface TradeMarkResponseData extends ResponseData {
  data: {
    records: Records
    total: number
    size: number
    current: number
    searchCount: boolean
    pages: number
  }
}

插槽

插槽(Slot)是 Vue.js 中一种用于在子组件中插入内容的机制。它允许父组件向子组件传递自定义内容,这使得子组件更加灵活和可复用。插槽通常用于在组件内部自定义显示部分的内容,比如按钮、列表、卡片等内容。

插槽的基本概念

在 Vue.js 中,插槽可以看作是一个占位符,父组件可以在这个占位符中插入特定内容。插槽有两种常见类型:普通插槽和具名插槽。Vue 还支持作用域插槽,它允许父组件接收来自子组件的动态数据。

基本插槽示例

假设你有一个 MyComponent 子组件,希望允许父组件定义其内部内容:

<!-- MyComponent.vue -->
<template>
  <div class="box">
    <slot></slot> <!-- 插槽占位符 -->
  </div>
</template>

MyComponent 中,我们使用 <slot></slot> 表示插槽。父组件可以向该插槽传入内容:

<!-- ParentComponent.vue -->
<template>
  <MyComponent>
    <p>这是一段由父组件提供的内容。</p>
  </MyComponent>
</template>

结果是:

<div class="box">
  <p>这是一段由父组件提供的内容。</p>
</div>

这里,父组件定义的 <p> 标签内容被插入到了 MyComponent 组件中的插槽位置。

具名插槽

有时,你可能希望在子组件的不同部分插入不同的内容,这时可以使用具名插槽。

<!-- MyComponent.vue -->
<template>
  <header>
    <slot name="header"></slot> <!-- 具名插槽 -->
  </header>
  <main>
    <slot></slot> <!-- 默认插槽 -->
  </main>
  <footer>
    <slot name="footer"></slot> <!-- 具名插槽 -->
  </footer>
</template>

在父组件中,你可以使用具名插槽来插入不同部分的内容:

<!-- ParentComponent.vue -->
<template>
  <MyComponent>
    <template #header>
      <h1>这是头部内容</h1>
    </template>
    <p>这是主体内容</p>
    <template #footer>
      <p>这是底部内容</p>
    </template>
  </MyComponent>
</template>

结果是:

<header>
  <h1>这是头部内容</h1>
</header>
<main>
  <p>这是主体内容</p>
</main>
<footer>
  <p>这是底部内容</p>
</footer>

作用域插槽

作用域插槽是一种特殊的插槽,它允许子组件向父组件传递数据,并让父组件根据这些数据渲染内容。

<!-- MyComponent.vue -->
<template>
  <ul>
    <slot :items="items"></slot> <!-- 通过插槽传递数据 -->
  </ul>
</template>

<script>
export default {
  data() {
    return {
      items: ['苹果', '香蕉', '橘子']
    };
  }
};
</script>

在父组件中,你可以通过作用域插槽接收这些数据:

<!-- ParentComponent.vue -->
<template>
  <MyComponent>
    <template v-slot:default="{ items }">
      <li v-for="item in items" :key="item">{{ item }}</li>
    </template>
  </MyComponent>
</template>

这里,v-slot:default="{ items }" 意味着父组件接收子组件传递的 items 数据,并基于这些数据动态生成列表。

总结

  • 普通插槽:允许父组件向子组件插入自定义内容。
  • 具名插槽:允许在子组件的不同部分插入不同内容。
  • 作用域插槽:允许父组件接收子组件传递的数据,并根据这些数据动态渲染内容。

插槽使得组件更加灵活和可复用,通过允许父组件控制子组件中的部分内容,你可以更方便地组合、定制和扩展组件的行为。

分页组件

export const reqHasTrademark = (page: number, limit: number) =>
  request.get<any, any>(API.TRADEMARK_URL + `${page}/${limit}`)
  • 先写好接口,参数有第几页和一页多少个.

  • 生命周期函数根据ref值,请求reqHasTrademark 接口.

  • const getHasTrademark = async (pager = 1) => {}
    这是前端的设定默认值.

function greet(name = 'Guest') {
  console.log(`Hello, ${name}!`);
}

// 调用时传递了参数
greet('Alice'); // 输出: Hello, Alice!

// 调用时没有传递参数,使用默认值
greet(); // 输出: Hello, Guest!

这是获得上传图片后的链接。

//图片上传成功钩子
const handleAvatarSuccess: UploadProps['onSuccess'] = (
  response,
  uploadFile,
) => {
  //response:即为当前这次上传图片post请求服务器返回的数据
  //收集上传图片的地址,添加一个新的品牌的时候带给服务器
  trademarkParams.logoUrl = response.data
  //图片上传成功,清除掉对应图片校验结果
  formRef.value.clearValidate('logoUrl')
}

新增和修改品牌的区别就是有id,只有存储过的品牌才会有id属性。

  • v-if 可以让一个组件走完生命周期,并且复活。

  • Object.assign()是浅拷贝

在这里插入图片描述

//添加品牌按钮的回调
const addTrademark = () => {
  //对话框显示
  dialogFormVisible.value = true
  //清空收集数据
  trademarkParams.id = 0
  trademarkParams.tmName = ''
  trademarkParams.logoUrl = ''
  //第一种写法:ts的问号语法
  formRef.value?.clearValidate('tmName')
  formRef.value?.clearValidate('logoUrl')
  /* nextTick(() => {
    formRef.value.clearValidate('tmName')
    formRef.value.clearValidate('logoUrl')
  }) */
}
  • ref会存储表单的校验结果,里面有清除验证信息,

数据库设计

在这里插入图片描述

  • 我们可以不断添加分类的属性.

在这里插入图片描述

  • 上面一个组件 下面一个组件。

  • 三个属性值给store。下面的监听第三个有的话就发送请求。

  • 模板中的

  • 一二三级分类都是在同一个数据表中。

  • 属性key和属性value也是在同一个表中。
    在这里插入图片描述
    在这里插入图片描述

  • 添加一次性是一个属性key多个属性value.

  • 浅拷贝还是保持响应式对象.

在你的代码中,:ref="(vc: any) => inputArr[$index] = vc" 是一个动态赋值的方式,用来将 <el-input> 组件的引用存储到 inputArr 数组中。这里是具体的含义:

  1. ref 的作用:在 Vue.js 中,ref 用于获取对 DOM 元素或组件实例的引用。当你给一个元素或组件添加 ref 属性时,可以在 Vue 实例中通过 this.$refs 来访问这个元素或组件。

  2. 动态赋值:在你的代码中,ref 的值是一个函数 (vc: any) => inputArr[$index] = vc。这个函数会在组件被渲染时调用,将 vc(即 <el-input> 组件的引用)赋值给 inputArr 数组中对应的索引 $index。这使得你可以在 inputArr 数组中存储多个 <el-input> 组件的引用。

举个例子:

假设你有一个表格,每一行都有一个 <el-input>。当表格渲染时,你希望能在后续的操作中访问这些输入框,例如在某些情况下聚焦到特定的输入框。使用动态 ref 可以让你在 inputArr 数组中存储每个输入框的引用,以便后续操作。

代码示例:
data() {
  return {
    inputArr: [] // 存储每个 <el-input> 的引用
  };
},
methods: {
  toLook(row, index) {
    // 处理输入框失去焦点后的逻辑
    console.log(this.inputArr[index]); // 访问当前行的输入框引用
  },
  toEdit(row, index) {
    // 切换到编辑模式
    row.flag = true;
    this.$nextTick(() => {
      // 使用 this.inputArr[index] 来访问和操作输入框
      this.inputArr[index]?.focus(); // 示例:使输入框获取焦点
    });
  }
}

在上面的示例中,inputArr 数组中存储了每个 <el-input> 的引用,你可以在方法中通过 this.inputArr[index] 访问到特定的输入框实例并进行操作。

  • 修改业务更多使用到了浅拷贝的东西,把id带过来了
Object.assign(attrParams, JSON.parse(JSON.stringify(row)))

路由销毁

在 Vue.js 中,路由跳转是否销毁组件取决于具体的情况和配置。一般来说,Vue Router 会根据路由的变化来决定是否销毁组件。以下是一些情况和相关的说明:

1. 路由跳转

  • 普通路由跳转:当你通过 Vue Router 进行路由跳转时,如果目标路由组件不同于当前路由组件,Vue 会销毁当前组件并创建目标组件。这是因为 Vue Router 默认会销毁和创建组件来反映路由的变化。

  • 组件缓存:如果你使用了 <keep-alive> 组件来包裹路由组件,那么在路由跳转时,组件不会被销毁,而是会被缓存起来。这样,组件的状态和数据会被保留,用户返回到之前的路由时,可以恢复之前的状态。

    <keep-alive>
      <router-view></router-view>
    </keep-alive>
    

2. beforeDestroydestroyed 生命周期钩子

  • 当组件被销毁时,会触发 beforeDestroydestroyed 生命周期钩子。你可以在这些钩子中执行一些清理操作,例如移除事件监听器、清理定时器等。

    beforeDestroy() {
      // 在组件销毁之前执行的代码
    },
    destroyed() {
      // 组件已销毁,执行清理操作
    }
    

3. 动态组件

  • 如果你使用了动态组件(例如 component 元素)并通过 :is 指令进行切换,那么组件会被销毁和重新创建,类似于路由跳转的行为。

    <component :is="currentComponent"></component>
    

4. router-view 的使用

  • <router-view> 组件会根据路由的变化来渲染相应的组件。路由跳转时,<router-view> 会卸载当前的组件并加载新组件。

如果你希望在路由跳转时保留组件的状态,可以考虑使用 keep-alive,或者在路由切换时将数据存储到 Vuex 或其他状态管理方案中。

如果你有具体的情况或代码片段,欢迎分享,我可以提供更详细的帮助。

在 Vue 3 中,onUnmounted 是一个生命周期钩子,用于在组件卸载时执行一些清理操作。你的代码片段 onUnmounted(() => { categoryStore.$reset() }) 是在组件卸载时重置 categoryStore 的状态。

categoryStore.$reset()

  • onUnmounted:这是一个 Vue 3 的 Composition API 生命周期钩子,它在组件从 DOM 中卸载时被调用。用于在组件销毁时执行清理任务,如取消订阅、清理定时器、重置状态等。

  • categoryStore.$reset():这是你在 onUnmounted 钩子中调用的一个方法。假设 categoryStore 是你在 Pinia 中定义的一个 store,$reset() 方法用于重置 store 的状态到初始值。这是确保当组件卸载时,store 的状态被恢复到默认状态的好方法。

使用场景

使用 onUnmounted 钩子来重置 store 状态是一个好的做法,特别是在以下情况下:

  1. 组件状态清理:当组件卸载时,你希望将相关的 store 状态恢复到初始状态,以避免旧数据影响新数据。

  2. 内存泄漏:如果组件使用了定时器、事件监听器等资源,确保在组件卸载时进行清理,以避免内存泄漏。

示例

下面是一个使用 onUnmounted 钩子的完整示例,其中 categoryStore 是一个 Pinia store,$reset 方法用于重置其状态:

import { defineComponent, onUnmounted } from 'vue';
import { useCategoryStore } from '@/stores/categoryStore'; // 假设这是你的 Pinia store

export default defineComponent({
  setup() {
    const categoryStore = useCategoryStore();

    // 在组件卸载时重置 store 状态
    onUnmounted(() => {
      categoryStore.$reset();
    });

    return {
      categoryStore
    };
  }
});

注意事项

  • store 的设计:确保你的 Pinia store 有一个 $reset 方法或其他方法可以用来重置状态。否则,你需要自定义重置逻辑。

  • 依赖清理:除了重置 store 状态,你还需要确保其他依赖(如事件监听器、网络请求等)也在组件卸载时被适当清理。

caregory组件

  • 把一二三级分类存储到category的仓库中,三个下拉框都是一样的数据,只是最后请求的结果要根据不同的组件解决,例如在spu组件内watch监听
//监听三级分类ID变化
watch(
  () => categoryStore.c3Id,
  () => {
    //当三级分类发生变化的时候清空对应的数据
    records.value = []
    //务必保证有三级分类ID
    if (!categoryStore.c3Id) return
    getHasSpu()
  }
)

在attr中

watch(
  () => categoryStore.c3Id,
  () => {
    attrArr.value = []
    //获取分类的ID
    console.log('获得c3')
    if (categoryStore.c3Id !== '') {
      getAttr()
    }
  }
)

这样就可以填完三个下拉框,使用不同的接口.

Spu模块

SPU(Standard Product Unit):标准化产品单元。是商品信息聚合的最小单位,是一组可复用、易检索的标准化信息的集合,该集合描述了一个产品的特性。通俗点讲,属性值、特性相同的商品就可以称为一个SPU。

在这里插入图片描述
spu就是有产品名字和属性,但是属性还没有值.

  • spu的属性值已经设定好了,但是还没有选择.

在这里插入图片描述

sku则是有属性值
sku就是选择属性的值.

修改和添加的页面是差不多的。页面1的四个地方都需要发请求拿数据,我们在这一部分分别编写4个部分的API以及ts类型
在这里插入图片描述

在这里插入图片描述

  • 因为没有这个四个的数据,进去详细页面需要再次请求;

权限

用户管理里面设置角色, 角色里面可以设置菜单权限.

在这里插入图片描述

用户管理

在这里插入图片描述

  • 添加用户的时候没有id.可以区分
//收集用户信息的响应式数据
let userParams = reactive<User>({
  username: '',
  name: '',
  password: ''
})
  • 修改用户的时候,有id.
          <el-button type="primary" size="small" icon="Edit" @click="updateUser(row)">编辑</el-button>
          --------------------
const updateUser = data => {
  drawer.value = true
  Object.assign(userParams, data)
}
  • 拿到row之后给data.

更换了本账号就会退出

因为后端已经没有这个用户,token找不到这个用户,所以在前端做好处理token出现问题的时候.就是注销登录状态.

多选框

在这里插入图片描述

 <el-form-item label="职位列表">
          <!-- v-model="checkAll" 控制是否确认全选    -->
          <!-- handleCheckAllChange 全选了把数据给userRole -->
          <!-- isIndeterminate 不确定的样式(确定样式) -->
          <el-checkbox @change="handleCheckAllChange"
           v-model="checkAll" :indeterminate="isIndeterminate">全选</el-checkbox>
          <!-- 显示职位的的复选框 -->

          <el-checkbox-group v-model="userRole" @change="handleCheckedCitiesChange">
          <!-- 负责渲染的列表 -->
          <!-- label是选择后的放到userRole中,   @change有改变选择框的话里面有监控改变全选款的样式.   -->
            <el-checkbox v-for="(role, index) in allRole" :key="index" :label="role">{{ role.roleName
                        }}</el-checkbox>
          </el-checkbox-group>

        </el-form-item>
//确定按钮的回调(分配职位)
const confirmClick = async () => {
  //收集参数
  let data: SetRoleData = {
    userId: userParams.id as number,
    roleIdList: userRole.value.map(item => {
      return item.id as number
    })
  }
  //分配用户的职位
  let result: any = await reqSetUserRole(data)
  if (result.code == 200) {
    //提示信息
    ElMessage({ type: 'success', message: '分配职务成功' })
    //关闭抽屉
    drawer1.value = false
    //获取更新完毕用户的信息,更新完毕留在当前页
    getHasUser(pageNo.value)
  }
}
  • 在数据库是一个用户表 一个角色(职位)表 一个多对多表记录两个的数据.
<template>
  <el-table
    ref="multipleTableRef"
    :data="tableData"
    style="width: 100%"
    @selection-change="handleSelectionChange"
  >
    <el-table-column type="selection" width="55" />
    <el-table-column label="Date" width="120">
      <template #default="scope">{{ scope.row.date }}</template>
    </el-table-column>
    <el-table-column property="name" label="Name" width="120" />
    <el-table-column property="address" label="Address" />
  </el-table>
  <div style="margin-top: 20px">
    <el-button @click="toggleSelection([tableData[1], tableData[2]])">
      Toggle selection status of second and third rows
    </el-button>
    <el-button @click="toggleSelection()">Clear selection</el-button>
  </div>
</template>

<script lang="ts" setup>
import { ref } from 'vue'
import { ElTable } from 'element-plus'

interface User {
  date: string
  name: string
  address: string
}

const multipleTableRef = ref<InstanceType<typeof ElTable>>()
const multipleSelection = ref<User[]>([])
const toggleSelection = (rows?: User[]) => {
  if (rows) {
    rows.forEach((row) => {
      // TODO: improvement typing when refactor table
      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
      // @ts-expect-error
      multipleTableRef.value!.toggleRowSelection(row, undefined)
    })
  } else {
    multipleTableRef.value!.clearSelection()
  }
}
const handleSelectionChange = (val: User[]) => {
  multipleSelection.value = val
}

const tableData: User[] = [
  {
    date: '2016-05-03',
    name: 'Tom',
    address: 'No. 189, Grove St, Los Angeles',
  },
  {
    date: '2016-05-02',
    name: 'Tom',
    address: 'No. 189, Grove St, Los Angeles',
  },
  {
    date: '2016-05-04',
    name: 'Tom',
    address: 'No. 189, Grove St, Los Angeles',
  },
  {
    date: '2016-05-01',
    name: 'Tom',
    address: 'No. 189, Grove St, Los Angeles',
  },
  {
    date: '2016-05-08',
    name: 'Tom',
    address: 'No. 189, Grove St, Los Angeles',
  },
  {
    date: '2016-05-06',
    name: 'Tom',
    address: 'No. 189, Grove St, Los Angeles',
  },
  {
    date: '2016-05-07',
    name: 'Tom',
    address: 'No. 189, Grove St, Los Angeles',
  },
]
</script>
  • 在模板中,@selection-change=“handleSelectionChange” 事件监听器会捕获表格中选中行的变化,然后 handleSelectionChange 会将最新的选中行更新到 multipleSelection 变量中。
    因此,multipleSelection 是存储你勾选的表格行数据的变量。
//批量删除按钮的回调
const deleteSelectUser = async () => {
  //整理批量删除的参数
  const idsList: any = multipleSelection.value.map(item => {
    return item.id
  })
  //批量删除的请求
  const result: any = await reqSelectUser(idsList)
  if (result.code == 200) {
    ElMessage({ type: 'success', message: '删除成功' })
    getHasUser(userArr.value.length > 1 ? pageNo.value : pageNo.value - 1)
  }
}

角色管理(职位管理)

例如:后端 前端就是一种职业,我们需要控制他们的添加和修改.

  <el-dialog v-model="dialogVisite" :title="RoleParams.id?'修改职位' : '添加职位'">
    <el-form :model="RoleParams" :rules="rules" ref="form" label-width="100px">
      <el-form-item label="职位名称" prop="roleName">
        <el-input placeholder="请你输入职位名称" v-model="RoleParams.roleName"></el-input>
      </el-form-item>
    </el-form>
    <template #footer>
      <el-button type="primary" size="default" @click="dialogVisite = false">
        取消
      </el-button>
      <el-button type="primary" size="default" @click="save">确定</el-button>
    </template>
  </el-dialog>

树形结构

// 只有叶子结点可以被选中
const filterSelectArr = (allData: any, initArr: any) => {
  console.log('allData')

  console.log(allData)

  allData.forEach((item: any) => {
    if (item.select && item.level == 4) {
      initArr.push(item.id)
    }
    if (item.children && item.children.length > 0) {
      filterSelectArr(item.children, initArr)
    }
  })
  return initArr
}
  • 通过for循环得到更小的单位.

在这里插入图片描述

  • 树形结构需要每个节点都需要有id,用来标识每个节点
  • 依靠id来给节点勾选.
  • 在这里插入图片描述
    依靠最底层的的select字段来判断是否选中,从而加入选中的数组中

在这里插入图片描述

  • 角色与权限,还有多对多的表.
    在这里插入图片描述
  • 角色勾选权限,有多少个勾选就有多少个id.
   <el-popconfirm :title="`你确定要删除${row.roleName}?`" width="260px" @confirm="removeRole(row.id)">
            <template #reference>
              <el-button type="primary" size="small" icon="Delete">
                删除
              </el-button>
            </template>
          </el-popconfirm>
  • 在消息提示框中是@confim是执行方法.

树形结构

  • 根据角色id获得权限.
//已有的职位的数据
const setPermisstion = async (row: RoleData) => {
  console.log(row)
  //抽屉显示出来
  drawer.value = true
  //收集当前要分类权限的职位的数据
  Object.assign(RoleParams, row)
  //根据职位获取权限的数据
  // 获得角色的权限数据
  let result: MenuResponseData = await reqAllMenuList(RoleParams.id as number)
  console.log(result.data)

  if (result.code == 200) {
    menuArr.value = result.data
    selectArr.value = filterSelectArr(menuArr.value, [])
    console.log('selectArr')
    console.log(selectArr)
  }
}
  • menuArr获得所有菜单
  • selectArr获得勾选的数据
    传递过来的是勾选的.
  <template #default>
      <!-- 树形控件 -->
      <el-tree ref="tree" :data="menuArr" show-checkbox node-key="id" :default-checked-keys="selectArr" :props="defaultProps" />
    </template>
  • :default-checked-keys=“selectArr” 这是获得选择的.

保存的时候

const handler = async () => {
  //职位的ID
  const roleId = RoleParams.id as number

  //选中节点的ID
  let arr = tree.value.getCheckedKeys()
 
  let permissionId = arr 
  //下发权限
  let result: any = await reqSetPermisstion(roleId, permissionId)
  if (result.code == 200) {
    //抽屉关闭
    drawer.value = false
    //提示信息
    ElMessage({ type: 'success', message: '分配权限成功' })
    //页面刷新
    // window.location.reload()
  }
}
  • 保存的数据是多个id.
  • 获得选择的数据方法是 let arr = tree.value.getCheckedKeys(),之后进行保存.

菜单管理

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

  • name和code展示出来了

在这里插入图片描述

//添加菜单按钮的回调
const addPermisstion = (row: Permisstion) => {
  //清空数据
  Object.assign(menuData, {
    id: 0,
    code: '',
    level: 0,
    name: '',
    pid: 0,
  })
  //对话框显示出来
  dialogVisible.value = true
  //收集新增的菜单的level数值
  menuData.level = row.level + 1
  //给谁新增子菜单
  menuData.pid = row.id as number
}
//编辑已有的菜单
const updatePermisstion = (row: Permisstion) => {
  dialogVisible.value = true
  //点击修改按钮:收集已有的菜单的数据进行更新
  Object.assign(menuData, row)
}
  • 传入父亲id和自身level.还有code和name

重点! 实现权限管理

在这里插入图片描述

  • name在vue插件可以用,还有权限管理
  1. 一级路由和二级路由需要进行梳理和整理 分为静态路由和异步路由和任意路由
  • 静态路由
import { createRouter, createWebHashHistory } from "vue-router";
import routerData from "./router";
const router = createRouter({
  // 路由模式hash
  history: createWebHashHistory(),
  routes: routerData
});

export default router;

const routerData = [
  {
    // 登录成功后的页面
    path: "/",
    name: "layout",
    component: () => import("@/layout/index.vue"),
    redirect: "/home",
    meta: {
      title: "layout",
      icon: "Aim",
    },
    children: [{
      path: "/home",
      name: "Home",
      component: () => import("@/views/home/index.vue"),
      meta: {
        title: "首页1",
        icon: "Promotion",
      }
    }]
  },
  {
    // 登录
    path: "/login",
    name: "Login",
    component: () => import("@/views/login/index.vue"),
    meta: {
      title: "登录",
      icon: "InfoFilled",
      hidden: true
    }
  },
  {
    path: "/404",
    name: "404",
    component: () => import("@/views/404/index.vue"),
    meta: {
      title: "404",
      icon: "Share",
      hidden: true  //true隐藏  false显示
    }
  },
  {
    path: "/:pathMatch(.*)*",
    redirect: "/404",
    name: "Any",
    meta: {
      title: "任意路由",
      icon: "el-icon-s-home",
      hidden: true
    }
  },
   ....
 

  }
]
export default routerData;

  • 这些都是静态路由.

如果把有些路由放到异步路由中所有用户都访问不到.

在这里插入图片描述

  • 一个用户登录返回职位和他的职位对应的权限. 还有异步路由信息.

  • 用户表 (users): 记录用户基本信息。
  • 角色表 (roles): 定义不同的角色,比如“管理员”、“后端工程师”等。
  • 权限表 (permissions): 定义具体的权限点,比如可以访问哪些路由,执行哪些操作。
  • 角色-权限关联表 (role_permissions): 用于存储每个角色有哪些权限。
  • 用户-角色关联表 (user_roles): 记录每个用户拥有哪些角色。

权限表
在这里插入图片描述
在这里插入图片描述

  • 每个占据一个id,还有用户管理
  • 有code和name.

按钮权限与路由权限的关系

按钮权限(如 btn.User.add, btn.Role.assign)决定了用户能在某个页面上执行哪些操作。因此,如果用户拥有该页面上的按钮权限,就推断用户应该能够访问该页面。因此,按钮权限的存在可以推断页面路由的访问权限。

  • 9
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@追求卓越

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值