Переглянути джерело

feat: customer form and search UI for pinyin initials.

Co-authored-by: Cursor <cursoragent@cursor.com>
zlwu 2 місяців тому
батько
коміт
021bd73301

+ 2 - 0
web-admin/src/api/crm/customerTypes.ts

@@ -63,6 +63,8 @@ export interface CustomerForm {
   id?: number | string;
   id?: number | string;
   /** 客户名称 */
   /** 客户名称 */
   customerName: string;
   customerName: string;
+  /** 客户名称拼音首字母(小写) */
+  namePinyinAbbr?: string;
   /** 客户简称 */
   /** 客户简称 */
   shortName: string;
   shortName: string;
   /** 客户类型(company-企业/person-个人) */
   /** 客户类型(company-企业/person-个人) */

+ 1 - 1
web-admin/src/components/business/db/DbCrmCustomerSelect.vue

@@ -39,7 +39,7 @@
       allowCreate?: boolean
       allowCreate?: boolean
     }>(),
     }>(),
     {
     {
-      placeholder: '请输入客户名称搜索',
+      placeholder: '请输入客户名称/拼音搜索',
       allowCreate: true
       allowCreate: true
     }
     }
   )
   )

+ 1 - 0
web-admin/src/views/crm/customer/CustomerDetailDialog.vue

