Browse Source

feat: 基础模板 vue3+vite+antdv 3+dayjs+tailwindcss

master
wally 4 years ago
parent
commit
0073954489
  1. 1
      .yarnrc
  2. 9595
      package-lock.json
  3. 2
      package.json
  4. 127
      src/App.vue
  5. 75
      src/apis/index.js
  6. 85
      src/components/commands/search-commands.vue
  7. 92
      src/components/config/config-log-function.vue
  8. 93
      src/components/config/config-log-network.vue
  9. 42
      src/components/config/device-select-and-status.vue
  10. 77
      src/components/config/function-config-applied.vue
  11. 287
      src/components/config/function-config-pending.vue
  12. 65
      src/components/config/network-config-applied.vue
  13. 150
      src/components/config/network-config-pending.vue
  14. 19
      src/components/config/refresh.vue
  15. 19
      src/components/config/status-and-last-time.vue
  16. 12
      src/components/config/status-tag-pending.vue
  17. 193
      src/components/device/device-create.vue
  18. 13
      src/components/device/device-edit.vue
  19. 233
      src/components/history/device.vue
  20. 42
      src/components/history/history-log-table.vue
  21. 35
      src/components/history/history-log.vue
  22. 162
      src/components/history/local.vue
  23. 48
      src/components/history/missing-data.vue
  24. 148
      src/components/history/search-bar-data.vue
  25. 77
      src/components/history/search-history-log.vue
  26. 23
      src/components/navbar.vue
  27. 49
      src/components/overview/chart-device-count.vue
  28. 93
      src/components/overview/chart-device-detail.vue
  29. 112
      src/components/overview/device-table.vue
  30. 66
      src/components/realtime/search-bar.vue
  31. 131
      src/components/statistical/search-bar.vue
  32. 82
      src/components/statistical/stastistical-chart.vue
  33. 82
      src/components/tall/left/Calendar.vue
  34. 13
      src/components/tall/left/Index.vue
  35. 5
      src/components/tall/left/Projects.vue
  36. 20
      src/components/tall/top/Navbar.vue
  37. 157
      src/config/chart.js
  38. 164
      src/config/config.js
  39. 110
      src/config/log.js
  40. 19
      src/hooks/useDeviceCreate.js
  41. 5
      src/main.js
  42. 77
      src/routers/index.js
  43. 163
      src/store/device.js
  44. 17
      src/store/index.js
  45. 78
      src/store/statistics.js
  46. 0
      src/store/tall/layout/actions.js
  47. 0
      src/store/tall/layout/getters.js
  48. 8
      src/store/tall/layout/index.js
  49. 13
      src/store/tall/layout/mutations.js
  50. 8
      src/store/tall/layout/state.js
  51. 2
      src/store/tall/user/index.js
  52. 8
      src/utils/axios.js
  53. 6
      src/utils/dayjs.js
  54. 128
      src/utils/overview.js
  55. 176
      src/utils/statistical.js
  56. 16
      src/utils/time.js
  57. 182
      src/views/commands.vue
  58. 32
      src/views/config.vue
  59. 185
      src/views/data-realtime.vue
  60. 3
      src/views/detail/Test.vue
  61. 192
      src/views/device-edit.vue
  62. 216
      src/views/device-list.vue
  63. 22
      src/views/history.vue
  64. 12
      src/views/home/Index.vue
  65. 23
      src/views/overview.vue
  66. 80
      src/views/statistical-report.vue
  67. 3
      src/views/user/SignIn.vue
  68. 6
      vite.config.js
  69. 8803
      yarn.lock

1
.yarnrc

@ -0,0 +1 @@
regisrty https://registry.npm.taobao.org

9595
package-lock.json

File diff suppressed because it is too large

2
package.json

@ -15,10 +15,10 @@
}, },
"dependencies": { "dependencies": {
"@vitejs/plugin-vue": "^1.9.3", "@vitejs/plugin-vue": "^1.9.3",
"ant-design-vue": "3.0.0-alpha.9",
"axios": "^0.23.0", "axios": "^0.23.0",
"dayjs": "^1.10.7", "dayjs": "^1.10.7",
"echarts": "^5.2.2", "echarts": "^5.2.2",
"element-plus": "^1.1.0-beta.24",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"vite": "^2.6.4", "vite": "^2.6.4",
"vite-plugin-compression": "^0.3.5", "vite-plugin-compression": "^0.3.5",

127
src/App.vue

@ -1,98 +1,35 @@
<template>
<a-config-provider :locale="locale">
<a-layout>
<!-- 日历页-->
<a-layout-sider v-show="collapsed" style="background: #fff"><Left /></a-layout-sider>
<a-layout>
<!-- 导航栏-->
<a-layout-header style="background: #fff">
<Navbar />
</a-layout-header>
<!-- 内容区-->
<a-layout-content><router-view></router-view></a-layout-content>
<!-- 脚部-->
<!-- <a-layout-footer>Footer</a-layout-footer>-->
</a-layout>
</a-layout>
</a-config-provider>
</template>
<script setup> <script setup>
import { computed, ref } from 'vue'; import { computed } from 'vue';
import { useStore } from 'vuex'; import { useStore } from 'vuex';
import { useRoute, useRouter } from 'vue-router'; import zhCN from 'ant-design-vue/es/locale/zh_CN';
import Navbar from 'components/navbar.vue'; import Left from 'components/tall/left/Index.vue';
import zhCn from 'element-plus/lib/locale/lang/zh-cn'; import Navbar from 'components/tall/top/Navbar.vue';
import { ElMessage } from 'element-plus';
import { routes } from '@/routers/index.js';
const local = zhCn; const locale = zhCN;
const store = useStore(); const store = useStore();
let timer = null; const collapsed = computed(() => store.state.layout.display.left); //
const routeList = ref(routes);
const menu = computed(() => store.state.menu);
const activeMenuIndex = ref(0);
// queryu token
const route = useRoute();
const router = useRouter();
useRouter()
.isReady()
.then(async () => {
activeMenuIndex.value = routes.findIndex(item => item.name === route.name);
const u = computed(() => route.query.u);
if (!u.value) {
// urlu,
ElMessage.error('缺少用户信息参数');
} else {
// userId token
await store.dispatch('user/getTokenByUserId', u.value);
}
});
const token = computed(() => store.getters['user/token']);
//
const getDeviceData = async () => {
if (token && token.value) {
await store.dispatch('device/getDevices');
timer && clearTimeout(timer);
timer = null;
} else {
timer = setTimeout(() => {
getDeviceData();
}, 16);
}
};
getDeviceData();
/**
* 打开页面
* @param {string} path 页面路径
*/
function openPage(path) {
const { query } = route;
router.push({
path,
query,
});
}
</script> </script>
<template>
<el-config-provider :locale="local">
<el-container>
<el-header style="padding: 0">
<Navbar />
</el-header>
<el-container class="overflow-hidden">
<!-- <el-aside width="180px" v-if="menu.show"> -->
<el-aside v-if="menu.show" :width="!menu.collapse ? '200px' : '64px'">
<el-menu :collapse="menu.collapse" :default-active="activeMenuIndex" class="el-menu-vertical-demo">
<el-menu-item
v-for="(item, index) in routeList"
:key="item.name"
:index="index"
class="flex items-center"
@click="openPage(item.path)"
>
<i :class="item.meta.icon"></i>
<template #title>{{ item.meta.title }}</template>
</el-menu-item>
</el-menu>
</el-aside>
<el-main style="padding: 0">
<div class="p-4">
<router-view></router-view>
</div>
</el-main>
</el-container>
</el-container>
</el-config-provider>
</template>
<style> <style>
html, html,
body, body,
@ -100,16 +37,4 @@ body,
#app > section { #app > section {
height: 100%; height: 100%;
} }
.el-menu {
min-height: 100%;
}
.el-form--label-top .el-form-item__label {
padding-bottom: 0 !important;
}
.el-form-item {
margin-bottom: 10px !important;
}
</style> </style>

75
src/apis/index.js

@ -4,80 +4,7 @@ import http from 'utils/axios';
const apiUrl = import.meta.env.VITE_API_URL; const apiUrl = import.meta.env.VITE_API_URL;
const users = `${apiUrl}/gateway/tall3/v3.0/users`; const users = `${apiUrl}/gateway/tall3/v3.0/users`;
const corrosion = `${apiUrl}/gateway/corrosion`;
// 根据userId 获取token // 根据userId 获取token
// eslint-disable-next-line import/prefer-default-export
export const getToken = userId => http.get(`${users}/userId`, { params: { userId } }); export const getToken = userId => http.get(`${users}/userId`, { params: { userId } });
// 获取设备列表
export const getDevices = () => http.get(`${corrosion}/devices`);
// 查设备概览 数据统计
export const getDevicesCount = () => http.get(`${corrosion}/devices/count`);
// 添加设备
export const createDevice = data => http.post(`${corrosion}/devices`, data);
// 获取设备列表 完整信息
export const getDevicesAll = (
params = {
paging: true,
deviceId: '',
page: 1,
size: 50,
sort: [
{
col: 'createdAt',
order: 'DESC',
},
],
},
) => http.post(`${corrosion}/devices/all`, params);
// 更新设备
export const updateDevice = (deviceId, data) => http.put(`${corrosion}/devices/all/${deviceId}`, data);
// 删除设备
export const deleteDevice = deviceId => http.delete(`${corrosion}/devices/all/${deviceId}`);
// 获取月累计数据分析
export const getMonthsDate = params => http.get(`${corrosion}/statistics/months`, { params });
// 获取网络配置参数 列表
export const getConfigNetwork = params => http.get(`${corrosion}/config/network`, { params });
// 获取网络配置参数 设备实时参数
export const getConfigAppliedNetwork = params => http.get(`${corrosion}/config-applied/network`, { params });
// 获取功能配置参数 列表
export const getConfigFunction = params => http.get(`${corrosion}/config/function`, { params });
// 获取功能配置参数 设备实时参数
export const getConfigAppliedFunction = params => http.get(`${corrosion}/config-applied/function`, { params });
// 提交网络配置参数
export const createConfigNetwork = data => http.post(`${corrosion}/config/network`, data);
// 提交功能配置参数
export const createConfigFunction = data => http.post(`${corrosion}/config/function`, data);
// 查上报数据
export const getDatas = params => http.post(`${corrosion}/datas`, params);
// 导出上报数据
export const exportDatas = params => http.post(`${corrosion}/export`, params);
// 查历史数据
export const getHistory = params => http.post(`${corrosion}/history/datas`, params);
// 导出历史数据
export const exportHistory = params => http.post(`${corrosion}/export`, params);
// 发送补传指令
export const sendCommand = params => http.post(`${corrosion}/history`, params);
// 补传记录
export const getSendHistory = params => http.get(`${corrosion}/history`, { params });
// 查询平台日志
export const getLog = params => http.post(`${corrosion}/tpclog/query`, params);

85
src/components/commands/search-commands.vue

@ -1,85 +0,0 @@
<template>
<el-form ref="searchDeviceForm" :inline="true" :model="searchDevice">
<el-form-item label="选择站点">
<el-select v-model="searchDevice.deviceId" placeholder="请选择站点" @change="change">
<el-option label="全部" value></el-option>
<el-option
v-for="item in devices"
:key="item.deviceId"
:label="`${item.address}-${item.deviceId}`"
:value="item.deviceId"
></el-option>
</el-select>
</el-form-item>
<!-- <el-form-item label="类型">-->
<!-- <el-select v-model="searchDevice.type" placeholder="选择查询类型">-->
<!-- <el-option label="全部" value=""></el-option>-->
<!-- <el-option label="事件上报" value="EVENT"></el-option>-->
<!-- <el-option label="业务上报" value="DATA"></el-option>-->
<!-- </el-select>-->
<!-- </el-form-item>-->
<!-- <el-form-item label="状态">-->
<!-- <el-select v-model="searchDevice.status" placeholder="选择查询类型">-->
<!-- <el-option label="全部" value=""></el-option>-->
<!-- <el-option label="PENDING" value="PENDING"></el-option>-->
<!-- <el-option label="SUCCESS" value="SUCCESS"></el-option>-->
<!-- </el-select>-->
<!-- </el-form-item>-->
<el-form-item>
<el-button type="primary" @click="onSubmit">
<i class="el-icon-search mr-2"></i>
查询
</el-button>
</el-form-item>
</el-form>
</template>
<script setup>
import { computed, defineEmits, reactive, ref, watch } from 'vue';
import { useStore } from 'vuex';
const emit = defineEmits(['search']);
const searchDevice = reactive({
deviceId: '',
paging: true,
page: 1,
size: 50,
sort: [
{
col: 'createdAt',
order: 'DESC',
},
],
type: '',
status: '',
});
const searchDeviceForm = ref(null); // form
const store = useStore();
const devices = computed(() => store.state.device.devices);
const currentDeviceId = computed(() => store.state.device.currentDeviceId); // id
// currentDeviceId
watch(
() => currentDeviceId.value,
newValue => {
if (newValue) {
searchDevice.deviceId !== newValue && (searchDevice.deviceId = newValue);
}
},
{ immediate: true },
);
const change = e => {
store.commit('device/setCurrentDeviceId', e);
};
//
const onSubmit = () => {
searchDeviceForm.value.validate(() => {
emit('search', { ...searchDevice });
});
};
</script>

92
src/components/config/config-log-function.vue

@ -1,92 +0,0 @@
<template>
<template v-if="data">
<el-table :data="data" :max-height="contentHeight" border stripe style="width: 100%">
<el-table-column type="expand">
<template #default="props">
<div class="mx-4">
<FunctionConfigApplied v-if="props.row" :data="props.row" />
</div>
</template>
</el-table-column>
<el-table-column align="center" label="设备ID" min-min-width="100" prop="deviceId" />
<el-table-column align="center" label="上次配置时间" min-width="200">
<template #default="scope">
{{ dayjs(new Date(+scope.row.settingTime)).format('YYYY-MM-DD HH:mm:ss') }}
</template>
</el-table-column>
<el-table-column align="center" label="状态" min-width="100">
<template #default="scope">
<StatusTagPending :type="scope.row.settingStatus" />
</template>
</el-table-column>
</el-table>
<!-- <el-pagination-->
<!-- :current-page="devicesAll.page.page"-->
<!-- :default-page-size="50"-->
<!-- :page-count="devicesAll.page.count"-->
<!-- :page-size="devicesAll.page.size"-->
<!-- :page-sizes="[1, 10, 20, 50, 100]"-->
<!-- :total="devicesAll.page.total"-->
<!-- background-->
<!-- class="my-3 float-right"-->
<!-- layout="total, sizes, prev, pager, next, jumper"-->
<!-- @size-change="onSizeChange"-->
<!-- @current-change="onCurrentPageChange"-->
<!-- ></el-pagination>-->
</template>
</template>
<script setup>
import { computed, onMounted, ref, watchEffect } from 'vue';
import { useStore } from 'vuex';
import { getConfigFunction } from 'apis';
import dayjs from 'dayjs';
import { ElMessage } from 'element-plus';
import StatusTagPending from 'components/config/status-tag-pending.vue';
import FunctionConfigApplied from 'components/config/function-config-applied.vue';
const store = useStore();
const currentDeviceId = computed(() => store.state.device.currentDeviceId);
const contentHeight = ref(600);
const data = ref(null);
//
onMounted(() => {
const winHeight = document.documentElement.clientHeight;
contentHeight.value = winHeight - 250;
});
watchEffect(() => {
const deviceId = currentDeviceId.value;
onSearch(deviceId);
});
/**
* 删除
* @param {number} page 页码
* @param {number} size 每页条数
*/
async function onSearch(deviceId = currentDeviceId.value) {
const params = { deviceId };
try {
const resData = await getConfigFunction(params);
data.value = resData;
} catch (error) {
ElMessage.error(error.message);
console.error(error);
}
}
currentDeviceId.value && onSearch(currentDeviceId.value);
//
// const onCurrentPageChange = page => {
// onSearch(page, devicesAll.value.page.size || 50);
// };
//
// const onSizeChange = size => {
// onSearch(1, size);
// };
</script>

93
src/components/config/config-log-network.vue

@ -1,93 +0,0 @@
<template>
<template v-if="data">
<el-table :data="data" :max-height="contentHeight" border stripe style="width: 100%">
<el-table-column type="expand">
<template #default="props">
<div class="mx-4">
<NetworkConfigApplied v-if="props.row" :data="props.row" />
</div>
</template>
</el-table-column>
<el-table-column align="center" label="设备ID" min-min-width="100" prop="deviceId" />
<el-table-column align="center" label="上次配置时间" min-width="200">
<template #default="scope">
{{ dayjs(new Date(+scope.row.settingTime)).format('YYYY-MM-DD HH:mm:ss') }}
</template>
</el-table-column>
<el-table-column align="center" label="状态" min-width="100">
<template #default="scope">
<StatusTagPending :type="scope.row.settingStatus" />
</template>
</el-table-column>
</el-table>
<!-- <el-pagination-->
<!-- :current-page="devicesAll.page.page"-->
<!-- :default-page-size="50"-->
<!-- :page-count="devicesAll.page.count"-->
<!-- :page-size="devicesAll.page.size"-->
<!-- :page-sizes="[1, 10, 20, 50, 100]"-->
<!-- :total="devicesAll.page.total"-->
<!-- background-->
<!-- class="my-3 float-right"-->
<!-- layout="total, sizes, prev, pager, next, jumper"-->
<!-- @size-change="onSizeChange"-->
<!-- @current-change="onCurrentPageChange"-->
<!-- ></el-pagination>-->
</template>
</template>
<script setup>
import { computed, onMounted, ref, watchEffect } from 'vue';
import { useStore } from 'vuex';
import dayjs from 'dayjs';
import { ElMessage } from 'element-plus';
import StatusTagPending from 'components/config/status-tag-pending.vue';
import NetworkConfigApplied from 'components/config/network-config-applied.vue';
import { getConfigNetwork } from 'apis';
const store = useStore();
const currentDeviceId = computed(() => store.state.device.currentDeviceId);
const contentHeight = ref(600);
const data = ref(null);
//
onMounted(() => {
const winHeight = document.documentElement.clientHeight;
contentHeight.value = winHeight - 250;
});
watchEffect(() => {
const deviceId = currentDeviceId.value;
onSearch(deviceId);
});
/**
* 删除
* @param {number} page 页码
* @param {number} size 每页条数
*/
async function onSearch(deviceId = currentDeviceId.value) {
const params = { deviceId };
try {
const resData = await getConfigNetwork(params);
data.value = resData;
} catch (error) {
ElMessage.error(error.message);
console.error(error);
}
}
currentDeviceId.value && onSearch(currentDeviceId.value);
//
// const onCurrentPageChange = page => {
// onSearch(page, devicesAll.value.page.size || 50);
// };
//
// const onSizeChange = size => {
// onSearch(1, size);
// };
</script>

