Commit 693796439b5e6f016c20d34309fa32da437648c7

Authored by “wangming”
1 parent 52443042

新增获取客户端ID功能,区分生产环境和开发环境,优化MQTT连接时的客户端ID设置,更新相关文档以反映新功能。

.DS_Store
No preview for this file type
antis-ncc-admin/src/views/login/index.vue
1 <template> 1 <template>
2 - <div class="container">  
3 - <el-container>  
4 - <el-aside style="width: 50%; background-color: #fff">  
5 - <img src="../../assets/images/3.png" alt="" />  
6 - </el-aside>  
7 - <el-main>  
8 - <p>安第斯管理系统</p>  
9 - <!-- <span>一体化协同服务</span> -->  
10 - <el-form ref="loginForm" :model="loginForm" :rules="loginRules">  
11 - <ul>  
12 - <li>  
13 - <el-form-item prop="account">  
14 - <img src="../../assets/images/1.png" alt="" /> 2 + <div class="login-container">
  3 + <!-- 背景装饰 -->
  4 + <div class="bg-decoration">
  5 + <div class="circle circle-1"></div>
  6 + <div class="circle circle-2"></div>
  7 + <div class="circle circle-3"></div>
  8 + </div>
  9 +
  10 + <!-- 主要内容区域 -->
  11 + <div class="main-content">
  12 + <!-- 左侧品牌区域 -->
  13 + <div class="brand-section">
  14 + <div class="brand-content">
  15 + <h1 class="brand-title">自助无人机租赁</h1>
  16 + <h2 class="brand-subtitle">管理后台</h2>
  17 + <p class="brand-desc">智能化无人机租赁管理平台</p>
  18 + </div>
  19 + </div>
  20 +
  21 + <!-- 右侧登录表单 -->
  22 + <div class="login-section">
  23 + <div class="login-card">
  24 + <div class="login-header">
  25 + <h3>欢迎登录</h3>
  26 + <p>请输入您的账号信息</p>
  27 + </div>
  28 +
  29 + <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form">
  30 + <div class="form-group">
  31 + <div class="input-wrapper">
  32 + <div class="input-icon">
  33 + <img src="../../assets/images/1.png" alt="用户" />
  34 + </div>
15 <input 35 <input
16 type="text" 36 type="text"
17 name="account" 37 name="account"
18 v-model="loginForm.account" 38 v-model="loginForm.account"
19 ref="account" 39 ref="account"
20 tabindex="1" 40 tabindex="1"
21 - :placeholder="$t('login.username')" 41 + placeholder="请输入用户名"
  42 + class="form-input"
22 /> 43 />
23 - </el-form-item>  
24 - </li>  
25 - <li>  
26 - <el-form-item prop="password">  
27 - <img src="../../assets/images/2.png" alt="" /> 44 + </div>
  45 + </div>
  46 +
  47 + <div class="form-group">
  48 + <div class="input-wrapper">
  49 + <div class="input-icon">
  50 + <img src="../../assets/images/2.png" alt="密码" />
  51 + </div>
28 <input 52 <input
29 type="password" 53 type="password"
30 name="password" 54 name="password"
@@ -32,22 +56,25 @@ @@ -32,22 +56,25 @@
32 ref="password" 56 ref="password"
33 tabindex="2" 57 tabindex="2"
34 :placeholder="$t('login.password')" 58 :placeholder="$t('login.password')"
  59 + class="form-input"
35 /> 60 />
36 - </el-form-item>  
37 - </li>  
38 - <li class="bottom"> 61 + </div>
  62 + </div>
  63 +
  64 + <div class="form-actions">
39 <el-button 65 <el-button
40 :loading="loading" 66 :loading="loading"
41 type="primary" 67 type="primary"
42 - round  
43 @click.native.prevent="handleLogin" 68 @click.native.prevent="handleLogin"
44 - >{{ $t("login.logIn") }}</el-button 69 + class="login-btn"
45 > 70 >
46 - </li>  
47 - </ul>  
48 - </el-form>  
49 - </el-main>  
50 - </el-container> 71 + {{ $t("login.logIn") }}
  72 + </el-button>
  73 + </div>
  74 + </el-form>
  75 + </div>
  76 + </div>
  77 + </div>
