: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.stylelint": true
},
"editor.defaultFormatter": "octref.vetur"
"editor.defaultFormatter": "Vue.volar"
},
"i18n-ally.localesPaths": ["src/locales/lang"],
"i18n-ally.keystyle": "nested",

View File

@ -5,7 +5,7 @@
import { title } from 'process'
import { useAppStore } from './store/modules/app'
import { ref, watch } from 'vue'
import { ref, watch,onMounted } from 'vue'
import { darkTheme } from 'naive-ui'
const appStore = useAppStore()
const apptheme = ref()
@ -45,6 +45,13 @@ watch(
immediate: true,
},
)
onMounted(() => {
const WSS = localStorage.getItem("WSS")
if (WSS) {
appStore.socketConnect(WSS)
}
})
</script>
<style>
@ -54,7 +61,7 @@ watch(
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
background-color: var(--color-bg-1);
height: 100vh;
overflow: hidden;
min-height: 100vh;
}
</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']
ACascader: typeof import('@arco-design/web-vue')['Cascader']
ACheckbox: typeof import('@arco-design/web-vue')['Checkbox']
ADescriptions: typeof import('@arco-design/web-vue')['Descriptions']
AInput: typeof import('@arco-design/web-vue')['Input']
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']
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']
Footer: typeof import('./components/footer/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 { useAppStore, useUserStore } from '@/store';
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 darkThemeMq = window.matchMedia('(prefers-color-scheme: dark)')
@ -40,6 +39,9 @@ const ThemeChange = (val: string | number | boolean) => {
document.documentElement.classList.remove('dark')
}
}
const LoginHS = ref(true)
</script>
<template>
@ -60,12 +62,12 @@ const ThemeChange = (val: string | number | boolean) => {
Grasscutter-docs-zh_CN
</a>
</li>
<li>
<router-link
to="/login"
class="hover:text-sky-500 dark:hover:text-sky-400"
>Login</router-link>
<li v-if="LoginHS">
<router-link to="/start/login" class="hover:text-sky-500 dark:hover:text-sky-400">
Console
</router-link>
</li>
</ul>
</nav>
<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 {
width: 500px;
margin: auto;
margin-top: 200px;
margin-top: 20vh;
.title {
text-align: center;

View File

@ -1,7 +1,6 @@
<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 commuse from './components/commuse.vue'
import { Message } from '@arco-design/web-vue'
import { useAppStore, useUserStore } from '@/store'
const appStore = useAppStore()
@ -15,6 +14,10 @@ function login() {
}
}
function close() {
appStore.socketClose()
}
function send() {
const send_msg = {
type: 'CMD',
@ -23,18 +26,87 @@ function send() {
const send_msg_str = JSON.stringify(send_msg)
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>
<template>
<Header />
<div class="cont">
<div class="title">
<div class="login" v-if="!appStore.isLogin">
<div class="title text-slate-900 dark:text-slate-100">
启用插件
<a
href="https://github.com/liujiaqi7998/GrasscuttersWebDashboard"
target="_blank"
rel="noopener noreferrer"
>GrasscuttersWebDashboard</a
>
<!-- <a href="https://github.com/liujiaqi7998/GrasscuttersWebDashboard" target="_blank"
rel="noopener noreferrer">GrasscuttersWebDashboard</a> -->
<a-link href="https://github.com/liujiaqi7998/GrasscuttersWebDashboard" target="_blank" icon>
GrasscuttersWebDashboard</a-link>
</div>
<div class="title text-slate-900 dark:text-slate-100">
连接失败先去直接先https://ip
访问一下 然后 高级 继续访问
</div>
<div>
<div class="commuse-item">
@ -45,18 +117,26 @@ function send() {
<a-button type="outline" @click="login"> 登录 </a-button>
</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>
<a-button type="outline" @click="send"> send </a-button>
<a-button type="outline" @click="close"> 退出 </a-button>
</div>
</div>
</template>
<style lang="less" scoped>
.cont {
width: 800px;
margin: auto;
margin-top: 40px;
.login {
width: 600px;
margin-top: 10vh;
.title {
text-align: center;
line-height: 40px;
}
.commuse-item {
display: flex;
@ -64,7 +144,7 @@ function send() {
color: #000;
margin: 18px 0;
> div {
>div {
&:nth-child(1) {
width: 150px;
text-align: right;

View File

@ -12,7 +12,7 @@ var uid = ref('')
var holyrelicnamevalue = ref('')
var holyrelicnmainvalue = ref('')
var grade = ref('0')
var grade = ref(0)
var selectedValue = 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">
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([
{ name: '常用', path: "/start/commuse" },
{ name: '圣遗物', path: "/start/holyrelic" },
@ -11,29 +21,77 @@ const datav = reactive([
{ 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>
<template>
<div class="nav ">
<div v-for="(item, index) in datav" :key="index">
<router-link :to="item.path" class="mr-3 flex-none w-[2.0625rem] md:w-auto leading-6 dark:text-slate-200">
<a-menu showCollapseButton :default-open-keys="['0', '1']" :selected-keys="selectedKey">
<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 }}
</router-link>
</div>
</a-menu-item>
</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>
</template>
<style lang="less" scoped>
.nav {
height: 600px;
width: 120px;
border-right: 1px solid #d1d1cb;
padding-right: 30px;
height: calc(100vh - 57px);
>div {
margin: 10px 0;
cursor: pointer;
font-size: 18px;
color: #000;
height: 100%;
}
}
</style>

View File

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

View File

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

View File

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

View File

@ -1,14 +1,67 @@
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
import routes from 'virtual:generated-pages'
// import routes from 'virtual:generated-pages'
// console.log(routes,'打印生成自动生成的路由')
routes.push({
let constantRoutes = [
{
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({
history: createWebHashHistory(),
routes,
routes: constantRoutes,
})
export default router

View File

@ -1,6 +1,4 @@
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 { useWebSocket } from '@vueuse/core'
import { watch } from 'vue'
import { Message } from '@arco-design/web-vue'
export const useAppStore = defineStore(
// 唯一ID
'app',
{
state: () => ({
title: "FastVue3,一个快速开箱即用的Vue3+Vite模板",
h1: 'Vue3+Vite2.x+Ts+Pinia大厂开发必备',
title: "",
h1: '',
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: {},
actions: {
@ -37,17 +82,135 @@ export const useAppStore = defineStore(
}
},
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(
data,
(v) => {
console.log(v);
(parse) => {
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.close = close
this.open = open
},
socketSend(str: string) {
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;
globalSettings: boolean;
[key: string]: unknown;
mesgData:string[];
}

View File

@ -113,5 +113,5 @@ export type { AxiosInstance, AxiosResponse };
* @return {Promise}
*/
// 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);
// };