Browse Source

做汇聚系统

inspection_yanyuan
douyage 1 day ago
parent
commit
339d6b3145
22 changed files with 502 additions and 144 deletions
  1. +5
    -3
      src/api/inspectionDataManage/inspectionReporter.js
  2. +43
    -0
      src/api/portal/index.js
  3. +20
    -4
      src/api/videosMonitor/index.js
  4. +3
    -1
      src/layout/components/AppMain.vue
  5. +5
    -2
      src/layout/components/Sidebar/index.vue
  6. +74
    -3
      src/layout/index.vue
  7. +1
    -0
      src/store/getters.js
  8. +17
    -1
      src/store/modules/siteContext.js
  9. +44
    -1
      src/utils/request.js
  10. +91
    -84
      src/views/cards/defectRecord/index.vue
  11. +9
    -0
      src/views/cards/patrolPlan/notstart.vue
  12. +15
    -13
      src/views/cards/patrolPlan/summary_editTask.vue
  13. +20
    -14
      src/views/cards/patrolPlan/summary_newTask.vue
  14. +12
    -0
      src/views/cards/patrolReport/InspectionReporter.vue
  15. +7
    -1
      src/views/cards/patrolReport/patrolReportDetail.vue
  16. +29
    -3
      src/views/cards/site_management/site_management.vue
  17. +9
    -0
      src/views/cards/taskSummary/inspectionTask.vue
  18. +3
    -1
      src/views/cards/taskSummary/inspectionTaskDefectDetail.vue
  19. +3
    -0
      src/views/cards/taskSummary/newInspectionTaskDetail.vue
  20. +8
    -2
      src/views/portal/index.vue
  21. +76
    -9
      src/views/video/LivePreview.vue
  22. +8
    -2
      src/views/video/components/NvrDeviceTree.vue

+ 5
- 3
src/api/inspectionDataManage/inspectionReporter.js View File

@ -20,10 +20,12 @@ export function getPatrolReport(lineId,filter) {
});
}
export function getPatrolReportShaoxing(url, lineId, filter) {
export function getPatrolReportShaoxing(url, lineId, filter, stationCode) {
// 汇聚态:走聚合代理,stationCode 取点击的报告所属站
const prefix = stationCode ? "/agg/proxy" : "";
return request({
url: `/patrol/insreport/shaoxing/${url}/${lineId}/${filter}`,
url: `${prefix}/patrol/insreport/shaoxing/${url}/${lineId}/${filter}`,
method: "get",
params: {},
params: stationCode ? { stationCode } : {},
});
}

+ 43
- 0
src/api/portal/index.js View File

@ -84,3 +84,46 @@ export function stopRealtimeVideo(query) {
noAlert: true
});
}
// 汇聚侧云台控制:按摄像头所属站点代理转发
// stationCode 取当前控制摄像头所在站(而非顶部站名筛选),以查询串方式传递
export function aggPtzControl(payload) {
const { stationCode, ...data } = payload || {};
return request({
url: "/agg/proxy/patrol/preset/ptzControl",
method: "post",
params: { stationCode },
data,
noAlert: true
});
}
// 汇聚侧预置位列表:路径与单站一致(cameraCode 按 # 拆成 device/domain 放路径上),仅追加 ?stationCode
export function aggPresetList(payload) {
const { stationCode, cameraCode } = payload || {};
const value = String(cameraCode || "");
const separatorIndex = value.indexOf("#");
if (separatorIndex <= 0 || separatorIndex === value.length - 1) {
return Promise.reject(new Error("摄像机编码格式不正确"));
}
const device = encodeURIComponent(value.slice(0, separatorIndex));
const domain = encodeURIComponent(value.slice(separatorIndex + 1));
return request({
url: `/agg/proxy/patrol/preset/presetList/${device}/${domain}`,
method: "get",
params: { stationCode },
noAlert: true
});
}
// 汇聚侧预置位保存:按摄像头所属站点代理转发
export function aggPresetCreate(payload) {
const { stationCode, ...data } = payload || {};
return request({
url: "/agg/proxy/patrol/preset/presetCreate",
method: "post",
params: { stationCode },
data,
noAlert: true
});
}

+ 20
- 4
src/api/videosMonitor/index.js View File

@ -1,4 +1,5 @@
import request from "@/utils/request";
import store from "@/store";
import { defectList } from "../home/index";
/**
@ -229,11 +230,13 @@ export function analysisDefectList(params) {
* @param {*} data
* @returns
*/
export function analysisDefectListShaoxing(params) {
export function analysisDefectListShaoxing(params, stationCode) {
// 汇聚态:走聚合代理,stationCode 取点击的任务所属站
const prefix = stationCode ? "/agg/proxy" : "";
return request({
url: `/patrol/analysis/list_v2_ex`,
url: `${prefix}/patrol/analysis/list_v2_ex`,
method: "get",
params,
params: stationCode ? { ...params, stationCode } : params,
});
}
@ -317,14 +320,27 @@ export function getDefectAlarmList(params = {}) {
});
}
/**
* 缺陷记录卡片的列表
* 缺陷记录卡片的列表支持 pageNum/pageSize/desc/pointName 等服务端筛选
* @param {*} data
* @returns
*/
export function defectRecordList(params) {
// 汇聚态由拦截器映射改写为 /agg/patrol/analysis/defect/list;单站保持原路径(服务端 algName 筛选)
return request({
url: `/patrol/analysis/defect/list`,
method: "get",
params,
});
}
/**
* 缺陷类型下拉选项汇聚态 descOptions单站 algNameOptions算法名称
* @returns
*/
export function defectDescOptions() {
const isAgg = store.getters.fromPortal;
return request({
url: isAgg ? `/agg/analysis/defect/algNameOptions` : `/patrol/analysis/defect/algNameOptions`,
method: "get",
});
}

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

