1.0
This commit is contained in:
		
							
								
								
									
										283
									
								
								xyzw_web_helper-main开源源码更新/src/components/ClubInfo.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										283
									
								
								xyzw_web_helper-main开源源码更新/src/components/ClubInfo.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,283 @@
 | 
			
		||||
<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>
 | 
			
		||||
		Reference in New Issue
	
	Block a user