【电销老师】 新增# 跟进信息表单和跟进记录

master
kaeery 2025-02-25 15:33:54 +08:00
parent 01d3cf875d
commit cf132dd72a
13 changed files with 353 additions and 213 deletions

View File

@ -0,0 +1,64 @@
<template>
<view class="p-[32rpx] bg-white">
<view class="flex justify-between items-center mb-[20rpx]">
<text class="text-[44rpx] font-bold">修改跟进信息</text>
<text class="text-[28rpx]">全部清空</text>
</view>
<TForm ref="tForm" :model="form" :rules="rules" errorType="toast">
<TFormItem prop="clientName">
<TInputField
v-model="form.clientName"
label="客户姓名"
placeholder="请填写客户姓名(必填)"
inputAlign="left"
/>
</TFormItem>
<TFormItem prop="mobile">
<TInputField
v-model="form.mobile"
label="电话"
placeholder="请填写电话(必填)"
inputAlign="left"
/>
</TFormItem>
<TFormItem prop="desc">
<TTextareaField label="基本情况" placeholder="请填写基本情况(必填)" autoHeight />
</TFormItem>
</TForm>
<u-button class="btn" color="#0E66FB" shape="circle" @click="handleConfirm"></u-button>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const tForm = ref()
const form = ref({
clientName: '',
mobile: '',
desc: ''
})
const rules = {
clientName: [{ required: true, message: '请填写客户姓名', trigger: 'blur' }],
mobile: [{ required: true, message: '请填写电话', trigger: 'blur' }],
desc: [{ required: true, message: '请填写基本情况', trigger: 'blur' }]
}
const handleConfirm = () => {
tForm.value
.validate()
.then(valid => {
if (valid) {
console.log('校验通过')
}
})
.catch(() => {})
}
</script>
<style scoped lang="scss">
:deep(.btn) {
button {
@apply h-[88rpx] px-[60rpx] mt-[60rpx] mb-[28rpx];
}
}
</style>

View File