42
src/components/config/device-select-and-status.vue

@ -1,42 +0,0 @@
<script setup>
import { computed, ref, watch } from 'vue';
import { useStore } from 'vuex';
const store = useStore();
const devices = computed(() => store.state.device.devices); // /
const deviceId = ref(''); // id
const currentDeviceId = computed(() => store.state.device.currentDeviceId); // id
// currentDeviceId
watch(
() => currentDeviceId.value,
newValue => {
if (newValue && deviceId.value !== newValue) {
deviceId.value = newValue;
}
},
{ immediate: true },
);
/**
* 设备id修改
* 更新store里的deviceId
*/
function onChange(event) {
store.commit('device/setCurrentDeviceId', event);
}
</script>
<template>
<div class="mb-3">
<span class="text-sm text-gray-500">选择站点</span>
<el-select v-model="deviceId" placeholder="选择站点" @change="onChange">
<el-option
v-for="item in devices"
:key="item.deviceId"
:label="`${item.address}-${item.deviceId}`"
:value="item.deviceId"
></el-option>
</el-select>
</div>
</template>

77
src/components/config/function-config-applied.vue

@ -1,77 +0,0 @@
<template>
<el-row :gutter="10">
<el-col :lg="8" :md="12" :xl="6" :xs="24" class="text-sm text-gray-500 py-3">
<span>金属腐蚀采样频率(分钟)</span>
<span>{{ data.frequency.metal }}</span>
</el-col>
<el-col :lg="8" :md="12" :xl="6" :xs="24" class="text-sm text-gray-500 py-3">
<span>温湿度采样频率(分钟)</span>
<span>{{ data.frequency.th }}</span>
</el-col>
<el-col :lg="8" :md="12" :xl="6" :xs="24" class="text-sm text-gray-500 py-3">
<span>SO2采样频率(分钟)</span>
<span>{{ data.frequency.so2 }}</span>
</el-col>
<el-col :lg="8" :md="12" :xl="6" :xs="24" class="text-sm text-gray-500 py-3">
<span>盐分采样频率(分钟)</span>
<span>{{ data.frequency.salt }}</span>
</el-col>
<el-col :lg="8" :md="12" :xl="6" :xs="24" class="text-sm text-gray-500 py-3">
<span>电池电压低阈值(V)</span>
<span>{{ data.batteryLow }}</span>
</el-col>
<el-col :lg="8" :md="12" :xl="6" :xs="24" class="text-sm text-gray-500 py-3">
<span>电池电压高阈值(V)</span>
<span>{{ data.batteryHigh }}</span>
</el-col>
<el-col :lg="8" :md="12" :xl="6" :xs="24" class="text-sm text-gray-500 py-3">
<span>太阳能板电压高阈值(V)</span>
<span>{{ data.sunHigh }}</span>
</el-col>
<el-col :lg="8" :md="12" :xl="6" :xs="24" class="text-sm text-gray-500 py-3">
<span>湿度高阈值(RH%)</span>
<span>{{ data.humidityHigh }}</span>
</el-col>
<el-col :lg="8" :md="12" :xl="6" :xs="24" class="text-sm text-gray-500 py-3">
<span>温度低阈值()</span>
<span>{{ data.temperatureLow }}</span>
</el-col>
<el-col :lg="8" :md="12" :xl="6" :xs="24" class="text-sm text-gray-500 py-3">
<span>温度高阈值()</span>
<span>{{ data.temperatureHigh }}</span>
</el-col>
<el-col :lg="8" :md="12" :xl="6" :xs="24" class="text-sm text-gray-500 py-3">
<span>金属腐蚀采集个数</span>
<span>{{ data.count }}</span>
</el-col>
<el-col :lg="8" :md="12" :xl="6" :xs="24" class="text-sm text-gray-500 py-3">
<span>安全模式</span>
<span>{{ SER[data.securityMode] }}</span>
</el-col>
</el-row>
<el-row>
<el-col :lg="8" :md="12" :xl="6" :xs="24" class="text-sm text-gray-500 py-3">
<span>上报类型</span>
<span class="mr-8">{{ REPORT_TYPE[data.report.type] }}</span>
<span v-if="data.report.type === 'POINT'">
<span v-for="item in data.report.timePoints" :key="item" class="mr-3">{{ item }}</span>
</span>
<span v-else>{{ data.report.cycle }}分钟</span>
</el-col>
</el-row>
<!-- 刷新下发按钮 -->
<!-- <Refresh @refresh="onSearch(currentDeviceI, 1)" /> -->
</template>
<script setup>
import { defineProps } from 'vue';
import { SER } from '@/config/log';
import { REPORT_TYPE } from '@/config/config';
defineProps({ data: Object });
</script>

287
src/components/config/function-config-pending.vue

@ -1,287 +0,0 @@
<template>
<el-form ref="functionForm" :model="data" label-position="top">
<StatusAndLastTime v-if="data.settingStatus" :setting-status="data.settingStatus" :setting-time="+data.settingTime" />
<el-row :gutter="20">
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="金属腐蚀采样频率(分钟)" prop="frequency.metal">
<el-input-number v-model="data.frequency.metal" :min="0" />
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="温湿度采样频率(分钟)" prop="frequency.th">
<el-input-number v-model="data.frequency.th" :min="0" />
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="SO2采样频率(分钟)" prop="frequency.so2">
<el-input-number v-model="data.frequency.so2" :min="0" />
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="盐分采样频率(分钟)" prop="frequency.salt">
<el-input-number v-model="data.frequency.salt" :min="0" />
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="电池电压低阈值(V)" prop="batteryLow">
<el-input-number v-model="data.batteryLow" :min="0" />
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="电池电压高阈值(V)" prop="batteryHigh">
<el-input-number v-model="data.batteryHigh" :min="0" />
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="太阳能板电压高阈值(V)" prop="sunHigh">
<el-input-number v-model="data.sunHigh" :min="0" />
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="湿度高阈值(RH%)" prop="humidityHigh">
<el-input-number v-model="data.humidityHigh" :min="0" />
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="温度低阈值(℃)" prop="temperatureLow">
<el-input-number v-model="data.temperatureLow" :min="-30" />
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="温度高阈值(℃)" prop="temperatureHigh">
<el-input-number v-model="data.temperatureHigh" :min="0" />
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="金属腐蚀采集个数" prop="count">
<el-input v-model="data.count" :min="0" disabled style="width: 180px" />
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="安全模式" prop="securityMode">
<!-- <el-input v-model="data.securityMode"></el-input> -->
<el-radio v-model="data.securityMode" label="OPEN">不加密</el-radio>
<el-radio v-model="data.securityMode" label="ENCRYPTION">加密</el-radio>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="上报时间" prop="report.type" style="margin-bottom: 0 !important">
<el-radio-group v-model="data.report.type">
<el-radio label="POINT">定时上报</el-radio>
<el-radio label="CYCLE">周期上报(分钟)</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<!-- 定时上报输入-->
<el-row v-if="data.report.type === 'POINT'" :gutter="20">
<el-col v-for="(item, index) in data.report.timePoints" :key="index" :lg="6" :md="12" :sm="24" :xl="4">
<el-form-item prop="report.timePoints" style="margin-bottom: 10px !important">
<el-time-picker v-model="data.report.timePoints[index]" :placeholder="`上报时间点${index + 1}`" format="HH:mm"></el-time-picker>
<el-button circle type="text" @click="removeTimePoint(index)">
<i class="el-icon-circle-close text-lg text-red-600"></i>
</el-button>
</el-form-item>
</el-col>
<el-col v-if="!data.report.timePoints || data.report.timePoints.length < 6" :lg="6" :md="8" :sm="12" :xl="4">
<el-button plain @click="addTimePoints">
<i class="el-icon-plus px-6"></i>
</el-button>
</el-col>
</el-row>
<!--周期上报输入-->
<el-row v-else :gutter="20">
<el-col :span="12">
<el-form-item prop="report.cycle">
<el-input-number v-model="data.report.cycle" :max="1440" :min="10" step="10" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="mt-5 pl-2">
<el-form-item>
<el-button type="primary" @click="onSubmit">提交</el-button>
<el-button @click="onReset">重置</el-button>
</el-form-item>
</el-row>
</el-form>
<!-- 刷新下发按钮 -->
<Refresh @refresh="onSearch(currentDeviceId, 1)" />
</template>
<script setup>
import { computed, defineEmits, defineProps, ref, watch } from 'vue';
import { useStore } from 'vuex';
import { ElMessage } from 'element-plus';
import cloneDeep from 'lodash/cloneDeep';
import isDate from 'lodash/isDate';
import { createConfigFunction, getConfigAppliedFunction } from 'apis';
import Refresh from 'components/config/refresh.vue';
import StatusAndLastTime from 'components/config/status-and-last-time.vue';
import { functionConfig } from '@/config/config';
const data = ref(functionConfig);
const functionForm = ref(null); // form
const store = useStore();
const currentDeviceId = computed(() => store.state.device.currentDeviceId);
const emit = defineEmits(['status']);
const props = defineProps({ activeName: String });
/**
* 格式化时间
*/
const formatTime = date => {
if (!isDate(date)) return '';
const hour = date.getHours();
const minute = date.getMinutes();
return `${hour}:${minute}`;
};
/**
* 查询网络参数配置
* @param {string} deviceId 设备id
* @param {number} type 0数据库查询 1刷新查询
*/
async function onSearch(deviceId, type = 0) {
const params = {
deviceId,
type,
};
const resData = await getConfigAppliedFunction(params);
if (resData && resData.time) {
// ms
resData.time = new Date(+resData.time);
}
if (resData && resData.status) {
//
emit('status', resData.status);
} else {
emit('status', '');
}
if (resData && resData.report.type === 'POINT') {
const { timePoints } = resData.report;
//
if (timePoints && timePoints.length) {
for (let i = 0; i < timePoints.length; i++) {
const date = new Date().toLocaleDateString();
const item = resData.report.timePoints[i];
const reg = /^\d{1,2}:\d{1,2}$/;
if (item && reg.test(item)) {
resData.report.timePoints[i] = new Date(`${date} ${item}`);
} else {
resData.report.timePoints[i] = '';
}
}
} else {
resData.report = [''];
}
}
data.value = resData || functionConfig;
}
watch(
currentDeviceId,
newDeviceId => {
newDeviceId && props.activeName === 'function' && onSearch(newDeviceId);
},
{ immediate: true },
);
//
const onSubmit = () => {
functionForm.value.validate(async () => {
try {
if (!currentDeviceId.value) {
return ElMessage.error('请选择站点');
}
const param = cloneDeep({
...data.value,
deviceId: currentDeviceId.value,
});
param.time = new Date(param.time).getTime();
if (param.report.type === 'POINT') {
//
const points = [...param.report.timePoints];
for (let i = 0; i < points.length; i++) {
if (points[i]) {
points[i] = formatTime(points[i]);
}
}
const validated = validateTimePoints(points);
if (!validated) {
ElMessage.error('上报时间点不能重复');
return;
}
param.report.timePoints = points;
}
await createConfigFunction(param);
ElMessage.success('提交成功');
} catch (error) {
throw new Error(error);
}
});
};
//
const onReset = () => {
functionForm.value.resetFields();
};
// currentDeviceId && onSearch(currentDeviceId.value);
//
function addTimePoints() {
const { timePoints } = data.value.report;
console.log(data.value.report);
if (!timePoints || !timePoints.length) {
data.value.report.timePoints = [''];
} else {
if (timePoints.length >= 6) return;
data.value.report.timePoints.push('');
}
}
/**
* 验证上报时间点是否重复
* @param {array|null} points ["时:分"]
* @returns {boolean}
*/
function validateTimePoints(points) {
if (!points || !points.length) return true;
const obj = {};
points.forEach(item => {
if (obj[item]) {
obj[item] += 1;
} else {
obj[item] = 1;
}
});
// eslint-disable-next-line no-restricted-syntax
for (const key in obj) {
if (obj[key] > 1) return false;
}
return true;
}
/**
* 移除上报时间点
* @param {number} index 索引
*/
function removeTimePoint(index) {
data.value.report.timePoints.splice(index, 1);
}
</script>

65
src/components/config/network-config-applied.vue

@ -1,65 +0,0 @@
<template>
<el-row :gutter="10">
<el-col :lg="8" :md="12" :xl="6" :xs="24" class="text-sm text-gray-500 py-3">
<span>上报IP地址1</span>
<span>{{ data.ip1 }}</span>
</el-col>
<el-col :lg="8" :md="12" :xl="6" :xs="24" class="text-sm text-gray-500 py-3">
<span>上报端口号1</span>
<span>{{ data.port1 }}</span>
</el-col>
<el-col :lg="8" :md="12" :xl="6" :xs="24" class="text-sm text-gray-500 py-3">
<span>上报IP地址2</span>
<span>{{ data.ip2 }}</span>
</el-col>
<el-col :lg="8" :md="12" :xl="6" :xs="24" class="text-sm text-gray-500 py-3">
<span>上报端口号2</span>
<span>{{ data.port2 }}</span>
</el-col>
<el-col :lg="8" :md="12" :xl="6" :xs="24" class="text-sm text-gray-500 py-3">
<span>上报IP地址3</span>
<span>{{ data.ip3 }}</span>
</el-col>
<el-col :lg="8" :md="12" :xl="6" :xs="24" class="text-sm text-gray-500 py-3">
<span>上报端口号3</span>
<span>{{ data.port3 }}</span>
</el-col>
<el-col :lg="8" :md="12" :xl="6" :xs="24" class="text-sm text-gray-500 py-3">
<span>上报IP地址4</span>
<span>{{ data.ip4 }}</span>
</el-col>
<el-col :lg="8" :md="12" :xl="6" :xs="24" class="text-sm text-gray-500 py-3">
<span>上报端口号4</span>
<span>{{ data.port4 }}</span>
</el-col>
<el-col :lg="8" :md="12" :xl="6" :xs="24" class="text-sm text-gray-500 py-3">
<span>备用ip地址</span>
<span>{{ data.ipBackup }}</span>
</el-col>
<el-col :lg="8" :md="12" :xl="6" :xs="24" class="text-sm text-gray-500 py-3">
<span>备用端口号</span>
<span>{{ data.portBackup }}</span>
</el-col>
<el-col :lg="8" :md="12" :xl="6" :xs="24" class="text-sm text-gray-500 py-3">
<span>用户名</span>
<span>{{ data.account }}</span>
</el-col>
<el-col :lg="8" :md="12" :xl="6" :xs="24" class="text-sm text-gray-500 py-3">
<span>密码</span>
<span>{{ data.password }}</span>
</el-col>
<el-col :lg="8" :md="12" :xl="6" :xs="24" class="text-sm text-gray-500 py-3">
<span>APN</span>
<span>{{ data.apn }}</span>
</el-col>
</el-row>
<!-- 刷新下发按钮 -->
<!-- <Refresh @refresh="onSearch(currentDeviceI, 1)" /> -->
</template>
<script setup>
import { defineProps } from 'vue';
defineProps({ data: Object });
</script>

150
src/components/config/network-config-pending.vue

@ -1,150 +0,0 @@
<template>
<el-form ref="networkForm" :model="data" label-position="top">
<StatusAndLastTime v-if="data.settingStatus" :setting-status="data.settingStatus" :setting-time="+data.settingTime" />
<el-row :gutter="20">
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="上报IP1地址" prop="ip1">
<el-input v-model="data.ip1"></el-input>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="上报端口号1" prop="port1">
<el-input v-model="data.port1"></el-input>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="上报IP2地址" prop="ip2">
<el-input v-model="data.ip2"></el-input>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="上报端口号2" prop="port2">
<el-input v-model="data.port2"></el-input>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="上报IP3地址" prop="ip3">
<el-input v-model="data.ip3"></el-input>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="上报端口号3" prop="port3">
<el-input v-model="data.port3"></el-input>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="备用ip地址" prop="ipBackup">
<el-input v-model="data.ipBackup"></el-input>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="备用端口号" prop="portBackup">
<el-input v-model="data.portBackup"></el-input>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="用户名" prop="account">
<el-input v-model="data.account"></el-input>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="密码" prop="password">
<el-input v-model="data.password"></el-input>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="APN" prop="apn">
<el-input v-model="data.apn"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="mt-5 pl-2">
<el-form-item>
<el-button type="primary" @click="onSubmit">提交</el-button>
<el-button @click="onReset">重置</el-button>
</el-form-item>
</el-row>
</el-form>
<!-- 刷新下发按钮 -->
<Refresh @refresh="onSearch(currentDeviceId, 1)" />
</template>
<script setup>
import { computed, defineEmits, defineProps, ref, watch } from 'vue';
import { useStore } from 'vuex';
import { ElMessage } from 'element-plus';
import { createConfigNetwork, getConfigAppliedNetwork } from 'apis';
import Refresh from 'components/config/refresh.vue';
import StatusAndLastTime from 'components/config/status-and-last-time.vue';
import { networkConfig } from '@/config/config';
const data = ref(networkConfig);
const networkForm = ref(null); // form
const store = useStore();
const currentDeviceId = computed(() => store.state.device.currentDeviceId);
const emit = defineEmits(['status']);
const props = defineProps({ activeName: String });
/**
* 查询网络参数配置
* @param {string} deviceId 设备id
* @param {number} type 0数据库查询 1刷新查询
*/
async function onSearch(deviceId, type = 0) {
try {
const params = {
deviceId,
type,
};
const resData = await getConfigAppliedNetwork(params);
data.value = resData || networkConfig;
if (resData && resData.status) {
emit('status', resData.status);
} else {
emit('status', '');
}
} catch (error) {
throw new Error(error);
}
}
watch(
currentDeviceId,
newDeviceId => {
newDeviceId && props.activeName === 'network' && onSearch(newDeviceId);
},
{ immediate: true },
);
//
const onSubmit = () => {
networkForm.value.validate(async () => {
const param = {
...data.value,
deviceId: currentDeviceId.value,
};
try {
await createConfigNetwork(param);
ElMessage.success('提交成功');
} catch (error) {
console.error('error: ', error);
ElMessage.error('提交失败, 请稍后重试');
}
});
};
//
const onReset = () => {
networkForm.value.resetFields();
};
// currentDeviceId && onSearch(currentDeviceId.value);
</script>

