diff --git a/src/App.tsx b/src/App.tsx index 2248a4e..da08752 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,7 +6,7 @@ import { useEffect } from "react"; import SocketService from "./util/socket"; const socketService = SocketService.getInstance(); const onMessage = (data: any) => { - console.log("message", data); + // console.log("message", data); }; socketService.on("message", onMessage); const onConnected = () => { diff --git a/src/components/ali_upload.tsx b/src/components/ali_upload.tsx index b607140..94afabc 100644 --- a/src/components/ali_upload.tsx +++ b/src/components/ali_upload.tsx @@ -12,6 +12,7 @@ interface UploadFileProps { interface UploadFileEx extends UploadFile { systemImageId?: number; bannerName?: string; + file_name?: string; redictUrl?: string; id?: number; } @@ -48,6 +49,7 @@ const AliUpload = (props: UploadFileProps) => { const handleChange: UploadProps["onChange"] = ({ fileList: newFileList }) => { newFileList.forEach((i)=>{ i.url = `${Config.uploadUrl}uploads/`+i.name + i.fileName = i.name }) setFileList(newFileList); props.onChnage(newFileList) @@ -66,7 +68,7 @@ const AliUpload = (props: UploadFileProps) => { fileList={files} action={`${Config.uploadUrl}v1/public/fts/upload`} onPreview={handlePreview} - maxCount={1} + maxCount={4} onChange={handleChange} > {files.length >= 4 ? null : uploadButton} diff --git a/src/components/b_table.tsx b/src/components/b_table.tsx index 8cc90da..091438b 100644 --- a/src/components/b_table.tsx +++ b/src/components/b_table.tsx @@ -1,10 +1,11 @@ import { Pagination, PaginationProps, Table } from "antd"; import { useState } from "react"; const BTable = (props: any) => { - const { store, dataSource } = props; + const { store, dataSource ,selectCallback} = props; const [selectedRowKeys, setSelectedRowKeys] = useState([]); const onSelectChange = (newSelectedRowKeys: React.Key[]) => { setSelectedRowKeys(newSelectedRowKeys); + selectCallback(newSelectedRowKeys); }; const rowSelection = { diff --git a/src/components/layout/layout.tsx b/src/components/layout/layout.tsx index e954769..d08d142 100644 --- a/src/components/layout/layout.tsx +++ b/src/components/layout/layout.tsx @@ -87,6 +87,13 @@ const LayOut = (props: Store) => { { label: "训练任务", key: "/admin/training" }, ], }, + { + key: "/admin/sys", + label: `系统管理`, + children: [ + { label: "系统设置", key: "/admin/sys/setting" }, + ], + }, ]; return (
diff --git a/src/components/map/MapFrom.tsx b/src/components/map/MapFrom.tsx index 21c7196..e21feda 100644 --- a/src/components/map/MapFrom.tsx +++ b/src/components/map/MapFrom.tsx @@ -37,7 +37,6 @@ export default function MapFrom(props:any) { // 开启坐标选择功能 mouseTool.on("draw", function (event) { var lnglat = event.obj.getBounds().getCenter(); // 获取图形的中心点坐标 - console.log("选择的坐标是:", lnglat.lng, lnglat.lat); markers = mouseTool.overlays.marker props.onChange({lng:lnglat.lng,lat:lnglat.lat}) var mk = [] diff --git a/src/model/userModel.ts b/src/model/userModel.ts index 1856ac9..6b89129 100644 --- a/src/model/userModel.ts +++ b/src/model/userModel.ts @@ -20,6 +20,7 @@ export interface UserDataType { startTime: any; endTime: any; status: any; + identity: string; } export interface TagDataType { diff --git a/src/pages/home/homeBottom/dispath.tsx b/src/pages/home/homeBottom/dispath.tsx index 5ecd6c5..3dc0858 100644 --- a/src/pages/home/homeBottom/dispath.tsx +++ b/src/pages/home/homeBottom/dispath.tsx @@ -1,5 +1,5 @@ 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 React, { useEffect } from "react"; import { useState } from "react"; @@ -15,7 +15,7 @@ const Dispath = (props: Store) => { const openDispatch = () => { setIsModalOpen(true); setProjectConfig([ - ...TrainingConfig, + ...traningConfig, { type: FormType.cehckboxGroup, label: "参与人员选择", diff --git a/src/pages/home/homeBottom/ec.tsx b/src/pages/home/homeBottom/ec.tsx index 4c80b8a..6e6304c 100644 --- a/src/pages/home/homeBottom/ec.tsx +++ b/src/pages/home/homeBottom/ec.tsx @@ -1,4 +1,4 @@ -import { Modal } from "antd"; +import { Modal } from "antd"; import { Store } from "antd/es/form/interface"; import { inject, observer } from "mobx-react"; import { useEffect, useState } from "react"; @@ -18,10 +18,10 @@ const Ec = (props: Store) => { const openDispatch = () => { setIsModalOpen(true); }; - const callphone=(record:any)=>{ - webRTC.init() - webRTC.calls() - } + const callphone = (record: any) => { + webRTC.init(); + webRTC.calls(); + }; return ( <> 应急连线 @@ -49,7 +49,13 @@ const Ec = (props: Store) => {
姓名:{item.account} : 未在线

- 点击呼叫:{callphone(item)}} /> + 点击呼叫: + { + callphone(item); + }} + />
); @@ -57,10 +63,22 @@ const Ec = (props: Store) => {
- -

{ - webRTC.close() - }}>结束通话

