功能添加

master
chris 2 weeks ago
parent 01da3ace7d
commit 1b510b8cd5

@ -0,0 +1,60 @@
/*
* @Author: chris
* @Date: 2025-09-05 11:34:53
* @LastEditors: chris
* @LastEditTime: 2025-09-17 15:28:22
*/
import request from "@/utils/request";
// 查询果园列表
export function listDevice(query) {
return request({
url: "/business/device/list",
method: "get",
params: query,
});
}
// 查询果园详细
export function getDevice(id) {
return request({
url: "/business/device/" + id,
method: "get",
});
}
// 新增果园
export function addDevice(data) {
return request({
url: "/business/device",
method: "post",
data,
});
}
// 修改果园
export function updateDevice(data) {
return request({
url: "/business/device",
method: "put",
data,
});
}
// 删除果园
export function delDevice(id) {
return request({
url: "/business/device/" + id,
method: "delete",
});
}
// 导出果园数据
export function exportDevice(query) {
return request({
url: "/business/device/export",
method: "post",
params: query,
responseType: "blob",
});
}

@ -0,0 +1,69 @@
/*
* @Author: chris
* @Date: 2025-09-05 11:34:53
* @LastEditors: chris
* @LastEditTime: 2025-09-18 14:45:50
*/
import request from "@/utils/request";
// 查询设备数据列表
export function listDeviceData(query) {
return request({
url: "/business/device-data/list",
method: "get",
params: query,
});
}
// 查询设备数据详细
export function getDeviceData(id) {
return request({
url: "/business/device-data/" + id,
method: "get",
});
}
// 查询设备数据统计分析数据
export function getDeviceDataAnalysis(params) {
return request({
url: "/business/device-data/analysis",
method: "get",
params,
});
}
// 新增设备数据
export function addDeviceData(data) {
return request({
url: "/business/device-data",
method: "post",
data,
});
}
// 修改设备数据
export function updateDeviceData(data) {
return request({
url: "/business/device-data",
method: "put",
data,
});
}
// 删除设备数据
export function delDeviceData(id) {
return request({
url: "/business/device-data/" + id,
method: "delete",
});
}
// 导出设备数据
export function exportDeviceData(query) {
return request({
url: "/business/device-data/export",
method: "post",
params: query,
responseType: "blob",
});
}

@ -0,0 +1,50 @@
/*
* @Author: chris
* @Date: 2025-09-19 11:37:22
* @LastEditors: chris
* @LastEditTime: 2025-09-19 11:37:30
*/
import request from "@/utils/request";
// 查询虫情预警推送人员接收列列表
export function listMember(query) {
return request({
url: "/business/member/list",
method: "get",
params: query,
});
}
// 查询虫情预警推送人员接收列详细
export function getMember(id) {
return request({
url: "/business/member/" + id,
method: "get",
});
}
// 新增虫情预警推送人员接收列
export function addMember(data) {
return request({
url: "/business/member",
method: "post",
data: data,
});
}
// 修改虫情预警推送人员接收列
export function updateMember(data) {
return request({
url: "/business/member",
method: "put",
data: data,
});
}
// 删除虫情预警推送人员接收列
export function delMember(id) {
return request({
url: "/business/member/" + id,
method: "delete",
});
}

