Sfoglia il codice sorgente

前端代码修改

沐梦. 1 mese fa
parent
commit
44dea796a1

+ 9 - 0
src/api/customer/teamMember.js

@@ -17,6 +17,15 @@ export function addTeamMember(data) {
   })
 }
 
+// 修改团队成员
+export function updateTeamMember(data) {
+  return request({
+    url: '/customer/teamMember',
+    method: 'put',
+    data: data
+  })
+}
+
 // 删除团队成员
 export function delTeamMember(ids) {
   return request({

+ 161 - 22
src/views/saleManage/leads/index.vue

@@ -126,7 +126,7 @@
         <el-table-column label="采购内容" align="center" prop="purchaseContent" min-width="150" show-overflow-tooltip />
         <el-table-column label="所属公司" align="center" min-width="150" show-overflow-tooltip>
           <template #default="scope">
-            {{ companyOptions.find(i => i.companyCode === scope.row.companyNo)?.companyName || scope.row.companyNo }}
+            {{ companyOptions.find(i => i.companyCode == scope.row.companyNo)?.companyName || scope.row.companyNo }}
           </template>
         </el-table-column>
         <el-table-column label="客户名称" align="center" prop="customerName" min-width="200" show-overflow-tooltip />
@@ -137,7 +137,7 @@
         </el-table-column>
         <el-table-column label="部门" align="center" min-width="150" show-overflow-tooltip>
           <template #default="scope">
-            {{ scope.row.projectManagerDepartment || '未分配' }}
+            {{ scope.row.deptName }}
           </template>
         </el-table-column>
         <el-table-column label="金额(万)" align="center" prop="projectBudget" width="100" sortable>
@@ -214,10 +214,17 @@
     </el-dialog>
 
     <!-- 弹窗2:批量认领 -->
-    <el-dialog title="您正在认领销售线索,请分配销售线索认领信息" v-model="claimVisible" width="550px" append-to-body>
-      <el-form label-width="110px" style="padding: 20px 0;">
+    <el-dialog v-model="claimVisible" width="550px" append-to-body :show-close="false" class="custom-claim-dialog">
+      <template #header>
+        <div class="custom-dialog-header">
+          <span class="header-title">您正在认领销售线索,请分配销售线索认领信息</span>
+          <el-icon class="close-icon" @click="claimVisible = false"><Close /></el-icon>
+        </div>
+      </template>
+
+      <el-form label-width="auto" label-position="left" class="claim-custom-form">
         <el-form-item label="项目负责人:" required>
-          <el-select v-model="claimForm.newManager" placeholder="请选择" style="width: 100%">
+          <el-select v-model="claimForm.newManager" placeholder="请选择" style="width: 320px">
             <el-option
               v-for="item in userOptions"
               :key="item.staffId"
@@ -226,20 +233,23 @@
             />
           </el-select>
         </el-form-item>
-        <el-form-item label="保留已有项目负责人:" label-width="160px" required class="label-nowrap">
+        <el-form-item label="保留已有项目负责人:" required>
            <el-radio-group v-model="claimForm.keepOldManager">
               <el-radio :label="true">是</el-radio>
               <el-radio :label="false">否</el-radio>
            </el-radio-group>
         </el-form-item>
-        <div class="claim-tip">
-          <el-icon><InfoFilled /></el-icon> 销售线索认领后将转到项目商机。
+        
+        <div class="claim-info-tip">
+          <el-icon class="info-icon"><InfoFilled /></el-icon>
+          <span class="tip-text">销售线索认领后将转到项目商机。</span>
         </div>
       </el-form>
+
       <template #footer>
-        <div class="dialog-footer">
-          <el-button type="primary" @click="submitClaim">确认</el-button>
-          <el-button @click="claimVisible = false">取消</el-button>
+        <div class="custom-dialog-footer">
+          <el-button type="primary" class="confirm-btn" @click="submitClaim">确认</el-button>
+          <el-button class="cancel-btn" @click="claimVisible = false">取消</el-button>
         </div>
       </template>
     </el-dialog>
@@ -681,16 +691,16 @@
                 </div>
                 <div class="info-grid">
                   <div class="info-item"><span class="info-label">行业</span><span class="info-value">{{ findIndustryName(detailData.profession) }}</span></div>
-                  <div class="info-item"><span class="info-label">部门</span><span class="info-value">{{ findDeptName(detailData.deptNo) }}</span></div>
+                  <div class="info-item"><span class="info-label">部门</span><span class="info-value">{{ detailData.deptName }}</span></div>
                   <div class="info-item"><span class="info-label">营销活动</span><span class="info-value">{{ detailData.marketingActivityName }}</span></div>
                 </div>
                 <div class="info-grid">
                   <div class="info-item"><span class="info-label">金额(万)</span><span class="info-value">{{ Number(detailData.projectBudget || 0).toFixed(2) }}</span></div>
                   <div class="info-item"><span class="info-label">赢单率(%)</span><span class="info-value">{{ detailData.winRate ? detailData.winRate + '%' : '' }}</span></div>
-                  <div class="info-item"><span class="info-label">立项时间</span><span class="info-value">{{ parseTime(detailData.expectedCompletionTime, '{y}-{m}-{d}') }}</span></div>
+                  <div class="info-item"><span class="info-label">立项时间</span><span class="info-value">{{ parseTime(detailData.approvalDate, '{y}-{m}-{d}') }}</span></div>
                 </div>
                 <div class="info-grid">
-                  <div class="info-item"><span class="info-label">成时间</span><span class="info-value">{{ parseTime(detailData.approvalDate, '{y}-{m}-{d}') }}</span></div>
+                  <div class="info-item"><span class="info-label">预计完成时间</span><span class="info-value">{{ parseTime(detailData.expectedCompletionTime, '{y}-{m}-{d}') }}</span></div>
                   <div class="info-item"><span class="info-label">项目状态</span><span class="info-value"><dict-tag :options="saleStatusOptions" :value="detailData.status" /></span></div>
                   <div class="info-item"></div> <!-- 占位 -->
                 </div>
@@ -1089,7 +1099,10 @@
         
         <!-- 编辑人员姓名显示 -->
         <el-form-item label="人员姓名:" v-if="memberEditVisible">
-          <span style="color: #333; font-weight: 500;">{{ memberEditForm.staffName }}{{ memberEditForm.deptName ? '/' + memberEditForm.deptName : '' }}</span>
+          <div style="display: flex; align-items: center; gap: 8px;">
+            <span style="color: #333; font-weight: 500;">{{ memberEditForm.staffName }}{{ memberEditForm.deptName ? '/' + memberEditForm.deptName : '' }}</span>
+            <el-tag v-if="memberEditForm.izManager === 1" size="small" type="success" effect="plain" class="leader-tag">负责人</el-tag>
+          </div>
         </el-form-item>
 
         <el-form-item label="成员角色:">
@@ -1308,6 +1321,7 @@ const detailData = reactive({
   status: undefined,
   profession: undefined,
   deptNo: undefined,
+  deptName: undefined, // 显式定义字段
   marketingActivityName: undefined,
   infoSource: undefined,
   projectLevel: undefined,
@@ -1657,8 +1671,9 @@ const getCurrentUserStaffId = () => {
 /** 单条认领 - 弹出认领弹窗 */
 const handleClaim = (row) => {
   claimSingleRow.value = row;
-  claimForm.newManager = getCurrentUserStaffId();
-  claimForm.keepOldManager = true;
+  // 默认不选中当前人,强制用户选择(贴合图片“请选择”状态)
+  claimForm.newManager = undefined; 
+  claimForm.keepOldManager = false; // 默认为“否”,用户可手动改
   claimVisible.value = true;
 };
 
@@ -1668,8 +1683,8 @@ const handleBatchClaim = () => {
     return;
   }
   claimSingleRow.value = null; // 批量认领模式
-  claimForm.newManager = getCurrentUserStaffId();
-  claimForm.keepOldManager = true;
+  claimForm.newManager = undefined;
+  claimForm.keepOldManager = false;
   claimVisible.value = true;
 };
 
@@ -1686,7 +1701,8 @@ const submitTransfer = async () => {
     await transferLeads({ 
       ids: ids, 
       leader: transferForm.newOwner, 
-      leaderName: leaderName 
+      leaderName: leaderName,
+      keepOldManager: transferForm.keepAsMember
     });
     proxy.$modal.msgSuccess("转移成功");
     transferVisible.value = false;
@@ -1978,13 +1994,30 @@ const handleUpdate = (row) => {
   reset();
   const id = row.id;
   getLeads(id).then(response => {
-    Object.assign(form, response.data);
+    const data = response.data;
+    Object.assign(form, {
+      ...data,
+      companyNo: data.companyNo ? String(data.companyNo) : undefined,
+      customerNo: data.customerNo ? String(data.customerNo) : undefined,
+      leader: data.leader ? String(data.leader) : undefined,
+      projectLevel: data.projectLevel ? String(data.projectLevel) : undefined,
+      procurementMethod: data.procurementMethod ? String(data.procurementMethod) : undefined,
+      infoSource: data.infoSource ? String(data.infoSource) : undefined,
+      deptNo: data.deptNo ? String(data.deptNo) : undefined,
+      profession: data.profession ? String(data.profession) : undefined
+    });
     drawerTitle.value = "修改销售线索";
     drawerOpen.value = true;
   });
 };
 
 const handleDetail = (row) => {
+  // 重置详情数据,防止旧数据残留
+  Object.keys(detailData).forEach(key => {
+    if (key === 'fileList') detailData[key] = [];
+    else detailData[key] = undefined;
+  });
+  
   detailLoading.value = true;
   detailDrawerOpen.value = true;
   activeLeftTab.value = 'info';
@@ -1992,6 +2025,15 @@ const handleDetail = (row) => {
   getLeads(row.id).then(response => {
     const data = response.data || {};
     Object.assign(detailData, data);
+
+    // 强行根据负责人同步部门名称,确保展示一致
+    if (detailData.leader && userOptions.value.length > 0) {
+      const staff = userOptions.value.find(s => s.staffId === detailData.leader);
+      if (staff && staff.deptId) {
+        detailData.deptName = findDeptName(staff.deptId);
+      }
+    }
+
     // 从API加载子项数据
     loadTeamMembers(data.id);
     loadFollowupList();
@@ -2383,7 +2425,7 @@ const handleEditFromDetail = () => {
 /** 查找公司名称 */
 const findCompanyName = (companyNo) => {
   if (!companyNo) return '';
-  return companyOptions.value.find(i => i.companyCode === companyNo)?.companyName || companyNo || '';
+  return companyOptions.value.find(i => i.companyCode == companyNo)?.companyName || companyNo || '';
 };
 
 /** 查找行业名称 */
@@ -2417,6 +2459,8 @@ const handleMemberEdit = (member) => {
   editingMemberIndex = teamMembers.value.indexOf(member);
   memberEditForm.staffId = member.userNo || member.staffId;
   memberEditForm.staffName = member.realName;
+  memberEditForm.deptName = member.deptName;
+  memberEditForm.izManager = member.izManager;
   // 用 updateAccredit 整型回填表单(新字典 0/1)
   memberEditForm.role = member.roleCode || '';
   memberEditForm.permission = String(member.updateAccredit ?? '0');
@@ -3772,5 +3816,100 @@ const getProjectRoleList = () => {
     }
   }
 }
+// 认领弹窗自定义样式
+.custom-claim-dialog {
+  :deep(.el-dialog__header) {
+    padding: 0;
+    margin: 0;
+  }
+  
+  :deep(.el-dialog__body) {
+    padding: 30px 40px;
+  }
+
+  .custom-dialog-header {
+    padding: 15px 24px;
+    border-bottom: 1px solid #f2f3f5;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+
+    .header-title {
+      font-size: 16px;
+      font-weight: normal; // 取消加粗
+      color: #333;
+    }
+
+    .close-icon {
+      font-size: 20px;
+      color: #ff6a00; // 橙色关闭按钮
+      cursor: pointer;
+      transition: transform 0.2s;
+      &:hover { transform: scale(1.1); }
+    }
+  }
+
+  .claim-custom-form {
+    margin-top: 10px;
+
+    :deep(.el-form-item) {
+      margin-bottom: 24px;
+    }
+
+    :deep(.el-form-item__label) {
+      font-size: 14px;
+      color: #333;
+      padding-right: 12px;
+      white-space: nowrap; // 禁止换行
+      font-weight: normal; // 确保标签也不加粗
+      &::before {
+        color: #ff4d4f !important;
+      }
+    }
+
+    .claim-info-tip {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      margin-left: 20px;
+      margin-top: -10px;
+      
+      .info-icon {
+        font-size: 16px;
+        color: #94a3b8;
+      }
+      
+      .tip-text {
+        font-size: 14px;
+        color: #64748b;
+      }
+    }
+  }
+
+  .custom-dialog-footer {
+    display: flex;
+    justify-content: flex-end;
+    gap: 12px;
+    padding: 0 20px 20px 0;
+
+    .confirm-btn {
+      background-color: #3b82f6;
+      border-color: #3b82f6;
+      height: 36px;
+      padding: 0 24px;
+      font-size: 14px;
+      font-weight: normal; // 取消加粗
+    }
+
+    .cancel-btn {
+      height: 36px;
+      padding: 0 24px;
+      font-size: 14px;
+      color: #333;
+      border-color: #dcdfe6;
+      font-weight: normal; // 取消加粗
+    }
+  }
+}
 </style>
 

+ 28 - 5
src/views/saleManage/opportunity/DetailDrawer.vue

@@ -60,7 +60,7 @@
                   <el-descriptions-item label="归属公司:">{{ detailData.companyName }}</el-descriptions-item>
                   <el-descriptions-item label="客户名称:" :span="2">{{ detailData.customerName }}</el-descriptions-item>
                   
-                  <el-descriptions-item label="行业:">{{ detailData.industry }}</el-descriptions-item>
+                  <el-descriptions-item label="行业:">{{ findIndustryName(detailData.industry) }}</el-descriptions-item>
                   <el-descriptions-item label="部门:">{{ detailData.deptName }}</el-descriptions-item>
                   <el-descriptions-item label="金额(万):">{{ detailData.projectBudget }}</el-descriptions-item>
                   
@@ -69,7 +69,7 @@
                   <el-descriptions-item label="截止时间:">{{ proxy.parseTime(detailData.expectedCompletionTime, '{y}-{m}-{d}') }}</el-descriptions-item>
                   
                   <el-descriptions-item label="项目状态:">{{ getSaleStatusLabel(detailData.status) }}</el-descriptions-item>
-                  <el-descriptions-item label="营销活动:" :span="2">{{ detailData.activityNo }}</el-descriptions-item>
+                  <el-descriptions-item label="营销活动:" :span="2">{{ detailData.marketingActivityName || detailData.activityNo }}</el-descriptions-item>
                 </el-descriptions>
               </div>
 
@@ -506,9 +506,12 @@
         <!-- 编辑模式 -->
         <template v-else>
           <el-form-item label="人员姓名:">
-            <span style="color: #333; font-weight: normal;">
-              {{ teamMemberForm.memberName || teamMemberForm.staffName || teamMemberForm.realName }}{{ teamMemberForm.deptName ? '/' + teamMemberForm.deptName : '' }}
-            </span>
+            <div style="display: flex; align-items: center; gap: 8px;">
+              <span style="color: #333; font-weight: normal;">
+                {{ teamMemberForm.memberName || teamMemberForm.staffName || teamMemberForm.realName }}{{ teamMemberForm.deptName ? '/' + teamMemberForm.deptName : '' }}
+              </span>
+              <el-tag v-if="teamMemberForm.izManager === 1" size="small" type="success" effect="plain" class="leader-tag">负责人</el-tag>
+            </div>
           </el-form-item>
         </template>
 
@@ -604,6 +607,7 @@ const filteredTeamMembers = computed(() => {
   });
 });
 
+const industryOptions = ref([]);
 const { proxy } = getCurrentInstance();
 
 // 跟进记录
@@ -736,6 +740,7 @@ const open = async (row) => {
       getVisitTypeList();
       getUserList();
       getRoleOptions();
+      getIndustryList();
     }
   } catch (error) {}
 };
@@ -775,6 +780,7 @@ const handleEditMember = (item) => {
   teamMemberForm.realName = item.realName;
   teamMemberForm.deptName = item.deptName;
   teamMemberForm.roleName = item.roleName;
+  teamMemberForm.izManager = item.izManager;
   teamMemberForm.updateAccredit = item.updateAccredit;
   teamMemberDialogOpen.value = true;
 };
@@ -970,6 +976,16 @@ const getUserList = async () => {
   }
 };
 
+/** 查询行业分类列表 */
+const getIndustryList = async () => {
+  try {
+    const res = await listIndustryCategory();
+    industryOptions.value = res.data || [];
+  } catch (error) {
+    console.error(error);
+  }
+};
+
 /** 新建跟进记录 */
 const handleNewFollowup = () => {
   if (followupFormRef.value) {
@@ -1169,6 +1185,13 @@ const findProcurementMethodName = (method) => {
   return item ? item.dictLabel : method;
 };
 
+const findIndustryName = (val) => {
+  if (!val) return '';
+  if (!industryOptions.value || industryOptions.value.length === 0) return val;
+  const item = industryOptions.value.find(i => String(i.id) === String(val));
+  return item ? item.industryCategoryName : val;
+};
+
 defineExpose({
   open
 });

File diff suppressed because it is too large
+ 618 - 143
src/views/saleManage/platformSelection/DetailDrawer.vue


+ 74 - 0
src/views/saleManage/platformSelection/FollowUpDialog.vue

@@ -0,0 +1,74 @@
+<template>
+  <el-dialog v-model="visible" title="新建跟进记录" width="600px" append-to-body>
+    <el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
+      <el-form-item label="拜访方式" prop="callTypeCode">
+        <el-select v-model="form.callTypeCode" placeholder="请选择">
+          <el-option label="电话拜访" value="phone" />
+          <el-option label="上门拜访" value="visit" />
+          <el-option label="邮件" value="email" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="拜访日期" prop="callDate">
+        <el-date-picker v-model="form.callDate" type="date" value-format="YYYY-MM-DD" style="width:100%" />
+      </el-form-item>
+      <el-form-item label="跟进内容" prop="followUpCondition">
+        <el-input v-model="form.followUpCondition" type="textarea" :rows="4" placeholder="请输入跟进内容" />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button @click="visible = false">取消</el-button>
+        <el-button type="primary" @click="handleSubmit">确定</el-button>
+      </div>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup>
+import { ref, computed } from 'vue';
+
+const props = defineProps({
+  modelValue: Boolean,
+  projectId: [String, Number],
+  customName: String
+});
+
+const emit = defineEmits(['update:modelValue', 'success']);
+
+const visible = computed({
+  get: () => props.modelValue,
+  set: (val) => emit('update:modelValue', val)
+});
+
+const form = ref({
+  callTypeCode: 'visit',
+  callDate: new Date().toISOString().split('T')[0],
+  followUpCondition: ''
+});
+
+const rules = {
+  callTypeCode: [{ required: true, message: '拜访方式不能为空', trigger: 'change' }],
+  followUpCondition: [{ required: true, message: '跟进内容不能为空', trigger: 'blur' }]
+};
+
+const formRef = ref(null);
+
+const handleSubmit = async () => {
+  if (!formRef.value) return;
+  await formRef.value.validate(async (valid) => {
+    if (valid) {
+      // 调用保存 API
+      emit('success');
+      visible.value = false;
+    }
+  });
+};
+</script>
+
+<style scoped>
+.dialog-footer {
+  display: flex;
+  justify-content: flex-end;
+  gap: 12px;
+}
+</style>

+ 105 - 0
src/views/saleManage/platformSelection/MemberDialog.vue

@@ -0,0 +1,105 @@
+<template>
+  <el-dialog v-model="visible" :title="isEdit ? '编辑团队成员' : '添加团队成员'" width="500px" append-to-body :close-on-click-modal="false">
+    <el-form :model="form" ref="formRef" label-width="90px">
+      <el-form-item v-if="!isEdit" label="添加人员:" prop="memberId">
+        <el-select v-model="form.memberId" placeholder="请选择" filterable style="width: 100%">
+          <el-option v-for="item in userOptions" :key="item.userId || item.staffId" :label="item.nickName || item.staffName" :value="String(item.userId || item.staffId)" />
+        </el-select>
+      </el-form-item>
+      <el-form-item v-else label="人员姓名:">
+        <span>{{ form.memberName }}</span>
+      </el-form-item>
+      <el-form-item label="成员角色:" prop="role">
+        <el-select v-model="form.role" placeholder="请选择" style="width: 100%">
+          <el-option label="负责人" :value="1" />
+          <el-option label="业务负责人" :value="2" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="权限:">
+        <el-checkbox v-if="!isEdit" v-model="form.hasPermission">分配修改权限</el-checkbox>
+        <el-radio-group v-else v-model="form.permission">
+          <el-radio :value="0">仅查看</el-radio>
+          <el-radio :value="1">修改权限</el-radio>
+        </el-radio-group>
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button type="primary" @click="handleSubmit">确认</el-button>
+        <el-button @click="visible = false">取消</el-button>
+      </div>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup>
+import { ref, computed, watch } from 'vue';
+
+const props = defineProps({
+  modelValue: Boolean,
+  projectId: [String, Number],
+  memberData: Object
+});
+
+const emit = defineEmits(['update:modelValue', 'success']);
+
+const visible = computed({
+  get: () => props.modelValue,
+  set: (val) => emit('update:modelValue', val)
+});
+
+const isEdit = computed(() => !!props.memberData);
+
+const form = ref({
+  memberId: undefined,
+  memberName: '',
+  role: 2,
+  permission: 1,
+  hasPermission: true
+});
+
+watch(() => props.memberData, (val) => {
+  if (val && Object.keys(val).length > 0) {
+    form.value = {
+      memberId: val.id || val.userId || val.staffId,
+      memberName: val.memberName || val.nickName || val.staffName || '',
+      role: val.izManager === 1 ? 1 : 2,
+      permission: val.permission ?? 1,
+      hasPermission: true
+    };
+  } else {
+    form.value = { memberId: undefined, memberName: '', role: 2, permission: 1, hasPermission: true };
+  }
+}, { immediate: true });
+
+const rules = computed(() => ({
+  memberId: [{ required: !isEdit.value, message: '请选择成员', trigger: 'change' }],
+  role: [{ required: true, message: '请选择角色', trigger: 'change' }]
+}));
+
+const formRef = ref(null);
+const userOptions = ref([]);
+
+const handleSubmit = async () => {
+  if (!formRef.value) return;
+  await formRef.value.validate(async (valid) => {
+    if (valid) {
+      const submitData = { ...form.value };
+      if (!isEdit.value) {
+        submitData.permission = submitData.hasPermission ? 1 : 0;
+      }
+      delete submitData.hasPermission;
+      emit('success', submitData);
+      visible.value = false;
+    }
+  });
+};
+</script>
+
+<style scoped>
+.dialog-footer {
+  display: flex;
+  justify-content: flex-end;
+  gap: 12px;
+}
+</style>

+ 9 - 31
src/views/saleManage/platformSelection/index.vue

@@ -116,7 +116,6 @@
           <div class="tab-item" :class="{ current: activeTab === 'join' }" @click="handleTabChange('join')">我参与的年度入围</div>
           <div class="tab-item" :class="{ current: activeTab === 'all' }" @click="handleTabChange('all')">
             全部年度入围
-            <div v-if="totalCountBadge > 0" class="total-badge">{{ totalCountBadge > 99 ? '99+' : totalCountBadge }}</div>
           </div>
         </div>
         <div class="summary-info">
@@ -242,7 +241,7 @@ const dateRange = ref([]);
 const multipleSelection = ref([]);
 const totalAmount = ref(0);
 const activeTab = ref('all');
-const totalCountBadge = ref(0);
+
 
 // 转移弹窗状态
 const transferVisible = ref(false);
@@ -288,7 +287,9 @@ const drawerOptions = reactive({
   type: [],
   shortlisted: [],
   profession: [],
-  industryList: []
+  industryList: [],
+  material: [],
+  status: []
 });
 
 /** 查询列表 */
@@ -300,9 +301,7 @@ const getList = async () => {
     total.value = res.total || 0;
     totalAmount.value = dataList.value.reduce((sum, item) => sum + (Number(item.amount) || 0), 0);
 
-    // 获取全部数量角标
-    const allRes = await listPlatformSelection({ pageNum: 1, pageSize: 1 });
-    totalCountBadge.value = allRes.total || 0;
+
   } catch (err) {
     console.error(err);
   } finally {
@@ -327,18 +326,8 @@ const resetQuery = () => { dateRange.value = []; proxy.resetForm("queryRef"); ha
 const handleSelectionChange = (selection) => { multipleSelection.value = selection; };
 
 const handleAdd = () => {
-  drawerForm.value = {
-    projectStatus: 1,
-    bidPeriodType: 1,
-    winningRate: 0,
-    amount: 0,
-    entryFee: 0,
-    bidBond: 0,
-    standardPeriod: 0
-  };
-  drawerIsEdit.value = true;
+  drawerId.value = null;
   drawerVisible.value = true;
-  drawerActiveTab.value = 'info';
 };
 
 const handleUpdate = (row) => {
@@ -407,9 +396,11 @@ const initData = () => {
   });
   listCommonDict('J0001').then(res => {
     statusOptions.value = res.data;
+    drawerOptions.status = res.data;
   });
   listCommonDict('ZBPL0001').then(res => {
     drawerOptions.profession = res.data;
+    drawerOptions.material = res.data;
   });
   listIndustryCategory().then(res => {
     drawerOptions.industryList = res.data;
@@ -516,20 +507,7 @@ onMounted(() => {
           border-color: var(--el-color-primary);
         }
 
-        .total-badge {
-          background: #f53f3f;
-          color: #fff;
-          font-size: 11px;
-          padding: 0 6px;
-          border-radius: 10px;
-          height: 18px;
-          line-height: 18px;
-          position: absolute;
-          top: -10px;
-          right: -10px;
-          box-shadow: 0 2px 4px rgba(245, 63, 63, 0.3);
-          z-index: 10;
-        }
+
       }
     }
 

+ 158 - 21
src/views/saleManage/projectSelection/ProjectSelectionDrawer.vue

@@ -63,7 +63,7 @@
                   </div>
                   <div class="info-item">
                     <span class="label">部门</span>
-                    <span class="value">{{ form.deptNo || '' }}</span>
+                    <span class="value">{{ deptName }}</span>
                   </div>
                   <div class="info-item">
                     <span class="label">项目负责人</span>
@@ -288,13 +288,84 @@
                     </el-select>
                   </div>
                 </div>
-                <div class="record-list">
+                <div class="record-list custom-scroll">
                    <div v-if="!(form.followRecords && form.followRecords.length)" class="tab-empty">
                      <el-empty description="暂无跟进记录" :image-size="60" />
                    </div>
-                   <div v-else class="log-item" v-for="(r, i) in form.followRecords" :key="i">
-                     <div class="log-time">{{ r.createTime }}</div>
-                     <div class="log-content">{{ r.content }}</div>
+                   <div v-else class="timeline-container">
+                     <div class="record-card-wrapper" v-for="(r, i) in form.followRecords" :key="i">
+                       <!-- 时间轴点与时间 -->
+                       <div class="timeline-header">
+                         <div class="timeline-dot"></div>
+                         <span class="timeline-time">{{ r.visitDate || r.createTime }}</span>
+                       </div>
+                       
+                       <!-- 内容卡片 -->
+                       <div class="record-card">
+                         <div class="card-top">
+                           <div class="user-info">
+                             <el-avatar :size="32" class="user-avatar">{{ (r.visitorName || r.createBy || '').charAt(0) }}</el-avatar>
+                             <div class="publish-info">
+                               <span class="user-name">{{ r.visitorName || r.createBy }}</span>
+                               <span class="publish-text">发布了条{{ options.visitType.find(o => String(o.dictValue) === String(r.visitType))?.dictLabel || '跟进' }}:</span>
+                             </div>
+                           </div>
+                           <el-icon class="expand-icon"><ArrowDown /></el-icon>
+                         </div>
+
+                         <div class="card-content">
+                           <div class="detail-row">
+                             <span class="label">客户:</span>
+                             <span class="value">{{ form.customerName }}</span>
+                           </div>
+                           <div class="detail-row">
+                             <span class="label">拜访目的:</span>
+                             <span class="value">{{ r.purpose || '--' }}</span>
+                           </div>
+                           <div class="detail-row">
+                             <span class="label">跟进情况:</span>
+                             <span class="value">{{ r.progress || r.content || '--' }}</span>
+                           </div>
+                           
+                           <!-- 记录图片 -->
+                           <div class="image-section" v-if="r.imageList && r.imageList.length">
+                             <span class="label">记录图片:</span>
+                             <div class="image-list">
+                               <el-image 
+                                 v-for="(img, idx) in r.imageList" 
+                                 :key="idx"
+                                 :src="img" 
+                                 :preview-src-list="r.imageList"
+                                 class="record-img"
+                                 fit="cover"
+                               />
+                             </div>
+                           </div>
+
+                           <div class="update-time-row">
+                             <span class="label">更新时间:</span>
+                             <span class="value">{{ r.updateTime || r.visitDate || r.createTime }}</span>
+                           </div>
+                         </div>
+
+                         <!-- 回复区域 -->
+                         <div class="reply-section">
+                           <el-input
+                             v-model="r.replyContent"
+                             type="textarea"
+                             placeholder="请输入回复内容"
+                             :rows="3"
+                             resize="none"
+                             class="reply-input"
+                           />
+                           <div class="reply-btn-row">
+                             <el-button type="primary" size="small" class="reply-submit-btn" @click="handleReplySubmit(r)">
+                               <el-icon style="margin-right: 4px;"><ChatLineSquare /></el-icon> 回复
+                             </el-button>
+                           </div>
+                         </div>
+                       </div>
+                     </div>
                    </div>
                 </div>
               </div>
@@ -355,7 +426,7 @@
                 <div class="form-item">
                   <span class="field-label"><span class="required-star">*</span>归属公司</span>
                   <el-select v-model="form.companyNo" placeholder="请选择" size="small">
-                    <el-option v-for="item in options.company" :key="item.companyCode || item.id" :label="item.companyName" :value="item.companyCode || item.id" />
+                    <el-option v-for="item in options.company" :key="item.companyCode || item.id" :label="item.companyName" :value="String(item.companyCode || item.id)" />
                   </el-select>
                 </div>
               </el-form-item>
@@ -385,7 +456,7 @@
                 <div class="form-item">
                   <span class="field-label"><span class="required-star">*</span>项目类型</span>
                   <el-select v-model="form.businessType" placeholder="请选择" size="small">
-                    <el-option v-for="item in options.type" :key="item.dictValue" :label="item.dictLabel" :value="Number(item.dictValue)" />
+                    <el-option v-for="item in options.type" :key="item.dictValue" :label="item.dictLabel" :value="String(item.dictValue)" />
                   </el-select>
                 </div>
               </el-form-item>
@@ -393,7 +464,7 @@
                 <div class="form-item">
                   <span class="field-label"><span class="required-star">*</span>项目类别</span>
                   <el-select v-model="form.projectLevel" placeholder="请选择" size="small">
-                    <el-option v-for="item in options.level" :key="item.dictValue" :label="item.dictLabel" :value="Number(item.dictValue)" />
+                    <el-option v-for="item in options.level" :key="item.dictValue" :label="item.dictLabel" :value="String(item.dictValue)" />
                   </el-select>
                 </div>
               </el-form-item>
@@ -482,7 +553,7 @@
                 <div class="form-item">
                   <span class="field-label"><span class="required-star">*</span>入围类型</span>
                   <el-select v-model="form.shortlistedType" placeholder="请选择" size="small">
-                    <el-option v-for="item in options.shortlisted" :key="item.dictValue" :label="item.dictLabel" :value="Number(item.dictValue)" />
+                    <el-option v-for="item in options.shortlisted" :key="item.dictValue" :label="item.dictLabel" :value="String(item.dictValue)" />
                   </el-select>
                 </div>
               </el-form-item>
@@ -491,7 +562,7 @@
                 <div class="form-item">
                   <span class="field-label"><span class="required-star">*</span>物资类目</span>
                   <el-select v-model="form.profession" placeholder="请选择" size="small">
-                    <el-option v-for="item in options.profession" :key="item.dictValue" :label="item.dictLabel" :value="Number(item.dictValue)" />
+                    <el-option v-for="item in options.profession" :key="item.dictValue" :label="item.dictLabel" :value="String(item.dictValue)" />
                   </el-select>
                 </div>
               </el-form-item>
@@ -814,7 +885,7 @@
         <el-col :span="12">
           <el-form-item label="拜访人:" prop="visitor">
             <el-select v-model="recordForm.visitor" placeholder="请选择" style="width: 100%" filterable>
-              <el-option v-for="item in options.user" :key="item.staffId || item.userId" :label="item.staffName || item.nickName" :value="String(item.staffId || item.userId)" />
+              <el-option v-for="item in (form.memberList || [])" :key="item.staffId || item.userId || item.id" :label="item.memberName || item.staffName || item.realName || item.name" :value="String(item.staffId || item.userId)" />
             </el-select>
           </el-form-item>
         </el-col>
@@ -898,6 +969,7 @@ import { listTeamMember, addTeamMember, updateTeamMember, delTeamMember } from "
 import { listOperationLog } from '@/api/customer/operationLog';
 import { listContactPerson, addContactPerson } from "@/api/customer/contactPerson";
 import { addRecord, listRecord } from "@/api/visit/record";
+import { deptTreeSelect } from "@/api/system/user";
 
 const props = defineProps({ modelValue: { type: Boolean, default: false }, data: Object, title: String });
 const emit = defineEmits(['update:modelValue', 'submit']);
@@ -922,7 +994,8 @@ const options = ref({
   industryList: [],   // 行业分类数据
   teamRole: [],       // 团队成员角色字典
   permission: [],     // 团队成员权限字典
-  visitType: []       // 拜访方式字典
+  visitType: [],       // 拜访方式字典
+  dept: []            // 部门数据
 });
 const recordFormRef = ref(null);
 const recordImages = ref([]);
@@ -1061,6 +1134,12 @@ const professionName = computed(() => {
   );
   return p?.industryCategoryName || p?.name || p?.categoryName || p?.dictLabel || '--';
 });
+const deptName = computed(() => {
+  const val = String(form.value.deptNo || '');
+  if (!val) return '--';
+  const target = (options.value.dept || []).find(o => String(o.value) === val);
+  return target?.label || val;
+});
 
 /** 行业名称(使用行业分类数据,与列表页一致) */
 const industryName = computed(() => {
@@ -1095,6 +1174,16 @@ watch(() => props.modelValue, (val) => {
   }
 });
 
+// 监听 data 变化,确保在不打开详情抽屉的情况下(如点击进度按钮),内部数据也能同步
+watch(() => props.data, (newVal) => {
+  if (newVal) {
+    form.value = { ...newVal };
+    if (!form.value.customerName && form.value.customName) {
+      form.value.customerName = form.value.customName;
+    }
+  }
+}, { deep: true });
+
 /** 加载项目团队成员 */
 const fetchTeamMembers = async () => {
   if (!form.value.id) return;
@@ -1132,22 +1221,57 @@ const fetchFollowRecords = async () => {
   try {
     const params = {
       objectNo: form.value.id,
-      dataType: 2
+      dataType: 2,
+      pageSize: 50 // 加大加载量,确保显示完整
     };
     if (recordFilterType.value) {
       params.visitType = recordFilterType.value;
     }
     const res = await listRecord(params);
-    form.value.followRecords = (res.rows || res.data || []).map(r => ({
-      createTime: r.visitDate || r.createTime,
-      content: r.progress || r.purpose || r.content,
-      visitType: r.visitType
-    }));
+    const records = res.rows || res.data || [];
+    
+    // 处理图片 URL
+    for (const r of records) {
+      if (r.recordPicture && !r.imageList) {
+        const ids = r.recordPicture.split(',');
+        try {
+          const ossRes = await listByIds(ids.join(','));
+          r.imageList = (ossRes.data || []).map(img => img.url);
+        } catch (e) {
+          r.imageList = [];
+        }
+      }
+      // 初始化回复内容
+      r.replyContent = '';
+    }
+    
+    form.value.followRecords = records;
   } catch (err) {
     console.error('加载跟进记录失败:', err);
   }
 };
 
+/** 提交回复 */
+const handleReplySubmit = async (record) => {
+  if (!record.replyContent?.trim()) {
+    proxy.$modal.msgWarning("请输入回复内容");
+    return;
+  }
+  try {
+    await publishProjectProgress({
+      objectNo: String(form.value.id),
+      followUpCondition: record.replyContent.trim(),
+      dataType: '2',
+      parentId: record.id // 假设接口支持 parentId 作为回复
+    });
+    proxy.$modal.msgSuccess("回复成功");
+    record.replyContent = '';
+    fetchFollowRecords(); // 刷新列表
+  } catch (err) {
+    console.error('回复失败:', err);
+  }
+};
+
 /** 加载操作日志 */
 const fetchOperationLogs = async () => {
   if (!form.value.id) return;
@@ -1305,10 +1429,11 @@ const handleOpenLink = (url) => {
 };
 
 /** 打开进度弹窗 */
-const openProgressDrawer = () => {
+const openProgressDrawer = (id) => {
   showProgressDrawer.value = true;
-  if (form.value.id) {
-    progressQueryParams.objectNo = String(form.value.id);
+  const targetId = id || form.value.id;
+  if (targetId) {
+    progressQueryParams.objectNo = String(targetId);
     progressQueryParams.pageNum = 1;
     loadProgressRecordList();
   }
@@ -1685,6 +1810,18 @@ onMounted(() => {
   listCompanyOption().then(r => options.value.company = r.data);
   remoteLoadCustomers(); // 默认加载前 500 条客户
   selectStaffOptionList().then(r => options.value.user = r.data);
+  // 加载部门数据并打平
+  deptTreeSelect().then(res => {
+    const flatList = (list) => {
+      let arr = [];
+      for (const item of list || []) {
+        arr.push({ label: item.label, value: String(item.id) });
+        if (item.children?.length) arr.push(...flatList(item.children));
+      }
+      return arr;
+    };
+    options.value.dept = flatList(res.data);
+  });
 });
 
 /** 远程加载客户信息 */

+ 8 - 24
src/views/saleManage/projectSelection/index.vue

@@ -107,8 +107,6 @@
           <div class="tab-item" :class="{ current: activeTab === 'join' }" @click="handleTabChange('join')">我参与的年度入围</div>
           <div class="tab-item badge-item" :class="{ current: activeTab === 'all' }" @click="handleTabChange('all')">
             全部年度入围
-            <span v-if="totalCountBadge > 99" class="badge">99+</span>
-            <span v-else-if="totalCountBadge > 0" class="badge">{{ totalCountBadge }}</span>
           </div>
         </div>
         <div class="summary-line">
@@ -201,9 +199,9 @@
           </template>
           <template #default="scope">
             <div style="display: flex; align-items: center; justify-content: center; gap: 12px;">
-              <el-button link type="info" @click="handleUpdate(scope.row)">详情</el-button>
-              <el-button link type="primary" @click="handleProgress(scope.row)">进度</el-button>
-              <el-button link type="info" @click="handleDelete(scope.row)">删除</el-button>
+              <el-button link type="info" @click.stop="handleUpdate(scope.row)">详情</el-button>
+              <el-button link type="primary" @click.stop="handleProgress(scope.row)">进度</el-button>
+              <el-button link type="info" @click.stop="handleDelete(scope.row)">删除</el-button>
             </div>
           </template>
         </el-table-column>
@@ -257,7 +255,7 @@ const totalAmount = ref(0);
 const open = ref(false);
 const title = ref("");
 const activeTab = ref('all');
-const totalCountBadge = ref(0);
+
 const drawerRef = ref(null);
 const transferVisible = ref(false);
 const transferOwner = ref('');
@@ -279,11 +277,7 @@ const getList = async () => {
     total.value = res.total || 0;
     // 计算统计金额(当前页)
     totalAmount.value = dataList.value.reduce((sum, item) => sum + (Number(item.amount) || 0), 0);
-    // 获取全部年度入围总数用于徽标
-    try {
-      const allRes = await listProjectSelection({ pageNum: 1, pageSize: 1 });
-      totalCountBadge.value = allRes.total || 0;
-    } catch (e) { /* ignore */ }
+
   } catch (err) {
     console.error(err);
   } finally {
@@ -371,14 +365,11 @@ const confirmTransfer = async () => {
 };
 
 const handleProgress = async (row) => {
-  // 先加载项目数据并打开抽屉
+  // 先加载项目数据
   const res = await getProjectSelection(row.id);
   form.value = res.data;
-  open.value = true;
-  // 延迟一确保抽屉已渲染
-  setTimeout(() => {
-    drawerRef.value?.openProgressDrawer();
-  }, 100);
+  // 直接通过 ref 打开内部的进度抽屉,传入 ID 确保数据正确
+  drawerRef.value?.openProgressDrawer(row.id);
 };
 
 const submitForm = async (formData) => {
@@ -521,13 +512,6 @@ onMounted(() => {
       &:hover:not(.current) { border-color: var(--el-border-color); }
       &.badge-item {
         margin-right: 8px;
-        .badge {
-          position: absolute; top: -8px; right: -12px;
-          background: var(--el-color-danger); color: #fff; font-size: 10px;
-          padding: 0 5px; border-radius: 8px; line-height: 16px;
-          min-width: 22px; text-align: center; font-weight: normal;
-          box-shadow: 0 0 0 2px var(--el-bg-color);
-        }
       }
     }
   }

+ 19 - 115
src/views/visit/record/index.vue

@@ -27,28 +27,14 @@
       </el-form>
     </el-card>
 
-    <!-- 列表标题与操作栏 -->
+    <!-- 列表标题 -->
     <div class="filter-action-bar">
-      <!-- 页面标题 -->
       <div class="page-title">跟进记录信息列表</div>
-      
-      <!-- 分类筛选点击项 -->
-      <div class="tab-filters">
-        <div class="tab-btn" :class="{ active: activeTab === 'all' }" @click="handleTabClick('all')">全部记录</div>
-        <div class="tab-btn" :class="{ active: activeTab === 'mine' }" @click="handleTabClick('mine')">我发布的</div>
-      </div>
-
-      <!-- 操作按钮(靠右) -->
-      <div class="right-actions">
-        <el-button type="info" size="small" icon="Download" @click="handleExport">导出</el-button>
-        <el-button type="danger" size="small" icon="Delete" :disabled="multipleSelection.length === 0" @click="handleBatchDelete">批量删除</el-button>
-      </div>
     </div>
 
     <!-- 表格区域 -->
     <el-card shadow="never" class="table-card">
-      <el-table ref="tableRef" v-loading="loading" :data="dataList" border class="standard-table" @selection-change="handleSelectionChange">
-        <el-table-column type="selection" width="50" align="center" fixed />
+      <el-table ref="tableRef" v-loading="loading" :data="dataList" border class="standard-table">
         <el-table-column label="记录编号" align="center" prop="recordsNo" width="140" fixed>
           <template #default="scope">
             <span class="link-text" @click="handleDetail(scope.row)">{{ scope.row.recordsNo }}</span>
@@ -57,10 +43,11 @@
         <el-table-column label="客户名称" align="center" prop="customerName" min-width="250" show-overflow-tooltip />
         <el-table-column label="部门" align="center" prop="department" width="120">
           <template #default="scope">
-            <span>{{ scope.row.projectManagerDepartment || '未分配' }}</span>
+            <span>{{ scope.row.visitorDeptName || scope.row.deptName || scope.row.department || '' }}</span>
           </template>
         </el-table-column>
         <el-table-column label="行业" align="center" prop="profession" width="100" />
+        <el-table-column label="业务员" align="center" prop="salesman" width="100" show-overflow-tooltip />
         <el-table-column label="对象类型" align="center" prop="dataType" width="100">
            <template #default="scope">
              <span>{{ scope.row.dataType === 1 ? '项目' : '通用' }}</span>
@@ -76,8 +63,8 @@
         </el-table-column>
         <el-table-column label="操作" align="center" width="120" fixed="right">
           <template #default="scope">
-            <el-button link type="primary" @click="handleDetail(scope.row)">详情</el-button>
-            <el-button link type="danger" @click="handleDelete(scope.row)">删除</el-button>
+            <el-button link class="op-btn" @click="handleDetail(scope.row)">详情</el-button>
+            <el-button link class="op-btn" @click="handleDelete(scope.row)">删除</el-button>
           </template>
         </el-table-column>
       </el-table>
@@ -337,9 +324,8 @@
 
 <script setup name="VisitRecord">
 import { ref, reactive, onMounted, getCurrentInstance } from 'vue';
-import { Setting, Close, ChatLineRound, Upload } from '@element-plus/icons-vue';
-import { listRecord, getRecord, delRecord, addRecord, updateRecord } from "@/api/visit/record";
-import { listCustomerInfo, getCustomerInfo } from "@/api/customer/customerInfo/";
+import { Close, ChatLineRound, Upload } from '@element-plus/icons-vue';
+import { listRecord, getRecord, delRecord } from "@/api/visit/record";
 import { listCommonDict } from "@/api/customer/customerDict";
 import { globalHeaders } from "@/utils/request";
 import { useUserStore } from "@/store/modules/user";
@@ -362,8 +348,6 @@ const queryParams = reactive({
   callDate: undefined
 });
 
-const activeTab = ref('all');
-const multipleSelection = ref([]);
 const dateRange = ref([]);
 const detailData = ref({});
 const leftTab = ref('followup');
@@ -406,37 +390,6 @@ const resetQuery = () => {
   handleQuery();
 };
 
-const handleTabClick = (tab) => {
-  activeTab.value = tab;
-  if (tab === 'mine') {
-    queryParams.visitor = userStore.nickname || userStore.name;
-  } else {
-    queryParams.visitor = undefined;
-  }
-  handleQuery();
-};
-
-const handleSelectionChange = (selection) => {
-  multipleSelection.value = selection;
-};
-
-const handleBatchDelete = () => {
-  const ids = multipleSelection.value.map(item => item.id);
-  proxy.$modal.confirm(`是否确认删除选中的 ${ids.length} 条跟进记录?`).then(() => {
-    return delRecord(ids.join(','));
-  }).then(() => {
-    getList();
-    proxy.$modal.msgSuccess("批量删除成功");
-  }).catch(() => {});
-};
-
-const handleExport = () => {
-  proxy.download('customer/crm/followup/log/export', {
-    ...queryParams
-  }, `followup_record_${new Date().getTime()}.xlsx`)
-};
-
-
 const handleDetail = (row) => {
   getRecord(row.id).then(response => {
     detailData.value = { ...row, ...(response.data || {}) };
@@ -543,11 +496,10 @@ onMounted(() => {
   :deep(.el-form-item__label) {
     font-size: 13px;
     color: #333;
-    font-weight: 500;
+    font-weight: normal;
   }
   :deep(.el-input__wrapper) {
-    background-color: #f7f8fa;
-    box-shadow: none !important;
+    border: 1px solid #dcdfe6;
     border-radius: 4px;
     height: 36px;
   }
@@ -579,56 +531,7 @@ onMounted(() => {
 
   .page-title {
     font-size: 14px;
-    font-weight: bold;
     color: #333;
-    margin-right: 30px;
-    white-space: nowrap;
-  }
-
-  .tab-filters {
-    display: flex;
-    gap: 0;
-    margin-right: 30px;
-
-    .tab-btn {
-      padding: 6px 16px;
-      font-size: 13px;
-      color: #64748b;
-      cursor: pointer;
-      border: 1px solid #e2e8f0;
-      background: #fff;
-      transition: all 0.2s;
-      margin-left: -1px;
-
-      &:first-child {
-        border-top-left-radius: 4px;
-        border-bottom-left-radius: 4px;
-        margin-left: 0;
-      }
-
-      &:last-child {
-        border-top-right-radius: 4px;
-        border-bottom-right-radius: 4px;
-      }
-
-      &.active {
-        background-color: #f8fafc;
-        color: #e60012;
-        border-color: #e60012;
-        z-index: 1;
-      }
-
-      &:hover:not(.active) {
-        color: #e60012;
-        background-color: #fff1f2;
-      }
-    }
-  }
-
-  .right-actions {
-    margin-left: auto;
-    display: flex;
-    gap: 8px;
   }
 }
 
@@ -656,16 +559,20 @@ onMounted(() => {
 /* 品牌红色记录编号链接 */
 .link-text {
   color: #e60012;
-  font-weight: 500;
   cursor: pointer;
   transition: all 0.2s;
   &:hover { 
-    color: #b3000e;
+    color: #b3000e; 
     text-decoration: none; 
-    opacity: 0.8;
+    opacity: 0.8; 
   }
 }
 
+.op-btn {
+  color: #909399;
+  &:hover { color: #409eff; }
+}
+
 /* 详情抽屉样式重构 */
 .custom-drawer {
     :deep(.el-drawer__body) { padding: 0; background: #fff; overflow: hidden; }
@@ -684,7 +591,7 @@ onMounted(() => {
     justify-content: space-between;
     padding: 0 20px;
     border-bottom: 1px solid #f0f0f0;
-    .header-title { font-size: 16px; font-weight: bold; color: #333; }
+    .header-title { font-size: 16px; color: #333; }
     .close-icon { font-size: 20px; cursor: pointer; color: #909399; &:hover { color: #333; } }
 }
 
@@ -727,7 +634,7 @@ onMounted(() => {
         color: #333;
         height: 50px;
         line-height: 50px;
-        &.is-active { color: #e60012; font-weight: bold; }
+        &.is-active { color: #e60012; }
         &:hover { color: #e60012; }
     }
     :deep(.el-tabs__content) { 
@@ -755,7 +662,6 @@ onMounted(() => {
     :deep(th.el-table__cell) {
         background-color: #f8fafc !important;
         font-size: 13px;
-        font-weight: 500;
         color: #475569;
     }
     :deep(td.el-table__cell) {
@@ -827,7 +733,6 @@ onMounted(() => {
     }
     .comment-user {
         font-size: 13px;
-        font-weight: 500;
         color: #333;
     }
     .comment-time {
@@ -848,7 +753,6 @@ onMounted(() => {
 
 .section-header {
     font-size: 15px;
-    font-weight: bold;
     color: #333;
     margin-bottom: 15px;
 }

+ 42 - 29
src/views/visit/routine/index.vue

@@ -34,7 +34,7 @@
 
     <!-- 列表区域 -->
     <el-card shadow="never" class="table-card">
-      <el-table v-loading="loading" :data="dataList" border class="custom-table" :header-cell-style="{ background: '#f8fafc', color: '#333', fontWeight: 'bold' }">
+      <el-table v-loading="loading" :data="dataList" border class="custom-table" :header-cell-style="{ background: '#f8fafc', color: '#333' }">
         <el-table-column label="日程编号" align="center" prop="scheduleNo" width="160">
           <template #default="scope">
             <span class="link-text" @click="handleDetail(scope.row)">{{ scope.row.scheduleNo }}</span>
@@ -44,7 +44,7 @@
         <el-table-column label="客户名称" align="center" prop="customerName" min-width="200" show-overflow-tooltip />
         <el-table-column label="拜访人" align="center" prop="callPeopleName" width="100">
           <template #default="scope">
-            <span>{{ scope.row.callPeopleName || '-' }}</span>
+            <span>{{ scope.row.callPeopleName || '' }}</span>
           </template>
         </el-table-column>
         <el-table-column label="拜访日期" align="center" prop="callDate" width="120" >
@@ -55,7 +55,7 @@
         <el-table-column label="随访人" align="center" prop="followPeopleName" width="120" />
         <el-table-column label="关联对象" align="center" prop="objectName" width="180" show-overflow-tooltip>
           <template #default="scope">
-            <span>{{ scope.row.objectName || scope.row.customerName || '-' }}</span>
+            <span>{{ scope.row.objectName || scope.row.customerName || '' }}</span>
           </template>
         </el-table-column>
         <el-table-column label="状态" align="center" prop="scheduleStatus" width="100">
@@ -72,7 +72,7 @@
         </el-table-column>
         <el-table-column label="重要级别" align="center" prop="importantLevel" width="100">
           <template #default="scope">
-            <span>{{ importanceOptions.find(d => String(d.dictValue) === String(scope.row.importantLevel))?.dictLabel || scope.row.importantLevel || '-' }}</span>
+            <span>{{ importanceOptions.find(d => String(d.dictValue) === String(scope.row.importantLevel))?.dictLabel || scope.row.importantLevel || '' }}</span>
           </template>
         </el-table-column>
         <el-table-column label="操作" align="center" width="180" fixed="right">
@@ -92,7 +92,7 @@
       <el-form ref="routineRef" :model="form" :rules="rules" label-width="120px">
         <el-form-item label="关联计划:" prop="planNo">
           <el-select v-model="form.planNo" placeholder="请选择" style="width: 100%" clearable>
-            <el-option v-for="item in planOptions" :key="item.id" :label="item.planNo" :value="item.planNo" />
+            <el-option v-for="item in planOptions" :key="item.id" :label="item.planNo + (item.customerName ? ' - ' + item.customerName : '')" :value="item.planNo" />
           </el-select>
         </el-form-item>
         <el-form-item label="关联类型:" prop="relevanceType">
@@ -102,9 +102,9 @@
             <el-radio value="2">供应商</el-radio>
           </el-radio-group>
         </el-form-item>
-        <el-form-item label="客户:" prop="customerNo">
-          <el-select v-model="form.customerNo" placeholder="请选择" style="width: 100%" clearable filterable @change="handleCustomerChange">
-            <el-option v-for="item in customerOptions" :key="item.customerNo" :label="item.customerName" :value="item.customerNo" />
+        <el-form-item label="客户:" prop="customerName">
+          <el-select v-model="form.customerName" placeholder="请选择" style="width: 100%" clearable filterable @change="handleCustomerChange">
+            <el-option v-for="item in customerOptions" :key="item.customerNo" :label="item.customerName" :value="item.customerName" />
           </el-select>
         </el-form-item>
         <el-form-item label="拜访人:" prop="callPeopleNo">
@@ -233,9 +233,9 @@
                      </div>
                      <div class="record-content">
                        <div class="aim-title">拜访目的:</div>
-                       <div class="aim-text">{{ item.callAim || '-' }}</div>
+                       <div class="aim-text">{{ item.callAim || '' }}</div>
                        <div class="progress-title">跟进情况:</div>
-                       <div class="progress-text">{{ item.followUpCondition || '-' }}</div>
+                       <div class="progress-text">{{ item.followUpCondition || '' }}</div>
                      </div>
                    </div>
                 </div>
@@ -261,21 +261,21 @@
                 <el-row class="manage-row">
                   <el-col :span="12" class="manage-col">
                     <span class="label">创建日期</span>
-                    <span class="value">{{ detailData.createTime || detailData.createDate ? parseTime(detailData.createTime || detailData.createDate, '{y}-{m}-{d}') : '--' }}</span>
+                    <span class="value">{{ detailData.createTime || detailData.createDate ? parseTime(detailData.createTime || detailData.createDate, '{y}-{m}-{d}') : '' }}</span>
                   </el-col>
                   <el-col :span="12" class="manage-col">
                     <span class="label">创建人</span>
-                    <span class="value">{{ detailData.createByName || detailData.createUserName || detailData.nickName || detailData.callPeopleName || detailData.createBy || '--' }}</span>
+                    <span class="value">{{ detailData.createByName || detailData.createUserName || detailData.nickName || detailData.callPeopleName || detailData.createBy || '' }}</span>
                   </el-col>
                 </el-row>
                 <el-row class="manage-row">
                   <el-col :span="12" class="manage-col">
                     <span class="label">修改日期</span>
-                    <span class="value">{{ detailData.updateTime || detailData.updateDate ? parseTime(detailData.updateTime || detailData.updateDate, '{y}-{m}-{d}') : '--' }}</span>
+                    <span class="value">{{ detailData.updateTime || detailData.updateDate ? parseTime(detailData.updateTime || detailData.updateDate, '{y}-{m}-{d}') : '' }}</span>
                   </el-col>
                   <el-col :span="12" class="manage-col">
                     <span class="label">最后修改人</span>
-                    <span class="value">{{ detailData.updateByName || detailData.updateUserName || detailData.followPeopleName || detailData.updateBy || '--' }}</span>
+                    <span class="value">{{ detailData.updateByName || detailData.updateUserName || detailData.followPeopleName || detailData.updateBy || '' }}</span>
                   </el-col>
                 </el-row>
               </div>
@@ -289,7 +289,7 @@
     <el-dialog title="跟进记录" v-model="recordDialogOpen" width="800px" append-to-body destroy-on-close class="record-add-dialog">
       <el-form ref="recordFormRef" :model="recordForm" :rules="recordRules" label-width="120px">
         <el-form-item label="客户:">
-          <span style="color: #333;">{{ detailData.customerName || '-' }}</span>
+          <span style="color: #333;">{{ detailData.customerName || '' }}</span>
         </el-form-item>
 
         <el-row :gutter="20">
@@ -394,7 +394,7 @@ const open = ref(false);
 const title = ref("");
 const form = ref({});
 const rules = reactive({
-  customerNo: [{ required: true, message: "客户不能为空", trigger: "change" }],
+  customerName: [{ required: true, message: "客户不能为空", trigger: "change" }],
   callDate: [{ required: true, message: "拜访日期不能为空", trigger: "change" }],
   importantLevel: [{ required: true, message: "重要级别不能为空", trigger: "change" }],
   purposeVisit: [{ required: true, message: "拜访目的不能为空", trigger: "blur" }]
@@ -496,16 +496,15 @@ const handleQuery = () => {
 };
 
 const handleCustomerChange = (val) => {
-  const customer = customerOptions.value.find(item => item.customerNo === val);
+  const customer = customerOptions.value.find(item => item.customerName === val);
   if (customer) {
-    form.value.customerName = customer.customerName;
-    // 如果关联类型是客户(0),同步设置关联对象
+    form.value.customerNo = customer.customerNo;
     if (form.value.relevanceType === '0') {
       form.value.objectNo = customer.customerNo;
       form.value.objectName = customer.customerName;
     }
   } else {
-    form.value.customerName = undefined;
+    form.value.customerNo = undefined;
     if (form.value.relevanceType === '0') {
       form.value.objectNo = undefined;
       form.value.objectName = undefined;
@@ -588,8 +587,14 @@ const handleUpdate = (row) => {
       purposeVisit: data.purposeVisit ?? undefined
     };
     title.value = "编辑拜访日程";
-    nextTick(() => {
-      open.value = true;
+    Promise.all([
+      listPlan({ pageSize: 1000 }),
+      listCustomerInfo({ pageSize: 1000 }),
+      selectStaffOptionList()
+    ]).then(() => {
+      nextTick(() => {
+        open.value = true;
+      });
     });
   });
 };
@@ -693,7 +698,7 @@ onMounted(() => {
   :deep(.el-form-item__label) {
     padding-right: 12px;
     color: #606266;
-    font-weight: 400;
+    font-weight: normal;
   }
   :deep(.el-input), :deep(.el-select), :deep(.el-date-editor) {
     width: 220px;
@@ -712,7 +717,7 @@ onMounted(() => {
   background-color: #fff;
   border-bottom: 1px solid #ebeef5;
   border-radius: 4px 4px 0 0;
-  .list-title { font-size: 15px; font-weight: 600; color: #333;}
+  .list-title { font-size: 15px; color: #333;}
 }
 
 .table-card {
@@ -721,6 +726,17 @@ onMounted(() => {
   :deep(.el-card__body) { padding: 10px 20px 20px; }
 }
 
+:deep(.el-table__header th .cell) {
+  font-weight: normal;
+}
+
+:deep(.el-button--primary.is-link) {
+  color: #409eff;
+}
+:deep(.el-button--danger.is-link) {
+  color: #f56c6c;
+}
+
 .link-text { color: #409eff; cursor: pointer; }
 
 /* 详情抽屉样式 */
@@ -733,7 +749,6 @@ onMounted(() => {
   }
   .drawer-title {
     font-size: 18px;
-    font-weight: 600;
   }
   .el-drawer__body {
     padding: 0;
@@ -754,7 +769,6 @@ onMounted(() => {
     
     :deep(.el-tabs__item) {
       font-size: 15px;
-      font-weight: 600;
       &.is-active {
         color: #f56c6c;
       }
@@ -768,7 +782,6 @@ onMounted(() => {
       
       .info-title {
         font-size: 14px;
-        font-weight: bold;
         color: #333;
         margin-bottom: 20px;
       }
@@ -874,14 +887,14 @@ onMounted(() => {
       display: flex;
       align-items: center;
       margin-bottom: 12px;
-      .visit-person { font-weight: bold; color: #333; margin-right: 12px; }
+      .visit-person { color: #333; margin-right: 12px; }
       .visit-time { color: #909399; font-size: 13px; flex: 1; }
     }
     
     .record-content {
       font-size: 13px;
       line-height: 1.6;
-      .aim-title, .progress-title { color: #64748b; font-weight: 500; margin-top: 8px; }
+      .aim-title, .progress-title { color: #64748b; margin-top: 8px; }
       .aim-text, .progress-text { color: #334155; margin-bottom: 5px; background: #fff; padding: 8px 12px; border-radius: 4px; border: 1px solid #e2e8f0;}
     }
   }

Some files were not shown because too many files changed in this diff