Forráskód Böngészése

feature: 项目管理

ZZH-wl 2 éve
szülő
commit
6b5665dfa4

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

@@ -18,13 +18,28 @@ export default {
   doDelete(query) {
     return micro_request.postRequest(basePath, 'Business', 'DeleteByIds', query)
   },
+  // 项目调级
   businessGradation(query) {
     return micro_request.postRequest(basePath, 'Business', 'BusinessGradation', query)
   },
+  // 项目转移
   businessTransfer(query) {
     return micro_request.postRequest(basePath, 'Business', 'BusinessTransfer', query)
   },
+  // 设置首要联系人
+  setPrimacyContact(query) {
+    return micro_request.postRequest(basePath, 'Business', 'SetPrimacyContact', query)
+  },
+  // 获取项目产品
   getProductByBusinessId(query) {
     return micro_request.postRequest(basePath, 'Business', 'GetBusinessProduct', query)
   },
+  // 获取项目动态
+  getBusinessDynamics(query) {
+    return micro_request.postRequest(basePath, 'Business', 'GetBusinessDynamics', query)
+  },
+  // 获取项目归属记录
+  getBusinessDynamicsList(query) {
+    return micro_request.postRequest(basePath, 'Business', 'GetBusinessDynamicsList', query)
+  },
 }

+ 21 - 0
src/api/proj/businessContact.js

@@ -0,0 +1,21 @@
+import micro_request from '@/utils/micro_request'
+
+const basePath = process.env.VUE_APP_ParentPath
+export default {
+  // 获取列表
+  getList(query) {
+    return micro_request.postRequest(basePath, 'BusinessContact', 'GetList', query)
+  },
+  getEntityById(query) {
+    return micro_request.postRequest(basePath, 'BusinessContact', 'GetEntityById', query)
+  },
+  doAdd(query) {
+    return micro_request.postRequest(basePath, 'BusinessContact', 'Create', query)
+  },
+  doEdit(query) {
+    return micro_request.postRequest(basePath, 'BusinessContact', 'UpdateById', query)
+  },
+  doDelete(query) {
+    return micro_request.postRequest(basePath, 'BusinessContact', 'DeleteByIds', query)
+  },
+}

+ 9 - 9
src/components/select/SelectBusiness.vue

@@ -30,7 +30,7 @@
         <table-tool :check-list.sync="checkList" :columns="columns" style="float: right" />
       </el-col>
     </el-row>
-    <el-table ref="table" v-loading="listLoading" :data="list" @selection-change="setSelectRows">
+    <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 finallyColumns"
@@ -62,7 +62,7 @@
       <el-button size="mini" @click="innerVisible = false">取消</el-button>
     </span>
     <!-- 新建项目弹窗 -->
-    <business-edit ref="businessEdit" />
+    <business-edit ref="businessEdit" @fetch-data="fetchData" />
   </el-dialog>
 </template>
 
