Files
shanxia2.0/web-admin/src/components/ContactSelector.vue
T
2026-07-20 10:50:26 +08:00

314 lines
9.8 KiB
Vue

<template>
<el-dialog
title="选择联系人"
v-model="dialogVisible"
width="1100px"
align-center
class="dialog-form-layout"
modal-class="order-module-dialog-mask"
:close-on-click-modal="false"
append-to-body
>
<!-- 提示信息 -->
<el-alert v-if="!customerId" title="请先选择客户" type="warning" :closable="false" class="mb-3" />
<!-- 查询表单 -->
<el-form v-if="customerId" :model="queryParams" ref="queryFormRef" :inline="true" class="mb-3 search-form-inline">
<el-form-item label="姓名" prop="contactName">
<el-input v-model="queryParams.contactName" placeholder="请输入联系人姓名" clearable @keyup.enter="handleQuery" style="width: 180px" />
</el-form-item>
<el-form-item label="手机号码" prop="mobile">
<el-input v-model="queryParams.mobile" placeholder="请输入手机号码" clearable @keyup.enter="handleQuery" style="width: 150px" />
</el-form-item>
<el-form-item label="职位" prop="position">
<el-input v-model="queryParams.position" placeholder="请输入职位" clearable @keyup.enter="handleQuery" style="width: 150px" />
</el-form-item>
<el-form-item class="form-buttons-right">
<el-button type="primary" :loading="loading" @click="handleQuery">
<el-icon><Search /></el-icon>搜索
</el-button>
<el-button :loading="loading" @click="resetQuery">
<el-icon><Refresh /></el-icon>重置
</el-button>
</el-form-item>
</el-form>
<!-- 联系人列表 -->
<div v-if="customerId" class="panel dialog-list-panel">
<div class="list-toolbar">
<div class="list-toolbar-row">
<div class="list-toolbar-selected">
<el-button class="btn-main" type="primary" @click="handleAddContact">
<el-icon><Plus /></el-icon>新增
</el-button>
</div>
</div>
</div>
<div class="list-body list-wrap dialog-list-body-only">
<el-table
ref="tableRef"
v-loading="loading"
:data="contactList"
class="list-table"
stripe
highlight-current-row
@current-change="handleCurrentChange"
:max-height="400"
>
<el-table-column type="index" label="序号" min-width="60" align="center" header-align="center" />
<el-table-column label="联系人姓名" prop="contactName" min-width="150" align="center" header-align="center" :show-overflow-tooltip="true">
<template #default="scope">
<div class="contact-name-cell">
<span>{{ scope.row.contactName }}</span>
<el-tag v-if="scope.row.isPrimary === 1" type="success" size="small">主要</el-tag>
<el-tag v-if="scope.row.isKey === 1" type="warning" size="small">决策人</el-tag>
</div>
</template>
</el-table-column>
<el-table-column label="性别" prop="gender" min-width="80" align="center" header-align="center">
<template #default="scope">
<DictTag dict-type="crm_contact_gender" :value="scope.row.gender" />
</template>
</el-table-column>
<el-table-column label="手机号码" prop="mobile" min-width="130" align="center" header-align="center" />
<el-table-column label="邮箱" prop="email" min-width="180" align="center" header-align="center" :show-overflow-tooltip="true" />
<el-table-column label="部门" prop="department" min-width="120" align="center" header-align="center" :show-overflow-tooltip="true" />
<el-table-column label="职位" prop="position" min-width="120" align="center" header-align="center" :show-overflow-tooltip="true" />
</el-table>
<ListPagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</div>
</div>
<!-- 当前选中联系人 -->
<template v-if="currentContact">
<el-divider content-position="left">当前选中联系人</el-divider>
<div class="selected-contact">
<el-descriptions :column="2" border size="small">
<el-descriptions-item label="联系人姓名">{{ currentContact.contactName }}</el-descriptions-item>
<el-descriptions-item label="性别">
<DictTag dict-type="crm_contact_gender" :value="currentContact.gender" />
</el-descriptions-item>
<el-descriptions-item label="手机号码">{{ currentContact.mobile || '-' }}</el-descriptions-item>
<el-descriptions-item label="邮箱">{{ currentContact.email || '-' }}</el-descriptions-item>
<el-descriptions-item label="部门">{{ currentContact.department || '-' }}</el-descriptions-item>
<el-descriptions-item label="职位">{{ currentContact.position || '-' }}</el-descriptions-item>
</el-descriptions>
</div>
</template>
<template #footer>
<div class="dialog-footer">
<el-button class="btn-ghost" @click="handleClose">
<el-icon><Close /></el-icon>取消
</el-button>
<el-button class="btn-main" type="primary" @click="handleConfirm" :disabled="!currentContact">
<el-icon><Check /></el-icon>确定
</el-button>
</div>
</template>
<ContactFormDialog ref="contactFormRef" @success="handleContactFormSuccess" />
</el-dialog>
</template>
<script setup lang="ts" name="ContactSelector">
import { useDictStore } from '@/store/modules/dict'
import DictTag from '@/components/DictTag/index.vue'
import ListPagination from '@/components/ListPagination/index.vue'
import ContactFormDialog from '@/views/crm/customer/components/ContactFormDialog.vue'
import { Check, Close, Plus, Refresh, Search } from '@element-plus/icons-vue'
import { ref, watch } from 'vue'
import type { ElFormInstance, TableInstance } from 'element-plus'
import { fetchContactList } from '@/api/crm/contact'
import type { ContactQuery, ContactVO } from '@/api/crm/customer'
interface Props {
visible: boolean
customerId?: number | string
}
const props = defineProps<Props>()
const emit = defineEmits(['update:visible', 'confirm'])
const dictStore = useDictStore()
const dialogVisible = ref(false)
watch(
() => props.visible,
async (val) => {
dialogVisible.value = val
if (val && props.customerId) {
await dictStore.loadAllDict()
resetQueryParams()
getList()
}
}
)
watch(
() => props.customerId,
(val) => {
if (val && dialogVisible.value) {
queryParams.value.customerId = val
getList()
}
}
)
watch(dialogVisible, (val) => {
emit('update:visible', val)
})
const queryParams = ref<ContactQuery>({
pageNum: 1,
pageSize: 20,
customerId: props.customerId,
contactName: undefined,
mobile: undefined,
position: undefined
})
const contactList = ref<ContactVO[]>([])
const loading = ref(false)
const total = ref(0)
const currentContact = ref<ContactVO | null>(null)
const queryFormRef = ref<ElFormInstance>()
const tableRef = ref<TableInstance>()
const contactFormRef = ref<InstanceType<typeof ContactFormDialog>>()
const resetQueryParams = () => {
queryParams.value.pageNum = 1
queryParams.value.contactName = undefined
queryParams.value.mobile = undefined
queryParams.value.position = undefined
queryFormRef.value?.resetFields()
}
const getList = async (selectId?: string | number) => {
if (!props.customerId) {
contactList.value = []
total.value = 0
return
}
loading.value = true
try {
queryParams.value.customerId = props.customerId
const res = await fetchContactList({
page: queryParams.value.pageNum,
pageSize: queryParams.value.pageSize,
customerId: String(props.customerId),
contactName: queryParams.value.contactName?.trim() || undefined,
mobile: queryParams.value.mobile?.trim() || undefined,
position: queryParams.value.position?.trim() || undefined
})
contactList.value = res.list || res.rows || []
total.value = res.total ?? 0
if (selectId) {
selectContactById(selectId)
}
} catch (error) {
console.error('获取联系人列表失败:', error)
} finally {
loading.value = false
}
}
const selectContactById = (id?: string | number) => {
if (!id) return
const row = contactList.value.find((item) => String(item.id) === String(id))
if (!row) return
currentContact.value = row
tableRef.value?.setCurrentRow(row)
}
const handleAddContact = () => {
if (!props.customerId) return
contactFormRef.value?.open(undefined, props.customerId)
}
const handleContactFormSuccess = async (payload?: { id?: string | number }) => {
resetQueryParams()
queryParams.value.pageNum = 1
await getList(payload?.id)
}
const handleQuery = () => {
queryParams.value.pageNum = 1
getList()
}
const resetQuery = () => {
resetQueryParams()
getList()
}
watchDebounced(
() => ({
contactName: queryParams.value.contactName,
mobile: queryParams.value.mobile,
position: queryParams.value.position
}),
() => {
if (!props.customerId) return
queryParams.value.pageNum = 1
getList()
},
{ debounce: 300, deep: true }
)
const handleCurrentChange = (row: ContactVO) => {
currentContact.value = row
}
const handleConfirm = () => {
emit('confirm', currentContact.value)
handleClose()
}
const handleClose = () => {
currentContact.value = null
dialogVisible.value = false
}
</script>
<style scoped>
.selected-contact {
padding: 10px;
background-color: #f5f7fa;
border-radius: 4px;
}
.mb-3 {
margin-bottom: 16px;
}
.mt-3 {
margin-top: 16px;
}
.search-form-inline {
display: flex;
flex-wrap: wrap;
}
.form-buttons-right {
margin-left: auto !important;
}
.contact-name-cell {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
gap: 5px;
}
</style>