xiaohaizhao 2 месяцев назад
Родитель
Сommit
aabb7b6959

+ 71 - 2
CRM/customer/create.js

@@ -145,8 +145,77 @@ Page({
       selectMode: options.selectMode === 'true'
     });
     
-    // 如果是编辑模式,获取客户详情
-    if (options.edit) {
+    // 检查是否是从线索详情页面跳转过来的
+    if (options.clueId && options.clueInfo) {
+      try {
+        const clueInfo = JSON.parse(decodeURIComponent(options.clueInfo));
+        let form = this.data.form;
+        
+        // 填充线索信息到表单
+        form = form.map(v => {
+          switch (v.valueName) {
+            case "name":
+              v.value = clueInfo.name || '';
+              break;
+            case "phonenumber":
+              v.value = clueInfo.phonenumber || '';
+              break;
+            case "sex":
+              v.value = clueInfo.sex || '';
+              break;
+            case "source":
+              // 设置客户来源为线索转化
+              v.radioList = [{
+                name: '线索转化',
+                id: '线索转化'
+              }];
+              v.disabled = true;
+              v.value = '线索转化';
+              break;
+            case "sat_orderclueid":
+              // 禁用选择线索字段
+              v.disabled = true;
+              v.value = [clueInfo.name,[clueInfo.sat_orderclueid]] || '';
+              break;
+            case "ext_no":
+              v.value = clueInfo.ext_no || '';
+              break;
+            case "region":
+              v.value = clueInfo.province ? [clueInfo.province, clueInfo.city, clueInfo.county] : [];
+              break;
+            case "address":
+              v.value = clueInfo.address || '';
+              break;
+            case "community":
+              v.value = clueInfo.community || '';
+              break;
+            case "remarks":
+              v.value = clueInfo.notes || '';
+              break;
+            default:
+              v.value = v.value;
+              break;
+          }
+          return v
+        });
+        
+        this.setData({
+          "content.sat_orderclueid": [options.clueId, [options.clueId]],
+          form
+        });
+        
+        this.selectComponent("#Form").confirm()
+        
+        // 设置页面标题为转化客户
+        wx.setNavigationBarTitle({
+          title: '转化客户'
+        })
+      } catch (error) {
+        console.error('解析线索信息失败', error);
+        this.getCustomerSourceList();
+      }
+    } else if (options.edit) {
+      // 如果是编辑模式,获取客户详情
       let data = _Http.detail;
       let form = this.data.form.filter(v => v.label != '选择线索');
       data.region = data.province ? [data.province, data.city, data.county] : [];

+ 3 - 1
CRM/customer/detail.js

@@ -270,7 +270,9 @@ Page({
             })
           }
         })
-      } else {}
+      } else if (page.__route__ == 'CRM/lead/detail') {
+        page.getDetail()
+      }
     })
   }
 });

+ 427 - 16
CRM/lead/detail.js