@@ -80,7 +80,7 @@
     props: {
       title: {
         type: String,
-        default: '选择',
+        default: '选择项目',
       },
       multiple: Boolean,
       queryParams: {
@@ -181,18 +181,18 @@
         this.listLoading = false
       },
       setSelectRows(val) {
-        if (!this.multiple && val.length === this.list.length) {
+        if (!this.multiple && val.length === this.list.length && val.length > 1) {
           // 返回单条数据情况下-控制全选情况下单选第一条数据
           if (this.selectRows.length === 1) {
-            this.$refs.table.clearSelection()
+            this.$refs.businessTable.clearSelection()
             return
           }
-          this.$refs.table.clearSelection()
-          this.$refs.table.toggleRowSelection(val.shift(), true)
+          this.$refs.businessTable.clearSelection()
+          this.$refs.businessTable.toggleRowSelection(val.shift(), true)
         } else if (!this.multiple && val.length > 1) {
           // 返回单条数据情况下-控制选择当前点击数据
-          this.$refs.table.clearSelection()
-          this.$refs.table.toggleRowSelection(val.pop(), true)
+          this.$refs.businessTable.clearSelection()
+          this.$refs.businessTable.toggleRowSelection(val.pop(), true)
         } else {
           this.selectRows = val
         }

+ 21 - 15
src/components/select/SelectContact.vue

@@ -30,7 +30,7 @@
         <table-tool :check-list.sync="checkList" :columns="columns" style="float: right" />
       </el-col>
     </el-row>
-    <el-table ref="table" v-loading="listLoading" :data="list" @selection-change="setSelectRows">
+    <el-table ref="contactTable" v-loading="listLoading" :data="list" @selection-change="setSelectRows">
       <el-table-column align="center" type="selection" />
       <el-table-column
         v-for="(item, index) in finallyColumns"
@@ -62,7 +62,7 @@
       <el-button size="mini" @click="innerVisible = false">取消</el-button>
     </span>
     <!-- 新建联系人弹窗 -->
-    <customer-contact ref="contact" />
+    <customer-contact ref="contact" @contactSave="fetchData" />
   </el-dialog>
 </template>
 
@@ -80,9 +80,16 @@
     props: {
       title: {
         type: String,
-        default: '选择',
+        default: '选择客户联系人',
       },
       multiple: Boolean,
+      // 示例{ custId: id, custName: custName}
+      defaultCustomer: {
+        type: Object,
+        default() {
+          return {}
+        },
+      },
       queryParams: {
         type: Object,
         default() {
@@ -157,20 +164,20 @@
         return this.columns.filter((item) => this.checkList.includes(item.label))
       },
     },
-    mounted() {
-      this.fetchData()
-    },
+    mounted() {},
     methods: {
       open() {
         this.innerVisible = true
+        this.fetchData()
       },
       save() {
         this.innerVisible = false
+        console.log(this.selectRows)
         this.$emit('save', this.selectRows)
       },
-      handleAdd(res) {
-        this.$refs.contact.contactForm.custId = res.id
-        this.$refs.contact.contactForm.custName = res.name
+      handleAdd() {
+        this.$refs.contact.contactForm.custId = this.defaultCustomer.custId
+        this.$refs.contact.contactForm.custName = this.defaultCustomer.custName
         this.$refs.contact.contactVisible = true
       },
       async fetchData() {
@@ -184,18 +191,17 @@
         this.listLoading = false
       },
       setSelectRows(val) {
-        if (!this.multiple && val.length === this.list.length) {
+        if (!this.multiple && val.length === this.list.length && val.length > 1) {
           // 返回单条数据情况下-控制全选情况下单选第一条数据
+          this.$refs.contactTable.clearSelection()
           if (this.selectRows.length === 1) {
-            this.$refs.table.clearSelection()
             return
           }
-          this.$refs.table.clearSelection()
-          this.$refs.table.toggleRowSelection(val.shift(), true)
+          this.$refs.contactTable.toggleRowSelection(val.shift(), true)
         } else if (!this.multiple && val.length > 1) {
           // 返回单条数据情况下-控制选择当前点击数据
-          this.$refs.table.clearSelection()
-          this.$refs.table.toggleRowSelection(val.pop(), true)
+          this.$refs.contactTable.clearSelection()
+          this.$refs.contactTable.toggleRowSelection(val.pop(), true)
         } else {
           this.selectRows = val
         }

+ 9 - 9
src/components/select/SelectCustomer.vue

@@ -30,7 +30,7 @@
         <table-tool :check-list.sync="checkList" :columns="columns" style="float: right" />
       </el-col>
     </el-row>
-    <el-table ref="table" v-loading="listLoading" :data="list" @selection-change="setSelectRows">
+    <el-table ref="customerTable" v-loading="listLoading" :data="list" @selection-change="setSelectRows">
       <el-table-column align="center" type="selection" />
       <el-table-column
         v-for="(item, index) in finallyColumns"
@@ -64,7 +64,7 @@
     <!-- 新增编辑客户弹窗 -->
     <customer-edit ref="edit" @createContact="createContact" @customerSave="fetchData" />
     <!-- 新建联系人弹窗 -->
-    <customer-contact ref="contact" />
+    <customer-contact ref="contact" @contactSave="fetchData" />
   </el-dialog>
 </template>
 
@@ -84,7 +84,7 @@
     props: {
       title: {
         type: String,
-        default: '选择',
+        default: '选择客户',
       },
       multiple: Boolean,
       queryParams: {
@@ -191,18 +191,18 @@
         this.listLoading = false
       },
       setSelectRows(val) {
-        if (!this.multiple && val.length === this.list.length) {
+        if (!this.multiple && val.length === this.list.length && val.length > 1) {
           // 返回单条数据情况下-控制全选情况下单选第一条数据
           if (this.selectRows.length === 1) {
-            this.$refs.table.clearSelection()
+            this.$refs.customerTable.clearSelection()
             return
           }
-          this.$refs.table.clearSelection()
-          this.$refs.table.toggleRowSelection(val.shift(), true)
+          this.$refs.customerTable.clearSelection()
+          this.$refs.customerTable.toggleRowSelection(val.shift(), true)
         } else if (!this.multiple && val.length > 1) {
           // 返回单条数据情况下-控制选择当前点击数据
-          this.$refs.table.clearSelection()
-          this.$refs.table.toggleRowSelection(val.pop(), true)
+          this.$refs.customerTable.clearSelection()
+          this.$refs.customerTable.toggleRowSelection(val.pop(), true)
         } else {
           this.selectRows = val
         }

+ 8 - 8
src/components/select/SelectDistributor.vue

@@ -25,7 +25,7 @@
         <table-tool :check-list.sync="checkList" :columns="columns" style="float: right" />
       </el-col>
     </el-row>
-    <el-table ref="table" v-loading="listLoading" :data="list" @selection-change="setSelectRows">
+    <el-table ref="distributorTable" v-loading="listLoading" :data="list" @selection-change="setSelectRows">
       <el-table-column align="center" type="selection" />
       <el-table-column
         v-for="(item, index) in finallyColumns"
@@ -75,7 +75,7 @@
     props: {
       title: {
         type: String,
-        default: '选择',
+        default: '选择经销商/代理商',
       },
       multiple: Boolean,
       queryParams: {
@@ -181,18 +181,18 @@
         this.listLoading = false
       },
       setSelectRows(val) {
-        if (!this.multiple && val.length === this.list.length) {
+        if (!this.multiple && val.length === this.list.length && val.length > 1) {
           // 返回单条数据情况下-控制全选情况下单选第一条数据
           if (this.selectRows.length === 1) {
-            this.$refs.table.clearSelection()
+            this.$refs.distributorTable.clearSelection()
             return
           }
-          this.$refs.table.clearSelection()
-          this.$refs.table.toggleRowSelection(val.shift(), true)
+          this.$refs.distributorTable.clearSelection()
+          this.$refs.distributorTable.toggleRowSelection(val.shift(), true)
         } else if (!this.multiple && val.length > 1) {
           // 返回单条数据情况下-控制选择当前点击数据
-          this.$refs.table.clearSelection()
-          this.$refs.table.toggleRowSelection(val.pop(), true)
+          this.$refs.distributorTable.clearSelection()
+          this.$refs.distributorTable.toggleRowSelection(val.pop(), true)
         } else {
           this.selectRows = val
         }

+ 8 - 8
src/components/select/SelectProduct.vue

@@ -25,7 +25,7 @@
         <table-tool :check-list.sync="checkList" :columns="columns" style="float: right" />
       </el-col>
     </el-row>
-    <el-table ref="table" v-loading="listLoading" :data="list" @selection-change="setSelectRows">
+    <el-table ref="productTable" v-loading="listLoading" :data="list" @selection-change="setSelectRows">
       <el-table-column align="center" type="selection" />
       <el-table-column
         v-for="(item, index) in finallyColumns"
@@ -75,7 +75,7 @@
     props: {
       title: {
         type: String,
-        default: '选择',
+        default: '选择产品',
       },
       multiple: Boolean,
       queryParams: {
@@ -182,18 +182,18 @@
         this.listLoading = false
       },
       setSelectRows(val) {
-        if (!this.multiple && val.length === this.list.length) {
+        if (!this.multiple && val.length === this.list.length && val.length > 1) {
           // 返回单条数据情况下-控制全选情况下单选第一条数据
           if (this.selectRows.length === 1) {
-            this.$refs.table.clearSelection()
+            this.$refs.productTable.clearSelection()
             return
           }
-          this.$refs.table.clearSelection()
-          this.$refs.table.toggleRowSelection(val.shift(), true)
+          this.$refs.productTable.clearSelection()
+          this.$refs.productTable.toggleRowSelection(val.shift(), true)
         } else if (!this.multiple && val.length > 1) {
           // 返回单条数据情况下-控制选择当前点击数据
-          this.$refs.table.clearSelection()
-          this.$refs.table.toggleRowSelection(val.pop(), true)
+          this.$refs.productTable.clearSelection()
+          this.$refs.productTable.toggleRowSelection(val.pop(), true)
         } else {
           this.selectRows = val
         }

+ 203 - 96
src/views/proj/business/components/BusinessEdit.vue

@@ -1,9 +1,15 @@
 <template>
   <el-dialog append-to-body :title="title" :visible.sync="dialogFormVisible" @close="close">
+    <el-steps :active="activeSteps" align-center style="margin: -15px 0 15px 0">
+      <el-step title="创建项目" />
+      <el-step title="添加产品" />
+      <el-step title="跟进日程" />
+    </el-steps>
+
     <el-form ref="form" label-width="120px" :model="form" :rules="rules">
-      <el-row :gutter="20">
+      <el-row v-if="activeSteps === 1" :gutter="20">
         <el-col :span="12">
-          <el-form-item label="项目标题" prop="nboName">
+          <el-form-item label="项目名称" prop="nboName">
             <el-input v-model="form.nboName" />
           </el-form-item>
         </el-col>
@@ -13,13 +19,15 @@
           </el-form-item>
         </el-col>
         <el-col :span="12">
-          <el-form-item label="获取日期" prop="title">
-            <el-date-picker v-model="form.value1" placeholder="选择日期" style="width: 100%" type="datetime" />
+          <el-form-item label="获取日期" prop="obtainTime">
+            <el-date-picker v-model="form.obtainTime" placeholder="选择日期" style="width: 100%" type="datetime" />
           </el-form-item>
         </el-col>
         <el-col :span="12">
           <el-form-item label="项目来源" prop="nboSource">
-            <el-input v-model="form.nboSource" />
+            <el-select v-model="form.nboSource" clearable placeholder="项目来源" style="width: 100%">
+              <el-option v-for="dict in nboSourceOptions" :key="dict.key" :label="dict.value" :value="dict.key" />
+            </el-select>
           </el-form-item>
         </el-col>
         <el-col :span="12">
@@ -38,184 +46,283 @@
           </el-form-item>
         </el-col>
         <el-col :span="12">
-          <el-form-item label="负责人员" prop="makerName">
-            <el-input v-model="form.makerName" />
+          <el-form-item label="销售工程师" prop="saleName">
+            <el-input v-model="form.saleName" readonly @focus="handleSelectSale" />
           </el-form-item>
         </el-col>
         <el-col :span="12">
           <el-form-item label="销售模式" prop="salesModel">
-            <el-input v-model="form.salesModel" />
+            <el-select v-model="form.salesModel" clearable placeholder="销售模式" style="width: 100%">
+              <el-option v-for="dict in salesModelOptions" :key="dict.key" :label="dict.value" :value="dict.key" />
+            </el-select>
           </el-form-item>
         </el-col>
         <el-col :span="12">
-          <el-form-item label="经销商名称" prop="distributorName">
-            <el-input v-model="form.distributorName" readonly @focus="handleSelectDistributor" />
+          <el-form-item label="经销商/代理商" prop="distributorName">
+            <el-input
+              v-model="form.distributorName"
+              :disabled="form.salesModel === '10'"
+              readonly
+              @focus="handleSelectDistributor" />
           </el-form-item>
         </el-col>
-        <el-col :span="12">
-          <el-form-item label="经销商负责人" prop="title">
-            <el-input v-model="form.title" />
+
+        <el-col :span="24">
+          <el-form-item label="备注信息" prop="remark">
+            <el-input v-model="form.remark" placeholder="请输入备注信息" rows="5" show-word-limit type="textarea" />
           </el-form-item>
         </el-col>
+      </el-row>
+
+      <el-row v-if="activeSteps === 2" :gutter="20">
+        <el-col :span="24">
+          <el-button size="mini" type="primary" @click="handleSelectProduct">添加产品</el-button>
+
+          <product-table
+            ref="productTable"
+            :product-data="productData"
+            @changeProductData="changeProductData"
+            @delProductData="delProductData" />
+        </el-col>
+      </el-row>
+
+      <el-row v-if="activeSteps === 3" :gutter="20">
         <el-col :span="12">
-          <el-form-item label="经销商联系方式" prop="title">
-            <el-input v-model="form.title" />
+          <el-form-item label="跟进时间" prop="followTime">
+            <el-date-picker v-model="form.followTime" placeholder="选择时间" style="width: 100%" type="datetime" />
           </el-form-item>
         </el-col>
         <el-col :span="12">
-          <el-form-item label="备注信息" prop="remark">
-            <el-input v-model="form.remark" />
+          <el-form-item label="负责人员" prop="followUserName">
+            <el-input v-model="form.followUserName" readonly @focus="handleSelectFollowUser" />
           </el-form-item>
         </el-col>
-      </el-row>
-      <!--      <el-row :gutter="20">-->
-      <!--        <el-col :span="24">-->
-      <!--          <div style="margin: -10px 0 10px 120px">-->
-      <!--            <el-checkbox v-model="form.checked">创建跟进任务</el-checkbox>-->
-      <!--          </div>-->
-      <!--        </el-col>-->
-      <!--      </el-row>-->
-      <!--      <el-row :gutter="20">-->
-      <!--        <el-col :span="12">-->
-      <!--          <el-form-item label="开始时间" prop="title">-->
-      <!--            <el-date-picker v-model="form.value1" placeholder="选择时间" style="width: 100%" type="datetime" />-->
-      <!--          </el-form-item>-->
-      <!--        </el-col>-->
-      <!--        <el-col :span="12">-->
-      <!--          <el-form-item label="结束时间" prop="title">-->
-      <!--            <el-date-picker v-model="form.value1" placeholder="选择时间" style="width: 100%" type="datetime" />-->
-      <!--          </el-form-item>-->
-      <!--        </el-col>-->
-      <!--        <el-col :span="12">-->
-      <!--          <el-form-item label="负责人员" prop="title">-->
-      <!--            <el-input v-model="form.title" />-->
-      <!--          </el-form-item>-->
-      <!--        </el-col>-->
-      <!--        <el-col :span="12">-->
-      <!--          <el-form-item label="跟进内容" prop="followContent">-->
-      <!--            <el-input v-model="form.followContent" />-->
-      <!--          </el-form-item>-->
-      <!--        </el-col>-->
-      <!--        <el-col :span="12">-->
-      <!--          <el-form-item label="任务提醒" prop="title">-->
-      <!--            <el-input v-model="form.title" />-->
-      <!--          </el-form-item>-->
-      <!--        </el-col>-->
-      <!--        <el-col :span="12">-->
-      <!--          <el-form-item label="提醒方式" prop="title">-->
-      <!--            <el-checkbox v-model="form.checked">系统消息</el-checkbox>-->
-      <!--          </el-form-item>-->
-      <!--        </el-col>-->
-      <!--      </el-row>-->
-      <el-row :gutter="20">
         <el-col :span="24">
-          <el-form-item label="产品(含版本)" prop="title">
-            <el-button size="mini" type="primary" @click="handleSelectProduct">添加产品</el-button>
-
-            <el-table>
-              <el-table-column align="center" label="产品名称" prop="custCode" />
-              <el-table-column align="center" label="产品类别" prop="custName" />
-              <el-table-column align="center" label="单位" prop="abbrName" />
-              <el-table-column align="center" label="价格" prop="custLocation" />
-              <el-table-column align="center" label="数量" prop="custIndustry" />
-              <el-table-column align="center" label="操作" width="80">
-                <template #default="{ row }">
-                  <el-button type="text">删除{{ row }}</el-button>
-                </template>
-              </el-table-column>
-            </el-table>
+          <el-form-item label="跟进内容" prop="followContent">
+            <el-input
+              v-model="form.followContent"
+              placeholder="请输入跟进内容"
+              rows="5"
+              show-word-limit
+              type="textarea" />
           </el-form-item>
         </el-col>
       </el-row>
     </el-form>
     <div slot="footer" class="dialog-footer">
-      <el-button type="primary" @click="save">提 交</el-button>
-      <el-button @click="close">重 置</el-button>
+      <el-button v-if="activeSteps !== 1" type="primary" @click="activeSteps--">上一步</el-button>
+      <el-button v-if="activeSteps !== 3" type="primary" @click="nextStep">下一步</el-button>
+      <el-button v-if="activeSteps === 3" type="primary" @click="save">提 交</el-button>
     </div>
     <!-- 选择客户弹窗 -->
     <select-customer ref="selectCustomer" @save="selectCustomer" />
-    <!-- 选择客户弹窗 -->
-    <select-contact ref="selectContact" :query-params="queryContact" @save="selectContact" />
+    <!-- 选择客户联系人弹窗 -->
+    <select-contact
+      ref="selectContact"
+      :default-customer="customerInfo"
+      :query-params="queryContact"
+      @save="selectContact" />
+    <!-- 选择销售工程师弹窗 -->
+    <select-user ref="selectSales" :query-params="{ roles: ['Sales', 'SalesManager'] }" @save="selectSales" />
     <!-- 选择经销商弹窗 -->
     <select-distributor ref="selectDistributor" @save="selectDistributor" />
     <!-- 选择产品弹窗 -->
-    <select-product ref="selectProduct" @save="selectProduct" />
+    <select-product ref="selectProduct" multiple @save="selectProduct" />
+    <!-- 选择跟进负责人弹窗 -->
+    <select-user ref="selectFollowUser" @save="selectFollowUser" />
   </el-dialog>
 </template>
 
 <script>
   import businessApi from '@/api/proj/business'
+  import ProductTable from './ProductTable'
   import SelectContact from '@/components/select/SelectContact'
   import SelectCustomer from '@/components/select/SelectCustomer'
+  import SelectUser from '@/components/select/SelectUser'
   import SelectDistributor from '@/components/select/SelectDistributor'
   import SelectProduct from '@/components/select/SelectProduct'
 
   export default {
     name: 'BusinessEdit',
-    components: { SelectContact, SelectProduct, SelectDistributor, SelectCustomer },
+    components: { ProductTable, SelectContact, SelectProduct, SelectDistributor, SelectCustomer, SelectUser },
     data() {
+      const validateDistributor = (rule, value, callback) => {
+        if ('' === value && this.form.salesModel !== '10')
+          callback(new Error(this.translateTitle('请选择经销商/代理商')))
+        else callback()
+      }
       return {
+        activeSteps: 1,
         form: {
-          title: '',
+          nboName: undefined,
           custId: undefined,
           custName: undefined,
+          obtainTime: undefined,
+          nboSource: undefined,
+          contactId: undefined,
           contactName: undefined,
+          contactPostion: undefined,
+          contactTelephone: undefined,
+          saleId: undefined,
+          saleName: undefined,
+          distributorId: undefined,
           distributorName: undefined,
+          remark: undefined,
+          products: undefined,
+
+          // 跟进
+          followTime: undefined,
+          followUserId: undefined,
+          followUserName: undefined,
+          followContent: undefined,
         },
         rules: {
-          title: [{ required: true, trigger: 'blur', message: '请输入标题' }],
+          nboName: [{ required: true, trigger: ['blur', 'change'], message: '请输入项目名称' }],
+          custName: [{ required: true, trigger: ['blur', 'change'], message: '请选择关联客户' }],
+          nboSource: [{ required: true, trigger: ['blur', 'change'], message: '请选择项目来源' }],
+          contactName: [{ required: true, trigger: ['blur', 'change'], message: '请选择主要联系人' }],
+          saleName: [{ required: true, trigger: ['blur', 'change'], message: '请选择销售工程师' }],
+          salesModel: [{ required: true, trigger: ['blur', 'change'], message: '请选择销售模式' }],
+          distributorName: [
+            { validator: validateDistributor, trigger: ['blur', 'change'], message: '请选择经销商/代理商' },
+          ],
+          // 跟进
+          followTime: [{ required: true, trigger: ['blur', 'change'], message: '请输入跟进时间' }],
+          followContent: [{ required: true, trigger: ['blur', 'change'], message: '请输入跟进内容' }],
         },
         title: '',
         dialogFormVisible: false,
+        nboSourceOptions: [],
+        salesModelOptions: [],
         queryContact: {},
+        customerInfo: {},
+        productData: [],
       }
     },
     created() {},
+    mounted() {
+      this.getDicts('proj_nbo_source').then((response) => {
+        this.nboSourceOptions = response.data.values || []
+      })
+      this.getDicts('proj_sales_model').then((response) => {
+        this.salesModelOptions = response.data.values || []
+      })
+    },
     methods: {
+      nextStep() {
+        if (this.activeSteps === 1) {
+          this.$refs['form'].validate(async (valid) => {
+            if (valid) {
+              this.activeSteps++
+            }
+          })
+          return
+        }
+        if (this.activeSteps === 2) {
+          this.form.products = this.productData
+          this.activeSteps++
+          return
+        }
+      },
       handleSelectCustomer() {
         this.$refs.selectCustomer.open()
       },
       handleSelectContact() {
         if (!this.queryContact.custId) {
-          this.$message.warning('请选择联系人')
+          this.$message.warning('请先选择客户')
+          return
         }
         this.$refs.selectContact.open()
       },
+      handleSelectSale() {
+        this.$refs.selectSales.open()
+      },
       handleSelectDistributor() {
         this.$refs.selectDistributor.open()
       },
       handleSelectProduct() {
         this.$refs.selectProduct.open()
       },
+      handleSelectFollowUser() {
+        this.$refs.selectFollowUser.open()
+      },
       selectCustomer(val) {
         if (val && val.length > 0) {
           this.queryContact.custId = val[0].id
+          this.customerInfo = {
+            custId: val[0].id,
+            custName: val[0].custName,
+          }
+          this.form.custId = val[0].id
+          this.form.custName = val.map((item) => item.custName).join()
         }
-        this.form.custName = val.map((item) => item.custName).join()
-        console.log(this.form.custName)
-        console.log(val)
       },
       selectContact(val) {
-        this.form.contactName = val.map((item) => item.cuctName).join()
-        console.log(this.form.contactName)
-
-        console.log(val)
+        if (val && val.length > 0) {
+          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) => item.telephone).join()
+        }
+      },
+      selectSales(val) {
+        if (val && val.length > 0) {
+          this.form.saleId = val[0].id
+          this.form.saleName = val.map((item) => item.userName).join()
+        }
       },
       selectDistributor(val) {
-        this.form.distributorName = val.map((item) => item.distName).join()
-        console.log(this.form.distributorName)
-
-        console.log(val)
+        if (val && val.length > 0) {
+          this.form.distributorId = val[0].id
+          this.form.distributorName = val.map((item) => item.distName).join()
+        }
+      },
+      selectFollowUser(val) {
+        this.form.followUserName = val.map((item) => item.userName).join()
+      },
+      selectProduct(data) {
+        console.log(data)
+        let projData = data.map((item) => ({
+          prodId: item.id,
+          prodCode: item.prodCode,
+          prodName: item.prodName,
+          prodClass: item.prodClass,
+          prodPrice: item.guidPrice,
+          prodNum: 1,
+        }))
+        this.productData.push(...projData)
+        this.productData = this.removeDuplicateObj(this.productData)
+      },
+      // 数组对象去重
+      removeDuplicateObj(arr) {
+        let obj = {}
+        arr = arr.reduce((newArr, next) => {
+          obj[next.prodId] ? '' : (obj[next.prodId] = true && newArr.push(next))
+          return newArr
+        }, [])
+        return arr
+      },
+      // 修改产品列表数据
+      changeProductData(data) {
+        this.productData = this.productData.map((item) => {
+          return item.prodId === data.prodId ? data : item
+        })
+      },
+      delProductData(data) {
+        this.productData = this.productData.filter((item) => item.prodId !== data.prodId)
       },
-      selectProduct(val) {
-        console.log(val)
+      async getProductData(busId) {
+        const { data } = await businessApi.getProductByBusinessId({ id: busId })
+        this.productData = data
       },
       showEdit(row) {
+        this.activeSteps = 1
         if (!row) {
           this.title = '添加'
         } else {
           this.title = '编辑'
           this.form = Object.assign({}, row)
+          this.getProductData(row.id)
         }
         this.dialogFormVisible = true
       },
@@ -227,7 +334,7 @@
       save() {
         this.$refs['form'].validate(async (valid) => {
           if (valid) {
-            const { msg } = await businessApi.doEdit(this.form)
+            const { msg } = await businessApi.doAdd(this.form)
             this.$baseMessage(msg, 'success')
             this.$emit('fetch-data')
             this.close()

+ 294 - 0
src/views/proj/business/components/DetailsContact.vue

@@ -0,0 +1,294 @@
+<template>
+  <div style="height: 100%; width: 100%">
+    <vab-query-form>
+      <vab-query-form-left-panel :span="12">
+        <el-input
+          v-model="queryForm.keyWords"
+          placeholder="请输入姓名"
+          prefix-icon="el-icon-search"
+          style="width: 50%" />
+      </vab-query-form-left-panel>
+      <vab-query-form-right-panel :span="12">
+        <el-button icon="el-icon-plus" @click="handleAddContact">新建联系人</el-button>
+        <el-button @click="handleSelectContact">关联</el-button>
+        <el-button type="danger" @click="handleDisassociation">解除关联</el-button>
+      </vab-query-form-right-panel>
+    </vab-query-form>
+    <el-table
+      v-loading="listLoading"
+      border
+      :data="contactList"
+      height="calc(100% - 42px)"
+      @selection-change="setSelectRows">
+      <el-table-column align="center" type="selection" />
+      <el-table-column align="center" label="姓名" prop="cuctName" />
+      <el-table-column align="center" label="岗位" prop="postion" />
+      <el-table-column align="center" label="电话" prop="telephone" />
+      <el-table-column align="center" label="微信" prop="wechat" />
+      <el-table-column align="center" label="邮箱" prop="email" />
+      <!--      <el-table-column align="center" label="是否决策人">-->
+      <!--        <template slot-scope="scope">-->
+      <!--          <el-switch v-model="scope.row.policy" :active-value="1" disabled :inactive-value="0" />-->
+      <!--        </template>-->
+      <!--      </el-table-column>-->
+      <el-table-column align="center" label="操作">
+        <template slot-scope="scope">
+          <el-button v-if="scope.row.id !== primacyContactId" type="text" @click="setPrimacyContact(scope.row)">
+            设为首要联系人
+          </el-button>
+          <p v-else>首要联系人</p>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- 选择客户联系人弹窗 -->
+    <select-contact
+      ref="selectContact"
+      :default-customer="customerInfo"
+      :query-params="queryContact"
+      @save="selectContact" />
+    <!-- 新建联系人弹窗 -->
+    <customer-contact ref="contact" @contactSave="handleSelectContact" />
+  </div>
+</template>
+
+<script>
+  import businessApi from '@/api/proj/business'
+  import businessContactApi from '@/api/proj/businessContact'
+  import SelectContact from '@/components/select/SelectContact'
+  import CustomerContact from '@/views/customer/components/Contact'
+
+  export default {
+    name: 'Records',
+    components: { SelectContact, CustomerContact },
+    props: {
+      // 项目Id
+      busId: {
+        type: Number,
+        default: 0,
+      },
+      // 主要联系人Id
+      primacyContactId: {
+        type: Number,
+        default: 0,
+      },
+      // 客户信息{ custId: id, custName: custName}
+      customerInfo: {
+        type: Object,
+        default() {
+          return {}
+        },
+      },
+    },
+    data() {
+      return {
+        queryForm: {
+          cuctName: undefined,
+        },
+        listLoading: false,
+        selectRows: [],
+        contactList: [],
+        queryContact: {},
+      }
+    },
+    mounted() {
+      // this.fetchData()
+    },
+    methods: {
+      handleAddContact() {
+        this.$refs.contact.contactForm.custId = this.customerInfo.custId
+        this.$refs.contact.contactForm.custName = this.customerInfo.custName
+        this.$refs.contact.contactVisible = true
+      },
+      handleSelectContact() {
+        this.queryContact.custId = this.customerInfo.custId
+        if (!this.queryContact.custId) {
+          this.$message.warning('请先选择客户')
+          return
+        }
+        this.$refs.selectContact.open()
+      },
+      async selectContact(val) {
+        if (val && val.length > 0) {
+          let form = {
+            busId: this.busId,
+            contactIds: val.map((item) => item.id),
+          }
+          const { msg } = await businessContactApi.doAdd(form)
+          this.$baseMessage(msg, 'success')
+          await this.fetchData()
+        }
+      },
+      handleDisassociation() {
+        if (this.selectRows.length > 0) {
+          const ids = this.selectRows.map((item) => item.id)
+          this.$baseConfirm('你确定要解除关联选中项吗', null, async () => {
+            const { msg } = await businessContactApi.doDelete({ ids })
+            this.$baseMessage(msg, 'success')
+            await this.fetchData()
+          })
+        } else {
+          this.$baseMessage('未选中任何行', 'error')
+          return false
+        }
+      },
+      async setPrimacyContact(row) {
+        let form = {
+          id: this.busId,
+          contactId: row.contactId,
+          contactName: row.cuctName,
+          contactPostion: row.telephone,
+          contactTelephone: row.cuctName,
+        }
+        const { msg } = await businessApi.setPrimacyContact(form)
+        this.$baseMessage(msg, 'success')
+        this.$emit('fetch-data')
+      },
+      setSelectRows(val) {
+        this.selectRows = val
+      },
+      async fetchData() {
+        this.listLoading = true
+        this.queryForm.busId = this.busId
+        const { data } = await businessContactApi.getList(this.queryForm)
+        this.contactList = data
+        this.listLoading = false
+        console.log(this.contactList)
+      },
+    },
+  }
+</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: 190px;
+          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;
+              }
+            }
+          }
+        }
+
+        + 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;
+        }
+      }
+    }
+  }
+
+  .no-follow {
+    height: 100%;
+    width: 100%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    font-size: 12px;
+    color: rgba(0, 0, 0, 0.65);
+  }
+</style>

+ 228 - 0
src/views/proj/business/components/DetailsFollow.vue

@@ -0,0 +1,228 @@
+<template>
+  <div>
+    <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.contactsName }} 跟进({{ formatType(item.followType) }})</span>
+                  <span>
+                    <vab-icon icon="time-line" />
+                    {{ item.followDate }}
+                  </span>
+                </p>
+                <p>{{ item.followContent }}</p>
+                <div class="footer">
+                  <p>
+                    来自客户:
+                    <span>{{ item.custName }}</span>
+                  </p>
+                  <div>
+                    <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 v-if="item.showComment" class="comments">
+                <li v-for="comment in item.comments" :key="comment.id">
+                  <vab-icon class="user-avatar" icon="account-circle-fill" />
+                  <div class="text">
+                    <p>{{ comment.createdName }}</p>
+                    <p>{{ comment.content }}</p>
+                    <p>{{ comment.createdTime }}</p>
+                  </div>
+                </li>
+              </ul>
+            </transition>
+          </li>
+        </ul>
+      </li>
+    </ul>
+    <div v-else class="no-follow">暂无跟进记录</div>
+  </div>
+</template>
+
+<script>
+  import follow from '@/api/customer/follow'
+  import to from 'await-to-js'
+
+  export default {
+    name: 'Records',
+    props: {
+      busId: {
+        type: Number,
+        default: 0,
+      },
+    },
+    data() {
+      return {
+        followList: [],
+      }
+    },
+    mounted() {
+      console.log(this.busId)
+    },
+    methods: {
+      async fetchData() {
+        let params = {
+          targetId: String(this.busId),
+          DaysBeforeToday: 9999,
+        }
+        let err, res
+        ;[err, res] = await to(follow.getListByDay(params))
+        if (err) return
+        this.followList = res.data.list || []
+      },
+    },
+  }
+</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: 190px;
+          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;
+              }
+            }
+          }
+        }
+
+        + 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;
+        }
+      }
+    }
+  }
+
+  .no-follow {
+    height: 100%;
+    width: 100%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    font-size: 12px;
+    color: rgba(0, 0, 0, 0.65);
+  }
+</style>

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

@@ -0,0 +1,169 @@
+<!--
+ * @Author: liuzl 461480418@qq.com
+ * @Date: 2023-01-09 17:42:13
+ * @LastEditors: liuzhenlin
+ * @LastEditTime: 2023-01-09 17:55:42
+ * @Description: file content
+ * @FilePath: \订单全流程管理系统\src\views\contract\components\DetailsRecords.vue
+-->
+<template>
+  <ul class="records">
+    <li v-for="(value, key) in dynamicsList" :key="key">
+      <div class="date">
+        <h2>{{ key.split('-')[2] }}</h2>
+        <h3>{{ key.split('-').splice(0, 2).join('.') }}</h3>
+      </div>
+      <ul class="content">
+        <li v-for="(item, index) in dynamicsList[key]" :key="index">
+          <!-- <el-avatar class="user-avatar"
+                           :src="avatar" /> -->
+          <vab-icon class="user-avatar" icon="account-circle-fill" />
+          <div class="text">
+            <p class="action">{{ item.opnPeople }} {{ opnTypeFormat(item.opnType) }}</p>
+            <p>{{ item.opnDate }}</p>
+            <div v-if="item.opnContent">
+              <p v-if="item.opnType === '30'">
+                销售工程师:
+                <span>{{ item.opnContent.saleName }}</span>
+              </p>
+              <p v-if="item.opnType === '40' || item.opnType === '50'">
+                <span>{{ item.opnContent }}</span>
+              </p>
+
+              <p v-if="item.opnContent.custName">
+                客户名称:
+                <span>{{ item.opnContent.custName }}</span>
+              </p>
+              <template v-else-if="item.opnContent.contactName">
+                <p>
+                  联系人名称:
+                  <span>{{ item.opnContent.contactName }}</span>
+                </p>
+                <p>职务:{{ item.opnContent.contactPostion }}</p>
+                <p>手机:{{ item.opnContent.contactTelephone }}</p>
+              </template>
+            </div>
+          </div>
+        </li>
+      </ul>
+    </li>
+  </ul>
+</template>
+
+<script>
+  export default {
+    name: 'Records',
+    props: {
+      dynamicsList: {
+        // eslint-disable-next-line vue/require-prop-type-constructor
+        type: Array | Object,
+        default: () => [],
+      },
+    },
+    data() {
+      return {}
+    },
+
+    mounted() {
+      console.log(this.dynamicsList)
+    },
+
+    methods: {
+      opnTypeFormat(opnType) {
+        if (opnType === '10') {
+          return '创建了项目'
+        } else if (opnType === '20') {
+          return '更新了项目'
+        } else if (opnType === '30') {
+          return '项目转移'
+        } else if (opnType === '40') {
+          return '项目升级'
+        } else if (opnType === '50') {
+          return '项目降级'
+        } else if (opnType === '60') {
+          return '设置首要联系人'
+        } else if (opnType === '70') {
+          return '关联了联系人'
+        } else {
+          return ''
+        }
+      },
+    },
+  }
+</script>
+
+<style lang="scss" scoped>
+  .records {
+    margin: 0;
+    padding: 10px 20px;
+    list-style: none;
+    height: 100%;
+    overflow-y: 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 {
+        display: flex;
+
+        & + li {
+          margin-top: 10px;
+        }
+      }
+
+      .user-avatar {
+        font-size: 40px;
+      }
+
+      .text {
+        flex: 1;
+        padding-left: 20px;
+
+        p {
+          font-weight: 500;
+          margin: 0;
+          line-height: 20px;
+
+          span {
+            color: #1d66dc;
+          }
+        }
+
+        p:nth-child(2) {
+          margin-bottom: 10px;
+        }
+
+        .action {
+          font-weight: bold;
+          color: #333;
+        }
+      }
+    }
+  }
+</style>

+ 125 - 0
src/views/proj/business/components/FollowAdd.vue

@@ -0,0 +1,125 @@
+<template>
+  <el-dialog :title="title" :visible.sync="dialogFormVisible" @close="close">
+    <el-form ref="form" label-width="96px" :model="form" :rules="rules">
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="跟进类型" prop="followType">
+            <el-input v-model="form.followType" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="跟进时间" prop="followDate">
+            <el-date-picker v-model="form.followDate" placeholder="选择日期" style="width: 100%" type="datetime" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="24">
+          <el-form-item label="跟进内容" prop="followContent">
+            <el-input
+              v-model="form.followContent"
+              placeholder="请输入跟进内容"
+              rows="5"
+              show-word-limit
+              type="textarea" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="提醒对象" prop="reminders">
+            <el-input v-model="form.reminders" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="下次联系时间" prop="nextTime">
+            <el-date-picker v-model="form.nextTime" placeholder="选择日期" style="width: 100%" type="datetime" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="24">
+          <el-form-item label="备注" prop="remark">
+            <el-input v-model="form.remark" placeholder="请输入备注信息" rows="5" show-word-limit type="textarea" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+    <div slot="footer" class="dialog-footer">
+      <el-button @click="close">取 消</el-button>
+      <el-button type="primary" @click="save">确 定</el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+  import followApi from '@/api/customer/follow'
+
+  export default {
+    name: 'ConfigEdit',
+    props: {
+      // 跟进信息
+      followup: {
+        type: Object,
+        default() {
+          return {}
+        },
+      },
+    },
+    data() {
+      return {
+        form: {
+          followType: '',
+          followDate: '',
+          followContent: '',
+          targetId: '',
+          targetName: '',
+          targetType: '',
+          custId: '',
+          custName: '',
+          contactsId: '',
+          contactsName: '',
+          reminders: '',
+          nextTime: '',
+          remark: '',
+        },
+        rules: {
+          followType: [{ required: true, trigger: 'blur', message: '请输入跟进类型' }],
+          followDate: [{ required: true, trigger: 'blur', message: '请输入跟进时间' }],
+          followContent: [{ required: true, trigger: 'blur', message: '请输入跟进内容' }],
+          targetId: [{ required: true, trigger: 'blur', message: '请选择跟进对象' }],
+          targetName: [{ required: true, trigger: 'blur', message: '跟进对象不能为空' }],
+          targetType: [{ required: true, trigger: 'blur', message: '请输入标题' }],
+          custId: [{ required: true, trigger: 'blur', message: '请选择关联客户' }],
+          custName: [{ required: true, trigger: 'blur', message: '客户名称不能为空' }],
+          contactsId: [{ required: true, trigger: 'blur', message: '请选择关联联系人' }],
+          contactsName: [{ required: true, trigger: 'blur', message: '联系人姓名不能为空' }],
+        },
+        title: '',
+        dialogFormVisible: false,
+      }
+    },
+    watch: {},
+    created() {},
+    methods: {
+      showEdit() {
+        this.title = '添加跟进记录'
+        this.form = { ...this.followup }
+        console.log(this.followup)
+        console.log(this.form)
+        this.dialogFormVisible = true
+      },
+      close() {
+        this.$refs['form'].resetFields()
+        this.form = this.$options.data().form
+        this.dialogFormVisible = false
+      },
+      save() {
+        this.$refs['form'].validate(async (valid) => {
+          if (valid) {
+            const { msg } = await followApi.addFollowUp(this.form)
+            this.$baseMessage(msg, 'success')
+            this.$emit('fetch-data')
+            this.close()
+          } else {
+            return false
+          }
+        })
+      },
+    },
+  }
+</script>

+ 151 - 0
src/views/proj/business/components/ProductTable.vue

@@ -0,0 +1,151 @@
+<!--
+ * @Author: liuzhenlin 461480418@qq.ocm
+ * @Date: 2023-01-05 17:43:46
+ * @LastEditors: liuzhenlin
+ * @LastEditTime: 2023-01-07 14:36:08
+ * @FilePath: \订单全流程管理系统\src\components\table\ProductTable.vue
+-->
+<template>
+  <div class="table-container">
+    <el-table border :data="data">
+      <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 == 'prodPrice'">
+            <amount-input
+              v-model.trim="row.price"
+              placeholder="请输入金额"
+              :value="row.price"
+              @change="handleChange(row)" />
+          </span>
+          <span v-else-if="item.prop == 'prodNum'">
+            <el-input-number v-model="row.prodNum" :min="0" size="mini" @change="handleChange(row)" />
+          </span>
+          <span v-else-if="item.label == '合计'">
+            {{ calculatedDiscount(row.prodPrice, row.prodNum) }}
+          </span>
+          <span v-else>{{ row[item.prop] }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column align="center" label="操作" width="68px">
+        <template #default="{ row }">
+          <el-button type="text" @click="handleDel(row)">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+  </div>
+</template>
+
+<script>
+  import AmountInput from '@/components/currency'
+
+  export default {
+    name: 'ProductTable',
+    components: {
+      AmountInput,
+    },
+    props: {
+      productData: {
+        type: Array,
+        default() {
+          return []
+        },
+      },
+      checkList: {
+        type: Array,
+        default() {
+          return []
+        },
+      },
+    },
+    data() {
+      return {
+        data: [],
+        columns: [
+          {
+            label: '产品编码',
+            width: 'auto',
+            prop: 'prodCode',
+          },
+          {
+            label: '产品名称',
+            width: 'auto',
+            prop: 'prodName',
+          },
+          {
+            label: '产品类别',
+            width: 'auto',
+            prop: 'prodClass',
+          },
+          {
+            label: '产品单价',
+            width: 'auto',
+            prop: 'prodPrice',
+          },
+          {
+            label: '数量',
+            width: 'auto',
+            prop: 'prodNum',
+          },
+        ],
+      }
+    },
+    watch: {
+      productData() {
+        this.data = []
+        this.data = this.productData.map((item) => {
+          return { ...item, prodPrice: item['prodPrice'] }
+        })
+        console.log(this.data)
+      },
+    },
+    created() {
+      this.data = []
+      this.data = this.productData.map((item) => {
+        return { ...item, prodPrice: item['prodPrice'] }
+      })
+    },
+    methods: {
+      // input修改
+      handleChange(row) {
+        console.log('row', row)
+        this.$emit('changeProductData', row)
+      },
+      // 删除
+      handleDel(row) {
+        this.$emit('delProductData', row)
+      },
+      // 计算总价
+      calculatedDiscount(price, count) {
+        let intPrice = price * 100
+        return this.formatPrice((intPrice * count) / 100)
+      },
+    },
+  }
+</script>
+
+<style lang="scss" scoped>
+  .custom-table-container {
+    :deep() {
+      i {
+        cursor: pointer;
+      }
+    }
+
+    .stripe-panel,
+    .border-panel {
+      margin: 0 10px #{$base-margin/2} 10px !important;
+    }
+  }
+</style>
+<style lang="scss">
+  .table-container {
+    width: 100%;
+  }
+</style>

+ 24 - 5
src/views/proj/business/components/Transfer.vue

@@ -7,7 +7,7 @@
           placeholder="选择人员"
           readonly
           suffix-icon="el-icon-user-solid"
-          @focus="selectUser" />
+          @focus="handleSelectUser" />
       </el-form-item>
       <el-form-item label="备注信息" prop="remark">
         <el-input
@@ -23,27 +23,46 @@
       <el-button @click="close">取 消</el-button>
       <el-button type="primary" @click="save">确 定</el-button>
     </template>
+    <!-- 选择负责人弹窗 -->
+    <select-user ref="selectUser" @save="selectUser" />
   </el-dialog>
 </template>
 
 <script>
   import businessApi from '@/api/proj/business'
+  import SelectUser from '@/components/select/SelectUser'
 
   export default {
     name: 'Transfer',
+    components: { SelectUser },
     data() {
       return {
-        form: {},
+        form: {
+          Id: undefined,
+          userId: undefined,
+          userName: undefined,
+          remark: undefined,
+        },
         rules: {
-          userName: [{ required: true, message: '不能为空', trigger: 'blur' }],
+          userName: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
         },
         title: '转移项目',
         dialogFormVisible: false,
       }
     },
     methods: {
-      selectUser() {},
-      open() {
+      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.userName).join()
+        console.log(this.form.userName)
+      },
+      open(row) {
+        this.form.Id = row.id
         this.dialogFormVisible = true
       },
       close() {

+ 564 - 0
src/views/proj/business/detail.vue

@@ -0,0 +1,564 @@
+<template>
+  <div class="details">
+    <el-row :gutter="10">
+      <el-col :span="16">
+        <div class="title">
+          <p>项目</p>
+          <h3>
+            {{ details.nboName }}
+            <span>
+              <el-button :disabled="details.nboType === 'A'" @click="handleBusinessGradation('升级')">升级</el-button>
+              <el-button :disabled="details.nboType === 'C'" @click="handleBusinessGradation('降级')">降级</el-button>
+              <el-button @click="handleTransfer">转移项目</el-button>
+              <el-button>创建工单</el-button>
+              <el-button>创建合同</el-button>
+            </span>
+          </h3>
+        </div>
+        <header>
+          <el-descriptions :colon="false" :column="6" direction="vertical">
+            <el-descriptions-item content-class-name="my-content" label="项目编码" label-class-name="my-label">
+              {{ details.nboName }}
+            </el-descriptions-item>
+            <el-descriptions-item content-class-name="my-content" label="客户名称" label-class-name="my-label">
+              {{ details.custName }}
+            </el-descriptions-item>
+            <el-descriptions-item content-class-name="my-content" label="项目预算" label-class-name="my-label">
+              {{ details.nboBudget }}
+            </el-descriptions-item>
+            <el-descriptions-item content-class-name="my-content" label="销售工程师" label-class-name="my-label">
+              {{ details.saleName }}
+            </el-descriptions-item>
+            <el-descriptions-item content-class-name="my-content" label="项目类型" label-class-name="my-label">
+              {{ details.nboType }} 类
+            </el-descriptions-item>
+            <el-descriptions-item content-class-name="my-content" label="最后跟进时间" label-class-name="my-label">
+              {{ details.finalFollowTime }}
+            </el-descriptions-item>
+          </el-descriptions>
+        </header>
+        <el-tabs v-model="activeName" @tab-click="handleClick">
+          <el-tab-pane label="跟进记录" name="follow">
+            <details-follow ref="follow" :bus-id="id" />
+          </el-tab-pane>
+          <el-tab-pane label="详细信息" name="details">
+            <el-descriptions border :column="2" size="medium">
+              <el-descriptions-item label="商机名称">
+                {{ details.nboName }}
+              </el-descriptions-item>
+              <el-descriptions-item label="客户名称">
+                {{ details.custName }}
+              </el-descriptions-item>
+              <el-descriptions-item label="商机金额">
+                {{ details.estTransPrice }}
+              </el-descriptions-item>
+              <el-descriptions-item label="预计成交日期">
+                {{ details.estTransTime }}
+              </el-descriptions-item>
+              <el-descriptions-item label="主要联系人">
+                {{ details.contactName }}
+              </el-descriptions-item>
+              <el-descriptions-item label="联系人电话">
+                {{ details.contactTelephone }}
+              </el-descriptions-item>
+              <el-descriptions-item label="销售模式">
+                {{ details.salesModel }}
+              </el-descriptions-item>
+              <el-descriptions-item label="经销商/代理商">
+                {{ details.distributorName }}
+              </el-descriptions-item>
+              <el-descriptions-item label="下次联系时间">
+                {{ details.nextFollowTime }}
+              </el-descriptions-item>
+              <el-descriptions-item label="创建人">
+                {{ details.createdName }}
+              </el-descriptions-item>
+              <el-descriptions-item label="创建时间">
+                {{ details.createdTime }}
+              </el-descriptions-item>
+              <el-descriptions-item label="更新时间">
+                {{ details.updatedTime }}
+              </el-descriptions-item>
+              <el-descriptions-item label="最后跟进时间">
+                {{ details.finalFollowTime }}
+              </el-descriptions-item>
+              <el-descriptions-item label="备注" :span="24">
+                {{ details.remark }}
+              </el-descriptions-item>
+            </el-descriptions>
+          </el-tab-pane>
+          <el-tab-pane label="联系人" name="contact">
+            <details-contact
+              ref="contact"
+              :bus-id="id"
+              :customer-info="{ custId: details.custId, custName: details.custName }"
+              :primacy-contact-id="details.contactId"
+              @fetch-data="init" />
+          </el-tab-pane>
+          <el-tab-pane label="合同记录" name="contract" />
+          <el-tab-pane label="工单记录" name="worksheet" />
+          <el-tab-pane label="归属记录" name="belong">
+            <el-table v-loading="belongLoading" border :data="belongs" height="calc(100% - 42px)">
+              <el-table-column align="center" label="归属销售" prop="opnContent.saleName" />
+              <el-table-column align="center" label="原来归属" prop="opnContent.origSaleName" />
+              <el-table-column align="center" label="操作方式" prop="opnType">
+                <template slot-scope="scope">
+                  <el-tag v-if="scope.row.opnType == 30">转移</el-tag>
+                </template>
+              </el-table-column>
+              <el-table-column align="center" label="操作人" prop="createdName" />
+              <el-table-column align="center" label="操作时间" min-width="160px" prop="opnDate" />
+              <el-table-column align="center" label="备注" prop="remark" />
+            </el-table>
+          </el-tab-pane>
+        </el-tabs>
+      </el-col>
+      <el-col :span="8">
+        <div class="buttons">
+          <el-button type="primary" @click="handleEdit">编辑</el-button>
+          <el-button @click="handleDelete">删除</el-button>
+        </div>
+        <details-records :dynamics-list="dynamicsList" />
+      </el-col>
+    </el-row>
+    <!-- 编辑 -->
+    <edit ref="edit" @fetch-data="init" />
+    <!-- 转移 -->
+    <transfer ref="transfer" @fetch-data="init" />
+  </div>
+</template>
+
+<script>
+  import to from 'await-to-js'
+  import { mapGetters } from 'vuex'
+  import businessApi from '@/api/proj/business'
+  import Edit from './components/BusinessEdit'
+  import Transfer from './components/Transfer'
+  import DetailsContact from './components/DetailsContact'
+  import DetailsRecords from './components/DetailsRecords'
+  import DetailsFollow from './components/DetailsFollow'
+
+  export default {
+    name: 'BusinessDetail',
+    components: { Edit, Transfer, DetailsContact, DetailsRecords, DetailsFollow },
+    data() {
+      return {
+        id: undefined,
+        details: {},
+        product: [],
+        abstract: {},
+        activeName: 'follow',
+        selectRows: [],
+        followList: [],
+        contactList: [],
+        dynamicsList: [],
+        belongLoading: false,
+        belongTotal: 0,
+        belongs: [],
+      }
+    },
+    computed: {
+      ...mapGetters({
+        avatar: 'user/avatar',
+        username: 'user/username',
+      }),
+    },
+    mounted() {
+      this.id = parseInt(this.$route.query.id)
+      this.init()
+    },
+    methods: {
+      async init() {
+        Promise.all([businessApi.getEntityById({ id: this.id })]).then(([details]) => {
+          console.log(details.data)
+          if (details.data) this.details = details.data
+        })
+        await this.getRecord()
+        await this.handleClick({ name: this.activeName })
+      },
+      async getRecord() {
+        const [err, res] = await to(businessApi.getBusinessDynamics({ busId: this.id }))
+        if (err) return
+        if (res.data.list[0]) {
+          let obj = res.data.list[0]
+          const keys = Object.keys(obj).reverse()
+          let records = {}
+          for (const item of keys) {
+            records[item] = obj[item]
+          }
+
+          this.dynamicsList = records
+          console.log(this.dynamicsList)
+        }
+      },
+      async handleClick(tab) {
+        if (tab.name == 'follow') {
+          await this.$refs.follow.fetchData()
+          return
+        } else if (tab.name == 'contact') {
+          await this.$refs.contact.fetchData()
+          return
+        } else if (tab.name == 'contract') {
+          return
+        } else if (tab.name == 'worksheet') {
+          return
+        } else if (tab.name == 'belong') {
+          // 获取项目转移记录
+          this.belongLoading = true
+          const {
+            data: { list, total },
+          } = await businessApi.getBusinessDynamicsList({ busId: this.id, opnType: '30' })
+          this.belongs = list ? list : []
+          this.belongTotal = total
+          this.belongLoading = false
+          return
+        } else {
+          return
+        }
+      },
+      handleTransfer() {
+        this.$refs.transfer.open(this.details)
+      },
+      // 编辑
+      handleEdit() {
+        this.$refs.edit.showEdit(this.details)
+      },
+      // 删除
+      handleDelete() {
+        this.$baseConfirm('你确定要删除当前项目吗', null, async () => {
+          const { msg } = await businessApi.doDelete({ ids: [this.id] })
+          this.$baseMessage(msg, 'success')
+          this.back()
+        })
+      },
+      // 业务调级(升级、降级)
+      handleBusinessGradation(type) {
+        let data = { id: this.id, nboType: '' }
+        if ((type === '降级' && this.details.nboType === 'A') || (type === '升级' && this.details.nboType === 'C')) {
+          data.nboType = 'B'
+        } else {
+          data.nboType = type === '升级' ? 'A' : 'C'
+        }
+        this.$baseConfirm('你确定要对当前项目' + type + '吗', null, async () => {
+          const { msg } = await businessApi.businessGradation(data)
+          this.$baseMessage(msg, 'success')
+          await this.init()
+        })
+      },
+      back() {
+        this.$router.go(-1)
+      },
+    },
+  }
+</script>
+
+<style lang="scss" scoped>
+  $base: '.details';
+  #{$base} {
+    height: calc(100vh - 60px - 50px - 12px * 2 - 40px);
+    display: flex;
+    padding: 20px 40px;
+
+    > .el-row {
+      flex: 1;
+
+      > .el-col {
+        height: 100%;
+      }
+    }
+
+    .title {
+      p,
+      h3 {
+        margin: 0;
+      }
+
+      p {
+        font-size: 14px;
+        font-weight: 400;
+        line-height: 22px;
+      }
+
+      h3 {
+        font-size: 24px;
+        font-weight: 500;
+        line-height: 36px;
+        color: #333;
+        display: flex;
+        justify-content: space-between;
+      }
+    }
+
+    header {
+      height: 74px;
+      background: rgba(196, 196, 196, 0.5);
+      border-radius: 4px;
+      display: flex;
+      align-items: center;
+      padding: 0 20px;
+      margin-top: 16px;
+
+      ::v-deep .el-descriptions__body {
+        background: transparent;
+      }
+
+      ::v-deep .my-label {
+        font-size: 14px;
+        font-weight: 600;
+        color: #1d66dc;
+      }
+
+      ::v-deep .my-content {
+        font-size: 14px;
+        font-weight: 600;
+        color: #333;
+      }
+    }
+
+    .el-tabs {
+      height: calc(100% - 148px);
+      display: flex;
+      flex-direction: column;
+
+      ::v-deep .el-tabs__content {
+        flex: 1;
+
+        .el-tab-pane {
+          height: 100%;
+        }
+      }
+    }
+
+    .buttons {
+      padding-top: 28px;
+      text-align: right;
+    }
+
+    .records {
+      margin: 0;
+      padding: 10px 20px;
+      list-style: none;
+      height: calc(100% - 60px);
+      overflow-y: 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 {
+          display: flex;
+
+          & + li {
+            margin-top: 10px;
+          }
+        }
+
+        .user-avatar {
+          font-size: 40px;
+        }
+
+        .text {
+          flex: 1;
+          padding-left: 20px;
+
+          p {
+            font-weight: 500;
+            margin: 0;
+            line-height: 20px;
+
+            span {
+              color: #1d66dc;
+            }
+          }
+
+          p:nth-child(2) {
+            margin-bottom: 10px;
+          }
+
+          .action {
+            font-weight: bold;
+            color: #333;
+          }
+        }
+      }
+    }
+
+    .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: 190px;
+            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;
+                }
+              }
+            }
+          }
+
+          + 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;
+          }
+        }
+      }
+    }
+
+    .no-follow {
+      height: 100%;
+      width: 100%;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      font-size: 12px;
+      color: rgba(0, 0, 0, 0.65);
+    }
+  }
+
+  .height-enter-active,
+  .height-leave-active {
+    transition: all 0.5s;
+  }
+
+  .height-enter-to,
+  .height-leave {
+    height: 190px;
+  }
+
+  .height-enter, .height-leave-to /* .fade-leave-active below version 2.1.8 */ {
+    height: 0;
+  }
+
+  ::v-deep .el-descriptions__table tbody {
+    td,
+    th {
+      width: 25%;
+    }
+  }
+</style>

