xsh_1997 hai 1 mes
pai
achega
2d06a67ed7

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

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

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

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

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

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

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

@@ -51,3 +51,20 @@ export function listLivestockMonitorOwnerOptions(query) {
51
     params: query
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
     colCreate: "བཟོ་དུས།",
17
     colCreate: "བཟོ་དུས།",
18
     msgDelOk: "བསུབ་ལེགས།",
18
     msgDelOk: "བསུབ་ལེགས།",
19
     msgModOk: "རྩོམ་ལེགས།",
19
     msgModOk: "རྩོམ་ལེགས།",
20
-    msgAddOk: "གསར་ལེགས།"
20
+    msgAddOk: "གསར་ལེགས།",
21
+    syncLoading: "གཞི་གྲངས་མཉམ་སྒྲིག་བཞིན... སྒུག་རོགས།"
21
   },
22
   },
22
   status: {
23
   status: {
23
     dash: "—",
24
     dash: "—",
24
     ownerType1: "འཕྲོད་ལས་གཙོ་བོ།",
25
     ownerType1: "འཕྲོད་ལས་གཙོ་བོ།",
25
-    ownerType2: "ཚོང་ལས་ཁང་།"
26
+    ownerType2: "ཚོང་ལས་ཁང་།",
27
+    videoSourceWvp: "WVP",
28
+    videoSourceYs7: "萤石云",
29
+    videoSourceUrl: "直链 URL"
26
   },
30
   },
