feat: 终端能力统计、设备型号及软硬件版本统计

master
李小林 7 months ago
parent 189d56b9d1
commit 6d8aa3d71d
  1. 6
      src/api/device-type-ver/index.ts
  2. 45
      src/api/report/index.ts
  3. 59
      src/api/report/types.ts
  4. 10
      src/views/family/statement/detail-inventory-statistics/index.vue
  5. 436
      src/views/family/statement/device-type-soft/index.vue
  6. 409
      src/views/family/statement/terminal-capability-statistics/index.vue

@ -160,3 +160,9 @@ export function upgradeSoftVerTable(
method: "GET", method: "GET",
}); });
} }
export function getSoftVerOption(): AxiosPromise<OptionType[]> {
return request({
url: `/api/device-type-ver/v1/soft-ver-option`,
method: "GET",
});
}

@ -1,6 +1,12 @@
import { AxiosPromise } from "axios"; import { AxiosPromise } from "axios";
import request from "@/utils/request"; import request from "@/utils/request";
import { ReportBasicQuery, ReportDetailPageResult } from "@/api/report/types"; import {
DeviceTypeHardSoftReportQuery,
DeviceTypeHardSoftVerVO,
ReportBasicQuery,
ReportDetailPageResult,
TerminalCapabilityStatisticsVO
} from "@/api/report/types";
export function reportArea(groupId?: number): AxiosPromise<OptionType[]> { export function reportArea(groupId?: number): AxiosPromise<OptionType[]> {
return request({ return request({
@ -26,3 +32,40 @@ export function reportExportDetail(queryParams: ReportBasicQuery) {
responseType: "arraybuffer", responseType: "arraybuffer",
}); });
} }
export function reportTerminalStatistics(
data: ReportBasicQuery
): AxiosPromise<TerminalCapabilityStatisticsVO[]> {
return request({
url: `/api/report/v1/terminal-capability-statistics`,
method: "POST",
data,
});
}
export function reportExportTerminal(queryParams: ReportBasicQuery) {
return request({
url: `/api/report/v1/_export/terminal`,
method: "get",
params: queryParams,
responseType: "arraybuffer",
});
}
export function reportDevTypeSoftVerStatistics(
data: DeviceTypeHardSoftReportQuery
): AxiosPromise<DeviceTypeHardSoftVerVO[]> {
return request({
url: `/api/report/v1/device-type-soft`,
method: "POST",
data,
});
}
export function reportExportDeviceTypeHardSoftVer(
queryParams: ReportBasicQuery
) {
return request({
url: `/api/report/v1/_export/device-type-soft-ver`,
method: "get",
params: queryParams,
responseType: "arraybuffer",
});
}

@ -59,3 +59,62 @@ export interface DetailInventoryStatisticsVO {
rgMode?: string; rgMode?: string;
} }
export type ReportDetailPageResult = PageResult<DetailInventoryStatisticsVO[]>; export type ReportDetailPageResult = PageResult<DetailInventoryStatisticsVO[]>;
export interface TerminalCapabilityStatisticsVO {
area?: String;
subArea?: string;
category?: string;
deviceName?: string;
accessType?: string;
deviceType?: string;
gateWayType?: string;
devCount?: number;
}
export interface DeviceTypeHardSoftReportQuery extends PageQuery {
area?: number;
subarea?: number;
category?: string;
devName?: string;
devHardVer?: string;
softVer?: string;
gateWayType?: string;
firstStartTime?: string;
firstEndTime?: string;
lastStartTime?: string;
lastEndTime?: string;
onLineStartTime?: string;
onLineEndTime?: string;
}
export interface DeviceTypeHardSoftVerVO {
area?: String;
subArea?: string;
category?: string;
deviceName?: string;
devHardVer?: string;
softVer?: string;
devCount?: number;
}