19
src/components/config/refresh.vue

@ -1,19 +0,0 @@
<script setup>
import { defineEmits } from 'vue';
defineEmits(['refresh']);
</script>
<template>
<el-tooltip class="refresh-btn shadow-md" effect="dark" content="刷新" placement="top-start">
<el-button type="primary" icon="el-icon-refresh" circle @click="$emit('refresh')"></el-button>
</el-tooltip>
</template>
<style scoped>
.refresh-btn {
position: fixed;
right: 20px;
bottom: 20px;
}
</style>

19
src/components/config/status-and-last-time.vue

@ -1,19 +0,0 @@
<template>
<el-row class="text-gray-500 text-sm my-2 flex items-center">
<span class="mr-10">
配置状态<el-tag :type="PEND_TYPE[settingStatus].type">{{ PEND_TYPE[settingStatus].text }}</el-tag>
</span>
<span>最后配置时间{{ dayjs(settingTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</el-row>
</template>
<script setup>
import dayjs from 'dayjs';
import { defineProps } from 'vue';
import { PEND_TYPE } from '@/config/config';
defineProps({
settingStatus: String,
settingTime: Number,
});
</script>

12
src/components/config/status-tag-pending.vue

@ -1,12 +0,0 @@
<template>
<el-tag :type="PEND_TYPE[type].type">
{{ PEND_TYPE[type].text }}
</el-tag>
</template>
<script setup>
import { defineProps } from 'vue';
import { PEND_TYPE } from '@/config/config';
defineProps({ type: String });
</script>

193
src/components/device/device-create.vue

@ -1,193 +0,0 @@
<template>
<el-form ref="deviceCreate" :model="data" :rules="deviceRules" label-position="top">
<el-row :gutter="20">
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="设备ID(6位)" prop="deviceId">
<el-input v-model="data.deviceId"></el-input>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="站点名称" prop="address">
<el-input v-model="data.address"></el-input>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="产品类型" prop="type">
<!-- <el-input v-model="data.securityMode"></el-input> -->
<el-radio v-model="data.type" label="IACD">大气腐蚀</el-radio>
<el-radio v-model="data.type" label="OTHER">其他</el-radio>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="设备完整ID" prop="deviceFullId">
<el-input v-model="data.deviceFullId"></el-input>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="地区" prop="area">
<el-input v-model="data.area"></el-input>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="联系人" prop="contact">
<el-input v-model="data.contact"></el-input>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="电话" prop="phone">
<el-input v-model="data.phone"></el-input>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="经度" prop="lon">
<el-input v-model="data.lon"></el-input>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="纬度" prop="lat">
<el-input v-model="data.lat"></el-input>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="负责人" prop="head">
<el-input v-model="data.head"></el-input>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="安装位置" prop="installLocation">
<el-input v-model="data.installLocation"></el-input>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="安装时间" prop="installTime">
<el-date-picker v-model="data.installTime" placeholder="安装时间" style="width: 100%" type="datetime"></el-date-picker>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="正式运行时间" prop="runTime">
<el-date-picker v-model="data.runTime" placeholder="正式运行时间" style="width: 100%" type="datetime"></el-date-picker>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="链路地址" prop="linkAddress">
<el-input v-model="data.linkAddress"></el-input>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="探头编号" prop="probNo">
<el-input v-model="data.probNo"></el-input>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="试样" prop="simple">
<el-input v-model="data.simple"></el-input>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="sim卡1" prop="sim1">
<el-input v-model="data.sim1"></el-input>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="设备朝向" prop="deviceDirection">
<el-input v-model="data.deviceDirection"></el-input>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="协议版本" prop="protocolVersion">
<el-input v-model="data.protocolVersion"></el-input>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="与主站后台联调情况" prop="joint">
<el-input v-model="data.joint" type="textarea"></el-input>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="备注" prop="remark">
<el-input v-model="data.remark" type="textarea"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="维修记录" prop="operationRecord">
<el-input v-model="data.operationRecord" type="textarea"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="mt-5 pl-2">
<el-form-item>
<el-button type="primary" @click="onSubmit">提交</el-button>
<el-button @click="onReset">重置</el-button>
</el-form-item>
</el-row>
</el-form>
</template>
<script setup>
import { defineEmits, reactive, ref } from 'vue';
import { useStore } from 'vuex';
import { ElMessage, ElMessageBox } from 'element-plus';
import { createDevice } from 'apis';
import { deviceData, deviceRules } from '@/config/config';
const data = reactive(deviceData);
const deviceCreate = ref(null); // form
const store = useStore();
const emit = defineEmits(['hide']);
//
const onReset = () => {
deviceCreate.value.resetFields();
};
//
const onSubmit = () => {
deviceCreate.value.validate(async valid => {
if (!valid) return;
if (data.installTime) {
data.installTime = new Date(data.installTime).getTime();
}
if (data.runTime) {
data.runTime = new Date(data.runTime).getTime();
}
try {
await createDevice({ ...data });
store.commit('device/unshiftDevice', { ...data });
ElMessageBox.confirm('添加成功,是否继续添加', '提示', {
confirmButtonText: '继续添加',
cancelButtonText: '关闭',
type: 'success',
})
.then(() => {
//
onReset();
})
.catch(() => {
onReset();
emit('on-hide');
});
await store.dispatch('device/getDevices'); //
} catch (error) {
ElMessage.error(error);
throw new Error(error);
}
});
};
</script>

13
src/components/device/device-edit.vue

@ -1,13 +0,0 @@
<template>
<el-dialog v-model="show" title="编辑设备信息" fullscreen="true" @close="$emit('toggle-mdoal')">
<DeviceEdit v-if="show" :edit="true" @cancel="$emit('toggle-mdoal')" />
</el-dialog>
</template>
<script setup>
import { defineProps, defineEmits } from 'vue';
import DeviceEdit from 'views/device-edit.vue';
defineProps({ show: Boolean });
defineEmits(['toggle-mdoal']);
</script>

233
src/components/history/device.vue

@ -1,233 +0,0 @@
<template>
<SearchBar :loading-search="loadingSearch" :show-command="true" :show-export="true" :show-pass-setting="true" @search="onSearch" />
<template v-if="data">
<el-table :data="data" :max-height="contentHeight" border stripe style="width: 100%">
<el-table-column align="center" fixed label="设备ID" min-width="80" prop="deviceId" />
<el-table-column align="center" label="采集时间" min-width="170">
<template #default="scope">
{{ formatTime(+scope.row.time) }}
</template>
</el-table-column>
<el-table-column align="center" label="后台接收时间" min-width="170">
<template #default="scope">
{{ formatTime(+scope.row.createdAt) }}
</template>
</el-table-column>
<el-table-column align="center" label="锌腐蚀电流(nA)" min-width="130" prop="corrosion1" />
<el-table-column align="center" label="铜腐蚀电流(nA)" min-width="130" prop="corrosion2" />
<el-table-column align="center" label="铝腐蚀电流(nA)" min-width="130" prop="corrosion3" />
<el-table-column align="center" label="钢腐蚀电流(nA)" min-width="130" prop="corrosion4" />
<el-table-column align="center" label="环境温度(℃)" min-width="110" prop="environmentTemperature" />
<el-table-column align="center" label="环境湿度(RH%)" min-width="130" prop="environmentHumidity" />
<el-table-column align="center" label="SO2(ppb)" min-width="90" prop="so2" />
<el-table-column align="center" label="盐分温度(℃)" min-width="110" prop="saltT" />
<el-table-column align="center" label="盐分阻抗(Ω)" min-width="110" prop="saltR" />
<el-table-column align="center" label="机箱温度(℃)" min-width="110" prop="deviceTemperature" />
<el-table-column align="center" label="机箱湿度(RH%)" min-width="130" prop="deviceHumidity" />
<el-table-column align="center" label="太阳能板电压(V)" min-width="140" prop="solarVoltage" />
<el-table-column align="center" label="蓄电池电压(V)" min-width="120" prop="batteryVoltage" />
<el-table-column align="center" label="电压百分比" min-width="94" prop="batteryVoltagePercentage" />
<el-table-column align="center" label="剩余电量(mAH)" min-width="140" prop="batteryVoltageRemain" />
<el-table-column align="center" label="消耗电量(mAH)" min-width="140" prop="batteryLoss" />
<el-table-column align="center" label="ICCID" min-width="190" prop="iccid" />
<el-table-column align="center" label="IMEI" min-width="150" prop="imei" />
<el-table-column align="center" label="信号强度" min-width="80" prop="signal" />
<el-table-column align="center" label="基站编号" min-width="130" prop="stationNo" />
<el-table-column align="center" label="硬件版本" min-width="80" prop="hardwareVersion" />
<el-table-column align="center" label="软件版本" min-width="80" prop="softwareVersion" />
</el-table>
<el-pagination
:current-page="page.page"
:default-page-size="50"
:page-count="page.count"
:page-size="page.size"
:page-sizes="[1, 10, 20, 50, 100]"
:total="page.total"
background
class="my-3 float-right"
layout="total, sizes, prev, pager, next, jumper"
@current-change="onCurrentPageChange"
@size-change="onSizeChange"
@prev-click="onPrev"
@next-click="onNext"
></el-pagination>
</template>
<el-badge v-if="missingData && missingData.length" :max="99" :value="missingData.length" class="pass-button animate-bounce">
<div circle class="shadow-xl btn" type="primary" @click="showMissing = true">
<i class="el-icon-upload"></i>
</div>
</el-badge>
<el-dialog v-model="showMissing" custom-class="device-dialog" title="待补传列表" top="30px" width="80%">
<MissingData :data="missingData" @on-success="onPassSuccess" />
</el-dialog>
</template>
<script setup>
import { computed, onMounted, ref } from 'vue';
import { useStore } from 'vuex';
import { getDatas } from 'apis';
import { ElMessage } from 'element-plus';
import dayjs from 'dayjs';
import SearchBar from 'components/history/search-bar-data.vue';
import MissingData from 'components/history/missing-data.vue';
const search = ref({});
const page = ref({
page: 1,
size: 50,
});
let timer = null;
const store = useStore();
const token = computed(() => store.getters['user/token']);
const currentDeviceId = computed(() => store.state.device.currentDeviceId); // id
const contentHeight = ref(600);
const loadingSearch = ref(false);
const data = ref(null);
const showMissing = ref(false);
const missingData = ref([]);
//
const getData = async () => {
try {
if (token && token.value) {
if (!currentDeviceId.value) {
return ElMessage.error('请选择站点');
}
const { date, missingIntervalInMs } = search.value;
if (!date || date.length !== 2) {
return ElMessage.error('请选择时间 ');
}
const params = {
deviceId: currentDeviceId.value,
gatheredDateRange: date,
paging: true,
missingIntervalInMs,
page: page.value.page,
size: page.value.size,
sort: [
{
col: 'time',
order: 'DESC',
},
],
};
loadingSearch.value = true;
const resData = await getDatas(params);
loadingSearch.value = false;
data.value = resData.data;
page.value = resData.page;
timer && clearTimeout(timer);
timer = null;
if (resData.missingData) {
missingData.value = [...resData.missingData];
missingData.value.forEach(item => {
item.uuid = `${item.deviceId}-${item.startTime}-${item.endTime}`;
});
}
} else {
timer = setTimeout(() => {
getData();
}, 20);
}
} catch (error) {
ElMessage.error(error.message || '获取数据失败');
console.log('error: ', error);
}
};
// getData();
//
onMounted(() => {
const winHeight = document.documentElement.clientHeight;
contentHeight.value = winHeight - 260;
});
/**
* 监听sear h信息
* @param {object} payload search组件emi 的数据
*/
const onSearch = payload => {
search.value = { ...payload };
getData();
};
/**
* 当前 码变化
* 更新page 重新 取数据
* @param {number} e 的页码
*/
const onCurrentPageChange = e => {
page.value.page = e;
getData();
};
const onSizeChange = e => {
page.value.size = e;
getData();
};
//
const onNext = e => {
page.value.page = e + 1;
getData();
};
//
const onPrev = e => {
page.value.page = e - 1;
getData();
};
/**
* 格式化时间
* @param {number} time 时间戳
* @returns {string}
*/
function formatTime(time) {
return dayjs(new Date(time)).format('YYYY-MM-DD HH:mm:ss');
}
//
function onPassSuccess(uuid) {
// eslint-disable-next-line no-restricted-syntax
for (const item of missingData.value) {
if (item.uuid === uuid) {
item.disabled = true;
}
}
}
</script>
<style scoped>
.device-dialog :deep(.el-dialog__body) {
padding-top: 0 !important;
}
.pass-button {
z-index: 999;
position: fixed;
right: 30px;
bottom: 40px;
}
.pass-button .btn {
width: 56px;
height: 56px;
display: flex;
justify-content: center;
align-items: center;
background-color: #409eff;
border-radius: 50%;
color: #fff;
font-size: 26px;
cursor: pointer;
}
</style>

42
src/components/history/history-log-table.vue

@ -1,42 +0,0 @@
<template>
<template v-if="data">
<el-table :data="data" :max-height="contentHeight" border class="mt-6" stripe style="width: 100%">
<el-table-column align="center" label="设备ID" min-min-width="100" prop="deviceId" />
<el-table-column align="center" label="开始时间" min-width="200">
<template #default="scope">
{{ dayjs(new Date(+scope.row.startTime)).format('YYYY-MM-DD HH:mm:ss') }}
</template>
</el-table-column>
<el-table-column align="center" label="截止时间" min-width="200">
<template #default="scope">
{{ dayjs(new Date(+scope.row.endTime)).format('YYYY-MM-DD HH:mm:ss') }}
</template>
</el-table-column>
<el-table-column align="center" header-align="center" label="下发状态" min-width="150">
<template #default="scope">
<StatusTagPending :type="scope.row.status" />
</template>
</el-table-column>
<el-table-column align="center" header-align="center" label="类型" min-width="150">
<template #default="scope">
<span v-if="scope.row && scope.row.type && PENDING_TYPE[scope.row.type]">{{ PENDING_TYPE[scope.row.type].text }}</span>
</template>
</el-table-column>
</el-table>
</template>
</template>
<script setup>
import { defineProps, onMounted, ref } from 'vue';
import dayjs from 'dayjs';
import { PENDING_TYPE } from '@/config/config';
defineProps({ data: Object });
const contentHeight = ref(600);
onMounted(() => {
const winHeight = document.documentElement.clientHeight;
contentHeight.value = winHeight - 150;
});
</script>

35
src/components/history/history-log.vue

@ -1,35 +0,0 @@
<template>
<SearchHistoryLog @search="getData" />
<TableHistoryLog :data="data" />
</template>
<script setup>
import { computed, ref } from 'vue';
import { useStore } from 'vuex';
import { getSendHistory } from 'apis';
import { ElMessage } from 'element-plus';
import SearchHistoryLog from 'components/history/search-history-log.vue';
import TableHistoryLog from 'components/history/history-log-table.vue';
const store = useStore();
const currentDeviceId = computed(() => store.state.device.currentDeviceId); // id
const data = ref([]);
async function getData(
params = {
deviceId: currentDeviceId.value,
status: '',
type: '',
},
) {
try {
const resData = await getSendHistory(params);
data.value = resData ? [...resData] : [];
} catch (error) {
ElMessage.error(error.message || '查询失败');
}
}
getData();
</script>

162
src/components/history/local.vue

@ -1,162 +0,0 @@
<template>
<SearchBar :show-export="true" :show-type-select="true" @search="onSearch" />
<template v-if="data">
<el-table :data="data" :max-height="contentHeight" border stripe style="width: 100%">
<el-table-column align="center" fixed label="设备ID" min-width="80" prop="deviceId" />
<el-table-column align="center" label="采集时间" min-width="170">
<template #default="scope">
{{ formatTime(+scope.row.time) }}
</template>
</el-table-column>
<el-table-column align="center" label="后台接收时间" min-width="170">
<template #default="scope">
{{ formatTime(+scope.row.createdAt) }}
</template>
</el-table-column>
<el-table-column align="center" label="锌腐蚀电流(nA)" min-width="130" prop="corrosion1" />
<el-table-column align="center" label="铜腐蚀电流(nA)" min-width="130" prop="corrosion2" />
<el-table-column align="center" label="铝腐蚀电流(nA)" min-width="130" prop="corrosion3" />
<el-table-column align="center" label="钢腐蚀电流(nA)" min-width="130" prop="corrosion4" />
<el-table-column align="center" label="环境温度(℃)" min-width="110" prop="environmentTemperature" />
<el-table-column align="center" label="环境湿度(RH%)" min-width="130" prop="environmentHumidity" />
<el-table-column align="center" label="SO2(ppb)" min-width="90" prop="so2" />
<el-table-column align="center" label="盐分温度(℃)" min-width="110" prop="saltT" />
<el-table-column align="center" label="盐分阻抗(Ω)" min-width="110" prop="saltR" />
<el-table-column align="center" label="太阳能板电压(V)" min-width="140" prop="solarVoltage" />
<el-table-column align="center" label="蓄电池电压(V)" min-width="120" prop="batteryVoltage" />
<el-table-column align="center" label="机箱温度(℃)" min-width="110" prop="deviceTemperature" />
<el-table-column align="center" label="机箱湿度(RH%)" min-width="130" prop="deviceHumidity" />
</el-table>
<el-pagination
:current-page="page.page"
:default-page-size="50"
:page-count="page.count"
:page-size="page.size"
:page-sizes="[1, 10, 20, 50, 100]"
:total="page.total"
background
class="my-3 float-right"
layout="total, sizes, prev, pager, next, jumper"
@current-change="onCurrentPageChange"
@size-change="onSizeChange"
@prev-click="onPrev"
@next-click="onNext"
></el-pagination>
</template>
</template>
<script setup>
import { getDatas } from 'apis';
import SearchBar from 'components/history/search-bar-data.vue';
import dayjs from 'dayjs';
import { ElMessage } from 'element-plus';
import { computed, onMounted, ref } from 'vue';
import { useStore } from 'vuex';
const search = ref({});
const page = ref({
page: 1,
size: 50,
});
let timer = null;
const store = useStore();
const token = computed(() => store.getters['user/token']);
const currentDeviceId = computed(() => store.state.device.currentDeviceId); // id
const contentHeight = ref(600);
const data = ref(null);
//
const getData = async () => {
try {
if (token && token.value) {
if (!currentDeviceId.value) {
return ElMessage.error('请选择站点');
}
const options = { ...search.value };
const date = options && options.date ? options.date : [];
console.log(date);
if (!date || date.length !== 2) {
return ElMessage.error('请选择时间 ');
}
const params = {
deviceId: currentDeviceId.value,
gatheredDateRange: date,
paging: true,
page: page.value.page,
size: page.value.size,
sort: [
{
col: 'time',
order: 'DESC',
},
],
};
const resData = await getDatas(params);
data.value = resData.data;
page.value = resData.page;
timer && clearTimeout(timer);
timer = null;
} else {
timer = setTimeout(() => {
getData();
}, 20);
}
} catch (error) {
ElMessage.error(error.message || '获取数据失败');
console.log('error: ', error);
}
};
//
onMounted(() => {
const winHeight = document.documentElement.clientHeight;
contentHeight.value = winHeight - 250;
});
/**
* 监听sear h信息
* @param {object} payload search组件emi 的数据
*/
const onSearch = payload => {
search.value = { ...payload };
getData();
};
/**
* 当前 码变化
* 更新page 重新 取数据
* @param {number} e 的页码
*/
const onCurrentPageChange = e => {
page.value.page = e;
getData();
};
const onSizeChange = e => {
page.value.size = e;
getData();
};
//
const onNext = e => {
page.value.page = e + 1;
getData();
};
//
const onPrev = e => {
page.value.page = e - 1;
getData();
};
/**
* 格式化时间
* @param {number} time 时间戳
* @returns {string}
*/
function formatTime(time) {
return dayjs(new Date(time)).format('YYYY-MM-DD HH:mm:ss');
}
</script>

48
src/components/history/missing-data.vue

@ -1,48 +0,0 @@
<template>
<el-table :data="data" :max-height="contentHeight" border class="mt-6" stripe style="width: 100%; margin-top: 0">
<el-table-column align="center" label="设备ID" min-width="100" prop="deviceId" />
<el-table-column align="center" label="起始时间" min-width="200">
<template #default="scope">
{{ dayjs(new Date(+scope.row.startTime)).format('YYYY-MM-DD HH:mm:ss') }}
</template>
</el-table-column>
<el-table-column align="center" label="截止时间" min-width="200">
<template #default="scope">
{{ dayjs(new Date(+scope.row.endTime)).format('YYYY-MM-DD HH:mm:ss') }}
</template>
</el-table-column>
<el-table-column align="center" header-align="center" label="操作" min-width="100">
<template #default="scope">
<el-button :disabled="scope.row.disabled" plain size="small" type="success" @click="handlePass(scope.row)">补传 </el-button>
</template>
</el-table-column>
</el-table>
</template>
<script setup>
import { defineProps, defineEmits, ref } from 'vue';
import dayjs from 'dayjs';
import { sendCommand } from 'apis';
import { ElMessage } from 'element-plus';
defineProps({ data: Object });
const emit = defineEmits(['on-success']);
const contentHeight = ref(600);
/**
* 发送补传指令
* @param { Object } item
* @returns {Promise<void>}
*/
async function handlePass(item) {
try {
await sendCommand({ ...item });
ElMessage.success('发送成功');
emit('on-success', item.uuid);
} catch (error) {
ElMessage.error(error.message || '发送补传指令失败');
}
}
</script>

148
src/components/history/search-bar-data.vue

@ -1,148 +0,0 @@
<template>
<el-form ref="searchDeviceForm" :inline="true" :model="searchDevice">
<el-form-item label="选择站点:">
<el-select v-model="searchDevice.deviceId" placeholder="请选择站点" @change="change">
<!-- <el-option label="全部" value></el-option> -->
<el-option
v-for="item in devices"
:key="item.deviceId"
:label="`${item.address}-${item.deviceId}`"
:value="item.deviceId"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="起始时间:">
<el-date-picker
v-model="searchDevice.date[0]"
format="YYYY-MM-DD HH:mm"
placeholder="起始时间"
style="width: 180px"
type="datetime"
></el-date-picker>
</el-form-item>
<el-form-item label="截止时间:">
<el-date-picker
v-model="searchDevice.date[1]"
format="YYYY-MM-DD HH:mm"
placeholder="截止时间"
style="width: 180px"
type="datetime"
></el-date-picker>
</el-form-item>
<el-form-item v-if="showPassSetting" label="补传间隔(h):">
<el-input-number v-model="searchDevice.missingIntervalInMs" :min="0" controls-position="right" style="width: 100px" />
</el-form-item>
<el-form-item>
<template v-if="showCommand">
<el-button type="primary" @click="onSubmit">查询历史数据</el-button>
<!-- <el-button type="warning" @click="onSend('EVENT')">查询历史事件</el-button>-->
</template>
<el-button v-else :loading="loadingSearch" type="primary" @click="onSubmit">
<i class="el-icon-search mr-2"></i>
查询
</el-button>
</el-form-item>
<el-form-item v-if="showExport">
<el-button :loading="loadingExport" type="success" @click="onExport">
<i class="el-icon-download mr-2"></i>
导出
</el-button>
</el-form-item>
</el-form>
</template>
<script setup>
import { exportHistory } from 'apis';
import dayjs from 'dayjs';
import { computed, defineEmits, defineProps, reactive, ref, watch } from 'vue';
import { useStore } from 'vuex';
const emit = defineEmits(['search']);
const searchDevice = reactive({
deviceId: '',
date: [dayjs().subtract(7, 'day').startOf('day'), new Date()],
missingIntervalInMs: 1,
});
const searchDeviceForm = ref(null); // form
const store = useStore();
const devices = computed(() => store.state.device.devices);
const currentDeviceId = computed(() => store.state.device.currentDeviceId); // id
const loadingExport = ref(false);
defineProps({
showExport: Boolean,
loadingSearch: Boolean,
showCommand: Boolean,
showPassSetting: Boolean,
});
// currentDeviceId
watch(
[searchDeviceForm, currentDeviceId],
([newForm, newDeviceId]) => {
if (newDeviceId && newForm) {
searchDevice.deviceId !== newDeviceId && (searchDevice.deviceId = newDeviceId);
onSubmit();
}
},
{ immediate: true },
);
const change = e => {
store.commit('device/setCurrentDeviceId', e);
};
//
function generateParams() {
const { deviceId, date, missingIntervalInMs } = searchDevice;
const params = {
deviceId,
missingIntervalInMs: missingIntervalInMs * 60 * 60 * 1000,
};
if (date) {
const start = +dayjs(date[0]).format('x');
const end = +dayjs(date[1]).format('x');
params.date = [start, end];
}
return params;
}
//
function onSubmit() {
searchDeviceForm.value.validate(valid => {
if (valid) {
const params = generateParams();
emit('search', params);
}
});
}
//
async function onExport() {
try {
loadingExport.value = true;
const params = generateParams();
params.sort = [
{
col: 'time',
order: 'DESC',
},
];
params.paging = false;
params.gatheredDateRange = params.date;
const resData = await exportHistory(params);
loadingExport.value = false;
resData && (window.location.href = resData);
} catch (error) {
loadingExport.value = false;
throw new Error(error);
}
}
</script>

77
src/components/history/search-history-log.vue

@ -1,77 +0,0 @@
<template>
<el-form ref="searchHistoryLogForm" :inline="true" :model="searchDevice">
<el-form-item label="选择站点">
<el-select v-model="searchDevice.deviceId" placeholder="请选择站点" @change="change">
<el-option label="全部" value></el-option>
<el-option
v-for="item in devices"
:key="item.deviceId"
:label="`${item.address}-${item.deviceId}`"
:value="item.deviceId"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="类型">
<el-select v-model="searchDevice.type" placeholder="选择查询类型">
<el-option label="全部" value=""></el-option>
<el-option label="事件上报" value="EVENT"></el-option>
<el-option label="业务上报" value="DATA"></el-option>
</el-select>
</el-form-item>
<el-form-item label="状态">
<el-select v-model="searchDevice.status" placeholder="选择查询类型">
<el-option label="全部" value=""></el-option>
<el-option label="待下发" value="PENDING"></el-option>
<el-option label="下发成功" value="SUCCESS"></el-option>
<el-option label="下发失败" value="FAIL"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">
<i class="el-icon-search mr-2"></i>
查询
</el-button>
</el-form-item>
</el-form>
</template>
<script setup>
import { computed, defineEmits, reactive, ref, watch } from 'vue';
import { useStore } from 'vuex';
const emit = defineEmits(['search']);
const searchDevice = reactive({
deviceId: '',
type: '',
status: '',
});
const searchHistoryLogForm = ref(null); // form
const store = useStore();
const devices = computed(() => store.state.device.devices);
const currentDeviceId = computed(() => store.state.device.currentDeviceId); // id
// currentDeviceId
watch(
() => currentDeviceId.value,
newValue => {
if (newValue) {
searchDevice.deviceId !== newValue && (searchDevice.deviceId = newValue);
}
},
{ immediate: true },
);
const change = e => {
store.commit('device/setCurrentDeviceId', e);
};
//
const onSubmit = () => {
searchHistoryLogForm.value.validate(() => {
emit('search', { ...searchDevice });
});
};
</script>

23
src/components/navbar.vue

@ -1,23 +0,0 @@
<script setup>
import { computed } from 'vue';
import { useStore } from 'vuex';
const store = useStore();
const toggleCollapse = () => {
console.log('Toggle Collapse');
store.commit('toggleCollapse');
};
const menu = computed(() => store.state.menu);
</script>
<template>
<h1 class="text-lg font-medium py-3 px-6 shadow">
<i
class="el-icon-guide mr-2"
:class="{ 'text-gray-800': !menu.collapse, 'text-gray-400': menu.collapse }"
@click="toggleCollapse"
v-if="menu.show"
></i>
{{ $route.meta.title || '智能大气腐蚀监测平台' }}
</h1>
</template>

49
src/components/overview/chart-device-count.vue

@ -1,49 +0,0 @@
<template>
<div id="device-overview-container" style="height: 354px"></div>
</template>
<script setup>
import { computed, onMounted, ref, watchEffect } from 'vue';
import { useStore } from 'vuex';
import isEmpty from 'lodash/isEmpty';
import { generateChartOption } from 'utils/overview';
import * as echarts from 'echarts/core';
import { TitleComponent, TooltipComponent } from 'echarts/components';
import { PieChart } from 'echarts/charts';
import { LabelLayout } from 'echarts/features';
import { CanvasRenderer } from 'echarts/renderers';
echarts.use([TitleComponent, TooltipComponent, PieChart, CanvasRenderer, LabelLayout]);
const store = useStore();
const count = computed(() => store.state.device.count);
const isMounted = ref(false);
let myChart = null;
watchEffect(() => {
if (isEmpty(count.value) || !isMounted.value) return;
if (!myChart) {
initChart();
render(count.value);
} else {
render(count.value);
}
});
// chart
function initChart() {
const chartDom = document.getElementById('device-overview-container');
myChart = echarts.init(chartDom);
}
// chart
function render(rawCount) {
const option = generateChartOption(rawCount);
option && myChart.setOption(option);
}
onMounted(() => {
initChart();
isMounted.value = true;
});
</script>

93
src/components/overview/chart-device-detail.vue

@ -1,93 +0,0 @@
<template>
<el-card class="pb-4" shadow="hover">
<el-row :gutter="20">
<el-col :span="12">
<div class="text-sm text-gray-400">设备总数</div>
<div class="text-gray-500 text-4xl mt-1 mb-3">{{ count.total }}</div>
<div class="line-wrap bg-blue-50">
<div :style="{ width: `${countPercent.total}` }" class="line bg-blue-300"></div>
</div>
</el-col>
<el-col :span="12">
<div class="text-sm text-gray-400">正常数量</div>
<div class="text-gray-500 text-4xl mt-1 mb-3">{{ count.normal }}</div>
<div class="line-wrap bg-lime-50">
<div :style="{ width: `${countPercent.normal}` }" class="line bg-lime-300"></div>
</div>
</el-col>
<el-col :span="12" class="mt-10">
<div class="text-sm text-gray-400">离线数量</div>
<div class="text-gray-500 text-4xl mt-1 mb-3">{{ count.offline }}</div>
<div class="line-wrap bg-gray-50">
<div :style="{ width: `${countPercent.offline}` }" class="line bg-gray-300"></div>
</div>
</el-col>
<el-col :span="12" class="mt-10">
<div class="text-sm text-gray-400">报警数量</div>
<div class="text-gray-500 text-4xl mt-1 mb-3">{{ count.warning }}</div>
<div class="line-wrap bg-yellow-50">
<div :style="{ width: `${countPercent.warning}` }" class="line bg-yellow-300"></div>
</div>
</el-col>
<el-col :span="12" class="mt-10">
<div class="text-sm text-gray-400">在线数量</div>
<div class="text-gray-500 text-4xl mt-1 mb-3">{{ count.online }}</div>
<div class="line-wrap bg-green-50">
<div :style="{ width: `${countPercent.online}` }" class="line bg-green-300"></div>
</div>
</el-col>
<el-col :span="12" class="mt-10">
<div class="text-sm text-gray-400">故障数量</div>
<div class="text-gray-500 text-4xl mt-1 mb-3">{{ count.fault }}</div>
<div class="line-wrap bg-red-50">
<div :style="{ width: `${countPercent.fault}` }" class="line bg-red-300"></div>
</div>
</el-col>
</el-row>
</el-card>
</template>
<script setup>
import { computed } from 'vue';
import { useStore } from 'vuex';
const store = useStore();
const count = computed(() => store.state.device.count);
const countPercent = computed(() => {
return {
online: computeWidth(count.value.online),
offline: computeWidth(count.value.offline),
warning: computeWidth(count.value.warning),
fault: computeWidth(count.value.fault),
total: computeWidth(count.value.total),
normal: computeWidth(count.value.normal),
};
});
/**
* 计算宽度百分比
* @param {number} itemCount
* @returns {string}
*/
function computeWidth(itemCount) {
const percent = (itemCount * 100) / count.value.total;
return `${percent}%`;
}
</script>
<style scoped>
.line-wrap {
position: relative;
width: 60%;
height: 10px;
border-radius: 5px;
overflow: hidden;
}
.line {
position: absolute;
left: 0;
top: 0;
height: 100%;
}
</style>

112
src/components/overview/device-table.vue

@ -1,112 +0,0 @@
<template>
<template v-if="devicesAll && devicesAll.data">
<el-table :data="devicesAll.data" :max-height="contentHeight" border class="mt-6" stripe style="width: 100%">
<el-table-column align="center" label="站点名称" min-width="150" prop="address" />
<el-table-column align="center" label="设备ID" min-width="100" prop="deviceId" />
<el-table-column align="center" label="注册时间" min-width="200">
<template #default="scope">
{{ dayjs(new Date(+scope.row.signupTime)).format('YYYY-MM-DD HH:mm:ss') }}
</template>
</el-table-column>
<el-table-column align="center" label="最近上传时间" min-width="200">
<template #default="scope">
{{ dayjs(new Date(+scope.row.lastUploadTime)).format('YYYY-MM-DD HH:mm:ss') }}
</template>
</el-table-column>
<el-table-column align="center" header-align="center" label="设备状态" min-width="150">
<template #default="scope">
<el-tag v-if="scope.row.status" :color="STATUS_COLOR[scope.row.status].color" style="color: #fff">
{{ STATUS_COLOR[scope.row.status].text }}
</el-tag>
</template>
</el-table-column>
<el-table-column align="center" header-align="center" label="产品类型" min-width="150">
<template #default="scope">
<span v-if="scope.row.type && ASDU_TYPE[scope.row.type]">{{ ASDU_TYPE[scope.row.type].text }}</span>
</template>
</el-table-column>
<el-table-column align="center" header-align="center" label="协议版本" min-width="150" prop="protocolVersion" />
</el-table>
<el-pagination
:current-page="devicesAll.page.page"
:default-page-size="50"
:page-count="devicesAll.page.count"
:page-size="devicesAll.page.size"
:page-sizes="[1, 10, 20, 50, 100]"
:total="devicesAll.page.total"
background
class="my-3 float-right"
layout="total, sizes, prev, pager, next, jumper"
@size-change="onSizeChange"
@current-change="onCurrentPageChange"
></el-pagination>
</template>
</template>
<script setup>
import { computed, onMounted, ref } from 'vue';
import { useStore } from 'vuex';
import dayjs from 'dayjs';
import { STATUS_COLOR } from '@/config/config';
import { ASDU_TYPE } from '@/config/log';
let timer = null;
const store = useStore();
const token = computed(() => store.getters['user/token']);
const devicesAll = computed(() => {
return store.state.device.devicesAll;
});
const currentDeviceId = computed(() => store.state.device.currentDeviceId);
const contentHeight = ref(600);
//
const getDevicesAllData = () => {
try {
if (token && token.value) {
store.dispatch('device/getDevicesAll');
timer && clearTimeout(timer);
timer = null;
} else {
timer = setTimeout(() => {
getDevicesAllData();
});
}
} catch (error) {
throw new Error(error);
}
};
getDevicesAllData();
//
onMounted(() => {
const winHeight = document.documentElement.clientHeight;
contentHeight.value = winHeight - 250;
});
/**
* 删除
* @param {number} page 页码
* @param {number} size 每页条数
*/
function onSearch(page, size = 50) {
const deviceId = currentDeviceId.value;
const params = {
deviceId,
page,
size,
};
store.dispatch('device/getDevicesAll', params);
}
//
const onCurrentPageChange = page => {
onSearch(page, devicesAll.value.page.size || 50);
};
//
const onSizeChange = size => {
onSearch(1, size);
};
</script>

66
src/components/realtime/search-bar.vue

@ -1,66 +0,0 @@
<template>
<el-form ref="searchDeviceForm" :inline="true" :model="searchDevice">
<el-form-item label="选择站点">
<el-select v-model="searchDevice.deviceId" placeholder="请选择站点" @change="change">
<el-option v-if="showAll" label="全部" value></el-option>
<el-option
v-for="item in devices"
:key="item.deviceId"
:label="`${item.address}-${item.deviceId}`"
:value="item.deviceId"
></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">
<i class="el-icon-search mr-2"></i>
查询
</el-button>
</el-form-item>
<el-form-item v-if="showRefresh">
<el-button type="primary" @click="$emit('refresh')">
<i class="el-icon-refresh mr-2"></i>
刷新
</el-button>
</el-form-item>
</el-form>
</template>
<script setup>
import { computed, defineEmits, defineProps, reactive, ref, watch } from 'vue';
import { useStore } from 'vuex';
import { useRouter } from 'vue-router';
defineProps({ showRefresh: Boolean });
const emit = defineEmits(['getDate', 'refresh']);
const router = useRouter();
const searchDevice = reactive({ deviceId: '' });
const searchDeviceForm = ref(null); // form
const store = useStore();
const devices = computed(() => store.state.device.devices);
const currentDeviceId = computed(() => store.state.device.currentDeviceId); // id
const showAll = computed(() => router.currentRoute.value.name === 'devices'); //
// currentDeviceId
watch(
() => currentDeviceId.value,
newValue => {
if (newValue && searchDevice.deviceId !== newValue) {
searchDevice.deviceId = newValue;
}
},
{ immediate: true },
);
const change = e => {
store.commit('device/setCurrentDeviceId', e);
};
const onSubmit = () => {
searchDeviceForm.value.validate(() => {
emit('search');
});
};
</script>

131
src/components/statistical/search-bar.vue

@ -1,131 +0,0 @@
<template>
<el-form ref="searchDeviceForm" :inline="true" :model="searchDevice">
<el-form-item label="选择站点:">
<el-select v-model="searchDevice.deviceId" placeholder="请选择站点" @change="change">
<!-- <el-option label="全部" value></el-option> -->
<el-option
v-for="item in devices"
:key="item.deviceId"
:label="`${item.address}-${item.deviceId}`"
:value="item.deviceId"
></el-option>
</el-select>
</el-form-item>
<el-form-item v-if="showTypeSelect" label="类型:">
<el-select v-model="searchDevice.dataType" placeholder="选择查询类型">
<el-option label="全部" value=""></el-option>
<el-option label="事件上报" value="ReportHistoryEvent"></el-option>
<el-option label="业务上报" value="ReportHistoryData"></el-option>
</el-select>
</el-form-item>
<el-form-item label="起始时间:">
<el-date-picker v-model="searchDevice.date[0]" format="YYYY-MM-DD HH:mm" placeholder="起始时间" type="datetime"></el-date-picker>
</el-form-item>
<el-form-item label="截止时间:">
<el-date-picker v-model="searchDevice.date[1]" format="YYYY-MM-DD HH:mm" placeholder="截止时间" type="datetime"></el-date-picker>
</el-form-item>
<el-form-item>
<el-button :loading="loadingSearch" type="primary" @click="onSubmit">
<i class="el-icon-search mr-2"></i>
查询
</el-button>
</el-form-item>
<el-form-item v-if="showExport">
<el-button :loading="loadingExport" type="success" @click="onExport">
<i class="el-icon-download mr-2"></i>
导出
</el-button>
</el-form-item>
</el-form>
</template>
<script setup>
import { computed, defineEmits, defineProps, reactive, ref, watch } from 'vue';
import { useStore } from 'vuex';
import dayjs from 'dayjs';
import { exportHistory } from 'apis';
const emit = defineEmits(['search']);
const searchDevice = reactive({
deviceId: '',
date: [dayjs().subtract(7, 'day').startOf('day'), new Date()],
dataType: 'ReportHistoryData',
});
const searchDeviceForm = ref(null); // form
const store = useStore();
const devices = computed(() => store.state.device.devices);
const currentDeviceId = computed(() => store.state.device.currentDeviceId); // id
const loadingExport = ref(false);
defineProps({
showExport: Boolean,
showTypeSelect: Boolean,
loadingSearch: Boolean,
});
// currentDeviceId
watch(
() => currentDeviceId.value,
newValue => {
if (newValue) {
searchDevice.deviceId !== newValue && (searchDevice.deviceId = newValue);
}
},
{ immediate: true },
);
const change = e => {
store.commit('device/setCurrentDeviceId', e);
};
//
function generateParams() {
const { deviceId, date, dataType } = searchDevice;
let params = {
deviceId,
date,
dataType,
};
if (date) {
const start = +dayjs(date[0]).format('x');
const end = +dayjs(date[1]).format('x');
const daterange = [start, end];
params = {
deviceId,
date: daterange,
dataType,
};
}
return params;
}
//
const onSubmit = () => {
searchDeviceForm.value.validate(valid => {
if (valid) {
const params = generateParams();
emit('search', params);
}
});
};
//
async function onExport() {
try {
loadingExport.value = true;
const params = generateParams();
const resData = await exportHistory(params);
loadingExport.value = false;
resData && (window.location.href = resData);
} catch (error) {
loadingExport.value = false;
throw new Error(error);
}
}
</script>

82
src/components/statistical/stastistical-chart.vue

@ -1,82 +0,0 @@
<template>
<el-card class="box-card">
<el-empty v-if="noData" description="暂无数据"></el-empty>
<div id="realtimeContainer"></div>
</el-card>
</template>
<script setup>
import { computed, defineExpose, defineProps, onMounted, ref, watchEffect } from 'vue';
import { useStore } from 'vuex';
import * as echarts from 'echarts/core';
import { DataZoomComponent, GridComponent, LegendComponent, ToolboxComponent, TooltipComponent } from 'echarts/components';
import { LineChart } from 'echarts/charts';
import { UniversalTransition } from 'echarts/features';
import { CanvasRenderer } from 'echarts/renderers';
import { generateChartOption } from 'utils/statistical';
echarts.use([
ToolboxComponent,
TooltipComponent,
GridComponent,
LegendComponent,
LineChart,
CanvasRenderer,
UniversalTransition,
DataZoomComponent,
]);
const store = useStore();
const props = defineProps({ searchHeight: Number });
const realtimeData = computed(() => store.state.statistics.realtimeData);
const isMounted = ref(false);
const noData = ref(true);
let myChart = null;
//
onMounted(() => {
isMounted.value = true;
window.addEventListener('resize', () => {
if (!myChart) return;
myChart.resize();
});
});
watchEffect(() => {
if (!realtimeData.value) return;
const { data } = realtimeData.value;
if (!data || !data.length || !isMounted.value) return;
initChart(data);
});
// chart
function initChart(data) {
const chartDom = document.getElementById('realtimeContainer');
const canvasHeight = document.documentElement.clientHeight - props.searchHeight - 150;
chartDom.style.height = `${canvasHeight}px`;
myChart && myChart.dispose();
myChart = echarts.init(chartDom);
render(data);
myChart.on('legendselectchanged', event => {
render(data, event.selected);
});
}
function render(data, selected) {
// myChart && myChart.clear();
if (!data || !data.length) {
noData.value = true;
return;
}
noData.value = false;
if (!myChart) {
initChart(data);
}
const option = generateChartOption(data, selected);
option && myChart.setOption(option);
}
defineExpose({ render });
</script>

82
src/components/tall/left/Calendar.vue

@ -0,0 +1,82 @@
<template>
<a-calendar v-model="date" :fullscreen="false" @panelChange="onPanelChange" @select="onSelectDate">
<template #headerRender="{ value: current, onChange }">
<div style="padding: 10px">
<a-row type="flex" justify="space-between">
<a-col>
<a-select
size="small"
:dropdown-match-select-width="false"
class="my-year-select"
:value="String(current.year())"
@change="
newYear => {
onChange(current.year(newYear));
}
"
>
<a-select-option v-for="val in getYears(current)" :key="String(val)" class="year-item">
{{ val }}
</a-select-option>
</a-select>
</a-col>
<a-col>
<a-select
size="small"
:dropdown-match-select-width="false"
:value="String(current.month())"
@change="
selectedMonth => {
onChange(current.month(parseInt(selectedMonth, 10)));
}
"
>
<a-select-option v-for="(val, index) in getMonths(current)" :key="String(index)" class="month-item">
{{ val }}
</a-select-option>
</a-select>
</a-col>
</a-row>
</div>
</template>
</a-calendar>
</template>
<script setup>
import { ref } from 'vue';
const date = ref();
const onPanelChange = (value, mode) => {
console.log(value, mode);
};
const getMonths = value => {
const localeData = value.localeData();
const months = [];
for (let i = 0; i < 12; i++) {
months.push(localeData.monthsShort(value.month(i)));
}
return months;
};
const getYears = value => {
const year = value.year();
const years = [];
for (let i = year - 10; i < year + 10; i += 1) {
years.push(i);
}
return years;
};
function onSelectDate(event) {
console.log(event);
}
</script>
<script>
export default { name: 'LeftCanlendar' };
</script>

13
src/components/tall/left/Index.vue

@ -0,0 +1,13 @@
<template>
<Calendar />
<Projects />
</template>
<script setup>
import Calendar from './Calendar.vue';
import Projects from './Projects.vue';
</script>
<script>
export default { name: 'LeftIndex' };
</script>

5
src/components/tall/left/Projects.vue

@ -0,0 +1,5 @@
<template>项目列表</template>
<script setup></script>
<style scoped></style>

20
src/components/tall/top/Navbar.vue

@ -0,0 +1,20 @@
<template>
<menu-unfold-outlined v-if="collapsed" @click="toggleCollapse" />
<menu-fold-outlined v-else class="trigger" @click="toggleCollapse" />
<h1>课题数据库</h1>
</template>
<script setup>
import { computed } from 'vue';
import { useStore } from 'vuex';
// eslint-disable-next-line import/no-extraneous-dependencies
import { MenuUnfoldOutlined, MenuFoldOutlined } from '@ant-design/icons-vue';
const store = useStore();
const collapsed = computed(() => store.state.layout.display.left); //
// toggle left window display
function toggleCollapse() {
store.commit('layout/setDisplay', { prop: 'left', show: !collapsed.value });
}
</script>

157
src/config/chart.js

@ -1,157 +0,0 @@
/* eslint-disable max-len */
export const colors = ['#5470C6', '#91CC75', '#EE6666', '#5470C6', '#91CC75', '#EE6666', '#5470C6', '#91CC75', '#5470C6'];
export const legendData = [
'SO2(ppb)',
'盐分阻抗(Ω)',
'盐分温度(℃)',
'环境温度(℃)',
'环境湿度(RH%)',
'锌腐蚀电流(nA)',
'铜腐蚀电流(nA)',
'铝腐蚀电流(nA)',
'钢腐蚀电流(nA)',
];
// 默认legend select
export const defaultSelectedLegend = {
'钢腐蚀电流(nA)': true,
'铜腐蚀电流(nA)': true,
'铝腐蚀电流(nA)': true,
'锌腐蚀电流(nA)': true,
'SO2(ppb)': false,
'盐分阻抗(Ω)': false,
'盐分温度(℃)': false,
'环境温度(℃)': false,
'环境湿度(RH%)': false,
};
// y轴定义
export const yAxisData = [
{
type: 'value',
name: '腐蚀电流(nA)',
offset: 0,
position: 'left',
axisLine: {
show: true,
lineStyle: { color: colors[0] },
},
axisLabel: { formatter: '{value}' },
axisPointer: { show: false },
},
{
type: 'value',
name: '温度(℃)',
offset: 0,
position: 'right',
axisLine: {
show: true,
lineStyle: { color: colors[1] },
},
axisLabel: { formatter: '{value}' },
axisPointer: { show: false },
},
{
type: 'value',
name: '湿度(RH%)',
offset: 70,
position: 'right',
axisLine: {
show: true,
lineStyle: { color: colors[2] },
},
axisLabel: { formatter: '{value}' },
axisPointer: { show: false },
},
{
type: 'value',
name: 'SO2(ppb)',
position: 'right',
show: false,
offset: 150,
axisLine: {
show: true,
lineStyle: { color: colors[3] },
},
axisLabel: { formatter: '{value}' },
axisPointer: { show: false },
},
{
type: 'value',
name: '盐分阻抗(Ω)',
show: false,
position: 'right',
offset: 220,
axisLine: {
show: true,
lineStyle: { color: colors[4] },
},
axisLabel: { formatter: '{value}' },
axisPointer: { show: false },
},
];
/**
* 生成默认数据
* @param {Object} data
* @returns {[{data: (number|[]|string|*), name: string, type: string},{data: (number|[]|BufferSource|string|*), name: string, type: string, yAxisIndex: number},{data: ([]|string|*), name: string, type: string, yAxisIndex: number},{data: ([]|string|*), name: string, type: string, yAxisIndex: number},{data: [], name: string, type: string, yAxisIndex: number},null,null,null]}
*/
export function generateDefaultSeries(data) {
return [
{
name: '锌腐蚀电流(nA)',
type: 'line',
yAxisIndex: 0,
data: data.corrosionXIN,
},
{
name: '铜腐蚀电流(nA)',
type: 'line',
yAxisIndex: 0,
data: data.corrosionTONG,
},
{
name: '铝腐蚀电流(nA)',
type: 'line',
yAxisIndex: 0,
data: data.corrosionLV,
},
{
name: '钢腐蚀电流(nA)',
type: 'line',
yAxisIndex: 0,
data: data.corrosionGANG,
},
{
name: '环境温度(℃)',
type: 'line',
yAxisIndex: 1,
data: data.environmentTemperature,
},
{
name: '盐分温度(℃)',
type: 'line',
yAxisIndex: 1,
data: data.saltT,
},
{
name: '环境湿度(RH%)',
type: 'line',
yAxisIndex: 2,
data: data.environmentHumidity,
},
{
name: 'SO2(ppb)',
type: 'line',
yAxisIndex: 3,
data: data.so2,
},
{
name: '盐分阻抗(Ω)',
type: 'line',
yAxisIndex: 4,
data: data.saltR,
},
];
}

164
src/config/config.js

@ -1,164 +0,0 @@
// 网络参数设置
export const networkConfig = {
ip1: '',
port1: '',
ip2: '',
port2: '',
ip3: '',
port3: '',
ipBackup: '',
portBackup: '',
account: '',
password: '',
apn: '',
status: '',
};
// 功能参数设置
export const functionConfig = {
frequency: {
so2: 0, // SO2采样频率
metal: 0, // 金属腐蚀采样频率
th: 0, // 温湿度 采样频率
salt: 0, // 盐雾 采样频率
}, // 采样频率
count: 0, // 采集个数
time: Date.now(), // 设置时间
batteryLow: 0, // 电池电压低阈值
batteryHigh: 0, // 电池电压高阈值
sunHigh: 0, // 太阳能电压高阈值
humidityHigh: 0, // 湿度高阈值
temperatureLow: 0, // 温度低阈值
temperatureHigh: 0, // 温度高阈值
securityMode: 'OPEN', // 安全模式 OPEN->不加密 ENCRYPTION->加密
report: {
type: 'CYCLE', // 上报周期类型 0->时间点 1->周期
timePoints: [''], // 设置时间点
cycle: 240, // 上报周期分钟数
},
status: '',
};
// 金属腐蚀类型
export const corrosiveTypes = [
{
value: 'XIN',
type: '锌',
},
{
value: 'LV',
type: '铝',
},
{
value: 'TONG',
type: '铜',
},
{
value: 'GANG',
type: '钢',
},
];
// 添加设备
export const deviceData = {
deviceId: '', // 设备id
deviceFullId: '', // 设备完整id
deviceDirection: '', // 设备朝向
area: '', // 地区
address: '', // 站点名称
contact: '', // 联系人
phone: '', // 联系人电话
lon: '', // 经度
lat: '', // 纬度
head: '', // 负责人
installLocation: '', // 安装位置
installTime: '', // 安装时间
runTime: '', // 正式运行时间
linkAddress: '', // 链路地址
probNo: '', // 探头编号
simple: '', // 试样
sim1: '', // sim卡1
protocolVersion: '', // 协议版本
joint: '', // 主站后台联调情况
operationRecord: '', // 维修记录
remark: '', // 备注
type: 'IACD', // 产品类型
};
// 设备添加编辑 规则
export const deviceRules = {
deviceId: [
{
required: true,
message: '请输入设备ID号',
trigger: 'blur',
},
{
len: 6,
message: '请输入6位设备ID号',
trigger: 'blur',
},
],
address: [
{
required: true,
message: '请输入站点名称',
trigger: 'blur',
},
],
};
// 下发类型
export const PEND_TYPE = {
PENDING: {
type: 'primary',
text: '待下发',
},
FAIL: {
type: 'danger',
text: '配置失败',
},
SUCCESS: {
type: 'success',
text: '配置成功',
},
};
// 上报类型
export const REPORT_TYPE = {
CYCLE: '周期上报',
POINT: '定时上报',
};
// 下发类型
export const PENDING_TYPE = {
DATA: { text: '业务上报' },
EVENT: { text: '事件上报' },
};
// 实时查询数据的轮询时间 1分钟
export const REALTIME_DATA_INTERVAL = 60000 * 5;
// 设备状态 情景
export const STATUS_COLOR = {
FAULT: {
text: '故障',
color: '#FCA5A5',
},
WARNING: {
text: '报警',
color: '#FCD34D',
},
NORMAL: {
text: '正常',
color: '#BEF264',
},
OFFLINE: {
text: '离线',
color: '#CBD5E1',
},
ONLINE: {
text: '在线',
color: '#6EE7B7',
},
};

110
src/config/log.js

@ -1,110 +0,0 @@
// 功能码
export const CODE = {
BUSINESS_REPORT: {
text: '业务上报',
code: 0,
},
EVENT_REPORT: {
text: '事件上报',
code: 1,
},
NET_PARAM_SETTING_OR_READING: {
code: 2,
text: '联网参数设置或读取',
},
FUNC_PARAM_SETTING_OR_READING: {
code: 3,
text: '功能参数设置或读取',
},
READ_HISTORY_DATA: {
code: 4,
text: '读取历史日志记录',
},
READ_HISTORY_EVENT: {
code: 5,
text: '读取历史事件记录',
},
UPGRADE: {
code: 6,
text: '升级启动、传输、结束',
},
ACK: {
code: 7,
text: '应答帧(固定帧)',
},
OVER: {
code: 8,
text: '结束帧',
},
RESERVED: {
code: 9,
text: '保留',
},
};
// 传输方向
export const DIRECTION = {
IACD2SERVER: '上行',
SERVER2IACD: '下行',
};
// 传输启动
export const PRM = {
ACK: '传输结束',
REQ: '传输启动',
};
// 流控
export const DFC = {
CONTINUE: '有后续包',
OVER: '无后续包',
};
// 安全方式
export const SER = {
OPEN: '不加密',
ENCRYPTION: '加密',
};
// 类型标识
export const ASDU_TYPE = {
IACD: {
code: 1,
text: '大气腐蚀',
},
OTHER: {
code: 2,
text: '其他',
},
};
// 信息体序
export const INFO_ORDER = {
NoOrder: '无序',
HasOrder: '有序',
None: '无',
};
// 传输原因
export const REASON = {
None: {
code: 0,
text: '无',
},
ReportData: {
code: 1,
text: '业务主动上报',
},
ReportEvent: {
code: 2,
text: '事件主动上报',
},
History: {
code: 3,
text: '历史数据记录或程序数据',
},
HumanOperator: {
code: 4,
text: '人工上报',
},
};

19
src/hooks/useDeviceCreate.js

@ -1,19 +0,0 @@
import { ref } from 'vue';
export default function useDeviceCreate() {
const display = ref(false);
function hide() {
display.value = false;
}
function show() {
display.value = true;
}
return {
display,
hide,
show,
};
}

5
src/main.js

@ -1,7 +1,10 @@
// noinspection ES6UnusedImports
import 'virtual:windi.css'; import 'virtual:windi.css';
import 'element-plus/dist/index.css';
import { createApp } from 'vue'; import { createApp } from 'vue';
// eslint-disable-next-line no-unused-vars
import dayjs from 'utils/dayjs';
import App from './App.vue'; import App from './App.vue';
import router from './routers/index'; import router from './routers/index';
import store from './store/index'; import store from './store/index';

77
src/routers/index.js

@ -1,69 +1,14 @@
import { createRouter, createWebHistory } from 'vue-router'; import { createRouter, createWebHistory } from 'vue-router';
// 还有 createWebHashHistory 和 createMemoryHistory // 还有 createWebHashHistory 和 createMemoryHistory
export const user = [{ path: '/user/signIn', name: 'signIn', component: () => import('views/user/SignIn.vue') }];
export const routes = [ export const routes = [
{ {
path: '/corrosion/overview', path: '/home',
name: 'overview', name: 'home',
meta: { redirect: '/home/test',
title: '设备概览', component: () => import('views/home/Index.vue'),
icon: 'el-icon-data-board', children: [{ path: '/home/test', name: 'test', component: () => import('views/detail/Test.vue') }],
},
component: () => import('@/views/overview.vue'),
},
{
path: '/corrosion/devices',
name: 'devices',
meta: {
title: '设备管理',
icon: 'el-icon-box',
},
component: () => import('@/views/device-list.vue'),
},
{
path: '/corrosion/config',
name: 'config',
meta: {
title: '参数配置',
icon: 'el-icon-set-up',
},
component: () => import('@/views/config.vue'),
},
{
path: '/corrosion/data-realtime',
name: 'data-realtime',
meta: {
title: '实时数据',
icon: 'el-icon-odometer',
},
component: () => import('@/views/data-realtime.vue'),
},
{
path: '/corrosion/statistical',
name: 'statistical',
meta: {
title: '数据统计',
icon: 'el-icon-data-line',
},
component: () => import('@/views/statistical-report.vue'),
},
{
path: '/corrosion/history',
name: 'history',
meta: {
title: '历史数据',
icon: 'el-icon-document',
},
component: () => import('@/views/history.vue'),
},
{
path: '/corrosion/commands',
name: 'commands',
meta: {
title: '平台日志',
icon: 'el-icon-moon-night',
},
component: () => import('@/views/commands.vue'),
}, },
]; ];
@ -72,13 +17,11 @@ const router = createRouter({
routes: [ routes: [
{ {
path: '/', path: '/',
redirect: '/corrosion/overview', redirect: '/home',
},
{
path: '/corrosion',
redirect: '/corrosion/overview',
}, },
].concat(routes), ...user,
...routes,
],
}); });
export default router; export default router;

163
src/store/device.js

@ -1,163 +0,0 @@
import { getDevices, getDevicesAll, getDevicesCount } from 'apis';
import dayjs from 'dayjs';
import { ElMessage } from 'element-plus';
const user = {
namespaced: true,
state: {
devices: [], // 站点列表 设备列表简版
devicesAll: { page: {}, data: [] }, // 设备列表完整版
currentDeviceId: '', // 当前正在编辑的设备deviceId
count: {
total: 0, // 总设备数量
online: 0, // 在线
offline: 0, // 离线
warning: 0, // 警告
fault: 0, // 故障
},
},
getters: {
// 当前正在编辑的设备的完整信息
current({ devicesAll, currentDeviceId }) {
try {
return devicesAll.data.find(device => device.deviceId === currentDeviceId);
} catch (error) {
return null;
}
},
},
mutations: {
/**
* 设置devices数据
* @param {*} state
* @param {array} devices
*/
setDevices(state, devices) {
if (devices && devices.length) {
state.devices = devices;
user.mutations.setCurrentDeviceId(state, devices[0].deviceId);
} else {
state.devices = [];
}
},
/**
* 设置设备统计数据信息
* @param {object} state
* @param {object} count // 服务端返回的对象
* @param {number} count.total // 总设备数量
* @param {number} count.online // 在线
* @param {number} count.offline // 离线
* @param {number} count.warning // 报警
* @param {number} count.fault // 故障
*/
setDevicesCount(state, count) {
state.count = count;
},
/**
* 设置devicesAll的数据
* @param {*} state
* @param {object} devices {page, data}
*/
setDevicesAll(state, devices) {
if (devices && devices.data) {
for (let i = 0; i < devices.data.length; i++) {
const device = devices.data[i];
if (device.installTime) {
devices.data[i].installTime = dayjs(new Date(+device.installTime)).format('YYYY-MM-DD HH:mm:ss');
}
if (device.runTime) {
devices.data[i].runTime = dayjs(new Date(+device.runTime)).format('YYYY-MM-DD HH:mm:ss');
}
}
}
state.devicesAll = devices;
},
/**
* 设置正则编辑的设备deviceId
* @param {*} state
* @param {string} deviceId
*/
setCurrentDeviceId(state, deviceId) {
state.currentDeviceId = deviceId;
},
/**
* 更新某个设备的信息
* @param {*} param0
* @param {object} newData 设备更新后的信息
*/
updateDevice({ devicesAll }, newData) {
for (let i = 0; i < devicesAll.data.length; i++) {
const item = devicesAll.data[i];
if (item && item.deviceId === newData.deviceId) {
newData.installTime = dayjs(new Date(+item.installTime)).format('YYYY-MM-DD HH:mm:ss');
newData.runTime = dayjs(new Date(+item.runTime)).format('YYYY-MM-DD HH:mm:ss');
devicesAll.data[i] = newData;
break;
}
}
},
/**
* 删除设备
* @param {*} param0
* @param {string} deviceId 设备id
*/
deleteDevice({ devicesAll }, deviceId) {
if (!devicesAll || !devicesAll.data || !devicesAll.data.length) return;
devicesAll.data = devicesAll.data.filter(item => item.deviceId !== deviceId);
},
/**
* 新增设备后将设备数据追加到最前边
* @param {array} devicesAll
* @param {object} device 后台返回的新增设备信息
*/
unshiftDevice({ devicesAll }, device) {
devicesAll.data.unshift(device);
},
},
actions: {
// 获取设备列表(站点列表)
async getDevices({ commit }) {
try {
const data = await getDevices();
commit('setDevices', data || []);
return data;
} catch (error) {
throw new Error(error);
}
},
// 查询设备数量信息
async getDevicesCount({ commit }) {
try {
const data = await getDevicesCount();
commit('setDevicesCount', data);
} catch (error) {
ElMessage.error(error.message || '获取设备统计信息失败');
throw new Error(error);
}
},
// 获取设备列表(站点列表) 完整信息
async getDevicesAll({ commit }, params) {
try {
const data = await getDevicesAll(params);
commit('setDevicesAll', data || null);
return data;
} catch (error) {
throw new Error(error);
}
},
},
};
export default user;

17
src/store/index.js

@ -1,16 +1,5 @@
import { createStore } from 'vuex'; import { createStore } from 'vuex';
import device from './device'; import user from './tall/user';
import statistics from './statistics'; import layout from './tall/layout';
import user from './user';
export default createStore({ export default createStore({ modules: { user, layout } });
modules: { user, device, statistics },
state: { menu: { show: true, collapse: false } },
getters: {},
mutations: {
toggleCollapse(state) {
state.menu.collapse = !state.menu.collapse;
},
},
actions: {},
});

78
src/store/statistics.js

@ -1,78 +0,0 @@
import { getDatas, getMonthsDate } from 'apis/index';
export default {
namespaced: true,
state: {
electricData: null,
corrosionData: null,
moistTimeData: null,
realtimeData: null,
},
getters: {},
mutations: {
/**
* 设置积分电量数据
* @param {*} state
* @param {array} data
*/
setElectricData(state, data) {
state.electricData = data;
},
/**
* 设置月累计腐蚀的数据
* @param {*} state
* @param {*} data
*/
setCorrosionData(state, data) {
state.corrosionData = data;
},
/**
* 设置月累计湿润时间图的数据
* @param {*} state
* @param {*} data
*/
setMoistTimeData(state, data) {
state.moistTimeData = data;
},
/**
* 设置实时数据统计的数据
* @param {*} state
* @param {*} data
*/
setRealtimeData(state, data) {
state.realtimeData = data;
},
},
actions: {
// 获取积分电量数据
async getMonthsDate({ commit }, params) {
try {
const data = await getMonthsDate(params);
commit('setElectricData', data.powers || null);
commit('setCorrosionData', data.corrosions || null);
commit('setMoistTimeData', data.humids || null);
return data;
} catch (error) {
throw new Error(error);
}
},
// 获取实时数据统计数据
async getRealtimeData({ commit }, params) {
try {
const data = await getDatas(params);
commit('setRealtimeData', data || null);
return data;
} catch (error) {
throw new Error(error);
}
},
},
};

0
src/store/tall/layout/actions.js

0
src/store/tall/layout/getters.js

8
src/store/tall/layout/index.js

@ -0,0 +1,8 @@
import state from './state';
import mutations from './mutations';
export default {
namespaced: true,
state,
mutations,
};

13
src/store/tall/layout/mutations.js

@ -0,0 +1,13 @@
const mutations = {
/**
* 设置显隐
* @param { Object} state
* @param {String} prop 要设置的字段/属性名
* @param {Boolean} show true->显示,false->隐藏
*/
setDisplay(state, { prop, show }) {
state.display[prop] = show;
},
};
export default mutations;

8
src/store/tall/layout/state.js

@ -0,0 +1,8 @@
/* eslint-disable object-curly-newline */
const state = {
display: {
left: true, // 是否显示左栏
},
};
export default state;

2
src/store/user.js → src/store/tall/user/index.js

@ -1,4 +1,4 @@
import { getToken } from 'apis/index'; import { getToken } from 'apis';
export default { export default {
namespaced: true, namespaced: true,

8
src/utils/axios.js

@ -1,5 +1,5 @@
import Axios from 'axios'; import Axios from 'axios';
import { ElMessage } from 'element-plus'; import { message } from 'ant-design-vue';
import store from 'store'; import store from 'store';
const baseUrl = '/gateway'; const baseUrl = '/gateway';
@ -37,12 +37,10 @@ instance.interceptors.response.use(
}, },
error => { error => {
if (error.response && error.response.data) { if (error.response && error.response.data) {
const code = error.response.status;
const msg = error.response.data.message; const msg = error.response.data.message;
ElMessage.error(`Code: ${code}, Message: ${msg}`); message.error(msg);
console.error(`[Axios Error]`, error.response);
} else { } else {
// ElMessage.error(`${error}`); message.error(error.message);
} }
return Promise.reject(error); return Promise.reject(error);
}, },

6
src/utils/dayjs.js

@ -0,0 +1,6 @@
import dayjs from 'dayjs';
import 'dayjs/locale/zh-cn';
dayjs.locale('zh-cn');
export default dayjs;

128
src/utils/overview.js

@ -1,128 +0,0 @@
import { STATUS_COLOR } from '@/config/config';
/**
* 生成设备概览 数量数据
* @param {object} count
* @param {number} count.online 在线数量
* @param {number} count.offline 离线数量
* @param {number} count.fault 故障数量
* @param {number} count.warning 报警数量
* @param {number} count.total 总设备数
* @returns {object} {}
*/
// eslint-disable-next-line import/prefer-default-export
export function generateChartOption(count) {
return {
tooltip: { trigger: 'item' },
title: {
text: '设备总数',
top: '45%',
left: '50%',
textAlign: 'center',
textVerticalAlign: 'middle',
textStyle: { fontSize: 22 },
subtext: count.total,
subtextStyle: { fontSize: 18 },
},
series: [
{
name: '设备统计',
type: 'pie',
radius: ['70%', '95%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2,
},
label: {
show: true,
position: 'inside',
fontSize: 17,
color: '#323232',
// formatter: '{b}\n{c}',
lineHeight: 40,
},
emphasis: {
label: {
show: true,
fontSize: 19,
fontWeight: 'bold',
},
},
labelLine: { show: false },
data: [
{
value: count.online,
name: STATUS_COLOR.ONLINE.text,
itemStyle: { color: STATUS_COLOR.ONLINE.color },
label: { show: count.online },
},
{
value: count.offline,
name: STATUS_COLOR.OFFLINE.text,
itemStyle: { color: STATUS_COLOR.OFFLINE.color },
label: { show: count.offline },
},
],
},
{
name: '设备统计',
type: 'pie',
radius: ['35%', '60%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2,
},
label: {
show: true,
position: 'inside',
fontSize: 14,
color: '#323232',
// formatter: '{b}\n{c}',
lineHeight: 40,
},
emphasis: {
label: {
show: true,
fontSize: 16,
fontWeight: 'bold',
},
},
labelLine: { show: false },
data: [
{
value: count.fault,
name: STATUS_COLOR.FAULT.text,
itemStyle: { color: STATUS_COLOR.FAULT.color },
label: { show: count.fault },
},
{
value: count.warning,
name: STATUS_COLOR.WARNING.text,
itemStyle: { color: STATUS_COLOR.WARNING.color },
label: { show: count.warning },
},
{
value: count.normal,
name: STATUS_COLOR.NORMAL.text,
itemStyle: { color: STATUS_COLOR.NORMAL.color },
label: { show: count.normal },
},
{
value: count.offline,
name: STATUS_COLOR.OFFLINE.text,
itemStyle: {
color: STATUS_COLOR.OFFLINE.color,
opacity: 0,
},
label: { show: count.offline },
},
],
},
],
};
}

176
src/utils/statistical.js

@ -1,176 +0,0 @@
/* eslint-disable max-len,object-curly-newline */
import dayjs from 'dayjs';
import max from 'lodash/max';
import { colors, defaultSelectedLegend, generateDefaultSeries, legendData, yAxisData } from '@/config/chart';
/**
* 生成chart所需参数
* @param {Object[]} data 服务端返回数据
* @param {string} data[].time 时间 ms
* @param {string} data[].so2 SO2
* @param {string} data[].saltR 盐阻
* @param {string} data[].saltT 盐温
* @param {string} data[].environmentTemperature 环温
* @param {string} data[].environmentHumidity 环湿
* @param {string} data[].corrosion1
* @param {string} data[].corrosion2
* @param {string} data[].corrosion3
* @param {string} data[].corrosion4
* @returns {{environmentTemperature: *[], corrosionXIN: *[], corrosionGANG: *[], corrosionTONG: *[], so2: *[], corrosionLV: *[], time: *[], saltT: *[], saltR: *[], environmentHumidity: *[]}}
*/
function generateParams(data) {
const result = {
time: [],
so2: [],
saltR: [],
saltT: [],
environmentTemperature: [],
environmentHumidity: [],
corrosionXIN: [],
corrosionTONG: [],
corrosionLV: [],
corrosionGANG: [],
};
data.forEach(item => {
result.time.push(dayjs(new Date(+item.time)).format('YY/MM/DD HH:mm'));
result.so2.push(+item.so2);
result.saltR.push(+item.saltR);
result.saltT.push(+item.saltT);
result.environmentTemperature.push(+item.environmentTemperature);
result.environmentHumidity.push(+item.environmentHumidity);
result.corrosionXIN.push(+item.corrosion1);
result.corrosionTONG.push(+item.corrosion2);
result.corrosionLV.push(+item.corrosion3);
result.corrosionGANG.push(+item.corrosion4);
});
return result;
}
/**
* 计算Y轴的显示
* @param {string} yName Y轴的name
* @param {Object} selectedLegend legends
* @returns {boolean}
*/
export function computeYAxisShow(yName, selectedLegend) {
// eslint-disable-next-line guard-for-in,no-restricted-syntax
for (const key in selectedLegend) {
if (key.includes(yName) && selectedLegend[key]) {
return true;
}
}
return false;
}
/**
* 生产y轴内容
* @param {Object} selectedLegend
* @returns {({axisLabel: {formatter: string}, axisLine: {lineStyle: {color: string}, show: boolean}, name: string, position: string, type: string}|{axisLabel: {formatter: string}, offset: number, axisLine: {lineStyle: {color: string}, show: boolean}, name: string, position: string, type: string}|{axisLabel: {formatter: string}, offset: number, axisLine: {lineStyle: {color: string}, show: boolean}, name: string, position: string, type: string}|{axisLabel: {formatter: string}, offset: number, axisLine: {lineStyle: {color: string}, show: boolean}, name: string, position: string, type: string}|{axisLabel: {formatter: string}, axisLine: {lineStyle: {color: string}, show: boolean}, name: string, position: string, type: string})[]}
*/
export function generateYAxis(selectedLegend) {
let leftIndex = 0;
let rightIndex = 0;
yAxisData.forEach(item => {
item.show = computeYAxisShow(item.name, selectedLegend);
if (item.show) {
if (item.position === 'left') {
item.offset = 100 * leftIndex;
leftIndex += 1;
} else {
item.offset = 80 * rightIndex;
rightIndex += 1;
}
}
});
return yAxisData;
}
/**
* 生成series数据
* @param {Object} data
* @param {Object[]} yAxis
* @returns {({data: ([]|number|string|*), name: string, type: string}|{data: ([]|number|BufferSource|string|*), name: string, type: string, yAxisIndex: number}|{data: ([]|string|*), name: string, type: string, yAxisIndex: number}|{data: ([]|string|*), name: string, type: string, yAxisIndex: number}|{data: [], name: string, type: string, yAxisIndex: number})[]|*[]}
*/
function generateSeries(data, yAxis) {
const seriesArr = generateDefaultSeries(data);
const showArr = seriesArr.filter(item => yAxis.find(y => y.name === item.name));
const hideArr = seriesArr.filter(item => !yAxis.find(y => y.name === item.name));
const result = [...showArr, ...hideArr];
result.forEach(item => {
if (item.name.includes('电流')) {
item.yAxisIndex = 0;
} else if (item.name.includes('温度')) {
item.yAxisIndex = 1;
} else if (item.name.includes('湿度')) {
item.yAxisIndex = 2;
} else if (item.name.includes('SO2')) {
item.yAxisIndex = 3;
} else if (item.name.includes('阻抗')) {
item.yAxisIndex = 4;
}
});
return result || [];
}
/**
* 计算图表grid left right值
* @param {Object[]} yAxis
* @returns {{left: number, right: number}}
*/
function generateGrid(yAxis) {
const left = [];
const right = [];
yAxis.forEach(item => {
if (item.show) {
if (item.position === 'left') {
left.push(item.offset || 0);
} else {
right.push(item.offset || 0);
}
}
});
return {
left: max(left) + 100,
right: max(right) + 80,
};
}
/**
* 生成chart参数
* @param {Object[]} rawData 返回段返回的data数据
* @param {Object[]} yAxis
* @returns {{yAxis: [{axisLabel: {formatter: string}, axisLine: {lineStyle: {color: string}, show: boolean}, name: string, position: string, type: string},{axisLabel: {formatter: string}, offset: number, axisLine: {lineStyle: {color: string}, show: boolean}, name: string, position: string, type: string},{axisLabel: {formatter: string}, axisLine: {lineStyle: {color: string}, show: boolean}, name: string, position: string, type: string},{axisLabel: {formatter: string}, axisLine: {lineStyle: {color: string}, show: boolean}, name: string, position: string, type: string},{axisLabel: {formatter: string}, axisLine: {lineStyle: {color: string}, show: boolean}, name: string, position: string, type: string},null,null,null], xAxis: [{data: *[], axisTick: {alignWithLabel: boolean}, type: string}], color: string[], grid: {right: string}, legend: {data: string[]}, series: [{data: *[], name: string, type: string},{data: *[], name: string, type: string, yAxisIndex: number},{data: *[], name: string, type: string, yAxisIndex: number},{data: *[], name: string, type: string, yAxisIndex: number},{data: *[], name: string, type: string, yAxisIndex: number},null,null,null], tooltip: {axisPointer: {type: string}, trigger: string}, toolbox: {feature: {saveAsImage: {show: boolean}, restore: {show: boolean}, dataView: {show: boolean, readOnly: boolean}}}}}
*/
// eslint-disable-next-line import/prefer-default-export
export function generateChartOption(rawData, selected = defaultSelectedLegend) {
const data = generateParams(rawData);
const yAxis = generateYAxis(selected);
const series = generateSeries(data, yAxis);
const grid = generateGrid(yAxis);
const option = {
color: colors,
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
snap: true,
},
},
grid,
legend: {
selected,
data: legendData,
},
dataZoom: [{ type: 'inside' }, { type: 'slider' }],
xAxis: [
{
type: 'category',
axisTick: { alignWithLabel: true },
data: data.time,
},
],
yAxis,
series,
};
return option;
}

