caoziwu 2 hete
szülő
commit
fd9d9eda3c

+ 93 - 3
src/renderer/src/assets/simulator.less

@@ -19,7 +19,7 @@
   gap: 14px;
   padding: 20px 0 18px;
   border-bottom: 1px solid var(--n-border-color, #efeff5);
-  margin-bottom: 18px;
+  margin-bottom: 16px;
 }
 .reslut-item {
   text-align: center;
@@ -64,7 +64,8 @@
 
 /* 面板卡片间距 */
 .panel-card {
-  margin-bottom: 18px;
+  position: relative;
+  margin-bottom: 16px;
 }
 .reslut-panel {
   padding: 1rem;
@@ -79,10 +80,37 @@
   padding: 4px 2px 2px;
 }
 
+/* 图表 Tab 与 结果面板 并列布局(左侧 tabs,右侧 ResultPanel) */
+.chart-result-wrap {
+  display: flex;
+  gap: 12px;
+  align-items: stretch;
+}
+.tabs-wrap {
+  flex: 1;
+  min-width: 0;
+}
+.result-wrap {
+  flex: 0 0 260px;
+  min-width: 200px;
+}
+
+/* 窄屏下堆叠为上下布局 */
+@media (max-width: 900px) {
+  .chart-result-wrap {
+    flex-direction: column;
+  }
+  .result-wrap {
+    flex: 1 1 auto;
+    width: auto;
+  }
+}
+
 /* 参数字段:标签(符号 + 名称 + 联动开关) */
 /* 设备型号下拉字段 */
 .model-field {
   margin-bottom: 16px;
+  margin-right: 16px;
 }
 
 /* 供电模式提示文字 */
@@ -152,6 +180,68 @@
 .assess-row {
   margin-top: 16px;
 }
+.device-info {
+  position: absolute;
+  top: 1rem;
+  right: 0;
+  display: flex;
+  margin-bottom: 10px;
+  .field-label {
+    margin-right: 18px;
+  }
+}
+/* 结果面板:竖直布局(替代原横向 NGrid/NGi/NStatistic) */
+.result-panel {
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+  margin-top: 2.2rem;
+}
+.result-list {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+}
+.result-item {
+  display: flex;
+  align-items: baseline;
+  justify-content: space-between;
+  padding: 8px 10px;
+  border-radius: 6px;
+  background: var(--n-fill-color, #f5f5f5);
+  font-size: 14px;
+}
+.result-label {
+  flex: 0 0 auto;
+  opacity: 0.75;
+  font-size: 13px;
+}
+.result-value {
+  flex: 1 1 auto;
+  text-align: right;
+  font-variant-numeric: tabular-nums;
+  font-weight: 500;
+}
+.result-item.diff-item {
+  font-weight: 600;
+}
+.result-item.diff-success {
+  color: var(--n-success-color, #19be6b);
+}
+.result-item.diff-warning {
+  color: var(--n-warning-color, #faad14);
+}
+.result-item.diff-error {
+  color: var(--n-error-color, #f5222d);
+}
+
+/* 旧类名保留,防止其他地方引用 */
+.reslut-panel {
+  padding: 1rem;
+}
+.reslut-item {
+  text-align: center;
+}
 
 /* 简介提示文字 */
 .intro-note {
@@ -167,7 +257,7 @@
 }
 
 .chart-box--diagram {
-  height: 400px;
+  height: 360px;
 }
 
 .chart-box--spl {

+ 14 - 15
src/renderer/src/components/simulator/ParamsPanel.vue

@@ -98,7 +98,7 @@ const onSelectModel = (model: string | null): void => {
   isExtension.value = false
   const firstOption = powerModeOptions.value[0]
   powerMode.value = firstOption ? firstOption.label : ''
-  formData.splMax = firstOption ? firstOption.value : 0
+  formData.splMax = firstOption ? firstOption.value : 20
   apply()
 }
 
@@ -176,6 +176,19 @@ const fields: FieldConfig[] = [
       />
     </div>
     <n-flex>
+      <div v-if="powerModeOptions.length > 0" class="field model-field">
+        <div class="field-label">
+          <span class="sym">⚡</span>
+          {{ $t('params.powerMode') }}
+          <div class="spl-mode-hint">{{ $t('params.splMaxAuto') }}</div>
+        </div>
+        <n-radio-group
+          v-model:value="powerMode"
+          :options="powerModeOptions"
+          value-field="label"
+          @update:value="onSelectPowerMode"
+        />
+      </div>
       <div
         class="field model-field"
         v-if="
@@ -193,20 +206,6 @@ const fields: FieldConfig[] = [
           {{ $t('params.hasSubwooferOn') }}
         </NCheckbox>
       </div>
-
-      <div v-if="powerModeOptions.length > 0" class="field model-field">
-        <div class="field-label">
-          <span class="sym">⚡</span>
-          {{ $t('params.powerMode') }}
-          <div class="spl-mode-hint">{{ $t('params.splMaxAuto') }}</div>
-        </div>
-        <n-radio-group
-          v-model:value="powerMode"
-          :options="powerModeOptions"
-          value-field="label"
-          @update:value="onSelectPowerMode"
-        />
-      </div>
     </n-flex>
     <NGrid cols="3" responsive="screen" :x-gap="14" :y-gap="14" item-responsive>
       <template v-for="field in fields" :key="field.key">

+ 39 - 54
src/renderer/src/components/simulator/ResultPanel.vue

@@ -57,60 +57,45 @@ const assessType = computed<'success' | 'info' | 'warning' | 'error'>(() =>
 </script>
 
 <template>
-  <div class="reslut-panel">
-    <NGrid cols="5" responsive="screen" :x-gap="10" item-responsive>
-      <NGi span="5 s:3 m:1">
-        <NStatistic
-          class="reslut-item"
-          :label="$t('result.near') + ' D1'"
-          :value="fmt(result?.s1)"
-          tabular-nums
-        >
-          <template #suffix><span class="unit">dB SPL</span></template>
-        </NStatistic>
-      </NGi>
-      <NGi span="5 s:2 m:1">
-        <NStatistic
-          class="reslut-item"
-          :label="$t('result.far') + ' D2'"
-          :value="fmt(result?.s2)"
-          tabular-nums
-        >
-          <template #suffix><span class="unit">dB SPL</span></template>
-        </NStatistic>
-      </NGi>
-      <NGi span="5 m:1">
-        <NStatistic
-          class="reslut-item"
-          :label="$t('result.axis') + ' D3'"
-          :value="fmt(result?.s3)"
-          tabular-nums
-        >
-          <template #suffix><span class="unit">dB SPL</span></template>
-        </NStatistic>
-      </NGi>
-      <NGi span="5 s:2 m:1">
-        <NStatistic
-          :label="$t('result.diff')"
-          :value="fmt(result?.diff)"
-          class="reslut-item"
-          tabular-nums
-          :class="result ? 'diff-' + diffColor(result.diff) : ''"
-        >
-          <template #suffix><span class="unit">dB SPL</span></template>
-        </NStatistic>
-      </NGi>
-      <NGi span="5 s:3 m:1">
-        <NStatistic
-          class="reslut-item"
-          :label="$t('result.avg')"
-          :value="fmt(result?.avg)"
-          tabular-nums
-        >
-          <template #suffix><span class="unit">dB SPL</span></template>
-        </NStatistic>
-      </NGi>
-    </NGrid>
+  <div class="result-panel">
+    <div class="result-list">
+      <div
+        class="result-item"
+        :class="{
+          'diff-item': true,
+          'diff-success': result && diffColor(result.diff) === 'success',
+          'diff-warning': result && diffColor(result.diff) === 'warning',
+          'diff-error': result && diffColor(result.diff) === 'error'
+        }"
+      >
+        <span class="result-label">{{ $t('result.near') + ' D1' }}</span>
+        <span class="result-value">{{ fmt(result?.s1) }}<span class="unit">dB SPL</span></span>
+      </div>
+      <div class="result-item">
+        <span class="result-label">{{ $t('result.far') + ' D2' }}</span>
+        <span class="result-value">{{ fmt(result?.s2) }}<span class="unit">dB SPL</span></span>
+      </div>
+      <div class="result-item">
+        <span class="result-label">{{ $t('result.axis') + ' D3' }}</span>
+        <span class="result-value">{{ fmt(result?.s3) }}<span class="unit">dB SPL</span></span>
+      </div>
+      <div
+        class="result-item"
+        :class="{
+          'diff-item': true,
+          'diff-success': result && diffColor(result.diff) === 'success',
+          'diff-warning': result && diffColor(result.diff) === 'warning',
+          'diff-error': result && diffColor(result.diff) === 'error'
+        }"
+      >
+        <span class="result-label">{{ $t('result.diff') }}</span>
+        <span class="result-value">{{ fmt(result?.diff) }}<span class="unit">dB SPL</span></span>
+      </div>
+      <div class="result-item">
+        <span class="result-label">{{ $t('result.avg') }}</span>
+        <span class="result-value">{{ fmt(result?.avg) }}<span class="unit">dB SPL</span></span>
+      </div>
+    </div>
 
     <div v-if="assessment" class="assess-row">
       <NAlert :type="assessType" :bordered="false">

+ 78 - 12
src/renderer/src/components/simulator/SpeakerSimulator.vue

@@ -19,6 +19,9 @@ import ResultPanel from './ResultPanel.vue'
 import SPLChartPanel from './SPLChartPanel.vue'
 import logo from '../../assets/logo.png'
 import BaseModel from '../BaseModel.vue'
+import { useI18n } from 'vue-i18n'
+
+const { t } = useI18n()
 
 const defaultParams: SpeakerParams = {
   splMax: 121,
@@ -46,7 +49,50 @@ const deviceState = reactive<DevicePanelState>({
   hCov: params.hCov,
   vCov: params.vCov
 })
-
+const deviceFields: any[] = [
+  {
+    key: 'model',
+    symbol: '⌘',
+    labelKey: 'params.model',
+    show: '',
+    unit: ''
+  },
+  {
+    key: 'isExtension',
+    symbol: '🔊',
+    labelKey: 'params.hasSubwoofer',
+    show: '',
+    unit: ''
+  },
+  {
+    key: 'powerMode',
+    symbol: '⚡',
+    labelKey: 'params.powerMode',
+    show: '',
+    unit: ''
+  },
+  {
+    key: 'splMax',
+    symbol: 'SPLₘₐₓ',
+    labelKey: 'params.splMax',
+    show: 'custom',
+    unit: 'dB SPL'
+  },
+  {
+    key: 'hCov',
+    symbol: 'H',
+    labelKey: 'params.hCov',
+    show: 'custom',
+    unit: '°'
+  },
+  {
+    key: 'vCov',
+    symbol: 'V',
+    labelKey: 'params.vCov',
+    show: 'custom',
+    unit: '°'
+  }
+]
 const patchDeviceState = (patch: Partial<DevicePanelState>): void => {
   Object.assign(deviceState, patch)
 }
@@ -93,7 +139,15 @@ const reset = (): void => {
   params.d3 = a > 0.05 && z > 0 ? Number((z / Math.tan(a * DEG)).toFixed(2)) : 0
   runCalc()
 }
-
+const computeLabel = (item) => {
+  if (item.key == 'isExtension')
+    return deviceState[item.key] ? t('params.withSubwoofer') : t('params.withoutSubwoofer')
+  return `${t(item.labelKey)}: ${deviceState[item.key]} ${item.unit}`
+}
+const showLabel = (item) => {
+  if (deviceState.model == 'custom') return item.show == 'custom'
+  else return item.show != 'custom'
+}
 runCalc()
 </script>
 
@@ -119,6 +173,14 @@ runCalc()
     </header>
 
     <NCard :title="$t('params.tuner')" size="small" class="panel-card">
+      <div class="device-info">
+        <template v-for="item in deviceFields" :key="item.key">
+          <div class="field-label" v-if="showLabel(item)">
+            <span class="sym">{{ item.symbol }}</span>
+            {{ computeLabel(item) }}
+          </div>
+        </template>
+      </div>
       <ParamTuner
         :params="params"
         :linked="linked"
@@ -130,17 +192,21 @@ runCalc()
     <!-- <ResultPanel :result="result" :assessment="assessment" /> -->
 
     <NCard size="small" class="panel-card chart-card">
-      <NTabs type="line" animated>
-        <NTabPane name="diagram" :tab="$t('diagram.panel')">
-          <DiagramPanel :params="params" :result="result" />
-        </NTabPane>
-        <NTabPane name="chart" :tab="$t('chart.panel')">
-          <SPLChartPanel :params="params" :result="result" />
-        </NTabPane>
-        <NTabPane name="result" :tab="$t('result.panel')">
+      <div class="chart-result-wrap">
+        <div class="tabs-wrap">
+          <NTabs type="line" animated>
+            <NTabPane name="diagram" :tab="$t('diagram.panel')">
+              <DiagramPanel :params="params" :result="result" />
+            </NTabPane>
+            <NTabPane name="chart" :tab="$t('chart.panel')">
+              <SPLChartPanel :params="params" :result="result" />
+            </NTabPane>
+          </NTabs>
+        </div>
+        <div class="result-wrap">
           <ResultPanel :result="result" :assessment="assessment" />
-        </NTabPane>
-      </NTabs>
+        </div>
+      </div>
     </NCard>
 
     <!-- 设备选型弹框 -->

+ 12 - 12
src/renderer/src/config/index.ts

@@ -58,9 +58,9 @@ export const devicesConfig = [
         { label: 'PoE', value: 95 }
       ],
       extension: [
-        { label: 'DC', value: 2 * 97 },
-        { label: 'PoE+', value: 2 * 95 },
-        { label: 'PoE', value: 2 * 92 }
+        { label: 'DC', value: 97 },
+        { label: 'PoE+', value: 95 },
+        { label: 'PoE', value: 92 }
       ]
     }
   },
@@ -75,9 +75,9 @@ export const devicesConfig = [
         { label: 'PoE', value: 93 }
       ],
       extension: [
-        { label: 'DC', value: 2 * 95 },
-        { label: 'PoE+', value: 2 * 93 },
-        { label: 'PoE', value: 2 * 90 }
+        { label: 'DC', value: 95 },
+        { label: 'PoE+', value: 93 },
+        { label: 'PoE', value: 90 }
       ]
     }
   },
@@ -92,9 +92,9 @@ export const devicesConfig = [
         { label: 'PoE', value: 93 }
       ],
       extension: [
-        { label: 'DC', value: 2 * 95 },
-        { label: 'PoE+', value: 2 * 93 },
-        { label: 'PoE', value: 2 * 90 }
+        { label: 'DC', value: 95 },
+        { label: 'PoE+', value: 93 },
+        { label: 'PoE', value: 90 }
       ]
     }
   },
@@ -109,9 +109,9 @@ export const devicesConfig = [
         { label: 'PoE', value: 115 }
       ],
       extension: [
-        { label: 'DC', value: 2 * 118 },
-        { label: 'PoE+', value: 2 * 114 },
-        { label: 'PoE', value: 2 * 112 }
+        { label: 'DC', value: 118 },
+        { label: 'PoE+', value: 114 },
+        { label: 'PoE', value: 112 }
       ]
     }
   }

+ 2 - 0
src/renderer/src/locales/lang/en_US.json

@@ -52,6 +52,8 @@
     "linkOff": "Click to link A and D3",
     "custom":"Custom",
     "hasSubwoofer": "With Extension Speaker ",
+    "withSubwoofer": "With Extension Speaker",
+    "withoutSubwoofer": "Without Extension Speaker",
     "hasSubwooferOn": "Equipped with Extension Speaker ",
     "powerMode": "Power Mode",
     "splMaxAuto": "Max SPL will be auto-filled by selected mode",

+ 2 - 0
src/renderer/src/locales/lang/zh_CN.json

@@ -52,6 +52,8 @@
     "linkOff": "点击关联 A 与 D3",
     "custom":"自定义",
     "hasSubwoofer": "是否有扩展音箱",
+    "withSubwoofer": "已配备扩展音箱",
+    "withoutSubwoofer": "未配备扩展音箱",
     "hasSubwooferOn": "配备扩展音箱",
     "powerMode": "供电模式",
     "splMaxAuto": "最大声压级将按所选模式自动填充",