vue-ts-demo

devtools/2024/11/15 6:05:29/

 npm i -g @vue/cli

PS D:\kwai\vue3\project> vue create vue3-te-demo

element-plus

一个 Vue 3 UI 框架 | Element Plus

https://element-plus.org/zh-CN/guide/installation.html

安装:

npm install element-plus --save

完整引入使用:

使用:

按需引入使用

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

shell

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

修改vue.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:{plugins: [AutoImport({resolvers: [ElementPlusResolver()],}),Components({resolvers: [ElementPlusResolver()],}),],}
})

如果报错:RROR  TypeError: AutoImport is not a function TypeError,则需要降版本:

ERROR TypeError: AutoImport is not a function TypeError: AutoImport is not a-CSDN博客

使用:

重启项目

快速生成模版快捷键

登录页面

表单:

解决外边距塌陷:

css解决外边距塌陷的7种方法_css外边距塌陷-CSDN博客

 内容:

<template><div class="login-box"><el-formref="ruleFormRef":model="ruleForm"status-icon:rules="rules"label-width="80px"class="demo-ruleForm"><h2>后台管理系统</h2><el-form-item label="姓名" prop="name"><el-input v-model="ruleForm.name"  type="name" autocomplete="off" /></el-form-item><el-form-item label="密码" prop="pass"><el-input v-model="ruleForm.pass"  type="password" autocomplete="off" /></el-form-item><el-form-item><el-button  class="login-btn" type="primary" @click="submitForm(ruleFormRef)">登录</el-button><el-button class="login-btn" @click="resetForm(ruleFormRef)">重置</el-button></el-form-item></el-form></div>
</template><script setup lang="ts">
import { reactive, ref } from 'vue'
import type { FormInstance, FormRules } from 'element-plus'const ruleFormRef = ref<FormInstance>()const rules = reactive<FormRules>({name: [{required: true,trigger: 'blur',},{min: 3,max: 5,message: 'Length should be 3 to 5',trigger: 'blur',},],pass: [{required: true,trigger: 'blur',},{min: 3,max: 5,message: 'Length should be 6 to 8',trigger: 'blur',},]
});const ruleForm = reactive({pass: '',name: '',
})const submitForm = (formEl: FormInstance | undefined) => {if (!formEl) returnformEl.validate((valid) => {if (valid) {console.log('submit!')} else {console.log('error submit!')return false}})
}const resetForm = (formEl: FormInstance | undefined) => {if (!formEl) returnformEl.resetFields()
}
</script><style scoped  lang="scss">.login-box{width: 100%;height: 100%;background:url("../assets/bg.png");padding: 1px;text-align: center;.demo-ruleForm{width: 500px;background-color: white;margin:100px auto;padding: 20px;border-radius: 20px;}.login-btn{width: 40%;}h2{margin-bottom: 16px;}}
</style>

使用ts对数据类型进行限制

新建一个文件夹type用于数据存放

export interface LoginForm{pass:string,name:string,
}export class LoginData{ruleForm:LoginForm={pass:"",name:""}
}

在之前对pass name的数据定义中,并没有规范类型

在定义类型之后:双向绑定

登录请求

axios中文网|axios API 中文文档 | axios

 npm install axios --save-dev  

新建 request/index.ts    使用index.ts的好处

https://juejin.cn/post/7221004205271646245

index.ts

import axios from "axios";const mockType = 'cbzMock'  // fastMock
const baseURL = mockType === 'cbzMock' ? 'https://mock.presstime.cn/mock/63569fbbbee0a00099ca48a1/api/vue-ts-mall-demo' : 'https://www.fastmock.site/mock/bf1fcb3c2e2945669c2c8d0ecb8009b8/api'
//创建axios实例
const service = axios.create({baseURL: baseURL,timeout: 5000,headers: {"Content-type" : "application/json;charset=utf-8"}
})//请求拦截
service.interceptors.request.use((config) => {config.headers = config.headers || {}if(localStorage.getItem("token")){config.headers.token = localStorage.getItem("token") || ""}return config
})//响应拦截
service.interceptors.response.use(({ data }) => {const code : number = data.data.codeif(code != 200){return Promise.reject(data)}return data
},(err) => {console.log(err)
})export default service

