xiaohaizhao 1 ماه پیش
والد
کامیت
826fcc9143

+ 11 - 34
CRM/contract/detail.wxml

@@ -119,8 +119,9 @@
         </view>
       </view>
     </block>
-
-    <Yl_Empty wx:if="{{list.length === 0}}" text="暂无经销商" />
+    <view wx:if="{{list.length === 0}}" style="margin-left: -40rpx;padding-bottom: 120rpx;">
+      <Yl_Empty text="暂无经销商" />
+    </view>
   </view>
 </view>
 
@@ -130,32 +131,14 @@
 <Yl_Tabbar wx:if="{{tabbarList.length}}" list='{{tabbarList}}' bind:callback="tabbarOnClick" />
 
 <!-- 编辑期限弹窗 -->
-<van-popup
-  show="{{ showEditTerm }}"
-  round
-  z-index="9999"
-  custom-style="width: 80%; max-width: 600rpx; overflow: hidden;"
-  bind:close="closeEditTerm"
->
+<van-popup show="{{ showEditTerm }}" round z-index="9999" custom-style="width: 80%; max-width: 600rpx; overflow: hidden;" bind:close="closeEditTerm">
   <view class="modal-header">编辑合同期限</view>
   <view class="modal-body">
     <picker mode="date" value="{{ beginDate }}" start="2020-01-01" end="2030-12-31" bind:change="onBeginDateChange">
-      <van-field
-        label="开始日期"
-        value="{{ beginDate }}"
-        placeholder="请选择开始日期"
-        readonly
-        is-link
-      />
+      <van-field label="开始日期" value="{{ beginDate }}" placeholder="请选择开始日期" readonly is-link />
     </picker>
     <picker mode="date" value="{{ endDate }}" start="2020-01-01" end="2030-12-31" bind:change="onEndDateChange">
-      <van-field
-        label="结束日期"
-        value="{{ endDate }}"
-        placeholder="请选择结束日期"
-        readonly
-        is-link
-      />
+      <van-field label="结束日期" value="{{ endDate }}" placeholder="请选择结束日期" readonly is-link />
     </picker>
   </view>
   <view class="modal-footer">
@@ -165,22 +148,16 @@
 </van-popup>
 
 <!-- 编辑金额弹窗 -->
-<van-popup
-  show="{{ showEditAmount }}"
-  round
-  z-index="9999"
-  custom-style="width: 80%; max-width: 600rpx; overflow: hidden;"
-  bind:close="closeEditAmount"
->
+<van-popup show="{{ showEditAmount }}" round z-index="9999" custom-style="width: 80%; max-width: 600rpx; overflow: hidden;" bind:close="closeEditAmount">
   <view class="modal-header">编辑季度金额</view>
   <view class="modal-body">
-    <van-field label="季度1" value="{{ editS1 }}" type="digit" confirm-type="next" placeholder="请输入金额" bind:change="onS1Change">
+    <van-field label="季度1" value="{{ editS1 }}" type="digit" confirm-type="done" placeholder="请输入金额" bind:change="onS1Change">
       <view slot="button" class="field-unit">万</view>
     </van-field>
-    <van-field label="季度2" value="{{ editS2 }}" type="digit" confirm-type="next" placeholder="请输入金额" bind:change="onS2Change">
+    <van-field label="季度2" value="{{ editS2 }}" type="digit" confirm-type="done" placeholder="请输入金额" bind:change="onS2Change">
       <view slot="button" class="field-unit">万</view>
     </van-field>
-    <van-field label="季度3" value="{{ editS3 }}" type="digit" confirm-type="next" placeholder="请输入金额" bind:change="onS3Change">
+    <van-field label="季度3" value="{{ editS3 }}" type="digit" confirm-type="done" placeholder="请输入金额" bind:change="onS3Change">
       <view slot="button" class="field-unit">万</view>
     </van-field>
     <van-field label="季度4" value="{{ editS4 }}" type="digit" confirm-type="done" placeholder="请输入金额" bind:change="onS4Change">
@@ -232,4 +209,4 @@
       return color;
     }
   }
-</wxs>
+</wxs>

+ 444 - 0
CRM/wmycontract/confirm.js

