shaogen1995 1 день назад
Родитель
Сommit
8f7e49b2e4

+ 25 - 16
node_socket_io/NeiMengGuServer.js

@@ -1,25 +1,25 @@
-const os = require('os');
+const os = require("os");
 
 
 // 获取本机IP地址
 // 获取本机IP地址
 function getLocalIP() {
 function getLocalIP() {
   const interfaces = os.networkInterfaces();
   const interfaces = os.networkInterfaces();
   for (const iface of Object.values(interfaces)) {
   for (const iface of Object.values(interfaces)) {
     for (const config of iface) {
     for (const config of iface) {
-      if (config.family === 'IPv4' && !config.internal) {
+      if (config.family === "IPv4" && !config.internal) {
         return config.address;
         return config.address;
       }
       }
     }
     }
   }
   }
-  return '127.0.0.1';
+  return "127.0.0.1";
 }
 }
 
 
 //数据格式
 //数据格式
 let theme = [
 let theme = [
-  { name: "空中成像", val: "历史" },
-  { name: "文物互动墙", val: "历史" },
-  { name: "透明展示柜A", val: "历史" },
-  { name: "透明展示柜B", val: "历史" },
-  { name: "AI数字人", val: "历史" },
+  { name: "空中成像", val: "历史", switch: "open" },
+  { name: "文物互动墙", val: "历史", switch: "open" },
+  { name: "透明展示柜A", val: "历史", switch: "open" },
+  { name: "透明展示柜B", val: "历史", switch: "open" },
+  { name: "AI数字人", val: "历史", switch: "open" },
 ];
 ];
 
 
 // 通信数据格式
 // 通信数据格式
@@ -52,7 +52,7 @@ let screen = [
 // {type:'screen',data:{ name: "户外裸眼3D", val: "open" }'}
 // {type:'screen',data:{ name: "户外裸眼3D", val: "open" }'}
 
 
 const localIP = getLocalIP();
 const localIP = getLocalIP();
-const port = 12345;
+const port = 8501;
 
 
 const io = require("socket.io")(port, {
 const io = require("socket.io")(port, {
   pingInterval: 30005,
   pingInterval: 30005,
@@ -63,12 +63,12 @@ const io = require("socket.io")(port, {
   serveClient: true,
   serveClient: true,
 });
 });
 
 
-console.log('Socket.IO 服务器已启动!');
-console.log('本机访问地址: http://localhost:' + port);
-console.log('局域网访问地址: http://' + localIP + ':' + port);
+console.log("Socket.IO 服务器已启动!");
+console.log("本机访问地址: http://localhost:" + port);
+console.log("局域网访问地址: http://" + localIP + ":" + port);
 
 
 io.on("connection", (socket) => {
 io.on("connection", (socket) => {
-console.log('✅ 用户通过 Socket.IO 连接成功:', socket.id);
+  console.log("✅ 用户通过 Socket.IO 连接成功:", socket.id);
 
 
   io.emit("UpdateDeviceInfo", { type: "cut", data: theme });
   io.emit("UpdateDeviceInfo", { type: "cut", data: theme });
   io.emit("UpdateDeviceInfo", { type: "show", data: show });
   io.emit("UpdateDeviceInfo", { type: "show", data: show });
@@ -82,21 +82,30 @@ console.log('✅ 用户通过 Socket.IO 连接成功:', socket.id);
         val: name === "all" ? val : v.name === name ? val : v.val,
         val: name === "all" ? val : v.name === name ? val : v.val,
       }));
       }));
       // 然后向前端广播 数据 {type:'cut',data:theme}
       // 然后向前端广播 数据 {type:'cut',data:theme}
-      io.emit("UpdateDeviceInfo", { type: "cut", data: theme });
+      io.emit("UpdateDeviceInfo", { type: "cut", data: theme, code: 0 });
     } else if (info.type === "show") {
     } else if (info.type === "show") {
       show = show.map((v) => ({
       show = show.map((v) => ({
         ...v,
         ...v,
         val: v.name === name ? val : v.val,
         val: v.name === name ? val : v.val,
       }));
       }));
       // 然后向前端广播 数据 {type:'show',data:show}
       // 然后向前端广播 数据 {type:'show',data:show}
-      io.emit("UpdateDeviceInfo", { type: "show", data: show });
+      io.emit("UpdateDeviceInfo", { type: "show", data: show, code: 0 });
     } else if (info.type === "screen") {
     } else if (info.type === "screen") {
       screen = screen.map((v) => ({
       screen = screen.map((v) => ({
         ...v,
         ...v,
         val: v.name === name ? val : v.val,
         val: v.name === name ? val : v.val,
       }));
       }));
       // 然后向前端广播 数据 {type:'screen',data:screen}
       // 然后向前端广播 数据 {type:'screen',data:screen}
-      io.emit("UpdateDeviceInfo", { type: "screen", data: screen });
+      io.emit("UpdateDeviceInfo", { type: "screen", data: screen, code: 0 });
+    } else if (info.type === "switch") {
+      theme = theme.map((v) => ({
+        ...v,
+        switch: name === "all" ? val : v.name === name ? val : v.switch,
+      }));
+      // 然后向前端广播 数据 {type:'cut',data:theme}
+      io.emit("UpdateDeviceInfo", { type: "cut", data: theme, code: 0 });
     }
     }
   });
   });
 });
 });
