Files
art-design/src/views/admin/inquiry_system/index.vue
T
2025-07-22 16:54:06 +08:00

680 lines
20 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.
<!-- eslint-disable vue/no-parsing-error -->
<!-- eslint-disable vue/valid-attribute-name -->
<!-- eslint-disable prettier/prettier -->
<template>
<div class="boxshadow">
<section class="search-bar art-custom-card">
<el-form v-model:filter="formFilters" label-position="left">
<el-row class="search-form-row" :gutter="12">
<el-col :xs="24" :sm="12" :md="6" :lg="6" :xl="6">
<el-form-item label="状态" label-width="70px">
<el-select v-model="filter.status">
<el-option label="全部" value="" />
<el-option label="当天" value="0" />
<el-option label="近三天" value="1" />
<el-option label="近七天" value="2" />
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="6" :lg="6" :xl="6">
<el-form-item label="渠道" label-width="70px">
<el-select v-model="filter.channel_id">
<el-option v-for="v in channelOptions" :key="v.id" :label="v.name" :value="v.id">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="6" :lg="6" :xl="6">
<el-form-item label="时间" label-width="70px">
<el-date-picker
v-model="filter.time"
type="daterange"
format="YYYY-MM-DD"
valueFormat="YYYY-MM-DD"
rangeSeparator="至"
startPlaceholder="开始日期"
endPlaceholder="结束日期"
:style="{ width: '100%' }"
/>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="6" :lg="6" :xl="6" class="action-column">
<div
class="action-buttons-wrapper"
:style="{
'justify-content': isMobile ? 'flex-end' : 3 <= 2 ? 'flex-start' : 'flex-end'
}"
>
<div class="form-buttons">
<el-button class="reset-button" @click="handleReset">{{
$t('table.searchBar.reset')
}}</el-button>
<el-button type="primary" class="search-button" @click="handleSearch">{{
$t('table.searchBar.search')
}}</el-button>
</div>
<div class="form-btn-right">
<el-button class="reset-button" @click="addfn()"
><i class="iconfont-sys">&#xe602;</i> 录单</el-button
>
</div>
</div>
</el-col>
</el-row>
</el-form>
</section>
<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="phone" label="国家"></el-table-column>
<el-table-column prop="phone" label="渠道"></el-table-column>
<el-table-column prop="magnitude" label="客户商机"></el-table-column>
<el-table-column prop="magnitude" label="发布人"></el-table-column>
<el-table-column prop="magnitude" label="跟单人"></el-table-column>
<el-table-column label="状态" min-width="140">
<template #default="scope">
<span :class="getCountdownClass(scope.row.deleted_at)">
<span class="custom-clock-icon" style="margin-right: 4px; vertical-align: middle"
><i class="iconfont-sys">&#xe7b1;</i></span
>
{{ getCountdown(scope.row.deleted_at) }}
</span>
</template>
</el-table-column>
<el-table-column fixed="right" label="操作" min-width="260px">
<template #default="scope">
<div class="follow-action fenpei" @click="onFenpei(scope.row)">
<span class="custom-clock-icon" style="margin-right: 2px; vertical-align: middle"
><i class="iconfont-sys">&#xe852;</i></span
>
分配跟单
</div>
<div class="follow-action detil" @click="onDetail(scope.row)">
<span class="custom-clock-icon" style="margin-right: 2px; vertical-align: middle"
><i class="iconfont-sys">&#xe70f;</i></span
>
详情
</div>
<div class="follow-action" @click="onFollow(scope.row)">
<span class="custom-clock-icon" style="margin-right: 2px; vertical-align: middle"
><i class="iconfont-sys">&#xe852;</i></span
>
领单
</div>
<div class="follow-action edit" @click="onEdit()">
<span class="custom-clock-icon" style="margin-right: 2px; vertical-align: middle"
><i class="iconfont-sys">&#xe6bf;</i></span
>
编辑
</div>
<div class="follow-action delete" @click="onDelete(scope.row)">
<span class="custom-clock-icon" style="margin-right: 2px; vertical-align: middle"
><i class="iconfont-sys">&#xe6d4;</i></span
>
删除
</div>
</template>
</el-table-column>
</template>
</ArtTable>
</div>
</div>
<el-dialog v-model="detailDialogVisible" title="详情">
<div class="detail-info-table">
<div class="row">
<div>客户名称:{{ detailRow.contacts }}</div>
<div>电话:{{ detailRow.phone }}</div>
<div>国家:{{ detailRow.country }}</div>
</div>
<div class="row">
<div>渠道:{{ detailRow.channel }}</div>
<div>邮箱:{{ detailRow.email }}</div>
<div>WhatsApp:{{ detailRow.whatsapp }}</div>
</div>
<div class="row">
<div>发布人:{{ detailRow.publisher }}</div>
<div>跟单人:{{ detailRow.follower }}</div>
<div>录单时间:{{ detailRow.created_at }}</div>
</div>
<div class="row">
<div>
等级:
<span :style="{ color: detailRow.levelColor || '#FF0000' }">{{ detailRow.level }}</span>
</div>
<div
>状态:<span>{{ detailRow.status }}</span></div
>
<div>SN:{{ detailRow.sn }}</div>
</div>
</div>
<div v-if="detailRow" v-html="detailRow.desc_collect"> </div>
</el-dialog>
<el-dialog v-model="AddDialogVisible" title="录单" width="800px">
<el-form :model="addForm" ref="addFormRef" label-width="80px" class="add-form">
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="渠道" prop="channel_id">
<el-select v-model="addForm.channel_id" placeholder="请选择渠道">
<el-option
v-for="item in channelOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="等级" prop="level">
<el-select v-model="addForm.level" placeholder="请选择等级">
<el-option
v-for="item in opportunityOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="国家" prop="country">
<el-select v-model="addForm.country" filterable placeholder="请选择国家">
<el-option
v-for="item in countryOptions"
:key="item.id"
:label="item.cn_addr"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="Email" prop="email">
<el-input v-model="addForm.email" placeholder="请输入Email" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="联系方式" prop="contact">
<el-input v-model="addForm.contact" placeholder="请输入联系方式" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="联系人" prop="contacts">
<el-input v-model="addForm.contacts" placeholder="请输入联系人" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="24">
<el-form-item label="WhatsApp" prop="whatsapp">
<el-input v-model="addForm.whatsapp" placeholder="请输入WhatsApp" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="详情内容" prop="desc_collect">
<ArtWangEditor class="el-top" v-model="editorHtml" />
</el-form-item>
<div class="form-submit-row">
<el-button type="primary" size="large" style="width: 50%" @click="handleAddSubmit"
>提交</el-button
>
</div>
</el-form>
</el-dialog>
<!-- 分配 -->
<el-dialog v-model="fenpeiDialogVisible" title="分配跟单" width="360px">
<el-form :model="fenpeiForm" label-width="0">
<el-form-item>
<el-select v-model="fenpeiForm.follower_id" placeholder="请选择" style="width: 100%">
<el-option
v-for="item in followerOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<div style="display: flex; justify-content: space-around; margin-top: 24px">
<el-button @click="fenpeiDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleFenpeiSubmit">确定</el-button>
</div>
</el-form>
</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'
const loading = ref(false)
const { width } = useWindowSize()
const isMobile = computed(() => width.value < 500)
const filter = ref({
status: '',
time: '',
channel_id: ''
})
const pagination = reactive({
currentPage: 1,
pageSize: 20,
total: 0
})
const editorHtml = ref('') // 编辑器内容
const timer = ref(0)
const refreshKey = ref(0)
const fenpeiDialogVisible = ref(false)
const detailDialogVisible = ref(false)
const AddDialogVisible = ref(false)
const detailRow = ref<any>(null)
const countryOptions = ref<any>()
const remindRange = ref('')
const tableData = ref<any[]>([])
const addFormRef = ref()
const addForm = reactive({
channel_id: '',
level: '',
country: '',
email: '',
contact: '',
contacts: '',
whatsapp: '',
desc_collect: ''
})
// 响应式表单数据
const formFilters = reactive({ ...filter })
// 重置表单
const handleReset = () => {
Object.assign(formFilters, { ...filter })
pagination.currentPage = 1 // 重置到第一页
gettableList()
}
// 搜索处理
const handleSearch = () => {
console.log('搜索参数:', formFilters)
pagination.currentPage = 1 // 搜索时重置到第一页
gettableList()
}
watch([remindRange], () => {
pagination.currentPage = 1
gettableList()
})
const opportunityOptions = ref<any>()
//商机
const getMagnitude = async () => {
// 这里调用获取品牌列表的接口
const res = await CrmService.getMagnitude()
opportunityOptions.value = res || []
}
const channelOptions = ref<any>()
//渠道
const getgetChannel = async () => {
const res = await CrmService.getChannel()
channelOptions.value = res
}
//国家
const getarea = async () => {
const res = await CrmService.getCountry()
countryOptions.value = res || []
}
// 获取列表数据
const gettableList = async () => {
loading.value = true
try {
const { currentPage, pageSize } = pagination
const params: any = {
current: currentPage,
size: pageSize
}
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()
}
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 addfn = () => {
getgetChannel()
getMagnitude()
getarea()
AddDialogVisible.value = true
}
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 onDetail(row: any) {
detailRow.value = row
detailDialogVisible.value = true
}
//分配
const onFenpei = (row: any) => {
fenpeiForm.customer_id = row.id
fenpeiDialogVisible.value = true
}
//编辑
const onEdit = () => {}
//删除
const onDelete = (row: any) => {
ElMessageBox.confirm('确定要删除此条询单吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(async () => {
// const res = await CrmService.customerclaim({ customer_id: row.id })
// ElMessage.success(res?.message || '提交成功!')
console.log(row.id)
gettableList()
// 你可以在这里调用接口或做后续处理
})
.catch(() => {
// 用户点击了取消
})
}
const fenpeiForm = reactive({
customer_id: '',
follower_id: ''
})
const followerOptions = ref<any[]>([])
const getFollowerOptions = async () => {
// try {
// const res = await CrmService.getFollowers();
// followerOptions.value = res || [];
// } catch (error) {
// console.error('获取跟单人列表失败:', error);
// followerOptions.value = [];
// }
}
const handleFenpeiSubmit = async () => {
if (!fenpeiForm.follower_id) {
ElMessage.warning('请选择跟单人')
return
}
// try {
// const res = await CrmService.assignFollow({
// customer_id: fenpeiForm.customer_id,
// follower_id: fenpeiForm.follower_id,
// });
// ElMessage.success(res?.message || '分配成功!');
// fenpeiDialogVisible.value = false;
// gettableList();
// } catch (error) {
// console.error('分配跟单失败:', error);
// ElMessage.error('分配跟单失败!');
// }
}
const handleAddSubmit = () => {}
onMounted(() => {
getFollowerOptions()
})
</script>
<style lang="scss" scoped>
.search-bar {
padding: 20px 20px 0;
background-color: var(--art-main-bg-color);
border-radius: calc(var(--custom-radius) / 2 + 2px);
:deep(.el-form-item__label) {
display: flex;
align-items: center;
line-height: 20px;
}
.search-form-row {
display: flex;
flex-wrap: wrap;
}
.action-column {
flex: 1;
max-width: 100%;
.action-buttons-wrapper {
display: flex;
flex-wrap: wrap;
align-items: center;
// justify-content: flex-end;
margin-bottom: 12px;
}
.filter-toggle {
display: flex;
align-items: center;
margin-left: 10px;
line-height: 32px;
color: var(--main-color);
cursor: pointer;
span {
font-size: 14px;
user-select: none;
}
.icon-wrapper {
display: flex;
align-items: center;
margin-left: 4px;
font-size: 14px;
}
}
.form-btn-right {
flex: 1;
text-align: right;
}
}
}
.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) {
font-weight: bold;
color: #333;
background: #f7f9fa !important;
}
:deep(.el-table__body tr.custom-table-row td) {
border-right: 1px solid #f7f9fa !important;
border-bottom: 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-right: 1px solid #f7f9fa !important;
border-bottom: 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: #f00 !important;
}
.countdown-success {
color: #00a43f !important;
}
.fenpei {
color: #a233c6 !important;
}
.detil {
color: #00a43f !important;
}
.edit {
color: #008477 !important;
}
.delete {
color: #f00 !important;
}
.follow-action {
display: inline-flex;
align-items: center;
padding: 4px;
color: #409eff;
cursor: pointer;
border-radius: 4px;
transition: background 0.2s;
&:hover {
background: #f5f7fa;
}
}
.detail-info-table {
.row {
display: flex;
justify-content: flex-start;
margin-bottom: 6px;
> div {
flex: 1;
min-width: 0;
margin-right: 16px;
overflow: hidden;
font-size: 14px;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
.form-submit-row {
text-align: center;
}
</style>