Browse Source

按要求修改样式问题

inspection_yanyuan
douyage 3 weeks ago
parent
commit
88ee4a9ad0
11 changed files with 151 additions and 86 deletions
  1. +11
    -0
      src/api/videosMonitor/index.js
  2. +3
    -18
      src/layout/components/AppMain.vue
  3. +9
    -6
      src/layout/components/TagsView/index.vue
  4. +17
    -35
      src/layout/index.vue
  5. +3
    -2
      src/router/index.js
  6. +5
    -1
      src/store/modules/tagsView.js
  7. +1
    -15
      src/views/cards/assets/styles/index.scss
  8. +46
    -1
      src/views/cards/taskSummary/inspectionTask.vue
  9. +5
    -1
      src/views/portal/components/PortalDeviceTree.vue
  10. +47
    -5
      src/views/video/LivePreview.vue
  11. +4
    -2
      src/views/video/components/NvrDeviceTree.vue

+ 11
- 0
src/api/videosMonitor/index.js View File

@ -124,6 +124,17 @@ export function taskStatusChange(params) {
params,
});
}
/**
* 按巡视任务执行 ID 删除任务状态。多个 ID 用英文逗号拼在路径里,不传请求体。
* @param {string} taskPatrolledIds
*/
export function removeTaskStatus(taskPatrolledIds) {
return request({
url: "/patrol/taskstatus/removeByTaskPatrolledIds/" + taskPatrolledIds,
method: "post",
headers: { repeatSubmit: false },
});
}
/**
* 录像列表
* @param {*} data


+ 3
- 18
src/layout/components/AppMain.vue View File

@ -46,7 +46,7 @@ export default {
<style lang="scss" scoped>
.app-main {
/* 5.5vh = navbar,和别家标题栏同一高度 */
/* 5.5vh = navbar,和别家标题栏同一高度。标题栏固定后由外边距占位,这里不再重复扣高度 */
height: calc(100vh - 5.5vh);
min-height: 0;
width: 100%;
@ -54,28 +54,13 @@ export default {
overflow: hidden;
}
.fixed-header + .app-main {
padding-top: 5.5vh;
}
.app-main.hasTagsView {
.app-main.hasTagsView,
.app-main.patrolWorkspace.hasTagsView {
/* 5.5vh + 80px = navbar + subbar + tags view */
height: calc(100vh - 5.5vh - 80px);
min-height: 0;
}
.fixed-header + .app-main.hasTagsView {
padding-top: calc(5.5vh + 80px);
}
.app-main.patrolWorkspace.hasTagsView {
height: calc(100vh - 5.5vh - 80px);
}
.fixed-header + .app-main.patrolWorkspace.hasTagsView {
padding-top: calc(5.5vh + 80px);
}
</style>
<style lang="scss">


+ 9
- 6
src/layout/components/TagsView/index.vue View File

@ -189,12 +189,15 @@ export default {
routes.forEach(route => {
if (route.meta && route.meta.affix) {
const tagPath = path.resolve(basePath, route.path)
tags.push({
fullPath: tagPath,
path: tagPath,
name: route.name,
meta: { ...route.meta }
})
// 本地首页和后端菜单可能各有一条同名固定页签,只保留先收集到的那条
if (!tags.some(tag => tag.path === tagPath || (tag.meta && tag.meta.title === route.meta.title))) {
tags.push({
fullPath: tagPath,
path: tagPath,
name: route.name,
meta: { ...route.meta }
})
}
}
if (route.children) {
const tempTags = this.filterAffixTags(route.children, route.path)


+ 17
- 35
src/layout/index.vue View File

@ -201,8 +201,15 @@ export default {
}
.layout-header {
margin-left: -$base-sidebar-width;
width: calc(100% + #{$base-sidebar-width});
// 标题栏固定铺满视口,不跟着侧边栏宽度伸缩,收起菜单时标题背景不会被重新拉伸。
// 自身脱离文档流,由下面的占位高度把内容顶到原位置
position: fixed;
top: 0;
left: 0;
right: 0;
// 低于侧边栏(1001),收起按钮仍盖在标题栏之上、可以点击
z-index: 1000;
margin-left: 0;
.tags-view-container {
width: calc(100% - #{$base-sidebar-width});
@ -211,6 +218,14 @@ export default {
}
}
.hasTagsView .layout-header + .app-main {
margin-top: calc(5.5vh + 80px);
}
.layout-header + .app-main {
margin-top: 5.5vh;
}
.module-subbar {
position: relative;
width: 100%;
@ -330,32 +345,14 @@ export default {
box-shadow: 0 0 14px rgba(63, 255, 254, 0.85);
}
.fixed-header {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1002;
margin-left: 0;
width: 100%;
transition: none;
}
.hideSidebar .layout-header {
margin-left: -54px;
width: calc(100% + 54px);
.tags-view-container {
width: calc(100% - 54px);
margin-left: 54px;
}
}
.sidebarWide .layout-header {
margin-left: -$base-sidebar-wide-width;
width: calc(100% + #{$base-sidebar-wide-width});
.tags-view-container {
width: calc(100% - #{$base-sidebar-wide-width});
margin-left: $base-sidebar-wide-width;
@ -363,9 +360,6 @@ export default {
}
.sidebarWide2 .layout-header {
margin-left: -$base-sidebar-wide2-width;
width: calc(100% + #{$base-sidebar-wide2-width});
.tags-view-container {
width: calc(100% - #{$base-sidebar-wide2-width});
margin-left: $base-sidebar-wide2-width;
@ -373,9 +367,6 @@ export default {
}
.sidebarWide3 .layout-header {
margin-left: -$base-sidebar-wide3-width;
width: calc(100% + #{$base-sidebar-wide3-width});
.tags-view-container {
width: calc(100% - #{$base-sidebar-wide3-width});
margin-left: $base-sidebar-wide3-width;
@ -386,21 +377,12 @@ export default {
margin-left: 54px;
}
.hideSidebar .fixed-header {
margin-left: 0;
width: 100%;
}
.sidebarHide .layout-header,
.mobile .layout-header {
margin-left: 0;
width: 100%;
.tags-view-container {
width: 100%;
margin-left: 0;
}
}
.sidebarHide .module-links,


+ 3
- 2
src/router/index.js View File

@ -48,12 +48,13 @@ export const constantRoutes = [
path: '/xfdh',
component: Layout,
hidden: true,
redirect: '/xfdh/index',
// 与默认首页 /index 是同一页,重定向过去复用固定页签,避免再开一个「巡检任务」
redirect: '/index',
meta: { title: '巡检任务', xfdhHeader: true },
children: [
{
path: 'index',
component: () => import('@/views/cards/taskSummary/inspectionTask'),
redirect: '/index',
name: 'XfdhIndex',
meta: { title: '巡检任务', activeMenu: '/task-summary/inspectionTask', xfdhHeader: true }
}


+ 5
- 1
src/store/modules/tagsView.js View File

@ -15,9 +15,13 @@ const mutations = {
},
ADD_VISITED_VIEW: (state, view) => {
if (state.visitedViews.some(v => v.path === view.path)) return
// 本地首页已经固定了一个「巡检任务」页签。后端菜单如果再带一条同名固定页签,
// 路径不同也会被当成新页签,登录后就会出现两个「巡检任务」。
const title = view.meta && view.meta.title
if (view.meta && view.meta.affix && title && state.visitedViews.some(v => v.meta && v.meta.affix && v.meta.title === title)) return
state.visitedViews.push(
Object.assign({}, view, {
title: view.meta.title || 'no-name'
title: title || 'no-name'
})
)
},


+ 1
- 15
src/views/cards/assets/styles/index.scss View File

@ -720,21 +720,7 @@
padding-bottom: 0;
.el-message-box__header {
background-color: #222F3D;
color: #fff;
border-bottom: #40486a 1px solid;
height: 63px;
padding: 17px 24px;
.el-message-box__headerbtn .el-message-box__close {
color: #fff;
}
.el-message-box__title {
color: #fff;
font-size: 20px;
}
display: none;
}
.el-message-box__content {


+ 46
- 1
src/views/cards/taskSummary/inspectionTask.vue View File

@ -296,6 +296,14 @@
@click="gotoTask('4', scope.row.lineId, scope.row)"
>终止</el-button
>
<el-button
class="task-row-btn"
type="primary"
size="mini"
:disabled="!scope.row.taskPatrolledId"
@click="deleteTask(scope.row)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
@ -494,7 +502,7 @@ import debounce from "lodash.debounce";
import moment from "moment";
import axios from "axios";
import { openDialog } from "../common.js";
import { list, areaList, taskStatusChange } from "@/api/videosMonitor/index";
import { list, areaList, taskStatusChange, removeTaskStatus } from "@/api/videosMonitor/index";
import { listData } from "@/api/system/dict/data";
import { listUser } from "@/api/system/user";
import modelDialog from "../components/model-dialog.vue";
@ -1007,6 +1015,43 @@ export default {
},
});
},
deleteTask(row) {
const taskPatrolledId = row && row.taskPatrolledId;
if (!taskPatrolledId) {
this.$message.warning("缺少任务执行ID,无法删除");
return;
}
const taskName = row.taskName || "该任务";
this.$confirm(
`删除后不可恢复,确认删除「${taskName}」吗?`,
{
title: "提示",
customClass: "card-page card-message-box",
type: "warning",
}
).then(() => {
removeTaskStatus(taskPatrolledId).then((res) => {
const body = res && res.data && typeof res.data === "object"
? Object.assign({}, res, res.data)
: (res || {});
const status = body.status !== undefined ? body.status : body.code;
const failed = [false, 0, "0", 500, "500", "fail", "failed"].includes(status);
if (failed) {
this.$modal.msgWarning(body.msg || "删除失败");
return;
}
if (body.pushSuccess === false) {
this.$modal.msgWarning("删除成功");
} else {
this.$modal.msgSuccess("删除成功");
}
this.selectedRows = this.selectedRows.filter(
(item) => item.taskPatrolledId !== taskPatrolledId
);
this.getList();
});
});
},
gotoTask(command, lineId, row) {
this.$confirm("确认操作吗?", {
title: "提示",


+ 5
- 1
src/views/portal/components/PortalDeviceTree.vue View File

@ -2,7 +2,7 @@
<aside class="portal-tree">
<div class="tree-title">
<span><i class="el-icon-s-platform" /> 设备列表</span>
<strong>{{ channelNodes.length }}</strong>
<strong>{{ visibleChannelCount }}</strong>
</div>
<div class="tree-filters">
<el-select v-model="onlineStatus" size="small" placeholder="状态" clearable>
@ -98,6 +98,10 @@ export default {
computed: {
channelNodes() {
return flatten(this.treeNodes).filter(node => node.channelFlag);
},
// 标题数字跟当前筛选走:在线/离线/搜索后只统计树上还看得到的通道
visibleChannelCount() {
return flatten(this.treeData).filter(node => node.channelFlag).length;
}
},
watch: {


+ 47
- 5
src/views/video/LivePreview.vue View File

@ -79,8 +79,8 @@
</div>
<div class="camera-actions">
<button v-if="!embed" type="button" title="历史回放" @click.stop="openPlayback(slot)"><i class="el-icon-time" /></button>
<button type="button" title="全屏" @click.stop="fullscreen(index)"><i class="el-icon-full-screen" /></button>
<button type="button" title="关闭" @click.stop="closeSlot(index)"><i class="el-icon-close" /></button>
<button type="button" :title="fullscreenIndex === index ? '退出放大' : '全屏'" @click.stop="fullscreen(index)"><i class="el-icon-full-screen" /></button>
<button type="button" :title="fullscreenIndex === index ? '退出放大' : '关闭'" @click.stop="closePreview(index)"><i class="el-icon-close" /></button>
</div>
</template>
<div v-else class="empty-slot">
@ -405,6 +405,7 @@ const PTZ_CODES = {
this.$root.$emit('video-control-panel-updated', this.controlPanelExpanded)
}
document.addEventListener('fullscreenchange', this.handleFullscreenChange)
document.addEventListener('webkitfullscreenchange', this.handleFullscreenChange)
if (this.embed) {
this.layout = this.normalizeLayout(this.embedLayout)
this.embedTreeVisible = this.embedTreeExpanded
@ -434,6 +435,7 @@ const PTZ_CODES = {
this.lifecycleId += 1
this.pageActive = false
document.removeEventListener('fullscreenchange', this.handleFullscreenChange)
document.removeEventListener('webkitfullscreenchange', this.handleFullscreenChange)
if (!this.embed) {
this.$root.$off('video-layout-change', this.changeLayout)
this.$root.$off('video-control-panel-change', this.changeControlPanel)
@ -657,9 +659,27 @@ const PTZ_CODES = {
this.selectChannel(channel)
this.startChannel(channel)
},
isSameCamera(left, right) {
if (!left || !right || !left.cameraCode || left.cameraCode !== right.cameraCode) return false
if (left.stationCode && right.stationCode && left.stationCode !== right.stationCode) return false
return true
},
// 同一摄像头只保留最新的一路,避免宫格里出现两路相同画面
async closeEarlierCopies(channel, keepIndex) {
const indexes = []
this.slots.forEach((slot, slotIndex) => {
if (slotIndex === keepIndex || !this.isSameCamera(slot, channel)) return
indexes.push(slotIndex)
})
for (const slotIndex of indexes) {
await this.closeSlot(slotIndex, false)
}
},
async startChannel(channel, targetIndex = this.activeIndex, attemptedCameraCodes = [], streamType = this.streamType) {
if (!this.pageActive || !channel || !channel.cameraCode) return
const index = targetIndex
await this.closeEarlierCopies(channel, index)
if (!this.pageActive) return
const current = this.slots[index]
if (current && current.cameraCode === channel.cameraCode && current.streamType === streamType) {
this.controlTarget = current
@ -890,13 +910,35 @@ const PTZ_CODES = {
this.$message.error((error && error.message) || '视频播放失败,暂无其他可用摄像头')
}
},
fullscreenElement() {
return document.fullscreenElement || document.webkitFullscreenElement || null
},
exitFullscreen() {
const exit = document.exitFullscreen || document.webkitExitFullscreen
if (exit) return exit.call(document)
},
fullscreen(index) {
const card = this.$refs.cameraCards && this.$refs.cameraCards[index]
if (card && card.requestFullscreen) card.requestFullscreen()
if (!card) return
if (this.fullscreenElement() === card) {
this.exitFullscreen()
return
}
const request = card.requestFullscreen || card.webkitRequestFullscreen
if (request) request.call(card)
},
// 放大后右下角关闭和全屏按钮一样,只退出放大,不停掉这一路视频
closePreview(index) {
const card = this.$refs.cameraCards && this.$refs.cameraCards[index]
if (card && this.fullscreenElement() === card) {
this.exitFullscreen()
return
}
this.closeSlot(index)
},
// 放大(全屏)观看时切主码流,退出全屏后恢复当前布局对应的码流
async handleFullscreenChange() {
const fsElement = document.fullscreenElement
const fsElement = this.fullscreenElement()
if (fsElement) {
const cards = this.$refs.cameraCards || []
const index = cards.indexOf(fsElement)
@ -1159,7 +1201,7 @@ const PTZ_CODES = {
.camera-grid { flex: 1; min-height: 0; display: grid; gap: 3px; background: rgba(14, 75, 146, .5); border: 1px solid #176bc2; }
.camera-card { position: relative; min-width: 0; min-height: 0; overflow: hidden; border: 1px solid transparent; background: #050b24; cursor: pointer; }.camera-card.active { border-color: #54ffff; box-shadow: inset 0 0 0 1px rgba(84,255,255,.35); }
.camera-label { position: absolute; left: 0; right: 0; top: 0; height: 28px; padding: 0 8px; display: flex; align-items: center; justify-content: space-between; color: #d8eaff; background: linear-gradient(180deg, rgba(0,10,36,.92), transparent); font-size: 11px; pointer-events: none; user-select: none; }.camera-label i { display: inline-block; width: 6px; height: 6px; margin-right: 5px; border-radius: 50%; background: #24e39f; box-shadow: 0 0 6px #24e39f; }
.camera-actions { position: absolute; right: 5px; bottom: 5px; display: none; gap: 3px; }.camera-card:hover .camera-actions { display: flex; }.camera-actions button { width: 26px; height: 25px; border: 1px solid rgba(66, 167, 255, .55); color: #bfe0ff; background: rgba(4,28,78,.88); cursor: pointer; }
.camera-actions { position: absolute; right: 5px; bottom: 5px; display: none; gap: 3px; }.camera-card:hover .camera-actions, .camera-card:fullscreen .camera-actions, .camera-card:-webkit-full-screen .camera-actions { display: flex; }.camera-actions button { width: 26px; height: 25px; border: 1px solid rgba(66, 167, 255, .55); color: #bfe0ff; background: rgba(4,28,78,.88); cursor: pointer; }
.empty-slot { height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px; color: #466f9c; background: radial-gradient(circle at center, #0b2a61, #050b24 70%); user-select: none; }.empty-slot i { font-size: 28px; }.empty-slot span { font-size: 11px; }
.control-panel { position: absolute; top: 0; right: 0; bottom: 0; z-index: 4; width: 242px; min-width: 242px; height: auto; margin: 0; overflow: visible; border: 1px solid rgba(63,127,216,.72); border-radius: 2px; background: rgba(7, 24, 78, .82); box-sizing: border-box; cursor: move; user-select: none; }
.control-panel-body { height: 100%; padding: 10px; overflow-x: hidden; overflow-y: auto; box-sizing: border-box; }


+ 4
- 2
src/views/video/components/NvrDeviceTree.vue View File

@ -2,7 +2,7 @@
<aside class="nvr-tree">
<div class="tree-title">
<span><i class="el-icon-s-platform" /> 设备列表</span>
<strong>{{ channelNodes.length }}</strong>
<strong>{{ visibleChannelCount }}</strong>
</div>
<div class="tree-filters">
<el-select v-model="onlineStatus" size="small" placeholder="状态" clearable>
@ -106,7 +106,9 @@ export default {
},
computed: {
isLoading() { return this.loading || this.internalLoading },
channelNodes() { return flatten(this.treeNodes).filter(node => node.channelFlag) }
channelNodes() { return flatten(this.treeNodes).filter(node => node.channelFlag) },
// 标题数字跟当前筛选走:在线/离线/搜索后只统计树上还看得到的通道
visibleChannelCount() { return flatten(this.treeData).filter(node => node.channelFlag).length }
},
watch: {
onlineStatus: 'refreshTree',


Loading…
Cancel
Save