@@ -605,6 +605,7 @@ const viewSections = computed<ViewSection[]>(() => {
   const address = [c.province, c.city, c.district, c.address].filter(Boolean).join(' ')
   const address = [c.province, c.city, c.district, c.address].filter(Boolean).join(' ')
   const basicFields: ViewField[] = [
   const basicFields: ViewField[] = [
     tagField('客户类型', 'crm_customer_type', c.customerType),
     tagField('客户类型', 'crm_customer_type', c.customerType),
+    { label: '拼音首字母', value: c.namePinyinAbbr },
     { label: '客户简称', value: c.shortName },
     { label: '客户简称', value: c.shortName },
     { label: '手机号', value: c.mobile },
     { label: '手机号', value: c.mobile },
     { label: '固定电话', value: c.telephone },
     { label: '固定电话', value: c.telephone },

+ 38 - 34
web-admin/src/views/crm/customer/CustomerFormDialog.vue

@@ -24,6 +24,20 @@
         <el-form-item label="客户名称" prop="customerName">
         <el-form-item label="客户名称" prop="customerName">
           <el-input v-model="form.customerName" placeholder="请输入客户名称" maxlength="200" />
           <el-input v-model="form.customerName" placeholder="请输入客户名称" maxlength="200" />
         </el-form-item>
         </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-form-item label="客户简称" prop="shortName">
           <el-input v-model="form.shortName" placeholder="请输入客户简称" maxlength="100" />
           <el-input v-model="form.shortName" placeholder="请输入客户简称" maxlength="100" />
         </el-form-item>
         </el-form-item>
@@ -152,7 +166,7 @@
 
 
       <el-divider content-position="left">证件信息</el-divider>
       <el-divider content-position="left">证件信息</el-divider>
       <div class="form-grid-4">
       <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
           <AttachmentUploadField
             ref="businessLicenseUploadRef"
             ref="businessLicenseUploadRef"
             v-model="form.businessLicenseImages"
             v-model="form.businessLicenseImages"
@@ -160,10 +174,9 @@
             accept="image/*,.pdf"
             accept="image/*,.pdf"
             folder="crm/customer/license"
             folder="crm/customer/license"
             hint="支持 JPG、PNG、PDF,最多 5 个"
             hint="支持 JPG、PNG、PDF,最多 5 个"
-            @update:model-value="handleCertFieldChange('businessLicenseImages')"
           />
           />
         </el-form-item>
         </el-form-item>
-        <el-form-item label="经营许可证" prop="businessPermitImages" class="form-field-span-2">
+        <el-form-item label="经营许可证" class="form-field-span-2">
           <AttachmentUploadField
           <AttachmentUploadField
             ref="businessPermitUploadRef"
             ref="businessPermitUploadRef"
             v-model="form.businessPermitImages"
             v-model="form.businessPermitImages"
@@ -171,7 +184,6 @@
             accept="image/*,.pdf"
             accept="image/*,.pdf"
             folder="crm/customer/permit"
             folder="crm/customer/permit"
             hint="支持 JPG、PNG、PDF,最多 5 个"
             hint="支持 JPG、PNG、PDF,最多 5 个"
-            @update:model-value="handleCertFieldChange('businessPermitImages')"
           />
           />
         </el-form-item>
         </el-form-item>
         <el-form-item label="其他证件" class="form-col-span-2">
         <el-form-item label="其他证件" class="form-col-span-2">
@@ -262,7 +274,7 @@
 
 
 <script setup lang="ts">
 <script setup lang="ts">
 import { ref, reactive, computed, nextTick } from 'vue'
 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 type { FormItemRule } from 'element-plus'
 import { ElMessage } from 'element-plus'
 import { ElMessage } from 'element-plus'
 import {
 import {
@@ -288,6 +300,7 @@ const emit = defineEmits(['success'])
 const form = ref<CustomerFormType>({
 const form = ref<CustomerFormType>({
   id: undefined,
   id: undefined,
   customerName: '',
   customerName: '',
+  namePinyinAbbr: '',
   shortName: '',
   shortName: '',
   customerType: '未分类',
   customerType: '未分类',
   mobile: undefined,
   mobile: undefined,
@@ -312,11 +325,6 @@ const form = ref<CustomerFormType>({
   otherCertImages: undefined
   otherCertImages: undefined
 })
 })
 
 
-/** 证件字段变更后触发表单校验 */
-function handleCertFieldChange(field: 'businessLicenseImages' | 'businessPermitImages') {
-  formRef.value?.validateField(field)
-}
-
 function unwrapApiData<T>(res: T | { data?: T }): T {
 function unwrapApiData<T>(res: T | { data?: T }): T {
   if (res != null && typeof res === 'object' && 'data' in res && (res as { data?: T }).data !== undefined) {
   if (res != null && typeof res === 'object' && 'data' in res && (res as { data?: T }).data !== undefined) {
     return (res as { data: T }).data
     return (res as { data: T }).data
@@ -334,30 +342,6 @@ const rules = ref<Record<string, FormItemRule[]>>({
   province: [{ required: true, message: '省份不能为空', trigger: 'blur' }],
   province: [{ required: true, message: '省份不能为空', trigger: 'blur' }],
   city: [{ required: true, message: '城市不能为空', trigger: 'blur' }],
   city: [{ required: true, message: '城市不能为空', trigger: 'blur' }],
   district: [{ 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' }],
   mobile: [{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }],
   email: [{ type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' }]
   email: [{ type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' }]
 })
 })
@@ -539,9 +523,16 @@ function buildSubmitPayload() {
   const payload: Record<string, any> = { ...form.value }
   const payload: Record<string, any> = { ...form.value }
   // 详情接口会同时返回 businessLicenseImg(旧字段),避免覆盖新上传的 businessLicenseImages
   // 详情接口会同时返回 businessLicenseImg(旧字段),避免覆盖新上传的 businessLicenseImages
   delete payload.businessLicenseImg
   delete payload.businessLicenseImg
+  // 显式传空串:后端按「重新生成」处理;有值则手改入库
+  payload.namePinyinAbbr = (form.value.namePinyinAbbr || '').trim()
   return payload
   return payload
 }
 }
 
 
+function regeneratePinyinAbbr() {
+  form.value.namePinyinAbbr = ''
+  ElMessage.info('已清空,保存时将按客户名称重新生成拼音首字母')
+}
+
 /** 提交表单 */
 /** 提交表单 */
 const submitForm = () => {
 const submitForm = () => {
   if (hasAttachmentUploading()) {
   if (hasAttachmentUploading()) {
@@ -591,6 +582,7 @@ const reset = () => {
   form.value = {
   form.value = {
     id: undefined,
     id: undefined,
     customerName: '',
     customerName: '',
+    namePinyinAbbr: '',
     shortName: '',
     shortName: '',
     customerType: '未分类',
     customerType: '未分类',
     mobile: undefined,
     mobile: undefined,
@@ -625,4 +617,16 @@ defineExpose({ open })
 .form-grid-4 .form-field-span-2 {
 .form-grid-4 .form-field-span-2 {
   grid-column: 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>
 </style>

+ 2 - 2
web-admin/src/views/crm/customer/index.vue

@@ -24,7 +24,7 @@
             <el-form-item label="客户名称">
             <el-form-item label="客户名称">
               <el-input
               <el-input
                 v-model="searchForm.customerName"
                 v-model="searchForm.customerName"
-                placeholder="客户名称"
+                placeholder="客户名称/拼音首字母"
                 clearable
                 clearable
                 @keyup.enter="handleSearch"
                 @keyup.enter="handleSearch"
               />
               />
@@ -133,7 +133,7 @@
         <div class="list-toolbar-row">
         <div class="list-toolbar-row">
           <ListQuickSearchInput
           <ListQuickSearchInput
             v-model="quickSearchKeyword"
             v-model="quickSearchKeyword"
-            placeholder="客户名称/编号/手机号"
+            placeholder="客户名称/编号/手机号/拼音首字母"
             @search="handleQuickSearch"
             @search="handleQuickSearch"
             @clear="handleQuickSearchClear"
             @clear="handleQuickSearchClear"
           />
           />