api.ts

import service from "@/request/index";
import {LoginData} from "@/type/login";// 登录接口
export function login(data: LoginData) {return service({url: "/login",method: "POST",data})
}// 商品列表接口
export function getGoodsList(){return service({url: "/getGoodsList",method: "GET"})
}// 用户列表接口
export function getUserList(){return service({url: "/getUserList",method: "GET"})
}// 角色列表接口
export function getRoleList(){return service({url: "/getRoleList",method: "GET"})
}// 权限列表接口
export function getAuthorityList(){return service({url: "/getAuthorityList",method: "GET"})
}

登录操作

const submitForm = (formEl: FormInstance | undefined) => {if (!formEl) returnformEl.validate((valid) => {if (valid) {console.log('submit!')//假装成功!login(ruleForm).then( (res)=>console.log(res) ).catch( (err)=>{ console.log(err)//登录成功,切换到主页面router.replace({ name:'mainview'});});} else {console.log('error submit!')return false}})
}

主页面


 

布局容器

https://element-plus.org/zh-CN/component/container.html

header 

<template><div>main view</div><div class="common-layout"><el-container><el-header><el-row :gutter="20"><el-col :span="4">   <img src="../assets/logo.png" alt="" class="icon-logo">  <div class="grid-content ep-bg-purple" /></el-col><el-col :span="16"> <h2 class="title">后台管理系统</h2> <div class="grid-content ep-bg-purple" /></el-col><el-col :span="4"> <span class="quit">退出登录</span>  <div class="grid-content ep-bg-purple" /></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><script setup lang="ts"></script><style scoped  lang="scss">
.el-header{height: 80px;background: #666;.icon-logo{height: 80px;}.title ,.quit{text-align: center;height: 80px;line-height: 80px;}}
</style>

 height 和line-height 的设置https://www.cnblogs.com/pwindy/p/13026176.html

实现上下居中的对齐,如果不设置line-height ,。则会居上

Aside

如何实现侧边栏一直到底部:

<template><div>main view</div><div class="common-layout"><el-container><el-header><el-row :gutter="20"><el-col :span="4">   <img src="../assets/logo.png" alt="" class="icon-logo">  <div class="grid-content ep-bg-purple" /></el-col><el-col :span="16"> <h2 class="title">后台管理系统</h2> <div class="grid-content ep-bg-purple" /></el-col><el-col :span="4"> <span class="quit">退出登录</span>  <div class="grid-content ep-bg-purple" /></el-col></el-row></el-header><el-container><el-aside width="200px"><el-col :span="120"><el-menuactive-text-color="#ffd04b"background-color="#545c64"class="el-menu-vertical-demo"default-active="2"text-color="#fff"><el-menu-item index="2"><el-icon><icon-menu /></el-icon><span>商品列表</span></el-menu-item></el-menu></el-col></el-aside><el-main>Main</el-main></el-container></el-container></div></template><script setup lang="ts">
import {Menu as IconMenu,Setting,
} from '@element-plus/icons-vue'
const handleOpen = (key: string, keyPath: string[]) => {console.log(key, keyPath)
}
const handleClose = (key: string, keyPath: string[]) => {console.log(key, keyPath)
}
</script><style scoped  lang="scss">
.el-header{height: 80px;background: #666;.icon-logo{height: 80px;}.title ,.quit{text-align: center;height: 80px;line-height: 80px;}
}.el-aside{.el-menu{height: calc(100vh - 80px);}}
</style>

可以通过计算:100vh即 100%的垂直视口 - header高度80px

.el-aside{.el-menu{height: calc(100vh - 80px);}}

动态路由

<template><div class="common-layout"><el-container><el-header><el-row :gutter="20"><el-col :span="4">   <img src="../assets/logo.png" alt="" class="icon-logo">  <div class="grid-content ep-bg-purple" /></el-col><el-col :span="16"> <h2 class="title">后台管理系统</h2> <div class="grid-content ep-bg-purple" /></el-col><el-col :span="4"> <span class="quit">退出登录</span>  <div class="grid-content ep-bg-purple" /></el-col></el-row></el-header><el-container><el-aside width="120px"><el-menuactive-text-color="#ffd04b"background-color="#545c64"class="el-menu-vertical-demo"default-active="2"text-color="#fff"router><!--router开启路由模式 可以通过标签的index 进行跳转--><el-menu-item :index="item.path"    v-for="item in subRouterList" :key="item.path"><span>{{item.meta.title}}</span></el-menu-item></el-menu></el-aside><!--设置路由出口--><el-main>  <router-view></router-view></el-main></el-container></el-container></div></template><script setup lang="ts">
import {
} from '@element-plus/icons-vue'import { useRouter } from 'vue-router';
const router = useRouter();
const subRouterList = router.getRoutes().filter( v=>
v.meta.isShow)
console.log(subRouterList)</script><style scoped  lang="scss">
.el-header{height: 80px;background: #666;.icon-logo{height: 80px;}.title ,.quit{text-align: center;height: 80px;line-height: 80px;}
}.el-aside{.el-menu{height: calc(100vh - 80px);}
}</style>

子路由设置:children

import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import GoodsView from '@/views/GoodsView.vue'
import LoginView from '../views/LoginView.vue'
import MainView from '../views/MainView.vue'
import UserView from '@/views/UserView.vue'
const routes: Array<RouteRecordRaw> = [{path: '/mainview',name: 'mainview',component: MainView,children:[{path:'goods',name:'goods',meta:{isShow:true,title:"商品列表"},component:GoodsView},{path:'user',name:'user',meta:{isShow:true,title:"用户列表"},component:UserView}]},{path: '/login',name: 'login',component:LoginView}
]const router = createRouter({history: createWebHistory(process.env.BASE_URL),routes
})export default router

商品页面

搜索

列表

分页展示

列表数据的获取:

数据定义:

export interface Goods{id:number,userId:number,title:string,introduce:string,
}interface SelectData{id:number,userId:number,title:string,introduce:string,curPage:number,count:number,pageSize:number,
}export class GoodsPages{// 被选择的数据, 查询时用selected_data:SelectData ={        userId: 0,id: 0,title: "",introduce: "",curPage: 1,count: 0,pageSize: 10};// 展示的商品数据goods_list: Goods[] = []
}

数据获取:

<script setup lang="ts">
import { computed, onMounted } from 'vue';
import { getGoodsList } from '@/request/api';
import {GoodsPages } from '@/type/goods';
import { reactive } from 'vue'//定义数据
const goods_data = reactive(new GoodsPages())//当前页数据
const curPagaData = reactive(  {comList:computed( ()=>{return goods_data.goods_list.slice((goods_data.selected_data.curPage-1)*10,goods_data.selected_data.curPage*10);})} )onMounted(  ()=>{console.log('onMounted');//请求数据getGoodsList().then( (res)=>{   goods_data.goods_list = res.data.data;console.log('goodsList',goods_data.goods_list)     }).catch( (err)=>{console.log(err) })}
)</script>

分页展示逻辑:

<div>curPagaData.comList 是列表当前页展示的数据<el-table :data=curPagaData.comList border style="width: 100%"><el-table-column prop="id" label="id" width="180" />   使用列表中的字段与  每一列标题 <el-table-column prop="title" label="title" width="180" /><el-table-column prop="introduce" label="introduce" /></el-table>分页展示<el-pagination layout="prev, pager, next" v-model:page-size=goods_data.selected_data.pageSize      页面的大小:设置为10v-model:current-page=goods_data.selected_data.curPage     当前是第几页:total=goods_data.goods_list.length />   总页数:是请求到的列表的总数</div>

查询逻辑

    <div class="select-box"><el-form :inline="true" :model="goods_data.selected_data" class="demo-form-inline"><el-form-item label="用户Title"><el-input v-model="goods_data.selected_data.title" placeholder="用户Title" clearable /></el-form-item>  <el-form-item><el-button type="primary" @click="onSubmit">查询</el-button></el-form-item></el-form></div>//查询数据
const onSubmit = () => {console.log('submit! suc',goods_data.selected_data.title)//如果是空的,则使用原来的数据if (goods_data.selected_data.title === ''){goods_data.goods_list = last_data;goods_data.selected_data.curPage=1;return;}//过滤数据let filterArry:Goods[]=[];//过滤数据filterArry = goods_data.goods_list.filter( (value)=>{return value.title.indexOf(goods_data.selected_data.title) !== -1} );goods_data.goods_list = filterArry;goods_data.selected_data.curPage=1;
}onMounted(  ()=>{getGoodsList().then( (res)=>{   goods_data.goods_list = res.data.data;//记录当前的数据,用于恢复last_data = goods_data.goods_list;console.log('goodsList',goods_data.goods_list)     }).catch( (err)=>{console.log(err) })}
)

用户列表

展示

自定义列模版:展示用户角色

 <template #default="scope"><el-text class="mx-1"   v-for="item in scope.row.role"  :key="item.role"> {{item.roleName +" "}}</el-text>         

选择器 

数据定义:

/*
获取用户列表接口 GET  /getUserList
请求报文:无
响应报文:
{"data": {"code": 200,"data": [{"id": 1,"nickName": "小明","userName": "小明","role": [{"role": 1,"roleName": "管理员"},{"role": 2,"roleName": "普通用户"}]},{"id": 2,"nickName": "红红","userName": "红红","role": [{"role": 1,"roleName": "管理员"}]},{"id": 3,"nickName": "绿绿","userName": "绿绿","role": [{"role": 2,"roleName": "普通用户"}]}]}
}获取角色列表接口 GET  /getRoleList
请求报文:无
响应报文:
{"data": {"code": 200,"data": [{"roleName": "管理员","roleId": 1,"authority": [1,2,4,5,6,7,8,9,11,13,14,15,16]},{"roleName": "普通用户","roleId": 2,"authority": [1,3,4,6,7,8,9,11,12,13]}]}
}*/interface RoloInfo{role:number,roleName:string,
}interface UserInfo{id:number,nickName:string,userName:stringrole:RoloInfo[],
}interface RoleType{roleName:string,roleId:number,authority:Array<number>[],
}interface QueryUser{nickName: string,  // 用户别名role: number  // 角色编号
}export class UserPage{roleList:RoleType[]=[];userList:UserInfo[]=[];selectedData:QueryUser={nickName:'',role:0}
}

vue:

<template><div class="select-box"><el-form :inline="true" :model="userPage.selectedData" class="demo-form-inline"><el-form-item label="用户Title"><el-input v-model="userPage.selectedData.nickName" placeholder="用户nickName" clearable /></el-form-item> <el-form-item label="用户roleid"><el-select v-model="userPage.selectedData.role" placeholder="全部" style="width: 240px"><el-option:key="0"label="全部":value="0"/><el-optionv-for="item in userPage.roleList":key="item.roleId":label="item.roleName":value="item.roleId"/></el-select></el-form-item>           <el-form-item><el-button type="primary" @click="onSubmit">查询</el-button></el-form-item></el-form></div><div><el-table :data=userPage.userList border style="width: 100%"><el-table-column prop="id" label="编号" width="180" /><el-table-column prop="nickName" label="用户昵称" width="180" /><el-table-column prop="role" label="用户角色" ><template #default="scope"><el-text class="mx-1"   v-for="item in scope.row.role"  :key="item.role"> {{item.roleName +" "}}</el-text>             </template></el-table-column></el-table></div>
</template><script setup lang="ts">
import { getRoleList, getUserList } from '@/request/api';
import { UserInfo, UserPage } from '@/type/userinfo';
import { onMounted, reactive } from 'vue';const userPage = reactive(new UserPage())
let lastUserList:UserInfo[]=[];onMounted(()=>{getUserList().then((res)=>{userPage.userList = res.data.data;lastUserList =userPage.userList;console.log("userList",userPage.userList);})getRoleList().then((res)=>{userPage.roleList =res.data.data;console.log("roleList",userPage.roleList);})
})const onSubmit=()=>{console.log('onsubmit',userPage.selectedData.role,userPage.selectedData.nickName);if (userPage.selectedData.role===0&& userPage.selectedData.nickName===''){userPage.userList = lastUserList;return;}let list:UserInfo[]= userPage.userList.filter((value)=>{let found = false;if (userPage.selectedData.role===0){return value.nickName.indexOf(userPage.selectedData.nickName)!=-1;}if (userPage.selectedData.nickName===''){for(let i=0;i<value.role.length;++i){if (value.role[i].role === userPage.selectedData.role){return true;}}}if (value.nickName.indexOf(userPage.selectedData.nickName)!=-1){for(let i=0;i<value.role.length;++i){if (value.role[i].role === userPage.selectedData.role){return true;}}}return found;});userPage.userList = list;
}</script><style scoped></style>

编辑

编辑按钮

           <el-table-column prop="role" label="操作" ><template #default="scope"><el-button type="primary"  @click="handleEditUser(scope.row)">编辑</el-button></template> </el-table-column>

弹出编辑框

<!-- 编辑用户的弹出窗--><el-dialog v-model="userPage.editShow" title="编辑用户信息"><el-form :model="userPage.editUser"><el-form-item label="用户昵称" label-width="120px"><el-input v-model="userPage.editUser.nickName" autocomplete="off" /></el-form-item><el-form-item label="用户角色" label-width="120px"><el-select multiple v-model="userPage.editUser.role" class="m-2" size="large" placeholder="请选择角色"><el-optionv-for="item in userPage.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="userPage.editShow = false">取消</el-button><el-button type="primary" @click="ensureEditUser">修改</el-button></span></template></el-dialog>

数据结构

// 用户编辑接口
export interface UserEdit {id: number,  // 用户idnickName: string,  // 用户昵称role: number[],   // 用户角色userName: string  // 用户名
}export class UserPage{roleList:RoleType[]=[];userList:UserInfo[]=[];selectedData:QueryUser={nickName:'',role:0}editShow = false  // 是否显示编辑用户弹出窗// 编辑用户时用到的对象editUser: UserEdit = {id: 0,nickName: "",role: [],userName: ""}
}

// 编辑用户弹窗
const handleEditUser = (row: UserInfo) => {userPage.editShow = trueuserPage.editUser = {id: row.id,nickName: row.nickName,role: row.role.map((value) => value.role),userName: ""}
}const ensureEditUser = () => {console.log(userPage.editUser)userPage.editShow = false;let obj= userPage.userList.find( (value)=>{ value.id === userPage.editUser.id})for(let i=0;i<userPage.userList.length;++i){if (userPage.editUser.id === userPage.userList[i].id){userPage.userList[i].nickName= userPage.editUser.nickName;userPage.userList[i].role =[];for(let item of userPage.roleList){if (userPage.editUser.role.find( (value)=>( value === item.roleId) )){userPage.userList[i].role.push({role:item.roleId,roleName:item.roleName})}}}}console.log(userPage.userList)  
}

查询

let lastUserList:UserInfo[]=[];
const onSubmit=()=>{console.log('onsubmit',userPage.selectedData.role,userPage.selectedData.nickName);if (userPage.selectedData.role===0&& userPage.selectedData.nickName===''){userPage.userList = lastUserList;return;}let list:UserInfo[]= userPage.userList.filter((value)=>{let found = false;if (userPage.selectedData.role===0){return value.nickName.indexOf(userPage.selectedData.nickName)!=-1;}if (userPage.selectedData.nickName===''){for(let i=0;i<value.role.length;++i){if (value.role[i].role === userPage.selectedData.role){return true;}}}if (value.nickName.indexOf(userPage.selectedData.nickName)!=-1){for(let i=0;i<value.role.length;++i){if (value.role[i].role === userPage.selectedData.role){return true;}}}return found;});userPage.userList = list;
}

代码

通过百度网盘分享的文件:src.zip
链接:https://pan.baidu.com/s/1p9YR2W0-DuIZs1_UJwDh6g?pwd=kdhs 
提取码:kdhs


http://www.ppmy.cn/devtools/114099.html

相关文章

STM32 芯片启动过程

目录 一、前言二、STM32 的启动模式三、STM32 启动文件分析1、栈 Stack2、堆 Heap3、中断向量表 Vectors3.1 中断响应流程 4、复位程序 Reset_Handler5、中断服务函数6、用户堆栈初始化 四、STM32 启动流程分析1、初始化 SP、PC 及中断向量表2、设置系统时钟3、初始化堆栈并进入…

判断变量是否为有限数字(非无穷大或NaN)math.isfinite() 判断变量是否为无穷大(正无穷大或负无穷大)math.isinf()

【小白从小学Python、C、Java】 【考研初试复试毕业设计】 【Python基础AI数据分析】 判断变量是否为有限数字&#xff08;非无穷大或NaN&#xff09; math.isfinite() 判断变量是否为无穷大&#xff08;正无穷大或负无穷大&#xff09; math.isinf() 请问关于以下代码表述错误…

qt信号与槽(自定义)

自定义信号与槽 在qt里&#xff0c;我们可以自己去定义信号与槽。 这里举个栗子&#xff1a; 信号的定义 在我们类里边定义一个信号&#xff0c;我们需要用signals&#xff1a;来声明&#xff0c;不用再去cpp文件里边定义。而且返回值必须是void&#xff0c;可以有参数。 槽…

构建自己的文生图工具:Python + Stable Diffusion + CUDA

构建自己的文生图工具&#xff1a;Python Stable Diffusion CUDA 前言概述环境搭建安装PyTorch安装Stable Diffusion编写Python代码结论结语 前言 在这个数字化和人工智能飞速发展的时代&#xff0c;图像生成技术正逐渐成为现实。想象一下&#xff0c;只需输入几个关键词&…

ffmpeg批量图片格式转换

参考别的地方的弄的 echo off::在下方设置要处理的视频或图片 set Ext*.png,*.bmpmd outputecho 开始视频转换::在下方设置输出格式&#xff0c;这里输出为mp4&#xff0c;可自行更改 for %%a in (%Ext%) do (echo 正在转换&#xff1a;%%a"../ffmpeg.exe" -logleve…

驱动开发知识点

裸机开发 ——————————————linux驱动 SOC&#xff1a; 定义&#xff1a;SOC&#xff0c;全称System on Chip&#xff0c;是一种集成了多个功能模块的芯片&#xff0c;包括处理器、内存、外设、接口等。它将原本分散在多个芯片上的功能集成到一个芯片上&#xff0…

Redis 底层数据结构,一文详解

Redis 底层用 C 语言实现&#xff0c;不同版本的数据类型使用的数据结构也不同&#xff0c;下面详细看 SDS 字符串在 Redis 中很常用&#xff0c;键值对的所有键都是字符串&#xff0c;值有时候也是字符串 Redis 是用 C 语言实现的&#xff0c;但是字符串没有直接用 C 语言的…

Photoshop使用方法大全

0、快捷键 1&#xff09;Alt Delete&#xff1a;填充前景色 2&#xff09;Ctrl D&#xff1a;取消选区 3&#xff09;Ctrl T&#xff1a;自由变换&#xff0c;等比缩放、旋转的效果 4&#xff09;Ctrl R&#xff1a;显示标尺 5&#xff09;Ctrl J&#xff1a;复制一个…