This commit is contained in:
snltty
2023-09-14 15:51:48 +08:00
parent 702d508d82
commit 72b57c4920
100 changed files with 23788 additions and 1 deletions

Submodule cmonitor.web deleted from b5a3da842b

23
cmonitor.web/.gitignore vendored Normal file
View File

@@ -0,0 +1,23 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

19
cmonitor.web/README.md Normal file
View File

@@ -0,0 +1,19 @@
# cmonitor.web
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

View File

@@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

View File

@@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"baseUrl": "./",
"moduleResolution": "node",
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
}
}

18146
cmonitor.web/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

30
cmonitor.web/package.json Normal file
View File

@@ -0,0 +1,30 @@
{
"name": "cmonitor.web",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build"
},
"dependencies": {
"@element-plus/icons-vue": "^2.1.0",
"core-js": "^3.8.3",
"element-plus": "^2.3.9",
"vconsole": "^3.15.1",
"vue": "^3.2.13",
"vue-router": "^4.0.3"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-router": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"stylus": "^0.55.0",
"stylus-loader": "^6.1.0"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead",
"not ie 11"
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>
<%= htmlWebpackPlugin.options.title %>
</title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

5
cmonitor.web/src/App.vue Normal file
View File

@@ -0,0 +1,5 @@
<template>
<router-view />
</template>
<style lang="stylus"></style>

View File

@@ -0,0 +1,22 @@
import { sendWebsocketMsg } from './request'
export const getActiveTimes = (name) => {
return sendWebsocketMsg('active/get', name);
}
export const activeTimesClear = (name) => {
return sendWebsocketMsg('active/clear', name);
}
export const activeDisallow = (names, filenames) => {
return sendWebsocketMsg('active/disallow', {
names, filenames
});
}
export const activeAddExe = (data) => {
return sendWebsocketMsg('active/add', data);
}
export const activeDelExe = (username, id) => {
return sendWebsocketMsg('active/del', {
username, id
});
}

View File

@@ -0,0 +1,7 @@
import { sendWebsocketMsg } from './request'
export const exec = (names, commands) => {
return sendWebsocketMsg('command/exec', {
names, commands
});
}

View File

@@ -0,0 +1,36 @@
import { sendWebsocketMsg } from './request'
export const getRules = () => {
return sendWebsocketMsg('hijack/info');
}
export const addName = (data) => {
return sendWebsocketMsg('hijack/addName', data);
}
export const addProcessGroup = (data) => {
return sendWebsocketMsg('hijack/addProcessGroup', data);
}
export const deleteProcessGroup = (data) => {
return sendWebsocketMsg('hijack/deleteProcessGroup', data);
}
export const addProcess = (data) => {
return sendWebsocketMsg('hijack/addProcess', data);
}
export const deleteProcess = (data) => {
return sendWebsocketMsg('hijack/deleteProcess', data);
}
export const addRule = (data) => {
return sendWebsocketMsg('hijack/AddRule', data);
}
export const deleteRule = (data) => {
return sendWebsocketMsg('hijack/deleteRule', data);
}
export const updateDevices = (data) => {
return sendWebsocketMsg('hijack/UpdateDevices', data);
}
export const setRules = (data) => {
return sendWebsocketMsg('hijack/setRules', data);
}

View File

@@ -0,0 +1,8 @@
import { sendWebsocketMsg } from './request'
export const setLight = (names, value) => {
return sendWebsocketMsg('light/update', {
names, value
});
}

View File

@@ -0,0 +1,8 @@
import { sendWebsocketMsg } from './request'
export const llockUpdate = (names, value) => {
return sendWebsocketMsg('llock/update', {
names, value
});
}

View File

@@ -0,0 +1,8 @@
import { sendWebsocketMsg } from './request'
export const reportUpdate = (names) => {
return sendWebsocketMsg('report/update', names);
}
export const reportPing = (names) => {
return sendWebsocketMsg('report/ping', names);
}

View File

@@ -0,0 +1,143 @@
import { ElMessage } from 'element-plus'
let requestId = 0, ws = null, wsUrl = '';
//请求缓存,等待回调
const requests = {};
const queues = [];
export const websocketState = { connected: false };
const sendQueueMsg = () => {
if (queues.length > 0 && websocketState.connected) {
ws.send(queues.shift());
}
setTimeout(sendQueueMsg, 1000 / 60);
}
sendQueueMsg();
//发布订阅
export const pushListener = {
subs: {
},
add: function (type, callback) {
if (typeof callback == 'function') {
if (!this.subs[type]) {
this.subs[type] = [];
}
this.subs[type].push(callback);
}
},
remove(type, callback) {
let funcs = this.subs[type] || [];
for (let i = funcs.length - 1; i >= 0; i--) {
if (funcs[i] == callback) {
funcs.splice(i, 1);
}
}
},
push(type, data) {
let funcs = this.subs[type] || [];
for (let i = funcs.length - 1; i >= 0; i--) {
funcs[i](data);
}
}
}
//消息处理
const onWebsocketOpen = () => {
websocketState.connected = true;
pushListener.push(websocketStateChangeKey, websocketState.connected);
}
const onWebsocketClose = (e) => {
websocketState.connected = false;
pushListener.push(websocketStateChangeKey, websocketState.connected);
initWebsocket();
}
export const onWebsocketMsg = (msg) => {
if (typeof msg.data != 'string') {
msg.data.arrayBuffer().then((res) => {
const length = new DataView(res).getInt8();
const reader = new FileReader();
reader.readAsText(msg.data.slice(4, 4 + length), 'utf8');
reader.onload = () => {
let json = JSON.parse(reader.result);
json.Content = {
Name: json.Content,
Img: msg.data.slice(4 + length, msg.data.length)
};
pushMessage(json);
}
});
return;
}
let json = JSON.parse(msg.data);
pushMessage(json);
}
const pushMessage = (json) => {
let callback = requests[json.RequestId];
if (callback) {
if (json.Code == 0) {
callback.resolve(json.Content);
} else if (json.Code == 1) {
callback.reject(json.Content);
}
else if (json.Code == 255) {
callback.reject(json.Content);
if (!callback.errHandle) {
ElMessage.error(`${callback.path}:${json.Content}`);
}
} else {
pushListener.push(json.Path, json.Content);
}
delete requests[json.RequestId];
} else {
pushListener.push(json.Path, json.Content);
}
}
export const initWebsocket = (url = wsUrl) => {
if (ws != null) {
ws.close();
}
wsUrl = url;
ws = new WebSocket(wsUrl);
ws.onopen = onWebsocketOpen;
ws.onclose = onWebsocketClose
ws.onmessage = onWebsocketMsg
}
//发送消息
export const sendWebsocketMsg = (path, msg = {}, errHandle = false) => {
return new Promise((resolve, reject) => {
let id = ++requestId;
try {
requests[id] = { resolve, reject, errHandle, path };
let str = JSON.stringify({
Path: path,
RequestId: id,
Content: typeof msg == 'string' ? msg : JSON.stringify(msg)
});
if (websocketState.connected) {
ws.send(str);
} else {
queues.push(str);
}
} catch (e) {
reject('网络错误~');
ElMessage.error('网络错误~');
delete requests[id];
}
});
}
const websocketStateChangeKey = Symbol();
export const subWebsocketState = (callback) => {
pushListener.add(websocketStateChangeKey, callback);
}
export const subNotifyMsg = (path, callback) => {
pushListener.add(path, callback);
}
export const unsubNotifyMsg = (path, callback) => {
pushListener.remove(path, callback);
}

View File

@@ -0,0 +1,5 @@
import { sendWebsocketMsg } from './request'
export const screenUpdate = (names) => {
return sendWebsocketMsg('screen/update', names);
}

View File

@@ -0,0 +1,11 @@
import { sendWebsocketMsg } from './request'
export const getList = () => {
return sendWebsocketMsg('signin/list');
}
export const getConfig = () => {
return sendWebsocketMsg('signin/config');
}
export const delDevice = (name) => {
return sendWebsocketMsg('signin/del', name);
}

View File

@@ -0,0 +1,8 @@
import { sendWebsocketMsg } from './request'
export const activeUpdate = (name) => {
return sendWebsocketMsg('active/update', name);
}
export const activeClear = (name) => {
return sendWebsocketMsg('active/clear', name);
}

View File

@@ -0,0 +1,8 @@
import { sendWebsocketMsg } from './request'
export const usbUpdate = (names, value) => {
return sendWebsocketMsg('usb/update', {
names, value
});
}

View File

@@ -0,0 +1,13 @@
import { sendWebsocketMsg } from './request'
export const setVolume = (names, value) => {
return sendWebsocketMsg('volume/update', {
names, value
});
}
export const setVolumeMute = (names, value) => {
return sendWebsocketMsg('volume/mute', {
names, value
});
}

View File

@@ -0,0 +1,7 @@
import { sendWebsocketMsg } from './request'
export const wallpaperUpdate = (names, value, url = '') => {
return sendWebsocketMsg('wallpaper/update', {
names, value, url
});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -0,0 +1,339 @@
* {
margin: 0;
padding: 0;
list-style: none;
}
a {
text-decoration: none;
color: #6f9ccd;
}
.flex {
display: flex;
display: -webkit-flex;
display: -ms-flex;
display: -o-flex;
flex-wrap: wrap;
}
.flex-nowrap {
flex-wrap: nowrap;
}
.flex-wrap {
flex-wrap: wrap;
}
.flex-column {
flex-direction: column;
}
.flex-row {
flex-direction: row;
}
.flex-1 {
-webkit-box-flex: 1;
-ms-flex-positive: 1;
flex: 1 1 0%;
}
.absolute {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
}
.relative {
position: relative;
}
.h-100 {
height: 100%;
}
.w-100 {
width: 100%;
}
.t-c {
text-align: center;
}
.t-r {
text-align: right;
}
.t-l {
text-align: left !important;
}
.m-r-1 {
margin-right: 1rem;
}
table {
border-spacing: 0;
border-collapse: collapse;
}
html {
font-size: 10px;
/* background-color: #282c34; */
background-color: #f4f4f4;
}
body {
overflow: hidden;
}
span.split {
width: 0.6rem;
}
span.split-pad {
padding: 0 0.3rem;
}
span.split-pad10 {
padding: 0 1rem;
}
.middle {
vertical-align: middle;
}
.red {
color: red;
}
.scrollbar,
.scrollbar-4,
.scrollbar-10 {
overflow: auto;
}
.scrollbar::-webkit-scrollbar {
width: 4px;
height: 1px;
}
.scrollbar::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.1);
border-radius: 10px;
}
.scrollbar-4::-webkit-scrollbar {
width: 4px;
height: 4px;
}
.scrollbar-4::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.1);
border-radius: 10px;
}
.scrollbar-10::-webkit-scrollbar {
width: 10px;
height: 1px;
}
.scrollbar-10::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.1);
border-radius: 10px;
}
.el-table--scrollable-y .el-table__body-wrapper::-webkit-scrollbar {
background: #f5f5f5
}
.el-table--scrollable-y .el-table__body-wrapper::-webkit-scrollbar-thumb {
background: #ddd;
}
.el-collapse-item__header {
background-color: #fafafa !important;
border-left: 1px solid #EBEEF5;
border-right: 1px solid #EBEEF5;
padding: 0 2rem;
}
.el-collapse-item__content {
padding: 1rem;
border: 1px solid #EBEEF5;
border-bottom: 0;
}
.el-input.w-search .el-input__inner,
.el-input.w-search,
.el-select.w-search {
width: 10rem;
}
.el-form-item.w-search .el-form-item__label {
font-size: 1.2rem;
}
.table-search .el-form--inline .el-form-item {
margin-bottom: 0;
}
.el-dropdown {
font-size: 1.3rem;
}
.el-dropdown-menu__item {
font-size: 1.3rem;
}
.el-dropdown-menu__item a {
color: #333;
}
.el-input__inner:focus {
border-color: var(--main-color);
}
.el-date-editor.el-input.w-auto,
.el-date-editor.el-input__inner.w-auto {
width: auto;
}
.el-table .active-row {
background: rgba(0, 0, 0, 0.15);
}
.el-table .table-green-row {
background: rgba(0, 255, 0, 0.15);
}
.el-table .table-red-row {
background: rgba(255, 0, 0, 0.15);
}
.el-table .table-green-row td {
background: transparent !important;
}
.el-table .table-red-row td {
background: transparent !important;
}
.el-date-editor.el-input,
.el-date-editor.el-input__inner {
width: auto
}
.el-table .active-row td {
background: transparent !important;
}
.el-table--border th {
background-color: #fafafa;
}
.el-table--border,
.el-table--group,
.el-table-filter,
.el-table td,
.el-table th.is-leaf {
border-color: var(--main-border-color);
}
.el-pagination.is-background .el-pager li:not(.disabled).active {
background-color: var(--main-color);
}
.el-pagination.is-background .el-pager li:not(.disabled):hover {
color: var(--main-color);
}
.el-pagination .btn-next .el-icon,
.el-pagination .btn-prev .el-icon {
width: inherit;
}
.el-dialog {
max-width: 96%;
}
.el-dialog__body .el-form-item:last-child {
margin-bottom: 0;
}
.el-input-group__append,
.el-input-group__prepend {
padding: 0 4px !important;
background-color: transparent !important;
}
.el-checkbox__label .el-icon {
vertical-align: middle;
margin-top: -2px;
}
/* .el-input__inner:read-only {
background-color: #fafafa;
} */
.el-color-picker {
vertical-align: middle;
}
.el-color-picker__trigger {
border: 0 !important;
}
.el-color-picker__color {
border: 0 !important;
border-radius: 2px;
}
.el-color-picker__color-inner {
border-radius: 2px;
}
.el-message {
min-width: 10rem !important;
}
.card-header {
font-size: 1.4rem;
}
.forward-wrap .el-table--small.el-table .el-table__expanded-cell[class*=cell] {
padding: 20px 50px 20px 50px;
}
h3.title {
font-size: 1.6rem;
padding-bottom: .6rem;
color: #555;
}
.el-message-box {
max-width: 90%;
}
.el-select-dropdown__item {
padding-right: 2rem !important;
}
.el-form-item--default {
--font-size: 13px !important;
}
.el-input__inner {
font-size: 13px;
}
.el-dialog--center .el-dialog__body {
padding-top: 1rem !important;
padding-bottom: 1rem !important;
}

45
cmonitor.web/src/main.js Normal file
View File

@@ -0,0 +1,45 @@
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
const app = createApp(App);
import './assets/style.css'
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css'
import 'element-plus/theme-chalk/display.css'
import 'element-plus/theme-chalk/dark/css-vars.css'
import {
ChromeFilled, Promotion, Grid, ArrowDown, Upload, Download, EditPen, Delete, Refresh, BellFilled, Microphone
, Position, Message, Bell, Mute, SwitchButton, Lock, DataLine, CirclePlus, QuestionFilled, Monitor, Sunny, Warning, Umbrella
} from '@element-plus/icons-vue'
app.component(ChromeFilled.name, ChromeFilled);
app.component(Promotion.name, Promotion);
app.component(Grid.name, Grid);
app.component(ArrowDown.name, ArrowDown);
app.component(Upload.name, Upload);
app.component(Download.name, Download);
app.component(EditPen.name, EditPen);
app.component(Delete.name, Delete);
app.component(Refresh.name, Refresh);
app.component(BellFilled.name, BellFilled);
app.component(Microphone.name, Microphone);
app.component(Position.name, Position);
app.component(Message.name, Message);
app.component(Bell.name, Bell);
app.component(Mute.name, Mute);
app.component(SwitchButton.name, SwitchButton);
app.component(Lock.name, Lock);
app.component(DataLine.name, DataLine);
app.component(CirclePlus.name, CirclePlus);
app.component(QuestionFilled.name, QuestionFilled);
app.component(Monitor.name, Monitor);
app.component(Sunny.name, Sunny);
app.component(Warning.name, Warning);
app.component(Umbrella.name, Umbrella);
app.use(ElementPlus, { size: 'default' }).use(router).mount('#app');

View File