16
src/utils/time.js

@ -1,16 +0,0 @@
import dayjs from 'dayjs';
/**
* 格式化时间
* @param {string | number} time 时间戳字符串或数字
* @param {string} formatStyle
* @returns
*/
export function formatMsTime(time, formatStyle = 'YYYY-MM-DD HH:mm:ss') {
return dayjs(new Date(+time)).format(formatStyle);
}
// 图表时间轴的时间格式化
export function formatChartTime(time) {
return formatMsTime(time, 'MM/DD HH:mm');
}

182
src/views/commands.vue

@ -1,182 +0,0 @@
<template>
<SearchCommands @search="onSearch" />
<template v-if="data">
<el-table :data="data" :max-height="contentHeight" border stripe style="width: 100%">
<el-table-column align="center" fixed label="设备ID" min-width="100" prop="deviceId" />
<el-table-column align="center" label="时间" min-width="200">
<template #default="scope">
{{ dayjs(new Date(+scope.row.createdAt)).format('YYYY-MM-DD HH:mm:ss') }}
</template>
</el-table-column>
<el-table-column align="center" label="传输方向" min-width="80">
<template #default="scope">
{{ log.DIRECTION[scope.row.dir] }}
</template>
</el-table-column>
<el-table-column align="center" label="传输启动" min-width="80">
<template #default="scope">
{{ log.PRM[scope.row.prm] }}
</template>
</el-table-column>
<el-table-column align="center" label="安全模式" min-width="80">
<template #default="scope">
{{ log.SER[scope.row.ser] }}
</template>
</el-table-column>
<el-table-column align="center" label="流控" min-width="80">
<template #default="scope">
{{ log.DFC[scope.row.dfc] }}
</template>
</el-table-column>
<el-table-column align="center" label="功能码" min-width="180">
<template #default="scope">
{{ log.CODE[scope.row.code].text }}
</template>
</el-table-column>
<el-table-column align="center" label="包序号" min-width="80" prop="serialNo" />
<el-table-column align="center" label="类型标识" min-width="80">
<template #default="scope">
<span v-if="scope.row.asduType && log.ASDU_TYPE[scope.row.asduType]">
{{ log.ASDU_TYPE[scope.row.asduType].text }}
</span>
</template>
</el-table-column>
<el-table-column align="center" label="信息体顺序" min-width="100">
<template #default="scope">
{{ log.INFO_ORDER[scope.row.infoOrder] }}
</template>
</el-table-column>
<el-table-column align="center" label="信息体个数" min-width="100" prop="infoNum" />
<el-table-column align="center" label="传输原因" min-width="180">
<template #default="scope">
{{ log.REASON[scope.row.reason].text }}
</template>
</el-table-column>
<el-table-column align="center" label="协议内容" min-width="80">
<template #default="scope">
<el-button type="text" @click="openContent(scope.row.content)">查看</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
:current-page="page.page"
:default-page-size="50"
:page-count="page.count"
:page-size="page.size"
:page-sizes="[1, 10, 20, 50, 100]"
:total="page.total"
background
class="my-3 float-right"
layout="total, sizes, prev, pager, next, jumper"
@size-change="onSizeChange"
@current-change="onCurrentPageChange"
></el-pagination>
</template>
<!-- 弹出框-->
<el-dialog v-model="showContent" title="协议内容">
<code class="font-mono">{{ contents }}</code>
</el-dialog>
</template>
<script setup>
import { getLog } from 'apis';
import SearchCommands from 'components/commands/search-commands.vue';
import dayjs from 'dayjs';
import { ElMessage } from 'element-plus';
import { computed, onMounted, ref } from 'vue';
import { useStore } from 'vuex';
import * as log from '@/config/log';
const data = ref([]);
const page = ref({
page: 1,
size: 50,
count: 0,
total: 0,
});
const showContent = ref(false); //
const contents = ref(''); //
const contentHeight = ref(700);
const store = useStore();
const currentDeviceId = computed(() => store.state.device.currentDeviceId);
//
onMounted(() => {
const winHeight = document.documentElement.clientHeight;
contentHeight.value = winHeight - 250;
onSearch();
});
/**
* 查询
* @param {object} options
* @returns {Promise<void>}
*/
async function onSearch(
options = {
...page.value,
sort: [
{
col: 'createdAt',
order: 'DESC',
},
],
deviceId: currentDeviceId.value,
},
) {
try {
const resData = await getLog(options);
page.value = resData.page;
data.value = resData.data;
} catch (error) {
ElMessage.error('查询失败');
throw new Error(error);
}
}
/**
* 打开协议内容的弹出框 并记录值
* @param {string} content 协议内容
*/
function openContent(content) {
showContent.value = true;
contents.value = content;
}
//
const onCurrentPageChange = pageEvent => {
const options = {
page: pageEvent,
size: page.value.size || 50,
sort: [
{
col: 'createdAt',
order: 'DESC',
},
],
deviceId: currentDeviceId.value,
};
onSearch(options);
};
//
const onSizeChange = size => {
const options = {
page: 1,
size,
sort: [
{
col: 'createdAt',
order: 'DESC',
},
],
deviceId: currentDeviceId.value,
};
onSearch(options);
};
</script>

