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 { Select, Spin } from 'antd';
import type { SelectProps } from 'antd';
import debounce from 'lodash/debounce';
import React, { useMemo, useRef, useState } from "react";
import { Select, Spin } from "antd";
import type { SelectProps } from "antd";
import debounce from "lodash/debounce";
export interface DebounceSelectProps<ValueType = any>
extends Omit<SelectProps<ValueType | ValueType[]>, 'options' | 'children'> {
extends Omit<SelectProps<ValueType | ValueType[]>, "options" | "children"> {
fetchOptions: (search: string) => Promise<ValueType[]>;
debounceTimeout?: number;
}
const DebounceSelect = <
ValueType extends { key?: string; label: React.ReactNode; value: string | number } = any,
>({ fetchOptions, debounceTimeout = 800, ...props }: DebounceSelectProps<ValueType>) =>{
ValueType extends {
key?: string;
label: React.ReactNode;
value: string | number;
} = any
>({
fetchOptions,
debounceTimeout = 800,
...props
}: DebounceSelectProps<ValueType>) => {
const [fetching, setFetching] = useState(false);
const [options, setOptions] = useState<ValueType[]>([]);
const fetchRef = useRef(0);
@ -43,9 +51,10 @@ const DebounceSelect = <
onSearch={debounceFetcher}
notFoundContent={fetching ? <Spin size="small" /> : null}
{...props}
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 { Store } from "antd/es/form/interface";
import { useEffect } from "react";
import { items } from "./layout_config";
const LayOut = (props: Store) => {
const { usrStore } = props;
const nav = useNavigate();
const location = useLocation();
useEffect(() => {
console.log("layout ",usrStore.isNeedLogin);
if (usrStore.isNeedLogin) {
nav("/login");
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [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 (
<div className="layout">
<Header
@ -136,12 +48,14 @@ const LayOut = (props: Store) => {
}}
style={{ flex: 1, minWidth: 0 }}
/>
<span style={{ color: "#fff" }}>退</span>
</Header>
<Content style={{ padding: "0 20px" }}>
<Outlet />
</Content>
<Footer style={{ textAlign: "center" }}>
©{new Date().getFullYear()} Created
©{new Date().getFullYear()} Created
</Footer>
</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);
formRef.current?.setFieldsValue(data);
setRecord(data);
setId(record.id);
setId(record.key);
};
const onFinish = (values: any) => {
if (!tagId) {
@ -95,11 +95,11 @@ const Dep = (props: Store) => {
type DirectoryTreeProps = GetProps<typeof Tree.DirectoryTree>;
const onSelect: DirectoryTreeProps["onSelect"] = (keys, info) => {
console.log("Trigger Select", keys, info);
// console.log("Trigger Select", keys, info);
};
const onExpand: DirectoryTreeProps["onExpand"] = (keys, info) => {
console.log("Trigger Expand", keys, info);
// console.log("Trigger Expand", keys, info);
};
return (
@ -126,11 +126,32 @@ const Dep = (props: Store) => {
onExpand={onExpand}
treeData={org}
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
title={!tagId ? "添加部门" : "编辑部门"}
title={!record ? "添加部门" : "编辑部门"}
width={800}
open={isModalOpen}
afterClose={() => formRef.current?.resetFields()}
@ -139,6 +160,7 @@ const Dep = (props: Store) => {
cancelText="取消"
onCancel={() => {
setId(null);
setRecord(null);
setIsModalOpen(false);
}}
>

View File

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

View File

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

View File

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

View File

@ -5,25 +5,33 @@ import { useState } from "react";
import "./bot.less";
import { PhoneTwoTone } from "@ant-design/icons";
import { webRTC } from "@/util/webRtc";
import DebounceSelect from "@/components/form/featch_select";
interface UserValue {
label: string;
value: string;
}
const Ec = (props: Store) => {
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
const { usrStore } = props;
const [userList, setUserList] = useState<any>([]);
const openDispatch = () => {
try {
usrStore.getlist().then(() => {
setUserList(usrStore.list);
webRTC.init();
setIsModalOpen(true);
});
setIsModalOpen(true);
} catch (error) {
console.log(error);
}
};
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 (
<>
<span onClick={openDispatch}> 线</span>
@ -45,16 +53,27 @@ const Ec = (props: Store) => {
<h3>线</h3>
</div>
<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 (
<div key={item.account}>
<div>{item.user_name} : 线</div>
<p></p>
<div key={item.key}>
<div>{item.label} : 线</div>
<div>
<PhoneTwoTone
style={{ fontSize: "20px" }}
onClick={(items) => {
webRTC.init();
callphone(item);
}}
/>
@ -75,7 +94,6 @@ const Ec = (props: Store) => {
style={{ width: "300px", height: "300px" }}
></video>
</div>
<p
className="ec_right_end"
onClick={() => {

View File

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

View File

@ -10,7 +10,7 @@ const videoJsOptions = {
fluid: true,
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",
},
],
@ -19,7 +19,6 @@ const Video = (props: Store) => {
const { homeStore, onReady } = props;
const videoRef = useRef<HTMLDivElement>(null);
const playerRef = useRef<any>(null); // 使用 any 类型
useEffect(() => {
homeStore.getNewTask();
}, [homeStore]);
@ -44,7 +43,6 @@ const Video = (props: Store) => {
}, [videoRef, onReady]);
useEffect(() => {
const player = playerRef.current;
return () => {
if (player && !player.isDisposed()) {
player.dispose();

View File

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

View File

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

View File

@ -24,7 +24,6 @@ axios.interceptors.response.use((res: AxiosResponse) => {
}
return res;
}, (err) => {
if (err.status === 401) {
store.usrStore.openLoginDilog()
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 uploadUrl = "https://rw.quwanya.cn/";
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/";
// 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 {
private mediaStream: MediaStream | Blob | null = null;
private pee: RTCPeerConnection | null = null;
private ws: WebSocket | null = null;
private ws: SocketService | null = null;
private video: HTMLVideoElement | null = null;
private userToId: string = "";
open = () => {
@ -17,57 +17,55 @@ class WebRtc {
}))
}
async init() {
this.ws = new WebSocket(Config.rtc)
this.ws.addEventListener('open', this.open);
let that = this;
this.ws = SocketService.getInstance();
this.ws.on("message", this.onMessage);
this.createOffer()
this.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
that.pee?.setRemoteDescription(offer)
that.pee?.createAnswer().then(answer => {
that.pee?.setLocalDescription(answer)
that.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
}
onMessage = (e: any) => {
let that = this;
let msg = JSON.parse(e.data)
if (!msg) {
return console.log('failed to parse msg')
}
switch (msg.type) {
case 'offer':
let offer = msg.data.description
that.pee?.setRemoteDescription(offer)
that.pee?.createAnswer().then(answer => {
that.pee?.setLocalDescription(answer)
that.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')
}
that.pee?.addIceCandidate(candidate)
break;
case "answer":
that.pee?.setRemoteDescription(msg.data.description)
break;
case "bye":
that.pee?.close()
that.close()
break;
}
case 'candidate':
let candidate = msg.data.candidate
if (!candidate) {
return console.log('failed to parse candidate')
}
that.pee?.addIceCandidate(candidate)
break;
case "answer":
that.pee?.setRemoteDescription(msg.data.description)
break;
case "bye":
that.pee?.close()
that.close()
break;
}
}
async createOffer() {
var url =
'http://rw.quwanya.cn:12217/api/turn?service=turn&username=flutter-webrtc';
fetch(url)
.then(response => response.json())
.then(data => {
const configuration = {
iceServers: [
{
@ -97,7 +95,7 @@ class WebRtc {
remoteVideo.controls = true
remoteVideo.srcObject = event.streams[0];
console.log(event);
event.track.onmute = function () {
remoteVideo.play()
}