@@ -0,0 +1,14 @@
import { createRouter, createWebHashHistory } from 'vue-router'
const routes = [
{
path: '/',
name: 'home',
component: () => import('../views/Index.vue')
}
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router

View File

@@ -0,0 +1,111 @@
<template>
<div>
<el-dialog title="选择角色" destroy-on-close v-model="showSelectUsername" center :show-close="false" :close-on-click-modal="false" align-center width="70%">
<div class="username-wrap t-c">
<el-select filterable allow-create default-first-option v-model="state.username" @change="handleChange" placeholder="选择角色" size="large">
<el-option v-for="item in state.usernames" :key="item" :label="item" :value="item" />
</el-select>
</div>
<template #footer>
<el-button type="primary" @click="handleUsername"> </el-button>
</template>
</el-dialog>
<el-dialog title="管理端口" destroy-on-close v-model="showPort" center :show-close="false" :close-on-click-modal="false" align-center width="70%">
<div class="port-wrap t-c">
<el-input v-model="state.port" style="width:auto"></el-input>
</div>
<template #footer>
<el-button type="primary" @click="handleConnect"> </el-button>
</template>
</el-dialog>
</div>
</template>
<script>
import { computed, onMounted, reactive, watch } from 'vue';
import { initWebsocket } from '../apis/request'
import { getRules, addName } from '../apis/hijack'
import { useRoute } from 'vue-router';
import { injectGlobalData } from './provide';
export default {
setup() {
const globalData = injectGlobalData();
const route = useRoute();
const port = +(route.query.api || localStorage.getItem('port') || 1801);
localStorage.setItem('port', port);
globalData.value.username = globalData.value.username || localStorage.getItem('username') || '';
const state = reactive({
port: port,
usernames: [],
username: globalData.value.username,
showPort: false
});
const showSelectUsername = computed(() => !!!globalData.value.username && globalData.value.connected);
const showPort = computed(() => globalData.value.connected == false && state.showPort);
watch(() => globalData.value.updateFlag, () => {
_getRules();
});
const _getRules = () => {
getRules().then((res) => {
globalData.value.usernames = res;
state.usernames = Object.keys(res);
}).catch(() => { });
}
onMounted(() => {
handleUsername();
handleConnect();
_getRules();
setTimeout(() => {
state.showPort = true;
}, 100);
});
const handleConnect = () => {
initWebsocket(`ws://${window.location.hostname}:${state.port}`);
localStorage.setItem('port', state.port);
}
const handleUsername = () => {
globalData.value.username = state.username || '';
localStorage.setItem('username', globalData.value.username);
document.title = `班长-${globalData.value.username}`
}
const handleChange = (value) => {
addName(value).then(() => {
globalData.value.updateFlag = Date.now();
}).catch(() => {
globalData.value.updateFlag = Date.now();
})
}
return {
state, showSelectUsername, showPort, handleUsername, handleConnect, handleChange
}
}
}
</script>
<style lang="stylus" scoped>
.head-wrap {
text-align: center;
padding: 0.5rem 0;
line-height: 4rem;
border-bottom: 1px solid #ddd;
background-color: #f0f0f0;
font-size: 1.5rem;
font-weight: bold;
z-index: 999;
position: relative;
box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.075);
}
img {
height: 4rem;
vertical-align: middle;
margin-right: 0.6rem;
}
</style>

View File

@@ -0,0 +1,59 @@
<template>
<div class="main-wrap flex flex-column flex-nowrap">
<div class="head">
<Head></Head>
</div>
<div class="body flex-1 scrollbar" v-if="showList">
<Device></Device>
</div>
</div>
</template>
<script>
import Head from './Head.vue'
import Device from './device/Index.vue'
import { subWebsocketState } from '../apis/request'
import { computed } from 'vue'
import { provideGlobalData } from './provide'
export default {
components: { Head, Device },
setup() {
const globalData = provideGlobalData();
subWebsocketState((state) => {
globalData.value.connected = state;
})
const showList = computed(() => !!globalData.value.username && globalData.value.connected);
return {
showList
}
}
}
</script>
<style lang="stylus" scoped>
@media (min-width: 768px) {
.main-wrap {
border: 2px solid #d0d7de;
height: 90% !important;
width: 390px !important;
}
}
.main-wrap {
width: 100%;
position: absolute;
left: 50%;
top: 50%;
transform: translateX(-50%) translateY(-50%);
background-color: #fff;
height: 100%;
.body {
position: relative;
background-color: #fafafa;
}
}
</style>

View File

@@ -0,0 +1,93 @@
<template>
<div class="device-list-wrap absolute flex flex-column" id="device-list-wrap">
<div class="items flex-1 relative scrollbar">
<Items></Items>
</div>
<div class="foot">
<div class="foot-options">
<FootOptions></FootOptions>
</div>
<div class="foot-menu">
<FootMenu></FootMenu>
</div>
</div>
<template v-for="(item,index) in indexModules" :key="index">
<component :is="item"></component>
</template>
</div>
</template>
<script>
import FootMenu from './wraps/FootMenu.vue'
import FootOptions from './wraps/FootOptions.vue'
import Items from './wraps/Items.vue'
import { providePluginState } from './provide'
import { provide, ref, watch } from 'vue'
export default {
components: { Items, FootMenu, FootOptions },
setup() {
const files = require.context('./plugins/', true, /index\.js/);
const pluginSettings = files.keys().map(c => files(c).default);
const pluginState = pluginSettings.reduce((data, item, index) => {
if (item.state) {
data = Object.assign(data, item.state);
}
return data;
}, {});
const state = providePluginState(pluginState);
const indexFiles = require.context('./plugins/', true, /Index\.vue/);
const indexModules = indexFiles.keys().map(c => indexFiles(c).default);
return {
indexModules
}
}
}
</script>
<style lang="stylus" scoped>
.device-list-wrap {
.head {
padding: 2rem 1rem 1rem 1rem;
border-bottom: 1px solid #ddd;
background-color: #f0f0f0;
z-index: 999;
position: relative;
box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.075);
}
.foot {
position: absolute;
z-index: 999;
left: 1rem;
right: 1rem;
bottom: 1rem;
border-radius: 4px;
&:after {
content: '';
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
z-index: -1;
border: 1px solid rgba(255, 255, 255, 0.4);
border-radius: 4px;
background-color: rgba(186, 217, 255, 0.5);
backdrop-filter: blur(2px);
}
}
.items {
padding: 1rem;
transform-style: preserve-3d;
perspective: 600px;
background-color: #333;
background-image: url('../../assets/bg.webp');
background-size: cover;
padding-bottom: 13rem;
}
}
</style>

View File

@@ -0,0 +1,108 @@
<template>
<div class="checkbox-wrap absolute flex flex-column">
<div class="head flex">
<span>
<el-checkbox :indeterminate="state.isIndeterminate" v-model="state.checkAll" @change="handleCheckAllChange" :label="state.title" size="large" />
</span>
<span class="flex-1"></span>
<slot name="title"></slot>
</div>
<div class="body flex-1 scrollbar">
<el-checkbox-group v-model="state.checkList" @change="handleChange">
<ul>
<template v-for="(item,index) in state.data" :key="index">
<li class="flex">
<div class="flex-1">
<el-checkbox :label="item[state.label]" size="large">
<slot name="name" :item="item">
{{item[state.text]}}
</slot>
</el-checkbox>
</div>
<slot name="oper" :item="item"></slot>
</li>
</template>
</ul>
</el-checkbox-group>
</div>
</div>
</template>
<script>
import { computed, onMounted, reactive, watch } from 'vue'
export default {
props: ['title', 'items', 'data', 'label', 'text'],
setup(props, { emit }) {
const state = reactive({
title: props.title,
label: props.label,
text: props.text || props.label,
data: computed(() => props.data),
checkList: props.items.map(c => c[props.label]),
checkAll: false,
isIndeterminate: false
});
watch(() => props.items, () => {
state.checkList = props.items.map(c => c[props.label]);
updateCheckAll(state.checkList);
})
const handleCheckAllChange = (value) => {
if (value) {
state.checkList = state.data.map(c => c[state.label]);
} else {
state.checkList = [];
}
updateCheckAll(state.checkList);
}
const handleChange = (values) => {
updateCheckAll(values);
}
const updateCheckAll = (values) => {
const checkedCount = values.length;
state.isIndeterminate = checkedCount > 0 && checkedCount < state.data.length;
state.checkAll = checkedCount > 0 && checkedCount == state.data.length;
}
onMounted(() => {
updateCheckAll(state.checkList);
});
const getData = () => {
return state.checkList;
}
return { state, handleCheckAllChange, handleChange, getData }
}
}
</script>
<style lang="stylus" scoped>
.checkbox-wrap {
border: 1px solid #ddd;
}
.head {
border-bottom: 1px solid #ddd;
line-height: 4rem;
padding: 0 1rem;
background-color: #fafafa;
}
.body {
ul {
padding: 1rem 0;
}
li {
padding: 0 1rem;
.el-checkbox {
width: 100%;
white-space: pre-wrap;
word-break: break-all;
}
}
}
</style>

View File

@@ -0,0 +1,76 @@
<template>
<div class="checkbox-wrap absolute flex flex-column">
<div class="head flex">
<span>
{{state.title}}
</span>
<span class="flex-1"></span>
</div>
<div class="body flex-1 scrollbar">
<slot name="wrap">
<ul>
<template v-for="(item,index) in state.data" :key="index">
<li class="flex">
<slot :item="item">
<div class="default" @click="handleClick(item)">
{{item}}
</div>
</slot>
</li>
</template>
</ul>
</slot>
</div>
</div>
</template>
<script>
import { computed, reactive } from 'vue'
export default {
props: ['title', 'data'],
setup(props, { emit }) {
const state = reactive({
title: props.title,
data: computed(() => props.data),
});
const handleClick = (item) => {
emit('prev', item);
}
return { state, handleClick }
}
}
</script>
<style lang="stylus" scoped>
.checkbox-wrap {
border: 1px solid #ddd;
}
.head {
border-bottom: 1px solid #ddd;
line-height: 4rem;
padding: 0 1rem;
background-color: #fafafa;
}
.body {
ul {
padding: 1rem 0;
}
li {
.el-checkbox {
width: 100%;
}
&>div.default {
padding: 0.6rem 1rem;
line-height: 2rem;
}
&:hover {
background-color: rgba(0, 0, 0, 0.1);
}
}
}
</style>

View File

@@ -0,0 +1,147 @@
<template>
<el-dialog class="options" title="窗口使用时间统计" destroy-on-close v-model="state.show" center align-center width="94%">
<div class="wrap flex flex-column">
<h4>{{state.startTime}} - 至今({{(state.totalTime/1000).toFixed(2)}}s)</h4>
<div class="inner flex-1 scrollbar">
<ul>
<template v-for="(item,index) in state.list" :key="index">
<li>
<dl>
<dt>{{item.Desc}} <el-button @click="showTitles(item)" size="small">{{item.titleLength}}</el-button></dt>
<dd>
<el-progress :percentage="(item.Time/state.totalTime*100).toFixed(2)" />
</dd>
</dl>
</li>
</template>
</ul>
</div>
</div>
<template #footer>
<el-button @click="handleCancel"> </el-button>
<el-button type="primary" @click="handleCancel"> </el-button>
</template>
</el-dialog>
<el-dialog class="options" title="详细标题" destroy-on-close v-model="state.showTitles" center align-center width="94%">
<div class="wrap flex flex-column">
<h5>{{state.currentTime/1000}}s</h5>
<div class="inner flex-1 scrollbar">
<ul>
<template v-for="(item,index) in state.currentTitles" :key="index">
<li>
<dl>
<dt>{{item.t}}</dt>
<dd>
<el-progress :percentage="(item.v/state.currentTime*100).toFixed(2)" />
</dd>
</dl>
</li>
</template>
</ul>
</div>
</div>
<template #footer>
<el-button @click="handleCancel"> </el-button>
<el-button type="primary" @click="handleCancel"> </el-button>
</template>
</el-dialog>
</template>
<script>
import { reactive } from '@vue/reactivity';
import { onMounted, watch } from '@vue/runtime-core';
import { getActiveTimes } from '../../../../apis/active'
import { injectPluginState } from '../../provide'
export default {
props: ['modelValue'],
emits: ['update:modelValue'],
components: {},
setup(props, { emit }) {
const pluginState = injectPluginState();
const state = reactive({
show: props.modelValue,
loading: false,
startTime: new Date(),
totalTime: 1,
list: [],
showTitles: false,
currentTitles: [],
currentTime: 1,
});
watch(() => state.show, (val) => {
if (!val) {
setTimeout(() => {
emit('update:modelValue', val);
}, 300);
}
});
const getData = () => {
getActiveTimes(pluginState.value.activeWindow.items[0].MachineName).then((res) => {
state.startTime = res.StartTime;
state.totalTime = res.List.reduce((val, item, index) => {
item.titleLength = Object.keys(item.Titles).length;
val += item.Time;
return val;
}, 0);
state.list = res.List.sort((a, b) => b.Time - a.Time);
}).catch((e) => {
})
}
const showTitles = (item) => {
state.showTitles = true;
let res = [];
let time = 0;
for (let j in item.Titles) {
res.push({
t: j,
v: item.Titles[j]
});
time += item.Titles[j];
}
state.currentTitles = res.sort((a, b) => b.v - a.v);
state.currentTime = time;
}
onMounted(() => {
getData();
});
const handleCancel = () => {
state.show = false;
}
return {
state, handleCancel, showTitles
}
}
}
</script>
<style lang="stylus" scoped>
.wrap {
height: 60vh;
.inner {
border: 1px solid #ddd;
border-radius: 4px;
padding: 1rem 0.6rem 1rem 1rem;
li {
border: 1px solid #ddd;
padding: 0.6rem;
margin-bottom: 0.6rem;
border-radius: 0.4rem;
}
dt {
word-break: break-all;
}
dd {
.time {
height: 1rem;
background-color: green;
}
}
}
}
</style>

View File

@@ -0,0 +1,27 @@
<template>
<a href="javascript:;" @click="handleTimes">
<el-icon>
<DataLine />
</el-icon>
</a>
</template>
<script>
import { injectPluginState } from '../../provide'
export default {
props: ['data'],
setup(props) {
const pluginState = injectPluginState();
const handleTimes = () => {
pluginState.value.activeWindow.items = [props.data];
pluginState.value.activeWindow.showTimes = true;
}
return {
handleTimes
}
}
}
</script>
<style lang="stylus" scoped></style>

View File

@@ -0,0 +1,147 @@
<template>
<el-dialog class="options" title="阻止窗口" destroy-on-close v-model="state.show" center align-center width="94%">
<div class="rule-wrap flex">
<div class="items">
<CheckBoxWrap ref="devices" :data="globalData.devices" :items="state.items" label="MachineName" title="选择设备"></CheckBoxWrap>
</div>
<div class="flex-1"></div>
<div class="Exes flex flex-column">
<div class="private">
<CheckBoxWrap ref="privateExes" :data="state.privateExes" :items="[]" label="FileName" text="Desc" title="私有窗口">
<template #name="scoped">
{{scoped.item.Desc || scoped.item.FileName }}
</template>
</CheckBoxWrap>
</div>
<div class="flex-1"></div>
<div class="public">
<CheckBoxWrap ref="publicExes" :data="state.publicExes" :items="[]" label="FileName" text="Desc" title="公共窗口">
<template #name="scoped">
{{scoped.item.Desc || scoped.item.FileName }}
</template>
</CheckBoxWrap>
</div>
</div>
</div>
<template #footer>
<el-button @click="handleCancel"> </el-button>
<el-button type="primary" :loading="state.loading" @click="handleSubmit"> </el-button>
</template>
</el-dialog>
</template>
<script>
import { reactive, ref } from '@vue/reactivity';
import { computed, onMounted, watch } from '@vue/runtime-core';
import CheckBoxWrap from '../../boxs/CheckBoxWrap.vue'
import { ElMessage, ElMessageBox } from 'element-plus';
import { activeDisallow } from '@/apis/active'
import { injectGlobalData } from '@/views/provide';
import { injectPluginState } from '../../provide';
export default {
props: ['modelValue', 'items'],
emits: ['update:modelValue'],
components: { CheckBoxWrap },
setup(props, { emit }) {
const globalData = injectGlobalData();
const pluginState = injectPluginState();
const user = computed(() => globalData.value.usernames[globalData.value.username]);
const publicUserName = globalData.value.publicUserName;
const publicUser = computed(() => globalData.value.usernames[publicUserName]);
const usePublic = publicUser.value && globalData.value.username != publicUserName;
const state = reactive({
show: props.modelValue,
items: computed(() => pluginState.value.activeWindow.devices),
privateExes: computed(() => user.value ? user.value.FileNames : []),
publicExes: computed(() => usePublic ? publicUser.value.FileNames : []),
loading: false
});
watch(() => state.show, (val) => {
if (!val) {
setTimeout(() => {
emit('update:modelValue', val);
}, 300);
}
});
onMounted(() => {
globalData.value.updateFlag = Date.now();
});
const devices = ref(null);
const privateExes = ref(null);
const publicExes = ref(null);
const parseRule = () => {
const _privateExes = privateExes.value.getData();
const _publicExes = publicExes.value.getData();
return _privateExes.concat(_publicExes).reduce((data, item, index) => {
let arr = item.split(',');
for (let i = 0; i < arr.length; i++) {
if (data.indexOf(arr[i]) == -1) {
data.push(arr[i]);
}
}
return data;
}, []);
}
const handleSubmit = () => {
const _devices = devices.value.getData();
if (_devices.length == 0) {
ElMessage.error('未选择任何设备');
return;
}
ElMessageBox.confirm('如果未选择程序,则视为清空程序,是否确定应用?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
state.loading = true;
const exes = parseRule();
activeDisallow(_devices, exes).then((res) => {
state.loading = false;
ElMessage.success('操作成功!');
}).catch((e) => {
state.loading = false;
ElMessage.error('操作失败');
});
}).catch((e) => {
console.log(e);
});
}
const handleCancel = () => {
state.show = false;
}
return {
state, globalData, devices, privateExes, publicExes, handleSubmit, handleCancel
}
}
}
</script>
<style lang="stylus" scoped>
.rule-wrap {
height: 60vh;
.items {
height: 100%;
width: 48%;
position: relative;
}
.Exes {
height: 100%;
width: 48%;
position: relative;
.private, .public {
height: 49%;
position: relative;
}
}
}
</style>

