refactor sect in vue

This commit is contained in:
bridge
2025-11-27 21:46:42 +08:00
parent a1210589b7
commit 796f48315f
14 changed files with 457 additions and 79 deletions

View File

@@ -4,6 +4,7 @@ import type { AvatarDetail, EffectEntity } from '@/types/core';
import { formatHp, formatAge } from '@/utils/formatters/number';
import StatItem from './components/StatItem.vue';
import EntityRow from './components/EntityRow.vue';
import RelationRow from './components/RelationRow.vue';
import TagList from './components/TagList.vue';
import SecondaryPopup from './components/SecondaryPopup.vue';
import { gameApi } from '@/api/game';
@@ -30,6 +31,10 @@ function jumpToAvatar(id: string) {
uiStore.select('avatar', id);
}
function jumpToSect(id: string) {
uiStore.select('sect', id);
}
async function handleSetObjective() {
if (!objectiveContent.value.trim()) return;
try {
@@ -85,7 +90,7 @@ async function handleClearObjective() {
label="宗门"
:value="data.sect?.name || '散修'"
:sub-value="data.sect?.rank"
:on-click="data.sect ? () => showDetail(data.sect) : undefined"
:on-click="data.sect ? () => jumpToSect(data.sect.id) : undefined"
/>
<StatItem
@@ -154,18 +159,14 @@ async function handleClearObjective() {
<div class="section" v-if="data.relations?.length">
<div class="section-title">关系</div>
<div class="list-container">
<div
<RelationRow
v-for="rel in data.relations"
:key="rel.target_id"
class="relation-item"
:name="rel.name"
:meta="rel.relation"
:sub="`${rel.sect} · ${rel.realm}`"
@click="jumpToAvatar(rel.target_id)"
>
<div class="rel-head">
<span class="rel-name">{{ rel.name }}</span>
<span class="rel-type">{{ rel.relation }}</span>
</div>
<div class="rel-sub">{{ rel.sect }} · {{ rel.realm }}</div>
</div>
/>
</div>
</div>
@@ -283,42 +284,6 @@ async function handleClearObjective() {
background: #1890ff;
}
/* Relations */
.relation-item {
padding: 6px 8px;
background: rgba(0, 0, 0, 0.2);
border-left: 2px solid #333;
cursor: pointer;
transition: all 0.2s;
}
.relation-item:hover {
background: rgba(255, 255, 255, 0.05);
border-left-color: #666;
}
.rel-head {
display: flex;
justify-content: space-between;
margin-bottom: 2px;
}
.rel-name {
font-size: 13px;
color: #eee;
font-weight: bold;
}
.rel-type {
font-size: 12px;
color: #aaa;
}
.rel-sub {
font-size: 11px;
color: #666;
}
/* Modal */
.modal-overlay {
position: absolute;

View File

@@ -5,6 +5,7 @@ import { useUiStore } from '../../../../stores/ui';
// Sub-components
import AvatarDetailView from './AvatarDetail.vue';
import RegionDetailView from './RegionDetail.vue';
import SectDetailView from './SectDetail.vue';
const uiStore = useUiStore();
const panelRef = ref<HTMLElement | null>(null);
@@ -15,6 +16,7 @@ let lastOpenAt = 0;
const currentComponent = computed(() => {
if (uiStore.selectedTarget?.type === 'avatar') return AvatarDetailView;
if (uiStore.selectedTarget?.type === 'region') return RegionDetailView;
if (uiStore.selectedTarget?.type === 'sect') return SectDetailView;
return null;
});

View File

@@ -3,12 +3,13 @@ import { ref } from 'vue';
import type { RegionDetail, EffectEntity } from '@/types/core';
import EntityRow from './components/EntityRow.vue';
import SecondaryPopup from './components/SecondaryPopup.vue';
import { translateElement } from '@/utils/formatters/dictionary';
import { useUiStore } from '@/stores/ui';
defineProps<{
data: RegionDetail;
}>();
const uiStore = useUiStore();
const secondaryItem = ref<EffectEntity | null>(null);
function showDetail(item: EffectEntity | undefined) {
@@ -16,6 +17,10 @@ function showDetail(item: EffectEntity | undefined) {
secondaryItem.value = item;
}
}
function jumpToSect(id: number) {
uiStore.select('sect', id.toString());
}
</script>
<template>
@@ -29,13 +34,18 @@ function showDetail(item: EffectEntity | undefined) {
<div class="section">
<div class="section-title">{{ data.type_name }}</div>
<div class="desc">{{ data.desc }}</div>
<!-- Sect Jump Button -->
<div v-if="data.sect_id" class="actions">
<button class="btn primary" @click="jumpToSect(data.sect_id!)">查看宗门详情</button>
</div>
</div>
<!-- Essence -->
<div class="section" v-if="data.essence">
<div class="section-title">灵气环境</div>
<div class="essence-info">
{{ translateElement(data.essence.type) }}行灵气 · 浓度 {{ data.essence.density }}
{{ data.essence.type }}行灵气 · 浓度 {{ data.essence.density }}
</div>
</div>
@@ -110,4 +120,34 @@ function showDetail(item: EffectEntity | undefined) {
flex-direction: column;
gap: 4px;
}
.actions {
margin-top: 8px;
}
.btn {
width: 100%;
padding: 6px 12px;
border: 1px solid rgba(255, 255, 255, 0.15);
background: rgba(255, 255, 255, 0.05);
color: #ccc;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
transition: all 0.2s;
}
.btn:hover {
background: rgba(255, 255, 255, 0.1);
}
.btn.primary {
background: #177ddc;
color: white;
border: none;
}
.btn.primary:hover {
background: #1890ff;
}
</style>

View File

@@ -0,0 +1,182 @@
<script setup lang="ts">
import { ref } from 'vue';
import type { SectDetail, EffectEntity } from '@/types/core';
import { useUiStore } from '@/stores/ui';
import StatItem from './components/StatItem.vue';
import SecondaryPopup from './components/SecondaryPopup.vue';
import EntityRow from './components/EntityRow.vue';
import RelationRow from './components/RelationRow.vue';
const props = defineProps<{
data: SectDetail;
}>();
const uiStore = useUiStore();
const secondaryItem = ref<EffectEntity | null>(null);
function jumpToAvatar(id: string) {
uiStore.select('avatar', id);
}
function showDetail(item: EffectEntity | undefined) {
if (item) {
secondaryItem.value = item;
}
}
const alignmentText = props.data.alignment;
</script>
<template>
<div class="sect-detail">
<SecondaryPopup
:item="secondaryItem"
@close="secondaryItem = null"
/>
<div class="content-scroll">
<!-- Stats Grid -->
<div class="stats-grid">
<StatItem label="阵营" :value="alignmentText" :class="data.alignment" />
<StatItem label="风格" :value="data.style" />
<StatItem label="擅长" :value="data.preferred_weapon || '无'" />
<StatItem label="成员" :value="data.members?.length || 0" />
</div>
<!-- Intro -->
<div class="section">
<div class="section-title">宗门简介</div>
<div class="text-content">{{ data.desc }}</div>
</div>
<!-- HQ -->
<div class="section">
<div class="section-title">驻地{{ data.hq_name }}</div>
<div class="text-content">{{ data.hq_desc }}</div>
</div>
<!-- Effects -->
<div class="section">
<div class="section-title">宗门加成</div>
<div class="text-content highlight">{{ data.effect_desc || '无特殊加成' }}</div>
</div>
<!-- Techniques -->
<div class="section">
<div class="section-title">独门绝学</div>
<div class="list-container" v-if="data.techniques?.length">
<EntityRow
v-for="t in data.techniques"
:key="t.id"
:item="t"
@click="showDetail(t)"
/>
</div>
<div v-else class="text-content"></div>
</div>
<!-- Members -->
<div class="section" v-if="data.members?.length">
<div class="section-title">门下弟子</div>
<div class="list-container">
<RelationRow
v-for="m in data.members"
:key="m.id"
:name="m.name"
:meta="m.rank"
:sub="m.realm"
@click="jumpToAvatar(m.id)"
/>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.sect-detail {
display: flex;
flex-direction: column;
height: 100%;
min-height: 0;
position: relative;
}
.content-scroll {
flex: 1;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 16px;
padding-right: 4px;
}
.stats-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
background: rgba(255, 255, 255, 0.03);
padding: 8px;
border-radius: 6px;
}
.section {
display: flex;
flex-direction: column;
gap: 6px;
}
.section-title {
font-size: 12px;
font-weight: bold;
color: #666;
border-bottom: 1px solid #333;
padding-bottom: 4px;
margin-bottom: 4px;
}
.text-content {
font-size: 13px;
line-height: 1.6;
color: #ccc;
white-space: pre-wrap;
}
.text-content.highlight {
color: #e6f7ff;
background: rgba(24, 144, 255, 0.1);
padding: 8px;
border-radius: 4px;
}
/* Tech List */
.tech-list {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.tech-item {
font-size: 13px;
color: #eee;
padding: 4px 8px;
background: rgba(255, 255, 255, 0.05);
border-radius: 4px;
display: flex;
align-items: center;
gap: 6px;
transition: background 0.2s;
}
.tech-item.clickable {
cursor: pointer;
}
.tech-item.clickable:hover {
background: rgba(255, 255, 255, 0.1);
}
.tech-icon {
font-size: 14px;
}
</style>

View File

@@ -0,0 +1,57 @@
<script setup lang="ts">
defineProps<{
name: string;
meta?: string;
sub?: string;
}>();
defineEmits(['click']);
</script>
<template>
<div class="relation-row" @click="$emit('click')">
<div class="rel-head">
<span class="rel-name">{{ name }}</span>
<span v-if="meta" class="rel-type">{{ meta }}</span>
</div>
<div v-if="sub" class="rel-sub">{{ sub }}</div>
</div>
</template>
<style scoped>
.relation-row {
padding: 6px 8px;
background: rgba(0, 0, 0, 0.2);
border-left: 2px solid #333;
cursor: pointer;
transition: all 0.2s;
}
.relation-row:hover {
background: rgba(255, 255, 255, 0.05);
border-left-color: #666;
}
.rel-head {
display: flex;
justify-content: space-between;
margin-bottom: 2px;
}
.rel-name {
font-size: 13px;
color: #eee;
font-weight: bold;
}
.rel-type {
font-size: 12px;
color: #aaa;
}
.rel-sub {
font-size: 11px;
color: #666;
}
</style>

View File

@@ -85,6 +85,28 @@ export interface SectInfo extends EffectEntity {
rank: string;
}
export interface SectMember {
id: string;
name: string;
pic_id: number;
gender: string;
rank: string;
realm: string;
}
export interface SectDetail extends EntityBase {
desc: string;
alignment: string;
style: string;
hq_name: string;
hq_desc: string;
effect_desc: string;
technique_names?: string[]; // Deprecated
techniques: EffectEntity[];
preferred_weapon: string;
members: SectMember[];
}
export interface RelationInfo {
target_id: string;
name: string;
@@ -106,6 +128,7 @@ export interface RegionDetail extends EntityBase {
desc: string;
type: string;
type_name: string; // 中文类型名
sect_id?: number;
essence?: {
type: string;

View File

@@ -1,28 +0,0 @@
/**
* 字典映射与翻译工具
*/
const ELEMENT_MAP: Record<string, string> = {
'metal': '金',
'wood': '木',
'water': '水',
'fire': '火',
'earth': '土',
'none': '无',
// Capitalized versions just in case
'Metal': '金',
'Wood': '木',
'Water': '水',
'Fire': '火',
'Earth': '土',
'None': '无',
// Map internal key names if they leak (e.g. GOLD from RootElement)
'gold': '金',
'Gold': '金',
'GOLD': '金'
};
export function translateElement(type: string): string {
return ELEMENT_MAP[type] || type;
}