|
|
@@ -0,0 +1,461 @@
|
|
|
+<template>
|
|
|
+ <div v-if="!metricCode" class="dim-cfg-empty">请先在「指标管理」选择一个 KPI</div>
|
|
|
+ <div v-else v-loading="loading" class="dim-cfg">
|
|
|
+ <div class="dim-cfg__head">
|
|
|
+ <h3>{{ metricCode }} · 维度配置</h3>
|
|
|
+ <span class="dim-cfg__sub">配置化 DIMENSION_SQL,用于卡片筛选与下钻;与汇总口径解耦</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 当前维度配置概览 -->
|
|
|
+ <el-descriptions v-if="current" :column="4" border size="small" class="dim-cfg__overview">
|
|
|
+ <el-descriptions-item label="维度版本">v{{ current.dimensionConfigVersion }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="绑定汇总版本">v{{ current.summaryConfigVersion }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="当前汇总版本">v{{ activeSummaryVersion ?? '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="版本一致">
|
|
|
+ <el-tag :type="isCurrentBindingConsistent ? 'success' : 'danger'" size="small">
|
|
|
+ {{ isCurrentBindingConsistent ? '一致' : '不一致' }}
|
|
|
+ </el-tag>
|
|
|
+ </el-descriptions-item>
|
|
|
+ <el-descriptions-item label="数据源">{{ current.dataSourceCode || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="聚合类型">{{ current.aggregationType }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="支持维度">{{ currentDimsText }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="发布状态">{{ current.publishStatus }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="最后运行">
|
|
|
+ <el-tag :type="runTagType(capability?.lastRunStatus)" size="small">{{ capability?.lastRunStatus || '—' }}</el-tag>
|
|
|
+ <span class="dim-cfg__muted"> {{ capability?.lastRunAt || '' }}</span>
|
|
|
+ </el-descriptions-item>
|
|
|
+ </el-descriptions>
|
|
|
+ <el-alert v-else type="info" :closable="false" show-icon title="当前 KPI 尚未配置维度 SQL,可新建草稿后校验、试算、发布、激活。" class="dim-cfg__overview" />
|
|
|
+
|
|
|
+ <el-alert
|
|
|
+ v-if="current && !isCurrentBindingConsistent"
|
|
|
+ type="warning"
|
|
|
+ :closable="false"
|
|
|
+ show-icon
|
|
|
+ class="dim-cfg__warn"
|
|
|
+ title="当前维度配置绑定的汇总版本与生效汇总版本不一致,看板将标记 version_mismatch 且不回落。请复制新版本、重新绑定当前汇总后发布激活。"
|
|
|
+ />
|
|
|
+
|
|
|
+ <!-- 版本列表 -->
|
|
|
+ <div class="dim-cfg__bar">
|
|
|
+ <span class="dim-cfg__bar-title">配置版本</span>
|
|
|
+ <el-button size="small" @click="load">刷新</el-button>
|
|
|
+ <el-button size="small" type="primary" plain @click="newDraft">新建草稿</el-button>
|
|
|
+ <el-button size="small" :loading="running" @click="doRun">运行一次</el-button>
|
|
|
+ </div>
|
|
|
+ <el-table :data="versions" size="small" border class="dim-cfg__versions">
|
|
|
+ <el-table-column prop="dimensionConfigVersion" label="版本" width="64" />
|
|
|
+ <el-table-column label="绑定汇总" width="90">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <span :class="{ 'dim-cfg__badmatch': isMismatch(row) }">v{{ row.summaryConfigVersion }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="publishStatus" label="状态" width="96" />
|
|
|
+ <el-table-column label="生效" width="60">
|
|
|
+ <template #default="{ row }"><el-tag v-if="row.isCurrent" type="success" size="small">生效</el-tag></template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="dataSourceCode" label="数据源" width="110" />
|
|
|
+ <el-table-column prop="aggregationType" label="聚合" width="140" />
|
|
|
+ <el-table-column prop="createdAt" label="创建时间" width="150" />
|
|
|
+ <el-table-column label="操作" min-width="260">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-button link size="small" @click="loadInto(row)">载入</el-button>
|
|
|
+ <el-button link size="small" @click="copyFrom(row)">复制新版本</el-button>
|
|
|
+ <el-button
|
|
|
+ v-if="row.publishStatus === CONFIG_STATUS.DRAFT"
|
|
|
+ link type="primary" size="small" @click="doPublish(row)"
|
|
|
+ >发布</el-button>
|
|
|
+ <el-button
|
|
|
+ v-if="row.publishStatus === CONFIG_STATUS.PUBLISHED && !row.isCurrent"
|
|
|
+ link type="warning" size="small"
|
|
|
+ :disabled="isMismatch(row)" :title="isMismatch(row) ? '绑定汇总版本不一致,禁止激活' : ''"
|
|
|
+ @click="doActivate(row)"
|
|
|
+ >激活</el-button>
|
|
|
+ <el-button
|
|
|
+ v-if="row.publishStatus !== CONFIG_STATUS.RETIRED"
|
|
|
+ link type="danger" size="small" @click="doRetire(row)"
|
|
|
+ >退役</el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+
|
|
|
+ <!-- 草稿编辑表单(仅草稿可编辑;已发布/退役只读) -->
|
|
|
+ <div class="dim-cfg__form">
|
|
|
+ <div class="dim-cfg__form-head">
|
|
|
+ <span>{{ draft.id ? (isDraftEditable ? '编辑草稿 v' + draft.dimensionConfigVersion : '查看 v' + draft.dimensionConfigVersion + '(' + draft.publishStatus + ',只读)') : '新建草稿' }}</span>
|
|
|
+ </div>
|
|
|
+ <el-form :inline="true" size="small">
|
|
|
+ <el-form-item label="数据源">
|
|
|
+ <el-select v-model="draft.dataSourceCode" style="width: 200px" :disabled="!isDraftEditable">
|
|
|
+ <el-option v-for="ds in dataSources" :key="ds.code" :label="ds.name" :value="ds.code" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="聚合类型">
|
|
|
+ <el-select v-model="draft.aggregationType" style="width: 180px" :disabled="!isDraftEditable">
|
|
|
+ <el-option v-for="a in AGG_OPTIONS" :key="a" :label="a" :value="a" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="超时(秒)">
|
|
|
+ <el-input-number v-model="draft.timeoutSeconds" :min="1" :max="120" :disabled="!isDraftEditable" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="支持维度">
|
|
|
+ <el-select v-model="draft.supportedDimensions" multiple style="width: 320px" :disabled="!isDraftEditable">
|
|
|
+ <el-option v-for="d in DIM_OPTIONS" :key="d.value" :label="d.label" :value="d.value" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <el-form :inline="true" size="small">
|
|
|
+ <el-form-item label="业务来源">
|
|
|
+ <el-input v-model="draft.businessSqlSource" style="width: 260px" :disabled="!isDraftEditable" placeholder="DIMENSION_SQL 业务口径来源" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="变更说明">
|
|
|
+ <el-input v-model="draft.changeRemark" style="width: 300px" :disabled="!isDraftEditable" placeholder="本次维度配置变更说明" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+
|
|
|
+ <div class="dim-cfg__params">
|
|
|
+ 可用参数:@tenant_id @factory_id @biz_date @ztid | 结果契约(列):value_date(必需) dimension_type(必需) dimension_code(必需)
|
|
|
+ dimension_name metric_value numerator denominator sum_value sample_count source_key | 只允许 SELECT/WITH
|
|
|
+ </div>
|
|
|
+ <SqlEditor ref="sqlEditorRef" v-model="draft.sqlScript" height="240px" :readonly="!isDraftEditable" />
|
|
|
+
|
|
|
+ <div class="dim-cfg__actions">
|
|
|
+ <el-button size="small" @click="doValidate">校验</el-button>
|
|
|
+ <el-button size="small" :loading="previewing" @click="doPreview">试算</el-button>
|
|
|
+ <el-button v-if="isDraftEditable" type="primary" size="small" :loading="saving" @click="saveDraft">保存草稿</el-button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div v-if="validation" class="dim-cfg__line" :class="validationOk ? 'ok' : 'err'">
|
|
|
+ 校验:{{ validation.validationStatus || validation.status }}
|
|
|
+ <span v-if="validation.errorMessage">|{{ validation.errorCode }}: {{ validation.errorMessage }}</span>
|
|
|
+ <span v-if="validation.outputColumns?.length">|输出列:{{ validation.outputColumns.join(', ') }}</span>
|
|
|
+ <span v-if="validation.missingColumns?.length" class="dim-cfg__miss">|缺失列:{{ validation.missingColumns.join(', ') }}</span>
|
|
|
+ </div>
|
|
|
+ <div v-if="preview" class="dim-cfg__line" :class="preview.ok ? 'ok' : 'err'">
|
|
|
+ 试算:{{ preview.resultStatus }} | 行数={{ preview.rowCount }} | 耗时={{ preview.durationMs }}ms
|
|
|
+ <span v-if="preview.errorMessage">|{{ preview.errorCode }}: {{ preview.errorMessage }}</span>
|
|
|
+ <div v-if="preview.outputColumns?.length" class="dim-cfg__muted">输出列:{{ preview.outputColumns.join(', ') }}</div>
|
|
|
+ <el-table v-if="preview.sampleRows?.length" :data="preview.sampleRows" size="small" border class="dim-cfg__preview-table">
|
|
|
+ <el-table-column v-for="col in previewColumns" :key="col" :prop="col" :label="col" min-width="110" show-overflow-tooltip />
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 维度运行日志 -->
|
|
|
+ <div class="dim-cfg__bar">
|
|
|
+ <span class="dim-cfg__bar-title">维度运行日志</span>
|
|
|
+ <el-button size="small" @click="loadRunLogs">刷新</el-button>
|
|
|
+ </div>
|
|
|
+ <el-table :data="runLogs" size="small" border class="dim-cfg__runlog">
|
|
|
+ <el-table-column prop="batchId" label="批次" min-width="200" show-overflow-tooltip />
|
|
|
+ <el-table-column prop="dimensionConfigVersion" label="维度版本" width="90" />
|
|
|
+ <el-table-column prop="summaryConfigVersion" label="汇总版本" width="90" />
|
|
|
+ <el-table-column prop="valueDate" label="业务日期" width="150" />
|
|
|
+ <el-table-column label="状态" width="96">
|
|
|
+ <template #default="{ row }"><el-tag :type="runTagType(row.status)" size="small">{{ row.status }}</el-tag></template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="rowCount" label="行数" width="80" align="right" />
|
|
|
+ <el-table-column prop="durationMs" label="耗时(ms)" width="100" align="right" />
|
|
|
+ <el-table-column prop="errorMessage" label="错误摘要" min-width="180" show-overflow-tooltip />
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { ref, computed, watch } from 'vue';
|
|
|
+import { ElMessage, ElMessageBox } from 'element-plus';
|
|
|
+import SqlEditor from './SqlEditor.vue';
|
|
|
+import {
|
|
|
+ getDimensionConfigByMetric,
|
|
|
+ getDimensionCapability,
|
|
|
+ getDimensionDataSources,
|
|
|
+ createDimensionDraft,
|
|
|
+ updateDimensionDraft,
|
|
|
+ validateDimensionSql,
|
|
|
+ previewDimensionConfig,
|
|
|
+ publishDimensionConfig,
|
|
|
+ activateDimensionConfig,
|
|
|
+ retireDimensionConfig,
|
|
|
+ runDimension,
|
|
|
+ getDimensionRunLog,
|
|
|
+ type KpiDimensionConfig,
|
|
|
+ type KpiDimensionCapability,
|
|
|
+} from '../../api/kpiDimensionConfigApi';
|
|
|
+import { CONFIG_STATUS, DIM_FIELD_LABEL, DIM_FIELD, unwrapDim } from './kpiDimensionConsts';
|
|
|
+
|
|
|
+const props = defineProps<{ metricCode?: string; moduleCode?: string }>();
|
|
|
+
|
|
|
+const AGG_OPTIONS = ['DIRECT_VALUE', 'RATIO_OF_SUMS', 'AVERAGE_OF_SUMS', 'SUMMARY_ONLY'];
|
|
|
+const DIM_OPTIONS = Object.values(DIM_FIELD).map((value) => ({ value, label: DIM_FIELD_LABEL[value] || value }));
|
|
|
+
|
|
|
+const loading = ref(false);
|
|
|
+const saving = ref(false);
|
|
|
+const previewing = ref(false);
|
|
|
+const running = ref(false);
|
|
|
+const versions = ref<KpiDimensionConfig[]>([]);
|
|
|
+const capability = ref<KpiDimensionCapability | null>(null);
|
|
|
+const dataSources = ref<{ code: string; name: string }[]>([]);
|
|
|
+const runLogs = ref<any[]>([]);
|
|
|
+const validation = ref<any>(null);
|
|
|
+const preview = ref<any>(null);
|
|
|
+
|
|
|
+interface DraftForm {
|
|
|
+ id?: string | number;
|
|
|
+ dimensionConfigVersion?: number;
|
|
|
+ publishStatus?: string;
|
|
|
+ dataSourceCode?: string;
|
|
|
+ sqlScript: string;
|
|
|
+ aggregationType: string;
|
|
|
+ supportedDimensions: string[];
|
|
|
+ timeoutSeconds: number;
|
|
|
+ businessSqlSource?: string;
|
|
|
+ changeRemark?: string;
|
|
|
+}
|
|
|
+function emptyDraft(): DraftForm {
|
|
|
+ return { sqlScript: '', aggregationType: 'AVERAGE_OF_SUMS', supportedDimensions: [], timeoutSeconds: 60 };
|
|
|
+}
|
|
|
+const draft = ref<DraftForm>(emptyDraft());
|
|
|
+const sqlEditorRef = ref<any>(null);
|
|
|
+
|
|
|
+const current = computed<KpiDimensionConfig | null>(() => versions.value.find((v) => v.isCurrent) || null);
|
|
|
+// 生效汇总版本:能力接口回传的绑定汇总版本即当前生效汇总版本
|
|
|
+const activeSummaryVersion = computed<number | null>(() => capability.value?.summaryConfigVersion ?? null);
|
|
|
+const isCurrentBindingConsistent = computed(() => {
|
|
|
+ if (!current.value) return true;
|
|
|
+ if (activeSummaryVersion.value == null) return true;
|
|
|
+ return current.value.summaryConfigVersion === activeSummaryVersion.value;
|
|
|
+});
|
|
|
+function isMismatch(row: KpiDimensionConfig): boolean {
|
|
|
+ return activeSummaryVersion.value != null && row.summaryConfigVersion !== activeSummaryVersion.value;
|
|
|
+}
|
|
|
+const currentDimsText = computed(() => parseDims(current.value?.supportedDimensionsJson).map((d) => DIM_FIELD_LABEL[d] || d).join('、') || '-');
|
|
|
+const isDraftEditable = computed(() => !draft.value.id || draft.value.publishStatus === CONFIG_STATUS.DRAFT);
|
|
|
+const validationOk = computed(() => {
|
|
|
+ const s = validation.value?.validationStatus || validation.value?.status;
|
|
|
+ return s === 'OK' || s === 'PASSED' || s === 'VALID' || validation.value?.ok === true;
|
|
|
+});
|
|
|
+const previewColumns = computed<string[]>(() => {
|
|
|
+ const first = preview.value?.sampleRows?.[0];
|
|
|
+ return first ? Object.keys(first) : [];
|
|
|
+});
|
|
|
+
|
|
|
+function parseDims(json?: string | null): string[] {
|
|
|
+ if (!json) return [];
|
|
|
+ try {
|
|
|
+ const v = JSON.parse(json);
|
|
|
+ return Array.isArray(v) ? v : [];
|
|
|
+ } catch {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+}
|
|
|
+function runTagType(s?: string | null) {
|
|
|
+ if (s === 'SUCCESS') return 'success';
|
|
|
+ if (s === 'FAILED') return 'danger';
|
|
|
+ if (s === 'NO_DATA') return 'info';
|
|
|
+ return 'info';
|
|
|
+}
|
|
|
+
|
|
|
+async function load() {
|
|
|
+ if (!props.metricCode || !props.moduleCode) return;
|
|
|
+ loading.value = true;
|
|
|
+ try {
|
|
|
+ const [vRes, cRes, dsRes] = await Promise.all([
|
|
|
+ getDimensionConfigByMetric(props.metricCode, props.moduleCode),
|
|
|
+ getDimensionCapability(props.metricCode, props.moduleCode),
|
|
|
+ getDimensionDataSources(),
|
|
|
+ ]);
|
|
|
+ const vList = unwrapDim<any>(vRes);
|
|
|
+ versions.value = Array.isArray(vList) ? vList : vList?.list || [];
|
|
|
+ capability.value = unwrapDim<KpiDimensionCapability>(cRes) || null;
|
|
|
+ const ds = unwrapDim<any>(dsRes);
|
|
|
+ const arr = Array.isArray(ds) ? ds : ds?.list || [];
|
|
|
+ dataSources.value = arr.map((x: any) => ({ code: x.code ?? x.dataSourceCode ?? x.value, name: x.name ?? x.dataSourceName ?? x.code ?? x.dataSourceCode }));
|
|
|
+ await loadRunLogs();
|
|
|
+ } catch (e: any) {
|
|
|
+ ElMessage.error('加载维度配置失败:' + (e?.response?.data?.message || e?.message || e));
|
|
|
+ } finally {
|
|
|
+ loading.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function loadRunLogs() {
|
|
|
+ if (!props.metricCode || !props.moduleCode) return;
|
|
|
+ try {
|
|
|
+ const res = unwrapDim<any>(await getDimensionRunLog(props.metricCode, { moduleCode: props.moduleCode, page: 1, pageSize: 20 }));
|
|
|
+ runLogs.value = Array.isArray(res) ? res : res?.list || res?.items || [];
|
|
|
+ } catch {
|
|
|
+ runLogs.value = [];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function newDraft() {
|
|
|
+ draft.value = emptyDraft();
|
|
|
+ validation.value = null;
|
|
|
+ preview.value = null;
|
|
|
+ sqlEditorRef.value?.refresh?.();
|
|
|
+}
|
|
|
+function loadInto(row: KpiDimensionConfig) {
|
|
|
+ draft.value = {
|
|
|
+ id: row.id,
|
|
|
+ dimensionConfigVersion: row.dimensionConfigVersion,
|
|
|
+ publishStatus: row.publishStatus,
|
|
|
+ dataSourceCode: row.dataSourceCode || undefined,
|
|
|
+ sqlScript: row.sqlScript || '',
|
|
|
+ aggregationType: row.aggregationType || 'AVERAGE_OF_SUMS',
|
|
|
+ supportedDimensions: parseDims(row.supportedDimensionsJson),
|
|
|
+ timeoutSeconds: row.timeoutSeconds || 60,
|
|
|
+ businessSqlSource: row.businessSqlSource || undefined,
|
|
|
+ changeRemark: row.changeRemark || undefined,
|
|
|
+ };
|
|
|
+ validation.value = null;
|
|
|
+ preview.value = null;
|
|
|
+ sqlEditorRef.value?.refresh?.();
|
|
|
+}
|
|
|
+function copyFrom(row: KpiDimensionConfig) {
|
|
|
+ loadInto(row);
|
|
|
+ // 去掉 id → 视为新草稿,后端分配新维度版本并重新绑定当前汇总
|
|
|
+ draft.value.id = undefined;
|
|
|
+ draft.value.dimensionConfigVersion = undefined;
|
|
|
+ draft.value.publishStatus = undefined;
|
|
|
+ ElMessage.info('已复制为新草稿,保存后将绑定当前汇总版本');
|
|
|
+}
|
|
|
+
|
|
|
+function buildUpsertPayload() {
|
|
|
+ return {
|
|
|
+ metricCode: props.metricCode,
|
|
|
+ moduleCode: props.moduleCode,
|
|
|
+ dataSourceCode: draft.value.dataSourceCode,
|
|
|
+ sqlScript: draft.value.sqlScript,
|
|
|
+ aggregationType: draft.value.aggregationType,
|
|
|
+ supportedDimensionsJson: JSON.stringify(draft.value.supportedDimensions || []),
|
|
|
+ timeoutSeconds: draft.value.timeoutSeconds,
|
|
|
+ businessSqlSource: draft.value.businessSqlSource,
|
|
|
+ changeRemark: draft.value.changeRemark,
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+async function doValidate() {
|
|
|
+ if (!draft.value.sqlScript?.trim()) return ElMessage.warning('请先填写 DIMENSION_SQL');
|
|
|
+ try {
|
|
|
+ validation.value = unwrapDim<any>(await validateDimensionSql(draft.value.sqlScript));
|
|
|
+ } catch (e: any) {
|
|
|
+ ElMessage.error('校验失败:' + (e?.response?.data?.message || e?.message || e));
|
|
|
+ }
|
|
|
+}
|
|
|
+async function doPreview() {
|
|
|
+ if (!draft.value.sqlScript?.trim()) return ElMessage.warning('请先填写 DIMENSION_SQL');
|
|
|
+ previewing.value = true;
|
|
|
+ try {
|
|
|
+ preview.value = unwrapDim<any>(
|
|
|
+ await previewDimensionConfig({
|
|
|
+ metricCode: props.metricCode,
|
|
|
+ moduleCode: props.moduleCode,
|
|
|
+ dataSourceCode: draft.value.dataSourceCode,
|
|
|
+ sqlScript: draft.value.sqlScript,
|
|
|
+ aggregationType: draft.value.aggregationType,
|
|
|
+ timeoutSeconds: draft.value.timeoutSeconds,
|
|
|
+ })
|
|
|
+ );
|
|
|
+ } catch (e: any) {
|
|
|
+ ElMessage.error('试算失败:' + (e?.response?.data?.message || e?.message || e));
|
|
|
+ } finally {
|
|
|
+ previewing.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+async function saveDraft() {
|
|
|
+ if (!draft.value.sqlScript?.trim()) return ElMessage.warning('请先填写 DIMENSION_SQL');
|
|
|
+ saving.value = true;
|
|
|
+ try {
|
|
|
+ if (draft.value.id) await updateDimensionDraft(draft.value.id, buildUpsertPayload());
|
|
|
+ else await createDimensionDraft(buildUpsertPayload());
|
|
|
+ ElMessage.success('草稿已保存');
|
|
|
+ await load();
|
|
|
+ } catch (e: any) {
|
|
|
+ ElMessage.error('保存失败:' + (e?.response?.data?.message || e?.message || e));
|
|
|
+ } finally {
|
|
|
+ saving.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+async function doPublish(row: KpiDimensionConfig) {
|
|
|
+ try {
|
|
|
+ await publishDimensionConfig(row.id);
|
|
|
+ ElMessage.success('已发布');
|
|
|
+ await load();
|
|
|
+ } catch (e: any) {
|
|
|
+ ElMessage.error('发布失败:' + (e?.response?.data?.message || e?.message || e));
|
|
|
+ }
|
|
|
+}
|
|
|
+async function doActivate(row: KpiDimensionConfig) {
|
|
|
+ if (isMismatch(row)) return ElMessage.warning('绑定汇总版本不一致,禁止激活');
|
|
|
+ try {
|
|
|
+ await activateDimensionConfig(row.id);
|
|
|
+ ElMessage.success('已激活');
|
|
|
+ await load();
|
|
|
+ } catch (e: any) {
|
|
|
+ ElMessage.error('激活失败:' + (e?.response?.data?.message || e?.message || e));
|
|
|
+ }
|
|
|
+}
|
|
|
+async function doRetire(row: KpiDimensionConfig) {
|
|
|
+ try {
|
|
|
+ await ElMessageBox.confirm('退役后该版本不可再激活,确认?', '提示', { type: 'warning' });
|
|
|
+ } catch {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ await retireDimensionConfig(row.id);
|
|
|
+ ElMessage.success('已退役');
|
|
|
+ await load();
|
|
|
+ } catch (e: any) {
|
|
|
+ ElMessage.error('退役失败:' + (e?.response?.data?.message || e?.message || e));
|
|
|
+ }
|
|
|
+}
|
|
|
+async function doRun() {
|
|
|
+ if (!props.metricCode || !props.moduleCode) return;
|
|
|
+ running.value = true;
|
|
|
+ try {
|
|
|
+ const res = unwrapDim<any>(await runDimension(props.metricCode, props.moduleCode));
|
|
|
+ ElMessage.success('已运行:' + (res?.status || 'OK') + (res?.rowCount != null ? `,行数 ${res.rowCount}` : ''));
|
|
|
+ await load();
|
|
|
+ } catch (e: any) {
|
|
|
+ ElMessage.error('运行失败:' + (e?.response?.data?.message || e?.message || e));
|
|
|
+ } finally {
|
|
|
+ running.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+watch(
|
|
|
+ () => [props.metricCode, props.moduleCode],
|
|
|
+ () => {
|
|
|
+ versions.value = [];
|
|
|
+ capability.value = null;
|
|
|
+ runLogs.value = [];
|
|
|
+ draft.value = emptyDraft();
|
|
|
+ validation.value = null;
|
|
|
+ preview.value = null;
|
|
|
+ load();
|
|
|
+ },
|
|
|
+ { immediate: true }
|
|
|
+);
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.dim-cfg-empty { padding: 40px; text-align: center; color: var(--el-text-color-secondary); }
|
|
|
+.dim-cfg { display: flex; flex-direction: column; gap: 12px; }
|
|
|
+.dim-cfg__head h3 { margin: 0; font-size: 15px; }
|
|
|
+.dim-cfg__sub { font-size: 12px; color: var(--el-text-color-secondary); }
|
|
|
+.dim-cfg__overview { margin-top: 4px; }
|
|
|
+.dim-cfg__warn { margin: 4px 0; }
|
|
|
+.dim-cfg__bar { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
|
|
|
+.dim-cfg__bar-title { font-weight: 600; font-size: 14px; margin-right: 4px; }
|
|
|
+.dim-cfg__badmatch { color: var(--el-color-danger); font-weight: 600; }
|
|
|
+.dim-cfg__form { border: 1px solid var(--el-border-color-lighter); border-radius: 6px; padding: 12px; }
|
|
|
+.dim-cfg__form-head { font-weight: 600; margin-bottom: 8px; }
|
|
|
+.dim-cfg__params { font-size: 12px; color: var(--el-text-color-secondary); margin: 6px 0; line-height: 1.6; }
|
|
|
+.dim-cfg__actions { display: flex; gap: 8px; margin-top: 8px; }
|
|
|
+.dim-cfg__line { font-size: 12px; margin-top: 6px; padding: 6px 8px; border-radius: 4px; }
|
|
|
+.dim-cfg__line.ok { background: var(--el-color-success-light-9); color: var(--el-color-success); }
|
|
|
+.dim-cfg__line.err { background: var(--el-color-danger-light-9); color: var(--el-color-danger); }
|
|
|
+.dim-cfg__miss { color: var(--el-color-danger); }
|
|
|
+.dim-cfg__muted { color: var(--el-text-color-secondary); }
|
|
|
+.dim-cfg__preview-table { margin-top: 6px; }
|
|
|
+</style>
|