|
@@ -2,19 +2,19 @@
|
|
|
<div v-if="showCustomerPreview" class="modal-overlay" @click.self="showCustomerPreview = false">
|
|
<div v-if="showCustomerPreview" class="modal-overlay" @click.self="showCustomerPreview = false">
|
|
|
<div class="modal" style="max-width: 860px;">
|
|
<div class="modal" style="max-width: 860px;">
|
|
|
<div class="modal-header">
|
|
<div class="modal-header">
|
|
|
- <div class="modal-title"><i class="fas fa-user-tag"></i>客户详情</div>
|
|
|
|
|
|
|
+ <div class="modal-title"><i class="fas fa-user-tag"></i>{{ modalType == 'add' ? '精确添加' : '客户详情' }}</div>
|
|
|
<button class="modal-close" @click="showCustomerPreview = false"><i class="fas fa-times"></i></button>
|
|
<button class="modal-close" @click="showCustomerPreview = false"><i class="fas fa-times"></i></button>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="modal-body">
|
|
<div class="modal-body">
|
|
|
<div class="list-header">
|
|
<div class="list-header">
|
|
|
- <el-form>
|
|
|
|
|
- <el-form-item label="">
|
|
|
|
|
|
|
+ <el-form ref="customerFormRef" :model="customerParams">
|
|
|
|
|
+ <el-form-item label="" prop="blurry">
|
|
|
<el-input v-model="customerParams.blurry" :prefix-icon="Search" placeholder="搜索客户信息"></el-input>
|
|
<el-input v-model="customerParams.blurry" :prefix-icon="Search" placeholder="搜索客户信息"></el-input>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
- <el-form-item label="">
|
|
|
|
|
|
|
+ <el-form-item label="" prop="liaisonName">
|
|
|
<el-input v-model="customerParams.liaisonName" :prefix-icon="Search" placeholder="搜索联系人"></el-input>
|
|
<el-input v-model="customerParams.liaisonName" :prefix-icon="Search" placeholder="搜索联系人"></el-input>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
- <el-form-item label="">
|
|
|
|
|
|
|
+ <el-form-item label="" prop="customerStatusDictValue">
|
|
|
<el-select v-model="customerParams.customerStatusDictValue" clearable placeholder="客户状态">
|
|
<el-select v-model="customerParams.customerStatusDictValue" clearable placeholder="客户状态">
|
|
|
<el-option label="暂无需求" value="1" />
|
|
<el-option label="暂无需求" value="1" />
|
|
|
<el-option label="潜在客户" value="2" />
|
|
<el-option label="潜在客户" value="2" />
|
|
@@ -28,11 +28,11 @@
|
|
|
<el-option label="暂时搁置" value="66" />
|
|
<el-option label="暂时搁置" value="66" />
|
|
|
</el-select>
|
|
</el-select>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
- <el-form-item label="">
|
|
|
|
|
|
|
+ <el-form-item label="" prop="regionValue">
|
|
|
<el-cascader style="width: 100%" placeholder="区域名称" :options="region"
|
|
<el-cascader style="width: 100%" placeholder="区域名称" :options="region"
|
|
|
v-model="regionValue" clearable filterable @change="getRegion" />
|
|
v-model="regionValue" clearable filterable @change="getRegion" />
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
- <el-form-item label="">
|
|
|
|
|
|
|
+ <el-form-item label="" prop="customerTypeDictValue">
|
|
|
<el-select v-model="customerParams.customerTypeDictValue" clearable placeholder="客户类型">
|
|
<el-select v-model="customerParams.customerTypeDictValue" clearable placeholder="客户类型">
|
|
|
<el-option label="供应厂商" value="4" />
|
|
<el-option label="供应厂商" value="4" />
|
|
|
<el-option label="代理合作" value="5" />
|
|
<el-option label="代理合作" value="5" />
|
|
@@ -44,7 +44,7 @@
|
|
|
<el-option label="协助项目" value="11" />
|
|
<el-option label="协助项目" value="11" />
|
|
|
</el-select>
|
|
</el-select>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
- <el-form-item label="">
|
|
|
|
|
|
|
+ <el-form-item label="" prop="customerLevelDictValue">
|
|
|
<el-select v-model="customerParams.customerLevelDictValue" clearable placeholder="客户等级">
|
|
<el-select v-model="customerParams.customerLevelDictValue" clearable placeholder="客户等级">
|
|
|
<el-option label="A级" value="1" />
|
|
<el-option label="A级" value="1" />
|
|
|
<el-option label="B级" value="2" />
|
|
<el-option label="B级" value="2" />
|
|
@@ -54,12 +54,53 @@
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
</el-form>
|
|
</el-form>
|
|
|
<div class="btn-div">
|
|
<div class="btn-div">
|
|
|
- <button class="btn btn-primary" @click="getCustomerList">
|
|
|
|
|
|
|
+ <button class="btn btn-primary" @click="searchCustomer">
|
|
|
<el-icon><Search /></el-icon>搜索</button>
|
|
<el-icon><Search /></el-icon>搜索</button>
|
|
|
<button class="btn btn-secondary" @click="resetCustomerParams">
|
|
<button class="btn btn-secondary" @click="resetCustomerParams">
|
|
|
<el-icon><Refresh /></el-icon>重置</button>
|
|
<el-icon><Refresh /></el-icon>重置</button>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ <div class="list-body">
|
|
|
|
|
+ <template v-if="customers && customers.length">
|
|
|
|
|
+ <div class="customer-list">
|
|
|
|
|
+ <div v-for="customer in customers" :key="customer.id" class="customer-card">
|
|
|
|
|
+ <div class="customer-card-left">
|
|
|
|
|
+ <div class="customer-logo">{{ customer.logoText || customer.customerName.charAt(0) }}</div>
|
|
|
|
|
+ <div class="customer-meta">
|
|
|
|
|
+ <div class="customer-name-row">
|
|
|
|
|
+ <div class="customer-name">{{ customer.customerName }}</div>
|
|
|
|
|
+ <span class="customer-badge">{{ customer.tag || '潜客' }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="customer-subtitle">{{ customer.role || '采购总监' }} · {{ customer.company }}</div>
|
|
|
|
|
+ <div class="customer-info-row">
|
|
|
|
|
+ <span><i class="fas fa-map-marker-alt"></i> {{ customer.continent }} - {{ customer.country }}</span>
|
|
|
|
|
+ <span><i class="fas fa-dollar-sign"></i> {{ customer.annualPurchase }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="customer-card-right">
|
|
|
|
|
+ <div class="card-right">
|
|
|
|
|
+ <div class="customer-score">{{ customer.score }}</div>
|
|
|
|
|
+ <div class="customer-score-label">意向分</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="right-btn">
|
|
|
|
|
+ <el-icon v-if="modalType == 'add'" @click="addCustomer(customer)"><Plus /></el-icon>
|
|
|
|
|
+ <el-icon v-else @click="deleteCustomer(customer)"><Delete /></el-icon>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <el-pagination
|
|
|
|
|
+ :total="total"
|
|
|
|
|
+ style="text-align: right;"
|
|
|
|
|
+ :current-page="customerParams.pageIndex"
|
|
|
|
|
+ :page-size="customerParams.pageSize"
|
|
|
|
|
+ layout="total, prev, pager, next"
|
|
|
|
|
+ @current-change="customerCurrentChange">
|
|
|
|
|
+ </el-pagination>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <div v-else class="empty-state">暂无客户数据</div>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
@@ -67,15 +108,42 @@
|
|
|
|
|
|
|
|
<script setup>
|
|
<script setup>
|
|
|
import { ref } from 'vue'
|
|
import { ref } from 'vue'
|
|
|
- import { Search, Refresh } from '@element-plus/icons-vue'
|
|
|
|
|
|
|
+ import { Search, Refresh, Plus, Delete } from '@element-plus/icons-vue'
|
|
|
import { region } from '@/utils/region';
|
|
import { region } from '@/utils/region';
|
|
|
|
|
+ import { getTradeCustomer } from '@/api/marketing'
|
|
|
|
|
+
|
|
|
|
|
+ const emits = defineEmits(['add', 'delete'])
|
|
|
|
|
|
|
|
|
|
+ const customerFormRef = ref()
|
|
|
const showCustomerPreview = ref(false)
|
|
const showCustomerPreview = ref(false)
|
|
|
- const open = () => {
|
|
|
|
|
|
|
+ const modalType = ref('')
|
|
|
|
|
+ const allData = ref([])
|
|
|
|
|
+ const open = (type, data) => {
|
|
|
|
|
+ customers.value = []
|
|
|
|
|
+ total.value = 0
|
|
|
|
|
+ modalType.value = type
|
|
|
|
|
+ regionValue.value = []
|
|
|
|
|
+ customerParams.value = {
|
|
|
|
|
+ pageIndex: 1,
|
|
|
|
|
+ pageSize: 10,
|
|
|
|
|
+ blurry: '',
|
|
|
|
|
+ country: '',
|
|
|
|
|
+ customerStatusDictValue: '',
|
|
|
|
|
+ customerTypeDictValue: '',
|
|
|
|
|
+ customerLevelDictValue: '',
|
|
|
|
|
+ liaisonName: ''
|
|
|
|
|
+ }
|
|
|
|
|
+ if(type == 'detail'){
|
|
|
|
|
+ allData.value = data
|
|
|
|
|
+ customers.value = data.slice(0, 10)
|
|
|
|
|
+ total.value = data.length
|
|
|
|
|
+ }
|
|
|
showCustomerPreview.value = true
|
|
showCustomerPreview.value = true
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const customerParams = ref({
|
|
const customerParams = ref({
|
|
|
|
|
+ pageIndex: 1,
|
|
|
|
|
+ pageSize: 10,
|
|
|
blurry: '',
|
|
blurry: '',
|
|
|
country: '',
|
|
country: '',
|
|
|
customerStatusDictValue: '',
|
|
customerStatusDictValue: '',
|
|
@@ -88,6 +156,65 @@
|
|
|
customerParams.value.country = value ? value[1] : ''
|
|
customerParams.value.country = value ? value[1] : ''
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ const customers = ref([])
|
|
|
|
|
+ const total = ref(0)
|
|
|
|
|
+ const getCustomerData = () => {
|
|
|
|
|
+ getTradeCustomer(customerParams.value).then(res => {
|
|
|
|
|
+ customers.value = res.result.records || []
|
|
|
|
|
+ total.value = res.result.total || 0
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ const customerCurrentChange = (page) => {
|
|
|
|
|
+ customerParams.value.pageIndex = page
|
|
|
|
|
+ if(modalType.value == 'add'){
|
|
|
|
|
+ getCustomerData()
|
|
|
|
|
+ }else{
|
|
|
|
|
+ customers.value = allData.value.slice((page - 1) * customerParams.value.pageSize, page * customerParams.value.pageSize)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const searchCustomer = () => {
|
|
|
|
|
+ if(modalType.value == 'add'){
|
|
|
|
|
+ customerParams.value.pageIndex = 1
|
|
|
|
|
+ getCustomerData()
|
|
|
|
|
+ }else{
|
|
|
|
|
+ const filterData = allData.value.filter(item => {
|
|
|
|
|
+ const blurryMatch = customerParams.value.blurry ? (item.customerName.includes(customerParams.value.blurry) || item.company.includes(customerParams.value.blurry) || item.email.includes(customerParams.value.blurry)) : true
|
|
|
|
|
+ const liaisonMatch = customerParams.value.liaisonName ? (item.liaisonName && item.liaisonName.includes(customerParams.value.liaisonName)) : true
|
|
|
|
|
+ const statusMatch = customerParams.value.customerStatusDictValue ? (item.customerStatusDictValue == customerParams.value.customerStatusDictValue) : true
|
|
|
|
|
+ const typeMatch = customerParams.value.customerTypeDictValue ? (item.customerTypeDictValue == customerParams.value.customerTypeDictValue) : true
|
|
|
|
|
+ const levelMatch = customerParams.value.customerLevelDictValue ? (item.customerLevelDictValue == customerParams.value.customerLevelDictValue) : true
|
|
|
|
|
+ const countryMatch = customerParams.value.country ? (item.country == customerParams.value.country) : true
|
|
|
|
|
+ return blurryMatch && liaisonMatch && statusMatch && typeMatch && levelMatch && countryMatch
|
|
|
|
|
+ })
|
|
|
|
|
+ customers.value = filterData.slice(0, 10)
|
|
|
|
|
+ total.value = filterData.length
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const resetCustomerParams = () => {
|
|
|
|
|
+ customerFormRef.value.resetFields()
|
|
|
|
|
+ regionValue.value = []
|
|
|
|
|
+ if(modalType.value == 'add'){
|
|
|
|
|
+ customers.value = []
|
|
|
|
|
+ total.value = 0
|
|
|
|
|
+ }else{
|
|
|
|
|
+ customers.value = allData.value.slice(0, 10)
|
|
|
|
|
+ total.value = allData.value.length
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const deleteCustomer = (data) => {
|
|
|
|
|
+ customers.value = customers.value.filter(item => item.id != data.id)
|
|
|
|
|
+ allData.value = allData.value.filter(item => item.id != data.id)
|
|
|
|
|
+ total.value = allData.value.length
|
|
|
|
|
+ emits('delete', data)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const addCustomer = (data) => {
|
|
|
|
|
+ emits('add', data)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
defineExpose({
|
|
defineExpose({
|
|
|
open
|
|
open
|
|
|
})
|
|
})
|
|
@@ -115,6 +242,7 @@
|
|
|
.list-header{
|
|
.list-header{
|
|
|
display: flex;
|
|
display: flex;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
|
|
+ padding-right: 20px;
|
|
|
}
|
|
}
|
|
|
.el-form{
|
|
.el-form{
|
|
|
flex: 1;
|
|
flex: 1;
|
|
@@ -137,4 +265,176 @@
|
|
|
height: 32px;
|
|
height: 32px;
|
|
|
padding: 10px 13px;
|
|
padding: 10px 13px;
|
|
|
}
|
|
}
|
|
|
|
|
+ .customer-list {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ padding-right: 20px;
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ overflow: auto;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .customer-card {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 18px;
|
|
|
|
|
+ padding: 18px 18px;
|
|
|
|
|
+ background: var(--bg-hover);
|
|
|
|
|
+ border: 1px solid var(--border);
|
|
|
|
|
+ border-radius: 16px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .customer-card-left {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 14px;
|
|
|
|
|
+ min-width: 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .customer-logo {
|
|
|
|
|
+ width: 52px;
|
|
|
|
|
+ height: 52px;
|
|
|
|
|
+ border-radius: 14px;
|
|
|
|
|
+ background: rgba(56, 189, 248, 0.15);
|
|
|
|
|
+ color: var(--primary);
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ font-size: 18px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .customer-meta {
|
|
|
|
|
+ min-width: 0;
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .customer-name-row {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+ margin-bottom: 6px;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .customer-name {
|
|
|
|
|
+ font-size: 15px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ color: var(--text-primary);
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ text-overflow: ellipsis;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .customer-badge {
|
|
|
|
|
+ display: inline-flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ padding: 2px 10px;
|
|
|
|
|
+ border-radius: 999px;
|
|
|
|
|
+ background: rgba(59, 130, 246, 0.12);
|
|
|
|
|
+ color: var(--primary);
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .customer-subtitle {
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ color: var(--text-secondary);
|
|
|
|
|
+ margin-bottom: 10px;
|
|
|
|
|
+ line-height: 1.5;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .customer-info-row {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ color: var(--text-secondary);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .customer-info-row span {
|
|
|
|
|
+ display: inline-flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 6px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .customer-info-row i {
|
|
|
|
|
+ width: 14px;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ color: var(--text-secondary);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .customer-card-right {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .card-right{
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ min-width: 92px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .customer-score {
|
|
|
|
|
+ width: 54px;
|
|
|
|
|
+ height: 54px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ background: rgba(34, 197, 94, 0.12);
|
|
|
|
|
+ color: var(--success);
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .customer-score-label {
|
|
|
|
|
+ margin-top: 8px;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ color: #64748B;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .right-btn{
|
|
|
|
|
+ width: 42px;
|
|
|
|
|
+ height: 28px;
|
|
|
|
|
+ background: #1E293B;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ border: 1px solid #485569;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ line-height: 28px;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .empty-state {
|
|
|
|
|
+ padding: 60px 0;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ color: var(--text-secondary);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .el-pagination{
|
|
|
|
|
+ margin-top: 12px;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ }
|
|
|
|
|
+ .el-pagination :deep(button), .el-pagination :deep(li){
|
|
|
|
|
+ background: transparent!important;
|
|
|
|
|
+ color: #FFF;
|
|
|
|
|
+ }
|
|
|
|
|
+ .el-pagination :deep(li.is-active){
|
|
|
|
|
+ color: var(--primary);
|
|
|
|
|
+ }
|
|
|
|
|
+ .modal-body{
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ padding-right: 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ .list-body{
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ margin-top: 16px;
|
|
|
|
|
+ }
|
|
|
</style>
|
|
</style>
|