261 lines
7.0 KiB
Vue
261 lines
7.0 KiB
Vue
<!-- 某个记录的跟进记录列表,目前主要用于 CRM 客户、商机等详情界面 -->
|
||
<template>
|
||
<!-- 操作栏 -->
|
||
<el-row class="mb-10px" justify="end">
|
||
<el-button @click="openForm">
|
||
<Icon class="mr-5px" icon="ep:edit" />
|
||
写跟进
|
||
</el-button>
|
||
</el-row>
|
||
<!-- 跟进记录时间线 -->
|
||
<el-timeline>
|
||
<el-timeline-item
|
||
v-for="(item, index) in list"
|
||
:key="item.id || index"
|
||
:timestamp="formatDate(item.createTime)"
|
||
placement="top"
|
||
>
|
||
<div class="el-timeline-right-content">
|
||
<div class="followup-meta mb-5px">
|
||
<span class="meta-label">跟进人:</span><el-tag class="mr-10px" type="success">{{ item.creatorName }}</el-tag>
|
||
<span class="meta-label">跟进类型:</span><dict-tag :type="DICT_TYPE.CRM_FOLLOW_UP_TYPE" :value="item.type" class="mr-10px" />
|
||
</div>
|
||
<span class="follow-row">跟进内容:{{ item.content }}</span>
|
||
<span v-if="item.nextTime" class="mb-5px follow-row text-gray-500">下次联系:{{ formatDate(item.nextTime) }}</span>
|
||
<!-- <span v-if="bizType === BizTypeEnum.CRM_CUSTOMER && item.contacts && item.contacts.length" class="ml-10px">
|
||
关联联系人:
|
||
<el-link
|
||
v-for="contact in item.contacts"
|
||
:key="`key-${contact.id}`"
|
||
:underline="false"
|
||
type="primary"
|
||
@click="openContactDetail(contact.id)"
|
||
class="ml-5px"
|
||
>
|
||
{{ contact.name }}
|
||
</el-link>
|
||
</span> -->
|
||
<!-- 图片直接展示 -->
|
||
<div v-if="item.picUrls && item.picUrls.length" class="followup-img-list follow-row mt-10px">
|
||
<el-image
|
||
v-for="url in item.picUrls"
|
||
:key="url"
|
||
:src="url"
|
||
style="width: 120px; height: 120px; object-fit: contain; border: 1px solid #eee; border-radius: 4px; margin-right: 8px;"
|
||
:preview-src-list="item.picUrls"
|
||
/>
|
||
</div>
|
||
<!-- 附件链接展示 -->
|
||
<div v-if="item.fileUrls && item.fileUrls.length" class="followup-file-list follow-row mt-5px">
|
||
<span style="font-weight: bold;">附件:</span>
|
||
<el-link
|
||
v-for="url in item.fileUrls"
|
||
:key="url"
|
||
:href="url"
|
||
target="_blank"
|
||
type="primary"
|
||
style="margin-right: 12px;"
|
||
>
|
||
{{ url.substring(url.lastIndexOf('/')+1) }}
|
||
</el-link>
|
||
</div>
|
||
</div>
|
||
<el-button link type="danger" class="mr-20px followup-delete-btn" @click="handleDelete(item.id)">
|
||
<Icon icon="ep:delete" class="mr-3px" /> 删除
|
||
</el-button>
|
||
<template #dot>
|
||
<span style="background-color: #67C23A;" class="dot-node-style">
|
||
{{ item.creatorName ? item.creatorName[0] : '' }}
|
||
</span>
|
||
</template>
|
||
</el-timeline-item>
|
||
</el-timeline>
|
||
<!-- 分页 -->
|
||
<Pagination
|
||
v-model:limit="queryParams.pageSize"
|
||
v-model:page="queryParams.pageNo"
|
||
:total="total"
|
||
@pagination="getList"
|
||
/>
|
||
|
||
<!-- 表单弹窗:添加/修改 -->
|
||
<FollowUpRecordForm ref="formRef" @success="getList" />
|
||
</template>
|
||
|
||
<script lang="ts" setup>
|
||
import { formatDate } from '@/utils/formatTime'
|
||
import { DICT_TYPE } from '@/utils/dict'
|
||
import { FollowUpRecordApi, FollowUpRecordVO } from '@/api/crm/followup'
|
||
import FollowUpRecordForm from './FollowUpRecordForm.vue'
|
||
import { BizTypeEnum } from '@/api/crm/permission'
|
||
|
||
/** 跟进记录列表 */
|
||
defineOptions({ name: 'FollowUpRecord' })
|
||
const props = defineProps<{
|
||
bizType: number
|
||
bizId: number
|
||
}>()
|
||
const message = useMessage() // 消息弹窗
|
||
const { t } = useI18n() // 国际化
|
||
|
||
const loading = ref(true) // 列表的加载中
|
||
const list = ref<FollowUpRecordVO[]>([]) // 列表的数据
|
||
const total = ref(0) // 列表的总页数
|
||
const queryParams = reactive({
|
||
pageNo: 1,
|
||
pageSize: 10,
|
||
bizType: 0,
|
||
bizId: 0
|
||
})
|
||
|
||
/** 查询列表 */
|
||
const getList = async () => {
|
||
loading.value = true
|
||
try {
|
||
const data = await FollowUpRecordApi.getFollowUpRecordPage(queryParams)
|
||
list.value = data.list
|
||
total.value = data.total
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
/** 添加/修改操作 */
|
||
const formRef = ref<InstanceType<typeof FollowUpRecordForm>>()
|
||
const openForm = () => {
|
||
formRef.value?.open(props.bizType, props.bizId)
|
||
}
|
||
|
||
/** 删除按钮操作 */
|
||
const handleDelete = async (id: number) => {
|
||
try {
|
||
// 删除的二次确认
|
||
await message.delConfirm()
|
||
// 发起删除
|
||
await FollowUpRecordApi.deleteFollowUpRecord(id)
|
||
message.success(t('common.delSuccess'))
|
||
// 刷新列表
|
||
await getList()
|
||
} catch {}
|
||
}
|
||
|
||
/** 打开联系人详情 */
|
||
const { push } = useRouter()
|
||
const openContactDetail = (id: number) => {
|
||
push({ name: 'CrmContactDetail', params: { id } })
|
||
}
|
||
|
||
/** 打开商机详情 */
|
||
const openBusinessDetail = (id: number) => {
|
||
push({ name: 'CrmBusinessDetail', params: { id } })
|
||
}
|
||
|
||
/** 文件查看弹窗 */
|
||
const fileDialogVisible = ref(false)
|
||
const fileDialogList = ref<string[]>([])
|
||
const fileDialogType = ref<'图片' | '附件'>('图片')
|
||
const fileDialogTitle = computed(() => fileDialogType.value === '图片' ? '查看图片' : '查看附件')
|
||
|
||
function openFileDialog(list: string[], type: '图片' | '附件') {
|
||
fileDialogList.value = list
|
||
fileDialogType.value = type
|
||
fileDialogVisible.value = true
|
||
}
|
||
|
||
watch(
|
||
() => props.bizId,
|
||
() => {
|
||
queryParams.bizType = props.bizType
|
||
queryParams.bizId = props.bizId
|
||
getList()
|
||
}
|
||
)
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
// 时间线样式参考 OperateLogV2
|
||
:deep(.el-timeline) {
|
||
margin: 10px 0 0 30px;
|
||
}
|
||
:deep(.el-timeline-item__wrapper) {
|
||
position: relative;
|
||
top: 0;
|
||
}
|
||
:deep(.el-timeline-item__timestamp) {
|
||
position: static !important;
|
||
display: block;
|
||
margin-bottom: 4px;
|
||
color: #999;
|
||
font-size: 13px;
|
||
left: unset;
|
||
top: unset;
|
||
text-align: left;
|
||
}
|
||
:deep(.el-timeline-right-content) {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
min-height: 30px;
|
||
padding: 10px;
|
||
background-color: #fff;
|
||
border-radius: 4px;
|
||
margin-bottom: 8px;
|
||
position: relative;
|
||
}
|
||
|
||
.followup-img-list {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
}
|
||
.followup-file-list {
|
||
margin-top: 5px;
|
||
}
|
||
.followup-meta {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
margin-bottom: 5px;
|
||
}
|
||
.follow-row {
|
||
margin-bottom: 5px;
|
||
color: #333;
|
||
font-size: 14px;
|
||
}
|
||
.dot-node-style {
|
||
position: absolute;
|
||
left: -5px;
|
||
display: flex;
|
||
width: 20px;
|
||
height: 20px;
|
||
font-size: 10px;
|
||
color: #fff;
|
||
border-radius: 50%;
|
||
justify-content: center;
|
||
align-items: center;
|
||
}
|
||
.meta-label {
|
||
margin-right: 4px;
|
||
font-weight: bold;
|
||
}
|
||
.followup-delete-btn {
|
||
color: #f56c6c !important;
|
||
font-weight: bold;
|
||
font-size: 14px;
|
||
transition: background 0.2s, color 0.2s;
|
||
border: none;
|
||
background: #fef0f0;
|
||
border-radius: 4px;
|
||
padding: 2px 10px;
|
||
margin: 10px 0 0 0;
|
||
&:hover {
|
||
background: #fef0f0;
|
||
color: #c0392b !important;
|
||
}
|
||
.icon {
|
||
font-size: 15px;
|
||
vertical-align: middle;
|
||
}
|
||
}
|
||
</style>
|