admin-vue3/src/views/crm/followup/index.vue

261 lines
7.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!-- 某个记录的跟进记录列表目前主要用于 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>