瀏覽代碼

Merge remote-tracking branch 'origin/develop' into develop

yanglingling 1 年之前
父節點
當前提交
a40a2feafc
共有 49 個文件被更改,包括 3081 次插入77 次删除
  1. 23 0
      .env.production
  2. 16 0
      src/api/contract/index.js
  3. 8 0
      src/api/customer/follow.js
  4. 8 0
      src/api/proj/business.js
  5. 12 8
      src/components/postComments/index.vue
  6. 390 0
      src/components/select/SelectCtrContract.vue
  7. 9 2
      src/components/table/TableTool.vue
  8. 5 0
      src/views/base/agent/detail.vue
  9. 34 2
      src/views/base/components/Follow.vue
  10. 6 0
      src/views/base/components/FollowDetail.vue
  11. 1 1
      src/views/base/components/ProjectRecords.vue
  12. 279 0
      src/views/base/components/Visit.vue
  13. 5 1
      src/views/base/distributor/detail.vue
  14. 34 2
      src/views/base/partners/detail.vue
  15. 2 2
      src/views/base/partners/index.vue
  16. 8 8
      src/views/base/productAuth/index.vue
  17. 2 2
      src/views/contract/components/ApplyContract.vue
  18. 2 2
      src/views/contract/components/DetailsEnclosure.vue
  19. 47 1
      src/views/contract/components/DetailsRecords.vue
  20. 205 0
      src/views/contract/components/DetailsRenew.vue
  21. 755 0
      src/views/contract/components/RenewEdit.vue
  22. 28 0
      src/views/contract/detail.vue
  23. 114 1
      src/views/contract/index.vue
  24. 6 0
      src/views/customer/components/FollowDetail.vue
  25. 36 3
      src/views/customer/detail.vue
  26. 39 4
      src/views/customer/follow.vue
  27. 34 2
      src/views/customer/inviteTenders/details.vue
  28. 7 0
      src/views/customer/list.vue
  29. 7 0
      src/views/customer/openSea.vue
  30. 59 1
      src/views/index/index.vue
  31. 2 2
      src/views/proj/business/components/BusinessAdd.vue
  32. 2 2
      src/views/proj/business/components/BusinessEdit.vue
  33. 6 4
      src/views/proj/business/components/BusinessGradation.vue
  34. 2 2
      src/views/proj/business/components/DetailsEnclosure.vue
  35. 34 2
      src/views/proj/business/components/DetailsFollow.vue
  36. 10 0
      src/views/proj/business/components/DetailsRecords.vue
  37. 36 2
      src/views/proj/business/components/FollowAdd.vue
  38. 1 1
      src/views/proj/business/components/Transfer.vue
  39. 101 0
      src/views/proj/business/components/TransferStrategic.vue
  40. 16 5
      src/views/proj/business/index.vue
  41. 673 0
      src/views/proj/business/strategic_index.vue
  42. 3 1
      src/views/system/user/index.vue
  43. 2 2
      src/views/work/deliver/components/completeProgress.vue
  44. 2 2
      src/views/work/deliver/components/deliver.vue
  45. 2 2
      src/views/work/deliver/components/inspect.vue
  46. 2 2
      src/views/work/deliver/components/pictrues.vue
  47. 2 2
      src/views/work/deliver/components/productSign.vue
  48. 2 2
      src/views/work/deliver/components/softwareComplete.vue
  49. 2 2
      src/views/work/train/sale/components/finish.vue

+ 23 - 0
.env.production

@@ -16,3 +16,26 @@ VUE_APP_PROTOCOL='https://'
 VUE_APP_UPLOAD_WEED='https://oms.dashoo.cn/dir/assign'
 # 文件一步上传
 VUE_APP_UPLOAD_FILE_WEED='https://oms.dashoo.cn/weedfs/upload'
+
+
+
+
+
+## 租户码
+#VUE_APP_TENANT=8b9ec443
+#
+## websocket地址
+#VUE_APP_WEBSOCKET_URL=ws://oms.bowei-bio.com:8080/ws
+#
+## GateWay地址
+#VUE_APP_MicroSrvProxy_API=http://oms.bowei-bio.com:8080/api/
+## 登录验证微服务名称
+#VUE_APP_AdminPath=dashoo.opms.admin-0.0.1
+## 业务接口微服务名称
+#VUE_APP_ParentPath=dashoo.opms.parent-0.0.1
+#
+## 文件上传
+#VUE_APP_PROTOCOL='http://'
+#VUE_APP_UPLOAD_WEED='http://oms.bowei-bio.com:8080/dir/assign'
+## 文件一步上传
+#VUE_APP_UPLOAD_FILE_WEED='http://oms.bowei-bio.com:8080/weedfs/upload'

+ 16 - 0
src/api/contract/index.js

@@ -54,4 +54,20 @@ export default {
   getContractShareList(query) {
     return micro_request.postRequest(basePath, 'CtrContract', 'GetContractShareList', query)
   },
+  // 合同项目运维信息导出
+  exportMaintenance(query) {
+    return micro_request.postRequest(basePath, 'CtrContract', 'CtrContractMaintenanceExport', query)
+  },
+  // 续签合同
+  reNewContract(query) {
+    return micro_request.postRequest(basePath, 'CtrContract', 'ReNew', query)
+  },
+  // 查询关联续签合同
+  renewList(query) {
+    return micro_request.postRequest(basePath, 'CtrContract', 'RenewList', query)
+  },
+  // 查询关联续签合同
+  updateReNew(query) {
+    return micro_request.postRequest(basePath, 'CtrContract', 'UpdateReNew', query)
+  },
 }

+ 8 - 0
src/api/customer/follow.js

@@ -30,8 +30,16 @@ export default {
   addComment(query) {
     return micro_request.postRequest(basePath, 'FollowUpComment', 'Create', query)
   },
+  // 回复
+  addReply(query) {
+    return micro_request.postRequest(basePath, 'FollowUpComment', 'Reply', query)
+  },
   // 获取附件列表
   getFollowupFileList(query) {
     return micro_request.postRequest(basePath, 'FollowUpFile', 'GetList', query)
   },
+  // 首页评论记录
+  getHomeCommentList(query) {
+    return micro_request.postRequest(basePath, 'FollowUpComment', 'GetLatestComments', query)
+  },
 }

+ 8 - 0
src/api/proj/business.js

@@ -6,6 +6,10 @@ export default {
   getList(query) {
     return micro_request.postRequest(basePath, 'Business', 'GetList', query)
   },
+  // 获取列表
+  getListByStrategic(query) {
+    return micro_request.postRequest(basePath, 'Business', 'GetListByStrategic', query)
+  },
   getEntityById(query) {
     return micro_request.postRequest(basePath, 'Business', 'GetEntityById', query)
   },
@@ -37,6 +41,10 @@ export default {
   sysAdminBusinessTransfer(query) {
     return micro_request.postRequest(basePath, 'Business', 'SysAdminBusinessTransfer', query)
   },
+  // 转移战略项目归属
+  strategicBusinessTransfer(query) {
+    return micro_request.postRequest(basePath, 'Business', 'StrategicBusinessTransfer', query)
+  },
   // 转为储备项目
   businessConvertToReserve(query) {
     return micro_request.postRequest(basePath, 'Business', 'ConvertToReserve', query)

+ 12 - 8
src/components/postComments/index.vue

@@ -7,13 +7,13 @@
  * @FilePath: \订单全流程管理系统\src\views\base\components\PostComments.vue
 -->
 <template>
-  <el-dialog title="发表评论" :visible.sync="visible">
+  <el-dialog :title="reply ? '回复' : '发表评论'" :visible.sync="visible">
     <el-form ref="editForm" :model="editForm" :rules="editRules">
-      <el-form-item label="评论" prop="content">
+      <el-form-item :label="reply ? '回复' : '评论'" prop="content">
         <el-input
           v-model="editForm.content"
           maxlength="500"
-          placeholder="请输入评论内容"
+          :placeholder="reply ? '请输入回复内容' : '请输入评论内容'"
           resize="none"
           :rows="5"
           show-word-limit
@@ -35,6 +35,7 @@
       return {
         loading: false,
         visible: false,
+        reply: false,
         editForm: {
           content: '',
         },
@@ -45,10 +46,11 @@
       }
     },
     created() {
-      console.log(this.visible)
-      console.log(this.editForm)
-      console.log(this.editRules)
+      // console.log(this.visible)
+      // console.log(this.editForm)
+      // console.log(this.editRules)
       console.log(this.form)
+      console.log('reply', this.reply)
     },
     methods: {
       async postContent() {
@@ -56,10 +58,12 @@
           if (valid) {
             this.loading = true
             let params = {
-              followId: '' + this.form.id,
+              followId: this.reply ? '' + this.form.followId : '' + this.form.id,
               content: this.editForm.content,
+              pid: this.reply ? this.form.id : 0,
             }
-            const [err, res] = await to(api.addComment({ ...params }))
+            const post = this.reply ? api.addReply : api.addComment
+            const [err, res] = await to(post({ ...params }))
             this.loading = false
             if (err) return
             if (res.code == 200) {

+ 390 - 0
src/components/select/SelectCtrContract.vue

@@ -0,0 +1,390 @@
+<template>
+  <el-dialog append-to-body :title="title" :visible.sync="innerVisible" @close="close">
+    <el-row v-if="add" style="margin-top: -30px">
+      <el-col :span="24">
+        <div style="float: right">
+          <el-button size="mini" type="primary" @click="handleAdd">新建项目</el-button>
+          <!--          <el-dropdown style="margin-left: 10px" trigger="click">-->
+          <!--            <el-button icon="el-icon-more" size="mini" />-->
+          <!--            <el-dropdown-menu slot="dropdown">-->
+          <!--              <el-dropdown-item icon="el-icon-upload2">导入</el-dropdown-item>-->
+          <!--              <el-dropdown-item icon="el-icon-download">导出</el-dropdown-item>-->
+          <!--            </el-dropdown-menu>-->
+          <!--          </el-dropdown>-->
+        </div>
+      </el-col>
+    </el-row>
+    <el-row>
+      <el-col :span="24">
+        <el-input
+          v-model="queryForm.contractCode"
+          clearable
+          placeholder="合同编号"
+          style="width: 30%; margin-right: 10px"
+          @keyup.enter.native="fetchData" />
+
+        <el-input
+          v-model="queryForm.contractName"
+          clearable
+          placeholder="合同名称"
+          style="width: 30%; margin-right: 10px"
+          @keyup.enter.native="fetchData" />
+        <el-button icon="el-icon-search" type="primary" @click="fetchData">查询</el-button>
+
+        <!--        <span>显示:</span>-->
+        <!--        <el-radio-group v-model="queryForm.type">-->
+        <!--          <el-radio-button label="全部客户" />-->
+        <!--          <el-radio-button label="我负责的客户" />-->
+        <!--        </el-radio-group>-->
+        <table-tool
+          :columns="columns"
+          :show-columns.sync="showColumns"
+          style="float: right"
+          table-type="selectBusinessTable" />
+      </el-col>
+    </el-row>
+    <el-table ref="businessTable" v-loading="listLoading" :data="list" @selection-change="setSelectRows">
+      <el-table-column align="center" type="selection" />
+      <el-table-column
+        v-for="(item, index) in showColumns"
+        :key="index + Math.random()"
+        align="center"
+        :label="item.label"
+        :prop="item.prop"
+        show-overflow-tooltip
+        :sortable="item.sortable"
+        :width="item.width">
+        <template #default="{ row }">
+          <span v-if="item.prop === 'contractAmount'">
+            {{ formatPrice(row.contractAmount) }}
+          </span>
+          <span v-else-if="item.prop === 'approStatus'">
+            {{ approStatusOption[row.approStatus] }}
+          </span>
+          <span v-else-if="item.prop === 'productLine'">
+            {{ productLineOptions[row.productLine] }}
+          </span>
+          <span v-else-if="item.label === '合同有效时间'">
+            {{ parseTime(row.contractStartTime, '{y}-{m}-{d}') }}~{{ parseTime(row.contractEndTime, '{y}-{m}-{d}') }}
+          </span>
+          <span v-else-if="item.label === '合同类型'">
+            {{ contractOptions[row.contractType] }}
+          </span>
+          <span v-else-if="item.label === '签订单位类型'">
+            {{
+              row.signatoryType == '10'
+                ? '终端用户'
+                : row.signatoryType == '20'
+                ? '经销商'
+                : row.signatoryType == '30'
+                ? '代理商'
+                : ''
+            }}
+          </span>
+          <span v-else-if="item.label === '合同签订单位'">
+            {{
+              row.signatoryType == '10'
+                ? row.custName
+                : row.signatoryType == '20'
+                ? row.distributorName
+                : row.signatoryType == '30'
+                ? row.distributorName
+                : ''
+            }}
+          </span>
+          <span v-else-if="item.label === '合同签订时间'">
+            {{ parseTime(row.contractSignTime, '{y}-{m}-{d}') }}
+          </span>
+          <span v-else-if="item.label === '回款金额'">
+            {{ formatPrice(row.collectedAmount) }}
+          </span>
+          <span v-else-if="item.label === '开票金额'">
+            {{ formatPrice(row.invoiceAmount) }}
+          </span>
+          <span v-else-if="item.prop === 'softwareMaintenanceLimit' || item.prop === 'hardwareMaintenanceLimit'">
+            {{ row[item.prop] + '年' }}
+          </span>
+          <span v-else>{{ row[item.prop] }}</span>
+        </template>
+      </el-table-column>
+    </el-table>
+    <el-pagination
+      background
+      :current-page="queryForm.pageNum"
+      :layout="layout"
+      :page-size="queryForm.pageSize"
+      :total="total"
+      @current-change="handleCurrentChange"
+      @size-change="handleSizeChange" />
+    <span slot="footer">
+      <el-button size="mini" type="primary" @click="save">保存</el-button>
+      <el-button size="mini" @click="innerVisible = false">取消</el-button>
+    </span>
+    <!-- 新建项目弹窗 -->
+    <business-edit ref="businessEdit" @fetch-data="fetchData" />
+  </el-dialog>
+</template>
+
+<script>
+  import TableTool from '@/components/table/TableTool'
+  import BusinessEdit from '@/views/proj/business/components/BusinessEdit'
+  import contractApi from '@/api/contract'
+  import api from '@/api/customer'
+
+  export default {
+    name: 'SelectCtrContract',
+    components: {
+      TableTool,
+      BusinessEdit,
+    },
+    props: {
+      title: {
+        type: String,
+        default: '选择项目',
+      },
+      add: Boolean,
+      multiple: Boolean,
+      queryParams: {
+        type: Object,
+        default() {
+          return {}
+        },
+      },
+    },
+    data() {
+      return {
+        innerVisible: false,
+        queryForm: {
+          type: '全部客户',
+          isNotMaintenanceContract: '10',
+          contractCode: '', // 合同编号
+          contractName: '', //合同名称
+          custName: '', // 客户名称  ()
+          nboName: '', //项目名称
+          approStatus: '30', //审批状态
+          productLine: '', // 产品线
+          inchargeName: '', // 负责人(销售工程师)
+          pageNum: 1,
+          pageSize: 10,
+        },
+        showColumns: [],
+        columns: [
+          {
+            label: '合同编号',
+            width: '160px',
+            prop: 'contractCode',
+            sortable: false,
+            disableCheck: false,
+          },
+          {
+            label: '合同类型',
+            width: '100px',
+            prop: 'contractType',
+            sortable: false,
+            disableCheck: false,
+          },
+          {
+            label: '所在省',
+            width: '200px',
+            prop: 'custProvince',
+            sortable: false,
+            disableCheck: false,
+          },
+          {
+            label: '所在市',
+            width: '100px',
+            prop: 'custCity',
+            sortable: false,
+            disableCheck: false,
+          },
+          {
+            label: '客户名称',
+            width: '280px',
+            prop: 'custName',
+            sortable: false,
+            disableCheck: false,
+          },
+          {
+            label: '产品线',
+            prop: 'productLine',
+            width: '140px',
+          },
+          {
+            label: '签订单位类型',
+            width: '120px',
+            prop: 'signatoryType',
+            sortable: false,
+            disableCheck: false,
+          },
+          {
+            label: '合同签订单位',
+            width: '280px',
+            prop: 'distributorName',
+            sortable: false,
+            disableCheck: false,
+          },
+          {
+            label: '合同签订时间',
+            width: '120px',
+            prop: 'contractSignTime',
+            sortable: false,
+            disableCheck: false,
+          },
+          {
+            label: '合同有效时间',
+            width: '200px',
+            prop: 'contractStartTime',
+            sortable: false,
+            disableCheck: false,
+          },
+          {
+            label: '合同金额',
+            width: '120px',
+            prop: 'contractAmount',
+            sortable: false,
+            disableCheck: false,
+          },
+          {
+            label: '回款金额',
+            width: '120px',
+            prop: 'collectedAmount',
+            sortable: false,
+            disableCheck: false,
+          },
+          {
+            label: '开票金额',
+            width: '120px',
+            prop: 'invoiceAmount',
+            sortable: false,
+            disableCheck: false,
+          },
+          {
+            label: '销售工程师',
+            width: '120px',
+            prop: 'inchargeName',
+            sortable: false,
+            disableCheck: false,
+          },
+          {
+            label: '审批状态',
+            width: '100px',
+            prop: 'approStatus',
+            sortable: false,
+            disableCheck: false,
+          },
+          {
+            label: '软件运维期限',
+            width: '120px',
+            prop: 'softwareMaintenanceLimit',
+            sortable: false,
+            disableCheck: false,
+          },
+          {
+            label: '硬件运维期限',
+            width: '120px',
+            prop: 'hardwareMaintenanceLimit',
+            sortable: false,
+            disableCheck: false,
+          },
+        ],
+        list: [],
+        listLoading: true,
+        layout: 'total, sizes, prev, pager, next, jumper',
+        total: 0,
+        selectRows: [],
+        nboTypeOptions: [],
+        nboPhaseOptions: [],
+        nboStatusOptions: [],
+        approStatusOptions: [],
+        salesModelOptions: [],
+        nboSourceOptions: [],
+        provinceOptions: [],
+        industryOptions: [], //客户行业
+        levelOptions: [], //客户级别
+        contractOptions: {}, //合同类型
+        productLineOptions: {}, //产品线
+        approStatusOption: {
+          10: '待提交审核',
+          20: '待审核',
+          30: '审核已同意',
+          40: '审核已拒绝',
+          50: '审核已撤销',
+        },
+      }
+    },
+    mounted() {
+      this.getOptions()
+    },
+    methods: {
+      getOptions() {
+        Promise.all([api.getProvinceDetail(), this.getDicts('contract_type'), this.getDicts('sys_product_line')])
+          .then(([province, contract, productLine]) => {
+            this.contractOptions = {}
+            contract.data.values.filter((i) => {
+              this.contractOptions[i.key] = i.value
+            })
+            this.productLineOptions = {}
+            productLine.data.values.filter((i) => {
+              this.productLineOptions[i.key] = i.value
+            })
+            this.lines = productLine.data.values
+            this.provinceOptions = province.data.list || []
+          })
+          .catch((err) => console.log(err))
+      },
+      open() {
+        this.innerVisible = true
+        this.fetchData()
+      },
+      close() {
+        this.selectRows = []
+        this.queryForm = this.$options.data().queryForm
+        this.$refs.businessTable.clearSelection()
+      },
+      save() {
+        this.innerVisible = false
+        this.$emit('save', this.selectRows)
+      },
+      handleAdd() {
+        this.$refs.businessEdit.showEdit()
+      },
+      async fetchData() {
+        this.listLoading = true
+        let query = Object.assign(this.queryForm, this.queryParams)
+        const {
+          data: { list, total },
+        } = await contractApi.getList(query)
+        this.list = list
+        this.total = total
+        this.listLoading = false
+      },
+      setSelectRows(val) {
+        if (!this.multiple && val.length === this.list.length && val.length > 1) {
+          // 返回单条数据情况下-控制全选情况下单选第一条数据
+          if (this.selectRows.length === 1) {
+            this.$refs.businessTable.clearSelection()
+            return
+          }
+          this.$refs.businessTable.clearSelection()
+          this.$refs.businessTable.toggleRowSelection(val.shift(), true)
+        } else if (!this.multiple && val.length > 1) {
+          // 返回单条数据情况下-控制选择当前点击数据
+          this.$refs.businessTable.clearSelection()
+          this.$refs.businessTable.toggleRowSelection(val.pop(), true)
+        } else {
+          this.selectRows = val
+        }
+      },
+      handleSizeChange(val) {
+        this.queryForm.pageSize = val
+        this.fetchData()
+      },
+      handleCurrentChange(val) {
+        this.queryForm.pageNum = val
+        this.fetchData()
+      },
+    },
+  }
+</script>
+
+<style scoped></style>

+ 9 - 2
src/components/table/TableTool.vue

@@ -109,7 +109,7 @@
           const { data: id } = await tableColsConfigApi.save({
             id: this.id,
             table: this.tableType,
-            columns: JSON.stringify(newValue),
+            columns: JSON.stringify(list.map((item) => item.label)),
           })
           this.id = id
         }
@@ -128,7 +128,14 @@
         if (data) {
           this.id = data.id
           if (data.columns !== '' && data.columns !== '[]') {
-            list = this.columns.filter((item) => JSON.parse(data.columns).includes(item.label))
+            const arr = JSON.parse(data.columns)
+            for (const item of list) {
+              const idx = arr.findIndex((it) => item.label == it)
+              item.sort = idx > -1 ? idx : 0
+            }
+            list.sort((pre, cur) => {
+              return pre.sort - cur.sort
+            })
           }
         }
         this.checkColumns = list.map((item) => item.label)

+ 5 - 0
src/views/base/agent/detail.vue

@@ -131,6 +131,9 @@
           <el-tab-pane label="历史代理记录" name="historyProxy">
             <history-proxy v-if="activeName == 'historyProxy'" />
           </el-tab-pane>
+          <el-tab-pane label="协访记录" name="visit">
+            <visit v-if="activeName == 'visit'" ref="followEl" target-type="50" />
+          </el-tab-pane>
         </el-tabs>
       </div>
       <div class="info-side">
@@ -158,11 +161,13 @@
   import DetailsRecords from './components/DetailsRecords'
   import BusinessTarget from './components/BusinessTarget'
   import Follow from '../components/Follow'
+  import Visit from '../components/Visit'
   import FollowAdd from '@/views/proj/business/components/FollowAdd'
 
   export default {
     name: 'DistributorDetail',
     components: {
+      Visit,
       DetailsRecords,
       Contacts,
       ProjectRecords,

+ 34 - 2
src/views/base/components/Follow.vue

@@ -41,13 +41,30 @@
             </div>
             <transition name="height">
               <ul class="comments">
-                <li v-for="comment in item.comments" :key="comment.id">
+                <li v-for="comment in item.comments" :key="comment.id" class="flex-wrap">
                   <vab-icon class="user-avatar" icon="account-circle-fill" />
                   <div class="text">
                     <p>{{ comment.createdName }}</p>
                     <p>{{ comment.content }}</p>
-                    <p>{{ comment.createdTime }}</p>
+                    <p>
+                      {{ comment.createdTime }}
+                      <span v-if="!comment.replyComments" @click="postReply(comment)">回复</span>
+                    </p>
                   </div>
+                  <transition v-if="comment.replyComments" name="height">
+                    <ul class="comments" style="width: 100%">
+                      <li v-for="replyComments in comment.replyComments" :key="replyComments.id">
+                        <vab-icon class="user-avatar" icon="account-circle-fill" />
+                        <div class="text">
+                          <p>{{ replyComments.createdName }}</p>
+                          <p>{{ replyComments.content }}</p>
+                          <p>
+                            {{ replyComments.createdTime }}
+                          </p>
+                        </div>
+                      </li>
+                    </ul>
+                  </transition>
                 </li>
               </ul>
             </transition>
@@ -111,6 +128,14 @@
           })
           .catch(() => {})
       },
+      // 发表回复
+      postReply(row) {
+        this.$PostComment({ form: row, visible: true, reply: true })
+          .then(() => {
+            this.getFollowList()
+          })
+          .catch(() => {})
+      },
       // 展开评论
       showComment(row) {
         if (!row.comments.length) return this.$message.warning('暂无评论')
@@ -174,6 +199,7 @@
           overflow: auto;
 
           li {
+            padding-top: 10px;
             display: flex;
             border-top: 1px solid #e3e5e7;
 
@@ -196,6 +222,9 @@
                 font-size: 12px;
                 color: #9499a0;
                 text-align: right;
+                span {
+                  cursor: pointer;
+                }
               }
             }
           }
@@ -243,4 +272,7 @@
       }
     }
   }
