Bläddra i källkod

Merge remote-tracking branch 'origin/dev' into dev

hmm 8 månader sedan
förälder
incheckning
7fd1b79261

+ 2 - 2
.env.development

@@ -9,7 +9,7 @@ VITE_APP_BASE_API = 'http://10.181.7.236:9988'
 VITE_APP_BASE_API2 = 'http://10.181.7.235/'
 
 # 应用访问路径 例如使用前缀 /admin/
-VITE_APP_CONTEXT_PATH = '/'
+VITE_APP_CONTEXT_PATH = '/yjdp/'
 
 # 监控地址
 VITE_APP_MONITOR_ADMIN = 'http://localhost:9090/admin/applications'
@@ -17,7 +17,7 @@ VITE_APP_MONITOR_ADMIN = 'http://localhost:9090/admin/applications'
 # SnailJob 控制台地址
 VITE_APP_SNAILJOB_ADMIN = 'http://localhost:8800/snail-job'
 
-VITE_APP_PORT = 80
+VITE_APP_PORT = 8086
 
 # 接口加密功能开关(如需关闭 后端也必须对应关闭)
 VITE_APP_ENCRYPT = false

+ 9 - 0
package-lock.json

@@ -51,6 +51,7 @@
         "vue-i18n": "9.10.2",
         "vue-router": "4.3.2",
         "vue-types": "5.1.1",
+        "vue3-print-nb": "^0.1.4",
         "vuex": "^4.1.0",
         "vxe-table": "4.5.22",
         "xlsx": "^0.18.5"
@@ -13550,6 +13551,14 @@
         "node": "^10 || ^12 || >=14"
       }
     },
