| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627 |
- <template>
- <div class="risk-page">
- <!-- Header -->
- <div class="risk-header">
- <div class="header-left">
- <div class="data-title">⚠️ 风险风控</div>
- <p class="header-desc">全面风险管理,AI驱动的风险预警和防控</p>
- </div>
- <div class="header-actions">
- <button class="btn btn-secondary" @click="showRiskAssessmentModal = true">
- <i class="fas fa-chart-bar"></i> 风险评估
- </button>
- <button class="btn btn-secondary" @click="showRiskMapModal = true">
- <i class="fas fa-map"></i> 风险地图
- </button>
- <button class="btn btn-secondary" @click="exportRiskReport">
- <i class="fas fa-download"></i> 导出报告
- </button>
- <button class="btn btn-primary" @click="showSettingsModal = true">
- <i class="fas fa-cog"></i> 风控设置
- </button>
- </div>
- </div>
- <!-- KPI Cards -->
- <div class="kpi-row">
- <div class="kpi-card critical" @click="filterByLevel('critical')">
- <div class="kpi-header">
- <span class="kpi-label">高风险</span>
- <span class="kpi-trend danger"><i class="fas fa-arrow-up"></i> 2</span>
- </div>
- <div class="kpi-value">{{ riskStats.critical }}</div>
- <div class="kpi-meta">需立即处理</div>
- </div>
- <div class="kpi-card medium" @click="filterByLevel('medium')">
- <div class="kpi-header">
- <span class="kpi-label">中风险</span>
- <span class="kpi-trend warning"><i class="fas fa-arrow-right"></i> 0</span>
- </div>
- <div class="kpi-value">{{ riskStats.medium }}</div>
- <div class="kpi-meta">需关注</div>
- </div>
- <div class="kpi-card low" @click="filterByLevel('low')">
- <div class="kpi-header">
- <span class="kpi-label">低风险</span>
- <span class="kpi-trend info"><i class="fas fa-arrow-down"></i> 3</span>
- </div>
- <div class="kpi-value">{{ riskStats.low }}</div>
- <div class="kpi-meta">监控中</div>
- </div>
- <div class="kpi-card resolved" @click="filterByLevel('resolved')">
- <div class="kpi-header">
- <span class="kpi-label">已处理</span>
- <span class="kpi-trend success"><i class="fas fa-check"></i> 8</span>
- </div>
- <div class="kpi-value">{{ riskStats.resolved }}</div>
- <div class="kpi-meta">本月处理</div>
- </div>
- <div class="kpi-card coverage">
- <div class="kpi-header">
- <span class="kpi-label">风控覆盖</span>
- <span class="kpi-trend success"><i class="fas fa-check"></i> +2%</span>
- </div>
- <div class="kpi-value">{{ coverageRate }}%</div>
- <div class="kpi-meta">全面覆盖</div>
- </div>
- </div>
- <!-- Tabs & Filters -->
- <div class="tabs-section">
- <div class="tabs">
- <div v-for="tab in tabs" :key="tab.key"
- class="tab-item" :class="{ active: activeTab === tab.key }"
- @click="activeTab = tab.key">
- {{ tab.label }}
- <span class="tab-count">{{ tab.count }}</span>
- </div>
- </div>
- <div class="filter-controls">
- <input type="text" class="search-input" v-model="searchQuery" placeholder="搜索风险...">
- <select class="filter-select" v-model="filterLevel">
- <option value="">全部风险</option>
- <option value="critical">高风险</option>
- <option value="medium">中风险</option>
- <option value="low">低风险</option>
- </select>
- <select class="filter-select" v-model="sortBy">
- <option value="latest">最新</option>
- <option value="severity">严重程度</option>
- <option value="impact">影响范围</option>
- </select>
- </div>
- </div>
- <!-- Content Sections -->
- <div v-if="activeTab === 'overview'" class="content-section">
- <!-- Risk Overview Grid -->
- <div class="overview-grid">
- <!-- Credit Risk -->
- <div class="risk-card-section">
- <div class="section-header">
- <h4><i class="fas fa-credit-card"></i> 客户信用风险</h4>
- <button class="btn-link" @click="showAllCreditRisks">查看全部</button>
- </div>
- <div class="risk-list">
- <div v-for="risk in creditRisks" :key="risk.id"
- class="risk-item" :class="risk.level"
- @click="openCreditDetail(risk)">
- <div class="risk-header">
- <span class="risk-title">{{ risk.customer }}</span>
- <span class="risk-level" :class="risk.level">{{ getLevelLabel(risk.level) }}</span>
- </div>
- <div class="risk-content">
- <div class="risk-reason">{{ risk.reason }}</div>
- <div class="risk-meta">
- <span><i class="fas fa-chart-line"></i> 评级: {{ risk.detail.oldRating }} → {{ risk.detail.newRating }}</span>
- <span><i class="fas fa-coins"></i> 年采购: {{ risk.detail.annualPurchase }}</span>
- </div>
- </div>
- <div class="risk-action">
- <button class="btn btn-sm btn-outline" @click.stop="handleRisk(risk)">
- <i class="fas fa-wrench"></i> 处理
- </button>
- </div>
- </div>
- </div>
- </div>
- <!-- Policy Risk -->
- <div class="risk-card-section">
- <div class="section-header">
- <h4><i class="fas fa-gavel"></i> 贸易政策风险</h4>
- <button class="btn-link" @click="showAllPolicyRisks">查看全部</button>
- </div>
- <div class="risk-list">
- <div v-for="risk in policyRisks" :key="risk.id"
- class="risk-item policy"
- @click="openPolicyDetail(risk)">
- <div class="risk-header">
- <span class="risk-title">{{ risk.region }} - {{ risk.title }}</span>
- <span class="risk-date">{{ risk.effectiveDate }}</span>
- </div>
- <div class="risk-content">
- <div class="risk-reason">{{ risk.description }}</div>
- <div class="risk-meta">
- <span><i class="fas fa-globe"></i> 影响客户: {{ risk.affectedCustomers }}</span>
- <span><i class="fas fa-box"></i> 影响产品: {{ risk.affectedProducts }}</span>
- </div>
- </div>
- <div class="risk-action">
- <button class="btn btn-sm btn-outline" @click.stop="handleRisk(risk)">
- <i class="fas fa-arrow-right"></i> 查看
- </button>
- </div>
- </div>
- </div>
- </div>
- <!-- Competitor Risk -->
- <div class="risk-card-section">
- <div class="section-header">
- <h4><i class="fas fa-chess"></i> 竞品动态风险</h4>
- <button class="btn-link" @click="showAllCompetitorRisks">查看全部</button>
- </div>
- <div class="risk-list">
- <div v-for="risk in competitorRisks" :key="risk.id"
- class="risk-item competitor"
- @click="openCompetitorDetail(risk)">
- <div class="risk-header">
- <span class="risk-title">{{ risk.competitor }}</span>
- <span class="risk-time">{{ risk.time }}</span>
- </div>
- <div class="risk-content">
- <div class="risk-reason">{{ risk.action }}</div>
- <div class="risk-meta">
- <span><i class="fas fa-impact"></i> 影响: {{ risk.impact }}</span>
- <span><i class="fas fa-chart-bar"></i> 市场份额: {{ risk.marketShare }}</span>
- </div>
- </div>
- <div class="risk-action">
- <button class="btn btn-sm btn-outline" @click.stop="handleRisk(risk)">
- <i class="fas fa-eye"></i> 详情
- </button>
- </div>
- </div>
- </div>
- </div>
- </div>
- <!-- Operational Risk -->
- <div class="risk-card-section full-width">
- <div class="section-header">
- <h4><i class="fas fa-industry"></i> 运营风险</h4>
- <button class="btn-link" @click="showAllOperationalRisks">查看全部</button>
- </div>
- <div class="operational-grid">
- <div v-for="risk in operationalRisks" :key="risk.id"
- class="operational-item" :class="risk.level"
- @click="openOperationalDetail(risk)">
- <div class="op-icon">
- <i :class="risk.icon"></i>
- </div>
- <div class="op-content">
- <div class="op-title">{{ risk.title }}</div>
- <div class="op-description">{{ risk.description }}</div>
- <div class="op-metrics">
- <span>影响: {{ risk.affectedAreas }}</span>
- <span>严重程度: {{ risk.severity }}</span>
- </div>
- </div>
- <div class="op-status" :class="risk.level">
- {{ getLevelLabel(risk.level) }}
- </div>
- </div>
- </div>
- </div>
- <!-- Risk Heatmap -->
- <div class="risk-card-section full-width">
- <div class="section-header">
- <h4><i class="fas fa-fire"></i> 风险热力图</h4>
- <button class="btn-link" @click="showRiskMapModal = true">展开查看</button>
- </div>
- <div class="heatmap-container">
- <div class="heatmap-grid">
- <div v-for="(cell, index) in riskHeatmap" :key="index"
- class="heatmap-cell" :class="cell.level"
- :title="cell.label">
- <span class="cell-value">{{ cell.value }}</span>
- </div>
- </div>
- <div class="heatmap-legend">
- <span class="legend-item critical"><i class="fas fa-square"></i> 高风险</span>
- <span class="legend-item medium"><i class="fas fa-square"></i> 中风险</span>
- <span class="legend-item low"><i class="fas fa-square"></i> 低风险</span>
- <span class="legend-item safe"><i class="fas fa-square"></i> 安全</span>
- </div>
- </div>
- </div>
- </div>
- <!-- Risk Timeline -->
- <div v-if="activeTab === 'timeline'" class="content-section">
- <div class="section-header">
- <h4><i class="fas fa-stream"></i> 风险时间线</h4>
- <button class="btn btn-secondary btn-sm" @click="refreshTimeline">
- <i class="fas fa-sync"></i> 刷新
- </button>
- </div>
- <div class="timeline">
- <div v-for="event in riskTimeline" :key="event.id" class="timeline-item">
- <div class="timeline-dot" :class="event.level"></div>
- <div class="timeline-content">
- <div class="timeline-header">
- <span class="timeline-title">{{ event.title }}</span>
- <span class="timeline-date">{{ event.date }}</span>
- </div>
- <div class="timeline-description">{{ event.description }}</div>
- <div class="timeline-action">
- <span class="action-status" :class="event.status">{{ event.status }}</span>
- </div>
- </div>
- </div>
- </div>
- </div>
- <!-- Risk Mitigation -->
- <div v-if="activeTab === 'mitigation'" class="content-section">
- <div class="section-header">
- <h4><i class="fas fa-shield-alt"></i> 风险缓解措施</h4>
- <button class="btn btn-secondary btn-sm" @click="showMitigationModal = true">
- <i class="fas fa-plus"></i> 新增措施
- </button>
- </div>
- <div class="mitigation-list">
- <div v-for="measure in mitigationMeasures" :key="measure.id"
- class="mitigation-item" :class="measure.status">
- <div class="measure-header">
- <span class="measure-title">{{ measure.title }}</span>
- <span class="measure-status" :class="measure.status">{{ measure.status }}</span>
- </div>
- <div class="measure-content">
- <div class="measure-description">{{ measure.description }}</div>
- <div class="measure-details">
- <span><i class="fas fa-calendar"></i> 开始: {{ measure.startDate }}</span>
- <span><i class="fas fa-flag"></i> 目标: {{ measure.targetDate }}</span>
- <span><i class="fas fa-chart-pie"></i> 进度: {{ measure.progress }}%</span>
- </div>
- </div>
- <div class="measure-progress">
- <div class="progress-bar">
- <div class="progress-fill" :style="{ width: measure.progress + '%' }"></div>
- </div>
- </div>
- <div class="measure-actions">
- <button class="btn btn-sm btn-outline" @click="editMeasure(measure)">
- <i class="fas fa-edit"></i> 编辑
- </button>
- <button class="btn btn-sm btn-outline" @click="viewMeasureDetail(measure)">
- <i class="fas fa-eye"></i> 详情
- </button>
- </div>
- </div>
- </div>
- </div>
- <!-- Risk Reports -->
- <div v-if="activeTab === 'reports'" class="content-section">
- <div class="section-header">
- <h4><i class="fas fa-file-alt"></i> 风险报告</h4>
- <button class="btn btn-secondary btn-sm" @click="generateRiskReport">
- <i class="fas fa-plus"></i> 生成报告
- </button>
- </div>
- <div class="reports-list">
- <div v-for="report in riskReports" :key="report.id"
- class="report-item" @click="openReport(report)">
- <div class="report-header">
- <span class="report-title">{{ report.title }}</span>
- <span class="report-date">{{ report.date }}</span>
- </div>
- <div class="report-content">
- <div class="report-summary">{{ report.summary }}</div>
- <div class="report-stats">
- <span><i class="fas fa-exclamation-triangle"></i> 风险数: {{ report.riskCount }}</span>
- <span><i class="fas fa-chart-bar"></i> 覆盖范围: {{ report.coverage }}</span>
- <span><i class="fas fa-shield-alt"></i> 措施数: {{ report.measures }}</span>
- </div>
- </div>
- <div class="report-actions">
- <button class="btn btn-sm btn-outline" @click.stop="viewReport(report)">
- <i class="fas fa-eye"></i> 查看
- </button>
- <button class="btn btn-sm btn-outline" @click.stop="downloadReport(report)">
- <i class="fas fa-download"></i> 下载
- </button>
- </div>
- </div>
- </div>
- </div>
- <!-- Settings Modal -->
- <div v-if="showSettingsModal" class="modal-overlay" @click.self="showSettingsModal = false">
- <div class="modal settings-modal">
- <div class="modal-header">
- <div class="modal-title"><i class="fas fa-cog"></i> 风控设置</div>
- <button class="modal-close" @click="showSettingsModal = false"><i class="fas fa-times"></i></button>
- </div>
- <div class="modal-body">
- <div class="settings-tabs">
- <div v-for="tab in settingsTabs" :key="tab.key"
- class="settings-tab" :class="{ active: activeSettingsTab === tab.key }"
- @click="activeSettingsTab = tab.key">
- <i :class="tab.icon"></i> {{ tab.label }}
- </div>
- </div>
- <!-- Credit Settings -->
- <div v-if="activeSettingsTab === 'credit'" class="settings-panel">
- <div class="panel-section">
- <h5>客户信用监控</h5>
- <div class="setting-item">
- <div class="setting-info">
- <div class="setting-name">启用自动信用评估</div>
- <div class="setting-desc">系统自动分析客户付款历史、财务状况</div>
- </div>
- <div class="setting-control">
- <label class="toggle-switch">
- <input type="checkbox" v-model="settings.credit.autoEvaluate">
- <span class="toggle-slider"></span>
- </label>
- </div>
- </div>
- <div class="setting-item">
- <div class="setting-info">
- <div class="setting-name">信用下调预警</div>
- <div class="setting-desc">客户信用评级下调时立即通知</div>
- </div>
- <div class="setting-control">
- <label class="toggle-switch">
- <input type="checkbox" v-model="settings.credit.downgradeAlert">
- <span class="toggle-slider"></span>
- </label>
- </div>
- </div>
- <div class="setting-item">
- <div class="setting-info">
- <div class="setting-name">付款逾期预警天数</div>
- <div class="setting-desc">客户付款逾期超过设定天数时触发预警</div>
- </div>
- <div class="setting-control">
- <select class="form-select" v-model="settings.credit.overdueDays">
- <option :value="7">7天</option>
- <option :value="15">15天</option>
- <option :value="30">30天</option>
- <option :value="60">60天</option>
- </select>
- </div>
- </div>
- <div class="setting-item">
- <div class="setting-info">
- <div class="setting-name">自动限制订单</div>
- <div class="setting-desc">高风险客户自动限制新订单发货</div>
- </div>
- <div class="setting-control">
- <label class="toggle-switch">
- <input type="checkbox" v-model="settings.credit.autoRestrict">
- <span class="toggle-slider"></span>
- </label>
- </div>
- </div>
- </div>
- </div>
- <!-- Policy Settings -->
- <div v-if="activeSettingsTab === 'policy'" class="settings-panel">
- <div class="panel-section">
- <h5>贸易政策监控</h5>
- <div class="setting-item">
- <div class="setting-info">
- <div class="setting-name">关税政策变动监控</div>
- <div class="setting-desc">监控主要出口市场的关税政策变化</div>
- </div>
- <div class="setting-control">
- <label class="toggle-switch">
- <input type="checkbox" v-model="settings.policy.tariffMonitor">
- <span class="toggle-slider"></span>
- </label>
- </div>
- </div>
- <div class="setting-item">
- <div class="setting-info">
- <div class="setting-name">产品认证要求监控</div>
- <div class="setting-desc">监控目标市场的产品认证和合规要求</div>
- </div>
- <div class="setting-control">
- <label class="toggle-switch">
- <input type="checkbox" v-model="settings.policy.certificationMonitor">
- <span class="toggle-slider"></span>
- </label>
- </div>
- </div>
- <div class="setting-item">
- <div class="setting-info">
- <div class="setting-name">提前预警天数</div>
- <div class="setting-desc">政策生效前多少天发送预警</div>
- </div>
- <div class="setting-control">
- <select class="form-select" v-model="settings.policy.advanceDays">
- <option :value="30">30天</option>
- <option :value="60">60天</option>
- <option :value="90">90天</option>
- <option :value="180">180天</option>
- </select>
- </div>
- </div>
- </div>
- </div>
- <!-- Competitor Settings -->
- <div v-if="activeSettingsTab === 'competitor'" class="settings-panel">
- <div class="panel-section">
- <h5>竞品动态监控</h5>
- <div class="setting-item">
- <div class="setting-info">
- <div class="setting-name">价格变动监控</div>
- <div class="setting-desc">监控竞品价格变化,超过阈值时预警</div>
- </div>
- <div class="setting-control">
- <label class="toggle-switch">
- <input type="checkbox" v-model="settings.competitor.priceMonitor">
- <span class="toggle-slider"></span>
- </label>
- </div>
- </div>
- <div class="setting-item">
- <div class="setting-info">
- <div class="setting-name">价格变动阈值</div>
- <div class="setting-desc">价格变动超过此百分比时触发预警</div>
- </div>
- <div class="setting-control">
- <select class="form-select" v-model="settings.competitor.priceThreshold">
- <option :value="5">5%</option>
- <option :value="10">10%</option>
- <option :value="15">15%</option>
- <option :value="20">20%</option>
- </select>
- </div>
- </div>
- <div class="setting-item">
- <div class="setting-info">
- <div class="setting-name">新产品发布监控</div>
- <div class="setting-desc">监控竞品新产品发布动态</div>
- </div>
- <div class="setting-control">
- <label class="toggle-switch">
- <input type="checkbox" v-model="settings.competitor.productMonitor">
- <span class="toggle-slider"></span>
- </label>
- </div>
- </div>
- </div>
- </div>
- <!-- Notification Settings -->
- <div v-if="activeSettingsTab === 'notification'" class="settings-panel">
- <div class="panel-section">
- <h5>预警通知方式</h5>
- <div class="setting-item">
- <div class="setting-info">
- <div class="setting-name">系统消息</div>
- <div class="setting-desc">在系统内显示风险预警通知</div>
- </div>
- <div class="setting-control">
- <label class="toggle-switch">
- <input type="checkbox" v-model="settings.notification.inApp">
- <span class="toggle-slider"></span>
- </label>
- </div>
- </div>
- <div class="setting-item">
- <div class="setting-info">
- <div class="setting-name">邮件通知</div>
- <div class="setting-desc">高风险事件发生时发送邮件通知</div>
- </div>
- <div class="setting-control">
- <label class="toggle-switch">
- <input type="checkbox" v-model="settings.notification.email">
- <span class="toggle-slider"></span>
- </label>
- </div>
- </div>
- <div class="setting-item">
- <div class="setting-info">
- <div class="setting-name">微信通知</div>
- <div class="setting-desc">紧急风险通过微信实时通知</div>
- </div>
- <div class="setting-control">
- <label class="toggle-switch">
- <input type="checkbox" v-model="settings.notification.wechat">
- <span class="toggle-slider"></span>
- </label>
- </div>
- </div>
- <div class="setting-item">
- <div class="setting-info">
- <div class="setting-name">通知级别</div>
- <div class="setting-desc">选择接收通知的风险级别</div>
- </div>
- <div class="setting-control">
- <select class="form-select" v-model="settings.notification.level">
- <option value="all">全部</option>
- <option value="critical">仅高风险</option>
- <option value="critical-medium">高风险和中风险</option>
- </select>
- </div>
- </div>
- </div>
- </div>
- </div>
- <div class="modal-footer">
- <button class="btn btn-secondary" @click="showSettingsModal = false">取消</button>
- <button class="btn btn-primary" @click="saveSettings">
- <i class="fas fa-save"></i> 保存设置
- </button>
- </div>
- </div>
- </div>
- <!-- Risk Assessment Modal -->
- <div v-if="showRiskAssessmentModal" class="modal-overlay" @click.self="showRiskAssessmentModal = false">
- <div class="modal assessment-modal">
- <div class="modal-header">
- <div class="modal-title"><i class="fas fa-chart-bar"></i> 风险评估</div>
- <button class="modal-close" @click="showRiskAssessmentModal = false"><i class="fas fa-times"></i></button>
- </div>
- <div class="modal-body">
- <div class="assessment-grid">
- <div class="assessment-item">
- <div class="assessment-label">总体风险指数</div>
- <div class="assessment-value">42/100</div>
- <div class="assessment-status">中等风险</div>
- </div>
- <div class="assessment-item">
- <div class="assessment-label">信用风险指数</div>
- <div class="assessment-value">35/100</div>
- <div class="assessment-status">低风险</div>
- </div>
- <div class="assessment-item">
- <div class="assessment-label">政策风险指数</div>
- <div class="assessment-value">58/100</div>
- <div class="assessment-status">中等风险</div>
- </div>
- <div class="assessment-item">
- <div class="assessment-label">竞争风险指数</div>
- <div class="assessment-value">45/100</div>
- <div class="assessment-status">中等风险</div>
- </div>
- </div>
- <div class="assessment-details">
- <h5>风险评估详情</h5>
- <div class="detail-item">
- <span class="detail-label">评估时间:</span>
- <span class="detail-value">2026-03-23 15:18</span>
- </div>
- <div class="detail-item">
- <span class="detail-label">覆盖范围:</span>
- <span class="detail-value">全球12个地区,156个客户,89个商机</span>
- </div>
- <div class="detail-item">
- <span class="detail-label">主要风险:</span>
- <span class="detail-value">贸易政策变化、竞品价格压力、客户信用下降</span>
- </div>
- </div>
- </div>
- <div class="modal-footer">
- <button class="btn btn-secondary" @click="showRiskAssessmentModal = false">关闭</button>
- <button class="btn btn-primary" @click="exportAssessment">
- <i class="fas fa-download"></i> 导出评估
- </button>
- </div>
- </div>
- </div>
- <!-- Risk Map Modal -->
- <div v-if="showRiskMapModal" class="modal-overlay" @click.self="showRiskMapModal = false">
- <div class="modal map-modal">
- <div class="modal-header">
- <div class="modal-title"><i class="fas fa-map"></i> 风险地图</div>
- <button class="modal-close" @click="showRiskMapModal = false"><i class="fas fa-times"></i></button>
- </div>
- <div class="modal-body">
- <div class="risk-map">
- <div class="map-placeholder">
- <i class="fas fa-globe"></i>
- <p>全球风险分布地图</p>
- <p class="map-desc">显示各地区风险热度分布</p>
- </div>
- </div>
- <div class="map-legend">
- <div class="legend-item">
- <span class="legend-color critical"></span>
- <span>高风险地区</span>
- </div>
- <div class="legend-item">
- <span class="legend-color medium"></span>
- <span>中风险地区</span>
- </div>
- <div class="legend-item">
- <span class="legend-color low"></span>
- <span>低风险地区</span>
- </div>
- <div class="legend-item">
- <span class="legend-color safe"></span>
- <span>安全地区</span>
- </div>
- </div>
- <div class="region-stats">
- <h5>地区风险统计</h5>
- <div class="stat-row">
- <div class="stat-item">
- <span class="stat-label">中东</span>
- <span class="stat-value critical">高风险</span>
- </div>
- <div class="stat-item">
- <span class="stat-label">欧洲</span>
- <span class="stat-value medium">中风险</span>
- </div>
- <div class="stat-item">
- <span class="stat-label">亚洲</span>
- <span class="stat-value low">低风险</span>
- </div>
- <div class="stat-item">
- <span class="stat-label">美洲</span>
- <span class="stat-value safe">安全</span>
- </div>
- </div>
- </div>
- </div>
- <div class="modal-footer">
- <button class="btn btn-secondary" @click="showRiskMapModal = false">关闭</button>
- </div>
- </div>
- </div>
- <!-- Risk Detail Modal -->
- <div v-if="showRiskDetailModal" class="modal-overlay" @click.self="showRiskDetailModal = false">
- <div class="modal detail-modal">
- <div class="modal-header">
- <div class="modal-title">
- <i class="fas fa-exclamation-circle"></i> {{ selectedRiskDetail?.title }}
- </div>
- <button class="modal-close" @click="showRiskDetailModal = false"><i class="fas fa-times"></i></button>
- </div>
- <div class="modal-body" v-if="selectedRiskDetail">
- <!-- Credit Risk Detail -->
- <div v-if="selectedRiskDetail.type === 'credit'" class="detail-content">
- <div class="detail-section">
- <h5>基本信息</h5>
- <div class="info-grid">
- <div class="info-item">
- <span class="label">客户名称</span>
- <span class="value">{{ selectedRiskDetail.details.customer }}</span>
- </div>
- <div class="info-item">
- <span class="label">风险等级</span>
- <span class="value" :class="selectedRiskDetail.details.level">{{ getLevelLabel(selectedRiskDetail.details.level) }}</span>
- </div>
- <div class="info-item">
- <span class="label">原信用评级</span>
- <span class="value">{{ selectedRiskDetail.details.oldRating }}</span>
- </div>
- <div class="info-item">
- <span class="label">当前评级</span>
- <span class="value danger">{{ selectedRiskDetail.details.newRating }}</span>
- </div>
- <div class="info-item">
- <span class="label">年采购额</span>
- <span class="value">{{ selectedRiskDetail.details.annualPurchase }}</span>
- </div>
- <div class="info-item">
- <span class="label">逾期次数</span>
- <span class="value warning">{{ selectedRiskDetail.details.overdueCount }}次</span>
- </div>
- </div>
- </div>
- <div class="detail-section">
- <h5>风险原因</h5>
- <p class="reason-text">{{ selectedRiskDetail.details.reason }}</p>
- </div>
- <div class="detail-section" v-if="selectedRiskDetail.details.affectedDeals">
- <h5>影响商机 ({{ selectedRiskDetail.details.affectedDeals }})</h5>
- <div class="affected-list">
- <div class="affected-item">
- <span class="deal-name">NVMe SSD订单</span>
- <span class="deal-amount">¥128K</span>
- <span class="deal-stage negotiation">谈判中</span>
- </div>
- <div class="affected-item">
- <span class="deal-name">SATA SSD订单</span>
- <span class="deal-amount">¥96K</span>
- <span class="deal-stage quotation">报价中</span>
- </div>
- </div>
- </div>
- <div class="detail-section">
- <h5>AI建议</h5>
- <div class="suggestion-box">
- <i class="fas fa-lightbulb"></i>
- <p>{{ selectedRiskDetail.details.suggestion }}</p>
- </div>
- </div>
- <div class="detail-section">
- <h5>建议处理措施</h5>
- <div class="actions-list">
- <div v-for="(action, index) in selectedRiskDetail.details.actions" :key="index" class="action-item">
- <i class="fas fa-check-circle"></i>
- <span>{{ action }}</span>
- </div>
- </div>
- </div>
- </div>
- <!-- Policy Risk Detail -->
- <div v-if="selectedRiskDetail.type === 'policy'" class="detail-content">
- <div class="detail-section">
- <h5>基本信息</h5>
- <div class="info-grid">
- <div class="info-item">
- <span class="label">地区</span>
- <span class="value">{{ selectedRiskDetail.details.region }}</span>
- </div>
- <div class="info-item">
- <span class="label">政策标题</span>
- <span class="value">{{ selectedRiskDetail.details.title }}</span>
- </div>
- <div class="info-item">
- <span class="label">生效日期</span>
- <span class="value">{{ selectedRiskDetail.details.effectiveDate }}</span>
- </div>
- <div class="info-item">
- <span class="label">影响客户</span>
- <span class="value">{{ selectedRiskDetail.details.affectedCustomers }}个</span>
- </div>
- <div class="info-item">
- <span class="label">影响产品</span>
- <span class="value">{{ selectedRiskDetail.details.affectedProducts }}款</span>
- </div>
- <div class="info-item">
- <span class="label">影响程度</span>
- <span class="value danger">{{ selectedRiskDetail.details.impact }}</span>
- </div>
- </div>
- </div>
- <div class="detail-section">
- <h5>政策描述</h5>
- <p class="reason-text">{{ selectedRiskDetail.details.description }}</p>
- </div>
- <div class="detail-section">
- <h5>时间线</h5>
- <div class="timeline-list">
- <div v-for="(item, index) in selectedRiskDetail.details.timeline" :key="index" class="timeline-item">
- <span class="timeline-date">{{ item.date }}</span>
- <span class="timeline-event">{{ item.event }}</span>
- </div>
- </div>
- </div>
- <div class="detail-section">
- <h5>应对建议</h5>
- <div class="suggestion-box">
- <i class="fas fa-lightbulb"></i>
- <p>{{ selectedRiskDetail.details.recommendation }}</p>
- </div>
- </div>
- </div>
- <!-- Competitor Risk Detail -->
- <div v-if="selectedRiskDetail.type === 'competitor'" class="detail-content">
- <div class="detail-section">
- <h5>基本信息</h5>
- <div class="info-grid">
- <div class="info-item">
- <span class="label">竞品名称</span>
- <span class="value">{{ selectedRiskDetail.details.competitor }}</span>
- </div>
- <div class="info-item">
- <span class="label">动作描述</span>
- <span class="value">{{ selectedRiskDetail.details.action }}</span>
- </div>
- <div class="info-item">
- <span class="label">发生时间</span>
- <span class="value">{{ selectedRiskDetail.details.time }}</span>
- </div>
- <div class="info-item">
- <span class="label">影响程度</span>
- <span class="value warning">{{ selectedRiskDetail.details.impact }}</span>
- </div>
- <div class="info-item">
- <span class="label">市场份额变化</span>
- <span class="value danger">{{ selectedRiskDetail.details.marketShare }}</span>
- </div>
- </div>
- </div>
- <div class="detail-section">
- <h5>风险分析</h5>
- <p class="reason-text">{{ selectedRiskDetail.details.analysis }}</p>
- </div>
- <div class="detail-section">
- <h5>应对建议</h5>
- <div class="suggestion-box">
- <i class="fas fa-lightbulb"></i>
- <p style="white-space: pre-line;">{{ selectedRiskDetail.details.recommendation }}</p>
- </div>
- </div>
- <div class="detail-section">
- <h5>我们的应对</h5>
- <div class="response-box">
- <span class="status-badge">{{ selectedRiskDetail.details.ourResponse }}</span>
- </div>
- </div>
- </div>
- <!-- Operational Risk Detail -->
- <div v-if="selectedRiskDetail.type === 'operational'" class="detail-content">
- <div class="detail-section">
- <h5>基本信息</h5>
- <div class="info-grid">
- <div class="info-item">
- <span class="label">风险标题</span>
- <span class="value">{{ selectedRiskDetail.details.title }}</span>
- </div>
- <div class="info-item">
- <span class="label">严重程度</span>
- <span class="value warning">{{ selectedRiskDetail.details.severity }}</span>
- </div>
- <div class="info-item">
- <span class="label">发生概率</span>
- <span class="value danger">{{ selectedRiskDetail.details.probability }}</span>
- </div>
- <div class="info-item">
- <span class="label">影响范围</span>
- <span class="value">{{ selectedRiskDetail.details.affectedAreas }}</span>
- </div>
- <div class="info-item">
- <span class="label">影响程度</span>
- <span class="value">{{ selectedRiskDetail.details.impact }}</span>
- </div>
- <div class="info-item">
- <span class="label">处理状态</span>
- <span class="value">{{ selectedRiskDetail.details.status }}</span>
- </div>
- </div>
- </div>
- <div class="detail-section">
- <h5>风险描述</h5>
- <p class="reason-text">{{ selectedRiskDetail.details.description }}</p>
- </div>
- <div class="detail-section">
- <h5>根本原因</h5>
- <p class="reason-text">{{ selectedRiskDetail.details.rootCause }}</p>
- </div>
- <div class="detail-section">
- <h5>缓解措施</h5>
- <div class="suggestion-box">
- <i class="fas fa-shield-alt"></i>
- <p style="white-space: pre-line;">{{ selectedRiskDetail.details.mitigation }}</p>
- </div>
- </div>
- </div>
- </div>
- <div class="modal-footer">
- <button class="btn btn-secondary" @click="showRiskDetailModal = false">关闭</button>
- <button class="btn btn-primary" @click="handleRisk(selectedRiskDetail.data)">
- <i class="fas fa-wrench"></i> 处理风险
- </button>
- </div>
- </div>
- </div>
- <transition name="toast">
- <div v-if="showToast" class="toast" :class="toastType">
- <i :class="toastIcon"></i> {{ toastMessage }}
- </div>
- </transition>
- </div>
- </template>
- <script setup>
- import { ref, computed } from 'vue'
- const activeTab = ref('overview')
- const activeSettingsTab = ref('credit')
- const searchQuery = ref('')
- const filterLevel = ref('')
- const sortBy = ref('latest')
- const showSettingsModal = ref(false)
- const showRiskAssessmentModal = ref(false)
- const showRiskMapModal = ref(false)
- const showMitigationModal = ref(false)
- const showToast = ref(false)
- const toastMessage = ref('')
- const toastType = ref('success')
- const toastIcon = ref('fas fa-check-circle')
- // Tabs
- const tabs = [
- { key: 'overview', label: '风险概览', count: 0 },
- { key: 'timeline', label: '风险时间线', count: 12 },
- { key: 'mitigation', label: '缓解措施', count: 8 },
- { key: 'reports', label: '风险报告', count: 5 }
- ]
- // KPI Stats
- const riskStats = ref({
- critical: 3,
- medium: 5,
- low: 12,
- resolved: 28
- })
- const coverageRate = ref(98)
- // Settings Tabs
- const settingsTabs = [
- { key: 'credit', label: '信用监控', icon: 'fas fa-credit-card' },
- { key: 'policy', label: '政策监控', icon: 'fas fa-gavel' },
- { key: 'competitor', label: '竞品监控', icon: 'fas fa-chess' },
- { key: 'notification', label: '通知设置', icon: 'fas fa-bell' }
- ]
- // Settings
- const settings = ref({
- credit: {
- autoEvaluate: true,
- downgradeAlert: true,
- overdueDays: 15,
- autoRestrict: true
- },
- policy: {
- tariffMonitor: true,
- certificationMonitor: true,
- advanceDays: 90
- },
- competitor: {
- priceMonitor: true,
- priceThreshold: 10,
- productMonitor: true
- },
- notification: {
- inApp: true,
- email: true,
- wechat: true,
- level: 'critical'
- }
- })
- // Credit Risks
- const creditRisks = ref([
- {
- id: 1,
- customer: 'ABC Corp',
- level: 'critical',
- reason: '信用评级 A→B,建议提高预付款比例',
- detail: {
- oldRating: 'A',
- newRating: 'B',
- annualPurchase: '$2.5M',
- overdueCount: 3
- },
- affectedDeals: 2
- },
- {
- id: 2,
- customer: 'XYZ Trading',
- level: 'medium',
- reason: '付款周期延长至90天,需关注',
- detail: {
- oldRating: 'A-',
- newRating: 'B+',
- annualPurchase: '$800K',
- overdueCount: 1
- },
- affectedDeals: 1
- },
- {
- id: 3,
- customer: 'Global Tech',
- level: 'low',
- reason: '近期订单量下降50%',
- detail: {
- oldRating: 'A',
- newRating: 'B+',
- annualPurchase: '$1.2M',
- overdueCount: 0
- },
- affectedDeals: 0
- }
- ])
- // Policy Risks
- const policyRisks = ref([
- {
- id: 1,
- region: '🇪🇺 欧盟',
- title: '能效认证新规',
- description: '欧盟能效标签新规将于2026年7月生效,涉及15款SSD产品',
- effectiveDate: '2026-07-01',
- affectedCustomers: 23,
- affectedProducts: 15
- },
- {
- id: 2,
- region: '🇺🇸 美国',
- title: '关税政策调整',
- description: '部分产品税率上调5%,已通知相关客户调整报价',
- effectiveDate: '2026-04-01',
- affectedCustomers: 12,
- affectedProducts: 8
- }
- ])
- // Competitor Risks
- const competitorRisks = ref([
- {
- id: 1,
- competitor: '三星',
- action: '发布新一代990 Pro SSD,价格下调15%',
- time: '2小时前',
- impact: '高',
- marketShare: '-3%'
- },
- {
- id: 2,
- competitor: '西部数据',
- action: '企业级SSD产品线更新',
- time: '昨天',
- impact: '中',
- marketShare: '-1%'
- },
- {
- id: 3,
- competitor: '海力士',
- action: '推出PCIe 5.0 SSD新品',
- time: '3天前',
- impact: '中',
- marketShare: '-2%'
- }
- ])
- // Operational Risks
- const operationalRisks = ref([
- {
- id: 1,
- icon: 'fas fa-truck',
- title: '供应链风险',
- description: '芯片供应紧张,可能影响交期',
- level: 'medium',
- affectedAreas: '全球',
- severity: '中等'
- },
- {
- id: 2,
- icon: 'fas fa-industry',
- title: '生产能力风险',
- description: '产能利用率达到95%,缺乏应急能力',
- level: 'medium',
- affectedAreas: '国内',
- severity: '中等'
- },
- {
- id: 3,
- icon: 'fas fa-users',
- title: '人力资源风险',
- description: '关键岗位人员流动率上升',
- level: 'low',
- affectedAreas: '技术部门',
- severity: '低'
- },
- {
- id: 4,
- icon: 'fas fa-database',
- title: '数据安全风险',
- description: '客户数据保护需要加强',
- level: 'critical',
- affectedAreas: '全公司',
- severity: '高'
- }
- ])
- // Risk Heatmap
- const riskHeatmap = ref([
- { level: 'critical', value: 3, label: '高风险' },
- { level: 'medium', value: 5, label: '中风险' },
- { level: 'low', value: 12, label: '低风险' },
- { level: 'safe', value: 28, label: '安全' },
- { level: 'critical', value: 2, label: '高风险' },
- { level: 'medium', value: 4, label: '中风险' },
- { level: 'low', value: 8, label: '低风险' },
- { level: 'safe', value: 15, label: '安全' }
- ])
- // Risk Timeline
- const riskTimeline = ref([
- { id: 1, title: 'ABC Corp信用下调', description: '客户信用评级从A下调至B', date: '2026-03-23', level: 'critical', status: '处理中' },
- { id: 2, title: '欧盟能效新规', description: '欧盟能效标签新规即将生效', date: '2026-03-22', level: 'medium', status: '监控中' },
- { id: 3, title: '三星价格下调', description: '三星SSD产品价格下调15%', date: '2026-03-21', level: 'medium', status: '已评估' },
- { id: 4, title: '供应链风险预警', description: '芯片供应紧张,可能影响交期', date: '2026-03-20', level: 'medium', status: '已处理' }
- ])
- // Mitigation Measures
- const mitigationMeasures = ref([
- { id: 1, title: '提高ABC Corp预付款比例', description: '将预付款比例从30%提高至50%', status: 'in-progress', startDate: '2026-03-23', targetDate: '2026-04-06', progress: 60 },
- { id: 2, title: '欧盟能效认证准备', description: '准备15款产品的欧盟能效认证', status: 'in-progress', startDate: '2026-03-20', targetDate: '2026-06-30', progress: 35 },
- { id: 3, title: '竞品价格应对方案', description: '制定竞品价格应对和市场策略', status: 'completed', startDate: '2026-03-21', targetDate: '2026-03-23', progress: 100 },
- { id: 4, title: '供应链多元化', description: '开发备用芯片供应商', status: 'in-progress', startDate: '2026-03-15', targetDate: '2026-05-31', progress: 45 }
- ])
- // Risk Reports
- const riskReports = ref([
- { id: 1, title: '2026年Q1风险评估报告', date: '2026-03-23', summary: '全面评估公司风险状况,识别关键风险', riskCount: 20, coverage: '全球12地区', measures: 8 },
- { id: 2, title: '客户信用风险报告', date: '2026-03-20', summary: '分析客户信用状况,提出风险预警', riskCount: 8, coverage: '156个客户', measures: 5 },
- { id: 3, title: '贸易政策风险报告', date: '2026-03-15', summary: '监控全球贸易政策变化', riskCount: 6, coverage: '12个国家', measures: 4 },
- { id: 4, title: '竞争风险分析报告', date: '2026-03-10', summary: '分析竞品动态和市场压力', riskCount: 5, coverage: '5个竞品', measures: 3 },
- { id: 5, title: '运营风险评估报告', date: '2026-03-05', summary: '评估内部运营风险', riskCount: 4, coverage: '全公司', measures: 6 }
- ])
- // Methods
- const getLevelLabel = (level) => {
- const map = { critical: '高风险', medium: '中风险', low: '低风险', safe: '安全' }
- return map[level] || level
- }
- const filterByLevel = (level) => {
- filterLevel.value = level
- toast(`筛选${getLevelLabel(level)}`, 'info')
- }
- const selectedRiskDetail = ref(null)
- const showRiskDetailModal = ref(false)
- const openCreditDetail = (risk) => {
- selectedRiskDetail.value = {
- type: 'credit',
- title: `${risk.customer} - 信用风险详情`,
- data: risk,
- details: {
- riskType: '客户信用风险',
- customer: risk.customer,
- level: risk.level,
- reason: risk.reason,
- oldRating: risk.detail.oldRating,
- newRating: risk.detail.newRating,
- annualPurchase: risk.detail.annualPurchase,
- overdueCount: risk.detail.overdueCount,
- affectedDeals: risk.affectedDeals,
- suggestion: '建议提高预付款比例至50%,并缩短账期至30天。对新订单要求银行保函。',
- actions: ['提高预付款比例', '缩短账期', '要求银行保函', '暂停新订单']
- }
- }
- showRiskDetailModal.value = true
- }
- const openPolicyDetail = (risk) => {
- selectedRiskDetail.value = {
- type: 'policy',
- title: `${risk.title} - 贸易政策风险详情`,
- data: risk,
- details: {
- riskType: '贸易政策风险',
- region: risk.region,
- title: risk.title,
- description: risk.description,
- effectiveDate: risk.effectiveDate,
- affectedCustomers: risk.affectedCustomers,
- affectedProducts: risk.affectedProducts,
- impact: '高',
- recommendation: '立即启动产品认证流程,预计需要3-4个月完成。建议提前通知客户,准备应对方案。',
- timeline: [
- { date: '2026-03-23', event: '政策发布' },
- { date: '2026-04-01', event: '开始咨询期' },
- { date: '2026-07-01', event: '政策生效' }
- ]
- }
- }
- showRiskDetailModal.value = true
- }
- const openCompetitorDetail = (risk) => {
- selectedRiskDetail.value = {
- type: 'competitor',
- title: `${risk.competitor} - 竞品动态风险详情`,
- data: risk,
- details: {
- riskType: '竞品动态风险',
- competitor: risk.competitor,
- action: risk.action,
- time: risk.time,
- impact: risk.impact,
- marketShare: risk.marketShare,
- analysis: '竞品价格下调15%,可能对我们的市场份额造成3-5%的影响。建议立即评估我们的定价策略。',
- recommendation: '1. 分析竞品新产品的技术特点\n2. 评估我们的竞争优势\n3. 制定应对策略(价格、功能、服务)\n4. 加强客户沟通和关系维护',
- ourResponse: '待制定'
- }
- }
- showRiskDetailModal.value = true
- }
- const openOperationalDetail = (risk) => {
- selectedRiskDetail.value = {
- type: 'operational',
- title: `${risk.title} - 运营风险详情`,
- data: risk,
- details: {
- riskType: '运营风险',
- title: risk.title,
- description: risk.description,
- affectedAreas: risk.affectedAreas,
- severity: risk.severity,
- probability: '高',
- impact: '中等',
- rootCause: '全球芯片短缺持续,主要供应商产能受限',
- mitigation: '1. 开发备用供应商\n2. 增加库存储备\n3. 优化生产计划\n4. 与客户沟通交期调整',
- status: '进行中'
- }
- }
- showRiskDetailModal.value = true
- }
- const showAllCreditRisks = () => {
- toast('查看全部信用风险', 'info')
- }
- const showAllPolicyRisks = () => {
- toast('查看全部政策风险', 'info')
- }
- const showAllCompetitorRisks = () => {
- toast('查看全部竞品风险', 'info')
- }
- const showAllOperationalRisks = () => {
- toast('查看全部运营风险', 'info')
- }
- const handleRisk = (risk) => {
- toast('风险处理已启动', 'success')
- }
- const refreshTimeline = () => {
- toast('正在刷新时间线...', 'info')
- setTimeout(() => {
- toast('时间线已更新', 'success')
- }, 1500)
- }
- const editMeasure = (measure) => {
- toast(`编辑措施: ${measure.title}`, 'info')
- }
- const viewMeasureDetail = (measure) => {
- toast(`查看措施详情: ${measure.title}`, 'info')
- }
- const generateRiskReport = () => {
- toast('正在生成风险报告...', 'info')
- setTimeout(() => {
- toast('风险报告已生成', 'success')
- }, 2000)
- }
- const openReport = (report) => {
- toast(`查看${report.title}`, 'info')
- }
- const viewReport = (report) => {
- toast(`查看${report.title}`, 'info')
- }
- const downloadReport = (report) => {
- toast(`${report.title}下载中...`, 'success')
- }
- const saveSettings = () => {
- showSettingsModal.value = false
- toast('风控设置已保存', 'success')
- }
- const exportRiskReport = () => {
- toast('风险报告导出中...', 'success')
- }
- const exportAssessment = () => {
- toast('风险评估导出中...', 'success')
- }
- const toast = (message, type = 'success') => {
- toastMessage.value = message
- toastType.value = type
- toastIcon.value = type === 'success' ? 'fas fa-check-circle' : 'fas fa-info-circle'
- showToast.value = true
- setTimeout(() => { showToast.value = false }, 3000)
- }
- </script>
- <style scoped>
- .risk-page { flex: 1; display: flex; flex-direction: column; padding: 24px; overflow-y: auto; background: var(--bg-dark); }
- .risk-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 24px; }
- .header-left { display: flex; flex-direction: column; gap: 4px; }
- .data-title { font-size: 20px; font-weight: 600; }
- .header-desc { font-size: 13px; color: var(--text-secondary); }
- .header-actions { display: flex; gap: 8px; }
- /* KPI Row */
- .kpi-row { display: grid; grid-template-columns: repeat(5, 1fr); gap: 12px; margin-bottom: 24px; }
- .kpi-card { background: var(--bg-card); border: 1px solid var(--border); border-radius: 12px; padding: 16px; cursor: pointer; transition: all 0.2s; }
- .kpi-card:hover { border-color: var(--primary); transform: translateY(-2px); }
- .kpi-card.critical { border-left: 4px solid #EF4444; }
- .kpi-card.medium { border-left: 4px solid #F59E0B; }
- .kpi-card.low { border-left: 4px solid #6366F1; }
- .kpi-card.resolved { border-left: 4px solid #10B981; }
- .kpi-card.coverage { border-left: 4px solid #8B5CF6; }
- .kpi-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
- .kpi-label { font-size: 12px; color: var(--text-secondary); }
- .kpi-trend { font-size: 11px; padding: 2px 8px; border-radius: 10px; display: flex; align-items: center; gap: 4px; }
- .kpi-trend.danger { background: rgba(239, 68, 68, 0.2); color: #EF4444; }
- .kpi-trend.warning { background: rgba(245, 158, 11, 0.2); color: #F59E0B; }
- .kpi-trend.info { background: rgba(99, 102, 241, 0.2); color: #6366F1; }
- .kpi-trend.success { background: rgba(16, 185, 129, 0.2); color: #10B981; }
- .kpi-value { font-size: 24px; font-weight: 700; margin-bottom: 4px; }
- .kpi-meta { font-size: 11px; color: var(--text-muted); }
- /* Tabs */
- .tabs-section { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding-bottom: 12px; border-bottom: 1px solid var(--border); }
- .tabs { display: flex; gap: 4px; }
- .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; }
- .tab-item:hover { background: var(--bg-hover); }
- .tab-item.active { background: var(--bg-hover); color: var(--text-primary); font-weight: 600; }
- .tab-count { font-size: 11px; padding: 2px 6px; background: var(--bg-dark); border-radius: 10px; }
- .tab-item.active .tab-count { background: var(--primary); color: white; }
- .filter-controls { display: flex; gap: 8px; }
- .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; }
- .filter-select { padding: 8px 12px; background: var(--bg-hover); border: 1px solid var(--border); border-radius: 8px; color: var(--text-primary); font-size: 13px; }
- /* Content Section */
- .content-section { flex: 1; overflow-y: auto; }
- /* Overview Grid */
- .overview-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-bottom: 24px; }
- .risk-card-section { background: var(--bg-card); border: 1px solid var(--border); border-radius: 12px; padding: 20px; }
- .risk-card-section.full-width { grid-column: span 3; }
- .section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
- .section-header h4 { font-size: 14px; font-weight: 600; margin: 0; display: flex; align-items: center; gap: 8px; }
- .section-header h4 i { color: var(--primary); }
- .btn-link { background: none; border: none; color: var(--primary); font-size: 12px; cursor: pointer; }
- /* Risk List */
- .risk-list { display: flex; flex-direction: column; gap: 10px; }
- .risk-item { padding: 12px; background: var(--bg-hover); border-radius: 10px; border-left: 4px solid var(--border); cursor: pointer; transition: all 0.2s; }
- .risk-item:hover { background: rgba(99, 102, 241, 0.1); }
- .risk-item.critical { border-left-color: #EF4444; }
- .risk-item.medium { border-left-color: #F59E0B; }
- .risk-item.low { border-left-color: #6366F1; }
- .risk-item.policy { border-left-color: #8B5CF6; }
- .risk-item.competitor { border-left-color: #EF4444; }
- .risk-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
- .risk-title { font-size: 13px; font-weight: 600; }
- .risk-level { font-size: 10px; padding: 2px 8px; border-radius: 10px; }
- .risk-level.critical { background: rgba(239, 68, 68, 0.2); color: #EF4444; }
- .risk-level.medium { background: rgba(245, 158, 11, 0.2); color: #F59E0B; }
- .risk-level.low { background: rgba(99, 102, 241, 0.2); color: #6366F1; }
- .risk-date { font-size: 10px; color: var(--text-muted); }
- .risk-time { font-size: 10px; color: var(--text-muted); }
- .risk-content { margin-bottom: 8px; }
- .risk-reason { font-size: 12px; color: var(--text-secondary); margin-bottom: 4px; }
- .risk-meta { display: flex; gap: 12px; font-size: 10px; color: var(--text-muted); }
- .risk-action { }
- /* Operational Grid */
- .operational-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; }
- .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; }
- .operational-item:hover { background: rgba(99, 102, 241, 0.1); }
- .operational-item.critical { border-left-color: #EF4444; }
- .operational-item.medium { border-left-color: #F59E0B; }
- .operational-item.low { border-left-color: #6366F1; }
- .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; }
- .op-content { flex: 1; min-width: 0; }
- .op-title { font-size: 13px; font-weight: 600; margin-bottom: 2px; }
- .op-description { font-size: 11px; color: var(--text-secondary); margin-bottom: 4px; }
- .op-metrics { display: flex; gap: 12px; font-size: 10px; color: var(--text-muted); }
- .op-status { font-size: 11px; padding: 2px 8px; border-radius: 10px; }
- .op-status.critical { background: rgba(239, 68, 68, 0.2); color: #EF4444; }
- .op-status.medium { background: rgba(245, 158, 11, 0.2); color: #F59E0B; }
- .op-status.low { background: rgba(99, 102, 241, 0.2); color: #6366F1; }
- /* Heatmap */
- .heatmap-container { }
- .heatmap-grid { display: grid; grid-template-columns: repeat(8, 1fr); gap: 4px; margin-bottom: 16px; }
- .heatmap-cell { height: 40px; border-radius: 6px; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 600; cursor: pointer; }
- .heatmap-cell.critical { background: rgba(239, 68, 68, 0.3); color: #EF4444; }
- .heatmap-cell.medium { background: rgba(245, 158, 11, 0.3); color: #F59E0B; }
- .heatmap-cell.low { background: rgba(99, 102, 241, 0.3); color: #6366F1; }
- .heatmap-cell.safe { background: rgba(16, 185, 129, 0.3); color: #10B981; }
- .heatmap-legend { display: flex; gap: 16px; font-size: 12px; }
- .legend-item { display: flex; align-items: center; gap: 6px; }
- .legend-item i { font-size: 10px; }
- /* Timeline */
- .timeline { display: flex; flex-direction: column; gap: 16px; }
- .timeline-item { display: flex; gap: 16px; }
- .timeline-dot { width: 12px; height: 12px; border-radius: 50%; flex-shrink: 0; margin-top: 4px; }
- .timeline-dot.critical { background: #EF4444; }
- .timeline-dot.medium { background: #F59E0B; }
- .timeline-dot.low { background: #6366F1; }
- .timeline-content { flex: 1; padding: 12px; background: var(--bg-hover); border-radius: 10px; }
- .timeline-header { display: flex; justify-content: space-between; margin-bottom: 6px; }
- .timeline-title { font-size: 13px; font-weight: 600; }
- .timeline-date { font-size: 11px; color: var(--text-muted); }
- .timeline-description { font-size: 12px; color: var(--text-secondary); margin-bottom: 8px; }
- .timeline-action { }
- .action-status { font-size: 10px; padding: 2px 8px; border-radius: 10px; }
- /* Mitigation */
- .mitigation-list { display: flex; flex-direction: column; gap: 12px; }
- .mitigation-item { background: var(--bg-card); border: 1px solid var(--border); border-radius: 12px; padding: 16px; }
- .mitigation-item.completed { opacity: 0.7; }
- .measure-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
- .measure-title { font-size: 14px; font-weight: 600; }
- .measure-status { font-size: 10px; padding: 2px 8px; border-radius: 10px; }
- .measure-status.in-progress { background: rgba(99, 102, 241, 0.2); color: #6366F1; }
- .measure-status.completed { background: rgba(16, 185, 129, 0.2); color: #10B981; }
- .measure-content { margin-bottom: 10px; }
- .measure-description { font-size: 12px; color: var(--text-secondary); margin-bottom: 6px; }
- .measure-details { display: flex; gap: 16px; font-size: 11px; color: var(--text-muted); }
- .measure-progress { margin-bottom: 10px; }
- .progress-bar { height: 4px; background: var(--bg-hover); border-radius: 2px; overflow: hidden; }
- .progress-fill { height: 100%; background: var(--primary); border-radius: 2px; }
- .measure-actions { display: flex; gap: 8px; }
- /* Reports */
- .reports-list { display: flex; flex-direction: column; gap: 12px; }
- .report-item { background: var(--bg-card); border: 1px solid var(--border); border-radius: 12px; padding: 16px; cursor: pointer; transition: all 0.2s; }
- .report-item:hover { border-color: var(--primary); }
- .report-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
- .report-title { font-size: 14px; font-weight: 600; }
- .report-date { font-size: 11px; color: var(--text-muted); }
- .report-content { margin-bottom: 10px; }
- .report-summary { font-size: 12px; color: var(--text-secondary); margin-bottom: 8px; }
- .report-stats { display: flex; gap: 16px; font-size: 11px; color: var(--text-muted); }
- .report-actions { display: flex; gap: 8px; }
- /* Modal */
- .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; }
- .modal { background: var(--bg-card); border-radius: 16px; width: 100%; max-width: 600px; max-height: 90vh; overflow: hidden; display: flex; flex-direction: column; }
- .settings-modal { max-width: 700px; }
- .assessment-modal { max-width: 500px; }
- .map-modal { max-width: 600px; }
- .detail-modal { max-width: 700px; }
- .modal-header { display: flex; justify-content: space-between; align-items: center; padding: 20px; border-bottom: 1px solid var(--border); }
- .modal-title { font-size: 16px; font-weight: 600; display: flex; align-items: center; gap: 8px; }
- .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; }
- .modal-body { flex: 1; overflow-y: auto; padding: 20px; }
- .modal-footer { display: flex; justify-content: flex-end; gap: 12px; padding: 20px; border-top: 1px solid var(--border); }
- /* Detail Content */
- .detail-content { }
- .detail-section { margin-bottom: 24px; }
- .detail-section h5 { font-size: 14px; font-weight: 600; margin-bottom: 12px; }
- .info-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; }
- .info-item { display: flex; flex-direction: column; gap: 4px; padding: 12px; background: var(--bg-hover); border-radius: 8px; }
- .info-item .label { font-size: 11px; color: var(--text-muted); }
- .info-item .value { font-size: 14px; font-weight: 600; }
- .info-item .value.critical { color: #EF4444; }
- .info-item .value.medium { color: #F59E0B; }
- .info-item .value.low { color: #6366F1; }
- .info-item .value.danger { color: #EF4444; }
- .info-item .value.warning { color: #F59E0B; }
- .reason-text { font-size: 13px; color: var(--text-secondary); line-height: 1.6; padding: 12px; background: var(--bg-hover); border-radius: 8px; }
- .affected-list { display: flex; flex-direction: column; gap: 8px; }
- .affected-item { display: flex; justify-content: space-between; align-items: center; padding: 10px 12px; background: var(--bg-hover); border-radius: 8px; }
- .deal-name { font-size: 13px; font-weight: 500; }
- .deal-amount { font-size: 12px; color: var(--text-secondary); }
- .deal-stage { font-size: 10px; padding: 2px 8px; border-radius: 6px; }
- .deal-stage.negotiation { background: rgba(99, 102, 241, 0.2); color: #6366F1; }
- .deal-stage.quotation { background: rgba(245, 158, 11, 0.2); color: #F59E0B; }
- .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; }
- .suggestion-box i { color: var(--success); font-size: 18px; flex-shrink: 0; }
- .suggestion-box p { font-size: 13px; color: var(--text-secondary); line-height: 1.6; margin: 0; }
- .actions-list { display: flex; flex-direction: column; gap: 8px; }
- .action-item { display: flex; align-items: center; gap: 10px; padding: 10px 12px; background: var(--bg-hover); border-radius: 8px; font-size: 13px; }
- .action-item i { color: var(--success); }
- .timeline-list { display: flex; flex-direction: column; gap: 8px; }
- .timeline-item { display: flex; justify-content: space-between; align-items: center; padding: 10px 12px; background: var(--bg-hover); border-radius: 8px; }
- .timeline-date { font-size: 12px; color: var(--text-muted); }
- .timeline-event { font-size: 13px; font-weight: 500; }
- .response-box { padding: 12px; background: var(--bg-hover); border-radius: 8px; }
- .status-badge { font-size: 12px; padding: 4px 12px; background: rgba(245, 158, 11, 0.2); color: #F59E0B; border-radius: 10px; }
- /* Settings */
- .settings-tabs { display: flex; gap: 8px; margin-bottom: 20px; padding-bottom: 16px; border-bottom: 1px solid var(--border); }
- .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; }
- .settings-tab:hover { background: var(--bg-hover); }
- .settings-tab.active { background: var(--primary); color: white; }
- .settings-panel { }
- .panel-section { margin-bottom: 20px; }
- .panel-section h5 { font-size: 13px; font-weight: 600; margin-bottom: 12px; }
- .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; }
- .setting-info { flex: 1; }
- .setting-name { font-size: 13px; font-weight: 600; margin-bottom: 2px; }
- .setting-desc { font-size: 11px; color: var(--text-secondary); }
- .setting-control { display: flex; align-items: center; flex-shrink: 0; }
- .toggle-switch { position: relative; display: inline-block; width: 44px; height: 24px; }
- .toggle-switch input { opacity: 0; width: 0; height: 0; }
- .toggle-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: var(--border); transition: .3s; border-radius: 24px; }
- .toggle-slider:before { position: absolute; content: ""; height: 18px; width: 18px; left: 3px; bottom: 3px; background-color: white; transition: .3s; border-radius: 50%; }
- input:checked + .toggle-slider { background-color: var(--primary); }
- input:checked + .toggle-slider:before { transform: translateX(20px); }
- .form-select { padding: 8px 12px; background: var(--bg-dark); border: 1px solid var(--border); border-radius: 6px; color: var(--text-primary); font-size: 13px; }
- /* Assessment */
- .assessment-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; margin-bottom: 20px; }
- .assessment-item { padding: 16px; background: var(--bg-hover); border-radius: 10px; text-align: center; }
- .assessment-label { font-size: 12px; color: var(--text-muted); margin-bottom: 8px; }
- .assessment-value { font-size: 24px; font-weight: 700; margin-bottom: 4px; }
- .assessment-status { font-size: 11px; color: var(--text-secondary); }
- .assessment-details { }
- .assessment-details h5 { font-size: 13px; font-weight: 600; margin-bottom: 12px; }
- .detail-item { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid var(--border); }
- .detail-label { font-size: 12px; color: var(--text-muted); }
- .detail-value { font-size: 12px; color: var(--text-secondary); }
- /* Risk Map */
- .risk-map { height: 300px; background: var(--bg-hover); border-radius: 10px; display: flex; align-items: center; justify-content: center; margin-bottom: 20px; }
- .map-placeholder { text-align: center; color: var(--text-muted); }
- .map-placeholder i { font-size: 48px; margin-bottom: 12px; }
- .map-desc { font-size: 11px; color: var(--text-muted); }
- .map-legend { display: flex; gap: 16px; margin-bottom: 20px; }
- .legend-color { width: 12px; height: 12px; border-radius: 3px; }
- .legend-color.critical { background: #EF4444; }
- .legend-color.medium { background: #F59E0B; }
- .legend-color.low { background: #6366F1; }
- .legend-color.safe { background: #10B981; }
- .region-stats { }
- .region-stats h5 { font-size: 13px; font-weight: 600; margin-bottom: 12px; }
- .stat-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; }
- .stat-item { display: flex; justify-content: space-between; align-items: center; padding: 10px 12px; background: var(--bg-hover); border-radius: 8px; }
- .stat-label { font-size: 12px; }
- .stat-value { font-size: 11px; padding: 2px 8px; border-radius: 10px; }
- .stat-value.critical { background: rgba(239, 68, 68, 0.2); color: #EF4444; }
- .stat-value.medium { background: rgba(245, 158, 11, 0.2); color: #F59E0B; }
- .stat-value.low { background: rgba(99, 102, 241, 0.2); color: #6366F1; }
- .stat-value.safe { background: rgba(16, 185, 129, 0.2); color: #10B981; }
- /* Toast */
- .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; }
- .toast.success { border-color: var(--success); color: var(--success); }
- .toast-enter-active, .toast-leave-active { transition: all 0.3s ease; }
- .toast-enter-from, .toast-leave-to { opacity: 0; transform: translateY(20px); }
- /* Responsive */
- @media (max-width: 1400px) {
- .kpi-row { grid-template-columns: repeat(3, 1fr); }
- .overview-grid { grid-template-columns: repeat(2, 1fr); }
- .operational-grid { grid-template-columns: 1fr; }
- }
- @media (max-width: 992px) {
- .kpi-row { grid-template-columns: repeat(2, 1fr); }
- .overview-grid { grid-template-columns: 1fr; }
- .risk-card-section.full-width { grid-column: span 1; }
- }
- </style>
|