Explorar el Código

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

yanglingling hace 2 años
padre
commit
f9286f0590
Se han modificado 51 ficheros con 4596 adiciones y 108 borrados
  1. 1 1
      package.json
  2. 3 0
      src/api/consult/index.js
  3. 4 0
      src/api/contract/target.js
  4. 6 0
      src/api/work/deliver.js
  5. 9 0
      src/api/work/deliverWork.js
  6. 39 0
      src/api/work/trainHead.js
  7. 3 0
      src/main.js
  8. 2 2
      src/utils/index.js
  9. 1 1
      src/views/collection/plan.vue
  10. 81 38
      src/views/consult/components/FollowUp.vue
  11. 16 2
      src/views/consult/detail.vue
  12. 47 1
      src/views/consult/index.vue
  13. 21 0
      src/views/contract/components/ApplyContract.vue
  14. 3 1
      src/views/contract/components/DetailsCollection.vue
  15. 21 0
      src/views/contract/components/DetailsInfo.vue
  16. 1 1
      src/views/contract/components/DetailsInvoice.vue
  17. 22 22
      src/views/contract/components/DetailsProduct.vue
  18. 37 0
      src/views/contract/components/Edit.vue
  19. 2 0
      src/views/contract/components/EditInvoice.vue
  20. 2 0
      src/views/contract/components/EditPlan.vue
  21. 7 1
      src/views/contract/detail.vue
  22. 17 0
      src/views/contract/index.vue
  23. 1 1
      src/views/contract/invoice.vue
  24. 210 0
      src/views/contract/target/components/EditTarget.vue
  25. 47 7
      src/views/contract/target/index.vue
  26. 41 0
      src/views/customer/inviteTenders/components/closeLoop.vue
  27. 18 0
      src/views/customer/inviteTenders/index.vue
  28. 71 0
      src/views/work/deliver/components/changeDeliverTime.vue
  29. 14 0
      src/views/work/deliver/components/deliver.vue
  30. 76 11
      src/views/work/deliver/components/detailWork.vue
  31. 142 0
      src/views/work/deliver/components/detailsRecords.vue
  32. 18 8
      src/views/work/deliver/components/editWork.vue
  33. 164 0
      src/views/work/deliver/components/productSign.vue
  34. 248 0
      src/views/work/deliver/components/softwareComplete.vue
  35. 94 0
      src/views/work/deliver/components/softwareStart.vue
  36. 16 2
      src/views/work/deliver/index.vue
  37. 70 0
      src/views/work/deliver/plan.vue
  38. 51 9
      src/views/work/deliver/progress.vue
  39. 145 0
      src/views/work/train/head/components/ChangeTime.vue
  40. 370 0
      src/views/work/train/head/components/CreateTrainHead.vue
  41. 297 0
      src/views/work/train/head/components/Detail.vue
  42. 208 0
      src/views/work/train/head/components/DetailFeedback.vue
  43. 410 0
      src/views/work/train/head/components/DingTalkFromToVue.vue
  44. 273 0
      src/views/work/train/head/components/Edit.vue
  45. 136 0
      src/views/work/train/head/components/Feedback.vue
  46. 309 0
      src/views/work/train/head/components/FeedbackRecord.vue
  47. 171 0
      src/views/work/train/head/components/SaleFeedback.vue
  48. 77 0
      src/views/work/train/head/components/SupportFeedback.vue
  49. 143 0
      src/views/work/train/head/components/TryFeedback.vue
  50. 122 0
      src/views/work/train/head/components/TryFeedbackDetail.vue
  51. 309 0
      src/views/work/train/head/index.vue

+ 1 - 1
package.json

@@ -29,7 +29,6 @@
     "jsencrypt": "^3.2.1",
     "jsplumb": "^2.15.6",
     "lodash": "^4.17.21",
-    "mockjs": "^1.1.0",
     "mysql": "^2.18.1",
     "nprogress": "^0.2.0",
     "qs": "^6.10.2",
@@ -42,6 +41,7 @@
     "vue-router": "^3.5.3",
     "vuedraggable": "^2.24.3",
     "vuex": "^3.6.2",
+    "vxe-table": "^3.7.7",
     "watermark-dom": "^2.3.0"
   },
   "devDependencies": {

+ 3 - 0
src/api/consult/index.js

@@ -16,4 +16,7 @@ export default {
   update(query) {
     return micro_request.postRequest(basePath, 'ProductConsultRecord', 'Update', query)
   },
+  followUp(query) {
+    return micro_request.postRequest(basePath, 'ProductConsultRecord', 'FollowUp', query)
+  },
 }

+ 4 - 0
src/api/contract/target.js

@@ -10,4 +10,8 @@ export default {
   importSaleTarget(query) {
     return micro_request.postRequest(basePath, 'SaleTarget', 'Import', query)
   },
+  // 销售签约指标导入
+  editSaleTarget(query) {
+    return micro_request.postRequest(basePath, 'SaleTarget', 'Edit', query)
+  },
 }

+ 6 - 0
src/api/work/deliver.js

@@ -32,4 +32,10 @@ export default {
   getHardwareUserInfo(query) {
     return micro_request.postRequest(basePath, 'DeliverOrder', 'GetHardwareUserInfo', query)
   },
+  softwareStart(query) {
+    return micro_request.postRequest(basePath, 'DeliverOrder', 'SoftwareStart', query)
+  },
+  getDeliverTimeChangeLogs(query) {
+    return micro_request.postRequest(basePath, 'DeliverOrder', 'GetDeliverTimeChangeLogs', query)
+  },
 }

+ 9 - 0
src/api/work/deliverWork.js

@@ -61,4 +61,13 @@ export default {
   updateProgressProductInfo(query) {
     return micro_request.postRequest(basePath, 'DeliverOrderProgress', 'UpdateProgressProductInfo', query)
   },
+  productSign(query) {
+    return micro_request.postRequest(basePath, 'DeliverOrderProgress', 'ProductSign', query)
+  },
+  completeSoftwareProgress(query) {
+    return micro_request.postRequest(basePath, 'DeliverOrderProgress', 'CompleteSoftwareProgress', query)
+  },
+  changeDeliverTime(query) {
+    return micro_request.postRequest(basePath, 'DeliverOrderProgress', 'ChangeDeliverTime', query)
+  },
 }

+ 39 - 0
src/api/work/trainHead.js

@@ -0,0 +1,39 @@
+/*
+ * @Author: liuzhenlin 461480418@qq.ocm
+ * @Date: 2023-03-21 16:33:47
+ * @LastEditors: liuzhenlin
+ * @LastEditTime: 2023-07-10 14:44:29
+ * @Description: file content
+ * @FilePath: \订单全流程管理系统\src\api\work\index.js
+ */
+import micro_request from '@/utils/micro_request'
+
+const basePath = process.env.VUE_APP_ParentPath
+export default {
+  // 获取列表
+  getList(query) {
+    return micro_request.postRequest(basePath, 'TrainHead', 'GetList', query)
+  },
+  //批量删除
+  DeleteByIds(query) {
+    return micro_request.postRequest(basePath, 'TrainHead', 'DeleteByIds', query)
+  },
+  //详情
+  getDetail(query) {
+    return micro_request.postRequest(basePath, 'TrainHead', 'GetEntityById', query)
+  },
+   //详情
+  getFeedBackDetail(query) {
+    return micro_request.postRequest(basePath, 'TrainHead', 'GetDetailById', query)
+  },
+  UpdateById(query) {
+    return micro_request.postRequest(basePath, 'TrainHead', 'UpdateById', query)
+  },
+  // 新建培训工单
+  Create(query) {
+    return micro_request.postRequest(basePath, 'TrainHead', 'Create', query)
+  },
+  FeedBackTrain(query) {
+    return micro_request.postRequest(basePath, 'TrainHead', 'FeedBackTrain', query)
+  },
+}

+ 3 - 0
src/main.js

@@ -12,6 +12,8 @@ import i18n from './i18n'
 import store from './store'
 import router from './router'
 import '@/vab'
+import VXETable from 'vxe-table'
+import 'vxe-table/lib/style.css'
 
 import { parseTime, translateDataToTree, resetForm, formatPrice, selectDictLabel } from '@/utils'
 import dictApi from '@/api/system/dict'
@@ -25,6 +27,7 @@ Vue.prototype.getDicts = dictApi.getDictDataByType
 Vue.prototype.resetForm = resetForm
 Vue.prototype.selectDictLabel = selectDictLabel
 Vue.prototype.formatPrice = formatPrice
+Vue.use(VXETable)
 
 Vue.config.productionTip = false
 new Vue({

+ 2 - 2
src/utils/index.js

@@ -271,9 +271,9 @@ export function resetForm(refName) {
  * @param currency {String} 币种 CNY 人民币;USD 美元;EUR 欧元
  * @returns {String} 格式化后的字符串
  */
-export function formatPrice(price, currency = 'CNY') {
+export function formatPrice(price, currency = 'CNY', maximumFractionDigits = 0) {
   if (!price) price = 0
-  return price.toLocaleString('zh-CN', { style: 'currency', currency, maximumFractionDigits: 0 })
+  return price.toLocaleString('zh-CN', { style: 'currency', currency, maximumFractionDigits: maximumFractionDigits })
 }
 
 // 回显数据字典

+ 1 - 1
src/views/collection/plan.vue

@@ -99,7 +99,7 @@
             @click="handleContractDetail(row)">
             {{ row.contractCode }}
           </el-button>
-          <span v-else-if="item.prop == 'planAmount'">{{ formatPrice(row.planAmount) }}</span>
+          <span v-else-if="item.prop == 'planAmount'">{{ formatPrice(row.planAmount, 'CNY', 2) }}</span>
           <span v-else-if="item.prop == 'cashedAmount'">{{ formatPrice(row.cashedAmount) }}</span>
           <span v-else-if="item.label == '未回款金额'">
             {{ formatPrice(row.planAmount - row.cashedAmount) }}

+ 81 - 38
src/views/consult/components/FollowUp.vue

@@ -90,28 +90,37 @@
             show-word-limit
             type="textarea" />
         </el-form-item>
-        <el-form-item label="进展描述" prop="progress">
-          <el-input v-model="form.progress" maxlength="500" resize="none" :rows="4" show-word-limit type="textarea" />
-        </el-form-item>
-        <el-form-item label="下一步计划" prop="nextPlan">
-          <el-input v-model="form.nextPlan" maxlength="500" resize="none" :rows="4" show-word-limit type="textarea" />
-        </el-form-item>
-
         <el-row :gutter="20">
           <el-col :span="12">
-            <el-form-item label="经销商/代理商" prop="distributorName">
-              <el-input v-model="form.distributorName" suffix-icon="el-icon-search" @focus="handleSelectDistributor" />
+            <el-form-item label="跟进沟通情况" prop="followCommunicateCase">
+              <el-select
+                v-model="form.followCommunicateCase"
+                placeholder="请选择"
+                style="width: 100%"
+                @change="changeIsProject">
+                <el-option label="信息有效,可继续跟进,转C类订单" value="10" />
+                <el-option label="信息有效,可转为储备用户" value="20" />
+                <el-option label="信息无效,不再跟进" value="30" />
+              </el-select>
             </el-form-item>
           </el-col>
-          <el-col :span="12">
-            <el-form-item label="是否创建项目" prop="isBig">
-              <el-select v-model="createProj" placeholder="请选择" style="width: 100%">
-                <el-option label="是" :value="true" />
-                <el-option label="否" :value="false" />
-              </el-select>
+          <el-col v-if="form.followCommunicateCase == '10' || form.followCommunicateCase == '20'" :span="12">
+            <el-form-item label="关联项目" prop="nboName">
+              <el-input
+                v-model="form.nboName"
+                placeholder="请选择关联项目"
+                readonly
+                suffix-icon="el-icon-search"
+                @focus="openProject" />
             </el-form-item>
           </el-col>
         </el-row>
+        <el-form-item label="进展描述" prop="progress">
+          <el-input v-model="form.progress" maxlength="500" resize="none" :rows="4" show-word-limit type="textarea" />
+        </el-form-item>
+        <el-form-item label="下一步计划" prop="nextPlan">
+          <el-input v-model="form.nextPlan" maxlength="500" resize="none" :rows="4" show-word-limit type="textarea" />
+        </el-form-item>
       </el-form>
       <span slot="footer">
         <el-button type="primary" @click="consultEdit">保存</el-button>
@@ -120,8 +129,10 @@
     </el-dialog>
     <!-- 选择销售工程师弹窗 -->
     <select-user ref="selectSales" :query-params="{ roles: ['SalesEngineer'] }" @save="selectSales" />
-    <!-- 选择经销商弹窗 -->
-    <select-distributor ref="selectDistributor" @save="selectDistributor" />
+    <!-- 客户 -->
+    <select-customer ref="selectCustomer" @save="selectCustomer" />
+    <!-- 选择项目 -->
+    <select-business ref="project" :multiple="false" @save="getBusinessInfo" />
   </div>
 </template>
 
@@ -130,15 +141,15 @@
   import customerApi from '@/api/customer'
   import consultApi from '@/api/consult'
   import SelectUser from '@/components/select/SelectUser'
-  import SelectDistributor from '@/components/select/SelectDistributor'
+  import SelectCustomer from '@/components/select/SelectCustomer'
+  import SelectBusiness from '@/components/select/SelectBusiness'
 
   export default {
-    components: { SelectUser, SelectDistributor },
+    components: { SelectUser, SelectCustomer, SelectBusiness },
     data() {
       return {
         title: '',
         visible: false,
-        createProj: false,
         areaEditDisable: true,
         provinceOptions: [],
         currentProvince: [],
@@ -159,10 +170,18 @@
           content: '', // 内容
           progress: '', // 进展描述
           nextPlan: '', // 下一步计划
-          distributorId: 0, // 经销商ID
-          distributorName: '', // 经销商
+          custId: 0, // 客户ID
+          custName: '', // 客户
+          nboId: 0, // 关联项目
+          nboName: '', // 项目名称,
+          followCommunicateCase: '', // 10、信息有效,可继续跟进,转C类订单;20、信息有效,可转为储备用户;30、信息无效,不再跟进。
+        },
+        rules: {
+          followCommunicateCase: [{ required: true, trigger: ['blur', 'change'], message: '请选择跟进沟通情况' }],
+          nboName: [{ required: true, trigger: ['blur', 'change'], message: '请选择项目' }],
+          progress: [{ required: true, trigger: ['blur', 'change'], message: '不能为空' }],
+          nextPlan: [{ required: true, trigger: ['blur', 'change'], message: '不能为空' }],
         },
-        rules: {},
       }
     },
     mounted() {
@@ -174,13 +193,12 @@
     },
     methods: {
       async init(id) {
-        this.createProj = false
         this.visible = true
         if (!id) {
-          this.title = '新建咨询记录'
+          this.title = '新建跟进记录'
           return
         }
-        this.title = '更新咨询记录'
+        this.title = '新建跟进记录' //'更新跟进记录'
         const [err, res] = await to(consultApi.get({ id: id }))
         if (err) return
         this.form = res.data
@@ -189,11 +207,14 @@
         this.$refs.form.validate(async (valid) => {
           if (valid) {
             let params = { ...this.form }
-            const [err, res] = await to(consultApi.update(params))
+            const [err, res] = await to(consultApi.followUp(params))
             if (err) return
             this.$message.success(res.msg)
             this.visible = false
-            this.$emit('consultSave', this.createProj)
+            this.$emit(
+              'consultSave',
+              this.form.followCommunicateCase == '10' || this.form.followCommunicateCase == '20'
+            )
           }
         })
       },
@@ -216,17 +237,19 @@
           this.form.inchargeName = val.map((item) => item.nickName).join()
         }
       },
-      selectDistributor(val) {
-        if (val && val.length > 0) {
-          this.form.distributorId = val[0].id
-          this.form.distributorName = val.map((item) => item.distName).join()
-        }
-      },
       handleSelectSale() {
         this.$refs.selectSales.open()
       },
-      handleSelectDistributor() {
-        this.$refs.selectDistributor.open()
+      handleSelectCustomer() {
+        this.$refs.selectCustomer.open()
+      },
+      selectCustomer(val) {
+        this.form.custId = 0
+        this.form.custName = ''
+        if (val && val.length > 0) {
+          this.form.custId = val[0].id
+          this.form.custName = val.map((item) => item.custName).join()
+        }
       },
       close() {
         this.form = {
@@ -246,11 +269,31 @@
           content: '', // 内容
           progress: '', // 进展描述
           nextPlan: '', // 下一步计划
-          distributorId: 0, // 经销商ID
-          distributorName: '', // 经销商
+          custId: 0, // 客户ID
+          custName: '', // 客户
+          nboId: 0, // 关联项目
+          nboName: '', // 项目名称
+          followCommunicateCase: '', // 10、信息有效,可继续跟进,转C类订单;20、信息有效,可转为储备用户;30、信息无效,不再跟进。
         }
         this.$refs['form'].resetFields()
       },
+      changeIsProject() {
+        this.form.nboId = 0
+        this.form.nboName = ''
+      },
+      // 打开选择项目
+      openProject() {
+        this.$refs.project.open()
+      },
+      // 关闭选择项目获取项目信息
+      getBusinessInfo(data) {
+        this.form.nboId = 0
+        this.form.nboName = ''
+        let business = data[0] || null
+        if (!business) return
+        this.form.nboId = business.id
+        this.form.nboName = business.nboName
+      },
     },
   }
 </script>

+ 16 - 2
src/views/consult/detail.vue

@@ -67,12 +67,21 @@
               <el-descriptions-item label="对接人">
                 {{ details.inchargeName }}
               </el-descriptions-item>
-              <el-descriptions-item label="经销商/代理商">
+              <!-- <el-descriptions-item label="经销商/代理商">
                 {{ details.distributorName }}
-              </el-descriptions-item>
+              </el-descriptions-item> -->
               <el-descriptions-item label="内容" :span="24">
                 {{ details.content }}
               </el-descriptions-item>
+              <!-- <el-descriptions-item label="客户">
+                {{ details.custName }}
+              </el-descriptions-item> -->
+              <el-descriptions-item label="跟进沟通情况">
+                {{ caseMap[details.followCommunicateCase] }}
+              </el-descriptions-item>
+              <el-descriptions-item label="项目">
+                {{ details.nboName }}
+              </el-descriptions-item>
               <el-descriptions-item label="进展描述" :span="24">
                 {{ details.progress }}
               </el-descriptions-item>
@@ -99,6 +108,11 @@
         id: undefined,
         details: {},
         activeName: 'details',
+        caseMap: {
+          10: '信息有效,可继续跟进,转C类订单',
+          20: '信息有效,可转为储备用户',
+          30: '信息无效,不再跟进',
+        },
       }
     },
     computed: {

+ 47 - 1
src/views/consult/index.vue

@@ -19,6 +19,11 @@
           <el-form-item prop="unit">
             <el-input v-model="queryForm.unit" clearable placeholder="单位" @keyup.enter.native="queryData" />
           </el-form-item>
+          <el-form-item prop="state">
+            <el-select v-model="queryForm.state" clearable placeholder="状态" @keyup.enter.native="queryData">
+              <el-option v-for="state in statusOptions" :key="state.value" :label="state.label" :value="state.value" />
+            </el-select>
+          </el-form-item>
           <el-form-item>
             <el-button icon="el-icon-search" type="primary" @click="queryData">查询</el-button>
           </el-form-item>
@@ -50,13 +55,23 @@
           <span v-else-if="item.label === '日期时间'">
             {{ parseTime(row.consultTime, '{y}-{m}-{d}') }}
           </span>
+          <span v-else-if="item.prop === 'state'">
+            {{ row[item.prop] == '20' ? '已跟进' : '未跟进' }}
+          </span>
+          <span v-else-if="item.prop === 'followCommunicateCase'">
+            {{ caseMap[row[item.prop]] }}
+          </span>
           <span v-else>{{ row[item.prop] }}</span>
         </template>
       </el-table-column>
       <el-table-column align="center" fixed="right" label="操作" width="140px">
         <template slot-scope="scope">
           <el-button v-permissions="['consult:manage:edit']" type="text" @click="handleEdit(scope.row)">编辑</el-button>
-          <el-button v-permissions="['consult:manage:followup']" type="text" @click="handleFollowUp(scope.row)">
+          <el-button
+            v-show="scope.row.state != '20'"
+            v-permissions="['consult:manage:followup']"
+            type="text"
+            @click="handleFollowUp(scope.row)">
             跟进
           </el-button>
           <el-button v-permissions="['consult:manage:delete']" type="text" @click="handleDelete(scope.row)">
@@ -94,6 +109,11 @@
     },
     data() {
       return {
+        caseMap: {
+          10: '信息有效,可继续跟进,转C类订单',
+          20: '信息有效,可转为储备用户',
+          30: '信息无效,不再跟进',
+        },
         height: this.$baseTableHeight(2),
         listLoading: false,
         layout: 'total, sizes, prev, pager, next, jumper',
@@ -105,10 +125,21 @@
           name: '', // 联系人
           inchargeName: '', //对接人
           unit: '', //单位
+          state: '', // 状态
         },
         selectRows: [], //选择的表格数据
         contractOptions: {}, //合同类型
         productLineOptions: {}, //产品线
+        statusOptions: [
+          {
+            value: '10',
+            label: '未跟进',
+          },
+          {
+            value: '20',
+            label: '已跟进',
+          },
+        ],
         lines: [],
         // 自定义列表
         showColumns: [],
@@ -160,6 +191,20 @@
             sortable: false,
             disableCheck: false,
           },
+          {
+            label: '状态',
+            width: '120px',
+            prop: 'state',
+            sortable: false,
+            disableCheck: false,
+          },
+          {
+            label: '跟进沟通情况',
+            width: '120px',
+            prop: 'followCommunicateCase',
+            sortable: false,
+            disableCheck: false,
+          },
         ],
       }
     },
@@ -208,6 +253,7 @@
           name: '', // 联系人
           inchargeName: '', //对接人
           unit: '', //单位
+          state: '', // 状态
         }
         this.queryData()
       },

+ 21 - 0
src/views/contract/components/ApplyContract.vue

@@ -223,6 +223,27 @@
                 this.loading = false
               })
           })
+        } else {
+          this.loading = true
+          let data = JSON.parse(JSON.stringify(this.form))
+          data.shares = this.shares
+          contractApi
+            .commitWithFileUrl(data)
+            .then((res) => {
+              if (res.code == 200) {
+                this.$message.success('提交成功')
+                this.visible = false
+                this.$emit('refresh')
+              } else {
+                this.$message.success('系统异常')
+                console.error(res)
+              }
+              this.loading = false
+            })
+            .catch((err) => {
+              console.error(err)
+              this.loading = false
+            })
         }
       },
       // 修改分成比例

+ 3 - 1
src/views/contract/components/DetailsCollection.vue

@@ -41,7 +41,9 @@
             <span v-else-if="item.prop == 'planScale'">
               {{ row.planScale + '%' }}
             </span>
-            <span v-else-if="item.prop == 'planAmount'">{{ formatPrice(row.planAmount) }}</span>
+            <span v-else-if="item.prop == 'planAmount'">
+              {{ formatPrice(row.planAmount, 'CNY', 2) }}
+            </span>
             <span v-else>{{ row[item.prop] }}</span>
           </template>
         </el-table-column>

+ 21 - 0
src/views/contract/components/DetailsInfo.vue

@@ -81,6 +81,27 @@
       <el-descriptions-item content-class-name="my-content" label="更新时间" label-class-name="my-label">
         {{ parseTime(details.updatedTime, '{y}-{m}-{d}') }}
       </el-descriptions-item>
+      <el-descriptions-item content-class-name="my-content" label="软件运维开始时间" label-class-name="my-label">
+        {{ parseTime(details.softwareMaintenanceBeginTime, '{y}-{m}-{d}') }}
+      </el-descriptions-item>
+      <el-descriptions-item content-class-name="my-content" label="软件运维期限" label-class-name="my-label">
+        {{ details.softwareMaintenanceLimit + '年' }}
+      </el-descriptions-item>
+      <el-descriptions-item content-class-name="my-content" label="软件运维结束时间" label-class-name="my-label">
+        {{ parseTime(details.softwareMaintenanceEndTime, '{y}-{m}-{d}') }}
+      </el-descriptions-item>
+      <el-descriptions-item content-class-name="my-content" label="硬件运维开始时间" label-class-name="my-label">
+        {{ parseTime(details.hardwareMaintenanceBeginTime, '{y}-{m}-{d}') }}
+      </el-descriptions-item>
+      <el-descriptions-item content-class-name="my-content" label="硬件运维期限" label-class-name="my-label">
+        {{ details.hardwareMaintenanceLimit + '年' }}
+      </el-descriptions-item>
+      <el-descriptions-item content-class-name="my-content" label="硬件运维结束时间" label-class-name="my-label">
+        {{ parseTime(details.hardwareMaintenanceEndTime, '{y}-{m}-{d}') }}
+      </el-descriptions-item>
+      <el-descriptions-item content-class-name="my-content" label="运维条款" label-class-name="my-label">
+        {{ details.maintenanceClause }}
+      </el-descriptions-item>
     </el-descriptions>
   </div>
 </template>

+ 1 - 1
src/views/contract/components/DetailsInvoice.vue

@@ -63,7 +63,7 @@
             {{ row.bank + '、' + row.accountNo }}
           </span>
           <span v-else-if="item.prop == 'invoiceAmount'">
-            {{ formatPrice(row.invoiceAmount) }}
+            {{ formatPrice(row.invoiceAmount, 'CNY', 2) }}
           </span>
           <span v-else>{{ row[item.prop] }}</span>
         </template>

+ 22 - 22
src/views/contract/components/DetailsProduct.vue

@@ -54,13 +54,13 @@
       <el-table-column align="center" fixed="right" label="操作" width="120px">
         <!-- approStatus 审核状态 10 待提交审核 20 待审核 30 审核已同意 40 审核已拒绝 50 审核已撤销 -->
         <template slot-scope="scope">
-          <el-button
+          <!-- <el-button
             v-permissions="['contract:detail:product:maintain']"
             :disabled="details.approStatus != '30'"
             type="text"
             @click="maintainOpen(scope.row)">
             维保
-          </el-button>
+          </el-button> -->
           <el-button
             v-permissions="['contract:detail:product:cost']"
             :disabled="details.approStatus != '30'"
@@ -261,31 +261,31 @@
             width: '120px',
             prop: 'directCost',
           },
-          {
-            label: '验收时间',
-            width: '120px',
-            prop: 'acceptTime',
-          },
+          // {
+          //   label: '验收时间',
+          //   width: '120px',
+          //   prop: 'acceptTime',
+          // },
           {
             label: '质保期(天)',
             width: '120px',
             prop: 'maintainPeriod',
           },
-          {
-            label: '运维开始时间',
-            width: '120px',
-            prop: 'maintainStartTime',
-          },
-          {
-            label: '运维期(天)',
-            width: '120px',
-            prop: 'warrantPeriod',
-          },
-          {
-            label: '运维约定条款',
-            width: '120px',
-            prop: 'maintainRemark',
-          },
+          // {
+          //   label: '运维开始时间',
+          //   width: '120px',
+          //   prop: 'maintainStartTime',
+          // },
+          // {
+          //   label: '运维期(天)',
+          //   width: '120px',
+          //   prop: 'warrantPeriod',
+          // },
+          // {
+          //   label: '运维约定条款',
+          //   width: '120px',
+          //   prop: 'maintainRemark',
+          // },
         ],
         productLineOptions: [],
         maintainVisible: false,

+ 37 - 0
src/views/contract/components/Edit.vue

@@ -155,6 +155,34 @@
           </el-form-item>
         </el-col>
       </el-row>
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="软件运维期限(年)" prop="softwareMaintenanceLimit">
+            <el-input-number
+              v-model.number="editForm.softwareMaintenanceLimit"
+              controls-position="right"
+              :min="0"
+              placeholder="请输入软件运维期限"
+              :step="1"
+              step-strictly
+              style="width: 100%" />
+          </el-form-item>
+          <el-form-item label="硬件运维期限(年)" prop="hardwareMaintenanceLimit">
+            <el-input-number
+              v-model="editForm.hardwareMaintenanceLimit"
+              controls-position="right"
+              :min="0"
+              placeholder="请输入软件运维期限"
+              :step="1"
+              style="width: 100%" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="运维条款" prop="maintenanceClause">
+            <el-input v-model="editForm.maintenanceClause" placeholder="请输入运维条款" :rows="5" type="textarea" />
+          </el-form-item>
+        </el-col>
+      </el-row>
       <el-row :gutter="20">
         <el-col :span="12">
           <el-form-item label="备注" prop="remark">
@@ -273,6 +301,9 @@
           custSignatoryName: '', //客户签约人name
           remark: '', //备注
           serviceFeeAgreement: '', // 运维服务费约定
+          softwareMaintenanceLimit: 1, // 软件运维期限(年)
+          hardwareMaintenanceLimit: 1, // 硬件运维期限(年)
+          maintenanceClause: '', // 运维条款
         },
         editRules: {
           contractName: [{ required: true, trigger: 'blur', message: '请输入合同名称' }],
@@ -290,6 +321,9 @@
           inchargeId: [{ required: true, trigger: 'change', message: '请选择销售工程师' }],
           signatoryName: [{ required: true, trigger: 'change', message: '请选择公司签约人' }],
           distributorName: [{ trigger: 'change', message: '请选择经销商' }],
+          softwareMaintenanceLimit: [{ required: true, trigger: 'change', message: '请输入软件运维期限' }],
+          hardwareMaintenanceLimit: [{ required: true, trigger: 'change', message: '请输入硬件运维期限' }],
+          maintenanceClause: [{ required: true, trigger: 'blur', message: '请输入运维条款' }],
         },
         pickerOptionsStart: {
           disabledDate: (time) => {
@@ -582,6 +616,9 @@
           distributorName: '', //经销商name
           remark: '', //备注
           serviceFeeAgreement: '', //运维服务费约定
+          softwareMaintenanceLimit: 1, // 软件运维期限(年)
+          hardwareMaintenanceLimit: 1, // 硬件运维期限(年)
+          maintenanceClause: '', // 运维条款
         }
         this.productData = []
         this.stepActive = 0

+ 2 - 0
src/views/contract/components/EditInvoice.vue

@@ -167,6 +167,7 @@
         let params = { ...this.editForm }
         const [valid] = await to(this.$refs.editForm.validate())
         if (valid == false) return
+        params['invoiceAmount'] = parseFloat(params['invoiceAmount'])
         const [err, res] = await to(invoiceApi.addInvoice(params))
         if (err) return
         if (res.code == 200) this.$message.success(res.msg)
@@ -179,6 +180,7 @@
         let params = { ...this.editForm }
         const [valid] = await to(this.$refs.editForm.validate())
         if (valid == false) return
+        params['invoiceAmount'] = parseFloat(params['invoiceAmount'])
         const [err, res] = await to(invoiceApi.updateInvoice(params))
         if (err) return
         if (res.code == 200) this.$message.success(res.msg)

+ 2 - 0
src/views/contract/components/EditPlan.vue

@@ -138,6 +138,7 @@
         const [valid] = await to(this.$refs.editForm.validate())
         if (valid == false) return
         params.planScale = Number(params.planScale)
+        params['planAmount'] = parseFloat(params['planAmount'])
         const [err, res] = await to(collectionPlanApi.addCollectionPlan(params))
         if (err) return
         if (res.code == 200) this.$message.success(res.msg)
@@ -151,6 +152,7 @@
         const [valid] = await to(this.$refs.editForm.validate())
         if (valid == false) return
         params.planScale = Number(params.planScale)
+        params['planAmount'] = parseFloat(params['planAmount'])
         const [err, res] = await to(collectionPlanApi.updateCollectionPlan(params))
         if (err) return
         if (res.code == 200) this.$message.success(res.msg)

+ 7 - 1
src/views/contract/detail.vue

@@ -71,7 +71,13 @@
       </div>
       <div class="info-side">
         <div class="buttons">
-          <el-button v-permissions="['contract:manage:edit']" type="primary" @click="handleEdit">编辑</el-button>
+          <el-button
+            v-if="details.approStatus == '10'"
+            v-permissions="['contract:manage:edit']"
+            type="primary"
+            @click="handleEdit">
+            编辑
+          </el-button>
           <el-button v-permissions="['contract:manage:delete']" @click="handleDelete">删除</el-button>
           <el-button @click="back">返回</el-button>
         </div>

+ 17 - 0
src/views/contract/index.vue

@@ -173,6 +173,9 @@
           <span v-else-if="item.label === '开票金额'">
             {{ formatPrice(row.invoiceAmount) }}
           </span>
+          <span v-else-if="item.prop === 'softwareMaintenanceLimit' || item.prop === 'hardwareMaintenanceLimit'">
+            {{ row[item.prop] + '年' }}
+          </span>
           <el-button
             v-else-if="item.prop === 'contractCode'"
             style="font-size: 14px"
@@ -383,6 +386,20 @@
             sortable: false,
             disableCheck: false,
           },
+          {
+            label: '软件运维期限',
+            width: '120px',
+            prop: 'softwareMaintenanceLimit',
+            sortable: false,
+            disableCheck: false,
+          },
+          {
+            label: '硬件运维期限',
+            width: '120px',
+            prop: 'hardwareMaintenanceLimit',
+            sortable: false,
+            disableCheck: false,
+          },
         ],
       }
     },

+ 1 - 1
src/views/contract/invoice.vue

@@ -81,7 +81,7 @@
             {{ row.bank + '、' + row.accountNo }}
           </span>
           <span v-else-if="item.prop == 'invoiceAmount'">
-            {{ formatPrice(row.invoiceAmount) }}
+            {{ formatPrice(row.invoiceAmount, 'CNY', 2) }}
           </span>
           <span v-else>{{ row[item.prop] }}</span>
         </template>

+ 210 - 0
src/views/contract/target/components/EditTarget.vue

@@ -0,0 +1,210 @@
+<!--
+ * @Author: wanglj 471442253@qq.com
+ * @Date: 2022-12-26 14:34:34
+ * @LastEditors: wanglj
+ * @LastEditTime: 2023-01-13 09:41:38
+ * @Description: file content
+ * @FilePath: \opms_frontend\src\views\customer\components\allocate.vue
+-->
+<template>
+  <el-dialog title="编辑指标" :visible.sync="visible" width="50%" @close="handleClose">
+    <el-form ref="form" label-width="150px" :model="form" :rules="rules">
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="销售" prop="saleName">
+            <el-input v-model="theTarget.saleName" disabled />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="年度" prop="year">
+            <el-input v-model="theTarget.year" disabled />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="1月计划" prop="plan1">
+            <el-input-number
+              v-model="theTarget.plan1"
+              clearable
+              controls-position="right"
+              :min="0"
+              placeholder="请输入指标(元)"
+              :precision="2"
+              style="width: 100%" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="2月计划" prop="plan2">
+            <el-input-number
+              v-model="theTarget.plan2"
+              controls-position="right"
+              :min="0"
+              placeholder="请输入指标(元)"
+              :precision="2"
+              style="width: 100%" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="3月计划" prop="plan3">
+            <el-input-number
+              v-model="theTarget.plan3"
+              controls-position="right"
+              :min="0"
+              placeholder="请输入指标(元)"
+              :precision="2"
+              style="width: 100%" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="4月计划" prop="plan4">
+            <el-input-number
+              v-model="theTarget.plan4"
+              controls-position="right"
+              :min="0"
+              placeholder="请输入指标(元)"
+              :precision="2"
+              style="width: 100%" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="5月计划" prop="plan5">
+            <el-input-number
+              v-model="theTarget.plan5"
+              controls-position="right"
+              :min="0"
+              placeholder="请输入指标(元)"
+              :precision="2"
+              style="width: 100%" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="6月计划" prop="plan6">
+            <el-input-number
+              v-model="theTarget.plan6"
+              controls-position="right"
+              :min="0"
+              placeholder="请输入指标(元)"
+              :precision="2"
+              style="width: 100%" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="7月计划" prop="plan7">
+            <el-input-number
+              v-model="theTarget.plan7"
+              controls-position="right"
+              :min="0"
+              placeholder="请输入指标(元)"
+              :precision="2"
+              style="width: 100%" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="8月计划" prop="plan8">
+            <el-input-number
+              v-model="theTarget.plan8"
+              controls-position="right"
+              :min="0"
+              placeholder="请输入指标(元)"
+              :precision="2"
+              style="width: 100%" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="9月计划" prop="plan9">
+            <el-input-number
+              v-model="theTarget.plan9"
+              controls-position="right"
+              :min="0"
+              placeholder="请输入指标(元)"
+              :precision="2"
+              style="width: 100%" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="10月计划" prop="plan10">
+            <el-input-number
+              v-model="theTarget.plan10"
+              controls-position="right"
+              :min="0"
+              placeholder="请输入指标(元)"
+              :precision="2"
+              style="width: 100%" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="11月计划" prop="plan11">
+            <el-input-number
+              v-model="theTarget.plan11"
+              controls-position="right"
+              :min="0"
+              placeholder="请输入指标(元)"
+              :precision="2"
+              style="width: 100%" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="12月计划" prop="plan12">
+            <el-input-number
+              v-model="theTarget.plan12"
+              controls-position="right"
+              :min="0"
+              placeholder="请输入指标(元)"
+              :precision="2"
+              style="width: 100%" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+    <span slot="footer">
+      <el-button :loading="loading" size="mini" type="primary" @click="handleSubmit">确定</el-button>
+      <el-button size="mini" @click="visible = false">取消</el-button>
+    </span>
+  </el-dialog>
+</template>
+
+<script>
+  import targetApi from '@/api/contract/target'
+
+  export default {
+    components: {},
+    data() {
+      return {
+        visible: false,
+        loading: false,
+        theTarget: {},
+      }
+    },
+    methods: {
+      // 打开弹窗
+      openDialog(row) {
+        this.theTarget = JSON.parse(JSON.stringify(row))
+        this.visible = true
+      },
+      //  保存数据
+      async handleSubmit() {
+        this.loading = true
+        targetApi
+          .editSaleTarget({ target: this.theTarget })
+          .then((res) => {
+            if (res.code == 200) {
+              this.$message.success('提交成功')
+              this.visible = false
+              this.$emit('refresh')
+            } else {
+              this.$message.success('系统异常')
+              console.error(res)
+            }
+            this.loading = false
+          })
+          .catch((err) => {
+            console.error(err)
+            this.loading = false
+          })
+      },
+    },
+  }
+</script>
+
+<style></style>

+ 47 - 7
src/views/contract/target/index.vue

@@ -35,15 +35,29 @@
           :http-request="uploadRequest"
           :show-file-list="false"
           style="margin: 0 10px 10px 0 !important">
-          <el-button v-permissions="['contract/target/import']" icon="el-icon-upload2" type="primary">导入</el-button>
+          <el-button
+            v-permissions="['contract:target:import']"
+            icon="el-icon-upload2"
+            :loading="importLoading"
+            type="primary">
+            导入
+          </el-button>
         </el-upload>
+        <el-button
+          v-permissions="['contract:target:edit']"
+          :disabled="!$refs.table || !$refs.table.selection || $refs.table.selection.length != 1"
+          icon="el-icon-plus"
+          type="primary"
+          @click="handleEdit()">
+          编辑
+        </el-button>
       </vab-query-form-left-panel>
       <vab-query-form-right-panel :span="12">
         <table-tool :columns="columns" :show-columns.sync="showColumns" table-type="saleTarget" />
       </vab-query-form-right-panel>
     </vab-query-form>
     <el-table ref="table" v-loading="listLoading" :data="list" :height="height">
-      <el-table-column align="center" show-overflow-tooltip type="selection" />
+      <el-table-column align="center" :selectable="selectable" show-overflow-tooltip type="selection" />
       <el-table-column
         v-for="(item, index) in showColumns"
         :key="index"
@@ -54,7 +68,7 @@
         show-overflow-tooltip
         :sortable="item.sortable">
         <template #default="{ row }">
-          <span v-if="item.prop.indexOf('完成率') >= 0">
+          <span v-if="item.label.indexOf('完成率') >= 0">
             {{ row[item.prop] + '%' }}
           </span>
           <span v-else>{{ row[item.prop] }}</span>
@@ -69,12 +83,15 @@
       :total="total"
       @current-change="handleCurrentChange"
       @size-change="handleSizeChange" />
+    <!-- 编辑指标 -->
+    <edit-target ref="editTarget" @refresh="queryData" />
   </div>
 </template>
 
 <script>
   import targetApi from '@/api/contract/target'
   import TableTool from '@/components/table/TableTool'
+  import EditTarget from './components/EditTarget'
   import to from 'await-to-js'
   import axios from 'axios'
   import asyncUploadFile from '@/utils/uploadajax'
@@ -83,9 +100,11 @@
     name: 'SaleTarget',
     components: {
       TableTool,
+      EditTarget,
     },
     data() {
       return {
+        importLoading: false,
         height: this.$baseTableHeight(3),
         listLoading: false,
         layout: 'total, sizes, prev, pager, next, jumper',
@@ -340,9 +359,9 @@
         this.queryForm.pageNum = val
         this.queryData()
       },
-      // 客户编辑
-      async handleEdit(row = null) {
-        row ? this.$refs.edit.init(row.id) : this.$refs.edit.init()
+      // 编辑销售指标
+      async handleEdit() {
+        this.$refs.editTarget.openDialog(this.$refs.table.selection[0])
       },
       beforeUpload(file) {
         let flag1 = file.size < this.fileSettings.fileSize
@@ -359,6 +378,7 @@
       },
       // 上传
       uploadRequest(option) {
+        this.importLoading = true
         let _this = this
         let url = process.env.VUE_APP_UPLOAD_WEED
         axios
@@ -372,8 +392,12 @@
                   excelUrl: fileUrl,
                 }
                 const [err, res] = await to(targetApi.importSaleTarget(params))
-                if (err) return
+                if (err) {
+                  this.importLoading = false
+                  return
+                }
                 if (res.code == 200) {
+                  this.importLoading = false
                   _this.$message({
                     type: 'success',
                     message: '操作成功',
@@ -382,6 +406,7 @@
                 }
               })
             } else {
+              this.importLoading = false
               _this.$message({
                 type: 'warning',
                 message: '未上传成功!请刷新界面重新上传!',
@@ -389,12 +414,27 @@
             }
           })
           .catch(function () {
+            this.importLoading = false
             _this.$message({
               type: 'warning',
               message: '未上传成功!请重新上传!',
             })
           })
       },
+      // 是否可选中
+      selectable(row, index) {
+        if (this.$refs.table && this.$refs.table.selection) {
+          for (let item of this.$refs.table.selection) {
+            if (item.id == row.id) {
+              return true
+            }
+          }
+        } else {
+          return true
+        }
+
+        return this.$refs.table.selection.length < 1
+      },
     },
   }
 </script>

+ 41 - 0
src/views/customer/inviteTenders/components/closeLoop.vue