@ -9,7 +9,15 @@
>详细清单统计</span >详细清单统计</span
> >
</div> </div>
<el-button type="primary" @click="getData">运行报表</el-button> <div>
<el-button type="primary" @click="getData">运行报表</el-button>
<el-button
type="primary"
v-if="tableData.length != 0"
@click="active = 1"
>查看报表
</el-button>
</div>
</div> </div>
</template> </template>
<div> <div>

@ -0,0 +1,436 @@
<template>
<div class="app-container">
<el-card v-show="active === 0" shadow="never">
<template #header>
<div class="flex justify-between">
<div style="display: flex; align-items: center">
<el-icon size="15"> <Grid /> </el-icon>&nbsp;<span
style="font-weight: 700; font-size: 14px; line-height: 16px"
>设备型号及软硬件版本统计条件</span
>
</div>
<div>
<el-button type="primary" @click="getData">运行报表</el-button>
<el-button
type="primary"
v-if="tableData.length != 0"
@click="active = 1"
>查看报表
</el-button>
</div>
</div>
</template>
<div>
<el-form :model="queryForm">
<el-descriptions :column="2" border>
<el-descriptions-item
label="分公司"
label-align="left"
align="center"
label-class-name="my-label"
class-name="my-content"
width="150px"
>
<el-form-item prop="area">
<el-select v-model="queryForm.area">
<el-option
v-for="item in areaOption"
:label="item.label"
:value="item.value"
:key="item.value"
/>
</el-select>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item
label="区局"
label-align="left"
align="center"
label-class-name="my-label"
class-name="my-content"
width="150px"
>
<el-form-item prop="subarea">
<el-select v-model="queryForm.subarea">
<el-option
v-for="item in subareaOption"
:label="item.label"
:value="item.value"
:key="item.value"
/>
</el-select>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item
label="设备厂商"
label-align="left"
align="center"
label-class-name="my-label"
class-name="my-content"
width="150px"
>
<el-form-item prop="category">
<el-select v-model="queryForm.category" filterable>
<el-option
v-for="item in vendorNameOption"
:label="item.label"
:value="item.value"
:key="item.value"
/>
</el-select>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item
label="终端型号"
label-align="left"
align="center"
label-class-name="my-label"
class-name="my-content"
width="150px"
>
<el-form-item prop="devName">
<el-select v-model="queryForm.devName" filterable>
<el-option
v-for="item in typeNameOption"
:label="item.label"
:value="item.value"
:key="item.value"
/>
</el-select>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item
label="设备硬件版本"
label-align="left"
align="center"
label-class-name="my-label"
class-name="my-content"
width="150px"
>
<el-form-item prop="devHardVer">
<el-select v-model="queryForm.devHardVer" filterable>
<el-option
v-for="item in devHardVerOption"
:label="item.label"
:value="item.value"
:key="item.value"
/>
</el-select>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item
label="软件版本"
label-align="left"
align="center"
label-class-name="my-label"
class-name="my-content"
width="150px"
>
<el-form-item prop="softVer">
<el-select v-model="queryForm.softVer" filterable>
<el-option
v-for="item in softVerOption"
:label="item.label"
:value="item.value"
:key="item.value"
/>
</el-select>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item
label="网关类型"
label-align="left"
align="center"
label-class-name="my-label"
class-name="my-content"
width="150px"
>
<el-form-item prop="gateWayType">
<dictionary
v-model="queryForm.gateWayType"
type-code="dev_type_name_new"
/>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item
label="首次装机时间"
label-align="left"
align="center"
label-class-name="my-label"
class-name="my-content"
width="150px"
>
<div class="flex justify-between" style="width: 160px">
<el-form-item prop="firstStartTime">
<el-date-picker
style="width: 230px"
v-model="queryForm.firstStartTime"
type="datetime"
value-format="YYYY-MM-DD hh:mm:ss"
/>
</el-form-item>
<span
style="line-height: 35px; margin-right: 5px; margin-left: 5px"
></span
>
<el-form-item prop="firstEndTime">
<el-date-picker
style="width: 235px"
v-model="queryForm.firstEndTime"
type="datetime"
value-format="YYYY-MM-DD hh:mm:ss"
/>
</el-form-item>
</div>
</el-descriptions-item>
<el-descriptions-item
label="最近一次装机时间"
label-align="left"
align="center"
label-class-name="my-label"
class-name="my-content"
width="150px"
>
<div class="flex justify-between" style="width: 160px">
<el-form-item prop="lastStartTime">
<el-date-picker
style="width: 230px"
v-model="queryForm.lastStartTime"
type="datetime"
/>
</el-form-item>
<span
style="line-height: 35px; margin-right: 5px; margin-left: 5px"
></span
>
<el-form-item prop="lastEndTime">
<el-date-picker
style="width: 235px"
v-model="queryForm.lastEndTime"
type="datetime"
/>
</el-form-item>
</div>
</el-descriptions-item>
<el-descriptions-item
label="最近一次上线时间"
label-align="left"
align="center"
label-class-name="my-label"
class-name="my-content"
width="150px"
>
<div class="flex justify-between" style="width: 160px">
<el-form-item prop="onLineStartTime">
<el-date-picker
style="width: 230px"
v-model="queryForm.onLineStartTime"
type="datetime"
/>
</el-form-item>
<span
style="line-height: 35px; margin-right: 5px; margin-left: 5px"
></span
>
<el-form-item prop="onLineEndTime">
<el-date-picker
style="width: 235px"
v-model="queryForm.onLineEndTime"
type="datetime"
/>
</el-form-item>
</div>
</el-descriptions-item>
</el-descriptions>
</el-form>
</div>
</el-card>
<el-card v-show="active === 1" shadow="never">
<template #header>
<div class="flex justify-between">
<div style="display: flex; align-items: center">
<el-icon size="15"> <Grid /> </el-icon>&nbsp;<span
style="font-weight: 700; font-size: 14px; line-height: 16px"
>设备型号及软硬件版本统计</span
>
</div>
<div>
<el-button type="primary" @click="exportDetail">导出报表</el-button>
<el-button type="primary" @click="back">返回</el-button>
</div>
</div>
</template>
<div class="any-table">
<el-table
:data="tableData"
v-loading="loading"
style="width: 100%"
max-height="540"
border
>
<el-table-column label="分公司" prop="area" align="center" fixed />
<el-table-column label="区局" prop="subArea" align="center" />
<el-table-column
label="厂商"
prop="category"
align="center"
width="150"
show-overflow-tooltip
/>
<el-table-column label="设备型号" prop="deviceName" align="center" />
<el-table-column label="硬件版本" prop="devHardVer" align="center" />
<el-table-column
label="软件版本"
show-overflow-tooltip
prop="softVer"
align="center"
/>
<el-table-column label="数量" prop="devCount" align="center" />
</el-table>
</div>
</el-card>
</div>
</template>
<script setup lang="ts">
import { Grid } from "@element-plus/icons-vue";
import {
DeviceTypeHardSoftReportQuery,
DeviceTypeHardSoftVerVO,
} from "@/api/report/types";
import {
reportArea,
reportDevTypeSoftVerStatistics,
reportExportDeviceTypeHardSoftVer,
} from "@/api/report";
import {
getHardVerOption,
getTypeNameOption,
getVendorNameOption,
} from "@/api/device-type";
import { confirm } from "@/utils/confirm";
import { getSoftVerOption } from "@/api/device-type-ver";
defineOptions({
name: "TerminalCapabilityStatistics",
inheritAttrs: false,
});
const queryForm = ref<DeviceTypeHardSoftReportQuery>({
pageNum: 1,
pageSize: 10,
});
const active = ref<number>(0);
const areaOption = ref<OptionType[]>([{ label: "全省", value: 0 }]);
const subareaOption = ref<OptionType[]>([]);
const vendorNameOption = ref<OptionType[]>([{ label: "全选", value: "-1" }]);
const typeNameOption = ref<OptionType[]>([{ label: "全选", value: "-1" }]);
const devHardVerOption = ref<OptionType[]>([{ label: "全选", value: "-1" }]);
const softVerOption = ref<OptionType[]>([{ label: "全选", value: "-1" }]);
const tableData = ref<DeviceTypeHardSoftVerVO[]>([]);
const loading = ref<boolean>(false);
watch(
() => queryForm.value.area,
() => {
if (queryForm.value.area === undefined) {
return;
}
loadSubarea(queryForm.value.area);
}
);
const getData = () => {
active.value = 1;
loading.value = true;
reportDevTypeSoftVerStatistics(queryForm.value)
.then(({ data }) => {
tableData.value = data;
})
.finally(() => {
loading.value = false;
});
};
const back = () => {
active.value = 0;
};
const loadVendorNameOption = () => {
getVendorNameOption().then(({ data }) => {
vendorNameOption.value.push(...data);
});
};
const loadHardVerOption = async () => {
await getHardVerOption("").then(({ data }) => {
devHardVerOption.value.push(...data);
});
};
const loadSoftVerOption = async () => {
await getSoftVerOption().then(({ data }) => {
softVerOption.value.push(...data);
});
};
const loadSubarea = (groupId: number) => {
reportArea(groupId).then(({ data }) => {
subareaOption.value = data;
});
};
const loadTypeNameOption = () => {
getTypeNameOption("").then(({ data }) => {
typeNameOption.value.push(...data);
});
};
const loadArea = (groupId: number) => {
reportArea(groupId).then(({ data }) => {
areaOption.value.push(...data);
});
};
const exportDetail = () => {
confirm("确认导出该数据吗", () => {
loading.value = true;
reportExportDeviceTypeHardSoftVer(queryForm.value)
.then((response: any) => {
const fileData = response.data;
const fileName = decodeURI(
response.headers["content-disposition"].split(";")[1].split("=")[1]
);
const fileType =
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8";
const blob = new Blob([fileData], { type: fileType });
const downloadUrl = window.URL.createObjectURL(blob);
const downloadLink = document.createElement("a");
downloadLink.href = downloadUrl;
downloadLink.download = fileName;
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
window.URL.revokeObjectURL(downloadUrl);
})
.finally(() => {
loading.value = false;
});
});
};
onMounted(() => {
loadArea(0);
loadVendorNameOption();
loadTypeNameOption();
loadSoftVerOption();
loadHardVerOption();
});
</script>
<style scoped lang="scss">
:deep(.my-label) {
width: 50px !important;
background: var(--el-color-white) !important;
}
:deep(.el-card__body) {
padding: 10px 5px;
}
:deep(.el-form-item--default) {
margin-bottom: 0;
}
</style>