View File

@@ -0,0 +1,148 @@
<template>
<el-dialog class="options" title="配置窗口列表" destroy-on-close v-model="state.show" center align-center width="94%">
<div class="filenames-items-wrap flex flex-nowrap flex-column">
<div class="head t-c flex">
<el-button @click="handleAdd()">添加项</el-button>
<span class="flex-1"></span>
<span style="line-height:3.2rem">不允许打开哪些窗口</span>
</div>
<div class="flex-1">
<div class="prevs-wrap">
<el-table :data="state.list" size="small" border stripe style="width: 100%" height="60vh">
<el-table-column prop="Desc" label="名称" />
<el-table-column prop="FileName" label="文件" />
<el-table-column label="操作" width="110">
<template #default="scope">
<el-button size="small" @click="handleAdd(scope.row)">
<el-icon>
<EditPen />
</el-icon>
</el-button>
<el-popconfirm title="删除不可逆,是否确定?" @confirm="handleDel(scope.row)">
<template #reference>
<el-button size="small" type="danger">
<el-icon>
<Delete />
</el-icon>
</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
</div>
</div>
<el-dialog :title="`${state.currentItem.ID==0?'添加项':'修改项'}`" destroy-on-close v-model="state.showEdit" center :close-on-click-modal="false" align-center width="80%">
<div>
<p><el-input v-model="state.currentItem.Desc" size="large" placeholder="名称" /></p>
<p style="padding-top:1rem"><el-input v-model="state.currentItem.FileName" size="large" placeholder="文件,多个逗号间隔,无后缀则按标题处理" /></p>
</div>
<template #footer>
<el-button @click="handleEditCancel"> </el-button>
<el-button type="primary" :loading="state.loading" @click="handleEditSubmit"> </el-button>
</template>
</el-dialog>
</div>
</el-dialog>
</template>
<script>
import { reactive } from '@vue/reactivity';
import { computed, watch } from '@vue/runtime-core';
import { ElMessage } from 'element-plus';
import { injectGlobalData } from '@/views/provide';
import { activeAddExe, activeDelExe } from '@/apis/active';
export default {
props: ['modelValue'],
emits: ['update:modelValue'],
setup(props, { emit }) {
const globalData = injectGlobalData();;
const state = reactive({
show: props.modelValue,
loading: false,
currentItem: { ID: 0, FileName: '', Desc: '' },
showEdit: false,
list: computed(() => {
let user = globalData.value.usernames[globalData.value.username];
if (user) {
return user.FileNames;
}
return [];
})
});
watch(() => state.show, (val) => {
if (!val) {
setTimeout(() => {
emit('update:modelValue', val);
}, 300);
}
});
const handleAdd = (item) => {
item = item || { ID: 0, FileName: '', Desc: '' };
state.currentItem.FileName = item.FileName;
state.currentItem.Desc = item.Desc;
state.currentItem.ID = item.ID;
state.showEdit = true;
}
const handleDel = (item) => {
state.loading = true;
activeDelExe(globalData.value.username, item.ID).then((error) => {
state.loading = false;
if (error) {
ElMessage.error(error);
} else {
ElMessage.success('操作成功!');
globalData.value.updateFlag = Date.now();
}
}).catch((e) => {
state.loading = false;
ElMessage.error('操作失败!');
})
}
const handleEditCancel = () => {
state.showEdit = false;
}
const handleEditSubmit = () => {
state.currentItem.Desc = state.currentItem.Desc.replace(/^\s|\s$/g, '');
state.currentItem.FileName = state.currentItem.FileName.replace(/^\s|\s$/g, '');
if (!state.currentItem.FileName || !state.currentItem.Desc) {
return;
}
state.loading = true;
activeAddExe({
UserName: globalData.value.username,
FileName: state.currentItem
}).then((error) => {
state.loading = false;
if (error) {
ElMessage.error(error);
} else {
ElMessage.success('操作成功!');
state.showEdit = false;
globalData.value.updateFlag = Date.now();
}
}).catch((e) => {
state.loading = false;
ElMessage.error('操作失败!');
})
}
return { state, handleAdd, handleDel, handleEditCancel, handleEditSubmit }
}
}
</script>
<style lang="stylus" scoped>
.filenames-items-wrap {
.head {
width: 100%;
padding-bottom: 1rem;
}
.prevs-wrap {
height: 100%;
position: relative;
}
}
</style>

View File

@@ -0,0 +1,36 @@
<template>
<a href="javascript:;" @click="handleFileNames">
<span>
<el-icon>
<Monitor />
</el-icon>窗口
</span>
</a>
</template>
<script>
import { injectGlobalData } from '@/views/provide';
import { injectPluginState } from '../../provide';
export default {
sort: 1,
setup() {
const pluginState = injectPluginState();
const globalData = injectGlobalData();
const handleFileNames = () => {
pluginState.value.activeWindow.showFileNames = true;
}
return {
handleFileNames
}
}
}
</script>
<style lang="stylus" scoped>
span {
display: inline-flex;
line-height: 1;
}
</style>

View File

@@ -0,0 +1,26 @@
<template>
<el-button size="small" plain dark @click="handleMessage">窗口<el-icon>
<Monitor />
</el-icon>
</el-button>
</template>
<script>
import { injectGlobalData } from '@/views/provide';
import { injectPluginState } from '../../provide';
export default {
setup() {
const pluginState = injectPluginState();
const globalData = injectGlobalData();
const handleMessage = () => {
pluginState.value.activeWindow.devices = globalData.value.devices;
pluginState.value.activeWindow.showChoose = true;
}
return { handleMessage }
}
}
</script>
<style lang="stylus" scoped></style>

View File

@@ -0,0 +1,25 @@
<template>
<div class="active">
<ActiveTimes v-if="pluginState.activeWindow.showTimes" v-model="pluginState.activeWindow.showTimes"></ActiveTimes>
<FileNames v-if="pluginState.activeWindow.showFileNames" v-model="pluginState.activeWindow.showFileNames"></FileNames>
<ChooseDig v-if="pluginState.activeWindow.showChoose" v-model="pluginState.activeWindow.showChoose"></ChooseDig>
</div>
</template>
<script>
import ActiveTimes from './ActiveTimes.vue'
import FileNames from './FileNames.vue'
import ChooseDig from './ChooseDig.vue'
import { injectPluginState } from '../../provide'
export default {
components: { ActiveTimes, FileNames, ChooseDig },
setup() {
const pluginState = injectPluginState();
return {
pluginState
}
}
}
</script>
<style lang="stylus" scoped></style>

View File

@@ -0,0 +1,135 @@
<template>
<a v-if="data.ActiveWindow.Pid>0" class="process flex" href="javascript:;" @click="handleCloseActive">
<span class="title flex-1">{{data.ActiveWindow.Title}}</span>
<p class="btn">
<a href="javascript:;" @click.stop="handleChoose">
<el-icon>
<Warning />
</el-icon>
<span class="num">{{data.ActiveWindow.Count}}</span>
</a>
<a href="javascript:;" @click.stop="handleAddExe">
<el-icon>
<CirclePlus />
</el-icon>
</a>
</p>
</a>
</template>
<script>
import { ElMessage, ElMessageBox } from 'element-plus';
import { exec } from '@/apis/command';
import { activeAddExe } from '@/apis/active';
import { injectGlobalData } from '@/views/provide';
import { injectPluginState } from '../../provide';
export default {
props: ['data'],
setup(props, { emit }) {
const globalData = injectGlobalData();
const pluginState = injectPluginState();
const handleCloseActive = () => {
ElMessageBox.confirm('是否确定关闭焦点窗口?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
exec([props.data.MachineName], [`taskkill /f /pid ${props.data.ActiveWindow.Pid}`]).then((res) => {
if (res) {
ElMessage.success('操作成功');
} else {
ElMessage.error('操作失败');
}
}).catch(() => {
ElMessage.error('操作失败');
});
}).catch(() => { });
}
const handleChoose = () => {
pluginState.value.activeWindow.devices = [props.data];
pluginState.value.activeWindow.showChoose = true;
}
const handleAddExe = () => {
const arr = props.data.ActiveWindow.FileName.split('\\');
const fileName = arr[arr.length - 1];
const desc = props.data.ActiveWindow.Desc;
const title = props.data.ActiveWindow.Title;
let html = `</p>是否确定添加到待选列表?</p>`;
html += `<p>标题:【${title}】</p>`;
html += `<p>描述:【${desc}】</p>`;
html += `<p>文件:【${fileName}】</p>`;
html += `<p>windows商店应用可能无法阻止需要手动添加例如【计算器】以侦测程序关闭</p>`;
ElMessageBox.confirm(html, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
dangerouslyUseHTMLString: true,
type: 'warning',
}).then(() => {
activeAddExe({
username: globalData.value.username,
FileName: {
ID: 0,
FileName: fileName,
Desc: desc
}
}).then((error) => {
globalData.value.updateFlag = Date.now();
if (!error) {
ElMessage.success('操作成功');
} else {
ElMessage.error(`操作失败:${error}`);
}
}).catch(() => {
ElMessage.error('操作失败');
});
}).catch(() => { });
}
return {
data: props.data,
handleCloseActive, handleChoose, handleAddExe
}
}
}
</script>
<style lang="stylus" scoped>
.process {
position: absolute;
left: 0rem;
right: 0rem;
bottom: 0.2rem;
background-color: rgba(0, 0, 0, 0.4);
padding: 0.6rem;
.title {
position: relative;
z-index: 2;
font-size: 1.4rem;
color: #fff;
word-break: break-all;
border-radius: 4px;
line-height: 1.4rem;
}
.btn {
a {
font-size: 1.6rem;
color: #fff;
margin-left: 0.6rem;
position: relative;
span.num {
font-size: 1.3rem;
position: absolute;
right: 110%;
top: -20%;
}
}
}
}
</style>

View File

@@ -0,0 +1,31 @@
export default {
field() {
return {
ActiveWindow: {
Title: '',
FileName: '',
Desc: '',
Pid: 0,
Count: 0
}
}
},
state: {
activeWindow: [
{
showTimes: false,
items: [],
showFileNames: false,
showChoose: true,
devices: []
}
]
},
update(item, report) {
item.ActiveWindow.Title = report.ActiveWindow.Title;
item.ActiveWindow.FileName = report.ActiveWindow.FileName;
item.ActiveWindow.Desc = report.ActiveWindow.Desc;
item.ActiveWindow.Pid = report.ActiveWindow.Pid;
item.ActiveWindow.Count = report.ActiveWindow.Count;
}
}

View File

@@ -0,0 +1,28 @@
<template>
<a href="javascript:;" @click="handleCommand">
<el-icon>
<Position />
</el-icon>
</a>
</template>
<script>
import { injectPluginState } from '../../provide'
export default {
props: ['data'],
setup(props) {
const pluginState = injectPluginState();
const handleCommand = () => {
pluginState.value.command.items = [props.data];
pluginState.value.command.showCommand = true;
}
return {
handleCommand
}
}
}
</script>
<style lang="stylus" scoped></style>

View File

@@ -0,0 +1,74 @@
<template>
<div>
<a href="javascript:;" @click="handleRebotSystem">
<el-icon>
<Refresh />
</el-icon>
</a>
<a href="javascript:;" @click="handleCloseSystem">
<el-icon>
<SwitchButton />
</el-icon>
</a>
</div>
</template>
<script>
import { ElMessage, ElMessageBox } from 'element-plus';
import { exec } from '@/apis/command';
export default {
props: ['data'],
setup(props) {
const handleRebotSystem = () => {
handleSendCommand('确定重启系统吗?', `shutdown -r -f -t 00`);
}
const handleCloseSystem = () => {
handleSendCommand('确定关闭系统吗?', `shutdown -s -f -t 00`);
}
const handleSendCommand = (desc, command, value) => {
ElMessageBox.confirm(desc, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
exec([props.data.MachineName], [command]).then((res) => {
if (res) {
ElMessage.success('操作成功');
} else {
ElMessage.error('操作失败');
}
}).catch(() => {
ElMessage.error('操作失败');
});
}).catch(() => { });
}
return { handleRebotSystem, handleCloseSystem }
}
}
</script>
<style lang="stylus" scoped>
a {
width: 2.4rem;
height: 2.4rem;
text-align: center;
line-height: 2.8rem;
margin-bottom: 0.6rem;
display: block;
font-size: 2rem;
border-radius: 50%;
border: 1px solid #3e5a6e;
box-shadow: 0 0 4px rgba(255, 255, 255, 0.1);
background-color: rgba(255, 255, 255, 0.5);
color: #3e5a6e;
transition: 0.3s;
&:hover {
box-shadow: 0 0 4px 2px rgba(255, 255, 255, 0.5);
}
}
</style>

View File

@@ -0,0 +1,138 @@
<template>
<el-dialog class="options" title="执行命令" destroy-on-close v-model="state.show" center align-center width="94%">
<div class="command-wrap flex">
<div class="items">
<CheckBoxWrap ref="items" :data="globalData.devices" :items="state.items" label="MachineName" title="选择设备"></CheckBoxWrap>
</div>
<div class="flex-1"></div>
<div class="commands">
<PrevBoxWrap ref="commands" :data="state.commands" title="命令多发">
<template #default="scope">
<div class="btn">
<el-button :loading="state.loading" @click="handleCommand(scope.item)">
{{scope.item.label}}
</el-button>
</div>
</template>
</PrevBoxWrap>
</div>
</div>
<template #footer>
<el-button @click="handleCancel"> </el-button>
<el-button type="primary" @click="handleCancel"> </el-button>
</template>
</el-dialog>
</template>
<script>
import { reactive, ref } from '@vue/reactivity';
import { computed, inject, watch } from '@vue/runtime-core';
import CheckBoxWrap from '../../boxs/CheckBoxWrap.vue'
import PrevBoxWrap from '../../boxs/PrevBoxWrap.vue'
import { ElMessage, ElMessageBox } from 'element-plus';
import { exec } from '../../../../apis/command'
import { llockUpdate } from '../../../../apis/llock'
import { wallpaperUpdate } from '../../../../apis/wallpaper'
import { usbUpdate } from '../../../../apis/usb'
import { setVolumeMute } from '../../../../apis/volume'
import { injectPluginState } from '../../provide';
import { injectGlobalData } from '@/views/provide';
export default {
props: ['modelValue'],
emits: ['update:modelValue'],
components: { CheckBoxWrap, PrevBoxWrap },
setup(props, { emit }) {
const globalData = injectGlobalData();
const pluginState = injectPluginState();
const wallpaperFunc = (names, value) => {
wallpaperUpdate(names, value, `http://${window.location.hostname}:${window.location.port}/bg.jpg`);
}
const state = reactive({
show: props.modelValue,
items: computed(() => pluginState.value.command.items),
commands: [
{ label: '强制关机', value: 'shutdown -s -f -t 00' },
{ label: '强制重启', value: 'shutdown -r -f -t 00' },
{ label: '打开锁屏', func: llockUpdate, value: true },
{ label: '关闭锁屏', func: llockUpdate, value: false },
{ label: '打开壁纸', func: wallpaperFunc, value: true },
{ label: '关闭壁纸', func: wallpaperFunc, value: false },
{ label: '禁用U盘', func: usbUpdate, value: true },
{ label: '启用U盘', func: usbUpdate, value: false },
{ label: '设置静音', func: setVolumeMute, value: true },
{ label: '取消静音', func: setVolumeMute, value: false },
],
loading: false
});
watch(() => state.show, (val) => {
if (!val) {
setTimeout(() => {
emit('update:modelValue', val);
}, 300);
}
});
const items = ref(null);
const handleCommand = (commandItem) => {
let _items = items.value.getData();
if (_items.length == 0) {
ElMessage.error('未选择任何设备');
return;
}
ElMessageBox.confirm('是否确定执行命令?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
state.loading = true;
const func = commandItem.func ? commandItem.func(_items, commandItem.value) : exec(_items, [commandItem.value]);
func.then((res) => {
if (res) {
ElMessage.success('操作成功');
} else {
ElMessage.error('操作失败');
}
state.loading = false;
}).catch(() => {
state.loading = false;
ElMessage.error('操作失败');
});
}).catch(() => { });
}
const handleCancel = () => {
state.show = false;
}
return {
state, globalData, items, handleCancel, handleCommand
}
}
}
</script>
<style lang="stylus" scoped>
.command-wrap {
height: 60vh;
.items {
height: 100%;
width: 48%;
position: relative;
}
.commands {
height: 100%;
width: 48%;
position: relative;
}
.btn {
text-align: center;
padding: 0.6rem 0;
width: 100%;
}
}
</style>