@@ -42,6 +42,36 @@
             show-word-limit
             type="textarea" />
         </el-form-item>
+        <el-form-item label="渠道推进工作" prop="channelPromotionWork">
+          <el-input
+            v-model="form.channelPromotionWork"
+            maxlength="200"
+            placeholder="请输入内容"
+            resize="none"
+            :rows="2"
+            show-word-limit
+            type="textarea" />
+        </el-form-item>
+        <el-form-item label="客户推进工作" prop="customerPromotionWork">
+          <el-input
+            v-model="form.customerPromotionWork"
+            maxlength="200"
+            placeholder="请输入内容"
+            resize="none"
+            :rows="2"
+            show-word-limit
+            type="textarea" />
+        </el-form-item>
+        <el-form-item label="客户后续销售机会" prop="customerSubsequentOpportunity">
+          <el-input
+            v-model="form.customerSubsequentOpportunity"
+            maxlength="200"
+            placeholder="请输入内容"
+            resize="none"
+            :rows="2"
+            show-word-limit
+            type="textarea" />
+        </el-form-item>
       </el-form>
       <span slot="footer">
         <el-button type="primary" @click="closeLoop">闭环</el-button>
@@ -69,12 +99,20 @@
           businessName: '',
           distributorName: '',
           closeReason: '',
+          channelPromotionWork: '',
+          customerPromotionWork: '',
+          customerSubsequentOpportunity: '',
         },
         rules: {
           closeMethod: [{ required: true, trigger: ['blur', 'change'], message: '请选择闭环方式' }],
           businessName: [{ required: true, trigger: ['blur', 'change'], message: '请选择项目' }],
           distributorName: [{ required: true, trigger: ['blur', 'change'], message: '请选择经销商' }],
           closeReason: [{ required: true, trigger: ['blur', 'change'], message: '请输入闭环原因' }],
+          channelPromotionWork: [{ required: true, trigger: ['blur', 'change'], message: '请输入渠道推进工作' }],
+          customerPromotionWork: [{ required: true, trigger: ['blur', 'change'], message: '请输入客户推进工作' }],
+          customerSubsequentOpportunity: [
+            { required: true, trigger: ['blur', 'change'], message: '请输入客户后续销售机会' },
+          ],
         },
         theBid: {},
       }
@@ -109,6 +147,9 @@
           businessName: '',
           distributorName: '',
           closeReason: '',
+          channelPromotionWork: '',
+          customerPromotionWork: '',
+          customerSubsequentOpportunity: '',
         }
         this.$refs['form'].resetFields()
       },

+ 18 - 0
src/views/customer/inviteTenders/index.vue

@@ -256,6 +256,24 @@
             prop: 'closeLoopMsg',
             sortable: false,
           },
