| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585 |
- <template style="background-color: #edf4ff">
- <el-row :gutter="20">
- <!-- 左侧部门树 -->
- <el-col :span="4" :xs="24">
- <ContentWrap class="h-1/1" v-if="treeShow" style="border: 0; height: 87.5vh">
- <DeptTree @node-click="handleDeptNodeClick" />
- </ContentWrap>
- </el-col>
- <el-col :span="contentSpan" :xs="24">
- <!-- 统计卡片 -->
- <el-row :gutter="21" class="mb-4">
- <el-col :span="9">
- <div class="flex gap-2 bg-white p-5 rounded-lg">
- <div class="stat-card bg-blue-gradient w-[40%]">
- <Icon icon="ep:histogram" :size="40" />
- <div class="card-title">设备总数</div>
- <div class="card-value pt-5">{{ devicesCount }}</div>
- </div>
- <div class="stat-card bg-green-gradient w-[60%]">
- <div class="flex flex-col items-center h-full">
- <!-- <Icon icon="ep:odometer" :size="40" class="mb-2" /> -->
- <div class="card-title mb-2">设备状态</div>
- <div class="flex-1 w-full" style="height: calc(100% - 60px)">
- <Echart
- v-if="statusChartOption.series && statusChartOption.series[0].data.length > 0"
- :options="statusChartOption"
- :height="160"
- />
- <div v-else class="flex items-center justify-center h-full text-gray-500">
- 暂无数据
- </div>
- </div>
- </div>
- </div>
- </div>
- </el-col>
- <el-col :span="10">
- <div style="background-color: #fff" class="rounded-lg">
- <Echart :options="myoption" :height="270" />
- </div>
- </el-col>
- <el-col :span="5">
- <div class="bg-[#fff] p-2 py-5 h-[270px] rounded-lg">
- <el-form ref="queryFormRef" :model="queryParams">
- <el-form-item label="设备编码" prop="deviceCode">
- <el-input
- style="height: 30px"
- @keyup.enter="handleQuery"
- v-model="queryParams.deviceCode"
- placeholder="请输入设备编码"
- />
- </el-form-item>
- <el-form-item label="设备名称" prop="deviceName">
- <el-input
- style="height: 30px"
- @keyup.enter="handleQuery"
- v-model="queryParams.deviceName"
- placeholder="请输入设备名称"
- />
- </el-form-item>
- <el-form-item>
- <el-button @click="handleQuery"
- ><Icon icon="ep:search" class="mr-3px" />{{
- t('operationFill.search')
- }}</el-button
- >
- </el-form-item>
- <el-form-item>
- <el-button @click="resetQuery"
- ><Icon icon="ep:refresh" class="mr-3px" />
- {{ t('operationFill.reset') }}</el-button
- >
- </el-form-item>
- <el-form-item>
- <el-button type="success" plain
- ><Icon icon="ep:download" class="mr-3px" /> 导出</el-button
- >
- </el-form-item>
- </el-form>
- </div>
- </el-col>
- </el-row>
- <!-- 列表 -->
- <ContentWrap style="border: 0; margin-top: 20px">
- <el-table
- v-loading="loading"
- :data="list"
- :stripe="true"
- :show-overflow-tooltip="true"
- @sort-change="handleSortChange"
- height="45vh"
- >
- <el-table-column :label="t('iotDevice.serial')" width="70" align="center">
- <template #default="scope">
- {{ scope.$index + 1 }}
- </template>
- </el-table-column>
- <el-table-column label="设备编码" sortable align="center" prop="deviceCode" width="150" />
- <el-table-column
- :label="t('iotDevice.name')"
- sortable
- align="center"
- prop="deviceName"
- min-width="250"
- >
- <template #default="scope">
- <el-link :underline="false" type="primary" @click="handleDetail(scope.row.id)">
- {{ scope.row.deviceName }}
- </el-link>
- </template>
- </el-table-column>
- <el-table-column
- :label="t('iotDevice.dept')"
- align="center"
- prop="deptName"
- min-width="150"
- />
- <el-table-column
- :label="t('iotDevice.status')"
- align="center"
- prop="deviceStatus"
- min-width="90"
- >
- <template #default="scope">
- <dict-tag :type="DICT_TYPE.PMS_DEVICE_STATUS" :value="scope.row.deviceStatus" />
- </template>
- </el-table-column>
- <el-table-column
- :label="t('iotDevice.assets')"
- align="center"
- prop="assetProperty"
- min-width="110"
- >
- <template #default="scope">
- <dict-tag :type="DICT_TYPE.PMS_ASSET_PROPERTY" :value="scope.row.assetProperty" />
- </template>
- </el-table-column>
- <el-table-column
- :label="t('iotDevice.assetClass')"
- align="center"
- prop="assetClassName"
- min-width="170"
- />
- <el-table-column label="生产厂家" align="center" prop="manufacturer" min-width="200" />
- <el-table-column label="生产日期" align="center" min-width="200">
- <template #default="scope">
- {{ formatDate(scope.row.manDate).substring(0, 10) }}
- </template>
- </el-table-column>
- <el-table-column label="投运日期" align="center" min-width="200">
- <template #default="scope">
- {{ formatDate(scope.row.enableDate).substring(0, 10) }}
- </template>
- </el-table-column>
- <el-table-column
- :label="t('deviceForm.brand')"
- align="center"
- prop="brandName"
- min-width="150"
- />
- <el-table-column
- :label="t('deviceForm.model')"
- align="center"
- prop="model"
- min-width="170"
- />
- <el-table-column
- :label="t('devicePerson.rp')"
- align="center"
- prop="chargeName"
- min-width="170"
- />
- <el-table-column
- :label="t('deviceForm.useProject')"
- align="center"
- prop="useProject"
- min-width="170"
- />
- <el-table-column
- :label="t('deviceForm.assetOwner')"
- align="center"
- prop="assetOwnership"
- min-width="170"
- />
- <!-- <el-table-column
- :label="t('operationFill.operation')"
- align="center"
- min-width="180px"
- fixed="right"
- >
- <template #default="scope">
- <el-button
- link
- type="primary"
- @click="openForm('update', scope.row.id)"
- v-hasPermi="['rq:iot-device:update']"
- >
- {{ t('iotDevice.update') }}
- </el-button>
- <el-button
- link
- type="danger"
- @click="handleDelete(scope.row.id)"
- v-hasPermi="['rq:iot-device:delete']"
- >
- {{ t('iotDevice.delete') }}
- </el-button>
- </template>
- </el-table-column> -->
- </el-table>
- <!-- 分页 -->
- <Pagination
- :total="total"
- v-model:page="queryParams.pageNo"
- v-model:limit="queryParams.pageSize"
- @pagination="getList"
- />
- </ContentWrap>
- </el-col>
- </el-row>
- </template>
- <script setup lang="ts">
- import download from '@/utils/download'
- import { IotDeviceApi, IotDeviceVO } from '@/api/pms/device'
- import { DICT_TYPE } from '@/utils/dict'
- import DeptTree from '@/views/system/user/DeptTree.vue'
- import { buildSortingField } from '@/utils'
- import { useRefreshStore } from '@/store/modules/pms/refreshStore'
- import Echart from '@/components/Echart/src/Echart.vue'
- import echarts from '@/plugins/echarts'
- import { formatDate } from '@/utils/formatTime'
- /** 设备台账 列表 */
- defineOptions({ name: 'IotDevicePms' })
- const message = useMessage() // 消息弹窗
- const { t } = useI18n() // 国际化
- const { push } = useRouter() // 路由跳转
- const refreshStore = useRefreshStore()
- const loading = ref(true) // 列表的加载中
- const ifShow = ref(false)
- const list = ref<IotDeviceVO[]>([]) // 列表的数据
- const total = ref(0) // 列表的总页数
- const queryParams = reactive({
- pageNo: 1,
- pageSize: 10,
- deviceCode: undefined,
- deviceName: undefined,
- brand: undefined,
- brandName: undefined,
- model: undefined,
- deptId: undefined,
- deviceStatus: undefined,
- assetProperty: undefined,
- picUrl: undefined,
- remark: undefined,
- manufacturerId: undefined,
- supplierId: undefined,
- manDate: [],
- nameplate: undefined,
- expires: undefined,
- plPrice: undefined,
- plDate: [],
- plYear: undefined,
- plStartDate: [],
- plMonthed: undefined,
- plAmounted: undefined,
- remainAmount: undefined,
- infoId: undefined,
- infoType: undefined,
- infoName: undefined,
- infoRemark: undefined,
- infoUrl: undefined,
- templateJson: undefined,
- creator: undefined,
- sortingFields: [],
- assetClass: undefined,
- yfDeviceCode: undefined
- })
- const queryFormRef = ref(null) // 搜索的表单
- const exportLoading = ref(false) // 导出的加载中
- const contentSpan = ref(20)
- const treeShow = ref(true)
- const handleSortChange = (params: any) => {
- //console.log(`排序字段: ${prop}, 排序方式: ${order}`);
- queryParams.sortingFields = []
- queryParams.sortingFields = [buildSortingField(params)]
- getList()
- }
- const myoption = computed(() => {
- return {
- tooltip: {
- trigger: 'axis'
- },
- xAxis: {
- type: 'category',
- data: deviceClassify.value.map((item) => item.category),
- axisLabel: { color: '#000', rotate: 50 }
- },
- yAxis: {
- name: '分类top10',
- type: 'value'
- },
- series: [
- {
- data: deviceClassify.value.map((item) => item.value),
- type: 'bar',
- barWidth: 20,
- barCategoryGap: '30%',
- itemStyle: {
- color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
- { offset: 0, color: '#83bff6' },
- { offset: 0.5, color: '#188df0' },
- { offset: 1, color: '#188df0' }
- ])
- },
- emphasis: {
- itemStyle: {
- color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
- { offset: 0, color: '#2378f7' },
- { offset: 0.7, color: '#2378f7' },
- { offset: 1, color: '#83bff6' }
- ])
- }
- }
- }
- ]
- }
- })
- /** 查询列表 */
- const getList = async () => {
- loading.value = true
- try {
- const data = await IotDeviceApi.getIotDeviceList(queryParams)
- list.value = data.list
- total.value = data.total
- } finally {
- loading.value = false
- }
- }
- const resetQuery = () => {
- queryFormRef.value.resetFields()
- handleQuery()
- }
- /** 处理部门被点击 */
- const handleDeptNodeClick = async (row) => {
- queryParams.deptId = row.id
- deviceCountQueryParams.deptId = row.id
- await getList()
- await getDeviceCount()
- }
- /** 搜索按钮操作 */
- const handleQuery = () => {
- queryParams.pageNo = 1
- getList()
- }
- const moreQuery = (show) => {
- ifShow.value = show
- }
- const openForm = (type: string, id?: number, deptId?: number) => {
- //修改
- if (typeof id === 'number') {
- push({ name: 'DeviceDetailEdit', params: { type, id }, query: { source: 'devicerouter' } })
- return
- }
- // 新增
- if (deptId) {
- push({ name: 'DeviceDetailAdd', params: { type, deptId }, query: { source: 'devicerouter' } })
- } else {
- push({ name: 'DeviceDetailAddd', params: {}, query: { source: 'devicerouter' } })
- }
- }
- /** 删除按钮操作 */
- const handleDelete = async (id: number) => {
- try {
- // 删除的二次确认
- await message.delConfirm()
- // 发起删除
- await IotDeviceApi.deleteIotDevice(id)
- message.success(t('common.delSuccess'))
- // 刷新列表
- await getList()
- } catch {}
- }
- const handleDetail = (id: number) => {
- push({ name: 'DeviceDetailInfo', params: { id } })
- }
- /** 导出按钮操作 */
- const handleExport = async () => {
- try {
- // 导出的二次确认
- await message.exportConfirm()
- // 发起导出
- exportLoading.value = true
- const data = await IotDeviceApi.exportIotDevice(queryParams)
- download.excel(data, '设备台账.xls')
- } catch {
- } finally {
- exportLoading.value = false
- }
- }
- // 设备数量
- let devicesCount = ref(0)
- const deviceCountQueryParams = reactive({
- deptId: undefined
- })
- // 设备状态
- let deviceStatus = ref([])
- const statusChartOption = computed(() => {
- // 将 deviceStatus 转换为饼图需要的格式
- const data = deviceStatus.value.map((item) => ({
- value: item.value,
- name: item.name
- }))
- // 如果没有数据,返回空配置
- if (data.length === 0) {
- return {
- series: [
- {
- type: 'pie',
- data: []
- }
- ]
- }
- }
- return {
- tooltip: {
- trigger: 'item',
- formatter: '{a} <br/>{b}: {c} ({d}%)'
- },
- legend: {
- show: false
- },
- series: [
- {
- name: ' ',
- type: 'pie',
- radius: ['40%', '70%'],
- avoidLabelOverlap: false,
- itemStyle: {
- borderRadius: 5,
- borderColor: '#fff',
- borderWidth: 2
- },
- label: {
- show: true,
- formatter: '{b}\n{d}%',
- fontSize: 10
- },
- labelLine: {
- show: true
- },
- data: data,
- emphasis: {
- itemStyle: {
- shadowBlur: 10,
- shadowOffsetX: 0,
- shadowColor: 'rgba(0, 0, 0, 0.5)'
- }
- }
- }
- ]
- }
- })
- // 分类top10
- let deviceClassify = ref([])
- const getDeviceCount = async () => {
- devicesCount.value = await IotDeviceApi.getIotDeviceCount(deviceCountQueryParams)
- deviceStatus.value = await IotDeviceApi.getIotDeviceStatus(deviceCountQueryParams)
- deviceClassify.value = await IotDeviceApi.getIotDeviceClassify(deviceCountQueryParams)
- }
- /** 初始化 **/
- onMounted(async () => {
- getDeviceCount()
- // productClassifyList.value = handleTree(
- // await ProductClassifyApi.IotProductClassifyApi.getSimpleProductClassifyList()
- // )
- const sort = {
- field: 'sortColumn',
- order: 'asc'
- }
- queryParams.sortingFields.push(sort)
- await getList()
- refreshStore.registerCallback('devicerouter', getList)
- })
- </script>
- <style scoped>
- .stat-card {
- padding: 20px;
- border-radius: 10px;
- color: white;
- text-align: center;
- font-size: 14px;
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
- transition:
- transform 0.3s ease,
- box-shadow 0.3s ease;
- backdrop-filter: blur(12px);
- height: 230px;
- }
- .stat-card::before {
- position: absolute;
- filter: blur(20px);
- z-index: -1;
- }
- .stat-card:hover {
- transform: translateY(-4px);
- box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
- }
- .card-title {
- margin: 8px 0;
- font-size: 16px;
- }
- .card-value {
- font-size: 28px;
- font-weight: bold;
- margin: 8px 0;
- }
- .card-trend {
- font-size: 12px;
- opacity: 0.9;
- }
- /* 毛玻璃渐变背景 —— 不再使用 background-image */
- .bg-blue-gradient {
- background: linear-gradient(135deg, rgba(77, 147, 255, 0.5), rgba(75, 132, 254));
- }
- .bg-green-gradient {
- background: linear-gradient(135deg, rgba(101, 226, 136, 0.1), #52d7a2);
- background-color: rgba(76, 175, 80, 0.1);
- }
- .bg-orange-gradient {
- background: linear-gradient(135deg, #ff9800, #f57c00);
- background-color: rgba(255, 152, 0, 0.5);
- }
- /* 确保内容不溢出 */
- :deep(.el-row) {
- flex-wrap: wrap;
- }
- .text-truncate {
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
- ::v-deep .el-table__header-wrapper {
- position: sticky !important;
- width: 100%;
- top: 0px;
- z-index: 2000;
- }
- </style>
|