206 lines
6.9 KiB
Vue
206 lines
6.9 KiB
Vue
<script setup>
|
|
import { ref } from 'vue'
|
|
import { onLoad, onShow } from '@dcloudio/uni-app'
|
|
import {
|
|
getClue,
|
|
listClueFollow,
|
|
addClueFollow,
|
|
convertClue,
|
|
markClueInvalid
|
|
} from '@/api/crm'
|
|
import { labelOf, dash } from '@/utils/crmLabels'
|
|
import { PAGE_PATHS } from '@/config/api'
|
|
|
|
const id = ref('')
|
|
const detail = ref(null)
|
|
const follows = ref([])
|
|
const loading = ref(false)
|
|
const followContent = ref('')
|
|
const followType = ref('电话')
|
|
const showFollow = ref(false)
|
|
const submitting = ref(false)
|
|
|
|
const statusLabel = (s) => labelOf('clueStatus', s)
|
|
const levelLabel = (s) => labelOf('level', s)
|
|
|
|
const goBack = () => {
|
|
uni.navigateBack({
|
|
fail: () => uni.navigateTo({ url: PAGE_PATHS.crmClueList })
|
|
})
|
|
}
|
|
|
|
const load = async () => {
|
|
if (!id.value) return
|
|
loading.value = true
|
|
try {
|
|
detail.value = await getClue(id.value)
|
|
const fl = await listClueFollow(id.value, 1, 50)
|
|
follows.value = fl.rows || []
|
|
} catch (e) {
|
|
uni.showToast({ title: e.message || '加载失败', icon: 'none' })
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
const submitFollow = async () => {
|
|
if (!followContent.value.trim()) {
|
|
uni.showToast({ title: '请填写跟进内容', icon: 'none' })
|
|
return
|
|
}
|
|
submitting.value = true
|
|
try {
|
|
await addClueFollow({
|
|
clueId: id.value,
|
|
followType: followType.value,
|
|
followContent: followContent.value.trim()
|
|
})
|
|
uni.showToast({ title: '已保存', icon: 'success' })
|
|
showFollow.value = false
|
|
followContent.value = ''
|
|
await load()
|
|
} catch (e) {
|
|
uni.showToast({ title: e.message || '保存失败', icon: 'none' })
|
|
} finally {
|
|
submitting.value = false
|
|
}
|
|
}
|
|
|
|
const onConvert = () => {
|
|
const d = detail.value || {}
|
|
uni.showModal({
|
|
title: '转化为客户',
|
|
content: `将「${d.clueName || ''}」转为客户,是否继续?`,
|
|
success: async (r) => {
|
|
if (!r.confirm) return
|
|
try {
|
|
await convertClue({
|
|
id: id.value,
|
|
customerName: d.company || d.clueName,
|
|
customerType: d.customerType || 'company',
|
|
contactName: d.clueName,
|
|
contactMobile: d.mobile
|
|
})
|
|
uni.showToast({ title: '转化成功', icon: 'success' })
|
|
load()
|
|
} catch (e) {
|
|
uni.showToast({ title: e.message || '转化失败', icon: 'none' })
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
const onInvalid = () => {
|
|
uni.showModal({
|
|
title: '标记无效',
|
|
editable: true,
|
|
placeholderText: '无效原因(可选)',
|
|
success: async (r) => {
|
|
if (!r.confirm) return
|
|
try {
|
|
await markClueInvalid({ id: id.value, remark: r.content || '' })
|
|
uni.showToast({ title: '已标记', icon: 'success' })
|
|
load()
|
|
} catch (e) {
|
|
uni.showToast({ title: e.message || '操作失败', icon: 'none' })
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
onLoad((q) => {
|
|
id.value = q?.id || ''
|
|
})
|
|
onShow(() => load())
|
|
</script>
|
|
|
|
<template>
|
|
<view class="mp-page mp-page--sub mp-page--with-bottom">
|
|
<view class="mp-header">
|
|
<text class="mp-subpage-back" @click="goBack">← 返回</text>
|
|
<text class="mp-header__title" style="margin-top: 16rpx">线索详情</text>
|
|
</view>
|
|
|
|
<view v-if="detail" class="mp-detail-hero">
|
|
<view class="mp-detail-hero__top">
|
|
<view>
|
|
<text class="mp-detail-hero__title">{{ dash(detail.clueName) }}</text>
|
|
<text class="mp-detail-hero__sub">{{ dash(detail.company) }}</text>
|
|
</view>
|
|
<text class="mp-tag" :class="'mp-tag--' + statusLabel(detail.status).tag">
|
|
{{ statusLabel(detail.status).text }}
|
|
</text>
|
|
</view>
|
|
<view class="mp-detail-hero__meta">
|
|
<text class="mp-detail-hero__chip">{{ levelLabel(detail.level).text }}</text>
|
|
<text class="mp-detail-hero__chip">{{ dash(detail.ownerUserName) }}</text>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="mp-block">
|
|
<text class="mp-block__title">基本信息</text>
|
|
<view class="mp-block__panel">
|
|
<view class="mp-kv"><text class="mp-kv__label">手机</text><text class="mp-kv__value">{{ dash(detail?.mobile) }}</text></view>
|
|
<view class="mp-kv"><text class="mp-kv__label">职位</text><text class="mp-kv__value">{{ dash(detail?.position) }}</text></view>
|
|
<view class="mp-kv"><text class="mp-kv__label">来源</text><text class="mp-kv__value">{{ dash(detail?.source) }}</text></view>
|
|
<view class="mp-kv"><text class="mp-kv__label">行业</text><text class="mp-kv__value">{{ dash(detail?.industry) }}</text></view>
|
|
<view class="mp-kv"><text class="mp-kv__label">园区</text><text class="mp-kv__value">{{ dash(detail?.parkArea) }}</text></view>
|
|
<view class="mp-kv"><text class="mp-kv__label">备注</text><text class="mp-kv__value">{{ dash(detail?.remark) }}</text></view>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="mp-block">
|
|
<text class="mp-block__title">跟进记录</text>
|
|
<mp-empty v-if="!follows.length" compact title="暂无跟进" />
|
|
<view v-else class="mp-list">
|
|
<view v-for="f in follows" :key="f.id" class="mp-card">
|
|
<view class="mp-card__head">
|
|
<text class="mp-card__title">{{ dash(f.followType) }}</text>
|
|
<text class="mp-card__sub">{{ dash(f.createTime) }}</text>
|
|
</view>
|
|
<text style="font-size: 26rpx; color: var(--mp-text)">{{ dash(f.followContent) }}</text>
|
|
<view class="mp-card__row" style="margin-top: 12rpx; margin-bottom: 0">
|
|
<text>跟进人</text>
|
|
<text class="mp-card__row-val">{{ dash(f.createByName) }}</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<view v-if="showFollow" class="mp-form-panel">
|
|
<view class="mp-form-row">
|
|
<text class="mp-form-label">跟进方式</text>
|
|
<input v-model="followType" class="mp-form-input" placeholder="如:电话 / 微信 / 拜访" />
|
|
</view>
|
|
<view class="mp-form-row">
|
|
<text class="mp-form-label mp-form-label--req">跟进内容</text>
|
|
<textarea v-model="followContent" class="mp-form-textarea" placeholder="填写本次沟通要点" />
|
|
</view>
|
|
<view class="mp-form-row" style="display: flex; gap: 16rpx">
|
|
<view class="mp-btn mp-btn--ghost" style="flex: 1" @click="showFollow = false">取消</view>
|
|
<view class="mp-btn mp-btn--primary" style="flex: 1" @click="submitFollow">
|
|
{{ submitting ? '提交中…' : '提交跟进' }}
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="mp-bottom-bar">
|
|
<view class="mp-btn mp-btn--ghost" @click="showFollow = true">跟进</view>
|
|
<view
|
|
v-if="detail && detail.status !== 'converted' && detail.status !== 'invalid'"
|
|
class="mp-btn mp-btn--primary"
|
|
@click="onConvert"
|
|
>
|
|
转客户
|
|
</view>
|
|
<view
|
|
v-if="detail && detail.status !== 'converted' && detail.status !== 'invalid'"
|
|
class="mp-btn mp-btn--danger"
|
|
@click="onInvalid"
|
|
>
|
|
无效
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|