+          {
+            label: '渠道推进工作',
+            width: 'auto',
+            prop: 'channelPromotionWork',
+            sortable: false,
+          },
+          {
+            label: '客户推进工作',
+            width: 'auto',
+            prop: 'customerPromotionWork',
+            sortable: false,
+          },
+          {
+            label: '客户后续销售机会',
+            width: 'auto',
+            prop: 'customerSubsequentOpportunity',
+            sortable: false,
+          },
           {
             label: '备注',
             width: 'auto',

+ 71 - 0
src/views/work/deliver/components/changeDeliverTime.vue

@@ -0,0 +1,71 @@
+<template>
+  <el-dialog title="变更" :visible.sync="dialogFormVisible" @close="close">
+    <el-form ref="form" label-position="top" :model="form" :rules="rules">
+      <el-row :gutter="20">
+        <el-col :span="24">
+          <el-form-item label="要求交付时间" prop="deliveryTime">
+            <el-date-picker
+              v-model="form.deliveryTime"
+              placeholder="要求交付时间"
+              type="date"
+              value-format="yyyy-MM-dd HH:mm:ss" />
+          </el-form-item>
+          <el-form-item label="变更原因" prop="changeReason">
+            <el-input v-model="form.changeReason" placeholder="变更原因" :rows="5" show-word-limit type="textarea" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+    <template #footer>
+      <el-button @click="close">取 消</el-button>
+      <el-button type="primary" @click="save">确 定</el-button>
+    </template>
+  </el-dialog>
+</template>
+
+<script>
+  import deliverApi from '@/api/work/deliverWork'
+
+  export default {
+    name: 'ChangeDeliverTime',
+    data() {
+      return {
+        form: {
+          deliverOrderId: '',
+          progressId: '',
+          deliveryTime: '',
+          changeReason: '',
+        },
+        rules: {
+          deliveryTime: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+          changeReason: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+        },
+        dialogFormVisible: false,
+      }
+    },
+    mounted() {},
+    methods: {
+      open(row) {
+        this.form.deliverOrderId = row.deliverOrderId
+        this.form.progressId = row.id
+        this.form.deliveryTime = ''
+        this.form.changeReason = ''
+        this.dialogFormVisible = true
+      },
+      close() {
+        this.$refs['form'].resetFields()
+        this.dialogFormVisible = false
+      },
+      save() {
+        this.$refs['form'].validate(async (valid) => {
+          if (valid) {
+            const { msg } = await deliverApi.changeDeliverTime(this.form)
+            this.$baseMessage(msg, 'success', 'vab-hey-message-success')
+            this.$emit('fetch-data')
+            this.close()
+          }
+        })
+      },
+    },
+  }
+</script>

+ 14 - 0
src/views/work/deliver/components/deliver.vue

@@ -39,6 +39,16 @@
               value-format="yyyy-MM-dd HH:mm:ss" />
           </el-form-item>
         </el-col>
+        <el-col :span="24">
+          <el-form-item label="选择实际发货时间" prop="realDeliveryTime">
+            <el-date-picker
+              v-model="form.realDeliveryTime"
+              placeholder="请选择日期"
+              style="width: 100%"
+              type="date"
+              value-format="yyyy-MM-dd HH:mm:ss" />
+          </el-form-item>
+        </el-col>
         <!-- <el-col :span="24">
           <el-form-item label="备注" prop="remark">
             <el-input v-model="form.remark" placeholder="请输入备注" />
@@ -72,6 +82,7 @@
       return {
         form: {
           date: '',
+          realDeliveryTime: '',
           expressName: '',
           expressCode: '',
           remark: '',
@@ -81,6 +92,7 @@
           expressCode: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
           expressName: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
           date: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+          realDeliveryTime: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
         },
         dialogFormVisible: false,
         planId: 0,
@@ -121,6 +133,7 @@
         this.title = '发货'
         this.progress = row
         this.form.date = ''
+        this.form.realDeliveryTime = ''
         this.form.expressName = ''
         this.form.expressCode = ''
         this.form.remark = ''
@@ -202,6 +215,7 @@
               deliverWorkApi.deliverGoods({
                 id: this.progress.id,
                 estimatedArrivalTime: this.form.date,
+                realDeliveryTime: this.form.realDeliveryTime,
                 expressName: this.form.expressName,
                 expressCode: this.form.expressCode,
                 // remark: this.form.remark,

+ 76 - 11
src/views/work/deliver/components/detailWork.vue

@@ -23,21 +23,41 @@
               {{ theDeliverOrder.orderCode }}
             </el-form-item>
           </el-col>
-          <el-col v-if="theProgress.progressType != ''" :span="12">
+          <el-col v-if="theProgress.progressType != '' && theProgress.progressType != '40'" :span="12">
             <el-form-item label="要求发货时间:" style="margin: 0px">
               {{ parseTime(theDeliverOrder.requiredDeliveryTime, '{y}-{m}-{d}') }}
             </el-form-item>
           </el-col>
-          <el-col v-if="theProgress.progressType != ''" :span="12">
+          <el-col v-if="theProgress && theProgress.deliverStatus >= 30" :span="12">
+            <el-form-item label="实际发货时间:" style="margin: 0px">
+              {{ parseTime(theDeliverOrder.realDeliveryTime, '{y}-{m}-{d}') }}
+            </el-form-item>
+          </el-col>
+          <el-col v-if="theProgress.progressType != '' && theProgress.progressType != '40'" :span="12">
             <el-form-item label="收货信息:" style="margin: 0px">
               {{ theDeliverOrder.receivingInfo }}
             </el-form-item>
           </el-col>
-          <el-col v-if="theProgress.progressType != ''" :span="24">
+          <el-col v-if="theProgress.progressType != '' && theProgress.progressType != '40'" :span="24">
             <el-form-item label="特殊要求说明:" style="margin: 0px">
               {{ theDeliverOrder.specialRequirements }}
             </el-form-item>
           </el-col>
+          <el-col v-if="theProgress.progressType == '40'" :span="12">
+            <el-form-item label="要求交付时间:" style="margin: 0px">
+              {{ parseTime(theDeliverOrder.softwareRequiredDeliveryTime, '{y}-{m}-{d}') }}
+            </el-form-item>
+          </el-col>
+          <el-col v-if="theProgress.progressType == '40'" :span="12">
+            <el-form-item label="是否延期:" style="margin: 0px">
+              {{ theDeliverOrder.isDelay == '20' ? '是' : '否' }}
+            </el-form-item>
+          </el-col>
+          <el-col v-if="theProgress.progressType == '40'" :span="24">
+            <el-form-item label="特殊事项说明:" style="margin: 0px">
+              {{ theDeliverOrder.softwareSpecialRequirements }}
+            </el-form-item>
+          </el-col>
           <!-- <el-col :span="12">
           <el-form-item label="任务标题:" style="margin: 0px">
             {{ form.progressTitle }}
@@ -143,6 +163,36 @@
               {{ parseTime(theProgress.arrivalTime, '{y}-{m}-{d}') }}
             </el-form-item>
           </el-col>
+          <el-col v-if="theProgress && theProgress.isSign == 20" :span="12">
+            <el-form-item label="签收时间:" style="margin: 0px">
+              {{ parseTime(theProgress.productSigningTime, '{y}-{m}-{d}') }}
+            </el-form-item>
+          </el-col>
+          <el-col v-if="form.progressType == '40'" :span="12">
+            <el-form-item label="实际验收时间:" style="margin: 0px">
+              {{ parseTime(theProgress.softwareCheckTime, '{y}-{m}-{d}') }}
+            </el-form-item>
+          </el-col>
+          <el-col v-if="form.progressType == '40'" :span="12">
+            <el-form-item label="已完成工作内容:" style="margin: 0px">
+              {{ theProgress.completedContent }}
+            </el-form-item>
+          </el-col>
+          <el-col v-if="form.progressType == '40'" :span="12">
+            <el-form-item label="已完成工作量:" style="margin: 0px">
+              {{ theProgress.completedWork }}
+            </el-form-item>
+          </el-col>
+          <el-col v-if="form.progressType == '40'" :span="12">
+            <el-form-item label="未完成工作内容:" style="margin: 0px">
+              {{ theProgress.uncompletedContent }}
+            </el-form-item>
+          </el-col>
+          <el-col v-if="form.progressType == '40'" :span="12">
+            <el-form-item label="未完成工作量:" style="margin: 0px">
+              {{ theProgress.uncompletedWork }}
+            </el-form-item>
+          </el-col>
           <el-col :span="24" style="margin-top: 10px">
             <el-button
               v-if="theProgress && theProgress.assembleFileName != ''"
@@ -165,6 +215,13 @@
               @click="showFile(theProgress.installCheckFileUrl, theProgress.installCheckFileName)">
               安装验收单:{{ theProgress.installCheckFileName }}
             </el-button>
+            <el-button
+              v-if="theProgress && theProgress.softwareCheckFileName != ''"
+              size="mini"
+              type="primary"
+              @click="showFile(theProgress.softwareCheckFileUrl, theProgress.softwareCheckFileName)">
+              验收报告:{{ theProgress.softwareCheckFileName }}
+            </el-button>
           </el-col>
           <el-col :span="24">
             <el-form-item label="备注:" style="margin: 0px">
@@ -293,16 +350,24 @@
           this.allProducts = res.data.product
           progressProducts = res.data.progressProducts ? res.data.progressProducts : []
         }
-        for (let p of progressProducts) {
-          p = JSON.parse(JSON.stringify(p))
-          // 10发货任务单/20组装任务单/30部署安装单
-          if (
-            (p.deliverProgressId == this.workId && this.form.progressType == '10') ||
-            (p.assembleProgressId == this.workId && this.form.progressType == '20') ||
-            (p.installProgressId == this.workId && this.form.progressType == '30')
-          ) {
+        // 10发货任务单、20组装任务单、30部署安装单、40软件交付验收任务单
+        if (this.form.progressType == '40') {
+          for (let p of this.allProducts) {
+            p = JSON.parse(JSON.stringify(p))
             this.products.push(p)
           }
+        } else {
+          for (let p of progressProducts) {
+            p = JSON.parse(JSON.stringify(p))
+            // 10发货任务单、20组装任务单、30部署安装单、40软件交付验收任务单
+            if (
+              (p.deliverProgressId == this.workId && this.form.progressType == '10') ||
+              (p.assembleProgressId == this.workId && this.form.progressType == '20') ||
+              (p.installProgressId == this.workId && this.form.progressType == '30')
+            ) {
+              this.products.push(p)
+            }
+          }
         }
       },
       // 保存产品信息

+ 142 - 0
src/views/work/deliver/components/detailsRecords.vue

@@ -0,0 +1,142 @@
+<!--
+ * @Author: liuzl 461480418@qq.com
+ * @Date: 2023-01-09 17:42:13
+ * @LastEditors: wanglj
+ * @LastEditTime: 2023-02-21 17:14:52
+ * @Description: file content
+ * @FilePath: \opms_frontend\src\views\contract\components\detailsRecords.vue
+-->
+<template>
+  <ul class="records">
+    <li v-for="(value, key) in dynamicsList" :key="key">
+      <div class="date">
+        {{ value.createdTime.split(' ')[0] }}
+        <h2>{{ value.createdTime.split(' ')[0].split('-')[2] }}</h2>
+        <h3>{{ value.createdTime.split(' ')[0].split('-').splice(0, 2).join('.') }}</h3>
+      </div>
+      <ul class="content">
+        <li>
+          <vab-icon class="user-avatar" icon="account-circle-fill" />
+          <div class="text">
+            <p class="action">{{ value.opnPeople }} {{ value.opnType }}</p>
+            <p>{{ parseTime(value.opnDate, '{y}-{m}-{d}') }}</p>
+            <p v-if="value.opnContent">
+              变更:
+              <span>{{ value.opnContent }}</span>
+            </p>
+            <p v-if="value.remark">
+              原因:
+              <span>{{ value.remark }}</span>
+            </p>
+            <!--
+            <template v-else-if="item.opnContent.cuctName">
+              <p>
+                联系人名称:
+                <span>{{ item.opnContent.cuctName }}</span>
+              </p>
+              <p>职务:{{ item.opnContent.postion }}</p>
+              <p>手机:{{ item.opnContent.telephone }}</p>
+            </template> -->
+          </div>
+        </li>
+      </ul>
+    </li>
+  </ul>
+</template>
+
+<script>
+  export default {
+    name: 'DetailsRecords',
+    props: {
+      dynamicsList: {
+        type: Array,
+        default: () => [],
+      },
+    },
+    data() {
+      return {}
+    },
+
+    mounted() {},
+
+    methods: {},
+  }
+</script>
+
+<style lang="scss" scoped>
+  .records {
+    margin: 0;
+    padding: 10px 20px;
+    list-style: none;
+    height: calc(100% - 60px);
+    width: 450px;
+    margin-top: 6px;
+    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>

+ 18 - 8
src/views/work/deliver/components/editWork.vue

@@ -13,6 +13,7 @@
               <el-option label="发货任务单" value="10" />
               <el-option label="组装任务单" value="20" />
               <el-option label="部署安装单" value="30" />
+              <el-option label="软件交付验收任务单" value="40" />
             </el-select>
           </el-form-item>
         </el-col>
@@ -399,16 +400,24 @@
           p = JSON.parse(JSON.stringify(p))
           this.deliverProducts.push(p)
         }
-        for (let p of progressProducts) {
-          p = JSON.parse(JSON.stringify(p))
-          // 10发货任务单/20组装任务单/30部署安装单
-          if (
-            (p.deliverProgressId == this.workId && this.form.progressType == '10') ||
-            (p.assembleProgressId == this.workId && this.form.progressType == '20') ||
-            (p.installProgressId == this.workId && this.form.progressType == '30')
-          ) {
+        // 10发货任务单、20组装任务单、30部署安装单、40软件交付验收任务单
+        if (this.form.progressType == '40') {
+          for (let p of this.allProducts) {
+            p = JSON.parse(JSON.stringify(p))
             this.selfProducts.push(p)
           }
+        } else {
+          for (let p of progressProducts) {
+            p = JSON.parse(JSON.stringify(p))
+            // 10发货任务单、20组装任务单、30部署安装单、40软件交付验收任务单
+            if (
+              (p.deliverProgressId == this.workId && this.form.progressType == '10') ||
+              (p.assembleProgressId == this.workId && this.form.progressType == '20') ||
+              (p.installProgressId == this.workId && this.form.progressType == '30')
+            ) {
+              this.selfProducts.push(p)
+            }
+          }
         }
         this.$forceUpdate()
         if (this.workId != 0) {
@@ -421,6 +430,7 @@
             this.products = this.selfProducts
           }
         }
+        console.info(this.products)
       },
     },
   }

+ 164 - 0
src/views/work/deliver/components/productSign.vue

@@ -0,0 +1,164 @@
+<template>
+  <el-dialog :title="title" :visible.sync="dialogFormVisible" width="500px">
+    <el-form ref="form" label-position="top" :model="form" :rules="rules">
+      <el-row :gutter="20">
+        <el-col :span="24">
+          <el-form-item label="选择签收日期" prop="signTime">
+            <el-date-picker
+              v-model="form.signTime"
+              placeholder="请选择日期"
+              style="width: 100%"
+              type="date"
+              value-format="yyyy-MM-dd HH:mm:ss" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+    <template #footer>
+      <el-button @click="dialogFormVisible = false">取 消</el-button>
+      <el-button type="primary" @click="sign">确 定</el-button>
+    </template>
+  </el-dialog>
+</template>
+
+<script>
+  import deliverWorkApi from '@/api/work/deliverWork'
+  import to from 'await-to-js'
+  import axios from 'axios'
+  import asyncUploadFile from '@/utils/uploadajax'
+
+  export default {
+    name: 'ProductSign',
+    components: {},
+    props: {
+      // orderStatusOptions: {
+      //   type: Array,
+      //   default: () => [],
+      // },
+    },
+    data() {
+      return {
+        form: {
+          signTime: '',
+        },
+        progress: {},
+        rules: {
+          expressCode: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+          expressName: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+          signTime: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+          realDeliveryTime: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+        },
+        dialogFormVisible: false,
+        planId: 0,
+        title: '',
+        fileSettings: {
+          // 文件配置信息
+          fileSize: 52428800,
+          fileTypes: '.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.txt',
+          pictureSize: 52428800,
+          pictureTypes: '.jpg,.jpeg,.gif,.png,.jfif,.txt',
+          types: '.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.mp4,.txt',
+          videoSize: 104857600,
+          videoType: '.mp4',
+        },
+      }
+    },
+    mounted() {},
+    methods: {
+      // 打开弹窗
+      open(row) {
+        this.title = '签收'
+        this.progress = row
+        this.form.signTime = ''
+        if (this.$refs['form']) {
+          this.$refs['form'].resetFields()
+        }
+        if (this.$refs.uploadRef) {
+          this.$refs.uploadRef.clearFiles() //去掉文件列表
+        }
+        this.dialogFormVisible = true
+      },
+      // 上传附件
+      beforeAvatarUpload(file) {
+        let flag1 = file.size < this.fileSettings.fileSize
+        if (!flag1) {
+          this.$message.warning('文件过大,请重新选择!')
+          return false
+        }
+        let flag2 = this.fileSettings.fileTypes.split(',').includes('.' + file.name.split('.').pop())
+        if (!flag2) {
+          this.$message.warning('文件类型不符合,请重新选择!')
+          return false
+        }
+        return true
+      },
+      // 上传
+      uploadRequest(option) {
+        let _this = this
+        let url = process.env.VUE_APP_UPLOAD_WEED
+        axios
+          .post(url)
+          .then(function (res) {
+            if (res.data && res.data.fid && res.data.fid !== '') {
+              option.action = `${process.env.VUE_APP_PROTOCOL}${res.data.publicUrl}/${res.data.fid}`
+              asyncUploadFile(option).then(() => {
+                _this.form.fileName = option.file.name
+                _this.form.fileUrl = `${process.env.VUE_APP_PROTOCOL}${res.data.publicUrl}/${res.data.fid}` // 资料存储url
+              })
+            } else {
+              _this.$message({
+                type: 'warning',
+                message: '未上传成功!请刷新界面重新上传!',
+              })
+            }
+          })
+          .catch(function () {
+            _this.$message({
+              type: 'warning',
+              message: '未上传成功!请重新上传!',
+            })
+          })
+      },
+      // 查看附件
+      showFile(url) {
+        let fileName = this.form.fileName
+        const xhr = new XMLHttpRequest()
+        xhr.open('GET', url, true)
+        xhr.responseType = 'blob' // 通过文件下载url拿到对应的blob对象
+        xhr.onload = () => {
+          if (xhr.status === 200) {
+            let link = document.createElement('a')
+            let body = document.querySelector('body')
+            link.href = window.URL.createObjectURL(xhr.response)
+            link.download = fileName
+            link.click()
+            this.$message.success('下载成功')
+            body.removeChild(link)
+            window.URL.revokeObjectURL(link.href)
+          }
+        }
+
+        xhr.send()
+      },
+      // 完成
+      async sign() {
+        this.$refs['form'].validate(async (valid) => {
+          if (valid) {
+            const [err, res] = await to(
+              deliverWorkApi.productSign({
+                id: this.progress.id,
+                signTime: this.form.signTime,
+              })
+            )
+            if (err) return
+            if (res.code == 200) {
+              this.$baseMessage(res.msg, 'success', 'vab-hey-message-success')
+              this.$emit('fetch-data')
+            }
+            this.dialogFormVisible = false
+          }
+        })
+      },
+    },
+  }
+</script>

+ 248 - 0
src/views/work/deliver/components/softwareComplete.vue

@@ -0,0 +1,248 @@
+<template>
+  <el-dialog :title="title" :visible.sync="dialogFormVisible" width="750px">
+    <el-form ref="form" label-position="top" :model="form" :rules="rules">
+      <el-row :gutter="20">
+        <el-col :span="24">
+          <el-form-item label="验收报告" prop="softwareCheckFileUrl">
+            <el-upload
+              ref="uploadRef"
+              action="#"
+              :before-upload="
+                (file) => {
+                  return beforeAvatarUpload(file)
+                }
+              "
+              :http-request="uploadRequest"
+              :limit="1">
+              <el-button size="mini" type="primary">点击上传</el-button>
+            </el-upload>
+            <el-button v-show="form.softwareCheckFileUrl != ''" @click="showFile(form.softwareCheckFileUrl)">
+              查看
+            </el-button>
+          </el-form-item>
+        </el-col>
+        <el-col :span="24">
+          <el-form-item label="实际验收时间" prop="softwareCheckTime">
+            <el-date-picker
+              v-model="form.softwareCheckTime"
+              placeholder="请选择日期"
+              style="width: 100%"
+              type="date"
+              value-format="yyyy-MM-dd HH:mm:ss" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="24">
+          <el-form-item label="已完成工作内容" prop="completedContent">
+            <el-input
+              v-model="form.completedContent"
+              placeholder="请输入已完成工作内容"
+              :rows="4"
+              show-word-limit
+              type="textarea" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="24">
+          <el-form-item label="已完成工作量" prop="completedWork">
+            <el-input v-model="form.completedWork" placeholder="请输入已完成工作量" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="24">
+          <el-form-item label="未完成工作内容" prop="uncompletedContent">
+            <el-input
+              v-model="form.uncompletedContent"
+              placeholder="请输入未完成工作内容"
+              :rows="4"
+              show-word-limit
+              type="textarea" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="24">
+          <el-form-item label="未完成工作量" prop="uncompletedWork">
+            <el-input v-model="form.uncompletedWork" placeholder="请输入未完成工作量" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+    <template #footer>
+      <el-button @click="dialogFormVisible = false">取 消</el-button>
+      <el-button type="primary" @click="complete">确 定</el-button>
+    </template>
+  </el-dialog>
+</template>
+
+<script>
+  import deliverWorkApi from '@/api/work/deliverWork'
+  import orderWorkApi from '@/api/work/deliver'
+  import to from 'await-to-js'
+  import axios from 'axios'
+  import asyncUploadFile from '@/utils/uploadajax'
+
+  export default {
+    name: 'SoftwareComplete',
+    components: {},
+    props: {
+      // orderStatusOptions: {
+      //   type: Array,
+      //   default: () => [],
+      // },
+    },
+    data() {
+      return {
+        contractId: '',
+        form: {
+          softwareCheckTime: '',
+          softwareCheckFileUrl: '',
+          softwareCheckFileName: '',
+          completedContent: '',
+          completedWork: '',
+          uncompletedContent: '',
+          uncompletedWork: '',
+        },
+        progress: {},
+        rules: {
+          softwareCheckFileUrl: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+          softwareCheckTime: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+          completedContent: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+          completedWork: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+          uncompletedContent: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+          uncompletedWork: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+        },
+        dialogFormVisible: false,
+        planId: 0,
+        title: '',
+        fileSettings: {
+          // 文件配置信息
+          fileSize: 52428800,
+          fileTypes: '.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.txt',
+          pictureSize: 52428800,
+          pictureTypes: '.jpg,.jpeg,.gif,.png,.jfif,.txt',
+          types: '.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.mp4,.txt',
+          videoSize: 104857600,
+          videoType: '.mp4',
+        },
+      }
+    },
+    mounted() {},
+    methods: {
+      // 打开弹窗
+      open(row) {
+        this.title = '完成'
+        this.contractId = ''
+        this.progress = row
+        this.form.softwareCheckTime = ''
+        this.form.softwareCheckFileUrl = ''
+        this.form.softwareCheckFileName = ''
+        this.form.completedContent = ''
+        this.form.completedWork = ''
+        this.form.uncompletedContent = ''
+        this.form.uncompletedWork = ''
+        if (this.$refs['form']) {
+          this.$refs['form'].resetFields()
+        }
+        if (this.$refs.uploadRef) {
+          this.$refs.uploadRef.clearFiles() //去掉文件列表
+        }
+        this.getDeliverInfo()
+        this.dialogFormVisible = true
+      },
+      // 获取产品
+      async getDeliverInfo() {
+        const [err, res] = await to(orderWorkApi.getDeliverOrder({ id: this.progress.deliverOrderId }))
+        if (err) return
+        if (res.code == 200 && res.data) {
+          this.contractId = res.data.contractId
+        }
+      },
+      // 上传附件
+      beforeAvatarUpload(file) {
+        let flag1 = file.size < this.fileSettings.fileSize
+        if (!flag1) {
+          this.$message.warning('文件过大,请重新选择!')
+          return false
+        }
+        let flag2 = this.fileSettings.fileTypes.split(',').includes('.' + file.name.split('.').pop())
+        if (!flag2) {
+          this.$message.warning('文件类型不符合,请重新选择!')
+          return false
+        }
+        return true
+      },
+      // 上传
+      uploadRequest(option) {
+        let _this = this
+        let url = process.env.VUE_APP_UPLOAD_WEED
+        axios
+          .post(url)
+          .then(function (res) {
+            if (res.data && res.data.fid && res.data.fid !== '') {
+              option.action = `${process.env.VUE_APP_PROTOCOL}${res.data.publicUrl}/${res.data.fid}`
+              asyncUploadFile(option).then(() => {
+                _this.form.softwareCheckFileName = option.file.name
+                _this.form.softwareCheckFileUrl = `${process.env.VUE_APP_PROTOCOL}${res.data.publicUrl}/${res.data.fid}` // 资料存储url
+              })
+            } else {
+              _this.$message({
+                type: 'warning',
+                message: '未上传成功!请刷新界面重新上传!',
+              })
+            }
+          })
+          .catch(function () {
+            _this.$message({
+              type: 'warning',
+              message: '未上传成功!请重新上传!',
+            })
+          })
+      },
+      // 查看附件
+      showFile(url) {
+        let fileName = this.form.softwareCheckFileName
+        const xhr = new XMLHttpRequest()
+        xhr.open('GET', url, true)
+        xhr.responseType = 'blob' // 通过文件下载url拿到对应的blob对象
+        xhr.onload = () => {
+          if (xhr.status === 200) {
+            let link = document.createElement('a')
+            let body = document.querySelector('body')
+            link.href = window.URL.createObjectURL(xhr.response)
+            link.download = fileName
+            link.click()
+            this.$message.success('下载成功')
+            body.removeChild(link)
+            window.URL.revokeObjectURL(link.href)
+          }
+        }
+
+        xhr.send()
+      },
+      // 完成
+      async complete() {
+        this.$refs['form'].validate(async (valid) => {
+          if (valid) {
+            // 10发货任务单、20组装任务单、30部署安装单、40软件交付验收任务单
+            const [err, res] = await to(
+              deliverWorkApi.completeSoftwareProgress({
+                id: this.progress.id,
+                contractId: this.contractId,
+                deliverOrderId: this.progress.deliverOrderId,
+                softwareCheckTime: this.form.softwareCheckTime,
+                softwareCheckFileUrl: this.form.softwareCheckFileUrl,
+                softwareCheckFileName: this.form.softwareCheckFileName,
+                completedContent: this.form.completedContent,
+                completedWork: this.form.completedWork,
+                uncompletedContent: this.form.uncompletedContent,
+                uncompletedWork: this.form.uncompletedWork,
+              })
+            )
+            if (err) return
+            if (res.code == 200) {
+              this.$baseMessage(res.msg, 'success', 'vab-hey-message-success')
+              this.$emit('fetch-data')
+            }
+            this.dialogFormVisible = false
+          }
+        })
+      },
+    },
+  }
+</script>

+ 94 - 0
src/views/work/deliver/components/softwareStart.vue

@@ -0,0 +1,94 @@
+<template>
+  <el-dialog title="启动" :visible.sync="dialogFormVisible" @close="close">
+    <el-form ref="form" label-position="top" :model="form" :rules="rules">
+      <el-row :gutter="20">
+        <el-col :span="24">
+          <el-form-item label="计划开始时间" prop="startDate">
+            <el-date-picker
+              v-model="form.startDate"
+              placeholder="计划开始时间"
+              type="date"
+              value-format="yyyy-MM-dd HH:mm:ss" />
+          </el-form-item>
+          <el-form-item label="计划结束时间" prop="endDate">
+            <el-date-picker
+              v-model="form.endDate"
+              placeholder="计划结束时间"
+              type="date"
+              value-format="yyyy-MM-dd HH:mm:ss" />
+          </el-form-item>
+          <el-form-item label="要求交付时间" prop="softwareRequiredDeliveryTime">
+            <el-date-picker
+              v-model="form.softwareRequiredDeliveryTime"
+              placeholder="要求交付时间"
+              type="date"
+              value-format="yyyy-MM-dd HH:mm:ss" />
+          </el-form-item>
+          <el-form-item label="特殊事项说明" prop="softwareSpecialRequirements">
+            <el-input
+              v-model="form.softwareSpecialRequirements"
+              placeholder="特殊事项说明"
+              :rows="5"
+              show-word-limit
+              type="textarea" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+    <template #footer>
+      <el-button @click="close">取 消</el-button>
+      <el-button type="primary" @click="save">确 定</el-button>
+    </template>
+  </el-dialog>
+</template>
+
+<script>
+  import deliverApi from '@/api/work/deliver'
+
+  export default {
+    name: 'SoftwareStart',
+    data() {
+      return {
+        form: {
+          orderId: '',
+          softwareRequiredDeliveryTime: '',
+          softwareSpecialRequirements: '',
+          startDate: '',
+          endDate: '',
+        },
+        rules: {
+          softwareRequiredDeliveryTime: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+          startDate: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+          endDate: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+          softwareSpecialRequirements: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+        },
+        dialogFormVisible: false,
+      }
+    },
+    mounted() {},
+    methods: {
+      open(row) {
+        this.form.orderId = row.id
+        this.form.softwareRequiredDeliveryTime = ''
+        this.form.softwareSpecialRequirements = ''
+        this.form.startDate = ''
+        this.form.endDate = ''
+        this.dialogFormVisible = true
+      },
+      close() {
+        this.$refs['form'].resetFields()
+        this.dialogFormVisible = false
+      },
+      save() {
+        this.$refs['form'].validate(async (valid) => {
+          if (valid) {
+            const { msg } = await deliverApi.softwareStart(this.form)
+            this.$baseMessage(msg, 'success', 'vab-hey-message-success')
+            this.$emit('fetch-data')
+            this.close()
+          }
+        })
+      },
+    },
+  }
+</script>

+ 16 - 2
src/views/work/deliver/index.vue

@@ -98,12 +98,19 @@
           <el-button v-if="row.orderType != '20' && !row.deliverManName" type="text" @click="handleUpdateUser(row)">
             成员
           </el-button>
+          <!-- 软件的启动 -->
           <el-button
+            v-if="row.orderType == '10' && row.orderStatus == 10 && userId == row.saleId"
+            type="text"
+            @click="handleSoftwareStart(row)">
+            启动
+          </el-button>
+          <!-- <el-button
             v-if="(row.orderStatus == 10 || row.orderStatus == 15) && row.orderType != '20'"
             type="text"
             @click="handleFinish(row)">
             完成
-          </el-button>
+          </el-button> -->
         </template>
       </el-table-column>
       <template #empty>
@@ -129,6 +136,8 @@
     <start ref="startOrder" @fetch-data="restFetchData" />
     <!-- 创建工单 -->
     <order-edit ref="order-edit" @fetch-data="restFetchData" />
+    <!-- 软件交付工单启动 -->
+    <softwareStart ref="softwareStart" @fetch-data="restFetchData" />
   </div>
 </template>
 
@@ -138,13 +147,14 @@
   import TableTool from '@/components/table/TableTool'
   import Finish from './components/finish'
   import start from './components/start'
+  import softwareStart from './components/softwareStart'
   import UpdateUser from './components/updateUser'
   import { mapGetters } from 'vuex'
   import orderEdit from '@/views/work/deliver/components/Edit'
 
   export default {
     name: 'WorkOrder',
-    components: { TableTool, Finish, start, UpdateUser, orderEdit },
+    components: { TableTool, Finish, start, UpdateUser, orderEdit, softwareStart },
     data() {
       return {
         activeName: 'first',
@@ -301,6 +311,10 @@
       handleStart(row) {
         this.$refs['startOrder'].showEdit(row)
       },
+      // 软件交付的启动
+      handleSoftwareStart(row) {
+        this.$refs['softwareStart'].open(row)
+      },
       handleFinish(row) {
         this.$refs['finish'].showEdit(row)
       },

+ 70 - 0
src/views/work/deliver/plan.vue

@@ -237,6 +237,17 @@
                 @click="handleInspectGoods(row)">
                 验收
               </el-button>
+              <el-button
+                v-if="
+                  row.deliverStatus >= 30 &&
+                  row.progressType == '10' &&
+                  row.isSign != '20' &&
+                  userId == row.principalPersonId
+                "
+                type="text"
+                @click="handleProductSign(row)">
+                签收
+              </el-button>
               <el-button
                 v-if="
                   row.progressStatus == 20 &&
@@ -283,6 +294,18 @@
                 @click="handleComplete(row)">
                 完成
               </el-button>
+              <el-button
+                v-if="row.progressStatus == 20 && row.progressType == '40' && userId == row.principalPersonId"
+                type="text"
+                @click="handleSoftwareComplete(row)">
+                完成
+              </el-button>
+              <el-button
+                v-if="row.progressStatus == 20 && row.progressType == '40' && userId == row.principalPersonId"
+                type="text"
+                @click="handleChangeDeliverTime(row)">
+                变更交付时间
+              </el-button>
               <!-- 部署安装任务审核 -->
               <el-button
                 v-show="row.progressStatus == 30 && row.progressType == '30'"
@@ -307,6 +330,9 @@
           @current-change="handleCurrentChange"
           @size-change="handleSizeChange" />
       </div>
+      <div class="info-side">
+        <detailsRecords :dynamics-list="dynamicsList" />
+      </div>
       <edit-plan ref="plan" @fetch-data="fetchPlanList" />
       <edit-work ref="editWork" @fetch-data="fetchWorkListPage1" />
       <select-status ref="status" @fetch-data="fetchPlanList" />
@@ -324,6 +350,12 @@
       <ConfirmInstall ref="ConfirmInstall" @fetch-data="fetchWorkListPage1" />
       <!-- 安装审核 -->
       <AuditInstall ref="AuditInstall" @fetch-data="fetchWorkListPage1" />
+      <!-- 签收 -->
+      <productSign ref="productSign" @fetch-data="fetchWorkListPage1" />
+      <!-- 软件完成 -->
+      <softwareComplete ref="softwareComplete" @fetch-data="fetchWorkListPage1" />
+      <!-- 变更要求交付时间 -->
+      <changeDeliverTime ref="changeDeliverTime" @fetch-data="getProgressAndLogs" />
     </div>
   </div>
 </template>
@@ -344,6 +376,10 @@
   import StartInstall from '@/views/work/deliver/components/startInstall'
   import ConfirmInstall from '@/views/work/deliver/components/confirmInstall'
   import AuditInstall from '@/views/work/deliver/components/auditInstall'
+  import productSign from '@/views/work/deliver/components/productSign'
+  import softwareComplete from '@/views/work/deliver/components/softwareComplete'
+  import detailsRecords from '@/views/work/deliver/components/detailsRecords.vue'
+  import changeDeliverTime from '@/views/work/deliver/components/changeDeliverTime'
   import { mapGetters } from 'vuex'
 
   export default {
@@ -360,9 +396,14 @@
       StartInstall,
       ConfirmInstall,
       AuditInstall,
+      productSign,
+      softwareComplete,
+      detailsRecords,
+      changeDeliverTime,
     },
     data() {
       return {
+        dynamicsList: [],
         canInstallProgress: false,
         orderType: '',
         saleId: '',
@@ -492,6 +533,7 @@
     },
     async mounted() {
       this.id = parseInt(this.$route.query.id)
+      this.getDynamicsList()
       await this.getOrderDetails()
       this.fetchPlanList()
       this.getInstallProgress()
@@ -532,6 +574,8 @@
           return '组装任务单'
         } else if (type == '30') {
           return '部署安装单'
+        } else if (type == '40') {
+          return '软件交付验收任务单'
         }
         return '未知类型'
       },
@@ -539,6 +583,14 @@
       handleComplete(row) {
         this.$refs.complete.open(row)
       },
+      // 软件交付完成
+      handleSoftwareComplete(row) {
+        this.$refs.softwareComplete.open(row)
+      },
+      // 变更要求交付时间
+      handleChangeDeliverTime(row) {
+        this.$refs.changeDeliverTime.open(row)
+      },
       // 发货
       handleDeliverGoods(row) {
         this.$refs.deliver.open(row)
@@ -547,6 +599,10 @@
       handleInspectGoods(row) {
         this.$refs.inspect.open(row)
       },
+      // 签收
+      handleProductSign(row) {
+        this.$refs.productSign.open(row)
+      },
       // 确认到货
       async handleConfirmArrival(row) {
         this.$prompt('你确定货物已到达吗', '提示', {
@@ -690,6 +746,10 @@
         this.queryForm.pageNum = 1
         this.fetchWorkList()
       },
+      getProgressAndLogs() {
+        this.fetchWorkListPage1()
+        this.getDynamicsList()
+      },
       // 查询计划
       async fetchPlanList() {
         this.listLoading = true
@@ -717,6 +777,15 @@
           this.orderType = res.data.orderType
         }
       },
+      async getDynamicsList() {
+        this.dynamicsList = []
+        const [err, res] = await to(orderWorkApi.getDeliverTimeChangeLogs({ deliverOrderId: this.id }))
+        if (err) return
+
+        if (res.code == 200 && res.data) {
+          this.dynamicsList = res.data
+        }
+      },
       // 查询工作项
       async fetchWorkList() {
         this.listLoading = true
@@ -740,6 +809,7 @@
   .left-scroll {
     height: 100%;
     overflow: auto;
+    width: 350px;
   }
   .tree-side {
     border-right: 0;

+ 51 - 9
src/views/work/deliver/progress.vue

@@ -16,6 +16,7 @@
               <el-option label="发货任务单" value="10" />
               <el-option label="组装任务单" value="20" />
               <el-option label="部署安装单" value="30" />
+              <el-option label="软件交付验收任务单" value="40" />
             </el-select>
           </el-form-item>
           <el-form-item prop="progressStatus">
@@ -108,13 +109,7 @@
           </el-button>
           <!-- 非部署安装任务的开始 -->
           <el-button
-            v-if="
-              row.progressStatus == 10 &&
-              workOrderStatus != 20 &&
-              curPlanStatus != 30 &&
-              row.progressType != '30' &&
-              userId == row.principalPersonId
-            "
+            v-if="row.progressStatus == 10 && row.progressType != '30' && userId == row.principalPersonId"
             type="text"
             @click="handleStartWork(row)">
             开始
@@ -124,8 +119,6 @@
           <!-- <el-button
             v-if="
               row.progressStatus == 10 &&
-              workOrderStatus != 20 &&
-              curPlanStatus != 30 &&
               row.progressType == '30' &&
               userId == saleId
             "
@@ -157,6 +150,17 @@
             @click="handleInspectGoods(row)">
             验收
           </el-button> -->
+          <el-button
+            v-if="
+              row.deliverStatus >= 30 &&
+              row.progressType == '10' &&
+              row.isSign != '20' &&
+              userId == row.principalPersonId
+            "
+            type="text"
+            @click="handleProductSign(row)">
+            签收
+          </el-button>
           <el-button
             v-if="
               row.progressStatus == 20 &&
@@ -197,6 +201,18 @@
             @click="handleComplete(row)">
             完成
           </el-button>
+          <el-button
+            v-if="row.progressStatus == 20 && row.progressType == '40' && userId == row.principalPersonId"
+            type="text"
+            @click="handleSoftwareComplete(row)">
+            完成
+          </el-button>
+          <el-button
+            v-if="row.progressStatus == 20 && row.progressType == '40' && userId == row.principalPersonId"
+            type="text"
+            @click="handleChangeDeliverTime(row)">
+            变更交付时间
+          </el-button>
           <!-- 部署安装任务审核 -->
           <el-button
             v-show="row.progressStatus == 30 && row.progressType == '30'"
@@ -237,6 +253,12 @@
     <ConfirmInstall ref="ConfirmInstall" @fetch-data="fetchData" />
     <!-- 安装审核 -->
     <AuditInstall ref="AuditInstall" @fetch-data="fetchData" />
+    <!-- 签收 -->
+    <productSign ref="productSign" @fetch-data="fetchData" />
+    <!-- 软件完成 -->
+    <softwareComplete ref="softwareComplete" @fetch-data="fetchData" />
+    <!-- 变更要求交付时间 -->
+    <changeDeliverTime ref="changeDeliverTime" @fetch-data="fetchData" />
   </div>
 </template>
 
@@ -253,6 +275,9 @@
   import StartInstall from '@/views/work/deliver/components/startInstall'
   import ConfirmInstall from '@/views/work/deliver/components/confirmInstall'
   import AuditInstall from '@/views/work/deliver/components/auditInstall'
+  import productSign from '@/views/work/deliver/components/productSign'
+  import softwareComplete from '@/views/work/deliver/components/softwareComplete'
+  import changeDeliverTime from '@/views/work/deliver/components/changeDeliverTime'
 
   export default {
     name: 'Progress',
@@ -266,6 +291,9 @@
       StartInstall,
       ConfirmInstall,
       AuditInstall,
+      productSign,
+      softwareComplete,
+      changeDeliverTime,
     },
     data() {
       return {
@@ -435,6 +463,8 @@
           return '组装任务单'
         } else if (type == '30') {
           return '部署安装单'
+        } else if (type == '40') {
+          return '软件交付验收任务单'
         }
         return '未知类型'
       },
@@ -462,6 +492,14 @@
       handleComplete(row) {
         this.$refs.complete.open(row)
       },
+      // 软件交付完成
+      handleSoftwareComplete(row) {
+        this.$refs.softwareComplete.open(row)
+      },
+      // 变更要求交付时间
+      handleChangeDeliverTime(row) {
+        this.$refs.changeDeliverTime.open(row)
+      },
       // 发货
       handleDeliverGoods(row) {
         this.$refs.deliver.open(row)
@@ -470,6 +508,10 @@
       handleInspectGoods(row) {
         this.$refs.inspect.open(row)
       },
+      // 签收
+      handleProductSign(row) {
+        this.$refs.productSign.open(row)
+      },
       // 确认到货
       async handleConfirmArrival(row) {
         this.$prompt('你确定货物已到达吗', '提示', {

+ 145 - 0
src/views/work/train/head/components/ChangeTime.vue

@@ -0,0 +1,145 @@
+<template>
+  <el-dialog title="改期" :visible.sync="dialogFormVisible" width="600px" @close="close">
+    <el-form v-if="detail" ref="form" label-position="top" :model="form" :rules="rules">
+      <el-row :gutter="20">
+        <!-- 技术支持 -->
+        <el-col v-if="detail.orderTypeName == '技术文件支持'" :span="24">
+          <el-form-item label="期望完成时间" prop="expectTime">
+            <el-date-picker
+              v-model="form.expectTime"
+              placeholder="选择期望完成时间"
+              style="width: 100%"
+              type="date"
+              value-format="yyyy-MM-dd" />
+          </el-form-item>
+        </el-col>
+        <!-- end -->
+        <!-- 售前 -->
+        <el-col v-if="detail.orderTypeName == '售前讲解支持'" :span="24">
+          <el-form-item label="支持时间" prop="supportTime">
+            <el-date-picker
+              v-model="form.supportTime"
+              placeholder="选择支持时间"
+              style="width: 100%"
+              type="datetime"
+              value-format="yyyy-MM-dd HH:mm:ss" />
+          </el-form-item>
+        </el-col>
+        <!-- end -->
+        <!-- 修改试用时间 -->
+        <el-col
+          v-if="detail.orderTypeName == '产品试用申请(硬件)' || detail.orderTypeName == '产品试用申请(软件)'"
+          :span="24">
+          <el-form-item label="试用时间" prop="trialTime">
+            <el-date-picker
+              v-model="form.trialTime"
+              end-placeholder="结束日期"
+              range-separator="至"
+              start-placeholder="开始日期"
+              style="width: 100%"
+              type="datetimerange"
+              value-format="yyyy-MM-dd HH:mm:ss" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+    <template #footer>
+      <el-button @click="close">取 消</el-button>
+      <el-button type="primary" @click="save">确 定</el-button>
+    </template>
+  </el-dialog>
+</template>
+
+<script>
+  import to from 'await-to-js'
+  import workOrderApi from '@/api/work/index'
+  export default {
+    name: 'WorkOrderFeedback',
+    data() {
+      return {
+        detail: null,
+        form: {
+          expectTime: '',
+          supportTime: '',
+          trialTime: [],
+        },
+        rules: {
+          expectTime: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+          supportTime: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+          trialTime: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+        },
+        dialogFormVisible: false,
+      }
+    },
+
+    mounted() {},
+    methods: {
+      open(row) {
+        this.detail = row
+        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) {
+            let params = {}
+            if (
+              this.detail.orderTypeName == '产品试用申请(硬件)' ||
+              this.detail.orderTypeName == '产品试用申请(软件)'
+            ) {
+              params = {
+                orderId: this.detail.id,
+                trialTimeEnd: this.form.trialTime[1],
+                trialTimeStart: this.form.trialTime[0],
+              }
+              const [err, res] = await to(workOrderApi.changeTryTime(params))
+              if (err) return
+              if (res.code == 200) {
+                this.$message({
+                  type: 'success',
+                  message: '修改成功!',
+                })
+                this.close()
+                this.$emit('update-detail')
+              }
+            } else if (this.detail.orderTypeName == '技术文件支持') {
+              params = {
+                orderId: this.detail.id,
+                expectTime: this.form.expectTime,
+              }
+              const [err, res] = await to(workOrderApi.changeSupportTime(params))
+              if (err) return
+              if (res.code == 200) {
+                this.$message({
+                  type: 'success',
+                  message: '修改成功!',
+                })
+                this.close()
+                this.$emit('update-detail')
+              }
+            } else if (this.detail.orderTypeName == '售前讲解支持') {
+              params = {
+                orderId: this.detail.id,
+                supportTime: this.form.supportTime,
+              }
+              const [err, res] = await to(workOrderApi.changeSaleTime(params))
+              if (err) return
+              if (res.code == 200) {
+                this.$message({
+                  type: 'success',
+                  message: '修改成功!',
+                })
+                this.close()
+                this.$emit('update-detail')
+              }
+            }
+          }
+        })
+      },
+    },
+  }
+</script>

+ 370 - 0
src/views/work/train/head/components/CreateTrainHead.vue

@@ -0,0 +1,370 @@
+<template>
+  <el-dialog append-to-body :title="title" :visible.sync="dialogFormVisible" @close="close">
+    <el-form ref="form" label-position="top" :model="form" :rules="rules">
+      <el-row :gutter="20">
+        <!-- <el-col :span="12">
+          <el-form-item label="工单名称" prop="name">
+            <el-input v-model="form.name" />
+          </el-form-item>
+        </el-col> -->
+        <el-col :span="24">
+          <el-form-item label="培训主题" prop="trainTitle">
+            <el-input v-model="form.trainTitle" placeholder="请输入培训主题" />
+            <!-- <el-input v-model="form.trainTitle" readonly suffix-icon="el-icon-search" @focus="handleSelectUser" /> -->
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="培训日期" prop="trainDate">
+            <el-date-picker v-model="form.trainDate" type="date" placeholder="选择日期" style="width: 400px" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="培训时间" prop="trainConcreteStartTime">
+            <el-time-select
+              v-model="form.trainConcreteStartTime"
+              :picker-options="{
+                start: '08:30',
+                step: '00:30',
+                end: '18:30',
+              }"
+              placeholder="选择培训开始时间"
+              style="width: 200px" />
+            <el-time-select
+              v-model="form.trainConcreteEndTime"
+              :picker-options="{
+                start: '08:30',
+                step: '00:30',
+                end: '18:30',
+                minTime: form.trainConcreteStartTime,
+              }"
+              placeholder="选择培训结束时间"
+              style="margin-left: 10px; width: 200px" />
+          </el-form-item>
+          <!-- <el-form-item label="培训结束时间" prop="trainConcreteEndTime">
+
+          </el-form-item> -->
+        </el-col>
+      </el-row>
+    </el-form>
+    <el-button icon="el-icon-plus" type="primary" @click="createFeedBack">添加工程师</el-button>
+    <div   style="height: 400px">
+      <vxe-table
+        ref="feedBackRef"
+        border
+        resizable
+        show-overflow
+        :data="form.feedbackList"
+        :edit-config="{ trigger: 'click', mode: 'cell' }"
+        style="height: auto">
+        <vxe-column type="seq" width="60" />
+        <vxe-column field="saleName" title="销售工程师" :edit-render="{}">
+          <template #default="{ row }">
+            {{ row.sale.saleName }}
+          </template>
+          <template #edit="{ row }">
+            <vxe-select v-model="row.sale" style="height: 50px">
+              <vxe-option
+                v-for="(item, index) in userList"
+                :key="item.saleId"
+                :value="userList[index]"
+                :label="item.saleName" />
+            </vxe-select>
+          </template>
+        </vxe-column>
+        <!-- <vxe-column field="distributorName" title="渠道名称">
+
+        <template #default="{ row }">
+          <vxe-input v-model="row.role" type="text" placeholder="请输入昵称" />
+        </template>
+      </vxe-column> -->
+
+        <vxe-column field="distributorList" title="渠道名称" :edit-render="{}">
+          <template #default="{ row }">
+            <span v-for="(item, index) in row.distributorList" :key="item.distributorId">
+              {{ item.distributorName }}
+              <span v-if="index == row.distributorList.length">,</span>
+            </span>
+          </template>
+          <template #edit="{ row }">
+            <vxe-select v-model="row.distributorList" multiple>
+              <vxe-option
+                v-for="(item, index) in sexList"
+                :key="item.distributorId"
+                :value="sexList[index]"
+                :label="item.distributorName" />
+            </vxe-select>
+          </template>
+        </vxe-column>
+        <vxe-column title="操作" fixed="right">
+          <template #default="{ row }">
+            <vxe-button type="text" status="primary" content="删除" @click="DeleteData(row)" />
+          </template>
+        </vxe-column>
+      </vxe-table>
+    </div>
+
+    <!-- <el-table :data="form.feedbackList" border style="width: 100%">
+      <el-table-column align="center" label="序号" width="80">
+        <template #default="{ $index }">
+          {{ $index + 1 }}
+        </template>
+      </el-table-column>
+      <el-table-column prop="saleName" label="销售工程师">
+        <template #default="{ $index }">
+          <el-input v-model="form.feedbackList[$index].saleName" @focus="handleSelectUser($index)" />
+        </template>
+      </el-table-column>
+      <el-table-column prop="distributorName" label="渠道名称">
+        <template slot-scope="scope">
+          <el-input v-model="scope.row.distributorName" />
+        </template>
+      </el-table-column>
+
+      <el-table-column fixed="right" label="操作" width="100">
+        <template slot-scope="scope">
+          <el-button type="text" @click="scope.row" size="small">编辑</el-button>
+          <el-button type="text" @click="deleteFeedBack(scope.row)" size="small">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table> -->
+
+    <template #footer>
+      <el-button @click="close">取 消</el-button>
+      <el-button :loading="loading" type="primary" @click="save">确 定</el-button>
+    </template>
+
+    <!-- 选择支持人员弹窗 -->
+    <select-user ref="selectUser" @save="selectUser" />
+    <!-- 选择经销商弹窗 -->
+    <select-distributor ref="selectDistributor" :multiple="true" :query-params="queryParams" @save="saveDistributors" />
+  </el-dialog>
+</template>
+
+<script>
+  import api from '@/api/work/trainHead'
+  import to from 'await-to-js'
+
+  import typeApi from '@/api/work/type'
+  import SelectUser from '@/components/select/SelectUser'
+  import { mapGetters } from 'vuex'
+  import SelectDistributor from '@/components/select/SelectDistributor'
+
+  export default {
+    name: 'WorkOrderEdit',
+    components: {
+      SelectUser,
+      SelectDistributor,
+    },
+    props: {
+      businessInfo: {
+        type: Object,
+        default: () => {},
+      },
+    },
+    data() {
+      return {
+        sexList: [
+          { distributorId: 1, distributorName: 'a' },
+          { distributorId: 2, distributorName: 'b' },
+          { distributorId: 3, distributorName: 'c' },
+        ],
+        userList: [
+          { saleId: 1, saleName: '工程师1' },
+          { saleId: 2, saleName: '工程师2' },
+          { saleId: 3, saleName: '工程师3' },
+        ],
+        row: {},
+        index: 0,
+        queryParams: {
+          distType: '',
+        },
+        loading: false,
+        form: {
+          id: undefined,
+          trainTitle: undefined,
+          trainDate: undefined,
+          trainConcreteStartTime: undefined,
+          trainConcreteEndTime: undefined,
+          feedbackList: [],
+        },
+        rules: {
+          trainTitle: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+          trainDate: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+          trainConcreteStartTime: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+          trainConcreteEndTime: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+        },
+        title: '',
+        dialogFormVisible: false,
+        orderTypeList: [],
+        dingtalkForm: undefined,
+      }
+    },
+    computed: {
+      ...mapGetters({
+        userId: 'user/id',
+        nickName: 'user/nickName',
+      }),
+    },
+    mounted() {},
+    methods: {
+      DeleteData(row) {
+        const $table = this.$refs.feedBackRef
+        $table.remove(row)
+      },
+      createFeedBack() {
+        const $table = this.$refs.feedBackRef
+
+        $table.insertAt(
+          {
+            sale: {},
+            distributorList: [],
+          },
+          0
+        )
+        // if (this.form.feedbackList == undefined) {
+        //   this.form.feedbackList = []
+        // }
+        // this.form.feedbackList.push({
+        //   saleId: '',
+        //   saleName: '',
+        //   distributorId: '',
+        //   distributorName: '',
+        // })
+      },
+      deleteFeedBack(row) {
+        // const index = this.form.feedbackList.findIndex((item) => {
+        //   item == row
+        // })
+        // this.form.feedbackList.splice(index, 1)
+        const $table = this.$refs.feedBackRef
+        $table.remove(row)
+      },
+      async getOrderTypeList() {
+        this.orderTypeList.splice(0, this.orderTypeList.length)
+        const { data } = await typeApi.getList()
+        for (let item of data.list) {
+          this.orderTypeList.push(item)
+        }
+        this.changeOrderType()
+      },
+      changeOrderType() {},
+      // handleUploadFile(file) {
+      //   this.form.file = file
+      // },
+      handleSelectUser(rowIndex) {
+        // Object.assign(this.row, rowIndex)
+        this.index = rowIndex
+        // const index = this.form.feedbackList.findIndex(item=>{
+        //   item==row
+        // })
+        this.$refs.selectUser.open()
+      },
+      selectUser(val) {
+        if (val && val.length > 0) {
+          // const $table = this.$refs.feedBackRef
+
+          // const index = this.form.feedbackList.findIndex((item) => {
+          //   item == this.row
+          // })
+
+          this.$refs.feedBackRef.updateData(this.index, { saleId: val[0].id })
+          this.$refs.feedBackRef.updateData(this.index, { saleName: val.map((item) => item.nickName).join() })
+          console.log(' this.$refs.feedBackRef', this.$refs.feedBackRef)
+          let data = this.$refs.feedBackRef.getRecordset()
+          console.log('data', data)
+
+          // this.row.saleId = val[0].id
+          // this.row.saleName = val.map((item) => item.nickName).join()
+
+          // this.$refs.feedBackRef.reload()
+
+          // console.log('index', this.index)
+          // console.log("val-------",val);
+          // console.log("item",this.row);
+          // console.log('this.form.feedbackList[index]', this.form.feedbackList[index])
+          // this.row.saleId = val[0].id
+          // this.row.saleName = val.map((item) => item.nickName).join()
+          // this.form.feedbackList[this.index] = {
+          //   saleId: val[0].id,
+          //   saleName: val.map((item) => item.nickName).join(),
+          //   distributorId: this.row.distributorId,
+          //   distributorName: this.row.distributorName,
+          // }
+          console.log('this.form.feedbackList', this.form.feedbackList)
+
+          console.log('row222', this.row)
+        }
+      },
+      async showEdit(id) {
+        this.getOrderTypeList()
+        this.loading = false
+        if (id == null) {
+          this.title = '添加'
+        } else {
+          const [err, res] = await to(api.getDetail({ id: id }))
+          if (err) return (this.listLoading = false)
+
+          // this.form = res.data || {}
+          this.form.id = res.data.id
+          this.form.trainConcreteEndTime = res.data.trainConcreteEndTime
+          this.form.trainConcreteStartTime = res.data.trainConcreteStartTime
+          this.form.trainDate = res.data.trainDate
+          this.form.trainTitle = res.data.trainTitle
+          this.form.feedbackList = res.data.feedbackList
+          this.form.feedbackList.forEach((item) => {
+            item.sale = {
+              saleId: item.saleId,
+              saleName: item.saleName,
+            }
+          })
+
+          console.log('this.form.feedbackList', this.form.feedbackList)
+        }
+
+        this.dialogFormVisible = true
+      },
+      close() {
+        // this.$refs['dingTalkFrom'].resetForm()
+        // this.$refs['form'].resetFields()
+        this.form = this.$options.data().form
+        this.dialogFormVisible = false
+      },
+      //
+      handleSelectDistributor(type) {
+        this.queryParams.distType = type
+        this.$refs.selectDistributor.open()
+      },
+      // 选中数据
+      saveDistributors() {},
+      save() {
+        // let dingValid = this.$refs['form'].validateForm()
+        this.$refs['form'].validate(async (valid) => {
+          if (valid) {
+            // this.loading = true
+            const $table = this.$refs.feedBackRef
+            let insertList = $table.getInsertRecords()
+            let removeList = $table.getRemoveRecords()
+            let updateList = $table.getUpdateRecords()
+            if (insertList.length > 0) {
+              insertList.map((item) => (item.operate = '10'))
+            }
+            if (updateList.length > 0) {
+              updateList.map((item) => (item.operate = '20'))
+            }
+            if (removeList.length > 0) {
+              removeList.map((item) => (item.operate = '30'))
+            }
+            this.form.feedbackList = [...insertList, ...updateList, ...removeList]
+            this.form.trainConcreteStartTime = '2024-01-11T' + this.form.trainConcreteStartTime + ':00.000Z'
+            this.form.trainConcreteEndTime = '2024-01-11T' + this.form.trainConcreteEndTime + ':00.000Z'
+            const { msg } = await api.Create(this.form)
+            // this.loading = false
+            this.$baseMessage(msg, 'success', 'vab-hey-message-success')
+            this.$emit('fetch-data')
+            this.close()
+          }
+        })
+      },
+    },
+  }
+</script>

+ 297 - 0
src/views/work/train/head/components/Detail.vue

@@ -0,0 +1,297 @@
+<template>
+  <el-dialog append-to-body :title="title" :visible.sync="dialogFormVisible" @close="close">
+    <el-descriptions class="margin-top" :column="2" border>
+      <el-descriptions-item>
+        <template slot="label">培训主题</template>
+        {{ detail.trainTitle }}
+      </el-descriptions-item>
+      <el-descriptions-item>
+        <template slot="label">培训日期</template>
+        {{ detail.trainDate }}
+      </el-descriptions-item>
+      <el-descriptions-item>
+        <template slot="label">培训开始时间</template>
+        {{ detail.trainConcreteStartTime }}
+      </el-descriptions-item>
+      <el-descriptions-item>
+        <template slot="label">培训结束时间</template>
+        {{ detail.trainConcreteEndTime }}
+      </el-descriptions-item>
+    </el-descriptions>
+
+    <vxe-table
+      ref="feedBackRef"
+      class="mt10"
+      border
+      resizable
+      show-overflow
+      :data="detail.feedbackDetailList"
+      :edit-config="{ trigger: 'click', mode: 'cell' }"
+      style="height: 300px">
+      <vxe-column type="seq" width="60" />
+      <vxe-column field="saleName" title="销售工程师" width="100" />
+      <vxe-column field="distributorName" title="渠道名称" width="100" />
+
+      <vxe-column field="trainingPersNum" title="参训人数" width="100">
+        <template #default="{ row }" v-if="type == 'edit'">
+          <vxe-input
+            v-model.number="row.trainingPersNum"
+            type="text"
+            :disabled="type == 'detail'"
+            placeholder="请输入参训人数" />
+        </template>
+        <template #default="{ row }" v-else>
+          {{ row.trainingPersNum }}
+        </template>
+      </vxe-column>
+      <vxe-column field="distributorFeedback" title="经销商反馈">
+        <template #default="{ row }" v-if="type == 'edit'">
+          <vxe-input
+            v-model="row.distributorFeedback"
+            type="text"
+            :disabled="type == 'detail'"
+            placeholder="请输入经销商反馈" />
+        </template>
+        <template #default="{ row }" v-else>
+          {{ row.distributorFeedback }}
+        </template>
+      </vxe-column>
+    </vxe-table>
+
+    <template #footer>
+      <el-button @click="close">取 消</el-button>
+      <el-button v-if="!form.id" :loading="loading" type="primary" @click="save">确 定</el-button>
+    </template>
+
+    <!-- 选择支持人员弹窗 -->
+    <select-user ref="selectUser" @save="selectUser" />
+    <!-- 选择经销商弹窗 -->
+    <select-distributor ref="selectDistributor" :multiple="true" :query-params="queryParams" @save="saveDistributors" />
+  </el-dialog>
+</template>
+
+<script>
+  import api from '@/api/work/trainHead'
+  import to from 'await-to-js'
+  import typeApi from '@/api/work/type'
+  import SelectUser from '@/components/select/SelectUser'
+  import { mapGetters } from 'vuex'
+  import SelectDistributor from '@/components/select/SelectDistributor'
+
+  export default {
+    name: 'WorkOrderEdit',
+    components: {
+      SelectUser,
+      SelectDistributor,
+    },
+    props: {
+      businessInfo: {
+        type: Object,
+        default: () => {},
+      },
+    },
+    data() {
+      return {
+        sexList: [
+          { distributorId: 1, distributorName: 'a' },
+          { distributorId: 2, distributorName: 'b' },
+          { distributorId: 3, distributorName: 'c' },
+        ],
+        userList: [
+          { saleId: 1, saleName: '工程师1' },
+          { saleId: 2, saleName: '工程师2' },
+          { saleId: 3, saleName: '工程师3' },
+        ],
+        type: '',
+        row: {},
+        index: 0,
+        queryParams: {
+          distType: '',
+        },
+        loading: false,
+        form: {
+          id: undefined,
+          trainTitle: undefined,
+          trainDate: undefined,
+          trainConcreteStartTime: undefined,
+          trainConcreteEndTime: undefined,
+          feedbackList: [],
+        },
+        rules: {
+          trainTitle: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+          trainDate: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+          trainConcreteStartTime: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+          trainConcreteEndTime: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+        },
+        title: '',
+        dialogFormVisible: false,
+        orderTypeList: [],
+        dingtalkForm: undefined,
+        detail: {
+          id: '',
+          trainTitle: '',
+          trainDate: '',
+          trainConcreteStartTime: '',
+          trainConcreteEndTime: '',
+          feedbackDetailList: [],
+        },
+      }
+    },
+    computed: {
+      ...mapGetters({
+        userId: 'user/id',
+        nickName: 'user/nickName',
+      }),
+    },
+    mounted() {},
+    methods: {
+      DeleteData(row) {
+        const $table = this.$refs.feedBackRef
+        $table.remove(row)
+      },
+      createFeedBack() {
+        const $table = this.$refs.feedBackRef
+
+        $table.insertAt(
+          {
+            sale: {},
+            distributorList: [],
+          },
+          0
+        )
+        // if (this.form.feedbackList == undefined) {
+        //   this.form.feedbackList = []
+        // }
+        // this.form.feedbackList.push({
+        //   saleId: '',
+        //   saleName: '',
+        //   distributorId: '',
+        //   distributorName: '',
+        // })
+      },
+      deleteFeedBack(row) {
+        // const index = this.form.feedbackList.findIndex((item) => {
+        //   item == row
+        // })
+        // this.form.feedbackList.splice(index, 1)
+        const $table = this.$refs.feedBackRef
+        $table.remove(row)
+      },
+      async getOrderTypeList() {
+        this.orderTypeList.splice(0, this.orderTypeList.length)
+        const { data } = await typeApi.getList()
+        for (let item of data.list) {
+          this.orderTypeList.push(item)
+        }
+        this.changeOrderType()
+      },
+      changeOrderType() {},
+      // handleUploadFile(file) {
+      //   this.form.file = file
+      // },
+      handleSelectUser(rowIndex) {
+        // Object.assign(this.row, rowIndex)
+        this.index = rowIndex
+        // const index = this.form.feedbackList.findIndex(item=>{
+        //   item==row
+        // })
+        this.$refs.selectUser.open()
+      },
+      selectUser(val) {
+        if (val && val.length > 0) {
+          // const $table = this.$refs.feedBackRef
+
+          // const index = this.form.feedbackList.findIndex((item) => {
+          //   item == this.row
+          // })
+
+          this.$refs.feedBackRef.updateData(this.index, { saleId: val[0].id })
+          this.$refs.feedBackRef.updateData(this.index, { saleName: val.map((item) => item.nickName).join() })
+          console.log(' this.$refs.feedBackRef', this.$refs.feedBackRef)
+          let data = this.$refs.feedBackRef.getRecordset()
+          console.log('data', data)
+
+          // this.row.saleId = val[0].id
+          // this.row.saleName = val.map((item) => item.nickName).join()
+
+          // this.$refs.feedBackRef.reload()
+
+          // console.log('index', this.index)
+          // console.log("val-------",val);
+          // console.log("item",this.row);
+          // console.log('this.form.feedbackList[index]', this.form.feedbackList[index])
+          // this.row.saleId = val[0].id
+          // this.row.saleName = val.map((item) => item.nickName).join()
+          // this.form.feedbackList[this.index] = {
+          //   saleId: val[0].id,
+          //   saleName: val.map((item) => item.nickName).join(),
+          //   distributorId: this.row.distributorId,
+          //   distributorName: this.row.distributorName,
+          // }
+          console.log('this.form.feedbackList', this.form.feedbackList)
+
+          console.log('row222', this.row)
+        }
+      },
+      async showEdit(id, type) {
+        console.log('id222', id)
+        console.log('type', type)
+        this.loading = false
+        if (type == 'edit') {
+          this.title = '反馈'
+        } else {
+          this.title = '详情'
+        }
+        this.type = type
+        // this.form.saleId = this.userId
+        // this.form.saleName = this.nickName
+        this.listLoading = true
+        const [err, res] = await to(api.getFeedBackDetail({ id: id }))
+        if (err) return (this.listLoading = false)
+
+        this.detail = res.data || {}
+
+        // this.$nextTick(() => this.$refs.table.doLayout())
+
+        this.dialogFormVisible = true
+      },
+      close() {
+        // this.$refs['dingTalkFrom'].resetForm()
+        // this.$refs['form'].resetFields()
+        this.form = this.$options.data().form
+        this.dialogFormVisible = false
+      },
+      //
+      handleSelectDistributor(type) {
+        this.queryParams.distType = type
+        this.$refs.selectDistributor.open()
+      },
+      // 选中数据
+      saveDistributors() {},
+      async save() {
+        // const $table = this.$refs.feedBackRef
+        // let insertList = $table.getInsertRecords()
+        // let removeList = $table.getRemoveRecords()
+        // let updateList = $table.getUpdateRecords()
+        // if (insertList.length > 0) {
+        //   insertList.map((item) => (item.operate = '10'))
+        // }
+        // if (updateList.length > 0) {
+        //   updateList.map((item) => (item.operate = '20'))
+        // }
+        // if (removeList.length > 0) {
+        //   removeList.map((item) => (item.operate = '30'))
+        // }
+        console.log('detail.feedbackDetailList', this.detail.feedbackDetailList)
+        // this.detail.feedbackDetailList = [...insertList, ...updateList, ...removeList]
+        // console.log(" this.detail.feedbackDetailList", this.detail.feedbackDetailList);
+        // return
+        const { msg } = await api.FeedBackTrain({ feedBackTrainList: this.detail.feedbackDetailList })
+        // this.loading = false
+        this.$baseMessage(msg, 'success', 'vab-hey-message-success')
+        this.$emit('fetch-data')
+        this.close()
+      },
+    },
+  }
+</script>

+ 208 - 0
src/views/work/train/head/components/DetailFeedback.vue

@@ -0,0 +1,208 @@
+<template>
+  <div style="height: 100%">
+    <ul v-if="feedbackList.length" class="feedback">
+      <li v-for="(date, index) in feedbackList" :key="index">
+        <div class="date">
+          <h2>{{ date.feeDate.split('-')[2] }}</h2>
+          <h3>
+            {{ date.feeDate.split('-').splice(0, 2).join('.') }}
+          </h3>
+        </div>
+        <ul class="content">
+          <li v-for="(item, idx) in date.workOrderFeedbackList" :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.feePeople }} 反馈</span>
+                  <span>
+                    <vab-icon icon="time-line" />
+                    {{ item.feeDate }}
+                  </span>
+                </p>
+                <p>{{ item.feedback }}</p>
+                <div class="footer"></div>
+              </div>
+            </div>
+          </li>
+        </ul>
+      </li>
+    </ul>
+    <div v-else class="no-feedback">暂无反馈记录</div>
+  </div>
+</template>
+
+<script>
+  import api from '@/api/work/index'
+  import to from 'await-to-js'
+
+  export default {
+    name: 'Records',
+
+    props: {
+      workOrderId: {
+        type: Number,
+        default: 0,
+      },
+    },
+    data() {
+      return {
+        feedbackList: [],
+      }
+    },
+    mounted() {
+      this.getOptions()
+    },
+    methods: {
+      getOptions() {
+        // Promise.all([this.getDicts('plat_feedback_type')]).then(([feedbackType]) => {
+        //   this.feedbackTypeOptions = feedbackType.data.values || []
+        // })
+      },
+      async fetchData() {
+        let params = {
+          orderId: this.workOrderId,
+          daysBeforeToday: 9999,
+        }
+        let [err, res] = await to(api.getWorkOrderFeedbackByDay(params))
+        if (err) return
+        this.feedbackList = res.data.list || []
+      },
+    },
+  }
+</script>
+
+<style lang="scss" scoped>
+  .feedback {
+    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-feedback {
+    height: 100%;
+    width: 100%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    font-size: 12px;
+    color: rgba(0, 0, 0, 0.65);
+  }
+</style>

+ 410 - 0
src/views/work/train/head/components/DingTalkFromToVue.vue

@@ -0,0 +1,410 @@
+<template>
+  <div class="">
+    <el-form ref="dingtalkForm" label-position="top" :model="dingtalkForm">
+      <el-row :gutter="20">
+        <div v-for="(item, index) in dingtalkForm.items" :key="index">
+          <el-col v-if="item.componentName !== 'TableField'" :span="colSpan">
+            <el-form-item
+              v-if="item.props.label != '支持人员'"
+              :label="item.props.label"
+              :prop="'items.' + index + '.props.value'"
+              :rules="{
+                required: item.props.required,
+                message: '不能为空',
+                trigger: ['blur', 'change'],
+              }">
+              <!--文本框-->
+              <el-input
+                v-if="item.componentName === 'TextField'"
+                v-model="item.props.value"
+                :disabled="item.props.disabled"
+                :placeholder="item.props.placeholder" />
+              <!--文本域-->
+              <el-input
+                v-else-if="item.componentName === 'TextareaField'"
+                v-model="item.props.value"
+                :disabled="item.props.disabled"
+                :placeholder="item.props.placeholder"
+                :rows="5"
+                show-word-limit
+                type="textarea" />
+              <!--下拉选-->
+              <el-select
+                v-else-if="item.componentName === 'DDSelectField'"
+                v-model="item.props.value"
+                clearable
+                :disabled="item.props.disabled || item.props.label == '工单类型'"
+                :placeholder="item.props.placeholder"
+                style="width: 100%"
+                @change="selectWorkOrderType">
+                <el-option
+                  v-for="option in selectDataToJson(item.props.options)"
+                  :key="option.key"
+                  :label="option.value"
+                  :value="option.value" />
+              </el-select>
+              <!--下拉多选-->
+              <el-select
+                v-else-if="item.componentName === 'DDMultiSelectField'"
+                v-model="item.props.value"
+                clearable
+                :disabled="item.props.disabled"
+                multiple
+                :placeholder="item.props.placeholder"
+                style="width: 100%">
+                <el-option
+                  v-for="option in selectDataToJson(item.props.options)"
+                  :key="option.key"
+                  :label="option.value"
+                  :value="option.value" />
+              </el-select>
+              <!--时间选择器-->
+              <el-date-picker
+                v-else-if="item.componentName === 'DDDateField' && item.props.label != '运维时间'"
+                v-model="item.props.value"
+                :disabled="item.props.disabled"
+                :placeholder="item.props.placeholder"
+                :type="item.props.label == '支持时间' ? 'datetime' : 'date'"
+                :value-format="item.props.format" />
+
+              <el-date-picker
+                v-else-if="item.componentName === 'DDDateField' && item.props.label == '运维时间'"
+                v-model="item.props.value"
+                :disabled="item.props.disabled"
+                format="yyyy-MM-dd HH:mm"
+                :placeholder="item.props.placeholder"
+                :type="item.props.label == '运维时间' ? 'datetime' : 'date'"
+                value-format="yyyy-MM-dd HH:mm"
+                :value-format="item.props.format" />
+
+              <!--时间范围选择器-->
+              <el-date-picker
+                v-else-if="item.componentName === 'DDDateRangeField'"
+                v-model="item.props.value"
+                :disabled="item.props.disabled"
+                :end-placeholder="item.props.placeholder[1]"
+                :start-placeholder="item.props.placeholder[0]"
+                style="width: 100%"
+                type="daterange"
+                :value-format="item.props.format" />
+
+              <!--文件-->
+              <el-upload
+                v-else-if="item.componentName === 'DDAttachment'"
+                :ref="item.props.id"
+                :action="uploadFileUrl"
+                :limit="1"
+                :on-remove="
+                  (file, fileList) => {
+                    return removeFile(file, fileList, index)
+                  }
+                "
+                :on-success="
+                  (response, file) => {
+                    return setQuotationFile(response, file, index)
+                  }
+                "
+                :show-file-list="typeof item.props.value == 'string'">
+                <el-button size="mini" type="primary">点击上传</el-button>
+              </el-upload>
+
+              <!--选择用户-->
+              <el-input
+                v-else-if="item.componentName === 'InnerContactField'"
+                v-model="item.props.value"
+                :disabled="item.props.disabled"
+                :placeholder="item.props.placeholder"
+                readonly
+                suffix-icon="el-icon-search"
+                @focus="handleSelectUser(index)" />
+            </el-form-item>
+          </el-col>
+
+          <el-col v-if="item.componentName === 'TableField'" :span="24">
+            <el-form-item :label="item.props.label" :prop="item.props.id" :required="item.props.required">
+              <el-button
+                v-show="item.props.actionName"
+                style="float: right; margin-top: -20px; margin-bottom: 10px"
+                @click="addTableData(index)">
+                {{ item.props.actionName }}
+              </el-button>
+              <!--表格-->
+              <el-table :key="dingTableFlag" :ref="'dingTable' + index" border :data="item.tableData" height="250px">
+                <el-table-column
+                  v-for="(child, idx) in item.children"
+                  :key="idx"
+                  align="center"
+                  :label="child.props.label"
+                  :prop="child.props.id"
+                  show-overflow-tooltip>
+                  <template #default="{ row }">
+                    <!--文本框-->
+                    <el-select
+                      v-if="child.props.label === '产品型号'"
+                      v-model="row[child.props.id]"
+                      filterable
+                      :placeholder="item.props.placeholder"
+                      value-key="prodCode"
+                      @change="(val) => selectLabel(val, item.children, row)">
+                      <el-option v-for="prod in productList" :key="prod.id" :label="prod.prodCode" :value="prod" />
+                    </el-select>
+                    <el-select
+                      v-if="child.props.label === '产品名称'"
+                      v-model="row[child.props.id]"
+                      filterable
+                      :placeholder="item.props.placeholder"
+                      value-key="prodName"
+                      @change="(val) => selectLabel(val, item.children, row)">
+                      <el-option v-for="prod in productList" :key="prod.id" :label="prod.prodName" :value="prod" />
+                    </el-select>
+                    <!--数字文本框-->
+                    <el-input
+                      v-if="child.componentName === 'NumberField'"
+                      v-model="row[child.props.id]"
+                      :min="1"
+                      onkeyup="value=value.replace(/[^\d]/g,'')" />
+                  </template>
+                </el-table-column>
+              </el-table>
+            </el-form-item>
+          </el-col>
+        </div>
+      </el-row>
+    </el-form>
+
+    <!-- 选择人员弹窗 -->
+    <select-user ref="selectUser" @save="selectUser" />
+  </div>
+</template>
+
+<script>
+  import productApi from '@/api/base/product'
+  import workApi from '@/api/work/index'
+  import SelectUser from '@/components/select/SelectUser'
+  import asyncUploadFile from '@/utils/uploadajax'
+  import axios from 'axios'
+  import to from 'await-to-js'
+  import { parseTime } from '@/utils/index'
+  export default {
+    name: 'DingTalkFromToVue',
+    components: {
+      SelectUser,
+    },
+    props: {
+      colSpan: {
+        type: Number,
+        default: 24,
+      },
+    },
+    data() {
+      return {
+        dingtalkForm: {},
+        productList: [],
+        dingRules: {},
+        userItemIndex: {},
+        dingTableFlag: true,
+        uploadFileUrl: process.env.VUE_APP_UPLOAD_FILE_WEED,
+        fileSettings: {
+          // 文件配置信息
+          fileSize: 20971520,
+          fileTypes: '.rar,.zip,.doc,.docx,.pdf',
+        },
+      }
+    },
+    mounted() {
+      this.getProduct()
+    },
+    // 方法集合
+    methods: {
+      selectWorkOrderType(val) {
+        console.log('下拉选择的val是:', val)
+      },
+      selectLabel(val, children, row) {
+        const codeId = children.find((item) => item.props.label == '产品型号').props.id
+        const nameId = children.find((item) => item.props.label == '产品名称').props.id
+        row[codeId] = val.prodCode
+        row[nameId] = val.prodName
+      },
+      getProduct() {
+        let params = {
+          pageNum: 1,
+          pageSize: 999,
+        }
+        Promise.all([productApi.getList({ ...params })])
+          .then(([product]) => {
+            this.productList = product.data.list || []
+          })
+          .catch((err) => console.log(err))
+      },
+      addTableData(index) {
+        let newObj = {}
+        let tableData = this.dingtalkForm.items[index].tableData
+        if (!tableData) {
+          this.dingtalkForm.items[index].tableData = []
+        }
+        for (let child of this.dingtalkForm.items[index].children) {
+          newObj[child.props.id] = ''
+        }
+        this.dingtalkForm.items[index].tableData.push(newObj)
+        this.dingTableFlag = !this.dingTableFlag
+      },
+      handleSelectUser(index) {
+        this.userItemIndex = index
+        this.$refs.selectUser.open()
+      },
+      selectUser(val) {
+        let nickName = val.map((item) => item.nickName).join()
+        this.dingtalkForm.items[this.userItemIndex].props.value = nickName
+        this.$forceUpdate()
+      },
+      removeFile(file, list, index) {
+        this.dingtalkForm.items[index].props.value = null
+      },
+      async setQuotationFile(res, file, index) {
+        // 如果上传成功
+        if (res.Code == 200) {
+          console.log(file)
+          const fileName = file.name // 资料存储url
+          const fileUrl = res.Data // 资料存储url
+          const [uploadErr, uploadRes] = await to(workApi.uploadDingtalk({ fileName, fileUrl }))
+          if (uploadErr) return
+          if (uploadRes.code == 200) {
+            this.dingtalkForm.items[index].props.value = uploadRes.data
+            let temp = JSON.parse(JSON.stringify(this.dingtalkForm))
+            this.$refs.dingtalkForm.clearValidate('items.' + index + '.props.value')
+            this.dingtalkForm = temp
+          }
+        } else {
+          this.$message.error('上传文件失败')
+        }
+      },
+      // 文件
+      setFile(index, file) {
+        // this.$emit('upload', file.raw)
+        let flag1 = file.size < this.fileSettings.fileSize
+        if (!flag1) {
+          this.$message.warning('文件过大,请重新选择!')
+          this.dingtalkForm.items[index].props.value = ''
+          return false
+        }
+        // let flag2 = this.fileSettings.fileTypes.split(',').includes('.' + file.name.split('.').pop())
+        // if (!flag2) {
+        //   this.$message.warning('文件类型不符合,请重新选择!')
+        //   this.dingtalkForm.items[index].props.value = ''
+        //   this.$forceUpdate()
+        //   return false
+        // }
+        return true
+        // // 上传文件一直校验不过问题方案
+        // let temp = JSON.parse(JSON.stringify(this.dingtalkForm))
+        // this.$refs.dingtalkForm.clearValidate('items.' + index + '.props.value')
+        // this.dingtalkForm = temp
+      },
+      // 上传
+      uploadrequest(index, option) {
+        let idx = index
+        console.log(option)
+        let _this = this
+        let url = process.env.VUE_APP_UPLOAD_WEED
+        axios
+          .post(url)
+          .then(function (res) {
+            if (res.data && res.data.fid && res.data.fid !== '') {
+              option.action = `${process.env.VUE_APP_PROTOCOL}${res.data.publicUrl}/${res.data.fid}`
+              asyncUploadFile(option).then(async (res) => {
+                if (res) {
+                  let fileRes = JSON.parse(res)
+                  const fileName = fileRes.name // 资料存储url
+                  const fileUrl = option.action // 资料存储url
+                  const [uploadErr, uploadRes] = await to(workApi.uploadDingtalk({ fileName, fileUrl }))
+                  if (uploadErr) return
+                  if (uploadRes.code == 200) {
+                    console.log(uploadRes)
+                    _this.dingtalkForm.items[idx].props.value = uploadRes.data
+                    let temp = JSON.parse(JSON.stringify(_this.dingtalkForm))
+                    _this.$refs.dingtalkForm.clearValidate('items.' + idx + '.props.value')
+                    _this.dingtalkForm = temp
+                  }
+                }
+              })
+            } else {
+              _this.$message({
+                type: 'warning',
+                message: '未上传成功!请刷新界面重新上传!',
+              })
+            }
+          })
+          .catch(function () {
+            _this.$message({
+              type: 'warning',
+              message: '未上传成功!请重新上传!',
+            })
+          })
+      },
+      selectDataToJson(data) {
+        console.log('select data is', data)
+        return data.map((item) => {
+          return JSON.parse(item)
+        })
+      },
+      setFormAndRules(dingtalkForm) {
+        for (let index in dingtalkForm.items) {
+          if (dingtalkForm.items[index].componentName === 'DDDateRangeField') {
+            dingtalkForm.items[index].props.placeholder = JSON.parse(dingtalkForm.items[index].props.label)
+            dingtalkForm.items[index].props.label = dingtalkForm.items[index].props.placeholder.join(' - ')
+          }
+          // console.log(dingtalkForm.items[index].props.label, dingtalkForm.items[index].componentName,"=================")
+          if (dingtalkForm.items[index].props.label == '工单类型') {
+            console.log(dingtalkForm.items[index].props.options)
+            dingtalkForm.items[index].props.value = JSON.parse(dingtalkForm.items[index].props.options[0]).value
+          }
+          if (dingtalkForm.items[index].props.label == '运维时间') {
+            this.$set(dingtalkForm.items[index].props, 'value', parseTime(new Date()).substring(0, 16))
+            dingtalkForm.items[index].props.format = 'yyyy-MM-dd HH:mm'
+          }
+        }
+        this.dingtalkForm = dingtalkForm
+        this.$forceUpdate()
+      },
+      getFormData() {
+        for (let index in this.dingtalkForm.items) {
+          // 表格数据需要特殊处理
+          if (this.dingtalkForm.items[index].componentName === 'TableField') {
+            this.dingtalkForm.items[index].props.value = []
+            let tableData = this.dingtalkForm.items[index].tableData || []
+            for (let row of tableData) {
+              // 循环数据,组装信息
+              let newData = []
+              for (let colKey in row) {
+                let child = this.dingtalkForm.items[index].children.find((val) => {
+                  return val.props.id === colKey //筛选出匹配数据
+                })
+                newData.push({
+                  value: row[colKey],
+                  id: child.props.id,
+                  name: child.props.label,
+                  required: child.props.required,
+                  componentName: child.componentName,
+                })
+              }
+              this.dingtalkForm.items[index].props.value.push(newData)
+            }
+          }
+        }
+        return this.dingtalkForm
+      },
+      validateForm() {
+        let result = false
+        this.$refs['dingtalkForm'].validate((valid) => {
+          result = valid
+        })
+        return result
+      },
+      resetForm() {
+        this.$refs['dingtalkForm'].resetFields()
+      },
+    },
+  }
+</script>
+
+<style lang="scss" scoped></style>

+ 273 - 0
src/views/work/train/head/components/Edit.vue

@@ -0,0 +1,273 @@
+<template>
+  <el-dialog append-to-body :title="title" :visible.sync="dialogFormVisible" @close="close">
+    <el-form ref="form" label-position="top" label-width="100px" :model="form" :rules="rules">
+      <el-row :gutter="20">
+        <!-- <el-col :span="12">
+          <el-form-item label="工单名称" prop="name">
+            <el-input v-model="form.name" />
+          </el-form-item>
+        </el-col> -->
+        <el-col :span="12">
+          <el-form-item label="工单类型" prop="orderTypeId">
+            <el-select
+              v-model="form.orderTypeId"
+              clearable
+              placeholder="工单类型"
+              style="width: 100%"
+              @change="changeOrderType">
+              <el-option v-for="item in orderTypeList" :key="item.id" :label="item.name" :value="item.id" />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="支持人员" prop="assignUserName">
+            <el-input v-model="form.assignUserName" readonly suffix-icon="el-icon-search" @focus="handleSelectUser" />
+          </el-form-item>
+        </el-col>
+        <!-- <el-col :span="12">
+          <el-form-item label="备注" prop="remark">
+            <el-input v-model="form.remark" placeholder="请输入内容" :rows="2" show-word-limit type="textarea" />
+          </el-form-item>
+        </el-col> -->
+        <!-- <el-col :span="12">
+          <el-form-item label="结束时间" prop="remark">
+            <el-date-picker
+              v-model="form.endTime"
+              placeholder="选择结束时间"
+              style="width: 100%"
+              type="datetime"
+              value-format="yyyy-MM-dd HH:mm:ss" />
+          </el-form-item>
+        </el-col> -->
+      </el-row>
+
+      <!--      钉钉审批流表单-->
+      <DingTalkFromToVue ref="dingTalkFrom" :col-span="12" />
+      <!--      钉钉审批流表单-->
+      <!--      <el-row :gutter="20">-->
+
+      <!--      </el-row>-->
+    </el-form>
+    <template #footer>
+      <el-button @click="close">取 消</el-button>
+      <el-button v-if="!form.id" :loading="loading" type="primary" @click="save">确 定</el-button>
+    </template>
+
+    <!-- 选择支持人员弹窗 -->
+    <select-user ref="selectUser" @save="selectUser" />
+  </el-dialog>
+</template>
+
+<script>
+  import workApi from '@/api/work/index'
+  import typeApi from '@/api/work/type'
+  import SelectUser from '@/components/select/SelectUser'
+  import DingTalkFromToVue from './DingTalkFromToVue.vue'
+
+  export default {
+    name: 'WorkOrderEdit',
+    components: {
+      SelectUser,
+      DingTalkFromToVue,
+    },
+    props: {
+      businessInfo: {
+        type: Object,
+        default: () => {},
+      },
+    },
+    data() {
+      return {
+        loading: false,
+        form: {
+          nboId: undefined,
+          nboName: undefined,
+          nboCode: undefined,
+          custId: undefined,
+          custName: undefined,
+          name: undefined,
+          orderTypeId: undefined,
+          orderTypeName: undefined,
+          assignUserId: undefined,
+          assignUserName: undefined,
+          formData: undefined,
+          dingTalkFormData: undefined,
+          feedback: undefined,
+          // file: undefined,
+          remark: undefined,
+          endTime: undefined,
+          dingtalkForm: undefined,
+          trialTimeStart: undefined,
+          trialTimeEnd: undefined,
+          expectTime: undefined,
+          supportTime: undefined,
+        },
+        rules: {
+          name: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+          orderTypeId: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+          assignUserName: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+        },
+        title: '',
+        dialogFormVisible: false,
+        orderTypeList: [],
+        dingtalkForm: undefined,
+      }
+    },
+    mounted() {
+      this.getOrderTypeList()
+    },
+    methods: {
+      async getOrderTypeList() {
+        this.orderTypeList.splice(0, this.orderTypeList.length)
+        const { data } = await typeApi.getList()
+        for (let item of data.list) {
+          if (item.name !== '硬件交付工单' && item.name !== '经销商支持') {
+            this.orderTypeList.push(item)
+          }
+        }
+      },
+      changeOrderType() {
+        let item = this.orderTypeList.find((item) => {
+          return item.id === this.form.orderTypeId
+        })
+        console.log(item.formColumn)
+        this.form.orderTypeName = item.name
+        this.form.orderTypeCode = item.workflowCode
+        this.dingtalkForm = JSON.parse(item.formColumn)
+        console.log(this.dingtalkForm)
+        for (let index in this.dingtalkForm.items) {
+          if (this.dingtalkForm.items[index].props.label === '项目编码') {
+            this.dingtalkForm.items[index].props.value = this.form.nboCode
+            this.dingtalkForm.items[index].props.disabled = true
+          }
+          if (this.dingtalkForm.items[index].props.label === '项目名称') {
+            this.dingtalkForm.items[index].props.value = this.form.nboName
+            this.dingtalkForm.items[index].props.disabled = true
+          }
+          if (this.dingtalkForm.items[index].props.label === '客户名称') {
+            this.dingtalkForm.items[index].props.value = this.form.custName
+            this.dingtalkForm.items[index].props.disabled = true
+          }
+          if (this.dingtalkForm.items[index].props.label === '所在省') {
+            this.dingtalkForm.items[index].props.value = this.form.custProvince
+            this.dingtalkForm.items[index].props.disabled = true
+          }
+          if (this.dingtalkForm.items[index].props.label === '所在市') {
+            this.dingtalkForm.items[index].props.value = this.form.custCity
+            this.dingtalkForm.items[index].props.disabled = true
+          }
+          if (this.dingtalkForm.items[index].props.label === '销售工程师') {
+            this.dingtalkForm.items[index].props.value = this.form.saleName
+            this.dingtalkForm.items[index].props.disabled = true
+          }
+          if (this.dingtalkForm.items[index].props.label === '工单名称') {
+            this.dingtalkForm.items[index].props.value = this.form.custName + this.form.orderTypeName
+          }
+        }
+        this.$refs.dingTalkFrom.setFormAndRules(this.dingtalkForm)
+      },
+      // handleUploadFile(file) {
+      //   this.form.file = file
+      // },
+      handleSelectUser() {
+        this.$refs.selectUser.open()
+      },
+      selectUser(val) {
+        if (val && val.length > 0) {
+          this.form.assignUserId = val[0].id
+          this.form.assignUserName = val.map((item) => item.nickName).join()
+        }
+      },
+      showEdit(row) {
+        this.loading = false
+        if (!row) {
+          this.title = '添加'
+        } else {
+          this.title = '编辑'
+          this.form = Object.assign({}, row)
+        }
+        if (this.businessInfo) {
+          this.form.nboId = this.businessInfo.id
+          this.form.nboName = this.businessInfo.nboName
+          this.form.nboCode = this.businessInfo.nboCode
+          this.form.custId = this.businessInfo.custId
+          this.form.custName = this.businessInfo.custName
+          this.form.saleId = this.businessInfo.saleId
+          this.form.saleName = this.businessInfo.saleName
+        }
+        this.dialogFormVisible = true
+      },
+      close() {
+        this.$refs['dingTalkFrom'].resetForm()
+        this.$refs['form'].resetFields()
+        this.form = this.$options.data().form
+        this.dialogFormVisible = false
+      },
+      save() {
+        let dingValid = this.$refs['dingTalkFrom'].validateForm()
+        this.$refs['form'].validate(async (valid) => {
+          if (valid && dingValid) {
+            let items = []
+            let dingtalkForm = this.$refs['dingTalkFrom'].getFormData()
+            for (let item of dingtalkForm.items) {
+              if (item.componentName === 'DDAttachment') {
+                if (!item.props && item.props.required) {
+                  this.$baseMessage('请上传' + item.props.label, 'error', 'vab-hey-message-error')
+                  return
+                }
+              }
+              if (item.props.label === '项目编码') {
+                this.form.nboCode = item.props.value
+              }
+              if (item.props.label === '项目名称') {
+                this.form.nboName = item.props.value
+              }
+              if (item.props.label === '客户名称') {
+                this.form.custName = item.props.value
+              }
+              if (item.props.label === '工单名称') {
+                this.form.name = item.props.value
+              }
+              if (item.props.label === '备注' && !item.props.value) {
+                item.props.value = ''
+              }
+              if (item.props.label === '调研表上传' && !item.props.value) {
+                item.props.value = ''
+              }
+
+              if (item.props.label === '试用开始时间') {
+                this.form.trialTimeStart = item.props.value
+              }
+              if (item.props.label === '试用结束时间') {
+                this.form.trialTimeEnd = item.props.value
+              }
+              if (item.props.label === '期望完成时间') {
+                this.form.expectTime = item.props.value
+              }
+              if (item.props.label === '支持时间') {
+                this.form.supportTime = item.props.value
+              }
+              if (item.props.label === '支持人员') {
+                item.props.value = this.form.assignUserName
+              }
+              items.push({
+                componentName: item.componentName,
+                id: item.props.id,
+                name: item.props.label,
+                value: item.props.value,
+                required: item.props.required,
+              })
+            }
+            this.form.formData = items
+            this.loading = true
+            const { msg } = await workApi.doAdd(this.form)
+            this.loading = false
+            this.$baseMessage(msg, 'success', 'vab-hey-message-success')
+            this.$emit('fetch-data')
+            this.close()
+          }
+        })
+      },
+    },
+  }
+</script>

+ 136 - 0
src/views/work/train/head/components/Feedback.vue

@@ -0,0 +1,136 @@
+<template>
+  <el-dialog :title="title" :visible.sync="dialogFormVisible" @close="close">
+    <el-form ref="form" label-position="top" :model="form" :rules="rules">
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="工单编号" prop="id">
+            <el-input v-model="form.id" disabled readonly />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="工单状态" prop="orderStatus">
+            <el-input
+              disabled
+              readonly
+              suffix-icon="el-icon-search"
+              :value="selectDictLabel(orderStatusOptions, form.orderStatus)" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="工单类型" prop="orderTypeName">
+            <el-input v-model="form.orderTypeName" disabled readonly />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="支持人员" prop="assignUserName">
+            <el-input v-model="form.assignUserName" disabled readonly suffix-icon="el-icon-search" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="关联客户" prop="custName">
+            <el-input v-model="form.custName" disabled readonly />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="关联项目" prop="nboName">
+            <el-input v-model="form.nboName" disabled readonly suffix-icon="el-icon-search" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row :gutter="20">
+        <el-col :span="24">
+          <el-form-item label="完成信息" prop="finishRemark">
+            <el-input
+              v-model="form.finishRemark"
+              placeholder="请输入完成信息"
+              :rows="5"
+              show-word-limit
+              type="textarea" />
+          </el-form-item>
+        </el-col>
+        <!-- <el-col :span="12">
+          <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>
+    <template #footer>
+      <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 workApi from '@/api/work/index'
+  import SelectUser from '@/components/select/SelectUser'
+
+  export default {
+    name: 'WorkOrderFeedback',
+    components: {
+      SelectUser,
+    },
+    props: {
+      orderStatusOptions: {
+        type: Array,
+        default: () => [],
+      },
+    },
+    data() {
+      return {
+        form: {
+          orderId: undefined,
+          finishRemark: undefined,
+        },
+        rules: {
+          orderId: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+          finishRemark: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+        },
+        title: '',
+        dialogFormVisible: false,
+        orderTypeList: [],
+        dingtalkForm: undefined,
+      }
+    },
+    mounted() {},
+    methods: {
+      handleSelectUser() {
+        this.$refs.selectUser.open()
+      },
+      selectUser(val) {
+        if (val && val.length > 0) {
+          this.form.assignUserId = val[0].id
+          this.form.assignUserName = val.map((item) => item.nickName).join()
+        }
+      },
+      showEdit(row) {
+        this.title = row.name
+        this.form = Object.assign({}, row)
+        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 workApi.finishWorkOrder(this.form)
+            this.$baseMessage(msg, 'success', 'vab-hey-message-success')
+            this.$emit('fetch-data')
+            this.close()
+          }
+        })
+      },
+    },
+  }
+</script>

+ 309 - 0
src/views/work/train/head/components/FeedbackRecord.vue

@@ -0,0 +1,309 @@
+<template>
+  <div style="height: 100%; overflow: auto">
+    <div v-if="type == 'try'">
+      <el-table border :data="detail.feedbackTrail">
+        <el-table-column
+          v-for="(item, index) in tryColumns"
+          :key="index"
+          align="center"
+          height="calc(100% - 42px)"
+          :label="item.label"
+          :min-width="item.width"
+          :prop="item.prop"
+          show-overflow-tooltip>
+          <template #default="{ row }">
+            <el-button v-if="item.prop === 'id'" style="font-size: 14px" type="text" @click="handleDetail(row)">
+              {{ row.id }}
+            </el-button>
+            <span v-else-if="item.prop === 'feedbackTrialType'">
+              {{ tryType[row.feedbackTrialType] }}
+            </span>
+            <span v-else-if="item.prop === 'feedbackTrialTime'">
+              {{ parseTime(row.feedbackTrialTime, '{y}-{m}-{d}') }}
+            </span>
+            <span v-else>{{ row[item.prop] }}</span>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+    <div v-if="type == 'support'">
+      <el-empty v-if="!detail.feedbackSupportTime" :image-size="200" />
+      <el-descriptions v-else border :column="1" :label-style="{ width: '130px' }">
+        <el-descriptions-item label="反馈时间">{{ detail.feedbackSupportTime }}</el-descriptions-item>
+        <el-descriptions-item label="本次讲解情况反馈">
+          <span v-html="detail.feedbackSupportContent.replace(/\n/g, '<br>')"></span>
+        </el-descriptions-item>
+      </el-descriptions>
+    </div>
+    <div v-if="type == 'sale'">
+      <el-empty v-if="!detail.feedbackSaleTime" :image-size="200" />
+      <el-descriptions
+        v-else-if="detail.orderTypeName == '技术文件支持'"
+        border
+        :column="1"
+        :label-style="{ width: '130px' }">
+        <el-descriptions-item label="反馈时间">{{ detail.feedbackSaleTime }}</el-descriptions-item>
+        <el-descriptions-item label="用户反馈">
+          <span v-html="detail.feedbackSaleUser.replace(/\n/g, '<br>')"></span>
+        </el-descriptions-item>
+        <el-descriptions-item label="下一步计划">
+          <span v-html="detail.feedbackSaleNext.replace(/\n/g, '<br>')"></span>
+        </el-descriptions-item>
+        <el-descriptions-item label="技术支持满意度">
+          {{ detail.satisfactionRating ? detail.satisfactionRating + '分' : '' }}
+        </el-descriptions-item>
+        <el-descriptions-item label="技术支持提升建议">
+          <span v-html="detail.promotionProposal.replace(/\n/g, '<br>')"></span>
+        </el-descriptions-item>
+      </el-descriptions>
+      <el-descriptions
+        v-else-if="detail.orderTypeName == '售前讲解支持' || detail.orderTypeName == '经销商支持'"
+        border
+        :column="1"
+        :label-style="{ width: '130px' }">
+        <el-descriptions-item label="反馈时间">
+          <span v-html="detail.feedbackSaleTime.replace(/\n/g, '<br>')"></span>
+        </el-descriptions-item>
+        <el-descriptions-item label="会议纪要">
+          <span v-html="detail.feedbackSaleMeeting.replace(/\n/g, '<br>')"></span>
+        </el-descriptions-item>
+        <el-descriptions-item label="客户/经销商反馈">
+          <span v-html="detail.feedbackSaleDist.replace(/\n/g, '<br>')"></span>
+        </el-descriptions-item>
+        <el-descriptions-item label="下一步计划">
+          <span v-html="detail.feedbackSaleNext.replace(/\n/g, '<br>')"></span>
+        </el-descriptions-item>
+        <el-descriptions-item label="技术支持满意度">
+          {{ detail.satisfactionRating ? detail.satisfactionRating + '分' : '' }}
+        </el-descriptions-item>
+        <el-descriptions-item label="技术支持提升建议">
+          <span v-html="detail.promotionProposal.replace(/\n/g, '<br>')"></span>
+        </el-descriptions-item>
+      </el-descriptions>
+      <el-descriptions
+        v-else-if="detail.orderTypeName == '售后运维工单'"
+        border
+        :column="1"
+        :label-style="{ width: '130px' }">
+        <el-descriptions-item label="反馈时间">{{ detail.feedbackSaleTime }}</el-descriptions-item>
+        <el-descriptions-item label="工单反馈">
+          <span v-html="detail.feedbackSaleOrder.replace(/\n/g, '<br>')"></span>
+        </el-descriptions-item>
+      </el-descriptions>
+    </div>
+    <try-feedback-detail ref="tryFB" />
+  </div>
+</template>
+
+<script>
+  import TryFeedbackDetail from '@/views/work/order/components/TryFeedbackDetail'
+
+  export default {
+    name: 'DetailsContract',
+    components: { TryFeedbackDetail },
+
+    props: {
+      detail: {
+        type: [Object, String],
+        required: true,
+      },
+      type: {
+        type: String,
+        required: true,
+      },
+    },
+    data() {
+      return {
+        tryType: {
+          10: '试用启动反馈',
+          20: '试用过程反馈',
+          30: '试用总结反馈',
+        },
+        tryColumns: [
+          {
+            label: '编号',
+            width: '40px',
+            prop: 'id',
+            disableCheck: true,
+          },
+          {
+            label: '试用反馈类型',
+            width: '100px',
+            prop: 'feedbackTrialType',
+            sortable: false,
+            disableCheck: false,
+          },
+          {
+            label: '总结/问题',
+            width: 'auto',
+            prop: 'feedbackTrialContent',
+            sortable: false,
+            disableCheck: false,
+          },
+          {
+            label: '客户/经销商反馈',
+            width: 'auto',
+            prop: 'feedbackTrialDist',
+            sortable: false,
+            disableCheck: false,
+          },
+          {
+            label: '计划',
+            width: 'auto',
+            prop: 'feedbackTrialPlan',
+            sortable: false,
+            disableCheck: false,
+          },
+          {
+            label: '反馈时间',
+            width: '80px',
+            prop: 'feedbackTrialTime',
+            sortable: false,
+            disableCheck: false,
+          },
+        ],
+        list: [],
+      }
+    },
+    mounted() {},
+    methods: {
+      handleDetail(row) {
+        this.$refs.tryFB.open(row)
+      },
+    },
+  }
+</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>

+ 171 - 0
src/views/work/train/head/components/SaleFeedback.vue

@@ -0,0 +1,171 @@
+<template>
+  <el-dialog title="销售反馈" :visible.sync="dialogFormVisible" width="500px" @close="close">
+    <el-form
+      ref="form"
+      label-position="top"
+      :model="form"
+      :rules="rules"
+      style="max-height: 550px; overflow-y: scroll; overflow-x: hidden">
+      <el-row :gutter="20">
+        <el-col :span="24">
+          <el-form-item label="反馈时间" prop="feedbackSaleTime">
+            <el-date-picker
+              v-model="form.feedbackSaleTime"
+              placeholder="选择反馈时间"
+              style="width: 100%"
+              type="date"
+              value-format="yyyy-MM-dd" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <!-- 售前讲解 -->
+      <el-row v-if="workType == '售前讲解支持' || workType == '经销商支持'">
+        <el-col :span="24">
+          <el-form-item label="会议纪要" prop="feedbackSaleMeeting">
+            <el-input
+              v-model="form.feedbackSaleMeeting"
+              placeholder="会议纪要"
+              :rows="5"
+              show-word-limit
+              type="textarea" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="24">
+          <el-form-item label="客户/经销商反馈" prop="feedbackSaleDist">
+            <el-input
+              v-model="form.feedbackSaleDist"
+              placeholder="客户/经销商反馈"
+              :rows="5"
+              show-word-limit
+              type="textarea" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <!-- END -->
+      <!-- 技术文件 -->
+      <el-row v-if="workType == '技术文件支持'">
+        <el-col :span="24">
+          <el-form-item label="用户反馈" prop="feedbackSaleUser">
+            <el-input
+              v-model="form.feedbackSaleUser"
+              placeholder="用户反馈"
+              :rows="5"
+              show-word-limit
+              type="textarea" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <!-- END -->
+      <el-row v-if="workType == '售前讲解支持' || workType == '技术文件支持' || workType == '经销商支持'">
+        <el-col :span="24">
+          <el-form-item label="下一步计划" prop="feedbackSaleNext">
+            <el-input
+              v-model="form.feedbackSaleNext"
+              placeholder="下一步计划"
+              :rows="5"
+              show-word-limit
+              type="textarea" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <!-- 技术文件 -->
+      <el-row v-if="workType == '售后运维工单'">
+        <el-col :span="24">
+          <el-form-item label="工单反馈" prop="feedbackSaleOrder">
+            <el-input
+              v-model="form.feedbackSaleOrder"
+              placeholder="问题是否已解决、用户是否满意"
+              :rows="5"
+              show-word-limit
+              type="textarea" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <!-- END -->
+      <el-row v-if="workType == '售前讲解支持' || workType == '技术文件支持' || workType == '经销商支持'">
+        <el-col :span="24">
+          <el-form-item label="技术支持满意度" prop="satisfactionRating">
+            <el-radio-group v-model="form.satisfactionRating">
+              <el-radio label="1">1分</el-radio>
+              <el-radio label="2">2分</el-radio>
+              <el-radio label="3">3分</el-radio>
+              <el-radio label="4">4分</el-radio>
+              <el-radio label="5">5分</el-radio>
+            </el-radio-group>
+          </el-form-item>
+        </el-col>
+        <el-col :span="24">
+          <el-form-item label="技术支持提升建议" prop="promotionProposal">
+            <el-input
+              v-model="form.promotionProposal"
+              placeholder="技术支持提升建议"
+              :rows="5"
+              show-word-limit
+              type="textarea" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+    <template #footer>
+      <el-button @click="close">取 消</el-button>
+      <el-button type="primary" @click="save">确 定</el-button>
+    </template>
+  </el-dialog>
+</template>
+
+<script>
+  import workOrderApi from '@/api/work/index'
+  export default {
+    name: 'WorkOrderFeedback',
+    data() {
+      return {
+        workType: '',
+        form: {
+          feedbackSaleTime: '',
+          feedbackSaleMeeting: '',
+          feedbackSaleDist: '',
+          feedbackSaleUser: '',
+          feedbackSaleNext: '',
+          feedbackSaleOrder: '',
+          orderId: '',
+          satisfactionRating: '',
+          promotionProposal: '',
+        },
+        rules: {
+          feedbackSaleTime: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+          feedbackSaleMeeting: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+          feedbackSaleDist: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+          feedbackSaleUser: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+          feedbackSaleNext: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+          feedbackSaleOrder: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+          satisfactionRating: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+        },
+        dialogFormVisible: false,
+      }
+    },
+    mounted() {},
+    methods: {
+      open(row) {
+        console.log(row)
+        this.form.orderId = row.id
+        this.workType = row.orderTypeName
+        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 workOrderApi.feedbackSale(this.form)
+            this.$baseMessage(msg, 'success', 'vab-hey-message-success')
+            this.$emit('update-detail')
+            this.close()
+          }
+        })
+      },
+    },
+  }
+</script>