+  .flex-wrap {
+    flex-wrap: wrap;
+  }
 </style>

+ 6 - 0
src/views/base/components/FollowDetail.vue

@@ -42,6 +42,12 @@
       <el-descriptions-item label="联系人">
         {{ form.contactsName }}
       </el-descriptions-item>
+      <el-descriptions-item label="渠道">
+        {{ form.distName }}
+      </el-descriptions-item>
+      <el-descriptions-item label="协访人员">
+        {{ form.visitorName }}
+      </el-descriptions-item>
       <el-descriptions-item label="相关附件">
         <a v-for="item in form.files" :key="item.id" :href="item.fileUrl">
           {{ item.fileName }}

+ 1 - 1
src/views/base/components/ProjectRecords.vue

@@ -139,7 +139,7 @@
             sortable: false,
           },
           {
-            label: '项目',
+            label: '战略项目',
             width: '160px',
             prop: 'isBig',
             sortable: false,

+ 279 - 0
src/views/base/components/Visit.vue

@@ -0,0 +1,279 @@
+<template>
+  <div style="height: 100%">
+    <ul v-if="followList.length" class="follow">
+      <li v-for="(date, index) in followList" :key="index">
+        <div class="date">
+          <h2>{{ date.followDay.split('-')[2] }}</h2>
+          <h3>
+            {{ date.followDay.split('-').splice(0, 2).join('.') }}
+          </h3>
+        </div>
+        <ul class="content">
+          <li v-for="(item, idx) in date.followupList" :key="idx">
+            <!-- <el-avatar class="user-avatar"
+              :src="avatar" />-->
+            <div class="text-container">
+              <vab-icon class="user-avatar" icon="account-circle-fill" />
+              <div class="text">
+                <p class="action">
+                  <span>{{ item.createdName }} 跟进({{ selectDictLabel(followTypeOptions, item.followType) }})</span>
+                  <span>
+                    <vab-icon icon="time-line" />
+                    {{ item.followDate }}
+                  </span>
+                </p>
+                <p>{{ item.followContent }}</p>
+                <p v-if="item.distName" style="white-space: pre-wrap">渠道: {{ item.distName }}</p>
+                <p v-if="item.visitorName" style="white-space: pre-wrap">协访人员:{{ item.visitorName }}</p>
+                <div class="footer">
+                  <!-- <p>
+                    来自客户:
+                    <span>{{ item.custName }}</span>
+                  </p> -->
+                  <div>
+                    <el-button icon="el-icon-edit" size="mini" @click="postComments(item)">发表评论</el-button>
+                    <el-button size="mini" @click="showDetail(item)">
+                      <vab-icon icon="arrow-right-circle-fill" />
+                      详情
+                    </el-button>
+                    <!--                    <el-button size="mini" @click="showComment(item)">评论({{ item.commentNumber }})</el-button>-->
+                  </div>
+                </div>
+              </div>
+            </div>
+            <transition name="height">
+              <ul class="comments">
+                <li v-for="comment in item.comments" :key="comment.id" class="flex-wrap">
+                  <vab-icon class="user-avatar" icon="account-circle-fill" />
+                  <div class="text">
+                    <p>{{ comment.createdName }}</p>
+                    <p>{{ comment.content }}</p>
+                    <p>
+                      {{ comment.createdTime }}
+                      <span v-if="!comment.replyComments" @click="postReply(comment)">回复</span>
+                    </p>
+                  </div>
+                  <transition v-if="comment.replyComments" name="height">
+                    <ul class="comments" style="width: 100%">
+                      <li v-for="replyComments in comment.replyComments" :key="replyComments.id">
+                        <vab-icon class="user-avatar" icon="account-circle-fill" />
+                        <div class="text">
+                          <p>{{ replyComments.createdName }}</p>
+                          <p>{{ replyComments.content }}</p>
+                          <p>
+                            {{ replyComments.createdTime }}
+                          </p>
+                        </div>
+                      </li>
+                    </ul>
+                  </transition>
+                </li>
+              </ul>
+            </transition>
+          </li>
+        </ul>
+      </li>
+    </ul>
+    <div v-else class="no-follow">暂无协访记录</div>
+    <!-- 跟进详情 -->
+    <FollowDetail ref="followDetail" />
+  </div>
+</template>
+
+<script>
+  import to from 'await-to-js'
+  import FollowDetail from './FollowDetail.vue'
+  import followApi from '@/api/customer/follow'
+  export default {
+    components: { FollowDetail },
+    props: {
+      targetType: {
+        type: String,
+        required: true,
+      },
+    },
+    data() {
+      return {
+        followList: [], //跟进记录
+        followTypeOptions: [],
+      }
+    },
+    mounted() {
+      this.getOptions()
+      this.getFollowList()
+    },
+    methods: {
+      getOptions() {
+        Promise.all([this.getDicts('plat_follow_type')]).then(([followType]) => {
+          this.followTypeOptions = followType.data.values || []
+        })
+      },
+      async getFollowList() {
+        let params = {
+          distId: this.$route.query.id.toString(),
+          // targetType: this.targetType,
+          DaysBeforeToday: 9999,
+        }
+        const [err, res] = await to(followApi.getListByDay(params))
+        if (err) return
+        this.followList = res.data.list || []
+      },
+      // 跟进记录详情
+      showDetail(row) {
+        this.$refs.followDetail.init({ ...row })
+      },
+      // 发表回复
+      postReply(row) {
+        this.$PostComment({ form: row, visible: true, reply: true })
+          .then(() => {
+            this.getFollowList()
+          })
+          .catch(() => {})
+      },
+      // 发表评论
+      postComments(row) {
+        this.$PostComment({ form: row, visible: true })
+          .then(() => {
+            this.getFollowList()
+          })
+          .catch(() => {})
+      },
+      // 展开评论
+      showComment(row) {
+        if (!row.comments.length) return this.$message.warning('暂无评论')
+        row.showComment = !row.showComment
+        this.$forceUpdate()
+      },
+    },
+  }
+</script>
+
+<style lang="scss" scoped>
+  .follow {
+    height: 100%;
+    padding: 10px 20px;
+    overflow: auto;
+
+    > li {
+      display: flex;
+
+      + li {
+        margin-top: 10px;
+      }
+    }
+
+    .date {
+      width: 100px;
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+
+      h2,
+      h3 {
+        margin: 0;
+      }
+
+      h2 {
+        font-size: 26px;
+        line-height: 32px;
+      }
+    }
+
+    .content {
+      flex: 1;
+      list-style: none;
+
+      > li {
+        border: 1px solid rgb(215, 232, 244);
+        background: rgb(247, 251, 254);
+        border-radius: 4px;
+        padding: 8px;
+        overflow: hidden;
+
+        .text-container {
+          display: flex;
+        }
+
+        .comments {
+          padding-left: 60px;
+          margin-top: 10px;
+          max-height: 200px;
+          overflow: auto;
+
+          li {
+            display: flex;
+            border-top: 1px solid #e3e5e7;
+
+            .text {
+              flex: 1;
+              padding: 0 10px;
+
+              p {
+                font-weight: 500;
+                margin: 0;
+                line-height: 32px;
+              }
+
+              p:first-child {
+                line-height: 30px;
+                font-weight: bold;
+              }
+
+              p:last-child {
+                font-size: 12px;
+                color: #9499a0;
+                text-align: right;
+                span {
+                  cursor: pointer;
+                }
+              }
+            }
+          }
+        }
+
+        + li {
+          margin-top: 10px;
+        }
+      }
+
+      .user-avatar {
+        font-size: 40px;
+      }
+
+      .text {
+        flex: 1;
+        padding-left: 20px;
+        padding-right: 10px;
+
+        p {
+          font-weight: 500;
+          margin: 0;
+          line-height: 32px;
+
+          span {
+            color: #1d66dc;
+          }
+        }
+
+        .action {
+          display: flex;
+          justify-content: space-between;
+
+          span:first-child {
+            font-weight: bold;
+            color: #333;
+          }
+        }
+
+        .footer {
+          display: flex;
+          justify-content: space-between;
+          align-items: center;
+        }
+      }
+    }
+  }
+  .flex-wrap {
+    flex-wrap: wrap;
+  }
+</style>

+ 5 - 1
src/views/base/distributor/detail.vue

@@ -102,6 +102,9 @@
           <el-tab-pane label="历史代理记录" name="historyProxy">
             <history-proxy v-if="activeName == 'historyProxy'" />
           </el-tab-pane>
+          <el-tab-pane label="协访记录" name="visit">
+            <visit v-if="activeName == 'visit'" ref="followEl" target-type="50" />
+          </el-tab-pane>
         </el-tabs>
       </div>
       <div class="info-side">
@@ -128,11 +131,12 @@
   import HistoryProxy from '../components/HistoryProxy'
   import DetailsRecords from './components/DetailsRecords'
   import Follow from '../components/Follow'
+  import Visit from '../components/Visit'
   import FollowAdd from '@/views/proj/business/components/FollowAdd'
 
   export default {
     name: 'DistributorDetail',
-    components: { DetailsRecords, Contacts, ProjectRecords, ContractRecords, HistoryProxy, Follow, FollowAdd },
+    components: { DetailsRecords, Contacts, ProjectRecords, ContractRecords, HistoryProxy, Follow, FollowAdd, Visit },
     data() {
       return {
         id: 0,

+ 34 - 2
src/views/base/partners/detail.vue

@@ -76,13 +76,30 @@
                     </div>
                     <transition name="height">
                       <ul class="comments">
-                        <li v-for="comment in item.comments" :key="comment.id">
+                        <li v-for="comment in item.comments" :key="comment.id" class="flex-wrap">
                           <vab-icon class="user-avatar" icon="account-circle-fill" />
                           <div class="text">
                             <p>{{ comment.createdName }}</p>
                             <p>{{ comment.content }}</p>
-                            <p>{{ comment.createdTime }}</p>
+                            <p>
+                              {{ comment.createdTime }}
+                              <span v-if="!comment.replyComments" @click="postReply(comment)">回复</span>
+                            </p>
                           </div>
+                          <transition v-if="comment.replyComments" name="height">
+                            <ul class="comments" style="width: 100%">
+                              <li v-for="replyComments in comment.replyComments" :key="replyComments.id">
+                                <vab-icon class="user-avatar" icon="account-circle-fill" />
+                                <div class="text">
+                                  <p>{{ replyComments.createdName }}</p>
+                                  <p>{{ replyComments.content }}</p>
+                                  <p>
+                                    {{ replyComments.createdTime }}
+                                  </p>
+                                </div>
+                              </li>
+                            </ul>
+                          </transition>
                         </li>
                       </ul>
                     </transition>
@@ -166,6 +183,14 @@
           })
           .catch(() => {})
       },
+      // 发表回复
+      postReply(row) {
+        this.$PostComment({ form: row, visible: true, reply: true })
+          .then(() => {
+            this.getFollowList()
+          })
+          .catch(() => {})
+      },
       async init() {
         const [err, res] = await to(partnerApi.getCompanyConcat({ id: parseInt(this.id) }))
         if (err) return
@@ -415,6 +440,7 @@
             overflow: auto;
 
             li {
+              padding-top: 10px;
               display: flex;
               border-top: 1px solid #e3e5e7;
 
@@ -437,6 +463,9 @@
                   font-size: 12px;
                   color: #9499a0;
                   text-align: right;
+                  span {
+                    cursor: pointer;
+                  }
                 }
               }
             }