@ -20,7 +20,9 @@ export default {
return this.$store.state.tagsView.cachedViews
},
key() {
return this.$route.path
//
const stationCode = this.$store.getters.workspaceStationCode || ''
return this.$route.path + (stationCode ? '||' + stationCode : '')
}
},
watch: {


+ 5
- 2
src/layout/components/Sidebar/index.vue View File

@ -50,7 +50,7 @@ export default {
},
computed: {
...mapState(["settings"]),
...mapGetters(["sidebarRouters", "sidebar", "roles"]),
...mapGetters(["sidebarRouters", "sidebar", "roles", "fromPortal"]),
// admin
isAdminAccount() {
if ((this.roles || []).indexOf("admin") !== -1) return true;
@ -80,7 +80,10 @@ export default {
const isTaskManagement = ["巡检任务管理", "巡视任务管理"].includes(title);
const isImplementationPoints = !this.isAdminAccount && isImplementationPointsMenu(route);
const isTemporarilyHiddenRootMenu = depth === 0 && temporarilyHiddenRootMenus.has(title);
if (isTaskManagement || isImplementationPoints || isTemporarilyHiddenRootMenu) return result;
//
const isHistoryPlaybackHidden = this.fromPortal &&
["/video-playback", "/inspection-workspace/video-playback"].includes(route.path);
if (isTaskManagement || isImplementationPoints || isTemporarilyHiddenRootMenu || isHistoryPlaybackHidden) return result;
result.push({
...route,


+ 74
- 3
src/layout/index.vue View File

@ -23,7 +23,23 @@
@click="goModule(item.path)"
>{{ item.label }}</button>
</div>
<!-- station selector removed -->
<!-- 汇聚态站名筛选接口与首页一致/agg/station/all默认全部场站 -->
<el-select
v-if="fromPortal"
v-model="stationCode"
class="module-station-select"
size="mini"
placeholder="全部场站"
clearable
>
<el-option label="全部场站" value="" />
<el-option
v-for="station in stationOptions"
:key="station.stationCode"
:label="station.stationName"
:value="station.stationCode"
/>
</el-select>
</div>
<tags-view v-if="needTagsView"/>
</div>
@ -41,6 +57,7 @@ import { AppMain, Navbar, Settings, Sidebar, TagsView } from './components'
import ResizeMixin from './mixin/ResizeHandler'
import { mapState, mapGetters } from 'vuex'
import variables from '@/assets/styles/variables.scss'
import { getStationList } from '@/api/portal'
export default {
name: 'Layout',
@ -55,7 +72,9 @@ export default {
data() {
return {
// 0=160px1=200px2=240px3=280px
sidebarWidthLevel: 0
sidebarWidthLevel: 0,
//
stationOptions: []
}
},
mixins: [ResizeMixin],
@ -83,7 +102,20 @@ export default {
isPatrolWorkspace() {
return this.$route.matched.some(route => route.meta && route.meta.patrolWorkspace)
},
...mapGetters(['fromPortal']),
// 1
sidebarWide() {
return this.sidebarWidthLevel >= 1
},
...mapGetters(['fromPortal', 'workspaceStationCode']),
// storesessionStorage
stationCode: {
get() {
return this.workspaceStationCode || ''
},
set(value) {
this.$store.dispatch('siteContext/setWorkspaceStation', value)
}
},
//
moduleLinks() {
if (!this.fromPortal) return []
@ -103,7 +135,18 @@ export default {
return variables;
}
},
mounted() {
if (this.fromPortal) this.loadStationOptions()
},
methods: {
//
loadStationOptions() {
getStationList({}).then(response => {
this.stationOptions = Array.isArray(response && response.data) ? response.data : []
}).catch(() => {
this.stationOptions = []
})
},
goModule(path) {
if (this.$route.path !== path) this.$router.push({ path }).catch(() => undefined)
},
@ -186,6 +229,34 @@ export default {
align-items: stretch;
}
/* 汇聚态站名筛选:靠行最右 */
.module-station-select {
width: 150px;
::v-deep .el-input__inner {
height: 28px;
line-height: 28px;
background: rgba(16, 30, 84, 0.65);
border-color: rgba(84, 130, 255, 0.35);
color: #eef4ff;
font-size: 12px;
&::placeholder {
color: rgba(159, 180, 232, 0.65);
}
}
::v-deep .el-input__suffix {
display: flex;
align-items: center;
height: 28px;
}
::v-deep .el-input__icon {
line-height: 28px;
}
}
.module-link {
position: relative;
min-width: 90px;


+ 1
- 0
src/store/getters.js View File

@ -21,5 +21,6 @@ const getters = {
siteName: state => state.siteContext.siteName,
inSite: state => state.siteContext.siteId !== undefined && state.siteContext.siteId !== null,
fromPortal: state => state.siteContext.fromPortal,
workspaceStationCode: state => state.siteContext.workspaceStationCode,
}
export default getters

+ 17
- 1
src/store/modules/siteContext.js View File

@ -5,6 +5,8 @@
const SITE_KEY = "portal_site";
// 是否从汇聚门户跳转进入单站视图(会话级标记,直接输地址打开时不带)
const FROM_KEY = "portal_from";
// 汇聚门户工作台顶部"站名"下拉选中的站(会话级)
const WORKSPACE_STATION_KEY = "portal_ws_station";
function readSite() {
try {
@ -22,7 +24,8 @@ const siteContext = {
return {
siteId: site ? site.id : undefined,
siteName: site ? site.name : "",
fromPortal: sessionStorage.getItem(FROM_KEY) === "1"
fromPortal: sessionStorage.getItem(FROM_KEY) === "1",
workspaceStationCode: sessionStorage.getItem(WORKSPACE_STATION_KEY) || ""
};
},
mutations: {
@ -42,6 +45,14 @@ const siteContext = {
} else {
sessionStorage.removeItem(SITE_KEY);
}
},
SET_WORKSPACE_STATION(state, code) {
state.workspaceStationCode = code || "";
if (code) {
sessionStorage.setItem(WORKSPACE_STATION_KEY, code);
} else {
sessionStorage.removeItem(WORKSPACE_STATION_KEY);
}
}
},
actions: {
@ -49,6 +60,10 @@ const siteContext = {
markFromPortal({ commit }) {
commit("SET_FROM_PORTAL", true);
},
// 工作台顶部站名下拉选择
setWorkspaceStation({ commit }, code) {
commit("SET_WORKSPACE_STATION", code);
},
// 进入单站视图
enterSite({ commit }, site) {
commit("SET_SITE", site);
@ -62,6 +77,7 @@ const siteContext = {
fromPortal: state => state.fromPortal,
siteId: state => state.siteId,
siteName: state => state.siteName,
workspaceStationCode: state => state.workspaceStationCode,
// 是否处于单站视图
inSite: state => state.siteId !== undefined && state.siteId !== null
}


+ 44
- 1
src/utils/request.js View File

@ -18,6 +18,39 @@ let downloadLoadingInstance;
export let isRelogin = { show: false };
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
// 汇聚门户(动环巡检)只读会话的接口改写映射:
// 从门户进入且未进入单站视图时,动环业务查询统一走后端聚合接口(agg)。
// key/value 不含查询参数,按 url(忽略首部斜杠)全等匹配,
// 因此 /relation/right、/patrol/insreport/{id}/{filter} 等衍生接口不会被误改。
const AGG_API_MAP = {
'patrol/taskstatus/list': 'agg/taskstatus/list',
'patrol/task/taskInfo_v2': 'agg/proxy/patrol/task/taskInfo_v2',
'patrol/task/getPatrolPointName': 'agg/proxy/patrol/task/getPatrolPointName',
'patrol/task/list': 'agg/task/list',
'basedata/prodevmnt/device/treeAreaDeviceSelectFilterByPreset/relation': 'patrol/agg/tree/device',
'basedata/patrolpointmnt/patrolpoint/list/monitor/relation': 'agg/patrolpoint/list/monitor',
'patrol/insreport/list': 'agg/insreport/list',
'patrol/calender/year': 'agg/calender/year',
'patrol/calender/month': 'agg/calender/month',
'patrol/calender/day': 'agg/calender/day',
'patrol/analysis/defect/list': 'agg/analysis/defect/list'
}
// 汇聚门户工作台顶部"站名"下拉:选中站后,下列查询接口统一携带 stationCode
// (前缀匹配;缺陷类型下拉选项 algNameOptions 也按选中站过滤)
const AGG_STATION_CODE_PATHS = [
'/agg/taskstatus/list',
'/agg/task/list',
'/agg/insreport/list',
'/agg/calender/',
'/agg/tree/channel',
'/patrol/agg/tree/device',
'/agg/patrolpoint/list/monitor',
'/agg/analysis/defect/list',
'/agg/analysis/defect/algNameOptions'
]
// 创建axios实例
const service = axios.create({
// axios中请求配置有baseURL选项,表示请求URL公共部分
@ -28,6 +61,16 @@ const service = axios.create({
// request拦截器
service.interceptors.request.use(config => {
// 汇聚门户只读会话:动环业务接口改写为聚合接口(单站/嵌入会话不受影响)
if (store.getters.fromPortal && !store.getters.inSite && typeof config.url === "string") {
const mapped = AGG_API_MAP[config.url.replace(/^\/+/, "")]
if (mapped) config.url = "/" + mapped
// 工作台顶部站名下拉:选中站后给列表/树查询带上 stationCode("全部场站"不传)
const wsCode = store.getters.workspaceStationCode
if (wsCode && config.method === "get" && AGG_STATION_CODE_PATHS.some(p => config.url.indexOf(p) === 0)) {
config.params = Object.assign({}, config.params, { stationCode: wsCode })
}
}
// 是否需要设置 token
const isToken = (config.headers || {}).isToken === false
// 是否需要防止数据重复提交
@ -89,7 +132,7 @@ service.interceptors.request.use(config => {
function LoginEntity() {
let username;
let password;
let password;
}


+ 91
- 84
src/views/cards/defectRecord/index.vue View File

@ -11,13 +11,11 @@
<span class="total-value">{{ total }}</span>
</div>
<el-select
v-model="defectTypeList"
v-model="defectType"
class="query-item query-type"
multiple
collapse-tags
filterable
clearable
placeholder="缺陷类型"
placeholder="算法类型"
@change="handleQuery"
>
<el-option
@ -77,7 +75,24 @@
align="center"
:index="table_index"
/>
<el-table-column
label="任务名称"
align="center"
prop="taskName"
min-width="140"
show-overflow-tooltip
/>
<el-table-column label="算法类型" align="center" prop="algName" />
<el-table-column label="区域" align="center" prop="areaName" />
<!-- 站名列汇聚门户聚合数据下展示 -->
<el-table-column
v-if="$store.getters.fromPortal"
label="站名"
align="center"
prop="stationName"
min-width="100"
show-overflow-tooltip
/>
<el-table-column label="点位" align="center" prop="pointName" />
<el-table-column label="采集时间" align="center" prop="patrolTime" />
<el-table-column label="缺陷照片" align="center">
@ -128,7 +143,7 @@
<script>
import CardBox from "../components/cardBox.vue";
import CardPagination from "../components/CardPagination/index.vue";
import { defectRecordList } from "@/api/videosMonitor/index";
import { defectRecordList, defectDescOptions } from "@/api/videosMonitor/index";
import ImagePreview from "@/views/cards/components/ImagePreview/index.vue";
import AsyncImage from "@/components/AsyncImage/index.vue";
export default {
@ -137,9 +152,8 @@ export default {
data() {
return {
queryParams: { pageNum: 1, pageSize: 20, pointName: "" },
defectTypeList: [],
allDataList: [], //
filteredList: [], //
defectType: "", //
defectTypeOptions: [], // descOptions
dataList: [],
loading: false,
total: 0,
@ -165,18 +179,9 @@ export default {
previewImagePos: 0,
};
},
computed: {
//
defectTypeOptions() {
const set = new Set();
this.allDataList.forEach((item) => {
const desc = (item.desc || "").trim();
if (desc) set.add(desc);
});
return Array.from(set).sort((a, b) => a.localeCompare(b, "zh"));
},
},
computed: {},
created() {
this.loadDescOptions();
this.loadData();
window.addEventListener("resize", this.setTableHeight);
},
@ -209,92 +214,97 @@ export default {
handleQuery() {
this.queryParams.pageNum = 1;
this.applyFilters();
this.loadData();
},
//
//
resetQuery() {
this.defectTypeList = [];
this.defectType = "";
this.queryParams.pointName = "";
this.dateRange = [];
this.handleQuery();
},
//
// algName
loadData() {
this.loading = true;
//
this.imageLoadCount = 0;
this.expectedImageCount = 0;
// 20
defectRecordList({ pageNum: 1, pageSize: 20 })
const params = {
pageNum: this.queryParams.pageNum,
pageSize: this.queryParams.pageSize,
pointName: this.queryParams.pointName || undefined,
algName: this.defectType || undefined,
beginTime:
this.dateRange && this.dateRange.length === 2
? this.dateRange[0]
: undefined,
endTime:
this.dateRange && this.dateRange.length === 2
? this.dateRange[1]
: undefined,
};
defectRecordList(params)
.then((res) => {
this.allDataList = res.rows || [];
this.applyFilters();
this.dataList = res.rows || [];
this.total = res.total || 0;
//
if (!this.defectTypeOptions.length) {
this.deriveDescOptions(this.dataList);
}
this.expectedImageCount = this.dataList.filter(
(item) => item.imgAnalyse
).length;
if (this.expectedImageCount === 0) {
this.forceRefreshTable();
}
})
.catch((error) => {
console.error("获取缺陷记录失败:", error);
this.allDataList = [];
this.applyFilters();
this.dataList = [];
this.total = 0;
this.expectedImageCount = 0;
this.forceRefreshTable();
})
.finally(() => {
this.loading = false;
//
this.$nextTick(this.setTableHeight);
});
},
//
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;
// descOptions algNameOptions
loadDescOptions() {
defectDescOptions()
.then((res) => {
let options = res && res.data;
if (!Array.isArray(options)) options = [];
this.defectTypeOptions = options
.map((item) =>
typeof item === "string"
? item
: (item &&
(item.algName || item.desc || item.name || item.label)) ||
""
)
.filter(Boolean)
.sort((a, b) => a.localeCompare(b, "zh"));
})
.catch(() => {
this.defectTypeOptions = [];
});
},
deriveDescOptions(rows) {
const set = new Set();
(rows || []).forEach((item) => {
const desc = (item.desc || "").trim();
if (desc) set.add(desc);
});
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.defectTypeOptions = Array.from(set).sort((a, b) =>
a.localeCompare(b, "zh")
);
//
this.expectedImageCount = this.dataList.filter(
(item) => item.imgAnalyse
).length;
//
if (this.expectedImageCount === 0) {
this.forceRefreshTable();
} else {
this.$nextTick(this.setTableHeight);
}
},
linkUrl() {
@ -357,12 +367,9 @@ export default {
},
//
//
getList() {
if (!this.allDataList.length) {
this.loadData();
return;
}
this.applyFilters();
this.loadData();
},
onSubmit() {


+ 9
- 0
src/views/cards/patrolPlan/notstart.vue View File

@ -111,6 +111,15 @@
:resizable="false"
/>
<el-table-column label="方案名称" align="left" prop="taskName" min-width="360" header-align="center" show-overflow-tooltip :resizable="true" />
<!-- 站名列汇聚门户聚合数据下展示 -->
<el-table-column
v-if="$store.getters.fromPortal"
label="站名"
align="center"
prop="stationName"
min-width="100"
show-overflow-tooltip
/>
<el-table-column label="点位数" align="center" prop="totalPoints" width="90" :resizable="true" />
<el-table-column label="时间" align="center" prop="createTime" width="170" show-overflow-tooltip :resizable="true" />
<el-table-column


+ 15
- 13
src/views/cards/patrolPlan/summary_editTask.vue View File

@ -284,21 +284,21 @@
/>
</div>
<!-- 点位类型 - 复选框组 -->
<!-- 点位类型 - 单选默认外观 -->
<div class="form-row checkbox-row">
<div class="form-label">点位类型 <span>*</span></div>
<div class="form-input checkbox-group-wrapper">
<el-checkbox-group
v-model="formData.pointTypeList"
<el-radio-group
v-model="formData.pointType"
@change="handlePointTypeChange"
>
<el-checkbox
<el-radio
v-for="item in pointTypeOptions"
:key="String(item.dictCode)"
:label="String(item.dictCode)"
>{{ item.dictLabel }}</el-checkbox
>{{ item.dictLabel }}</el-radio
>
</el-checkbox-group>
</el-radio-group>
</div>
</div>
@ -415,7 +415,7 @@ export default {
taskName: "",
priority: "1",
type: null,
pointTypeList: [],
pointType: "",
deviceTypeList: [],
executionMode: true,
executionType: "1",
@ -481,7 +481,7 @@ export default {
return findSpecialPatrol(this.listPatroltype);
},
submitPointType() {
return this.formData.pointTypeList.join(",");
return this.formData.pointType;
},
submitDeviceType() {
return this.formData.deviceTypeList.join(",");
@ -610,15 +610,17 @@ export default {
this.formData.isEnabled = data.isenable || "0";
if (data.pointType) {
this.formData.pointTypeList = data.pointType
//
const codes = String(data.pointType)
.split(",")
.filter((code) =>
this.pointTypeOptions.some(
(opt) => String(opt.dictCode) === String(code)
)
);
this.formData.pointType = codes.length ? codes[0] : "";
} else {
this.formData.pointTypeList = [];
this.formData.pointType = "";
}
if (data.deviceType) {
@ -653,8 +655,8 @@ export default {
if (this.formData.deviceTypeList.length) {
params.devTypeCodeList = this.formData.deviceTypeList;
}
if (this.formData.pointTypeList.length) {
params.algTypeCodeList = this.formData.pointTypeList;
if (this.formData.pointType) {
params.algTypeCodeList = [this.formData.pointType];
}
params.filterByPreset = true;
return treeAreaDeviceSelectFilterByPresetRight(params)
@ -966,7 +968,7 @@ export default {
this.$modal.msgError("请选择任务类型");
return false;
}
if (this.formData.pointTypeList.length === 0) {
if (!this.formData.pointType) {
this.$modal.msgError("请选择点位类型");
return false;
}


+ 20
- 14
src/views/cards/patrolPlan/summary_newTask.vue View File

@ -282,21 +282,21 @@
/>
</div>
<!-- 点位类型 - 复选框组 -->
<!-- 点位类型 - 单选默认外观 -->
<div class="form-row checkbox-row">
<div class="form-label">点位类型 <span>*</span></div>
<div class="form-input checkbox-group-wrapper">
<el-checkbox-group
v-model="pointTypeList"
<el-radio-group
v-model="pointType"
@change="handlePointTypeChange"
>
<el-checkbox
<el-radio
v-for="item in pointTypeOptions"
:key="item.dictCode"
:label="item.dictCode"
>{{ item.dictLabel }}</el-checkbox
>{{ item.dictLabel }}</el-radio
>
</el-checkbox-group>
</el-radio-group>
</div>
</div>
@ -370,7 +370,7 @@ export default {
// "0"
isEnabled: "0",
pointTypeOptions: [],
pointTypeList: [],
pointType: "",
deviceTypeOptions: [],
deviceTypeList: [],
skipWatch: false, // watch
@ -467,7 +467,7 @@ export default {
return findSpecialPatrol(this.listPatroltype);
},
submitPointType() {
return this.pointTypeList.join(",");
return this.pointType;
},
submitDeviceType() {
return this.deviceTypeList.join(",");
@ -513,9 +513,15 @@ export default {
//
this.pointTypeOptions = pointTypeRes.rows || [];
this.pointTypeList = (pointTypeRes.rows || []).map(
(item) => item.dictCode
// ""退
const appearance = this.pointTypeOptions.find(
(item) => item.dictLabel === "外观"
);
this.pointType = appearance
? appearance.dictCode
: this.pointTypeOptions.length
? this.pointTypeOptions[0].dictCode
: "";
this.deviceTypeOptions = deviceTypeRes.rows || [];
this.deviceTypeList = (deviceTypeRes.rows || []).map(
@ -542,9 +548,9 @@ export default {
params.devTypeCodeList = this.deviceTypeList;
}
//
if (this.pointTypeList && this.pointTypeList.length > 0) {
params.algTypeCodeList = this.pointTypeList;
//
if (this.pointType) {
params.algTypeCodeList = [this.pointType];
}
params.filterByPreset = true
treeAreaDeviceSelectFilterByPresetRight(params)
@ -806,7 +812,7 @@ export default {
this.$modal.msgError("请选择任务类型");
return false;
}
if (this.pointTypeList.length === 0) {
if (!this.pointType) {
this.$modal.msgError("请选择点位类型");
return false;
}


+ 12
- 0
src/views/cards/patrolReport/InspectionReporter.vue View File

@ -78,6 +78,15 @@
width="620"
header-align="center"
/>
<!-- 站名列汇聚门户聚合数据下展示 -->
<el-table-column
v-if="$store.getters.fromPortal"
label="站名"
align="center"
prop="stationName"
min-width="100"
show-overflow-tooltip
/>
<el-table-column
label="巡检开始时间"
align="center"
@ -343,6 +352,8 @@ export default {
sessionStorage.setItem("reportLineId", JSON.stringify(info.lineId));
sessionStorage.setItem("reportType", JSON.stringify(info.filter));
//
sessionStorage.setItem("reportStationCode", info.stationCode || "");
this.$router.push({
name: this.$route.meta.patrolWorkspace
? "InspectionWorkspaceReportDetail"
@ -351,6 +362,7 @@ export default {
lineId: info.lineId,
filter: info.filter,
reportType: info.filter,
stationCode: info.stationCode || undefined,
fromPage: "InspectionReporter",
fromComponent: "InspectionReporter",
},


+ 7
- 1
src/views/cards/patrolReport/patrolReportDetail.vue View File

@ -219,6 +219,7 @@ export default {
return {
lineId: null,
reportType: 0,
stationCode: "",
showPreviewImages: false,
showImgesUrls: [],
previewImagePos: 0,
@ -257,6 +258,11 @@ export default {
const savedReportType = sessionStorage.getItem("reportType");
this.reportType = savedReportType ? JSON.parse(savedReportType) : 0;
}
// stationCode
this.stationCode =
this.$route.query.stationCode ||
sessionStorage.getItem("reportStationCode") ||
"";
this.getDetail();
if (
@ -281,7 +287,7 @@ export default {
} else {
url = `report`;
}
getPatrolReportShaoxing(url, this.lineId, this.reportType || 0).then((res) => {
getPatrolReportShaoxing(url, this.lineId, this.reportType || 0, this.stationCode).then((res) => {
// Some deployments return detail as a JSON string; normalize it before grouping.
const report = res && res.data ? res.data : res;
if (report && typeof report.detail === "string") {


+ 29
- 3
src/views/cards/site_management/site_management.vue View File

@ -218,6 +218,7 @@
</template>
</el-table-column>
<el-table-column
prop="patrolpointName"
header-align="center"
@ -228,7 +229,13 @@
<span>点位名称 <span style="color: red">*</span> </span>
</template>
<template slot-scope="scope">
<!-- 汇聚门户只读视图点位名称展示纯文本不允许修改 -->
<span
v-if="$store.getters.fromPortal"
class="point-name-text"
>{{ scope.row.patrolpointName || "-" }}</span>
<el-input
v-else
v-model="scope.row.patrolpointName"
@change="handleChangePointCode(scope.row)"
@input="handlePatrolpointNameInput($event, scope.row)"
@ -238,6 +245,17 @@
></el-input>
</template>
</el-table-column>
<!-- 站名列汇聚门户聚合数据下展示 -->
<el-table-column
v-if="$store.getters.fromPortal"
label="站名"
align="center"
header-align="center"
class-name="station-name-col"
prop="stationName"
min-width="100"
>
</el-table-column>
<el-table-column
prop="algSubtypeName"
@ -250,9 +268,9 @@
:class="[
'nowarp',
'algorithm-setting-link',
{ 'is-disabled': batchDeleteMode },
{ 'is-disabled': batchDeleteMode || $store.getters.fromPortal },
]"
@click="!batchDeleteMode && chooseAlgorithm(scope.row)"
@click="!batchDeleteMode && !$store.getters.fromPortal && chooseAlgorithm(scope.row)"
:title="scope.row.algSubtypeName"
>
{{
@ -261,8 +279,9 @@
</a>
</template>
</el-table-column>
<!-- 新增设备类型列 -->
<el-table-column
v-if="!$store.getters.fromPortal"
prop="pointTypeName"
header-align="center"
align="center"
@ -274,6 +293,7 @@
</template>
</el-table-column>
<el-table-column
v-if="!$store.getters.fromPortal"
align="center"
prop="patrolContent"
header-align="center"
@ -4211,6 +4231,12 @@ export default {
white-space: nowrap;
}
/* 站名列强制居中(覆盖可能的对齐/宽度冲突样式) */
::v-deep .el-table th.station-name-col .cell,
::v-deep .el-table td.station-name-col .cell {
text-align: center !important;
}
::v-deep .el-table__row td.operation-column .cell,
::v-deep .el-table__fixed-right td.operation-column .cell {
overflow: visible;


+ 9
- 0
src/views/cards/taskSummary/inspectionTask.vue View File

@ -147,6 +147,15 @@
</el-tooltip>
</template>
</el-table-column>
<!-- 站名列汇聚门户聚合数据下展示 -->
<el-table-column
v-if="$store.getters.fromPortal"
label="站名"
align="center"
prop="stationName"
min-width="100"
show-overflow-tooltip
/>
<el-table-column
label="执行时间"
align="center"


+ 3
- 1
src/views/cards/taskSummary/inspectionTaskDefectDetail.vue View File

@ -112,13 +112,15 @@ export default {
}
this.queryParams.taskPatrolledId = this.taskInfo.taskPatrolledId;
this.queryParams.filter = this.taskInfo.defectType;
// stationCode
this.queryParams.stationCode = this.taskInfo.stationCode;
this.getList();
},
beforeDestroy() {},
methods: {
//
getList() {
analysisDefectListShaoxing(this.queryParams).then((res) => {
analysisDefectListShaoxing(this.queryParams, this.queryParams.stationCode).then((res) => {
this.dataList = res.rows;
this.total = res.total;
});


+ 3
- 0
src/views/cards/taskSummary/newInspectionTaskDetail.vue View File

@ -550,6 +550,8 @@ export default {
this.queryParams.taskPatrolledId = this.taskInfo.taskPatrolledId;
this.queryParams.posType = this.taskInfo.posType;
this.queryParams.lineId = this.taskInfo.lineId;
// stationCode axios
this.queryParams.stationCode = this.taskInfo.stationCode;
this.getPointList();
this.initBaseInfo();
},
@ -742,6 +744,7 @@ export default {
taskPatrolledId: this.queryParams.taskPatrolledId,
posType: this.queryParams.posType,
lineId: this.queryParams.lineId,
stationCode: this.queryParams.stationCode,
};
getPatrolPointName(params).then((res) => {
this.searchSourceData.listPointName = res.data?.listPointName || [];


+ 8
- 2
src/views/portal/index.vue View File

@ -1586,9 +1586,10 @@ $panel-border: rgba(84, 130, 255, 0.22);
filter: brightness(1.2) drop-shadow(0 0 6px rgba(32, 218, 255, 0.65));
}
/* 树展开时:按钮贴到树面板左缘,显示收起箭头 */
/*
树宽被 min-width/max-width 钳制按钮用同样的 clamp 规则才能在任何屏幕下对齐树缘 */
&.open {
right: 38.3%;
right: clamp(280px, 46%, 360px);
}
}
@ -1757,6 +1758,11 @@ $panel-border: rgba(84, 130, 255, 0.22);
min-width: 260px;
}
/* 窄屏下树宽下限变为 260px,按钮同步对齐 */
.tree-toggle.open {
right: clamp(260px, 46%, 360px);
}
.video-monitor-body.tree-visible .camera-stat {
padding-right: 6px;
padding-left: 6px;


+ 76
- 9
src/views/video/LivePreview.vue View File

@ -7,6 +7,7 @@
:channels="channels"
:selected-code="selectedCode"
:loading="loading"
:use-agg-api="isAggMode"
@channels-loaded="handleTreeChannels"
@select="selectChannel"
@open="openChannel"
@ -171,6 +172,7 @@ import {
getNvrConfig, startLive, getLiveStatus, stopLive, controlPtz,
getPresetList, createPreset, createSnapshot, downloadSnapshot, controlWiper
} from '@/api/nvrVideo'
import { startRealtimeVideo, stopRealtimeVideo, aggPtzControl, aggPresetList, aggPresetCreate } from '@/api/portal'
const PTZ_CODES = {
UP: 2, DOWN: 3, LEFT: 4, UP_LEFT: 5, DOWN_LEFT: 6,
@ -256,6 +258,8 @@ export default {
}
},
computed: {
//
isAggMode() { return this.$store.getters.fromPortal },
capacity() { return this.layout },
visibleSlots() { return this.slots.slice(0, this.capacity) },
activeSlot() { return this.slots[this.activeIndex] || null },
@ -330,6 +334,13 @@ export default {
this.loading = true
this.connectionText = '读取配置'
try {
if (this.isAggMode) {
// NVR
this.config = {}
this.connectionText = '汇聚模式'
this.connectionState = 'online'
return
}
const config = await getNvrConfig()
this.config = config || {}
// Keep config as metadata only until the device tree has supplied the
@ -446,12 +457,20 @@ export default {
this.presets = []
this.presetId = null
try {
slot.startPromise = startLive(channel, streamType)
// hls/flvapplyLiveResult
slot.startPromise = this.isAggMode
? startRealtimeVideo({ stationCode: channel.stationCode, cameraCode: channel.cameraCode })
: startLive(channel, streamType)
const result = await slot.startPromise
if (this.slots[index] !== slot) return
this.applyLiveResult(index, slot, result)
if (!slot.streamSource.hlsUrl && !slot.streamSource.flvUrl) {
this.pollLive(index, slot, 0)
if (this.isAggMode) {
slot.loading = false
this.$message.error('视频流启动失败,请稍后重试')
} else {
this.pollLive(index, slot, 0)
}
return
}
if (index === this.activeIndex) this.loadPresets(true)
@ -507,7 +526,13 @@ export default {
if (slot.startPromise) {
try { await slot.startPromise } catch (_) { /* no stream session was created */ }
}
try { await stopLive(slot.cameraCode) } catch (_) { /* release the UI slot first */ }
try {
if (this.isAggMode && slot.stationCode) {
await stopRealtimeVideo({ stationCode: slot.stationCode, cameraCode: slot.cameraCode })
} else {
await stopLive(slot.cameraCode)
}
} catch (_) { /* release the UI slot first */ }
if (notify) this.$message.success('视频通道已关闭')
},
closeAll() {
@ -572,7 +597,13 @@ export default {
}
}
this.$set(this.slots, index, null)
try { await stopLive(failedSlot.cameraCode) } catch (_) { /* continue switching cameras */ }
try {
if (this.isAggMode && failedSlot.stationCode) {
await stopRealtimeVideo({ stationCode: failedSlot.stationCode, cameraCode: failedSlot.cameraCode })
} else {
await stopLive(failedSlot.cameraCode)
}
} catch (_) { /* continue switching cameras */ }
if (!this.pageActive) return
if (nextChannel) {
await this.startChannel(nextChannel, index, Array.from(attemptedCodes))
@ -611,10 +642,23 @@ export default {
this.$router.push({ name: 'VideoHistoryPlayback', query: { cameraCode: slot.cameraCode }})
},
async sendPtz(controlCode, controlPara1 = '', controlPara2 = '', cameraCode = '') {
const targetCode = cameraCode || (this.controlTarget && this.controlTarget.cameraCode)
const target = cameraCode
? this.slots.find(slot => slot && slot.cameraCode === cameraCode) || this.controlTarget
: this.controlTarget
const targetCode = (target && target.cameraCode) || cameraCode
if (!targetCode) { this.$message.warning('请先选择有视频的画面'); return }
const result = await controlPtz({ cameraCode: targetCode, controlCode, controlPara1, controlPara2 })
if (!result || Number(result.resultCode) !== 0) throw new Error('云台控制失败')
let result
if (this.isAggMode) {
// stationCode
const stationCode = target && target.stationCode
if (!stationCode) { this.$message.warning('未获取到该摄像头所属站点信息'); return }
result = await aggPtzControl({ stationCode, cameraCode: targetCode, controlCode, controlPara1, controlPara2 })
} else {
result = await controlPtz({ cameraCode: targetCode, controlCode, controlPara1, controlPara2 })
}
// dataresultCode 0
const body = result && Object.prototype.hasOwnProperty.call(result, 'data') && result.data ? result.data : result
if (body && body.resultCode !== undefined && Number(body.resultCode) !== 0) throw new Error('云台控制失败')
},
startPtz(action) {
if (action === 'STOP') {
@ -651,7 +695,17 @@ export default {
const cameraCode = this.controlTarget && this.controlTarget.cameraCode
if (!cameraCode) return
try {
const result = await getPresetList(cameraCode)
let result
if (this.isAggMode) {
// stationCode
const stationCode = this.controlTarget && this.controlTarget.stationCode
if (!stationCode) { this.$message.warning('未获取到该摄像头所属站点信息'); return }
result = await aggPresetList({ stationCode, cameraCode })
} else {
result = await getPresetList(cameraCode)
}
// data
result = result && Object.prototype.hasOwnProperty.call(result, 'data') && result.data ? result.data : result
if (!this.controlTarget || this.controlTarget.cameraCode !== cameraCode) return
const source = result && result.ptzPresetInfoList && result.ptzPresetInfoList.ptzPresetInfo
this.presets = (Array.isArray(source) ? source : []).map(item => ({ id: Number(item.presetIndex), name: item.presetName || `预置位 ${item.presetIndex}` })).filter(item => item.id > 0)
@ -687,7 +741,20 @@ export default {
}
try {
await this.$confirm(`确认保存预置位 ${this.newPresetId}${this.newPresetName ? ` · ${this.newPresetName}` : ''}`, '保存预置位')
await createPreset({ cameraCode: channel.cameraCode, nvrIp: channel.nvrIp, channel: Number(channel.channel), preset: this.newPresetId, presetName: this.newPresetName || null })
if (this.isAggMode) {
// stationCode
if (!channel.stationCode) { this.$message.warning('未获取到该摄像头所属站点信息'); return }
await aggPresetCreate({
stationCode: channel.stationCode,
cameraCode: channel.cameraCode,
nvrIp: channel.nvrIp,
channel: Number(channel.channel),
preset: this.newPresetId,
presetName: this.newPresetName || null
})
} else {
await createPreset({ cameraCode: channel.cameraCode, nvrIp: channel.nvrIp, channel: Number(channel.channel), preset: this.newPresetId, presetName: this.newPresetName || null })
}
await this.loadPresets(true)
this.$message.success('预置位保存成功')
} catch (error) { if (error !== 'cancel' && error !== 'close') this.$message.error(error.message) }


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

@ -56,6 +56,7 @@
<script>
import { getAreaEqubookTreeSelect } from '@/api/videosMonitor/index'
import { getChannelTree } from '@/api/portal'
function flatten(nodes, result = []) {
const source = nodes || []
@ -84,7 +85,9 @@ export default {
channels: { type: Array, default: () => [] },
selectedCode: { type: String, default: '' },
loading: { type: Boolean, default: false },
openHint: { type: String, default: '单击通道打开实时预览' }
openHint: { type: String, default: '单击通道打开实时预览' },
// /agg/tree/channel stationCode
useAggApi: { type: Boolean, default: false }
},
data() {
return {
@ -113,7 +116,10 @@ export default {
async loadTree() {
this.internalLoading = true
try {
const response = await getAreaEqubookTreeSelect({ patroldeviceName: '', onlineStatus: '', patroldeviceTypeFlag: 'ipc' })
const queryParams = { patroldeviceName: '', onlineStatus: '', patroldeviceTypeFlag: 'ipc' }
const response = this.useAggApi
? await getChannelTree(queryParams)
: await getAreaEqubookTreeSelect(queryParams)
this.treeNodes = Array.isArray(response.data) ? response.data : []
this.expandedKeys = []
const walk = nodes => (nodes || []).forEach(node => { if (node.children && node.children.length) { this.expandedKeys.push(node.id); walk(node.children) } })


Loading…
Cancel
Save