Browse Source

feat(admin): 添加跟进管理功能- 新增跟进管理列表页面,包含搜索、筛选、分页等功能

- 添加跟进管理编辑页面,支持添加和编辑跟进记录
- 实现跟进记录的删除功能- 集成字典数据,支持低中高、是否、客户状态等选项的动态加载- 添加销售关联、跟进人员等下拉选项的数据获取接口
master
liutong 1 year ago
parent
commit
11565ecddc
  1. 2
      admin/components.d.ts
  2. 58
      admin/src/addon/zhjw/api/follow_up_logs.ts
  3. 51
      admin/src/addon/zhjw/lang/zh-cn/follow_up_logs.follow_up_logs.json
  4. 55
      admin/src/addon/zhjw/lang/zh-cn/follow_up_logs.follow_up_logs_edit.json
  5. 555
      admin/src/addon/zhjw/views/follow_up_logs/follow_up_logs.vue
  6. 604
      admin/src/addon/zhjw/views/follow_up_logs/follow_up_logs_edit.vue
  7. 58
      niucloud/addon/zhjw/admin/api/follow_up_logs.ts
  8. 51
      niucloud/addon/zhjw/admin/lang/zh-cn/follow_up_logs.follow_up_logs.json
  9. 55
      niucloud/addon/zhjw/admin/lang/zh-cn/follow_up_logs.follow_up_logs_edit.json
  10. 517
      niucloud/addon/zhjw/admin/views/follow_up_logs/follow_up_logs.vue
  11. 563
      niucloud/addon/zhjw/admin/views/follow_up_logs/follow_up_logs_edit.vue
  12. 157
      niucloud/addon/zhjw/app/adminapi/controller/follow_up_logs/FollowUpLogs.php
  13. 27
      niucloud/addon/zhjw/app/adminapi/route/route.php
  14. 310
      niucloud/addon/zhjw/app/model/follow_up_logs/FollowUpLogs.php
  15. 115
      niucloud/addon/zhjw/app/service/admin/follow_up_logs/FollowUpLogsService.php
  16. 43
      niucloud/addon/zhjw/app/validate/follow_up_logs/FollowUpLogs.php

2
admin/components.d.ts

