Files
xyzw_web_helper/xyzw_web_helper-main开源源码更新/src/components/ClubInfo.vue

284 lines
9.0 KiB
Vue
Raw Normal View History

2025-10-17 20:56:50 +08:00
<template>
<div class="status-card club-info">
<div class="card-header">
<img
src="/icons/1733492491706152.png"
alt="俱乐部图标"
class="status-icon"
>
<div class="status-info">
<h3>俱乐部信息</h3>
<p>军团/俱乐部概览与成员</p>
</div>
<div class="status-badge" :class="{ active: !!club }">
<div class="status-dot" />
<span>{{ club ? '已加入' : '暂无俱乐部' }}</span>
</div>
</div>
<div class="card-content">
<div v-if="!club" class="empty-club">
<n-empty description="暂无俱乐部" />
<div class="actions">
<n-button size="small" @click="refreshClub">刷新</n-button>
</div>
</div>
<div v-else>
<div class="toolbar">
<n-space size="small">
<n-button size="small" @click="refreshClub">刷新</n-button>
</n-space>
</div>
<n-tabs v-model:value="activeTab" type="line" animated>
<n-tab-pane name="overview" tab="概览" display-directive="show:lazy">
<div class="overview">
<div class="club-header">
<n-avatar :size="48" :src="club.logo || '/icons/xiaoyugan.png'" />
<div class="meta">
<div class="name">{{ club.name }}</div>
<div class="sub">ID {{ club.id }} · Lv.{{ club.level }} · 服务器 {{ club.serverId }}</div>
</div>
</div>
<div class="grid">
<div class="item">
<div class="label">战力</div>
<div class="value">{{ formatNumber(clubOverview.power) }}</div>
</div>
<div class="item">
<div class="label">段位</div>
<div class="value">{{ clubOverview.dan }}</div>
</div>
<div class="item">
<div class="label">成员数</div>
<div class="value">{{ memberCount }}</div>
</div>
<div class="item">
<div class="label">红洗次数</div>
<div class="value">{{ clubOverview.redQuench }}</div>
</div>
</div>
<div v-if="club.announcement" class="announcement">
<div class="label">公告</div>
<div class="text">{{ club.announcement }}</div>
</div>
<div class="leader" v-if="leader">
<div class="label">会长</div>
<div class="leader-info">
<n-avatar :size="32" :src="leader.headImg || '/icons/xiaoyugan.png'"/>
<span class="leader-name">{{ leader.name }}</span>
</div>
</div>
</div>
</n-tab-pane>
<n-tab-pane name="members" tab="成员" display-directive="show:lazy">
<div class="members">
<div class="members-list">
<div v-for="m in topMembers" :key="m.roleId" class="member-row">
<div class="left">
<n-avatar :size="28" :src="m.headImg || '/icons/xiaoyugan.png'"/>
<span class="name">{{ m.name }}</span>
</div>
<div class="right">
<span class="power">{{ formatNumber(m.power || m.custom?.s_power || 0) }}</span>
<span class="tag">{{ jobLabel(m.job) }}</span>
</div>
</div>
</div>
<div v-if="memberCount > topMembers.length" class="hint">仅显示前 {{ topMembers.length }} (按战力)</div>
</div>
</n-tab-pane>
<n-tab-pane name="records" tab="盐场战绩" display-directive="show:lazy">
<ClubBattleRecords inline />
</n-tab-pane>
</n-tabs>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import { useTokenStore } from '@/stores/tokenStore'
import ClubBattleRecords from './ClubBattleRecords.vue'
const tokenStore = useTokenStore()
const info = computed(() => tokenStore.gameData?.legionInfo || null)
const club = computed(() => info.value?.info || null)
const membersObj = computed(() => club.value?.members || {})
const members = computed(() => Object.values(membersObj.value || {}))
const memberCount = computed(() => members.value.length)
const leader = computed(() => {
const lid = club.value?.leaderId
if (!lid) return null
return members.value.find(m => Number(m.roleId) === Number(lid)) || null
})
const topMembers = computed(() => {
return [...members.value]
.sort((a, b) => (Number(b.power || b.custom?.s_power || 0) - Number(a.power || a.custom?.s_power || 0)))
.slice(0, 20)
})
const activeTab = ref('overview')
// 兼容不同服务端字段:从 info.info 和顶层 info 以及 statistics 中聚合
const clubOverview = computed(() => {
const i = info.value || {}
const base = i.info || {}
const stats = i.statistics || i.stat || {}
const power = Number(
base.power ?? i.power ?? base.s_power ?? i.s_power ?? 0
)
const dan = base.dan ?? i.dan ?? base.rank ?? i.rank ?? '-'
const redQuench = Number(
base.redQuenchCnt ?? i.redQuenchCnt ?? stats['red:quench'] ?? stats['red_quench'] ?? 0
)
const lastWarRank = (
stats['last:war:rank'] ?? stats['lastWarRank'] ?? stats['legion:last:war:rank'] ?? '-'
)
const noApply = Boolean(base.noApply ?? i.noApply)
return { power, dan: dan ?? '-', redQuench, lastWarRank, noApply }
})
const refreshClub = () => {
const token = tokenStore.selectedToken
if (!token) return
tokenStore.sendMessage(token.id, 'legion_getinfo')
}
const jobLabel = (job) => {
if (job === 1) return '会长'
if (job === 2) return '副会长'
return '成员'
}
const formatNumber = (num) => {
const n = Number(num || 0)
if (n >= 1e8) return (n / 1e8).toFixed(2) + '亿'
if (n >= 1e4) return (n / 1e4).toFixed(2) + '万'
return String(n)
}
</script>
<style scoped lang="scss">
.club-info {
.toolbar {
display: flex;
justify-content: flex-end;
margin-bottom: var(--spacing-sm);
}
.overview {
display: flex;
flex-direction: column;
gap: var(--spacing-md);
}
.club-header {
display: flex;
align-items: center;
gap: var(--spacing-md);
}
.meta {
.name { font-size: var(--font-size-lg); font-weight: var(--font-weight-semibold); }
.sub { color: var(--text-secondary); font-size: var(--font-size-sm); }
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: var(--spacing-md);
}
.item {
background: var(--bg-tertiary);
border-radius: var(--border-radius-medium);
padding: var(--spacing-sm);
.label { color: var(--text-secondary); font-size: var(--font-size-xs); margin-bottom: 2px; }
.value { font-weight: var(--font-weight-medium); }
}
.announcement .label, .leader .label { color: var(--text-secondary); font-size: var(--font-size-sm); margin-bottom: 4px; }
.announcement .text { white-space: pre-wrap; }
.leader .leader-info { display: flex; align-items: center; gap: var(--spacing-sm); }
.members-list { display: flex; flex-direction: column; gap: 8px; }
.member-row { display: flex; align-items: center; justify-content: space-between; padding: 8px; border-radius: 8px; background: var(--bg-tertiary); }
.member-row .left { display: flex; align-items: center; gap: 8px; }
.member-row .right { display: flex; align-items: center; gap: 8px; color: var(--text-secondary); }
.member-row .name { font-weight: var(--font-weight-medium); }
.member-row .power { font-feature-settings: 'tnum' 1; font-variant-numeric: tabular-nums; }
.hint { margin-top: 8px; color: var(--text-tertiary); font-size: var(--font-size-xs); }
.empty-club { text-align: center; }
.empty-club .actions { margin-top: var(--spacing-sm); }
}
/* 卡片基础样式,保持与 GameStatus 一致 */
.status-card {
background: var(--bg-primary);
border-radius: var(--border-radius-xl);
padding: var(--spacing-lg);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
transition: all var(--transition-normal);
min-height: 200px;
&:hover {
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
transform: translateY(-2px);
}
}
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: var(--spacing-md);
}
.status-icon {
width: 40px;
height: 40px;
border-radius: 10px;
margin-right: var(--spacing-md);
}
.status-info {
flex: 1;
h3 { margin: 0; font-size: var(--font-size-lg); }
p { margin: 0; color: var(--text-secondary); font-size: var(--font-size-sm); }
}
.status-badge {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 6px 10px;
border-radius: 999px;
background: var(--bg-tertiary);
color: var(--text-secondary);
&.active {
background: rgba(24, 160, 88, 0.12);
color: var(--success-color);
}
}
.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: currentColor;
}
</style>