@@ -509,4 +538,7 @@
   .height-enter, .height-leave-to /* .fade-leave-active below version 2.1.8 */ {
     height: 0;
   }
+  .flex-wrap {
+    flex-wrap: wrap;
+  }
 </style>

+ 2 - 2
src/views/base/partners/index.vue

@@ -201,10 +201,10 @@
         fileSettings: {
           // 文件配置信息
           fileSize: 52428800,
-          fileTypes: '.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.txt',
+          fileTypes: '.pdf,.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.txt',
           pictureSize: 52428800,
           pictureTypes: '.jpg,.jpeg,.gif,.png,.jfif,.txt',
-          types: '.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.mp4,.txt',
+          types: '.pdf,.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.mp4,.txt',
           videoSize: 104857600,
           videoType: '.mp4',
         },

+ 8 - 8
src/views/base/productAuth/index.vue

@@ -65,9 +65,9 @@
             </el-checkbox>
           </el-checkbox-group>
         </el-form-item>
-        <el-form-item label="大项目授权">
-          <el-switch v-model="form.isBig" active-text="是" active-value="10" inactive-text="否" inactive-value="20" />
-        </el-form-item>
+        <!--        <el-form-item label="大项目授权">-->
+        <!--          <el-switch v-model="form.isBig" active-text="是" active-value="10" inactive-text="否" inactive-value="20" />-->
+        <!--        </el-form-item>-->
       </el-form>
       <template #footer>
         <el-button @click="visible = false">取 消</el-button>
@@ -121,11 +121,11 @@
             sortable: false,
             disableCheck: false,
           },
