feat: customer form and search UI for pinyin initials.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zlwuandCursor committed 2026-08-07 17:33:23 +08:00
1 parent b151ac6499
commit 021bd73301
5 files changed
+44 -37

No files matched your search

+2
View File
@@ -63,6 +63,8 @@ export interface CustomerForm {
id?: number | string;
/** 客户名称 */
customerName: string;
/** 客户名称拼音首字母(小写) */
namePinyinAbbr?: string;
/** 客户简称 */
shortName: string;
/** 客户类型(company-企业/person-个人) */
@@ -39,7 +39,7 @@
allowCreate?: boolean
}>(),
{
placeholder: '请输入客户名称搜索',
placeholder: '请输入客户名称/拼音搜索',
allowCreate: true
}
)
@@ -605,6 +605,7 @@ const viewSections = computed<ViewSection[]>(() => {
const address = [c.province, c.city, c.district, c.address].filter(Boolean).join(' ')
const basicFields: ViewField[] = [
tagField('客户类型', 'crm_customer_type', c.customerType),
{ label: '拼音首字母', value: c.namePinyinAbbr },
{ label: '客户简称', value: c.shortName },
{ label: '手机号', value: c.mobile },
{ label: '固定电话', value: c.telephone },
@@ -24,6 +24,20 @@
<el-form-item label="客户名称" prop="customerName">
<el-input v-model="form.customerName" placeholder="请输入客户名称" maxlength="200" />
</el-form-item>
<el-form-item label="拼音首字母" class="form-field-span-2">
<div class="pinyin-abbr-row">
<el-input
v-model="form.namePinyinAbbr"
placeholder="默认按名称自动生成,可手改"
maxlength="100"
clearable
/>
<el-button class="btn-ghost" @click="regeneratePinyinAbbr">
<el-icon><Refresh /></el-icon>
重新生成
</el-button>
</div>
</el-form-item>
<el-form-item label="客户简称" prop="shortName">
<el-input v-model="form.shortName" placeholder="请输入客户简称" maxlength="100" />
</el-form-item>
@@ -152,7 +166,7 @@
<el-divider content-position="left">证件信息</el-divider>
<div class="form-grid-4">
<el-form-item label="营业执照" prop="businessLicenseImages" class="form-field-span-2">
<el-form-item label="营业执照" class="form-field-span-2">
<AttachmentUploadField
ref="businessLicenseUploadRef"
v-model="form.businessLicenseImages"
@@ -160,10 +174,9 @@
accept="image/*,.pdf"
folder="crm/customer/license"
hint="支持 JPG、PNG、PDF,最多 5 个"
@update:model-value="handleCertFieldChange('businessLicenseImages')"
/>
</el-form-item>
<el-form-item label="经营许可证" prop="businessPermitImages" class="form-field-span-2">
<el-form-item label="经营许可证" class="form-field-span-2">
<AttachmentUploadField
ref="businessPermitUploadRef"
v-model="form.businessPermitImages"
@@ -171,7 +184,6 @@
accept="image/*,.pdf"
folder="crm/customer/permit"
hint="支持 JPG、PNG、PDF,最多 5 个"
@update:model-value="handleCertFieldChange('businessPermitImages')"
/>
</el-form-item>
<el-form-item label="其他证件" class="form-col-span-2">
@@ -262,7 +274,7 @@
<script setup lang="ts">
import { ref, reactive, computed, nextTick } from 'vue'
import { Close, Check } from '@element-plus/icons-vue'
import { Close, Check, Refresh } from '@element-plus/icons-vue'
import type { FormItemRule } from 'element-plus'
import { ElMessage } from 'element-plus'
import {
@@ -288,6 +300,7 @@ const emit = defineEmits(['success'])
const form = ref<CustomerFormType>({
id: undefined,
customerName: '',
namePinyinAbbr: '',
shortName: '',
customerType: '未分类',
mobile: undefined,
@@ -312,11 +325,6 @@ const form = ref<CustomerFormType>({
otherCertImages: undefined
})
/** 证件字段变更后触发表单校验 */
function handleCertFieldChange(field: 'businessLicenseImages' | 'businessPermitImages') {
formRef.value?.validateField(field)
}
function unwrapApiData<T>(res: T | { data?: T }): T {
if (res != null && typeof res === 'object' && 'data' in res && (res as { data?: T }).data !== undefined) {
return (res as { data: T }).data
@@ -334,30 +342,6 @@ const rules = ref<Record<string, FormItemRule[]>>({
province: [{ required: true, message: '省份不能为空', trigger: 'blur' }],
city: [{ required: true, message: '城市不能为空', trigger: 'blur' }],
district: [{ required: true, message: '区县不能为空', trigger: 'blur' }],
businessLicenseImages: [
{
validator: (_rule: any, value: any, callback: any) => {
if (!value || !String(value).trim()) {
callback(new Error('请至少上传一张营业执照图片'))
} else {
callback()
}
},
trigger: 'change'
}
],
businessPermitImages: [
{
validator: (_rule: any, value: any, callback: any) => {
if (!value || !String(value).trim()) {
callback(new Error('请至少上传一张经营许可证图片'))
} else {
callback()
}
},
trigger: 'change'
}
],
mobile: [{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }],
email: [{ type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' }]
})
@@ -539,9 +523,16 @@ function buildSubmitPayload() {
const payload: Record<string, any> = { ...form.value }
// 详情接口会同时返回 businessLicenseImg(旧字段),避免覆盖新上传的 businessLicenseImages
delete payload.businessLicenseImg
// 显式传空串:后端按「重新生成」处理;有值则手改入库
payload.namePinyinAbbr = (form.value.namePinyinAbbr || '').trim()
return payload
}
function regeneratePinyinAbbr() {
form.value.namePinyinAbbr = ''
ElMessage.info('已清空,保存时将按客户名称重新生成拼音首字母')
}
/** 提交表单 */
const submitForm = () => {
if (hasAttachmentUploading()) {
@@ -591,6 +582,7 @@ const reset = () => {
form.value = {
id: undefined,
customerName: '',
namePinyinAbbr: '',
shortName: '',
customerType: '未分类',
mobile: undefined,
@@ -625,4 +617,16 @@ defineExpose({ open })
.form-grid-4 .form-field-span-2 {
grid-column: span 2;
}
.pinyin-abbr-row {
display: flex;
gap: 8px;
align-items: center;
width: 100%;
.el-input {
flex: 1;
min-width: 0;
}
}
</style>
+2 -2
View File
@@ -24,7 +24,7 @@
<el-form-item label="客户名称">
<el-input
v-model="searchForm.customerName"
placeholder="客户名称"
placeholder="客户名称/拼音首字母"
clearable
@keyup.enter="handleSearch"
/>
@@ -133,7 +133,7 @@
<div class="list-toolbar-row">
<ListQuickSearchInput
v-model="quickSearchKeyword"
placeholder="客户名称/编号/手机号"
placeholder="客户名称/编号/手机号/拼音首字母"
@search="handleQuickSearch"
@clear="handleQuickSearchClear"
/>