20230710----重返学习-物模型增删改查-欢迎页图表展示-图表展示

day-109-one-hundred-and-nine-20230710-物模型增删改查-欢迎页图表展示-图表展示

物模型增删改查

修改一个表单列表项的方式

  1. 如果是一个弹框,在同一个组件中,可以给弹框中存储一个公共状态对象,之后通过那个对象中的数据来修改。

    <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>
    
  2. 如果是另一个路由页面,那么将使用如问号传参或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表单
    1. Form第一次渲染的时候,表单中的数据是啥,Form认为这就是初始数据
    2. 基于 resetFields 重置数据的时候,会重置为Form认为的初始数据
    • 但是这个初始值,是不一定准确的;

新增修改数据

新增修改数据一条数据

删除多条数据

表格下载

表格下载的步骤
  1. 先问chatGTP或网上找,如’主流的前端excel表格下载插件是?’

欢迎页图表展示

扒接口

百度地图

设备统计-骨架屏及数据绑定

新闻页-弹窗提示信息

图表展示

  • Echarts的核心是canvas。
  • canvas:
    1. 准备一个盒子,把这个盒子作为我么的画布 「需要盒子有固定的宽高」
    2. 准备画笔,去绘制图形
    3. 保存
    • 绘制出来的是一张图片!

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="最小值"
          />
          &nbsp; 到 &nbsp;
          <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>

进阶参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值