-          {
-            label: '大项目',
-            width: '120px',
-            prop: 'isBig',
-          },
+          // {
+          //   label: '大项目',
+          //   width: '120px',
+          //   prop: 'isBig',
+          // },
           {
             label: '授权产品线',
             width: 'auto',

+ 2 - 2
src/views/contract/components/ApplyContract.vue

@@ -110,10 +110,10 @@
         fileSettings: {
           // 文件配置信息
           fileSize: 52428800,
-          fileTypes: '.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.txt',
+          fileTypes: '.pdf,.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.txt',
           pictureSize: 52428800,
           pictureTypes: '.jpg,.jpeg,.gif,.png,.jfif,.txt',
-          types: '.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.mp4,.txt',
+          types: '.pdf,.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.mp4,.txt',
           videoSize: 104857600,
           videoType: '.mp4',
         },

+ 2 - 2
src/views/contract/components/DetailsEnclosure.vue

@@ -95,10 +95,10 @@
         fileSettings: {
           // 文件配置信息
           fileSize: 52428800,
-          fileTypes: '.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.txt',
+          fileTypes: '.pdf,.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.txt',
           pictureSize: 52428800,
           pictureTypes: '.jpg,.jpeg,.gif,.png,.jfif,.txt',
-          types: '.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.mp4,.txt',
+          types: '.pdf,.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.mp4,.txt',
           videoSize: 104857600,
           videoType: '.mp4',
         },

+ 47 - 1
src/views/contract/components/DetailsRecords.vue

@@ -34,6 +34,50 @@
               <p>职务:{{ item.opnContent.postion }}</p>
               <p>手机:{{ item.opnContent.telephone }}</p>
             </template>
+            <template v-else-if="item.opnContent.newHardwareMaintenanceBeginTime">
+              <p>
+                合同名称:
+                <span>{{ item.opnContent.contractName }}</span>
+              </p>
+              <p>
+                原合同软件运维时间:
+                <br />
+                <span>
+                  {{ parseTime(item.opnContent.oldSoftwareMaintenanceBeginTime, '{y}-{m}-{d}') }} ~
+                  {{ parseTime(item.opnContent.oldSoftwareMaintenanceEndTime, '{y}-{m}-{d}') }}
+                </span>
+              </p>
+              <p>
+                原合同硬件运维时间:
+                <br />
+                <span>
+                  {{ parseTime(item.opnContent.oldHardwareMaintenanceBeginTime, '{y}-{m}-{d}') }} ~
+                  {{ parseTime(item.opnContent.oldHardwareMaintenanceEndTime, '{y}-{m}-{d}') }}
+                </span>
+              </p>
+              <p>
+                续签合同软件运维时间:
+                <br />
+                <span>
+                  {{ parseTime(item.opnContent.newSoftwareMaintenanceBeginTime, '{y}-{m}-{d}') }} ~
+                  {{ parseTime(item.opnContent.newSoftwareMaintenanceEndTime, '{y}-{m}-{d}') }}
+                </span>
+              </p>
+              <p>
+                续签合同硬件运维时间:
+                <br />
+                <span>
+                  {{ parseTime(item.opnContent.newHardwareMaintenanceBeginTime, '{y}-{m}-{d}') }} ~
+                  {{ parseTime(item.opnContent.newHardwareMaintenanceEndTime, '{y}-{m}-{d}') }}
+                </span>
+              </p>
+            </template>
+            <template v-else-if="item.opnContent.contractName">
+              <p>
+                合同名称:
+                <span>{{ item.opnContent.contractName }}</span>
+              </p>
+            </template>
           </div>
         </li>
       </ul>
@@ -42,6 +86,8 @@
 </template>
 
 <script>
+  import { parseTime } from '../../../utils'
+
   export default {
     name: 'Records',
     props: {
@@ -57,7 +103,7 @@
 
     mounted() {},
 
-    methods: {},
+    methods: { parseTime },
   }
 </script>
 

+ 205 - 0
src/views/contract/components/DetailsRenew.vue

@@ -0,0 +1,205 @@
+<!--
+ * @Author: liuzl 461480418@qq.com
+ * @Date: 2023-01-09 13:54:40
+ * @LastEditors: liuzhenlin
+ * @LastEditTime: 2023-01-10 14:38:22
+ * @Description: file content
+ * @FilePath: \订单全流程管理系统\src\views\contract\components\DetailsProduct.vue
+-->
+<template>
+  <div>
+    <el-table border :data="renewList">
+      <el-table-column
+        v-for="(item, index) in columns"
+        :key="index"
+        align="center"
+        :label="item.label"
+        :min-width="item.width"
+        :prop="item.prop"
+        show-overflow-tooltip>
+        <template #default="{ row }">
+          <span v-if="item.prop === 'contractType'">
+            {{ selectDictLabel(contractTypeOptions, row.contractType) }}
+          </span>
+          <span v-else-if="item.prop === 'signatoryType'">
+            {{ selectDictLabel(signatoryTypeOptions, row.signatoryType) }}
+          </span>
+          <span v-else-if="item.prop === 'contractAmount'">
+            {{ formatPrice(row.contractAmount) }}
+          </span>
+          <span v-else-if="item.prop === 'collectedAmount'">
+            {{ formatPrice(row.collectedAmount) }}
+          </span>
+          <span v-else-if="item.prop === 'contractSignTime'">
+            {{ parseTime(row.contractSignTime, '{y}-{m}-{d}') }}
+          </span>
+          <span v-else-if="item.label === '合同时间'">
+            {{ parseTime(row.contractStartTime, '{y}-{m}-{d}') }}~{{ parseTime(row.contractEndTime, '{y}-{m}-{d}') }}
+          </span>
+          <span v-else>{{ row[item.prop] }}</span>
+        </template>
+      </el-table-column>
+    </el-table>
+  </div>
+</template>
+
+<script>
+  import { selectDictLabel } from '../../../utils'
+
+  export default {
+    name: 'DetailsRenew',
+    components: {},
+    props: {
+      details: {
+        type: Object,
+        default: () => {},
+      },
+      renewList: {
+        type: Array,
+        default: () => [],
+      },
+    },
+    data() {
+      return {
+        columns: [
+          {
+            label: '合同编号',
+            width: '160px',
+            prop: 'contractCode',
+            sortable: false,
+            disableCheck: false,
+          },
+          {
+            label: '合同类型',
+            width: '100px',
+            prop: 'contractType',
+            sortable: false,
+            disableCheck: false,
+          },
+          // {
+          //   label: '审批状态',
+          //   width: '100px',
+          //   prop: 'approStatus',
+          //   sortable: false,
+          //   disableCheck: false,
+          // },
+          {
+            label: '所在省',
+            width: '100px',
+            prop: 'custProvince',
+            sortable: false,
+            disableCheck: false,
+          },
+          {
+            label: '所在市',
+            width: '100px',
+            prop: 'custCity',
+            sortable: false,
+            disableCheck: false,
+          },
+          {
+            label: '客户名称',
+            width: '280px',
+            prop: 'custName',
+            sortable: false,
+            disableCheck: false,
+          },
+          {
+            label: '签订单位类型',
+            width: '120px',
+            prop: 'signatoryType',
+            sortable: false,
+            disableCheck: false,
+          },
+          {
+            label: '合同签订单位',
+            width: '120px',
+            prop: 'distributorName',
+            sortable: false,
+            disableCheck: false,
+          },
+          {
+            label: '合同签订时间',
+            width: '180px',
+            prop: 'contractSignTime',
+            sortable: false,
+            disableCheck: false,
+          },
+          {
+            label: '合同有效时间',
+            width: '180px',
+            prop: 'contractStartTime',
+            sortable: false,
+            disableCheck: false,
+          },
+          {
+            label: '合同金额',
+            width: '120px',
+            prop: 'contractAmount',
+            sortable: false,
+            disableCheck: false,
+          },
+          {
+            label: '回款金额',
+            width: '120px',
+            prop: 'collectedAmount',
+            sortable: false,
+            disableCheck: false,
+          },
+          {
+            label: '销售工程师',
+            width: '120px',
+            prop: 'inchargeName',
+            sortable: false,
+            disableCheck: false,
+          },
+        ],
+        productLineOptions: [],
+        maintainVisible: false,
+        maintainForm: {
+          id: 0,
+          prodName: '',
+          maintainPeriod: 0,
+          warrantPeriod: 0,
+          maintainStartTime: null,
+          maintainRemark: '',
+          acceptTime: null,
+        },
+        costVisible: false,
+        costForm: {
+          id: 0,
+          prodName: '',
+          purchaseCost: 0,
+          devCost: 0,
+          maintainCost: 0,
+          directCost: 0,
+        },
+        contractTypeOptions: [],
+        signatoryTypeOptions: [],
+      }
+    },
+    computed: {
+      detailsNoMutation() {
+        let obj = Object.assign({}, this.details)
+        return obj
+      },
+    },
+    mounted() {
+      this.getOptions()
+    },
+    methods: {
+      selectDictLabel,
+      getOptions() {
+        Promise.all([this.getDicts('contract_type'), this.getDicts('contract_signatory_type')])
+          .then(([contract, signatory]) => {
+            this.contractTypeOptions = contract.data.values || []
+            this.signatoryTypeOptions = signatory.data.values || []
+            console.log('this.contractTypeOptions', this.contractTypeOptions)
+          })
+          .catch((err) => console.log(err))
+      },
+    },
+  }
+</script>
+
+<style lang="scss" scoped></style>

+ 755 - 0
src/views/contract/components/RenewEdit.vue

@@ -0,0 +1,755 @@
+<!--
+ * @Author: liuzhenlin 461480418@qq.ocm
+ * @Date: 2023-01-09 15:49:34
+ * @LastEditors: niezch@dashoo.cn
+ * @LastEditTime: 2023-04-04 16:18:50
+ * @Description: file content
+ * @FilePath: \opms_frontend\src\views\contract\components\Edit.vue
+-->
+<template>
+  <el-dialog class="edit-container" :title="title" top="5vh" :visible.sync="editVisible" @close="handleClose">
+    <div class="setp-wrap">
+      <el-steps :active="stepActive" :align-center="true" finish-status="success">
+        <el-step title="基础信息" />
+        <el-step title="产品列表" />
+      </el-steps>
+    </div>
+    <el-form v-show="stepActive == 0" ref="editForm" :model="editForm" :rules="editRules">
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="合同编号" prop="contractCode">
+            <el-input v-model="editForm.contractCode" disabled placeholder="根据编号规则自动生产" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="关联合同" prop="renewContractName">
+            <el-input
+              v-model="editForm.renewContractName"
+              :disabled="businessData.length > 0"
+              placeholder="请选择关联合同"
+              readonly
+              suffix-icon="el-icon-search"
+              @focus="openProject" />
+          </el-form-item>
+        </el-col>
+
+        <el-col :span="12">
+          <el-form-item label="合同名称" prop="contractName">
+            <el-input v-model="editForm.contractName" placeholder="请输入合同名称" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="客户签约人" prop="custSignatoryName">
+            <el-input
+              v-model="editForm.custSignatoryName"
+              :disabled="!businessUserQueryParams.busId"
+              placeholder="请选择客户签约人"
+              readonly
+              suffix-icon="el-icon-search"
+              @focus="openContact" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="合同开始时间" prop="contractStartTime">
+            <el-date-picker
+              v-model="editForm.contractStartTime"
+              :picker-options="pickerOptionsStart"
+              placeholder="选择开始日期"
+              style="width: 100%"
+              type="date"
+              value-format="yyyy-MM-dd" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="合同结束时间" prop="contractEndTime">
+            <el-date-picker
+              v-model="editForm.contractEndTime"
+              :picker-options="pickerOptionsEnd"
+              placeholder="选择结束日期"
+              style="width: 100%"
+              type="date"
+              value-format="yyyy-MM-dd" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="合同签订时间" prop="contractSignTime">
+            <el-date-picker
+              v-model="editForm.contractSignTime"
+              :picker-options="pickerOptionsSign"
+              placeholder="选择签订日期"
+              style="width: 100%"
+              type="date"
+              value-format="yyyy-MM-dd" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="销售工程师" prop="inchargeName">
+            <el-input
+              v-model="editForm.inchargeName"
+              :disabled="true"
+              placeholder="请选择销售工程师"
+              readonly
+              suffix-icon="el-icon-search"
+              @focus="openUser(false, 'inchargeId', 'inchargeName')" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="公司签约人" prop="signatoryName">
+            <el-input
+              ref="signatoryName"
+              v-model="editForm.signatoryName"
+              placeholder="请选择公司签约人"
+              readonly
+              suffix-icon="el-icon-search"
+              @focus="openUser(false, 'signatoryId', 'signatoryName')" />
+          </el-form-item>
+        </el-col>
+
+        <el-col :span="12">
+          <el-form-item label="经销商/代理商" prop="distributorName">
+            <el-input
+              v-model="editForm.distributorName"
+              placeholder="请选择经销商/代理商"
+              readonly
+              suffix-icon="el-icon-search"
+              @focus="openDistributor" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="合同类型" prop="contractType">
+            <el-select v-model="editForm.contractType" placeholder="合同类型" style="width: 100%">
+              <el-option v-for="item in contractOptions" :key="item.value" :label="item.value" :value="item.key" />
+            </el-select>
+          </el-form-item>
+        </el-col>
+
+        <el-col :span="12">
+          <el-form-item label="签订单位类型" prop="signatoryType">
+            <el-select
+              v-model="editForm.signatoryType"
+              placeholder="签订单位类型"
+              style="width: 100%"
+              @change="signatoryTypeChange">
+              <el-option v-for="item in signatoryOptions" :key="item.value" :label="item.value" :value="item.key" />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="合同签订单位" prop="signatoryUnit">
+            <el-input v-model="editForm.signatoryUnit" disabled placeholder="请选择合同签订单位" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="质量/履约保证金(元)" prop="earnestMoney">
+            <el-input v-model.number="editForm.earnestMoney" clearable placeholder="请输入质量/履约保证金" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row :gutter="20">
+        <!--        <el-col :span="12">-->
+        <!--          <el-form-item label="软件运维期限(年)" prop="softwareMaintenanceLimit">-->
+        <!--            <el-input-number-->
+        <!--              v-model.number="editForm.softwareMaintenanceLimit"-->
+        <!--              controls-position="right"-->
+        <!--              :min="0"-->
+        <!--              placeholder="请输入软件运维期限"-->
+        <!--              :step="1"-->
+        <!--              step-strictly-->
+        <!--              style="width: 100%" />-->
+        <!--          </el-form-item>-->
+        <!--          <el-form-item label="硬件运维期限(年)" prop="hardwareMaintenanceLimit">-->
+        <!--            <el-input-number-->
+        <!--              v-model="editForm.hardwareMaintenanceLimit"-->
+        <!--              controls-position="right"-->
+        <!--              :min="0"-->
+        <!--              placeholder="请输入软件运维期限"-->
+        <!--              :step="1"-->
+        <!--              style="width: 100%" />-->
+        <!--          </el-form-item>-->
+        <!--        </el-col>-->
+        <el-col :span="12">
+          <el-form-item label="软件运维开始时间" prop="softwareMaintenanceBeginTime">
+            <el-date-picker
+              v-model="editForm.softwareMaintenanceBeginTime"
+              :picker-options="softwarePickerOptionsStart"
+              placeholder="选择软件运维开始时间"
+              style="width: 100%"
+              type="date"
+              value-format="yyyy-MM-dd" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="软件运维结束时间" prop="softwareMaintenanceEndTime">
+            <el-date-picker
+              v-model="editForm.softwareMaintenanceEndTime"
+              :picker-options="softwarePickerOptionsEnd"
+              placeholder="选择软件运维结束时间"
+              style="width: 100%"
+              type="date"
+              value-format="yyyy-MM-dd" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="硬件运维开始时间" prop="hardwareMaintenanceBeginTime">
+            <el-date-picker
+              v-model="editForm.hardwareMaintenanceBeginTime"
+              :picker-options="hardwarePickerOptionsStart"
+              placeholder="选择硬件运维开始时间"
+              style="width: 100%"
+              type="date"
+              value-format="yyyy-MM-dd" />
+          </el-form-item>
+
+          <el-form-item label="硬件运维结束时间" prop="hardwareMaintenanceEndTime">
+            <el-date-picker
+              v-model="editForm.hardwareMaintenanceEndTime"
+              :picker-options="hardwarePickerOptionsEnd"
+              placeholder="选择硬件运维结束时间"
+              style="width: 100%"
+              type="date"
+              value-format="yyyy-MM-dd" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="运维服务费约定" prop="serviceFeeAgreement">
+            <el-input
+              v-model="editForm.serviceFeeAgreement"
+              placeholder="请输入运维服务费约定"
+              :rows="5"
+              type="textarea" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="运维条款" prop="maintenanceClause">
+            <el-input v-model="editForm.maintenanceClause" placeholder="请输入运维条款" :rows="5" type="textarea" />
+          </el-form-item>
+        </el-col>
+
+        <el-col :span="12">
+          <el-form-item label="备注" prop="remark">
+            <el-input v-model="editForm.remark" placeholder="请输入备注" :rows="5" type="textarea" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+    <!-- 产品分类 -->
+    <el-row v-show="stepActive == 1">
+      <el-row class="mb10" :gutter="20">
+        <el-col :span="12">
+          <p>产品:</p>
+        </el-col>
+        <el-col class="proj-col" :span="12">
+          <el-button type="primary" @click="$refs.product.open()">选择产品</el-button>
+        </el-col>
+      </el-row>
+      <el-row>
+        <el-col :span="24">
+          <product-table
+            ref="productTable"
+            :product-data="productData"
+            @changeProductData="changeProductData"
+            @delProductData="delProductData" />
+        </el-col>
+      </el-row>
+    </el-row>
+
+    <span slot="footer">
+      <el-button v-show="stepActive == 0" type="primary" @click="setStep(1)">下一步</el-button>
+      <el-button v-show="stepActive == 1" type="primary" @click="setStep(0)">上一步</el-button>
+      <el-button v-show="!editForm.id && stepActive == 1" type="primary" @click="contractSave">保存</el-button>
+      <el-button v-show="editForm.id && stepActive == 1" type="primary" @click="contractEdit">保存</el-button>
+      <el-button @click="editVisible = false">取消</el-button>
+    </span>
+    <!-- 选择合同 -->
+    <SelectCtrContract ref="project" :multiple="false" @save="getBusinessInfo" />
+    <!-- 选择经销商 -->
+    <select-distributor ref="distributor" :multiple="false" @save="getDistributor" />
+    <!-- 选择用户 -->
+    <select-user ref="user" :label="label" :multiple="multiple" :property="property" @save="getUser" />
+    <!-- 选择产品 -->
+    <select-product ref="product" :multiple="true" @save="getProduct" />
+    <!-- 选择联系人 -->
+    <select-business-contact
+      ref="contact"
+      :multiple="false"
+      :query-params="businessUserQueryParams"
+      @save="getContact" />
+  </el-dialog>
+</template>
+
+<script>
+  import { mapGetters } from 'vuex'
+  import to from 'await-to-js'
+  import contractApi from '@/api/contract'
+  import businessApi from '@/api/proj/business'
+  import ProductTable from './ProductTable'
+  import SelectCtrContract from '@/components/select/SelectCtrContract'
+  import SelectDistributor from '@/components/select/SelectDistributor'
+  import SelectUser from '@/components/select/SelectUser'
+  import SelectProduct from '@/components/select/SelectProduct'
+  import SelectBusinessContact from '@/components/select/SelectBusinessContact'
+  export default {
+    components: {
+      ProductTable,
+      SelectCtrContract,
+      SelectDistributor,
+      SelectUser,
+      SelectProduct,
+      SelectBusinessContact,
+    },
+    props: {
+      businessData: {
+        type: Array,
+        default: () => [],
+      },
+    },
+    data() {
+      return {
+        stepActive: 0, //步骤
+        title: '新增客户信息',
+        editVisible: false,
+        editForm: {
+          contractId: null, //关联合同id
+          renewContractName: '', //关联合同
+          contractCode: '', //合同编号
+          contractName: '', //合同名称
+          contractType: '', //合同类型
+          nboId: null, //项目id
+          nboName: '', //项目名称
+          custName: '', // 客户名称
+          inchargeId: null, //销售工程师
+          inchargeName: '', //销售工程师姓名
+          signatoryType: '',
+          signatoryUnit: '',
+          earnestMoney: 0,
+          contractStartTime: '', //合同开始时间
+          contractEndTime: '', //合同结束时间
+          contractSignTime: '', //合同签订时间
+          signatoryName: '', //公司签约人
+          signatoryId: null, //公司签约人id
+          distributorId: null, //经销商id
+          distributorName: '', //经销商name
+          custSignatoryId: null, //客户签约人id
+          custSignatoryName: '', //客户签约人name
+          remark: '', //备注
+          serviceFeeAgreement: '', // 运维服务费约定
+          softwareMaintenanceBeginTime: '',
+          softwareMaintenanceEndTime: '',
+          hardwareMaintenanceBeginTime: '',
+          hardwareMaintenanceEndTime: '',
+          softwareMaintenanceLimit: 1, // 软件运维期限(年)
+          hardwareMaintenanceLimit: 1, // 硬件运维期限(年)
+          maintenanceClause: '', // 运维条款
+        },
+        editRules: {
+          contractName: [{ required: true, trigger: 'blur', message: '请输入合同名称' }],
+          contractType: [{ required: true, trigger: 'change', message: '请选择合同类型' }],
+          signatoryType: [{ required: true, trigger: 'change', message: '请选择签订单位类型' }],
+          signatoryUnit: [{ required: true, trigger: 'change', message: '请选择合同签订单位' }],
+          nboName: [{ required: true, trigger: 'change', message: '请选择关联项目' }],
+          contractStartTime: [
+            { required: true, trigger: 'change', validator: this.pickerOptionsStart, message: '请选择开始时间' },
+          ],
+          contractEndTime: [{ trigger: 'change', validator: this.pickerOptionsEnd, message: '请选择结束时间' }],
+          contractSignTime: [
+            { required: true, trigger: 'change', validator: this.pickerOptionsSign, message: '请选择签订时间' },
+          ],
+          inchargeId: [{ required: true, trigger: 'change', message: '请选择销售工程师' }],
+          signatoryName: [{ required: true, trigger: 'change', message: '请选择公司签约人' }],
+          distributorName: [{ trigger: 'change', message: '请选择经销商' }],
+          softwareMaintenanceLimit: [{ required: true, trigger: 'change', message: '请输入软件运维期限' }],
+          hardwareMaintenanceLimit: [{ required: true, trigger: 'change', message: '请输入硬件运维期限' }],
+          maintenanceClause: [{ required: true, trigger: 'blur', message: '请输入运维条款' }],
+          softwareMaintenanceBeginTime: [{ required: true, trigger: 'blur', message: '请选择软件运维开始时间' }],
+          softwareMaintenanceEndTime: [{ required: true, trigger: 'blur', message: '请选择软件运维结束时间' }],
+          hardwareMaintenanceBeginTime: [{ required: true, trigger: 'blur', message: '请选择硬件运维开始时间' }],
+          hardwareMaintenanceEndTime: [{ required: true, trigger: 'blur', message: '请选择硬件运维结束时间' }],
+        },
+        pickerOptionsStart: {
+          disabledDate: (time) => {
+            let endDateVal = this.editForm.contractEndTime
+            if (endDateVal) {
+              return time.getTime() > new Date(endDateVal).getTime()
+            }
+          },
+        },
+        pickerOptionsEnd: {
+          disabledDate: (time) => {
+            let beginDateVal = this.editForm.contractStartTime
+            if (beginDateVal) {
+              return time.getTime() < new Date(beginDateVal).getTime() - 1 * 24 * 60 * 60 * 1000
+            }
+          },
+        },
+        softwarePickerOptionsStart: {
+          disabledDate: (time) => {
+            let endDateVal = this.editForm.softwareMaintenanceEndTime
+            if (endDateVal) {
+              return time.getTime() > new Date(endDateVal).getTime()
+            }
+          },
+        },
+        softwarePickerOptionsEnd: {
+          disabledDate: (time) => {
+            let beginDateVal = this.editForm.softwareMaintenanceBeginTime
+            if (beginDateVal) {
+              return time.getTime() < new Date(beginDateVal).getTime() - 1 * 24 * 60 * 60 * 1000
+            }
+          },
+        },
+        hardwarePickerOptionsStart: {
+          disabledDate: (time) => {
+            let endDateVal = this.editForm.hardwareMaintenanceEndTime
+            if (endDateVal) {
+              return time.getTime() > new Date(endDateVal).getTime()
+            }
+          },
+        },
+        hardwarePickerOptionsEnd: {
+          disabledDate: (time) => {
+            let beginDateVal = this.editForm.hardwareMaintenanceBeginTime
+            if (beginDateVal) {
+              return time.getTime() < new Date(beginDateVal).getTime() - 1 * 24 * 60 * 60 * 1000
+            }
+          },
+        },
+        pickerOptionsSign: {
+          disabledDate: (time) => {
+            return time.getTime() > new Date().getTime()
+          },
+        },
+        contractOptions: [], //合同类型
+        signatoryOptions: [
+          {
+            key: '10',
+            value: '终端用户',
+          },
+          {
+            key: '20',
+            value: '经销商',
+          },
+          {
+            key: '30',
+            value: '代理商',
+          },
+        ], //合同类型
+        productData: [],
+        multiple: false,
+        property: '',
+        label: '',
+        businessUserQueryParams: {}, //查询客户签约人参数
+      }
+    },
+    computed: {
+      ...mapGetters({
+        userId: 'user/id',
+        username: 'user/username',
+      }),
+    },
+    mounted() {
+      this.getOptions()
+    },
+    methods: {
+      getOptions() {
+        Promise.all([this.getDicts('contract_type')])
+          .then(([contract]) => {
+            this.contractOptions = contract.data.values || []
+          })
+          .catch((err) => console.log(err))
+      },
+      async init(id) {
+        if (this.businessData[0]) this.getBusinessInfo(this.businessData)
+        if (!id) {
+          this.title = '新增合同信息'
+          // 设置销售工程师默认为当前用户
+          // this.editForm.inchargeId = this.userId
+          // this.editForm.inchargeName = this.nickName
+          this.editForm.signatoryId = this.userId
+          this.editForm.signatoryName = this.nickName
+        } else {
+          this.title = '编辑合同'
+          const [err, res] = await to(contractApi.getDetails({ id }))
+          if (err) return
+          if (res.data) {
+            // eslint-disable-next-line no-unused-vars
+            let { product, ...data } = res.data
+            this.editForm = data
+            this.businessUserQueryParams = { busId: data.nboId, custId: data.custId }
+            this.productData =
+              product.length > 0
+                ? product.map((item) => ({
+                    prodCode: item.prodCode,
+                    id: item.prodId,
+                    prodName: item.prodName,
+                    prodClass: item.prodClass,
+                    guidPrice: item.sugSalesPrice,
+                    price: item.tranPrice,
+                    count: item.prodNum,
+                  }))
+                : []
+
+            const [err, resp] = await to(contractApi.getDetails({ id: data.contractId }))
+            if (err) return
+            if (resp.data) {
+              this.editForm.renewContractName = resp.data.contractName
+            }
+          }
+        }
+        this.editVisible = true
+      },
+      // 修改步骤
+      async setStep(step) {
+        if (step == 1) {
+          const [valid] = await to(this.$refs.editForm.validate())
+          if (valid == false) return
+        }
+        this.stepActive = step
+      },
+      // 打开选择项目
+      openProject() {
+        this.$refs.project.open()
+      },
+      signatoryTypeChange() {
+        // 10: '终端用户'
+        // 20: '经销商'
+        // 30: '代理商'
+        if (this.editForm.signatoryType == '10') {
+          this.editForm.signatoryUnit = this.editForm.custName
+        }
+        if (this.editForm.signatoryType == '20') {
+          this.editForm.signatoryUnit = this.editForm.distributorName
+        }
+        // 现在还没有代理商
+        if (this.editForm.signatoryType == '30') {
+          this.editForm.signatoryUnit = this.editForm.distributorName
+        }
+      },
+      // 获取合同信息
+      async getBusinessInfo(data) {
+        let business = data[0] || null
+        if (!business) return
+        console.log(business, 'business')
+        // 获取合同信息
+        const [err, res] = await to(contractApi.getDetails({ id: business.id }))
+        if (err) return
+        if (res.data) {
+          // eslint-disable-next-line no-unused-vars
+          let { product, ...data } = res.data
+          this.editForm = data
+          this.editForm.renewContractName = data.contractName
+          this.editForm.contractId = business.id
+
+          this.editForm.id = null
+          this.editForm.contractCode = ''
+          this.editForm.contractEndTime = ''
+          this.businessUserQueryParams = { busId: data.nboId, custId: data.custId }
+          this.productData =
+            product.length > 0
+              ? product.map((item) => ({
+                  prodCode: item.prodCode,
+                  id: item.prodId,
+                  prodName: item.prodName,
+                  prodClass: item.prodClass,
+                  guidPrice: item.sugSalesPrice,
+                  price: item.tranPrice,
+                  count: item.prodNum,
+                }))
+              : []
+        }
+        this.signatoryTypeChange()
+      },
+      // 打开选择经销商
+      openDistributor() {
+        this.$refs.distributor.open()
+      },
+      // 关闭经销商获取经销商信息
+      getDistributor(data) {
+        let distributor = data[0] || null
+        if (!distributor) return
+        this.editForm.distributorName = distributor.distName
+        this.editForm.distributorId = distributor.id
+        this.signatoryTypeChange()
+      },
+      // 打开选择公司签约人
+      openUser(multiple, property, label) {
+        this.multiple = multiple
+        this.property = property
+        this.label = label
+        if (this.editForm[property].length) {
+          this.$refs.user.ids = this.editForm[property]
+        } else if (this.editForm[property]) {
+          this.$refs.user.ids = [this.editForm[property]]
+        } else {
+          this.$refs.user.ids = []
+        }
+        this.$refs.user.open()
+      },
+      // 获取签约人信息
+      getUser(userList, property, label) {
+        this.editForm[label] = userList.map((item) => item.nickName).join()
+        if (this.multiple) {
+          this.editForm[property] = userList.map((item) => item.id)
+        } else {
+          this.editForm[property] = userList[0] ? userList[0].id : ''
+        }
+        this.$forceUpdate()
+      },
+      // 打开选择公司签约人
+      openContact() {
+        this.$refs.contact.open()
+      },
+      // 获取签约人信息
+      getContact(data) {
+        let user = data[0] || null
+        if (!user) return
+        this.editForm['custSignatoryName'] = user.cuctName
+        this.editForm['custSignatoryId'] = user.id
+      },
+      // 获取产品信息
+      getProduct(data) {
+        // 重构产品数据结构
+        if (data.length > 0) this.setProductData(data)
+      },
+      // 根据项目id获取产品信息
+      async getProjectInfo(id) {
+        let params = { id }
+        const [err, res] = await to(businessApi.getProductByBusinessId(params))
+        if (err) return
+        if (res.data && res.data.length > 0) this.setProductData(res.data)
+      },
+      setProductData(data) {
+        let projData = data.map((item) => ({
+          id: item.prodId ? item.prodId : item.id,
+          prodName: item.prodName,
+          prodCode: item.prodCode,
+          prodClass: item.prodClass,
+          guidPrice: item.guidPrice,
+          count: item.prodNum,
+          price: item.prodPrice ? item.prodPrice : item.guidPrice,
+        }))
+        this.productData.push(...projData)
+        this.productData = this.removeDuplicateObj(this.productData)
+      },
+      // 数组对象去重
+      removeDuplicateObj(arr) {
+        let obj = {}
+        arr = arr.reduce((newArr, next) => {
+          obj[next.id] ? '' : (obj[next.id] = true && newArr.push(next))
+          return newArr
+        }, [])
+        return arr
+      },
+      // 修改产品列表数据
+      changeProductData(data) {
+        this.productData = this.productData.map((item) => {
+          return item.id === data.id ? data : item
+        })
+      },
+      delProductData(data) {
+        this.productData = this.productData.filter((item) => item.id != data.id)
+      },
+      // 保存合同
+      async contractSave() {
+        let product = this.productData.map((item) => ({
+          prodId: item.id,
+          prodNum: parseInt(item.count),
+          maintTerm: 1,
+          sugSalesPrice: item.guidPrice,
+          tranPrice: item.price,
+          remark: '',
+        }))
+        let params = Object.assign({ ...this.editForm }, { product })
+        if (product.length == 0) return this.$message.warning('请选择产品信息')
+        const [err, res] = await to(contractApi.reNewContract(params))
+        if (err) return
+        if (res.code == 200) this.$message.success(res.msg)
+        else return
+        this.editVisible = false
+        this.$emit('contractSave')
+      },
+      // 编辑合同
+      async contractEdit() {
+        let product = this.productData.map((item) => ({
+          prodId: item.id,
+          prodNum: parseInt(item.count),
+          maintTerm: 1,
+          sugSalesPrice: item.guidPrice,
+          tranPrice: item.price,
+          remark: '',
+        }))
+        let params = Object.assign({ ...this.editForm }, { product })
+        if (product.length == 0) return this.$message.warning('请选择产品信息')
+        const [err, res] = await to(contractApi.updateReNew(params))
+        if (err) return
+        if (res.code == 200) this.$message.success(res.msg)
+        else return
+        this.editVisible = false
+        this.$emit('contractSave')
+      },
+      handleClose() {
+        this.editForm = {
+          contractCode: '', //合同编号
+          contractName: '', //合同名称
+          contractType: '', //合同类型
+          nboId: null, //项目id
+          nboName: '', //项目名称
+          custName: '', // 客户名称
+          inchargeId: null, //销售工程师
+          inchargeName: '', //销售工程师名称
+          signatoryType: '',
+          contractStartTime: '', //合同开始时间
+          contractEndTime: '', //合同结束时间
+          signatoryName: '', //公司签约人
+          signatoryId: null, //公司签约人id
+          distributorId: null, //经销商id
+          distributorName: '', //经销商name
+          remark: '', //备注
+          serviceFeeAgreement: '', //运维服务费约定
+          softwareMaintenanceLimit: 1, // 软件运维期限(年)
+          hardwareMaintenanceLimit: 1, // 硬件运维期限(年)
+          maintenanceClause: '', // 运维条款
+        }
+        this.productData = []
+        this.stepActive = 0
+        this.businessUserQueryParams = {}
+        this.$refs.editForm.resetFields()
+      },
+    },
+  }
+</script>
+
+<style lang="scss" scoped>
+  $base: '.edit-container';
+  #{$base} {
+    .mb10 {
+      margin-bottom: 10px;
+    }
+    .proj-col {
+      text-align: right;
+    }
+    // 进度条样式
+    // ::v-deep .el-steps {
+    //   .el-step__line {
+    //     top: 17px;
+    //   }
+    //   .el-step__icon {
+    //     width: 35px;
+    //     height: 35px;
+    //     font-size: 18px;
+    //   }
+    //   .is-process {
+    //     .el-step__icon {
+    //       background: #2e51ff;
+    //       color: #fff;
+    //       border-color: #2e51ff;
+    //     }
+    //   }
+    // }
+  }
+</style>

+ 28 - 0
src/views/contract/detail.vue

@@ -67,6 +67,13 @@
           <el-tab-pane label="分成信息" name="share">
             <details-share v-if="activeName == 'share'" :details="details" :shares="shares" />
           </el-tab-pane>
+          <el-tab-pane v-if="details.contractId == 0" label="续签合同" name="renew">
+            <DetailRenew
+              v-if="activeName == 'renew'"
+              :details="details"
+              :renew-list="renewList"
+              @productUpdate="init" />
+          </el-tab-pane>
         </el-tabs>
       </div>
       <div class="info-side">
@@ -97,6 +104,7 @@
   import contractApi from '@/api/contract'
   import DetailsInfo from './components/DetailsInfo'
   import DetailsProduct from './components/DetailsProduct'
+  import DetailRenew from './components/DetailsRenew.vue'
   import DetailsCollection from './components/DetailsCollection'
   import DetailsRecords from './components/DetailsRecords'
   import DetailsInvoice from './components/DetailsInvoice'
@@ -119,6 +127,7 @@
       DetailsShare,
       Edit,
       Transfer,
+      DetailRenew,
     },
     data() {
       return {
@@ -129,6 +138,7 @@
         activeName: 'details',
         dynamicsList: [],
         shares: [],
+        renewList: [],
       }
     },
     computed: {
@@ -142,6 +152,7 @@
       this.init()
       this.getShares()
       this.getRecord()
+      this.getRenew()
     },
     methods: {
       async init() {
@@ -162,6 +173,15 @@
           this.shares = list
         }
       },