+
+

+ 12 - 0
node_socket_io/README.md

@@ -0,0 +1,12 @@
+
+
+2.测试堡垒机存放目录
+/Default/腾讯云/项目节点/腾讯云-四维时代-项目测试服务器-111.230.233.212/data/data/node_porject/museum_neimenggu_huhehaote_liudongzhanche
+
+// 启动 pm2 start NeiMengGuServer.js --name neimenggu_liudongzhanche
+
+// 重启 pm2 restart neimenggu_liudongzhanche
+
+// 从 PM2 列表中删除应用:pm2 delete neimenggu_liudongzhanche
+
+// 域名:https://sit-nmgzhanche.4dage.com/websocket

+ 5 - 3
后台管理/README.md

@@ -1,10 +1,12 @@
 1.npm 有问题的话试试用 yarn
 1.npm 有问题的话试试用 yarn
 
 
 2.测试堡垒机存放目录
 2.测试堡垒机存放目录
-/Default/腾讯云/文物专项/腾讯云-文物专项-122.152.225.83/home/data/zhejiang_yiwu_storage_data/backstage
+/Default/腾讯云/项目节点/腾讯云-四维时代-项目测试服务器-111.230.233.212/data/data/museum_neimenggu_huhehaote_liudongzhanche_data
 
 
 3.蓝湖地址
 3.蓝湖地址
-https://lanhuapp.com/web/#/item/project/product?tid=de3e5e3e-a489-4b19-862a-7c87ce113467&pid=ca5138d7-3848-429e-a539-ffc35df132ce&image_id=60d85b80-4274-4d06-a976-d2ad13f841ca&docId=60d85b80-4274-4d06-a976-d2ad13f841ca&docType=axure&versionId=305ef5a7-cce2-4b8c-acea-70ed2d684c78&pageId=868d51a177904c178b4c28de9f0c5615&parentId=48d3f365-46a9-4f97-847f-192c66632085&share_type=quickShare 4.测试域名
-https://sit-wenwucms.4dage.com
+/Default/腾讯云/项目节点/腾讯云-四维时代-项目测试服务器-111.230.233.212/data/data/museum_neimenggu_huhehaote_liudongzhanche_data
+
+4.测试域名
+https://sit-nmgzhanche.4dage.com
 接口地址在后面拼接:/api/doc.html#/home
 接口地址在后面拼接:/api/doc.html#/home
 测试网址在后面拼接:/backstage
 测试网址在后面拼接:/backstage

+ 2 - 1
后台管理/public/myData/index.js

@@ -2,7 +2,8 @@
 const baseUrlTempOne = 'xxxxxxxxxxx'
 const baseUrlTempOne = 'xxxxxxxxxxx'
 
 
 // node_socket_io 服务地址
 // node_socket_io 服务地址
-const nodeSocketUrl = 'http://192.168.20.55:12345'
+// const nodeSocketUrl = 'http://192.168.20.55:8501'
+const nodeSocketUrl = 'https://sit-nmgzhanche.4dage.com/websocket'
 
 
 // 大模型接口地址
 // 大模型接口地址
 const modelUrl = 'xxxxxxxxxxxxxxxxxx'
 const modelUrl = 'xxxxxxxxxxxxxxxxxx'

BIN
后台管理/src/assets/img/icon_close.png


BIN
后台管理/src/assets/img/icon_open.png


BIN
后台管理/src/assets/img/icon_switch_active.png


BIN
后台管理/src/assets/img/icon_switch_normal.png


+ 120 - 0
后台管理/src/pages/A1facility/index.module.scss