32
src/views/config.vue

@ -1,32 +0,0 @@
<template>
<!-- 设置站点选择 设备下发状态 -->
<DeviceSelectAndStatus :status="status" />
<el-tabs v-model="activeName">
<el-tab-pane :lazy="true" label="远动参数" name="function">
<FunctionConfigPending v-if="activeName === 'function'" :active-name="activeName" />
</el-tab-pane>
<el-tab-pane :lazy="true" label="网络参数" name="network">
<NetworkConfigPending v-if="activeName === 'network'" :active-name="activeName" />
</el-tab-pane>
<el-tab-pane :lazy="true" label="远动参数配置记录" name="function-log">
<ConfigLogFunction v-if="activeName === 'function-log'" :active-name="activeName" />
</el-tab-pane>
<el-tab-pane :lazy="true" label="网络参数配置记录" name="network-log">
<ConfigLogNetwork v-if="activeName === 'network-log'" :active-name="activeName" />
</el-tab-pane>
</el-tabs>
</template>
<script setup>
import { ref } from 'vue';
import DeviceSelectAndStatus from 'components/config/device-select-and-status.vue';
import FunctionConfigPending from 'components/config/function-config-pending.vue';
import NetworkConfigPending from 'components/config/network-config-pending.vue';
import ConfigLogFunction from 'components/config/config-log-function.vue';
import ConfigLogNetwork from 'components/config/config-log-network.vue';
const status = ref('');
const activeName = ref('function');
</script>