View File

@@ -0,0 +1,27 @@
<template>
<el-button size="small" plain dark @click="handleCommand">命令<el-icon>
<Position />
</el-icon></el-button>
</template>
<script>
import { injectGlobalData } from '@/views/provide';
import { injectPluginState } from '../../provide'
export default {
setup() {
const pluginState = injectPluginState();
const globalData = injectGlobalData();
const handleCommand = () => {
pluginState.value.command.items = globalData.value.devices;
pluginState.value.command.showCommand = true;
}
return {
handleCommand
}
}
}
</script>
<style lang="stylus" scoped></style>

View File

@@ -0,0 +1,19 @@
<template>
<div>
<ChooseDig v-if="pluginState.command.showCommand" v-model="pluginState.command.showCommand"></ChooseDig>
</div>
</template>
<script>
import { injectPluginState } from '../../provide'
import ChooseDig from './ChooseDig.vue'
export default {
components: { ChooseDig },
setup() {
const pluginState = injectPluginState();
return { pluginState }
}
}
</script>
<style lang="stylus" scoped></style>

View File

@@ -0,0 +1,15 @@
import { commandPing } from '../../../../apis/command'
import { injectGlobalData } from '@/views/provide';
export default {
field() {
return {}
},
state: {
command: {
showCommand: false,
items: []
}
},
init() {
}
}

View File

@@ -0,0 +1,23 @@
//Index.vue 主入口,用于放置一些内容,弹窗什么的
//Option.vue 下方快捷选项
//BtnLeft.vue 左按钮
//BtnRight.vue 右按钮
//TitleRight.vue //标题右边
//Screen.vue //屏幕
//FootOptionTop.vue
//FootOptionBottom.vue
//FootMenu.vue
//动态扩展功能配置示例
export default {
//字段
field: {},
//状态数据
state: {},
//报告更新
update(item, report) { },
//初始化
init() { }
}

View File

@@ -0,0 +1,112 @@
<template>
<el-dialog class="options" title="选择你的设备" destroy-on-close v-model="state.show" center :close-on-click-modal="false" align-center width="94%">
<div class="devices-wrap">
<CheckBoxWrap ref="devices" :data="state.list" :items="state.items" label="MachineName" text="MachineName" title="选择设备">
<template #oper="scope">
<div>
<el-popconfirm title="删除不可逆,是否确认?" @confirm="handleDel(scope.item.MachineName)">
<template #reference>
<span class="del-btn">
<el-icon>
<Delete />
</el-icon>
</span>
</template>
</el-popconfirm>
</div>
</template>
</CheckBoxWrap>
</div>
<template #footer>
<el-button @click="handleCancel"> </el-button>
<el-button type="primary" :loading="state.loading" @click="handleSubmit"> </el-button>
</template>
</el-dialog>
</template>
<script>
import { reactive, ref } from '@vue/reactivity';
import { computed, onMounted, watch } from '@vue/runtime-core';
import CheckBoxWrap from '../../boxs/CheckBoxWrap.vue'
import { updateDevices } from '../../../../apis/hijack'
import { delDevice } from '../../../../apis/signin'
import { ElMessage } from 'element-plus';
import { injectGlobalData } from '@/views/provide';
export default {
props: ['modelValue'],
emits: ['update:modelValue'],
components: { CheckBoxWrap },
setup(props, { emit }) {
const globalData = injectGlobalData();;
const state = reactive({
show: props.modelValue,
loading: false,
list: computed(() => globalData.value.allDevices),
items: computed(() => globalData.value.devices),
});
watch(() => state.show, (val) => {
if (!val) {
setTimeout(() => {
emit('update:modelValue', val);
}, 300);
}
});
const handleCancel = () => {
state.show = false;
globalData.value.updateFlag = Date.now();
}
const handleDel = (name) => {
state.loading = true;
delDevice(name).then(() => {
state.loading = false;
globalData.value.updateFlag = Date.now();
}).catch(() => {
state.loading = false;
});
}
const devices = ref(null);
const handleSubmit = () => {
const _devices = devices.value.getData();
state.loading = true;
updateDevices({
username: globalData.value.username,
devices: _devices
}).then((error) => {
state.loading = false;
globalData.value.updateFlag = Date.now();
if (error) {
ElMessage.error(error);
} else {
ElMessage.success('操作成功!');
}
}).catch(() => {
state.loading = false;
ElMessage.error('操作失败!');
});
}
onMounted(() => {
globalData.value.updateFlag = Date.now();
});
return {
state, devices, handleCancel, handleSubmit, handleDel
}
}
}
</script>
<style lang="stylus" scoped>
.devices-wrap {
height: 60vh;
position: relative;
.del-btn {
font-size: 2rem;
}
}
</style>

View File

@@ -0,0 +1,32 @@
<template>
<a href="javascript:;" @click="handleDevices">
<span><el-icon>
<Grid />
</el-icon>设备</span>
</a>
</template>
<script>
import { injectPluginState } from '../../provide'
export default {
sort: 0,
setup() {
const pluginState = injectPluginState();
const handleDevices = () => {
pluginState.value.device.showDevices = true;
}
return {
handleDevices
}
}
}
</script>
<style lang="stylus" scoped>
span {
display: inline-flex;
line-height: 1;
}
</style>

View File

@@ -0,0 +1,19 @@
<template>
<div>
<ChooseDig v-if="pluginState.device.showDevices" v-model="pluginState.device.showDevices"></ChooseDig>
</div>
</template>
<script>
import { injectPluginState } from '../../provide';
import ChooseDig from './ChooseDig.vue'
export default {
components: { ChooseDig },
setup() {
const pluginState = injectPluginState();
return { pluginState }
}
}
</script>
<style lang="stylus" scoped></style>

View File

@@ -0,0 +1,7 @@
export default {
state: {
device: {
showDevices: false
}
}
}

View File

@@ -0,0 +1,42 @@
<template>
<a href="javascript:;" @click="handleRule">
<el-icon>
<Umbrella />
</el-icon>
<span class="value">{{data.Hijack.Count}}</span>
</a>
</template>
<script>
import { injectPluginState } from '../../provide'
export default {
props: ['data'],
setup(props) {
const pluginState = injectPluginState();
const handleRule = () => {
pluginState.value.hijack.showRulesItems = [props.data];
pluginState.value.hijack.showRules = true;
}
return { data: props.data, handleRule }
}
}
</script>
<style lang="stylus" scoped>
a {
position: relative;
span.value {
position: absolute;
left: 100%;
top: 50%;
transform: translateY(-50%);
color: #fff;
font-size: 1.4rem;
text-shadow: 1px 1px 2px #000;
}
}
</style>

View File

@@ -0,0 +1,164 @@
<template>
<el-dialog class="options" title="网络限制" destroy-on-close v-model="state.show" center align-center width="94%">
<div class="rule-wrap flex">
<div class="items">
<CheckBoxWrap ref="devices" :data="globalData.devices" :items="state.items" label="MachineName" title="选择设备"></CheckBoxWrap>
</div>
<div class="flex-1"></div>
<div class="rules flex flex-column">
<div class="private">
<CheckBoxWrap ref="privateRules" :data="state.privateRules" :items="[]" label="ID" text="Name" title="私有限制"></CheckBoxWrap>
</div>
<div class="flex-1"></div>
<div class="public">
<CheckBoxWrap ref="publicRules" :data="state.publicRules" :items="[]" label="ID" text="Name" title="公共限制"></CheckBoxWrap>
</div>
</div>
</div>
<template #footer>
<el-button @click="handleCancel"> </el-button>
<el-button type="primary" :loading="state.loading" @click="handleSubmit"> </el-button>
</template>
</el-dialog>
</template>
<script>
import { reactive, ref } from '@vue/reactivity';
import { computed, inject, watch } from '@vue/runtime-core';
import CheckBoxWrap from '../../boxs/CheckBoxWrap.vue'
import { ElMessage, ElMessageBox } from 'element-plus';
import { setRules } from '../../../../apis/hijack'
import { injectGlobalData } from '@/views/provide';
import { injectPluginState } from '../../provide';
export default {
props: ['modelValue', 'items'],
emits: ['update:modelValue'],
components: { CheckBoxWrap },
setup(props, { emit }) {
const globalData = injectGlobalData();
const pluginState = injectPluginState();
const user = computed(() => globalData.value.usernames[globalData.value.username]);
const publicUserName = globalData.value.publicUserName;
const publicUser = computed(() => globalData.value.usernames[publicUserName]);
const usePublic = publicUser.value && globalData.value.username != publicUserName;
const state = reactive({
show: props.modelValue,
items: computed(() => pluginState.value.hijack.showRulesItems),
privateRules: computed(() => user.value ? user.value.Rules : []),
publicRules: computed(() => usePublic ? publicUser.value.Rules : []),
loading: false
});
watch(() => state.show, (val) => {
if (!val) {
setTimeout(() => {
emit('update:modelValue', val);
}, 300);
}
});
const devices = ref(null);
const privateRules = ref(null);
const publicRules = ref(null);
const parseRule = () => {
const _privateRules = privateRules.value.getData();
const _publicRules = publicRules.value.getData();
const _user = user.value;
const _publicUser = publicUser.value;
const publicList = _user.Rules.filter(c => _privateRules.indexOf(c.ID) >= 0).map(rule => {
return _user.Processs.filter(c => rule.PrivateProcesss.indexOf(c.ID) >= 0);
});
const privateList = _publicUser.Rules.filter(c => _publicRules.indexOf(c.ID) >= 0).map(rule => {
return _publicUser.Processs.filter(c => rule.PublicProcesss.indexOf(c.ID) >= 0);
});
const origin = publicList.concat(privateList).reduce((arr, value, index) => {
arr = arr.concat(value.reduce((arr, value, index) => {
arr = arr.concat(value.List);
return arr;
}, []));
return arr;
}, []);
const res = [];
origin.forEach(element => {
if (res.filter(c => c.Name == element.Name && c.DataType == element.DataType && c.AllowType == element.AllowType).length == 0) {
res.push(element);
}
});
return {
AllowProcesss: res.filter(c => c.DataType == 0 && c.AllowType == 0).map(c => c.Name),
DeniedProcesss: res.filter(c => c.DataType == 0 && c.AllowType == 1).map(c => c.Name),
AllowDomains: res.filter(c => c.DataType == 1 && c.AllowType == 0).map(c => c.Name),
DeniedDomains: res.filter(c => c.DataType == 1 && c.AllowType == 1).map(c => c.Name),
AllowIPs: res.filter(c => c.DataType == 2 && c.AllowType == 0).map(c => c.Name),
DeniedIPs: res.filter(c => c.DataType == 2 && c.AllowType == 1).map(c => c.Name),
}
}
const handleSubmit = () => {
const _devices = devices.value.getData();
if (_devices.length == 0) {
ElMessage.error('未选择任何设备');
return;
}
ElMessageBox.confirm('如果未选择任何限制,则视为清空限制,是否确定应用限制?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
state.loading = true;
const rules = parseRule();
setRules({
Devices: _devices,
Rules: rules
}).then((errorDevices) => {
state.loading = false;
if (errorDevices && errorDevices.length > 0) {
ElMessage.error(`操作失败,失败设备:${errorDevices.join(',')}`);
} else {
ElMessage.success('操作成功!');
}
}).catch((e) => {
state.loading = false;
ElMessage.error('操作失败');
});
}).catch(() => { });
}
const handleCancel = () => {
state.show = false;
}
return {
state, globalData, devices, privateRules, publicRules, handleSubmit, handleCancel
}
}
}
</script>
<style lang="stylus" scoped>
.rule-wrap {
height: 60vh;
.items {
height: 100%;
width: 48%;
position: relative;
}
.rules {
height: 100%;
width: 48%;
position: relative;
.private, .public {
height: 49%;
position: relative;
}
}
}
</style>

View File

@@ -0,0 +1,56 @@
<template>
<a href="javascript:;">
<el-dropdown>
<span class="el-dropdown-link">
<el-icon>
<Umbrella />
</el-icon>网络
<el-icon class="el-icon--right">
<arrow-down />
</el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="handleProcess">程序配置</el-dropdown-item>
<el-dropdown-item divided @click="handleRule">分组配置</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</a>
</template>
<script>
import { injectPluginState } from '@/views/device/provide'
export default {
sort: 2,
setup() {
const pluginState = injectPluginState();
const handleProcess = () => {
pluginState.value.hijack.showProcessSetting = true;
}
const handleRule = () => {
pluginState.value.hijack.showRuleSetting = true;
}
return {
handleProcess, handleRule
}
}
}
</script>
<style lang="stylus" scoped>
.el-icon {
font-size: 1.6rem;
}
.el-dropdown {
color: #ebffef;
font-size: 1.6rem;
.el-dropdown-link {
display: inline-flex;
line-height: 1;
}
}
</style>

View File

@@ -0,0 +1,24 @@
<template>
<div>
<ChooseDig v-if="pluginState.hijack.showRules" v-model="pluginState.hijack.showRules"></ChooseDig>
<RuleSetting v-if="pluginState.hijack.showRuleSetting" v-model="pluginState.hijack.showRuleSetting"></RuleSetting>
<ProcessSetting v-if="pluginState.hijack.showProcessSetting" v-model="pluginState.hijack.showProcessSetting"></ProcessSetting>
</div>
</template>
<script>
import { injectPluginState } from '../../provide'
import ChooseDig from './ChooseDig.vue'
import RuleSetting from './rules/Index.vue'
import ProcessSetting from './process/Index.vue'
export default {
components: { ChooseDig, RuleSetting, ProcessSetting },
setup() {
const pluginState = injectPluginState();
return { pluginState }
}
}
</script>
<style lang="stylus" scoped></style>

View File

@@ -0,0 +1,45 @@
<template>
<span class="speed">
<span>
<el-icon>
<Upload />
</el-icon>
{{data.Hijack.UploadText}}
</span>
<span>
<el-icon>
<Download />
</el-icon>
{{data.Hijack.DownloadText}}
</span>
</span>
</template>
<script>
export default {
props: ['data'],
setup(props) {
return {
data: props.data
}
}
}
</script>
<style lang="stylus" scoped>
.speed {
font-size: 1.3rem;
padding-top: 0.1rem;
color: #333;
span:nth-child(1) {
margin-right: 1rem;
}
.el-icon {
vertical-align: middle;
margin-top: -2px;
}
}
</style>

View File

@@ -0,0 +1,68 @@
import { injectGlobalData } from "@/views/provide";
export default {
field() {
return {
Hijack: {
Upload: 0,
UploadText: '',
Download: 0,
DownloadText: '',
Count: 0
}
}
},
state: {
hijack: {
showRules: false,
showRulesItems: [],
showRuleSetting: false,
showProcessSetting: false,
}
},
timer: 0,
speedCaches: {},
sizeFormat(size) {
let unites = ['B', 'KB', 'MB', 'GB', 'TB'];
let unit = unites[0];
while ((unit = unites.shift()) && size.toFixed(2) >= 1024) {
size /= 1024;
}
return unit == 'B' ? [size, unit] : [size.toFixed(2), unit];
},
globalData: null,
init() {
this.globalData = injectGlobalData();;
const speedCaches = this.speedCaches;
const sizeFormat = this.sizeFormat;
this.timer = setInterval(() => {
this.globalData.value.devices.forEach(item => {
let cache = speedCaches[item.MachineName] || { up: 0, down: 0 };
if (isNaN(cache.up)) cache.up = 0;
if (isNaN(cache.down)) cache.down = 0;
item.Hijack.Upload = item.Hijack.Upload || 0;
item.Hijack.Download = item.Hijack.Download || 0;
let bytes = item.Hijack.Upload - cache.up;
cache.up = item.Hijack.Upload;
let format = sizeFormat(bytes);
item.Hijack.UploadText = `${format[0]}${format[1]}/s`;
bytes = item.Hijack.Download - cache.down;
cache.down = item.Hijack.Download;
format = sizeFormat(bytes);
item.Hijack.DownloadText = `${format[0]}${format[1]}/s`;
speedCaches[item.MachineName] = cache;
});
}, 1000);
},
update(item, report) {
item.Hijack.Upload = report.Hijack.Upload;
item.Hijack.Download = report.Hijack.Download;
item.Hijack.Count = report.Hijack.Count;
}
}

