<template>
|
<view class="app-container">
|
<!-- 搜索区域 -->
|
<view class="search-section">
|
<view class="search-row">
|
<up-input
|
v-model="searchForm.customerName"
|
placeholder="请输入客户名称"
|
prefixIcon="search"
|
:customStyle="{ marginBottom: '10rpx' }"
|
@confirm="handleQuery"
|
/>
|
</view>
|
<view class="search-row">
|
<up-input
|
v-model="searchForm.customerType"
|
placeholder="请选择客户分类"
|
suffixIcon="arrow-down"
|
readonly
|
@click="showTypePicker = true"
|
/>
|
</view>
|
<view class="search-actions">
|
<up-button type="primary" size="small" @click="handleQuery">搜索</up-button>
|
<up-button size="small" @click="resetSearch">重置</up-button>
|
</view>
|
</view>
|
|
<!-- 操作按钮 -->
|
<view class="action-bar">
|
<up-button type="primary" size="small" icon="plus" @click="openForm('add')">新增</up-button>
|
<up-button size="small" icon="download" @click="handleOut">导出</up-button>
|
<up-button type="info" size="small" icon="upload" @click="handleImport">导入</up-button>
|
<up-button type="error" size="small" icon="trash" @click="handleDelete">删除</up-button>
|
</view>
|
|
<!-- 客户列表 -->
|
<scroll-view class="customer-list" scroll-y @scrolltolower="loadMore">
|
<view
|
v-for="item in tableData"
|
:key="item.id"
|
class="customer-card"
|
@click="openDetailDialog(item)"
|
>
|
<view class="card-header">
|
<text class="customer-name">{{ item.customerName }}</text>
|
<up-tag :text="item.customerType" type="primary" size="mini" />
|
</view>
|
<view class="card-body">
|
<view class="info-row">
|
<text class="label">联系人:</text>
|
<text class="value">{{ item.contactPerson || '-' }}</text>
|
</view>
|
<view class="info-row">
|
<text class="label">联系电话:</text>
|
<text class="value">{{ item.contactPhone || '-' }}</text>
|
</view>
|
<view class="info-row">
|
<text class="label">跟进进度:</text>
|
<text class="value">{{ item.followUpLevel || '-' }}</text>
|
</view>
|
<view class="info-row">
|
<text class="label">跟进时间:</text>
|
<text class="value">{{ item.followUpTime || '-' }}</text>
|
</view>
|
<view class="info-row">
|
<text class="label">维护人:</text>
|
<text class="value">{{ item.maintainer || '-' }}</text>
|
</view>
|
</view>
|
<view class="card-footer">
|
<up-button type="primary" text="编辑" size="mini" @click.stop="openForm('edit', item)" />
|
<up-button type="warning" text="洽谈进度" size="mini" @click.stop="openNegotiationDialog(item)" />
|
<up-button type="success" text="回访提醒" size="mini" @click.stop="openReminderDialog(item)" />
|
</view>
|
</view>
|
<up-loadmore :status="loadStatus" />
|
</scroll-view>
|
|
<!-- 客户分类选择器 -->
|
<up-picker
|
:show="showTypePicker"
|
:columns="[customerTypeOptions]"
|
@confirm="onTypeConfirm"
|
@cancel="showTypePicker = false"
|
/>
|
|
<!-- 新增/编辑客户弹窗 -->
|
<up-popup :show="dialogFormVisible" mode="bottom" :round="10" @close="closeDia">
|
<view class="popup-header">
|
<text class="popup-title">{{ operationType === 'add' ? '新增客户信息' : '编辑客户信息' }}</text>
|
<up-icon name="close" size="20" @click="closeDia" />
|
</view>
|
<scroll-view class="popup-body" scroll-y>
|
<up-form :model="form" :rules="rules" ref="formRef" labelPosition="top">
|
<up-form-item label="客户名称" prop="customerName" required>
|
<up-input v-model="form.customerName" placeholder="请输入" />
|
</up-form-item>
|
<up-form-item label="纳税人识别号" prop="taxpayerIdentificationNumber" required>
|
<up-input v-model="form.taxpayerIdentificationNumber" placeholder="请输入" />
|
</up-form-item>
|
<up-form-item label="公司地址" prop="companyAddress" required>
|
<up-input v-model="form.companyAddress" placeholder="请输入" />
|
</up-form-item>
|
<up-form-item label="公司电话" prop="companyPhone" required>
|
<up-input v-model="form.companyPhone" placeholder="请输入" />
|
</up-form-item>
|
<up-form-item label="银行基本户" prop="basicBankAccount" required>
|
<up-input v-model="form.basicBankAccount" placeholder="请输入" />
|
</up-form-item>
|
<up-form-item label="银行账号" prop="bankAccount" required>
|
<up-input v-model="form.bankAccount" placeholder="请输入" />
|
</up-form-item>
|
<up-form-item label="开户行号" prop="bankCode" required>
|
<up-input v-model="form.bankCode" placeholder="请输入" />
|
</up-form-item>
|
<up-form-item label="客户分类" prop="customerType" required>
|
<up-input
|
v-model="form.customerType"
|
placeholder="请选择"
|
suffixIcon="arrow-down"
|
readonly
|
@click="showFormTypePicker = true"
|
/>
|
</up-form-item>
|
<view v-for="(contact, index) in formYYs.contactList" :key="index" class="contact-section">
|
<up-form-item :label="`联系人${index + 1}`">
|
<up-input v-model="contact.contactPerson" placeholder="请输入" />
|
</up-form-item>
|
<up-form-item :label="`联系电话${index + 1}`">
|
<view class="contact-phone-row">
|
<up-input v-model="contact.contactPhone" placeholder="请输入" />
|
<up-button type="error" size="mini" icon="minus" @click="removeContact(index)" />
|
</view>
|
</up-form-item>
|
</view>
|
<up-button type="primary" plain size="small" icon="plus" @click="addNewContact" style="margin-bottom: 20rpx;">
|
新增联系人
|
</up-button>
|
<up-form-item label="维护人" prop="maintainer">
|
<up-input v-model="form.maintainer" disabled />
|
</up-form-item>
|
<up-form-item label="维护时间" prop="maintenanceTime">
|
<up-input
|
v-model="form.maintenanceTime"
|
placeholder="请选择"
|
suffixIcon="calendar"
|
readonly
|
@click="showDatePicker = true"
|
/>
|
</up-form-item>
|
</up-form>
|
</scroll-view>
|
<view class="popup-footer">
|
<up-button type="primary" @click="submitForm">确认</up-button>
|
<up-button @click="closeDia">取消</up-button>
|
</view>
|
</up-popup>
|
|
<!-- 客户分类选择器(表单内) -->
|
<up-picker
|
:show="showFormTypePicker"
|
:columns="[customerTypeOptions]"
|
@confirm="onFormTypeConfirm"
|
@cancel="showFormTypePicker = false"
|
/>
|
|
<!-- 日期选择器 -->
|
<up-datetime-picker
|
:show="showDatePicker"
|
v-model="datePickerValue"
|
mode="date"
|
@confirm="onDateConfirm"
|
@cancel="showDatePicker = false"
|
/>
|
|
<!-- 导入弹窗 -->
|
<up-popup :show="upload.open" mode="center" :round="10" @close="upload.open = false">
|
<view class="popup-content" style="width: 600rpx; padding: 30rpx;">
|
<text class="popup-title">{{ upload.title }}</text>
|
<view class="upload-area" @click="chooseFile">
|
<up-icon name="upload" size="48" color="#909399" />
|
<text class="upload-text">点击选择文件</text>
|
<text class="upload-tip">仅支持 xls、xlsx 格式</text>
|
</view>
|
<text v-if="upload.fileName" class="file-name">已选择: {{ upload.fileName }}</text>
|
<view class="popup-footer">
|
<up-button type="primary" size="small" @click="submitFileForm">确定</up-button>
|
<up-button size="small" @click="upload.open = false">取消</up-button>
|
</view>
|
<up-button type="primary" text="下载模板" size="mini" plain @click="importTemplate" style="margin-top: 20rpx;" />
|
</view>
|
</up-popup>
|
|
<!-- 回访提醒弹窗 -->
|
<up-popup :show="reminderDialogVisible" mode="bottom" :round="10" @close="closeReminderDialog">
|
<view class="popup-header">
|
<text class="popup-title">回访提醒</text>
|
<up-icon name="close" size="20" @click="closeReminderDialog" />
|
</view>
|
<view class="popup-body">
|
<up-form :model="reminderForm" :rules="reminderRules" ref="reminderFormRef" labelPosition="top">
|
<up-form-item label="客户名称">
|
<up-input v-model="reminderForm.customerName" disabled />
|
</up-form-item>
|
<up-form-item label="提醒开关">
|
<up-switch v-model="reminderForm.reminderSwitch" />
|
</up-form-item>
|
<up-form-item label="提醒内容" prop="reminderContent">
|
<up-textarea v-model="reminderForm.reminderContent" placeholder="请输入提醒内容" maxlength="100" count />
|
</up-form-item>
|
<up-form-item label="提醒时间" prop="reminderTime">
|
<up-input
|
v-model="reminderForm.reminderTime"
|
placeholder="请选择提醒时间"
|
suffixIcon="clock"
|
readonly
|
@click="showReminderTimePicker = true"
|
/>
|
</up-form-item>
|
</up-form>
|
</view>
|
<view class="popup-footer">
|
<up-button type="primary" @click="submitReminderForm">确认</up-button>
|
<up-button @click="closeReminderDialog">取消</up-button>
|
</view>
|
</up-popup>
|
|
<!-- 提醒时间选择器 -->
|
<up-datetime-picker
|
:show="showReminderTimePicker"
|
v-model="reminderTimeValue"
|
mode="datetime"
|
@confirm="onReminderTimeConfirm"
|
@cancel="showReminderTimePicker = false"
|
/>
|
|
<!-- 洽谈进度弹窗 -->
|
<up-popup :show="negotiationDialogVisible" mode="bottom" :round="10" @close="closeNegotiationDialog">
|
<view class="popup-header">
|
<text class="popup-title">{{ negotiationForm.editIndex !== undefined ? '修改进度' : '添加进度' }}</text>
|
<up-icon name="close" size="20" @click="closeNegotiationDialog" />
|
</view>
|
<view class="popup-body">
|
<up-form :model="negotiationForm" :rules="negotiationRules" ref="negotiationFormRef" labelPosition="top">
|
<up-form-item label="跟进方式" prop="followUpMethod" required>
|
<up-input
|
v-model="negotiationForm.followUpMethod"
|
placeholder="请选择"
|
suffixIcon="arrow-down"
|
readonly
|
@click="showFollowMethodPicker = true"
|
/>
|
</up-form-item>
|
<up-form-item label="跟进程度" prop="followUpLevel" required>
|
<up-input
|
v-model="negotiationForm.followUpLevel"
|
placeholder="请选择"
|
suffixIcon="arrow-down"
|
readonly
|
@click="showFollowLevelPicker = true"
|
/>
|
</up-form-item>
|
<up-form-item label="跟进时间" prop="followUpTime" required>
|
<up-input
|
v-model="negotiationForm.followUpTime"
|
placeholder="请选择"
|
suffixIcon="clock"
|
readonly
|
@click="showFollowTimePicker = true"
|
/>
|
</up-form-item>
|
<up-form-item label="跟进人">
|
<up-input v-model="negotiationForm.followerUserName" disabled />
|
</up-form-item>
|
<up-form-item label="内容" prop="content" required>
|
<up-textarea v-model="negotiationForm.content" placeholder="请输入" :rows="4" />
|
</up-form-item>
|
</up-form>
|
</view>
|
<view class="popup-footer">
|
<up-button type="primary" @click="submitNegotiationForm">确认</up-button>
|
<up-button @click="closeNegotiationDialog">取消</up-button>
|
</view>
|
</up-popup>
|
|
<!-- 跟进方式选择器 -->
|
<up-picker
|
:show="showFollowMethodPicker"
|
:columns="[followMethodOptions]"
|
@confirm="onFollowMethodConfirm"
|
@cancel="showFollowMethodPicker = false"
|
/>
|
|
<!-- 跟进程度选择器 -->
|
<up-picker
|
:show="showFollowLevelPicker"
|
:columns="[followLevelOptions]"
|
@confirm="onFollowLevelConfirm"
|
@cancel="showFollowLevelPicker = false"
|
/>
|
|
<!-- 跟进时间选择器 -->
|
<up-datetime-picker
|
:show="showFollowTimePicker"
|
v-model="followTimeValue"
|
mode="datetime"
|
@confirm="onFollowTimeConfirm"
|
@cancel="showFollowTimePicker = false"
|
/>
|
|
<!-- 客户详情弹窗 -->
|
<up-popup :show="detailDialogVisible" mode="bottom" :round="10" @close="closeDetailDialog">
|
<view class="popup-header">
|
<text class="popup-title">客户详情</text>
|
<up-icon name="close" size="20" @click="closeDetailDialog" />
|
</view>
|
<scroll-view class="popup-body" scroll-y style="max-height: 70vh;">
|
<!-- 客户基本信息 -->
|
<view class="detail-section">
|
<view class="section-header">
|
<text class="section-title">客户基本信息</text>
|
</view>
|
<view class="info-list">
|
<view class="info-item">
|
<text class="info-label">客户名称:</text>
|
<text class="info-value">{{ detailForm.customerName }}</text>
|
</view>
|
<view class="info-item">
|
<text class="info-label">客户分类:</text>
|
<text class="info-value">{{ detailForm.customerType }}</text>
|
</view>
|
<view class="info-item">
|
<text class="info-label">纳税人识别号:</text>
|
<text class="info-value">{{ detailForm.taxpayerIdentificationNumber }}</text>
|
</view>
|
<view class="info-item">
|
<text class="info-label">公司电话:</text>
|
<text class="info-value">{{ detailForm.companyPhone }}</text>
|
</view>
|
<view class="info-item">
|
<text class="info-label">公司地址:</text>
|
<text class="info-value">{{ detailForm.companyAddress }}</text>
|
</view>
|
<view class="info-item">
|
<text class="info-label">银行基本户:</text>
|
<text class="info-value">{{ detailForm.basicBankAccount }}</text>
|
</view>
|
<view class="info-item">
|
<text class="info-label">银行账号:</text>
|
<text class="info-value">{{ detailForm.bankAccount }}</text>
|
</view>
|
<view class="info-item">
|
<text class="info-label">开户行号:</text>
|
<text class="info-value">{{ detailForm.bankCode }}</text>
|
</view>
|
<view class="info-item">
|
<text class="info-label">联系人:</text>
|
<text class="info-value">{{ detailForm.contactPerson }}</text>
|
</view>
|
<view class="info-item">
|
<text class="info-label">联系电话:</text>
|
<text class="info-value">{{ detailForm.contactPhone }}</text>
|
</view>
|
<view class="info-item">
|
<text class="info-label">维护人:</text>
|
<text class="info-value">{{ detailForm.maintainer }}</text>
|
</view>
|
<view class="info-item">
|
<text class="info-label">维护时间:</text>
|
<text class="info-value">{{ detailForm.maintenanceTime }}</text>
|
</view>
|
</view>
|
</view>
|
|
<!-- 洽谈进度记录 -->
|
<view class="detail-section">
|
<view class="section-header">
|
<text class="section-title">洽谈进度记录</text>
|
<up-button type="primary" size="mini" @click="openNegotiationDialog(detailForm)">添加进度</up-button>
|
</view>
|
<view v-if="negotiationRecords.length === 0" class="empty-text">暂无记录</view>
|
<view v-for="(record, index) in negotiationRecords" :key="index" class="record-card">
|
<view class="record-header">
|
<text class="record-time">{{ record.followUpTime }}</text>
|
<up-tag :text="record.followUpMethod" type="info" size="mini" />
|
</view>
|
<view class="record-body">
|
<text class="record-level">{{ record.followUpLevel }}</text>
|
<text class="record-content">{{ record.content }}</text>
|
</view>
|
<view class="record-footer">
|
<text class="record-user">跟进人:{{ record.followerUserName }}</text>
|
<view class="record-actions">
|
<up-button type="primary" text="附件" size="mini" @click="openAttachmentDialog(record)" />
|
<up-button type="warning" text="编辑" size="mini" @click="editNegotiationRecord(record, index)" />
|
<up-button type="error" text="删除" size="mini" @click="deleteNegotiationRecord(record, index)" />
|
</view>
|
</view>
|
</view>
|
</view>
|
</scroll-view>
|
</up-popup>
|
|
<!-- 附件弹窗 -->
|
<up-popup :show="attachmentDialogVisible" mode="bottom" :round="10" @close="closeAttachmentDialog">
|
<view class="popup-header">
|
<text class="popup-title">附件管理</text>
|
<up-icon name="close" size="20" @click="closeAttachmentDialog" />
|
</view>
|
<scroll-view class="popup-body" scroll-y style="max-height: 60vh;">
|
<view v-if="currentAttachmentList.length === 0" class="empty-text">暂无附件</view>
|
<view v-for="(file, index) in currentAttachmentList" :key="index" class="file-item">
|
<view class="file-info">
|
<up-icon name="file-text" size="24" />
|
<view class="file-detail">
|
<text class="file-name">{{ file.name }}</text>
|
<text class="file-size">{{ formatFileSize(file.size) }}</text>
|
</view>
|
</view>
|
<view class="file-actions">
|
<up-button type="primary" size="mini" text="下载" @click="downloadAttachment(file)" />
|
<up-button type="error" size="mini" text="删除" @click="deleteAttachment(file, index)" />
|
</view>
|
</view>
|
</scroll-view>
|
<view class="popup-footer">
|
<up-button type="primary" @click="chooseAttachment">上传附件</up-button>
|
<up-button @click="closeAttachmentDialog">关闭</up-button>
|
</view>
|
</up-popup>
|
</view>
|
</template>
|
|
<script setup>
|
import { onMounted, ref, reactive, getCurrentInstance, toRefs } from 'vue';
|
import {
|
addCustomer,
|
delCustomer,
|
getCustomer,
|
listCustomer,
|
updateCustomer,
|
addCustomerFollow,
|
updateCustomerFollow,
|
delCustomerFollow,
|
addReturnVisit,
|
getReturnVisit,
|
} from '@/api/basicData/customerFile.js';
|
import { userListNoPage } from '@/api/system/user.js';
|
import useUserStore from '@/store/modules/user';
|
import { getToken } from '@/utils/auth.js';
|
|
const { proxy } = getCurrentInstance();
|
const userStore = useUserStore();
|
|
// 搜索相关
|
const showTypePicker = ref(false);
|
const customerTypeOptions = ['零售客户', '进销商客户'];
|
|
// 表单相关
|
const showFormTypePicker = ref(false);
|
const showDatePicker = ref(false);
|
const datePickerValue = ref(Date.now());
|
|
// 回访提醒相关
|
const reminderDialogVisible = ref(false);
|
const reminderFormRef = ref();
|
const currentCustomerId = ref();
|
const showReminderTimePicker = ref(false);
|
const reminderTimeValue = ref(Date.now());
|
const reminderForm = reactive({
|
customerName: '',
|
reminderSwitch: false,
|
reminderContent: '',
|
reminderTime: '',
|
});
|
const reminderRules = {
|
reminderContent: [
|
{ required: true, message: '请输入提醒内容', trigger: 'blur' },
|
],
|
reminderTime: [
|
{ required: true, message: '请选择提醒时间', trigger: 'change' },
|
],
|
};
|
|
// 洽谈进度相关
|
const negotiationDialogVisible = ref(false);
|
const negotiationFormRef = ref();
|
const showFollowMethodPicker = ref(false);
|
const showFollowLevelPicker = ref(false);
|
const showFollowTimePicker = ref(false);
|
const followTimeValue = ref(Date.now());
|
const followMethodOptions = ['电话', '邮件', '上门', '微信', '其他'];
|
const followLevelOptions = ['潜在客户', '初次拜访', '多次拜访', '意向客户', '已签约客户'];
|
const negotiationForm = reactive({
|
customerName: '',
|
customerId: '',
|
followUpMethod: '',
|
followUpLevel: '',
|
followUpTime: '',
|
followerUserName: '',
|
content: '',
|
});
|
const negotiationRules = {
|
followUpMethod: [
|
{ required: true, message: '请选择跟进方式', trigger: 'change' },
|
],
|
followUpLevel: [
|
{ required: true, message: '请选择跟进程度', trigger: 'change' },
|
],
|
followUpTime: [
|
{ required: true, message: '请选择跟进时间', trigger: 'change' },
|
],
|
content: [{ required: true, message: '请输入内容', trigger: 'blur' }],
|
};
|
|
// 详情相关
|
const detailDialogVisible = ref(false);
|
const detailForm = reactive({
|
customerName: '',
|
customerType: '',
|
taxpayerIdentificationNumber: '',
|
companyPhone: '',
|
companyAddress: '',
|
basicBankAccount: '',
|
bankAccount: '',
|
bankCode: '',
|
contactPerson: '',
|
contactPhone: '',
|
maintainer: '',
|
maintenanceTime: '',
|
});
|
const negotiationRecords = ref([]);
|
|
// 附件相关
|
const attachmentDialogVisible = ref(false);
|
const currentAttachmentList = ref([]);
|
const currentFollowRecord = ref({});
|
|
// 列表相关
|
const tableData = ref([]);
|
const selectedRows = ref([]);
|
const userList = ref([]);
|
const tableLoading = ref(false);
|
const loadStatus = ref('loadmore');
|
const page = reactive({
|
current: 1,
|
size: 10,
|
total: 0,
|
});
|
|
// 用户信息表单弹框数据
|
const operationType = ref('');
|
const dialogFormVisible = ref(false);
|
const formRef = ref();
|
const formYYs = ref({
|
contactList: [
|
{
|
contactPerson: '',
|
contactPhone: '',
|
},
|
],
|
});
|
const data = reactive({
|
searchForm: {
|
customerName: '',
|
customerType: '',
|
},
|
form: {
|
customerName: '',
|
taxpayerIdentificationNumber: '',
|
companyAddress: '',
|
companyPhone: '',
|
contactPerson: '',
|
contactPhone: '',
|
maintainer: '',
|
maintenanceTime: '',
|
basicBankAccount: '',
|
bankAccount: '',
|
bankCode: '',
|
customerType: '',
|
},
|
rules: {
|
customerName: [{ required: true, message: '请输入', trigger: 'blur' }],
|
taxpayerIdentificationNumber: [
|
{ required: true, message: '请输入', trigger: 'blur' },
|
],
|
companyAddress: [{ required: true, message: '请输入', trigger: 'blur' }],
|
companyPhone: [{ required: true, message: '请输入', trigger: 'blur' }],
|
basicBankAccount: [{ required: true, message: '请输入', trigger: 'blur' }],
|
bankAccount: [{ required: true, message: '请输入', trigger: 'blur' }],
|
bankCode: [{ required: true, message: '请输入', trigger: 'blur' }],
|
customerType: [{ required: true, message: '请选择', trigger: 'change' }],
|
},
|
});
|
const upload = reactive({
|
open: false,
|
title: '',
|
isUploading: false,
|
headers: { Authorization: 'Bearer ' + getToken() },
|
url: import.meta.env.VITE_APP_BASE_API + '/basic/customer/importData',
|
fileName: '',
|
filePath: '',
|
});
|
|
const { searchForm, form, rules } = toRefs(data);
|
|
// 选择客户分类(搜索)
|
const onTypeConfirm = (e) => {
|
searchForm.value.customerType = e.value[0];
|
showTypePicker.value = false;
|
handleQuery();
|
};
|
|
// 选择客户分类(表单)
|
const onFormTypeConfirm = (e) => {
|
form.value.customerType = e.value[0];
|
showFormTypePicker.value = false;
|
};
|
|
// 选择日期
|
const onDateConfirm = (e) => {
|
const date = new Date(e.value);
|
form.value.maintenanceTime = formatDate(date);
|
showDatePicker.value = false;
|
};
|
|
// 选择提醒时间
|
const onReminderTimeConfirm = (e) => {
|
const date = new Date(e.value);
|
reminderForm.reminderTime = formatDateTime(date);
|
showReminderTimePicker.value = false;
|
};
|
|
// 选择跟进方式
|
const onFollowMethodConfirm = (e) => {
|
negotiationForm.followUpMethod = e.value[0];
|
showFollowMethodPicker.value = false;
|
};
|
|
// 选择跟进程度
|
const onFollowLevelConfirm = (e) => {
|
negotiationForm.followUpLevel = e.value[0];
|
showFollowLevelPicker.value = false;
|
};
|
|
// 选择跟进时间
|
const onFollowTimeConfirm = (e) => {
|
const date = new Date(e.value);
|
negotiationForm.followUpTime = formatDateTime(date);
|
showFollowTimePicker.value = false;
|
};
|
|
// 格式化日期
|
const formatDate = (date) => {
|
const year = date.getFullYear();
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
const day = String(date.getDate()).padStart(2, '0');
|
return `${year}-${month}-${day}`;
|
};
|
|
// 格式化日期时间
|
const formatDateTime = (date) => {
|
const year = date.getFullYear();
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
const day = String(date.getDate()).padStart(2, '0');
|
const hours = String(date.getHours()).padStart(2, '0');
|
const minutes = String(date.getMinutes()).padStart(2, '0');
|
const seconds = String(date.getSeconds()).padStart(2, '0');
|
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
};
|
|
// 重置搜索
|
const resetSearch = () => {
|
searchForm.value.customerName = '';
|
searchForm.value.customerType = '';
|
handleQuery();
|
};
|
|
// 新增联系人
|
const addNewContact = () => {
|
formYYs.value.contactList.push({
|
contactPerson: '',
|
contactPhone: '',
|
});
|
};
|
|
// 删除联系人
|
const removeContact = (index) => {
|
if (formYYs.value.contactList.length > 1) {
|
formYYs.value.contactList.splice(index, 1);
|
} else {
|
uni.showToast({ title: '至少保留一个联系人', icon: 'none' });
|
}
|
};
|
|
// 查询列表
|
const handleQuery = () => {
|
page.current = 1;
|
getList();
|
};
|
|
// 加载更多
|
const loadMore = () => {
|
if (loadStatus.value === 'nomore') return;
|
page.current++;
|
getList(true);
|
};
|
|
const getList = (isLoadMore = false) => {
|
tableLoading.value = true;
|
loadStatus.value = 'loading';
|
listCustomer({ ...searchForm.value, ...page })
|
.then((res) => {
|
tableLoading.value = false;
|
if (isLoadMore) {
|
tableData.value = [...tableData.value, ...res.records];
|
} else {
|
tableData.value = res.records;
|
}
|
page.total = res.total;
|
loadStatus.value = tableData.value.length >= res.total ? 'nomore' : 'loadmore';
|
})
|
.catch(() => {
|
tableLoading.value = false;
|
loadStatus.value = 'loadmore';
|
});
|
};
|
|
// 提交上传文件
|
const chooseFile = () => {
|
uni.chooseFile({
|
count: 1,
|
extension: ['.xls', '.xlsx'],
|
success: (res) => {
|
const tempFile = res.tempFiles[0];
|
upload.fileName = tempFile.name;
|
upload.filePath = tempFile.path;
|
},
|
});
|
};
|
|
const submitFileForm = () => {
|
if (!upload.filePath) {
|
uni.showToast({ title: '请选择文件', icon: 'none' });
|
return;
|
}
|
upload.isUploading = true;
|
uni.uploadFile({
|
url: upload.url,
|
filePath: upload.filePath,
|
name: 'file',
|
header: upload.headers,
|
success: (res) => {
|
upload.isUploading = false;
|
const data = JSON.parse(res.data);
|
if (data.code === 200) {
|
uni.showToast({ title: '上传成功', icon: 'success' });
|
upload.open = false;
|
getList();
|
} else {
|
uni.showToast({ title: data.msg || '上传失败', icon: 'none' });
|
}
|
},
|
fail: () => {
|
upload.isUploading = false;
|
uni.showToast({ title: '上传失败', icon: 'none' });
|
},
|
});
|
};
|
|
// 导入按钮操作
|
const handleImport = () => {
|
upload.title = '客户导入';
|
upload.open = true;
|
upload.fileName = '';
|
upload.filePath = '';
|
};
|
|
// 下载模板
|
const importTemplate = () => {
|
proxy.download('/basic/customer/downloadTemplate', {}, '客户导入模板.xlsx');
|
};
|
|
// 打开弹框
|
const openForm = (type, row) => {
|
operationType.value = type;
|
form.value = {
|
customerName: '',
|
taxpayerIdentificationNumber: '',
|
companyAddress: '',
|
companyPhone: '',
|
contactPerson: '',
|
contactPhone: '',
|
maintainer: userStore.nickName,
|
maintenanceTime: getCurrentDate(),
|
basicBankAccount: '',
|
bankAccount: '',
|
bankCode: '',
|
customerType: '',
|
};
|
formYYs.value.contactList = [
|
{
|
contactPerson: '',
|
contactPhone: '',
|
},
|
];
|
userListNoPage().then((res) => {
|
userList.value = res.data;
|
});
|
if (type === 'edit' && row) {
|
getCustomer(row.id).then((res) => {
|
form.value = { ...res.data };
|
formYYs.value.contactList = res.data.contactPerson
|
? res.data.contactPerson.split(',').map((item, index) => {
|
return {
|
contactPerson: item,
|
contactPhone: res.data.contactPhone
|
? res.data.contactPhone.split(',')[index] || ''
|
: '',
|
};
|
})
|
: [{ contactPerson: '', contactPhone: '' }];
|
});
|
}
|
dialogFormVisible.value = true;
|
};
|
|
// 提交表单
|
const submitForm = () => {
|
formRef.value.validate().then(() => {
|
if (operationType.value === 'edit') {
|
submitEdit();
|
} else {
|
submitAdd();
|
}
|
}).catch(() => {});
|
};
|
|
// 提交新增
|
const submitAdd = () => {
|
if (formYYs.value.contactList.length < 1) {
|
uni.showToast({ title: '请至少添加一个联系人', icon: 'none' });
|
return;
|
}
|
form.value.contactPerson = formYYs.value.contactList
|
.map((item) => item.contactPerson)
|
.join(',');
|
form.value.contactPhone = formYYs.value.contactList
|
.map((item) => item.contactPhone)
|
.join(',');
|
addCustomer(form.value).then((res) => {
|
uni.showToast({ title: '提交成功', icon: 'success' });
|
closeDia();
|
getList();
|
});
|
};
|
|
// 提交修改
|
const submitEdit = () => {
|
form.value.contactPerson = formYYs.value.contactList
|
.map((item) => item.contactPerson)
|
.join(',');
|
form.value.contactPhone = formYYs.value.contactList
|
.map((item) => item.contactPhone)
|
.join(',');
|
updateCustomer(form.value).then((res) => {
|
uni.showToast({ title: '提交成功', icon: 'success' });
|
closeDia();
|
getList();
|
});
|
};
|
|
// 关闭弹框
|
const closeDia = () => {
|
formRef.value?.resetFields?.();
|
dialogFormVisible.value = false;
|
};
|
|
// 导出
|
const handleOut = () => {
|
uni.showModal({
|
title: '导出',
|
content: '选中的内容将被导出,是否确认导出?',
|
success: (res) => {
|
if (res.confirm) {
|
proxy.download('/basic/customer/export', {}, '客户档案.xlsx');
|
}
|
},
|
});
|
};
|
|
// 删除
|
const handleDelete = () => {
|
if (selectedRows.value.length === 0) {
|
uni.showToast({ title: '请选择数据', icon: 'none' });
|
return;
|
}
|
const unauthorizedData = selectedRows.value.filter(
|
(item) => item.maintainer !== userStore.nickName
|
);
|
if (unauthorizedData.length > 0) {
|
uni.showToast({ title: '不可删除他人维护的数据', icon: 'none' });
|
return;
|
}
|
const ids = selectedRows.value.map((item) => item.id);
|
uni.showModal({
|
title: '删除提示',
|
content: '选中的内容将被删除,是否确认删除?',
|
success: (res) => {
|
if (res.confirm) {
|
tableLoading.value = true;
|
delCustomer(ids)
|
.then(() => {
|
uni.showToast({ title: '删除成功', icon: 'success' });
|
getList();
|
})
|
.finally(() => {
|
tableLoading.value = false;
|
});
|
}
|
},
|
});
|
};
|
|
// 打开回访提醒弹窗
|
const openReminderDialog = (row) => {
|
currentCustomerId.value = row.id;
|
reminderForm.customerName = row.customerName;
|
reminderForm.reminderSwitch = false;
|
reminderForm.reminderContent = '';
|
reminderForm.reminderTime = '';
|
delete reminderForm.id;
|
|
getReturnVisit(row.id)
|
.then((res) => {
|
if (res.code === 200 && res.data) {
|
reminderForm.reminderSwitch = res.data.isEnabled === 1;
|
reminderForm.reminderContent = res.data.content;
|
reminderForm.reminderTime = res.data.reminderTime;
|
reminderForm.id = res.data.id;
|
}
|
})
|
.catch((error) => {
|
console.error('获取回访提醒失败:', error);
|
});
|
|
reminderDialogVisible.value = true;
|
};
|
|
// 关闭回访提醒弹窗
|
const closeReminderDialog = () => {
|
reminderFormRef.value?.resetFields?.();
|
reminderDialogVisible.value = false;
|
};
|
|
// 提交回访提醒
|
const submitReminderForm = () => {
|
reminderFormRef.value.validate().then(() => {
|
const submitvalue = reminderForm.id
|
? {
|
id: reminderForm.id,
|
customerId: currentCustomerId.value,
|
isEnabled: reminderForm.reminderSwitch ? 1 : 0,
|
content: reminderForm.reminderContent,
|
reminderTime: reminderForm.reminderTime,
|
remindUserId: userStore.id,
|
}
|
: {
|
customerId: currentCustomerId.value,
|
isEnabled: reminderForm.reminderSwitch ? 1 : 0,
|
content: reminderForm.reminderContent,
|
reminderTime: reminderForm.reminderTime,
|
remindUserId: userStore.id,
|
};
|
|
addReturnVisit(submitvalue)
|
.then((res) => {
|
if (res.code === 200) {
|
uni.showToast({ title: '回访提醒设置成功', icon: 'success' });
|
closeReminderDialog();
|
} else {
|
uni.showToast({ title: res.msg || '设置失败', icon: 'none' });
|
}
|
})
|
.catch((error) => {
|
console.error('设置回访提醒失败:', error);
|
uni.showToast({ title: '设置失败', icon: 'none' });
|
});
|
}).catch(() => {});
|
};
|
|
// 打开洽谈进度弹窗
|
const openNegotiationDialog = (row) => {
|
negotiationForm.customerName = row.customerName;
|
negotiationForm.customerId = row.id;
|
negotiationForm.followUpMethod = '';
|
negotiationForm.followUpLevel = '';
|
negotiationForm.followUpTime = '';
|
negotiationForm.followerUserName = userStore.nickName;
|
negotiationForm.content = '';
|
delete negotiationForm.editIndex;
|
delete negotiationForm.id;
|
negotiationDialogVisible.value = true;
|
};
|
|
// 关闭洽谈进度弹窗
|
const closeNegotiationDialog = () => {
|
negotiationFormRef.value?.resetFields?.();
|
delete negotiationForm.editIndex;
|
delete negotiationForm.id;
|
negotiationDialogVisible.value = false;
|
};
|
|
// 提交洽谈进度
|
const submitNegotiationForm = () => {
|
negotiationFormRef.value.validate().then(() => {
|
const isEdit = negotiationForm.editIndex !== undefined;
|
|
if (isEdit) {
|
updateCustomerFollow(negotiationForm).then((res) => {
|
getCustomer(negotiationForm.customerId).then((res) => {
|
negotiationRecords.value = res.data.followUpList || [];
|
});
|
uni.showToast({ title: '修改成功', icon: 'success' });
|
closeNegotiationDialog();
|
});
|
} else {
|
addCustomerFollow(negotiationForm).then((res) => {
|
const newRecord = {
|
followUpTime: negotiationForm.followUpTime,
|
followUpMethod: negotiationForm.followUpMethod,
|
followUpLevel: negotiationForm.followUpLevel,
|
followerUserName: negotiationForm.followerUserName,
|
content: negotiationForm.content,
|
};
|
negotiationRecords.value.unshift(newRecord);
|
uni.showToast({ title: '提交成功', icon: 'success' });
|
closeNegotiationDialog();
|
getList();
|
});
|
}
|
}).catch(() => {});
|
};
|
|
// 打开详情弹窗
|
const openDetailDialog = (row) => {
|
getCustomer(row.id).then((res) => {
|
Object.assign(detailForm, res.data);
|
negotiationRecords.value = res.data.followUpList || [];
|
detailDialogVisible.value = true;
|
});
|
};
|
|
// 关闭详情弹窗
|
const closeDetailDialog = () => {
|
detailDialogVisible.value = false;
|
};
|
|
// 修改洽谈记录
|
const editNegotiationRecord = (row, index) => {
|
Object.assign(negotiationForm, {
|
customerName: row.customerName,
|
customerId: row.customerId,
|
followUpMethod: row.followUpMethod,
|
followUpLevel: row.followUpLevel,
|
followUpTime: row.followUpTime,
|
followerUserName: row.followerUserName,
|
content: row.content,
|
id: row.id,
|
editIndex: index,
|
});
|
negotiationDialogVisible.value = true;
|
};
|
|
// 删除洽谈记录
|
const deleteNegotiationRecord = (row, index) => {
|
uni.showModal({
|
title: '删除提示',
|
content: '确定要删除这条洽谈记录吗?',
|
success: (res) => {
|
if (res.confirm) {
|
delCustomerFollow(row.id).then(() => {
|
getCustomer(row.customerId).then((res) => {
|
negotiationRecords.value = res.data.followUpList || [];
|
});
|
uni.showToast({ title: '删除成功', icon: 'success' });
|
});
|
negotiationRecords.value.splice(index, 1);
|
}
|
},
|
});
|
};
|
|
// 打开附件弹窗
|
const openAttachmentDialog = (row) => {
|
currentFollowRecord.value = row;
|
currentAttachmentList.value = (row.fileList || []).map((file, index) => ({
|
name: file.fileName,
|
url: file.fileUrl,
|
size: file.fileSize,
|
id: file.id,
|
uid: file.id || index,
|
status: 'success',
|
}));
|
attachmentDialogVisible.value = true;
|
};
|
|
// 关闭附件弹窗
|
const closeAttachmentDialog = () => {
|
attachmentDialogVisible.value = false;
|
currentFollowRecord.value = {};
|
currentAttachmentList.value = [];
|
};
|
|
// 选择附件
|
const chooseAttachment = () => {
|
uni.chooseFile({
|
count: 1,
|
success: (res) => {
|
const tempFile = res.tempFiles[0];
|
uploadAttachment(tempFile);
|
},
|
});
|
};
|
|
// 上传附件
|
const uploadAttachment = (file) => {
|
const maxSize = 50 * 1024 * 1024;
|
if (file.size > maxSize) {
|
uni.showToast({ title: '文件大小不能超过50MB', icon: 'none' });
|
return;
|
}
|
const uploadUrl = currentFollowRecord.value.id
|
? import.meta.env.VITE_APP_BASE_API +
|
'/basic/customer-follow/upload/' +
|
currentFollowRecord.value.id
|
: import.meta.env.VITE_APP_BASE_API + '/basic/customer-follow/upload';
|
|
uni.uploadFile({
|
url: uploadUrl,
|
filePath: file.path,
|
name: 'file',
|
header: { Authorization: 'Bearer ' + getToken() },
|
success: (res) => {
|
const data = JSON.parse(res.data);
|
if (data.code === 200) {
|
uni.showToast({ title: '上传成功', icon: 'success' });
|
currentAttachmentList.value.push({
|
name: file.name,
|
size: file.size,
|
url: data.data?.url || '',
|
id: data.data?.id,
|
uid: Date.now(),
|
status: 'success',
|
});
|
} else {
|
uni.showToast({ title: data.msg || '上传失败', icon: 'none' });
|
}
|
},
|
fail: () => {
|
uni.showToast({ title: '上传失败', icon: 'none' });
|
},
|
});
|
};
|
|
// 格式化文件大小
|
const formatFileSize = (size) => {
|
if (!size) return '0 B';
|
if (size < 1024) {
|
return size + ' B';
|
} else if (size < 1024 * 1024) {
|
return (size / 1024).toFixed(2) + ' KB';
|
} else {
|
return (size / (1024 * 1024)).toFixed(2) + ' MB';
|
}
|
};
|
|
// 下载附件
|
const downloadAttachment = (row) => {
|
if (row.url) {
|
proxy.$download.name(row.url);
|
} else {
|
uni.showToast({ title: '下载链接不存在', icon: 'none' });
|
}
|
};
|
|
// 删除附件
|
const deleteAttachment = (row, index) => {
|
uni.showModal({
|
title: '删除提示',
|
content: '确定要删除这个附件吗?',
|
success: (res) => {
|
if (res.confirm) {
|
const deleteUrl =
|
import.meta.env.VITE_APP_BASE_API +
|
'/basic/customer-follow/file/' +
|
row.id;
|
uni.request({
|
url: deleteUrl,
|
method: 'DELETE',
|
header: {
|
Authorization: 'Bearer ' + getToken(),
|
'Content-Type': 'application/json',
|
},
|
success: (res) => {
|
if (res.data.code === 200) {
|
currentAttachmentList.value.splice(index, 1);
|
uni.showToast({ title: '删除成功', icon: 'success' });
|
} else {
|
uni.showToast({ title: res.data.msg || '删除失败', icon: 'none' });
|
}
|
},
|
fail: () => {
|
uni.showToast({ title: '删除失败', icon: 'none' });
|
},
|
});
|
}
|
},
|
});
|
};
|
|
// 获取当前日期
|
function getCurrentDate() {
|
const today = new Date();
|
const year = today.getFullYear();
|
const month = String(today.getMonth() + 1).padStart(2, '0');
|
const day = String(today.getDate()).padStart(2, '0');
|
return `${year}-${month}-${day}`;
|
}
|
|
onMounted(() => {
|
getList();
|
});
|
</script>
|
|
<style scoped lang="scss">
|
.app-container {
|
min-height: 100vh;
|
background-color: #f5f5f5;
|
padding: 20rpx;
|
}
|
|
.search-section {
|
background-color: #fff;
|
padding: 20rpx;
|
border-radius: 12rpx;
|
margin-bottom: 20rpx;
|
}
|
|
.search-row {
|
margin-bottom: 16rpx;
|
}
|
|
.search-actions {
|
display: flex;
|
gap: 20rpx;
|
justify-content: flex-end;
|
}
|
|
.action-bar {
|
display: flex;
|
gap: 16rpx;
|
margin-bottom: 20rpx;
|
flex-wrap: wrap;
|
}
|
|
.customer-list {
|
height: calc(100vh - 300rpx);
|
}
|
|
.customer-card {
|
background-color: #fff;
|
border-radius: 12rpx;
|
padding: 24rpx;
|
margin-bottom: 20rpx;
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
}
|
|
.card-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
margin-bottom: 20rpx;
|
padding-bottom: 16rpx;
|
border-bottom: 1rpx solid #f0f0f0;
|
}
|
|
.customer-name {
|
font-size: 32rpx;
|
font-weight: bold;
|
color: #333;
|
}
|
|
.card-body {
|
margin-bottom: 20rpx;
|
}
|
|
.info-row {
|
display: flex;
|
margin-bottom: 12rpx;
|
}
|
|
.label {
|
color: #666;
|
font-size: 26rpx;
|
width: 140rpx;
|
flex-shrink: 0;
|
}
|
|
.value {
|
color: #333;
|
font-size: 26rpx;
|
flex: 1;
|
}
|
|
.card-footer {
|
display: flex;
|
gap: 16rpx;
|
justify-content: flex-end;
|
padding-top: 16rpx;
|
border-top: 1rpx solid #f0f0f0;
|
}
|
|
.popup-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
padding: 30rpx;
|
border-bottom: 1rpx solid #eee;
|
}
|
|
.popup-title {
|
font-size: 32rpx;
|
font-weight: bold;
|
color: #333;
|
}
|
|
.popup-body {
|
padding: 30rpx;
|
max-height: 60vh;
|
}
|
|
.popup-footer {
|
display: flex;
|
gap: 20rpx;
|
padding: 20rpx 30rpx;
|
border-top: 1rpx solid #eee;
|
}
|
|
.contact-section {
|
background-color: #f9f9f9;
|
padding: 20rpx;
|
border-radius: 8rpx;
|
margin-bottom: 20rpx;
|
}
|
|
.contact-phone-row {
|
display: flex;
|
gap: 16rpx;
|
align-items: center;
|
}
|
|
.upload-area {
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
justify-content: center;
|
padding: 60rpx;
|
border: 2rpx dashed #dcdcdc;
|
border-radius: 8rpx;
|
margin: 30rpx 0;
|
}
|
|
.upload-text {
|
font-size: 28rpx;
|
color: #666;
|
margin-top: 16rpx;
|
}
|
|
.upload-tip {
|
font-size: 24rpx;
|
color: #999;
|
margin-top: 8rpx;
|
}
|
|
.file-name {
|
font-size: 26rpx;
|
color: #333;
|
margin-bottom: 20rpx;
|
word-break: break-all;
|
}
|
|
.detail-section {
|
margin-bottom: 30rpx;
|
}
|
|
.section-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
margin-bottom: 20rpx;
|
}
|
|
.section-title {
|
font-size: 30rpx;
|
font-weight: bold;
|
color: #333;
|
}
|
|
.info-list {
|
background-color: #f9f9f9;
|
padding: 20rpx;
|
border-radius: 8rpx;
|
}
|
|
.info-item {
|
display: flex;
|
padding: 12rpx 0;
|
border-bottom: 1rpx solid #eee;
|
}
|
|
.info-item:last-child {
|
border-bottom: none;
|
}
|
|
.info-label {
|
color: #666;
|
font-size: 26rpx;
|
width: 200rpx;
|
flex-shrink: 0;
|
}
|
|
.info-value {
|
color: #333;
|
font-size: 26rpx;
|
flex: 1;
|
word-break: break-all;
|
}
|
|
.record-card {
|
background-color: #f9f9f9;
|
padding: 20rpx;
|
border-radius: 8rpx;
|
margin-bottom: 16rpx;
|
}
|
|
.record-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
margin-bottom: 12rpx;
|
}
|
|
.record-time {
|
font-size: 26rpx;
|
color: #666;
|
}
|
|
.record-body {
|
margin-bottom: 12rpx;
|
}
|
|
.record-level {
|
font-size: 28rpx;
|
font-weight: bold;
|
color: #333;
|
margin-bottom: 8rpx;
|
display: block;
|
}
|
|
.record-content {
|
font-size: 26rpx;
|
color: #666;
|
line-height: 1.5;
|
}
|
|
.record-footer {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
padding-top: 12rpx;
|
border-top: 1rpx solid #eee;
|
}
|
|
.record-user {
|
font-size: 24rpx;
|
color: #999;
|
}
|
|
.record-actions {
|
display: flex;
|
gap: 12rpx;
|
}
|
|
.file-item {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
padding: 20rpx;
|
background-color: #f9f9f9;
|
border-radius: 8rpx;
|
margin-bottom: 16rpx;
|
}
|
|
.file-info {
|
display: flex;
|
align-items: center;
|
flex: 1;
|
overflow: hidden;
|
}
|
|
.file-detail {
|
margin-left: 16rpx;
|
flex: 1;
|
overflow: hidden;
|
}
|
|
.file-name {
|
font-size: 26rpx;
|
color: #333;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
white-space: nowrap;
|
}
|
|
.file-size {
|
font-size: 24rpx;
|
color: #999;
|
margin-top: 4rpx;
|
}
|
|
.file-actions {
|
display: flex;
|
gap: 12rpx;
|
}
|
|
.empty-text {
|
text-align: center;
|
padding: 40rpx;
|
color: #999;
|
font-size: 28rpx;
|
}
|
</style>
|