51 </div> 78 </div>
52 </template> 79 </template>
53 <script> 80 <script>
@@ -199,79 +226,293 @@ export default { @@ -199,79 +226,293 @@ export default {
199 * { 226 * {
200 margin: 0; 227 margin: 0;
201 padding: 0; 228 padding: 0;
202 - list-style: none;  
203 box-sizing: border-box; 229 box-sizing: border-box;
204 } 230 }
205 -html,  
206 -body { 231 +
  232 +.login-container {
  233 + width: 100vw;
  234 + height: 100vh;
  235 + background: linear-gradient(135deg, #4f46e5 0%, #06b6d4 100%);
  236 + background-image: url(../../assets/images/4.jpg);
  237 + background-size: cover;
  238 + background-position: center;
  239 + background-blend-mode: overlay;
  240 + position: relative;
  241 + overflow: hidden;
  242 + display: flex;
  243 + align-items: center;
  244 + justify-content: center;
  245 +}
  246 +
  247 +// 背景装饰圆圈
  248 +.bg-decoration {
  249 + position: absolute;
  250 + top: 0;
  251 + left: 0;
207 width: 100%; 252 width: 100%;
208 height: 100%; 253 height: 100%;
  254 + pointer-events: none;
  255 + z-index: 1;
  256 +}
  257 +
  258 +.circle {
  259 + position: absolute;
  260 + border-radius: 50%;
  261 + background: rgba(255, 255, 255, 0.1);
  262 + backdrop-filter: blur(20px);
  263 + animation: float 6s ease-in-out infinite;
  264 +}
  265 +
  266 +.circle-1 {
  267 + width: 200px;
  268 + height: 200px;
  269 + top: 10%;
  270 + left: 10%;
  271 + animation-delay: 0s;
209 } 272 }
210 -.container { 273 +
  274 +.circle-2 {
  275 + width: 150px;
  276 + height: 150px;
  277 + top: 60%;
  278 + right: 15%;
  279 + animation-delay: 2s;
  280 +}
  281 +
  282 +.circle-3 {
  283 + width: 100px;
  284 + height: 100px;
  285 + bottom: 20%;
  286 + left: 20%;
  287 + animation-delay: 4s;
  288 +}
  289 +
  290 +@keyframes float {
  291 + 0%, 100% { transform: translateY(0px) rotate(0deg); }
  292 + 50% { transform: translateY(-20px) rotate(180deg); }
  293 +}
  294 +
  295 +// 主要内容区域
  296 +.main-content {
211 width: 100%; 297 width: 100%;
212 - height: 100%;  
213 - background-image: url(../../assets/images/4.jpg);  
214 - background-size: 100% 100%;  
215 - padding-top: 10%;  
216 - box-sizing: border-box; 298 + max-width: 1200px;
  299 + height: 600px;
  300 + display: flex;
  301 + background: rgba(255, 255, 255, 0.1);
  302 + backdrop-filter: blur(20px);
  303 + border-radius: 24px;
  304 + border: 1px solid rgba(255, 255, 255, 0.2);
  305 + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
  306 + overflow: hidden;
  307 + position: relative;
  308 + z-index: 2;
  309 +}
  310 +
  311 +// 左侧品牌区域
  312 +.brand-section {
  313 + flex: 1;
  314 + background: linear-gradient(135deg, rgba(79, 70, 229, 0.8) 0%, rgba(6, 182, 212, 0.8) 100%);
  315 + display: flex;
  316 + align-items: center;
  317 + justify-content: center;
  318 + position: relative;
  319 + overflow: hidden;
217 } 320 }
218 321
219 -.container > .el-container {  
220 - width: 50%;  
221 - max-width: 900px;  
222 - min-width: 600px;  
223 - margin: 0 auto;  
224 - background-color: #fff;  
225 - border-radius: 20px;  
226 - padding: 50px 0; 322 +.brand-section::before {
  323 + content: '';
  324 + position: absolute;
  325 + top: 0;
  326 + left: 0;
  327 + right: 0;
  328 + bottom: 0;
  329 + background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="grid" width="10" height="10" patternUnits="userSpaceOnUse"><path d="M 10 0 L 0 0 0 10" fill="none" stroke="rgba(255,255,255,0.1)" stroke-width="0.5"/></pattern></defs><rect width="100" height="100" fill="url(%23grid)"/></svg>');
  330 + opacity: 0.3;
227 } 331 }
228 -.el-aside { 332 +
  333 +.brand-content {
229 text-align: center; 334 text-align: center;
230 - padding: 40px; 335 + color: white;
  336 + z-index: 1;
  337 + position: relative;
  338 +}
  339 +
  340 +.logo {
  341 + margin-bottom: 30px;
  342 +}
  343 +
  344 +.logo-icon {
  345 + font-size: 80px;
  346 + margin-bottom: 20px;
  347 + filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.3));
  348 +}
  349 +
  350 +.brand-title {
  351 + font-size: 36px;
  352 + font-weight: 700;
  353 + margin-bottom: 10px;
  354 + letter-spacing: 2px;
  355 + text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
231 } 356 }
232 -.el-main {  
233 - padding-right: 80px; 357 +
  358 +.brand-subtitle {
  359 + font-size: 24px;
  360 + font-weight: 500;
  361 + margin-bottom: 20px;
  362 + opacity: 0.9;
  363 + letter-spacing: 1px;
234 } 364 }
235 -.el-aside img { 365 +
  366 +.brand-desc {
  367 + font-size: 16px;
  368 + opacity: 0.8;
  369 + font-weight: 300;
  370 + letter-spacing: 1px;
  371 +}
  372 +
  373 +// 右侧登录区域
  374 +.login-section {
  375 + flex: 1;
  376 + background: rgba(255, 255, 255, 0.95);
  377 + display: flex;
  378 + align-items: center;
  379 + justify-content: center;
  380 + padding: 60px 40px;
  381 +}
  382 +
  383 +.login-card {
236 width: 100%; 384 width: 100%;
  385 + max-width: 400px;
237 } 386 }
238 -.el-main p {  
239 - font-size: 25px; 387 +
  388 +.login-header {
  389 + text-align: center;
  390 + margin-bottom: 40px;
  391 +}
  392 +
  393 +.login-header h3 {
  394 + font-size: 28px;
  395 + color: #333;
240 margin-bottom: 10px; 396 margin-bottom: 10px;
241 - margin-top: 40px; 397 + font-weight: 600;
  398 +}
  399 +
  400 +.login-header p {
  401 + color: #666;
  402 + font-size: 14px;
  403 + font-weight: 400;
  404 +}
  405 +
  406 +// 表单样式
  407 +.login-form {
  408 + width: 100%;
242 } 409 }
243 -.el-main > span {  
244 - color: #bfbfbf;  
245 - font-size: 20px; 410 +
  411 +.form-group {
  412 + margin-bottom: 24px;
246 } 413 }
247 -.el-main ul {  
248 - margin-top: 70px; 414 +
  415 +.input-wrapper {
  416 + position: relative;
  417 + display: flex;
  418 + align-items: center;
  419 + background: #f8f9fa;
  420 + border: 2px solid #e9ecef;
  421 + border-radius: 12px;
  422 + padding: 0 16px;
  423 + height: 56px;
  424 + transition: all 0.3s ease;
249 } 425 }
250 -.el-main ul li {  
251 - border-bottom: 1px solid #eeeeee;  
252 - padding-bottom: 10px;  
253 - margin: 20px 0; 426 +
  427 +.input-wrapper:focus-within {
  428 + border-color: #4f46e5;
  429 + background: #fff;
  430 + box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);
254 } 431 }
255 -.el-main ul .bottom {  
256 - border-bottom: none;  
257 - margin-top: 60px; 432 +
  433 +.input-icon {
  434 + margin-right: 12px;
  435 + display: flex;
  436 + align-items: center;
258 } 437 }
259 -.el-main ul li img {  
260 - width: 25px;  
261 - vertical-align: middle; 438 +
  439 +.input-icon img {
  440 + width: 20px;
  441 + height: 20px;
  442 + opacity: 0.6;
  443 + filter: brightness(0) saturate(100%) invert(27%) sepia(51%) saturate(2878%) hue-rotate(346deg) brightness(104%) contrast(97%);
262 } 444 }
263 -.el-main ul li input {  
264 - width: 80%; 445 +
  446 +.form-input {
  447 + flex: 1;
265 border: none; 448 border: none;
266 outline: none; 449 outline: none;
267 - padding-left: 10px; 450 + background: transparent;
268 font-size: 16px; 451 font-size: 16px;
  452 + color: #333;
  453 + font-weight: 400;
  454 + height: 100%;
  455 +}
  456 +
  457 +.form-input::placeholder {
  458 + color: #999;
  459 + font-weight: 400;
269 } 460 }
270 -.el-main ul li button {  
271 - width: 90%;  
272 - color: #fff;  
273 - display: block;  
274 - margin: 20px auto;  
275 - font-size: 20px; 461 +
  462 +.form-actions {
  463 + margin-top: 32px;
  464 +}
  465 +
  466 +.login-btn {
  467 + width: 100%;
  468 + height: 56px;
  469 + background: linear-gradient(135deg, #4f46e5 0%, #06b6d4 100%);
  470 + border: none;
  471 + border-radius: 12px;
  472 + color: white;
  473 + font-size: 16px;
  474 + font-weight: 600;
  475 + cursor: pointer;
  476 + transition: all 0.3s ease;
  477 + box-shadow: 0 4px 15px rgba(79, 70, 229, 0.4);
  478 +}
  479 +
  480 +.login-btn:hover {
  481 + transform: translateY(-2px);
  482 + box-shadow: 0 8px 25px rgba(79, 70, 229, 0.6);
  483 +}
  484 +
  485 +.login-btn:active {
  486 + transform: translateY(0);
  487 +}
  488 +
  489 +// 响应式设计
  490 +@media (max-width: 768px) {
  491 + .main-content {
  492 + flex-direction: column;
  493 + height: auto;
  494 + margin: 20px;
  495 + max-width: none;
  496 + }
  497 +
  498 + .brand-section {
  499 + min-height: 200px;
  500 + }
  501 +
  502 + .brand-title {
  503 + font-size: 24px;
  504 + }
  505 +
  506 + .brand-subtitle {
  507 + font-size: 18px;
  508 + }
  509 +
  510 + .logo-icon {
  511 + font-size: 60px;
  512 + }
  513 +
  514 + .login-section {
  515 + padding: 40px 20px;
  516 + }
276 } 517 }
277 </style> 518 </style>
antis-ncc-admin/src/views/uavAppUpdateInfo/index.vue
@@ -85,9 +85,6 @@ @@ -85,9 +85,6 @@
85 <span>设备列表</span> 85 <span>设备列表</span>
86 </div> 86 </div>
87 <div class="header-actions"> 87 <div class="header-actions">
88 - <el-button type="text" size="small" @click="refreshDeviceStatus" :loading="deviceStatusLoading">  
89 - <i class="el-icon-refresh"></i>  
90 - </el-button>  
91 <el-dropdown @command="handleBatchSelect" trigger="click"> 88 <el-dropdown @command="handleBatchSelect" trigger="click">
92 <el-button type="text" size="small"> 89 <el-button type="text" size="small">
93 <i class="el-icon-s-operation"></i> 90 <i class="el-icon-s-operation"></i>
@@ -120,13 +117,7 @@ @@ -120,13 +117,7 @@
120 > 117 >
121 全部 118 全部
122 </div> 119 </div>
123 - <div  
124 - class="filter-chip"  
125 - :class="{ active: deviceFilterStatus === 'online' }"  
126 - @click="setDeviceFilter('online')"  
127 - >  
128 - 在线  
129 - </div> 120 +
130 <div 121 <div
131 class="filter-chip" 122 class="filter-chip"
132 :class="{ active: deviceFilterStatus === 'updated' }" 123 :class="{ active: deviceFilterStatus === 'updated' }"
@@ -146,18 +137,14 @@ @@ -146,18 +137,14 @@
146 137
147 <!-- 设备列表 --> 138 <!-- 设备列表 -->
148 <div class="device-list" v-loading="deviceListLoading"> 139 <div class="device-list" v-loading="deviceListLoading">
149 - <div v-for="device in filteredDeviceList" :key="device.id" class="modern-device-card" :class="{ 'offline': !device.isOnline, 'selected': device.selected }">  
150 - <el-checkbox v-model="device.selected" :disabled="!device.isOnline" class="device-checkbox"> 140 + <div v-for="device in filteredDeviceList" :key="device.id" class="modern-device-card" :class="{ 'selected': device.selected }">
  141 + <el-checkbox v-model="device.selected" class="device-checkbox">
151 <div class="device-content"> 142 <div class="device-content">
152 <div class="device-main"> 143 <div class="device-main">
153 <div class="device-name">{{ device.deviceName }}</div> 144 <div class="device-name">{{ device.deviceName }}</div>
154 <div class="device-code">{{ device.deviceCode }}</div> 145 <div class="device-code">{{ device.deviceCode }}</div>
155 </div> 146 </div>
156 <div class="device-meta"> 147 <div class="device-meta">
157 - <div class="device-status" :class="{ 'online': device.isOnline, 'offline': !device.isOnline }">  
158 - <span class="status-dot"></span>  
159 - {{ device.isOnline ? '在线' : '离线' }}  
160 - </div>  
161 <div class="device-version" :class="{ 'updated': device.appVersion === currentUpdateInfo.version }"> 148 <div class="device-version" :class="{ 'updated': device.appVersion === currentUpdateInfo.version }">
162 {{ device.appVersion || '未知' }} 149 {{ device.appVersion || '未知' }}
163 </div> 150 </div>
@@ -186,7 +173,6 @@ @@ -186,7 +173,6 @@
186 <div class="bottom-actions"> 173 <div class="bottom-actions">
187 <div class="action-info"> 174 <div class="action-info">
188 <span class="selected-count">已选择:{{ selectedDeviceCount }} 台设备</span> 175 <span class="selected-count">已选择:{{ selectedDeviceCount }} 台设备</span>
189 - <span class="online-count">在线:{{ onlineDeviceCount }} 台</span>  
190 <span class="outdated-count">待更新:{{ outdatedDeviceCount }} 台</span> 176 <span class="outdated-count">待更新:{{ outdatedDeviceCount }} 台</span>
191 </div> 177 </div>
192 <div class="action-buttons"> 178 <div class="action-buttons">
@@ -250,9 +236,6 @@ @@ -250,9 +236,6 @@
250 }, 236 },
251 // 刷新设备状态相关 237 // 刷新设备状态相关
252 deviceStatusLoading: false, 238 deviceStatusLoading: false,
253 - // 设备状态缓存  
254 - deviceStatusCache: new Map(),  
255 - lastStatusCheckTime: null,  
256 columnList: [ 239 columnList: [
257 { prop: 'version', label: 'App版本号' }, 240 { prop: 'version', label: 'App版本号' },
258 ], 241 ],
@@ -264,11 +247,7 @@ @@ -264,11 +247,7 @@
264 let filtered = this.deviceList; 247 let filtered = this.deviceList;
265 248
266 // 按状态筛选 249 // 按状态筛选
267 - if (this.deviceFilterStatus === 'online') {  
268 - filtered = filtered.filter(device => device.isOnline);  
269 - } else if (this.deviceFilterStatus === 'offline') {  
270 - filtered = filtered.filter(device => !device.isOnline);  
271 - } else if (this.deviceFilterStatus === 'updated') { 250 + if (this.deviceFilterStatus === 'updated') {
272 filtered = filtered.filter(device => device.appVersion === this.currentUpdateInfo.version); 251 filtered = filtered.filter(device => device.appVersion === this.currentUpdateInfo.version);
273 } else if (this.deviceFilterStatus === 'outdated') { 252 } else if (this.deviceFilterStatus === 'outdated') {
274 filtered = filtered.filter(device => device.appVersion !== this.currentUpdateInfo.version); 253 filtered = filtered.filter(device => device.appVersion !== this.currentUpdateInfo.version);
@@ -297,13 +276,7 @@ @@ -297,13 +276,7 @@
297 return this.deviceList.length; 276 return this.deviceList.length;
298 }, 277 },
299 278
300 - onlineDeviceCount() {  
301 - return this.deviceList.filter(device => device.isOnline).length;  
302 - },  
303 -  
304 - offlineDeviceCount() {  
305 - return this.deviceList.filter(device => !device.isOnline).length;  
306 - }, 279 +
307 280
308 updatedDeviceCount() { 281 updatedDeviceCount() {
309 return this.deviceList.filter(device => device.appVersion === this.currentUpdateInfo.version).length; 282 return this.deviceList.filter(device => device.appVersion === this.currentUpdateInfo.version).length;
@@ -428,94 +401,16 @@ @@ -428,94 +401,16 @@
428 this.deviceList = res.data.list.map(device => ({ 401 this.deviceList = res.data.list.map(device => ({
429 ...device, 402 ...device,
430 selected: false, 403 selected: false,
431 - isOnline: false // 默认离线,等待缓存查询 404 + isOnline: true // 默认在线,不再检测
432 })); 405 }));
433 406
434 - // 使用缓存查询设备在线状态  
435 - this.checkDeviceStatusFromCache();  
436 -  
437 this.deviceListLoading = false; 407 this.deviceListLoading = false;
438 }).catch(() => { 408 }).catch(() => {
439 this.deviceListLoading = false; 409 this.deviceListLoading = false;
440 }); 410 });
441 }, 411 },
442 412
443 - // 从缓存检查设备状态  
444 - checkDeviceStatusFromCache() {  
445 - const now = Date.now();  
446 - const cacheExpireTime = 30000; // 30秒缓存  
447 -  
448 - // 检查缓存是否有效  
449 - if (this.lastStatusCheckTime && (now - this.lastStatusCheckTime) < cacheExpireTime) {  
450 - // 使用缓存的状态  
451 - this.deviceList.forEach(device => {  
452 - const cachedStatus = this.deviceStatusCache.get(device.deviceCode);  
453 - device.isOnline = cachedStatus !== undefined ? cachedStatus : false;  
454 - });  
455 - return;  
456 - }  
457 -  
458 - // 缓存过期,需要重新查询  
459 - this.batchCheckDeviceStatus();  
460 - },  
461 -  
462 - // 批量查询设备在线状态  
463 - batchCheckDeviceStatus() {  
464 - if (this.deviceList.length === 0) return;  
465 -  
466 - const now = Date.now();  
467 - const cacheExpireTime = 30000; // 30秒缓存  
468 -  
469 - // 检查缓存是否有效  
470 - if (this.lastStatusCheckTime && (now - this.lastStatusCheckTime) < cacheExpireTime) {  
471 - // 使用缓存的状态  
472 - this.deviceList.forEach(device => {  
473 - const cachedStatus = this.deviceStatusCache.get(device.deviceCode);  
474 - device.isOnline = cachedStatus !== undefined ? cachedStatus : false;  
475 - });  
476 -  
477 - // 显示缓存状态  
478 - const onlineCount = this.deviceList.filter(device => device.isOnline).length;  
479 - this.$message.info(`使用缓存数据,在线设备:${onlineCount}个,离线设备:${this.deviceList.length - onlineCount}个`);  
480 - return;  
481 - }  
482 -  
483 - const deviceIds = this.deviceList.map(device => device.deviceCode);  
484 -  
485 - // 显示加载提示  
486 - this.$message.info('正在查询设备在线状态,请稍候...');  
487 -  
488 - request({  
489 - url: '/api/Extend/UavDevice/BatchCheckOnlineStatus',  
490 - method: 'POST',  
491 - data: {  
492 - deviceIds: deviceIds  
493 - }  
494 - }).then(res => {  
495 - if (res.data && res.data.length > 0) {  
496 - // 更新设备在线状态并缓存  
497 - this.deviceList.forEach(device => {  
498 - const onlineStatus = res.data.find(item => item.deviceId === device.deviceCode);  
499 - const isOnline = onlineStatus ? onlineStatus.isOnline : false;  
500 - device.isOnline = isOnline;  
501 -  
502 - // 缓存状态  
503 - this.deviceStatusCache.set(device.deviceCode, isOnline);  
504 - });  
505 -  
506 - // 更新缓存时间  
507 - this.lastStatusCheckTime = now;  
508 -  
509 - // 统计在线设备数量  
510 - const onlineCount = this.deviceList.filter(device => device.isOnline).length;  
511 - this.$message.success(`设备状态查询完成,在线设备:${onlineCount}个,离线设备:${this.deviceList.length - onlineCount}个`);  
512 - }  
513 - }).catch(() => {  
514 - // 如果批量查询失败,可以降级为逐个查询或保持默认状态  
515 - console.warn('批量查询设备在线状态失败');  
516 - this.$message.warning('设备在线状态查询失败,将显示为离线状态');  
517 - });  
518 - }, 413 +
519 414
520 // 处理设备搜索 415 // 处理设备搜索
521 handleDeviceSearch() { 416 handleDeviceSearch() {
@@ -536,9 +431,7 @@ @@ -536,9 +431,7 @@
536 // 全选当前页设备 431 // 全选当前页设备
537 selectAllDevices() { 432 selectAllDevices() {
538 this.filteredDeviceList.forEach(device => { 433 this.filteredDeviceList.forEach(device => {
539 - if (device.isOnline) {  
540 - device.selected = true;  
541 - } 434 + device.selected = true;
542 }); 435 });
543 }, 436 },
544 437
@@ -554,7 +447,7 @@ @@ -554,7 +447,7 @@
554 // 全选所有待更新设备 447 // 全选所有待更新设备
555 selectAllOutdatedDevices() { 448 selectAllOutdatedDevices() {
556 this.deviceList.forEach(device => { 449 this.deviceList.forEach(device => {
557 - if (device.isOnline && device.appVersion !== this.currentUpdateInfo.version) { 450 + if (device.appVersion !== this.currentUpdateInfo.version) {
558 device.selected = true; 451 device.selected = true;
559 } 452 }
560 }); 453 });
@@ -585,20 +478,7 @@ @@ -585,20 +478,7 @@
585 }); 478 });
586 }, 479 },
587 480
588 - // 刷新设备状态  
589 - refreshDeviceStatus() {  
590 - this.deviceStatusLoading = true;  
591 - // 清除缓存,强制重新查询  
592 - this.deviceStatusCache.clear();  
593 - this.lastStatusCheckTime = null;  
594 -  
595 - this.batchCheckDeviceStatus();  
596 -  
597 - // 延迟结束loading状态  
598 - setTimeout(() => {  
599 - this.deviceStatusLoading = false;  
600 - }, 1000);  
601 - }, 481 +
602 482
603 // 确认推送更新 483 // 确认推送更新
604 confirmPushUpdate() { 484 confirmPushUpdate() {
@@ -661,23 +541,7 @@ @@ -661,23 +541,7 @@
661 }); 541 });
662 }, 542 },
663 543
664 - // 格式化缓存时间  
665 - formatCacheTime(timestamp) {  
666 - if (!timestamp) return 'N/A';  
667 - const date = new Date(timestamp);  
668 - const now = Date.now();  
669 - const diff = now - timestamp;  
670 -  
671 - if (diff < 60000) { // 1分钟内  
672 - return '刚刚';  
673 - } else if (diff < 3600000) { // 1小时内  
674 - return `${Math.floor(diff / 60000)}分钟前`;  
675 - } else if (diff < 86400000) { // 24小时内  
676 - return `${Math.floor(diff / 3600000)}小时前`;  
677 - } else {  
678 - return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;  
679 - }  
680 - } 544 +
681 } 545 }
682 } 546 }
683 </script> 547 </script>
@@ -963,15 +827,7 @@ @@ -963,15 +827,7 @@
963 box-shadow: 0 10px 15px -3px rgba(59, 130, 246, 0.2), 0 4px 6px -2px rgba(59, 130, 246, 0.1); 827 box-shadow: 0 10px 15px -3px rgba(59, 130, 246, 0.2), 0 4px 6px -2px rgba(59, 130, 246, 0.1);
964 } 828 }
965 829
966 -.modern-device-card.offline {  
967 - opacity: 0.7;  
968 - background: #f8fafc;  
969 -}  
970 830
971 -.modern-device-card.offline:hover {  
972 - border-color: #ef4444;  
973 - box-shadow: 0 10px 15px -3px rgba(239, 68, 68, 0.1), 0 4px 6px -2px rgba(239, 68, 68, 0.05);  
974 -}  
975 831
976 .device-checkbox { 832 .device-checkbox {
977 width: 100%; 833 width: 100%;
@@ -1017,49 +873,7 @@ @@ -1017,49 +873,7 @@
1017 align-items: center; 873 align-items: center;
1018 } 874 }
1019 875
1020 -.device-status {  
1021 - display: flex;  
1022 - align-items: center;  
1023 - gap: 3px;  
1024 - font-size: 11px;  
1025 - font-weight: 500;  
1026 - padding: 3px 6px;  
1027 - border-radius: 4px;  
1028 -}  
1029 876
1030 -.device-status.online {  
1031 - background: rgba(16, 185, 129, 0.1);  
1032 - color: #10b981;  
1033 -}  
1034 -  
1035 -.device-status.offline {  
1036 - background: rgba(239, 68, 68, 0.1);  
1037 - color: #ef4444;  
1038 -}  
1039 -  
1040 -.modern-device-card.selected .device-status.online {  
1041 - background: rgba(255, 255, 255, 0.2);  
1042 - color: white;  
1043 -}  
1044 -  
1045 -.status-dot {  
1046 - width: 6px;  
1047 - height: 6px;  
1048 - border-radius: 50%;  
1049 - display: inline-block;  
1050 -}  
1051 -  
1052 -.device-status.online .status-dot {  
1053 - background-color: #10b981;  
1054 -}  
1055 -  
1056 -.device-status.offline .status-dot {  
1057 - background-color: #ef4444;  
1058 -}  
1059 -  
1060 -.modern-device-card.selected .device-status.online .status-dot {  
1061 - background-color: white;  
1062 -}  
1063 877
1064 .device-version { 878 .device-version {
1065 font-size: 11px; 879 font-size: 11px;
@@ -1127,10 +941,7 @@ @@ -1127,10 +941,7 @@
1127 font-weight: 500; 941 font-weight: 500;
1128 } 942 }
1129 943
1130 -.online-count {  
1131 - color: #10b981;  
1132 - font-weight: 500;  
1133 -} 944 +
1134 945
1135 .outdated-count { 946 .outdated-count {
1136 color: #f59e0b; 947 color: #f59e0b;
antis-ncc-admin/src/views/uavDevice/index.vue
@@ -29,35 +29,6 @@ @@ -29,35 +29,6 @@
29 <div class="NCC-common-layout-main NCC-flex-main"> 29 <div class="NCC-common-layout-main NCC-flex-main">
30 <div class="NCC-common-head"> 30 <div class="NCC-common-head">
31 <div class="head-left"> 31 <div class="head-left">
32 - <el-button  
33 - type="primary"  
34 - icon="el-icon-refresh"  
35 - @click="refreshOnlineStatus()"  
36 - :loading="statusLoading"  
37 - size="small"  
38 - >  
39 - 刷新在线状态  
40 - </el-button>  
41 -  
42 - <!-- 设备统计信息 -->  
43 - <div class="device-stats-inline">  
44 - <div class="stats-item">  
45 - <span class="stats-label">在线:</span>  
46 - <span class="stats-number online">{{ onlineDeviceCount }}</span>  
47 - </div>  
48 - <div class="stats-item">  
49 - <span class="stats-label">离线:</span>  
50 - <span class="stats-number offline">{{ offlineDeviceCount }}</span>  
51 - </div>  
52 - <div class="stats-item">  
53 - <span class="stats-label">总数:</span>  
54 - <span class="stats-number total">{{ totalDeviceCount }}</span>  
55 - </div>  
56 - <div class="stats-item">  
57 - <span class="stats-label">在线率:</span>  
58 - <span class="stats-number rate">{{ onlineRate }}%</span>  
59 - </div>  
60 - </div>  
61 </div> 32 </div>
62 <div class="NCC-common-head-right"> 33 <div class="NCC-common-head-right">
63 <el-tooltip effect="dark" content="刷新" placement="top"> 34 <el-tooltip effect="dark" content="刷新" placement="top">
@@ -74,14 +45,17 @@ @@ -74,14 +45,17 @@
74 <div class="cabinet-top"> 45 <div class="cabinet-top">
75 <div class="device-name-row"> 46 <div class="device-name-row">
76 <span>名称:{{ item.deviceName }}</span> 47 <span>名称:{{ item.deviceName }}</span>
77 - <div class="device-status"> 48 + <div class="device-status" :class="{ 'status-online': item.isOnline, 'status-offline': !item.isOnline }">
78 <span class="status-dot" :class="{ 'online': item.isOnline, 'offline': !item.isOnline }"></span> 49 <span class="status-dot" :class="{ 'online': item.isOnline, 'offline': !item.isOnline }"></span>
79 - <span>{{ item.isOnline ? '在线' : '离线' }}</span> 50 + <span class="status-text">{{ item.isOnline ? '在线' : '离线' }}</span>
80 </div> 51 </div>
81 </div> 52 </div>
82 <div class="device-info-row"> 53 <div class="device-info-row">
83 <span>编号:{{ item.deviceCode ? item.deviceCode.toUpperCase() : '' }}</span> 54 <span>编号:{{ item.deviceCode ? item.deviceCode.toUpperCase() : '' }}</span>
84 - <i class="el-icon-menu" style="cursor: pointer; margin-left: 5px;" @click="CreateQrCode(item.deviceCode)"></i> 55 + <div class="device-actions">
  56 + <i class="el-icon-menu" style="cursor: pointer; margin-left: 5px;" @click="CreateQrCode(item.deviceCode)" title="查看二维码"></i>
  57 + <i class="el-icon-upload" style="cursor: pointer; margin-left: 8px; color: #409EFF;" @click="pushAppUpdate(item)" title="推送APP更新"></i>
  58 + </div>
85 </div> 59 </div>
86 <div class="device-info-row"> 60 <div class="device-info-row">
87 <span>代理商:{{ item.belongUserName }}</span> 61 <span>代理商:{{ item.belongUserName }}</span>
@@ -196,37 +170,13 @@ export default { @@ -196,37 +170,13 @@ export default {
196 showQr: false, 170 showQr: false,
197 qrImgUrl: '',// 二维码图片地址 171 qrImgUrl: '',// 二维码图片地址
198 qrmessage: '暂无二维码', // 二维码提示信息 172 qrmessage: '暂无二维码', // 二维码提示信息
199 - statusLoading: false, // 在线状态加载状态  
200 - deviceStats: {  
201 - onlineCount: 0,  
202 - offlineCount: 0,  
203 - totalCount: 0  
204 - } // 设备统计数据  
205 - }  
206 - },  
207 - computed: {  
208 - // 计算在线设备数量  
209 - onlineDeviceCount() {  
210 - return this.deviceStats.onlineCount || 0;  
211 - },  
212 - // 计算离线设备数量  
213 - offlineDeviceCount() {  
214 - return this.deviceStats.offlineCount || 0;  
215 - },  
216 - // 计算总设备数量  
217 - totalDeviceCount() {  
218 - return this.deviceStats.totalCount || 0;  
219 - },  
220 - // 计算在线率  
221 - onlineRate() {  
222 - if (this.totalDeviceCount === 0) return 0;  
223 - return Math.round((this.onlineDeviceCount / this.totalDeviceCount) * 100); 173 +
224 } 174 }
225 }, 175 },
  176 +
