feat: 优化警告提示和调解流程 UI,新增 AI 背景和图片资源
6 files added
9 files modified
| | |
| | | background: #f8f9fa; |
| | | border-radius: 8px; |
| | | padding: 14px 16px; |
| | | margin-bottom: 16px; |
| | | text-align: center; |
| | | margin-bottom: 10px; |
| | | text-align: left; |
| | | } |
| | | |
| | | .success-rate-label { |
| | |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .success-rate-row { |
| | | display: flex; |
| | | align-items: baseline; |
| | | gap: 10px; |
| | | margin-bottom: 10px; |
| | | } |
| | | |
| | | .success-rate-value { |
| | | font-size: 2.2rem; |
| | | font-weight: 700; |
| | | color: #1a6fb8; |
| | | line-height: 1; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .success-rate-yoy { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | gap: 6px; |
| | | gap: 4px; |
| | | font-size: 0.8rem; |
| | | } |
| | | |
| | | .yoy-icon { |
| | | color: #52c41a; |
| | | font-size: 0.75rem; |
| | | .yoy-icon-img { |
| | | width: 12px; |
| | | height: 7px; |
| | | } |
| | | |
| | | .yoy-rate { |
| | |
| | | } |
| | | |
| | | .yoy-time { |
| | | color: #999; |
| | | color: #52c41a; |
| | | } |
| | | |
| | | /* 进度条 */ |
| | | .success-rate-progress { |
| | | width: 100%; |
| | | } |
| | | |
| | | .progress-bar-bg { |
| | | width: 100%; |
| | | height: 6px; |
| | | background: #e8e8e8; |
| | | border-radius: 3px; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .progress-bar-fill { |
| | | height: 100%; |
| | | background: #1a6fb8; |
| | | border-radius: 3px; |
| | | transition: width 0.3s ease; |
| | | } |
| | | |
| | | /* 调解数据看板 - 左右分栏布局 */ |
| | |
| | | flex-direction: column; |
| | | padding: 0; |
| | | background: transparent; |
| | | gap: 0; |
| | | gap: 0px; |
| | | } |
| | | |
| | | .metric-card.right-column > * { |
| | | .metric-card.right-column > *:last-child { |
| | | margin-bottom: 0; |
| | | } |
| | | |
| | |
| | | min-width: 0; |
| | | } |
| | | |
| | | .warning-icon { |
| | | color: #faad14; |
| | | font-size: 1rem; |
| | | .warning-icon-img { |
| | | width: 16px; |
| | | height: 16px; |
| | | flex-shrink: 0; |
| | | margin-right: 6px; |
| | | } |
| | | |
| | | .warning-modal-icon { |
| | | width: 16px; |
| | | height: 16px; |
| | | margin-right: 8px; |
| | | vertical-align: middle; |
| | | } |
| | | |
| | | .warning-label { |
| | |
| | | |
| | | import React, { useState, useEffect, useCallback } from 'react'; |
| | | import { Modal, List } from 'antd'; |
| | | import { WarningOutlined, RightOutlined } from '@ant-design/icons'; |
| | | import { RightOutlined } from '@ant-design/icons'; |
| | | import { useCaseData } from '../../contexts/CaseDataContext'; |
| | | import MediationTimelineAPIService from '../../services/MediationTimelineAPIService'; |
| | | import './WarningAlert.css'; |
| | | |
| | | // 警告图标图片 |
| | | const WARNING_ICON = '/warning.png'; |
| | | |
| | | /** |
| | | * 预警消息列表弹窗 |
| | | */ |
| | | const WarningModal = ({ visible, warnings, onClose }) => ( |
| | | <Modal |
| | | title={<span><WarningOutlined style={{ color: '#faad14', marginRight: 8 }} />预警消息列表</span>} |
| | | title={<span><img src={WARNING_ICON} alt="warning" className="warning-modal-icon" />预警消息列表</span>} |
| | | open={visible} |
| | | onCancel={onClose} |
| | | footer={null} |
| | |
| | | return ( |
| | | <div className="warning-alert"> |
| | | <div className="warning-alert-content"> |
| | | <WarningOutlined className="warning-icon" /> |
| | | <img src={WARNING_ICON} alt="warning" className="warning-icon-img" /> |
| | | <span className="warning-label">预警:</span> |
| | | <span className="warning-text">{firstWarning.content}</span> |
| | | </div> |
| | |
| | | */ |
| | | |
| | | .ai-suggestion-card { |
| | | background: linear-gradient(135deg, #e6f4ff, #f0f7ff); |
| | | border-radius: 8px; |
| | | background: #1a6fb8; |
| | | border-radius: 12px; |
| | | padding: 14px 16px; |
| | | margin-top: 12px; |
| | | border-left: 4px solid #1a6fb8; |
| | | box-shadow: 0px 4px 6px -4px rgba(0,0,0,0.10), 0px 10px 15px -3px rgba(0,0,0,0.10); |
| | | position: relative; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .ai-suggestion-bg { |
| | | position: absolute; |
| | | top: 15px; |
| | | right: 15px; |
| | | width: 48px; |
| | | height: 50px; |
| | | opacity: 0.1; |
| | | pointer-events: none; |
| | | } |
| | | |
| | | .ai-suggestion-header { |
| | |
| | | |
| | | .ai-suggestion-icon { |
| | | font-size: 1rem; |
| | | color: #1a6fb8; |
| | | color: #fff; |
| | | } |
| | | |
| | | .ai-suggestion-title { |
| | | font-size: 0.9rem; |
| | | font-weight: 600; |
| | | color: #0d4a8a; |
| | | color: #fff; |
| | | } |
| | | |
| | | .ai-suggestion-content { |
| | | font-size: 0.85rem; |
| | | line-height: 1.6; |
| | | color: #333; |
| | | color: #fff; |
| | | max-height: 80px; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | |
| | | } |
| | | |
| | | .ai-suggestion-btn { |
| | | padding: 0; |
| | | padding: 8px 24px; |
| | | font-size: 0.85rem; |
| | | color: #1a6fb8; |
| | | color: #FFFFFF; |
| | | background: rgba(255, 255, 255, 0.9); |
| | | border-radius: 20px; |
| | | border: none; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); |
| | | height: auto; |
| | | line-height: 1.5; |
| | | margin: 10px; |
| | | width: 90%; |
| | | background: rgba(255, 255, 255, 0.20); |
| | | border-radius: 8px; |
| | | } |
| | | |
| | | .ai-suggestion-btn:hover { |
| | | color: #0d4a8a; |
| | | background: rgba(255, 255, 255, 1); |
| | | } |
| | |
| | | |
| | | return ( |
| | | <div className="ai-suggestion-card"> |
| | | {/* 背景图标 */} |
| | | <img src="/ai-bg.png" alt="" className="ai-suggestion-bg" /> |
| | | |
| | | {/* 标题 */} |
| | | <div className="ai-suggestion-header"> |
| | | <BulbOutlined className="ai-suggestion-icon" /> |
| | |
| | | background: #f8f9fa; |
| | | border-radius: 8px; |
| | | padding: 12px 16px; |
| | | border-top: 3px solid #1a6fb8; |
| | | } |
| | | |
| | | .negotiation-header { |
| | |
| | | .progress-dot-wrapper { |
| | | display: flex; |
| | | align-items: center; |
| | | flex: 1; |
| | | justify-content: flex-start; |
| | | } |
| | | |
| | | .progress-dot-wrapper.last { |
| | | flex: 0; |
| | | } |
| | | |
| | | /* 进度点 */ |
| | | /* 进度短横线 */ |
| | | .progress-dot { |
| | | width: 10px; |
| | | height: 10px; |
| | | border-radius: 50%; |
| | | width: 16px; |
| | | height: 4px; |
| | | border-radius: 2px; |
| | | background: #d9d9d9; |
| | | flex-shrink: 0; |
| | | transition: background 0.3s; |
| | | margin-left: 5px; |
| | | } |
| | | |
| | | .progress-dot-wrapper:first-child .progress-dot { |
| | | margin-left: 0; |
| | | } |
| | | |
| | | .progress-dot.active { |
| | |
| | | /* 连接线 */ |
| | | .progress-line { |
| | | flex: 1; |
| | | height: 3px; |
| | | height: 2px; |
| | | background: #d9d9d9; |
| | | margin: 0 2px; |
| | | margin: 0 4px; |
| | | transition: background 0.3s; |
| | | } |
| | | |
| | |
| | | background: #f8f9fa; |
| | | border-radius: 8px; |
| | | padding: 12px 16px; |
| | | margin-bottom: 16px; |
| | | margin-bottom: 10px; |
| | | } |
| | | |
| | | .party-info-title { |
| | |
| | | padding: 8px; |
| | | } |
| | | |
| | | /* 情绪标签 */ |
| | | /* 情绪标签 - 实心红背景白字 */ |
| | | .party-tag { |
| | | position: absolute; |
| | | top: -4px; |
| | | left: 50%; |
| | | transform: translateX(-50%); |
| | | font-size: 0.7rem; |
| | | padding: 0 6px; |
| | | line-height: 18px; |
| | | border-radius: 4px; |
| | | padding: 2px 8px; |
| | | line-height: 16px; |
| | | border-radius: 10px; |
| | | background-color: #ff4d4f !important; |
| | | color: white !important; |
| | | border: none !important; |
| | | } |
| | | |
| | | .party-avatar { |
| | | .party-avatar-img { |
| | | width: 48px; |
| | | height: 48px; |
| | | border-radius: 50%; |
| | | margin-top: 12px; |
| | | margin-bottom: 8px; |
| | | object-fit: cover; |
| | | } |
| | | |
| | | .party-role { |
| | |
| | | padding: 0 8px; |
| | | } |
| | | |
| | | .vs-icon { |
| | | font-size: 1.5rem; |
| | | color: #d9d9d9; |
| | | .vs-icon-img { |
| | | width: 24px; |
| | | height: 24px; |
| | | object-fit: contain; |
| | | } |
| | |
| | | */ |
| | | |
| | | import React, { useState, useEffect, useCallback } from 'react'; |
| | | import { Avatar, Tag } from 'antd'; |
| | | import { UserOutlined, TeamOutlined } from '@ant-design/icons'; |
| | | import { Tag } from 'antd'; |
| | | import { useCaseData } from '../../contexts/CaseDataContext'; |
| | | import MediationTimelineAPIService from '../../services/MediationTimelineAPIService'; |
| | | import './PartyInfoCard.css'; |
| | | |
| | | // 申请人头像图片 |
| | | const APPLICANT_AVATAR = '/in_person.png'; |
| | | // 被申请人头像图片 |
| | | const RESPONDENT_AVATAR = '/to_person.png'; |
| | | |
| | | |
| | | |
| | |
| | | * 当事人信息卡片 |
| | | */ |
| | | const PartyCard = ({ person, isApplicantSide }) => { |
| | | const avatarIcon = isApplicantSide ? <UserOutlined /> : <TeamOutlined />; |
| | | const avatarBg = isApplicantSide ? '#1a6fb8' : '#faad14'; |
| | | const avatarSrc = isApplicantSide ? APPLICANT_AVATAR : RESPONDENT_AVATAR; |
| | | |
| | | return ( |
| | | <div className={`party-card ${isApplicantSide ? 'applicant' : 'respondent'}`}> |
| | |
| | | )} |
| | | |
| | | {/* 头像 */} |
| | | <Avatar |
| | | size={48} |
| | | icon={avatarIcon} |
| | | style={{ backgroundColor: avatarBg }} |
| | | className="party-avatar" |
| | | <img |
| | | src={avatarSrc} |
| | | alt={isApplicantSide ? '申请人' : '被申请人'} |
| | | className="party-avatar-img" |
| | | /> |
| | | |
| | | {/* 角色标签 */} |
| | |
| | | ); |
| | | }; |
| | | |
| | | // VS分隔符图片 |
| | | const VS_ICON = '/join.png'; |
| | | |
| | | /** |
| | | * VS分隔符 |
| | | */ |
| | | const VSSeparator = () => ( |
| | | <div className="vs-separator"> |
| | | <span className="vs-icon">⚖</span> |
| | | <img src={VS_ICON} alt="VS" className="vs-icon-img" /> |
| | | </div> |
| | | ); |
| | | |
| | |
| | | import MediationAgreementAPIService from '../../services/MediationAgreementAPIService'; |
| | | import { getMergedParams } from '../../utils/urlParams'; |
| | | import { message, Spin, Tag, Modal, Button, Input, Image } from 'antd'; |
| | | import { PhoneOutlined, ArrowUpOutlined } from '@ant-design/icons'; |
| | | import { PhoneOutlined } from '@ant-design/icons'; |
| | | import { CallRecordModal } from '../call-record'; |
| | | |
| | | // 新增组件导入 |
| | |
| | | const updateTime = formatDuration(timeline.before_duration); |
| | | const successRate = formatSuccessRate(mediation.success_rate); |
| | | |
| | | // 获取成功率数值(用于进度条) |
| | | const successRateValue = (mediation.success_rate || 0) * 100; |
| | | |
| | | // 获取同比数据 |
| | | const yoyData = getSuccessRateYoY(mediation); |
| | | const yoyRate = yoyData.rate >= 0 ? `+${yoyData.rate.toFixed(0)}%` : `${yoyData.rate.toFixed(0)}%`; |
| | |
| | | {/* 预计调解成功率 */} |
| | | <div className="success-rate-section"> |
| | | <div className="success-rate-label">预计调解成功率</div> |
| | | <div className="success-rate-value">{successRate}</div> |
| | | <div className="success-rate-yoy"> |
| | | <ArrowUpOutlined className="yoy-icon" /> |
| | | <span className="yoy-rate">{yoyRate}</span> |
| | | <span className="yoy-time">较{yoyHours}小时前</span> |
| | | <div className="success-rate-row"> |
| | | <span className="success-rate-value">{successRate}</span> |
| | | <div className="success-rate-yoy"> |
| | | <img src="/mom.png" alt="" className="yoy-icon-img" /> |
| | | <span className="yoy-rate">{yoyRate}</span> |
| | | <span className="yoy-time">较{yoyHours}小时前</span> |
| | | </div> |
| | | </div> |
| | | <div className="success-rate-progress"> |
| | | <div className="progress-bar-bg"> |
| | | <div className="progress-bar-fill" style={{ width: `${successRateValue}%` }}></div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |