This commit is contained in:
wang_yp 2024-10-04 23:42:00 +08:00
parent 87963fa8e1
commit 3d3f2fd89e
22 changed files with 791 additions and 152 deletions

View File

@ -6,7 +6,7 @@ import { useEffect } from "react";
import SocketService from "./util/socket"; import SocketService from "./util/socket";
const socketService = SocketService.getInstance(); const socketService = SocketService.getInstance();
const onMessage = (data: any) => { const onMessage = (data: any) => {
console.log("message", data); // console.log("message", data);
}; };
socketService.on("message", onMessage); socketService.on("message", onMessage);
const onConnected = () => { const onConnected = () => {

View File

@ -12,6 +12,7 @@ interface UploadFileProps {
interface UploadFileEx extends UploadFile { interface UploadFileEx extends UploadFile {
systemImageId?: number; systemImageId?: number;
bannerName?: string; bannerName?: string;
file_name?: string;
redictUrl?: string; redictUrl?: string;
id?: number; id?: number;
} }
@ -48,6 +49,7 @@ const AliUpload = (props: UploadFileProps) => {
const handleChange: UploadProps["onChange"] = ({ fileList: newFileList }) => { const handleChange: UploadProps["onChange"] = ({ fileList: newFileList }) => {
newFileList.forEach((i)=>{ newFileList.forEach((i)=>{
i.url = `${Config.uploadUrl}uploads/`+i.name i.url = `${Config.uploadUrl}uploads/`+i.name
i.fileName = i.name
}) })
setFileList(newFileList); setFileList(newFileList);
props.onChnage(newFileList) props.onChnage(newFileList)
@ -66,7 +68,7 @@ const AliUpload = (props: UploadFileProps) => {
fileList={files} fileList={files}
action={`${Config.uploadUrl}v1/public/fts/upload`} action={`${Config.uploadUrl}v1/public/fts/upload`}
onPreview={handlePreview} onPreview={handlePreview}
maxCount={1} maxCount={4}
onChange={handleChange} onChange={handleChange}
> >
{files.length >= 4 ? null : uploadButton} {files.length >= 4 ? null : uploadButton}

View File

@ -1,10 +1,11 @@
import { Pagination, PaginationProps, Table } from "antd"; import { Pagination, PaginationProps, Table } from "antd";
import { useState } from "react"; import { useState } from "react";
const BTable = (props: any) => { const BTable = (props: any) => {
const { store, dataSource } = props; const { store, dataSource ,selectCallback} = props;
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]); const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
const onSelectChange = (newSelectedRowKeys: React.Key[]) => { const onSelectChange = (newSelectedRowKeys: React.Key[]) => {
setSelectedRowKeys(newSelectedRowKeys); setSelectedRowKeys(newSelectedRowKeys);
selectCallback(newSelectedRowKeys);
}; };
const rowSelection = { const rowSelection = {

View File

@ -87,6 +87,13 @@ const LayOut = (props: Store) => {
{ label: "训练任务", key: "/admin/training" }, { label: "训练任务", key: "/admin/training" },
], ],
}, },
{
key: "/admin/sys",
label: `系统管理`,
children: [
{ label: "系统设置", key: "/admin/sys/setting" },
],
},
]; ];
return ( return (
<div className="layout"> <div className="layout">

View File

@ -37,7 +37,6 @@ export default function MapFrom(props:any) {
// 开启坐标选择功能 // 开启坐标选择功能
mouseTool.on("draw", function (event) { mouseTool.on("draw", function (event) {
var lnglat = event.obj.getBounds().getCenter(); // 获取图形的中心点坐标 var lnglat = event.obj.getBounds().getCenter(); // 获取图形的中心点坐标
console.log("选择的坐标是:", lnglat.lng, lnglat.lat);
markers = mouseTool.overlays.marker markers = mouseTool.overlays.marker
props.onChange({lng:lnglat.lng,lat:lnglat.lat}) props.onChange({lng:lnglat.lng,lat:lnglat.lat})
var mk = [] var mk = []

View File

@ -20,6 +20,7 @@ export interface UserDataType {
startTime: any; startTime: any;
endTime: any; endTime: any;
status: any; status: any;
identity: string;
} }
export interface TagDataType { export interface TagDataType {

View File

@ -1,5 +1,5 @@
import SimpleForm from "@/components/form/simple_form"; import SimpleForm from "@/components/form/simple_form";
import { TrainingConfig } from "@/pages/training/traning_config"; import { traningConfig } from "@/pages/training/traning_config";
import { Button, Form, FormInstance, Modal, Select } from "antd"; import { Button, Form, FormInstance, Modal, Select } from "antd";
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { useState } from "react"; import { useState } from "react";
@ -15,7 +15,7 @@ const Dispath = (props: Store) => {
const openDispatch = () => { const openDispatch = () => {
setIsModalOpen(true); setIsModalOpen(true);
setProjectConfig([ setProjectConfig([
...TrainingConfig, ...traningConfig,
{ {
type: FormType.cehckboxGroup, type: FormType.cehckboxGroup,
label: "参与人员选择", label: "参与人员选择",

View File

@ -1,4 +1,4 @@
import { Modal } from "antd"; import { Modal } from "antd";
import { Store } from "antd/es/form/interface"; import { Store } from "antd/es/form/interface";
import { inject, observer } from "mobx-react"; import { inject, observer } from "mobx-react";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
@ -18,10 +18,10 @@ const Ec = (props: Store) => {
const openDispatch = () => { const openDispatch = () => {
setIsModalOpen(true); setIsModalOpen(true);
}; };
const callphone=(record:any)=>{ const callphone = (record: any) => {
webRTC.init() webRTC.init();
webRTC.calls() webRTC.calls();
} };
return ( return (
<> <>
<span onClick={openDispatch}> 线</span> <span onClick={openDispatch}> 线</span>
@ -49,7 +49,13 @@ const Ec = (props: Store) => {
<div>{item.account} : 线</div> <div>{item.account} : 线</div>
<p></p> <p></p>
<div> <div>
<PhoneTwoTone style={{ fontSize: "20px" }} onClick={(item)=>{callphone(item)}} />
<PhoneTwoTone
style={{ fontSize: "20px" }}
onClick={(item) => {
callphone(item);
}}
/>
</div> </div>
</div> </div>
); );
@ -57,10 +63,22 @@ const Ec = (props: Store) => {
</div> </div>
</div> </div>
<div className="ec_right"> <div className="ec_right">
<video id="rtcVideo"></video> <div style={{display:"flex"}}>
<p className="ec_right_end" onClick={()=>{ <video id="rtcVideo" style={{ width: "300px", height: "300px" }}></video>
webRTC.close() <video
}}></p> id="remoteVideo"
style={{ width: "300px", height: "300px" }}
></video>
</div>
<p
className="ec_right_end"
onClick={() => {
webRTC.close();
}}
>
</p>
</div> </div>
</div> </div>
</Modal> </Modal>

View File

@ -0,0 +1,45 @@
import AliUpload from "@/components/ali_upload";
import { Button, UploadFile } from "antd";
import { Store } from "antd/es/form/interface";
import { inject, observer } from "mobx-react";
import { useEffect, useState } from "react";
interface UploadFilewx extends UploadFile {
file_name: string;
file_url: string;
}
const Banner = (props: Store) => {
const { sysStore } = props;
const [files, setFileList] = useState<UploadFilewx[]>([]);
const uploadChange = (v) => {
v.forEach((i) => {
i.file_name = i.fileName;
i.file_url = i.url
});
setFileList(v);
};
useEffect(() => {
sysStore.getlist().then(() => {
let data = sysStore.list
data.forEach((e)=>{
e.fileNmae = e.file_name
e.name = e.file_name
e.url = e.file_url
})
setFileList(data);
});
}, [sysStore]);
const save = () => {
sysStore.add({ list: files });
};
return (
<div>
<Button type="dashed" onClick={save}>
</Button>
<AliUpload imgList={files} onChnage={uploadChange} />
</div>
);
};
export default inject("sysStore")(observer(Banner));

View File

@ -0,0 +1,20 @@
import { Tabs, TabsProps } from "antd";
import Banner from "./banner";
const SystemPage = () => {
const items: TabsProps["items"] = [
{
key: "1",
label: "banner管理",
children: <Banner />,
},
];
return (
<>
<Tabs defaultActiveKey="1" items={items} />
</>
);
};
export default SystemPage;

View File

@ -7,14 +7,17 @@ import { UserDataType } from "@/model/userModel";
import { Store } from "antd/lib/form/interface"; import { Store } from "antd/lib/form/interface";
import SimpleForm from "@/components/form/simple_form"; import SimpleForm from "@/components/form/simple_form";
import React from "react"; import React from "react";
import { FormType } from "@/components/form/interface";
import baseHttp from "@/service/base"; import baseHttp from "@/service/base";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { traningConfig } from "./traning_config";
import { FormType } from "@/components/form/interface";
import TraningUser from "./traningUser";
const { Option } = Select; const { Option } = Select;
const Trainings = (props: Store) => { const Trainings = (props: Store) => {
const { trainingStore, trainingCatStore } = props; const { trainingStore, trainingCatStore } = props;
const [isModalOpen, setIsModalOpen] = useState<boolean>(false); const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
const [isModalOpenUser, setIsModalOpenUser] = useState<boolean>(false);
const [projectConfig, setProjectConfig] = useState<any>([]); const [projectConfig, setProjectConfig] = useState<any>([]);
const formRef = React.useRef<FormInstance>(null); const formRef = React.useRef<FormInstance>(null);
const [record, setRecord] = useState<any>(null); const [record, setRecord] = useState<any>(null);
@ -34,6 +37,16 @@ const Trainings = (props: Store) => {
dataIndex: "id", dataIndex: "id",
render: (any, record) => ( render: (any, record) => (
<Space wrap> <Space wrap>
<Button
type="dashed"
size="small"
onClick={() => {
trainingStore.id = record?.identity;
setIsModalOpenUser(true);
}}
>
</Button>
<Button <Button
type="dashed" type="dashed"
size="small" size="small"
@ -66,12 +79,25 @@ const Trainings = (props: Store) => {
start_time: dayjs(record.start_time), start_time: dayjs(record.start_time),
end_time: dayjs(record.end_time), end_time: dayjs(record.end_time),
}; };
setProjectConfig(defaultConfig); setProjectConfig([
...traningConfig,
{
type: FormType.cehckboxGroup,
label: "参与人员选择",
name: "user_id",
value: [],
checkboxData: userList,
rules: [{ required: true, message: "请选择参与人员!" }],
},
]);
setIsModalOpen(true); setIsModalOpen(true);
formRef.current?.setFieldsValue(data); formRef.current?.setFieldsValue(data);
setRecord(data); setRecord(data);
setId(record.id); setId(record.id);
}; };
useEffect(() => {
trainingStore.getlist();
}, [trainingStore]);
useEffect(() => { useEffect(() => {
trainingCatStore.getlist().then(() => { trainingCatStore.getlist().then(() => {
setStash(trainingCatStore.list); setStash(trainingCatStore.list);
@ -99,86 +125,31 @@ const Trainings = (props: Store) => {
} }
setIsModalOpen(false); setIsModalOpen(false);
}; };
useEffect(() => {
trainingStore.getlist(); // 任务发布handler
}, [trainingStore]); const fabu = () => {
const defaultConfig = [ setProjectConfig([
{ ...traningConfig,
type: FormType.input, {
label: "任务标题", type: FormType.cehckboxGroup,
name: "title", label: "参与人员选择",
value: "", name: "user_id",
rules: [{ required: true, message: "请输入任务标题!" }], value: [],
}, checkboxData: userList,
{ rules: [{ required: true, message: "请选择参与人员!" }],
type: FormType.input, },
label: "任务描述", ]);
name: "desc", setId(null);
value: "", setIsModalOpen(true);
rules: [{ required: true, message: "请输入任务描述!" }], };
},
{
type: FormType.input,
label: "任务地点",
name: "address",
value: "",
rules: [{ required: true, message: "请输入任务地点!" }],
},
{
type: FormType.date,
label: "任务开始时间",
name: "start_time",
value: "",
rules: [{ required: true, message: "请输入任务开始时间!" }],
},
{
type: FormType.date,
label: "任务结束时间",
name: "end_time",
value: "",
rules: [{ required: true, message: "请输入任务结束时间!" }],
},
{
type: FormType.input,
label: "任务积分设置",
name: "score",
value: "",
rules: [{ required: true, message: "请输入任务积分设置!" }],
},
{
type: FormType.input,
label: "训练次数",
name: "count",
value: 0,
rules: [{ required: true, message: "请输入训练次数!" }],
},
{
type: FormType.cehckboxGroup,
label: "参与人员选择",
name: "user_id",
value: [],
checkboxData: userList,
rules: [{ required: true, message: "请选择参与人员!" }],
},
];
const onFinishFailed = () => {}; const onFinishFailed = () => {};
// 用户选择回调 // 用户选择回调
return ( return (
<div className="contentBox"> <div className="contentBox">
<Space direction="vertical" size="middle" style={{ display: "flex" }}> <Space direction="vertical" size="middle" style={{ display: "flex" }}>
<Space direction="horizontal" size={"middle"}> <Space direction="horizontal" size={"middle"}>
<Button <Button onClick={() => fabu()}></Button>
type="default"
onClick={() => {
setProjectConfig(defaultConfig);
setId(null);
setIsModalOpen(true);
}}
>
</Button>
</Space> </Space>
<BTable <BTable
store={trainingStore} store={trainingStore}
columns={columns} columns={columns}
@ -228,6 +199,18 @@ const Trainings = (props: Store) => {
</> </>
</SimpleForm> </SimpleForm>
</Modal> </Modal>
<Modal
title="训练人员"
width={1200}
open={isModalOpenUser}
footer={null}
onCancel={() => {
setIsModalOpenUser(false);
}}
>
<TraningUser/>
</Modal>
</Space> </Space>
</div> </div>
); );

View File

@ -0,0 +1,103 @@
import BTable from "@/components/b_table";
import { UserDataType } from "@/model/userModel";
import { Button, InputNumber, message, Modal, Space } from "antd";
import { Store } from "antd/es/form/interface";
import { ColumnsType } from "antd/lib/table";
import { inject, observer } from "mobx-react";
import { useEffect, useState } from "react";
const TraningUser = (props: Store) => {
const { trainingStore } = props;
const [keys, setKeys] = useState<Array<string>>([]);
const [score, setScore] = useState<number | null>(0);
const [isModalOpenUser, setIsModalOpenUser] = useState<boolean>(false);
useEffect(() => {
trainingStore.getUserListByTraning();
}, [trainingStore]);
const okhandler = () => {
let arr: any[] = [];
for (let index = 0; index < keys.length; index++) {
arr.push({
score: score,
user_identity: keys[index],
source: 2,
source_identity: trainingStore.id,
});
}
let res = trainingStore.setUserScore(arr);
if (res){
message.success("积分设置成功")
setIsModalOpenUser(false);
}
};
const _columns: ColumnsType<UserDataType> = [
{ title: "民兵名称", dataIndex: "user_name" },
{ title: "民兵描述", dataIndex: "remark" },
{ title: "民兵账号", dataIndex: "account" },
{
title: "操作",
dataIndex: "id",
render: (any, record) => (
<Space wrap>
<Button
type="dashed"
size="small"
onClick={() => {
setKeys([record.identity]);
setIsModalOpenUser(true);
}}
>
</Button>
</Space>
),
},
];
return (
<>
<div style={{ display: "flex", justifyContent: "space-between" }}>
<p></p>
<Button
disabled={keys.length === 0}
type="primary"
size="small"
onClick={() => {
setIsModalOpenUser(true);
}}
>
</Button>
</div>
<Modal
title="积分设置"
width={300}
open={isModalOpenUser}
onOk={() => okhandler()}
afterClose={() => setKeys([])}
onCancel={() => {
setIsModalOpenUser(false);
}}
>
<InputNumber
style={{ width: "200px" }}
defaultValue={0}
value={score}
onChange={(e) => {
setScore(e);
}}
/>
</Modal>
<BTable
store={trainingStore}
columns={_columns}
dataSource={trainingStore.userList}
selectCallback={(e) => {
setKeys(e);
}}
/>
</>
);
};
export default inject("trainingStore")(observer(TraningUser));

View File

@ -1,54 +1,53 @@
import { FormType } from "@/components/form/interface"; import { FormType } from "@/components/form/interface";
export const traningConfig = [
{
type: FormType.input,
label: "任务标题",
name: "title",
value: "",
rules: [{ required: true, message: "请输入任务标题!" }],
},
{
type: FormType.input,
label: "任务描述",
name: "desc",
value: "",
rules: [{ required: true, message: "请输入任务描述!" }],
},
{
type: FormType.input,
label: "任务地点",
name: "address",
value: "",
rules: [{ required: true, message: "请输入任务地点!" }],
},
{
type: FormType.date,
label: "任务开始时间",
name: "start_time",
value: "",
rules: [{ required: true, message: "请输入任务开始时间!" }],
},
{
type: FormType.date,
label: "任务结束时间",
name: "end_time",
value: "",
rules: [{ required: true, message: "请输入任务结束时间!" }],
},
{
type: FormType.input,
label: "任务积分设置",
name: "score",
value: "",
rules: [{ required: true, message: "请输入任务积分设置!" }],
},
{
type: FormType.input,
label: "训练次数",
name: "count",
value: 0,
rules: [{ required: true, message: "请输入训练次数!" }],
},
export const TrainingConfig = [ ];
{
type: FormType.input,
label: "任务标题",
name: "title",
value: "",
rules: [{ required: true, message: "请输入任务标题!" }],
},
{
type: FormType.input,
label: "任务描述",
name: "desc",
value: "",
rules: [{ required: true, message: "请输入任务描述!" }],
},
{
type: FormType.input,
label: "任务地点",
name: "address",
value: "",
rules: [{ required: true, message: "请输入任务地点!" }],
},
{
type: FormType.date,
label: "任务开始时间",
name: "start_time",
value: "",
rules: [{ required: true, message: "请输入任务开始时间!" }],
},
{
type: FormType.date,
label: "任务结束时间",
name: "end_time",
value: "",
rules: [{ required: true, message: "请输入任务结束时间!" }],
},
{
type: FormType.input,
label: "任务积分设置",
name: "score",
value: "",
rules: [{ required: true, message: "请输入任务积分设置!" }],
},
{
type: FormType.input,
label: "训练次数",
name: "count",
value: 0,
rules: [{ required: true, message: "请输入训练次数!" }],
},
]

View File

@ -19,6 +19,7 @@ import Training from "@/pages/training";
import OrgChart from "@/pages/OrgChart"; import OrgChart from "@/pages/OrgChart";
import PoverPage from "@/pages/poverPage"; import PoverPage from "@/pages/poverPage";
import PoverDetail from "@/pages/poverDetail"; import PoverDetail from "@/pages/poverDetail";
import SystemPage from "@/pages/system";
export const homeRouter = [ export const homeRouter = [
{ {
path: "/", path: "/",
@ -130,7 +131,17 @@ export const homeRouter = [
}, },
] ]
}, },
{
path: "/admin/sys",
element: <WhseMgmtRoute />,
children:[
{
path: "/admin/sys/setting",
index: true,
element: <SystemPage />,
},
]
},
], ],
}, },
]; ];

View File

@ -19,6 +19,7 @@ import { emergencyStore } from './emergency';
import { patrolStore } from './patrol'; import { patrolStore } from './patrol';
import { trainingStore } from './training'; import { trainingStore } from './training';
import { trainingCatStore } from './trainingCat'; import { trainingCatStore } from './trainingCat';
import { sysStore } from './sys';
const store = { const store = {
usrStore, usrStore,
@ -41,7 +42,8 @@ const store = {
emergencyStore, emergencyStore,
patrolStore, patrolStore,
trainingStore, trainingStore,
trainingCatStore trainingCatStore,
sysStore,
}; };
export default store; export default store;

15
src/store/sys.ts Normal file
View File

@ -0,0 +1,15 @@
// 档案分类
import BaseStore from "./baseStore";
import { TagDataType } from "@/model/userModel";
class SysConfig {
static LIST: string = "sys/banner"
static ADD: string = "sys/banner"
}
class SysStore extends BaseStore<TagDataType> {
constructor() {
super(SysConfig)
}
}
export const sysStore = new SysStore()

View File

@ -1,28 +1,60 @@
import { action, makeObservable } from "mobx"; import { action, makeObservable, observable, runInAction } from "mobx";
// 档案分类 // 档案分类
import BaseStore from "./baseStore"; import BaseStore from "./baseStore";
import { TagDataType } from "@/model/userModel"; import { TagDataType } from "@/model/userModel";
import baseHttp from "@/service/base"; import baseHttp from "@/service/base";
class TrainingConfig { class TrainingConfig {
static LIST: string = "training/list" static LIST: string = "training/list"
static ADD: string = "training" static ADD: string = "training"
static DELETE: string = "training" static DELETE: string = "training"
static EDIT: string = "training" static EDIT: string = "training"
static ACCESS: string = "training/accept" static ACCESS: string = "training/accept"
static tran_user: string = "training/user"
static addScores: string = "scoreMgmt/scores"
} }
class TrainingStore extends BaseStore<TagDataType> { class TrainingStore extends BaseStore<TagDataType> {
constructor() { constructor() {
super(TrainingConfig) super(TrainingConfig)
makeObservable(this, { makeObservable(this, {
access: action access: action,
id: observable,
userList: observable,
getUserListByTraning: action,
setUserScore: action,
}) })
} }
async setUserScore(ids: Array<any>) {
let res= await baseHttp.post(TrainingConfig.addScores, {"list": ids})
if (res.data==="ok"){
this.getUserListByTraning();
return true
}
}
async access(id: number, param: any) { async access(id: number, param: any) {
await baseHttp.put(TrainingConfig.ACCESS + "/" + id, param) await baseHttp.put(TrainingConfig.ACCESS + "/" + id, param)
this.getlist() this.getlist()
} }
async getUserListByTraning() {
let res = await baseHttp.get(TrainingConfig.tran_user + "/" + this.id, null)
let data: Array<any> = []
if (!res?.data?.record) {
runInAction(() => {
this.userList = data;
})
return;
}
for (let i = 0; i < res.data.record.length; i++) {
data.push({
key: res.data.record[i].identity,
...res.data.record[i]
})
}
this.userList = data;
}
id!: string
userList!: Array<any>
} }
export const trainingStore = new TrainingStore() export const trainingStore = new TrainingStore()

143
src/test_socke.html Normal file
View File

@ -0,0 +1,143 @@
<!DOCTYPE html>
<html>
<!--
SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
SPDX-License-Identifier: MIT
-->
<head>
<meta charset="utf-8">
</head>
<body>
<h3> Local Video </h3>
<video id="localVideo" width="160" height="120" autoplay muted></video>
<br />
<h3> Remote Video </h3>
<div id="remoteVideos"></div>
<br />
<h3> Logs </h3>
<div id="logs"></div>
<button onclick="call()">呼叫</button>
</body>
<script>
let ws;
let pc;
const call = () => {
console.log(ws);
pc?.createOffer().then(function (offer) {
pc?.setLocalDescription(offer);
ws.send(JSON.stringify({
type: "offer", "data": {
"to": "576030",
"from": "31283192",
"description": offer,
"media": "video",
"session_id": "576030-31283192",
}
}))
// socketService.send({ type: 'offer', data: offer })
}).catch(function (error) {
// 错误处理
console.error(error);
});
}
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
.then(stream => {
const configuration = {
iceServers: [{ urls: 'stun:rw.quwanya.cn/rtc' }]
};
pc = new RTCPeerConnection(configuration)
pc.ontrack = function (event) {
if (event.track.kind === 'audio') {
return
}
let el = document.createElement(event.track.kind)
el.srcObject = event.streams[0]
el.autoplay = true
el.controls = true
document.getElementById('remoteVideos').appendChild(el)
event.track.onmute = function (event) {
el.play()
}
event.streams[0].onremovetrack = ({ track }) => {
if (el.parentNode) {
el.parentNode.removeChild(el)
}
}
}
document.getElementById('localVideo').srcObject = stream
stream.getTracks().forEach(track => pc.addTrack(track, stream))
ws = new WebSocket("ws://127.0.0.1:12214/ws?id=admin123")
pc.onicecandidate = e => {
if (!e.candidate) {
return
}
ws.send(JSON.stringify({ type: 'candidate', data: e }))
}
ws.addEventListener('open', function (event) {
ws.send(JSON.stringify({
type: "new", "data": {
"name": "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 )",
"id": "31283192",
"user_agent": "flutter-webrtc/js"
}
}))
});
ws.onclose = function (evt) {
// window.alert("Websocket has closed")
}
ws.onmessage = function (evt) {
let msg = JSON.parse(evt.data)
if (!msg) {
return console.log('failed to parse msg')
}
switch (msg.type) {
case 'offer':
let offer = msg.data.description
pc.setRemoteDescription(offer)
pc.createAnswer().then(answer => {
pc.setLocalDescription(answer)
ws.send(JSON.stringify({
type: 'answer', data: {
'to': msg.data.from,
'from': "31283192",
'description': { 'sdp': answer.sdp, 'type': answer.type },
'session_id': msg.data.from + "-31283192",
}
}))
})
return
case 'candidate':
let candidate = msg.data.candidate
if (!candidate) {
return console.log('failed to parse candidate')
}
pc.addIceCandidate(candidate)
case "answer":
pc?.setRemoteDescription(msg.data.description)
// let answer = JSON.parse(msg.content.body)
// if (!answer) {
// return console.log('failed to parse answer')
// }
// console.log(answer)
// this.setOffer(answer.data.description)
// break;
}
}
ws.onerror = function (evt) {
console.log("ERROR: " + evt.data)
}
})
</script>
</html>

146
src/ttttt.html Normal file
View File

@ -0,0 +1,146 @@
<!DOCTYPE html>
<html>
<!--
SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
SPDX-License-Identifier: MIT
-->
<head>
<meta charset="utf-8">
</head>
<body>
<h3> Local Video </h3>
<video id="localVideo" width="160" height="120" autoplay muted></video>
<br />
<h3> Remote Video </h3>
<div id="remoteVideos"></div>
<br />
<h3> Logs </h3>
<div id="logs"></div>
<button onclick="call()">呼叫</button>
</body>
<script>
let ws;
let pc;
const call = () => {
console.log(ws);
pc?.createOffer().then(function (offer) {
pc?.setLocalDescription(offer);
ws.send(JSON.stringify({
type: "offer", "data": {
"to": "467056",
"from": "31283192",
"description": offer,
"media": "video",
"session_id": "467056-31283192",
}
}))
// socketService.send({ type: 'offer', data: offer })
}).catch(function (error) {
// 错误处理
console.error(error);
});
}
// wss://rw.quwanya.cn/ws
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
.then(stream => {
const configuration = {
iceServers: [{ urls: 'stun:127.0.0.1:19302' }]
};
pc = new RTCPeerConnection(configuration)
pc.ontrack = function (event) {
if (event.track.kind === 'audio') {
return
}
let el = document.createElement(event.track.kind)
el.srcObject = event.streams[0]
el.autoplay = true
el.controls = true
document.getElementById('remoteVideos').appendChild(el)
event.track.onmute = function (event) {
el.play()
}
event.streams[0].onremovetrack = ({ track }) => {
if (el.parentNode) {
el.parentNode.removeChild(el)
}
}
}
document.getElementById('localVideo').srcObject = stream
stream.getTracks().forEach(track => pc.addTrack(track, stream))
ws = new WebSocket("ws://rw.quwanya.cn:12217/ws")
pc.onicecandidate = e => {
if (!e.candidate) {
return
}
ws.send(JSON.stringify({ type: 'candidate', data: e }))
}
ws.addEventListener('open', function (event) {
ws.send(JSON.stringify({
type: "new", "data": {
"name": "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 )",
"id": "31283192",
"user_agent": "flutter-webrtc/js"
}
}))
});
ws.onclose = function (evt) {
window.alert("Websocket has closed")
}
ws.onmessage = function (evt) {
let msg = JSON.parse(evt.data)
if (!msg) {
return console.log('failed to parse msg')
}
switch (msg.type) {
case 'offer':
let offer = msg.data.description
pc.setRemoteDescription(offer)
pc.createAnswer().then(answer => {
pc.setLocalDescription(answer)
ws.send(JSON.stringify({
type: 'answer', data: {
'to': msg.data.from,
'from': "31283192",
'description': { 'sdp': answer.sdp, 'type': answer.type },
'session_id': msg.data.from + "-31283192",
}
}))
})
return
case 'candidate':
let candidate = msg.data.candidate
if (!candidate) {
return console.log('failed to parse candidate')
}
pc.addIceCandidate(candidate)
case "answer":
pc?.setRemoteDescription(msg.data.description)
// let answer = JSON.parse(msg.content.body)
// if (!answer) {
// return console.log('failed to parse answer')
// }
// console.log(answer)
// this.setOffer(answer.data.description)
// break;
}
}
ws.onerror = function (evt) {
console.log("ERROR: " + evt.data)
}
}).catch(window.alert)
</script>
</html>

View File

@ -1,5 +1,12 @@
class Config { class Config {
// static baseUrl = "https://rw.quwanya.cn/";
// static uploadUrl = "https://rw.quwanya.cn/";
// static ws = "wss://rw.quwanya.cn/ws?id=admin";
static baseUrl = "http://127.0.0.1:12214/"; static baseUrl = "http://127.0.0.1:12214/";
static uploadUrl = "http://127.0.0.1:12214/"; static uploadUrl = "http://127.0.0.1:12214/";
static ws = "ws://rw.quwanya.cn:12217/ws";
} }
export default Config; export default Config;

View File

@ -1,3 +1,5 @@
import Config from "./config"
export type AutoReconnectOptions = boolean | { export type AutoReconnectOptions = boolean | {
maxRetries?: number maxRetries?: number
retryInterval?: number retryInterval?: number
@ -35,7 +37,7 @@ export type AutoReconnectOptions = boolean | {
} }
public connect() { public connect() {
this.ws = new WebSocket('ws://127.0.0.1:12214/ws?id=admin') this.ws = new WebSocket(Config.ws)
this.ws.onopen = () => { this.ws.onopen = () => {
this.connectionStatus = ConnectionStatus.Connected this.connectionStatus = ConnectionStatus.Connected
this.emit('connected', null) this.emit('connected', null)

View File

@ -1,12 +1,114 @@
import SocketService from "./socket";
const socketService = SocketService.getInstance();
class WebRtc { class WebRtc {
private mediaStream: MediaStream | Blob | null = null; private mediaStream: MediaStream | Blob | null = null;
private pee: RTCPeerConnection | null = null;
private video: HTMLVideoElement | null = null; private video: HTMLVideoElement | null = null;
async init() { async init() {
let constraints = { audio: false, video: true };
this.video = document.getElementById('rtcVideo') as HTMLVideoElement; this.video = document.getElementById('rtcVideo') as HTMLVideoElement;
this.mediaStream = await navigator.mediaDevices.getUserMedia(constraints) as MediaStream | Blob socketService.on("message", (e) => {
let msg = JSON.parse(e)
switch (msg.type) {
case "answer":
let answer = JSON.parse(msg.content.body)
if (!answer) {
return console.log('failed to parse answer')
}
console.log(answer)
this.setOffer(answer.data.description)
break;
case "offer":
let offer = msg.data.description
this.pee?.setRemoteDescription(offer)
this.pee?.createAnswer().then(answer => {
this.pee?.setLocalDescription(answer)
socketService.send(JSON.stringify({
type: 'answer', data: {
'to': msg.data.from,
'from': "31283192",
'description': { 'sdp': answer.sdp, 'type': answer.type },
'session_id': msg.data.from + "-31283192",
}
}))
})
break;
case 'candidate':
this.pee?.setRemoteDescription(msg.data.description)
break;
default:
break;
}
});
this.createOffer();
}
async createOffer() {
try {
const configuration = {
iceServers: [{ urls: 'stun:127.0.0.1:19302' }]
};
const peerConnection = new RTCPeerConnection(configuration);
this.pee = peerConnection
// 获取远方流添加到页面播放
peerConnection.ontrack = event => {
const remoteVideo = document.querySelector('#remoteVideo') as HTMLVideoElement;
remoteVideo.autoplay = true
remoteVideo.controls = true
remoteVideo.srcObject = event.streams[0];
event.track.onmute = function (event) {
remoteVideo.play()
}
};
await this.getMedia(peerConnection)
this.sendOffer()
peerConnection.onicecandidate = e => {
if (!e.candidate) {
return
}
socketService.send({ type: 'candidate', data: e.candidate })
}
} catch (error) {
console.log(error);
}
}
sendOffer() {
let that = this;
that.pee?.createOffer().then(function (offer) {
that.pee?.setLocalDescription(offer);
socketService.send(JSON.stringify({
type: "offer", "data": {
"to": "848401",
"from": "31283192",
"description": offer,
"media": "video",
"session_id": "848401-31283192",
}
}))
}).catch(function (error) {
// 错误处理
console.error(error);
});
}
setOffer(offer) {
this.pee?.setRemoteDescription(offer)
}
addIceCandidate(candidate) {
console.log('addIceCandidate');
this.pee?.addIceCandidate(candidate)
}
async getMedia(pee: RTCPeerConnection) {
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
});
this.mediaStream = stream;
stream.getTracks().forEach(track => pee.addTrack(track, stream))
this.calls();
} catch (error) {
console.log(error);
}
} }
calls() { calls() {
if (this.video) { if (this.video) {
@ -15,6 +117,7 @@ class WebRtc {
} }
} }
close() { close() {
this.video?.pause(); this.video?.pause();
(this.mediaStream as MediaStream)?.getTracks().forEach(track => track.stop()); (this.mediaStream as MediaStream)?.getTracks().forEach(track => track.stop());