185
src/views/data-realtime.vue

@ -1,185 +0,0 @@
<template>
<SearchBar :show-refresh="true" @refresh="onRefresh" @search="getData" />
<template v-if="data">
<el-table :data="data" :max-height="contentHeight" border stripe style="width: 100%">
<el-table-column align="center" fixed label="设备ID" min-width="80" prop="deviceId" />
<el-table-column align="center" label="采集时间" min-width="170">
<template #default="scope">
{{ formatTime(+scope.row.time) }}
</template>
</el-table-column>
<el-table-column align="center" label="后台接收时间" min-width="170">
<template #default="scope">
{{ formatTime(+scope.row.createdAt) }}
</template>
</el-table-column>
<el-table-column align="center" label="锌腐蚀电流(nA)" min-width="130" prop="corrosion1" />
<el-table-column align="center" label="铜腐蚀电流(nA)" min-width="130" prop="corrosion2" />
<el-table-column align="center" label="铝腐蚀电流(nA)" min-width="130" prop="corrosion3" />
<el-table-column align="center" label="钢腐蚀电流(nA)" min-width="130" prop="corrosion4" />
<el-table-column align="center" label="环境温度(℃)" min-width="110" prop="environmentTemperature" />
<el-table-column align="center" label="环境湿度(RH%)" min-width="130" prop="environmentHumidity" />
<el-table-column align="center" label="SO2(ppb)" min-width="90" prop="so2" />
<!-- TODO:-->
<el-table-column align="center" label="盐分温度(℃)" min-width="110" prop="saltT" />
<el-table-column align="center" label="盐分阻抗(Ω)" min-width="110" prop="saltR" />
<el-table-column align="center" label="机箱温度(℃)" min-width="110" prop="deviceTemperature" />
<el-table-column align="center" label="机箱湿度(RH%)" min-width="130" prop="deviceHumidity" />
<el-table-column align="center" label="太阳能板电压(V)" min-width="140" prop="solarVoltage" />
<el-table-column align="center" label="蓄电池电压(V)" min-width="120" prop="batteryVoltage" />
<el-table-column align="center" label="电压百分比" min-width="94" prop="batteryVoltagePercentage" />
<el-table-column align="center" label="剩余电量(mAH)" min-width="140" prop="batteryVoltageRemain" />
<el-table-column align="center" label="消耗电量(mAH)" min-width="140" prop="batteryLoss" />
<el-table-column align="center" label="ICCID" min-width="190" prop="iccid" />
<el-table-column align="center" label="IMEI" min-width="150" prop="imei" />
<el-table-column align="center" label="信号强度" min-width="80" prop="signal" />
<el-table-column align="center" label="基站编号" min-width="130" prop="stationNo" />
<el-table-column align="center" label="硬件版本" min-width="80" prop="hardwareVersion" />
<el-table-column align="center" label="软件版本" min-width="80" prop="softwareVersion" />
</el-table>
<el-pagination
:current-page="page.page"
:default-page-size="50"
:page-count="page.count"
:page-size="page.size"
:page-sizes="[1, 10, 20, 50, 100]"
:total="page.total"
background
class="my-3 float-right"
layout="total, sizes, prev, pager, next, jumper"
@current-change="onCurrentPageChange"
@size-change="onSizeChange"
@prev-click="onPrev"
@next-click="onNext"
></el-pagination>
</template>
</template>
<script setup>
import { computed, onMounted, onUnmounted, ref } from 'vue';
import { useStore } from 'vuex';
import SearchBar from 'components/realtime/search-bar.vue';
import { getDatas } from 'apis';
import { ElMessage } from 'element-plus';
import dayjs from 'dayjs';
import { REALTIME_DATA_INTERVAL } from '@/config/config';
const page = ref({
page: 1,
size: 50,
});
let timer = null;
let apiTimer = null;
const store = useStore();
const token = computed(() => store.getters['user/token']);
const currentDeviceId = computed(() => store.state.device.currentDeviceId); // id
const contentHeight = ref(600);
const loadingSearch = ref(false);
const data = ref(null);
//
const getData = async () => {
try {
if (token && token.value) {
if (!currentDeviceId.value) {
return ElMessage.error('请选择站点');
}
const date = [dayjs().startOf('day').format('x'), dayjs().endOf('day').format('x')];
const params = {
deviceId: currentDeviceId.value,
dateRange: date,
paging: true,
page: page.value.page,
size: page.value.size,
sort: [
{
col: 'createdAt',
order: 'DESC',
},
],
};
loadingSearch.value = true;
const resData = await getDatas(params);
loadingSearch.value = false;
data.value = resData.data;
page.value = resData.page;
timer && clearTimeout(timer);
timer = null;
//
if (!apiTimer) {
apiTimer = setInterval(() => {
getData();
}, REALTIME_DATA_INTERVAL);
}
} else {
timer = setTimeout(() => {
getData();
}, 20);
}
} catch (error) {
ElMessage.error(error.message || '获取数据失败');
console.log('error: ', error);
}
};
//
onMounted(() => {
const winHeight = document.documentElement.clientHeight;
contentHeight.value = winHeight - 170;
currentDeviceId.value && getData();
});
onUnmounted(() => {
apiTimer && clearInterval(apiTimer);
apiTimer = null;
});
/**
* 当前 码变化
* 更新page 重新 取数据
* @param {number} e 的页码
*/
const onCurrentPageChange = e => {
page.value.page = e;
getData();
};
const onSizeChange = e => {
page.value.size = e;
getData();
};
//
const onNext = e => {
page.value.page = e + 1;
getData();
};
//
const onPrev = e => {
page.value.page = e - 1;
getData();
};
/**
* 格式化时间
* @param {number} time 时间戳
* @returns {string}
*/
function formatTime(time) {
return dayjs(new Date(time)).format('YYYY-MM-DD HH:mm:ss');
}
//
function onRefresh() {
apiTimer && clearInterval(apiTimer);
apiTimer = null;
getData();
}
</script>

