zlwu 10 месяцев назад
Родитель
Сommit
c278b06015

BIN
backend/.DS_Store


BIN
backend/ruoyi-modules/.DS_Store


+ 15 - 0
backend/ruoyi-modules/ruoyi-crm/src/main/java/org/dromara/crm/domain/CrmCustomer.java

@@ -230,4 +230,19 @@ public class CrmCustomer extends TenantEntity {
      * 备注
      */
     private String remark;
+
+    /**
+     * 营业执照图片OSS ID(多个用逗号分隔)
+     */
+    private String businessLicenseImages;
+
+    /**
+     * 经营许可证图片OSS ID(多个用逗号分隔)
+     */
+    private String businessPermitImages;
+
+    /**
+     * 其他证件图片OSS ID(多个用逗号分隔)
+     */
+    private String otherCertImages;
 }

+ 15 - 0
backend/ruoyi-modules/ruoyi-crm/src/main/java/org/dromara/crm/domain/bo/CrmCustomerBo.java

@@ -164,6 +164,21 @@ public class CrmCustomerBo extends BaseEntity {
      */
     private String remark;
 
+    /**
+     * 营业执照图片OSS ID(多个用逗号分隔)
+     */
+    private String businessLicenseImages;
+
+    /**
+     * 经营许可证图片OSS ID(多个用逗号分隔)
+     */
+    private String businessPermitImages;
+
+    /**
+     * 其他证件图片OSS ID(多个用逗号分隔)
+     */
+    private String otherCertImages;
+
     /**
      * 搜索关键字(用于客户名称/编号的OR查询)
      */

+ 15 - 0
backend/ruoyi-modules/ruoyi-crm/src/main/java/org/dromara/crm/domain/vo/CrmCustomerVo.java

@@ -287,4 +287,19 @@ public class CrmCustomerVo implements Serializable {
      */
     @ExcelProperty(value = "备注")
     private String remark;
+
+    /**
+     * 营业执照图片OSS ID(多个用逗号分隔)
+     */
+    private String businessLicenseImages;
+
+    /**
+     * 经营许可证图片OSS ID(多个用逗号分隔)
+     */
+    private String businessPermitImages;
+
+    /**
+     * 其他证件图片OSS ID(多个用逗号分隔)
+     */
+    private String otherCertImages;
 }

BIN
backend/ruoyi-modules/ruoyi-xg/.DS_Store


+ 12 - 0
backend/script/sql/update/add_customer_cert_images.sql

@@ -0,0 +1,12 @@
+-- 新增客户证件图片字段
+-- 创建时间: 2025-12-30
+-- 说明: 为crm_customer表添加营业执照、经营许可证、其他证件图片存储字段
+
+ALTER TABLE `crm_customer`
+    ADD COLUMN `business_license_images` varchar(1000) DEFAULT NULL COMMENT '营业执照图片OSS ID(多个用逗号分隔)' AFTER `remark`;
+
+ALTER TABLE `crm_customer`
+    ADD COLUMN `business_permit_images` varchar(1000) DEFAULT NULL COMMENT '经营许可证图片OSS ID(多个用逗号分隔)' AFTER `business_license_images`;
+
+ALTER TABLE `crm_customer`
+    ADD COLUMN `other_cert_images` varchar(1000) DEFAULT NULL COMMENT '其他证件图片OSS ID(多个用逗号分隔)' AFTER `business_permit_images`;

+ 12 - 0
web/src/api/crm/customer/types.ts

@@ -101,6 +101,12 @@ export interface CustomerForm {
   clueId?: number;
   /** 备注 */
   remark?: string;
+  /** 营业执照图片OSS ID(多个用逗号分隔) */
+  businessLicenseImages?: string;
+  /** 经营许可证图片OSS ID(多个用逗号分隔) */
+  businessPermitImages?: string;
+  /** 其他证件图片OSS ID(多个用逗号分隔) */
+  otherCertImages?: string;
 }
 
 /**
@@ -181,6 +187,12 @@ export interface CustomerVO {
   updateTime?: string;
   /** 备注 */
   remark?: string;
+  /** 营业执照图片OSS ID(多个用逗号分隔) */
+  businessLicenseImages?: string;
+  /** 经营许可证图片OSS ID(多个用逗号分隔) */
+  businessPermitImages?: string;
+  /** 其他证件图片OSS ID(多个用逗号分隔) */
+  otherCertImages?: string;
 }
 
 /**

+ 20 - 31
web/src/assets/styles/fix-datepicker-border.scss

@@ -5,81 +5,70 @@
  */
 
 // 日期选择器 - 单日期
