设备列表
背景图的样式
background-image: url('@/assets/drawer-bg.jpg'); /* 使用本地图片 */
background-size: cover; /* 背景填充整个容器 */
background-position: center; /* 居中显示 */
background-repeat: no-repeat; /* 不重复 */
::v-deep(.el-drawer__body) {
background-image: url(@/assets/nbBg.svg);
}
正则表达式
^8986(11|06)\d{13}$|^898604\d{14}$
const regex = /^8986(11|06)\d{13}$|^898604\d{14}$/;
^8986
:匹配必须以 8986
开头的字符串。
(11|06)
:匹配 8986
后面的部分,如果是 11
或 06
,则表示后面必须跟 13 位数字(总共 19 位)。
\d{13}
:表示跟随 13 位数字。
|
:表示或者的关系。
898604
:匹配以 898604
开头的字符串,后面必须跟 14 位数字(总共 20 位)。
\d{14}
:表示跟随 14 位数字。
$
:表示字符串的结尾。
const regex = /^8986(11|06)\d{13}$|^898604\d{14}$/;
console.log(regex.test("8986111234567890123")); // true
console.log(regex.test("8986061234567890123")); // true
console.log(regex.test("89860412345678901234")); // true
console.log(regex.test("8986121234567890123")); // false
数据库删除表的数据
databae Ctrl+y 然后点击提交 ctrl+enter
Echart Y轴拐点
{
name: '高温',
type: 'line',
symbol: 'circle',
symbolSize: 8,
emphasis: {
focus: 'series',
},
itemStyle: {
borderColor: 'rgba(255,255,255)',
borderWidth: 2,
},
data: yAxisData.value[0].data,
smooth: true,
markPoint: {
symbol: 'diamond', // 标记样式,比如菱形
symbolSize: 12, // 标记大小
label: {
show: true,
formatter: (params: any) => `断点`, // 标注文字
color: '#fff',
},
itemStyle: {
color: 'rgba(255, 0, 0, 0.8)', // 标注的颜色
},
data: xAxisDataInterrupt.value.map((xValue: any) => {
// 找到 x 值对应的 y 值
const xIndex = xAxisData.value.indexOf(xValue);
if (xIndex !== -1) {
return {
coord: [xValue, yAxisData.value[0].data[xIndex]], // 坐标点
};
}
return null; // 如果未找到,忽略该点
}).filter((point: any) => point !== null), // 过滤掉无效点
},
}
xAxisData.value.indexOf(xValue)
:
- 查找
xValue
在xAxisData.value
中的索引位置。 - 确保找到断点在
x
轴上的位置。
yAxisData.value[0].data[xIndex]
:
- 根据
xIndex
获取对应的y
值,用于标记点的coord
。
filter
:
- 如果
xValue
不存在于xAxisData.value
,则忽略该点。
markPoint.data
:
- 动态生成符合条件的标记点。
表格导出Excel
使用 npm 安装 xlsx
和文件下载工具 file-saver
:
npm install xlsx file-saver
npm install --save-dev @types/file-saver
以下是一个使用 xlsx
导出表格数据到 Excel 的完整实现
<template>
<div>
<el-table :data="tableData" border style="width: 100%">
<el-table-column prop="name" label="姓名" width="120"></el-table-column>
<el-table-column prop="age" label="年龄" width="80"></el-table-column>
<el-table-column prop="address" label="地址"></el-table-column>
</el-table>
<el-button type="primary" @click="exportToExcel">导出到 Excel</el-button>
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import * as XLSX from "xlsx";
import { saveAs } from "file-saver";
export default defineComponent({
name: "ExportTable",
data() {
return {
tableData: [
{ name: "张三", age: 25, address: "北京市" },
{ name: "李四", age: 30, address: "上海市" },
{ name: "王五", age: 28, address: "广州市" },
],
};
},
methods: {
exportToExcel() {
// 1. 将表格数据转换为工作表
const worksheet = XLSX.utils.json_to_sheet(this.tableData);
// 2. 创建工作簿并附加工作表
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, "表格数据");
// 3. 生成 Excel 文件并触发下载
const excelBuffer = XLSX.write(workbook, {
bookType: "xlsx",
type: "array",
});
// 4. 使用 file-saver 保存文件
const blob = new Blob([excelBuffer], {
type: "application/octet-stream",
});
saveAs(blob, "表格数据.xlsx");
},
},
});
</script>
<style scoped>
.el-table {
margin-bottom: 20px;
}
</style>
代码解析
1. 安装依赖
xlsx
: 用于生成 Excel 文件。file-saver
: 用于触发文件下载。
2. 步骤说明
- 将 JSON 数据转换为工作表:
XLSX.utils.json_to_sheet(data)
。 - 创建工作簿并附加工作表:使用
XLSX.utils.book_new()
和XLSX.utils.book_append_sheet()
。 - 导出 Excel 文件:使用
XLSX.write()
生成文件,类型为array
。 - 触发文件下载:将文件通过
Blob
格式保存并使用saveAs()
下载。
优化建议
- 支持字段映射: 如果你需要将字段名映射为更友好的表头名称,可以自定义字段映射。例如
const fields = {
name: "姓名",
age: "年龄",
address: "地址",
};
const mappedData = tableData.map(item => {
const mappedItem: any = {};
for (const key in fields) {
mappedItem[fields[key]] = item[key];
}
return mappedItem;
});
const worksheet = XLSX.utils.json_to_sheet(mappedData);
动态文件名:
ts复制编辑const filename = `表格数据_${new Date().toLocaleDateString()}.xlsx`;
saveAs(blob, filename);
样式支持: 如果需要更丰富的样式,可以结合更高级的库,如 exceljs
。
父组件使用子组件中的数据
父组件
<el-button color="#626aef" :dark="false" plain style="padding: 0 10px;" @click="handleChangeScene" v-show="sceneNumber === 1"
class="btn-pue">
<ele-Promotion style="width: 1em; height: 1em; margin-right: 2px;"></ele-Promotion>
<span>PUE查询</span>
</el-button>
<el-button color="#626aef" :dark="false" plain style="padding: 0 10px;" @click="handleChangeSceneBack" v-show="sceneNumber === 2"
class="btn-pue">
<ele-Promotion style="width: 1em; height: 1em; margin-right: 2px;"></ele-Promotion>
<span>返回</span>
</el-button>
<TablePower v-if="idNumber === '3'" :tableDataPower="tableDataPower" :tableLoading="tableLoading" ref="tableDataPowerRef"></TablePower>
const tableDataPowerRef = ref()
const sceneNumber = computed(()=>{
return tableDataPowerRef.value?.scene ?? 1; // 加上 ?. 和默认值处理
})
子组件
<div class="table" v-show="scene === 1">
<div class="pue" v-show="scene === 2">
import { onMounted, ref, computed, markRaw, nextTick } from 'vue'
const scene = ref(2)
defineExpose({scene})
导出Excel工具类
准备所有数据
确保后端或接口支持返回完整的表格数据。分页通常只返回当前页的数据,所以需要额外的接口或参数来获取全部数据。
async function fetchAllData() {
try {
const response = await api.get('/your-api-endpoint', {
params: {
page: 1, // 默认请求第一页
pageSize: totalCount, // 请求所有数据
},
});
return response.data; // 返回所有数据
} catch (error) {
console.error('获取所有数据失败:', error);
}
}
添加导出功能
在组件中添加一个按钮,点击后获取所有数据并触发导出逻辑。
<template>
<el-button type="primary" @click="exportAllData">导出全部数据</el-button>
<el-table :data="tableDataWind" style="width: 100%" max-height="680" highlight-current-row>
<el-table-column prop="sn" label="设备编号" width="240"></el-table-column>
<el-table-column prop="winds" label="风速(Hz)" width="200"></el-table-column>
<el-table-column prop="etemp" label="环境温度(℃)" width="200"></el-table-column>
<el-table-column prop="sigQlty" width="200" label="信号强度(dBm)" />
<el-table-column prop="time" label="采集时间" align="center" />
</el-table>
</template>
<script setup>
import { ref } from 'vue';
import { ElMessage } from 'element-plus';
import { exportToExcel } from './utils'; // 假设这里有一个工具函数用于导出 Excel
const tableDataWind = ref([]);
const totalCount = 1000; // 假设总数据条目数
const exportAllData = async () => {
try {
// 获取所有数据
const allData = await fetchAllData();
// 检查是否获取成功
if (!allData || allData.length === 0) {
ElMessage.error('未能获取到数据');
return;
}
// 调用工具函数导出为 Excel
exportToExcel({
data: allData,
filename: '表格导出.xlsx',
columns: [
{ header: '设备编号', key: 'sn' },
{ header: '风速(Hz)', key: 'winds' },
{ header: '环境温度(℃)', key: 'etemp' },
{ header: '信号强度(dBm)', key: 'sigQlty' },
{ header: '采集时间', key: 'time' },
],
});
ElMessage.success('数据导出成功');
} catch (error) {
console.error('导出数据失败:', error);
ElMessage.error('导出失败');
}
};
</script>
创建导出工具函数
npm install xlsx file-saver
工具函数(utils.js
):
import * as XLSX from 'xlsx';
import { saveAs } from 'file-saver';
export function exportToExcel({ data, filename, columns }) {
const sheetData = data.map((row) => {
const sheetRow = {};
columns.forEach((col) => {
sheetRow[col.header] = row[col.key];
});
return sheetRow;
});
const worksheet = XLSX.utils.json_to_sheet(sheetData);
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1');
const excelBuffer = XLSX.write(workbook, { bookType: 'xlsx', type: 'array' });
const blob = new Blob([excelBuffer], {
type: 'application/octet-stream',
});
saveAs(blob, filename);
}
附加优化
加载状态: 添加加载动画或按钮禁用逻辑,避免用户多次点击按钮:
ts<el-button :loading="loading" @click="exportAllData">导出全部数据</el-button>
分批加载数据: 如果数据量特别大,可以在前端使用分页多次请求并合并数据。
确保在点击导出按钮时,能够成功获取所有数据并正确生成文件。
如果接口数据量较大,可以考虑后端直接生成文件并提供下载地址。
时间任务
使用 vue3-cron
之类的第三方库来实现 Cron 表达式选择
npm install vue3-cron
<template>
<vue3-cron v-model="cronValue" />
</template>
<script setup>
import { ref } from "vue";
import Vue3Cron from "vue3-cron";
const cronValue = ref("* * * * *");
</script>
使用 Element Plus 现有组件自定义 Cron 选择器 可以组合 el-select
、el-time-picker
和 el-date-picker
来手动创建一个 Cron 表达式选择器。
时间字符串转时区
使用 toLocaleString()
const dateStr = "2025-02-07T13:00:00.000+00:00";
const date = new Date(dateStr);
const beijingTime = date.toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" });
console.log(beijingTime); // "2025/2/7 21:00:00"
toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" })
会自动转换到东八区时间。
如果你用 moment-timezone
:
import moment from "moment-timezone";
const dateStr = "2025-02-07T13:00:00.000+00:00";
const beijingTime = moment.utc(dateStr).tz("Asia/Shanghai").format("YYYY-MM-DD HH:mm:ss");
console.log(beijingTime); // "2025-02-07 21:00:00"
使用dayjs
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import timezone from "dayjs/plugin/timezone";
dayjs.extend(utc);
dayjs.extend(timezone);
const dateStr = "2025-02-07T13:00:00.000+00:00";
const beijingTime = dayjs.utc(dateStr).tz("Asia/Shanghai").format("YYYY-MM-DD HH:mm:ss");
console.log(beijingTime); // "2025-02-07 21:00:00"
svg全局图标
/src/components/SvgIcon/index.vue
<template>
<i v-if="isShowIconSvg" class="el-icon" :style="setIconSvgStyle">
<component :is="getIconName" />
</i>
<div v-else-if="isShowLocalSvg" :style="setIconImgOutStyle">
<component :is="getIconName" :style="setIconSvgStyle" />
</div>
<div v-else-if="isShowIconImg" :style="setIconImgOutStyle">
<img :src="getIconName" :style="setIconSvgInsStyle" />
</div>
<i v-else :class="getIconName" :style="setIconSvgStyle" />
</template>
<script lang="ts">
import { computed, defineComponent } from 'vue';
// 本地svg
const customSvgIcons = import.meta.glob('@/assets/svg/*.svg', { eager: true });
export default defineComponent({
name: 'svgIcon',
props: {
// svg 图标组件名字
name: {
type: String,
},
// svg 大小
size: {
type: Number,
default: () => 14,
},
// svg 颜色
color: {
type: String,
},
},
setup(props) {
// 在线链接、本地引入地址前缀
const linesString = ['https', 'http', '/src', '/assets'];
// 获取 icon 图标名称
const getIconName = computed(() => {
if (props?.name?.startsWith('ele-')) {
return props.name; // Element Plus 图标
}
if (customSvgIcons[`/src/assets/svg/${props.name}.svg`]) {
return customSvgIcons[`/src/assets/svg/${props.name}.svg`].default;
}
return props.name; // 可能是 URL
});
// 用于判断 element plus 自带 svg 图标的显示、隐藏
const isShowIconSvg = computed(() => {
return props?.name?.startsWith('ele-');
});
// 判断是否为本地 SVG 组件
const isShowLocalSvg = computed(() => {
return !!customSvgIcons[`/src/assets/svg/${props.name}.svg`];
});
// 用于判断在线链接、本地引入等图标显示、隐藏
const isShowIconImg = computed(() => {
return linesString.find((str) => props.name?.startsWith(str));
});
// 设置图标样式
const setIconSvgStyle = computed(() => {
return `font-size: ${props.size}px;color: ${props.color};`;
});
// 设置图片样式
const setIconImgOutStyle = computed(() => {
return `width: ${props.size}px;height: ${props.size}px;display: inline-block;overflow: hidden;`;
});
// 设置图片样式
// https://gitee.com/lyt-top/vue-next-admin/issues/I59ND0
const setIconSvgInsStyle = computed(() => {
const filterStyle: string[] = [];
const compatibles: string[] = ['-webkit', '-ms', '-o', '-moz'];
compatibles.forEach((j) => filterStyle.push(`${j}-filter: drop-shadow(${props.color} 30px 0);`));
return `width: ${props.size}px;height: ${props.size}px;position: relative;left: -${props.size}px;${filterStyle.join('')}`;
});
return {
getIconName,
isShowLocalSvg,
isShowIconSvg,
isShowIconImg,
setIconSvgStyle,
setIconImgOutStyle,
setIconSvgInsStyle,
};
},
});
</script>
/src/components/SvgIcon/ELSvg.ts
import type { App} from 'vue'
import * as svg from '@element-plus/icons-vue';
import SvgIcon from '@/components/SvgIcon/index.vue'
export default function (app: App) {
app.component('svg-icon', SvgIcon)
// import.meta.glob('./svg/*.svg')
// import.meta.globEager('./svg/*.svg')
const icons = svg as any;
for (const i in icons) {
app.component(`ele-${icons[i].name}`, icons[i]);
}
app.component('SvgIcon', SvgIcon);
}
main.ts
import { createApp } from 'vue'
import pinia from './stores'
import ElementPlus from 'element-plus'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import 'element-plus/dist/index.css'
import App from './App.vue'
import router from './router'
import './styles/index.scss'
import ElSvg from './components/SvgIcon/ElSvg'
import './permission'
import VueEcharts from 'vue-echarts'
import VueAMap, { initAMapApiLoader } from '@vuemap/vue-amap';
import '@vuemap/vue-amap/dist/style.css';
import '@/utils/resizeObserver'
const app = createApp(App)
ElSvg(app)
app.component('v-chart', VueEcharts);
// 初始化vue-amap
initAMapApiLoader({
// 高德的key
key: '00266de077c5944c71bc80c4d339c1c9',
securityJsCode: 'a8de0fa258d29c6d039fc65e2c158e99', // 新版key需要配合安全密钥使用
//Loca:{
// version: '2.0.0'
//} // 如果需要使用loca组件库,需要加载Loca
});
app.use(pinia)
.use(router)
.use(ElementPlus, {
locale: zhCn,
})
.use(VueAMap)
.mount('#app')
使用
使用 Element Plus 图标
<SvgIcon name="ele-Edit" size="20" color="red" />
使用本地 SVG 组件
你的 @/assets/svg/ 目录下有 urgent.svg 文件:
<SvgIcon name="urgent" size="20" color="blue" />
使用 URL 方式
<SvgIcon name="https://example.com/icon.svg" size="20" />