day-109-one-hundred-and-nine-20230710-物模型增删改查-欢迎页图表展示-图表展示
物模型增删改查
修改一个表单列表项的方式
-
如果是一个弹框,在同一个组件中,可以给弹框中存储一个公共状态对象,之后通过那个对象中的数据来修改。
<script> export default { data() { return { // 表格相关的状态。 table: { data: [], loading: false, }, // 弹出层相关的状态。 dialogVisible: false, templateId: null, }; }, methods: { // 触发修改操作。 async triggerUpdate(row) { let { templateId, templateName, identifier, modelOrder, isChart } = row; let { type, isHistory, isMonitor, isReadonly, datatype, specs } = row; this.dialogVisible = true; this.templateId = templateId; // 把row中的信息-物模型的详细信息放在对应的表单中。 let typeItem = this.iotThingsList.find((item) => +item.value === +type); if (typeItem) { type = typeItem.label; } let isList = []; if (+isChart === 1 && type === "属性") { isList.push("图表展示"); } if (+isMonitor === 1 && type === "属性") { isList.push("实时监测"); } if (+isHistory === 1) { isList.push("历史存储"); } if (+isReadonly === 1) { isList.push("只读数据"); } this.ruleForm = { templateName, identifier, modelOrder, type, isList, datatype, min: +specs.min, max: +specs.max, unit: specs.unit, step: +specs.step, }; }, }, }; </script> <template> <!-- 表格区域 --> <el-table stripe :data="table.data" v-loading="table.loading" @selection-change="selectionChange" ref="tableIns" > <el-table-column type="selection" align="center" min-width="4%" /> <el-table-column label="图表展示" prop="isChart" :formatter="formatterWhether" align="center" min-width="8%" /> <el-table-column label="操作" min-width="10%"> <template v-slot="{ row }"> <el-link type="primary" @click="triggerUpdate(row)">修改</el-link> <el-popconfirm title="您确定要删除这一项吗?" @confirm="handleDelete(row.templateId)" > <el-link type="danger" slot="reference">删除</el-link> </el-popconfirm> </template> </el-table-column> </el-table> </template>
-
如果是另一个路由页面,那么将使用如问号传参或params传参或路径传参这类路由传参将当前项的id传递到另一个路由地址中,而另一个路由地址再通过id查询到当前表单的信息。
<script> export default { data() { return { // 表格相关的状态。 table: { data: [], loading: false, }, // 弹出层相关的状态。 dialogVisible: false, templateId: null, }; }, methods: { // 触发修改操作。 async triggerUpdate(thdID) { this.dialogVisible = true; this.templateId = thdID; // 物模型现有的信息放在各个表单中。 // ... //如果当前列表中没有。 try { let { code,row } = await this.$API.iot.queryTemplateInfo(this.templateId); if (+code === 200) { //把data中包含的信息,放在各个表单中。 let { templateId, templateName, identifier, modelOrder, isChart } = row; let { type, isHistory, isMonitor, isReadonly, datatype, specs } = row; // 把row中的信息-物模型的详细信息放在对应的表单中。 let typeItem = this.iotThingsList.find( (item) => +item.value === +type ); if (typeItem) { type = typeItem.label; } let isList = []; if (+isChart === 1 && type === "属性") { isList.push("图表展示"); } if (+isMonitor === 1 && type === "属性") { isList.push("实时监测"); } if (+isHistory === 1) { isList.push("历史存储"); } if (+isReadonly === 1) { isList.push("只读数据"); } this.ruleForm = { templateName, identifier, modelOrder, type, isList, datatype, min: +specs.min, max: +specs.max, unit: specs.unit, step: +specs.step, }; } } catch (error) { console.log(`error:-->`, error); } }, }, }; </script> <template> <!-- 表格区域 --> <el-table stripe :data="table.data" v-loading="table.loading" @selection-change="selectionChange" ref="tableIns" > <el-table-column type="selection" align="center" min-width="4%" /> <el-table-column label="图表展示" prop="isChart" :formatter="formatterWhether" align="center" min-width="8%" /> <el-table-column label="操作" min-width="10%"> <template v-slot="{ row }"> <el-link type="primary" @click="triggerUpdate(row.templateId)">修改</el-link> <el-popconfirm title="您确定要删除这一项吗?" @confirm="handleDelete(row.templateId)" > <el-link type="danger" slot="reference">删除</el-link> </el-popconfirm> </template> </el-table-column> </el-table> </template>
绑定表单列表项数据
form表单的初始数据
- Form表单
- Form第一次渲染的时候,表单中的数据是啥,Form认为这就是初始数据
- 基于 resetFields 重置数据的时候,会重置为Form认为的初始数据
- 但是这个初始值,是不一定准确的;
新增修改数据
新增修改数据一条数据
删除多条数据
表格下载
表格下载的步骤
- 先问chatGTP或网上找,如’主流的前端excel表格下载插件是?’
欢迎页图表展示
扒接口
百度地图
设备统计-骨架屏及数据绑定
新闻页-弹窗提示信息
图表展示
- Echarts的核心是canvas。
- canvas:
- 准备一个盒子,把这个盒子作为我么的画布 「需要盒子有固定的宽高」
- 准备画笔,去绘制图形
- 保存
- …
- 绘制出来的是一张图片!
Echat的安装
Echat的配置修改
Echat的封装
绘制的处理
修改后代码
- fang/f20230708/ManageSystem/src/views/iot/Template.vue
<script>
import dayjs from "dayjs";
// console.log(`dayjs('20230705 16:01:39')-->`, dayjs("20230705 16:01:39"));
import * as xlsx from 'xlsx'
console.log(`xlsx-->`, xlsx);
export default {
data() {
return {
// 相关类别的全部数据。
iotThingsList: [],
iotDataList: [],
//筛选区域需要的状态。
filter: {
templateName: "",
type: "",
},
//分页相关的状态。
page: {
pageNum: 1,
pageSize: 10,
total: 0,
},
// 表格相关的状态。
table: {
data: [],
loading: false,
},
selections: [], //当前已选项。
// 弹出层相关的状态。
dialogVisible: false,
templateId: null,
// 表单相关的状态和校验规则。
ruleForm: {
templateName: "",
identifier: "",
modelOrder: 0,
type: "属性",
isList: ["图表展示", "只读数据", "实时监测", "历史存储"],
datatype: "integer",
min: 0,
max: 100,
unit: "米",
step: 0,
},
rules: {
templateName: [
{ required: true, message: "模型名字是必填项", trigger: "blur" },
],
identifier: [
{ required: true, message: "模型标识是必填项", trigger: "blur" },
],
modelOrder: [
{ required: true, message: "模型排序值是必填项", trigger: "blur" },
],
isList: [
{ required: true, message: "至少选择一个特性", trigger: "change" },
],
},
};
},
methods: {
// 获取物模型全部类型。
async queryIotThingsList() {
let arr = await this.$API.iot.queryIotThingsType();
this.iotThingsList = Object.freeze(arr);
// console.log(`arr-->`, arr);
},
// 获取全部的数据类别。
async queryIotDataList() {
let arr = await this.$API.iot.queryIotDataType();
this.iotDataList = Object.freeze(arr);
},
//获取物模型列表数据。
async initData() {
this.table.loading = true;
// console.log(`initData-->`);
let {
page: { pageNum, pageSize },
filter: { templateName, type },
} = this;
try {
let { code, rows, total } = await this.$API.iot.queryTemplateList({
pageNum,
pageSize,
templateName,
type,
});
if (+code !== 200) {
rows = [];
total = 0;
}
//获取数据的二次格式化处理。
rows = rows.map((item) => {
try {
item.specs = JSON.parse(item.specs);
} catch (error) {
item.specs = {};
console.log(`error:-->`, error);
}
return item;
});
// console.log(`rows-->`, rows);
this.page.total = total;
this.table.data = Object.freeze(rows);
} catch (error) {
console.log(`error:-->`, error);
}
this.table.loading = false;
},
// 格式化表格列中的数据。
// 格式化是否。
formatterWhether(row, column, cellValue) {
return +cellValue === 0 ? `否` : `是`;
},
//格式化-物模型识别。
formatterThingsType(row) {
let { type } = row;
let item = this.iotThingsList.find((item) => +item.value === +type);
// console.log(`item-->`, item);
if (!item) {
return null;
}
//formatter函数可以返回基于JSX语法构建的视图。
return <el-tag type={item.class}>{item.label}</el-tag>;
},
//格式化-数据类型。
formatterDataType({ datatype }) {
let item = this.iotDataList.find((item) => item.value === datatype);
return !item ? null : item.label;
},
//格式化-创建时间。
formatterTime({ createTime }) {
return dayjs(createTime).format(`YYYY-MM-DD`);
},
// 触发修改操作。
async triggerUpdate(row) {
/* this.dialogVisible = true;
this.templateId = row.templateId;
// 物模型现有的信息放在各个表单中。
// ...
//如果当前列表中没有。
try {
let { code } = await this.$API.iot.queryTemplateInfo(this.templateId);
if (+code === 200) {
//把data中包含的信息,放在各个表单中。
}
} catch (error) {
console.log(`error:-->`, error);
} */
let {
templateId,
templateName,
identifier,
modelOrder,
type,
isChart,
isHistory,
isMonitor,
isReadonly,
datatype,
specs,
} = row;
this.dialogVisible = true;
this.templateId = templateId;
// 把row中的信息-物模型的详细信息放在对应的表单中。
let typeItem = this.iotThingsList.find((item) => +item.value === +type);
if (typeItem) {
type = typeItem.label;
}
let isList = [];
if (+isChart === 1 && type === "属性") {
isList.push("图表展示");
}
if (+isMonitor === 1 && type === "属性") {
isList.push("实时监测");
}
if (+isHistory === 1) {
isList.push("历史存储");
}
if (+isReadonly === 1) {
isList.push("只读数据");
}
this.ruleForm = {
templateName,
identifier,
modelOrder,
type,
isList,
datatype,
min: +specs.min,
max: +specs.max,
unit: specs.unit,
step: +specs.step,
};
},
// 删除指定id的记录。
async handleDelete(templateId) {
try {
let { code, msg } = await this.$API.iot.deleteTemplateInfo(templateId);
if (+code !== 200) {
// this.$message.error(msg);
this.$message.error(`删除失败,请联系管理员`);
return;
}
this.$message.success(`恭喜你,删除成功!`);
//关于在最后一页删除的处理。
let { total, pageSize, pageNum } = this.page;
let totalPage = Math.ceil(total / pageSize);
if (+pageNum === +totalPage) {
if (!Array.isArray(templateId)) {
templateId = [templateId];
}
if (templateId.length === this.table.data.length) {
//最后一页全部被删除了!
this.page.pageNum--;
}
}
this.initData();
} catch (error) {
console.log(`error:-->`, error);
}
},
// 当表格选择项发生改变后触发。
selectionChange(selections) {
// selections: 记录了目前所有选中项每一行的信息-数组。
console.log(`selections-->`, selections);
this.selections = selections;
},
//触发删除多项的按钮。
async triggerDeleteAll() {
let selections = this.selections;
if (selections.length === 0) {
this.$message.warning(`请你至少选择一项进行删除`);
return;
}
try {
await this.$confirm(
`此操作将永久删除这些信息,是否继续?`,
`温馨提示`,
{
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}
);
//点击的是确定
selections = selections.map((item) => item.templateId);
this.handleDelete(selections);
} catch (error) {
console.log(`error:-->`, error);
}
},
// 触发下载按钮。
triggerDownLoad() {
/* let data = [["编号", "名称", "标识符"], ...rows];
const worksheet = xlsx.utils.aoa_to_sheet(data);
const workbook = xlsx.utils.book_new();
xlsx.utils.book_append_sheet(workbook, worksheet, "Sheet1");
xlsx.writeFile(workbook, `wumei-template-${+new Date()}.xlsx`); */
let selections = this.selections;
if (selections.length === 0) {
this.$message.warning(`请你至少选择一项进行下载`);
return;
}
console.log(`selections-->`, selections);
//准备表格的数据
selections = selections.map(
({ templateId, templateName, identifier }) => {
return [templateId, templateName, identifier];
}
);
let data = [["编号", "物模型信息", "标识符"], ...selections];
//下载Excel。
const workbook = xlsx.utils.book_new();//创建一个新的Excel表格。
const worksheet = xlsx.utils.aoa_to_sheet(data);//把数据变为sheet数据。
xlsx.utils.book_append_sheet(workbook, worksheet, "Sheet1");//把数据插入到Excel中。
xlsx.writeFile(workbook, `fastbee-template-${+new Date()}.xlsx`);//下载表格。
this.$refs.tableIns.clearSelection()
},
// 关闭弹出层。
closeDialog() {
this.dialogVisible = false;
this.templateId = null;
//清除表单内容和校验信息。
// ...
// this.$refs.formIns.resetFields();
this.$refs.formIns.clearValidate();
this.ruleForm = {
templateName: "",
identifier: "",
modelOrder: 0,
type: "属性",
isList: ["图表展示", "只读数据", "实时监测", "历史存储"],
datatype: "integer",
min: 0,
max: 100,
unit: "米",
step: 0,
};
},
// 确认提交-修改和新增。
async submit() {
try {
await this.$refs.formIns.validate();
// this.$message.success(`哈哈`);
// 获取表单中的信息,把其准备成为接口所需要的格式。
let {
templateName,
identifier,
modelOrder,
type,
isList,
datatype,
min,
max,
unit,
step,
} = this.ruleForm;
let typeItem = this.iotThingsList.find((item) => item.label === type);
type = typeItem ? typeItem.value : 1;
let isChart = 0;
let isHistory = 0;
let isMonitor = 0;
let isReadonly = 0;
if (isList.includes("图表展示") && type === 1) {
isChart = 1;
}
if (isList.includes("实时监测") && type === 1) {
isMonitor = 1;
}
if (isList.includes("只读数据")) {
isReadonly = 1;
}
if (isList.includes("历史存储")) {
isHistory = 1;
}
let body = {
templateName,
identifier,
modelOrder,
type,
datatype,
isChart,
isHistory,
isMonitor,
isReadonly,
specs: JSON.stringify({ min, max, unit, step, type: datatype }),
};
// 向服务器发送请求。
let requestFn = this.$API.iot.insertTemplateInfo;
let tip = "新增";
//如果是修改
if (this.templateId) {
body.templateId = this.templateId;
requestFn = this.$API.iot.updateTemplateInfo;
tip = "修改";
}
let { code, msg } = await requestFn(body);
if (+code !== 200) {
// this.$message.error(`很遗憾,${tip}失败,请稍后再试~`);
this.$message.error(msg);
return;
}
this.$message.success(`恭喜你,${tip}成功!`);
this.closeDialog();
if (!this.templateId) {
this.page.pageNum = 1; //新增成功后,跳转回第一页
}
this.initData();
} catch (error) {
console.log(`error:-->`, error);
}
},
},
async created() {
//并行获取三种数据:物模型类别、数据类别、物模型列表。
this.queryIotThingsList();
this.queryIotDataList();
this.initData();
},
};
</script>
<template>
<div class="template-box">
<!-- 筛选/操作区域 -->
<div class="filter-box">
<div class="filter">
<div class="form-item">
<label>名称</label>
<el-input
placeholder="请输入物模型名称"
v-model.trim="filter.templateName"
@keydown.native.enter="initData"
/>
</div>
<div class="form-item">
<label>类别</label>
<el-select v-model="filter.type" @change="initData">
<el-option value="" label="全部" />
<el-option
v-for="item in iotThingsList"
:key="item.value"
:value="item.value"
:label="item.label"
/>
</el-select>
</div>
</div>
<div class="handler">
<el-button
type="primary"
ghost
icon="el-icon-plus"
@click="dialogVisible = true"
>新增</el-button
>
<el-button
type="danger"
ghost
icon="el-icon-minus"
@click="triggerDeleteAll"
>删除</el-button
>
<el-button
type="success"
ghost
icon="el-icon-download"
@click="triggerDownLoad"
>下载</el-button
>
</div>
</div>
<!-- 表格区域 -->
<div class="table-box">
<el-table
stripe
:data="table.data"
v-loading="table.loading"
@selection-change="selectionChange"
ref="tableIns"
>
<el-table-column type="selection" align="center" min-width="4%" />
<el-table-column
label="名称"
prop="templateName"
align="center"
min-width="8%"
/>
<el-table-column
label="标识符"
prop="identifier"
align="center"
min-width="8%"
/>
<el-table-column
label="图表展示"
prop="isChart"
:formatter="formatterWhether"
align="center"
min-width="8%"
/>
<el-table-column
label="实时监测"
prop="isMonitor"
:formatter="formatterWhether"
align="center"
min-width="8%"
/>
<el-table-column
label="只读"
prop="isReadonly"
:formatter="formatterWhether"
align="center"
min-width="6%"
/>
<el-table-column
label="物模型识别"
prop="type"
:formatter="formatterThingsType"
align="center"
min-width="10%"
/>
<el-table-column
label="数据类型"
prop="datatype"
:formatter="formatterDataType"
align="center"
min-width="8%"
/>
<el-table-column label="数据定义" min-width="14%">
<template
v-slot="{
row: {
specs: { max, min, unit, step },
},
}"
>
<div class="specs-box">
<p class="specs-item">
<span>最大值:</span>
<span>{{ max }}</span>
</p>
<p class="specs-item">
<span>步长:</span>
<span>{{ step }}</span>
</p>
<p class="specs-item">
<span>最小值:</span>
<span>{{ min }}</span>
</p>
<p class="specs-item">
<span>单位:</span>
<span>{{ unit }}</span>
</p>
</div>
</template>
</el-table-column>
<el-table-column
label="排序"
prop="modelOrder"
align="center"
min-width="6%"
/>
<el-table-column
label="创建时间"
prop="createTime"
:formatter="formatterTime"
min-width="10%"
/>
<el-table-column label="操作" min-width="10%">
<template v-slot="{ row }">
<el-link type="primary" @click="triggerUpdate(row)">修改</el-link>
<el-popconfirm
title="您确定要删除这一项吗?"
@confirm="handleDelete(row.templateId)"
>
<el-link type="danger" slot="reference">删除</el-link>
</el-popconfirm>
</template>
</el-table-column>
<template #append>
<el-pagination
background
layout="sizes, prev, pager, next"
hide-on-single-page
:current-page.sync="page.pageNum"
:page-size.sync="page.pageSize"
:total="page.total"
@size-change="initData"
@current-change="initData"
>
</el-pagination>
</template>
</el-table>
</div>
<!-- 弹出层 -->
<el-dialog
:title="`${templateId ? '修改' : '新增'}通用物模型`"
top="5vh"
width="650px"
:visible="dialogVisible"
:close-on-click-modal="false"
:close-on-press-escape="false"
:before-close="closeDialog"
>
<el-form
label-width="85px"
label-suffix=":"
:model="ruleForm"
:rules="rules"
ref="formIns"
>
<el-form-item label="模型名称" prop="templateName">
<el-input
v-model.trim="ruleForm.templateName"
placeholder="请输入物模型名称,例如:温度"
/>
</el-form-item>
<el-form-item label="模型标识" prop="identifier">
<el-input
v-model.trim="ruleForm.identifier"
placeholder="请输入标识符,例如:temperature"
/>
</el-form-item>
<el-form-item label="模型排序" prop="modelOrder">
<el-input-number v-model="ruleForm.modelOrder" :min="0" />
</el-form-item>
<el-form-item label="模型类别" props="type" required>
<el-radio-group v-model="ruleForm.type">
<el-radio-button
v-for="item in iotThingsList"
:key="item.vallue"
:label="item.label"
:value="item.value"
></el-radio-button>
<!-- <el-radio-button label="功能"></el-radio-button>
<el-radio-button label="事件"></el-radio-button> -->
</el-radio-group>
</el-form-item>
<!-- <el-form-item label="模型特性" prop="isList" required> -->
<el-form-item label="模型特性" prop="isList" required>
<el-checkbox-group v-model="ruleForm.isList">
<template>
<el-checkbox
v-show="ruleForm.type === '属性'"
label="图表展示"
></el-checkbox>
<el-checkbox
v-show="ruleForm.type === '属性'"
label="实时监测"
></el-checkbox>
</template>
<el-checkbox
label="只读数据"
:disabled="ruleForm.type === '事件'"
></el-checkbox>
<el-checkbox label="历史存储"></el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-divider />
<el-form-item label="数据类型" prop="datatype" required>
<el-select v-model="ruleForm.datatype" value="">
<el-option
v-for="item in iotDataList"
:key="item.value"
:value="item.value"
:label="item.label"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="取值范围">
<el-input-number
v-model="ruleForm.min"
:min="0"
:max="100"
placeholder="最小值"
/>
到
<el-input-number
v-model="ruleForm.max"
:min="0"
:max="100"
placeholder="最大值"
/>
</el-form-item>
<el-form-item label="单位">
<el-input
v-model.trim="ruleForm.unit"
placeholder="请输入单位,例如:米"
style="width: 40%"
/>
</el-form-item>
<el-form-item label="步长" style="margin-bottom: 0">
<el-input-number
v-model="ruleForm.step"
placeholder="请输入步长"
style="width: 40%"
/>
</el-form-item>
</el-form>
<template #footer>
<button-again type="primary" @click="submit">确认提交</button-again>
<el-button @click="closeDialog">取消</el-button>
</template>
</el-dialog>
</div>
</template>
<style lang="less" scoped>
.filter-box {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
background: #fff;
.handler {
display: flex;
.el-button {
margin-left: 10px;
}
}
.filter {
display: flex;
flex-wrap: wrap;
.form-item {
display: flex;
align-items: center;
margin-right: 20px;
height: 40px;
label {
padding: 0 10px;
width: auto;
}
.el-input {
width: 240px;
}
.el-select {
width: 150px;
}
}
}
}
.table-box {
position: relative;
margin-top: 10px;
background: #fff;
.el-link {
margin-right: 10px;
}
.el-table {
position: absolute;
box-sizing: border-box;
padding: 15px;
width: 100%;
}
.el-pagination {
margin-top: 15px;
text-align: right;
}
}
.specs-box {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
.specs-item {
margin-bottom: 0;
width: 62%;
line-height: 20px;
font-size: 12px;
&:nth-child(2n) {
width: 34%;
}
span {
&:nth-child(2) {
color: #f56c6c;
}
}
}
}
:deep(.el-dialog__body) {
padding: 20px;
}
@media all and (max-width: 1100px) {
.filter-box {
display: block;
.handler {
margin-top: 10px;
}
}
}
</style>
- fang/f20230708/ManageSystem/src/components/DrawPieImage.vue
<script>
import * as echarts from "echarts";
export default {
name: "DrawPieImage",
props: {
title: {
required: true,
type: String,
},
data: {
required: true,
type: Array,
},
},
methods: {
draw() {
let chart = echarts.init(this.$refs.pieBox);
let option = {
title: {
text: this.title,
left: "center",
},
tooltip: {
trigger: "item",
},
legend: {
orient: "vertical",
left: "left",
},
series: [
{
name: this.title,
type: "pie",
radius: "50%",
data: this.data,
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: "rgba(0, 0, 0, 0.5)",
},
},
},
],
};
chart.setOption(option);
},
},
watch: {
data: {
immediate: true,
deep: true,
handler() {
if (this.data.length > 0) {
this.draw();
}
},
},
},
mounted() {},
};
</script>
<template>
<div ref="pieBox"></div>
</template>
- fang/f20230708/ManageSystem/src/api/index.js
import http from "./http";
import iot from "./iot";
// 获取验证码
const queryCaptchaImage = () => http.get("/captchaImage");
// 用户登录校验
const checkUserLogin = (body) => http.post("/login", body);
// 获取登录者信息「含权限信息」
const queryUserProfile = () => http.get("/getInfo");
// 获取地图标注数据
const queryDeviceAll = () => http.get("/iot/device/all");
// 获取新闻列表的数据
/* /system/notice/list?pageNum=1&pageSize=6
新闻列表 */
const queryNoticeList = (pageNum = 1, pageSize = 6) => {
return http.get(`/system/notice/list`, {
params: {
pageNum,
pageSize,
},
});
};
// 获取设备统计的数据
/* iot/device/statistic
设备统计
*/
const queryDeviceStatistic = () => http.get(`/iot/device/statistic`);
// 获取饼状图需要的数据
/*
/monitor/server
饼状图需要的数据 */
const queryMonitorServer = () => http.get(`/monitor/server`);
// 获取Mqtt的状态数据
/*
/bashBoard/stats
Mqtt的状态数据*/
const queryBashBoardStats = () => http.get(`/bashBoard/stats`);
/* 暴露API */
const API = {
iot, //this.$API.ito.xxxx来调用。
queryCaptchaImage,
checkUserLogin,
queryUserProfile,
queryDeviceAll,
queryNoticeList,
queryDeviceStatistic,
queryMonitorServer,
queryBashBoardStats,
};
export default API;
- fang/f20230708/ManageSystem/src/views/index/Welcome.vue
<script>
import MapBaidu from "./particle/Map.vue";
import Device from "./particle/Device.vue";
import News from "./particle/News.vue";
//前提:安装echarts依赖。这里是全量导入。
import * as echarts from "echarts";
export default {
components: {
MapBaidu,
Device,
News,
},
data() {
return {
mqtt: null,
cpuData: [],
memoryData: [],
systemData: [],
};
},
methods: {
formatValue(val) {
return (val / 10000).toFixed(2);
},
//mqtt相关的操作。
async initMqttData() {
try {
let { code, data } = await this.$API.queryBashBoardStats();
if (+code === 200) {
Object.keys(data).forEach((key) => {
if (/_total$/.test(key)) {
data[key] = this.formatValue(data[key]);
}
});
this.mqtt = Object.freeze(data);
this.drawBarImage();
}
} catch (error) {
console.log(`error:-->`, error);
}
},
drawBarImage() {
let {
connection_count,
connection_total,
retain_count,
retain_total,
session_count,
session_total,
subscription_count,
subscription_total,
} = this.mqtt;
let chart = echarts.init(this.$refs.mqttBox);
let option = {
/* tooltip: {
trigger: "axis",
axisPointer: {
type: "MQTT状态数据统计",
},
}, */
title: { text: "MQTT状态数据统计" },
legend: {},
tooltip: {
trigger: "axis",
axisPointer: {
type: "shadow",
},
/* formatter(a, b, c) {
console.log(a, b, c);
}, */
formatter: "{b} <br/> {a0}:{c0}万 <br/> {a1}:{c1} ",
},
grid: {
left: "3%",
right: "4%",
bottom: "3%",
containLabel: true,
},
yAxis: [
{
type: "category",
data: ["连接数量", "会话数量", "订阅数量", "路由数量", "保留消息"],
},
],
xAxis: [
{
type: "value",
axisLabel: {
formatter: "{value} 万",
},
},
],
series: [
{
name: "总数量",
type: "bar",
data: [
connection_total,
session_total,
subscription_total,
retain_total,
retain_total,
],
},
{
name: "当前数量",
type: "bar",
data: [
connection_count,
session_count,
subscription_count,
retain_count,
retain_count,
],
},
],
};
chart.setOption(option);
this.$el.addEventListener("resize", () => {
console.log(`1111-->`, 1111);
});
},
//饼状图相关的操作。
async initPieData() {
try {
let { code, data } = await this.$API.queryMonitorServer();
if (+code === 200) {
let {
cpu: { free, sys, used },
mem: { free: memFree, used: memUsed },
sysFiles: [{ free: sysFree, used: sysUsed }],
} = data;
this.cpuData = Object.freeze([
{ name: "空闲", value: free },
{ name: "用户", value: used },
{ name: "系统", value: sys },
]);
this.memoryData = Object.freeze([
{ name: "用户", value: memUsed },
{ name: "空闲", value: memFree },
]);
this.systemData = Object.freeze([
{ name: "用户", value: parseFloat(sysUsed) },
{ name: "空闲", value: parseFloat(sysFree) },
]);
}
} catch (error) {
console.log(`error:-->`, error);
}
},
},
created() {
this.initMqttData();
this.initPieData();
},
mounted() {
var ro = new ResizeObserver((entries) => {
console.log(`entries-->`, entries);
});
console.log(`this.$refs.mqttBox-->`, this.$refs.mqttBox);
// 观察一个或多个元素
ro.observe(this.$el);
},
};
</script>
<template>
<div class="welcome-box">
<div class="content">
<MapBaidu class="map" />
<div class="statistics">
<Device class="device" />
<News class="news" />
</div>
</div>
<div class="echarts-box">
<div class="mqtt" ref="mqttBox"></div>
<DrawPieImage
class="cup-rate"
title="CPU使用率(%)"
:data="cpuData"
></DrawPieImage>
<DrawPieImage
class="memory-rate"
title="内存使用率(GB)"
:data="memoryData"
></DrawPieImage>
<DrawPieImage
class="system-rate"
title="系统盘使用率(GB)"
:data="systemData"
></DrawPieImage>
<!-- <div class="cup-rate"></div>
<div class="memory-rate"></div>
<div class="system-rate"></div> -->
<div class="seat"></div>
</div>
</div>
</template>
<style lang="less" scoped>
.content {
display: flex;
justify-content: flex-start;
align-items: flex-start;
.map {
box-sizing: border-box;
margin-right: 20px;
width: calc((100% - 20px) / 2);
height: 600px;
background: url("@/assets/images/Loading_icon.gif") no-repeat center center
#fff;
background-size: 30px 30px;
border: 2px solid #fff;
}
.statistics {
box-sizing: border-box;
width: calc((100% - 20px) / 2);
.device,
.news {
box-sizing: border-box;
padding: 15px;
height: 290px;
background: #fff;
overflow: hidden;
}
.news {
margin-top: 20px;
}
}
}
.echarts-box {
margin-top: 20px;
padding: 20px 15px;
padding-bottom: 0;
background: #fff;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
@w: calc((100% - 40px) / 3);
& > div {
flex-basis: @w;
height: 350px;
margin-bottom: 20px;
background: url("@/assets/images/Loading_icon.gif") no-repeat center center
#f9f9f9;
background-size: 30px 30px;
:deep(div) {
background: #fff;
}
&.mqtt {
flex-basis: calc(@w * 2 + 20px);
}
&.seat {
background: #fff;
}
}
}
</style>
- fang/f20230708/ManageSystem/src/global.js
import Vue from "vue"
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import API from "@/api"
import ButtonAgain from '@/components/ButtonAgain.vue'
import DrawPieImage from '@/components/DrawPieImage'
Vue.use(ElementUI)
Vue.config.productionTip = false
Vue.prototype.$API = API
/* 全局混入的方法 */
Vue.mixin({
methods: {
mixinPrefixAdd(url, prefix = 'https://iot.fastbee.cn/prod-api') {
let reg = /^http(s)?:\/\//
return reg.test(url) ? url : prefix + url
}
}
})
/* 注册全局组件 */
Vue.component(ButtonAgain.name, ButtonAgain)
Vue.component(DrawPieImage.name, DrawPieImage)
- fang/f20230708/ManageSystem/src/views/index/particle/Map.vue
<script>
// https://lbsyun.baidu.com/index.php
export default {
methods: {
// 绘制高亮选中的点
createPoint(map, { lan, lat, item }) {
// 绘制坐标点
let point = new BMapGL.Point(lan, lat);
let marker = new BMapGL.Marker3D(point, 0, {
size: 18,
shape: 1,
fillColor: "#34bfa3",
fillOpacity: 0.8,
});
map.addOverlay(marker);
// 详细信息
let info = new BMapGL.InfoWindow(
`<div class="map-device-info">
<p>设备名称:<span>${item.deviceName}</span></p>
<p>设备编号:${item.serialNumber}</p>
<p>所在地址:${item.networkAddress}</p>
</div>`,
{
width: 200,
height: 100,
title: `<h2 class="map-device-info">设备详细信息</h2>`,
}
);
marker.addEventListener("click", () => map.openInfoWindow(info, point));
},
},
async created() {
// 第一次渲染之前,从服务器获取标注点的数据
try {
let { code, rows } = await this.$API.queryDeviceAll();
if (+code === 200 && this.map) {
// 按照获取的数据,循环创建标注点
rows.forEach((item) => {
this.createPoint(this.map, {
lan: item.longitude,
lat: item.latitude,
item,
});
});
}
} catch (_) {}
},
mounted() {
const mapBox = this.$refs.mapBox;
this.map = new BMapGL.Map(mapBox); // 创建地图实例
let point = new BMapGL.Point(103.38861, 35.86166); // 创建点坐标
this.map.centerAndZoom(point, 5); // 初始化地图,设置中心点坐标和地图级别
this.map.enableScrollWheelZoom(true); //开启鼠标滚轮缩放
},
};
</script>
<template>
<div class="map-box" ref="mapBox"></div>
</template>
<style lang="less" scoped></style>
- fang/f20230708/ManageSystem/src/views/index/particle/Device.vue
<script>
export default {
data() {
return {
info: null,
};
},
async created() {
try {
let { code, data } = await this.$API.queryDeviceStatistic();
if (+code === 200) {
this.info = Object.freeze(data);
}
} catch (error) {
console.log(`error:-->`, error);
}
},
};
</script>
<template>
<div class="device-box">
<h2 class="title">
<i class="el-icon-pie-chart"></i>
设备统计
</h2>
<el-skeleton :rows="5" animated v-if="!info" />
<div class="content" v-else>
<div class="item">
<div class="icon"><i class="el-icon-discount"></i></div>
<div class="text">
<span>设备数量</span>
<span>{{ info.deviceCount }}</span>
</div>
</div>
<div class="item">
<div class="icon"><i class="el-icon-stopwatch"></i></div>
<div class="text">
<span>监测数据</span>
<span>{{info.monitorCount}}</span>
</div>
</div>
<div class="item">
<div class="icon"><i class="el-icon-sell"></i></div>
<div class="text">
<span>产品数量</span>
<span>{{info.productCount}}</span>
</div>
</div>
<div class="item">
<div class="icon"><i class="el-icon-warning-outline"></i></div>
<div class="text">
<span>告警数量</span>
<span>{{info.alertCount}}</span>
</div>
</div>
<div class="item">
<div class="icon"><i class="el-icon-edit-outline"></i></div>
<div class="text">
<span>操作记录</span>
<span>{{info.functionCount}}</span>
</div>
</div>
<div class="item">
<div class="icon"><i class="el-icon-position"></i></div>
<div class="text">
<span>上报事件</span>
<span>{{info.eventCount}}</span>
</div>
</div>
</div>
</div>
</template>
<style lang="less" scoped>
.title {
margin-bottom: 0;
font-size: 18px;
line-height: 50px;
i {
font-size: 20px;
}
}
.content {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
.item {
box-sizing: border-box;
flex-basis: calc((100% - 10px) / 2);
height: 63px;
padding: 0 10px;
margin-bottom: 10px;
border: 1px solid #f1f1f1;
&:nth-last-child(1),
&:nth-last-child(2) {
margin-bottom: 0;
}
display: flex;
justify-content: space-between;
align-items: center;
.icon {
box-sizing: border-box;
width: 50px;
height: 50px;
line-height: 50px;
text-align: center;
font-size: 26px;
color: #36a3f7;
transition: all 0.6s;
}
.text {
span {
display: block;
line-height: 25px;
text-align: right;
font-size: 14px;
color: #555;
&:nth-child(2) {
font-weight: bold;
font-size: 16px;
}
}
}
&:nth-child(2n) {
.icon {
color: #f56c6c;
}
}
&:hover {
.icon {
background: #36a3f7;
color: #fff;
}
&:nth-child(2n) {
.icon {
background: #f56c6c;
}
}
}
}
}
:deep(.el-skeleton) {
margin-top: 10px;
}
</style>
- fang/f20230708/ManageSystem/src/views/index/particle/News.vue
<script>
import dayjs from "dayjs";
export default {
data() {
return {
list: [],
visible: false,
tipInfo: null,
};
},
async created() {
try {
let { code, rows } = await this.$API.queryNoticeList();
if (code === 200) {
this.list = Object.freeze(rows);
}
} catch (error) {
console.log(`error:-->`, error);
}
},
methods: {
formatTime(time) {
return dayjs(time).format("YYYY/MM/DD");
},
handle(item) {
this.tipInfo = item;
this.visible = true;
},
},
};
</script>
<template>
<div class="news-box">
<h2 class="title">
<i class="el-icon-news"></i>
最新信息
</h2>
<el-skeleton :rows="5" animated v-if="list.length === 0" />
<div class="content" v-else>
<div
v-for="item in list"
:key="item.noticeId"
class="item"
@click="handle(item)"
>
<p class="title">
<el-tag
size="mini"
:color="+item.noticeType === 2 ? `#E6A23C` : `#409EFF`"
>
{{ +item.noticeType === 2 ? `公告` : `信息` }}
</el-tag>
物美智能V1.2版本发布
{{ item.noticeTitle }}
</p>
<p class="time">
<i class="el-icon-timer"></i>
{{ formatTime(item.createTime) }}
</p>
</div>
<!-- <div class="item">
<p class="title">
<el-tag size="mini" color="#409EFF">信息</el-tag>
物美智能V1.2版本发布
</p>
<p class="time">
<i class="el-icon-timer"></i>
2023/12/23
</p>
</div> -->
</div>
<el-dialog :visible.sync="visible" :title="tipInfo?.noticeTitle">
<div class="time-box">
<el-tag
size="mini"
:color="+tipInfo?.noticeType === 2 ? `#E6A23C` : `#409EFF`"
>
{{ +tipInfo?.noticeType === 2 ? `公告` : `信息` }}
</el-tag>
<span>{{ tipInfo?.createTime }}</span>
<div class="text-box" v-html="tipInfo?.noticeContent"></div>
</div>
<div class="text-box"></div>
</el-dialog>
</div>
</template>
<style lang="less" scoped>
.title {
margin-bottom: 0;
font-size: 18px;
line-height: 50px;
i {
font-size: 20px;
}
}
.content {
.item {
display: flex;
cursor: pointer;
.title,
.time {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
height: 52.5px;
line-height: 52.5px;
}
.title {
flex-basis: calc(100% - 120px);
font-size: 15px;
.el-tag {
color: #fff;
border: none;
}
.el-tag--mini {
line-height: 20px;
}
}
.time {
margin-left: 10px;
margin-bottom: 0;
flex-basis: 110px;
text-align: right;
}
}
}
:deep(.el-skeleton) {
margin-top: 10px;
}
:deep(.el-dialog__body){
padding: 10px 20px 20px;
.time-box{
line-height: 40px;
.el-tag{
color: #fff;
border: none;
margin-right: 10px;
}
}
.text-box{
padding: 10px;
border: 1px solid #ddd;
p{
margin: 10px 0;
}
ol{
padding-left: 30px;
list-style: initial;
li{
line-height: 30px;
}
}
}
}
</style>