@@ -0,0 +1,444 @@
+const _Http = getApp().globalData.http;
+const __env = wx.getAccountInfoSync().miniProgram.envVersion;
+const ESIGN_APPID = __env === 'release' ? 'wxa023b292fd19d41d' : 'wx371151823f6f3edf';
+
+Page({
+  data: {
+    mxid: '',
+    loading: false,
+    submitting: false,
+    showAll: false,
+    disabled: true,
+    formHasEmpty: true,
+    licenseHasFiles: false,
+    idcardHasFiles: false,
+    licenseAttachmentids: [],
+    idcardAttachmentids: [],
+    signurl: '',
+    flowid: '',
+    form: [{
+      label: "营业执照名称",
+      error: false,
+      errMsg: "",
+      type: "text",
+      value: "",
+      placeholder: "请输入营业执照名称",
+      valueName: "license_name",
+      required: true,
+      checking: "base"
+    }, {
+      label: "纳税人识别号",
+      error: false,
+      errMsg: "",
+      type: "text",
+      value: "",
+      placeholder: "请输入纳税人识别号",
+      valueName: "taxno",
+      required: true,
+      checking: "base"
+    }, {
+      label: "营业执照地址",
+      error: false,
+      errMsg: "",
+      type: "text",
+      value: "",
+      placeholder: "请输入营业执照地址",
+      valueName: "license_address",
+      required: true,
+      checking: "base"
+    }, {
+      label: "签署人",
+      error: false,
+      errMsg: "",
+      type: "text",
+      value: "",
+      placeholder: "请输入签署人姓名",
+      valueName: "legal_rep",
+      required: true,
+      checking: "base"
+    }, {
+      label: "电话",
+      error: false,
+      errMsg: "",
+      type: "text",
+      value: "",
+      placeholder: "请输入电话",
+      valueName: "mobile",
+      required: true,
+      checking: "base"
+    }, {
+      label: "签署人手机号码",
+      error: false,
+      errMsg: "",
+      type: "number",
+      value: "",
+      placeholder: "请输入签署人手机号码",
+      valueName: "phonenumber",
+      required: true
+    }, {
+      label: "售前电话",
+      error: false,
+      errMsg: "",
+      type: "text",
+      value: "",
+      placeholder: "请输入售前电话",
+      valueName: "presalesphonenumber",
+      required: true,
+      checking: "base"
+    }, {
+      label: "售后电话",
+      error: false,
+      errMsg: "",
+      type: "text",
+      value: "",
+      placeholder: "请输入售后电话",
+      valueName: "aftersalesphonenumber",
+      required: true,
+      checking: "base"
+    }, {
+      label: "备案汇款人",
+      error: false,
+      errMsg: "",
+      type: "text",
+      value: "",
+      placeholder: "请输入备案汇款人(多人用逗号分隔)",
+      valueName: "paymans",
+      required: true,
+      checking: "base"
+    }, {
+      label: "签署人身份证号码",
+      error: false,
+      errMsg: "",
+      type: "text",
+      value: "",
+      placeholder: "请输入身份证号码",
+      valueName: "idcard",
+      required: true,
+      checking: "base"
+    }]
+  },
+
+  onLoad(options) {
+    if (options.id) {
+      this.setData({
+        mxid: options.id
+      });
+      this.loadDetail(options.id);
+    }
+  },
+
+  /* 加载任务明细详情(系统取值) */
+  loadDetail(mxid) {
+    this.setData({
+      loading: true
+    });
+    _Http.basic({
+      id: 2026041516335302,
+      content: {
+        sa_esign_contract_taskmxid: mxid
+      }
+    }).then(res => {
+      this.setData({
+        loading: false
+      });
+      console.log("合同明细详情", res);
+      if (res.msg !== '成功') return wx.showToast({
+        title: res.msg || '加载失败',
+        icon: 'none'
+      });
+      const data = res.data || {};
+      // 回填表单
+      const form = this.data.form.map(item => {
+        if (data[item.valueName] !== undefined && data[item.valueName] !== null) {
+          item.value = String(data[item.valueName]);
+        }
+        return item;
+      });
+      this.setData({
+        form
+      });
+
+      // 初始化附件 - 按 usetype 分类
+      const attinfos = data.attinfos || [];
+      const licenseFiles = attinfos.filter(v => v.usetype === 'bl');
+      const idcardFiles = attinfos.filter(v => v.usetype === 'idcard');
+      const licenseAttachmentids = licenseFiles.map(v => v.attachmentid);
+      const idcardAttachmentids = idcardFiles.map(v => v.attachmentid);
+      this.setData({
+        licenseAttachmentids,
+        idcardAttachmentids,
+        signurl: data.signurl || '',
+        flowid: data.flowid || ''
+      });
+
+      // 渲染已有附件到 Yl_Files
+      if (licenseFiles.length > 0) {
+        this.selectComponent("#Yl_license_files").handleFiles(licenseFiles, true);
+      }
+      if (idcardFiles.length > 0) {
+        this.selectComponent("#Yl_idcard_files").handleFiles(idcardFiles, true);
+      }
+      // 主动触发 Yl_Field 校验,更新 formComplete 状态
+      const formComp = this.selectComponent("#Form");
+      if (formComp && formComp.confirm) {
+        formComp.confirm();
+      }
+      this.refreshDisabled();
+    }).catch(() => {
+      this.setData({
+        loading: false
+      });
+      wx.showToast({
+        title: '加载失败',
+        icon: 'none'
+      });
+    });
+  },
+
+  /* 表单必填项是否完成 - detail=true 表示还有必填项未完成 */
+  onConfirm({
+    detail
+  }) {
+    this.setData({
+      formHasEmpty: detail
+    });
+    this.refreshDisabled();
+  },
+
+  /* 中断 */
+  interrupt({
+    detail
+  }) {},
+
+  /* 切换显示 */
+  onChange(e) {
+    this.setData({
+      showAll: e.detail
+    });
+  },
+
+  /* 刷新按钮禁用状态 */
+  refreshDisabled() {
+    // 已确认(有flowid)不禁用
+    if (this.data.flowid) {
+      this.setData({
+        disabled: false
+      });
+      return;
+    }
+    const licenseFiles = this.selectComponent("#Yl_license_files");
+    const idcardFiles = this.selectComponent("#Yl_idcard_files");
+    const licenseHasFiles = licenseFiles ? licenseFiles.getFiles().attachmentids.length > 0 : this.data.licenseAttachmentids.length > 0;
+    const idcardHasFiles = idcardFiles ? idcardFiles.getFiles().attachmentids.length > 0 : this.data.idcardAttachmentids.length > 0;
+    this.setData({
+      licenseHasFiles,
+      idcardHasFiles
+    });
+    // formHasEmpty=true 表示还有必填项未填
+    this.setData({
+      disabled: this.data.formHasEmpty || !licenseHasFiles || !idcardHasFiles
+    });
+  },
+
+  /* 上传状态变化 */
+  changeState({
+    detail
+  }) {
+    this.setData({
+      loading: detail
+    });
+  },
+
+  /* 查询文件详情并传给指定 Yl_Files */
+  fetchAndPushFiles(detail, filesId) {
+    Promise.all(detail.map(attachmentid => {
+      return _Http.basic({
+        "id": 2024061710590401,
+        "content": {
+          "pageSize": 1,
+          "pageNumber": 1,
+          "attachmentid": attachmentid
+        }
+      })
+    })).then(res => {
+      this.selectComponent(filesId).handleFiles(res.map(v => v.data[0]));
+      this.refreshDisabled();
+    });
+  },
+
+  /* 营业执照上传回调 */
+  handleLicenseUpload({
+    detail
+  }) {
+    this.setData({
+      licenseAttachmentids: [...this.data.licenseAttachmentids, ...detail]
+    });
+    this.fetchAndPushFiles(detail, "#Yl_license_files");
+    this.createFileLink(detail, 'bl');
+  },
+
+  /* 身份证上传回调 */
+  handleIdcardUpload({
+    detail
+  }) {
+    this.setData({
+      idcardAttachmentids: [...this.data.idcardAttachmentids, ...detail]
+    });
+    this.fetchAndPushFiles(detail, "#Yl_idcard_files");
+    this.createFileLink(detail, 'idcard');
+  },
+
+  /* 文件删除回调 */
+  onFileDelete() {
+    this.refreshDisabled();
+  },
+
+  /* 提交确认 */
+  onSubmit() {
+    if (this.data.disabled || this.data.submitting) return;
+    // 通过 Yl_Field 组件校验并获取表单数据
+    let formData = this.selectComponent("#Form").submit();
+    if (!formData) return;
+
+    // 检查附件(兼容从组件获取或从 data 获取)
+    const licenseFiles = this.selectComponent("#Yl_license_files");
+    const idcardFiles = this.selectComponent("#Yl_idcard_files");
+    const licenseIds = licenseFiles ? licenseFiles.getFiles().attachmentids : this.data.licenseAttachmentids;
+    const idcardIds = idcardFiles ? idcardFiles.getFiles().attachmentids : this.data.idcardAttachmentids;
+    if (licenseIds.length === 0) {
+      wx.showToast({
+        title: '请上传营业执照复印件',
+        icon: 'none'
+      });
+      return;
+    }
+    if (idcardIds.length === 0) {
+      wx.showToast({
+        title: '请上传身份证复印件',
+        icon: 'none'
+      });
+      return;
+    }
+
+    // 二次弹窗确认
+    wx.showModal({
+      title: '确认',
+      content: '合同基础信息确认后不可修改,是否开始签署?',
+      confirmText: '开始签署',
+      confirmColor: '#3874F6',
+      success: (res) => {
+        if (res.confirm) {
+          this.doConfirm(formData);
+        }
+      }
+    });
+  },
+
+  /* 执行确认 - 确认任务明细后跳转电子签小程序 */
+  doConfirm(formData) {
+    wx.showLoading({
+      title: '提交中...',
+      mask: true
+    });
+    this.setData({
+      submitting: true
+    });
+
+    _Http.basic({
+      id: 2026041315410702,
+      content: {
+        sa_esign_contract_taskmxid: this.data.mxid,
+        ...formData
+      }
+    }).then(res => {
+      wx.hideLoading();
+      this.setData({
+        submitting: false
+      });
+      console.log("确认任务明细", res);
+      if (res.msg !== '成功') {
+        return wx.showToast({
+          title: res.msg || '确认失败',
+          icon: 'none'
+        });
+      }
+      const data = res.data || {};
+      const signurl = data.signurl || '';
+      const flowid = data.flowid || '';
+      if (!flowid) {
+        return wx.showToast({
+          title: '签署流程未生成',
+          icon: 'none'
+        });
+      }
+      this.setData({
+        signurl,
+        flowid,
+        disabled: false
+      });
+      // 询问用户是否跳转电子签(navigateToMiniProgram 必须由用户点击触发)
+      wx.showModal({
+        title: '确认成功',
+        content: '合同确认成功,是否立即前往签署?',
+        confirmText: '去签署',
+        cancelText: '返回列表',
+        confirmColor: '#3874F6',
+        success: (modalRes) => {
+          if (modalRes.confirm) {
+            this.goToSign();
+          } else {
+            wx.navigateBack();
+          }
+        }
+      });
+    }).catch(() => {
+      wx.hideLoading();
+      this.setData({
+        submitting: false
+      });
+      wx.showToast({
+        title: '确认失败',
+        icon: 'none'
+      });
+    });
+  },
+
+  /* 创建文件关联 */
+  createFileLink(attachmentids, usetype) {
+    return _Http.basic({
+      "classname": "system.attachment.Attachment",
+      "method": "createFileLink",
+      id: 10020501,
+      "content": {
+        ownertable: "sa_esign_contract_taskmx",
+        ownerid: this.data.mxid,
+        usetype,
+        attachmentids
+      }
+    }).then(res => {
+      console.log('绑定附件', usetype, res);
+    }).catch(err => {
+      console.error('绑定附件失败', usetype, err);
+    });
+  },
+
+  /* 跳转腾讯电子签小程序签署 */
+  goToSign() {
+    const flowid = this.data.flowid;
+    if (!flowid) {
+      return wx.showToast({
+        title: '签署流程未生成',
+        icon: 'none'
+      });
+    }
+    
+    wx.navigateToMiniProgram({
+      appId: ESIGN_APPID,
+      path: `pages/guide?from=app&to=CONTRACT_DETAIL&id=${flowid}`,
+      envVersion: 'release',
+      complete() {
+        wx.navigateBack()
+      }
+    });
+  }
+});

