fix(amap):集成webrtc 到自有代码

This commit is contained in:
wang_yp 2024-11-11 23:08:50 +08:00
parent 08c8a91182
commit 210df9fa06
16 changed files with 249 additions and 182 deletions

View File

@ -1,17 +1,25 @@
import React, { useMemo, useRef, useState } from 'react'; import React, { useMemo, useRef, useState } from "react";
import { Select, Spin } from 'antd'; import { Select, Spin } from "antd";
import type { SelectProps } from 'antd'; import type { SelectProps } from "antd";
import debounce from 'lodash/debounce'; import debounce from "lodash/debounce";
export interface DebounceSelectProps<ValueType = any> export interface DebounceSelectProps<ValueType = any>
extends Omit<SelectProps<ValueType | ValueType[]>, 'options' | 'children'> { extends Omit<SelectProps<ValueType | ValueType[]>, "options" | "children"> {
fetchOptions: (search: string) => Promise<ValueType[]>; fetchOptions: (search: string) => Promise<ValueType[]>;
debounceTimeout?: number; debounceTimeout?: number;
} }
const DebounceSelect = < const DebounceSelect = <
ValueType extends { key?: string; label: React.ReactNode; value: string | number } = any, ValueType extends {
>({ fetchOptions, debounceTimeout = 800, ...props }: DebounceSelectProps<ValueType>) =>{ key?: string;
label: React.ReactNode;
value: string | number;
} = any
>({
fetchOptions,
debounceTimeout = 800,
...props
}: DebounceSelectProps<ValueType>) => {
const [fetching, setFetching] = useState(false); const [fetching, setFetching] = useState(false);
const [options, setOptions] = useState<ValueType[]>([]); const [options, setOptions] = useState<ValueType[]>([]);
const fetchRef = useRef(0); const fetchRef = useRef(0);
@ -43,9 +51,10 @@ const DebounceSelect = <
onSearch={debounceFetcher} onSearch={debounceFetcher}
notFoundContent={fetching ? <Spin size="small" /> : null} notFoundContent={fetching ? <Spin size="small" /> : null}
{...props} {...props}
options={options} options={options}
/> />
); );
} };
export default DebounceSelect export default DebounceSelect;

View File