View File

@@ -0,0 +1,138 @@
<template>
<div class="process-items-wrap flex flex-nowrap flex-column">
<div class="head t-c flex">
<el-button @click="handleAdd()">添加项</el-button>
</div>
<div class="flex-1">
<div class="prevs-wrap">
<el-table :data="state.list" size="small" border stripe style="width: 100%" height="50vh">
<el-table-column prop="Name" label="名称" />
<el-table-column label="操作" width="110">
<template #default="scope">
<template v-if="scope.row.ID > 1">
<el-button size="small" @click="handleAdd(scope.row)">
<el-icon>
<EditPen />
</el-icon>
</el-button>
<el-popconfirm title="删除不可逆,是否确定?" @confirm="handleDel(scope.row)">
<template #reference>
<el-button size="small" type="danger">
<el-icon>
<Delete />
</el-icon>
</el-button>
</template>
</el-popconfirm>
</template>
</template>
</el-table-column>
</el-table>
</div>
</div>
<el-dialog :title="`${state.currentItem.ID==0?'添加项':'修改项'}`" destroy-on-close v-model="state.showEdit" center :close-on-click-modal="false" align-center width="80%">
<div>
<el-input v-model="state.currentItem.Name" size="large" placeholder="分组名称" />
</div>
<template #footer>
<el-button @click="handleEditCancel"> </el-button>
<el-button type="primary" :loading="state.loading" @click="handleEditSubmit"> </el-button>
</template>
</el-dialog>
</div>
</template>
<script>
import { reactive } from '@vue/reactivity';
import { computed } from '@vue/runtime-core';
import { ElMessage } from 'element-plus';
import { addProcessGroup, deleteProcessGroup } from '../../../../../apis/hijack'
import { injectGlobalData } from '@/views/provide';
export default {
setup() {
const globalData = injectGlobalData();;
const state = reactive({
loading: false,
currentItem: { ID: 0, Name: '' },
showEdit: false,
list: computed(() => {
let user = globalData.value.usernames[globalData.value.username];
if (user) {
if (state.group == 0 && user.Processs.length > 0) {
state.group = user.Processs[0].ID;
}
return user.Processs;
}
return [];
})
});
const handleAdd = (item) => {
item = item || { Name: '', ID: 0 };
state.currentItem.Name = item.Name;
state.currentItem.ID = item.ID;
state.showEdit = true;
}
const handleDel = (item) => {
state.loading = true;
deleteProcessGroup({
UserName: globalData.value.username,
ID: item.ID
}).then((error) => {
state.loading = false;
if (error) {
ElMessage.error(error);
} else {
ElMessage.success('操作成功!');
globalData.value.updateFlag = Date.now();
}
}).catch((e) => {
state.loading = false;
ElMessage.error('操作失败!');
})
}
const handleEditCancel = () => {
state.showEdit = false;
}
const handleEditSubmit = () => {
state.currentItem.Name = state.currentItem.Name.replace(/^\s|\s$/g, '');
if (!state.currentItem.Name) {
return;
}
state.loading = true;
addProcessGroup({
UserName: globalData.value.username,
Group: state.currentItem
}).then((error) => {
state.loading = false;
if (error) {
ElMessage.error(error);
} else {
ElMessage.success('操作成功!');
state.showEdit = false;
globalData.value.updateFlag = Date.now();
}
}).catch((e) => {
state.loading = false;
ElMessage.error('操作失败!');
})
}
return { state, handleAdd, handleDel, handleEditCancel, handleEditSubmit }
}
}
</script>
<style lang="stylus" scoped>
.process-items-wrap {
.head {
width: 100%;
padding-bottom: 1rem;
}
.prevs-wrap {
height: 100%;
position: relative;
}
}
</style>

View File

@@ -0,0 +1,57 @@
<template>
<el-dialog class="options" title="限制程序配置" destroy-on-close v-model="state.show" center :close-on-click-modal="false" align-center width="94%">
<el-tabs type="border-card">
<el-tab-pane label="程序分组">
<Groups></Groups>
</el-tab-pane>
<el-tab-pane label="程序列表">
<Items></Items>
</el-tab-pane>
</el-tabs>
<template #footer>
<el-button @click="handleCancel"> </el-button>
</template>
</el-dialog>
</template>
<script>
import { reactive } from '@vue/reactivity';
import { onMounted, watch } from '@vue/runtime-core';
import Groups from './Groups.vue'
import Items from './Items.vue'
import { injectGlobalData } from '@/views/provide';
export default {
props: ['modelValue'],
emits: ['update:modelValue'],
components: { Groups, Items },
setup(props, { emit }) {
const state = reactive({
show: props.modelValue,
loading: false,
});
watch(() => state.show, (val) => {
if (!val) {
setTimeout(() => {
emit('update:modelValue', val);
}, 300);
}
});
const globalData = injectGlobalData();;
onMounted(() => {
globalData.value.updateFlag = Date.now();
});
const handleCancel = () => {
state.show = false;
}
return {
state, handleCancel
}
}
}
</script>
<style lang="stylus" scoped></style>

View File

@@ -0,0 +1,196 @@
<template>
<div class="process-items-wrap flex flex-nowrap flex-column">
<div class="head t-c flex">
<el-select v-model="state.group" placeholder="选择一个分组" style="width:13rem">
<el-option v-for="item in state.groups" :key="item.ID" :label="item.Name" :value="item.ID" />
</el-select>
<span class="flex-1"></span>
<el-button @click="handleAdd()">添加项</el-button>
</div>
<div class="flex-1">
<div class="prevs-wrap">
<el-table :data="state.list" size="small" border stripe style="width: 100%" height="50vh">
<el-table-column prop="Name" label="名称">
<template #default="scope">
<strong :class="`allow-type-${scope.row.AllowType}`">{{scope.row.Name}}</strong>
</template>
</el-table-column>
<el-table-column label="操作" width="110">
<template #default="scope">
<el-button size="small" @click="handleAdd(scope.row)">
<el-icon>
<EditPen />
</el-icon>
</el-button>
<el-popconfirm title="删除不可逆,是否确定?" @confirm="handleDel(scope.row)">
<template #reference>
<el-button size="small" type="danger">
<el-icon>
<Delete />
</el-icon>
</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
</div>
</div>
<el-dialog :title="`${state.currentItem.ID==0?'添加项':'修改项'}`" destroy-on-close v-model="state.showEdit" center :close-on-click-modal="false" align-center width="80%">
<div>
<div class="alert">
<p>1黑名单优先</p>
<p>2支持进程名域名ip(支持掩码)</p>
<p>3进程域名后序截取判断</p>
</div>
<div style="padding-bottom:1rem">
<el-input v-model="state.currentItem.Name" size="large" placeholder="进程 | 域名 | ip(支持掩码/32)" />
</div>
<div class="t-c" style="padding-bottom:1rem">
<el-radio-group v-model="state.currentItem.DataType">
<el-radio :label="0">进程</el-radio>
<el-radio :label="1">域名</el-radio>
<el-radio :label="2">IP</el-radio>
</el-radio-group>
</div>
<div class="t-c">
<el-switch v-model="state.currentItem.AllowType" size="large" active-text="允许" inactive-text="阻止" :active-value="0" :inactive-value="1" />
</div>
</div>
<template #footer>
<el-button @click="handleEditCancel"> </el-button>
<el-button type="primary" :loading="state.loading" @click="handleEditSubmit"> </el-button>
</template>
</el-dialog>
</div>
</template>
<script>
import { reactive } from '@vue/reactivity';
import { computed, watch } from '@vue/runtime-core';
import { ElMessage } from 'element-plus';
import { addProcess, deleteProcess } from '../../../../../apis/hijack'
import { injectGlobalData } from '@/views/provide';
export default {
setup() {
const globalData = injectGlobalData();;
const state = reactive({
loading: false,
group: 0,
currentItem: { ID: 0, Name: '', AllowType: 1, DataType: 0 },
showEdit: false,
groups: computed(() => {
let user = globalData.value.usernames[globalData.value.username];
if (user) {
if (state.group == 0 && user.Processs.length > 0) {
state.group = user.Processs[0].ID;
}
return user.Processs;
}
return [];
}),
list: computed(() => {
let group = state.groups.filter(c => c.ID == state.group)[0];
if (group) return group.List;
return [];
})
});
watch(() => state.currentItem.Name, () => {
handleNameChange(state.currentItem.Name);
})
const handleNameChange = (value) => {
const isExe = /^.{0,}(\.exe)$/.test(value);
const isip = /^((?:(?:25[0-5]|2[0-4]\d|[01]?\d?\d)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d?\d))(\/\d{1,})?$/.test(value);
state.currentItem.DataType = isExe ? 0 : (isip ? 2 : 1);
if (isip && value.indexOf('/') < 0) {
state.currentItem.Name = state.currentItem.Name + '/32';
}
}
const handleAdd = (item) => {
item = item || { Name: '', ID: 0, AllowType: 1, DataType: 0 };
state.currentItem.Name = item.Name;
state.currentItem.ID = item.ID;
state.currentItem.AllowType = item.AllowType;
state.showEdit = true;
}
const handleDel = (item) => {
state.loading = true;
deleteProcess({
UserName: globalData.value.username,
GroupID: state.group,
ID: item.ID
}).then((error) => {
state.loading = false;
if (error) {
ElMessage.error(error);
} else {
ElMessage.success('操作成功!');
globalData.value.updateFlag = Date.now();
}
}).catch((e) => {
state.loading = false;
ElMessage.error('操作失败!');
})
}
const handleEditCancel = () => {
state.showEdit = false;
}
const handleEditSubmit = () => {
state.currentItem.Name = state.currentItem.Name.replace(/^\s|\s$/g, '');
if (!state.currentItem.Name) {
return;
}
state.loading = true;
addProcess({
UserName: globalData.value.username,
GroupID: state.group,
Item: state.currentItem
}).then((error) => {
state.loading = false;
if (error) {
ElMessage.error(error);
} else {
ElMessage.success('操作成功!');
state.showEdit = false;
globalData.value.updateFlag = Date.now();
}
}).catch((e) => {
state.loading = false;
ElMessage.error('操作失败!');
})
}
return { state, handleNameChange, handleAdd, handleDel, handleEditCancel, handleEditSubmit }
}
}
</script>
<style lang="stylus" scoped>
.process-items-wrap {
.head {
width: 100%;
padding-bottom: 1rem;
}
.prevs-wrap {
height: 100%;
position: relative;
}
.allow-type-0 {
color: green;
}
.allow-type-1 {
color: red;
}
.alert {
background-color: rgba(255, 136, 0, 0.2);
border: 1px solid #ddd;
margin-bottom: 1rem;
padding: 0.6rem;
border-radius: 0.4rem;
}
}
</style>

View File

@@ -0,0 +1,25 @@
<template>
<el-button size="small" plain dark @click="handleRule">网络<el-icon>
<Umbrella />
</el-icon></el-button>
</template>
<script>
import { injectPluginState } from '@/views/device/provide';
import { injectGlobalData } from '@/views/provide';
export default {
setup() {
const pluginState = injectPluginState();
const globalData = injectGlobalData();
const handleRule = () => {
pluginState.value.hijack.showRulesItems = globalData.value.devices;
pluginState.value.hijack.showRules = true;
}
return {
handleRule
}
}
}
</script>
<style lang="stylus" scoped></style>

View File

@@ -0,0 +1,138 @@
<template>
<div class="rule-items-wrap flex flex-nowrap flex-column">
<div class="head t-c flex">
<el-button @click="handleAdd()">添加项</el-button>
</div>
<div class="flex-1">
<div class="prevs-wrap">
<el-table :data="state.list" size="small" border stripe style="width: 100%" height="50vh">
<el-table-column prop="Name" label="名称" />
<el-table-column label="操作" width="110">
<template #default="scope">
<template v-if="scope.row.ID > 1">
<el-button size="small" @click="handleAdd(scope.row)">
<el-icon>
<EditPen />
</el-icon>
</el-button>
<el-popconfirm title="删除不可逆,是否确定?" @confirm="handleDel(scope.row)">
<template #reference>
<el-button size="small" type="danger">
<el-icon>
<Delete />
</el-icon>
</el-button>
</template>
</el-popconfirm>
</template>
</template>
</el-table-column>
</el-table>
</div>
</div>
<el-dialog :title="`${state.currentItem.ID==0?'添加项':'修改项'}`" destroy-on-close v-model="state.showEdit" center :close-on-click-modal="false" align-center width="80%">
<div>
<el-input v-model="state.currentItem.Name" size="large" placeholder="分组名称" />
</div>
<template #footer>
<el-button @click="handleEditCancel"> </el-button>
<el-button type="primary" :loading="state.loading" @click="handleEditSubmit"> </el-button>
</template>
</el-dialog>
</div>
</template>
<script>
import { reactive } from '@vue/reactivity';
import { computed } from '@vue/runtime-core';
import { ElMessage } from 'element-plus';
import { addRule, deleteRule } from '../../../../../apis/hijack'
import { injectGlobalData } from '@/views/provide';
export default {
setup() {
const globalData = injectGlobalData();;
const state = reactive({
loading: false,
currentItem: { ID: 0, Name: '' },
showEdit: false,
list: computed(() => {
let user = globalData.value.usernames[globalData.value.username];
if (user) {
if (state.group == 0 && user.Rules.length > 0) {
state.group = user.Rules[0].ID;
}
return user.Rules;
}
return [];
})
});
const handleAdd = (item) => {
item = item || { Name: '', ID: 0 };
state.currentItem.Name = item.Name;
state.currentItem.ID = item.ID;
state.showEdit = true;
}
const handleDel = (item) => {
state.loading = true;
deleteRule({
UserName: globalData.value.username,
ID: item.ID
}).then((error) => {
state.loading = false;
if (error) {
ElMessage.error(error);
} else {
ElMessage.success('操作成功!');
globalData.value.updateFlag = Date.now();
}
}).catch((e) => {
state.loading = false;
ElMessage.error('操作失败!');
})
}
const handleEditCancel = () => {
state.showEdit = false;
}
const handleEditSubmit = () => {
state.currentItem.Name = state.currentItem.Name.replace(/^\s|\s$/g, '');
if (!state.currentItem.Name) {
return;
}
state.loading = true;
addRule({
UserName: globalData.value.username,
Rule: state.currentItem
}).then((error) => {
state.loading = false;
if (error) {
ElMessage.error(error);
} else {
ElMessage.success('操作成功!');
state.showEdit = false;
globalData.value.updateFlag = Date.now();
}
}).catch((e) => {
state.loading = false;
ElMessage.error('操作失败!');
})
}
return { state, handleAdd, handleDel, handleEditCancel, handleEditSubmit }
}
}
</script>
<style lang="stylus" scoped>
.rule-items-wrap {
.head {
width: 100%;
padding-bottom: 1rem;
}
.prevs-wrap {
height: 100%;
position: relative;
}
}
</style>

View File

@@ -0,0 +1,49 @@
<template>
<el-dialog class="options" title="限制组配置" destroy-on-close v-model="state.show" center :close-on-click-modal="false" align-center width="94%">
<el-tabs type="border-card">
<el-tab-pane label="限制组">
<Groups></Groups>
</el-tab-pane>
<el-tab-pane label="限制内容">
<Rule></Rule>
</el-tab-pane>
</el-tabs>
<template #footer>
<el-button @click="handleCancel"> </el-button>
</template>
</el-dialog>
</template>
<script>
import { reactive } from '@vue/reactivity';
import { watch } from '@vue/runtime-core';
import Groups from './Groups.vue'
import Rule from './Rule.vue'
export default {
props: ['modelValue'],
emits: ['update:modelValue'],
components: { Groups, Rule },
setup(props, { emit }) {
const state = reactive({
show: props.modelValue
});
watch(() => state.show, (val) => {
if (!val) {
setTimeout(() => {
emit('update:modelValue', val);
}, 300);
}
});
const handleCancel = () => {
state.show = false;
}
return {
state, handleCancel
}
}
}
</script>
<style lang="stylus" scoped></style>

View File

