update
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CodeQL / Analyze (${{ matrix.language }}) (none, javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
CI / CI OK (push) Has been cancelled

This commit is contained in:
hahwu 2025-12-30 11:01:05 +08:00
parent 29aeb8fa29
commit 1dc256452e
13 changed files with 363 additions and 62 deletions

View File

@ -14,3 +14,5 @@ VITE_DEVTOOLS=false
# 是否注入全局loading
VITE_INJECT_APP_LOADING=true
VITE_PUBLIC_SRC_PATH=../../public

View File

@ -1,3 +1,4 @@
{
"workbench.colorTheme": "GitHub Light Colorblind (Beta)"
"workbench.colorTheme":"One Dark Pro",
"editor.fontSize": 14
}

View File

@ -13,9 +13,10 @@ export interface UserLogAssetParam {
StartTime?: number;
EndTime?: number;
ItemId?: number;
PageSize: number;
CurrentPage: number;
PageSize?: number;
CurrentPage?: number;
AppId?: number;
Node?: number;
}
export interface UserLogOrder {
Id: number;

View File

@ -24,6 +24,10 @@ export interface ServerData {
PlayerNum?: number;
Loading?:boolean;
Reload?:boolean;
CpuUsage?: number;
MemUsage?: number;
ClientVersion?: string;
Tags?: string[];
}
export interface NodeData{

View File

@ -15,6 +15,7 @@ export interface UserListParam {
currentPage: number;
StartTime?: number;
EndTime?: number;
Nickname?: string;
}

View File

@ -52,6 +52,7 @@ export interface languageType {
en_US: string;
zh_CN: string;
pt_BR: string;
url?: string;
}

View File

@ -0,0 +1,52 @@
import MergeData from "./MergeData.json";
export function getImageUrl(key: string): string {
if (!key) return "";
// 1. 判断 key 是否为 UI_MergeData_<number> 格式,取出数字;同时支持直接传入数字字符串或数字
let id: string | null = null;
const m = /^UI_MergeData_(\d+)$/.exec(key);
if (m) id = m[1] ?? "";
else if (/^\d+$/.test(key)) id = key;
else id = String(key);
// 2. 从 MergeData 中取出 id 对应的 Icon
const item = (MergeData as any)[id];
const icon = item && typeof item.Icon === 'string' ? item.Icon : '';
if (!icon) return '';
// 3. 在 Node 环境中遍历指定文件夹寻找包含 Icon 名称的文件并返回绝对路径
// 如果不在 Node 环境(例如浏览器),则回退返回 icon 字符串
try {
// @ts-ignore
const fs = require('fs');
// @ts-ignore
const path = require('path');
const SEARCH_DIR = 'D:\\Github\\AplusB_Pet_nation\\Assets\\GameMain\\UI\\UISprites\\MergeObj';
function findFile(dir: string): string | null {
let entries: string[] = [];
try { entries = fs.readdirSync(dir); } catch (e) { return null; }
for (const name of entries) {
const full = path.join(dir, name);
let stat;
try { stat = fs.statSync(full); } catch (e) { continue; }
if (stat.isDirectory()) {
const res = findFile(full);
if (res) return res;
} else {
if (name.indexOf(icon) !== -1) return path.resolve(full);
}
}
return null;
}
const found = findFile(SEARCH_DIR);
if (found) return found;
} catch (e) {
// 非 Node 环境或访问失败,忽略并回退
}
// 回退:返回 icon 名称(供前端拼接资源路径使用)
return icon;
}

View File

@ -1,10 +1,10 @@
<script lang="ts" setup>
import { Button, Tag, notification, Modal } from 'ant-design-vue';
import { Button, Tag, notification, Modal, Progress } from 'ant-design-vue';
import type { ServerData } from '#/api/core/server';
import {restartServer, getServerListApi, reloadServer} from '#/api/core/server';
import AddServerModal from './addServer.vue'
import dayjs from 'dayjs';
import EditServer from './editServer.vue';
import { useVbenModal } from '@vben/common-ui'
defineOptions({
name: 'AppList',
@ -16,7 +16,13 @@ interface Props {
withDefaults(defineProps<Props>(),{
items: () => [],
})
const [editServerM, editServerApi] = useVbenModal({
connectedComponent: EditServer,
});
async function editServer(Server: ServerData) {
editServerApi.setData(Server);
editServerApi.open();
}
function getColor(status: number) {
switch (status){
@ -29,6 +35,23 @@ function getColor(status: number) {
}
}
function getCpuColor(usage: number) {
if (usage < 50) {
return 'green';
} else if (usage < 80) {
return 'orange';
} else {
return 'red';
}
}
function formatMemUsage(usage: number) {
if (usage < 1024) {
return usage + ' MB';
}
return (usage/1024).toFixed(2) + ' GB';
}
async function restart(Server: ServerData) {
try{
@ -96,31 +119,27 @@ function confirmUpdate(Server: ServerData) {
<!-- <------------------- Add your code here ------------------->
<template>
<editServerM></editServerM>
<AddServerModal></AddServerModal>
<div v-for="item in items" :key="item.ServerId">
<div class="card-box p-4 py-6 md:mt-4">
<div class="flex justify-between h-2 border-foreground/10">
<div class="flex flex-col justify-center md:mt-0 lg:w-1/12">
<h2 class="text-md font-semibold md:text-ml text-center">
<span>{{ item.ServerId }}</span>
</h2>
</div>
<div class="flex flex-col justify-center md:mt-0 lg:w-1/12">
<div class="flex justify-between h-4 border-foreground/10">
<div @click="editServer(item)" class="flex flex-col justify-center md:mt-0 lg:w-1/12">
<h1 class="text-md font-semibold md:text-ml text-center">
<span>{{ item.ServerName }}</span>
</h1>
</div>
<div class="flex flex-col justify-center md:mt-0 lg:w-1/12">
<h1 class="text-md font-semibold md:text-ml text-center">
<Tag :color="getColor(item.Status || 0)">
{{ item.Status == 0 ? 'Inactive' : item.Status == 1 ? 'Active' : 'Unknown' }}
</Tag>
<h1 class="text-md font-semibold md:text-ml text-center">
<Tag color="red">LOG</Tag>
<Tag v-for="tag in item.Tags" :key="tag" color="blue">{{ tag }}</Tag>
</h1>
</div>
<div class="flex flex-col justify-center md:mt-0 lg:w-1/12">
<h1 class="text-md font-semibold md:text-ml text-center">
<span>{{ dayjs((item.StartTime ?? 0) * 1000).format('YYYY-MM-DD HH:mm:ss') }}</span>
<Tag :color="getColor(item.Status || 0)">
{{ item.Status == 0 ? 'Inactive' : item.Status == 1 ? 'Running' : 'Unknown' }}
</Tag>
</h1>
</div>
<div class="flex flex-col justify-center md:mt-0 lg:w-1/12">
@ -129,8 +148,21 @@ function confirmUpdate(Server: ServerData) {
</h1>
</div>
<div class="flex flex-col justify-center md:mt-0 lg:w-1/12">
<h1 class="text-md font-semibold md:text-ml text-center">
<span>{{ dayjs((item.OpenServerTime ?? 0) * 1000).format('YYYY-MM-DD HH:mm:ss') }}</span>
<Progress :percent="item.CpuUsage || 0" :stroke-color="getCpuColor(item.CpuUsage || 0)"/>
</div>
<div class="flex flex-col justify-center md:mt-0 lg:w-1/12">
<h1 class="text-md font-semibold md:text-ml text-center">
<span>{{formatMemUsage(item.MemUsage||0)}}</span>
</h1>
</div>
<div class="flex flex-col justify-center md:mt-0 lg:w-1/12">
<h1 class="text-md font-semibold md:text-ml text-center">
<span>32ms</span>
</h1>
</div>
<div class="flex flex-col justify-center md:mt-0 lg:w-1/12">
<h1 class="text-md font-semibold md:text-ml text-center">
<span>1.0.0</span>
</h1>
</div>
<div class="flex flex-col justify-center md:mt-0 lg:w-1/12">
@ -138,11 +170,11 @@ function confirmUpdate(Server: ServerData) {
<Button @click=confirmUpdate(item) type="primary" :loading="item.Loading">Restart</Button>
</h1>
</div>
<div class="flex flex-col justify-center md:mt-0 lg:w-1/12">
<!-- <div class="flex flex-col justify-center md:mt-0 lg:w-1/12">
<h1 class="text-md font-semibold md:text-ml text-center">
<Button @click=reload(item) type="primary" :loading="item.Reload">Reload</Button>
</h1>
</div>
</div> -->
</div>
</div>
</div>

View File

@ -0,0 +1,188 @@
<script lang="ts" setup>
import { addServer } from '#/api/core/server';
import { useVbenModal } from '@vben/common-ui';
import { useVbenForm } from '#/adapter/form';
import dayjs from 'dayjs';
const date = dayjs();
const [Form, FormApi] = useVbenForm({
//
commonConfig: {
//
componentProps: {
class: 'w-full',
},
},
// 使 tailwindcss grid
//
// labelinputvertical
layout: 'horizontal',
// labelinput
schema: [
{
component: 'Input',
disabled: false,
defaultValue: 0,
componentProps: {
placeholder: '1',
},
formItemClass: 'col-span-2',
fieldName: 'AppId',
label: 'AppId',
},
{
component: 'Input',
disabled: false,
componentProps: {
placeholder: '2',
typeof: 'number',
},
rules: "required",
fieldName: 'ServerId',
label: 'ServerId',
formItemClass: 'col-span-2',
},
{
component: 'Input',
defaultValue: '',
componentProps: {
placeholder: 'node1',
},
rules: "required",
fieldName: 'ServerName',
label: 'ServerName',
formItemClass: 'col-span-2',
},
{
component: 'Input',
defaultValue: '',
componentProps: {
placeholder: 'node1',
},
rules: "required",
fieldName: 'Tags',
label: 'Tags',
formItemClass: 'col-span-2',
},
{
component: 'Input',
defaultValue: '',
componentProps: {
placeholder: 'node1',
},
rules: "required",
fieldName: 'version',
label: 'version',
formItemClass: 'col-span-2',
},
{
component: 'NumberPicker',
defaultValue: '',
componentProps: {
placeholder: 'node1',
},
rules: "required",
fieldName: 'max_online',
label: 'max_online',
formItemClass: 'col-span-2',
},
],
showDefaultActions: false,
// 321
wrapperClass: 'grid-cols-2',
});
const [Modal, modalApi] = useVbenModal({
confirmText: '提交',
onOpenChange: () => {
const modalData = modalApi.getData();
const tag = modalData.Tags ? modalData.Tags.join(',') : '';
FormApi.updateSchema([
{
component: 'Input',
disabled: true,
defaultValue: modalData.AppId ?? 0,
componentProps: {
placeholder: '1',
},
formItemClass: 'col-span-2',
fieldName: 'AppId',
label: 'AppId',
},
{
component: 'Input',
disabled: true,
defaultValue: modalData.ServerId ?? 0,
componentProps: {
placeholder: '2',
typeof: 'number',
},
fieldName: 'ServerId',
label: 'ServerId',
formItemClass: 'col-span-2',
},
{
component: 'Input',
defaultValue: modalData.ServerName ?? '',
componentProps: {
placeholder: 'node1',
},
rules: "required",
fieldName: 'ServerName',
label: 'ServerName',
formItemClass: 'col-span-2',
},
{
component: 'Input',
defaultValue: tag ?? '',
componentProps: {
placeholder: 'node1',
},
rules: "required",
fieldName: 'Tags',
label: 'Tags',
formItemClass: 'col-span-2',
},
{
component: 'Input',
defaultValue: modalData.ClientVersion ?? '',
componentProps: {
placeholder: 'node1',
},
rules: "required",
fieldName: 'version',
label: 'client-version',
formItemClass: 'col-span-2',
},
])
},
onConfirm: async () => {
//
const values = await FormApi.getValues();
const date = dayjs(values.datePicker).format('YYYY-MM-DD');
const time = dayjs(values.timePicker).format('HH:mm:ss');
const dateTime = dayjs(`${date} ${time}`).valueOf() / 1000;
const modalData = modalApi.getData();
const ServerId = parseInt(values.ServerId, 10);
await addServer(modalData.AppId, ServerId, values.ServerName, values.Status, dateTime);
modalApi.close();
},
});
defineOptions({
name: 'AddServerModal',
})
defineExpose({
FormApi
})
</script>
<template>
<Modal title="修改服务器信息" :width="800">
<Form />
</Modal>
</template>

View File

@ -74,6 +74,7 @@ const [addServerM, addServerApi] = useVbenModal({
},
});
const serverList = [
{
AppId: 1,
@ -135,6 +136,8 @@ async function addServer() {
async function update() {
reload.value = true;
const Value = await BaseFormApi.getValues();
@ -163,64 +166,64 @@ async function update() {
<template>
<Page>
<addServerM class="w-[50%]" @hidden="update"/>
<editServerM class="w-[50%]" @hidden="update"/>
<Card class="mb-5" title="服务器操作">
<BaseForm />
<Space>
<Button @click="addServer">新增</Button>
<Button type="primary" @click="addServer">新增</Button>
<Button type="primary" @click="update" :loading="reload"> 更新 </Button>
<Button> 删除 </Button>
<Button danger> 更新配置 </Button>
<Button type="primary"> 更新配置 </Button>
</Space>
</Card>
<div class="p-5">
<div class="card-box p-4 py-6 flex justify-between h-12">
<div class="flex flex-col justify-center md:mt-0 lg:w-1/12">
<h2 class="text-md font-semibold md:text-ml text-center">
<span>ServerId</span>
</h2>
<h1 class="text-md font-semibold md:text-ml text-center">
<span>SERVER INFO</span>
</h1>
</div>
<div class="flex flex-col justify-center md:mt-0 lg:w-1/12">
<h1 class="text-md font-semibold md:text-ml text-center">
<span>ServerName</span>
<span>TAGS</span>
</h1>
</div>
<div class="flex flex-col justify-center md:mt-0 lg:w-1/12">
<h1 class="text-md font-semibold md:text-ml text-center">
<span>Status</span>
<span>STATUS</span>
</h1>
</div>
<div class="flex flex-col justify-center md:mt-0 lg:w-1/12">
<h1 class="text-md font-semibold md:text-ml text-center">
<span>StartTime</span>
<span>PLAYERS</span>
</h1>
</div>
<div class="flex flex-col justify-center md:mt-0 lg:w-1/12">
<h1 class="text-md font-semibold md:text-ml text-center">
<span>PlayerNum</span>
<span>CPU LOAD</span>
</h1>
</div>
<div class="flex flex-col justify-center md:mt-0 lg:w-1/12">
<h1 class="text-md font-semibold md:text-ml text-center">
<span>OpenServerTime</span>
<span>MEMORY</span>
</h1>
</div>
<div class="flex flex-col justify-center md:mt-0 lg:w-1/12">
<h1 class="text-md font-semibold md:text-ml text-center">
<span>Handle</span>
<span>LATENCY</span>
</h1>
</div>
<div class="flex flex-col justify-center md:mt-0 lg:w-1/12">
<h1 class="text-md font-semibold md:text-ml text-center">
<span>Config</span>
<span>CLIENT VERSION</span>
</h1>
</div>
<div class="flex flex-col justify-center md:mt-0 lg:w-1/12">
<h1 class="text-md font-semibold md:text-ml text-center">
<span>RESTART</span>
</h1>
</div>
</div>
<!-- <template>
<AppList :items="ServerList" title="服务器列表"/>
</template> -->
<AppList :items="ServerList" title="服务器列表"/>
</div>
</Page>
</template>

View File

@ -1,6 +1,6 @@
<script lang="ts" setup>
import type { VxeGridProps, VxeGridListeners } from '#/adapter/vxe-table';
import { Button, notification } from 'ant-design-vue';
import { Button, notification,Image } from 'ant-design-vue';
import { Page } from '@vben/common-ui';
import { getLanguageList, exportLanguageFile, saveLanguageList, deleteLanguageItem } from '#/api/core/statistics';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
@ -106,6 +106,12 @@ const gridOptions: VxeGridProps<languageType> = {
columns: [
{ title: 'Id', field: 'Id', width: 100 },
{ editRender: { name: 'input' }, field: 'key', title: 'key', filters: [{ data: "" }], filterRender: { name: "input" }, visible: keyVisible },
{
field: 'imageUrl',
slots: { default: 'image-url' },
title: 'Image',
width: 100,
},
{ editRender: { name: 'input' }, field: 'en_US', title: 'en_US', filters: [{ data: "" }], filterRender: { name: "input" }, visible: en_USVisible },
{
editRender: { name: 'input' },
@ -238,10 +244,17 @@ const gridOptions: VxeGridProps<languageType> = {
} else {
actionVisible = false;
}
let length = response.data ? response.data.length : 0;
GridApi.setGridOptions({
columns: [
{ editRender: { name: 'input' }, field: 'key', title: 'key', filters: [{ data: "" }], filterRender: { name: "input" }, visible: keyVisible },
{
field: 'imageUrl',
slots: { default: 'image-url' },
title: 'Image',
width: 50,
},
{ editRender: { name: 'input' }, field: 'en_US', title: 'en_US', filters: [{ data: "" }], filterRender: { name: "input" }, visible: en_USVisible },
{
editRender: { name: 'input' },
@ -265,7 +278,7 @@ const gridOptions: VxeGridProps<languageType> = {
}],
});
return {
data: response.data || [],
data: newData,
total: response.total || 0,
};
}
@ -417,7 +430,10 @@ function deleteRow(row: languageType) {
<Button type="primary" @click="saveAll" class="mr-2"> {{ $t('page.common.save') }} </Button>
<Button type="primary" @click="exportLang"> {{ $t('page.common.gitCommit') }} </Button>
</template>
<template #image-url="{ row }">
<!-- <Image src="../../../../public/merge/Launcher_A_LV1.png" height="50" width="50" /> -->
<Image v-if="row.url" :src="row.url" height="50" width="50" />
</template>
<template #action="{ row }">
<AccessControl :codes="['super', 'admin']" type="role">
<Button type="primary" @click="deleteRow(row)">删除</Button>
@ -426,5 +442,4 @@ function deleteRow(row: languageType) {
</Grid>
</Page>
</div>
</template>

View File

@ -142,7 +142,7 @@ const [BaseForm] = useVbenForm({
const r = await userGmApi({
Uid: cv.uid,
AppId: cv.AppId,
ServerId: cv.ServerId,
ServerId: cv.Node,
Command: gm,
});
message.success(r.Msg);
@ -265,9 +265,7 @@ const [Modal, modalApi] = useVbenModal({
try {
const r = await getUserlogInfoApi({
Id: data.value.uid,
Event: 'someEvent', // replace 'someEvent' with the actual event
PageSize: 10, // replace 10 with the actual page size
CurrentPage: 1, // replace 1 with the actual current page
Node: data.value.Node,
});
trendItems = [];
let id = 0;

View File

@ -30,6 +30,8 @@ interface RowType {
Energy: string;
LoginTime: number;
Online: string;
Node: number;
Nickname: string;
}
const startDate = dayjs().subtract(7, 'day').startOf('day');
const endDate = dayjs().endOf('day');
@ -69,18 +71,17 @@ const formOptions: VbenFormProps = {
label: 'APP',
},
{
component: 'Select',
defaultValue: 1,
component: 'Input',
componentProps: {
filterOption: true,
options: [
],
placeholder: '请选择',
placeholder: '请输入',
showSearch: true,
},
fieldName: 'ServerId',
label: 'ServerId',
fieldName: 'Uid',
label: 'Uid',
},
{
component: 'Input',
@ -89,11 +90,11 @@ const formOptions: VbenFormProps = {
options: [
],
placeholder: '请选择',
placeholder: '请输入',
showSearch: true,
},
fieldName: 'Uid',
label: 'Uid',
fieldName: 'Nickname',
label: '昵称',
},
{
component: 'DatePicker',
@ -132,7 +133,7 @@ const formOptions: VbenFormProps = {
const gridEvents: VxeGridListeners<RowType> = {
cellClick: async ({ row }) => {
const value = await GridApi.formApi.getValues();
userModalApi.setData({ uid: row.Uid, AppId: value.AppId, ServerId: value.ServerId });
userModalApi.setData({ uid: row.Uid, AppId: value.AppId, Node: row.Node });
userModalApi.open();
state.uid = row.Uid;
},
@ -144,7 +145,8 @@ const gridOptions: VxeGridProps<RowType> = {
{ field: 'Uid', title: 'id' },
{ field: 'UserName', title: '登录名' },
{ field: 'Level', title: '等级', formatter: ({ cellValue }) => `${cellValue}`, sortable: true, sortBy: 'Level' },
{ field: 'Exp', title: '经验', formatter: ({ cellValue }) => `${cellValue} XP`, sortable: true, sortBy: 'Exp' },
{ field: 'Node', title: '节点', },
{ field: 'Nickname', title: '昵称', },
{ field: 'Diamond', title: '钻石', formatter: ({ cellValue }) => `${cellValue} 💎`, sortable: true, sortBy: 'Diamond' },
{ field: 'Star', title: '星星', formatter: ({ cellValue }) => `${cellValue}`, sortable: true, sortBy: 'Star' },
{ field: 'Energy', title: '能量', formatter: ({ cellValue }) => `${cellValue}`, sortable: true, sortBy: 'Energy' },
@ -170,9 +172,10 @@ const gridOptions: VxeGridProps<RowType> = {
ajax: {
query: async ({ page }, formValues) => {
let Id = parseInt(formValues.AppId, 10);
let ServerId = parseInt(formValues.ServerId, 10);
let ServerId = 1;
let Uid = parseInt(formValues.Uid, 10);
let r = await getUserListApi({ Id: Id, ServerId: ServerId, pageSize: page.pageSize, currentPage: page.currentPage, Uid: Uid, StartTime: formValues.StartTime ? Math.floor(new Date(formValues.StartTime).getTime() / 1000) : undefined, EndTime: formValues.EndTime ? Math.floor(new Date(formValues.EndTime).getTime() / 1000) : undefined });
let Nickname = formValues.Nickname ? String(formValues.Nickname) : '';
let r = await getUserListApi({ Id: Id, ServerId: ServerId, pageSize: page.pageSize, currentPage: page.currentPage, Uid: Uid, Nickname:Nickname, StartTime: formValues.StartTime ? Math.floor(new Date(formValues.StartTime).getTime() / 1000) : undefined, EndTime: formValues.EndTime ? Math.floor(new Date(formValues.EndTime).getTime() / 1000) : undefined });
return r;
},
},