沐梦. 2 päivää sitten
vanhempi
sitoutus
7e61c46f89
35 muutettua tiedostoa jossa 750 lisäystä ja 392 poistoa
  1. 22 7
      src/api/customer/customerPool.js
  2. 31 4
      src/views/common/businessActivity.vue
  3. 22 10
      src/views/customer/care/add.vue
  4. 22 10
      src/views/customer/care/edit.vue
  5. 7 15
      src/views/customer/highseas/detail.vue
  6. 75 42
      src/views/customer/highseas/edit.vue
  7. 5 5
      src/views/customer/highseas/index.vue
  8. 12 7
      src/views/customer/valid/detail.vue
  9. 73 40
      src/views/customer/valid/edit.vue
  10. 15 6
      src/views/customer/valid/index.vue
  11. 1 1
      src/views/saleManage/accountsReceivable/index.vue
  12. 31 13
      src/views/saleManage/leads/add.vue
  13. 12 10
      src/views/saleManage/leads/detail.vue
  14. 29 9
      src/views/saleManage/leads/edit.vue
  15. 10 8
      src/views/saleManage/leads/index.vue
  16. 36 16
      src/views/saleManage/opportunity/add.vue
  17. 12 10
      src/views/saleManage/opportunity/detail.vue
  18. 27 9
      src/views/saleManage/opportunity/edit.vue
  19. 13 11
      src/views/saleManage/opportunity/index.vue
  20. 67 23
      src/views/saleManage/platformSelection/add.vue
  21. 11 9
      src/views/saleManage/platformSelection/detail.vue
  22. 67 21
      src/views/saleManage/platformSelection/edit.vue
  23. 6 5
      src/views/saleManage/platformSelection/index.vue
  24. 49 35
      src/views/saleManage/projectSelection/add.vue
  25. 12 9
      src/views/saleManage/projectSelection/detail.vue
  26. 49 35
      src/views/saleManage/projectSelection/edit.vue
  27. 6 6
      src/views/saleManage/projectSelection/index.vue
  28. 6 3
      src/views/visit/plan/add.vue
  29. 6 3
      src/views/visit/plan/edit.vue
  30. 1 1
      src/views/visit/plan/index.vue
  31. 1 1
      src/views/visit/record/index.vue
  32. 6 3
      src/views/visit/routine/add.vue
  33. 1 1
      src/views/visit/routine/detail.vue
  34. 6 3
      src/views/visit/routine/edit.vue
  35. 1 1
      src/views/visit/routine/index.vue

+ 22 - 7
src/api/customer/customerPool.js

@@ -12,15 +12,30 @@ export function listPool(query) {
   })
 }
 
