modified: .env

modified:   src/components/OperateLogV2/src/OperateLogV2.vue
	modified:   src/views/crm/contact/components/ContactListModal.vue
	modified:   src/views/crm/contact/detail/index.vue
	modified:   src/views/crm/followup/FollowUpRecordForm.vue
	modified:   src/views/crm/followup/index.vue
pull/853/head
wersd 2025-05-25 19:11:27 +08:00
parent 68af678655
commit 627b6ea38e
6 changed files with 169 additions and 90 deletions

4
.env
View File

@ -1,5 +1,5 @@
# 标题 # 标题
VITE_APP_TITLE=芋道管理系统 VITE_APP_TITLE=速捷充电管理系统
# 项目本地运行端口号 # 项目本地运行端口号
VITE_PORT=80 VITE_PORT=80
@ -11,7 +11,7 @@ VITE_OPEN=true
VITE_APP_TENANT_ENABLE=true VITE_APP_TENANT_ENABLE=true
# 验证码的开关 # 验证码的开关
VITE_APP_CAPTCHA_ENABLE=true VITE_APP_CAPTCHA_ENABLE=false
# 文档地址的开关 # 文档地址的开关
VITE_APP_DOCALERT_ENABLE=true VITE_APP_DOCALERT_ENABLE=true

View File