+ 10 - 0
CRM/wmycontract/confirm.json

@@ -0,0 +1,10 @@
+{
+  "usingComponents": {
+    "Yl_Field": "../../components/Yl_Field/index",
+    "Yl_Headline": "../../components/Yl_Headline/index",
+    "van-button": "@vant/weapp/button/index",
+    "Yl_Upload": "../../components/Yl_Upload/index",
+    "Yl_Files": "../../components/Yl_Files/index"
+  },
+  "navigationBarTitleText": "合同内容确认"
+}

+ 110 - 0
CRM/wmycontract/confirm.scss

@@ -0,0 +1,110 @@
+page {
+  background-color: #f5f5f5;
+  overflow-x: hidden;
+}
+
+.container {
+  padding: 20rpx;
+  min-height: 100vh;
+  box-sizing: border-box;
+  overflow-x: hidden;
+  max-width: 100vw;
+}
+
+.form-card {
+  background-color: #fff;
+  border-radius: 16rpx;
+  padding: 24rpx;
+  margin-top: 20rpx;
+  box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
+}
+
+.section-title {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #1a1a1a;
+  margin-bottom: 20rpx;
+  padding-left: 20rpx;
+  position: relative;
+
+  &::before {
+    content: '';
+    position: absolute;
+    left: 0;
+    top: 50%;
+    transform: translateY(-50%);
+    width: 6rpx;
+    height: 28rpx;
+    background: #3874F6;
+    border-radius: 3rpx;
+  }
+}
+
+/* 照片墙布局 */
+.photo-wall {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 18rpx;
+}
+
+.photo-add {
+  width: 158rpx;
+  height: 158rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  border: 2rpx dashed #d9d9d9;
+  border-radius: 8rpx;
+  background: #fafafa;
+  flex-shrink: 0;
+}
+
+/* 照片墙内 Yl_Files 内联显示 */
+.inline-files {
+  .media {
+    margin-top: 0;
+    padding: 0;
+    gap: 0;
+
+    .item {
+      margin-right: 18rpx;
+      margin-bottom: 18rpx;
+    }
+  }
+
+  Yl_Empty {
+    display: none !important;
+  }
+}
+
+/* 底部提交按钮 */
+.new-footer {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  padding: 10rpx 30rpx 20rpx;
+  background-color: #fff;
+  box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.1);
+  display: flex;
+  justify-content: center;
+  z-index: 999;
+  box-sizing: border-box;
+  overflow: hidden;
+}
+
+.new-submit {
+  width: 100% !important;
+  height: 88rpx !important;
+  line-height: 88rpx !important;
+  border-radius: 44rpx !important;
+  font-size: 32rpx !important;
+  background: linear-gradient(135deg, #3874F6, #5b9bff) !important;
+  border: none !important;
+  color: #fff !important;
+}
+
+.new-submit.van-button--disabled {
+  opacity: 0.5 !important;
+  background: #c0c4cc !important;
+}

+ 42 - 0
CRM/wmycontract/confirm.wxml

@@ -0,0 +1,42 @@
+<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 class="form-card">
+    <view class="section-title">营业执照</view>
+    <view class="photo-wall">
+      <Yl_Files delete hideEmpty id="Yl_license_files" bind:deleteCallBack="onFileDelete" class="inline-files" />
+      <view style="padding-top: 20rpx;">
+        <Yl_Upload accept="image" bind:uploadCallback="handleLicenseUpload" bind:changeState="changeState">
+          <view class="photo-add">
+            <van-icon name="plus" size="24px" color="#999" />
+          </view>
+        </Yl_Upload>
+      </view>
+    </view>
+  </view>
+
+  <!-- 身份证复印件 -->
+  <view class="form-card">
+    <view class="section-title">身份证</view>
+    <view class="photo-wall">
+      <Yl_Files delete hideEmpty id="Yl_idcard_files" bind:deleteCallBack="onFileDelete" class="inline-files" />
+      <view style="padding-top: 20rpx;">
+        <Yl_Upload accept="image" bind:uploadCallback="handleIdcardUpload" bind:changeState="changeState">
+          <view class="photo-add">
+            <van-icon name="plus" size="24px" color="#999" />
+          </view>
+        </Yl_Upload>
+      </view>
+    </view>
+  </view>
+
+  <!-- 底部按钮 -->
+  <view style="height: 150rpx;" />
+  <view class="new-footer">
+    <van-button wx:if="{{flowid}}" custom-class='new-submit' type="primary" bindtap='goToSign'>去签署</van-button>
+    <van-button wx:else custom-class='new-submit' disabled='{{disabled || submitting}}' loading='{{submitting}}' bindtap='onSubmit'>确认并开始签署</van-button>
+  </view>
+</view>

+ 47 - 0
CRM/wmycontract/detail.js

@@ -0,0 +1,47 @@
+const _Http = getApp().globalData.http;
+
+Page({
+  data: {
+    mxid: '',
+    detail: {},
+    files: {
+      images: [],
+      viewImages: [],
+      videos: [],
+      viewVideos: [],
+      files: []
+    },
+    loading: true
+  },
+
+  onLoad(options) {
+    if (options.id) {
+      this.setData({ mxid: options.id });
+      this.loadDetail(options.id);
+    }
+  },
+
+  /* 加载合同详情 */
+  loadDetail(mxid) {
+    this.setData({ loading: true });
+    _Http.basic({
+      id: 2026041516335302,
+      content: {
+        sa_esign_contract_taskmxid: mxid
+      }
+    }).then(res => {
+      this.setData({ loading: false });
+      console.log("合同详情", res);
+      if (res.msg !== '成功') return wx.showToast({ title: res.msg || '加载失败', icon: 'none' });
+      const data = res.data || {};
+      this.setData({ detail: data });
+      // 处理附件
+      if (data.attinfos && data.attinfos.length > 0) {
+        this.selectComponent('#Files').handleFiles(data.attinfos, true);
+      }
+    }).catch(() => {
+      this.setData({ loading: false });
+      wx.showToast({ title: '加载失败', icon: 'none' });
+    });
+  }
+});

+ 6 - 0
CRM/wmycontract/detail.json

@@ -0,0 +1,6 @@
+{
+  "usingComponents": {
+    "Yl_Files": "/components/Yl_Files/index"
+  },
+  "navigationBarTitleText": "合同详情"
+}

+ 99 - 0
CRM/wmycontract/detail.scss

@@ -0,0 +1,99 @@
+page {
+  background-color: #f5f6fa;
+}
+
+.container {
+  padding: 20rpx;
+  min-height: 100vh;
+}
+
+/* 顶部信息卡 */
+.info-card {
+  background: linear-gradient(135deg, #3874F6 0%, #5B9BFF 100%);
+  border-radius: 20rpx;
+  padding: 32rpx;
+  color: #fff;
+  margin-bottom: 20rpx;
+  box-shadow: 0 8rpx 24rpx rgba(56, 116, 246, 0.3);
+}
+
+.info-top {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 28rpx;
+  padding-bottom: 24rpx;
+  border-bottom: 1rpx solid rgba(255, 255, 255, 0.2);
+}
+
+.contract-name {
+  font-size: 36rpx;
+  font-weight: bold;
+  flex: 1;
+  margin-right: 16rpx;
+}
+
+.status-tag {
+  font-size: 24rpx;
+  padding: 10rpx 28rpx;
+  border-radius: 24rpx;
+  font-weight: 600;
+  white-space: nowrap;
+  box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
+}
+
+.info-grid {
+  display: grid;
+  grid-template-columns: repeat(2, 1fr);
+  gap: 20rpx 32rpx;
+}
+
+.info-cell {
+  .info-label {
+    font-size: 22rpx;
+    color: rgba(255, 255, 255, 0.7);
+    margin-bottom: 6rpx;
+  }
+
+  .info-value {
+    font-size: 26rpx;
+    font-weight: 500;
+  }
+}
+
+/* 区块卡片 */
+.section-card {
+  background-color: #fff;
+  border-radius: 20rpx;
+  padding: 28rpx;
+  box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.05);
+}
+
+.section-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 24rpx;
+  padding-bottom: 20rpx;
+  border-bottom: 1rpx solid #f0f0f0;
+}
+
+.section-title {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #1a1a1a;
+  position: relative;
+  padding-left: 20rpx;
+
+  &::before {
+    content: '';
+    position: absolute;
+    left: 0;
+    top: 50%;
+    transform: translateY(-50%);
+    width: 6rpx;
+    height: 28rpx;
+    background: #3874F6;
+    border-radius: 3rpx;
+  }
+}

