:add 样式改版;GrasscuttersWebDashboard支持

This commit is contained in:
wmn 2022-05-14 23:46:12 +08:00
parent 45d44f6cc6
commit 8786a54136
18 changed files with 548 additions and 85 deletions

View File

@ -91,7 +91,7 @@
"source.fixAll.eslint": true, "source.fixAll.eslint": true,
"source.fixAll.stylelint": true "source.fixAll.stylelint": true
}, },
"editor.defaultFormatter": "octref.vetur" "editor.defaultFormatter": "Vue.volar"
}, },
"i18n-ally.localesPaths": ["src/locales/lang"], "i18n-ally.localesPaths": ["src/locales/lang"],
"i18n-ally.keystyle": "nested", "i18n-ally.keystyle": "nested",

View File

@ -5,7 +5,7 @@
import { title } from 'process' import { title } from 'process'
import { useAppStore } from './store/modules/app' import { useAppStore } from './store/modules/app'
import { ref, watch } from 'vue' import { ref, watch,onMounted } from 'vue'
import { darkTheme } from 'naive-ui' import { darkTheme } from 'naive-ui'
const appStore = useAppStore() const appStore = useAppStore()
const apptheme = ref() const apptheme = ref()
@ -45,6 +45,13 @@ watch(
immediate: true, immediate: true,
}, },
) )
onMounted(() => {
const WSS = localStorage.getItem("WSS")
if (WSS) {
appStore.socketConnect(WSS)
}
})
</script> </script>
<style> <style>
@ -54,7 +61,7 @@ watch(
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
color: #2c3e50; color: #2c3e50;
background-color: var(--color-bg-1); background-color: var(--color-bg-1);
height: 100vh;
overflow: hidden; overflow: hidden;
min-height: 100vh;
} }
</style> </style>

7
src/components.d.ts vendored
View File