+ 77 - 0
src/views/work/train/head/components/SupportFeedback.vue

@@ -0,0 +1,77 @@
+<template>
+  <el-dialog title="支持人员总结" :visible.sync="dialogFormVisible" width="600px" @close="close">
+    <el-form ref="form" label-position="top" :model="form" :rules="rules">
+      <el-row :gutter="20">
+        <el-col :span="24">
+          <el-form-item label="反馈时间" prop="feedbackSupportTime">
+            <el-date-picker
+              v-model="form.feedbackSupportTime"
+              placeholder="选择反馈时间"
+              style="width: 100%"
+              type="date"
+              value-format="yyyy-MM-dd" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="24">
+          <el-form-item label="本次情况反馈" prop="feedbackSupportContent">
+            <el-input
+              v-model="form.feedbackSupportContent"
+              placeholder="本次情况反馈"
+              :rows="5"
+              show-word-limit
+              type="textarea" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+    <template #footer>
+      <el-button @click="close">取 消</el-button>
+      <el-button type="primary" @click="save">确 定</el-button>
+    </template>
+  </el-dialog>
+</template>
+
+<script>
+  import workOrderApi from '@/api/work/index'
+  export default {
+    name: 'WorkOrderFeedback',
+    data() {
+      return {
+        workType: '',
+        form: {
+          feedbackSupportContent: '',
+          feedbackSupportTime: '',
+          orderId: '',
+        },
+        rules: {
+          feedbackSupportContent: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+          feedbackSupportTime: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+        },
+        dialogFormVisible: false,
+      }
+    },
+    mounted() {},
+    methods: {
+      open(row) {
+        console.log(row)
+        this.form.orderId = row.id
+        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 workOrderApi.feedbackSupport(this.form)
+            this.$baseMessage(msg, 'success', 'vab-hey-message-success')
+            this.$emit('update-detail')
+            this.close()
+          }
+        })
+      },
+    },
+  }
+</script>