+ 85 - 22
src/views/proj/business/index.vue

@@ -1,6 +1,15 @@
 <template>
   <div class="business-container">
-    <vab-query-form>
+    <el-menu :default-active="activeIndex" mode="horizontal" @select="handleMenuSelect">
+      <el-menu-item index="ALL">全部项目</el-menu-item>
+      <el-menu-item index="C">C类项目</el-menu-item>
+      <el-menu-item index="B">B类项目</el-menu-item>
+      <el-menu-item index="A">A类项目</el-menu-item>
+      <el-menu-item index="DEAL">成交项目</el-menu-item>
+      <el-menu-item index="VOID">无效项目</el-menu-item>
+    </el-menu>
+
+    <vab-query-form style="margin-top: 10px">
       <vab-query-form-top-panel>
         <el-form ref="queryForm" :inline="true" :model="queryForm" @submit.native.prevent>
           <el-form-item prop="nboName">
@@ -40,9 +49,9 @@
       <vab-query-form-left-panel :span="12">
         <el-button icon="el-icon-plus" type="primary" @click="handleEdit">新增项目</el-button>
         <el-button icon="el-icon-refresh" type="primary" @click="handleTransfer">转移项目</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-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-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">
@@ -60,11 +69,19 @@
         :prop="item.prop"
         show-overflow-tooltip
         :sortable="item.sortable"
