优化批量执行指令和windows接入发送组合键
This commit is contained in:
parent
ba982dacd0
commit
038f59d155
102
pkg/api/ssh.go
102
pkg/api/ssh.go
@ -2,6 +2,7 @@ package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/labstack/echo/v4"
|
||||
@ -46,6 +47,22 @@ func (w *NextWriter) Read() ([]byte, int, error) {
|
||||
return buf, read, err
|
||||
}
|
||||
|
||||
const (
|
||||
Data = "data"
|
||||
Resize = "resize"
|
||||
Closed = "closed"
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
Type string `json:"type"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
type WindowSize struct {
|
||||
Height int `json:"height"`
|
||||
Width int `json:"width"`
|
||||
}
|
||||
|
||||
func SSHEndpoint(c echo.Context) error {
|
||||
ws, err := UpGrader.Upgrade(c.Response().Writer, c.Request(), nil)
|
||||
if err != nil {
|
||||
@ -60,12 +77,22 @@ func SSHEndpoint(c echo.Context) error {
|
||||
sshClient, err := CreateSshClient(assetId)
|
||||
if err != nil {
|
||||
logrus.Errorf("创建SSH客户端失败:%v", err.Error())
|
||||
msg := Message{
|
||||
Type: Closed,
|
||||
Content: err.Error(),
|
||||
}
|
||||
err := WriteMessage(ws, msg)
|
||||
return err
|
||||
}
|
||||
|
||||
session, err := sshClient.NewSession()
|
||||
if err != nil {
|
||||
logrus.Errorf("创建SSH会话失败:%v", err.Error())
|
||||
msg := Message{
|
||||
Type: Closed,
|
||||
Content: err.Error(),
|
||||
}
|
||||
err := WriteMessage(ws, msg)
|
||||
return err
|
||||
}
|
||||
defer session.Close()
|
||||
@ -93,15 +120,39 @@ func SSHEndpoint(c echo.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
msg := Message{
|
||||
Type: Data,
|
||||
Content: "Connect to server successfully.",
|
||||
}
|
||||
_ = WriteMessage(ws, msg)
|
||||
|
||||
var mut sync.Mutex
|
||||
var active = true
|
||||
|
||||
go func() {
|
||||
for true {
|
||||
mut.Lock()
|
||||
if !active {
|
||||
logrus.Debugf("会话: %v -> %v 关闭", sshClient.LocalAddr().String(), sshClient.RemoteAddr().String())
|
||||
break
|
||||
}
|
||||
mut.Unlock()
|
||||
|
||||
p, n, err := b.Read()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if n > 0 {
|
||||
WriteByteMessage(ws, p)
|
||||
msg := Message{
|
||||
Type: Data,
|
||||
Content: string(p),
|
||||
}
|
||||
message, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
logrus.Warnf("生成Json失败 %v", err)
|
||||
continue
|
||||
}
|
||||
WriteByteMessage(ws, message)
|
||||
}
|
||||
time.Sleep(time.Duration(100) * time.Millisecond)
|
||||
}
|
||||
@ -110,16 +161,53 @@ func SSHEndpoint(c echo.Context) error {
|
||||
for true {
|
||||
_, message, err := ws.ReadMessage()
|
||||
if err != nil {
|
||||
// web socket会话关闭后主动关闭ssh会话
|
||||
_ = session.Close()
|
||||
mut.Lock()
|
||||
active = false
|
||||
mut.Unlock()
|
||||
break
|
||||
}
|
||||
|
||||
var msg Message
|
||||
err = json.Unmarshal(message, &msg)
|
||||
if err != nil {
|
||||
logrus.Warnf("解析Json失败: %v, 原始字符串:%v", err, string(message))
|
||||
continue
|
||||
}
|
||||
_, err = stdinPipe.Write(message)
|
||||
if err != nil {
|
||||
logrus.Debugf("Tunnel write: %v", err)
|
||||
|
||||
switch msg.Type {
|
||||
case Resize:
|
||||
var winSize WindowSize
|
||||
err = json.Unmarshal([]byte(msg.Content), &winSize)
|
||||
if err != nil {
|
||||
logrus.Warnf("解析SSH会话窗口大小失败: %v", err)
|
||||
continue
|
||||
}
|
||||
if err := session.WindowChange(winSize.Height, winSize.Height); err != nil {
|
||||
logrus.Warnf("更改SSH会话窗口大小失败: %v", err)
|
||||
continue
|
||||
}
|
||||
case Data:
|
||||
_, err = stdinPipe.Write([]byte(msg.Content))
|
||||
if err != nil {
|
||||
logrus.Debugf("SSH会话写入失败: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func WriteMessage(ws *websocket.Conn, msg Message) error {
|
||||
message, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
logrus.Warnf("生成Json失败 %v", err)
|
||||
}
|
||||
WriteByteMessage(ws, message)
|
||||
return err
|
||||
}
|
||||
|
||||
func CreateSshClient(assetId string) (*ssh.Client, error) {
|
||||
asset, err := model.FindAssetById(assetId)
|
||||
if err != nil {
|
||||
@ -195,10 +283,6 @@ func CreateSshClient(assetId string) (*ssh.Client, error) {
|
||||
return sshClient, nil
|
||||
}
|
||||
|
||||
func WriteMessage(ws *websocket.Conn, message string) {
|
||||
WriteByteMessage(ws, []byte(message))
|
||||
}
|
||||
|
||||
func WriteByteMessage(ws *websocket.Conn, p []byte) {
|
||||
err := ws.WriteMessage(websocket.TextMessage, p)
|
||||
if err != nil {
|
||||
|
@ -89,6 +89,7 @@ func TunEndpoint(c echo.Context) error {
|
||||
configuration.SetParameter(guacd.DisableBitmapCaching, propertyMap[guacd.DisableBitmapCaching])
|
||||
configuration.SetParameter(guacd.DisableOffscreenCaching, propertyMap[guacd.DisableOffscreenCaching])
|
||||
configuration.SetParameter(guacd.DisableGlyphCaching, propertyMap[guacd.DisableGlyphCaching])
|
||||
configuration.SetParameter("server-layout", "en-us-qwerty")
|
||||
break
|
||||
case "ssh":
|
||||
if len(session.PrivateKey) > 0 && session.PrivateKey != "-" {
|
||||
|
20
web/package-lock.json
generated
20
web/package-lock.json
generated
@ -2537,14 +2537,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz",
|
||||
"integrity": "sha512-giAlZwstKbmvMk1OO7WXSj4OZ0keXAcl2TQq4LWHiiPH2ByaH7WeUzng+Qej8UPxxv+8lRTuouo0iaNDBuzIBA=="
|
||||
},
|
||||
"@types/http-proxy": {
|
||||
"version": "1.17.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.4.tgz",
|
||||
"integrity": "sha512-IrSHl2u6AWXduUaDLqYpt45tLVCtYv7o4Z0s1KghBCDgIIS9oW5K1H8mZG/A2CfeLdEa7rTd1ACOiHBc1EMT2Q==",
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/istanbul-lib-coverage": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz",
|
||||
@ -7972,18 +7964,6 @@
|
||||
"requires-port": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"http-proxy-middleware": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-1.0.6.tgz",
|
||||
"integrity": "sha512-NyL6ZB6cVni7pl+/IT2W0ni5ME00xR0sN27AQZZrpKn1b+qRh+mLbBxIq9Cq1oGfmTc7BUq4HB77mxwCaxAYNg==",
|
||||
"requires": {
|
||||
"@types/http-proxy": "^1.17.4",
|
||||
"http-proxy": "^1.18.1",
|
||||
"is-glob": "^4.0.1",
|
||||
"lodash": "^4.17.20",
|
||||
"micromatch": "^4.0.2"
|
||||
}
|
||||
},
|
||||
"http-signature": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
|
||||
|
@ -17,7 +17,8 @@
|
||||
"react-scripts": "^4.0.0",
|
||||
"typescript": "^3.9.7",
|
||||
"xterm": "^4.9.0",
|
||||
"xterm-addon-web-links": "^0.4.0"
|
||||
"xterm-addon-web-links": "^0.4.0",
|
||||
"xterm-addon-fit": "^0.4.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
|
@ -28,7 +28,7 @@ import {
|
||||
CloudUploadOutlined,
|
||||
CopyOutlined,
|
||||
DeleteOutlined,
|
||||
DesktopOutlined,
|
||||
DesktopOutlined, ExpandOutlined,
|
||||
FileZipOutlined,
|
||||
FolderAddOutlined,
|
||||
LoadingOutlined,
|
||||
@ -36,7 +36,7 @@ import {
|
||||
UploadOutlined
|
||||
} from '@ant-design/icons';
|
||||
import Upload from "antd/es/upload";
|
||||
import {download, getToken} from "../../utils/utils";
|
||||
import {download, exitFull, getToken, requestFullScreen} from "../../utils/utils";
|
||||
import './Access.css'
|
||||
import Draggable from 'react-draggable';
|
||||
|
||||
@ -87,7 +87,9 @@ class Access extends Component {
|
||||
confirmLoading: false,
|
||||
uploadVisible: false,
|
||||
uploadLoading: false,
|
||||
startTime: new Date()
|
||||
startTime: new Date(),
|
||||
fullScreen: false,
|
||||
fullScreenBtnText: '进入全屏'
|
||||
};
|
||||
|
||||
async componentDidMount() {
|
||||
@ -345,6 +347,7 @@ class Access extends Component {
|
||||
return true;
|
||||
}
|
||||
|
||||
console.log('--------------------')
|
||||
console.log(keysym)
|
||||
this.state.client.sendKeyEvent(1, keysym);
|
||||
if (keysym === 65288) {
|
||||
@ -370,6 +373,24 @@ class Access extends Component {
|
||||
});
|
||||
};
|
||||
|
||||
fullScreen = () => {
|
||||
let fs = this.state.fullScreen;
|
||||
if(fs){
|
||||
exitFull();
|
||||
this.setState({
|
||||
fullScreen: false,
|
||||
fullScreenBtnText: '进入全屏'
|
||||
})
|
||||
}else {
|
||||
requestFullScreen(document.documentElement);
|
||||
this.setState({
|
||||
fullScreen: true,
|
||||
fullScreenBtnText: '退出全屏'
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
showClipboard = () => {
|
||||
this.setState({
|
||||
clipboardVisible: true
|
||||
@ -823,11 +844,18 @@ class Access extends Component {
|
||||
<Menu.Item key="2" icon={<FileZipOutlined/>} onClick={this.showFileSystem}>
|
||||
文件管理
|
||||
</Menu.Item>
|
||||
<Menu.Item key="3" icon={<ExpandOutlined />} onClick={this.fullScreen}>
|
||||
{this.state.fullScreenBtnText}
|
||||
</Menu.Item>
|
||||
<SubMenu title="发送快捷键" icon={<DesktopOutlined/>}>
|
||||
<Menu.Item
|
||||
onClick={() => this.sendCombinationKey(['65507', '65513', '65535'])}>Ctrl+Alt+Delete</Menu.Item>
|
||||
<Menu.Item
|
||||
onClick={() => this.sendCombinationKey(['65507', '65513', '65288'])}>Ctrl+Alt+Backspace</Menu.Item>
|
||||
<Menu.Item
|
||||
onClick={() => this.sendCombinationKey(['65515', '114'])}>Windows+R</Menu.Item>
|
||||
<Menu.Item
|
||||
onClick={() => this.sendCombinationKey(['65515'])}>Windows</Menu.Item>
|
||||
</SubMenu>
|
||||
</Menu>
|
||||
);
|
||||
@ -892,22 +920,6 @@ class Access extends Component {
|
||||
</Affix>
|
||||
</Draggable>
|
||||
|
||||
{/*{*/}
|
||||
{/* this.state.protocol === 'ssh' || this.state.protocol === 'rdp' ?*/}
|
||||
{/* <Affix style={{position: 'absolute', top: 50, right: 50}}>*/}
|
||||
{/* <Button*/}
|
||||
{/* shape="circle"*/}
|
||||
{/* icon={<FolderOpenOutlined/>}*/}
|
||||
{/* onClick={() => {*/}
|
||||
{/* this.showFileSystem();*/}
|
||||
{/* }}*/}
|
||||
{/* >*/}
|
||||
{/* </Button>*/}
|
||||
{/* </Affix>*/}
|
||||
{/* : null*/}
|
||||
{/*}*/}
|
||||
|
||||
|
||||
<Drawer
|
||||
title={title}
|
||||
placement="right"
|
||||
|
@ -1,16 +1,16 @@
|
||||
import React, {Component} from 'react';
|
||||
import "xterm/css/xterm.css"
|
||||
import {Terminal} from "xterm";
|
||||
import {AttachAddon} from 'xterm-addon-attach';
|
||||
import qs from "qs";
|
||||
import {wsServer} from "../../common/constants";
|
||||
import "./Console.css"
|
||||
import {getToken} from "../../utils/utils";
|
||||
import {FitAddon} from 'xterm-addon-fit'
|
||||
|
||||
|
||||
function getGeometry(width, height) {
|
||||
const cols = Math.floor(width / 9);
|
||||
const rows = Math.floor(height / 17);
|
||||
const rows = Math.floor(height / 17) - 1;
|
||||
return [cols, rows];
|
||||
}
|
||||
|
||||
@ -18,9 +18,11 @@ class Console extends Component {
|
||||
|
||||
state = {
|
||||
containerOverflow: 'hidden',
|
||||
containerWidth: 0,
|
||||
containerHeight: 0,
|
||||
term: null,
|
||||
width: 0,
|
||||
height: 0,
|
||||
term: undefined,
|
||||
webSocket: undefined,
|
||||
fitAddon: undefined
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
@ -30,9 +32,6 @@ class Console extends Component {
|
||||
let width = this.props.width;
|
||||
let height = this.props.height;
|
||||
|
||||
// let width = Math.floor(window.innerWidth * scale);
|
||||
// let height = Math.floor(window.innerHeight * scale);
|
||||
|
||||
let params = {
|
||||
'width': width,
|
||||
'height': height,
|
||||
@ -41,66 +40,127 @@ class Console extends Component {
|
||||
|
||||
let paramStr = qs.stringify(params);
|
||||
|
||||
let [cols, rows] = getGeometry(width, height);
|
||||
const ua = navigator.userAgent.toLowerCase();
|
||||
let lineHeight = 1;
|
||||
if (ua.includes('windows')) {
|
||||
lineHeight = 1.1;
|
||||
}
|
||||
|
||||
let term = new Terminal({
|
||||
cols: cols,
|
||||
rows: rows,
|
||||
// screenKeys: true,
|
||||
// fontFamily: 'menlo',
|
||||
fontFamily: 'monaco, Consolas, "Lucida Console", monospace',
|
||||
fontSize: 14,
|
||||
lineHeight: lineHeight,
|
||||
theme: {
|
||||
background: '#1b1b1b'
|
||||
},
|
||||
rightClickSelectsWord: true,
|
||||
});
|
||||
|
||||
// let fitAddon = new FitAddon();
|
||||
// term.loadAddon(fitAddon);
|
||||
let fitAddon = new FitAddon();
|
||||
term.loadAddon(fitAddon);
|
||||
term.open(this.refs.terminal);
|
||||
// fitAddon.fit();
|
||||
|
||||
term.writeln('正在努力连接服务器中...');
|
||||
term.writeln('Trying to connect to the server ...');
|
||||
term.onResize(e => {
|
||||
|
||||
});
|
||||
|
||||
term.onData(data => {
|
||||
let webSocket = this.state.webSocket;
|
||||
if (webSocket !== undefined) {
|
||||
webSocket.send(JSON.stringify({type: 'data', content: data}));
|
||||
}
|
||||
});
|
||||
|
||||
let token = getToken();
|
||||
|
||||
let webSocket = new WebSocket(wsServer + '/ssh?X-Auth-Token=' + token + '&' + paramStr);
|
||||
term.loadAddon(new AttachAddon(webSocket));
|
||||
this.props.appendWebsocket(webSocket);
|
||||
|
||||
this.props.appendWebsocket({'id': assetId, 'ws': webSocket});
|
||||
|
||||
webSocket.onopen = (e => {
|
||||
term.clear();
|
||||
term.focus();
|
||||
|
||||
if (command !== '') {
|
||||
webSocket.send(command + String.fromCharCode(13));
|
||||
}
|
||||
|
||||
this.onWindowResize();
|
||||
});
|
||||
|
||||
webSocket.onerror = (e) => {
|
||||
term.writeln("Failed to connect to server.");
|
||||
}
|
||||
webSocket.onclose = (e) => {
|
||||
term.writeln("Connection is closed.");
|
||||
}
|
||||
|
||||
let executedCommand = false
|
||||
webSocket.onmessage = (e) => {
|
||||
let msg = JSON.parse(e.data);
|
||||
switch (msg['type']) {
|
||||
case 'data':
|
||||
term.write(msg['content']);
|
||||
break;
|
||||
case 'closed':
|
||||
term.writeln(`\x1B[1;3;31m${msg['content']}\x1B[0m `)
|
||||
webSocket.close();
|
||||
break;
|
||||
}
|
||||
|
||||
if (!executedCommand) {
|
||||
if (command !== '') {
|
||||
let webSocket = this.state.webSocket;
|
||||
if (webSocket !== undefined && webSocket.readyState === WebSocket.OPEN) {
|
||||
webSocket.send(JSON.stringify({type: 'data', content: command + String.fromCharCode(13)}));
|
||||
}
|
||||
}
|
||||
executedCommand = true;
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({
|
||||
term: term,
|
||||
containerWidth: width,
|
||||
containerHeight: height
|
||||
fitAddon: fitAddon,
|
||||
webSocket: webSocket,
|
||||
width: width,
|
||||
height: height
|
||||
});
|
||||
|
||||
// window.addEventListener('resize', this.onWindowResize);
|
||||
window.addEventListener('resize', this.onWindowResize);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
let webSocket = this.state.webSocket;
|
||||
if (webSocket) {
|
||||
webSocket.close()
|
||||
}
|
||||
}
|
||||
|
||||
onWindowResize = (e) => {
|
||||
let term = this.state.term;
|
||||
if (term) {
|
||||
const [cols, rows] = getGeometry(this.state.containerWidth, this.state.containerHeight);
|
||||
term.resize(cols, rows);
|
||||
let fitAddon = this.state.fitAddon;
|
||||
let webSocket = this.state.webSocket;
|
||||
if (term && fitAddon && webSocket) {
|
||||
|
||||
let height = term.cols;
|
||||
let width = term.rows;
|
||||
|
||||
try {
|
||||
fitAddon.fit();
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
term.focus();
|
||||
if(webSocket.readyState === WebSocket.OPEN){
|
||||
webSocket.send(JSON.stringify({type: 'resize', content: JSON.stringify({height, width})}));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<div ref='terminal' style={{
|
||||
<div ref='terminal' id='terminal' style={{
|
||||
overflow: this.state.containerOverflow,
|
||||
width: this.state.containerWidth,
|
||||
height: this.state.containerHeight,
|
||||
backgroundColor: 'black'
|
||||
width: this.state.width,
|
||||
height: this.state.height,
|
||||
backgroundColor: '#1b1b1b'
|
||||
}}/>
|
||||
</div>
|
||||
);
|
||||
|
@ -249,6 +249,8 @@ class Asset extends Component {
|
||||
} else {
|
||||
asset['tags'] = asset['tags'].split(',');
|
||||
}
|
||||
}else {
|
||||
asset['tags'] = [];
|
||||
}
|
||||
|
||||
this.setState({
|
||||
|
@ -1,8 +1,10 @@
|
||||
import React, {Component} from 'react';
|
||||
import {List, Card, Input, PageHeader} from "antd";
|
||||
import {Card, Input, List, PageHeader, Popconfirm} from "antd";
|
||||
import Console from "../access/Console";
|
||||
import {itemRender} from "../../utils/utils";
|
||||
import Logout from "../user/Logout";
|
||||
import './Command.css'
|
||||
|
||||
const {Search} = Input;
|
||||
const routes = [
|
||||
{
|
||||
@ -25,7 +27,8 @@ class BatchCommand extends Component {
|
||||
|
||||
state = {
|
||||
webSockets: [],
|
||||
assets: []
|
||||
assets: [],
|
||||
active: undefined,
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@ -66,7 +69,13 @@ class BatchCommand extends Component {
|
||||
<div className="page-search">
|
||||
<Search ref={this.commandRef} placeholder="请输入指令" onSearch={value => {
|
||||
for (let i = 0; i < this.state.webSockets.length; i++) {
|
||||
this.state.webSockets[i].send(value + String.fromCharCode(13))
|
||||
let ws = this.state.webSockets[i]['ws'];
|
||||
if (ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify({
|
||||
type: 'data',
|
||||
content: value + String.fromCharCode(13)
|
||||
}));
|
||||
}
|
||||
}
|
||||
this.commandRef.current.setValue('');
|
||||
}} enterButton='执行'/>
|
||||
@ -78,10 +87,23 @@ class BatchCommand extends Component {
|
||||
dataSource={this.state.assets}
|
||||
renderItem={item => (
|
||||
<List.Item>
|
||||
<Card title={item.name}>
|
||||
<Card title={item.name}
|
||||
className={this.state.active === item['id'] ? 'command-active' : ''}
|
||||
onClick={() => {
|
||||
if (this.state.active === item['id']) {
|
||||
this.setState({
|
||||
active: undefined
|
||||
})
|
||||
} else {
|
||||
this.setState({
|
||||
active: item['id']
|
||||
})
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Console assetId={item.id} command={this.state.command}
|
||||
width={(window.innerWidth - 350) / 2}
|
||||
height={400}
|
||||
height={420}
|
||||
appendWebsocket={this.appendWebsocket}/>
|
||||
</Card>
|
||||
</List.Item>
|
||||
|
4
web/src/components/command/Command.css
Normal file
4
web/src/components/command/Command.css
Normal file
@ -0,0 +1,4 @@
|
||||
.command-active {
|
||||
box-shadow: 0 0 0 2px #1890FF;
|
||||
outline: 2px solid #1890FF;
|
||||
}
|
@ -78,7 +78,7 @@ export const cloneObj = (obj, ignoreFields) => {
|
||||
export function download(url) {
|
||||
let aElement = document.createElement('a');
|
||||
aElement.setAttribute('download', '');
|
||||
aElement.setAttribute('target', '_blank');
|
||||
// aElement.setAttribute('target', '_blank');
|
||||
aElement.setAttribute('href', url);
|
||||
aElement.click();
|
||||
}
|
||||
@ -176,4 +176,37 @@ export function difference(a, b) {
|
||||
let aSet = new Set(a)
|
||||
let bSet = new Set(b)
|
||||
return Array.from(new Set(a.concat(b).filter(v => !aSet.has(v) || !bSet.has(v))))
|
||||
}
|
||||
|
||||
export function requestFullScreen(element) {
|
||||
// 判断各种浏览器,找到正确的方法
|
||||
const requestMethod = element.requestFullScreen || //W3C
|
||||
element.webkitRequestFullScreen || //FireFox
|
||||
element.mozRequestFullScreen || //Chrome等
|
||||
element.msRequestFullScreen; //IE11
|
||||
if (requestMethod) {
|
||||
requestMethod.call(element);
|
||||
} else if (typeof window.ActiveXObject !== "undefined") { //for Internet Explorer
|
||||
const wScript = new window.ActiveXObject("WScript.Shell");
|
||||
if (wScript !== null) {
|
||||
wScript.SendKeys("{F11}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//退出全屏 判断浏览器种类
|
||||
export function exitFull() {
|
||||
// 判断各种浏览器,找到正确的方法
|
||||
const exitMethod = document.exitFullscreen || //W3C
|
||||
document.mozCancelFullScreen || //FireFox
|
||||
document.webkitExitFullscreen || //Chrome等
|
||||
document.webkitExitFullscreen; //IE11
|
||||
if (exitMethod) {
|
||||
exitMethod.call(document);
|
||||
} else if (typeof window.ActiveXObject !== "undefined") { //for Internet Explorer
|
||||
const wScript = new window.ActiveXObject("WScript.Shell");
|
||||
if (wScript !== null) {
|
||||
wScript.SendKeys("{F11}");
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user