226 created() { 177 created() {
227 this.initData() 178 this.initData()
228 this.getsiteIdOptions(); 179 this.getsiteIdOptions();
229 - this.getDeviceStats(); // 获取设备统计数据  
230 }, 180 },
231 methods: { 181 methods: {
232 getStatusColor(status) { 182 getStatusColor(status) {
@@ -362,7 +312,6 @@ export default { @@ -362,7 +312,6 @@ export default {
362 sidx: "", 312 sidx: "",
363 } 313 }
364 this.initData() 314 this.initData()
365 - this.getDeviceStats() // 更新统计数据  
366 }, 315 },
367 refresh(isrRefresh) { 316 refresh(isrRefresh) {
368 this.formVisible = false 317 this.formVisible = false
@@ -380,286 +329,63 @@ export default { @@ -380,286 +329,63 @@ export default {
380 } 329 }
381 330
382 this.initData() 331 this.initData()
383 - this.getDeviceStats() // 更新统计数据  
384 }, 332 },
385 333
386 - // 刷新在线状态  
387 - refreshOnlineStatus() {  
388 - this.statusLoading = true;  
389 - this.getDeviceOnlineStatus();  
390 - this.getDeviceOnlineStatusAsync(); // 直接调用异步获取在线状态 334 + // 推送APP更新
  335 + pushAppUpdate(device) {
  336 + if (!device.isOnline) {
  337 + this.$message.warning('设备离线,无法推送更新');
  338 + return;
  339 + }
391 340
392 - // 延迟结束loading状态  
393 - setTimeout(() => {  
394 - this.statusLoading = false;  
395 - }, 1000);  
396 - },  
397 -  
398 - // 获取设备统计数据(快速版本)  
399 - getDeviceStats() {  
400 - // 先获取设备总数,快速显示  
401 - request({  
402 - url: `/api/Extend/UavDevice`,  
403 - method: 'GET',  
404 - data: {  
405 - currentPage: 1,  
406 - pageSize: 1, // 只获取1条数据来获取总数  
407 - sort: "desc",  
408 - sidx: "",  
409 - ...this.query  
410 - }  
411 - }).then(res => {  
412 - if (res.data && res.data.pagination) {  
413 - const totalCount = res.data.pagination.total || 0;  
414 -  
415 - // 先显示总数,在线状态默认为0  
416 - this.deviceStats = {  
417 - onlineCount: 0,  
418 - offlineCount: totalCount,  
419 - totalCount: totalCount  
420 - };  
421 -  
422 - // 如果有设备,异步获取在线状态  
423 - if (totalCount > 0) {  
424 - this.getDeviceOnlineStatusAsync(); 341 + this.$confirm(`确定要推送APP更新到设备 "${device.deviceName}" 吗?`, '确认推送', {
  342 + confirmButtonText: '确定',
  343 + cancelButtonText: '取消',
  344 + type: 'warning'
  345 + }).then(() => {
  346 + this.$message.info('正在推送APP更新,请稍候...');
  347 +
  348 + // 调用推送更新接口
  349 + request({
  350 + url: `/api/Extend/UavDevice/PushAppUpdate`,
  351 + method: 'POST',
  352 + data: {
  353 + deviceCode: device.deviceCode,
  354 + downloadUrl: "" // 可选参数,传空字符串使用默认下载地址
425 } 355 }
426 - } else {  
427 - this.deviceStats = {  
428 - onlineCount: 0,  
429 - offlineCount: 0,  
430 - totalCount: 0  
431 - };  
432 - }  
433 - }).catch(error => {  
434 - console.error('获取设备总数失败:', error);  
435 - this.deviceStats = {  
436 - onlineCount: 0,  
437 - offlineCount: 0,  
438 - totalCount: 0  
439 - };  
440 - });  
441 - },  
442 -  
443 - // 异步获取设备在线状态  
444 - getDeviceOnlineStatusAsync() {  
445 - // 获取所有设备  
446 - request({  
447 - url: `/api/Extend/UavDevice`,  
448 - method: 'GET',  
449 - data: {  
450 - currentPage: 1,  
451 - pageSize: 10000,  
452 - sort: "desc",  
453 - sidx: "",  
454 - ...this.query  
455 - }  
456 - }).then(res => {  
457 - if (res.data && res.data.list) {  
458 - const allDevices = res.data.list;  
459 - const deviceCodes = allDevices.map(item => item.deviceCode).filter(code => code);  
460 -  
461 - if (deviceCodes.length > 0) {  
462 - // 显示查询提示  
463 - this.$message.info('正在查询设备在线状态,请稍候...'); 356 + }).then(res => {
  357 + if (res.code === 200) {
  358 + this.$message.success(`APP更新推送成功!设备:${device.deviceName}`);
464 359
465 - request({  
466 - url: `/api/Extend/UavDevice/BatchCheckOnlineStatus`,  
467 - method: 'POST',  
468 - data: {  
469 - deviceIds: deviceCodes  
470 - }  
471 - }).then(statusRes => {  
472 - if (statusRes.data && statusRes.data.length > 0) {  
473 - let onlineCount = 0;  
474 - let offlineCount = 0;  
475 -  
476 - statusRes.data.forEach(statusInfo => {  
477 - if (statusInfo.isOnline) {  
478 - onlineCount++;  
479 - } else {  
480 - offlineCount++;  
481 - }  
482 - });  
483 -  
484 - // 更新统计数据  
485 - this.deviceStats = {  
486 - onlineCount: onlineCount,  
487 - offlineCount: offlineCount,  
488 - totalCount: allDevices.length  
489 - };  
490 -  
491 - // 显示查询结果  
492 - this.$message.success(`设备状态查询完成,在线:${onlineCount}个,离线:${offlineCount}个`);  
493 - } else {  
494 - this.deviceStats = {  
495 - onlineCount: 0,  
496 - offlineCount: allDevices.length,  
497 - totalCount: allDevices.length  
498 - };  
499 - }  
500 - }).catch(error => {  
501 - console.error('获取设备在线状态失败:', error);  
502 - this.$message.warning('在线状态查询失败,将显示为离线状态');  
503 - }); 360 + // 可选:刷新设备状态
  361 + setTimeout(() => {
  362 + this.getDeviceOnlineStatus();
  363 + }, 2000);
  364 + } else {
  365 + this.$message.error(res.msg || 'APP更新推送失败');
504 } 366 }
505 - }  
506 - }).catch(error => {  
507 - console.error('获取设备列表失败:', error); 367 + }).catch(error => {
  368 + console.error('推送APP更新失败:', error);
  369 + this.$message.error('APP更新推送失败,请检查网络连接');
  370 + });
  371 + }).catch(() => {
  372 + this.$message.info('已取消推送');
508 }); 373 });
509 - } 374 + },
  375 +
  376 +
510 } 377 }
511 } 378 }
512 </script> 379 </script>
513 380
514 <style> 381 <style>
515 -/* 设备统计信息样式 */ 382 +/* 头部样式 */
516 .head-left { 383 .head-left {
517 display: flex; 384 display: flex;
518 align-items: center; 385 align-items: center;
519 gap: 20px; 386 gap: 20px;
520 } 387 }
521 388
522 -.device-stats-inline {  
523 - display: flex;  
524 - align-items: center;  
525 - gap: 16px;  
526 - margin-left: 20px;  
527 -}  
528 -  
529 -.stats-item {  
530 - display: flex;  
531 - align-items: center;  
532 - gap: 4px;  
533 -}  
534 -  
535 -.stats-item .stats-label {  
536 - font-size: 12px;  
537 - color: #909399;  
538 - font-weight: 500;  
539 -}  
540 -  
541 -.stats-item .stats-number {  
542 - font-size: 14px;  
543 - font-weight: 600;  
544 - padding: 2px 6px;  
545 - border-radius: 4px;  
546 - min-width: 20px;  
547 - text-align: center;  
548 -}  
549 -  
550 -.stats-item .stats-number.online {  
551 - background: rgba(103, 194, 58, 0.1);  
552 - color: #67C23A;  
553 -}  
554 -  
555 -.stats-item .stats-number.offline {  
556 - background: rgba(245, 108, 108, 0.1);  
557 - color: #F56C6C;  
558 -}  
559 -  
560 -.stats-item .stats-number.total {  
561 - background: rgba(64, 158, 255, 0.1);  
562 - color: #409EFF;  
563 -}  
564 -  
565 -.stats-item .stats-number.rate {  
566 - background: rgba(230, 162, 60, 0.1);  
567 - color: #E6A23C;  
568 -}  
569 -  
570 -@media (max-width: 1200px) {  
571 - .device-stats-inline {  
572 - gap: 12px;  
573 - margin-left: 16px;  
574 - }  
575 -  
576 - .stats-item .stats-number {  
577 - font-size: 13px;  
578 - padding: 1px 4px;  
579 - }  
580 -}  
581 -  
582 -@media (max-width: 768px) {  
583 - .head-left {  
584 - flex-direction: column;  
585 - align-items: flex-start;  
586 - gap: 8px;  
587 - }  
588 -  
589 - .device-stats-inline {  
590 - margin-left: 0;  
591 - gap: 8px;  
592 - flex-wrap: wrap;  
593 - }  
594 -  
595 - .stats-item .stats-number {  
596 - font-size: 12px;  
597 - padding: 1px 3px;  
598 - }  
599 -}  
600 -  
601 -.stats-card {  
602 - flex: 1;  
603 - background: white;  
604 - border-radius: 8px;  
605 - padding: 20px;  
606 - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);  
607 - display: flex;  
608 - align-items: center;  
609 - transition: all 0.3s ease;  
610 -}  
611 -  
612 -.stats-card:hover {  
613 - transform: translateY(-2px);  
614 - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);  
615 -}  
616 -  
617 -.stats-icon {  
618 - width: 48px;  
619 - height: 48px;  
620 - border-radius: 50%;  
621 - display: flex;  
622 - align-items: center;  
623 - justify-content: center;  
624 - margin-right: 16px;  
625 - font-size: 24px;  
626 - color: white;  
627 -}  
628 -  
629 -.stats-icon.online {  
630 - background: linear-gradient(135deg, #67C23A, #85CE61);  
631 -}  
632 -  
633 -.stats-icon.offline {  
634 - background: linear-gradient(135deg, #F56C6C, #F78989);  
635 -}  
636 -  
637 -.stats-icon.total {  
638 - background: linear-gradient(135deg, #409EFF, #66B1FF);  
639 -}  
640 -  
641 -.stats-icon.online-rate {  
642 - background: linear-gradient(135deg, #E6A23C, #EEBE77);  
643 -}  
644 -  
645 -.stats-content {  
646 - flex: 1;  
647 -}  
648 -  
649 -.stats-number {  
650 - font-size: 28px;  
651 - font-weight: 700;  
652 - color: #303133;  
653 - line-height: 1;  
654 - margin-bottom: 4px;  
655 -}  
656 -  
657 -.stats-label {  
658 - font-size: 14px;  
659 - color: #909399;  
660 - font-weight: 500;  
661 -}  
662 -  
663 .qr-popup { 389 .qr-popup {
664 position: fixed; 390 position: fixed;
665 left: 0; 391 left: 0;
@@ -728,7 +454,7 @@ export default { @@ -728,7 +454,7 @@ export default {
728 margin-bottom: 8px; 454 margin-bottom: 8px;
729 } 455 }
730 456
731 -.cabinet-bottom {} 457 +
732 458
733 .cabinet-middle { 459 .cabinet-middle {
734 /* display: grid; 460 /* display: grid;
@@ -756,8 +482,24 @@ export default { @@ -756,8 +482,24 @@ export default {
756 .device-status { 482 .device-status {
757 display: flex; 483 display: flex;
758 align-items: center; 484 align-items: center;
759 - gap: 4px; 485 + gap: 6px;
760 font-size: 11px; 486 font-size: 11px;
  487 + padding: 4px 8px;
  488 + border-radius: 12px;
  489 + font-weight: 600;
  490 + transition: all 0.3s ease;
  491 +}
  492 +
  493 +.device-status.status-online {
  494 + background: rgba(103, 194, 58, 0.2);
  495 + border: 1px solid #67C23A;
  496 + color: #67C23A;
  497 +}
  498 +
  499 +.device-status.status-offline {
  500 + background: rgba(245, 108, 108, 0.2);
  501 + border: 1px solid #F56C6C;
  502 + color: #F56C6C;
761 } 503 }
762 504
763 .device-name-row { 505 .device-name-row {
@@ -774,18 +516,60 @@ export default { @@ -774,18 +516,60 @@ export default {
774 margin-bottom: 2px; 516 margin-bottom: 2px;
775 } 517 }
776 518
  519 +.device-actions {
  520 + display: flex;
  521 + align-items: center;
  522 + gap: 4px;
  523 +}
  524 +
  525 +.device-actions i {
  526 + transition: all 0.3s ease;
  527 + padding: 2px;
  528 + border-radius: 3px;
  529 +}
  530 +
  531 +.device-actions i:hover {
  532 + background-color: rgba(255, 255, 255, 0.2);
  533 + transform: scale(1.1);
  534 +}
  535 +
777 .status-dot { 536 .status-dot {
778 - width: 8px;  
779 - height: 8px; 537 + width: 10px;
  538 + height: 10px;
780 border-radius: 50%; 539 border-radius: 50%;
781 display: inline-block; 540 display: inline-block;
  541 + box-shadow: 0 0 4px rgba(0, 0, 0, 0.3);
  542 + animation: pulse 2s infinite;
782 } 543 }
783 544
784 .status-dot.online { 545 .status-dot.online {
785 background-color: #67C23A; 546 background-color: #67C23A;
  547 + box-shadow: 0 0 6px rgba(103, 194, 58, 0.6);
786 } 548 }
787 549
788 .status-dot.offline { 550 .status-dot.offline {
789 background-color: #F56C6C; 551 background-color: #F56C6C;
  552 + box-shadow: 0 0 6px rgba(245, 108, 108, 0.6);
  553 + animation: none;
  554 +}
  555 +
  556 +.status-text {
  557 + font-weight: 600;
  558 + letter-spacing: 0.5px;
  559 +}
  560 +
  561 +@keyframes pulse {
  562 + 0% {
  563 + transform: scale(1);
  564 + opacity: 1;
  565 + }
  566 + 50% {
  567 + transform: scale(1.1);
  568 + opacity: 0.8;
  569 + }
  570 + 100% {
  571 + transform: scale(1);
  572 + opacity: 1;
  573 + }
790 } 574 }
791 </style> 575 </style>
792 \ No newline at end of file 576 \ No newline at end of file
antis-ncc-admin/src/views/uavDeviceCell/AdjustTimeDialog.vue 0 → 100644
  1 +<template>
  2 + <el-dialog
  3 + title="调整修改时间"
  4 + :visible.sync="visible"
  5 + width="600px"
  6 + :close-on-click-modal="false"
  7 + class="adjust-time-dialog">
  8 +
  9 + <!-- 格位信息卡片 -->
  10 + <div class="cell-info-card">
  11 + <div class="cell-info-header">
  12 + <i class="el-icon-location"></i>
  13 + <span>格位信息</span>
  14 + </div>
  15 + <div class="cell-info-content">
  16 + <div class="info-item">
  17 + <span class="info-label">设备名称:</span>
  18 + <span class="info-value">{{ currentCell.deviceName }}</span>
  19 + </div>
  20 + <div class="info-item">
  21 + <span class="info-label">格位编号:</span>
  22 + <span class="info-value">{{ currentCell.cellCode }}</span>
  23 + </div>
  24 + <div class="info-item">
  25 + <span class="info-label">当前修改时间:</span>
  26 + <span class="info-value time-value">{{ formatDateTime(currentCell.updateTime) }}</span>
  27 + </div>
  28 + </div>
  29 + </div>
  30 +
  31 + <!-- 调整表单 -->
  32 + <el-form :model="adjustTimeForm" :rules="adjustTimeRules" ref="adjustTimeForm" class="adjust-form">
  33 + <el-form-item label="调整方式" prop="adjustType">
  34 + <el-radio-group v-model="adjustTimeForm.adjustType" class="adjust-type-group">
  35 + <el-radio label="set" class="adjust-radio">
  36 + <i class="el-icon-time"></i>
  37 + <span>设置为指定时间</span>
  38 + </el-radio>
  39 + <el-radio label="add" class="adjust-radio">
  40 + <i class="el-icon-plus"></i>
  41 + <span>增加时间</span>
  42 + </el-radio>
  43 + <el-radio label="subtract" class="adjust-radio">
  44 + <i class="el-icon-minus"></i>
  45 + <span>减少时间</span>
  46 + </el-radio>
  47 + </el-radio-group>
  48 + </el-form-item>
  49 +
  50 + <el-form-item v-if="adjustTimeForm.adjustType === 'set'" label="设置时间" prop="setTime">
  51 + <el-date-picker
  52 + v-model="adjustTimeForm.setTime"
  53 + type="datetime"
  54 + placeholder="选择时间"
  55 + format="yyyy-MM-dd HH:mm:ss"
  56 + value-format="yyyy-MM-dd HH:mm:ss"
  57 + class="time-picker">
  58 + </el-date-picker>
  59 + </el-form-item>
  60 +
  61 + <el-form-item v-if="adjustTimeForm.adjustType !== 'set'" label="调整时长" prop="adjustHours">
  62 + <div class="hours-input-wrapper">
  63 + <el-input-number
  64 + v-model="adjustTimeForm.adjustHours"
  65 + :min="0"
  66 + :max="24"
  67 + :precision="1"
  68 + :step="0.5"
  69 + class="hours-input">
  70 + </el-input-number>
  71 + <span class="hours-unit">小时</span>
  72 + </div>
  73 + </el-form-item>
  74 +
  75 + <el-form-item v-if="adjustTimeForm.adjustType !== 'set'" label="调整后时间">
  76 + <div class="preview-time">
  77 + <i class="el-icon-clock"></i>
  78 + <span>{{ getPreviewTime() }}</span>
  79 + </div>
  80 + </el-form-item>
  81 + </el-form>
  82 +
  83 + <div slot="footer" class="dialog-footer">
  84 + <el-button @click="handleCancel" size="medium" class="cancel-btn">取消</el-button>
  85 + <el-button type="primary" @click="handleConfirm" :loading="loading" size="medium" class="confirm-btn">
  86 + <i class="el-icon-check"></i>
  87 + 确定调整
  88 + </el-button>
  89 + </div>
  90 + </el-dialog>
  91 +</template>
  92 +
  93 +<script>
  94 +import request from '@/utils/request'
  95 +
  96 +export default {
  97 + name: 'AdjustTimeDialog',
  98 + data() {
  99 + return {
  100 + visible: false,
  101 + loading: false,
  102 + currentCell: {},
  103 + adjustTimeForm: {
  104 + adjustType: 'set',
  105 + setTime: '',
  106 + adjustHours: 0.5
  107 + },
  108 + adjustTimeRules: {
  109 + adjustType: [
  110 + { required: true, message: '请选择调整方式', trigger: 'change' }
  111 + ],
  112 + setTime: [
  113 + { required: true, message: '请选择设置时间', trigger: 'change' }
  114 + ],
  115 + adjustHours: [
  116 + { required: true, message: '请输入调整时长', trigger: 'blur' }
  117 + ]
  118 + }
  119 + }
  120 + },
  121 + methods: {
  122 + // 打开对话框
  123 + open(cell) {
  124 + this.currentCell = cell;
  125 + this.adjustTimeForm = {
  126 + adjustType: 'set',
  127 + setTime: '',
  128 + adjustHours: 0.5
  129 + };
  130 + this.visible = true;
  131 + },
  132 +
  133 + // 关闭对话框
  134 + handleCancel() {
  135 + this.visible = false;
  136 + },
  137 +
  138 + // 格式化日期时间
  139 + formatDateTime(dateTime) {
  140 + if (!dateTime) {
  141 + return '--';
  142 + }
  143 +
  144 + const date = new Date(dateTime);
  145 +
  146 + if (isNaN(date.getTime())) {
  147 + return '--';
  148 + }
  149 +
  150 + const year = date.getFullYear();
  151 + const month = String(date.getMonth() + 1).padStart(2, '0');
  152 + const day = String(date.getDate()).padStart(2, '0');
  153 + const hours = String(date.getHours()).padStart(2, '0');
  154 + const minutes = String(date.getMinutes()).padStart(2, '0');
  155 + const seconds = String(date.getSeconds()).padStart(2, '0');
  156 +
  157 + return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
  158 + },
  159 +
  160 + // 格式化日期时间给后台(本地时间格式)
  161 + formatDateTimeForBackend(dateTime) {
  162 + if (!dateTime) {
  163 + return null;
  164 + }
  165 +
  166 + const date = new Date(dateTime);
  167 +
  168 + if (isNaN(date.getTime())) {
  169 + return null;
  170 + }
  171 +
  172 + const year = date.getFullYear();
  173 + const month = String(date.getMonth() + 1).padStart(2, '0');
  174 + const day = String(date.getDate()).padStart(2, '0');
  175 + const hours = String(date.getHours()).padStart(2, '0');
  176 + const minutes = String(date.getMinutes()).padStart(2, '0');
  177 + const seconds = String(date.getSeconds()).padStart(2, '0');
  178 +
  179 + return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
  180 + },
  181 +
  182 + // 获取预览时间
  183 + getPreviewTime() {
  184 + if (!this.currentCell.updateTime) {
  185 + return '--';
  186 + }
  187 +
  188 + if (this.adjustTimeForm.adjustType === 'set') {
  189 + if (this.adjustTimeForm.setTime) {
  190 + return this.formatDateTime(new Date(this.adjustTimeForm.setTime));
  191 + }
  192 + return '--';
  193 + }
  194 +
  195 + const currentTime = new Date(this.currentCell.updateTime);
  196 + let newTime;
  197 +
  198 + if (this.adjustTimeForm.adjustType === 'add') {
  199 + newTime = new Date(currentTime.getTime() + this.adjustTimeForm.adjustHours * 60 * 60 * 1000);
  200 + } else if (this.adjustTimeForm.adjustType === 'subtract') {
  201 + newTime = new Date(currentTime.getTime() - this.adjustTimeForm.adjustHours * 60 * 60 * 1000);
  202 + }
  203 +
  204 + return this.formatDateTime(newTime);
  205 + },
  206 +
  207 + // 确认调整时间
  208 + handleConfirm() {
  209 + this.$refs.adjustTimeForm.validate((valid) => {
  210 + if (valid) {
  211 + this.loading = true;
  212 +
  213 + // 计算新的修改时间
  214 + let newUpdateTime;
  215 + const currentTime = new Date(this.currentCell.updateTime);
  216 +
  217 + console.log('当前格位时间:', this.currentCell.updateTime);
  218 + console.log('当前时间对象:', currentTime);
  219 + console.log('调整类型:', this.adjustTimeForm.adjustType);
  220 +
  221 + if (this.adjustTimeForm.adjustType === 'set') {
  222 + newUpdateTime = new Date(this.adjustTimeForm.setTime);
  223 + console.log('设置时间:', this.adjustTimeForm.setTime);
  224 + } else if (this.adjustTimeForm.adjustType === 'add') {
  225 + const addMilliseconds = this.adjustTimeForm.adjustHours * 60 * 60 * 1000;
  226 + newUpdateTime = new Date(currentTime.getTime() + addMilliseconds);
  227 + console.log('增加小时:', this.adjustTimeForm.adjustHours);
  228 + console.log('增加毫秒:', addMilliseconds);
  229 + } else if (this.adjustTimeForm.adjustType === 'subtract') {
  230 + const subtractMilliseconds = this.adjustTimeForm.adjustHours * 60 * 60 * 1000;
  231 + newUpdateTime = new Date(currentTime.getTime() - subtractMilliseconds);
  232 + console.log('减少小时:', this.adjustTimeForm.adjustHours);
  233 + console.log('减少毫秒:', subtractMilliseconds);
  234 + }
  235 +
  236 + // 调试信息
  237 + console.log('原始时间:', this.currentCell.updateTime);
  238 + console.log('计算后时间:', newUpdateTime);
  239 + console.log('发送给后台的时间:', this.formatDateTimeForBackend(newUpdateTime));
  240 +
  241 + // 调用后台接口调整时间
  242 + request({
  243 + url: `/api/Extend/UavDeviceCell/AdjustUpdateTime`,
  244 + method: 'POST',
  245 + data: {
  246 + cellId: this.currentCell.id,
  247 + newUpdateTime: this.formatDateTimeForBackend(newUpdateTime)
  248 + }
  249 + }).then(res => {
  250 + if (res.code === 200) {
  251 + this.$message.success('修改时间调整成功!');
  252 + this.visible = false;
  253 + this.$emit('success'); // 通知父组件刷新数据
  254 + } else {
  255 + this.$message.error(res.msg || '调整失败');
  256 + }
  257 + }).catch(error => {
  258 + console.error('调整时间失败:', error);
  259 + this.$message.error('调整失败,请重试');
  260 + }).finally(() => {
  261 + this.loading = false;
  262 + });
  263 + }
  264 + });
  265 + }
  266 + }
  267 +}
  268 +</script>
  269 +
  270 +<style lang="scss" scoped>
  271 +/* 调整时间对话框样式 */
  272 +.adjust-time-dialog .el-dialog__header {
  273 + background: #409EFF;
  274 + color: white;
  275 + padding: 20px 20px 15px;
  276 + border-radius: 8px 8px 0 0;
  277 +}
  278 +
  279 +.adjust-time-dialog .el-dialog__title {
  280 + color: white;
  281 + font-weight: 600;
  282 + font-size: 18px;
  283 +}
  284 +
  285 +.adjust-time-dialog .el-dialog__headerbtn .el-dialog__close {
  286 + color: white;
  287 + font-size: 20px;
  288 +}
  289 +
  290 +.adjust-time-dialog .el-dialog__body {
  291 + padding: 30px 20px;
  292 +}
  293 +
  294 +/* 格位信息卡片 */
  295 +.cell-info-card {
  296 + background: #f0f9ff;
  297 + border: 1px solid #bae6fd;
  298 + border-radius: 12px;
  299 + margin-bottom: 25px;
  300 + overflow: hidden;
  301 + box-shadow: 0 2px 8px rgba(64, 158, 255, 0.1);
  302 +}
  303 +
  304 +.cell-info-header {
  305 + background: #409EFF;
  306 + color: white;
  307 + padding: 12px 20px;
  308 + display: flex;
  309 + align-items: center;
  310 + gap: 8px;
  311 + font-weight: 600;
  312 +}
  313 +
  314 +.cell-info-header i {
  315 + font-size: 16px;
  316 +}
  317 +
  318 +.cell-info-content {
  319 + padding: 20px;
  320 +}
  321 +
  322 +.info-item {
  323 + display: flex;
  324 + align-items: center;
  325 + margin-bottom: 12px;
  326 +}
  327 +
  328 +.info-item:last-child {
  329 + margin-bottom: 0;
  330 +}
  331 +
  332 +.info-label {
  333 + color: #666;
  334 + font-weight: 500;
  335 + min-width: 100px;
  336 +}
  337 +
  338 +.info-value {
  339 + color: #333;
  340 + font-weight: 600;
  341 +}
  342 +
  343 +.time-value {
  344 + color: #409EFF;
  345 + font-family: 'Courier New', monospace;
  346 +}
  347 +
  348 +/* 调整表单样式 */
  349 +.adjust-form .el-form-item__label {
  350 + font-weight: 600;
  351 + color: #333;
  352 +}
  353 +
  354 +.adjust-type-group {
  355 + display: flex;
  356 + flex-direction: column;
  357 + gap: 12px;
  358 +}
  359 +
  360 +.adjust-radio {
  361 + display: flex;
  362 + align-items: center;
  363 + gap: 8px;
  364 + padding: 12px 16px;
  365 + border: 2px solid #e4e7ed;
  366 + border-radius: 8px;
  367 + transition: all 0.3s ease;
  368 + cursor: pointer;
  369 + width: 100%;
  370 + justify-content: flex-start;
  371 +}
  372 +
  373 +.adjust-radio:hover {
  374 + border-color: #409EFF;
  375 + background: rgba(64, 158, 255, 0.05);
  376 +}
  377 +
  378 +.adjust-radio.is-checked {
  379 + border-color: #409EFF;
  380 + background: rgba(64, 158, 255, 0.1);
  381 + color: #409EFF;
  382 +}
  383 +
  384 +.adjust-radio i {
  385 + font-size: 16px;
  386 +}
  387 +
  388 +/* 时间选择器样式 */
  389 +.time-picker {
  390 + width: 100%;
  391 +}
  392 +
  393 +.time-picker .el-input__inner {
  394 + border-radius: 8px;
  395 + border: 2px solid #e4e7ed;
  396 + transition: all 0.3s ease;
  397 +}
  398 +
  399 +.time-picker .el-input__inner:focus {
  400 + border-color: #409EFF;
  401 + box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
  402 +}
  403 +
  404 +/* 小时输入样式 */
  405 +.hours-input-wrapper {
  406 + display: flex;
  407 + align-items: center;
  408 + gap: 12px;
  409 +}
  410 +
  411 +.hours-input {
  412 + width: 200px;
  413 +}
  414 +
  415 +.hours-input .el-input__inner {
  416 + border-radius: 8px;
  417 + border: 2px solid #e4e7ed;
  418 + transition: all 0.3s ease;
  419 +}
  420 +
  421 +.hours-input .el-input__inner:focus {
  422 + border-color: #409EFF;
  423 + box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
  424 +}
  425 +
  426 +.hours-unit {
  427 + color: #666;
  428 + font-weight: 500;
  429 + font-size: 14px;
  430 +}
  431 +
  432 +/* 预览时间样式 */
  433 +.preview-time {
  434 + display: flex;
  435 + align-items: center;
  436 + gap: 8px;
  437 + padding: 12px 16px;
  438 + background: #f0f9ff;
  439 + border: 1px solid #bae6fd;
  440 + border-radius: 8px;
  441 + color: #0369a1;
  442 + font-weight: 600;
  443 + font-family: 'Courier New', monospace;
  444 +}
  445 +
  446 +.preview-time i {
  447 + color: #409EFF;
  448 + font-size: 16px;
  449 +}
  450 +
  451 +/* 对话框底部按钮样式 */
  452 +.dialog-footer {
  453 + text-align: right;
  454 + padding: 20px;
  455 + border-top: 1px solid #e4e7ed;
  456 + background: #fafafa;
  457 +}
  458 +
  459 +.dialog-footer .el-button {
  460 + border-radius: 8px;
  461 + font-weight: 500;
  462 + padding: 10px 20px;
  463 +}
  464 +
  465 +.dialog-footer .el-button {
  466 + padding: 12px 24px;
  467 + font-size: 14px;
  468 + font-weight: 500;
  469 + border-radius: 6px;
  470 + min-width: 80px;
  471 +}
  472 +
  473 +.cancel-btn {
  474 + background: #f5f5f5;
  475 + border-color: #d9d9d9;
  476 + color: #666;
  477 +}
  478 +
  479 +.cancel-btn:hover {
  480 + background: #e6f7ff;
  481 + border-color: #409EFF;
  482 + color: #409EFF;
  483 +}
  484 +
  485 +.confirm-btn {
  486 + background: #409EFF;
  487 + border: none;
  488 +}
  489 +
  490 +.confirm-btn:hover {
  491 + background: #66b1ff;
  492 + transform: translateY(-1px);
  493 + box-shadow: 0 4px 12px rgba(64, 158, 255, 0.4);
  494 +}
  495 +
  496 +.confirm-btn i {
  497 + margin-right: 6px;
  498 +}
  499 +</style>
antis-ncc-admin/src/views/uavDeviceCell/Form.vue
1 <template> 1 <template>
2 - <el-dialog :title="!dataForm.id ? '新建' : isDetail ? '详情':'编辑'" :close-on-click-modal="false" :visible.sync="visible" class="NCC-dialog NCC-dialog_center" lock-scroll width="600px">  
3 - <el-row :gutter="15" class="" >  
4 - <el-form ref="elForm" :model="dataForm" size="small" label-width="100px" label-position="right" :disabled="!!isDetail" :rules="rules"> 2 + <el-dialog :title="!dataForm.id ? '新建格位' : isDetail ? '格位详情':'调整格位状态'" :close-on-click-modal="false" :visible.sync="visible" class="NCC-dialog NCC-dialog_center" lock-scroll width="600px">
  3 + <el-row :gutter="15" class="edit-form-container">
  4 + <!-- 只读信息展示区域 -->
  5 + <div class="readonly-info-section">
  6 + <div class="section-title">
  7 + <i class="el-icon-info"></i>
  8 + <span>格位信息</span>
  9 + </div>
  10 + <div class="info-grid">
  11 + <div class="info-item">
  12 + <span class="info-label">设备名称:</span>
  13 + <span class="info-value">{{ getDeviceName(dataForm.deviceId) }}</span>
  14 + </div>
  15 + <div class="info-item">
  16 + <span class="info-label">格位编号:</span>
  17 + <span class="info-value">{{ dataForm.cellCode }}</span>
  18 + </div>
  19 + <div class="info-item">
  20 + <span class="info-label">当前状态:</span>
  21 + <el-tag :type="getStatusColor(dataForm.status)" class="status-tag">
  22 + {{ getStatusName(dataForm.status) }}
  23 + </el-tag>
  24 + </div>
  25 + </div>
  26 + </div>
  27 +
  28 + <!-- 状态修改区域 -->
  29 + <div class="edit-section">
  30 + <div class="section-title">
  31 + <i class="el-icon-edit"></i>
  32 + <span>状态调整</span>
  33 + </div>
  34 + <el-form ref="elForm" :model="dataForm" size="small" label-width="100px" label-position="right" :rules="rules" class="status-form">
5 <el-col :span="24"> 35 <el-col :span="24">
6 - <el-form-item label="所属设备ID" prop="deviceId">  
7 - <el-select v-model="dataForm.deviceId" placeholder="请选择" clearable :style='{"width":"100%"}' >  
8 - <el-option v-for="(item, index) in deviceIdOptions" :key="index" :label="item.F_DeviceName" :value="item.F_Id" ></el-option>  
9 - </el-select>  
10 - </el-form-item>  
11 - </el-col>  
12 - <el-col :span="24">  
13 - <el-form-item label="格子编号" prop="cellCode">  
14 - <el-input v-model="dataForm.cellCode" placeholder="请输入" clearable :style='{"width":"100%"}' >  
15 - </el-input>  
16 - </el-form-item>  
17 - </el-col>  
18 - <el-col :span="24">  
19 - <el-form-item label="状态" prop="status">  
20 - <el-radio-group v-model="dataForm.status" :style='{}' >  
21 - <el-radio v-for="(item, index) in statusOptions" :key="index" :label="item.id" >{{item.fullName}}</el-radio> 36 + <el-form-item label="新状态" prop="status" class="status-form-item">
  37 + <el-radio-group v-model="dataForm.status" class="status-radio-group">
  38 + <el-radio v-for="(item, index) in statusOptions" :key="index" :label="item.id" class="status-radio">
  39 + <span class="radio-text">{{ item.fullName }}</span>
  40 + </el-radio>
22 </el-radio-group> 41 </el-radio-group>
23 </el-form-item> 42 </el-form-item>
24 </el-col> 43 </el-col>
25 </el-form> 44 </el-form>
  45 + </div>
26 </el-row> 46 </el-row>
27 <span slot="footer" class="dialog-footer"> 47 <span slot="footer" class="dialog-footer">
28 - <el-button @click="visible = false">取 消</el-button>  
29 - <el-button type="primary" @click="dataFormSubmit()" v-if="!isDetail">确 定</el-button> 48 + <el-button @click="visible = false" size="medium" class="cancel-btn">取 消</el-button>
  49 + <el-button type="primary" @click="dataFormSubmit()" v-if="!isDetail" size="medium" class="confirm-btn">确 定</el-button>
30 </span> 50 </span>
31 </el-dialog> 51 </el-dialog>
32 </template> 52 </template>
@@ -73,6 +93,31 @@ @@ -73,6 +93,31 @@
73 this.deviceIdOptions = res.data 93 this.deviceIdOptions = res.data
74 }); 94 });
75 }, 95 },
  96 +
  97 + // 获取设备名称
  98 + getDeviceName(deviceId) {
  99 + if (!deviceId || !this.deviceIdOptions.length) return '--';
  100 + const device = this.deviceIdOptions.find(item => item.F_Id === deviceId);
  101 + return device ? device.F_DeviceName : '--';
  102 + },
  103 +
  104 + // 获取状态名称
  105 + getStatusName(status) {
  106 + const statusItem = this.statusOptions.find(item => item.id === status);
  107 + return statusItem ? statusItem.fullName : '未知';
  108 + },
  109 +
  110 + // 获取状态颜色
  111 + getStatusColor(status) {
  112 + const colorMap = {
  113 + 1: 'success', // 空闲 - 绿色
  114 + 2: 'info', // 停用 - 灰色
  115 + 3: 'warning', // 充电 - 橙色
  116 + 4: 'danger', // 损坏 - 红色
  117 + 5: 'primary' // 已租接 - 蓝色
  118 + }
  119 + return colorMap[status] || 'success'
  120 + },
76 goBack() { 121 goBack() {
77 this.$emit('refresh') 122 this.$emit('refresh')
78 }, 123 },
@@ -134,3 +179,178 @@ @@ -134,3 +179,178 @@
134 } 179 }
135 } 180 }
136 </script> 181 </script>
  182 +
  183 +<style lang="scss" scoped>
  184 +/* 编辑表单容器 */
  185 +.edit-form-container {
  186 + padding: 0;
  187 +}
  188 +
  189 +/* 只读信息展示区域 */
  190 +.readonly-info-section {
  191 + background: #f8f9fa;
  192 + border: 1px solid #e9ecef;
  193 + border-radius: 8px;
  194 + margin-bottom: 20px;
  195 + overflow: hidden;
  196 +}
  197 +
  198 +.section-title {
  199 + background: #409EFF;
  200 + color: white;
  201 + padding: 12px 16px;
  202 + display: flex;
  203 + align-items: center;
  204 + gap: 8px;
  205 + font-weight: 600;
  206 + font-size: 14px;
  207 +}
  208 +
  209 +.section-title i {
  210 + font-size: 16px;
  211 +}
  212 +
  213 +.info-grid {
  214 + padding: 20px;
  215 + display: grid;
  216 + grid-template-columns: 1fr 1fr;
  217 + gap: 16px;
  218 +}
  219 +
  220 +.info-item {
  221 + display: flex;
  222 + align-items: center;
  223 + gap: 8px;
  224 +}
  225 +
  226 +.info-label {
  227 + color: #666;
  228 + font-weight: 500;
  229 + min-width: 80px;
  230 +}
  231 +
  232 +.info-value {
  233 + color: #333;
  234 + font-weight: 600;
  235 +}
  236 +
  237 +.status-tag {
  238 + font-weight: 600;
  239 +}
  240 +
  241 +/* 状态修改区域 */
  242 +.edit-section {
  243 + background: #fff;
  244 + border: 1px solid #e9ecef;
  245 + border-radius: 8px;
  246 + overflow: hidden;
  247 +}
  248 +
  249 +.edit-section .section-title {
  250 + background: #67C23A;
  251 +}
  252 +
  253 +/* 状态表单样式 */
  254 +.status-form {
  255 + padding: 20px 20px 24px 20px;
  256 +}
  257 +
  258 +.status-form-item {
  259 + margin-bottom: 0 !important;
  260 +}
  261 +
  262 +.status-form-item .el-form-item__label {
  263 + padding-bottom: 12px;
  264 + font-weight: 600;
  265 + color: #333;
  266 +}
  267 +
  268 +/* 状态单选按钮组 */
  269 +.status-radio-group {
  270 + display: grid;
  271 + grid-template-columns: repeat(3, 1fr);
  272 + gap: 12px;
  273 + width: 100%;
  274 + margin-bottom: 8px;
  275 +}
  276 +
  277 +.status-radio {
  278 + margin: 0 !important;
  279 + display: flex;
  280 + align-items: center;
  281 + justify-content: center;
  282 + padding: 12px 8px;
  283 + border: 2px solid #e4e7ed;
  284 + border-radius: 8px;
  285 + transition: all 0.3s ease;
  286 + cursor: pointer;
  287 + background: #fff;
  288 +}
  289 +
  290 +.status-radio:hover {
  291 + border-color: #409EFF;
  292 + background: rgba(64, 158, 255, 0.05);
  293 +}
  294 +
  295 +.status-radio.is-checked {
  296 + border-color: #409EFF;
  297 + background: rgba(64, 158, 255, 0.1);
  298 + color: #409EFF;
  299 +}
  300 +
  301 +.radio-text {
  302 + font-weight: 500;
  303 + font-size: 14px;
  304 +}
  305 +
  306 +/* 对话框底部 */
  307 +.dialog-footer {
  308 + text-align: right;
  309 + padding: 20px 0 0 0;
  310 + border-top: 1px solid #e4e7ed;
  311 + margin-top: 20px;
  312 +}
  313 +
  314 +.dialog-footer .el-button {
  315 + padding: 12px 24px;
  316 + font-size: 14px;
  317 + font-weight: 500;
  318 + border-radius: 6px;
  319 + min-width: 80px;
  320 +}
  321 +
  322 +.cancel-btn {
  323 + background: #f5f5f5;
  324 + border-color: #d9d9d9;
  325 + color: #666;
  326 +}
  327 +
  328 +.cancel-btn:hover {
  329 + background: #e6f7ff;
  330 + border-color: #409EFF;
  331 + color: #409EFF;
  332 +}
  333 +
  334 +.confirm-btn {
  335 + background: #409EFF;
  336 + border-color: #409EFF;
  337 +}
  338 +
  339 +.confirm-btn:hover {
  340 + background: #66b1ff;
  341 + border-color: #66b1ff;
  342 +}
  343 +
  344 +/* 响应式设计 */
  345 +@media (max-width: 768px) {
  346 + .info-grid {
  347 + grid-template-columns: 1fr;
  348 + gap: 12px;
  349 + }
  350 +
  351 + .status-radio-group {
  352 + grid-template-columns: 1fr;
  353 + gap: 8px;
  354 + }
  355 +}
  356 +</style>