@@ -1,4 +1,124 @@
 .A1facility {
 .A1facility {
+  background-color: #edeae0;
+  position: relative;
+
   :global {
   :global {
+    .A1main {
+      width: 100%;
+      height: 100%;
+      background-image: url('../../assets/img/img.png');
+      background-size: 100% 100%;
+      position: absolute;
+      top: -5%;
+      left: -4%;
+    }
+
+    .A1row {
+      position: absolute;
+      display: flex;
+      align-items: center;
+      .A1rtxt {
+        color: #3a3a3a;
+        font-size: 20px;
+        position: relative;
+        padding-left: 15px;
+        font-weight: 700;
+        &::before {
+          content: '';
+          position: absolute;
+          width: 4px;
+          height: 80%;
+          top: 10%;
+          left: 0;
+          background-color: var(--themeColor3);
+        }
+
+        .ant-switch {
+          position: relative;
+          top: -2px;
+        }
+
+        .ant-switch-checked {
+          background-color: var(--themeColor3);
+          &:hover {
+            opacity: 0.8;
+            background-color: var(--themeColor3);
+          }
+        }
+      }
+      .A1Color {
+        position: absolute;
+        top: 40px;
+        left: 15px;
+        display: flex;
+        .A1ColorRow {
+          cursor: pointer;
+          width: 24px;
+          height: 24px;
+          transition: all 0.3s;
+          border-radius: 21px;
+          margin-right: 15px;
+          position: relative;
+          opacity: 0.3;
+
+          &:hover {
+            opacity: 1;
+          }
+          & > div {
+            position: absolute;
+            left: 3px;
+            bottom: 3px;
+            width: 18px;
+            height: 18px;
+            border-radius: 9px;
+          }
+        }
+        .A1ColorRowShow {
+          background-color: #fff;
+          opacity: 1;
+        }
+      }
+    }
+
+    .A1rowAll {
+      top: 5%;
+      right: 5%;
+      .A1Color .A1ColorRow {
+        opacity: 0.5;
+        .A1ColorRowTxt {
+          text-align: center;
+          position: absolute;
+          left: 50%;
+          bottom: -24px;
+          transform: translateX(-50%);
+          width: 50px;
+        }
+      }
+    }
+
+    .A1btn {
+      position: absolute;
+      right: 3%;
+      bottom: 2%;
+      & > div {
+        cursor: pointer;
+        width: 140px;
+        height: 50px;
+        background-color: #fff;
+        border-radius: 25px;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        margin-bottom: 16px;
+        opacity: 1;
+        transition: all 0.3s;
+        &:hover {
+          opacity: 0.8;
+        }
+        &:nth-of-type(1) {
+          color: var(--themeColor3);
+        }
+      }
+    }
   }
   }
 }
 }

+ 94 - 13
后台管理/src/pages/A1facility/index.tsx

@@ -2,31 +2,112 @@ import React from 'react'
 import styles from './index.module.scss'
 import styles from './index.module.scss'
 import { useSelector } from 'react-redux'
 import { useSelector } from 'react-redux'
 import { RootState } from '@/store'
 import { RootState } from '@/store'
