ManualView.vue 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044
  1. <template>
  2. <div class="ignition-view manual-view">
  3. <!-- 左:人工录入 -->
  4. <ModuleCommon
  5. :show-style="panelShowStyle(23, 105, 335, 934)"
  6. :module-data="EMPTY_MODULE_DATA"
  7. module-name="人工录入"
  8. device-type="ignition_manual_input"
  9. :data="{}"
  10. :visible="true"
  11. >
  12. <div class="manual-form">
  13. <div class="form-toolbar">
  14. <div class="target-row">
  15. <span class="target-label">评价对象:</span>
  16. <PanelSelect v-model="manageSystemId" :options="sceneSelectOptions" @update:model-value="loadLatest" />
  17. </div>
  18. <div class="eval-type-tabs">
  19. <span :class="{ active: evalType === 1 }" @click="changeEvalType(1)">采前评价</span>
  20. <span :class="{ active: evalType === 2 }" @click="changeEvalType(2)">采中评价</span>
  21. </div>
  22. </div>
  23. <div class="group-list">
  24. <div v-if="loadingOptions" class="form-loading">评价指标加载中...</div>
  25. <template v-else>
  26. <div v-for="group in fieldGroups" :key="group.key" class="form-group">
  27. <div class="group-collapse" @click="toggleGroup(group.key)">
  28. <img class="group-icon" src="@/assets/images/vent/home/selfIgnition/form-icon.svg" alt="" />
  29. <span>{{ group.title }}</span>
  30. <i class="collapse-arrow" :class="{ collapsed: collapsed[group.key] }"></i>
  31. </div>
  32. <template v-if="!collapsed[group.key]">
  33. <div class="group-title">
  34. <span class="title-bars">
  35. <i v-for="n in 4" :key="n"></i>
  36. </span>
  37. <span>{{ group.title }}指标</span>
  38. <span class="title-dots">
  39. <i v-for="n in 3" :key="n"></i>
  40. <b></b>
  41. </span>
  42. </div>
  43. <div class="field-list">
  44. <div v-for="item in group.fields" :key="item.code" class="field-row">
  45. <span class="field-label" :title="item.name">{{ item.name }}:</span>
  46. <PanelSelect v-model="formValues[FIELD_BY_CODE[item.code]]" :options="selectOptions(item)" />
  47. </div>
  48. </div>
  49. </template>
  50. </div>
  51. <div v-if="!fieldGroups.length" class="form-loading">暂无评价指标</div>
  52. </template>
  53. </div>
  54. <div class="form-actions">
  55. <button class="action-button preview-button" :disabled="busy" @click="preview">
  56. {{ previewing ? '计算中...' : '计算预览' }}
  57. </button>
  58. <button class="action-button submit-button" :disabled="busy" @click="submit">
  59. {{ submitting ? '提交中...' : '提交' }}
  60. </button>
  61. </div>
  62. </div>
  63. </ModuleCommon>
  64. <!-- 右:评价结果 -->
  65. <ModuleCommon
  66. :show-style="panelShowStyle(1523, 105, 337, 934)"
  67. :module-data="EMPTY_MODULE_DATA"
  68. module-name="评价结果"
  69. device-type="ignition_evaluation_result"
  70. :data="{}"
  71. :visible="true"
  72. >
  73. <div class="result-panel">
  74. <div class="result-top">
  75. <div class="score-line">
  76. <span class="score-label">安全评价得分:</span>
  77. <img class="score-info" src="@/assets/images/vent/home/selfIgnition/info-icon.svg" alt="由后端算法计算" />
  78. <span class="score-value">{{ resultScore }}</span>
  79. </div>
  80. <div class="gauge-line">
  81. <div class="gauge">
  82. <img class="gauge-segment gauge-segment-1" src="@/assets/images/vent/home/selfIgnition/risk-gauge.png" alt="" />
  83. <img class="gauge-segment gauge-segment-2" src="@/assets/images/vent/home/selfIgnition/risk-gauge.png" alt="" />
  84. </div>
  85. <span class="risk-tag" :style="{ '--risk-color': resultRiskColor }">{{ resultTagText }}</span>
  86. </div>
  87. </div>
  88. <div class="result-info">
  89. <div class="info-row"
  90. ><span>评价对象:</span><em>{{ resultTarget }}</em></div
  91. >
  92. <div class="info-row"
  93. ><span>数据时间:</span><em>{{ resultTime }}</em></div
  94. >
  95. <div class="info-row"><span>数据来源:</span><em>人工录入</em></div>
  96. </div>
  97. <div class="level-table">
  98. <div class="level-head"><span>评价等级</span><span>评分范围</span><span>对应颜色</span></div>
  99. <div
  100. v-for="item in levels"
  101. :key="item.level"
  102. class="level-row"
  103. :class="{ highlight: item.level === resultLevel }"
  104. :style="item.level === resultLevel ? { '--highlight-color': resultRiskColor } : undefined"
  105. >
  106. <span class="level-name">
  107. <i :style="{ background: item.color, boxShadow: `0 0 8px ${item.color}` }"></i>
  108. {{ item.level }}
  109. </span>
  110. <span>{{ item.range }}</span>
  111. <span>{{ item.colorName }}</span>
  112. </div>
  113. </div>
  114. <div class="suggestion-box">
  115. <span class="suggestion-icon"></span>
  116. <p>{{ resultSuggestion }}</p>
  117. </div>
  118. <button class="action-button report-button" :disabled="!hasEvaluation" @click="showReport = true">查看评价报告</button>
  119. </div>
  120. </ModuleCommon>
  121. <Transition name="ignition-fade">
  122. <div v-if="showReport" class="report-mask" @click.self="showReport = false">
  123. <div class="report-dialog">
  124. <div class="report-title">自然发火安全评价报告</div>
  125. <div class="report-body">
  126. <div class="report-row"
  127. ><span>评价对象</span><em>{{ resultTarget }}</em></div
  128. >
  129. <div class="report-row"
  130. ><span>评价类型</span><em>{{ evalTypeText }}</em></div
  131. >
  132. <div class="report-row"
  133. ><span>安全评价得分</span><em>{{ resultScore }}</em></div
  134. >
  135. <div class="report-row"
  136. ><span>风险等级</span><em :style="{ color: resultRiskColor }">{{ resultTagText }}</em></div
  137. >
  138. <div class="report-row"
  139. ><span>数据时间</span><em>{{ resultTime }}</em></div
  140. >
  141. <div class="report-row"><span>数据来源</span><em>人工录入</em></div>
  142. <div class="report-suggestion">
  143. <span class="suggestion-icon"></span>
  144. <p>{{ resultSuggestion }}</p>
  145. </div>
  146. </div>
  147. <button class="action-button report-close" @click="showReport = false">关闭</button>
  148. </div>
  149. </div>
  150. </Transition>
  151. </div>
  152. </template>
  153. <script lang="ts" setup>
  154. import { computed, onMounted, reactive, ref } from 'vue';
  155. import { useRoute } from 'vue-router';
  156. import ModuleCommon from '../../configurable/components/ModuleCommon.vue';
  157. import PanelSelect from './PanelSelect.vue';
  158. import { EMPTY_MODULE_DATA, panelShowStyle } from '../utils';
  159. import { useMessage } from '/@/hooks/web/useMessage';
  160. import {
  161. calculateSafetyEvaluation,
  162. getSafetyEvaluationByScene,
  163. getSafetyItemOptions,
  164. getSafetySceneList,
  165. saveSafetyEvaluation,
  166. } from '../safetyEvaluation.api';
  167. import { EVAL_RISK_LEVELS, EVAL_RISK_MAP, FIELD_BY_CODE, FALLBACK_SAFETY_ITEMS, FALLBACK_SCENE } from '../evaluation.config';
  168. import type { SafetyEvaluationEntity, SafetyItem, SafetyScene } from '../evaluation.config';
  169. const route = useRoute();
  170. const { createMessage } = useMessage();
  171. const levels = EVAL_RISK_LEVELS;
  172. const items = ref<SafetyItem[]>([]);
  173. const scenes = ref<SafetyScene[]>([]);
  174. const manageSystemId = ref('');
  175. const evalType = ref(1);
  176. const formValues = reactive<Record<string, string>>({});
  177. const collapsed = reactive<Record<string, boolean>>({});
  178. const evaluation = ref<SafetyEvaluationEntity | null>(null);
  179. const loadingOptions = ref(false);
  180. const loadingScenes = ref(false);
  181. const previewing = ref(false);
  182. const submitting = ref(false);
  183. const showReport = ref(false);
  184. const busy = computed(() => loadingOptions.value || previewing.value || submitting.value);
  185. const sceneSelectOptions = computed(() =>
  186. scenes.value.map((scene) => ({
  187. label: scene.systemname,
  188. value: scene.id,
  189. score: 0,
  190. }))
  191. );
  192. const selectedScene = computed(() => scenes.value.find((scene) => scene.id === manageSystemId.value));
  193. const targetName = computed(() => {
  194. const scene = selectedScene.value;
  195. if (!scene) return '未选择评价对象';
  196. const name = scene.systemname;
  197. return name.endsWith('工作面') || name.endsWith('采空区') ? name : `${name}工作面`;
  198. });
  199. const visibleItems = computed(() =>
  200. items.value.filter((item) => {
  201. const weight = evalType.value === 2 ? item.midWeight : item.preWeight;
  202. return weight !== null && weight !== undefined;
  203. })
  204. );
  205. const fieldGroups = computed(() => {
  206. const groups = [
  207. { key: 'pre', title: '采前评价', fields: visibleItems.value.filter((item) => item.code <= 6) },
  208. { key: 'condition', title: '开采条件', fields: visibleItems.value.filter((item) => item.code >= 7 && item.code <= 11) },
  209. ];
  210. if (evalType.value === 2) {
  211. groups.push({ key: 'monitor', title: '监测与措施', fields: visibleItems.value.filter((item) => item.code >= 12) });
  212. }
  213. return groups.filter((group) => group.fields.length);
  214. });
  215. const hasEvaluation = computed(() => !!evaluation.value);
  216. const resultLevel = computed(() => evaluation.value?.riskLevel || '');
  217. const resultLevelMeta = computed(() => EVAL_RISK_MAP[resultLevel.value] || null);
  218. const resultRiskColor = computed(() => resultLevelMeta.value?.color || '#3df6ff');
  219. const resultTagText = computed(() => resultLevel.value || '未评价');
  220. const resultTarget = computed(() => evaluation.value?.workfaceName || targetName.value);
  221. const resultTime = computed(() => evaluation.value?.createTime || '--');
  222. const resultSuggestion = computed(() => resultLevelMeta.value?.suggestion || '请完成左侧指标选择并提交评价。');
  223. const evalTypeText = computed(() => (evalType.value === 1 ? '采前评价' : '采中评价'));
  224. const resultScore = computed(() => {
  225. const value = evaluation.value?.totalScore;
  226. if (value === null || value === undefined || value === '') return '--';
  227. const score = Number(value);
  228. return Number.isFinite(score) ? score.toFixed(2) : String(value);
  229. });
  230. function selectOptions(item: SafetyItem) {
  231. return item.options.map((option) => ({
  232. label: option.name,
  233. value: option.name,
  234. score: option.normalizedScore,
  235. }));
  236. }
  237. function toggleGroup(key: string) {
  238. collapsed[key] = !collapsed[key];
  239. }
  240. function formatDateTime(date: Date) {
  241. const pad = (value: number) => `${value}`.padStart(2, '0');
  242. return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
  243. }
  244. function normalizeItems(list: any[]): SafetyItem[] {
  245. return (list || [])
  246. .map((item) => ({
  247. code: Number(item.code),
  248. name: item.name,
  249. preWeight: item.preWeight ?? null,
  250. midWeight: item.midWeight ?? null,
  251. options: (item.options || []).map((option: any) => {
  252. if (typeof option === 'string') return { name: option, normalizedScore: 0 };
  253. return { name: option.name, normalizedScore: option.normalizedScore ?? 0 };
  254. }),
  255. }))
  256. .filter((item) => item.code && item.options.length);
  257. }
  258. function initDefaults() {
  259. items.value.forEach((item) => {
  260. const field = FIELD_BY_CODE[item.code];
  261. if (field && !formValues[field]) {
  262. formValues[field] = item.options[0]?.name || '';
  263. }
  264. });
  265. }
  266. async function loadItems() {
  267. loadingOptions.value = true;
  268. try {
  269. const res: any = await getSafetyItemOptions();
  270. const list = Array.isArray(res) ? res : (res?.result ?? []);
  271. items.value = normalizeItems(list);
  272. if (!items.value.length) {
  273. items.value = normalizeItems(FALLBACK_SAFETY_ITEMS);
  274. }
  275. } catch {
  276. items.value = normalizeItems(FALLBACK_SAFETY_ITEMS);
  277. createMessage.warning('评价指标接口异常,已使用内置指标字典');
  278. } finally {
  279. loadingOptions.value = false;
  280. initDefaults();
  281. }
  282. }
  283. async function loadScenes() {
  284. loadingScenes.value = true;
  285. try {
  286. const res: any = await getSafetySceneList();
  287. const records = res?.records || res?.result?.records || [];
  288. const list: SafetyScene[] = records.map((record: any) => ({
  289. id: String(record.id ?? record.deviceID ?? ''),
  290. systemname: record.systemname || record.name || record.sysname || '未命名场景',
  291. }));
  292. scenes.value = list.length ? list : [{ ...FALLBACK_SCENE }];
  293. } catch {
  294. scenes.value = [{ ...FALLBACK_SCENE }];
  295. } finally {
  296. loadingScenes.value = false;
  297. const queryScene = (route.query.systemID || route.query.manageSystemId || route.query.sceneId) as string;
  298. if (queryScene && !scenes.value.some((scene) => scene.id === queryScene)) {
  299. scenes.value.unshift({ id: queryScene, systemname: '当前场景' });
  300. }
  301. manageSystemId.value = queryScene || scenes.value[0]?.id || '';
  302. }
  303. }
  304. function applyEvaluation(entity: SafetyEvaluationEntity) {
  305. if (!entity) return;
  306. evaluation.value = {
  307. ...entity,
  308. createTime: entity.createTime || formatDateTime(new Date()),
  309. };
  310. items.value.forEach((item) => {
  311. const field = FIELD_BY_CODE[item.code];
  312. const value = entity[field];
  313. if (field && value) {
  314. formValues[field] = value;
  315. }
  316. });
  317. }
  318. async function loadLatest() {
  319. evaluation.value = null;
  320. if (!manageSystemId.value) {
  321. return;
  322. }
  323. try {
  324. const res: any = await getSafetyEvaluationByScene(manageSystemId.value);
  325. const data = res?.result ?? res;
  326. const entity = evalType.value === 2 ? data?.miningEval : data?.beforeEval;
  327. if (entity) {
  328. applyEvaluation(entity);
  329. } else {
  330. evaluation.value = null;
  331. }
  332. } catch {
  333. evaluation.value = null;
  334. }
  335. }
  336. function changeEvalType(type: number) {
  337. if (evalType.value === type) return;
  338. evalType.value = type;
  339. evaluation.value = null;
  340. loadLatest();
  341. }
  342. function buildPayload() {
  343. const payload: Record<string, any> = {
  344. evalType: evalType.value,
  345. workfaceName: targetName.value,
  346. };
  347. if (manageSystemId.value) {
  348. payload.manageSystemId = manageSystemId.value;
  349. }
  350. visibleItems.value.forEach((item) => {
  351. const field = FIELD_BY_CODE[item.code];
  352. if (field) {
  353. payload[field] = formValues[field];
  354. }
  355. });
  356. return payload;
  357. }
  358. function validateForm() {
  359. for (const item of visibleItems.value) {
  360. const field = FIELD_BY_CODE[item.code];
  361. if (field && !formValues[field]) {
  362. createMessage.warning(`请选择【${item.name}】`);
  363. return false;
  364. }
  365. }
  366. return true;
  367. }
  368. function getApiErrorMessage(error: any, fallback: string) {
  369. const status = error?.response?.status || error?.status || error?.code;
  370. if (status === 404) {
  371. return '评价接口未部署(404),请确认后端服务';
  372. }
  373. return error?.message || error?.msg || fallback;
  374. }
  375. async function preview() {
  376. if (!validateForm()) return;
  377. previewing.value = true;
  378. try {
  379. const res: any = await calculateSafetyEvaluation(buildPayload());
  380. applyEvaluation(res?.result ?? res);
  381. createMessage.success('计算完成');
  382. } catch (error) {
  383. createMessage.error(getApiErrorMessage(error, '计算失败,请检查指标选项'));
  384. } finally {
  385. previewing.value = false;
  386. }
  387. }
  388. async function submit() {
  389. if (!validateForm()) return;
  390. submitting.value = true;
  391. try {
  392. const res: any = await saveSafetyEvaluation(buildPayload());
  393. applyEvaluation(res?.result ?? res);
  394. createMessage.success('评价已保存');
  395. } catch (error) {
  396. createMessage.error(getApiErrorMessage(error, '评价保存失败'));
  397. } finally {
  398. submitting.value = false;
  399. }
  400. }
  401. onMounted(async () => {
  402. await loadItems();
  403. await loadScenes();
  404. await loadLatest();
  405. });
  406. </script>
  407. <style lang="less" scoped>
  408. .manual-view {
  409. position: absolute;
  410. inset: 0;
  411. }
  412. .manual-form {
  413. position: relative;
  414. display: flex;
  415. flex-direction: column;
  416. height: 100%;
  417. padding: 8px 10px 12px;
  418. box-sizing: border-box;
  419. }
  420. .form-toolbar {
  421. flex: none;
  422. }
  423. .target-row {
  424. display: flex;
  425. align-items: center;
  426. gap: 6px;
  427. margin-bottom: 8px;
  428. :deep(.ignition-select) {
  429. flex: 1;
  430. min-width: 0;
  431. height: 32px;
  432. font-size: 13px;
  433. }
  434. }
  435. .target-label {
  436. flex: none;
  437. color: #fff;
  438. font-size: 13px;
  439. white-space: nowrap;
  440. }
  441. .eval-type-tabs {
  442. display: flex;
  443. gap: 6px;
  444. margin-bottom: 8px;
  445. span {
  446. flex: 1;
  447. height: 30px;
  448. line-height: 30px;
  449. border: 1px solid rgba(61, 246, 255, 0.25);
  450. background: rgba(8, 42, 70, 0.6);
  451. color: #a9dcf4;
  452. font-size: 13px;
  453. text-align: center;
  454. cursor: pointer;
  455. transition: all 0.2s;
  456. &.active {
  457. border-color: rgba(61, 246, 255, 0.7);
  458. background: linear-gradient(180deg, #2a5985 0%, #1d4773 100%);
  459. color: #fff;
  460. }
  461. }
  462. }
  463. .group-list {
  464. flex: 1;
  465. min-height: 0;
  466. overflow-y: auto;
  467. overflow-x: hidden;
  468. padding-right: 2px;
  469. &::-webkit-scrollbar {
  470. width: 4px;
  471. }
  472. &::-webkit-scrollbar-thumb {
  473. background: rgba(61, 246, 255, 0.35);
  474. border-radius: 2px;
  475. }
  476. }
  477. .form-group {
  478. margin-bottom: 8px;
  479. &:last-child {
  480. margin-bottom: 0;
  481. }
  482. }
  483. .group-collapse {
  484. display: flex;
  485. align-items: center;
  486. gap: 6px;
  487. height: 34px;
  488. padding: 0 8px;
  489. border-left: 2px solid #3df6ff;
  490. background: linear-gradient(90deg, rgba(15, 77, 120, 0.9), rgba(9, 43, 70, 0.15));
  491. color: #fff;
  492. font-size: 15px;
  493. letter-spacing: 0.5px;
  494. cursor: pointer;
  495. user-select: none;
  496. }
  497. .group-icon {
  498. width: 13px;
  499. height: 15px;
  500. }
  501. .collapse-arrow {
  502. margin-left: auto;
  503. width: 0;
  504. height: 0;
  505. border-left: 4px solid transparent;
  506. border-right: 4px solid transparent;
  507. border-top: 5px solid #cfefff;
  508. transition: transform 0.2s;
  509. &.collapsed {
  510. transform: rotate(-90deg);
  511. }
  512. }
  513. .group-title {
  514. display: flex;
  515. align-items: center;
  516. gap: 6px;
  517. height: 24px;
  518. padding: 0 4px;
  519. color: #a9dcf4;
  520. font-size: 12px;
  521. letter-spacing: 0.5px;
  522. }
  523. .title-bars {
  524. display: flex;
  525. align-items: flex-end;
  526. gap: 2px;
  527. height: 12px;
  528. i {
  529. width: 3px;
  530. background: linear-gradient(180deg, #b3fff5, #17b7ff);
  531. &:nth-child(1) {
  532. height: 5px;
  533. }
  534. &:nth-child(2) {
  535. height: 12px;
  536. }
  537. &:nth-child(3) {
  538. height: 8px;
  539. }
  540. &:nth-child(4) {
  541. height: 4px;
  542. }
  543. }
  544. }
  545. .title-dots {
  546. display: flex;
  547. align-items: center;
  548. gap: 3px;
  549. margin-left: auto;
  550. i {
  551. width: 2px;
  552. height: 2px;
  553. background: rgba(61, 246, 255, 0.65);
  554. }
  555. b {
  556. width: 3px;
  557. height: 3px;
  558. background: #3df6ff;
  559. box-shadow: 0 0 4px rgba(61, 246, 255, 0.9);
  560. }
  561. }
  562. .field-list {
  563. display: flex;
  564. flex-direction: column;
  565. gap: 7px;
  566. }
  567. .field-row {
  568. display: flex;
  569. align-items: center;
  570. gap: 6px;
  571. min-width: 0;
  572. min-height: 42px;
  573. :deep(.ignition-select) {
  574. flex: 1;
  575. min-width: 0;
  576. height: 34px;
  577. font-size: 13px;
  578. }
  579. }
  580. .field-label {
  581. width: 126px;
  582. flex: none;
  583. text-align: right;
  584. color: #fff;
  585. font-size: 12px;
  586. line-height: 1.3;
  587. white-space: normal;
  588. word-break: break-all;
  589. }
  590. .form-loading {
  591. display: flex;
  592. align-items: center;
  593. justify-content: center;
  594. height: 100%;
  595. min-height: 160px;
  596. color: #a9dcf4;
  597. font-size: 13px;
  598. }
  599. .form-actions {
  600. display: flex;
  601. flex: none;
  602. gap: 8px;
  603. margin-top: 8px;
  604. .action-button {
  605. flex: 1;
  606. width: auto;
  607. margin-top: 0;
  608. }
  609. }
  610. .preview-button {
  611. background: linear-gradient(180deg, rgba(13, 74, 115, 0.9) 0%, rgba(8, 47, 77, 0.95) 100%);
  612. &::after {
  613. display: none;
  614. }
  615. }
  616. .result-panel {
  617. display: flex;
  618. flex-direction: column;
  619. gap: 10px;
  620. height: 100%;
  621. padding: 8px 12px 12px;
  622. box-sizing: border-box;
  623. }
  624. .result-top {
  625. flex: none;
  626. height: 165px;
  627. padding-top: 10px;
  628. box-sizing: border-box;
  629. background: url('@/assets/images/vent/home/selfIgnition/result-top-bg.png') no-repeat center / 100% 100%;
  630. }
  631. .score-line {
  632. display: flex;
  633. align-items: baseline;
  634. padding: 0 20px;
  635. }
  636. .score-label {
  637. color: #fff;
  638. font-size: 19px;
  639. letter-spacing: 1px;
  640. }
  641. .score-info {
  642. width: 12px;
  643. height: 12px;
  644. margin-left: 5px;
  645. align-self: center;
  646. opacity: 0.85;
  647. }
  648. .score-value {
  649. margin-left: 16px;
  650. color: #fff;
  651. font-size: 42px;
  652. font-weight: 700;
  653. line-height: 1;
  654. letter-spacing: 1px;
  655. text-shadow: 0 0 16px rgba(255, 255, 255, 0.35);
  656. }
  657. .gauge-line {
  658. display: flex;
  659. align-items: center;
  660. justify-content: space-between;
  661. margin-top: 16px;
  662. padding: 0 18px;
  663. }
  664. .gauge {
  665. display: flex;
  666. align-items: center;
  667. width: 150px;
  668. height: 26px;
  669. }
  670. .gauge-segment {
  671. width: 80px;
  672. height: 22px;
  673. object-fit: fill;
  674. & + & {
  675. margin-left: -8px;
  676. }
  677. }
  678. .gauge-segment-2 {
  679. opacity: 0.72;
  680. }
  681. .risk-tag {
  682. display: flex;
  683. align-items: center;
  684. width: 159px;
  685. height: 34px;
  686. padding-left: 22px;
  687. box-sizing: border-box;
  688. color: #fff;
  689. font-size: 15px;
  690. font-weight: 600;
  691. letter-spacing: 0.5px;
  692. white-space: nowrap;
  693. -webkit-mask: url('@/assets/images/vent/home/selfIgnition/risk-tag.svg') no-repeat center / 100% 100%;
  694. mask: url('@/assets/images/vent/home/selfIgnition/risk-tag.svg') no-repeat center / 100% 100%;
  695. background: var(--risk-color, #3df6ff);
  696. }
  697. .result-info {
  698. flex: none;
  699. display: flex;
  700. flex-direction: column;
  701. justify-content: center;
  702. gap: 2px;
  703. padding: 10px 14px;
  704. border-left: 1px solid rgba(61, 246, 255, 0.16);
  705. border-right: 1px solid rgba(61, 246, 255, 0.16);
  706. background: rgba(7, 42, 70, 0.62);
  707. }
  708. .info-row {
  709. display: flex;
  710. align-items: center;
  711. justify-content: space-between;
  712. height: 38px;
  713. color: #fff;
  714. font-size: 15px;
  715. letter-spacing: 0.5px;
  716. em {
  717. color: #3df6ff;
  718. font-style: normal;
  719. font-size: 16px;
  720. text-shadow: 0 0 8px rgba(61, 246, 255, 0.35);
  721. }
  722. }
  723. .level-table {
  724. flex: none;
  725. padding-top: 2px;
  726. }
  727. .level-head,
  728. .level-row {
  729. display: flex;
  730. align-items: center;
  731. text-align: center;
  732. }
  733. .level-head {
  734. height: 34px;
  735. border: 1px solid rgba(61, 246, 255, 0.4);
  736. border-radius: 4px;
  737. color: #3df6ff;
  738. font-size: 13px;
  739. background: url('@/assets/images/vent/home/selfIgnition/result-table-head.png') no-repeat center / 100% 100%;
  740. span:nth-child(1) {
  741. width: 42%;
  742. }
  743. span:nth-child(2) {
  744. width: 32%;
  745. }
  746. span:nth-child(3) {
  747. width: 26%;
  748. }
  749. }
  750. .level-row {
  751. position: relative;
  752. height: 38px;
  753. color: #e8f8ff;
  754. font-size: 14px;
  755. background: url('@/assets/images/vent/home/selfIgnition/result-row-glow.png') no-repeat center bottom / 100% 10px;
  756. span:nth-child(1) {
  757. width: 42%;
  758. }
  759. span:nth-child(2) {
  760. width: 32%;
  761. }
  762. span:nth-child(3) {
  763. width: 26%;
  764. }
  765. &.highlight {
  766. outline: 1px solid var(--highlight-color, #ff9d1c);
  767. outline-offset: -1px;
  768. background-color: rgba(255, 157, 28, 0.08);
  769. box-shadow: inset 0 0 18px rgba(255, 157, 28, 0.16);
  770. }
  771. }
  772. .level-name {
  773. display: flex;
  774. align-items: center;
  775. justify-content: center;
  776. gap: 6px;
  777. i {
  778. width: 7px;
  779. height: 7px;
  780. border-radius: 50%;
  781. }
  782. }
  783. .suggestion-box {
  784. position: relative;
  785. flex: 1;
  786. min-height: 0;
  787. padding: 14px 16px 14px 34px;
  788. box-sizing: border-box;
  789. overflow-y: auto;
  790. border: 1px solid rgba(61, 246, 255, 0.4);
  791. background: url('@/assets/images/vent/home/selfIgnition/result-block-bg.png') no-repeat center / 100% 100%;
  792. &::-webkit-scrollbar {
  793. width: 4px;
  794. }
  795. &::-webkit-scrollbar-thumb {
  796. background: rgba(61, 246, 255, 0.35);
  797. border-radius: 2px;
  798. }
  799. p {
  800. margin: 0;
  801. color: #fff;
  802. font-size: 15px;
  803. line-height: 30px;
  804. letter-spacing: 0.5px;
  805. }
  806. }
  807. .suggestion-icon {
  808. position: absolute;
  809. left: 16px;
  810. top: 20px;
  811. width: 9px;
  812. height: 9px;
  813. background: #f5222d;
  814. box-shadow: 0 0 9px rgba(245, 34, 45, 0.85);
  815. transform: rotate(45deg);
  816. }
  817. .action-button {
  818. position: relative;
  819. flex: none;
  820. width: 100%;
  821. height: 42px;
  822. border: 1px solid rgba(61, 246, 255, 0.55);
  823. border-radius: 3px;
  824. background:
  825. url('@/assets/images/vent/home/selfIgnition/submit-bg.png') no-repeat center / 100% 100%,
  826. linear-gradient(180deg, #1496e6 0%, #0a6cb5 100%);
  827. color: #fff;
  828. font-size: 16px;
  829. letter-spacing: 3px;
  830. cursor: pointer;
  831. transition:
  832. filter 0.2s,
  833. box-shadow 0.2s;
  834. &:hover:not(:disabled) {
  835. filter: brightness(1.12);
  836. box-shadow: 0 0 16px rgba(61, 246, 255, 0.35);
  837. }
  838. &:disabled {
  839. opacity: 0.5;
  840. cursor: not-allowed;
  841. &::after {
  842. display: none;
  843. }
  844. }
  845. &::after {
  846. content: '';
  847. position: absolute;
  848. left: 0;
  849. right: 0;
  850. bottom: -8px;
  851. height: 16px;
  852. background: url('@/assets/images/vent/home/selfIgnition/submit-glow.png') no-repeat center / 100% 100%;
  853. pointer-events: none;
  854. }
  855. }
  856. .report-dialog {
  857. width: 540px;
  858. padding: 22px 26px 24px;
  859. box-sizing: border-box;
  860. border: 1px solid rgba(61, 246, 255, 0.75);
  861. background: linear-gradient(180deg, #0b3a5e 0%, #071d33 100%);
  862. box-shadow: 0 0 40px rgba(61, 246, 255, 0.35);
  863. }
  864. .report-title {
  865. padding-bottom: 14px;
  866. border-bottom: 1px solid rgba(61, 246, 255, 0.3);
  867. color: #3df6ff;
  868. font-family: 'douyuFont', sans-serif;
  869. font-size: 20px;
  870. letter-spacing: 2px;
  871. text-align: center;
  872. }
  873. .report-body {
  874. padding: 16px 0;
  875. }
  876. .report-row {
  877. display: flex;
  878. align-items: center;
  879. justify-content: space-between;
  880. height: 36px;
  881. color: #cfefff;
  882. font-size: 14px;
  883. em {
  884. color: #fff;
  885. font-style: normal;
  886. font-size: 15px;
  887. }
  888. }
  889. .report-suggestion {
  890. position: relative;
  891. margin-top: 12px;
  892. padding: 14px 16px 14px 34px;
  893. border: 1px solid rgba(61, 246, 255, 0.3);
  894. background: rgba(7, 42, 70, 0.7);
  895. p {
  896. margin: 0;
  897. color: #fff;
  898. font-size: 14px;
  899. line-height: 26px;
  900. }
  901. }
  902. .report-close {
  903. margin-top: 4px;
  904. }
  905. .report-mask {
  906. position: absolute;
  907. inset: 0;
  908. z-index: 100;
  909. display: flex;
  910. align-items: center;
  911. justify-content: center;
  912. background: rgba(2, 10, 20, 0.72);
  913. backdrop-filter: blur(3px);
  914. }
  915. .ignition-fade-enter-active,
  916. .ignition-fade-leave-active {
  917. transition: opacity 0.2s ease;
  918. }
  919. .ignition-fade-enter-from,
  920. .ignition-fade-leave-to {
  921. opacity: 0;
  922. }
  923. </style>