Files
fishery-app/src/pages/main/home.vue
2026-03-02 23:36:53 +08:00

1726 lines
50 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>
<!-- <nut-row class="home_home" :style="{ marginTop: navBarHeight + 'px' }"> -->
<nut-row class="home_home">
<!-- 固定区域开始 -->
<view class="sticky-header">
<CustomNavigationBar @refresh="resUsetInfo" />
<!-- 头部操作按钮区 -->
<nut-col
:span="24"
class="view_f_between header-btn-container"
>
<view>
<nut-button type="primary" class="btn header-btn" size="small" @click="changeMode">
<template #icon>
<IconFont :name="change" size="18" />
</template>
<text class="font_32 c_fff">切换模式</text>
</nut-button>
</view>
<view>
<nut-button
type="primary"
class="btn header-btn font_28"
size="small"
open-type="contact"
>
<template #icon>
<IconFont :name="phone" size="18" />
</template>
<text class="font_32">联系工程师</text>
</nut-button>
</view>
<view>
<nut-button
type="primary"
class="btn header-btn header-btn-warning font_28"
size="small"
@click="openNotice"
>
<template #icon>
<Tips size="18" />
</template>
<text class="font_32">系统公告</text>
</nut-button>
</view>
</nut-col>
</view>
<!-- 固定区域结束 -->
<nut-col :span="24" class="container" :class="showChart ? 'ov' : ''">
<nut-config-provider :theme-vars="themeVars">
<nut-row class="body" :catch-move="false">
<!-- 消息通知 -->
<nut-col
:span="24"
class="pond m_t_15 view_f_between"
:style="{ padding: '0 20rpx' }"
@click="toPay"
>
<view :style="{ width: '100%' }">
<nut-noticebar
background="rgba(255, 255, 255, 0.7)"
class="m_t_15"
:scrollable="false"
v-show="deadList.length > 0 && store.getUnLogin != 1"
>
<template #left-icon>
<img :src="noticeImg" style="width: 32rpx; height: 32rpx" />
</template>
<text class="content">{{ `${deadList.length}个设备即将到期` }}</text>
</nut-noticebar>
</view>
</nut-col>
<!-- 塘口列表标题 -->
<nut-col
:span="24"
class="pond m_t_15 view_f_between"
:style="{ padding: '0 20rpx' }"
>
<view class="font_34 f_weight">塘口列表</view>
<view id="addPondBtn" class="view_f" :style="{ gap: '10rpx' }">
<nut-button
type="primary"
class="btn font_28"
size="small"
@click="addPond"
color="#E7F8F9"
:style="{ color: '#09B8C2 !important' }"
>
<template #icon>
<Uploader size="18" />
</template>
<text class="font_32">新建塘口</text>
</nut-button>
</view>
</nut-col>
<!-- 模式一 -->
<nut-col
:span="24"
v-if="mode == 0 && store.getUnLogin != 1"
:style="{ padding: '0rpx 20rpx 20rpx 20rpx' }"
>
<scroll-view
:scroll-y="true"
:style="{
height: m1_height * 2 + 'rpx',
'-webkit-overflow-scrolling': 'touch',
'overflow-scrolling': 'touch',
}"
>
<nut-row v-if="pondList.length > 0" class="pond m_t_15">
<nut-col
:span="24"
v-for="(item, index) in pondList"
:key="index"
class="m_d_15"
@click="toPondInfo(item)"
>
<nut-cell-group>
<nut-cell>
<!-- 头部标题 -->
<view class="view_f_between">
<view class="font_32 f_weight c_222">{{ item.pondName }}</view>
<view>
<nut-tag class="tag font_28 m_r_15" v-if="item.status == 1">
正常</nut-tag
>
<nut-tag
class="tagErr font_28 m_r_15"
v-else-if="item.status == 2"
>
{{ item.warnCodeInfo.warnDescription }}</nut-tag
>
<nut-tag
class="font_28 m_r_15"
color="#E9E9E9"
text-color="#999999"
v-else
>
空闲</nut-tag
>
</view>
</view>
</nut-cell>
<!-- 中部监测信息 -->
<nut-cell>
<nut-row>
<nut-col :span="24" v-if="item.deviceCount">
<nut-grid :column-num="item.num" square border>
<nut-grid-item class="grid">
溶解氧<text class="font_20">Mg/L</text>
<template #text>
<text
v-if="!item.isAllDead"
class="h_grid_content_5"
:class="
!item.up
? ''
: alarmJudgeCode(item.warnCodeInfo.warnCode, 7)
? 'c_E22323'
: ''
"
>{{
item.up
? toDecimal(item.valueDissolvedOxygen, 2)
: "--"
}}</text
>
<text v-else class="h_grid_content_5">--</text>
</template>
</nut-grid-item>
<nut-grid-item class="grid">
水温<text class="font_20">()</text>
<template #text>
<text
v-if="!item.isAllDead"
class="h_grid_content_5"
:class="
!item.up
? ''
: alarmJudgeCode(item.warnCodeInfo.warnCode, 9) ||
alarmJudgeCode(item.warnCodeInfo.warnCode, 8)
? 'c_E22323'
: ''
"
>{{
item.up ? toDecimal(item.valueTemperature, 2) : "--"
}}</text
>
<text v-else class="h_grid_content_5">--</text>
</template>
</nut-grid-item>
<nut-grid-item class="grid">
饱和度<text class="font_20">(%)</text>
<template #text>
<text
v-if="!item.isAllDead"
class="h_grid_content_5"
:class="
!item.up
? ''
: alarmJudgeCode(item.warnCodeInfo.warnCode, 10)
? 'c_E22323'
: ''
"
>{{
item.up ? toDecimal(item.valueSaturability, 2) : "--"
}}</text
>
<text v-else class="h_grid_content_5">--</text>
</template>
</nut-grid-item>
<nut-grid-item class="grid" v-show="item.isPh">
PH<text class="font_20">(Ph)</text>
<template #text>
<text class="h_grid_content_5" v-if="!item.isAllDead">{{
item.up ?toDecimal(item.valuePH, 2):'--'
}}</text>
<text v-else class="h_grid_content_5">--</text>
</template>
</nut-grid-item>
<nut-grid-item class="grid" v-show="item.isSa">
盐度<text class="font_20">()</text>
<template #text>
<text class="h_grid_content_5" v-if="!item.isAllDead">{{
item.up ?toDecimal(item.valueSalinity, 2):'--'
}}</text>
<text v-else class="h_grid_content_5">--</text>
</template>
</nut-grid-item>
</nut-grid>
</nut-col>
<nut-col v-else>
<view class="font_28">未绑定设备</view>
</nut-col>
</nut-row>
</nut-cell>
<nut-cell>
<nut-row>
<nut-col :span="10">
<view class="font_28"
>启用开关数量<text class="c_17B4B2">{{
item.switchOpenCount
}}</text>
</view>
</nut-col>
<nut-col :span="14" class="c_E22323" v-if="item.ErrorMessage">
{{ item.ErrorMessage }}
</nut-col>
</nut-row>
</nut-cell>
</nut-cell-group>
</nut-col>
</nut-row>
<view v-else>
<nut-cell>
<nut-row>
<nut-col :span="24" v-if="store.getUnLogin == 1">
<nut-empty description="">
<template #image>
<img :src="pond404" />
</template>
<div style="margin-top: 10px">
<nut-button type="primary" @click="toLogin"
>登录/注册</nut-button
>
</div>
</nut-empty>
</nut-col>
<nut-col :span="24" v-else>
<nut-empty description="暂无塘口">
<template #image>
<img :src="pond404" />
</template>
</nut-empty>
</nut-col>
</nut-row>
</nut-cell>
</view>
</scroll-view>
</nut-col>
<!-- 模式二 -->
<nut-col
class="pond_mode2 m_t_10"
:span="24"
v-if="mode == 1 && store.getUnLogin != 1"
:style="{ padding: '0rpx 20rpx 20rpx 20rpx' }"
>
<nut-row v-if="pondList.length > 0">
<!-- 切换选择塘口 -->
<nut-col :span="24">
<view :style="{ width: '100%' }">
<nut-radio-group
v-model="selPond"
direction="horizontal"
:style="{ flexWrap: 'wrap', gap: '10rpx' }"
>
<nut-radio
:label="item.id"
shape="button"
v-for="item in pondList"
:key="item.id"
>{{ item.pondName }}</nut-radio
>
</nut-radio-group>
</view>
</nut-col>
<nut-col
:span="24"
class="topDiv m_t_5"
:style="{ height: showChart ? '100%' : '100%' }"
>
<pond-info
v-model="selPond"
@getPondInfo="getPondInfo"
@addDevice="addDevice"
ref="pondChildInfo"
:type="1"
/>
</nut-col>
</nut-row>
<view v-else>
<nut-cell>
<nut-col :span="24" v-if="store.getUnLogin == 1">
<nut-empty description="">
<template #image>
<img :src="pond404" />
</template>
<div style="margin-top: 10px">
<nut-button type="primary" @click="toLogin">登录/注册</nut-button>
</div>
</nut-empty>
</nut-col>
<nut-col :span="24" v-else>
<nut-empty description="暂无塘口">
<template #image>
<img :src="pond404" />
</template>
</nut-empty>
</nut-col>
</nut-cell>
</view>
</nut-col>
<!-- 游客模式 -->
<nut-col
class="pond_mode2 m_t_10"
:span="24"
v-if="store.getUnLogin == 1"
:style="{ padding: '0rpx 20rpx 20rpx 20rpx' }"
>
<nut-cell>
<nut-col :span="24">
<nut-empty description="">
<template #image>
<img :src="pond404" />
</template>
<div style="margin-top: 10px">
<nut-button type="primary" @click="toLogin">登录/注册</nut-button>
</div>
</nut-empty>
</nut-col>
</nut-cell>
</nut-col>
</nut-row>
</nut-config-provider>
</nut-col>
<!-- 添加设备弹框 -->
<nut-dialog v-model:visible="bandDevShow" text-align="left" no-footer>
<view class="d_body" :style="{ paddingBottom: '0px' }">
<nut-row>
<nut-col :span="24">
<view class="view_f_between_2">
<view class="d_title">绑定设备</view>
<view class="font_36 c_def" @click="addDev">添加设备</view>
</view>
</nut-col>
<nut-col :span="24">
<nut-divider class="divider" />
</nut-col>
<nut-col :span="24" class="font_28 c_888">
如将控制器开关转移至其它塘口该开关的定时设置将保留自动增氧设置将失效如需自动增氧需要重新进行设置
</nut-col>
<nut-col :span="24" v-if="deviceList.length > 0">
<next-tree
ref="qiantree"
:selectParent="false"
labelKey="deviceName"
valueKey="id"
:multiple="true"
:treeData="deviceList"
@confirm="onconfirm"
childrenKey="listSwitch"
border
@cancel="oncancel"
showChild
isOpen
/>
</nut-col>
<nut-col :span="24" v-else>
<nut-empty description="暂无设备">
<template #image>
<img :src="pond404" />
</template>
</nut-empty>
</nut-col>
</nut-row>
</view>
</nut-dialog>
<!-- 弹出层提示 -->
<nut-toast :msg="state.msg" v-model:visible="state.show" :type="state.type" />
<!-- 引导 -->
<nut-tour
v-model="showTour"
:steps="steps"
type="tile"
location="bottom-end"
></nut-tour>
<!-- 弹出添加设备提示 -->
<nut-config-provider :theme-vars="themeVars2">
<nut-dialog v-model:visible="alertShow" text-align="left" no-footer>
<view :style="{ height: '100%' }" class="d_body">
<nut-row>
<nut-col :span="24" class="font_36 f_weight c_222">
请选择添加设备方式
</nut-col>
<nut-col :span="24">
<nut-divider class="divider" />
</nut-col>
<nut-col :span="24">
<nut-button
block
type="primary"
size="large"
:style="{
fontWeight: 'bold',
fontSize: '14px !important',
marginTop: '20px',
}"
@click="goScan"
>扫描二维码</nut-button
>
<nut-button
block
type="primary"
size="large"
:style="{
fontWeight: 'bold',
fontSize: '14px !important',
marginTop: '20px',
}"
@click="goInput"
>输入设备号</nut-button
>
</nut-col>
</nut-row>
</view>
</nut-dialog>
<!-- 提醒扫码异常 -->
<nut-dialog v-model:visible="errorShow" text-align="left" no-footer>
<view :style="{ height: '100%' }">
<view class="d_body">
<nut-row>
<nut-col :span="24" class="d_title" :style="{ textAlign: 'center' }">
扫描识别失败请前往手动录入
</nut-col>
</nut-row>
</view>
<view class="d_btn" :style="{ position: 'absolute' }">
<nut-row>
<nut-col
:span="12"
:style="{ border: '1px solid #eee', color: '#222' }"
@click="errorShow = false"
>
取消
</nut-col>
<nut-col :span="12" :style="{ border: '1px solid #eee', color: '#15BBC5' }">
<nut-button type="primary" shape="square" block @click="goInput"
>确定</nut-button
>
</nut-col>
</nut-row>
</view>
</view>
</nut-dialog>
</nut-config-provider>
<nut-config-provider :theme-vars="themeVars3">
<!-- 公告列表信息 -->
<nut-dialog
v-model:visible="noticeOpen"
text-align="left"
no-footer
:pop-style="{ maxWidth: '100%', maxHeight: '100%' }"
>
<nut-row>
<nut-col
:span="24"
:style="{ textAlign: 'left', padding: '20rpx' }"
@click="closeNotice"
>
<CircleClose />
</nut-col>
</nut-row>
<nut-tabs
v-model="noticeValue"
direction="vertical"
title-scroll
style="height: 280px"
name="tabName"
v-if="noticeRows.length > 0"
>
<nut-tab-pane
v-for="(item, index) in noticeRows"
:key="index"
:title="`${item.noticeTitle}`"
:pane-key="index"
>
<nut-row>
<nut-col :span="24" :style="{ height: '200px' }">
<view v-html="item.noticeContent"></view>
</nut-col>
<nut-col :span="24">
<view :style="{ textAlign: 'right' }"
>发布时间{{ formatDateString(item.createTime, "yyyy-mm-dd") }}</view
>
<!-- <div>有效期至{{formatDateMin(item.deadTime)}}</div> -->
</nut-col>
</nut-row>
</nut-tab-pane>
</nut-tabs>
<nut-empty description="暂无公告" v-else></nut-empty>
</nut-dialog>
</nut-config-provider>
<!-- 塘口详情 -->
<nut-popup v-model:visible="pondShow" position="bottom">
<view class="d_body">
<nut-row>
<nut-col :span="22" class="d_title" :style="{ textAlign: 'center' }">{{
selPondName_2
}}</nut-col>
<nut-col :span="2" @click="closePond">
<Close />
</nut-col>
<nut-col :span="24">
<nut-divider class="divider" />
</nut-col>
<nut-col :span="24">
<pond-info
v-model="selPond_2"
@addDevice="addDevice"
ref="pondChildInfo"
:type="2"
/>
</nut-col>
</nut-row>
</view>
</nut-popup>
</nut-row>
</template>
<script setup lang="ts" name="Home">
import CustomNavigationBar from "@/components/custom-navigation-bar/index";
import nextTree from "@/components/tree/index";
import pondInfo from "@/components/other/index";
import { IconFont, Uploader, Close, CircleClose, Tips } from "@nutui/icons-vue-taro";
import { imgUrl } from "@/utils/imgUrl";
import Taro from "@tarojs/taro";
import QQMapWX from "@/utils/js-sdk/map/qqmap-wx-jssdk.min.js";
import { getPond1, noticeList, deviceDead } from "@/api/home";
import { PondListType } from "./types";
import { stateType } from "@/utils/types";
import { toDecimal, alarmJudgeCode, sortByField, formatDateString } from "@/utils/tools";
import { msgWarn } from "@/api/msg";
import { allDeviceList, bandDeviceToPond, deviceScan } from "@/api/device";
import { useRootUserStore } from "@/store/index";
definePageConfig({
navigationStyle: "custom",
disableScroll: false,
enablePullDownRefresh: true,
backgroundTextStyle: "dark",
});
const store: any = useRootUserStore();
// 防止重复检查登录状态
let isCheckingLogin = false;
// 防止重复弹窗
let isShowingLoginDialog = false;
// 导航拦信息
const navBarHeight = ref();
const windowInfo = Taro.getWindowInfo();
const menuButtonInfo = Taro.getMenuButtonBoundingClientRect();
const statusBarHeight: number = ref(windowInfo.statusBarHeight);
navBarHeight.value =
menuButtonInfo.bottom -
statusBarHeight.value +
(menuButtonInfo.top - statusBarHeight.value);
// 其他
const c1 = `${imgUrl}c1.png`;
const c1_n = `${imgUrl}c1_n.png`;
const c2 = `${imgUrl}c2.png`;
const c2_n = `${imgUrl}c2_n.png`;
const c3 = `${imgUrl}c3.png`;
const c3_n = `${imgUrl}c3_n.png`;
const c_def = `${imgUrl}c_def.png`;
const location = `${imgUrl}location.png`;
const change = `${imgUrl}change.png`;
const phone = `${imgUrl}tel.png`;
const setting = `${imgUrl}setting.png`;
const noticeImg = `${imgUrl}noticeImg.png`;
const pond404 = `${imgUrl}zanwurenwu.png`;
// const notice = ref("有设备服务快到期,请尽快续费");
const themeVars = ref({
noticebarLefticonMargin: "0px 5px",
noticebarBoxPadding: "0px 5px",
gridItemTextColor: "#222222",
gridItemTextFontSize: "28rpx",
gridItemTextMargin: "10px",
gridItemContentBgColor: "#F5F9FC",
gridItemContentPadding: "0px",
radioButtonFontSize: "28rpx",
radioLabelFontColor: "#000",
radioLabelFontActiveColor: "#fff",
radioLabelButtonBackground: "#09B8C2",
radioLabelButtonBorderColor: "#09B8C2",
switchWidth: "112rpx",
switchHeight: "56rpx",
switchLineHeight: "56rpx",
switchInsideHeight: "40rpx",
switchInsideWidth: "40rpx",
switchInsideOpenTransform: "translateX(155%)",
switchInsideCloseTransform: "translateX(20%)",
noticebarAcrossHeight: "60rpx",
gridBorderColor: "#E5EEF4",
cellAfterBorderBottom: "0px",
cellPadding: "20rpx",
tabsHorizontalTitlesHeight: "60rpx",
});
const themeVars2 = ref({
cellBoxShadow: "0px",
cellPadding: "0",
popupBorderRadius: "10rpx",
cellDescColor: "#222222",
});
const themeVars3 = ref({
dialogWidth: "92vw",
});
let qqmapsdk = new QQMapWX({
key: "75RBZ-WQSYB-6KEUB-NCZME-D62T7-YIF4G",
});
// 选中的塘口id
const selPond = ref<number | string | undefined>(undefined);
// 多糖选中塘口id
const selPond_2 = ref<number | string | undefined>(undefined);
const selPondName_2 = ref<string | undefined>("塘口详情");
const visControl = ref(false);
// 展示塘口详情
const pondShow = ref(false);
// 控制器开关
const showChart = ref(true);
const pondList = ref<PondListType[]>([]);
// 子组件
const pondChildInfo = ref(null);
// 控制器列表
const mode = ref(1);
const state: stateType = reactive({
msg: "",
type: "text",
center: true,
show: false,
});
const bandDevShow = ref<boolean>(false);
const deviceList = ref<any[]>([]);
const deadList = ref<any[]>([]);
const bandDeviceList = ref<any[]>([]);
const showTour = ref<boolean>(false);
const steps = ref([
{
content: "请添加塘口",
target: "addPondBtn",
},
]);
// 当前控制器下开关列表
const controlInfo = ref<any>({
deviceName: undefined,
id: undefined,
listSwitch: [],
});
const m1_height = ref(0);
// 添加设备信息
const alertShow = ref(false);
const errorShow = ref(false);
const params = reactive({
pondId: [],
pondName: "",
devType: "1",
devNum: "",
});
const city = ref("青岛市");
// 消息2分钟轮询定时器
const timer = ref<any>(null);
const notice = ref(null);
const noticeRows = ref<any[]>([]);
const noticeOpen = ref<boolean>(false);
const noticeValue = ref(0);
/** -----------------method start----------------- */
/** 下拉刷新 */
Taro.usePullDownRefresh(() => {
Taro.startPullDownRefresh({
complete: () => {
// 默认
pond1();
getNotice();
getDeviceDead();
bandDevShow.value = false;
if (mode.value == 1) {
pond2();
}
// 模式一滚动条高度
m1_height.value = Taro.getWindowInfo().windowHeight - 200;
nextTick(() => {
if (pondChildInfo.value) {
if (alertShow.value) {
pondChildInfo.value.hideTour(alertShow.value);
}
pondChildInfo.value.refresh();
}
});
Taro.stopPullDownRefresh();
},
});
});
Taro.useDidShow(() => {
// 检查登录状态
checkLoginStatus();
startPolling();
// 默认
pond1();
getNotice();
getDeviceDead();
bandDevShow.value = false;
if (mode.value == 1) {
pond2();
}
// 刷新子组件数据
nextTick(() => {
if (pondChildInfo.value) {
pondChildInfo.value.refresh();
}
});
if (store.getUnLogin != 1 && store.getLoginStatus != 1) {
// 位置解析
// Taro.getLocation({
// type: "gcj02",
// success: function (res) {
// const latitude = res.latitude;
// const longitude = res.longitude;
// qqmapsdk.reverseGeocoder({
// location: `${latitude},${longitude}`,
// success: (res) => {
// const address_component = res.result.address_component;
// city.value = address_component
// ? address_component.city + "-" + address_component.district
// : "青岛市";
// },
// });
// },
// });
}
// 模式一滚动条高度
m1_height.value = Taro.getWindowInfo().windowHeight - 200;
nextTick(() => {
if (alertShow.value) {
pondChildInfo.value.hideTour(alertShow.value);
}
});
const tmplIds = [
"g_FpveCvmPtV4ulFHTaUYnwng6Nke3AmEAspg16D8Ow",
"R0qEUHhf5e6g21fIvC_v7x49qHfe2pCSJir9YG2haRc",
];
if (store.getUnLogin != 1) {
}
});
Taro.useDidHide(() => {
stopPolling();
});
Taro.useUnload(() => {
stopPolling();
});
// 查询公告
function getNotice(openType = 0) {
noticeList().then((res) => {
if (res.code == 200) {
const nList: any = [];
noticeRows.value = res.rows.sort(sortByField("priority", false));
if (noticeRows.value.length > 0) {
noticeRows.value.forEach((item: AnalyserOptions) => {
const content: string = `${item.noticeTitle}${item.noticeContent}`;
nList.push(content);
});
if (openType == 0) {
notice.value = nList[0];
if (store.getNoticeRead == 0) {
noticeOpen.value = true;
} else if (store.getNoticeRead != noticeRows.value[0]["createTime"]) {
noticeOpen.value = true;
} else {
noticeOpen.value = false;
}
}
}
}
});
}
function handleSubscriptionStatus(status, tmplId, message) {
if (status === "accept") {
} else if (status === "reject") {
requestUserSubscription([tmplId], "订阅消息", `请授权开通${message}`);
} else if (status === "ban") {
} else {
requestUserSubscription([tmplId], "订阅消息", `请授权开通${message}`);
}
}
function requestUserSubscription(tmplIds, title, content) {
Taro.showModal({
title: title,
content: content,
showCancel: false,
success: function (modalRes) {
if (modalRes.confirm) {
// getDeviceTicket().then((res) => {
// if (res.statusCode == 200) {
const params = {
tmplIds: tmplIds,
// sn: `sn_${store.getUserId}`,
// snTicket: `${res.data}`,
// modelId: "q0B1qUL_hGwtcLxTMah3Rw"
};
Taro.requestSubscribeMessage({
...params,
success: function (subscribeRes) {
tmplIds.forEach((tmplId) => {
if (subscribeRes[tmplId] === "accept") {
state.show = true;
state.msg = "订阅成功";
}
});
},
});
// }
// });
}
},
});
}
function resUsetInfo() {
pond1();
getDeviceDead();
bandDevShow.value = false;
if (mode.value == 1) {
pond2();
}
}
function getWarnMsg() {
const warnParams = ref({
pageSize: 10,
pageNum: 1,
});
const userId = Taro.getStorageSync("UserId");
msgWarn({ ...warnParams.value, userId }).then((res: any) => {
if (res.code == 200) {
const unReadCount = res.rows ? res.rows.filter(item => !item.isRead).length : 0;
if (unReadCount) {
Taro.setTabBarBadge({
index: 1, // tabBar的位置从0开始计数
text: unReadCount > 9 ? "9+" : String(unReadCount),
});
} else {
Taro.removeTabBarBadge({
index: 1,
});
}
}
});
}
function startPolling() {
stopPolling(); // 防止多个轮询同时运行
getWarnMsg();
timer.value = setInterval(() => {
getWarnMsg();
}, 120000); // 每3秒执行一次
}
function stopPolling() {
if (timer.value) {
clearInterval(timer.value);
timer.value = null;
}
}
// 检查登录状态
function checkLoginStatus() {
// 防止重复检查
if (isCheckingLogin) {
return;
}
// 游客模式直接跳过
if (store.getUnLogin == 1) {
return;
}
const token = Taro.getStorageSync('Access-Token');
const userId = Taro.getStorageSync('UserId');
const loginType = Taro.getStorageSync('LoginType');
// 方式1检查本地是否有token和userId
if (!token || !userId) {
isCheckingLogin = true;
// 如果之前是微信登录LoginType=1尝试引导重新授权
if (loginType === '1') {
attemptAutoLogin();
} else {
// 非微信登录,提示用户重新登录
showLoginExpiredDialog();
}
return;
}
// 方式2通过接口验证token是否有效
// 这里调用一个轻量级接口如果返回401request.ts会统一拦截处理
verifyTokenValid();
}
// 验证token是否有效
function verifyTokenValid() {
// 使用塘口列表接口来验证token
getPond1().then((res: any) => {
console.log('getPond1返回结果:', res);
if (res && res.code === 200) {
// token有效正常继续
console.log('登录状态有效');
isCheckingLogin = false;
} else if (res && (res.code === 401 || res.statusCode === 401)) {
// token无效主动弹窗提示
console.log('检测到401token已过期弹出提示');
isCheckingLogin = false;
showLoginExpiredDialog();
} else if (!res || res.code !== 200) {
// 其他异常情况也可能是token问题
console.log('接口返回异常code:', res?.code);
isCheckingLogin = false;
// 如果返回的不是200可能也是登录问题
if (res && res.msg && res.msg.includes('登录')) {
showLoginExpiredDialog();
}
} else {
// 其他错误码
isCheckingLogin = false;
}
}).catch((error) => {
// 接口调用失败
console.log('登录状态检查失败error:', error);
isCheckingLogin = false;
// 如果是401错误也弹出提示
if (error && (error.code === 401 || error.statusCode === 401)) {
console.log('catch到401错误弹出提示');
showLoginExpiredDialog();
}
});
}
// 显示登录过期提示
function showLoginExpiredDialog() {
// 防止重复弹窗
if (isShowingLoginDialog) {
console.log('登录过期弹窗已显示,跳过重复弹窗');
return;
}
isShowingLoginDialog = true;
console.log('显示登录过期弹窗');
Taro.showModal({
title: '提示',
content: '登录信息已过期,请重新登录',
showCancel: false,
success: function (res) {
if (res.confirm) {
Taro.clearStorageSync();
store.updateUnLogin(1);
Taro.redirectTo({
url: '/pages/login/index',
});
}
},
complete: function() {
// 1秒后重置状态允许再次弹窗
setTimeout(() => {
isShowingLoginDialog = false;
isCheckingLogin = false;
}, 1000);
}
});
}
// 尝试自动登录
function attemptAutoLogin() {
// 防止重复弹窗
if (isShowingLoginDialog) {
return;
}
isShowingLoginDialog = true;
console.log('显示重新授权弹窗');
// 由于后端需要手机号授权code无法实现完全静默登录
// 直接提示用户重新授权
Taro.showModal({
title: '登录已过期',
content: '需要重新授权获取手机号以继续使用',
confirmText: '去授权',
cancelText: '稍后再说',
success: function (res) {
if (res.confirm) {
// 跳转到登录页,让用户重新授权
Taro.clearStorageSync();
store.updateUnLogin(1);
Taro.redirectTo({
url: '/pages/login/index',
});
} else {
// 用户选择稍后,进入游客模式
store.updateUnLogin(1);
pond1();
}
},
complete: function() {
// 1秒后重置状态
setTimeout(() => {
isShowingLoginDialog = false;
isCheckingLogin = false;
}, 1000);
}
});
}
/////////////////////
function getPondInfo(e) {
// Taro.nextTick(() => {
// if (selPond.value) {
// if (e && e.id && e.id == selPond.value) {
// if (e && e.listDetector && e.listDetector.length <= 0) {
// setTimeout(() => {
// showTour.value = alertShow.value ? false : true;
// steps.value = [
// {
// content: "请添加设备",
// target: "addDevice",
// },
// ];
// }, 500);
// }
// }
// }
// });
}
// 切换模式
function changeMode() {
mode.value = mode.value == 1 ? 0 : 1;
if (mode.value == 0) {
pond1();
} else {
pond2();
}
}
// 塘口模式1
function pond1() {
const userId = Taro.getStorageSync("UserId");
getPond1().then((res: any) => {
if (res.code == 200) {
const pondIds = [];
const rows = res.data || [];
rows.forEach((item: any) => {
pondIds.push(item.id);
let num = 3;
const msg = item.warnCodeInfo?.warnDescription || '';
const showPh = !alarmJudgeCode(item.warnCodeInfo?.warnCode, 1);
const showSa = !alarmJudgeCode(item.warnCodeInfo?.warnCode, 2);
if (msg) {
item.isPh = showPh;
item.isSa = showSa;
} else {
item.isPh = showPh == false ? false : item.valuePH ? item.valuePH : false;
item.isSa =
showSa == false ? false : item.valueSalinity ? item.valueSalinity : false;
}
if (item.isPh) {
num += 1;
}
if (item.isSa) {
num += 1;
}
item.num = num;
item.status = Number(item.deviceCount) == 0 ? 0 : msg ? 2 : 1;
item.up = true;
});
pondList.value = rows;
// 立即设置默认选中的塘口,并强制触发响应式更新
if (rows.length > 0) {
const firstPondId = rows[0]["id"]; // 保持原始类型
if (!selPond.value || !pondIds.includes(selPond.value)) {
// 延迟一帧确保 DOM 已渲染
setTimeout(() => {
selPond.value = firstPondId;
console.log('已选中塘口:', selPond.value, '类型:', typeof selPond.value, '所有塘口:', rows.map(r => ({id: r.id, name: r.pondName})));
}, 50);
}
} else {
selPond.value = undefined;
}
setTimeout(() => {
showTour.value = rows.length == 0 ? true : false;
}, 500);
}
});
}
// 塘口模式2
function pond2() {
if (selPond.value) {
// 保持字符串格式避免精度丢失
params.pondId = [String(selPond.value)];
console.log('pond2 设置 pondId:', params.pondId);
}
}
// 新建塘口
function addPond() {
Taro.navigateTo({
url: "/home/addPond",
});
}
// 编辑塘口
function updatePond() {
const pondId = mode.value == 1 ? selPond.value : selPond_2.value;
if (pondId) {
Taro.navigateTo({
url: `/home/updatePond?id=${pondId}`,
});
} else {
state.show = true;
state.msg = "请先选择塘口";
}
}
// 绑定设备页面
function addDevice() {
loadDeviceList();
bandDevShow.value = true;
}
// 加载设备列表
function loadDeviceList() {
const userId = Taro.getStorageSync("UserId");
const pondId = mode.value == 1 ? selPond.value : selPond_2.value;
allDeviceList({ type: 1 ,rootUserId:userId}).then((res) => {
if (res.code == 200) {
if (res.data.length > 0) {
res.data.forEach((r) => {
r.disabled = false;
r.checked = false;
// r.lastRank = true;
r.open = true;
r.showChild = true;
// 判断是否在当前选中的港口id
if (r.pondInfo) {
if (pondId == r.pondInfo.id) {
r.checked = true;
} else {
r.disabled = true;
}
}
if (r.deviceType == 2) {
r.kzId = r.id;
r.id = undefined;
//r.id = r.deviceName;
}
if (r.listSwitch && r.listSwitch.length > 0) {
r.listSwitch.forEach((rr) => {
rr.deviceName = rr.switchName;
rr.disabled = false;
rr.checked = false;
// 判断是否在当前选中的港口id
if (rr.pondInfo) {
if (pondId == rr.pondInfo.id) {
rr.checked = true;
} else {
rr.disabled = true;
}
}
});
}
});
const ids: any = [];
res.data.forEach((device, i) => {
if (device.deviceType === 2 && device.isOxygenUsed) {
ids.push(i);
}
});
if (ids.length > 0) {
ids.forEach((i) => {
let newDevice = { ...res.data[i], deviceType: 1, listSwitch: [] }; // 修改deviceType并清空listSwitch
newDevice.id = newDevice.kzId;
res.data.push(newDevice);
});
}
res.data.forEach((r) => {
r.open = true;
r.disabled = false;
r.checked = false;
// 判断是否在当前选中的港口id
if (r.pondInfo) {
if (pondId == r.pondInfo.id) {
r.checked = true;
} else {
r.disabled = true;
}
}
});
res.data.sort(sortByField("deviceType", true));
deviceList.value = res.data;
} else {
deviceList.value = [];
}
}
});
}
// 取消绑定设备选择
function oncancel() {
bandDevShow.value = false;
}
// 确定选择绑定的设备
function onconfirm(list) {
bandDeviceList.value = list.list;
const listDetectorId: any = [];
const listSwitchId: any = [];
list.list.forEach((res) => {
if (res.source.deviceType) {
if (res.source.id) {
listDetectorId.push(res.source.id);
}
} else {
listSwitchId.push(res.source.id);
}
});
// 确定提示框
Taro.showModal({
title: "提示",
content: "确认添加选中的设备?",
success: function (res) {
if (res.confirm) {
const data = {
pondId: mode.value == 1 ? selPond.value : selPond_2.value,
listDetectorId,
listSwitchId,
};
bandDeviceToPond(data)
.then((res) => {
if (res.code == 200) {
state.show = true;
state.msg = "操作成功";
// 先关闭弹窗
bandDevShow.value = false;
// 延迟刷新数据,确保后端数据更新完成
setTimeout(() => {
// 先刷新塘口列表(会自动选中当前塘口)
pond1();
// 等待 pond1 完成后再刷新设备详情
setTimeout(() => {
if (pondChildInfo.value && pondChildInfo.value.getDeviceList) {
pondChildInfo.value.getDeviceList();
}
}, 500);
}, 500);
} else {
// 操作失败也关闭弹窗
bandDevShow.value = false;
state.show = true;
state.msg = res.msg || "绑定失败,请重试";
state.type = "fail";
}
})
.catch((error) => {
// 网络错误也关闭弹窗
bandDevShow.value = false;
state.show = true;
state.msg = "网络错误,请重试";
state.type = "fail";
});
} else if (res.cancel) {
bandDevShow.value = false;
}
},
});
}
// 跳转到添加设备页面
function addDev() {
bandDevShow.value = false;
alertShow.value = true;
}
/** 添加设备逻辑start */
// 手动
function goInput() {
alertShow.value = false;
Taro.navigateTo({
url: "/my/addDevice?pondId=" + selPond.value + "&page=home",
});
}
/** 扫码识别 */
function goScan() {
Taro.scanCode({
success: (res) => {
const result = res.result;
deviceScan({ qrCode: result }).then((v) => {
if (v.code == 200) {
params.devType = String(v.data.deviceType);
params.devNum = v.data.serialNum;
// 成功前往添加设备页面
submit();
} else {
errorShow.value = true;
alertShow.value = false;
}
});
},
fail: () => {
errorShow.value = true;
alertShow.value = false;
},
});
}
// 进入不同设备添加页面
function submit() {
if (params.pondId.length <= 0) {
state.show = true;
state.msg = "请选择塘口";
return;
}
if (!params.devNum) {
state.show = true;
state.msg = "请输入设备号";
return;
}
alertShow.value = false;
showTour.value = false;
// 传参前确保 pondId 为字符串
const paramsToSend = {
...params,
pondId: params.pondId.map(id => String(id))
};
console.log('home 准备传递的参数:', paramsToSend);
if (params.devType == 1) {
Taro.navigateTo({
url: "/my/addDetector?params=" + encodeURIComponent(JSON.stringify(paramsToSend)) + "&page=home",
});
} else {
Taro.navigateTo({
url: "/my/addController?params=" + encodeURIComponent(JSON.stringify(paramsToSend)) + "&page=home",
});
}
}
/** 添加设备逻辑end-- */
// 查看塘口详情
function toPondInfo(e) {
pondShow.value = true;
selPond_2.value = e.id;
selPondName_2.value = e.pondName;
}
// 关闭查看详情
function closePond() {
pondShow.value = false;
selPond_2.value = undefined;
selPondName_2.value = "塘口详情";
}
/** 前往登录 */
function toLogin() {
Taro.clearStorageSync();
store.updateUnLogin(2);
Taro.redirectTo({
url: "/pages/login/index",
});
}
// 查询设备到期
function getDeviceDead() {
deviceDead().then((res) => {
if (res.code == 200) {
deadList.value = res.data;
// if(res.data.length>0){
// Taro.showModal({
// title: '设备到期',
// content: '有设备即将到期,请及时续费',
// showCancel: true,
// confirmText: '续费',
// success: function (res) {
// if (res.confirm) {
// Taro.navigateTo({ url: `/my/deviceRecharge` });
// }
// }
// })
// }
}
});
}
// 关闭公告
function closeNotice() {
noticeOpen.value = false;
if (noticeRows.value.length > 0) {
if (store.getNoticeRead == 0) {
store.updateNoticeRead(noticeRows.value[0]["createTime"]);
} else if (store.getNoticeRead != noticeRows.value[0]["createTime"]) {
store.updateNoticeRead(noticeRows.value[0]["createTime"]);
}
} else {
store.updateNoticeRead(0);
}
}
// 打开公告
function openNotice() {
getNotice(1);
noticeOpen.value = true;
}
// 跳转刀续费页面
function toPay() {
Taro.navigateTo({
url: "/my/deviceRecharge",
});
}
/** -----------------method end------------------- */
</script>
<style lang="scss">
.sticky-header {
position: sticky;
top: 0;
z-index: 999;
background: #fff;
padding-bottom: 10rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
}
.header-btn-container {
padding: 20rpx !important;
gap: 16rpx !important;
display: flex !important;
align-items: stretch !important;
background: #fff !important;
view {
flex: 1;
display: flex;
}
}
.header-btn {
border-radius: 8rpx !important;
width: 100%;
height: 72rpx !important;
min-height: 72rpx !important;
max-height: 72rpx !important;
padding: 0 !important;
margin: 0 !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
box-sizing: border-box !important;
font-size: 28rpx !important;
font-weight: 500 !important;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1) !important;
transition: all 0.3s ease !important;
&:active {
transform: scale(0.98);
box-shadow: 0 1rpx 4rpx rgba(0, 0, 0, 0.1) !important;
}
}
.header-btn-warning {
background: #ff8800 !important;
background-color: #ff8800 !important;
}
.home_home {
width: 100%;
height: 100%;
.container {
width: 100vw;
height: 100vh;
background-image: url("https://www.yuceyun.cn/wechat/bg.png");
background-color: #e5ebed;
background-repeat: no-repeat;
background-size: 100% 30%;
margin: 0;
.body {
// 头部
.btn {
border-radius: 10px;
}
// 通知管理
.nut-noticebar__page {
border-radius: 15px;
}
.content {
color: #ff6b00;
}
.tag {
background: RGB(14, 191, 117, 0.1);
border-radius: 4px;
color: #0ebf75;
}
.tagErr {
background: RGB(191, 41, 14, 0.1);
border-radius: 4px;
color: #bf290e;
}
// 塘口管理
.pond {
.nut-cell-group__wrap {
margin: 0px;
}
}
// 模式2塘口管理
.pond_mode2 {
.nut-radio__button--active {
background: linear-gradient(135deg, #09b8c2 0%, #06a0a9 100%) !important;
border-radius: 10px;
color: #fff !important;
font-weight: bold;
font-size: 30rpx !important;
box-shadow: 0 6rpx 16rpx rgba(9, 184, 194, 0.6);
transform: scale(1.08);
transition: all 0.3s ease;
border: 2px solid #09b8c2;
}
.nut-radio__button--normal {
border-radius: 10px;
background-color: #f5f6f7;
color: #999;
font-size: 28rpx;
box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.06);
transition: all 0.3s ease;
border: 1px solid transparent;
}
.nut-radio--button {
background-color: #f5f6f7;
border-radius: 10px;
margin-bottom: 10rpx;
margin-right: 10rpx;
}
.nut-tab-pane {
padding: 0px !important;
}
.t {
width: 100%;
height: 2px;
background: #09b8c2;
border-radius: 10px;
}
}
}
}
.nut-grid-item__content {
height: 140rpx;
}
.nut-grid-item {
height: 140rpx;
padding-top: 0% !important;
}
.nut-grid {
height: 140rpx;
}
.grid {
.nut-grid-item__text {
font-weight: 500;
font-size: 28px;
color: #222;
}
}
.grid.active {
color: #fff;
.nut-grid-item__text {
color: #fff;
}
.nut-grid-item__content--center {
background: #09b8c2;
}
}
.gridSmall {
width: 160px;
background: #fff;
border: 1px solid #eee;
text-align: center;
height: 48px;
}
.gridSmall.active {
color: #fff;
background: #09b8c2;
}
.gridRight {
text-align: right;
color: #1589e9;
}
.nut-popup--bottom {
box-shadow: 0px 1px 7px 0px rgb(20, 20, 20);
border-radius: 0px;
max-height: 80vh;
overflow-y: auto;
}
.nut-popup--center.round {
border-radius: 10px;
}
.nut-dialog {
padding: 0;
}
.nut-dialog__content {
margin: 0;
max-height: 100%;
}
.d_body {
padding: 30px;
height: 100%;
.d_title {
font-size: 36rpx;
font-weight: bold;
color: #222;
}
.d_content {
font-size: 28rpx;
color: #888888;
}
.d_c_title {
font-size: 32rpx;
color: #222;
}
}
.d_btn {
// position: absolute;
bottom: 0px;
width: 100%;
font-size: 32rpx;
// border: 1px solid #eee;
height: 80px;
text-align: center;
line-height: 80px;
}
}
/** 底部弹出层 */
.rowPd {
padding: 40px;
height: 100%;
.gridCon {
background: #fff;
border: 1px solid #eee;
font-size: 26rpx;
color: #222;
text-align: center;
height: 48px;
line-height: 48px;
width: 90px;
}
.gridCon.active {
color: #fff;
background: #09b8c2;
}
.nut-grid-item__content {
background: #fff;
}
}
.charts {
height: 400px;
width: 100%;
}
</style>