+ 143 - 0
src/views/work/train/head/components/TryFeedback.vue

@@ -0,0 +1,143 @@
+<template>
+  <el-dialog title="试用反馈" :visible.sync="dialogFormVisible" width="600px" @close="close">
+    <el-form ref="form" label-position="top" :model="form" :rules="rules">
+      <el-row :gutter="20">
+        <el-col :span="24">
+          <el-form-item label="反馈时间" prop="feedbackTrialTime">
+            <el-date-picker
+              v-model="form.feedbackTrialTime"
+              placeholder="选择反馈时间"
+              style="width: 100%"
+              type="date"
+              value-format="yyyy-MM-dd" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="24">
+          <el-form-item :label="summarizeLabel" prop="feedbackTrialContent">
+            <el-input
+              v-model="form.feedbackTrialContent"
+              :placeholder="summarizeLabel"
+              :rows="5"
+              show-word-limit
+              type="textarea" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="24">
+          <el-form-item label="客户/经销商反馈" prop="feedbackTrialDist">
+            <el-input
+              v-model="form.feedbackTrialDist"
+              placeholder="客户/经销商反馈"
+              :rows="5"
+              show-word-limit
+              type="textarea" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="24">
+          <el-form-item :label="nextPlanLabel" prop="feedbackTrialPlan">
+            <el-input
+              v-model="form.feedbackTrialPlan"
+              :placeholder="nextPlanLabel"
+              :rows="5"
+              show-word-limit
+              type="textarea" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+    <template #footer>
+      <el-button @click="close">取 消</el-button>
+      <el-button type="primary" @click="save">确 定</el-button>
+    </template>
+  </el-dialog>
+</template>
+
+<script>
+  import workOrderApi from '@/api/work/index'
+  export default {
+    name: 'WorkOrderFeedback',
+    data() {
+      return {
+        workType: '',
+        fbType: '',
+        form: {
+          feedbackTrialTime: '',
+          feedbackTrialContent: '',
+          feedbackTrialDist: '',
+          feedbackTrialPlan: '',
+          workOrderId: '',
+        },
+        rules: {
+          feedbackTrialTime: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+          feedbackTrialContent: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+          feedbackTrialDist: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+          feedbackTrialPlan: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+        },
+        feedbackTrialTypeObj: {
+          start: '10',
+          process: '20',
+          summarize: '30',
+        },
+        dialogFormVisible: false,
+      }
+    },
+    computed: {
+      summarizeLabel() {
+        let str = ''
+        switch (this.fbType) {
+          case 'start':
+            str = this.workType == '产品试用申请(软件)' ? '部署安装总结' : '会议总结'
+            break
+          case 'process':
+            str = '过程问题'
+            break
+          case 'summarize':
+            str = '使用总结'
+            break
+        }
+        return str
+      },
+      nextPlanLabel() {
+        let str = ''
+        switch (this.fbType) {
+          case 'start':
+            str = '跟进计划'
+            break
+          case 'process':
+            str = '跟进计划'
+            break
+          case 'summarize':
+            str = '下一步计划'
+            break
+        }
+        return str
+      },
+    },
+    mounted() {},
+    methods: {
+      open(row, type) {
+        console.log(row, type)
+        this.form.workOrderId = row.id
+        this.workType = row.orderTypeName
+        this.fbType = type
+        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) {
+            let params = Object.assign(this.form)
+            params.feedbackTrialType = this.feedbackTrialTypeObj[this.fbType]
+            const { msg } = await workOrderApi.feedbackTrail(params)
+            this.$baseMessage(msg, 'success', 'vab-hey-message-success')
+            this.$emit('update-detail')
+            this.close()
+          }
+        })
+      },
+    },
+  }
+</script>