@ -58,7 +58,7 @@ const getUserTypeColor = (type: number) => {
<style lang="scss" scoped> <style lang="scss" scoped>
// 线 // 线
:deep(.el-timeline) { :deep(.el-timeline) {
margin: 10px 0 0 110px; margin: 10px 0 0 150px;
.el-timeline-item__wrapper { .el-timeline-item__wrapper {
position: relative; position: relative;

View File

@ -43,8 +43,11 @@
:data="list" :data="list"
:show-overflow-tooltip="true" :show-overflow-tooltip="true"
:stripe="true" :stripe="true"
row-key="id"
highlight-current-row
@current-change="handleCurrentChange"
> >
<el-table-column type="selection" width="55" /> <el-table-column type="radio" width="55" />
<el-table-column align="center" fixed="left" label="姓名" prop="name"> <el-table-column align="center" fixed="left" label="姓名" prop="name">
<template #default="scope"> <template #default="scope">
<el-link :underline="false" type="primary" @click="openDetail(scope.row.id)"> <el-link :underline="false" type="primary" @click="openDetail(scope.row.id)">
@ -143,13 +146,13 @@ const openForm = () => {
/** 关联联系人提交 */ /** 关联联系人提交 */
const emit = defineEmits(['success']) // success const emit = defineEmits(['success']) // success
const contactRef = ref() const contactRef = ref()
const selectedRow = ref<ContactApi.ContactVO | null>(null)
const submitForm = async () => { const submitForm = async () => {
const contactIds = contactRef.value.getSelectionRows().map((row: ContactApi.ContactVO) => row.id) if (!selectedRow.value) {
if (contactIds.length === 0) {
return message.error('未选择联系人') return message.error('未选择联系人')
} }
dialogVisible.value = false dialogVisible.value = false
emit('success', contactIds, contactRef.value.getSelectionRows()) emit('success', [selectedRow.value.id], [selectedRow.value])
} }
/** 打开联系人详情 */ /** 打开联系人详情 */
@ -157,4 +160,9 @@ const { push } = useRouter()
const openDetail = (id: number) => { const openDetail = (id: number) => {
push({ name: 'CrmContactDetail', params: { id } }) push({ name: 'CrmContactDetail', params: { id } })
} }
/** 单选框变化事件 */
const handleCurrentChange = (val: ContactApi.ContactVO | null) => {
selectedRow.value = val
}
</script> </script>

View File

@ -38,7 +38,6 @@ import { useTagsViewStore } from '@/store/modules/tagsView'
import * as ContactApi from '@/api/crm/contact' import * as ContactApi from '@/api/crm/contact'
import ContactDetailsHeader from '@/views/crm/contact/detail/ContactDetailsHeader.vue' import ContactDetailsHeader from '@/views/crm/contact/detail/ContactDetailsHeader.vue'
import ContactDetailsInfo from '@/views/crm/contact/detail/ContactDetailsInfo.vue' import ContactDetailsInfo from '@/views/crm/contact/detail/ContactDetailsInfo.vue'
import BusinessList from '@/views/crm/business/components/BusinessList.vue' //
import PermissionList from '@/views/crm/permission/components/PermissionList.vue' // import PermissionList from '@/views/crm/permission/components/PermissionList.vue' //
import { BizTypeEnum } from '@/api/crm/permission' import { BizTypeEnum } from '@/api/crm/permission'
import { OperateLogVO } from '@/api/system/operatelog' import { OperateLogVO } from '@/api/system/operatelog'

View File

@ -50,20 +50,11 @@
<el-form-item label="关联联系人" prop="contactIds"> <el-form-item label="关联联系人" prop="contactIds">
<el-button @click="handleOpenContact"> <el-button @click="handleOpenContact">
<Icon class="mr-5px" icon="ep:plus" /> <Icon class="mr-5px" icon="ep:plus" />
添加联系人 关联联系人
</el-button> </el-button>
<FollowUpRecordContactForm :contacts="formData.contacts" /> <FollowUpRecordContactForm :contacts="formData.contacts" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24" v-if="formData.bizType == BizTypeEnum.CRM_CUSTOMER">
<el-form-item label="关联商机" prop="businessIds">
<el-button @click="handleOpenBusiness">
<Icon class="mr-5px" icon="ep:plus" />
添加商机
</el-button>
<FollowUpRecordBusinessForm :businesses="formData.businesses" />
</el-form-item>
</el-col>
</el-row> </el-row>
</el-form> </el-form>
<template #footer> <template #footer>
@ -155,11 +146,10 @@ const handleOpenContact = () => {
contactTableSelectRef.value?.open() contactTableSelectRef.value?.open()
} }
const handleAddContact = (contactId: [], newContacts: ContactApi.ContactVO[]) => { const handleAddContact = (contactId: [], newContacts: ContactApi.ContactVO[]) => {
newContacts.forEach((contact) => { if (newContacts.length > 0) {
if (!formData.value.contacts.some((item) => item.id === contact.id)) { //
formData.value.contacts.push(contact) formData.value.contacts = [newContacts[0]]
} }
})
} }
/** 关联商机 */ /** 关联商机 */

View File

@ -7,39 +7,25 @@
写跟进 写跟进
</el-button> </el-button>
</el-row> </el-row>
<!-- 列表 --> <!-- 跟进记录时间线 -->
<ContentWrap> <el-timeline>
<el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true"> <el-timeline-item
<el-table-column v-for="(item, index) in list"
:formatter="dateFormatter" :key="item.id || index"
align="center" :timestamp="formatDate(item.createTime)"
label="创建时间" placement="top"
prop="createTime" >
width="180px" <div class="el-timeline-right-content">
/> <div class="followup-meta mb-5px">
<el-table-column align="center" label="跟进人" prop="creatorName" /> <span class="meta-label">跟进人</span><el-tag class="mr-10px" type="success">{{ item.creatorName }}</el-tag>
<el-table-column align="center" label="跟进类型" prop="type"> <span class="meta-label">跟进类型</span><dict-tag :type="DICT_TYPE.CRM_FOLLOW_UP_TYPE" :value="item.type" class="mr-10px" />
<template #default="scope"> </div>
<dict-tag :type="DICT_TYPE.CRM_FOLLOW_UP_TYPE" :value="scope.row.type" /> <span class="follow-row">跟进内容{{ item.content }}</span>
</template> <span v-if="item.nextTime" class="mb-5px follow-row text-gray-500">{{ formatDate(item.nextTime) }}</span>
</el-table-column> <!-- <span v-if="bizType === BizTypeEnum.CRM_CUSTOMER && item.contacts && item.contacts.length" class="ml-10px">
<el-table-column align="center" label="跟进内容" prop="content" /> 关联联系人
<el-table-column
:formatter="dateFormatter"
align="center"
label="下次联系时间"
prop="nextTime"
width="180px"
/>
<el-table-column
align="center"
label="关联联系人"
prop="contactIds"
v-if="bizType === BizTypeEnum.CRM_CUSTOMER"
>
<template #default="scope">
<el-link <el-link
v-for="contact in scope.row.contacts" v-for="contact in item.contacts"
:key="`key-${contact.id}`" :key="`key-${contact.id}`"
:underline="false" :underline="false"
type="primary" type="primary"
@ -48,47 +34,56 @@
> >
{{ contact.name }} {{ contact.name }}
</el-link> </el-link>
</template> </span> -->
</el-table-column> <!-- 图片直接展示 -->
<el-table-column align="center" label="图片/附件"> <div v-if="item.picUrls && item.picUrls.length" class="followup-img-list follow-row mt-10px">
<template #default="scope"> <el-image
<el-button link type="primary" v-if="scope.row.picUrls && scope.row.picUrls.length" @click="openFileDialog(scope.row.picUrls, '图片')"></el-button> v-for="url in item.picUrls"
<el-button link type="primary" v-if="scope.row.fileUrls && scope.row.fileUrls.length" @click="openFileDialog(scope.row.fileUrls, '附件')"></el-button> :key="url"
</template> :src="url"
</el-table-column> style="width: 120px; height: 120px; object-fit: contain; border: 1px solid #eee; border-radius: 4px; margin-right: 8px;"
<el-table-column align="center" label="操作"> :preview-src-list="item.picUrls"
<template #default="scope"> />
<el-button link type="danger" @click="handleDelete(scope.row.id)"> </el-button> </div>
</template> <!-- 附件链接展示 -->
</el-table-column> <div v-if="item.fileUrls && item.fileUrls.length" class="followup-file-list follow-row mt-5px">
</el-table> <span style="font-weight: bold;">附件:</span>
<!-- 分页 --> <el-link
<Pagination v-for="url in item.fileUrls"
v-model:limit="queryParams.pageSize" :key="url"
v-model:page="queryParams.pageNo" :href="url"
:total="total" target="_blank"
@pagination="getList" type="primary"
/> style="margin-right: 12px;"
</ContentWrap> >
{{ 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" /> <FollowUpRecordForm ref="formRef" @success="getList" />
<!-- 文件查看弹窗 -->
<el-dialog v-model="fileDialogVisible" :title="fileDialogTitle" width="600px">
<div v-if="fileDialogType === '图片'">
<div style="display: flex; flex-wrap: wrap; gap: 12px;">
<el-image v-for="url in fileDialogList" :key="url" :src="url" style="width: 120px; height: 120px; object-fit: contain; border: 1px solid #eee; border-radius: 4px;" :preview-src-list="fileDialogList" />
</div>
</div>
<div v-else>
<el-link v-for="url in fileDialogList" :key="url" :href="url" target="_blank" type="primary" style="display: block; margin-bottom: 8px;">{{ url.substring(url.lastIndexOf('/')+1) }}</el-link>
</div>
</el-dialog>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { dateFormatter } from '@/utils/formatTime' import { formatDate } from '@/utils/formatTime'
import { DICT_TYPE } from '@/utils/dict' import { DICT_TYPE } from '@/utils/dict'
import { FollowUpRecordApi, FollowUpRecordVO } from '@/api/crm/followup' import { FollowUpRecordApi, FollowUpRecordVO } from '@/api/crm/followup'
import FollowUpRecordForm from './FollowUpRecordForm.vue' import FollowUpRecordForm from './FollowUpRecordForm.vue'
@ -176,3 +171,90 @@ watch(
} }
) )
</script> </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>