-.el-date-editor.el-input,
-.el-date-editor.el-input__wrapper {
+// 只给内部的 .el-input__wrapper 添加边框,避免同时给外层 .el-date-editor 与内层 wrapper 都设置边框造成“二重边框”
+.el-date-editor .el-input__wrapper {
   border: 1px solid #dcdfe6 !important;
   box-shadow: none !important;
 }
 
-.el-date-editor.el-input:hover,
-.el-date-editor.el-input__wrapper:hover {
+.el-date-editor .el-input__wrapper:hover {
   border-color: #c0c4cc !important;
 }
 
-.el-date-editor.el-input.is-focus,
-.el-date-editor.el-input__wrapper.is-focus,
-.el-date-editor.is-focus .el-input__wrapper {
+.el-date-editor.is-focus .el-input__wrapper,
+.el-date-editor .el-input__wrapper.is-focus {
   border-color: #409eff !important;
 }
 
 // 日期范围选择器
-.el-date-editor--daterange.el-input,
-.el-date-editor--daterange.el-input__wrapper,
+.el-date-editor--daterange .el-input__wrapper,
 .el-date-editor.el-date-editor--daterange .el-input__wrapper {
   border: 1px solid #dcdfe6 !important;
   box-shadow: none !important;
   transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1) !important;
 }
 
-.el-date-editor--daterange.el-input:hover,
-.el-date-editor--daterange.el-input__wrapper:hover,
-.el-date-editor.el-date-editor--daterange:hover .el-input__wrapper {
+.el-date-editor--daterange .el-input__wrapper:hover,
+.el-date-editor.el-date-editor--daterange .el-input__wrapper:hover {
   border-color: #c0c4cc !important;
 }
 
-.el-date-editor--daterange.el-input.is-focus,
-.el-date-editor--daterange.el-input__wrapper.is-focus,
-.el-date-editor.el-date-editor--daterange.is-focus .el-input__wrapper {
+.el-date-editor--daterange.is-focus .el-input__wrapper,
+.el-date-editor.el-date-editor--daterange .el-input__wrapper.is-focus {
   border-color: #409eff !important;
 }
 
 // 日期时间选择器
-.el-date-editor--datetime.el-input,
-.el-date-editor--datetime.el-input__wrapper,
+.el-date-editor--datetime .el-input__wrapper,
 .el-date-editor.el-date-editor--datetime .el-input__wrapper {
   border: 1px solid #dcdfe6 !important;
   box-shadow: none !important;
 }
 
-.el-date-editor--datetime.el-input:hover,
-.el-date-editor--datetime.el-input__wrapper:hover,
-.el-date-editor.el-date-editor--datetime:hover .el-input__wrapper {
+.el-date-editor--datetime .el-input__wrapper:hover,
+.el-date-editor.el-date-editor--datetime .el-input__wrapper:hover {
   border-color: #c0c4cc !important;
 }
 
-.el-date-editor--datetime.el-input.is-focus,
-.el-date-editor--datetime.el-input__wrapper.is-focus,
-.el-date-editor.el-date-editor--datetime.is-focus .el-input__wrapper {
+.el-date-editor--datetime.is-focus .el-input__wrapper,
+.el-date-editor.el-date-editor--datetime .el-input__wrapper.is-focus {
   border-color: #409eff !important;
 }
 
 // 日期时间范围选择器
-.el-date-editor--datetimerange.el-input,
-.el-date-editor--datetimerange.el-input__wrapper,
+.el-date-editor--datetimerange .el-input__wrapper,
 .el-date-editor.el-date-editor--datetimerange .el-input__wrapper {
   border: 1px solid #dcdfe6 !important;
   box-shadow: none !important;
 }
 
-.el-date-editor--datetimerange.el-input:hover,
-.el-date-editor--datetimerange.el-input__wrapper:hover,
-.el-date-editor.el-date-editor--datetimerange:hover .el-input__wrapper {
+.el-date-editor--datetimerange .el-input__wrapper:hover,
+.el-date-editor.el-date-editor--datetimerange .el-input__wrapper:hover {
   border-color: #c0c4cc !important;
 }
 
-.el-date-editor--datetimerange.el-input.is-focus,
-.el-date-editor--datetimerange.el-input__wrapper.is-focus,
-.el-date-editor.el-date-editor--datetimerange.is-focus .el-input__wrapper {
+.el-date-editor--datetimerange.is-focus .el-input__wrapper,
+.el-date-editor.el-date-editor--datetimerange .el-input__wrapper.is-focus {
   border-color: #409eff !important;
 }
 

+ 10 - 15
web/src/views/crm/customer/components/contactForm.vue

@@ -41,23 +41,18 @@
         </el-col>
       </el-row>
       <el-row :gutter="20">
-        <el-col :span="12">
-          <el-form-item label="QQ号" prop="qq">
-            <el-input v-model="form.qq" placeholder="请输入QQ号" maxlength="20" />
-          </el-form-item>
-        </el-col>
         <el-col :span="12">
           <el-form-item label="部门" prop="department">
             <el-input v-model="form.department" placeholder="请输入部门" maxlength="100" />
           </el-form-item>
         </el-col>
-      </el-row>
-      <el-row :gutter="20">
         <el-col :span="12">
           <el-form-item label="职位" prop="position">
             <el-input v-model="form.position" placeholder="请输入职位" maxlength="100" />
           </el-form-item>
         </el-col>
+      </el-row>
+      <el-row :gutter="20">
         <el-col :span="12">
           <el-form-item label="角色" prop="role">
             <el-select v-model="form.role" placeholder="请选择角色" clearable>
@@ -65,13 +60,6 @@
             </el-select>
           </el-form-item>
         </el-col>
-      </el-row>
-      <el-row :gutter="20">
-        <el-col :span="12">
-          <el-form-item label="生日" prop="birthday">
-            <el-date-picker v-model="form.birthday" type="date" placeholder="请选择生日" value-format="YYYY-MM-DD" style="width: 100%" />
-          </el-form-item>
-        </el-col>
         <el-col :span="12">
           <el-form-item label="状态" prop="status">
             <el-radio-group v-model="form.status">
@@ -81,7 +69,14 @@
           </el-form-item>
         </el-col>
       </el-row>
-      <el-row>
+      <el-row :gutter="20">
+        <el-col :span="12" v-if="false">
+          <el-form-item label="生日" prop="birthday">
+            <el-date-picker v-model="form.birthday" type="date" placeholder="请选择生日" value-format="YYYY-MM-DD" style="width: 100%" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row v-if="false">
         <el-col :span="24">
           <el-form-item label="住址" prop="address">
             <el-input v-model="form.address" placeholder="请输入住址" type="textarea" :rows="2" maxlength="255" />

+ 71 - 0
web/src/views/crm/customer/detail.vue

@@ -51,6 +51,46 @@
               <el-descriptions-item label="地址">
                 {{ [customerInfo.province, customerInfo.city, customerInfo.district, customerInfo.address].filter(Boolean).join(' ') }}
               </el-descriptions-item>
+
+              <!-- 证件图片展示 -->
+              <el-descriptions-item label="营业执照" v-if="customerInfo.businessLicenseImages">
+                <div class="certificate-images">
+                  <ImageUpload
+                    :modelValue="customerInfo.businessLicenseImages"
+                    :limit="5"
+                    :file-size="10"
+                    :file-type="['jpg', 'jpeg', 'png', 'pdf']"
+                    :disabled="true"
+                    placeholder="暂无营业执照"
+                  />
+                </div>
+              </el-descriptions-item>
+
+              <el-descriptions-item label="经营许可证" v-if="customerInfo.businessPermitImages">
+                <div class="certificate-images">
+                  <ImageUpload
+                    :modelValue="customerInfo.businessPermitImages"
+                    :limit="5"
+                    :file-size="10"
+                    :file-type="['jpg', 'jpeg', 'png', 'pdf']"
+                    :disabled="true"
+                    placeholder="暂无经营许可证"
+                  />
+                </div>
+              </el-descriptions-item>
+
+              <el-descriptions-item label="其他证件" v-if="customerInfo.otherCertImages">
+                <div class="certificate-images">
+                  <ImageUpload
+                    :modelValue="customerInfo.otherCertImages"
+                    :limit="10"
+                    :file-size="10"
+                    :file-type="['jpg', 'jpeg', 'png', 'pdf']"
+                    :disabled="true"
+                    placeholder="暂无其他证件"
+                  />
+                </div>
+              </el-descriptions-item>
             </el-descriptions>
             <el-skeleton v-else :rows="5" animated />
           </el-card>
@@ -402,6 +442,7 @@ import { useUserStore } from '@/store/modules/user';
 import { useRouter } from 'vue-router';
 import { ElMessageBox } from 'element-plus/es';
 import ImagePreview from '@/components/ImagePreview/index.vue';
+import ImageUpload from '@/components/ImageUpload/index.vue';
 import request from '@/utils/request';
 import CustomerForm from './form.vue';
 import FollowForm from './followForm.vue';
@@ -1124,4 +1165,34 @@ defineExpose({
   border-color: #67c23a;
   color: #fff;
 }
+
+/* 证件图片展示样式 */
+.certificate-images {
+  max-width: 400px;
+}
+
+.certificate-images :deep(.el-upload-list--picture-card .el-upload-list__item) {
+  width: 80px;
+  height: 80px;
+  margin: 0 8px 8px 0;
+}
+
+.certificate-images :deep(.el-upload--picture-card) {
+  width: 80px;
+  height: 80px;
+  margin: 0;
+}
+
+.certificate-images :deep(.el-upload-list--picture-card .el-upload-list__item-thumbnail) {
+  width: 100%;
+  height: 100%;
+}
+
+.certificate-images :deep(.el-upload-list--picture-card .el-upload-list__item-name) {
+  display: none;
+}
+
+.certificate-images :deep(.el-upload-list--picture-card .el-upload-list__item-actions) {
+  display: none;
+}
 </style>

+ 81 - 3
web/src/views/crm/customer/form.vue

@@ -134,6 +134,50 @@
           </el-form-item>
         </el-col>
       </el-row>
+
+      <el-divider content-position="left">证件信息</el-divider>
+      <el-row :gutter="20">
+        <el-col :span="24">
+          <el-form-item label="营业执照" prop="businessLicenseImages">
+            <ImageUpload
+              v-model="form.businessLicenseImages"
+              :limit="5"
+              :file-size="10"
+              :file-type="['jpg', 'jpeg', 'png', 'pdf']"
+              :isShowTip="false"
+              placeholder="请上传营业执照图片或PDF"
+            />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row :gutter="20">
+        <el-col :span="24">
+          <el-form-item label="经营许可证" prop="businessPermitImages">
+            <ImageUpload
+              v-model="form.businessPermitImages"
+              :limit="5"
+              :file-size="10"
+              :file-type="['jpg', 'jpeg', 'png', 'pdf']"
+              :isShowTip="false"
+              placeholder="请上传经营许可证图片或PDF"
+            />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row :gutter="20">
+        <el-col :span="24">
+          <el-form-item label="其他证件" prop="otherCertImages">
+            <ImageUpload
+              v-model="form.otherCertImages"
+              :limit="10"
+              :file-size="10"
+              :file-type="['jpg', 'jpeg', 'png', 'pdf']"
+              placeholder="请上传其他相关证件图片或PDF"
+            />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
       <el-row>
         <el-col :span="24">
           <el-form-item label="备注" prop="remark">
@@ -153,12 +197,14 @@
 
 <script setup lang="ts" name="CustomerForm">
 import { ref, reactive, getCurrentInstance, toRefs } from 'vue';
+import type { FormItemRule } from 'element-plus';
 import { getCustomer, addCustomer, updateCustomer } from '@/api/crm/customer';
 import type { CustomerForm as CustomerFormType } from '@/api/crm/customer/types';
 import { ComponentInternalInstance } from 'vue';
 import { listUser } from '@/api/system/user';
 import { useDict } from '@/utils/dict';
 import { useUserStore } from '@/store/modules/user';
+import ImageUpload from '@/components/ImageUpload/index.vue';
 
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const userStore = useUserStore();
@@ -192,11 +238,14 @@ const form = ref<CustomerFormType>({
   ownerUserId: undefined,
   nextTime: undefined,
   clueId: undefined,
-  remark: undefined
+  remark: undefined,
+  businessLicenseImages: undefined,
+  businessPermitImages: undefined,
+  otherCertImages: undefined
 });
 
 // 表单校验规则
-const rules = ref({
+const rules = ref<Record<string, FormItemRule[]>>({
   customerName: [{ required: true, message: '客户名称不能为空', trigger: 'blur' }],
   shortName: [{ required: true, message: '客户简称不能为空', trigger: 'blur' }],
   customerType: [{ required: true, message: '客户类型不能为空', trigger: 'change' }],
@@ -205,6 +254,32 @@ const rules = ref({
   province: [{ required: true, message: '省份不能为空', trigger: 'blur' }],
   city: [{ required: true, message: '城市不能为空', trigger: 'blur' }],
   district: [{ required: true, message: '区县不能为空', trigger: 'blur' }],
+  businessLicenseImages: [
+    { required: true, message: '请至少上传一张营业执照图片', trigger: 'change' },
+    {
+      validator: (rule, value, callback) => {
+        if (!value || (Array.isArray(value) && value.length === 0)) {
+          callback(new Error('请至少上传一张营业执照图片'));
+        } else {
+          callback();
+        }
+      },
+      trigger: 'change'
+    }
+  ],
+  businessPermitImages: [
+    { required: true, message: '请至少上传一张经营许可证图片', trigger: 'change' },
+    {
+      validator: (rule, value, callback) => {
+        if (!value || (Array.isArray(value) && value.length === 0)) {
+          callback(new Error('请至少上传一张经营许可证图片'));
+        } else {
+          callback();
+        }
+      },
+      trigger: 'change'
+    }
+  ],
   mobile: [{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }],
   email: [{ type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' }]
 });
@@ -303,7 +378,10 @@ const reset = () => {
     ownerUserId: undefined,
     nextTime: undefined,
     clueId: undefined,
-    remark: undefined
+    remark: undefined,
+    businessLicenseImages: undefined,
+    businessPermitImages: undefined,
+    otherCertImages: undefined
   };
   formRef.value?.resetFields();
 };