Vue+TS+Element-plus项目

2023-10-31

目录

后台管理系统实现

1. 项目搭建                             

1.创建项目

 2.启动项目

3.搭建第三方库element-plus

1.安装

2.完整引入

3.按需导入

2.登录页面

1.下载插件

2.配置路由

3.样式配置

4.设置背景图片

5.表单展示

6.使用ts对数据类型限制

7.封装axios和重置

1.安装

2.封装axios

8.登录的逻辑实现

3.首页

1.首页的头部编写

1.首页布局

2.头部布局 

2.侧边栏的基本样式

3.侧边栏的动态路由

1.创建Goods.vue页面

2.写入Home.vue的子路由中

3.生成动态路由

 4.创建子路由:用户列表User.vue

5.把title渲染到侧边栏

6.index改为动态颜色

7.开启路由模式

8.右边路由内容展示

4.商品列表子路由

1.商品列表的表单搜索页面

2.展示res中的数据

1.复制Form表单

2.规范数据

3.使用

4.外界就可以使用selectData的属性

3.商品列表的数据展示

4.分页处理

1.切割

2.改为显示5页

5.查询功能

1.删除复原

6.封装函数,进行优化

5.用户列表子路由

1.获取用户列表数据

2.定义用户列表的数据类型

3.编写用户列表内容

4.用户列表的查询实现

5.用户列表的编辑页面

1.编辑栏的样式

2.user.ts添加

3.编辑页面数据的获取

 4.编辑功能的实现

6.角色列表

1.创建Role.vue,在router/index.ts添加Role子路由     

2.角色列表的类型规范

3.写数据

4.页面的编写

5.添加角色功能

6.创建Authority.vue权限列表

7.点击修改权限跳转页面

8.Role.vue修改跳转

9.获取路由参数

10.接收authority和id

11.获取数据

12.定义类型

13.接收数据

14.修改功能

7.解决侧边栏刷新后,无高亮

8.解决进入首页,空白

9.路由守卫

10.退出登录


后台管理系统实现

1. 项目搭建                             

1.创建项目

 2.启动项目

cd vue3-ts-demo
npm run serve

3.搭建第三方库element-plus

1.安装

 NPM
npm install element-plus --save

安装 | Element Plus

2.完整引入

如果你对打包后的文件大小不是很在乎,那么使用完整导入会更方便。

// main.ts
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'

//createApp(App).use(router).use(ElementPlus).mount('#app')
// 如果报错的话,设置类型为any
const app:any=createApp(App)
app.use(router).use(ElementPlus).mount('#app')

  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any  关闭报错即可

 //package.json
"rules": {
     "@typescript-eslint/no-explicit-any":["off"]
    }

3.按需导入

您需要使用额外的插件来导入要使用的组件。

自动导入推荐

1.首先你需要安装unplugin-vue-components 和 unplugin-auto-import这两款插件

npm install -D unplugin-vue-components unplugin-auto-import

2.然后把下列代码插入到你的 Vite 或 Webpack 的配置文件中

Webpack--->可以在vue.config.js中添加webpack.config.js即可

const { defineConfig } = require('@vue/cli-service')
const AutoImport = require('unplugin-auto-import/webpack')
const Components = require('unplugin-vue-components/webpack')
const { ElementPlusResolver } = require('unplugin-vue-components/resolvers')

module.exports = defineConfig({
  transpileDependencies: true,
  configureWebpack:{//配置的是webpack的内容
    plugins: [
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [ElementPlusResolver()],
    }),
  ],
  }
})
//main.ts
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

createApp(App).use(router).mount('#app')

修改配置了之后,要重启项目

角色列表消息框出现问题,使用完整引入方法

2.登录页面

1.下载插件

自动生成Vue3代码:Vue VSCode Snippets(下载失败:使用管理员运行vscode)

 输入vb即可快速生成模板

 

2.配置路由

router/index.ts

{
    path: '/login',
    name: 'login',
    component: () => import('../views/LoginView.vue'),
  },

3.样式配置

App.vue

<template>
  <div>
    <router-view />
  </div>
</template>

<style lang="scss">
* {
  margin: 0;
  padding: 0;
}
html,
body,
#app {
  width: 100%;
  height: 100%;
}
</style>

4.设置背景图片

<template>
  <div class="login-box"></div>
</template>

<script lang="ts">
import { defineComponent } from "vue";

export default defineComponent({
  setup() {
    return {};
  },
});
</script>

<style lang='scss' scoped>
.login-box {
  width: 100%;
  height: 100vh; //100%没有显示图片
  background: url("../assets/bg.jpg");
  background-size: 100% 100%;
  background-repeat: no-repeat;
  background-attachment: fixed;
}
</style>

5.表单展示

//Login.vue
<script lang="ts">
import { defineComponent, reactive, toRefs } from "vue";

export default defineComponent({
  setup() {
    const data = reactive({
      ruleForm: {
        username: "",
        password: "",
      },
      rules: {
        username: [
          {
            required: true,
            message: "请输入账号",
            trigger: "blur",
          },
          {
            min: 3,
            max: 10,
            message: "账号的长度在3-10之间",
            trigger: "blur",
          },
        ],
        password: [
          {
            required: true,
            message: "请输入密码",
            trigger: "blur",
          },
          {
            min: 3,
            max: 10,
            message: "密码的长度在3-10之间",
            trigger: "blur",
          },
        ],
      },
    });
    // 解构出来,就可以直接使用
    return { ...toRefs(data) };
  },
});
</script>
<template>
  <div class="login-box">
    <el-form
      ref="ruleFormRef"
      :model="ruleForm"
      status-icon
      :rules="rules"
      label-width="80px"
      class="demo-ruleForm"
    >
      <h2>后台管理系统</h2>
      <el-form-item label="账号:" prop="username">
        <el-input v-model="ruleForm.username" autocomplete="off" />
      </el-form-item>
      <el-form-item label="密码:" prop="password">
        <el-input
          v-model="ruleForm.password"
          type="password"
          autocomplete="off"
        />
      </el-form-item>
      <el-form-item>
        <el-button
          class="loginBtn"
          type="primary"
          @click="submitForm(ruleFormRef)"
          >登录</el-button
        >
        <el-button class="loginBtn" @click="resetForm(ruleFormRef)"
          >重置</el-button
        >
      </el-form-item>
    </el-form>
  </div>