@ -94,7 +94,8 @@ const onClear = () => {
<style lang="scss" scoped> <style lang="scss" scoped>
.design-search { .design-search {
width: 100%; width: 100%;
padding: 0 32rpx; padding: 14rpx 24rpx;
background-color: $white;
.t-search { .t-search {
width: 100%; width: 100%;
display: flex; display: flex;

View File

@ -6,17 +6,26 @@
* @FilePath: \chargingpile-uniapp\src\components\design-textarea-field.vue * @FilePath: \chargingpile-uniapp\src\components\design-textarea-field.vue
--> -->
<template> <template>
<view class="remark" :style="remarkStyle"> <view class="design-field">
<view :class="required ? 'field-required' : ''" v-if="label">{{ label }}</view> <text class="field" :class="{ 'field-required': required }">{{ label }}</text>
<u-textarea <u-textarea
class="textarea"
v-model="innerValue"
:placeholder="placeholder"
border="none"
:autoHeight="autoHeight"
placeholderStyle="color: '#7c7e82'"
></u-textarea>
<!-- <u-textarea
v-model="innerValue" v-model="innerValue"
:placeholder="placeholder" :placeholder="placeholder"
:maxlength="maxlength" :maxlength="maxlength"
:count="count" :count="count"
:disabled="disabled" :disabled="disabled"
:autoHeight="autoHeight"
border="none" border="none"
placeholderStyle="color: '#7c7e82'" placeholderStyle="color: '#7c7e82'"
></u-textarea> ></u-textarea> -->
</view> </view>
</template> </template>
@ -46,7 +55,7 @@ const props = defineProps({
}, },
required: { required: {
type: Boolean, type: Boolean,
default: false default: true
}, },
disabled: { disabled: {
type: Boolean, type: Boolean,
@ -55,6 +64,10 @@ const props = defineProps({
remarkStyle: { remarkStyle: {
type: Object, type: Object,
default: () => {} default: () => {}
},
autoHeight: {
type: Boolean,
default: false
} }
}) })
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
@ -75,9 +88,16 @@ watch(
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.remark { .design-field {
@include flex-direction; @include flex-align;
padding: 32rpx 20rpx 20rpx 32rpx; border-bottom: 1px solid $gray-3;
gap: 20rpx; padding: 24rpx 32rpx 24rpx 32rpx;
.field {
width: 240rpx;
}
.wrapper,
.textarea {
flex: 1;
}
} }
</style> </style>

View File

@ -0,0 +1,25 @@
<template>
<view class="flex flex-col bg-white rounded-[16rpx] mb-[20rpx]">
<view
class="flex justify-between px-[24rpx] py-[12rpx] border-b border-solid border-[#F3F3F3]"
>
<slot name="title" />
</view>
<view class="px-[24rpx] py-[16rpx] flex flex-col gap-[16rpx]">
<slot name="content" />
</view>
<view class="px-[32rpx] border-t border-solid border-[#F3F3F3] flex justify-end py-[12rpx]">
<slot name="action" />
</view>
</view>
</template>
<script setup lang="ts">
defineProps({
cardInfo: {
type: Object,
default: () => ({})
}
})
</script>
<style scoped></style>

View File

@ -0,0 +1,62 @@
<template>
<w-card>
<template #title>
<view class="flex justify-between w-full py-[10rpx]">
<view class="flex">
<text>韩梅梅</text>
<view class="flex ml-[48rpx] gap-[4rpx] items-center">
<text class="text-primary">18138952909</text>
<u-copy content="uview-plus is great !">
<TIcon name="icon-copy" color="#0E66FB" />
</u-copy>
</view>
</view>
<text>待领取</text>
</view>
</template>
<template #content>
<view class="flex gap-[20rpx] mb-[16rpx]">
<text class="text-muted w-[128rpx]">基本情况</text>
<text class="flex-1">
{{ ellipsisDesc(item) }}
</text>
</view>
<view class="flex gap-[20rpx] mb-[16rpx]">
<text class="text-muted w-[128rpx]">跟进时间</text>
<text class="flex-1">2025-02-08 11:11:30</text>
</view>
<view class="flex gap-[20rpx] mb-[16rpx]">
<text class="text-muted w-[128rpx]">招生老师</text>
<text class="flex-1">王五</text>
</view>
<view class="flex gap-[20rpx]">
<text class="text-muted w-[128rpx]">状态</text>
<text class="flex-1">账号不存在</text>
</view>
</template>
<template #action>
<u-button color="#0E66FB" shape="circle" @click="handleEditFollow"></u-button>
</template>
</w-card>
</template>
<script setup lang="ts">
import { computed } from 'vue'
const props = defineProps({
item: {
type: Object,
default: () => ({})
}
})
const ellipsisDesc = computed(
() => item => item.desc?.length >= 14 ? item.desc?.slice(0, 14) + '...' : item.desc
)
const handleEditFollow = () => {
const { item } = props
uni.navigateTo({
url: '/bundle/pages/follow_edit/index?id=' + item.id
})
}
</script>
<style scoped></style>

View File

@ -0,0 +1,64 @@
<template>
<view class="mt-[32rpx] p-[32rpx] bg-white">
<view class="flex justify-between items-center mb-[20rpx]">
<text class="text-[44rpx] font-bold">跟进信息</text>
<text class="text-[28rpx]">全部清空</text>
</view>
<TForm ref="tForm" :model="form" :rules="rules" errorType="toast">
<TFormItem prop="clientName">
<TInputField
v-model="form.clientName"
label="客户姓名"
placeholder="请填写客户姓名(必填)"
inputAlign="left"
/>
</TFormItem>
<TFormItem prop="mobile">
<TInputField
v-model="form.mobile"
label="电话"
placeholder="请填写电话(必填)"
inputAlign="left"
/>
</TFormItem>
<TFormItem prop="desc">
<TTextareaField label="基本情况" placeholder="请填写基本情况(必填)" autoHeight />
</TFormItem>
</TForm>
<u-button class="btn" color="#0E66FB" shape="circle" @click="handleConfirm"></u-button>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const tForm = ref()
const form = ref({
clientName: '',
mobile: '',
desc: ''
})
const rules = {
clientName: [{ required: true, message: '请填写客户姓名', trigger: 'blur' }],
mobile: [{ required: true, message: '请填写电话', trigger: 'blur' }],
desc: [{ required: true, message: '请填写基本情况', trigger: 'blur' }]
}
const handleConfirm = () => {
tForm.value
.validate()
.then(valid => {
if (valid) {
console.log('校验通过')
}
})
.catch(() => {})
}
</script>
<style scoped lang="scss">
:deep(.btn) {
button {
@apply h-[88rpx] px-[60rpx] mt-[60rpx] mb-[28rpx];
}
}
</style>

View File

@ -0,0 +1,46 @@
<template>
<view class="flex-1 h-full flex flex-col">
<TSearch
v-model="searchValue"
placeholder="搜索客户姓名/手机号码"
backgroundColor="#F5F5F5"
/>
<view class="flex-1 mt-3 px-2 overflow-auto bg-[#FAFAFE]">
<!-- <z-paging
ref="paging"
v-model="dataList"
@query="queryList"
:fixed="false"
height="100%"
> -->
<clue-card
v-for="(item, index) in dataList"
:key="`${index} + 'unique'`"
:item="item"
/>
<!-- </z-paging> -->
</view>
</view>
</template>
<script setup lang="ts">
import { apiOverhaulPagelist } from '@/api/overhaul'
import { useZPaging } from '@/hooks/useZPaging'
import { ref } from 'vue'
import clueCard from './clue-card.vue'
const searchValue = ref('')
const queryParams = ref({})
const dataList = ref([
{
id: 1
}
])
const { paging, queryList, refresh, changeApi, setParams } = useZPaging(
queryParams.value,
apiOverhaulPagelist,
() => {}
)
</script>
<style scoped></style>

View File

@ -1,5 +1,11 @@
{ {
"pages": [ "pages": [
{
"path": "pages/telesale/home/index",
"style": {
"navigationBarTitleText": ""
}
},
{ {
"path": "pages/salesman/contract/index", "path": "pages/salesman/contract/index",
"style": { "style": {
@ -96,6 +102,12 @@
{ {
"root": "bundle", "root": "bundle",
"pages": [ "pages": [
{
"path": "pages/follow_edit/index",
"style": {
"navigationBarTitleText": ""
}
},
{ {
"path": "pages/select-role/index", "path": "pages/select-role/index",
"style": { "style": {

View File

@ -0,0 +1,35 @@
<template>
<view class="flex flex-col h-screen">
<u-tabs
:list="tabs"
:scrollable="false"
:itemStyle="{ height: '44px', flex: 1 }"
:activeStyle="{ color: '#0E66FB' }"
lineWidth="60"
lineColor="#0E66FB"
:current="activeTab"
@change="handleChangeTab"
></u-tabs>
<view class="flex-1 bg-gray3">
<follow-form v-if="activeTab === 0" />
<follow-record v-else-if="activeTab === 1" />
</view>
</view>
</template>
<script setup lang="ts">
import { shallowRef } from 'vue'
import { ref } from 'vue'
import followForm from '@/components/widgets/telesale/follow-form.vue'
import followRecord from '@/components/widgets/telesale/follow-record.vue'
const activeTab = ref(1)
const tabs = shallowRef([
{ name: '新增跟进', value: 0 },
{ name: '跟进动态', value: 1 }
])
const handleChangeTab = item => {
activeTab.value = item.value
}
</script>
<style scoped></style>

View File

@ -1,214 +1,18 @@
@font-face { @font-face {
font-family: 'iconfont'; /* Project id 4649146 */ font-family: 'iconfont'; /* Project id 4837700 */
src: url('//at.alicdn.com/t/c/font_4649146_kddkfg8pdqn.woff2?t=1728201715233') format('woff2'), src: url('//at.alicdn.com/t/c/font_4837700_qu0wamoi86a.woff2?t=1740458346519') format('woff2'),
url('//at.alicdn.com/t/c/font_4649146_kddkfg8pdqn.woff?t=1728201715233') format('woff'), url('//at.alicdn.com/t/c/font_4837700_qu0wamoi86a.woff?t=1740458346519') format('woff'),
url('//at.alicdn.com/t/c/font_4649146_kddkfg8pdqn.ttf?t=1728201715233') format('truetype'); url('//at.alicdn.com/t/c/font_4837700_qu0wamoi86a.ttf?t=1740458346519') format('truetype');
} }
.iconfont { .iconfont {
font-family: 'iconfont' !important; font-family: 'iconfont' !important;
font-size: 32rpx; font-size: 16px;
font-style: normal; font-style: normal;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.icon-weixiugongren:before { .icon-copy:before {
content: '\e60c';
}
.icon-fuzhi_o:before {
content: '\eb4e'; content: '\eb4e';
} }
.icon-tequanG:before {
content: '\e668';
}
.icon-overtime:before {
content: '\e604';
}
.icon-selected:before {
content: '\e67e';
}
.icon-empty:before {
content: '\e621';
}
.icon-success-fill:before {
content: '\e842';
}
.icon-success-fill-active:before {
content: '\f463';
}
.icon-home:before {
content: '\f461';
}
.icon-home-active-copy:before {
content: '\f462';
}
.icon-my-task:before {
content: '\e69e';
}
.icon-my-task-active-copy:before {
content: '\e881';
}
.icon-repeal:before {
content: '\e76b';
}
.icon-view:before {
content: '\e636';
}
.icon-hollow-down:before {
content: '\e615';
}
.icon-rmb:before {
content: '\e634';
}
.icon-profile:before {
content: '\e603';
}
.icon-profile-copy:before {
content: '\e87e';
}
.icon-client-copy:before {
content: '\e87c';
}
.icon-contract-copy:before {
content: '\e87d';
}
.icon-contract:before {
content: '\e601';
}
.icon-client:before {
content: '\e605';
}
.icon-Cooperation:before {
content: '\e749';
}
.icon-success:before {
content: '\e843';
}
.icon-xuanze:before {
content: '\e627';
}
.icon-solid-time:before {
content: '\e63b';
}
.icon-clock:before {
content: '\e600';
}
.icon-scan-code:before {
content: '\e712';
}
.icon-shaixuan:before {
content: '\e641';
}
.icon-location:before {
content: '\e625';
}
.icon-add-device:before {
content: '\e711';
}
.icon-add-contact:before {
content: '\e602';
}
.icon-female:before {
content: '\e71a';
}
.icon-male:before {
content: '\e71c';
}
.icon-image:before {
content: '\e68d';
}
.icon-edit:before {
content: '\e67a';
}
.icon-dianhua:before {
content: '\e608';
}
.icon-scan:before {
content: '\e68b';
}
.icon-baoxiuneirong:before {
content: '\e87a';
}
.icon-weixiu:before {
content: '\e635';
}
.icon-right:before {
content: '\e623';
}
.icon-down:before {
content: '\e87f';
}
.icon-up:before {
content: '\e880';
}
.icon-left:before {
content: '\e87b';
}
.icon-add:before {
content: '\e633';
}
.icon-navigator:before {
content: '\e631';
}
.icon-mobile:before {
content: '\e669';
}
.icon-more:before {
content: '\e867';
}
.icon-clear:before {
content: '\e648';
}
.icon-search:before {
content: '\e64c';
}

View File

@ -120,7 +120,11 @@ page {
} }
.u-textarea { .u-textarea {
background-color: $gray-1 !important; padding-top: 6rpx !important;
padding-bottom: 6rpx !important;
padding-left: 0 !important;
padding-right: 0 !important;
// background-color: $gray-1 !important;
} }
// //
.c-form-item-wrapper { .c-form-item-wrapper {

View File

@ -11,6 +11,7 @@ $gray1: #7f7f7f;
$gray2: #999; $gray2: #999;
$gray3: #dedede; $gray3: #dedede;
$gray4: #46c6a8; $gray4: #46c6a8;
$gray5: #f8f8f8;
$blue1: #2759ff; $blue1: #2759ff;
$blue2: #2799ff; $blue2: #2799ff;
$blue3: #495aff; $blue3: #495aff;

View File

@ -27,8 +27,10 @@ module.exports = {
blue: '#4173ff', blue: '#4173ff',
gray: '#EFEFEF', gray: '#EFEFEF',
gray2: '#7C7E82', gray2: '#7C7E82',
gray3: '#F8F8F8',
lightblack: '#3D3D3D', lightblack: '#3D3D3D',
border: '#F1F1F1' border: '#F1F1F1',
border2: '#F3F3F3'
}, },
fontSize: { fontSize: {
xs: '24rpx', xs: '24rpx',