@@ -1,25 +1,436 @@
+const _Http = getApp().globalData.http;
+
 Page({
   data: {
-    leadInfo: {}
+    detail: {},
+    followRecords: [],
+    operationRecords: [],
+    teamList: [],
+    showActionSheet: false,
+    teamActions: [],
+    currentOperation: '', // 记录当前操作类型:'分配'或'撤回'
+    showInvalidModal: false, // 控制无效原因模态框显示
+    invalidReason: '', // 无效原因
+    showAssignConfirm: false, // 控制分配确认弹窗显示
+    showWithdrawConfirm: false, // 控制撤回确认弹窗显示
+    selectedTeam: null, // 存储选择的团队成员
+    tabsList: [{
+        label: '跟进记录',
+        icon: "icon-tabgenjinjilu"
+      },
+      {
+        icon: "icon-tabcaozuojilu1",
+        label: '操作记录',
+        model: "#record"
+      }
+    ],
+    tabbarList: [],
+    tabsActive: 0
   },
   onLoad(options) {
-    const id = options.id;
-    this.getLeadDetail(id);
-  },
-  getLeadDetail(id) {
-    // 模拟获取线索详情数据
-    const mockData = {
-      id: id,
-      name: `线索${id}`,
-      source: id === 1 ? '网站' : id === 2 ? '电话' : '展会',
-      status: id === 1 ? '待跟进' : id === 2 ? '跟进中' : '已转化',
-      contact: `1380013800${id}`,
-      email: `lead${id}@example.com`,
-      description: `线索${id}的详细描述`,
-      createTime: '2026-03-30'
+    let auth = wx.getStorageSync("auth").wcrmlead;
+    this.setData({
+      sat_orderclueid: options.id,
+      authoptions: auth.options
+    })
+    this.getDetail();
+  },
+  getDetail() {
+    _Http.basic({
+      classname: "webmanage.saletool.orderclue.publicclue.PublicClue",
+      method: "selectDetail",
+      content: {
+        sat_orderclueid: this.data.sat_orderclueid
+      },
+    }).then(res => {
+      console.log("线索详情", res)
+      if (res.code === 1) {
+        this.setData({
+          detail: res.data || {},
+        });
+        this.partialRenewal();
+        this.getTeamList();
+      } else {
+        wx.showToast({
+          title: '获取线索详情失败',
+          icon: 'none'
+        });
+      }
+    }).catch(err => {
+      wx.showToast({
+        title: '网络错误',
+        icon: 'none'
+      });
+    });
+  },
+  tabsChange(e) {
+    this.setData({
+      tabsActive: e.detail
+    });
+    this.partialRenewal();
+  },
+  tabbarOnClick(e) {
+    switch (e.detail.label) {
+      case '线索分配':
+        this.setData({
+          currentOperation: '分配',
+          showActionSheet: true
+        });
+        break;
+      case '撤回':
+        // 撤回功能:默认选择团队中的负责人
+        const {
+          teamList
+        } = this.data;
+        const leader = teamList.find(team => team.isleader === 1);
+        if (leader) {
+          this.setData({
+            selectedTeam: leader,
+            showWithdrawConfirm: true
+          });
+        } else {
+          wx.showToast({
+            title: '未找到团队负责人',
+            icon: 'none'
+          });
+        }
+        break;
+      case '转化客户':
+        // 跳转到新建客户页面,默认带入当前的线索信息
+        const {
+          detail
+        } = this.data;
+        wx.navigateTo({
+          url: `/CRM/customer/create?clueId=${detail.sat_orderclueid}&clueInfo=${encodeURIComponent(JSON.stringify(detail))}`
+        });
+        break;
+      case '无效':
+        // 显示无效原因模态框
+        this.setData({
+          showInvalidModal: true,
+          invalidReason: ''
+        });
+        break;
+
+      default:
+        break;
+    }
+  },
+  // 关闭动作面板
+  onActionSheetClose() {
+    this.setData({
+      showActionSheet: false,
+      currentOperation: ''
+    });
+  },
+  // 处理无效原因输入
+  onInvalidReasonInput(e) {
+    this.setData({
+      invalidReason: e.detail.value
+    });
+  },
+  // 确认无效操作
+  confirmInvalid() {
+    const {
+      invalidReason,
+      sat_orderclueid
+    } = this.data;
+
+    // 验证无效原因是否为空
+    if (!invalidReason) {
+      wx.showToast({
+        title: '请输入无效原因',
+        icon: 'none'
+      });
+      return;
+    }
+
+    // 构建请求参数
+    const content = {
+      sat_orderclueid: sat_orderclueid,
+      content: invalidReason,
+      logtype: "无效",
+      followTime: new Date().toISOString().slice(0, 19).replace('T', ' ') // 当前时间
     };
+
+    // 调用跟进接口
+    _Http.basic({
+      id: "20221208100602",
+      content
+    }).then(res => {
+      console.log('标记无效结果:', res);
+
+      // 关闭模态框
+      this.setData({
+        showInvalidModal: false,
+        invalidReason: ''
+      });
+
+      if (res.code === 1) {
+        // 显示成功提示
+        wx.showToast({
+          title: '线索已标记为无效',
+          icon: 'none'
+        });
+
+        // 刷新详情页
+        this.getDetail();
+      } else {
+        wx.showToast({
+          title: res.msg || '标记无效失败',
+          icon: 'none'
+        });
+      }
+    }).catch(err => {
+      console.error('标记无效失败:', err);
+
+      // 关闭模态框
+      this.setData({
+        showInvalidModal: false,
+        invalidReason: ''
+      });
+
+      wx.showToast({
+        title: '网络错误',
+        icon: 'none'
+      });
+    });
+  },
+  // 取消无效操作
+  cancelInvalid() {
+    this.setData({
+      showInvalidModal: false,
+      invalidReason: ''
+    });
+  },
+  // 确认分配
+  confirmAssign() {
+    const {
+      selectedTeam
+    } = this.data;
+    if (selectedTeam) {
+      this.assignClue(selectedTeam);
+    }
+    this.setData({
+      showAssignConfirm: false,
+      selectedTeam: null
+    });
+  },
+  // 取消分配
+  cancelAssign() {
+    this.setData({
+      showAssignConfirm: false,
+      selectedTeam: null
+    });
+  },
+  // 确认撤回
+  confirmWithdraw() {
+    const {
+      selectedTeam
+    } = this.data;
+    if (selectedTeam) {
+      this.assignClue(selectedTeam);
+    }
+    this.setData({
+      showWithdrawConfirm: false,
+      selectedTeam: null
+    });
+  },
+  // 取消撤回
+  cancelWithdraw() {
+    this.setData({
+      showWithdrawConfirm: false,
+      selectedTeam: null
+    });
+  },
+  // 选择团队成员
+  onActionSheetSelect(event) {
+    const selectedTeam = event.detail.data;
+    this.setData({
+      selectedTeam: selectedTeam,
+      showAssignConfirm: true
+    });
+  },
+  // 分配线索
+  assignClue(team) {
+    _Http.basic({
+      "classname": "crm.agent.orderclue.orderclue",
+      "method": "changeClue",
+      content: {
+        sat_orderclueid: [this.data.sat_orderclueid],
+        sys_enterprise_hrid: team.sys_enterprise_hrid,
+        ordercluecount: "待跟进"
+      }
+    }).then(res => {
+      console.log("线索分配结果", res)
+      if (res.code === 1) {
+        wx.showToast({
+          title: '线索分配成功',
+          icon: "none"
+        });
+        // 关闭动作面板
+        this.setData({
+          showActionSheet: false
+        });
+        // 刷新详情页
+        this.getDetail();
+      } else {
+        wx.showToast({
+          title: '线索分配失败',
+          icon: 'none'
+        });
+      }
+    }).catch(err => {
+      console.error('线索分配失败', err);
+      wx.showToast({
+        title: '网络错误',
+        icon: 'none'
+      });
+    });
+  },
+  //局部数据更新 tabs
+  partialRenewal(init = false) {
+    let model = this.data.tabsList[this.data.tabsActive].model;
+    if (model) {
+      let Component = this.selectComponent(model),
+        {
+          total,
+          pageNumber,
+          pageTotal
+        } = Component.data.content,
+        id = this.data.sat_orderclueid;
+      if (model == "#Files") init = true;
+      if (total == null || init) {
+        Component.getList(id, init);
+      } else if (pageNumber <= pageTotal) {
+        Component.getList(id, false);
+      }
+    } else if (this.data.tabsActive === 0) {
+      // 跟进记录Tab
+      let Component = this.selectComponent('#FollowRecord');
+      if (Component) {
+        Component.getList(this.data.sat_orderclueid, init);
+      }
+    }
+  },
+  onReachBottom() {
+    this.partialRenewal();
+  },
+  onUnload() {
+    _Http.updateList && _Http.updateList()
+    const page = getCurrentPages().find(v => v.__route__ == 'CRM/lead/index');
+    if (!page) return;
+    let content = JSON.parse(JSON.stringify(page.data.content));
+    content.pageSize = (content.pageNumber - 1) * content.pageSize;
+    content.pageNumber = 1;
+    _Http.basic({
+      id: page.data.id,
+      content
+    }).then(res => {
+      console.log("更新线索列表", res);
+      if (res.code == '1') {
+        page.setData({
+          list: res.data,
+          "content.total": res.total
+        })
+      }
+    })
+  },
+  // 获取经销商团队列表
+  getTeamList() {
+    _Http.basic({
+      classname: "sale.team.team",
+      method: "query_teamList",
+      content: {
+        pageNumber: 1,
+        pageSize: 99999,
+        where: {
+          condition: ""
+        }
+      },
+    }).then(res => {
+      console.log("经销商团队列表", res)
+      if (res.code === 1) {
+        const teamList = res.data || [];
+        this.setData({
+          teamList: teamList,
+        });
+
+        // 生成团队成员操作选项
+        this.generateTeamActions(teamList);
+
+        // 生成操作按钮列表
+        this.generateTabbarList(teamList);
+      } else {
+        console.error('获取经销商团队列表失败', res.msg);
+      }
+    }).catch(err => {
+      console.error('获取经销商团队列表失败', err);
+    });
+  },
+  // 生成团队成员操作选项
+  generateTeamActions(teamList) {
+    const {
+      detail,
+      currentOperation
+    } = this.data;
+    const teamActions = teamList.map(team => {
+      // 禁用与当前线索负责人姓名相同的选项
+      const disabled = detail.leadername === team.name;
+      return {
+        name: team.name,
+        subname: '在手 待跟进、跟进中 线索数量:' + team.ordercluecount,
+        disabled: disabled,
+        data: team
+      };
+    });
+    this.setData({
+      teamActions: teamActions
+    });
+  },
+  // 生成操作按钮列表
+  generateTabbarList(teamList) {
+    const {
+      detail
+    } = this.data;
+    const tabbarList = [];
+
+    // 若线索状态不等于已转化,则可使用转化客户
+    if (detail.status !== '已无效' && detail.status !== '已转化') {
+      tabbarList.push({
+        icon: "icon-tabkehu",
+        label: "转化客户"
+      });
+    }
+
+    // 查找团队中的负责人
+    const leader = teamList.find(team => team.isleader === 1);
+
+    // 若detail.leadername === teamList中isleader==1的name,则可操作分配,反之可操作撤回
+    if (detail.status !== '已无效' && this.data.authoptions.some(v => v == 'allot')) {
+      if (leader && detail.leadername === leader.name) {
+        tabbarList.push({
+          icon: "icon-tabchanpinleibie",
+          label: "线索分配"
+        });
+      } else {
+        tabbarList.push({
+          icon: "icon-a-baobeibohuituihui",
+          label: "撤回"
+        });
+      }
+    }
+
+    // 都为待跟进、跟进中状态才可用无效
+    if (detail.status !== '已无效') {
+      tabbarList.push({
+        icon: "icon-tabxiangxixinxi1",
+        label: "无效"
+      });
+    }
+
     this.setData({
-      leadInfo: mockData
+      tabbarList: tabbarList
     });
   }
 });

+ 5 - 1
CRM/lead/detail.json

@@ -1,4 +1,8 @@
 {
-  "usingComponents": {},
+  "usingComponents": {
+    "FollowRecord": "./modules/followRecord/index",
+    "van-action-sheet": "@vant/weapp/action-sheet/index",
+    "van-dialog": "@vant/weapp/dialog/index"
+  },
   "navigationBarTitleText": "线索详情"
 }

+ 131 - 11
CRM/lead/detail.scss

@@ -4,23 +4,143 @@
   min-height: 100vh;
 }
 
