【招生小程序】 新增# 电销:对接修改跟进、线索列表接口

master
kaeery 2025-02-27 21:42:37 +08:00
parent 30d61360fa
commit 504d381eff
12 changed files with 196 additions and 77 deletions

View File

@ -32,3 +32,6 @@ export function apiEditRemark(params: any) {
export function apiEditClue(params: any) { export function apiEditClue(params: any) {
return request.post({ url: '/clue/edit', data: params }) return request.post({ url: '/clue/edit', data: params })
} }
export function apiTeleClueList(params: any) {
return request.get({ url: '/clue/telemarketingList', data: params })
}

View File

@ -2,10 +2,10 @@
<view class="bg-[#FAFAFE]"> <view class="bg-[#FAFAFE]">
<view class="bg-white px-[32rpx]"> <view class="bg-white px-[32rpx]">
<TForm ref="tForm" :model="form" :rules="rules" errorType="toast"> <TForm ref="tForm" :model="form" :rules="rules" errorType="toast">
<TFormItem prop="publishName"> <TFormItem prop="recruitTeacherName">
<TInputField <TInputField
v-model="form.publishName" v-model="form.recruitTeacherName"
label="发布人" label="电销老师"
placeholder="" placeholder=""
inputAlign="left" inputAlign="left"
readonly readonly
@ -13,9 +13,9 @@
:labelWidth="120" :labelWidth="120"
/> />
</TFormItem> </TFormItem>
<TFormItem prop="clientName"> <TFormItem prop="studentName">
<TInputField <TInputField
v-model="form.clientName" v-model="form.studentName"
label="客户姓名" label="客户姓名"
placeholder="" placeholder=""
inputAlign="left" inputAlign="left"
@ -24,9 +24,9 @@
:labelWidth="120" :labelWidth="120"
/> />
</TFormItem> </TFormItem>
<TFormItem prop="mobile"> <TFormItem prop="phone">
<TInputField <TInputField
v-model="form.mobile" v-model="form.phone"
label="电话" label="电话"
placeholder="" placeholder=""
inputAlign="left" inputAlign="left"
@ -35,9 +35,9 @@
:labelWidth="120" :labelWidth="120"
/> />
</TFormItem> </TFormItem>
<TFormItem prop="desc"> <TFormItem prop="basicInformation">
<TTextareaField <TTextareaField
v-model="form.desc" v-model="form.basicInformation"
label="基本情况" label="基本情况"
placeholder="" placeholder=""
autoHeight autoHeight
@ -46,7 +46,7 @@
:labelWidth="120" :labelWidth="120"
/> />
</TFormItem> </TFormItem>
<TFormItem prop="status"> <TFormItem prop="state">
<TInputField <TInputField
v-model="parseStatusText" v-model="parseStatusText"
label="状态" label="状态"
@ -57,9 +57,9 @@
:labelWidth="120" :labelWidth="120"
/> />
</TFormItem> </TFormItem>
<TFormItem prop="isComplete"> <TFormItem prop="isConversion">
<TSwitchField <TSwitchField
v-model="form.isComplete" v-model="form.isConversion"
label="是否转化成功" label="是否转化成功"
:labelWidth="120" :labelWidth="120"
/> />
@ -78,7 +78,13 @@
</TForm> </TForm>
</view> </view>
<view class="px-[60rpx]"> <view class="px-[60rpx]">
<u-button class="btn" color="#0E66FB" shape="circle" @click="handleSubmit"> <u-button
class="btn"
color="#0E66FB"
shape="circle"
:loading="loading"
@click="handleSubmit"
>
提交 提交
</u-button> </u-button>
</view> </view>
@ -87,49 +93,71 @@
<script setup lang="ts"> <script setup lang="ts">
import { onLoad } from '@dcloudio/uni-app' import { onLoad } from '@dcloudio/uni-app'
import { computed } from 'vue' import { ref, computed } from 'vue'
import { shallowRef } from 'vue' import { useClueDetail } from '@/hooks/useCommon'
import { ref } from 'vue' import { optionsMap } from '@/config/options'
import { apiCompleteCluse } from '@/api/clue'
import { toast } from '@/utils/util'
const { fetchClueDetail, clueDetailInfo } = useClueDetail()
const id = ref('') const id = ref('')
const tForm = ref() const tForm = ref()
const form = ref({ const form = ref({
publishName: '张三', id: '',
clientName: '张三', recruitTeacherName: '',
mobile: '18138952909', studentName: '',
desc: '学生爸爸接电话学生高三毕业300多分家长不清楚学生收到录取通知家长说学生不读书了我让家长先问问学生对未来的规划先和学生沟通一下家长同意我们加他微信发专业资料给他看看可以在微信上问问学生具体情况。推荐3+2给家长发一下学校简介和专业资料。', phone: '',
status: 1, basicInformation: '',
isComplete: 0, state: 1,
isConversion: 0,
remark: '' remark: ''
}) })
const rules = { const rules = {
isComplete: [{ required: true, message: '请选择是否转化成功' }] isConversion: [{ required: true, message: '请选择是否转化成功' }]
} }
const statusList = shallowRef([
{ label: '账号已添加', value: 1 },
{ label: '账号不存在', value: 2 },
{ label: '账号未通过', value: 3 }
])
const parseStatusText = computed( const parseStatusText = computed(
() => statusList.value.find(item => item.value == form.value.status)?.label () => optionsMap.stateOptions.find(item => item.value == form.value.state)?.label
) )
onLoad(option => { onLoad(async option => {
if (option?.id) { if (option?.id) {
id.value = option.id id.value = option.id
await fetchClueDetail(id.value)
setFormData()
} }
}) })
const loading = ref(false)
const handleSubmit = () => { const handleSubmit = () => {
tForm.value tForm.value
.validate() .validate()
.then(valid => { .then(async valid => {
if (valid) { if (valid) {
console.log('校验通过', form.value) loading.value = true
try {
const data = {
id: form.value.id,
isConversion: form.value.isConversion,
remark: form.value.remark
}
await apiCompleteCluse(data)
toast('转化完成')
uni.navigateBack()
uni.$emit('refreshPage')
} catch (error) {}
} }
}) })
.catch(() => {}) .catch(() => {})
} }
const setFormData = () => {
for (const key in clueDetailInfo.value) {
if (Object.prototype.hasOwnProperty.call(form.value, key)) {
form.value[key] = clueDetailInfo.value[key]
}
}
}
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@ -2,57 +2,101 @@
<view class="p-[32rpx] bg-white"> <view class="p-[32rpx] bg-white">
<view class="flex justify-between items-center mb-[20rpx]"> <view class="flex justify-between items-center mb-[20rpx]">
<text class="text-[44rpx] font-bold">修改跟进信息</text> <text class="text-[44rpx] font-bold">修改跟进信息</text>
<text class="text-[28rpx]">全部清</text> <text class="text-[28rpx]" @click="handleClear"></text>
</view> </view>
<TForm ref="tForm" :model="form" :rules="rules" errorType="toast"> <TForm ref="tForm" :model="form" :rules="rules" errorType="toast">
<TFormItem prop="clientName"> <TFormItem prop="studentName">
<TInputField <TInputField
v-model="form.clientName" v-model="form.studentName"
label="客户姓名" label="客户姓名"
placeholder="请填写客户姓名(必填)" placeholder="请填写客户姓名(必填)"
inputAlign="left" inputAlign="left"
/> />
</TFormItem> </TFormItem>
<TFormItem prop="mobile"> <TFormItem prop="phone">
<TInputField <TInputField
v-model="form.mobile" v-model="form.phone"
label="电话" label="电话"
placeholder="请填写电话(必填)" placeholder="请填写电话(必填)"
inputAlign="left" inputAlign="left"
/> />
</TFormItem> </TFormItem>
<TFormItem prop="desc"> <TFormItem prop="basicInformation">
<TTextareaField label="基本情况" placeholder="请填写基本情况(必填)" autoHeight /> <TTextareaField
v-model="form.basicInformation"
label="基本情况"
placeholder="请填写基本情况(必填)"
autoHeight
/>
</TFormItem> </TFormItem>
</TForm> </TForm>
<u-button class="btn" color="#0E66FB" shape="circle" @click="handleConfirm"></u-button> <u-button
class="btn"
color="#0E66FB"
shape="circle"
:loading="loading"
@click="handleConfirm"
>
确认
</u-button>
</view> </view>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { onLoad } from '@dcloudio/uni-app'
import { ref } from 'vue' import { ref } from 'vue'
import { useClueDetail } from '@/hooks/useCommon'
import { apiEditClue } from '@/api/clue'
import { toast } from '@/utils/util'
const { fetchClueDetail, clueDetailInfo } = useClueDetail()
const tForm = ref() const tForm = ref()
const form = ref({ const form = ref({
clientName: '', id: '',
mobile: '', studentName: '',
desc: '' phone: '',
basicInformation: ''
}) })
const rules = { const rules = {
clientName: [{ required: true, message: '请填写客户姓名', trigger: 'blur' }], studentName: [{ required: true, message: '请填写客户姓名', trigger: 'blur' }],
mobile: [{ required: true, message: '请填写电话', trigger: 'blur' }], phone: [{ required: true, message: '请填写电话', trigger: 'blur' }],
desc: [{ required: true, message: '请填写基本情况', trigger: 'blur' }] basicInformation: [{ required: true, message: '请填写基本情况', trigger: 'blur' }]
} }
const loading = ref(false)
const handleConfirm = () => { const handleConfirm = () => {
tForm.value tForm.value
.validate() .validate()
.then(valid => { .then(async valid => {
if (valid) { if (valid) {
console.log('校验通过') loading.value = true
try {
await apiEditClue(form.value)
toast('修改成功')
uni.navigateBack()
uni.$emit('refreshPage')
} catch (error) {}
loading.value = false
} }
}) })
.catch(() => {}) .catch(() => {})
} }
const handleClear = () => {
tForm.value.resetFields()
}
onLoad(async option => {
if (option?.id) {
await fetchClueDetail(option.id)
setFormData()
}
})
const setFormData = () => {
for (const key in clueDetailInfo.value) {
if (Object.prototype.hasOwnProperty.call(form.value, key)) {
form.value[key] = clueDetailInfo.value[key] as any
}
}
}
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@ -5,7 +5,7 @@
<TFormItem prop="recruitTeacherName"> <TFormItem prop="recruitTeacherName">
<TInputField <TInputField
v-model="form.recruitTeacherName" v-model="form.recruitTeacherName"
label="发布人" label="电销老师"
placeholder="" placeholder=""
inputAlign="left" inputAlign="left"
readonly readonly
@ -50,7 +50,7 @@
<TMultiSelect <TMultiSelect
v-model="form.state" v-model="form.state"
label="状态" label="状态"
:groupList="statusList" :groupList="optionsMap.stateOptions"
:labelWidth="100" :labelWidth="100"
/> />
</TFormItem> </TFormItem>
@ -64,20 +64,19 @@
:loading="loading" :loading="loading"
@click="handleSubmit" @click="handleSubmit"
> >
提交{{ form.state }} 提交
</u-button> </u-button>
</view> </view>
</view> </view>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { apiAddCluseProgress, apiCluseEdit } from '@/api/clue'
import { stateEnum } from '@/enums'
import { useClueDetail } from '@/hooks/useCommon'
import { toast } from '@/utils/util'
import { onLoad } from '@dcloudio/uni-app' import { onLoad } from '@dcloudio/uni-app'
import { shallowRef } from 'vue'
import { ref } from 'vue' import { ref } from 'vue'
import { useClueDetail } from '@/hooks/useCommon'
import { apiAddCluseProgress } from '@/api/clue'
import { optionsMap } from '@/config/options'
import { toast } from '@/utils/util'
const { fetchClueDetail, clueDetailInfo } = useClueDetail() const { fetchClueDetail, clueDetailInfo } = useClueDetail()
@ -95,11 +94,6 @@ const form = ref({
const rules = { const rules = {
state: [{ required: true, message: '请选择状态' }] state: [{ required: true, message: '请选择状态' }]
} }
const statusList = shallowRef([
{ label: '账号已添加', value: stateEnum.ADD_RELATION },
{ label: '账号不存在', value: stateEnum.NO_EXIST },
{ label: '账号未通过', value: stateEnum.UN_PASS }
])
onLoad(async option => { onLoad(async option => {
if (option?.id) { if (option?.id) {
id.value = option.id id.value = option.id

View File

@ -101,6 +101,7 @@ const onInput = e => {
const onClear = () => { const onClear = () => {
innerValue.value = '' innerValue.value = ''
emit('update:modelValue', '') emit('update:modelValue', '')
emit('onInput')
} }
</script> </script>

View File

@ -48,7 +48,7 @@ const props = defineProps({
} }
}) })
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const innerValue = ref('') const innerValue = ref(props.modelValue)
const labelStyle = computed(() => { const labelStyle = computed(() => {
const { labelWidth } = props const { labelWidth } = props
return { return {

View File

@ -40,7 +40,9 @@
<view <view
class="flex gap-[20rpx]" class="flex gap-[20rpx]"
v-if=" v-if="
item.situation == converStatusEnum.CONVERTED_PROCESS && item.state !== null (item.situation == converStatusEnum.ADD_RELATION ||
item.situation == converStatusEnum.EXCEPTION) &&
item.state !== null
" "
> >
<text class="text-muted w-[128rpx]">状态</text> <text class="text-muted w-[128rpx]">状态</text>
@ -54,8 +56,8 @@
" "
> >
<text class="text-muted w-[128rpx]">备注</text> <text class="text-muted w-[128rpx]">备注</text>
<view class="flex gap-[12rpx]"> <view class="flex gap-[12rpx] flex-1">
<text class="flex-1 text-error">已交一部分定位金</text> <text class="text-error">{{ item.remark }}</text>
<view <view
class="flex gap-[4rpx] items-center text-primary text-[28rpx]" class="flex gap-[4rpx] items-center text-primary text-[28rpx]"
@click="handleUpdateRemark" @click="handleUpdateRemark"
@ -67,13 +69,14 @@
</view> </view>
<view class="flex gap-[20rpx]" v-if="item.situation == converStatusEnum.CONVERTED"> <view class="flex gap-[20rpx]" v-if="item.situation == converStatusEnum.CONVERTED">
<text class="text-muted w-[128rpx]">成交时间</text> <text class="text-muted w-[128rpx]">成交时间</text>
<text class="flex-1">2025-02-10 16:04:00</text> <text class="flex-1">{{ item.updateTime }}</text>
</view> </view>
</view> </view>
</template> </template>
<template #action> <template #action>
<view <view
class="px-[32rpx] border-t border-solid border-[#F3F3F3] flex justify-end py-[12rpx]" class="px-[32rpx] border-t border-solid border-[#F3F3F3] flex justify-end py-[12rpx]"
v-if="!showAction"
> >
<view class="flex justify-end gap-[16rpx]"> <view class="flex justify-end gap-[16rpx]">
<u-button <u-button
@ -128,6 +131,14 @@ const stateMap: Record<stateEnum, string> = {
[stateEnum.UN_PASS]: '账号未通过' [stateEnum.UN_PASS]: '账号未通过'
} }
const parseStateText = computed(() => stateMap[props.item.state]) const parseStateText = computed(() => stateMap[props.item.state])
const showAction = computed(() => {
const { situation } = props.item
return (
situation == converStatusEnum.EXCEPTION ||
situation == converStatusEnum.CONVERTED ||
situation == converStatusEnum.FAILED
)
})
// //
const handleGet = async () => { const handleGet = async () => {
const { const {

View File

@ -33,7 +33,7 @@
</view> </view>
<view class="flex gap-[20rpx]" v-if="item.state !== null"> <view class="flex gap-[20rpx]" v-if="item.state !== null">
<text class="text-muted w-[128rpx]">状态</text> <text class="text-muted w-[128rpx]">状态</text>
<text class="flex-1">账号不存在</text> <text class="flex-1 text-orage">{{ parseStateText }}</text>
</view> </view>
</template> </template>
<template #action> <template #action>
@ -50,8 +50,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { PropType } from 'vue' import { computed, PropType } from 'vue'
import { computed } from 'vue'
import { converStatusEnum, stateEnum } from '@/enums' import { converStatusEnum, stateEnum } from '@/enums'
export interface IClue { export interface IClue {
@ -69,6 +68,7 @@ export interface IClue {
recruitTeacherName: string // recruitTeacherName: string //
recruitTeacherId: number recruitTeacherId: number
telemarketingTeacherId: number telemarketingTeacherId: number
updateTime: string
} }
const props = defineProps({ const props = defineProps({
item: { item: {
@ -82,6 +82,13 @@ const ellipsisDesc = computed(
? item.basicInformation?.slice(0, 14) + '...' ? item.basicInformation?.slice(0, 14) + '...'
: item.basicInformation : item.basicInformation
) )
const stateMap: Record<stateEnum, string> = {
[stateEnum.ADD_RELATION]: '账号已添加',
[stateEnum.NO_EXIST]: '账号不存在',
[stateEnum.UN_PASS]: '账号未通过'
}
const parseStateText = computed(() => stateMap[props.item.state])
const handleEditFollow = () => { const handleEditFollow = () => {
const { item } = props const { item } = props
uni.navigateTo({ uni.navigateTo({

View File

@ -25,11 +25,12 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useZPaging } from '@/hooks/useZPaging' import { onLoad } from '@dcloudio/uni-app'
import { ref } from 'vue' import { ref } from 'vue'
import clueCard from './clue-card.vue'
import { apiCluseList } from '@/api/clue'
import { debounce } from 'lodash-es' import { debounce } from 'lodash-es'
import clueCard from './clue-card.vue'
import { useZPaging } from '@/hooks/useZPaging'
import { apiTeleClueList } from '@/api/clue'
const queryParams = ref({ const queryParams = ref({
likeWork: '' likeWork: ''
@ -37,11 +38,17 @@ const queryParams = ref({
const dataList = ref([]) const dataList = ref([])
const { paging, queryList, refresh, changeApi, setParams } = useZPaging( const { paging, queryList, refresh, changeApi, setParams } = useZPaging(
queryParams.value, queryParams.value,
apiCluseList, apiTeleClueList,
() => {} () => {}
) )
const searchChange = debounce(() => { const searchChange = debounce(() => {
refresh() refresh()
}, 300) }, 300)
onLoad(() => {
uni.$on('refreshPage', () => {
refresh(queryParams.value)
})
})
</script> </script>
<style scoped></style> <style scoped></style>

View File

@ -1,3 +1,4 @@
import { stateEnum } from '@/enums'
import { SexEnum } from '@/enums/appEnums' import { SexEnum } from '@/enums/appEnums'
import { import {
OrderStatusEnum, OrderStatusEnum,
@ -175,5 +176,11 @@ export const optionsMap = {
label: infoCheckMap[InfoCheckEnum.RETURN], label: infoCheckMap[InfoCheckEnum.RETURN],
value: InfoCheckEnum.RETURN value: InfoCheckEnum.RETURN
} }
],
stateOptions: [
{ label: '账号已添加', value: stateEnum.ADD_RELATION },
{ label: '账号不存在', value: stateEnum.NO_EXIST },
{ label: '账号未通过', value: stateEnum.UN_PASS }
] ]
} }

View File

@ -5,6 +5,7 @@
placeholder="搜索客户姓名/手机号码" placeholder="搜索客户姓名/手机号码"
backgroundColor="#F5F5F5" backgroundColor="#F5F5F5"
showBorder showBorder
@on-input="searchChange"
/> />
<u-tabs <u-tabs
:list="tabs" :list="tabs"
@ -41,15 +42,16 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { apiOverhaulPagelist } from '@/api/overhaul'
import { useZPaging } from '@/hooks/useZPaging' import { useZPaging } from '@/hooks/useZPaging'
import { ref } from 'vue' import { ref } from 'vue'
import clueCard from '@/components/widgets/recruitsale/clue-card.vue' import clueCard from '@/components/widgets/recruitsale/clue-card.vue'
import { shallowRef } from 'vue' import { shallowRef } from 'vue'
import { computed } from 'vue' import { computed } from 'vue'
import { apiCluseList } from '@/api/clue' import { apiCluseList, apiEditRemark } from '@/api/clue'
import { converStatusEnum } from '@/enums' import { converStatusEnum } from '@/enums'
import { onLoad } from '@dcloudio/uni-app' import { onLoad } from '@dcloudio/uni-app'
import { debounce } from 'lodash-es'
import { toast } from '@/utils/util'
const tabs = shallowRef([ const tabs = shallowRef([
{ name: '待领取', value: converStatusEnum.UN_RECEIVED }, { name: '待领取', value: converStatusEnum.UN_RECEIVED },
@ -77,16 +79,31 @@ const handleChangeTab = item => {
} }
const popupShow = ref(false) const popupShow = ref(false)
const contentText = ref('') const contentText = ref('')
const clueId = ref('')
const handleUpdateRemark = item => { const handleUpdateRemark = item => {
const { id, remark } = item const { id, remark } = item
popupShow.value = true popupShow.value = true
contentText.value = remark contentText.value = remark
clueId.value = id
}
const handleConfirm = async () => {
try {
const params = {
id: clueId.value,
remark: contentText.value
}
await apiEditRemark(params)
toast('修改备注成功')
refresh(queryParams.value)
} catch (error) {}
} }
const handleConfirm = () => {}
onLoad(() => { onLoad(() => {
uni.$on('refreshPage', () => { uni.$on('refreshPage', () => {
refresh(queryParams.value) refresh(queryParams.value)
}) })
}) })
const searchChange = debounce(() => {
refresh()
}, 300)
</script> </script>
<style scoped></style> <style scoped></style>

View File

@ -290,6 +290,6 @@ page {
} }
.u-tabbar { .u-tabbar {
.u-tabbar__content { .u-tabbar__content {
z-index: 99999 !important; z-index: 999 !important;
} }
} }