diff --git a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/ProjectServiceImpl.java b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/ProjectServiceImpl.java
index 9dc06361..ec9b1bf0 100644
--- a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/ProjectServiceImpl.java
+++ b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/ProjectServiceImpl.java
@@ -1,5 +1,6 @@
package com.fastbee.iot.service.impl;
+import com.fastbee.common.core.domain.model.LoginUser;
import com.fastbee.common.enums.DeviceDistributeTypeEnum;
import com.fastbee.common.enums.DeviceRecordTypeEnum;
import com.fastbee.common.enums.StatusEnum;
@@ -75,15 +76,20 @@ public class ProjectServiceImpl implements IProjectService {
// project.setTenantId(deptUserId);
// }
Long userId = project.getResponsibleUserId();
+ LoginUser loginUser = getLoginUser();
if (userId == null) {
- userId = getLoginUser().getUserId();
+ userId = loginUser.getUserId();
}
boolean admin = SecurityUtils.isAdmin(userId);
if (admin) {
- project.setTenantId(userId);
project.setResponsibleUserId(null);
} else {
- project.setResponsibleUserId(userId);
+ Long deptId = loginUser.getDeptId();
+ if (deptId == null || deptId == 0L) {
+ // 无机构用户看不到任何项目,与 selectUserListByScope 的空列表逻辑一致
+ return new ArrayList<>();
+ }
+ project.getParams().put("deptId", deptId);
}
return projectMapper.selectProjectList(project);
}
diff --git a/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/ProjectMapper.xml b/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/ProjectMapper.xml
index 7ae07ed4..7941b463 100644
--- a/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/ProjectMapper.xml
+++ b/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/ProjectMapper.xml
@@ -66,6 +66,19 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
and p.project_code = #{projectCode}
and p.tenant_id = #{tenantId}
and p.responsible_user_id = #{responsibleUserId}
+
+ and p.responsible_user_id in (
+ select u.user_id
+ from sys_user u
+ where u.del_flag = '0'
+ and (u.dept_id = #{params.deptId}
+ or u.dept_id in (
+ select t.dept_id
+ from sys_dept t
+ where find_in_set(#{params.deptId}, t.ancestors)
+ ))
+ )
+
and p.project_status = #{projectStatus}
GROUP BY p.project_id
diff --git a/yack-web/src/lang/en-US/dataCenter.json b/yack-web/src/lang/en-US/dataCenter.json
index 6fb7850b..8b74e64a 100644
--- a/yack-web/src/lang/en-US/dataCenter.json
+++ b/yack-web/src/lang/en-US/dataCenter.json
@@ -41,9 +41,15 @@
"dataCenter.history.384934-17": "data sources ",
"dataCenter.history.384934-18": "Please select the data source",
"dataCenter.history.384934-19": "Last 2 hours",
- "dataCenter.history.384934-20": "Last day",
+ "dataCenter.history.384934-20": "Last 24 hours",
"dataCenter.history.384934-21": "In the past seven days",
"dataCenter.history.384934-22": "Last 30 days",
+ "dataCenter.history.384934-26": "Last 4 hours",
+ "dataCenter.history.384934-27": "Last 8 hours",
+ "dataCenter.history.384934-28": "Last 12 hours",
+ "dataCenter.history.384934-29": "Last 3 days",
+ "dataCenter.history.384934-30": "Last 5 days",
+ "dataCenter.history.384934-31": "Last 15 days",
"dataCenter.history.384934-23": "total",
"dataCenter.history.384934-24": "Export Excel",
"dataCenter.history.gwCalculated": "Gateway Calculated",
diff --git a/yack-web/src/lang/zh-CN/dataCenter.json b/yack-web/src/lang/zh-CN/dataCenter.json
index bbbf9e17..c8eface4 100644
--- a/yack-web/src/lang/zh-CN/dataCenter.json
+++ b/yack-web/src/lang/zh-CN/dataCenter.json
@@ -8,7 +8,7 @@
"dataCenter.analysis.349202-6": "重置",
"dataCenter.analysis.349202-7": "暂无数据",
"dataCenter.analysis.349202-8": "最近2小时",
- "dataCenter.analysis.384934-25": "最近两天",
+ "dataCenter.analysis.384934-25": "最近2天",
"dataCenter.analysis.349202-9": "最近一天",
"dataCenter.analysis.349202-10": "最近七天",
"dataCenter.analysis.349202-11": "最近三十天",
@@ -41,9 +41,15 @@
"dataCenter.history.384934-17": "数据来源",
"dataCenter.history.384934-18": "请选择数据来源",
"dataCenter.history.384934-19": "最近2小时",
- "dataCenter.history.384934-20": "最近一天",
- "dataCenter.history.384934-21": "最近七天",
- "dataCenter.history.384934-22": "最近三十天",
+ "dataCenter.history.384934-20": "最近24小时",
+ "dataCenter.history.384934-21": "最近7天",
+ "dataCenter.history.384934-22": "最近30天",
+ "dataCenter.history.384934-26": "最近4小时",
+ "dataCenter.history.384934-27": "最近8小时",
+ "dataCenter.history.384934-28": "最近12小时",
+ "dataCenter.history.384934-29": "最近3天",
+ "dataCenter.history.384934-30": "最近5天",
+ "dataCenter.history.384934-31": "最近15天",
"dataCenter.history.384934-23": "总量",
"dataCenter.history.384934-24": "导出Excel",
"dataCenter.history.gwCalculated": "网关计算量",
diff --git a/yack-web/src/views/dataCenter/components/DeviceHistoryTablePanel.vue b/yack-web/src/views/dataCenter/components/DeviceHistoryTablePanel.vue
index 604b8a33..18ec8086 100644
--- a/yack-web/src/views/dataCenter/components/DeviceHistoryTablePanel.vue
+++ b/yack-web/src/views/dataCenter/components/DeviceHistoryTablePanel.vue
@@ -205,6 +205,30 @@ export default {
picker.$emit('pick', [start.format('YYYY-MM-DD HH:mm:ss'), end.format('YYYY-MM-DD HH:mm:ss')]);
},
},
+ {
+ text: this.$t('dataCenter.history.384934-26'),
+ onClick(picker) {
+ const end = moment();
+ const start = moment().subtract(4, 'hours');
+ picker.$emit('pick', [start.format('YYYY-MM-DD HH:mm:ss'), end.format('YYYY-MM-DD HH:mm:ss')]);
+ },
+ },
+ {
+ text: this.$t('dataCenter.history.384934-27'),
+ onClick(picker) {
+ const end = moment();
+ const start = moment().subtract(8, 'hours');
+ picker.$emit('pick', [start.format('YYYY-MM-DD HH:mm:ss'), end.format('YYYY-MM-DD HH:mm:ss')]);
+ },
+ },
+ {
+ text: this.$t('dataCenter.history.384934-28'),
+ onClick(picker) {
+ const end = moment();
+ const start = moment().subtract(12, 'hours');
+ picker.$emit('pick', [start.format('YYYY-MM-DD HH:mm:ss'), end.format('YYYY-MM-DD HH:mm:ss')]);
+ },
+ },
{
text: this.$t('dataCenter.history.384934-20'),
onClick(picker) {
@@ -221,6 +245,22 @@ export default {
picker.$emit('pick', [start.format('YYYY-MM-DD HH:mm:ss'), end.format('YYYY-MM-DD HH:mm:ss')]);
},
},
+ {
+ text: this.$t('dataCenter.history.384934-29'),
+ onClick(picker) {
+ const end = moment();
+ const start = moment().subtract(3, 'days');
+ picker.$emit('pick', [start.format('YYYY-MM-DD HH:mm:ss'), end.format('YYYY-MM-DD HH:mm:ss')]);
+ },
+ },
+ {
+ text: this.$t('dataCenter.history.384934-30'),
+ onClick(picker) {
+ const end = moment();
+ const start = moment().subtract(5, 'days');
+ picker.$emit('pick', [start.format('YYYY-MM-DD HH:mm:ss'), end.format('YYYY-MM-DD HH:mm:ss')]);
+ },
+ },
{
text: this.$t('dataCenter.history.384934-21'),
onClick(picker) {
@@ -229,6 +269,14 @@ export default {
picker.$emit('pick', [start.format('YYYY-MM-DD HH:mm:ss'), end.format('YYYY-MM-DD HH:mm:ss')]);
},
},
+ {
+ text: this.$t('dataCenter.history.384934-31'),
+ onClick(picker) {
+ const end = moment();
+ const start = moment().subtract(15, 'days');
+ picker.$emit('pick', [start.format('YYYY-MM-DD HH:mm:ss'), end.format('YYYY-MM-DD HH:mm:ss')]);
+ },
+ },
{
text: this.$t('dataCenter.history.384934-22'),
onClick(picker) {
@@ -392,11 +440,12 @@ export default {
},
},
watch: {
- device: {
+ // 只监听设备身份变化(deviceId),避免父页面实时更新 form 的其他字段
+ // (如 MQTT 推送的设备状态 status/isShadow/rssi)时把已查询的历史数据清空
+ 'device.deviceId': {
handler() {
this.bootstrap();
},
- deep: true,
immediate: true,
},
defaultIdentifiers: {
diff --git a/yack-web/src/views/dataCenter/history.vue b/yack-web/src/views/dataCenter/history.vue
index 14709aa0..e107f4e7 100644
--- a/yack-web/src/views/dataCenter/history.vue
+++ b/yack-web/src/views/dataCenter/history.vue
@@ -339,6 +339,33 @@ export default {
picker.$emit('pick', [start, end]);
},
},
+ {
+ text: this.$t('dataCenter.history.384934-26'),
+ onClick(picker) {
+ const end = new Date();
+ const start = new Date();
+ start.setTime(start.getTime() - 3600 * 1000 * 4);
+ picker.$emit('pick', [start, end]);
+ },
+ },
+ {
+ text: this.$t('dataCenter.history.384934-27'),
+ onClick(picker) {
+ const end = new Date();
+ const start = new Date();
+ start.setTime(start.getTime() - 3600 * 1000 * 8);
+ picker.$emit('pick', [start, end]);
+ },
+ },
+ {
+ text: this.$t('dataCenter.history.384934-28'),
+ onClick(picker) {
+ const end = new Date();
+ const start = new Date();
+ start.setTime(start.getTime() - 3600 * 1000 * 12);
+ picker.$emit('pick', [start, end]);
+ },
+ },
{
text: this.$t('dataCenter.history.384934-20'),
onClick(picker) {
@@ -357,6 +384,24 @@ export default {
picker.$emit('pick', [start, end]);
},
},
+ {
+ text: this.$t('dataCenter.history.384934-29'),
+ onClick(picker) {
+ const end = new Date();
+ const start = new Date();
+ start.setTime(start.getTime() - 3600 * 1000 * 24 * 3);
+ picker.$emit('pick', [start, end]);
+ },
+ },
+ {
+ text: this.$t('dataCenter.history.384934-30'),
+ onClick(picker) {
+ const end = new Date();
+ const start = new Date();
+ start.setTime(start.getTime() - 3600 * 1000 * 24 * 5);
+ picker.$emit('pick', [start, end]);
+ },
+ },
{
text: this.$t('dataCenter.history.384934-21'),
onClick(picker) {
@@ -366,6 +411,15 @@ export default {
picker.$emit('pick', [start, end]);
},
},
+ {
+ text: this.$t('dataCenter.history.384934-31'),
+ onClick(picker) {
+ const end = new Date();
+ const start = new Date();
+ start.setTime(start.getTime() - 3600 * 1000 * 24 * 15);
+ picker.$emit('pick', [start, end]);
+ },
+ },
{
text: this.$t('dataCenter.history.384934-22'),
onClick(picker) {
diff --git a/yack-web/src/views/iot/device/running-status.vue b/yack-web/src/views/iot/device/running-status.vue
index 37e1ae1d..676fd775 100644
--- a/yack-web/src/views/iot/device/running-status.vue
+++ b/yack-web/src/views/iot/device/running-status.vue
@@ -401,6 +401,43 @@
+
+
+
+
+ 便捷指令
+ 点击按钮一键下发指令
+
+
+
+
+ {{ item.name }}
+
+
+
+ 自定义指令
+
+ 发送
+
+
+
@@ -646,6 +683,19 @@ export default {
lbsLoading: false,
saveLoadingImei: false,
saveLoadingIccid: false,
+ // 便捷指令:一键下发,仅发送不回显、不保存
+ quickCommands: [
+ {
+ key: 'reset',
+ name: '重启设备',
+ cmd: 'AT+RESET\\r\\n',
+ icon: 'el-icon-refresh',
+ danger: true,
+ loading: false,
+ },
+ ],
+ quickCustomCmd: '',
+ quickCustomLoading: false,
imeiPopoverVisible: false, // 控制IMEI指令编辑弹窗
iccidPopoverVisible: false, // 控制ICCID指令编辑弹窗
@@ -706,15 +756,47 @@ export default {
},
async sendAt(type) {
+ const cmd = type === 'imei' ? this.imeiCmd : type === 'iccid' ? this.iccidCmd : type === 'lbs' ? this.lbsCmd : '';
+ const loadingSetter = type === 'imei'
+ ? (v) => (this.imeiLoading = v)
+ : type === 'iccid'
+ ? (v) => (this.iccidLoading = v)
+ : type === 'lbs'
+ ? (v) => (this.lbsLoading = v)
+ : null;
+ // 读取型指令:下发后轮询刷新设备信息,等待回显
+ await this.sendRawCommand(cmd, { loadingSetter, refreshAfter: true });
+ },
+
+ // 便捷指令:一键下发,仅发送、不回显、不保存
+ async sendQuickCommand(item) {
+ if (!item || !item.cmd) return;
+ await this.sendRawCommand(item.cmd, {
+ loadingSetter: (v) => (item.loading = v),
+ refreshAfter: false,
+ });
+ },
+
+ // 便捷指令:自定义指令发送(仅下发,不保存)
+ async sendQuickCustom() {
+ const cmd = String(this.quickCustomCmd || '').trim();
+ if (!cmd) {
+ this.$message({ type: 'warning', message: '请输入要下发的指令' });
+ return;
+ }
+ await this.sendRawCommand(cmd, {
+ loadingSetter: (v) => (this.quickCustomLoading = v),
+ refreshAfter: false,
+ });
+ },
+
+ async sendRawCommand(cmd, { loadingSetter = null, refreshAfter = true } = {}) {
try {
- const cmd = type === 'imei' ? this.imeiCmd : type === 'iccid' ? this.iccidCmd : type === 'lbs' ? this.lbsCmd : '';
if (!cmd) {
this.$message({ type: 'warning', message: '指令不能为空' });
return;
}
- if (type === 'imei') this.imeiLoading = true;
- if (type === 'iccid') this.iccidLoading = true;
- if (type === 'lbs') this.lbsLoading = true;
+ if (loadingSetter) loadingSetter(true);
// FUNCTION_GET 下发链路最终会走 ByteUtils.convertToHexByteArray(command)。
// 因此这里必须传“十六进制字节串”,而不是原始 ASCII。
@@ -737,15 +819,15 @@ export default {
this.$message({ type: 'success', message: '已下发指令,等待设备回复解析…' });
}
- // 后端收到回复后会自动解析并写入数据库,这里做一次轻量轮询刷新3秒、5秒各一次
- setTimeout(() => this.refreshDeviceBaseInfo(), 3000);
- setTimeout(() => this.refreshDeviceBaseInfo(), 8000);
+ // 读取型指令:后端收到回复后会自动解析并写入数据库,这里做轻量轮询刷新(3秒、8秒各一次)
+ if (refreshAfter) {
+ setTimeout(() => this.refreshDeviceBaseInfo(), 3000);
+ setTimeout(() => this.refreshDeviceBaseInfo(), 8000);
+ }
} catch (e) {
this.$message({ type: 'error', message: e && e.message ? e.message : '指令下发失败' });
} finally {
- this.imeiLoading = false;
- this.iccidLoading = false;
- this.lbsLoading = false;
+ if (loadingSetter) loadingSetter(false);
}
},
@@ -1396,6 +1478,59 @@ export default {
width: 100%;
}
+/* 便捷指令:一键下发,仅发送不回显、不保存 */
+.quick-command-panel {
+ background-color: #f5f7fa;
+ border: 1px solid #ebeef5;
+ border-radius: 15px;
+ padding: 16px 20px;
+ margin-bottom: 50px;
+}
+
+.quick-command-title {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 14px;
+ font-weight: 600;
+ color: #303133;
+ margin-bottom: 12px;
+}
+
+.quick-command-title i {
+ font-size: 16px;
+ color: #409eff;
+}
+
+.quick-command-tip {
+ font-size: 12px;
+ font-weight: 400;
+ color: #909399;
+}
+
+.quick-command-list {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 10px;
+ margin-bottom: 12px;
+}
+
+.quick-command-btn i {
+ margin-right: 4px;
+}
+
+.quick-command-custom {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.quick-command-custom-label {
+ font-size: 12px;
+ color: #909399;
+ white-space: nowrap;
+}
+
/* 重写滑动块样式 */
.el-slider__bar {
height: 18px;