-// 查询有效客户信息列表
-export function listValidCustomer(query) {
+// 查询我负责的客户列表
+export function listMineCustomer(query) {
   return request({
-    url: '/customer/customerPool/list',
+    url: '/customer/customerPool/mineList',
     method: 'get',
-    params: {
-      ...query,
-      isHighSeas: 'false'
-    }
+    params: query
+  })
+}
+
+// 查询我参与的客户列表
+export function listInvolvedCustomer(query) {
+  return request({
+    url: '/customer/customerPool/involvedList',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询全部有效客户列表
+export function listAllCustomer(query) {
+  return request({
+    url: '/customer/customerPool/allList',
+    method: 'get',
+    params: query
   })
 }
 

+ 31 - 4
src/views/common/businessActivity.vue

@@ -55,7 +55,7 @@
       <el-tab-pane label="跟进记录" name="record">
         <div class="pane-content-custom">
           <div class="record-toolbar">
-            <el-button link type="primary" icon="Plus" class="new-record-btn" @click="handleNewRecord">
+            <el-button v-if="!isReadOnly" link type="primary" icon="Plus" class="new-record-btn" @click="handleNewRecord">
               新建跟进记录
             </el-button>
             <div class="filter-item">
@@ -186,7 +186,7 @@
           <el-col :span="12">
             <el-form-item label="随访人" prop="followPeople">
               <el-select v-model="recordForm.followPeople" placeholder="请选择" class="w100" filterable multiple collapse-tags>
-                <el-option v-for="item in staffOptions" :key="item.staffId" :label="item.staffName" :value="item.staffId" />
+                <el-option v-for="item in staffOptions" :key="item.staffId" :label="(item.staffCode ? '(' + item.staffCode + ') ' : '') + item.staffName" :value="item.staffId" />
               </el-select>
             </el-form-item>
           </el-col>
@@ -288,7 +288,7 @@
         </el-form-item>
         <el-form-item v-else label="添加人员:" prop="userNo">
           <el-select v-model="memberForm.userNo" placeholder="请选择" class="w100" filterable @change="handleUserChange">
-            <el-option v-for="item in staffOptions" :key="item.staffId" :label="item.staffName" :value="item.staffId" />
+            <el-option v-for="item in staffOptions" :key="item.staffId" :label="(item.staffCode ? '(' + item.staffCode + ') ' : '') + item.staffName" :value="item.staffId" />
           </el-select>
         </el-form-item>
 
@@ -319,7 +319,7 @@
 </template>
 
 <script setup>
-import { ref, computed, watch, onMounted, reactive, getCurrentInstance, toRefs } from 'vue';
+import { ref, computed, watch, onMounted, reactive, getCurrentInstance, toRefs, nextTick } from 'vue';
 import { User, Search, Plus } from '@element-plus/icons-vue';
 import { listTeamMember, delTeamMember, addTeamMember, updateTeamMember } from "@/api/customer/teamMember";
 import { listRecord, addRecord } from "@/api/visit/record";
@@ -327,6 +327,7 @@ import { listOperateLog } from "@/api/visit/operateLog";
 import { listUser } from "@/api/system/user/index";
 import { listComStaff } from "@/api/system/comStaff/index";
 import { globalHeaders } from "@/utils/request";
+import { useUserStore } from "@/store/modules/user";
 
 const proxy = getCurrentInstance().proxy;
 const { visit_type: visitTypeDict, T0001: teamRoleDict } = toRefs(reactive(proxy.useDict("visit_type", "T0001")));
@@ -352,6 +353,9 @@ const activeTab = ref('team');
 const memberSearch = ref('');
 const visitType = ref('');
 const submitLoading = ref(false);
+const userStore = useUserStore();
+
+const emit = defineEmits(['update-readonly']);
 
 // 状态
 const userOptions = ref([]);
@@ -409,6 +413,26 @@ const filteredTeam = computed(() => {
   return teamList.value.filter(m => (m.realName || '').includes(memberSearch.value));
 });
 
+const isReadOnly = computed(() => {
+  // 只有系统超级管理员(userId=1)才免除只读限制
+  // 租户管理员(roleKey=admin)仍需遵守团队成员权限
+  if (String(userStore.userId) === '1') {
+    return false;
+  }
+  const member = teamList.value.find(m => String(m.userId) === String(userStore.userId));
+  if (member) {
+    return member.updateAccredit !== 1;
+  }
+  return true;
+});
+
+// 当 isReadOnly 变化时,通知父组件;用 nextTick 避免在 Vue patch 循环中同步触发更新
+watch(isReadOnly, (val) => {
+  nextTick(() => {
+    emit('update-readonly', val);
+  });
+});
+
 const filteredTeamRoleDict = computed(() => {
   if (!teamRoleDict.value) return [];
   // 获取已被其他团队成员占用的角色编码
@@ -783,6 +807,9 @@ onMounted(() => {
   loadStaffs();
   loadDicts();
   fetchDynamicData();
+  nextTick(() => {
+    emit('update-readonly', isReadOnly.value);
+  });
 });
 </script>
 

+ 22 - 10
src/views/customer/care/add.vue

@@ -52,7 +52,7 @@
             <el-col :span="12">
               <el-form-item label="业务员:" prop="salesman">
                 <el-select v-model="form.salesman" placeholder="请选择" style="width: 100%" clearable>
-                  <el-option v-for="item in staffOptions" :key="item.staffId" :label="item.staffName" :value="item.staffName" />
+                  <el-option v-for="item in staffOptions" :key="item.staffId" :label="(item.staffCode ? '(' + item.staffCode + ') ' : '') + item.staffName" :value="item.staffName" />
                 </el-select>
               </el-form-item>
             </el-col>
@@ -78,14 +78,14 @@
             </el-col>
             <el-col :span="12">
               <el-form-item label="手机:" prop="phone">
-                <el-input v-model="form.phone" placeholder="请输入手机" />
+                <el-input v-model="form.phone" placeholder="请输入手机" maxlength="11" />
               </el-form-item>
             </el-col>
           </el-row>
           <el-row :gutter="20">
             <el-col :span="12">
               <el-form-item label="固定电话:" prop="telephone">
-                <el-input v-model="form.telephone" placeholder="请输入固定电话" />
+                <el-input v-model="form.telephone" placeholder="请输入固定电话" maxlength="20" />
               </el-form-item>
             </el-col>
             <el-col :span="12">
@@ -139,10 +139,10 @@
 
         <div class="dialog-section no-border no-margin">
           <el-form-item label="礼品描述:" prop="giftDesc">
-            <el-input v-model="form.giftDesc" type="textarea" :rows="4" placeholder="请输入内容" />
+            <el-input v-model="form.giftDesc" type="textarea" :rows="4" placeholder="请输入内容" maxlength="500" show-word-limit />
           </el-form-item>
           <el-form-item label="关怀理由:" prop="concernArgument">
-            <el-input v-model="form.concernArgument" type="textarea" :rows="4" placeholder="请输入内容" />
+            <el-input v-model="form.concernArgument" type="textarea" :rows="4" placeholder="请输入内容" maxlength="500" show-word-limit />
           </el-form-item>
         </div>
       </el-form>
@@ -227,12 +227,24 @@ const rules = reactive({
   department: [{ required: true, message: "部门不能为空", trigger: "change" }],
   salesman: [{ required: true, message: "业务员不能为空", trigger: "change" }],
   contactPerson: [{ required: true, message: "联系人不能为空", trigger: "change" }],
-  phone: [{ required: true, message: "手机不能为空", trigger: "blur" }],
-  telephone: [{ required: true, message: "固定电话不能为空", trigger: "blur" }],
+  phone: [
+    { required: true, message: "手机不能为空", trigger: "blur" },
+    { pattern: /^1[3-9]\d{9}$/, message: "请输入正确的11位手机号码", trigger: "blur" }
+  ],
+  telephone: [
+    { required: true, message: "固定电话不能为空", trigger: "blur" },
+    { pattern: /^([0-9-]{7,20})$/, message: "请输入正确的固定电话号码", trigger: "blur" }
+  ],
   concernType: [{ required: true, message: "关怀类型不能为空", trigger: "change" }],
   amount: [{ required: true, message: "金额不能为空", trigger: "blur" }],
-  giftDesc: [{ required: true, message: "礼品描述不能为空", trigger: "blur" }],
-  concernArgument: [{ required: true, message: "关怀理由不能为空", trigger: "blur" }]
+  giftDesc: [
+    { required: true, message: "礼品描述不能为空", trigger: "blur" },
+    { max: 500, message: "描述不能超过500个字符", trigger: "blur" }
+  ],
+  concernArgument: [
+    { required: true, message: "关怀理由不能为空", trigger: "blur" },
+    { max: 500, message: "理由不能超过500个字符", trigger: "blur" }
+  ]
 });
 
 /** 加载客户列表 (500条) */
@@ -337,7 +349,7 @@ function handleClose() {
 
 /** 加载基础选项 */
 const initBaseOptions = () => {
-  listIndustryCategory().then(res => { industryOptions.value = res.data || []; });
+  listIndustryCategory().then(res => { industryOptions.value = res.data || res.rows || []; });
   listComStaff({ pageSize: 1000 }).then(res => { staffOptions.value = res.rows || res.data || []; });
   deptTreeSelect().then(res => {
     const data = res.data || [];

+ 22 - 10
src/views/customer/care/edit.vue

@@ -52,7 +52,7 @@
             <el-col :span="12">
               <el-form-item label="业务员:" prop="salesman">
                 <el-select v-model="form.salesman" placeholder="请选择" style="width: 100%" clearable>
-                  <el-option v-for="item in staffOptions" :key="item.staffId" :label="item.staffName" :value="item.staffName" />
+                  <el-option v-for="item in staffOptions" :key="item.staffId" :label="(item.staffCode ? '(' + item.staffCode + ') ' : '') + item.staffName" :value="item.staffName" />
                 </el-select>
               </el-form-item>
             </el-col>
@@ -78,14 +78,14 @@
             </el-col>
             <el-col :span="12">
               <el-form-item label="手机:" prop="phone">
-                <el-input v-model="form.phone" placeholder="请输入手机" />
+                <el-input v-model="form.phone" placeholder="请输入手机" maxlength="11" />
               </el-form-item>
             </el-col>
           </el-row>
           <el-row :gutter="20">
             <el-col :span="12">
               <el-form-item label="固定电话:" prop="telephone">
-                <el-input v-model="form.telephone" placeholder="请输入固定电话" />
+                <el-input v-model="form.telephone" placeholder="请输入固定电话" maxlength="20" />
               </el-form-item>
             </el-col>
             <el-col :span="12">
@@ -128,10 +128,10 @@
 
         <div class="dialog-section no-border no-margin">
           <el-form-item label="礼品描述:" prop="giftDesc">
-            <el-input v-model="form.giftDesc" type="textarea" :rows="4" placeholder="请输入内容" />
+            <el-input v-model="form.giftDesc" type="textarea" :rows="4" placeholder="请输入内容" maxlength="500" show-word-limit />
           </el-form-item>
           <el-form-item label="关怀理由:" prop="concernArgument">
-            <el-input v-model="form.concernArgument" type="textarea" :rows="4" placeholder="请输入内容" />
+            <el-input v-model="form.concernArgument" type="textarea" :rows="4" placeholder="请输入内容" maxlength="500" show-word-limit />
           </el-form-item>
         </div>
       </el-form>
@@ -195,12 +195,24 @@ const rules = reactive({
   department: [{ required: true, message: "部门不能为空", trigger: "change" }],
   salesman: [{ required: true, message: "业务员不能为空", trigger: "change" }],
   contactPerson: [{ required: true, message: "联系人不能为空", trigger: "change" }],
-  phone: [{ required: true, message: "手机不能为空", trigger: "blur" }],
-  telephone: [{ required: true, message: "固定电话不能为空", trigger: "blur" }],
+  phone: [
+    { required: true, message: "手机不能为空", trigger: "blur" },
+    { pattern: /^1[3-9]\d{9}$/, message: "请输入正确的11位手机号码", trigger: "blur" }
+  ],
+  telephone: [
+    { required: true, message: "固定电话不能为空", trigger: "blur" },
+    { pattern: /^([0-9-]{7,20})$/, message: "请输入正确的固定电话号码", trigger: "blur" }
+  ],
   concernType: [{ required: true, message: "关怀类型不能为空", trigger: "change" }],
   amount: [{ required: true, message: "金额不能为空", trigger: "blur" }],
-  giftDesc: [{ required: true, message: "礼品描述不能为空", trigger: "blur" }],
-  concernArgument: [{ required: true, message: "关怀理由不能为空", trigger: "blur" }]
+  giftDesc: [
+    { required: true, message: "礼品描述不能为空", trigger: "blur" },
+    { max: 500, message: "描述不能超过500个字符", trigger: "blur" }
+  ],
+  concernArgument: [
+    { required: true, message: "关怀理由不能为空", trigger: "blur" },
+    { max: 500, message: "理由不能超过500个字符", trigger: "blur" }
+  ]
 });
 
 /** 获取详情 */
@@ -318,7 +330,7 @@ function handleClose(refresh = false) {
 
 /** 加载基础选项 */
 const initBaseOptions = () => {
-  listIndustryCategory().then(res => { industryOptions.value = res.data || []; });
+  listIndustryCategory().then(res => { industryOptions.value = res.data || res.rows || []; });
   listComStaff({ pageSize: 1000 }).then(res => { staffOptions.value = res.rows || res.data || []; });
   deptTreeSelect().then(res => {
     const data = res.data || [];

+ 7 - 15
src/views/customer/highseas/detail.vue

@@ -22,7 +22,7 @@
                 <div class="info-section">
                   <div class="section-header">
                     <span class="section-title">基本信息</span>
-                    <el-button type="primary" class="blue-btn" icon="Edit" @click="handleEdit">编辑</el-button>
+                    <el-button v-if="!isReadOnly" type="primary" class="blue-btn" icon="Edit" @click="handleEdit">编辑</el-button>
                   </div>
                   <el-row :gutter="40" class="info-grid">
                     <el-col :span="8"><div class="info-cell"><span class="label">工商名称</span><span class="value">{{ customerData.businessCustomerName }}</span></div></el-col>
@@ -121,7 +121,7 @@
             <el-tab-pane label="客户联系人" name="contact">
               <div class="tab-scroll-pane">
                 <div class="contact-toolbar">
-                  <el-button type="primary" icon="Plus" class="blue-btn" @click="handleAddContact">新增</el-button>
+                  <el-button v-if="!isReadOnly" type="primary" icon="Plus" class="blue-btn" @click="handleAddContact">新增</el-button>
                 </div>
                 <el-table :data="customerData.customerContactVoList || []" border class="contact-table">
                   <el-table-column label="联系人姓名" align="center" prop="contactName" />
@@ -131,8 +131,8 @@
                   <el-table-column label="办公座机" align="center" prop="officePhone" />
                   <el-table-column label="操作" align="center" width="120">
                     <template #default="scope">
-                      <el-button link type="primary" @click="handleEditContact(scope.row)">编辑</el-button>
-                      <el-button link type="danger" @click="handleDeleteContact(scope.row)">删除</el-button>
+                      <el-button v-if="!isReadOnly" link type="primary" @click="handleEditContact(scope.row)">编辑</el-button>
+                      <el-button v-if="!isReadOnly" link type="danger" @click="handleDeleteContact(scope.row)">删除</el-button>
                     </template>
                   </el-table-column>
                   <template #empty>暂无数据</template>
@@ -156,6 +156,7 @@
               industryName: customerData.industryName || '',
               profession: customerData.industryName || ''
             }"
+            @update-readonly="val => isReadOnly = val"
           />
         </div>
       </div>
@@ -337,6 +338,7 @@ import CustomerEdit from './edit.vue';
 const emit = defineEmits(['success']);
 const proxy = getCurrentInstance().proxy;
 const visible = ref(false);
+const isReadOnly = ref(true);
 const loading = ref(false);
 const customerData = ref({});
 const bizInfo = ref({});
@@ -394,21 +396,11 @@ const loadData = (id) => {
     customerData.value = res.data;
     bizInfo.value = res.data.customerBusinessInfoVo || {};
     salesInfo.value = res.data.customerSalesInfoVo || {};
-    refreshTeamMembers();
     loading.value = false;
   }).catch(() => loading.value = false);
 };
 
-const refreshTeamMembers = () => {
-  if (!customerData.value?.customerNo) return;
-  listTeamMember(customerData.value.customerNo).then(res => {
-    teamMembers.value = (res.data || []).map(m => ({
-      id: m.id,
-      name: m.realName || '未知',
-      role: m.roleCodeName || m.roleCode
-    }));
-  });
-};
+// refreshTeamMembers removed since we listen to update-readonly event
 
 const handleAddContact = () => {
   contactTitle.value = "新建客户联系人";

+ 75 - 42
src/views/customer/highseas/edit.vue

@@ -31,12 +31,12 @@
             </el-col>
             <el-col :span="8">
               <el-form-item label="客户名称" required prop="customerName">
-                <el-input v-model="form.customerName" placeholder="请输入客户名称" class="w100" />
+                <el-input v-model="form.customerName" placeholder="请输入客户名称" class="w100" maxlength="100" show-word-limit />
               </el-form-item>
             </el-col>
             <el-col :span="8">
               <el-form-item label="工商名称" prop="businessCustomerName">
-                <el-input v-model="form.businessCustomerName" placeholder="请输入工商名称" class="w100" />
+                <el-input v-model="form.businessCustomerName" placeholder="请输入工商名称" class="w100" maxlength="100" show-word-limit />
               </el-form-item>
             </el-col>
           </el-row>
@@ -44,7 +44,7 @@
             <el-col :span="8">
               <el-form-item label="客户来源" prop="customerSourceId">
                 <el-select v-model="form.customerSourceId" placeholder="请选择" class="w100" clearable>
-                  <el-option v-for="item in dict.customer_source" :key="item.value" :label="item.label" :value="item.value" />
+                  <el-option v-for="item in dict.K0001" :key="item.value" :label="item.label" :value="item.value" />
                 </el-select>
               </el-form-item>
             </el-col>
@@ -58,7 +58,7 @@
             <el-col :span="8">
               <el-form-item label="企业规模" prop="enterpriseScaleId">
                 <el-select v-model="form.enterpriseScaleId" placeholder="请选择" class="w100" clearable>
-                  <el-option v-for="item in dict.enterprise_scale" :key="item.value" :label="item.label" :value="item.value" />
+                  <el-option v-for="item in scaleOptions" :key="String(item.id)" :label="item.enterpriseScaleName || item.enterpriseScale || item.label || item.name" :value="String(item.id)" />
                 </el-select>
               </el-form-item>
             </el-col>
@@ -67,14 +67,14 @@
             <el-col :span="8">
               <el-form-item label="客户类别" prop="customerTypeId">
                 <el-select v-model="form.customerTypeId" placeholder="请选择" class="w100" clearable>
-                  <el-option v-for="item in dict.customer_type" :key="item.value" :label="item.label" :value="item.value" />
+                  <el-option v-for="item in categoryOptions" :key="String(item.id)" :label="item.customerTypeName || item.customerCategoryName || item.typeName || item.name" :value="String(item.id)" />
                 </el-select>
               </el-form-item>
             </el-col>
             <el-col :span="8">
               <el-form-item label="所属行业" prop="industryCategoryId">
                 <el-select v-model="form.industryCategoryId" placeholder="请选择" class="w100" clearable>
-                  <el-option v-for="item in dict.industry_category" :key="item.value" :label="item.label" :value="item.value" />
+                  <el-option v-for="item in industryOptions" :key="item.id" :label="item.industryCategoryName" :value="item.id" />
                 </el-select>
               </el-form-item>
             </el-col>
@@ -89,29 +89,29 @@
           <el-row :gutter="20">
             <el-col :span="8">
               <el-form-item label="固定电话" prop="landline">
-                <el-input v-model="form.landline" placeholder="请输入固定电话" class="w100" />
+                <el-input v-model="form.landline" placeholder="请输入固定电话" class="w100" maxlength="20" />
               </el-form-item>
             </el-col>
             <el-col :span="8">
               <el-form-item label="传真" prop="fax">
-                <el-input v-model="form.fax" placeholder="请输入传真" class="w100" />
+                <el-input v-model="form.fax" placeholder="请输入传真" class="w100" maxlength="20" />
               </el-form-item>
             </el-col>
             <el-col :span="8">
               <el-form-item label="网址" prop="url">
-                <el-input v-model="form.url" placeholder="请输入网址" class="w100" />
+                <el-input v-model="form.url" placeholder="请输入网址" class="w100" maxlength="100" />
               </el-form-item>
             </el-col>
           </el-row>
           <el-row :gutter="20">
             <el-col :span="8">
               <el-form-item label="邮政编码" prop="postCode">
-                <el-input v-model="form.postCode" placeholder="请输入邮政编码" class="w100" />
+                <el-input v-model="form.postCode" placeholder="请输入邮政编码" class="w100" maxlength="6" />
               </el-form-item>
             </el-col>
             <el-col :span="8">
               <el-form-item label="邮箱" prop="email">
-                <el-input v-model="form.email" placeholder="请输入邮箱" class="w100" />
+                <el-input v-model="form.email" placeholder="请输入邮箱" class="w100" maxlength="50" />
               </el-form-item>
             </el-col>
           </el-row>
@@ -127,7 +127,7 @@
                     class="flex1"
                     @change="handleAreaChange"
                   />
-                  <el-input v-model="form.address" placeholder="请输入详细地址" class="flex1" />
+                  <el-input v-model="form.address" placeholder="请输入详细地址" class="flex1" maxlength="200" show-word-limit />
                 </div>
               </el-form-item>
             </el-col>
@@ -139,30 +139,30 @@
           <div class="section-title">工商信息</div>
           <el-row :gutter="20">
             <el-col :span="8">
-              <el-form-item label="企业工商名称">
-                <el-input v-model="form.businessCustomerName" class="w100" />
+              <el-form-item label="企业工商名称" prop="businessCustomerName">
+                <el-input v-model="form.businessCustomerName" class="w100" maxlength="100" show-word-limit />
               </el-form-item>
             </el-col>
             <el-col :span="8">
-              <el-form-item label="社会信用代码">
-                <el-input v-model="form.socialCreditCode" class="w100" />
+              <el-form-item label="社会信用代码" prop="socialCreditCode">
+                <el-input v-model="form.socialCreditCode" class="w100" maxlength="18" />
               </el-form-item>
             </el-col>
             <el-col :span="8">
-              <el-form-item label="法人姓名">
-                <el-input v-model="form.legalPersonName" class="w100" />
+              <el-form-item label="法人姓名" prop="legalPersonName">
+                <el-input v-model="form.legalPersonName" class="w100" maxlength="50" />
               </el-form-item>
             </el-col>
           </el-row>
           <el-row :gutter="20">
             <el-col :span="8">
-              <el-form-item label="注册资本">
-                <el-input v-model="form.registeredCapital" class="w100" />
+              <el-form-item label="注册资本" prop="registeredCapital">
+                <el-input v-model="form.registeredCapital" class="w100" maxlength="50" />
               </el-form-item>
             </el-col>
             <el-col :span="8">
-              <el-form-item label="登记机关">
-                <el-input v-model="form.registrationAuthority" class="w100" />
+              <el-form-item label="登记机关" prop="registrationAuthority">
+                <el-input v-model="form.registrationAuthority" class="w100" maxlength="100" />
               </el-form-item>
             </el-col>
             <el-col :span="8">
@@ -185,15 +185,15 @@
               </el-form-item>
             </el-col>
             <el-col :span="8">
-              <el-form-item label="实缴资本">
-                <el-input v-model="form.paidInCapital" class="w100" />
+              <el-form-item label="实缴资本" prop="paidInCapital">
+                <el-input v-model="form.paidInCapital" class="w100" maxlength="50" />
               </el-form-item>
             </el-col>
           </el-row>
           <el-row :gutter="20">
             <el-col :span="24">
-              <el-form-item label="详细地址">
-                <el-input v-model="form.businessAddress" placeholder="请输入详细地址" class="w100" />
+              <el-form-item label="详细地址" prop="businessAddress">
+                <el-input v-model="form.businessAddress" placeholder="请输入详细地址" class="w100" maxlength="200" show-word-limit />
               </el-form-item>
             </el-col>
           </el-row>
@@ -223,14 +223,14 @@
             <el-col :span="8">
               <el-form-item label="负责人">
                 <el-select v-model="form.salesPersonId" placeholder="请选择" class="w100" clearable>
-                  <el-option v-for="item in staffOptions" :key="item.staffId" :label="item.staffName" :value="String(item.staffId)" />
+                  <el-option v-for="item in staffOptions" :key="item.staffId" :label="(item.staffCode ? '(' + item.staffCode + ') ' : '') + item.staffName" :value="String(item.staffId)" />
                 </el-select>
               </el-form-item>
             </el-col>
             <el-col :span="8">
               <el-form-item label="客服支持">
                 <el-select v-model="form.serviceStaffId" placeholder="请选择" class="w100" clearable>
-                  <el-option v-for="item in staffOptions" :key="item.staffId" :label="item.staffName" :value="String(item.staffId)" />
+                  <el-option v-for="item in staffOptions" :key="item.staffId" :label="(item.staffCode ? '(' + item.staffCode + ') ' : '') + item.staffName" :value="String(item.staffId)" />
                 </el-select>
               </el-form-item>
             </el-col>
@@ -262,17 +262,17 @@ import { ref, reactive, onMounted, getCurrentInstance, isRef } from 'vue';
 import { getPool as getCustomerInfo, updatePool as updateCustomerInfo, listCompanyOption, listLevelOption } from "@/api/customer/customerPool";
 import { listComStaff } from "@/api/system/comStaff/index";
 import { listProvinceWithCities } from "@/api/customer/addressArea";
+import { listIndustryCategory } from "@/api/customer/industryCategory";
+import { listEnterpriseScale } from "@/api/customer/enterpriseScale";
+import { listCustomerTypeOption } from "@/api/customer/customerDict";
 import ImageUpload from "@/components/ImageUpload";
 
 const proxy = getCurrentInstance().proxy;
-const dict = proxy.useDict(
-  'customer_source', 
-  'Q0001', 
-  'enterprise_scale', 
-  'customer_type', 
-  'industry_category',
+const dict = reactive(proxy.useDict(
+  'K0001', 
+  'Q0001',
   'registration_status'
-);
+));
 
 const visible = ref(false);
 const loading = ref(false);
@@ -326,14 +326,44 @@ const form = reactive({
 });
 
 const rules = {
-  customerName: [{ required: true, message: "客户名称不能为空", trigger: "blur" }],
-  belongCompanyId: [{ required: true, message: "所属公司不能为空", trigger: "change" }]
+  customerName: [
+    { required: true, message: "客户名称不能为空", trigger: "blur" },
+    { max: 100, message: "客户名称不能超过100个字符", trigger: "blur" }
+  ],
+  belongCompanyId: [{ required: true, message: "所属公司不能为空", trigger: "change" }],
+  businessCustomerName: [{ max: 100, message: "工商名称不能超过100个字符", trigger: "blur" }],
+  landline: [
+    { pattern: /^([0-9-]{7,20})$/, message: "请输入正确的电话号码", trigger: "blur" }
+  ],
+  fax: [
+    { pattern: /^([0-9-]{7,20})$/, message: "请输入正确的传真号码", trigger: "blur" }
+  ],
+  url: [
+    { pattern: /^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([\/\w .-]*)*\/?$/, message: "请输入正确的网址", trigger: "blur" }
+  ],
+  postCode: [
+    { pattern: /^\d{6}$/, message: "请输入正确的6位邮政编码", trigger: "blur" }
+  ],
+  email: [
+    { pattern: /^([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})$/, message: "请输入正确的邮箱地址", trigger: "blur" }
+  ],
+  socialCreditCode: [
+    { pattern: /^[A-Za-z0-9]{18}$/, message: "请输入正确的18位统一社会信用代码", trigger: "blur" }
+  ],
+  legalPersonName: [{ max: 50, message: "法人姓名不能超过50个字符", trigger: "blur" }],
+  registeredCapital: [{ max: 50, message: "注册资本不能超过50个字符", trigger: "blur" }],
+  registrationAuthority: [{ max: 100, message: "登记机关不能超过100个字符", trigger: "blur" }],
+  paidInCapital: [{ max: 50, message: "实缴资本不能超过50个字符", trigger: "blur" }],
+  businessAddress: [{ max: 200, message: "详细地址不能超过200个字符", trigger: "blur" }]
 };
 
 const companyOptions = ref([]);
 const levelOptions = ref([]);
 const staffOptions = ref([]);
 const areaOptions = ref([]);
+const scaleOptions = ref([]);
+const categoryOptions = ref([]);
+const industryOptions = ref([]);
 
 const open = (id) => {
   if (!id) return;
@@ -391,12 +421,12 @@ const loadData = (id) => {
     };
 
     form.enterpriseTypeId = getDictValue(dict.Q0001, data.enterpriseTypeName);
-    form.customerTypeId = getDictValue(dict.customer_type, data.customerTypeName);
+    form.customerTypeId = data.customerTypeId ? String(data.customerTypeId) : undefined;
 
     // 基础字典项 (这些通常已有 code 映射)
-    form.customerSourceId = sales.customerSource;
-    form.enterpriseScaleId = data.enterpriseScale;
-    form.industryCategoryId = data.industryCategory;
+    form.customerSourceId = sales.customerSource ? String(sales.customerSource).split(',')[0] : undefined;
+    form.enterpriseScaleId = (data.enterpriseScaleId || data.enterpriseScale) ? String(data.enterpriseScaleId || data.enterpriseScale) : undefined;
+    form.industryCategoryId = (data.industryCategoryId || data.industryCategory) ? Number(data.industryCategoryId || data.industryCategory) : undefined;
     form.registrationStatus = biz.registrationStatus;
     
     form.landline = data.landline;
@@ -411,7 +441,7 @@ const loadData = (id) => {
     form.regCityNo = data.regCityNo;
     form.regCountyNo = data.regCountyNo;
     if (data.regProvincialNo && data.regCityNo && data.regCountyNo) {
-      form.areaArray = [String(data.regProvincialNo), String(data.regCityNo), String(data.regCountyNo)];
+      form.areaArray = [Number(data.regProvincialNo), Number(data.regCityNo), Number(data.regCountyNo)];
     }
 
     // 工商信息回显
@@ -499,6 +529,9 @@ onMounted(() => {
     const list = res.rows || [];
     areaOptions.value = handleTree(list, "id", "parentId");
   });
+  listIndustryCategory().then(res => industryOptions.value = res.data || res.rows || []);
+  listEnterpriseScale().then(res => scaleOptions.value = res.data || res.rows || []);
+  listCustomerTypeOption().then(res => categoryOptions.value = res.data || res.rows || []);
 });
 
 defineExpose({ open });

+ 5 - 5
src/views/customer/highseas/index.vue

@@ -36,14 +36,14 @@
           <el-col :span="6">
             <el-form-item label="业务负责人" prop="salesPersonId" class="custom-form-item">
               <el-select v-model="queryParams.salesPersonId" placeholder="请选择" clearable style="width: 100%">
-                <el-option v-for="item in staffOptions" :key="item.staffId" :label="item.staffName" :value="item.staffId" />
+                <el-option v-for="item in staffOptions" :key="item.staffId" :label="(item.staffCode ? '(' + item.staffCode + ') ' : '') + item.staffName" :value="item.staffId" />
               </el-select>
             </el-form-item>
           </el-col>
           <el-col :span="6">
             <el-form-item label="客服支持" prop="serviceStaffId" class="custom-form-item">
               <el-select v-model="queryParams.serviceStaffId" placeholder="请选择" clearable style="width: 100%">
-                <el-option v-for="item in staffOptions" :key="item.staffId" :label="item.staffName" :value="item.staffId" />
+                <el-option v-for="item in staffOptions" :key="item.staffId" :label="(item.staffCode ? '(' + item.staffCode + ') ' : '') + item.staffName" :value="item.staffId" />
               </el-select>
             </el-form-item>
           </el-col>
@@ -134,12 +134,12 @@
       <el-form ref="claimRef" :model="claimForm" :rules="claimRules" label-width="140px">
         <el-form-item label="业务员:" prop="salespersonId">
           <el-select v-model="claimForm.salespersonId" placeholder="请选择" style="width: 100%">
-            <el-option v-for="item in staffOptions" :key="item.staffId" :label="item.staffName" :value="item.staffId" />
+            <el-option v-for="item in staffOptions" :key="item.staffId" :label="(item.staffCode ? '(' + item.staffCode + ') ' : '') + item.staffName" :value="item.staffId" />
           </el-select>
         </el-form-item>
         <el-form-item label="客服:" prop="serviceStaffId">
           <el-select v-model="claimForm.serviceStaffId" placeholder="请选择" style="width: 100%">
-            <el-option v-for="item in staffOptions" :key="item.staffId" :label="item.staffName" :value="item.staffId" />
+            <el-option v-for="item in staffOptions" :key="item.staffId" :label="(item.staffCode ? '(' + item.staffCode + ') ' : '') + item.staffName" :value="item.staffId" />
           </el-select>
         </el-form-item>
         <el-form-item label="保留已有负责人:" prop="keepOwner">
@@ -224,7 +224,7 @@ const claimRules = {
 const getOptions = async () => {
   try {
     const industryRes = await listIndustryCategory();
-    industryOptions.value = industryRes.data || [];
+    industryOptions.value = industryRes.data || industryRes.rows || [];
     
     const levelRes = await listLevel();
     levelOptions.value = levelRes.rows || [];

+ 12 - 7
src/views/customer/valid/detail.vue

@@ -36,7 +36,7 @@
 
             <div class="tab-pane-container">
               <div v-show="activeMainTab === 'profile'" class="profile-pane">
-                <div class="pane-header">
+                <div v-if="!isReadOnly" class="pane-header">
                   <el-button type="primary" icon="Edit" @click="handleEdit" class="edit-btn">编辑</el-button>
                 </div>
 
@@ -207,7 +207,7 @@
               </div>
 
               <div v-show="activeMainTab === 'contacts'" class="contacts-pane">
-                <div class="pane-header">
+                <div v-if="!isReadOnly" class="pane-header">
                   <el-button type="primary" icon="Plus" class="add-btn" @click="handleAddContact">新增</el-button>
                 </div>
                 <el-table :data="customerData?.customerContactVoList || []" border class="custom-tab-table">
@@ -218,8 +218,8 @@
                   <el-table-column label="办公座机" align="center" prop="officePhone" />
                   <el-table-column label="操作" align="center" width="120">
                     <template #default="scope">
-                      <el-button link type="primary" @click="handleEditContact(scope.row)">编辑</el-button>
-                      <el-button link type="danger" @click="handleDeleteContact(scope.row)">删除</el-button>
+                      <el-button v-if="!isReadOnly" link type="primary" @click="handleEditContact(scope.row)">编辑</el-button>
+                      <el-button v-if="!isReadOnly" link type="danger" @click="handleDeleteContact(scope.row)">删除</el-button>
                     </template>
                   </el-table-column>
                   <template #empty>暂无数据</template>
@@ -296,7 +296,7 @@
               </div>
 
               <div v-show="activeMainTab === 'care'" class="care-pane">
-                <div class="pane-header">
+                <div v-if="!isReadOnly" class="pane-header">
                   <el-button type="primary" icon="Plus" class="add-btn" @click="handleAddCare">新增</el-button>
                 </div>
                 <el-table :data="careList" border class="custom-tab-table">
@@ -325,7 +325,7 @@
               </div>
 
               <div v-show="activeMainTab === 'attachment'" class="attachment-pane">
-                <div class="pane-header">
+                <div v-if="!isReadOnly" class="pane-header">
                   <el-upload
                     :action="uploadUrl"
                     :headers="uploadHeaders"
@@ -343,7 +343,7 @@
                   <el-table-column label="操作" align="center" width="120">
                     <template #default="scope">
                       <el-button link type="primary" @click="handleDownload(scope.row)">下载</el-button>
-                      <el-button link type="danger" @click="handleDeleteFile(scope.row)">删除</el-button>
+                      <el-button v-if="!isReadOnly" link type="danger" @click="handleDeleteFile(scope.row)">删除</el-button>
                     </template>
                   </el-table-column>
                   <template #empty>暂无数据</template>
@@ -370,6 +370,7 @@
                 leaderName: customerData.customerSalesInfoVo?.salesPersonName || customerData.customerSalesInfoVo?.salesPerson || customerData.salesPersonName || customerData.leaderName || customerData.staffName || customerData.salesman || '',
                 staffName: customerData.customerSalesInfoVo?.salesPersonName || customerData.customerSalesInfoVo?.salesPerson || customerData.salesPersonName || customerData.leaderName || customerData.staffName || customerData.salesman || ''
               }"
+              @update-readonly="val => isReadOnly = val"
             />
           </div>
         </div>
@@ -725,6 +726,7 @@ import { getContactPerson, addContactPerson, updateContactPerson, delContactPers
 import { listProvinceWithCities } from "@/api/customer/addressArea";
 import { getToken } from "@/utils/auth";
 import { globalHeaders } from "@/utils/request";
+import { useUserStore } from "@/store/modules/user";
 import BusinessActivity from '../../common/businessActivity.vue';
 import CustomerEdit from "./edit.vue";
 import CareDetail from "../care/detail.vue";
@@ -740,6 +742,7 @@ const {
 } = toRefs(reactive(proxy.useDict("care_type", "opportunity_level", "annual_level", "project_type")));
 const route = useRoute();
 const router = useRouter();
+const userStore = useUserStore();
 const emit = defineEmits(['success']);
 
 const loading = ref(false);
@@ -872,6 +875,8 @@ const cityDisplay = computed(() => {
   return '';
 });
 
+const isReadOnly = ref(true);
+
 const loadData = async (id) => {
   if (!id) return;
   loading.value = true;

+ 73 - 40
src/views/customer/valid/edit.vue

@@ -35,12 +35,12 @@
             </el-col>
             <el-col :span="8">
               <el-form-item label="客户名称" prop="customerName">
-                <el-input v-model="form.customerName" placeholder="请输入客户名称" />
+                <el-input v-model="form.customerName" placeholder="请输入客户名称" maxlength="100" show-word-limit />
               </el-form-item>
             </el-col>
             <el-col :span="8">
               <el-form-item label="工商名称" prop="businessCustomerName">
-                <el-input v-model="form.businessCustomerName" placeholder="请输入工商名称" />
+                <el-input v-model="form.businessCustomerName" placeholder="请输入工商名称" maxlength="100" show-word-limit />
               </el-form-item>
             </el-col>
           </el-row>
@@ -73,7 +73,7 @@
             <el-col :span="8">
               <el-form-item label="客户类别" prop="customerTypeId">
                   <el-select v-model="form.customerTypeId" placeholder="请选择" class="w100">
-                    <el-option v-for="item in categoryOptions" :key="item.id" :label="item.customerTypeName || item.customerCategoryName || item.typeName || item.name" :value="item.id" />
+                    <el-option v-for="item in categoryOptions" :key="String(item.id)" :label="item.customerTypeName || item.customerCategoryName || item.typeName || item.name" :value="String(item.id)" />
                   </el-select>
               </el-form-item>
             </el-col>
@@ -96,30 +96,30 @@
           <el-row :gutter="20">
             <el-col :span="8">
               <el-form-item label="固定电话" prop="landline">
-                <el-input v-model="form.landline" placeholder="请输入固定电话" />
+                <el-input v-model="form.landline" placeholder="请输入固定电话" maxlength="20" />
               </el-form-item>
             </el-col>
             <el-col :span="8">
-              <el-form-item label="传真">
-                <el-input v-model="form.fax" placeholder="请输入传真" />
+              <el-form-item label="传真" prop="fax">
+                <el-input v-model="form.fax" placeholder="请输入传真" maxlength="20" />
               </el-form-item>
             </el-col>
             <el-col :span="8">
-              <el-form-item label="网址">
-                <el-input v-model="form.url" placeholder="请输入网址" />
+              <el-form-item label="网址" prop="url">
+                <el-input v-model="form.url" placeholder="请输入网址" maxlength="100" />
               </el-form-item>
             </el-col>
           </el-row>
 
           <el-row :gutter="20">
             <el-col :span="8">
-              <el-form-item label="邮政编码">
-                <el-input v-model="form.postCode" placeholder="请输入邮政编码" />
+              <el-form-item label="邮政编码" prop="postCode">
+                <el-input v-model="form.postCode" placeholder="请输入邮政编码" maxlength="6" />
               </el-form-item>
             </el-col>
             <el-col :span="8">
-              <el-form-item label="邮箱">
-                <el-input v-model="form.email" placeholder="请输入邮箱" />
+              <el-form-item label="邮箱" prop="email">
+                <el-input v-model="form.email" placeholder="请输入邮箱" maxlength="50" />
               </el-form-item>
             </el-col>
           </el-row>
@@ -137,7 +137,7 @@
                       clearable
                       @change="handleAreaChange"
                     />
-                    <el-input v-model="form.address" placeholder="请输入详细地址" style="flex: 1.5" />
+                    <el-input v-model="form.address" placeholder="请输入详细地址" style="flex: 1.5" maxlength="200" show-word-limit />
                  </div>
               </el-form-item>
             </el-col>
@@ -148,31 +148,31 @@
         <div class="form-section">
           <el-row :gutter="20">
             <el-col :span="8">
-              <el-form-item label="企业工商名称">
-                <el-input v-model="form.customerBusinessBo.businessCustomerName" />
+              <el-form-item label="企业工商名称" prop="customerBusinessBo.businessCustomerName">
+                <el-input v-model="form.customerBusinessBo.businessCustomerName" maxlength="100" show-word-limit />
               </el-form-item>
             </el-col>
             <el-col :span="8">
-              <el-form-item label="社会信用代码">
-                <el-input v-model="form.customerBusinessBo.socialCreditCode" />
+              <el-form-item label="社会信用代码" prop="customerBusinessBo.socialCreditCode">
+                <el-input v-model="form.customerBusinessBo.socialCreditCode" maxlength="18" />
               </el-form-item>
             </el-col>
             <el-col :span="8">
-              <el-form-item label="法人姓名">
-                <el-input v-model="form.customerBusinessBo.legalPersonName" />
+              <el-form-item label="法人姓名" prop="customerBusinessBo.legalPersonName">
+                <el-input v-model="form.customerBusinessBo.legalPersonName" maxlength="50" />
               </el-form-item>
             </el-col>
           </el-row>
 
           <el-row :gutter="20">
             <el-col :span="8">
-              <el-form-item label="注册资本">
-                <el-input v-model="form.customerBusinessBo.registeredCapital" />
+              <el-form-item label="注册资本" prop="customerBusinessBo.registeredCapital">
+                <el-input v-model="form.customerBusinessBo.registeredCapital" maxlength="50" />
               </el-form-item>
             </el-col>
             <el-col :span="8">
-              <el-form-item label="登记机关">
-                <el-input v-model="form.customerBusinessBo.registrationAuthority" />
+              <el-form-item label="登记机关" prop="customerBusinessBo.registrationAuthority">
+                <el-input v-model="form.customerBusinessBo.registrationAuthority" maxlength="100" />
               </el-form-item>
             </el-col>
             <el-col :span="8">
@@ -189,21 +189,21 @@
               </el-form-item>
             </el-col>
             <el-col :span="8">
-              <el-form-item label="登记状态">
-                <el-input v-model="form.customerBusinessBo.registrationStatus" />
+              <el-form-item label="登记状态" prop="customerBusinessBo.registrationStatus">
+                <el-input v-model="form.customerBusinessBo.registrationStatus" maxlength="50" />
               </el-form-item>
             </el-col>
             <el-col :span="8">
-              <el-form-item label="实缴资本">
-                <el-input v-model="form.customerBusinessBo.paidInCapital" />
+              <el-form-item label="实缴资本" prop="customerBusinessBo.paidInCapital">
+                <el-input v-model="form.customerBusinessBo.paidInCapital" maxlength="50" />
               </el-form-item>
             </el-col>
           </el-row>
 
           <el-row :gutter="20">
             <el-col :span="24">
-              <el-form-item label="详细地址">
-                <el-input v-model="form.customerBusinessBo.businessAddress" placeholder="请输入详细地址" />
+              <el-form-item label="详细地址" prop="customerBusinessBo.businessAddress">
+                <el-input v-model="form.customerBusinessBo.businessAddress" placeholder="请输入详细地址" maxlength="200" show-word-limit />
               </el-form-item>
             </el-col>
           </el-row>
@@ -233,14 +233,14 @@
             <el-col :span="8">
               <el-form-item label="负责人">
                  <el-select v-model="form.salesPersonId" placeholder="请选择" class="w100">
-                   <el-option v-for="item in staffOptions" :key="item.staffId" :label="item.staffName" :value="item.staffId" />
+                   <el-option v-for="item in staffOptions" :key="item.staffId" :label="(item.staffCode ? '(' + item.staffCode + ') ' : '') + item.staffName" :value="item.staffId" />
                  </el-select>
               </el-form-item>
             </el-col>
             <el-col :span="8">
               <el-form-item label="客服支持">
                  <el-select v-model="form.serviceStaffId" placeholder="请选择" class="w100">
-                   <el-option v-for="item in staffOptions" :key="item.staffId" :label="item.staffName" :value="item.staffId" />
+                   <el-option v-for="item in staffOptions" :key="item.staffId" :label="(item.staffCode ? '(' + item.staffCode + ') ' : '') + item.staffName" :value="item.staffId" />
                  </el-select>
               </el-form-item>
             </el-col>
@@ -280,7 +280,7 @@ import { listProvinceWithCities } from "@/api/customer/addressArea";
 import { deptTreeSelect } from "@/api/system/dept";
 
 const proxy = getCurrentInstance().proxy;
-const { customer_source: sourceOptions, Q0001: corpTypeOptions } = toRefs(reactive(proxy.useDict("customer_source", "Q0001")));
+const { K0001: sourceOptions, Q0001: corpTypeOptions } = toRefs(reactive(proxy.useDict("K0001", "Q0001")));
 const route = useRoute();
 const router = useRouter();
 
@@ -328,16 +328,47 @@ const form = reactive({
 
 const rules = reactive({
   belongCompanyId: [{ required: true, message: "所属公司不能为空", trigger: "change" }],
-  customerName: [{ required: true, message: "客户名称不能为空", trigger: "blur" }],
-  businessCustomerName: [{ required: true, message: "工商名称不能为空", trigger: "blur" }],
+  customerName: [
+    { required: true, message: "客户名称不能为空", trigger: "blur" },
+    { max: 100, message: "客户名称不能超过100个字符", trigger: "blur" }
+  ],
+  businessCustomerName: [
+    { required: true, message: "工商名称不能为空", trigger: "blur" },
+    { max: 100, message: "工商名称不能超过100个字符", trigger: "blur" }
+  ],
   customerSourceId: [{ required: true, message: "客户来源不能为空", trigger: "change" }],
   enterpriseTypeId: [{ required: true, message: "企业类型不能为空", trigger: "change" }],
   enterpriseScaleId: [{ required: true, message: "企业规模不能为空", trigger: "change" }],
   customerTypeId: [{ required: true, message: "客户类别不能为空", trigger: "change" }],
   industryCategoryId: [{ required: true, message: "所属行业不能为空", trigger: "change" }],
   customerLevelId: [{ required: true, message: "客户等级不能为空", trigger: "change" }],
-  landline: [{ required: true, message: "固定电话不能为空", trigger: "blur" }],
-  areaArray: [{ required: true, message: "详细地区不能为空", trigger: "change", type: 'array' }]
+  landline: [
+    { required: true, message: "固定电话不能为空", trigger: "blur" },
+    { pattern: /^([0-9-]{7,20})$/, message: "请输入正确的电话号码", trigger: "blur" }
+  ],
+  fax: [
+    { pattern: /^([0-9-]{7,20})$/, message: "请输入正确的传真号码", trigger: "blur" }
+  ],
+  url: [
+    { pattern: /^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([\/\w .-]*)*\/?$/, message: "请输入正确的网址", trigger: "blur" }
+  ],
+  postCode: [
+    { pattern: /^\d{6}$/, message: "请输入正确的6位邮政编码", trigger: "blur" }
+  ],
+  email: [
+    { pattern: /^([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})$/, message: "请输入正确的邮箱地址", trigger: "blur" }
+  ],
+  areaArray: [{ required: true, message: "详细地区不能为空", trigger: "change", type: 'array' }],
+  'customerBusinessBo.businessCustomerName': [{ max: 100, message: "企业工商名称不能超过100个字符", trigger: "blur" }],
+  'customerBusinessBo.socialCreditCode': [
+    { pattern: /^[A-Za-z0-9]{18}$/, message: "请输入正确的18位统一社会信用代码", trigger: "blur" }
+  ],
+  'customerBusinessBo.legalPersonName': [{ max: 50, message: "法人姓名不能超过50个字符", trigger: "blur" }],
+  'customerBusinessBo.registeredCapital': [{ max: 50, message: "注册资本不能超过50个字符", trigger: "blur" }],
+  'customerBusinessBo.registrationAuthority': [{ max: 100, message: "登记机关不能超过100个字符", trigger: "blur" }],
+  'customerBusinessBo.registrationStatus': [{ max: 50, message: "登记状态不能超过50个字符", trigger: "blur" }],
+  'customerBusinessBo.paidInCapital': [{ max: 50, message: "实缴资本不能超过50个字符", trigger: "blur" }],
+  'customerBusinessBo.businessAddress': [{ max: 200, message: "详细地址不能超过200个字符", trigger: "blur" }]
 });
 
 const companyOptions = ref([]);
@@ -351,10 +382,10 @@ const deptOptions = ref([]);
 
 const initOptions = async () => {
   listCompanyOption().then(res => companyOptions.value = res.data || []).catch(e => console.error("公司选项接口404或出错", e));
-  listIndustryCategory().then(res => industryOptions.value = res.data || []).catch(e => console.error("行业类别接口404或出错", e));
+  listIndustryCategory().then(res => industryOptions.value = res.data || res.rows || []).catch(e => console.error("行业类别接口404或出错", e));
   listLevel().then(res => levelOptions.value = res.rows || []).catch(e => console.error("客户等级接口404或出错", e));
   listComStaff({ pageSize: 1000 }).then(res => staffOptions.value = res.rows || res.data || []).catch(e => console.error("员工选项接口404或出错", e));
-  listEnterpriseScale().then(res => scaleOptions.value = res.data || []).catch(e => console.error("企业规模接口404或出错", e));
+  listEnterpriseScale().then(res => scaleOptions.value = res.data || res.rows || []).catch(e => console.error("企业规模接口404或出错", e));
   listCustomerTypeOption().then(res => {
     categoryOptions.value = res.data || res.rows || [];
   }).catch(e => {
@@ -409,7 +440,7 @@ const loadData = (id) => {
 
     // 显式处理字典项回显和类型转换
     const sourceVal = data.customerSourceId || sales.customerSource || sales.customerSourceId;
-    form.customerSourceId = sourceVal ? String(sourceVal) : undefined;
+    form.customerSourceId = sourceVal ? String(sourceVal).split(',')[0] : undefined;
     
     const typeVal = data.enterpriseTypeId || data.enterpriseType;
     form.enterpriseTypeId = typeVal ? String(typeVal) : undefined;
@@ -417,6 +448,8 @@ const loadData = (id) => {
     const scaleVal = data.enterpriseScaleId || data.enterpriseScale || data.scaleId;
     form.enterpriseScaleId = scaleVal ? String(scaleVal) : undefined;
 
+    form.customerTypeId = data.customerTypeId ? String(data.customerTypeId) : undefined;
+
     const industryVal = data.industryCategoryId || data.industryId;
     form.industryCategoryId = industryVal ? Number(industryVal) : undefined;
 
@@ -425,7 +458,7 @@ const loadData = (id) => {
     
     // 地区回显处理
     if (data.regProvincialNo && data.regCityNo && data.regCountyNo) {
-      form.areaArray = [String(data.regProvincialNo), String(data.regCityNo), String(data.regCountyNo)];
+      form.areaArray = [Number(data.regProvincialNo), Number(data.regCityNo), Number(data.regCountyNo)];
     } else {
       form.areaArray = [];
     }

+ 15 - 6
src/views/customer/valid/index.vue

@@ -42,14 +42,14 @@
           <el-col :span="6">
             <el-form-item label="业务负责人" prop="salesPersonId" class="custom-form-item">
               <el-select v-model="queryParams.salesPersonId" placeholder="请选择" clearable style="width: 100%">
-                <el-option v-for="item in staffOptions" :key="item.staffId" :label="item.staffName" :value="item.staffId" />
+                <el-option v-for="item in staffOptions" :key="item.staffId" :label="(item.staffCode ? '(' + item.staffCode + ') ' : '') + item.staffName" :value="item.staffId" />
               </el-select>
             </el-form-item>
           </el-col>
           <el-col :span="6">
             <el-form-item label="客服支持" prop="serviceStaffId" class="custom-form-item">
               <el-select v-model="queryParams.serviceStaffId" placeholder="请选择" clearable style="width: 100%">
-                <el-option v-for="item in staffOptions" :key="item.staffId" :label="item.staffName" :value="item.staffId" />
+                <el-option v-for="item in staffOptions" :key="item.staffId" :label="(item.staffCode ? '(' + item.staffCode + ') ' : '') + item.staffName" :value="item.staffId" />
               </el-select>
             </el-form-item>
           </el-col>
@@ -162,7 +162,7 @@
             <span style="color: #f56c6c; margin-right: 4px;">*</span>选择人员:
           </template>
           <el-select v-model="transferForm.staffId" placeholder="请选择" filterable style="width: 100%">
-            <el-option v-for="item in staffOptions" :key="item.staffId" :label="item.staffName" :value="item.staffId" />
+            <el-option v-for="item in staffOptions" :key="item.staffId" :label="(item.staffCode ? '(' + item.staffCode + ') ' : '') + item.staffName" :value="item.staffId" />
           </el-select>
         </el-form-item>
       </el-form>
@@ -185,7 +185,7 @@
 <script setup name="CustomerValid">
 import { ref, reactive, onMounted, getCurrentInstance, toRefs } from 'vue';
 import { useRouter } from 'vue-router';
-import { listValidCustomer, releaseToPool, listCompanyOption, transferSalesPerson, transferServiceStaff } from "@/api/customer/customerPool";
+import { listMineCustomer, listInvolvedCustomer, listAllCustomer, releaseToPool, listCompanyOption, transferSalesPerson, transferServiceStaff } from "@/api/customer/customerPool";
 import { listIndustryCategory } from "@/api/customer/industryCategory";
 import { listLevel } from "@/api/customer/customerLevel";
 import { listComStaff } from "@/api/system/comStaff/index";
@@ -246,7 +246,16 @@ const queryParams = reactive({
 
 const getList = () => {
   loading.value = true;
-  listValidCustomer(queryParams).then(res => {
+  let req = null;
+  if (activeTab.value === 'mine') {
+    req = listMineCustomer(queryParams);
+  } else if (activeTab.value === 'involved') {
+    req = listInvolvedCustomer(queryParams);
+  } else {
+    req = listAllCustomer(queryParams);
+  }
+  
+  req.then(res => {
     dataList.value = res.rows || [];
     total.value = res.total || 0;
     loading.value = false;
@@ -349,7 +358,7 @@ const handleDetail = debounce((row) => {
 
 const initOptions = async () => {
   listCompanyOption().then(res => companyOptions.value = res.data || []);
-  listIndustryCategory().then(res => industryOptions.value = res.data || []);
+  listIndustryCategory().then(res => industryOptions.value = res.data || res.rows || []);
   listLevel().then(res => levelOptions.value = res.rows || []);
   listComStaff({ pageSize: 1000 }).then(res => staffOptions.value = res.rows || res.data || []);
   deptTreeSelect().then(res => deptOptions.value = proxy.handleTree(res.data, "deptId"));

+ 1 - 1
src/views/saleManage/accountsReceivable/index.vue

@@ -154,7 +154,7 @@
             <el-form-item label="联系人:" prop="contactPerson">
               <el-select v-model="form.contactPerson" placeholder="请选择联系人" style="width: 100%" filterable>
                 <el-option v-for="item in userOptions" :key="item.staffId || item.userId"
-                  :label="item.staffName || item.nickName" :value="item.staffName || item.nickName" />
+                  :label="(item.staffCode || item.userName ? '(' + (item.staffCode || item.userName) + ') ' : '') + (item.staffName || item.nickName)" :value="item.staffName || item.nickName" />
               </el-select>
             </el-form-item>
           </el-col>

+ 31 - 13
src/views/saleManage/leads/add.vue

@@ -60,7 +60,7 @@
                   <el-option
                     v-for="item in userOptions"
                     :key="item.staffId"
-                    :label="item.staffName"
+                    :label="(item.staffCode ? '(' + item.staffCode + ') ' : '') + item.staffName"
                     :value="item.staffId"
                   />
                 </el-select>
@@ -68,7 +68,7 @@
             </el-col>
             <el-col :span="12">
               <el-form-item label="项目名称" prop="projectName">
-                <el-input v-model="form.projectName" placeholder="请输入" />
+                <el-input v-model="form.projectName" placeholder="请输入" maxlength="200" show-word-limit />
               </el-form-item>
             </el-col>
           </el-row>
@@ -88,7 +88,7 @@
             </el-col>
             <el-col :span="12">
               <el-form-item label="赢单率(%)" prop="winRate">
-                <el-input v-model="form.winRate" placeholder="请输入" type="number">
+                <el-input v-model="form.winRate" placeholder="请输入" type="number" maxlength="6">
                    <template #append>%</template>
                 </el-input>
               </el-form-item>
@@ -120,7 +120,7 @@
                   <el-option
                     v-for="item in userOptions"
                     :key="item.staffId"
-                    :label="item.staffName"
+                    :label="(item.staffCode ? '(' + item.staffCode + ') ' : '') + item.staffName"
                     :value="item.staffId"
                   />
                 </el-select>
@@ -142,7 +142,7 @@
             </el-col>
             <el-col :span="12">
               <el-form-item label="项目区域" prop="projectArea">
-                <el-input v-model="form.projectArea" placeholder="请输入" />
+                <el-input v-model="form.projectArea" placeholder="请输入" maxlength="100" />
               </el-form-item>
             </el-col>
           </el-row>
@@ -165,21 +165,21 @@
           <el-row :gutter="30">
             <el-col :span="24">
               <el-form-item label="采购内容" prop="purchaseContent">
-                <el-input v-model="form.purchaseContent" type="textarea" :rows="3" maxlength="200" show-word-limit placeholder="请输入采购内容" />
+                <el-input v-model="form.purchaseContent" type="textarea" :rows="3" maxlength="500" show-word-limit placeholder="请输入采购内容" />
               </el-form-item>
             </el-col>
           </el-row>
           <el-row :gutter="30">
             <el-col :span="24">
               <el-form-item label="项目描述" prop="projectDescription">
-                <el-input v-model="form.projectDescription" type="textarea" :rows="3" maxlength="200" show-word-limit placeholder="请输入项目描述" />
+                <el-input v-model="form.projectDescription" type="textarea" :rows="3" maxlength="500" show-word-limit placeholder="请输入项目描述" />
               </el-form-item>
             </el-col>
           </el-row>
           <el-row :gutter="30">
             <el-col :span="24">
               <el-form-item label="竞争对手" prop="competitor">
-                <el-input v-model="form.competitor" type="textarea" :rows="3" maxlength="200" show-word-limit placeholder="请输入竞争对手" />
+                <el-input v-model="form.competitor" type="textarea" :rows="3" maxlength="500" show-word-limit placeholder="请输入竞争对手" />
               </el-form-item>
             </el-col>
           </el-row>
@@ -269,16 +269,34 @@ const rules = reactive({
   companyNo: [{ required: true, message: '归属公司不能为空', trigger: 'change' }],
   customerNo: [{ required: true, message: '客户名称不能为空', trigger: 'change' }],
   leader: [{ required: true, message: '项目负责人不能为空', trigger: 'change' }],
-  projectName: [{ required: true, message: '项目名称不能为空', trigger: 'blur' }],
+  projectName: [
+    { required: true, message: '项目名称不能为空', trigger: 'blur' },
+    { max: 200, message: '项目名称不能超过200个字符', trigger: 'blur' }
+  ],
   projectBudget: [{ required: true, message: '金额不能为空', trigger: 'blur' }],
-  winRate: [{ required: true, message: '赢单率不能为空', trigger: 'blur' }],
+  winRate: [
+    { required: true, message: '赢单率不能为空', trigger: 'blur' },
+    { pattern: /^(100|[1-9]?\d(\.\d+)?)$/, message: '请输入正确的赢单率(0-100)', trigger: 'blur' }
+  ],
   expectedCompletionTime: [{ required: true, message: '成单时间不能为空', trigger: 'change' }],
   projectLevel: [{ required: true, message: '项目级别不能为空', trigger: 'change' }],
-  projectArea: [{ required: true, message: '项目区域不能为空', trigger: 'blur' }],
+  projectArea: [
+    { required: true, message: '项目区域不能为空', trigger: 'blur' },
+    { max: 100, message: '项目区域不能超过100个字符', trigger: 'blur' }
+  ],
   procurementMethod: [{ required: true, message: '采购方式不能为空', trigger: 'change' }],
   infoSource: [{ required: true, message: '信息来源不能为空', trigger: 'change' }],
-  purchaseContent: [{ required: true, message: '采购内容不能为空', trigger: 'blur' }],
-  projectDescription: [{ required: true, message: '项目描述不能为空', trigger: 'blur' }],
+  purchaseContent: [
+    { required: true, message: '采购内容不能为空', trigger: 'blur' },
+    { max: 500, message: '采购内容不能超过500个字符', trigger: 'blur' }
+  ],
+  projectDescription: [
+    { required: true, message: '项目描述不能为空', trigger: 'blur' },
+    { max: 500, message: '项目描述不能超过500个字符', trigger: 'blur' }
+  ],
+  competitor: [
+    { max: 500, message: '竞争对手不能超过500个字符', trigger: 'blur' }
+  ]
 });
 
 const remoteLoadCustomers = (query) => {

+ 12 - 10
src/views/saleManage/leads/detail.vue

@@ -32,7 +32,7 @@
             <div class="info-section">
               <div class="section-header">
                 <span class="section-label">基本信息</span>
-                <el-button type="primary" size="small" @click="handleEdit"><el-icon style="margin-right:4px;vertical-align:-2px;"><Edit /></el-icon>编 辑</el-button>
+                <el-button v-if="!isReadOnly" type="primary" size="small" @click="handleEdit"><el-icon style="margin-right:4px;vertical-align:-2px;"><Edit /></el-icon>编 辑</el-button>
               </div>
               <div class="info-row-item">
                 <span class="info-label">项目名称</span>
@@ -55,7 +55,7 @@
               </div>
               <div class="info-grid">
                 <div class="info-item"><span class="info-label">成单时间</span><span class="info-value">{{ parseTime(form.expectedCompletionTime, '{y}-{m}-{d}') }}</span></div>
-                <div class="info-item"><span class="info-label">项目状态</span><span class="info-value"><dict-tag :options="saleStatusOptions" :value="form.status" /></span></div>
+                <div class="info-item"><span class="info-label">项目状态</span><span class="info-value"><el-tag :type="form.status === '1' ? 'success' : 'primary'">{{ form.statusName }}</el-tag></span></div>
                 <div class="info-item"></div>
               </div>
               <div class="info-grid">
@@ -87,7 +87,7 @@
           <div v-show="activeTab === 'contact'" class="tab-content">
             <div class="contact-section">
               <div class="contact-header">
-                <el-dropdown trigger="hover" @command="handleContactCommand">
+                <el-dropdown v-if="!isReadOnly" trigger="hover" @command="handleContactCommand">
                   <el-button type="primary" size="small" icon="Plus">
                     新建联系人 <el-icon class="el-icon--right"><arrow-down /></el-icon>
                   </el-button>
@@ -135,8 +135,8 @@
                 </el-table-column>
                 <el-table-column label="操作" align="center" width="140" fixed="right">
                   <template #default="scope">
-                    <el-button link type="primary" size="small" @click="handleEditContact(scope.row)">编辑</el-button>
-                    <el-button link type="danger" size="small" @click="handleDeleteContact(scope.row)">删除</el-button>
+                    <el-button v-if="!isReadOnly" link type="primary" size="small" @click="handleEditContact(scope.row)">编辑</el-button>
+                    <el-button v-if="!isReadOnly" link type="danger" size="small" @click="handleDeleteContact(scope.row)">删除</el-button>
                   </template>
                 </el-table-column>
               </el-table>
@@ -154,7 +154,7 @@
                     <el-radio value="lose">丢单</el-radio>
                   </el-radio-group>
                   <div style="flex:1"></div>
-                  <el-button type="primary" icon="CircleCheck" @click="handleSaveAnalysis">保 存</el-button>
+                  <el-button v-if="!isReadOnly" type="primary" icon="CircleCheck" @click="handleSaveAnalysis">保 存</el-button>
                 </div>
                 <template v-if="analysisForm.resultType === 'win'">
                   <div class="analysis-row"><span class="form-label">赢单总结</span></div>
@@ -169,7 +169,7 @@
                   </div>
                 </template>
                 <div class="analysis-row" style="margin-top: 20px;"><span class="form-label">附件</span></div>
-                <div class="upload-area">
+                <div v-if="!isReadOnly" class="upload-area">
                   <el-upload
                     :action="uploadFileUrl"
                     :headers="headers"
@@ -186,7 +186,7 @@
                     <el-table-column label="操作" width="120" align="center">
                        <template #default="scope">
                           <el-button link type="primary" @click="downloadFile(scope.row)">下载</el-button>
-                          <el-button link type="danger" @click="handleAnalysisDeleteFile(scope.$index)">删除</el-button>
+                          <el-button v-if="!isReadOnly" link type="danger" @click="handleAnalysisDeleteFile(scope.$index)">删除</el-button>
                        </template>
                     </el-table-column>
                   </el-table>
@@ -198,7 +198,7 @@
           <!-- 附件 Tab -->
           <div v-show="activeTab === 'attachment'" class="tab-content">
             <div class="attachment-section">
-              <div style="margin-bottom: 12px; display: flex; justify-content: flex-end;">
+              <div v-if="!isReadOnly" style="margin-bottom: 12px; display: flex; justify-content: flex-end;">
                 <el-upload
                   :action="uploadFileUrl"
                   :headers="headers"
@@ -216,7 +216,7 @@
                 <el-table-column label="操作" width="150" align="center">
                   <template #default="scope">
                     <el-link type="primary" :underline="false" @click="handleDownloadFile(scope.row)" style="margin-right: 10px;">下载</el-link>
-                    <el-link type="danger" :underline="false" @click="handleDeleteFile(scope.row)">删除</el-link>
+                    <el-link v-if="!isReadOnly" type="danger" :underline="false" @click="handleDeleteFile(scope.row)">删除</el-link>
                   </template>
                 </el-table-column>
               </el-table>
@@ -237,6 +237,7 @@
               profession: findIndustryName(form.profession),
               customerName: form.customerName
             }"
+            @update-readonly="val => isReadOnly = val"
           />
         </div>
       </div>
@@ -589,6 +590,7 @@ const props = defineProps({
 const emit = defineEmits(['update:modelValue', 'edit']);
 
 const visible = ref(props.modelValue);
+const isReadOnly = ref(true);
 const loading = ref(false);
 const activeTab = ref('info');
 const form = ref({});

+ 29 - 9
src/views/saleManage/leads/edit.vue

@@ -43,13 +43,13 @@
             <el-col :span="12">
               <el-form-item label="项目负责人" prop="leader">
                 <el-select v-model="form.leader" placeholder="请选择" filterable style="width: 100%" @change="handleLeaderChange">
-                  <el-option v-for="item in computedUserOptions" :key="item.staffId" :label="item.staffName" :value="String(item.staffId)" />
+                  <el-option v-for="item in computedUserOptions" :key="item.staffId" :label="(item.staffCode ? '(' + item.staffCode + ') ' : '') + item.staffName" :value="String(item.staffId)" />
                 </el-select>
               </el-form-item>
             </el-col>
             <el-col :span="12">
               <el-form-item label="项目名称" prop="projectName">
-                <el-input v-model="form.projectName" placeholder="请输入" />
+                <el-input v-model="form.projectName" placeholder="请输入" maxlength="200" show-word-limit />
               </el-form-item>
             </el-col>
           </el-row>
@@ -69,7 +69,7 @@
             </el-col>
             <el-col :span="12">
               <el-form-item label="赢单率(%)" prop="winRate">
-                <el-input v-model="form.winRate" placeholder="请输入" type="number"><template #append>%</template></el-input>
+                <el-input v-model="form.winRate" placeholder="请输入" type="number" maxlength="6"><template #append>%</template></el-input>
               </el-form-item>
             </el-col>
           </el-row>
@@ -105,7 +105,7 @@
             <el-col :span="12">
               <el-form-item label="产品支持" prop="productSupport">
                 <el-select v-model="form.productSupport" placeholder="请选择" filterable style="width: 100%" clearable @change="handleProductSupportChange">
-                  <el-option v-for="item in computedProductSupportOptions" :key="item.staffId" :label="item.staffName" :value="String(item.staffId)" />
+                  <el-option v-for="item in computedProductSupportOptions" :key="item.staffId" :label="(item.staffCode ? '(' + item.staffCode + ') ' : '') + item.staffName" :value="String(item.staffId)" />
                 </el-select>
               </el-form-item>
             </el-col>
@@ -123,7 +123,9 @@
               </el-form-item>
             </el-col>
             <el-col :span="12">
-              <el-form-item label="项目区域" prop="projectArea"><el-input v-model="form.projectArea" /></el-form-item>
+              <el-form-item label="项目区域" prop="projectArea">
+                <el-input v-model="form.projectArea" placeholder="请输入" maxlength="100" />
+              </el-form-item>
             </el-col>
           </el-row>
           <el-row :gutter="30">
@@ -145,21 +147,21 @@
           <el-row :gutter="30">
             <el-col :span="24">
               <el-form-item label="采购内容" prop="purchaseContent">
-                <el-input v-model="form.purchaseContent" type="textarea" :rows="3" maxlength="200" show-word-limit placeholder="请输入采购内容" />
+                <el-input v-model="form.purchaseContent" type="textarea" :rows="3" maxlength="500" show-word-limit placeholder="请输入采购内容" />
               </el-form-item>
             </el-col>
           </el-row>
           <el-row :gutter="30">
             <el-col :span="24">
               <el-form-item label="项目描述" prop="projectDescription">
-                <el-input v-model="form.projectDescription" type="textarea" :rows="3" maxlength="200" show-word-limit placeholder="请输入项目描述" />
+                <el-input v-model="form.projectDescription" type="textarea" :rows="3" maxlength="500" show-word-limit placeholder="请输入项目描述" />
               </el-form-item>
             </el-col>
           </el-row>
           <el-row :gutter="30">
             <el-col :span="24">
               <el-form-item label="竞争对手" prop="competitor">
-                <el-input v-model="form.competitor" type="textarea" :rows="3" maxlength="200" show-word-limit placeholder="请输入竞争对手" />
+                <el-input v-model="form.competitor" type="textarea" :rows="3" maxlength="500" show-word-limit placeholder="请输入竞争对手" />
               </el-form-item>
             </el-col>
           </el-row>
@@ -282,7 +284,25 @@ const rules = reactive({
   companyNo: [{ required: true, message: '必填', trigger: 'change' }],
   customerNo: [{ required: true, message: '必填', trigger: 'change' }],
   leader: [{ required: true, message: '必填', trigger: 'change' }],
-  projectName: [{ required: true, message: '必填', trigger: 'blur' }],
+  projectName: [
+    { required: true, message: '必填', trigger: 'blur' },
+    { max: 200, message: '项目名称不能超过200个字符', trigger: 'blur' }
+  ],
+  winRate: [
+    { pattern: /^(100|[1-9]?\d(\.\d+)?)$/, message: '请输入正确的赢单率(0-100)', trigger: 'blur' }
+  ],
+  projectArea: [
+    { max: 100, message: '项目区域不能超过100个字符', trigger: 'blur' }
+  ],
+  purchaseContent: [
+    { max: 500, message: '采购内容不能超过500个字符', trigger: 'blur' }
+  ],
+  projectDescription: [
+    { max: 500, message: '项目描述不能超过500个字符', trigger: 'blur' }
+  ],
+  competitor: [
+    { max: 500, message: '竞争对手不能超过500个字符', trigger: 'blur' }
+  ]
 });
 
 watch(() => props.modelValue, (val) => { 

+ 10 - 8
src/views/saleManage/leads/index.vue

@@ -31,7 +31,7 @@
           <el-col :span="6">
             <el-form-item label="负责人" prop="leader">
               <el-select v-model="queryParams.leader" placeholder="请选择" style="width: 100%" clearable>
-                <el-option v-for="item in userOptions" :key="item.staffId" :label="item.staffName" :value="item.staffId" />
+                <el-option v-for="item in userOptions" :key="item.staffId" :label="(item.staffCode ? '(' + item.staffCode + ') ' : '') + item.staffName" :value="item.staffId" />
               </el-select>
             </el-form-item>
           </el-col>
@@ -55,7 +55,7 @@
           <el-col :span="6">
             <el-form-item label="产品支持" prop="productSupport">
               <el-select v-model="queryParams.productSupport" placeholder="请选择" style="width: 100%" clearable>
-                <el-option v-for="item in userOptions" :key="item.staffId" :label="item.staffName" :value="item.staffId" />
+                <el-option v-for="item in userOptions" :key="item.staffId" :label="(item.staffCode ? '(' + item.staffCode + ') ' : '') + item.staffName" :value="item.staffId" />
               </el-select>
             </el-form-item>
           </el-col>
@@ -193,9 +193,11 @@
         </el-table-column>
 
         <!-- 15. 项目状态 -->
-        <el-table-column label="项目状态" align="center" prop="status" width="100">
+        <el-table-column label="项目状态" align="center" prop="statusName" width="100">
           <template #default="scope">
-            <dict-tag :options="saleStatusOptions" :value="scope.row.status" :show-value="false" />
+            <el-tag :type="scope.row.status === '1' ? 'success' : 'primary'">
+              {{ scope.row.statusName }}
+            </el-tag>
           </template>
         </el-table-column>
         <el-table-column label="操作" align="center" width="180" fixed="right">
@@ -254,7 +256,7 @@
       <el-form :model="transferForm" label-width="100px" style="padding: 10px 20px 0;">
         <el-form-item label="新负责人" required>
           <el-select v-model="transferForm.newOwner" placeholder="请选择" style="width:100%" filterable>
-            <el-option v-for="u in userOptions" :key="u.staffId" :label="u.staffName" :value="u.staffId" />
+            <el-option v-for="u in userOptions" :key="u.staffId" :label="(u.staffCode ? '(' + u.staffCode + ') ' : '') + u.staffName" :value="u.staffId" />
           </el-select>
         </el-form-item>
         <el-form-item label-width="100px">
@@ -274,13 +276,13 @@
       <el-form :model="claimForm" label-width="150px">
         <el-form-item label="项目负责人" required>
           <el-select v-model="claimForm.leader" filterable placeholder="请选择" style="width: 100%">
-            <el-option v-for="item in computedClaimUserOptions" :key="item.staffId" :label="item.staffName" :value="item.staffId" />
+            <el-option v-for="item in computedClaimUserOptions" :key="item.staffId" :label="(item.staffCode ? '(' + item.staffCode + ') ' : '') + item.staffName" :value="item.staffId" />
           </el-select>
         </el-form-item>
 
         <el-form-item label="产品支持" required>
           <el-select v-model="claimForm.productSupport" filterable placeholder="请选择" style="width: 100%">
-            <el-option v-for="item in userOptions" :key="item.staffId" :label="item.staffName" :value="item.staffId" />
+            <el-option v-for="item in userOptions" :key="item.staffId" :label="(item.staffCode ? '(' + item.staffCode + ') ' : '') + item.staffName" :value="item.staffId" />
           </el-select>
         </el-form-item>
 
@@ -425,7 +427,7 @@ const getDicts = () => {
     const list = res.rows || res.data || [];
     userOptions.value = list.map(i => ({ ...i, staffId: String(i.staffId) }));
   });
-  listIndustryCategory().then(res => industryOptions.value = res.data);
+  listIndustryCategory().then(res => industryOptions.value = res.data || res.rows || []);
   deptTreeSelect().then(res => deptOptions.value = res.data);
 };
 

+ 36 - 16
src/views/saleManage/opportunity/add.vue

@@ -49,15 +49,15 @@
             <el-row :gutter="24">
               <el-col :span="12">
                 <el-form-item label="项目负责人" prop="managerId">
-                  <el-select v-model="form.managerId" placeholder="请选择" style="width: 100%" clearable filterable @change="handleLeaderChange">
-                    <el-option v-for="item in userOptions" :key="item.staffId || item.userId"
-                      :label="item.staffName || item.nickName" :value="String(item.staffId || item.userId)" />
-                  </el-select>
+                    <el-select v-model="form.managerId" placeholder="请选择" style="width: 100%" clearable filterable @change="handleLeaderChange">
+                      <el-option v-for="item in userOptions" :key="item.staffId || item.userId"
+                        :label="(item.staffCode || item.userName ? '(' + (item.staffCode || item.userName) + ') ' : '') + (item.staffName || item.nickName)" :value="String(item.staffId || item.userId)" />
+                    </el-select>
                 </el-form-item>
               </el-col>
               <el-col :span="12">
                 <el-form-item label="项目名称" prop="projectName">
-                  <el-input v-model="form.projectName" placeholder="请输入" />
+                  <el-input v-model="form.projectName" placeholder="请输入" maxlength="200" show-word-limit />
                 </el-form-item>
               </el-col>
             </el-row>
@@ -69,7 +69,7 @@
               </el-col>
               <el-col :span="12">
                 <el-form-item label="赢单率(%)" prop="winRate">
-                  <el-input v-model="form.winRate" placeholder="请输入" type="number">
+                  <el-input v-model="form.winRate" placeholder="请输入" type="number" maxlength="6">
                     <template #append>%</template>
                   </el-input>
                 </el-form-item>
@@ -112,7 +112,7 @@
               </el-col>
               <el-col :span="12">
                 <el-form-item label="项目区域" prop="projectArea">
-                  <el-input v-model="form.projectArea" placeholder="请输入" />
+                  <el-input v-model="form.projectArea" placeholder="请输入" maxlength="100" />
                 </el-form-item>
               </el-col>
             </el-row>
@@ -135,13 +135,13 @@
               </el-col>
             </el-row>
             <el-form-item label="采购内容" prop="purchaseContent">
-              <el-input v-model="form.purchaseContent" type="textarea" :rows="3" maxlength="200" show-word-limit placeholder="请输入采购内容" />
+              <el-input v-model="form.purchaseContent" type="textarea" :rows="3" maxlength="500" show-word-limit placeholder="请输入采购内容" />
             </el-form-item>
             <el-form-item label="项目描述" prop="description">
-              <el-input v-model="form.description" type="textarea" :rows="3" maxlength="200" show-word-limit placeholder="请输入项目描述" />
+              <el-input v-model="form.description" type="textarea" :rows="3" maxlength="500" show-word-limit placeholder="请输入项目描述" />
             </el-form-item>
             <el-form-item label="竞争对手" prop="competitor">
-              <el-input v-model="form.competitor" type="textarea" :rows="3" maxlength="200" show-word-limit placeholder="请输入竞争对手" />
+              <el-input v-model="form.competitor" type="textarea" :rows="3" maxlength="500" show-word-limit placeholder="请输入竞争对手" />
             </el-form-item>
           </div>
 
@@ -244,16 +244,36 @@ const rules = reactive({
   companyId: [{ required: true, message: '归属公司不能为空', trigger: 'change' }],
   customerNo: [{ required: true, message: '客户名称不能为空', trigger: 'change' }],
   managerId: [{ required: true, message: '项目负责人不能为空', trigger: 'change' }],
-  projectName: [{ required: true, message: '项目名称不能为空', trigger: 'blur' }],
-  amount: [{ required: true, message: '金额不能为空', trigger: 'blur' }],
-  winRate: [{ required: true, message: '赢单率不能为空', trigger: 'blur' }],
+  projectName: [
+    { required: true, message: '项目名称不能为空', trigger: 'blur' },
+    { max: 200, message: '项目名称不能超过200个字符', trigger: 'blur' }
+  ],
+  amount: [
+    { required: true, message: '金额不能为空', trigger: 'blur' }
+  ],
+  winRate: [
+    { required: true, message: '赢单率不能为空', trigger: 'blur' },
+    { pattern: /^(100|[1-9]?\d(\.\d+)?)$/, message: '请输入正确的赢单率(0-100)', trigger: 'blur' }
+  ],
   setupTime: [{ required: true, message: '立项时间不能为空', trigger: 'change' }],
   projectLevel: [{ required: true, message: '项目级别不能为空', trigger: 'change' }],
-  projectArea: [{ required: true, message: '项目区域不能为空', trigger: 'blur' }],
+  projectArea: [
+    { required: true, message: '项目区域不能为空', trigger: 'blur' },
+    { max: 100, message: '项目区域不能超过100个字符', trigger: 'blur' }
+  ],
   purchaseMethod: [{ required: true, message: '采购方式不能为空', trigger: 'change' }],
   source: [{ required: true, message: '信息来源不能为空', trigger: 'change' }],
-  purchaseContent: [{ required: true, message: '采购内容不能为空', trigger: 'blur' }],
-  description: [{ required: true, message: '项目描述不能为空', trigger: 'blur' }],
+  purchaseContent: [
+    { required: true, message: '采购内容不能为空', trigger: 'blur' },
+    { max: 500, message: '采购内容不能超过500个字符', trigger: 'blur' }
+  ],
+  description: [
+    { required: true, message: '项目描述不能为空', trigger: 'blur' },
+    { max: 500, message: '项目描述不能超过500个字符', trigger: 'blur' }
+  ],
+  competitor: [
+    { max: 500, message: '竞争对手不能超过500个字符', trigger: 'blur' }
+  ]
 });
 
 watch(() => props.modelValue, (val) => {

+ 12 - 10
src/views/saleManage/opportunity/detail.vue

@@ -45,7 +45,7 @@
               <div class="info-block">
                 <div class="info-header">
                   <span class="title">基本信息</span>
-                  <el-button type="primary" class="edit-btn" @click="$emit('edit', detailData)" size="default">
+                  <el-button v-if="!isReadOnly" type="primary" class="edit-btn" @click="$emit('edit', detailData)" size="default">
                     <el-icon style="margin-right: 4px;"><Edit /></el-icon>编辑
                   </el-button>
                 </div>
@@ -98,7 +98,7 @@
 
             <el-tab-pane label="项目联系人" name="contact">
                <div class="tab-toolbar">
-                 <el-dropdown @command="handleContactCommand">
+                 <el-dropdown v-if="!isReadOnly" @command="handleContactCommand">
                    <el-button type="primary">
                      <el-icon style="margin-right: 4px;"><Plus /></el-icon>新建联系人<el-icon class="el-icon--right"><arrow-down /></el-icon>
                    </el-button>
@@ -141,8 +141,8 @@
                   </el-table-column>
                   <el-table-column label="操作" align="center" width="120" fixed="right">
                     <template #default="scope">
-                      <el-button link type="primary" size="small" @click="handleEditContact(scope.row)">编辑</el-button>
-                      <el-button link type="danger" size="small" @click="handleDeleteContact(scope.row)">删除</el-button>
+                      <el-button v-if="!isReadOnly" link type="primary" size="small" @click="handleEditContact(scope.row)">编辑</el-button>
+                      <el-button v-if="!isReadOnly" link type="danger" size="small" @click="handleDeleteContact(scope.row)">删除</el-button>
                     </template>
                   </el-table-column>
                  <template #empty><span class="empty-text">暂无数据</span></template>
@@ -159,7 +159,7 @@
                        <el-radio value="lose">丢单</el-radio>
                      </el-radio-group>
                      <div style="flex:1"></div>
-                     <el-button type="primary" icon="CircleCheck" @click="handleSaveAnalysis">保 存</el-button>
+                     <el-button v-if="!isReadOnly" type="primary" icon="CircleCheck" @click="handleSaveAnalysis">保 存</el-button>
                    </div>
                    <template v-if="analysisForm.resultType === 'win'">
                      <div class="summary-title">赢单总结</div>
@@ -174,7 +174,7 @@
                      </div>
                    </template>
                    <div class="attachment-section-title">附件</div>
-                   <div class="upload-area">
+                   <div v-if="!isReadOnly" class="upload-area">
                      <el-upload
                        :action="uploadFileUrl"
                        :headers="headers"
@@ -191,7 +191,7 @@
                        <el-table-column label="操作" width="120" align="center">
                           <template #default="scope">
                              <el-button link type="primary" @click="downloadFile(scope.row)">下载</el-button>
-                             <el-button link type="danger" @click="handleAnalysisDeleteFile(scope.$index)">删除</el-button>
+                             <el-button v-if="!isReadOnly" link type="danger" @click="handleAnalysisDeleteFile(scope.$index)">删除</el-button>
                           </template>
                        </el-table-column>
                      </el-table>
@@ -201,7 +201,7 @@
             </el-tab-pane>
 
             <el-tab-pane label="附件" name="attachment">
-               <div class="tab-toolbar">
+               <div v-if="!isReadOnly" class="tab-toolbar">
                  <el-upload
                    :action="uploadFileUrl"
                    :headers="headers"
@@ -219,7 +219,7 @@
                   <el-table-column label="操作" align="center" width="120">
                     <template #default="scope">
                       <el-button link type="primary" @click="downloadFile(scope.row)" style="margin-right: 10px;">下载</el-button>
-                      <el-button link type="danger" @click="handleDeleteFile(scope.row)">删除</el-button>
+                      <el-button v-if="!isReadOnly" link type="danger" @click="handleDeleteFile(scope.row)">删除</el-button>
                     </template>
                   </el-table-column>
                  <template #empty><span class="empty-text">暂无数据</span></template>
@@ -242,6 +242,7 @@
             }" 
             businessType="opportunity"
             @success="getList"
+            @update-readonly="val => isReadOnly = val"
           />
         </div>
       </div>
@@ -521,6 +522,7 @@ const currentStepIndex = computed(() => {
 });
 
 const visible = ref(false);
+const isReadOnly = ref(true);
 const detailData = ref({});
 const leftActiveTab = ref('info');
 const industryOptions = ref([]);
@@ -820,7 +822,7 @@ function handleTree(data, id, parentId, children) {
 const getIndustryList = async () => {
   try {
     const res = await listIndustryCategory();
-    industryOptions.value = res.data || [];
+    industryOptions.value = res.data || res.rows || [];
   } catch (error) {}
 };
 

+ 27 - 9
src/views/saleManage/opportunity/edit.vue

@@ -43,13 +43,13 @@
                 <el-form-item label="项目负责人" prop="managerId">
                   <el-select v-model="form.managerId" placeholder="请选择" style="width: 100%" clearable filterable @change="handleLeaderChange">
                     <el-option v-for="item in computedUserOptions" :key="item.staffId || item.userId"
-                      :label="item.staffName || item.nickName" :value="String(item.staffId || item.userId)" />
+                      :label="(item.staffCode || item.userName ? '(' + (item.staffCode || item.userName) + ') ' : '') + (item.staffName || item.nickName)" :value="String(item.staffId || item.userId)" />
                   </el-select>
                 </el-form-item>
               </el-col>
               <el-col :span="12">
                 <el-form-item label="项目名称" prop="projectName">
-                  <el-input v-model="form.projectName" placeholder="请输入" />
+                  <el-input v-model="form.projectName" placeholder="请输入" maxlength="200" show-word-limit />
                 </el-form-item>
               </el-col>
             </el-row>
@@ -61,7 +61,7 @@
               </el-col>
               <el-col :span="8">
                 <el-form-item label="赢单率(%)" prop="winRate">
-                  <el-input v-model="form.winRate" placeholder="请输入" type="number">
+                  <el-input v-model="form.winRate" placeholder="请输入" type="number" maxlength="6">
                     <template #append>%</template>
                   </el-input>
                 </el-form-item>
@@ -102,7 +102,7 @@
               </el-col>
               <el-col :span="8">
                 <el-form-item label="项目区域" prop="projectArea">
-                  <el-input v-model="form.projectArea" placeholder="请输入" />
+                  <el-input v-model="form.projectArea" placeholder="请输入" maxlength="100" />
                 </el-form-item>
               </el-col>
               <el-col :span="8">
@@ -125,10 +125,10 @@
               </el-col>
             </el-row>
             <el-form-item label="项目描述" prop="description">
-              <el-input v-model="form.description" type="textarea" :rows="3" placeholder="请输入项目描述" />
+              <el-input v-model="form.description" type="textarea" :rows="3" placeholder="请输入项目描述" maxlength="500" show-word-limit />
             </el-form-item>
             <el-form-item label="竞争对手" prop="competitor">
-              <el-input v-model="form.competitor" type="textarea" :rows="3" placeholder="请输入竞争对手" />
+              <el-input v-model="form.competitor" type="textarea" :rows="3" placeholder="请输入竞争对手" maxlength="500" show-word-limit />
             </el-form-item>
           </div>
 
@@ -226,10 +226,28 @@ const rules = reactive({
   companyId: [{ required: true, message: '归属公司不能为空', trigger: 'change' }],
   customerNo: [{ required: true, message: '客户名称不能为空', trigger: 'change' }],
   managerId: [{ required: true, message: '项目负责人不能为空', trigger: 'change' }],
-  projectName: [{ required: true, message: '项目名称不能为空', trigger: 'blur' }],
-  amount: [{ required: true, message: '金额不能为空', trigger: 'blur' }],
-  winRate: [{ required: true, message: '赢单率不能为空', trigger: 'blur' }],
+  projectName: [
+    { required: true, message: '项目名称不能为空', trigger: 'blur' },
+    { max: 200, message: '项目名称不能超过200个字符', trigger: 'blur' }
+  ],
+  amount: [
+    { required: true, message: '金额不能为空', trigger: 'blur' }
+  ],
+  winRate: [
+    { required: true, message: '赢单率不能为空', trigger: 'blur' },
+    { pattern: /^(100|[1-9]?\d(\.\d+)?)$/, message: '请输入正确的赢单率(0-100)', trigger: 'blur' }
+  ],
   setupTime: [{ required: true, message: '立项时间不能为空', trigger: 'change' }],
+  projectArea: [
+    { max: 100, message: '项目区域不能超过100个字符', trigger: 'blur' }
+  ],
+  description: [
+    { required: true, message: '项目描述不能为空', trigger: 'blur' },
+    { max: 500, message: '项目描述不能超过500个字符', trigger: 'blur' }
+  ],
+  competitor: [
+    { max: 500, message: '竞争对手不能超过500个字符', trigger: 'blur' }
+  ]
 });
 
 // 【增强回显】通过计算属性确保当前项在选项列表中,防止分页或数据不全导致回显显示为 ID

+ 13 - 11
src/views/saleManage/opportunity/index.vue

@@ -25,7 +25,7 @@
             <el-form-item label="项目负责人" prop="managerId">
               <el-select v-model="queryParams.managerId" placeholder="请选择" style="width: 100%" clearable>
                 <el-option v-for="item in userOptions" :key="item.staffId || item.userId"
-                  :label="item.staffName || item.nickName" :value="item.staffId || item.userId" />
+                  :label="(item.staffCode || item.userName ? '(' + (item.staffCode || item.userName) + ') ' : '') + (item.staffName || item.nickName)" :value="item.staffId || item.userId" />
               </el-select>
             </el-form-item>
           </el-col>
@@ -49,7 +49,7 @@
             <el-form-item label="产品支持" prop="productSupport">
               <el-select v-model="queryParams.productSupport" placeholder="请选择" style="width: 100%" clearable>
                 <el-option v-for="item in userOptions" :key="item.staffId || item.userId"
-                  :label="item.staffName || item.nickName" :value="String(item.staffId || item.userId)" />
+                  :label="(item.staffCode || item.userName ? '(' + (item.staffCode || item.userName) + ') ' : '') + (item.staffName || item.nickName)" :value="String(item.staffId || item.userId)" />
               </el-select>
             </el-form-item>
           </el-col>
@@ -175,9 +175,11 @@
             <span>{{ parseTime(scope.row.expectedCompletionTime, '{y}-{m}-{d}') }}</span>
           </template>
         </el-table-column>
-        <el-table-column label="项目状态" align="center" prop="status" width="100">
+        <el-table-column label="项目状态" align="center" prop="statusName" width="100">
           <template #default="{ row }">
-            <dict-tag :options="saleStatusOptions" :value="row.status" :show-value="false" />
+            <span :class="getStatusClass(row.status)">
+              {{ row.statusName }}
+            </span>
           </template>
         </el-table-column>
         <el-table-column label="成交结果" align="center" prop="dealResult" width="100">
@@ -204,7 +206,7 @@
         <el-form-item label="新负责人:" required>
           <el-select v-model="transferOwner" placeholder="请选择" style="width: 100%">
             <el-option v-for="item in userOptions" :key="item.staffId || item.userId"
-              :label="item.staffName || item.nickName" :value="String(item.staffId || item.userId)" />
+              :label="(item.staffCode || item.userName ? '(' + (item.staffCode || item.userName) + ') ' : '') + (item.staffName || item.nickName)" :value="String(item.staffId || item.userId)" />
           </el-select>
         </el-form-item>
         <el-form-item class="label-nowrap">
@@ -535,12 +537,12 @@ const getSaleStatusLabel = (status) => {
 };
 
 const getStatusClass = (status) => {
-  if (!saleStatusOptions.value || saleStatusOptions.value.length === 0) return 'status-following';
+  if (String(status) === '1') return 'status-tag status-success';
+  if (String(status) === '0') return 'status-tag status-following';
+  if (!saleStatusOptions.value || saleStatusOptions.value.length === 0) return 'status-tag status-following';
   const item = saleStatusOptions.value.find(i => String(i.value) === String(status));
-  // 根据字典的特定值或属性来决定样式,这里如果字典有listClass可以使用
-  if (item && item.listClass === 'success') return 'status-success';
-  if (item && item.label === '结案') return 'status-success';
-  return 'status-following'; // 默认橙色/蓝色
+  if (item && (item.listClass === 'success' || item.label === '结案')) return 'status-tag status-success';
+  return 'status-tag status-following';
 };
 
 const findProjectLevelName = (id) => {
@@ -571,7 +573,7 @@ const findUserName = (id) => {
   return user ? (user.staffName || user.nickName) : id;
 };
 
-function getIndustryList() { listIndustryCategory().then(r => industryOptions.value = r.data); }
+function getIndustryList() { listIndustryCategory().then(r => industryOptions.value = r.data || r.rows || []); }
 
 onMounted(() => {
   getList(); getCompanyList(); getUserList(); getDeptList();

+ 67 - 23
src/views/saleManage/platformSelection/add.vue

@@ -55,13 +55,13 @@
             </el-col>
             <el-col :span="8">
               <el-form-item label="项目名称" prop="projectName">
-                <el-input v-model="drawerForm.projectName" placeholder="请输入项目名称" />
+                <el-input v-model="drawerForm.projectName" placeholder="请输入项目名称" maxlength="200" show-word-limit />
               </el-form-item>
             </el-col>
             <el-col :span="8">
               <el-form-item label="项目负责人" prop="leader" label-for="leaderSelect">
                 <el-select id="leaderSelect" v-model="drawerForm.leader" style="width:100%" placeholder="请选择" filterable>
-                  <el-option v-for="item in options.user" :key="item.userId || item.staffId" :label="item.nickName || item.staffName" :value="String(item.userId || item.staffId)" />
+                  <el-option v-for="item in options.user" :key="item.userId || item.staffId" :label="(item.userName || item.staffCode ? '(' + (item.userName || item.staffCode) + ') ' : '') + (item.nickName || item.staffName)" :value="String(item.userId || item.staffId)" />
                 </el-select>
               </el-form-item>
             </el-col>
@@ -69,13 +69,13 @@
           <el-row :gutter="20">
             <el-col :span="8">
               <el-form-item label="平台名称" prop="platformName">
-                <el-input v-model="drawerForm.platformName" placeholder="请输入平台名称" />
+                <el-input v-model="drawerForm.platformName" placeholder="请输入平台名称" maxlength="200" show-word-limit />
               </el-form-item>
             </el-col>
             <el-col :span="8">
               <el-form-item label="产品支持" prop="productSupport" label-for="productSupportSelect">
                 <el-select id="productSupportSelect" v-model="drawerForm.productSupport" style="width:100%" placeholder="请选择" filterable clearable>
-                  <el-option v-for="item in options.user" :key="item.userId || item.staffId" :label="item.nickName || item.staffName" :value="String(item.userId || item.staffId)" />
+                  <el-option v-for="item in options.user" :key="item.userId || item.staffId" :label="(item.userName || item.staffCode ? '(' + (item.userName || item.staffCode) + ') ' : '') + (item.nickName || item.staffName)" :value="String(item.userId || item.staffId)" />
                 </el-select>
               </el-form-item>
             </el-col>
@@ -83,7 +83,7 @@
           <el-row :gutter="20">
             <el-col :span="24">
               <el-form-item label="平台链接" prop="platformLink">
-                <el-input v-model="drawerForm.platformLink" placeholder="请输入平台链接" />
+                <el-input v-model="drawerForm.platformLink" placeholder="请输入平台链接" maxlength="500" />
               </el-form-item>
             </el-col>
           </el-row>
@@ -94,26 +94,26 @@
           <el-row :gutter="20">
             <el-col :span="8">
               <el-form-item label="金额" prop="amount">
-                <el-input v-model="drawerForm.amount" placeholder="请输入金额">
+                <el-input v-model="drawerForm.amount" placeholder="请输入金额" maxlength="15">
                   <template #append>万</template>
                 </el-input>
               </el-form-item>
             </el-col>
             <el-col :span="8">
               <el-form-item label="报名费" prop="entryFee">
-                <el-input v-model="drawerForm.entryFee" placeholder="请输入报名费" />
+                <el-input v-model="drawerForm.entryFee" placeholder="请输入报名费" maxlength="15" />
               </el-form-item>
             </el-col>
             <el-col :span="8">
               <el-form-item label="投标保证金" prop="bidBond">
-                <el-input v-model="drawerForm.bidBond" placeholder="请输入投标保证金" />
+                <el-input v-model="drawerForm.bidBond" placeholder="请输入投标保证金" maxlength="15" />
               </el-form-item>
             </el-col>
           </el-row>
           <el-row :gutter="20">
             <el-col :span="8">
               <el-form-item label="赢单率" prop="winningRate">
-                <el-input v-model="drawerForm.winningRate" placeholder="请输入赢单率">
+                <el-input v-model="drawerForm.winningRate" placeholder="请输入赢单率" maxlength="6">
                   <template #append>%</template>
                 </el-input>
               </el-form-item>
@@ -132,14 +132,14 @@
           <el-row :gutter="20">
             <el-col :span="8">
               <el-form-item label="服务期" prop="standardPeriod">
-                <el-input v-model="drawerForm.standardPeriod" placeholder="请输入服务期">
+                <el-input v-model="drawerForm.standardPeriod" placeholder="请输入服务期" maxlength="50">
                   <template #append>年</template>
                 </el-input>
               </el-form-item>
             </el-col>
             <el-col :span="8">
               <el-form-item label="服务时间段" prop="serviceTime">
-                <el-input v-model="drawerForm.serviceTime" placeholder="请输入服务时间段" />
+                <el-input v-model="drawerForm.serviceTime" placeholder="请输入服务时间段" maxlength="100" />
               </el-form-item>
             </el-col>
             <el-col :span="8">
@@ -160,12 +160,12 @@
             </el-col>
             <el-col :span="8">
               <el-form-item label="招标代理机构" prop="biddingAgency">
-                <el-input v-model="drawerForm.biddingAgency" placeholder="请输入代理机构" />
+                <el-input v-model="drawerForm.biddingAgency" placeholder="请输入代理机构" maxlength="100" />
               </el-form-item>
             </el-col>
             <el-col :span="8">
               <el-form-item label="代理机构联系方式" prop="agencyContact">
-                <el-input v-model="drawerForm.agencyContact" placeholder="请输入联系方式" />
+                <el-input v-model="drawerForm.agencyContact" placeholder="请输入联系方式" maxlength="50" />
               </el-form-item>
             </el-col>
           </el-row>
@@ -188,7 +188,7 @@
               </el-col>
               <el-col :span="8">
                 <el-form-item label="提前提醒天数" prop="noticeAdvanceDays">
-                  <el-input v-model="drawerForm.noticeAdvanceDays" placeholder="请输入提醒天数" />
+                  <el-input v-model="drawerForm.noticeAdvanceDays" placeholder="请输入提醒天数" maxlength="5" />
                 </el-form-item>
               </el-col>
             </el-row>
@@ -196,14 +196,14 @@
           <el-row :gutter="20">
             <el-col :span="24">
               <el-form-item label="入围要求" prop="shortlistedRequirement">
-                <el-input v-model="drawerForm.shortlistedRequirement" type="textarea" :rows="3" placeholder="请输入入围要求" maxlength="200" show-word-limit />
+                <el-input v-model="drawerForm.shortlistedRequirement" type="textarea" :rows="3" placeholder="请输入入围要求" maxlength="500" show-word-limit />
               </el-form-item>
             </el-col>
           </el-row>
           <el-row :gutter="20">
             <el-col :span="24">
               <el-form-item label="项目描述" prop="projectDesc">
-                <el-input v-model="drawerForm.projectDesc" type="textarea" :rows="3" placeholder="请输入项目描述" maxlength="200" show-word-limit />
+                <el-input v-model="drawerForm.projectDesc" type="textarea" :rows="3" placeholder="请输入项目描述" maxlength="500" show-word-limit />
               </el-form-item>
             </el-col>
           </el-row>
@@ -331,19 +331,63 @@ const drawerRules = {
   customName: [{ required: true, message: '请选择客户名称', trigger: 'change' }],
   projectLevel: [{ required: true, message: '请选择项目级别', trigger: 'change' }],
   businessType: [{ required: true, message: '请选择项目类型', trigger: 'change' }],
-  projectName: [{ required: true, message: '请输入项目名称', trigger: 'blur' }],
-  amount: [{ required: true, message: '请输入金额', trigger: 'blur' }],
-  winningRate: [{ required: true, message: '请输入赢单率', trigger: 'blur' }],
+  projectName: [
+    { required: true, message: '请输入项目名称', trigger: 'blur' },
+    { max: 200, message: '项目名称不能超过200个字符', trigger: 'blur' }
+  ],
+  platformName: [
+    { max: 200, message: '平台名称不能超过200个字符', trigger: 'blur' }
+  ],
+  platformLink: [
+    { max: 500, message: '平台链接不能超过500个字符', trigger: 'blur' },
+    { pattern: /^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([\/\w .-]*)*\/?$/, message: '请输入正确的平台链接地址', trigger: 'blur' }
+  ],
+  amount: [
+    { required: true, message: '请输入金额', trigger: 'blur' },
+    { pattern: /^\d+(\.\d+)?$/, message: '请输入正确的金额数字', trigger: 'blur' }
+  ],
+  entryFee: [
+    { pattern: /^\d+(\.\d+)?$/, message: '请输入正确的报名费', trigger: 'blur' }
+  ],
+  bidBond: [
+    { pattern: /^\d+(\.\d+)?$/, message: '请输入正确的投标保证金', trigger: 'blur' }
+  ],
+  winningRate: [
+    { required: true, message: '请输入赢单率', trigger: 'blur' },
+    { pattern: /^(100|[1-9]?\d(\.\d+)?)$/, message: '请输入正确的赢单率(0-100)', trigger: 'blur' }
+  ],
   signUpDeadline: [{ required: true, message: '请选择报名截止时间', trigger: 'change' }],
   tenderDeadline: [{ required: true, message: '请选择投标截止时间', trigger: 'change' }],
-  standardPeriod: [{ required: true, message: '请输入服务期', trigger: 'blur' }],
+  standardPeriod: [
+    { required: true, message: '请输入服务期', trigger: 'blur' },
+    { max: 50, message: '服务期不能超过50个字符', trigger: 'blur' }
+  ],
+  serviceTime: [
+    { max: 100, message: '服务时间段不能超过100个字符', trigger: 'blur' }
+  ],
+  biddingAgency: [
+    { max: 100, message: '招标代理机构不能超过100个字符', trigger: 'blur' }
+  ],
+  agencyContact: [
+    { max: 50, message: '代理机构联系方式不能超过50个字符', trigger: 'blur' },
+    { pattern: /^(1[3-9]\d{9}|[0-9-]{7,20})$/, message: '请输入正确的联系电话(手机或固话)', trigger: 'blur' }
+  ],
   shortlistedType: [{ required: true, message: '请选择入围类型', trigger: 'change' }],
   profession: [{ required: true, message: '请选择物资类目', trigger: 'change' }],
   bidPeriodType: [{ required: true, message: '请选择标期类型', trigger: 'change' }],
-  shortlistedRequirement: [{ required: true, message: '请输入入围要求', trigger: 'blur' }],
-  projectDesc: [{ required: true, message: '请输入项目描述', trigger: 'blur' }],
+  shortlistedRequirement: [
+    { required: true, message: '请输入入围要求', trigger: 'blur' },
+    { max: 500, message: '入围要求不能超过500个字符', trigger: 'blur' }
+  ],
+  projectDesc: [
+    { required: true, message: '请输入项目描述', trigger: 'blur' },
+    { max: 500, message: '项目描述不能超过500个字符', trigger: 'blur' }
+  ],
   nextBiddingTime: [{ required: true, message: '请选择预计下次投标时间', trigger: 'change' }],
-  noticeAdvanceDays: [{ required: true, message: '请输入提前提醒天数', trigger: 'blur' }]
+  noticeAdvanceDays: [
+    { required: true, message: '请输入提前提醒天数', trigger: 'blur' },
+    { pattern: /^[1-9]\d*$/, message: '请输入大于0的整数', trigger: 'blur' }
+  ]
 };
 
 const handleSubmit = async () => {

+ 11 - 9
src/views/saleManage/platformSelection/detail.vue

@@ -48,7 +48,7 @@
                 <div class="info-section">
                   <div class="info-section-header">
                     <span class="title">基本信息</span>
-                    <el-button type="primary" size="small" @click="handleEdit">
+                    <el-button v-if="!isReadOnly" type="primary" size="small" @click="handleEdit">
                       <el-icon style="margin-right: 4px;"><Edit /></el-icon>编辑
                     </el-button>
                   </div>
@@ -104,7 +104,7 @@
               <el-tab-pane label="项目联系人" name="contact">
                 <div class="contact-tab-content">
                   <div class="tab-toolbar">
-                    <el-dropdown @command="handleContactCommand">
+                    <el-dropdown v-if="!isReadOnly" @command="handleContactCommand">
                       <el-button type="primary">
                         <el-icon style="margin-right: 4px;"><Plus /></el-icon>新建联系人<el-icon class="el-icon--right"><arrow-down /></el-icon>
                       </el-button>
@@ -143,8 +143,8 @@
                     </el-table-column>
                     <el-table-column label="操作" align="center" width="120" fixed="right">
                       <template #default="scope">
-                        <el-button link type="primary" @click="handleEditContact(scope.row)">编辑</el-button>
-                        <el-button link type="danger" @click="handleDeleteContact(scope.row)">删除</el-button>
+                        <el-button v-if="!isReadOnly" link type="primary" @click="handleEditContact(scope.row)">编辑</el-button>
+                        <el-button v-if="!isReadOnly" link type="danger" @click="handleDeleteContact(scope.row)">删除</el-button>
                       </template>
                     </el-table-column>
                   </el-table>
@@ -160,7 +160,7 @@
                         <el-radio value="lose">丢单</el-radio>
                       </el-radio-group>
                       <div style="flex:1"></div>
-                      <el-button type="primary" icon="CircleCheck" @click="handleSaveAnalysis">保 存</el-button>
+                      <el-button v-if="!isReadOnly" type="primary" icon="CircleCheck" @click="handleSaveAnalysis">保 存</el-button>
                     </div>
                     <template v-if="analysisForm.resultType === 'win'">
                       <div class="summary-title">赢单总结</div>
@@ -175,7 +175,7 @@
                       </div>
                     </template>
                     <div class="attachment-section-title">附件</div>
-                    <div class="upload-area">
+                    <div v-if="!isReadOnly" class="upload-area">
                       <el-upload
                         :action="uploadFileUrl"
                         :headers="headers"
@@ -192,7 +192,7 @@
                         <el-table-column label="操作" width="120" align="center">
                           <template #default="scope">
                             <el-button link type="primary" @click="downloadFile(scope.row)">下载</el-button>
-                            <el-button link type="danger" @click="handleAnalysisDeleteFile(scope.$index)">删除</el-button>
+                            <el-button v-if="!isReadOnly" link type="danger" @click="handleAnalysisDeleteFile(scope.$index)">删除</el-button>
                           </template>
                         </el-table-column>
                       </el-table>
@@ -202,7 +202,7 @@
               </el-tab-pane>
               <el-tab-pane label="附件" name="files">
                 <div class="files-tab-content">
-                    <div class="tab-toolbar">
+                    <div v-if="!isReadOnly" class="tab-toolbar">
                       <el-upload
                         :action="uploadFileUrl"
                         :headers="headers"
@@ -221,7 +221,7 @@
                       <el-table-column label="操作" width="120" align="center">
                         <template #default="scope">
                           <el-button link type="primary" @click="downloadFile(scope.row)">下载</el-button>
-                          <el-button link type="danger" @click="handleDeleteFile(scope.row)">删除</el-button>
+                          <el-button v-if="!isReadOnly" link type="danger" @click="handleDeleteFile(scope.row)">删除</el-button>
                         </template>
                       </el-table-column>
                     </el-table>
@@ -495,6 +495,7 @@ const visible = computed({
 });
 
 const loading = ref(false);
+const isReadOnly = ref(true);
 const drawerForm = ref({ projectStatus: 1, fileList: [], memberList: [] });
 const drawerActiveTab = ref('info');
 const stepList = ['获取信息', '正式立项', '竞价投标', '项目跟进', '结案'];
@@ -1003,6 +1004,7 @@ const handleDeleteFile = (row) => {
 };
 
 const handleStepClick = async (status) => {
+  if (isReadOnly.value) return;
   // 再次点击取消:如果点击已激活步骤则设为 null,否则设为目标步骤
   const newStatus = String(drawerForm.value.projectStatus) === String(status) ? null : status;
   

+ 67 - 21
src/views/saleManage/platformSelection/edit.vue

@@ -54,13 +54,13 @@
             </el-col>
             <el-col :span="8">
               <el-form-item label="项目名称" prop="projectName">
-                <el-input v-model="drawerForm.projectName" placeholder="请输入项目名称" />
+                <el-input v-model="drawerForm.projectName" placeholder="请输入项目名称" maxlength="200" show-word-limit />
               </el-form-item>
             </el-col>
             <el-col :span="8">
               <el-form-item label="项目负责人" prop="leader">
                 <el-select v-model="drawerForm.leader" style="width:100%" placeholder="请选择" filterable clearable>
-                  <el-option v-for="item in computedUserOptions" :key="item.userId || item.staffId" :label="item.nickName || item.staffName" :value="String(item.userId || item.staffId)" />
+                  <el-option v-for="item in computedUserOptions" :key="item.userId || item.staffId" :label="(item.userName || item.staffCode ? '(' + (item.userName || item.staffCode) + ') ' : '') + (item.nickName || item.staffName)" :value="String(item.userId || item.staffId)" />
                 </el-select>
               </el-form-item>
             </el-col>
@@ -68,13 +68,15 @@
           <el-row :gutter="20">
             <el-col :span="8">
               <el-form-item label="平台名称" prop="platformName">
-                <el-input v-model="drawerForm.platformName" placeholder="请输入平台名称" />
+                <el-input v-model="drawerForm.platformName" placeholder="请输入平台名称" maxlength="200" show-word-limit />
               </el-form-item>
             </el-col>
             <el-col :span="8">
               <el-form-item label="产品支持" prop="productSupport" label-for="productSupportSelect">
                 <el-select id="productSupportSelect" v-model="drawerForm.productSupport" style="width:100%" placeholder="请选择" filterable clearable @change="handleProductSupportChange">
-                  <el-option v-for="item in computedProductSupportOptions" :key="item.userId || item.staffId" :label="item.nickName || item.staffName" :value="String(item.userId || item.staffId)" />
+                  <template v-if="computedProductSupportOptions && computedProductSupportOptions.length">
+                    <el-option v-for="item in computedProductSupportOptions" :key="item.userId || item.staffId" :label="(item.userName || item.staffCode ? '(' + (item.userName || item.staffCode) + ') ' : '') + (item.nickName || item.staffName)" :value="String(item.userId || item.staffId)" />
+                  </template>
                 </el-select>
               </el-form-item>
             </el-col>
@@ -82,7 +84,7 @@
           <el-row :gutter="20">
             <el-col :span="24">
               <el-form-item label="平台链接" prop="platformLink">
-                <el-input v-model="drawerForm.platformLink" placeholder="请输入平台链接" />
+                <el-input v-model="drawerForm.platformLink" placeholder="请输入平台链接" maxlength="500" />
               </el-form-item>
             </el-col>
           </el-row>
@@ -93,26 +95,26 @@
           <el-row :gutter="20">
             <el-col :span="8">
               <el-form-item label="金额" prop="amount">
-                <el-input v-model="drawerForm.amount" placeholder="请输入金额">
+                <el-input v-model="drawerForm.amount" placeholder="请输入金额" maxlength="15">
                   <template #append>万</template>
                 </el-input>
               </el-form-item>
             </el-col>
             <el-col :span="8">
               <el-form-item label="报名费" prop="entryFee">
-                <el-input v-model="drawerForm.entryFee" placeholder="请输入报名费" />
+                <el-input v-model="drawerForm.entryFee" placeholder="请输入报名费" maxlength="15" />
               </el-form-item>
             </el-col>
             <el-col :span="8">
               <el-form-item label="投标保证金" prop="bidBond">
-                <el-input v-model="drawerForm.bidBond" placeholder="请输入投标保证金" />
+                <el-input v-model="drawerForm.bidBond" placeholder="请输入投标保证金" maxlength="15" />
               </el-form-item>
             </el-col>
           </el-row>
           <el-row :gutter="20">
             <el-col :span="8">
               <el-form-item label="赢单率" prop="winningRate">
-                <el-input v-model="drawerForm.winningRate" placeholder="请输入赢单率">
+                <el-input v-model="drawerForm.winningRate" placeholder="请输入赢单率" maxlength="6">
                   <template #append>%</template>
                 </el-input>
               </el-form-item>
@@ -131,14 +133,14 @@
           <el-row :gutter="20">
             <el-col :span="8">
               <el-form-item label="服务期" prop="standardPeriod">
-                <el-input v-model="drawerForm.standardPeriod" placeholder="请输入服务期">
+                <el-input v-model="drawerForm.standardPeriod" placeholder="请输入服务期" maxlength="50">
                   <template #append>年</template>
                 </el-input>
               </el-form-item>
             </el-col>
             <el-col :span="8">
               <el-form-item label="服务时间段" prop="serviceTime">
-                <el-input v-model="drawerForm.serviceTime" placeholder="请输入服务时间段" />
+                <el-input v-model="drawerForm.serviceTime" placeholder="请输入服务时间段" maxlength="100" />
               </el-form-item>
             </el-col>
             <el-col :span="8">
@@ -159,12 +161,12 @@
             </el-col>
             <el-col :span="8">
               <el-form-item label="招标代理机构" prop="biddingAgency">
-                <el-input v-model="drawerForm.biddingAgency" placeholder="请输入代理机构" />
+                <el-input v-model="drawerForm.biddingAgency" placeholder="请输入代理机构" maxlength="100" />
               </el-form-item>
             </el-col>
             <el-col :span="8">
               <el-form-item label="代理联系方式" prop="agencyContact">
-                <el-input v-model="drawerForm.agencyContact" placeholder="请输入联系方式" />
+                <el-input v-model="drawerForm.agencyContact" placeholder="请输入联系方式" maxlength="50" />
               </el-form-item>
             </el-col>
           </el-row>
@@ -187,7 +189,7 @@
               </el-col>
               <el-col :span="8">
                 <el-form-item label="提前提醒天数" prop="noticeAdvanceDays">
-                  <el-input v-model="drawerForm.noticeAdvanceDays" placeholder="请输入提醒天数" />
+                  <el-input v-model="drawerForm.noticeAdvanceDays" placeholder="请输入提醒天数" maxlength="5" />
                 </el-form-item>
               </el-col>
             </el-row>
@@ -363,19 +365,63 @@ const drawerRules = {
   customName: [{ required: true, message: '请选择客户名称', trigger: 'change' }],
   projectLevel: [{ required: true, message: '请选择项目级别', trigger: 'change' }],
   businessType: [{ required: true, message: '请选择项目类型', trigger: 'change' }],
-  projectName: [{ required: true, message: '请输入项目名称', trigger: 'blur' }],
-  amount: [{ required: true, message: '请输入金额', trigger: 'blur' }],
-  winningRate: [{ required: true, message: '请输入赢单率', trigger: 'blur' }],
+  projectName: [
+    { required: true, message: '请输入项目名称', trigger: 'blur' },
+    { max: 200, message: '项目名称不能超过200个字符', trigger: 'blur' }
+  ],
+  platformName: [
+    { max: 200, message: '平台名称不能超过200个字符', trigger: 'blur' }
+  ],
+  platformLink: [
+    { max: 500, message: '平台链接不能超过500个字符', trigger: 'blur' },
+    { pattern: /^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([\/\w .-]*)*\/?$/, message: '请输入正确的平台链接地址', trigger: 'blur' }
+  ],
+  amount: [
+    { required: true, message: '请输入金额', trigger: 'blur' },
+    { pattern: /^\d+(\.\d+)?$/, message: '请输入正确的金额数字', trigger: 'blur' }
+  ],
+  entryFee: [
+    { pattern: /^\d+(\.\d+)?$/, message: '请输入正确的报名费', trigger: 'blur' }
+  ],
+  bidBond: [
+    { pattern: /^\d+(\.\d+)?$/, message: '请输入正确的投标保证金', trigger: 'blur' }
+  ],
+  winningRate: [
+    { required: true, message: '请输入赢单率', trigger: 'blur' },
+    { pattern: /^(100|[1-9]?\d(\.\d+)?)$/, message: '请输入正确的赢单率(0-100)', trigger: 'blur' }
+  ],
   signUpDeadline: [{ required: true, message: '请选择报名截止时间', trigger: 'change' }],
   tenderDeadline: [{ required: true, message: '请选择投标截止时间', trigger: 'change' }],
-  standardPeriod: [{ required: true, message: '请输入服务期', trigger: 'blur' }],
+  standardPeriod: [
+    { required: true, message: '请输入服务期', trigger: 'blur' },
+    { max: 50, message: '服务期不能超过50个字符', trigger: 'blur' }
+  ],
+  serviceTime: [
+    { max: 100, message: '服务时间段不能超过100个字符', trigger: 'blur' }
+  ],
+  biddingAgency: [
+    { max: 100, message: '招标代理机构不能超过100个字符', trigger: 'blur' }
+  ],
+  agencyContact: [
+    { max: 50, message: '代理机构联系方式不能超过50个字符', trigger: 'blur' },
+    { pattern: /^(1[3-9]\d{9}|[0-9-]{7,20})$/, message: '请输入正确的联系电话(手机或固话)', trigger: 'blur' }
+  ],
   shortlistedType: [{ required: true, message: '请选择入围类型', trigger: 'change' }],
   profession: [{ required: true, message: '请选择物资类目', trigger: 'change' }],
   bidPeriodType: [{ required: true, message: '请选择标期类型', trigger: 'change' }],
-  shortlistedRequirement: [{ required: true, message: '请输入入围要求', trigger: 'blur' }],
-  projectDesc: [{ required: true, message: '请输入项目描述', trigger: 'blur' }],
+  shortlistedRequirement: [
+    { required: true, message: '请输入入围要求', trigger: 'blur' },
+    { max: 500, message: '入围要求不能超过500个字符', trigger: 'blur' }
+  ],
+  projectDesc: [
+    { required: true, message: '请输入项目描述', trigger: 'blur' },
+    { max: 500, message: '项目描述不能超过500个字符', trigger: 'blur' }
+  ],
   nextBiddingTime: [{ required: true, message: '请选择预计下次投标时间', trigger: 'change' }],
-  noticeAdvanceDays: [{ required: true, message: '请输入提前提醒天数', trigger: 'blur' }]
+  noticeAdvanceDays: [
+    { required: true, message: '请输入提前提醒天数', trigger: 'blur' },
+    { pattern: /^[1-9]\d*$/, message: '请输入大于0的整数', trigger: 'blur' }
+  ]
 };
 
 watch(() => props.modelValue, (val) => {

+ 6 - 5
src/views/saleManage/platformSelection/index.vue

@@ -24,7 +24,7 @@
           <el-col :span="6">
             <el-form-item label="负责人" prop="leader">
               <el-select v-model="queryParams.leader" placeholder="请选择" style="width: 100%" clearable>
-                 <el-option v-for="item in userOptions" :key="item.staffId || item.userId" :label="item.staffName || item.nickName" :value="String(item.staffId || item.userId)" />
+                 <el-option v-for="item in userOptions" :key="item.staffId || item.userId" :label="(item.staffCode || item.userName ? '(' + (item.staffCode || item.userName) + ') ' : '') + (item.staffName || item.nickName)" :value="String(item.staffId || item.userId)" />
               </el-select>
             </el-form-item>
           </el-col>
@@ -48,7 +48,7 @@
           <el-col :span="6">
             <el-form-item label="产品支持" prop="productSupport">
               <el-select v-model="queryParams.productSupport" placeholder="请选择" style="width: 100%" clearable>
-                 <el-option v-for="item in userOptions" :key="item.staffId || item.userId" :label="item.staffName || item.nickName" :value="String(item.staffId || item.userId)" />
+                 <el-option v-for="item in userOptions" :key="item.staffId || item.userId" :label="(item.staffCode || item.userName ? '(' + (item.staffCode || item.userName) + ') ' : '') + (item.staffName || item.nickName)" :value="String(item.staffId || item.userId)" />
               </el-select>
             </el-form-item>
           </el-col>
@@ -302,7 +302,7 @@
       <el-form label-width="110px" style="padding: 10px 20px 0;">
         <el-form-item label="新负责人:" required>
           <el-select v-model="transferOwner" placeholder="请选择" style="width: 100%" filterable clearable>
-            <el-option v-for="item in userOptions" :key="item.staffId || item.userId" :label="item.staffName || item.nickName" :value="String(item.staffId || item.userId)" />
+            <el-option v-for="item in userOptions" :key="item.staffId || item.userId" :label="(item.staffCode || item.userName ? '(' + (item.staffCode || item.userName) + ') ' : '') + (item.staffName || item.nickName)" :value="String(item.staffId || item.userId)" />
           </el-select>
         </el-form-item>
         <el-form-item label="">
@@ -611,9 +611,10 @@ const initData = () => {
       });
       return result;
     };
-    const flatData = flatten(res.data || []);
+    const listData = res.data || res.rows || [];
+    const flatData = flatten(listData);
     industryOptions.value = flatData;
-    drawerOptionsBase.industryList = res.data; 
+    drawerOptionsBase.industryList = listData; 
   });
   listComStaff({ pageSize: 1000 }).then(res => { 
     const list = res.rows || res.data || [];

+ 49 - 35
src/views/saleManage/projectSelection/add.vue

@@ -58,21 +58,21 @@
               </el-col>
               <el-col :span="8">
                 <el-form-item label="项目负责人" prop="leader">
-                  <el-select v-model="form.leader" placeholder="请选择" style="width: 100%" clearable filterable @change="handleLeaderChange">
-                    <el-option v-for="item in userOptions" :key="item.staffId || item.userId" :label="item.staffName || item.nickName" :value="String(item.staffId || item.userId)" />
-                  </el-select>
+                    <el-select v-model="form.leader" placeholder="请选择" style="width: 100%" clearable filterable @change="handleLeaderChange">
+                      <el-option v-for="item in userOptions" :key="item.staffId || item.userId" :label="(item.staffCode || item.userName ? '(' + (item.staffCode || item.userName) + ') ' : '') + (item.staffName || item.nickName)" :value="String(item.staffId || item.userId)" />
+                    </el-select>
                 </el-form-item>
               </el-col>
               <el-col :span="8">
                 <el-form-item label="产品支持" prop="productSupport">
-                  <el-select v-model="form.productSupport" placeholder="请选择" style="width: 100%" clearable filterable>
-                    <el-option v-for="item in userOptions" :key="item.staffId || item.userId" :label="item.staffName || item.nickName" :value="String(item.staffId || item.userId)" />
-                  </el-select>
+                    <el-select v-model="form.productSupport" placeholder="请选择" style="width: 100%" clearable filterable>
+                      <el-option v-for="item in userOptions" :key="item.staffId || item.userId" :label="(item.staffCode || item.userName ? '(' + (item.staffCode || item.userName) + ') ' : '') + (item.staffName || item.nickName)" :value="String(item.staffId || item.userId)" />
+                    </el-select>
                 </el-form-item>
               </el-col>
             </el-row>
             <el-form-item label="项目名称" prop="projectName">
-              <el-input v-model="form.projectName" placeholder="请输入项目名称" />
+              <el-input v-model="form.projectName" placeholder="请输入项目名称" maxlength="200" show-word-limit />
             </el-form-item>
           </div>
 
@@ -116,18 +116,16 @@
               </el-col>
             </el-row>
             <el-row :gutter="24">
-              <el-col :span="8">
-                <el-form-item label="服务期(年)" prop="standardPeriod">
-                  <el-input v-model="form.standardPeriod" placeholder="请输入">
-                    <template #append>年</template>
-                  </el-input>
-                </el-form-item>
-              </el-col>
-              <el-col :span="8">
-                <el-form-item label="服务时间段" prop="serviceTime">
-                  <el-input v-model="form.serviceTime" placeholder="请输入" />
-                </el-form-item>
-              </el-col>
+               <el-col :span="8">
+                 <el-form-item label="服务期(年)" prop="standardPeriod">
+                   <el-input v-model="form.standardPeriod" placeholder="请输入" maxlength="50" />
+                 </el-form-item>
+               </el-col>
+               <el-col :span="8">
+                 <el-form-item label="服务时间段" prop="serviceTime">
+                   <el-input v-model="form.serviceTime" placeholder="请输入" maxlength="100" />
+                 </el-form-item>
+               </el-col>
               <el-col :span="8">
                 <el-form-item label="入围类型" prop="shortlistedType">
                   <el-select v-model="form.shortlistedType" placeholder="请选择" style="width: 100%" clearable>
@@ -144,16 +142,16 @@
                   </el-select>
                 </el-form-item>
               </el-col>
-              <el-col :span="8">
-                <el-form-item label="招标代理机构" prop="biddingAgency">
-                  <el-input v-model="form.biddingAgency" placeholder="请输入" />
-                </el-form-item>
-              </el-col>
-              <el-col :span="8">
-                <el-form-item label="代理机构联系方式" prop="agencyContact" class="label-nowrap">
-                  <el-input v-model="form.agencyContact" placeholder="请输入" />
-                </el-form-item>
-              </el-col>
+               <el-col :span="8">
+                 <el-form-item label="招标代理机构" prop="biddingAgency">
+                   <el-input v-model="form.biddingAgency" placeholder="请输入" maxlength="100" />
+                 </el-form-item>
+               </el-col>
+               <el-col :span="8">
+                 <el-form-item label="代理机构联系方式" prop="agencyContact" class="label-nowrap">
+                   <el-input v-model="form.agencyContact" placeholder="请输入" maxlength="50" />
+                 </el-form-item>
+               </el-col>
             </el-row>
             <el-form-item label="标期类型" prop="bidPeriodType">
               <el-radio-group v-model="form.bidPeriodType">
@@ -161,9 +159,9 @@
                 <el-radio :value="2">周期性框架</el-radio>
               </el-radio-group>
             </el-form-item>
-            <el-form-item label="招标链接" prop="biddingLink" v-if="form.bidPeriodType === 2">
-              <el-input v-model="form.biddingLink" placeholder="请输入/粘贴链接" />
-            </el-form-item>
+             <el-form-item label="招标链接" prop="biddingLink" v-if="form.bidPeriodType === 2">
+               <el-input v-model="form.biddingLink" placeholder="请输入/粘贴链接" maxlength="500" />
+             </el-form-item>
             <el-form-item label="入围要求" prop="condition">
               <el-input v-model="form.condition" type="textarea" :rows="3" placeholder="请输入" maxlength="200" show-word-limit />
             </el-form-item>
@@ -257,17 +255,33 @@ const rules = reactive({
   customName: [{ required: true, message: '客户名称不能为空', trigger: 'change' }],
   businessType: [{ required: true, message: '项目类型不能为空', trigger: 'change' }],
   projectLevel: [{ required: true, message: '项目类别不能为空', trigger: 'change' }],
-  projectName: [{ required: true, message: '项目名称不能为空', trigger: 'blur' }],
+  projectName: [
+    { required: true, message: '项目名称不能为空', trigger: 'blur' },
+    { max: 200, message: '项目名称不能超过200个字符', trigger: 'blur' }
+  ],
   amount: [{ required: true, message: '金额不能为空', trigger: 'blur' }],
   entryFee: [{ required: true, message: '报名费不能为空', trigger: 'blur' }],
   winningRate: [{ required: true, message: '赢单率不能为空', trigger: 'blur' }],
   signUpDeadline: [{ required: true, message: '报名截止时间不能为空', trigger: 'change' }],
   tenderDeadline: [{ required: true, message: '投标截止时间不能为空', trigger: 'change' }],
-  standardPeriod: [{ required: true, message: '服务期不能为空', trigger: 'blur' }],
+  standardPeriod: [
+    { required: true, message: '服务期不能为空', trigger: 'blur' },
+    { max: 50, message: '服务期不能超过50个字符', trigger: 'blur' }
+  ],
   shortlistedType: [{ required: true, message: '入围类型不能为空', trigger: 'change' }],
   profession: [{ required: true, message: '物资类目不能为空', trigger: 'change' }],
   bidPeriodType: [{ required: true, message: '标期类型不能为空', trigger: 'change' }],
-  condition: [{ required: true, message: '入围要求不能为空', trigger: 'blur' }],
+  condition: [
+    { required: true, message: '入围要求不能为空', trigger: 'blur' },
+    { max: 200, message: '入围要求不能超过200个字符', trigger: 'blur' }
+  ],
+  serviceTime: [{ max: 100, message: '服务时间段不能超过100个字符', trigger: 'blur' }],
+  biddingAgency: [{ max: 100, message: '招标代理机构不能超过100个字符', trigger: 'blur' }],
+  agencyContact: [{ max: 50, message: '代理机构联系方式不能超过50个字符', trigger: 'blur' }],
+  biddingLink: [
+    { pattern: /^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([\/\w .-]*)*\/?$/, message: '请输入正确的招标链接地址', trigger: 'blur' }
+  ],
+  projectDesc: [{ max: 200, message: '项目描述不能超过200个字符', trigger: 'blur' }]
 });
 
 watch(() => props.modelValue, (val) => {

+ 12 - 9
src/views/saleManage/projectSelection/detail.vue

@@ -45,7 +45,7 @@
               <div class="info-block">
                 <div class="info-header">
                   <span class="title">基本信息</span>
-                  <el-button type="primary" class="edit-btn" @click="$emit('edit', detailData)" size="default">
+                  <el-button v-if="!isReadOnly" type="primary" class="edit-btn" @click="$emit('edit', detailData)" size="default">
                     <el-icon style="margin-right: 4px;"><Edit /></el-icon>编辑
                   </el-button>
                 </div>
@@ -104,7 +104,7 @@
 
             <el-tab-pane label="项目联系人" name="contact">
                <div class="tab-toolbar">
-                 <el-dropdown @command="handleContactCommand">
+                 <el-dropdown v-if="!isReadOnly" @command="handleContactCommand">
                    <el-button type="primary">
                      <el-icon style="margin-right: 4px;"><Plus /></el-icon>新建联系人<el-icon class="el-icon--right"><arrow-down /></el-icon>
                    </el-button>
@@ -147,8 +147,8 @@
                   </el-table-column>
                   <el-table-column label="操作" align="center" width="120" fixed="right">
                     <template #default="scope">
-                      <el-button link type="primary" size="small" @click="handleEditContact(scope.row)">编辑</el-button>
-                      <el-button link type="danger" size="small" @click="handleDeleteContact(scope.row)">删除</el-button>
+                      <el-button v-if="!isReadOnly" link type="primary" size="small" @click="handleEditContact(scope.row)">编辑</el-button>
+                      <el-button v-if="!isReadOnly" link type="danger" size="small" @click="handleDeleteContact(scope.row)">删除</el-button>
                     </template>
                   </el-table-column>
                   <template #empty><span class="empty-text">暂无数据</span></template>
@@ -165,7 +165,7 @@
                        <el-radio value="lose">丢单</el-radio>
                      </el-radio-group>
                      <div style="flex:1"></div>
-                     <el-button type="primary" icon="CircleCheck" @click="handleSaveAnalysis">保 存</el-button>
+                     <el-button v-if="!isReadOnly" type="primary" icon="CircleCheck" @click="handleSaveAnalysis">保 存</el-button>
                    </div>
                    <template v-if="analysisForm.resultType === 'win'">
                      <div class="summary-title">赢单总结</div>
@@ -180,7 +180,7 @@
                      </div>
                    </template>
                    <div class="attachment-section-title">附件</div>
-                   <div class="upload-area">
+                   <div v-if="!isReadOnly" class="upload-area">
                      <el-upload
                        :action="uploadFileUrl"
                        :headers="headers"
@@ -197,7 +197,7 @@
                        <el-table-column label="操作" width="120" align="center">
                           <template #default="scope">
                              <el-button link type="primary" @click="downloadFile(scope.row)">下载</el-button>
-                             <el-button link type="danger" @click="handleAnalysisDeleteFile(scope.$index)">删除</el-button>
+                             <el-button v-if="!isReadOnly" link type="danger" @click="handleAnalysisDeleteFile(scope.$index)">删除</el-button>
                           </template>
                        </el-table-column>
                      </el-table>
@@ -207,7 +207,7 @@
             </el-tab-pane>
 
             <el-tab-pane label="附件" name="attachment">
-               <div class="tab-toolbar">
+               <div v-if="!isReadOnly" class="tab-toolbar">
                  <el-upload
                    :action="uploadFileUrl"
                    :headers="headers"
@@ -224,7 +224,7 @@
                  <el-table-column label="操作" align="center" width="120">
                     <template #default="scope">
                       <el-link :href="scope.row.url" :underline="false" target="_blank" type="primary" style="margin-right: 10px;">下载</el-link>
-                      <el-link type="danger" :underline="false" @click="handleDeleteFile(scope.row)">删除</el-link>
+                      <el-link v-if="!isReadOnly" type="danger" :underline="false" @click="handleDeleteFile(scope.row)">删除</el-link>
                     </template>
                  </el-table-column>
                  <template #empty><span class="empty-text">暂无数据</span></template>
@@ -250,6 +250,7 @@
             }" 
             businessType="projectSelection"
             @success="getList"
+            @update-readonly="val => isReadOnly = val"
           />
         </div>
       </div>
@@ -473,6 +474,7 @@ const currentStepIndex = computed(() => {
 });
 
 const visible = ref(false);
+const isReadOnly = ref(true);
 const detailData = ref({});
 const leftActiveTab = ref('info');
 const businessActivityRef = ref(null);
@@ -859,6 +861,7 @@ const handleDeleteFile = (row) => {
 };
 
 const handleStepClick = async (index) => {
+  if (isReadOnly.value) return;
   const targetStatus = String(index + 1);
   const isCancel = targetStatus === String(detailData.value.projectStatus);
   

+ 49 - 35
src/views/saleManage/projectSelection/edit.vue

@@ -59,21 +59,21 @@
               </el-col>
               <el-col :span="8">
                 <el-form-item label="项目负责人" prop="leader">
-                  <el-select v-model="form.leader" placeholder="请选择" style="width: 100%" clearable filterable @change="handleLeaderChange">
-                    <el-option v-for="item in computedUserOptions" :key="item.staffId || item.userId" :label="item.staffName || item.nickName" :value="String(item.staffId || item.userId)" />
-                  </el-select>
+                    <el-select v-model="form.leader" placeholder="请选择" style="width: 100%" clearable filterable @change="handleLeaderChange">
+                      <el-option v-for="item in computedUserOptions" :key="item.staffId || item.userId" :label="(item.staffCode || item.userName ? '(' + (item.staffCode || item.userName) + ') ' : '') + (item.staffName || item.nickName)" :value="String(item.staffId || item.userId)" />
+                    </el-select>
                 </el-form-item>
               </el-col>
               <el-col :span="8">
                 <el-form-item label="产品支持" prop="productSupport">
-                  <el-select v-model="form.productSupport" placeholder="请选择" style="width: 100%" clearable filterable @change="handleProductSupportChange">
-                    <el-option v-for="item in computedProductSupportOptions" :key="item.staffId || item.userId" :label="item.staffName || item.nickName" :value="String(item.staffId || item.userId)" />
-                  </el-select>
+                    <el-select v-model="form.productSupport" placeholder="请选择" style="width: 100%" clearable filterable @change="handleProductSupportChange">
+                      <el-option v-for="item in computedProductSupportOptions" :key="item.staffId || item.userId" :label="(item.staffCode || item.userName ? '(' + (item.staffCode || item.userName) + ') ' : '') + (item.staffName || item.nickName)" :value="String(item.staffId || item.userId)" />
+                    </el-select>
                 </el-form-item>
               </el-col>
             </el-row>
             <el-form-item label="项目名称" prop="projectName">
-              <el-input v-model="form.projectName" placeholder="请输入项目名称" />
+              <el-input v-model="form.projectName" placeholder="请输入项目名称" maxlength="200" show-word-limit />
             </el-form-item>
           </div>
 
@@ -117,18 +117,16 @@
               </el-col>
             </el-row>
             <el-row :gutter="24">
-              <el-col :span="8">
-                <el-form-item label="服务期(年)" prop="standardPeriod">
-                  <el-input v-model="form.standardPeriod" placeholder="请输入">
-                    <template #append>年</template>
-                  </el-input>
-                </el-form-item>
-              </el-col>
-              <el-col :span="8">
-                <el-form-item label="服务时间段" prop="serviceTime">
-                  <el-input v-model="form.serviceTime" placeholder="请输入" />
-                </el-form-item>
-              </el-col>
+               <el-col :span="8">
+                 <el-form-item label="服务期(年)" prop="standardPeriod">
+                   <el-input v-model="form.standardPeriod" placeholder="请输入" maxlength="50" />
+                 </el-form-item>
+               </el-col>
+               <el-col :span="8">
+                 <el-form-item label="服务时间段" prop="serviceTime">
+                   <el-input v-model="form.serviceTime" placeholder="请输入" maxlength="100" />
+                 </el-form-item>
+               </el-col>
               <el-col :span="8">
                 <el-form-item label="入围类型" prop="shortlistedType">
                   <el-select v-model="form.shortlistedType" placeholder="请选择" style="width: 100%" clearable>
@@ -145,16 +143,16 @@
                   </el-select>
                 </el-form-item>
               </el-col>
-              <el-col :span="8">
-                <el-form-item label="招标代理机构" prop="biddingAgency">
-                  <el-input v-model="form.biddingAgency" placeholder="请输入" />
-                </el-form-item>
-              </el-col>
-              <el-col :span="8">
-                <el-form-item label="代理机构联系方式" prop="agencyContact" class="label-nowrap">
-                  <el-input v-model="form.agencyContact" placeholder="请输入" />
-                </el-form-item>
-              </el-col>
+               <el-col :span="8">
+                 <el-form-item label="招标代理机构" prop="biddingAgency">
+                   <el-input v-model="form.biddingAgency" placeholder="请输入" maxlength="100" />
+                 </el-form-item>
+               </el-col>
+               <el-col :span="8">
+                 <el-form-item label="代理机构联系方式" prop="agencyContact" class="label-nowrap">
+                   <el-input v-model="form.agencyContact" placeholder="请输入" maxlength="50" />
+                 </el-form-item>
+               </el-col>
             </el-row>
             <el-form-item label="标期类型" prop="bidPeriodType">
               <el-radio-group v-model="form.bidPeriodType">
@@ -162,9 +160,9 @@
                 <el-radio :value="2">周期性框架</el-radio>
               </el-radio-group>
             </el-form-item>
-            <el-form-item label="招标链接" prop="biddingLink" v-if="form.bidPeriodType === 2">
-              <el-input v-model="form.biddingLink" placeholder="请输入/粘贴链接" />
-            </el-form-item>
+             <el-form-item label="招标链接" prop="biddingLink" v-if="form.bidPeriodType === 2">
+               <el-input v-model="form.biddingLink" placeholder="请输入/粘贴链接" maxlength="500" />
+             </el-form-item>
             <el-form-item label="入围要求" prop="condition">
               <el-input v-model="form.condition" type="textarea" :rows="3" placeholder="请输入" maxlength="500" show-word-limit />
             </el-form-item>
@@ -248,17 +246,33 @@ const rules = reactive({
   customName: [{ required: true, message: '客户名称不能为空', trigger: 'change' }],
   businessType: [{ required: true, message: '项目类型不能为空', trigger: 'change' }],
   projectLevel: [{ required: true, message: '项目类别不能为空', trigger: 'change' }],
-  projectName: [{ required: true, message: '项目名称不能为空', trigger: 'blur' }],
+  projectName: [
+    { required: true, message: '项目名称不能为空', trigger: 'blur' },
+    { max: 200, message: '项目名称不能超过200个字符', trigger: 'blur' }
+  ],
   amount: [{ required: true, message: '金额不能为空', trigger: 'blur' }],
   entryFee: [{ required: true, message: '报名费不能为空', trigger: 'blur' }],
   winningRate: [{ required: true, message: '赢单率不能为空', trigger: 'blur' }],
   signUpDeadline: [{ required: true, message: '报名截止时间不能为空', trigger: 'change' }],
   tenderDeadline: [{ required: true, message: '投标截止时间不能为空', trigger: 'change' }],
-  standardPeriod: [{ required: true, message: '服务期不能为空', trigger: 'blur' }],
+  standardPeriod: [
+    { required: true, message: '服务期不能为空', trigger: 'blur' },
+    { max: 50, message: '服务期不能超过50个字符', trigger: 'blur' }
+  ],
   shortlistedType: [{ required: true, message: '入围类型不能为空', trigger: 'change' }],
   profession: [{ required: true, message: '物资类目不能为空', trigger: 'change' }],
   bidPeriodType: [{ required: true, message: '标期类型不能为空', trigger: 'change' }],
-  condition: [{ required: true, message: '入围要求不能为空', trigger: 'blur' }],
+  condition: [
+    { required: true, message: '入围要求不能为空', trigger: 'blur' },
+    { max: 200, message: '入围要求不能超过200个字符', trigger: 'blur' }
+  ],
+  serviceTime: [{ max: 100, message: '服务时间段不能超过100个字符', trigger: 'blur' }],
+  biddingAgency: [{ max: 100, message: '招标代理机构不能超过100个字符', trigger: 'blur' }],
+  agencyContact: [{ max: 50, message: '代理机构联系方式不能超过50个字符', trigger: 'blur' }],
+  biddingLink: [
+    { pattern: /^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([\/\w .-]*)*\/?$/, message: '请输入正确的招标链接地址', trigger: 'blur' }
+  ],
+  projectDesc: [{ max: 200, message: '项目描述不能超过200个字符', trigger: 'blur' }]
 });
 
 // 【回显增强】处理选项列表中不存在当前项的情况

+ 6 - 6
src/views/saleManage/projectSelection/index.vue

@@ -23,7 +23,7 @@
           <el-col :span="6">
             <el-form-item label="负责人" prop="managerId">
               <el-select v-model="queryParams.managerId" placeholder="请选择" style="width: 100%" clearable filterable>
-                <el-option v-for="item in userOptions" :key="item.staffId || item.userId" :label="item.staffName || item.nickName" :value="String(item.staffId || item.userId)" />
+                <el-option v-for="item in userOptions" :key="item.staffId || item.userId" :label="(item.staffCode || item.userName ? '(' + (item.staffCode || item.userName) + ') ' : '') + (item.staffName || item.nickName)" :value="String(item.staffId || item.userId)" />
               </el-select>
             </el-form-item>
           </el-col>
@@ -47,7 +47,7 @@
           <el-col :span="6">
             <el-form-item label="产品支持" prop="productSupport">
               <el-select v-model="queryParams.productSupport" placeholder="请选择" style="width: 100%" clearable filterable>
-                <el-option v-for="item in userOptions" :key="item.staffId || item.userId" :label="item.staffName || item.nickName" :value="String(item.staffId || item.userId)" />
+                <el-option v-for="item in userOptions" :key="item.staffId || item.userId" :label="(item.staffCode || item.userName ? '(' + (item.staffCode || item.userName) + ') ' : '') + (item.staffName || item.nickName)" :value="String(item.staffId || item.userId)" />
               </el-select>
             </el-form-item>
           </el-col>
@@ -296,9 +296,9 @@
     <el-dialog title="项目转移" v-model="transferVisible" width="500px">
        <el-form label-width="100px">
          <el-form-item label="新负责人" required>
-           <el-select v-model="transferOwner" placeholder="请选择" style="width: 100%" filterable>
-             <el-option v-for="item in userOptions" :key="item.staffId || item.userId" :label="item.staffName || item.nickName" :value="String(item.staffId || item.userId)" />
-           </el-select>
+            <el-select v-model="transferOwner" placeholder="请选择" style="width: 100%" filterable>
+              <el-option v-for="item in userOptions" :key="item.staffId || item.userId" :label="(item.staffCode || item.userName ? '(' + (item.staffCode || item.userName) + ') ' : '') + (item.staffName || item.nickName)" :value="String(item.staffId || item.userId)" />
+            </el-select>
          </el-form-item>
          <el-form-item>
            <el-checkbox v-model="keepAsMember">保留原负责人为团队成员</el-checkbox>
@@ -597,7 +597,7 @@ const handleBatchDelete = () => {
 
 const initOptions = () => {
   listIndustryCategory().then(res => {
-    industryOptions.value = res.data || [];
+    industryOptions.value = res.data || res.rows || [];
   });
   listComStaff({ pageSize: 1000 }).then(r => userOptions.value = r.rows || r.data || []);
   listCompanyOption().then(r => companyOptions.value = r.data);

+ 6 - 3
src/views/visit/plan/add.vue

@@ -11,7 +11,7 @@
               <el-col :span="8">
                 <el-form-item label="拜访人:" prop="visitorId">
                   <el-select v-model="form.visitorId" placeholder="请选择" style="width: 100%" clearable filterable @change="handleStaffChange">
-                <el-option v-for="item in staffOptions" :key="item.staffId" :label="item.staffName" :value="item.staffId" />
+                <el-option v-for="item in staffOptions" :key="item.staffId" :label="(item.staffCode ? '(' + item.staffCode + ') ' : '') + item.staffName" :value="item.staffId" />
                   </el-select>
                 </el-form-item>
               </el-col>
@@ -108,7 +108,7 @@
 
         <el-form-item label="随访人" prop="followPeopleName">
           <el-select v-model="scheduleForm.followPeopleName" placeholder="请选择" style="width: 100%" multiple clearable filterable collapse-tags>
-            <el-option v-for="item in staffOptions" :key="item.staffId" :label="item.staffName" :value="item.staffName" />
+            <el-option v-for="item in staffOptions" :key="item.staffId" :label="(item.staffCode ? '(' + item.staffCode + ') ' : '') + item.staffName" :value="item.staffName" />
           </el-select>
         </el-form-item>
 
@@ -216,7 +216,10 @@ const scheduleRules = {
   customerName: [{ required: true, message: "请选择关联对象", trigger: "change" }],
   callDate: [{ required: true, message: "请选择拜访日期", trigger: "change" }],
   importantLevel: [{ required: true, message: "请选择紧要程度", trigger: "change" }],
-  purposeVisit: [{ required: true, message: "请输入拜访目的", trigger: "blur" }]
+  purposeVisit: [
+    { required: true, message: "请输入拜访目的", trigger: "blur" },
+    { max: 200, message: "不能超过200个字符", trigger: "blur" }
+  ]
 };
 
 /** 选择人员处理 */

+ 6 - 3
src/views/visit/plan/edit.vue

@@ -11,7 +11,7 @@
               <el-col :span="8">
                 <el-form-item label="拜访人:" prop="visitorId">
                   <el-select v-model="form.visitorId" placeholder="请选择" style="width: 100%" clearable filterable @change="handleStaffChange">
-                    <el-option v-for="item in staffOptions" :key="item.staffId" :label="item.staffName + (item.staffCode ? ' (' + item.staffCode + ')' : '')" :value="item.staffId" />
+                    <el-option v-for="item in staffOptions" :key="item.staffId" :label="(item.staffCode ? '(' + item.staffCode + ') ' : '') + item.staffName" :value="item.staffId" />
                   </el-select>
                 </el-form-item>
               </el-col>
@@ -108,7 +108,7 @@
 
         <el-form-item label="随访人:" prop="followPeopleName">
           <el-select v-model="scheduleForm.followPeopleName" placeholder="请选择" style="width: 100%" multiple clearable filterable>
-            <el-option v-for="item in staffOptions" :key="item.staffId" :label="item.staffName + (item.staffCode ? ' (' + item.staffCode + ')' : '')" :value="item.staffName" />
+            <el-option v-for="item in staffOptions" :key="item.staffId" :label="(item.staffCode ? '(' + item.staffCode + ') ' : '') + item.staffName" :value="item.staffName" />
           </el-select>
         </el-form-item>
 
@@ -208,7 +208,10 @@ const scheduleRules = {
   customerName: [{ required: true, message: "请选择关联对象", trigger: "change" }],
   callDate: [{ required: true, message: "请选择拜访日期", trigger: "change" }],
   importantLevel: [{ required: true, message: "请选择紧要程度", trigger: "change" }],
-  purposeVisit: [{ required: true, message: "请输入拜访目的", trigger: "blur" }]
+  purposeVisit: [
+    { required: true, message: "请输入拜访目的", trigger: "blur" },
+    { max: 200, message: "不能超过200个字符", trigger: "blur" }
+  ]
 };
 
 watch(() => props.modelValue, (val) => {

+ 1 - 1
src/views/visit/plan/index.vue

@@ -8,7 +8,7 @@
         </el-form-item>
         <el-form-item label="拜访人" prop="visitorId" class="custom-form-item">
           <el-select v-model="queryParams.visitorId" placeholder="请选择" clearable filterable style="width: 200px">
-            <el-option v-for="item in staffOptions" :key="item.staffId" :label="item.staffName" :value="item.staffId" />
+            <el-option v-for="item in staffOptions" :key="item.staffId" :label="(item.staffCode ? '(' + item.staffCode + ') ' : '') + item.staffName" :value="item.staffId" />
           </el-select>
         </el-form-item>
         <el-form-item label="计划状态" prop="status" class="custom-form-item">

+ 1 - 1
src/views/visit/record/index.vue

@@ -182,7 +182,7 @@ onMounted(() => {
       });
       return result;
     };
-    industryOptions.value = flatten(res.data || []);
+    industryOptions.value = flatten(res.data || res.rows || []);
   });
 });
 </script>

+ 6 - 3
src/views/visit/routine/add.vue

@@ -31,7 +31,7 @@
 
             <el-form-item label="拜访人" prop="callPeopleNo">
               <el-select v-model="form.callPeopleNo" placeholder="请选择" style="width: 100%" clearable filterable @change="handleStaffChange">
-                <el-option v-for="item in staffOptions" :key="item.staffId" :label="item.staffName" :value="item.staffId" />
+                <el-option v-for="item in staffOptions" :key="item.staffId" :label="(item.staffCode ? '(' + item.staffCode + ') ' : '') + item.staffName" :value="item.staffId" />
               </el-select>
             </el-form-item>
 
@@ -52,7 +52,7 @@
 
             <el-form-item label="随访人" prop="followPeopleName">
               <el-select v-model="form.followPeopleName" placeholder="请选择" style="width: 100%" clearable filterable multiple collapse-tags>
-                <el-option v-for="item in staffOptions" :key="item.staffId" :label="item.staffName" :value="item.staffName" />
+                <el-option v-for="item in staffOptions" :key="item.staffId" :label="(item.staffCode ? '(' + item.staffCode + ') ' : '') + item.staffName" :value="item.staffName" />
               </el-select>
             </el-form-item>
 
@@ -118,7 +118,10 @@ const rules = reactive({
   objectName: [{ required: true, message: "关联对象不能为空", trigger: "change" }],
   callDate: [{ required: true, message: "拜访日期不能为空", trigger: "change" }],
   importantLevel: [{ required: true, message: "重要级别不能为空", trigger: "change" }],
-  purposeVisit: [{ required: true, message: "拜访目的不能为空", trigger: "blur" }]
+  purposeVisit: [
+    { required: true, message: "拜访目的不能为空", trigger: "blur" },
+    { max: 200, message: "不能超过200个字符", trigger: "blur" }
+  ]
 });
 
 const typeLabel = computed(() => {

+ 1 - 1
src/views/visit/routine/detail.vue

@@ -104,7 +104,7 @@
           <el-col :span="12">
             <el-form-item label="随访人:" prop="accompanyPerson">
               <el-select v-model="recordForm.accompanyPerson" placeholder="请选择" style="width: 100%" clearable filterable>
-                 <el-option v-for="item in staffOptions" :key="item.staffId" :label="`${item.staffName}${item.staffCode ? ' (' + item.staffCode + ')' : ''}`" :value="item.staffName" />
+                  <el-option v-for="item in staffOptions" :key="item.staffId" :label="(item.staffCode ? '(' + item.staffCode + ') ' : '') + item.staffName" :value="item.staffName" />
               </el-select>
             </el-form-item>
           </el-col>

+ 6 - 3
src/views/visit/routine/edit.vue

@@ -23,7 +23,7 @@
 
         <el-form-item label="拜访人:" prop="callPeopleNo">
           <el-select v-model="form.callPeopleNo" placeholder="请选择拜访人" style="width: 100%" clearable filterable @change="handleStaffChange">
-            <el-option v-for="item in staffOptions" :key="item.staffId" :label="`${item.staffName}${item.staffCode ? ' (' + item.staffCode + ')' : ''}`" :value="item.staffId" />
+            <el-option v-for="item in staffOptions" :key="item.staffId" :label="(item.staffCode ? '(' + item.staffCode + ') ' : '') + item.staffName" :value="item.staffId" />
           </el-select>
         </el-form-item>
 
@@ -49,7 +49,7 @@
 
         <el-form-item label="随访人:" prop="followPeopleName">
           <el-select v-model="form.followPeopleName" placeholder="请选择" style="width: 100%" clearable filterable>
-            <el-option v-for="item in staffOptions" :key="item.staffId" :label="`${item.staffName}${item.staffCode ? ' (' + item.staffCode + ')' : ''}`" :value="item.staffName" />
+            <el-option v-for="item in staffOptions" :key="item.staffId" :label="(item.staffCode ? '(' + item.staffCode + ') ' : '') + item.staffName" :value="item.staffName" />
           </el-select>
         </el-form-item>
 
@@ -113,7 +113,10 @@ const rules = reactive({
   objectName: [{ required: true, message: "关联对象不能为空", trigger: "change" }],
   callDate: [{ required: true, message: "拜访日期不能为空", trigger: "change" }],
   importantLevel: [{ required: true, message: "重要级别不能为空", trigger: "change" }],
-  purposeVisit: [{ required: true, message: "拜访目的不能为空", trigger: "blur" }]
+  purposeVisit: [
+    { required: true, message: "拜访目的不能为空", trigger: "blur" },
+    { max: 200, message: "不能超过200个字符", trigger: "blur" }
+  ]
 });
 
 const typeLabel = computed(() => {

+ 1 - 1
src/views/visit/routine/index.vue

@@ -228,7 +228,7 @@ const handleDelete = (row) => {
 
 const getOptions = () => {
   listComStaff({ pageSize: 1000 }).then(res => { staffOptions.value = res.rows || res.data || []; });
-  listIndustryCategory({ pageSize: 1000 }).then(res => { industryOptions.value = res.rows || []; });
+  listIndustryCategory({ pageSize: 1000 }).then(res => { industryOptions.value = res.data || res.rows || []; });
   listCustomerInfo({ pageNum: 1, pageSize: 500, isHighSeas: 'all' }).then(res => { customerList.value = res.rows || []; });
 };