@ -16,9 +16,9 @@ declare module '@vue/runtime-core' {
ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
ElButton: typeof import('element-plus/es')['ElButton']
ElCalendar: typeof import('element-plus/es')['ElCalendar']
ElCard: typeof import('element-plus/es')['ElCard']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
ElCol: typeof import('element-plus/es')['ElCol']
ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']

58
admin/src/addon/zhjw/api/follow_up_logs.ts

@ -0,0 +1,58 @@
import request from '@/utils/request'
// USER_CODE_BEGIN -- zhjw_follow_up_logs
/**
*
* @param params
* @returns
*/
export function getFollowUpLogsList(params: Record<string, any>) {
return request.get(`zhjw/follow_up_logs`, {params})
}
/**
*
* @param id id
* @returns
*/
export function getFollowUpLogsInfo(id: number) {
return request.get(`zhjw/follow_up_logs/${id}`);
}
/**
*
* @param params
* @returns
*/
export function addFollowUpLogs(params: Record<string, any>) {
return request.post('zhjw/follow_up_logs', params, {showErrorMessage: true, showSuccessMessage: true})
}
/**
*
* @param id
* @param params
* @returns
*/
export function editFollowUpLogs(params: Record<string, any>) {
return request.put(`zhjw/follow_up_logs/${params.id}`, params, {showErrorMessage: true, showSuccessMessage: true})
}
/**
*
* @param id
* @returns
*/
export function deleteFollowUpLogs(id: number) {
return request.delete(`zhjw/follow_up_logs/${id}`, {showErrorMessage: true, showSuccessMessage: true})
}
export function getWithSalesList(params: Record<string, any>) {
return request.get('zhjw/sales_all', {params})
}
export function getWithStaffList(params: Record<string, any>) {
return request.get('zhjw/staff_all', {params})
}
// USER_CODE_END -- zhjw_follow_up_logs

51
admin/src/addon/zhjw/lang/zh-cn/follow_up_logs.follow_up_logs.json

@ -0,0 +1,51 @@
{
"id":"序号",
"salesId":"销售关联",
"salesIdPlaceholder":"全部",
"staffId":"跟进人员",
"staffIdPlaceholder":"全部",
"roleId":"跟进人员角色",
"entryType":"填写人员类型",
"entryTypePlaceholder":"请输入填写人员类型",
"requirement":"需求",
"purchasingPower":"购买力",
"purchasingPowerPlaceholder":"请输入购买力",
"cognitiveConcept":"认知理念",
"cognitiveConceptPlaceholder":"请输入认知理念",
"schooltime":"时间",
"schooltimePlaceholder":"请输入时间",
"distance":"距离",
"communicationNotes":"沟通备注",
"decisionMaker":"决策人",
"emotionalIntensity":"情感粘度",
"emotionalIntensityPlaceholder":"请输入情感粘度",
"initialCustomerIntent":"客户初步意向度",
"initialCustomerIntentPlaceholder":"请输入客户初步意向度",
"initialRelationshipIntent":"客情初步意向度",
"initialRelationshipIntentPlaceholder":"请输入客情初步意向度",
"promisedVisitDate":"承诺到访时间",
"promisedVisitDatePlaceholder":"请输入承诺到访时间",
"actualVisitDate":"实际到访时间",
"actualVisitDatePlaceholder":"请输入实际到访时间",
"firstVisitFeedback":"当面资讯反馈-第1次访问情况",
"secondVisitFeedback":"当面资讯反馈-第2次访问情况",
"isClosedDeal":"是否关单",
"isClosedDealPlaceholder":"请输入是否关单",
"followUpType":"跟进类型",
"followUpTypePlaceholder":"请输入跟进类型",
"followUpTime":"跟进时间",
"followUpTimePlaceholder":"请输入跟进时间",
"audioUpload":"上传录音(单文件)",
"followUpContent":"跟进内容",
"customerStatus":"客户状态",
"customerStatusPlaceholder":"请输入客户状态",
"signUpContactId":"签单意向联系人(员工id)",
"signUpContactIdPlaceholder":"全部",
"createTime":"添加时间",
"createTimePlaceholder":"请输入添加时间",
"addFollowUpLogs":"添加跟进管理",
"updateFollowUpLogs":"编辑跟进管理",
"followUpLogsDeleteTips":"确定要删除该数据吗?",
"startDate":"请选择开始时间",
"endDate":"请选择结束时间"
}

55
admin/src/addon/zhjw/lang/zh-cn/follow_up_logs.follow_up_logs_edit.json

@ -0,0 +1,55 @@
{
"salesId":"销售关联",
"staffId":"跟进人员",
"roleId":"跟进人员角色",
"entryType":"填写人员类型",
"requirement":"需求",
"purchasingPower":"购买力",
"cognitiveConcept":"认知理念",
"schooltime":"时间",
"distance":"距离",
"communicationNotes":"沟通备注",
"decisionMaker":"决策人",
"emotionalIntensity":"情感粘度",
"initialCustomerIntent":"客户初步意向度",
"initialRelationshipIntent":"客情初步意向度",
"promisedVisitDate":"承诺到访时间",
"actualVisitDate":"实际到访时间",
"firstVisitFeedback":"当面资讯反馈-第1次访问情况",
"secondVisitFeedback":"当面资讯反馈-第2次访问情况",
"isClosedDeal":"是否关单",
"followUpType":"跟进类型",
"followUpTime":"跟进时间",
"audioUpload":"上传录音(单文件)",
"followUpContent":"跟进内容",
"customerStatus":"客户状态",
"signUpContactId":"签单意向联系人(员工id)",
"salesIdPlaceholder":"请选择销售关联",
"staffIdPlaceholder":"请选择跟进人员",
"roleIdPlaceholder":"请输入跟进人员角色",
"entryTypePlaceholder":"请选择填写人员类型",
"requirementPlaceholder":"请输入需求",
"purchasingPowerPlaceholder":"请选择购买力",
"cognitiveConceptPlaceholder":"请选择认知理念",
"schooltimePlaceholder":"请选择时间",
"distancePlaceholder":"请输入距离",
"communicationNotesPlaceholder":"请输入沟通备注",
"decisionMakerPlaceholder":"请输入决策人",
"emotionalIntensityPlaceholder":"请选择情感粘度",
"initialCustomerIntentPlaceholder":"请选择客户初步意向度",
"initialRelationshipIntentPlaceholder":"请选择客情初步意向度",
"promisedVisitDatePlaceholder":"请选择承诺到访时间",
"actualVisitDatePlaceholder":"请选择实际到访时间",
"firstVisitFeedbackPlaceholder":"请输入当面资讯反馈-第1次访问情况",
"secondVisitFeedbackPlaceholder":"请输入当面资讯反馈-第2次访问情况",
"isClosedDealPlaceholder":"请选择是否关单",
"followUpTypePlaceholder":"请选择跟进类型",
"followUpTimePlaceholder":"请选择跟进时间",
"audioUploadPlaceholder":"请输入上传录音(单文件)",
"followUpContentPlaceholder":"请输入跟进内容",
"customerStatusPlaceholder":"请选择客户状态",
"signUpContactIdPlaceholder":"请选择签单意向联系人(员工id)",
"addFollowUpLogs":"添加跟进管理",
"updateFollowUpLogs":"编辑跟进管理",
"followUpLogsDeleteTips":"确定要删除该跟进管理吗?"
}

555
admin/src/addon/zhjw/views/follow_up_logs/follow_up_logs.vue

@ -0,0 +1,555 @@
<template>
<div class="main-container">
<el-card class="box-card !border-none" shadow="never">
<div class="flex justify-between items-center">
<span class="text-lg">{{ pageName }}</span>
<el-button type="primary" @click="addEvent">
{{ t('addFollowUpLogs') }}
</el-button>
</div>
<el-card class="box-card !border-none my-[10px] table-search-wrap" shadow="never">
<el-form :inline="true" :model="followUpLogsTable.searchParam" ref="searchFormRef">
<el-form-item :label="t('salesId')" prop="sales_id">
<el-select class="w-[280px]" v-model="followUpLogsTable.searchParam.sales_id" clearable
:placeholder="t('salesIdPlaceholder')">
<el-option
v-for="(item, index) in salesIdList"
:key="index"
:label="item['student_name']"
:value="item['id']"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('staffId')" prop="staff_id">
<el-select class="w-[280px]" v-model="followUpLogsTable.searchParam.staff_id" clearable
:placeholder="t('staffIdPlaceholder')">
<el-option
v-for="(item, index) in staffIdList"
:key="index"
:label="item['name']"
:value="item['id']"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('entryType')" prop="entry_type">
<el-select class="w-[280px]" v-model="followUpLogsTable.searchParam.entry_type" clearable
:placeholder="t('entryTypePlaceholder')">
<el-option label="全部" value=""></el-option>
<el-option
v-for="(item, index) in entry_typeList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('purchasingPower')" prop="purchasing_power">
<el-select class="w-[280px]" v-model="followUpLogsTable.searchParam.purchasing_power" clearable
:placeholder="t('purchasingPowerPlaceholder')">
<el-option label="全部" value=""></el-option>
<el-option
v-for="(item, index) in purchasing_powerList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('cognitiveConcept')" prop="cognitive_concept">
<el-select class="w-[280px]" v-model="followUpLogsTable.searchParam.cognitive_concept" clearable
:placeholder="t('cognitiveConceptPlaceholder')">
<el-option label="全部" value=""></el-option>
<el-option
v-for="(item, index) in cognitive_conceptList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('schooltime')" prop="schooltime">
<el-date-picker v-model="followUpLogsTable.searchParam.schooltime" type="datetimerange"
format="YYYY-MM-DD hh:mm:ss"
:start-placeholder="t('startDate')" :end-placeholder="t('endDate')"/>
</el-form-item>
<el-form-item :label="t('emotionalIntensity')" prop="emotional_intensity">
<el-select class="w-[280px]" v-model="followUpLogsTable.searchParam.emotional_intensity" clearable
:placeholder="t('emotionalIntensityPlaceholder')">
<el-option label="全部" value=""></el-option>
<el-option
v-for="(item, index) in emotional_intensityList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('initialCustomerIntent')" prop="initial_customer_intent">
<el-select class="w-[280px]" v-model="followUpLogsTable.searchParam.initial_customer_intent" clearable
:placeholder="t('initialCustomerIntentPlaceholder')">
<el-option label="全部" value=""></el-option>
<el-option
v-for="(item, index) in initial_customer_intentList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('initialRelationshipIntent')" prop="initial_relationship_intent">
<el-select class="w-[280px]" v-model="followUpLogsTable.searchParam.initial_relationship_intent" clearable
:placeholder="t('initialRelationshipIntentPlaceholder')">
<el-option label="全部" value=""></el-option>
<el-option
v-for="(item, index) in initial_relationship_intentList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('promisedVisitDate')" prop="promised_visit_date">
<el-date-picker v-model="followUpLogsTable.searchParam.promised_visit_date" type="datetimerange"
format="YYYY-MM-DD hh:mm:ss"
:start-placeholder="t('startDate')" :end-placeholder="t('endDate')"/>
</el-form-item>
<el-form-item :label="t('actualVisitDate')" prop="actual_visit_date">
<el-date-picker v-model="followUpLogsTable.searchParam.actual_visit_date" type="datetimerange"
format="YYYY-MM-DD hh:mm:ss"
:start-placeholder="t('startDate')" :end-placeholder="t('endDate')"/>
</el-form-item>
<el-form-item :label="t('isClosedDeal')" prop="is_closed_deal">
<el-select class="w-[280px]" v-model="followUpLogsTable.searchParam.is_closed_deal" clearable
:placeholder="t('isClosedDealPlaceholder')">
<el-option label="全部" value=""></el-option>
<el-option
v-for="(item, index) in is_closed_dealList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('followUpType')" prop="follow_up_type">
<el-select class="w-[280px]" v-model="followUpLogsTable.searchParam.follow_up_type" clearable
:placeholder="t('followUpTypePlaceholder')">
<el-option label="全部" value=""></el-option>
<el-option
v-for="(item, index) in follow_up_typeList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('followUpTime')" prop="follow_up_time">
<el-date-picker v-model="followUpLogsTable.searchParam.follow_up_time" type="datetimerange"
format="YYYY-MM-DD hh:mm:ss"
:start-placeholder="t('startDate')" :end-placeholder="t('endDate')"/>
</el-form-item>
<el-form-item :label="t('customerStatus')" prop="customer_status">
<el-select class="w-[280px]" v-model="followUpLogsTable.searchParam.customer_status" clearable
:placeholder="t('customerStatusPlaceholder')">
<el-option label="全部" value=""></el-option>
<el-option
v-for="(item, index) in customer_statusList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('signUpContactId')" prop="sign_up_contact_id">
<el-select class="w-[280px]" v-model="followUpLogsTable.searchParam.sign_up_contact_id" clearable
:placeholder="t('signUpContactIdPlaceholder')">
<el-option
v-for="(item, index) in signUpContactIdList"
:key="index"
:label="item['name']"
:value="item['id']"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('createTime')" prop="create_time">
<el-date-picker v-model="followUpLogsTable.searchParam.create_time" type="datetimerange"
format="YYYY-MM-DD hh:mm:ss"
:start-placeholder="t('startDate')" :end-placeholder="t('endDate')"/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="loadFollowUpLogsList()">{{ t('search') }}</el-button>
<el-button @click="resetForm(searchFormRef)">{{ t('reset') }}</el-button>
</el-form-item>
</el-form>
</el-card>
<div class="mt-[10px]">
<el-table :data="followUpLogsTable.data" size="large" v-loading="followUpLogsTable.loading">
<template #empty>
<span>{{ !followUpLogsTable.loading ? t('emptyData') : '' }}</span>
</template>
<el-table-column prop="id" :label="t('id')" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column prop="sales_id_name" :label="t('salesId')" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column prop="staff_id_name" :label="t('staffId')" min-width="120" :show-overflow-tooltip="true"/>
<!-- <el-table-column prop="role_id" :label="t('roleId')" min-width="120" :show-overflow-tooltip="true"/>-->
<el-table-column :label="t('entryType')" min-width="180" align="center" :show-overflow-tooltip="true">
<template #default="{ row }">
<div v-for="(item, index) in entry_typeList">
<div v-if="item.value == row.entry_type">{{ item.name }}</div>
</div>
</template>
</el-table-column>
<el-table-column prop="requirement" :label="t('requirement')" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column :label="t('purchasingPower')" min-width="180" align="center" :show-overflow-tooltip="true">
<template #default="{ row }">
<div v-for="(item, index) in purchasing_powerList">
<div v-if="item.value == row.purchasing_power">{{ item.name }}</div>
</div>
</template>
</el-table-column>
<el-table-column :label="t('cognitiveConcept')" min-width="180" align="center" :show-overflow-tooltip="true">
<template #default="{ row }">
<div v-for="(item, index) in cognitive_conceptList">
<div v-if="item.value == row.cognitive_concept">{{ item.name }}</div>
</div>
</template>
</el-table-column>
<el-table-column prop="schooltime" :label="t('schooltime')" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column prop="distance" :label="t('distance')" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column prop="communication_notes" :label="t('communicationNotes')" min-width="120"
:show-overflow-tooltip="true"/>
<el-table-column prop="decision_maker" :label="t('decisionMaker')" min-width="120"
:show-overflow-tooltip="true"/>
<el-table-column :label="t('emotionalIntensity')" min-width="180" align="center"
:show-overflow-tooltip="true">
<template #default="{ row }">
<div v-for="(item, index) in emotional_intensityList">
<div v-if="item.value == row.emotional_intensity">{{ item.name }}</div>
</div>
</template>
</el-table-column>
<el-table-column :label="t('initialCustomerIntent')" min-width="180" align="center"
:show-overflow-tooltip="true">
<template #default="{ row }">
<div v-for="(item, index) in initial_customer_intentList">
<div v-if="item.value == row.initial_customer_intent">{{ item.name }}</div>
</div>
</template>
</el-table-column>
<el-table-column :label="t('initialRelationshipIntent')" min-width="180" align="center"
:show-overflow-tooltip="true">
<template #default="{ row }">
<div v-for="(item, index) in initial_relationship_intentList">
<div v-if="item.value == row.initial_relationship_intent">{{ item.name }}</div>
</div>
</template>
</el-table-column>
<el-table-column prop="promised_visit_date" :label="t('promisedVisitDate')" min-width="120"
:show-overflow-tooltip="true"/>
<el-table-column prop="actual_visit_date" :label="t('actualVisitDate')" min-width="120"
:show-overflow-tooltip="true"/>
<el-table-column prop="first_visit_feedback" :label="t('firstVisitFeedback')" min-width="120"
:show-overflow-tooltip="true"/>
<el-table-column prop="second_visit_feedback" :label="t('secondVisitFeedback')" min-width="120"
:show-overflow-tooltip="true"/>
<el-table-column :label="t('isClosedDeal')" min-width="180" align="center" :show-overflow-tooltip="true">
<template #default="{ row }">
<div v-for="(item, index) in is_closed_dealList">
<div v-if="item.value == row.is_closed_deal">{{ item.name }}</div>
</div>
</template>
</el-table-column>
<el-table-column :label="t('followUpType')" min-width="180" align="center" :show-overflow-tooltip="true">
<template #default="{ row }">
<div v-for="(item, index) in follow_up_typeList">
<div v-if="item.value == row.follow_up_type">{{ item.name }}</div>
</div>
</template>
</el-table-column>
<el-table-column prop="follow_up_time" :label="t('followUpTime')" min-width="120"
:show-overflow-tooltip="true"/>
<el-table-column prop="audio_upload" :label="t('audioUpload')" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column prop="follow_up_content" :label="t('followUpContent')" min-width="120"
:show-overflow-tooltip="true"/>
<el-table-column :label="t('customerStatus')" min-width="180" align="center" :show-overflow-tooltip="true">
<template #default="{ row }">
<div v-for="(item, index) in customer_statusList">
<div v-if="item.value == row.customer_status">{{ item.name }}</div>
</div>
</template>
</el-table-column>
<el-table-column prop="sign_up_contact_id_name" :label="t('signUpContactId')" min-width="120"
:show-overflow-tooltip="true"/>
<el-table-column :label="t('createTime')" min-width="180" align="center" :show-overflow-tooltip="true">
<template #default="{ row }">
{{ row.create_time || '' }}
</template>
</el-table-column>
<el-table-column :label="t('operation')" fixed="right" min-width="120">
<template #default="{ row }">
<el-button type="primary" link @click="editEvent(row)">{{ t('edit') }}</el-button>
<el-button type="primary" link @click="deleteEvent(row.id)">{{ t('delete') }}</el-button>
</template>
</el-table-column>
</el-table>
<div class="mt-[16px] flex justify-end">
<el-pagination v-model:current-page="followUpLogsTable.page" v-model:page-size="followUpLogsTable.limit"
layout="total, sizes, prev, pager, next, jumper" :total="followUpLogsTable.total"
@size-change="loadFollowUpLogsList()" @current-change="loadFollowUpLogsList"/>
</div>
</div>
</el-card>
</div>
</template>
<script lang="ts" setup>
import {reactive, ref, watch} from 'vue'
import {t} from '@/lang'
import {useDictionary} from '@/app/api/dict'
import {
getFollowUpLogsList,
deleteFollowUpLogs,
getWithSalesList,
getWithStaffList,
} from '@/addon/zhjw/api/follow_up_logs'
import {img} from '@/utils/common'
import {ElMessageBox, FormInstance} from 'element-plus'
import {useRouter} from 'vue-router'
import {useRoute} from 'vue-router'
const route = useRoute()
const pageName = route.meta.title;
let followUpLogsTable = reactive({
page: 1,
limit: 10,
total: 0,
loading: true,
data: [],
searchParam: {
"sales_id": "",
"staff_id": "",
"entry_type": "",
"purchasing_power": "",
"cognitive_concept": "",
"schooltime": [],
"emotional_intensity": "",
"initial_customer_intent": "",
"initial_relationship_intent": "",
"promised_visit_date": [],
"actual_visit_date": [],
"is_closed_deal": "",
"follow_up_type": "",
"follow_up_time": [],
"customer_status": "",
"sign_up_contact_id": "",
"create_time": []
}
})
const searchFormRef = ref<FormInstance>()
//
const selectData = ref<any[]>([])
//
const entry_typeList = ref([] as any[])
const entry_typeDictList = async () => {
entry_typeList.value = await (await useDictionary('entry_type')).data.dictionary
}
entry_typeDictList();
const purchasing_powerList = ref([] as any[])
const purchasing_powerDictList = async () => {
purchasing_powerList.value = await (await useDictionary('low_middle_high')).data.dictionary
}
purchasing_powerDictList();
const cognitive_conceptList = ref([] as any[])
const cognitive_conceptDictList = async () => {
cognitive_conceptList.value = await (await useDictionary('low_middle_high')).data.dictionary
}
cognitive_conceptDictList();
const emotional_intensityList = ref([] as any[])
const emotional_intensityDictList = async () => {
emotional_intensityList.value = await (await useDictionary('low_middle_high')).data.dictionary
}
emotional_intensityDictList();
const initial_customer_intentList = ref([] as any[])
const initial_customer_intentDictList = async () => {
initial_customer_intentList.value = await (await useDictionary('initial_customer_intent')).data.dictionary
}
initial_customer_intentDictList();
const initial_relationship_intentList = ref([] as any[])
const initial_relationship_intentDictList = async () => {
initial_relationship_intentList.value = await (await useDictionary('initial_relationship_intent')).data.dictionary
}
initial_relationship_intentDictList();
const is_closed_dealList = ref([] as any[])
const is_closed_dealDictList = async () => {
is_closed_dealList.value = await (await useDictionary('is_radio')).data.dictionary
}
is_closed_dealDictList();
const follow_up_typeList = ref([] as any[])
const follow_up_typeDictList = async () => {
follow_up_typeList.value = await (await useDictionary('follow_up_type')).data.dictionary
}
follow_up_typeDictList();
const customer_statusList = ref([] as any[])
const customer_statusDictList = async () => {
customer_statusList.value = await (await useDictionary('customer_status')).data.dictionary
}
customer_statusDictList();
/**
* 获取跟进管理列表
*/
const loadFollowUpLogsList = (page: number = 1) => {
followUpLogsTable.loading = true
followUpLogsTable.page = page
getFollowUpLogsList({
page: followUpLogsTable.page,
limit: followUpLogsTable.limit,
...followUpLogsTable.searchParam
}).then(res => {
followUpLogsTable.loading = false
followUpLogsTable.data = res.data.data
followUpLogsTable.total = res.data.total
}).catch(() => {
followUpLogsTable.loading = false
})
}
loadFollowUpLogsList()
const router = useRouter()
/**
* 添加跟进管理
*/
const addEvent = () => {
router.push('/follow_up_logs/follow_up_logs_edit')
}
/**
* 编辑跟进管理
* @param data
*/
const editEvent = (data: any) => {
router.push('/follow_up_logs/follow_up_logs_edit?id=' + data.id)
}
/**
* 删除跟进管理
*/
const deleteEvent = (id: number) => {
ElMessageBox.confirm(t('followUpLogsDeleteTips'), t('warning'),
{
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning',
}
).then(() => {
deleteFollowUpLogs(id).then(() => {
loadFollowUpLogsList()
}).catch(() => {
})
})
}
const salesIdList = ref([])
const setSalesIdList = async () => {
salesIdList.value = await (await getWithSalesList({})).data
}
setSalesIdList()
const staffIdList = ref([])
const setStaffIdList = async () => {
staffIdList.value = await (await getWithStaffList({})).data
}
setStaffIdList()
const signUpContactIdList = ref([])
const setSignUpContactIdList = async () => {
signUpContactIdList.value = await (await getWithStaffList({})).data
}
setSignUpContactIdList()
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.resetFields()
loadFollowUpLogsList()
}
</script>
<style lang="scss" scoped>
/* 多行超出隐藏 */
.multi-hidden {
word-break: break-all;
text-overflow: ellipsis;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
</style>

604
admin/src/addon/zhjw/views/follow_up_logs/follow_up_logs_edit.vue

@ -0,0 +1,604 @@
<template>
<div class="main-container">
<div class="detail-head">
<div class="left" @click="back()">
<span class="iconfont iconxiangzuojiantou !text-xs"></span>
<span class="ml-[1px]">{{ t('returnToPreviousPage') }}</span>
</div>
<span class="adorn">|</span>
<span class="right">{{ pageName }}</span>
</div>
<el-card class="box-card !border-none" shadow="never">
<el-form :model="formData" label-width="90px" ref="formRef" :rules="formRules" class="page-form">
<el-form-item :label="t('salesId')" prop="sales_id">
<el-select class="input-width" v-model="formData.sales_id" clearable :placeholder="t('salesIdPlaceholder')">
<el-option label="请选择" value=""></el-option>
<el-option
v-for="(item, index) in salesIdList"
:key="index"
:label="item['student_name']"
:value="item['id']"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('staffId')" prop="staff_id">
<el-select class="input-width" v-model="formData.staff_id" clearable :placeholder="t('staffIdPlaceholder')">
<el-option label="请选择" value=""></el-option>
<el-option
v-for="(item, index) in staffIdList"
:key="index"
:label="item['name']"
:value="item['id']"
/>
</el-select>
</el-form-item>
<!-- <el-form-item :label="t('roleId')" prop="role_id">-->
<!-- <el-input v-model="formData.role_id" clearable :placeholder="t('roleIdPlaceholder')" class="input-width"/>-->
<!-- </el-form-item>-->
<el-form-item :label="t('entryType')" prop="entry_type">
<el-select class="input-width" v-model="formData.entry_type" clearable
:placeholder="t('entryTypePlaceholder')">
<el-option label="请选择" value=""></el-option>
<el-option
v-for="(item, index) in entry_typeList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('requirement')">
<el-input v-model="formData.requirement" type="textarea" rows="4" clearable
:placeholder="t('requirementPlaceholder')" class="input-width"/>
</el-form-item>
<el-form-item :label="t('purchasingPower')">
<el-select class="input-width" v-model="formData.purchasing_power" clearable
:placeholder="t('purchasingPowerPlaceholder')">
<el-option label="请选择" value=""></el-option>
<el-option
v-for="(item, index) in purchasing_powerList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('cognitiveConcept')">
<el-select class="input-width" v-model="formData.cognitive_concept" clearable
:placeholder="t('cognitiveConceptPlaceholder')">
<el-option label="请选择" value=""></el-option>
<el-option
v-for="(item, index) in cognitive_conceptList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('schooltime')" class="input-width">
<el-date-picker
class="flex-1 !flex"
v-model="formData.schooltime"
clearable
type="datetime"
value-format="YYYY-MM-DD HH:mm:ss"
:placeholder="t('schooltimePlaceholder')">
</el-date-picker>
</el-form-item>
<el-form-item :label="t('distance')">
<el-input v-model="formData.distance" clearable :placeholder="t('distancePlaceholder')" class="input-width"/>
</el-form-item>
<el-form-item :label="t('communicationNotes')">
<el-input v-model="formData.communication_notes" type="textarea" rows="4" clearable
:placeholder="t('communicationNotesPlaceholder')" class="input-width"/>
</el-form-item>
<el-form-item :label="t('decisionMaker')">
<el-input v-model="formData.decision_maker" clearable :placeholder="t('decisionMakerPlaceholder')"
class="input-width"/>
</el-form-item>
<el-form-item :label="t('emotionalIntensity')">
<el-select class="input-width" v-model="formData.emotional_intensity" clearable
:placeholder="t('emotionalIntensityPlaceholder')">
<el-option label="请选择" value=""></el-option>
<el-option
v-for="(item, index) in emotional_intensityList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('initialCustomerIntent')">
<el-select class="input-width" v-model="formData.initial_customer_intent" clearable
:placeholder="t('initialCustomerIntentPlaceholder')">
<el-option label="请选择" value=""></el-option>
<el-option
v-for="(item, index) in initial_customer_intentList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('initialRelationshipIntent')">
<el-select class="input-width" v-model="formData.initial_relationship_intent" clearable
:placeholder="t('initialRelationshipIntentPlaceholder')">
<el-option label="请选择" value=""></el-option>
<el-option
v-for="(item, index) in initial_relationship_intentList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('promisedVisitDate')" class="input-width">
<el-date-picker
class="flex-1 !flex"
v-model="formData.promised_visit_date"
clearable
type="datetime"
value-format="YYYY-MM-DD HH:mm:ss"
:placeholder="t('promisedVisitDatePlaceholder')">
</el-date-picker>
</el-form-item>
<el-form-item :label="t('actualVisitDate')" class="input-width">
<el-date-picker
class="flex-1 !flex"
v-model="formData.actual_visit_date"
clearable
type="datetime"
value-format="YYYY-MM-DD HH:mm:ss"
:placeholder="t('actualVisitDatePlaceholder')">
</el-date-picker>
</el-form-item>
<el-form-item :label="t('firstVisitFeedback')">
<el-input v-model="formData.first_visit_feedback" type="textarea" rows="4" clearable
:placeholder="t('firstVisitFeedbackPlaceholder')" class="input-width"/>
</el-form-item>
<el-form-item :label="t('secondVisitFeedback')">
<el-input v-model="formData.second_visit_feedback" type="textarea" rows="4" clearable
:placeholder="t('secondVisitFeedbackPlaceholder')" class="input-width"/>
</el-form-item>
<el-form-item :label="t('isClosedDeal')">
<el-select class="input-width" v-model="formData.is_closed_deal" clearable
:placeholder="t('isClosedDealPlaceholder')">
<el-option label="请选择" value=""></el-option>
<el-option
v-for="(item, index) in is_closed_dealList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('followUpType')">
<el-select class="input-width" v-model="formData.follow_up_type" clearable
:placeholder="t('followUpTypePlaceholder')">
<el-option label="请选择" value=""></el-option>
<el-option
v-for="(item, index) in follow_up_typeList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('followUpTime')" prop="follow_up_time" class="input-width">
<el-date-picker
class="flex-1 !flex"
v-model="formData.follow_up_time"
clearable
type="datetime"
value-format="YYYY-MM-DD HH:mm:ss"
:placeholder="t('followUpTimePlaceholder')">
</el-date-picker>
</el-form-item>
<el-form-item :label="t('audioUpload')">
<upload-file v-model="formData.audio_upload"/>
</el-form-item>
<el-form-item :label="t('followUpContent')">
<el-input v-model="formData.follow_up_content" type="textarea" rows="4" clearable
:placeholder="t('followUpContentPlaceholder')" class="input-width"/>
</el-form-item>
<el-form-item :label="t('customerStatus')">
<el-select class="input-width" v-model="formData.customer_status" clearable
:placeholder="t('customerStatusPlaceholder')">
<el-option label="请选择" value=""></el-option>
<el-option
v-for="(item, index) in customer_statusList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('signUpContactId')">
<el-select class="input-width" v-model="formData.sign_up_contact_id" clearable
:placeholder="t('signUpContactIdPlaceholder')">
<el-option label="请选择" value=""></el-option>
<el-option
v-for="(item, index) in signUpContactIdList"
:key="index"
:label="item['name']"
:value="item['id']"
/>
</el-select>
</el-form-item>
</el-form>
</el-card>
<div class="fixed-footer-wrap">
<div class="fixed-footer">
<el-button type="primary" @click="onSave(formRef)">{{ t('save') }}</el-button>
<el-button @click="back()">{{ t('cancel') }}</el-button>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import {ref, reactive, computed, watch} from 'vue'
import {t} from '@/lang'
import {useDictionary} from '@/app/api/dict'
import type {FormInstance} from 'element-plus'
import {
getFollowUpLogsInfo,
addFollowUpLogs,
editFollowUpLogs,
getWithSalesList,
getWithStaffList,
} from '@/addon/zhjw/api/follow_up_logs';
import {useRoute} from 'vue-router'
const route = useRoute()
const id: number = parseInt(route.query.id);
const loading = ref(false)
const pageName = route.meta.title
/**
* 表单数据
*/
const initialFormData = {
id: 0,
sales_id: '',
staff_id: '',
role_id: '',
entry_type: '',
requirement: '',
purchasing_power: '',
cognitive_concept: '',
schooltime: '',
distance: '',
communication_notes: '',
decision_maker: '',
emotional_intensity: '',
initial_customer_intent: '',
initial_relationship_intent: '',
promised_visit_date: '',
actual_visit_date: '',
first_visit_feedback: '',
second_visit_feedback: '',
is_closed_deal: '',
follow_up_type: '',
follow_up_time: '',
audio_upload: '',
follow_up_content: '',
customer_status: '',
sign_up_contact_id: '',
}
const formData: Record<string, any> = reactive({...initialFormData})
const setFormData = async (id: number = 0) => {
Object.assign(formData, initialFormData)
const data = await (await getFollowUpLogsInfo(id)).data
Object.keys(formData).forEach((key: string) => {
if (data[key] != undefined) formData[key] = data[key]
})
}
if (id) setFormData(id);
const formRef = ref<FormInstance>()
//
const selectData = ref<any[]>([])
//
let entry_typeList = ref([])
const entry_typeDictList = async () => {
entry_typeList.value = await (await useDictionary('entry_type')).data.dictionary
}
entry_typeDictList();
watch(() => entry_typeList.value, () => {
formData.entry_type = entry_typeList.value[0].value
})
let purchasing_powerList = ref([])
const purchasing_powerDictList = async () => {
purchasing_powerList.value = await (await useDictionary('low_middle_high')).data.dictionary
}
purchasing_powerDictList();
watch(() => purchasing_powerList.value, () => {
formData.purchasing_power = purchasing_powerList.value[0].value
})
let cognitive_conceptList = ref([])
const cognitive_conceptDictList = async () => {
cognitive_conceptList.value = await (await useDictionary('low_middle_high')).data.dictionary
}
cognitive_conceptDictList();
watch(() => cognitive_conceptList.value, () => {
formData.cognitive_concept = cognitive_conceptList.value[0].value
})
let emotional_intensityList = ref([])
const emotional_intensityDictList = async () => {
emotional_intensityList.value = await (await useDictionary('low_middle_high')).data.dictionary
}
emotional_intensityDictList();
watch(() => emotional_intensityList.value, () => {
formData.emotional_intensity = emotional_intensityList.value[0].value
})
let initial_customer_intentList = ref([])
const initial_customer_intentDictList = async () => {
initial_customer_intentList.value = await (await useDictionary('initial_customer_intent')).data.dictionary
}
initial_customer_intentDictList();
watch(() => initial_customer_intentList.value, () => {
formData.initial_customer_intent = initial_customer_intentList.value[0].value
})
let initial_relationship_intentList = ref([])
const initial_relationship_intentDictList = async () => {
initial_relationship_intentList.value = await (await useDictionary('initial_relationship_intent')).data.dictionary
}
initial_relationship_intentDictList();
watch(() => initial_relationship_intentList.value, () => {
formData.initial_relationship_intent = initial_relationship_intentList.value[0].value
})
let is_closed_dealList = ref([])
const is_closed_dealDictList = async () => {
is_closed_dealList.value = await (await useDictionary('is_radio')).data.dictionary
}
is_closed_dealDictList();
watch(() => is_closed_dealList.value, () => {
formData.is_closed_deal = is_closed_dealList.value[0].value
})
let follow_up_typeList = ref([])
const follow_up_typeDictList = async () => {
follow_up_typeList.value = await (await useDictionary('follow_up_type')).data.dictionary
}
follow_up_typeDictList();
watch(() => follow_up_typeList.value, () => {
formData.follow_up_type = follow_up_typeList.value[0].value
})
let customer_statusList = ref([])
const customer_statusDictList = async () => {
customer_statusList.value = await (await useDictionary('customer_status')).data.dictionary
}
customer_statusDictList();
watch(() => customer_statusList.value, () => {
formData.customer_status = customer_statusList.value[0].value
})
const salesIdList = ref([] as any[])
const setSalesIdList = async () => {
salesIdList.value = await (await getWithSalesList({})).data
}
setSalesIdList()
const staffIdList = ref([] as any[])
const setStaffIdList = async () => {
staffIdList.value = await (await getWithStaffList({})).data
}
setStaffIdList()
const signUpContactIdList = ref([] as any[])
const setSignUpContactIdList = async () => {
signUpContactIdList.value = await (await getWithStaffList({})).data
}
setSignUpContactIdList()
//
const formRules = computed(() => {
return {
sales_id: [
{required: true, message: t('salesIdPlaceholder'), trigger: 'blur'},
]
,
staff_id: [
{required: true, message: t('staffIdPlaceholder'), trigger: 'blur'},
]
,
role_id: [
{required: true, message: t('roleIdPlaceholder'), trigger: 'blur'},
]
,
entry_type: [
{required: true, message: t('entryTypePlaceholder'), trigger: 'blur'},
]
,
requirement: [
{required: true, message: t('requirementPlaceholder'), trigger: 'blur'},
]
,
purchasing_power: [
{required: true, message: t('purchasingPowerPlaceholder'), trigger: 'blur'},
]
,
cognitive_concept: [
{required: true, message: t('cognitiveConceptPlaceholder'), trigger: 'blur'},
]
,
schooltime: [
{required: true, message: t('schooltimePlaceholder'), trigger: 'blur'},
]
,
distance: [
{required: true, message: t('distancePlaceholder'), trigger: 'blur'},
]
,
communication_notes: [
{required: true, message: t('communicationNotesPlaceholder'), trigger: 'blur'},
]
,
decision_maker: [
{required: true, message: t('decisionMakerPlaceholder'), trigger: 'blur'},
]
,
emotional_intensity: [
{required: true, message: t('emotionalIntensityPlaceholder'), trigger: 'blur'},
]
,
initial_customer_intent: [
{required: true, message: t('initialCustomerIntentPlaceholder'), trigger: 'blur'},
]
,
initial_relationship_intent: [
{required: true, message: t('initialRelationshipIntentPlaceholder'), trigger: 'blur'},
]
,
promised_visit_date: [
{required: true, message: t('promisedVisitDatePlaceholder'), trigger: 'blur'},
]
,
actual_visit_date: [
{required: true, message: t('actualVisitDatePlaceholder'), trigger: 'blur'},
]
,
first_visit_feedback: [
{required: true, message: t('firstVisitFeedbackPlaceholder'), trigger: 'blur'},
]
,
second_visit_feedback: [
{required: true, message: t('secondVisitFeedbackPlaceholder'), trigger: 'blur'},
]
,
is_closed_deal: [
{required: true, message: t('isClosedDealPlaceholder'), trigger: 'blur'},
]
,
follow_up_type: [
{required: true, message: t('followUpTypePlaceholder'), trigger: 'blur'},
]
,
follow_up_time: [
{required: true, message: t('followUpTimePlaceholder'), trigger: 'blur'},
]
,
audio_upload: [
{required: true, message: t('audioUploadPlaceholder'), trigger: 'blur'},
]
,
follow_up_content: [
{required: true, message: t('followUpContentPlaceholder'), trigger: 'blur'},
]
,
customer_status: [
{required: true, message: t('customerStatusPlaceholder'), trigger: 'blur'},
]
,
sign_up_contact_id: [
{required: true, message: t('signUpContactIdPlaceholder'), trigger: 'blur'},
]
,
}
})
const onSave = async (formEl: FormInstance | undefined) => {
if (loading.value || !formEl) return
await formEl.validate(async (valid) => {
if (valid) {
loading.value = true
let data = formData
const save = id ? editFollowUpLogs : addFollowUpLogs
save(data).then(res => {
loading.value = false
history.back()
}).catch(err => {
loading.value = false
})
}
})
}
//
const mobileVerify = (rule: any, value: any, callback: any) => {
if (value && !/^1[3-9]\d{9}$/.test(value)) {
callback(new Error(t('generateMobile')))
} else {
callback()
}
}
//
const idCardVerify = (rule: any, value: any, callback: any) => {
if (value && !/^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X)$/.test(value)) {
callback(new Error(t('generateIdCard')))
} else {
callback()
}
}
//
const emailVerify = (rule: any, value: any, callback: any) => {
if (value && !/\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*/.test(value)) {
callback(new Error(t('generateEmail')))
} else {
callback()
}
}
//
const numberVerify = (rule: any, value: any, callback: any) => {
if (!Number.isInteger(value)) {
callback(new Error(t('generateNumber')))
} else {
callback()
}
}
const back = () => {
history.back()
}
</script>
<style lang="scss" scoped></style>

