合并
This commit is contained in:
		| @ -6,9 +6,9 @@ VITE_APP_ENV = 'development' | ||||
|  | ||||
| # 开发环境 | ||||
| # 李陈杰 209 | ||||
| VITE_APP_BASE_API = 'http://192.168.110.209:8899' | ||||
| # VITE_APP_BASE_API = 'http://192.168.110.209:8899' | ||||
| # 曾涛 | ||||
| # VITE_APP_BASE_API = 'http://192.168.110.180:8899' | ||||
| VITE_APP_BASE_API = 'http://192.168.110.180:8899' | ||||
| # 罗成 | ||||
| # VITE_APP_BASE_API = 'http://192.168.110.188:8899' | ||||
| # 朱银 | ||||
|  | ||||
							
								
								
									
										103
									
								
								src/api/equipment/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								src/api/equipment/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,103 @@ | ||||
| import request from '@/utils/request'; | ||||
| import { AxiosPromise } from 'axios'; | ||||
| import { EquipmentVO, EquipmentForm, EquipmentQuery } from '@/api/equipment/types'; | ||||
|  | ||||
| /** | ||||
|  * 查询GPS设备详细列表 | ||||
|  * @param query | ||||
|  * @returns {*} | ||||
|  */ | ||||
|  | ||||
| export const listEquipment = (query?: EquipmentQuery): AxiosPromise<EquipmentVO[]> => { | ||||
|   return request({ | ||||
|     url: '/gps/equipment/list', | ||||
|     method: 'get', | ||||
|     params: query | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * 查询GPS设备详细详细 | ||||
|  * @param id | ||||
|  */ | ||||
| export const getEquipment = (id: string | number): AxiosPromise<EquipmentVO> => { | ||||
|   return request({ | ||||
|     url: '/gps/equipment/' + id, | ||||
|     method: 'get' | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * 新增GPS设备详细 | ||||
|  * @param data | ||||
|  */ | ||||
| export const addEquipment = (data: EquipmentForm) => { | ||||
|   return request({ | ||||
|     url: '/gps/equipment', | ||||
|     method: 'post', | ||||
|     data: data | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * 修改GPS设备详细 | ||||
|  * @param data | ||||
|  */ | ||||
| export const updateEquipment = (data: EquipmentForm) => { | ||||
|   return request({ | ||||
|     url: '/gps/equipment', | ||||
|     method: 'put', | ||||
|     data: data | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * 删除GPS设备详细 | ||||
|  * @param id | ||||
|  */ | ||||
| export const delEquipment = (id: string | number | Array<string | number>) => { | ||||
|   return request({ | ||||
|     url: '/gps/equipment/' + id, | ||||
|     method: 'delete' | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| export const bindUser = (data) => { | ||||
|   return request({ | ||||
|     url: '/gps/equipment/bindManmachine', | ||||
|     method: 'post', | ||||
|     data: data | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| export const getUserId = (projectId) => { | ||||
|   return request({ | ||||
|     url: '/gps/equipment/userList', | ||||
|     method: 'get', | ||||
|  | ||||
|     params: { projectId } | ||||
|   }); | ||||
| }; | ||||
| export const gethistroyUser = (data) => { | ||||
|   return request({ | ||||
|     url: '/gps/equipment/getUserList', | ||||
|     method: 'get', | ||||
|     params: data | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| // 解除绑定接口定义(确保使用POST方法并正确传递data) | ||||
| export function getRemoveBind(data: { id: number; clientId: string }) { | ||||
|   return request({ | ||||
|     url: '/gps/equipment/unbindManmachine', | ||||
|     method: 'post', | ||||
|     data: data | ||||
|   }); | ||||
| } | ||||
|  | ||||
| export function getProjectId() { | ||||
|   return request({ | ||||
|     url: 'gps/equipment/getProjectList', | ||||
|     method: 'get' | ||||
|   }); | ||||
| } | ||||
							
								
								
									
										169
									
								
								src/api/equipment/types.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										169
									
								
								src/api/equipment/types.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,169 @@ | ||||
| export interface EquipmentVO { | ||||
|   /** | ||||
|    * | ||||
|    */ | ||||
|   id: string | number; | ||||
|  | ||||
|   /** | ||||
|    * 项目ID | ||||
|    */ | ||||
|   projectId: string | number; | ||||
|  | ||||
|   /** | ||||
|    * 用户id | ||||
|    */ | ||||
|   userId: string | number; | ||||
|  | ||||
|   /** | ||||
|    * 设备标识 | ||||
|    */ | ||||
|   clientId: string | number; | ||||
|  | ||||
|   /** | ||||
|    * 设备名称 | ||||
|    */ | ||||
|   deviceName: string; | ||||
|  | ||||
|   /** | ||||
|    * 是否使用UDP协议:0=否,1=是 | ||||
|    */ | ||||
|   udp: number; | ||||
|  | ||||
|   /** | ||||
|    * 远程连接地址(IP:端口) | ||||
|    */ | ||||
|   remoteAddressStr: string; | ||||
|  | ||||
|   /** | ||||
|    * 连接创建时间 | ||||
|    */ | ||||
|   creationTime: number; | ||||
|  | ||||
|   /** | ||||
|    * 最后活动时间 | ||||
|    */ | ||||
|   lastAccessedTime: number; | ||||
|  | ||||
|   /** | ||||
|    * 是否已注册:0=未注册,1=已注册 | ||||
|    */ | ||||
|   registered: number; | ||||
|  | ||||
|   /** | ||||
|    * 备注 | ||||
|    */ | ||||
|   remark: string; | ||||
| } | ||||
|  | ||||
| export interface EquipmentForm extends BaseEntity { | ||||
|   /** | ||||
|    * | ||||
|    */ | ||||
|   id?: string | number; | ||||
|  | ||||
|   /** | ||||
|    * 项目ID | ||||
|    */ | ||||
|   projectId?: string | number; | ||||
|  | ||||
|   /** | ||||
|    * 用户id | ||||
|    */ | ||||
|   userId?: string | number; | ||||
|  | ||||
|   /** | ||||
|    * 设备标识 | ||||
|    */ | ||||
|   clientId?: string | number; | ||||
|  | ||||
|   /** | ||||
|    * 设备名称 | ||||
|    */ | ||||
|   deviceName?: string; | ||||
|  | ||||
|   /** | ||||
|    * 是否使用UDP协议:0=否,1=是 | ||||
|    */ | ||||
|   udp?: number; | ||||
|  | ||||
|   /** | ||||
|    * 远程连接地址(IP:端口) | ||||
|    */ | ||||
|   remoteAddressStr?: string; | ||||
|  | ||||
|   /** | ||||
|    * 连接创建时间 | ||||
|    */ | ||||
|   creationTime?: number; | ||||
|  | ||||
|   /** | ||||
|    * 最后活动时间 | ||||
|    */ | ||||
|   lastAccessedTime?: number; | ||||
|  | ||||
|   /** | ||||
|    * 是否已注册:0=未注册,1=已注册 | ||||
|    */ | ||||
|   registered?: number; | ||||
|  | ||||
|   /** | ||||
|    * 备注 | ||||
|    */ | ||||
|   remark?: string; | ||||
| } | ||||
|  | ||||
| export interface EquipmentQuery extends PageQuery { | ||||
|   /** | ||||
|    * 项目ID | ||||
|    */ | ||||
|   projectId?: string | number; | ||||
|   /** | ||||
|    * 是否绑定 | ||||
|    */ | ||||
|   type?: string | number; | ||||
|  | ||||
|   /** | ||||
|    * 用户id | ||||
|    */ | ||||
|   userId?: string | number; | ||||
|  | ||||
|   /** | ||||
|    * 设备标识 | ||||
|    */ | ||||
|   clientId?: string | number; | ||||
|  | ||||
|   /** | ||||
|    * 设备名称 | ||||
|    */ | ||||
|   deviceName?: string; | ||||
|  | ||||
|   /** | ||||
|    * 是否使用UDP协议:0=否,1=是 | ||||
|    */ | ||||
|   udp?: number; | ||||
|  | ||||
|   /** | ||||
|    * 远程连接地址(IP:端口) | ||||
|    */ | ||||
|   remoteAddressStr?: string; | ||||
|  | ||||
|   /** | ||||
|    * 连接创建时间 | ||||
|    */ | ||||
|   creationTime?: number; | ||||
|  | ||||
|   /** | ||||
|    * 最后活动时间 | ||||
|    */ | ||||
|   lastAccessedTime?: number; | ||||
|  | ||||
|   /** | ||||
|    * 是否已注册:0=未注册,1=已注册 | ||||
|    */ | ||||
|   registered?: number; | ||||
|  | ||||
|   /** | ||||
|    * 日期范围参数 | ||||
|    */ | ||||
|   params?: any; | ||||
| } | ||||
| @ -203,3 +203,31 @@ export const importConstructionUserInfo = (file: string) => { | ||||
|     data: { file } | ||||
|   }); | ||||
| }; | ||||
|  | ||||
|  | ||||
| // 获取项目列表 | ||||
| export const ProjectList = (query) => { | ||||
|   return request({ | ||||
|     url: '/contractor/constructionUser/projectList', | ||||
|     method: 'get', | ||||
|     params: query | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| // 获取班组列表 | ||||
| export const TeamList = (query) => { | ||||
|   return request({     | ||||
|     url: '/contractor/constructionUser/teamList', | ||||
|     method: 'get', | ||||
|     params: query | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| // 班组分配 | ||||
| export const TeamDistribution = (data) => { | ||||
|   return request({ | ||||
|     url: '/contractor/constructionUser/addTeam', | ||||
|     method: 'post', | ||||
|     data: data | ||||
|   }); | ||||
| }; | ||||
| @ -182,6 +182,8 @@ export interface ConstructionUserVO { | ||||
|    * 创建时间 | ||||
|    */ | ||||
|   createTime: string; | ||||
|  | ||||
|   sysUserId: string | number; | ||||
| } | ||||
| export interface skipType { | ||||
|   /** | ||||
|  | ||||
| @ -72,3 +72,12 @@ export const delProjectTeam = (id: string | number | Array<string | number>) => | ||||
|     method: 'delete' | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| // 获取项目得打卡范围 | ||||
| export const getProjectTeamClockIn = (params) => { | ||||
|   return request({ | ||||
|     url: '/project/projectTeam/rangeList', | ||||
|     method: 'get', | ||||
|     params | ||||
|   }); | ||||
| }; | ||||
| @ -55,6 +55,11 @@ export interface ProjectTeamForm extends BaseEntity { | ||||
|    * 备注 | ||||
|    */ | ||||
|   remark?: string; | ||||
|    | ||||
|   /** | ||||
|    * 创建时间 | ||||
|    */ | ||||
|   punchRangeList?: []; | ||||
| } | ||||
|  | ||||
| export interface ProjectTeamQuery extends PageQuery { | ||||
|  | ||||
| @ -120,6 +120,9 @@ const createEarth = () => { | ||||
|       } | ||||
|     } | ||||
|     loadBaseMap(earth.viewer) | ||||
|     YJ.Global.CesiumContainer(window.Earth1, { | ||||
|       compass: false, //罗盘 | ||||
|     }); | ||||
|     // YJ.Global.flyTo(earth, view); | ||||
|     // YJ.Global.setDefaultView(earth.viewer, view) | ||||
|   }) | ||||
|  | ||||
							
								
								
									
										314
									
								
								src/views/equipment/equipmentGPS.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										314
									
								
								src/views/equipment/equipmentGPS.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,314 @@ | ||||
| <template> | ||||
|   <div class="p5" style="width: 100%;height: calc(100vh - 84px);" v-loading="loading"> | ||||
|     <div id="TrajectoryEarth" style="width: 100%;height: 100%;"></div> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script setup name="equipmentGPS"> | ||||
| import { ref, onMounted, onUnmounted } from 'vue'; | ||||
|  | ||||
| const loading = ref(true); | ||||
| let earthInstance = null; | ||||
| let data = [ | ||||
|   { | ||||
|     "lng": 106.45637808828741, | ||||
|     "lat": 29.5597535878972, | ||||
|     "alt": 0 | ||||
|   }, | ||||
|   { | ||||
|     "lng": 106.45971817314378, | ||||
|     "lat": 29.54708008996366, | ||||
|     "alt": 0 | ||||
|   }, | ||||
|   { | ||||
|     "lng": 106.4594902314301, | ||||
|     "lat": 29.53682043192008, | ||||
|     "alt": 0 | ||||
|   }, | ||||
|   { | ||||
|     "lng": 106.47398277208025, | ||||
|     "lat": 29.5448688679258, | ||||
|     "alt": 0 | ||||
|   }, | ||||
|   { | ||||
|     "lng": 106.4793889453858, | ||||
|     "lat": 29.549294608101395, | ||||
|     "alt": 0.0016450895529057888 | ||||
|   }, | ||||
|   { | ||||
|     "lng": 106.49100748408087, | ||||
|     "lat": 29.551808876409023, | ||||
|     "alt": 0 | ||||
|   }, | ||||
|   { | ||||
|     "lng": 106.5040076285079, | ||||
|     "lat": 29.55321574288158, | ||||
|     "alt": 0 | ||||
|   }, | ||||
|   { | ||||
|     "lng": 106.51355510567937, | ||||
|     "lat": 29.546776414794298, | ||||
|     "alt": 0 | ||||
|   }, | ||||
|   { | ||||
|     "lng": 106.51838453732344, | ||||
|     "lat": 29.537721996213506, | ||||
|     "alt": 0 | ||||
|   }, | ||||
|   { | ||||
|     "lng": 106.51457768703192, | ||||
|     "lat": 29.524855377287736, | ||||
|     "alt": 0.0009144105438296915 | ||||
|   }, | ||||
|   { | ||||
|     "lng": 106.52872030672225, | ||||
|     "lat": 29.53289655789934, | ||||
|     "alt": 0.0005979679117487334 | ||||
|   }, | ||||
|   { | ||||
|     "lng": 106.53493249730421, | ||||
|     "lat": 29.541341118458874, | ||||
|     "alt": 0 | ||||
|   }, | ||||
|   { | ||||
|     "lng": 106.53390022310705, | ||||
|     "lat": 29.54848036964581, | ||||
|     "alt": 0 | ||||
|   }, | ||||
|   { | ||||
|     "lng": 106.53471751083796, | ||||
|     "lat": 29.55380771856629, | ||||
|     "alt": 0 | ||||
|   }, | ||||
|   { | ||||
|     "lng": 106.53518023558718, | ||||
|     "lat": 29.560247052020156, | ||||
|     "alt": 0 | ||||
|   }, | ||||
|   { | ||||
|     "lng": 106.52470634506619, | ||||
|     "lat": 29.560148336926677, | ||||
|     "alt": 0 | ||||
|   }, | ||||
|   { | ||||
|     "lng": 106.52527983360243, | ||||
|     "lat": 29.55240114606003, | ||||
|     "alt": 0 | ||||
|   }, | ||||
|   { | ||||
|     "lng": 106.51837614087053, | ||||
|     "lat": 29.557734494325807, | ||||
|     "alt": 0.0021893863172707047 | ||||
|   }, | ||||
|   { | ||||
|     "lng": 106.51573052195917, | ||||
|     "lat": 29.564878142363643, | ||||
|     "alt": 0 | ||||
|   }, | ||||
|   { | ||||
|     "lng": 106.50951515081469, | ||||
|     "lat": 29.556427329943944, | ||||
|     "alt": 0 | ||||
|   }, | ||||
|   { | ||||
|     "lng": 106.5076746620389, | ||||
|     "lat": 29.565884717127823, | ||||
|     "alt": 0 | ||||
|   }, | ||||
|   { | ||||
|     "lng": 106.50341642164544, | ||||
|     "lat": 29.558842113740727, | ||||
|     "alt": 0 | ||||
|   }, | ||||
|   { | ||||
|     "lng": 106.50065453145776, | ||||
|     "lat": 29.567897073636868, | ||||
|     "alt": 0 | ||||
|   }, | ||||
|   { | ||||
|     "lng": 106.49328990493458, | ||||
|     "lat": 29.558741275828908, | ||||
|     "alt": 0 | ||||
|   }, | ||||
|   { | ||||
|     "lng": 106.49363360765534, | ||||
|     "lat": 29.57333026493932, | ||||
|     "alt": 0 | ||||
|   }, | ||||
|   { | ||||
|     "lng": 106.51905977395505, | ||||
|     "lat": 29.57828423907017, | ||||
|     "alt": 0 | ||||
|   }, | ||||
|   { | ||||
|     "lng": 106.51145952743101, | ||||
|     "lat": 29.584527208426245, | ||||
|     "alt": 0 | ||||
|   }, | ||||
|   { | ||||
|     "lng": 106.49372239119027, | ||||
|     "lat": 29.58774998459616, | ||||
|     "alt": 0 | ||||
|   }, | ||||
|   { | ||||
|     "lng": 106.48214171543982, | ||||
|     "lat": 29.580866974898736, | ||||
|     "alt": 0.006089177027688381 | ||||
|   }, | ||||
|   { | ||||
|     "lng": 106.48801515807557, | ||||
|     "lat": 29.57049465877845, | ||||
|     "alt": 0 | ||||
|   }, | ||||
|   { | ||||
|     "lng": 106.47638485150803, | ||||
|     "lat": 29.571501851940585, | ||||
|     "alt": 0.0011061005102942808 | ||||
|   }, | ||||
|   { | ||||
|     "lng": 106.47903303165843, | ||||
|     "lat": 29.562542838689904, | ||||
|     "alt": 0.0011137835517156711 | ||||
|   }, | ||||
|   { | ||||
|     "lng": 106.4645244823306, | ||||
|     "lat": 29.56868284644567, | ||||
|     "alt": 0.00008251706068442191 | ||||
|   }, | ||||
|   { | ||||
|     "lng": 106.47108853087332, | ||||
|     "lat": 29.555194220499004, | ||||
|     "alt": 0 | ||||
|   }, | ||||
| ] | ||||
|  | ||||
|  | ||||
| // 创建地球 | ||||
| const createEarth = () => { | ||||
|   if (!window.YJ) { | ||||
|     ElMessage.error('YJ库未加载,请检查依赖'); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   window.YJ.on({ | ||||
|     ws: true, | ||||
|     // host: getIP(), // 资源所在服务器地址 | ||||
|     // username: this.loginForm.username, // 用户名 | ||||
|     // password: md5pass, // 密码 | ||||
|   }).then((res) => { | ||||
|     loading.value = false; | ||||
|     // 创建地球实例 | ||||
|     earthInstance = new YJ.YJEarth('TrajectoryEarth'); | ||||
|     window.Earth3 = earthInstance; | ||||
|  | ||||
|     // 开启右键和左键点击事件 | ||||
|     YJ.Global.openRightClick(window.Earth3); | ||||
|     YJ.Global.openLeftClick(window.Earth3); | ||||
|  | ||||
|     // 设置初始视角 | ||||
|     const view = { | ||||
|       position: { | ||||
|         lng: 102.03643298211526, | ||||
|         lat: 34.393586474501, | ||||
|         alt: 11298179.51993155 | ||||
|       }, | ||||
|       orientation: { | ||||
|         heading: 360, | ||||
|         pitch: -89.94481747201486, | ||||
|         roll: 0 | ||||
|       } | ||||
|     }; | ||||
|  | ||||
|     YJ.Global.CesiumContainer(window.Earth3, { | ||||
|       compass: false, //罗盘 | ||||
|     }); | ||||
|     // 加载底图 | ||||
|     loadBaseMap(earthInstance.viewer); | ||||
|  | ||||
|     // 可以取消注释以下代码来设置初始视角 | ||||
|     // YJ.Global.flyTo(earthInstance, view); | ||||
|     // YJ.Global.setDefaultView(earthInstance.viewer, view) | ||||
|     renderRange(data); | ||||
|   }).catch((err) => { | ||||
|     console.error('初始化地球失败:', err); | ||||
|     ElMessage.error('初始化地球失败,请稍后重试'); | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| // 加载底图 | ||||
| const loadBaseMap = (viewer) => { | ||||
|   if (!viewer || !Cesium) { | ||||
|     ElMessage.error('Cesium库未加载,请检查依赖'); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   try { | ||||
|     // 创建瓦片提供器 | ||||
|     const imageryProvider = new Cesium.UrlTemplateImageryProvider({ | ||||
|       url: 'https://webst01.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}', | ||||
|       fileExtension: 'png', | ||||
|       minimumLevel: 0, | ||||
|       maximumLevel: 18, | ||||
|       projection: Cesium.WebMercatorProjection, | ||||
|       credit: new Cesium.Credit('卫星图数据来源') | ||||
|     }); | ||||
|  | ||||
|     // 添加图层到视图 | ||||
|     viewer.imageryLayers.addImageryProvider(imageryProvider); | ||||
|   } catch (err) { | ||||
|     console.error('加载底图失败:', err); | ||||
|     ElMessage.error('加载底图失败'); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| // 渲染轨迹 | ||||
| const renderRange = (data) => { | ||||
|   if (!data || data.length === 0) { | ||||
|     ElMessage.warning('无轨迹数据可渲染'); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   if (!earthInstance || !earthInstance.viewer) { | ||||
|     ElMessage.error('地球实例未初始化'); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   try { | ||||
|     const positions = data.map((point) => { | ||||
|       return Cesium.Cartesian3.fromDegrees(point.lng, point.lat, point.alt || 0); | ||||
|     }); | ||||
|  | ||||
|     const entity = earthInstance.viewer.entities.add({ | ||||
|       polyline: { | ||||
|         positions: positions, | ||||
|         width: 5, | ||||
|         material: Cesium.Color.RED, | ||||
|         clampToGround: true | ||||
|       } | ||||
|     }); | ||||
|  | ||||
|     // 调整视角以适应轨迹 | ||||
|     earthInstance.viewer.flyTo(entity); | ||||
|   } catch (err) { | ||||
|     console.error('渲染轨迹失败:', err); | ||||
|     ElMessage.error('渲染轨迹失败'); | ||||
|   } | ||||
| }; | ||||
|  | ||||
|  | ||||
| //  | ||||
| onMounted(() => { | ||||
|   createEarth(); | ||||
| }); | ||||
|  | ||||
| //  | ||||
| onUnmounted(() => { | ||||
|   if (earthInstance) { | ||||
|     earthInstance.destroy(); | ||||
|     earthInstance = null; | ||||
|     window.Earth3 = null; | ||||
|   } | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style></style> | ||||
							
								
								
									
										776
									
								
								src/views/equipment/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										776
									
								
								src/views/equipment/index.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,776 @@ | ||||
| <template> | ||||
|   <div class="p-2"> | ||||
|     <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> | ||||
|       <div v-show="showSearch" class="mb-[10px]"> | ||||
|         <el-card shadow="hover"> | ||||
|           <el-form ref="queryFormRef" :model="queryParams" :inline="true"> | ||||
|             <el-form-item label="设备标识" prop="clientId"> | ||||
|               <el-input v-model="queryParams.clientId" placeholder="请输入设备标识" clearable @keyup.enter="handleQuery" /> | ||||
|             </el-form-item> | ||||
|             <el-form-item label="设备名称" prop="deviceName"> | ||||
|               <el-input v-model="queryParams.deviceName" placeholder="请输入设备名称" clearable @keyup.enter="handleQuery" /> | ||||
|             </el-form-item> | ||||
|  | ||||
|             <el-form-item> | ||||
|               <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button> | ||||
|               <el-button icon="Refresh" @click="resetQuery">重置</el-button> | ||||
|             </el-form-item> | ||||
|           </el-form> | ||||
|         </el-card> | ||||
|       </div> | ||||
|     </transition> | ||||
|  | ||||
|     <el-card shadow="never"> | ||||
|       <template #header> | ||||
|         <el-row :gutter="10" class="mb8"> | ||||
|           <el-col :span="1.5"> | ||||
|             <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['system:equipment:edit']" | ||||
|               >修改</el-button | ||||
|             > | ||||
|           </el-col> | ||||
|           <el-col :span="1.5"> | ||||
|             <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['system:equipment:remove']" | ||||
|               >删除</el-button | ||||
|             > | ||||
|           </el-col> | ||||
|           <el-col :span="1.8"> | ||||
|             <el-button type="primary" plain icon="User" :disabled="single" @click="handleBindUser()" v-hasPermi="['system:equipment:bindUser']" | ||||
|               >绑定用户</el-button | ||||
|             > | ||||
|           </el-col> | ||||
|           <el-col :span="2"> | ||||
|             <el-button type="primary" plain @click="handleViewAll" v-hasPermi="['system:equipment:view']">{{ viewAllButtonText }}</el-button> | ||||
|           </el-col> | ||||
|  | ||||
|           <!-- 新增:跳转空页面按钮 --> | ||||
|           <el-col :span="2"> | ||||
|             <el-button type="primary" plain icon="international" @click="handleGoToEmptyPage"> GPS定位 </el-button> | ||||
|           </el-col> | ||||
|  | ||||
|           <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar> | ||||
|         </el-row> | ||||
|       </template> | ||||
|  | ||||
|       <el-table v-loading="loading" :data="equipmentList" @selection-change="handleSelectionChange"> | ||||
|         <el-table-column type="selection" width="55" align="center" /> | ||||
|  | ||||
|         <el-table-column label="项目名称" align="center" prop="projectName" /> | ||||
|         <el-table-column label="用户名称" align="center" prop="userName" /> | ||||
|         <el-table-column label="设备标识" align="center" prop="clientId" /> | ||||
|  | ||||
|         <!-- 绑定状态列 --> | ||||
|         <el-table-column label="绑定状态" align="center"> | ||||
|           <template #default="scope"> | ||||
|             <el-tag :type="scope.row.type === 1 ? 'success' : 'warning'" size="small"> | ||||
|               {{ scope.row.type === 1 ? '已绑定' : '未绑定' }} | ||||
|             </el-tag> | ||||
|           </template> | ||||
|         </el-table-column> | ||||
|  | ||||
|         <!-- 设备名称列 --> | ||||
|         <el-table-column label="设备名称" align="center"> | ||||
|           <template #default="scope"> | ||||
|             <el-button | ||||
|               type="text" | ||||
|               @click="handleOpenHistoryUser(scope.row.clientId, scope.row.userId)" | ||||
|               style="padding: 0; color: #409eff; cursor: pointer" | ||||
|             > | ||||
|               {{ scope.row.deviceName || '-' }} | ||||
|             </el-button> | ||||
|           </template> | ||||
|         </el-table-column> | ||||
|  | ||||
|         <el-table-column label="远程连接地址" align="center" prop="remoteAddressStr" /> | ||||
|         <el-table-column label="连接创建时间" align="center"> | ||||
|           <template #default="scope"> | ||||
|             {{ formatDateTime(scope.row.creationTime) }} | ||||
|           </template> | ||||
|         </el-table-column> | ||||
|         <el-table-column label="最后活动时间" align="center"> | ||||
|           <template #default="scope"> | ||||
|             {{ formatDateTime(scope.row.lastAccessedTime) }} | ||||
|           </template> | ||||
|         </el-table-column> | ||||
|  | ||||
|         <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> | ||||
|           <template #default="scope"> | ||||
|             <el-tooltip content="修改" placement="top"> | ||||
|               <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['gps:equipment:edit']"></el-button> | ||||
|             </el-tooltip> | ||||
|             <el-tooltip content="删除" placement="top"> | ||||
|               <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['gps:equipment:remove']"></el-button> | ||||
|             </el-tooltip> | ||||
|             <el-tooltip :content="scope.row.type === 1 ? '取消绑定' : '绑定用户'" placement="top"> | ||||
|               <el-button | ||||
|                 link | ||||
|                 type="primary" | ||||
|                 icon="User" | ||||
|                 @click="scope.row.type === 1 ? handleUnbindUser(scope.row) : handleBindUser(scope.row)" | ||||
|                 v-hasPermi="['gps:equipment:bindUser']" | ||||
|               > | ||||
|               </el-button> | ||||
|             </el-tooltip> | ||||
|           </template> | ||||
|         </el-table-column> | ||||
|       </el-table> | ||||
|  | ||||
|       <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" /> | ||||
|     </el-card> | ||||
|  | ||||
|     <!-- 添加或修改GPS设备详细对话框 --> | ||||
|     <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body> | ||||
|       <el-form ref="equipmentFormRef" :model="form" :rules="rules" label-width="80px"> | ||||
|         <el-form-item label="设备名称" prop="deviceName"> | ||||
|           <el-input v-model="form.deviceName" placeholder="请输入设备名称" /> | ||||
|         </el-form-item> | ||||
|       </el-form> | ||||
|       <template #footer> | ||||
|         <div class="dialog-footer"> | ||||
|           <el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button> | ||||
|           <el-button @click="cancel">取 消</el-button> | ||||
|         </div> | ||||
|       </template> | ||||
|     </el-dialog> | ||||
|  | ||||
|     <!-- 绑定用户对话框 --> | ||||
|     <el-dialog title="绑定用户" v-model="bindDialogVisible" width="500px" append-to-body> | ||||
|       <el-form ref="bindUserFormRef" :model="bindForm" :rules="bindRules" label-width="80px"> | ||||
|         <!-- 绑定用户对话框 - 项目选择下拉框 --> | ||||
|         <el-form-item label="项目选择" prop="projectId" required> | ||||
|           <el-select v-model="bindForm.projectId" placeholder="请选择项目" clearable @change="handleProjectChange"> | ||||
|             <el-option v-for="project in projectList" :key="project.projectId" :label="project.projectName" :value="project.projectId" /> | ||||
|           </el-select> | ||||
|         </el-form-item> | ||||
|         <el-form-item label="设备名称" prop="deviceName"> | ||||
|           <el-input v-model="bindForm.deviceName" disabled placeholder="设备名称" /> | ||||
|         </el-form-item> | ||||
|         <el-form-item label="选择用户" prop="userId" required> | ||||
|           <el-select v-model="bindForm.userId" placeholder="请选择用户" clearable> | ||||
|             <el-option v-for="user in userList" :key="user.sysUserId" :label="user.userName" :value="user.sysUserId" /> | ||||
|           </el-select> | ||||
|         </el-form-item> | ||||
|       </el-form> | ||||
|       <template #footer> | ||||
|         <div class="dialog-footer"> | ||||
|           <el-button :loading="bindButtonLoading" type="primary" @click="submitBindUser">确 定</el-button> | ||||
|           <el-button @click="cancelBindUser">取 消</el-button> | ||||
|         </div> | ||||
|       </template> | ||||
|     </el-dialog> | ||||
|  | ||||
|     <!-- 历史用户弹窗 --> | ||||
|     <el-dialog title="设备历史用户" v-model="historyUserDialogVisible" width="600px" append-to-body @close="() => historyUserList.splice(0)"> | ||||
|       <div v-loading="historyUserLoading" style="min-height: 200px; padding: 16px"> | ||||
|         <el-table | ||||
|           :data="historyUserList" | ||||
|           stripe | ||||
|           size="small" | ||||
|           :show-header="historyUserList.length > 0" | ||||
|           v-if="!historyUserLoading || historyUserList.length > 0" | ||||
|         > | ||||
|           <el-table-column label="序号" type="index" width="60" align="center" /> | ||||
|           <el-table-column label="用户名" align="center" prop="userName" /> | ||||
|           <el-table-column label="状态" align="center" width="120"> | ||||
|             <template #default="scope"> | ||||
|               <el-tag :type="scope.row.type === 0 ? 'success' : 'info'" size="small"> | ||||
|                 {{ scope.row.type === 0 ? '当前绑定' : '历史绑定' }} | ||||
|               </el-tag> | ||||
|             </template> | ||||
|           </el-table-column> | ||||
|         </el-table> | ||||
|  | ||||
|         <div v-if="!historyUserLoading && historyUserList.length === 0" style="text-align: center; padding: 50px 0"> | ||||
|           <el-empty description="暂无该设备的历史用户数据"></el-empty> | ||||
|         </div> | ||||
|       </div> | ||||
|  | ||||
|       <template #footer> | ||||
|         <el-button @click="historyUserDialogVisible = false">关 闭</el-button> | ||||
|       </template> | ||||
|     </el-dialog> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script setup name="Equipment" lang="ts"> | ||||
| import { | ||||
|   listEquipment, | ||||
|   getEquipment, | ||||
|   delEquipment, | ||||
|   addEquipment, | ||||
|   updateEquipment, | ||||
|   bindUser, | ||||
|   getUserId, | ||||
|   gethistroyUser, | ||||
|   getRemoveBind, | ||||
|   getProjectId | ||||
| } from '@/api/equipment/index'; | ||||
| import { EquipmentVO, EquipmentQuery, EquipmentForm } from '@/api/equipment/types'; | ||||
| import { getCurrentInstance, ComponentInternalInstance, onMounted, ref, reactive, toRefs, watch, computed } from 'vue'; | ||||
| import { useUserStoreHook } from '@/store/modules/user'; | ||||
| import { useRouter } from 'vue-router'; // 新增:导入路由钩子 | ||||
|  | ||||
| const { proxy } = getCurrentInstance() as ComponentInternalInstance; | ||||
| const router = useRouter(); // 新增:初始化路由实例 | ||||
|  | ||||
| // 扩展EquipmentVO类型,添加type字段 | ||||
| interface ExtendedEquipmentVO extends EquipmentVO { | ||||
|   type: 1 | 2; // 1=已绑定,2=未绑定 | ||||
| } | ||||
|  | ||||
| // 设备列表相关 | ||||
| const equipmentList = ref<ExtendedEquipmentVO[]>([]); | ||||
| const buttonLoading = ref(false); | ||||
| const loading = ref(true); | ||||
| const showSearch = ref(true); | ||||
| const ids = ref<Array<string | number>>([]); | ||||
| const single = ref(true); | ||||
| const multiple = ref(true); | ||||
| const total = ref(0); | ||||
|  | ||||
| // 表单相关引用 | ||||
| const queryFormRef = ref<ElFormInstance>(); | ||||
| const equipmentFormRef = ref<ElFormInstance>(); | ||||
| const bindUserFormRef = ref<ElFormInstance>(); | ||||
|  | ||||
| // 项目和用户相关 | ||||
| const userStore = useUserStoreHook(); | ||||
| const currentProject = computed(() => userStore.selectedProject); | ||||
| const userList = ref<any[]>([]); | ||||
| const projectList = ref<any[]>([]); | ||||
| const projectLoading = ref(false); | ||||
| const userLoading = ref(false); | ||||
| const viewAllButtonText = computed(() => { | ||||
|   return queryParams.value.type === 1 ? '查看未绑定设备' : '查看已绑定设备'; | ||||
| }); | ||||
|  | ||||
| // 对话框相关 | ||||
| const dialog = reactive<DialogOption>({ | ||||
|   visible: false, | ||||
|   title: '' | ||||
| }); | ||||
| const bindDialogVisible = ref(false); | ||||
| const bindButtonLoading = ref(false); | ||||
|  | ||||
| // 历史用户弹窗相关 | ||||
| const historyUserDialogVisible = ref(false); | ||||
| const historyUserList = ref<HistoryUserVO[]>([]); | ||||
| const historyUserLoading = ref(false); | ||||
|  | ||||
| // 绑定用户表单数据 | ||||
| const bindForm = reactive({ | ||||
|   id: undefined, | ||||
|   projectId: undefined, | ||||
|   userId: undefined, | ||||
|   deviceName: undefined | ||||
| }); | ||||
|  | ||||
| // 绑定用户表单验证规则 | ||||
| const bindRules = reactive({ | ||||
|   projectId: [{ required: true, message: '请选择项目', trigger: 'change' }], | ||||
|   userId: [{ required: true, message: '请选择用户', trigger: 'change' }] | ||||
| }); | ||||
|  | ||||
| // 初始化表单数据 | ||||
| const initFormData: EquipmentForm = { | ||||
|   id: undefined, | ||||
|   projectId: undefined, | ||||
|   userId: undefined, | ||||
|   deviceName: undefined, | ||||
|   udp: undefined, | ||||
|   remoteAddressStr: undefined, | ||||
|   creationTime: undefined, | ||||
|   lastAccessedTime: undefined, | ||||
|   registered: undefined, | ||||
|   remark: undefined | ||||
| }; | ||||
|  | ||||
| // 页面数据 | ||||
| const data = reactive<PageData<EquipmentForm, EquipmentQuery>>({ | ||||
|   form: { ...initFormData }, | ||||
|   queryParams: { | ||||
|     type: 1, // 默认查询已绑定设备 | ||||
|     pageNum: 1, | ||||
|     pageSize: 10, | ||||
|     projectId: undefined, | ||||
|     userId: undefined, | ||||
|     clientId: undefined, | ||||
|     deviceName: undefined, | ||||
|     udp: undefined, | ||||
|     remoteAddressStr: undefined, | ||||
|     creationTime: undefined, | ||||
|     lastAccessedTime: undefined, | ||||
|     registered: undefined, | ||||
|     params: {} | ||||
|   }, | ||||
|   rules: { | ||||
|     deviceName: [{ required: true, message: '请输入设备名称', trigger: 'blur' }] | ||||
|   } | ||||
| }); | ||||
|  | ||||
| const { queryParams, form, rules } = toRefs(data); | ||||
|  | ||||
| /** 格式化日期时间 */ | ||||
| const formatDateTime = (timestamp: any): string => { | ||||
|   if (!timestamp) return '-'; | ||||
|  | ||||
|   let date: Date; | ||||
|  | ||||
|   // 处理自定义的 yyyyMMddHHmmss 格式数字 | ||||
|   if (typeof timestamp === 'number' && timestamp.toString().length === 14) { | ||||
|     const timeStr = timestamp.toString(); | ||||
|     const year = timeStr.substring(0, 4); | ||||
|     const month = timeStr.substring(4, 6); | ||||
|     const day = timeStr.substring(6, 8); | ||||
|     const hours = timeStr.substring(8, 10); | ||||
|     const minutes = timeStr.substring(10, 12); | ||||
|     const seconds = timeStr.substring(12, 14); | ||||
|     date = new Date(`${year}-${month}-${day} ${hours}:${minutes}:${seconds}`); | ||||
|   } | ||||
|   // 处理时间戳(毫秒数) | ||||
|   else if (typeof timestamp === 'number') { | ||||
|     date = new Date(timestamp.toString().length === 10 ? timestamp * 1000 : timestamp); | ||||
|   } | ||||
|   // 处理字符串类型的时间戳或日期 | ||||
|   else if (typeof timestamp === 'string') { | ||||
|     if (/^\d+$/.test(timestamp)) { | ||||
|       const num = parseInt(timestamp); | ||||
|       if (timestamp.length === 14) { | ||||
|         const year = timestamp.substring(0, 4); | ||||
|         const month = timestamp.substring(4, 6); | ||||
|         const day = timestamp.substring(6, 8); | ||||
|         const hours = timestamp.substring(8, 10); | ||||
|         const minutes = timestamp.substring(10, 12); | ||||
|         const seconds = timestamp.substring(12, 14); | ||||
|         date = new Date(`${year}-${month}-${day} ${hours}:${minutes}:${seconds}`); | ||||
|       } else { | ||||
|         date = new Date(timestamp.length === 10 ? num * 1000 : num); | ||||
|       } | ||||
|     } else { | ||||
|       date = new Date(timestamp); | ||||
|     } | ||||
|   } | ||||
|   // 其他情况 | ||||
|   else { | ||||
|     return '-'; | ||||
|   } | ||||
|  | ||||
|   // 检查日期是否有效 | ||||
|   if (isNaN(date.getTime())) return '-'; | ||||
|  | ||||
|   // 格式化日期 | ||||
|   const year = date.getFullYear(); | ||||
|   const month = String(date.getMonth() + 1).padStart(2, '0'); | ||||
|   const day = String(date.getDate()).padStart(2, '0'); | ||||
|   const hours = String(date.getHours()).padStart(2, '0'); | ||||
|   const minutes = String(date.getMinutes()).padStart(2, '0'); | ||||
|   const seconds = String(date.getSeconds()).padStart(2, '0'); | ||||
|  | ||||
|   return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; | ||||
| }; | ||||
|  | ||||
| /** 获取设备列表 */ | ||||
| const getList = async () => { | ||||
|   loading.value = true; | ||||
|   try { | ||||
|     // 已绑定设备默认关联当前项目 | ||||
|     if (queryParams.value.type === 1 && currentProject.value?.id && !queryParams.value.projectId) { | ||||
|       queryParams.value.projectId = currentProject.value.id; | ||||
|     } else if (queryParams.value.type === 2) { | ||||
|       // 未绑定设备清空项目筛选 | ||||
|       queryParams.value.projectId = undefined; | ||||
|     } | ||||
|  | ||||
|     const res = await listEquipment(queryParams.value); | ||||
|     equipmentList.value = res.rows as ExtendedEquipmentVO[]; | ||||
|     total.value = res.total; | ||||
|   } catch (error) { | ||||
|     console.error('获取设备列表失败:', error); | ||||
|     proxy?.$modal.msgError('获取设备列表失败,请重试'); | ||||
|   } finally { | ||||
|     loading.value = false; | ||||
|   } | ||||
| }; | ||||
|  | ||||
| const getProjects = async () => { | ||||
|   projectLoading.value = true; | ||||
|   try { | ||||
|     const res = await getProjectId(); | ||||
|     console.log('getProjectId接口原始返回:', res); | ||||
|  | ||||
|     // 从res.data中获取数组(适配外层包裹的接口格式) | ||||
|     const rawProjects = res?.data || []; | ||||
|  | ||||
|     // 验证数据类型,确保是数组 | ||||
|     if (!Array.isArray(rawProjects)) { | ||||
|       console.warn('接口返回的项目数据不是数组,已自动修正为空数组'); | ||||
|       projectList.value = []; | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     // 处理项目数据(使用projectId和projectName) | ||||
|     projectList.value = rawProjects | ||||
|       .map((project) => ({ | ||||
|         projectId: project.projectId, | ||||
|         projectName: project.projectName || '未命名项目' | ||||
|       })) | ||||
|       .filter((project) => project.projectId); | ||||
|  | ||||
|     console.log('处理后项目列表:', projectList.value); | ||||
|   } catch (error) { | ||||
|     console.error('获取项目列表失败:', error); | ||||
|     proxy?.$modal.msgError('获取项目列表失败,请重试'); | ||||
|   } finally { | ||||
|     projectLoading.value = false; | ||||
|   } | ||||
| }; | ||||
|  | ||||
| /** 根据项目ID获取用户列表 */ | ||||
| const getUsersByProjectId = async (projectId: any) => { | ||||
|   if (!projectId) { | ||||
|     userList.value = []; | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   userLoading.value = true; | ||||
|   try { | ||||
|     const res = await getUserId(projectId); | ||||
|     userList.value = res.data || []; | ||||
|   } catch (error) { | ||||
|     console.error('获取用户列表失败:', error); | ||||
|     proxy?.$modal.msgError('获取用户列表失败,请重试'); | ||||
|   } finally { | ||||
|     userLoading.value = false; | ||||
|   } | ||||
| }; | ||||
|  | ||||
| /** 项目选择变化时触发 */ | ||||
| const handleProjectChange = (projectId: any) => { | ||||
|   bindForm.userId = undefined; | ||||
|   getUsersByProjectId(projectId); | ||||
|  | ||||
|   // 仅已绑定设备需要更新项目筛选 | ||||
|   if (queryParams.value.type === 1) { | ||||
|     queryParams.value.projectId = projectId; | ||||
|     getList(); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| /** 切换查看已绑定/未绑定设备 */ | ||||
| const handleViewAll = () => { | ||||
|   queryParams.value.pageNum = 1; | ||||
|  | ||||
|   if (queryParams.value.type === 1) { | ||||
|     // 切换到未绑定设备 | ||||
|     queryParams.value.type = 2; | ||||
|     proxy?.$modal.msgSuccess('已切换到查看未绑定设备模式'); | ||||
|   } else { | ||||
|     // 切换到已绑定设备 | ||||
|     queryParams.value.type = 1; | ||||
|     queryParams.value.projectId = currentProject.value?.id; | ||||
|     proxy?.$modal.msgSuccess('已切换到查看已绑定设备模式'); | ||||
|   } | ||||
|  | ||||
|   getList(); | ||||
| }; | ||||
|  | ||||
| const handleGoToEmptyPage = () => { | ||||
|   router.push({ | ||||
|     path: './equipmentGPS' | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| /** 取消按钮 */ | ||||
| const cancel = () => { | ||||
|   reset(); | ||||
|   dialog.visible = false; | ||||
| }; | ||||
|  | ||||
| /** 表单重置 */ | ||||
| const reset = () => { | ||||
|   form.value = { ...initFormData, projectId: currentProject.value?.id }; | ||||
|   equipmentFormRef.value?.resetFields(); | ||||
| }; | ||||
|  | ||||
| /** 搜索按钮操作 */ | ||||
| const handleQuery = () => { | ||||
|   queryParams.value.pageNum = 1; | ||||
|   getList(); | ||||
| }; | ||||
|  | ||||
| /** 重置按钮操作 */ | ||||
| const resetQuery = () => { | ||||
|   queryFormRef.value?.resetFields(); | ||||
|   queryParams.value.type = 1; | ||||
|   queryParams.value.projectId = currentProject.value?.id; | ||||
|   handleQuery(); | ||||
| }; | ||||
|  | ||||
| /** 多选框选中数据 */ | ||||
| const handleSelectionChange = (selection: ExtendedEquipmentVO[]) => { | ||||
|   ids.value = selection.map((item) => item.id); | ||||
|   single.value = selection.length != 1; | ||||
|   multiple.value = !selection.length; | ||||
| }; | ||||
|  | ||||
| /** 修改按钮操作 */ | ||||
| const handleUpdate = async (row?: ExtendedEquipmentVO) => { | ||||
|   reset(); | ||||
|   const _id = row?.id || ids.value[0]; | ||||
|   try { | ||||
|     const res = await getEquipment(_id); | ||||
|     Object.assign(form.value, res.data); | ||||
|     dialog.visible = true; | ||||
|     dialog.title = '修改GPS设备详细'; | ||||
|   } catch (error) { | ||||
|     console.error('获取设备详情失败:', error); | ||||
|     proxy?.$modal.msgError('获取设备详情失败,请重试'); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| /** 提交表单 */ | ||||
| const submitForm = () => { | ||||
|   equipmentFormRef.value?.validate(async (valid: boolean) => { | ||||
|     if (valid) { | ||||
|       buttonLoading.value = true; | ||||
|       try { | ||||
|         if (form.value.id) { | ||||
|           await updateEquipment(form.value); | ||||
|         } else { | ||||
|           await addEquipment(form.value); | ||||
|         } | ||||
|         proxy?.$modal.msgSuccess('操作成功'); | ||||
|         dialog.visible = false; | ||||
|         await getList(); | ||||
|       } catch (error) { | ||||
|         console.error('提交表单失败:', error); | ||||
|         proxy?.$modal.msgError('操作失败'); | ||||
|       } finally { | ||||
|         buttonLoading.value = false; | ||||
|       } | ||||
|     } | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| /** 删除按钮操作 */ | ||||
| const handleDelete = async (row?: ExtendedEquipmentVO) => { | ||||
|   const _ids = row?.id || ids.value; | ||||
|   try { | ||||
|     await proxy?.$modal.confirm('是否确认删除GPS设备详细编号为"' + _ids + '"的数据项?'); | ||||
|     await delEquipment(_ids); | ||||
|     proxy?.$modal.msgSuccess('删除成功'); | ||||
|     await getList(); | ||||
|   } catch (error) { | ||||
|     console.error('删除设备失败:', error); | ||||
|     proxy?.$modal.msgError('删除失败,请重试'); | ||||
|   } finally { | ||||
|     loading.value = false; | ||||
|   } | ||||
| }; | ||||
|  | ||||
| /** 绑定用户按钮操作 */ | ||||
| const handleBindUser = async (row?: EquipmentVO) => { | ||||
|   Object.assign(bindForm, { | ||||
|     id: undefined, | ||||
|     projectId: undefined, | ||||
|     userId: undefined, | ||||
|     clientId: row?.clientId, | ||||
|     deviceName: undefined | ||||
|   }); | ||||
|   bindUserFormRef.value?.resetFields(); | ||||
|   userList.value = []; | ||||
|  | ||||
|   const _id = row?.id || ids.value[0]; | ||||
|   try { | ||||
|     const res = await getEquipment(_id); | ||||
|     const equipmentData = res.data; | ||||
|     bindForm.id = equipmentData.id; | ||||
|     bindForm.deviceName = equipmentData.deviceName; | ||||
|     bindForm.clientId = row?.clientId || equipmentData.clientId; | ||||
|  | ||||
|     if (projectList.value.length === 0) { | ||||
|       await getProjects(); | ||||
|     } | ||||
|  | ||||
|     if (equipmentData.projectId) { | ||||
|       bindForm.projectId = equipmentData.projectId; | ||||
|       await getUsersByProjectId(equipmentData.projectId); | ||||
|     } else if (currentProject.value?.id) { | ||||
|       bindForm.projectId = currentProject.value.id; | ||||
|       await getUsersByProjectId(currentProject.value.id); | ||||
|     } | ||||
|  | ||||
|     bindDialogVisible.value = true; | ||||
|   } catch (error) { | ||||
|     console.error('获取绑定用户信息失败:', error); | ||||
|     proxy?.$modal.msgError('获取数据失败,请重试'); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| /** 提交绑定用户 */ | ||||
| const submitBindUser = () => { | ||||
|   bindUserFormRef.value?.validate(async (valid: boolean) => { | ||||
|     if (valid) { | ||||
|       if (!bindForm.clientId) { | ||||
|         proxy?.$modal.msgWarning('设备标识clientId不存在,无法完成绑定'); | ||||
|         bindButtonLoading.value = false; | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       bindButtonLoading.value = true; | ||||
|       try { | ||||
|         const bindData = { | ||||
|           id: bindForm.id, | ||||
|           projectId: bindForm.projectId, | ||||
|           userId: bindForm.userId, | ||||
|           clientId: bindForm.clientId, | ||||
|           deviceName: bindForm.deviceName | ||||
|         }; | ||||
|  | ||||
|         console.log('提交绑定用户参数:', bindData); | ||||
|  | ||||
|         await bindUser(bindData); | ||||
|         proxy?.$modal.msgSuccess('用户绑定成功'); | ||||
|         bindDialogVisible.value = false; | ||||
|         await getList(); | ||||
|       } catch (error) { | ||||
|         console.error('绑定用户失败:', error); | ||||
|         proxy?.$modal.msgError('绑定失败,请重试'); | ||||
|       } finally { | ||||
|         bindButtonLoading.value = false; | ||||
|       } | ||||
|     } | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| /** 取消绑定用户对话框 */ | ||||
| const cancelBindUser = () => { | ||||
|   bindDialogVisible.value = false; | ||||
| }; | ||||
|  | ||||
| /** 取消用户绑定操作 */ | ||||
| const handleUnbindUser = async (row: ExtendedEquipmentVO) => { | ||||
|   try { | ||||
|     if (!row) { | ||||
|       proxy?.$modal.msgWarning('未获取到设备信息'); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     if (row.id === undefined || row.id === null || row.id === '') { | ||||
|       proxy?.$modal.msgWarning('设备ID不能为空'); | ||||
|       return; | ||||
|     } | ||||
|     const deviceId = Number(row.id); | ||||
|     if (isNaN(deviceId) || deviceId <= 0) { | ||||
|       proxy?.$modal.msgWarning(`设备ID格式错误,必须是正整数,当前值: ${row.id}`); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     if (!row.clientId || typeof row.clientId !== 'string' || row.clientId.trim() === '') { | ||||
|       proxy?.$modal.msgWarning(`设备标识clientId格式错误,必须是非空字符串,当前值: ${row.clientId}`); | ||||
|       return; | ||||
|     } | ||||
|     const clientId = row.clientId.trim(); | ||||
|  | ||||
|     const unbindParams = { | ||||
|       id: deviceId, | ||||
|       clientId: clientId | ||||
|     }; | ||||
|  | ||||
|     await proxy?.$modal.confirm('是否确认解除该设备的用户绑定?'); | ||||
|  | ||||
|     const response = await getRemoveBind(unbindParams); | ||||
|     if (response && response.code === 200) { | ||||
|       proxy?.$modal.msgSuccess('解除绑定成功'); | ||||
|       await getList(); | ||||
|     } else { | ||||
|       throw new Error(`接口返回非成功状态: ${JSON.stringify(response)}`); | ||||
|     } | ||||
|   } catch (error) { | ||||
|     console.error('解除绑定 - 错误详情:', error); | ||||
|  | ||||
|     if (error === 'cancel' || (error instanceof Error && error.message.includes('cancel'))) { | ||||
|       console.log('用户取消了解除绑定操作'); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     const errorMsg = error instanceof Error ? error.message : typeof error === 'string' ? error : '未知错误'; | ||||
|     proxy?.$modal.msgError(`解除绑定失败: ${errorMsg}`); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| /** 打开历史用户弹窗 */ | ||||
| const handleOpenHistoryUser = async (clientId: string | number | undefined, currentUserId: string | number | undefined) => { | ||||
|   if (!clientId) { | ||||
|     proxy?.$modal.msgWarning('设备标识(clientId)不存在,无法获取历史用户'); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   historyUserDialogVisible.value = true; | ||||
|   historyUserLoading.value = true; | ||||
|   historyUserList.value = []; | ||||
|  | ||||
|   try { | ||||
|     const res = await gethistroyUser({ | ||||
|       clientId: clientId, | ||||
|       userId: currentUserId || '' | ||||
|     }); | ||||
|  | ||||
|     const rawUserList = res.data || []; | ||||
|     if (currentUserId && rawUserList.length > 0) { | ||||
|       const currentUser = rawUserList.find((user: HistoryUserVO) => user.sysUserId === currentUserId); | ||||
|       const historyUsers = rawUserList.filter((user: HistoryUserVO) => user.sysUserId !== currentUserId); | ||||
|       historyUserList.value = currentUser ? [currentUser, ...historyUsers] : historyUsers; | ||||
|     } else { | ||||
|       historyUserList.value = rawUserList; | ||||
|     } | ||||
|   } catch (error) { | ||||
|     console.error('获取历史用户接口调用失败(clientId:%s):', clientId, error); | ||||
|     proxy?.$modal.msgError('获取历史用户失败,请重试'); | ||||
|     historyUserList.value = []; | ||||
|   } finally { | ||||
|     historyUserLoading.value = false; | ||||
|   } | ||||
| }; | ||||
|  | ||||
| /** 页面挂载时初始化 */ | ||||
| onMounted(() => { | ||||
|   getList(); | ||||
| }); | ||||
|  | ||||
| /** 监听项目变化 */ | ||||
| const listeningProject = watch( | ||||
|   () => currentProject.value?.id, | ||||
|   (nid) => { | ||||
|     if (queryParams.value.type === 1) { | ||||
|       queryParams.value.projectId = nid; | ||||
|       form.value.projectId = nid; | ||||
|       getList(); | ||||
|     } | ||||
|   } | ||||
| ); | ||||
|  | ||||
| /** 页面卸载时清理 */ | ||||
| onUnmounted(() => { | ||||
|   listeningProject(); | ||||
| }); | ||||
|  | ||||
| // 类型定义 | ||||
| interface DialogOption { | ||||
|   visible: boolean; | ||||
|   title: string; | ||||
| } | ||||
|  | ||||
| interface PageData<T, Q> { | ||||
|   form: T; | ||||
|   queryParams: Q; | ||||
|   rules: Record<string, any[]>; | ||||
| } | ||||
|  | ||||
| interface HistoryUserVO { | ||||
|   sysUserId: string | number; | ||||
|   userName: string; | ||||
|   lastUseTime?: number | string; | ||||
|   bindTime?: number | string; | ||||
|   type?: number; // 1:当前绑定,其他:历史绑定 | ||||
| } | ||||
| </script> | ||||
| @ -12,7 +12,7 @@ | ||||
| </template> | ||||
|  | ||||
| <script setup> | ||||
| import { ref, onMounted, watch, defineEmits, defineProps } from 'vue'; | ||||
| import { ref, onMounted, watch, defineEmits, defineProps,onUnmounted } from 'vue'; | ||||
| import { ElMessage } from 'element-plus'; | ||||
| import { | ||||
|     addAttendanceRange, | ||||
| @ -265,6 +265,15 @@ onMounted(() => { | ||||
| defineExpose({ | ||||
|     show | ||||
| }); | ||||
|  | ||||
| //  | ||||
| onUnmounted(() => { | ||||
|     if (earthInstance) { | ||||
|         earthInstance.destroy(); | ||||
|         earthInstance = null; | ||||
|         window.Earth2 = null; | ||||
|     } | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
|  | ||||
| @ -80,6 +80,11 @@ | ||||
|             <el-option v-for="item in team_clock_type" :key="item.value" :label="item.label" :value="item.value" /> | ||||
|           </el-select> | ||||
|         </el-form-item> | ||||
|         <el-form-item label="打卡范围" prop="punchRangeList" v-if="form.isClockIn == 1"> | ||||
|           <el-select v-model="form.punchRangeList" multiple clearable placeholder="请选择打卡范围"> | ||||
|             <el-option v-for="item in projectTeamRangeList" :key="item.id" :label="item.punchName" :value="item.id" /> | ||||
|           </el-select> | ||||
|         </el-form-item> | ||||
|         <el-form-item label="备注" prop="remark"> | ||||
|           <el-input v-model="form.remark" type="textarea" placeholder="请输入内容" /> | ||||
|         </el-form-item> | ||||
| @ -98,13 +103,15 @@ | ||||
| </template> | ||||
|  | ||||
| <script setup name="ProjectTeam" lang="ts"> | ||||
| import { addProjectTeam, delProjectTeam, getProjectTeam, listProjectTeam, updateProjectTeam } from '@/api/project/projectTeam'; | ||||
| import { addProjectTeam, delProjectTeam, getProjectTeam, listProjectTeam, updateProjectTeam,getProjectTeamClockIn } from '@/api/project/projectTeam'; | ||||
| import { ProjectTeamForm, ProjectTeamQuery, ProjectTeamVO } from '@/api/project/projectTeam/types'; | ||||
| import { useUserStoreHook } from '@/store/modules/user'; | ||||
| import UserListDialog from '@/views/project/projectTeam/component/UserListDialog.vue'; | ||||
|  | ||||
| const { proxy } = getCurrentInstance() as ComponentInternalInstance; | ||||
| const { team_clock_type } = toRefs<any>(proxy?.useDict('team_clock_type')); | ||||
| console.log(team_clock_type); | ||||
|  | ||||
| // 获取用户 store | ||||
| const userStore = useUserStoreHook(); | ||||
| // 从 store 中获取项目列表和当前选中的项目 | ||||
| @ -117,6 +124,7 @@ const ids = ref<Array<string | number>>([]); | ||||
| const single = ref(true); | ||||
| const multiple = ref(true); | ||||
| const total = ref(0); | ||||
| const projectTeamRangeList = ref([]); | ||||
| const currentRow = ref<ProjectTeamVO>({ | ||||
|   id: undefined, | ||||
|   projectId: undefined, | ||||
| @ -140,7 +148,8 @@ const initFormData: ProjectTeamForm = { | ||||
|   teamName: undefined, | ||||
|   isClockIn: undefined, | ||||
|   remark: undefined, | ||||
|   peopleNumber: undefined | ||||
|   peopleNumber: undefined, | ||||
|   punchRangeList: undefined | ||||
| }; | ||||
| const data = reactive<PageData<ProjectTeamForm, ProjectTeamQuery>>({ | ||||
|   form: { ...initFormData }, | ||||
| @ -171,6 +180,14 @@ const getList = async () => { | ||||
|   total.value = res.total; | ||||
|   loading.value = false; | ||||
| }; | ||||
| /** 获取该项目的打开范围 "*/  | ||||
| const getClockIn = async () => { | ||||
|   if(currentProject.value?.id){ | ||||
|     const res = await getProjectTeamClockIn({projectId:currentProject.value?.id}); | ||||
|     projectTeamRangeList.value = res.rows | ||||
|   } | ||||
| }; | ||||
|  | ||||
|  | ||||
| /** 取消按钮 */ | ||||
| const cancel = () => { | ||||
| @ -280,5 +297,6 @@ onUnmounted(() => { | ||||
|  | ||||
| onMounted(() => { | ||||
|   getList(); | ||||
|   getClockIn(); | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| @ -5,6 +5,11 @@ | ||||
|       <div v-show="showSearch" class="mb-[10px]"> | ||||
|         <el-card shadow="hover"> | ||||
|           <el-form ref="queryFormRef" :model="queryParams" :inline="true"> | ||||
|             <el-form-item label="项目名称" prop="projectId"> | ||||
|               <el-select v-model="queryParams.projectId" clearable placeholder="全部"> | ||||
|                 <el-option v-for="item in projectList" :key="item.value" :label="item.projectName" :value="item.id" /> | ||||
|               </el-select> | ||||
|             </el-form-item> | ||||
|             <el-form-item label="人员姓名" prop="userName"> | ||||
|               <el-input v-model="queryParams.userName" placeholder="请输入人员姓名" clearable @keyup.enter="handleQuery" /> | ||||
|             </el-form-item> | ||||
| @ -156,6 +161,7 @@ | ||||
|               </el-button> | ||||
|               <!-- <el-button link type="primary" icon="Switch" @click="handleToggle(scope.row)"> 切换人脸 </el-button> --> | ||||
|               <el-button link type="primary" icon="Switch" @click="handleChange(scope.row)"> 人员迁移 </el-button> | ||||
|               <el-button link type="primary" icon="Switch" @click="handleAssign(scope.row)"> 分配班组 </el-button> | ||||
|               <el-button link type="primary" icon="ChatLineSquare" @click="handleExit(scope.row)"> 入退场记录 </el-button> | ||||
|               <el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" | ||||
|                 v-hasPermi="['contractor:constructionUser:remove']"> | ||||
| @ -443,6 +449,30 @@ | ||||
|         </template> | ||||
|       </el-calendar> | ||||
|     </el-dialog> | ||||
|     <el-dialog draggable :title="skipName + '-人员分配'" v-model="personnelAllocation" width="500px"> | ||||
|       <el-form-item label="所属项目" label-width="130px"> | ||||
|         <el-select v-model="personnelAllocationObject.projectId" @change="selectProject1" placeholder="请选择所属项目" style="width: 240px"> | ||||
|           <el-option v-for="item in projectList" :key="item.id" :label="item.projectName" :value="item.id" /> | ||||
|         </el-select> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="岗位" label-width="130px"> | ||||
|         <el-select v-model="personnelAllocationObject.postId" placeholder="请选择岗位" style="width: 240px"> | ||||
|           <el-option v-for="item in user_post_type" :key="item.value" :label="item.label" :value="item.value" /> | ||||
|         </el-select> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="班组" label-width="130px"> | ||||
|         <el-select v-model="personnelAllocationObject.teamId" :disabled="!personnelAllocationObject.projectId" placeholder="请选择分包单位" | ||||
|           style="width: 240px"> | ||||
|           <el-option v-for="item in teamList" :key="item.id" :label="item.teamName" :value="item.id" /> | ||||
|         </el-select> | ||||
|       </el-form-item> | ||||
|       <template #footer> | ||||
|         <div class="dialog-footer"> | ||||
|           <el-button type="primary" @click="handlePersonnelAllocation">确认</el-button> | ||||
|           <el-button @click="personnelAllocation = false"> 取消 </el-button> | ||||
|         </div> | ||||
|       </template> | ||||
|     </el-dialog> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| @ -462,7 +492,10 @@ import { | ||||
|   getConstructionUserExit, | ||||
|   dowloadConstructionUserTemplate, | ||||
|   importConstructionUserInfo, | ||||
|   listConstructionMonth | ||||
|   listConstructionMonth, | ||||
|   ProjectList, | ||||
|   TeamList, | ||||
|   TeamDistribution | ||||
| } from '@/api/project/constructionUser'; | ||||
| import { | ||||
|   ConstructionUserForm, | ||||
| @ -494,8 +527,8 @@ import { parseTime } from '@/utils/ruoyi'; | ||||
|  | ||||
| const calendar = ref<CalendarInstance>(); | ||||
| const { proxy } = getCurrentInstance() as ComponentInternalInstance; | ||||
| const { type_of_work, user_sex_type, user_clock_type, user_file_type, user_status_type, wage_measure_unit_type } = toRefs<any>( | ||||
|   proxy?.useDict('type_of_work', 'user_sex_type', 'user_clock_type', 'user_file_type', 'user_status_type', 'wage_measure_unit_type') | ||||
| const { type_of_work, user_sex_type, user_clock_type, user_file_type, user_status_type, wage_measure_unit_type,user_post_type } = toRefs<any>( | ||||
|   proxy?.useDict('type_of_work', 'user_sex_type', 'user_clock_type', 'user_file_type', 'user_status_type', 'wage_measure_unit_type','user_post_type') | ||||
| ); | ||||
| // 获取用户 store | ||||
| const userStore = useUserStoreHook(); | ||||
| @ -511,6 +544,7 @@ const single = ref(true); | ||||
| const multiple = ref(true); | ||||
| const total = ref(0); | ||||
| const skip = ref(false); | ||||
| const personnelAllocation = ref(false); | ||||
| const fileStatus = ref(false); | ||||
| const showFaceDrawer = ref(false); | ||||
| const statusDialog = ref(false); | ||||
| @ -531,6 +565,10 @@ const queryFormRef = ref<ElFormInstance>(); | ||||
| const constructionUserFormRef = ref<ElFormInstance>(); | ||||
| const skipName = ref(''); | ||||
| const calendarList = ref<Array<AttendanceMonthVO>>([]); | ||||
| // 项目列表 | ||||
| const projectList = ref([]); | ||||
| // 班组列表 | ||||
| const teamList = ref([]); | ||||
| const dialog = reactive<DialogOption>({ | ||||
|   visible: false, | ||||
|   title: '', | ||||
| @ -543,6 +581,14 @@ const skipObject: skipType = reactive({ | ||||
|   projectId: '', | ||||
|   contractorId: '' | ||||
| }); | ||||
| // 人员分配 | ||||
| const personnelAllocationObject = reactive({ | ||||
|   memberId: null, | ||||
|   projectId: '', | ||||
|   teamId: '', | ||||
|   postId: '', | ||||
| }); | ||||
|  | ||||
| const contractorList = ref<Array<skipTeamType>>([]); | ||||
| //项目列表 | ||||
| const skipOptions = ref<Array<skipOptionType>>([]); | ||||
| @ -661,6 +707,13 @@ const uploadPath = computed(() => { | ||||
|   return list; | ||||
| }); | ||||
|  | ||||
| // 获取项目列表 | ||||
| const getProjectList = async () => { | ||||
|   const res = await ProjectList({}); | ||||
|   projectList.value = res.rows; | ||||
|   projectList.value.unshift({ id: '', projectName: '全部' }); | ||||
| }; | ||||
|  | ||||
| /** 返回文件上传状态 */ | ||||
| const uploadStatusColor = computed(() => (str: string) => { | ||||
|   switch (str) { | ||||
| @ -825,7 +878,7 @@ const reset = () => { | ||||
| /** 搜索按钮操作 */ | ||||
| const handleQuery = () => { | ||||
|   queryParams.value.pageNum = 1; | ||||
|   if (contractorOpt.value.length == 1) queryParams.value.contractorId = contractorOpt.value[0].value; | ||||
|   // if (contractorOpt.value.length == 1) queryParams.value.contractorId = contractorOpt.value[0].value; | ||||
|   getList(); | ||||
| }; | ||||
|  | ||||
| @ -1053,6 +1106,36 @@ const listeningProject = watch( | ||||
|     getContractorList(); | ||||
|   } | ||||
| ); | ||||
| // 分配班组 | ||||
| const handleAssign = async (row: ConstructionUserVO) => { | ||||
|   const _id = row?.id || ids.value[0]; | ||||
|   currentUserId.value = _id; | ||||
|   personnelAllocationObject.memberId = row?.sysUserId; | ||||
|   personnelAllocation.value = true; | ||||
| }; | ||||
| // 选择项目1 | ||||
| const selectProject1 = (e: any) => { | ||||
|   // 请求班组 | ||||
|   getTeamList(personnelAllocationObject.projectId); | ||||
|    | ||||
| }; | ||||
| const getTeamList = async (projectId) => { | ||||
|   const res = await TeamList({ | ||||
|     projectId, | ||||
|     pageNum: 1, | ||||
|     pageSize: 100 | ||||
|   }); | ||||
|   teamList.value = res.rows; | ||||
| }; | ||||
| // 人员分配 | ||||
| const handlePersonnelAllocation = async () => { | ||||
|   let res = await TeamDistribution(personnelAllocationObject); | ||||
|   if (res.code == 200) { | ||||
|     ElMessage.success(res.msg); | ||||
|     personnelAllocation.value = false; | ||||
|     getList(); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| onUnmounted(() => { | ||||
|   listeningProject(); | ||||
| @ -1060,6 +1143,7 @@ onUnmounted(() => { | ||||
|  | ||||
| onMounted(() => { | ||||
|   getContractorList(); | ||||
|   getProjectList(); | ||||
| }); | ||||
| </script> | ||||
| <style scoped lang="scss"> | ||||
|  | ||||
		Reference in New Issue
	
	Block a user