处理页面

This commit is contained in:
Xin Wang 2025-08-21 13:17:33 +08:00
parent 81b502f993
commit c22880375d
4 changed files with 735 additions and 114 deletions

View File

@ -0,0 +1,585 @@
<!-- MissionConfigPanel.vue - 任务配置面板组件 -->
<template>
<div class="mission-config-panel">
<!-- 标签页导航 -->
<div class="tab-navigation">
<div
v-for="tab in tabs"
:key="tab.key"
class="tab-item"
:class="{ 'active': activeTab === tab.key }"
@click="activeTab = tab.key"
>
{{ tab.label }}
</div>
</div>
<!-- 标签页内容 -->
<div class="tab-content">
<!-- 航线规划 -->
<div v-if="activeTab === 'route'" class="tab-panel route-panel">
<div class="panel-section">
<h4>航线规划</h4>
<p>航线规划功能正在开发中...</p>
</div>
</div>
<!-- 武器装备 -->
<div v-if="activeTab === 'weapon'" class="tab-panel weapon-panel">
<!-- 基础信息 -->
<div class="panel-section basic-info">
<h4>基础信息</h4>
<div class="info-grid">
<div class="info-item">
<label>名称</label>
<el-select v-model="entityConfig.name" size="small">
<el-option label="J-16" value="J-16" />
<el-option label="J-20" value="J-20" />
<el-option label="J-10" value="J-10" />
</el-select>
</div>
<div class="info-item">
<label>名称</label>
<el-input v-model="entityConfig.displayName" size="small" />
</div>
<div class="info-item">
<label>选择传感</label>
<el-select v-model="entityConfig.armorType" size="small">
<el-option label="红方" value="red" />
<el-option label="蓝方" value="blue" />
<el-option label="中立" value="neutral" />
</el-select>
</div>
<div class="info-item">
<label>任务类型</label>
<el-select v-model="entityConfig.missionType" size="small">
<el-option label="扫荡" value="sweep" />
<el-option label="侦察" value="scout" />
<el-option label="攻击" value="attack" />
</el-select>
</div>
<div class="info-item">
<label>包络最高高度</label>
<el-select v-model="entityConfig.maxAltitude" size="small">
<el-option label="10000m" value="10000" />
<el-option label="15000m" value="15000" />
<el-option label="20000m" value="20000" />
</el-select>
</div>
<div class="info-item">
<label>包络最低高度</label>
<el-select v-model="entityConfig.minAltitude" size="small">
<el-option label="100m" value="100" />
<el-option label="500m" value="500" />
<el-option label="1000m" value="1000" />
</el-select>
</div>
</div>
</div>
<!-- 武器挂载 -->
<div class="panel-section weapon-mount">
<h4>武器挂载</h4>
<div class="weapon-schemes">
<div
v-for="scheme in weaponSchemes"
:key="scheme.id"
class="weapon-scheme"
:class="{
'active': selectedWeaponScheme === scheme.id,
'disabled': !scheme.available
}"
@click="selectWeaponScheme(scheme.id)"
>
<div class="scheme-indicator"></div>
<span class="scheme-name">{{ scheme.name }}</span>
<span v-if="!scheme.available" class="scheme-status">北方暂不可用</span>
</div>
</div>
<!-- 武器挂载示意图 -->
<div class="weapon-diagram">
<div class="aircraft-container">
<!-- 飞机主体 -->
<div class="aircraft-body">
<svg viewBox="0 0 400 200" class="aircraft-svg">
<!-- 飞机轮廓 -->
<path d="M200 20 L220 40 L380 60 L390 80 L380 100 L340 120 L200 140 L60 120 L20 100 L10 80 L20 60 L180 40 Z"
fill="#e0e0e0" stroke="#999" stroke-width="2"/>
<!-- 机翼 -->
<path d="M80 80 L150 70 L200 80 L250 70 L320 80 L250 90 L200 100 L150 90 Z"
fill="#f0f0f0" stroke="#999" stroke-width="1"/>
</svg>
</div>
<!-- 武器挂载点 -->
<div class="weapon-mount-points">
<div class="mount-point mount-1" v-if="mountedWeapons[0]">
<img :src="getWeaponIcon(mountedWeapons[0])" :alt="mountedWeapons[0].name" />
<span class="mount-number">1</span>
</div>
<div class="mount-point mount-2" v-if="mountedWeapons[1]">
<img :src="getWeaponIcon(mountedWeapons[1])" :alt="mountedWeapons[1].name" />
<span class="mount-number">2</span>
</div>
<div class="mount-point mount-3" v-if="mountedWeapons[2]">
<img :src="getWeaponIcon(mountedWeapons[2])" :alt="mountedWeapons[2].name" />
<span class="mount-number">3</span>
</div>
<div class="mount-point mount-4" v-if="mountedWeapons[3]">
<img :src="getWeaponIcon(mountedWeapons[3])" :alt="mountedWeapons[3].name" />
<span class="mount-number">4</span>
</div>
<div class="mount-point mount-5" v-if="mountedWeapons[4]">
<img :src="getWeaponIcon(mountedWeapons[4])" :alt="mountedWeapons[4].name" />
<span class="mount-number">5</span>
</div>
<div class="mount-point mount-6" v-if="mountedWeapons[5]">
<img :src="getWeaponIcon(mountedWeapons[5])" :alt="mountedWeapons[5].name" />
<span class="mount-number">6</span>
</div>
</div>
</div>
<!-- 武器信息描述 -->
<div class="weapon-description">
<p>武器挂载方案一 *方案创建于2019.7.11</p>
<p>这是一段关于武器挂载信息的详细描述文本</p>
</div>
</div>
<!-- 燃油管理 -->
<div class="fuel-management">
<div class="fuel-info">
<span class="fuel-label">油量</span>
<span class="fuel-amount">1200 kg</span>
</div>
<div class="fuel-bar">
<div class="fuel-progress" :style="{ width: `${fuelPercentage}%` }"></div>
</div>
</div>
</div>
</div>
<!-- 雷达传感器 -->
<div v-if="activeTab === 'radar'" class="tab-panel radar-panel">
<div class="panel-section">
<h4>雷达传感器</h4>
<p>雷达传感器配置功能正在开发中...</p>
</div>
</div>
<!-- 光电传感器 -->
<div v-if="activeTab === 'optical'" class="tab-panel optical-panel">
<div class="panel-section">
<h4>光电传感器</h4>
<p>光电传感器配置功能正在开发中...</p>
</div>
</div>
<!-- 通信规划 -->
<div v-if="activeTab === 'communication'" class="tab-panel communication-panel">
<div class="panel-section">
<h4>通信规划</h4>
<p>通信规划功能正在开发中...</p>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
interface EntityConfig {
name: string
displayName: string
armorType: string
missionType: string
maxAltitude: string
minAltitude: string
}
interface WeaponScheme {
id: string
name: string
available: boolean
}
interface WeaponItem {
id: string
name: string
type: string
}
const props = defineProps<{
entityConfig: EntityConfig
weaponSchemes: WeaponScheme[]
selectedWeaponScheme: string
fuelPercentage: number
}>()
const emit = defineEmits<{
'update:entityConfig': [config: EntityConfig]
'update:selectedWeaponScheme': [schemeId: string]
'weapon-scheme-select': [schemeId: string]
}>()
//
const tabs = [
{ key: 'route', label: '航线规划' },
{ key: 'weapon', label: '武器装备' },
{ key: 'radar', label: '雷达传感器' },
{ key: 'optical', label: '光电传感器' },
{ key: 'communication', label: '通信规划' }
]
const activeTab = ref('weapon')
//
const entityConfig = computed({
get: () => props.entityConfig,
set: (value) => emit('update:entityConfig', value)
})
//
const selectedWeaponScheme = computed({
get: () => props.selectedWeaponScheme,
set: (value) => emit('update:selectedWeaponScheme', value)
})
//
const mountedWeapons = ref([
{ id: '1', name: 'PL-8', type: 'missile' },
{ id: '2', name: 'AIM-7', type: 'missile' },
{ id: '3', name: 'AIM-120', type: 'missile' },
{ id: '4', name: 'PL-12', type: 'missile' },
{ id: '5', name: 'PL-15', type: 'missile' },
{ id: '6', name: 'PL-10', type: 'missile' }
])
//
const selectWeaponScheme = (schemeId: string) => {
const scheme = props.weaponSchemes.find(s => s.id === schemeId)
if (scheme?.available) {
emit('weapon-scheme-select', schemeId)
}
}
const getWeaponIcon = (weapon: WeaponItem) => {
//
return '/weapon-icons/missile.svg'
}
</script>
<style lang="scss" scoped>
@use '@/styles/variables.scss' as *;
.mission-config-panel {
height: 100%;
display: flex;
flex-direction: column;
background: rgba(15, 20, 25, 0.6);
}
//
.tab-navigation {
display: flex;
background: rgba(20, 25, 30, 0.8);
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.tab-item {
padding: 12px 24px;
color: rgba(255, 255, 255, 0.7);
cursor: pointer;
border-bottom: 2px solid transparent;
transition: all 0.3s;
&:hover {
color: rgba(255, 255, 255, 0.9);
background: rgba(255, 255, 255, 0.05);
}
&.active {
color: #409eff;
border-bottom-color: #409eff;
background: rgba(64, 158, 255, 0.1);
}
}
//
.tab-content {
flex: 1;
overflow-y: auto;
padding: 16px;
}
.tab-panel {
height: 100%;
}
.panel-section {
margin-bottom: 24px;
h4 {
color: #ffffff;
margin: 0 0 16px 0;
font-size: 16px;
font-weight: 600;
}
}
//
.basic-info {
.info-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
}
.info-item {
display: flex;
flex-direction: column;
gap: 6px;
label {
color: rgba(255, 255, 255, 0.8);
font-size: 13px;
font-weight: 500;
}
:deep(.el-select),
:deep(.el-input) {
.el-input__wrapper {
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.el-input__inner {
color: #ffffff;
}
}
:deep(.el-select-dropdown) {
background: rgba(30, 35, 40, 0.95);
border: 1px solid rgba(255, 255, 255, 0.2);
.el-select-dropdown__item {
color: #ffffff;
&:hover {
background: rgba(64, 158, 255, 0.2);
}
}
}
}
}
//
.weapon-mount {
.weapon-schemes {
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 24px;
}
.weapon-scheme {
display: flex;
align-items: center;
gap: 12px;
padding: 8px 12px;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 6px;
cursor: pointer;
transition: all 0.3s;
&:hover:not(.disabled) {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.2);
}
&.active {
background: rgba(64, 158, 255, 0.2);
border-color: #409eff;
}
&.disabled {
opacity: 0.5;
cursor: not-allowed;
}
.scheme-indicator {
width: 12px;
height: 12px;
border: 2px solid rgba(255, 255, 255, 0.4);
border-radius: 50%;
.active & {
background: #409eff;
border-color: #409eff;
}
}
.scheme-name {
flex: 1;
color: #ffffff;
font-size: 14px;
}
.scheme-status {
color: #f56c6c;
font-size: 12px;
}
}
}
//
.weapon-diagram {
background: rgba(0, 0, 0, 0.3);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 8px;
padding: 24px;
margin-bottom: 16px;
.aircraft-container {
position: relative;
display: flex;
justify-content: center;
align-items: center;
min-height: 200px;
}
.aircraft-body {
.aircraft-svg {
width: 400px;
height: 200px;
}
}
.weapon-mount-points {
position: absolute;
width: 100%;
height: 100%;
.mount-point {
position: absolute;
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
img {
width: 40px;
height: 20px;
filter: brightness(0.8);
}
.mount-number {
background: #409eff;
color: #ffffff;
width: 20px;
height: 20px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: bold;
}
&.mount-1 { top: 50px; left: 50px; }
&.mount-2 { top: 80px; left: 120px; }
&.mount-3 { top: 30px; left: 200px; }
&.mount-4 { top: 30px; right: 200px; }
&.mount-5 { top: 80px; right: 120px; }
&.mount-6 { top: 50px; right: 50px; }
}
}
.weapon-description {
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid rgba(255, 255, 255, 0.1);
p {
color: rgba(255, 255, 255, 0.7);
margin: 0 0 8px 0;
font-size: 13px;
line-height: 1.4;
&:last-child {
margin-bottom: 0;
}
}
}
}
//
.fuel-management {
background: rgba(0, 0, 0, 0.2);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 6px;
padding: 16px;
.fuel-info {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
.fuel-label {
color: rgba(255, 255, 255, 0.8);
font-size: 14px;
}
.fuel-amount {
color: #ffffff;
font-size: 16px;
font-weight: 600;
}
}
.fuel-bar {
height: 8px;
background: rgba(255, 255, 255, 0.1);
border-radius: 4px;
overflow: hidden;
.fuel-progress {
height: 100%;
background: linear-gradient(90deg, #409eff, #66b1ff);
transition: width 0.3s;
}
}
}
//
@media (max-width: $breakpoint-md) {
.tab-navigation {
flex-wrap: wrap;
}
.tab-item {
padding: 8px 16px;
font-size: 13px;
}
.basic-info .info-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: $breakpoint-sm) {
.basic-info .info-grid {
grid-template-columns: 1fr;
}
.weapon-diagram .aircraft-svg {
width: 300px;
height: 150px;
}
}
</style>

View File

@ -1,23 +1,31 @@
<!-- WeaponStatsPanel.vue - 武器统计面板组件 --> <!-- WeaponStatsPanel.vue - 武器统计面板组件 -->
<template> <template>
<div class="weapon-stats-panel"> <div class="weapon-stats-panel">
<div class="stats-header"> <!-- 当前挂载重量 -->
<h3>武器统计</h3> <div class="weight-section">
<el-tag :type="statusType" size="small">{{ statusText }}</el-tag> <div class="weight-header">
</div> <h3>当前挂载重量</h3>
</div>
<div class="stats-content">
<div class="weapon-item" v-for="weapon in weapons" :key="weapon.id"> <div class="weight-display">
<div class="weapon-info"> <div class="weight-value">
<span class="weapon-name">{{ weapon.name }}</span> <span class="weight-number">{{ totalWeight }}t</span>
<span class="weapon-count">{{ weapon.count }}</span> <span class="weight-change" :class="{ 'positive': weightChange > 0, 'negative': weightChange < 0 }">
{{ weightChange > 0 ? '+' : '' }}{{ weightChange }}%
</span>
</div> </div>
<div class="weapon-bar"> </div>
<div </div>
class="weapon-progress"
:style="{ width: `${weapon.readiness}%` }" <!-- 武器清单 -->
:class="`weapon-progress--${weapon.type}`" <div class="weapon-inventory">
></div> <div class="weapon-list">
<div class="weapon-item" v-for="weapon in weaponInventory" :key="weapon.id">
<div class="weapon-icon">{{ weapon.icon }}</div>
<div class="weapon-details">
<div class="weapon-name">{{ weapon.name }}</div>
<div class="weapon-weight">{{ weapon.weight }}</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -25,44 +33,31 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue' interface WeaponItem {
interface Weapon {
id: string id: string
name: string name: string
count: number weight: string
readiness: number icon: string
type: 'primary' | 'secondary' | 'defense'
} }
const props = defineProps<{ const props = defineProps<{
weapons?: Weapon[] weaponInventory?: WeaponItem[]
status?: 'ready' | 'loading' | 'disabled' totalWeight?: number
weightChange?: number
}>() }>()
const weapons = computed(() => props.weapons || [ //
{ id: '1', name: '主炮', count: 2, readiness: 100, type: 'primary' as const }, const weaponInventory = props.weaponInventory || [
{ id: '2', name: '导弹', count: 8, readiness: 75, type: 'secondary' as const }, { id: '1', name: 'PL-8', weight: '1.6 t', icon: '🚀' },
{ id: '3', name: '防空炮', count: 4, readiness: 90, type: 'defense' as const } { id: '2', name: 'Aim-7雷管', weight: '1.6 t', icon: '🚀' },
]) { id: '3', name: 'AIM-120闪电拉姆', weight: '1.6 t', icon: '🚀' },
{ id: '4', name: 'PL-12', weight: '1.6 t', icon: '🚀' },
{ id: '5', name: 'PL-15', weight: '1.6 t', icon: '🚀' },
{ id: '6', name: 'PL-10', weight: '1.6 t', icon: '🚀' }
]
const statusType = computed(() => { const totalWeight = props.totalWeight || 16
switch (props.status) { const weightChange = props.weightChange || 19.6
case 'ready': return 'success'
case 'loading': return 'warning'
case 'disabled': return 'danger'
default: return 'info'
}
})
const statusText = computed(() => {
switch (props.status) {
case 'ready': return '就绪'
case 'loading': return '装填中'
case 'disabled': return '停用'
default: return '未知'
}
})
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@ -70,72 +65,114 @@ const statusText = computed(() => {
@use '@/styles/variables.scss' as *; @use '@/styles/variables.scss' as *;
.weapon-stats-panel { .weapon-stats-panel {
padding: $spacing-base;
background: $bg-secondary;
border-radius: $border-radius-base;
border: 1px solid $border-light;
}
.stats-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: $spacing-base;
h3 {
margin: 0;
color: $text-primary;
font-size: $font-size-lg;
font-weight: $font-weight-semibold;
}
}
.weapon-item {
margin-bottom: $spacing-sm;
&:last-child {
margin-bottom: 0;
}
}
.weapon-info {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: $spacing-xs;
}
.weapon-name {
color: $text-primary;
font-weight: $font-weight-medium;
}
.weapon-count {
color: $text-secondary;
font-size: $font-size-sm;
}
.weapon-bar {
height: 6px;
background: $border-light;
border-radius: $border-radius-small;
overflow: hidden;
}
.weapon-progress {
height: 100%; height: 100%;
transition: width $transition-base; padding: 16px;
background: rgba(15, 20, 25, 0.8);
color: #ffffff;
display: flex;
flex-direction: column;
}
//
.weight-section {
margin-bottom: 24px;
&--primary { .weight-header {
background: linear-gradient(90deg, $danger-color, color.adjust($danger-color, $lightness: 10%)); margin-bottom: 12px;
h3 {
margin: 0;
color: #ffffff;
font-size: 14px;
font-weight: 500;
}
} }
&--secondary { .weight-display {
background: linear-gradient(90deg, $warning-color, color.adjust($warning-color, $lightness: 10%)); .weight-value {
display: flex;
align-items: center;
gap: 8px;
.weight-number {
font-size: 24px;
font-weight: 600;
color: #ffffff;
}
.weight-change {
font-size: 12px;
font-weight: 500;
&.positive {
color: #67c23a;
}
&.negative {
color: #f56c6c;
}
}
}
}
}
//
.weapon-inventory {
flex: 1;
.weapon-list {
display: flex;
flex-direction: column;
gap: 8px;
} }
&--defense { .weapon-item {
background: linear-gradient(90deg, $success-color, color.adjust($success-color, $lightness: 10%)); display: flex;
align-items: center;
gap: 8px;
padding: 8px;
background: rgba(255, 255, 255, 0.05);
border-radius: 4px;
transition: background 0.3s;
&:hover {
background: rgba(255, 255, 255, 0.1);
}
.weapon-icon {
font-size: 16px;
width: 20px;
text-align: center;
}
.weapon-details {
flex: 1;
display: flex;
flex-direction: column;
gap: 2px;
.weapon-name {
color: #ffffff;
font-size: 12px;
font-weight: 500;
}
.weapon-weight {
color: rgba(255, 255, 255, 0.7);
font-size: 11px;
}
}
}
}
//
@media (max-width: $breakpoint-md) {
.weapon-stats-panel {
padding: 12px;
}
.weight-section .weight-display .weight-value .weight-number {
font-size: 20px;
} }
} }
</style> </style>

View File

@ -1,10 +1,11 @@
// 实体管理组件导出 // 实体管理组件导出
// ========================================== // ==========================================
export { default as EntityConfigPanel } from './EntityConfigPanel.vue'
export { default as WeaponStatsPanel } from './WeaponStatsPanel.vue'
export { default as MissionConfigPanel } from './MissionConfigPanel.vue'
// 暂时导出空对象,待后续添加实体相关组件 // 暂时导出空对象,待后续添加实体相关组件
// export { default as EntityCard } from './EntityCard.vue' // export { default as EntityCard } from './EntityCard.vue'
// export { default as EntityList } from './EntityList.vue' // export { default as EntityList } from './EntityList.vue'
// export { default as EntityForm } from './EntityForm.vue' // export { default as EntityForm } from './EntityForm.vue'
// 临时空导出,避免编译错误
export {}

View File

@ -97,8 +97,7 @@
<!-- 中央配置区域 --> <!-- 中央配置区域 -->
<div class="center-panel"> <div class="center-panel">
<EntityConfigPanel <MissionConfigPanel
v-model:activeTab="activeTab"
v-model:entityConfig="entityConfig" v-model:entityConfig="entityConfig"
v-model:selectedWeaponScheme="selectedWeaponScheme" v-model:selectedWeaponScheme="selectedWeaponScheme"
:weapon-schemes="weaponSchemes" :weapon-schemes="weaponSchemes"
@ -124,12 +123,11 @@ import { ref, onMounted } from 'vue'
import { import {
ArrowLeft, ArrowRight, Search, Refresh, Setting, Folder, Operation, Position ArrowLeft, ArrowRight, Search, Refresh, Setting, Folder, Operation, Position
} from '@element-plus/icons-vue' } from '@element-plus/icons-vue'
import EntityConfigPanel from '@/components/entity/EntityConfigPanel.vue' import MissionConfigPanel from '@/components/entity/MissionConfigPanel.vue'
import WeaponStatsPanel from '@/components/entity/WeaponStatsPanel.vue' import WeaponStatsPanel from '@/components/entity/WeaponStatsPanel.vue'
// //
const searchKeyword = ref('') const searchKeyword = ref('')
const activeTab = ref('weapon')
const selectedNode = ref<any>(null) const selectedNode = ref<any>(null)
const selectedWeaponScheme = ref('scheme1') const selectedWeaponScheme = ref('scheme1')
const fuelPercentage = ref(75) const fuelPercentage = ref(75)