27
   livestockMonitor: {
31
   livestockMonitor: {
28
     queryMonitorName: "བརྙན་ཟློས་མིང་།",
32
     queryMonitorName: "བརྙན་ཟློས་མིང་།",
@@ -34,33 +38,42 @@ export default {
34
     colInstallLocation: "སྒྲིག་གནས།",
38
     colInstallLocation: "སྒྲིག་གནས།",
35
     actionLiveView: "ད་ལྟ་བརྙན་ལྟ",
39
     actionLiveView: "ད་ལྟ་བརྙན་ལྟ",
36
     dialogLiveView: "ད་ལྟ་བརྙན",
40
     dialogLiveView: "ད་ལྟ་བརྙན",
41
+    btnSync: "མཉམ་སྒྲིག",
42
+    syncSummary: "མཉམ་སྒྲིག་ཟིན:གསར་{insert} རྩོམ་{update} ཕམ་{fail}",
43
+    syncTimeHint: "མཉམ་སྒྲིག་དུས:{time}",
44
+    formMonitorId: "摄像头 ID",
37
     formMonitorName: "བརྙན་ཟློས་མིང་།",
45
     formMonitorName: "བརྙན་ཟློས་མིང་།",
38
     formOwner: "གཙོ་བོ།",
46
     formOwner: "གཙོ་བོ།",
47
+    formEnclosureName: "圈舍",
39
     formInstallLocation: "སྒྲིག་གནས།",
48
     formInstallLocation: "སྒྲིག་གནས།",
40
-    formIpAddress: "IP",
49
+    formVideoSource: "视频来源",
50
+    phMonitorId: "摄像头 ID ≤32",
41
     phMonitorName: "མིང 1~10",
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
     dialogAdd: "[གསར]བརྙན་ཟློས",
56
     dialogAdd: "[གསར]བརྙན་ཟློས",
46
     dialogEdit: "[རྩོམ]བརྙན་ཟློས",
57
     dialogEdit: "[རྩོམ]བརྙན་ཟློས",
47
     confirmDelete: "ཐོ་གཞུང་འདི་སུབ་རྒྱུ་གཏན་ཁེལ་ལམ?",
58
     confirmDelete: "ཐོ་གཞུང་འདི་སུབ་རྒྱུ་གཏན་ཁེལ་ལམ?",
48
-    msgLiveNotOpen: "ད་ལྟ་བརྙན་མ་ཁ་ཕྱེ། སྒྲིག་གནས་སྔོན་དུ་གྲུབ",
49
-    msgLiveNoStream: "བརྙན་ཟློས་མེད(接口对接待)",
59
+    msgLiveNoMonitorId: "摄像头 ID མེད",
60
+    msgLiveNoStream: "བརྙན་ཟློས་མེད",
61
+    viewMonitorId: "摄像头 ID",
50
     viewMonitorName: "བརྙན་ཟློས་མིང་།",
62
     viewMonitorName: "བརྙན་ཟློས་མིང་།",
51
     viewOwner: "གཙོ་བོ།",
63
     viewOwner: "གཙོ་བོ།",
64
+    viewEnclosureName: "圈舍",
52
     viewInstallLocation: "སྒྲིག་གནས།",
65
     viewInstallLocation: "སྒྲིག་གནས།",
53
-    viewIpAddress: "IP",
66
+    viewVideoSource: "视频来源",
54
     viewCreateBy: "བཟོ་མཁན།",
67
     viewCreateBy: "བཟོ་མཁན།",
55
     viewUpdateBy: "རྩོམ་མཁན།",
68
     viewUpdateBy: "རྩོམ་མཁན།",
56
     viewUpdateTime: "རྩོམ་དུས།",
69
     viewUpdateTime: "རྩོམ་དུས།",
70
+    ruleMonitorIdRequired: "摄像头 ID འབྲི",
71
+    ruleMonitorIdLen: "ID 32 ལས་མི་འགྲོ",
57
     ruleMonitorNameRequired: "མིང་འབྲི",
72
     ruleMonitorNameRequired: "མིང་འབྲི",
58
     ruleMonitorNameLen: "1~10 ཡིག",
73
     ruleMonitorNameLen: "1~10 ཡིག",
59
-    ruleOwnerRequired: "གཙོ་བོ་འདེམ",
60
-    ruleInstallLocationRequired: "སྒྲིག་གནས་འབྲི",
74
+    ruleOwnerIncomplete: "གཙོ་བོ་ཆ་ཚང་མིན",
75
+    ruleEnclosureNameLen: "圈舍 64 ལས་མི་འགྲོ",
61
     ruleInstallLocationLen: "1~10 ཡིག",
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
     colCreate: "创建时间",
17
     colCreate: "创建时间",
18
     msgDelOk: "删除成功",
18
     msgDelOk: "删除成功",
19
     msgModOk: "修改成功",
19
     msgModOk: "修改成功",
20
-    msgAddOk: "新增成功"
20
+    msgAddOk: "新增成功",
21
+    syncLoading: "正在同步数据,请稍候..."
21
   },
22
   },
22
   status: {
23
   status: {
23
     dash: "—",
24
     dash: "—",
24
     ownerType1: "养殖主体",
25
     ownerType1: "养殖主体",
25
-    ownerType2: "交易市场"
26
+    ownerType2: "交易市场",
27
+    videoSourceWvp: "WVP 平台",
28
+    videoSourceYs7: "萤石云",
29
+    videoSourceUrl: "直链 URL"
26
   },
30
   },
27
   livestockMonitor: {
31
   livestockMonitor: {
28
     queryMonitorName: "监控名称",
32
     queryMonitorName: "监控名称",
@@ -34,33 +38,42 @@ export default {
34
     colInstallLocation: "安装位置",
38
     colInstallLocation: "安装位置",
35
     actionLiveView: "查看实时监控",
39
     actionLiveView: "查看实时监控",
36
     dialogLiveView: "实时监控",
40
     dialogLiveView: "实时监控",
41
+    btnSync: "同步",
42
+    syncSummary: "同步完成:新增 {insert} 条,更新 {update} 条,失败 {fail} 条",
43
+    syncTimeHint: "最近同步时间:{time}",
44
+    formMonitorId: "摄像头 ID",
37
     formMonitorName: "监控名称",
45
     formMonitorName: "监控名称",
38
     formOwner: "所属主体",
46
     formOwner: "所属主体",
47
+    formEnclosureName: "圈舍",
39
     formInstallLocation: "安装位置",
48
     formInstallLocation: "安装位置",
40
-    formIpAddress: "IP 地址",
49
+    formVideoSource: "视频来源",
50
+    phMonitorId: "请输入摄像头 ID(≤32 字符)",
41
     phMonitorName: "请输入监控名称(1~10字)",
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
     dialogAdd: "[新增]监控点位",
56
     dialogAdd: "[新增]监控点位",
46
     dialogEdit: "[编辑]监控点位",
57
     dialogEdit: "[编辑]监控点位",
47
     confirmDelete: "是否确认删除这条数据?",
58
     confirmDelete: "是否确认删除这条数据?",
48
-    msgLiveNotOpen: "实时监控功能暂未开放,请先完成监控点位登记",
49
-    msgLiveNoStream: "暂无直播流(监控流地址待接口对接)",
59
+    msgLiveNoMonitorId: "该监控点位缺少摄像头 ID,无法获取播放地址",
60
+    msgLiveNoStream: "暂无直播流",
61
+    viewMonitorId: "摄像头 ID",
50
     viewMonitorName: "监控名称",
62
     viewMonitorName: "监控名称",
51
     viewOwner: "所属主体",
63
     viewOwner: "所属主体",
64
+    viewEnclosureName: "圈舍",
52
     viewInstallLocation: "安装位置",
65
     viewInstallLocation: "安装位置",
53
-    viewIpAddress: "IP 地址",
66
+    viewVideoSource: "视频来源",
54
     viewCreateBy: "创建人",
67
     viewCreateBy: "创建人",
55
     viewUpdateBy: "修改人",
68
     viewUpdateBy: "修改人",
56
     viewUpdateTime: "修改时间",
69
     viewUpdateTime: "修改时间",
70
+    ruleMonitorIdRequired: "请填写摄像头 ID",
71
+    ruleMonitorIdLen: "摄像头 ID 不能超过 32 个字符",
57
     ruleMonitorNameRequired: "请填写监控名称",
72
     ruleMonitorNameRequired: "请填写监控名称",
58
     ruleMonitorNameLen: "监控名称长度为 1~10 个字符",
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
         return "warning"
21
         return "warning"
22
       }
22
       }
23
       return "info"
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
         <el-form-item>
37
         <el-form-item>
38
           <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">{{ vsCommon("search") }}</el-button>
38
           <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">{{ vsCommon("search") }}</el-button>
39
           <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">{{ vsCommon("reset") }}</el-button>
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
           <el-button
50
           <el-button
41
             v-hasPermi="['videoSurveillance:livestockMonitor:add']"
51
             v-hasPermi="['videoSurveillance:livestockMonitor:add']"
42
             type="primary"
52
             type="primary"
@@ -45,8 +55,10 @@
45
             size="mini"
55
             size="mini"
46
             @click="handleAdd"
56
             @click="handleAdd"
47
           >{{ vsCommon("add") }}</el-button>
57
           >{{ vsCommon("add") }}</el-button>
58
+          -->
48
         </el-form-item>
59
         </el-form-item>
49
       </el-form>
60
       </el-form>
61
+      <div v-if="lastSyncHint" class="livestock-monitor__sync-hint">{{ lastSyncHint }}</div>
50
     </el-card>
62
     </el-card>
51
 
63
 
52
     <br />
64
     <br />
@@ -63,15 +75,21 @@
63
           </template>
75
           </template>
64
         </el-table-column>
76
         </el-table-column>
65
         <el-table-column :label="vsT('colInstallLocation')" prop="installLocation" align="center" min-width="120" :show-overflow-tooltip="true" />
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
           <template slot-scope="scope">
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
             <el-button
86
             <el-button
70
               v-hasPermi="['videoSurveillance:livestockMonitor:query']"
87
               v-hasPermi="['videoSurveillance:livestockMonitor:query']"
71
               type="text"
88
               type="text"
72
               size="mini"
89
               size="mini"
73
               @click="handleView(scope.row)"
90
               @click="handleView(scope.row)"
74
             >{{ vsCommon("view") }}</el-button>
91
             >{{ vsCommon("view") }}</el-button>
92
+            <!-- 编辑、删除(暂隐藏)
75
             <el-button
93
             <el-button
76
               v-hasPermi="['videoSurveillance:livestockMonitor:edit']"
94
               v-hasPermi="['videoSurveillance:livestockMonitor:edit']"
77
               type="text"
95
               type="text"
@@ -84,6 +102,7 @@
84
               size="mini"
102
               size="mini"
85
               @click="handleDelete(scope.row)"
103
               @click="handleDelete(scope.row)"
86
             >{{ vsCommon("delete") }}</el-button>
104
             >{{ vsCommon("delete") }}</el-button>
105
+            -->
87
           </template>
106
           </template>
88
         </el-table-column>
107
         </el-table-column>
89
       </el-table>
108
       </el-table>
@@ -96,8 +115,13 @@
96
       />
115
       />
97
     </el-card>
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
       <el-form :key="formKey" ref="form" :model="form" label-width="110px">
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
         <el-form-item prop="monitorName" :rules="formRules.monitorName">
125
         <el-form-item prop="monitorName" :rules="formRules.monitorName">
102
           <template slot="label">{{ vsT("formMonitorName") }}</template>
126
           <template slot="label">{{ vsT("formMonitorName") }}</template>
103
           <el-input v-model="form.monitorName" :placeholder="vsT('phMonitorName')" maxlength="10" show-word-limit />
127
           <el-input v-model="form.monitorName" :placeholder="vsT('phMonitorName')" maxlength="10" show-word-limit />
@@ -109,6 +133,7 @@
109
             filterable
133
             filterable
110
             remote
134
             remote
111
             reserve-keyword
135
             reserve-keyword
136
+            clearable
112
             :placeholder="vsT('phOwner')"
137
             :placeholder="vsT('phOwner')"
113
             :remote-method="searchOwner"
138
             :remote-method="searchOwner"
114
             :loading="ownerLoading"
139
             :loading="ownerLoading"
@@ -123,13 +148,24 @@
123
             />
148
             />
124
           </el-select>
149
           </el-select>
125
         </el-form-item>
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
         <el-form-item prop="installLocation" :rules="formRules.installLocation">
155
         <el-form-item prop="installLocation" :rules="formRules.installLocation">
127
           <template slot="label">{{ vsT("formInstallLocation") }}</template>
156
           <template slot="label">{{ vsT("formInstallLocation") }}</template>
128
           <el-input v-model="form.installLocation" :placeholder="vsT('phInstallLocation')" maxlength="10" show-word-limit />
157
           <el-input v-model="form.installLocation" :placeholder="vsT('phInstallLocation')" maxlength="10" show-word-limit />
129
         </el-form-item>
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
         </el-form-item>
169
         </el-form-item>
134
       </el-form>
170
       </el-form>
135
       <div slot="footer" class="dialog-footer">
171
       <div slot="footer" class="dialog-footer">
@@ -137,9 +173,13 @@
137
         <el-button @click="cancel">{{ vsCommon("cancel") }}</el-button>
173
         <el-button @click="cancel">{{ vsCommon("cancel") }}</el-button>
138
       </div>
174
       </div>
139
     </el-dialog>
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
       <el-form label-width="110px" size="small">
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
         <el-form-item :label="vsT('viewMonitorName')">
183
         <el-form-item :label="vsT('viewMonitorName')">
144
           <span>{{ viewRow.monitorName || dash }}</span>
184
           <span>{{ viewRow.monitorName || dash }}</span>
145
         </el-form-item>
185
         </el-form-item>
@@ -149,11 +189,14 @@
149
           </el-tag>
189
           </el-tag>
150
           <span>{{ viewRow.ownerName || dash }}</span>
190
           <span>{{ viewRow.ownerName || dash }}</span>
151
         </el-form-item>
191
         </el-form-item>
192
+        <el-form-item :label="vsT('viewEnclosureName')">
193
+          <span>{{ viewRow.enclosureName || dash }}</span>
194
+        </el-form-item>
152
         <el-form-item :label="vsT('viewInstallLocation')">
195
         <el-form-item :label="vsT('viewInstallLocation')">
153
           <span>{{ viewRow.installLocation || dash }}</span>
196
           <span>{{ viewRow.installLocation || dash }}</span>
154
         </el-form-item>
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
         </el-form-item>
200
         </el-form-item>
158
         <el-form-item :label="vsCommon('colCreate')">
201
         <el-form-item :label="vsCommon('colCreate')">
159
           <span>{{ viewRow.createTime ? parseTime(viewRow.createTime) : dash }}</span>
202
           <span>{{ viewRow.createTime ? parseTime(viewRow.createTime) : dash }}</span>
@@ -169,11 +212,13 @@
169
         </el-form-item>
212
         </el-form-item>
170
       </el-form>
213
       </el-form>
171
       <div slot="footer" class="dialog-footer">
214
       <div slot="footer" class="dialog-footer">
215
+        <!-- 编辑(暂隐藏)
172
         <el-button
216
         <el-button
173
           v-hasPermi="['videoSurveillance:livestockMonitor:edit']"
217
           v-hasPermi="['videoSurveillance:livestockMonitor:edit']"
174
           type="primary"
218
           type="primary"
175
           @click="handleEditFromView"
219
           @click="handleEditFromView"
176
         >{{ vsCommon("edit") }}</el-button>
220
         >{{ vsCommon("edit") }}</el-button>
221
+        -->
177
         <el-button @click="viewOpen = false">{{ vsCommon("close") }}</el-button>
222
         <el-button @click="viewOpen = false">{{ vsCommon("close") }}</el-button>
178
       </div>
223
       </div>
179
     </el-dialog>
224
     </el-dialog>
@@ -186,7 +231,7 @@
186
       destroy-on-close
231
       destroy-on-close
187
       @close="closeLiveView"
232
       @close="closeLiveView"
188
     >
233
     >
189
-      <div class="live-player-wrap">
234
+      <div v-loading="liveLoading" class="live-player-wrap">
190
         <LivePlayer
235
         <LivePlayer
191
           :video-url="liveVideoUrl"
236
           :video-url="liveVideoUrl"
192
           :video-title="liveVideoTitle"
237
           :video-title="liveVideoTitle"
@@ -216,18 +261,22 @@ import {
216
   addLivestockMonitor,
261
   addLivestockMonitor,
217
   updateLivestockMonitor,
262
   updateLivestockMonitor,
218
   delLivestockMonitor,
263
   delLivestockMonitor,
219
-  listLivestockMonitorOwnerOptions
264
+  listLivestockMonitorOwnerOptions,
265
+  syncLivestockMonitor,
266
+  getLivestockMonitorPlayUrl
220
 } from "@/api/videoSurveillance/livestockMonitor"
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
 function createEmptyForm() {
271
 function createEmptyForm() {
225
   return {
272
   return {
226
     id: undefined,
273
     id: undefined,
274
+    monitorId: undefined,
227
     monitorName: undefined,
275
     monitorName: undefined,
228
     ownerSelectKey: undefined,
276
     ownerSelectKey: undefined,
277
+    enclosureName: undefined,
229
     installLocation: undefined,
278
     installLocation: undefined,
230
-    ipAddress: undefined
279
+    videoSource: undefined
231
   }
280
   }
232
 }
281
 }
233
 
282
 
@@ -241,6 +290,8 @@ export default {
241
     return {
290
     return {
242
       vsNs: "livestockMonitor",
291
       vsNs: "livestockMonitor",
243
       loading: false,
292
       loading: false,
293
+      syncing: false,
294
+      liveLoading: false,
244
       open: false,
295
       open: false,
245
       viewOpen: false,
296
       viewOpen: false,
246
       liveOpen: false,
297
       liveOpen: false,
@@ -256,6 +307,7 @@ export default {
256
       ownerLoading: false,
307
       ownerLoading: false,
257
       ownerSearchTimer: null,
308
       ownerSearchTimer: null,
258
       queryOwnerKey: undefined,
309
       queryOwnerKey: undefined,
310
+      lastSyncHint: "",
259
       queryParams: {
311
       queryParams: {
260
         pageNum: 1,
312
         pageNum: 1,
261
         pageSize: 20,
313
         pageSize: 20,
@@ -277,23 +329,26 @@ export default {
277
       const name = this.liveRow.monitorName || this.liveVideoTitle
329
       const name = this.liveRow.monitorName || this.liveVideoTitle
278
       return name ? `${this.vsT("dialogLiveView")} - ${name}` : this.vsT("dialogLiveView")
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
     formRules() {
338
     formRules() {
281
       return {
339
       return {
340
+        monitorId: [
341
+          { required: true, message: this.vsT("ruleMonitorIdRequired"), trigger: "blur" },
342
+          { validator: this.validateMonitorId, trigger: "blur" }
343
+        ],
282
         monitorName: [
344
         monitorName: [
283
           { required: true, message: this.vsT("ruleMonitorNameRequired"), trigger: "blur" },
345
           { required: true, message: this.vsT("ruleMonitorNameRequired"), trigger: "blur" },
284
           { validator: this.validateMonitorName, trigger: "blur" }
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
   methods: {
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
     ownerTypeName(ownerType) {
376
     ownerTypeName(ownerType) {
310
       if (ownerType === 1) {
377
       if (ownerType === 1) {
311
         return this.$t("videoSurveillance.status.ownerType1")
378
         return this.$t("videoSurveillance.status.ownerType1")
@@ -369,42 +436,59 @@ export default {
369
           })
436
           })
370
       }, 300)
437
       }, 300)
371
     },
438
     },
372
-    validateMonitorName(rule, value, callback) {
439
+    validateMonitorId(rule, value, callback) {
373
       const text = value != null ? String(value).trim() : ""
440
       const text = value != null ? String(value).trim() : ""
374
       if (!text) {
441
       if (!text) {
375
-        callback(new Error(this.vsT("ruleMonitorNameRequired")))
442
+        callback(new Error(this.vsT("ruleMonitorIdRequired")))
376
         return
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
         return
447
         return
381
       }
448
       }
382
       callback()
449
       callback()
383
     },
450
     },
384
-    validateInstallLocation(rule, value, callback) {
451
+    validateMonitorName(rule, value, callback) {
385
       const text = value != null ? String(value).trim() : ""
452
       const text = value != null ? String(value).trim() : ""
386
       if (!text) {
453
       if (!text) {
387
-        callback(new Error(this.vsT("ruleInstallLocationRequired")))
454
+        callback(new Error(this.vsT("ruleMonitorNameRequired")))
388
         return
455
         return
389
       }
456
       }
390
       if (text.length > 10) {
457
       if (text.length > 10) {
391
-        callback(new Error(this.vsT("ruleInstallLocationLen")))
458
+        callback(new Error(this.vsT("ruleMonitorNameLen")))
392
         return
459
         return
393
       }
460
       }
394
       callback()
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
         return
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
         return
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
         return
492
         return
409
       }
493
       }
410
       callback()
494
       callback()
@@ -427,13 +511,24 @@ export default {
427
     },
511
     },
428
     buildPayload() {
512
     buildPayload() {
429
       const parsed = this.parseOwnerKey(this.form.ownerSelectKey)
513
       const parsed = this.parseOwnerKey(this.form.ownerSelectKey)
430
-      return {
514
+      const payload = {
515
+        monitorId: String(this.form.monitorId).trim(),
431
         monitorName: String(this.form.monitorName).trim(),
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
     resetFormModel() {
533
     resetFormModel() {
439
       this.form = createEmptyForm()
534
       this.form = createEmptyForm()
@@ -444,10 +539,12 @@ export default {
444
       this.ensureOwnerOptionInList(data.ownerType, data.ownerId, data.ownerName, data.ownerTypeName)
539
       this.ensureOwnerOptionInList(data.ownerType, data.ownerId, data.ownerName, data.ownerTypeName)
445
       this.form = Object.assign(createEmptyForm(), {
540
       this.form = Object.assign(createEmptyForm(), {
446
         id: data.id,
541
         id: data.id,
542
+        monitorId: data.monitorId,
447
         monitorName: data.monitorName,
543
         monitorName: data.monitorName,
448
         ownerSelectKey,
544
         ownerSelectKey,
545
+        enclosureName: data.enclosureName,
449
         installLocation: data.installLocation,
546
         installLocation: data.installLocation,
450
-        ipAddress: data.ipAddress
547
+        videoSource: data.videoSource
451
       })
548
       })
452
     },
549
     },
453
     getList() {
550
     getList() {
@@ -511,29 +608,56 @@ export default {
511
       })
608
       })
512
     },
609
     },
513
     handleLiveView(row) {
610
     handleLiveView(row) {
514
-      if (!row || row.id == null) {
611
+      if (!row || !row.monitorId) {
612
+        this.$modal.msgWarning(this.vsT("msgLiveNoMonitorId"))
515
         return
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
     closeLiveView() {
634
     closeLiveView() {
526
       this.liveVideoUrl = ""
635
       this.liveVideoUrl = ""
527
       this.liveVideoTitle = ""
636
       this.liveVideoTitle = ""
528
       this.liveRow = {}
637
       this.liveRow = {}
529
       this.liveOpen = false
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
     handleDelete(row) {
662
     handleDelete(row) {
539
       this.$modal
663
       this.$modal
@@ -582,6 +706,12 @@ export default {
582
   vertical-align: middle;
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
 .live-player-wrap {
715
 .live-player-wrap {
586
   position: relative;
716
   position: relative;
587
   width: 100%;
717
   width: 100%;