-        :width="item.width" />
+        :width="item.width">
+        <template #default="{ row }">
+          <el-button v-if="item.prop === 'nboName'" class="link-button" type="text" @click="handleDetail(row)">
+            {{ row.nboName }}
+          </el-button>
+          <span v-else-if="item.prop === 'approStatus'">{{ row[item.prop] }}</span>
+          <span v-else>{{ row[item.prop] }}</span>
+        </template>
+      </el-table-column>
 
       <el-table-column align="center" label="操作" width="120">
         <template #default="{ row }">
-          <el-button type="text" @click="handleEdit(row)">跟进</el-button>
+          <!--          <el-button type="text" @click="handleFollow(row)">跟进</el-button>-->
           <el-button type="text" @click="handleEdit(row)">编辑</el-button>
           <!--          <el-button type="text" @click="handleDelete(row)">删除</el-button>-->
         </template>
@@ -78,8 +95,12 @@
       :total="total"
       @current-change="handleCurrentChange"
       @size-change="handleSizeChange" />
+    <!-- 编辑 -->
     <edit ref="edit" @fetch-data="fetchData" />
+    <!-- 转移 -->
     <transfer ref="transfer" @fetch-data="fetchData" />
+    <!-- 添加跟进记录 -->
+    <follow-add ref="follow" :followup.sync="followup" @fetch-data="fetchData" />
   </div>
 </template>
 