antis-ncc-admin/src/views/uavDeviceCell/index.vue
1 -<template> 1 +<template>
2 <div class="NCC-common-layout"> 2 <div class="NCC-common-layout">
3 <div class="NCC-common-layout-center"> 3 <div class="NCC-common-layout-center">
4 <el-row class="NCC-common-search-box" :gutter="16"> 4 <el-row class="NCC-common-search-box" :gutter="16">
@@ -51,11 +51,15 @@ @@ -51,11 +51,15 @@
51 <el-tag :type="getStatusColor(scope.row.status)">{{ scope.row.statusName }}</el-tag> 51 <el-tag :type="getStatusColor(scope.row.status)">{{ scope.row.statusName }}</el-tag>
52 </template> 52 </template>
53 </el-table-column> 53 </el-table-column>
54 - <el-table-column label="操作" fixed="right" width="100"> 54 + <el-table-column label="修改时间" prop="updateTime" align="left" width="180">
55 <template slot-scope="scope"> 55 <template slot-scope="scope">
56 - <el-button type="text" @click="addOrUpdateHandle(scope.row.id)">编辑</el-button>  
57 - <!-- <el-button type="text" @click="handleDel(scope.row.id)"  
58 - class="NCC-table-delBtn">删除</el-button> --> 56 + <span>{{ formatDateTime(scope.row.updateTime) }}</span>
  57 + </template>
  58 + </el-table-column>
  59 + <el-table-column label="操作" fixed="right" width="180">
  60 + <template slot-scope="scope">
  61 + <el-button type="text" @click="addOrUpdateHandle(scope.row.id)">调整状态</el-button>
  62 + <el-button type="text" @click="openAdjustTimeDialog(scope.row)" style="color: #E6A23C;">调整时间</el-button>