+      // 获取续签合同
+      async getRenew() {
+        const [err, res] = await to(contractApi.renewList({ contractId: this.id }))
+        if (err) return
+        if (res.data) {
+          let list = res.data
+          this.renewList = list
+        }
+      },
       scale(numer1, numer2) {
         if (numer2 == 0) {
           return '-'
@@ -176,10 +196,18 @@
           const keys = Object.keys(obj).reverse()
           let records = {}
           for (const item of keys) {
+            if (obj[item].length > 0) {
+              obj[item].forEach((record) => {
+                if (record.opnContent) {
+                  record.opnContent = JSON.parse(record.opnContent)
+                }
+              })
+            }
             records[item] = obj[item]
           }
 
           this.dynamicsList = records
+          console.log('records========================================================', records)
         }
       },
       async handleEdit() {

+ 114 - 1
src/views/contract/index.vue

@@ -71,6 +71,7 @@
               type="daterange"
               value-format="yyyy-MM-dd" />
           </el-form-item>
+
           <el-form-item prop="contractSignTime">
             <el-date-picker
               v-model="queryForm.contractSignTime"
@@ -80,8 +81,48 @@
               type="daterange"
               value-format="yyyy-MM-dd" />
           </el-form-item>
+
+          <el-form-item prop="softwareMaintenanceBeginTime">
+            <el-date-picker
+              v-model="queryForm.softwareMaintenanceBeginTime"
+              end-placeholder="软件运维开始时间结束"
+              range-separator="至"
+              start-placeholder="软件运维开始时间开始"
+              type="daterange"
+              value-format="yyyy-MM-dd" />
+          </el-form-item>
+          <el-form-item prop="softwareMaintenanceEndTime">
+            <el-date-picker
+              v-model="queryForm.softwareMaintenanceEndTime"
+              end-placeholder="软件运维结束时间结束"
+              range-separator="至"
+              start-placeholder="软件运维结束时间开始"
+              type="daterange"
+              value-format="yyyy-MM-dd" />
+          </el-form-item>
+          <el-form-item prop="hardwareMaintenanceBeginTime">
+            <el-date-picker
+              v-model="queryForm.hardwareMaintenanceBeginTime"
+              end-placeholder="硬件运维开始时间结束"
+              range-separator="至"
+              start-placeholder="硬件运维开始时间开始"
+              type="daterange"
+              value-format="yyyy-MM-dd" />
+          </el-form-item>
+          <el-form-item prop="hardwareMaintenanceEndTime">
+            <el-date-picker
+              v-model="queryForm.hardwareMaintenanceEndTime"
+              end-placeholder="硬件运维结束时间结束"
+              range-separator="至"
+              start-placeholder="硬件运维结束时间开始"
+              type="daterange"
+              value-format="yyyy-MM-dd" />
+          </el-form-item>
           <el-form-item>
             <el-button icon="el-icon-search" type="primary" @click="queryData">查询</el-button>
+            <el-button v-permissions="['contract:manage:exportMaintenance']" type="primary" @click="exportMaintenance">
+              导出
+            </el-button>
           </el-form-item>
         </el-form>
       </vab-query-form-top-panel>
@@ -89,6 +130,13 @@
         <el-button v-permissions="['contract:manage:add']" icon="el-icon-plus" type="primary" @click="handleEdit()">
           新建
         </el-button>
+        <el-button
+          v-permissions="['contract:manage:add']"
+          icon="el-icon-plus"
+          type="primary"
+          @click="handleRenewEdit()">
+          续签
+        </el-button>
         <el-button
           v-permissions="['contract:manage:transfer']"
           icon="el-icon-refresh"
@@ -218,6 +266,7 @@
       @size-change="handleSizeChange" />
     <!-- 新增编辑客户弹窗 -->
     <Edit ref="edit" @contractSave="contractSave" />
+    <RenewEdit ref="renewEdit" @contractSave="contractSave" />
     <!-- 转移合同 -->
     <Transfer ref="transfer" :contract-id="contractId" @transferSave="contractSave" />
     <ApplyContract ref="applyContract" @refresh="queryData" />
@@ -228,10 +277,12 @@
   import to from 'await-to-js'
   import contractApi from '@/api/contract'
   import Edit from './components/Edit'
+  import RenewEdit from './components/RenewEdit'
   import TableTool from '@/components/table/TableTool'
   import Transfer from './components/Transfer'
   import ApplyContract from './components/ApplyContract'
   import api from '@/api/customer'
+  import downloadFileByByte from '@/utils/base64ToFile'
 
   export default {
     name: 'Contract',
@@ -240,6 +291,7 @@
       TableTool,
       Transfer,
       ApplyContract,
+      RenewEdit,
     },
     data() {
       return {
@@ -272,6 +324,10 @@
           custCity: null,
           filterDate: [],
           contractSignTime: [],
+          softwareMaintenanceBeginTime: [],
+          softwareMaintenanceEndTime: [],
+          hardwareMaintenanceBeginTime: [],
+          hardwareMaintenanceEndTime: [],
         },
         provinceOptions: [],
         selectRows: [], //选择的表格数据
@@ -445,6 +501,22 @@
           params.contractSignTimeStart = this.queryForm.contractSignTime[0]
           params.contractSignTimeEnd = this.queryForm.contractSignTime[1]
         }
+        if (this.queryForm.softwareMaintenanceBeginTime && this.queryForm.softwareMaintenanceBeginTime.length === 2) {
+          params.softwareMaintenanceBeginTimeStart = this.queryForm.softwareMaintenanceBeginTime[0]
+          params.softwareMaintenanceBeginTimeEnd = this.queryForm.softwareMaintenanceBeginTime[1]
+        }
+        if (this.queryForm.softwareMaintenanceEndTime && this.queryForm.softwareMaintenanceEndTime.length === 2) {
+          params.softwareMaintenanceEndTimeStart = this.queryForm.softwareMaintenanceEndTime[0]
+          params.softwareMaintenanceEndTimeEnd = this.queryForm.softwareMaintenanceEndTime[1]
+        }
+        if (this.queryForm.hardwareMaintenanceBeginTime && this.queryForm.hardwareMaintenanceBeginTime.length === 2) {
+          params.hardwareMaintenanceBeginTimeStart = this.queryForm.hardwareMaintenanceBeginTime[0]
+          params.hardwareMaintenanceBeginTimeEnd = this.queryForm.hardwareMaintenanceBeginTime[1]
+        }
+        if (this.queryForm.hardwareMaintenanceEndTime && this.queryForm.hardwareMaintenanceEndTime.length === 2) {
+          params.hardwareMaintenanceEndTimeStart = this.queryForm.hardwareMaintenanceEndTime[0]
+          params.hardwareMaintenanceEndTimeEnd = this.queryForm.hardwareMaintenanceEndTime[1]
+        }
         const [err, res] = await to(contractApi.getList(params))
         if (err) return (this.listLoading = false)
         this.list = res.data.list || []
@@ -452,6 +524,39 @@
         this.listLoading = false
         this.$nextTick(() => this.$refs.table.doLayout())
       },
+      async exportMaintenance() {
+        const params = { ...this.queryForm }
+        params.custProvinceId = params.custProvince ? params.custProvince.id : 0
+        params.custCityId = params.custCity ? params.custCity.id : 0
+        if (this.queryForm.filterDate && this.queryForm.filterDate.length === 2) {
+          params.contractEndTimeStart = this.queryForm.filterDate[0]
+          params.contractEndTimeEnd = this.queryForm.filterDate[1]
+        }
+        if (this.queryForm.contractSignTime && this.queryForm.contractSignTime.length === 2) {
+          params.contractSignTimeStart = this.queryForm.contractSignTime[0]
+          params.contractSignTimeEnd = this.queryForm.contractSignTime[1]
+        }
+        if (this.queryForm.softwareMaintenanceBeginTime && this.queryForm.softwareMaintenanceBeginTime.length === 2) {
+          params.softwareMaintenanceBeginTimeStart = this.queryForm.softwareMaintenanceBeginTime[0]
+          params.softwareMaintenanceBeginTimeEnd = this.queryForm.softwareMaintenanceBeginTime[1]
+        }
+        if (this.queryForm.softwareMaintenanceEndTime && this.queryForm.softwareMaintenanceEndTime.length === 2) {
+          params.softwareMaintenanceEndTimeStart = this.queryForm.softwareMaintenanceEndTime[0]
+          params.softwareMaintenanceEndTimeEnd = this.queryForm.softwareMaintenanceEndTime[1]
+        }
+        if (this.queryForm.hardwareMaintenanceBeginTime && this.queryForm.hardwareMaintenanceBeginTime.length === 2) {
+          params.hardwareMaintenanceBeginTimeStart = this.queryForm.hardwareMaintenanceBeginTime[0]
+          params.hardwareMaintenanceBeginTimeEnd = this.queryForm.hardwareMaintenanceBeginTime[1]
+        }
+        if (this.queryForm.hardwareMaintenanceEndTime && this.queryForm.hardwareMaintenanceEndTime.length === 2) {
+          params.hardwareMaintenanceEndTimeStart = this.queryForm.hardwareMaintenanceEndTime[0]
+          params.hardwareMaintenanceEndTimeEnd = this.queryForm.hardwareMaintenanceEndTime[1]
+        }
+        const [err, res] = await to(contractApi.exportMaintenance(params))
+        if (err) return (this.listLoading = false)
+        downloadFileByByte(res.data, '项目运维信息.xlsx')
+        // window.open(res.data)
+      },
       handleApply(row) {
         this.$refs.applyContract.form.id = row.id
         this.$refs.applyContract.openDialog(row)
@@ -480,7 +585,15 @@
       },
       // 客户编辑
       async handleEdit(row = null) {
-        row ? this.$refs.edit.init(row.id) : this.$refs.edit.init()
+        if (row && row.contractId) {
+          this.$refs.renewEdit.init(row.id)
+        } else {
+          row ? this.$refs.edit.init(row.id) : this.$refs.edit.init()
+        }
+      },
+      // 客户编辑
+      async handleRenewEdit() {
+        this.$refs.renewEdit.init()
       },
       // 合同详情
       handleContractDetail(row) {

+ 6 - 0
src/views/customer/components/FollowDetail.vue

@@ -42,6 +42,12 @@
       <el-descriptions-item label="联系人">
         {{ form.contactsName }}
       </el-descriptions-item>
+      <el-descriptions-item label="渠道">
+        {{ form.distName }}
+      </el-descriptions-item>
+      <el-descriptions-item label="协访人员">
+        {{ form.visitorName }}
+      </el-descriptions-item>
       <el-descriptions-item label="相关附件">
         <a v-for="item in form.files" :key="item.id" :href="item.fileUrl">
           {{ item.fileName }}

+ 36 - 3
src/views/customer/detail.vue

@@ -140,6 +140,8 @@
                           </span>
                         </p>
                         <p style="white-space: pre-wrap">{{ item.followContent }}</p>
+                        <p v-if="item.distName" style="white-space: pre-wrap">渠道: {{ item.distName }}</p>
+                        <p v-if="item.visitorName" style="white-space: pre-wrap">协访人员:{{ item.visitorName }}</p>
                         <div class="footer">
                           <p>
                             来自客户:
@@ -158,13 +160,30 @@
                     </div>
                     <transition name="height">
                       <ul class="comments">
-                        <li v-for="comment in item.comments" :key="comment.id">
+                        <li v-for="comment in item.comments" :key="comment.id" class="flex-wrap">
                           <vab-icon class="user-avatar" icon="account-circle-fill" />
                           <div class="text">
                             <p>{{ comment.createdName }}</p>
                             <p>{{ comment.content }}</p>
-                            <p>{{ comment.createdTime }}</p>
+                            <p>
+                              {{ comment.createdTime }}
+                              <span v-if="!comment.replyComments" @click="postReply(comment)">回复</span>
+                            </p>
                           </div>
+                          <transition v-if="comment.replyComments" name="height">
+                            <ul class="comments" style="width: 100%">
+                              <li v-for="replyComments in comment.replyComments" :key="replyComments.id">
+                                <vab-icon class="user-avatar" icon="account-circle-fill" />
+                                <div class="text">
+                                  <p>{{ replyComments.createdName }}</p>
+                                  <p>{{ replyComments.content }}</p>
+                                  <p>
+                                    {{ replyComments.createdTime }}
+                                  </p>
+                                </div>
+                              </li>
+                            </ul>
+                          </transition>
                         </li>
                       </ul>
                     </transition>
@@ -853,6 +872,14 @@
           })
           .catch(() => {})
       },
