|
|
@@ -8,9 +8,10 @@
|
|
|
*
|
|
|
* 迁移纪律(用户令 2026-09-29):页签逐个搬,**与重构前页面对拍通过后**才删对应 Python 前端源码。
|
|
|
*/
|
|
|
-import { onMounted, ref, watch } from 'vue'
|
|
|
+import { computed, onMounted, ref, watch } from 'vue'
|
|
|
import ContractSmoke from './views/ContractSmoke.vue'
|
|
|
import Workbench from './views/Workbench.vue'
|
|
|
+import { t } from './i18n/zh'
|
|
|
import PortalView from './views/PortalView.vue'
|
|
|
import { callApi } from './api/contract'
|
|
|
|
|
|
@@ -29,6 +30,15 @@ if (_tabInHash) tab.value = _tabInHash
|
|
|
const sub = ref<string | null>(null)
|
|
|
const d = ref<any>(null)
|
|
|
const win = ref<string>('')
|
|
|
+const WIN_PRESETS: [string, string][] = [['近30日', 'win.30d'], ['近90日', 'win.90d'], ['2026年', 'win.2026'], ['2026H1', 'win.2026H1'], ['2025H2', 'win.2025H2'], ['全程', 'win.all']]
|
|
|
+const WIN_SLUG: Record<string, string> = {'近30日': '30d', '近90日': '90d', '2026年': '2026', '2026H1': '2026H1', '2025H2': '2025H2', '全程': 'all'}
|
|
|
+const winSel = computed<string>(() => win.value || '')
|
|
|
+const isCustomWin = computed<boolean>(() => /^\d{4}-\d{2}-\d{2}~\d{4}-\d{2}-\d{2}$/.test(winSel.value))
|
|
|
+const mon = computed<string[]>(() => (((d.value || {}).all_months) || []).slice().reverse())
|
|
|
+const wa = ref<string>('')
|
|
|
+const wb2 = ref<string>('')
|
|
|
+function pickWin(ev: Event) { const v = (ev.target as HTMLSelectElement).value; win.value = WIN_SLUG[v] || v }
|
|
|
+function applyRange() { if (wa.value && wb2.value) win.value = wa.value + '~' + wb2.value }
|
|
|
const loading = ref(false)
|
|
|
const err = ref('')
|
|
|
|
|
|
@@ -74,7 +84,30 @@ onMounted(() => {
|
|
|
<PortalView v-if="mode === 'portal'" />
|
|
|
<div v-else class="wrap">
|
|
|
<header class="top">
|
|
|
- <div class="brand">观澜 · 风电数据分析与决策支持</div>
|
|
|
+ <span class="mark">≋</span>
|
|
|
+ <span class="brand">观澜<small>WIND ASSET INTELLIGENCE</small></span>
|
|
|
+ <div class="right">
|
|
|
+ <a class="back-portal" href="#" :title="t('ui.back_portal')" @click.prevent="go('portal')">{{ t('ui.back_portal') }}</a>
|
|
|
+ <button type="button" class="reselect" :title="t('ui.reselect')" @click="reselect">{{ t('ui.reselect') }}</button>
|
|
|
+ <label class="small muted" for="win">{{ t('win.label') }}</label>
|
|
|
+ <select id="win" :aria-label="t('win.label')" :value="winSel" @change="pickWin">
|
|
|
+ <optgroup :label="t('win.presets')">
|
|
|
+ <option v-for="[w2, k] in WIN_PRESETS" :key="w2" :value="w2">{{ t(k) }}</option>
|
|
|
+ </optgroup>
|
|
|
+ <optgroup v-if="isCustomWin" :label="t('win.custom')">
|
|
|
+ <option :value="winSel">{{ t('win.custom_sel', { a: winSel.split('~')[0], b: winSel.split('~')[1] }) }}</option>
|
|
|
+ </optgroup>
|
|
|
+ <optgroup v-if="mon.length" :label="t('win.months')">
|
|
|
+ <option v-for="m in mon" :key="m" :value="m">{{ m }}</option>
|
|
|
+ </optgroup>
|
|
|
+ </select>
|
|
|
+ <span class="winrng" :title="t('win.custom_hint')">
|
|
|
+ <input type="date" :aria-label="t('win.from')" v-model="wa">
|
|
|
+ <span class="muted">~</span>
|
|
|
+ <input type="date" :aria-label="t('win.to')" v-model="wb2">
|
|
|
+ <button type="button" @click="applyRange">{{ t('win.apply') }}</button>
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
<nav class="tabs">
|
|
|
<button :class="{ on: mode === 'workbench' }" @click="go('workbench')">工作台</button>
|
|
|
<button :class="{ on: mode === 'smoke' }" @click="go('smoke')">契约自检</button>
|