+ 52 - 0
CRM/wmycontract/detail.wxml

@@ -0,0 +1,52 @@
+<view class="container">
+  <!-- 合同信息卡 -->
+  <view class="info-card">
+    <view class="info-top">
+      <view class="contract-name">{{detail.name || '--'}}</view>
+      <view class="status-tag" style="background: {{set.color(detail.status)}}; color: #fff;">
+        {{detail.status || '--'}}
+      </view>
+    </view>
+    <view class="info-grid">
+      <view class="info-cell">
+        <view class="info-label">年度</view>
+        <view class="info-value">{{detail.year || '--'}}</view>
+      </view>
+      <view class="info-cell">
+        <view class="info-label">合同类型</view>
+        <view class="info-value">{{detail.type || '--'}}</view>
+      </view>
+      <view class="info-cell">
+        <view class="info-label">归档日期</view>
+        <view class="info-value">{{detail.archivedate || detail.enddate || '--'}}</view>
+      </view>
+    </view>
+  </view>
+
+  <!-- 附件区域 -->
+  <view class="section-card">
+    <view class="section-header">
+      <view class="section-title">合同附件</view>
+    </view>
+    <Yl_Files id="Files" files="{{files}}" />
+    <Yl_Empty wx:if="{{!files.images.length && !files.videos.length && !files.files.length}}" text="暂无附件" marTop="80rpx" />
+  </view>
+</view>
+
+<wxs module="set">
+  module.exports = {
+    color: function (statu) {
+      var color = '#999999';
+      switch (statu) {
+        case "已签署":
+        case "已完成":
+          color = '#67C23A';
+          break;
+        case "已拒签":
+          color = '#F56C6C';
+          break;
+      };
+      return color;
+    }
+  }
+</wxs>