+      // 发表回复
+      postReply(row) {
+        this.$PostComment({ form: row, visible: true, reply: true })
+          .then(() => {
+            this.handleClick({ name: 'follow' })
+          })
+          .catch(() => {})
+      },
       // 展开评论
       showComment(row) {
         if (!row.comments.length) return this.$message.warning('暂无评论')
@@ -1098,7 +1125,7 @@
             li {
               display: flex;
               border-top: 1px solid #e3e5e7;
-
+              padding-top: 10px;
               .text {
                 flex: 1;
                 padding: 0 10px;
@@ -1118,6 +1145,9 @@
                   font-size: 12px;
                   color: #9499a0;
                   text-align: right;
+                  span {
+                    cursor: pointer;
+                  }
                 }
               }
             }
@@ -1190,4 +1220,7 @@
   .height-enter, .height-leave-to /* .fade-leave-active below version 2.1.8 */ {
     height: 0;
   }
+  .flex-wrap {
+    flex-wrap: wrap;
+  }
 </style>

+ 39 - 4
src/views/customer/follow.vue

@@ -93,7 +93,7 @@
                 </p>
                 <div class="footer">
                   <p>
-                    来自{{ selectDictLabel(targetTypeOptions, item.targetType) }}:
+                    来自{{ selectDictLabel(targetTypeOptions, item.targetType) }}:111
                     <span @click="jumpTo(item)">{{ item.targetName }}</span>
                   </p>
                   <div>
@@ -114,12 +114,25 @@
       </ul>
       <div class="comment">
         <ul>
-          <li v-for="item in comments" :key="item.id">
+          <li v-for="item in comments" :key="item.id" class="flex-wrap">
             <vab-icon class="user-avatar" icon="account-circle-fill" />
             <div class="text">
               <p>{{ item.createdName }}</p>
               <p>{{ item.content }}</p>
-              <p>{{ item.createdTime }}</p>
+              <p>
+                {{ item.createdTime }}
+                <span v-if="!item.replyComments" @click="postReply(item)">回复</span>
+              </p>
+            </div>
+            <div v-for="reply in item.replyComments" :key="reply.id" class="reply-box">
+              <vab-icon class="user-avatar" icon="account-circle-fill" />
+              <div class="text">
+                <p>{{ reply.createdName }}</p>
+                <p>{{ reply.content }}</p>
+                <p>
+                  {{ reply.createdTime }}
+                </p>
+              </div>
             </div>
           </li>
         </ul>
@@ -291,7 +304,7 @@
       },
       async showComment(id) {
         this.followId = id
-        const [err, res] = await to(api.getComment({ followId: id + '' }))
+        const [err, res] = await to(api.getComment({ followId: id + '', pid: '0' }))
         if (err) return
         this.comments = res.data.list || []
       },
@@ -320,6 +333,16 @@
         this.comment = ''
         this.fetchData()
       },
+      // 发表回复
+      postReply(row) {
+        this.$PostComment({ form: row, visible: true, reply: true })
+          .then(() => {
+            // this.handleClick({ name: 'follow' })
+            this.showComment(this.followId)
+            this.fetchData()
+          })
+          .catch(() => {})
+      },
       addFollowUp() {
         let params = {
           followType: '20',
@@ -386,6 +409,10 @@
               font-size: 12px;
               color: #9499a0;
               text-align: right;
+              span {
+                cursor: pointer;
+                color: #1d66dc;
+              }
             }
           }
         }
@@ -503,4 +530,12 @@
       }
     }
   }
+  .flex-wrap {
+    flex-wrap: wrap;
+  }
+  .reply-box {
+    padding-left: 30px;
+    width: 100%;
+    display: flex;
+  }
 </style>

+ 34 - 2
src/views/customer/inviteTenders/details.vue

@@ -95,13 +95,30 @@
                     </div>
                     <transition name="height">
                       <ul class="comments">
-                        <li v-for="comment in item.comments" :key="comment.id">
+                        <li v-for="comment in item.comments" :key="comment.id" class="flex-wrap">
                           <vab-icon class="user-avatar" icon="account-circle-fill" />
                           <div class="text">
                             <p>{{ comment.createdName }}</p>
                             <p>{{ comment.content }}</p>
-                            <p>{{ comment.createdTime }}</p>
+                            <p>
+                              {{ comment.createdTime }}
+                              <span v-if="!comment.replyComments" @click="postReply(comment)">回复</span>
+                            </p>
                           </div>
+                          <transition v-if="comment.replyComments" name="height">
+                            <ul class="comments" style="width: 100%">
+                              <li v-for="replyComments in comment.replyComments" :key="replyComments.id">
+                                <vab-icon class="user-avatar" icon="account-circle-fill" />
+                                <div class="text">
+                                  <p>{{ replyComments.createdName }}</p>
+                                  <p>{{ replyComments.content }}</p>
+                                  <p>
+                                    {{ replyComments.createdTime }}
+                                  </p>
+                                </div>
+                              </li>
+                            </ul>
+                          </transition>
                         </li>
                       </ul>
                     </transition>
@@ -178,6 +195,14 @@
           })
           .catch(() => {})
       },
+      // 发表回复
+      postReply(row) {
+        this.$PostComment({ form: row, visible: true, reply: true })
+          .then(() => {
+            this.handleClick({ name: 'follow' })
+          })
+          .catch(() => {})
+      },
       getOptions() {
         this.getDicts('bid_info_type').then((response) => {
           this.bidInfoTypeOptions = {}
@@ -455,6 +480,7 @@
             overflow: auto;
 
             li {
+              padding-top: 10px;
               display: flex;
               border-top: 1px solid #e3e5e7;
 
@@ -477,6 +503,9 @@
                   font-size: 12px;
                   color: #9499a0;
                   text-align: right;
+                  span {
+                    cursor: pointer;
+                  }
                 }
               }
             }
@@ -549,4 +578,7 @@
   .height-enter, .height-leave-to /* .fade-leave-active below version 2.1.8 */ {
     height: 0;
   }
+  .flex-wrap {
+    flex-wrap: wrap;
+  }
 </style>

+ 7 - 0
src/views/customer/list.vue

@@ -320,6 +320,13 @@
             sortable: false,
             disableCheck: false,
           },
+          {
+            label: '备注',
+            width: '120px',
+            prop: 'remark',
+            sortable: false,
+            disableCheck: false,
+          },
         ],
       }
     },

+ 7 - 0
src/views/customer/openSea.vue

@@ -357,6 +357,13 @@
             sortable: false,
             disableCheck: false,
           },
+          {
+            label: '备注',
+            width: '120px',
+            prop: 'remark',
+            sortable: false,
+            disableCheck: false,
+          },
         ],
         fileList: [],
         fileSettings: {

+ 59 - 1
src/views/index/index.vue

@@ -191,6 +191,39 @@
         </el-timeline>
         <div class="timelineEmpty">暂无日程</div>
       </el-card> -->
+      <el-card v-if="roleKeys.includes('GeneralManager') || roleKeys.includes('SalesDirector')" class="notice">
+        <div slot="header" class="card-title">
+          <span>评论回复</span>
+          <div class="buttons">
+            <el-button size="mini" @click="handleFollow">
+              更多
+              <i class="el-icon-arrow-right el-icon--right"></i>
+            </el-button>
+          </div>
+        </div>
+        <el-table border :data="commentList" height="100%" style="width: 100%">
+          <el-table-column
+            align="center"
+            label="项目/客户/渠道名称"
+            prop="targetName"
+            show-overflow-tooltip
+            width="160" />
+          <el-table-column align="center" label="评论人" prop="createdName" width="120" />
+          <el-table-column align="center" label="评论内容" prop="content" show-overflow-tooltip width="220" />
+          <el-table-column align="center" label="评论时间" prop="createdTime" width="160" />
+          <el-table-column align="center" label="回复人" width="120">
+            <template #default="{ row }">{{ row.replyComments ? row.replyComments[0].createdName : '-' }}</template>
+          </el-table-column>
+          <el-table-column align="center" label="回复内容" show-overflow-tooltip width="220">
+            <template #default="{ row }">{{ row.replyComments ? row.replyComments[0].content : '-' }}</template>
+          </el-table-column>
+          <el-table-column align="center" label="回复时间" width="160">
+            <template #default="{ row }">
+              {{ row.replyComments ? parseTime(row.replyComments[0].createdTime, '{y}-{m}-{d} {h}:{i}') : '-' }}
+            </template>
+          </el-table-column>
+        </el-table>
+      </el-card>
       <el-card class="notice">
         <div slot="header" class="card-title">
           <span>督办管理</span>
@@ -220,7 +253,7 @@
           </li>
         </ul>
       </el-card>
-      <el-card class="notice">
+      <el-card v-if="!roleKeys.includes('GeneralManager') && !roleKeys.includes('SalesDirector')" class="notice">
         <div slot="header" class="card-title">
           <span>公告</span>
           <div class="buttons">
@@ -289,6 +322,7 @@
 </template>
 
 <script>
+  import { mapGetters } from 'vuex'
   import NoticeDetails from '@/views/system/notice/details.vue'
   import * as echarts from 'echarts'
   import VueDragger from 'vuedraggable'
@@ -297,6 +331,8 @@
   import indexApi from '@/api/index'
   import messageApi from '@/api/system/message'
   import taskApi from '@/api/plat/task'
+  import followApi from '@/api/customer/follow'
+
   export default {
     name: 'Index',
     components: {
@@ -401,9 +437,19 @@
         statisticsForm: {
           toDoNumber: 0,
         },
+        commentList: [], //评论列表
       }
     },
+    computed: {
+      ...mapGetters({
+        roleKeys: 'user/roleKeys',
+      }),
+    },
     mounted() {
+      // GeneralManager 总经理
+      // SalesDirector 销售总监
+      console.log('roleKeys', this.roleKeys)
+      this.getCommentList()
       this.init()
       this.getDateList()
       this.getOptions()
@@ -420,6 +466,13 @@
       window.removeEventListener('resize', this.handleResize)
     },
     methods: {
+      // 获取首页评论信息
+      async getCommentList() {
+        if (!this.roleKeys.includes('GeneralManager') && !this.roleKeys.includes('SalesDirector')) return
+        const [err, res] = await to(followApi.getHomeCommentList({}))
+        if (err) return
+        this.commentList = res.data.list
+      },
       async getHomeNumReportData() {
         let ids = this.privateBoard.map((item) => {
           return item.id
@@ -1039,6 +1092,11 @@
           name: 'NoticeHistory',
         })
       },
+      handleFollow() {
+        this.$router.push({
+          name: 'CustomerFollow',
+        })
+      },
       handleSupervision() {
         this.$router.push({
           name: 'Task',

+ 2 - 2
src/views/proj/business/components/BusinessAdd.vue

@@ -77,7 +77,7 @@
           </el-form-item>
         </el-col>
         <el-col :span="8">
-          <el-form-item label="是否项目" prop="isBig">
+          <el-form-item label="是否战略项目" prop="isBig">
             <el-select v-model="form.isBig" placeholder="请选择" style="width: 100%">
               <el-option v-for="item in yesOrNoOptions" :key="item.key" :label="item.value" :value="item.key" />
             </el-select>
@@ -178,7 +178,7 @@
           distributorName: [
             { validator: validateDistributor, trigger: ['blur', 'change'], message: '请选择经销商/代理商' },
           ],
-          isBig: [{ required: true, trigger: ['blur', 'change'], message: '请选择是否项目' }],
+          isBig: [{ required: true, trigger: ['blur', 'change'], message: '请选择是否战略项目' }],
           nboType: [{ required: true, trigger: ['blur', 'change'], message: '请选择项目类别' }],
         },
         title: '',

+ 2 - 2
src/views/proj/business/components/BusinessEdit.vue

@@ -41,7 +41,7 @@
           </el-form-item>
         </el-col>
         <el-col :span="8">
-          <el-form-item label="是否项目" prop="isBig">
+          <el-form-item label="是否战略项目" prop="isBig">
             <el-select v-model="form.isBig" placeholder="请选择" style="width: 100%">
               <el-option v-for="item in yesOrNoOptions" :key="item.key" :label="item.value" :value="item.key" />
             </el-select>
@@ -350,7 +350,7 @@
           // distributorName: [
           //   { validator: validateDistributor, trigger: ['blur', 'change'], message: '请选择经销商/代理商' },
           // ],
-          // isBig: [{ required: true, trigger: ['blur', 'change'], message: '请选择是否项目' }],
+          // isBig: [{ required: true, trigger: ['blur', 'change'], message: '请选择是否战略项目' }],
           //
           // nboType: [{ required: true, trigger: ['blur', 'change'], message: '请选择项目类别' }],
           // // C => B

+ 6 - 4
src/views/proj/business/components/BusinessGradation.vue

@@ -375,10 +375,10 @@
         fileSettings: {
           // 文件配置信息
           fileSize: 52428800,
-          fileTypes: '.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.txt',
+          fileTypes: '.pdf,.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.txt',
           pictureSize: 52428800,
           pictureTypes: '.jpg,.jpeg,.gif,.png,.jfif,.txt',
-          types: '.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.mp4,.txt',
+          types: '.pdf,.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.mp4,.txt',
           videoSize: 104857600,
           videoType: '.mp4',
         },
@@ -477,7 +477,8 @@
           this.form.contactId = val[0].id
           this.form.contactName = val.map((item) => item.cuctName).join()
           this.form.contactPostion = val.map((item) => item.postion).join()
-          this.form.contactTelephone = val.map((item) => {
+          this.form.contactTelephone = val
+            .map((item) => {
               if (item.telephone !== '' && item.wechat !== '') {
                 return item.telephone + '/' + item.wechat
               }
@@ -487,7 +488,8 @@
               if (item.wechat !== '') {
                 return item.wechat
               }
-            }).join()
+            })
+            .join()
         }
       },
       handleSelectDistributorContact() {

+ 2 - 2
src/views/proj/business/components/DetailsEnclosure.vue

@@ -95,10 +95,10 @@
         fileSettings: {
           // 文件配置信息
           fileSize: 52428800,
-          fileTypes: '.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.txt,.pdf',
+          fileTypes: '.pdf,.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.txt',
           pictureSize: 52428800,
           pictureTypes: '.jpg,.jpeg,.gif,.png,.jfif,.txt',
-          types: '.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.mp4,.txt,.pdf',
+          types: '.pdf,.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.mp4,.txt',
           videoSize: 104857600,
           videoType: '.mp4',
         },

+ 34 - 2
src/views/proj/business/components/DetailsFollow.vue

@@ -44,13 +44,30 @@
             </div>
             <transition name="height">
               <ul class="comments">
-                <li v-for="comment in item.comments" :key="comment.id">
+                <li v-for="comment in item.comments" :key="comment.id" class="flex-wrap">
                   <vab-icon class="user-avatar" icon="account-circle-fill" />
                   <div class="text">
                     <p>{{ comment.createdName }}</p>
                     <p>{{ comment.content }}</p>
-                    <p>{{ comment.createdTime }}</p>
+                    <p>
+                      {{ comment.createdTime }}
+                      <span v-if="!comment.replyComments" @click="postReply(comment)">回复</span>
+                    </p>
                   </div>
+                  <transition v-if="comment.replyComments" name="height">
+                    <ul class="comments" style="width: 100%">
+                      <li v-for="replyComments in comment.replyComments" :key="replyComments.id">
+                        <vab-icon class="user-avatar" icon="account-circle-fill" />
+                        <div class="text">
+                          <p>{{ replyComments.createdName }}</p>
+                          <p>{{ replyComments.content }}</p>
+                          <p>
+                            {{ replyComments.createdTime }}
+                          </p>
+                        </div>
+                      </li>
+                    </ul>
+                  </transition>
                 </li>
               </ul>
             </transition>
@@ -108,6 +125,14 @@
           })
           .catch(() => {})
       },
+      // 发表回复
+      postReply(row) {
+        this.$PostComment({ form: row, visible: true, reply: true })
+          .then(() => {
+            this.fetchData()
+          })
+          .catch(() => {})
+      },
       // 展开评论
       showComment(row) {
         if (!row.comments.length) return this.$message.warning('暂无评论')
@@ -182,6 +207,7 @@
           overflow: auto;
 
           li {
+            padding-top: 10px;
             display: flex;
             border-top: 1px solid #e3e5e7;
 
@@ -204,6 +230,9 @@
                 font-size: 12px;
                 color: #9499a0;
                 text-align: right;
+                span {
+                  cursor: pointer;
+                }
               }
             }
           }
@@ -261,4 +290,7 @@
     font-size: 12px;
     color: rgba(0, 0, 0, 0.65);
   }
+  .flex-wrap {
+    flex-wrap: wrap;
+  }
 </style>

+ 10 - 0
src/views/proj/business/components/DetailsRecords.vue

@@ -36,6 +36,14 @@
                   }}
                 </span>
               </p>
+              <p v-if="item.opnType === '60'">
+                销售:
+                <span>{{ item.opnContent.origSaleName + ' => ' + item.opnContent.saleName }}</span>
+              </p>
+              <p v-if="item.opnType === '62'">
+                战略工程师:
+                <span>{{ item.opnContent.origStrategicSaleName + ' => ' + item.opnContent.strategicSaleName }}</span>
+              </p>
               <p v-if="['11', '41', '51', '61', '81'].includes(item.opnType)">
                 审批结果:
                 <span>{{ selectDictLabel(approStatusOptions, item.opnContent.approStatus) }}</span>