@@ -0,0 +1,139 @@
<template>
<div class="command-wrap flex flex-column">
<div class="head t-c flex">
<div>
<el-select v-model="state.group" placeholder="选择一个分组" style="width:13rem">
<el-option v-for="item in state.groups" :key="item.ID" :label="item.Name" :value="item.ID" />
</el-select>
</div>
<div class="flex-1"></div>
<div>
<el-button @click="handleSave()" :loading="state.loading">保存选择</el-button>
</div>
</div>
<div class="body flex flex-1">
<div class="private">
<CheckBoxWrap ref="privateProcess" :data="state.privateProcess" :items="state.privateProcessItems" label="ID" text="Name" title="私有程序组"></CheckBoxWrap>
</div>
<div class="flex-1"></div>
<div class="public">
<CheckBoxWrap ref="publicProcess" :data="state.publicProcess" :items="state.publicProcessItems" label="ID" text="Name" title="公共程序组"></CheckBoxWrap>
</div>
</div>
</div>
</template>
<script>
import { computed, reactive, ref, watch } from 'vue';
import CheckBoxWrap from '../../../boxs/CheckBoxWrap.vue'
import { ElMessage } from 'element-plus';
import { addRule } from '../../../../../apis/hijack'
import { injectGlobalData } from '@/views/provide';
export default {
components: { CheckBoxWrap },
setup() {
const globalData = injectGlobalData();;
const user = computed(() => globalData.value.usernames[globalData.value.username]);
const publicUserName = globalData.value.publicUserName;
const publicUser = computed(() => globalData.value.usernames[publicUserName]);
const usePublic = publicUser.value && globalData.value.username != publicUserName;
const state = reactive({
loading: false,
group: 0,
groups: computed(() => {
if (user.value) {
if (state.group == 0 && user.value.Rules.length > 0) {
state.group = user.value.Rules[0].ID;
}
return user.value.Rules;
}
return [];
}),
rule: computed(() => {
if (user) {
let rule = user.value.Rules.filter(c => c.ID == state.group)[0];
if (rule) {
return rule;
}
}
return {
ID: 0,
Name: '',
PrivateProcesss: [],
PublicProcesss: [],
}
}),
privateProcess: computed(() => user.value ? user.value.Processs : []),
privateProcessItems: computed(() => user.value ? user.value.Processs.filter(c => state.rule.PrivateProcesss.indexOf(c.ID) >= 0) : []),
publicProcess: computed(() => usePublic ? publicUser.value.Processs : []),
publicProcessItems: computed(() => usePublic ? publicUser.value.Processs.filter(c => state.rule.PublicProcesss.indexOf(c.ID) >= 0) : []),
});
watch(() => state.show, (val) => {
if (!val) {
setTimeout(() => {
emit('update:modelValue', val);
}, 300);
}
});
const privateProcess = ref(null);
const publicProcess = ref(null);
const handleSave = () => {
let rule = user.value.Rules.filter(c => c.ID == state.group)[0];
if (!rule) {
ElMessage.error('未选择任何限制分组');
return;
}
rule.PrivateProcesss = privateProcess.value.getData();
rule.PublicProcesss = publicProcess.value.getData();
state.loading = true;
addRule({
UserName: globalData.value.username,
Rule: rule
}).then((error) => {
state.loading = false;
if (error) {
ElMessage.error(error);
} else {
globalData.value.updateFlag = Date.now();
ElMessage.success('操作成功');
}
}).catch(() => {
state.loading = false;
ElMessage.error('操作失败');
})
}
return {
state, handleSave, privateProcess, publicProcess
}
}
}
</script>
<style lang="stylus" scoped>
.command-wrap {
height: 55vh;
.head {
width: 100%;
padding-bottom: 1rem;
}
.private, .public {
width: 49%;
position: relative;
}
.process {
height: 100%;
width: 48%;
position: relative;
}
}
</style>

View File

@@ -0,0 +1,78 @@
<template>
<a href="javascript:;">
<span class="light" @click="handleLight">
<el-icon>
<Sunny />
</el-icon>
<div class="light-bg" :style="{height:`${data.Light.Value}%`}">
<el-icon class="value">
<Sunny />
</el-icon>
</div>
</span>
<p class="light-value">{{data.Light.Value}}</p>
</a>
</template>
<script>
import { injectPluginState } from '../../provide'
export default {
props: ['data'],
setup(props) {
const pluginState = injectPluginState();
const handleLight = () => {
pluginState.value.light.items = [props.data];
pluginState.value.light.showLightSingle = true;
}
return {
data: props.data,
handleLight
}
}
}
</script>
<style lang="stylus" scoped>
a {
position: relative;
}
.light-value {
color: #fff;
font-size: 1.4rem;
text-shadow: 1px 1px 2px #000;
position: absolute;
right: 100%;
top: 50%;
transform: translateY(-50%);
}
span.light {
position: relative;
height: 100%;
display: block;
.el-icon {
font-size: 2rem;
}
.light-bg {
position: absolute;
bottom: 2px;
left: 0;
height: 0%;
width: 100%;
overflow: hidden;
.el-icon {
color: green;
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: 0;
}
}
}
</style>

View File

@@ -0,0 +1,27 @@
<template>
<el-button size="small" plain dark @click="handleLight">亮度 <el-icon>
<Sunny />
</el-icon></el-button>
</template>
<script>
import { injectGlobalData } from '@/views/provide';
import { injectPluginState } from '../../provide';
export default {
setup() {
const pluginState = injectPluginState();
const globalData = injectGlobalData();
const handleLight = () => {
pluginState.value.light.items = globalData.value.devices;
pluginState.value.light.showLight = true;
}
return {
handleLight
}
}
}
</script>
<style lang="stylus" scoped></style>

View File

@@ -0,0 +1,21 @@
<template>
<div>
<Light v-if="pluginState.light.showLight" v-model="pluginState.light.showLight"></Light>
<LightSingle v-if="pluginState.light.showLightSingle" v-model="pluginState.light.showLightSingle"></LightSingle>
</div>
</template>
<script>
import { injectPluginState } from '../../provide';
import Light from './Light.vue'
import LightSingle from './LightSingle.vue'
export default {
components: { Light, LightSingle },
setup() {
const pluginState = injectPluginState();
return { pluginState }
}
}
</script>
<style lang="stylus" scoped></style>

View File

@@ -0,0 +1,137 @@
<template>
<el-dialog class="options" title="调节亮度" destroy-on-close v-model="state.show" center align-center width="94%">
<div class="command-wrap flex">
<div class="items">
<CheckBoxWrap ref="items" :data="globalData.devices" :items="state.items" label="MachineName" title="全选">
<template #name="scope">
<span>
<span class="name">
{{scope.item.MachineName}}
</span>
<strong class="light">
<el-icon>
<Sunny />
</el-icon>
<strong class="value">{{scope.item.Light.Value?Math.floor(scope.item.Light.Value):scope.item.Light.Value}}%</strong>
</strong>
</span>
</template>
</CheckBoxWrap>
</div>
<div class="flex-1"></div>
<div class="commands">
<PrevBoxWrap ref="commands" title="调节亮度">
<template #wrap>
<div class="slider-wrap flex flex-column">
<div class="silder flex flex-1">
<div class="flex-1">
<el-slider @change="handleChangeLight" v-model="state.light" vertical height="100%" />
</div>
</div>
</div>
</template>
</PrevBoxWrap>
</div>
</div>
<template #footer>
<el-button @click="handleCancel"> </el-button>
<el-button type="primary" @click="handleCancel"> </el-button>
</template>
</el-dialog>
</template>
<script>
import { reactive, ref } from '@vue/reactivity';
import { computed, watch } from '@vue/runtime-core';
import CheckBoxWrap from '../../boxs/CheckBoxWrap.vue'
import PrevBoxWrap from '../../boxs/PrevBoxWrap.vue'
import { ElMessage } from 'element-plus';
import { setLight } from '../../../../apis/light'
import { injectGlobalData } from '@/views/provide';
import { injectPluginState } from '../../provide';
export default {
props: ['modelValue'],
emits: ['update:modelValue'],
components: { CheckBoxWrap, PrevBoxWrap },
setup(props, { emit }) {
const globalData = injectGlobalData();
const pluginState = injectPluginState();
const state = reactive({
show: props.modelValue,
items: computed(() => pluginState.value.light.items),
mute: false,
loading: false,
light: 0
});
watch(() => state.show, (val) => {
if (!val) {
setTimeout(() => {
emit('update:modelValue', val);
}, 300);
}
});
const handleCancel = () => {
state.show = false;
}
const items = ref(null);
const handleChangeLight = () => {
let _items = items.value.getData();
if (_items.length == 0) {
ElMessage.error('未选择任何设备');
return;
}
setLight(_items, state.light);
}
return {
state, globalData, items, handleCancel, handleChangeLight
}
}
}
</script>
<style lang="stylus" scoped>
.command-wrap {
height: 60vh;
.items {
height: 100%;
width: 60%;
position: relative;
}
.commands {
height: 100%;
width: 38%;
position: relative;
}
.light {
font-size: 2rem;
padding-left: 1rem;
.value {
font-size: 1.4rem;
}
}
.slider-wrap {
height: 100%;
text-align: center;
.silder {
padding: 2rem 0;
}
.btn {
padding: 2rem 0;
}
.btn+.btn {
padding-top: 0rem;
}
}
}
</style>

View File

@@ -0,0 +1,81 @@
<template>
<el-dialog title="调节亮度" destroy-on-close v-model="state.show" center align-center width="94%">
<div class="slider-wrap flex flex-column">
<div class="silder flex flex-1">
<div class="flex-1">
<el-slider @change="handleChangeLight" v-model="state.light" />
</div>
</div>
</div>
</el-dialog>
</template>
<script>
import { reactive } from '@vue/reactivity';
import { computed, watch } from '@vue/runtime-core';
import { setLight } from '../../../../apis/light'
import { injectGlobalData } from '@/views/provide';
import { injectPluginState } from '../../provide';
export default {
props: ['modelValue', 'items'],
emits: ['update:modelValue'],
components: {},
setup(props, { emit }) {
const globalData = injectGlobalData();
const pluginState = injectPluginState();
const state = reactive({
show: props.modelValue,
items: computed(() => pluginState.value.light.items),
loading: false,
light: pluginState.value.light.items[0].Light.Value
});
watch(() => state.show, (val) => {
if (!val) {
setTimeout(() => {
emit('update:modelValue', val);
}, 300);
}
});
const handleCancel = () => {
state.show = false;
}
const handleChangeLight = () => {
setLight(state.items.map(c => c.MachineName), state.light);
}
return {
state, globalData, handleCancel, handleChangeLight
}
}
}
</script>
<style lang="stylus" scoped>
.light {
font-size: 2rem;
padding-left: 1rem;
.value {
font-size: 1.4rem;
}
}
.slider-wrap {
text-align: center;
// height: 10rem;
.silder {
padding: 2rem 4rem;
}
.btn {
padding: 2rem 0;
}
.btn+.btn {
padding-top: 0rem;
}
}
</style>

View File

@@ -0,0 +1,28 @@
import { injectGlobalData } from "@/views/provide";
export default {
field() {
return {
Light: {
Value: 0
},
}
},
state: {
light: {
showLight: false,
showLightSingle: false,
items: []
}
},
globalData: null,
init() {
this.globalData = injectGlobalData();
},
update(item, report) {
if (report.Light) {
item.Light.Value = Math.floor(+report.Light.Value);
if (isNaN(item.Light.Value)) item.Light.Value = 0;
}
}
}

View File

@@ -0,0 +1,44 @@
<template>
<el-col :span="4">
<el-switch size="small" @click="handleLock" :model-value="data.LLock.Value" inline-prompt active-text="锁屏" inactive-text="锁屏" />
</el-col>
</template>
<script>
import { llockUpdate } from '@/apis/llock';
import { injectPluginState } from '../../provide'
import { ElMessage, ElMessageBox } from 'element-plus';
export default {
sort: 2,
props: ['data'],
setup(props) {
const pluginState = injectPluginState();
const handleLock = () => {
let desc = props.data.LLock.Value ? '确定解除锁屏吗?' : '确定开启锁屏吗?';
ElMessageBox.confirm(desc, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
llockUpdate([props.data.MachineName], !props.data.LLock.Value).then((res) => {
if (res) {
ElMessage.success('操作成功');
} else {
ElMessage.error('操作失败');
}
}).catch(() => {
ElMessage.error('操作失败');
});
}).catch(() => { });
}
return {
data: props.data, handleLock
}
}
}
</script>
<style lang="stylus" scoped></style>

View File

@@ -0,0 +1,12 @@
export default {
field() {
return {
LLock: {
Value: false
},
}
},
update(item, report) {
item.LLock.Value = report.LLock.Value;
}
}

View File

@@ -0,0 +1,26 @@
<template>
<a href="javascript:;" @click="handleMessage">
<el-icon>
<Bell />
</el-icon>
</a>
</template>
<script>
import { injectPluginState } from '../../provide'
export default {
props: ['data'],
setup(props) {
const pluginState = injectPluginState();
const handleMessage = () => {
pluginState.value.message.items = [props.data];
pluginState.value.message.showMessage = true;
}
return {
handleMessage
}
}
}
</script>
<style lang="stylus" scoped></style>

View File

@@ -0,0 +1,148 @@
<template>
<el-dialog class="options" title="发送提醒" destroy-on-close v-model="state.show" center align-center width="94%">
<div class="command-wrap flex">
<div class="items">
<CheckBoxWrap ref="items" :data="globalData.devices" :items="state.items" label="MachineName" title="选择设备"></CheckBoxWrap>
</div>
<div class="flex-1"></div>
<div class="prevs-wrap flex flex-column flex-nowrap">
<div class="prevs">
<PrevBoxWrap ref="prevs" :data="state.prevs" @prev="handlePrev" title="快捷短语"></PrevBoxWrap>
</div>
<div class="flex-1"></div>
<div>
<div class="times">
<el-input v-model="state.sec" size="large">
<template #append>秒钟</template>
</el-input>
</div>
<div class="prev">
<el-input v-model="state.prev" type="textarea" resize="none" placeholder="输入提醒消息"></el-input>
</div>
</div>
</div>
</div>
<template #footer>
<el-button @click="handleCancel"> </el-button>
<el-button type="primary" :loading="state.loading" @click="handleSubmit"> </el-button>
</template>
</el-dialog>
</template>
<script>
import { reactive, ref } from '@vue/reactivity';
import { computed, watch } from '@vue/runtime-core';
import CheckBoxWrap from '../../boxs/CheckBoxWrap.vue'
import PrevBoxWrap from '../../boxs/PrevBoxWrap.vue'
import { ElMessage, ElMessageBox } from 'element-plus';
import { exec } from '../../../../apis/command'
import { injectGlobalData } from '@/views/provide';
import { injectPluginState } from '../../provide';
export default {
props: ['modelValue'],
emits: ['update:modelValue'],
components: { CheckBoxWrap, PrevBoxWrap },
setup(props, { emit }) {
const globalData = injectGlobalData();
const pluginState = injectPluginState();
const state = reactive({
show: props.modelValue,
items: computed(() => pluginState.value.message.items),
prevs: [
'请注意上课纪律!',
'请勿玩游戏!',
'请勿大声喧哗!'
],
sec: 10,
prev: '',
loading: false
});
try {
if (pluginState.value.message.items.length == 1 && pluginState.value.message.items[0].Screen.UserName) {
state.prevs.push(`${pluginState.value.message.items[0].Screen.UserName}】请注意上课纪律!`);
}
} catch (e) {
}
watch(() => state.show, (val) => {
if (!val) {
setTimeout(() => {
emit('update:modelValue', val);
}, 300);
}
});
const handlePrev = (item) => {
state.prev = item;
}
const items = ref(null);
const prevs = ref(null);
const handleSubmit = () => {
let _items = items.value.getData();
if (_items.length == 0) {
ElMessage.error('未选择任何设备');
return;
}
if (state.prev.length == 0) {
ElMessage.error('未填写消息');
return;
}
ElMessageBox.confirm('是否确定发送消息?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
state.loading = true;
exec(_items, [`start message.win.exe "${state.prev}" ${state.sec}`]).then((res) => {
if (res) {
ElMessage.success('操作成功');
} else {
ElMessage.error('操作失败');
}
state.loading = false;
}).catch(() => {
state.loading = false;
ElMessage.error('操作失败');
});
}).catch(() => { });
}
const handleCancel = () => {
state.show = false;
}
return {
state, globalData, items, prevs, handleSubmit, handleCancel, handlePrev
}
}
}
</script>
<style lang="stylus" scoped>
.command-wrap {
height: 60vh;
.items {
height: 100%;
width: 48%;
position: relative;
}
.prevs-wrap {
height: 100%;
width: 48%;
position: relative;
.times {
margin: 0.6rem 0;
}
.prevs {
height: 100%;
width: 100%;
position: relative;
}
}
}
</style>

View File

@@ -0,0 +1,26 @@
<template>
<el-button size="small" plain dark @click="handleMessage">提醒<el-icon>
<Bell />
</el-icon>
</el-button>
</template>
<script>
import { injectGlobalData } from '@/views/provide';
import { injectPluginState } from '../../provide';
export default {
setup() {
const pluginState = injectPluginState();
const globalData = injectGlobalData();
const handleMessage = () => {
pluginState.value.message.items = globalData.value.devices;
pluginState.value.message.showMessage = true;
}
return { handleMessage }
}
}
</script>
<style lang="stylus" scoped></style>

View File