+ 116 - 0
CRM/wmycontract/index.js

@@ -0,0 +1,116 @@
+const _Http = getApp().globalData.http;
+const __env = wx.getAccountInfoSync().miniProgram.envVersion;
+const ESIGN_APPID = __env === 'release' ? 'wxa023b292fd19d41d' : 'wx371151823f6f3edf';
+
+Page({
+  data: {
+    "id": "2026041315425102",
+    list: [],
+    content: {
+      nocache: true,
+      pageNumber: 1,
+      pageSize: 20,
+      pageTotal: 1,
+      where: {
+        condition: ""
+      }
+    }
+  },
+  onLoad() {
+    this.getList();
+  },
+  onShow() {
+    if (this.data._loaded) {
+      this.getList(true);
+    }
+    this.setData({
+      _loaded: true
+    });
+  },
+  /* 获取合同列表 */
+  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 || 1)) return;
+    this.setListHeight();
+
+    _Http.basic({
+      id: this.data.id,
+      content: this.data.content
+    }).then(res => {
+      console.log("我的合同列表", res);
+      this.selectComponent('#ListBox').RefreshToComplete();
+      const list = (res.data || []).map(item => ({
+        ...item,
+        _status: (item.status || '').trim() || this.inferStatus(item)
+      }));
+      this.setData({
+        'content.pageNumber': res.pageNumber + 1,
+        'content.pageTotal': res.pageTotal,
+        list: res.pageNumber == 1 ? list : this.data.list.concat(list),
+        total: res.total
+      });
+    }).catch(err => {
+      console.error("获取合同列表失败", err);
+      this.selectComponent('#ListBox').RefreshToComplete();
+      wx.showToast({
+        title: '加载失败',
+        icon: 'none'
+      });
+    });
+  },
+  /* 设置页面高度 */
+  setListHeight() {
+    this.selectComponent("#ListBox").setHeight(".header", this);
+  },
+  /* 推断状态(后端 status 为空时的兜底) */
+  inferStatus(item) {
+    if (!item.flowid) return '待开始';
+    if (item.flowid && !item.downloadurl) return '待签署';
+    if (item.downloadurl) return '已签署';
+    return '';
+  },
+  /* 搜索 */
+  onSearch({
+    detail
+  }) {
+    this.setData({
+      'content.where.condition': detail
+    });
+    this.getList(true);
+  },
+
+  /* 点击列表项 - 根据状态跳转不同页面 */
+  onItemClick(e) {
+    const item = e.currentTarget.dataset.item;
+    const status = item._status;
+    if (status === '待开始') {
+      wx.navigateTo({
+        url: `/CRM/wmycontract/confirm?id=${item.sa_esign_contract_taskmxid}`
+      });
+    } else {
+      if (item.flowid) {
+        wx.navigateToMiniProgram({
+          appId: ESIGN_APPID,
+          path: `pages/guide?from=app&to=CONTRACT_DETAIL&id=${item.flowid}`,
+          envVersion: 'release',
+          success(res) {
+            console.log('跳转电子签成功', res);
+          },
+          fail(err) {
+            console.error('跳转电子签失败', err);
+          }
+        });
+      } else {
+        wx.showToast({
+          title: '签署链接暂未生成',
+          icon: 'none'
+        });
+      }
+    }
+  }
+});