@ -0,0 +1,409 @@
<template>
<div class="app-container">
<el-card v-show="active === 0" shadow="never">
<template #header>
<div class="flex justify-between">
<div style="display: flex; align-items: center">
<el-icon size="15"> <Grid /> </el-icon>&nbsp;<span
style="font-weight: 700; font-size: 14px; line-height: 16px"
>分地市终端能力统计条件</span
>
</div>
<div>
<el-button type="primary" @click="getData">运行报表</el-button>
<el-button
type="primary"
v-if="tableData.length != 0"
@click="active = 1"
>查看报表
</el-button>
</div>
</div>
</template>
<div>
<el-form :model="queryForm">
<el-descriptions :column="2" border>
<el-descriptions-item
label="分公司"
label-align="left"
align="center"
label-class-name="my-label"
class-name="my-content"
width="150px"
>
<el-form-item prop="area">
<el-select v-model="queryForm.area">
<el-option
v-for="item in areaOption"
:label="item.label"
:value="item.value"
:key="item.value"
/>
</el-select>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item
label="区局"
label-align="left"
align="center"
label-class-name="my-label"
class-name="my-content"
width="150px"
>
<el-form-item prop="subarea">
<el-select v-model="queryForm.subarea">
<el-option
v-for="item in subareaOption"
:label="item.label"
:value="item.value"
:key="item.value"
/>
</el-select>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item
label="设备厂商"
label-align="left"
align="center"
label-class-name="my-label"
class-name="my-content"
width="150px"
>
<el-form-item prop="category">
<el-select v-model="queryForm.category" filterable>
<el-option
v-for="item in vendorNameOption"
:label="item.label"
:value="item.value"
:key="item.value"
/>
</el-select>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item
label="终端型号"
label-align="left"
align="center"
label-class-name="my-label"
class-name="my-content"
width="150px"
>
<el-form-item prop="devName">
<el-select v-model="queryForm.devName" filterable>
<el-option
v-for="item in typeNameOption"
:label="item.label"
:value="item.value"
:key="item.value"
/>
</el-select>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item
label="设备网络侧接口"
label-align="left"
align="center"
label-class-name="my-label"
class-name="my-content"
width="150px"
>
<el-form-item prop="accessType">
<dictionary
v-model="queryForm.accessType"
type-code="dev_access_type"
/>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item
label="设备类型"
label-align="left"
align="center"
label-class-name="my-label"
class-name="my-content"
width="150px"
>
<el-form-item prop="devType">
<dictionary
v-model="queryForm.devType"
type-code="dev_type_name"
/>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item
label="网关类型"
label-align="left"
align="center"
label-class-name="my-label"
class-name="my-content"
width="150px"
>
<el-form-item prop="gateWayType">
<dictionary
v-model="queryForm.gateWayType"
type-code="dev_type_name_new"
/>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item
label="首次装机时间"
label-align="left"
align="center"
label-class-name="my-label"
class-name="my-content"
width="150px"
>
<div class="flex justify-between" style="width: 160px">
<el-form-item prop="firstStartTime">
<el-date-picker
style="width: 230px"
v-model="queryForm.firstStartTime"
type="datetime"
value-format="YYYY-MM-DD hh:mm:ss"
/>
</el-form-item>
<span
style="line-height: 35px; margin-right: 5px; margin-left: 5px"
></span
>
<el-form-item prop="firstEndTime">
<el-date-picker
style="width: 235px"
v-model="queryForm.firstEndTime"
type="datetime"
value-format="YYYY-MM-DD hh:mm:ss"
/>
</el-form-item>
</div>
</el-descriptions-item>
<el-descriptions-item
label="最近一次装机时间"
label-align="left"
align="center"
label-class-name="my-label"
class-name="my-content"
width="150px"
>
<div class="flex justify-between" style="width: 160px">
<el-form-item prop="lastStartTime">
<el-date-picker
style="width: 230px"
v-model="queryForm.lastStartTime"
type="datetime"
/>
</el-form-item>
<span
style="line-height: 35px; margin-right: 5px; margin-left: 5px"
></span
>
<el-form-item prop="lastEndTime">
<el-date-picker
style="width: 235px"
v-model="queryForm.lastEndTime"
type="datetime"
/>
</el-form-item>
</div>
</el-descriptions-item>
<el-descriptions-item
label="最近一次上线时间"
label-align="left"
align="center"
label-class-name="my-label"
class-name="my-content"
width="150px"
>
<div class="flex justify-between" style="width: 160px">
<el-form-item prop="onLineStartTime">
<el-date-picker
style="width: 230px"
v-model="queryForm.onLineStartTime"
type="datetime"
/>
</el-form-item>
<span
style="line-height: 35px; margin-right: 5px; margin-left: 5px"
></span
>
<el-form-item prop="onLineEndTime">
<el-date-picker
style="width: 235px"
v-model="queryForm.onLineEndTime"
type="datetime"
/>
</el-form-item>
</div>
</el-descriptions-item>
</el-descriptions>
</el-form>
</div>
</el-card>
<el-card v-show="active === 1" shadow="never">
<template #header>
<div class="flex justify-between">
<div style="display: flex; align-items: center">
<el-icon size="15"> <Grid /> </el-icon>&nbsp;<span
style="font-weight: 700; font-size: 14px; line-height: 16px"
>分地市终端能力统计</span
>
</div>
<div>
<el-button type="primary" @click="exportDetail">导出报表</el-button>
<el-button type="primary" @click="back">返回</el-button>
</div>
</div>
</template>
<div class="any-table">
<el-table
:data="tableData"
v-loading="loading"
style="width: 100%"
max-height="540"
border
>
<el-table-column label="分公司" prop="area" align="center" fixed />
<el-table-column label="区局" prop="subArea" align="center" />
<el-table-column
label="厂商"
prop="category"
align="center"
width="150"
show-overflow-tooltip
/>
<el-table-column label="设备型号" prop="deviceName" align="center" />
<el-table-column
label="设备网络侧接口"
prop="accessType"
align="center"
/>
<el-table-column label="设备类型" prop="deviceType" align="center" />
<el-table-column label="网关类型" prop="gateWayType" align="center" />
<el-table-column label="数量" prop="devCount" align="center" />
</el-table>
</div>
</el-card>
</div>
</template>
<script setup lang="ts">
import { Grid } from "@element-plus/icons-vue";
import {
ReportBasicQuery,
TerminalCapabilityStatisticsVO,
} from "@/api/report/types";
import {
reportArea,
reportExportTerminal,
reportTerminalStatistics,
} from "@/api/report";
import { getTypeNameOption, getVendorNameOption } from "@/api/device-type";
import { confirm } from "@/utils/confirm";
defineOptions({
name: "TerminalCapabilityStatistics",
inheritAttrs: false,
});
const queryForm = ref<ReportBasicQuery>({
pageNum: 1,
pageSize: 10,
});
const active = ref<number>(0);
const areaOption = ref<OptionType[]>([{ label: "全省", value: 0 }]);
const subareaOption = ref<OptionType[]>([]);
const vendorNameOption = ref<OptionType[]>([{ label: "全选", value: "-1" }]);
const typeNameOption = ref<OptionType[]>([{ label: "全选", value: "-1" }]);
const tableData = ref<TerminalCapabilityStatisticsVO[]>([]);
const loading = ref<boolean>(false);
watch(
() => queryForm.value.area,
() => {
if (queryForm.value.area === undefined) {
return;
}
loadSubarea(queryForm.value.area);
}
);
const getData = () => {
active.value = 1;
loading.value = true;
reportTerminalStatistics(queryForm.value)
.then(({ data }) => {
tableData.value = data;
})
.finally(() => {
loading.value = false;
});
};
const back = () => {
active.value = 0;
};
const loadVendorNameOption = () => {
getVendorNameOption().then(({ data }) => {
vendorNameOption.value.push(...data);
});
};
const loadSubarea = (groupId: number) => {
reportArea(groupId).then(({ data }) => {
subareaOption.value = data;
});
};
const loadTypeNameOption = () => {
getTypeNameOption("").then(({ data }) => {
typeNameOption.value.push(...data);
});
};
const loadArea = (groupId: number) => {
reportArea(groupId).then(({ data }) => {
areaOption.value.push(...data);
});
};
const exportDetail = () => {
confirm("确认导出该数据吗", () => {
loading.value = true;
reportExportTerminal(queryForm.value)
.then((response: any) => {
const fileData = response.data;
const fileName = decodeURI(
response.headers["content-disposition"].split(";")[1].split("=")[1]
);
const fileType =
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8";
const blob = new Blob([fileData], { type: fileType });
const downloadUrl = window.URL.createObjectURL(blob);
const downloadLink = document.createElement("a");
downloadLink.href = downloadUrl;
downloadLink.download = fileName;
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
window.URL.revokeObjectURL(downloadUrl);
})
.finally(() => {
loading.value = false;
});
});
};
onMounted(() => {
loadArea(0);
loadVendorNameOption();
loadTypeNameOption();
});
</script>
<style scoped lang="scss">
:deep(.my-label) {
width: 50px !important;
background: var(--el-color-white) !important;
}
:deep(.el-card__body) {
padding: 10px 5px;
}
:deep(.el-form-item--default) {
margin-bottom: 0;
}
</style>
Loading…
Cancel
Save