+
+ + +
+ +

{ + webRTC.close(); + }} + > + 结束通话 +

diff --git a/src/pages/system/banner.tsx b/src/pages/system/banner.tsx new file mode 100644 index 0000000..2068b03 --- /dev/null +++ b/src/pages/system/banner.tsx @@ -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([]); + 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 ( +
+ + +
+ ); +}; + +export default inject("sysStore")(observer(Banner)); diff --git a/src/pages/system/index.tsx b/src/pages/system/index.tsx new file mode 100644 index 0000000..1ac00c9 --- /dev/null +++ b/src/pages/system/index.tsx @@ -0,0 +1,20 @@ +import { Tabs, TabsProps } from "antd"; +import Banner from "./banner"; + +const SystemPage = () => { + const items: TabsProps["items"] = [ + { + key: "1", + label: "banner管理", + children: , + }, + + ]; + return ( + <> + + + ); +}; + +export default SystemPage; diff --git a/src/pages/training/training.tsx b/src/pages/training/training.tsx index c9e5156..503efea 100644 --- a/src/pages/training/training.tsx +++ b/src/pages/training/training.tsx @@ -7,14 +7,17 @@ import { UserDataType } from "@/model/userModel"; import { Store } from "antd/lib/form/interface"; import SimpleForm from "@/components/form/simple_form"; import React from "react"; -import { FormType } from "@/components/form/interface"; import baseHttp from "@/service/base"; import dayjs from "dayjs"; +import { traningConfig } from "./traning_config"; +import { FormType } from "@/components/form/interface"; +import TraningUser from "./traningUser"; const { Option } = Select; const Trainings = (props: Store) => { const { trainingStore, trainingCatStore } = props; const [isModalOpen, setIsModalOpen] = useState(false); + const [isModalOpenUser, setIsModalOpenUser] = useState(false); const [projectConfig, setProjectConfig] = useState([]); const formRef = React.useRef(null); const [record, setRecord] = useState(null); @@ -34,6 +37,16 @@ const Trainings = (props: Store) => { dataIndex: "id", render: (any, record) => ( + + - { + + { + setIsModalOpenUser(false); + }} + > + + ); diff --git a/src/pages/training/traningUser.tsx b/src/pages/training/traningUser.tsx new file mode 100644 index 0000000..4c08ec9 --- /dev/null +++ b/src/pages/training/traningUser.tsx @@ -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>([]); + const [score, setScore] = useState(0); + const [isModalOpenUser, setIsModalOpenUser] = useState(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 = [ + { title: "民兵名称", dataIndex: "user_name" }, + { title: "民兵描述", dataIndex: "remark" }, + { title: "民兵账号", dataIndex: "account" }, + { + title: "操作", + dataIndex: "id", + render: (any, record) => ( + + + + ), + }, + ]; + return ( + <> +
+

训练人员

+ +
+ okhandler()} + afterClose={() => setKeys([])} + onCancel={() => { + setIsModalOpenUser(false); + }} + > + { + setScore(e); + }} + /> + + { + setKeys(e); + }} + /> + + ); +}; + +export default inject("trainingStore")(observer(TraningUser)); diff --git a/src/pages/training/traning_config.ts b/src/pages/training/traning_config.ts index 07802ec..4aa8814 100644 --- a/src/pages/training/traning_config.ts +++ b/src/pages/training/traning_config.ts @@ -1,54 +1,53 @@ import { FormType } from "@/components/form/interface"; - -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: "请输入训练次数!" }], - }, +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: "请输入训练次数!" }], + }, - ] +]; \ No newline at end of file diff --git a/src/router/routers/home_router.tsx b/src/router/routers/home_router.tsx index 617f0a3..4edf3ba 100644 --- a/src/router/routers/home_router.tsx +++ b/src/router/routers/home_router.tsx @@ -19,6 +19,7 @@ import Training from "@/pages/training"; import OrgChart from "@/pages/OrgChart"; import PoverPage from "@/pages/poverPage"; import PoverDetail from "@/pages/poverDetail"; +import SystemPage from "@/pages/system"; export const homeRouter = [ { path: "/", @@ -130,7 +131,17 @@ export const homeRouter = [ }, ] }, - + { + path: "/admin/sys", + element: , + children:[ + { + path: "/admin/sys/setting", + index: true, + element: , + }, + ] + }, ], }, ]; diff --git a/src/store/index.ts b/src/store/index.ts index db97d7d..092c865 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -19,6 +19,7 @@ import { emergencyStore } from './emergency'; import { patrolStore } from './patrol'; import { trainingStore } from './training'; import { trainingCatStore } from './trainingCat'; +import { sysStore } from './sys'; const store = { usrStore, @@ -41,7 +42,8 @@ const store = { emergencyStore, patrolStore, trainingStore, - trainingCatStore + trainingCatStore, + sysStore, }; export default store; \ No newline at end of file diff --git a/src/store/sys.ts b/src/store/sys.ts new file mode 100644 index 0000000..278229f --- /dev/null +++ b/src/store/sys.ts @@ -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 { + constructor() { + super(SysConfig) + } +} +export const sysStore = new SysStore() + diff --git a/src/store/training.ts b/src/store/training.ts index 590e523..6760f7b 100644 --- a/src/store/training.ts +++ b/src/store/training.ts @@ -1,28 +1,60 @@ -import { action, makeObservable } from "mobx"; +import { action, makeObservable, observable, runInAction } from "mobx"; // 档案分类 import BaseStore from "./baseStore"; import { TagDataType } from "@/model/userModel"; import baseHttp from "@/service/base"; - class TrainingConfig { static LIST: string = "training/list" static ADD: string = "training" static DELETE: string = "training" static EDIT: string = "training" static ACCESS: string = "training/accept" + static tran_user: string = "training/user" + static addScores: string = "scoreMgmt/scores" + } class TrainingStore extends BaseStore { constructor() { super(TrainingConfig) makeObservable(this, { - access: action + access: action, + id: observable, + userList: observable, + getUserListByTraning: action, + setUserScore: action, }) } + async setUserScore(ids: Array) { + let res= await baseHttp.post(TrainingConfig.addScores, {"list": ids}) + if (res.data==="ok"){ + this.getUserListByTraning(); + return true + } + } async access(id: number, param: any) { await baseHttp.put(TrainingConfig.ACCESS + "/" + id, param) this.getlist() } + async getUserListByTraning() { + let res = await baseHttp.get(TrainingConfig.tran_user + "/" + this.id, null) + let data: Array = [] + 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 } export const trainingStore = new TrainingStore() diff --git a/src/test_socke.html b/src/test_socke.html new file mode 100644 index 0000000..05df7de --- /dev/null +++ b/src/test_socke.html @@ -0,0 +1,143 @@ + + + + + + + + + +

Local Video

+ +
+ +

Remote Video

+
+
+ +

Logs

+
+ + + + + + + \ No newline at end of file diff --git a/src/ttttt.html b/src/ttttt.html new file mode 100644 index 0000000..3483cb6 --- /dev/null +++ b/src/ttttt.html @@ -0,0 +1,146 @@ + + + + + + + + + +

Local Video

+ +
+ +

Remote Video

+
+
+ +

Logs

+
+ + + + + + + \ No newline at end of file diff --git a/src/util/config.ts b/src/util/config.ts index 8cbfc15..e3a0e5f 100644 --- a/src/util/config.ts +++ b/src/util/config.ts @@ -1,5 +1,12 @@ 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 uploadUrl = "http://127.0.0.1:12214/"; + static ws = "ws://rw.quwanya.cn:12217/ws"; + } export default Config; \ No newline at end of file diff --git a/src/util/socket.ts b/src/util/socket.ts index a0bc81f..3696b05 100644 --- a/src/util/socket.ts +++ b/src/util/socket.ts @@ -1,3 +1,5 @@ +import Config from "./config" + export type AutoReconnectOptions = boolean | { maxRetries?: number retryInterval?: number @@ -33,9 +35,9 @@ export type AutoReconnectOptions = boolean | { public setAutoReconnectOptions(options: AutoReconnectOptions) { this.autoReconnect = options } - + 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.connectionStatus = ConnectionStatus.Connected this.emit('connected', null) diff --git a/src/util/webRtc.ts b/src/util/webRtc.ts index 45f9947..fd7f23c 100644 --- a/src/util/webRtc.ts +++ b/src/util/webRtc.ts @@ -1,12 +1,114 @@ +import SocketService from "./socket"; +const socketService = SocketService.getInstance(); class WebRtc { private mediaStream: MediaStream | Blob | null = null; + private pee: RTCPeerConnection | null = null; private video: HTMLVideoElement | null = null; async init() { - let constraints = { audio: false, video: true }; 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() { if (this.video) { @@ -15,6 +117,7 @@ class WebRtc { } } + close() { this.video?.pause(); (this.mediaStream as MediaStream)?.getTracks().forEach(track => track.stop());