3
src/views/detail/Test.vue

@ -0,0 +1,3 @@
<template>测试</template>
<script setup></script>

192
src/views/device-edit.vue

@ -1,192 +0,0 @@
<template>
<el-form ref="deviceEdit" :model="data" :rules="deviceRules" label-position="top">
<el-row :gutter="20">
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="设备ID" prop="deviceId">
<el-input v-model="data.deviceId" disabled></el-input>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="站点名称" prop="address">
<el-input v-model="data.address"></el-input>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="产品类型" prop="type">
<!-- <el-input v-model="data.securityMode"></el-input> -->
<el-radio v-model="data.type" label="IACD">大气腐蚀</el-radio>
<el-radio v-model="data.type" label="OTHER">其他</el-radio>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="设备完整ID" prop="deviceFullId">
<el-input v-model="data.deviceFullId"></el-input>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="地区" prop="area">
<el-input v-model="data.area"></el-input>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="联系人" prop="contact">
<el-input v-model="data.contact"></el-input>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="电话" prop="phone">
<el-input v-model="data.phone"></el-input>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="经度" prop="lon">
<el-input v-model="data.lon"></el-input>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="纬度" prop="lat">
<el-input v-model="data.lat"></el-input>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="负责人" prop="head">
<el-input v-model="data.head"></el-input>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="安装位置" prop="installLocation">
<el-input v-model="data.installLocation"></el-input>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="安装时间" prop="installTime">
<el-date-picker v-model="data.installTime" placeholder="安装时间" style="width: 100%" type="datetime"></el-date-picker>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="正式运行时间" prop="runTime">
<el-date-picker v-model="data.runTime" placeholder="正式运行时间" style="width: 100%" type="datetime"></el-date-picker>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="链路地址" prop="linkAddress">
<el-input v-model="data.linkAddress"></el-input>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="探头编号" prop="probNo">
<el-input v-model="data.probNo"></el-input>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="试样" prop="simple">
<el-input v-model="data.simple"></el-input>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="sim卡1" prop="sim1">
<el-input v-model="data.sim1"></el-input>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="设备朝向" prop="deviceDirection">
<el-input v-model="data.deviceDirection"></el-input>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="协议版本" prop="protocolVersion">
<el-input v-model="data.protocolVersion"></el-input>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="与主站后台联调情况" prop="joint">
<el-input v-model="data.joint" type="textarea"></el-input>
</el-form-item>
</el-col>
<el-col :lg="8" :md="12" :span="12" :xl="6" :xs="24">
<el-form-item label="备注" prop="remark">
<el-input v-model="data.remark" type="textarea"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="维修记录" prop="operationRecord">
<el-input v-model="data.operationRecord" type="textarea"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="mt-5 pl-2">
<el-form-item>
<el-button type="primary" @click="onSubmit">提交</el-button>
<el-button @click="$emit('cancel')">取消</el-button>
</el-form-item>
</el-row>
</el-form>
</template>
<script setup>
import { computed, defineEmits, reactive, ref, watch } from 'vue';
import { useStore } from 'vuex';
import { updateDevice } from 'apis';
import { ElMessage } from 'element-plus';
import { deviceData, deviceRules } from '@/config/config';
let data = reactive(deviceData);
const deviceEdit = ref(null); // form
const store = useStore();
const device = computed(() => store.getters['device/current']);
const currentDeviceId = computed(() => store.state.device.currentDeviceId);
const emit = defineEmits(['cancel']);
watch(
() => device.value,
newValue => {
data = newValue;
},
{
immediate: true,
deep: true,
},
);
//
const onSubmit = () => {
deviceEdit.value.validate(async valid => {
if (valid) {
if (data.installTime) {
data.installTime = new Date(data.installTime).getTime();
}
if (data.runTime) {
data.runTime = new Date(data.runTime).getTime();
}
try {
await updateDevice(currentDeviceId.value, data);
ElMessage.success('更新成功');
emit('cancel');
store.commit('device/updateDevice', data);
await store.dispatch('device/getDevices'); //
} catch (error) {
ElMessage.error('更新失败, 请稍后重试');
throw new Error(error);
}
}
});
};
</script>

