Browse Source

'修改商机管理'

zhujun 2 năm trước cách đây
mục cha
commit
c05f266e1f

+ 25 - 0
src/api/saleManage/quotation.js

@@ -65,3 +65,28 @@ export async function getExport(id) {
     }
     return Promise.reject(new Error(res.data.message));
   }
+
+
+  /**
+   * 产品分类
+   */
+  
+  export async function getProduceTreeByPid() {
+    const res = await request.get(`/main/categoryLevel/getProduceTreeByPid?type=1`, {});
+    if (res.data.code == 0) {
+      return res.data.data;
+    }
+    return Promise.reject(new Error(res.data.message));
+  }
+
+/**
+   * 产品列表
+   */
+  export async function getProductList(params) {
+    const res = await request.get(`/main/category/getList`, { params });
+    if (res.data.code == 0) {
+      return res.data.data;
+    }
+    return Promise.reject(new Error(res.data.message));
+  }
+  

+ 4 - 1
src/enum/dict.js

@@ -21,6 +21,10 @@ export default {
   企业类型: 'company_category',
   结算方式: 'settlement_mode',
   付款方式: 'pay_way',
+  商机来源:'business_opport_code',
+  影响力:'influence_level_code',
+  态度:'attittude_code',
+  商机阶段:'business_stage_code',
   客户联系人状态: 'contact_link_status'
 };
 
@@ -33,7 +37,6 @@ export const numberList = [
   'order_source',
   'production_status',
   'plan_type',
-  'pay_way',
   'delivery_status'
 ];
 

+ 151 - 20
src/views/saleManage/businessOpportunity/components/addContactDialog.vue

@@ -8,9 +8,13 @@
     width="80%"
     @close="cancel"
   >
-
+  <el-card
+        shadow="never"
+        header="基本信息"
+        body-style="padding: 22px 20px 0 0;"
+      >
   <el-form
-          label-width="100px"
+          label-width="120px"
           ref="form"
           :model="form"
           :rules="rules"
@@ -32,16 +36,45 @@
             </el-col>
             <el-col :span="8">
               <el-form-item label="选择负责人" prop="responsibleName">
-                <el-input placeholder="请选择" v-model="form.responsibleName"  @click.native="handHead" maxlength="50"></el-input>
+                <el-input placeholder="请选择" v-model="userInfo.name" disabled maxlength="50"></el-input>
               </el-form-item>
             </el-col>
             <el-col :span="8">
-              <el-form-item label="	商机来源" prop="source">
-                <el-input
-                  placeholder="请输入"
-                  v-model="form.source"
-                  maxlength="100"
-                ></el-input>
+              <el-form-item label="商机来源" prop="sourceCode">
+                <DictSelection  dictName="商机来源" clearable v-model="form.sourceCode">
+                </DictSelection>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="赢单率" prop="winRate">
+                <el-input placeholder="请输入内容" v-model="form.winRate">
+                    <template slot="append">%</template>
+              </el-input>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="预算" prop="budget">
+                <el-input placeholder="请输入内容" v-model="form.budget">
+                    <template slot="append">万元</template>
+              </el-input>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="预计结单日期" prop="expectedClosingDate">
+                <el-date-picker
+                v-model="form.expectedClosingDate"
+                  value-format="yyyy-MM-dd"
+                  placeholder="请选择"
+                  type="date"
+                  style="width: 200px"
+                  class="filter-item"
+                ></el-date-picker>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="商机阶段" prop="stageCode">
+                <DictSelection  dictName="商机阶段" clearable v-model="form.stageCode">
+                </DictSelection>
               </el-form-item>
             </el-col>
             <el-col :span="16">
@@ -59,7 +92,30 @@
             </el-col>
           </el-row>
         </el-form>
-
+      </el-card>
+      <el-card
+        shadow="never"
+        header="竞品"
+        body-style="padding: 22px 20px 0 20px;"
+      >
+        <businessAddTable
+          ref="businessAddTable"
+          @timeAll="getDetailTable"
+          :delDetailIds="delDetailIds"
+        ></businessAddTable>
+      </el-card>
+      <el-card
+        shadow="never"
+        header="关键人信息"
+        body-style="padding: 22px 20px 0 20px;"
+      >
+        <personnelAddTable
+          ref="personnelAddTable"
+          :contactId="form.contactId"
+          @timeAll="getPersonnelAddTable"
+          :delDetailIds="PersonnelDetailIds"
+        ></personnelAddTable>
+      </el-card>
     <div slot="footer" class="footer">
       <el-button type="primary" @click="save">保存</el-button>
       <el-button @click="cancel">返回</el-button>
@@ -70,10 +126,13 @@
 </template>
 
 <script>
+import store from '@/store';
 import {categoryData} from 'element-china-category-data';
 import fileUpload from '@/components/upload/fileUpload';
 import {cityData} from 'ele-admin/packages/utils/regions';
 import dictMixins from '@/mixins/dictMixins';
+import businessAddTable from './businessAddTable.vue'
+import personnelAddTable from './personnelAddTable.vue'
 import deptSelect from '@/components/CommomSelect/dept-select.vue';
 import personSelect from '@/components/CommomSelect/person-select.vue';
 import {listOrganizations} from '@/api/system/organization';
