xsh_1997 vor 1 Monat
Ursprung
Commit
2d06a67ed7

+ 1 - 1
ruoyi-ui-app/locale/bo.js

@@ -1,7 +1,7 @@
1 1
 /** 藏文文案(键与 zh.js 一致) */
2 2
 export default {
3 3
   app: {
4
-    name: 'རུའོ་ཡི་ APP'
4
+    name: 'ལས་ཁུངས་གྲངས་ཀ་ཞབས་ཞུའི་འབྲེལ་ལམ།'
5 5
   },
6 6
   nav: {
7 7
     home: 'གཙོ་ངོས།',

+ 1 - 1
ruoyi-ui-app/locale/zh.js

@@ -1,7 +1,7 @@
1 1
 /** 中文文案(键与 bo.js 保持一致) */
2 2
 export default {
3 3
   app: {
4
-    name: '巴青牧业大脑'
4
+    name: '行业数字服务链'
5 5
   },
6 6
   nav: {
7 7
     home: '首页',

+ 1 - 1
ruoyi-ui-app/locale/zh.json

@@ -1,6 +1,6 @@
1 1
 {
2 2
   "app": {
3
-    "name": "巴青移动端"
3
+    "name": "行业数字服务链"
4 4
   },
5 5
   "nav": {
6 6
     "home": "首页",

+ 17 - 0
ruoyi-ui/src/api/videoSurveillance/livestockMonitor.js

@@ -51,3 +51,20 @@ export function listLivestockMonitorOwnerOptions(query) {
51 51
     params: query
52 52
   })
53 53
 }
54
+
55
+/** 第三方同步(仅列表 upsert) */
56
+export function syncLivestockMonitor() {
57
+  return request({
58
+    url: "/videoSurveillance/livestockMonitor/sync",
59
+    method: "post"
60
+  })
61
+}
62
+
63
+/** 实时播放地址(不落库) */
64
+export function getLivestockMonitorPlayUrl(monitorId) {
65
+  return request({
66
+    url: "/videoSurveillance/livestockMonitor/playUrl",
67
+    method: "get",
68
+    params: { monitorId }
69
+  })
70
+}

+ 27 - 14
ruoyi-ui/src/lang/bo/videoSurveillance.js

@@ -17,12 +17,16 @@ export default {
17 17
     colCreate: "བཟོ་དུས།",
18 18
     msgDelOk: "བསུབ་ལེགས།",
19 19
     msgModOk: "རྩོམ་ལེགས།",
20
-    msgAddOk: "གསར་ལེགས།"
20
+    msgAddOk: "གསར་ལེགས།",
21
+    syncLoading: "གཞི་གྲངས་མཉམ་སྒྲིག་བཞིན... སྒུག་རོགས།"
21 22
   },
22 23
   status: {
23 24
     dash: "—",
24 25
     ownerType1: "འཕྲོད་ལས་གཙོ་བོ།",
25
-    ownerType2: "ཚོང་ལས་ཁང་།"
26
+    ownerType2: "ཚོང་ལས་ཁང་།",
27
+    videoSourceWvp: "WVP",
28
+    videoSourceYs7: "萤石云",
29
+    videoSourceUrl: "直链 URL"
26 30
   },
27 31
   livestockMonitor: {
28 32
     queryMonitorName: "བརྙན་ཟློས་མིང་།",
@@ -34,33 +38,42 @@ export default {
34 38
     colInstallLocation: "སྒྲིག་གནས།",
35 39
     actionLiveView: "ད་ལྟ་བརྙན་ལྟ",
36 40
     dialogLiveView: "ད་ལྟ་བརྙན",
41
+    btnSync: "མཉམ་སྒྲིག",
42
+    syncSummary: "མཉམ་སྒྲིག་ཟིན:གསར་{insert} རྩོམ་{update} ཕམ་{fail}",
43
+    syncTimeHint: "མཉམ་སྒྲིག་དུས:{time}",
44
+    formMonitorId: "摄像头 ID",
37 45
     formMonitorName: "བརྙན་ཟློས་མིང་།",
38 46
     formOwner: "གཙོ་བོ།",
47
+    formEnclosureName: "圈舍",
39 48
     formInstallLocation: "སྒྲིག་གནས།",
40
-    formIpAddress: "IP",
49
+    formVideoSource: "视频来源",
50
+    phMonitorId: "摄像头 ID ≤32",
41 51
     phMonitorName: "མིང 1~10",
42
-    phOwner: "གཙོ་བོ་འདེམ",
43
-    phInstallLocation: "སྒྲིག་གནས 1~10",
44
-    phIpAddress: "192.168.1.100",
52
+    phOwner: "可选",
53
+    phEnclosureName: "圈舍 ≤64",
54
+    phInstallLocation: "སྒྲིག་གནས ≤10",
55
+    phVideoSource: "视频来源",
45 56
     dialogAdd: "[གསར]བརྙན་ཟློས",
46 57
     dialogEdit: "[རྩོམ]བརྙན་ཟློས",
47 58
     confirmDelete: "ཐོ་གཞུང་འདི་སུབ་རྒྱུ་གཏན་ཁེལ་ལམ?",
48
-    msgLiveNotOpen: "ད་ལྟ་བརྙན་མ་ཁ་ཕྱེ། སྒྲིག་གནས་སྔོན་དུ་གྲུབ",
49
-    msgLiveNoStream: "བརྙན་ཟློས་མེད(接口对接待)",
59
+    msgLiveNoMonitorId: "摄像头 ID མེད",
60
+    msgLiveNoStream: "བརྙན་ཟློས་མེད",
61
+    viewMonitorId: "摄像头 ID",
50 62
     viewMonitorName: "བརྙན་ཟློས་མིང་།",
51 63
     viewOwner: "གཙོ་བོ།",
64
+    viewEnclosureName: "圈舍",
52 65
     viewInstallLocation: "སྒྲིག་གནས།",
53
-    viewIpAddress: "IP",
66
+    viewVideoSource: "视频来源",
54 67
     viewCreateBy: "བཟོ་མཁན།",
55 68
     viewUpdateBy: "རྩོམ་མཁན།",
56 69
     viewUpdateTime: "རྩོམ་དུས།",
70
+    ruleMonitorIdRequired: "摄像头 ID འབྲི",
71
+    ruleMonitorIdLen: "ID 32 ལས་མི་འགྲོ",
57 72
     ruleMonitorNameRequired: "མིང་འབྲི",
58 73
     ruleMonitorNameLen: "1~10 ཡིག",
59
-    ruleOwnerRequired: "གཙོ་བོ་འདེམ",
60
-    ruleInstallLocationRequired: "སྒྲིག་གནས་འབྲི",
74
+    ruleOwnerIncomplete: "གཙོ་བོ་ཆ་ཚང་མིན",
75
+    ruleEnclosureNameLen: "圈舍 64 ལས་མི་འགྲོ",
61 76
     ruleInstallLocationLen: "1~10 ཡིག",
62
-    ruleIpRequired: "IP་འབྲི",
63
-    ruleIpFormat: "IP་ནོར",
64
-    ruleIpLen: "IP 20 ལས་མི་འགྲོ"
77
+    ruleVideoSourceRequired: "视频来源 འདེམ"
65 78
   }
66 79
 }

+ 28 - 15
ruoyi-ui/src/lang/zh/videoSurveillance.js

@@ -17,12 +17,16 @@ export default {
17 17
     colCreate: "创建时间",
18 18
     msgDelOk: "删除成功",
19 19
     msgModOk: "修改成功",
20
-    msgAddOk: "新增成功"
20
+    msgAddOk: "新增成功",
21
+    syncLoading: "正在同步数据,请稍候..."
21 22
   },
22 23
   status: {
23 24
     dash: "—",
24 25
     ownerType1: "养殖主体",
25
-    ownerType2: "交易市场"
26
+    ownerType2: "交易市场",
27
+    videoSourceWvp: "WVP 平台",
28
+    videoSourceYs7: "萤石云",
29
+    videoSourceUrl: "直链 URL"
26 30
   },
27 31
   livestockMonitor: {
28 32
     queryMonitorName: "监控名称",
@@ -34,33 +38,42 @@ export default {
34 38
     colInstallLocation: "安装位置",
35 39
     actionLiveView: "查看实时监控",
36 40
     dialogLiveView: "实时监控",
41
+    btnSync: "同步",
42
+    syncSummary: "同步完成:新增 {insert} 条,更新 {update} 条,失败 {fail} 条",
43
+    syncTimeHint: "最近同步时间:{time}",
44
+    formMonitorId: "摄像头 ID",
37 45
     formMonitorName: "监控名称",
38 46
     formOwner: "所属主体",
47
+    formEnclosureName: "圈舍",
39 48
     formInstallLocation: "安装位置",
40
-    formIpAddress: "IP 地址",
49
+    formVideoSource: "视频来源",
50
+    phMonitorId: "请输入摄像头 ID(≤32 字符)",
41 51
     phMonitorName: "请输入监控名称(1~10字)",
42
-    phOwner: "输入关键字检索并选择主体",
43
-    phInstallLocation: "请输入安装位置(1~10字)",
44
-    phIpAddress: "如 192.168.1.100",
52
+    phOwner: "可选,输入关键字检索并选择主体",
53
+    phEnclosureName: "请输入圈舍名称(可选,≤64字)",
54
+    phInstallLocation: "请输入安装位置(可选,≤10字)",
55
+    phVideoSource: "请选择视频来源",
45 56
     dialogAdd: "[新增]监控点位",
46 57
     dialogEdit: "[编辑]监控点位",
47 58
     confirmDelete: "是否确认删除这条数据?",
48
-    msgLiveNotOpen: "实时监控功能暂未开放,请先完成监控点位登记",
49
-    msgLiveNoStream: "暂无直播流(监控流地址待接口对接)",
59
+    msgLiveNoMonitorId: "该监控点位缺少摄像头 ID,无法获取播放地址",
60
+    msgLiveNoStream: "暂无直播流",
61
+    viewMonitorId: "摄像头 ID",
50 62
     viewMonitorName: "监控名称",
51 63
     viewOwner: "所属主体",
64
+    viewEnclosureName: "圈舍",
52 65
     viewInstallLocation: "安装位置",
53
-    viewIpAddress: "IP 地址",
66
+    viewVideoSource: "视频来源",
54 67
     viewCreateBy: "创建人",
55 68
     viewUpdateBy: "修改人",
56 69
     viewUpdateTime: "修改时间",
70
+    ruleMonitorIdRequired: "请填写摄像头 ID",
71
+    ruleMonitorIdLen: "摄像头 ID 不能超过 32 个字符",
57 72
     ruleMonitorNameRequired: "请填写监控名称",
58 73
     ruleMonitorNameLen: "监控名称长度为 1~10 个字符",
59
-    ruleOwnerRequired: "请选择所属主体",
60
-    ruleInstallLocationRequired: "请填写安装位置",
61
-    ruleInstallLocationLen: "安装位置长度为 1~10 个字符",
62
-    ruleIpRequired: "请填写 IP 地址",
63
-    ruleIpFormat: "请输入正确的 IP 地址",
64
-    ruleIpLen: "IP 地址不能超过 20 个字符"
74
+    ruleOwnerIncomplete: "所属主体信息不完整,请重新选择或清空",
75
+    ruleEnclosureNameLen: "圈舍名称不能超过 64 个字符",
76
+    ruleInstallLocationLen: "安装位置长度不能超过 10 个字符",
77
+    ruleVideoSourceRequired: "请选择视频来源"
65 78
   }
66 79
 }

+ 16 - 0
ruoyi-ui/src/mixins/videoSurveillanceLocaleMixin.js

@@ -21,6 +21,22 @@ export default {
21 21
         return "warning"
22 22
       }
23 23
       return "info"
24
+    },
25
+    /** 第三方数据同步,带全局 loading */
26
+    runSyncWithLoading(syncFn) {
27
+      if (this.syncing) {
28
+        return Promise.reject(new Error("syncing"))
29
+      }
30
+      let modalLoading = false
31
+      this.syncing = true
32
+      this.$modal.loading(this.vsCommon("syncLoading"))
33
+      modalLoading = true
34
+      return syncFn().finally(() => {
35
+        this.syncing = false
36
+        if (modalLoading) {
37
+          this.$modal.closeLoading()
38
+        }
39
+      })
24 40
     }
25 41
   }
26 42
 }

+ 189 - 59
ruoyi-ui/src/views/videoSurveillance/livestockMonitor/index.vue

@@ -37,6 +37,16 @@
37 37
         <el-form-item>
38 38
           <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">{{ vsCommon("search") }}</el-button>
39 39
           <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">{{ vsCommon("reset") }}</el-button>
40
+          <el-button
41
+            v-hasPermi="['videoSurveillance:livestockMonitor:sync']"
42
+            type="warning"
43
+            plain
44
+            icon="el-icon-refresh"
45
+            size="mini"
46
+            :loading="syncing"
47
+            @click="handleSync"
48
+          >{{ vsT("btnSync") }}</el-button>
49
+          <!-- 新增(暂隐藏)
40 50
           <el-button
41 51
             v-hasPermi="['videoSurveillance:livestockMonitor:add']"
42 52
             type="primary"
@@ -45,8 +55,10 @@
45 55
             size="mini"
46 56
             @click="handleAdd"
47 57
           >{{ vsCommon("add") }}</el-button>
58
+          -->
48 59
         </el-form-item>
49 60
       </el-form>
61
+      <div v-if="lastSyncHint" class="livestock-monitor__sync-hint">{{ lastSyncHint }}</div>
50 62
     </el-card>
51 63
 
52 64
     <br />
@@ -63,15 +75,21 @@
63 75
           </template>
64 76
         </el-table-column>
65 77
         <el-table-column :label="vsT('colInstallLocation')" prop="installLocation" align="center" min-width="120" :show-overflow-tooltip="true" />
66
-        <el-table-column :label="vsCommon('colOp')" align="center" class-name="small-padding fixed-width" width="300" fixed="right">
78
+        <el-table-column :label="vsCommon('colOp')" align="center" class-name="small-padding fixed-width" width="180" fixed="right">
67 79
           <template slot-scope="scope">
68
-            <el-button type="text" size="mini" @click="handleLiveView(scope.row)">{{ vsT("actionLiveView") }}</el-button>
80
+            <el-button
81
+              v-hasPermi="['videoSurveillance:livestockMonitor:query']"
82
+              type="text"
83
+              size="mini"
84
+              @click="handleLiveView(scope.row)"
85
+            >{{ vsT("actionLiveView") }}</el-button>
69 86
             <el-button
70 87
               v-hasPermi="['videoSurveillance:livestockMonitor:query']"
71 88
               type="text"
72 89
               size="mini"
73 90
               @click="handleView(scope.row)"
74 91
             >{{ vsCommon("view") }}</el-button>
92
+            <!-- 编辑、删除(暂隐藏)
75 93
             <el-button
76 94
               v-hasPermi="['videoSurveillance:livestockMonitor:edit']"
77 95
               type="text"
@@ -84,6 +102,7 @@
84 102
               size="mini"
85 103
               @click="handleDelete(scope.row)"
86 104
             >{{ vsCommon("delete") }}</el-button>
105
+            -->
87 106
           </template>
88 107
         </el-table-column>
89 108
       </el-table>
@@ -96,8 +115,13 @@
96 115
       />
97 116
     </el-card>
98 117
 
99
-    <el-dialog :title="dialogTitle" :visible.sync="open" width="560px" append-to-body @close="cancel">
118
+    <!-- 新增/编辑弹窗(暂隐藏)
119
+    <el-dialog :title="dialogTitle" :visible.sync="open" width="620px" append-to-body @close="cancel">
100 120
       <el-form :key="formKey" ref="form" :model="form" label-width="110px">
121
+        <el-form-item prop="monitorId" :rules="formRules.monitorId">
122
+          <template slot="label">{{ vsT("formMonitorId") }}</template>
123
+          <el-input v-model="form.monitorId" :placeholder="vsT('phMonitorId')" maxlength="32" show-word-limit />
124
+        </el-form-item>
101 125
         <el-form-item prop="monitorName" :rules="formRules.monitorName">
102 126
           <template slot="label">{{ vsT("formMonitorName") }}</template>
103 127
           <el-input v-model="form.monitorName" :placeholder="vsT('phMonitorName')" maxlength="10" show-word-limit />
@@ -109,6 +133,7 @@
109 133
             filterable
110 134
             remote
111 135
             reserve-keyword
136
+            clearable
112 137
             :placeholder="vsT('phOwner')"
113 138
             :remote-method="searchOwner"
114 139
             :loading="ownerLoading"
@@ -123,13 +148,24 @@
123 148
             />
124 149
           </el-select>
125 150
         </el-form-item>
151
+        <el-form-item prop="enclosureName" :rules="formRules.enclosureName">
152
+          <template slot="label">{{ vsT("formEnclosureName") }}</template>
153
+          <el-input v-model="form.enclosureName" :placeholder="vsT('phEnclosureName')" maxlength="64" show-word-limit />
154
+        </el-form-item>
126 155
         <el-form-item prop="installLocation" :rules="formRules.installLocation">
127 156
           <template slot="label">{{ vsT("formInstallLocation") }}</template>
128 157
           <el-input v-model="form.installLocation" :placeholder="vsT('phInstallLocation')" maxlength="10" show-word-limit />
129 158
         </el-form-item>
130
-        <el-form-item prop="ipAddress" :rules="formRules.ipAddress">
131
-          <template slot="label">{{ vsT("formIpAddress") }}</template>
132
-          <el-input v-model="form.ipAddress" :placeholder="vsT('phIpAddress')" maxlength="20" />
159
+        <el-form-item prop="videoSource" :rules="formRules.videoSource">
160
+          <template slot="label">{{ vsT("formVideoSource") }}</template>
161
+          <el-select v-model="form.videoSource" :placeholder="vsT('phVideoSource')" style="width: 100%">
162
+            <el-option
163
+              v-for="item in videoSourceOptions"
164
+              :key="item.value"
165
+              :label="item.label"
166
+              :value="item.value"
167
+            />
168
+          </el-select>
133 169
         </el-form-item>
134 170
       </el-form>
135 171
       <div slot="footer" class="dialog-footer">
@@ -137,9 +173,13 @@
137 173
         <el-button @click="cancel">{{ vsCommon("cancel") }}</el-button>
138 174
       </div>
139 175
     </el-dialog>
176
+    -->
140 177
 
141
-    <el-dialog :title="vsCommon('viewTitle')" :visible.sync="viewOpen" width="560px" append-to-body>
178
+    <el-dialog :title="vsCommon('viewTitle')" :visible.sync="viewOpen" width="620px" append-to-body>
142 179
       <el-form label-width="110px" size="small">
180
+        <el-form-item :label="vsT('viewMonitorId')">
181
+          <span>{{ viewRow.monitorId || dash }}</span>
182
+        </el-form-item>
143 183
         <el-form-item :label="vsT('viewMonitorName')">
144 184
           <span>{{ viewRow.monitorName || dash }}</span>
145 185
         </el-form-item>
@@ -149,11 +189,14 @@
149 189
           </el-tag>
150 190
           <span>{{ viewRow.ownerName || dash }}</span>
151 191
         </el-form-item>
192
+        <el-form-item :label="vsT('viewEnclosureName')">
193
+          <span>{{ viewRow.enclosureName || dash }}</span>
194
+        </el-form-item>
152 195
         <el-form-item :label="vsT('viewInstallLocation')">
153 196
           <span>{{ viewRow.installLocation || dash }}</span>
154 197
         </el-form-item>
155
-        <el-form-item :label="vsT('viewIpAddress')">
156
-          <span>{{ viewRow.ipAddress || dash }}</span>
198
+        <el-form-item :label="vsT('viewVideoSource')">
199
+          <span>{{ viewRow.videoSourceName || videoSourceLabel(viewRow.videoSource) }}</span>
157 200
         </el-form-item>
158 201
         <el-form-item :label="vsCommon('colCreate')">
159 202
           <span>{{ viewRow.createTime ? parseTime(viewRow.createTime) : dash }}</span>
@@ -169,11 +212,13 @@
169 212
         </el-form-item>
170 213
       </el-form>
171 214
       <div slot="footer" class="dialog-footer">
215
+        <!-- 编辑(暂隐藏)
172 216
         <el-button
173 217
           v-hasPermi="['videoSurveillance:livestockMonitor:edit']"
174 218
           type="primary"
175 219
           @click="handleEditFromView"
176 220
         >{{ vsCommon("edit") }}</el-button>
221
+        -->
177 222
         <el-button @click="viewOpen = false">{{ vsCommon("close") }}</el-button>
178 223
       </div>
179 224
     </el-dialog>
@@ -186,7 +231,7 @@
186 231
       destroy-on-close
187 232
       @close="closeLiveView"
188 233
     >
189
-      <div class="live-player-wrap">
234
+      <div v-loading="liveLoading" class="live-player-wrap">
190 235
         <LivePlayer
191 236
           :video-url="liveVideoUrl"
192 237
           :video-title="liveVideoTitle"
@@ -216,18 +261,22 @@ import {
216 261
   addLivestockMonitor,
217 262
   updateLivestockMonitor,
218 263
   delLivestockMonitor,
219
-  listLivestockMonitorOwnerOptions
264
+  listLivestockMonitorOwnerOptions,
265
+  syncLivestockMonitor,
266
+  getLivestockMonitorPlayUrl
220 267
 } from "@/api/videoSurveillance/livestockMonitor"
221 268
 
222
-const IPV4_REG = /^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$/
269
+const VIDEO_SOURCE_VALUES = ["wvp", "ys7", "url"]
223 270
 
224 271
 function createEmptyForm() {
225 272
   return {
226 273
     id: undefined,
274
+    monitorId: undefined,
227 275
     monitorName: undefined,
228 276
     ownerSelectKey: undefined,
277
+    enclosureName: undefined,
229 278
     installLocation: undefined,
230
-    ipAddress: undefined
279
+    videoSource: undefined
231 280
   }
232 281
 }
233 282
 
@@ -241,6 +290,8 @@ export default {
241 290
     return {
242 291
       vsNs: "livestockMonitor",
243 292
       loading: false,
293
+      syncing: false,
294
+      liveLoading: false,
244 295
       open: false,
245 296
       viewOpen: false,
246 297
       liveOpen: false,
@@ -256,6 +307,7 @@ export default {
256 307
       ownerLoading: false,
257 308
       ownerSearchTimer: null,
258 309
       queryOwnerKey: undefined,
310
+      lastSyncHint: "",
259 311
       queryParams: {
260 312
         pageNum: 1,
261 313
         pageSize: 20,
@@ -277,23 +329,26 @@ export default {
277 329
       const name = this.liveRow.monitorName || this.liveVideoTitle
278 330
       return name ? `${this.vsT("dialogLiveView")} - ${name}` : this.vsT("dialogLiveView")
279 331
     },
332
+    videoSourceOptions() {
333
+      return VIDEO_SOURCE_VALUES.map((value) => ({
334
+        value,
335
+        label: this.videoSourceLabel(value)
336
+      }))
337
+    },
280 338
     formRules() {
281 339
       return {
340
+        monitorId: [
341
+          { required: true, message: this.vsT("ruleMonitorIdRequired"), trigger: "blur" },
342
+          { validator: this.validateMonitorId, trigger: "blur" }
343
+        ],
282 344
         monitorName: [
283 345
           { required: true, message: this.vsT("ruleMonitorNameRequired"), trigger: "blur" },
284 346
           { validator: this.validateMonitorName, trigger: "blur" }
285 347
         ],
286
-        ownerSelectKey: [
287
-          { required: true, message: this.vsT("ruleOwnerRequired"), trigger: "change" }
288
-        ],
289
-        installLocation: [
290
-          { required: true, message: this.vsT("ruleInstallLocationRequired"), trigger: "blur" },
291
-          { validator: this.validateInstallLocation, trigger: "blur" }
292
-        ],
293
-        ipAddress: [
294
-          { required: true, message: this.vsT("ruleIpRequired"), trigger: "blur" },
295
-          { validator: this.validateIpAddress, trigger: "blur" }
296
-        ]
348
+        ownerSelectKey: [{ validator: this.validateOwnerSelect, trigger: "change" }],
349
+        enclosureName: [{ validator: this.validateEnclosureName, trigger: "blur" }],
350
+        installLocation: [{ validator: this.validateInstallLocation, trigger: "blur" }],
351
+        videoSource: [{ required: true, message: this.vsT("ruleVideoSourceRequired"), trigger: "change" }]
297 352
       }
298 353
     }
299 354
   },
@@ -306,6 +361,18 @@ export default {
306 361
     }
307 362
   },
308 363
   methods: {
364
+    videoSourceLabel(value) {
365
+      if (value === "wvp") {
366
+        return this.$t("videoSurveillance.status.videoSourceWvp")
367
+      }
368
+      if (value === "ys7") {
369
+        return this.$t("videoSurveillance.status.videoSourceYs7")
370
+      }
371
+      if (value === "url") {
372
+        return this.$t("videoSurveillance.status.videoSourceUrl")
373
+      }
374
+      return value || this.dash
375
+    },
309 376
     ownerTypeName(ownerType) {
310 377
       if (ownerType === 1) {
311 378
         return this.$t("videoSurveillance.status.ownerType1")
@@ -369,42 +436,59 @@ export default {
369 436
           })
370 437
       }, 300)
371 438
     },
372
-    validateMonitorName(rule, value, callback) {
439
+    validateMonitorId(rule, value, callback) {
373 440
       const text = value != null ? String(value).trim() : ""
374 441
       if (!text) {
375
-        callback(new Error(this.vsT("ruleMonitorNameRequired")))
442
+        callback(new Error(this.vsT("ruleMonitorIdRequired")))
376 443
         return
377 444
       }
378
-      if (text.length > 10) {
379
-        callback(new Error(this.vsT("ruleMonitorNameLen")))
445
+      if (text.length > 32) {
446
+        callback(new Error(this.vsT("ruleMonitorIdLen")))
380 447
         return
381 448
       }
382 449
       callback()
383 450
     },
384
-    validateInstallLocation(rule, value, callback) {
451
+    validateMonitorName(rule, value, callback) {
385 452
       const text = value != null ? String(value).trim() : ""
386 453
       if (!text) {
387
-        callback(new Error(this.vsT("ruleInstallLocationRequired")))
454
+        callback(new Error(this.vsT("ruleMonitorNameRequired")))
388 455
         return
389 456
       }
390 457
       if (text.length > 10) {
391
-        callback(new Error(this.vsT("ruleInstallLocationLen")))
458
+        callback(new Error(this.vsT("ruleMonitorNameLen")))
392 459
         return
393 460
       }
394 461
       callback()
395 462
     },
396
-    validateIpAddress(rule, value, callback) {
397
-      const text = value != null ? String(value).trim() : ""
398
-      if (!text) {
399
-        callback(new Error(this.vsT("ruleIpRequired")))
463
+    validateOwnerSelect(rule, value, callback) {
464
+      if (!value) {
465
+        callback()
400 466
         return
401 467
       }
402
-      if (text.length > 20) {
403
-        callback(new Error(this.vsT("ruleIpLen")))
468
+      const parsed = this.parseOwnerKey(value)
469
+      if (
470
+        parsed.ownerType == null ||
471
+        isNaN(parsed.ownerType) ||
472
+        parsed.ownerId == null ||
473
+        isNaN(parsed.ownerId)
474
+      ) {
475
+        callback(new Error(this.vsT("ruleOwnerIncomplete")))
404 476
         return
405 477
       }
406
-      if (!IPV4_REG.test(text)) {
407
-        callback(new Error(this.vsT("ruleIpFormat")))
478
+      callback()
479
+    },
480
+    validateEnclosureName(rule, value, callback) {
481
+      const text = value != null ? String(value).trim() : ""
482
+      if (text.length > 64) {
483
+        callback(new Error(this.vsT("ruleEnclosureNameLen")))
484
+        return
485
+      }
486
+      callback()
487
+    },
488
+    validateInstallLocation(rule, value, callback) {
489
+      const text = value != null ? String(value).trim() : ""
490
+      if (text.length > 10) {
491
+        callback(new Error(this.vsT("ruleInstallLocationLen")))
408 492
         return
409 493
       }
410 494
       callback()
@@ -427,13 +511,24 @@ export default {
427 511
     },
428 512
     buildPayload() {
429 513
       const parsed = this.parseOwnerKey(this.form.ownerSelectKey)
430
-      return {
514
+      const payload = {
515
+        monitorId: String(this.form.monitorId).trim(),
431 516
         monitorName: String(this.form.monitorName).trim(),
432
-        ownerType: parsed.ownerType,
433
-        ownerId: parsed.ownerId,
434
-        installLocation: String(this.form.installLocation).trim(),
435
-        ipAddress: String(this.form.ipAddress).trim()
517
+        videoSource: this.form.videoSource
518
+      }
519
+      if (parsed.ownerType != null && !isNaN(parsed.ownerType) && parsed.ownerId != null && !isNaN(parsed.ownerId)) {
520
+        payload.ownerType = parsed.ownerType
521
+        payload.ownerId = parsed.ownerId
522
+      }
523
+      const enclosureName = this.form.enclosureName != null ? String(this.form.enclosureName).trim() : ""
524
+      if (enclosureName) {
525
+        payload.enclosureName = enclosureName
436 526
       }
527
+      const installLocation = this.form.installLocation != null ? String(this.form.installLocation).trim() : ""
528
+      if (installLocation) {
529
+        payload.installLocation = installLocation
530
+      }
531
+      return payload
437 532
     },
438 533
     resetFormModel() {
439 534
       this.form = createEmptyForm()
@@ -444,10 +539,12 @@ export default {
444 539
       this.ensureOwnerOptionInList(data.ownerType, data.ownerId, data.ownerName, data.ownerTypeName)
445 540
       this.form = Object.assign(createEmptyForm(), {
446 541
         id: data.id,
542
+        monitorId: data.monitorId,
447 543
         monitorName: data.monitorName,
448 544
         ownerSelectKey,
545
+        enclosureName: data.enclosureName,
449 546
         installLocation: data.installLocation,
450
-        ipAddress: data.ipAddress
547
+        videoSource: data.videoSource
451 548
       })
452 549
     },
453 550
     getList() {
@@ -511,29 +608,56 @@ export default {
511 608
       })
512 609
     },
513 610
     handleLiveView(row) {
514
-      if (!row || row.id == null) {
611
+      if (!row || !row.monitorId) {
612
+        this.$modal.msgWarning(this.vsT("msgLiveNoMonitorId"))
515 613
         return
516 614
       }
517
-      getLivestockMonitor(row.id).then((res) => {
518
-        const data = res.data || row
519
-        this.liveRow = data
520
-        this.liveVideoTitle = data.monitorName || ""
521
-        this.liveVideoUrl = this.resolveLiveVideoUrl(data)
522
-        this.liveOpen = true
523
-      })
615
+      this.liveRow = row
616
+      this.liveVideoTitle = row.monitorName || ""
617
+      this.liveVideoUrl = ""
618
+      this.liveOpen = true
619
+      this.liveLoading = true
620
+      getLivestockMonitorPlayUrl(row.monitorId)
621
+        .then((res) => {
622
+          const data = res.data || {}
623
+          this.liveVideoUrl = data.videoUrl ? String(data.videoUrl).trim() : ""
624
+          if (data.monitorName) {
625
+            this.liveVideoTitle = data.monitorName
626
+            this.liveRow = Object.assign({}, row, { monitorName: data.monitorName })
627
+          }
628
+        })
629
+        .catch(() => {})
630
+        .finally(() => {
631
+          this.liveLoading = false
632
+        })
524 633
     },
525 634
     closeLiveView() {
526 635
       this.liveVideoUrl = ""
527 636
       this.liveVideoTitle = ""
528 637
       this.liveRow = {}
529 638
       this.liveOpen = false
639
+      this.liveLoading = false
530 640
     },
531
-    resolveLiveVideoUrl(row) {
532
-      if (!row) {
533
-        return ""
534
-      }
535
-      const url = row.liveStreamUrl || row.streamUrl || row.videoUrl
536
-      return url ? String(url).trim() : ""
641
+    handleSync() {
642
+      this.runSyncWithLoading(() => syncLivestockMonitor())
643
+        .then((res) => {
644
+          const data = res.data || {}
645
+          this.$modal.msgSuccess(
646
+            this.vsT("syncSummary", {
647
+              insert: data.insertCount != null ? data.insertCount : 0,
648
+              update: data.updateCount != null ? data.updateCount : 0,
649
+              fail: data.failCount != null ? data.failCount : 0
650
+            })
651
+          )
652
+          if (data.failCount > 0 && data.failMessages && data.failMessages.length) {
653
+            this.$modal.msgWarning(data.failMessages.join(";"))
654
+          }
655
+          if (data.syncTime) {
656
+            this.lastSyncHint = this.vsT("syncTimeHint", { time: this.parseTime(data.syncTime) })
657
+          }
658
+          this.getList()
659
+        })
660
+        .catch(() => {})
537 661
     },
538 662
     handleDelete(row) {
539 663
       this.$modal
@@ -582,6 +706,12 @@ export default {
582 706
   vertical-align: middle;
583 707
 }
584 708
 
709
+.livestock-monitor__sync-hint {
710
+  margin-top: 8px;
711
+  font-size: 12px;
712
+  color: #909399;
713
+}
714
+
585 715
 .live-player-wrap {
586 716
   position: relative;
587 717
   width: 100%;