</template>
<style lang='scss' scoped>
.login-box {
  width: 100%;
  height: 100vh; //100%没有显示图片
  background: url("../assets/bg.jpg");
  background-size: 100% 100%;
  background-repeat: no-repeat;
  background-attachment: fixed;
  text-align: center;
  // 外边距重叠问题
  // padding: 1px;
  overflow: hidden;
  .demo-ruleForm {
    width: 500px;
    margin: 200px auto;
    background-color: rgb(232, 250, 223);
    padding: 40px;
    border-radius: 20px;
  }
  .loginBtn {
    width: 48%;
  }
  h2 {
    margin-bottom: 10px;
  }
}
</style>

Form 表单 | Element Plus

Form 表单 | Element Plus

外边距重叠

样式下拉时,出现父样式随着子样式移动的情况,跟上面外边距一起了(外边距重叠)父元素受子元素影响。可以给使用父元素使用overflow:hidden;也可以给父元素设置padding一定距离。

6.使用ts对数据类型限制

type文件夹:规范整个项目里面所对应的数据的类型。

//src/type/login.ts
export interface LoginFormInt {
  username: string
  password: string
}
export class LoginData {
  ruleForm: LoginFormInt = {
    username:"",
    password:""
  }
}
//Login.vue
import {LoginData} from '../type/login'