@@ -90,6 +149,8 @@ export default {
     mixins: [dictMixins],
     components: {
       fileUpload,
+      businessAddTable,
+      personnelAddTable,
       deptSelect,
       headList,
       parentList,
@@ -99,7 +160,14 @@ export default {
       let formDef = {
           id:'',
           contactName: '',
-          contactId: 0,
+          contactId: '',
+          budget:null,
+          expectedClosingDate:'',
+          sourceCode:'',
+          sourceName:'',
+          stageCode:'',
+          stageName:'',
+          winRate:null,
           name: '',
           remark: '',
           responsibleName: '',
@@ -109,9 +177,12 @@ export default {
         };
    
       return {
+        delDetailIds: [],
+        PersonnelDetailIds:[],
         visible: false,
         title: '',
         row: {},
+        userInfo:{},
         activeName: 'base',
         formDef,
         form: copyObj(formDef),
@@ -125,10 +196,16 @@ export default {
             { required: true, message: '请选择客户', trigger: 'change' }
           ],
           name: [
-            { required: true, message: '请输入客户名称', trigger: 'blur' }
+            { required: true, message: '请输入商机名称', trigger: 'blur' }
+          ],
+          expectedClosingDate:[
+            { required: true, message: '请选择预计结单日期', trigger: 'blur' }
+          ],
+          sourceCode:[
+          { required: true, message: '请选择商机来源名称', trigger: 'change' }
           ],
-          responsibleName: [
-            { required: true, message: '请选择负责人', trigger: 'change' }
+          stageCode:[
+          { required: true, message: '请选择商机阶段名称', trigger: 'change' }
           ]
         },
       
@@ -147,7 +224,21 @@ export default {
         groupData: [],
       };
     },
+    mounted(){
+      this.$nextTick(()=>{
+        this.userInfo=store.state.user.info
+      })
+      
+    },
     methods: {
+      //获取竞品
+      getDetailTable (val) {
+        this.form.competAnalysisList = val;
+      },
+      //获取关键人信息
+      getPersonnelAddTable(val){
+        this.form.partyList = val;
+      },
       //选择客户回调
       changeParent(obj) {
         this.$set( this.form, 'contactId',  obj.id)
@@ -179,7 +270,23 @@ export default {
           this.isUpdate = false;
         } else {
           this.isUpdate = true;
-          this.form=row
+          this.getDetailData(row.id)
+        }
+      },
+
+      //获取详情
+      async getDetailData (id) {
+        this.loading = true;
+        const data = await getDetail(id);
+        this.loading = false;
+        if (data) {
+          this.$nextTick(()=>{
+            this.form = data;
+            this.$refs.businessAddTable&&this.$refs.businessAddTable.putTableValue(data.competAnalysisList)
+            this.$refs.personnelAddTable&&this.$refs.personnelAddTable.putTableValue(data.partyList)
+          })
+          
+          
         }
       },
 
@@ -199,7 +306,8 @@ export default {
     
    
       getValidate() {
-          return  new Promise((resolve, reject) => {
+        return Promise.all([
+            new Promise((resolve, reject) => {
               this.$refs.form.validate((valid) => {
                 if (!valid) {
                   reject(false);
@@ -207,10 +315,31 @@ export default {
                   resolve(true);
                 }
               });
-            })
+            }),
+            new Promise((resolve, reject) => {
+              this.$refs.businessAddTable.validateForm((valid) => {
+                if (!valid) {
+                  reject(false);
+                } else {
+                  resolve(true);
+                }
+              });
+            }),
+            new Promise((resolve, reject) => {
+              this.$refs.personnelAddTable.validateForm((valid) => {
+                if (!valid) {
+                  reject(false);
+                } else {
+                  resolve(true);
+                }
+              });
+            }),
+          
+          ]);
      },
       async save() {
         try {
+         
            await this.getValidate();
            // 表单验证通过,执行保存操作
            this.loading = true;
@@ -218,9 +347,11 @@ export default {
         if (!this.isUpdate) {
           delete this.form.id;
         } 
-       
+        let _sourceName=this.getDictValue('商机来源', this.form.sourceCode),_stageName=this.getDictValue('商机阶段', this.form.stageCode)
+        this.form=Object.assign({},this.form,{responsibleId:this.userInfo.userId,responsibleName:this.userInfo.name,sourceName:_sourceName,stageName:_stageName})
+        const commitData={opportunity:this.form,competAnalysisList:this.$refs.businessAddTable.getTableValue(),partyList:this.$refs.personnelAddTable.getTableValue()}
         if (this.isUpdate) {
-          UpdateInformation(this.form)
+          UpdateInformation(commitData)
             .then((res) => {
               this.loading = false;
               this.$message.success("修改成功");
@@ -231,7 +362,7 @@ export default {
               //this.loading = false;
             });
         } else {
-          addInformation(this.form)
+          addInformation(commitData)
             .then((res) => {
               this.loading = false;
               this.$message.success("新增成功");

+ 23 - 14
src/views/saleManage/businessOpportunity/components/addFollowDialog.vue

@@ -17,14 +17,8 @@
           :rules="rules"
           style="margin-top: 30px;padding-right: 20px;"
         >
-            <el-form-item label="跟进内容" prop="content">
-                <el-input placeholder="请输入" type="textarea" v-model="form.content" maxlength="500"></el-input>
-              </el-form-item>
-              <el-form-item label="达成共识" prop="agreement">
-                <el-input placeholder="请输入" type="textarea" v-model="form.agreement" maxlength="500"></el-input>
-              </el-form-item>
-              <el-form-item label="客户联系人" prop="linkId">
-                <el-select v-model="form.linkId" placeholder="请选择" @change="((value)=>{onchangeLink(value)})" >
+        <el-form-item label="客户联系人" prop="linkId">
+                <el-select v-model="form.linkId" multiple placeholder="请选择" style="width:300px" @change="onchangeLink" >
                  <el-option
                    v-for="item in linkNameOptions"
                    :key="item.id"
@@ -43,6 +37,14 @@
                   class="filter-item"
                 ></el-date-picker>
               </el-form-item>
+            <el-form-item label="跟进内容" prop="content">
+                <el-input placeholder="请输入" type="textarea" v-model="form.content" maxlength="500"></el-input>
+              </el-form-item>
+              <el-form-item label="达成共识" prop="agreement">
+                <el-input placeholder="请输入" type="textarea" v-model="form.agreement" maxlength="500"></el-input>
+              </el-form-item>
+             
+           
               <el-form-item
                 label="下一步计划"
                 prop="nextPlan"
@@ -66,6 +68,7 @@
   <script>
 import dictMixins from '@/mixins/dictMixins';
 import {copyObj} from '@/utils/util';
+import { deepClone } from '@/utils/index';
 import { getDetail, UpdateInformation, addInformation,deleteInformation,updateStatus,getcontactlink} from '@/api/saleManage/businessFollow';
 export default {
   mixins: [dictMixins],
@@ -122,6 +125,7 @@ export default {
           this.isUpdate = false;
         } else {
           this.isUpdate = true;
+          row.linkId= row?.linkId?.split(',')||[]
           this.form=row
         }
       },
@@ -148,10 +152,14 @@ export default {
     },
   
     //选择下拉框
-    onchangeLink(e){
-     let selectLable=this.linkNameOptions.find(v=>v.id===e).linkName
-     this.form.linkName=selectLable
+    onchangeLink(selectedOptions){
+      const selectedLabels = selectedOptions.map(optionId => {
+      const option = this.linkNameOptions.find(opt => opt.id === optionId);
+      return option ? option.linkName : '';
+    }).join(',');
+    this.form.linkName=selectedLabels
     },
+
     //保存
     async sumbit() {
       try {
@@ -162,10 +170,11 @@ export default {
         if (!this.isUpdate) {
           delete this.form.id;
         } 
+        let params = deepClone(this.form)
         let {contactId,id}=this.currentDetail
-        this.form=Object.assign({},this.form,{contactId:contactId,opportunityId:id})
+        params=Object.assign({},params,{contactId:contactId,opportunityId:id,linkId: params?.linkId?.join(',')||''})
         if (this.isUpdate) {
-          UpdateInformation(this.form)
+          UpdateInformation(params)
             .then((res) => {
               this.loading = false;
               this.$message.success("修改成功");
@@ -176,7 +185,7 @@ export default {
               //this.loading = false;
             });
         } else {
-          addInformation(this.form)
+          addInformation(params)
             .then((res) => {
               this.loading = false;
               this.$message.success("新增成功");

+ 150 - 0
src/views/saleManage/businessOpportunity/components/businessAddTable.vue

@@ -0,0 +1,150 @@
+<template>
+    <el-form ref="form" :model="form" :rules="rules">
+      <ele-pro-table ref="table" :needPage="false" :columns="columns" :datasource="form.datasource"
+        cache-key="systemRoleTable17" class="time-form">
+        <!-- 表头工具栏 -->
+        <template v-slot:toolbar>
+          <el-button size="small" type="primary" icon="el-icon-plus" class="ele-btn-icon" @click="handlAdd">
+            新增
+          </el-button>
+        </template>
+        <template v-slot:name="scope">
+          <el-form-item style="margin-bottom: 20px;" :prop="'datasource.' + scope.$index + '.name'" :rules="{
+            required: true,
+            message: '请输入',
+            trigger: 'change'
+          }">
+            <el-input v-model="scope.row.name" placeholder="请输入"></el-input>
+          </el-form-item>
+        </template>
+        <template v-slot:analysis="scope">
+          <el-form-item style="margin-bottom: 20px" :prop="'datasource.' + scope.$index + '.analysis'" :rules="{
+            required: true,
+            message: '请输入',
+            trigger: 'change'
+          }">
+            <el-input v-model="scope.row.analysis" type="textarea" placeholder="请输入"></el-input>
+          </el-form-item>
+        </template>
+        <!-- 操作列 -->
+        <template v-slot:action="{ row }">
+          <el-popconfirm class="ele-action" title="确定要删除吗?" @confirm="remove(row)">
+            <template v-slot:reference>
+              <el-link type="danger" :underline="false" icon="el-icon-delete">
+                删除
+              </el-link>
+            </template>
+          </el-popconfirm>
+        </template>
+      </ele-pro-table>
+    </el-form>
+  </template>
+  <script>
+  export default {
+    props: {
+      delDetailIds: Array
+    },
+    data() {
+      const defaultForm = {
+        key: null,
+        name: '',
+        analysis: ''
+      };
+      return {
+        defaultForm,
+        form: {
+          datasource: []
+        },
+        rules: {},
+        columns: [
+          {
+            width: 45,
+            type: 'index',
+            columnKey: 'index',
+            align: 'center',
+            fixed: 'left'
+          },
+          
+          {
+            prop: 'name',
+            label: '竞争对手名称',
+            slot: 'name'
+          },
+          {
+            prop: 'analysis',
+            label: '竞品分析',
+            slot: 'analysis'
+          },
+          {
+            width: 100,
+            columnKey: 'action',
+            label: '操作',
+            align: 'center',
+            resizable: false,
+            slot: 'action',
+            showOverflowTooltip: true
+          }
+        ],
+      };
+    },
+    computed: {
+      canHandl() {
+        return this.form.datasource.length;
+      }
+    },
+    methods: {
+        // 返回列表数据
+     getTableValue(){
+         return this.form.datasource
+      },
+      //修改回显
+      putTableValue(data){
+        if(data&&data?.length){
+            this.form.datasource=data
+        }
+      },
+      remove(row) {
+        let index = this.form.datasource.findIndex((n) => n.key == row.key);
+        if (index !== -1) {
+          this.form.datasource.splice(index, 1);
+          this.setSort();
+          if (row.id) {
+            this.delDetailIds.push(row.id);
+          }
+        }
+      },
+      // 清空表格
+      restTable() {
+        this.form.datasource = [];
+      },
+      // 重新排序
+      setSort() {
+        this.form.datasource.forEach((n, index) => {
+          n.key = index + 1;
+        });
+      },
+      // 添加
+      handlAdd() {
+        let item = JSON.parse(JSON.stringify(this.defaultForm));
+        item.key = this.form.datasource.length + 1;
+        this.form.datasource.push(item);
+      },
+ 
+
+      validateForm (callback) {
+        //开始表单校验
+        this.$refs.form.validate((valid) => {
+          callback(valid);
+        });
+      },
+  
+  
+    }
+  };
+  </script>
+  <style lang="scss" scoped>
+  .time-form .el-form-item {
+    margin-bottom: 0 !important;
+  }
+  </style>
+  

+ 132 - 44
src/views/saleManage/businessOpportunity/components/contactDetailDialog.vue

@@ -8,10 +8,16 @@
     width="80%"
     @close="cancel"
   >
-      <el-form
-          label-width="160px"
-          ref="formRef"
+  <el-card
+        shadow="never"
+        header="基本信息"
+        body-style="padding: 22px 20px 0 0;"
+      >
+  <el-form
+          label-width="120px"
+          ref="form"
           :model="form"
+          :rules="rules"
           style="margin-top: 30px"
         >
           <el-row>
@@ -20,38 +26,71 @@
                 label="客户名称:"
                 prop="contactName"
               >
-                {{form.contactName}}
+              {{detailData.contactName}}
               </el-form-item>
             </el-col>
             <el-col :span="8">
-              <el-form-item label="商机名称:" prop="name">
-                {{form.name}}
+              <el-form-item label="	商机名称:" prop="name">
+                {{detailData.name}}
               </el-form-item>
             </el-col>
             <el-col :span="8">
               <el-form-item label="负责人名称:" prop="responsibleName">
-                {{form.responsibleName}}
+                {{detailData.responsibleName}}
               </el-form-item>
             </el-col>
             <el-col :span="8">
-              <el-form-item label="商机来源:" prop="source">
-                {{form.source}}
+              <el-form-item label="商机来源:" prop="sourceCode">
+                {{detailData.sourceName}}
               </el-form-item>
             </el-col>
             <el-col :span="8">
-              <el-form-item label="状态:" prop="status">
-                {{ form.status === 1 ? "有效" : "失效"}}
+              <el-form-item label="赢单率:" prop="winRate">
+                {{detailData.winRate+'%'}}
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="预算:" prop="budget">
+                {{detailData.budget+'万元'}}
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="预计结单日期:" prop="expectedClosingDate">
+                {{detailData.expectedClosingDate}}
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="商机阶段:" prop="stageCode">
+                {{detailData.stageCode}}
               </el-form-item>
             </el-col>
-          
             <el-col :span="16">
-              <el-form-item label="备注:" prop="remark">
-                {{form.remark}}
+              <el-form-item
+                label="备注:"
+                prop="remark"
+              >
+              {{detailData.remark}}
               </el-form-item>
             </el-col>
           </el-row>
         </el-form>
-
+      </el-card>
+      <el-card
+        shadow="never"
+        header="竞品"
+        body-style="padding: 22px 20px 0 20px;"
+      >
+      <ele-pro-table ref="table" :needPage="false" :columns="competAnalysisListcolumns" :datasource="detailData.competAnalysisList" row-key="id">
+        </ele-pro-table>
+      </el-card>
+      <el-card
+        shadow="never"
+        header="关键人信息"
+        body-style="padding: 22px 20px 0 20px;"
+      >
+      <ele-pro-table ref="table" :needPage="false" :columns="partyListcolumns" :datasource="detailData.partyList" row-key="id">
+        </ele-pro-table>
+      </el-card>
     <div slot="footer" class="footer">
       <el-button @click="cancel">返回</el-button>
     </div>
@@ -59,48 +98,88 @@
 </template>
 
 <script>
-import {contactDetail} from '@/api/saleManage/contact';
+import { getDetail} from '@/api/saleManage/businessOpportunity';
 import {getFile} from '@/api/system/file';
 import {getInfoById as getCategoryInfo} from '@/api/classifyManage/index';
-import fileUpload from '@/components/upload/fileUpload';
-import dictMixins from '@/mixins/dictMixins';
-import deptSelect from '@/components/CommomSelect/dept-select.vue';
-import personSelect from '@/components/CommomSelect/person-select.vue';
+import dictMixins from '@/mixins/dictMixins';;
 import {copyObj} from '@/utils/util';
 
 
 export default {
-    props: {
-      categoryTreeList: Array
-    },
     mixins: [dictMixins],
-    components: {
-      fileUpload,
-      deptSelect,
-      personSelect
-    },
     data() {
-      let formDef = {
-        id:'',
-          contactName: '',
-          contactId: 0,
-          name: '',
-          remark: '',
-          responsibleName: '',
-          responsibleId: '',
-          source: '',
-          status: 1
-        };
     
       return {
         visible: false,
         title: '详情',
         row: {},
         activeName: 'base',
-        formDef,
-        form: copyObj(formDef),
-        tableBankData: [],
-        tableLinkData: [],
+        form:{},
+        rules:{},
+        detailData:{},
+        competAnalysisListcolumns: [
+      
+         {
+             prop: 'name',
+             label: '竞争对手名称',
+             // sortable: 'custom',
+             showOverflowTooltip: true,
+             align: 'center',
+             minWidth: 110
+         },
+         {
+             prop: 'analysis',
+             label: '竞品分析',
+             showOverflowTooltip: true,
+             align: 'center',
+             minWidth: 110
+         },
+         ],
+         partyListcolumns: [
+         {
+             prop: 'linkName',
+             label: '关键人名称',
+             // sortable: 'custom',
+             showOverflowTooltip: true,
+             align: 'center',
+             minWidth: 110
+         },
+         {
+             prop: 'linkMobilePhone',
+             label: '联系方式',
+             showOverflowTooltip: true,
+             align: 'center',
+             minWidth: 110
+         },
+         {
+             prop: 'linkPost',
+             label: '职位',
+             showOverflowTooltip: true,
+             align: 'center',
+             minWidth: 110
+         },
+         {
+             prop: 'attitudeName',
+             label: '态度',
+             showOverflowTooltip: true,
+             align: 'center',
+             minWidth: 110
+         },
+         {
+             prop: 'influenceName',
+             label: '影响力',
+             showOverflowTooltip: true,
+             align: 'center',
+             minWidth: 110
+         },
+         {
+             prop: 'goal',
+             label: '项目目标/期望',
+             showOverflowTooltip: true,
+             align: 'center',
+             minWidth: 110
+         },
+         ],
         ifChiefList: [
           {
             value: 0,
@@ -117,6 +196,7 @@ export default {
       async open(row) {
         this.form = row;
         this.visible = true;
+        this.getDetailData(row.id)
       },
 
       cancel() {
@@ -130,7 +210,15 @@ export default {
         })
       },
 
-   
+      async getDetailData (id) {
+        this.loading = true;
+        const data = await getDetail(id);
+        this.loading = false;
+        if (data) {
+          this.detailData = data;
+          
+        }
+      },
 
     }
   };

+ 259 - 0
src/views/saleManage/businessOpportunity/components/personnelAddTable.vue

@@ -0,0 +1,259 @@
+<template>
+    <el-form ref="form" :model="form" :rules="rules">
+      <ele-pro-table ref="table" :needPage="false" :columns="columns" :datasource="form.datasource"
+        cache-key="systemRoleTable17" class="time-form">
+        <!-- 表头工具栏 -->
+        <template v-slot:toolbar>
+          <el-button size="small" type="primary" icon="el-icon-plus" class="ele-btn-icon" @click="handlAdd">
+            新增
+          </el-button>
+        </template>
+        <template v-slot:linkId="scope">
+          <el-form-item style="margin-bottom: 20px;" :prop="'datasource.' + scope.$index + '.linkId'" :rules="{
+            required: true,
+            message: '请选择',
+            trigger: 'change'
+          }">
+           <el-select v-model="scope.row.linkId" placeholder="请选择"  @change="changePersonel(scope.row, $event)" >
+                 <el-option
+                   v-for="item in linkNameOptions"
+                   :key="item.id"
+                   :label="item.linkName"
+                   :value="item.id">
+                 </el-option>
+               </el-select>
+          </el-form-item>
+        </template>
+        <template v-slot:linkPost="scope">
+          <el-form-item style="margin-bottom: 20px" :prop="'datasource.' + scope.$index + '.linkPost'">
+            <el-input v-model="scope.row.linkPost" disabled placeholder="请输入"></el-input>
+          </el-form-item>
+        </template>
+        <template v-slot:linkMobilePhone="scope">
+          <el-form-item style="margin-bottom: 20px" :prop="'datasource.' + scope.$index + '.linkMobilePhone'" >
+            <el-input v-model="scope.row.linkMobilePhone" disabled placeholder="请输入"></el-input>
+          </el-form-item>
+        </template>
+        <template v-slot:goal="scope">
+          <el-form-item style="margin-bottom: 20px" :prop="'datasource.' + scope.$index + '.goal'" :rules="{
+            required: true,
+            message: '请输入',
+            trigger: 'change'
+          }">
+            <el-input v-model="scope.row.goal" placeholder="请输入"></el-input>
+          </el-form-item>
+        </template>
+        <template v-slot:influenceCode="scope">
+          <el-form-item style="margin-bottom: 20px" :prop="'datasource.' + scope.$index + '.influenceCode'" :rules="{
+            required: true,
+            message: '请选择',
+            trigger: 'change'
+          }">
+             <DictSelection dictName="影响力" clearable v-model="scope.row.influenceCode">
+                </DictSelection>
+          </el-form-item>
+        </template>
+        <template v-slot:attitudeCode="scope">
+          <el-form-item style="margin-bottom: 20px" :prop="'datasource.' + scope.$index + '.attitudeCode'" :rules="{
+            required: true,
+            message: '请选择',
+            trigger: 'change'
+          }">
+            <DictSelection dictName="态度" clearable v-model="scope.row.attitudeCode">
+                </DictSelection>
+          </el-form-item>
+        </template>
+       
+        <!-- 操作列 -->
+        <template v-slot:action="{ row }">
+          <el-popconfirm class="ele-action" title="确定要删除吗?" @confirm="remove(row)">
+            <template v-slot:reference>
+              <el-link type="danger" :underline="false" icon="el-icon-delete">
+                删除
+              </el-link>
+            </template>
+          </el-popconfirm>
+        </template>
+      </ele-pro-table>
+    </el-form>
+  </template>
+  <script>
+  import { getcontactlink} from '@/api/saleManage/businessFollow';
+  import dictMixins from '@/mixins/dictMixins';
+  export default {
+    props: {
+      delDetailIds: Array,
+      contactId:{
+        type:String,
+        default:''
+      }
+    },
+    mixins: [dictMixins],
+    data() {
+      const defaultForm = {
+        key: null,
+        linkId: '',
+        linkPost: '',
+        linkMobilePhone: '',
+        goal: '',
+        influenceCode: '',
+        influenceName:'',
+        attitudeCode:'',
+        attitudeName:''
+      };
+      return {
+        newContactId:'',
+        linkNameOptions:[],
+        defaultForm,
+        form: {
+          datasource: []
+        },
+        rules: {},
+        columns: [
+          {
+            width: 45,
+            type: 'index',
+            columnKey: 'index',
+            align: 'center',
+            fixed: 'left'
+          },
+          
+          {
+            prop: 'linkId',
+            label: '姓名',
+            slot: 'linkId'
+          },
+          {
+            prop: 'linkPost',
+            label: '职位',
+            slot: 'linkPost'
+          },
+          {
+            prop: 'linkMobilePhone',
+            label: '联系方式',
+            slot: 'linkMobilePhone'
+          },
+          {
+            prop: 'goal',
+            label: '项目目标/期望',
+            slot: 'goal'
+          },
+          {
+            prop: 'influenceCode',
+            label: '影响力',
+            slot: 'influenceCode'
+          },
+          {
+            prop: 'attitudeCode',
+            label: '态度',
+            slot: 'attitudeCode'
+          },
+          {
+            width: 100,
+            columnKey: 'action',
+            label: '操作',
+            align: 'center',
+            resizable: false,
+            slot: 'action',
+            showOverflowTooltip: true
+          }
+        ],
+      };
+    },
+    computed: {
+      canHandl() {
+        return this.form.datasource.length;
+      }
+    },
+    watch:{
+        contactId(val){
+           if(val){
+            this.newContactId=val
+            this.getContactIdInfo()
+           }
+        }
+
+    },
+    methods: {
+          //初始数据
+    async getContactIdInfo() {
+      const data = await getcontactlink({contactId:this.newContactId});
+         if(data&&data?.length){
+           this.linkNameOptions=data
+         }
+    },
+     //修改回显
+     putTableValue(data){
+        if(data&&data?.length){
+            this.form.datasource=data
+        }
+      },
+     // 返回列表数据
+     getTableValue(){
+      let _datasource=this.form.datasource||[]
+      if(_datasource.length===0) return
+         _datasource.forEach((v,index)=>{
+            if(_datasource[index].influenceCode){
+              _datasource[index].influenceName=this.getDictValue('影响力', _datasource[index].influenceCode)
+            }
+            if(_datasource[index].attitudeCode){
+              _datasource[index].attitudeName=this.getDictValue('态度', _datasource[index].attitudeCode)
+            }
+        })
+         return _datasource
+      },
+        //选择关键人
+        changePersonel(row, val){
+         console.log(row,val)
+         let selectVal=this.linkNameOptions.find(v=>v.id===val)
+         row.linkPost=selectVal.post
+         row.linkMobilePhone=selectVal.phone
+        },
+      remove(row) {
+        let index = this.form.datasource.findIndex((n) => n.key == row.key);
+        if (index !== -1) {
+          this.form.datasource.splice(index, 1);
+          this.setSort();
+          if (row.id) {
+            this.delDetailIds.push(row.id);
+          }
+        }
+      },
+      // 清空表格
+      restTable() {
+        this.form.datasource = [];
+      },
+      // 重新排序
+      setSort() {
+        this.form.datasource.forEach((n, index) => {
+          n.key = index + 1;
+        });
+      },
+      // 添加
+      handlAdd() {
+        if(!this.newContactId){
+            return this.$message.error('请先选择客户')
+        }
+        let item = JSON.parse(JSON.stringify(this.defaultForm));
+        item.key = this.form.datasource.length + 1;
+        this.form.datasource.push(item);
+      },
+ 
+
+      validateForm (callback) {
+        //开始表单校验
+        this.$refs.form.validate((valid) => {
+          callback(valid);
+        });
+      },
+  
+  
+    }
+  };
+  </script>
+  <style lang="scss" scoped>
+  .time-form .el-form-item {
+    margin-bottom: 0 !important;
+  }
+  </style>
+  

+ 38 - 1
src/views/saleManage/businessOpportunity/index.vue

@@ -165,12 +165,49 @@ export default {
             minWidth: 200
           },
           {
-            prop: 'source',
+            prop: 'sourceName',
             label: '商机来源',
             align: 'center',
             showOverflowTooltip: true,
             minWidth: 140
           },
+          {
+            prop: 'stageName',
+            label: '商机阶段',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 140
+          },
+          {
+            prop: 'budget',
+            label: '预算',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 140,
+            formatter: (_row, _column, cellValue) => {
+              return _row.budget+'万元';
+            }
+          },
+          {
+            prop: 'winRate',
+            label: '赢单率',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 140,
+            formatter: (_row, _column, cellValue) => {
+              return _row.winRate+'%';
+            }
+          },
+          {
+            prop: 'expectedClosingDate',
+            label: '预计结单日期',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 140,
+            formatter: (_row, _column, cellValue) => {
+              return _row.expectedClosingDate?.substr(0,10)||'';
+            }
+          },
           {
             prop: 'remark',
             label: '备注',

+ 3 - 6
src/views/saleManage/quotation/components/addDialog.vue

@@ -207,13 +207,13 @@
       <el-card
         shadow="never"
         header="报价单产品清单"
-        body-style="padding: 22px 20px 0 0;"
+        body-style="padding: 22px 20px 0 20px;"
       >
-        <inventoryTable
+        <!-- <inventoryTable
           ref="inventoryTable"
           @timeAll="getDetailTable"
           :delDetailIds="delDetailIds"
-        ></inventoryTable>
+        ></inventoryTable> -->
       </el-card>
     </el-form>
 
@@ -221,7 +221,6 @@
       <el-button type="primary" @click="save">保存</el-button>
       <el-button @click="cancel">返回</el-button>
     </div>
-    <head-list ref="headRef"  @changeParent="changeHead"></head-list>
     <parentList ref="parentRef" classType="1" @changeParent="changeParent"></parentList>
   </ele-modal>
 </template>
@@ -238,7 +237,6 @@ import personSelect from '@/components/CommomSelect/person-select.vue';
 import {listOrganizations} from '@/api/system/organization';
 import parentList from '@/views/saleManage/contact/components/parentList.vue'
 import { getDetail, UpdateInformation, addInformation,deleteInformation,updateStatus} from '@/api/saleManage/businessOpportunity';
-import headList from './head-list.vue'
 import {copyObj} from '@/utils/util';
 
 
@@ -251,7 +249,6 @@ export default {
       fileUpload,
       inventoryTable,
       deptSelect,
-      headList,
       parentList,
       personSelect
     },

+ 212 - 41
src/views/saleManage/quotation/components/inventoryTable.vue

@@ -9,56 +9,154 @@
           新增
         </el-button>
       </template>
-      <template v-slot:name="scope">
-        <el-form-item style="margin-bottom: 20px" :prop="'datasource.' + scope.$index + '.name'" :rules="{
+      <template v-slot:productName="scope">
+        <el-form-item style="margin-bottom: 20px;" :prop="'datasource.' + scope.$index + '.productName'" :rules="{
           required: true,
           message: '请输入',
           trigger: 'change'
         }">
-          <el-input v-model="scope.row.name" placeholder="请输入"></el-input>
+          <el-input v-model="scope.row.productName" placeholder="请输入"></el-input>
         </el-form-item>
       </template>
-      <template v-slot:startTime="scope">
-        <el-form-item style="margin-bottom: 20px" :prop="'datasource.' + scope.$index + '.startTime'" :rules="{
+      <template v-slot:productCode="scope">
+        <el-form-item style="margin-bottom: 20px" :prop="'datasource.' + scope.$index + '.productCode'" :rules="{
           required: true,
           message: '请输入',
           trigger: 'change'
         }">
-          <el-time-picker style="width: 100%" placeholder="起始时间" v-model="scope.row.startTime" format="HH:mm"
-            value-format="HH:mm:ss" @change="changeStartTime">
-          </el-time-picker>
+          <el-input v-model="scope.row.productCode" placeholder="请输入"></el-input>
         </el-form-item>
       </template>
-      <template v-slot:endTime="scope">
-        <el-form-item style="margin-bottom: 20px" :prop="'datasource.' + scope.$index + '.endTime'" :rules="{
+      <template v-slot:productCategoryName="scope">
+        <el-form-item style="margin-bottom: 20px" :prop="'datasource.' + scope.$index + '.productCategoryName'" :rules="{
           required: true,
           message: '请输入',
           trigger: 'change'
         }">
-          <el-time-picker style="width: 100%" format="HH:mm" value-format="HH:mm:ss" placeholder="结束时间"
-            v-model="scope.row.endTime"  @change="changeEndTime">
-          </el-time-picker>
-          <!-- :picker-options="{
-              selectableRange: setEndTime(scope)
-            }" -->
+          <el-input v-model="scope.row.productCategoryName" placeholder="请输入"></el-input>
         </el-form-item>
       </template>
-      <template v-slot:isFirst="scope">
-
-        <el-form-item style="margin-bottom: 20px" :prop="'datasource.' + scope.$index + '.isFirst'" :rules="{
+      <template v-slot:totalCount="scope">
+        <el-form-item style="margin-bottom: 20px" :prop="'datasource.' + scope.$index + '.totalCount'" :rules="{
           required: true,
-          message: '请选择',
+          message: '请输入',
           trigger: 'change'
         }">
-          <el-select clearable class="ele-block" v-model="scope.row.isFirst" placeholder="请选择">
-            <el-option label="是" :value="1" />
-            <el-option label="否" :value="0" />
-          </el-select>
+          <el-input v-model="scope.row.totalCount" placeholder="请输入"></el-input>
         </el-form-item>
+      </template>
+      <template v-slot:totalPrice="scope">
+        <el-form-item style="margin-bottom: 20px" :prop="'datasource.' + scope.$index + '.totalPrice'" :rules="{
+          required: true,
+          message: '请输入',
+          trigger: 'change'
+        }">
+            <el-input v-model="scope.row.totalPrice" placeholder="请输入"></el-input>
+        </el-form-item>
+      </template>
 
+      <template v-slot:productBrand="scope">
+        <el-form-item style="margin-bottom: 20px" :prop="'datasource.' + scope.$index + '.productBrand'" :rules="{
+          required: true,
+          message: '请输入',
+          trigger: 'change'
+        }">
+            <el-input v-model="scope.row.productBrand" placeholder="请输入"></el-input>
+        </el-form-item>
+      </template>
 
-
+      <template v-slot:modelType="scope">
+        <el-form-item style="margin-bottom: 20px" :prop="'datasource.' + scope.$index + '.modelType'" :rules="{
+          required: true,
+          message: '请输入',
+          trigger: 'change'
+        }">
+            <el-input v-model="scope.row.modelType" placeholder="请输入"></el-input>
+        </el-form-item>
+      </template>
+      <template v-slot:specification="scope">
+        <el-form-item style="margin-bottom: 20px" :prop="'datasource.' + scope.$index + '.specification'" :rules="{
+          required: true,
+          message: '请输入',
+          trigger: 'change'
+        }">
+            <el-input v-model="scope.row.specification" placeholder="请输入"></el-input>
+        </el-form-item>
+      </template>
+      <template v-slot:deliveryDays="scope">
+        <el-form-item style="margin-bottom: 20px" :prop="'datasource.' + scope.$index + '.deliveryDays'" :rules="{
+          required: true,
+          message: '请输入',
+          trigger: 'change'
+        }">
+            <el-input v-model="scope.row.deliveryDays" placeholder="请输入"></el-input>
+        </el-form-item>
+      </template>
+      <template v-slot:guaranteePeriod="scope">
+        <el-form-item style="margin-bottom: 20px" :prop="'datasource.' + scope.$index + '.guaranteePeriod'" :rules="{
+          required: true,
+          message: '请输入',
+          trigger: 'change'
+        }">
+            <el-input v-model="scope.row.guaranteePeriod" placeholder="请输入"></el-input>
+        </el-form-item>
+      </template>
+      <template v-slot:guaranteePeriodUnitCode="scope">
+        <el-form-item style="margin-bottom: 20px" :prop="'datasource.' + scope.$index + '.guaranteePeriodUnitCode'" :rules="{
+          required: true,
+          message: '请输入',
+          trigger: 'change'
+        }">
+            <el-input v-model="scope.row.guaranteePeriodUnitCode" placeholder="请输入"></el-input>
+        </el-form-item>
+      </template>
+      <template v-slot:measuringUnit="scope">
+        <el-form-item style="margin-bottom: 20px" :prop="'datasource.' + scope.$index + '.measuringUnit'" :rules="{
+          required: true,
+          message: '请输入',
+          trigger: 'change'
+        }">
+            <el-input v-model="scope.row.measuringUnit" placeholder="请输入"></el-input>
+        </el-form-item>
       </template>
+      <template v-slot:remark="scope">
+        <el-form-item style="margin-bottom: 20px" :prop="'datasource.' + scope.$index + '.remark'" :rules="{
+          required: true,
+          message: '请输入',
+          trigger: 'change'
+        }">
+            <el-input v-model="scope.row.remark" placeholder="请输入"></el-input>
+        </el-form-item>
+      </template>
+      <template v-slot:singlePrice="scope">
+        <el-form-item style="margin-bottom: 20px" :prop="'datasource.' + scope.$index + '.singlePrice'" :rules="{
+          required: true,
+          message: '请输入',
+          trigger: 'change'
+        }">
+            <el-input v-model="scope.row.singlePrice" placeholder="请输入"></el-input>
+        </el-form-item>
+      </template>
+      <template v-slot:technicalAnswerName="scope">
+        <el-form-item style="margin-bottom: 20px" :prop="'datasource.' + scope.$index + '.technicalAnswerName'" :rules="{
+          required: true,
+          message: '请输入',
+          trigger: 'change'
+        }">
+            <el-input v-model="scope.row.technicalAnswerName" placeholder="请输入"></el-input>
+        </el-form-item>
+      </template>
+      <template v-slot:technicalParams="scope">
+        <el-form-item style="margin-bottom: 20px" :prop="'datasource.' + scope.$index + '.technicalParams'" :rules="{
+          required: true,
+          message: '请输入',
+          trigger: 'change'
+        }">
+            <el-input v-model="scope.row.technicalParams" placeholder="请输入"></el-input>
+        </el-form-item>
+      </template>
+      
+
       <template v-slot:workHour="scope">
         {{ getHour(scope.row) }}
       </template>
@@ -114,37 +212,110 @@ export default {
           fixed: 'left'
         },
         {
-          prop: 'name',
-          label: '时段名称',
-          slot: 'name'
+          width: 120,
+          prop: 'productName',
+          label: '产品名称',
+          slot: 'productName'
+        },
+        {
+          width: 120,
+          prop: 'productCode',
+          label: '产品编号',
+          slot: 'productCode'
+        },
+        {
+          width: 120,
+          prop: 'productCategoryName',
+          label: '产品分类',
+          slot: 'productCategoryName'
+        },
+        {
+          width: 120,
+          prop: 'totalCount',
+          label: '数量',
+          slot: 'totalCount'
+        },
+        {
+          width: 120,
+          prop: 'totalPrice',
+          label: '产品销售总金额',
+          slot: 'totalPrice'
+        },
+        {
+          width: 120,
+          prop: 'productBrand',
+          label: '产品品牌',
+          slot: 'productBrand'
+        },
+        {
+          width: 120,
+          prop: 'modelType',
+          label: '型号',
+          slot: 'modelType'
+        },
+        {
+          width: 120,
+          prop: 'specification',
+          label: '规格',
+          slot: 'specification'
+        },
+        {
+          width: 120,
+          prop: 'deliveryDays',
+          label: '交期(天)',
+          slot: 'deliveryDays'
+        },
+        {
+          width: 120,
+          prop: 'guaranteePeriod',
+          label: '质保期',
+          slot: 'guaranteePeriod'
+        },
+        {
+          width: 120,
+          prop: 'guaranteePeriodUnitCode',
+          label: '质保期单位名称',
+          slot: 'guaranteePeriodUnitCode'
+        },
+        {
+          width: 120,
+          prop: 'measuringUnit',
+          label: '计量单位',
+          slot: 'measuringUnit'
         },
         {
-          prop: 'startTime',
-          label: '上班时间',
-          slot: 'startTime'
+          width: 120,
+          prop: 'remark',
+          label: '备注',
+          slot: 'remark'
         },
         {
-          prop: 'endTime',
-          label: '下班时间',
-          slot: 'endTime'
+          width: 120,
+          prop: 'singlePrice',
+          label: '销售单价',
+          slot: 'singlePrice'
         },
         {
-          prop: 'workHour',
-          label: '工作时数',
-          slot: 'workHour'
+          width: 120,
+          prop: 'technicalAnswerName',
+          label: '技术答疑人名称',
+          slot: 'technicalAnswerName'
         },
         {
-          prop: 'isFirst',
-          label: '是否当日首班',
-          slot: 'isFirst'
+          width: 120,
+          prop: 'technicalParams',
+          label: '技术参数',
+          slot: 'technicalParams'
         },
+        
         {
           columnKey: 'action',
           label: '操作',
-          width: 220,
+          width: 120,
           align: 'center',
           resizable: false,
           slot: 'action',
+          fixed:'right',
           showOverflowTooltip: true
         }
       ],

+ 1 - 1
src/views/saleManage/quotation/components/head-list.vue → src/views/saleManage/quotation/components/product-list.vue

@@ -1,5 +1,5 @@
 <template>
-    <el-dialog title="选择负责人" :visible.sync="visible" :before-close="handleClose" :close-on-click-modal="false"  top="5vh"
+    <el-dialog title="选择产品" :visible.sync="visible" :before-close="handleClose" :close-on-click-modal="false"  top="5vh"
         :close-on-press-escape="false" append-to-body width="80%">
         <el-card shadow="never">
             <ele-split-layout width="244px" allow-collapse :right-style="{ overflow: 'hidden' }">