版本更新
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-06-26 14:40:45 +08:00
parent 707b2e2013
commit 94ef1448d9
11 changed files with 2375 additions and 961 deletions

3
apps/web-antd/env.d.ts vendored Normal file
View File

@ -0,0 +1,3 @@
declare module "*.js";
declare module 'vuejs-heatmap';

View File

@ -42,8 +42,10 @@
"@vben/utils": "workspace:*", "@vben/utils": "workspace:*",
"@vueuse/core": "catalog:", "@vueuse/core": "catalog:",
"ant-design-vue": "catalog:", "ant-design-vue": "catalog:",
"cal-heatmap": "^4.2.4",
"dayjs": "catalog:", "dayjs": "catalog:",
"pinia": "catalog:", "pinia": "catalog:",
"prettier-eslint": "^16.4.2",
"vue": "catalog:", "vue": "catalog:",
"vue-router": "catalog:", "vue-router": "catalog:",
"vxe-table": "^4.6.25" "vxe-table": "^4.6.25"

View File

@ -24,7 +24,10 @@ export interface UserLogOrder{
Diff: number, Diff: number,
ChessId: string, ChessId: string,
} }
export interface heatType {
date :string;
value : number;
}
export interface UserLogInfo{ export interface UserLogInfo{
AreaId : number, AreaId : number,
Charge:number, Charge:number,
@ -39,6 +42,7 @@ export interface UserLogInfo{
TodayCumulative: number, TodayCumulative: number,
Bonus?: number, Bonus?: number,
Order:UserLogOrder[], Order:UserLogOrder[],
Heatmap?: heatType[],
} }

View File

@ -14,6 +14,8 @@ export interface UserListParam {
pageSize: number; pageSize: number;
currentPage: number; currentPage: number;
} }
/** /**
* *
*/ */
@ -34,3 +36,4 @@ export async function userGmApi(data : object) {

View File

@ -0,0 +1,144 @@
<script setup lang="ts">
import { getUserlogEventApi } from '#/api/core/log';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { inject } from 'vue';
import { globalState } from '#/store/globalState';
import { $t } from '#/locales';
import type { VxeGridProps } from '#/adapter/vxe-table';
import type { VbenFormProps } from '#/adapter/form';
import { Page } from '@vben/common-ui';
import dayjs from 'dayjs';
const state = inject('globalState', globalState);
const date = dayjs();
const startDate = dayjs().startOf('day');
interface RowType {
Uid: string;
Event: string;
Param: string;
Timestamp: string;
}
const formOptions: VbenFormProps = {
//
collapsed: false,
schema: [
{
component: 'Input',
componentProps: {
placeholder: 'Uid',
},
defaultValue: state.uid,
fieldName: 'Uid',
label: 'Uid',
},
{
component: 'Input',
componentProps: {
placeholder: 'Event',
},
defaultValue: state.Event,
fieldName: 'Event',
label: '事件类型',
},
{
component: 'DatePicker',
defaultValue: startDate,
componentProps: {
format: 'YYYY-MM-DD',
},
fieldName: 'StartTime',
label: '开始时间',
formItemClass: 'col-start-1',
},
{
component: 'TimePicker',
componentProps: {
placeholder: '时间',
},
fieldName: 'StartTime',
label: '--',
},
{
component: 'DatePicker',
defaultValue: date,
componentProps: {
format: 'YYYY-MM-DD',
},
fieldName: 'EndTime',
label: '结束时间',
},
{
component: 'TimePicker',
componentProps: {
placeholder: '时间',
},
fieldName: 'EndTime',
label: '--',
},
],
//
showCollapseButton: true,
submitButtonOptions: {
content: '查询',
},
//
submitOnChange: false,
//
submitOnEnter: false,
wrapperClass: 'grid-cols-1 md:grid-cols-5',
}
const gridOptions: VxeGridProps<RowType> = {
columns: [
{ field: 'Uid', title: 'id' },
{ field: 'Event', title: '事件类型', formatter: ({ cellValue }) => $t('page.log.event.' + `${cellValue}`) || cellValue },
{ field: 'Label', title: 'Label'},
{ field: 'Param', title: '参数' },
{ field: 'Timestamp', title: '时间', formatter: ({ cellValue }) => new Date(cellValue*1000).toLocaleString()},
],
stripe: true,
height: 'auto',
pagerConfig: {},
proxyConfig: {
response: {
total: "total",
result: "data"
},
ajax: {
query: async ({ page }, formValues) => {
let Uid = parseInt(formValues.Uid, 10);
if (Uid == 0) {
return []
}
state.uid = Uid;
state.Event = formValues.Event;
return await getUserlogEventApi({
Id: Uid,
Event: formValues.Event,
StartTime: formValues.StartTime,
EndTime: formValues.EndTime,
CurrentPage: page.currentPage,
PageSize: page.pageSize,
});
},
},
},
rowConfig: {
isHover: true,
},
};
const [Grid] = useVbenVxeGrid({ formOptions, gridOptions });
</script>
<template>
<Page auto-content-height>
<Grid />
</Page>
</template>

View File

@ -0,0 +1,100 @@
<script setup lang="ts">
import {onMounted, onUnmounted } from 'vue';
// @ts-ignore
import CalHeatMap from 'cal-heatmap';
// @ts-ignore
import Tooltip from 'cal-heatmap/plugins/Tooltip';
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from '../../../../../packages/@core/ui-kit/shadcn-ui';
import dayjs from 'dayjs';
interface Props {
title: string;
dataList: dataType[];
}
const props = withDefaults(defineProps<Props>(), {
dataList: () => [],
title: '热力图',
});
const dataList = props.dataList;
let cal: any = null;
export interface dataType{
date: string;
value: number;
}
onMounted(() => {
cal = new CalHeatMap();
console.log(new Date(new Date().getFullYear(), 1, 1), new Date(new Date().getFullYear()+1, 0, 0));
cal.paint({
itemSelector: '#cal-heatmap',
data: {
source: dataList,
x: "date",
y: "value",
},
theme: 'light',
date: { start: new Date(new Date().getFullYear(), 1, 1), end: new Date(new Date().getFullYear()+1, 0, 0) },
range: 1,
scale: {
color: {
type: 'linear',
scheme: 'Greens',
domain: [0, 1440]
}
},
domain: {
type: 'year',
gutter: 3,
padding:[5,5,5,5],
label: {
text: '2025',
position: 'top',
align: 'center',
}
},
subDomain: {
type: 'day',
gutter: 2,
width: 10,
height: 10,
radius: 2
},
},
[
[
Tooltip,
{
},
],
]
);
});
onUnmounted(() => {
if (cal) {
cal.destroy();
}
});
</script>
<template>
<Card>
<CardHeader class="py-4">
<CardTitle class="text-lg">{{title}}</CardTitle>
</CardHeader>
<CardContent class="flex flex-wrap p-0">
<div id="cal-heatmap" class="border-border w-full border-b border-r border-t p-5 transition-all hover:shadow-xl"></div>
</CardContent>
</Card>
</template>
<style lang="scss" scoped>
@import '../../../node_modules/cal-heatmap/src/cal-heatmap.scss';
</style>

View File

@ -0,0 +1,5 @@
import eventTable from "./calendar/event-table.vue";
import calendar from "./calendar/index.vue";
import type {dataType} from "./calendar/index.vue";
export { eventTable, calendar };
export type { dataType };

View File

@ -85,7 +85,6 @@ const chartTabs: TabOption[] = [
</script> </script>
<template> <template>
<div class="p-5"> <div class="p-5">
<AnalysisOverview :items="overviewItems" /> <AnalysisOverview :items="overviewItems" />
<AnalysisChartsTabs :tabs="chartTabs" class="mt-5"> <AnalysisChartsTabs :tabs="chartTabs" class="mt-5">

View File

@ -6,11 +6,20 @@ interface Props {
} }
async function copyDescription() { async function copyUserName() {
const doc = document.getElementById('description'); const doc = document.getElementById('user_name');
console.log('copyDescription'); const parts = (doc?.innerText || '').split(':');
console.log(doc?.innerText); const secondPart = parts[1] || '';
navigator.clipboard.writeText(doc?.innerText || ''); const trimmedSecondPart = secondPart.trim();
navigator.clipboard.writeText(trimmedSecondPart);
message.success('复制成功');
}
async function copyUid() {
const doc = document.getElementById('uid');
const parts = (doc?.innerText || '').split(':');
const secondPart = parts[1] || '';
const trimmedSecondPart = secondPart.trim();
navigator.clipboard.writeText(trimmedSecondPart);
message.success('复制成功'); message.success('复制成功');
} }
defineOptions({ defineOptions({
@ -25,14 +34,17 @@ withDefaults(defineProps<Props>(), {
<div class="card-box p-4 py-6 lg:flex"> <div class="card-box p-4 py-6 lg:flex">
<VbenAvatar :src="avatar" class="size-20" /> <VbenAvatar :src="avatar" class="size-20" />
<div <div
v-if="$slots.title || $slots.description" v-if="$slots.nick_name || $slots.user_name"
class="flex flex-col justify-center md:ml-6 md:mt-0" class="flex flex-col justify-center md:ml-6 md:mt-0"
> >
<h1 v-if="$slots.title" class="text-md font-semibold md:text-xl"> <h1 v-if="$slots.nick_name" class="text-md font-semibold md:text-xl">
<slot name="title"></slot> <slot name="nick_name"></slot>
</h1> </h1>
<span v-if="$slots.description" class="text-foreground/80 mt-1" @click='copyDescription' id="description"> <span v-if="$slots.user_name" class="text-foreground/80 mt-1" @click='copyUserName' id="user_name">
<slot name="description"></slot> <slot name="user_name"></slot>
</span>
<span v-if="$slots.uid" class="text-foreground/80 mt-1" @click='copyUid' id="uid">
<slot name="uid"></slot>
</span> </span>
</div> </div>
<div class="mt-4 flex flex-1 justify-end md:mt-0"> <div class="mt-4 flex flex-1 justify-end md:mt-0">

View File

@ -1,9 +1,13 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue'; import { ref, watch } from 'vue';
import { useVbenModal, useVbenForm } from '@vben/common-ui'; import { useVbenModal, useVbenForm } from '@vben/common-ui';
import { message } from 'ant-design-vue'; import { message } from 'ant-design-vue';
import { getUserlogInfoApi } from '#/api/core/log'; import { getUserlogInfoApi } from '#/api/core/log';
import { userGmApi } from '#/api/core/user'; import { userGmApi } from '#/api/core/user';
import {calendar} from '#/component/index'
import type {dataType} from '#/component/index';
// cal-heatmap
import 'cal-heatmap/cal-heatmap.css';
import type { import type {
WorkbenchProjectItem, WorkbenchProjectItem,
@ -80,15 +84,18 @@ const info = ref<{
Diamond: number; Diamond: number;
Energy: number; Energy: number;
Mac: string; Mac: string;
Uid: number;
Cumulative:string; Cumulative:string;
TodayCumulative:string; TodayCumulative:string;
Bonus?: number; Bonus?: number;
Order: WorkbenchProjectItem[]; Order: WorkbenchProjectItem[];
Heatmap: dataType[];
}>({ }>({
Level: 0, Level: 0,
Star: 0, Star: 0,
Name: '', Name: '',
Charge: 0, Charge: 0,
Uid: 0,
AreaId: 0, AreaId: 0,
LoginTime: '', LoginTime: '',
Diamond: 0, Diamond: 0,
@ -96,7 +103,8 @@ const info = ref<{
Mac: '', Mac: '',
Cumulative:'0h', Cumulative:'0h',
TodayCumulative:'0h', TodayCumulative:'0h',
Order: [] Order: [],
Heatmap: [],
}) })
const [Modal, modalApi] = useVbenModal({ const [Modal, modalApi] = useVbenModal({
onCancel() { onCancel() {
@ -116,6 +124,7 @@ const [Modal, modalApi] = useVbenModal({
PageSize: 10, // replace 10 with the actual page size PageSize: 10, // replace 10 with the actual page size
CurrentPage: 1, // replace 1 with the actual current page CurrentPage: 1, // replace 1 with the actual current page
}); });
info.value.Uid = data.value.uid;
info.value.Level = r.Level info.value.Level = r.Level
info.value.Star = r.Star info.value.Star = r.Star
info.value.Name = r.Name info.value.Name = r.Name
@ -128,6 +137,7 @@ const [Modal, modalApi] = useVbenModal({
info.value.Cumulative = (r.Cumulative / 3600).toFixed(2) + 'h' info.value.Cumulative = (r.Cumulative / 3600).toFixed(2) + 'h'
info.value.TodayCumulative = (r.TodayCumulative / 3600).toFixed(2) + 'h' info.value.TodayCumulative = (r.TodayCumulative / 3600).toFixed(2) + 'h'
info.value.Order = [] info.value.Order = []
info.value.Heatmap = r.Heatmap || [];
for (const i in r.Order) { for (const i in r.Order) {
info.value.Order.push({ info.value.Order.push({
color: '#3fb27f', color: '#3fb27f',
@ -145,15 +155,28 @@ const [Modal, modalApi] = useVbenModal({
} }
}, },
}); });
//
watch(
() => info.value.Heatmap,
(newHeatmap) => {
if (newHeatmap && newHeatmap.length > 0) {
console.log('Heatmap data loaded:', newHeatmap);
//
}
},
{ deep: true }
);
</script> </script>
<template> <template>
<Modal title="玩家详情"> <Modal title="玩家详情">
<div class="p-5"> <div class="p-5">
<UserHeader :avatar="userStore.userInfo?.avatar || preferences.app.defaultAvatar"> <UserHeader :avatar="userStore.userInfo?.avatar || preferences.app.defaultAvatar">
<template #title> <template #nick_name>
Name: {{ info.Name || 'N/A' }} nick_name: {{ info.Name || 'N/A' }}
</template> </template>
<template #description> MAC: {{ info.Mac }} </template> <template #user_name> user_name: {{ info.Mac }} </template>
<template #uid> uid: {{ info.Uid }} </template>
<template #level>{{ info.Level }}</template> <template #level>{{ info.Level }}</template>
<template #star>{{ info.Star }}</template> <template #star>{{ info.Star }}</template>
<template #energy>{{ info.Energy }} </template> <template #energy>{{ info.Energy }} </template>
@ -177,11 +200,20 @@ const [Modal, modalApi] = useVbenModal({
<template #Bonus>{{ info.Bonus || 0 }}</template> <template #Bonus>{{ info.Bonus || 0 }}</template>
<template #TodayCumulative>{{ info.TodayCumulative }}</template> <template #TodayCumulative>{{ info.TodayCumulative }}</template>
</WorkbenchDetail> </WorkbenchDetail>
<calendar
v-if="info.Heatmap.length > 0"
style="margin-top:15px"
:dataList="info.Heatmap"
title="热力图"
:key="`heatmap-${info.Uid}`"
/>
<div v-else style="margin-top:15px; padding: 20px; text-align: center; color: #999;">
暂无热力图数据
</div>
<!-- <WorkbenchTodo :items="todoItems" class="mt-5" title="待办事项" /> --> <!-- <WorkbenchTodo :items="todoItems" class="mt-5" title="待办事项" /> -->
</div> </div>
</div> </div>
</div> </div>
</Modal> </Modal>
</template> </template>

File diff suppressed because it is too large Load Diff