refactor(RawDataCards): 重构通话记录卡片布局并添加时间格式化功能 - 在CustomerDetail组件中,简化SOP分析按钮的状态逻辑并更新API地址 - 在RawDataCards组件中,重新设计操作按钮布局,添加时间显示功能并优化样式
755 lines
19 KiB
Vue
755 lines
19 KiB
Vue
<template>
|
||
<div class="raw-data-cards">
|
||
<div class="cards-container">
|
||
<!-- 表单信息卡片 -->
|
||
<div class="data-card form-card">
|
||
<div class="card-header">
|
||
<div class="card-icon">
|
||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||
<polyline points="14,2 14,8 20,8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||
<line x1="16" y1="13" x2="8" y2="13" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||
<line x1="16" y1="17" x2="8" y2="17" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||
<polyline points="10,9 9,9 8,9" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||
</svg>
|
||
</div>
|
||
<h3 class="card-title">表单信息</h3>
|
||
</div>
|
||
<div class="card-content">
|
||
<div class="form-data-list">
|
||
<div v-for="(field, index) in formFields" :key="index" class="form-field">
|
||
<span class="field-label">{{ field.label }}:</span>
|
||
<span class="field-value">{{ field.value }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 聊天记录和通话录音卡片 -->
|
||
<div class="data-card communication-card">
|
||
<div class="card-header">
|
||
<div class="card-icon">
|
||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||
</svg>
|
||
</div>
|
||
<h3 class="card-title">沟通记录</h3>
|
||
</div>
|
||
<div class="card-content">
|
||
<!-- Tab 切换 -->
|
||
<div class="tab-container">
|
||
<div class="tab-buttons">
|
||
<button
|
||
class="tab-btn"
|
||
:class="{ active: activeTab === 'chat' }"
|
||
@click="activeTab = 'chat'"
|
||
>
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||
</svg>
|
||
聊天记录
|
||
</button>
|
||
<button
|
||
class="tab-btn"
|
||
:class="{ active: activeTab === 'call' }"
|
||
@click="activeTab = 'call'"
|
||
>
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||
<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||
</svg>
|
||
通话录音
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Tab 内容 -->
|
||
<div class="tab-content">
|
||
<!-- 聊天记录内容 -->
|
||
<div v-if="activeTab === 'chat'" class="chat-content">
|
||
<div class="content-header">
|
||
<span class="content-count">共 {{ chatData.count }} 条消息</span>
|
||
<span class="content-time">最新: {{ chatData.lastMessage }}</span>
|
||
</div>
|
||
<div class="message-list">
|
||
<div v-for="(message, index) in props.chatInfo.messages" :key="index" class="message-item">
|
||
<div class="message-header">
|
||
<span class="message-sender">{{ message.format_direction }}</span>
|
||
<span class="message-time">{{ message.format_add_time }}</span>
|
||
</div>
|
||
<div class="message-text">{{ message.content }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 通话录音内容 -->
|
||
<div v-if="activeTab === 'call'" class="call-content">
|
||
<div class="content-header">
|
||
<span class="content-count">共 {{ callRecords.length }} 次通话</span>
|
||
<span class="content-time">通话记录</span>
|
||
</div>
|
||
<div class="call-list">
|
||
<div v-for="(call, index) in callRecords" :key="index" class="call-item">
|
||
<div class="call-header">
|
||
<span class="call-type">用户: {{ call.user_name }}</span>
|
||
<span class="call-duration">客户: {{ call.customer_name }}</span>
|
||
</div>
|
||
<div class="call-actions">
|
||
<div class="action-buttons">
|
||
<button class="action-btn download-btn" @click="downloadRecording(call)">
|
||
<i class="icon-download"></i>
|
||
录音下载
|
||
</button>
|
||
<button class="action-btn view-btn" @click="viewTranscript(call)">
|
||
<i class="icon-view"></i>
|
||
查看原文
|
||
</button>
|
||
</div>
|
||
<div class="call-time-info">
|
||
<span class="call-duration">{{ formatDateTime(call.record_create_time) }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed } from 'vue'
|
||
import axios from 'axios'
|
||
|
||
// Props
|
||
const props = defineProps({
|
||
selectedContact: {
|
||
type: Object,
|
||
default: () => ({})
|
||
},
|
||
formInfo: {
|
||
type: Object,
|
||
default: () => ({})
|
||
},
|
||
chatInfo: {
|
||
type: Object,
|
||
default: () => ({})
|
||
},
|
||
callInfo: {
|
||
type: Object,
|
||
default: () => ({})
|
||
}
|
||
})
|
||
|
||
// Emits
|
||
const emit = defineEmits(['analyze-sop'])
|
||
|
||
// 当前激活的tab
|
||
const activeTab = ref('chat')
|
||
|
||
// 聊天消息列表
|
||
const chatMessages = computed(() => {
|
||
return props.chatInfo?.messages || []
|
||
})
|
||
|
||
// 表单字段数据
|
||
const formFields = computed(() => {
|
||
const formData = props.formInfo
|
||
if (!formData || Object.keys(formData).length === 0) {
|
||
return [
|
||
{ label: '姓名', value: '暂无数据' },
|
||
{ label: '联系方式', value: '暂无数据' },
|
||
{ label: '孩子信息', value: '暂无数据' },
|
||
{ label: '地区', value: '暂无数据' }
|
||
]
|
||
}
|
||
|
||
let fields = []
|
||
|
||
// 检查是否为第一种格式(包含name, mobile等字段)
|
||
if (formData.name || formData.mobile || formData.child_name) {
|
||
const customerInfo = [formData.name, formData.mobile, formData.child_relation, formData.occupation].filter(item => item && item !== '暂无').join(' | ')
|
||
const childInfo = [formData.child_name, formData.child_gender, formData.child_education].filter(item => item && item !== '暂无').join(' | ')
|
||
|
||
fields = [
|
||
{ label: '客户信息', value: customerInfo || '暂无' },
|
||
{ label: '孩子信息', value: childInfo || '暂无' },
|
||
{ label: '地区', value: formData.territory || '暂无' }
|
||
]
|
||
|
||
// 如果有additional_info,添加所有问题
|
||
if (formData.additional_info && Array.isArray(formData.additional_info)) {
|
||
formData.additional_info.forEach((item) => {
|
||
fields.push({
|
||
label: item.topic,
|
||
value: item.answer
|
||
})
|
||
})
|
||
}
|
||
} else {
|
||
// 第二种格式(expandXXX字段)
|
||
const customerInfo = [formData.expandTwentyOne, formData.expandOne].filter(item => item && item !== '暂无').join(' | ')
|
||
const childInfo = [formData.expandTwentyNine, formData.expandTwentyFive, formData.expandTwo].filter(item => item && item !== '暂无').join(' | ')
|
||
|
||
fields = [
|
||
{ label: '客户信息', value: customerInfo || '暂无' },
|
||
{ label: '孩子信息', value: childInfo || '暂无' },
|
||
{ label: '学习状态', value: formData.expandFive || '暂无' },
|
||
{ label: '沟通情况', value: formData.expandEight || '暂无' },
|
||
{ label: '主要问题', value: formData.expandTwentySeven || '暂无' },
|
||
{ label: '关注领域', value: formData.expandFifteen || '暂无' },
|
||
{ label: '学习成绩', value: formData.expandFourteen || '暂无' },
|
||
{ label: '孩子数量', value: formData.expandTwenty || '暂无' },
|
||
{ label: '预期时间', value: formData.expandThirty || '暂无' }
|
||
]
|
||
}
|
||
// 合并表单数据和聊天数据
|
||
const allFields = [...fields]
|
||
|
||
return allFields
|
||
})
|
||
|
||
// 聊天数据
|
||
const chatData = computed(() => ({
|
||
count: props.chatInfo?.messages?.length || 0,
|
||
lastMessage: props.chatInfo?.update_time || '1小时前'
|
||
}))
|
||
|
||
// 通话数据
|
||
const callData = computed(() => ({
|
||
count: props.selectedContact?.callCount || 5,
|
||
totalDuration: props.selectedContact?.totalCallDuration || '45分钟'
|
||
}))
|
||
|
||
// 通话记录列表
|
||
const callRecords = computed(() => {
|
||
|
||
// 从 props.callInfo 中获取真实的通话记录数据
|
||
if (props.callInfo && Array.isArray(props.callInfo)) {
|
||
return props.callInfo
|
||
}
|
||
|
||
// 如果 callInfo 是单个对象(API返回的数据格式)
|
||
if (props.callInfo && typeof props.callInfo === 'object' && props.callInfo.user_name) {
|
||
return [props.callInfo] // 将单个对象包装成数组
|
||
}
|
||
|
||
// 如果 callInfo 是对象且包含数据数组
|
||
if (props.callInfo && props.callInfo && Array.isArray(props.callInfo)) {
|
||
return props.callInfo
|
||
}
|
||
// 如果没有数据,返回空数组
|
||
return []
|
||
})
|
||
|
||
// 录音下载方法
|
||
const downloadRecording = (call) => {
|
||
console.log('下载录音:', call)
|
||
|
||
// 检查是否有录音文件地址
|
||
if (call.record_file_addr) {
|
||
const recordingUrl = call.record_file_addr
|
||
|
||
// 从URL中提取文件名
|
||
const urlParts = recordingUrl.split('/')
|
||
const fileName = urlParts[urlParts.length - 1]
|
||
|
||
// 创建下载链接
|
||
const link = document.createElement('a')
|
||
link.href = recordingUrl
|
||
link.download = fileName
|
||
link.target = '_blank'
|
||
|
||
// 触发下载
|
||
document.body.appendChild(link)
|
||
link.click()
|
||
document.body.removeChild(link)
|
||
|
||
} else {
|
||
alert('该通话记录暂无录音文件')
|
||
}
|
||
}
|
||
|
||
// 查看原文方法
|
||
const viewTranscript = async (call) => {
|
||
// 触发SOP分析
|
||
alert(call.record_context)
|
||
|
||
// 显示通话记录内容
|
||
if (call.record_context) {
|
||
alert(call.record_context)
|
||
} else {
|
||
alert('该通话记录暂无原文内容')
|
||
}
|
||
}
|
||
|
||
// 时间格式化方法
|
||
const formatDateTime = (dateTimeString) => {
|
||
if (!dateTimeString) return '暂无时间'
|
||
|
||
try {
|
||
const date = new Date(dateTimeString)
|
||
const year = date.getFullYear()
|
||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||
const day = String(date.getDate()).padStart(2, '0')
|
||
const hours = String(date.getHours()).padStart(2, '0')
|
||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||
|
||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||
} catch (error) {
|
||
console.error('时间格式化错误:', error)
|
||
return dateTimeString
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.raw-data-cards {
|
||
margin: 24px 0;
|
||
}
|
||
|
||
.cards-container {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 20px;
|
||
|
||
@media (max-width: 768px) {
|
||
grid-template-columns: 1fr;
|
||
gap: 16px;
|
||
}
|
||
}
|
||
|
||
.data-card {
|
||
background: #ffffff;
|
||
border: 1px solid #e5e7eb;
|
||
border-radius: 12px;
|
||
padding: 20px;
|
||
transition: all 0.3s ease;
|
||
position: relative;
|
||
overflow: hidden;
|
||
|
||
&:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
|
||
border-color: #d1d5db;
|
||
}
|
||
|
||
&::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
height: 4px;
|
||
background: linear-gradient(90deg, #3b82f6, #8b5cf6);
|
||
}
|
||
|
||
&.form-card::before {
|
||
background: linear-gradient(90deg, #10b981, #059669);
|
||
}
|
||
|
||
&.communication-card::before {
|
||
background: linear-gradient(90deg, #3b82f6, #1d4ed8);
|
||
}
|
||
}
|
||
|
||
.card-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.card-icon {
|
||
width: 40px;
|
||
height: 40px;
|
||
border-radius: 10px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: #f3f4f6;
|
||
color: #6b7280;
|
||
|
||
.form-card & {
|
||
background: #ecfdf5;
|
||
color: #059669;
|
||
}
|
||
|
||
.communication-card & {
|
||
background: #eff6ff;
|
||
color: #1d4ed8;
|
||
}
|
||
}
|
||
|
||
.card-title {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: #111827;
|
||
margin: 0;
|
||
flex: 1;
|
||
}
|
||
|
||
// 表单字段样式
|
||
.form-data-list {
|
||
.form-field {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 12px 0;
|
||
border-bottom: 1px solid #f3f4f6;
|
||
|
||
&:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.field-label {
|
||
font-size: 14px;
|
||
color: #6b7280;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.field-value {
|
||
font-size: 14px;
|
||
color: #1f2937;
|
||
font-weight: 500;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Tab 容器样式
|
||
.tab-container {
|
||
.tab-buttons {
|
||
display: flex;
|
||
border-bottom: 1px solid #e5e7eb;
|
||
margin-bottom: 16px;
|
||
|
||
.tab-btn {
|
||
flex: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 8px;
|
||
padding: 12px 16px;
|
||
background: none;
|
||
border: none;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
color: #6b7280;
|
||
cursor: pointer;
|
||
border-bottom: 2px solid transparent;
|
||
transition: all 0.2s ease;
|
||
|
||
&.active {
|
||
color: #3b82f6;
|
||
border-bottom-color: #3b82f6;
|
||
}
|
||
|
||
&:hover {
|
||
color: #3b82f6;
|
||
}
|
||
|
||
svg {
|
||
width: 16px;
|
||
height: 16px;
|
||
}
|
||
}
|
||
}
|
||
|
||
.tab-content {
|
||
min-height: 300px;
|
||
max-height: 450px;
|
||
overflow-y: auto;
|
||
|
||
.content-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 16px;
|
||
padding-bottom: 12px;
|
||
border-bottom: 1px solid #f3f4f6;
|
||
|
||
.content-count {
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
color: #3b82f6;
|
||
}
|
||
|
||
.content-time {
|
||
font-size: 12px;
|
||
color: #9ca3af;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 聊天消息样式
|
||
.message-list {
|
||
.message-item {
|
||
margin-bottom: 16px;
|
||
padding: 12px;
|
||
border-radius: 8px;
|
||
background: #f9fafb;
|
||
|
||
.message-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 8px;
|
||
|
||
.message-sender {
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
color: #3b82f6;
|
||
}
|
||
|
||
.message-time {
|
||
font-size: 12px;
|
||
color: #9ca3af;
|
||
}
|
||
}
|
||
|
||
.message-text {
|
||
font-size: 14px;
|
||
color: #374151;
|
||
line-height: 1.5;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 通话记录样式
|
||
.call-list {
|
||
.call-item {
|
||
margin-bottom: 16px;
|
||
padding: 16px;
|
||
border-radius: 8px;
|
||
background: #f9fafb;
|
||
border-left: 4px solid #3b82f6;
|
||
|
||
.call-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 8px;
|
||
|
||
.call-type {
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
padding: 4px 8px;
|
||
border-radius: 4px;
|
||
background: #dbeafe;
|
||
color: #3b82f6;
|
||
}
|
||
|
||
.call-duration {
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
color: #6b7280;
|
||
}
|
||
|
||
.call-time {
|
||
font-size: 12px;
|
||
color: #9ca3af;
|
||
}
|
||
}
|
||
|
||
.call-actions {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-top: 12px;
|
||
padding-top: 8px;
|
||
border-top: 1px solid #f3f4f6;
|
||
|
||
.action-buttons {
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
|
||
.action-btn {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
padding: 6px 10px;
|
||
border: none;
|
||
border-radius: 6px;
|
||
font-size: 11px;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
|
||
&.download-btn {
|
||
background: #dbeafe;
|
||
color: #3b82f6;
|
||
|
||
&:hover {
|
||
background: #bfdbfe;
|
||
transform: translateY(-1px);
|
||
box-shadow: 0 2px 4px rgba(59, 130, 246, 0.2);
|
||
}
|
||
}
|
||
|
||
&.view-btn {
|
||
background: #d1fae5;
|
||
color: #059669;
|
||
|
||
&:hover {
|
||
background: #a7f3d0;
|
||
transform: translateY(-1px);
|
||
box-shadow: 0 2px 4px rgba(5, 150, 105, 0.2);
|
||
}
|
||
}
|
||
|
||
i {
|
||
width: 12px;
|
||
height: 12px;
|
||
|
||
&.icon-download::before {
|
||
content: '⬇';
|
||
}
|
||
|
||
&.icon-view::before {
|
||
content: '👁';
|
||
}
|
||
}
|
||
}
|
||
|
||
.call-time-info {
|
||
.call-duration {
|
||
font-size: 11px;
|
||
color: #6b7280;
|
||
font-weight: 500;
|
||
background: #f9fafb;
|
||
padding: 4px 8px;
|
||
border-radius: 4px;
|
||
border: 1px solid #e5e7eb;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.card-content {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.card-description {
|
||
color: #6b7280;
|
||
font-size: 14px;
|
||
margin: 0 0 16px 0;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.card-stats {
|
||
display: flex;
|
||
gap: 20px;
|
||
|
||
@media (max-width: 480px) {
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
}
|
||
}
|
||
|
||
.stat-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
}
|
||
|
||
.stat-label {
|
||
font-size: 12px;
|
||
color: #9ca3af;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.stat-value {
|
||
font-size: 14px;
|
||
color: #111827;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.card-action {
|
||
border-top: 1px solid #f3f4f6;
|
||
padding-top: 16px;
|
||
margin-top: 16px;
|
||
}
|
||
|
||
.view-btn {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
background: none;
|
||
border: none;
|
||
color: #6b7280;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
padding: 8px 0;
|
||
transition: color 0.2s ease;
|
||
|
||
&:hover {
|
||
color: #111827;
|
||
}
|
||
|
||
svg {
|
||
transition: transform 0.2s ease;
|
||
}
|
||
|
||
&:hover svg {
|
||
transform: translateX(2px);
|
||
}
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.raw-data-cards {
|
||
margin: 20px 0;
|
||
}
|
||
|
||
.data-card {
|
||
padding: 16px;
|
||
}
|
||
|
||
.card-header {
|
||
gap: 10px;
|
||
margin-bottom: 14px;
|
||
}
|
||
|
||
.card-icon {
|
||
width: 36px;
|
||
height: 36px;
|
||
}
|
||
|
||
.card-title {
|
||
font-size: 15px;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 480px) {
|
||
.cards-container {
|
||
gap: 12px;
|
||
}
|
||
|
||
.data-card {
|
||
padding: 14px;
|
||
}
|
||
|
||
.card-header {
|
||
gap: 8px;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.card-icon {
|
||
width: 32px;
|
||
height: 32px;
|
||
}
|
||
|
||
.card-title {
|
||
font-size: 14px;
|
||
}
|
||
|
||
.card-description {
|
||
font-size: 13px;
|
||
}
|
||
}
|
||
</style> |