- 后端: cashflow_structure.py 模块 (GET rules/periods/values/analysis + POST values upsert) - 判定: O/I/F 三净额符号 → 8类企业定性 (CMA P2 现金流量表分析, 无'现金流肖像'字样) - 数据: cash_flow_structure_values 表 (entity_id+period 唯一), 官方现金流量表净额按期间注入 - 前端: CashFlowStructure.vue 判定横幅/瀑布图/规则表/入库记录 + MyDialog 录入弹窗 - 菜单: 财务报表分析 → 现金流量表分析 (路由 /cashflow-structure, 角色 ceo/finance/it) - 测试: tests/test_cashflow_structure.py 7 条通过 (规则8类/判定/覆盖/博海边界6-7/非法期间)
577 lines
22 KiB
Vue
577 lines
22 KiB
Vue
<template>
|
||
<div class="cashflow-structure-page">
|
||
<div class="page-header">
|
||
<h2>💰 现金流量表结构分析</h2>
|
||
<div class="header-right">
|
||
<el-tag size="small" type="info" effect="plain">{{ entityName || '账套' }}</el-tag>
|
||
<el-select v-model="period" size="small" style="width:140px" placeholder="选择期间" @change="loadData" :loading="loadingPeriods" clearable>
|
||
<el-option v-for="p in periods" :key="p" :value="p" :label="p" />
|
||
</el-select>
|
||
<el-button size="small" :icon="Refresh" @click="reload" :loading="loading">刷新</el-button>
|
||
<el-button v-if="canWrite" size="small" type="primary" @click="openDialog()">录入三大活动净额</el-button>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-loading="loading" style="min-height:200px">
|
||
<!-- 无数据空态 -->
|
||
<el-empty v-if="!dataAvailable && !loading" :description="emptyMessage" style="padding:60px 0">
|
||
<el-button v-if="canWrite" type="primary" size="small" @click="openDialog()">录入官方现金流量表净额</el-button>
|
||
</el-empty>
|
||
|
||
<template v-if="dataAvailable">
|
||
<!-- 判定结果横幅 -->
|
||
<el-row :gutter="16">
|
||
<el-col :span="24">
|
||
<el-card shadow="never" class="verdict-card">
|
||
<div class="verdict-left" v-if="classification.classified">
|
||
<div class="verdict-badge">
|
||
<span class="verdict-type">类型{{ classification.type_no }}</span>
|
||
<span class="verdict-name">{{ classification.health_emoji }} {{ classification.name }}</span>
|
||
</div>
|
||
<div class="verdict-health">
|
||
<el-tag :type="healthTagType" size="small">健康度:{{ classification.health_emoji }} {{ classification.health_label }}</el-tag>
|
||
<el-tag v-if="classification.lifecycle" size="small" type="info" effect="plain" style="margin-left:8px">生命周期:{{ classification.lifecycle }}</el-tag>
|
||
<el-tag size="small" effect="plain" style="margin-left:8px">{{ source === 'official' ? '📄 官方现金流量表' : '✍️ 人工录入' }}</el-tag>
|
||
</div>
|
||
</div>
|
||
<div class="verdict-left" v-else>
|
||
<el-alert type="warning" :closable="false" :title="classification.reason || '净额数据不足,暂无法判定类型'" show-icon />
|
||
</div>
|
||
|
||
<div class="verdict-nets">
|
||
<div class="net-item" v-for="n in netList" :key="n.key">
|
||
<div class="net-name">{{ n.name }}</div>
|
||
<div class="net-value" :style="{ color: netColor(n) }">
|
||
{{ fmt(n.value) }}
|
||
</div>
|
||
<div class="net-sign" :style="{ color: netColor(n) }">
|
||
{{ n.sign === '+' ? '进钱 ▲' : n.sign === '-' ? '出钱 ▼' : '为0' }}
|
||
</div>
|
||
<div class="net-hint">{{ n.hint }}</div>
|
||
</div>
|
||
</div>
|
||
</el-card>
|
||
</el-col>
|
||
</el-row>
|
||
|
||
<!-- 瀑布图 + 画像/风险 -->
|
||
<el-row :gutter="16" style="margin-top:16px">
|
||
<el-col :span="15">
|
||
<el-card shadow="never" header="现金流瀑布图 — 期初现金 → 经营 → 投资 → 筹资 → 期末现金">
|
||
<div ref="waterfallRef" style="height:300px"></div>
|
||
<div v-if="!waterfall" style="color:#909399;font-size:12px;text-align:center;padding:8px">
|
||
暂缺期初/期末现金余额,仅显示三大活动净额
|
||
</div>
|
||
</el-card>
|
||
</el-col>
|
||
<el-col :span="9">
|
||
<el-card shadow="never" header="判定说明(CMA P2 · 现金流量表分析)">
|
||
<template v-if="classification.classified">
|
||
<p class="desc-p">{{ classification.profile }}</p>
|
||
<el-alert type="error" :closable="false" title="风险提示" show-icon>
|
||
<span>{{ classification.risk }}</span>
|
||
</el-alert>
|
||
</template>
|
||
<div v-else style="color:#909399;font-size:13px;line-height:1.8">
|
||
八类判定需要经营(O)/投资(I)/筹资(F)三大活动净额符号:
|
||
<br />黄金法则:长期看经营现金流必须为正,且应大于净利润(利润质量)。
|
||
<br />当前账套请先录入官方现金流量表三大活动净额。
|
||
</div>
|
||
</el-card>
|
||
</el-col>
|
||
</el-row>
|
||
|
||
<!-- 八类判定规则表 -->
|
||
<el-row :gutter="16" style="margin-top:16px">
|
||
<el-col :span="24">
|
||
<el-card shadow="never" header="八类企业定性判定规则表(O/I/F 符号组合)">
|
||
<el-table :data="rules" size="small" stripe :row-class-name="ruleRowClass">
|
||
<el-table-column prop="type_no" label="类型" width="60" align="center">
|
||
<template #default="{ row }">
|
||
<el-tag :type="row.type_no === currentType ? 'primary' : 'info'" size="small" effect="plain">
|
||
{{ row.type_no }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="组合" width="150" align="center">
|
||
<template #default="{ row }">
|
||
<span class="sign-combo">
|
||
<b :style="{ color: row.o_sign === '+' ? '#67C23A' : '#F56C6C' }">O{{ row.o_sign }}</b>
|
||
<b :style="{ color: row.i_sign === '+' ? '#67C23A' : '#F56C6C' }">I{{ row.i_sign }}</b>
|
||
<b :style="{ color: row.f_sign === '+' ? '#67C23A' : '#F56C6C' }">F{{ row.f_sign }}</b>
|
||
</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="name" label="定性名称" width="120" />
|
||
<el-table-column label="健康度" width="120">
|
||
<template #default="{ row }">{{ row.health_emoji }} {{ row.health_label }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="lifecycle" label="生命周期" width="160" />
|
||
<el-table-column prop="profile" label="画像(行为特征)" min-width="300" show-overflow-tooltip />
|
||
<el-table-column prop="risk" label="风险提示" min-width="260" show-overflow-tooltip />
|
||
</el-table>
|
||
</el-card>
|
||
</el-col>
|
||
</el-row>
|
||
|
||
<!-- 已录入数据 -->
|
||
<el-row :gutter="16" style="margin-top:16px">
|
||
<el-col :span="24">
|
||
<el-card shadow="never" header="三大活动净额入库记录(万元)">
|
||
<el-table :data="valueRows" size="small" stripe>
|
||
<el-table-column prop="period" label="期间" width="100" />
|
||
<el-table-column label="经营活动净额" width="130" align="right">
|
||
<template #default="{ row }"><span :style="{ color: row.operating_net >= 0 ? '#67C23A' : '#F56C6C' }">{{ fmt(row.operating_net) }}</span></template>
|
||
</el-table-column>
|
||
<el-table-column label="投资活动净额" width="130" align="right">
|
||
<template #default="{ row }"><span :style="{ color: row.investing_net >= 0 ? '#67C23A' : '#F56C6C' }">{{ fmt(row.investing_net) }}</span></template>
|
||
</el-table-column>
|
||
<el-table-column label="筹资活动净额" width="130" align="right">
|
||
<template #default="{ row }"><span :style="{ color: row.financing_net >= 0 ? '#67C23A' : '#F56C6C' }">{{ fmt(row.financing_net) }}</span></template>
|
||
</el-table-column>
|
||
<el-table-column label="期初现金" width="100" align="right"><template #default="{ row }">{{ row.begin_cash != null ? fmt(row.begin_cash) : '-' }}</template></el-table-column>
|
||
<el-table-column label="期末现金" width="100" align="right"><template #default="{ row }">{{ row.end_cash != null ? fmt(row.end_cash) : '-' }}</template></el-table-column>
|
||
<el-table-column prop="source" label="来源" width="130" />
|
||
<el-table-column prop="remark" label="备注" min-width="160" show-overflow-tooltip />
|
||
<el-table-column prop="updated_at" label="更新时间" width="150" />
|
||
<el-table-column v-if="canWrite" label="操作" width="80" align="center">
|
||
<template #default="{ row }">
|
||
<el-button size="small" text type="primary" @click="openDialog(row)">更新</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</el-card>
|
||
</el-col>
|
||
</el-row>
|
||
</template>
|
||
</div>
|
||
|
||
<!-- 录入/更新弹窗(MyDialog 项目铁律) -->
|
||
<MyDialog v-model="showDialog" title="录入现金流量表三大活动净额" :width="620">
|
||
<el-form label-width="140px" size="small">
|
||
<el-form-item label="期间" required>
|
||
<el-input v-model="form.period" placeholder="YYYY-MM,如 2026-08" style="width:220px" />
|
||
<div class="form-tip">官方现金流量表所属期间(月度)</div>
|
||
</el-form-item>
|
||
<el-form-item label="经营活动净额" required>
|
||
<el-input-number v-model="form.operating_net" :precision="2" :step="1" :controls="true" style="width:220px" />
|
||
<div class="form-tip">万元;正=造血进钱,负=经营失血</div>
|
||
</el-form-item>
|
||
<el-form-item label="投资活动净额" required>
|
||
<el-input-number v-model="form.investing_net" :precision="2" :step="1" style="width:220px" />
|
||
<div class="form-tip">万元;负=扩张投入,正=处置/回报回笼</div>
|
||
</el-form-item>
|
||
<el-form-item label="筹资活动净额" required>
|
||
<el-input-number v-model="form.financing_net" :precision="2" :step="1" style="width:220px" />
|
||
<div class="form-tip">万元;正=融资输血,负=还债/分红</div>
|
||
</el-form-item>
|
||
<el-form-item label="期初现金余额">
|
||
<el-input-number v-model="form.begin_cash" :precision="2" :step="1" style="width:220px" />
|
||
<div class="form-tip">可选;用于瀑布图(万元)</div>
|
||
</el-form-item>
|
||
<el-form-item label="期末现金余额">
|
||
<el-input-number v-model="form.end_cash" :precision="2" :step="1" style="width:220px" />
|
||
<div class="form-tip">可选;用于瀑布图(万元)</div>
|
||
</el-form-item>
|
||
<el-form-item label="来源">
|
||
<el-radio-group v-model="form.source">
|
||
<el-radio value="official">官方现金流量表</el-radio>
|
||
<el-radio value="manual">人工录入</el-radio>
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
<el-form-item label="备注">
|
||
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="口径说明 / 数据来源,如:2026年8月官方现金流量表" />
|
||
</el-form-item>
|
||
</el-form>
|
||
<template #footer>
|
||
<el-button size="small" @click="showDialog = false">取消</el-button>
|
||
<el-button size="small" type="primary" :loading="saving" @click="saveValue">保存</el-button>
|
||
</template>
|
||
</MyDialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, computed, onMounted, nextTick } from 'vue'
|
||
import { ElMessage } from 'element-plus'
|
||
import { Refresh } from '@element-plus/icons-vue'
|
||
import * as echarts from 'echarts'
|
||
import api from '@/api'
|
||
import MyDialog from '@/components/MyDialog.vue'
|
||
|
||
const entityName = ref(localStorage.getItem('cma_entity_name') || '')
|
||
const userRole = ref<string>((JSON.parse(localStorage.getItem('cma_user') || '{}') as any).role || '')
|
||
const canWrite = computed(() => ['ceo', 'finance'].includes(userRole.value))
|
||
|
||
const period = ref('')
|
||
const periods = ref<string[]>([])
|
||
const loading = ref(false)
|
||
const loadingPeriods = ref(false)
|
||
|
||
const dataAvailable = ref(false)
|
||
const emptyMessage = ref('该期间暂无官方现金流量表净额数据')
|
||
const source = ref('official')
|
||
const nets = ref<any>(null)
|
||
const waterfall = ref<any>(null)
|
||
const classification = ref<any>({ classified: false })
|
||
const rules = ref<any[]>([])
|
||
const valueRows = ref<any[]>([])
|
||
|
||
const showDialog = ref(false)
|
||
const saving = ref(false)
|
||
const form = ref<any>({
|
||
period: '',
|
||
operating_net: 0,
|
||
investing_net: 0,
|
||
financing_net: 0,
|
||
begin_cash: null,
|
||
end_cash: null,
|
||
source: 'official',
|
||
remark: '',
|
||
})
|
||
|
||
const waterfallRef = ref<HTMLElement>()
|
||
let waterfallChart: echarts.ECharts | null = null
|
||
|
||
const netList = computed(() => {
|
||
const n = nets.value || {}
|
||
const meta: any = {
|
||
operating: { name: '经营活动 O', hint: '造血:主业赚不赚钱' },
|
||
investing: { name: '投资活动 I', hint: '成长:扩张 or 收缩' },
|
||
financing: { name: '筹资活动 F', hint: '输血:融资 or 还债' },
|
||
}
|
||
return ['operating', 'investing', 'financing']
|
||
.filter((k) => n[k] && n[k].value != null)
|
||
.map((k) => ({ key: k, ...meta[k], ...n[k] }))
|
||
})
|
||
|
||
const currentType = computed(() => (classification.value?.classified ? classification.value.type_no : -1))
|
||
|
||
const healthTagType = computed(() => {
|
||
const label = classification.value?.health_label || ''
|
||
if (['最优', '良好'].includes(label)) return 'success'
|
||
if (['看增速', '需辨析'].includes(label)) return 'warning'
|
||
if (['高风险'].includes(label)) return 'warning'
|
||
return 'danger'
|
||
})
|
||
|
||
function fmt(v: any): string {
|
||
if (v === null || v === undefined || Number.isNaN(Number(v))) return '-'
|
||
return Number(v).toFixed(2)
|
||
}
|
||
|
||
function netColor(n: any): string {
|
||
if (n.sign === '+') return '#67C23A'
|
||
if (n.sign === '-') return '#F56C6C'
|
||
return '#909399'
|
||
}
|
||
|
||
function ruleRowClass({ row }: any) {
|
||
return row.type_no === currentType.value ? 'rule-row-current' : ''
|
||
}
|
||
|
||
async function loadPeriods() {
|
||
loadingPeriods.value = true
|
||
try {
|
||
const r: any = await api.get('/cashflow-structure/periods')
|
||
periods.value = (r && (r.periods || [])) || []
|
||
} catch (_) { /* 静默 */ }
|
||
loadingPeriods.value = false
|
||
}
|
||
|
||
async function loadValues() {
|
||
try {
|
||
const r: any = await api.get('/cashflow-structure/values')
|
||
valueRows.value = (r && (r.items || [])) || []
|
||
} catch (_) { valueRows.value = [] }
|
||
}
|
||
|
||
async function loadRules() {
|
||
try {
|
||
const r: any = await api.get('/cashflow-structure/rules')
|
||
rules.value = (r && (r.rules || [])) || []
|
||
} catch (_) { rules.value = [] }
|
||
}
|
||
|
||
async function loadData() {
|
||
loading.value = true
|
||
try {
|
||
const params: any = {}
|
||
if (period.value) params.period = period.value
|
||
const d: any = await api.get('/cashflow-structure/analysis', { params })
|
||
dataAvailable.value = !!d?.data_available
|
||
source.value = d?.source || 'official'
|
||
nets.value = d?.nets || null
|
||
waterfall.value = d?.waterfall || null
|
||
classification.value = d?.classification || { classified: false }
|
||
if (!dataAvailable.value) {
|
||
emptyMessage.value = d?.message || '该期间暂无官方现金流量表净额数据'
|
||
}
|
||
if (!period.value && d?.period) period.value = d.period
|
||
await nextTick()
|
||
renderWaterfall()
|
||
} catch (e: any) {
|
||
console.error('load cashflow structure analysis failed', e)
|
||
dataAvailable.value = false
|
||
emptyMessage.value = '加载失败:' + (e?.message || '请稍后重试')
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
async function reload() {
|
||
await Promise.all([loadPeriods(), loadValues()])
|
||
if (!period.value && periods.value.length) period.value = periods.value[0]
|
||
await loadData()
|
||
}
|
||
|
||
function renderWaterfall() {
|
||
if (!waterfallRef.value) return
|
||
if (!waterfallChart) waterfallChart = echarts.init(waterfallRef.value)
|
||
|
||
const steps = waterfall.value?.steps || []
|
||
if (!steps.length) {
|
||
waterfallChart.clear()
|
||
return
|
||
}
|
||
|
||
const baseData: number[] = []
|
||
const valueData: number[] = []
|
||
const colorData: string[] = []
|
||
const labelData: string[] = []
|
||
let cum = 0
|
||
|
||
steps.forEach((s: any, idx: number) => {
|
||
if (idx === 0 && s.type === 'begin') {
|
||
baseData.push(0)
|
||
valueData.push(Number(s.value) || 0)
|
||
colorData.push('#409EFF')
|
||
labelData.push(fmt(s.value))
|
||
cum = Number(s.value) || 0
|
||
} else if (s.type === 'end') {
|
||
baseData.push(0)
|
||
valueData.push(Number(s.value) || 0)
|
||
colorData.push('#409EFF')
|
||
labelData.push(fmt(s.value))
|
||
cum = Number(s.value) || 0
|
||
} else {
|
||
const v = Number(s.value) || 0
|
||
baseData.push(cum)
|
||
valueData.push(v)
|
||
colorData.push(v >= 0 ? '#67C23A' : '#F56C6C')
|
||
labelData.push((v >= 0 ? '+' : '') + fmt(v))
|
||
cum += v
|
||
}
|
||
})
|
||
|
||
const names = steps.map((s: any) => s.step)
|
||
|
||
waterfallChart.setOption({
|
||
tooltip: {
|
||
trigger: 'axis',
|
||
axisPointer: { type: 'shadow' },
|
||
formatter: (params: any[]) => {
|
||
const i = params[0]?.dataIndex ?? 0
|
||
const s = steps[i]
|
||
let tip = `<b>${s.step}(${period.value})</b><br/>`
|
||
if (s.type === 'flow') {
|
||
const v = Number(s.value) || 0
|
||
tip += `净额:<span style="color:${v >= 0 ? '#67C23A' : '#F56C6C'}">${v >= 0 ? '+' : ''}${v.toFixed(2)} 万元</span>`
|
||
} else {
|
||
tip += `余额:${Number(s.value).toFixed(2)} 万元`
|
||
}
|
||
return tip
|
||
},
|
||
},
|
||
grid: { left: 20, right: 20, top: 40, bottom: 30, containLabel: true },
|
||
xAxis: { type: 'category', data: names, axisLabel: { interval: 0 } },
|
||
yAxis: { type: 'value', name: '万元', axisLabel: { formatter: '{value}' } },
|
||
series: [
|
||
{
|
||
name: '底', type: 'bar', stack: 'wf',
|
||
itemStyle: { color: 'transparent' },
|
||
emphasis: { itemStyle: { color: 'transparent' } },
|
||
data: baseData,
|
||
silent: true,
|
||
},
|
||
{
|
||
name: '净变动', type: 'bar', stack: 'wf',
|
||
data: valueData,
|
||
barWidth: 46,
|
||
itemStyle: { borderRadius: 2 },
|
||
label: {
|
||
show: true, position: 'top', formatter: (p: any) => labelData[p.dataIndex],
|
||
color: '#333', fontSize: 11,
|
||
},
|
||
emphasis: { focus: 'series' },
|
||
},
|
||
],
|
||
})
|
||
}
|
||
|
||
function openDialog(row?: any) {
|
||
form.value = {
|
||
period: row?.period || '',
|
||
operating_net: row ? row.operating_net : 0,
|
||
investing_net: row ? row.investing_net : 0,
|
||
financing_net: row ? row.financing_net : 0,
|
||
begin_cash: row?.begin_cash ?? null,
|
||
end_cash: row?.end_cash ?? null,
|
||
source: row?.source || 'official',
|
||
remark: row?.remark || '',
|
||
}
|
||
showDialog.value = true
|
||
}
|
||
|
||
async function saveValue() {
|
||
const f = form.value
|
||
if (!/^\d{4}-\d{2}$/.test(f.period || '')) {
|
||
ElMessage.warning('期间格式须为 YYYY-MM')
|
||
return
|
||
}
|
||
if ([f.operating_net, f.investing_net, f.financing_net].some((v) => v === null || v === undefined || Number.isNaN(Number(v)))) {
|
||
ElMessage.warning('三大活动净额必填')
|
||
return
|
||
}
|
||
saving.value = true
|
||
try {
|
||
const payload: any = {
|
||
period: f.period,
|
||
operating_net: Number(f.operating_net),
|
||
investing_net: Number(f.investing_net),
|
||
financing_net: Number(f.financing_net),
|
||
source: f.source,
|
||
}
|
||
if (f.begin_cash !== null && f.begin_cash !== undefined && !Number.isNaN(Number(f.begin_cash))) payload.begin_cash = Number(f.begin_cash)
|
||
if (f.end_cash !== null && f.end_cash !== undefined && !Number.isNaN(Number(f.end_cash))) payload.end_cash = Number(f.end_cash)
|
||
if (f.remark) payload.remark = f.remark
|
||
const r: any = await api.post('/cashflow-structure/values', payload)
|
||
const cls = r?.classification || {}
|
||
if (cls?.classified) {
|
||
ElMessage.success(`已保存:${r.period} → 类型${cls.type_no} ${cls.name}`)
|
||
} else {
|
||
ElMessage.success(`已保存:${r.period}${cls?.reason ? '(' + cls.reason + ')' : ''}`)
|
||
}
|
||
showDialog.value = false
|
||
await reload()
|
||
} catch (e: any) {
|
||
ElMessage.error('保存失败:' + (e?.response?.data?.detail || e?.message || '未知错误'))
|
||
} finally {
|
||
saving.value = false
|
||
}
|
||
}
|
||
|
||
function resizeCharts() {
|
||
waterfallChart?.resize()
|
||
}
|
||
|
||
onMounted(async () => {
|
||
window.addEventListener('resize', resizeCharts)
|
||
await loadRules()
|
||
await loadPeriods()
|
||
if (periods.value.length && !period.value) period.value = periods.value[0]
|
||
await loadValues()
|
||
await loadData()
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.cashflow-structure-page {
|
||
padding: 4px;
|
||
}
|
||
.page-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 12px;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
}
|
||
.page-header h2 {
|
||
margin: 0;
|
||
font-size: 18px;
|
||
}
|
||
.header-right {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
.verdict-card {
|
||
border-left: 4px solid #409EFF;
|
||
}
|
||
.verdict-left {
|
||
margin-bottom: 14px;
|
||
}
|
||
.verdict-badge {
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 12px;
|
||
margin-bottom: 8px;
|
||
}
|
||
.verdict-type {
|
||
font-size: 26px;
|
||
font-weight: 700;
|
||
color: #409EFF;
|
||
}
|
||
.verdict-name {
|
||
font-size: 20px;
|
||
font-weight: 600;
|
||
}
|
||
.verdict-health {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
.verdict-nets {
|
||
display: flex;
|
||
gap: 16px;
|
||
flex-wrap: wrap;
|
||
}
|
||
.net-item {
|
||
flex: 1;
|
||
min-width: 140px;
|
||
background: #f7f9fc;
|
||
border-radius: 6px;
|
||
padding: 10px 14px;
|
||
text-align: center;
|
||
}
|
||
.net-name {
|
||
font-size: 12px;
|
||
color: #606266;
|
||
}
|
||
.net-value {
|
||
font-size: 22px;
|
||
font-weight: 700;
|
||
margin: 2px 0;
|
||
}
|
||
.net-sign {
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
}
|
||
.net-hint {
|
||
font-size: 11px;
|
||
color: #909399;
|
||
margin-top: 2px;
|
||
}
|
||
.desc-p {
|
||
font-size: 13px;
|
||
line-height: 1.8;
|
||
color: #303133;
|
||
margin: 0 0 10px;
|
||
}
|
||
.sign-combo {
|
||
display: inline-flex;
|
||
gap: 10px;
|
||
font-size: 15px;
|
||
letter-spacing: 1px;
|
||
}
|
||
.form-tip {
|
||
font-size: 12px;
|
||
color: #909399;
|
||
line-height: 1.5;
|
||
margin-left: 8px;
|
||
}
|
||
:deep(.rule-row-current td) {
|
||
background: #ecf5ff !important;
|
||
}
|
||
</style>
|