@ -8,9 +8,16 @@ declare module '@vue/runtime-core' {
AButton: typeof import('@arco-design/web-vue')['Button'] AButton: typeof import('@arco-design/web-vue')['Button']
ACascader: typeof import('@arco-design/web-vue')['Cascader'] ACascader: typeof import('@arco-design/web-vue')['Cascader']
ACheckbox: typeof import('@arco-design/web-vue')['Checkbox'] ACheckbox: typeof import('@arco-design/web-vue')['Checkbox']
ADescriptions: typeof import('@arco-design/web-vue')['Descriptions']
AInput: typeof import('@arco-design/web-vue')['Input'] AInput: typeof import('@arco-design/web-vue')['Input']
AInputNumber: typeof import('@arco-design/web-vue')['InputNumber'] AInputNumber: typeof import('@arco-design/web-vue')['InputNumber']
ALink: typeof import('@arco-design/web-vue')['Link']
AMenu: typeof import('@arco-design/web-vue')['Menu']
AMenuItem: typeof import('@arco-design/web-vue')['MenuItem']
ASelect: typeof import('@arco-design/web-vue')['Select'] ASelect: typeof import('@arco-design/web-vue')['Select']
ASubMenu: typeof import('@arco-design/web-vue')['SubMenu']
ATable: typeof import('@arco-design/web-vue')['Table']
ATableColumn: typeof import('@arco-design/web-vue')['TableColumn']
ATooltip: typeof import('@arco-design/web-vue')['Tooltip'] ATooltip: typeof import('@arco-design/web-vue')['Tooltip']
Footer: typeof import('./components/footer/index.vue')['default'] Footer: typeof import('./components/footer/index.vue')['default']
Header: typeof import('./components/Header/index.vue')['default'] Header: typeof import('./components/Header/index.vue')['default']

View File

@ -7,8 +7,7 @@ import { ref } from 'vue'
import { useDark, useToggle } from '@vueuse/core'; import { useDark, useToggle } from '@vueuse/core';
import { useAppStore, useUserStore } from '@/store'; import { useAppStore, useUserStore } from '@/store';
import { IconMoonFill, IconSunFill } from '@arco-design/web-vue/es/icon'; import { IconMoonFill, IconSunFill } from '@arco-design/web-vue/es/icon';
import { IconClockCircle } from '@arco-design/web-vue/es/icon';
const title = ref('I want to study typescript') const title = ref('I want to study typescript')
// //
const darkThemeMq = window.matchMedia('(prefers-color-scheme: dark)') const darkThemeMq = window.matchMedia('(prefers-color-scheme: dark)')
@ -40,6 +39,9 @@ const ThemeChange = (val: string | number | boolean) => {
document.documentElement.classList.remove('dark') document.documentElement.classList.remove('dark')
} }
} }
const LoginHS = ref(true)
</script> </script>
<template> <template>
@ -60,12 +62,12 @@ const ThemeChange = (val: string | number | boolean) => {
Grasscutter-docs-zh_CN Grasscutter-docs-zh_CN
</a> </a>
</li> </li>
<li> <li v-if="LoginHS">
<router-link <router-link to="/start/login" class="hover:text-sky-500 dark:hover:text-sky-400">
to="/login" Console
class="hover:text-sky-500 dark:hover:text-sky-400" </router-link>
>Login</router-link>
</li> </li>
</ul> </ul>
</nav> </nav>
<div class="flex items-center pl-6 ml-6 border-l border-slate-200 dark:border-slate-800"> <div class="flex items-center pl-6 ml-6 border-l border-slate-200 dark:border-slate-800">

View File

@ -0,0 +1,60 @@
<template>
<div class="msg-title text-slate-900 dark:text-slate-100">控制台</div>
<div class="msg" id="msg">
<p class="text-slate-900 dark:text-slate-100" v-for="(item, index) in appStore.mesgData" :key="index">
{{ item }}
</p>
</div>
<div class="cmd">
<a-input v-model="cmdvalue" placeholder="" allow-clear @keydown.enter="send" />
<a-button type="outline" @click="send">发送</a-button>
</div>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import { useAppStore } from '@/store/modules/app'
const appStore = useAppStore()
const cmdvalue = ref("")
function send() {
const send_msg = {
type: 'CMD',
data: cmdvalue.value,
}
const send_msg_str = JSON.stringify(send_msg)
appStore.socketSend(send_msg_str)
}
watch(
() => appStore.mesgData,
() => {
var div = document.getElementById('msg');
if (div) {
div.scrollTop = div.scrollHeight + 30;
}
},
{
immediate: true,
deep: true
},
)
</script>
<style lang="less" scoped>
.msg-title {
margin: 10px 0;
text-align: center;
font-size: 20px;
}
.msg {
width: 600px;
height: 240px;
border: 1px solid #ccc;
overflow-y: auto;
}
.cmd {
margin-top: 10px;
width: 600px;
display: flex;
}
</style>

View File

@ -22,7 +22,7 @@
.ct { .ct {
width: 500px; width: 500px;
margin: auto; margin: auto;
margin-top: 200px; margin-top: 20vh;
.title { .title {
text-align: center; text-align: center;

View File

@ -1,7 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { reactive, ref, watch } from 'vue' import { reactive, ref, watch, onMounted } from 'vue'
import Header from '@/components/Header/index.vue' import Header from '@/components/Header/index.vue'
import commuse from './components/commuse.vue'
import { Message } from '@arco-design/web-vue' import { Message } from '@arco-design/web-vue'
import { useAppStore, useUserStore } from '@/store' import { useAppStore, useUserStore } from '@/store'
const appStore = useAppStore() const appStore = useAppStore()
@ -15,6 +14,10 @@ function login() {
} }
} }
function close() {
appStore.socketClose()
}
function send() { function send() {
const send_msg = { const send_msg = {
type: 'CMD', type: 'CMD',
@ -23,18 +26,87 @@ function send() {
const send_msg_str = JSON.stringify(send_msg) const send_msg_str = JSON.stringify(send_msg)
appStore.socketSend(send_msg_str) appStore.socketSend(send_msg_str)
} }
const data = ref([
{
label: '系统运行时长',
value: '14小时8分钟44秒',
},
{
label: '在线玩家数量',
value: '0',
},
{
label: 'Tick耗时',
value: '999',
},
{
label: '内存占用',
value: '66719376/101498880',
}
])
const data2 = ref([
{
label: '服务器名称',
value: 'ttt',
},
{
label: '系统',
value: 'win10',
},
{
label: '服务器地址',
value: '0.0.0.0',
},
{
label: 'JAVA版本',
value: '17.0',
},
{
label: '插件版本',
value: '1.0',
}
])
onMounted(() => {
const WSS = localStorage.getItem("WSS")
if (WSS) {
wss.value = WSS
}
})
watch(
() => appStore.tick,
() => {
data.value = appStore.tick
},
{
immediate: true,
},
)
watch(
() => appStore.baseData,
() => {
data2.value = appStore.baseData
},
{
immediate: true,
},
)
</script> </script>
<template> <template>
<Header /> <div class="login" v-if="!appStore.isLogin">
<div class="cont"> <div class="title text-slate-900 dark:text-slate-100">
<div class="title">
启用插件 启用插件
<a <!-- <a href="https://github.com/liujiaqi7998/GrasscuttersWebDashboard" target="_blank"
href="https://github.com/liujiaqi7998/GrasscuttersWebDashboard" rel="noopener noreferrer">GrasscuttersWebDashboard</a> -->
target="_blank" <a-link href="https://github.com/liujiaqi7998/GrasscuttersWebDashboard" target="_blank" icon>
rel="noopener noreferrer" GrasscuttersWebDashboard</a-link>
>GrasscuttersWebDashboard</a </div>
> <div class="title text-slate-900 dark:text-slate-100">
连接失败先去直接先https://ip
访问一下 然后 高级 继续访问
</div> </div>
<div> <div>
<div class="commuse-item"> <div class="commuse-item">
@ -45,18 +117,26 @@ function send() {
<a-button type="outline" @click="login"> 登录 </a-button> <a-button type="outline" @click="login"> 登录 </a-button>
</div> </div>
</div> </div>
</div>
<div v-else>
<div class="inf">
<a-descriptions style="margin-top: 20px" :data="data" size="large" title="运行状态" :column="1" />
<a-descriptions style="margin-top: 20px" :data="data2" size="large" title="系统信息 " :column="1" />
</div>
<div> <div>
<a-button type="outline" @click="send"> send </a-button> <a-button type="outline" @click="close"> 退出 </a-button>
</div> </div>
</div> </div>
</template> </template>
<style lang="less" scoped> <style lang="less" scoped>
.cont { .login {
width: 800px; width: 600px;
margin: auto; margin-top: 10vh;
margin-top: 40px;
.title { .title {
text-align: center; text-align: center;
line-height: 40px;
} }
.commuse-item { .commuse-item {
display: flex; display: flex;
@ -64,7 +144,7 @@ function send() {
color: #000; color: #000;
margin: 18px 0; margin: 18px 0;
> div { >div {
&:nth-child(1) { &:nth-child(1) {
width: 150px; width: 150px;
text-align: right; text-align: right;

View File

@ -12,7 +12,7 @@ var uid = ref('')
var holyrelicnamevalue = ref('') var holyrelicnamevalue = ref('')
var holyrelicnmainvalue = ref('') var holyrelicnmainvalue = ref('')
var grade = ref('0') var grade = ref(0)
var selectedValue = ref() var selectedValue = ref()
var num = ref() var num = ref()

View File

@ -0,0 +1,51 @@
<template>
<div class="personnel">
<a-table :columns="columns" :data="data">
<template #optional="{ record }">
<a-button @click="$modal.info({ title: 'Name', content: record.name })">操作</a-button>
</template>
</a-table>
</div>
</template>
<script setup lang="ts">
import { reactive, ref, computed } from 'vue'
const data = ref([])
const columns = [
{
title: 'UID',
dataIndex: 'uid',
},
{
title: '昵称',
dataIndex: 'nickname',
},
{
title: '签名',
dataIndex: 'signature',
},
{
title: '等级',
dataIndex: 'Level',
},
{
title: '坐标',
dataIndex: 'pos',
},
{
title: '场景',
dataIndex: 'SceneId',
},
{
title: '操作',
dataIndex: 'Optional',
},
];
</script>
<style lang="less" scoped>
.personnel {
margin-top: 20px;
width: 700px;
}
</style>

View File

@ -1,6 +1,16 @@
<script setup lang="ts"> <script setup lang="ts">
import { reactive, ref } from 'vue' import { reactive, ref, onMounted } from 'vue'
import {
IconMenuFold,
IconMenuUnfold,
IconApps,
IconBug,
IconBulb,
} from '@arco-design/web-vue/es/icon';
import router from "@/router/index"
import { useAppStore } from '@/store'
import { throwStatement } from '@babel/types';
const appStore = useAppStore()
const datav = reactive([ const datav = reactive([
{ name: '常用', path: "/start/commuse" }, { name: '常用', path: "/start/commuse" },
{ name: '圣遗物', path: "/start/holyrelic" }, { name: '圣遗物', path: "/start/holyrelic" },
@ -11,29 +21,77 @@ const datav = reactive([
{ name: '角色属性', path: "/start/role" }, { name: '角色属性', path: "/start/role" },
]) ])
const datav2 = reactive([
{ name: 'WSS连接', path: "/start/login" },
{ name: '控制台', path: "/start/consoled" },
{ name: '在线人员', path: "/start/personnel" },
])
const GMTitle = ref("GM控制面板")
function topath(path: string) {
router.push({ path: path })
}
const selectedKey = ref([""])
onMounted(() => {
selectedKey.value = [router.currentRoute.value.fullPath]
})
watch(
() => appStore.isLogin,
() => {
const isLogin: boolean = appStore.isLogin
if (isLogin) {
GMTitle.value = "GM控制面板-已登录"
} else {
GMTitle.value = "GM控制面板"
}
},
{
immediate: true,
},
)
watch(
() => router.currentRoute.value.path,
(newValue, oldValue) => {
selectedKey.value = [newValue]
},
{ immediate: true }
)
</script> </script>
<template> <template>
<div class="nav "> <div class="nav ">
<div v-for="(item, index) in datav" :key="index"> <a-menu showCollapseButton :default-open-keys="['0', '1']" :selected-keys="selectedKey">
<router-link :to="item.path" class="mr-3 flex-none w-[2.0625rem] md:w-auto leading-6 dark:text-slate-200"> <a-sub-menu key="0">
<template #icon>
<IconApps></IconApps>
</template>
<template #title>控制台代码生成 </template>
<a-menu-item v-for="(item, index) in datav" :key="item.path" @click="topath(item.path)">
{{ item.name }} {{ item.name }}
</router-link> </a-menu-item>
</div> </a-sub-menu>
<a-sub-menu key="1">
<template #icon>
<IconBug></IconBug>
</template>
<template #title>{{ GMTitle }}</template>
<a-menu-item v-for="(item, index) in datav2" :key="item.path" @click="topath(item.path)">
{{ item.name }}
</a-menu-item>
</a-sub-menu>
</a-menu>
</div> </div>
</template> </template>
<style lang="less" scoped> <style lang="less" scoped>
.nav { .nav {
height: 600px; height: calc(100vh - 57px);
width: 120px;
border-right: 1px solid #d1d1cb;
padding-right: 30px;
>div { >div {
margin: 10px 0; height: 100%;
cursor: pointer;
font-size: 18px;
color: #000;
} }
} }
</style> </style>

View File

@ -3,8 +3,8 @@
import { reactive, ref, computed } from 'vue' import { reactive, ref, computed } from 'vue'
import { useClipboard } from '@vueuse/core' import { useClipboard } from '@vueuse/core'
import thing from './json/thing.json' import thing from './json/thing.json'
const { text, isSupported, copy } = useClipboard()
import { Message } from '@arco-design/web-vue' import { Message } from '@arco-design/web-vue'
const { text, isSupported, copy } = useClipboard()
var value2 = ref() var value2 = ref()
var value3 = ref('/give') var value3 = ref('/give')
@ -53,7 +53,6 @@ function copyvalue() {
v-model="value2" v-model="value2"
:options="options" :options="options"
placeholder="请输入物品" placeholder="请输入物品"
:virtual-list-props="{ height: 200 }"
filterable filterable
/> />
</div> </div>

View File

@ -11,7 +11,7 @@ import commuse from './components/commuse.vue'
<div class="cont"> <div class="cont">
<startnav /> <startnav />
<div class="selectcom"> <div class="selectcom">
<commuse /> <router-view></router-view>
</div> </div>
</div> </div>

View File

@ -1,25 +1,9 @@
.cont { .cont {
width: 800px; height: 100%;
overflow: auto;
margin: auto; margin: auto;
display: flex; display: flex;
.selectcom{
.nav { margin: 0 auto ;
height: 600px;
width: 120px;
border-right: 1px solid #777777;
padding-right: 30px;
>div {
margin: 10px 0;
cursor: pointer;
font-size: 18px;
color: #000;
} }
}
.selectcom {
width: 660px;
}
} }

View File

@ -1,14 +1,67 @@
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router' import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
import routes from 'virtual:generated-pages' // import routes from 'virtual:generated-pages'
// console.log(routes,'打印生成自动生成的路由') // console.log(routes,'打印生成自动生成的路由')
routes.push({ let constantRoutes = [
{
path: '/', path: '/',
redirect: '/login', component: () => import('@/pages/index.vue'),
}) },
{
path: '/start',
component: () => import('@/pages/start/index.vue'),
children: [
{
path: "/start/commuse",
component: () => import('@/pages/start/components/commuse.vue'),
},
{
path: "/start/thing",
component: () => import('@/pages/start/components/thing.vue'),
},
{
path: "/start/role",
component: () => import('@/pages/start/components/role.vue'),
},
{
path: "/start/holyrelic",
component: () => import('@/pages/start/components/holyrelic.vue'),
},
{
path: "/start/monster",
component: () => import('@/pages/start/components/monster.vue'),
},
{
path: "/start/other",
component: () => import('@/pages/start/components/other.vue'),
},
{
path: "/start/weapon",
component: () => import('@/pages/start/components/weapon.vue'),
},
{
path: "/start/login",
component: () => import('@/pages/login/index.vue'),
},
{
path: "/start/consoled",
component: () => import('@/pages/consoled/index.vue'),
},
{
path: "/start/personnel",
component: () => import('@/pages/start/components/personnel.vue'),
}
]
}
]
//导入生成的路由数据 //导入生成的路由数据
const router = createRouter({ const router = createRouter({
history: createWebHashHistory(), history: createWebHashHistory(),
routes, routes: constantRoutes,
}) })
export default router export default router

View File

@ -1,6 +1,4 @@
export default [ export default [
{
path: '/',
component: () => import('@/pages/login/index.vue'),
},
] ]

View File

@ -3,16 +3,61 @@ import piniaStore from '@/store/index'
import { AppState } from './types'; import { AppState } from './types';
import { useWebSocket } from '@vueuse/core' import { useWebSocket } from '@vueuse/core'
import { watch } from 'vue' import { watch } from 'vue'
import { Message } from '@arco-design/web-vue'
export const useAppStore = defineStore( export const useAppStore = defineStore(
// 唯一ID // 唯一ID
'app', 'app',
{ {
state: () => ({ state: () => ({
title: "FastVue3,一个快速开箱即用的Vue3+Vite模板", title: "",
h1: 'Vue3+Vite2.x+Ts+Pinia大厂开发必备', h1: '',
theme: 'dark', theme: 'dark',
send: (data: string | ArrayBuffer | Blob, useBuffer?: boolean | undefined) => { } isLogin: false,
tick: [
{
label: '系统运行时长',
value: '/',
},
{
label: '在线玩家数量',
value: '/',
},
{
label: 'Tick耗时',
value: '/',
},
{
label: '内存占用',
value: '/',
}
],
baseData: [
{
label: '服务器名称',
value: '/',
},
{
label: '系统',
value: '/',
},
{
label: '服务器地址',
value: '/',
},
{
label: 'JAVA版本',
value: '/',
},
{
label: '插件版本',
value: '/',
}
],
send: (data: string | ArrayBuffer | Blob, useBuffer?: boolean | undefined) => { },
close: () => { },
open: () => { },
mesgData: [""]
}), }),
getters: {}, getters: {},
actions: { actions: {
@ -37,17 +82,135 @@ export const useAppStore = defineStore(
} }
}, },
socketConnect(wss: string) { socketConnect(wss: string) {
const { status, data, send, open, close } = useWebSocket(wss)
const { status, data, send, open, close } = useWebSocket(wss, {
autoReconnect: {
retries: 2,
delay: 1000,
onFailed() {
Message.error('连接失败,请去GrasscuttersWebDashboard查看处理方法')
localStorage.removeItem("WSS")
},
},
// heartbeat: {
// message: 'ping',
// interval: 10000,
// },
onConnected: (ws) => {
this.isLogin = true
console.log("已登录");
ws.send("{\"type\":\"State\",\"data\":\"0\"}");
ws.send("{\"type\":\"Player\",\"data\":\"0\"}");
}
})
localStorage.setItem("WSS", wss)
watch( watch(
data, data,
(v) => { (parse) => {
console.log(v); parse = JSON.parse(parse)
switch (parse.eventName) {
case "tick":
const data = []
for (const key in parse.data) {
if (Object.prototype.hasOwnProperty.call(parse.data, key)) {
var element = parse.data[key];
var label = ""
if (key == "getAllocatedMemory") {
label = "系统内存"
element = (element/1024/1024).toFixed(2) + "M"
}
if (key == "playerCount") {
label = "在线玩家数量"
}
if (key == "serverUptime") {
label = "系统运行时长"
element = this.formatSecToStr(Math.ceil(element / 1000))
}
if (key == "tickTimeElapsed") {
label = "Tick耗时"
}
if (key == "getFreeMemory") {
label = "已用内存"
element = (element/1024/1024).toFixed(2) + "M"
}
data.push({
label: label,
value: element,
})
}
}
this.tick = data
break;
case "BaseData":
const data2 = []
for (const key in parse.data) {
if (Object.prototype.hasOwnProperty.call(parse.data, key)) {
var element = parse.data[key];
var label = ""
if (key == "GrVersion") {
label = "插件版本"
}
if (key == "IP") {
label = "服务器地址"
}
if (key == "JavaVersion") {
label = "JAVA版本"
}
if (key == "SystemVersion") {
label = "系统"
}
if (key == "ServerName") {
label = "服务名称"
}
data2.push({
label: label,
value: element,
})
}
}
this.baseData = data2
break;
case "cmd_msg":
this.mesgData.push(parse.data)
break;
default:
break;
}
} }
) )
this.send = send this.send = send
this.close = close
this.open = open
}, },
socketSend(str: string) { socketSend(str: string) {
this.send(str) this.send(str)
},
socketClose() {
this.isLogin = false
this.close()
localStorage.removeItem("WSS")
},
formatSecToStr(seconds: number) {
let daySec = 24 * 60 * 60;
let hourSec = 60 * 60;
let minuteSec = 60;
let dd = Math.floor(seconds / daySec);
let hh = Math.floor((seconds % daySec) / hourSec);
let mm = Math.floor((seconds % hourSec) / minuteSec);
let ss = seconds % minuteSec;
if (dd > 0) {
return dd + "天" + hh + "小时" + mm + "分钟" + ss + "秒";
} else if (hh > 0) {
return hh + "小时" + mm + "分钟" + ss + "秒";
} else if (mm > 0) {
return mm + "分钟" + ss + "秒";
} else {
return ss + "秒";
}
} }
} }
} }

View File

@ -9,4 +9,5 @@ export interface AppState {
menuWidth: number; menuWidth: number;
globalSettings: boolean; globalSettings: boolean;
[key: string]: unknown; [key: string]: unknown;
mesgData:string[];
} }

View File

@ -113,5 +113,5 @@ export type { AxiosInstance, AxiosResponse };
* @return {Promise} * @return {Promise}
*/ */
// export const login = (params: ILogin): Promise<IResponse> => { // export const login = (params: ILogin): Promise<IResponse> => {
// return axiosInstance.post('user/login', params).then(res => res.data); // return axiosInstance.post('user', params).then(res => res.data);
// }; // };