对接询单接口

This commit is contained in:
浅念
2025-07-23 18:22:56 +08:00
parent 7979a31e36
commit c333b907e6
5 changed files with 332 additions and 113 deletions
+63
View File
@@ -0,0 +1,63 @@
import request from '@/utils/http'
export class manageService {
//询单列表
static getinquiryList(params: Api.Common.PaginatingParams) {
return request.get<Api.Common.CommonListData>({
url: '/admin/inquiry/list',
params
})
}
// 获取详情
static inquirydetail(params: any) {
return request.get<any>({
url: '/admin/inquiry/show',
params
})
}
//添加询单
static inquiryAdd(params: any) {
return request.post<Api.Common.AddResponse>({
url: '/admin/inquiry/store',
params
// showErrorMessage: false // 不显示错误消息
})
}
//修改询单
static inquiryedit(params: any) {
return request.post<Api.Common.AddResponse>({
url: '/admin/inquiry/update',
params
// showErrorMessage: false // 不显示错误消息
})
}
//删除询单
static inquirydelete(params: any) {
return request.get<Api.Common.AddResponse>({
url: '/admin/inquiry/destroy',
params
})
}
//分配询单
static inquiryallocation(params: any) {
return request.post<Api.Work.AddResponse>({
url: '/admin/inquiry/allocation',
params
// showErrorMessage: false // 不显示错误消息
})
}
//发布询单
static inquiryrelease(params: any) {
return request.post<Api.Work.AddResponse>({
url: '/admin/inquiry/release',
params
// showErrorMessage: false // 不显示错误消息
})
}
//获取用户下拉框
static presetusers() {
return request.get<any>({
url: '/common/preset/users'
})
}
}
+34 -13
View File
@@ -67,11 +67,14 @@ export const useUserStore = defineStore(
refreshToken.value = newRefreshToken
}
if (expiresIn) {
// 记录绝对过期时间
const expireAt = Date.now() + expiresIn * 1000
tokenExpireAt.value = expireAt
localStorage.setItem('token_expire_at', String(expireAt))
scheduleTokenRefresh(expiresIn)
// 检查是否需要重新设置定时器
const currentLeftSeconds = Math.floor((expireAt - Date.now()) / 1000)
if (currentLeftSeconds > 0) {
scheduleTokenRefresh(currentLeftSeconds)
}
}
}
const setCompany = (id: string, abbr: string) => {
@@ -82,37 +85,59 @@ export const useUserStore = defineStore(
systemsid.value = id
systemsabbr.value = abbr
}
function restoreTokenTimer() {
const expireAt = Number(localStorage.getItem('token_expire_at') || '0')
if (accessToken.value && expireAt > Date.now()) {
const leftSeconds = Math.floor((expireAt - Date.now()) / 1000)
scheduleTokenRefresh(leftSeconds)
scheduleTokenRefresh(leftSeconds);
}
}
let isScheduled = false
function scheduleTokenRefresh(expiresIn: number) {
if (refreshTimeout) clearTimeout(refreshTimeout)
if (refreshTimeout) {
clearTimeout(refreshTimeout)
refreshTimeout = null
}
// 提前1分钟刷新
const refreshTime = (expiresIn - 100) * 1000
if (refreshTime > 0) {
refreshTimeout = setTimeout(refreshTokenAction, refreshTime)
if (!isScheduled) {
isScheduled = true
refreshTimeout = setTimeout(() => {
refreshTokenAction().finally(() => {
isScheduled = false // 重置标志位
})
}, refreshTime)
} else {
console.log('scheduleTokenRefresh is already scheduled, skipping...')
}
} else {
console.error('Invalid refreshTime:', refreshTime)
}
}
async function refreshTokenAction() {
try {
const { data } = await UserService.refreshToken({ refresh_token: refreshToken.value })
setToken(data.access_token, data.refresh_token, data.expires_in)
// 动态计算剩余时间
const currentTime = Date.now()
const expireAt = currentTime + data.expires_in * 1000
const leftSeconds = Math.floor((expireAt - currentTime) / 1000)
setToken(data.access_token, data.refresh_token, leftSeconds)
} catch (e) {
console.error('Token refresh failed:', e)
logOut()
}
}
const logOut = () => {
// 清空所有本地缓存
localStorage.clear();
sessionStorage.clear();
localStorage.clear()
sessionStorage.clear()
companyabbr.value = ''
companyid.value = ''
@@ -123,17 +148,13 @@ export const useUserStore = defineStore(
accessToken.value = ''
refreshToken.value = ''
if (refreshTimeout) clearTimeout(refreshTimeout) // 清理定时器
// 清理工作台标签页
useWorktabStore().clearAll()
// 清理sessionStorage中的用户信息缓存
sessionStorage.removeItem('lastUserInfo')
sessionStorage.removeItem('iframeRoutes')
// 重置路由状态
resetRouterState(router)
// 跳转到登录页
router.push(RoutesAlias.Login)
}
+17
View File
@@ -29,6 +29,23 @@ declare namespace Api {
/** 启用状态 */
type EnableStatus = '1' | '2'
interface CommonInfo {
data: never[]
}
/** 列表数据 */
interface CommonListData {
records: CommonInfo[]
current: number
size: number
total: number
}
interface AddResponse {
code: string
message: string
}
}
/** 认证类型 */
+212 -94
View File
@@ -10,15 +10,15 @@
<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-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-select v-model="filter.channel">
<el-option v-for="v in channelOptions" :key="v.id" :label="v.name" :value="v.id">
</el-option>
</el-select>
@@ -80,23 +80,61 @@
>
<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">
<el-table-column prop="tel" label="电话"></el-table-column>
<el-table-column prop="phone" label="国家">
<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>
{{scope.row.country? scope.row.country.cn_addr : ''}}
</span>
<span class="steer" v-if="scope.row.country">{{ scope.row.country.steering }}</span>
</template>
</el-table-column>
<el-table-column prop="phone" label="渠道">
<template #default="scope">
<span>
{{scope.row.channel? scope.row.channel.name : ''}}
</span>
</template>
</el-table-column>
<el-table-column fixed="right" label="操作" min-width="260px">
<el-table-column prop="quality" label="客户商机">
<template #default="scope">
<span :style="scope.row.quality==0 ? { color: '#F5AB00' } : scope.row.quality==1 ? { color: '#8495B8' }: { color: '#9B7200' }">
{{scope.row.quality==0 ? '金牌商机' : scope.row.quality==1 ? '银牌商机' : '铜牌商机'}}
</span>
</template>
</el-table-column>
<el-table-column prop="magnitude" label="发布人">
<template #default="scope">
<span>
{{scope.row.enter? scope.row.enter.name : ''}}
</span>
</template>
</el-table-column>
<el-table-column prop="recipient" label="跟单人">
<template #default="scope">
<span>
{{scope.row.recipient? scope.row.recipient.name : ''}}
</span>
</template>
</el-table-column>
<el-table-column label="状态">
<template #default="scope">
<span :style="scope.row.status== 0 ? { color: '#ccc' } :scope.row.status== 1 ? { color: '#00A43F' }: { color: '#3471FF' }">
{{ scope.row.status== 0 ? '未发布' : scope.row.status== 1 ? '待跟踪' : '已跟踪' }}
</span>
</template>
</el-table-column>
<el-table-column label="倒计时" min-width="140">
<template #default="scope">
<span :class="getCountdownClass(scope.row.countdown)" v-if="scope.row.status==1">
<span class="custom-clock-icon" style="margin-right: 4px; vertical-align: middle"
><i class="iconfont-sys">&#xe7b1;</i></span
>
{{ getCountdown(scope.row.countdown) }}
</span>
</template>
</el-table-column>
<el-table-column fixed="right" label="操作" 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"
@@ -110,13 +148,13 @@
>
详情
</div>
<div class="follow-action" @click="onFollow(scope.row)">
<div class="follow-action" @click="onFollow(scope.row)" v-if="scope.row.status==0">
<span class="custom-clock-icon" style="margin-right: 2px; vertical-align: middle"
><i class="iconfont-sys">&#xe852;</i></span
><i class="iconfont-sys">&#xe712;</i></span
>
领单
发布
</div>
<div class="follow-action edit" @click="onEdit()">
<div class="follow-action edit" @click="onEdit(scope.row)" >
<span class="custom-clock-icon" style="margin-right: 2px; vertical-align: middle"
><i class="iconfont-sys">&#xe6bf;</i></span
>
@@ -164,12 +202,17 @@
</div>
<div v-if="detailRow" v-html="detailRow.desc_collect"> </div>
</el-dialog>
<el-dialog v-model="AddDialogVisible" title="录单" width="800px">
<el-dialog
:title="dialogType === 'add' ? '录单' : '编辑'"
v-model="AddDialogVisible"
width="800px"
:close-on-click-modal="false"
>
<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-form-item label="渠道" prop="channel">
<el-select v-model="addForm.channel" placeholder="请选择渠道">
<el-option
v-for="item in channelOptions"
:key="item.id"
@@ -180,8 +223,8 @@
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="等级" prop="level">
<el-select v-model="addForm.level" placeholder="请选择等级">
<el-form-item label="等级" prop="quality">
<el-select v-model="addForm.quality" placeholder="请选择等级">
<el-option
v-for="item in opportunityOptions"
:key="item.id"
@@ -213,8 +256,8 @@
</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 label="联系方式" prop="tel">
<el-input v-model="addForm.tel" placeholder="请输入联系方式" />
</el-form-item>
</el-col>
<el-col :span="12">
@@ -230,7 +273,7 @@
</el-form-item>
</el-col>
</el-row>
<el-form-item label="详情内容" prop="desc_collect">
<el-form-item label="详情内容" prop="content">
<ArtWangEditor class="el-top" v-model="editorHtml" />
</el-form-item>
<div class="form-submit-row">
@@ -244,9 +287,9 @@
<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-select v-model="fenpeiForm.recipient" placeholder="请选择" style="width: 100%">
<el-option
v-for="item in followerOptions"
v-for="item in userOptions"
:key="item.id"
:label="item.name"
:value="item.id"
@@ -266,13 +309,15 @@
import dayjs from 'dayjs'
import { ElMessageBox } from 'element-plus'
import { CrmService } from '@/api/crmApi'
import { manageService } from '@/api/adminApi'
const loading = ref(false)
const dialogType = ref('add')
const { width } = useWindowSize()
const isMobile = computed(() => width.value < 500)
const filter = ref({
status: '',
time: '',
channel_id: ''
channel: ''
})
const pagination = reactive({
currentPage: 1,
@@ -287,23 +332,27 @@
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: '',
let addForm = reactive({
channel: '',
quality: '',
country: '',
email: '',
contact: '',
tel: '',
contacts: '',
whatsapp: '',
desc_collect: ''
content: ''
})
// 响应式表单数据
const formFilters = reactive({ ...filter })
// 重置表单
const handleReset = () => {
filter.value = {
status: '',
time: '',
channel: ''
}
Object.assign(formFilters, { ...filter })
pagination.currentPage = 1 // 重置到第一页
gettableList()
@@ -315,10 +364,6 @@
pagination.currentPage = 1 // 搜索时重置到第一页
gettableList()
}
watch([remindRange], () => {
pagination.currentPage = 1
gettableList()
})
const opportunityOptions = ref<any>()
//商机
const getMagnitude = async () => {
@@ -337,6 +382,14 @@
const res = await CrmService.getCountry()
countryOptions.value = res || []
}
// 计算属性:将时间格式化为 "开始日期 - 结束日期"
const formattedTimeRange = () => {
if (!filter.value.time || filter.value.time.length !== 2) {
return '' // 如果时间范围无效,返回空字符串
}
const [startDate, endDate] = filter.value.time
return `${startDate} - ${endDate}`
}
// 获取列表数据
const gettableList = async () => {
loading.value = true
@@ -344,11 +397,13 @@
const { currentPage, pageSize } = pagination
const params: any = {
current: currentPage,
size: pageSize
size: pageSize,
channel: filter.value.channel,
status: filter.value.status,
time: formattedTimeRange()
}
if (remindRange.value) params.follow_last = remindRange.value
const { data, current, size, total } = await CrmService.getcustomerList(params)
tableData.value = data.map((item: any) => ({
const { records, current, size, total } = await manageService.getinquiryList(params)
tableData.value = records.map((item: any) => ({
...item
}))
Object.assign(pagination, { currentPage: current, pageSize: size, total })
@@ -387,13 +442,12 @@
clearInterval(timer.value)
}
})
function getCountdown(targetDate: string) {
function getCountdown(targetTimestamp: number) {
// 让 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')
const target = dayjs.unix(targetTimestamp) // 使用时间戳创建 dayjs 对象
if (!target.isValid()) return '时间格式错误'
const diff = target.diff(now)
if (diff <= 0) return '已超时'
@@ -403,30 +457,60 @@
const seconds = Math.floor((diff / 1000) % 60)
return `${days > 0 ? days + '天' : ''}${hours}小时${minutes}分${seconds}秒`
}
// 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) {
function getCountdownClass(targetDate: number) {
const now = dayjs()
const target = dayjs(targetDate, 'YYYY-MM-DD HH:mm:ss')
// const target = dayjs(targetDate, 'YYYY-MM-DD HH:mm:ss')
const target = dayjs.unix(targetDate)
if (!target.isValid() || target.diff(now) <= 0) {
return 'countdown-danger'
}
return 'countdown-success'
}
function reactiveForm() {
Object.assign(addForm, {
channel: '',
quality: '',
country: '',
email: '',
tel: '',
contacts: '',
whatsapp: '',
content: ''
})
}
const addfn = () => {
dialogType.value = 'add'
reactiveForm()
getgetChannel()
getMagnitude()
getarea()
AddDialogVisible.value = true
}
const onFollow = async (row: any) => {
ElMessageBox.confirm('确定要领取此客户吗?', '提示', {
ElMessageBox.confirm('确定要发布此询单吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(async () => {
const res = await CrmService.customerclaim({ customer_id: row.id })
ElMessage.success(res?.message || '提交成功!')
const res = await manageService.inquiryrelease({ id: row.id, status: 1 })
ElMessage.success(res?.message || '请求成功!')
gettableList()
// 你可以在这里调用接口或做后续处理
})
@@ -439,13 +523,49 @@
detailRow.value = row
detailDialogVisible.value = true
}
//分配
const onFenpei = (row: any) => {
fenpeiForm.customer_id = row.id
fenpeiDialogVisible.value = true
}
//编辑
const onEdit = () => {}
const carrid = ref('')
const onEdit = async (row: any) => {
dialogType.value = 'edit'
getgetChannel()
getMagnitude()
getarea()
AddDialogVisible.value = true
carrid.value = row.id
addForm = reactive({
channel: row.channel ? row.channel.id : '',
quality: row.quality,
country: row.country ? row.country.id : '',
email: row.email,
tel: row.tel,
contacts: row.contacts,
whatsapp: row.whatsapp,
content: row.content
})
// 这里可以添加编辑逻辑
// 例如打开编辑对话框或跳转到编辑页面
}
const delCodeTrim = (content: string): string => {
return content.replace(/(\s*)<\/code>/g, '</code>')
}
const handleAddSubmit = async () => {
// 提交表单逻辑
// 这里可以调用 API 提交 addForm 数据
if (dialogType.value === 'add') {
editorHtml.value = delCodeTrim(editorHtml.value)
addForm.content = editorHtml.value
const res = await manageService.inquiryAdd(addForm)
ElMessage.success(res?.message || '录单成功!')
} else {
editorHtml.value = delCodeTrim(editorHtml.value)
addForm.content = editorHtml.value
const forms = { ...addForm, id: carrid.value }
const res = await manageService.inquiryedit(forms)
ElMessage.success(res?.message || '录单成功!')
}
AddDialogVisible.value = false
gettableList()
}
//删除
const onDelete = (row: any) => {
ElMessageBox.confirm('确定要删除此条询单吗?', '提示', {
@@ -454,9 +574,8 @@
type: 'warning'
})
.then(async () => {
// const res = await CrmService.customerclaim({ customer_id: row.id })
// ElMessage.success(res?.message || '提交成功!')
console.log(row.id)
const res = await manageService.inquirydelete({ id: row.id })
ElMessage.success(res?.message || '提交成功!')
gettableList()
// 你可以在这里调用接口或做后续处理
})
@@ -466,44 +585,36 @@
}
const fenpeiForm = reactive({
customer_id: '',
follower_id: ''
id: '',
recipient: ''
})
const followerOptions = ref<any[]>([])
const getFollowerOptions = async () => {
// try {
// const res = await CrmService.getFollowers();
// followerOptions.value = res || [];
// } catch (error) {
// console.error('获取跟单人列表失败:', error);
// followerOptions.value = [];
// }
const userOptions = ref<any>()
const getuserList = async () => {
// 这里调用获取用户列表的接口
const res = await manageService.presetusers()
userOptions.value = res || []
// return res || []
}
//分配
const onFenpei = (row: any) => {
getuserList()
fenpeiForm.id = row.id
fenpeiDialogVisible.value = true
}
const handleFenpeiSubmit = async () => {
if (!fenpeiForm.follower_id) {
if (!fenpeiForm.recipient) {
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()
const res = await manageService.inquiryallocation({
id: fenpeiForm.id,
recipient: fenpeiForm.recipient,
status: 2
})
ElMessage.success(res?.message || '分配成功!')
fenpeiDialogVisible.value = false
gettableList()
}
</script>
<style lang="scss" scoped>
@@ -676,4 +787,11 @@
.form-submit-row {
text-align: center;
}
.steer {
margin-left: 5px;
display: inline-block;
border: 1px solid #00a43f;
padding: 0px 4px;
color: #00a43f;
}
</style>
+2 -2
View File
@@ -34,10 +34,10 @@
<el-table-column prop="magnitude" label="客户商机" min-width="140"></el-table-column>
<el-table-column label="状态" min-width="140">
<template #default="scope">
<span :class="getCountdownClass(scope.row.deleted_at)">
<span :class="getCountdownClass(scope.row.follow_next)">
<span class="custom-clock-icon" style="vertical-align: middle; margin-right: 4px;"><i class="iconfont-sys">&#xe7b1;</i></span>
{{ getCountdown(scope.row.deleted_at) }}
{{ getCountdown(scope.row.follow_next) }}
</span>
</template>
</el-table-column>