59 </template> 63 </template>
60 </el-table-column> 64 </el-table-column>
61 </NCC-table> 65 </NCC-table>
@@ -65,16 +69,22 @@ @@ -65,16 +69,22 @@
65 </div> 69 </div>
66 <NCC-Form v-if="formVisible" ref="NCCForm" @refresh="refresh" /> 70 <NCC-Form v-if="formVisible" ref="NCCForm" @refresh="refresh" />
67 <ExportBox v-if="exportBoxVisible" ref="ExportBox" @download="download" /> 71 <ExportBox v-if="exportBoxVisible" ref="ExportBox" @download="download" />
  72 +
  73 + <!-- 调整时间对话框 -->
  74 + <AdjustTimeDialog ref="adjustTimeDialog" @success="refresh" />
68 </div> 75 </div>
69 </template> 76 </template>
  77 +
70 <script> 78 <script>
71 import request from '@/utils/request' 79 import request from '@/utils/request'
72 import { getDictionaryDataSelector } from '@/api/systemData/dictionary' 80 import { getDictionaryDataSelector } from '@/api/systemData/dictionary'
73 import NCCForm from './Form' 81 import NCCForm from './Form'
74 import ExportBox from './ExportBox' 82 import ExportBox from './ExportBox'
  83 +import AdjustTimeDialog from './AdjustTimeDialog'