@@ -0,0 +1,21 @@
<template>
<div>
<ChooseDig v-if="pluginState.message.showMessage" v-model="pluginState.message.showMessage"></ChooseDig>
</div>
</template>
<script>
import { injectPluginState } from '../../provide'
import ChooseDig from './ChooseDig.vue'
export default {
components: { ChooseDig },
setup() {
const pluginState = injectPluginState();
return { pluginState }
}
}
</script>
<style lang="stylus" scoped></style>

View File

@@ -0,0 +1,8 @@
export default {
state: {
message: {
showMessage: false,
items: []
}
}
}

View File

@@ -0,0 +1,29 @@
<template>
<el-col :span="8" class="time">
<span>fps : {{data.Report.fps}} {{data.Report.ping}}ms</span>
</el-col>
</template>
<script>
export default {
sort: -1,
props: ['data'],
setup(props) {
return {
data: props.data
}
}
}
</script>
<style lang="stylus" scoped>
.time {
text-align: left !important;
color: #666;
line-height: 2rem;
&>span {
font-size: 1.2rem;
}
}
</style>

View File

@@ -0,0 +1,93 @@
import { subNotifyMsg } from '@/apis/request';
import { reportPing, reportUpdate } from '../../../../apis/report'
import { injectGlobalData } from '@/views/provide';
export default {
field() {
return {
Report: {
fps: 0,
fpsTimes: 0,
ping: 0
},
}
},
globalData: null,
init() {
this.globalData = injectGlobalData();
this.reportInterval();
this.reportPingInterval();
subNotifyMsg('/notify/report/pong', (res) => {
let item = this.globalData.value.devices.filter(c => c.MachineName == res.Name)[0];
if (item) {
item.Connected = true;
item.Report.ping = res.Time;
}
});
this.fpsInterval();
},
reportTimer: 0,
reported: true,
reportInterval() {
if (this.reported) {
this.reported = false;
reportUpdate(this.globalData.value.reportNames).then(() => {
this.reported = true;
this.reportTimer = setTimeout(() => {
this.reportInterval();
}, 30);
}).catch(() => {
this.reported = true;
this.reportTimer = setTimeout(() => {
this.reportInterval();
}, 30);
});
} else {
this.reportTimer = setTimeout(() => {
this.reportInterval();
}, 30);
}
},
reportPingTimer: 0,
reportedPing: true,
reportPingInterval() {
if (this.reportedPing) {
this.reportedPing = false;
let start = Date.now();
reportPing(this.globalData.value.reportNames).then(() => {
this.reportedPing = true;
let lastTime = 1000 - (Date.now() - start);
if (lastTime < 10) {
lastTime = 10;
}
this.reportPingTimer = setTimeout(() => {
this.reportPingInterval();
}, lastTime);
}).catch(() => {
this.reportedPing = true;
this.reportPingTimer = setTimeout(() => {
this.reportPingInterval();
}, 1000);
});
} else {
this.reportPingTimer = setTimeout(() => {
this.reportPingInterval();
}, 1000);
}
},
fpsInterval() {
this.globalData.value.devices.forEach(item => {
item.Report.fps = item.Report.fpsTimes;
item.Report.fpsTimes = 0;
});
setTimeout(() => {
this.fpsInterval();
}, 1000)
},
update(item, report) {
item.Report.fpsTimes++;
}
}

View File

@@ -0,0 +1,139 @@
import { injectGlobalData } from '@/views/provide';
import { screenUpdate } from '../../../../apis/screen'
import { subNotifyMsg } from '@/apis/request';
export default {
field() {
return {
Screen: {
fps: 0,
fpsTimes: 0,
img: null,
LastInput: 0,
UserName: '',
KeyBoard: ''
}
};
},
reportTimer: 0,
globalData: null,
reported: true,
init() {
this.globalData = injectGlobalData();;
this.reportInterval();
this.subMessage();
this.fpsInterval();
this.draw();
},
draw() {
const devices = this.globalData.value.devices.filter(c => this.globalData.value.reportNames.indexOf(c.MachineName) >= 0);
for (let i = 0; i < devices.length; i++) {
const item = devices[i];
if (!item.canvas) {
item.canvas = document.getElementById(`canvas-${item.MachineName}`);
if (item.canvas) {
item.ctx = item.canvas.getContext('2d');
}
}
if (item.ctx) {
const img = item.Screen.img;
if (img) {
item.ctx.clearRect(0, 0, item.canvas.width, item.canvas.height);
item.ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, item.canvas.width, item.canvas.height);
item.ctx.lineWidth = 3;
item.ctx.font = 'bold 50px Arial';
item.ctx.fillStyle = '#000';
item.ctx.fillText(`FPS : ${item.Screen.fps} 、LT : ${item.Screen.LastInput}ms`, 50, 70);
item.ctx.strokeStyle = '#0f0';
item.ctx.strokeText(`FPS : ${item.Screen.fps} 、LT : ${item.Screen.LastInput}ms`, 50, 70);
item.ctx.lineWidth = 10;
let top = (item.canvas.height - 100) / 2;
let left = (item.canvas.width - 50 * item.Screen.KeyBoard.length) / 2;
item.ctx.font = 'bold 100px Arial';
item.ctx.fillStyle = 'rgba(255,255,255,0.5)';
item.ctx.fillText(`${item.Screen.KeyBoard}`, left, top);
item.ctx.lineWidth = 2;
item.ctx.strokeStyle = '#000';
item.ctx.strokeText(`${item.Screen.KeyBoard}`, left, top);
}
for (let j in item) {
try {
if (item[j] && item[j].draw) {
item[j].draw(item.canvas, item.ctx);
}
} catch (e) {
console.log(item);
console.log(e);
}
}
}
//item.Screen.img = null;
}
requestAnimationFrame(() => {
this.draw();
});
},
subMessage() {
subNotifyMsg('/notify/report/screen', (res, param) => {
if (this.globalData.value.reportNames.indexOf(res.Name) == -1) return;
let item = this.globalData.value.devices.filter(c => c.MachineName == res.Name)[0];
if (item) {
item.Screen.fpsTimes++;
const img = new Image();
if (typeof res.Img == 'string') {
img.src = `data:image/jpg;base64,${res.Img}`;
img.onload = function () {
item.Screen.img = img;
};
} else {
const reader = new FileReader();
reader.readAsDataURL(res.Img);
reader.onload = function (e) {
img.src = e.target.result;
img.onload = function () {
item.Screen.img = img;
};
};
}
}
});
},
fpsInterval() {
this.globalData.value.devices.forEach(item => {
item.Screen.fps = item.Screen.fpsTimes;
item.Screen.fpsTimes = 0;
});
setTimeout(() => {
this.fpsInterval();
}, 1000)
},
reported: true,
reportInterval() {
if (this.reported) {
this.reported = false;
screenUpdate(this.globalData.value.reportNames).then(() => {
this.reported = true;
this.reportTimer = setTimeout(() => {
this.reportInterval();
}, 30);
}).catch(() => {
this.reported = true;
this.reportTimer = setTimeout(() => {
this.reportInterval();
}, 30);
});
} else {
this.reportTimer = setTimeout(() => {
this.reportInterval();
}, 30);
}
},
update(item, report) {
if (report.Screen) {
item.Screen.LastInput = report.Screen.LastInput || '0';
item.Screen.UserName = report.Screen.UserName || '';
item.Screen.KeyBoard = report.Screen.KeyBoard || '';
}
}
}

View File

@@ -0,0 +1,44 @@
<template>
<el-col :span="4">
<el-switch size="small" @click="handleUSB" :model-value="data.Usb.Value" inline-prompt active-color="#ff0000" active-text="U盘" inactive-text="U盘" />
</el-col>
</template>
<script>
import { usbUpdate } from '@/apis/usb';
import { injectPluginState } from '../../provide'
import { ElMessage, ElMessageBox } from 'element-plus';
export default {
sort: 4,
props: ['data'],
setup(props) {
const pluginState = injectPluginState();
const handleUSB = () => {
let desc = props.data.Usb.Value ? '确定启用USB吗' : '确定禁用USB吗';
ElMessageBox.confirm(desc, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
usbUpdate([props.data.MachineName], !props.data.Usb.Value).then((res) => {
if (res) {
ElMessage.success('操作成功');
} else {
ElMessage.error('操作失败');
}
}).catch(() => {
ElMessage.error('操作失败');
});
}).catch(() => { });
}
return {
data: props.data, handleUSB
}
}
}
</script>
<style lang="stylus" scoped></style>

View File

@@ -0,0 +1,12 @@
export default {
field() {
return {
Usb: {
Value: false
},
}
},
update(item, report) {
item.Usb.Value = report.Usb.Value;
}
}

View File

@@ -0,0 +1,85 @@
<template>
<a href="javascript:;">
<span class="volume" @click="handleVolume">
<template v-if="data.Volume.Mute">
<el-icon>
<Mute />
</el-icon>
</template>
<template v-else>
<el-icon>
<Microphone />
</el-icon>
<div class="volume-bg" :style="{height:`${data.Volume.Value}%`}">
<el-icon class="value">
<Microphone />
</el-icon>
</div>
</template>
</span>
<p class="volume-value">{{Math.floor(data.Volume.Value)}}</p>
</a>
</template>
<script>
import { injectPluginState } from '../../provide'
export default {
props: ['data'],
setup(props) {
const pluginState = injectPluginState();
const handleVolume = () => {
pluginState.value.volume.items = [props.data];
pluginState.value.volume.showVolumeSingle = true;
}
return {
data: props.data,
handleVolume
}
}
}
</script>
<style lang="stylus" scoped>
a {
position: relative;
}
.volume-value {
color: #fff;
font-size: 1.4rem;
text-shadow: 1px 1px 2px #000;
position: absolute;
right: 100%;
top: 50%;
transform: translateY(-50%);
}
span.volume {
position: relative;
height: 100%;
display: block;
.el-icon {
font-size: 2rem;
}
.volume-bg {
position: absolute;
bottom: 2px;
left: 0;
height: 0%;
width: 100%;
overflow: hidden;
.el-icon {
color: green;
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: 0;
}
}
}
</style>

View File

@@ -0,0 +1,27 @@
<template>
<el-button size="small" plain dark @click="handleVolume">音量<el-icon>
<Microphone />
</el-icon></el-button>
</template>
<script>
import { injectGlobalData } from '@/views/provide';
import { injectPluginState } from '../../provide';
export default {
setup() {
const pluginState = injectPluginState();
const globalData = injectGlobalData();
const handleVolume = () => {
pluginState.value.volume.items = globalData.value.devices;
pluginState.value.volume.showVolume = true;
}
return {
handleVolume
}
}
}
</script>
<style lang="stylus" scoped></style>

View File

@@ -0,0 +1,21 @@
<template>
<div>
<Volume v-if="pluginState.volume.showVolume" v-model="pluginState.volume.showVolume"></Volume>
<VolumeSingle v-if="pluginState.volume.showVolumeSingle" v-model="pluginState.volume.showVolumeSingle"></VolumeSingle>
</div>
</template>
<script>
import { injectPluginState } from '../../provide';
import Volume from './Volume.vue'
import VolumeSingle from './VolumeSingle.vue'
export default {
components: { Volume, VolumeSingle },
setup() {
const pluginState = injectPluginState();
return { pluginState }
}
}
</script>
<style lang="stylus" scoped></style>

View File

@@ -0,0 +1,45 @@
<template>
<el-col :span="4">
<el-switch size="small" @click="handleVolumeMute" :model-value="data.Volume.Mute" inline-prompt active-text="静音" inactive-text="静音" />
</el-col>
</template>
<script>
import { setVolumeMute } from '@/apis/volume';
import { ElMessage, ElMessageBox } from 'element-plus';
import { injectPluginState } from '../../provide';
export default {
sort: 0,
props: ['data'],
setup(props) {
const pluginState = injectPluginState();
const handleVolumeMute = () => {
let desc = props.data.Volume.Mute ? '确定取消静音吗?' : '确定静音吗?';
ElMessageBox.confirm(desc, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
setVolumeMute([props.data.MachineName], !props.data.Volume.Mute).then((res) => {
if (res) {
ElMessage.success('操作成功');
} else {
ElMessage.error('操作失败');
}
}).catch(() => {
ElMessage.error('操作失败');
});
}).catch(() => { });
}
return {
data: props.data, handleVolumeMute
}
}
}
</script>
<style lang="stylus" scoped></style>

View File

@@ -0,0 +1,169 @@
<template>
<el-dialog class="options" title="调节音量" destroy-on-close v-model="state.show" center align-center width="94%">
<div class="command-wrap flex">
<div class="items">
<CheckBoxWrap ref="items" :data="globalData.devices" :items="state.items" label="MachineName" title="全选">
<template #title>
<div>
<el-button size="small" @click="handleSelectMute">状态选择</el-button>
</div>
</template>
<template #name="scope">
<span>
<span class="name">
{{scope.item.MachineName}}
</span>
<strong class="volume">
<template v-if="scope.item.VolumeMute">
<el-icon>
<Mute />
</el-icon>
</template>
<template v-else>
<el-icon>
<Microphone />
</el-icon>
</template>
<strong class="value">{{scope.item.Volume.Value?Math.floor(scope.item.Volume.Value):scope.item.Volume.Value}}%</strong>
</strong>
</span>
</template>
</CheckBoxWrap>
</div>
<div class="flex-1"></div>
<div class="commands">
<PrevBoxWrap ref="commands" title="调节音量">
<template #wrap>
<div class="slider-wrap flex flex-column">
<div class="silder flex flex-1">
<div class="flex-1">
<el-slider @change="handleChangeVolume" v-model="state.volume" vertical height="100%" />
</div>
</div>
<div class="btn">
<el-button @click="handleMute(true)">静音</el-button>
</div>
<div class="btn">
<el-button @click="handleMute(false)">取消静音</el-button>
</div>
</div>
</template>
</PrevBoxWrap>
</div>
</div>
<template #footer>
<el-button @click="handleCancel"> </el-button>
<el-button type="primary" @click="handleCancel"> </el-button>
</template>
</el-dialog>
</template>
<script>
import { reactive, ref } from '@vue/reactivity';
import { computed, watch } from '@vue/runtime-core';
import CheckBoxWrap from '../../boxs/CheckBoxWrap.vue'
import PrevBoxWrap from '../../boxs/PrevBoxWrap.vue'
import { ElMessage } from 'element-plus';
import { setVolumeMute, setVolume } from '../../../../apis/volume'
import { injectGlobalData } from '@/views/provide';
import { injectPluginState } from '../../provide';
export default {
props: ['modelValue'],
emits: ['update:modelValue'],
components: { CheckBoxWrap, PrevBoxWrap },
setup(props, { emit }) {
const globalData = injectGlobalData();
const pluginState = injectPluginState();
const state = reactive({
show: props.modelValue,
items: computed(() => pluginState.value.volume.items),
mute: false,
loading: false,
volume: 0
});
watch(() => state.show, (val) => {
if (!val) {
setTimeout(() => {
emit('update:modelValue', val);
}, 300);
}
});
const handleCancel = () => {
state.show = false;
}
const handleSelectMute = () => {
state.items = globalData.value.devices.filter(c => c.VolumeMute == state.mute);
ElMessage.success(`已选中${state.mute ? '静音' : '未静音'}设备`)
state.mute = !state.mute;
}
const items = ref(null);
const handleMute = (mute) => {
let _items = items.value.getData();
if (_items.length == 0) {
ElMessage.error('未选择任何设备');
return;
}
setVolumeMute(_items, mute);
}
const handleChangeVolume = () => {
let _items = items.value.getData();
if (_items.length == 0) {
ElMessage.error('未选择任何设备');
return;
}
setVolume(_items, state.volume / 100);
}
return {
state, globalData, items, handleCancel, handleSelectMute, handleMute, handleChangeVolume
}
}
}
</script>
<style lang="stylus" scoped>
.command-wrap {
height: 60vh;
.items {
height: 100%;
width: 60%;
position: relative;
}
.commands {
height: 100%;
width: 38%;
position: relative;
}
.volume {
font-size: 2rem;
padding-left: 1rem;
.value {
font-size: 1.4rem;
}
}
.slider-wrap {
height: 100%;
text-align: center;
.silder {
padding: 2rem 0;
}
.btn {
padding: 2rem 0;
}
.btn+.btn {
padding-top: 0rem;
}
}
}
</style>

View File

