You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

827 lines
22 KiB
Vue

1 year ago
<template>
12 months ago
<div>
<my-card title="搜索条件" search>
<n-form inline label-placement="top" :model="searchForm">
<n-form-item label="客户名称" path="clientName">
<n-input v-model:value="searchForm.clientName" placeholder="请输入客户名称"></n-input>
</n-form-item>
<n-form-item label="统一社会代码" path="creditCode">
<n-input v-model:value="searchForm.creditCode" placeholder="请输入统一社会代码"></n-input>
</n-form-item>
<n-form-item label="业务员" path="salesmanName">
<n-input v-model:value="searchForm.salesmanName" placeholder="请输入业务员"></n-input>
</n-form-item>
<n-form-item label="跟进状态">
<n-select
v-model:value="searchForm.attr1"
placeholder="请选择跟进状态"
class="w-180px"
:options="followUpStatusList"
filterable
></n-select>
</n-form-item>
<n-form-item label="客户类型">
<n-select
v-model:value="searchForm.attr2"
placeholder="请选择客户类型"
class="w-180px"
:options="clientTypeList"
filterable
></n-select>
</n-form-item>
<n-form-item>
<n-button type="primary" class="mr-10px" @click="handleSearch">
<icon-ic-round-search class="mr-4px text-20px" />
搜索
</n-button>
<n-button @click="handleReset">
<icon-ic-round-refresh class="mr-4px text-20px" />
重置
</n-button>
</n-form-item>
</n-form>
</my-card>
<my-card title="用户列表">
<template #right>
<div class="flex-center">
<cx-columns v-model:columns="columns"></cx-columns>
<!-- <n-button type="warning" size="small" class="ml-5px" @click="synchronization">-->
<!-- <icon-ic-round-plus class="mr-4px text-20px" />-->
<!-- 同步erp客户-->
<!-- </n-button>-->
<n-button type="primary" size="small" class="ml-5px" @click="openDialog">
<icon-ic-round-plus class="mr-4px text-20px" />
新增客户
</n-button>
</div>
</template>
<n-data-table
:loading="loading"
:data="data"
:columns="columns"
:max-height="dataTableConfig.maxHeight"
:scroll-x="dataTableConfig.scrollWidth(columns)"
:row-props="rowProps"
></n-data-table>
<my-pagination v-model:search-form="searchForm" @init="init"></my-pagination>
</my-card>
<my-dialog
v-model:show="flag"
width="1200px"
:title="editFlag ? '编辑客户' : '新增客户'"
@cancel="cancel"
@submit="submit"
>
<template #content>
<div style="width: 100%">
<n-form ref="addFormRef" :model="addForm" :rules="rules" label-placement="left" label-width="130px">
<n-grid :cols="3" :x-gap="10" :y-gap="10">
<n-form-item-grid-item :span="1" label="客户名称" path="clientName">
<n-input v-model:value="addForm.clientName" placeholder="请输入客户名称" style="width: 100%" />
</n-form-item-grid-item>
<!-- <n-form-item-grid-item :span="1" label="开户银行" path="bankAccount">
<n-input v-model:value="addForm.bankAccount" placeholder="请输入开户银行" />
</n-form-item-grid-item> -->
<n-form-item-grid-item :span="1" label="客户简称" path="clientNick">
<n-input v-model:value="addForm.clientNick" placeholder="请输入客户简称" style="width: 100%" />
</n-form-item-grid-item>
<n-form-item-grid-item :span="1" label="客户编码" path="clientCode">
<n-input v-model:value="addForm.clientCode" placeholder="请输入客户编码" style="width: 100%" />
</n-form-item-grid-item>
<!-- <n-form-item-grid-item :span="1" label="统一信用社代码" path="creditCode">
<n-input v-model:value="addForm.creditCode" placeholder="请输入统一信用社代码"></n-input>
</n-form-item-grid-item> -->
<n-form-item-grid-item :span="1" label="地址" path="addressId">
<!-- <n-input v-model:value="addForm.address" placeholder="请输入地址" /> -->
<div style="display: flex; flex-wrap: wrap; width: 100%">
<n-cascader
v-model:value="addForm.addressId"
style="width: 100%"
:expand-trigger="'click'"
:options="addressOptions"
:check-strategy="checkStrategyIsChild ? 'child' : 'all'"
:show-path="showPath"
remote
placeholder="请选择省市区"
/>
<n-input
v-model:value="addForm.contact2Tel"
placeholder="请输入详细地址"
style="width: 100%"
class="mt-10px"
type="textarea"
/>
</div>
</n-form-item-grid-item>
<!-- <n-form-item-grid-item :span="1" label="法人姓名">
<n-input v-model:value="addForm.legalPersonName" placeholder="请输入法人姓名" />
</n-form-item-grid-item>
<n-form-item-grid-item :span="1" label="法人电话" path="legalPersonPhone">
<n-input v-model:value="addForm.legalPersonPhone" placeholder="请输入法人电话" />
</n-form-item-grid-item>
<n-form-item-grid-item :span="1" label="法人身份证">
<n-input v-model:value="addForm.legalPersonIdcard" placeholder="请输入法人身份证"></n-input>
</n-form-item-grid-item>
<n-form-item-grid-item :span="1" label="联系人">
<n-input v-model:value="addForm.contact1" placeholder="请输入联系人"></n-input>
</n-form-item-grid-item>
<n-form-item-grid-item :span="1" label="联系人职务">
<n-input v-model:value="addForm.contact1Title" placeholder="请输入联系人职务"></n-input>
</n-form-item-grid-item>
<n-form-item-grid-item :span="1" label="联系人电话">
<n-input v-model:value="addForm.contact1Tel" placeholder="请输入联系人电话"></n-input>
</n-form-item-grid-item>
<n-form-item-grid-item :span="1" label="联系人手机">
<n-input v-model:value="addForm.contact1Phone" placeholder="请输入联系人手机"></n-input>
</n-form-item-grid-item>
<n-form-item-grid-item :span="1" label="联系人传真">
<n-input v-model:value="addForm.contact1Fax" placeholder="请输入联系人传真"></n-input>
</n-form-item-grid-item>
<n-form-item-grid-item :span="1" label="联系人邮箱">
<n-input v-model:value="addForm.contact1Email" placeholder="请输入联系人邮箱"></n-input>
</n-form-item-grid-item> -->
<!-- <n-form-item-grid-item :span="1" label="公司成立时间">
<n-input v-model:value="addForm.foundingTime" placeholder="请输入公司成立时间"></n-input>
</n-form-item-grid-item>
<n-form-item-grid-item :span="1" label="注册资金">
<n-input v-model:value="addForm.registeredCapital" placeholder="请输入注册资金"></n-input>
</n-form-item-grid-item>
<n-form-item-grid-item :span="1" label="经营范围">
<n-input v-model:value="addForm.businessScope" placeholder="请输入经营范围"></n-input>
</n-form-item-grid-item>
<n-form-item-grid-item :span="1" label="终端/经销商">
<n-select
v-model:value="addForm.ifDistributor"
:options="[
{ label: '终端', value: 0 },
{ label: '经销商', value: 1 }
]"
placeholder="请选择"
></n-select>
</n-form-item-grid-item> -->
<n-form-item-grid-item :span="1" label="客户来源">
<n-select
v-model:value="addForm.leadSource"
:options="[
1 year ago
{ label: '电话来访', value: 1 },
{ label: '客户介绍', value: 2 },
{ label: '上门拜访', value: 3 },
{ label: '销售自拓', value: 4 },
{ label: '其他', value: 5 }
]"
12 months ago
style="width: 100%"
placeholder="请选择"
></n-select>
</n-form-item-grid-item>
<n-form-item-grid-item :span="1" label="客户级别">
<n-select
v-model:value="addForm.contact2"
:options="clientGradeList"
placeholder="请选择"
style="width: 100%"
></n-select>
</n-form-item-grid-item>
<!-- <n-form-item-grid-item :span="1" label="价格模式">
<n-select
v-model:value="addForm.pricingModel"
:options="[
{ label: '报价', value: '报价' },
{ label: '固定', value: '固定' },
{ label: '其他', value: '其他' }
]"
placeholder="请选择"
></n-select>
</n-form-item-grid-item>
<n-form-item-grid-item :span="1" label="需求产品">
<n-input v-model:value="addForm.products" placeholder="请输入需求产品"></n-input>
</n-form-item-grid-item>
<n-form-item-grid-item :span="1" label="月需求量">
<n-input v-model:value="addForm.productsNum" placeholder="请输入月需求量"></n-input>
</n-form-item-grid-item>
<n-form-item-grid-item :span="1" label="付款方式">
<n-select
v-model:value="addForm.paymentMethod"
:options="paymentMethodOptions"
placeholder="请输入付款方式"
></n-select>
</n-form-item-grid-item> -->
<n-form-item-grid-item v-show="addForm.attr2 === 1" :span="1" label="业务员">
<n-input v-model:value="addForm.salesmanName" placeholder="请输入业务员" />
</n-form-item-grid-item>
<n-form-item-grid-item v-show="addForm.attr2 === 1" :span="1" label="业务员电话" path="salesmanPhone">
<n-input v-model:value="addForm.salesmanPhone" placeholder="请输入业务员电话" />
</n-form-item-grid-item>
<n-form-item-grid-item :span="1" label="跟进状态">
<n-select
v-model:value="addForm.attr1"
placeholder="请选择跟进状态"
style="width: 100%"
:options="followUpStatusList"
filterable
></n-select>
</n-form-item-grid-item>
<n-form-item-grid-item :span="1" label="客户类型">
<n-select v-model:value="addForm.attr2" :options="clientTypeList" placeholder="请选择"></n-select>
</n-form-item-grid-item>
</n-grid>
</n-form>
</div>
</template>
</my-dialog>
<archives v-show="archivesShow" :from-data="archivesForm" @close-archives="closeArchives" @save="save" />
</div>
1 year ago
</template>
<script setup lang="tsx">
import type { Ref } from 'vue';
import { ref, onMounted } from 'vue';
import { useMessage, useDialog } from 'naive-ui';
import type { DataTableColumns, FormInst } from 'naive-ui';
import {
12 months ago
getUserList,
addUser,
deleteUser,
editUserFach,
getSyncClientList
// getProvinces
1 year ago
} from '@/service/api/sale/userManage';
import { useEditBtn, useDelBtn } from '@/hooks/common/useBtn';
import { dataTableConfig } from '@/config/dataTableConfig';
import { useLoading, useBoolean } from '~/src/hooks';
import { createRequiredFormRule } from '~/src/utils';
import { proCityList } from './options/address';
import archives from './archives/index.vue';
const paymentMethodOptions = ref([
12 months ago
{ label: '款到发货', value: 1 },
{ label: '货到付款', value: 0 }
1 year ago
]);
const followUpStatusList = ref([
12 months ago
{ label: '待跟进', value: '待跟进' },
{ label: '潜在客户', value: '潜在客户' },
{ label: '有意向', value: '有意向' },
{ label: '高意向', value: '高意向' },
{ label: '未成交', value: '未成交' },
{ label: '已成交', value: '已成交' }
1 year ago
]);
const clientGradeList = ref<Array<{ label: string; value: string }>>([
12 months ago
{ label: '普通客户', value: '1' },
{ label: '重要客户', value: '2' },
{ label: '战略客户', value: '3' }
1 year ago
]);
const clientTypeList = ref([
12 months ago
{ label: '海量客户', value: 0 },
{ label: '个人客户', value: 1 }
1 year ago
]);
const checkStrategyIsChild = ref(true);
const showPath = ref(true);
const addressOptions = ref<Array<any>>(JSON.parse(JSON.stringify(proCityList)));
const rowClickTimer = ref<any>(null);
const archivesForm = ref({
12 months ago
bankAccount: '',
creditCode: '',
foundingTime: null,
registeredCapital: '',
businessScope: '',
ifDistributor: null,
pricingModel: null,
products: '',
productsNum: ''
1 year ago
});
const dialog = useDialog();
const archivesShow = ref<boolean>(false);
const message = useMessage();
const addFormRef = ref<FormInst | null>(null);
// const dialog = useWarning();
const { bool: flag, setFalse: closeDialog, setTrue: openDialog } = useBoolean();
const { loading, startLoading, endLoading } = useLoading();
const editFlag = ref<boolean>(false);
const addForm = ref<UserManage.addForm>({
12 months ago
paymentMethod: '',
productsNum: '',
products: '',
pricingModel: null,
leadSource: null,
ifDistributor: null,
businessScope: '',
registeredCapital: '',
foundingTime: '',
contact1Email: '',
contact1Fax: '',
contact1Phone: '',
contact1Tel: '',
contact1Title: '',
contact1: '',
legalPersonIdcard: '',
bankAccount: '', // 银行账号
clientName: '',
clientCode: '',
creditCode: '',
clientNick: '',
address: '',
legalPersonName: '',
legalPersonPhone: '',
salesmanName: '',
salesmanPhone: '',
attr1: null,
attr2: null,
contact2: null,
addressId: null,
contact2Tel: ''
1 year ago
});
const rules = {
12 months ago
clientCode: createRequiredFormRule('客户编码'),
clientName: createRequiredFormRule('客户名称'),
addressId: createRequiredFormRule('请选择地址'),
creditCode: [
{
required: false,
message: '请输入统一社会信用代码',
trigger: 'blur'
},
{
pattern: /^[0-9A-HJ-NPQRTUWXY]{2}\d{6}[0-9A-HJ-NPQRTUWXY]{10}$/,
message: '统一社会信用代码格式不正确',
trigger: 'blur'
}
],
legalPersonPhone: [
{
required: false,
message: '请输入法人电话',
trigger: 'blur'
},
{
pattern: / ^((13[0-9])|(14[0-9])|(15[0-9])|(17[0-9])|(18[0-9]))\d{8}$/,
message: '手机号格式错误',
trigger: 'change'
}
],
salesmanName: createRequiredFormRule('请输入业务员'),
salesmanPhone: [
{
required: false,
message: '请输入业务员电话',
trigger: 'blur'
},
{
pattern: /^((1[0-9]))\d{9}$/,
message: '手机号格式错误',
trigger: 'change'
}
]
1 year ago
};
const searchForm = ref<UserManage.searchForm>({
12 months ago
creditCode: '',
clientName: '',
salesmanName: '',
pageNum: 1,
pageSize: 10,
total: 0,
attr1: null,
attr2: null
1 year ago
});
const data = ref<UserManage.columns[]>([]);
const columns: Ref<DataTableColumns<UserManage.columns>> = ref([
12 months ago
{
title: '序号',
key: 'index',
align: 'center',
render: (_row, index) => (searchForm.value.pageNum - 1) * searchForm.value.pageSize + index + 1,
width: 120
},
{
title: '客户名称',
key: 'clientName',
width: 200,
align: 'center',
ellipsis: {
tooltip: true
}
},
{
title: '客户简称',
align: 'center',
key: 'clientNick',
width: 200,
ellipsis: {
tooltip: true
}
},
{
title: '客户编码',
align: 'center',
key: 'clientCode',
width: 120
},
{
title: '客户来源',
align: 'center',
key: 'leadSource',
width: 120,
render: row => {
return getLeadSource(row.leadSource);
}
},
{
title: '客户级别',
align: 'center',
key: 'contact2',
width: 120,
render: row => {
return getGrade(row.contact2 + '');
}
},
// {
// title: '统一社会信用代码',
// align: 'center',
// key: 'creditCode',
// width: 200,
// ellipsis: {
// tooltip: true
// }
// },
{
title: '地址',
align: 'center',
key: 'address',
width: 200,
ellipsis: {
tooltip: true
},
render: row => {
return (row.address || '') + (row.contact2Tel || '');
}
},
// {
// title: '法人姓名',
// key: 'legalPersonName',
// align: 'center',
// width: 200,
// ellipsis: {
// tooltip: true
// }
// },
// {
// title: '法人电话',
// align: 'center',
// key: 'legalPersonPhone',
// width: 150
// },
{
title: '业务员',
align: 'center',
width: 120,
key: 'salesmanName'
},
{
title: '业务员电话',
align: 'center',
width: 150,
key: 'salesmanPhone'
},
{
title: '客户类型',
align: 'center',
key: '',
width: 100,
fixed: 'right',
render: row => getClientTypeText(row.attr2).text
},
{
title: '跟进状态',
align: 'center',
width: 100,
key: 'attr1',
fixed: 'right',
render: row => (
<n-tag type={attr1Style(row.attr1).type} v-show={row.attr1}>
{row.attr1}
</n-tag>
)
},
{
title: '操作',
key: 'action',
align: 'center',
width: 180,
fixed: 'right',
render: row => {
return (
<div class="flex">
{/* <n-button
1 year ago
type="info"
size="small"
class="mr-10px"
onClick={() => {
openRecord(row);
}}
>
走访记录
</n-button> */}
12 months ago
{useEditBtn(() => {
editUser(row);
}, 'small')}
{useDelBtn(() => {
deleteById(row);
}, 'small')}
</div>
);
}
}
1 year ago
]);
function synchronization() {
12 months ago
const d = dialog.warning({
title: '提示',
content: '确认要同步数据吗吗?',
positiveText: '确认',
negativeText: '取消',
onPositiveClick: () => {
d.loading = true;
return new Promise(resolve => {
getSyncClientList().then(res => {
if (res.code === 200) {
init();
window.$message?.success('同步成功');
}
resolve(true);
});
});
}
});
1 year ago
}
function getLeadSource(leadSource: number) {
12 months ago
switch (leadSource) {
case 1:
return '电话来访';
case 2:
return '客户介绍';
case 3:
return '上门拜访';
case 4:
return '销售自拓';
case 5:
return '其他';
default:
return '';
}
1 year ago
}
12 months ago
function getGrade(grade: String) {
switch (grade) {
case "1":
return '普通客户';
case "2":
return '重要客户';
case "3":
return '战略客户';
default:
return '普通客户';
}
1 year ago
}
function rowProps(row: UserManage.columns) {
12 months ago
return {
style: 'cursor: pointer;',
onClick: () => {
if (rowClickTimer.value) {
clearTimeout(rowClickTimer.value);
rowClickTimer.value = null;
archivesShow.value = true;
addForm.value = JSON.parse(JSON.stringify(row));
for (const key in archivesForm.value) {
if (Object.hasOwn(archivesForm.value, key)) {
archivesForm.value[key] = row[key];
}
}
return;
}
rowClickTimer.value = setTimeout(() => {
rowClickTimer.value = null;
}, 300);
}
};
1 year ago
}
function closeArchives() {
12 months ago
archivesShow.value = false;
1 year ago
}
function getPaymentMethod() {
12 months ago
for (let i = 2; i <= 30; i += 1) {
paymentMethodOptions.value.push({ label: `到货${i}天付款`, value: i });
}
1 year ago
}
function getClientTypeText(num: number) {
12 months ago
switch (num) {
case 0:
return {
text: '海量客户'
};
case 1:
return {
text: '个人客户'
};
default:
return { text: '个人客户' };
}
1 year ago
}
function attr1Style(str: string) {
12 months ago
switch (str) {
case '待跟进':
return {
type: 'warning'
};
case '潜在客户':
return {
type: 'info'
};
case '有意向':
return {
type: 'info'
};
case '高意向':
return {
type: 'success'
};
case '未成交':
return {
type: 'error'
};
case '已成交':
return {
type: 'success'
};
default:
return {
type: ''
};
}
1 year ago
}
function save(e) {
12 months ago
for (const key in e.value) {
if (Object.hasOwn(e.value, key)) {
addForm.value[key] = e.value[key];
}
}
editUserFach(addForm.value).then(res => {
if (res.code === 200) {
message.success(e.tip);
init();
}
});
1 year ago
}
function handleSearch() {
12 months ago
init();
1 year ago
}
function handleReset() {
12 months ago
searchForm.value = {
creditCode: '',
clientName: '',
salesmanName: '',
pageNum: 1,
pageSize: 10,
total: 0,
attr1: null,
attr2: null
};
init();
1 year ago
}
function cancel() {
12 months ago
addForm.value = {
paymentMethod: '',
productsNum: '',
products: '',
pricingModel: null,
leadSource: null,
ifDistributor: null,
businessScope: '',
registeredCapital: '',
foundingTime: '',
contact1Email: '',
contact1Fax: '',
contact1Phone: '',
contact1Tel: '',
contact1Title: '',
contact1: '',
legalPersonIdcard: '',
bankAccount: '', // 银行账号
clientName: '',
clientCode: '',
creditCode: '',
clientNick: '',
address: '',
legalPersonName: '',
legalPersonPhone: '',
salesmanName: '',
salesmanPhone: '',
attr1: null,
attr2: null,
contact2: null,
addressId: null,
contact2Tel: ''
};
closeDialog();
1 year ago
}
function editUser(row) {
12 months ago
row.addressId = row.addressId?.toString();
addForm.value = { ...row };
editFlag.value = true;
openDialog();
1 year ago
}
function deleteById(row: UserManage.columns) {
12 months ago
// dialog.warn(() => {
// console.log(row);
// });
deleteUser(row.id).then(res => {
if (res.code === 200) {
init();
}
});
1 year ago
}
function submit() {
12 months ago
addFormRef.value?.validate(errors => {
if (!errors) {
if (addForm.value.addressId !== null) {
addForm.value.address = getAddressName(addForm.value.addressId);
}
if (addForm.value.id) {
editUserFach(addForm.value).then(res => {
if (res.code === 200) {
message.success('修改成功!');
cancel();
init();
}
});
} else {
addUser(addForm.value).then(res => {
if (res.code === 200) {
message.success('新增成功!');
cancel();
init();
}
});
}
}
});
1 year ago
}
function getAddressName(code: string) {
12 months ago
const provinceCode = `${code.slice(0, 2)}0000`;
const cityCode = `${code.slice(0, 4)}00`;
const provinceItem = proCityList.find(item => item.value === provinceCode);
if (!provinceItem) return '';
if (!provinceItem.children) return provinceItem.label;
const cityItem = provinceItem.children.find(item => item.value === cityCode);
if (!cityItem) return provinceItem.label;
const districtItem = cityItem.children.find(item => item.value === code);
return provinceItem.label + cityItem.label + (districtItem?.label || '');
1 year ago
}
function init() {
12 months ago
startLoading();
getUserList(searchForm.value).then(res => {
endLoading();
if (res.code === 200) {
data.value = res.rows;
searchForm.value.total = res.total;
}
});
1 year ago
}
onMounted(() => {
12 months ago
init();
getPaymentMethod();
1 year ago
});
</script>
<style scoped lang="scss"></style>