216
src/views/device-list.vue

@ -1,216 +0,0 @@
<template>
<SearchBar @search="onSearch" />
<div class="my-3">
<el-button type="primary" @click="deviceCreate.show()">
<i class="el-icon-plus mr-2"></i>
添加设备
</el-button>
</div>
<template v-if="devicesAll && devicesAll.data">
<el-table :data="devicesAll.data" :max-height="contentHeight" border stripe style="width: 100%">
<el-table-column type="expand">
<template #default="props">
<el-row :gutter="20" class="px-6 text-gray-400">
<el-col :span="9">链路地址{{ props.row.linkAddress }}</el-col>
<el-col :span="9">探头编号{{ props.row.probNo }}</el-col>
<el-col :span="9">设备朝向{{ props.row.deviceDirection }}</el-col>
<el-col :span="9">试样{{ props.row.simple }}</el-col>
<el-col :span="9">安装位置{{ props.row.installLocation }}</el-col>
<el-col :span="9">sim1{{ props.row.sim1 }}</el-col>
<el-col :span="9">协议版本{{ props.row.protocolVersion }}</el-col>
<el-col :span="9">
产品类型
<span v-if="ASDU_TYPE[props.row.type]">{{ ASDU_TYPE[props.row.type].text }}</span>
</el-col>
<el-col :span="9">与主站后台联调情况{{ props.row.joint }}</el-col>
<el-col :span="9">备注{{ props.row.remark }}</el-col>
<el-col :span="9">维修记录{{ props.row.operationRecord }}</el-col>
</el-row>
</template>
</el-table-column>
<el-table-column align="center" label="设备ID" min-width="100" prop="deviceId" />
<el-table-column align="center" label="设备完整ID" min-width="150" prop="deviceFullId" />
<el-table-column align="left" header-align="center" label="站点" min-width="150" prop="address" />
<el-table-column align="left" header-align="center" label="地区" min-width="120" prop="area" />
<el-table-column align="center" label="状态" min-width="70">
<template #default="scope">
<el-tag v-if="scope.row.status" :color="STATUS_COLOR[scope.row.status].color" style="color: #fff">
{{ STATUS_COLOR[scope.row.status].text }}
</el-tag>
</template>
</el-table-column>
<el-table-column align="center" label="联系人" min-width="100" prop="contact" />
<el-table-column align="center" label="联系电话" min-width="150" prop="phone" />
<el-table-column align="center" label="安装时间" min-width="200" prop="installTime" />
<el-table-column align="center" label="正式运行时间" min-width="200" prop="runTime" />
<el-table-column align="center" fixed="right" label="操作" min-width="180">
<template #default="props">
<el-tooltip class="item" content="参数配置" effect="dark" placement="top">
<i class="el-icon-set-up text-base text-yellow-600 mx-1" @click="openPage(props.row, 'config')"></i>
</el-tooltip>
<el-tooltip class="item" content="实时数据" effect="dark" placement="top">
<i class="el-icon-odometer text-base text-green-600 mx-1" @click="openPage(props.row, 'data-realtime')"></i>
</el-tooltip>
<el-tooltip class="item" content="历史数据" effect="dark" placement="top">
<i class="el-icon-document-copy text-base text-green-600 mx-1" @click="openPage(props.row, 'history')"></i>
</el-tooltip>
<el-tooltip class="item" content="数据统计" effect="dark" placement="top">
<i class="el-icon-data-line text-base text-green-600 mx-1" @click="openPage(props.row, 'statistical')"></i>
</el-tooltip>
<el-popconfirm title="确定要删除此设备吗?" @confirm="handleDelete(props.row.deviceId)">
<template #reference>
<i class="el-icon-delete text-base text-red-600 mx-1"></i>
<!-- <el-button type="text" plain size="mini" icon="el-icon-delete"></el-button> -->
</template>
</el-popconfirm>
<el-tooltip class="item" content="编辑设备信息" effect="dark" placement="top">
<i class="el-icon-edit text-base text-blue-600 mx-1" @click="handleEdit(props.row)"></i>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<el-pagination
:current-page="devicesAll.page.page"
:default-page-size="50"
:page-count="devicesAll.page.count"
:page-size="devicesAll.page.size"
:page-sizes="[1, 10, 20, 50, 100]"
:total="devicesAll.page.total"
background
class="my-3 float-right"
layout="total, sizes, prev, pager, next, jumper"
@size-change="onSizeChange"
@current-change="onCurrentPageChange"
></el-pagination>
</template>
<!-- 编辑设备信息 -->
<DeviceEdit :show="editing" @toggle-mdoal="editing = false" />
<el-dialog v-model="deviceCreate.display" title="添加设备" top="30px" width="80%">
<DeviceCreate @on-hide="deviceCreate.hide()" />
</el-dialog>
</template>
<script setup>
import { computed, onMounted, reactive, ref } from 'vue';
import { useStore } from 'vuex';
import { useRoute, useRouter } from 'vue-router';
import { deleteDevice } from 'apis';
import { ElMessage } from 'element-plus';
import SearchBar from 'components/realtime/search-bar.vue';
import DeviceEdit from 'components/device/device-edit.vue';
import DeviceCreate from 'components/device/device-create.vue';
import useDeviceCreate from '@/hooks/useDeviceCreate';
import { STATUS_COLOR } from '@/config/config';
import { ASDU_TYPE } from '@/config/log';
let timer = null;
const store = useStore();
const router = useRouter();
const route = useRoute();
const token = computed(() => store.getters['user/token']);
const devicesAll = computed(() => {
return store.state.device.devicesAll;
});
const currentDeviceId = computed(() => store.state.device.currentDeviceId);
const contentHeight = ref(600);
const editing = ref(false);
const { show, display, hide } = useDeviceCreate();
const deviceCreate = reactive({
show,
display,
hide,
}); // hook
//
const getDevicesAllData = () => {
try {
if (token && token.value) {
store.dispatch('device/getDevicesAll');
timer && clearTimeout(timer);
timer = null;
} else {
timer = setTimeout(() => {
getDevicesAllData();
});
}
} catch (error) {
ElMessage.error(error);
throw new Error(error);
}
};
getDevicesAllData();
//
onMounted(() => {
const winHeight = document.documentElement.clientHeight;
contentHeight.value = winHeight - 250;
});
/**
* 删除
* @param {number} page 页码
* @param {number} size 每页条数
*/
function onSearch(page, size = 50) {
const deviceId = currentDeviceId.value;
const params = {
deviceId,
page,
size,
};
store.dispatch('device/getDevicesAll', params);
}
//
const onCurrentPageChange = page => {
onSearch(page, devicesAll.value.page.size || 50);
};
//
const onSizeChange = size => {
onSearch(1, size);
};
/**
* 删除某设备
* @param {string} deviceId 设备id
*/
const handleDelete = async deviceId => {
try {
await deleteDevice(deviceId);
store.commit('device/deleteDevice', deviceId);
ElMessage.success('删除成功');
store.dispatch('device/getDevices'); //
} catch (error) {
ElMessage.error('删除失败, 请稍后重试');
throw new Error(error);
}
};
//
const handleEdit = item => {
store.commit('device/setCurrentDeviceId', item.deviceId);
editing.value = true;
};
/**
* 打开页面
* @param {object} item 设备对象
* @param {string} pageName 页面name
*/
const openPage = (item, pageName) => {
item && store.commit('device/setCurrentDeviceId', item.deviceId);
router.push({
path: `/corrosion/${pageName}`,
query: route.query,
});
};
</script>

22
src/views/history.vue

@ -1,22 +0,0 @@
<template>
<el-tabs v-model="activeName">
<el-tab-pane :lazy="true" label="本地数据" name="local">
<Local v-if="activeName === 'local'" :active-name="activeName" />
</el-tab-pane>
<el-tab-pane :lazy="true" label="设备数据" name="device">
<Device v-if="activeName === 'device'" :active-name="activeName" />
</el-tab-pane>
<el-tab-pane :lazy="true" label="补传记录" name="log">
<HistoryLog v-if="activeName === 'log'" :active-name="activeName" />
</el-tab-pane>
</el-tabs>
</template>
<script setup>
import { ref } from 'vue';
import Local from 'components/history/local.vue';
import Device from 'components/history/device.vue';
import HistoryLog from 'components/history/history-log.vue';
const activeName = ref('local');
</script>

12
src/views/home/Index.vue

@ -0,0 +1,12 @@
<template>
<div>
<div>左边 时间轴</div>
<div>
<router-view></router-view>
</div>
</div>
</template>
<script setup></script>
<style scoped></style>

23
src/views/overview.vue

@ -1,23 +0,0 @@
<template>
<el-row :gutter="20">
<el-col :span="8">
<el-card shadow="hover">
<ChartDeviceCount />
</el-card>
</el-col>
<el-col :span="16">
<ChartDeviceDetail />
</el-col>
</el-row>
<DeviceTable />
</template>
<script setup>
import ChartDeviceCount from 'components/overview/chart-device-count.vue';
import ChartDeviceDetail from 'components/overview/chart-device-detail.vue';
import DeviceTable from 'components/overview/device-table.vue';
import { useStore } from 'vuex';
const store = useStore();
store.dispatch('device/getDevicesCount'); //
</script>

80
src/views/statistical-report.vue

@ -1,80 +0,0 @@
<template>
<SearchBar ref="searchBar" :loading-search="loadingSearch" @search="onSearch" />
<HistoryData :search-height="searchHeight" class="mt-4" />
</template>
<script setup>
import SearchBar from 'components/statistical/search-bar.vue';
import HistoryData from 'components/statistical/stastistical-chart.vue';
import { computed, onMounted, ref } from 'vue';
import { useStore } from 'vuex';
import { ElMessage } from 'element-plus';
import dayjs from 'dayjs';
const searchBar = ref(null);
let timer = null;
const store = useStore();
const token = computed(() => store.getters['user/token']);
const currentDeviceId = computed(() => store.state.device.currentDeviceId); // id
const search = ref({});
const loadingSearch = ref(false);
const searchHeight = ref(50);
onMounted(() => {
searchHeight.value = searchBar.value.$el.clientHeight;
});
/**
* 历史数据统计
* @param {*} deviceId // id
* @param {*} date //
* @param {*} sort // 1 -> -1->
* @param {*} page //
* @param {*} size //
* @param {*} type // 0
*/
const getData = async () => {
try {
if (token && token.value) {
if (!currentDeviceId.value) return;
loadingSearch.value = true;
const options = { ...search.value };
const date = options && options.date ? options.date : [+dayjs().subtract(7, 'day').startOf('day').format('x'), +dayjs().format('x')];
const params = {
deviceId: currentDeviceId.value,
gatheredDateRange: date,
paging: false,
sort: [
{
col: 'time',
order: 'ASC',
},
],
};
await store.dispatch('statistics/getRealtimeData', params);
timer && clearTimeout(timer);
timer = null;
loadingSearch.value = false;
} else {
loadingSearch.value = false;
timer = setTimeout(() => {
getData();
});
}
} catch (error) {
loadingSearch.value = false;
ElMessage.error(error.message || '查询失败');
}
};
getData();
/**
* 监听search信息
* @param {object} payload search组件emit的数据
*/
const onSearch = payload => {
search.value = { ...payload };
getData();
};
</script>

3
src/views/user/SignIn.vue

@ -0,0 +1,3 @@
<template>登录</template>
<script setup></script>

6
vite.config.js

@ -1,5 +1,5 @@
import Components from 'unplugin-vue-components/vite'; import Components from 'unplugin-vue-components/vite';
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'; import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers';
import WindiCSS from 'vite-plugin-windicss'; import WindiCSS from 'vite-plugin-windicss';
import { defineConfig } from 'vite'; import { defineConfig } from 'vite';
import path from 'path'; import path from 'path';
@ -11,8 +11,8 @@ const resolve = dir => path.join(__dirname, dir);
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
base: '/corrosion/', base: '/',
plugins: [vue(), VitePWA(), WindiCSS(), Components({ resolvers: [ElementPlusResolver()] }), viteCompression()], plugins: [vue(), VitePWA(), WindiCSS(), Components({ resolvers: [AntDesignVueResolver()] }), viteCompression()],
resolve: { resolve: {
alias: { alias: {
'~': __dirname, '~': __dirname,

8803
yarn.lock

File diff suppressed because it is too large
Loading…
Cancel
Save