| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614 |
- <template>
- <div class="data-page">
- <div class="data-header">
- <div class="data-title">📋 客户池 - 共{{ customerStats.total }}家</div>
- <div style="display: flex; gap: 8px;">
- <button class="btn btn-secondary"><i class="fas fa-download"></i> 导出</button>
- <!-- <button class="btn btn-primary" @click="showAddModal = true"><i class="fas fa-plus"></i> 添加客户</button> -->
- </div>
- </div>
- <!-- Stats -->
- <div class="stat-row">
- <div class="stat-card-lg" @click="filterTab = 'all'">
- <div class="stat-icon-lg blue"><i class="fas fa-users"></i></div>
- <div class="stat-content-lg"><h3>{{ customerStats.total }}</h3><p>总客户数</p></div>
- </div>
- <div class="stat-card-lg" @click="filterTab = 'prospect'">
- <div class="stat-icon-lg purple"><i class="fas fa-user-clock"></i></div>
- <div class="stat-content-lg"><h3>{{ customerStats.prospect }}</h3><p>潜在客户</p></div>
- </div>
- <div class="stat-card-lg" @click="filterTab = 'intent'">
- <div class="stat-icon-lg orange"><i class="fas fa-star"></i></div>
- <div class="stat-content-lg"><h3>{{ customerStats.intent }}</h3><p>意向客户</p></div>
- </div>
- <div class="stat-card-lg" @click="filterTab = 'deal'">
- <div class="stat-icon-lg green"><i class="fas fa-handshake"></i></div>
- <div class="stat-content-lg"><h3>{{ customerStats.deal }}</h3><p>成交客户</p></div>
- </div>
- <div class="stat-card-lg" @click="filterTab = 'silent'">
- <div class="stat-icon-lg" style="background: rgba(148, 163, 184, 0.2); color: var(--text-secondary);"><i class="fas fa-moon"></i></div>
- <div class="stat-content-lg"><h3>{{ customerStats.silent }}</h3><p>沉默客户</p></div>
- </div>
- </div>
- <!-- Search & Filter -->
- <div style="display: flex; gap: 16px; margin-bottom: 20px;">
- <div class="search-box" style="flex: 1;">
- <i class="fas fa-search search-icon"></i>
- <input type="text" v-model="searchQuery" class="search-input" placeholder="搜索公司名称、联系人、邮箱..." @input="getCustomer">
- </div>
- <div style="display: flex; gap: 8px;">
- <el-cascader class="form-select" style="width: auto; padding: 0;" placeholder="区域名称" :options="region"
- v-model="regionValue" clearable filterable @change="getRegion" />
- <!-- <select v-model="industryFilter" class="form-select" style="width: auto; padding: 8px 16px;">
- <option value="">全部行业</option>
- <option value="固态硬盘">固态硬盘</option>
- <option value="电子分销">电子分销</option>
- <option value="企业级存储">企业级存储</option>
- </select>
- <button class="btn btn-secondary"><i class="fas fa-sliders-h"></i> 高级筛选</button> -->
- </div>
- </div>
- <!-- Tabs -->
- <div style="display: flex; gap: 4px; margin-bottom: 20px; border-bottom: 1px solid var(--border); padding-bottom: 12px;">
- <div v-for="tab in tabs" :key="tab.key"
- :style="{
- padding: '8px 16px',
- borderRadius: '8px 8px 0 0',
- cursor: 'pointer',
- fontSize: '13px',
- background: filterTab === tab.key ? 'var(--bg-hover)' : 'transparent',
- color: filterTab === tab.key ? 'var(--text-primary)' : 'var(--text-secondary)',
- fontWeight: filterTab === tab.key ? '600' : '400'
- }"
- @click="filterTab = tab.key">
- {{ tab.label }} <span style="font-size: 11px; opacity: 0.7;">({{ tab.count }})</span>
- </div>
- </div>
- <!-- Customer List -->
- <!-- <div style="flex: 1; overflow-y: auto;"> -->
- <div v-for="customer in filteredCustomers" :key="customer.id"
- class="customer-card"
- @click="showCustomerDetail(customer)">
- <div class="customer-flag">{{ customer.flag }}</div>
- <div class="customer-info">
- <div class="customer-header">
- <span class="customer-name">{{ customer.customerName }}</span>
- <!-- 客户类型标识 -->
- <!-- <span class="customer-status" :style="{ background: getStatusBg(customer.status), color: getStatusColor(customer.status) }">
- {{ getStatusLabel(customer.status) }}
- </span> -->
- </div>
- <div class="customer-contact">
- <i class="fas fa-user"></i> {{ customer.liaison?.name }}<span v-if="customer.liaison?.departmentPosition"> · </span>{{ customer.liaison?.departmentPosition }}
- </div>
- <div class="customer-meta">
- <span><i class="fas fa-map-marker-alt"></i> {{ customer.continent }} · {{ customer.country }}</span>
- <span><i class="fas fa-industry"></i> {{ customer.customerCompany?.industry }}</span>
- <!-- <span><i class="fas fa-dollar-sign"></i> {{ customer.annualPurchase }}</span> -->
- </div>
- </div>
- <!-- <div class="customer-score">
- <div class="score-circle" :class="getScoreClass(customer.score)">{{ customer.score }}</div>
- <div class="score-label">意向分</div>
- </div> -->
- <div class="customer-actions" @click.stop>
- <button class="btn btn-secondary btn-sm" @click="showCustomerDetail(customer)"><i class="fas fa-eye"></i>更多详情</button>
- </div>
- </div>
- <div class="pagination-area">
- <el-pagination
- background
- :total="customerStats.total"
- style="text-align: right;"
- :current-page="params.pageIndex"
- :page-size="params.pageSize"
- layout="total, prev, pager, next"
- @current-change="pageCurrentChange">
- </el-pagination>
- </div>
- <!-- </div> -->
- <!-- 引入添加客户弹窗组件 -->
- <!-- <AddCustomerModal
- :show-modal="showAddModal"
- @close="showAddModal = false"
- @add="handleAddCustomer"
- /> -->
- <!-- Customer Detail Modal -->
- <div v-if="showDetailModal && selectedCustomer" class="modal-overlay" @click.self="showDetailModal = false">
- <div class="modal customer-detail-modal">
- <div class="modal-header">
- <div class="modal-title">
- <span class="detail-flag">{{ selectedCustomer.flag }}</span>
- <div>
- <h3>{{ selectedCustomer.name }}</h3>
- <p>{{ selectedCustomer.industry }} · {{ selectedCustomer.location }}</p>
- </div>
- </div>
- <button class="modal-close" @click="showDetailModal = false"><i class="fas fa-times"></i></button>
- </div>
- <div class="modal-body">
- <!-- Quick Stats -->
- <!-- <div class="detail-stats">
- <div class="detail-stat-card">
- <div class="detail-stat-value" :class="getScoreClass(selectedCustomer.score)">{{ selectedCustomer.score }}</div>
- <div class="detail-stat-label">意向分</div>
- </div>
- <div class="detail-stat-card">
- <div class="detail-stat-value">{{ selectedCustomer.totalDeals }}</div>
- <div class="detail-stat-label">累计订单</div>
- </div>
- <div class="detail-stat-card">
- <div class="detail-stat-value success">¥{{ formatNumber(selectedCustomer.totalAmount) }}</div>
- <div class="detail-stat-label">累计金额</div>
- </div>
- <div class="detail-stat-card">
- <div class="detail-stat-value">{{ selectedCustomer.lastContact }}</div>
- <div class="detail-stat-label">最后联系</div>
- </div>
- </div> -->
- <!-- Contact Info -->
- <div class="detail-section">
- <h4><i class="fas fa-address-card"></i> 联系信息</h4>
- <div class="contact-grid">
- <div class="contact-item">
- <i class="fas fa-user"></i>
- <div class="contact-content">
- <div class="contact-label">主要联系人</div>
- <div class="contact-value">{{ selectedCustomer.liaison?.name }}</div>
- <div class="contact-sub">{{ selectedCustomer.liaison?.departmentPosition }}</div>
- </div>
- </div>
- <div class="contact-item">
- <i class="fas fa-envelope"></i>
- <div class="contact-content">
- <div class="contact-label">邮箱</div>
- <div class="contact-value">{{ selectedCustomer.liaison?.email }}</div>
- </div>
- </div>
- <div class="contact-item">
- <i class="fas fa-phone"></i>
- <div class="contact-content">
- <div class="contact-label">电话</div>
- <div class="contact-value">{{ selectedCustomer.liaison?.telephone }}</div>
- </div>
- </div>
- <div class="contact-item">
- <i class="fas fa-globe"></i>
- <div class="contact-content">
- <div class="contact-label">官网</div>
- <div class="contact-value link">{{ selectedCustomer.customerCompany?.website }}</div>
- </div>
- </div>
- </div>
- </div>
- <!-- AI Insights -->
- <div class="detail-section">
- <h4><i class="fas fa-robot"></i> AI 洞察</h4>
- <div class="ai-insight-cards" v-if="selectedCustomer.aiInsights">
- <div class="ai-insight-card" v-for="insight in selectedCustomer.aiInsights" :key="insight.type">
- <div class="insight-icon" :class="insight.level">
- <i :class="insight.icon"></i>
- </div>
- <div class="insight-content">
- <div class="insight-title">{{ insight.title }}</div>
- <div class="insight-desc">{{ insight.description }}</div>
- </div>
- </div>
- </div>
- </div>
- <!-- Interaction Timeline -->
- <div class="detail-section">
- <h4><i class="fas fa-history"></i> 互动记录</h4>
- <div class="timeline" v-if="selectedCustomer.interactions">
- <div class="timeline-item" v-for="record in selectedCustomer.interactions" :key="record.id">
- <div class="timeline-dot" :class="record.type"></div>
- <div class="timeline-content">
- <div class="timeline-header">
- <span class="timeline-action">{{ record.action }}</span>
- <span class="timeline-time">{{ record.time }}</span>
- </div>
- <div class="timeline-detail">{{ record.detail }}</div>
- </div>
- </div>
- </div>
- </div>
- <!-- Related Opportunities -->
- <!-- <div class="detail-section">
- <h4><i class="fas fa-briefcase"></i> 关联商机</h4>
- <div class="related-opps">
- <div class="related-opp" v-for="opp in selectedCustomer.opportunities" :key="opp.id">
- <div class="opp-info">
- <div class="opp-name">{{ opp.name }}</div>
- <div class="opp-meta">¥{{ formatNumber(opp.amount) }} · {{ opp.stage }}</div>
- </div>
- <div class="opp-status" :class="opp.statusClass">{{ opp.statusLabel }}</div>
- </div>
- </div>
- </div> -->
- </div>
- <div class="modal-footer">
- <button class="btn btn-secondary" @click="showDetailModal = false">关闭</button>
- <!-- <button class="btn btn-secondary"><i class="fas fa-edit"></i> 编辑</button> -->
- <button class="btn btn-primary" @click="">
- <i class="fas fa-rocket"></i> 客户详情
- </button>
- </div>
- </div>
- </div>
- <!-- Toast -->
- <transition name="toast">
- <div v-if="showToast" class="toast" :class="toastType">
- <i :class="toastType === 'success' ? 'fas fa-check-circle' : 'fas fa-info-circle'"></i>
- {{ toastMessage }}
- </div>
- </transition>
- </div>
- </template>
- <script setup>
- import { ref, computed } from 'vue'
- import { getCustomerList } from '../api/customer'
- import {region} from '@/utils/region';
- // 引入添加客户组件
- import AddCustomerModal from '../components/addCustomer.vue'
- const showAddModal = ref(false)
- const showDetailModal = ref(false)
- const selectedCustomer = ref(null)
- const filterTab = ref('all')
- const searchQuery = ref('')
- const regionFilter = ref('')
- const industryFilter = ref('')
- const showToast = ref(false)
- const toastMessage = ref('')
- const toastType = ref('success')
- const customerStats = ref({
- total: 0,
- prospect: 0,
- intent: 0,
- deal: 0,
- silent: 0
- })
- const tabs = ref([
- { key: 'all', label: '全部客户', count: 0 },
- // { key: 'prospect', label: '潜在客户', count: 812 },
- // { key: 'intent', label: '意向客户', count: 224 },
- // { key: 'deal', label: '成交客户', count: 156 },
- // { key: 'silent', label: '沉默客户', count: 55 }
- ])
- const customers = ref([])
- const customerTotal = ref()
- const params = ref({
- pageIndex: 1,
- pageSize: 10,
- blurry:null,
- country:null,
- })
- const getCustomer = () => {
- params.value.blurry = searchQuery.value
- getCustomerList(params.value).then(res => {
- customerStats.value.total = tabs.value[0].count = res.result.total
- customers.value = res.result.records
- })
- }
- getCustomer()
- const pageCurrentChange = (page) => {
- params.value.pageIndex = page
- getCustomer()
- }
- const regionValue = ref([])
- const getRegion = (value) => {
- params.value.country = value?value[1]:''
- getCustomer()
- }
- // const customers = ref([
- // {
- // id: 1, flag: '🇸🇦', name: 'Riyadh Tech Distribution', status: 'prospect', contact: 'Ahmed Al-Rashid',
- // role: '采购总监', location: '沙特·利雅得', industry: '固态硬盘进口商', annualPurchase: '年采购$2M+', score: 92,
- // email: 'ahmed@riyahtech.sa', phone: '+966 50 123 4567', website: 'www.riyahtech.sa',
- // totalDeals: 3, totalAmount: 4560000, lastContact: '3天前',
- // aiInsights: [
- // { type: 'purchase', level: 'high', icon: 'fas fa-shopping-cart', title: '采购意愿强烈', description: '近30天内访问官网5次,下载产品手册,建议重点跟进' },
- // { type: 'timing', level: 'medium', icon: 'fas fa-clock', title: '最佳联系时间', description: '根据历史数据分析,周二上午10点联系成功率最高' },
- // { type: 'decision', level: 'high', icon: 'fas fa-user-tie', title: '决策人已识别', description: 'Ahmed Al-Rashid 为采购决策人,已验证邮箱有效性' }
- // ],
- // interactions: [
- // { id: 1, type: 'email', action: '发送报价单', detail: 'SSD采购报价已发送,等待回复', time: '3天前' },
- // { id: 2, type: 'visit', action: '官网访问', detail: '浏览了企业级SSD产品页面,停留8分钟', time: '5天前' },
- // { id: 3, type: 'call', action: '电话沟通', detail: '确认了采购需求,数量约5000片', time: '1周前' },
- // { id: 4, type: 'email', action: '首次联系', detail: '通过LinkedIn找到联系人,发送介绍邮件', time: '2周前' }
- // ],
- // opportunities: [
- // { id: 1, name: 'SSD批量采购', amount: 850000, stage: '谈判中', statusClass: 'negotiation', statusLabel: '跟进中' }
- // ]
- // },
- // {
- // id: 2, flag: '🇦🇪', name: 'Dubai Storage Solutions', status: 'prospect', contact: 'Mohammed Khan',
- // role: 'CEO', location: '阿联酋·迪拜', industry: '电子分销商', annualPurchase: '年采购$1.5M', score: 88,
- // email: 'mohammed@dubaistorage.ae', phone: '+971 50 987 6543', website: 'www.dubaistorage.ae',
- // totalDeals: 2, totalAmount: 2800000, lastContact: '1天前',
- // aiInsights: [
- // { type: 'purchase', level: 'medium', icon: 'fas fa-chart-line', title: '采购潜力', description: '公司规模扩张中,预计明年需求增长30%' }
- // ],
- // interactions: [
- // { id: 1, type: 'meeting', action: '视频会议', detail: '讨论Q2采购计划,对NVMe SSD感兴趣', time: '1天前' }
- // ],
- // opportunities: []
- // },
- // {
- // id: 3, flag: '🇰🇼', name: 'Kuwait Data Systems', status: 'intent', contact: 'Faisal Al-Mutairi',
- // role: 'IT总监', location: '科威特', industry: '数据中心集成商', annualPurchase: '年采购$2.5M', score: 85,
- // email: 'faisal@kwdata.kw', phone: '+965 99 888 777', website: 'www.kwdata.kw',
- // totalDeals: 5, totalAmount: 6800000, lastContact: '今天',
- // aiInsights: [
- // { type: 'timing', level: 'high', icon: 'fas fa-fire', title: '紧急需求', description: '数据中心扩容项目急需采购,建议立即跟进' }
- // ],
- // interactions: [
- // { id: 1, type: 'email', action: '回复询价', detail: '确认了技术规格,要求提供样品', time: '今天' }
- // ],
- // opportunities: [
- // { id: 1, name: '数据中心扩容项目', amount: 1200000, stage: '方案报价', statusClass: 'proposal', statusLabel: '待报价' }
- // ]
- // },
- // {
- // id: 4, flag: '🇩🇪', name: 'Berlin Tech GmbH', status: 'deal', contact: 'Hans Mueller',
- // role: '采购经理', location: '德国·柏林', industry: '企业级存储', annualPurchase: '年采购$3M+', score: 95,
- // email: 'hans@berlintech.de', phone: '+49 30 12345678', website: 'www.berlintech.de',
- // totalDeals: 8, totalAmount: 12500000, lastContact: '2天前',
- // aiInsights: [],
- // interactions: [
- // { id: 1, type: 'order', action: '订单确认', detail: 'PO-2026-0315 已确认,金额¥680,000', time: '2天前' }
- // ],
- // opportunities: [
- // { id: 1, name: 'NVMe SSD批量采购', amount: 680000, stage: '已成交', statusClass: 'won', statusLabel: '已成交' }
- // ]
- // }
- // ])
- const filteredCustomers = computed(() => {
- let result = customers.value
- if (filterTab.value !== 'all') {
- result = result.filter(c => c.status === filterTab.value)
- }
- if (searchQuery.value) {
- const query = searchQuery.value.toLowerCase()
- result = result.filter(c =>
- c.name.toLowerCase().includes(query) ||
- c.contact.toLowerCase().includes(query) ||
- c.email?.toLowerCase().includes(query)
- )
- }
- return result
- })
- const formatNumber = (num) => num.toLocaleString()
- const getScoreClass = (score) => score >= 85 ? 'high' : score >= 70 ? 'medium' : 'low'
- const getStatusBg = (status) => ({
- prospect: 'rgba(139, 92, 246, 0.2)',
- intent: 'rgba(245, 158, 11, 0.2)',
- deal: 'rgba(16, 185, 129, 0.2)',
- silent: 'rgba(148, 163, 184, 0.2)'
- }[status] || 'rgba(148, 163, 184, 0.2)')
- const getStatusColor = (status) => ({
- prospect: 'var(--secondary)',
- intent: 'var(--warning)',
- deal: 'var(--success)',
- silent: 'var(--text-secondary)'
- }[status] || 'var(--text-secondary)')
- const getStatusLabel = (status) => ({
- prospect: '潜客',
- intent: '意向',
- deal: '成交',
- silent: '沉默'
- }[status] || status)
- const showCustomerDetail = (customer) => {
- selectedCustomer.value = customer
- showDetailModal.value = true
- }
- const sendEmail = (customer) => {
- showToastMessage(`正在发送邮件给 ${customer.contact}...`, 'success')
- }
- const createOpportunity = (customer) => {
- showDetailModal.value = false
- showToastMessage(`正在为 ${customer.name} 创建商机...`, 'success')
- }
- // 辅助函数:根据国家代码获取国旗
- const getFlagByCountry = (countryCode) => {
- const flagMap = {
- 'SA': '🇸🇦',
- 'AE': '🇦🇪',
- 'DE': '🇩🇪',
- 'FR': '🇫🇷',
- '': '🌐'
- }
- return flagMap[countryCode] || '🌐'
- }
- // 辅助函数:根据国家代码获取地区
- const getLocationByCountry = (countryCode) => {
- const locationMap = {
- 'SA': '沙特阿拉伯',
- 'AE': '阿联酋',
- 'DE': '德国',
- 'FR': '法国',
- '': '未填写'
- }
- return locationMap[countryCode] || '未填写'
- }
- const showToastMessage = (message, type = 'success') => {
- toastMessage.value = message
- toastType.value = type
- showToast.value = true
- setTimeout(() => showToast.value = false, 3000)
- }
- // ---------------- 暂时无用 ---------------------
- // 处理添加客户逻辑
- const handleAddCustomer = (customerData) => {
- // 模拟生成客户ID(实际项目中由后端生成)
- const newId = customers.value.length + 1
- // 构建完整的客户数据(补充默认值)
- const newCustomer = {
- id: newId,
- flag: getFlagByCountry(customerData.country),
- name: customerData.name,
- status: customerData.type,
- contact: customerData.contact,
- role: '未填写',
- location: getLocationByCountry(customerData.country),
- industry: '未填写',
- annualPurchase: '未填写',
- score: 80,
- email: customerData.email || '未填写',
- phone: customerData.phone || '未填写',
- website: '未填写',
- totalDeals: 0,
- totalAmount: 0,
- lastContact: '刚刚',
- aiInsights: [],
- interactions: [],
- opportunities: []
- }
- // 添加到客户列表
- customers.value.push(newCustomer)
- // 更新统计数据
- customerStats.total += 1
- if (customerData.type === 'prospect') customerStats.prospect += 1
- else if (customerData.type === 'intent') customerStats.intent += 1
- else if (customerData.type === 'deal') customerStats.deal += 1
- // 更新tabs计数
- tabs.forEach(tab => {
- if (tab.key === 'all') tab.count += 1
- else if (tab.key === customerData.type) tab.count += 1
- })
- // 显示提示
- showToastMessage('客户添加成功', 'success')
- }
- </script>
- <style scoped>
- .data-page { flex: 1; display: flex; flex-direction: column; padding: 24px; overflow-y: auto; background: var(--bg-dark); }
- .data-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px; }
- .data-title { font-size: 20px; font-weight: 600; }
- .stat-row { display: grid; grid-template-columns: repeat(5, 1fr); gap: 16px; margin-bottom: 24px; }
- .stat-card-lg { display: flex; align-items: center; gap: 14px; padding: 18px; background: var(--bg-card); border-radius: 14px; border: 1px solid var(--border); cursor: pointer; transition: all 0.2s; }
- .stat-card-lg:hover { border-color: var(--primary); }
- .stat-icon-lg { width: 48px; height: 48px; border-radius: 12px; display: flex; align-items: center; justify-content: center; font-size: 20px; background: rgba(99, 102, 241, 0.2); color: var(--primary); }
- .stat-icon-lg.green { background: rgba(16, 185, 129, 0.2); color: var(--success); }
- .stat-icon-lg.purple { background: rgba(139, 92, 246, 0.2); color: var(--secondary); }
- .stat-icon-lg.orange { background: rgba(245, 158, 11, 0.2); color: var(--warning); }
- .stat-content-lg h3 { font-size: 24px; font-weight: 700; }
- .stat-content-lg p { font-size: 12px; color: var(--text-secondary); }
- .search-box { position: relative; }
- .search-icon { position: absolute; left: 14px; top: 50%; transform: translateY(-50%); color: var(--text-muted); }
- .search-input { width: 100%; padding: 10px 14px 10px 40px; background: var(--bg-card); border: 1px solid var(--border); border-radius: 10px; color: var(--text-primary); font-size: 13px; }
- .search-input:focus { outline: none; border-color: var(--primary); }
- .form-select { background: var(--bg-card); border: 1px solid var(--border); color: var(--text-primary); }
- .form-select :deep(.el-input__wrapper){ background: var(--bg-card);}
- .customer-card { display: flex; align-items: center; gap: 16px; padding: 16px; background: var(--bg-card); border-radius: 12px; margin-bottom: 12px; border: 1px solid var(--border); cursor: pointer; transition: all 0.2s; }
- .customer-card:hover { border-color: var(--primary); }
- .customer-flag { font-size: 32px; }
- .customer-info { flex: 1; }
- .customer-header { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; }
- .customer-name { font-size: 15px; font-weight: 600; }
- .customer-status { font-size: 10px; padding: 2px 8px; border-radius: 10px; }
- .customer-contact { font-size: 12px; color: var(--text-secondary); margin-bottom: 6px; }
- .customer-meta { display: flex; gap: 16px; font-size: 11px; color: var(--text-muted); }
- .customer-score { text-align: center; margin-right: 20px; }
- .score-circle { width: 50px; height: 50px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 16px; font-weight: 700; }
- .score-circle.high { background: rgba(16, 185, 129, 0.2); color: var(--success); }
- .score-circle.medium { background: rgba(245, 158, 11, 0.2); color: var(--warning); }
- .score-circle.low { background: rgba(148, 163, 184, 0.2); color: var(--text-secondary); }
- .score-label { font-size: 10px; color: var(--text-muted); margin-top: 4px; }
- .customer-actions { display: flex; gap: 8px; }
- .modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 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; }
- .customer-detail-modal { max-width: 800px; }
- .modal-header { display: flex; justify-content: space-between; align-items: center; padding: 20px; border-bottom: 1px solid var(--border); }
- .modal-title { display: flex; align-items: center; gap: 12px; font-size: 16px; font-weight: 600; }
- .modal-title h3 { font-size: 16px; font-weight: 600; margin: 0; }
- .modal-title p { font-size: 12px; color: var(--text-secondary); margin: 4px 0 0 0; }
- .detail-flag { font-size: 32px; }
- .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-close:hover { background: var(--border); color: var(--text-primary); }
- .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-stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-bottom: 24px; }
- .detail-stat-card { background: var(--bg-hover); border-radius: 12px; padding: 16px; text-align: center; }
- .detail-stat-value { font-size: 24px; font-weight: 700; margin-bottom: 4px; }
- .detail-stat-value.high { color: var(--success); }
- .detail-stat-value.medium { color: var(--warning); }
- .detail-stat-value.success { color: var(--success); }
- .detail-stat-label { font-size: 12px; color: var(--text-secondary); }
- .detail-section { margin-bottom: 24px; }
- .detail-section h4 { font-size: 14px; font-weight: 600; margin-bottom: 12px; display: flex; align-items: center; gap: 8px; }
- .detail-section h4 i { color: var(--primary); }
- .contact-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; }
- .contact-item { display: flex; gap: 12px; padding: 12px; background: var(--bg-hover); border-radius: 10px; }
- .contact-item i { font-size: 16px; color: var(--primary); width: 20px; }
- .contact-label { font-size: 11px; color: var(--text-muted); margin-bottom: 2px; }
- .contact-value { font-size: 13px; font-weight: 500; }
- .contact-value.link { color: var(--primary); cursor: pointer; }
- .contact-sub { font-size: 11px; color: var(--text-secondary); }
- .ai-insight-cards { display: flex; flex-direction: column; gap: 10px; }
- .ai-insight-card { display: flex; gap: 12px; padding: 14px; background: var(--bg-hover); border-radius: 12px; border-left: 3px solid var(--primary); }
- .insight-icon { width: 36px; height: 36px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 14px; }
- .insight-icon.high { background: rgba(16, 185, 129, 0.2); color: var(--success); }
- .insight-icon.medium { background: rgba(245, 158, 11, 0.2); color: var(--warning); }
- .insight-title { font-size: 13px; font-weight: 600; margin-bottom: 4px; }
- .insight-desc { font-size: 11px; color: var(--text-secondary); line-height: 1.5; }
- .timeline { display: flex; flex-direction: column; gap: 12px; position: relative; padding-left: 24px; }
- .timeline::before { content: ''; position: absolute; left: 6px; top: 0; bottom: 0; width: 2px; background: var(--border); }
- .timeline-item { position: relative; }
- .timeline-dot { position: absolute; left: -20px; top: 4px; width: 12px; height: 12px; border-radius: 50%; background: var(--primary); border: 2px solid var(--bg-card); }
- .timeline-dot.email { background: var(--primary); }
- .timeline-dot.call { background: var(--success); }
- .timeline-dot.meeting { background: var(--warning); }
- .timeline-dot.visit { background: var(--secondary); }
- .timeline-dot.order { background: var(--success); }
- .timeline-content { padding: 12px; background: var(--bg-hover); border-radius: 10px; }
- .timeline-header { display: flex; justify-content: space-between; margin-bottom: 4px; }
- .timeline-action { font-size: 13px; font-weight: 500; }
- .timeline-time { font-size: 11px; color: var(--text-muted); }
- .timeline-detail { font-size: 12px; color: var(--text-secondary); }
- .related-opps { display: flex; flex-direction: column; gap: 10px; }
- .related-opp { display: flex; justify-content: space-between; align-items: center; padding: 12px; background: var(--bg-hover); border-radius: 10px; }
- .opp-name { font-size: 13px; font-weight: 500; margin-bottom: 2px; }
- .opp-meta { font-size: 11px; color: var(--text-secondary); }
- .opp-status { font-size: 10px; padding: 3px 8px; border-radius: 10px; }
- .opp-status.negotiation { background: rgba(99, 102, 241, 0.2); color: var(--primary); }
- .opp-status.proposal { background: rgba(245, 158, 11, 0.2); color: var(--warning); }
- .opp-status.won { background: rgba(16, 185, 129, 0.2); color: var(--success); }
- .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); }
- .toast.success i { 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); }
- </style>
|