@@ -109,6 +117,8 @@
           return '项目转移'
         } else if (opnType === '61') {
           return '项目转移审批'
+        } else if (opnType === '62') {
+          return '修改战略项目归属人员  '
         } else if (opnType === '70') {
           return '关联了联系人'
         } else if (opnType === '71') {

+ 36 - 2
src/views/proj/business/components/FollowAdd.vue

@@ -76,6 +76,23 @@
             </el-upload>
           </el-form-item>
         </el-col>
+      </el-row>
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="渠道" prop="distName">
+            <el-input
+              v-model="form.distName"
+              placeholder="请选择渠道"
+              readonly
+              suffix-icon="el-icon-search"
+              @focus="openDistributor" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="协访人员" prop="visitorName">
+            <el-input v-model="form.visitorName" placeholder="请输入协访人员" />
+          </el-form-item>
+        </el-col>
         <!--        <el-col :span="12">-->
         <!--          <el-form-item label="提醒对象" prop="reminders">-->
         <!--            <el-input v-model="form.reminders" />-->
@@ -107,6 +124,7 @@
       :query-params="queryContact"
       @save="selectContact" />
 
+    <SelectDist ref="selectDist" :multiple="false" @save="getDistributor" />
     <select-distributor
       ref="selectDistributor"
       :query-params="queryContact"
@@ -122,6 +140,7 @@
   import to from 'await-to-js'
   import followApi from '@/api/customer/follow'
   import SelectDistributor from '@/components/select/SelectDistributorContact'
+  import SelectDist from '@/components/select/SelectDistributor'
   import SelectUser from '@/components/select/SelectUser'
   import asyncUploadFile from '@/utils/uploadajax'
   import SelectCustomerContact from '@/components/select/SelectCustomerContact'
@@ -134,6 +153,7 @@
       SelectBusinessContact,
       SelectDistributor,
       SelectUser,
+      SelectDist,
     },
     data() {
       return {
@@ -159,6 +179,9 @@
           nextTime: '',
           remark: '',
           supportName: '',
+          distId: null,
+          distName: '',
+          visitorName: '',
           files: [],
         },
         rules: {
@@ -184,10 +207,10 @@
         fileSettings: {
           // 文件配置信息
           fileSize: 52428800,
-          fileTypes: '.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.txt',
+          fileTypes: '.pdf,.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.txt',
           pictureSize: 52428800,
           pictureTypes: '.jpg,.jpeg,.gif,.png,.jfif,.txt',
-          types: '.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.mp4,.txt',
+          types: '.pdf,.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.mp4,.txt',
           videoSize: 104857600,
           videoType: '.mp4',
         },
@@ -303,6 +326,17 @@
           }
         })
       },
+      // 打开选择经销商
+      openDistributor() {
+        this.$refs.selectDist.open()
+      },
+      // 关闭经销商获取经销商信息
+      getDistributor(data) {
+        let distributor = data[0] || null
+        if (!distributor) return
+        this.form.distName = distributor.distName
+        this.form.distId = distributor.id
+      },
 
       // 上传图片
       beforeAvatarUpload(file) {

+ 1 - 1
src/views/proj/business/components/Transfer.vue

@@ -26,7 +26,7 @@
     <!-- 选择负责人弹窗 -->
     <select-user
       ref="selectUser"
-      :query-params="{ roles: ['SalesEngineer', 'ProductLineManager', 'SalesDirector'] }"
+      :query-params="{ roles: ['SalesEngineer', 'ProductLineManager', 'SalesDirector', 'StrategicProjectTeam'] }"
       @save="selectUser" />
   </el-dialog>
 </template>

+ 101 - 0
src/views/proj/business/components/TransferStrategic.vue

@@ -0,0 +1,101 @@
+<template>
+  <el-dialog :title="title" :visible.sync="dialogFormVisible" width="500px" @close="close">
+    <el-form ref="form" label-width="80px" :model="form" :rules="rules">
+      <el-form-item label="接收对象" prop="userName">
+        <el-input
+          v-model="form.userName"
+          placeholder="选择人员"
+          readonly
+          suffix-icon="el-icon-search"
+          @focus="handleSelectUser" />
+      </el-form-item>
+      <el-form-item label="转移原因" prop="remark">
+        <el-input
+          v-model="form.remark"
+          maxlength="300"
+          placeholder="请输入转移原因"
+          rows="5"
+          show-word-limit
+          type="textarea" />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button @click="close">取 消</el-button>
+      <el-button :loading="loading" type="primary" @click="save">确 定</el-button>
+    </template>
+    <!-- 选择负责人弹窗 -->
+    <select-user
+      ref="selectUser"
+      :query-params="{
+        roles: ['GeneralManager', 'SalesDirector', 'StrategicProjectTeamLeader', 'StrategicProjectTeam'],
+      }"
+      @save="selectUser" />
+  </el-dialog>
+</template>
+
+<script>
+  import to from 'await-to-js'
+  import businessApi from '@/api/proj/business'
+  import SelectUser from '@/components/select/SelectUser'
+
+  export default {
+    name: 'TransferStrategic',
+    components: { SelectUser },
+    data() {
+      return {
+        title: '转移战略项目归属',
+        dialogFormVisible: false,
+        loading: false,
+        form: {
+          id: undefined,
+          ids: undefined,
+          userId: undefined,
+          userName: undefined,
+          remark: undefined,
+        },
+        rules: {
+          userName: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+          remark: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+        },
+      }
+    },
+    methods: {
+      handleSelectUser() {
+        this.$refs.selectUser.open()
+      },
+      selectUser(val) {
+        if (val && val.length > 0) {
+          this.form.userId = val[0].id
+        }
+        this.form.userName = val.map((item) => item.nickName).join()
+      },
+      open(row) {
+        this.form.id = row.id
+        this.dialogFormVisible = true
+        this.loading = false
+      },
+      close() {
+        this.$refs['form'].resetFields()
+        this.form = this.$options.data().form
+        this.dialogFormVisible = false
+        this.loading = false
+      },
+      save() {
+        this.$refs['form'].validate(async (valid) => {
+          if (valid) {
+            this.loading = true
+            let [err, res] = await to(businessApi.strategicBusinessTransfer(this.form))
+            if (err) {
+              this.$baseMessage(res.msg, 'error')
+            } else {
+              this.$baseMessage(res.msg, 'success')
+            }
+            this.loading = false
+            this.$emit('fetch-data')
+            this.close()
+          }
+        })
+      },
+    },
+  }
+</script>

+ 16 - 5
src/views/proj/business/index.vue

@@ -326,9 +326,9 @@
             width: '120px',
           },
           {
-            label: '项目',
+            label: '战略项目',
             prop: 'isBig',
-            width: '70px',
+            width: '80px',
           },
           {
             label: '最新跟进时间',
@@ -360,6 +360,13 @@
             prop: 'projConversionReason',
             width: '140px',
           },
+          {
+            label: '备注',
+            width: '120px',
+            prop: 'remark',
+            sortable: false,
+            disableCheck: false,
+          },
         ],
         allColumns: [
           '项目编码',
@@ -376,8 +383,9 @@
           '项目类别',
           '项目预算',
           '出货金额',
-          '项目',
+          '战略项目',
           '最新跟进时间',
+          '科室',
         ],
         abcColumns: [
           '项目编码',
@@ -393,9 +401,10 @@
           '产品线',
           '项目预算',
           '出货金额',
-          '项目',
+          '战略项目',
           '最新跟进时间',
           '最新跟进',
+          '科室',
         ],
         dealColumns: [
           '项目编码',
@@ -409,9 +418,10 @@
           '销售模式',
           '经销商/代理商',
           '产品线',
-          '项目',
+          '战略项目',
           '项目成交时间',
           '合同金额',
+          '科室',
         ],
         reserveColumns: [
           '项目编码',
@@ -427,6 +437,7 @@
           '产品线',
           '项目转化时间',
           '转化原因',
+          '科室',
         ],
         list: [],
         listLoading: true,

+ 673 - 0
src/views/proj/business/strategic_index.vue