@@ -0,0 +1,81 @@
<template>
<el-dialog title="调节音量" destroy-on-close v-model="state.show" center align-center width="94%">
<div class="slider-wrap flex flex-column">
<div class="silder flex flex-1">
<div class="flex-1">
<el-slider @change="handleChangeVolume" v-model="state.volume" />
</div>
</div>
</div>
</el-dialog>
</template>
<script>
import { reactive } from '@vue/reactivity';
import { computed, watch } from '@vue/runtime-core';
import { setVolume } from '../../../../apis/volume'
import { injectGlobalData } from '@/views/provide';
import { injectPluginState } from '../../provide';
export default {
props: ['modelValue', 'items'],
emits: ['update:modelValue'],
components: {},
setup(props, { emit }) {
const globalData = injectGlobalData();
const pluginState = injectPluginState();
const state = reactive({
show: props.modelValue,
items: computed(() => pluginState.value.volume.items),
loading: false,
volume: pluginState.value.volume.items[0].Volume.Value
});
watch(() => state.show, (val) => {
if (!val) {
setTimeout(() => {
emit('update:modelValue', val);
}, 300);
}
});
const handleCancel = () => {
state.show = false;
}
const handleChangeVolume = () => {
setVolume(state.items.map(c => c.MachineName), state.volume / 100);
}
return {
state, globalData, handleCancel, handleChangeVolume
}
}
}
</script>
<style lang="stylus" scoped>
.volume {
font-size: 2rem;
padding-left: 1rem;
.value {
font-size: 1.4rem;
}
}
.slider-wrap {
text-align: center;
// height: 10rem;
.silder {
padding: 2rem 4rem;
}
.btn {
padding: 2rem 0;
}
.btn+.btn {
padding-top: 0rem;
}
}
</style>

View File

@@ -0,0 +1,38 @@
export default {
field() {
return {
Volume: {
Value: 0,
Mute: false,
MasterPeak: 0,
draw(canvas, ctx) {
this.MasterPeak -= 1;
if (this.MasterPeak < 0) {
this.MasterPeak = 0;
return;
}
ctx.beginPath();
ctx.fillStyle = '#0f0';
ctx.fillRect(0, canvas.height - 10, this.MasterPeak / 100 * canvas.width, 10);
ctx.closePath();
}
},
}
},
state: {
volume: {
showVolume: false,
showVolumeSingle: false,
items: []
}
},
init() {
},
update(item, report) {
item.Volume.Value = report.Volume.Value;
item.Volume.Mute = report.Volume.Mute;
if (report.Volume.MasterPeak > item.Volume.MasterPeak) {
item.Volume.MasterPeak = report.Volume.MasterPeak;
}
}
}

View File

@@ -0,0 +1,41 @@
<template>
<el-col :span="4">
<el-switch size="small" @click="handleWallpaper" :model-value="data.Wallpaper.Value" inline-prompt active-text="壁纸" inactive-text="壁纸" />
</el-col>
</template>
<script>
import { wallpaperUpdate } from '@/apis/wallpaper';
import { ElMessage, ElMessageBox } from 'element-plus';
import { injectPluginState } from '../../provide';
export default {
sort: 3,
props: ['data'],
setup(props) {
const pluginState = injectPluginState();
const handleWallpaper = () => {
let desc = props.data.Wallpaper.Value ? '确定关闭壁纸吗?' : '确定开启壁纸吗?';
ElMessageBox.confirm(desc, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
wallpaperUpdate([props.data.MachineName], !props.data.Wallpaper.Value, `${window.location.origin}/bg.jpg`).then((res) => {
if (res) {
ElMessage.success('操作成功');
} else {
ElMessage.error('操作失败');
}
}).catch(() => {
ElMessage.error('操作失败');
});
}).catch(() => { });
}
return { data: props.data, handleWallpaper }
}
}
</script>
<style lang="stylus" scoped></style>

View File

@@ -0,0 +1,12 @@
export default {
field() {
return {
Wallpaper: {
Value: false
},
}
},
update(item, report) {
item.Wallpaper.Value = report.Wallpaper.Value;
}
}

View File

@@ -0,0 +1,11 @@
import { inject, provide, ref } from "vue"
const pluginStateSymbol = Symbol();
export const providePluginState = (data) => {
const state = ref(data);
provide(pluginStateSymbol, state);
return state;
}
export const injectPluginState = () => {
return inject(pluginStateSymbol);
}

View File

@@ -0,0 +1,53 @@
<template>
<div class="foot-wrap">
<ul class="flex">
<template v-for="(item,index) in footMenuModules" :key="index">
<li>
<component :is="item"></component>
</li>
</template>
</ul>
</div>
</template>
<script>
export default {
components: {},
setup() {
const footMenuFiles = require.context('../plugins/', true, /FootMenu\.vue/);
const footMenuModules = footMenuFiles.keys().map(c => footMenuFiles(c).default).sort((a, b) => a.sort - b.sort);
return { footMenuModules }
}
}
</script>
<style lang="stylus">
.el-dialog.is-align-center.options {
margin-top: 3vh;
}
</style>
<style lang="stylus" scoped>
.foot-wrap {
border-top: 1px solid rgba(0, 0, 0, 0.2);
position: relative;
z-index: 999;
}
ul li {
width: 33.333333%;
text-align: center;
a {
padding: 1.7rem 0;
font-size: 1.6rem;
display: block;
color: #ebffef;
line-height: 1;
&:hover {
background-color: rgba(0, 0, 0, 0.05);
}
}
}
</style>

View File

@@ -0,0 +1,91 @@
<template>
<div class="foot-options-wrap flex">
<el-button size="default" plain dark @click="handleRefresh">
<el-icon>
<Refresh />
</el-icon>
</el-button>
<span class="flex-1"></span>
<div class="options-btn">
<p>
<template v-for="(item,index) in footOptionTopModules" :key="index">
<component :is="item"></component>
</template>
</p>
<p>
<template v-for="(item,index) in footOptionBottomModules" :key="index">
<component :is="item"></component>
</template>
</p>
</div>
<span class="flex-1"></span>
<el-button size="default" plain dark @click="handleUpdate">{{username}}</el-button>
</div>
</template>
<script>
import { computed } from 'vue';
import { ElMessageBox } from 'element-plus';
import { injectGlobalData } from '@/views/provide';
export default {
setup() {
const footOptionTopFiles = require.context('../plugins/', true, /FootOptionTop\.vue/);
const footOptionTopModules = footOptionTopFiles.keys().map(c => footOptionTopFiles(c).default);
const footOptionBottomFiles = require.context('../plugins/', true, /FootOptionBottom\.vue/);
const footOptionBottomModules = footOptionBottomFiles.keys().map(c => footOptionBottomFiles(c).default);
const globalData = injectGlobalData();
const username = computed(() => globalData.value.username);
const handleRefresh = () => {
window.location.reload();
}
const handleUpdate = () => {
ElMessageBox.confirm('是否确定重选角色?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
globalData.value.username = '';
localStorage.setItem('username', '');
}).catch(() => { });
}
return {
footOptionTopModules, footOptionBottomModules, username, handleRefresh, handleUpdate
}
}
}
</script>
<style lang="stylus" scoped>
.foot-options-wrap {
text-align: center;
padding: 0.6rem;
.el-button {
background-color: rgba(58, 74, 102, 0.5);
border-color: rgba(58, 74, 102, 0.2);
color: #f5f5f5;
}
.el-dropdown {
margin: 0 0.6rem;
}
.options-btn {
.el-button+.el-button {
margin-left: 0.6rem;
}
p {
padding-top: 0.6rem;
&:nth-child(1) {
padding: 0;
}
}
}
}
</style>

View File

@@ -0,0 +1,214 @@
<template>
<div class="device-item" :style="data.style">
<dl>
<dt>
<div class="bg"></div>
<div class="value flex">
<span class="name" :class="{connected:data.Connected}">
<span class="machine-mame">{{data.MachineName}}</span>
<i class="user-name" v-if="data.Screen.UserName"> - {{data.Screen.UserName}}</i>
</span>
<span class="flex-1"></span>
<template v-for="(item,index) in titleRightModules" :key="index">
<component :is="item" :data="data"></component>
</template>
</div>
</dt>
<dd class="img">
<div class="inner">
<canvas v-if="data.Connected" width="1920" height="1080" :id="`canvas-${data.MachineName}`"></canvas>
<template v-for="(item,index) in screenModules" :key="index">
<component :is="item" :data="data"></component>
</template>
<div class="btns flex">
<div class="left">
<template v-for="(item,index) in btnLeftModules" :key="index">
<component :is="item" :data="data"></component>
</template>
</div>
<div class="flex-1"></div>
<div class="right">
<template v-for="(item,index) in btnRightModules" :key="index">
<component :is="item" :data="data"></component>
</template>
</div>
</div>
</div>
</dd>
<dd class="options">
<el-row>
<template v-for="(item,index) in optionModules" :key="index">
<component :is="item" :data="data"></component>
</template>
</el-row>
</dd>
</dl>
</div>
</template>
<script>
export default {
props: {
data: {
type: Object,
default: {}
}
},
setup(props, { emit }) {
const data = props.data;
const titleRightFiles = require.context('../plugins/', true, /TitleRight\.vue/);
const titleRightModules = titleRightFiles.keys().map(c => titleRightFiles(c).default);
const screenFiles = require.context('../plugins/', true, /Screen\.vue/);
const screenModules = screenFiles.keys().map(c => screenFiles(c).default);
const btnLeftFiles = require.context('../plugins/', true, /BtnLeft\.vue/);
const btnLeftModules = btnLeftFiles.keys().map(c => btnLeftFiles(c).default);
const btnRightFiles = require.context('../plugins/', true, /BtnRight\.vue/);
const btnRightModules = btnRightFiles.keys().map(c => btnRightFiles(c).default);
const optionFiles = require.context('../plugins/', true, /\/Option\.vue/);
const optionModules = optionFiles.keys().map(c => optionFiles(c).default).sort((a, b) => (a.sort || 0) - (b.sort || 0));
return {
data, titleRightModules, screenModules, btnLeftModules, btnRightModules, optionModules
}
}
}
</script>
<style lang="stylus" scoped>
.device-item {
border: 1px solid #ddd;
font-size: 1.6rem;
background-color: #fff;
box-shadow: 0 0 4px rgba(0, 0, 0, 0.05);
border-radius: 4px;
width: 98%;
margin: 0 auto 1rem auto;
position: relative;
transition: 0.3s;
&:after {
content: '';
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
z-index: -1;
}
dt {
padding: 0.6rem 0.6rem 0 0.6rem;
border-radius: 4px;
position: relative;
span.name {
line-height: 2rem;
&.connected {
color: green;
font-weight: bold;
}
span.machine-mame {
}
i.user-name {
color: #666;
}
}
}
dd.img {
padding: 0.6rem;
position: relative;
font-size: 0;
box-sizing: border-box;
&:before {
content: '';
display: inline-block;
padding-bottom: 56.25%;
width: 0.1px;
vertical-align: middle;
}
.inner {
position: absolute;
left: 0.6rem;
top: 0.6rem;
right: 0.6rem;
bottom: 0.6rem;
overflow: hidden;
background-color: #000;
border-radius: 4px;
border: 1px solid #ddd;
box-sizing: border-box;
canvas {
width: 100%;
position: absolute;
height: 100%;
border-radius: 4px;
}
.btns {
position: absolute;
left: 0;
right: 0;
top: 10%;
.left {
padding-left: 0.6rem;
}
.right {
padding-right: 0.6rem;
}
.left, .right {
a {
width: 2.4rem;
height: 2.4rem;
text-align: center;
line-height: 2.8rem;
margin-bottom: 0.6rem;
display: block;
font-size: 2rem;
border-radius: 50%;
border: 1px solid #3e5a6e;
box-shadow: 0 0 4px rgba(255, 255, 255, 0.1);
background-color: rgba(255, 255, 255, 0.5);
color: #3e5a6e;
transition: 0.3s;
&:hover {
box-shadow: 0 0 4px 2px rgba(255, 255, 255, 0.5);
}
}
}
}
}
}
dd.options {
padding: 0 0.6rem 0.6rem 0.6rem;
border-radius: 4px;
text-align: center;
.el-col {
text-align: right;
.el-switch {
--el-switch-off-color: #ccc;
--el-switch-on-color: #69b56c;
}
}
}
}
</style>

View File

@@ -0,0 +1,137 @@
<template>
<template v-if="devices.length > 0">
<template v-for="(item) in devices" :key="item.MachineName">
<Item :data="item"></Item>
</template>
</template>
<template v-else>
<el-empty description="或许你应该先去选择管理设备"></el-empty>
</template>
</template>
<script>
import Item from './Item.vue'
import { computed, watch } from 'vue'
import { getList } from '../../../apis/signin.js'
import { nextTick, onMounted, onUnmounted } from 'vue'
import { injectGlobalData } from '@/views/provide'
import { subNotifyMsg } from '@/apis/request'
export default {
components: { Item },
setup() {
const files = require.context('../plugins/', true, /index\.js/);
const pluginSettings = files.keys().map(c => files(c).default);
pluginSettings.forEach((item) => {
item.init && item.init();
});
const globalData = injectGlobalData();
const getData = () => {
getList().then((res) => {
globalData.value.allDevices = res.map(c => {
return pluginSettings.reduce((result, item, index) => {
if (item.field) {
result = Object.assign(result, item.field());
}
return result;
}, c);
}).sort((a, b) => a.MachineName < b.MachineName ? -1 : 1);
nextTick(() => {
updateVisibleItems();
});
}).catch(() => {
});
}
const devices = computed(() => {
nextTick(() => {
updateVisibleItems();
});
return globalData.value.devices;
});
const subMessage = () => {
subNotifyMsg('/notify/report/report', (res) => {
if (globalData.value.reportNames.indexOf(res.Name) == -1) return;
if (typeof res.Report == 'string') {
res.Report = JSON.parse(res.Report);
}
let item = globalData.value.devices.filter(c => c.MachineName == res.Name)[0];
if (item) {
pluginSettings.forEach(plugin => {
plugin.update && plugin.update(item, res.Report);
});
}
});
}
const listWrapScrollListener = () => {
nextTick(() => {
document.querySelector('#device-list-wrap').querySelector('.items').addEventListener('scroll', updateVisibleItems);
});
}
const listWrapRemoveScrollListener = () => {
try {
document.querySelector('#device-list-wrap').querySelector('.items').removeEventListener('scroll', updateVisibleItems);
} catch (e) { }
}
const updateVisibleItems = () => {
try {
const itemsWrap = document.querySelector('#device-list-wrap').querySelector('.items');
const scrollTop = itemsWrap.scrollTop;
const items = itemsWrap.querySelectorAll('.device-item');
if (items.length == 0) return;
const wrapHeight = itemsWrap.offsetHeight;
const doms = [...items].map((item, index) => {
const topLine = item.offsetTop - scrollTop;
const middleLine = topLine + item.offsetHeight / 2;
const offset = Math.abs(middleLine - wrapHeight / 2);
return { dom: item, index: index, offset: offset };
});
//哪个是在最中间的
const middleItem = doms.sort((a, b) => a.offset - b.offset)[0];
for (let i = 0; i < items.length; i++) {
let style = 'z-index:9;background-color:#fff;';
const dist = Math.abs((middleItem.index - i));
const opacity = 1 - Math.abs((middleItem.index - i)) / 10 * 2;
const translateZ = 100 + (dist * 100);
if (i < middleItem.index && middleItem.index > 1) {
style = `background-color:rgba(255,255,255,${opacity});z-index:8;transform: translateZ(-${translateZ}px) `;
style += `translateY(30px);`;
} else if (i > middleItem.index && middleItem.index < items.length - 2) {
style = `background-color:rgba(255,255,255,${opacity});z-index:8;transform: translateZ(-${translateZ}px) `;
style += `translateY(-30px);`;
}
items[i].style = style;
}
//有哪些需要报告
const reportDoms = doms.filter(item => item.index >= middleItem.index - 2 && item.index <= middleItem.index + 2).map(c => c.index);
globalData.value.reportNames = globalData.value.devices
.filter((value, index) => reportDoms.indexOf(index) >= 0)
.map(c => c.MachineName);
} catch (e) {
console.log(e);
}
}
onMounted(() => {
getData();
listWrapScrollListener();
updateVisibleItems();
subMessage();
});
onUnmounted(() => {
listWrapRemoveScrollListener();
});
return {
devices
}
}
}
</script>
<style lang="stylus" scoped></style>

View File

@@ -0,0 +1,27 @@
import { computed, inject, provide, ref } from "vue";
const globalDataSymbol = Symbol();
export const provideGlobalData = () => {
const globalData = ref({
username: '',
publicUserName: 'snltty',
usernames: {},
connected: false,
updateFlag: 0,
allDevices: [],
devices: computed(() => {
const user = globalData.value.usernames[globalData.value.username];
if (user) {
return globalData.value.allDevices.filter(c => user.Devices.indexOf(c.MachineName) >= 0);
}
return [];
}),
reportNames: []
});
provide(globalDataSymbol, globalData);
return globalData;
}
export const injectGlobalData = () => {
return inject(globalDataSymbol);
}

View File

@@ -0,0 +1,6 @@
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
productionSourceMap: process.env.NODE_ENV === 'production' ? false : true,
outputDir: '../cmonitor/web',
transpileDependencies: true
})