@ -7,107 +7,19 @@ import { HomeTwoTone } from "@ant-design/icons";
import { inject, observer } from "mobx-react"; import { inject, observer } from "mobx-react";
import { Store } from "antd/es/form/interface"; import { Store } from "antd/es/form/interface";
import { useEffect } from "react"; import { useEffect } from "react";
import { items } from "./layout_config";
const LayOut = (props: Store) => { const LayOut = (props: Store) => {
const { usrStore } = props; const { usrStore } = props;
const nav = useNavigate(); const nav = useNavigate();
const location = useLocation(); const location = useLocation();
useEffect(() => { useEffect(() => {
console.log("layout ",usrStore.isNeedLogin);
if (usrStore.isNeedLogin) { if (usrStore.isNeedLogin) {
nav("/login"); nav("/login");
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [usrStore.isNeedLogin]); }, [usrStore.isNeedLogin]);
const items = [
{
key: "/admin/user",
label: `用户管理`,
children: [
{
key: "/admin/dep",
label: `部门管理`,
},
{
key: "/admin/user",
label: `用户管理`,
},
{
key: "/admin/teamMgmt",
label: `队伍属性管理`,
},
{
key: "/admin/persMgmt",
label: `个人身份管理`,
},
{
key: "/admin/community",
label: `社区管理`,
},
{
key: "/admin/grid",
label: `网格管理`,
},
{
key: "/admin/patrolBrigade",
label: `巡防大队`,
},
],
},
{
key: "/admin/archives/box",
label: `档案管理`,
},
{
key: "/admin/material",
label: `物资管理`,
children: [
{
key: "/admin/whse/whseMgmt",
label: `仓库管理`,
},
{
key: "/admin/materialMgmt",
label: `物资管理`,
},
],
},
{
key: "/admin/leaveApproval",
label: `请假审批`,
},
{
key: "/admin/political",
label: `政治法规`,
children: [
{
key: "/admin/politicalStudy",
label: `政治学习`,
},
{
key: `/admin/polRegulations`,
label: `政治法规管理`,
},
],
},
{
key: "/admin/task",
label: `任务管理`,
children: [
{ label: "处突任务", key: "/admin/emergency" },
{ label: "巡逻任务", key: "/admin/patrol" },
{ label: "训练任务", key: "/admin/training" },
],
},
{
key: "/admin/sys",
label: `系统管理`,
children: [
{ label: "系统设置", key: "/admin/sys/setting" },
{ label: "光荣牌审核", key: "/admin/sys/gp" },
],
},
];
return ( return (
<div className="layout"> <div className="layout">
<Header <Header
@ -136,12 +48,14 @@ const LayOut = (props: Store) => {
}} }}
style={{ flex: 1, minWidth: 0 }} style={{ flex: 1, minWidth: 0 }}
/> />
<span style={{ color: "#fff" }}>退</span>
</Header> </Header>
<Content style={{ padding: "0 20px" }}> <Content style={{ padding: "0 20px" }}>
<Outlet /> <Outlet />
</Content> </Content>
<Footer style={{ textAlign: "center" }}> <Footer style={{ textAlign: "center" }}>
©{new Date().getFullYear()} Created ©{new Date().getFullYear()} Created
</Footer> </Footer>
</div> </div>
); );

View File

@ -0,0 +1,87 @@
export const items = [
{
key: "/admin/user",
label: `用户管理`,
children: [
{
key: "/admin/user",
label: `用户管理`,
},
{
key: "/admin/teamMgmt",
label: `队伍属性管理`,
},
{
key: "/admin/persMgmt",
label: `个人身份管理`,
},
{
key: "/admin/community",
label: `社区管理`,
},
{
key: "/admin/grid",
label: `网格管理`,
},
{
key: "/admin/patrolBrigade",
label: `巡防大队`,
},
],
},
{
key: "/admin/archives/box",
label: `档案管理`,
},
{
key: "/admin/material",
label: `物资管理`,
children: [
{
key: "/admin/whse/whseMgmt",
label: `仓库管理`,
},
{
key: "/admin/materialMgmt",
label: `物资管理`,
},
],
},
{
key: "/admin/leaveApproval",
label: `请假审批`,
},
{
key: "/admin/political",
label: `政治法规`,
children: [
{
key: "/admin/politicalStudy",
label: `政治学习`,
},
{
key: `/admin/polRegulations`,
label: `政治法规管理`,
},
],
},
{
key: "/admin/task",
label: `任务管理`,
children: [
{ label: "处突任务", key: "/admin/emergency" },
{ label: "巡逻任务", key: "/admin/patrol" },
{ label: "训练任务", key: "/admin/training" },
],
},
{
key: "/admin/sys",
label: `系统管理`,
children: [
{ label: `部门管理`, key: "/admin/dep" },
{ label: "系统设置", key: "/admin/sys/setting" },
{ label: "光荣牌审核", key: "/admin/sys/gp" },
{ label: "评优审核", key: "/admin/sys/gp" },
],
},
];

View File

@ -35,7 +35,7 @@ const Dep = (props: Store) => {
setIsModalOpen(true); setIsModalOpen(true);
formRef.current?.setFieldsValue(data); formRef.current?.setFieldsValue(data);
setRecord(data); setRecord(data);
setId(record.id); setId(record.key);
}; };
const onFinish = (values: any) => { const onFinish = (values: any) => {
if (!tagId) { if (!tagId) {
@ -95,11 +95,11 @@ const Dep = (props: Store) => {
type DirectoryTreeProps = GetProps<typeof Tree.DirectoryTree>; type DirectoryTreeProps = GetProps<typeof Tree.DirectoryTree>;
const onSelect: DirectoryTreeProps["onSelect"] = (keys, info) => { const onSelect: DirectoryTreeProps["onSelect"] = (keys, info) => {
console.log("Trigger Select", keys, info); // console.log("Trigger Select", keys, info);
}; };
const onExpand: DirectoryTreeProps["onExpand"] = (keys, info) => { const onExpand: DirectoryTreeProps["onExpand"] = (keys, info) => {
console.log("Trigger Expand", keys, info); // console.log("Trigger Expand", keys, info);
}; };
return ( return (
@ -126,11 +126,32 @@ const Dep = (props: Store) => {
onExpand={onExpand} onExpand={onExpand}
treeData={org} treeData={org}
titleRender={(nodeData: DataNode) => { titleRender={(nodeData: DataNode) => {
return <>{nodeData.title}<span style={{marginLeft:"10px",color:"blue"}} onClick={edit}></span></> return (
<>
{nodeData.title}
<span
style={{ marginLeft: "10px", color: "blue" }}
onClick={() => {
edit(nodeData);
}}
>
</span>
<span
style={{ marginLeft: "10px", color: "blue" }}
onClick={() => {
depStore.deleteItem(nodeData.key);
getOrg()
}}
>
</span>
</>
);
}} }}
/> />
<Modal <Modal
title={!tagId ? "添加部门" : "编辑部门"} title={!record ? "添加部门" : "编辑部门"}
width={800} width={800}
open={isModalOpen} open={isModalOpen}
afterClose={() => formRef.current?.resetFields()} afterClose={() => formRef.current?.resetFields()}
@ -139,6 +160,7 @@ const Dep = (props: Store) => {
cancelText="取消" cancelText="取消"
onCancel={() => { onCancel={() => {
setId(null); setId(null);
setRecord(null);
setIsModalOpen(false); setIsModalOpen(false);
}} }}
> >

View File

@ -149,7 +149,7 @@ const Emergency = (props: Store) => {
baseHttp.get("/user/list", null).then((res) => { baseHttp.get("/user/list", null).then((res) => {
let data = res.data?.record ?? []; let data = res.data?.record ?? [];
data.forEach((item) => { data.forEach((item) => {
item.label = item.account; item.label = item.user_name;
item.value = item.identity; item.value = item.identity;
}); });
setUserList(data ?? []); setUserList(data ?? []);

View File

@ -50,7 +50,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
flex: 2; flex: 5;
.title_img { .title_img {
margin-left: 10px; margin-left: 10px;
width: 20px; width: 20px;
@ -66,7 +66,7 @@
margin-left: 15px; margin-left: 15px;
margin-right: 15px; margin-right: 15px;
color: #fff; color: #fff;
font-size: 30px; font-size:25px;
font-weight: normal; font-weight: normal;
line-height: normal; line-height: normal;
letter-spacing: 0.1em; letter-spacing: 0.1em;
@ -81,7 +81,7 @@
margin-left: 15px; margin-left: 15px;
margin-right: 15px; margin-right: 15px;
color: #fff; color: #fff;
font-size: 20px; font-size: 14px;
font-weight: normal; font-weight: normal;
line-height: normal; line-height: normal;
letter-spacing: 0.1em; letter-spacing: 0.1em;

View File

@ -21,7 +21,7 @@ const Home = observer(() => {
</div> </div>
<div className="map_container_t_c"> <div className="map_container_t_c">
<img className="twp on_to" src={image2} alt="" /> <img className="twp on_to" src={image2} alt="" />
<span></span> <span></span>
<img className="twp" src={image2} alt="" /> <img className="twp" src={image2} alt="" />
</div> </div>
<div className="map_container_t_r"> <div className="map_container_t_r">

View File

@ -5,25 +5,33 @@ import { useState } from "react";
import "./bot.less"; import "./bot.less";
import { PhoneTwoTone } from "@ant-design/icons"; import { PhoneTwoTone } from "@ant-design/icons";
import { webRTC } from "@/util/webRtc"; import { webRTC } from "@/util/webRtc";
import DebounceSelect from "@/components/form/featch_select";
interface UserValue {
label: string;
value: string;
}
const Ec = (props: Store) => { const Ec = (props: Store) => {
const [isModalOpen, setIsModalOpen] = useState<boolean>(false); const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
const { usrStore } = props; const { usrStore } = props;
const [userList, setUserList] = useState<any>([]);
const openDispatch = () => { const openDispatch = () => {
try { try {
usrStore.getlist().then(() => { setIsModalOpen(true);
setUserList(usrStore.list);
webRTC.init();
setIsModalOpen(true);
});
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }
}; };
const callphone = (record: any) => { const callphone = (record: any) => {
webRTC.calls(record.identity); webRTC.calls(record.value);
}; };
const [value, setValue] = useState<UserValue[]>([]);
async function fetchUserList(username: string): Promise<UserValue[]> {
return usrStore.serchUser(username).then((res) => {
return res.data.record.map((item) => ({
label: item.user_name,
value: item.identity,
}));
});
}
return ( return (
<> <>
<span onClick={openDispatch}> 线</span> <span onClick={openDispatch}> 线</span>
@ -45,16 +53,27 @@ const Ec = (props: Store) => {
<h3>线</h3> <h3>线</h3>
</div> </div>
<div className="u-item"> <div className="u-item">
{userList.map((item: any) => { <DebounceSelect
mode="multiple"
value={value}
placeholder="输入搜索"
fetchOptions={fetchUserList}
onChange={(newValue) => {
setValue(newValue as UserValue[]);
}}
style={{ width: "100%" }}
/>
<p></p>
{value.map((item: any) => {
return ( return (
<div key={item.account}> <div key={item.key}>
<div>{item.user_name} : 线</div> <div>{item.label} : 线</div>
<p></p>
<div> <div>
<PhoneTwoTone <PhoneTwoTone
style={{ fontSize: "20px" }} style={{ fontSize: "20px" }}
onClick={(items) => { onClick={(items) => {
webRTC.init();
callphone(item); callphone(item);
}} }}
/> />
@ -75,7 +94,6 @@ const Ec = (props: Store) => {
style={{ width: "300px", height: "300px" }} style={{ width: "300px", height: "300px" }}
></video> ></video>
</div> </div>
<p <p
className="ec_right_end" className="ec_right_end"
onClick={() => { onClick={() => {

View File

@ -14,16 +14,33 @@ const Weather = () => {
<div <div
style={{ style={{
flex: "1", flex: "1",
display: "flex",
alignItems: "center",
justifyContent: "space-around",
color: "#fff", color: "#fff",
fontSize: "13px",
padding:"10px"
}} }}
> >
<p>:{wechaer?.weather}</p> <div
<p>:{wechaer?.windDirection}</p> style={{
<p>:{wechaer?.windPower}</p> fontSize: "13px",
<p>湿:{wechaer?.humidity}</p> display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
<span>:{wechaer?.weather}</span>
<span>:{wechaer?.windDirection}</span>
</div>
<div
style={{
fontSize: "13px",
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
<span>:{wechaer?.windPower}</span>
<span>湿:{wechaer?.humidity}</span>
</div>
</div> </div>
); );
}; };

View File

@ -10,7 +10,7 @@ const videoJsOptions = {
fluid: true, fluid: true,
sources: [ sources: [
{ {
src: "http://112.19.145.68:18000/hls/stream_1_0/playlist.m3u8", src: "http://183.221.86.205:18000/hls/stream_1_0/playlist.m3u8",
type: "application/x-mpegURL", type: "application/x-mpegURL",
}, },
], ],
@ -19,7 +19,6 @@ const Video = (props: Store) => {
const { homeStore, onReady } = props; const { homeStore, onReady } = props;
const videoRef = useRef<HTMLDivElement>(null); const videoRef = useRef<HTMLDivElement>(null);
const playerRef = useRef<any>(null); // 使用 any 类型 const playerRef = useRef<any>(null); // 使用 any 类型
useEffect(() => { useEffect(() => {
homeStore.getNewTask(); homeStore.getNewTask();
}, [homeStore]); }, [homeStore]);
@ -44,7 +43,6 @@ const Video = (props: Store) => {
}, [videoRef, onReady]); }, [videoRef, onReady]);
useEffect(() => { useEffect(() => {
const player = playerRef.current; const player = playerRef.current;
return () => { return () => {
if (player && !player.isDisposed()) { if (player && !player.isDisposed()) {
player.dispose(); player.dispose();

View File

@ -64,7 +64,7 @@ const Regulations = (props: Store) => {
const onFinish = (values: any) => { const onFinish = (values: any) => {
let data = { let data = {
...values, ...values,
file_url: values.file_url[0].name, file_url: (values.file_url.length>0)? values.file_url[0].name:'',
}; };
if (!record?.id) { if (!record?.id) {
regulationsStore.add(data); regulationsStore.add(data);

View File

@ -37,7 +37,12 @@ export const defaultConfig = [
label: "法规副标题", label: "法规副标题",
name: "sub_title", name: "sub_title",
value: "", value: "",
rules: [{ required: true, message: "请输入法规副标题!" }], },
{
type: FormType.inputNumber,
label: "排序",
name: "level",
value: 0,
}, },
{ {
type: "editor", type: "editor",

View File

@ -24,7 +24,6 @@ axios.interceptors.response.use((res: AxiosResponse) => {
} }
return res; return res;
}, (err) => { }, (err) => {
if (err.status === 401) { if (err.status === 401) {
store.usrStore.openLoginDilog() store.usrStore.openLoginDilog()
store.usrStore.logOut() store.usrStore.logOut()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 362 KiB

After

Width:  |  Height:  |  Size: 101 KiB

View File

@ -2,7 +2,7 @@ class Config {
static baseUrl = "https://rw.quwanya.cn/"; static baseUrl = "https://rw.quwanya.cn/";
static uploadUrl = "https://rw.quwanya.cn/"; static uploadUrl = "https://rw.quwanya.cn/";
static ws = "wss://rw.quwanya.cn/wsadmin?id=admin"; static ws = "wss://rw.quwanya.cn/wsadmin?id=admin";
static rtc = "wss://rw.quwanya.cn/ws"; // static rtc = "wss://rw.quwanya.cn/ws";
static userStatic = "https://rw.quwanya.cn/uploads/user/"; static userStatic = "https://rw.quwanya.cn/uploads/user/";
// https://rw.quwanya.cn/uploads/user/%E5%B4%94%E6%96%87%E8%8C%9C.jpg // https://rw.quwanya.cn/uploads/user/%E5%B4%94%E6%96%87%E8%8C%9C.jpg
} }

View File

@ -1,10 +1,10 @@
import Config from "./config"; import SocketService from "./socket";
class WebRtc { class WebRtc {
private mediaStream: MediaStream | Blob | null = null; private mediaStream: MediaStream | Blob | null = null;
private pee: RTCPeerConnection | null = null; private pee: RTCPeerConnection | null = null;
private ws: WebSocket | null = null; private ws: SocketService | null = null;
private video: HTMLVideoElement | null = null; private video: HTMLVideoElement | null = null;
private userToId: string = ""; private userToId: string = "";
open = () => { open = () => {
@ -17,57 +17,55 @@ class WebRtc {
})) }))
} }
async init() { async init() {
this.ws = new WebSocket(Config.rtc) this.ws = SocketService.getInstance();
this.ws.addEventListener('open', this.open); this.ws.on("message", this.onMessage);
let that = this;
this.createOffer() this.createOffer()
this.ws.onmessage = function (evt) { }
let msg = JSON.parse(evt.data) onMessage = (e: any) => {
if (!msg) { let that = this;
return console.log('failed to parse msg') let msg = JSON.parse(e.data)
} if (!msg) {
switch (msg.type) { return console.log('failed to parse msg')
case 'offer': }
let offer = msg.data.description switch (msg.type) {
that.pee?.setRemoteDescription(offer) case 'offer':
that.pee?.createAnswer().then(answer => { let offer = msg.data.description
that.pee?.setLocalDescription(answer) that.pee?.setRemoteDescription(offer)
that.ws?.send(JSON.stringify({ that.pee?.createAnswer().then(answer => {
type: 'answer', data: { that.pee?.setLocalDescription(answer)
'to': msg.data.from, that.ws?.send(JSON.stringify({
'from': "31283192", type: 'answer', data: {
'description': { 'sdp': answer.sdp, 'type': answer.type }, 'to': msg.data.from,
'session_id': msg.data.from + "-31283192", 'from': "31283192",
} 'description': { 'sdp': answer.sdp, 'type': answer.type },
})) 'session_id': msg.data.from + "-31283192",
}) }
return }))
})
return
case 'candidate': case 'candidate':
let candidate = msg.data.candidate let candidate = msg.data.candidate
if (!candidate) { if (!candidate) {
return console.log('failed to parse candidate') return console.log('failed to parse candidate')
} }
that.pee?.addIceCandidate(candidate) that.pee?.addIceCandidate(candidate)
break; break;
case "answer": case "answer":
that.pee?.setRemoteDescription(msg.data.description) that.pee?.setRemoteDescription(msg.data.description)
break; break;
case "bye": case "bye":
that.pee?.close() that.pee?.close()
that.close() that.close()
break; break;
}
} }
} }
async createOffer() { async createOffer() {
var url = var url =
'http://rw.quwanya.cn:12217/api/turn?service=turn&username=flutter-webrtc'; 'http://rw.quwanya.cn:12217/api/turn?service=turn&username=flutter-webrtc';
fetch(url) fetch(url)
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
const configuration = { const configuration = {
iceServers: [ iceServers: [
{ {