@@ -87,56 +108,58 @@
   import businessApi from '@/api/proj/business'
   import Edit from './components/BusinessEdit'
   import Transfer from './components/Transfer'
+  import FollowAdd from './components/FollowAdd'
   import TableTool from '@/components/table/TableTool'
 
   export default {
     name: 'Business',
-    components: { Edit, Transfer, TableTool },
+    components: { Edit, Transfer, TableTool, FollowAdd },
     data() {
       return {
-        height: this.$baseTableHeight(2),
+        activeIndex: 'ALL',
+        height: this.$baseTableHeight(2) - 20,
         checkList: [],
         columns: [
           {
             label: '商机标题',
-            width: 'auto',
+            width: '120px',
             prop: 'nboName',
             sortable: true,
             disableCheck: true,
           },
           {
             label: '关联客户',
-            width: 'auto',
+            width: '200px',
             prop: 'custName',
           },
           {
             label: '审批状态',
-            width: 'auto',
+            width: '320px',
             prop: 'approStatus',
           },
           {
             label: '商机状态',
-            width: 'auto',
+            width: '320px',
             prop: 'nboPhase',
           },
           {
             label: '商机类别',
-            width: 'auto',
+            width: '320px',
             prop: 'nboType',
           },
           {
             label: '商机金额',
-            width: 'auto',
-            prop: 'nboBudget',
+            width: '320px',
+            prop: 'estTransPrice',
           },
           {
             label: '最后跟进时间',
-            width: 'auto',
+            width: '320px',
             prop: 'finalFollowTime',
           },
           {
             label: '下次跟进时间',
-            width: 'auto',
+            width: '320px',
             prop: 'nextFollowTime',
           },
         ],
@@ -154,6 +177,8 @@
           saleName: undefined,
         },
         nboTypeOptions: [],
+        approStatusOptions: [],
+        followup: {},
       }
     },
     computed: {
@@ -163,16 +188,51 @@
     },
     created() {
       this.fetchData()
-      this.getDicts('proj-nbo-type').then((response) => {
+      this.getDicts('proj_nbo_type').then((response) => {
         this.nboTypeOptions = response.data.values || []
       })
+      this.getDicts('proj_appro_status').then((response) => {
+        this.approStatusOptions = response.data.values || []
+      })
     },
     methods: {
-      setSelectRows(val) {
-        this.selectRows = val
+      approStatusFormat() {},
+      handleMenuSelect(key) {
+        this.queryForm.nboType = key
+        // 全不项目,成交项目,无效项目 项目类型不传
+        if (key === 'ALL' || key === 'DEAL' || key === 'VOID') {
+          this.queryForm.nboType = ''
+        }
+        this.fetchData()
       },
-      handleTransfer(row) {
-        this.$refs['transfer'].open(row)
+      handleTransfer() {
+        if (this.selectRows.length !== 1) {
+          this.$baseMessage('请选择一个项目', 'warning')
+          return
+        }
+        this.$refs['transfer'].open(this.selectRows[0])
+      },
+      // 跳转详情
+      handleDetail(row) {
+        this.$router.push({
+          path: '/opportunity/detail',
+          query: {
+            id: row.id,
+          },
+        })
+      },
+      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()
       },
       handleEdit(row) {
         if (row.id) {
@@ -219,6 +279,9 @@
         this.resetForm('queryForm')
         this.fetchData()
       },
+      setSelectRows(val) {
+        this.selectRows = val
+      },
       async fetchData() {
         this.listLoading = true
         const { data } = await businessApi.getList(this.queryForm)

+ 4 - 1
src/views/system/dict/components/DataEdit.vue

@@ -56,7 +56,10 @@
     name: 'DataEdit',
     data() {
       return {
-        form: {},
+        form: {
+          dictSort: 1,
+          status: '10',
+        },
         rules: {
           dictLabel: [{ required: true, message: '数据标签不能为空', trigger: 'blur' }],
           dictValue: [{ required: true, message: '数据键值不能为空', trigger: 'blur' }],

+ 3 - 1
src/views/system/dict/components/TypeEdit.vue

@@ -38,7 +38,9 @@
     name: 'TypeEdit',
     data() {
       return {
-        form: {},
+        form: {
+          status: '10',
+        },
         rules: {
           dictName: [{ required: true, message: '字典名称不能为空', trigger: 'blur' }],
           dictType: [{ required: true, message: '字典类型不能为空', trigger: 'blur' }],