@@ -0,0 +1,673 @@
+<template>
+  <div class="business-container">
+    <el-tabs v-model="activeName" @tab-click="handleTabClick">
+      <el-tab-pane label="全部项目" name="all" />
+      <el-tab-pane label="A类项目" name="10" />
+      <el-tab-pane label="B类项目" name="20" />
+      <el-tab-pane label="C类项目" name="30" />
+      <el-tab-pane label="储备项目" name="50" />
+      <el-tab-pane label="成交项目" name="40" />
+    </el-tabs>
+    <vab-query-form>
+      <vab-query-form-top-panel>
+        <el-form ref="queryForm" :inline="true" :model="queryForm" @submit.native.prevent>
+          <!--          <el-form-item prop="nboName">-->
+          <!--            <el-input v-model="queryForm.nboName" clearable placeholder="项目名称" @keyup.enter.native="queryData" />-->
+          <!--          </el-form-item>-->
+
+          <el-form-item prop="custName">
+            <el-input
+              v-model="queryForm.custName"
+              clearable
+              placeholder="客户名称"
+              size="small"
+              @keyup.enter.native="queryData" />
+          </el-form-item>
+
+          <el-form-item v-show="activeName === 'all'" prop="nboType">
+            <el-select v-model="queryForm.nboType" clearable placeholder="项目类别">
+              <el-option v-for="dict in nboTypeOptions" :key="dict.key" :label="dict.value" :value="dict.key" />
+            </el-select>
+          </el-form-item>
+          <el-form-item prop="saleName">
+            <el-input
+              v-model="queryForm.saleName"
+              clearable
+              placeholder="销售工程师"
+              size="small"
+              @keyup.enter.native="queryData" />
+          </el-form-item>
+          <el-form-item prop="productLine">
+            <el-select v-model="queryForm.productLine" clearable placeholder="产品线">
+              <el-option v-for="item in productLineOptions" :key="item.key" :label="item.value" :value="item.key" />
+            </el-select>
+          </el-form-item>
+          <el-form-item prop="nboSource">
+            <el-select v-model="queryForm.nboSource" clearable placeholder="项目来源">
+              <el-option v-for="dict in nboSourceOptions" :key="dict.key" :label="dict.value" :value="dict.key" />
+            </el-select>
+          </el-form-item>
+          <el-form-item prop="distributorName">
+            <el-input
+              v-model="queryForm.distributorName"
+              clearable
+              placeholder="经销商/代理商"
+              size="small"
+              @keyup.enter.native="queryData" />
+          </el-form-item>
+          <el-form-item prop="custProvince">
+            <el-select v-model="queryForm.custProvince" clearable placeholder="所在省" value-key="id">
+              <el-option v-for="item in provinceOptions" :key="item.id" :label="item.distName" :value="item" />
+            </el-select>
+          </el-form-item>
+          <el-form-item prop="filingTime">
+            <el-date-picker
+              v-model="queryForm.filingTime"
+              end-placeholder="结束"
+              start-placeholder="备案日期开始"
+              type="daterange"
+              value-format="yyyy-MM-dd" />
+          </el-form-item>
+          <el-form-item>
+            <el-button icon="el-icon-search" type="primary" @click="queryData">查询</el-button>
+            <el-button icon="el-icon-refresh" type="primary" @click="resetQuery">重置</el-button>
+            <el-button v-permissions="['proj:business:add']" icon="el-icon-plus" type="primary" @click="handleEdit({})">
+              新增项目
+            </el-button>
+            <el-button
+              v-permissions="['proj:business:transfer']"
+              icon="el-icon-refresh"
+              type="primary"
+              @click="handleTransfer">
+              转移项目
+            </el-button>
+            <el-button
+              v-permissions="['proj:business:transfer_strategic']"
+              icon="el-icon-refresh"
+              type="primary"
+              @click="handleTransferStrategic">
+              转移战略归属
+            </el-button>
+            <el-button
+              v-permissions="['proj:business:sysadmin_transfer']"
+              icon="el-icon-refresh"
+              type="primary"
+              @click="handleSysAdminTransfer">
+              管理员转移项目
+            </el-button>
+          </el-form-item>
+        </el-form>
+      </vab-query-form-top-panel>
+
+      <vab-query-form-left-panel :span="12">
+        <!--        <el-button icon="el-icon-plus" type="primary" @click="handleEdit">创建任务</el-button>-->
+        <!--        <el-button icon="el-icon-plus" type="primary" @click="handleEdit">创建工单</el-button>-->
+        <!--        <el-button icon="el-icon-plus" type="primary" @click="handleEdit">创建合同</el-button>-->
+        <!--        <el-button icon="el-icon-delete" type="danger" @click="handleDelete">删除</el-button>-->
+      </vab-query-form-left-panel>
+      <vab-query-form-right-panel :span="12">
+        <table-tool
+          ref="tableTool"
+          :columns.sync="finallyColumns"
+          :show-columns.sync="showColumns"
+          :table-type.sync="tableType" />
+      </vab-query-form-right-panel>
+    </vab-query-form>
+
+    <el-table
+      :key="tableKey"
+      ref="busTable"
+      v-loading="listLoading"
+      :data="list"
+      :height="height"
+      @selection-change="setSelectRows">
+      <el-table-column align="center" show-overflow-tooltip type="selection" />
+      <el-table-column
+        v-for="(item, index) in showColumns"
+        :key="index"
+        align="center"
+        :label="item.label"
+        :min-width="item.minWidth"
+        :prop="item.prop"
+        show-overflow-tooltip
+        :sortable="item.sortable"
+        :width="item.width">
+        <template #default="{ row }">
+          <el-button
+            v-if="item.prop === 'nboCode' || item.prop === 'nboName'"
+            class="link-button"
+            type="text"
+            @click="handleDetail(row)">
+            {{ row[item.prop] }}
+          </el-button>
+          <span v-else-if="item.prop === 'nboType'">
+            {{ selectDictLabel(nboTypeOptions, row.nboType) }}
+          </span>
+          <span v-else-if="item.prop === 'nboSource'">
+            {{ selectDictLabel(nboSourceOptions, row.nboSource) }}
+          </span>
+          <span v-else-if="item.prop === 'salesModel'">
+            {{ selectDictLabel(salesModelOptions, row.salesModel) }}
+          </span>
+          <span v-else-if="item.prop === 'approStatus'">
+            {{ selectDictLabel(approStatusOptions, row.approStatus) }}
+          </span>
+          <span v-else-if="item.prop === 'nboStatus'">
+            {{ selectDictLabel(nboStatusOptions, row.nboStatus) }}
+          </span>
+          <span v-else-if="item.prop === 'nboPhase'">
+            {{ selectDictLabel(nboPhaseOptions, row.nboPhase) }}
+          </span>
+          <span v-else-if="item.prop === 'productLine'">
+            {{ selectDictLabel(productLineOptions, row.productLine) }}
+          </span>
+          <span v-else-if="item.prop === 'isBig'">
+            {{ selectDictLabel(yesOrNoOptions, row.isBig) }}
+          </span>
+          <span v-else-if="item.prop === 'estTransPrice'">
+            {{ formatPrice(row.estTransPrice) }}
+          </span>
+          <span v-else-if="item.prop === 'nboBudget'">
+            {{ formatPrice(row.nboBudget) }}
+          </span>
+          <span v-else-if="item.prop === 'contractAmount'">
+            {{ formatPrice(row.contractAmount) }}
+          </span>
+          <span v-else-if="item.prop === 'filingTime'">
+            {{ parseTime(row.filingTime, '{y}-{m}-{d}') }}
+          </span>
+          <span v-else-if="item.prop === 'finalFollowTime'">
+            {{ parseTime(row.finalFollowTime, '{y}-{m}-{d}') }}
+          </span>
+          <span v-else-if="item.prop === 'nextFollowTime'">
+            {{ parseTime(row.nextFollowTime, '{y}-{m}-{d}') }}
+          </span>
+          <span v-else-if="item.prop === 'projClosingTime'">
+            {{ parseTime(row.projClosingTime, '{y}-{m}-{d}') }}
+          </span>
+          <span v-else-if="item.prop === 'projConversionTime'">
+            {{ parseTime(row.projConversionTime, '{y}-{m}-{d}') }}
+          </span>
+          <span v-else>{{ row[item.prop] }}</span>
+        </template>
+      </el-table-column>
+
+      <el-table-column align="center" fixed="right" label="操作" width="68">
+        <template #default="{ row }">
+          <!--          <el-button type="text" @click="handleFollow(row)">跟进</el-button>-->
+          <el-button v-permissions="['proj:business:edit']" type="text" @click="handleEdit(row)">编辑</el-button>
+          <!--                    <el-button type="text" @click="handleDelete(row)">删除</el-button>-->
+        </template>
+      </el-table-column>
+    </el-table>
+    <el-pagination
+      background
+      :current-page="queryForm.pageNum"
+      :layout="layout"
+      :page-size="queryForm.pageSize"
+      :total="total"
+      @current-change="handleCurrentChange"
+      @size-change="handleSizeChange" />
+    <!-- 添加 -->
+    <add ref="add" @fetch-data="fetchData" />
+    <!-- 编辑 -->
+    <edit ref="edit" @fetch-data="fetchData" />
+    <!-- 转移 -->
+    <transfer ref="transfer" @fetch-data="fetchData" />
+    <!-- 转移战略项目归属 -->
+    <transfer-strategic ref="transferStrategic" @fetch-data="fetchData" />
+    <!-- 添加跟进记录 -->
+    <follow-add ref="follow" @fetch-data="fetchData" />
+  </div>
+</template>
+
+<script>
+  import businessApi from '@/api/proj/business'
+  import Edit from './components/BusinessEdit'
+  import Add from './components/BusinessAdd'
+  import Transfer from './components/Transfer'
+  import TransferStrategic from './components/TransferStrategic'
+  import FollowAdd from './components/FollowAdd'
+  import TableTool from '@/components/table/TableTool'
+  import customerApi from '@/api/customer'
+
+  export default {
+    name: 'StrategicBusiness',
+    components: { Edit, Transfer, TransferStrategic, TableTool, FollowAdd, Add },
+    data() {
+      return {
+        tableKey: 0,
+        activeName: '',
+        height: this.$baseTableHeight(4),
+        queryForm: {
+          pageNum: 1,
+          pageSize: 10,
+          nboName: undefined,
+          custName: undefined,
+          nboType: undefined,
+          saleName: undefined,
+          productLine: undefined,
+          nboSource: undefined,
+          distributorName: undefined,
+          custProvince: null,
+          filingTime: undefined,
+          beginTime: undefined,
+          endTime: undefined,
+        },
+        tableType: 'businessTable',
+        showColumns: [],
+        columns: [
+          {
+            label: '项目编码',
+            prop: 'nboCode',
+            width: '120px',
+            minWidth: '120px',
+            sortable: false,
+            disableCheck: true,
+          },
+          {
+            label: '项目名称',
+            prop: 'nboName',
+            width: '240px',
+            minWidth: '240px',
+            sortable: false,
+            disableCheck: true,
+          },
+          {
+            label: '所在省',
+            prop: 'custProvince',
+            width: '100px',
+          },
+          {
+            label: '所在市',
+            prop: 'custCity',
+            width: '100px',
+          },
+          {
+            label: '销售工程师',
+            prop: 'saleName',
+            width: '100px',
+          },
+          {
+            label: '战略工程师',
+            prop: 'strategicSaleName',
+            width: '100px',
+          },
+          {
+            label: '客户名称',
+            prop: 'custName',
+            width: 'auto',
+            minWidth: '240px',
+          },
+          {
+            label: '项目备案时间',
+            prop: 'filingTime',
+            width: '110px',
+          },
+          {
+            label: '项目来源',
+            prop: 'nboSource',
+            width: '140px',
+          },
+          {
+            label: '销售模式',
+            prop: 'salesModel',
+            width: '80px',
+          },
+          {
+            label: '经销商/代理商',
+            prop: 'distributorName',
+            minWidth: '240px',
+            width: 'auto',
+          },
+          {
+            label: '产品线',
+            prop: 'productLine',
+            width: '140px',
+          },
+          {
+            label: '项目类别',
+            prop: 'nboType',
+            width: '80px',
+          },
+          {
+            label: '项目预算',
+            prop: 'nboBudget',
+            width: '120px',
+          },
+          {
+            label: '出货金额',
+            prop: 'estTransPrice',
+            width: '120px',
+          },
+          {
+            label: '战略项目',
+            prop: 'isBig',
+            width: '80px',
+          },
+          {
+            label: '最新跟进时间',
+            prop: 'finalFollowTime',
+            width: '110px',
+          },
+          {
+            label: '最新跟进人',
+            prop: 'finalFollowName',
+            width: '50px',
+          },
+          {
+            label: '项目成交时间',
+            prop: 'projClosingTime',
+            width: '110px',
+          },
+          {
+            label: '合同金额',
+            prop: 'contractAmount',
+            width: '140px',
+          },
+          {
+            label: '项目转化时间',
+            prop: 'projConversionTime',
+            width: '110px',
+          },
+          {
+            label: '转化原因',
+            prop: 'projConversionReason',
+            width: '140px',
+          },
+          {
+            label: '备注',
+            width: '120px',
+            prop: 'remark',
+            sortable: false,
+            disableCheck: false,
+          },
+        ],
+        allColumns: [
+          '项目编码',
+          '项目名称',
+          '销售工程师',
+          '战略工程师',
+          '所在省',
+          '所在市',
+          '客户名称',
+          '项目备案时间',
+          '项目来源',
+          '销售模式',
+          '经销商/代理商',
+          '产品线',
+          '项目类别',
+          '项目预算',
+          '出货金额',
+          '战略项目',
+          '最新跟进时间',
+          '科室',
+        ],
+        abcColumns: [
+          '项目编码',
+          '项目名称',
+          '所在省',
+          '所在市',
+          '销售工程师',
+          '战略工程师',
+          '客户名称',
+          '项目备案时间',
+          '项目来源',
+          '销售模式',
+          '经销商/代理商',
+          '产品线',
+          '项目预算',
+          '出货金额',
+          '战略项目',
+          '最新跟进时间',
+          '最新跟进',
+          '科室',
+        ],
+        dealColumns: [
+          '项目编码',
+          '项目名称',
+          '所在省',
+          '所在市',
+          '销售工程师',
+          '战略工程师',
+          '客户名称',
+          '项目备案时间',
+          '项目来源',
+          '销售模式',
+          '经销商/代理商',
+          '产品线',
+          '战略项目',
+          '项目成交时间',
+          '合同金额',
+          '科室',
+        ],
+        reserveColumns: [
+          '项目编码',
+          '项目名称',
+          '所在省',
+          '所在市',
+          '销售工程师',
+          '战略工程师',
+          '客户名称',
+          '项目备案时间',
+          '项目来源',
+          '销售模式',
+          '经销商/代理商',
+          '产品线',
+          '项目转化时间',
+          '转化原因',
+          '科室',
+        ],
+        list: [],
+        listLoading: true,
+        layout: 'total, sizes, prev, pager, next, jumper',
+        total: 0,
+        selectRows: '',
+        yesOrNoOptions: [],
+        nboTypeOptions: [],
+        nboSourceOptions: [],
+        salesModelOptions: [],
+        productLineOptions: [],
+        nboPhaseOptions: [],
+        nboStatusOptions: [],
+        approStatusOptions: [],
+        followup: {},
+        finallyColumns: [],
+        provinceOptions: [],
+      }
+    },
+    watch: {
+      showColumns: function () {
+        this.tableKey++
+        this.$nextTick(() => this.$refs.busTable.doLayout())
+      },
+      activeName(newValue) {
+        this.tableType = 'strategicBusinessTable' + this.selectDictLabel(this.nboTypeOptions, newValue)
+        switch (newValue) {
+          case '10':
+          case '20':
+          case '30':
+            this.finallyColumns = this.columns.filter((item) => this.abcColumns.includes(item.label))
+            break
+          case '40':
+            this.finallyColumns = this.columns.filter((item) => this.dealColumns.includes(item.label))
+            break
+          case '50':
+            this.finallyColumns = this.columns.filter((item) => this.reserveColumns.includes(item.label))
+            break
+          default:
+            this.finallyColumns = this.columns.filter((item) => this.allColumns.includes(item.label))
+        }
+      },
+    },
+    activated() {
+      this.fetchData()
+    },
+    created() {
+      this.activeName = 'all'
+      if (this.$route.params && this.$route.params.length > 0) {
+        this.queryForm = this.$route.params
+        this.activeName = this.queryForm.activeName ? this.queryForm.activeName : 'all'
+      }
+      this.handleTabClick()
+      this.getOptions()
+    },
+    updated() {
+      if (this.$refs.busTable && this.$refs.busTable.doLayout) {
+        this.$refs.busTable.doLayout()
+      }
+    },
+    methods: {
+      getOptions() {
+        Promise.all([
+          this.getDicts('proj_nbo_type'),
+          this.getDicts('proj_nbo_source'),
+          this.getDicts('proj_sales_model'),
+          this.getDicts('sys_product_line'),
+          this.getDicts('sys_yes_no'),
+          // this.getDicts('proj_nbo_phase'),
+          // this.getDicts('proj_nbo_status'),
+          // this.getDicts('proj_appro_status'),
+          customerApi.getProvinceDetail(),
+        ])
+          .then(([nboType, nboSource, salesModel, productLine, yesOrNo, province]) => {
+            this.nboTypeOptions = nboType.data.values || []
+            this.nboSourceOptions = nboSource.data.values || []
+            this.salesModelOptions = salesModel.data.values || []
+            this.productLineOptions = productLine.data.values || []
+            this.yesOrNoOptions = yesOrNo.data.values || []
+            // this.nboPhaseOptions = nboPhase.data.values || []
+            // this.nboStatusOptions = nboStatus.data.values || []
+            // this.approStatusOptions = approStatus.data.values || []
+            this.provinceOptions = province.data.list || []
+          })
+          .catch((err) => console.log(err))
+      },
+      handleTabClick() {
+        this.queryForm.pageNum = 1
+        if (this.activeName !== 'all') {
+          this.queryForm.nboType = this.activeName
+        } else {
+          this.queryForm.nboType = undefined
+        }
+        this.$nextTick(() => {
+          this.$refs['tableTool'].getInitData()
+        })
+        this.fetchData()
+      },
+      handleTransfer() {
+        if (this.selectRows.length !== 1) {
+          this.$baseMessage('请选择一个项目', 'warning')
+          return
+        }
+        this.$refs['transfer'].open(this.selectRows[0])
+      },
+      handleSysAdminTransfer() {
+        if (this.selectRows.length === 0) {
+          this.$baseMessage('请选择项目', 'warning')
+          return
+        }
+        this.$refs['transfer'].sysAdminOpen(this.selectRows)
+      },
+      handleTransferStrategic() {
+        if (this.selectRows.length !== 1) {
+          this.$baseMessage('请选择一个项目', 'warning')
+          return
+        }
+        this.$refs['transferStrategic'].open(this.selectRows[0])
+      },
+      // 跳转详情
+      handleDetail(row) {
+        this.queryForm.activeName = this.activeName
+        this.$router.push({
+          name: 'BusinessDetail',
+          query: {
+            id: row.id,
+          },
+          params: this.queryForm,
+        })
+      },
+      handleFollow(row) {
+        this.followup = {
+          targetId: row.id,
+          targetType: '20',
+          targetName: row.nboName,
+          custId: row.custId,
+          custName: row.custName,
+          contactsId: row.contactId,
+          contactsName: row.contactName,
+        }
+        console.log(this.followup)
+        this.$refs.follow.showEdit(this.followup)
+      },
+      handleEdit(row) {
+        if (row.id) {
+          this.$refs['edit'].showEdit(row)
+        } else {
+          this.$refs['add'].showEdit(row)
+        }
+      },
+      handleDelete(row) {
+        if (row.id) {
+          this.$baseConfirm('你确定要删除当前项吗', null, async () => {
+            const { msg } = await businessApi.doDelete({ ids: [row.id] })
+            this.$baseMessage(msg, 'success')
+            await this.fetchData()
+          })
+        } else {
+          if (this.selectRows.length > 0) {
+            const ids = this.selectRows.map((item) => item.id)
+            this.$baseConfirm('你确定要删除选中项吗', null, async () => {
+              const { msg } = await businessApi.doDelete({ ids })
+              this.$baseMessage(msg, 'success')
+              await this.fetchData()
+            })
+          } else {
+            this.$baseMessage('未选中任何行', 'error')
+            return false
+          }
+        }
+      },
+      handleSizeChange(val) {
+        this.queryForm.pageSize = val
+        this.fetchData()
+      },
+      handleCurrentChange(val) {
+        this.queryForm.pageNum = val
+        this.fetchData()
+      },
+      queryData() {
+        this.queryForm.pageNum = 1
+        this.fetchData()
+      },
+      /** 重置按钮操作 */
+      resetQuery() {
+        this.resetForm('queryForm')
+        this.handleTabClick()
+      },
+      setSelectRows(val) {
+        this.selectRows = val
+      },
+      async fetchData() {
+        if (this.queryForm.filingTime && this.queryForm.filingTime.length === 2) {
+          this.queryForm.beginTime = this.queryForm.filingTime[0] + ' 00:00:00'
+          this.queryForm.endTime = this.queryForm.filingTime[1] + ' 23:59:59'
+        } else {
+          this.queryForm.beginTime = undefined
+          this.queryForm.endTime = undefined
+        }
+        this.listLoading = true
+        if (this.queryForm.custProvince) {
+          this.queryForm.provinceId = this.queryForm.custProvince.id
+        }
+        const { data } = await businessApi.getListByStrategic(this.queryForm)
+        const { list, total } = data
+        this.list = list
+        this.total = total
+        this.listLoading = false
+        this.tableKey++
+        this.$nextTick(() => this.$refs.busTable.doLayout())
+      },
+    },
+  }
+</script>

+ 3 - 1
src/views/system/user/index.vue

@@ -66,7 +66,7 @@
               {{ $index + 1 }}
             </template>
           </el-table-column>-->
-          <el-table-column v-permissions="['SysAdmin']" align="center" label="id" prop="id" width="60" />
+          <el-table-column v-if="hasPermission(['SysAdmin'])" align="center" label="id" prop="id" width="60" />
           <el-table-column align="center" label="用户名" prop="userName" show-overflow-tooltip />
           <el-table-column align="center" label="昵称" prop="nickName" show-overflow-tooltip />
           <el-table-column align="center" label="手机号" prop="phone" show-overflow-tooltip />
@@ -119,6 +119,7 @@
 </template>
 
 <script>
+  import { hasPermission } from '@/utils/permission'
   import deptApi from '@/api/system/dept'
   import userApi from '@/api/system/user'
   import Edit from './components/UserEdit'
@@ -164,6 +165,7 @@
       })
     },
     methods: {
+      hasPermission,
       /** 查询部门下拉树结构 */
       async getTreeselect() {
         const { data: data } = await deptApi.getTree({

+ 2 - 2
src/views/work/deliver/components/completeProgress.vue

@@ -89,10 +89,10 @@
         fileSettings: {
           // 文件配置信息
           fileSize: 52428800,
-          fileTypes: '.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.txt',
+          fileTypes: '.pdf,.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.txt',
           pictureSize: 52428800,
           pictureTypes: '.jpg,.jpeg,.gif,.png,.jfif,.txt',
-          types: '.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.mp4,.txt',
+          types: '.pdf,.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.mp4,.txt',
           videoSize: 104857600,
           videoType: '.mp4',
         },

+ 2 - 2
src/views/work/deliver/components/deliver.vue

@@ -100,10 +100,10 @@
         fileSettings: {
           // 文件配置信息
           fileSize: 52428800,
-          fileTypes: '.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.txt',
+          fileTypes: '.pdf,.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.txt',
           pictureSize: 52428800,
           pictureTypes: '.jpg,.jpeg,.gif,.png,.jfif,.txt',
-          types: '.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.mp4,.txt',
+          types: '.pdf,.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.mp4,.txt',
           videoSize: 104857600,
           videoType: '.mp4',
         },

+ 2 - 2
src/views/work/deliver/components/inspect.vue

@@ -72,10 +72,10 @@
         fileSettings: {
           // 文件配置信息
           fileSize: 52428800,
-          fileTypes: '.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.txt',
+          fileTypes: '.pdf,.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.txt',
           pictureSize: 52428800,
           pictureTypes: '.jpg,.jpeg,.gif,.png,.jfif,.txt',
-          types: '.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.mp4,.txt',
+          types: '.pdf,.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.mp4,.txt',
           videoSize: 104857600,
           videoType: '.mp4',
         },

+ 2 - 2
src/views/work/deliver/components/pictrues.vue

@@ -61,10 +61,10 @@
         fileSettings: {
           // 文件配置信息
           fileSize: 52428800,
-          fileTypes: '.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.txt',
+          fileTypes: '.pdf,.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.txt',
           pictureSize: 52428800,
           pictureTypes: '.jpg,.jpeg,.gif,.png,.jfif',
-          types: '.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.mp4,.txt',
+          types: '.pdf,.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.mp4,.txt',
           videoSize: 104857600,
           videoType: '.mp4',
         },

+ 2 - 2
src/views/work/deliver/components/productSign.vue

@@ -54,10 +54,10 @@
         fileSettings: {
           // 文件配置信息
           fileSize: 52428800,
-          fileTypes: '.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.txt',
+          fileTypes: '.pdf,.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.txt',
           pictureSize: 52428800,
           pictureTypes: '.jpg,.jpeg,.gif,.png,.jfif,.txt',
-          types: '.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.mp4,.txt',
+          types: '.pdf,.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.mp4,.txt',
           videoSize: 104857600,
           videoType: '.mp4',
         },

+ 2 - 2
src/views/work/deliver/components/softwareComplete.vue

@@ -125,10 +125,10 @@
         fileSettings: {
           // 文件配置信息
           fileSize: 52428800,
-          fileTypes: '.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.txt',
+          fileTypes: '.pdf,.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.txt',
           pictureSize: 52428800,
           pictureTypes: '.jpg,.jpeg,.gif,.png,.jfif,.txt',
-          types: '.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.mp4,.txt',
+          types: '.pdf,.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.mp4,.txt',
           videoSize: 104857600,
           videoType: '.mp4',
         },

+ 2 - 2
src/views/work/train/sale/components/finish.vue

@@ -55,10 +55,10 @@
         fileSettings: {
           // 文件配置信息
           fileSize: 52428800,
-          fileTypes: '.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.txt',
+          fileTypes: '.pdf,.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.txt',
           pictureSize: 52428800,
           pictureTypes: '.jpg,.jpeg,.gif,.png,.jfif,.txt',
-          types: '.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.mp4,.txt',
+          types: '.pdf,.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.mp4,.txt',
           videoSize: 104857600,
           videoType: '.mp4',
         },