Files
fishery-web/src/views/fishery/aquUser/index.vue

1430 lines
54 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="用户名" prop="userName">
<el-input v-model="queryParams.userName" placeholder="请输入用户名" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="手机号" prop="mobilePhone">
<el-input v-model="queryParams.mobilePhone" placeholder="请输入手机号" clearable @keyup.enter="handleQuery" />
</el-form-item>
<!-- <el-form-item label="省份" prop="province">
<el-input v-model="queryParams.province" placeholder="请输入省份" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="城市" prop="city">
<el-input v-model="queryParams.city" placeholder="请输入城市" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="区县" prop="district">
<el-input v-model="queryParams.district" placeholder="请输入区县" clearable @keyup.enter="handleQuery" />
</el-form-item> -->
<el-form-item label="报警电话" prop="warnPhoneJson">
<el-input v-model="queryParams.warnPhoneJson" placeholder="请输入报警电话" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['fishery:aquUser:add']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['fishery:aquUser:edit']"
>修改</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['fishery:aquUser:remove']"
>删除</el-button
>
</el-col>
<!-- <el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['fishery:aquUser:export']">导出</el-button>
</el-col> -->
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" border :data="aquUserList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="主键id" align="center" prop="id" v-if="false" />
<el-table-column label="用户名" align="center" prop="userName" />
<el-table-column label="手机号" align="center" prop="mobilePhone" />
<!-- <el-table-column label="省份" align="center" prop="province" />
<el-table-column label="城市" align="center" prop="city" />
<el-table-column label="区县" align="center" prop="district" /> -->
<!-- <el-table-column label="展示标题" align="center" prop="title" /> -->
<el-table-column label="创建时间" align="center" prop="createTime" />
<el-table-column label="更新时间" align="center" prop="updateTime" />
<el-table-column label="操作" align="center" fixed="right" width="240" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="子账号" placement="top">
<el-button link type="primary" icon="User" @click="handleViewSubAccounts(scope.row)"></el-button>
</el-tooltip>
<el-tooltip content="报警电话" placement="top">
<el-button link type="primary" icon="Phone" @click="handleViewWarnPhones(scope.row)"></el-button>
</el-tooltip>
<el-tooltip content="查看塘口" placement="top">
<el-button link type="primary" icon="View" @click="handleViewPonds(scope.row)"></el-button>
</el-tooltip>
<el-tooltip content="修改" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['fishery:aquUser:edit']"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['fishery:aquUser:remove']"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<!-- 添加或修改养殖账号对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="850px" append-to-body>
<el-form ref="aquUserFormRef" :model="form" :rules="rules" :inline="true" label-width="120px">
<el-form-item label="用户名" prop="userName">
<el-input v-model="form.userName" placeholder="请输入用户名" />
</el-form-item>
<el-form-item label="手机号" prop="mobilePhone">
<el-input v-model="form.mobilePhone" placeholder="请输入手机号" />
</el-form-item>
<el-form-item label="报警电话" prop="warnPhoneJson">
<el-input v-model="form.warnPhoneJson" placeholder="请输入报警电话" />
</el-form-item>
<el-form-item label="省份" prop="province">
<el-input v-model="form.province" placeholder="请输入省份" />
</el-form-item>
<el-form-item label="城市" prop="city">
<el-input v-model="form.city" placeholder="请输入城市" />
</el-form-item>
<el-form-item label="区县" prop="district">
<el-input v-model="form.district" placeholder="请输入区县" />
</el-form-item>
<el-form-item label="访问Token" prop="accessToken">
<el-input v-model="form.accessToken" placeholder="请输入访问Token" />
</el-form-item>
<el-form-item label="刷新Token" prop="refreshToken">
<el-input v-model="form.refreshToken" placeholder="请输入刷新Token" />
</el-form-item>
<el-form-item label="小程序的openId" prop="wxOpenId">
<el-input v-model="form.wxOpenId" placeholder="请输入小程序的openId" />
</el-form-item>
<el-form-item label="微信SessionKey" prop="wxSessionKey">
<el-input v-model="form.wxSessionKey" placeholder="请输入微信SessionKey" />
</el-form-item>
<el-form-item label="是否管理员" prop="isManager">
<el-input v-model="form.isManager" placeholder="请输入是否管理员" />
</el-form-item>
<el-form-item label="微信unionId" prop="wxUnionId">
<el-input v-model="form.wxUnionId" placeholder="请输入微信unionId" />
</el-form-item>
<el-form-item label="公众号的openId" prop="tecentOpenId">
<el-input v-model="form.tecentOpenId" placeholder="请输入公众号的openId" />
</el-form-item>
<el-form-item label="展示标题" prop="title">
<el-input v-model="form.title" placeholder="请输入展示标题" />
</el-form-item>
<el-form-item label="是否拥有大屏" prop="hasScreen">
<el-input v-model="form.hasScreen" placeholder="请输入是否拥有大屏" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入备注" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
<!-- 子账号对话框 -->
<el-dialog :title="subAccountDialog.title" v-model="subAccountDialog.visible" width="1000px" append-to-body>
<!-- 加载状态 -->
<div v-if="subAccountLoading" class="text-center py-8">
<el-icon class="is-loading" :size="40" color="#409eff">
<Loading />
</el-icon>
<p class="mt-4 text-gray-500">正在加载子账号信息...</p>
</div>
<!-- 子账号表格 -->
<template v-else>
<el-table
:data="subAccountList"
border
height="400px"
v-if="subAccountList.length > 0"
>
<el-table-column type="index" label="序号" width="60" align="center"/>
<el-table-column label="用户名" align="center" prop="userName" width="180"/>
<el-table-column label="手机号" align="center" prop="mobilePhone" width="150"/>
<el-table-column label="报警电话" align="center" prop="warnPhoneJson" show-overflow-tooltip>
<template #default="scope">
<span v-if="scope.row.warnPhoneJson">
<el-icon class="mr-1"><Phone /></el-icon>
{{ scope.row.warnPhoneJson }}
</span>
<span v-else class="text-gray-400">未设置</span>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180"/>
</el-table>
<div v-if="subAccountList.length === 0" class="text-center py-8 text-gray-500">
该用户暂无子账号
</div>
</template>
<template #footer>
<div class="dialog-footer">
<el-button @click="subAccountDialog.visible = false"> </el-button>
</div>
</template>
</el-dialog>
<!-- 报警电话对话框 -->
<el-dialog :title="warnPhoneDialog.title" v-model="warnPhoneDialog.visible" width="500px" append-to-body>
<el-descriptions :column="1" border>
<el-descriptions-item label="第一报警电话">
<span v-if="warnPhones[0]" class="text-base">
<el-icon class="mr-1"><Phone /></el-icon>
{{ warnPhones[0] }}
</span>
<span v-else class="text-gray-400">未设置</span>
</el-descriptions-item>
<el-descriptions-item label="第二报警电话">
<span v-if="warnPhones[1]" class="text-base">
<el-icon class="mr-1"><Phone /></el-icon>
{{ warnPhones[1] }}
</span>
<span v-else class="text-gray-400">未设置</span>
</el-descriptions-item>
<el-descriptions-item label="第三报警电话">
<span v-if="warnPhones[2]" class="text-base">
<el-icon class="mr-1"><Phone /></el-icon>
{{ warnPhones[2] }}
</span>
<span v-else class="text-gray-400">未设置</span>
</el-descriptions-item>
</el-descriptions>
<div v-if="warnPhones.length === 0" class="text-center py-8 text-gray-500">
该用户暂未设置报警电话
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="warnPhoneDialog.visible = false"> </el-button>
</div>
</template>
</el-dialog>
<!-- 用户塘口信息对话框 -->
<el-dialog
:title="pondDialog.title"
v-model="pondDialog.visible"
width="1400px"
top="7.5vh"
class="pond-dialog"
append-to-body
destroy-on-close
>
<!-- 加载状态 -->
<div v-if="pondLoading" class="loading-container">
<div class="text-center py-20">
<el-icon class="is-loading" :size="40" color="#409eff">
<Loading />
</el-icon>
<p class="mt-4 text-gray-500">正在加载塘口信息...</p>
</div>
</div>
<!-- 内容区 -->
<template v-else>
<el-tabs v-model="activePondTab" type="border-card" v-if="userPondList.length > 0">
<el-tab-pane
v-for="pond in userPondList"
:key="pond.id"
:label="pond.pondName"
:name="String(pond.id)"
>
<!-- 塘口基本信息 -->
<div class="pond-info mb-2">
<el-descriptions :column="4" border size="small">
<el-descriptions-item label="设备数量">
<span class="text-lg font-bold text-primary">{{ getPondDeviceCount(String(pond.id)) }}</span>
</el-descriptions-item>
<el-descriptions-item label="开关数量">
<span class="text-lg font-bold">{{ getPondSwitchCount(String(pond.id)) }}</span>
</el-descriptions-item>
<el-descriptions-item label="夜间防扰开关">
<dict-tag :options="[{label:'关',value:'0'},{label:'开',value:'1'}]" :value="'0'"/>
</el-descriptions-item>
<el-descriptions-item label="创建时间">
{{ (pond as any).createTime ? parseTime((pond as any).createTime, '{y}-{m}-{d} {h}:{i}:{s}') : '-' }}
</el-descriptions-item>
</el-descriptions>
</div>
<!-- 设备列表 -->
<div class="devices-section">
<div class="device-list">
<el-card
v-for="device in getPondDevicesByType(String(pond.id), [1,3])"
:key="device.id"
class="device-card mb-2"
shadow="hover"
>
<template #header>
<div class="flex justify-between items-center">
<span class="font-bold">{{ device.deviceName }}</span>
<el-tag size="small" type="primary">水质检测仪</el-tag>
</div>
</template>
<!-- 水质检测仪基本信息 -->
<el-row :gutter="10">
<el-col :span="8">
<div class="param-item">
<div class="param-label">设备编号</div>
<div class="param-value-small">{{ device.serialNum || '-' }}</div>
</div>
</el-col>
<el-col :span="8">
<div class="param-item">
<div class="param-label">物联网卡号</div>
<div class="param-value-small">{{ device.iccId || '-' }}</div>
</div>
</el-col>
<el-col :span="8">
<div class="param-item">
<div class="param-label">设备分类</div>
<div class="param-value-small">{{ device.category || '-' }}</div>
</div>
</el-col>
</el-row>
<!-- 实时监测数据 -->
<el-row :gutter="10" class="mt-3">
<el-col :span="6">
<div class="param-item">
<div class="param-label">溶解氧</div>
<div class="param-value text-green-600">{{ device.valueDissolvedOxygen || '0.00' }}</div>
</div>
</el-col>
<el-col :span="6">
<div class="param-item">
<div class="param-label">水温</div>
<div class="param-value text-blue-600">{{ device.valueTemperature || '0.00' }}</div>
</div>
</el-col>
<el-col :span="6">
<div class="param-item">
<div class="param-label">饱和度</div>
<div class="param-value">{{ device.valueSaturability || '0.00' }}</div>
</div>
</el-col>
<el-col :span="6">
<div class="param-item">
<div class="param-label">PH值</div>
<div class="param-value">{{ device.valuePh || '0.00' }}</div>
</div>
</el-col>
</el-row>
<el-row :gutter="10" class="mt-3">
<el-col :span="6">
<div class="param-item">
<div class="param-label">盐度</div>
<div class="param-value">{{ device.valueSalinity || '0.00' }}</div>
</div>
</el-col>
<el-col :span="6">
<div class="param-item">
<div class="param-label">参比值</div>
<div class="param-value">{{ device.treference || '0.00' }}</div>
</div>
</el-col>
<el-col :span="6">
<div class="param-item">
<div class="param-label">荧光值</div>
<div class="param-value">{{ device.tfluorescence || '0.00' }}</div>
</div>
</el-col>
<el-col :span="6">
<div class="param-item">
<div class="param-label">相位差</div>
<div class="param-value">{{ device.phaseDifference || '0.00' }}</div>
</div>
</el-col>
</el-row>
<!-- 补偿参数 -->
<el-row :gutter="10" class="mt-3">
<el-col :span="6">
<div class="param-item">
<div class="param-label">盐度补偿</div>
<div class="param-value-small">{{ device.salinityCompensation || '0.00' }}</div>
</div>
</el-col>
<el-col :span="6">
<div class="param-item">
<div class="param-label">温度补偿</div>
<div class="param-value-small">{{ device.temperatureCompensation || '0.00' }}</div>
</div>
</el-col>
<el-col :span="6">
<div class="param-item">
<div class="param-label">线性系数补偿</div>
<div class="param-value-small">{{ device.phaseCompensation || '0.00' }}</div>
</div>
</el-col>
<el-col :span="6">
<div class="param-item">
<div class="param-label">相位差补偿</div>
<div class="param-value-small">{{ device.phasedifCompensation || '0.00' }}</div>
</div>
</el-col>
</el-row>
<!-- 时间信息 -->
<el-row :gutter="10" class="mt-3">
<el-col :span="6">
<div class="param-item">
<div class="param-label">绑定时间</div>
<div class="param-value-small">{{ parseTime(device.bindTime, '{y}-{m}-{d}') || '-' }}</div>
</div>
</el-col>
<el-col :span="6">
<div class="param-item">
<div class="param-label">激活时间</div>
<div class="param-value-small">{{ parseTime((device as any).createTime, '{y}-{m}-{d}') || '-' }}</div>
</div>
</el-col>
<el-col :span="6">
<div class="param-item">
<div class="param-label">服务到期</div>
<div class="param-value-small text-danger">{{ parseTime(device.deadTime, '{y}-{m}-{d}') || '-' }}</div>
</div>
</el-col>
<el-col :span="6">
<div class="param-item">
<div class="param-label">校准时间</div>
<div class="param-value-small">{{ (device as any).correctTime ? parseTime((device as any).correctTime, '{y}-{m}-{d}') : '-' }}</div>
</div>
</el-col>
</el-row>
<!-- 告警配置 -->
<el-row :gutter="10" class="mt-3">
<el-col :span="6">
<div class="param-item">
<div class="param-label">溶解氧电话告警</div>
<dict-tag :options="[{label:'关',value:'0'},{label:'开',value:'1'}]" :value="String(device.oxyWarnCallOpen || 0)"/>
</div>
</el-col>
<el-col :span="6">
<div class="param-item">
<div class="param-label">溶解氧告警下限</div>
<div class="param-value-small">{{ device.oxyWarnLower || 0 }}</div>
</div>
</el-col>
<el-col :span="6">
<div class="param-item">
<div class="param-label">温度电话告警</div>
<dict-tag :options="[{label:'关',value:'0'},{label:'开',value:'1'}]" :value="String(device.tempWarnCallOpen || 0)"/>
</div>
</el-col>
<el-col :span="6">
<div class="param-item">
<div class="param-label">电压告警开关</div>
<dict-tag :options="[{label:'关',value:'0'},{label:'开',value:'1'}]" :value="String(device.voltageWarnOpen || 0)"/>
</div>
</el-col>
</el-row>
<el-row :gutter="10" class="mt-3">
<el-col :span="6">
<div class="param-item">
<div class="param-label">温度告警上限</div>
<div class="param-value-small">{{ device.tempWarnUpper || 0 }}</div>
</div>
</el-col>
<el-col :span="6">
<div class="param-item">
<div class="param-label">温度告警下限</div>
<div class="param-value-small">{{ device.tempWarnLower || 0 }}</div>
</div>
</el-col>
<el-col :span="6">
<div class="param-item">
<div class="param-label">电量电话告警</div>
<dict-tag :options="[{label:'关',value:'0'},{label:'开',value:'1'}]" :value="String(device.batteryWarnCallOpen || 0)"/>
</div>
</el-col>
<el-col :span="6">
<div class="param-item">
<div class="param-label">电量告警下限</div>
<div class="param-value-small">{{ device.batteryWarnLower || 0 }}</div>
</div>
</el-col>
</el-row>
</el-card>
</div>
<div v-if="getPondDevicesByType(String(pond.id), [1,3]).length === 0" class="text-center py-4 text-gray-400">
暂无水质检测仪
</div>
</div>
<!-- 测控一体机设备 -->
<div class="devices-section mt-4" v-if="getPondDevicesByType(String(pond.id), [2]).length > 0">
<div class="device-list">
<el-card
v-for="device in getPondDevicesByType(String(pond.id), [2])"
:key="device.id"
class="device-card mb-3"
shadow="hover"
>
<template #header>
<div class="flex justify-between items-center">
<span class="font-bold">{{ device.deviceName }}</span>
<el-tag size="small" type="success">测控一体机</el-tag>
</div>
</template>
<!-- 测控一体机基本信息 -->
<el-row :gutter="10">
<el-col :span="8">
<div class="param-item">
<div class="param-label">设备编号</div>
<div class="param-value-small">{{ device.serialNum || '-' }}</div>
</div>
</el-col>
<el-col :span="8">
<div class="param-item">
<div class="param-label">物联网卡号</div>
<div class="param-value-small">{{ device.iccId || '-' }}</div>
</div>
</el-col>
<el-col :span="8">
<div class="param-item">
<div class="param-label">设备分类</div>
<div class="param-value-small">{{ device.category || '-' }}</div>
</div>
</el-col>
</el-row>
<!-- 实时监测数据 -->
<el-row :gutter="10" class="mt-3">
<el-col :span="6">
<div class="param-item">
<div class="param-label">溶解氧</div>
<div class="param-value text-green-600">{{ device.valueDissolvedOxygen || '0.00' }}</div>
</div>
</el-col>
<el-col :span="6">
<div class="param-item">
<div class="param-label">水温</div>
<div class="param-value text-blue-600">{{ device.valueTemperature || '0.00' }}</div>
</div>
</el-col>
<el-col :span="6">
<div class="param-item">
<div class="param-label">饱和度</div>
<div class="param-value">{{ device.valueSaturability || '0.00' }}</div>
</div>
</el-col>
<el-col :span="6">
<div class="param-item">
<div class="param-label">PH值</div>
<div class="param-value">{{ device.valuePh || '0.00' }}</div>
</div>
</el-col>
</el-row>
<el-row :gutter="10" class="mt-3">
<el-col :span="6">
<div class="param-item">
<div class="param-label">盐度</div>
<div class="param-value">{{ device.valueSalinity || '0.00' }}</div>
</div>
</el-col>
<el-col :span="6">
<div class="param-item">
<div class="param-label">参比值</div>
<div class="param-value">{{ device.treference || '0.00' }}</div>
</div>
</el-col>
<el-col :span="6">
<div class="param-item">
<div class="param-label">荧光值</div>
<div class="param-value">{{ device.tfluorescence || '0.00' }}</div>
</div>
</el-col>
<el-col :span="6">
<div class="param-item">
<div class="param-label">相位差</div>
<div class="param-value">{{ device.phaseDifference || '0.00' }}</div>
</div>
</el-col>
</el-row>
<!-- 补偿参数 -->
<el-row :gutter="10" class="mt-3">
<el-col :span="6">
<div class="param-item">
<div class="param-label">盐度补偿</div>
<div class="param-value-small">{{ device.salinityCompensation || '0.00' }}</div>
</div>
</el-col>
<el-col :span="6">
<div class="param-item">
<div class="param-label">温度补偿</div>
<div class="param-value-small">{{ device.temperatureCompensation || '0.00' }}</div>
</div>
</el-col>
<el-col :span="6">
<div class="param-item">
<div class="param-label">线性系数补偿</div>
<div class="param-value-small">{{ device.phaseCompensation || '0.00' }}</div>
</div>
</el-col>
<el-col :span="6">
<div class="param-item">
<div class="param-label">相位差补偿</div>
<div class="param-value-small">{{ device.phasedifCompensation || '0.00' }}</div>
</div>
</el-col>
</el-row>
<!-- 时间信息 -->
<el-row :gutter="10" class="mt-3">
<el-col :span="6">
<div class="param-item">
<div class="param-label">绑定时间</div>
<div class="param-value-small">{{ parseTime(device.bindTime, '{y}-{m}-{d}') || '-' }}</div>
</div>
</el-col>
<el-col :span="6">
<div class="param-item">
<div class="param-label">激活时间</div>
<div class="param-value-small">{{ parseTime((device as any).createTime, '{y}-{m}-{d}') || '-' }}</div>
</div>
</el-col>
<el-col :span="6">
<div class="param-item">
<div class="param-label">服务到期</div>
<div class="param-value-small text-danger">{{ parseTime(device.deadTime, '{y}-{m}-{d}') || '-' }}</div>
</div>
</el-col>
<el-col :span="6">
<div class="param-item">
<div class="param-label">校准时间</div>
<div class="param-value-small">{{ (device as any).correctTime ? parseTime((device as any).correctTime, '{y}-{m}-{d}') : '-' }}</div>
</div>
</el-col>
</el-row>
<!-- 告警配置 -->
<el-row :gutter="10" class="mt-3">
<el-col :span="6">
<div class="param-item">
<div class="param-label">溶解氧电话告警</div>
<dict-tag :options="[{label:'关',value:'0'},{label:'开',value:'1'}]" :value="String(device.oxyWarnCallOpen || 0)"/>
</div>
</el-col>
<el-col :span="6">
<div class="param-item">
<div class="param-label">溶解氧告警下限</div>
<div class="param-value-small">{{ device.oxyWarnLower || 0 }}</div>
</div>
</el-col>
<el-col :span="6">
<div class="param-item">
<div class="param-label">温度电话告警</div>
<dict-tag :options="[{label:'关',value:'0'},{label:'开',value:'1'}]" :value="String(device.tempWarnCallOpen || 0)"/>
</div>
</el-col>
<el-col :span="6">
<div class="param-item">
<div class="param-label">电压告警开关</div>
<dict-tag :options="[{label:'关',value:'0'},{label:'开',value:'1'}]" :value="String(device.voltageWarnOpen || 0)"/>
</div>
</el-col>
</el-row>
<el-row :gutter="10" class="mt-3">
<el-col :span="6">
<div class="param-item">
<div class="param-label">温度告警上限</div>
<div class="param-value-small">{{ device.tempWarnUpper || 0 }}</div>
</div>
</el-col>
<el-col :span="6">
<div class="param-item">
<div class="param-label">温度告警下限</div>
<div class="param-value-small">{{ device.tempWarnLower || 0 }}</div>
</div>
</el-col>
<el-col :span="6">
<div class="param-item">
<div class="param-label">电量电话告警</div>
<dict-tag :options="[{label:'关',value:'0'},{label:'开',value:'1'}]" :value="String(device.batteryWarnCallOpen || 0)"/>
</div>
</el-col>
<el-col :span="6">
<div class="param-item">
<div class="param-label">电量告警下限</div>
<div class="param-value-small">{{ device.batteryWarnLower || 0 }}</div>
</div>
</el-col>
</el-row>
<!-- 开关列表 -->
<div class="mt-4" v-if="getPondSwitchesByDevice(String(pond.id), device.id).length > 0">
<el-divider content-position="left">
<span class="text-sm font-bold text-gray-600">开关列表 ({{ getPondSwitchesByDevice(String(pond.id), device.id).length }})</span>
</el-divider>
<el-row :gutter="10">
<el-col
:span="12"
v-for="sw in getPondSwitchesByDevice(String(pond.id), device.id)"
:key="sw.id"
>
<div class="switch-item mb-3">
<div class="switch-header">
<div class="flex items-center gap-2">
<span class="switch-index">#{{ sw.index }}</span>
<span class="switch-name">{{ sw.switchName }}</span>
</div>
<el-tag :type="sw.isOpen === 1 ? 'success' : 'info'" size="small">
{{ sw.isOpen === 1 ? '开启' : '关闭' }}
</el-tag>
</div>
<div class="switch-details mt-3">
<el-row :gutter="8">
<el-col :span="12">
<div class="detail-item">
<span class="detail-label">测定电流</span>
<span class="detail-value text-primary">{{ sw.detectElectricValue || 0 }}A</span>
</div>
</el-col>
<el-col :span="12">
<div class="detail-item">
<span class="detail-label">测定电压</span>
<span class="detail-value text-blue-600">{{ sw.detectVoltageValue || 0 }}V</span>
</div>
</el-col>
</el-row>
<el-row :gutter="8" class="mt-2">
<el-col :span="12">
<div class="detail-item">
<span class="detail-label">额定电流</span>
<span class="detail-value">{{ sw.rateElectricValue || 0 }}A</span>
</div>
</el-col>
<el-col :span="12">
<div class="detail-item">
<span class="detail-label">接线方式</span>
<dict-tag :options="connect_voltage_type" :value="String(sw.connectVoltageType)" :show-value="true"/>
</div>
</el-col>
</el-row>
<el-row :gutter="8" class="mt-2">
<el-col :span="12">
<div class="detail-item">
<span class="detail-label">电流告警</span>
<dict-tag :options="[{label:'关',value:'0'},{label:'开',value:'1'}]" :value="String(sw.electricWarnOpen || 0)"/>
</div>
</el-col>
<el-col :span="12">
<div class="detail-item">
<span class="detail-label">操作时间</span>
<span class="detail-value-time">{{ sw.lastTurnTime ? parseTime(sw.lastTurnTime, '{m}-{d} {h}:{i}') : '-' }}</span>
</div>
</el-col>
</el-row>
</div>
</div>
</el-col>
</el-row>
</div>
</el-card>
</div>
</div>
</el-tab-pane>
</el-tabs>
<div v-if="userPondList.length === 0" class="text-center py-8 text-gray-500">
该用户暂无塘口信息
</div>
</template>
<template #footer>
<div class="dialog-footer">
<el-button @click="pondDialog.visible = false"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="AquUser" lang="ts">
import { listAquUser, getAquUser, delAquUser, addAquUser, updateAquUser } from '@/api/fishery/aquUser';
import { listPond } from '@/api/fishery/pond';
import { listDevice } from '@/api/fishery/device';
import { listDeviceSwitch } from '@/api/fishery/deviceSwitch';
import { listUserRelation } from '@/api/fishery/userRelation';
import { AquUserVO, AquUserQuery, AquUserForm } from '@/api/fishery/aquUser/types';
import { PondVO } from '@/api/fishery/pond/types';
import { DeviceVO } from '@/api/fishery/device/types';
import { DeviceSwitchVO } from '@/api/fishery/deviceSwitch/types';
import { UserRelationVO } from '@/api/fishery/userRelation/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { connect_voltage_type } = toRefs<any>(proxy?.useDict('connect_voltage_type'));
const aquUserList = ref<AquUserVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const queryFormRef = ref<ElFormInstance>();
const aquUserFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
// 塘口信息相关
const pondDialog = reactive<DialogOption>({
visible: false,
title: ''
});
const userPondList = ref<PondVO[]>([]);
const currentUserId = ref<string | number>('');
const activePondTab = ref<string>('');
const pondDeviceMap = ref<Map<string | number, DeviceVO[]>>(new Map());
const pondSwitchMap = ref<Map<string | number, DeviceSwitchVO[]>>(new Map());
const pondLoading = ref(false); // 塘口数据加载状态
const deviceLoading = ref(false); // 设备数据加载状态
const loadedPonds = ref<Set<string | number>>(new Set()); // 已加载设备数据的塘口ID集合
// 报警电话相关
const warnPhoneDialog = reactive<DialogOption>({
visible: false,
title: ''
});
const warnPhones = ref<string[]>([]);
// 子账号相关
const subAccountDialog = reactive<DialogOption>({
visible: false,
title: ''
});
const subAccountList = ref<AquUserVO[]>([]);
const subAccountLoading = ref(false);
const currentParentUser = ref<AquUserVO | null>(null);
const initFormData: AquUserForm = {
id: undefined,
userName: undefined,
mobilePhone: undefined,
province: undefined,
city: undefined,
district: undefined,
warnPhoneJson: undefined,
accessToken: undefined,
refreshToken: undefined,
wxOpenId: undefined,
wxSessionKey: undefined,
isManager: undefined,
wxUnionId: undefined,
tecentOpenId: undefined,
title: undefined,
hasScreen: undefined,
remark: undefined
};
const data = reactive<PageData<AquUserForm, AquUserQuery>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
userName: undefined,
mobilePhone: undefined,
province: undefined,
city: undefined,
district: undefined,
warnPhoneJson: undefined,
params: {}
},
rules: {
userName: [{ required: true, message: '用户名不能为空', trigger: 'blur' }],
mobilePhone: [{ required: true, message: '手机号不能为空', trigger: 'blur' }],
warnPhoneJson: [{ required: true, message: '报警电话不能为空', trigger: 'blur' }]
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询养殖账号列表 */
const getList = async () => {
loading.value = true;
const res = await listAquUser(queryParams.value);
aquUserList.value = res.rows;
total.value = res.total;
loading.value = false;
};
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
};
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
aquUserFormRef.value?.resetFields();
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: AquUserVO[]) => {
ids.value = selection.map((item) => item.id);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
/** 新增按钮操作 */
const handleAdd = () => {
reset();
dialog.visible = true;
dialog.title = '添加养殖账号';
};
/** 修改按钮操作 */
const handleUpdate = async (row?: AquUserVO) => {
reset();
const _id = row?.id || ids.value[0];
const res = await getAquUser(_id);
Object.assign(form.value, res.data);
dialog.visible = true;
dialog.title = '修改养殖账号';
};
/** 提交按钮 */
const submitForm = () => {
aquUserFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
if (form.value.id) {
await updateAquUser(form.value).finally(() => (buttonLoading.value = false));
} else {
await addAquUser(form.value).finally(() => (buttonLoading.value = false));
}
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row?: AquUserVO) => {
const _ids = row?.id || ids.value;
await proxy?.$modal.confirm('是否确认删除养殖账号编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
await delAquUser(_ids);
proxy?.$modal.msgSuccess('删除成功');
await getList();
};
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download(
'fishery/aquUser/export',
{
...queryParams.value
},
`aquUser_${new Date().getTime()}.xlsx`
);
};
/** 查看子账号 */
const handleViewSubAccounts = async (row: AquUserVO) => {
currentParentUser.value = row;
subAccountDialog.title = `${row.userName} 的子账号列表`;
subAccountDialog.visible = true;
subAccountLoading.value = true;
try {
// 查询该用户的子账号关系
const relationRes = await listUserRelation({
pageNum: 1,
pageSize: 1000,
parentUserId: row.id
});
const relations = relationRes.rows || relationRes.data || [];
if (relations.length === 0) {
subAccountList.value = [];
return;
}
// 获取所有子账号ID
const childUserIds = relations.map((r: UserRelationVO) => r.childUserId);
// 查询子账号详细信息
const userRes = await listAquUser({
pageNum: 1,
pageSize: 1000
});
const allUsers = userRes.rows || [];
// 筛选出子账号
subAccountList.value = allUsers.filter((u: AquUserVO) => childUserIds.includes(u.id));
} catch (error) {
console.error('加载子账号失败:', error);
proxy?.$modal.msgError('加载子账号失败,请重试');
subAccountList.value = [];
} finally {
subAccountLoading.value = false;
}
};
/** 查看报警电话 */
const handleViewWarnPhones = (row: AquUserVO) => {
warnPhoneDialog.title = `${row.userName} 的报警电话`;
// 解析报警电话JSON字符串
try {
if (row.warnPhoneJson) {
const phonesData = JSON.parse(row.warnPhoneJson);
// 如果是数组,直接使用;如果是对象,提取值
if (Array.isArray(phonesData)) {
warnPhones.value = phonesData.filter(phone => phone && phone.trim());
} else if (typeof phonesData === 'object') {
warnPhones.value = Object.values(phonesData)
.filter(phone => phone && String(phone).trim())
.map(phone => String(phone));
} else {
// 如果是逗号分隔的字符串
warnPhones.value = row.warnPhoneJson.split(',').filter(phone => phone && phone.trim());
}
} else {
warnPhones.value = [];
}
} catch (e) {
// 如果解析失败,尝试按逗号分隔
if (row.warnPhoneJson) {
warnPhones.value = row.warnPhoneJson.split(',').filter(phone => phone && phone.trim());
} else {
warnPhones.value = [];
}
}
warnPhoneDialog.visible = true;
};
/** 获取塘口设备数量 */
const getPondDeviceCount = (pondId: string | number): number => {
return pondDeviceMap.value.get(pondId)?.length || 0;
};
/** 获取塘口开关数量 */
const getPondSwitchCount = (pondId: string | number): number => {
const switches = pondSwitchMap.value.get(pondId) || [];
return switches.length;
};
/** 根据设备类型获取塘口设备列表 */
const getPondDevicesByType = (pondId: string | number, types: number[]): DeviceVO[] => {
console.log(`🔍 调用 getPondDevicesByType - 塘口ID: ${pondId}, 类型: ${types}`);
const devices = pondDeviceMap.value.get(pondId) || [];
console.log(` - 从 Map 中获取的设备数量: ${devices.length}`);
console.log(` - 设备详情:`, devices);
const filtered = devices.filter(d => {
const match = types.includes(d.deviceType);
console.log(` - 设备 ${d.deviceName} (type: ${d.deviceType}) 匹配: ${match}`);
return match;
});
console.log(` - 过滤后的设备数量: ${filtered.length}`);
console.log(` - 过滤后的设备:`, filtered);
return filtered;
};
/** 根据设备ID获取开关列表 */
const getPondSwitchesByDevice = (pondId: string | number, deviceId: string | number): DeviceSwitchVO[] => {
const switches = pondSwitchMap.value.get(pondId) || [];
const filtered = switches.filter(sw => sw.deviceId === deviceId);
// 调试:打印开关数据
if (filtered.length > 0) {
console.log('开关数据:', filtered);
filtered.forEach(sw => {
console.log(`开关 ${sw.switchName} 的接线方式值:`, sw.connectVoltageType, '类型:', typeof sw.connectVoltageType);
});
}
return filtered;
};
/** 加载指定塘口的设备和开关数据 */
const loadPondDevices = async (pondId: string | number) => {
console.log('===== 开始加载塘口设备数据 =====');
console.log('塘口ID:', pondId);
console.log('塘口ID类型:', typeof pondId);
console.log('已加载塘口集合:', Array.from(loadedPonds.value));
// 如果已经加载过,直接返回
if (loadedPonds.value.has(pondId)) {
console.log('该塘口已加载过,跳过');
return;
}
deviceLoading.value = true;
try {
console.log('发起设备和开关数据请求...');
// 并行查询该塘口的设备和开关
const [deviceRes, switchRes] = await Promise.all([
listDevice({
pageNum: 1,
pageSize: 1000,
pondId: pondId
}),
listDeviceSwitch({
pageNum: 1,
pageSize: 1000,
pondId: pondId
})
]);
console.log('设备数据响应:', deviceRes);
console.log('开关数据响应:', switchRes);
// 更新设备数据(即使为空也要设置,避免重复查询)
const devices = deviceRes.rows || deviceRes.data || [];
console.log('解析出的设备列表:', devices);
console.log('设备数量:', devices.length);
// 创建新的 Map 以触发 Vue 响应式更新
const newDeviceMap = new Map(pondDeviceMap.value);
newDeviceMap.set(pondId, devices);
pondDeviceMap.value = newDeviceMap;
console.log('设备Map已更新新的Map:', pondDeviceMap.value);
// 更新开关数据(即使为空也要设置)
const switches = switchRes.rows || switchRes.data || [];
console.log('解析出的开关列表:', switches);
console.log('开关数量:', switches.length);
// 创建新的 Map 以触发 Vue 响应式更新
const newSwitchMap = new Map(pondSwitchMap.value);
newSwitchMap.set(pondId, switches);
pondSwitchMap.value = newSwitchMap;
console.log('开关Map已更新新的Map:', pondSwitchMap.value);
// 标记为已加载
loadedPonds.value.add(pondId);
console.log(`✅ 塘口 ${pondId} 加载完成: ${devices.length} 个设备, ${switches.length} 个开关`);
console.log('当前设备Map:', pondDeviceMap.value);
console.log('===== 加载完成 =====');
} catch (error) {
console.error('❌ 加载塘口设备数据失败:', error);
proxy?.$modal.msgError('加载设备数据失败,请重试');
} finally {
deviceLoading.value = false;
}
};
/** 监听塘口Tab切换 */
watch(activePondTab, async (newPondId, oldPondId) => {
console.log('\n===== Tab切换事件触发 =====');
console.log('从塘口', oldPondId, '切换到塘口', newPondId);
if (newPondId) {
await loadPondDevices(newPondId);
}
console.log('===== Tab切换处理完成 =====\n');
});
/** 查看用户塘口信息 */
const handleViewPonds = async (row: AquUserVO) => {
currentUserId.value = row.id;
pondDialog.title = `${row.userName} 的塘口信息`;
pondDialog.visible = true; // 先打开弹窗
pondLoading.value = true; // 开启加载状态
// 清空之前的数据
userPondList.value = [];
pondDeviceMap.value.clear();
pondSwitchMap.value.clear();
loadedPonds.value.clear();
try {
// 只查询塘口列表,不查询设备数据
const pondRes = await listPond({
pageNum: 1,
pageSize: 1000,
userId: row.id
});
userPondList.value = pondRes.rows || [];
// 设置第一个塘口为激活状态,并显式加载该塘口的设备数据
if (userPondList.value.length > 0) {
const firstPondId = String(userPondList.value[0].id);
activePondTab.value = firstPondId;
// 显式加载第一个塘口的设备数据,不依赖 watch 触发
await loadPondDevices(firstPondId);
}
} catch (error) {
console.error('加载塘口信息失败:', error);
proxy?.$modal.msgError('加载塘口信息失败,请重试');
} finally {
pondLoading.value = false; // 关闭加载状态
}
};
onMounted(() => {
getList();
});
</script>
<style scoped lang="scss">
// 加载容器
.loading-container {
min-height: 300px;
display: flex;
align-items: center;
justify-content: center;
}
// 塘口弹窗样式
:deep(.pond-dialog) {
.el-dialog {
height: 85vh !important;
min-height: 85vh !important;
max-height: 85vh !important;
margin: 0 !important;
display: flex !important;
flex-direction: column !important;
}
.el-dialog__header {
flex-shrink: 0 !important;
padding: 15px 20px;
}
.el-dialog__body {
flex: 1 !important;
overflow-y: auto !important;
padding: 15px 20px;
min-height: 0 !important;
}
.el-dialog__footer {
flex-shrink: 0 !important;
padding: 10px 20px;
}
.el-tabs--border-card {
border: none;
box-shadow: none;
height: 100% !important;
display: flex !important;
flex-direction: column !important;
}
.el-tabs__header {
flex-shrink: 0 !important;
}
.el-tabs__content {
flex: 1 !important;
overflow-y: auto !important;
padding: 10px;
min-height: 0 !important;
}
}
.pond-info {
:deep(.el-descriptions) {
.el-descriptions__body {
.el-descriptions__cell {
padding: 6px 10px;
}
}
}
}
.device-card {
border: 2px solid #e4e7ed;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
transition: all 0.3s ease;
background: #fff;
height: auto !important;
min-height: auto !important;
&:hover {
border-color: #409eff;
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.15);
transform: translateY(-2px);
}
:deep(.el-card__header) {
padding: 12px 18px;
font-size: 16px;
font-weight: 600;
background: linear-gradient(to right, #f8f9fa, #ffffff);
border-bottom: 2px solid #e4e7ed;
}
:deep(.el-card__body) {
padding: 15px 18px;
height: auto !important;
max-height: none !important;
overflow: visible !important;
}
}
.param-item {
text-align: center;
padding: 5px 0;
border-radius: 4px;
transition: background-color 0.2s;
&:hover {
background-color: #f5f7fa;
}
.param-label {
font-size: 13px;
color: #909399;
margin-bottom: 4px;
line-height: 1.4;
font-weight: 500;
}
.param-value {
font-size: 18px;
font-weight: 700;
color: #303133;
line-height: 1.5;
}
.param-value-small {
font-size: 14px;
color: #606266;
line-height: 1.4;
font-weight: 500;
}
}
.text-primary {
color: #409eff;
}
.text-danger {
color: #f56c6c;
font-weight: 600;
}
// 开关卡片样式
.switch-item {
background: #f8f9fa;
border: 1px solid #e4e7ed;
border-radius: 6px;
padding: 12px 14px;
transition: all 0.3s ease;
&:hover {
border-color: #409eff;
background: #ecf5ff;
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.15);
}
.switch-header {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 10px;
border-bottom: 1px solid #e4e7ed;
.switch-index {
font-size: 12px;
font-weight: 600;
color: #909399;
background: #e4e7ed;
padding: 2px 6px;
border-radius: 3px;
}
.switch-name {
font-size: 15px;
font-weight: 600;
color: #303133;
}
}
.switch-details {
.detail-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 6px 8px;
background: #ffffff;
border-radius: 4px;
.detail-label {
font-size: 12px;
color: #909399;
font-weight: 500;
}
.detail-value {
font-size: 13px;
color: #606266;
font-weight: 600;
}
.detail-value-time {
font-size: 12px;
color: #909399;
}
}
}
}
</style>