+ 4 - 0
CRM/wmycontract/index.json

@@ -0,0 +1,4 @@
+{
+  "usingComponents": {},
+  "navigationBarTitleText": "我的合同"
+}

+ 60 - 0
CRM/wmycontract/index.scss

@@ -0,0 +1,60 @@
+.container {
+  background-color: #f5f5f5;
+  min-height: 100vh;
+
+  .item {
+    background-color: #fff;
+    border-radius: 16rpx;
+    padding: 24rpx;
+    margin-bottom: 20rpx;
+    display: block;
+    box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
+
+    .top {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: 16rpx;
+
+      .name {
+        font-size: 34rpx;
+        font-weight: 600;
+        color: #1a1a1a;
+        flex: 1;
+      }
+
+      .statu {
+        font-size: 24rpx;
+        padding: 6rpx 20rpx;
+        border-radius: 8rpx;
+        font-weight: 500;
+        background-color: #fff;
+        border: 2rpx solid;
+        flex-shrink: 0;
+      }
+    }
+
+    .content {
+      .row {
+        display: flex;
+        flex-wrap: wrap;
+        margin-bottom: 12rpx;
+
+        .exp {
+          flex: 1;
+          font-size: 26rpx;
+          color: #666;
+          line-height: 1.5;
+
+          &.full-width {
+            width: 100%;
+          }
+        }
+      }
+    }
+  }
+
+  .header {
+    background-color: #f5f5f5;
+  }
+}