58
niucloud/addon/zhjw/admin/api/follow_up_logs.ts

@ -0,0 +1,58 @@
import request from '@/utils/request'
// USER_CODE_BEGIN -- zhjw_follow_up_logs
/**
*
* @param params
* @returns
*/
export function getFollowUpLogsList(params: Record<string, any>) {
return request.get(`zhjw/follow_up_logs`, {params})
}
/**
*
* @param id id
* @returns
*/
export function getFollowUpLogsInfo(id: number) {
return request.get(`zhjw/follow_up_logs/${id}`);
}
/**
*
* @param params
* @returns
*/
export function addFollowUpLogs(params: Record<string, any>) {
return request.post('zhjw/follow_up_logs', params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param id
* @param params
* @returns
*/
export function editFollowUpLogs(params: Record<string, any>) {
return request.put(`zhjw/follow_up_logs/${params.id}`, params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param id
* @returns
*/
export function deleteFollowUpLogs(id: number) {
return request.delete(`zhjw/follow_up_logs/${id}`, { showErrorMessage: true, showSuccessMessage: true })
}
export function getWithSalesList(params: Record<string,any>){
return request.get('zhjw/sales_all', {params})
}export function getWithStaffList(params: Record<string,any>){
return request.get('zhjw/staff_all', {params})
}export function getWithStaffList(params: Record<string,any>){
return request.get('zhjw/staff_all', {params})
}
// USER_CODE_END -- zhjw_follow_up_logs

51
niucloud/addon/zhjw/admin/lang/zh-cn/follow_up_logs.follow_up_logs.json

@ -0,0 +1,51 @@
{
"id":"序号",
"salesId":"销售关联",
"salesIdPlaceholder":"全部",
"staffId":"跟进人员",
"staffIdPlaceholder":"全部",
"roleId":"跟进人员角色",
"entryType":"填写人员类型",
"entryTypePlaceholder":"请输入填写人员类型",
"requirement":"需求",
"purchasingPower":"购买力",
"purchasingPowerPlaceholder":"请输入购买力",
"cognitiveConcept":"认知理念",
"cognitiveConceptPlaceholder":"请输入认知理念",
"schooltime":"时间",
"schooltimePlaceholder":"请输入时间",
"distance":"距离",
"communicationNotes":"沟通备注",
"decisionMaker":"决策人",
"emotionalIntensity":"情感粘度",
"emotionalIntensityPlaceholder":"请输入情感粘度",
"initialCustomerIntent":"客户初步意向度",
"initialCustomerIntentPlaceholder":"请输入客户初步意向度",
"initialRelationshipIntent":"客情初步意向度",
"initialRelationshipIntentPlaceholder":"请输入客情初步意向度",
"promisedVisitDate":"承诺到访时间",
"promisedVisitDatePlaceholder":"请输入承诺到访时间",
"actualVisitDate":"实际到访时间",
"actualVisitDatePlaceholder":"请输入实际到访时间",
"firstVisitFeedback":"当面资讯反馈-第1次访问情况",
"secondVisitFeedback":"当面资讯反馈-第2次访问情况",
"isClosedDeal":"是否关单",
"isClosedDealPlaceholder":"请输入是否关单",
"followUpType":"跟进类型",
"followUpTypePlaceholder":"请输入跟进类型",
"followUpTime":"跟进时间",
"followUpTimePlaceholder":"请输入跟进时间",
"audioUpload":"上传录音(单文件)",
"followUpContent":"跟进内容",
"customerStatus":"客户状态",
"customerStatusPlaceholder":"请输入客户状态",
"signUpContactId":"签单意向联系人(员工id)",
"signUpContactIdPlaceholder":"全部",
"createTime":"添加时间",
"createTimePlaceholder":"请输入添加时间",
"addFollowUpLogs":"添加跟进管理",
"updateFollowUpLogs":"编辑跟进管理",
"followUpLogsDeleteTips":"确定要删除该数据吗?",
"startDate":"请选择开始时间",
"endDate":"请选择结束时间"
}

55
niucloud/addon/zhjw/admin/lang/zh-cn/follow_up_logs.follow_up_logs_edit.json

@ -0,0 +1,55 @@
{
"salesId":"销售关联",
"staffId":"跟进人员",
"roleId":"跟进人员角色",
"entryType":"填写人员类型",
"requirement":"需求",
"purchasingPower":"购买力",
"cognitiveConcept":"认知理念",
"schooltime":"时间",
"distance":"距离",
"communicationNotes":"沟通备注",
"decisionMaker":"决策人",
"emotionalIntensity":"情感粘度",
"initialCustomerIntent":"客户初步意向度",
"initialRelationshipIntent":"客情初步意向度",
"promisedVisitDate":"承诺到访时间",
"actualVisitDate":"实际到访时间",
"firstVisitFeedback":"当面资讯反馈-第1次访问情况",
"secondVisitFeedback":"当面资讯反馈-第2次访问情况",
"isClosedDeal":"是否关单",
"followUpType":"跟进类型",
"followUpTime":"跟进时间",
"audioUpload":"上传录音(单文件)",
"followUpContent":"跟进内容",
"customerStatus":"客户状态",
"signUpContactId":"签单意向联系人(员工id)",
"salesIdPlaceholder":"请选择销售关联",
"staffIdPlaceholder":"请选择跟进人员",
"roleIdPlaceholder":"请输入跟进人员角色",
"entryTypePlaceholder":"请选择填写人员类型",
"requirementPlaceholder":"请输入需求",
"purchasingPowerPlaceholder":"请选择购买力",
"cognitiveConceptPlaceholder":"请选择认知理念",
"schooltimePlaceholder":"请选择时间",
"distancePlaceholder":"请输入距离",
"communicationNotesPlaceholder":"请输入沟通备注",
"decisionMakerPlaceholder":"请输入决策人",
"emotionalIntensityPlaceholder":"请选择情感粘度",
"initialCustomerIntentPlaceholder":"请选择客户初步意向度",
"initialRelationshipIntentPlaceholder":"请选择客情初步意向度",
"promisedVisitDatePlaceholder":"请选择承诺到访时间",
"actualVisitDatePlaceholder":"请选择实际到访时间",
"firstVisitFeedbackPlaceholder":"请输入当面资讯反馈-第1次访问情况",
"secondVisitFeedbackPlaceholder":"请输入当面资讯反馈-第2次访问情况",
"isClosedDealPlaceholder":"请选择是否关单",
"followUpTypePlaceholder":"请选择跟进类型",
"followUpTimePlaceholder":"请选择跟进时间",
"audioUploadPlaceholder":"请输入上传录音(单文件)",
"followUpContentPlaceholder":"请输入跟进内容",
"customerStatusPlaceholder":"请选择客户状态",
"signUpContactIdPlaceholder":"请选择签单意向联系人(员工id)",
"addFollowUpLogs":"添加跟进管理",
"updateFollowUpLogs":"编辑跟进管理",
"followUpLogsDeleteTips":"确定要删除该跟进管理吗?"
}

517
niucloud/addon/zhjw/admin/views/follow_up_logs/follow_up_logs.vue

@ -0,0 +1,517 @@
<template>
<div class="main-container">
<el-card class="box-card !border-none" shadow="never">
<div class="flex justify-between items-center">
<span class="text-lg">{{pageName}}</span>
<el-button type="primary" @click="addEvent">
{{ t('addFollowUpLogs') }}
</el-button>
</div>
<el-card class="box-card !border-none my-[10px] table-search-wrap" shadow="never">
<el-form :inline="true" :model="followUpLogsTable.searchParam" ref="searchFormRef">
<el-form-item :label="t('salesId')" prop="sales_id">
<el-select class="w-[280px]" v-model="followUpLogsTable.searchParam.sales_id" clearable :placeholder="t('salesIdPlaceholder')">
<el-option
v-for="(item, index) in salesIdList"
:key="index"
:label="item['student_name']"
:value="item['id']"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('staffId')" prop="staff_id">
<el-select class="w-[280px]" v-model="followUpLogsTable.searchParam.staff_id" clearable :placeholder="t('staffIdPlaceholder')">
<el-option
v-for="(item, index) in staffIdList"
:key="index"
:label="item['name']"
:value="item['id']"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('entryType')" prop="entry_type">
<el-select class="w-[280px]" v-model="followUpLogsTable.searchParam.entry_type" clearable :placeholder="t('entryTypePlaceholder')">
<el-option label="全部" value=""></el-option>
<el-option
v-for="(item, index) in entry_typeList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('purchasingPower')" prop="purchasing_power">
<el-select class="w-[280px]" v-model="followUpLogsTable.searchParam.purchasing_power" clearable :placeholder="t('purchasingPowerPlaceholder')">
<el-option label="全部" value=""></el-option>
<el-option
v-for="(item, index) in purchasing_powerList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('cognitiveConcept')" prop="cognitive_concept">
<el-select class="w-[280px]" v-model="followUpLogsTable.searchParam.cognitive_concept" clearable :placeholder="t('cognitiveConceptPlaceholder')">
<el-option label="全部" value=""></el-option>
<el-option
v-for="(item, index) in cognitive_conceptList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('schooltime')" prop="schooltime">
<el-date-picker v-model="followUpLogsTable.searchParam.schooltime" type="datetimerange" format="YYYY-MM-DD hh:mm:ss"
:start-placeholder="t('startDate')" :end-placeholder="t('endDate')" />
</el-form-item>
<el-form-item :label="t('emotionalIntensity')" prop="emotional_intensity">
<el-select class="w-[280px]" v-model="followUpLogsTable.searchParam.emotional_intensity" clearable :placeholder="t('emotionalIntensityPlaceholder')">
<el-option label="全部" value=""></el-option>
<el-option
v-for="(item, index) in emotional_intensityList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('initialCustomerIntent')" prop="initial_customer_intent">
<el-select class="w-[280px]" v-model="followUpLogsTable.searchParam.initial_customer_intent" clearable :placeholder="t('initialCustomerIntentPlaceholder')">
<el-option label="全部" value=""></el-option>
<el-option
v-for="(item, index) in initial_customer_intentList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('initialRelationshipIntent')" prop="initial_relationship_intent">
<el-select class="w-[280px]" v-model="followUpLogsTable.searchParam.initial_relationship_intent" clearable :placeholder="t('initialRelationshipIntentPlaceholder')">
<el-option label="全部" value=""></el-option>
<el-option
v-for="(item, index) in initial_relationship_intentList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('promisedVisitDate')" prop="promised_visit_date">
<el-date-picker v-model="followUpLogsTable.searchParam.promised_visit_date" type="datetimerange" format="YYYY-MM-DD hh:mm:ss"
:start-placeholder="t('startDate')" :end-placeholder="t('endDate')" />
</el-form-item>
<el-form-item :label="t('actualVisitDate')" prop="actual_visit_date">
<el-date-picker v-model="followUpLogsTable.searchParam.actual_visit_date" type="datetimerange" format="YYYY-MM-DD hh:mm:ss"
:start-placeholder="t('startDate')" :end-placeholder="t('endDate')" />
</el-form-item>
<el-form-item :label="t('isClosedDeal')" prop="is_closed_deal">
<el-select class="w-[280px]" v-model="followUpLogsTable.searchParam.is_closed_deal" clearable :placeholder="t('isClosedDealPlaceholder')">
<el-option label="全部" value=""></el-option>
<el-option
v-for="(item, index) in is_closed_dealList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('followUpType')" prop="follow_up_type">
<el-select class="w-[280px]" v-model="followUpLogsTable.searchParam.follow_up_type" clearable :placeholder="t('followUpTypePlaceholder')">
<el-option label="全部" value=""></el-option>
<el-option
v-for="(item, index) in follow_up_typeList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('followUpTime')" prop="follow_up_time">
<el-date-picker v-model="followUpLogsTable.searchParam.follow_up_time" type="datetimerange" format="YYYY-MM-DD hh:mm:ss"
:start-placeholder="t('startDate')" :end-placeholder="t('endDate')" />
</el-form-item>
<el-form-item :label="t('customerStatus')" prop="customer_status">
<el-select class="w-[280px]" v-model="followUpLogsTable.searchParam.customer_status" clearable :placeholder="t('customerStatusPlaceholder')">
<el-option label="全部" value=""></el-option>
<el-option
v-for="(item, index) in customer_statusList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('signUpContactId')" prop="sign_up_contact_id">
<el-select class="w-[280px]" v-model="followUpLogsTable.searchParam.sign_up_contact_id" clearable :placeholder="t('signUpContactIdPlaceholder')">
<el-option
v-for="(item, index) in signUpContactIdList"
:key="index"
:label="item['name']"
:value="item['id']"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('createTime')" prop="create_time">
<el-date-picker v-model="followUpLogsTable.searchParam.create_time" type="datetimerange" format="YYYY-MM-DD hh:mm:ss"
:start-placeholder="t('startDate')" :end-placeholder="t('endDate')" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="loadFollowUpLogsList()">{{ t('search') }}</el-button>
<el-button @click="resetForm(searchFormRef)">{{ t('reset') }}</el-button>
</el-form-item>
</el-form>
</el-card>
<div class="mt-[10px]">
<el-table :data="followUpLogsTable.data" size="large" v-loading="followUpLogsTable.loading">
<template #empty>
<span>{{ !followUpLogsTable.loading ? t('emptyData') : '' }}</span>
</template>
<el-table-column prop="id" :label="t('id')" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column prop="sales_id_name" :label="t('salesId')" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column prop="staff_id_name" :label="t('staffId')" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column prop="role_id" :label="t('roleId')" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column :label="t('entryType')" min-width="180" align="center" :show-overflow-tooltip="true">
<template #default="{ row }">
<div v-for="(item, index) in entry_typeList">
<div v-if="item.value == row.entry_type">{{ item.name }}</div>
</div>
</template>
</el-table-column>
<el-table-column prop="requirement" :label="t('requirement')" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column :label="t('purchasingPower')" min-width="180" align="center" :show-overflow-tooltip="true">
<template #default="{ row }">
<div v-for="(item, index) in purchasing_powerList">
<div v-if="item.value == row.purchasing_power">{{ item.name }}</div>
</div>
</template>
</el-table-column>
<el-table-column :label="t('cognitiveConcept')" min-width="180" align="center" :show-overflow-tooltip="true">
<template #default="{ row }">
<div v-for="(item, index) in cognitive_conceptList">
<div v-if="item.value == row.cognitive_concept">{{ item.name }}</div>
</div>
</template>
</el-table-column>
<el-table-column prop="schooltime" :label="t('schooltime')" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column prop="distance" :label="t('distance')" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column prop="communication_notes" :label="t('communicationNotes')" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column prop="decision_maker" :label="t('decisionMaker')" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column :label="t('emotionalIntensity')" min-width="180" align="center" :show-overflow-tooltip="true">
<template #default="{ row }">
<div v-for="(item, index) in emotional_intensityList">
<div v-if="item.value == row.emotional_intensity">{{ item.name }}</div>
</div>
</template>
</el-table-column>
<el-table-column :label="t('initialCustomerIntent')" min-width="180" align="center" :show-overflow-tooltip="true">
<template #default="{ row }">
<div v-for="(item, index) in initial_customer_intentList">
<div v-if="item.value == row.initial_customer_intent">{{ item.name }}</div>
</div>
</template>
</el-table-column>
<el-table-column :label="t('initialRelationshipIntent')" min-width="180" align="center" :show-overflow-tooltip="true">
<template #default="{ row }">
<div v-for="(item, index) in initial_relationship_intentList">
<div v-if="item.value == row.initial_relationship_intent">{{ item.name }}</div>
</div>
</template>
</el-table-column>
<el-table-column prop="promised_visit_date" :label="t('promisedVisitDate')" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column prop="actual_visit_date" :label="t('actualVisitDate')" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column prop="first_visit_feedback" :label="t('firstVisitFeedback')" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column prop="second_visit_feedback" :label="t('secondVisitFeedback')" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column :label="t('isClosedDeal')" min-width="180" align="center" :show-overflow-tooltip="true">
<template #default="{ row }">
<div v-for="(item, index) in is_closed_dealList">
<div v-if="item.value == row.is_closed_deal">{{ item.name }}</div>
</div>
</template>
</el-table-column>
<el-table-column :label="t('followUpType')" min-width="180" align="center" :show-overflow-tooltip="true">
<template #default="{ row }">
<div v-for="(item, index) in follow_up_typeList">
<div v-if="item.value == row.follow_up_type">{{ item.name }}</div>
</div>
</template>
</el-table-column>
<el-table-column prop="follow_up_time" :label="t('followUpTime')" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column prop="audio_upload" :label="t('audioUpload')" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column prop="follow_up_content" :label="t('followUpContent')" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column :label="t('customerStatus')" min-width="180" align="center" :show-overflow-tooltip="true">
<template #default="{ row }">
<div v-for="(item, index) in customer_statusList">
<div v-if="item.value == row.customer_status">{{ item.name }}</div>
</div>
</template>
</el-table-column>
<el-table-column prop="sign_up_contact_id_name" :label="t('signUpContactId')" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column :label="t('createTime')" min-width="180" align="center" :show-overflow-tooltip="true">
<template #default="{ row }">
{{ row.create_time || '' }}
</template>
</el-table-column>
<el-table-column :label="t('operation')" fixed="right" min-width="120">
<template #default="{ row }">
<el-button type="primary" link @click="editEvent(row)">{{ t('edit') }}</el-button>
<el-button type="primary" link @click="deleteEvent(row.id)">{{ t('delete') }}</el-button>
</template>
</el-table-column>
</el-table>
<div class="mt-[16px] flex justify-end">
<el-pagination v-model:current-page="followUpLogsTable.page" v-model:page-size="followUpLogsTable.limit"
layout="total, sizes, prev, pager, next, jumper" :total="followUpLogsTable.total"
@size-change="loadFollowUpLogsList()" @current-change="loadFollowUpLogsList" />
</div>
</div>
</el-card>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref, watch } from 'vue'
import { t } from '@/lang'
import { useDictionary } from '@/app/api/dict'
import { getFollowUpLogsList, deleteFollowUpLogs, getWithSalesList, getWithStaffList, getWithStaffList } from '@/addon/zhjw/api/follow_up_logs'
import { img } from '@/utils/common'
import { ElMessageBox,FormInstance } from 'element-plus'
import { useRouter } from 'vue-router'
import { useRoute } from 'vue-router'
const route = useRoute()
const pageName = route.meta.title;
let followUpLogsTable = reactive({
page: 1,
limit: 10,
total: 0,
loading: true,
data: [],
searchParam:{
"sales_id":"",
"staff_id":"",
"entry_type":"",
"purchasing_power":"",
"cognitive_concept":"",
"schooltime":[],
"emotional_intensity":"",
"initial_customer_intent":"",
"initial_relationship_intent":"",
"promised_visit_date":[],
"actual_visit_date":[],
"is_closed_deal":"",
"follow_up_type":"",
"follow_up_time":[],
"customer_status":"",
"sign_up_contact_id":"",
"create_time":[]
}
})
const searchFormRef = ref<FormInstance>()
//
const selectData = ref<any[]>([])
//
const entry_typeList = ref([] as any[])
const entry_typeDictList = async () => {
entry_typeList.value = await (await useDictionary('entry_type')).data.dictionary
}
entry_typeDictList();
const purchasing_powerList = ref([] as any[])
const purchasing_powerDictList = async () => {
purchasing_powerList.value = await (await useDictionary('low_middle_high')).data.dictionary
}
purchasing_powerDictList();
const cognitive_conceptList = ref([] as any[])
const cognitive_conceptDictList = async () => {
cognitive_conceptList.value = await (await useDictionary('low_middle_high')).data.dictionary
}
cognitive_conceptDictList();
const emotional_intensityList = ref([] as any[])
const emotional_intensityDictList = async () => {
emotional_intensityList.value = await (await useDictionary('low_middle_high')).data.dictionary
}
emotional_intensityDictList();
const initial_customer_intentList = ref([] as any[])
const initial_customer_intentDictList = async () => {
initial_customer_intentList.value = await (await useDictionary('initial_customer_intent')).data.dictionary
}
initial_customer_intentDictList();
const initial_relationship_intentList = ref([] as any[])
const initial_relationship_intentDictList = async () => {
initial_relationship_intentList.value = await (await useDictionary('initial_relationship_intent')).data.dictionary
}
initial_relationship_intentDictList();
const is_closed_dealList = ref([] as any[])
const is_closed_dealDictList = async () => {
is_closed_dealList.value = await (await useDictionary('is_radio')).data.dictionary
}
is_closed_dealDictList();
const follow_up_typeList = ref([] as any[])
const follow_up_typeDictList = async () => {
follow_up_typeList.value = await (await useDictionary('follow_up_type')).data.dictionary
}
follow_up_typeDictList();
const customer_statusList = ref([] as any[])
const customer_statusDictList = async () => {
customer_statusList.value = await (await useDictionary('customer_status')).data.dictionary
}
customer_statusDictList();
/**
* 获取跟进管理列表
*/
const loadFollowUpLogsList = (page: number = 1) => {
followUpLogsTable.loading = true
followUpLogsTable.page = page
getFollowUpLogsList({
page: followUpLogsTable.page,
limit: followUpLogsTable.limit,
...followUpLogsTable.searchParam
}).then(res => {
followUpLogsTable.loading = false
followUpLogsTable.data = res.data.data
followUpLogsTable.total = res.data.total
}).catch(() => {
followUpLogsTable.loading = false
})
}
loadFollowUpLogsList()
const router = useRouter()
/**
* 添加跟进管理
*/
const addEvent = () => {
router.push('/follow_up_logs/follow_up_logs_edit')
}
/**
* 编辑跟进管理
* @param data
*/
const editEvent = (data: any) => {
router.push('/follow_up_logs/follow_up_logs_edit?id='+data.id)
}
/**
* 删除跟进管理
*/
const deleteEvent = (id: number) => {
ElMessageBox.confirm(t('followUpLogsDeleteTips'), t('warning'),
{
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning',
}
).then(() => {
deleteFollowUpLogs(id).then(() => {
loadFollowUpLogsList()
}).catch(() => {
})
})
}
const salesIdList = ref([])
const setSalesIdList = async () => {
salesIdList.value = await (await getWithSalesList({})).data
}
setSalesIdList()
const staffIdList = ref([])
const setStaffIdList = async () => {
staffIdList.value = await (await getWithStaffList({})).data
}
setStaffIdList()
const signUpContactIdList = ref([])
const setSignUpContactIdList = async () => {
signUpContactIdList.value = await (await getWithStaffList({})).data
}
setSignUpContactIdList()
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.resetFields()
loadFollowUpLogsList()
}
</script>
<style lang="scss" scoped>
/* 多行超出隐藏 */
.multi-hidden {
word-break: break-all;
text-overflow: ellipsis;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
</style>

563
niucloud/addon/zhjw/admin/views/follow_up_logs/follow_up_logs_edit.vue

@ -0,0 +1,563 @@
<template>
<div class="main-container">
<div class="detail-head">
<div class="left" @click="back()">
<span class="iconfont iconxiangzuojiantou !text-xs"></span>
<span class="ml-[1px]">{{t('returnToPreviousPage')}}</span>
</div>
<span class="adorn">|</span>
<span class="right">{{ pageName }}</span>
</div>
<el-card class="box-card !border-none" shadow="never">
<el-form :model="formData" label-width="90px" ref="formRef" :rules="formRules" class="page-form">
<el-form-item :label="t('salesId')" prop="sales_id">
<el-select class="input-width" v-model="formData.sales_id" clearable :placeholder="t('salesIdPlaceholder')">
<el-option label="请选择" value=""></el-option>
<el-option
v-for="(item, index) in salesIdList"
:key="index"
:label="item['student_name']"
:value="item['id']"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('staffId')" prop="staff_id">
<el-select class="input-width" v-model="formData.staff_id" clearable :placeholder="t('staffIdPlaceholder')">
<el-option label="请选择" value=""></el-option>
<el-option
v-for="(item, index) in staffIdList"
:key="index"
:label="item['name']"
:value="item['id']"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('roleId')" prop="role_id">
<el-input v-model="formData.role_id" clearable :placeholder="t('roleIdPlaceholder')" class="input-width" />
</el-form-item>
<el-form-item :label="t('entryType')" prop="entry_type">
<el-select class="input-width" v-model="formData.entry_type" clearable :placeholder="t('entryTypePlaceholder')">
<el-option label="请选择" value=""></el-option>
<el-option
v-for="(item, index) in entry_typeList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('requirement')" >
<el-input v-model="formData.requirement" type="textarea" rows="4" clearable :placeholder="t('requirementPlaceholder')" class="input-width"/>
</el-form-item>
<el-form-item :label="t('purchasingPower')" >
<el-select class="input-width" v-model="formData.purchasing_power" clearable :placeholder="t('purchasingPowerPlaceholder')">
<el-option label="请选择" value=""></el-option>
<el-option
v-for="(item, index) in purchasing_powerList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('cognitiveConcept')" >
<el-select class="input-width" v-model="formData.cognitive_concept" clearable :placeholder="t('cognitiveConceptPlaceholder')">
<el-option label="请选择" value=""></el-option>
<el-option
v-for="(item, index) in cognitive_conceptList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('schooltime')" class="input-width">
<el-date-picker
class="flex-1 !flex"
v-model="formData.schooltime"
clearable
type="datetime"
value-format="YYYY-MM-DD HH:mm:ss"
:placeholder="t('schooltimePlaceholder')">
</el-date-picker>
</el-form-item>
<el-form-item :label="t('distance')" >
<el-input v-model="formData.distance" clearable :placeholder="t('distancePlaceholder')" class="input-width" />
</el-form-item>
<el-form-item :label="t('communicationNotes')" >
<el-input v-model="formData.communication_notes" type="textarea" rows="4" clearable :placeholder="t('communicationNotesPlaceholder')" class="input-width"/>
</el-form-item>
<el-form-item :label="t('decisionMaker')" >
<el-input v-model="formData.decision_maker" clearable :placeholder="t('decisionMakerPlaceholder')" class="input-width" />
</el-form-item>
<el-form-item :label="t('emotionalIntensity')" >
<el-select class="input-width" v-model="formData.emotional_intensity" clearable :placeholder="t('emotionalIntensityPlaceholder')">
<el-option label="请选择" value=""></el-option>
<el-option
v-for="(item, index) in emotional_intensityList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('initialCustomerIntent')" >
<el-select class="input-width" v-model="formData.initial_customer_intent" clearable :placeholder="t('initialCustomerIntentPlaceholder')">
<el-option label="请选择" value=""></el-option>
<el-option
v-for="(item, index) in initial_customer_intentList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('initialRelationshipIntent')" >
<el-select class="input-width" v-model="formData.initial_relationship_intent" clearable :placeholder="t('initialRelationshipIntentPlaceholder')">
<el-option label="请选择" value=""></el-option>
<el-option
v-for="(item, index) in initial_relationship_intentList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('promisedVisitDate')" class="input-width">
<el-date-picker
class="flex-1 !flex"
v-model="formData.promised_visit_date"
clearable
type="datetime"
value-format="YYYY-MM-DD HH:mm:ss"
:placeholder="t('promisedVisitDatePlaceholder')">
</el-date-picker>
</el-form-item>
<el-form-item :label="t('actualVisitDate')" class="input-width">
<el-date-picker
class="flex-1 !flex"
v-model="formData.actual_visit_date"
clearable
type="datetime"
value-format="YYYY-MM-DD HH:mm:ss"
:placeholder="t('actualVisitDatePlaceholder')">
</el-date-picker>
</el-form-item>
<el-form-item :label="t('firstVisitFeedback')" >
<el-input v-model="formData.first_visit_feedback" type="textarea" rows="4" clearable :placeholder="t('firstVisitFeedbackPlaceholder')" class="input-width"/>
</el-form-item>
<el-form-item :label="t('secondVisitFeedback')" >
<el-input v-model="formData.second_visit_feedback" type="textarea" rows="4" clearable :placeholder="t('secondVisitFeedbackPlaceholder')" class="input-width"/>
</el-form-item>
<el-form-item :label="t('isClosedDeal')" >
<el-select class="input-width" v-model="formData.is_closed_deal" clearable :placeholder="t('isClosedDealPlaceholder')">
<el-option label="请选择" value=""></el-option>
<el-option
v-for="(item, index) in is_closed_dealList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('followUpType')" >
<el-select class="input-width" v-model="formData.follow_up_type" clearable :placeholder="t('followUpTypePlaceholder')">
<el-option label="请选择" value=""></el-option>
<el-option
v-for="(item, index) in follow_up_typeList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('followUpTime')" prop="follow_up_time" class="input-width">
<el-date-picker
class="flex-1 !flex"
v-model="formData.follow_up_time"
clearable
type="datetime"
value-format="YYYY-MM-DD HH:mm:ss"
:placeholder="t('followUpTimePlaceholder')">
</el-date-picker>
</el-form-item>
<el-form-item :label="t('audioUpload')">
<upload-file v-model="formData.audio_upload" />
</el-form-item>
<el-form-item :label="t('followUpContent')" >
<el-input v-model="formData.follow_up_content" type="textarea" rows="4" clearable :placeholder="t('followUpContentPlaceholder')" class="input-width"/>
</el-form-item>
<el-form-item :label="t('customerStatus')" >
<el-select class="input-width" v-model="formData.customer_status" clearable :placeholder="t('customerStatusPlaceholder')">
<el-option label="请选择" value=""></el-option>
<el-option
v-for="(item, index) in customer_statusList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('signUpContactId')" >
<el-select class="input-width" v-model="formData.sign_up_contact_id" clearable :placeholder="t('signUpContactIdPlaceholder')">
<el-option label="请选择" value=""></el-option>
<el-option
v-for="(item, index) in signUpContactIdList"
:key="index"
:label="item['name']"
:value="item['id']"
/>
</el-select>
</el-form-item>
</el-form>
</el-card>
<div class="fixed-footer-wrap">
<div class="fixed-footer">
<el-button type="primary" @click="onSave(formRef)">{{ t('save') }}</el-button>
<el-button @click="back()">{{ t('cancel') }}</el-button>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, computed, watch } from 'vue'
import { t } from '@/lang'
import { useDictionary } from '@/app/api/dict'
import type { FormInstance } from 'element-plus'
import { getFollowUpLogsInfo,addFollowUpLogs,editFollowUpLogs, getWithSalesList, getWithStaffList, getWithStaffList } from '@/addon/zhjw/api/follow_up_logs';
import { useRoute } from 'vue-router'
const route = useRoute()
const id:number = parseInt(route.query.id);
const loading = ref(false)
const pageName = route.meta.title
/**
* 表单数据
*/
const initialFormData = {
id: 0,
sales_id: '',
staff_id: '',
role_id: 0,
entry_type: '',
requirement: '',
purchasing_power: '',
cognitive_concept: '',
schooltime: '',
distance: '',
communication_notes: '',
decision_maker: '',
emotional_intensity: '',
initial_customer_intent: '',
initial_relationship_intent: '',
promised_visit_date: '',
actual_visit_date: '',
first_visit_feedback: '',
second_visit_feedback: '',
is_closed_deal: '',
follow_up_type: '',
follow_up_time: '',
audio_upload: '',
follow_up_content: '',
customer_status: '',
sign_up_contact_id: '',
}
const formData: Record<string, any> = reactive({ ...initialFormData })
const setFormData = async (id:number = 0) => {
Object.assign(formData, initialFormData)
const data = await (await getFollowUpLogsInfo(id)).data
Object.keys(formData).forEach((key: string) => {
if (data[key] != undefined) formData[key] = data[key]
})
}
if(id) setFormData(id);
const formRef = ref<FormInstance>()
//
const selectData = ref<any[]>([])
//
let entry_typeList = ref([])
const entry_typeDictList = async () => {
entry_typeList.value = await (await useDictionary('entry_type')).data.dictionary
}
entry_typeDictList();
watch(() => entry_typeList.value, () => { formData.entry_type = entry_typeList.value[0].value })
let purchasing_powerList = ref([])
const purchasing_powerDictList = async () => {
purchasing_powerList.value = await (await useDictionary('low_middle_high')).data.dictionary
}
purchasing_powerDictList();
watch(() => purchasing_powerList.value, () => { formData.purchasing_power = purchasing_powerList.value[0].value })
let cognitive_conceptList = ref([])
const cognitive_conceptDictList = async () => {
cognitive_conceptList.value = await (await useDictionary('low_middle_high')).data.dictionary
}
cognitive_conceptDictList();
watch(() => cognitive_conceptList.value, () => { formData.cognitive_concept = cognitive_conceptList.value[0].value })
let emotional_intensityList = ref([])
const emotional_intensityDictList = async () => {
emotional_intensityList.value = await (await useDictionary('low_middle_high')).data.dictionary
}
emotional_intensityDictList();
watch(() => emotional_intensityList.value, () => { formData.emotional_intensity = emotional_intensityList.value[0].value })
let initial_customer_intentList = ref([])
const initial_customer_intentDictList = async () => {
initial_customer_intentList.value = await (await useDictionary('initial_customer_intent')).data.dictionary
}
initial_customer_intentDictList();
watch(() => initial_customer_intentList.value, () => { formData.initial_customer_intent = initial_customer_intentList.value[0].value })
let initial_relationship_intentList = ref([])
const initial_relationship_intentDictList = async () => {
initial_relationship_intentList.value = await (await useDictionary('initial_relationship_intent')).data.dictionary
}
initial_relationship_intentDictList();
watch(() => initial_relationship_intentList.value, () => { formData.initial_relationship_intent = initial_relationship_intentList.value[0].value })
let is_closed_dealList = ref([])
const is_closed_dealDictList = async () => {
is_closed_dealList.value = await (await useDictionary('is_radio')).data.dictionary
}
is_closed_dealDictList();
watch(() => is_closed_dealList.value, () => { formData.is_closed_deal = is_closed_dealList.value[0].value })
let follow_up_typeList = ref([])
const follow_up_typeDictList = async () => {
follow_up_typeList.value = await (await useDictionary('follow_up_type')).data.dictionary
}
follow_up_typeDictList();
watch(() => follow_up_typeList.value, () => { formData.follow_up_type = follow_up_typeList.value[0].value })
let customer_statusList = ref([])
const customer_statusDictList = async () => {
customer_statusList.value = await (await useDictionary('customer_status')).data.dictionary
}
customer_statusDictList();
watch(() => customer_statusList.value, () => { formData.customer_status = customer_statusList.value[0].value })
const salesIdList = ref([] as any[])
const setSalesIdList = async () => {
salesIdList.value = await (await getWithSalesList({})).data
}
setSalesIdList()
const staffIdList = ref([] as any[])
const setStaffIdList = async () => {
staffIdList.value = await (await getWithStaffList({})).data
}
setStaffIdList()
const signUpContactIdList = ref([] as any[])
const setSignUpContactIdList = async () => {
signUpContactIdList.value = await (await getWithStaffList({})).data
}
setSignUpContactIdList()
//
const formRules = computed(() => {
return {
sales_id: [
{ required: true, message: t('salesIdPlaceholder'), trigger: 'blur' },
]
,
staff_id: [
{ required: true, message: t('staffIdPlaceholder'), trigger: 'blur' },
]
,
role_id: [
{ required: true, message: t('roleIdPlaceholder'), trigger: 'blur' },
]
,
entry_type: [
{ required: true, message: t('entryTypePlaceholder'), trigger: 'blur' },
]
,
requirement: [
{ required: true, message: t('requirementPlaceholder'), trigger: 'blur' },
]
,
purchasing_power: [
{ required: true, message: t('purchasingPowerPlaceholder'), trigger: 'blur' },
]
,
cognitive_concept: [
{ required: true, message: t('cognitiveConceptPlaceholder'), trigger: 'blur' },
]
,
schooltime: [
{ required: true, message: t('schooltimePlaceholder'), trigger: 'blur' },
]
,
distance: [
{ required: true, message: t('distancePlaceholder'), trigger: 'blur' },
]
,
communication_notes: [
{ required: true, message: t('communicationNotesPlaceholder'), trigger: 'blur' },
]
,
decision_maker: [
{ required: true, message: t('decisionMakerPlaceholder'), trigger: 'blur' },
]
,
emotional_intensity: [
{ required: true, message: t('emotionalIntensityPlaceholder'), trigger: 'blur' },
]
,
initial_customer_intent: [
{ required: true, message: t('initialCustomerIntentPlaceholder'), trigger: 'blur' },
]
,
initial_relationship_intent: [
{ required: true, message: t('initialRelationshipIntentPlaceholder'), trigger: 'blur' },
]
,
promised_visit_date: [
{ required: true, message: t('promisedVisitDatePlaceholder'), trigger: 'blur' },
]
,
actual_visit_date: [
{ required: true, message: t('actualVisitDatePlaceholder'), trigger: 'blur' },
]
,
first_visit_feedback: [
{ required: true, message: t('firstVisitFeedbackPlaceholder'), trigger: 'blur' },
]
,
second_visit_feedback: [
{ required: true, message: t('secondVisitFeedbackPlaceholder'), trigger: 'blur' },
]
,
is_closed_deal: [
{ required: true, message: t('isClosedDealPlaceholder'), trigger: 'blur' },
]
,
follow_up_type: [
{ required: true, message: t('followUpTypePlaceholder'), trigger: 'blur' },
]
,
follow_up_time: [
{ required: true, message: t('followUpTimePlaceholder'), trigger: 'blur' },
]
,
audio_upload: [
{ required: true, message: t('audioUploadPlaceholder'), trigger: 'blur' },
]
,
follow_up_content: [
{ required: true, message: t('followUpContentPlaceholder'), trigger: 'blur' },
]
,
customer_status: [
{ required: true, message: t('customerStatusPlaceholder'), trigger: 'blur' },
]
,
sign_up_contact_id: [
{ required: true, message: t('signUpContactIdPlaceholder'), trigger: 'blur' },
]
,
}
})
const onSave = async (formEl: FormInstance | undefined) => {
if (loading.value || !formEl) return
await formEl.validate(async (valid) => {
if (valid) {
loading.value = true
let data = formData
const save = id ? editFollowUpLogs : addFollowUpLogs
save(data).then(res => {
loading.value = false
history.back()
}).catch(err => {
loading.value = false
})
}
})
}
//
const mobileVerify = (rule: any, value: any, callback: any) => {
if (value && !/^1[3-9]\d{9}$/.test(value)) {
callback(new Error(t('generateMobile')))
} else {
callback()
}
}
//
const idCardVerify = (rule: any, value: any, callback: any) => {
if (value && !/^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X)$/.test(value)) {
callback(new Error(t('generateIdCard')))
} else {
callback()
}
}
//
const emailVerify = (rule: any, value: any, callback: any) => {
if (value && !/\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*/.test(value)) {
callback(new Error(t('generateEmail')))
} else {
callback()
}
}
//
const numberVerify = (rule: any, value: any, callback: any) => {
if (!Number.isInteger(value)) {
callback(new Error(t('generateNumber')))
} else {
callback()
}
}
const back = () => {
history.back()
}
</script>
<style lang="scss" scoped></style>

157
niucloud/addon/zhjw/app/adminapi/controller/follow_up_logs/FollowUpLogs.php

@ -0,0 +1,157 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的多应用管理平台
// +----------------------------------------------------------------------
// | 官方网址:https://www.niucloud.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
namespace addon\zhjw\app\adminapi\controller\follow_up_logs;
use core\base\BaseAdminController;
use addon\zhjw\app\service\admin\follow_up_logs\FollowUpLogsService;
/**
* 跟进管理控制器
* Class FollowUpLogs
* @package addon\zhjw\app\adminapi\controller\follow_up_logs
*/
class FollowUpLogs extends BaseAdminController
{
/**
* 获取跟进管理列表
* @return \think\Response
*/
public function lists(){
$data = $this->request->params([
["sales_id",""],
["staff_id",""],
["entry_type",""],
["purchasing_power",""],
["cognitive_concept",""],
["schooltime",["",""]],
["emotional_intensity",""],
["initial_customer_intent",""],
["initial_relationship_intent",""],
["promised_visit_date",["",""]],
["actual_visit_date",["",""]],
["is_closed_deal",""],
["follow_up_type",""],
["follow_up_time",["",""]],
["customer_status",""],
["sign_up_contact_id",""],
["create_time",["",""]]
]);
return success((new FollowUpLogsService())->getPage($data));
}
/**
* 跟进管理详情
* @param int $id
* @return \think\Response
*/
public function info(int $id){
return success((new FollowUpLogsService())->getInfo($id));
}
/**
* 添加跟进管理
* @return \think\Response
*/
public function add(){
$data = $this->request->params([
["sales_id",0],
["staff_id",0],
["role_id",0],
["entry_type",""],
["requirement",""],
["purchasing_power",""],
["cognitive_concept",""],
["schooltime","2025-03-14 19:18:08"],
["distance",""],
["communication_notes",""],
["decision_maker",""],
["emotional_intensity",""],
["initial_customer_intent",""],
["initial_relationship_intent",""],
["promised_visit_date","2025-03-14 19:18:08"],
["actual_visit_date","2025-03-14 19:18:08"],
["first_visit_feedback",""],
["second_visit_feedback",""],
["is_closed_deal",""],
["follow_up_type",""],
["follow_up_time","2025-03-14 19:18:08"],
["audio_upload",""],
["follow_up_content",""],
["customer_status",""],
["sign_up_contact_id",0],
]);
$this->validate($data, 'addon\zhjw\app\validate\follow_up_logs\FollowUpLogs.add');
$id = (new FollowUpLogsService())->add($data);
return success('ADD_SUCCESS', ['id' => $id]);
}
/**
* 跟进管理编辑
* @param $id 跟进管理id
* @return \think\Response
*/
public function edit(int $id){
$data = $this->request->params([
["sales_id",0],
["staff_id",0],
["role_id",0],
["entry_type",""],
["requirement",""],
["purchasing_power",""],
["cognitive_concept",""],
["schooltime","2025-03-14 19:18:08"],
["distance",""],
["communication_notes",""],
["decision_maker",""],
["emotional_intensity",""],
["initial_customer_intent",""],
["initial_relationship_intent",""],
["promised_visit_date","2025-03-14 19:18:08"],
["actual_visit_date","2025-03-14 19:18:08"],
["first_visit_feedback",""],
["second_visit_feedback",""],
["is_closed_deal",""],
["follow_up_type",""],
["follow_up_time","2025-03-14 19:18:08"],
["audio_upload",""],
["follow_up_content",""],
["customer_status",""],
["sign_up_contact_id",0],
]);
$this->validate($data, 'addon\zhjw\app\validate\follow_up_logs\FollowUpLogs.edit');
(new FollowUpLogsService())->edit($id, $data);
return success('EDIT_SUCCESS');
}
/**
* 跟进管理删除
* @param $id 跟进管理id
* @return \think\Response
*/
public function del(int $id){
(new FollowUpLogsService())->del($id);
return success('DELETE_SUCCESS');
}
public function getSalesAll(){
return success(( new FollowUpLogsService())->getSalesAll());
}
public function getStaffAll(){
return success(( new FollowUpLogsService())->getStaffAll());
}
}

27
niucloud/addon/zhjw/app/adminapi/route/route.php

@ -381,3 +381,30 @@ Route::group('zhjw', function () {
AdminLog::class
]);
// USER_CODE_END -- zhjw_sales
// USER_CODE_BEGIN -- zhjw_follow_up_logs
Route::group('zhjw', function () {
//跟进管理列表
Route::get('follow_up_logs', 'addon\zhjw\app\adminapi\controller\follow_up_logs\FollowUpLogs@lists');
//跟进管理详情
Route::get('follow_up_logs/:id', 'addon\zhjw\app\adminapi\controller\follow_up_logs\FollowUpLogs@info');
//添加跟进管理
Route::post('follow_up_logs', 'addon\zhjw\app\adminapi\controller\follow_up_logs\FollowUpLogs@add');
//编辑跟进管理
Route::put('follow_up_logs/:id', 'addon\zhjw\app\adminapi\controller\follow_up_logs\FollowUpLogs@edit');
//删除跟进管理
Route::delete('follow_up_logs/:id', 'addon\zhjw\app\adminapi\controller\follow_up_logs\FollowUpLogs@del');
Route::get('sales_all','addon\zhjw\app\adminapi\controller\follow_up_logs\FollowUpLogs@getSalesAll');
Route::get('staff_all','addon\zhjw\app\adminapi\controller\follow_up_logs\FollowUpLogs@getStaffAll');
})->middleware([
AdminCheckToken::class,
AdminCheckRole::class,
AdminLog::class
]);
// USER_CODE_END -- zhjw_follow_up_logs

310
niucloud/addon/zhjw/app/model/follow_up_logs/FollowUpLogs.php

@ -0,0 +1,310 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的多应用管理平台
// +----------------------------------------------------------------------
// | 官方网址:https://www.niucloud.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
namespace addon\zhjw\app\model\follow_up_logs;
use core\base\BaseModel;
use think\model\concern\SoftDelete;
use think\model\relation\HasMany;
use think\model\relation\HasOne;
use addon\zhjw\app\model\sales\Sales;
use addon\zhjw\app\model\staff\Staff;
/**
* 跟进管理模型
* Class FollowUpLogs
* @package addon\zhjw\app\model\follow_up_logs
*/
class FollowUpLogs extends BaseModel
{
use SoftDelete;
/**
* 数据表主键
* @var string
*/
protected $pk = 'id';
/**
* 模型名称
* @var string
*/
protected $name = 'zhjw_follow_up_logs';
/**
* 定义软删除标记字段.
* @var string
*/
protected $deleteTime = 'is_deleted';
/**
* 定义软删除字段的默认值.
* @var int
*/
protected $defaultSoftDelete = 0;
/**
* 搜索器:跟进管理销售关联
* @param $value
* @param $data
*/
public function searchSalesIdAttr($query, $value, $data)
{
if ($value) {
$query->where("sales_id", $value);
}
}
/**
* 搜索器:跟进管理跟进人员
* @param $value
* @param $data
*/
public function searchStaffIdAttr($query, $value, $data)
{
if ($value) {
$query->where("staff_id", $value);
}
}
/**
* 搜索器:跟进管理填写人员类型
* @param $value
* @param $data
*/
public function searchEntryTypeAttr($query, $value, $data)
{
if ($value) {
$query->where("entry_type", $value);
}
}
/**
* 搜索器:跟进管理购买力
* @param $value
* @param $data
*/
public function searchPurchasingPowerAttr($query, $value, $data)
{
if ($value) {
$query->where("purchasing_power", $value);
}
}
/**
* 搜索器:跟进管理认知理念
* @param $value
* @param $data
*/
public function searchCognitiveConceptAttr($query, $value, $data)
{
if ($value) {
$query->where("cognitive_concept", $value);
}
}
/**
* 搜索器:跟进管理时间
* @param $value
* @param $data
*/
public function searchSchooltimeAttr($query, $value, $data)
{
$start = empty($value[0]) ? 0 : strtotime($value[0]);
$end = empty($value[1]) ? 0 : strtotime($value[1]);
if ($start > 0 && $end > 0) {
$query->where([["schooltime", "between", [$start, $end]]]);
} else if ($start > 0 && $end == 0) {
$query->where([["schooltime", ">=", $start]]);
} else if ($start == 0 && $end > 0) {
$query->where([["schooltime", "<=", $end]]);
}
}
/**
* 搜索器:跟进管理情感粘度
* @param $value
* @param $data
*/
public function searchEmotionalIntensityAttr($query, $value, $data)
{
if ($value) {
$query->where("emotional_intensity", $value);
}
}
/**
* 搜索器:跟进管理客户初步意向度
* @param $value
* @param $data
*/
public function searchInitialCustomerIntentAttr($query, $value, $data)
{
if ($value) {
$query->where("initial_customer_intent", $value);
}
}
/**
* 搜索器:跟进管理客情初步意向度
* @param $value
* @param $data
*/
public function searchInitialRelationshipIntentAttr($query, $value, $data)
{
if ($value) {
$query->where("initial_relationship_intent", $value);
}
}
/**
* 搜索器:跟进管理承诺到访时间
* @param $value
* @param $data
*/
public function searchPromisedVisitDateAttr($query, $value, $data)
{
$start = empty($value[0]) ? 0 : strtotime($value[0]);
$end = empty($value[1]) ? 0 : strtotime($value[1]);
if ($start > 0 && $end > 0) {
$query->where([["promised_visit_date", "between", [$start, $end]]]);
} else if ($start > 0 && $end == 0) {
$query->where([["promised_visit_date", ">=", $start]]);
} else if ($start == 0 && $end > 0) {
$query->where([["promised_visit_date", "<=", $end]]);
}
}
/**
* 搜索器:跟进管理实际到访时间
* @param $value
* @param $data
*/
public function searchActualVisitDateAttr($query, $value, $data)
{
$start = empty($value[0]) ? 0 : strtotime($value[0]);
$end = empty($value[1]) ? 0 : strtotime($value[1]);
if ($start > 0 && $end > 0) {
$query->where([["actual_visit_date", "between", [$start, $end]]]);
} else if ($start > 0 && $end == 0) {
$query->where([["actual_visit_date", ">=", $start]]);
} else if ($start == 0 && $end > 0) {
$query->where([["actual_visit_date", "<=", $end]]);
}
}
/**
* 搜索器:跟进管理是否关单
* @param $value
* @param $data
*/
public function searchIsClosedDealAttr($query, $value, $data)
{
if ($value) {
$query->where("is_closed_deal", $value);
}
}
/**
* 搜索器:跟进管理跟进类型
* @param $value
* @param $data
*/
public function searchFollowUpTypeAttr($query, $value, $data)
{
if ($value) {
$query->where("follow_up_type", $value);
}
}
/**
* 搜索器:跟进管理跟进时间
* @param $value
* @param $data
*/
public function searchFollowUpTimeAttr($query, $value, $data)
{
$start = empty($value[0]) ? 0 : strtotime($value[0]);
$end = empty($value[1]) ? 0 : strtotime($value[1]);
if ($start > 0 && $end > 0) {
$query->where([["follow_up_time", "between", [$start, $end]]]);
} else if ($start > 0 && $end == 0) {
$query->where([["follow_up_time", ">=", $start]]);
} else if ($start == 0 && $end > 0) {
$query->where([["follow_up_time", "<=", $end]]);
}
}
/**
* 搜索器:跟进管理客户状态
* @param $value
* @param $data
*/
public function searchCustomerStatusAttr($query, $value, $data)
{
if ($value) {
$query->where("customer_status", $value);
}
}
/**
* 搜索器:跟进管理签单意向联系人(员工id)
* @param $value
* @param $data
*/
public function searchSignUpContactIdAttr($query, $value, $data)
{
if ($value) {
$query->where("sign_up_contact_id", $value);
}
}
/**
* 搜索器:跟进管理添加时间
* @param $value
* @param $data
*/
public function searchCreateTimeAttr($query, $value, $data)
{
$start = empty($value[0]) ? 0 : strtotime($value[0]);
$end = empty($value[1]) ? 0 : strtotime($value[1]);
if ($start > 0 && $end > 0) {
$query->where([["create_time", "between", [$start, $end]]]);
} else if ($start > 0 && $end == 0) {
$query->where([["create_time", ">=", $start]]);
} else if ($start == 0 && $end > 0) {
$query->where([["create_time", "<=", $end]]);
}
}
public function sales(){
return $this->hasOne(Sales::class, 'id', 'sales_id')->joinType('left')->withField('student_name,id')->bind(['sales_id_name'=>'student_name']);
}
public function staff(){
return $this->hasOne(Staff::class, 'id', 'staff_id')->joinType('left')->withField('name,id')->bind(['staff_id_name'=>'name']);
}
//签单意向联系人
public function staffContact(){
return $this->hasOne(Staff::class, 'id', 'sign_up_contact_id')->joinType('left')->withField('name,id')->bind(['sign_up_contact_id_name'=>'name']);
}
}

115
niucloud/addon/zhjw/app/service/admin/follow_up_logs/FollowUpLogsService.php

@ -0,0 +1,115 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的多应用管理平台
// +----------------------------------------------------------------------
// | 官方网址:https://www.niucloud.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
namespace addon\zhjw\app\service\admin\follow_up_logs;
use addon\zhjw\app\model\follow_up_logs\FollowUpLogs;
use addon\zhjw\app\model\sales\Sales;
use addon\zhjw\app\model\staff\Staff;
use core\base\BaseAdminService;
/**
* 跟进管理服务层
* Class FollowUpLogsService
* @package addon\zhjw\app\service\admin\follow_up_logs
*/
class FollowUpLogsService extends BaseAdminService
{
public function __construct()
{
parent::__construct();
$this->model = new FollowUpLogs();
}
/**
* 获取跟进管理列表
* @param array $where
* @return array
*/
public function getPage(array $where = [])
{
$field = 'id,sales_id,staff_id,role_id,entry_type,requirement,purchasing_power,cognitive_concept,schooltime,distance,communication_notes,decision_maker,emotional_intensity,initial_customer_intent,initial_relationship_intent,promised_visit_date,actual_visit_date,first_visit_feedback,second_visit_feedback,is_closed_deal,follow_up_type,follow_up_time,audio_upload,follow_up_content,customer_status,sign_up_contact_id,is_deleted,create_time,update_time,created_by,created_role,updated_by,updated_role';
$order = 'id desc';
$search_model = $this->model->withSearch(["sales_id", "staff_id", "entry_type", "purchasing_power", "cognitive_concept", "schooltime", "emotional_intensity", "initial_customer_intent", "initial_relationship_intent", "promised_visit_date", "actual_visit_date", "is_closed_deal", "follow_up_type", "follow_up_time", "customer_status", "sign_up_contact_id", "create_time"], $where)->with(['sales', 'staff', 'staff'])->field($field)->order($order);
$list = $this->pageQuery($search_model);
return $list;
}
/**
* 获取跟进管理信息
* @param int $id
* @return array
*/
public function getInfo(int $id)
{
$field = 'id,sales_id,staff_id,role_id,entry_type,requirement,purchasing_power,cognitive_concept,schooltime,distance,communication_notes,decision_maker,emotional_intensity,initial_customer_intent,initial_relationship_intent,promised_visit_date,actual_visit_date,first_visit_feedback,second_visit_feedback,is_closed_deal,follow_up_type,follow_up_time,audio_upload,follow_up_content,customer_status,sign_up_contact_id,is_deleted,create_time,update_time,created_by,created_role,updated_by,updated_role';
$info = $this->model->field($field)->where([['id', "=", $id]])->with(['sales', 'staff', 'staffContact'])->findOrEmpty()->toArray();
return $info;
}
/**
* 添加跟进管理
* @param array $data
* @return mixed
*/
public function add(array $data)
{
$res = $this->model->create($data);
return $res->id;
}
/**
* 跟进管理编辑
* @param int $id
* @param array $data
* @return bool
*/
public function edit(int $id, array $data)
{
$this->model->where([['id', '=', $id]])->update($data);
return true;
}
/**
* 删除跟进管理
* @param int $id
* @return bool
*/
public function del(int $id)
{
$model = $this->model->where([['id', '=', $id]])->find();
$res = $model->delete();
return $res;
}
public function getSalesAll()
{
$salesModel = new Sales();
return $salesModel->select()->toArray();
}
public function getStaffAll()
{
$staffModel = new Staff();
return $staffModel->select()->toArray();
}
}

43
niucloud/addon/zhjw/app/validate/follow_up_logs/FollowUpLogs.php

@ -0,0 +1,43 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的多应用管理平台
// +----------------------------------------------------------------------
// | 官方网址:https://www.niucloud.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
namespace addon\zhjw\app\validate\follow_up_logs;
use core\base\BaseValidate;
/**
* 跟进管理验证器
* Class FollowUpLogs
* @package addon\zhjw\app\validate\follow_up_logs
*/
class FollowUpLogs extends BaseValidate
{
protected $rule = [
'sales_id' => 'require',
'staff_id' => 'require',
'role_id' => 'require',
'entry_type' => 'require',
'follow_up_time' => 'require',
];
protected $message = [
'sales_id.require' => ['common_validate.require', ['sales_id']],
'staff_id.require' => ['common_validate.require', ['staff_id']],
'role_id.require' => ['common_validate.require', ['role_id']],
'entry_type.require' => ['common_validate.require', ['entry_type']],
'follow_up_time.require' => ['common_validate.require', ['follow_up_time']],
];
protected $scene = [
"add" => ['sales_id', 'staff_id', 'role_id', 'entry_type', 'requirement', 'purchasing_power', 'cognitive_concept', 'schooltime', 'distance', 'communication_notes', 'decision_maker', 'emotional_intensity', 'initial_customer_intent', 'initial_relationship_intent', 'promised_visit_date', 'actual_visit_date', 'first_visit_feedback', 'second_visit_feedback', 'is_closed_deal', 'follow_up_type', 'follow_up_time', 'audio_upload', 'follow_up_content', 'customer_status', 'sign_up_contact_id'],
"edit" => ['sales_id', 'staff_id', 'role_id', 'entry_type', 'requirement', 'purchasing_power', 'cognitive_concept', 'schooltime', 'distance', 'communication_notes', 'decision_maker', 'emotional_intensity', 'initial_customer_intent', 'initial_relationship_intent', 'promised_visit_date', 'actual_visit_date', 'first_visit_feedback', 'second_visit_feedback', 'is_closed_deal', 'follow_up_type', 'follow_up_time', 'audio_upload', 'follow_up_content', 'customer_status', 'sign_up_contact_id']
];
}
Loading…
Cancel
Save