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 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 35 <input
16 36 type="text"
17 37 name="account"
18 38 v-model="loginForm.account"
19 39 ref="account"
20 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 52 <input
29 53 type="password"
30 54 name="password"
... ... @@ -32,22 +56,25 @@
32 56 ref="password"
33 57 tabindex="2"
34 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 65 <el-button
40 66 :loading="loading"
41 67 type="primary"
42   - round
43 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 78 </div>
52 79 </template>
53 80 <script>
... ... @@ -199,79 +226,293 @@ export default {
199 226 * {
200 227 margin: 0;
201 228 padding: 0;
202   - list-style: none;
203 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 252 width: 100%;
208 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 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 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 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 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 448 border: none;
266 449 outline: none;
267   - padding-left: 10px;
  450 + background: transparent;
268 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 518 </style>
... ...
antis-ncc-admin/src/views/uavAppUpdateInfo/index.vue
... ... @@ -85,9 +85,6 @@
85 85 <span>设备列表</span>
86 86 </div>
87 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 88 <el-dropdown @command="handleBatchSelect" trigger="click">
92 89 <el-button type="text" size="small">
93 90 <i class="el-icon-s-operation"></i>
... ... @@ -120,13 +117,7 @@
120 117 >
121 118 全部
122 119 </div>
123   - <div
124   - class="filter-chip"
125   - :class="{ active: deviceFilterStatus === 'online' }"
126   - @click="setDeviceFilter('online')"
127   - >
128   - 在线
129   - </div>
  120 +
130 121 <div
131 122 class="filter-chip"
132 123 :class="{ active: deviceFilterStatus === 'updated' }"
... ... @@ -146,18 +137,14 @@
146 137  
147 138 <!-- 设备列表 -->
148 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 142 <div class="device-content">
152 143 <div class="device-main">
153 144 <div class="device-name">{{ device.deviceName }}</div>
154 145 <div class="device-code">{{ device.deviceCode }}</div>
155 146 </div>
156 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 148 <div class="device-version" :class="{ 'updated': device.appVersion === currentUpdateInfo.version }">
162 149 {{ device.appVersion || '未知' }}
163 150 </div>
... ... @@ -186,7 +173,6 @@
186 173 <div class="bottom-actions">
187 174 <div class="action-info">
188 175 <span class="selected-count">已选择:{{ selectedDeviceCount }} 台设备</span>
189   - <span class="online-count">在线:{{ onlineDeviceCount }} 台</span>
190 176 <span class="outdated-count">待更新:{{ outdatedDeviceCount }} 台</span>
191 177 </div>
192 178 <div class="action-buttons">
... ... @@ -250,9 +236,6 @@
250 236 },
251 237 // 刷新设备状态相关
252 238 deviceStatusLoading: false,
253   - // 设备状态缓存
254   - deviceStatusCache: new Map(),
255   - lastStatusCheckTime: null,
256 239 columnList: [
257 240 { prop: 'version', label: 'App版本号' },
258 241 ],
... ... @@ -264,11 +247,7 @@
264 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 251 filtered = filtered.filter(device => device.appVersion === this.currentUpdateInfo.version);
273 252 } else if (this.deviceFilterStatus === 'outdated') {
274 253 filtered = filtered.filter(device => device.appVersion !== this.currentUpdateInfo.version);
... ... @@ -297,13 +276,7 @@
297 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 281 updatedDeviceCount() {
309 282 return this.deviceList.filter(device => device.appVersion === this.currentUpdateInfo.version).length;
... ... @@ -428,94 +401,16 @@
428 401 this.deviceList = res.data.list.map(device => ({
429 402 ...device,
430 403 selected: false,
431   - isOnline: false // 默认离线,等待缓存查询
  404 + isOnline: true // 默认在线,不再检测
432 405 }));
433 406  
434   - // 使用缓存查询设备在线状态
435   - this.checkDeviceStatusFromCache();
436   -
437 407 this.deviceListLoading = false;
438 408 }).catch(() => {
439 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 416 handleDeviceSearch() {
... ... @@ -536,9 +431,7 @@
536 431 // 全选当前页设备
537 432 selectAllDevices() {
538 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 447 // 全选所有待更新设备
555 448 selectAllOutdatedDevices() {
556 449 this.deviceList.forEach(device => {
557   - if (device.isOnline && device.appVersion !== this.currentUpdateInfo.version) {
  450 + if (device.appVersion !== this.currentUpdateInfo.version) {
558 451 device.selected = true;
559 452 }
560 453 });
... ... @@ -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 484 confirmPushUpdate() {
... ... @@ -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 547 </script>
... ... @@ -963,15 +827,7 @@
963 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 832 .device-checkbox {
977 833 width: 100%;
... ... @@ -1017,49 +873,7 @@
1017 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 878 .device-version {
1065 879 font-size: 11px;
... ... @@ -1127,10 +941,7 @@
1127 941 font-weight: 500;
1128 942 }
1129 943  
1130   -.online-count {
1131   - color: #10b981;
1132   - font-weight: 500;
1133   -}
  944 +
1134 945  
1135 946 .outdated-count {
1136 947 color: #f59e0b;
... ...
antis-ncc-admin/src/views/uavDevice/index.vue
... ... @@ -29,35 +29,6 @@
29 29 <div class="NCC-common-layout-main NCC-flex-main">
30 30 <div class="NCC-common-head">
31 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 32 </div>
62 33 <div class="NCC-common-head-right">
63 34 <el-tooltip effect="dark" content="刷新" placement="top">
... ... @@ -74,14 +45,17 @@
74 45 <div class="cabinet-top">
75 46 <div class="device-name-row">
76 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 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 51 </div>
81 52 </div>
82 53 <div class="device-info-row">
83 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 59 </div>
86 60 <div class="device-info-row">
87 61 <span>代理商:{{ item.belongUserName }}</span>
... ... @@ -196,37 +170,13 @@ export default {
196 170 showQr: false,
197 171 qrImgUrl: '',// 二维码图片地址
198 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 177 created() {
227 178 this.initData()
228 179 this.getsiteIdOptions();
229   - this.getDeviceStats(); // 获取设备统计数据
230 180 },
231 181 methods: {
232 182 getStatusColor(status) {
... ... @@ -362,7 +312,6 @@ export default {
362 312 sidx: "",
363 313 }
364 314 this.initData()
365   - this.getDeviceStats() // 更新统计数据
366 315 },
367 316 refresh(isrRefresh) {
368 317 this.formVisible = false
... ... @@ -380,286 +329,63 @@ export default {
380 329 }
381 330  
382 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 379 </script>
513 380  
514 381 <style>
515   -/* 设备统计信息样式 */
  382 +/* 头部样式 */
516 383 .head-left {
517 384 display: flex;
518 385 align-items: center;
519 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 389 .qr-popup {
664 390 position: fixed;
665 391 left: 0;
... ... @@ -728,7 +454,7 @@ export default {
728 454 margin-bottom: 8px;
729 455 }
730 456  
731   -.cabinet-bottom {}
  457 +
732 458  
733 459 .cabinet-middle {
734 460 /* display: grid;
... ... @@ -756,8 +482,24 @@ export default {
756 482 .device-status {
757 483 display: flex;
758 484 align-items: center;
759   - gap: 4px;
  485 + gap: 6px;
760 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 505 .device-name-row {
... ... @@ -774,18 +516,60 @@ export default {
774 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 536 .status-dot {
778   - width: 8px;
779   - height: 8px;
  537 + width: 10px;
  538 + height: 10px;
780 539 border-radius: 50%;
781 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 545 .status-dot.online {
785 546 background-color: #67C23A;
  547 + box-shadow: 0 0 6px rgba(103, 194, 58, 0.6);
786 548 }
787 549  
788 550 .status-dot.offline {
789 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 575 </style>
792 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 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 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 41 </el-radio-group>
23 42 </el-form-item>
24 43 </el-col>
25 44 </el-form>
  45 + </div>
26 46 </el-row>
27 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 50 </span>
31 51 </el-dialog>
32 52 </template>
... ... @@ -73,6 +93,31 @@
73 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 121 goBack() {
77 122 this.$emit('refresh')
78 123 },
... ... @@ -134,3 +179,178 @@
134 179 }
135 180 }
136 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 2 <div class="NCC-common-layout">
3 3 <div class="NCC-common-layout-center">
4 4 <el-row class="NCC-common-search-box" :gutter="16">
... ... @@ -51,11 +51,15 @@
51 51 <el-tag :type="getStatusColor(scope.row.status)">{{ scope.row.statusName }}</el-tag>
52 52 </template>
53 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 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 63 </template>
60 64 </el-table-column>
61 65 </NCC-table>
... ... @@ -65,16 +69,22 @@
65 69 </div>
66 70 <NCC-Form v-if="formVisible" ref="NCCForm" @refresh="refresh" />
67 71 <ExportBox v-if="exportBoxVisible" ref="ExportBox" @download="download" />
  72 +
  73 + <!-- 调整时间对话框 -->
  74 + <AdjustTimeDialog ref="adjustTimeDialog" @success="refresh" />
68 75 </div>
69 76 </template>
  77 +
70 78 <script>
71 79 import request from '@/utils/request'
72 80 import { getDictionaryDataSelector } from '@/api/systemData/dictionary'
73 81 import NCCForm from './Form'
74 82 import ExportBox from './ExportBox'
  83 +import AdjustTimeDialog from './AdjustTimeDialog'
75 84 import { previewDataInterface } from '@/api/systemData/dataInterface'
  85 +
76 86 export default {
77   - components: { NCCForm, ExportBox },
  87 + components: { NCCForm, ExportBox, AdjustTimeDialog },
78 88 data() {
79 89 return {
80 90 query: {
... ... @@ -102,6 +112,13 @@ export default {
102 112 }
103 113 },
104 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 122 created() {
106 123 this.initData()
107 124 this.getdeviceIdOptions();
... ... @@ -113,10 +130,37 @@ export default {
113 130 2: 'info', // 停用 - 灰色
114 131 3: 'warning', // 充电 - 橙色
115 132 4: 'danger', // 损坏 - 红色
116   - 5: 'primary' // 已租接 - 蓝色
  133 + 5: 'primary', // 已租接 - 蓝色
  134 + 6: 'success' // 充电完成 - 绿色
117 135 }
118 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 164 getdeviceIdOptions() {
121 165 previewDataInterface('702759008724845829').then(res => {
122 166 this.deviceIdOptions = res.data
... ... @@ -146,31 +190,6 @@ export default {
146 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 193 search() {
175 194 this.listQuery = {
176 195 currentPage: 1,
... ... @@ -183,6 +202,7 @@ export default {
183 202 refresh(isrRefresh) {
184 203 this.formVisible = false
185 204 if (isrRefresh) this.reset()
  205 + this.initData() // 刷新列表数据
186 206 },
187 207 reset() {
188 208 for (let key in this.query) {
... ... @@ -195,7 +215,22 @@ export default {
195 215 sidx: "",
196 216 }
197 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 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 1 using System;
2 2 using System.Collections.Generic;
  3 +using NCC.Common.Extension;
  4 +using NCC.Extend.Entitys.Enums;
3 5  
4 6 namespace NCC.Extend.Entitys.Dto.UavDeviceCell
5 7 {
... ... @@ -26,7 +28,12 @@ namespace NCC.Extend.Entitys.Dto.UavDeviceCell
26 28 /// <summary>
27 29 /// 格子状态(1空闲,2租赁中,3故障)
28 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 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 75 /// </summary>
76 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 90 private const int RETRY_INTERVAL_MINUTES = 5; // 重试间隔5分钟
91 91  
92 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 116 /// </summary>
95 117 public MqttPublisherService(IServiceProvider serviceProvider, ISqlSugarClient db)
... ... @@ -101,10 +123,11 @@ public class MqttPublisherService : IMqttPublisherService, ITransient, IDisposab
101 123 _mqttClient = factory.CreateMqttClient();
102 124  
103 125 // 构建连接配置(MQTT 服务器地址、端口、用户名密码、客户端 ID)
  126 + var clientId = GetClientId();
104 127 _mqttOptions = new MqttClientOptionsBuilder()
105 128 .WithTcpServer("mqtt.cqjiangzhichao.cn", 1883) // Broker 地址
106 129 .WithCredentials("wrjservice", "P@ssw0rd") // 账号密码
107   - .WithClientId("server_publisher") // 客户端 ID,必须唯一
  130 + .WithClientId(clientId) // 客户端 ID,必须唯一
108 131 .WithKeepAlivePeriod(TimeSpan.FromSeconds(60)) // 保持连接心跳
109 132 .WithCleanSession(false) // 保持会话状态
110 133 .Build();
... ... @@ -112,7 +135,7 @@ public class MqttPublisherService : IMqttPublisherService, ITransient, IDisposab
112 135 // 连接成功事件:订阅所有设备的响应主题(如 device/xxx/response)
113 136 _mqttClient.UseConnectedHandler(async e =>
114 137 {
115   - Log.Information("MQTT 已连接成功");
  138 + Log.Information($"MQTT 已连接成功,客户端ID: {_mqttOptions.ClientId}");
116 139 _subscriptionVerified = false; // 重置订阅验证状态
117 140  
118 141 // 订阅所有设备的响应主题(+ 代表通配符)
... ...
netcore/src/Modularity/Extend/NCC.Extend/NCC.Extend.xml
... ... @@ -157,6 +157,12 @@
157 157 MQTT 连接参数配置
158 158 </summary>
159 159 </member>
  160 + <member name="M:NCC.Extend.MqttPublisherService.GetClientId">
  161 + <summary>
  162 + 获取客户端ID,区分生产环境和开发环境
  163 + </summary>
  164 + <returns>客户端ID</returns>
  165 + </member>
160 166 <member name="M:NCC.Extend.MqttPublisherService.#ctor(System.IServiceProvider,SqlSugar.ISqlSugarClient)">
161 167 <summary>
162 168 构造函数:初始化客户端和配置、注册事件
... ... @@ -623,6 +629,13 @@
623 629 </summary>
624 630 <returns></returns>
625 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 639 <member name="T:NCC.Extend.UavDeviceLog.UavDeviceLogService">
627 640 <summary>
628 641 设备串口日志服务
... ...
netcore/src/Modularity/Extend/NCC.Extend/UavDeviceCellService.cs
... ... @@ -82,6 +82,8 @@ namespace NCC.Extend.UavDeviceCell
82 82 rfid1 = it.Rfid1,
83 83 rfid2 = it.Rfid2,
84 84 uavCode = it.UavCode,
  85 + createTime = it.CreateTime,
  86 + updateTime = it.UpdateTime,
85 87 }).MergeTable().OrderBy(sidx + " " + input.sort).ToPagedListAsync(input.currentPage, input.pageSize);
86 88 return PageResult<UavDeviceCellListOutput>.SqlSugarPageResult(data);
87 89 }
... ... @@ -133,6 +135,53 @@ namespace NCC.Extend.UavDeviceCell
133 135 var isOk = await _db.Deleteable<UavDeviceCellEntity>().Where(d => d.Id == id).ExecuteCommandAsync();
134 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 }
... ...