前端笔记2025
前端
与后端交互
下载后端接口的文件时,若是二进制,需要在请求中添加responseType: ‘blob’
例如
axios.get(‘http://127.0.0.1:8612/api/daily/report/tdjzxz?selectedMonth=2022-06’, {
headers: {
‘Accesstoken’: ‘f033b94655f84386a0c112b41a82583b’
},
responseType: ‘blob’
})
.then(res => {
console.log(res)
const blob = new Blob([res.data], { type: res.headers[‘content-type’] })
const a = document.createElement(‘a’)
const url = window.URL.createObjectURL(blob)
a.href = url
a.download = ‘file.xlsx’
document.body.appendChild(a)
a.click()
window.URL.revokeObjectURL(url)
document.body.removeChild(a)
})
.catch(error => {
console.log(error)
})
原因
在Vue中,使用get请求时,可以选择是否带上responseType参数。如果不带上该参数,则默认返回的是JSON格式的数据。如果带上参数responseType: ‘blob’,则返回的是二进制数据。
区别在于:
1.返回的数据格式不同:不带参数时返回JSON格式的数据,带参数时返回二进制数据。
2.处理方式不同:对于JSON格式的数据,可以直接使用Vue提供的方法进行解析和处理,而对于二进制数据,则需要使用其他方式进行处理,例如将其转换为Blob对象或者使用FileReader进行读取。
3.适用场景不同:如果需要下载文件或者获取图片等二进制数据,使用带参数的方式比较合适;如果需要获取JSON格式的数据,则使用不带参数的方式即可。
在前后端分离的项目中,后端使用了springboot,前端使用了vue,现在有一个下载的需求,下载正常时二进制数据,不正常是包含提醒信息的json数据,前端应该怎么区分处理正常与不正常的数据?
可以在后端在返回数据时设置一个特定的HTTP响应头,例如 Content-Type: application/json ,来指示返回的数据是JSON格式的。在前端中,可以检查响应头来判断数据的类型。如果响应头为 Content-Type: application/json ,则可以将响应体解析为JSON格式的数据,并根据数据中的字段进行相应的处理。如果响应头为其他类型,则可以将响应体视为二进制数据进行处理。
另外,如果后端返回的数据在不正常情况下包含提醒信息,可以在JSON数据中添加一个特定的字段,例如 error_message ,来指示错误信息。在前端中,可以检查返回的JSON数据是否包含该字段,如果包含,则可以将其显示为提醒信息。
如果前端在请求中添加了 responseType: ‘blob’ 参数,但是后端返回的数据是JSON格式的,那么前端需要将响应体转换为JSON格式的数据类型,才能对其进行处理。以下是一种可能的解决方案:
axios.get(‘/api/download’, {
responseType: ‘blob’
}).then(response => {
if (response.data.type === ‘application/json’) {
// 如果响应类型为JSON格式,则将响应体转换为JSON格式的数据类型
return response.json();
} else {
// 如果响应类型为其他类型,则直接返回响应体
return response.data;
}
}).then(data => {
// 在这里对JSON数据进行处理
}).catch(error => {
// 处理错误
});
在这个例子中,我们首先使用 axios 库发起一个带有 responseType: ‘blob’ 参数的GET请求。当响应返回时,我们检查响应的类型是否为JSON格式。如果是,我们将响应体转换为JSON格式的数据类型。否则,我们直接返回响应体。
接着,我们使用 then 方法对返回的数据进行处理。如果响应类型为JSON格式,我们可以直接使用返回的JSON数据进行处理。否则,我们需要将响应体转换为JSON格式的数据类型,然后再进行处理。
需要注意的是,在使用 response.json() 方法将响应体转换为JSON格式的数据类型时,需要确保响应体的类型确实为JSON格式。如果响应体的类型不是JSON格式,这个方法将会抛出一个异常。
不应该判断后端返回数据的响应头content-type吗?
是的,判断后端返回数据的响应头content-type是更好的做法。因为content-type是HTTP响应头中标识响应体类型的字段,它能够准确地告诉前端响应体的类型,从而避免了前端对响应体类型的猜测和判断。
以下是一种使用content-type判断响应体类型的解决方案:
axios.get(‘/api/download’, {
responseType: ‘blob’
}).then(response => {
const contentType = response.headers[‘content-type’];
if (contentType === ‘application/json’) {
// 如果响应类型为JSON格式,则将响应体转换为JSON格式的数据类型
return response.json();
} else {
// 如果响应类型为其他类型,则直接返回响应体
return response.data;
}
}).then(data => {
// 在这里对JSON数据进行处理
}).catch(error => {
// 处理错误
});
在这个例子中,我们首先使用 axios 库发起一个带有 responseType: ‘blob’ 参数的GET请求。当响应返回时,我们获取响应头中的content-type字段,并根据其值判断响应体的类型。如果响应类型为JSON格式,我们将响应体转换为JSON格式的数据类型。否则,我们直接返回响应体。
接着,我们使用 then 方法对返回的数据进行处理。如果响应类型为JSON格式,我们可以直接使用返回的JSON数据进行处理。否则,我们需要将响应体转换为JSON格式的数据类型,然后再进行处理。
需要注意的是,在使用 response.json() 方法将响应体转换为JSON格式的数据类型时,需要确保响应体的类型确实为JSON格式。如果响应体的类型不是JSON格式,这个方法将会抛出一个异常。
前端工具
nvm
简介
NodeVersionManager(NVM)在处理多项目中不同Node.js版本需求的解决方案。NVM允许轻松切换版本,包括下载远程LTS版本、Windows安装步骤、配置淘宝镜像、基本使用命令及常见操作。对于Node.js开发者来说,NVM简化了版本管理,提高工作效率。
下载配置
配置源
C:\Users\qctc\AppData\Roaming\nvm\settings.txt
添加
node_mirror: http://npmmirror.com/mirrors/node/
npm_mirror: http://registry.npmmirror.com/mirrors/npm/
使用
--查看版本
nvm -v
--看安装的所有node.js的版本
nvm ls
--查显示可以安装的所有node.js的版本
nvm list available
--安装
nvm install 16.15.0
--卸载对应node版本(如:nvm uninstall 17.2.0)
nvm uninstall 版本号
--使用某个版本
nvm use 16.15.0
Vite
简介
Vite 是一个现代化的前端构建工具,旨在提高开发和构建速度。它由 Evan You(Vue.js 的创始人)开发,旨在解决传统构建工具(如 Webpack)在开发和构建过程中的一些痛点。以下是对 Vite 的详细介绍,包括其特性、工作原理、优点以及如何使用它。
特性
- 极速启动和热重载:
- Vite 利用原生 ES 模块(ESM)来实现开发时的快速启动和即时热重载(HMR)。在开发过程中,Vite 不需要进行打包,而是直接利用浏览器对 ES 模块的原生支持,动态地加载模块。
- 按需编译:
- 在开发时,Vite 会按需编译文件,只编译那些被实际引用的模块。这种按需编译的方式大大减少了编译的时间和计算资源。
- 优化构建:
- Vite 使用 Rollup 作为生产环境的构建工具,这提供了高效的静态资源打包、代码分割和 Tree-shaking 功能。Rollup 是一个成熟的打包工具,以其高效的打包性能而闻名。
- 兼容性:
- Vite 支持现代浏览器的原生 ES 模块功能,但也提供了降级机制,使得可以通过插件进行老旧浏览器的兼容性处理。
- 插件系统:
- Vite 拥有一个丰富的插件生态系统,支持扩展和自定义功能。插件系统与 Rollup 的插件系统兼容,可以复用许多现有的 Rollup 插件。
- 内置开发服务器:
- Vite 自带一个开发服务器,可以提供热重载、错误提示等功能,提升开发体验。
工作原理
- 开发模式:
- 在开发模式下,Vite 使用原生 ES 模块直接在浏览器中加载源代码。这意味着 Vite 可以实现极快的启动速度和即时的模块热重载。
- 当模块被修改时,Vite 只重新编译变化的模块,并通过 WebSocket 发送更新到浏览器,浏览器接收到更新后会刷新修改的模块。
- 生产模式:
- 在生产模式下,Vite 使用 Rollup 进行项目的打包。Rollup 对 JavaScript 进行高级优化,包括代码分割、Tree-shaking 等,以生成高效的生产构建文件。
- Vite 配置 Rollup 用于构建优化,因此用户可以使用 Rollup 插件和配置进行进一步的优化。
优点
- 快速启动:
- 由于开发时无需进行打包,Vite 的启动速度远快于传统工具,尤其是大型项目。
- 即刻反馈:
- Vite 的热重载非常快速,修改文件后可以即时看到效果,大大提高了开发效率。
- 现代化工具链:
- Vite 支持现代 JavaScript 特性,如 ES 模块,TypeScript,CSS 模块等,提供了更为现代化的开发体验。
- 配置简单:
- Vite 的配置非常简单易用,默认配置已经适用于大多数常见的使用场景。如果需要自定义,配置也很直观。
- 支持多种前端框架:
- Vite 原生支持 Vue、React、Svelte 等常见前端框架,并提供了框架特定的插件来简化集成。
使用
安装
可以使用 npm 或 yarn 安装 Vite。以下是创建一个新项目的步骤:
# 使用 npm
npm create vite@latest my-project# 使用 yarn
yarn create vite my-project
选择你想要的框架和模板,Vite 将自动生成一个基础项目结构。
启动
进入项目目录后,使用以下命令启动开发服务器:
cd my-project
npm install
npm run dev
或者使用 yarn:
cd my-project
yarn install
yarn dev
这将启动一个本地开发服务器,你可以在浏览器中访问 http://localhost:3000
查看项目。
构建
当你完成开发并准备好构建生产版本时,可以使用以下命令:
npm run build
或者使用 yarn:
yarn build
构建后的文件会生成在 dist
目录中,可以用来部署到生产环境。
前端基础
CSS
三大特性
- 层叠性:如果发生了样式冲突,那就会根据一定的规则(选择器优先级),进行样式层叠(覆盖)。
- 继承性:元素会自动拥有其父元素,或其祖先元素上所设置的某些样式。优先继承离得近的
- 优先级:!important > 行内样式 > ID选择器 > 类选择器 > 元素选择器 > * >继承的样式(多个选择器需要计算权重)
display属性
CSS中的display属性用于定义元素的布局方式和外观,让块级元素和行内元素相互转换。常见的取值有block、inline、inline-block、none等
- none:让生成的元素根本没有框,该框及其内容不再显示,不占用文档中的空间
- block块级:让行内元素(比如元素)表现得像块级元素一样,设置width、height有效,独占一行
- inline行内:让块级元素(比如
元素)表现得像内联或行内元素一样,设置width、height无效,一行可显示多个
- inline-block行内块:设置width、height有效,一行可显示多个
盒子模型
盒子模型是指CSS中的一个重要概念,用来描述一个元素在页面中占据的空间和位置
在CSS中认为所有的HTML标签都是一个盒子,这些盒子有以下内容:边框border、内容content、内边距padding(内容与边框的距离)、外边距margin(盒子与盒子之间的距离)
- 盒子有尺寸,用CSS写法为宽度(width)和高度(height)
- 盒子有边框,用CSS写法为上下左右边框(border)
- 盒子有内边距,CSS写法为上下左右内边距(padding)
- 盒子有外边距,CSS写法为上下左右外边距(margin)
Padding 和 Margin
padding 属性用于设置元素内容与其边框之间的间距(内边距)
padding 属性可以通过以下几种方式进行设置:
- 单个值:应用于所有四个边。
- 两个值:第一个值应用于上下边,第二个值应用于左右边。
- 三个值:第一个值应用于上边,第二个值应用于左右边,第三个值应用于下边。
- 四个值:依次应用于上、右、下、左边。
margin
属性用于设置元素与其相邻元素之间的间距(外边距)
margin
属性的设置方式与 padding
类似
Flex弹性布局
Flex是Flexible Box的缩写,意为”弹性布局”,用来替代float浮动布局
采用Flex布局的元素,称为Flex容器(flex container),简称”容器”。它的所有子元素自动成为容器成员,称为Flex项目(flex item),简称”项目”。
容器默认存在两根轴:水平的主轴(main axis)和垂直的交叉轴(cross axis)。
主轴的开始位置(与边框的交叉点)叫做main start,结束位置叫做main end;交叉轴的开始位置叫做cross start,结束位置叫做cross end。
项目默认沿主轴排列。单个项目占据的主轴空间叫做main size,占据的交叉轴空间叫做cross size。
flex-direction属性
flex-direction属性决定主轴的方向(即项目的排列方向)。
- row(默认值):主轴为水平方向,起点在左端。
- row-reverse:主轴为水平方向,起点在右端。
- column:主轴为垂直方向,起点在上沿。
- column-reverse:主轴为垂直方向,起点在下沿。
flex-wrap属性
默认情况下,项目都排在一条线(又称”轴线”)上。flex-wrap属性定义,如果一条轴线排不下,如何换行。
nowrap(默认):不换行。
wrap:换行,第一行在上方。
wrap-reverse:换行,第一行在下方。
justify-content
- 定义:
justify-content
控制的是主轴(flexbox 的默认主轴是水平方向,grid 的主轴依赖于布局方向)的对子元素的对齐方式。 - 属性值:
flex-start
:子元素靠近主轴的开始位置。flex-end
:子元素靠近主轴的结束位置。center
:子元素在主轴上居中对齐。space-between
:子元素在主轴上均匀分布,首尾元素靠边。space-around
:子元素在主轴上均匀分布,每个元素两侧都有相等的空间。
align-items
- 定义:
align-items
控制的是交叉轴(flexbox 的默认交叉轴是垂直方向)上的对齐方式。 - 属性值:
flex-start
:子元素在交叉轴的开始位置对齐。flex-end
:子元素在交叉轴的结束位置对齐。center
:子元素在交叉轴上居中对齐。baseline
:子元素的基线对齐。stretch
:子元素被拉伸以填充容器(这是默认值)。
align-content属性
align-content属性定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。也就是垂直对齐,不分行对齐。
- flex-start:与交叉轴的起点对齐。
- flex-end:与交叉轴的终点对齐。
- center:与交叉轴的中点对齐。
- space-between:与交叉轴两端对齐,轴线之间的间隔平均分布。
- space-around:每根轴线两侧的间隔都相等。所以,轴线之间的间隔比轴线与边框的间隔大一倍。
- stretch(默认值):轴线占满整个交叉轴。
边框
基本用法
border: [border-width] [border-style] [border-color];
border: 2px solid red;
JavaScript/TypeScript
javaScript常用数组方法
顺序 | 方法名 | 功能 | 返回值 | 是否改变原数组 | 版本 |
---|---|---|---|---|---|
1 | push() | (在结尾)向数组添加一或多个元素 | 返回新数组长度 | Y | ES5 |
2 | unshift() | (在开头)向数组添加一或多个元素 | 返回新数组长度 | Y | ES5 |
3 | find() | 遍历数组,执行回调函数,回调函数执行一个条件,返回满足条件的第一个元素,不存在返回undefined | 满足条件第一个元素/否则返回undefined | N | ES6 |
4 | forEach() | (迭代) 遍历数组,每次循环中执行传入的回调函数 | 无/(undefined) | N | ES5- |
5 | splice() | 向数组中添加,或从数组删除,或替换数组中的元素,然后返回被删除/替换的元素所组成的数组。可以实现数组的增删改 | 被删除/替换的元素所组成的数组 | ||
6 | map() | 遍历数组, 每次循环时执行传入的回调函数,根据回调函数的返回值,生成一个新的数组 | |||
7 | join() | 用特定的字符,将数组拼接形成字符串 (默认",") | 返回拼接后的字符串 | N | ES5- |
async/await
async
和 await
是 JavaScript 中用于处理异步操作的语法糖,它们使得编写异步代码更加简洁和可读。以下是它们的详细解释和用法。
1. 基本概念
async
:用于声明一个异步函数,该函数返回一个 Promise 对象。即使你在函数中没有显式返回 Promise,函数会自动封装为 Promise。await
:用于等待一个 Promise 的解析。它只能在async
函数内部使用。await
表达式会暂停异步函数的执行,直到 Promise 完成并返回结果。
2. 使用方法
2.1 声明异步函数
使用 async
关键字声明一个异步函数:
async function myAsyncFunction() {// 这里的代码是异步的
}
2.2 使用 await
在异步函数中,你可以使用 await
来等待 Promise 的结果:
async function fetchData() {const response = await axios.get('/api/data'); // 等待请求完成const data = response.data; // 获取数据return data; // 返回数据
}
3. 完整示例
下面是一个完整的示例,展示如何使用 async
和 await
:
import axios from 'axios';async function getUserData(userId) {try {const response = await axios.get(`https://jsonplaceholder.typicode.com/users/${userId}`);console.log('用户数据:', response.data);} catch (error) {console.error('请求错误:', error);}
}getUserData(1); // 调用函数,获取用户数据
4. 错误处理
使用 try...catch
语句可以有效地处理异步操作中的错误:
async function fetchData() {try {const response = await axios.get('/api/data');console.log('数据:', response.data);} catch (error) {console.error('请求失败:', error);}
}
5. 关键点总结
- 提高可读性:使用
async/await
使得异步代码更接近于同步代码的书写风格,增强可读性。 - 错误处理:可以使用
try...catch
来捕获错误,处理方式更为简洁。 - 不阻塞主线程:尽管代码看起来是顺序执行的,
await
不会阻塞主线程,仍然可以执行其他操作。
6. 限制
await
只能在async
函数内部使用。- 如果你在非
async
函数中使用await
,会抛出语法错误。
7. 结论
async/await
是处理 JavaScript 异步操作的强大工具,使得异步代码更易于编写、阅读和维护。通过结合 Promise 和 async/await
,可以有效管理异步请求和其他异步操作。
Promise 对象
Promise 是一种用于表示异步操作的最终完成(或失败)及其结果值的对象。它有三种状态:
- Pending(待定):初始状态,既不是成功,也不是失败。
- Fulfilled(已完成):操作成功完成。
- Rejected(已拒绝):操作失败。
使用 Promise
可以更好地管理异步操作,使得代码更清晰。
async/await
是基于 Promise
的语法糖,用于简化异步代码的编写。通过将函数声明为 async
,你可以在函数内部使用 await
等待一个 Promise 的解析,从而使异步代码更接近同步的写法。
Axios 是一个基于 Promise 的 HTTP 客户端,用于发送网络请求。当你使用 Axios 进行请求时,它返回一个 Promise。你可以用 .then()
和 .catch()
方法处理结果,或在 async
函数中使用 await
来获取数据。
示例
import axios from 'axios';async function fetchData() {try {const response = await axios.get('/api/data'); // axios 返回一个 Promiseconsole.log('数据:', response.data);} catch (error) {console.error('请求错误:', error);}
}fetchData();
总结
- Promise 处理异步操作的结果。
- async/await 提供了更易读的语法来处理 Promise。
- Axios 使用 Promise 进行网络请求,能与
async/await
无缝结合,提高代码可读性。
定时执行 setInterval方法
setInterval
是 JavaScript 中用于定时执行函数的一个全局方法。它的主要作用是在指定的时间间隔内重复调用一个函数或执行一段代码。这在需要周期性地执行某些任务时非常有用,比如更新界面、轮播图的切换、定时检查状态等。
语法
let intervalID = setInterval(function, delay, arg1, arg2, ...);
- function:要执行的函数,可以是一个函数引用或匿名函数。
- delay:延迟的时间(以毫秒为单位),即每次调用之间的间隔时间。
- arg1, arg2, …:可选的参数,在调用函数时传给该函数。
返回值
setInterval
返回一个整数 ID,这个 ID 可以用于停止定时器。这个 ID 是唯一的标识符,用于后续通过clearInterval
方法来清除该定时器。
示例
简单示例
下面是一个简单的例子,使用 setInterval
每隔一秒打印一次当前时间:
let intervalID = setInterval(() => {let date = new Date();console.log("当前时间: " + date.toLocaleTimeString());
}, 1000);
停止定时器
可以使用 clearInterval
来停止定时器,如下所示:
// 停止定时器
clearInterval(intervalID);
可以调用 clearInterval(intervalID)
来停止之前设置的 setInterval
,如在一定条件下停止定时执行。
js-cookie库
js-cookie
是一个简单易用的 JavaScript 库,用于操作 Cookie。以下是其基本用法:
-
设置 Cookie:
Cookies.set('key', 'value'); // 可选参数:{ expires: 7, path: '/', domain: '.example.com' }
-
获取 Cookie:
const value = Cookies.get('key');
-
删除 Cookie:
Cookies.remove('key'); // 保存7天 const expires = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); Cookies.set('key', '', { expires });
-
读取所有 Cookie:
const allCookies = Cookies.get();
使用 js-cookie
可简化 Cookie 的操作,便于在前端存储和读取数据。
断言
变量非空断言(Non-null Assertion)是一种特殊的类型断言,用于告诉编译器某个变量或表达式不是 null
或 undefined
。这种断言通常用后缀 !
表示。
为什么需要非空断言
在 TypeScript 中,很多类型默认可能是 null
或 undefined
。例如,在接口中未赋值的可选属性,默认值就是 undefined
。非空断言可以帮助你在某些情况下避免冗长的类型检查。
非空断言的用法
非空断言的基本语法是在变量或表达式后面加上 !
数字格式化
使用padStart(length, '0')
函数,length表示长度(位数),'0’表示位数不足时补充0,通常结合toString()和parseInt(t1[0])使用
let length = 2;
let t1 = row.allowedCharging.split("_");
startTime1.value = parseInt(t1[0]).toString().padStart(length, '0')+":00"
endTime1.value = parseInt(t1[1]).toString().padStart(length, '0')+":00"
let t2 = row.allowedDischarge.split("_")
startTime2.value = parseInt(t2[0]).toString().padStart(length, '0')+":00"
endTime2.value = parseInt(t2[1]).toString().padStart(length, '0')+":00"
读取解析Excel
使用原生的input组件,结合xlsx.js解析Excel文件,解析结果为数组,每行也是数组
<template><el-dialog :title="fileDialogTitle" v-model="openfile" width="700px" append-to-body><el-row style="display: flex; justify-content: center; padding: 30px auto;"><el-col :span="12"><input type="file" ref="fileInput" accept=".xlsx,.xls" /></el-col></el-row><template #footer><div class="dialog-footer"><el-button type="primary" @click="submitFile">确 定</el-button><el-button @click="cancel">取 消</el-button></div></template></el-dialog>
</template>
<script setup lang="ts">
const openfile = ref<boolean>(false);
const fileDialogTitle = ref<string>("打开文件");
const fileInput = ref<HTMLInputElement | null>(null);function handleFileChange() {const file = fileInput.value?.files?.[0];if (!file) { // 检查是否有文件选择ElMessage.info('请选择一个 Excel 文件!');} else {console.log('选择的文件:', file.name); // 输出选择的文件}
};async function submitFile() {const file = fileInput.value?.files?.[0];if (!file) {ElMessage.info('请选择一个 Excel 文件!');return;}let tableHeaders: any = ['fl', 'lx', 'm1', '', '', '', '', '', '', ''];const reader = new FileReader();reader.onload = (e) => {const data = new Uint8Array(e.target?.result as ArrayBuffer);const workbook = XLSX.read(data, { type: 'array' });// 读取第一个工作表const worksheet = workbook.Sheets[workbook.SheetNames[0]];const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 });console.log(jsonData)// 设置表头和数据if (jsonData.length > 0) {jsonData.forEach((value: unknown, index: number, array: unknown[]) => {console.log(value + " " + index + " ")if (Array.isArray(value)) {if (index >= 2) {tableData[index - 2].m1 = value[2];tableData[index - 2].m2 = value[3];tableData[index - 2].m3 = value[4];tableData[index - 2].m4 = value[5];tableData[index - 2].m5 = value[6];tableData[index - 2].m6 = value[7];tableData[index - 2].m7 = value[8];tableData[index - 2].m8 = value[9];tableData[index - 2].m9 = value[10];tableData[index - 2].m10 = value[11];tableData[index - 2].m11 = value[12];tableData[index - 2].m12 = value[13];}}})openfile.value = false;}};reader.readAsArrayBuffer(file);
};
function cancel() {open.value = falseopenfile.value = false
}
</script>
Element-Plus框架
Container布局容器
用于布局的容器组件,方便跨苏搭建页面的基本结构
<el-container>
:外层容器。 当子元素中包含<el-header>
或<el-footer>
时,全部子元素会垂直上下排列, 否则会水平左右排列。<el-header>
:顶栏容器。<el-aside>
:侧边栏容器。<el-main>
:主要区域容器。<el-footer>
:底栏容器。
以上组件采用了 flex 布局,使用前请确定目标浏览器是否兼容。 此外,
<el-container>
的直接子元素必须是后四个组件中的一个或多个。 后四个组件的父元素必须是一个<el-container>
Color色彩
Element Plus 为了避免视觉传达差异,使用一套特定的调色板来规定颜色,为你所搭建的产品提供一致的外观视觉感受。
参考页面:色彩
Layout布局
通过基础的24分栏,迅速简便地创建布局
使用列创建基础网格布局。
通过 row
和 col
组件,并通过 col 组件的 span
属性我们就可以自由地组合布局。
行提供 gutter
属性来指定列之间的间距,其默认值为0。
通过制定 col 组件的 offset
属性可以指定分栏偏移的栏数。
您可以通过justify
属性来定义子元素的排版方式,其取值为start、center、end、space-between、space-around或space-evenly。
row的属性
属性名 | 说明 | 类型 | 默认值 |
---|---|---|---|
gutter | 栅格间隔 | number | 0 |
justify | flex 布局下的水平排列方式 | enum | start |
align | flex 布局下的垂直排列方式 | enum | — |
tag | 自定义元素标签 | string | div |
justify:‘start’ | ‘end’ | ‘center’ | ‘space-around’ | ‘space-between’ | ‘space-evenly’
align:‘top’ | ‘middle’ | ‘bottom’
行提供 gutter
属性来指定列之间的间距,其默认值为0。
<template><el-row :gutter="20"><el-col :span="6"><div class="grid-content ep-bg-purple" /></el-col><el-col :span="6"><div class="grid-content ep-bg-purple" /></el-col><el-col :span="6"><div class="grid-content ep-bg-purple" /></el-col><el-col :span="6"><div class="grid-content ep-bg-purple" /></el-col></el-row>
</template>
col的属性
属性名 | 说明 | 类型 | 默认值 |
---|---|---|---|
span | 栅格占据的列数 | number | 24 |
offset | 栅格左侧的间隔格数 | number | 0 |
push | 栅格向右移动格数 | number | 0 |
pull | 栅格向左移动格数 | number | 0 |
xs | <768px 响应式栅格数或者栅格属性对象 | number / object | — |
sm | ≥768px 响应式栅格数或者栅格属性对象 | number / object | — |
md | ≥992px 响应式栅格数或者栅格属性对象 | number / object | — |
lg | ≥1200px 响应式栅格数或者栅格属性对象 | number / object | — |
xl | ≥1920px 响应式栅格数或者栅格属性对象 | number / object | — |
tag | 自定义元素标签 | string | div |
Button 按钮
基础用法
<template><div class="mb-4"><el-button>Default</el-button><el-button type="primary">Primary</el-button><el-button type="success">Success</el-button><el-button type="info">Info</el-button><el-button type="warning">Warning</el-button><el-button type="danger">Danger</el-button></div><div class="mb-4"><el-button plain>Plain</el-button><el-button type="primary" plain>Primary</el-button><el-button type="success" plain>Success</el-button><el-button type="info" plain>Info</el-button><el-button type="warning" plain>Warning</el-button><el-button type="danger" plain>Danger</el-button></div><div class="mb-4"><el-button round>Round</el-button><el-button type="primary" round>Primary</el-button><el-button type="success" round>Success</el-button><el-button type="info" round>Info</el-button><el-button type="warning" round>Warning</el-button><el-button type="danger" round>Danger</el-button></div><div><el-button :icon="Search" circle /><el-button type="primary" :icon="Edit" circle /><el-button type="success" :icon="Check" circle /><el-button type="info" :icon="Message" circle /><el-button type="warning" :icon="Star" circle /><el-button type="danger" :icon="Delete" circle /></div><!-- 常用 -->
<el-button type="primary" :icon="Plus" plain @click="handleAdd">添加</el-button>
<el-button type="warning" :icon="Delete" plain @click="handleDel">删除</el-button>
<el-button type="success" :icon="Select" plain @click="handleSave">保存</el-button>
<el-button type="primary" :icon="DataAnalysis" plain>统计信息</el-button>
<el-button type="primary" :icon="DataAnalysis" plain>分月信息</el-button>
<el-button type="primary" :icon="Plus" plain>添加电厂</el-button>
<el-button type="primary" :icon="Upload" plain>导入</el-button>
<el-button type="primary" :icon="Download" plain>导出</el-button>
</template><script lang="ts" setup>
import {Check,Delete,Edit,Message,Search,Star,
} from '@element-plus/icons-vue'
</script><!-- 禁用 -->
<el-button type="primary" disabled>Primary</el-button>
<!-- 加载中 -->
<el-button type="primary" :loading="execbtn" :icon="Select" @click="execinit">执行初始化</el-button>
Table 表格
列内容居中
<el-table-column align="center" prop="swjd" label="上网节点" />
列内容不显示
对el-table-column
使用v-if="false"
<el-table-column property="unitid" label="机组ID" width="100" v-if="false" />
列内容格式化
给formatter属性添加处理函数
<el-table-column property="isFixedCurve" label="是否固定功率曲线" align="center" :formatter="isOrNotFormatter" />
<script setup lang="ts">const isOrNot = reactive([{ label: "是", value: '1' }, { label: '否', value: '0' }])function isOrNotFormatter(row: any, column: any, cellValue: any, index: number){return isOrNot.find(e => e.value === cellValue)?.label || '未知';}
</script>
固定列
固定列需要使用 fixed
属性,它接受 Boolean
值。
斑马纹
stripe
可以创建带斑马纹的表格
行样式
row-style属性
<el-table :data="tableData" style="width: 1680px; height: 400px; max-height: 400px;" stripe border :row-style="rowStyle"></el-table>
<script lang='ts'>const rowStyle = ({ row, rowIndex }: { row: any, rowIndex: number }) => {// 根据行索引或行数据动态设置样式if (rowIndex % 2 === 0) {return { backgroundColor: '#f0f0f0', opacity: 0.8 };} else {return { backgroundColor: '#ffffff', opacity: 1 };}};
</script>
带边框表格
默认情况下,Table 组件是不具有竖直方向的边框的, 如果需要,可以使用 border
属性
显示溢出提示
属性 show-overflow-tooltip 接受一个布尔值。 为 true
时多余的内容会在 hover 时以 tooltip 的形式显示出来。
流体高度
通过设置 max-height
属性为 el-table
指定最大高度。 此时若表格所需的高度大于最大高度,则会显示一个滚动条。
单选
选择单行数据时使用色块表示。
Table 组件提供了单选的支持, 只需要配置 highlight-current-row
属性即可实现单选。 之后由 current-change
事件来管理选中时触发的事件,它会传入 currentRow
,oldCurrentRow
。 如果需要显示索引,可以增加一列 el-table-column
,设置 type
属性为 index
即可显示从 1 开始的索引号。
<template><el-tableref="singleTableRef":data="tableData"highlight-current-rowstyle="width: 100%"@current-change="handleCurrentChange"><el-table-column type="index" width="50" /><el-table-column property="date" label="Date" width="120" /><el-table-column property="name" label="Name" width="120" /><el-table-column property="address" label="Address" /></el-table>
</template>
<script lang="ts" setup>const currentRow = ref()const handleCurrentChange = (val: User | undefined) => {currentRow.value = val}
</script>
多选
在2.8.3 之后, toggleRowSelection
支持第三个参数 ignoreSelectable
以确定是否忽略可选属性。
实现多选非常简单: 手动添加一个 el-table-column
,设 type
属性为 selection
即可
<template><el-tableref="multipleTableRef":data="tableData"style="width: 100%"@selection-change="handleSelectionChange"><el-table-column type="selection" :selectable="selectable" 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([tableData[1], tableData[2]], false)">Toggle selection status based on selectable</el-button><el-button @click="toggleSelection()">Clear selection</el-button></div>
</template><script lang="ts" setup>
import { ref } from 'vue'
import type { TableInstance } from 'element-plus'interface User {id: numberdate: stringname: stringaddress: string
}const multipleTableRef = ref<TableInstance>()
const multipleSelection = ref<User[]>([])const selectable = (row: User) => ![1, 2].includes(row.id)
const toggleSelection = (rows?: User[], ignoreSelectable?: boolean) => {if (rows) {rows.forEach((row) => {multipleTableRef.value!.toggleRowSelection(row,undefined,ignoreSelectable)})} else {multipleTableRef.value!.clearSelection()}
}
const handleSelectionChange = (val: User[]) => {multipleSelection.value = val
}......
表格布局
通过属性 table-layout 可以指定表格中单元格、行和列的布局方式
<template><el-radio-group v-model="tableLayout"><el-radio-button value="fixed">fixed</el-radio-button><el-radio-button value="auto">auto</el-radio-button></el-radio-group><el-table :data="tableData" :table-layout="tableLayout"><el-table-column prop="date" label="Date" /><el-table-column prop="name" label="Name" /><el-table-column prop="address" label="Address" /></el-table>
</template><script lang="ts" setup>
import { ref } from 'vue'
import type { TableInstance } from 'element-plus'const tableLayout = ref<TableInstance['tableLayout']>('fixed')
...
单元格合并
多行或多列共用一个数据时,可以合并行或列。
通过给 table 传入span-method
方法可以实现合并行或列, 方法的参数是一个对象,里面包含当前行 row
、当前列 column
、当前行号 rowIndex
、当前列号 columnIndex
四个属性。 该函数可以返回一个包含两个元素的数组,第一个元素代表 rowspan
,第二个元素代表 colspan
。 也可以返回一个键名为 rowspan
和 colspan
的对象。
<template><div><el-table:data="tableData":span-method="arraySpanMethod"borderstyle="width: 100%"><el-table-column prop="id" label="ID" width="180" /><el-table-column prop="name" label="Name" /><el-table-column prop="amount1" sortable label="Amount 1" /><el-table-column prop="amount2" sortable label="Amount 2" /><el-table-column prop="amount3" sortable label="Amount 3" /></el-table><el-table:data="tableData":span-method="objectSpanMethod"borderstyle="width: 100%; margin-top: 20px"><el-table-column prop="id" label="ID" width="180" /><el-table-column prop="name" label="Name" /><el-table-column prop="amount1" label="Amount 1" /><el-table-column prop="amount2" label="Amount 2" /><el-table-column prop="amount3" label="Amount 3" /></el-table></div>
</template>
<script lang="ts" setup>
import type { TableColumnCtx } from 'element-plus'interface User {id: stringname: stringamount1: stringamount2: stringamount3: number
}interface SpanMethodProps {row: Usercolumn: TableColumnCtx<User>rowIndex: numbercolumnIndex: number
}const arraySpanMethod = ({row,column,rowIndex,columnIndex,
}: SpanMethodProps) => {if (rowIndex % 2 === 0) {if (columnIndex === 0) {return [1, 2]} else if (columnIndex === 1) {return [0, 0]}}
}const objectSpanMethod = ({row,column,rowIndex,columnIndex,
}: SpanMethodProps) => {if (columnIndex === 0) {if (rowIndex % 2 === 0) {return {rowspan: 2,colspan: 1,}} else {return {rowspan: 0,colspan: 0,}}}
}const tableData: User[] = [{id: '12987122',name: 'Tom',amount1: '234',amount2: '3.2',amount3: 10,},{id: '12987123',name: 'Tom',amount1: '165',amount2: '4.43',amount3: 12,},{id: '12987124',name: 'Tom',amount1: '324',amount2: '1.9',amount3: 9,},{id: '12987125',name: 'Tom',amount1: '621',amount2: '2.2',amount3: 17,},{id: '12987126',name: 'Tom',amount1: '539',amount2: '4.1',amount3: 15,},
]
</script>
删除某行
function handleDel(){console.log("点击了删除按钮")if (!currentRow.value) {ElMessage.warning("请选择要删除的数据")return;}// console.log(currentRow.value);tableData.value.splice(tableData.value.indexOf(currentRow.value), 1);ElMessage.success("删除成功!");
}
某列搜索
<el-table ref="mxTableRef" :data="filteredData"style="width: 100%; height: 730px; max-height: 730px;" stripe border:row-style="rowStyle" highlight-current-row @current-change="handleCurrentChange"><el-table-column prop="name" label="母线名称" align="center"><template #header>
<span>母线名称</span>
<el-input v-model="mxSearch" placeholder="搜索母线" style="width: 120px"/></template></el-table-column><el-table-column prop="limit" label="最大限制(MW)" width="120" align="center" /><el-table-column prop="avg" label="平均负载率(%)" width="120" align="center" /><el-table-column prop="max" label="最大负载率(%)" width="120" align="center" />
</el-table>
<script>
const filteredData = computed(() => {return tableData.value.filter(item =>item.name.includes(mxSearch.value));
});
</script>
填充测试数据
tableData.value = [];const intervalMinutes = 15; // 每15分钟一个时间点const startDate = new Date();startDate.setHours(0, 0, 0, 0); // 设置为当天的00:00for (let i = 0; i < 96; i++) {const currentTime = new Date(startDate.getTime() + i * intervalMinutes * 60 * 1000);const hours = currentTime.getHours().toString().padStart(2, '0');const minutes = currentTime.getMinutes().toString().padStart(2, '0');const sk = `${hours}:${minutes}`;// 生成随机的sjfh和ycfh值,可以根据实际需求调整const sjfh = (Math.random() * 100).toFixed(2);const ycfh = (Math.random() * 100).toFixed(2);tableData.value.push({sk: sk,gl: parseFloat(sjfh),fbyz: 1,llxxe: parseFloat(ycfh)});}
双击单元格修改数据
<el-table ref="table1Ref" :data="tableData1"style="width: 100%; height: 100%; max-height: 100%;" stripe border :row-style="rowStyle"@cell-dblclick="cellDbclick1" highlight-current-row><!-- 场景ID 场景名称 场景类型 负荷变动系数 风电变动系数 光伏变动系数 送出变动系数 受入变动系数 是否生效 --><el-table-column prop="sceneId" label="场景ID" align="center" /><el-table-column prop="sceneName" label="场景名称" align="center" /><el-table-column prop="sceneType" label="场景类型" align="center" /><el-table-column v-for="(column, index) in columns1" :key="index" :prop="column.prop":label="column.label" align="center"><template #default="{ row }">
<div v-if="editing && editing.row === row && editing.prop === column.prop"><el-input :ref="`input-${row.sceneId}-${column.prop}`" v-model="row[column.prop]"@blur="handleInputBlur($event, row, column.prop)"@keydown="handleInputKeydown($event, row, column.prop)"></el-input></div>
<div v-else>{{ row[column.prop] }}</div></template></el-table-column>
</el-table>
function cellDbclick1(row: any, column: any, cell: HTMLTableCellElement, event: Event) {console.log(column)editing.value = { row, prop: column.property };// nextTick(() => {// const inputRef = document.querySelector(`input-${row.sceneId}-${column.property}`);// // const inputRef = table1Ref.value?.getRef(`input-${row.sceneId}-${column.property}`)// console.log(inputRef);// // if (inputRef) {// // inputRef.focus();// // }// });}
function handleInputBlur(event, row, prop) {console.log(row, prop);row[prop] = event.target.value;updData1(row);this.editing = null;
}
function handleInputKeydown(event, row, prop) {if (event.key === 'Enter') {row[prop] = event.target.value;updData1(row);this.editing = null;}
}
右键菜单
主要是使用了el-table组件的cell-contextmenu事件属性
<template>
<el-tableref="table1Ref":data="tableData1"style="width: 100%; height: 100%; max-height: 100%;"stripeborder:row-style="rowStyle"@cell-dblclick="cellDbclick1"@cell-contextmenu="handleCellContextMenu"highlight-current-row></el-table>
<script setup lang="ts">
function cellContextmenu(row: any, column: any, cell: HTMLTableCellElement, event: MouseEvent) {event.preventDefault(); // 阻止默认的右键菜单// console.log(row)// console.log(column)const menuItems = initstates.value.map(item => ({label: item.name,icon: 'el-icon-edit',action: () => {// 处理单元格右键菜单点击事件row.prestate = item.id;}}));const contextMenu = document.createElement('div');contextMenu.style.position = 'absolute';contextMenu.style.left = `${event.clientX}px`;contextMenu.style.top = `${event.clientY}px`;contextMenu.style.backgroundColor = '#fff';contextMenu.style.border = '1px solid #ccc';contextMenu.style.boxShadow = '2px 2px 5px rgba(0, 0, 0, 0.3)';contextMenu.style.zIndex = '1000';menuItems.forEach(item => {const menuItem = document.createElement('div');menuItem.className = 'context-menu-item';menuItem.innerHTML = `<i class="${item.icon}"></i> ${item.label}`;menuItem.addEventListener('click', () => {item.action();contextMenu.remove();});contextMenu.appendChild(menuItem);});document.body.appendChild(contextMenu);// 添加点击空白区域关闭菜单的事件const closeMenu = (e: MouseEvent) => {if (!contextMenu.contains(e.target as Node)) {contextMenu.remove();document.removeEventListener('click', closeMenu);}};document.addEventListener('click', closeMenu);
}
</script>
列内容改为按钮
<!-- 修改按钮 -->
<el-table-column fixed="right" label="操作" width="100"><template #default="scope"><el-button type="primary" size="small" @click="handleUpdate(scope.row)">修改</el-button></template>
</el-table-column>
<script setup lang="ts">function handleUpdate(row: any) {form.value = row;openUpdate.value = true;}
</script>
分页
<el-row style="display: flex; justify-content: center; margin-top: 5px"><el-pagination v-model:current-page="currentPage" v-model:page-size="pageSize" :page-sizes="[20, 96, 200, 1000]" :size="size" :disabled="disabled" :background="background" layout="sizes, prev, pager, next" :total="total" @size-change="handleSizeChange" @current-change="handleCurrentChange" />
</el-row>
const currentPage = ref(1)
const pageSize = ref(20)
const size = ref<ComponentSize>('default')
const background = ref(false)
const disabled = ref(false)
const total = ref<number>(1000)const handleSizeChange = (val: number) => {console.log(`${val} items per page`)initTable()
}
const handleCurrentChange = (val: number) => {console.log(`current page: ${val}`)initTable()
}
Dialog对话框
<el-dialog :title="addOrUpdateTitle" v-model="open" width="700px" append-to-body>
</el-dialog>
例子
<el-dialog :title="addOrUpdateTitle" v-model="openAddOrUpdate" width="700px" append-to-body><el-row><!-- 断面名称 包含设备数 正极限 负极限 开始日期 结束日期 数据来源 状态 --><el-form :model="form" :rules="rules" ref="userRef" label-width="90px"><el-form-item label="序号"><el-input type="number" v-model="form!.xh" /></el-form-item><el-form-item label="停电设备名称"><el-input v-model="form!.tdsbmc" placeholder="停电设备名称" /></el-form-item><el-form-item label="检修开始日期"><el-date-picker v-model="form!.jxksrq" type="date" placeholder="检修开始日期" style="width: 100%"format="YYYY-MM-DD" value-format="YYYY-MM-DD" /></el-form-item><el-form-item label="检修结束日期"><el-date-picker v-model="form!.jxjsrq" type="date" placeholder="检修结束日期" style="width: 100%"format="YYYY-MM-DD" value-format="YYYY-MM-DD" /></el-form-item><el-form-item label="检修天数"><el-input v-model="form!.jxts" disabled /></el-form-item><el-form-item label="设别类型"><el-select v-model="form!.sblx" placeholder="请选择设别类型"><el-option v-for="jxlx in sblxs" :label="jxlx.label" :value="jxlx.value" /></el-select></el-form-item><el-form-item label="电压等级"><el-select v-model="form!.dydj" placeholder="请选择电压等级"><el-option v-for="jxlx in dydjs" :label="jxlx.label" :value="jxlx.value" /></el-select></el-form-item><el-form-item label="检修内容"><el-input type="texg" v-model="form!.jxnr" /></el-form-item></el-form></el-row><template #footer><div class="dialog-footer"><el-button type="primary" @click="okBtn">确 定</el-button><el-button @click="closeBtn">取 消</el-button></div></template></el-dialog>
<script>
const openAddOrUpdate = ref<boolean>(false);
const addOrUpdateTitle = ref<string>("新增");
const form = ref<tableRow>()
const zts = ref<any[]>([{ label: "正常", value: "正常" }, { label: "异常", value: "异常" }])function okBtn() {console.log("点击了确定按钮")console.log(form.value)if (addOrUpdateTitle.value === "新增") {if (form.value){tableData.value.push(form.value!)}} else if (addOrUpdateTitle.value === "更新") {let index = tableData.value.indexOf(selectionRep.value[0])tableData[index] = form.value}form.value = { ...form.value, bhsbs: 0, dmmc: "", zjx: 0, fjx: 0, ksrq: "", jsrq: "", sjly: "", zt: "" }openAddOrUpdate.value = false;ElMessage.success(addOrUpdateTitle.value + '成功!请点击保存按钮进行保存');
}
function closeBtn() {console.log("点击了取消按钮")console.log(form)openAddOrUpdate.value = false;
}
</script>
对话框中展示其它组件
<el-dialog title="发电计划正式优化(SCUC)" v-model="openDialog2" append-to-body class="sfdy-dialog-style":show-close="true" :close-on-click-modal="false" :transition="'fade'" destroy-on-close><keep-alive><component :is="kt3CurrentComponent" /></keep-alive><template #footer><divstyle="width: 100%;display: flex; justify-content: center; align-items: center; gap: 20px;"><el-button type="primary" @click="kt3CurrentComponent = Kt3Tab1Content">概览</el-button><el-button type="primary" :icon="Histogram" plain@click="kt3CurrentComponent = Kt3Tab2Content">新能源消纳情况</el-button><el-button type="primary" :icon="Histogram" plain@click="kt3CurrentComponent = Kt3Tab3Content">系统备用情况</el-button><el-button type="primary" :icon="Histogram" plain@click="kt3CurrentComponent = Kt3Tab4Content">机组发电计划</el-button><el-button type="primary" :icon="Histogram" plain@click="kt3CurrentComponent = Kt3Tab5Content">电网潮流情况</el-button></div></template></el-dialog>
相关变量
// 课题三部分
import Kt3Tab1Content from '../jgzs/component/kt3Overview.vue';
import Kt3Tab2Content from '../jgzs/xnyxn.vue';
import Kt3Tab3Content from '../jgzs/dmclfx.vue';
import Kt3Tab4Content from '../jhbz/jzjhjg.vue';
import Kt3Tab5Content from '../jgzs/dwclqk.vue';const kt3CurrentComponent = ref(Kt3Tab1Content);
Form表单
<el-form :model="form" :rules="rules" ref="userRef" label-width="90px"><el-form-item label="断面名称" prop="dmmc"><el-input v-model="formModel.dmmc" placeholder="断面名称" /></el-form-item><el-form-item label="包含设备数" v-show="false" prop="bhsbs"><el-input v-model="formModel!.bhsbs" placeholder="包含设备数" disabled /></el-form-item><el-form-item label="正极限" prop="zjx"><el-input type="number" v-model="formModel!.zjx" /></el-form-item><el-form-item label="负极限" prop="fjx"><el-input type="number" v-model="formModel!.fjx" /></el-form-item><el-form-item label="开始日期" prop="ksrq"><el-date-picker v-model="formModel!.ksrq" type="date" placeholder="" style="width: 180px" format="YYYY-MM-DD" value-format="YYYY-MM-DD" /></el-form-item><el-form-item label="结束日期" prop="jsrq"><el-date-picker v-model="formModel!.jsrq" type="date" placeholder="" style="width: 180px" format="YYYY-MM-DD" value-format="YYYY-MM-DD" /></el-form-item><el-form-item label="数据来源" prop="sjly"><el-input v-model="formModel!.sjly" placeholder="" /></el-form-item><el-form-item label="状态" prop="zt"><el-select v-model="formModel!.zt" placeholder="请选择状态"><el-option v-for="jxlx in zts" :label="jxlx.label" :value="jxlx.value" /></el-select></el-form-item><el-form-item><el-button type="primary" @click="okBtn(formRef)">确 定</el-button><el-button @click="closeBtn">取 消</el-button></el-form-item>
</el-form>
表单验证
属性::rules=“rules”
<script lang="ts" setup>
const rules = reactive<FormRules<RuleForm>>({name: [{ required: true, message: '请输入断面名称', trigger: 'blur' },{ min: 3, max: 5, message: 'Length should be 3 to 5', trigger: 'blur' },],region: [{required: true,message: 'Please select Activity zone',trigger: 'change',},],
})
</script>
普通输入框
一键清空属性 clearable
<template><el-inputv-model="input"style="width: 240px"placeholder="Please input"clearable/>
</template><script lang="ts" setup>
import { ref } from 'vue'
const input = ref('')
</script>
文本域
文本域高度可通过 rows
属性控制
<template><el-inputv-model="textarea"style="width: 240px":rows="2"type="textarea"placeholder="Please input"/>
</template><script lang="ts" setup>
import { ref } from 'vue'
const textarea = ref('')
</script>
使用input-style修改组件
<el-input v-model="ycwtlbText" style="width: 100%;height: 250px" type="textarea"input-style="background-color: transparent;height: 100%" disabled />
日期选择
<!-- 普通日期 -->
<el-date-picker v-model="clsxxDate" type="date" align="center" format="YYYY-MM-DD" value-format="YYYY-MM-DD"/>
<!-- 选择月 -->
<el-date-picker v-model="value5" type="month" placeholder="Pick a month" format="YYYY-MM" value-format="YYYY-MM" />
数字输入框
<!-- min和max设置最大最小值 step设置步进值 precision设置精度 -->
<el-input-number v-model="fdnum" :min="0" :max="1" :step="0.01" :precision="2"/>
<!-- 带前缀 --><el-input-number v-model="num" :min="1" :max="10"><template #prefix><span>¥</span></template></el-input-number>
<!-- 带后缀,有问题,不生效 --><el-input-number v-model="num" :min="1" :max="10"><template #suffix><span>RMB</span></template></el-input-number>
<!-- 建议这种 -->
<el-input type="number" v-model="yssx" style="width: 120px;"><template #suffix> <span>MW</span> </template>
</el-input>
下拉选择框
<el-select v-model="value" value-key="id" placeholder="Select" style="width: 240px"><el-option v-for="item in options" :key="item.id" :label="item.label" :value="item"/>
</el-select>
const value = ref<any>()
注意 value-key=“id”
分割线
普通横向
<template><div><!-- 普通使用(横向) --><el-divider /><!-- 左侧添加字 --><el-divider content-position="left">Rabindranath Tagore</el-divider><!-- 中间添加图标 --><el-divider><el-icon><star-filled /></el-icon></el-divider><!-- 右侧添加字 --><el-divider content-position="right">Rabindranath Tagore</el-divider><!-- 虚线1 --><el-divider border-style="dashed" /><!-- 虚线2 --><el-divider border-style="dotted" /><!-- 虚线3 --><el-divider border-style="double" /><!-- 垂直 --><el-divider direction="vertical" /></div>
</template>
Tree树
带搜索框的树
<template><el-inputv-model="filterText"style="width: 240px"placeholder="Filter keyword"/><el-treeref="treeRef"style="max-width: 600px"class="filter-tree":data="data":props="defaultProps"default-expand-all:filter-node-method="filterNode"highlight-current/>
</template><script lang="ts" setup>
import { ref, watch } from 'vue'
import { ElTree } from 'element-plus'interface Tree {[key: string]: any
}const filterText = ref('')
const treeRef = ref<InstanceType<typeof ElTree>>()const defaultProps = {children: 'children',label: 'label',
}watch(filterText, (val) => {treeRef.value!.filter(val)
})const filterNode = (value: string, data: Tree) => {if (!value) return truereturn data.label.includes(value)
}const data: Tree[] = [{id: 1,label: 'Level one 1',children: [{id: 4,label: 'Level two 1-1',children: [{id: 9,label: 'Level three 1-1-1',},{id: 10,label: 'Level three 1-1-2',},],},],},{id: 2,label: 'Level one 2',children: [{id: 5,label: 'Level two 2-1',},{id: 6,label: 'Level two 2-2',},],},{id: 3,label: 'Level one 3',children: [{id: 7,label: 'Level two 3-1',},{id: 8,label: 'Level two 3-2',},],},
]
</script>
高亮选择节点
添加属性 highlight-current
节点选择事件
@current-change=“changeLlxSelect”
<el-tree ref="treeRef" style="max-width: 100%" class="filter-tree" :data="treeData":props="defaultProps" default-expand-all :filter-node-method="filterNode"@current-change="changeLlxSelect" highlight-current />
<script>
function changeLlxSelect(val: any, obj: any) {if (obj.isLeaf) {console.log(val)console.log(obj)initSelectedLlx();}
}
</script>
默认选择
el-tree
组件使用 default-checked-keys
属性来设置默认选中的节点。
卡片
<!-- 阴影效果 --><el-card style="width: 480px" shadow="always">Always</el-card><el-card style="width: 480px" shadow="hover">Hover</el-card><el-card style="width: 480px" shadow="never">Never</el-card>
<!-- 带标题和页脚 -->
<el-card style="max-width: 480px"><template #header><div class="card-header"><span>Card name</span></div></template><p v-for="o in 4" :key="o" class="text item">{{ 'List item ' + o }}</p><template #footer>Footer content</template>
</el-card>
<!-- 设置内容样式 -->
<el-card style="" body-style="padding: 10px 20px !important;">
</el-card>
标签页
<template><el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick"><el-tab-pane label="User" name="first">User</el-tab-pane><el-tab-pane label="Config" name="second">Config</el-tab-pane><el-tab-pane label="Role" name="third">Role</el-tab-pane><el-tab-pane label="Task" name="fourth">Task</el-tab-pane></el-tabs>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import type { TabsPaneContext } from 'element-plus'const activeName = ref('first')const handleClick = (tab: TabsPaneContext, event: Event) => {console.log(tab, event)
}
</script><style>
.demo-tabs > .el-tabs__content {padding: 32px;color: #6b778c;font-size: 32px;font-weight: 600;
}
</style>
提示确认框
ElMessageBox.confirm('确定进行计划日期为 ' + selectedDate.value + "、计划方案为 " + selectedplan.value + '的计划编制吗?').then(() => {console.log("开始计算")
}).catch(() => {// catch error
})
时间线
可视化地呈现时间流信息。
<template><el-timeline style="max-width: 600px"><el-timeline-itemv-for="(activity, index) in activities":key="index":icon="activity.icon":type="activity.type":color="activity.color":size="activity.size":hollow="activity.hollow":timestamp="activity.timestamp">{{ activity.content }}</el-timeline-item></el-timeline>
</template><script lang="ts" setup>
import { MoreFilled } from '@element-plus/icons-vue'const activities = [{content: 'Custom icon',timestamp: '2018-04-12 20:46',size: 'large',type: 'primary',icon: MoreFilled,},{content: 'Custom color',timestamp: '2018-04-03 20:46',color: '#0bbd87',},{content: 'Custom size',timestamp: '2018-04-03 20:46',size: 'large',},{content: 'Custom hollow',timestamp: '2018-04-03 20:46',type: 'primary',hollow: true,},{content: 'Default node',timestamp: '2018-04-03 20:46',},
]
</script>
Echarts
在vue3中使用
const myChart1 = shallowRef<echarts.ECharts | null>(null);if (!myChart1.value){myChart1.value = echarts.init(document.getElementById('kt2Chart1') as HTMLDivElement);}myChart1.value.setOption(option1);// 挂载
onMounted(() => {
// 其它操作nextTick(() => {initKt2Chart();});
});// 卸载时销毁
onUnmounted(() => {myChart1 && myChart1.value.dispose();myChart1.value = null;
});
折线图
import * as echarts from 'echarts';type EChartsOption = echarts.EChartsOption;var chartDom = document.getElementById('main')!;
var myChart = echarts.init(chartDom);
var option: EChartsOption;option = {title: {text: 'Main Title',subtext: 'Sub Title',left: 'center',// top: "center",textStyle: {fontSize: 30},subtextStyle: {fontSize: 20}},xAxis: {type: 'category',data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']},yAxis: {type: 'value'},series: [{data: [150, 230, 224, 218, 135, 147, 260],type: 'line'}]
};option && myChart.setOption(option);
重复刷新图形,加上true
option && myChart.setOption(option, true);
甘特图
import * as echarts from 'echarts';type EChartsOption = echarts.EChartsOption;var chartDom = document.getElementById('main')!;
var myChart = echarts.init(chartDom);
var option: EChartsOption;option = {tooltip: {trigger: 'axis',axisPointer: {type: 'shadow'},formatter: function (params: any) {let date = new Date('2024-01-01');date.setDate(date.getDate() + params.data);return ('开始日期:' +date.toISOString().split('T')[0] +'<br />结束日期:' +date.toISOString().split('T')[0]);}// formatter: '开始日期: {c0}<br />结束日期: {c1}'},grid: {left: '3%',right: '4%',bottom: '3%',containLabel: true},yAxis: [{type: 'category',data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']}],xAxis: [{type: 'value',max: 365,min: 0,axisLabel: {formatter: function (value, index) {let date = new Date('2024-01-01');date.setDate(date.getDate() + value);return date.toISOString().split('T')[0];}}}],series: [{name: '开始',type: 'bar',stack: 'Ad',itemStyle: {opacity: 0},data: [(new Date('2024-10-05').getTime() - new Date('2024-01-01').getTime()) /(24 * 60 * 60 * 1000),100,100,100,100,100,100]},{name: '检修',type: 'bar',stack: 'Ad',emphasis: {focus: 'series'},data: [new Date('2024-10-15').getDate() - new Date('2024-10-05').getDate(),100,100,100,100,100,100]}]
};option && myChart.setOption(option);
流程图
function initChart() {const option = {tooltip: {},// legend: {// data: [// { name: '已完成', icon: 'circle', itemStyle: { color: '#67C23A' } },// { name: '执行中', icon: 'circle', itemStyle: { color: '#E6A23C' } },// { name: '错误', icon: 'circle', itemStyle: { color: '#F56C6C' } },// { name: '未执行', icon: 'circle', itemStyle: { color: '#909399' } }// ],// selectedMode: false, // 禁用图例的点击交互// top: 20,// left: 'center',// textStyle: {// fontSize: 12// }// },series: [{type: 'graph',layout: 'none',symbolSize: 50,// roam: true,roam: false,label: {show: true},edgeSymbol: ['circle', 'arrow'],edgeSymbolSize: [4, 10],edgeLabel: {fontSize: 20},data: nodes.value.map(node => ({id: node.id,name: node.name,x: node.x,y: node.y,itemStyle: {color: statusColors[node.status]},symbol: node.symbol,label: {show: true,position: 'bottom',distance: 10, // 标签与节点的距离color: '#666',fontSize: 14,fontWeight: 'bold'}})),links: edges.value.map(edge => ({source: edge.source,target: edge.target,lineStyle: {color: '#aaa'}})),}, {type: 'graph',layout: 'none',symbolSize: 50,// roam: true,roam: false,label: {show: true},edgeSymbol: ['circle', 'arrow'],edgeSymbolSize: [4, 10],edgeLabel: {fontSize: 20},z: 1,data: nodesBg.value.map(node => ({id: node.id,name: node.name,x: node.x,y: node.y,itemStyle: {color: statusColors[node.status]},symbol: node.symbol,label: {show: false,}})),},]};processChart.setOption(option);processChart.off('click');processChart.on('click', params => {if (params.componentType === 'series' && params.seriesType === 'graph') {if (params.dataType === 'node') {// console.log('点击了节点:', params.data);const nodeData = params.data as { id: string; name: string }; // 类型断言if (nodeData.id === 'checked2') {showKt3();} else if (nodeData.id === 'checked3') {showKt2();} else if (nodeData.id === 'checked4') {showKt1();}}}});
}
节点和边
// 动态更新
watch(checkList, () => {initChart();
}, { deep: true })const nodesBg = computed(() => {const nodesTmp = [];nodesTmp.push({id: 'start', name: '', x: 150, y: 180, z: 3, status: '未执行',symbol: `image://${defaultIcon}`,symbolSize: [45, 45],});if (checkList.value.length === 0) {nodesTmp.push({id: 'end', name: '', x: 700, y: 180, status: '未执行', symbol: `image://${defaultIcon}`,symbolSize: [45, 45],});return nodesTmp;}let index = 0;if (checkList.value.indexOf("checked3") > -1) {index++;// 基于预机组组合和前瞻调度的储能调峰计划优化算法(课题二)nodesTmp.push({id: 'checked3', name: "", x: 150 + index * 150, y: 180, status: '未执行', symbol: `image://${defaultIcon}`,symbolSize: [45, 45],})}if (checkList.value.indexOf("checked2") > -1) {index++;// 省网非市场机组日前出力计划的自动编排(课题三)nodesTmp.push({id: 'checked2', name: "", x: 150 + index * 150, y: 180, status: '未执行', symbol: `image://${defaultIcon}`,symbolSize: [45, 45],})}if (checkList.value.indexOf("checked4") > -1) {index++;// 日前多时段交流最优潮流技术(课题一)nodesTmp.push({id: 'checked4', name: "", x: 150 + index * 150, y: 180, status: '未执行', symbol: `image://${defaultIcon}`,symbolSize: [45, 45],})}index++;nodesTmp.push({id: 'end', name: '结果发布', x: 150 + index * 150, y: 180, status: '未执行', symbol: `image://${defaultIcon}`,symbolSize: [45, 45],});return nodesTmp;
})
const nodes = computed(() => {const nodesTmp = [];nodesTmp.push({id: 'start', name: '数据准备', x: 150, y: 180, z: 2, status: '未执行',symbol: `image://${icon1}`,symbolSize: [40, 40],});if (checkList.value.length === 0) {nodesTmp.push({id: 'end', name: '结果发布', x: 700, y: 180, status: '未执行',symbol: `image://${icon5}`,symbolSize: [40, 40],});return nodesTmp;}let index = 0;if (checkList.value.indexOf("checked3") > -1) {index++;// 基于预机组组合和前瞻调度的储能调峰计划优化算法(课题二)nodesTmp.push({id: 'checked3', name: "储能预优化", x: 150 + index * 150, y: 180, status: '未执行',symbol: `image://${icon2}`,symbolSize: [40, 40],})}if (checkList.value.indexOf("checked2") > -1) {index++;// 省网非市场机组日前出力计划的自动编排(课题三)nodesTmp.push({id: 'checked2', name: "发电计划正式优化(SCUC)", x: 150 + index * 150, y: 180, status: '未执行',symbol: `image://${icon3}`,symbolSize: [40, 40],})}if (checkList.value.indexOf("checked4") > -1) {index++;// 日前多时段交流最优潮流技术(课题一)nodesTmp.push({id: 'checked4', name: "发电计划精细化修正(OPF)", x: 150 + index * 150, y: 180, status: '未执行',symbol: `image://${icon4}`,symbolSize: [40, 40],})}index++;nodesTmp.push({id: 'end', name: '结果发布', x: 150 + index * 150, y: 180, status: '未执行',symbol: `image://${icon5}`,symbolSize: [40, 40],});return nodesTmp;
})const edges = computed(() => {const edges = [];if (checkList.value.indexOf("checked3") > -1) {edges.push({ source: 'start', target: 'checked3' });}if (checkList.value.indexOf("checked2") > -1) {edges.push({ source: edges.length === 0 ? 'start' : edges[edges.length - 1].target, target: 'checked2' });}if (checkList.value.indexOf("checked4") > -1) {edges.push({ source: edges.length === 0 ? 'start' : edges[edges.length - 1].target, target: 'checked4' });}edges.push({ source: edges.length === 0 ? 'start' : edges[edges.length - 1].target, target: 'end' });return edges;
})
引入图片,使用// @ts-ignore抑制警告
// @ts-ignore
import defaultIcon from '@/assets/images/process/default.png';
Vue3
生命周期函数
- beforeCreate
- 组件实例刚被创建,数据观测和事件配置尚未进行。
- 不可访问
data
、computed
、methods
和生命周期中的this
。
- created
- 组件实例已被创建,数据观测和事件配置已完成。
- 这时可以访问
data
、computed
、methods
和生命周期中的this
,但 DOM 尚未被挂载。
- beforeMount
- 在挂载开始之前被调用,此时模板已编译到虚拟 DOM 但尚未插入到 DOM 中。
- mounted
- 组件挂载完成后调用,此时可以访问到真实的 DOM 元素。
- 常用于进行数据请求或其他需要访问 DOM 的操作。
- beforeUpdate
- 在数据变化时,组件重新渲染之前调用。
- 常用于做一些操作,比如在更新之前保存数据的状态。
- updated
- 组件重新渲染后调用。
- 此时可以根据更新后的状态进行进一步的操作。
- beforeUnmount
- 组件卸载之前调用,此时可以做一些清理工作,比如取消定时器或解绑事件。
- unmounted
- 组件卸载后调用。
- 此时可以确认组件已被完全卸载。
- activated (keep-alive 组件中)
- 当组件被激活时调用。
- 适用于使用
<keep-alive>
包裹的组件。
- deactivated (keep-alive 组件中)
- 当组件被停用时调用。
- 适用于使用
<keep-alive>
包裹的组件。
ref函数
是Vue3中的一个函数,一般用于将基本类型数据处理成响应式数据
作用:定义一个基本类型的响应式数据,只有数据成为响应式数据,这样当数据变化时,才能被检测到
使用时需要从vue中引入
<script>import { ref } from "vue"; // 引入响应式函数ref...
</script>
语法:const xxx = ref(数据变量)
结果:返回一个RefImpl的引用对象,获取时为xxx.value
在页面模板使用数据直接使用插值表达式,不需要加value,例如 姓名:{{ name }}
接受的数据可以是基本类型,此时的原理是给数据设置get和set监听函数
也可以是对象类型,此时的原理是Proxy
reactive函数
定义一个对象类型的响应式数据,返回一个Proxy实例对象,不能用于基本类型
语法:const 代理对象 = reactive(源对象)
接收一个对象或数组,返回一个代理对象
使用时先从vue中引入
reactive和ref的对比
- 数据定义角度:
- ref用来定义基本数据类型,也可以定义对象或数组,但内容还是通过reactive转换为代理对象
- reactive用来定义对象或数组
- 原理角度:
- ref通过get和set监听函数实现数据的响应式,是数据劫持
- reactive通过代理实现数据响应式的,并通过Reflect来操作源对象数据
- 使用角度:
- ref定义的数据操作时需要使用value,而插值表达式中不需要value
- reactive定义的数据都不需要value
setup语法糖
是Vue3引入的新特性之一,是组合式API的核心部分,旨在简化组件逻辑的组织和管理
setup
函数概述
setup
函数是 Vue 3 组件的一个新的生命周期钩子,它在组件创建之前执行,并且是在创建组件实例之后、模板编译之前调用的。它主要用于设置组件的响应式状态、计算属性和方法。
基本用法
html<template><div><p>Message: {{ message }}</p><button @click="increment">Increment</button><p>Count: {{ count }}</p></div>
</template><script>
import { ref, computed } from 'vue';export default {setup() {// 响应式状态const count = ref(0);// 计算属性const message = computed(() => `The count is ${count.value}`);// 方法const increment = () => {count.value++;};// 返回模板需要的数据和方法return {count,message,increment};}
}
</script>
setup
函数的特点
- 响应式状态:
- 使用 Vue 3 提供的响应式 API,如
ref
和reactive
,在setup
函数中定义组件的响应式状态。 ref
用于定义基本类型的响应式数据,而reactive
用于定义复杂类型的响应式数据。
- 使用 Vue 3 提供的响应式 API,如
- 计算属性:
- 使用
computed
函数定义计算属性,类似于 Vue 2 中的计算属性。
- 使用
- 方法:
- 在
setup
函数中定义的方法可以直接在模板中使用。
- 在
- 返回值:
setup
函数需要返回一个对象,该对象的属性和方法将暴露给模板(<template>
)和其他组件的上下文。
- 生命周期钩子:
- 通过
onMounted
、onUpdated
、onUnmounted
等 API 在setup
中使用生命周期钩子。
- 通过
相比 Options API 的优势
- 逻辑复用:
setup
允许通过组合函数(composable functions)来复用逻辑,使组件逻辑更加模块化和可重用。
- 类型推断:
- 更好地支持 TypeScript 的类型推断和类型检查。
- 更清晰的组织:
- 使组件的逻辑更加清晰和集中,尤其是对于大型组件或复杂逻辑。
例子:组合函数
可以创建一个组合函数来封装和复用逻辑:
javascript// useCounter.js
import { ref, computed } from 'vue';export function useCounter() {const count = ref(0);const increment = () => count.value++;const message = computed(() => `The count is ${count.value}`);return {count,message,increment};
}
然后在组件中使用这个组合函数:
<template><div><p>Message: {{ message }}</p><button @click="increment">Increment</button><p>Count: {{ count }}</p></div>
</template><script>
import { useCounter } from './useCounter';export default {setup() {const { count, message, increment } = useCounter();return {count,message,increment};}
}
</script>
总结
setup
函数是 Vue 3 中 Composition API 的核心特性,它提供了一个新的方式来组织组件的逻辑,使代码更加模块化和清晰。通过在 setup
中定义响应式状态、计算属性和方法,并将它们返回给模板,可以更好地管理和复用组件逻辑。
计算属性 computed properties
在 Vue 3 中,计算属性(computed properties)是一种基于响应式数据进行计算的属性。它们类似于 Vue 2 中的计算属性,但在 Vue 3 的 Composition API 中也有对应的实现。
计算属性的主要特点是它们的值是基于依赖的响应式数据自动计算和缓存的,只在相关依赖发生变化时才会重新计算。
计算属性的特点
- 基于依赖进行计算: 计算属性的值是基于其他响应式数据自动计算的,当这些数据发生变化时,计算属性会重新计算其值。
- 缓存: 计算属性会缓存其计算结果,只有当其依赖的数据发生变化时才会重新计算。这使得计算属性比直接在模板中写表达式更高效,避免了不必要的重复计算。
- 简化模板: 计算属性可以将复杂的逻辑提取到 JavaScript 代码中,使模板代码更简洁和易读。
如何使用计算属性
在 Vue 3 中,计算属性是通过 computed
函数来创建的。下面是一个简单的例子:
<template><div><p>Full Name: {{ fullName }}</p><input v-model="firstName" placeholder="First Name" /><input v-model="lastName" placeholder="Last Name" /></div>
</template><script>
import { ref, computed } from 'vue';export default {setup() {const firstName = ref('');const lastName = ref('');// 定义计算属性const fullName = computed(() => {return `${firstName.value} ${lastName.value}`;});return {firstName,lastName,fullName};}
}
</script>
在这个例子中,fullName
是一个计算属性,它会根据 firstName
和 lastName
的值自动计算和更新。
计算属性的用处
- 数据变换: 计算属性可以用来格式化或变换数据。例如,将日期格式化为特定格式,或计算某个值的总和。
- 条件计算: 可以根据多个响应式数据的值计算一个复杂的条件,并在模板中直接使用计算结果。
- 提高性能: 由于计算属性是基于依赖缓存的,所以它们在依赖的数据没有变化时不会重新计算,避免了不必要的重复计算,从而提高性能。
计算属性和网络请求
计算属性不适合用于执行网络请求。网络请求通常需要在组件的生命周期中进行,并且可能会有副作用,比如修改数据或处理异步操作。通常,你会在 setup
函数中使用 Vue 3 的 watch
、watchEffect
或生命周期钩子(如 onMounted
)来进行网络请求。
例如,如果你要在页面加载时进行网络请求,可以使用 onMounted
钩子:
import { ref, onMounted } from 'vue';
import axios from 'axios';export default {setup() {const data = ref(null);const error = ref(null);onMounted(async () => {try {const response = await axios.get('https://api.example.com/data');data.value = response.data;} catch (err) {error.value = err;}});return {data,error};}
}
在这个例子中,onMounted
钩子用于在组件加载完成后执行网络请求。计算属性则不适合这种场景,因为它们不支持异步操作或副作用。
总结
- 计算属性:在 Vue 3 中,计算属性通过
computed
函数创建,用于基于响应式数据进行高效的计算和缓存。它们适用于将复杂逻辑从模板中分离出来,并且在依赖的数据没有变化时不会重复计算。 - 网络请求:网络请求应在组件的生命周期钩子(如
onMounted
)或watch
、watchEffect
中处理,不适合在计算属性中执行,因为计算属性不支持异步操作。
scoped,在style标签中
vue为了防止css样式污染,在每个组件中提供了 scoped
属性进行限定css作用域;
当 <style>
标签有 scoped 属性时,它的 CSS 只作用于当前组件中的元素。
scope,在template标签中
在 Vue 中,<template>
标签内的 #default="scope"
是一个作用域插槽的语法。这里的 scope
是一个对象,包含了当前行的数据和其他一些有用的信息。具体来说:
scope.row
:当前行的数据对象。scope.$index
:当前行的索引。scope.column
:当前列的对象。scope.store
:表格的 store 对象,包含了一些内部状态和方法。
在你的代码中,scope.row
代表了当前行的数据对象,你可以通过它访问该行的所有字段。例如,handleUpdate(scope.row)
方法会被调用,并且传递当前行的数据作为参数。
总结一下,scope
是一个包含当前行数据和其他信息的对象,通过它可以方便地获取和操作表格中的数据。
emit
在 Vue 3 中,emit
是一个用于子组件向父组件传递数据的方法。
通过 emit
,子组件可以触发自定义事件,并将数据传递给父组件。
以下是 emit
的详细用法:
-
声明自定义事件
在子组件中,可以通过
emits
选项来声明可以触发的自定义事件。这有助于提高代码的可读性和维护性。
<template> <button @click="handleClick">点击我</button>
</template>
<script> export default { emits: ['customEvent'], // 声明可以触发的自定义事件 methods: { handleClick() { this.$emit('customEvent', '这是传递的数据'); // 触发自定义事件并传递数据 } } }
</script>
2.使用 setup
函数 在使用 setup
函数的组合式 API 中,可以通过 context.emit
来触发自定义事件。
<template> <button @click="handleClick">点击我</button>
</template>
<script> import { defineComponent } from 'vue'; export default defineComponent({ emits: ['customEvent'], // 声明可以触发的自定义事件 setup(props, context) { const handleClick = () => { context.emit('customEvent', '这是传递的数据'); // 触发自定义事件并传递数据 }; return { handleClick }; } });
</script>
- 在父组件中监听自定义事件 父组件可以通过
v-on
指令或@
简写形式来监听子组件触发的自定义事件。
<template> <ChildComponent @customEvent="handleCustomEvent" />
</template>
<script> import ChildComponent from './ChildComponent.vue'; export default { components: { ChildComponent }, methods: { handleCustomEvent(data) { console.log(data); // 输出: 这是传递的数据 } } }
</script>
- 动态绑定事件 可以在父组件中使用动态绑定来监听子组件的自定义事件。
<template> <ChildComponent v-on="listeners" />
</template>
<script> import ChildComponent from './ChildComponent.vue'; export default { components: { ChildComponent }, data() { return { listeners: { customEvent: this.handleCustomEvent } }; }, methods: { handleCustomEvent(data) { console.log(data); // 输出: 这是传递的数据 } } }
</script>
- 使用修饰符 Vue 3 支持在
v-on
指令上使用修饰符,如.once
和.native
。
.once
:事件只触发一次。
.native
:监听组件根元素的原生事件。
<template><ChildComponent @customEvent.once="handleCustomEvent" />
</template>
<script> import ChildComponent from './ChildComponent.vue'; export default { components: { ChildComponent }, methods: { handleCustomEvent(data) { console.log(data); // 输出: 这是传递的数据 } } }
</script>
总结
emit
用于子组件向父组件传递数据。- 可以通过
emits
选项声明可以触发的自定义事件。 - 在
setup
函数中使用context.emit
触发自定义事件。 - 父组件通过
v-on
或@
监听子组件的自定义事件。 - 支持动态绑定和修饰符的使用。
watch
在 Vue 3 中,watch
是一个响应式侦听器,用于监听数据的变化。当被监听的数据变化时,watch
会执行相应的回调函数。它通常用于处理副作用操作,如 API 请求或操作 DOM。
使用方法:
watch
主要有两种使用方式:
- 监听单个响应式数据或计算属性:
import { ref, watch } from 'vue';const count = ref(0);watch(count, (newVal, oldVal) => {console.log(`count changed from ${oldVal} to ${newVal}`);
});count.value = 1; // 输出: count changed from 0 to 1
- 监听多个数据或计算属性:
import { ref, computed, watch } from 'vue';const a = ref(1);
const b = ref(2);
const sum = computed(() => a.value + b.value);watch([a, b], ([newA, newB], [oldA, oldB]) => {console.log(`a changed from ${oldA} to ${newA}`);console.log(`b changed from ${oldB} to ${newB}`);
});
watch
的参数:
-
第一个参数:可以是一个响应式数据、计算属性或数组(监听多个数据)。
-
第二个参数:回调函数,接收
newVal
(新值)和oldVal
(旧值)作为参数。 -
可选的第三个参数
:配置选项,常见的选项有:
immediate: true
:在侦听开始时立即执行一次回调。deep: true
:深度侦听嵌套对象或数组的变化。
示例:立即执行和深度监听
import { ref, watch } from 'vue';const user = ref({ name: 'John', age: 30 });watch(user, (newVal, oldVal) => {console.log('User changed:', newVal);
}, { deep: true, immediate: true });
总结:
watch
用于响应式数据变化时执行副作用操作。- 可以监听单个或多个数据。
- 支持深度监听、立即执行等配置选项。
SCSS
SCSS(Sassy CSS)是一种CSS预处理器,它是Sass(Syntactically Awesome Style Sheets)语法的一种扩展。
语法
使用大括号和分号,语法类似于CSS。例如:
.container {color: blue;.button {background-color: red;}
}
CSS:遵循标准的CSS语法,没有嵌套和变量等特性。
嵌套
-
SCSS:支持嵌套规则,使得样式层次更加清晰。
-
CSS:没有嵌套的能力,必须逐层定义选择器。
变量
-
SCSS
:可以定义变量,方便管理颜色、字体等重复使用的值。
scss$primary-color: blue; .header {color: $primary-color; }
-
CSS:原生CSS也支持变量(CSS自定义属性),但在SCSS中使用更为广泛。
混入(Mixins)
-
SCSS:支持混入,可以定义一组样式,并在多个选择器中复用。
scss@mixin border-radius($radius) {border-radius: $radius; }.box {@include border-radius(10px); }
-
CSS:没有混入的概念。
运算
-
SCSS
:支持数学运算,可以直接在样式中进行加减乘除。
scss.box {width: 100px + 20px; }
-
CSS:原生CSS不支持这样的运算。
导入
- SCSS:可以使用
@import
导入其他SCSS文件,并自动处理依赖。 - CSS:虽然也可以用
@import
导入,但在性能和管理上不如SCSS高效。
总结
SCSS提供了许多增强功能,使得样式表的编写和管理更加高效和模块化。它在大型项目中尤其有用,因为它可以提高代码的可读性和可维护性。最终,SCSS会被编译成标准的CSS,以供浏览器使用。
Restful风格
在 Java 开发中,前后端分离时,RESTful 风格是一种设计 Web 服务的架构风格,它基于 HTTP 协议,利用 HTTP 方法(GET、POST、PUT、DELETE 等)来操作资源。以下是 RESTful 风格的简要讲解:
资源(Resources)
资源 是 RESTful 设计的核心概念,通常对应于数据库中的表或业务实体。
资源通过 URI(Uniform Resource Identifier)进行标识,例如 /users
表示用户资源。
HTTP 方法
- GET:用于获取资源,例如
GET /users
获取所有用户。 - POST:用于创建资源,例如
POST /users
创建一个新用户。 - PUT:用于更新资源,例如
PUT /users/1
更新 ID 为 1 的用户。 - DELETE:用于删除资源,例如
DELETE /users/1
删除 ID 为 1 的用户。 - PATCH:用于部分更新资源,例如
PATCH /users/1
更新 ID 为 1 的用户的某个属性
状态码
- 200 OK:请求成功。
- 201 Created:资源已创建。
- 204 No Content:请求成功,但没有返回内容。
- 400 Bad Request:客户端请求有误。
- 401 Unauthorized:未授权。
- 403 Forbidden:禁止访问。
- 404 Not Found:资源未找到。
- 500 Internal Server Error:服务器内部错误。
无状态
RESTful 服务是无状态的,即每次请求都必须包含所有必要的信息,服务器不会保存任何客户端的状态信息。
统一接口
所有的操作都通过统一的接口进行,即 HTTP 方法和 URI。
假设我们有一个用户管理系统,以下是一些 RESTful 风格的 API 示例:
- 获取所有用户
http GET /users
- 获取单个用户
http GET /users/{id}
- 创建用户
http POST /users
- 更新用户
http PUT /users/{id}
- 删除用户
http DELETE /users/{id}
好处
- 简洁明了:URI 和 HTTP 方法的结合使得 API 设计简洁明了。
- 可扩展性强:易于添加新的资源和操作。
- 无状态:每个请求都是独立的,便于缓存和分布式处理。
- 标准化:遵循标准的 HTTP 方法和状态码,便于理解和使用。
实现
在 Java 开发中,可以使用 Spring Boot 等框架来实现 RESTful 风格的 API。以下是一个简单的 Spring Boot 控制器示例:
@RestController @RequestMapping("/users")
public class UserController { @GetMapping public List<User> getAllUsers() { // 返回所有用户 } @GetMapping("/{id}") public User getUser(@PathVariable Long id) { // 返回指定 ID 的用户 } @PostMapping public User createUser(@RequestBody User user) { // 创建新用户 } @PutMapping("/{id}") public User updateUser(@PathVariable Long id, @RequestBody User user) { // 更新指定 ID 的用户 } @DeleteMapping("/{id}") public void deleteUser(@PathVariable Long id) { // 删除指定 ID 的用户 }
}