75 import { previewDataInterface } from '@/api/systemData/dataInterface' 84 import { previewDataInterface } from '@/api/systemData/dataInterface'
  85 +
76 export default { 86 export default {
77 - components: { NCCForm, ExportBox }, 87 + components: { NCCForm, ExportBox, AdjustTimeDialog },
78 data() { 88 data() {
79 return { 89 return {
80 query: { 90 query: {
@@ -102,6 +112,13 @@ export default { @@ -102,6 +112,13 @@ export default {
102 } 112 }
103 }, 113 },
104 computed: {}, 114 computed: {},
  115 + filters: {
  116 + dynamicText(value, options) {
  117 + if (!value || !options) return value;
  118 + const option = options.find(item => item.F_Id === value);
  119 + return option ? option.F_DeviceName : value;
  120 + }
  121 + },
105 created() { 122 created() {
106 this.initData() 123 this.initData()
107 this.getdeviceIdOptions(); 124 this.getdeviceIdOptions();
@@ -113,10 +130,37 @@ export default { @@ -113,10 +130,37 @@ export default {
113 2: 'info', // 停用 - 灰色 130 2: 'info', // 停用 - 灰色
114 3: 'warning', // 充电 - 橙色 131 3: 'warning', // 充电 - 橙色
115 4: 'danger', // 损坏 - 红色 132 4: 'danger', // 损坏 - 红色
116 - 5: 'primary' // 已租接 - 蓝色 133 + 5: 'primary', // 已租接 - 蓝色
  134 + 6: 'success' // 充电完成 - 绿色
117 } 135 }
118 return colorMap[status] || 'success' 136 return colorMap[status] || 'success'
119 }, 137 },
  138 +
  139 + // 格式化日期时间
  140 + formatDateTime(dateTime) {
  141 + if (!dateTime) {
  142 + return '--';
  143 + }
  144 +
  145 + // 如果是字符串,转换为Date对象
  146 + const date = new Date(dateTime);
  147 +
  148 + // 检查日期是否有效
  149 + if (isNaN(date.getTime())) {
  150 + return '--';
  151 + }
  152 +
  153 + // 格式化为 YYYY-MM-DD HH:mm:ss
  154 + const year = date.getFullYear();
  155 + const month = String(date.getMonth() + 1).padStart(2, '0');
  156 + const day = String(date.getDate()).padStart(2, '0');
  157 + const hours = String(date.getHours()).padStart(2, '0');
  158 + const minutes = String(date.getMinutes()).padStart(2, '0');
  159 + const seconds = String(date.getSeconds()).padStart(2, '0');
  160 +
  161 + return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
  162 + },
  163 +
120 getdeviceIdOptions() { 164 getdeviceIdOptions() {
121 previewDataInterface('702759008724845829').then(res => { 165 previewDataInterface('702759008724845829').then(res => {
122 this.deviceIdOptions = res.data 166 this.deviceIdOptions = res.data
@@ -146,31 +190,6 @@ export default { @@ -146,31 +190,6 @@ export default {
146 this.listLoading = false 190 this.listLoading = false
147 }) 191 })
148 }, 192 },
149 - handleDel(id) {  
150 - this.$confirm('此操作将永久删除该数据, 是否继续?', '提示', {  
151 - type: 'warning'  
152 - }).then(() => {  
153 - request({  
154 - url: `/api/Extend/UavDeviceCell/${id}`,  
155 - method: 'DELETE'  
156 - }).then(res => {  
157 - this.$message({  
158 - type: 'success',  
159 - message: res.msg,  
160 - onClose: () => {  
161 - this.initData()  
162 - }  
163 - });  
164 - })  
165 - }).catch(() => {  
166 - });  
167 - },  
168 - addOrUpdateHandle(id, isDetail) {  
169 - this.formVisible = true  
170 - this.$nextTick(() => {  
171 - this.$refs.NCCForm.init(id, isDetail)  
172 - })  
173 - },  
174 search() { 193 search() {
175 this.listQuery = { 194 this.listQuery = {
176 currentPage: 1, 195 currentPage: 1,
@@ -183,6 +202,7 @@ export default { @@ -183,6 +202,7 @@ export default {
183 refresh(isrRefresh) { 202 refresh(isrRefresh) {
184 this.formVisible = false 203 this.formVisible = false
185 if (isrRefresh) this.reset() 204 if (isrRefresh) this.reset()
  205 + this.initData() // 刷新列表数据
186 }, 206 },
187 reset() { 207 reset() {
188 for (let key in this.query) { 208 for (let key in this.query) {
@@ -195,7 +215,22 @@ export default { @@ -195,7 +215,22 @@ export default {
195 sidx: "", 215 sidx: "",
196 } 216 }
197 this.initData() 217 this.initData()
198 - } 218 + },
  219 + addOrUpdateHandle(id, isDetail) {
  220 + this.formVisible = true
  221 + this.$nextTick(() => {
  222 + this.$refs.NCCForm.init(id, isDetail)
  223 + })
  224 + },
  225 +
  226 + // 打开调整时间对话框
  227 + openAdjustTimeDialog(cell) {
  228 + this.$refs.adjustTimeDialog.open(cell);
  229 + },
199 } 230 }
200 } 231 }
201 -</script>  
202 \ No newline at end of file 232 \ No newline at end of file
  233 +</script>
  234 +
  235 +<style lang="scss" scoped>
  236 +/* 页面样式 */
  237 +</style>
antis-ncc-admin/src/views/uavDeviceCell/index_new.vue 0 → 100644
  1 +<template>
  2 + <div class="NCC-common-layout">
  3 + <div class="NCC-common-layout-content">
  4 + <div class="NCC-common-head">
  5 + <el-form :model="query" size="small" :inline="true" v-show="showSearch" label-width="68px">
  6 + <el-col :span="6">
  7 + <el-form-item label="设备ID" prop="deviceId">
  8 + <el-select v-model="query.deviceId" placeholder="请选择" clearable>
  9 + <el-option v-for="(item, index) in deviceIdOptions" :key="index" :label="item.F_DeviceName" :value="item.F_Id"></el-option>
  10 + </el-select>
  11 + </el-form-item>
  12 + </el-col>
  13 + <el-col :span="6">
  14 + <el-form-item label="格子编号" prop="cellCode">
  15 + <el-input v-model="query.cellCode" placeholder="请输入格子编号" clearable @keyup.enter.native="search()" />
  16 + </el-form-item>
  17 + </el-col>
  18 + <el-col :span="6">
  19 + <el-form-item>
  20 + <el-button type="primary" icon="el-icon-search" @click="search()">查询</el-button>
  21 + <el-button icon="el-icon-refresh-right" @click="reset()">重置</el-button>
  22 + </el-form-item>
  23 + </el-col>
  24 + </el-form>
  25 + <el-row :gutter="10" class="mb8">
  26 + <el-col :span="1.5">
  27 + <div>
  28 + <!-- <el-button type="primary" icon="el-icon-plus" @click="addOrUpdateHandle()">新增</el-button> -->
  29 + </div>
  30 + <div class="NCC-common-head-right">
  31 + <el-tooltip effect="dark" content="刷新" placement="top">
  32 + <el-button size="mini" circle icon="el-icon-refresh" @click="initData" />
  33 + </el-tooltip>
  34 + <el-tooltip effect="dark" content="显隐列" placement="top">
  35 + <el-button size="mini" circle icon="el-icon-s-operation" @click="showSearch = !showSearch" />
  36 + </el-tooltip>
  37 + </div>
  38 + </el-col>
  39 + </el-row>
  40 + <NCC-table v-loading="listLoading" :data="list" @selection-change="handleSelectionChange">
  41 + <el-table-column type="selection" width="55" align="center" />
  42 + <el-table-column label="设备名称" prop="deviceName" align="left" />
  43 + <el-table-column label="格子编号" prop="cellCode" align="left" />
  44 + <el-table-column label="状态" prop="status" align="left">
  45 + <template slot-scope="scope">
  46 + <el-tag :type="getStatusColor(scope.row.status)">{{ scope.row.statusName }}</el-tag>
  47 + </template>
  48 + </el-table-column>
  49 + <el-table-column label="修改时间" prop="updateTime" align="left" width="180">
  50 + <template slot-scope="scope">
  51 + <span>{{ formatDateTime(scope.row.updateTime) }}</span>
  52 + </template>
  53 + </el-table-column>
  54 + <el-table-column label="操作" fixed="right" width="180">
  55 + <template slot-scope="scope">
  56 + <el-button type="text" @click="addOrUpdateHandle(scope.row.id)">调整状态</el-button>
  57 + <el-button type="text" @click="openAdjustTimeDialog(scope.row)" style="color: #E6A23C;">调整时间</el-button>
  58 + </template>
  59 + </el-table-column>
  60 + </NCC-table>
  61 + <pagination :total="total" :page.sync="listQuery.currentPage" :limit.sync="listQuery.pageSize" @pagination="initData" />
  62 + </div>
  63 + </div>
  64 + <NCC-Form v-if="formVisible" ref="NCCForm" @refresh="refresh" />
  65 + <ExportBox v-if="exportBoxVisible" ref="ExportBox" @download="download" />
  66 +
  67 + <!-- 调整时间对话框 -->
  68 + <AdjustTimeDialog ref="adjustTimeDialog" @success="refresh" />
  69 + </div>
  70 +</template>
  71 +
  72 +<script>
  73 +import request from '@/utils/request'
  74 +import { getDictionaryDataSelector } from '@/api/systemData/dictionary'
  75 +import NCCForm from './Form'
  76 +import ExportBox from './ExportBox'
  77 +import AdjustTimeDialog from './AdjustTimeDialog'
  78 +import { previewDataInterface } from '@/api/systemData/dataInterface'
  79 +
  80 +export default {
  81 + components: { NCCForm, ExportBox, AdjustTimeDialog },
  82 + data() {
  83 + return {
  84 + query: {
  85 + deviceId: undefined,
  86 + cellCode: undefined,
  87 + },
  88 + list: [],
  89 + listLoading: true,
  90 + multipleSelection: [],
  91 + total: 0,
  92 + listQuery: {
  93 + currentPage: 1,
  94 + pageSize: 20,
  95 + sort: "desc",
  96 + sidx: "",
  97 + },
  98 + showSearch: true,
  99 + formVisible: false,
  100 + exportBoxVisible: false,
  101 + columnList: [
  102 + { prop: 'deviceId', label: '所属设备ID' },
  103 + { prop: 'cellCode', label: '格子编号' },
  104 + { prop: 'status', label: '状态' },
  105 + ],
  106 + deviceIdOptions: [],
  107 + statusOptions: [{ "fullName": "空闲", "id": "空闲" }, { "fullName": "充电", "id": "充电" }, { "fullName": "故障", "id": "故障" }],
  108 + }
  109 + },
  110 + computed: {},
  111 + created() {
  112 + this.initData()
  113 + this.getdeviceIdOptions();
  114 + },
  115 + methods: {
  116 + getStatusColor(status) {
  117 + const colorMap = {
  118 + 1: 'success', // 空闲 - 绿色
  119 + 2: 'info', // 停用 - 灰色
  120 + 3: 'warning', // 充电 - 橙色
  121 + 4: 'danger', // 损坏 - 红色
  122 + 5: 'primary', // 已租接 - 蓝色
  123 + 6: 'success' // 充电完成 - 绿色
  124 + }
  125 + return colorMap[status] || 'success'
  126 + },
  127 +
  128 + // 格式化日期时间
  129 + formatDateTime(dateTime) {
  130 + if (!dateTime) {
  131 + return '--';
  132 + }
  133 +
  134 + // 如果是字符串,转换为Date对象
  135 + const date = new Date(dateTime);
  136 +
  137 + // 检查日期是否有效
  138 + if (isNaN(date.getTime())) {
  139 + return '--';
  140 + }
  141 +
  142 + // 格式化为 YYYY-MM-DD HH:mm:ss
  143 + const year = date.getFullYear();
  144 + const month = String(date.getMonth() + 1).padStart(2, '0');
  145 + const day = String(date.getDate()).padStart(2, '0');
  146 + const hours = String(date.getHours()).padStart(2, '0');
  147 + const minutes = String(date.getMinutes()).padStart(2, '0');
  148 + const seconds = String(date.getSeconds()).padStart(2, '0');
  149 +
  150 + return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
  151 + },
  152 +
  153 + getdeviceIdOptions() {
  154 + previewDataInterface('702759008724845829').then(res => {
  155 + this.deviceIdOptions = res.data
  156 + });
  157 + },
  158 + initData() {
  159 + this.listLoading = true;
  160 + request({
  161 + url: '/api/Extend/UavDeviceCell/GetList',
  162 + method: 'get',
  163 + params: {
  164 + ...this.query,
  165 + ...this.listQuery
  166 + }
  167 + }).then(res => {
  168 + this.list = res.data.list;
  169 + this.total = res.data.pagination.total;
  170 + this.listLoading = false;
  171 + })
  172 + },
  173 + search() {
  174 + this.listQuery.currentPage = 1;
  175 + this.initData();
  176 + },
  177 + reset() {
  178 + this.query = {
  179 + deviceId: undefined,
  180 + cellCode: undefined,
  181 + }
  182 + this.listQuery = {
  183 + currentPage: 1,
  184 + pageSize: 20,
  185 + sort: "desc",
  186 + sidx: "",
  187 + }
  188 + this.initData()
  189 + },
  190 + handleSelectionChange(val) {
  191 + this.multipleSelection = val;
  192 + },
  193 + addOrUpdateHandle(id) {
  194 + this.formVisible = true;
  195 + this.$nextTick(() => {
  196 + this.$refs.NCCForm.init(id);
  197 + })
  198 + },
  199 + refresh() {
  200 + this.formVisible = false;
  201 + this.exportBoxVisible = false;
  202 + this.initData();
  203 + },
  204 + download() {
  205 + this.exportBoxVisible = false;
  206 + },
  207 +
  208 + // 打开调整时间对话框
  209 + openAdjustTimeDialog(cell) {
  210 + this.$refs.adjustTimeDialog.open(cell);
  211 + },
  212 + }
  213 +}
  214 +</script>
  215 +
  216 +<style lang="scss" scoped>
  217 +/* 页面样式 */
  218 +</style>
netcore/.DS_Store
No preview for this file type
netcore/src/.DS_Store
No preview for this file type
netcore/src/Application/.DS_Store
No preview for this file type
netcore/src/Application/NCC.API/.DS_Store
No preview for this file type
netcore/src/Modularity/Extend/NCC.Extend.Entitys/uavDeviceCell/Dto/AdjustUpdateTimeInput.cs 0 → 100644
  1 +using System;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.UavDeviceCell
  4 +{
  5 + /// <summary>
  6 + /// 调整格位修改时间输入参数
  7 + /// </summary>
  8 + public class AdjustUpdateTimeInput
  9 + {
  10 + /// <summary>
  11 + /// 格位ID
  12 + /// </summary>
  13 + public string CellId { get; set; }
  14 +
  15 + /// <summary>
  16 + /// 新的修改时间
  17 + /// </summary>
  18 + public DateTime NewUpdateTime { get; set; }
  19 + }
  20 +}
netcore/src/Modularity/Extend/NCC.Extend.Entitys/uavDeviceCell/Dto/UavDeviceCellInfoOutput.cs
1 using System; 1 using System;
2 using System.Collections.Generic; 2 using System.Collections.Generic;
  3 +using NCC.Common.Extension;
  4 +using NCC.Extend.Entitys.Enums;
3 5
4 namespace NCC.Extend.Entitys.Dto.UavDeviceCell 6 namespace NCC.Extend.Entitys.Dto.UavDeviceCell
5 { 7 {
@@ -26,7 +28,12 @@ namespace NCC.Extend.Entitys.Dto.UavDeviceCell @@ -26,7 +28,12 @@ namespace NCC.Extend.Entitys.Dto.UavDeviceCell
26 /// <summary> 28 /// <summary>
27 /// 格子状态(1空闲,2租赁中,3故障) 29 /// 格子状态(1空闲,2租赁中,3故障)
28 /// </summary> 30 /// </summary>
29 - public string status { get; set; } 31 + public int status { get; set; }
  32 +
  33 + /// <summary>
  34 + /// 格子状态中文描述
  35 + /// </summary>
  36 + public string statusName { get; set; }
30 37
31 /// <summary> 38 /// <summary>
32 /// 备注 39 /// 备注
netcore/src/Modularity/Extend/NCC.Extend.Entitys/uavDeviceCell/Dto/UavDeviceCellListOutput.cs
@@ -75,6 +75,15 @@ namespace NCC.Extend.Entitys.Dto.UavDeviceCell @@ -75,6 +75,15 @@ namespace NCC.Extend.Entitys.Dto.UavDeviceCell
75 /// </summary> 75 /// </summary>
76 public string uavCode { get; set; } 76 public string uavCode { get; set; }
77 77
  78 + /// <summary>
  79 + /// 创建时间
  80 + /// </summary>
  81 + public DateTime? createTime { get; set; }
  82 +
  83 + /// <summary>
  84 + /// 更新时间
  85 + /// </summary>
  86 + public DateTime? updateTime { get; set; }
78 87
79 } 88 }
80 } 89 }
netcore/src/Modularity/Extend/NCC.Extend/MqttPublisherService.cs
@@ -90,6 +90,28 @@ public class MqttPublisherService : IMqttPublisherService, ITransient, IDisposab @@ -90,6 +90,28 @@ public class MqttPublisherService : IMqttPublisherService, ITransient, IDisposab
90 private const int RETRY_INTERVAL_MINUTES = 5; // 重试间隔5分钟 90 private const int RETRY_INTERVAL_MINUTES = 5; // 重试间隔5分钟
91 91
92 /// <summary> 92 /// <summary>
  93 + /// 获取客户端ID,区分生产环境和开发环境
  94 + /// </summary>
  95 + /// <returns>客户端ID</returns>
  96 + private string GetClientId()
  97 + {
  98 + // 手动设置:true为生产环境,false为开发环境
  99 + bool isProduction = false; // 这里可以手动调整
  100 +
  101 + if (isProduction)
  102 + {
  103 + // 生产环境使用固定ID
  104 + return "server_publisher";
  105 + }
  106 + else
  107 + {
  108 + // 开发环境使用带机器名的ID,避免冲突
  109 + var machineName = Environment.MachineName;
  110 + return $"dev_publisher_{machineName}";
  111 + }
  112 + }
  113 +
  114 + /// <summary>
93 /// 构造函数:初始化客户端和配置、注册事件 115 /// 构造函数:初始化客户端和配置、注册事件
94 /// </summary> 116 /// </summary>
95 public MqttPublisherService(IServiceProvider serviceProvider, ISqlSugarClient db) 117 public MqttPublisherService(IServiceProvider serviceProvider, ISqlSugarClient db)
@@ -101,10 +123,11 @@ public class MqttPublisherService : IMqttPublisherService, ITransient, IDisposab @@ -101,10 +123,11 @@ public class MqttPublisherService : IMqttPublisherService, ITransient, IDisposab
101 _mqttClient = factory.CreateMqttClient(); 123 _mqttClient = factory.CreateMqttClient();
102 124
103 // 构建连接配置(MQTT 服务器地址、端口、用户名密码、客户端 ID) 125 // 构建连接配置(MQTT 服务器地址、端口、用户名密码、客户端 ID)
  126 + var clientId = GetClientId();
104 _mqttOptions = new MqttClientOptionsBuilder() 127 _mqttOptions = new MqttClientOptionsBuilder()
105 .WithTcpServer("mqtt.cqjiangzhichao.cn", 1883) // Broker 地址 128 .WithTcpServer("mqtt.cqjiangzhichao.cn", 1883) // Broker 地址
106 .WithCredentials("wrjservice", "P@ssw0rd") // 账号密码 129 .WithCredentials("wrjservice", "P@ssw0rd") // 账号密码
107 - .WithClientId("server_publisher") // 客户端 ID,必须唯一 130 + .WithClientId(clientId) // 客户端 ID,必须唯一
108 .WithKeepAlivePeriod(TimeSpan.FromSeconds(60)) // 保持连接心跳 131 .WithKeepAlivePeriod(TimeSpan.FromSeconds(60)) // 保持连接心跳
109 .WithCleanSession(false) // 保持会话状态 132 .WithCleanSession(false) // 保持会话状态
110 .Build(); 133 .Build();
@@ -112,7 +135,7 @@ public class MqttPublisherService : IMqttPublisherService, ITransient, IDisposab @@ -112,7 +135,7 @@ public class MqttPublisherService : IMqttPublisherService, ITransient, IDisposab
112 // 连接成功事件:订阅所有设备的响应主题(如 device/xxx/response) 135 // 连接成功事件:订阅所有设备的响应主题(如 device/xxx/response)
113 _mqttClient.UseConnectedHandler(async e => 136 _mqttClient.UseConnectedHandler(async e =>
114 { 137 {
115 - Log.Information("MQTT 已连接成功"); 138 + Log.Information($"MQTT 已连接成功,客户端ID: {_mqttOptions.ClientId}");
116 _subscriptionVerified = false; // 重置订阅验证状态 139 _subscriptionVerified = false; // 重置订阅验证状态
117 140
118 // 订阅所有设备的响应主题(+ 代表通配符) 141 // 订阅所有设备的响应主题(+ 代表通配符)
netcore/src/Modularity/Extend/NCC.Extend/NCC.Extend.xml
@@ -157,6 +157,12 @@ @@ -157,6 +157,12 @@
157 MQTT 连接参数配置 157 MQTT 连接参数配置
158 </summary> 158 </summary>
159 </member> 159 </member>
  160 + <member name="M:NCC.Extend.MqttPublisherService.GetClientId">
  161 + <summary>
  162 + 获取客户端ID,区分生产环境和开发环境
  163 + </summary>
  164 + <returns>客户端ID</returns>
  165 + </member>
160 <member name="M:NCC.Extend.MqttPublisherService.#ctor(System.IServiceProvider,SqlSugar.ISqlSugarClient)"> 166 <member name="M:NCC.Extend.MqttPublisherService.#ctor(System.IServiceProvider,SqlSugar.ISqlSugarClient)">
161 <summary> 167 <summary>
162 构造函数:初始化客户端和配置、注册事件 168 构造函数:初始化客户端和配置、注册事件
@@ -623,6 +629,13 @@ @@ -623,6 +629,13 @@
623 </summary> 629 </summary>
624 <returns></returns> 630 <returns></returns>
625 </member> 631 </member>
  632 + <member name="M:NCC.Extend.UavDeviceCell.UavDeviceCellService.AdjustUpdateTime(NCC.Extend.Entitys.Dto.UavDeviceCell.AdjustUpdateTimeInput)">
  633 + <summary>
  634 + 调整格位修改时间
  635 + </summary>
  636 + <param name="input">调整时间输入参数</param>
  637 + <returns></returns>
  638 + </member>
626 <member name="T:NCC.Extend.UavDeviceLog.UavDeviceLogService"> 639 <member name="T:NCC.Extend.UavDeviceLog.UavDeviceLogService">
627 <summary> 640 <summary>
628 设备串口日志服务 641 设备串口日志服务
netcore/src/Modularity/Extend/NCC.Extend/UavDeviceCellService.cs
@@ -82,6 +82,8 @@ namespace NCC.Extend.UavDeviceCell @@ -82,6 +82,8 @@ namespace NCC.Extend.UavDeviceCell
82 rfid1 = it.Rfid1, 82 rfid1 = it.Rfid1,
83 rfid2 = it.Rfid2, 83 rfid2 = it.Rfid2,
84 uavCode = it.UavCode, 84 uavCode = it.UavCode,
  85 + createTime = it.CreateTime,
  86 + updateTime = it.UpdateTime,
85 }).MergeTable().OrderBy(sidx + " " + input.sort).ToPagedListAsync(input.currentPage, input.pageSize); 87 }).MergeTable().OrderBy(sidx + " " + input.sort).ToPagedListAsync(input.currentPage, input.pageSize);
86 return PageResult<UavDeviceCellListOutput>.SqlSugarPageResult(data); 88 return PageResult<UavDeviceCellListOutput>.SqlSugarPageResult(data);
87 } 89 }
@@ -133,6 +135,53 @@ namespace NCC.Extend.UavDeviceCell @@ -133,6 +135,53 @@ namespace NCC.Extend.UavDeviceCell
133 var isOk = await _db.Deleteable<UavDeviceCellEntity>().Where(d => d.Id == id).ExecuteCommandAsync(); 135 var isOk = await _db.Deleteable<UavDeviceCellEntity>().Where(d => d.Id == id).ExecuteCommandAsync();
134 if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1002); 136 if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1002);
135 } 137 }
  138 + #endregion
  139 +
  140 + #region 调整格位修改时间
  141 + /// <summary>
  142 + /// 调整格位修改时间
  143 + /// </summary>
  144 + /// <param name="input">调整时间输入参数</param>
  145 + /// <returns></returns>
  146 + [HttpPost("AdjustUpdateTime")]
  147 + public async Task AdjustUpdateTime([FromBody] AdjustUpdateTimeInput input)
  148 + {
  149 + try
  150 + {
  151 + if (input == null)
  152 + {
  153 + throw NCCException.Oh(ErrorCode.COM1000, "输入参数不能为空");
  154 + }
  155 +
  156 + if (string.IsNullOrEmpty(input.CellId))
  157 + {
  158 + throw NCCException.Oh(ErrorCode.COM1000, "格位ID不能为空");
  159 + }
  160 +
  161 + var entity = await _db.Queryable<UavDeviceCellEntity>().FirstAsync(p => p.Id == input.CellId);
  162 + _ = entity ?? throw NCCException.Oh(ErrorCode.COM1005, "格位不存在");
  163 +
  164 + // 记录原始时间用于调试
  165 + var originalTime = entity.UpdateTime;
  166 +
  167 + // 更新修改时间
  168 + entity.UpdateTime = input.NewUpdateTime;
  169 +
  170 + var isOk = await _db.Updateable(entity)
  171 + .UpdateColumns(it => new { it.UpdateTime })
  172 + .ExecuteCommandAsync();
  173 +
  174 + if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1001, "更新失败");
  175 +
  176 + // 这里可以添加日志记录
  177 + // TODO: 添加操作日志记录
  178 + }
  179 + catch (Exception ex)
  180 + {
  181 + // 记录异常日志
  182 + throw NCCException.Oh(ErrorCode.COM1000, $"调整修改时间失败: {ex.Message}");
  183 + }
  184 + }
  185 + #endregion
136 } 186 }
137 - #endregion  
138 } 187 }