Przeglądaj źródła

前端代码修改

沐梦. 1 miesiąc temu
rodzic
commit
efd830d259

+ 5 - 5
src/api/customer/contactPerson.js

@@ -3,7 +3,7 @@ import request from '@/utils/request'
 // 查询联系人列表
 export function listContactPerson(query) {
   return request({
-    url: '/customer/contactPerson/list',
+    url: '/customer/customerContact/list',
     method: 'get',
     params: query
   })
@@ -12,7 +12,7 @@ export function listContactPerson(query) {
 // 查询联系人详细
 export function getContactPerson(id) {
   return request({
-    url: '/customer/contactPerson/' + id,
+    url: '/customer/customerContact/' + id,
     method: 'get'
   })
 }
@@ -20,7 +20,7 @@ export function getContactPerson(id) {
 // 新增联系人
 export function addContactPerson(data) {
   return request({
-    url: '/customer/contactPerson',
+    url: '/customer/customerContact',
     method: 'post',
     data: data
   })
@@ -29,7 +29,7 @@ export function addContactPerson(data) {
 // 修改联系人
 export function updateContactPerson(data) {
   return request({
-    url: '/customer/contactPerson',
+    url: '/customer/customerContact',
     method: 'put',
     data: data
   })
@@ -38,7 +38,7 @@ export function updateContactPerson(data) {
 // 删除联系人
 export function delContactPerson(id) {
   return request({
-    url: '/customer/contactPerson/' + id,
+    url: '/customer/customerContact/' + id,
     method: 'delete'
   })
 }

+ 1 - 1
src/api/customer/customerInfo/index.ts

@@ -52,7 +52,7 @@ export function listCustomerListPage(query?: any) {
  */
 export function listValidCustomer(query?: any) {
   return request({
-    url: '/customer/customerInfo/highseas',
+    url: '/customer/customerInfo/customerList',
     method: 'get',
     params: query
   });

+ 15 - 12
src/api/customer/customerPool.js

@@ -1,28 +1,31 @@
 import request from '@/utils/request'
 
-// 查询客户公海信息列表(基于 customer_info 表查询)
+// 查询客户公海信息列表
 export function listPool(query) {
   return request({
-    url: '/customer/customerInfo/highseas',
+    url: '/customer/customerInfo/customerList',
     method: 'get',
-    params: query
+    params: {
+      ...query,
+      isHighSeas: 'true'
+    }
   })
 }
 
-// 获取详情
-export function getPool(id) {
+// 公海客户认领
+export function claimPool(data) {
   return request({
-      url: `/customer/customerInfo/${id}`,
-      method: 'get'
+    url: '/customer/customerInfo/claimPool',
+    method: 'put',
+    data: data
   })
 }
 
-// 认领客户
-export function claimPool(data) {
+// 详情接口
+export function getPool(id) {
   return request({
-    url: '/customer/customerInfo/claim',
-    method: 'post',
-    data: data
+      url: `/customer/customerInfo/${id}`,
+      method: 'get'
   })
 }
 

+ 4 - 0
src/api/saleManage/projectSelection/types.ts

@@ -47,7 +47,11 @@ export interface ProjectSelectionVO {
     createTime?: string;
     updateTime?: string;
     createUserId?: string | number;
+    createBy?: string | number;
+    createByName?: string;
     updateUserId?: string | number;
+    updateBy?: string | number;
+    updateByName?: string;
     isDelete?: number;
     // 进度与状态
     projectSchedule?: string;

+ 6 - 0
src/api/saleManage/quotation/types.ts

@@ -10,6 +10,12 @@ export interface QuotationVO {
     companyName?: string;
     managerName?: string;
     status?: string;
+    createTime?: string;
+    updateTime?: string;
+    createUserId?: string | number;
+    createByName?: string;
+    updateUserId?: string | number;
+    updateByName?: string;
 }
 
 export interface QuotationGoodsVO {

+ 11 - 0
src/api/workbench.ts

@@ -0,0 +1,11 @@
+import request from '@/utils/request';
+
+/**
+ * 获取工作台统计数据
+ */
+export function getWorkbenchStat() {
+  return request({
+    url: '/customer/workbench/stat',
+    method: 'get'
+  });
+}

+ 1 - 179
src/router/index.ts

@@ -89,186 +89,8 @@ export const constantRoutes: RouteRecordRaw[] = [
       }
     ]
   },
-  {
-    path: '/sales',
-    component: Layout,
-    hidden: false,
-    alwaysShow: true,
-    meta: { title: '销售目标', icon: 'chart' },
-    children: [
-      {
-        path: 'year',
-        component: () => import('@/views/sale/annualTask/index.vue'),
-        name: 'AnnualYearTask',
-        meta: { title: '年度任务', icon: 'money' }
-      },
-      {
-        path: 'year-detail/:id(\\d+)?',
-        component: () => import('@/views/sale/annualTask/detail.vue'),
-        name: 'AnnualYearTaskDetail',
-        hidden: true,
-        meta: { title: '年度任务详情', activeMenu: '/sales/year' }
-      },
-      {
-        path: 'quarter',
-        component: () => import('@/views/sale/quarterlyTask/index.vue'),
-        name: 'QuarterlyTask',
-        meta: { title: '季度任务', icon: 'chart' }
-      },
-      {
-        path: 'quarter-detail/:id(\\d+)?',
-        component: () => import('@/views/sale/quarterlyTask/detail.vue'),
-        name: 'QuarterlyTaskDetail',
-        hidden: true,
-        meta: { title: '季度任务详情', activeMenu: '/sales/quarter' }
-      },
-      {
-        path: 'month',
-        component: () => import('@/views/sale/monthlyTask/index.vue'),
-        name: 'MonthlyTask',
-        meta: { title: '月度任务', icon: 'date' }
-      },
-      {
-        path: 'month-detail/:id(\\d+)?',
-        component: () => import('@/views/sale/monthlyTask/detail.vue'),
-        name: 'MonthlyTaskDetail',
-        hidden: true,
-        meta: { title: '月度任务详情', activeMenu: '/sales/month' }
-      },
-      {
-        path: 'customer',
-        component: () => import('@/views/sale/customerTarget/index.vue'),
-        name: 'CustomerTarget',
-        meta: { title: '客户目标', icon: 'user' }
-      },
-      {
-        path: 'kpi',
-        component: () => import('@/views/sale/kpiTask/index.vue'),
-        name: 'KpiAssessment',
-        meta: { title: 'KPI考核', icon: 'list' }
-      }
-    ]
-  },
-  {
-    path: '/salesManage',
-    component: Layout,
-    hidden: false,
-    alwaysShow: true,
-    meta: { title: '销售管理', icon: 'shopping' },
-    children: [
-      {
-        path: 'leads',
-        component: () => import('@/views/saleManage/leads/index.vue'),
-        name: 'SalesLeads',
-        meta: { title: '销售线索', icon: 'people' }
-      },
-      {
-        path: 'opportunity',
-        component: () => import('@/views/saleManage/opportunity/index.vue'),
-        name: 'ProjectOpportunity',
-        meta: { title: '项目商机', icon: 'chart' }
-      },
-      {
-        path: 'project-selection',
-        component: () => import('@/views/saleManage/projectSelection/index.vue'),
-        name: 'ProjectSelection',
-        meta: { title: '年度入围(项目)', icon: 'star' }
-      },
-      {
-        path: 'platform-selection',
-        component: () => import('@/views/saleManage/platformSelection/index.vue'),
-        name: 'PlatformSelection',
-        meta: { title: '年度入围(平台)', icon: 'component' }
-      },
-      {
-        path: 'quotation',
-        component: () => import('@/views/saleManage/quotation/index.vue'),
-        name: 'Quotation',
-        meta: { title: '报价单', icon: 'form' }
-      },
-      {
-        path: 'receivable',
-        component: () => import('@/views/saleManage/accountsReceivable/index.vue'),
-        name: 'Receivable',
-        meta: { title: '应收账款', icon: 'money' }
-      }
-    ]
-  },
-  {
-    path: '/customer',
-    component: Layout,
-    hidden: false,
-    alwaysShow: true,
-    meta: { title: '客户管理', icon: 'peoples' },
-    children: [
-      {
-        path: 'highseas',
-        component: () => import('@/views/customer/highseas/index.vue'),
-        name: 'CustomerHighSeas',
-        meta: { title: '客户公海', icon: 'international' }
-      },
-      {
-        path: 'highseas-add',
-        component: () => import('@/views/customer/highseas/AddCustomer.vue'),
-        name: 'CustomerHighSeasAdd',
-        hidden: true,
-        meta: { title: '新增客户', activeMenu: '/customer/highseas' }
-      },
-      {
-        path: 'valid',
-        component: () => import('@/views/customer/valid/index.vue'),
-        name: 'CustomerValid',
-        meta: { title: '有效客户', icon: 'user' }
-      },
-      {
-        path: 'valid-add',
-        component: () => import('@/views/customer/valid/AddCustomer.vue'),
-        name: 'CustomerValidAdd',
-        hidden: true,
-        meta: { title: '新增有效客户', activeMenu: '/customer/valid' }
-      },
-      {
-        path: 'contact',
-        component: () => import('@/views/customer/contact/index.vue'),
-        name: 'Contact',
-        meta: { title: '联系人', icon: 'people' }
-      },
-      {
-        path: 'care',
-        component: () => import('@/views/customer/care/index.vue'),
-        name: 'CustomerCare',
-        meta: { title: '客户关怀', icon: 'message' }
-      },
 
-    ]
-  },
-  {
-    path: '/visit',
-    component: Layout,
-    hidden: false,
-    alwaysShow: true,
-    meta: { title: '拜访管理', icon: 'guide' },
-    children: [
-      {
-        path: 'plan',
-        component: () => import('@/views/visit/plan/index.vue'),
-        name: 'VisitPlan',
-        meta: { title: '拜访计划', icon: 'date-range' }
-      },
-      {
-        path: 'routine',
-        component: () => import('@/views/visit/routine/index.vue'),
-        name: 'VisitRoutine',
-        meta: { title: '拜访日程', icon: 'log' }
-      },
-      {
-        path: 'record',
-        component: () => import('@/views/visit/record/index.vue'),
-        name: 'VisitRecord',
-        meta: { title: '跟进记录', icon: 'edit' }
-      }
-    ]
-  }
+
 ];
 
 // 动态路由,基于用户权限动态去加载

+ 91 - 140
src/views/customer/contactPerson/index.vue

@@ -1,25 +1,25 @@
 <template>
   <div class="app-container">
     <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
-      <el-form-item label="姓名" prop="name">
+      <el-form-item label="姓名" prop="contactName">
         <el-input
-          v-model="queryParams.name"
+          v-model="queryParams.contactName"
           placeholder="请输入姓名"
           clearable
           @keyup.enter="handleQuery"
         />
       </el-form-item>
-      <el-form-item label="人员编号" prop="personnelNumber">
+      <el-form-item label="人员编号" prop="contactNo">
         <el-input
-          v-model="queryParams.personnelNumber"
+          v-model="queryParams.contactNo"
           placeholder="请输入人员编号"
           clearable
           @keyup.enter="handleQuery"
         />
       </el-form-item>
-      <el-form-item label="手机号码" prop="phoneNumber">
+      <el-form-item label="手机号码" prop="phone">
         <el-input
-          v-model="queryParams.phoneNumber"
+          v-model="queryParams.phone"
           placeholder="请输入手机号码"
           clearable
           @keyup.enter="handleQuery"
@@ -69,21 +69,22 @@
       <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
 
-    <el-table v-loading="loading" :data="contactPersonList" @selection-change="handleSelectionChange">
-      <el-table-type="selection" width="55" align="center" />
-      <el-table-column label="人员编号" align="center" prop="personnelNumber" />
-      <el-table-column label="姓名" align="center" prop="name" />
-      <el-table-column label="联系人类型" align="center" prop="contactType" />
-      <el-table-column label="性别" align="center" prop="sex" />
-      <el-table-column label="年龄" align="center" prop="age" />
-      <el-table-column label="手机号码" align="center" prop="phoneNumber" />
-      <el-table-column label="部门" align="center" prop="department" />
-      <el-table-column label="职位" align="center" prop="position" />
-      <el-table-column label="客户名称" align="center" prop="customName" />
+    <el-table v-loading="loading" :data="contactPersonList" @selection-change="handleSelectionChange" border>
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="联系人" align="center" prop="contactName">
+        <template #default="scope">
+          <span class="contact-name-text">{{ scope.row.contactName }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="部门" align="center" prop="deptName" />
+      <el-table-column label="客户名称" align="left" prop="customerName" :show-overflow-tooltip="true" />
+      <el-table-column label="职位" align="center" prop="roleName" />
+      <el-table-column label="手机号码" align="center" prop="phone" />
+      <el-table-column label="办公电话" align="center" prop="officePhone" />
       <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
         <template #default="scope">
-          <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)">修改</el-button>
-          <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
+          <el-button link type="primary" @click="handleUpdate(scope.row)">详情</el-button>
+          <el-button link type="info" @click="handleDelete(scope.row)">删除</el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -103,28 +104,23 @@
           <el-tab-pane label="基本信息" name="basic">
             <el-row>
               <el-col :span="12">
-                <el-form-item label="人员编号" prop="personnelNumber">
-                  <el-input v-model="form.personnelNumber" placeholder="请输入人员编号" />
+                <el-form-item label="人员编号" prop="contactNo">
+                  <el-input v-model="form.contactNo" placeholder="请输入人员编号" />
                 </el-form-item>
               </el-col>
               <el-col :span="12">
-                <el-form-item label="姓名" prop="name">
-                  <el-input v-model="form.name" placeholder="请输入姓名" />
+                <el-form-item label="姓名" prop="contactName">
+                  <el-input v-model="form.contactName" placeholder="请输入姓名" />
                 </el-form-item>
               </el-col>
               <el-col :span="12">
-                <el-form-item label="性别" prop="sex">
-                  <el-select v-model="form.sex" placeholder="请选择性别">
-                    <el-option label="男" value="" />
-                    <el-option label="女" value="" />
+                <el-form-item label="性别" prop="gender">
+                  <el-select v-model="form.gender" placeholder="请选择性别">
+                    <el-option label="男" value="0" />
+                    <el-option label="女" value="1" />
                   </el-select>
                 </el-form-item>
               </el-col>
-              <el-col :span="12">
-                <el-form-item label="年龄" prop="age">
-                  <el-input-number v-model="form.age" :min="0" :max="150" />
-                </el-form-item>
-              </el-col>
               <el-col :span="12">
                 <el-form-item label="生日" prop="birthday">
                   <el-date-picker clearable
@@ -136,8 +132,8 @@
                 </el-form-item>
               </el-col>
               <el-col :span="12">
-                <el-form-item label="联系人类型" prop="contactType">
-                  <el-input v-model="form.contactType" placeholder="请输入联系人类型" />
+                <el-form-item label="角色" prop="roleName">
+                  <el-input v-model="form.roleName" placeholder="请输入角色" />
                 </el-form-item>
               </el-col>
             </el-row>
@@ -145,38 +141,13 @@
           <el-tab-pane label="工作信息" name="work">
              <el-row>
               <el-col :span="12">
-                <el-form-item label="公司编号" prop="companyNo">
-                  <el-input v-model="form.companyNo" placeholder="请输入公司编号" />
-                </el-form-item>
-              </el-col>
-              <el-col :span="12">
-                <el-form-item label="客户编号" prop="customerNo">
-                  <el-input v-model="form.customerNo" placeholder="请输入客户编号" />
-                </el-form-item>
-              </el-col>
-              <el-col :span="12">
-                <el-form-item label="客户名称" prop="customName">
-                  <el-input v-model="form.customName" placeholder="请输入客户名称" />
-                </el-form-item>
-              </el-col>
-              <el-col :span="12">
-                <el-form-item label="部门" prop="department">
-                  <el-input v-model="form.department" placeholder="请输入部门" />
+                <el-form-item label="客户名称" prop="customerName">
+                  <el-input v-model="form.customerName" placeholder="请输入客户名称" />
                 </el-form-item>
               </el-col>
               <el-col :span="12">
-                <el-form-item label="职位" prop="position">
-                  <el-input v-model="form.position" placeholder="请输入职位" />
-                </el-form-item>
-              </el-col>
-              <el-col :span="12">
-                <el-form-item label="在职状态" prop="employmentStatusCode">
-                  <el-input v-model="form.employmentStatusCode" placeholder="请输入在职状态代码" />
-                </el-form-item>
-              </el-col>
-              <el-col :span="24">
-                <el-form-item label="工作描述" prop="workDesc">
-                  <el-input v-model="form.workDesc" type="textarea" placeholder="请输入内容" />
+                <el-form-item label="部门" prop="deptName">
+                  <el-input v-model="form.deptName" placeholder="请输入部门" />
                 </el-form-item>
               </el-col>
             </el-row>
@@ -184,58 +155,27 @@
           <el-tab-pane label="联系方式" name="contact">
             <el-row>
               <el-col :span="12">
-                <el-form-item label="手机号码" prop="phoneNumber">
-                  <el-input v-model="form.phoneNumber" placeholder="请输入手机号码" />
+                <el-form-item label="手机号码" prop="phone">
+                  <el-input v-model="form.phone" placeholder="请输入手机号码" />
                 </el-form-item>
               </el-col>
               <el-col :span="12">
-                <el-form-item label="座机" prop="landline">
-                  <el-input v-model="form.landline" placeholder="请输入座机" />
-                </el-form-item>
-              </el-col>
-              <el-col :span="24">
-                <el-form-item label="办公地址" prop="officeAddress">
-                  <el-input v-model="form.officeAddress" placeholder="请输入办公地址" />
+                <el-form-item label="座机" prop="officePhone">
+                  <el-input v-model="form.officePhone" placeholder="请输入座机" />
                 </el-form-item>
               </el-col>
               <el-col :span="24">
-                <el-form-item label="工作地址" prop="workAddress">
-                  <el-input v-model="form.workAddress" placeholder="请输入工作地址" />
+                <el-form-item label="办公地址" prop="addressDetail">
+                  <el-input v-model="form.addressDetail" placeholder="请输入办公地址" />
                 </el-form-item>
               </el-col>
             </el-row>
           </el-tab-pane>
-          <el-tab-pane label="生活/描述" name="other">
+          <el-tab-pane label="其他" name="other">
              <el-row>
-              <el-col :span="12">
-                <el-form-item label="是否吸烟" prop="izSmoking">
-                  <el-radio-group v-model="form.izSmoking">
-                    <el-radio :label="1">是</el-radio>
-                    <el-radio :label="0">否</el-radio>
-                  </el-radio-group>
-                </el-form-item>
-              </el-col>
-              <el-col :span="12">
-                <el-form-item label="是否饮酒" prop="izWineDrink">
-                  <el-radio-group v-model="form.izWineDrink">
-                    <el-radio :label="1">是</el-radio>
-                    <el-radio :label="0">否</el-radio>
-                  </el-radio-group>
-                </el-form-item>
-              </el-col>
-              <el-col :span="12">
-                <el-form-item label="性格" prop="characterDesc">
-                  <el-input v-model="form.characterDesc" placeholder="请输入性格" />
-                </el-form-item>
-              </el-col>
-              <el-col :span="12">
-                <el-form-item label="爱好" prop="fond">
-                  <el-input v-model="form.fond" placeholder="请输入爱好" />
-                </el-form-item>
-              </el-col>
               <el-col :span="24">
-                <el-form-item label="家庭情况" prop="familySituation">
-                  <el-input v-model="form.familySituation" type="textarea" placeholder="请输入内容" />
+                <el-form-item label="备注" prop="remark">
+                  <el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
                 </el-form-item>
               </el-col>
             </el-row>
@@ -253,6 +193,7 @@
 </template>
 
 <script setup name="ContactPerson">
+import { ref, reactive, toRefs, getCurrentInstance, onMounted } from 'vue';
 import { listContactPerson, getContactPerson, delContactPerson, addContactPerson, updateContactPerson } from "@/api/customer/contactPerson";
 
 const { proxy } = getCurrentInstance();
@@ -273,15 +214,15 @@ const data = reactive({
   queryParams: {
     pageNum: 1,
     pageSize: 10,
-    name: null,
-    personnelNumber: null,
-    phoneNumber: null,
-    customerNo: null,
-    customName: null,
-    contactType: null
+    contactName: null,
+    contactNo: null,
+    phone: null,
+    customerId: null,
+    customerName: null,
+    roleId: null
   },
   rules: {
-    name: [
+    contactName: [
       { required: true, message: "姓名不能为空", trigger: "blur" }
     ],
   }
@@ -309,36 +250,21 @@ function cancel() {
 function reset() {
   form.value = {
     id: null,
-    personnelNumber: null,
-    name: null,
-    contactType: null,
-    sex: null,
-    age: null,
-    nativePlace: null,
+    contactNo: null,
+    contactName: null,
+    roleId: null,
+    roleName: null,
+    gender: null,
     birthday: null,
-    workDesc: null,
-    companyNo: null,
-    customerNo: null,
-    customName: null,
-    employmentStatusCode: null,
-    phoneNumber: null,
-    department: null,
-    position: null,
-    landline: null,
-    projectRole: null,
-    izCruxPerson: null,
-    officeAddress: null,
-    workAddress: null,
-    authorityDesc: null,
-    projectRelationDesc: null,
-    homeAddress: null,
-    familyAddress: null,
-    izSmoking: null,
-    izWineDrink: null,
-    familySituation: null,
-    fond: null,
-    characterDesc: null,
-    platformCode: null
+    customerId: null,
+    customerName: null,
+    phone: null,
+    deptId: null,
+    deptName: null,
+    officePhone: null,
+    addressDetail: null,
+    status: null,
+    remark: null
   };
   proxy.resetForm("contactPersonRef");
 }
@@ -414,10 +340,35 @@ function handleDelete(row) {
 
 /** 导出按钮操作 */
 function handleExport() {
-  proxy.download('customer/contactPerson/export', {
+  proxy.download('/customer/customerContact/export', {
     ...queryParams.value
   }, `contactPerson_${new Date().getTime()}.xlsx`)
 }
 
 getList();
 </script>
+
+<style scoped>
+/* 表头背景色和文字样式 */
+:deep(.el-table th.el-table__cell) {
+  background-color: #f8f9fb !important;
+  color: #333 !important;
+  height: 50px;
+  font-weight: normal !important;
+}
+
+/* 联系人名称颜色(原型图中为橙色) */
+.contact-name-text {
+  color: #ff9900;
+}
+
+/* 表格行高调整 */
+:deep(.el-table .el-table__cell) {
+  padding: 8px 0;
+}
+
+/* 操作按钮间距 */
+.el-button--link {
+  margin: 0 5px;
+}
+</style>

+ 77 - 25
src/views/customer/customerPool/index.vue

@@ -31,15 +31,15 @@
 
       <el-row :gutter="10">
         <el-col :span="6">
-          <el-form-item label="业务负责人" prop="managerId" style="width: 100%">
-            <el-select v-model="queryParams.managerId" placeholder="搜索并选择负责人" clearable filterable style="width: 100%">
+          <el-form-item label="业务负责人" prop="salesPersonId" style="width: 100%">
+            <el-select v-model="queryParams.salesPersonId" placeholder="搜索并选择负责人" clearable filterable style="width: 100%">
               <el-option v-for="item in staffOptions" :key="item.staffId" :label="item.staffName" :value="item.staffId" />
             </el-select>
           </el-form-item>
         </el-col>
         <el-col :span="6">
-          <el-form-item label="客服支持" prop="supportId" style="width: 100%">
-            <el-select v-model="queryParams.supportId" placeholder="搜索并选择客服" clearable filterable style="width: 100%">
+          <el-form-item label="客服支持" prop="serviceStaffId" style="width: 100%">
+            <el-select v-model="queryParams.serviceStaffId" placeholder="搜索并选择客服" clearable filterable style="width: 100%">
               <el-option v-for="item in staffOptions" :key="item.staffId" :label="item.staffName" :value="item.staffId" />
             </el-select>
           </el-form-item>
@@ -84,18 +84,19 @@
           <span class="customer-name-link">{{ scope.row.customerName }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="归属公司" align="center" prop="belongCompanyName" min-width="140" show-overflow-tooltip/>
+      <el-table-column label="归属公司" align="center" prop="companyName" min-width="140" show-overflow-tooltip/>
       <el-table-column label="行业" align="center" prop="industryName" width="100" />
       <el-table-column label="企业类型" align="center" prop="enterpriseTypeName" width="100" />
       <el-table-column label="合作等级" align="center" prop="customerLevelName" width="100" />
-      <el-table-column label="业务负责人" align="center" prop="managerName" width="120" />
-      <el-table-column label="客服支持" align="center" prop="supportName" width="120" />
+      <el-table-column label="业务负责人" align="center" prop="salesPersonName" width="120" />
+      <el-table-column label="客服支持" align="center" prop="serviceStaffName" width="120" />
       <el-table-column label="部门" align="center" prop="deptName" width="120" />
-      <el-table-column label="合作状况" align="center" prop="statusName" width="100">
+      <el-table-column label="合作状况" align="center" prop="cooperationName" width="100">
         <template #default="scope">
-          <el-tag type="success">
-            {{ cooperationOptions.find(o => o.dictValue === scope.row.status)?.dictLabel || scope.row.status }}
+          <el-tag type="success" v-if="scope.row.cooperationName">
+            {{ scope.row.cooperationName }}
           </el-tag>
+          <span v-else>{{ scope.row.status }}</span>
         </template>
       </el-table-column>
       <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="140">
@@ -244,15 +245,15 @@
 
         <el-row :gutter="20">
           <el-col :span="8">
-            <el-form-item label="负责人" prop="managerId">
-              <el-select v-model="form.managerId" placeholder="请选择业务负责人" clearable filterable style="width: 100%">
+            <el-form-item label="负责人" prop="salesPersonId">
+              <el-select v-model="form.salesPersonId" placeholder="请选择业务负责人" clearable filterable style="width: 100%">
                 <el-option v-for="item in staffOptions" :key="item.staffId" :label="item.staffName" :value="item.staffId" />
               </el-select>
             </el-form-item>
           </el-col>
           <el-col :span="8">
-            <el-form-item label="客服支持" prop="supportId">
-              <el-select v-model="form.supportId" placeholder="请选择客服支持" clearable filterable style="width: 100%">
+            <el-form-item label="客服支持" prop="serviceStaffId">
+              <el-select v-model="form.serviceStaffId" placeholder="请选择客服支持" clearable filterable style="width: 100%">
                 <el-option v-for="item in staffOptions" :key="item.staffId" :label="item.staffName" :value="item.staffId" />
               </el-select>
             </el-form-item>
@@ -298,6 +299,34 @@
         </div>
       </template>
     </el-dialog>
+
+    <!-- 客户认领对话框 -->
+    <el-dialog :title="claimTitle" v-model="claimOpen" width="500px" append-to-body>
+      <el-form ref="claimRef" :model="claimForm" :rules="claimRules" label-width="120px">
+        <el-form-item label="业务员" prop="salesPersonId">
+          <el-select v-model="claimForm.salesPersonId" placeholder="请选择业务员" clearable filterable style="width: 100%">
+            <el-option v-for="item in staffOptions" :key="item.staffId" :label="item.staffName" :value="item.staffId" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="客服" prop="serviceStaffId">
+          <el-select v-model="claimForm.serviceStaffId" placeholder="请选择客服支持" clearable filterable style="width: 100%">
+            <el-option v-for="item in staffOptions" :key="item.staffId" :label="item.staffName" :value="item.staffId" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="保留已有负责人" prop="keepOldManager">
+          <el-radio-group v-model="claimForm.keepOldManager">
+            <el-radio value="1">是</el-radio>
+            <el-radio value="0">否</el-radio>
+          </el-radio-group>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitClaim">确 定</el-button>
+          <el-button @click="claimOpen = false">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
@@ -316,9 +345,17 @@ const poolList = ref([]);
 const loading = ref(true);
 const showSearch = ref(true);
 const total = ref(0);
-const open = ref(false);
 const title = ref("");
 
+const claimOpen = ref(false);
+const claimTitle = ref("");
+const claimForm = ref({});
+const claimRules = ref({
+  salesPersonId: [{ required: true, message: "业务员不能为空", trigger: "change" }],
+  serviceStaffId: [{ required: true, message: "客服不能为空", trigger: "change" }],
+  keepOldManager: [{ required: true, message: "请选择是否保留已有负责人", trigger: "change" }]
+});
+
 // 地区数据(示例数据,生产环境下应通过接口获查询)
 const regionArr = ref([]);
 const regionOptions = ref([
@@ -368,8 +405,8 @@ const data = reactive({
       customerName: undefined,
       customerLevelId: undefined,
       industryId: undefined,
-      managerId: undefined,
-      supportId: undefined,
+      salesPersonId: undefined,
+      serviceStaffId: undefined,
       deptId: undefined,
       status: undefined,
       enterpriseTypeId: undefined,
@@ -443,8 +480,8 @@ function reset() {
     customerCategoryId: undefined,
     industryId: undefined,
     customerLevelId: undefined,
-    managerId: undefined,
-    supportId: undefined,
+    salesPersonId: undefined,
+    serviceStaffId: undefined,
     deptId: undefined,
     status: '1',  // 默认未合作
     telephone: undefined,
@@ -512,12 +549,27 @@ function handleDetail(row) {
 
 /** 认领按钮操作 */
 function handleClaim(row) {
-  proxy.$modal.confirm('是否确定认领该客户:“' + row.customerName + '”?').then(function() {
-    return claimPool(row.id);
-  }).then(() => {
-    proxy.$modal.msgSuccess("认领成功");
-    getList();
-  }).catch(() => {});
+  claimForm.value = {
+    id: row.id,
+    salesPersonId: undefined,
+    serviceStaffId: undefined,
+    keepOldManager: '0'
+  };
+  claimTitle.value = "您正在认领公海客户,请录入客户认领信息";
+  claimOpen.value = true;
+}
+
+/** 提交认领 */
+function submitClaim() {
+  proxy.$refs["claimRef"].validate(valid => {
+    if (valid) {
+      claimPool(claimForm.value).then(response => {
+        proxy.$modal.msgSuccess("认领成功");
+        claimOpen.value = false;
+        getList();
+      }).catch(() => {});
+    }
+  });
 }
 
 getList();

+ 20 - 13
src/views/customer/highseas/CustomerInfo.vue

@@ -1,19 +1,20 @@
 <template>
   <el-drawer v-model="visible" title="客户详情" size="80%" class="customer-info-drawer" :with-header="false">
-    <div class="info-container" v-if="customerData">
-      <!-- 头部布局 -->
-      <div class="info-header">
-        <div class="header-left">
-          <span class="company-name">{{ customerData.customerName }}</span>
-          <!-- 头部展示:显示合作状态(直接引用后端名称字段) -->
-          <span class="customer-type" v-if="customerData.cooperationName">
-            合作状态:{{ customerData.cooperationName }}
-          </span>
-        </div>
-        <div class="header-right">
-          <el-icon class="close-icon" @click="handleClose"><Close /></el-icon>
+    <div class="info-container" v-loading="loading" element-loading-text="数据加载中...">
+      <div v-if="customerData">
+        <!-- 头部布局 -->
+        <div class="info-header">
+          <div class="header-left">
+            <span class="company-name">{{ customerData.customerName }}</span>
+            <!-- 头部展示:显示合作状态(直接引用后端名称字段) -->
+            <span class="customer-type" v-if="customerData.cooperationName">
+              合作状态:{{ customerData.cooperationName }}
+            </span>
+          </div>
+          <div class="header-right">
+            <el-icon class="close-icon" @click="handleClose"><Close /></el-icon>
+          </div>
         </div>
-      </div>
 
       <!-- 主体内容 -->
       <div class="info-body">
@@ -328,6 +329,7 @@
           </div>
         </div>
       </div>
+      </div>
     </div>
   </el-drawer>
 
@@ -554,6 +556,7 @@ const props = defineProps({
 const emit = defineEmits(['close', 'edit', 'success']);
 
 const visible = ref(false);
+const loading = ref(false);
 const isEdit = ref(false);
 const customerData = ref(null); 
 const contactList = ref([]);
@@ -867,6 +870,7 @@ const handleAddCare = () => {
 
 const loadData = async (id) => {
   if (!id) return;
+  loading.value = true;
   // 重置状态
   isEdit.value = false;
   // 重置 URL
@@ -911,6 +915,9 @@ const loadData = async (id) => {
     
     // 加载团队成员:移出 bizVo 嵌套,确保始终尝试加载
     refreshTeamMembers();
+    loading.value = false;
+  }).catch(() => {
+    loading.value = false;
   });
 };
 

+ 50 - 28
src/views/customer/highseas/index.vue

@@ -74,7 +74,6 @@
             <div class="search-btns-area">
               <el-button type="default" icon="Search" @click="handleQuery">搜索</el-button>
               <el-button icon="Refresh" @click="resetQuery">重置</el-button>
-              <el-button type="primary" icon="Plus" @click="handleAdd" v-hasPermi="['customer:pool:add']">新增</el-button>
             </div>
           </el-col>
         </el-row>
@@ -91,7 +90,7 @@
         <el-table-column label="客户编号" align="center" prop="customerNo" width="100" />
         <el-table-column align="center" min-width="250" show-overflow-tooltip>
           <template #header>
-            <span class="required-star">*</span>客户名称
+            <span class="required-star"></span>客户名称
           </template>
           <template #default="scope">
             <span class="customer-name-text" @click="handleDetail(scope.row)">{{ scope.row.customerName }}</span>
@@ -104,15 +103,15 @@
         <el-table-column label="业务负责人" align="center" prop="salesPersonName" width="120" />
         <el-table-column align="center" prop="serviceStaffName" width="120">
           <template #header>
-            <span class="required-star">*</span>客服支持
+            <span class="required-star"></span>客服支持
           </template>
         </el-table-column>
         <el-table-column label="部门" align="center" prop="deptName" width="120" />
         <el-table-column label="合作状态" align="center" prop="cooperationName" width="100" />
         <el-table-column label="操作" align="center" width="130" fixed="right">
           <template #default="scope">
-            <el-button link type="primary" class="op-link" @click="handleDetail(scope.row)">详情</el-button>
-            <el-button link type="primary" class="op-link" @click="handleClaim(scope.row)">认领</el-button>
+            <el-button link type="info" class="op-link detail-link" @click="handleDetail(scope.row)">详情</el-button>
+            <el-button link type="primary" class="op-link claim-link" @click="handleClaim(scope.row)">认领</el-button>
           </template>
         </el-table-column>
       </el-table>
@@ -155,11 +154,12 @@
 <script setup name="CustomerHighSeas">
 import { ref, reactive, onMounted, getCurrentInstance } from 'vue';
 import { useRouter } from 'vue-router';
-import { listPool as listHighSeas, claimPool } from "@/api/customer/customerPool";
+import { listPool, claimPool } from "@/api/customer/customerPool";
 import { listIndustryCategory } from "@/api/customer/industryCategory";
 import { listDictByType } from "@/api/customer/customerDict";
 import { listLevel } from "@/api/customer/customerLevel";
 import { selectStaffOptionList } from "@/api/customer/crmStaff";
+import { debounce } from 'lodash-es';
 import CustomerInfo from "./CustomerInfo.vue";
 
 const { proxy } = getCurrentInstance();
@@ -192,7 +192,8 @@ const queryParams = reactive({
   cooperation: undefined,
   enterpriseSource: undefined,
   procurementType: undefined,
-  isApprove: undefined
+  isApprove: undefined,
+  platformCode: undefined
 });
 
 // 认领相关数据
@@ -215,7 +216,8 @@ const claimRules = {
 const getOptions = async () => {
   industryOptions.value = (await listIndustryCategory()).data || [];
   levelOptions.value = (await listLevel()).rows || [];
-  staffOptions.value = (await selectStaffOptionList()).data || [];
+  const staffRes = await selectStaffOptionList();
+  staffOptions.value = staffRes.data || [];
   
   // 企业类型和合作状态通常来自字典
   listDictByType('enterprise_type').then(response => {
@@ -229,7 +231,7 @@ const getOptions = async () => {
 /** 查询公海列表数据 */
 const getList = () => {
   loading.value = true;
-  listHighSeas(queryParams).then(response => {
+  listPool(queryParams).then(response => {
     dataList.value = response.rows;
     total.value = response.total;
     loading.value = false;
@@ -274,22 +276,24 @@ const cancelClaim = () => {
 };
 
 const submitClaimForm = () => {
-  proxy.$refs["claimRef"].validate(valid => {
+  proxy.$refs["claimRef"].validate(async valid => {
     if (valid) {
       loading.value = true;
-      const params = { 
-        id: claimForm.customerId,
-        salespersonId: claimForm.salespersonId,
-        serviceStaffId: claimForm.serviceStaffId,
-        keepOwner: claimForm.keepOwner
-      };
-      claimPool(params).then(response => {
+      try {
+        await claimPool({
+          id: claimForm.customerId,
+          salesPersonId: claimForm.salespersonId,
+          serviceStaffId: claimForm.serviceStaffId,
+          keepOldManager: claimForm.keepOwner
+        });
         proxy.$modal.msgSuccess("认领成功");
         claimOpen.value = false;
         getList();
-      }).finally(() => {
+      } catch (err) {
+        console.error(err);
+      } finally {
         loading.value = false;
-      });
+      }
     }
   });
 };
@@ -375,7 +379,7 @@ onMounted(() => {
   margin: 20px 0 15px 0;
   .list-title {
     font-size: 16px;
-    font-weight: bold;
+    font-weight: normal;
     color: #1e293b;
     border-left: 4px solid #3b82f6;
     padding-left: 12px;
@@ -398,7 +402,7 @@ onMounted(() => {
     background-color: #f8fafc !important;
     color: #1e293b;
     font-size: 13px;
-    font-weight: 600;
+    font-weight: normal;
     height: 44px;
   }
   :deep(.el-table__row) {
@@ -408,26 +412,44 @@ onMounted(() => {
       color: #334155;
     }
   }
+  // 修复固定列左侧边框缺失问题
+  :deep(.el-table__fixed-right) {
+    border-left: 1px solid #ebeef5;
+    height: 100% !important; // 确保边框高度撑满
+  }
+  :deep(.el-table__fixed-right-patch) {
+    border-left: 1px solid #ebeef5;
+    background-color: #f8fafc !important;
+  }
 }
 
 .customer-name-text {
-  color: #ea580c; 
-  font-weight: 600;
+  color: #409eff; 
+  font-weight: normal;
   cursor: pointer;
   transition: all 0.2s;
   &:hover {
-    color: #c2410c;
-    text-decoration: underline;
+    color: #66b1ff;
   }
 }
 
 .op-link {
-  font-weight: 500;
-  color: #3b82f6;
+  font-weight: normal;
   margin: 0 6px;
   font-size: 13px;
+}
+
+.detail-link {
+  color: #909399 !important;
+  &:hover {
+    color: #606266 !important;
+  }
+}
+
+.claim-link {
+  color: #409eff !important;
   &:hover {
-    color: #2563eb;
+    color: #66b1ff !important;
   }
 }
 

+ 1 - 1
src/views/customer/valid/ValidCustomerInfo.vue

@@ -1,6 +1,6 @@
 <template>
   <el-drawer v-model="visible" size="90%" class="customer-info-drawer" :with-header="false">
-    <div class="info-container" v-loading="loading">
+    <div class="info-container" v-loading="loading" element-loading-text="数据加载中...">
       <template v-if="customerData">
       <!-- 头部布局 -->
       <div class="info-header">

+ 205 - 153
src/views/customer/valid/index.vue

@@ -1,152 +1,150 @@
 <template>
   <div class="app-container">
-    <!-- 搜索区域 -->
-    <div class="search-section">
+    <el-card shadow="never" class="search-card">
       <el-form :model="queryParams" ref="queryRef" label-width="90px">
-        <el-row :gutter="15">
+        <!-- 第一行 -->
+        <el-row :gutter="20">
           <el-col :span="6">
-            <el-form-item label="归属公司" prop="belongCompanyId">
-              <el-select v-model="queryParams.belongCompanyId" placeholder="请选择" clearable class="w100">
+            <el-form-item label="归属公司" prop="belongCompanyId" class="custom-form-item">
+              <el-select v-model="queryParams.belongCompanyId" placeholder="请选择" clearable style="width: 100%">
                 <el-option v-for="item in companyOptions" :key="item.id" :label="item.companyName" :value="item.id" />
               </el-select>
             </el-form-item>
           </el-col>
           <el-col :span="6">
-            <el-form-item label="客户编号" prop="customerNo">
+            <el-form-item label="客户编号" prop="customerNo" class="custom-form-item">
               <el-input v-model="queryParams.customerNo" placeholder="请输入客户编号" clearable />
             </el-form-item>
           </el-col>
           <el-col :span="6">
-            <el-form-item label="客户名称" prop="customerName">
+            <el-form-item label="客户名称" prop="customerName" class="custom-form-item">
               <el-input v-model="queryParams.customerName" placeholder="请输入客户名称" clearable />
             </el-form-item>
           </el-col>
           <el-col :span="6">
-            <el-form-item label="客户等级" prop="customerLevelId">
-              <el-select v-model="queryParams.customerLevelId" placeholder="请选择" clearable class="w100">
+            <el-form-item label="客户等级" prop="customerLevelId" class="custom-form-item">
+              <el-select v-model="queryParams.customerLevelId" placeholder="请选择" clearable style="width: 100%">
                 <el-option v-for="item in levelOptions" :key="item.id" :label="item.levelName" :value="item.id" />
               </el-select>
             </el-form-item>
           </el-col>
         </el-row>
 
-        <el-row :gutter="15">
+        <!-- 第二行 -->
+        <el-row :gutter="20" class="mt-15">
           <el-col :span="6">
-            <el-form-item label="行业" prop="industryCategoryId">
-              <el-select v-model="queryParams.industryCategoryId" placeholder="请选择" clearable class="w100">
+            <el-form-item label="行业" prop="industryCategoryId" class="custom-form-item">
+              <el-select v-model="queryParams.industryCategoryId" placeholder="请选择" clearable style="width: 100%">
                 <el-option v-for="item in industryOptions" :key="item.id" :label="item.industryCategoryName" :value="item.id" />
               </el-select>
             </el-form-item>
           </el-col>
           <el-col :span="6">
-            <el-form-item label="业务负责人" prop="salesPersonId">
-              <el-select v-model="queryParams.salesPersonId" placeholder="请选择" clearable class="w100">
+            <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-select>
             </el-form-item>
           </el-col>
           <el-col :span="6">
-            <el-form-item label="客服支持" prop="serviceStaffId">
-              <el-select v-model="queryParams.serviceStaffId" placeholder="请选择" clearable class="w100">
+            <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-select>
             </el-form-item>
           </el-col>
           <el-col :span="6">
-            <el-form-item label="部门" prop="belongingDepartmentId">
+            <el-form-item label="部门" prop="belongingDepartmentId" class="custom-form-item">
               <el-input v-model="queryParams.belongingDepartmentId" placeholder="请输入部门" clearable />
             </el-form-item>
           </el-col>
         </el-row>
 
-        <el-row :gutter="15">
+        <!-- 第三行 -->
+        <el-row :gutter="20" class="mt-15">
           <el-col :span="6">
-            <el-form-item label="合作状态" prop="cooperation">
-              <el-select v-model="queryParams.cooperation" placeholder="请选择" clearable class="w100">
+            <el-form-item label="合作状态" prop="cooperation" class="custom-form-item">
+              <el-select v-model="queryParams.cooperation" placeholder="请选择" clearable style="width: 100%">
                 <el-option v-for="item in cooperationOptions" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue" />
               </el-select>
             </el-form-item>
           </el-col>
           <el-col :span="6">
-            <el-form-item label="企业类型" prop="enterpriseTypeId">
-              <el-select v-model="queryParams.enterpriseTypeId" placeholder="请选择" clearable class="w100">
+            <el-form-item label="企业类型" prop="enterpriseTypeId" class="custom-form-item">
+              <el-select v-model="queryParams.enterpriseTypeId" placeholder="请选择" clearable style="width: 100%">
                 <el-option v-for="item in corpTypeOptions" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue" />
               </el-select>
             </el-form-item>
           </el-col>
           <el-col :span="12">
-            <div class="search-btn-container">
-              <el-button icon="Search" @click="handleQuery">搜索</el-button>
+            <div class="search-btns-area">
+              <el-button type="default" icon="Search" @click="handleQuery">搜索</el-button>
               <el-button icon="Refresh" @click="resetQuery">重置</el-button>
-              <el-button type="primary" icon="Plus" @click="handleAdd" class="btn-blue">新增</el-button>
             </div>
           </el-col>
         </el-row>
       </el-form>
-    </div>
-
-    <!-- 列表标题与工具栏 -->
-    <div class="list-tool-row">
-      <div class="tabs-left">
-        <div 
-          v-for="tab in tabs" 
-          :key="tab.key" 
-          :class="['tab-nav-item', activeTab === tab.key ? 'active' : '']"
-          @click="handleTabClick(tab.key)"
-        >
-          {{ tab.label }}
-          <el-badge v-if="tab.badge" :value="tab.badge" :max="99" class="count-badge" />
+    </el-card>
+
+    <el-card shadow="never" class="table-card">
+      <div class="list-tool-row">
+        <div class="tabs-left">
+          <span class="list-main-title">客户信息列表</span>
+          <div 
+            v-for="tab in tabs" 
+            :key="tab.key" 
+            :class="['tab-nav-item', activeTab === tab.key ? 'active' : '']"
+            @click="handleTabClick(tab.key)"
+          >
+            {{ tab.label }}
+            <el-badge v-if="tab.badge" :value="tab.badge" :max="99" class="count-badge" />
+          </div>
+        </div>
+        <div class="tools-right">
+          <el-button type="primary" icon="Back" class="blue-btn">退回到客户公海池</el-button>
+          <el-button type="primary" icon="Switch" class="blue-btn">转移业务员</el-button>
+          <el-button type="primary" icon="UserFilled" class="blue-btn">转移客服人员</el-button>
         </div>
       </div>
-      <div class="tools-right">
-        <el-button type="primary" icon="Back" class="blue-action-btn" @click="handleReturnHighSeas">返回客户公海池</el-button>
-        <el-button type="primary" icon="Switch" class="blue-action-btn" @click="handleTransferSales">转移业务员</el-button>
-        <el-button type="primary" icon="UserFilled" class="blue-action-btn" @click="handleTransferSupport">转移客服人员</el-button>
-      </div>
-    </div>
 
-    <!-- 数据表格 -->
-    <div class="table-outer">
       <el-table 
         v-loading="loading" 
         :data="dataList" 
         @selection-change="handleSelectionChange"
         border
-        fit
-        highlight-current-row
+        class="custom-table"
       >
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column label="客户编号" align="center" prop="customerNo" width="100" />
-        <el-table-column label="客户名称" align="left" prop="customerName" min-width="280" show-overflow-tooltip>
+        <el-table-column label="客户名称" align="left" min-width="250" show-overflow-tooltip>
           <template #default="scope">
-            <span class="customer-link" @click="handleDetail(scope.row)">{{ scope.row.customerName }}</span>
+            <span class="customer-name-text" @click="handleDetail(scope.row)">{{ scope.row.customerName }}</span>
           </template>
         </el-table-column>
         <el-table-column label="归属公司" align="center" prop="companyName" min-width="180" show-overflow-tooltip />
-        <el-table-column label="行业" align="center" prop="industryName" width="100" />
-        <el-table-column label="企业类型" align="center" prop="enterpriseTypeName" width="100" />
-        <el-table-column label="合作等级" align="center" prop="creditLevelName" width="100" />
-        <el-table-column label="业务负责人" align="center" prop="salesPersonName" width="100" />
-        <el-table-column label="客服支持" align="center" prop="serviceStaffName" width="100" />
-        <el-table-column label="部门" align="center" prop="deptName" width="100" show-overflow-tooltip />
-        <el-table-column label="合作状" align="center" prop="cooperationName" width="100" />
-        <el-table-column label="操作" align="center" width="80" fixed="right">
+        <el-table-column label="行业" align="center" prop="industryName" width="120" />
+        <el-table-column label="企业类型" align="center" prop="enterpriseTypeName" width="120" />
+        <el-table-column label="合作等级" align="center" prop="customerLevelName" width="100" />
+        <el-table-column label="业务负责人" align="center" prop="salesPersonName" width="120" />
+        <el-table-column label="客服支持" align="center" prop="serviceStaffName" width="120" />
+        <el-table-column label="部门" align="center" prop="deptName" width="120" show-overflow-tooltip />
+        <el-table-column label="合作状" align="center" prop="cooperationName" width="100" />
+        <el-table-column label="操作" align="center" width="100" fixed="right">
           <template #default="scope">
-            <el-button link type="primary" @click="handleDetail(scope.row)">详情</el-button>
+            <el-button link type="info" class="op-link detail-link" @click="handleDetail(scope.row)">详情</el-button>
           </template>
         </el-table-column>
       </el-table>
 
-      <div class="pagination-wrapper">
-        <pagination 
-          v-show="total > 0" 
-          :total="total" 
-          v-model:page="queryParams.pageNum" 
-          v-model:limit="queryParams.pageSize" 
-          @pagination="getList" 
-        />
-      </div>
-    </div>
+      <pagination 
+        v-show="total > 0" 
+        :total="total" 
+        v-model:page="queryParams.pageNum" 
+        v-model:limit="queryParams.pageSize" 
+        layout="total, sizes, prev, pager, next, jumper"
+        @pagination="getList" 
+      />
+    </el-card>
 
     <!-- 客户详情组件 (有效客户专用) -->
     <valid-customer-info ref="detailRef" />
@@ -161,6 +159,7 @@ import { listIndustryCategory } from "@/api/customer/industryCategory";
 import { listLevel } from "@/api/customer/customerLevel";
 import { selectStaffOptionList } from "@/api/customer/crmStaff";
 import { listDictByType } from "@/api/customer/customerDict";
+import { debounce } from 'lodash-es';
 import ValidCustomerInfo from './ValidCustomerInfo.vue';
 
 const { proxy } = getCurrentInstance();
@@ -180,9 +179,8 @@ const levelOptions = ref([]);
 const corpTypeOptions = ref([]);
 const cooperationOptions = ref([]);
 
-const activeTab = ref('list');
+const activeTab = ref('all');
 const tabs = [
-  { label: '客户信息列表', key: 'list' },
   { label: '我负责的客户', key: 'mine' },
   { label: '我参与的客户', key: 'involved' },
   { label: '全部客户', key: 'all', badge: 99 }
@@ -190,6 +188,16 @@ const tabs = [
 
 const handleTabClick = (key) => {
   activeTab.value = key;
+  // 重置负责人相关的过滤参数
+  queryParams.salesPersonId = undefined;
+  queryParams.serviceStaffId = undefined;
+
+  if (key === 'mine') {
+    // 假设后端会根据这个标记自动过滤当前用户负责的,或者我们需要传入当前用户 ID
+    // 既然用户要求和公海一样接口,这里可能需要传入 salesPersonId
+    // 这里暂时留个注释,通常需要从 store 获取当前用户信息
+    // queryParams.salesPersonId = useUserStore().userId;
+  }
   handleQuery();
 };
 
@@ -205,11 +213,15 @@ const queryParams = reactive({
   serviceStaffId: undefined,
   belongingDepartmentId: undefined,
   cooperation: undefined,
-  enterpriseTypeId: undefined
+  enterpriseTypeId: undefined,
+  'params[isHighSeas]': 'false'
 });
 
 const getList = () => {
   loading.value = true;
+  // 核心逻辑:有效客户必须是非公海客户(即已分配负责人和客服)
+  queryParams['params[isHighSeas]'] = 'false';
+  
   listValidCustomer(queryParams).then(res => {
     dataList.value = res.rows;
     total.value = res.total;
@@ -251,7 +263,10 @@ const handleTransferSupport = () => {
   }).catch(() => {});
 };
 
-const handleQuery = () => { getList(); };
+const handleQuery = debounce(() => { 
+  queryParams.pageNum = 1;
+  getList(); 
+}, 300);
 const resetQuery = () => { 
   proxy.resetForm("queryRef"); 
   handleQuery(); 
@@ -264,8 +279,9 @@ const initOptions = async () => {
   listIndustryCategory().then(res => industryOptions.value = res.data || []);
   listLevel().then(res => levelOptions.value = res.rows || []);
   selectStaffOptionList().then(res => staffOptions.value = res.data || []);
-  listDictByType("crm_enterprise_type").then(res => corpTypeOptions.value = res.data || []);
-  listDictByType("crm_cooperation_status").then(res => cooperationOptions.value = res.data || []);
+  // 修正字典类型名称,与公海保持一致
+  listDictByType("enterprise_type").then(res => corpTypeOptions.value = res.data || []);
+  listDictByType("cooperation_status").then(res => cooperationOptions.value = res.data || []);
 };
 
 onMounted(() => {
@@ -276,151 +292,187 @@ onMounted(() => {
 
 <style scoped lang="scss">
 .app-container {
-  padding: 15px;
-  background-color: #f7f8fa;
+  padding: 24px;
+  background-color: #f8fafc;
   min-height: 100vh;
 }
 
-/* 搜索容器 */
-.search-section {
-  background: #fff;
-  padding: 24px 20px 4px 20px;
-  border-radius: 4px;
-  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
-  margin-bottom: 5px;
+.search-card {
+  margin-bottom: 20px;
+  border-radius: 8px;
+  border: none;
+  box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
+}
 
-  .w100 { width: 100%; }
-  
-  :deep(.el-form-item) {
-    margin-bottom: 20px;
-    .el-form-item__label {
-      font-weight: 500;
-      color: #666;
+.custom-form-item {
+  margin-bottom: 0px;
+  :deep(.el-form-item__label) {
+    font-weight: 500;
+    color: #475569;
+    padding-right: 12px;
+  }
+  :deep(.el-input__wrapper), :deep(.el-select__wrapper) {
+    box-shadow: 0 0 0 1px #cbd5e1 inset !important;
+    &:hover, &.is-focus {
+      box-shadow: 0 0 0 1px #3b82f6 inset !important;
     }
   }
 }
 
-.search-btn-container {
+.search-btns-area {
   display: flex;
   justify-content: flex-start;
   align-items: center;
   gap: 12px;
-  height: 32px;
-  margin-top: 1px;
-  
+  padding-left: 20px;
   .el-button {
+    border-radius: 2px;
     height: 32px;
-    border-radius: 4px;
-    padding: 0 16px;
+    padding: 0 15px;
     font-size: 14px;
-    border: 1px solid #dcdfe6;
+    transition: all 0.2s;
+  }
+  .el-button--default, .el-button:not(.el-button--primary) {
+    border: 1px solid #d1d5db !important;
+    color: #374151 !important;
+    background-color: #fff;
+    &:hover {
+      background-color: #f9fafb;
+      border-color: #9ca3af !important;
+    }
   }
+}
 
-  .btn-blue {
-    background-color: #3b82f6;
-    border-color: #3b82f6;
-    color: #fff;
-    &:hover { background-color: #2563eb; }
+.table-card {
+  border-radius: 4px;
+  border: 1px solid #e2e8f0;
+  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
+  :deep(.el-card__body) {
+    padding: 0;
   }
 }
 
-/* 列表标题栏 */
+/* 列表标题栏与工具栏 */
 .list-tool-row {
   display: flex;
   justify-content: space-between;
   align-items: center;
   padding: 0 20px;
   background: #fff;
-  height: 50px;
-  border-radius: 4px 4px 0 0;
-  border-bottom: 1px solid #f0f2f5;
+  height: 60px;
+  border-bottom: 1px solid #f1f5f9;
 }
 
 .tabs-left {
   display: flex;
-  gap: 15px;
+  gap: 12px;
   height: 100%;
+  align-items: center;
+
+  .list-main-title {
+    font-size: 15px;
+    font-weight: normal;
+    color: #1e293b;
+    margin-right: 20px;
+  }
 
   .tab-nav-item {
     display: flex;
     align-items: center;
-    position: relative;
     cursor: pointer;
     font-size: 14px;
     color: #333;
-    padding: 0 15px;
-    border: 1px solid transparent;
-    border-bottom: none;
-    margin-bottom: -1px;
+    padding: 6px 16px;
+    border: 1px solid #e2e8f0;
+    border-radius: 2px;
+    transition: all 0.2s;
+    background-color: #fff;
 
     &.active {
-      background-color: #fff;
-      font-weight: bold;
-      border: 1px solid #e4e7ed;
-      border-bottom-color: #fff;
-      border-radius: 4px 4px 0 0;
+      color: #333;
+      font-weight: normal;
+      border-color: #333;
+    }
+    &:hover:not(.active) {
+      border-color: #cbd5e1;
     }
   }
   
   .count-badge {
     margin-left: 5px;
     :deep(.el-badge__content) {
-      background-color: #f56c6c;
-      transform: scale(0.8) translateY(-2px);
+      background-color: #ef4444;
+      transform: scale(0.8) translateY(-4px);
     }
   }
 }
 
 .tools-right {
   display: flex;
-  gap: 10px;
-
-  .blue-action-btn {
-    background-color: #3b82f6;
-    border-color: #3b82f6;
-    height: 30px;
-    padding: 0 15px;
-    font-size: 13px;
+  gap: 12px;
+  .blue-btn {
+    background-color: #3b82f6 !important;
+    border-color: #3b82f6 !important;
+    height: 36px;
+    padding: 0 16px;
+    font-size: 14px;
     border-radius: 4px;
+    display: flex;
+    align-items: center;
+    gap: 6px;
+    &:hover {
+      background-color: #2563eb !important;
+    }
   }
 }
 
-/* 表格外部容器 */
-.table-outer {
-  background: #fff;
-  padding: 15px 20px 20px 20px;
-  border-radius: 0 0 4px 4px;
-  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
-
-  :deep(.el-table__header) {
-    th {
-      background-color: #f8fafc !important;
-      color: #333;
-      font-weight: bold;
-      height: 44px;
+.custom-table {
+  border-radius: 0;
+  overflow: hidden;
+  :deep(th.el-table__cell) {
+    background-color: #f8fafc !important;
+    color: #1e293b;
+    font-size: 13px;
+    font-weight: normal;
+    height: 48px;
+  }
+  :deep(.el-table__row) {
+    height: 52px;
+    td {
       font-size: 13px;
+      color: #334155;
     }
   }
-  
-  :deep(.el-table__row) {
-    height: 48px;
-    td { font-size: 13px; color: #333; }
+  :deep(.el-table__fixed-right) {
+    border-left: 1px solid #ebeef5;
+    height: 100% !important;
   }
 }
 
-.customer-link {
-  color: #ea580c;
+.customer-name-text {
+  color: #ea5413; 
   cursor: pointer;
-  font-weight: 600;
-  
+  font-weight: normal;
   &:hover {
+    color: #f97316;
     text-decoration: underline;
   }
 }
 
-.pagination-wrapper {
-  margin-top: 20px;
-  display: flex;
-  justify-content: flex-end;
+.op-link {
+  font-weight: normal;
+  margin: 0 6px;
+  font-size: 13px;
+}
+
+.detail-link {
+  color: #94a3b8 !important;
+  &:hover {
+    color: #64748b !important;
+  }
+}
+
+.mt-15 {
+  margin-top: 15px;
 }
 </style>

+ 63 - 27
src/views/saleManage/accountsReceivable/index.vue

@@ -1,29 +1,33 @@
 <template>
   <div class="app-container">
     <el-card shadow="never" class="search-card">
-      <el-form :model="queryParams" ref="queryRef" inline label-width="80px">
-        <el-form-item label="联系人" prop="contactPerson">
-          <el-input v-model="queryParams.contactPerson" placeholder="请输入联系人" clearable style="width: 220px;" />
-        </el-form-item>
-        <el-form-item label="电话" prop="telephone">
-          <el-input v-model="queryParams.telephone" placeholder="请输入电话" clearable style="width: 220px;" />
-        </el-form-item>
-        <el-form-item label="到期日期" prop="dueDate">
-          <el-date-picker v-model="queryParams.dueDate" type="date" value-format="YYYY-MM-DD" placeholder="请选择" style="width: 220px;" />
-        </el-form-item>
-        <el-form-item label="是否欠款" prop="izArrears">
-          <el-select v-model="queryParams.izArrears" placeholder="请选择" clearable style="width: 180px;">
-             <el-option label="是" :value="1" />
-             <el-option label="否" :value="0" />
-          </el-select>
-        </el-form-item>
-        <el-form-item label-width="0">
-          <div class="search-btns">
-            <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
-            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
-            <el-button type="primary" icon="Plus" @click="handleAdd" class="btn-new">新增</el-button>
+      <el-form :model="queryParams" ref="queryRef" class="custom-search-form">
+        <div class="search-row">
+          <div class="search-item">
+            <span class="search-label">联系人</span>
+            <el-input v-model="queryParams.contactPerson" placeholder="请输入联系人" clearable style="width: 250px;" />
           </div>
-        </el-form-item>
+          <div class="search-item">
+            <span class="search-label">电话</span>
+            <el-input v-model="queryParams.telephone" placeholder="请输入电话" clearable style="width: 220px;" />
+          </div>
+          <div class="search-item">
+            <span class="search-label">到期日期</span>
+            <el-date-picker v-model="queryParams.dueDate" type="date" value-format="YYYY-MM-DD" placeholder="请选择" style="width: 220px;" />
+          </div>
+          <div class="search-item">
+            <span class="search-label">是否欠款</span>
+            <el-select v-model="queryParams.izArrears" placeholder="请选择" clearable style="width: 220px;">
+               <el-option label="是" :value="1" />
+               <el-option label="否" :value="0" />
+            </el-select>
+          </div>
+        </div>
+        <div class="search-btns">
+          <el-button icon="Search" @click="handleQuery">搜索</el-button>
+          <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+          <el-button type="primary" icon="Plus" @click="handleAdd">新增</el-button>
+        </div>
       </el-form>
     </el-card>
 
@@ -324,18 +328,50 @@ onMounted(() => {
 .app-container { padding: 20px; background-color: #f7f9fb; min-height: calc(100vh - 84px); }
 
 .search-card { 
-  margin-bottom: 20px; border: none; background: #fff; border-radius: 8px;
-  :deep(.el-form-item) { margin-bottom: 0px; }
+  margin-bottom: 10px; border: none; background: #fff; border-radius: 8px; padding: 10px 0;
 }
 
-.search-btns { margin-left: 20px; display: flex; gap: 12px; }
+.custom-search-form {
+  padding: 0 10px;
+}
+
+.search-row {
+  display: flex; flex-wrap: wrap; gap: 45px; align-items: center; margin-bottom: 20px;
+}
+
+.search-item {
+  display: flex; align-items: center; gap: 12px;
+  .search-label { font-size: 14px; color: #333; white-space: nowrap; }
+  :deep(.el-input__wrapper), :deep(.el-select__wrapper) {
+    box-shadow: 0 0 0 1px #e4e7ed inset;
+    &:hover { box-shadow: 0 0 0 1px #409eff inset; }
+  }
+}
+
+.search-btns { 
+  display: flex; gap: 12px; justify-content: flex-start; align-items: center;
+  :deep(.el-button) {
+    height: 32px; padding: 0 16px; font-weight: 500; border-radius: 4px;
+  }
+  :deep(.el-button--default) {
+    color: #333; border-color: #dcdfe6;
+    &:hover { border-color: #409eff; color: #409eff; }
+  }
+  :deep(.el-button--primary) {
+    background-color: #409eff; border-color: #409eff; color: #fff !important;
+    &:hover { background-color: #66b1ff; border-color: #66b1ff; color: #fff !important; }
+  }
+}
 
 .table-heading {
-  font-size: 16px; font-weight: 600; color: #475569; padding: 10px 5px; margin-bottom: 10px; border-bottom: 1px solid #e2e8f0;
+  font-size: 14px; font-weight: 400; color: var(--el-text-color-primary); padding: 0 4px; margin: 12px 0 16px;
 }
 
 .table-card { border: none; }
-.standard-table :deep(th.el-table__cell) { background-color: #f8fafc !important; color: #475569; font-weight: 600; }
+.standard-table {
+  :deep(th.el-table__cell) { background-color: var(--el-fill-color-light) !important; color: var(--el-text-color-regular); font-weight: 600; font-size: 13px; }
+  :deep(td.el-table__cell) { font-size: 13px; color: var(--el-text-color-primary); }
+}
 
 .dialog-footer { display: flex; justify-content: flex-end; gap: 15px; }
 

+ 338 - 97
src/views/saleManage/leads/index.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="app-container">
     <el-card shadow="never" class="search-card">
-      <el-form :model="queryParams" ref="queryRef" label-width="100px">
+      <el-form :model="queryParams" ref="queryRef" label-width="85px">
         <!-- 第一行 -->
         <el-row :gutter="24">
           <el-col :span="6">
@@ -88,26 +88,30 @@
         </el-row>
 
         <!-- 第三行 -->
-        <el-row :gutter="24" style="margin-top: 10px;">
+        <el-row :gutter="24" style="margin-top: 5px;">
           <el-col :span="6">
             <el-form-item label="时间范围" prop="dateRange">
               <el-date-picker v-model="dateRange" type="daterange" range-separator="To" start-placeholder="开始时间" end-placeholder="结束时间" style="width: 100%" />
             </el-form-item>
           </el-col>
           <el-col :span="18">
-            <div class="search-btns" style="padding-left: 10px;">
-              <el-button type="default" icon="Search" @click="handleQuery">搜索</el-button>
-              <el-button icon="Refresh" @click="resetQuery">重置</el-button>
-              <el-button type="primary" icon="Plus" @click="handleAdd" class="btn-new">新增</el-button>
-            </div>
+            <el-form-item label-width="0">
+              <div class="search-btns">
+                <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+                <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+                <el-button type="primary" icon="Plus" @click="handleAdd" class="btn-new">新增</el-button>
+              </div>
+            </el-form-item>
           </el-col>
         </el-row>
       </el-form>
     </el-card>
 
     <div class="summary-stripe">
-      <span class="stripe-title">销售线索信息列表</span>
-      <span class="stripe-total">金额总计<span class="red-text">{{ totalAmount.toFixed(2) }}</span> (万)</span>
+      <div class="stripe-left">
+        <span class="stripe-title">销售线索信息列表</span>
+        <span class="stripe-total"><span class="total-label">金额总计</span><span class="red-text">{{ totalAmount.toFixed(2) }}</span> (万)</span>
+      </div>
       <div class="stripe-actions">
         <el-button type="primary" size="small" :disabled="multipleSelection.length === 0" icon="Switch" @click="handleTransfer">转移给他人</el-button>
         <el-button type="primary" size="small" :disabled="multipleSelection.length === 0" icon="Link" @click="handleBatchClaim">批量认领</el-button>
@@ -126,12 +130,12 @@
           </template>
         </el-table-column>
         <el-table-column label="客户名称" align="center" prop="customerName" min-width="200" show-overflow-tooltip />
-        <el-table-column label="行业" align="center" width="150">
+        <el-table-column label="行业" align="center" width="150" show-overflow-tooltip>
           <template #default="scope">
             {{ industryOptions.find(i => i.id == scope.row.profession)?.industryCategoryName || scope.row.profession }}
           </template>
         </el-table-column>
-        <el-table-column label="部门" align="center" min-width="150">
+        <el-table-column label="部门" align="center" min-width="150" show-overflow-tooltip>
           <template #default="scope">
             {{ findDeptName(scope.row.deptNo) || scope.row.deptNo }}
           </template>
@@ -146,13 +150,13 @@
             <span>{{ scope.row.winRate }}%</span>
           </template>
         </el-table-column>
-        <el-table-column label="项目级别" align="center" width="120">
+        <el-table-column label="项目级别" align="center" width="120" show-overflow-tooltip>
           <template #default="scope">
             {{ findProjectLevelName(scope.row.projectLevel) }}
           </template>
         </el-table-column>
-        <el-table-column label="项目负责人" align="center" prop="leaderName" width="120" />
-        <el-table-column label="产品支持" align="center" prop="productSupport" width="100" />
+        <el-table-column label="项目负责人" align="center" prop="leaderName" width="120" show-overflow-tooltip />
+        <el-table-column label="产品支持" align="center" prop="productSupport" width="100" show-overflow-tooltip />
         <el-table-column label="创建时间" align="center" prop="createTime" width="120" sortable>
           <template #default="scope">
             <span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d}') }}</span>
@@ -273,7 +277,7 @@
 
       <div class="drawer-body-standard">
         <el-form :model="projectContactForm" :rules="projectContactRules" ref="projectContactFormRef" label-width="100px" label-position="right">
-          <div class="section-title">基本信息</div>
+          <div class="section-title"><span>基本信息</span></div>
           <el-row :gutter="32">
             <el-col :span="8">
               <el-form-item label="姓名" prop="name" required>
@@ -426,19 +430,19 @@
       class="leads-drawer"
       :with-header="false"
     >
-      <div class="drawer-header">
-        <div class="header-title">
-          <span class="main-title">销售线索</span>
-          <span class="sub-title">(3个月以上出结果或金额不明确的项目信息,计入:销售线索)</span>
-        </div>
-        <el-button link icon="Close" @click="cancelForm" class="close-btn"></el-button>
-      </div>
-
       <div class="drawer-content">
         <el-form :model="form" :rules="rules" ref="formRef" label-width="110px" label-position="left">
+          <div class="drawer-header">
+            <div class="header-title">
+              <span class="main-title">销售线索</span>
+              <span class="sub-title">(3个月以上出结果或金额不明确的项目信息,计入:销售线索)</span>
+            </div>
+            <el-button link icon="Close" @click="cancelForm" class="close-btn"></el-button>
+          </div>
+
           <!-- 基本信息 -->
           <div class="form-section">
-            <div class="section-title">基本信息</div>
+            <div class="section-title"><span>基本信息</span></div>
             <el-row :gutter="30">
               <el-col :span="12">
                 <el-form-item label="归属公司" prop="companyNo">
@@ -479,35 +483,35 @@
                 </el-form-item>
               </el-col>
               <el-col :span="12">
-                <el-form-item label="项目名称" prop="projectName" required>
+                <el-form-item label="项目名称" prop="projectName">
                   <el-input v-model="form.projectName" placeholder="请输入" />
                 </el-form-item>
               </el-col>
             </el-row>
             <el-row :gutter="30">
               <el-col :span="8">
-                <el-form-item label="金额(万)" prop="projectBudget" required>
+                <el-form-item label="金额(万)" prop="projectBudget">
                   <el-input v-model="form.projectBudget" placeholder="请输入" type="number">
                      <template #append>万</template>
                   </el-input>
                 </el-form-item>
               </el-col>
               <el-col :span="8">
-                <el-form-item label="赢单率(%)" prop="winRate" required>
+                <el-form-item label="赢单率(%)" prop="winRate">
                   <el-input v-model="form.winRate" placeholder="请输入" type="number">
                      <template #append>%</template>
                   </el-input>
                 </el-form-item>
               </el-col>
               <el-col :span="8">
-                <el-form-item label="立项时间" prop="expectedCompletionTime" required>
+                <el-form-item label="立项时间" prop="expectedCompletionTime">
                   <el-date-picker v-model="form.expectedCompletionTime" type="date" placeholder="请选择" style="width: 100%" />
                 </el-form-item>
               </el-col>
             </el-row>
             <el-row :gutter="30">
               <el-col :span="12">
-                <el-form-item label="成时间" prop="approvalDate">
+                <el-form-item label="成时间" prop="approvalDate">
                   <el-date-picker v-model="form.approvalDate" type="date" placeholder="请选择" style="width: 100%" />
                 </el-form-item>
               </el-col>
@@ -523,7 +527,7 @@
 
           <!-- 项目信息 -->
           <div class="form-section">
-            <div class="section-title">项目信息</div>
+            <div class="section-title"><span>项目信息</span></div>
             <el-row :gutter="30">
               <el-col :span="8">
                 <el-form-item label="项目级别" prop="projectLevel">
@@ -538,7 +542,7 @@
                 </el-form-item>
               </el-col>
               <el-col :span="8">
-                <el-form-item label="项目区域" prop="projectArea" required>
+                <el-form-item label="项目区域" prop="projectArea">
                   <el-input v-model="form.projectArea" placeholder="请输入" />
                 </el-form-item>
               </el-col>
@@ -571,14 +575,14 @@
             </el-row>
             <el-row :gutter="30">
               <el-col :span="24">
-                <el-form-item label="采购内容" prop="purchaseContent" required>
+                <el-form-item label="采购内容" prop="purchaseContent">
                   <el-input v-model="form.purchaseContent" type="textarea" :rows="3" placeholder="请输入采购内容" />
                 </el-form-item>
               </el-col>
             </el-row>
             <el-row :gutter="30">
               <el-col :span="24">
-                <el-form-item label="项目描述" prop="projectDescription" required>
+                <el-form-item label="项目描述" prop="projectDescription">
                   <el-input v-model="form.projectDescription" type="textarea" :rows="3" placeholder="请输入项目描述" />
                 </el-form-item>
               </el-col>
@@ -586,7 +590,7 @@
             <el-row :gutter="30">
               <el-col :span="24">
                 <el-form-item label="竞争对手" prop="competitor">
-                  <el-input v-model="form.competitor" type="textarea" :rows="2" placeholder="请输入竞争对手" />
+                  <el-input v-model="form.competitor" placeholder="请输入竞争对手" />
                 </el-form-item>
               </el-col>
             </el-row>
@@ -1232,11 +1236,11 @@ const formatLogTarget = (log) => {
   return log.targetObject;
 };
 
-const fetchOperationLogs = async (id) => {
-  if (!id) return;
+const fetchOperationLogs = async (projectNo) => {
+  if (!projectNo) return;
   logLoading.value = true;
   try {
-    const res = await listOperationLog({ dataType: 1, objectNo: id });
+    const res = await listOperationLog({ dataType: 1, objectNo: projectNo });
     logList.value = res.data || [];
   } catch (error) {
   } finally {
@@ -1575,7 +1579,10 @@ const rules = reactive({
   projectBudget: [{ required: true, message: '金额不能为空', trigger: 'blur' }],
   winRate: [{ required: true, message: '赢单率不能为空', trigger: 'blur' }],
   expectedCompletionTime: [{ required: true, message: '立项时间不能为空', trigger: 'change' }],
+  projectLevel: [{ required: true, message: '项目级别不能为空', trigger: 'change' }],
   projectArea: [{ required: true, message: '项目区域不能为空', 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' }],
 });
@@ -1589,8 +1596,8 @@ const queryParams = reactive({
   leader: undefined,
   deptNo: undefined,
   productSupport: undefined,
-  status: undefined,
-  timeType: undefined
+  status: '1',
+  timeType: '1'
 });
 
 const transferForm = reactive({
@@ -1602,6 +1609,8 @@ const claimForm = reactive({
   newManager: '',
   keepOldManager: true
 });
+// 单条认领时暂存行数据,批量认领时为 null
+const claimSingleRow = ref(null);
 
 const getList = async () => {
   loading.value = true;
@@ -1645,18 +1654,20 @@ const handleTransfer = () => {
   transferVisible.value = true;
 };
 
-/** 单条认领 */
-const handleClaim = async (row) => {
-  try {
-    await claimLeads({
-      ids: [row.id],
-      leader: userStore.userId,
-      leaderName: userStore.nickname
-    });
-    proxy.$modal.msgSuccess("认领成功");
-    getList();
-  } catch (e) {
-  }
+/** 获取当前用户在 userOptions 中对应的 staffId */
+const getCurrentUserStaffId = () => {
+  const currentUser = userOptions.value.find(
+    u => String(u.staffId) === String(userStore.userId) || String(u.userId) === String(userStore.userId)
+  );
+  return currentUser ? currentUser.staffId : undefined;
+};
+
+/** 单条认领 - 弹出认领弹窗 */
+const handleClaim = (row) => {
+  claimSingleRow.value = row;
+  claimForm.newManager = getCurrentUserStaffId();
+  claimForm.keepOldManager = true;
+  claimVisible.value = true;
 };
 
 const handleBatchClaim = () => {
@@ -1664,6 +1675,9 @@ const handleBatchClaim = () => {
     proxy.$modal.msgWarning("请先选择要认领的线索");
     return;
   }
+  claimSingleRow.value = null; // 批量认领模式
+  claimForm.newManager = getCurrentUserStaffId();
+  claimForm.keepOldManager = true;
   claimVisible.value = true;
 };
 
@@ -1690,19 +1704,27 @@ const submitClaim = async () => {
     proxy.$modal.msgWarning("请选择项目负责人");
     return;
   }
-  const ids = multipleSelection.value.map(item => item.id);
+  // 判断是单条认领还是批量认领
+  const rows = claimSingleRow.value
+    ? [claimSingleRow.value]
+    : multipleSelection.value;
   // 根据用户ID查找用户名称
   const managerUser = userOptions.value.find(u => u.staffId == claimForm.newManager || u.userId == claimForm.newManager);
   const leaderName = managerUser ? managerUser.staffName : '';
   try {
-    await claimLeads({
-      ids,
-      leader: claimForm.newManager,
-      leaderName: leaderName
+    // 逐条修改项目负责人
+    const promises = rows.map(row => {
+      return updateLeads({
+        id: row.id,
+        leader: claimForm.newManager,
+        leaderName: leaderName
+      });
     });
-    proxy.$modal.msgSuccess("认领成功,线索已转为项目商机");
+    await Promise.all(promises);
+    proxy.$modal.msgSuccess("认领成功");
     claimVisible.value = false;
-    getList(); // 刷新列表,项目负责人会更新
+    claimSingleRow.value = null;
+    getList(); // 刷新列表
   } catch (e) {
     /* API error handled by request interceptor */
   }
@@ -1971,7 +1993,7 @@ const handleDetail = (row) => {
     loadFollowupList();
     loadProjectContacts(); // 加载联系人
     loadAnalysisData(); // 加载结果分析
-    fetchOperationLogs(data.id); // 加载操作日志
+    fetchOperationLogs(data.projectNo); // 加载操作日志(按项目编号过滤)
   }).finally(() => {
     detailLoading.value = false;
   });
@@ -1983,8 +2005,7 @@ const loadProjectContacts = async () => {
   contactLoading.value = true;
   try {
     const params = { 
-      customerNo: detailData.customerNo,
-      companyNo: detailData.companyNo
+      platformCode: String(detailData.id)
     };
     console.log('加载项目联系人参数:', params);
     const res = await listContact(params);
@@ -2265,6 +2286,7 @@ const submitProjectContact = () => {
         companyNo: projectContactForm.companyNo,
         customerNo: projectContactForm.customerNo,
         customName: projectContactForm.customName,
+        platformCode: String(detailData.id),
       };
       
       try {
@@ -2661,54 +2683,59 @@ const getProjectRoleList = () => {
 }
 
 .search-card {
-  margin-bottom: 12px;
+  margin-bottom: 10px;
   border: none;
   background: #fff;
-  border-radius: 10px;
-  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
+  border-radius: 8px;
   :deep(.el-form-item) {
     margin-bottom: 2px;
   }
+  :deep(.el-form-item__label) {
+    font-weight: normal;
+    white-space: nowrap;
+  }
 }
 
 .search-btns {
   display: flex;
   align-items: center;
-  justify-content: flex-start;
+  flex-wrap: nowrap;
   gap: 12px;
-  padding-left: 0;
   
   .el-button {
-    border-radius: 6px;
     height: 32px;
-    padding: 8px 16px;
+    padding: 0 20px;
   }
 }
 
 .summary-stripe {
-  margin: 15px 0;
-  background: #fff;
-  padding: 10px 20px;
-  border-radius: 4px;
+  margin: 12px 0;
   display: flex;
   align-items: center;
   justify-content: space-between;
+  padding: 0 4px;
   
-  .stripe-title {
-    font-size: 15px;
-    font-weight: 600;
-    color: #333;
-  }
-  
-  .stripe-total {
-     font-size: 14px;
-     color: #475569;
-     margin-left: -200px; // 拉近标题与总计
-     .red-text {
-       color: #f43f5e;
-       font-weight: bold;
-       margin: 0 5px;
-     }
+  .stripe-left {
+    display: flex;
+    align-items: center;
+    gap: 20px;
+    
+    .stripe-title {
+      font-size: 14px;
+      font-weight: 400;
+      color: #333;
+      white-space: nowrap;
+    }
+    
+    .stripe-total {
+      font-size: 13px;
+      color: #f43f5e;
+      white-space: nowrap;
+      .total-label { color: #64748b; margin-right: 4px; }
+      .red-text {
+        font-weight: normal;
+      }
+    }
   }
   
   .stripe-actions {
@@ -2727,7 +2754,7 @@ const getProjectRoleList = () => {
   :deep(th.el-table__cell) {
     background-color: #f8fafc !important;
     color: #475569;
-    font-weight: 600;
+    font-weight: 500;
   }
 }
 
@@ -2909,8 +2936,15 @@ const getProjectRoleList = () => {
 
 // 详情抽屉样式
 .detail-drawer {
+  :deep(.el-drawer) {
+    top: 0 !important;
+    padding: 0 !important;
+  }
+  :deep(.el-drawer__header) {
+    display: none !important;
+  }
   :deep(.el-drawer__body) {
-    padding: 0;
+    padding: 0 !important;
     display: flex;
     flex-direction: column;
     background-color: #fff;
@@ -3017,10 +3051,21 @@ const getProjectRoleList = () => {
     margin-bottom: 16px;
 
     .section-label {
-      font-size: 16px;
+      font-size: 15px;
       font-weight: 600;
-      color: #1e293b;
-      position: relative;
+      color: #409eff;
+      display: flex;
+      align-items: center;
+      
+      &::before {
+        content: '';
+        display: inline-block;
+        width: 4px;
+        height: 14px;
+        background-color: #409eff;
+        border-radius: 2px;
+        margin-right: 8px;
+      }
     }
   }
 
@@ -3341,6 +3386,7 @@ const getProjectRoleList = () => {
           color: #1d2129;
         }
         
+        
         .leader-tag {
           font-size: 12px;
           height: 20px;
@@ -3512,10 +3558,25 @@ const getProjectRoleList = () => {
 
   .section-title {
     color: #409eff;
-    font-size: 15px;
-    padding: 12px 16px;
-    margin: 0 -24px 20px -24px;
-    background: #f5f7fa;
+    font-size: 14px;
+    font-weight: 600;
+    padding: 0 16px;
+    height: 34px;
+    line-height: 34px;
+    margin: 0 -24px 24px -24px;
+    background: #f0f7ff;
+    display: flex;
+    align-items: center;
+    
+    &::before {
+      content: '';
+      display: inline-block;
+      width: 4px;
+      height: 14px;
+      background-color: #409eff;
+      border-radius: 2px;
+      margin-right: 8px;
+    }
   }
 }
 
@@ -3540,8 +3601,15 @@ const getProjectRoleList = () => {
 }
 
 .project-contact-drawer {
+  :deep(.el-drawer) {
+    top: 0 !important;
+    padding: 0 !important;
+  }
+  :deep(.el-drawer__header) {
+    display: none !important;
+  }
   :deep(.el-drawer__body) {
-    padding: 0;
+    padding: 0 !important;
     display: flex;
     flex-direction: column;
   }
@@ -3588,4 +3656,177 @@ const getProjectRoleList = () => {
     }
   }
 }
+// 销售线索抽屉样式
+.leads-drawer {
+  padding: 0 !important;
+  :deep(.el-drawer) {
+    padding: 0 !important;
+  }
+  :deep(.el-drawer__header) {
+    display: none !important;
+    margin: 0 !important;
+    padding: 0 !important;
+  }
+  :deep(.el-drawer__body) {
+    padding: 0 !important;
+    display: flex;
+    flex-direction: column;
+  }
+
+  .drawer-header {
+    padding: 16px 24px;
+    background: #fff;
+    border-bottom: 1px solid #f1f5f9;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    flex-shrink: 0;
+    margin: 0 0 12px 0;
+
+    .header-title {
+      display: flex;
+      align-items: baseline;
+      .main-title {
+        font-size: 18px;
+        font-weight: 500;
+        color: #1e293b;
+      }
+      .sub-title {
+        font-size: 13px;
+        color: #94a3b8;
+        margin-left: 12px;
+        font-weight: normal;
+      }
+    }
+    
+    .close-btn {
+      font-size: 20px;
+      color: #94a3b8;
+      cursor: pointer;
+      &:hover { color: #ef4444; }
+    }
+  }
+
+  .drawer-content {
+    flex: 1;
+    padding: 0 !important;
+    background-color: #fff;
+    overflow-y: auto;
+
+    :deep(.el-form) {
+      padding: 0 !important;
+    }
+
+    .form-section {
+      background: #fff;
+      padding: 0;
+      margin: 0 0 24px;
+      border-radius: 0;
+      box-shadow: none;
+
+      &:first-child {
+        margin-top: 0 !important;
+      }
+
+      .section-title {
+        margin: 0 0 20px 0;
+        color: #409eff;
+        font-size: 14px;
+        font-weight: 600;
+        padding: 0 12px;
+        height: 34px;
+        line-height: 34px;
+        background: #f0f7ff;
+        display: flex;
+        align-items: center;
+        position: relative;
+
+        span {
+          color: #409eff !important;
+        }
+
+        &.attachment-header {
+          display: flex;
+          justify-content: space-between;
+          align-items: center;
+          
+          .upload-btn {
+            margin-right: 0;
+            :deep(.el-button) {
+              font-weight: normal;
+              font-size: 14px;
+              display: flex;
+              align-items: center;
+              gap: 4px;
+            }
+          }
+        }
+      }
+
+      // 为表单项增加内边距,因为 section-title 是全宽的
+      .el-row {
+        padding: 0 24px;
+      }
+      .at-table {
+        margin: 0 24px 24px 24px;
+      }
+      
+      :deep(.el-form-item) {
+        margin-bottom: 20px;
+      }
+
+      :deep(.el-form-item__label) {
+        font-weight: normal;
+        color: #475569;
+        white-space: nowrap;
+      }
+
+      :deep(.el-input-group__append) {
+        background-color: #f8fafc;
+        color: #64748b;
+      }
+    }
+  }
+
+  .drawer-footer {
+    padding: 16px 24px;
+    background: #fff;
+    border-top: 1px solid #f1f5f9;
+    display: flex;
+    justify-content: flex-end;
+    gap: 12px;
+    
+    :deep(.el-button) {
+      padding: 10px 30px;
+      border-radius: 4px;
+      font-weight: normal;
+    }
+  }
+
+  .at-table {
+    border-radius: 4px;
+    overflow: hidden;
+    
+    :deep(th.el-table__cell) {
+      background-color: #f8fafc !important;
+      color: #475569;
+      font-weight: 600;
+      font-size: 13px;
+      padding: 12px 0;
+    }
+    
+    :deep(td.el-table__cell) {
+      font-size: 13px;
+      color: #334155;
+      padding: 10px 0;
+    }
+
+    .empty-text {
+      padding: 30px 0;
+      color: #94a3b8;
+      font-size: 13px;
+    }
+  }
+}
 </style>
+

+ 14 - 1
src/views/saleManage/opportunity/index.vue

@@ -107,7 +107,7 @@
         <div class="tab-btn" :class="{ active: activeTab === 'participated' }" @click="handleTabClick('participated')">我参与的项目商机</div>
         <div class="tab-btn badge-btn" :class="{ active: activeTab === 'all' }" @click="handleTabClick('all')">
           全部项目商机
-          <span class="badge">99+</span>
+          <span class="badge" v-if="allCount > 0">{{ allCount > 99 ? '99+' : allCount }}</span>
         </div>
       </div>
 
@@ -421,6 +421,7 @@ const opportunityList = ref([]);
 const dateRange = ref([]);
 const multipleSelection = ref([]);
 const totalAmount = ref(0);
+const allCount = ref(0);
 
 /* ========== 下拉选项 ========== */
 const companyOptions = ref([]);
@@ -502,6 +503,18 @@ const getList = async () => {
     const res = await listOpportunity(params);
     opportunityList.value = res.rows || [];
     total.value = res.total || 0;
+    
+    // 异步获取全部项目商机的总数用于红点显示
+    try {
+      const allRes = await listOpportunity({ 
+        projectType: '销售商机', 
+        tabType: 'all', 
+        pageNum: 1, 
+        pageSize: 1 
+      });
+      allCount.value = allRes.total || 0;
+    } catch (e) { console.error('获取商机总数失败', e); }
+
     totalAmount.value = opportunityList.value.reduce((acc, cur) => acc + (Number(cur.projectBudget) || 0), 0);
   } catch (err) { console.error(err); }
   finally { loading.value = false; }

+ 24 - 33
src/views/saleManage/platformSelection/index.vue

@@ -120,12 +120,14 @@
           <span v-else-if="totalCountBadge > 0" class="badge">{{ totalCountBadge }}</span>
         </div>
       </div>
-      <div class="summary-line">
-         <span class="stripe-total">金额总计<span class="red-text">{{ Number(totalAmount).toFixed(2) }}</span> (万)</span>
-      </div>
-      <div class="stripe-actions">
-        <el-button type="info" size="small" icon="Delete" :disabled="multipleSelection.length === 0" @click="handleBatchDelete">批量删除</el-button>
-        <el-button type="primary" size="small" icon="Switch" :disabled="multipleSelection.length === 0" @click="handleTransfer">转移给他人</el-button>
+      <div class="stripe-right-wrap">
+        <div class="summary-line">
+           <span class="stripe-total">金额总计<span class="red-text">{{ Number(totalAmount).toFixed(2) }}</span> (万)</span>
+        </div>
+        <div class="stripe-actions">
+          <el-button type="info" size="small" icon="Delete" :disabled="multipleSelection.length === 0" @click="handleBatchDelete">批量删除</el-button>
+          <el-button type="primary" size="small" icon="Switch" :disabled="multipleSelection.length === 0" @click="handleTransfer">转移给他人</el-button>
+        </div>
       </div>
     </div>
 
@@ -136,57 +138,45 @@
         <el-table-column label="项目名称" align="center" prop="projectName" min-width="240" show-overflow-tooltip fixed />
         <el-table-column label="所属公司" align="center" min-width="180" show-overflow-tooltip>
           <template #default="scope">
-            {{ companyOptions.find(c => (c.companyCode || c.id) === scope.row.companyNo)?.companyName || scope.row.companyNo }}
+            {{ companyOptions.find(c => String(c.companyCode || c.id) === String(scope.row.companyNo))?.companyName || scope.row.companyNo || '--' }}
           </template>
         </el-table-column>
         <el-table-column label="客户名称" align="center" prop="customName" min-width="200" show-overflow-tooltip />
         <el-table-column label="项目级别" align="center" prop="projectLevel" width="100">
           <template #default="scope">
-            {{ projectLevelOptions.find(o => String(o.dictValue) === String(scope.row.projectLevel))?.dictLabel || scope.row.projectLevel }}
+            {{ projectLevelOptions.find(o => String(o.dictValue) === String(scope.row.projectLevel))?.dictLabel || scope.row.projectLevel || '--' }}
           </template>
         </el-table-column>
         <el-table-column label="项目类型" align="center" prop="businessType" width="100">
           <template #default="scope">
-            {{ projectTypeOptions.find(o => String(o.dictValue) === String(scope.row.businessType))?.dictLabel || scope.row.businessType }}
+            {{ projectTypeOptions.find(o => String(o.dictValue) === String(scope.row.businessType))?.dictLabel || scope.row.businessType || '--' }}
           </template>
         </el-table-column>
-        <el-table-column label="金额(万)" align="center" prop="amount" width="100" sortable />
-        <el-table-column label="服务期(年)" align="center" prop="standardPeriod" width="100" />
-        <el-table-column label="服务时间段" align="center" prop="serviceTime" width="150" show-overflow-tooltip />
-        <el-table-column label="投标截止时间" align="center" prop="tenderDeadline" width="110">
+        <el-table-column label="金额(万)" align="center" prop="amount" width="100" sortable>
           <template #default="scope">
-            <span>{{ parseTime(scope.row.tenderDeadline, '{y}-{m}-{d}') }}</span>
+            {{ scope.row.amount != null ? Number(scope.row.amount).toFixed(2) : '--' }}
           </template>
         </el-table-column>
-        <el-table-column label="部门编号" align="center" prop="deptNo" width="100" />
-        <el-table-column label="负责人" align="center" prop="leaderName" width="100" />
-        <el-table-column label="产品支持" align="center" prop="productSupport" width="100" />
-        <el-table-column label="赢单率(%)" align="center" prop="winningRate" width="100" />
-        <el-table-column label="行业" align="center" prop="profession" width="120">
+        <el-table-column label="服务期(年)" align="center" prop="standardPeriod" width="100">
           <template #default="scope">
-            {{ getProfessionLabel(scope.row.profession) }}
+            {{ scope.row.standardPeriod != null ? scope.row.standardPeriod : '--' }}
           </template>
         </el-table-column>
-        <el-table-column label="创建时间" align="center" prop="createTime" width="110">
+        <el-table-column label="服务时间段" align="center" prop="serviceTime" width="150" show-overflow-tooltip>
           <template #default="scope">
-            <span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d}') }}</span>
+            {{ scope.row.serviceTime || '--' }}
           </template>
         </el-table-column>
-        <el-table-column label="报名截止时间" align="center" prop="signUpDeadline" width="110" sortable>
+        <el-table-column label="投标截止时间" align="center" prop="tenderDeadline" width="110">
           <template #default="scope">
-            <span>{{ parseTime(scope.row.signUpDeadline, '{y}-{m}-{d}') }}</span>
+            <span>{{ scope.row.tenderDeadline ? parseTime(scope.row.tenderDeadline, '{y}-{m}-{d}') : '--' }}</span>
           </template>
         </el-table-column>
-        <el-table-column label="入围类型" align="center" prop="shortlistedType" width="100">
+        <el-table-column label="部门编号" align="center" prop="deptNo" width="100">
           <template #default="scope">
-            {{ shortlistedTypeOptions.find(o => String(o.dictValue) === String(scope.row.shortlistedType))?.dictLabel || scope.row.shortlistedType }}
+            {{ scope.row.deptNo || '--' }}
           </template>
         </el-table-column>
-        <el-table-column label="项目状态" align="center" prop="projectStatus" width="100">
-           <template #default="scope">
-             <el-tag :type="scope.row.projectStatus === 1 ? 'warning' : 'info'" plain>{{ scope.row.projectStatus === 1 ? '跟进中' : '已归档' }}</el-tag>
-           </template>
-        </el-table-column>
         <el-table-column label="操作" align="center" width="220" fixed="right">
           <template #default="scope">
             <el-button link type="primary" @click="handleUpdate(scope.row)">详情</el-button>
@@ -1696,8 +1686,9 @@ onMounted(() => {
       }
     }
   }
-  .summary-line { display: flex; align-items: center; .stripe-total { font-size: 13px; color: #1d2129; white-space: nowrap; .red-text { color: #f53f3f; } } }
-  .stripe-actions { display: flex; gap: 10px; flex-shrink: 0; margin-left: 16px; }
+  .stripe-right-wrap { display: flex; align-items: center; gap: 24px; }
+  .summary-line { display: flex; align-items: center; .stripe-total { font-size: 13px; color: #1d2129; white-space: nowrap; .red-text { color: #f53f3f; font-weight: 600; margin: 0 4px; } } }
+  .stripe-actions { display: flex; gap: 10px; flex-shrink: 0; }
 }
 
 .table-card { border: none; }

+ 100 - 55
src/views/saleManage/projectSelection/ProjectSelectionDrawer.vue

@@ -10,7 +10,7 @@
     <!-- 顶部标题栏 -->
     <div class="detail-header">
       <span class="project-title">{{ modeTitle }}</span>
-      <el-icon class="close-btn" @click="handleClose"><Close /></el-icon>
+      <el-icon class="close-btn" @click="handleClose"><CloseIcon /></el-icon>
     </div>
 
     <!-- 模式 1: 详情展示模式 -->
@@ -38,7 +38,7 @@
             <el-tab-pane label="项目信息" name="info">
               <!-- 悬浮在右侧的编辑按钮 (原型图标准) -->
               <el-button type="primary" class="floating-edit-btn" @click="handleEdit">
-                <el-icon style="margin-right: 4px;"><Edit /></el-icon> 编辑
+                <el-icon style="margin-right: 4px;"><EditIcon /></el-icon> 编辑
               </el-button>
               <div class="info-block">
                 <div class="block-header">
@@ -47,7 +47,7 @@
                 <div class="info-grid info-grid-2col">
                   <div class="info-item full-row">
                     <span class="label">项目名称</span>
-                    <span class="value">{{ form.projectName || '--' }}</span>
+                    <span class="value">{{ form.projectName || '' }}</span>
                   </div>
                   <div class="info-item">
                     <span class="label">归属公司</span>
@@ -55,7 +55,7 @@
                   </div>
                   <div class="info-item">
                     <span class="label">客户名称</span>
-                    <span class="value">{{ form.customerName || '--' }}</span>
+                    <span class="value">{{ form.customerName || '' }}</span>
                   </div>
                   <div class="info-item">
                     <span class="label">行业</span>
@@ -63,7 +63,11 @@
                   </div>
                   <div class="info-item">
                     <span class="label">部门</span>
-                    <span class="value">{{ form.deptNo || '--' }}</span>
+                    <span class="value">{{ form.deptNo || '' }}</span>
+                  </div>
+                  <div class="info-item">
+                    <span class="label">项目负责人</span>
+                    <span class="value">{{ leaderName }}</span>
                   </div>
                   <div class="info-item">
                     <span class="label">项目状态</span>
@@ -131,11 +135,11 @@
                   </div>
                   <div class="info-item full-row-3col">
                     <span class="label">入围要求</span>
-                    <span class="value wrap-text">{{ form.condition || '--' }}</span>
+                    <span class="value wrap-text">{{ form.condition || '' }}</span>
                   </div>
                   <div class="info-item full-row-3col">
                     <span class="label">项目描述</span>
-                    <span class="value wrap-text">{{ form.projectDesc || '--' }}</span>
+                    <span class="value wrap-text">{{ form.projectDesc || '' }}</span>
                   </div>
                 </div>
               </div>
@@ -146,7 +150,7 @@
                 <div style="display: flex; justify-content: flex-end; margin-bottom: 12px;">
                   <el-dropdown @command="handleContactCommand">
                     <el-button type="primary" size="small">
-                      <el-icon style="margin-right: 4px;"><Plus /></el-icon> 新建联系人
+                      <el-icon style="margin-right: 4px;"><PlusIcon /></el-icon> 新建联系人
                     </el-button>
                     <template #dropdown>
                       <el-dropdown-menu>
@@ -218,23 +222,20 @@
                     :show-file-list="false"
                     :on-success="handleUploadSuccess"
                   >
-                    <el-button type="primary" icon="Upload" size="small">上传附件</el-button>
+                    <el-button type="primary" :icon="UploadIcon" size="small">上传附件</el-button>
                   </el-upload>
                 </div>
-                <el-table :data="(form.fileList || [])" border class="file-table">
+                <el-table :data="(form.fileList || [])" border class="file-table" empty-text="暂无附件">
                   <el-table-column label="文件名称" align="left" prop="fileName" min-width="200" show-overflow-tooltip />
                   <el-table-column label="文件类型" align="center" prop="fileType" width="100" />
-                  <el-table-column label="文件大小" align="center" prop="fileSize" width="100" />
                   <el-table-column label="上传时间" align="center" prop="uploadTime" width="160" />
-                  <el-table-column label="上传人" align="center" prop="uploader" width="100" show-overflow-tooltip />
                   <el-table-column label="操作" align="center" width="140">
                     <template #default="scope">
-                      <el-button link type="primary" size="small">下载</el-button>
-                      <el-button link type="danger" size="small">删除</el-button>
+                      <el-button link type="primary" size="small" @click="handleDownloadFile(scope.row)">下载</el-button>
+                      <el-button link type="danger" size="small" @click="handleDeleteFile(scope.$index)">删除</el-button>
                     </template>
                   </el-table-column>
                 </el-table>
-                <div v-if="!(form.fileList && form.fileList.length)" class="tab-empty">暂无附件</div>
               </div>
             </el-tab-pane>
 
@@ -265,10 +266,10 @@
               <div class="side-content">
                 <div class="team-header">
                   <span>团队成员 ({{ (form.memberList || []).length }})</span>
-                  <el-icon class="add-icon" @click="handleAddMember"><Plus /></el-icon>
+                  <el-icon class="add-icon" @click="handleAddMember"><PlusIcon /></el-icon>
                 </div>
                 <div class="search-box">
-                  <el-input v-model="memberSearchKeyword" placeholder="请输入成员" prefix-icon="Search" clearable />
+                  <el-input v-model="memberSearchKeyword" placeholder="请输入成员" :prefix-icon="SearchIcon" clearable />
                 </div>
                 <div class="team-list">
                   <div v-for="(m, i) in filteredMemberList" :key="i" class="team-item">
@@ -294,7 +295,7 @@
               <div class="side-content">
                 <div class="record-header">
                   <el-button link type="primary" size="small" @click="handleAddRecord">
-                    <el-icon><Plus /></el-icon> 新建跟进记录
+                    <el-icon><PlusIcon /></el-icon> 新建跟进记录
                   </el-button>
                   <div class="filter-item">
                     <span class="filter-label">拜访方式</span>
@@ -334,7 +335,7 @@
                 <div class="manage-info-grid">
                   <div class="manage-item">
                     <span class="label">创建人</span>
-                    <span class="value">{{ form.createBy || '--' }}</span>
+                    <span class="value">{{ form.createByName || form.createBy || '--' }}</span>
                   </div>
                   <div class="manage-item">
                     <span class="label">创建日期</span>
@@ -346,7 +347,7 @@
                   </div>
                   <div class="manage-item">
                     <span class="label">最后修改人</span>
-                    <span class="value">{{ form.updateBy || '--' }}</span>
+                    <span class="value">{{ form.updateByName || form.updateBy || '--' }}</span>
                   </div>
                 </div>
               </div>
@@ -569,15 +570,16 @@
                 :show-file-list="false"
                 multiple
               >
-                <span class="upload-link"><el-icon><Upload /></el-icon> 上传附件</span>
+                <span class="upload-link"><el-icon><UploadIcon /></el-icon> 上传附件</span>
               </el-upload>
             </div>
             <el-table :data="(form.fileList || [])" class="attachment-table" empty-text="暂无附件" border>
               <el-table-column label="文件名称" align="left" prop="fileName" show-overflow-tooltip />
               <el-table-column label="文件类型" align="center" prop="fileType" width="100" />
               <el-table-column label="上传时间" align="center" prop="uploadTime" width="160" />
-              <el-table-column label="操作" align="center" width="80">
+              <el-table-column label="操作" align="center" width="120">
                 <template #default="scope">
+                  <el-button link type="primary" size="small" @click="handleDownloadFile(scope.row)">下载</el-button>
                   <el-button link type="danger" size="small" @click="handleDeleteFile(scope.$index)">删除</el-button>
                 </template>
               </el-table-column>
@@ -604,7 +606,7 @@
   >
     <div class="progress-drawer-header">
       <span class="progress-drawer-title">查看进度</span>
-      <el-icon class="progress-close-btn" @click="handleProgressClose"><Close /></el-icon>
+      <el-icon class="progress-close-btn" @click="handleProgressClose"><CloseIcon /></el-icon>
     </div>
     <div class="progress-drawer-body">
       <div class="progress-input-wrapper">
@@ -620,7 +622,7 @@
       </div>
       <div class="publish-btn-row">
         <el-button type="primary" class="publish-btn" style="background-color: #409eff; border-color: #409eff;" @click="handlePublishProgress" :loading="progressLoading">
-          <el-icon style="margin-right: 4px;"><Plus /></el-icon> 发布
+          <el-icon style="margin-right: 4px;"><PlusIcon /></el-icon> 发布
         </el-button>
       </div>
       <div class="record-list" v-loading="recordLoading">
@@ -796,7 +798,7 @@
   <el-dialog v-model="editMemberVisible" title="编辑团队成员" width="460px" append-to-body class="member-dialog">
     <el-form :model="memberEditForm" label-width="100px">
       <el-form-item label="人员姓名:">
-        <span>{{ memberEditForm.name }}{{ memberEditForm.deptName ? '/' + memberEditForm.deptName : '' }}</span>
+        <span>{{ memberEditForm.memberName || memberEditForm.staffName || memberEditForm.realName || memberEditForm.name }}{{ memberEditForm.deptName ? '/' + memberEditForm.deptName : '' }}</span>
       </el-form-item>
       <el-form-item label="成员角色:">
         <el-select v-model="memberEditForm.roleName" placeholder="请选择" style="width: 100%">
@@ -876,7 +878,7 @@
           v-model:file-list="recordImages"
           :on-success="handleRecordImgSuccess"
         >
-          <el-icon><Plus /></el-icon>
+          <el-icon><PlusIcon /></el-icon>
         </el-upload>
       </el-form-item>
     </el-form>
@@ -890,13 +892,21 @@
 </template>
 
 <script setup>
-import { ref, watch, computed, onMounted, getCurrentInstance } from 'vue';
+import { ref, reactive, watch, computed, onMounted, getCurrentInstance } from 'vue';
 import { useUserStore } from "@/store/modules/user";
 import { listCompanyOption, listCustomerInfo } from "@/api/customer/customerInfo/index";
 import { listCommonDict } from "@/api/customer/customerDict";
 import { listIndustryCategory } from "@/api/customer/industryCategory";
 import { selectStaffOptionList } from "@/api/system/comStaff/index";
-import { Upload, Close, Edit, Plus, Search, Delete } from '@element-plus/icons-vue';
+import { 
+  Upload as UploadIcon, 
+  Close as CloseIcon, 
+  Edit as EditIcon, 
+  Plus as PlusIcon, 
+  Search as SearchIcon, 
+  Delete as DeleteIcon, 
+  Download as DownloadIcon 
+} from '@element-plus/icons-vue';
 import { globalHeaders } from '@/utils/request';
 import { listByIds } from "@/api/system/oss/index";
 import { publishProjectProgress, updateProjectSelection, getProjectSelection } from '@/api/saleManage/projectSelection/index';
@@ -905,12 +915,12 @@ import { listOperationLog } from '@/api/customer/operationLog';
 import { listContactPerson, addContactPerson } from "@/api/customer/contactPerson";
 import { addRecord, listRecord } from "@/api/visit/record";
 
-const { proxy } = getCurrentInstance();
-const userStore = useUserStore();
-
 const props = defineProps({ modelValue: { type: Boolean, default: false }, data: Object, title: String });
 const emit = defineEmits(['update:modelValue', 'submit']);
 
+const { proxy } = getCurrentInstance();
+const userStore = useUserStore();
+
 const visible = ref(false);
 const activeTab = ref('info');
 const activeSideTab = ref('team');
@@ -1052,6 +1062,10 @@ const projectLevelName = computed(() => options.value.level.find(o => String(o.d
 const projectTypeName = computed(() => options.value.type.find(o => String(o.dictValue) === String(form.value.businessType))?.dictLabel || form.value.businessType || '--');
 const shortlistedTypeName = computed(() => options.value.shortlisted.find(o => String(o.dictValue) === String(form.value.shortlistedType))?.dictLabel || form.value.shortlistedType || '--');
 const projectStatusName = computed(() => options.value.status.find(o => String(o.dictValue) === String(form.value.projectStatus))?.dictLabel || form.value.projectStatus || '--');
+const leaderName = computed(() => {
+  const target = options.value.user.find(u => String(u.staffId || u.userId) === String(form.value.leader));
+  return target?.staffName || target?.nickName || form.value.leaderName || form.value.leader || '--';
+});
 const professionName = computed(() => {
   const val = String(form.value.profession || '');
   if (!val) return '--';
@@ -1164,6 +1178,15 @@ const fetchOperationLogs = async () => {
   }
 };
 
+/** 翻译详情字段名 */
+const translateDetails = (details) => {
+  if (!details) return '';
+  if (details.includes('projectStatus') || details.includes('年度入围')) {
+    return '年度入围(项目)';
+  }
+  return details;
+};
+
 /** 格式化日志描述 - 模仿商机详情逻辑 */
 const formatLogAction = (log) => {
   let details = log.actionDetails || '';
@@ -1353,15 +1376,27 @@ const handleUploadSuccess = (res, file) => {
       const parts = n.split('.');
       return parts.length > 1 ? parts[parts.length - 1].toUpperCase() : '文件';
     };
+    
+    // 格式化文件大小
+    const formatSize = (bytes) => {
+      if (!bytes) return '0 B';
+      const k = 1024;
+      const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
+      const i = Math.floor(Math.log(bytes) / Math.log(k));
+      return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
+    };
+
     form.value.fileList.push({
       ossId: item.ossId,
       fileName: name,
       fileType: getExt(name),
+      fileSize: formatSize(file.size),
       fileUrl: item.url,
       url: item.url,
-      uploadTime: new Date().toISOString().split('T')[0],
-      uploader: '当前用户'
+      uploadTime: proxy.parseTime(new Date()),
+      uploader: userStore.nickname || userStore.name || '当前用户'
     });
+    
     if (!isEdit.value) {
       syncProjectData();
     } else {
@@ -1372,6 +1407,15 @@ const handleUploadSuccess = (res, file) => {
   }
 };
 
+/** 处理附件下载 */
+const handleDownloadFile = (row) => {
+  if (row.ossId) {
+    proxy.$download.oss(row.ossId);
+  } else if (row.fileUrl || row.url) {
+    window.open(row.fileUrl || row.url, '_blank');
+  }
+};
+
 /** 处理附件删除 */
 const handleDeleteFile = (index) => {
   form.value.fileList.splice(index, 1);
@@ -1708,6 +1752,13 @@ const remoteLoadCustomers = (query) => {
   }
 }
 /* 强制重置 el-drawer 的内部 body 样式,方便 flex 布局 */
+:deep(.el-drawer) {
+  top: 0 !important;
+  padding: 0 !important;
+}
+:deep(.el-drawer__header) {
+  display: none !important;
+}
 :deep(.el-drawer__body) {
   padding: 0 !important;
   display: flex;
@@ -1723,7 +1774,7 @@ const remoteLoadCustomers = (query) => {
   justify-content: space-between;
   align-items: center;
   padding: 0 20px;
-  border-bottom: 1px solid var(--el-border-color-lighter);
+  border-bottom: none; // 移除边框以实现无缝衔接
   background-color: var(--el-bg-color);
 
   .project-title {
@@ -1747,19 +1798,20 @@ const remoteLoadCustomers = (query) => {
   border-bottom: 1px solid #f2f3f5; // 增加浅色底边分隔
 
   .section-label {
-    font-size: 13px;
+    font-size: 14px;
     font-weight: 600;
-    color: var(--el-text-color-primary);
-    margin-bottom: 8px;
+    color: #409eff;
+    margin-bottom: 12px;
     display: flex;
     align-items: center;
     &::before {
       content: '';
-      width: 3px;
-      height: 12px;
-      background: #2bc48d;
+      display: inline-block;
+      width: 4px;
+      height: 14px;
+      background: #409eff;
       border-radius: 2px;
-      margin-right: 6px;
+      margin-right: 8px;
     }
   }
 
@@ -1947,7 +1999,10 @@ const remoteLoadCustomers = (query) => {
     .block-title {
       font-size: 15px;
       font-weight: 600;
-      color: #1d2129;
+      color: #409eff;
+      position: relative;
+      display: flex;
+      align-items: center;
     }
   }
 }
@@ -2231,25 +2286,15 @@ const remoteLoadCustomers = (query) => {
   align-items: center;
   justify-content: space-between;
   margin-bottom: 20px;
-  background-color: var(--el-color-primary-light-9);
-  height: 32px;
+  background-color: #f0f7ff;
+  height: 34px;
   padding: 0 12px;
   position: relative;
-  
-  &::before {
-    content: '';
-    position: absolute;
-    left: 0;
-    top: 0;
-    bottom: 0;
-    width: 3px;
-    background-color: var(--el-color-primary);
-  }
 
   .section-title {
     font-size: 14px;
     font-weight: 600;
-    color: var(--el-color-primary);
+    color: #409eff;
     padding-left: 8px;
   }
   .upload-link {

+ 25 - 36
src/views/saleManage/projectSelection/index.vue

@@ -3,105 +3,97 @@
     <el-card shadow="never" class="search-card">
       <el-form :model="queryParams" ref="queryRef" label-width="85px">
         <el-row :gutter="24">
-          <el-col :span="6">
+          <el-col :lg="6" :md="8" :sm="12">
             <el-form-item label="所属公司" prop="companyNo">
               <el-select v-model="queryParams.companyNo" placeholder="请选择" style="width: 100%" clearable>
                  <el-option v-for="item in companyOptions" :key="item.companyCode || item.id" :label="item.companyName" :value="item.companyCode || item.id" />
               </el-select>
             </el-form-item>
           </el-col>
-          <el-col :span="6">
+          <el-col :lg="6" :md="8" :sm="12">
             <el-form-item label="项目名称" prop="projectName">
               <el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable />
             </el-form-item>
           </el-col>
-          <el-col :span="6">
+          <el-col :lg="6" :md="8" :sm="12">
             <el-form-item label="客户名称" prop="customerName">
               <el-input v-model="queryParams.customerName" placeholder="请输入客户名称" clearable />
             </el-form-item>
           </el-col>
-          <el-col :span="6">
+          <el-col :lg="6" :md="8" :sm="12">
             <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="String(item.staffId || item.userId)" />
               </el-select>
             </el-form-item>
           </el-col>
-        </el-row>
-        <el-row :gutter="24" style="margin-top: 5px;">
-          <el-col :span="6">
+          <el-col :lg="6" :md="8" :sm="12">
             <el-form-item label="部门" prop="deptId">
               <el-select v-model="queryParams.deptId" placeholder="所属部门" style="width: 100%" clearable>
                 <el-option v-for="item in deptOptions" :key="item.value" :label="item.label" :value="item.value" />
               </el-select>
             </el-form-item>
           </el-col>
-          <el-col :span="6">
+          <el-col :lg="6" :md="8" :sm="12">
             <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-select>
             </el-form-item>
           </el-col>
-          <el-col :span="6">
+          <el-col :lg="6" :md="8" :sm="12">
             <el-form-item label="项目级别" prop="projectLevel">
               <el-select v-model="queryParams.projectLevel" placeholder="请选择" style="width: 100%" clearable>
                  <el-option v-for="item in projectLevelOptions" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue" />
               </el-select>
             </el-form-item>
           </el-col>
-          <el-col :span="6">
+          <el-col :lg="6" :md="8" :sm="12">
             <el-form-item label="项目类型" prop="businessType">
               <el-select v-model="queryParams.businessType" placeholder="请选择" style="width: 100%" clearable>
                  <el-option v-for="item in projectTypeOptions" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue" />
               </el-select>
             </el-form-item>
           </el-col>
-        </el-row>
-        <el-row :gutter="24" style="margin-top: 5px;">
-           <el-col :span="6">
+          <el-col :lg="6" :md="8" :sm="12">
             <el-form-item label="入围类型" prop="selectionType">
               <el-select v-model="queryParams.selectionType" placeholder="请选择" style="width: 100%" clearable>
                 <el-option v-for="item in shortlistedTypeOptions" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue" />
               </el-select>
             </el-form-item>
           </el-col>
-          <el-col :span="6">
+          <el-col :lg="6" :md="8" :sm="12">
             <el-form-item label="项目状态" prop="projectStatus">
               <el-select v-model="queryParams.projectStatus" placeholder="请选择" style="width: 100%" clearable>
                 <el-option v-for="item in statusOptions" :key="item.dictValue" :label="item.dictLabel || item.label" :value="item.dictValue" />
               </el-select>
             </el-form-item>
           </el-col>
-          <el-col :span="6">
+          <el-col :lg="6" :md="8" :sm="12">
             <el-form-item label="成交结果" prop="result">
               <el-select v-model="queryParams.result" placeholder="请选择" style="width: 100%" clearable>
                 <el-option v-for="item in resultOptions" :key="item.dictValue" :label="item.dictLabel" :value="Number(item.dictValue)" />
               </el-select>
             </el-form-item>
           </el-col>
-          <el-col :span="6">
-            <el-form-item label="查询类型" prop="timeType" label-width="85px">
+          <el-col :lg="6" :md="8" :sm="12">
+            <el-form-item label="查询类型" prop="timeType">
               <el-select v-model="queryParams.timeType" style="width: 100%" clearable>
                 <el-option v-for="item in timeTypeOptions" :key="item.dictValue" :label="item.dictLabel" :value="Number(item.dictValue)" />
               </el-select>
             </el-form-item>
           </el-col>
-        </el-row>
-        <el-row :gutter="24" style="margin-top: 5px;">
-          <el-col :span="8">
+          <el-col :lg="8" :md="12" :sm="24">
             <el-form-item label="时间范围" prop="dateRange">
-              <el-date-picker v-model="dateRange" type="daterange" range-separator="To" start-placeholder="开始时间" end-placeholder="结束时间" style="width: 100%" />
+              <el-date-picker v-model="dateRange" type="daterange" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" style="width: 100%" />
             </el-form-item>
           </el-col>
-          <el-col :span="16">
-            <el-form-item label-width="0">
-              <div class="search-btns">
-                <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
-                <el-button icon="Refresh" @click="resetQuery">重置</el-button>
-                <el-button type="primary" icon="Plus" @click="handleAdd" class="btn-new">新增</el-button>
-              </div>
-            </el-form-item>
+          <el-col :lg="16" :md="12" :sm="24" class="btn-group-col">
+            <div class="search-btns">
+              <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+              <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+              <el-button type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
+            </div>
           </el-col>
         </el-row>
       </el-form>
@@ -273,7 +265,7 @@ const keepAsMember = ref(false);
 const queryParams = reactive({
   pageNum: 1, pageSize: 10,
   companyNo: undefined, projectName: undefined, customerName: undefined, leader: undefined,
-  deptNo: undefined, productSupport: undefined, projectLevel: undefined, businessType: undefined, projectStatus: undefined,
+  deptNo: undefined, productSupport: undefined, projectLevel: undefined, businessType: undefined, projectStatus: '1',
   shortlistedType: undefined, result: undefined, timeType: undefined
 });
 
@@ -490,12 +482,9 @@ onMounted(() => {
 }
 .app-container { padding: 20px; background-color: #f7f9fb; min-height: calc(100vh - 84px); }
 
-.search-card { 
-  margin-bottom: 10px; border: none; background: #fff; border-radius: 8px;
-  :deep(.el-form-item) { margin-bottom: 2px; }
-}
-
-.search-btns { display: flex; gap: 12px; }
+.search-card { margin-bottom: 10px; border: none; background: #fff; border-radius: 8px; }
+.search-btns { display: flex; gap: 12px; justify-content: flex-end; align-items: center; width: 100%; }
+.btn-group-col { display: flex; align-items: flex-start; padding-top: 2px; }
 
 .tabs-control {
   margin: 12px 0 16px; display: flex; align-items: center; justify-content: space-between;

+ 63 - 21
src/views/saleManage/quotation/index.vue

@@ -1,23 +1,31 @@
 <template>
   <div class="app-container">
     <el-card shadow="never" class="search-card">
-      <el-form :model="queryParams" ref="queryRef" inline label-width="80px">
-        <el-form-item label="客户编号" prop="customerNo">
-          <el-input v-model="queryParams.customerNo" placeholder="请输入客户编号" clearable style="width: 320px;" />
-        </el-form-item>
-        <el-form-item label="客户名称" prop="customerName">
-          <el-input v-model="queryParams.customerName" placeholder="请输入客户名称" clearable style="width: 320px;" />
-        </el-form-item>
-        <el-form-item label="项目名称" prop="projectName">
-          <el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable style="width: 320px;" />
-        </el-form-item>
-        <el-form-item label-width="0">
-          <div class="search-btns">
-            <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
-            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
-            <el-button type="primary" icon="Plus" @click="handleAdd" class="btn-new">新增</el-button>
-          </div>
-        </el-form-item>
+      <el-form :model="queryParams" ref="queryRef" label-width="80px">
+        <el-row :gutter="24">
+          <el-col :lg="6" :md="8" :sm="12">
+            <el-form-item label="客户编号" prop="customerNo">
+              <el-input v-model="queryParams.customerNo" placeholder="请输入客户编号" clearable />
+            </el-form-item>
+          </el-col>
+          <el-col :lg="6" :md="8" :sm="12">
+            <el-form-item label="客户名称" prop="customerName">
+              <el-input v-model="queryParams.customerName" placeholder="请输入客户名称" clearable />
+            </el-form-item>
+          </el-col>
+          <el-col :lg="6" :md="8" :sm="12">
+            <el-form-item label="项目名称" prop="projectName">
+              <el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable />
+            </el-form-item>
+          </el-col>
+          <el-col :lg="6" :md="8" :sm="12" style="display: flex; justify-content: flex-end;">
+            <div class="search-btns">
+              <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+              <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+              <el-button type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
+            </div>
+          </el-col>
+        </el-row>
       </el-form>
     </el-card>
 
@@ -38,8 +46,8 @@
         </el-table-column>
         <el-table-column label="操作" align="center" width="150" fixed="right">
           <template #default="scope">
-            <el-button link type="primary">详情</el-button>
-            <el-button link type="primary">编辑</el-button>
+            <el-button link type="primary" @click="handleUpdate(scope.row)">详情</el-button>
+            <el-button link type="primary" @click="handleUpdate(scope.row)">编辑</el-button>
             <el-button link type="danger" @click="handleDelete(scope.row)">删除</el-button>
           </template>
         </el-table-column>
@@ -280,6 +288,20 @@ const handleAdd = () => {
   open.value = true; 
 };
 
+/** 查看/修改详情 */
+import { getQuotation } from '@/api/saleManage/quotation/index';
+const handleUpdate = async (row) => {
+  const id = row.id;
+  if (!id) return;
+  try {
+    const res = await getQuotation(id);
+    form.value = res.data;
+    open.value = true;
+  } catch (err) {
+    console.error('获取报价单详情失败:', err);
+  }
+};
+
 /** 清单操作逻辑 */
 const handleAddGoods = () => {
   form.value.goodsList.push({
@@ -329,11 +351,11 @@ onMounted(() => {
 .app-container { padding: 20px; background-color: #f7f9fb; min-height: calc(100vh - 84px); }
 
 .search-card { 
-  margin-bottom: 20px; border: none; background: #fff; border-radius: 8px;
+  margin-bottom: 12px; border: none; background: #fff; border-radius: 8px;
   :deep(.el-form-item) { margin-bottom: 0px; }
 }
 
-.search-btns { margin-left: 20px; display: flex; gap: 12px; }
+.search-btns { display: flex; gap: 12px; align-items: center; }
 
 .table-heading {
   font-size: 16px; font-weight: 600; color: #475569; padding: 10px 5px; margin-bottom: 10px; border-bottom: 1px solid #e2e8f0;
@@ -358,4 +380,24 @@ onMounted(() => {
 }
 
 .dialog-footer { display: flex; justify-content: center; gap: 20px; }
+
+.manage-info-grid {
+  display: grid;
+  grid-template-columns: repeat(2, 1fr);
+  gap: 16px;
+  padding: 0 15px;
+  .manage-item {
+    display: flex;
+    align-items: center;
+    font-size: 13px;
+    .label {
+      color: #64748b;
+      width: 80px;
+    }
+    .value {
+      color: #1e293b;
+      font-weight: 500;
+    }
+  }
+}
 </style>

+ 138 - 0
src/views/workbench/dashboard/components/FunnelChart.vue

@@ -0,0 +1,138 @@
+<template>
+  <div ref="chartRef" style="width: 100%; height: 350px;"></div>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted, watch } from 'vue';
+import * as echarts from 'echarts';
+
+const props = defineProps({
+  data: {
+    type: Array as any,
+    default: () => []
+  },
+  title: {
+    type: String,
+    default: ''
+  }
+});
+
+const chartRef = ref<HTMLElement | null>(null);
+let chart: echarts.ECharts | null = null;
+
+const initChart = () => {
+  if (!chartRef.value || props.data.length === 0) return;
+  if (!chart) {
+    chart = echarts.init(chartRef.value);
+  }
+
+  // 颜色配置
+  const colors = ['#3AA1FF', '#4ECB73', '#FADB14'];
+
+  const funnelData = props.data.map((item: any, index: number) => ({
+    name: item.label,
+    // 为了美观,漏斗图的数据通常做一下处理,不完全按真实比例,否则数据差太大底部会看不见
+    // 这里我们使用固定比例来模拟原型图的视觉效果
+    value: 100 - index * 30,
+    realValue: item.value,
+    unit: item.unit || '',
+    itemStyle: {
+      color: colors[index % colors.length]
+    }
+  }));
+
+  const option = {
+    tooltip: {
+      trigger: 'item',
+      formatter: (params: any) => {
+        return `${params.data.name}: ${params.data.realValue}${params.data.unit}`;
+      }
+    },
+    series: [
+      {
+        name: props.title,
+        type: 'funnel',
+        left: '15%',
+        top: 20,
+        bottom: 20,
+        width: '50%',
+        minSize: '30%',
+        maxSize: '100%',
+        sort: 'descending',
+        gap: 0,
+        label: {
+          show: true,
+          position: 'inside',
+          formatter: (params: any) => {
+             return `${params.data.name}: ${params.data.realValue}${params.data.unit.includes('万') ? '万' : ''}`;
+          },
+          color: '#fff',
+          fontSize: 12
+        },
+        labelLine: {
+          show: false
+        },
+        itemStyle: {
+          borderWidth: 0
+        },
+        data: funnelData
+      },
+      // 右侧文字标注
+      {
+        name: 'Label',
+        type: 'funnel',
+        left: '15%',
+        top: 20,
+        bottom: 20,
+        width: '50%',
+        minSize: '30%',
+        maxSize: '100%',
+        sort: 'descending',
+        label: {
+          show: true,
+          position: 'right',
+          formatter: (params: any) => {
+             return `{name|${params.data.name}}\n{value|${params.data.realValue}${params.data.unit}}`;
+          },
+          rich: {
+            name: {
+              color: '#999',
+              fontSize: 12,
+              lineHeight: 18,
+              align: 'left'
+            },
+            value: {
+              color: '#333',
+              fontSize: 13,
+              fontWeight: 'bold',
+              lineHeight: 18,
+              align: 'left'
+            }
+          }
+        },
+        labelLine: {
+          show: true,
+          length: 30,
+          lineStyle: {
+            color: '#e8e8e8',
+            width: 1
+          }
+        },
+        data: funnelData,
+        z: 1 // 放在底层,只显示 labelLine 和 label
+      }
+    ]
+  };
+
+  chart.setOption(option);
+};
+
+onMounted(() => {
+  initChart();
+  window.addEventListener('resize', () => chart?.resize());
+});
+
+watch(() => props.data, () => {
+  initChart();
+}, { deep: true });
+</script>

+ 113 - 0
src/views/workbench/dashboard/components/VisitStatsChart.vue

@@ -0,0 +1,113 @@
+<template>
+  <div ref="chartRef" style="width: 100%; height: 350px;"></div>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted, watch } from 'vue';
+import * as echarts from 'echarts';
+
+const props = defineProps({
+  data: {
+    type: Array as any,
+    default: () => []
+  }
+});
+
+const chartRef = ref<HTMLElement | null>(null);
+let chart: echarts.ECharts | null = null;
+
+const initChart = () => {
+  if (!chartRef.value || props.data.length === 0) return;
+  if (!chart) {
+    chart = echarts.init(chartRef.value);
+  }
+
+  const colors = ['#3AA1FF', '#4ECB73', '#FFBB96'];
+
+  const chartData = props.data.map((item: any, index: number) => ({
+    name: item.label,
+    value: parseFloat(item.value),
+    itemStyle: {
+      color: colors[index % colors.length]
+    }
+  }));
+
+  const option = {
+    tooltip: {
+      trigger: 'item',
+      formatter: '{b}: {c}'
+    },
+    legend: {
+      orient: 'vertical',
+      right: '5%',
+      top: 'center',
+      itemWidth: 12,
+      itemHeight: 12,
+      itemGap: 25,
+      formatter: (name: string) => {
+        const item = props.data.find((i: any) => i.label === name);
+        return `{name|${name}: }{value|${item ? item.value : ''}}`;
+      },
+      textStyle: {
+        rich: {
+          name: {
+            color: '#666',
+            fontSize: 12
+          },
+          value: {
+            color: '#333',
+            fontSize: 13,
+            fontWeight: 'bold'
+          }
+        }
+      }
+    },
+    series: [
+      {
+        name: '访销统计',
+        type: 'pie',
+        radius: ['60%', '80%'],
+        center: ['35%', '50%'],
+        avoidLabelOverlap: false,
+        label: {
+          show: false,
+          position: 'center'
+        },
+        emphasis: {
+          label: {
+            show: false
+          }
+        },
+        labelLine: {
+          show: false
+        },
+        data: chartData
+      },
+      // 装饰性的背景圆环
+      {
+        type: 'pie',
+        radius: ['55%', '85%'],
+        center: ['35%', '50%'],
+        silent: true,
+        itemStyle: {
+          color: '#f8f9fb'
+        },
+        z: -1,
+        label: { show: false },
+        data: [{ value: 1 }]
+      }
+    ]
+  };
+
+  chart.setOption(option);
+};
+
+onMounted(() => {
+  initChart();
+  window.addEventListener('resize', () => chart?.resize());
+});
+
+watch(() => props.data, () => {
+  initChart();
+}, { deep: true });
+</script>

+ 201 - 0
src/views/workbench/dashboard/index.vue

@@ -0,0 +1,201 @@
+<template>
+  <div class="dashboard-container">
+    <el-row :gutter="20">
+      <!-- 跟进中项目商机 -->
+      <el-col :span="12">
+        <div class="stat-card">
+          <div class="card-header">
+            <span class="title">跟进中项目商机</span>
+          </div>
+          <div class="card-body">
+            <div class="stat-grid">
+              <div v-for="(item, index) in opportunityStats" :key="index" class="stat-item">
+                <div class="label">{{ item.label }}</div>
+                <div class="value">{{ item.value }}</div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </el-col>
+
+      <!-- 跟进中的年度入围 -->
+      <el-col :span="12">
+        <div class="stat-card">
+          <div class="card-header">
+            <span class="title">跟进中的年度入围</span>
+          </div>
+          <div class="card-body">
+            <div class="stat-grid">
+              <div v-for="(item, index) in selectionStats" :key="index" class="stat-item">
+                <div class="label">{{ item.label }}</div>
+                <div class="value">{{ item.value }}</div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </el-col>
+    </el-row>
+
+    <!-- 第二排:统计漏斗 -->
+    <el-row :gutter="20" class="mt20">
+      <el-col :span="8">
+        <div class="stat-card">
+          <div class="card-header">
+            <span class="title">商机漏斗</span>
+          </div>
+          <div class="card-body">
+            <FunnelChart :data="opportunityFunnel" title="商机漏斗" />
+          </div>
+        </div>
+      </el-col>
+      <el-col :span="8">
+        <div class="stat-card">
+          <div class="card-header">
+            <span class="title">客户漏斗</span>
+          </div>
+          <div class="card-body">
+            <FunnelChart :data="customerFunnel" title="客户漏斗" />
+          </div>
+        </div>
+      </el-col>
+      <el-col :span="8">
+        <div class="stat-card">
+          <div class="card-header">
+            <span class="title">访销统计</span>
+          </div>
+          <div class="card-body">
+            <VisitStatsChart :data="visitStats" />
+          </div>
+        </div>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted } from 'vue';
+import { getWorkbenchStat } from '@/api/workbench';
+import FunnelChart from './components/FunnelChart.vue';
+import VisitStatsChart from './components/VisitStatsChart.vue';
+
+// 商机统计数据
+const opportunityStats = ref([
+  { label: '商机数', value: '0' },
+  { label: '金额(万)', value: '0' },
+  { label: '3天未跟进', value: '0' },
+  { label: '7天未跟进', value: '0' },
+  { label: '7天以上未跟进', value: '0' },
+  { label: '滞留超过30天', value: '0' }
+]);
+
+// 年度入围统计数据
+const selectionStats = ref([
+  { label: '入围项目数', value: '0' },
+  { label: '金额(万)', value: '0' },
+  { label: '3天未跟进', value: '0' },
+  { label: '3天以上未跟进', value: '0' },
+  { label: '7天以上未跟进', value: '0' },
+  { label: '滞留超过30天', value: '0' }
+]);
+
+// 商机漏斗数据
+const opportunityFunnel = ref([]);
+// 客户漏斗数据
+const customerFunnel = ref([]);
+// 访销统计数据
+const visitStats = ref([]);
+
+/** 获取统计数据 */
+const getStats = async () => {
+  try {
+    const res = await getWorkbenchStat();
+    if (res.data) {
+      opportunityStats.value = res.data.opportunityStats;
+      selectionStats.value = res.data.selectionStats;
+      opportunityFunnel.value = res.data.opportunityFunnel;
+      customerFunnel.value = res.data.customerFunnel;
+      visitStats.value = res.data.visitStats;
+    }
+  } catch (error) {
+    console.error('获取统计数据失败', error);
+  }
+};
+
+onMounted(() => {
+  getStats();
+});
+</script>
+
+<style lang="scss" scoped>
+.dashboard-container {
+  padding: 20px;
+  background-color: #f5f7fa;
+  min-height: calc(100vh - 84px);
+
+  .mt20 {
+    margin-top: 20px;
+  }
+
+  .stat-card {
+    background: #fff;
+    border-radius: 4px;
+    border: 1px solid #e6ebf5;
+    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
+    height: 100%;
+
+    transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
+    cursor: pointer;
+
+    &:hover {
+      transform: translateY(-5px);
+      box-shadow: 0 15px 30px rgba(0, 0, 0, 0.1);
+      border-color: #409EFF;
+    }
+
+    .card-header {
+      padding: 15px 20px;
+      border-bottom: 1px solid #f0f0f0;
+      
+      .title {
+        font-size: 16px;
+        font-weight: bold;
+        color: #333;
+      }
+    }
+
+    .card-body {
+      padding: 20px;
+
+      .stat-grid {
+        display: flex;
+        flex-wrap: wrap;
+        gap: 10px;
+
+        .stat-item {
+          flex: 1;
+          min-width: 100px;
+          background-color: #f8f9fb;
+          padding: 12px 15px;
+          border-radius: 4px;
+          display: flex;
+          flex-direction: column;
+          align-items: flex-start;
+          justify-content: center;
+
+          .label {
+            font-size: 12px;
+            color: #999;
+            margin-bottom: 8px;
+          }
+
+          .value {
+            font-size: 18px;
+            font-weight: 500;
+            color: #333;
+          }
+        }
+      }
+    }
+  }
+}
+</style>