玩家列表UI调整
Some checks are pending
CI / Test (ubuntu-latest) (push) Waiting to run
CI / Test (windows-latest) (push) Waiting to run
CI / Lint (ubuntu-latest) (push) Waiting to run
CI / Lint (windows-latest) (push) Waiting to run
CI / Check (ubuntu-latest) (push) Waiting to run
CI / Check (windows-latest) (push) Waiting to run
CI / CI OK (push) Blocked by required conditions
CodeQL / Analyze (${{ matrix.language }}) (none, javascript-typescript) (push) Waiting to run
Deploy Website on push / Deploy Push Playground Ftp (push) Waiting to run
Deploy Website on push / Deploy Push Docs Ftp (push) Waiting to run
Deploy Website on push / Deploy Push Antd Ftp (push) Waiting to run
Deploy Website on push / Deploy Push Element Ftp (push) Waiting to run
Deploy Website on push / Deploy Push Naive Ftp (push) Waiting to run
Release Drafter / update_release_draft (push) Waiting to run
Some checks are pending
CI / Test (ubuntu-latest) (push) Waiting to run
CI / Test (windows-latest) (push) Waiting to run
CI / Lint (ubuntu-latest) (push) Waiting to run
CI / Lint (windows-latest) (push) Waiting to run
CI / Check (ubuntu-latest) (push) Waiting to run
CI / Check (windows-latest) (push) Waiting to run
CI / CI OK (push) Blocked by required conditions
CodeQL / Analyze (${{ matrix.language }}) (none, javascript-typescript) (push) Waiting to run
Deploy Website on push / Deploy Push Playground Ftp (push) Waiting to run
Deploy Website on push / Deploy Push Docs Ftp (push) Waiting to run
Deploy Website on push / Deploy Push Antd Ftp (push) Waiting to run
Deploy Website on push / Deploy Push Element Ftp (push) Waiting to run
Deploy Website on push / Deploy Push Naive Ftp (push) Waiting to run
Release Drafter / update_release_draft (push) Waiting to run
This commit is contained in:
parent
8b5a7d28b8
commit
f412003d8b
@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { Page, useVbenModal } from '@vben/common-ui';
|
||||
import { Page, useVbenModal, VbenIcon } from '@vben/common-ui';
|
||||
import type { VxeGridListeners } from '#/adapter/vxe-table';
|
||||
import { getUserLogAssetApi } from '#/api/core/log';
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
@ -10,6 +10,7 @@ import type { VbenFormProps } from '#/adapter/form';
|
||||
import { ItemData } from '#/store/item';
|
||||
import { eventModal } from '#/component';
|
||||
import { getUnixTime, formatUTC8Time } from "#/store/util";
|
||||
import { Tag } from 'ant-design-vue';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
// 接收父组件传递的 props
|
||||
@ -148,11 +149,11 @@ const gridEvents: VxeGridListeners<RowType> = {
|
||||
const gridOptions: VxeGridProps<RowType> = {
|
||||
columns: [
|
||||
// { field: 'Uid', title: 'Uid', align: 'center' },
|
||||
{ field: 'change_type', title: '变化类型', formatter: ({ cellValue }) => formatType(cellValue), align: 'center' },
|
||||
{ field: 'change_type', title: '变化类型', align: 'center', slots: { default: 'change_type' } },
|
||||
{ field: 'change_num', title: '变化数值', align: 'center', width: 120 },
|
||||
{ field: 'change_after', title: '变化后数值', align: 'center' },
|
||||
{ field: 'change_reason', title: '原因', align: 'center' },
|
||||
{ field: 'item_name', title: '道具名称', align: 'center' },
|
||||
{ field: 'change_reason', title: '原因', align: 'center', slots: { default: 'change_reason' } },
|
||||
{ field: 'item_name', title: '道具名称', align: 'center', slots: { default: 'item_name' } },
|
||||
{ field: 'timestamp', title: '时间', formatter: ({ cellValue }) => formatUTC8Time(cellValue), align: 'center', slots: { header: 'time_header' } },
|
||||
],
|
||||
stripe: true,
|
||||
@ -235,29 +236,124 @@ const gridOptions: VxeGridProps<RowType> = {
|
||||
|
||||
const [Grid] = useVbenVxeGrid({ formOptions, gridOptions, gridEvents });
|
||||
|
||||
function getChangeTypeColor(type: string) {
|
||||
return type === 'consume' ? 'error' : 'success';
|
||||
}
|
||||
|
||||
function getChangeTypeIcon(type: string) {
|
||||
return type === 'consume' ? 'solar:arrow-to-down-left-bold' : 'solar:arrow-to-top-left-bold';
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="asset-log-page">
|
||||
<Page auto-content-height>
|
||||
<Grid>
|
||||
<div class="asset-log-panel">
|
||||
<Grid>
|
||||
<template #empty>
|
||||
<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;">
|
||||
<div class="asset-log-empty">
|
||||
<img src="https://n.sinaimg.cn/sinacn17/w120h120/20180314/89fc-fyscsmv5911424.gif" alt="no-data"
|
||||
style="max-width:200px;display:block;">
|
||||
<p style="margin:8px 0 0;">没有更多数据了!</p>
|
||||
class="asset-log-empty__image">
|
||||
<p class="asset-log-empty__text">没有更多数据了!</p>
|
||||
</div>
|
||||
</template>
|
||||
<template #time_header>
|
||||
时间 <span style="color: red">(UTC+8)</span>
|
||||
</template>
|
||||
<template #toolbar-tools>
|
||||
总数:<span style="margin-right: 10px;margin-left: 5px;"> {{ d.sum }} </span>
|
||||
正数和: <span style="margin-right: 10px;margin-left: 5px;color:green">{{ d.psum }} </span>
|
||||
负数和: <span style="color: red;margin-left: 5px;">{{ d.nsum }} </span>
|
||||
<template #change_type="{ row }">
|
||||
<Tag :color="getChangeTypeColor(row.change_type)" class="asset-log-change-tag">
|
||||
<VbenIcon :icon="getChangeTypeIcon(row.change_type)" class="mr-1" />
|
||||
{{ formatType(row.change_type) }}
|
||||
</Tag>
|
||||
</template>
|
||||
</Grid>
|
||||
<template #change_reason="{ row }">
|
||||
<span class="asset-log-reason">{{ row.change_reason || '-' }}</span>
|
||||
</template>
|
||||
<template #item_name="{ row }">
|
||||
<span class="asset-log-item-chip">{{ row.item_name || '-' }}</span>
|
||||
</template>
|
||||
<template #toolbar-tools>
|
||||
<div class="asset-log-toolbar">
|
||||
<Tag color="processing" class="asset-log-toolbar__tag">
|
||||
<VbenIcon icon="solar:user-id-bold" class="mr-1" />
|
||||
UID {{ props.uid || '-' }}
|
||||
</Tag>
|
||||
<Tag color="blue" class="asset-log-toolbar__tag">
|
||||
总数 {{ d.sum }}
|
||||
</Tag>
|
||||
<Tag color="success" class="asset-log-toolbar__tag">
|
||||
正数和 {{ d.psum }}
|
||||
</Tag>
|
||||
<Tag color="error" class="asset-log-toolbar__tag">
|
||||
负数和 {{ d.nsum }}
|
||||
</Tag>
|
||||
</div>
|
||||
</template>
|
||||
</Grid>
|
||||
</div>
|
||||
</Page>
|
||||
<Modal class="w-[1200px]"> </Modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="css">
|
||||
.asset-log-page {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.asset-log-panel {
|
||||
overflow: hidden;
|
||||
border-radius: 22px;
|
||||
box-shadow: 0 16px 30px rgb(15 23 42 / 8%);
|
||||
}
|
||||
|
||||
.asset-log-empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.asset-log-empty__image {
|
||||
display: block;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
.asset-log-empty__text {
|
||||
margin-top: 8px;
|
||||
color: #475569;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.asset-log-toolbar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.asset-log-toolbar__tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
border-radius: 999px;
|
||||
padding-inline: 10px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.asset-log-change-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
border-radius: 999px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.asset-log-item-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 4px 10px;
|
||||
border-radius: 999px;
|
||||
background: linear-gradient(135deg, #e0ecff 0%, #dbeafe 100%);
|
||||
color: #1d4ed8;
|
||||
font-weight: 700;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -7,7 +7,7 @@ import type { VxeGridListeners } from 'vxe-table';
|
||||
import { globalState } from '#/store/globalState';
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
import type { VbenFormProps } from '#/adapter/form';
|
||||
import { Page, useVbenModal } from '@vben/common-ui';
|
||||
import { Page, useVbenModal, VbenIcon } from '@vben/common-ui';
|
||||
import { assetModal } from '#/component';
|
||||
import { Tag } from 'ant-design-vue';
|
||||
import dayjs from 'dayjs';
|
||||
@ -100,7 +100,7 @@ const gridEvents: VxeGridListeners<RowType> = {
|
||||
const gridOptions: VxeGridProps<RowType> = {
|
||||
columns: [
|
||||
// { field: 'Uid', title: 'Uid' },
|
||||
{ field: 'Label', title: '事件类型',width:120 },
|
||||
{ field: 'Label', title: '事件名称', width: 160 },
|
||||
{ field: 'Event', title: '事件类型',width:120, slots: { default: 'event' } },
|
||||
{ field: 'Param', title: '参数' },
|
||||
{ field: 'Timestamp', title: '时间',width:180, formatter: ({ cellValue }) => formatUTC8Time(cellValue), slots: { header: 'time_header' } },
|
||||
@ -184,21 +184,86 @@ function getTagColor(tag:string){
|
||||
return "green";
|
||||
}
|
||||
}
|
||||
|
||||
function getTagIcon(tag: string) {
|
||||
switch (tag) {
|
||||
case 'error':
|
||||
return 'solar:danger-circle-bold';
|
||||
default:
|
||||
return 'solar:check-circle-bold';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="event-log-page">
|
||||
<Page auto-content-height>
|
||||
<Grid>
|
||||
<div class="event-log-panel">
|
||||
<Grid>
|
||||
<template #toolbar-tools>
|
||||
<div class="event-log-toolbar">
|
||||
<Tag color="processing" class="event-log-toolbar__tag">
|
||||
<VbenIcon icon="solar:user-id-bold" class="mr-1" />
|
||||
UID {{ props.uid || '-' }}
|
||||
</Tag>
|
||||
<Tag color="blue" class="event-log-toolbar__tag">
|
||||
<VbenIcon icon="solar:cursor-square-bold" class="mr-1" />
|
||||
双击日志可查看资产快照
|
||||
</Tag>
|
||||
</div>
|
||||
</template>
|
||||
<template #time_header>
|
||||
时间 <span style="color: red">(UTC+8)</span>
|
||||
</template>
|
||||
<template #event="{ row }">
|
||||
<Tag v-for="(label, index) in getTag(row.Event)" :key="index" :color="getTagColor(label)">{{ label }}</Tag>
|
||||
<Tag
|
||||
v-for="(label, index) in getTag(row.Event)"
|
||||
:key="index"
|
||||
:color="getTagColor(label)"
|
||||
class="event-log-status-tag"
|
||||
>
|
||||
<VbenIcon :icon="getTagIcon(label)" class="mr-1" />
|
||||
{{ label }}
|
||||
</Tag>
|
||||
</template>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</div>
|
||||
</Page>
|
||||
<Modal class="w-[800px]"> </Modal>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<style lang="css">
|
||||
.event-log-page {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.event-log-panel {
|
||||
overflow: hidden;
|
||||
border-radius: 22px;
|
||||
background: linear-gradient(180deg, rgb(255 255 255 / 98%) 0%, rgb(247 250 255 / 98%) 100%);
|
||||
box-shadow: 0 16px 30px rgb(15 23 42 / 8%);
|
||||
}
|
||||
|
||||
.event-log-toolbar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.event-log-toolbar__tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding-inline: 10px;
|
||||
border-radius: 999px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.event-log-status-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
border-radius: 999px;
|
||||
font-weight: 700;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -3,8 +3,8 @@ import { ref, computed } from 'vue';
|
||||
import MergeData from '#/store/MergeData.json';
|
||||
import { orderTypeData, orderDiffData } from '#/store/order';
|
||||
import { faceTypeData } from '#/store/face';
|
||||
import { useVbenModal, useVbenForm, WorkbenchTrends } from '@vben/common-ui';
|
||||
import { message, Card, Tabs } from 'ant-design-vue';
|
||||
import { useVbenModal, useVbenForm, WorkbenchTrends, VbenIcon } from '@vben/common-ui';
|
||||
import { message, Card, Collapse, Tabs, Tag } from 'ant-design-vue';
|
||||
import { getUserlogInfoApi } from '#/api/core/log';
|
||||
import { userGmApi, userBanApi } from '#/api/core/user';
|
||||
import { heatmap as LoginHeatmap } from '#/component/index';
|
||||
@ -26,16 +26,23 @@ import orderTable from './order-table.vue';
|
||||
// url 也可以是内部路由,在 navTo 方法中识别处理,进行内部跳转
|
||||
// 例如:url: /dashboard/workspace
|
||||
const projectItems: WorkbenchProjectItem[] = [];
|
||||
|
||||
const { hasAccessByCodes, hasAccessByRoles } = useAccess();
|
||||
const canUseGm = computed(() =>
|
||||
hasAccessByRoles(['super']) || hasAccessByCodes(['AC3003']),
|
||||
);
|
||||
const canBanUser = computed(() =>
|
||||
hasAccessByRoles(['super']) || hasAccessByCodes(['AC3004']),
|
||||
);
|
||||
|
||||
const gmPermissions:boolean = useAccess().hasAccessByRoles(['super']) || useAccess().hasAccessByRoles(['AC9301']);
|
||||
const chargeDisplay = computed(() => (Number(info.value?.Charge ?? 0)).toFixed(2));
|
||||
const banStatusText = computed(() => {
|
||||
if (info.value?.Ban === -1) {
|
||||
return '永久封禁';
|
||||
}
|
||||
if (Number(info.value?.Ban ?? 0) > 0) {
|
||||
return `剩余 ${info.value?.Ban} 天`;
|
||||
}
|
||||
return '正常';
|
||||
});
|
||||
const banStatusColor = computed(() => {
|
||||
if (info.value?.Ban === -1 || Number(info.value?.Ban ?? 0) > 0) {
|
||||
return 'error';
|
||||
}
|
||||
return 'success';
|
||||
});
|
||||
|
||||
const [BaseForm] = useVbenForm({
|
||||
// 所有表单项共用,可单独在表单内覆盖
|
||||
@ -276,8 +283,6 @@ const [Modal, modalApi] = useVbenModal({
|
||||
async onOpenChange(isOpen: boolean) {
|
||||
if (isOpen) {
|
||||
data.value = modalApi.getData<Record<string, any>>();
|
||||
const b = hasAccessByCodes(['AC3003']);
|
||||
console.log('canUseGm', canUseGm.value, 'hasAccessByCodes AC3003', b);
|
||||
try {
|
||||
const r = await getUserlogInfoApi({
|
||||
Id: data.value.uid,
|
||||
@ -473,39 +478,86 @@ function formatActLog(type: number, content = ''): [string, string] {
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<Modal title="玩家详情" class="h-[100%]">
|
||||
<div class="p-5">
|
||||
<Tabs>
|
||||
<Modal title="玩家详情" class="player-detail-modal h-[100%]">
|
||||
<div class="player-detail-page p-5">
|
||||
<Tabs class="player-detail-tabs">
|
||||
<Tabs.TabPane key="1" tab="玩家信息">
|
||||
<UserHeader :avatar="info.Face" :ban="info.Ban">
|
||||
<template #nick_name> nick_name: {{ info.Name || 'N/A' }} </template>
|
||||
<template #user_name> user_name: {{ info.Mac }} </template>
|
||||
<template #uid> uid: {{ info.Uid }} </template>
|
||||
<template #level>{{ info.Level }}</template>
|
||||
<template #star>{{ info.Star }}</template>
|
||||
<template #energy>{{ info.Energy }} </template>
|
||||
<template #diamond>{{ info.Diamond }}</template>
|
||||
</UserHeader>
|
||||
<div class="mt-5 flex">
|
||||
<AccessControl :codes="['AC3003']" type="code">
|
||||
<Card class="card-box flex flex-col p-5 w-[50%]">
|
||||
<div class="player-detail-hero">
|
||||
<UserHeader :avatar="info.Face" :ban="info.Ban">
|
||||
<template #nick_name> nick_name: {{ info.Name || 'N/A' }} </template>
|
||||
<template #user_name> user_name: {{ info.Mac }} </template>
|
||||
<template #uid> uid: {{ info.Uid }} </template>
|
||||
<template #level>{{ info.Level }}</template>
|
||||
<template #star>{{ info.Star }}</template>
|
||||
<template #energy>{{ info.Energy }} </template>
|
||||
<template #diamond>{{ info.Diamond }}</template>
|
||||
</UserHeader>
|
||||
|
||||
<div class="player-detail-metrics">
|
||||
<div class="player-detail-metric-card">
|
||||
<div class="player-detail-metric-card__label">累计充值</div>
|
||||
<div class="player-detail-metric-card__value">${{ chargeDisplay }}</div>
|
||||
</div>
|
||||
<div class="player-detail-metric-card">
|
||||
<div class="player-detail-metric-card__label">最高充值</div>
|
||||
<div class="player-detail-metric-card__value">${{ info.MaxCharge ?? 0 }}</div>
|
||||
</div>
|
||||
<div class="player-detail-metric-card">
|
||||
<div class="player-detail-metric-card__label">今日在线</div>
|
||||
<div class="player-detail-metric-card__value">{{ info.TodayCumulative }}</div>
|
||||
</div>
|
||||
<div class="player-detail-metric-card">
|
||||
<div class="player-detail-metric-card__label">封禁状态</div>
|
||||
<div class="player-detail-metric-card__value">
|
||||
<Tag :color="banStatusColor">{{ banStatusText }}</Tag>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="player-detail-panel-grid mt-5">
|
||||
<div if="gmPermissions">
|
||||
<Card class="player-detail-action-card" :bordered="false">
|
||||
<template #title>
|
||||
<div class="player-detail-card-title">
|
||||
<VbenIcon icon="solar:shield-keyhole-bold" />
|
||||
<span>GM 指令</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="player-detail-card-desc">输入 GM 指令后直接对当前玩家执行调试操作。</div>
|
||||
<BaseForm />
|
||||
</Card>
|
||||
</AccessControl>
|
||||
</div>
|
||||
<AccessControl :codes="['AC3004']" type="code">
|
||||
<Card class="card-box flex flex-col p-5 w-[45%] ml-5">
|
||||
<Card class="player-detail-action-card" :bordered="false">
|
||||
<template #title>
|
||||
<div class="player-detail-card-title">
|
||||
<VbenIcon icon="solar:forbidden-circle-bold" />
|
||||
<span>封禁管理</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="player-detail-card-desc">支持快捷封禁、解封和填写操作原因。</div>
|
||||
<BanForm />
|
||||
</Card>
|
||||
</AccessControl>
|
||||
</div>
|
||||
<LoginHeatmap :app-id="data?.AppId" :uid="data?.uid" title="登录热力图" class="mt-5" />
|
||||
<div class="mt-5 flex flex-col lg:flex-row">
|
||||
<div class="mr-4 w-full lg:w-3/5">
|
||||
<orderComponent :items="info.Order" title="订单" />
|
||||
<WorkbenchTrends :items="trendItems" class="mt-5" title="最新动态" />
|
||||
<LoginHeatmap :app-id="data?.AppId" :uid="data?.uid" title="登录热力图" class="player-detail-section mt-5" />
|
||||
<div class="player-detail-content mt-5">
|
||||
<div class="player-detail-content__main">
|
||||
<div class="player-detail-section">
|
||||
<orderComponent :items="info.Order" title="订单" />
|
||||
</div>
|
||||
<div class="player-detail-section player-detail-section--transparent mt-5">
|
||||
<Collapse class="player-detail-collapse" :bordered="false">
|
||||
<Collapse.Panel key="latest-trends" header="最新动态">
|
||||
<WorkbenchTrends :items="trendItems" title="最新动态" />
|
||||
</Collapse.Panel>
|
||||
</Collapse>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full lg:w-2/5">
|
||||
<WorkbenchDetail :items="projectItems" class="mt-5 lg:mt-0" title="玩家详情">
|
||||
<div class="player-detail-content__side">
|
||||
<div class="player-detail-section">
|
||||
<WorkbenchDetail :items="projectItems" title="玩家详情">
|
||||
<template #areaid> {{ info.AreaId }}</template>
|
||||
<template #charge> <b>$</b>{{ chargeDisplay }}</template>
|
||||
<template #maxCharge> <b>$</b>{{ info.MaxCharge }}</template>
|
||||
@ -516,10 +568,14 @@ function formatActLog(type: number, content = ''): [string, string] {
|
||||
<template #Code>{{ info.Code || 0 }}</template>
|
||||
<template #TodayCumulative>{{ info.TodayCumulative }}</template>
|
||||
<template #ad_watch>{{ info.AdWatch ? '是' : '否' }}</template>
|
||||
</WorkbenchDetail>
|
||||
<chessComponent :items="info.Chess" title="棋盘" class="mt-6" />
|
||||
<friendComponent :Items="info.Friend" title="好友" class="mt-6" />
|
||||
<!-- <WorkbenchTodo :items="todoItems" class="mt-5" title="待办事项" /> -->
|
||||
</WorkbenchDetail>
|
||||
</div>
|
||||
<div class="player-detail-section mt-6">
|
||||
<chessComponent :items="info.Chess" title="棋盘" />
|
||||
</div>
|
||||
<div class="player-detail-section mt-6">
|
||||
<friendComponent :Items="info.Friend" title="好友" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -538,3 +594,133 @@ function formatActLog(type: number, content = ''): [string, string] {
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style lang="css">
|
||||
.player-detail-page {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.player-detail-tabs .ant-tabs-nav {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.player-detail-tabs .ant-tabs-tab {
|
||||
border-radius: 999px;
|
||||
padding-inline: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.player-detail-tabs .ant-tabs-tab-active {
|
||||
background: rgb(29 78 216 / 8%);
|
||||
}
|
||||
|
||||
.player-detail-hero {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.player-detail-metrics {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.player-detail-metric-card {
|
||||
padding: 16px 18px;
|
||||
border: 1px solid rgb(191 219 254 / 80%);
|
||||
border-radius: 20px;
|
||||
background: linear-gradient(180deg, rgb(255 255 255 / 96%) 0%, rgb(239 246 255 / 96%) 100%);
|
||||
box-shadow: 0 12px 24px rgb(59 130 246 / 10%);
|
||||
}
|
||||
|
||||
.player-detail-metric-card__label {
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.player-detail-metric-card__value {
|
||||
margin-top: 8px;
|
||||
font-size: 22px;
|
||||
font-weight: 800;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.player-detail-panel-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.player-detail-action-card,
|
||||
.player-detail-section {
|
||||
border-radius: 24px;
|
||||
box-shadow: 0 18px 32px rgb(15 23 42 / 8%);
|
||||
}
|
||||
|
||||
.player-detail-section--transparent {
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.player-detail-card-title {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.player-detail-card-desc {
|
||||
margin-bottom: 16px;
|
||||
color: #64748b;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.player-detail-content {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.6fr) minmax(320px, 1fr);
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.player-detail-content__main,
|
||||
.player-detail-content__side {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.player-detail-collapse {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.player-detail-collapse :where(.ant-collapse-header) {
|
||||
align-items: center !important;
|
||||
padding: 18px 22px 12px !important;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.player-detail-collapse :where(.ant-collapse-content) {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.player-detail-collapse :where(.ant-collapse-content-box) {
|
||||
padding: 0 22px 22px !important;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.player-detail-metrics {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.player-detail-content {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.player-detail-panel-grid,
|
||||
.player-detail-metrics {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,22 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import { getUserListApi } from '#/api';
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { VbenIcon, Page } from '@vben/common-ui';
|
||||
import { Tag } from 'ant-design-vue';
|
||||
import dayjs from 'dayjs';
|
||||
import { formatUTC8Time } from '#/store/util';
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
import type { VbenFormProps } from '#/adapter/form';
|
||||
import { Page } from '@vben/common-ui';
|
||||
import type { VxeGridListeners } from 'vxe-table';
|
||||
import { useVbenModal } from '@vben/common-ui'
|
||||
import { onMounted, ref, inject } from 'vue';
|
||||
import { computed, inject, onMounted, ref } from 'vue';
|
||||
import { globalState } from '#/store/globalState';
|
||||
import { getServerListApi, getAppListApi } from '#/api/core/server';
|
||||
import type { AppData, ServerData } from '#/api/core/server';
|
||||
import { getAppListApi } from '#/api/core/server';
|
||||
import type { AppData } from '#/api/core/server';
|
||||
import { $t } from '#/locales'
|
||||
const state = inject('globalState', globalState);
|
||||
const appList = ref<AppData[]>([]);
|
||||
const ServerList = ref<ServerData[]>([]);
|
||||
const selectedAppId = ref<number>(1);
|
||||
import userModalDemo from './user.vue';
|
||||
// import { PlayerInfo } from '#/model/player';
|
||||
const [userModal, userModalApi] = useVbenModal({
|
||||
@ -37,6 +37,11 @@ interface RowType {
|
||||
}
|
||||
const startDate = dayjs().subtract(7, 'day').startOf('day');
|
||||
const endDate = dayjs().endOf('day');
|
||||
const currentAppLabel = computed(() => {
|
||||
const current = appList.value.find((item) => item.AppId === selectedAppId.value);
|
||||
return current ? $t(`page.server.${current.AppName}`) : '未选择 APP';
|
||||
});
|
||||
|
||||
const formOptions: VbenFormProps = {
|
||||
// 默认展开
|
||||
collapsed: false,
|
||||
@ -45,22 +50,8 @@ const formOptions: VbenFormProps = {
|
||||
component: 'Select',
|
||||
defaultValue: 1,
|
||||
componentProps: {
|
||||
onChange: async (value: number) => {
|
||||
const serverResponse = await getServerListApi({ AppId: value, Type: 1 });
|
||||
ServerList.value = Array.isArray(serverResponse) ? serverResponse : [];
|
||||
GridApi.formApi.updateSchema([
|
||||
{
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
options: ServerList.value.map((item) => ({
|
||||
label: item.ServerId,
|
||||
value: item.ServerId,
|
||||
})),
|
||||
},
|
||||
fieldName: 'ServerId',
|
||||
},
|
||||
]);
|
||||
GridApi.formApi.setValues({ ServerId: 1 });
|
||||
onChange: (value: number) => {
|
||||
selectedAppId.value = value;
|
||||
},
|
||||
filterOption: true,
|
||||
options: [
|
||||
@ -170,7 +161,7 @@ const gridEvents: VxeGridListeners<RowType> = {
|
||||
|
||||
const gridOptions: VxeGridProps<RowType> = {
|
||||
columns: [
|
||||
{ field: 'Uid', title: 'id', sortable: true, sortBy: 'Uid' },
|
||||
{ field: 'Uid', title: 'id', sortable: true, sortBy: 'Uid', slots: { default: 'uid' } },
|
||||
{ field: 'UserName', title: '登录名' },
|
||||
{ field: 'Level', title: '等级', formatter: ({ cellValue }: { cellValue: string | number }) => `${cellValue} 级`, sortable: true, sortBy: 'Level' },
|
||||
{ field: 'Node', title: '节点', },
|
||||
@ -203,12 +194,17 @@ const gridOptions: VxeGridProps<RowType> = {
|
||||
formValues: Record<string, any>,
|
||||
) => {
|
||||
let Id = parseInt(formValues.AppId, 10);
|
||||
let ServerId = 1;
|
||||
let Uid = parseInt(formValues.Uid, 10);
|
||||
let Nickname = formValues.Nickname ? String(formValues.Nickname) : '';
|
||||
let Username = formValues.Username ? String(formValues.Username) : '';
|
||||
let DeviceId = formValues.DeviceId ? String(formValues.DeviceId) : '';
|
||||
let r = await getUserListApi({ Id: Id, ServerId: ServerId, pageSize: page.pageSize, currentPage: page.currentPage, Uid: Uid, Nickname: Nickname, Username: Username, DeviceId: DeviceId, StartTime: formValues.StartTime ? Math.floor(new Date(formValues.StartTime).getTime() / 1000) : undefined, EndTime: formValues.EndTime ? Math.floor(new Date(formValues.EndTime).getTime() / 1000) : undefined });
|
||||
if (Number.isNaN(Id)) {
|
||||
Id = selectedAppId.value;
|
||||
}
|
||||
if (Number.isNaN(Uid)) {
|
||||
Uid = 0;
|
||||
}
|
||||
let r = await getUserListApi({ Id: Id, ServerId: 1, pageSize: page.pageSize, currentPage: page.currentPage, Uid: Uid, Nickname: Nickname, Username: Username, DeviceId: DeviceId, 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;
|
||||
},
|
||||
},
|
||||
@ -228,6 +224,7 @@ onMounted(async () => {
|
||||
appList.value = Array.isArray(response) ? response : [];
|
||||
const app = appList.value[0];
|
||||
if (!app) return;
|
||||
selectedAppId.value = app.AppId;
|
||||
GridApi.formApi.updateSchema([
|
||||
{
|
||||
component: 'Select',
|
||||
@ -240,20 +237,7 @@ onMounted(async () => {
|
||||
fieldName: 'AppId',
|
||||
},
|
||||
]);
|
||||
const serverResponse = await getServerListApi({ AppId: app.AppId, Type: 1 });
|
||||
ServerList.value = Array.isArray(serverResponse) ? serverResponse : [];
|
||||
GridApi.formApi.updateSchema([
|
||||
{
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
options: ServerList.value.map((item) => ({
|
||||
label: item.ServerId,
|
||||
value: item.ServerId,
|
||||
})),
|
||||
},
|
||||
fieldName: 'ServerId',
|
||||
},
|
||||
]);
|
||||
GridApi.formApi.setValues({ AppId: app.AppId });
|
||||
} catch (e) {
|
||||
appList.value = [];
|
||||
console.log(e);
|
||||
@ -266,21 +250,93 @@ function getTagColor(online: string): string {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page auto-content-height>
|
||||
<Grid>
|
||||
<template #login_time_header>
|
||||
登录时间 <span style="color: red">(UTC+8)</span>
|
||||
</template>
|
||||
<template #online="{ row }">
|
||||
<Tag :color="getTagColor(row.Online)">{{ row.Online }}</Tag>
|
||||
</template>
|
||||
</Grid>
|
||||
<userModal class="w-[100%]" />
|
||||
</Page>
|
||||
<div class="userlist-page">
|
||||
<Page auto-content-height>
|
||||
<div class="userlist-grid-panel">
|
||||
<Grid>
|
||||
<template #toolbar-tools>
|
||||
<div class="userlist-toolbar-meta">
|
||||
<Tag color="processing" class="userlist-toolbar-tag">
|
||||
<VbenIcon icon="solar:gamepad-bold" class="mr-1" />
|
||||
{{ currentAppLabel }}
|
||||
</Tag>
|
||||
</div>
|
||||
</template>
|
||||
<template #login_time_header>
|
||||
登录时间 <span style="color: red">(UTC+8)</span>
|
||||
</template>
|
||||
<template #uid="{ row }">
|
||||
<span class="user-id-chip">{{ row.Uid }}</span>
|
||||
</template>
|
||||
<template #online="{ row }">
|
||||
<Tag :color="getTagColor(row.Online)" :class="['user-status-tag', row.Online === '在线' ? 'user-status-tag--online' : 'user-status-tag--offline']">
|
||||
<VbenIcon :icon="row.Online === '在线' ? 'solar:check-circle-bold' : 'solar:close-circle-bold'" class="mr-1" />
|
||||
{{ row.Online }}
|
||||
</Tag>
|
||||
</template>
|
||||
</Grid>
|
||||
</div>
|
||||
<userModal class="w-[100%]" />
|
||||
</Page>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<style lang="css">
|
||||
.userlist-page {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.userlist-grid-panel {
|
||||
border-radius: 24px;
|
||||
background: rgb(255 255 255 / 94%);
|
||||
box-shadow: 0 16px 34px rgb(15 23 42 / 8%);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.userlist-toolbar-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.userlist-toolbar-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 4px 10px;
|
||||
border-radius: 999px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.user-id-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 68px;
|
||||
padding: 4px 12px;
|
||||
border-radius: 999px;
|
||||
background: linear-gradient(135deg, #e0ecff 0%, #c7ddff 100%);
|
||||
color: #1d4ed8;
|
||||
font-weight: 700;
|
||||
box-shadow: inset 0 0 0 1px rgb(59 130 246 / 14%);
|
||||
}
|
||||
|
||||
.user-status-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
border-radius: 999px;
|
||||
padding-inline: 10px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.user-status-tag--online {
|
||||
box-shadow: 0 8px 16px rgb(34 197 94 / 14%);
|
||||
}
|
||||
|
||||
.user-status-tag--offline {
|
||||
box-shadow: 0 8px 16px rgb(239 68 68 / 14%);
|
||||
}
|
||||
|
||||
.broder-bottom-1 {
|
||||
border-bottom: 0.5px solid rgb(136, 134, 134);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user