+    "node_modules/vue3-print-nb": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmmirror.com/vue3-print-nb/-/vue3-print-nb-0.1.4.tgz",
+      "integrity": "sha512-LExI7viEzplR6ZKQ2b+V4U0cwGYbVD4fut/XHvk3UPGlT5CcvIGs6VlwGp107aKgk6P8Pgx4rco3Rehv2lti3A==",
+      "dependencies": {
+        "vue": "^3.0.5"
+      }
+    },
     "node_modules/vuex": {
       "version": "4.1.0",
       "resolved": "https://registry.npmjs.org/vuex/-/vuex-4.1.0.tgz",

+ 1 - 0
package.json

@@ -59,6 +59,7 @@
     "vue-i18n": "9.10.2",
     "vue-router": "4.3.2",
     "vue-types": "5.1.1",
+    "vue3-print-nb": "^0.1.4",
     "vuex": "^4.1.0",
     "vxe-table": "4.5.22",
     "xlsx": "^0.18.5"

+ 4 - 0
public/print.css

@@ -0,0 +1,4 @@
+.print-title {
+  color: #000000 !important;
+  font-size: 24px;
+}

+ 4 - 0
src/main.ts

@@ -37,6 +37,9 @@ import 'vxe-table/lib/style.css';
 VXETable.config({
   zIndex: 999999
 });
+// 打印插件
+import print from 'vue3-print-nb';
+
 
 // 修改 el-dialog 默认点击遮照为不关闭
 import { ElDialog } from 'element-plus';
@@ -51,6 +54,7 @@ app.use(store);
 app.use(i18n);
 app.use(VXETable);
 app.use(plugins);
+app.use(print);
 // 自定义指令
 directive(app);
 

+ 4 - 0
src/types/auto-imports.d.ts

@@ -78,6 +78,7 @@ declare global {
   const onStartTyping: typeof import('@vueuse/core')['onStartTyping']
   const onUnmounted: typeof import('vue')['onUnmounted']
   const onUpdated: typeof import('vue')['onUpdated']
+  const onWatcherCleanup: typeof import('vue')['onWatcherCleanup']
   const pausableWatch: typeof import('@vueuse/core')['pausableWatch']
   const provide: typeof import('vue')['provide']
   const provideLocal: typeof import('@vueuse/core')['provideLocal']
@@ -189,6 +190,7 @@ declare global {
   const useFullscreen: typeof import('@vueuse/core')['useFullscreen']
   const useGamepad: typeof import('@vueuse/core')['useGamepad']
   const useGeolocation: typeof import('@vueuse/core')['useGeolocation']
+  const useId: typeof import('vue')['useId']
   const useIdle: typeof import('@vueuse/core')['useIdle']
   const useImage: typeof import('@vueuse/core')['useImage']
   const useInfiniteScroll: typeof import('@vueuse/core')['useInfiniteScroll']
@@ -205,6 +207,7 @@ declare global {
   const useMediaQuery: typeof import('@vueuse/core')['useMediaQuery']
   const useMemoize: typeof import('@vueuse/core')['useMemoize']
   const useMemory: typeof import('@vueuse/core')['useMemory']
+  const useModel: typeof import('vue')['useModel']
   const useMounted: typeof import('@vueuse/core')['useMounted']
   const useMouse: typeof import('@vueuse/core')['useMouse']
   const useMouseInElement: typeof import('@vueuse/core')['useMouseInElement']
@@ -252,6 +255,7 @@ declare global {
   const useStyleTag: typeof import('@vueuse/core')['useStyleTag']
   const useSupported: typeof import('@vueuse/core')['useSupported']
   const useSwipe: typeof import('@vueuse/core')['useSwipe']
+  const useTemplateRef: typeof import('vue')['useTemplateRef']
   const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList']
   const useTextDirection: typeof import('@vueuse/core')['useTextDirection']
   const useTextSelection: typeof import('@vueuse/core')['useTextSelection']

+ 18 - 0
src/types/components.d.ts

@@ -20,6 +20,8 @@ declare module 'vue' {
     ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
     ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
     ElButton: typeof import('element-plus/es')['ElButton']
+    ElCard: typeof import('element-plus/es')['ElCard']
+    ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
     ElCol: typeof import('element-plus/es')['ElCol']
     ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
     ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
@@ -40,17 +42,28 @@ declare module 'vue' {
     ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
     ElOption: typeof import('element-plus/es')['ElOption']
     ElPagination: typeof import('element-plus/es')['ElPagination']
+    ElPopover: typeof import('element-plus/es')['ElPopover']
+    ElRadio: typeof import('element-plus/es')['ElRadio']
+    ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
     ElRow: typeof import('element-plus/es')['ElRow']
     ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
     ElSelect: typeof import('element-plus/es')['ElSelect']
     ElSkeleton: typeof import('element-plus/es')['ElSkeleton']
     ElSkeletonItem: typeof import('element-plus/es')['ElSkeletonItem']
     ElSlider: typeof import('element-plus/es')['ElSlider']
+    ElStep: typeof import('element-plus/es')['ElStep']
+    ElSteps: typeof import('element-plus/es')['ElSteps']
     ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
     ElSwitch: typeof import('element-plus/es')['ElSwitch']
+    ElTable: typeof import('element-plus/es')['ElTable']
+    ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
+    ElTabPane: typeof import('element-plus/es')['ElTabPane']
+    ElTabs: typeof import('element-plus/es')['ElTabs']
     ElTimeline: typeof import('element-plus/es')['ElTimeline']
     ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
+    ElTooltip: typeof import('element-plus/es')['ElTooltip']
     ElTree: typeof import('element-plus/es')['ElTree']
+    ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect']
     ElUpload: typeof import('element-plus/es')['ElUpload']
     FileUpload: typeof import('./../components/FileUpload/index.vue')['default']
     FooterSection: typeof import('./../components/FooterSection/index.vue')['default']
@@ -61,6 +74,7 @@ declare module 'vue' {
     HikvisionPlayer: typeof import('./../components/HKVideo/hikvision-player.vue')['default']
     HKVideo: typeof import('./../components/HKVideo/index.vue')['default']
     IconSelect: typeof import('./../components/IconSelect/index.vue')['default']
+    IEpUploadFilled: typeof import('~icons/ep/upload-filled')['default']
     IFrame: typeof import('./../components/iFrame/index.vue')['default']
     ImagePreview: typeof import('./../components/ImagePreview/index.vue')['default']
     ImageUpload: typeof import('./../components/ImageUpload/index.vue')['default']
@@ -93,6 +107,10 @@ declare module 'vue' {
     VideoContainer: typeof import('./../components/HKVideo/video-container.vue')['default']
     VideoContainer2: typeof import('./../components/HKVideo/video-container2.vue')['default']
     YMap: typeof import('./../components/Map/YMap.vue')['default']
+    YMapold: typeof import('./../components/Map/YMapold.vue')['default']
     YztMap: typeof import('./../components/Map/YztMap/index.vue')['default']
   }
+  export interface ComponentCustomProperties {
+    vLoading: typeof import('element-plus/es')['ElLoadingDirective']
+  }
 }

+ 49 - 4
src/views/emergencyCommandMap/RightSection/JointDuty.vue

@@ -27,15 +27,25 @@
     </div>
   </div>
   <Dialog v-model="showQrCode" title="签到码" type="sm" hide-footer>
-    <div style="display: flex; justify-content: center; align-items: center; width: 100%; height: 100%">
-      <img :src="qrCodeUrl" alt="" style="width: 900px; height: 900px" />
+    <div style="display: flex; flex-direction: column; justify-content: center; align-items: center; width: 100%; height: 100%">
+      <div id="qrCodeBox" style="display: flex; flex-direction: column; justify-content: center; align-items: center">
+        <div class="print-title" style="margin: 30px 0; color: #ffffff">请联合值守人员通过粤政易扫描二维码进行签到、签退</div>
+        <img :src="qrCodeUrl" alt="" style="width: 800px; height: 800px" />
+      </div>
+      <div class="flex" style="margin-top: 20px">
+        <div class="common-btn-primary" v-print="printObj">打印</div>
+        <div class="common-btn-primary" @click="handleDownLoad">下载</div>
+      </div>
     </div>
   </Dialog>
 </template>
 
 <script lang="ts" setup>
 import { getCheckinList } from '@/api/emergencyCommandMap/JointDuty';
-
+import print from 'vue3-print-nb';
+directives: {
+  print;
+}
 const route = useRoute();
 const tableHeader = reactive([
   {
@@ -54,11 +64,46 @@ const tableHeader = reactive([
 const listData = ref([]);
 let eventId = ref('');
 let qrCodeUrl = ref('');
+let printObj = reactive({
+  id: 'qrCodeBox', // 这里是要打印元素的ID
+  popTitle: '',
+  extraCss: '/print.css'
+});
 const showQrCode = ref(false);
 const handleShowQrCode = () => {
   showQrCode.value = true;
 };
-
+const base64ToBlob = (code) => {
+  let parts = code.split(';base64,');
+  let contentType = parts[0].split(':')[1];
+  let raw = window.atob(parts[1]);
+  let rawLength = raw.length;
+  let uInt8Array = new Uint8Array(rawLength);
+  for (let i = 0; i < rawLength; ++i) {
+    uInt8Array[i] = raw.charCodeAt(i);
+  }
+  return new Blob([uInt8Array], { type: contentType });
+};
+// 下载
+const handleDownLoad = () => {
+  let image = new Image();
+  image.crossOrigin = '';
+  image.src = qrCodeUrl.value;
+  image.onload = () => {
+    let canvas = document.createElement('canvas');
+    canvas.width = 370;
+    canvas.height = 370;
+    let ctx = canvas.getContext('2d');
+    ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
+    let dataUrl = canvas.toDataURL();
+    const a = document.createElement('a');
+    a.href = dataUrl;
+    a.download = '签到码.png';
+    document.body.appendChild(a);
+    a.click();
+    document.body.removeChild(a);
+  };
+};
 onMounted(() => {
   eventId.value = route.query.event_id as string;
   /*

+ 2 - 2
src/views/emergencyCommandMap/RightSection/RenWuGenZong.vue

@@ -96,7 +96,7 @@ const toggleScroll = () => {
 };
 
 // 设置定时器
-const fetchInterval = 1000; // 每1秒刷新一次
+const fetchInterval = process.env.NODE_ENV === 'development' ? 60000 : 3000; // 每60秒刷新一次(刷新太频繁影响调试)
 let intervalId: number | null = null;
 
 const startFetchingData = () => {
@@ -231,7 +231,7 @@ watch(
 }
 .more {
   position: absolute;
-  bottom: 10px; /* 根据需要调整位置 */
+  bottom: 20px; /* 根据需要调整位置 */
   right: 50%; /* 根据需要调整位置 */
   transform: translateX(50%);
   color: #1890ff;

+ 6 - 3
src/views/emergencyCommandMap/RightSection/RightTop.vue

@@ -67,6 +67,9 @@ const activeTab = ref('任务跟踪');
 
 const setActiveTab = (id) => {
   activeTab.value = id;
+  if(id === '预案通知' && notifications.value.length === 0) {
+    fetchData();
+  }
 };
 
 // 定义 notifications
@@ -153,7 +156,7 @@ watch(
   width: 100%;
 
   .table-content {
-    height: 600px;
+    height: 550px;
     overflow-y: auto;
 
     .box1 {
@@ -243,7 +246,7 @@ watch(
 
         .box-content {
           color: #fff;
-          width: 80%;
+          width: 100%;
           font-size: 38px;
           line-height: 1.5;
           margin-top: 10px;
@@ -264,7 +267,7 @@ watch(
   .card-content {
     display: flex;
     flex-wrap: wrap;
-    padding: 0 80px;
+    padding: 0 0 0 80px;
     width: 100%;
   }
 }

+ 15 - 6
src/views/emergencyCommandMap/RightSection/StartPlan.vue

@@ -149,7 +149,7 @@ const onStartPlan = async () => {
     } else if (error.response && error.response.status) {
       ElMessage.error(`启动预案时发生错误,HTTP 状态码:${error.response.status}`);
     } else {
-      ElMessage.error('启动预案时发生未知错误,请稍后再试。');
+      // ElMessage.error('启动预案时发生未知错误,请稍后再试。');
     }
   }
 };
@@ -339,10 +339,12 @@ h2 {
   display: flex;
   margin-top: 30px;
   .tabs2 {
-    height: 620px;
+    height: 560px;
+    
     overflow-y: auto;
     .tab {
-      width: 399px;
+      /*width: 399px;*/
+      width: 600px;
       height: 70px;
       background: url('@/assets/images/plan/tab2.png') no-repeat;
       display: flex;
@@ -354,6 +356,9 @@ h2 {
       margin-top: 60px;
       position: relative;
       cursor: pointer;
+      .text {
+        white-space:nowrap;
+      }
       &::before {
         content: '';
         width: 2px;
@@ -370,7 +375,8 @@ h2 {
         }
       }
       &:hover {
-        width: 439px;
+        /*width: 439px;*/
+        width: 600px;
         background: url('@/assets/images/plan/tab2Active.png') no-repeat;
         .text {
           /* 设置字体透明 */
@@ -387,7 +393,8 @@ h2 {
       }
     }
     .tab-active2 {
-      width: 439px;
+      /*width: 439px;*/
+      width: 600px;
       background: url('@/assets/images/plan/tab2Active.png') no-repeat;
       .text {
         /* 设置字体透明 */
@@ -404,7 +411,9 @@ h2 {
     }
   }
   .tab-content2 {
-    height: 620px;
+    padding-left:20px;
+    height: 560px;
+    width: 90%;
     overflow-y: auto;
   }
 }

+ 4 - 3
src/views/emergencyCommandMap/RightSection/TaskDelivery.vue

@@ -69,7 +69,7 @@ const fetchDataWithLoading = async () => {
       ElMessage.error('未能从服务器获取有效任务数据');
     }
   } catch (error) {
-    ElMessage.error('获取任务数据失败');
+    // ElMessage.error('获取任务数据失败');
   } finally {
     loading.value = false;
   }
@@ -80,14 +80,15 @@ const sendTasks = () => {
     ElMessage.error('事件ID未定义,无法发送任务!');
     return;
   }
+  
   sendTask({ eventId: props.eventId })
     .then(() => {
       ElMessage.success('任务已成功发送!');
       closeDialog(); // 使用关闭方法
     })
     .catch(() => {
-      ElMessage.error('发送任务失败,请稍后再试!');
-    });
+      //ElMessage.error('发送任务失败,请稍后再试!');
+    })
 };
 </script>
 

+ 5 - 0
src/views/emergencyCommandMap/RightSection/index.vue

@@ -92,6 +92,11 @@ const getEventIdFromUrl = () => {
 const startPlan = async() => {
   const response = await getEventDetail({ event_id: eventId.value })
   const event_data = (response.data);
+  if(event_data.emergency_notify_count > 0) {
+    ElMessage.warning('预案已启动');
+    return;
+  }
+
   if (event_data.del_flag === '0') {
     // 正式事件
     startPlanState.title = '启动预案';

+ 25 - 22
src/views/routineCommandMap/PositionMap.vue

@@ -163,8 +163,8 @@ function handleInput(flag) {
 
   if (!placeSearch) {
     placeSearch = new amap.PlaceSearch({
-      pageSize: 10, // 每页条数,默认10,范围1-50
-      pageIndex: 1, // 页码
+      pageSize: pageSize.value, // 每页条数,默认10,范围1-50
+      pageIndex: pageNum.value, // 页码
       extensions: 'all' // 默认base,返回基本地址信息;all:返回详细信息
     });
   }
@@ -214,27 +214,32 @@ function handlePanTo(index) {
   setMarks(lnglat);
   closeSearchList();
 }
-const initMap = async () => {
-  let position = [110.93154257997, 21.669064031332];
-  const AMap = await AMapLoader.load({
+const initMap = () => {
+  let position = [110.925175, 21.678955];
+  AMapLoader.load({
     key: '30d3d8448efd68cb0b284549fd41adcf', // 申请好的Web端开发者Key,首次调用 load 时必填
     version: '2.0', // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
     plugins: ['AMap.PlaceSearch', 'AMap.ContextMenu', 'AMap.PolygonEditor', 'AMap.Geocoder'] // 插件列表
+  }).then((res) => {
+    AMap = res;
+    map = new AMap.Map('map', {
+      viewMode: '3D', //是否为3D地图模式
+      center: position,
+      zoom: 15
+    });
+    amap = AMap;
+    geocoder = new AMap.Geocoder({
+      // city 指定进行编码查询的城市,支持传入城市名、adcode 和 citycode
+      city: '010'
+    });
+    // 创建右键菜单
+    ContextMenu();
+    map.on('rightclick', handleRightclick);
+    handleResize();
+    map.on('complete', () => {
+      setMarks(position);
+    });
   });
-  map = new AMap.Map('map', {
-    viewMode: '3D', //是否为3D地图模式
-    center: position,
-    zoom: 15
-  });
-  amap = AMap;
-  geocoder = new AMap.Geocoder({
-    // city 指定进行编码查询的城市,支持传入城市名、adcode 和 citycode
-    city: '010'
-  });
-  // 创建右键菜单
-  ContextMenu();
-  map.on('rightclick', handleRightclick);
-  handleResize();
 };
 function ContextMenu() {
   contextMenu = new AMap.ContextMenu();
@@ -286,9 +291,7 @@ function handleResize() {
   const containerHeight = containerRef.value.clientHeight * (document.body.clientHeight / 2520);
   width.value = containerWidth + 'px';
   height.value = containerHeight + 'px';
-  nextTick(() => {
-    map.resize();
-  });
+  map.resize();
 }
 function submit() {
   queryFormRef.value.validate((valid) => {