@ -2,23 +2,23 @@
* @Author: chris * @Author: chris
* @Date: 2025-09-05 11:34:53 * @Date: 2025-09-05 11:34:53
* @LastEditors: chris * @LastEditors: chris
* @LastEditTime: 2025-09-05 11:37:30 * @LastEditTime: 2025-09-17 11:53:48
*/ */
import request from "@/utils/request"; import request from "@/utils/request";
// 查询果园列表 // 查询果园列表
export function listPest(query) { export function listPest(query) {
return request({ return request({
url: "/business/pest/list", url: "/leilinglitchi/business/device/list",
method: "get", method: "get",
params: query, params: Object.assign({ type: 1 }, query),
}); });
} }
// 查询果园详细 // 查询果园详细
export function getPest(id) { export function getPest(id) {
return request({ return request({
url: "/business/pest/" + id, url: "/leilinglitchi/business/device/" + id,
method: "get", method: "get",
}); });
} }
@ -26,25 +26,25 @@ export function getPest(id) {
// 新增果园 // 新增果园
export function addPest(data) { export function addPest(data) {
return request({ return request({
url: "/business/pest", url: "/leilinglitchi/business/device",
method: "post", method: "post",
data: data, data,
}); });
} }
// 修改果园 // 修改果园
export function updatePest(data) { export function updatePest(data) {
return request({ return request({
url: "/business/pest", url: "/leilinglitchi/business/device",
method: "put", method: "put",
data: data, data,
}); });
} }
// 删除果园 // 删除果园
export function delPest(id) { export function delPest(id) {
return request({ return request({
url: "/business/pest/" + id, url: "/leilinglitchi/business/device/" + id,
method: "delete", method: "delete",
}); });
} }
@ -52,8 +52,8 @@ export function delPest(id) {
// 导出果园数据 // 导出果园数据
export function exportPest(query) { export function exportPest(query) {
return request({ return request({
url: "/business/pest/export", url: "/leilinglitchi/business/device/export",
method: "get", method: "post",
params: query, params: query,
responseType: "blob", responseType: "blob",
}); });

@ -2,28 +2,29 @@
* @Author: chris * @Author: chris
* @Date: 2025-02-06 16:43:54 * @Date: 2025-02-06 16:43:54
* @LastEditors: chris * @LastEditors: chris
* @LastEditTime: 2025-08-06 16:13:59 * @LastEditTime: 2025-09-22 17:33:54
--> -->
<template> <template>
<div class="device-list-container"> <div class="device-list-container">
<div class="device-list"> <div class="device-list">
<div <div
v-for="device in deviceData" v-for="device in props.list"
:key="device.deviceAddr" :key="device.deviceAddr"
@click="handleDeviceClick(device)" @click="handleDeviceClick(device)"
> >
<div class="device-item" :class="{ 'device-item-selected': selectedDevice === device.deviceAddr }"> <div class="device-item" :class="{ 'device-item-selected': selectedDevice === device.deviceAddr }">
<Icon :icon="getDeviceIcon(device.deviceType)" class="device-icon mr-5px" /> <Icon :icon="getDeviceIcon(device.type)" class="device-icon mr-5px" />
<div class="device-details"> <div class="device-details">
<div class="device-name font-medium">{{ device.deviceName }}</div> <div class="device-name font-medium">{{ device.deviceNo }}</div>
<el-tag :type="device.deviceEnabled ? 'success' : 'danger'" size="small"> <div class="device-model">{{ device.model }}</div>
<!-- <el-tag :type="device.deviceEnabled ? 'success' : 'danger'" size="small">
{{ device.deviceEnabled ? '在线' : '离线' }} {{ device.deviceEnabled ? '在线' : '离线' }}
</el-tag> </el-tag> -->
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div v-if="deviceData.length === 0" class="empty-state text-center py-10"> <div v-if="props.list.length === 0" class="empty-state text-center py-10">
<Icon icon="carbon:error" class="text-gray-300 text-4xl mb-2" /> <Icon icon="carbon:error" class="text-gray-300 text-4xl mb-2" />
<p class="text-gray-400">暂无设备数据</p> <p class="text-gray-400">暂无设备数据</p>
</div> </div>
@ -33,16 +34,22 @@
<script setup name="DeviceFlatList"> <script setup name="DeviceFlatList">
import { ref, defineEmits } from 'vue' import { ref, defineEmits } from 'vue'
import { Icon } from '@iconify/vue' import { Icon } from '@iconify/vue'
import { deviceData } from '@/views/indexComponents/testData' // import { deviceData } from '@/views/indexComponents/testData'
// //
const deviceIconDict = { const deviceIconDict = {
worm: 'carbon:pest', // 1: 'carbon:pest', // carbon:radar-weather', //
weather: 'carbon:radar-weather', // 2: 'carbon:soil-moisture', //
soil: 'carbon:soil-moisture', // 3: 'carbon:radar-weather', // '
irrigation: 'carbon:rain-drop', //
default: 'carbon:devices'
} }
const props = defineProps({
list: {
type: Array,
default: () => [],
},
})
const emit = defineEmits(['change']) const emit = defineEmits(['change'])
const selectedDevice = ref('') const selectedDevice = ref('')
@ -65,7 +72,7 @@ function handleDeviceClick(device) {
} }
.device-item { .device-item {
@apply flex items-center cursor-pointer transition-all duration-200 hover:bg-blue-50 my-4px py-6px px-6px border-rounded-6px; @apply flex items-center cursor-pointer transition-all duration-200 hover:bg-blue-50 my-4px py-10px px-6px border-rounded-6px;
&-selected { &-selected {
@apply bg-blue-100; @apply bg-blue-100;
@ -73,7 +80,7 @@ function handleDeviceClick(device) {
} }
.device-icon { .device-icon {
@apply text-blue-500 w-5 h-5; @apply text-blue-500 w-26px h-26px;
} }
.device-details { .device-details {
@ -84,6 +91,11 @@ function handleDeviceClick(device) {
@apply text-gray-800; @apply text-gray-800;
} }
.device-model {
@apply text-xs color-#9ca3af;
}
.device-id { .device-id {
@apply text-xs; @apply text-xs;
} }

@ -1,3 +1,9 @@
/*
* @Author: chris
* @Date: 2025-01-13 09:34:10
* @LastEditors: chris
* @LastEditTime: 2025-09-17 09:20:33
*/
import router from "./router"; import router from "./router";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import NProgress from "nprogress"; import NProgress from "nprogress";
@ -19,8 +25,8 @@ const isWhiteList = (path) => {
router.beforeEach((to, from, next) => { router.beforeEach((to, from, next) => {
// TODO 测试,先跳过 // TODO 测试,先跳过
next(); // next();
return; // return;
NProgress.start(); NProgress.start();
if (getToken()) { if (getToken()) {
to.meta.title && useSettingsStore().setTitle(to.meta.title); to.meta.title && useSettingsStore().setTitle(to.meta.title);

@ -69,61 +69,61 @@ export const constantRoutes = [
meta: { title: "首页", icon: "dashboard", affix: true }, meta: { title: "首页", icon: "dashboard", affix: true },
}, },
// TODO 测试开始, 后续删除 // TODO 测试开始, 后续删除
{
path: "/pest/pest-monitor",
component: () => import("@/views/pest/pestMonitor/index"),
name: "PestMonitor",
},
{
path: "/orchard",
component: () => import("@/views/orchard"),
name: "Orchard",
},
{
path: "/pest",
component: () => import("@/views/pest"),
name: "Pest",
},
{
path: "/pest/imgAnalysis",
component: () => import("@/views/pest/imgAnalysis"),
name: "ImgAnalysis",
},
{
path: "/pest/pest-statistics",
component: () => import("@/views/pest/pestStatistics"),
name: "PestStatistics",
},
{
path: "/pest/trendAnalysis",
component: () => import("@/views/pest/trendAnalysis"),
name: "TrendAnalysis",
},
{
path: "/weather/monitor",
component: () => import("@/views/weather/monitor"),
name: "WeatherMonitor",
},
{
path: "/weather/history",
component: () => import("@/views/weather/history"),
name: "WeatherHistory",
},
{
path: "/soil/monitor",
component: () => import("@/views/soil/monitor"),
name: "SoilMonitor",
},
{
path: "/soil/history",
component: () => import("@/views/soil/history"),
name: "SoilHistory",
},
// { // {
// path: "/orchard-screen", // path: "/pest/pest-monitor",
// component: () => import("@/views/orchardScreen"), // component: () => import("@/views/pest/pestMonitor/index"),
// name: "OrchardScreen", // name: "PestMonitor",
// },
// {
// path: "/orchard",
// component: () => import("@/views/orchard"),
// name: "Orchard",
// },
// {
// path: "/pest",
// component: () => import("@/views/pest"),
// name: "Pest",
// },
// {
// path: "/pest/imgAnalysis",
// component: () => import("@/views/pest/imgAnalysis"),
// name: "ImgAnalysis",
// },
// {
// path: "/pest/pest-statistics",
// component: () => import("@/views/pest/pestStatistics"),
// name: "PestStatistics",
// },
// {
// path: "/pest/trendAnalysis",
// component: () => import("@/views/pest/trendAnalysis"),
// name: "TrendAnalysis",
// },
// {
// path: "/weather/monitor",
// component: () => import("@/views/weather/monitor"),
// name: "WeatherMonitor",
// },
// {
// path: "/weather/history",
// component: () => import("@/views/weather/history"),
// name: "WeatherHistory",
// },
// {
// path: "/soil/monitor",
// component: () => import("@/views/soil/monitor"),
// name: "SoilMonitor",
// },
// {
// path: "/soil/history",
// component: () => import("@/views/soil/history"),
// name: "SoilHistory",
// }, // },
// // {
// // path: "/orchard-screen",
// // component: () => import("@/views/orchardScreen"),
// // name: "OrchardScreen",
// // },
// TODO 测试结束, 后续删除 // TODO 测试结束, 后续删除
], ],
}, },

@ -1,5 +1,11 @@
import useDictStore from '@/store/modules/dict' /*
import { getDicts } from '@/api/system/dict/data' * @Author: chris
* @Date: 2025-01-13 09:34:10
* @LastEditors: chris
* @LastEditTime: 2025-09-17 16:54:35
*/
import useDictStore from "@/store/modules/dict";
import { getDicts } from "@/api/system/dict/data";
/** /**
* 获取字典数据 * 获取字典数据
@ -13,12 +19,17 @@ export function useDict(...args) {
if (dicts) { if (dicts) {
res.value[dictType] = dicts; res.value[dictType] = dicts;
} else { } else {
getDicts(dictType).then(resp => { getDicts(dictType).then((resp) => {
res.value[dictType] = resp.data.map(p => ({ label: p.dictLabel, value: p.dictValue, elTagType: p.listClass, elTagClass: p.cssClass })) res.value[dictType] = resp.data.map((p) => ({
label: p.dictLabel,
value: p.dictValue,
elTagType: p.listClass,
elTagClass: p.cssClass,
}));
useDictStore().setDict(dictType, res.value[dictType]); useDictStore().setDict(dictType, res.value[dictType]);
}) });
} }
}) });
return toRefs(res.value); return toRefs(res.value);
})() })();
} }

@ -1,114 +1,155 @@
import axios from 'axios' import axios from "axios";
import { ElNotification , ElMessageBox, ElMessage, ElLoading } from 'element-plus' import {
import { getToken } from '@/utils/auth' ElNotification,
import errorCode from '@/utils/errorCode' ElMessageBox,
import { tansParams, blobValidate } from '@/utils/ruoyi' ElMessage,
import cache from '@/plugins/cache' ElLoading,
import { saveAs } from 'file-saver' } from "element-plus";
import useUserStore from '@/store/modules/user' import { getToken } from "@/utils/auth";
import errorCode from "@/utils/errorCode";
import { tansParams, blobValidate } from "@/utils/ruoyi";
import cache from "@/plugins/cache";
import { saveAs } from "file-saver";
import useUserStore from "@/store/modules/user";
let downloadLoadingInstance; let downloadLoadingInstance;
// 是否显示重新登录 // 是否显示重新登录
export let isRelogin = { show: false }; export let isRelogin = { show: false };
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8' axios.defaults.headers["Content-Type"] = "application/json;charset=utf-8";
// 创建axios实例 // 创建axios实例
const service = axios.create({ const service = axios.create({
// axios中请求配置有baseURL选项表示请求URL公共部分 // axios中请求配置有baseURL选项表示请求URL公共部分
baseURL: import.meta.env.VITE_APP_BASE_API, baseURL: import.meta.env.VITE_APP_BASE_API,
// 超时 // 超时
timeout: 10000 timeout: 10000,
}) });
// request拦截器 // request拦截器
service.interceptors.request.use(config => { service.interceptors.request.use(
// 是否需要设置 token (config) => {
const isToken = (config.headers || {}).isToken === false // 是否需要设置 token
// 是否需要防止数据重复提交 const isToken = (config.headers || {}).isToken === false;
const isRepeatSubmit = (config.headers || {}).repeatSubmit === false // 是否需要防止数据重复提交
if (getToken() && !isToken) { const isRepeatSubmit = (config.headers || {}).repeatSubmit === false;
config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改 if (getToken() && !isToken) {
} config.headers["Authorization"] = "Bearer " + getToken(); // 让每个请求携带自定义token 请根据实际情况自行修改
// get请求映射params参数
if (config.method === 'get' && config.params) {
let url = config.url + '?' + tansParams(config.params);
url = url.slice(0, -1);
config.params = {};
config.url = url;
}
if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {
const requestObj = {
url: config.url,
data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,
time: new Date().getTime()
} }
const requestSize = Object.keys(JSON.stringify(requestObj)).length; // 请求数据大小 // get请求映射params参数
const limitSize = 5 * 1024 * 1024; // 限制存放数据5M if (config.method === "get" && config.params) {
if (requestSize >= limitSize) { let url = config.url + "?" + tansParams(config.params);
console.warn(`[${config.url}]: ` + '请求数据大小超出允许的5M限制无法进行防重复提交验证。') url = url.slice(0, -1);
return config; config.params = {};
config.url = url;
} }
const sessionObj = cache.session.getJSON('sessionObj') if (
if (sessionObj === undefined || sessionObj === null || sessionObj === '') { !isRepeatSubmit &&
cache.session.setJSON('sessionObj', requestObj) (config.method === "post" || config.method === "put")
} else { ) {
const s_url = sessionObj.url; // 请求地址 const requestObj = {
const s_data = sessionObj.data; // 请求数据 url: config.url,
const s_time = sessionObj.time; // 请求时间 data:
const interval = 1000; // 间隔时间(ms),小于此时间视为重复提交 typeof config.data === "object"
if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) { ? JSON.stringify(config.data)
const message = '数据正在处理,请勿重复提交'; : config.data,
console.warn(`[${s_url}]: ` + message) time: new Date().getTime(),
return Promise.reject(new Error(message)) };
const requestSize = Object.keys(JSON.stringify(requestObj)).length; // 请求数据大小
const limitSize = 5 * 1024 * 1024; // 限制存放数据5M
if (requestSize >= limitSize) {
console.warn(
`[${config.url}]: ` +
"请求数据大小超出允许的5M限制无法进行防重复提交验证。"
);
return config;
}
const sessionObj = cache.session.getJSON("sessionObj");
if (
sessionObj === undefined ||
sessionObj === null ||
sessionObj === ""
) {
cache.session.setJSON("sessionObj", requestObj);
} else { } else {
cache.session.setJSON('sessionObj', requestObj) const s_url = sessionObj.url; // 请求地址
const s_data = sessionObj.data; // 请求数据
const s_time = sessionObj.time; // 请求时间
const interval = 1000; // 间隔时间(ms),小于此时间视为重复提交
if (
s_data === requestObj.data &&
requestObj.time - s_time < interval &&
s_url === requestObj.url
) {
const message = "数据正在处理,请勿重复提交";
console.warn(`[${s_url}]: ` + message);
return Promise.reject(new Error(message));
} else {
cache.session.setJSON("sessionObj", requestObj);
}
} }
} }
return config;
},
(error) => {
console.log(error);
Promise.reject(error);
} }
return config );
}, error => {
console.log(error)
Promise.reject(error)
})
// 响应拦截器 // 响应拦截器
service.interceptors.response.use(res => { service.interceptors.response.use(
(res) => {
// 未设置状态码则默认成功状态 // 未设置状态码则默认成功状态
const code = res.data.code || 200; const code = res.data.code || 200;
// 获取错误信息 // 获取错误信息
const msg = errorCode[code] || res.data.msg || errorCode['default'] const msg = errorCode[code] || res.data.msg || errorCode["default"];
// 二进制数据则直接返回 // 二进制数据则直接返回
if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') { if (
return res.data res.request.responseType === "blob" ||
res.request.responseType === "arraybuffer"
) {
return res.data;
} }
if (code === 401) { if (code === 401) {
if (!isRelogin.show) { if (!isRelogin.show) {
isRelogin.show = true; isRelogin.show = true;
ElMessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => { ElMessageBox.confirm(
isRelogin.show = false; "登录状态已过期,您可以继续留在该页面,或者重新登录",
useUserStore().logOut().then(() => { "系统提示",
location.href = '/index'; {
confirmButtonText: "重新登录",
cancelButtonText: "取消",
type: "warning",
}
)
.then(() => {
isRelogin.show = false;
useUserStore()
.logOut()
.then(() => {
location.href = "/index";
});
}) })
}).catch(() => { .catch(() => {
isRelogin.show = false; isRelogin.show = false;
}); });
} }
return Promise.reject('无效的会话,或者会话已过期,请重新登录。') return Promise.reject("无效的会话,或者会话已过期,请重新登录。");
} else if (code === 500) { } else if (code === 500) {
ElMessage({ message: msg, type: 'error' }) ElMessage({ message: msg, type: "error" });
return Promise.reject(new Error(msg)) return Promise.reject(new Error(msg));
} else if (code === 601) { } else if (code === 601) {
ElMessage({ message: msg, type: 'warning' }) ElMessage({ message: msg, type: "warning" });
return Promise.reject(new Error(msg)) return Promise.reject(new Error(msg));
} else if (code !== 200) { } else if (code !== 200) {
ElNotification.error({ title: msg }) ElNotification.error({ title: msg });
return Promise.reject('error') return Promise.reject("error");
} else { } else {
return Promise.resolve(res.data) return Promise.resolve(res.data);
} }
}, },
error => { (error) => {
console.log('err' + error) console.log("err" + error);
let { message } = error; let { message } = error;
if (message == "Network Error") { if (message == "Network Error") {
message = "后端接口连接异常"; message = "后端接口连接异常";
@ -117,36 +158,50 @@ service.interceptors.response.use(res => {
} else if (message.includes("Request failed with status code")) { } else if (message.includes("Request failed with status code")) {
message = "系统接口" + message.substr(message.length - 3) + "异常"; message = "系统接口" + message.substr(message.length - 3) + "异常";
} }
ElMessage({ message: message, type: 'error', duration: 5 * 1000 }) ElMessage({ message: message, type: "error", duration: 5 * 1000 });
return Promise.reject(error) return Promise.reject(error);
} }
) );
// 通用下载方法 // 通用下载方法
export function download(url, params, filename, config) { export function download(url, params, filename, config) {
downloadLoadingInstance = ElLoading.service({ text: "正在下载数据,请稍候", background: "rgba(0, 0, 0, 0.7)", }) downloadLoadingInstance = ElLoading.service({
return service.post(url, params, { text: "正在下载数据,请稍候",
transformRequest: [(params) => { return tansParams(params) }], background: "rgba(0, 0, 0, 0.7)",
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, });
responseType: 'blob', let res = null;
...config return service
}).then(async (data) => { .post(url, params, {
const isBlob = blobValidate(data); transformRequest: [
if (isBlob) { (params) => {
const blob = new Blob([data]) return tansParams(params);
saveAs(blob, filename) },
} else { ],
const resText = await data.text(); headers: { "Content-Type": "application/x-www-form-urlencoded" },
const rspObj = JSON.parse(resText); responseType: "blob",
const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default'] ...config,
ElMessage.error(errMsg); })
} .then(async (data) => {
downloadLoadingInstance.close(); const isBlob = blobValidate(data);
}).catch((r) => { if (isBlob) {
console.error(r) const blob = new Blob([data]);
ElMessage.error('下载文件出现错误,请联系管理员!') saveAs(blob, filename);
downloadLoadingInstance.close(); } else {
}) const resText = await data.text();
const rspObj = JSON.parse(resText);
const errMsg =
errorCode[rspObj.code] || rspObj.msg || errorCode["default"];
// ElMessage.error(errMsg);
res = errMsg;
}
downloadLoadingInstance.close();
return res;
})
.catch((r) => {
console.error(r);
ElMessage.error("下载文件出现错误,请联系管理员!");
downloadLoadingInstance.close();
});
} }
export default service export default service;

@ -1,5 +1,3 @@
/** /**
* 通用js方法封装处理 * 通用js方法封装处理
* Copyright (c) 2019 ruoyi * Copyright (c) 2019 ruoyi
@ -8,22 +6,25 @@
// 日期格式化 // 日期格式化
export function parseTime(time, pattern) { export function parseTime(time, pattern) {
if (arguments.length === 0 || !time) { if (arguments.length === 0 || !time) {
return null return null;
} }
const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}' const format = pattern || "{y}-{m}-{d} {h}:{i}:{s}";
let date let date;
if (typeof time === 'object') { if (typeof time === "object") {
date = time date = time;
} else { } else {
if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) { if (typeof time === "string" && /^[0-9]+$/.test(time)) {
time = parseInt(time) time = parseInt(time);
} else if (typeof time === 'string') { } else if (typeof time === "string") {
time = time.replace(new RegExp(/-/gm), '/').replace('T', ' ').replace(new RegExp(/\.[\d]{3}/gm), ''); time = time
.replace(new RegExp(/-/gm), "/")
.replace("T", " ")
.replace(new RegExp(/\.[\d]{3}/gm), "");
} }
if ((typeof time === 'number') && (time.toString().length === 10)) { if (typeof time === "number" && time.toString().length === 10) {
time = time * 1000 time = time * 1000;
} }
date = new Date(time) date = new Date(time);
} }
const formatObj = { const formatObj = {
y: date.getFullYear(), y: date.getFullYear(),
@ -32,18 +33,20 @@ export function parseTime(time, pattern) {
h: date.getHours(), h: date.getHours(),
i: date.getMinutes(), i: date.getMinutes(),
s: date.getSeconds(), s: date.getSeconds(),
a: date.getDay() a: date.getDay(),
} };
const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
let value = formatObj[key] let value = formatObj[key];
// Note: getDay() returns 0 on Sunday // Note: getDay() returns 0 on Sunday
if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] } if (key === "a") {
return ["日", "一", "二", "三", "四", "五", "六"][value];
}
if (result.length > 0 && value < 10) { if (result.length > 0 && value < 10) {
value = '0' + value value = "0" + value;
} }
return value || 0 return value || 0;
}) });
return time_str return time_str;
} }
// 表单重置 // 表单重置
@ -56,14 +59,19 @@ export function resetForm(refName) {
// 添加日期范围 // 添加日期范围
export function addDateRange(params, dateRange, propName) { export function addDateRange(params, dateRange, propName) {
let search = params; let search = params;
search.params = typeof (search.params) === 'object' && search.params !== null && !Array.isArray(search.params) ? search.params : {}; search.params =
typeof search.params === "object" &&
search.params !== null &&
!Array.isArray(search.params)
? search.params
: {};
dateRange = Array.isArray(dateRange) ? dateRange : []; dateRange = Array.isArray(dateRange) ? dateRange : [];
if (typeof (propName) === 'undefined') { if (typeof propName === "undefined") {
search.params['beginTime'] = dateRange[0]; search.params["beginTime"] = dateRange[0];
search.params['endTime'] = dateRange[1]; search.params["endTime"] = dateRange[1];
} else { } else {
search.params['begin' + propName] = dateRange[0]; search.params["begin" + propName] = dateRange[0];
search.params['end' + propName] = dateRange[1]; search.params["end" + propName] = dateRange[1];
} }
return search; return search;
} }
@ -75,20 +83,20 @@ export function selectDictLabel(datas, value) {
} }
var actions = []; var actions = [];
Object.keys(datas).some((key) => { Object.keys(datas).some((key) => {
if (datas[key].value == ('' + value)) { if (datas[key].value == "" + value) {
actions.push(datas[key].label); actions.push(datas[key].label);
return true; return true;
} }
}) });
if (actions.length === 0) { if (actions.length === 0) {
actions.push(value); actions.push(value);
} }
return actions.join(''); return actions.join("");
} }
// 回显数据字典(字符串数组) // 回显数据字典(字符串数组)
export function selectDictLabels(datas, value, separator) { export function selectDictLabels(datas, value, separator) {
if (value === undefined || value.length ===0) { if (value === undefined || value.length === 0) {
return ""; return "";
} }
if (Array.isArray(value)) { if (Array.isArray(value)) {
@ -100,30 +108,32 @@ export function selectDictLabels(datas, value, separator) {
Object.keys(value.split(currentSeparator)).some((val) => { Object.keys(value.split(currentSeparator)).some((val) => {
var match = false; var match = false;
Object.keys(datas).some((key) => { Object.keys(datas).some((key) => {
if (datas[key].value == ('' + temp[val])) { if (datas[key].value == "" + temp[val]) {
actions.push(datas[key].label + currentSeparator); actions.push(datas[key].label + currentSeparator);
match = true; match = true;
} }
}) });
if (!match) { if (!match) {
actions.push(temp[val] + currentSeparator); actions.push(temp[val] + currentSeparator);
} }
}) });
return actions.join('').substring(0, actions.join('').length - 1); return actions.join("").substring(0, actions.join("").length - 1);
} }
// 字符串格式化(%s ) // 字符串格式化(%s )
export function sprintf(str) { export function sprintf(str) {
var args = arguments, flag = true, i = 1; var args = arguments,
flag = true,
i = 1;
str = str.replace(/%s/g, function () { str = str.replace(/%s/g, function () {
var arg = args[i++]; var arg = args[i++];
if (typeof arg === 'undefined') { if (typeof arg === "undefined") {
flag = false; flag = false;
return ''; return "";
} }
return arg; return arg;
}); });
return flag ? str : ''; return flag ? str : "";
} }
// 转换字符串undefined,null等转化为"" // 转换字符串undefined,null等转化为""
@ -148,7 +158,7 @@ export function mergeRecursive(source, target) {
} }
} }
return source; return source;
}; }
/** /**
* 构造树型结构数据 * 构造树型结构数据
@ -159,9 +169,9 @@ export function mergeRecursive(source, target) {
*/ */
export function handleTree(data, id, parentId, children) { export function handleTree(data, id, parentId, children) {
let config = { let config = {
id: id || 'id', id: id || "id",
parentId: parentId || 'parentId', parentId: parentId || "parentId",
childrenList: children || 'children' childrenList: children || "children",
}; };
var childrenListMap = {}; var childrenListMap = {};
@ -202,19 +212,23 @@ export function handleTree(data, id, parentId, children) {
} }
/** /**
* 参数处理 * 参数处理
* @param {*} params 参数 * @param {*} params 参数
*/ */
export function tansParams(params) { export function tansParams(params) {
let result = '' let result = "";
for (const propName of Object.keys(params)) { for (const propName of Object.keys(params)) {
const value = params[propName]; const value = params[propName];
var part = encodeURIComponent(propName) + "="; var part = encodeURIComponent(propName) + "=";
if (value !== null && value !== "" && typeof (value) !== "undefined") { if (value !== null && value !== "" && typeof value !== "undefined") {
if (typeof value === 'object') { if (typeof value === "object") {
for (const key of Object.keys(value)) { for (const key of Object.keys(value)) {
if (value[key] !== null && value[key] !== "" && typeof (value[key]) !== 'undefined') { if (
let params = propName + '[' + key + ']'; value[key] !== null &&
value[key] !== "" &&
typeof value[key] !== "undefined"
) {
let params = propName + "[" + key + "]";
var subPart = encodeURIComponent(params) + "="; var subPart = encodeURIComponent(params) + "=";
result += subPart + encodeURIComponent(value[key]) + "&"; result += subPart + encodeURIComponent(value[key]) + "&";
} }
@ -224,23 +238,22 @@ export function tansParams(params) {
} }
} }
} }
return result return result;
} }
// 返回项目路径 // 返回项目路径
export function getNormalPath(p) { export function getNormalPath(p) {
if (p.length === 0 || !p || p == 'undefined') { if (p.length === 0 || !p || p == "undefined") {
return p return p;
}; }
let res = p.replace('//', '/') let res = p.replace("//", "/");
if (res[res.length - 1] === '/') { if (res[res.length - 1] === "/") {
return res.slice(0, res.length - 1) return res.slice(0, res.length - 1);
} }
return res; return res;
} }
// 验证是否为blob格式 // 验证是否为blob格式
export function blobValidate(data) { export function blobValidate(data) {
return data.type !== 'application/json' return data.type !== "application/json";
} }

@ -2,7 +2,7 @@
* @Author: chris * @Author: chris
* @Date: 2025-08-08 16:17:48 * @Date: 2025-08-08 16:17:48
* @LastEditors: chris * @LastEditors: chris
* @LastEditTime: 2025-08-12 10:09:05 * @LastEditTime: 2025-09-17 17:14:23
--> -->
<template> <template>
<common-action-toolbar <common-action-toolbar
@ -31,7 +31,7 @@ const showSearch = computed({
}) })
// //
const buttons = [ const buttons = reactive([
{ {
key: 'add', key: 'add',
text: '新增', text: '新增',
@ -47,7 +47,7 @@ const buttons = [
plain: true, plain: true,
icon: 'Edit', icon: 'Edit',
action: 'update', action: 'update',
disabled: computed(() => props.selectedRows.length !== 1).value disabled: computed(() => props.selectedRows.length !== 1)
}, },
{ {
key: 'delete', key: 'delete',
@ -56,7 +56,7 @@ const buttons = [
plain: true, plain: true,
icon: 'Delete', icon: 'Delete',
action: 'delete', action: 'delete',
disabled: computed(() => props.selectedRows.length === 0).value disabled: computed(() => props.selectedRows.length === 0)
}, },
{ {
key: 'export', key: 'export',
@ -66,7 +66,7 @@ const buttons = [
icon: 'Download', icon: 'Download',
action: 'export' action: 'export'
} }
] ])
// //
function handleButtonClick(action) { function handleButtonClick(action) {

@ -0,0 +1,152 @@
<template>
<el-dialog :title="title" v-model="dialogOpen" width="600px" append-to-body>
<el-form :model="props.form" :rules="rules" ref="deviceRef" label-width="80px">
<el-row :gutter="10">
<el-col :span="24">
<el-form-item label="设备图片" prop="imgUrl">
<image-upload v-model="uploadImg" :limit="1" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="设备类型" prop="type">
<el-select v-model="props.form.type" placeholder="请选择设备类型" clearable style="width: 200px">
<el-option v-for="dict in deviceTypes" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="设备号" prop="deviceNo">
<el-input v-model="props.form.deviceNo" placeholder="请输入设备号" maxlength="100" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="设备型号" prop="model">
<el-input v-model="props.form.model" placeholder="请输入设备型号" maxlength="50" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="设备地址" prop="deviceAddr">
<el-input v-model="props.form.deviceAddr" placeholder="请输入设备地址" maxlength="100" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="设备点位" prop="installPointDes">
<el-input v-model="props.form.installPointDes" placeholder="请输入设备点位" maxlength="100" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="状态">
<el-radio-group v-model="props.form.status">
<el-radio v-for="item in statusOptions" :key="item.value" :value="item.value">{{ item.label }}</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="备注">
<el-input v-model="props.form.remark" type="textarea" placeholder="请输入备注内容"></el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancel"> </el-button>
<el-button type="primary" @click="submitForm"> </el-button>
</div>
</template>
</el-dialog>
</template>
<script setup>
import { addDevice, updateDevice } from '@/api/device'
const props = defineProps({
open: { type: Boolean, required: true },
form: { type: Object, required: true },
title: { type: String, required: true },
deviceTypes: { type: Array, required: true },
statusOptions: { type: Array, required: true },
})
const emits = defineEmits(['update:open', 'success'])
// open
const dialogOpen = ref(props.open)
const uploadImg = ref('')
watch(() => uploadImg.value, (newVal) => {
props.form.imgUrl = newVal
})
//
const rules = {
imgUrl: [{ required: true, message: '请上传设备图片', trigger: 'blur' }],
type: [{ required: true, message: '请选择设备类型', trigger: 'change' }],
deviceNo: [{ required: true, message: '设备号不能为空', trigger: 'blur' }],
deviceAddr: [{ required: true, message: '设备地址不能为空', trigger: 'blur' }],
model: [{ required: true, message: '设备型号不能为空', trigger: 'blur' }],
status: [{ required: true, message: '请选择状态', trigger: 'blur' }],
installPointDes: [{ required: true, message: '安装点描述不能为空', trigger: 'blur' }],
}
//
const deviceRef = ref(null)
const { proxy } = getCurrentInstance()
onMounted(() => {
//
setTimeout(() => {
deviceRef.value?.resetFields()
}, 0)
})
// props.open
watch(
() => props.open,
(newValue) => {
dialogOpen.value = newValue
}
)
//
watch(
dialogOpen,
(newValue) => {
if (newValue !== props.open) {
emits('update:open', newValue)
}
}
)
// emits
//
async function submitForm() {
try {
await deviceRef.value.validate()
if (props.form.id) {
await updateDevice(props.form)
proxy.$modal.msgSuccess('修改成功')
} else {
await addDevice(props.form)
proxy.$modal.msgSuccess('新增成功')
}
dialogOpen.value = false
emits('success')
} catch (error) {
//
if (error !== 'cancel') {
proxy.$modal.msgError('操作失败: ' + (error.message || '未知错误'))
}
}
}
//
function cancel() {
dialogOpen.value = false
deviceRef.value?.resetFields()
}
</script>

@ -2,16 +2,18 @@
* @Author: chris * @Author: chris
* @Date: 2025-08-08 16:17:37 * @Date: 2025-08-08 16:17:37
* @LastEditors: chris * @LastEditors: chris
* @LastEditTime: 2025-09-05 11:02:44 * @LastEditTime: 2025-09-18 14:50:47
--> -->
<template> <template>
<el-form :model="queryParams" ref="queryRef" :inline="true" label-width="68px"> <el-form :model="queryParams" ref="queryRef" :inline="true" label-width="68px">
<el-form-item label="设备名称" prop="orchardName"> <el-form-item label="设备类型" prop="deviceType">
<el-input v-model="queryParams.orchardName" placeholder="请输入设备名称" clearable style="width: 240px" @keyup.enter="handleQuery" /> <el-select v-model="queryParams.type" placeholder="设备类型" clearable style="width: 240px">
<el-option v-for="dict in props.deviceTypes" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item> </el-form-item>
<el-form-item label="状态" prop="status"> <el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="设备状态" clearable style="width: 240px"> <el-select v-model="queryParams.status" placeholder="设备状态" clearable style="width: 240px">
<el-option v-for="dict in pestStatus" :key="dict.value" :label="dict.label" :value="dict.value" /> <el-option v-for="dict in props.statusOptions" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
@ -22,18 +24,16 @@
</template> </template>
<script setup> <script setup>
import { useDict } from '@/utils/dict'
// props // props
const props = defineProps({ const props = defineProps({
queryParams: { type: Object, required: true }, queryParams: { type: Object, required: true },
deviceTypes: { type: Array, required: true },
statusOptions: { type: Array, required: true }
}) })
// //
const emits = defineEmits(['query', 'reset', 'update:dateRange']) const emits = defineEmits(['query', 'reset'])
//
const { pest_status: pestStatus } = useDict('pest_status')
// //
function handleQuery() { function handleQuery() {
@ -42,7 +42,6 @@ function handleQuery() {
// //
function resetQuery() { function resetQuery() {
emits('update:dateRange', [])
emits('reset') emits('reset')
} }
</script> </script>

@ -2,12 +2,12 @@
* @Author: chris * @Author: chris
* @Date: 2025-08-08 16:17:54 * @Date: 2025-08-08 16:17:54
* @LastEditors: chris * @LastEditors: chris
* @LastEditTime: 2025-09-09 09:50:07 * @LastEditTime: 2025-09-17 16:59:45
--> -->
<template> <template>
<el-table <el-table
v-loading="loading" v-loading="loading"
:data="orchardList" :data="list"
@selection-change="handleSelectionChange" @selection-change="handleSelectionChange"
row-key="orchardId" row-key="orchardId"
border border
@ -15,27 +15,30 @@
style="width: 100%" style="width: 100%"
> >
<el-table-column type="selection" width="50" align="center" /> <el-table-column type="selection" width="50" align="center" />
<el-table-column label="设备编号" align="center" prop="no" v-if="columns[0].visible"/> <el-table-column label="设备号" align="center" prop="deviceNo" v-if="columns[0].visible" :show-overflow-tooltip="true" />
<el-table-column label="设备名称" align="center" prop="deviceName" v-if="columns[1].visible" :show-overflow-tooltip="true" /> <el-table-column label="型号" align="center" prop="model" v-if="columns[1].visible" />
<el-table-column label="型号" align="center" prop="model" v-if="columns[2].visible" /> <el-table-column label="类型" align="center" prop="type" v-if="columns[2].visible">
<el-table-column label="ip地址" align="center" prop="address" v-if="columns[3].visible" /> <template #default="scope">
<el-table-column label="状态" align="center" v-if="columns[4].visible"> <span>{{ selectDictLabel(props.deviceTypes, scope.row.type) }}</span>
</template>
</el-table-column>
<el-table-column label="状态" align="center" v-if="columns[3].visible">
<template #default="scope"> <template #default="scope">
<el-tag :type="statusColorMap[scope.row.status]"> <el-tag :type="statusColorMap[scope.row.status]">
{{ scope.row.status === '0' ? '正常' : '停用' }} {{ props.statusOptions.find(item => item.value == scope.row.status)?.label || '未知' }}
</el-tag> </el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" v-if="columns[5].visible"> <el-table-column label="创建时间" align="center" prop="createTime" v-if="columns[4].visible">
<template #default="scope"> <template #default="scope">
<span>{{ parseTime(scope.row.createTime) }}</span> <span>{{ parseTime(scope.row.createTime) }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" align="center" fixed="right"> <el-table-column label="操作" align="center" fixed="right">
<template #default="scope"> <template #default="scope">
<el-tooltip content="查看" placement="top"> <!-- <el-tooltip content="查看" placement="top">
<el-button link type="primary" icon="Eye" @click="handleView(scope.row)" v-hasPermi="['business:orchard:query']"></el-button> <el-button link type="primary" icon="Eye" @click="handleView(scope.row)" v-hasPermi="['business:orchard:query']"></el-button>
</el-tooltip> </el-tooltip> -->
<el-tooltip content="修改" placement="top"> <el-tooltip content="修改" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['business:orchard:edit']"></el-button> <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['business:orchard:edit']"></el-button>
</el-tooltip> </el-tooltip>
@ -48,13 +51,15 @@
</template> </template>
<script setup> <script setup>
import { parseTime } from '@/utils/ruoyi' import { parseTime, selectDictLabel } from '@/utils/ruoyi'
import { statusColorMap } from '../config' import { statusColorMap } from '../config'
const props = defineProps({ const props = defineProps({
orchardList: { type: Array, required: true }, list: { type: Array, required: true },
loading: { type: Boolean, required: true }, loading: { type: Boolean, required: true },
columns: { type: Array, required: true } columns: { type: Array, required: true },
statusOptions: { type: Array, required: true },
deviceTypes: { type: Array, required: true }
}) })
const emits = defineEmits(['selection-change', 'view', 'update', 'delete']) const emits = defineEmits(['selection-change', 'view', 'update', 'delete'])

@ -0,0 +1,21 @@
/*
* @Author: chris
* @Date: 2025-09-05 10:12:41
* @LastEditors: chris
* @LastEditTime: 2025-09-18 17:26:54
*/
import request from "@/utils/request";
// 列配置
export const columnsConfig = [
{ key: 0, label: "设备号", visible: true },
{ key: 1, label: "型号", visible: true },
{ key: 2, label: "类型", visible: true },
{ key: 3, label: "状态", visible: true },
{ key: 4, label: "创建时间", visible: true },
];
// 状态颜色映射
export const statusColorMap = {
0: "success",
1: "danger",
};

@ -4,7 +4,8 @@
<pest-search-form <pest-search-form
v-show="showSearch" v-show="showSearch"
:query-params="queryParams" :query-params="queryParams"
v-model:dateRange="dateRange" :device-types="deviceTypes"
:status-options="statusOptions"
@query="handleQuery" @query="handleQuery"
@reset="resetQuery" @reset="resetQuery"
/> />
@ -22,10 +23,12 @@
/> />
<!-- 数据表格 --> <!-- 数据表格 -->
<pest-table <device-table
:list="deviceList" :list="deviceList"
:loading="loading" :loading="loading"
:columns="columns" :columns="columns"
:statusOptions="statusOptions"
:deviceTypes="deviceTypes"
@selection-change="handleSelectionChange" @selection-change="handleSelectionChange"
@view="handleView" @view="handleView"
@update="handleUpdate" @update="handleUpdate"
@ -35,30 +38,33 @@
<!-- 分页 --> <!-- 分页 -->
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" /> <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
<!-- 添加或修改果园对话框 --> <!-- 添加或修改果园对话框 -->
<pest-form-dialog <device-form-dialog
v-model:open="open" v-model:open="open"
:device-types="deviceTypes"
:status-options="statusOptions"
:form="form" :form="form"
:title="title" :title="title"
@success="getList" @success="getList"
/> />
<!-- 查看果园对话框 --> <!-- 查看果园对话框 -->
<pest-view-dialog <device-view-dialog
v-model:open="viewOpen" v-model:open="viewOpen"
:pest-id="currentId" :pest-id="currentId"
/> />
</div> </div>
</template> </template>
<script setup name="Pest"> <script setup name="Device">
import { listPest, delPest } from '@/api/pest' import { listDevice, delDevice } from '@/api/device'
import { columnsConfig } from './config.js' import { columnsConfig } from './config.js'
import Pagination from '@/components/Pagination' import Pagination from '@/components/Pagination'
import PestSearchForm from './components/PestSearchForm' import PestSearchForm from './components/PestSearchForm'
import ActionButtons from './components/ActionButtons' import ActionButtons from './components/ActionButtons'
import PestTable from './components/PestTable' import DeviceTable from './components/PestTable'
import PestFormDialog from './components/PestFormDialog' import DeviceFormDialog from './components/PestFormDialog'
import PestViewDialog from './components/PestViewDialog' import DeviceViewDialog from './components/PestViewDialog'
import { useDict } from '@/utils/dict'
// //
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
@ -76,17 +82,26 @@ const title = ref('')
const dateRange = ref([]) const dateRange = ref([])
const currentId = ref(null) const currentId = ref(null)
//
const { monitor_device_type: deviceTypes } = useDict('monitor_device_type')
const { device_status: statusOptions } = useDict('device_status')
// //
const data = reactive({ const data = reactive({
form: {}, form: {
deviceNo: '',
deviceAddr: '',
model: '',
installPointDes: '',
imgUrl: '',
type: null,
status: '0'
},
queryParams: { queryParams: {
type: null,
status: null,
pageNum: 1, pageNum: 1,
pageSize: 10, pageSize: 10,
deviceName: undefined,
manager: undefined,
status: undefined,
beginTime: undefined,
endTime: undefined
} }
}) })
@ -101,20 +116,11 @@ onMounted(() => {
async function getList() { async function getList() {
loading.value = true loading.value = true
try { try {
// const res = await listDevice(queryParams.value)
if (dateRange.value && dateRange.value.length) {
queryParams.value.beginTime = dateRange.value[0]
queryParams.value.endTime = dateRange.value[1]
} else {
queryParams.value.beginTime = undefined
queryParams.value.endTime = undefined
}
const res = await listPest(queryParams.value)
deviceList.value = res.rows || [] deviceList.value = res.rows || []
total.value = res.total || 0 total.value = res.total || 0
} catch (error) { } catch (error) {
proxy.$model.msgError('获取虫情设备数据失败: ' + (error.message || '未知错误')) proxy.$modal.msgError('获取虫情设备数据失败: ' + (error.message || '未知错误'))
console.error('Failed to get pest list:', error) console.error('Failed to get pest list:', error)
} finally { } finally {
loading.value = false loading.value = false
@ -138,7 +144,7 @@ function resetQuery() {
function handleAdd() { function handleAdd() {
open.value = true open.value = true
title.value = '新增虫情设备' title.value = '新增虫情设备'
form.value = { status: '0' } // // form.value = { status: '1' } //
} }
/** 修改按钮操作 */ /** 修改按钮操作 */
@ -165,21 +171,22 @@ async function handleDelete(row) {
try { try {
await proxy.$confirm(`是否确认删除选中的${deviceNames.join(', ')}虫情设备数据?`) await proxy.$confirm(`是否确认删除选中的${deviceNames.join(', ')}虫情设备数据?`)
await delPest(ids) await delDevice(ids)
proxy.$model.msgSuccess('删除成功') proxy.$modal.msgSuccess('删除成功')
getList() getList()
selectedRows.value = [] // selectedRows.value = [] //
} catch (error) { } catch (error) {
// //
if (error !== 'cancel') { if (error !== 'cancel') {
proxy.$model.msgError('删除失败: ' + (error.message || '未知错误')) proxy.$modal.msgError('删除失败: ' + (error.message || '未知错误'))
} }
} }
} }
/** 导出按钮操作 */ /** 导出按钮操作 */
function handleExport() { async function handleExport() {
proxy.download('/business/pest/export', queryParams.value, `虫情设备数据_${new Date().getTime()}.xlsx`) const fileName = await proxy.download('/business/device/export', queryParams.value, `虫情设备数据_${new Date().getTime()}.xlsx`)
fileName && proxy.$download.name(fileName)
} }
/** 选择框选中数据变化 */ /** 选择框选中数据变化 */

@ -95,7 +95,7 @@ const loginRules = {
const codeUrl = ref(""); const codeUrl = ref("");
const loading = ref(false); const loading = ref(false);
// //
const captchaEnabled = ref(true); const captchaEnabled = ref(false);
// //
const register = ref(false); const register = ref(false);
const redirect = ref(undefined); const redirect = ref(undefined);
@ -106,8 +106,8 @@ watch(route, (newRoute) => {
function handleLogin() { function handleLogin() {
// TODO // TODO
router.push({ path: "/" }); // router.push({ path: "/" });
return; // return;
proxy.$refs.loginRef.validate(valid => { proxy.$refs.loginRef.validate(valid => {
if (valid) { if (valid) {
@ -166,8 +166,8 @@ function getCookie() {
} }
// TODO: // TODO:
// getCode(); captchaEnabled.value && getCode();
// getCookie(); getCookie();
</script> </script>
<style lang='scss' scoped> <style lang='scss' scoped>

@ -0,0 +1,286 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="用户昵称" prop="nickname">
<el-input
v-model="queryParams.nickname"
placeholder="请输入用户昵称"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="手机号" prop="mobile">
<el-input
v-model="queryParams.mobile"
placeholder="请输入手机号"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="openId" prop="openId">
<el-input
v-model="queryParams.openId"
placeholder="请输入openId"
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-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="Plus"
@click="handleAdd"
v-hasPermi="['business:member:add']"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="Edit"
:disabled="single"
@click="handleUpdate"
v-hasPermi="['business:member:edit']"
>修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="Delete"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['business:member:remove']"
>删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="Download"
@click="handleExport"
v-hasPermi="['business:member:export']"
>导出</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="memberList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="用户昵称" align="center" prop="nickname" />
<el-table-column label="用户头像" align="center" prop="avatarUrl" width="100">
<template #default="scope">
<image-preview :src="scope.row.avatarUrl" :width="50" :height="50"/>
</template>
</el-table-column>
<el-table-column label="性别" align="center" prop="gender" />
<el-table-column label="手机号" align="center" prop="mobile" />
<el-table-column label="openId" align="center" prop="openId" />
<el-table-column label="unionId" align="center" prop="unionId" />
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['business:member:edit']"></el-button>
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['business:member:remove']"></el-button>
</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-dialog :title="title" v-model="open" width="500px" append-to-body>
<el-form ref="memberRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="用户昵称" prop="nickname">
<el-input v-model="form.nickname" placeholder="请输入用户昵称" />
</el-form-item>
<el-form-item label="用户头像" prop="avatarUrl">
<image-upload :limit="1" v-model="form.avatarUrl"/>
</el-form-item>
<el-form-item label="手机号" prop="mobile">
<el-input v-model="form.mobile" placeholder="请输入手机号" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Member">
import { listMember, getMember, delMember, addMember, updateMember } from "@/api/memberPush"
const { proxy } = getCurrentInstance()
const memberList = ref([])
const open = ref(false)
const loading = ref(true)
const showSearch = ref(true)
const ids = ref([])
const single = ref(true)
const multiple = ref(true)
const total = ref(0)
const title = ref("")
const data = reactive({
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
nickname: null,
mobile: null,
openId: null,
},
rules: {
nickname: [
{ required: true, message: "用户昵称不能为空", trigger: "blur" }
],
mobile: [
{ required: true, message: "手机号不能为空", trigger: "blur" }
],
openId: [
{ required: true, message: "openId不能为空", trigger: "blur" }
],
}
})
const { queryParams, form, rules } = toRefs(data)
/** 查询虫情预警推送人员接收列列表 */
function getList() {
loading.value = true
listMember(queryParams.value).then(response => {
memberList.value = response.rows
total.value = response.total
loading.value = false
})
}
//
function cancel() {
open.value = false
reset()
}
//
function reset() {
form.value = {
id: null,
deptId: null,
nickname: null,
avatarUrl: null,
gender: null,
country: null,
province: null,
city: null,
mobile: null,
openId: null,
unionId: null,
createBy: null,
createTime: null,
updateBy: null,
updateTime: null,
remark: null
}
proxy.resetForm("memberRef")
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1
getList()
}
/** 重置按钮操作 */
function resetQuery() {
proxy.resetForm("queryRef")
handleQuery()
}
//
function handleSelectionChange(selection) {
ids.value = selection.map(item => item.id)
single.value = selection.length != 1
multiple.value = !selection.length
}
/** 新增按钮操作 */
function handleAdd() {
reset()
open.value = true
title.value = "添加虫情预警推送人员接收列"
}
/** 修改按钮操作 */
function handleUpdate(row) {
reset()
const _id = row.id || ids.value
getMember(_id).then(response => {
form.value = response.data
open.value = true
title.value = "修改虫情预警推送人员接收列"
})
}
/** 提交按钮 */
function submitForm() {
proxy.$refs["memberRef"].validate(valid => {
if (valid) {
if (form.value.id != null) {
updateMember(form.value).then(response => {
proxy.$modal.msgSuccess("修改成功")
open.value = false
getList()
})
} else {
addMember(form.value).then(response => {
proxy.$modal.msgSuccess("新增成功")
open.value = false
getList()
})
}
}
})
}
/** 删除按钮操作 */
function handleDelete(row) {
const _ids = row.id || ids.value
proxy.$modal.confirm('是否确认删除虫情预警推送人员接收列编号为"' + _ids + '"的数据项?').then(function() {
return delMember(_ids)
}).then(() => {
getList()
proxy.$modal.msgSuccess("删除成功")
}).catch(() => {})
}
/** 导出按钮操作 */
function handleExport() {
proxy.download('business/member/export', {
...queryParams.value
}, `member_${new Date().getTime()}.xlsx`)
}
getList()
</script>

@ -1,125 +0,0 @@
<template>
<el-dialog :title="title" v-model="dialogOpen" width="600px" append-to-body>
<el-form :model="form" :rules="rules" ref="deviceRef" label-width="80px">
<el-row :gutter="10">
<el-col :span="24">
<el-form-item label="设备名称" prop="deviceName">
<el-input v-model="form.deviceName" placeholder="请输入设备名称" maxlength="50" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="设备型号" prop="model">
<el-input v-model="form.model" placeholder="请输入设备型号" maxlength="50" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="ip地址" prop="address">
<el-input v-model="form.address" placeholder="请输入ip地址" maxlength="100" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="状态">
<el-radio-group v-model="form.status">
<el-radio :value="'0'">正常</el-radio>
<el-radio :value="'1'">停用</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="备注">
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注内容"></el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancel"> </el-button>
<el-button type="primary" @click="submitForm"> </el-button>
</div>
</template>
</el-dialog>
</template>
<script setup>
import { addPest, updatePest } from '@/api/pest'
const props = defineProps({
open: { type: Boolean, required: true },
form: { type: Object, required: true },
title: { type: String, required: true }
})
const emits = defineEmits(['update:open', 'success'])
// open
const dialogOpen = ref(props.open)
//
const rules = {
deviceName: [{ required: true, message: '设备名称不能为空', trigger: 'blur' }],
address: [{ required: true, message: 'ip地址不能为空', trigger: 'blur' }],
model: [{ required: true, message: '设备型号不能为空', trigger: 'blur' }],
status: [{ required: true, message: '请选择状态', trigger: 'blur' }]
}
//
const deviceRef = ref(null)
const { proxy } = getCurrentInstance()
onMounted(() => {
//
setTimeout(() => {
deviceRef.value?.resetFields()
}, 0)
})
// props.open
watch(
() => props.open,
(newValue) => {
dialogOpen.value = newValue
}
)
//
watch(
dialogOpen,
(newValue) => {
if (newValue !== props.open) {
emits('update:open', newValue)
}
}
)
// emits
//
async function submitForm() {
try {
await deviceRef.value.validate()
if (props.form.id) {
await updatePest(props.form)
proxy.$model.msgSuccess('修改成功')
} else {
await addPest(props.form)
proxy.$model.msgSuccess('新增成功')
}
dialogOpen.value = false
emits('success')
} catch (error) {
//
if (error !== 'cancel') {
proxy.$model.msgError('操作失败: ' + (error.message || '未知错误'))
}
}
}
//
function cancel() {
dialogOpen.value = false
deviceRef.value?.resetFields()
}
</script>

@ -1,21 +0,0 @@
/*
* @Author: chris
* @Date: 2025-09-05 10:12:41
* @LastEditors: chris
* @LastEditTime: 2025-09-09 09:50:16
*/
// 列配置
export const columnsConfig = [
{ key: 0, label: "设备编号", visible: true },
{ key: 1, label: "设备名称", visible: true },
{ key: 2, label: "型号", visible: true },
{ key: 3, label: "ip地址", visible: true },
{ key: 4, label: "状态", visible: true },
{ key: 5, label: "创建时间", visible: true },
];
// 状态颜色映射
export const statusColorMap = {
0: "success",
1: "danger",
};

@ -4,15 +4,18 @@
<!-- 左侧部分 --> <!-- 左侧部分 -->
<el-col :span="4" class="page-left"> <el-col :span="4" class="page-left">
<el-card class="device-three-card"> <el-card class="device-three-card">
<device-flat-list /> <template #header>
<span class="model-title">设备列表</span>
</template>
<device-flat-list :list="deviceList" @change="handleChangeDevice" />
</el-card> </el-card>
</el-col> </el-col>
<!-- 右侧部分 --> <!-- 右侧部分 -->
<el-col :span="20" class="page-right"> <el-col :span="20" class="page-right">
<div class="page-title text-center"> <div class="page-title text-center" v-if="false">
检测设备名称 检测设备名称
</div> </div>
<el-card class="run-model-container"> <el-card class="run-model-container" v-if="false">
<template #header> <template #header>
<span class="model-title">运行模式</span> <span class="model-title">运行模式</span>
<el-segmented v-model="modelValue" :options="modelOptions" size="default" @change="handleModelChange"> <el-segmented v-model="modelValue" :options="modelOptions" size="default" @change="handleModelChange">
@ -38,7 +41,7 @@
<el-row :gutter="10" justify="space-between"> <el-row :gutter="10" justify="space-between">
<el-col :span="14" class="model-header__left"> <el-col :span="14" class="model-header__left">
<span class="model-title">历史数据</span> <span class="model-title">历史数据</span>
<el-segmented v-model="historyValue" :options="historyOptions" size="default"> <el-segmented v-model="historyValue" :options="historyOptions" size="default" v-if="false">
<template #default="{ item }"> <template #default="{ item }">
<p>{{ item.label }}</p> <p>{{ item.label }}</p>
</template> </template>
@ -52,8 +55,9 @@
range-separator="至" range-separator="至"
start-placeholder="开始日期" start-placeholder="开始日期"
end-placeholder="结束日期" end-placeholder="结束日期"
value-format="YYYY-MM-DD"
/> />
<el-button type="primary">查询</el-button> <el-button type="primary" @click="getDeviceDataList"></el-button>
</el-col> </el-col>
</el-row> </el-row>
</template> </template>
@ -64,11 +68,14 @@
<el-table-column prop="userName" label="操作用户" /> <el-table-column prop="userName" label="操作用户" />
<el-table-column prop="createTime" label="操作时间" /> <el-table-column prop="createTime" label="操作时间" />
</el-table> </el-table>
<el-table :data="operationData" style="width: 100%" v-if="historyValue === '1'"> <el-table :data="operationData" v-loading="loading" style="width: 100%" v-if="historyValue === '1'">
<el-table-column prop="deviceAddr" label="地址码" /> <el-table-column prop="name" label="名称" />
<el-table-column prop="value" label="详情" /> <el-table-column prop="value" label="数量" />
<el-table-column prop="createTime" label="操作时间" /> <el-table-column prop="unit" label="单位" />
<el-table-column prop="createTime" label="时间" />
</el-table> </el-table>
<!-- 分页 -->
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getDeviceDataList" />
</div> </div>
</el-card> </el-card>
</el-col> </el-col>
@ -78,8 +85,11 @@
<script setup> <script setup>
import DeviceFlatList from '@/components/deviceFlatList'; import DeviceFlatList from '@/components/deviceFlatList';
import Pagination from '@/components/Pagination'
import useSettingsStore from '@/store/modules/settings'; import useSettingsStore from '@/store/modules/settings';
import pestImg from '@/assets/images/devices/worm.png'; import pestImg from '@/assets/images/devices/pest.png';
import { listDevice } from '@/api/device';
import { listDeviceData } from '@/api/deviceData';
const settingsStore = useSettingsStore(); const settingsStore = useSettingsStore();
@ -93,13 +103,73 @@ const historyOptions = readonly([
{ label: '历史数据', value: '1' }, { label: '历史数据', value: '1' },
]) ])
const { proxy } = getCurrentInstance()
const total = ref(0)
const modelValue = ref('0') const modelValue = ref('0')
const historyValue = ref('0') const historyValue = ref('1')
const dateRange = ref('') const dateRange = ref(null)
const operationData = ref([]) const operationData = ref([])
const loading = ref(false)
const liveLoading = ref(false)
const deviceList = ref([])
const liveData = ref({})
const queryParams = ref({
pageNum: 1,
pageSize: 20,
deviceType: 1,
deviceId: null,
beginTime: null,
endTime: null
})
const hasTags = computed(() => settingsStore.tagsView); const hasTags = computed(() => settingsStore.tagsView);
watch(() => dateRange.value, () => {
if (!dateRange.value.length) return;
queryParams.value.beginTime = dateRange.value[0]
queryParams.value.endTime = dateRange.value[1]
})
getDeviceList();
getDeviceDataList();
async function getDeviceList () {
const params = {
pageNum: 1,
pageSize: 20,
}
loading.value = true;
try {
const res = await listDevice(params);
deviceList.value = res.rows;
} catch (error) {
proxy.$modal.msgError('获取虫情设备数据失败: ' + (error.message || '未知错误'))
console.error('Failed to get pest list:', error)
} finally {
loading.value = false;
}
}
async function getDeviceDataList () {
loading.value = true;
try {
const res = await listDeviceData(queryParams.value);
operationData.value = res.rows;
total.value = res.total;
} catch (error) {
proxy.$modal.msgError('获取虫情设备实时数据失败: ' + (error.message || '未知错误'))
console.error('Failed to get device live data:', error)
} finally {
loading.value = false;
}
}
function handleChangeDevice(device) {
queryParams.value.deviceId = device.id
getDeviceDataList()
}
function handleModelChange(val) { function handleModelChange(val) {
console.log(val) console.log(val)
} }
@ -119,7 +189,7 @@ function handleModelChange(val) {
} }
.run-model-container { .run-model-container {
@apply h-600px; @apply h-450px;
:deep(.el-card__body) { :deep(.el-card__body) {
height: calc(100% - 54px); height: calc(100% - 54px);
@ -163,7 +233,7 @@ function handleModelChange(val) {
} }
.history-container { .history-container {
@apply mt-10px flex-1; @apply flex-1;
} }
.page-title { .page-title {

@ -2,7 +2,7 @@
* @Author: chris * @Author: chris
* @Date: 2025-08-08 16:17:54 * @Date: 2025-08-08 16:17:54
* @LastEditors: chris * @LastEditors: chris
* @LastEditTime: 2025-09-09 16:50:46 * @LastEditTime: 2025-09-19 17:36:13
--> -->
<template> <template>
<el-table <el-table
@ -17,7 +17,8 @@
<el-table-column type="selection" width="50" align="center" /> <el-table-column type="selection" width="50" align="center" />
<el-table-column label="参数名" align="center" prop="name" v-if="columns[0].visible"/> <el-table-column label="参数名" align="center" prop="name" v-if="columns[0].visible"/>
<el-table-column label="数值" align="center" prop="value" v-if="columns[1].visible" :show-overflow-tooltip="true" /> <el-table-column label="数值" align="center" prop="value" v-if="columns[1].visible" :show-overflow-tooltip="true" />
<el-table-column label="创建时间" align="center" prop="createTime" v-if="columns[2].visible"> <el-table-column label="单位" align="center" prop="unit" v-if="columns[2].visible" />
<el-table-column label="创建时间" align="center" prop="createTime" v-if="columns[3].visible">
<template #default="scope"> <template #default="scope">
<span>{{ parseTime(scope.row.createTime) }}</span> <span>{{ parseTime(scope.row.createTime) }}</span>
</template> </template>
@ -34,7 +35,7 @@ const props = defineProps({
columns: { type: Array, required: true } columns: { type: Array, required: true }
}) })
const emits = defineEmits(['selection-change', 'view', 'update', 'delete']) const emits = defineEmits(['selection-change'])
function handleSelectionChange(selection) { function handleSelectionChange(selection) {
emits('selection-change', selection) emits('selection-change', selection)

@ -2,16 +2,13 @@
* @Author: chris * @Author: chris
* @Date: 2025-08-18 09:27:23 * @Date: 2025-08-18 09:27:23
* @LastEditors: chris * @LastEditors: chris
* @LastEditTime: 2025-09-12 14:51:11 * @LastEditTime: 2025-09-19 17:28:35
--> -->
<script setup> <script setup>
const props = defineProps({ const props = defineProps({
params: { params: {
type: Object, type: Object,
default: () => ({ default: () => ({})
nodeId: null,
dateRange: []
})
}, },
soilOptions: { soilOptions: {
type: Object, type: Object,
@ -19,24 +16,33 @@ const props = defineProps({
} }
}) })
const emits = defineEmits(['update:params']) const emits = defineEmits(['update:params', 'reset', 'query', 'export'])
const queryRef = ref() const queryRef = ref()
const queryParams = reactive({...props.params}); const queryParams = ref({...props.params});
const dateRange = ref(null);
watch(() => queryParams, (newVal) => { watch(() => dateRange.value, () => {
emits('update:params', newVal) if (!dateRange.value.length) return;
queryParams.value.beginTime = dateRange.value[0]
queryParams.value.endTime = dateRange.value[1]
}) })
watch(() => queryParams.value, (newVal) => {
emits('update:params', newVal)
}, {deep: true})
watch(() => props.params, (newVal) => { watch(() => props.params, (newVal) => {
Object.assign(queryParams, newVal) Object.assign(queryParams.value, newVal)
}) })
function resetQuery() { function resetQuery() {
emits('reset') dateRange.value = [];
emits('reset', queryRef.value)
} }
function handleQuery() { function handleQuery() {
// query
emits('query') emits('query')
} }
@ -48,14 +54,14 @@ function exportData() {
<template> <template>
<el-form :model="queryParams" ref="queryRef" :inline="true" label-width="68px"> <el-form :model="queryParams" ref="queryRef" :inline="true" label-width="68px">
<el-form-item label="参数" style="width: 260px;"> <el-form-item label="参数" style="width: 260px;">
<el-select v-model="queryParams.nodeId" placeholder="请选择参数"> <el-select v-model="queryParams.identifier" placeholder="请选择参数">
<el-option v-for="value in soilOptions" :key="value.id" :label="value.name" :value="value.id"></el-option> <el-option v-for="item in soilOptions" :key="item.value" :label="item.label" :value="item.value"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="记录时间"> <el-form-item label="记录时间">
<el-date-picker <el-date-picker
style="width: 300px" style="width: 300px"
v-model="queryParams.dateRange" v-model="dateRange"
value-format="YYYY-MM-DD" value-format="YYYY-MM-DD"
type="daterange" type="daterange"
range-separator="至" range-separator="至"
@ -70,6 +76,3 @@ function exportData() {
</el-form-item> </el-form-item>
</el-form> </el-form>
</template> </template>
<style lang="scss" scoped>
</style>

@ -2,13 +2,14 @@
* @Author: chris * @Author: chris
* @Date: 2025-09-05 10:12:41 * @Date: 2025-09-05 10:12:41
* @LastEditors: chris * @LastEditors: chris
* @LastEditTime: 2025-09-09 16:17:59 * @LastEditTime: 2025-09-19 17:35:58
*/ */
// 列配置 // 列配置
export const columnsConfig = [ export const columnsConfig = [
{ key: 0, label: "参数名称", visible: true }, { key: 0, label: "参数名称", visible: true },
{ key: 1, label: "数值", visible: true }, { key: 1, label: "数值", visible: true },
{ key: 2, label: "记录时间", visible: true }, { key: 2, label: "单位", visible: true },
{ key: 3, label: "记录时间", visible: true },
]; ];
// 状态颜色映射 // 状态颜色映射
@ -16,3 +17,13 @@ export const statusColorMap = {
0: "success", 0: "success",
1: "danger", 1: "danger",
}; };
export const defaultQueryParams = {
pageNum: 1,
pageSize: 10,
type: 2,
deviceId: null,
identifier: null,
beginTime: null,
endTime: null,
};

@ -2,33 +2,71 @@
* @Author: chris * @Author: chris
* @Date: 2025-09-11 17:11:16 * @Date: 2025-09-11 17:11:16
* @LastEditors: chris * @LastEditors: chris
* @LastEditTime: 2025-09-12 14:48:24 * @LastEditTime: 2025-09-19 17:26:21
--> -->
<script setup> <script setup>
import HistoryTable from './components/HistoryTable.vue'; import HistoryTable from './components/HistoryTable.vue';
import SearchForm from './components/SearchForm.vue'; import SearchForm from './components/SearchForm.vue';
import { columnsConfig } from './config'; import { columnsConfig, defaultQueryParams } from './config';
import { useSettings } from '@/hooks/useSettings'; import { useSettings } from '@/hooks/useSettings';
import { listDeviceData, exportDeviceData } from '@/api/deviceData';
import { useDict } from '@/utils/dict.js'
const { hasTags } = useSettings(); const { hasTags } = useSettings();
const { proxy } = getCurrentInstance();
const historyList = ref([]); const historyList = ref([]);
const queryParams = ref({}); const total = ref(0);
const loading = ref(false);
const queryParams = ref({...defaultQueryParams});
getHistoryList(); const { soil_type: soilTypeOptions } = useDict('soil_type')
function getHistoryList() { getDeviceDataList();
async function getDeviceDataList () {
loading.value = true;
try {
const res = await listDeviceData(queryParams.value);
historyList.value = res.rows;
total.value = res.total;
} catch (error) {
proxy.$modal.msgError('获取土壤墒情设备实时数据失败: ' + (error.message || '未知错误'))
console.error('Failed to get device live data:', error)
} finally {
loading.value = false;
}
} }
function handleSelectionChange(selection) { function handleSelectionChange(selection) {
console.log(selection) console.log(selection)
} }
function handleQuery() {
queryParams.value.pageNum = 1
getDeviceDataList();
}
function handleReset(queryRef) {
queryParams.value = {...defaultQueryParams}
queryRef.resetFields()
getDeviceDataList()
}
async function handleExport() {
const fileName = await proxy.download('/business/device-data/export', queryParams.value, `土壤墒情设备数据_${new Date().getTime()}.xlsx`)
fileName && proxy.$download.name(fileName)
}
</script> </script>
<template> <template>
<div :class="['soil-history-page', 'page-height', { 'hasTagsView': hasTags }]"> <div :class="['soil-history-page', 'page-height', { 'hasTagsView': hasTags }]">
<search-form v-model:params="queryParams" class="mb-12px" /> <!-- 搜索表单 -->
<history-table :dataList="historyList" :columns="columnsConfig" @selection-change="handleSelectionChange" /> <search-form v-model:params="queryParams" :soil-options="soilTypeOptions" class="mb-12px" @query="handleQuery" @reset="handleReset" @export="handleExport" />
<!-- 历史记录表格 -->
<history-table :dataList="historyList" :columns="columnsConfig" :loading="loading" @selection-change="handleSelectionChange" />
<!-- 分页 -->
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getDeviceDataList" />
</div> </div>
</template> </template>

@ -2,7 +2,7 @@
* @Author: chris * @Author: chris
* @Date: 2025-08-08 16:17:54 * @Date: 2025-08-08 16:17:54
* @LastEditors: chris * @LastEditors: chris
* @LastEditTime: 2025-09-09 16:50:46 * @LastEditTime: 2025-09-19 17:34:55
--> -->
<template> <template>
<el-table <el-table
@ -17,7 +17,8 @@
<el-table-column type="selection" width="50" align="center" /> <el-table-column type="selection" width="50" align="center" />
<el-table-column label="参数名" align="center" prop="name" v-if="columns[0].visible"/> <el-table-column label="参数名" align="center" prop="name" v-if="columns[0].visible"/>
<el-table-column label="数值" align="center" prop="value" v-if="columns[1].visible" :show-overflow-tooltip="true" /> <el-table-column label="数值" align="center" prop="value" v-if="columns[1].visible" :show-overflow-tooltip="true" />
<el-table-column label="创建时间" align="center" prop="createTime" v-if="columns[2].visible"> <el-table-column label="单位" align="center" prop="unit" v-if="columns[2].visible" />
<el-table-column label="创建时间" align="center" prop="createTime" v-if="columns[3].visible">
<template #default="scope"> <template #default="scope">
<span>{{ parseTime(scope.row.createTime) }}</span> <span>{{ parseTime(scope.row.createTime) }}</span>
</template> </template>
@ -34,7 +35,7 @@ const props = defineProps({
columns: { type: Array, required: true } columns: { type: Array, required: true }
}) })
const emits = defineEmits(['selection-change', 'view', 'update', 'delete']) const emits = defineEmits(['selection-change'])
function handleSelectionChange(selection) { function handleSelectionChange(selection) {
emits('selection-change', selection) emits('selection-change', selection)

@ -2,16 +2,13 @@
* @Author: chris * @Author: chris
* @Date: 2025-08-18 09:27:23 * @Date: 2025-08-18 09:27:23
* @LastEditors: chris * @LastEditors: chris
* @LastEditTime: 2025-09-09 16:54:23 * @LastEditTime: 2025-09-19 17:33:45
--> -->
<script setup> <script setup>
const props = defineProps({ const props = defineProps({
params: { params: {
type: Object, type: Object,
default: () => ({ default: () => ({})
nodeId: null,
dateRange: []
})
}, },
weatherOptions: { weatherOptions: {
type: Object, type: Object,
@ -19,21 +16,29 @@ const props = defineProps({
} }
}) })
const emits = defineEmits(['update:params']) const emits = defineEmits(['update:params', 'reset', 'query', 'export'])
const queryRef = ref() const queryRef = ref()
const queryParams = reactive({...props.params}); const queryParams = ref({...props.params});
const dateRange = ref([]);
watch(() => queryParams, (newVal) => { watch(() => dateRange.value, () => {
emits('update:params', newVal) if (!dateRange.value.length) return;
queryParams.value.beginTime = dateRange.value[0]
queryParams.value.endTime = dateRange.value[1]
}) })
watch(() => queryParams.value, (newVal) => {
emits('update:params', newVal)
}, {deep: true})
watch(() => props.params, (newVal) => { watch(() => props.params, (newVal) => {
Object.assign(queryParams, newVal) Object.assign(queryParams.value, newVal)
}) })
function resetQuery() { function resetQuery() {
emits('reset') dateRange.value = [];
emits('reset', queryRef.value)
} }
function handleQuery() { function handleQuery() {
@ -48,14 +53,14 @@ function exportData() {
<template> <template>
<el-form :model="queryParams" ref="queryRef" :inline="true" label-width="68px"> <el-form :model="queryParams" ref="queryRef" :inline="true" label-width="68px">
<el-form-item label="参数" style="width: 260px;"> <el-form-item label="参数" style="width: 260px;">
<el-select v-model="queryParams.nodeId" placeholder="请选择参数"> <el-select v-model="queryParams.identifier" placeholder="请选择参数">
<el-option v-for="value in weatherOptions" :key="value.id" :label="value.name" :value="value.id"></el-option> <el-option v-for="item in weatherOptions" :key="item.value" :label="item.label" :value="item.value"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="记录时间"> <el-form-item label="记录时间">
<el-date-picker <el-date-picker
style="width: 300px" style="width: 300px"
v-model="queryParams.dateRange" v-model="dateRange"
value-format="YYYY-MM-DD" value-format="YYYY-MM-DD"
type="daterange" type="daterange"
range-separator="至" range-separator="至"
@ -70,6 +75,3 @@ function exportData() {
</el-form-item> </el-form-item>
</el-form> </el-form>
</template> </template>
<style lang="scss" scoped>
</style>

@ -2,13 +2,14 @@
* @Author: chris * @Author: chris
* @Date: 2025-09-05 10:12:41 * @Date: 2025-09-05 10:12:41
* @LastEditors: chris * @LastEditors: chris
* @LastEditTime: 2025-09-09 16:17:59 * @LastEditTime: 2025-09-19 17:35:15
*/ */
// 列配置 // 列配置
export const columnsConfig = [ export const columnsConfig = [
{ key: 0, label: "参数名称", visible: true }, { key: 0, label: "参数名称", visible: true },
{ key: 1, label: "数值", visible: true }, { key: 1, label: "数值", visible: true },
{ key: 2, label: "记录时间", visible: true }, { key: 2, label: "单位", visible: true },
{ key: 3, label: "记录时间", visible: true },
]; ];
// 状态颜色映射 // 状态颜色映射
@ -16,3 +17,13 @@ export const statusColorMap = {
0: "success", 0: "success",
1: "danger", 1: "danger",
}; };
export const defaultQueryParams = {
pageNum: 1,
pageSize: 10,
type: 3,
deviceId: null,
identifier: null,
beginTime: null,
endTime: null,
};

@ -2,38 +2,76 @@
* @Author: chris * @Author: chris
* @Date: 2025-09-11 17:11:16 * @Date: 2025-09-11 17:11:16
* @LastEditors: chris * @LastEditors: chris
* @LastEditTime: 2025-09-11 17:21:28 * @LastEditTime: 2025-09-19 11:12:17
--> -->
<script setup> <script setup>
import HistoryTable from './components/HistoryTable.vue'; import HistoryTable from './components/HistoryTable.vue';
import SearchForm from './components/SearchForm.vue'; import SearchForm from './components/SearchForm.vue';
import { columnsConfig } from './config'; import { columnsConfig, defaultQueryParams } from './config';
import { useSettings } from '@/hooks/useSettings'; import { useSettings } from '@/hooks/useSettings';
import { listDeviceData, exportDeviceData } from '@/api/deviceData';
import { useDict } from '@/utils/dict.js'
const { hasTags } = useSettings(); const { hasTags } = useSettings();
const { proxy } = getCurrentInstance();
const historyList = ref([]); const historyList = ref([]);
const queryParams = ref({}); const total = ref(0);
const loading = ref(false);
const queryParams = ref({...defaultQueryParams});
getHistoryList(); const { weather_type: weatherTypeOptions } = useDict('weather_type')
function getHistoryList() { getDeviceDataList();
async function getDeviceDataList () {
loading.value = true;
try {
const res = await listDeviceData(queryParams.value);
historyList.value = res.rows;
total.value = res.total;
} catch (error) {
proxy.$modal.msgError('获取土壤墒情设备实时数据失败: ' + (error.message || '未知错误'))
console.error('Failed to get device live data:', error)
} finally {
loading.value = false;
}
} }
function handleSelectionChange(selection) { function handleSelectionChange(selection) {
console.log(selection) console.log(selection)
} }
function handleQuery() {
queryParams.value.pageNum = 1
getDeviceDataList();
}
function handleReset(queryRef) {
queryParams.value = {...defaultQueryParams}
queryRef.resetFields()
getDeviceDataList()
}
async function handleExport() {
const fileName = await proxy.download('/business/device-data/export', queryParams.value, `土壤墒情设备数据_${new Date().getTime()}.xlsx`)
fileName && proxy.$download.name(fileName)
}
</script> </script>
<template> <template>
<div :class="['weather-history-page', 'page-height', { 'hasTagsView': hasTags }]"> <div :class="['soil-history-page', 'page-height', { 'hasTagsView': hasTags }]">
<search-form v-model:params="queryParams" class="mb-12px" /> <!-- 搜索表单 -->
<history-table :dataList="historyList" :columns="columnsConfig" @selection-change="handleSelectionChange" /> <search-form v-model:params="queryParams" :weather-options="weatherTypeOptions" class="mb-12px" @query="handleQuery" @reset="handleReset" @export="handleExport" />
<!-- 历史记录表格 -->
<history-table :dataList="historyList" :columns="columnsConfig" :loading="loading" @selection-change="handleSelectionChange" />
<!-- 分页 -->
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getDeviceDataList" />
</div> </div>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.weather-history-page { .soil-history-page {
@apply p-20px; @apply p-20px;
} }
</style> </style>

@ -2,7 +2,7 @@
* @Author: chris * @Author: chris
* @Date: 2025-09-09 15:30:24 * @Date: 2025-09-09 15:30:24
* @LastEditors: chris * @LastEditors: chris
* @LastEditTime: 2025-09-12 17:36:39 * @LastEditTime: 2025-09-22 17:25:39
--> -->
<script setup> <script setup>
import weatherImg from '@/assets/images/devices/weather.png' import weatherImg from '@/assets/images/devices/weather.png'
@ -11,7 +11,7 @@ const testDataList = [
{ {
id: 1, id: 1,
title: '温度', title: '温度',
type: 'temperature', type: 'Temperature',
value: '25', value: '25',
icon: 'temperature2', icon: 'temperature2',
color: '#FF6B6B', color: '#FF6B6B',
@ -19,58 +19,40 @@ const testDataList = [
}, },
{ {
id: 2, id: 2,
title: '湿度',
type: 'humidity',
value: '60',
icon: 'humidity',
color: '#4ECDC4',
unit: '%',
},
{
id: 3,
title: '光照', title: '光照',
type: 'light', type: 'Illuminance',
value: '10000', value: '10000',
icon: 'light', icon: 'light',
color: '#FFD166', color: '#FFD166',
unit: 'lux', unit: 'lux',
}, },
{ {
id: 4, id: 3,
title: '降雨量', title: '降雨量',
type: 'rainfall', type: 'DailyRainfall',
value: '10', value: '10',
icon: 'rain', icon: 'rain',
color: '#6A0572', color: '#6A0572',
unit: 'mm', unit: 'mm',
}, },
{ {
id: 5, id: 4,
title: '风向', title: '风向',
type: 'windDirection', type: 'WindDirection',
value: '东南风', value: '东南风',
icon: 'wind', icon: 'wind',
color: '#1A535C', color: '#1A535C',
unit: '°', unit: '°',
}, },
{ {
id: 6, id: 5,
title: '风速', title: '风速',
type: 'windSpeed', type: 'WindSpeed',
value: '2', value: '2',
icon: 'windPower', icon: 'windPower',
color: '#77DD77', color: '#77DD77',
unit: 'm/s', unit: 'm/s',
}, },
{
id: 7,
title: '气压',
type: 'pressure',
value: '1013',
icon: 'hpa',
color: '#845EC2',
unit: 'hPa',
}
] ]
const emits = defineEmits([ 'select' ]) const emits = defineEmits([ 'select' ])
@ -147,7 +129,7 @@ $transition-base: all 0.3s ease;
// //
.weather-photo { .weather-photo {
@apply w-full h-480px rounded-8px overflow-hidden; @apply w-full h-600px rounded-8px overflow-hidden;
box-shadow: $shadow-normal; box-shadow: $shadow-normal;
transition: $transition-base; transition: $transition-base;
} }

@ -2,12 +2,13 @@
* @Author: chris * @Author: chris
* @Date: 2025-09-12 17:20:00 * @Date: 2025-09-12 17:20:00
* @LastEditors: chris * @LastEditors: chris
* @LastEditTime: 2025-09-11 17:35:22 * @LastEditTime: 2025-09-22 17:15:25
* @Description: 气象曲线图组件支持7天/30/90天数据切换 * @Description: 气象曲线图组件支持7天/30/90天数据切换
--> -->
<script setup> <script setup>
import { ref, watch, onMounted, nextTick } from 'vue' import { ref, watch, onMounted, nextTick } from 'vue'
import * as echarts from 'echarts' import * as echarts from 'echarts'
import { weatherDict, weatherTypes } from '../config'
// props // props
const props = defineProps({ const props = defineProps({
@ -17,7 +18,7 @@ const props = defineProps({
required: true, required: true,
validator: (value) => { validator: (value) => {
// //
const validTypes = ['temperature', 'humidity', 'rainfall', 'windSpeed', 'pressure', 'light'] const validTypes = weatherTypes
return validTypes.includes(value) return validTypes.includes(value)
} }
}, },
@ -62,171 +63,172 @@ const chartInstance = ref(null)
const chartContainer = ref(null) const chartContainer = ref(null)
// //
const weatherTypeConfig = { let weatherTypeConfig = null
temperature: { // const weatherTypeConfig = {
name: '温度', // temperature: {
unit: '℃', // name: '',
color: '#FF6B6B', // unit: '',
yAxis: { // color: '#FF6B6B',
name: '温度 (℃)', // yAxis: {
min: function(value) { return value.min - 2; }, // name: ' ()',
max: function(value) { return value.max + 2; }, // min: function(value) { return value.min - 2; },
show: true, // max: function(value) { return value.max + 2; },
axisLabel: { // show: true,
show: true, // axisLabel: {
formatter: '{value}', // show: true,
color: '#333', // formatter: '{value}',
fontSize: 12 // color: '#333',
}, // fontSize: 12
axisTick: { // },
show: true // axisTick: {
}, // show: true
axisLine: { // },
show: true // axisLine: {
}, // show: true
splitLine: { // },
show: true, // splitLine: {
lineStyle: { // show: true,
type: 'dashed' // lineStyle: {
} // type: 'dashed'
} // }
} // }
}, // }
humidity: { // },
name: '湿度', // humidity: {
unit: '%', // name: '湿',
color: '#4ECDC4', // unit: '%',
yAxis: { // color: '#4ECDC4',
name: '湿度 (%)', // yAxis: {
min: 0, // name: '湿 (%)',
max: 100, // min: 0,
show: true, // max: 100,
axisLabel: { // show: true,
show: true, // axisLabel: {
formatter: '{value}' // show: true,
}, // formatter: '{value}'
axisTick: { // },
show: true // axisTick: {
}, // show: true
axisLine: { // },
show: true // axisLine: {
}, // show: true
splitLine: { // },
show: true, // splitLine: {
lineStyle: { // show: true,
type: 'dashed' // lineStyle: {
} // type: 'dashed'
} // }
} // }
}, // }
rainfall: { // },
name: '降雨量', // rainfall: {
unit: 'mm', // name: '',
color: '#6A0572', // unit: 'mm',
yAxis: { // color: '#6A0572',
name: '降雨量 (mm)', // yAxis: {
min: 0, // name: ' (mm)',
show: true, // min: 0,
axisLabel: { // show: true,
show: true, // axisLabel: {
formatter: '{value}' // show: true,
}, // formatter: '{value}'
axisTick: { // },
show: true // axisTick: {
}, // show: true
axisLine: { // },
show: true // axisLine: {
}, // show: true
splitLine: { // },
show: true, // splitLine: {
lineStyle: { // show: true,
type: 'dashed' // lineStyle: {
} // type: 'dashed'
} // }
} // }
}, // }
windSpeed: { // },
name: '风速', // windSpeed: {
unit: 'm/s', // name: '',
color: '#77DD77', // unit: 'm/s',
yAxis: { // color: '#77DD77',
name: '风速 (m/s)', // yAxis: {
min: 0, // name: ' (m/s)',
show: true, // min: 0,
axisLabel: { // show: true,
show: true, // axisLabel: {
formatter: '{value}' // show: true,
}, // formatter: '{value}'
axisTick: { // },
show: true // axisTick: {
}, // show: true
axisLine: { // },
show: true // axisLine: {
}, // show: true
splitLine: { // },
show: true, // splitLine: {
lineStyle: { // show: true,
type: 'dashed' // lineStyle: {
} // type: 'dashed'
} // }
} // }
}, // }
pressure: { // },
name: '气压', // pressure: {
unit: 'hPa', // name: '',
color: '#845EC2', // unit: 'hPa',
yAxis: { // color: '#845EC2',
name: '气压 (hPa)', // yAxis: {
min: function(value) { return value.min - 5; }, // name: ' (hPa)',
max: function(value) { return value.max + 5; }, // min: function(value) { return value.min - 5; },
show: true, // max: function(value) { return value.max + 5; },
axisLabel: { // show: true,
show: true, // axisLabel: {
formatter: '{value}', // show: true,
color: '#333', // formatter: '{value}',
fontSize: 12 // color: '#333',
}, // fontSize: 12
axisTick: { // },
show: true // axisTick: {
}, // show: true
axisLine: { // },
show: true // axisLine: {
}, // show: true
splitLine: { // },
show: true, // splitLine: {
lineStyle: { // show: true,
type: 'dashed' // lineStyle: {
} // type: 'dashed'
} // }
} // }
}, // }
light: { // },
name: '光照', // light: {
unit: 'lux', // name: '',
color: '#FFD166', // unit: 'lux',
yAxis: { // color: '#FFD166',
name: '光照 (lux)', // yAxis: {
min: 0, // name: ' (lux)',
show: true, // Y // min: 0,
axisLabel: { // show: true, // Y
show: true, // // axisLabel: {
formatter: '{value}' // show: true, //
}, // formatter: '{value}'
axisTick: { // },
show: true // 线 // axisTick: {
}, // show: true // 线
axisLine: { // },
show: true // 线 // axisLine: {
}, // show: true // 线
splitLine: { // },
show: true, // 线 // splitLine: {
lineStyle: { // show: true, // 线
type: 'dashed' // lineStyle: {
} // type: 'dashed'
} // }
} // }
} // }
} // }
// }
// //
const initChart = () => { const initChart = () => {
@ -256,7 +258,7 @@ const updateChart = () => {
const option = { const option = {
title: { title: {
text: `${config.name}趋势图 (近${props.selectedTimeRange})`, text: `${config.name}趋势图 (近24小时)`,
left: 'center', left: 'center',
textStyle: { textStyle: {
fontSize: 16, fontSize: 16,
@ -383,6 +385,7 @@ watch(
// //
onMounted(() => { onMounted(() => {
weatherTypeConfig = createWeatherConfig()
initChart() initChart()
}) })
@ -398,17 +401,52 @@ onUnmounted(() => {
defineExpose({ defineExpose({
refreshChart: updateChart refreshChart: updateChart
}) })
function createWeatherConfig () {
const config = {}
weatherTypes.forEach(type => {
const typeDict = weatherDict[type]
if (!typeDict) return
config[type] = {
name: typeDict.name,
unit: typeDict.unit,
color: typeDict.color,
yAxis: {
name: `${typeDict.name} (${typeDict.unit})`,
min: 0,
show: true, // Y
axisLabel: {
show: true, //
formatter: '{value}'
},
axisTick: {
show: true // 线
},
axisLine: {
show: true // 线
},
splitLine: {
show: true, // 线
lineStyle: {
type: 'dashed'
}
}
}
}
})
return config
}
</script> </script>
<template> <template>
<div class="weather-chart-container"> <div class="weather-chart-container">
<!-- 时间选择器 --> <!-- 时间选择器 -->
<div class="chart-controls"> <div class="chart-controls">
<el-radio-group v-model="selectedDays" class="time-range-selector"> <!-- <el-radio-group v-model="selectedDays" class="time-range-selector">
<el-radio-button :value="7" :disabled="loading">近7天</el-radio-button> <el-radio-button :value="7" :disabled="loading">近7天</el-radio-button>
<el-radio-button :value="30" :disabled="loading">近30天</el-radio-button> <el-radio-button :value="30" :disabled="loading">近30天</el-radio-button>
<el-radio-button :value="90" :disabled="loading">近90天</el-radio-button> <el-radio-button :value="90" :disabled="loading">近90天</el-radio-button>
</el-radio-group> </el-radio-group> -->
<!-- 加载状态指示器 --> <!-- 加载状态指示器 -->
<!-- <div v-if="loading" class="loading-indicator"> <!-- <div v-if="loading" class="loading-indicator">

@ -0,0 +1,34 @@
export const weatherTypes = [
"Temperature",
"Illuminance",
"WindSpeed",
"WindDirection",
"DailyRainfall",
];
export const weatherDict = {
Temperature: {
name: "温度",
unit: "°C",
color: "#FF6B6B",
},
Illuminance: {
name: "光照",
unit: "lux",
color: "#FFD166",
},
WindSpeed: {
name: "风速",
unit: "km/h",
color: "#009688",
},
WindDirection: {
name: "风向",
unit: "°",
color: "#845EC2",
},
DailyRainfall: {
name: "降雨量",
unit: "mm",
color: "#6A0572",
},
};

@ -2,46 +2,107 @@
* @Author: chris * @Author: chris
* @Date: 2025-09-05 09:29:59 * @Date: 2025-09-05 09:29:59
* @LastEditors: chris * @LastEditors: chris
* @LastEditTime: 2025-09-11 17:11:53 * @LastEditTime: 2025-09-22 17:23:27
--> -->
<script setup> <script setup>
import LiveData from './components/LiveData.vue' import LiveData from './components/LiveData.vue'
import WeatherChart from './components/WeatherChart.vue'; import WeatherChart from './components/WeatherChart.vue';
import { useSettings } from '@/hooks/useSettings'; import { useSettings } from '@/hooks/useSettings';
import { getFormattedWeatherData } from './mock'; // import { getFormattedWeatherData } from './mock';
import { getDeviceDataAnalysis } from '@/api/deviceData';
import { formatDate } from '@/utils';
import { weatherTypes } from './config';
const { hasTags } = useSettings() const { hasTags } = useSettings()
const { proxy } = getCurrentInstance()
const liveData = ref({}) const liveData = ref({})
const chartData = ref({ dates: [], values: []}) const chartData = ref({ dates: [], values: []})
const weatherType = ref('temperature') const weatherType = ref('Temperature')
const weatherData = ref({})
const selectedTimeRange = ref(7) const selectedTimeRange = ref(7)
const chartLoading = ref(false) const chartLoading = ref(false)
const loading = ref(false)
getChartData() // getChartData()
getDeviceAnalysisData()
async function getDeviceAnalysisData() {
loading.value = true
const beginTime = formatDate(new Date().getTime() - 24 * 60 * 60 * 1000)
const endTime = formatDate(new Date().getTime())
try {
const res = await getDeviceDataAnalysis({ type: 3 })
weatherData.value = formatData(res.rows)
chartData.value =createChartData(weatherData.value, weatherType.value)
} catch (error) {
proxy.$modal.msgError('获取气象分析数据失败: ' + (error.message || '未知错误'))
console.error('Failed to get device live data:', error)
} finally {
loading.value = false;
}
function getChartData() {
chartLoading.value = true
setTimeout(() => {
console.log(getFormattedWeatherData(weatherType.value, selectedTimeRange.value))
chartData.value = getFormattedWeatherData(weatherType.value, selectedTimeRange.value)
chartLoading.value = false
}, 300)
return chartData.value
} }
// function getChartData() {
// chartLoading.value = true
// setTimeout(() => {
// console.log(getFormattedWeatherData(weatherType.value, selectedTimeRange.value))
// chartData.value = getFormattedWeatherData(weatherType.value, selectedTimeRange.value)
// chartLoading.value = false
// }, 300)
// return chartData.value
// }
function handleSelectionChange(selection) { function handleSelectionChange(selection) {
console.log(selection) console.log(selection)
} }
function handleSelectWeatherItem(item) { function handleSelectWeatherItem(item) {
weatherType.value = item.type weatherType.value = item.type
getChartData() chartData.value = createChartData(weatherData.value, weatherType.value)
} }
function handleTimeRangeChange(range) { function handleTimeRangeChange(range) {
selectedTimeRange.value = range selectedTimeRange.value = range
getChartData() }
function createChartData(data, type) {
const dateList = getHourList()
return {
dates: dateList,
values: data[type] || []
}
}
function formatData (data) {
const result = {};
data.sort((a,b) => weatherTypes.indexOf(a.identifier) - weatherTypes.indexOf(b.identifier))
data.forEach(item => {
const identifier = item.identifier;
if (!identifier || identifier === 'null') return false;
result[identifier] ? result[identifier].push(item.value) : (result[identifier] = [item.value])
})
console.log(result)
// TODO: ,
delete result.Humidity
delete result.WindPower
delete result.DailyRainfall
return result
}
function getHourList () {
const hours = [];
const now = new Date();
for (let i = 23; i >= 0; i--) {
const hourDate = new Date(now);
hourDate.setHours(hourDate.getHours() - i);
const hourStr = hourDate.getHours().toString().padStart(2, '0');
hours.push(`${hourStr}:00`);
}
return hours;
} }
</script> </script>

@ -2,7 +2,7 @@
* @Author: chris * @Author: chris
* @Date: 2025-01-13 09:33:28 * @Date: 2025-01-13 09:33:28
* @LastEditors: chris * @LastEditors: chris
* @LastEditTime: 2025-08-14 11:51:41 * @LastEditTime: 2025-09-16 10:07:43
*/ */
import { defineConfig, loadEnv } from "vite"; import { defineConfig, loadEnv } from "vite";
import path from "path"; import path from "path";
@ -38,7 +38,7 @@ export default defineConfig(({ mode, command }) => {
// https://cn.vitejs.dev/config/#server-proxy // https://cn.vitejs.dev/config/#server-proxy
"/dev-api": { "/dev-api": {
// target: "http://192.168.0.111:8080", // target: "http://192.168.0.111:8080",
target: "http://aeo.gdguanhui.com/aeo", target: "http://goose.gdguanhui.com/leilinglitchi",
changeOrigin: true, changeOrigin: true,
rewrite: (p) => p.replace(/^\/dev-api/, ""), rewrite: (p) => p.replace(/^\/dev-api/, ""),
}, },

Loading…
Cancel
Save