RiskView.vue 71 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627
  1. <template>
  2. <div class="risk-page">
  3. <!-- Header -->
  4. <div class="risk-header">
  5. <div class="header-left">
  6. <div class="data-title">⚠️ 风险风控</div>
  7. <p class="header-desc">全面风险管理,AI驱动的风险预警和防控</p>
  8. </div>
  9. <div class="header-actions">
  10. <button class="btn btn-secondary" @click="showRiskAssessmentModal = true">
  11. <i class="fas fa-chart-bar"></i> 风险评估
  12. </button>
  13. <button class="btn btn-secondary" @click="showRiskMapModal = true">
  14. <i class="fas fa-map"></i> 风险地图
  15. </button>
  16. <button class="btn btn-secondary" @click="exportRiskReport">
  17. <i class="fas fa-download"></i> 导出报告
  18. </button>
  19. <button class="btn btn-primary" @click="showSettingsModal = true">
  20. <i class="fas fa-cog"></i> 风控设置
  21. </button>
  22. </div>
  23. </div>
  24. <!-- KPI Cards -->
  25. <div class="kpi-row">
  26. <div class="kpi-card critical" @click="filterByLevel('critical')">
  27. <div class="kpi-header">
  28. <span class="kpi-label">高风险</span>
  29. <span class="kpi-trend danger"><i class="fas fa-arrow-up"></i> 2</span>
  30. </div>
  31. <div class="kpi-value">{{ riskStats.critical }}</div>
  32. <div class="kpi-meta">需立即处理</div>
  33. </div>
  34. <div class="kpi-card medium" @click="filterByLevel('medium')">
  35. <div class="kpi-header">
  36. <span class="kpi-label">中风险</span>
  37. <span class="kpi-trend warning"><i class="fas fa-arrow-right"></i> 0</span>
  38. </div>
  39. <div class="kpi-value">{{ riskStats.medium }}</div>
  40. <div class="kpi-meta">需关注</div>
  41. </div>
  42. <div class="kpi-card low" @click="filterByLevel('low')">
  43. <div class="kpi-header">
  44. <span class="kpi-label">低风险</span>
  45. <span class="kpi-trend info"><i class="fas fa-arrow-down"></i> 3</span>
  46. </div>
  47. <div class="kpi-value">{{ riskStats.low }}</div>
  48. <div class="kpi-meta">监控中</div>
  49. </div>
  50. <div class="kpi-card resolved" @click="filterByLevel('resolved')">
  51. <div class="kpi-header">
  52. <span class="kpi-label">已处理</span>
  53. <span class="kpi-trend success"><i class="fas fa-check"></i> 8</span>
  54. </div>
  55. <div class="kpi-value">{{ riskStats.resolved }}</div>
  56. <div class="kpi-meta">本月处理</div>
  57. </div>
  58. <div class="kpi-card coverage">
  59. <div class="kpi-header">
  60. <span class="kpi-label">风控覆盖</span>
  61. <span class="kpi-trend success"><i class="fas fa-check"></i> +2%</span>
  62. </div>
  63. <div class="kpi-value">{{ coverageRate }}%</div>
  64. <div class="kpi-meta">全面覆盖</div>
  65. </div>
  66. </div>
  67. <!-- Tabs & Filters -->
  68. <div class="tabs-section">
  69. <div class="tabs">
  70. <div v-for="tab in tabs" :key="tab.key"
  71. class="tab-item" :class="{ active: activeTab === tab.key }"
  72. @click="activeTab = tab.key">
  73. {{ tab.label }}
  74. <span class="tab-count">{{ tab.count }}</span>
  75. </div>
  76. </div>
  77. <div class="filter-controls">
  78. <input type="text" class="search-input" v-model="searchQuery" placeholder="搜索风险...">
  79. <select class="filter-select" v-model="filterLevel">
  80. <option value="">全部风险</option>
  81. <option value="critical">高风险</option>
  82. <option value="medium">中风险</option>
  83. <option value="low">低风险</option>
  84. </select>
  85. <select class="filter-select" v-model="sortBy">
  86. <option value="latest">最新</option>
  87. <option value="severity">严重程度</option>
  88. <option value="impact">影响范围</option>
  89. </select>
  90. </div>
  91. </div>
  92. <!-- Content Sections -->
  93. <div v-if="activeTab === 'overview'" class="content-section">
  94. <!-- Risk Overview Grid -->
  95. <div class="overview-grid">
  96. <!-- Credit Risk -->
  97. <div class="risk-card-section">
  98. <div class="section-header">
  99. <h4><i class="fas fa-credit-card"></i> 客户信用风险</h4>
  100. <button class="btn-link" @click="showAllCreditRisks">查看全部</button>
  101. </div>
  102. <div class="risk-list">
  103. <div v-for="risk in creditRisks" :key="risk.id"
  104. class="risk-item" :class="risk.level"
  105. @click="openCreditDetail(risk)">
  106. <div class="risk-header">
  107. <span class="risk-title">{{ risk.customer }}</span>
  108. <span class="risk-level" :class="risk.level">{{ getLevelLabel(risk.level) }}</span>
  109. </div>
  110. <div class="risk-content">
  111. <div class="risk-reason">{{ risk.reason }}</div>
  112. <div class="risk-meta">
  113. <span><i class="fas fa-chart-line"></i> 评级: {{ risk.detail.oldRating }} → {{ risk.detail.newRating }}</span>
  114. <span><i class="fas fa-coins"></i> 年采购: {{ risk.detail.annualPurchase }}</span>
  115. </div>
  116. </div>
  117. <div class="risk-action">
  118. <button class="btn btn-sm btn-outline" @click.stop="handleRisk(risk)">
  119. <i class="fas fa-wrench"></i> 处理
  120. </button>
  121. </div>
  122. </div>
  123. </div>
  124. </div>
  125. <!-- Policy Risk -->
  126. <div class="risk-card-section">
  127. <div class="section-header">
  128. <h4><i class="fas fa-gavel"></i> 贸易政策风险</h4>
  129. <button class="btn-link" @click="showAllPolicyRisks">查看全部</button>
  130. </div>
  131. <div class="risk-list">
  132. <div v-for="risk in policyRisks" :key="risk.id"
  133. class="risk-item policy"
  134. @click="openPolicyDetail(risk)">
  135. <div class="risk-header">
  136. <span class="risk-title">{{ risk.region }} - {{ risk.title }}</span>
  137. <span class="risk-date">{{ risk.effectiveDate }}</span>
  138. </div>
  139. <div class="risk-content">
  140. <div class="risk-reason">{{ risk.description }}</div>
  141. <div class="risk-meta">
  142. <span><i class="fas fa-globe"></i> 影响客户: {{ risk.affectedCustomers }}</span>
  143. <span><i class="fas fa-box"></i> 影响产品: {{ risk.affectedProducts }}</span>
  144. </div>
  145. </div>
  146. <div class="risk-action">
  147. <button class="btn btn-sm btn-outline" @click.stop="handleRisk(risk)">
  148. <i class="fas fa-arrow-right"></i> 查看
  149. </button>
  150. </div>
  151. </div>
  152. </div>
  153. </div>
  154. <!-- Competitor Risk -->
  155. <div class="risk-card-section">
  156. <div class="section-header">
  157. <h4><i class="fas fa-chess"></i> 竞品动态风险</h4>
  158. <button class="btn-link" @click="showAllCompetitorRisks">查看全部</button>
  159. </div>
  160. <div class="risk-list">
  161. <div v-for="risk in competitorRisks" :key="risk.id"
  162. class="risk-item competitor"
  163. @click="openCompetitorDetail(risk)">
  164. <div class="risk-header">
  165. <span class="risk-title">{{ risk.competitor }}</span>
  166. <span class="risk-time">{{ risk.time }}</span>
  167. </div>
  168. <div class="risk-content">
  169. <div class="risk-reason">{{ risk.action }}</div>
  170. <div class="risk-meta">
  171. <span><i class="fas fa-impact"></i> 影响: {{ risk.impact }}</span>
  172. <span><i class="fas fa-chart-bar"></i> 市场份额: {{ risk.marketShare }}</span>
  173. </div>
  174. </div>
  175. <div class="risk-action">
  176. <button class="btn btn-sm btn-outline" @click.stop="handleRisk(risk)">
  177. <i class="fas fa-eye"></i> 详情
  178. </button>
  179. </div>
  180. </div>
  181. </div>
  182. </div>
  183. </div>
  184. <!-- Operational Risk -->
  185. <div class="risk-card-section full-width">
  186. <div class="section-header">
  187. <h4><i class="fas fa-industry"></i> 运营风险</h4>
  188. <button class="btn-link" @click="showAllOperationalRisks">查看全部</button>
  189. </div>
  190. <div class="operational-grid">
  191. <div v-for="risk in operationalRisks" :key="risk.id"
  192. class="operational-item" :class="risk.level"
  193. @click="openOperationalDetail(risk)">
  194. <div class="op-icon">
  195. <i :class="risk.icon"></i>
  196. </div>
  197. <div class="op-content">
  198. <div class="op-title">{{ risk.title }}</div>
  199. <div class="op-description">{{ risk.description }}</div>
  200. <div class="op-metrics">
  201. <span>影响: {{ risk.affectedAreas }}</span>
  202. <span>严重程度: {{ risk.severity }}</span>
  203. </div>
  204. </div>
  205. <div class="op-status" :class="risk.level">
  206. {{ getLevelLabel(risk.level) }}
  207. </div>
  208. </div>
  209. </div>
  210. </div>
  211. <!-- Risk Heatmap -->
  212. <div class="risk-card-section full-width">
  213. <div class="section-header">
  214. <h4><i class="fas fa-fire"></i> 风险热力图</h4>
  215. <button class="btn-link" @click="showRiskMapModal = true">展开查看</button>
  216. </div>
  217. <div class="heatmap-container">
  218. <div class="heatmap-grid">
  219. <div v-for="(cell, index) in riskHeatmap" :key="index"
  220. class="heatmap-cell" :class="cell.level"
  221. :title="cell.label">
  222. <span class="cell-value">{{ cell.value }}</span>
  223. </div>
  224. </div>
  225. <div class="heatmap-legend">
  226. <span class="legend-item critical"><i class="fas fa-square"></i> 高风险</span>
  227. <span class="legend-item medium"><i class="fas fa-square"></i> 中风险</span>
  228. <span class="legend-item low"><i class="fas fa-square"></i> 低风险</span>
  229. <span class="legend-item safe"><i class="fas fa-square"></i> 安全</span>
  230. </div>
  231. </div>
  232. </div>
  233. </div>
  234. <!-- Risk Timeline -->
  235. <div v-if="activeTab === 'timeline'" class="content-section">
  236. <div class="section-header">
  237. <h4><i class="fas fa-stream"></i> 风险时间线</h4>
  238. <button class="btn btn-secondary btn-sm" @click="refreshTimeline">
  239. <i class="fas fa-sync"></i> 刷新
  240. </button>
  241. </div>
  242. <div class="timeline">
  243. <div v-for="event in riskTimeline" :key="event.id" class="timeline-item">
  244. <div class="timeline-dot" :class="event.level"></div>
  245. <div class="timeline-content">
  246. <div class="timeline-header">
  247. <span class="timeline-title">{{ event.title }}</span>
  248. <span class="timeline-date">{{ event.date }}</span>
  249. </div>
  250. <div class="timeline-description">{{ event.description }}</div>
  251. <div class="timeline-action">
  252. <span class="action-status" :class="event.status">{{ event.status }}</span>
  253. </div>
  254. </div>
  255. </div>
  256. </div>
  257. </div>
  258. <!-- Risk Mitigation -->
  259. <div v-if="activeTab === 'mitigation'" class="content-section">
  260. <div class="section-header">
  261. <h4><i class="fas fa-shield-alt"></i> 风险缓解措施</h4>
  262. <button class="btn btn-secondary btn-sm" @click="showMitigationModal = true">
  263. <i class="fas fa-plus"></i> 新增措施
  264. </button>
  265. </div>
  266. <div class="mitigation-list">
  267. <div v-for="measure in mitigationMeasures" :key="measure.id"
  268. class="mitigation-item" :class="measure.status">
  269. <div class="measure-header">
  270. <span class="measure-title">{{ measure.title }}</span>
  271. <span class="measure-status" :class="measure.status">{{ measure.status }}</span>
  272. </div>
  273. <div class="measure-content">
  274. <div class="measure-description">{{ measure.description }}</div>
  275. <div class="measure-details">
  276. <span><i class="fas fa-calendar"></i> 开始: {{ measure.startDate }}</span>
  277. <span><i class="fas fa-flag"></i> 目标: {{ measure.targetDate }}</span>
  278. <span><i class="fas fa-chart-pie"></i> 进度: {{ measure.progress }}%</span>
  279. </div>
  280. </div>
  281. <div class="measure-progress">
  282. <div class="progress-bar">
  283. <div class="progress-fill" :style="{ width: measure.progress + '%' }"></div>
  284. </div>
  285. </div>
  286. <div class="measure-actions">
  287. <button class="btn btn-sm btn-outline" @click="editMeasure(measure)">
  288. <i class="fas fa-edit"></i> 编辑
  289. </button>
  290. <button class="btn btn-sm btn-outline" @click="viewMeasureDetail(measure)">
  291. <i class="fas fa-eye"></i> 详情
  292. </button>
  293. </div>
  294. </div>
  295. </div>
  296. </div>
  297. <!-- Risk Reports -->
  298. <div v-if="activeTab === 'reports'" class="content-section">
  299. <div class="section-header">
  300. <h4><i class="fas fa-file-alt"></i> 风险报告</h4>
  301. <button class="btn btn-secondary btn-sm" @click="generateRiskReport">
  302. <i class="fas fa-plus"></i> 生成报告
  303. </button>
  304. </div>
  305. <div class="reports-list">
  306. <div v-for="report in riskReports" :key="report.id"
  307. class="report-item" @click="openReport(report)">
  308. <div class="report-header">
  309. <span class="report-title">{{ report.title }}</span>
  310. <span class="report-date">{{ report.date }}</span>
  311. </div>
  312. <div class="report-content">
  313. <div class="report-summary">{{ report.summary }}</div>
  314. <div class="report-stats">
  315. <span><i class="fas fa-exclamation-triangle"></i> 风险数: {{ report.riskCount }}</span>
  316. <span><i class="fas fa-chart-bar"></i> 覆盖范围: {{ report.coverage }}</span>
  317. <span><i class="fas fa-shield-alt"></i> 措施数: {{ report.measures }}</span>
  318. </div>
  319. </div>
  320. <div class="report-actions">
  321. <button class="btn btn-sm btn-outline" @click.stop="viewReport(report)">
  322. <i class="fas fa-eye"></i> 查看
  323. </button>
  324. <button class="btn btn-sm btn-outline" @click.stop="downloadReport(report)">
  325. <i class="fas fa-download"></i> 下载
  326. </button>
  327. </div>
  328. </div>
  329. </div>
  330. </div>
  331. <!-- Settings Modal -->
  332. <div v-if="showSettingsModal" class="modal-overlay" @click.self="showSettingsModal = false">
  333. <div class="modal settings-modal">
  334. <div class="modal-header">
  335. <div class="modal-title"><i class="fas fa-cog"></i> 风控设置</div>
  336. <button class="modal-close" @click="showSettingsModal = false"><i class="fas fa-times"></i></button>
  337. </div>
  338. <div class="modal-body">
  339. <div class="settings-tabs">
  340. <div v-for="tab in settingsTabs" :key="tab.key"
  341. class="settings-tab" :class="{ active: activeSettingsTab === tab.key }"
  342. @click="activeSettingsTab = tab.key">
  343. <i :class="tab.icon"></i> {{ tab.label }}
  344. </div>
  345. </div>
  346. <!-- Credit Settings -->
  347. <div v-if="activeSettingsTab === 'credit'" class="settings-panel">
  348. <div class="panel-section">
  349. <h5>客户信用监控</h5>
  350. <div class="setting-item">
  351. <div class="setting-info">
  352. <div class="setting-name">启用自动信用评估</div>
  353. <div class="setting-desc">系统自动分析客户付款历史、财务状况</div>
  354. </div>
  355. <div class="setting-control">
  356. <label class="toggle-switch">
  357. <input type="checkbox" v-model="settings.credit.autoEvaluate">
  358. <span class="toggle-slider"></span>
  359. </label>
  360. </div>
  361. </div>
  362. <div class="setting-item">
  363. <div class="setting-info">
  364. <div class="setting-name">信用下调预警</div>
  365. <div class="setting-desc">客户信用评级下调时立即通知</div>
  366. </div>
  367. <div class="setting-control">
  368. <label class="toggle-switch">
  369. <input type="checkbox" v-model="settings.credit.downgradeAlert">
  370. <span class="toggle-slider"></span>
  371. </label>
  372. </div>
  373. </div>
  374. <div class="setting-item">
  375. <div class="setting-info">
  376. <div class="setting-name">付款逾期预警天数</div>
  377. <div class="setting-desc">客户付款逾期超过设定天数时触发预警</div>
  378. </div>
  379. <div class="setting-control">
  380. <select class="form-select" v-model="settings.credit.overdueDays">
  381. <option :value="7">7天</option>
  382. <option :value="15">15天</option>
  383. <option :value="30">30天</option>
  384. <option :value="60">60天</option>
  385. </select>
  386. </div>
  387. </div>
  388. <div class="setting-item">
  389. <div class="setting-info">
  390. <div class="setting-name">自动限制订单</div>
  391. <div class="setting-desc">高风险客户自动限制新订单发货</div>
  392. </div>
  393. <div class="setting-control">
  394. <label class="toggle-switch">
  395. <input type="checkbox" v-model="settings.credit.autoRestrict">
  396. <span class="toggle-slider"></span>
  397. </label>
  398. </div>
  399. </div>
  400. </div>
  401. </div>
  402. <!-- Policy Settings -->
  403. <div v-if="activeSettingsTab === 'policy'" class="settings-panel">
  404. <div class="panel-section">
  405. <h5>贸易政策监控</h5>
  406. <div class="setting-item">
  407. <div class="setting-info">
  408. <div class="setting-name">关税政策变动监控</div>
  409. <div class="setting-desc">监控主要出口市场的关税政策变化</div>
  410. </div>
  411. <div class="setting-control">
  412. <label class="toggle-switch">
  413. <input type="checkbox" v-model="settings.policy.tariffMonitor">
  414. <span class="toggle-slider"></span>
  415. </label>
  416. </div>
  417. </div>
  418. <div class="setting-item">
  419. <div class="setting-info">
  420. <div class="setting-name">产品认证要求监控</div>
  421. <div class="setting-desc">监控目标市场的产品认证和合规要求</div>
  422. </div>
  423. <div class="setting-control">
  424. <label class="toggle-switch">
  425. <input type="checkbox" v-model="settings.policy.certificationMonitor">
  426. <span class="toggle-slider"></span>
  427. </label>
  428. </div>
  429. </div>
  430. <div class="setting-item">
  431. <div class="setting-info">
  432. <div class="setting-name">提前预警天数</div>
  433. <div class="setting-desc">政策生效前多少天发送预警</div>
  434. </div>
  435. <div class="setting-control">
  436. <select class="form-select" v-model="settings.policy.advanceDays">
  437. <option :value="30">30天</option>
  438. <option :value="60">60天</option>
  439. <option :value="90">90天</option>
  440. <option :value="180">180天</option>
  441. </select>
  442. </div>
  443. </div>
  444. </div>
  445. </div>
  446. <!-- Competitor Settings -->
  447. <div v-if="activeSettingsTab === 'competitor'" class="settings-panel">
  448. <div class="panel-section">
  449. <h5>竞品动态监控</h5>
  450. <div class="setting-item">
  451. <div class="setting-info">
  452. <div class="setting-name">价格变动监控</div>
  453. <div class="setting-desc">监控竞品价格变化,超过阈值时预警</div>
  454. </div>
  455. <div class="setting-control">
  456. <label class="toggle-switch">
  457. <input type="checkbox" v-model="settings.competitor.priceMonitor">
  458. <span class="toggle-slider"></span>
  459. </label>
  460. </div>
  461. </div>
  462. <div class="setting-item">
  463. <div class="setting-info">
  464. <div class="setting-name">价格变动阈值</div>
  465. <div class="setting-desc">价格变动超过此百分比时触发预警</div>
  466. </div>
  467. <div class="setting-control">
  468. <select class="form-select" v-model="settings.competitor.priceThreshold">
  469. <option :value="5">5%</option>
  470. <option :value="10">10%</option>
  471. <option :value="15">15%</option>
  472. <option :value="20">20%</option>
  473. </select>
  474. </div>
  475. </div>
  476. <div class="setting-item">
  477. <div class="setting-info">
  478. <div class="setting-name">新产品发布监控</div>
  479. <div class="setting-desc">监控竞品新产品发布动态</div>
  480. </div>
  481. <div class="setting-control">
  482. <label class="toggle-switch">
  483. <input type="checkbox" v-model="settings.competitor.productMonitor">
  484. <span class="toggle-slider"></span>
  485. </label>
  486. </div>
  487. </div>
  488. </div>
  489. </div>
  490. <!-- Notification Settings -->
  491. <div v-if="activeSettingsTab === 'notification'" class="settings-panel">
  492. <div class="panel-section">
  493. <h5>预警通知方式</h5>
  494. <div class="setting-item">
  495. <div class="setting-info">
  496. <div class="setting-name">系统消息</div>
  497. <div class="setting-desc">在系统内显示风险预警通知</div>
  498. </div>
  499. <div class="setting-control">
  500. <label class="toggle-switch">
  501. <input type="checkbox" v-model="settings.notification.inApp">
  502. <span class="toggle-slider"></span>
  503. </label>
  504. </div>
  505. </div>
  506. <div class="setting-item">
  507. <div class="setting-info">
  508. <div class="setting-name">邮件通知</div>
  509. <div class="setting-desc">高风险事件发生时发送邮件通知</div>
  510. </div>
  511. <div class="setting-control">
  512. <label class="toggle-switch">
  513. <input type="checkbox" v-model="settings.notification.email">
  514. <span class="toggle-slider"></span>
  515. </label>
  516. </div>
  517. </div>
  518. <div class="setting-item">
  519. <div class="setting-info">
  520. <div class="setting-name">微信通知</div>
  521. <div class="setting-desc">紧急风险通过微信实时通知</div>
  522. </div>
  523. <div class="setting-control">
  524. <label class="toggle-switch">
  525. <input type="checkbox" v-model="settings.notification.wechat">
  526. <span class="toggle-slider"></span>
  527. </label>
  528. </div>
  529. </div>
  530. <div class="setting-item">
  531. <div class="setting-info">
  532. <div class="setting-name">通知级别</div>
  533. <div class="setting-desc">选择接收通知的风险级别</div>
  534. </div>
  535. <div class="setting-control">
  536. <select class="form-select" v-model="settings.notification.level">
  537. <option value="all">全部</option>
  538. <option value="critical">仅高风险</option>
  539. <option value="critical-medium">高风险和中风险</option>
  540. </select>
  541. </div>
  542. </div>
  543. </div>
  544. </div>
  545. </div>
  546. <div class="modal-footer">
  547. <button class="btn btn-secondary" @click="showSettingsModal = false">取消</button>
  548. <button class="btn btn-primary" @click="saveSettings">
  549. <i class="fas fa-save"></i> 保存设置
  550. </button>
  551. </div>
  552. </div>
  553. </div>
  554. <!-- Risk Assessment Modal -->
  555. <div v-if="showRiskAssessmentModal" class="modal-overlay" @click.self="showRiskAssessmentModal = false">
  556. <div class="modal assessment-modal">
  557. <div class="modal-header">
  558. <div class="modal-title"><i class="fas fa-chart-bar"></i> 风险评估</div>
  559. <button class="modal-close" @click="showRiskAssessmentModal = false"><i class="fas fa-times"></i></button>
  560. </div>
  561. <div class="modal-body">
  562. <div class="assessment-grid">
  563. <div class="assessment-item">
  564. <div class="assessment-label">总体风险指数</div>
  565. <div class="assessment-value">42/100</div>
  566. <div class="assessment-status">中等风险</div>
  567. </div>
  568. <div class="assessment-item">
  569. <div class="assessment-label">信用风险指数</div>
  570. <div class="assessment-value">35/100</div>
  571. <div class="assessment-status">低风险</div>
  572. </div>
  573. <div class="assessment-item">
  574. <div class="assessment-label">政策风险指数</div>
  575. <div class="assessment-value">58/100</div>
  576. <div class="assessment-status">中等风险</div>
  577. </div>
  578. <div class="assessment-item">
  579. <div class="assessment-label">竞争风险指数</div>
  580. <div class="assessment-value">45/100</div>
  581. <div class="assessment-status">中等风险</div>
  582. </div>
  583. </div>
  584. <div class="assessment-details">
  585. <h5>风险评估详情</h5>
  586. <div class="detail-item">
  587. <span class="detail-label">评估时间:</span>
  588. <span class="detail-value">2026-03-23 15:18</span>
  589. </div>
  590. <div class="detail-item">
  591. <span class="detail-label">覆盖范围:</span>
  592. <span class="detail-value">全球12个地区,156个客户,89个商机</span>
  593. </div>
  594. <div class="detail-item">
  595. <span class="detail-label">主要风险:</span>
  596. <span class="detail-value">贸易政策变化、竞品价格压力、客户信用下降</span>
  597. </div>
  598. </div>
  599. </div>
  600. <div class="modal-footer">
  601. <button class="btn btn-secondary" @click="showRiskAssessmentModal = false">关闭</button>
  602. <button class="btn btn-primary" @click="exportAssessment">
  603. <i class="fas fa-download"></i> 导出评估
  604. </button>
  605. </div>
  606. </div>
  607. </div>
  608. <!-- Risk Map Modal -->
  609. <div v-if="showRiskMapModal" class="modal-overlay" @click.self="showRiskMapModal = false">
  610. <div class="modal map-modal">
  611. <div class="modal-header">
  612. <div class="modal-title"><i class="fas fa-map"></i> 风险地图</div>
  613. <button class="modal-close" @click="showRiskMapModal = false"><i class="fas fa-times"></i></button>
  614. </div>
  615. <div class="modal-body">
  616. <div class="risk-map">
  617. <div class="map-placeholder">
  618. <i class="fas fa-globe"></i>
  619. <p>全球风险分布地图</p>
  620. <p class="map-desc">显示各地区风险热度分布</p>
  621. </div>
  622. </div>
  623. <div class="map-legend">
  624. <div class="legend-item">
  625. <span class="legend-color critical"></span>
  626. <span>高风险地区</span>
  627. </div>
  628. <div class="legend-item">
  629. <span class="legend-color medium"></span>
  630. <span>中风险地区</span>
  631. </div>
  632. <div class="legend-item">
  633. <span class="legend-color low"></span>
  634. <span>低风险地区</span>
  635. </div>
  636. <div class="legend-item">
  637. <span class="legend-color safe"></span>
  638. <span>安全地区</span>
  639. </div>
  640. </div>
  641. <div class="region-stats">
  642. <h5>地区风险统计</h5>
  643. <div class="stat-row">
  644. <div class="stat-item">
  645. <span class="stat-label">中东</span>
  646. <span class="stat-value critical">高风险</span>
  647. </div>
  648. <div class="stat-item">
  649. <span class="stat-label">欧洲</span>
  650. <span class="stat-value medium">中风险</span>
  651. </div>
  652. <div class="stat-item">
  653. <span class="stat-label">亚洲</span>
  654. <span class="stat-value low">低风险</span>
  655. </div>
  656. <div class="stat-item">
  657. <span class="stat-label">美洲</span>
  658. <span class="stat-value safe">安全</span>
  659. </div>
  660. </div>
  661. </div>
  662. </div>
  663. <div class="modal-footer">
  664. <button class="btn btn-secondary" @click="showRiskMapModal = false">关闭</button>
  665. </div>
  666. </div>
  667. </div>
  668. <!-- Risk Detail Modal -->
  669. <div v-if="showRiskDetailModal" class="modal-overlay" @click.self="showRiskDetailModal = false">
  670. <div class="modal detail-modal">
  671. <div class="modal-header">
  672. <div class="modal-title">
  673. <i class="fas fa-exclamation-circle"></i> {{ selectedRiskDetail?.title }}
  674. </div>
  675. <button class="modal-close" @click="showRiskDetailModal = false"><i class="fas fa-times"></i></button>
  676. </div>
  677. <div class="modal-body" v-if="selectedRiskDetail">
  678. <!-- Credit Risk Detail -->
  679. <div v-if="selectedRiskDetail.type === 'credit'" class="detail-content">
  680. <div class="detail-section">
  681. <h5>基本信息</h5>
  682. <div class="info-grid">
  683. <div class="info-item">
  684. <span class="label">客户名称</span>
  685. <span class="value">{{ selectedRiskDetail.details.customer }}</span>
  686. </div>
  687. <div class="info-item">
  688. <span class="label">风险等级</span>
  689. <span class="value" :class="selectedRiskDetail.details.level">{{ getLevelLabel(selectedRiskDetail.details.level) }}</span>
  690. </div>
  691. <div class="info-item">
  692. <span class="label">原信用评级</span>
  693. <span class="value">{{ selectedRiskDetail.details.oldRating }}</span>
  694. </div>
  695. <div class="info-item">
  696. <span class="label">当前评级</span>
  697. <span class="value danger">{{ selectedRiskDetail.details.newRating }}</span>
  698. </div>
  699. <div class="info-item">
  700. <span class="label">年采购额</span>
  701. <span class="value">{{ selectedRiskDetail.details.annualPurchase }}</span>
  702. </div>
  703. <div class="info-item">
  704. <span class="label">逾期次数</span>
  705. <span class="value warning">{{ selectedRiskDetail.details.overdueCount }}次</span>
  706. </div>
  707. </div>
  708. </div>
  709. <div class="detail-section">
  710. <h5>风险原因</h5>
  711. <p class="reason-text">{{ selectedRiskDetail.details.reason }}</p>
  712. </div>
  713. <div class="detail-section" v-if="selectedRiskDetail.details.affectedDeals">
  714. <h5>影响商机 ({{ selectedRiskDetail.details.affectedDeals }})</h5>
  715. <div class="affected-list">
  716. <div class="affected-item">
  717. <span class="deal-name">NVMe SSD订单</span>
  718. <span class="deal-amount">¥128K</span>
  719. <span class="deal-stage negotiation">谈判中</span>
  720. </div>
  721. <div class="affected-item">
  722. <span class="deal-name">SATA SSD订单</span>
  723. <span class="deal-amount">¥96K</span>
  724. <span class="deal-stage quotation">报价中</span>
  725. </div>
  726. </div>
  727. </div>
  728. <div class="detail-section">
  729. <h5>AI建议</h5>
  730. <div class="suggestion-box">
  731. <i class="fas fa-lightbulb"></i>
  732. <p>{{ selectedRiskDetail.details.suggestion }}</p>
  733. </div>
  734. </div>
  735. <div class="detail-section">
  736. <h5>建议处理措施</h5>
  737. <div class="actions-list">
  738. <div v-for="(action, index) in selectedRiskDetail.details.actions" :key="index" class="action-item">
  739. <i class="fas fa-check-circle"></i>
  740. <span>{{ action }}</span>
  741. </div>
  742. </div>
  743. </div>
  744. </div>
  745. <!-- Policy Risk Detail -->
  746. <div v-if="selectedRiskDetail.type === 'policy'" class="detail-content">
  747. <div class="detail-section">
  748. <h5>基本信息</h5>
  749. <div class="info-grid">
  750. <div class="info-item">
  751. <span class="label">地区</span>
  752. <span class="value">{{ selectedRiskDetail.details.region }}</span>
  753. </div>
  754. <div class="info-item">
  755. <span class="label">政策标题</span>
  756. <span class="value">{{ selectedRiskDetail.details.title }}</span>
  757. </div>
  758. <div class="info-item">
  759. <span class="label">生效日期</span>
  760. <span class="value">{{ selectedRiskDetail.details.effectiveDate }}</span>
  761. </div>
  762. <div class="info-item">
  763. <span class="label">影响客户</span>
  764. <span class="value">{{ selectedRiskDetail.details.affectedCustomers }}个</span>
  765. </div>
  766. <div class="info-item">
  767. <span class="label">影响产品</span>
  768. <span class="value">{{ selectedRiskDetail.details.affectedProducts }}款</span>
  769. </div>
  770. <div class="info-item">
  771. <span class="label">影响程度</span>
  772. <span class="value danger">{{ selectedRiskDetail.details.impact }}</span>
  773. </div>
  774. </div>
  775. </div>
  776. <div class="detail-section">
  777. <h5>政策描述</h5>
  778. <p class="reason-text">{{ selectedRiskDetail.details.description }}</p>
  779. </div>
  780. <div class="detail-section">
  781. <h5>时间线</h5>
  782. <div class="timeline-list">
  783. <div v-for="(item, index) in selectedRiskDetail.details.timeline" :key="index" class="timeline-item">
  784. <span class="timeline-date">{{ item.date }}</span>
  785. <span class="timeline-event">{{ item.event }}</span>
  786. </div>
  787. </div>
  788. </div>
  789. <div class="detail-section">
  790. <h5>应对建议</h5>
  791. <div class="suggestion-box">
  792. <i class="fas fa-lightbulb"></i>
  793. <p>{{ selectedRiskDetail.details.recommendation }}</p>
  794. </div>
  795. </div>
  796. </div>
  797. <!-- Competitor Risk Detail -->
  798. <div v-if="selectedRiskDetail.type === 'competitor'" class="detail-content">
  799. <div class="detail-section">
  800. <h5>基本信息</h5>
  801. <div class="info-grid">
  802. <div class="info-item">
  803. <span class="label">竞品名称</span>
  804. <span class="value">{{ selectedRiskDetail.details.competitor }}</span>
  805. </div>
  806. <div class="info-item">
  807. <span class="label">动作描述</span>
  808. <span class="value">{{ selectedRiskDetail.details.action }}</span>
  809. </div>
  810. <div class="info-item">
  811. <span class="label">发生时间</span>
  812. <span class="value">{{ selectedRiskDetail.details.time }}</span>
  813. </div>
  814. <div class="info-item">
  815. <span class="label">影响程度</span>
  816. <span class="value warning">{{ selectedRiskDetail.details.impact }}</span>
  817. </div>
  818. <div class="info-item">
  819. <span class="label">市场份额变化</span>
  820. <span class="value danger">{{ selectedRiskDetail.details.marketShare }}</span>
  821. </div>
  822. </div>
  823. </div>
  824. <div class="detail-section">
  825. <h5>风险分析</h5>
  826. <p class="reason-text">{{ selectedRiskDetail.details.analysis }}</p>
  827. </div>
  828. <div class="detail-section">
  829. <h5>应对建议</h5>
  830. <div class="suggestion-box">
  831. <i class="fas fa-lightbulb"></i>
  832. <p style="white-space: pre-line;">{{ selectedRiskDetail.details.recommendation }}</p>
  833. </div>
  834. </div>
  835. <div class="detail-section">
  836. <h5>我们的应对</h5>
  837. <div class="response-box">
  838. <span class="status-badge">{{ selectedRiskDetail.details.ourResponse }}</span>
  839. </div>
  840. </div>
  841. </div>
  842. <!-- Operational Risk Detail -->
  843. <div v-if="selectedRiskDetail.type === 'operational'" class="detail-content">
  844. <div class="detail-section">
  845. <h5>基本信息</h5>
  846. <div class="info-grid">
  847. <div class="info-item">
  848. <span class="label">风险标题</span>
  849. <span class="value">{{ selectedRiskDetail.details.title }}</span>
  850. </div>
  851. <div class="info-item">
  852. <span class="label">严重程度</span>
  853. <span class="value warning">{{ selectedRiskDetail.details.severity }}</span>
  854. </div>
  855. <div class="info-item">
  856. <span class="label">发生概率</span>
  857. <span class="value danger">{{ selectedRiskDetail.details.probability }}</span>
  858. </div>
  859. <div class="info-item">
  860. <span class="label">影响范围</span>
  861. <span class="value">{{ selectedRiskDetail.details.affectedAreas }}</span>
  862. </div>
  863. <div class="info-item">
  864. <span class="label">影响程度</span>
  865. <span class="value">{{ selectedRiskDetail.details.impact }}</span>
  866. </div>
  867. <div class="info-item">
  868. <span class="label">处理状态</span>
  869. <span class="value">{{ selectedRiskDetail.details.status }}</span>
  870. </div>
  871. </div>
  872. </div>
  873. <div class="detail-section">
  874. <h5>风险描述</h5>
  875. <p class="reason-text">{{ selectedRiskDetail.details.description }}</p>
  876. </div>
  877. <div class="detail-section">
  878. <h5>根本原因</h5>
  879. <p class="reason-text">{{ selectedRiskDetail.details.rootCause }}</p>
  880. </div>
  881. <div class="detail-section">
  882. <h5>缓解措施</h5>
  883. <div class="suggestion-box">
  884. <i class="fas fa-shield-alt"></i>
  885. <p style="white-space: pre-line;">{{ selectedRiskDetail.details.mitigation }}</p>
  886. </div>
  887. </div>
  888. </div>
  889. </div>
  890. <div class="modal-footer">
  891. <button class="btn btn-secondary" @click="showRiskDetailModal = false">关闭</button>
  892. <button class="btn btn-primary" @click="handleRisk(selectedRiskDetail.data)">
  893. <i class="fas fa-wrench"></i> 处理风险
  894. </button>
  895. </div>
  896. </div>
  897. </div>
  898. <transition name="toast">
  899. <div v-if="showToast" class="toast" :class="toastType">
  900. <i :class="toastIcon"></i> {{ toastMessage }}
  901. </div>
  902. </transition>
  903. </div>
  904. </template>
  905. <script setup>
  906. import { ref, computed } from 'vue'
  907. const activeTab = ref('overview')
  908. const activeSettingsTab = ref('credit')
  909. const searchQuery = ref('')
  910. const filterLevel = ref('')
  911. const sortBy = ref('latest')
  912. const showSettingsModal = ref(false)
  913. const showRiskAssessmentModal = ref(false)
  914. const showRiskMapModal = ref(false)
  915. const showMitigationModal = ref(false)
  916. const showToast = ref(false)
  917. const toastMessage = ref('')
  918. const toastType = ref('success')
  919. const toastIcon = ref('fas fa-check-circle')
  920. // Tabs
  921. const tabs = [
  922. { key: 'overview', label: '风险概览', count: 0 },
  923. { key: 'timeline', label: '风险时间线', count: 12 },
  924. { key: 'mitigation', label: '缓解措施', count: 8 },
  925. { key: 'reports', label: '风险报告', count: 5 }
  926. ]
  927. // KPI Stats
  928. const riskStats = ref({
  929. critical: 3,
  930. medium: 5,
  931. low: 12,
  932. resolved: 28
  933. })
  934. const coverageRate = ref(98)
  935. // Settings Tabs
  936. const settingsTabs = [
  937. { key: 'credit', label: '信用监控', icon: 'fas fa-credit-card' },
  938. { key: 'policy', label: '政策监控', icon: 'fas fa-gavel' },
  939. { key: 'competitor', label: '竞品监控', icon: 'fas fa-chess' },
  940. { key: 'notification', label: '通知设置', icon: 'fas fa-bell' }
  941. ]
  942. // Settings
  943. const settings = ref({
  944. credit: {
  945. autoEvaluate: true,
  946. downgradeAlert: true,
  947. overdueDays: 15,
  948. autoRestrict: true
  949. },
  950. policy: {
  951. tariffMonitor: true,
  952. certificationMonitor: true,
  953. advanceDays: 90
  954. },
  955. competitor: {
  956. priceMonitor: true,
  957. priceThreshold: 10,
  958. productMonitor: true
  959. },
  960. notification: {
  961. inApp: true,
  962. email: true,
  963. wechat: true,
  964. level: 'critical'
  965. }
  966. })
  967. // Credit Risks
  968. const creditRisks = ref([
  969. {
  970. id: 1,
  971. customer: 'ABC Corp',
  972. level: 'critical',
  973. reason: '信用评级 A→B,建议提高预付款比例',
  974. detail: {
  975. oldRating: 'A',
  976. newRating: 'B',
  977. annualPurchase: '$2.5M',
  978. overdueCount: 3
  979. },
  980. affectedDeals: 2
  981. },
  982. {
  983. id: 2,
  984. customer: 'XYZ Trading',
  985. level: 'medium',
  986. reason: '付款周期延长至90天,需关注',
  987. detail: {
  988. oldRating: 'A-',
  989. newRating: 'B+',
  990. annualPurchase: '$800K',
  991. overdueCount: 1
  992. },
  993. affectedDeals: 1
  994. },
  995. {
  996. id: 3,
  997. customer: 'Global Tech',
  998. level: 'low',
  999. reason: '近期订单量下降50%',
  1000. detail: {
  1001. oldRating: 'A',
  1002. newRating: 'B+',
  1003. annualPurchase: '$1.2M',
  1004. overdueCount: 0
  1005. },
  1006. affectedDeals: 0
  1007. }
  1008. ])
  1009. // Policy Risks
  1010. const policyRisks = ref([
  1011. {
  1012. id: 1,
  1013. region: '🇪🇺 欧盟',
  1014. title: '能效认证新规',
  1015. description: '欧盟能效标签新规将于2026年7月生效,涉及15款SSD产品',
  1016. effectiveDate: '2026-07-01',
  1017. affectedCustomers: 23,
  1018. affectedProducts: 15
  1019. },
  1020. {
  1021. id: 2,
  1022. region: '🇺🇸 美国',
  1023. title: '关税政策调整',
  1024. description: '部分产品税率上调5%,已通知相关客户调整报价',
  1025. effectiveDate: '2026-04-01',
  1026. affectedCustomers: 12,
  1027. affectedProducts: 8
  1028. }
  1029. ])
  1030. // Competitor Risks
  1031. const competitorRisks = ref([
  1032. {
  1033. id: 1,
  1034. competitor: '三星',
  1035. action: '发布新一代990 Pro SSD,价格下调15%',
  1036. time: '2小时前',
  1037. impact: '高',
  1038. marketShare: '-3%'
  1039. },
  1040. {
  1041. id: 2,
  1042. competitor: '西部数据',
  1043. action: '企业级SSD产品线更新',
  1044. time: '昨天',
  1045. impact: '中',
  1046. marketShare: '-1%'
  1047. },
  1048. {
  1049. id: 3,
  1050. competitor: '海力士',
  1051. action: '推出PCIe 5.0 SSD新品',
  1052. time: '3天前',
  1053. impact: '中',
  1054. marketShare: '-2%'
  1055. }
  1056. ])
  1057. // Operational Risks
  1058. const operationalRisks = ref([
  1059. {
  1060. id: 1,
  1061. icon: 'fas fa-truck',
  1062. title: '供应链风险',
  1063. description: '芯片供应紧张,可能影响交期',
  1064. level: 'medium',
  1065. affectedAreas: '全球',
  1066. severity: '中等'
  1067. },
  1068. {
  1069. id: 2,
  1070. icon: 'fas fa-industry',
  1071. title: '生产能力风险',
  1072. description: '产能利用率达到95%,缺乏应急能力',
  1073. level: 'medium',
  1074. affectedAreas: '国内',
  1075. severity: '中等'
  1076. },
  1077. {
  1078. id: 3,
  1079. icon: 'fas fa-users',
  1080. title: '人力资源风险',
  1081. description: '关键岗位人员流动率上升',
  1082. level: 'low',
  1083. affectedAreas: '技术部门',
  1084. severity: '低'
  1085. },
  1086. {
  1087. id: 4,
  1088. icon: 'fas fa-database',
  1089. title: '数据安全风险',
  1090. description: '客户数据保护需要加强',
  1091. level: 'critical',
  1092. affectedAreas: '全公司',
  1093. severity: '高'
  1094. }
  1095. ])
  1096. // Risk Heatmap
  1097. const riskHeatmap = ref([
  1098. { level: 'critical', value: 3, label: '高风险' },
  1099. { level: 'medium', value: 5, label: '中风险' },
  1100. { level: 'low', value: 12, label: '低风险' },
  1101. { level: 'safe', value: 28, label: '安全' },
  1102. { level: 'critical', value: 2, label: '高风险' },
  1103. { level: 'medium', value: 4, label: '中风险' },
  1104. { level: 'low', value: 8, label: '低风险' },
  1105. { level: 'safe', value: 15, label: '安全' }
  1106. ])
  1107. // Risk Timeline
  1108. const riskTimeline = ref([
  1109. { id: 1, title: 'ABC Corp信用下调', description: '客户信用评级从A下调至B', date: '2026-03-23', level: 'critical', status: '处理中' },
  1110. { id: 2, title: '欧盟能效新规', description: '欧盟能效标签新规即将生效', date: '2026-03-22', level: 'medium', status: '监控中' },
  1111. { id: 3, title: '三星价格下调', description: '三星SSD产品价格下调15%', date: '2026-03-21', level: 'medium', status: '已评估' },
  1112. { id: 4, title: '供应链风险预警', description: '芯片供应紧张,可能影响交期', date: '2026-03-20', level: 'medium', status: '已处理' }
  1113. ])
  1114. // Mitigation Measures
  1115. const mitigationMeasures = ref([
  1116. { id: 1, title: '提高ABC Corp预付款比例', description: '将预付款比例从30%提高至50%', status: 'in-progress', startDate: '2026-03-23', targetDate: '2026-04-06', progress: 60 },
  1117. { id: 2, title: '欧盟能效认证准备', description: '准备15款产品的欧盟能效认证', status: 'in-progress', startDate: '2026-03-20', targetDate: '2026-06-30', progress: 35 },
  1118. { id: 3, title: '竞品价格应对方案', description: '制定竞品价格应对和市场策略', status: 'completed', startDate: '2026-03-21', targetDate: '2026-03-23', progress: 100 },
  1119. { id: 4, title: '供应链多元化', description: '开发备用芯片供应商', status: 'in-progress', startDate: '2026-03-15', targetDate: '2026-05-31', progress: 45 }
  1120. ])
  1121. // Risk Reports
  1122. const riskReports = ref([
  1123. { id: 1, title: '2026年Q1风险评估报告', date: '2026-03-23', summary: '全面评估公司风险状况,识别关键风险', riskCount: 20, coverage: '全球12地区', measures: 8 },
  1124. { id: 2, title: '客户信用风险报告', date: '2026-03-20', summary: '分析客户信用状况,提出风险预警', riskCount: 8, coverage: '156个客户', measures: 5 },
  1125. { id: 3, title: '贸易政策风险报告', date: '2026-03-15', summary: '监控全球贸易政策变化', riskCount: 6, coverage: '12个国家', measures: 4 },
  1126. { id: 4, title: '竞争风险分析报告', date: '2026-03-10', summary: '分析竞品动态和市场压力', riskCount: 5, coverage: '5个竞品', measures: 3 },
  1127. { id: 5, title: '运营风险评估报告', date: '2026-03-05', summary: '评估内部运营风险', riskCount: 4, coverage: '全公司', measures: 6 }
  1128. ])
  1129. // Methods
  1130. const getLevelLabel = (level) => {
  1131. const map = { critical: '高风险', medium: '中风险', low: '低风险', safe: '安全' }
  1132. return map[level] || level
  1133. }
  1134. const filterByLevel = (level) => {
  1135. filterLevel.value = level
  1136. toast(`筛选${getLevelLabel(level)}`, 'info')
  1137. }
  1138. const selectedRiskDetail = ref(null)
  1139. const showRiskDetailModal = ref(false)
  1140. const openCreditDetail = (risk) => {
  1141. selectedRiskDetail.value = {
  1142. type: 'credit',
  1143. title: `${risk.customer} - 信用风险详情`,
  1144. data: risk,
  1145. details: {
  1146. riskType: '客户信用风险',
  1147. customer: risk.customer,
  1148. level: risk.level,
  1149. reason: risk.reason,
  1150. oldRating: risk.detail.oldRating,
  1151. newRating: risk.detail.newRating,
  1152. annualPurchase: risk.detail.annualPurchase,
  1153. overdueCount: risk.detail.overdueCount,
  1154. affectedDeals: risk.affectedDeals,
  1155. suggestion: '建议提高预付款比例至50%,并缩短账期至30天。对新订单要求银行保函。',
  1156. actions: ['提高预付款比例', '缩短账期', '要求银行保函', '暂停新订单']
  1157. }
  1158. }
  1159. showRiskDetailModal.value = true
  1160. }
  1161. const openPolicyDetail = (risk) => {
  1162. selectedRiskDetail.value = {
  1163. type: 'policy',
  1164. title: `${risk.title} - 贸易政策风险详情`,
  1165. data: risk,
  1166. details: {
  1167. riskType: '贸易政策风险',
  1168. region: risk.region,
  1169. title: risk.title,
  1170. description: risk.description,
  1171. effectiveDate: risk.effectiveDate,
  1172. affectedCustomers: risk.affectedCustomers,
  1173. affectedProducts: risk.affectedProducts,
  1174. impact: '高',
  1175. recommendation: '立即启动产品认证流程,预计需要3-4个月完成。建议提前通知客户,准备应对方案。',
  1176. timeline: [
  1177. { date: '2026-03-23', event: '政策发布' },
  1178. { date: '2026-04-01', event: '开始咨询期' },
  1179. { date: '2026-07-01', event: '政策生效' }
  1180. ]
  1181. }
  1182. }
  1183. showRiskDetailModal.value = true
  1184. }
  1185. const openCompetitorDetail = (risk) => {
  1186. selectedRiskDetail.value = {
  1187. type: 'competitor',
  1188. title: `${risk.competitor} - 竞品动态风险详情`,
  1189. data: risk,
  1190. details: {
  1191. riskType: '竞品动态风险',
  1192. competitor: risk.competitor,
  1193. action: risk.action,
  1194. time: risk.time,
  1195. impact: risk.impact,
  1196. marketShare: risk.marketShare,
  1197. analysis: '竞品价格下调15%,可能对我们的市场份额造成3-5%的影响。建议立即评估我们的定价策略。',
  1198. recommendation: '1. 分析竞品新产品的技术特点\n2. 评估我们的竞争优势\n3. 制定应对策略(价格、功能、服务)\n4. 加强客户沟通和关系维护',
  1199. ourResponse: '待制定'
  1200. }
  1201. }
  1202. showRiskDetailModal.value = true
  1203. }
  1204. const openOperationalDetail = (risk) => {
  1205. selectedRiskDetail.value = {
  1206. type: 'operational',
  1207. title: `${risk.title} - 运营风险详情`,
  1208. data: risk,
  1209. details: {
  1210. riskType: '运营风险',
  1211. title: risk.title,
  1212. description: risk.description,
  1213. affectedAreas: risk.affectedAreas,
  1214. severity: risk.severity,
  1215. probability: '高',
  1216. impact: '中等',
  1217. rootCause: '全球芯片短缺持续,主要供应商产能受限',
  1218. mitigation: '1. 开发备用供应商\n2. 增加库存储备\n3. 优化生产计划\n4. 与客户沟通交期调整',
  1219. status: '进行中'
  1220. }
  1221. }
  1222. showRiskDetailModal.value = true
  1223. }
  1224. const showAllCreditRisks = () => {
  1225. toast('查看全部信用风险', 'info')
  1226. }
  1227. const showAllPolicyRisks = () => {
  1228. toast('查看全部政策风险', 'info')
  1229. }
  1230. const showAllCompetitorRisks = () => {
  1231. toast('查看全部竞品风险', 'info')
  1232. }
  1233. const showAllOperationalRisks = () => {
  1234. toast('查看全部运营风险', 'info')
  1235. }
  1236. const handleRisk = (risk) => {
  1237. toast('风险处理已启动', 'success')
  1238. }
  1239. const refreshTimeline = () => {
  1240. toast('正在刷新时间线...', 'info')
  1241. setTimeout(() => {
  1242. toast('时间线已更新', 'success')
  1243. }, 1500)
  1244. }
  1245. const editMeasure = (measure) => {
  1246. toast(`编辑措施: ${measure.title}`, 'info')
  1247. }
  1248. const viewMeasureDetail = (measure) => {
  1249. toast(`查看措施详情: ${measure.title}`, 'info')
  1250. }
  1251. const generateRiskReport = () => {
  1252. toast('正在生成风险报告...', 'info')
  1253. setTimeout(() => {
  1254. toast('风险报告已生成', 'success')
  1255. }, 2000)
  1256. }
  1257. const openReport = (report) => {
  1258. toast(`查看${report.title}`, 'info')
  1259. }
  1260. const viewReport = (report) => {
  1261. toast(`查看${report.title}`, 'info')
  1262. }
  1263. const downloadReport = (report) => {
  1264. toast(`${report.title}下载中...`, 'success')
  1265. }
  1266. const saveSettings = () => {
  1267. showSettingsModal.value = false
  1268. toast('风控设置已保存', 'success')
  1269. }
  1270. const exportRiskReport = () => {
  1271. toast('风险报告导出中...', 'success')
  1272. }
  1273. const exportAssessment = () => {
  1274. toast('风险评估导出中...', 'success')
  1275. }
  1276. const toast = (message, type = 'success') => {
  1277. toastMessage.value = message
  1278. toastType.value = type
  1279. toastIcon.value = type === 'success' ? 'fas fa-check-circle' : 'fas fa-info-circle'
  1280. showToast.value = true
  1281. setTimeout(() => { showToast.value = false }, 3000)
  1282. }
  1283. </script>
  1284. <style scoped>
  1285. .risk-page { flex: 1; display: flex; flex-direction: column; padding: 24px; overflow-y: auto; background: var(--bg-dark); }
  1286. .risk-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 24px; }
  1287. .header-left { display: flex; flex-direction: column; gap: 4px; }
  1288. .data-title { font-size: 20px; font-weight: 600; }
  1289. .header-desc { font-size: 13px; color: var(--text-secondary); }
  1290. .header-actions { display: flex; gap: 8px; }
  1291. /* KPI Row */
  1292. .kpi-row { display: grid; grid-template-columns: repeat(5, 1fr); gap: 12px; margin-bottom: 24px; }
  1293. .kpi-card { background: var(--bg-card); border: 1px solid var(--border); border-radius: 12px; padding: 16px; cursor: pointer; transition: all 0.2s; }
  1294. .kpi-card:hover { border-color: var(--primary); transform: translateY(-2px); }
  1295. .kpi-card.critical { border-left: 4px solid #EF4444; }
  1296. .kpi-card.medium { border-left: 4px solid #F59E0B; }
  1297. .kpi-card.low { border-left: 4px solid #6366F1; }
  1298. .kpi-card.resolved { border-left: 4px solid #10B981; }
  1299. .kpi-card.coverage { border-left: 4px solid #8B5CF6; }
  1300. .kpi-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
  1301. .kpi-label { font-size: 12px; color: var(--text-secondary); }
  1302. .kpi-trend { font-size: 11px; padding: 2px 8px; border-radius: 10px; display: flex; align-items: center; gap: 4px; }
  1303. .kpi-trend.danger { background: rgba(239, 68, 68, 0.2); color: #EF4444; }
  1304. .kpi-trend.warning { background: rgba(245, 158, 11, 0.2); color: #F59E0B; }
  1305. .kpi-trend.info { background: rgba(99, 102, 241, 0.2); color: #6366F1; }
  1306. .kpi-trend.success { background: rgba(16, 185, 129, 0.2); color: #10B981; }
  1307. .kpi-value { font-size: 24px; font-weight: 700; margin-bottom: 4px; }
  1308. .kpi-meta { font-size: 11px; color: var(--text-muted); }
  1309. /* Tabs */
  1310. .tabs-section { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding-bottom: 12px; border-bottom: 1px solid var(--border); }
  1311. .tabs { display: flex; gap: 4px; }
  1312. .tab-item { padding: 8px 16px; border-radius: 8px; cursor: pointer; font-size: 13px; color: var(--text-secondary); display: flex; align-items: center; gap: 6px; transition: all 0.2s; }
  1313. .tab-item:hover { background: var(--bg-hover); }
  1314. .tab-item.active { background: var(--bg-hover); color: var(--text-primary); font-weight: 600; }
  1315. .tab-count { font-size: 11px; padding: 2px 6px; background: var(--bg-dark); border-radius: 10px; }
  1316. .tab-item.active .tab-count { background: var(--primary); color: white; }
  1317. .filter-controls { display: flex; gap: 8px; }
  1318. .search-input { padding: 8px 12px; background: var(--bg-hover); border: 1px solid var(--border); border-radius: 8px; color: var(--text-primary); font-size: 13px; width: 200px; }
  1319. .filter-select { padding: 8px 12px; background: var(--bg-hover); border: 1px solid var(--border); border-radius: 8px; color: var(--text-primary); font-size: 13px; }
  1320. /* Content Section */
  1321. .content-section { flex: 1; overflow-y: auto; }
  1322. /* Overview Grid */
  1323. .overview-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-bottom: 24px; }
  1324. .risk-card-section { background: var(--bg-card); border: 1px solid var(--border); border-radius: 12px; padding: 20px; }
  1325. .risk-card-section.full-width { grid-column: span 3; }
  1326. .section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
  1327. .section-header h4 { font-size: 14px; font-weight: 600; margin: 0; display: flex; align-items: center; gap: 8px; }
  1328. .section-header h4 i { color: var(--primary); }
  1329. .btn-link { background: none; border: none; color: var(--primary); font-size: 12px; cursor: pointer; }
  1330. /* Risk List */
  1331. .risk-list { display: flex; flex-direction: column; gap: 10px; }
  1332. .risk-item { padding: 12px; background: var(--bg-hover); border-radius: 10px; border-left: 4px solid var(--border); cursor: pointer; transition: all 0.2s; }
  1333. .risk-item:hover { background: rgba(99, 102, 241, 0.1); }
  1334. .risk-item.critical { border-left-color: #EF4444; }
  1335. .risk-item.medium { border-left-color: #F59E0B; }
  1336. .risk-item.low { border-left-color: #6366F1; }
  1337. .risk-item.policy { border-left-color: #8B5CF6; }
  1338. .risk-item.competitor { border-left-color: #EF4444; }
  1339. .risk-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
  1340. .risk-title { font-size: 13px; font-weight: 600; }
  1341. .risk-level { font-size: 10px; padding: 2px 8px; border-radius: 10px; }
  1342. .risk-level.critical { background: rgba(239, 68, 68, 0.2); color: #EF4444; }
  1343. .risk-level.medium { background: rgba(245, 158, 11, 0.2); color: #F59E0B; }
  1344. .risk-level.low { background: rgba(99, 102, 241, 0.2); color: #6366F1; }
  1345. .risk-date { font-size: 10px; color: var(--text-muted); }
  1346. .risk-time { font-size: 10px; color: var(--text-muted); }
  1347. .risk-content { margin-bottom: 8px; }
  1348. .risk-reason { font-size: 12px; color: var(--text-secondary); margin-bottom: 4px; }
  1349. .risk-meta { display: flex; gap: 12px; font-size: 10px; color: var(--text-muted); }
  1350. .risk-action { }
  1351. /* Operational Grid */
  1352. .operational-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; }
  1353. .operational-item { display: flex; align-items: center; gap: 12px; padding: 12px; background: var(--bg-hover); border-radius: 10px; border-left: 4px solid var(--border); cursor: pointer; transition: all 0.2s; }
  1354. .operational-item:hover { background: rgba(99, 102, 241, 0.1); }
  1355. .operational-item.critical { border-left-color: #EF4444; }
  1356. .operational-item.medium { border-left-color: #F59E0B; }
  1357. .operational-item.low { border-left-color: #6366F1; }
  1358. .op-icon { width: 40px; height: 40px; border-radius: 10px; background: var(--bg-dark); display: flex; align-items: center; justify-content: center; font-size: 16px; flex-shrink: 0; }
  1359. .op-content { flex: 1; min-width: 0; }
  1360. .op-title { font-size: 13px; font-weight: 600; margin-bottom: 2px; }
  1361. .op-description { font-size: 11px; color: var(--text-secondary); margin-bottom: 4px; }
  1362. .op-metrics { display: flex; gap: 12px; font-size: 10px; color: var(--text-muted); }
  1363. .op-status { font-size: 11px; padding: 2px 8px; border-radius: 10px; }
  1364. .op-status.critical { background: rgba(239, 68, 68, 0.2); color: #EF4444; }
  1365. .op-status.medium { background: rgba(245, 158, 11, 0.2); color: #F59E0B; }
  1366. .op-status.low { background: rgba(99, 102, 241, 0.2); color: #6366F1; }
  1367. /* Heatmap */
  1368. .heatmap-container { }
  1369. .heatmap-grid { display: grid; grid-template-columns: repeat(8, 1fr); gap: 4px; margin-bottom: 16px; }
  1370. .heatmap-cell { height: 40px; border-radius: 6px; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 600; cursor: pointer; }
  1371. .heatmap-cell.critical { background: rgba(239, 68, 68, 0.3); color: #EF4444; }
  1372. .heatmap-cell.medium { background: rgba(245, 158, 11, 0.3); color: #F59E0B; }
  1373. .heatmap-cell.low { background: rgba(99, 102, 241, 0.3); color: #6366F1; }
  1374. .heatmap-cell.safe { background: rgba(16, 185, 129, 0.3); color: #10B981; }
  1375. .heatmap-legend { display: flex; gap: 16px; font-size: 12px; }
  1376. .legend-item { display: flex; align-items: center; gap: 6px; }
  1377. .legend-item i { font-size: 10px; }
  1378. /* Timeline */
  1379. .timeline { display: flex; flex-direction: column; gap: 16px; }
  1380. .timeline-item { display: flex; gap: 16px; }
  1381. .timeline-dot { width: 12px; height: 12px; border-radius: 50%; flex-shrink: 0; margin-top: 4px; }
  1382. .timeline-dot.critical { background: #EF4444; }
  1383. .timeline-dot.medium { background: #F59E0B; }
  1384. .timeline-dot.low { background: #6366F1; }
  1385. .timeline-content { flex: 1; padding: 12px; background: var(--bg-hover); border-radius: 10px; }
  1386. .timeline-header { display: flex; justify-content: space-between; margin-bottom: 6px; }
  1387. .timeline-title { font-size: 13px; font-weight: 600; }
  1388. .timeline-date { font-size: 11px; color: var(--text-muted); }
  1389. .timeline-description { font-size: 12px; color: var(--text-secondary); margin-bottom: 8px; }
  1390. .timeline-action { }
  1391. .action-status { font-size: 10px; padding: 2px 8px; border-radius: 10px; }
  1392. /* Mitigation */
  1393. .mitigation-list { display: flex; flex-direction: column; gap: 12px; }
  1394. .mitigation-item { background: var(--bg-card); border: 1px solid var(--border); border-radius: 12px; padding: 16px; }
  1395. .mitigation-item.completed { opacity: 0.7; }
  1396. .measure-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
  1397. .measure-title { font-size: 14px; font-weight: 600; }
  1398. .measure-status { font-size: 10px; padding: 2px 8px; border-radius: 10px; }
  1399. .measure-status.in-progress { background: rgba(99, 102, 241, 0.2); color: #6366F1; }
  1400. .measure-status.completed { background: rgba(16, 185, 129, 0.2); color: #10B981; }
  1401. .measure-content { margin-bottom: 10px; }
  1402. .measure-description { font-size: 12px; color: var(--text-secondary); margin-bottom: 6px; }
  1403. .measure-details { display: flex; gap: 16px; font-size: 11px; color: var(--text-muted); }
  1404. .measure-progress { margin-bottom: 10px; }
  1405. .progress-bar { height: 4px; background: var(--bg-hover); border-radius: 2px; overflow: hidden; }
  1406. .progress-fill { height: 100%; background: var(--primary); border-radius: 2px; }
  1407. .measure-actions { display: flex; gap: 8px; }
  1408. /* Reports */
  1409. .reports-list { display: flex; flex-direction: column; gap: 12px; }
  1410. .report-item { background: var(--bg-card); border: 1px solid var(--border); border-radius: 12px; padding: 16px; cursor: pointer; transition: all 0.2s; }
  1411. .report-item:hover { border-color: var(--primary); }
  1412. .report-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
  1413. .report-title { font-size: 14px; font-weight: 600; }
  1414. .report-date { font-size: 11px; color: var(--text-muted); }
  1415. .report-content { margin-bottom: 10px; }
  1416. .report-summary { font-size: 12px; color: var(--text-secondary); margin-bottom: 8px; }
  1417. .report-stats { display: flex; gap: 16px; font-size: 11px; color: var(--text-muted); }
  1418. .report-actions { display: flex; gap: 8px; }
  1419. /* Modal */
  1420. .modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.7); display: flex; align-items: center; justify-content: center; z-index: 1000; padding: 20px; }
  1421. .modal { background: var(--bg-card); border-radius: 16px; width: 100%; max-width: 600px; max-height: 90vh; overflow: hidden; display: flex; flex-direction: column; }
  1422. .settings-modal { max-width: 700px; }
  1423. .assessment-modal { max-width: 500px; }
  1424. .map-modal { max-width: 600px; }
  1425. .detail-modal { max-width: 700px; }
  1426. .modal-header { display: flex; justify-content: space-between; align-items: center; padding: 20px; border-bottom: 1px solid var(--border); }
  1427. .modal-title { font-size: 16px; font-weight: 600; display: flex; align-items: center; gap: 8px; }
  1428. .modal-close { width: 32px; height: 32px; background: var(--bg-hover); border: none; border-radius: 8px; color: var(--text-secondary); cursor: pointer; display: flex; align-items: center; justify-content: center; }
  1429. .modal-body { flex: 1; overflow-y: auto; padding: 20px; }
  1430. .modal-footer { display: flex; justify-content: flex-end; gap: 12px; padding: 20px; border-top: 1px solid var(--border); }
  1431. /* Detail Content */
  1432. .detail-content { }
  1433. .detail-section { margin-bottom: 24px; }
  1434. .detail-section h5 { font-size: 14px; font-weight: 600; margin-bottom: 12px; }
  1435. .info-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; }
  1436. .info-item { display: flex; flex-direction: column; gap: 4px; padding: 12px; background: var(--bg-hover); border-radius: 8px; }
  1437. .info-item .label { font-size: 11px; color: var(--text-muted); }
  1438. .info-item .value { font-size: 14px; font-weight: 600; }
  1439. .info-item .value.critical { color: #EF4444; }
  1440. .info-item .value.medium { color: #F59E0B; }
  1441. .info-item .value.low { color: #6366F1; }
  1442. .info-item .value.danger { color: #EF4444; }
  1443. .info-item .value.warning { color: #F59E0B; }
  1444. .reason-text { font-size: 13px; color: var(--text-secondary); line-height: 1.6; padding: 12px; background: var(--bg-hover); border-radius: 8px; }
  1445. .affected-list { display: flex; flex-direction: column; gap: 8px; }
  1446. .affected-item { display: flex; justify-content: space-between; align-items: center; padding: 10px 12px; background: var(--bg-hover); border-radius: 8px; }
  1447. .deal-name { font-size: 13px; font-weight: 500; }
  1448. .deal-amount { font-size: 12px; color: var(--text-secondary); }
  1449. .deal-stage { font-size: 10px; padding: 2px 8px; border-radius: 6px; }
  1450. .deal-stage.negotiation { background: rgba(99, 102, 241, 0.2); color: #6366F1; }
  1451. .deal-stage.quotation { background: rgba(245, 158, 11, 0.2); color: #F59E0B; }
  1452. .suggestion-box { display: flex; gap: 12px; padding: 14px; background: rgba(16, 185, 129, 0.1); border: 1px solid rgba(16, 185, 129, 0.2); border-radius: 10px; }
  1453. .suggestion-box i { color: var(--success); font-size: 18px; flex-shrink: 0; }
  1454. .suggestion-box p { font-size: 13px; color: var(--text-secondary); line-height: 1.6; margin: 0; }
  1455. .actions-list { display: flex; flex-direction: column; gap: 8px; }
  1456. .action-item { display: flex; align-items: center; gap: 10px; padding: 10px 12px; background: var(--bg-hover); border-radius: 8px; font-size: 13px; }
  1457. .action-item i { color: var(--success); }
  1458. .timeline-list { display: flex; flex-direction: column; gap: 8px; }
  1459. .timeline-item { display: flex; justify-content: space-between; align-items: center; padding: 10px 12px; background: var(--bg-hover); border-radius: 8px; }
  1460. .timeline-date { font-size: 12px; color: var(--text-muted); }
  1461. .timeline-event { font-size: 13px; font-weight: 500; }
  1462. .response-box { padding: 12px; background: var(--bg-hover); border-radius: 8px; }
  1463. .status-badge { font-size: 12px; padding: 4px 12px; background: rgba(245, 158, 11, 0.2); color: #F59E0B; border-radius: 10px; }
  1464. /* Settings */
  1465. .settings-tabs { display: flex; gap: 8px; margin-bottom: 20px; padding-bottom: 16px; border-bottom: 1px solid var(--border); }
  1466. .settings-tab { padding: 10px 16px; border-radius: 8px; font-size: 13px; cursor: pointer; display: flex; align-items: center; gap: 8px; color: var(--text-secondary); transition: all 0.2s; }
  1467. .settings-tab:hover { background: var(--bg-hover); }
  1468. .settings-tab.active { background: var(--primary); color: white; }
  1469. .settings-panel { }
  1470. .panel-section { margin-bottom: 20px; }
  1471. .panel-section h5 { font-size: 13px; font-weight: 600; margin-bottom: 12px; }
  1472. .setting-item { display: flex; justify-content: space-between; align-items: flex-start; padding: 12px; background: var(--bg-hover); border-radius: 10px; margin-bottom: 10px; gap: 12px; }
  1473. .setting-info { flex: 1; }
  1474. .setting-name { font-size: 13px; font-weight: 600; margin-bottom: 2px; }
  1475. .setting-desc { font-size: 11px; color: var(--text-secondary); }
  1476. .setting-control { display: flex; align-items: center; flex-shrink: 0; }
  1477. .toggle-switch { position: relative; display: inline-block; width: 44px; height: 24px; }
  1478. .toggle-switch input { opacity: 0; width: 0; height: 0; }
  1479. .toggle-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: var(--border); transition: .3s; border-radius: 24px; }
  1480. .toggle-slider:before { position: absolute; content: ""; height: 18px; width: 18px; left: 3px; bottom: 3px; background-color: white; transition: .3s; border-radius: 50%; }
  1481. input:checked + .toggle-slider { background-color: var(--primary); }
  1482. input:checked + .toggle-slider:before { transform: translateX(20px); }
  1483. .form-select { padding: 8px 12px; background: var(--bg-dark); border: 1px solid var(--border); border-radius: 6px; color: var(--text-primary); font-size: 13px; }
  1484. /* Assessment */
  1485. .assessment-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; margin-bottom: 20px; }
  1486. .assessment-item { padding: 16px; background: var(--bg-hover); border-radius: 10px; text-align: center; }
  1487. .assessment-label { font-size: 12px; color: var(--text-muted); margin-bottom: 8px; }
  1488. .assessment-value { font-size: 24px; font-weight: 700; margin-bottom: 4px; }
  1489. .assessment-status { font-size: 11px; color: var(--text-secondary); }
  1490. .assessment-details { }
  1491. .assessment-details h5 { font-size: 13px; font-weight: 600; margin-bottom: 12px; }
  1492. .detail-item { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid var(--border); }
  1493. .detail-label { font-size: 12px; color: var(--text-muted); }
  1494. .detail-value { font-size: 12px; color: var(--text-secondary); }
  1495. /* Risk Map */
  1496. .risk-map { height: 300px; background: var(--bg-hover); border-radius: 10px; display: flex; align-items: center; justify-content: center; margin-bottom: 20px; }
  1497. .map-placeholder { text-align: center; color: var(--text-muted); }
  1498. .map-placeholder i { font-size: 48px; margin-bottom: 12px; }
  1499. .map-desc { font-size: 11px; color: var(--text-muted); }
  1500. .map-legend { display: flex; gap: 16px; margin-bottom: 20px; }
  1501. .legend-color { width: 12px; height: 12px; border-radius: 3px; }
  1502. .legend-color.critical { background: #EF4444; }
  1503. .legend-color.medium { background: #F59E0B; }
  1504. .legend-color.low { background: #6366F1; }
  1505. .legend-color.safe { background: #10B981; }
  1506. .region-stats { }
  1507. .region-stats h5 { font-size: 13px; font-weight: 600; margin-bottom: 12px; }
  1508. .stat-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; }
  1509. .stat-item { display: flex; justify-content: space-between; align-items: center; padding: 10px 12px; background: var(--bg-hover); border-radius: 8px; }
  1510. .stat-label { font-size: 12px; }
  1511. .stat-value { font-size: 11px; padding: 2px 8px; border-radius: 10px; }
  1512. .stat-value.critical { background: rgba(239, 68, 68, 0.2); color: #EF4444; }
  1513. .stat-value.medium { background: rgba(245, 158, 11, 0.2); color: #F59E0B; }
  1514. .stat-value.low { background: rgba(99, 102, 241, 0.2); color: #6366F1; }
  1515. .stat-value.safe { background: rgba(16, 185, 129, 0.2); color: #10B981; }
  1516. /* Toast */
  1517. .toast { position: fixed; bottom: 24px; right: 24px; padding: 12px 20px; background: var(--bg-card); border: 1px solid var(--border); border-radius: 10px; display: flex; align-items: center; gap: 10px; font-size: 13px; box-shadow: 0 4px 20px rgba(0,0,0,0.3); z-index: 2000; }
  1518. .toast.success { border-color: var(--success); color: var(--success); }
  1519. .toast-enter-active, .toast-leave-active { transition: all 0.3s ease; }
  1520. .toast-enter-from, .toast-leave-to { opacity: 0; transform: translateY(20px); }
  1521. /* Responsive */
  1522. @media (max-width: 1400px) {
  1523. .kpi-row { grid-template-columns: repeat(3, 1fr); }
  1524. .overview-grid { grid-template-columns: repeat(2, 1fr); }
  1525. .operational-grid { grid-template-columns: 1fr; }
  1526. }
  1527. @media (max-width: 992px) {
  1528. .kpi-row { grid-template-columns: repeat(2, 1fr); }
  1529. .overview-grid { grid-template-columns: 1fr; }
  1530. .risk-card-section.full-width { grid-column: span 1; }
  1531. }
  1532. </style>