-import { Button } from 'antd'
+import { Switch } from 'antd'
+import classNames from 'classnames'
+import { CheckOutlined, ExclamationCircleOutlined } from '@ant-design/icons'
 
 
-const typeArr = ['历史', '红色', '非遗']
+const typeArr = [
+  { name: '历史', color: '#e7ba5c' },
+  { name: '红色', color: '#a02e35' },
+  { name: '非遗', color: '#2a3f94' }
+]
+
+const rowLoc: any = {
+  空中成像: ['70%', '18%'],
+  文物互动墙: ['15%', '44%'],
+  透明展示柜A: ['84%', '33%'],
+  透明展示柜B: ['84%', '45%'],
+  AI数字人: ['72%', '82%']
+}
+
+const btnArr = [
+  { name: '一键开启', icon: <CheckOutlined /> },
+  { name: '一键关闭', icon: <ExclamationCircleOutlined /> }
+]
 
 
 function A1facility() {
 function A1facility() {
   const { theme, Ref } = useSelector((state: RootState) => state.socket)
   const { theme, Ref } = useSelector((state: RootState) => state.socket)
 
 
   return (
   return (
     <div className={styles.A1facility}>
     <div className={styles.A1facility}>
-      {theme.map(v => (
-        <div key={v.name}>
-          {v.name}-------
-          {typeArr.map(c => (
-            <Button
-              key={c}
-              type={c === v.val ? 'primary' : 'default'}
+      <div className='A1main'>
+        {theme.map(v => (
+          <div
+            key={v.name}
+            className='A1row'
+            style={{ top: rowLoc[v.name][0], left: rowLoc[v.name][1] }}
+          >
+            <div className='A1rtxt'>
+              {v.name}&nbsp;
+              <Switch
+                checked={v.switch === 'open'}
+                onChange={() =>
+                  Ref.emit('ChangeDeviceInfo', {
+                    type: 'switch',
+                    data: { name: v.name, val: v.switch === 'open' ? 'close' : 'open' }
+                  })
+                }
+              />
+            </div>
+            <div className='A1Color'>
+              {typeArr.map(item => (
+                <div
+                  key={item.name}
+                  onClick={() => {
+                    Ref.emit('ChangeDeviceInfo', {
+                      type: 'cut',
+                      data: { name: v.name, val: item.name }
+                    })
+                  }}
+                  className={classNames('A1ColorRow', item.name === v.val ? 'A1ColorRowShow' : '')}
+                >
+                  <div style={{ backgroundColor: item.color }}></div>
+                </div>
+              ))}
+            </div>
+          </div>
+        ))}
+      </div>
+
+      {/* 一键切换主题 */}
+      <div className='A1row A1rowAll'>
+        <div className='A1rtxt'>一键切换主题</div>
+        <div className='A1Color'>
+          {typeArr.map(item => (
+            <div
+              key={item.name}
               onClick={() => {
               onClick={() => {
-                Ref.emit('ChangeDeviceInfo', { type: 'cut', data: { name: v.name, val: c } })
+                Ref.emit('ChangeDeviceInfo', {
+                  type: 'cut',
+                  data: { name: 'all', val: item.name }
+                })
               }}
               }}
+              className='A1ColorRow'
             >
             >
-              {c}
-            </Button>
+              <div style={{ backgroundColor: item.color }}></div>
+              <p className='A1ColorRowTxt'>{item.name}</p>
+            </div>
           ))}
           ))}
         </div>
         </div>
-      ))}
+      </div>
+
+      {/* 一键开关 */}
+      <div className='A1btn'>
+        {btnArr.map(item => (
+          <div
+            key={item.name}
+            onClick={() => {
+              Ref.emit('ChangeDeviceInfo', {
+                type: 'switch',
+                data: { name: 'all', val: item.name === '一键开启' ? 'open' : 'close' }
+              })
+            }}
+          >
+            {item.icon}
+            &emsp;{item.name}
+          </div>
+        ))}
+      </div>
     </div>
     </div>
   )
   )
 }
 }

+ 3 - 1
后台管理/src/pages/Layout/index.tsx

@@ -338,7 +338,9 @@ function Layout() {
         </>
         </>
       ) : (
       ) : (
         <div className='layLoding'>
         <div className='layLoding'>
-          {socketTime ? 'socket服务连接中...' : 'socket服务连接失败,请联系管理员'}
+          {socketTime
+            ? 'socket服务连接中...'
+            : 'socket服务连接失败,请联系管理员,或者尝试刷新页面'}
         </div>
         </div>
       )}
       )}
     </div>
     </div>

+ 8 - 0
后台管理/src/pages/Layout/useSocket.ts

@@ -1,5 +1,6 @@
 // 2. 创建自定义Hook:src/hooks/useSocket.ts
 // 2. 创建自定义Hook:src/hooks/useSocket.ts
 import store from '@/store'
 import store from '@/store'
+import { MessageFu } from '@/utils/message'
 import { useEffect, useRef, useState } from 'react'
 import { useEffect, useRef, useState } from 'react'
 import * as io from 'socket.io-client'
 import * as io from 'socket.io-client'
 
 
@@ -27,6 +28,12 @@ export function useSocket(serverUrl: string) {
       setIsConnected(true)
       setIsConnected(true)
     })
     })
 
 
+    // 监听连接断开事件
+    socket.on('disconnect', () => {
+      console.log('❌ 连接已断开')
+      setIsConnected(false)
+    })
+
     // 监听消息事件
     // 监听消息事件
     socket.on('UpdateDeviceInfo', (info: any) => {
     socket.on('UpdateDeviceInfo', (info: any) => {
       let type: 'theme' | 'show' | 'screen' = 'theme'
       let type: 'theme' | 'show' | 'screen' = 'theme'
@@ -35,6 +42,7 @@ export function useSocket(serverUrl: string) {
       else if (info.type === 'screen') type = 'screen'
       else if (info.type === 'screen') type = 'screen'
 
 
       store.dispatch({ type: `socket/${type}`, payload: info.data })
       store.dispatch({ type: `socket/${type}`, payload: info.data })
+      if (info.code === 0) MessageFu.success('操作成功')
 
 
       // console.log('xxxxxxxx接受后端消息', msg)
       // console.log('xxxxxxxx接受后端消息', msg)
     })
     })

+ 1 - 1
后台管理/src/store/reducer/socket.ts

@@ -1,4 +1,4 @@
-export type SocketType = { name: string; val: string }
+export type SocketType = { name: string; val: string; switch: 'open' | 'close' }
 
 
 // 初始化状态
 // 初始化状态
 const initState = {
 const initState = {