diff --git a/src/api/nvrVideo.js b/src/api/nvrVideo.js
index 6c1d081..1072b13 100644
--- a/src/api/nvrVideo.js
+++ b/src/api/nvrVideo.js
@@ -28,13 +28,13 @@ export function getNvrConfig() {
return unwrap(request({ url: '/patrol/video/config', method: 'get' }))
}
-export function startLive(channel) {
+export function startLive(channel, streamType = 1) {
return unwrap(request({
url: '/patrol/video/realtime/start',
method: 'post',
data: {
cameraCode: channel.cameraCode || channel.channelCode,
- streamType: 1
+ streamType
}
}))
}
diff --git a/src/assets/styles/sidebar.scss b/src/assets/styles/sidebar.scss
index ca5a4aa..9642955 100644
--- a/src/assets/styles/sidebar.scss
+++ b/src/assets/styles/sidebar.scss
@@ -238,6 +238,17 @@
}
}
}
+
+ // 收起时仅展示图标:隐藏菜单文字(保留图标),图标保持居中。
+ // 菜单图标与文字被 .menu-item-inner 包裹,需只隐藏其中的文字 span。
+ .el-menu > div > a > .submenu-title-noDropdown .menu-item-inner,
+ .el-menu > div > .el-submenu > .el-submenu__title .menu-item-inner {
+ overflow: visible;
+
+ > span:not(.menu-icon-img):not(.menu-icon-placeholder) {
+ display: none !important;
+ }
+ }
}
.el-menu--collapse .el-menu .el-submenu {
diff --git a/src/router/index.js b/src/router/index.js
index 53c4f8e..6728349 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -170,12 +170,13 @@ export const constantRoutes = [
{
path: '/task-summary',
component: Layout,
- redirect: '/task-summary/inspectionTask',
+ // 与默认首页 /index 渲染同一组件,重定向过去复用固定的巡视任务页签,避免出现两个同名页签
+ redirect: '/index',
meta: { title: '巡视任务' },
children: [
{
path: 'inspectionTask',
- component: () => import('@/views/cards/taskSummary/inspectionTask'),
+ redirect: '/index',
name: 'InspectionTaskSummary',
meta: { title: '巡视任务', activeMenu: '/task-summary/inspectionTask' }
}
diff --git a/src/views/cards/assets/styles/index.scss b/src/views/cards/assets/styles/index.scss
index d9febcb..c2d5542 100644
--- a/src/views/cards/assets/styles/index.scss
+++ b/src/views/cards/assets/styles/index.scss
@@ -555,7 +555,8 @@
}
.site-point-add-dialog {
- height: 92vh;
+ // 高度随内容自适应,避免表单内容少时弹框底部出现大片空白
+ height: auto;
max-height: 92vh;
margin-top: 4vh;
display: flex;
@@ -563,8 +564,15 @@
border: 0;
box-shadow: none;
+ // 去掉表单自带的深色背景,与弹窗底色保持一致
+ // (ruoyi.scss 对 .card-page 的背景使用了 !important,这里需同样处理)
+ .el-form.card-page {
+ background-color: transparent !important;
+ border: 0 !important;
+ }
+
.el-dialog__body {
- flex: 1 1 auto;
+ flex: 0 0 auto;
min-height: 0;
height: auto;
max-height: calc(92vh - 135px);
diff --git a/src/views/cards/defectRecord/index.vue b/src/views/cards/defectRecord/index.vue
index 026a033..613c6e2 100644
--- a/src/views/cards/defectRecord/index.vue
+++ b/src/views/cards/defectRecord/index.vue
@@ -4,6 +4,62 @@
:showTitle="false"
>
+
+
+
+ 总缺陷数
+ {{ total }}
+
+
+
+
+
+
+
搜索
+
重置
+
{
+ const desc = (item.desc || "").trim();
+ if (desc) set.add(desc);
+ });
+ return Array.from(set).sort((a, b) => a.localeCompare(b, "zh"));
+ },
+ },
created() {
- this.getList();
+ this.loadData();
window.addEventListener("resize", this.setTableHeight);
},
mounted() {
@@ -139,7 +209,91 @@ export default {
handleQuery() {
this.queryParams.pageNum = 1;
- this.getList();
+ this.applyFilters();
+ },
+
+ // 重置查询条件(筛选在前端完成,无需重新请求后端)
+ resetQuery() {
+ this.defectTypeList = [];
+ this.queryParams.pointName = "";
+ this.dateRange = [];
+ this.handleQuery();
+ },
+
+ // 一次性拉取全量数据,之后的筛选、分页都在前端完成
+ loadData() {
+ this.loading = true;
+ // 重置图片计数器
+ this.imageLoadCount = 0;
+ this.expectedImageCount = 0;
+
+ defectRecordList({ pageNum: 1, pageSize: 9999 })
+ .then((res) => {
+ this.allDataList = res.rows || [];
+ this.applyFilters();
+ })
+ .catch((error) => {
+ console.error("获取缺陷记录失败:", error);
+ this.allDataList = [];
+ this.applyFilters();
+ })
+ .finally(() => {
+ this.loading = false;
+ });
+ },
+
+ // 前端筛选:缺陷结果、点位名称、采集时间,随后本地分页
+ applyFilters() {
+ const keyword = (this.queryParams.pointName || "").trim().toLowerCase();
+ const types = this.defectTypeList || [];
+ const range =
+ this.dateRange && this.dateRange.length === 2 ? this.dateRange : null;
+
+ this.filteredList = this.allDataList.filter((row) => {
+ if (types.length && !types.includes((row.desc || "").trim())) {
+ return false;
+ }
+ if (
+ keyword &&
+ (row.pointName || "").toLowerCase().indexOf(keyword) === -1
+ ) {
+ return false;
+ }
+ if (range) {
+ const day = (row.patrolTime || "").slice(0, 10);
+ if (!day || day < range[0] || day > range[1]) {
+ return false;
+ }
+ }
+ return true;
+ });
+
+ this.total = this.filteredList.length;
+ // 当前页超出范围时回到第一页
+ const maxPage = Math.max(
+ 1,
+ Math.ceil(this.total / (this.queryParams.pageSize || 20))
+ );
+ if (this.queryParams.pageNum > maxPage) {
+ this.queryParams.pageNum = 1;
+ }
+ const start = (this.queryParams.pageNum - 1) * this.queryParams.pageSize;
+ this.dataList = this.filteredList.slice(
+ start,
+ start + this.queryParams.pageSize
+ );
+
+ // 计算需要加载的图片数量
+ this.expectedImageCount = this.dataList.filter(
+ (item) => item.imgAnalyse
+ ).length;
+
+ // 如果没有图片需要加载,直接刷新表格
+ if (this.expectedImageCount === 0) {
+ this.forceRefreshTable();
+ } else {
+ this.$nextTick(this.setTableHeight);
+ }
},
linkUrl() {
@@ -201,42 +355,13 @@ export default {
});
},
+ // 翻页:数据已在前端,直接本地切片(首次进入时才请求后端)
getList() {
- this.loading = true;
- // 重置图片计数器
- this.imageLoadCount = 0;
- this.expectedImageCount = 0;
-
- const params = { ...this.queryParams };
- if (this.dateRange && this.dateRange.length === 2) {
- params.beginTime = this.dateRange[0];
- params.endTime = this.dateRange[1];
+ if (!this.allDataList.length) {
+ this.loadData();
+ return;
}
-
- defectRecordList(params)
- .then((res) => {
- this.dataList = res.rows || [];
- this.total = res.total || 0;
- this.$nextTick(this.setTableHeight);
-
- // 计算需要加载的图片数量
- this.expectedImageCount = this.dataList.filter(
- (item) => item.imgAnalyse
- ).length;
-
- // 如果没有图片需要加载,直接刷新表格
- if (this.expectedImageCount === 0) {
- this.forceRefreshTable();
- }
- })
- .catch((error) => {
- console.error("获取缺陷记录失败:", error);
- this.dataList = [];
- this.total = 0;
- })
- .finally(() => {
- this.loading = false;
- });
+ this.applyFilters();
},
onSubmit() {
@@ -252,6 +377,67 @@ export default {
display: flex;
flex-direction: column;
}
+// 查询栏:总缺陷数 + 条件筛选
+.query-bar {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 12px;
+ padding: 12px 14px;
+ margin-bottom: 12px;
+ border: 1px solid #0b2567;
+ border-radius: 2px;
+ background: rgba(11, 37, 103, 0.35);
+
+ .total-chip {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 5px 14px;
+ border: 1px solid rgba(63, 127, 216, 0.72);
+ border-radius: 2px;
+ background: rgba(11, 37, 103, 0.55);
+
+ .total-label {
+ font-size: 14px;
+ color: #dcecff;
+ }
+
+ .total-value {
+ font-size: 20px;
+ font-weight: 700;
+ color: #54ffff;
+ line-height: 1.2;
+ }
+ }
+
+ .query-type {
+ width: 220px;
+ }
+
+ .query-input {
+ width: 180px;
+ }
+
+ .query-date {
+ width: 260px;
+ }
+
+ // 重置按钮:与巡视方案页“添加”按钮同款配色
+ .query-reset-btn {
+ background: linear-gradient(#01016b, #01016b) padding-box,
+ linear-gradient(135deg, #3988ff, #0351c6) border-box !important;
+ border: 1px solid transparent !important;
+ color: #43c0ff !important;
+
+ &:hover,
+ &:focus {
+ background: linear-gradient(#01016b, #01016b) padding-box,
+ linear-gradient(135deg, #3988ff, #0351c6) border-box !important;
+ color: #43c0ff !important;
+ }
+ }
+}
.card-page {
height: 100%;
min-height: 0;
diff --git a/src/views/cards/patrolPlan/summary_editTask.vue b/src/views/cards/patrolPlan/summary_editTask.vue
index 71b96da..6daebf2 100644
--- a/src/views/cards/patrolPlan/summary_editTask.vue
+++ b/src/views/cards/patrolPlan/summary_editTask.vue
@@ -1195,6 +1195,11 @@ export default {
align-items: flex-start;
margin-bottom: 24px;
+ // 间隔类型下拉去掉禁用态默认的深色背景,与普通下拉外观一致
+ ::v-deep .el-select .el-input.is-disabled .el-input__inner {
+ background-color: rgba(27, 42, 94, 0.94) !important;
+ }
+
.form-label {
width: 130px;
flex-shrink: 0;
diff --git a/src/views/cards/patrolPlan/summary_newTask.vue b/src/views/cards/patrolPlan/summary_newTask.vue
index 09f5194..5402c50 100644
--- a/src/views/cards/patrolPlan/summary_newTask.vue
+++ b/src/views/cards/patrolPlan/summary_newTask.vue
@@ -1042,6 +1042,11 @@ export default {
align-items: flex-start;
margin-bottom: 24px;
+ // 间隔类型为禁用下拉,去掉禁用态默认的深色背景,与普通下拉外观一致
+ ::v-deep .el-select .el-input.is-disabled .el-input__inner {
+ background-color: rgba(27, 42, 94, 0.94) !important;
+ }
+
.form-label {
width: 130px;
flex-shrink: 0;
diff --git a/src/views/cards/patrolReport/patrolReportDetail.vue b/src/views/cards/patrolReport/patrolReportDetail.vue
index 8d0339b..b9e3166 100644
--- a/src/views/cards/patrolReport/patrolReportDetail.vue
+++ b/src/views/cards/patrolReport/patrolReportDetail.vue
@@ -19,13 +19,13 @@
:show-header="false"
:span-method="headerSpan"
>
-
+
-
+
diff --git a/src/views/video/LivePreview.vue b/src/views/video/LivePreview.vue
index a3c24cc..b6d67a4 100644
--- a/src/views/video/LivePreview.vue
+++ b/src/views/video/LivePreview.vue
@@ -199,6 +199,7 @@ export default {
streamCleanupPromise: null,
selectedCode: '',
layout: 9,
+ fullscreenIndex: -1,
layouts: [
{ value: 1, label: '单画面' }, { value: 4, label: '四画面' },
{ value: 9, label: '九画面' }, { value: 16, label: '十六画面' }
@@ -259,6 +260,8 @@ export default {
visibleSlots() { return this.slots.slice(0, this.capacity) },
activeSlot() { return this.slots[this.activeIndex] || null },
activeMuted() { return this.controlTarget ? this.controlTarget.muted !== false : true },
+ // 单画面用主码流,4/9/16 宫格用辅码流,减轻多路视频卡顿
+ streamType() { return this.layout === 1 ? 1 : 2 },
gridStyle() {
const columns = { 1: 1, 4: 2, 9: 3, 16: 4 }[this.layout]
return { gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`, gridTemplateRows: `repeat(${columns}, minmax(0, 1fr))` }
@@ -269,6 +272,7 @@ export default {
created() {
this.$root.$on('video-layout-change', this.changeLayout)
this.$root.$on('video-control-panel-change', this.changeControlPanel)
+ document.addEventListener('fullscreenchange', this.handleFullscreenChange)
this.$root.$emit('video-layout-updated', this.layout)
this.$root.$emit('video-control-panel-updated', this.controlPanelExpanded)
this.loadConfig()
@@ -293,6 +297,7 @@ export default {
this.stopTreeResize()
this.lifecycleId += 1
this.pageActive = false
+ document.removeEventListener('fullscreenchange', this.handleFullscreenChange)
this.$root.$off('video-layout-change', this.changeLayout)
this.$root.$off('video-control-panel-change', this.changeControlPanel)
this.closeAll()
@@ -421,11 +426,11 @@ export default {
this.selectChannel(channel)
this.startChannel(channel)
},
- async startChannel(channel, targetIndex = this.activeIndex, attemptedCameraCodes = []) {
+ async startChannel(channel, targetIndex = this.activeIndex, attemptedCameraCodes = [], streamType = this.streamType) {
if (!this.pageActive || !channel || !channel.cameraCode) return
const index = targetIndex
const current = this.slots[index]
- if (current && current.cameraCode === channel.cameraCode) {
+ if (current && current.cameraCode === channel.cameraCode && current.streamType === streamType) {
this.controlTarget = current
return
}
@@ -439,7 +444,8 @@ export default {
muted: true,
errorNotified: false,
attemptedCameraCodes: [...attemptedCameraCodes, channel.cameraCode],
- timer: null
+ timer: null,
+ streamType
}
this.$set(this.slots, index, slot)
if (index === this.activeIndex) {
@@ -449,7 +455,7 @@ export default {
this.presets = []
this.presetId = null
try {
- slot.startPromise = startLive(channel)
+ slot.startPromise = startLive(channel, streamType)
const result = await slot.startPromise
if (this.slots[index] !== slot) return
this.applyLiveResult(index, slot, result)
@@ -587,6 +593,29 @@ export default {
const card = this.$refs.cameraCards && this.$refs.cameraCards[index]
if (card && card.requestFullscreen) card.requestFullscreen()
},
+ // 放大(全屏)观看时切主码流,退出全屏后恢复当前布局对应的码流
+ async handleFullscreenChange() {
+ const fsElement = document.fullscreenElement
+ if (fsElement) {
+ const cards = this.$refs.cameraCards || []
+ const index = cards.indexOf(fsElement)
+ if (index >= 0) {
+ this.fullscreenIndex = index
+ await this.switchSlotStream(index, 1)
+ }
+ } else if (this.fullscreenIndex >= 0) {
+ const index = this.fullscreenIndex
+ this.fullscreenIndex = -1
+ await this.switchSlotStream(index, this.streamType)
+ }
+ },
+ async switchSlotStream(index, streamType) {
+ const slot = this.slots[index]
+ if (!this.pageActive || !slot || slot.streamType === streamType) return
+ await this.closeSlot(index, false)
+ if (!this.pageActive) return
+ await this.startChannel(slot, index, [], streamType)
+ },
openPlayback(slot) {
this.$router.push({ name: 'VideoHistoryPlayback', query: { cameraCode: slot.cameraCode }})
},
diff --git a/src/views/video/components/NvrDeviceTree.vue b/src/views/video/components/NvrDeviceTree.vue
index f248924..2bb7789 100644
--- a/src/views/video/components/NvrDeviceTree.vue
+++ b/src/views/video/components/NvrDeviceTree.vue
@@ -155,7 +155,7 @@ export default {
.tree-filters .el-input { flex: 1; min-width: 0; }
.tree-filters .el-button { flex: 0 0 32px; padding: 0; border-color: #2787ed; color: #54ffff; background: #0a2b67; }
.tree-filters .el-button:hover, .tree-filters .el-button:focus { border-color: #2787ed; color: #ffffff; background: #176bc2; }
-.tree-content { flex: 1; min-height: 0; overflow-x: auto; overflow-y: auto; padding: 0 8px 8px; scrollbar-width: thin; scrollbar-color: #2f7fe8 rgba(6,27,79,.7); }
+.tree-content { flex: 1; min-height: 0; overflow-x: auto; overflow-y: auto; padding: 0 0 8px; scrollbar-width: thin; scrollbar-color: #2f7fe8 rgba(6,27,79,.7); }
.tree-content::-webkit-scrollbar { width: 8px; height: 8px; }
.tree-content::-webkit-scrollbar-track { background: rgba(6,27,79,.7); }
.tree-content::-webkit-scrollbar-thumb { background: #2f7fe8; border: 1px solid #0d327d; border-radius: 4px; }
@@ -169,7 +169,11 @@ export default {
::v-deep .custom-tree-node,
::v-deep .node-label { font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif !important; font-weight: 400 !important; }
::v-deep .el-tree-node__content { display: flex; }
-::v-deep .el-tree-node__content { height: 34px; }
+/* 行高与点位管理设备树保持一致,展开后更紧凑 */
+::v-deep .el-tree-node__content { height: 24px; }
+/* 叶子节点没有展开小三角,隐形三角占位收窄一半,名称略向左靠,
+ 与前几级的间距节奏保持一致(同点位管理设备树的处理) */
+::v-deep .el-tree-node__expand-icon.is-leaf { display: inline-block; width: 12px; }
::v-deep .el-tree-node__content:hover, ::v-deep .el-tree-node:focus > .el-tree-node__content { background: transparent; }
::v-deep .el-tree-node.is-current > .el-tree-node__content { color: #54ffff; background: transparent; }