-.info-item {
+.intr {
+  position: relative;
+  width: 100vw;
+  box-sizing: border-box;
+  background-color: #fff;
+  padding: 24rpx;
+  margin-bottom: 12rpx;
+  border-radius: 12rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
+
+  .top {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 24rpx;
+
+    .name {
+      font-size: 34rpx;
+      font-weight: bold;
+      color: #333;
+      flex: 1;
+      margin-right: 16rpx;
+    }
+
+    .statu {
+      font-size: 24rpx;
+      padding: 6rpx 18rpx;
+      border-radius: 16rpx;
+      font-weight: 500;
+    }
+  }
+
+  .content {
+    width: 100%;
+
+    .row {
+      display: flex;
+      margin-top: 18rpx;
+      flex-wrap: wrap;
+
+      .exp {
+        flex: 1;
+        font-size: 26rpx;
+        color: #666;
+        line-height: 38rpx;
+
+        &.full-width {
+          flex: none;
+          width: 100%;
+        }
+      }
+    }
+  }
+}
+
+/* 各tab组件的样式 */
+.follow-record,
+.operation-record {
+  padding: 24rpx;
+  background-color: #fff;
+  margin-bottom: 12rpx;
+  border-radius: 12rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
+
+  .placeholder {
+    text-align: center;
+    color: #999;
+    padding: 120rpx 0;
+    font-size: 28rpx;
+  }
+}
+
+/* 记录项样式 */
+.record-item {
+  padding: 20rpx;
+  border-bottom: 1rpx solid #f0f0f0;
+}
+
+.record-item:last-child {
+  border-bottom: none;
+}
+
+.record-header {
   display: flex;
+  justify-content: space-between;
   align-items: center;
-  padding: 20rpx;
-  background-color: #fff;
-  margin-bottom: 10rpx;
-  border-radius: 8rpx;
+  margin-bottom: 12rpx;
 }
 
-.label {
-  width: 160rpx;
-  font-size: 28rpx;
+.record-person {
+  font-size: 26rpx;
+  font-weight: bold;
+  color: #333;
+}
+
+.record-time {
+  font-size: 24rpx;
+  color: #999;
+}
+
+.record-content {
+  font-size: 26rpx;
+  color: #333;
+  line-height: 1.5;
+  margin-bottom: 12rpx;
+  word-break: break-all;
+}
+
+.record-convert {
+  display: flex;
+  align-items: center;
+  font-size: 24rpx;
+}
+
+.convert-label {
   color: #666;
+  margin-right: 8rpx;
+}
+
+.convert-value {
+  color: #085CDF;
+  font-weight: bold;
 }
 
-.value {
-  flex: 1;
+/* 底部间距 */
+.bottom-space {
+  height: 130rpx;
+}
+
+
+.textarea {
+  height: 180rpx;
+  padding: 20rpx;
+  border: 2rpx solid #e8e8e8;
+  border-radius: 10rpx;
   font-size: 28rpx;
-  color: #333;
+  line-height: 40rpx;
+  transition: all 0.3s ease;
+  margin: 40rpx 20rpx;
 }

+ 89 - 28
CRM/lead/detail.wxml

@@ -1,30 +1,91 @@
-<view class="container">
-  <view class="info-item">
-    <view class="label">线索名称</view>
-    <view class="value">{{leadInfo.name}}</view>
+<view class="intr">
+  <view class="top">
+    <view class="name">{{detail.name || '--'}}</view>
+    <view class="statu" style="background-color: {{set.color(detail.status)}}; color: #fff;">
+      {{detail.status || '--'}}
+    </view>
   </view>
-  <view class="info-item">
-    <view class="label">来源</view>
-    <view class="value">{{leadInfo.source}}</view>
+  <view class="content">
+    <view class="row">
+      <view class="exp">手机号:{{detail.phonenumber || '--'}}</view>
+      <view class="exp">来源:{{detail.cluesource || '--'}}</view>
+    </view>
+    <view class="row">
+      <view class="exp">负责人:{{detail.leadername || '--'}}</view>
+      <view class="exp">状态:{{detail.status || '--'}}</view>
+    </view>
+    <view class="row">
+      <view class="exp full-width">省市县:{{(detail.province || '--') + '-' + (detail.city || '--') + '-' + (detail.county
+        || '--')}}</view>
+    </view>
+    <view class="row">
+      <view class="exp full-width">地址:{{detail.address || '--'}}</view>
+    </view>
+    <view class="row">
+      <view class="exp full-width">备注:{{detail.notes || '--'}}</view>
+    </view>
   </view>
-  <view class="info-item">
-    <view class="label">状态</view>
-    <view class="value">{{leadInfo.status}}</view>
-  </view>
-  <view class="info-item">
-    <view class="label">联系电话</view>
-    <view class="value">{{leadInfo.contact}}</view>
-  </view>
-  <view class="info-item">
-    <view class="label">邮箱</view>
-    <view class="value">{{leadInfo.email}}</view>
-  </view>
-  <view class="info-item">
-    <view class="label">创建时间</view>
-    <view class="value">{{leadInfo.createTime}}</view>
-  </view>
-  <view class="info-item">
-    <view class="label">描述</view>
-    <view class="value">{{leadInfo.description}}</view>
-  </view>
-</view>
+</view>
+
+<Yl_FunTabs list='{{tabsList}}' active='{{tabsActive}}' bind:onChenge="tabsChange">
+  <FollowRecord slot='跟进记录' id='FollowRecord' clue-id="{{detail.sat_orderclueid}}" />
+  <record slot='操作记录' id='record' ownertable='sat_orderclue' ownerid='{{sat_orderclueid}}' />
+</Yl_FunTabs>
+<view style="height: 130rpx;" />
+<Yl_Tabbar wx:if="{{tabbarList.length}}" list='{{tabbarList}}' bind:callback="tabbarOnClick" />
+
+<!-- 线索分配动作面板 -->
+<van-action-sheet show="{{ showActionSheet }}" title="选择负责人" actions="{{ teamActions }}" cancel-text="取消" zIndex='99999999' bind:cancel='onActionSheetClose' bind:close="onActionSheetClose" bind:select="onActionSheetSelect" />
+
+<!-- 无效原因模态框 -->
+<van-dialog show="{{ showInvalidModal }}" title="填写无效原因" show-cancel-button bind:confirm="confirmInvalid" bind:cancel="cancelInvalid" confirm-button-color="#385CDF" use-slot>
+  <textarea class="textarea" value="{{ invalidReason }}" placeholder="请输入无效原因" bind:input="onInvalidReasonInput" bind:focus="onTextareaFocus" bind:blur="onTextareaBlur" placeholder-style="color: #999; font-size: 26rpx;" />
+</van-dialog>
+
+<!-- 分配确认弹窗 -->
+<van-dialog
+  show="{{ showAssignConfirm }}"
+  title="确认分配"
+  message="确定要将此线索分配给所选负责人吗?"
+  show-cancel-button
+  bind:confirm="confirmAssign"
+  bind:cancel="cancelAssign"
+  confirm-button-color="#385CDF"
+/>
+
+<!-- 撤回确认弹窗 -->
+<van-dialog
+  show="{{ showWithdrawConfirm }}"
+  title="确认撤回"
+  message="确定要将此线索撤回给团队负责人吗?"
+  show-cancel-button
+  bind:confirm="confirmWithdraw"
+  bind:cancel="cancelWithdraw"
+  confirm-button-color="#385CDF"
+/>
+
+<wxs module="set">
+  module.exports = {
+    color: function (statu) {
+      var color = '#999999';
+      switch (statu) {
+        case "待跟进":
+          color = '#FA8C16';
+          break;
+        case "跟进中":
+          color = '#52C41A';
+          break;
+        case "已无效":
+          color = '#FF4D4F';
+          break;
+        case "已转化":
+          color = '#1890FF';
+          break;
+        case "已成交":
+          color = '#722ED1';
+          break;
+      };
+      return color;
+    }
+  }
+</wxs>

+ 60 - 10
CRM/lead/index.js

@@ -1,21 +1,71 @@
+const _Http = getApp().globalData.http;
+
 Page({
   data: {
-    leadList: []
+    "id": 20221101094502,
+    list: [],
+    content: {
+      isAll: false,
+      nocache: true,
+      pageNumber: 1,
+      pageSize: 20,
+      pageTotal: 1,
+      where: {
+        condition: "",
+        status: ""
+      },
+      tableid: 1784
+    }
   },
   onLoad() {
-    this.getLeadList();
+    this.getList();
   },
-  getLeadList() {
-    // 模拟获取线索列表数据
-    const mockData = [
-      { id: 1, name: '线索1', source: '网站', status: '待跟进' },
-      { id: 2, name: '线索2', source: '电话', status: '跟进中' },
-      { id: 3, name: '线索3', source: '展会', status: '已转化' }
-    ];
+  /* 获取线索列表 */
+  getList(init = false) {
+    if (init.detail != undefined) init = init.detail;
+    if (init) {
+      this.setData({
+        'content.pageNumber': 1
+      });
+    }
+    if (this.data.content.pageNumber > this.data.content.pageTotal) return;
+    this.setListHeight();
+
+    _Http.basic({
+      id: this.data.id,
+      content: this.data.content
+    }).then(res => {
+      console.log("线索列表数据", res);
+      this.selectComponent('#ListBox').RefreshToComplete();
+      this.setData({
+        'content.pageNumber': res.pageNumber + 1,
+        'content.pageTotal': res.pageTotal,
+        list: res.pageNumber == 1 ? res.data : this.data.list.concat(res.data),
+        total: res.total
+      });
+    }).catch(err => {
+      console.error("获取线索列表失败", err);
+      this.selectComponent('#ListBox').RefreshToComplete();
+      wx.showToast({
+        title: '获取线索列表失败',
+        icon: 'none'
+      });
+    });
+  },
+  /* 设置页面高度 */
+  setListHeight() {
+    this.selectComponent("#ListBox").setHeight(".header", this);
+  },
+  /* 搜索 */
+  onSearch({
+    detail
+  }) {
     this.setData({
-      leadList: mockData
+      'content.where.condition': detail
     });
+    this.getList(true);
   },
+  /* 跳转到详情页 */
   goToDetail(e) {
     const id = e.currentTarget.dataset.id;
     wx.navigateTo({

+ 59 - 27
CRM/lead/index.scss

@@ -1,37 +1,69 @@
 .container {
-  padding: 10rpx;
+  background-color: #f5f5f5;
+  min-height: 100vh;
 }
 
-.lead-item {
+.header {
+  width: 100%;
+}
+
+.item {
   display: flex;
-  align-items: center;
-  justify-content: space-between;
-  padding: 20rpx;
+  flex-direction: column;
+  padding: 24rpx;
   background-color: #fff;
-  margin-bottom: 10rpx;
-  border-radius: 8rpx;
-  box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
-}
+  margin-bottom: 20rpx;
+  border-radius: 12rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
 
-.lead-info {
-  flex: 1;
-}
+  .top {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 16rpx;
 
-.lead-name {
-  font-size: 32rpx;
-  font-weight: bold;
-  margin-bottom: 8rpx;
-}
+    .name {
+      font-size: 34rpx;
+      font-weight: 600;
+      color: #1a1a1a;
+      flex: 1;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+      margin-right: 16rpx;
+    }
 
-.lead-source {
-  font-size: 26rpx;
-  color: #666;
-}
+    .statu {
+      font-size: 22rpx;
+      padding: 6rpx 16rpx;
+      border-radius: 6rpx;
+      font-weight: 500;
+      flex-shrink: 0;
+    }
+  }
+
+  .content {
+    width: 100%;
+
+    .row {
+      display: flex;
+      margin-top: 12rpx;
+      
+      &:first-child {
+        margin-top: 0;
+      }
+    }
+
+    .exp {
+      flex: 1;
+      font-size: 26rpx;
+      color: #666;
+      line-height: 1.5;
 
-.lead-status {
-  font-size: 28rpx;
-  color: #085CDF;
-  padding: 4rpx 16rpx;
-  background-color: #e6f0ff;
-  border-radius: 16rpx;
+      &.full-width {
+        flex: none;
+        width: 100%;
+      }
+    }
+  }
 }

+ 64 - 7
CRM/lead/index.wxml

@@ -1,9 +1,66 @@
 <view class="container">
-  <view class="lead-item" wx:for="{{leadList}}" wx:key="id" bindtap="goToDetail" data-id="{{item.id}}">
-    <view class="lead-info">
-      <view class="lead-name">{{item.name}}</view>
-      <view class="lead-source">来源: {{item.source}}</view>
+  <van-search use-action-slot placeholder='请输入搜索关键词' shape='round' bind:search="onSearch" bind:clear="onSearch" />
+  <view class="header" style="height: 20rpx;"></view>
+  <Yl_ListBox id='ListBox' bind:getlist='getList'>
+    <view wx:for="{{list}}" wx:key="sat_orderclueid" bind:tap="goToDetail" data-id="{{item.sat_orderclueid}}">
+      <view class="item">
+        <view class="top">
+          <view class="name">{{item.name || '--'}}</view>
+          <view class="statu" style="background-color: {{set.color(item.status)}}; color: #fff;">
+            {{item.status || '--'}}
+          </view>
+        </view>
+        <view class="content">
+          <view class="row">
+            <view class="exp">手机号:{{item.phonenumber || '--'}}</view>
+            <view class="exp">来源:{{item.cluesource || '--'}}</view>
+          </view>
+          <view class="row">
+            <view class="exp">负责人:{{item.leadername || '--'}}</view>
+            <view class="exp">状态:{{item.status || '--'}}</view>
+          </view>
+          <view class="row">
+            <view class="exp full-width">省市县:{{(item.province || '--') + '-' + (item.city || '--') + '-' + (item.county || '--')}}</view>
+          </view>
+          <view class="row">
+            <view class="exp full-width">地址:{{item.address || '--'}}</view>
+          </view>
+          <view class="row">
+            <view class="exp full-width">备注:{{item.notes || '--'}}</view>
+          </view>
+          <view class="row">
+            <view class="exp">创建人:{{item.followBy || '--'}}</view>
+            <view class="exp">最后跟进时间:{{item.followDate || '--'}}</view>
+          </view>
+        </view>
+      </view>
     </view>
-    <view class="lead-status">{{item.status}}</view>
-  </view>
-</view>
+    <Yl_Empty wx:if="{{list.length==0}}" />
+    <view style="height:150rpx;" />
+  </Yl_ListBox>
+</view>
+<wxs module="set">
+   module.exports = {
+    color: function (statu) {
+      var color = '#999999';
+      switch (statu) {
+        case "待跟进":
+          color = '#FA8C16';
+          break;
+        case "跟进中":
+          color = '#52C41A';
+          break;
+        case "已无效":
+          color = '#FF4D4F';
+          break;
+        case "已转化":
+          color = '#1890FF';
+          break;
+        case "已成交":
+          color = '#722ED1';
+          break;
+      };
+      return color;
+    }
+  }
+</wxs>

+ 233 - 0
CRM/lead/modules/followRecord/create.js

@@ -0,0 +1,233 @@
+const _Http = getApp().globalData.http;
+
+Page({
+  data: {
+    loading: false,
+    disabled: true,
+    showAll: false,
+    form: [{
+        label: "跟进日期",
+        error: false,
+        errMsg: "",
+        type: "date",
+        value: new Date().toISOString().split('T')[0],
+        placeholder: "请选择跟进日期",
+        valueName: "followDate",
+        required: true,
+        checking: "base"
+      },
+      {
+        label: "跟进时间",
+        error: false,
+        errMsg: "",
+        type: "time",
+        value: new Date().toTimeString().substring(0, 8),
+        placeholder: "请选择跟进时间",
+        valueName: "followTime",
+        required: true,
+        checking: "base"
+      },
+      {
+        label: "跟进方式",
+        error: false,
+        errMsg: "",
+        type: "radio",
+        value: "",
+        radioList: [],
+        valueName: "followupmode",
+        required: true,
+        checking: "base"
+      },
+      {
+        label: "跟进结果",
+        error: false,
+        errMsg: "",
+        type: "radio",
+        value: "跟进",
+        radioList: [{
+            id: "跟进",
+            name: "跟进"
+          },
+          {
+            id: "预约到店",
+            name: "预约到店"
+          },
+          {
+            id: "互加微信",
+            name: "互加微信"
+          },
+          {
+            id: "成交",
+            name: "成交"
+          },
+          {
+            id: "转化客户",
+            name: "转化客户"
+          },
+          {
+            id: "无效",
+            name: "无效"
+          }
+        ],
+        valueName: "logtype",
+        required: true,
+        checking: "base",
+        interrupt: true
+      }, {
+        label: "跟进内容",
+        error: false,
+        errMsg: "",
+        type: "textarea",
+        value: "",
+        placeholder: "请输入跟进内容",
+        valueName: "content",
+        required: true,
+        checking: "base"
+      }
+    ],
+    "content": {
+      "sat_orderclueid": "",
+      "content": "",
+      "followupmode": "",
+      "logtype": ""
+    }
+  },
+  onLoad(options) {
+    // 获取线索ID
+    if (options.sat_orderclueid) {
+      this.setData({
+        "content.sat_orderclueid": options.sat_orderclueid
+      });
+    }
+    // 获取跟进方式列表
+    this.getFollowTypeList();
+  },
+  // 获取跟进方式列表
+  getFollowTypeList() {
+    _Http.basic({
+      "classname": "sysmanage.develop.optiontype.optiontype",
+      "method": "optiontypeselect",
+      "content": {
+        "pageNumber": 1,
+        "pageSize": 1000,
+        "typename": "followType",
+        "parameter": {}
+      }
+    }).then(res => {
+      console.log("跟进方式列表", res);
+      if (res.code == 1 && res.data && res.data.length) {
+        let form = this.data.form;
+        let typeField = form.find(v => v.valueName == 'followupmode');
+        typeField.radioList = res.data.map(item => ({
+          id: item.value,
+          name: item.value
+        }));
+        // 默认选择第一个
+        if (typeField.radioList.length > 0) {
+          typeField.value = typeField.radioList[0].id;
+        }
+        this.setData({
+          form
+        });
+      }
+    }).catch(err => {
+      console.error("获取跟进方式列表失败", err);
+    });
+  },
+  submit() {
+    this.setData({
+      loading: true
+    });
+    let formData = this.selectComponent("#Form").submit();
+
+    // 合并跟进日期和时间
+    let followDateTime = formData.followDate + ' ' + formData.followTime;
+
+    // 构建content
+    let content = {
+      ...this.data.content,
+      ...formData,
+      followTime: followDateTime
+    };
+
+    // 删除不需要的字段
+    delete content.followDate;
+    delete content.followTime;
+
+    _Http.basic({
+      id: "20221208100602",
+      content
+    }).then(res => {
+      this.setData({
+        loading: false
+      });
+      console.log("保存跟进记录", res);
+      if (res.code == 1) {
+        // 刷新详情页面的跟进记录
+        getCurrentPages().find(v => v.__route__ == 'CRM/lead/detail').partialRenewal(true);
+        wx.navigateBack({
+          success() {
+            wx.showToast({
+              title: "跟进成功",
+              icon: "success"
+            });
+          }
+        });
+      } else {
+        wx.showToast({
+          title: res.msg || '保存失败',
+          icon: 'none'
+        });
+      }
+    }).catch(err => {
+      this.setData({
+        loading: false
+      });
+      console.error("保存跟进记录失败", err);
+      wx.showToast({
+        title: '网络错误',
+        icon: 'none'
+      });
+    });
+  },
+  interrupt({
+    detail
+  }) {
+    if (detail.data.label == '跟进结果') {
+      let form = detail.form;
+      const contentField = form.find(v => v.valueName == 'content');
+
+      if (detail.data.value == '无效') {
+        // 当选择"无效"时,将跟进内容的label改为"无效原因"
+        contentField.label = "无效原因";
+        contentField.placeholder = "请输入无效原因";
+      } else {
+        // 当选择其他选项时,将label改回"跟进内容"
+        contentField.label = "跟进内容";
+        contentField.placeholder = "请输入跟进内容";
+      }
+
+      this.setData({
+        form
+      });
+    }
+  },
+  /* 表单必填项是否完成 */
+  onConfirm({
+    detail
+  }) {
+    this.setData({
+      disabled: detail
+    });
+  },
+  onChange(e) {
+    this.setData({
+      showAll: e.detail
+    });
+  },
+  closePage() {
+    wx.navigateBack({
+      delta: 1
+    });
+  }
+});

+ 4 - 0
CRM/lead/modules/followRecord/create.json

@@ -0,0 +1,4 @@
+{
+  "usingComponents": {
+  }
+}

+ 25 - 0
CRM/lead/modules/followRecord/create.scss

@@ -0,0 +1,25 @@
+.container {
+  background-color: #f5f5f5;
+}
+
+.new-footer {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  padding: 20rpx;
+  background-color: #fff;
+  box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.1);
+  z-index: 999;
+}
+
+.van-button.new-submit {
+  width: 100%;
+  height: 80rpx;
+  line-height: 80rpx;
+  font-size: 32rpx;
+  border-radius: 10rpx;
+  background-color: #3874F6 !important;
+  color: #fff !important;
+  border: none !important;
+}

+ 8 - 0
CRM/lead/modules/followRecord/create.wxml

@@ -0,0 +1,8 @@
+<view class="container">
+  <Yl_Headline title='跟进记录' type='switch' switchLabel='仅显示必填信息' switch='{{showAll}}' bind:callBack='onChange' />
+  <Yl_Field id='Form' form='{{form}}' showAll='{{!showAll}}' bind:onConfirm='onConfirm' bind:interrupt="interrupt" />
+  <view style="height: 150rpx;" />
+  <view class="new-footer">
+    <van-button custom-class='new-submit' disabled='{{disabled || loading}}' loading='{{loading}}' bindtap='submit'>确定</van-button>
+  </view>
+</view>

+ 61 - 0
CRM/lead/modules/followRecord/index.js

@@ -0,0 +1,61 @@
+const _Http = getApp().globalData.http;
+
+Component({
+  properties: {},
+  data: {
+    list: [],
+    content: {
+      nocache: true,
+      pageNumber: 1,
+      pageSize: 20,
+      pageTotal: 1,
+      total: null,
+      isdesc: 1,
+      where: {
+        start: "",
+        end: ""
+      }
+    }
+  },
+  methods: {
+    getList(id, init) {
+      let content = this.data.content;
+      content.sat_orderclueid = id || content.sat_orderclueid;
+      if (init) content.pageNumber = 1;
+      if(content.pageNumber > content.pageTotal) return;
+      const data = {
+        classname: "webmanage.saletool.orderclue.publicclue.PublicClue",
+        method: "getFollowList",
+        content
+      };
+      _Http.basic(data).then(res => {
+        console.log("线索跟进列表", res);
+        if (res.code == 1) {
+          this.setData({
+            list: res.pageNumber == 1 ? res.data : this.data.list.concat(res.data),
+            "content.pageNumber": res.pageNumber + 1,
+            "content.pageSize": res.pageSize,
+            "content.pageTotal": res.pageTotal,
+            "content.total": res.total
+          });
+        } else {
+          wx.showToast({
+            title: res.msg || '获取跟进记录失败',
+            icon: "none"
+          });
+        }
+      }).catch(err => {
+        console.error("获取跟进记录失败", err);
+        wx.showToast({
+          title: '网络错误',
+          icon: "none"
+        });
+      });
+    },
+    add() {
+      wx.navigateTo({
+        url: `/CRM/lead/modules/followRecord/create?sat_orderclueid=${this.data.content.sat_orderclueid}`
+      });
+    }
+  }
+});

+ 4 - 0
CRM/lead/modules/followRecord/index.json

@@ -0,0 +1,4 @@
+{
+  "component": true,
+  "usingComponents": {}
+}

+ 116 - 0
CRM/lead/modules/followRecord/index.scss

@@ -0,0 +1,116 @@
+.head {
+  display: flex;
+  align-items: center;
+  width: 100vw;
+  height: 120rpx;
+  padding: 0 20rpx 0 30rpx;
+  box-sizing: border-box;
+
+  .count {
+      font-size: 28rpx;
+      font-family: PingFang SC-Regular, PingFang SC;
+      color: #333333;
+  }
+
+  .expand {
+      flex: 1;
+      display: flex;
+      justify-content: flex-end;
+
+      .but {
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          width: 80rpx;
+          height: 80rpx;
+          background: #FFFFFF;
+          border-radius: 8rpx;
+          border: 2rpx solid #CCCCCC;
+          margin-left: 20rpx;
+          color: #666666;
+      }
+  }
+}
+
+
+.container {
+  background-color: #f5f5f5;
+}
+
+.header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20rpx;
+
+  .title {
+    font-size: 32rpx;
+    font-weight: bold;
+    color: #333;
+  }
+
+  .add-btn {
+    font-size: 28rpx;
+    color: #085CDF;
+    padding: 8rpx 16rpx;
+    border: 1rpx solid #085CDF;
+    border-radius: 8rpx;
+  }
+}
+
+.follow-list {
+  background-color: #fff;
+  border-radius: 12rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
+  overflow: hidden;
+}
+
+.follow-item {
+  padding: 24rpx;
+  border-bottom: 1rpx solid #f0f0f0;
+
+  &:last-child {
+    border-bottom: none;
+  }
+
+  .follow-header {
+    margin-bottom: 16rpx;
+
+    .follow-title {
+      font-size: 26rpx;
+      color: #333;
+      line-height: 1.4;
+
+      .bold-text {
+        font-weight: bold;
+      }
+    }
+  }
+
+  .follow-content {
+    font-size: 26rpx;
+    color: #333;
+    line-height: 1.5;
+    margin-bottom: 16rpx;
+    word-break: break-all;
+    background-color: #F1F2F3;
+    border-radius: 8rpx;
+    padding: 20rpx;
+  }
+
+  .follow-footer {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+
+    .follow-mode {
+      font-size: 24rpx;
+      color: #666;
+    }
+
+    .follow-type {
+      font-size: 24rpx;
+      color: #666;
+    }
+  }
+}

+ 23 - 0
CRM/lead/modules/followRecord/index.wxml

@@ -0,0 +1,23 @@
+<view class="head">
+  <view class="count">跟进记录</view>
+  <view class="expand">
+    <navigator url="#" class="but" bindtap="add">
+      <van-icon name="plus" />
+    </navigator>
+  </view>
+</view>
+
+<view class="container">
+  <view class="follow-list">
+    <view wx:for="{{list}}" wx:key="index" class="follow-item">
+      <view class="follow-header">
+        <view class="follow-title">
+          {{item.rowindex || index + 1}}. {{item.createdate || '--'}}, 由经销商端 <text class="bold-text">{{item.createby || '--'}}</text> {{item.logtype || '转化'}}, 跟进方式: <text class="bold-text">{{item.followupmode || '--'}}</text>, 跟进内容:
+        </view>
+      </view>
+      <view class="follow-content">{{item.content || '--'}}</view>
+    </view>
+  </view>
+</view>
+
+<Yl_Empty wx:if="{{list.length==0}}" />

+ 1 - 1
CRM/order/index.scss

@@ -76,7 +76,7 @@
   bottom: 0;
   left: 0;
   right: 0;
-  padding: 20rpx 30rpx;
+  padding: 10rpx 30rpx 20rpx;
   background-color: #fff;
   box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.1);
   display: flex;

+ 1 - 0
app.json

@@ -125,6 +125,7 @@
         "lead/detail",
         "customer/modules/order/details",
         "customer/modules/followRecord/create",
+        "lead/modules/followRecord/create",
         "customer/modules/orderCreate/create",
         "customer/modules/orderCreate/productSelect/index"
       ]

+ 4 - 0
components/Yl_Field/index.js

@@ -27,6 +27,8 @@ Component({
 			const {
 				item
 			} = e.currentTarget.dataset;
+			// 如果字段被禁用,不执行跳转
+			if (item.disabled) return;
 			getApp().globalData.handleSelect = this.handleRoute.bind(this);
 			wx.navigateTo({
 				url: item.url + '?params=' + JSON.stringify(item.params) + (item.query || '')
@@ -245,6 +247,8 @@ Component({
 		clearValue(e) {
 			let item = e.currentTarget.dataset.item,
 				index = this.data.form.findIndex(v => v.valueName === item.valueName);
+			// 如果字段被禁用,不执行清空操作
+			if (item.disabled) return;
 			this.setData({
 				[`form[${index}].value`]: [],
 				[`form[${index}].error`]: false,

+ 9 - 2
project.private.config.json

@@ -24,12 +24,19 @@
   "condition": {
     "miniprogram": {
       "list": [
+        {
+          "name": "CRM/lead/detail",
+          "pathName": "CRM/lead/detail",
+          "query": "id=424",
+          "scene": null,
+          "launchMode": "default"
+        },
         {
           "name": "CRM/customer/detail",
           "pathName": "CRM/customer/detail",
           "query": "id=5",
-          "scene": null,
-          "launchMode": "default"
+          "launchMode": "default",
+          "scene": null
         },
         {
           "name": "客户列表",