+ 122 - 0
src/views/work/train/head/components/TryFeedbackDetail.vue

@@ -0,0 +1,122 @@
+<template>
+  <el-dialog title="试用反馈" :visible.sync="dialogFormVisible" width="600px" @close="close">
+    <el-form ref="form" label-position="top" :model="form" :rules="rules">
+      <el-row :gutter="20">
+        <el-col :span="24">
+          <el-form-item label="反馈时间" prop="feedbackTrialTime">
+            <el-date-picker
+              v-model="form.feedbackTrialTime"
+              placeholder="选择反馈时间"
+              readonly
+              style="width: 100%"
+              type="date"
+              value-format="yyyy-MM-dd" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="24">
+          <el-form-item :label="summarizeLabel" prop="feedbackTrialContent">
+            <el-input
+              v-model="form.feedbackTrialContent"
+              :placeholder="summarizeLabel"
+              readonly
+              :rows="5"
+              show-word-limit
+              type="textarea" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="24">
+          <el-form-item label="客户/经销商反馈" prop="feedbackTrialDist">
+            <el-input
+              v-model="form.feedbackTrialDist"
+              placeholder="客户/经销商反馈"
+              readonly
+              :rows="5"
+              show-word-limit
+              type="textarea" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="24">
+          <el-form-item :label="nextPlanLabel" prop="feedbackTrialPlan">
+            <el-input
+              v-model="form.feedbackTrialPlan"
+              :placeholder="nextPlanLabel"
+              readonly
+              :rows="5"
+              show-word-limit
+              type="textarea" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+    <template #footer>
+      <el-button @click="close">取 消</el-button>
+    </template>
+  </el-dialog>
+</template>
+
+<script>
+  export default {
+    name: 'WorkOrderFeedback',
+    data() {
+      return {
+        workType: '',
+        fbType: '',
+        form: {},
+        rules: {
+          feedbackTrialTime: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+          feedbackTrialContent: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+          feedbackTrialDist: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+          feedbackTrialPlan: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
+        },
+        feedbackTrialTypeObj: {
+          start: '10',
+          process: '20',
+          summarize: '30',
+        },
+        dialogFormVisible: false,
+      }
+    },
+    computed: {
+      summarizeLabel() {
+        let str = ''
+        switch (this.form.feedbackTrialType) {
+          case '10':
+            str = '总结'
+            break
+          case '20':
+            str = '过程问题'
+            break
+          case '30':
+            str = '使用总结'
+            break
+        }
+        return str
+      },
+      nextPlanLabel() {
+        let str = ''
+        switch (this.form.feedbackTrialType) {
+          case '10':
+            str = '跟进计划'
+            break
+          case '20':
+            str = '跟进计划'
+            break
+          case '30':
+            str = '下一步计划'
+            break
+        }
+        return str
+      },
+    },
+    mounted() {},
+    methods: {
+      open(row) {
+        this.form = row
+        this.dialogFormVisible = true
+      },
+      close() {
+        this.dialogFormVisible = false
+      },
+    },
+  }
+</script>