export default defineComponent({
  setup() {
    const data = reactive(new LoginData());
    const rules={//rules是不会变的,可以拿出来
        username: [
          {
            required: true,
            message: "请输入账号",
            trigger: "blur",
          },
          {...
    return { ...toRefs(data),rules };
  }
})
//重置
    const resetForm = (formEl: FormInstance | undefined) => {
      if (!formEl) return
      data.ruleForm.username = "";
      data.ruleForm.password = "";
    };
    // data.ruleForm.username=1//不能将类型“number”分配给类型“string”。
  //  data.ruleForm.username=""//才可以(ts的类型推断)
    // 解构出来,就可以直接使用
    return { ...toRefs(data),rules,resetForm };

7.封装axios和重置

1.安装

npm install axios

2.封装axios

request/index.ts:封装axios的文件夹

import axios from 'axios'

// 创建axios实例
const service = axios.create({
  baseURL:
    'https://www.fastmock.site/mock/bf1fcb3c2e2945669c2c8d0ecb8009b8/api',
  timeout: 5000, //超时值
  headers: {
    //会返回一个token,需要把token保存到头部里面
    'Content-Type': 'application/json;charset=utf-8',
  },
})
// 请求拦截
service.interceptors.request.use((config) => {
  // headers没有,需要给它一个空的对象
  config.headers = config.headers || {}
  // 如果没有token,不用在headers加入token(确保它登录了);有就加token
  if (localStorage.getItem('token')) {
    config.headers.token = localStorage.getItem('token') || ''
  }
  return config
})

// 响应拦截
service.interceptors.response.use((res)=>{
  // 获取code,进行类型定义
  const code:number=res.data.code
  // 状态码code
  if(code!=200){
    // 信息错误
    return Promise.reject(res.data)
  }
  return res.data
},(err)=>{//打印错误信息
  console.log(err);
})

// 发送请求需要整个实例,把实例暴露出去
export default service

8.登录的逻辑实现

src/request/api.ts:针对发送的请求做处理。

  •  登录接口
  • 地址:/login
  • 方式:post
  • 参数:username && password
//app.ts
import service from '.'
interface LoginData {
  username: string
  password: string
}
//登录接口
// 给data做类型规范
export function login(data: LoginData) {
  // 返回service,通过axios实例发送请求
  return service({
    url: '/login',
    method: 'post',
    data,
  })
}

发送请求

import {  ref } from "vue";
import type { FormInstance } from 'element-plus'
import {login} from '../request/api'
import { useRouter } from "vue-router";

 // 登录
const ruleFormRef = ref<FormInstance>()
const router=useRouter()//-->Router
const submitForm = (formEl: FormInstance | undefined) => {
      if (!formEl) return;
           // 对表单内容进行验证
//valid布尔类型,为true表示验证成功,反之
      formEl.validate((valid) => {
        if (valid) {
          // console.log("submit!");
          login(data.ruleForm).then((res) => {
            console.log(res);
            // 将token进行保存
            localStorage.setItem("token", res.data.token);
            // 跳转页面,首页
            // $router.push vue2
            router.push("/");
          }); 
        } else {
          console.log("error submit!");
          return false;
        }
      });
      // console.log(formEl);
    };
return {ruleFormRef, submitForm};

3.首页

1.首页的头部编写

1.首页布局

 Container 布局容器 | Element Plus

2.头部布局 

Layout 布局 | Element Plus

//Home.vue
<template>
  <div class="home">
    <el-container>
      <el-header>
        <el-row :gutter="20">
          <el-col :span="4">
            <img src="../assets/logo.png" alt="" class="logo" />
          </el-col>
          <el-col :span="16">
            <h2>后台管理系统</h2>
          </el-col>
          <el-col :span="4"> <span class="quit-login">退出系统</span> </el-col>
        </el-row>
      </el-header>
      <el-container>
        <el-aside width="200px">Aside</el-aside>
        <el-main>Main</el-main>
      </el-container>
    </el-container>
  </div>
</template>

<style lang="scss" scoped>
.el-header {
  height: 80px;
  background-color: rgb(136, 220, 67);
  .logo {
    height: 80px;
  }
  h2,
  .quit-login {
    text-align: center;
    height: 80px;
    line-height: 80px;
    color: rgb(244, 244, 244);
  }
}
</style>

2.侧边栏的基本样式

 <!-- 侧边栏 -->
        <el-aside width="200px">
          <el-menu
            active-text-color="#ffd04b"
            background-color="#545c64"
            class="el-menu-vertical-demo"
            default-active="2"
            text-color="#fff"
          >
            <el-menu-item index="2">
              <span>商品列表</span>
            </el-menu-item>
          </el-menu>
        </el-aside>

.el-aside {
  .el-menu {
    // 拿到窗口到底部部分100vh-上面后台管理系统盒子高度80px
    height: calc(100vh - 80px);
  }
}

Menu 菜单 | Element Plus

3.侧边栏的动态路由

 根据侧边栏去渲染(展示)对应的内容,在右边区域。点击商品列表,展示商品列表的数据。

需要写Home.vue的子路由,因为只有当它是子路由的时候,才可以在当前页面里面去渲染另一个页面。

1.创建Goods.vue页面

2.写入Home.vue的子路由中

//src/router/index.ts
{
    path: '/',
    name: 'home',
    component: HomeView,
    children: [
      {
        path: 'goods',
        name: 'goods',
        // 懒加载
        component: () => import('../views/GoodsView.vue'),
      },
    ],
  },

3.生成动态路由

根据首页的子路由,去展示侧边栏的内容。

//src/router/index.ts
name: 'goods',
meta: {
    isShow: true,
},//根据meta来设置,有哪些子路由需要展示

 4.创建子路由:用户列表User.vue

//Home.vue
import { useRouter } from "vue-router";

 setup() {
    const router = useRouter();
    console.log(router.getRoutes()); //Array(5)
 //拿取到页面所有的路由,发现childern里的值是不确定的,会随着添加而添加,所以需要直接拿到isShow
   const list = router.getRoutes().filter((v) => v.meta.isShow); //filter不能在html模块用 vue3
   console.log(list);//Array(2)
//渲染页面,需要用到list
    return {list}
  },

//src/router/index.ts
 meta: {
          isShow: true,
          title:"商品列表"
        },
 meta: {
          isShow: true,
          title:"用户列表"

        },

5.把title渲染到侧边栏

 <el-menu-item index="2" v-for="item in list" :key="item.path">
              <span>{{item.meta.title}}</span>
            </el-menu-item>

6.index改为动态颜色

index为文字点击颜色。

7.开启路由模式

router:开启路由模式,通过el-menu-item 的index来进行跳转。点击哪一个路由,就会跳转到所对应的index路径去。

<el-menu
  router
  >
<el-menu-item :index="item.path" >
</el-menu-item>

8.右边路由内容展示

组件哪个地方需要展示出来,需要设置路由出口。

  <!-- 右边路由内容 -->
        <el-main>
          <router-view></router-view>
        </el-main>

4.商品列表子路由

1.商品列表的表单搜索页面

  • 商品列表接口
  • 地址:/getGoodsList
  • 方式:get
src/request/api.ts
// 商品列表接口
export function getGoodsList(){
  return service({
    url:"/getGoodsList",
    method:"get"
  })
}

执行整个函数getGoodsList,发送请求

import { getGoodsList } from "../request/api";
export default defineComponent({
  setup() {
    getGoodsList().then((res) => {
      console.log(res);
    });

2.展示res中的数据

1.复制Form表单

//Goods.vue
<template>
  <div>
    <div class="select-box">
      <el-form :inline="true" :model="formInline" class="demo-form-inline">
        <el-form-item label="标题">
          <el-input v-model="formInline.user" placeholder="请输入关键字" />
        </el-form-item>
        <el-form-item label="详情">
          <el-input v-model="formInline.user" placeholder="请输入关键字" />

        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="onSubmit">查询</el-button>
        </el-form-item>
      </el-form>
    </div>
  </div>
</template>

Form 表单 | Element Plus

2.规范数据

//src/type/goods.ts
// 定义接口
export interface ListInt {
  userId: number
  id: number
  title: string
  introduce: string
}
interface selectDataInt {
  title: string
  introduce: string
  page: number //页码
  count: number //总条数
  pagesize: number //默认一页显示几条
}
// 定义class类
export class InitData {
  // selectData针对搜索表单去定义的
  selectData: selectDataInt = {
    title:"",
    introduce:"",
    page:1,
    count:0,
    pagesize:10//设置默认展示10条数据
  }
  list: ListInt[] = [] //展示的内容的数据,接收后台返回的数据
}

3.使用

//GoodsView.vue
import { defineComponent, reactive, toRefs } from "vue";
//引入
import {InitData} from '../type/goods'
//实例化
const data =reactive(new InitData())
//返回data
return {...toRefs(data)};

4.外界就可以使用selectData的属性

      <el-form :inline="true" :model="selectData" class="demo-form-inline">
        <el-form-item label="标题">
          <el-input v-model="selectData.title" placeholder="请输入关键字" />
        </el-form-item>
        <el-form-item label="详情">
          <el-input v-model="selectData.introduce" placeholder="请输入关键字" />
        </el-form-item>
      </el-form>

3.商品列表的数据展示

//Goods.vue
<el-table :data="list" border style="width: 100%">
      <el-table-column prop="id" label="ID" width="180" />
      <el-table-column prop="title" label="标题" width="180" />
      <el-table-column prop="introduce" label="详情" />
</el-table>

 getGoodsList().then((res) => {
      // console.log(res);
      // data.list接受后台返回过来的数据
      data.list = res.data;
    });

Table 表格 | Element Plus

4.分页处理

<el-pagination layout="prev, pager, next" :total="selectData.count" />

 getGoodsList().then((res) => {
     data.selectData.count=res.data.length // 赋值总条数
    });

Pagination 分页 | Element Plus

1.切割

<el-table :data="dataList.comList" border style="width: 100%">

import { computed} from "vue";

  // list会随着currentChange,sizeChange而更改
    const dataList = reactive({
      comList: computed(() => {
        // 切割
        // 1-- [1-10]
        // 2-- [11-20]
        // 3-- [21-30]
        // (slice从0开始的(截取下标),page从1开始的)
        // 第一条-1 * 展示的页数
        return data.list.slice(
          (data.selectData.page - 1) * data.selectData.pagesize, //page=1-->0,page=2-->10
          data.selectData.page * data.selectData.pagesize
        ); //page=1-->10,page=2-->20
        // 包头不包尾,0-9,10-19
      }),
    });
    const currentChange = (page: number) => {
      data.selectData.page = page;
    };
    const sizeChange = (pagesize: number) => {
      data.selectData.pagesize = pagesize;
    };
    return { currentChange, sizeChange,dataList };

点击第n页的时候,触发currentChange事件,把当前的page传过来,传过来后把page赋值给 data.selectData.page,这个page就和要展示的数据有关系,(computed:当依赖值发生改变时,属性comList就会发生改变)

Pagination 分页 | Element Plus

2.改为显示5页

//Goods.vue
<el-pagination
      :total="selectData.count*2" //总页数*2
    />

//type/goods.ts
    pagesize: 5, //设置默认展示10条数据

5.查询功能

import { InitData,ListInt } from "../type/goods";

//查询
 const onSubmit = () => {
      // console.log(data.selectData.title);
      // console.log(data.selectData.introduce);
      let arr: ListInt[] = []; //定义数组,用来接收查询过后的要展示的数据
      // 将查询的数据,赋值给它
      if (data.selectData.title || data.selectData.introduce) {
        //判断两个是否其中一个有值
        if (data.selectData.title) {
          // value:数组list里的每一个对象
          arr = data.list.filter((value) => {//将过滤出来的数组赋值给arr
            // 查找输入进来的关键字,不等于-1,说明有这个关键字
            return value.title.indexOf(data.selectData.title)!==-1;
          });
        } 
        if (data.selectData.introduce) {
          arr = data.list.filter((value) => {
            return value.introduce.indexOf(data.selectData.introduce)!==-1;
          });
        }
      }else{
        // 输入为空,让arr等于原本数组即可
        arr=data.list
      }
      data.selectData.count=arr.length//分页总数改变
      // 过滤之后的数组
      data.list=arr
    };
    return { onSubmit };

arr过滤之后的数组,赋值给list,data.list.slice发生改变,comList也就发生改变,:data="dataList.comList"就发生改变了。分页的总数也要发生改变。

1.删除复原

删除搜索后,需要恢复原状。监听

import { watch } from "vue";
//监听多个值,以数组形式存在
 // 监听输入框的两个属性
    watch(
      [() => data.selectData.title, () => data.selectData.introduce],
      () => {
        // 只有属性发生改变,进行判断
        if (data.selectData.title == "" && data.selectData.introduce == "") {
          // 为空,则重新赋值(1.重新获取数组);2.也可以重新定义数组,去接收它
          getGoodsList().then((res) => {
            data.list = res.data; //赋值数据
            data.selectData.count = res.data.length; // 赋值总条数
          });
        }
      }
    );

6.封装函数,进行优化

原先为直接调用,优化为在生命周期去调用,获取数据。

 onMounted(() => {
      getGoods()
    });
    //  封装到一个函数里面
const getGoods = () => {
      getGoodsList().then((res) => {
        // console.log(res);
        // data.list接受后台返回过来的数据
        data.list = res.data; //赋值数据
        data.selectData.count = res.data.length; // 赋值总条数
      });
    };
watch(
      [() => data.selectData.title, () => data.selectData.introduce],
      () => {
        // 只有属性发生改变,进行判断
        if (data.selectData.title == "" && data.selectData.introduce == "") {
          // 为空,则重新赋值(1.重新获取数组);2.也可以重新定义数组,去接收它
             getGoods()
        }
      }
    );

5.用户列表子路由

1.获取用户列表数据

  • 用户列表接口
  • 地址:/getUserList
  • 方式:get
  • 角色列表接口
  • 地址:/getRoleList
  • 方式:get
//request/api.ts
// 用户列表接口
export function getUserList() {
  return service({
    url: '/getUserList',
    method: 'get',
  })
}
// 角色列表接口
export function getRoleList() {
  return service({
    url: '/getRoleList',
    method: 'get',
  })
}
import { defineComponent, onMounted } from "vue";
import { getUserList, getRoleList } from "../request/api";
export default defineComponent({
  setup() {
    onMounted(() => {
      getUser();
      getRole();
    });
    const getUser = () => {
      getUserList().then((res) => {
        console.log(res);
      });
    };
    const getRole = () => {
      getRoleList().then((res) => {
        console.log(res);
      });
    };
    return {};
  },
});

2.定义用户列表的数据类型

用接口来规范对象。

//src/type/user.ts
//id: 1
//nickName: "小明"
//role: (2) [{…}, {…}]
//userName: "小明"

export interface ListInt {
  id: number
  nickName: string
  role: RoleInt[]
  userName: string
}

// 为role写的接口
interface RoleInt {
  role: number
  roleName: string
}

// 查询,通过role选择管理员
interface SelectDataInt {
  role: number
  nickName: string
}
interface RoleListInt {
  authority: number[]
  roleId: number
  roleName: string
}
// 写类,得到一个对象
export class InitData {
  selectData: SelectDataInt = {
    nickName: '',
    role: 0,
  }
  list: ListInt[] = [] //接收用户信息的列表
  roleList: RoleListInt[] = [] // 接收角色信息的列表
}

3.编写用户列表内容

//User.vue
import {InitData} from '../type/user'

const data=reactive(new InitData())

return {...toRefs(data)};

 角色:选择器选择时管理员还是普通用户。

import { defineComponent, onMounted, reactive, toRefs } from "vue";
import { getUserList, getRoleList } from "../request/api";

  setup() {
    const data = reactive(new InitData());
    onMounted(() => {
      getUser();
      getRole();
    });
    const getUser = () => {
      getUserList().then((res) => {
        console.log(res);
         data.list=res.data
      });
    };
    const getRole = () => {
      getRoleList().then((res) => {
        console.log(res);
        data.roleList=res.data
      });
    };
  },
<div class="select-box">
      <el-form :inline="true" :model="selectData" class="demo-form-inline">
        <el-form-item label="姓名">
          <el-input v-model="selectData.nickName" placeholder="请输入姓名" />
        </el-form-item>
        <el-form-item label="角色">
          <el-select
            v-model="selectData.role"
            class="m-2"
            placeholder="请选择"
            size="large"
          >
            <!-- 提供默认的,如果为0是显示 -->
            <el-option label="全部" :value="0" />
            <el-option
              v-for="item in roleList"
              :key="item.roleId"
              :label="item.roleName"
              :value="item.roleId"
            />
            <!-- 根据value来决定显示的是管理员还是普通用户 -->
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="onSubmit">查询</el-button>
        </el-form-item>
      </el-form>
    </div>
    <el-table :data="list" border style="width: 100%">
      <el-table-column prop="id" label="ID" width="180" />
      <el-table-column prop="nickName" label="姓名" width="180" />
      <!-- role:数组里面的内容进行展示,使用插槽 -->
      <el-table-column prop="role" label="角色">
        <!-- 展示管理员还是用户信息 -->
        <template #default="scope">
          <el-button
            v-for="item in scope.row.role"
            :key="item.role"
           // type="text"//vue3不支持,使用link
           link
            size="small"
          >
            {{ item.roleName }}
          </el-button>
        </template>
      </el-table-column>
    </el-table>

了解scope

<el-button type="primary" size="small" @click="deleteRow(scope)">
            Remove
</el-button>
const deleteRow = (row: any) => {
      console.log(row);
    };
return { deleteRow };

 有选择器

Select 选择器 | Element Plus

有插槽

Table 表格 | Element Plus

4.用户列表的查询实现

User查询跟Goods的查询相似,复制过来进行更改即可。

import { InitData,ListInt } from "../type/user";
import { defineComponent, onMounted, reactive, toRefs, watch } from "vue";

 // 查询
    const onSubmit = () => {
      // console.log(data.selectData.role);
      // console.log(data.selectData.nickName);
      let arr: ListInt[] = []; //定义数组,用来接收查询过后的要展示的数据
      // 将查询的数据,赋值给它
      if (data.selectData.nickName || data.selectData.role) {
        //判断两个是否其中一个有值
        if (data.selectData.nickName) {
          // value:数组list里的每一个对象
          arr = data.list.filter((value) => {
            //将过滤出来的数组赋值给arr
            // 查找输入进来的关键字,不等于-1,说明有这个关键字
            return value.nickName.indexOf(data.selectData.nickName) !== -1;
          });
        }
        // 输入小明,又要有管理员,即两个都要成立才行,所以这里过滤的值,是上面已经过滤过的值
        if (data.selectData.role) {
          arr = (data.selectData.nickName ? arr : data.list).filter((value) => {
            // 将过滤出来的数组赋值给arr
            // 将role跟选择器中的role进行对比,如果相等就返回出去
            return value.role.find(
              (item) => item.role === data.selectData.role
            );
          });
        }
      } else {
        // 输入为空,让arr等于原本数组即可
        arr = data.list;
      }
      // 过滤之后的数组
      data.list = arr;
    };
    // 监听输入框的两个属性
    watch([() => data.selectData.nickName, () => data.selectData.role], () => {
      // 只有属性发生改变,进行判断
      if (data.selectData.nickName == "" || data.selectData.role == 0) {
        getUser();
      }
    });
    return { ...toRefs(data), deleteRow, onSubmit };

5.用户列表的编辑页面

1.编辑栏的样式

   <el-table :data="list" border style="width: 100%">
      <el-table-column prop="id" label="ID" width="180" />
      <el-table-column prop="nickName" label="姓名" width="180" />
      <!-- role:数组里面的内容进行展示,使用插槽 -->
      <el-table-column prop="role" label="角色">
        <!-- 展示管理员还是用户信息 -->
        <template #default="scope">
          <el-button
            v-for="item in scope.row.role"
            :key="item.role"
            link
            size="small"
          >
            {{ item.roleName }}
          </el-button>
        </template>
      </el-table-column>
      <el-table-column prop="role" label="操作">
        <template #default="scope">
          <el-button
            link
            type="primary"
            size="small"
            @click="changeUser(scope.row)"
          >
            编辑
          </el-button>
        </template>
      </el-table-column>
    </el-table>
  </div>
 <el-dialog v-model="isShow" title="编辑信息">
    <el-form :model="active">
      <el-form-item label="姓名" label-width="50px">
        <el-input v-model="active.nickName" autocomplete="off" />
      </el-form-item>
      <el-form-item label="角色" label-width="50px">
        <el-select multiple v-model="active.role" placeholder="请选择角色">
          <el-option
            v-for="item in roleList"
            :key="item.roleId"
            :label="item.roleName"
            :value="item.roleId"
          />
        </el-select>
      </el-form-item>
    </el-form>
    <template #footer>
      <span class="dialog-footer">
        <el-button @click="dialogFormVisible = false">Cancel</el-button>
        <el-button type="primary" @click="dialogFormVisible = false"
          >Confirm</el-button
        >
      </span>
    </template>
  </el-dialog>

Dialog 对话框 | Element Plus

2.user.ts添加

// 接收名字和角色
interface ActiveInt {
  id: number
  nickName: string
  // [1,2]
  role: number[]
  userName: string
}
export class InitData {
  isShow = false
  active: ActiveInt = {
    //选中的对象
    id: 0,
    nickName: '',
    role: [],
    userName: '',
  }

3.编辑页面数据的获取

 const changeUser = (row: ListInt) => {
      console.log(row);
      data.active = {
        id: row.id,
        nickName: row.nickName,
        userName: row.userName,
        role: row.role.map((value) => value.role),
      };
      data.isShow = true;
    };
    return { changeUser };

 4.编辑功能的实现

  const changeUser = (row: ListInt) => {
        role: row.role.map((value: any) => value.role || value.roleId),
      };
      data.isShow = true;
    };
    const updateUser = () => {
      // console.log(data.active);
      let obj: any = data.list.find((value) => value.id == data.active.id);
      obj.nickName = data.active.nickName;
      // data.active.role-->[1,2],选中管理员和用户
      // roleList-->roleId-->1,2  既选中管理员又用户.这里有1,上面才会有1.
      obj.role = data.roleList.filter(
        (value) => data.active.role.indexOf(value.roleId) !== -1
      );
      console.log(obj.role); //改变之后的值拿到了
      // 将选中的内容进行赋值
      data.list.forEach((item, i) => {
        if (item.id == obj.id) {
          data.list[i] = obj;
        }
      });
      data.isShow = false;
    };
    return { changeUser, updateUser };

实际开发时,点击更改是会调用数据库里面的值,进行更改的。这里是更改的渲染。

6.角色列表

1.创建Role.vue,在router/index.ts添加Role子路由     

 {
        path: '/role',
        name: 'role',
        meta: {
          isShow: true,
          title: '角色列表',
        },
        component: () => import('../views/RoleView.vue'),
      },
// 角色列表接口
export function getRoleList() {
  return service({
    url: '/getRoleList',
    method: 'get',
  })
}
import { getRoleList } from "@/request/api";
export default defineComponent({
  setup() {
    onMounted(() => {
      getRoleList().then((res) => {
        console.log(res);
      });
    });

    return {};
  },
});

2.角色列表的类型规范

role.ts

export interface ListInt{
  authority:number[],
  roleId:number,
  roleName:string
}

export class InitData{
  list:ListInt[]=[]
}

3.写数据

import { defineComponent, onMounted, reactive } from "vue";
import { getRoleList } from "@/request/api";
import { InitData } from "@/type/role";
export default defineComponent({
  setup() {
    const data=reactive(new InitData())
    onMounted(() => {
      getRoleList().then((res) => {
        console.log(res);
        data.list=res.data

      });
    });

    return {...toRefs(data) };
  },
});

4.页面的编写

<template>
  <div>
    <el-form :inline="true" class="demo-form-inline">
      <el-form-item>
        <el-button type="primary" @click="addRole">添加角色</el-button>
      </el-form-item>
    </el-form>

    <el-table :data="list" border style="width: 100%">
      <el-table-column prop="roleId" label="ID" width="180" />
      <el-table-column prop="roleName" label="角色名" width="180" />
      <el-table-column prop="role" label="操作">
        <template #default="scope">
          <el-button link size="small" @click="changeRole(scope.row)">
            修改权限
          </el-button>
        </template>
      </el-table-column>
    </el-table>
  </div>
</template>

5.添加角色功能

import { ElMessage, ElMessageBox } from "element-plus";
export default defineComponent({
  setup() {
    const addRole = () => {
      ElMessageBox.prompt("请输入角色名称", "添加", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
      })
        .then(({ value }) => {
          //value表示你在输入框中填写的值
          if (value) {
            //判断输入框中有值,就将对应的值添加到列表中
            // 数组的长度会发生改变,
            data.list.push({
              roleId: data.list.length + 1,
              roleName: value,
              authority: [],
            });
          }
          ElMessage({
            type: "success",
            message: `${value}角色添加成功`,
          });
        })
        .catch(() => {
          ElMessage({
            type: "info",
            message: "取消操作",
          });
        });
    };

    return { ...toRefs(data), addRole };
  },
});

弹框出错,改用全局引入element-plus,无问题

MessageBox 消息弹框 | Element Plus

6.创建Authority.vue权限列表

是首页的子路由。

{
        path: '/authority',
        name: 'authority',
        meta: {
          isShow: false,
          title: '权限列表',
        },
        component: () => import('../views/AuthorityView.vue'),
      },

7.点击修改权限跳转页面

import { InitData, ListInt } from "../type/role";
import { useRouter } from "vue-router";

const router=useRouter()
const changeRole = (row: ListInt) => {
      router.push({
        path: "authority",
        query: {
          id: row.roleId,
          // 把数组变成字符串,给它分隔开
          authority: row.authority.join(','),
        },
      });
    };
    return { changeRole };
http://localhost:8080/authority?id=1&authority=1,2,4,5,6,7,8,9,11,13,14,15,16

8.Role.vue修改跳转

const changeRole = (row: ListInt) => {
      router.push({
        name: "authority", //name跟path后面的值设的是一样的
        params: {
          id: row.roleId,
          authority: row.authority,
        },
      });
    };

9.获取路由参数

// 当前活跃的路由,路径显示的是哪一个,通过这个函数去获取,就可以得到哪一个对象
import { useRoute } from "vue-router";
export default defineComponent({
  setup() {
    const route = useRoute();
    console.log(route);

    return {};
  },
});

10.接收authority和id

authority.ts

export class InitData{
  id:number
  authority:number[]
  // 赋值,这两个是从params中来的,需要进行传参
  constructor(id:number,authority:number[]){
    this.id=id
    this.authority=authority
  }
}
import { defineComponent, reactive, toRefs } from "vue";
// 当前活跃的路由,路径显示的是哪一个,通过这个函数去获取,就可以得到哪一个对象
import { useRoute } from "vue-router";
import { InitData } from "../type/authority";
export default defineComponent({
  setup() {
    const route = useRoute();
    console.log(route);
    // 不知道传过来的属性是什么,所以使用any
    const params: any = route.params;
    const data = reactive(new InitData(params.id, params.authority));
    return { ...toRefs(data) };
  },
});
  <el-tree
    :data="data"
    show-checkbox
    node-key="id"
    :default-expanded-keys="[2, 3]"
    :default-checked-keys="[5]"
    :props="defaultProps"
  />

Tree 树形控件 | Element Plus

  • 权限列表接口
  • 地址:/getAuthorityList
  • 方式:get

 api.ts

// 权限列表接口
export function getAuthorityList() {
  return service({
    url: '/getAuthorityList',
    method: 'get',
  })
}

11.获取数据

import { getAuthorityList } from "../request/api";
 onMounted(() => {
      getAuthorityList().then((res) => {
        console.log(res);
      });
    });

12.定义类型

export interface ListInt {
  name: string
  roleId: number
  // 是可选的?
  roleList?: ListInt[]
  viewRole?: string
}
export class InitData {
  list:ListInt[]=[]
}

13.接收数据

 onMounted(() => {
      getAuthorityList().then((res) => {
        console.log(res);
        data.list=res.data
      });
    });

父级勾选上了,子级就会自动勾选上,这里不需要,即设置它们不互相关联

 <el-tree
      :data="list"
      show-checkbox
      node-key="roleId"
      :check-strictly="true"
      :default-checked-keys="authority"
      :props="{
        children: 'roleList',
        label: 'name',
      }"
    />

Tree 树形控件 | Element Plus

14.修改功能

定义Ref,可以通过Ref去获取DOM的注册信息

//authority.ts
 treeRef:any

//Authority.vue
<el-tree
    ref="treeRef"
 <el-button @click="changeAuthority">确认修改</el-button>
 const changeAuthority = () => {
      console.log(data.treeRef);
      console.log(data.treeRef.getCheckedKeys());
    };
    return { changeAuthority };

 获取选中的状态,可以拿到拿一下是选中的

 

  // 获取升序
      console.log(data.treeRef.getCheckedKeys().sort(function(a:number,b:number){return a-b}));
  // 传给后台,后台去进行修改

7.解决侧边栏刷新后,无高亮

 <el-menu
            :default-active="active"
          >
import { useRouter,useRoute } from "vue-router";
 setup() {
    const route = useRoute();
    return { active:route.path };
  },

8.解决进入首页,空白

const routes: Array<RouteRecordRaw> = [
  {
    path: '/',
    name: 'home',
    redirect:"goods",//设置重定向
  }
]

9.路由守卫

如果没有登录,需要登录后才能进入详情页面。设置全局路由守卫

//src/router/index.ts
router.beforeEach((to, from, next) => {
  // 给token定义类型,登录了的话为string,未登录则是null
  // 从localStorge里进行获取
  const token: string | null = localStorage.getItem('token')
  // 如果没有token,跳转到登录页面去;去往的路径本来就是登录页面的话,不需要跳转,直接进入token页面。
  // 如果去往的页面不是token页面,并且没有token;则让他跳转到token页面
  if (!token && to.path !== '/login') {
    next('/login')
  }
  // 如果有token,则让它next就可以了
  else {
    next()
  }
})

10.退出登录

将HomeView.vue退出换成按钮形式

<el-col :span="4"  class="col-token">
  <el-button @click="delToken">退出登录</el-button>
</el-col>

const delToken =()=>{
      // 删除token
      localStorage.removeItem('token')
      // 跳转到登陆页面去
      router.push('/login')
    }

return { delToken };

 .col-token {
    height: 80px;
    line-height: 80px;
  }

https://gitee.com/gru77/vue-ts-demo.git

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Vue+TS+Element-plus项目 的相关文章

  • 从 thymeleaf 获取数据到模态引导程序、jquery

    我正在尝试获取模态视图的 id 这是为了更新 onclick 元素 但我找不到方法 知道如何为 boostrap 5 完成此操作 或我可以用其他方法吗 谢谢 tr a inactivate a div class modal fade mo
  • 如何检测浏览器是否支持自定义元素

    我正在查看 Modernizr 它应该有助于功能检测 这应该可以帮助确定您的网站是否与给定的 Web 浏览器兼容 但我没有看到任何表明我可以使用它来检测自定义 HTML 的内容我们在内容中创建和定义的元素 如果不是 Modernizr 我如
  • 在 javascript/jquery 中将光标更改为等待

    当调用函数时 如何让光标更改为此加载图标以及如何将其更改回 javascript jquery 中的普通光标 在你的 jQuery 中使用 body css cursor progress 然后又恢复正常 body css cursor d
  • 为什么 JavaScript base-36 转换看起来不明确

    我目前正在编写一段使用 Base 36 编码的 JavaScript 我遇到了这个问题 parseInt welcomeback 36 toString 36 看来要回归了 welcomebacg 我在 Chrome 开发者控制台和 Nod
  • TypeError: props.render 不是一个函数(React hook 形式)

    我将方法作为我用react hook form制作的形式的道具传递 当从react hook form添加控制器时 它给了我 TypeError props render不是一个函数 我在网上找不到任何解决方案 因此感谢任何帮助 impor
  • 如何重置使用 JavaScript 更改的 CSS 属性?

    我的导航按钮的宽度从 100px 增加到 150px 当鼠标悬停在 nav li hover width 150px 但是使用 javascript 我已经做到了 无论选择哪个选项 宽度都将继续为 150px 当选择每个选项时 它会使其他选
  • 使用 useReducers 调度函数发送多个操作?

    使用时是否可以通过调度函数发送多个动作useReducer挂钩反应 我尝试向它传递一组操作 但这会引发未处理的运行时异常 明确地说 通常会有一个初始状态对象和一个减速器 如下所示 const initialState message1 nu
  • 检查 JavaScript 字符串是否为 URL

    JavaScript 有没有办法检查字符串是否是 URL 正则表达式被排除在外 因为 URL 很可能是这样写的stackoverflow 也就是说它可能没有 com www or http 如果你想检查一个字符串是否是有效的 HTTP UR
  • 在requestAnimationFrame中使用clearRect不显示动画

    我正在尝试在 HTML5 画布上做一个简单的 javascript 动画 现在我的画布是分层的 这样当我收到鼠标事件时 背景层不会改变 但带有头像的顶层会移动 如果我使用 requestAnimationFrame 并且不清除屏幕 我会看到
  • MVC 在布局代码之前执行视图代码并破坏我的脚本顺序

    我正在尝试将所有 javascript 包含内容移至页面底部 我正在将 MVC 与 Razor 一起使用 我编写了一个辅助方法来注册脚本 它按注册顺序保留脚本 并排除重复的内容 Html RegisterScript scripts som
  • 跟踪用户何时点击浏览器上的后退按钮

    是否可以检测用户何时单击浏览器的后退按钮 我有一个 Ajax 应用程序 如果我可以检测到用户何时单击后退按钮 我可以显示适当的数据 任何使用 PHP JavaScript 的解决方案都是优选的 任何语言的解决方案都可以 只需要我可以翻译成
  • Babel 7 Jest Core JS“TypeError:wks不是函数”

    将我的项目升级到 Babel 7 后 通过 Jest 运行测试会抛出以下错误 测试在 Babel 6 中运行没有任何问题 但在 Babel 7 中失败并出现以下错误 TypeError wks is not a function at Ob
  • Angular 2+ 安全性;保护服务器上的延迟加载模块

    我有一个 Angular 2 应用程序 用户可以在其中输入个人数据 该数据在应用程序的另一部分进行分析 该部分仅适用于具有特定权限的人员 问题是我们不想让未经授权的人知道how我们正在分析这些数据 因此 如果他们能够在应用程序中查看模板 那
  • 为什么在 Internet Explorer 中访问 localStorage 对象会引发错误?

    我正在解决一个客户端问题 Modernizr 意外地没有检测到对localStorageInternet Explorer 9 中的对象 我的页面正确使用 HTML 5 文档类型 并且开发人员工具报告该页面具有 IE9 的浏览器模式和 IE
  • 模块构建失败(来自 ./node_modules/babel-loader/lib/index.js)Vue Js

    我从 GitHub 下载了一个我和我的朋友正在开发的项目 但是当我尝试运行时 npm run serve 我收到这个错误 src main js 中的错误 Module build failed from node modules babe
  • Javascript转换时区问题

    我在转换当前时区的日期时间时遇到问题 我从服务器收到此日期字符串 格式为 2015 10 09T08 00 00 这是中部时间 但是当我使用 GMT 5 中的 new Date strDate 转换此日期时间时 它返回给我的信息如下 这是不
  • 条件在反应本机生产中失败,但在开发中有效

    我创建了一个反应本机应用程序 我需要通过它进行比较 如果属实 就会执行死刑 问题是 该条件适用于 React Native 开发模式 而不适用于 React Native 生产版本 我使用 firebase 作为数据库 也使用 redux
  • 如何仅在最后一个
  • 处给出透明六边形角度?
  • 我必须制作这样的菜单 替代文本 http shup com Shup 330421 1104422739 My Desktop png http shup com Shup 330421 1104422739 My Desktop png
  • 导致回发到与弹出窗口不同的页面

    我有一个主页和一个详细信息页面 详细信息页面是从主页调用的 JavaScript 弹出窗口 当单击详细信息页面上的 保存 按钮时 我希望主页 刷新 是否有一种方法可以调用主页的回发 同时还可以从详细信息页面维护保存回发 Edit 使用win
  • 如何从图像输入中获取 xy 坐标?

    我有一个输入设置为图像类型

随机推荐

  • Spark GC overhead limit exceeded

    1 在运行spark 代码时 抛出错误 18 03 24 08 52 00 WARN server TransportChannelHandler Exception in connection from 192 168 200 164 3
  • linux下TCP连接的client和server

    linux下TCP连接的client和server http blog 163 com caipeipei love 126 blog static 2596603220101118433940 基于TCP连接的client和server简
  • 英飞凌 AURIX TC3XX 系列单片机的 SOTA 功能实现

    1 前言 通过前一章了解到了 AURIX TC3XX 系列单片机的 SOTA 功能 下面讲述如何实现 SOTA 功能 以 TC37X 为例 附完整代码实现 在实现 SOTA 功能前 有必要简单了解一下 UCB 全称 User Configu
  • winidows下安装pytorch报PackageNotFoundError:cudatoolkit错误的解决方法

    今天给新电脑装pytorch的时候查到MX450驱动的CUDA版本是11 1 于是兴冲冲跑去pytorch官网找到安装命令准备安装 pytorch官网告诉我命令是这个 conda install pytorch torchvision to
  • 在AIX系统下搭建一个全新的weblogic服务器

    weblogic服务器作为付费服务器 在各个行业中的使用还是相当广泛的 尤其在金融行业 使用的很多都是weblogic服务器 毕竟 有钱任性 那么在工作的时候肯定会有小伙伴遇到过要自己搭建weblogic服务器的情况 这里整理下本人搭建we
  • Echarts—词云库(echarts-wordcloud)配置详解和使用(可自定义形状)

    词云库的详解 前言 安装 基本配置详解 具体使用步骤 Vue为例 自定义展示形状 前言 我们经常会看到一些网站或者页面有一堆五颜六色的词汇的聚在一块 有大有小的散落着 看着挺好看的 也许项目中也会涉及到显示一些关键词之类的需求 这个时候也可
  • springboot整合eureka

    服务端 1 maven依赖 注意springboot和springcloud的版本对应
  • 三层交换机配置静态路由

    一 建立拓扑图 二 配置主机IP地址 网关 主机号 IP地址 网关 PC 0 192 168 10 101 192 168 10 1 PC 1 192 168 20 101 192 168 20 1 PC 2 192 168 30 101
  • c语言实现的最简单log debug

    我们在些简单的c原因程序时 如果打印log 用专用的log不划算 这个时候可以采用下面简单的log Name debug h Purpose general debug system Copyright C 2014 wowotech Su
  • 指标体系、原子指标和衍生指标

    指标 是一个可以量化目标事物多少的数值 有时候也称为度量 如 DNU 留存率等都是指标 原子指标和衍生指标 按照个人的理解 不加任何修饰词的指标就是原子指标 也叫度量 一般存在于olap表中 例如订单量 用户量的等等 而在原子指标上进行加减
  • 运放电流检测采样电路电压采样电路

    输入输出电压检测 输入输出电压通过运放LMC6482采用差分电路将输出电压按比例缩小至ADC能够采样的范围 再使用ADC采样 软件解算出输出电压 输入电压采样是通过MCU内部运放按比例缩小在送到ADC进行采样的 具体电路如图3 5 1所示
  • R数据处理包plyr:超越apply函数族的向量化运算

    R有着强大而又丰富的数据处理能力 除了一些常用的基础数据处理函数之外 R还为我们提供了大量以实现不同的数据处理功能的扩展包 关注小编公众号的朋友应该还记得之前曾写过一篇关于R向量化运算的 apply函数族的文章 对于日常数据处理工作而言 可
  • flask模块mock接口(二)

    目录 一 获取请求传入数据 二 服务端回话保持 1 通过cookie实现回话保持 2 通过session实现回话保持 一 获取请求传入数据 1 模块 from flask import request 2 方法 method 获取客户端提交
  • NG Model

    组件传值双向绑定 output绑定事件 由组件绑定事件EventEmitter向父组件传输信息 属性名 属性后缀Change 是约定的固定写法 child component html h1 status in child childSta
  • LESS命令简单介绍以及使用

    LESS命令简单介绍以及使用 http www cnblogs com molao doing articles 6541455 html b 缓冲区大小 设置缓冲区的大小 e 当文件显示结束后 自动离开 f 强迫打开特殊文件 例如外围设备
  • 微积分的前世今生

    参考链接 你也能懂的微积分 微积分 顾名思义 简单来说可以分为微分和积分 下面先说说积分 简单来说 积分是用来求面积的 毕竟积分的 积 和面积的 积 是同一个字 而 分 可以理解为方法 所以积分就是用来求面积的 参看百度百科的定义 也是这个
  • (三)Python3 NLTK(Natural Language Toolkit)安装和下载的常见问题

    NLTK Python自然语言工具包 用于诸如标记化 词形还原 词干化 解析 POS标注等任务 该库具有几乎所有NLP任务的工具 1 安装nltk pip install nltk 不要像一开始我一样傻傻的以为pip完就结束啦 2 进入py
  • Spring Security升级到5.7.x

    Spring Security升级到5 7 x 问题描述 WebSecurityConfigurerAdapter类是Spring Security中经常使用到的一个类 用于快速配置WebSecurity 在升级到5 7版本后这个类被废弃掉
  • Java 中如何避免循环引用,解决相互依赖的问题

    Java 中如何避免循环引用 解决相互依赖的问题 返回数据存在 r e f ref ref data 的问题 FastJSON
  • Vue+TS+Element-plus项目

    目录 后台管理系统实现 1 项目搭建 1 创建项目 2 启动项目 3 搭建第三方库element plus 1 安装 2 完整引入 3 按需导入 2 登录页面 1 下载插件 2 配置路由 3 样式配置 4 设置背景图片 5 表单展示 6 使