+ 61 - 0
CRM/wmycontract/index.wxml

@@ -0,0 +1,61 @@
+<view class="container">
+  <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 class="item" wx:for="{{list}}" wx:key="sa_esign_contract_taskmxid" bindtap="onItemClick" data-item="{{item}}">
+      <view class="top">
+        <view class="name">{{item.taskname || '--'}}</view>
+        <view class="statu" style="border-color: {{set.color(item._status)}}; color: {{set.color(item._status)}};">
+          {{item._status || '--'}}
+        </view>
+      </view>
+      <view class="content">
+        <view class="row">
+          <view class="exp">合同模版:{{item.name || '--'}}</view>
+        </view>
+        <view class="row">
+          <view class="exp">年度:{{item.year || '--'}}</view>
+          <view class="exp">合同类型:{{item.type || '--'}}</view>
+        </view>
+        <view class="row">
+          <view class="exp">发布时间:{{item.senddate || '--'}}</view>
+        </view>
+        <view class="row">
+          <view class="exp full-width">备注:{{item.remarks || '--'}}</view>
+        </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 = '#909399';
+          break;
+        case "合同创建":
+          color = '#3874F6';
+          break;
+        case "待签署":
+          color = '#E6A23C';
+          break;
+        case "部分签署":
+          color = '#409EFF';
+          break;
+        case "合同签署完成":
+        case "已签署":
+        case "已完成":
+          color = '#67C23A';
+          break;
+        case "已拒签":
+          color = '#F56C6C';
+          break;
+      };
+      return color;
+    }
+  }
+</wxs>

+ 3 - 0
app.json

@@ -126,6 +126,9 @@
         "contract/index",
         "contract/create",
         "contract/detail",
+        "wmycontract/index",
+        "wmycontract/confirm",
+        "wmycontract/detail",
         "customer/modules/order/details",
         "customer/modules/followRecord/create",
         "lead/modules/followRecord/create",

