by-crm/frontend/src/views/Customer.vue
2026-01-27 11:54:11 +08:00

384 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="customer-page">
<el-card>
<template #header>
<div class="card-header">
<span>客户管理</span>
<div style="display: flex; gap: 10px; align-items: center">
<el-tag type="info" size="large">
当前保护期配置{{ protectDays }}
</el-tag>
<el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>
新增客户
</el-button>
</div>
</div>
</template>
<!-- 查询表单 -->
<el-form :inline="true" :model="queryForm" class="query-form">
<el-form-item label="客户名称">
<el-input v-model="queryForm.name" placeholder="请输入客户名称" clearable />
</el-form-item>
<el-form-item label="所属行业">
<el-select v-model="queryForm.industry" placeholder="请选择行业" clearable>
<el-option label="制造业" value="manufacturing" />
<el-option label="互联网" value="internet" />
<el-option label="金融" value="finance" />
<el-option label="零售" value="retail" />
<el-option label="教育" value="education" />
<el-option label="医疗" value="healthcare" />
<el-option label="其他" value="other" />
</el-select>
</el-form-item>
<el-form-item label="状态">
<el-select v-model="queryForm.status" placeholder="请选择状态" clearable>
<el-option label="可报备" :value="0" />
<el-option label="保护中" :value="1" />
<el-option label="已失效" :value="2" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">
<el-icon><Search /></el-icon>
查询
</el-button>
<el-button @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
<!-- 数据表格 -->
<el-table :data="tableData" border stripe v-loading="loading">
<el-table-column prop="name" label="客户名称" />
<el-table-column prop="phone" label="联系电话" />
<el-table-column prop="address" label="地址" />
<el-table-column prop="industry" label="所属行业">
<template #default="{ row }">
{{ getIndustryLabel(row.industry) }}
</template>
</el-table-column>
<el-table-column prop="status" label="状态">
<template #default="{ row }">
<el-tag v-if="row.status === 0" type="success">可报备</el-tag>
<el-tag v-else-if="row.status === 1" type="warning">保护中</el-tag>
<el-tag v-else type="info">已失效</el-tag>
</template>
</el-table-column>
<el-table-column prop="currentDealerName" label="当前经销商" width="150">
<template #default="{ row }">
{{ row.status === 1 ? (row.currentDealerName || '-') : '-' }}
</template>
</el-table-column>
<el-table-column prop="protectEndDate" label="保护期截止" width="180">
<template #default="{ row }">
{{ row.status === 1 && row.protectEndDate ? formatDate(row.protectEndDate) : '-' }}
</template>
</el-table-column>
<el-table-column prop="createdAt" label="创建时间" width="180" />
<el-table-column label="操作" width="180" fixed="right">
<template #default="{ row }">
<el-button link type="primary" @click="handleEdit(row)">编辑</el-button>
<el-button link type="danger" @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-model:current-page="queryForm.current"
v-model:page-size="queryForm.size"
:total="total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
prev-text="上一页"
next-text="下一页"
@size-change="fetchData"
@current-change="fetchData"
style="margin-top: 20px; justify-content: flex-end"
/>
</el-card>
<!-- 新增/编辑对话框 -->
<el-dialog
v-model="dialogVisible"
:title="dialogTitle"
width="600px"
@close="handleDialogClose"
>
<el-form ref="formRef" :model="formData" :rules="rules" label-width="100px">
<el-form-item label="客户名称" prop="name">
<el-autocomplete
v-model="formData.name"
:fetch-suggestions="searchSchool"
placeholder="请输入客户名称"
:trigger-on-focus="false"
style="width: 100%"
@select="handleSchoolSelect"
>
<template #default="{ item }">
<div class="school-option">
<div class="school-name">{{ item.schoolName }}</div>
<div class="school-location">{{ item.location }}</div>
</div>
</template>
</el-autocomplete>
</el-form-item>
<el-form-item label="联系电话" prop="phone">
<el-input v-model="formData.phone" placeholder="请输入联系电话" />
</el-form-item>
<el-form-item label="地址" prop="address">
<el-input v-model="formData.address" placeholder="请输入地址" />
</el-form-item>
<el-form-item label="所属行业" prop="industry">
<el-select v-model="formData.industry" placeholder="请选择行业" style="width: 100%">
<el-option label="制造业" value="manufacturing" />
<el-option label="互联网" value="internet" />
<el-option label="金融" value="finance" />
<el-option label="零售" value="retail" />
<el-option label="教育" value="education" />
<el-option label="医疗" value="healthcare" />
<el-option label="其他" value="other" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSubmit">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, computed } from 'vue'
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus'
import { getCustomerPage, createCustomer, updateCustomer, deleteCustomer } from '@/api/customer'
import { getConfigValue } from '@/api/system'
import { searchSchoolByName } from '@/api/school'
import type { Customer, CustomerForm } from '@/types'
import type { School } from '@/api/school'
const protectDays = ref<number>(90)
const loading = ref(false)
const tableData = ref<Customer[]>([])
const total = ref(0)
const dialogVisible = ref(false)
const dialogTitle = computed(() => (formData.id ? '编辑客户' : '新增客户'))
const formRef = ref<FormInstance>()
const schoolSearchLoading = ref(false)
const queryForm = reactive({
current: 1,
size: 10,
name: '',
industry: '',
status: undefined as number | undefined
})
const formData = reactive<CustomerForm & { id?: number }>({
name: '',
phone: '',
address: '',
industry: ''
})
const rules: FormRules = {
name: [{ required: true, message: '请输入客户名称', trigger: 'blur' }]
}
// 格式化日期,去掉时分秒
const formatDate = (dateStr: string | null | undefined) => {
if (!dateStr) return '-'
return dateStr.split(' ')[0] // 只保留日期部分,去掉时间
}
// 搜索学校(用于自动完成)
const searchSchool = async (queryString: string, cb: any) => {
if (!queryString || queryString.trim().length < 1) {
cb([])
return
}
schoolSearchLoading.value = true
try {
const schools = await searchSchoolByName(queryString)
// 转换为 el-autocomplete 需要的格式
const suggestions = schools.map(school => ({
...school,
value: school.schoolName
}))
cb(suggestions)
} catch (error) {
console.error('搜索学校失败', error)
cb([])
} finally {
schoolSearchLoading.value = false
}
}
// 选择学校后的回调
const handleSchoolSelect = (item: School) => {
// 用户可以选择性地填充地址字段
if (item.location && !formData.address) {
formData.address = item.location
}
}
// 获取行业标签
const getIndustryLabel = (industry: string) => {
const map: Record<string, string> = {
manufacturing: '制造业',
internet: '互联网',
finance: '金融',
retail: '零售',
education: '教育',
healthcare: '医疗',
other: '其他'
}
return map[industry] || industry
}
// 查询数据
const fetchData = async () => {
loading.value = true
try {
const res = await getCustomerPage(queryForm)
tableData.value = res.records
total.value = res.total
} catch (error) {
console.error('查询失败', error)
} finally {
loading.value = false
}
}
// 查询按钮
const handleQuery = () => {
queryForm.current = 1
fetchData()
}
// 重置按钮
const handleReset = () => {
queryForm.name = ''
queryForm.industry = ''
queryForm.status = undefined
queryForm.current = 1
fetchData()
}
// 新增按钮
const handleAdd = () => {
dialogVisible.value = true
}
// 编辑按钮
const handleEdit = (row: Customer) => {
Object.assign(formData, row)
dialogVisible.value = true
}
// 删除按钮
const handleDelete = async (row: Customer) => {
try {
await ElMessageBox.confirm(
'确定要删除该客户吗?',
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
)
await deleteCustomer(row.id)
ElMessage.success('删除成功')
fetchData()
} catch (error) {
// 取消删除
}
}
// 提交表单
const handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid) => {
if (valid) {
try {
if (formData.id) {
await updateCustomer(formData.id, formData)
ElMessage.success('更新成功')
} else {
await createCustomer(formData)
ElMessage.success('创建成功')
}
dialogVisible.value = false
fetchData()
} catch (error) {
console.error('提交失败', error)
}
}
})
}
// 对话框关闭
const handleDialogClose = () => {
formRef.value?.resetFields()
Object.assign(formData, {
name: '',
phone: '',
address: '',
industry: ''
})
delete formData.id
}
// 获取保护期配置
const fetchProtectDays = async () => {
try {
const res = await getConfigValue('report.protect.days')
protectDays.value = parseInt(res, 10) || 90
} catch (error) {
console.error('获取配置失败', error)
}
}
onMounted(() => {
fetchProtectDays()
fetchData()
})
</script>
<style scoped>
.customer-page {
width: 100%;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.query-form {
margin-bottom: 20px;
}
.school-option {
display: flex;
flex-direction: column;
}
.school-name {
font-weight: 500;
color: #303133;
}
.school-location {
font-size: 12px;
color: #909399;
margin-top: 4px;
}
</style>