commit
7311f1ed36
@ -0,0 +1,24 @@ |
|||||||
|
# Logs |
||||||
|
logs |
||||||
|
*.log |
||||||
|
npm-debug.log* |
||||||
|
yarn-debug.log* |
||||||
|
yarn-error.log* |
||||||
|
pnpm-debug.log* |
||||||
|
lerna-debug.log* |
||||||
|
|
||||||
|
node_modules |
||||||
|
dist |
||||||
|
dist-ssr |
||||||
|
*.local |
||||||
|
|
||||||
|
# Editor directories and files |
||||||
|
.vscode/* |
||||||
|
!.vscode/extensions.json |
||||||
|
.idea |
||||||
|
.DS_Store |
||||||
|
*.suo |
||||||
|
*.ntvs* |
||||||
|
*.njsproj |
||||||
|
*.sln |
||||||
|
*.sw? |
@ -0,0 +1,20 @@ |
|||||||
|
module.exports = { |
||||||
|
root: true, |
||||||
|
env: { browser: true, es2020: true }, |
||||||
|
extends: [ |
||||||
|
'eslint:recommended', |
||||||
|
'plugin:react/recommended', |
||||||
|
'plugin:react/jsx-runtime', |
||||||
|
'plugin:react-hooks/recommended', |
||||||
|
], |
||||||
|
ignorePatterns: ['dist', '.eslintrc.cjs'], |
||||||
|
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, |
||||||
|
settings: { react: { version: '18.2' } }, |
||||||
|
plugins: ['react-refresh'], |
||||||
|
rules: { |
||||||
|
'react-refresh/only-export-components': [ |
||||||
|
'warn', |
||||||
|
{ allowConstantExport: true }, |
||||||
|
], |
||||||
|
}, |
||||||
|
} |
@ -0,0 +1,24 @@ |
|||||||
|
# Logs |
||||||
|
logs |
||||||
|
*.log |
||||||
|
npm-debug.log* |
||||||
|
yarn-debug.log* |
||||||
|
yarn-error.log* |
||||||
|
pnpm-debug.log* |
||||||
|
lerna-debug.log* |
||||||
|
|
||||||
|
node_modules |
||||||
|
dist |
||||||
|
dist-ssr |
||||||
|
*.local |
||||||
|
|
||||||
|
# Editor directories and files |
||||||
|
.vscode/* |
||||||
|
!.vscode/extensions.json |
||||||
|
.idea |
||||||
|
.DS_Store |
||||||
|
*.suo |
||||||
|
*.ntvs* |
||||||
|
*.njsproj |
||||||
|
*.sln |
||||||
|
*.sw? |
@ -0,0 +1,8 @@ |
|||||||
|
# React + Vite |
||||||
|
|
||||||
|
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. |
||||||
|
|
||||||
|
Currently, two official plugins are available: |
||||||
|
|
||||||
|
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh |
||||||
|
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh |
@ -0,0 +1,13 @@ |
|||||||
|
<!doctype html> |
||||||
|
<html lang="zh"> |
||||||
|
<head> |
||||||
|
<meta charset="UTF-8" /> |
||||||
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> |
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
||||||
|
<title>实时通信</title> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
<div id="root"></div> |
||||||
|
<script type="module" src="/src/main.jsx"></script> |
||||||
|
</body> |
||||||
|
</html> |
@ -0,0 +1,29 @@ |
|||||||
|
{ |
||||||
|
"name": "client", |
||||||
|
"private": true, |
||||||
|
"version": "0.0.0", |
||||||
|
"type": "module", |
||||||
|
"scripts": { |
||||||
|
"dev": "vite", |
||||||
|
"build": "vite build", |
||||||
|
"lint": "eslint client --ext js,jsx --report-unused-disable-directives --max-warnings 0", |
||||||
|
"preview": "vite preview" |
||||||
|
}, |
||||||
|
"dependencies": { |
||||||
|
"localforage": "^1.10.0", |
||||||
|
"react": "^18.2.0", |
||||||
|
"react-dom": "^18.2.0", |
||||||
|
"react-router-dom": "^6.15.0", |
||||||
|
"sass": "^1.66.1" |
||||||
|
}, |
||||||
|
"devDependencies": { |
||||||
|
"@types/react": "^18.2.15", |
||||||
|
"@types/react-dom": "^18.2.7", |
||||||
|
"@vitejs/plugin-react": "^4.0.3", |
||||||
|
"eslint": "^8.45.0", |
||||||
|
"eslint-plugin-react": "^7.32.2", |
||||||
|
"eslint-plugin-react-hooks": "^4.6.0", |
||||||
|
"eslint-plugin-react-refresh": "^0.4.3", |
||||||
|
"vite": "^4.4.5" |
||||||
|
} |
||||||
|
} |
File diff suppressed because it is too large
Load Diff
After Width: | Height: | Size: 1.5 KiB |
@ -0,0 +1,11 @@ |
|||||||
|
import { useState } from 'react' |
||||||
|
import {useRoutes} from "react-router-dom"; |
||||||
|
import {routerList} from "./route/index.jsx"; |
||||||
|
|
||||||
|
function App() { |
||||||
|
const [count, setCount] = useState(0) |
||||||
|
|
||||||
|
return useRoutes(routerList) |
||||||
|
} |
||||||
|
|
||||||
|
export default App |
@ -0,0 +1,22 @@ |
|||||||
|
html,body,#root{ |
||||||
|
position: relative; |
||||||
|
height: 100%; |
||||||
|
width: 100%; |
||||||
|
margin: 0; |
||||||
|
padding: 0; |
||||||
|
} |
||||||
|
#root{ |
||||||
|
display: flex; |
||||||
|
overflow: hidden; |
||||||
|
& > div{ |
||||||
|
position: relative; |
||||||
|
overflow: hidden; |
||||||
|
} |
||||||
|
& > div:first-child{ |
||||||
|
flex-shrink: 0; |
||||||
|
width: 240px; |
||||||
|
} |
||||||
|
& > div:last-child{ |
||||||
|
flex: 1; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,13 @@ |
|||||||
|
import React from 'react' |
||||||
|
import ReactDOM from 'react-dom/client' |
||||||
|
import { BrowserRouter } from 'react-router-dom' |
||||||
|
import App from "./App.jsx"; |
||||||
|
import './assets/index.scss' |
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById('root')).render( |
||||||
|
<React.StrictMode> |
||||||
|
<BrowserRouter> |
||||||
|
<App/> |
||||||
|
</BrowserRouter> |
||||||
|
</React.StrictMode>, |
||||||
|
) |
@ -0,0 +1,37 @@ |
|||||||
|
// | ------------------------------------------------------------ |
||||||
|
// | @版本: version 0.1 |
||||||
|
// | @创建人: 【Nie-x7129】 |
||||||
|
// | @E-mail: x71291@outlook.com |
||||||
|
// | @文件描述: index.jsx | 路由表 |
||||||
|
// | @创建时间: 2023-08-30 15:43 |
||||||
|
// | @更新时间: 2023-08-30 15:43 |
||||||
|
// | @修改记录: |
||||||
|
// | -*-*-*- (时间--修改人--修改说明) -*-*-*- |
||||||
|
// | = |
||||||
|
// | ------------------------------------------------------------ |
||||||
|
|
||||||
|
import { Navigate } from 'react-router-dom' |
||||||
|
import Home from "../views/Home/index.jsx"; |
||||||
|
import Signin from "../views/Signin/index.jsx"; |
||||||
|
import WebSocketClient from "../views/WebSocket/index.jsx"; |
||||||
|
|
||||||
|
export const routerList = [ |
||||||
|
{ |
||||||
|
path:'/', |
||||||
|
element: <Navigate to = '/signin'/> |
||||||
|
}, |
||||||
|
{ |
||||||
|
path:'/signin', |
||||||
|
element: <Signin/> |
||||||
|
}, |
||||||
|
{ |
||||||
|
path:'/home', |
||||||
|
element: <Home/>, |
||||||
|
children:[ |
||||||
|
{ |
||||||
|
path:'websocket', |
||||||
|
element: <WebSocketClient/> |
||||||
|
}, |
||||||
|
] |
||||||
|
}, |
||||||
|
] |
@ -0,0 +1,45 @@ |
|||||||
|
// | ------------------------------------------------------------ |
||||||
|
// | @版本: version 0.1 |
||||||
|
// | @创建人: 【Nie-x7129】 |
||||||
|
// | @E-mail: x71291@outlook.com |
||||||
|
// | @文件描述: Home.jsx | |
||||||
|
// | @创建时间: 2023-08-30 15:52 |
||||||
|
// | @更新时间: 2023-08-30 15:52 |
||||||
|
// | @修改记录: |
||||||
|
// | -*-*-*- (时间--修改人--修改说明) -*-*-*- |
||||||
|
// | = |
||||||
|
// | ------------------------------------------------------------ |
||||||
|
|
||||||
|
import {Outlet, NavLink, useLocation} from "react-router-dom"; |
||||||
|
import css from './index.module.scss' |
||||||
|
import {useEffect, useState} from "react"; |
||||||
|
|
||||||
|
export default function Home(){ |
||||||
|
// @ host - String - 描述:ws链接地址 |
||||||
|
const [host, setHost] = useState(''); |
||||||
|
// @ nickname - String - 描述:ws昵称 |
||||||
|
const [nickname, setNickName] = useState(''); |
||||||
|
// @ location - String - 描述:当前路由信息 |
||||||
|
const [location, setLocation] = useState(useLocation()); |
||||||
|
useEffect(() => { |
||||||
|
setHost(window.localStorage.getItem('wsHost')); |
||||||
|
setNickName(window.localStorage.getItem('wsNickname')) |
||||||
|
}, [0]); |
||||||
|
return <> |
||||||
|
<div className={css.left}> |
||||||
|
<div className={css.info}> |
||||||
|
<header>个人信息</header> |
||||||
|
<div>昵称: {nickname}</div> |
||||||
|
<div>链接地址: {host}</div> |
||||||
|
</div> |
||||||
|
<div className={css.server}> |
||||||
|
<header>服务</header> |
||||||
|
<NavLink to={'websocket'} className={({ isActive }) => isActive ? css.choose : ''}>WebSocket</NavLink> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div className={css.right}> |
||||||
|
<header>{location?.pathname && location?.pathname?.split('/').slice(-1).toString().toUpperCase()}</header> |
||||||
|
<div><Outlet/></div> |
||||||
|
</div> |
||||||
|
</> |
||||||
|
} |
@ -0,0 +1,76 @@ |
|||||||
|
.left{ |
||||||
|
position: relative; |
||||||
|
overflow: hidden; |
||||||
|
height: 100%; |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
box-sizing: border-box; |
||||||
|
padding: 10px; |
||||||
|
& > div.info{ |
||||||
|
position: relative; |
||||||
|
flex-shrink: 0; |
||||||
|
overflow: hidden; |
||||||
|
border-radius: 10px; |
||||||
|
background: #00033322; |
||||||
|
box-sizing: border-box; |
||||||
|
padding: 10px; |
||||||
|
&>header{ |
||||||
|
position: relative; |
||||||
|
line-height: 3em; |
||||||
|
text-align: center; |
||||||
|
font-size: 18px; |
||||||
|
} |
||||||
|
& > div{ |
||||||
|
position: relative; |
||||||
|
line-height: 2em; |
||||||
|
font-size: 14px; |
||||||
|
} |
||||||
|
} |
||||||
|
& > div.server{ |
||||||
|
position: relative; |
||||||
|
flex: 1; |
||||||
|
overflow: hidden; |
||||||
|
background: aquamarine; |
||||||
|
margin-top: 10px; |
||||||
|
border-radius: 10px; |
||||||
|
padding: 10px; |
||||||
|
&>header{ |
||||||
|
position: relative; |
||||||
|
line-height: 3em; |
||||||
|
text-align: center; |
||||||
|
font-size: 18px; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
.choose{ |
||||||
|
all: unset; |
||||||
|
cursor: pointer; |
||||||
|
background: darkmagenta; |
||||||
|
color: #fefefe; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.right{ |
||||||
|
position: relative; |
||||||
|
overflow: hidden; |
||||||
|
height: 100%; |
||||||
|
padding: 10px; |
||||||
|
box-sizing: border-box; |
||||||
|
margin: 10px 10px 10px 0; |
||||||
|
border-radius: 10px; |
||||||
|
background: #eeeeee99; |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
& > header{ |
||||||
|
position: relative; |
||||||
|
flex-shrink: 0; |
||||||
|
line-height: 3em; |
||||||
|
font-size: 18px; |
||||||
|
font-weight: bold; |
||||||
|
} |
||||||
|
& > div{ |
||||||
|
position: relative; |
||||||
|
flex: 1; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,55 @@ |
|||||||
|
import css from './index.module.scss' |
||||||
|
import {useEffect, useState} from "react"; |
||||||
|
export default function MessageWindow(props){ |
||||||
|
// @ inputMessage - String - 描述:输入内容 |
||||||
|
const [inputMessage, setInputMessage] = useState(''); |
||||||
|
// @ messageList - Object[] - 描述:消息列表 |
||||||
|
const [messageList, setMessageList] = useState([]); |
||||||
|
useEffect(() => { |
||||||
|
setMessageList(props.messageList) |
||||||
|
}, [props.messageList]) |
||||||
|
|
||||||
|
// = 函数名: handleInputMessageChange |
||||||
|
// = 描述: 响应消息内容输入 |
||||||
|
// = 参数: event |
||||||
|
// = 返回值: undefined |
||||||
|
// = 创建人: Nier |
||||||
|
// = 创建时间: 2023-08-30 19:41:26 - |
||||||
|
function handleInputMessageChange(event) { |
||||||
|
setInputMessage(event.target.value) |
||||||
|
} |
||||||
|
|
||||||
|
// = 函数名: handleSendMessage |
||||||
|
// = 描述: 发送消息 |
||||||
|
// = 参数: null |
||||||
|
// = 返回值: undefined |
||||||
|
// = 创建人: Nier |
||||||
|
// = 创建时间: 2023-08-30 19:42:04 - |
||||||
|
function handleSendMessage() { |
||||||
|
props?.sendMessage(inputMessage) |
||||||
|
setInputMessage('') |
||||||
|
} |
||||||
|
return <div className={css.messageWindow}> |
||||||
|
<div className={css.container}> |
||||||
|
<div className={css.body}> |
||||||
|
<div className={css.container}> |
||||||
|
{ |
||||||
|
messageList.map(item => <div key={item.timeId} className={item.isMe ? [css.messageBox,css.myMessageBox].join(' ') : css.messageBox}> |
||||||
|
<div> |
||||||
|
<div> |
||||||
|
<div>{item.nickname}</div> | |
||||||
|
<div>{item.timeId}</div> |
||||||
|
</div> |
||||||
|
<div>{item.message}</div> |
||||||
|
</div> |
||||||
|
</div>) |
||||||
|
} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div className={css.footer}> |
||||||
|
<textarea value={inputMessage} onChange={handleInputMessageChange}></textarea> |
||||||
|
<div className={css.sendButton} onClick={handleSendMessage}>发送</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
} |
@ -0,0 +1,110 @@ |
|||||||
|
.messageWindow{ |
||||||
|
position: relative; |
||||||
|
height: 100%; |
||||||
|
width: 100%; |
||||||
|
overflow: hidden; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
& > div.container{ |
||||||
|
position: relative; |
||||||
|
min-width: 800px; |
||||||
|
max-width: 1200px; |
||||||
|
width: auto; |
||||||
|
overflow: hidden; |
||||||
|
height: 80%; |
||||||
|
min-height: 700px; |
||||||
|
max-height: 900px; |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
background: #9D44C099; |
||||||
|
border-radius: 10px; |
||||||
|
box-sizing: border-box; |
||||||
|
padding: 15px; |
||||||
|
box-shadow: 0 0 20px -5px #33333333; |
||||||
|
& > div.body{ |
||||||
|
position: relative; |
||||||
|
flex: 1; |
||||||
|
background: #F7E987; |
||||||
|
border-radius: 10px; |
||||||
|
box-shadow: 0 0 10px -5px #33333399 inset; |
||||||
|
margin-bottom: 15px; |
||||||
|
padding: 15px; |
||||||
|
overflow: hidden; |
||||||
|
& > div.container{ |
||||||
|
position: relative; |
||||||
|
max-height: 100%; |
||||||
|
box-sizing: border-box; |
||||||
|
overflow: auto; |
||||||
|
&>div.messageBox{ |
||||||
|
position: relative; |
||||||
|
|
||||||
|
& > div{ |
||||||
|
position: relative; |
||||||
|
border-radius: 10px 10px 10px 0; |
||||||
|
padding: 10px; |
||||||
|
box-sizing: border-box; |
||||||
|
box-shadow: 0 0 10px 2px #33333333; |
||||||
|
background: #1BB74Bcc; |
||||||
|
width:fit-content; |
||||||
|
width:-webkit-fit-content; |
||||||
|
width:-moz-fit-content; |
||||||
|
cursor: pointer; |
||||||
|
color: #fefefe; |
||||||
|
margin: 10px 0; |
||||||
|
& > div:first-child{ |
||||||
|
position: relative; |
||||||
|
display: flex; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
&>div.myMessageBox{ |
||||||
|
display: flex; |
||||||
|
justify-content: end; |
||||||
|
& > div{ |
||||||
|
border-radius: 10px 10px 0 10px ; |
||||||
|
background: darkmagenta; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
& > div.footer{ |
||||||
|
position: relative; |
||||||
|
height: 140px; |
||||||
|
flex-shrink: 0; |
||||||
|
background: #fcfcfc; |
||||||
|
border-radius: 10px; |
||||||
|
box-sizing: border-box; |
||||||
|
box-shadow: 0 6px 20px -2px #33333333; |
||||||
|
padding: 15px; |
||||||
|
& > textarea{ |
||||||
|
border:unset; |
||||||
|
outline: unset; |
||||||
|
resize: unset; |
||||||
|
position: relative; |
||||||
|
display: block; |
||||||
|
width: 100%; |
||||||
|
background: #fcfcfc; |
||||||
|
height: 100%; |
||||||
|
overflow: auto; |
||||||
|
} |
||||||
|
& > div.sendButton{ |
||||||
|
position: absolute; |
||||||
|
padding: 5px 15px; |
||||||
|
bottom: 15px; |
||||||
|
right: 15px; |
||||||
|
background: #1BB74B; |
||||||
|
border-radius: 5px; |
||||||
|
cursor: pointer; |
||||||
|
user-select: none; |
||||||
|
transition: all ease-in-out 200ms; |
||||||
|
color: #fefefe; |
||||||
|
//font-weight: 600; |
||||||
|
font-size: 16px; |
||||||
|
&:hover{ |
||||||
|
box-shadow: 0 0 10px 1px #33333333; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,57 @@ |
|||||||
|
import css from './index.module.scss' |
||||||
|
import {useEffect, useState} from "react"; |
||||||
|
import { useNavigate} from 'react-router-dom' |
||||||
|
|
||||||
|
export default function Signin(){ |
||||||
|
const navigate = useNavigate() |
||||||
|
// @ host - String - 描述:webSocket连接地址 |
||||||
|
const [host, setHost] = useState('') |
||||||
|
// @ nickname - String - 描述:昵称 |
||||||
|
const [nickname, setNickname] = useState(''); |
||||||
|
useEffect(() => { |
||||||
|
// @ storageHost - String - 描述:来自localStorage的ws链接地址 |
||||||
|
const storageHost = window.localStorage.getItem('wsHost'); |
||||||
|
// @ storageNickname - String - 描述:来自localStorage的ws昵称 |
||||||
|
const storageNickname =window.localStorage.getItem('wsNickname'); |
||||||
|
storageHost && setHost(storageHost) |
||||||
|
storageNickname && setNickname(storageNickname) |
||||||
|
}, [0]); |
||||||
|
// = 函数名: handleHostChange |
||||||
|
// = 描述: 响应修改host输入 |
||||||
|
// = 参数: None |
||||||
|
// = 返回值: undefined |
||||||
|
// = 创建人: Nier |
||||||
|
// = 创建时间: 2023-08-30 16:42:16 - |
||||||
|
function handleHostChange(event) { |
||||||
|
setHost(event.target.value) |
||||||
|
} |
||||||
|
|
||||||
|
// = 函数名: handleChangeNickname |
||||||
|
// = 描述: 响应修改nickName输入 |
||||||
|
// = 参数: None |
||||||
|
// = 返回值: undefined |
||||||
|
// = 创建人: Nier |
||||||
|
// = 创建时间: 2023-08-30 16:42:51 - |
||||||
|
function handleChangeNickname(event) { |
||||||
|
setNickname(event.target.value) |
||||||
|
} |
||||||
|
|
||||||
|
// = 函数名: handleConfirmHost |
||||||
|
// = 描述: 确认host地址 |
||||||
|
// = 参数: None |
||||||
|
// = 返回值: undefined |
||||||
|
// = 创建人: Nier |
||||||
|
// = 创建时间: 2023-08-30 16:42:19 - |
||||||
|
function handleConfirmHost() { |
||||||
|
window.localStorage.setItem('wsHost', host) |
||||||
|
window.localStorage.setItem('wsNickname', nickname) |
||||||
|
navigate('/home') |
||||||
|
} |
||||||
|
return <div className={css.signin}> |
||||||
|
<div> |
||||||
|
<div className={css.input}><input type="text" value={host} onChange={handleHostChange} placeholder="请输入连接地址"/></div> |
||||||
|
<div className={css.input}><input type="text" value={nickname} onChange={handleChangeNickname} placeholder="请输入昵称"/></div> |
||||||
|
<div className={css.button} onClick={handleConfirmHost}>确认连接</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
} |
@ -0,0 +1,45 @@ |
|||||||
|
.signin{ |
||||||
|
position: relative; |
||||||
|
height: 100%; |
||||||
|
width: 100%; |
||||||
|
overflow: hidden; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
& > div{ |
||||||
|
position: relative; |
||||||
|
input{ |
||||||
|
all: unset; |
||||||
|
width: 300px; |
||||||
|
background: #00033322; |
||||||
|
padding: 10px 10px; |
||||||
|
font-size: 20px; |
||||||
|
font-weight: bold; |
||||||
|
border-radius: 5px; |
||||||
|
text-align: center; |
||||||
|
} |
||||||
|
&>.input{ |
||||||
|
position: relative; |
||||||
|
margin: 20px 0; |
||||||
|
} |
||||||
|
& > .button{ |
||||||
|
position: relative; |
||||||
|
margin: 0 auto; |
||||||
|
/*div宽度适应文字*/ |
||||||
|
width:fit-content; |
||||||
|
width:-webkit-fit-content; |
||||||
|
width:-moz-fit-content; |
||||||
|
margin-top: 40px; |
||||||
|
padding: 10px 20px; |
||||||
|
border-radius: 5px; |
||||||
|
background: darkmagenta; |
||||||
|
font-weight: bold; |
||||||
|
color: #fefefe; |
||||||
|
cursor: pointer; |
||||||
|
transition: all ease-in-out 300ms; |
||||||
|
&:hover{ |
||||||
|
box-shadow: 0 10px 20px -8px #33333366; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,72 @@ |
|||||||
|
import {useEffect, useLayoutEffect, useState, useRef} from "react"; |
||||||
|
import localforage from 'localforage'; |
||||||
|
import MessageWindow from "../MessageWindow/index.jsx"; |
||||||
|
|
||||||
|
export default function WebSocketClient(){ |
||||||
|
// @ nickname - String - 描述:WebSocket客户端用户名 |
||||||
|
const [nickname, setNickname] = useState(window.localStorage.getItem('wsNickname')) |
||||||
|
// @ ws - WebSocket - 描述:WebSocket客户端 |
||||||
|
const [ws, setWs] = useState(null) |
||||||
|
// @ wsState - Boolean - 描述:websocket状态 |
||||||
|
const [wsState, setWsState] = useState(false); |
||||||
|
// @ webSocketDB - LocalForage - 描述:本地WebSocket消息数据库 |
||||||
|
const [webSocketDB, setWebSocketDB] = useState(null); |
||||||
|
// @ messageList - Object[] - 描述:实时消息列表 |
||||||
|
const [messageList, setMessageList] = useState([]); |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
const socket = ws === null && new WebSocket(`ws://${window.localStorage.getItem('wsHost')}:10001`); |
||||||
|
const db = localforage.createInstance({ |
||||||
|
name: "websocket" |
||||||
|
}); |
||||||
|
const msgList = [] |
||||||
|
db.iterate((value, key, iterationNumber) => { |
||||||
|
msgList.push({ |
||||||
|
timeId: key, |
||||||
|
...value |
||||||
|
}) |
||||||
|
}).then(resd => { |
||||||
|
setMessageList(msgList) |
||||||
|
}) |
||||||
|
socket.onopen = event => { |
||||||
|
setWsState(true) |
||||||
|
} |
||||||
|
socket.onerror = event =>{ |
||||||
|
console.error('Error',event) |
||||||
|
} |
||||||
|
socket.onclose = event => { |
||||||
|
setWsState(false) |
||||||
|
setWs(null) |
||||||
|
} |
||||||
|
setWs(socket) |
||||||
|
setWebSocketDB(db) |
||||||
|
return () => { |
||||||
|
socket.readyState === 1 &&socket.close() |
||||||
|
} |
||||||
|
}, [0]) |
||||||
|
|
||||||
|
if(ws){ |
||||||
|
ws.onmessage = event => { |
||||||
|
const msg = JSON.parse(event.data) |
||||||
|
msg.nickname == nickname ? msg.isMe = true : msg.isMe = false; |
||||||
|
setMessageList([...messageList,msg]) |
||||||
|
webSocketDB.setItem(msg.timeId, msg) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// = 函数名: handleSendMessage |
||||||
|
// = 描述: 发送WebSocket消息 |
||||||
|
// = 参数: message | String |
||||||
|
// = 返回值: undefined |
||||||
|
// = 创建人: Nier |
||||||
|
// = 创建时间: 2023-08-30 19:58:30 - |
||||||
|
function handleSendMessage(message) { |
||||||
|
ws.send(JSON.stringify({ |
||||||
|
message, |
||||||
|
timeId: new Date().getTime().toString() + parseInt(Math.random() * 10000000), |
||||||
|
nickname |
||||||
|
})) |
||||||
|
} |
||||||
|
|
||||||
|
return <MessageWindow sendMessage={handleSendMessage} messageList={messageList}/> |
||||||
|
} |
@ -0,0 +1,9 @@ |
|||||||
|
import { defineConfig } from 'vite' |
||||||
|
import react from '@vitejs/plugin-react' |
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({ |
||||||
|
plugins: [react()], |
||||||
|
server: { |
||||||
|
host: "0.0.0.0",} |
||||||
|
}) |
@ -0,0 +1,16 @@ |
|||||||
|
// | ------------------------------------------------------------
|
||||||
|
// | @版本: version 0.1
|
||||||
|
// | @创建人: 【Nie-x7129】
|
||||||
|
// | @E-mail: x71291@outlook.com
|
||||||
|
// | @文件描述: app.js | 实时通信服务端
|
||||||
|
// | @创建时间: 2023-08-30 14:57
|
||||||
|
// | @更新时间: 2023-08-30 14:57
|
||||||
|
// | @修改记录:
|
||||||
|
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
|
||||||
|
// | =
|
||||||
|
// | ------------------------------------------------------------
|
||||||
|
|
||||||
|
import {httpWebSocket} from "./ws.js"; |
||||||
|
|
||||||
|
// http WebSocket
|
||||||
|
httpWebSocket() |
@ -0,0 +1,85 @@ |
|||||||
|
// | ------------------------------------------------------------
|
||||||
|
// | @版本: version 0.1
|
||||||
|
// | @创建人: 【Nie-x7129】
|
||||||
|
// | @E-mail: x71291@outlook.com
|
||||||
|
// | @文件描述: ws.js | 通过WebSocket建立服务
|
||||||
|
// | @创建时间: 2023-08-30 15:08
|
||||||
|
// | @更新时间: 2023-08-30 15:08
|
||||||
|
// | @修改记录:
|
||||||
|
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
|
||||||
|
// | =
|
||||||
|
// | ------------------------------------------------------------
|
||||||
|
|
||||||
|
// 引入WebSocketServer
|
||||||
|
import WebSocket, { WebSocketServer } from 'ws'; |
||||||
|
// 文件操作
|
||||||
|
import { createServer } from 'https'; |
||||||
|
import { readFileSync } from 'fs'; |
||||||
|
|
||||||
|
// = 函数名: httpWebSocket
|
||||||
|
// = 描述: 使用http模式建立WebSocket服务
|
||||||
|
// = 参数: None
|
||||||
|
// = 返回值: undefined
|
||||||
|
// = 创建人: Nier
|
||||||
|
// = 创建时间: 2023-08-30 15:12:57 -
|
||||||
|
async function httpWebSocket() { |
||||||
|
// @ option - Object - 描述:webSocket配置项
|
||||||
|
const option = { |
||||||
|
// 创建服务器 必须提供 port、server、noServer其中任意一个 否则报错
|
||||||
|
port:10001, // 类型:Number 服务器端口号
|
||||||
|
host:'10.10.10.10', // 类型:String 服务器ip(主机名)
|
||||||
|
backlog:50, // 类型:Number 最大等待连接队列的长度
|
||||||
|
// server // 类型:http.Server|https.Server 预先创建的http或https的服务器
|
||||||
|
// path:'message', // 只接受与此路径匹配的连接。
|
||||||
|
// noServer:false, // 类型:Boolean 不启用服务器模式
|
||||||
|
//clientTracking // 指定是否跟踪客户端
|
||||||
|
maxPayload:4096, // 允许的最大消息大小(以字节为单位),默认为 100 MiB(104857600 字节)。
|
||||||
|
} |
||||||
|
const wss = new WebSocketServer(option,() => { |
||||||
|
console.log(`HTTP WebSocket 已启动: ws://${option.host}:${option.port}`) |
||||||
|
}); |
||||||
|
wss.on('connection', function connection(ws) { |
||||||
|
console.log('Conn') |
||||||
|
ws.on('error', console.error); |
||||||
|
ws.on('message', function message(data, isBinary) { |
||||||
|
wss.clients.forEach(function each(client) { |
||||||
|
if (client.readyState === WebSocket.OPEN) { |
||||||
|
client.send(data, { binary: isBinary }); |
||||||
|
} |
||||||
|
}); |
||||||
|
}); |
||||||
|
ws.onclose = event => { |
||||||
|
console.log('Close') |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
// = 函数名: httpsWebSocket
|
||||||
|
// = 描述: 使用https模式建立WebSocket服务
|
||||||
|
// = 参数: None
|
||||||
|
// = 返回值: undefined
|
||||||
|
// = 创建人: Nier
|
||||||
|
// = 创建时间: 2023-08-30 15:13:30 -
|
||||||
|
function httpsWebSocket() { |
||||||
|
const server = createServer({ |
||||||
|
cert: readFileSync('/path/to/cert.pem'), |
||||||
|
key: readFileSync('/path/to/key.pem') |
||||||
|
}); |
||||||
|
const wss = new WebSocketServer({ server, port: 10002 }); |
||||||
|
|
||||||
|
wss.on('connection', function connection(ws) { |
||||||
|
ws.on('error', console.error); |
||||||
|
|
||||||
|
ws.on('message', function message(data) { |
||||||
|
console.log('received: %s', data); |
||||||
|
}); |
||||||
|
|
||||||
|
ws.send('something'); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
export { |
||||||
|
httpWebSocket, |
||||||
|
httpsWebSocket |
||||||
|
} |
@ -0,0 +1,18 @@ |
|||||||
|
{ |
||||||
|
"name": "real-time-communication", |
||||||
|
"version": "1.0.0", |
||||||
|
"description": "", |
||||||
|
"main": "index.js", |
||||||
|
"type": "module", |
||||||
|
"scripts": { |
||||||
|
"test": "echo \"Error: no test specified\" && exit 1", |
||||||
|
"start:server": "node Server/app.js", |
||||||
|
"start:client": "cd Client && npm run dev" |
||||||
|
}, |
||||||
|
"keywords": [], |
||||||
|
"author": "", |
||||||
|
"license": "ISC", |
||||||
|
"dependencies": { |
||||||
|
"ws": "^8.13.0" |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,25 @@ |
|||||||
|
lockfileVersion: '6.0' |
||||||
|
|
||||||
|
settings: |
||||||
|
autoInstallPeers: true |
||||||
|
excludeLinksFromLockfile: false |
||||||
|
|
||||||
|
dependencies: |
||||||
|
ws: |
||||||
|
specifier: ^8.13.0 |
||||||
|
version: 8.13.0 |
||||||
|
|
||||||
|
packages: |
||||||
|
|
||||||
|
/ws@8.13.0: |
||||||
|
resolution: {integrity: sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==} |
||||||
|
engines: {node: '>=10.0.0'} |
||||||
|
peerDependencies: |
||||||
|
bufferutil: ^4.0.1 |
||||||
|
utf-8-validate: '>=5.0.2' |
||||||
|
peerDependenciesMeta: |
||||||
|
bufferutil: |
||||||
|
optional: true |
||||||
|
utf-8-validate: |
||||||
|
optional: true |
||||||
|
dev: false |
Loading…
Reference in new issue