+ 31 - 25
components/Yl_Files/index.js

@@ -17,6 +17,10 @@ Component({
         delete: {
             type: Boolean
         },
+        hideEmpty: {
+            type: Boolean,
+            value: false
+        },
         deleteCallBack: {
             type: Function
         }
@@ -136,34 +140,36 @@ Component({
         },
         /* 处理附件 */
         handleFiles(arr, init = false) {
-            let files = init ? {
-                    images: [],
-                    viewImages: [],
-                    videos: [],
-                    viewVideos: [],
-                    files: []
-                } : this.data.files,
+            let oldFiles = this.data.files,
                 list = fileList(arr);
 
+            let files = init ? {
+                images: [],
+                viewImages: [],
+                videos: [],
+                viewVideos: [],
+                files: []
+            } : {
+                images: [...oldFiles.images],
+                viewImages: [...oldFiles.viewImages],
+                videos: [...oldFiles.videos],
+                viewVideos: [...oldFiles.viewVideos],
+                files: [...oldFiles.files]
+            };
+
             // 去重处理,确保每个文件只添加一次
-            const uniqueList = [];
-            const seenUrls = new Set();
+            const seenUrls = new Set(files.images.map(v => v.url).concat(files.videos.map(v => v.url)).concat(files.files.map(v => v.url)));
 
             list.forEach(v => {
-                if (!seenUrls.has(v.url)) {
-                    seenUrls.add(v.url);
-                    uniqueList.push(v);
-                }
-            });
-
-            uniqueList.forEach(v => {
+                if (seenUrls.has(v.url)) return;
+                seenUrls.add(v.url);
                 switch (v.fileType) {
                     case "video":
                         files.videos.push(v)
                         files.viewVideos.push({
                             url: v.url,
                             type: "video",
-                            poster: v.subfiles[0].url
+                            poster: v.subfiles && v.subfiles[0] ? v.subfiles[0].url : ''
                         })
                         break;
                     case "image":
@@ -178,14 +184,14 @@ Component({
                         break;
                 }
             });
-            this.setData({
-                files
-            })
-            setTimeout(() => {
-                this.setData({
-                    attachmentids: this.getFiles().attachmentids
-                })
-            });
+
+            // 计算 attachmentids
+            const attachmentids = [];
+            files.files.forEach(v => attachmentids.push(v.attachmentid));
+            files.images.forEach(v => attachmentids.push(v.attachmentid));
+            files.videos.forEach(v => attachmentids.push(v.attachmentid));
+
+            this.setData({ files, attachmentids });
         },
         /* 返回数据ID数组 用来换绑数据 */
         getFiles() {

+ 2 - 3
components/Yl_Files/index.wxml

@@ -1,4 +1,4 @@
-<view class="media">
+<view class="media" wx:if="{{attachmentids.length > 0}}">
     <!-- 图片 -->
     <navigator url="#" class="item" wx:for="{{files.images}}" wx:key="attachmentid">
         <image src="{{item.url}}" data-index="{{index}}" data-type='image' mode="aspectFill" bindtap="viewMedias" />
@@ -21,5 +21,4 @@
         <image wx:if="{{delete}}" class="delete" src="/static/image/delete.png" data-item="{{item}}" catchtap="handleDeleteFile" />
     </navigator>
 </view>
-<!--  list="{{files.files}}" -->
-<Yl_Empty wx:if="{{attachmentids.length == 0}}" />
+<Yl_Empty wx:elif="{{!hideEmpty && attachmentids.length == 0}}" />

+ 1 - 1
components/Yl_ListBox/index.wxml

@@ -1,5 +1,5 @@
 <view id="mylisttop" />
-<scroll-view class="scroll-view" scroll-y refresher-enabled='{{pullDown}}' refresher-triggered='{{inRefresh}}' style="height: {{height}}px;" triggered='{{true}}' bindrefresherrefresh='pullToRefresh' lower-threshold='300' bindscrolltolower='loadThePage'>
+<scroll-view class="scroll-view" scroll-y refresher-enabled='{{pullDown}}' refresher-triggered='{{inRefresh}}' style="height: {{height}}px;" bindrefresherrefresh='pullToRefresh' lower-threshold='300' bindscrolltolower='loadThePage'>
     <slot />
     <view class="safety" wx:if="{{safety}}" />
 </scroll-view>

+ 6 - 1
pages/index/index.js

@@ -240,7 +240,12 @@ Page({
 					name: "合同任务",
 					key: "wcrmsign",
 					path: "/CRM/contract/index",
-					icon: "work-hehuoren"
+					icon: "work-hetong"
+				}, {
+					name: "我的合同",
+					key: "wmycontract",
+					path: "/CRM/wmycontract/index",
+					icon: "work-hetong"
 				}];
 
 				let crm = getApp().globalData.queryPer.query(wx.getStorageSync('userauth'), ["CRM"], ["crm"]),