detail.vue 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117
  1. <!--
  2. * @Author: wanglj 471442253@qq.com
  3. * @Date: 2022-12-26 09:30:47
  4. * @LastEditors: niezch@dashoo.cn
  5. * @LastEditTime: 2023-04-11 16:44:55
  6. * @Description: file content
  7. * @FilePath: \opms_frontend\src\views\customer\detail.vue
  8. -->
  9. <template>
  10. <div class="detail">
  11. <div class="side-layout">
  12. <div class="info">
  13. <div class="title">
  14. <p>客户</p>
  15. <h3>
  16. {{ detail.custName }}
  17. <div>
  18. <span v-show="detail.salesId > 0">
  19. <el-button v-permissions="['cust:list:shift']" @click="handleShift">转移客户</el-button>
  20. <el-button v-permissions="['cust:list:open']" @click="handleToOpen">移入公海</el-button>
  21. <el-button v-permissions="['cust:list:project']" @click="handleBusiness">创建项目</el-button>
  22. </span>
  23. <span v-show="detail.salesId == 0">
  24. <el-button v-permissions="['cust:open:receive']" @click="handleReceive">领取客户</el-button>
  25. <el-button v-permissions="['cust:open:allocate']" @click="handleAllocate">分配客户</el-button>
  26. </span>
  27. </div>
  28. </h3>
  29. </div>
  30. <header>
  31. <el-descriptions :colon="false" :column="6" direction="vertical">
  32. <el-descriptions-item content-class-name="my-content" label="客户编码" label-class-name="my-label">
  33. {{ detail.custCode }}
  34. </el-descriptions-item>
  35. <el-descriptions-item content-class-name="my-content" label="助记名" label-class-name="my-label">
  36. {{ detail.abbrName }}
  37. </el-descriptions-item>
  38. <el-descriptions-item content-class-name="my-content" label="客户级别" label-class-name="my-label">
  39. {{ selectDictLabel(levelOptions, detail.custLevel) }}
  40. </el-descriptions-item>
  41. <el-descriptions-item content-class-name="my-content" label="客户类型" label-class-name="my-label">
  42. {{ selectDictLabel(industryOptions, detail.custIndustry) }}
  43. </el-descriptions-item>
  44. <!-- <el-descriptions-item content-class-name="my-content" label="客户状态" label-class-name="my-label">
  45. {{ detail.custStatus == 10 ? '正常' : '异常' }}
  46. </el-descriptions-item> -->
  47. <el-descriptions-item content-class-name="my-content" label="最后跟进时间" label-class-name="my-label">
  48. {{ parseTime(detail.followUpDate, '{y}-{m}-{d}') }}
  49. </el-descriptions-item>
  50. </el-descriptions>
  51. </header>
  52. <el-tabs v-model="activeName" @tab-click="handleClick">
  53. <el-tab-pane label="详细信息" name="detail">
  54. <el-descriptions
  55. border
  56. :column="2"
  57. :content-style="{ width: '25%', 'word-break': 'break-all' }"
  58. :label-style="{ width: '25%' }"
  59. size="medium">
  60. <el-descriptions-item label="客户级别">
  61. {{ selectDictLabel(levelOptions, detail.custLevel) }}
  62. </el-descriptions-item>
  63. <el-descriptions-item label="最后跟进时间">
  64. {{ detail.followUpDate }}
  65. </el-descriptions-item>
  66. <el-descriptions-item label="所在省">
  67. {{ detail.custProvince }}
  68. </el-descriptions-item>
  69. <el-descriptions-item label="所在市">
  70. {{ detail.custCity }}
  71. </el-descriptions-item>
  72. <el-descriptions-item label="所在区">
  73. {{ detail.custRegion }}
  74. </el-descriptions-item>
  75. <el-descriptions-item label="详细地址">
  76. {{ detail.custAddress }}
  77. </el-descriptions-item>
  78. <el-descriptions-item label="创建人">
  79. {{ detail.createdName }}
  80. </el-descriptions-item>
  81. <el-descriptions-item label="创建时间">
  82. {{ detail.createdTime }}
  83. </el-descriptions-item>
  84. <el-descriptions-item label="跟进次数">
  85. {{ abstract.followCount }}
  86. </el-descriptions-item>
  87. <el-descriptions-item label="未跟进时长">
  88. {{ abstract.notFollowDay }}
  89. </el-descriptions-item>
  90. <el-descriptions-item label="项目数量">
  91. {{ abstract.business }}
  92. </el-descriptions-item>
  93. <el-descriptions-item label="项目总额">
  94. {{ formatPrice(abstract.businessTotal) }}
  95. </el-descriptions-item>
  96. <el-descriptions-item label="成交次数">
  97. {{ abstract.dealCotal }}
  98. </el-descriptions-item>
  99. <el-descriptions-item label="成交总额">
  100. {{ formatPrice(abstract.dealTotal) }}
  101. </el-descriptions-item>
  102. <el-descriptions-item label="回款总额">
  103. {{ formatPrice(abstract.paymentTotal) }}
  104. </el-descriptions-item>
  105. <el-descriptions-item label="未回款总额">
  106. {{ formatPrice(abstract.notPaymentTotal) }}
  107. </el-descriptions-item>
  108. <el-descriptions-item label="开票总额" :span="24">
  109. {{ formatPrice(abstract.drawTotal) }}
  110. </el-descriptions-item>
  111. <el-descriptions-item label="备注" :span="24">
  112. {{ detail.remark }}
  113. </el-descriptions-item>
  114. </el-descriptions>
  115. </el-tab-pane>
  116. <el-tab-pane label="跟进记录" name="follow">
  117. <ul v-if="followList.length" class="follow">
  118. <li v-for="(date, index) in followList" :key="index">
  119. <div class="date">
  120. <h2>{{ date.followDay.split('-')[2] }}</h2>
  121. <h3>
  122. {{ date.followDay.split('-').splice(0, 2).join('.') }}
  123. </h3>
  124. </div>
  125. <ul class="content">
  126. <li v-for="(item, idx) in date.followupList" :key="idx">
  127. <!-- <el-avatar class="user-avatar"
  128. :src="avatar" />-->
  129. <div class="text-container">
  130. <vab-icon class="user-avatar" icon="account-circle-fill" />
  131. <div class="text">
  132. <p class="action">
  133. <span>{{ item.createdName }} 跟进({{ formatType(item.followType) }})</span>
  134. <span>
  135. <vab-icon icon="time-line" />
  136. {{ item.followDate }}
  137. </span>
  138. </p>
  139. <p>{{ item.followContent }}</p>
  140. <div class="footer">
  141. <p>
  142. 来自客户:
  143. <span>{{ item.custName }}</span>
  144. </p>
  145. <div>
  146. <el-button size="mini" @click="showDetail(item)">
  147. <vab-icon icon="arrow-right-circle-fill" />
  148. 详情
  149. </el-button>
  150. <el-button size="mini" @click="showComment(item)">评论({{ item.commentNumber }})</el-button>
  151. </div>
  152. </div>
  153. </div>
  154. </div>
  155. <transition name="height">
  156. <ul v-if="item.showComment" class="comments">
  157. <li v-for="comment in item.comments" :key="comment.id">
  158. <vab-icon class="user-avatar" icon="account-circle-fill" />
  159. <div class="text">
  160. <p>{{ comment.createdName }}</p>
  161. <p>{{ comment.content }}</p>
  162. <p>{{ comment.createdTime }}</p>
  163. </div>
  164. </li>
  165. </ul>
  166. </transition>
  167. </li>
  168. </ul>
  169. </li>
  170. </ul>
  171. <div v-else class="no-follow">暂无跟进记录</div>
  172. </el-tab-pane>
  173. <el-tab-pane label="联系人" name="contact">
  174. <vab-query-form>
  175. <vab-query-form-left-panel :span="12">
  176. <el-input
  177. v-model="cuctName"
  178. placeholder="请输入单据名称/编号"
  179. prefix-icon="el-icon-search"
  180. style="width: 50%"
  181. @blur="handleClick({ name: 'contact' })"
  182. @keyup.enter.native="handleClick({ name: 'contact' })" />
  183. </vab-query-form-left-panel>
  184. <vab-query-form-right-panel :span="12">
  185. <el-button v-permissions="['cust:detail:contact:add']" icon="el-icon-plus" @click="addContact">
  186. 新建联系人
  187. </el-button>
  188. </vab-query-form-right-panel>
  189. </vab-query-form>
  190. <el-table border :data="contactList" height="calc(100% - 42px)" @selection-change="setSelectRows">
  191. <el-table-column align="center" type="selection" />
  192. <el-table-column align="center" label="姓名" prop="cuctName" />
  193. <el-table-column align="center" label="岗位" prop="postion" />
  194. <el-table-column align="center" label="电话" prop="telephone" />
  195. <el-table-column align="center" label="微信" prop="wechat" />
  196. <el-table-column align="center" label="邮箱" prop="email" />
  197. <el-table-column align="center" label="是否决策人">
  198. <template slot-scope="scope">
  199. <el-switch v-model="scope.row.isDecision" active-value="10" disabled inactive-value="20" />
  200. </template>
  201. </el-table-column>
  202. <el-table-column align="center" label="操作">
  203. <template slot-scope="scope">
  204. <el-button v-permissions="['cust:deatil:contact:edit']" type="text" @click="contactEdit(scope.row)">
  205. 编辑
  206. </el-button>
  207. <el-button v-permissions="['cust:detail:contact:delete']" type="text" @click="contactDel(scope.row)">
  208. 删除
  209. </el-button>
  210. </template>
  211. </el-table-column>
  212. </el-table>
  213. </el-tab-pane>
  214. <el-tab-pane label="项目记录" name="item">
  215. <el-table border :data="items" height="calc(100% - 42px)">
  216. <el-table-column align="center" label="项目编号" prop="nboCode" width="120px" />
  217. <el-table-column align="center" label="项目名称" prop="nboName" show-overflow-tooltip width="200px" />
  218. <el-table-column align="center" label="审批状态" prop="approStatus" width="120px">
  219. <template slot-scope="scope">
  220. {{ getStatus(scope.row.approStatus) }}
  221. </template>
  222. </el-table-column>
  223. <!-- <el-table-column align="center" label="项目类别" prop="nboType" width="80px" />-->
  224. <!-- <el-table-column align="center" label="产品线" prop="productLine" width="120px" />-->
  225. <el-table-column
  226. align="center"
  227. label="经销商/代理商"
  228. prop="distributorName"
  229. show-overflow-tooltip
  230. width="200px" />
  231. <el-table-column align="center" label="项目预算" prop="nboBudget" width="120px" />
  232. <el-table-column align="center" label="项目备案时间" prop="filingTime" width="160px" />
  233. <!-- <el-table-column align="center" label="项目来源" prop="nboSource" width="160px" />-->
  234. <!-- <el-table-column align="center" label="销售模式" prop="salesModel" width="160px" />-->
  235. <el-table-column align="center" label="最后跟进时间" prop="finalFollowTime" width="160px" />
  236. <el-table-column align="center" label="下次跟进时间" prop="nextFollowTime " width="160px" />
  237. </el-table>
  238. </el-tab-pane>
  239. <el-tab-pane label="合同记录" name="contract">
  240. <el-table border :data="contracts" height="calc(100% - 42px)">
  241. <el-table-column align="center" label="合同编号" prop="contractCode" width="140px" />
  242. <el-table-column
  243. align="center"
  244. label="合同名称"
  245. prop="contractName"
  246. show-overflow-tooltip
  247. width="240px" />
  248. <el-table-column align="center" label="项目名称" prop="nboName" show-overflow-tooltip width="240px" />
  249. <el-table-column align="center" label="审批状态" prop="approStatus" width="120px">
  250. <template slot-scope="scope">
  251. {{ getStatus(scope.row.approStatus) }}
  252. </template>
  253. </el-table-column>
  254. <el-table-column align="center" label="合同类型" prop="contractType" width="120px">
  255. <template slot-scope="scope">
  256. {{ selectDictLabel(contractTypeOptions, scope.row.contractType) }}
  257. </template>
  258. </el-table-column>
  259. <el-table-column align="center" label="合同金额" prop="contractAmount" width="120px" />
  260. <el-table-column align="center" label="负责人" prop="inchargeName" width="120px" />
  261. <el-table-column align="center" label="公司签约人" prop="signatoryName" width="140px" />
  262. <el-table-column align="center" label="经销商" prop="distributorName" width="320px" />
  263. </el-table>
  264. </el-tab-pane>
  265. <el-tab-pane label="工单记录" name="worksheet">工单记录</el-tab-pane>
  266. <el-tab-pane label="归属记录" name="belong">
  267. <el-table border :data="belongs" height="calc(100% - 42px)">
  268. <el-table-column align="center" label="归属销售" prop="saleName" />
  269. <el-table-column align="center" label="原来归属" prop="origSaleName" />
  270. <el-table-column align="center" label="操作方式" prop="opnType">
  271. <template slot-scope="scope">
  272. <el-tag v-if="scope.row.opnType == 10">分配</el-tag>
  273. <el-tag v-else-if="scope.row.opnType == 20">转移</el-tag>
  274. <el-tag v-else-if="scope.row.opnType == 30">领取</el-tag>
  275. <el-tag v-else-if="scope.row.opnType == 40">合并</el-tag>
  276. </template>
  277. </el-table-column>
  278. <el-table-column align="center" label="操作人" prop="createdName" />
  279. <el-table-column align="center" label="操作时间" min-width="160px" prop="opnDatetime" />
  280. <el-table-column align="center" label="开始时间" min-width="160px" prop="startDate" />
  281. <el-table-column align="center" label="结束时间" min-width="160px" prop="endDate" />
  282. <el-table-column align="center" label="备注" prop="remark" />
  283. </el-table>
  284. </el-tab-pane>
  285. <el-tab-pane label="招标记录" name="bid">
  286. <vab-query-form>
  287. <vab-query-form-left-panel :span="12">
  288. <el-input
  289. v-model="bidSearchText"
  290. placeholder="招标产品名称/招标信息标题/中标单位"
  291. prefix-icon="el-icon-search"
  292. style="width: 50%"
  293. @blur="handleClick({ name: 'bid' })"
  294. @keyup.enter.native="handleClick({ name: 'bid' })" />
  295. </vab-query-form-left-panel>
  296. <vab-query-form-right-panel :span="12">
  297. <el-button icon="el-icon-plus" @click="addBid">新建招标记录</el-button>
  298. </vab-query-form-right-panel>
  299. </vab-query-form>
  300. <el-table border :data="bidlist" height="calc(100% - 42px)">
  301. <el-table-column align="center" label="客户名称" prop="cuctName" width="120px" />
  302. <el-table-column align="center" label="招标产品名称" prop="productName" width="120px" />
  303. <el-table-column align="center" label="发布招标日期" prop="publishedTime" width="160px" />
  304. <el-table-column align="center" label="项目预算" prop="budget" width="120px" />
  305. <el-table-column align="center" label="招标信息标题" prop="title" width="120px" />
  306. <el-table-column align="center" label="信息分类" prop="infoType" width="120px">
  307. <template #default="{ row }">
  308. {{ bidInfoTypeOptions[row.infoType] }}
  309. </template>
  310. </el-table-column>
  311. <el-table-column align="center" label="中标单位" prop="bidder" width="120px" />
  312. <el-table-column align="center" label="创建招标日期" prop="createdOn" width="160px" />
  313. <el-table-column align="center" label="备注" prop="remark" width="120px" />
  314. <el-table-column align="center" fixed="right" label="操作" width="90px">
  315. <template slot-scope="scope">
  316. <el-button type="text" @click="bidEdit(scope.row)">编辑</el-button>
  317. <el-button type="text" @click="bidDel(scope.row)">删除</el-button>
  318. </template>
  319. </el-table-column>
  320. </el-table>
  321. </el-tab-pane>
  322. <el-tab-pane label="开票抬头" name="invoiceHeader">
  323. <vab-query-form>
  324. <vab-query-form-left-panel :span="12">
  325. <el-input
  326. v-model="invoiceHeaderSearchText"
  327. placeholder="客户名称/公司名称/纳税人识别号"
  328. prefix-icon="el-icon-search"
  329. style="width: 50%"
  330. @blur="handleClick({ name: 'invoiceHeader' })"
  331. @keyup.enter.native="handleClick({ name: 'invoiceHeader' })" />
  332. </vab-query-form-left-panel>
  333. <vab-query-form-right-panel :span="12">
  334. <el-button icon="el-icon-plus" @click="addInvoiceHeader">新建开票抬头</el-button>
  335. </vab-query-form-right-panel>
  336. </vab-query-form>
  337. <el-table border :data="invoiceHeaderList" height="calc(100% - 42px)">
  338. <el-table-column align="center" label="客户名称" prop="cuctName" width="220px" />
  339. <el-table-column align="center" label="公司名称" prop="companyName" width="220px" />
  340. <el-table-column align="center" label="纳税人识别号" prop="taxNo" width="120px" />
  341. <el-table-column align="center" label="地址" prop="address" width="160px" />
  342. <el-table-column align="center" label="开户行及账号" prop="bankNo" width="120px" />
  343. <el-table-column align="center" label="备注" prop="remark" width="120px" />
  344. <el-table-column align="center" fixed="right" label="操作" width="90px">
  345. <template slot-scope="scope">
  346. <el-button type="text" @click="invoiceHeaderEdit(scope.row)">编辑</el-button>
  347. <el-button type="text" @click="invoiceHeaderDel(scope.row)">删除</el-button>
  348. </template>
  349. </el-table-column>
  350. </el-table>
  351. </el-tab-pane>
  352. </el-tabs>
  353. </div>
  354. <div class="info-side">
  355. <div class="buttons">
  356. <el-button v-if="checkPermi('edit')" type="primary" @click="handleEdit">编辑</el-button>
  357. <el-button v-if="checkPermi('delete')" @click="handleDelete">删除</el-button>
  358. <el-button @click="$router.go(-1)">返回</el-button>
  359. </div>
  360. <ul class="records">
  361. <li v-for="(value, key) in records" :key="key">
  362. <div class="date">
  363. <h2>{{ key.split('-')[2] }}</h2>
  364. <h3>{{ key.split('-').splice(0, 2).join('.') }}</h3>
  365. </div>
  366. <ul class="content">
  367. <li v-for="(item, index) in records[key]" :key="index">
  368. <!-- <el-avatar class="user-avatar"
  369. :src="avatar" /> -->
  370. <vab-icon class="user-avatar" icon="account-circle-fill" />
  371. <div class="text">
  372. <p class="action">{{ item.opnPeople }} {{ item.opnType }}</p>
  373. <p>{{ item.opnDate }}</p>
  374. <p v-if="item.opnContent.custName">
  375. 客户名称:
  376. <span>{{ item.opnContent.custName }}</span>
  377. </p>
  378. <template v-else-if="item.opnContent.cuctName">
  379. <p>
  380. 联系人名称:
  381. <span>{{ item.opnContent.cuctName }}</span>
  382. </p>
  383. <p>职务:{{ item.opnContent.postion }}</p>
  384. <p>手机:{{ item.opnContent.telephone }}</p>
  385. </template>
  386. </div>
  387. </li>
  388. </ul>
  389. </li>
  390. </ul>
  391. </div>
  392. </div>
  393. <Contact ref="contact" @contactSave="contactSave" />
  394. <Edit ref="edit" @customerSave="customerSave" />
  395. <!-- 分配客户 -->
  396. <Allocate ref="allocate" @refresh="back" />
  397. <!-- 转移客户 -->
  398. <Shift ref="shift" @refresh="back" />
  399. <!-- 移入公海 -->
  400. <ToOpen ref="toOpen" @refresh="backToOpen" />
  401. <!-- 跟进详情 -->
  402. <FollowDetail ref="followDetail" />
  403. <!-- 添加项目 -->
  404. <Businessedit ref="businessedit" :cust-info="custInfo" @fetch-data="getBusiness" />
  405. <!-- 领取客户 -->
  406. <Pick ref="pick" />
  407. <Bid ref="bid" @bidSave="bidSave" />
  408. <InvoiceHeader ref="invoiceHeader" @save="invoiceHeaderSave" />
  409. </div>
  410. </template>
  411. <script>
  412. import { hasPermission } from '@/utils/permission'
  413. import { mapGetters } from 'vuex'
  414. import api from '@/api/customer'
  415. import follow from '@/api/customer/follow'
  416. import to from 'await-to-js'
  417. import businessApi from '@/api/proj/business'
  418. import contractApi from '@/api/contract'
  419. import bidApi from '@/api/customer/bid'
  420. import invoiceHeaderApi from '@/api/customer/invoiceHeader'
  421. import Contact from './components/Contact'
  422. import Edit from './components/Edit'
  423. import Allocate from './components/Allocate'
  424. import Shift from './components/Shift'
  425. import ToOpen from './components/ToOpen'
  426. import FollowDetail from './components/FollowDetail'
  427. import Businessedit from '../proj/business/components/BusinessEdit'
  428. import Pick from './components/Pick'
  429. import Bid from './components/Bid'
  430. import InvoiceHeader from './components/InvoiceHeader'
  431. export default {
  432. name: 'CustomerDetail',
  433. components: {
  434. Edit,
  435. Contact,
  436. Allocate,
  437. Shift,
  438. Pick,
  439. Bid,
  440. InvoiceHeader,
  441. ToOpen,
  442. FollowDetail,
  443. Businessedit,
  444. },
  445. data() {
  446. return {
  447. id: '',
  448. detail: {
  449. custCode: '', //客户编码
  450. abbrName: '', //助记名
  451. level: '', //客户级别
  452. indusTry: '', //客户类型
  453. custStatus: '', //客户状态
  454. followUpDate: '', //最后跟进时间
  455. },
  456. abstract: {
  457. followContent: '', //跟进次数
  458. notFollowDay: '', //未跟进天数
  459. business: '', //项目数量
  460. businessTotal: '', //项目总额
  461. dealCotal: '', //成交次数
  462. dealTotal: '', //成交总额
  463. paymentTotal: '', //回款总额
  464. notPaymentTotal: '', //未回款总额
  465. drawTotal: '', //开票总额
  466. },
  467. activeName: 'detail',
  468. cuctName: '',
  469. bidSearchText: '',
  470. invoiceHeaderSearchText: '',
  471. contactList: [],
  472. selectRows: [],
  473. records: [], //操作记录
  474. followList: [], //跟进记录
  475. belongs: [],
  476. bidlist: [],
  477. invoiceHeaderList: [],
  478. items: [], //项目
  479. contracts: [], //合同
  480. appro: [],
  481. bidInfoTypeOptions: {},
  482. custInfo: {},
  483. levelOptions: [],
  484. industryOptions: [],
  485. contractTypeOptions: [],
  486. }
  487. },
  488. computed: {
  489. ...mapGetters({
  490. avatar: 'user/avatar',
  491. username: 'user/username',
  492. }),
  493. },
  494. mounted() {
  495. this.id = this.$route.query.id
  496. this.init()
  497. this.getOptions()
  498. this.getDynamics()
  499. this.handleClick({ name: 'follow' })
  500. },
  501. methods: {
  502. getOptions() {
  503. Promise.all([this.getDicts('cust_level'), this.getDicts('cust_idy'), this.getDicts('contract_type')])
  504. .then(([level, industry, contractType]) => {
  505. this.levelOptions = level.data.values || []
  506. this.industryOptions = industry.data.values || []
  507. this.contractTypeOptions = contractType.data.values || []
  508. })
  509. .catch((err) => console.log(err))
  510. },
  511. checkPermi(val) {
  512. let str = 'cust:'
  513. if (this.detail.salesId) str += 'list:'
  514. else str += 'open:'
  515. str += val
  516. return hasPermission([str])
  517. },
  518. async init() {
  519. Promise.all([
  520. api.getDetail({ ids: [parseInt(this.id)] }),
  521. api.getAbstract({ id: parseInt(this.id) }),
  522. this.getDicts('proj_appro_status'),
  523. ]).then(([detail, abstract, appro]) => {
  524. if (detail.data.list[0]) this.detail = detail.data.list[0]
  525. if (abstract.data.list) this.abstract = abstract.data.list
  526. this.appro = appro.data.values || []
  527. })
  528. this.getDicts('bid_info_type').then((response) => {
  529. this.bidInfoTypeOptions = {}
  530. response.data.values.filter((i) => {
  531. this.bidInfoTypeOptions[i.key] = i.value
  532. })
  533. })
  534. },
  535. getStatus(val) {
  536. const obj = this.appro.find((item) => item.key == val)
  537. if (obj) return obj.value
  538. },
  539. async getDynamics() {
  540. const [err, res] = await to(api.dynamicsList({ custId: parseInt(this.id) }))
  541. if (err) return
  542. if (res.data.list && res.data.list[0]) {
  543. let obj = res.data.list[0]
  544. const keys = Object.keys(obj).reverse()
  545. let records = {}
  546. for (const item of keys) {
  547. records[item] = obj[item]
  548. }
  549. this.records = records
  550. }
  551. },
  552. setSelectRows(val) {
  553. this.selectRows = val
  554. },
  555. async handleClick(tab) {
  556. let err, res
  557. if (tab.name == 'contact') {
  558. ;[err, res] = await to(api.getContact({ custId: parseInt(this.id), cuctName: this.cuctName }))
  559. if (err) return
  560. this.contactList = res.data.list || []
  561. } else if (tab.name == 'follow') {
  562. let params = {
  563. custId: String(this.id),
  564. DaysBeforeToday: 99999,
  565. }
  566. ;[err, res] = await to(follow.getListByDay(params))
  567. if (err) return
  568. this.followList = res.data.list || []
  569. } else if (tab.name == 'belong') {
  570. ;[err, res] = await to(api.getBelongs({ custId: parseInt(this.id) }))
  571. if (err) return
  572. this.belongs = res.data.list || []
  573. } else if (tab.name == 'item') {
  574. ;[err, res] = await to(businessApi.getList({ custName: this.detail.custName }))
  575. if (err) return
  576. this.items = res.data.list || []
  577. } else if (tab.name == 'contract') {
  578. ;[err, res] = await to(contractApi.getList({ custId: parseInt(this.id) }))
  579. this.contracts = res.data.list || []
  580. } else if (tab.name == 'bid') {
  581. ;[err, res] = await to(api.bidList({ custId: parseInt(this.id), searchText: this.bidSearchText }))
  582. this.bidlist = res.data.list || []
  583. } else if (tab.name == 'invoiceHeader') {
  584. ;[err, res] = await to(
  585. invoiceHeaderApi.list({ custId: parseInt(this.id), searchText: this.invoiceHeaderSearchText })
  586. )
  587. this.invoiceHeaderList = res.data.list || []
  588. }
  589. },
  590. // 添加联系人
  591. addContact() {
  592. this.$refs.contact.contactForm.custId = this.detail.id
  593. this.$refs.contact.contactForm.custName = this.detail.custName
  594. this.$refs.contact.contactVisible = true
  595. },
  596. addBid() {
  597. this.$refs.bid.form.custId = this.detail.id
  598. this.$refs.bid.init()
  599. },
  600. addInvoiceHeader() {
  601. this.$refs.invoiceHeader.form.custId = this.detail.id
  602. this.$refs.invoiceHeader.init()
  603. },
  604. bidEdit(row) {
  605. this.$refs.bid.init(row.id)
  606. },
  607. invoiceHeaderEdit(row) {
  608. this.$refs.invoiceHeader.init(row.id)
  609. },
  610. invoiceHeaderDel(row) {
  611. this.$confirm('确认删除?', '提示', {
  612. confirmButtonText: '确定',
  613. cancelButtonText: '取消',
  614. type: 'warning',
  615. })
  616. .then(async () => {
  617. const [err, res] = await to(invoiceHeaderApi.delete({ id: [row.id] }))
  618. if (err) return
  619. if (res.code == 200) {
  620. this.$message({
  621. type: 'success',
  622. message: '删除成功!',
  623. })
  624. this.handleClick({ name: 'invoiceHeader' })
  625. }
  626. })
  627. .catch((err) => console.log(err))
  628. },
  629. // 删除联系人
  630. bidDel(row) {
  631. this.$confirm('确认删除?', '提示', {
  632. confirmButtonText: '确定',
  633. cancelButtonText: '取消',
  634. type: 'warning',
  635. })
  636. .then(async () => {
  637. const [err, res] = await to(bidApi.delete({ id: [row.id] }))
  638. if (err) return
  639. if (res.code == 200) {
  640. this.$message({
  641. type: 'success',
  642. message: '删除成功!',
  643. })
  644. this.handleClick({ name: 'bid' })
  645. }
  646. })
  647. .catch(() => {})
  648. },
  649. // 保存联系人
  650. contactSave() {
  651. this.handleClick({ name: 'contact' })
  652. this.getDynamics()
  653. },
  654. bidSave() {
  655. this.handleClick({ name: 'bid' })
  656. },
  657. invoiceHeaderSave() {
  658. this.handleClick({ name: 'invoiceHeader' })
  659. },
  660. // 编辑客户
  661. handleEdit() {
  662. this.$refs.edit.title = '编辑客户'
  663. this.$refs.edit.editForm = { ...this.detail }
  664. this.$refs.edit.editVisible = true
  665. this.$refs.edit.areaEditDisable = true
  666. this.$refs.edit.showLocation()
  667. },
  668. // 编辑联系人
  669. contactEdit(row) {
  670. this.$refs.contact.contactForm = { ...row }
  671. this.$refs.contact.contactForm.custName = this.detail.custName
  672. this.$refs.contact.contactVisible = true
  673. },
  674. // 删除联系人
  675. contactDel(row) {
  676. this.$confirm('确认删除?', '提示', {
  677. confirmButtonText: '确定',
  678. cancelButtonText: '取消',
  679. type: 'warning',
  680. })
  681. .then(async () => {
  682. const [err, res] = await to(api.deleteContact({ Ids: [row.id], custId: parseInt(this.id) }))
  683. if (err) return
  684. if (res.code == 200) {
  685. this.$message({
  686. type: 'success',
  687. message: '删除成功!',
  688. })
  689. this.contactSave()
  690. }
  691. })
  692. .catch(() => {})
  693. },
  694. // 转移客户
  695. handleShift() {
  696. this.$refs.shift.form.Ids = [parseInt(this.id)]
  697. this.$refs.shift.visible = true
  698. },
  699. // 移入公海
  700. handleToOpen() {
  701. this.$refs.toOpen.form.ids = [parseInt(this.id)]
  702. this.$refs.toOpen.visible = true
  703. },
  704. // 客户删除
  705. handleDelete() {
  706. this.$confirm('确认删除?', '提示', {
  707. confirmButtonText: '确定',
  708. cancelButtonText: '取消',
  709. type: 'warning',
  710. })
  711. .then(async () => {
  712. const [err, res] = await to(api.deleteCustomer({ Id: parseInt(this.id) }))
  713. if (err) return
  714. if (res.code == 200) {
  715. this.$message({
  716. type: 'success',
  717. message: '删除成功!',
  718. })
  719. this.$router.go(-1)
  720. }
  721. })
  722. .catch(() => {})
  723. },
  724. backToOpen() {
  725. this.$router.push('/customer/openSea')
  726. },
  727. back() {
  728. this.$router.go(-1)
  729. },
  730. // 领取
  731. handleReceive() {
  732. this.$refs.pick.ids = [parseInt(this.id)]
  733. this.$refs.pick.visible = true
  734. // this.$confirm('确认领取客户?', '提示', {
  735. // confirmButtonText: '确定',
  736. // cancelButtonText: '取消',
  737. // type: 'warning',
  738. // })
  739. // .then(async () => {
  740. // const [err, res] = await to(
  741. // api.receiveCustomer({
  742. // ids: [parseInt(this.id)],
  743. // salesId: this.$store.state.user.id,
  744. // salesName: this.$store.state.user.nickName,
  745. // receive: '1',
  746. // })
  747. // )
  748. // if (err) return
  749. // if (res.code == 200) {
  750. // this.$message({
  751. // type: 'success',
  752. // message: '领取成功!',
  753. // })
  754. // this.$router.push('/customer/list')
  755. // }
  756. // })
  757. // .catch(() => {})
  758. },
  759. handleAllocate() {
  760. this.$refs.allocate.ids = [parseInt(this.id)]
  761. this.$refs.allocate.visible = true
  762. },
  763. customerSave() {
  764. this.init()
  765. this.getDynamics()
  766. },
  767. formatType(val) {
  768. let str = ''
  769. if (val == 10) str = '电话'
  770. else if (val == 20) str = '邮件'
  771. else if (val == 30) str = '拜访'
  772. return str
  773. },
  774. // 跟进记录详情
  775. showDetail(row) {
  776. this.$refs.followDetail.init({ ...row })
  777. },
  778. // 展开评论
  779. showComment(row) {
  780. if (!row.comments.length) return this.$message.warning('暂无评论')
  781. row.showComment = !row.showComment
  782. this.$forceUpdate()
  783. },
  784. // 创建项目
  785. handleBusiness() {
  786. this.custInfo = {
  787. custId: parseInt(this.id),
  788. custName: this.detail.custName,
  789. }
  790. this.$refs.businessedit.dialogFormVisible = true
  791. },
  792. getBusiness() {
  793. if (this.activeName == 'item') {
  794. this.handleClick({ name: 'item' })
  795. }
  796. },
  797. },
  798. }
  799. </script>
  800. <style lang="scss" scoped>
  801. $base: '.detail';
  802. #{$base} {
  803. height: calc(100vh - 60px - 12px * 2 - 40px);
  804. display: flex;
  805. padding: 20px 40px;
  806. > .el-row {
  807. flex: 1;
  808. width: 100%;
  809. > .el-col {
  810. height: 100%;
  811. }
  812. }
  813. .title {
  814. p,
  815. h3 {
  816. margin: 0;
  817. }
  818. p {
  819. font-size: 14px;
  820. font-weight: 400;
  821. line-height: 22px;
  822. }
  823. h3 {
  824. font-size: 24px;
  825. font-weight: 500;
  826. line-height: 36px;
  827. color: #333;
  828. display: flex;
  829. justify-content: space-between;
  830. }
  831. }
  832. header {
  833. height: 74px;
  834. background: rgba(196, 196, 196, 0.5);
  835. border-radius: 4px;
  836. display: flex;
  837. align-items: center;
  838. padding: 0 20px;
  839. margin-top: 16px;
  840. ::v-deep .el-descriptions__body {
  841. background: transparent;
  842. }
  843. ::v-deep .my-label {
  844. font-size: 14px;
  845. font-weight: 600;
  846. color: #1d66dc;
  847. }
  848. ::v-deep .my-content {
  849. font-size: 14px;
  850. font-weight: 600;
  851. color: #333;
  852. white-space: nowrap;
  853. overflow: hidden;
  854. text-overflow: ellipsis;
  855. }
  856. }
  857. .el-tabs {
  858. height: calc(100% - 148px);
  859. display: flex;
  860. flex-direction: column;
  861. ::v-deep .el-tabs__content {
  862. flex: 1;
  863. .el-tab-pane {
  864. height: 100%;
  865. .el-descriptions {
  866. table-layout: fixed;
  867. .is-bordered {
  868. table-layout: fixed;
  869. }
  870. }
  871. }
  872. }
  873. }
  874. .buttons {
  875. padding-top: 28px;
  876. text-align: right;
  877. }
  878. .records {
  879. margin: 0;
  880. padding: 10px 20px;
  881. list-style: none;
  882. height: calc(100% - 60px);
  883. overflow-y: auto;
  884. > li {
  885. display: flex;
  886. & + li {
  887. margin-top: 10px;
  888. }
  889. }
  890. .date {
  891. width: 100px;
  892. display: flex;
  893. flex-direction: column;
  894. align-items: center;
  895. h2,
  896. h3 {
  897. margin: 0;
  898. }
  899. h2 {
  900. font-size: 26px;
  901. line-height: 32px;
  902. }
  903. }
  904. .content {
  905. flex: 1;
  906. list-style: none;
  907. li {
  908. display: flex;
  909. & + li {
  910. margin-top: 10px;
  911. }
  912. }
  913. .user-avatar {
  914. font-size: 40px;
  915. }
  916. .text {
  917. flex: 1;
  918. padding-left: 20px;
  919. p {
  920. font-weight: 500;
  921. margin: 0;
  922. line-height: 20px;
  923. span {
  924. color: #1d66dc;
  925. }
  926. }
  927. p:nth-child(2) {
  928. margin-bottom: 10px;
  929. }
  930. .action {
  931. font-weight: bold;
  932. color: #333;
  933. }
  934. }
  935. }
  936. }
  937. .follow {
  938. height: 100%;
  939. padding: 10px 20px;
  940. overflow: auto;
  941. > li {
  942. display: flex;
  943. + li {
  944. margin-top: 10px;
  945. }
  946. }
  947. .date {
  948. width: 100px;
  949. display: flex;
  950. flex-direction: column;
  951. align-items: center;
  952. h2,
  953. h3 {
  954. margin: 0;
  955. }
  956. h2 {
  957. font-size: 26px;
  958. line-height: 32px;
  959. }
  960. }
  961. .content {
  962. flex: 1;
  963. list-style: none;
  964. > li {
  965. border: 1px solid rgb(215, 232, 244);
  966. background: rgb(247, 251, 254);
  967. border-radius: 4px;
  968. padding: 8px;
  969. overflow: hidden;
  970. .text-container {
  971. display: flex;
  972. }
  973. .comments {
  974. padding-left: 60px;
  975. margin-top: 10px;
  976. max-height: 190px;
  977. overflow: auto;
  978. li {
  979. display: flex;
  980. border-top: 1px solid #e3e5e7;
  981. .text {
  982. flex: 1;
  983. padding: 0 10px;
  984. p {
  985. font-weight: 500;
  986. margin: 0;
  987. line-height: 32px;
  988. }
  989. p:first-child {
  990. line-height: 30px;
  991. font-weight: bold;
  992. }
  993. p:last-child {
  994. font-size: 12px;
  995. color: #9499a0;
  996. text-align: right;
  997. }
  998. }
  999. }
  1000. }
  1001. + li {
  1002. margin-top: 10px;
  1003. }
  1004. }
  1005. .user-avatar {
  1006. font-size: 40px;
  1007. }
  1008. .text {
  1009. flex: 1;
  1010. padding-left: 20px;
  1011. padding-right: 10px;
  1012. p {
  1013. font-weight: 500;
  1014. margin: 0;
  1015. line-height: 32px;
  1016. span {
  1017. color: #1d66dc;
  1018. }
  1019. }
  1020. .action {
  1021. display: flex;
  1022. justify-content: space-between;
  1023. span:first-child {
  1024. font-weight: bold;
  1025. color: #333;
  1026. }
  1027. }
  1028. .footer {
  1029. display: flex;
  1030. justify-content: space-between;
  1031. align-items: center;
  1032. }
  1033. }
  1034. }
  1035. }
  1036. .no-follow {
  1037. height: 100%;
  1038. width: 100%;
  1039. display: flex;
  1040. align-items: center;
  1041. justify-content: center;
  1042. font-size: 12px;
  1043. color: rgba(0, 0, 0, 0.65);
  1044. }
  1045. }
  1046. .height-enter-active,
  1047. .height-leave-active {
  1048. transition: all 0.5s;
  1049. }
  1050. .height-enter-to,
  1051. .height-leave {
  1052. height: 190px;
  1053. }
  1054. .height-enter, .height-leave-to /* .fade-leave-active below version 2.1.8 */ {
  1055. height: 0;
  1056. }
  1057. </style>