Files
art-design/src/views/crmpc/highseas/index.vue
T
mzl92 2c6a2dafec feat: 客户详情内容图片支持点击放大查看
desc_collect 富文本中的图片(如WhatsApp导入截图)点击后用
el-image-viewer 全屏放大(支持滚轮缩放/旋转,点击遮罩关闭),
图片加 cursor:zoom-in 提示可点击。
覆盖:业务端客户列表/公海、管理后台客户/公海/回收站/审核,共6处。

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-20 09:27:24 +08:00

341 lines
10 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="boxshadow">
<div class="heard">
<div class="heard-title">公海</div>
<el-select
v-model="remindRange"
placeholder="请选择"
class="heard-select"
style="min-width: 120px"
>
<el-option label="全部" value="" />
<el-option label="当天" value="0" />
<el-option label="近三天" value="1" />
<el-option label="近七天" value="2" />
</el-select>
</div>
<div class="table">
<ArtTable
ref="tableRef"
row-key="id"
:loading="loading"
:data="tableData"
:currentPage="pagination.currentPage"
:pageSize="pagination.pageSize"
:total="pagination.total"
:marginTop="10"
@selection-change="handleSelectionChange"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
<template #default>
<el-table-column prop="contacts" label="客户名称"></el-table-column>
<!-- <el-table-column prop="phone" label="电话"></el-table-column>
<el-table-column prop="WhatsApp" label="WhatsApp"></el-table-column>
<el-table-column prop="email" label="邮箱"></el-table-column> -->
<el-table-column prop="follow" label="联系方式" width="240px" show-overflow-tooltip>
<template #default="scope">
<div
>WhatsApp:{{
scope.row.WhatsApp == '' ? ' --' : '【' + scope.row.WhatsApp + ' 】'
}}</div
>
<div>电话:{{ scope.row.phone == '' ? ' --' : '【' + scope.row.phone + ' 】' }}</div>
<div>邮箱:{{ scope.row.email == '' ? ' --' : '【' + scope.row.email + ' 】' }}</div>
</template>
</el-table-column>
<el-table-column prop="magnitude_txt" label="客户商机"></el-table-column>
<el-table-column prop="channel_name" label="渠道"></el-table-column>
<el-table-column prop="country_name" label="国家"></el-table-column>
<el-table-column prop="enter_user_name" label="录单人"></el-table-column>
<el-table-column prop="created_at" label="创建时间"></el-table-column>
<el-table-column fixed="right" label="操作" width="160px">
<template #default="scope">
<div
class="follow-detli"
@click="onDetail(scope.row)"
v-if="hasAuth('highseas@show')"
>
<span class="custom-clock-icon" style="vertical-align: middle; margin-right: 4px">
<i class="iconfont-sys">&#xe70f;</i>
</span>
详情
</div>
<div
class="follow-action"
@click="onFollow(scope.row)"
v-if="hasAuth('highseas@claim')"
>
<span class="custom-clock-icon" style="vertical-align: middle; margin-right: 4px">
<i class="iconfont-sys">&#xe841;</i>
</span>
领单
</div>
</template>
</el-table-column>
</template>
</ArtTable>
</div>
</div>
<el-dialog v-model="detailDialogVisible" title="详情">
<div v-if="detailRow" v-html="detailRow.desc_collect" class="detils-content" @click="onDescImgClick"></div>
<el-image-viewer v-if="descViewerUrl" :url-list="[descViewerUrl]" hide-on-click-modal @close="descViewerUrl = ''" />
</el-dialog>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import dayjs from 'dayjs'
import { ElMessageBox } from 'element-plus'
import { CrmService } from '@/api/crmApi'
import { useAuth } from '@/composables/useAuth'
const { hasAuth } = useAuth()
defineOptions({ name: 'CrmHighseas' })
const loading = ref(false)
const pagination = reactive({
currentPage: 1,
pageSize: 10,
total: 0
})
const remindRange = ref('')
const tableData = ref<any[]>([])
watch([remindRange], () => {
pagination.currentPage = 1
gettableList()
})
// 获取列表数据
const gettableList = async () => {
loading.value = true
try {
const { currentPage, pageSize } = pagination
const params: any = {
current: currentPage,
size: pageSize,
seas: 'seas'
}
if (remindRange.value) params.follow_last = remindRange.value
const { data, current, size, total } = await CrmService.getcustomerList(params)
tableData.value = data.map((item: any) => ({
...item
}))
Object.assign(pagination, { currentPage: current, pageSize: size, total })
} catch (error) {
console.error('获取用户列表失败:', error)
} finally {
loading.value = false
}
}
// 选中的行数据
const selectedRows = ref<any[]>([])
// 处理表格行选择变化
const handleSelectionChange = (selection: any[]) => {
selectedRows.value = selection
}
// 处理表格分页变化
const handleSizeChange = (newPageSize: number) => {
pagination.pageSize = newPageSize
gettableList()
}
const handleCurrentChange = (newCurrentPage: number) => {
pagination.currentPage = newCurrentPage
gettableList()
}
const timer = ref(0)
const refreshKey = ref(0)
const detailDialogVisible = ref(false)
const detailRow = ref<any>(null)
onMounted(() => {
gettableList()
timer.value = window.setInterval(() => {
refreshKey.value++
}, 1000)
})
onUnmounted(() => {
if (timer.value) {
clearInterval(timer.value)
}
})
function getCountdown(targetDate: string) {
// 让 refreshKey 成为依赖,确保每秒刷新
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
refreshKey.value
const now = dayjs()
const target = dayjs(targetDate, 'YYYY-MM-DD HH:mm:ss')
if (!target.isValid()) return '时间格式错误'
const diff = target.diff(now)
if (diff <= 0) return '已超时'
const days = Math.floor(diff / (1000 * 60 * 60 * 24))
const hours = Math.floor((diff / (1000 * 60 * 60)) % 24)
const minutes = Math.floor((diff / (1000 * 60)) % 60)
const seconds = Math.floor((diff / 1000) % 60)
return `${days > 0 ? days + '天' : ''}${hours}小时${minutes}分${seconds}秒`
}
function getCountdownClass(targetDate: string) {
const now = dayjs()
const target = dayjs(targetDate, 'YYYY-MM-DD HH:mm:ss')
if (!target.isValid() || target.diff(now) <= 0) {
return 'countdown-danger'
}
return 'countdown-success'
}
const onFollow = async (row: any) => {
ElMessageBox.confirm('确定要领取此客户吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(async () => {
const res = await CrmService.customerclaim({ customer_id: row.id })
ElMessage.success(res?.message || '提交成功!')
gettableList()
// 你可以在这里调用接口或做后续处理
})
.catch(() => {
// 用户点击了取消
})
}
function processImages(html: string) {
const parser = new DOMParser()
const doc = parser.parseFromString(html, 'text/html')
doc.querySelectorAll('img').forEach((img) => {
img.style.maxWidth = '100%'
img.style.height = 'auto'
img.style.cursor = 'zoom-in'
})
return doc.body.innerHTML
}
// 点击 desc_collect 中的图片 -> el-image-viewer 放大查看
const descViewerUrl = ref('')
function onDescImgClick(e: MouseEvent) {
const target = e.target as HTMLElement
if (target.tagName === 'IMG') {
descViewerUrl.value = (target as HTMLImageElement).src
}
}
function onDetail(row: any) {
row.desc_collect = processImages(row.desc_collect) // 处理图片
detailRow.value = row
detailDialogVisible.value = true
}
</script>
<style lang="scss" scoped>
.detils-content {
padding-left: 15px;
img {
max-width: 100% !important;
}
}
.boxshadow {
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 10, 118, 0.1);
background: var(--el-card-bg-color);
padding: 20px;
margin-bottom: 10px;
}
.heard {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
.heard-title {
font-size: 20px;
font-weight: bold;
color: #333;
border-left: 4px solid #3471ff;
padding-left: 12px;
height: 32px;
display: flex;
align-items: center;
}
.heard-date-picker {
width: 120px;
}
.heard-select {
width: 120px;
}
}
.dark {
.card-list {
.art-custom-card {
.iconfont-sys {
background-color: #232323 !important;
}
}
}
}
.table {
:deep(.el-table__empty-block) {
height: 300px !important;
}
:deep(.el-table__header th) {
background: #f7f9fa !important;
color: #333;
font-weight: bold;
}
:deep(.el-table__body tr.custom-table-row td) {
border-bottom: 1px solid #f7f9fa !important;
border-right: 1px solid #f7f9fa !important;
}
:deep(.el-table__body tr td:last-child) {
border-right: none !important;
}
:deep(.el-table__body tr:last-child td) {
border-bottom: none !important;
}
:deep(.el-table__header-wrapper th) {
border-bottom: 1px solid #f7f9fa !important;
border-right: 1px solid #f7f9fa !important;
}
:deep(.el-table__header-wrapper th:last-child) {
border-right: none !important;
}
:deep(.el-table) {
border: 1px solid #f7f9fa !important;
border-radius: 6px;
}
:deep(.el-table th),
:deep(.el-table td) {
text-align: center !important;
}
}
.countdown-danger {
color: #ff0000 !important;
}
.countdown-success {
color: #00a43f !important;
}
.follow-detli {
display: inline-flex;
align-items: center;
cursor: pointer;
color: #00a43f;
padding: 4px 10px;
border-radius: 4px;
transition: background 0.2s;
&:hover {
background: #f5f7fa;
}
}
.follow-action {
display: inline-flex;
align-items: center;
cursor: pointer;
color: #409eff;
padding: 4px 10px;
border-radius: 4px;
transition: background 0.2s;
&:hover {
background: #f5f7fa;
}
}
</style>