+ 309 - 0
src/views/work/train/head/index.vue

@@ -0,0 +1,309 @@
+<template>
+  <div class="list-container">
+    <vab-query-form>
+      <vab-query-form-top-panel>
+        <el-form ref="queryForm" :inline="true" :model="queryForm" @submit.native.prevent>
+          <el-form-item prop="trainTitle">
+            <el-input v-model="queryForm.trainTitle" placeholder="培训主题" @keyup.enter.native="fetchData" />
+          </el-form-item>
+          <el-form-item prop="trainDate">
+            <el-date-picker
+              v-model="queryForm.trainDate"
+              end-placeholder="日期结束"
+              range-separator="至"
+              start-placeholder="日期开始"
+              type="daterange"
+              value-format="yyyy-MM-dd" />
+          </el-form-item>
+          <el-form-item>
+            <el-button icon="el-icon-search" type="primary" @click="fetchData">查询</el-button>
+            <el-button icon="el-icon-refresh-right" @click="reset">重置</el-button>
+          </el-form-item>
+        </el-form>
+      </vab-query-form-top-panel>
+      <vab-query-form-left-panel :span="12">
+        <vab-query-form-left-panel :span="12">
+          <el-button
+            v-permissions="['proj:business:workOrder']"
+            icon="el-icon-plus"
+            type="primary"
+            @click="createOrder(null)">
+            创建工单
+          </el-button>
+        </vab-query-form-left-panel>
+        <!--        <el-button icon="el-icon-plus" size="mini" type="primary" @click="handleEdit">新建</el-button>-->
+      </vab-query-form-left-panel>
+      <vab-query-form-right-panel :span="12">
+        <table-tool :columns="columns" :show-columns.sync="showColumns" table-type="workOrderTable" />
+      </vab-query-form-right-panel>
+    </vab-query-form>
+
+    <el-table ref="table" v-loading="listLoading" border :data="list" :height="$baseTableHeight(2)">
+      <el-table-column align="center" label="序号" show-overflow-tooltip width="80">
+        <template #default="{ $index }">
+          {{ $index + 1 }}
+        </template>
+      </el-table-column>
+      <el-table-column
+        v-for="(item, index) in showColumns"
+        :key="index"
+        align="center"
+        :label="item.label"
+        :prop="item.prop"
+        show-overflow-tooltip
+        :sortable="item.sortable">
+        <!-- :width="item.width" -->
+        <template #default="{ row }">
+          <el-button v-if="item.prop === 'id'" style="font-size: 14px" type="text" @click="handleDetail(row)">
+            {{ row.id }}
+          </el-button>
+          <span v-else-if="item.prop === 'createdTime'">
+            {{ parseTime(row.createdTime, '{y}-{m}-{d}') }}
+          </span>
+          <span v-else-if="item.prop === 'trainDate'">
+            {{ parseTime(row.createdTime, '{y}-{m}-{d}') }}
+          </span>
+          <span v-else-if="item.prop === 'trainConcreteTime'">
+            {{ overdue(row.trainConcreteTime) }}
+          </span>
+          <span v-else>{{ row[item.prop] }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column align="center" fixed="right" label="操作" width="200">
+        <template #default="{ row }">
+          <el-button type="text" @click="del(row.id)">删除</el-button>
+          <el-button type="text" @click="createOrder(row.id)">修改</el-button>
+          <el-button type="text" @click="toDetail(row.id,'edit')">反馈</el-button>
+          <el-button type="text" @click="toDetail(row.id,'detail')">详情</el-button>
+        </template>
+      </el-table-column>
+      <template #empty>
+        <el-image class="vab-data-empty" :src="require('@/assets/empty_images/data_empty.png')" />
+      </template>
+    </el-table>
+
+    <el-pagination
+      background
+      :current-page="queryForm.pageNum"
+      :layout="layout"
+      :page-size="queryForm.pageSize"
+      :total="total"
+      @current-change="handleCurrentChange"
+      @size-change="handleSizeChange" />
+    <!--创建工单-->
+    <edit ref="edit" @fetch-data="fetchData" />
+    <!-- 创建经销商支持工单 -->
+    <createTrainHead ref="createTrainHead" @fetch-data="fetchData" />
+    <Detail ref="DetailRef" @fetch-data="fetchData" />
+  </div>
+</template>
+
+<script>
+  import to from 'await-to-js'
+  import api from '@/api/work/trainHead'
+  import TableTool from '@/components/table/TableTool'
+  import Edit from './components/Edit'
+  import createTrainHead from './components/CreateTrainHead'
+  import Detail from './components/Detail'
+
+  export default {
+    name: 'WorkOrder',
+    components: { TableTool, Edit, createTrainHead, Detail },
+    data() {
+      return {
+        activeName: 'first',
+        layout: 'total, sizes, prev, pager, next, jumper',
+        queryForm: {
+          trainTitle: '',
+          trainDate: [],
+          trainStartDate: '',
+          trainEndDate: '',
+          trainConcreteTime: '',
+          pageNum: 1,
+          pageSize: 10,
+        },
+        total: 0,
+        listLoading: false,
+        list: [],
+        selectRows: [],
+        showColumns: [],
+        productLineOptions: [],
+        columns: [
+          {
+            label: '工单编号',
+            width: '80px',
+            prop: 'id',
+            disableCheck: true,
+          },
+          {
+            label: '培训主题',
+            width: '320px',
+            prop: 'trainTitle',
+            disableCheck: true,
+          },
+          {
+            label: '培训日期',
+            width: '200px',
+            prop: 'trainDate',
+          },
+          {
+            label: '培训具体时间',
+            width: '100px',
+            prop: 'trainConcreteTime',
+          },
+          {
+            label: '创建时间',
+            width: '100px',
+            prop: 'createdTime',
+          },
+        ],
+        orderStatusOptions: [],
+      }
+    },
+    watch: {
+      showColumns: function () {
+        this.$nextTick(() => this.$refs.table.doLayout())
+      },
+    },
+    activated() {
+      this.fetchData()
+    },
+    mounted() {
+      this.getOptions()
+      this.fetchData()
+    },
+
+    methods: {
+      // 创建工单
+      createOrder(id) {
+        this.$refs['createTrainHead'].showEdit(id)
+      },
+      toDetail(id,type) {
+        console.log("id",id);
+        this.$refs['DetailRef'].showEdit(id,type)
+      },
+      async del(id) {
+        const [err, res] = await to(api.DeleteByIds({ ids: [id] }))
+        if (err) return (this.listLoading = false)
+        this.fetchData()
+      },
+      changeProductLine() {},
+      getOptions() {
+        Promise.all([this.getDicts('work_order_status'), this.getDicts('sys_product_line')])
+          .then(([workOrderStatus, productLine]) => {
+            this.orderStatusOptions = workOrderStatus.data.values || []
+            this.productLineOptions = productLine.data.values || []
+          })
+          .catch((err) => console.log(err))
+      },
+      overdue(endTime) {
+        if (endTime) {
+          var oDate1 = new Date()
+          var oDate2 = new Date(endTime)
+          if (oDate1.getTime() > oDate2.getTime()) {
+            return '是' //第一个大
+          } else {
+            return '否' //第二个大
+          }
+        } else {
+          return '否'
+        }
+      },
+      async fetchData() {
+        this.listLoading = true
+        const params = { ...this.queryForm }
+        const [err, res] = await to(api.getList(params))
+        if (err) return (this.listLoading = false)
+        // if (res.data.list) {
+        //   res.data.list.filter((i) => {
+        //     let idata = JSON.parse(i.formData)
+        //     idata.filter((field) => {
+        //       if (field.name == '产品线') {
+        //         i.productLineName = field.value
+        //       }
+        //     })
+        //   })
+        // }
+        this.list = res.data.list || []
+        this.total = res.data.total
+        this.listLoading = false
+        this.$nextTick(() => this.$refs.table.doLayout())
+      },
+      handleEdit(row) {
+        if (row.id) {
+          this.$refs['edit'].showEdit(row)
+        } else {
+          this.$refs['edit'].showEdit()
+        }
+      },
+      // 关闭工单
+      handleClose(id) {
+        this.$confirm('确认关闭?', '提示', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning',
+        })
+          .then(async () => {
+            const [err, res] = await to(api.closeWorkOrder({ orderId: id }))
+            if (err) return
+            if (res.code == 200) {
+              this.fetchData()
+              this.$message({
+                type: 'success',
+                message: '关闭成功!',
+              })
+            }
+          })
+          .catch(() => {})
+      },
+      //详情
+      handleDetail(row) {
+        this.$router.push({
+          name: 'WorkOrderDetail',
+          query: {
+            id: row.id,
+          },
+        })
+      },
+      handleProjDetail(row) {
+        this.$router.push({
+          path: '/opportunity/detail',
+          query: {
+            id: row.nboId,
+          },
+        })
+      },
+      reset() {
+        this.queryForm = {
+          pageNum: 1,
+          pageSize: 10,
+          name: '',
+          orderTypeName: '',
+          orderStatus: '',
+          assignUserName: '',
+          supportTime: [],
+          productLine: '',
+          nboName: '',
+          custName: '',
+        }
+        this.fetchData()
+      },
+
+      handleSizeChange(val) {
+        this.queryForm.pageSize = val
+        this.fetchData()
+      },
+      handleCurrentChange(val) {
+        this.queryForm.pageNum = val
+        this.fetchData()
+      },
+      setSelectRows(val) {
+        this.selectRows = val
+      },
+    },
+  }
+</script>
+
+<style lang="scss" scoped>
+  $base: '.list';
+</style>