follow.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569
  1. <!--
  2. * @Author: liuzhenlin 461480418@qq.ocm
  3. * @Date: 2023-01-12 11:57:48
  4. * @LastEditors: liuzhenlin
  5. * @LastEditTime: 2023-07-03 10:43:41
  6. * @Description: file content
  7. * @FilePath: \oms\pages\publicPages\follow.vue
  8. -->
  9. <template>
  10. <view class="home">
  11. <view class="nav">
  12. <view :style="{ paddingTop }">
  13. <view class="title" :style="[{ height }, { lineHeight: height }]">
  14. <view class="back" @click="goBack()">
  15. <u-icon name="arrow-left" color="#ffffff" size="22"></u-icon>
  16. </view>
  17. <text>填写跟进</text>
  18. </view>
  19. </view>
  20. </view>
  21. <view class="main">
  22. <u-form :model="addForm" :rules="rules" ref="addForm" label-width="0">
  23. <u-form-item prop="content" customStyle="padding:40rpx 0 34rpx">
  24. <view class="form-label flex_l">
  25. <view class="label-tag"></view>
  26. 本次跟进内容
  27. </view>
  28. <u-textarea
  29. v-model="addForm.content"
  30. placeholder="请输入本次跟进内容"
  31. maxlength="500"
  32. height="150rpx"
  33. count
  34. customStyle="border-radius:16rpx"></u-textarea>
  35. </u-form-item>
  36. <u-form-item prop="effect" customStyle="padding:40rpx 0 34rpx">
  37. <view class="form-label flex_l">
  38. <view class="label-tag"></view>
  39. 达成效果
  40. </view>
  41. <u-textarea
  42. v-model="addForm.effect"
  43. placeholder="请输入达成效果"
  44. maxlength="500"
  45. height="150rpx"
  46. count
  47. customStyle="border-radius:16rpx"></u-textarea>
  48. </u-form-item>
  49. <u-form-item prop="issue" customStyle="padding:40rpx 0 34rpx">
  50. <view class="form-label flex_l">
  51. <view class="label-tag"></view>
  52. 问题或困难
  53. </view>
  54. <u-textarea
  55. v-model="addForm.issue"
  56. placeholder="请输入问题或困难"
  57. maxlength="500"
  58. height="150rpx"
  59. count
  60. customStyle="border-radius:16rpx"></u-textarea>
  61. </u-form-item>
  62. <u-form-item prop="furtherPlan" customStyle="padding:40rpx 0 34rpx">
  63. <view class="form-label flex_l">
  64. <view class="label-tag"></view>
  65. 下一步跟进计划和目标
  66. </view>
  67. <u-textarea
  68. v-model="addForm.furtherPlan"
  69. placeholder="请输入下一步跟进计划和目标"
  70. maxlength="500"
  71. height="150rpx"
  72. count
  73. customStyle="border-radius:16rpx"></u-textarea>
  74. </u-form-item>
  75. <u-form-item prop="date" borderBottom customStyle="padding:40rpx 0 30rpx" @click="showDate = true">
  76. <view class="form-label flex_l">
  77. <view class="label-tag"></view>
  78. 跟进时间
  79. </view>
  80. <u-input
  81. :readonly="true"
  82. placeholder="请选择跟进时间"
  83. v-model="addForm.date"
  84. border="none"
  85. suffixIcon="arrow-down"
  86. suffixIconStyle="color:#CDCDCD"
  87. clearable
  88. customStyle="padding: 0 30rpx 0 12rpx"></u-input>
  89. </u-form-item>
  90. <u-form-item prop="mode" borderBottom customStyle="padding:40rpx 0 30rpx" @click="showMode = true">
  91. <view class="form-label flex_l">
  92. <view class="label-tag"></view>
  93. 跟进方式
  94. </view>
  95. <u-input
  96. :readonly="true"
  97. placeholder="请选择跟进方式"
  98. v-model="addForm.mode"
  99. border="none"
  100. suffixIcon="arrow-down"
  101. suffixIconStyle="color:#CDCDCD"
  102. clearable
  103. customStyle="padding: 0 30rpx 0 12rpx"></u-input>
  104. </u-form-item>
  105. <u-form-item prop="contactsName" borderBottom customStyle="padding:40rpx 0 30rpx" @click="open">
  106. <view class="form-label flex_l">
  107. 联系人
  108. </view>
  109. <u-input
  110. :readonly="true"
  111. placeholder="请选择联系人"
  112. v-model="addForm.contactsName"
  113. border="none"
  114. suffixIcon="arrow-down"
  115. suffixIconStyle="color:#CDCDCD"
  116. clearable
  117. customStyle="padding: 0 30rpx 0 12rpx"></u-input>
  118. </u-form-item>
  119. </u-form>
  120. <view class="upload-file-box">
  121. <view class="form-label flex_l">附件</view>
  122. <uni-file-picker file-mediatype="all" v-model="imageValue" mode="grid" @select="select" ref="upload" limit="1">
  123. <view class="upload-btn">
  124. <u-icon name="plus-circle-fill" color="blue" size="28"></u-icon>
  125. </view>
  126. </uni-file-picker>
  127. </view>
  128. <view class="save" @click="handleAdd" :class="!flag ? 'disabledBtn' : ''">保存</view>
  129. <!-- 选择跟进方式 -->
  130. <u-picker
  131. :show="showMode"
  132. :columns="modeColumns"
  133. keyName="label"
  134. @cancel="showMode = false"
  135. @confirm="pickMode"></u-picker>
  136. <!-- 选择下次时间 -->
  137. <u-datetime-picker
  138. v-if="showDate"
  139. :maxDate="maxDate"
  140. :show="showDate"
  141. mode="datetime"
  142. v-model="addForm.date"
  143. @cancel="showDate = false"
  144. @confirm="pickDate"></u-datetime-picker>
  145. </view>
  146. <u-notify ref="uNotify"></u-notify>
  147. <u-toast ref="uToast"></u-toast>
  148. <!-- 客户联系人 -->
  149. <customer-contact ref="concat" @close="closeConcat"></customer-contact>
  150. <!-- 项目联系人 -->
  151. <project-contact ref="projectConcat" @close="closeConcat"></project-contact>
  152. <!-- 经销商代理商联系人 -->
  153. <distr-contact ref="distrConcat" @close="closeConcat" :distrType="targetType"></distr-contact>
  154. <!-- 联系人 -->
  155. <select-user ref="userConcat" @close="closeConcat"></select-user>
  156. </view>
  157. </template>
  158. <script>
  159. // import { mapGetters } from 'vuex'
  160. import to from 'await-to-js'
  161. import followApi from '../../api/follow'
  162. import distrApi from '../../api/base/distr'
  163. import custApi from '../../api/customer'
  164. import prodApi from '../../api/project'
  165. import bidApi from '../../api/bid'
  166. import partnerApi from '../../api/partner'
  167. import ProjectContact from '@/components/ProjectContact'
  168. import CustomerContact from '@/components/CustomerContact'
  169. import DistrContact from '@/components/DistrContact'
  170. import SelectUser from '@/components/SelectUser'
  171. export default {
  172. name: 'omsIndex',
  173. components: {
  174. CustomerContact,
  175. ProjectContact,
  176. DistrContact,
  177. SelectUser,
  178. },
  179. data() {
  180. return {
  181. flag: true,
  182. imageValue: [],
  183. height: '',
  184. paddingTop: '',
  185. showMode: false, //选择行业
  186. showDate: false,
  187. maxDate: new Date() * 1,
  188. modeColumns: [
  189. [
  190. {
  191. label: '电话',
  192. id: '10',
  193. },
  194. {
  195. label: '邮件',
  196. id: '20',
  197. },
  198. {
  199. label: '拜访',
  200. id: '30',
  201. },
  202. ],
  203. ],
  204. addForm: {
  205. files: [],
  206. date: this.parseTime(new Date(), '{y}-{m}-{d}'), //时间
  207. content: '', //客户姓名
  208. furtherPlan: '', //下一步跟进计划
  209. effect: '',
  210. issue: '',
  211. mode: '', //跟进方式
  212. modeId: 0,
  213. fileName: '', //附件
  214. contactsName: '', //联系人
  215. contactsId: 0, //联系人ID
  216. },
  217. targetType: 0, //跟进对象类型(10客户,20项目,30合同,40回款,50经销商,50代理商)
  218. rules: {
  219. furtherPlan: {
  220. type: 'string',
  221. required: true,
  222. message: '请填写下一步跟进计划和目标',
  223. trigger: ['blur'],
  224. },
  225. content: {
  226. type: 'string',
  227. required: true,
  228. message: '请填写跟进内容',
  229. trigger: ['blur'],
  230. },
  231. effect: {
  232. type: 'string',
  233. required: true,
  234. message: '请填写达成效果',
  235. trigger: ['blur'],
  236. },
  237. issue: {
  238. type: 'string',
  239. required: true,
  240. message: '请填写问题或困难',
  241. trigger: ['blur'],
  242. },
  243. date: {
  244. type: 'string',
  245. required: true,
  246. message: '请填写跟进时间',
  247. trigger: ['blur'],
  248. },
  249. mode: {
  250. type: 'string',
  251. required: true,
  252. message: '请选择跟进方式',
  253. trigger: ['blur', 'change'],
  254. },
  255. },
  256. details: {},
  257. curId: 0,
  258. }
  259. },
  260. computed: {
  261. // ...mapGetters(['details']),
  262. },
  263. onLoad(option) {
  264. this.targetType = option.targetType
  265. this.curId = parseInt(option.id)
  266. },
  267. created() {
  268. const navData = uni.getMenuButtonBoundingClientRect()
  269. this.height = navData.height + 'px'
  270. this.paddingTop = navData.top + 'px'
  271. },
  272. mounted() {
  273. this.initData()
  274. },
  275. methods: {
  276. // 获取客户/项目信息
  277. async initData() {
  278. if (this.targetType == '10') {
  279. //客户
  280. const [err, res] = await to(
  281. custApi.getDetail({
  282. ids: [this.curId],
  283. })
  284. )
  285. if (err) return
  286. if (res && res.code == 200) {
  287. this.details = res.data.list[0]
  288. }
  289. } else if (this.targetType == '20') {
  290. //项目
  291. const [err, res] = await to(
  292. prodApi.getDetail({
  293. id: this.curId,
  294. })
  295. )
  296. if (err) return
  297. if (res && res.code == 200) {
  298. this.details = res.data
  299. }
  300. } else if (this.targetType == '50') {
  301. // 经销商代理商
  302. const [err, res] = await to(distrApi.getEntity({ id: this.curId }))
  303. if (err) return
  304. if (res && res.code == 200) {
  305. this.details = res.data.list
  306. }
  307. } else if (this.targetType == '60') {
  308. // 招标信息
  309. const [err, res] = await to(bidApi.get({ id: this.curId }))
  310. if (err) return
  311. if (res && res.code == 200) {
  312. this.details = res.data
  313. }
  314. } else if (this.targetType == '70') {
  315. // 合作伙伴信息
  316. const [err, res] = await to(partnerApi.getCompanyConcat({ id: this.curId }))
  317. if (err) return
  318. if (res && res.code == 200) {
  319. this.details = res.data
  320. }
  321. }
  322. },
  323. // 打开联系人
  324. open() {
  325. if (this.targetType == '10') {
  326. this.$refs.concat.open(this.details.id)
  327. } else if (this.targetType == '20') {
  328. this.$refs.projectConcat.open(this.details.id, this.details.custId)
  329. } else if (this.targetType == '50') {
  330. this.$refs.distrConcat.open(this.details.id)
  331. } else if (this.targetType == '60') {
  332. this.$refs.concat.open(this.details.custId)
  333. } else if (this.targetType == '70') {
  334. this.$refs.userConcat.open()
  335. }
  336. },
  337. // 行业选择
  338. pickMode(e) {
  339. this.addForm.modeId = e.value[0].id
  340. this.addForm.mode = e.value[0].label
  341. this.showMode = false
  342. },
  343. async pickDate(e) {
  344. this.showDate = false
  345. const timeFormat = uni.$u.timeFormat
  346. let timeValue = await timeFormat(e.value, 'yyyy-mm-dd hh:MM')
  347. this.addForm.date = timeValue
  348. },
  349. // 关闭选择用户
  350. closeConcat(user) {
  351. if (user) {
  352. if (this.targetType == '50') {
  353. this.addForm.contactsName = user.name
  354. this.addForm.contactsId = user.id
  355. } else {
  356. this.addForm.contactsName = user.label
  357. this.addForm.contactsId = user.id
  358. }
  359. }
  360. },
  361. handleAdd() {
  362. if (!this.flag) return
  363. this.$refs.addForm
  364. .validate()
  365. .then(async () => {
  366. let idParams = {}
  367. if (this.targetType == '10') {
  368. idParams = {
  369. custId: this.details.id,
  370. custName: this.details.custName,
  371. targetId: this.details.id,
  372. targetName: this.details.custName,
  373. }
  374. } else if (this.targetType == '20') {
  375. idParams = {
  376. custId: this.details.custId,
  377. custName: this.details.custName,
  378. targetId: this.details.id,
  379. targetName: this.details.nboName,
  380. }
  381. } else if (this.targetType == '50') {
  382. idParams = {
  383. targetId: this.details.id,
  384. targetName: this.details.distName,
  385. }
  386. } else if (this.targetType == '60') {
  387. idParams = {
  388. targetId: this.details.id,
  389. targetName: this.details.title,
  390. }
  391. } else if (this.targetType == '70') {
  392. idParams = {
  393. custId: null,
  394. custName: null,
  395. targetId: this.details.id,
  396. targetName: this.details.name,
  397. }
  398. }
  399. let params = Object.assign(
  400. {
  401. contactsId: this.addForm.contactsId,
  402. contactsName: this.addForm.contactsName,
  403. followContent: this.addForm.content,
  404. effect: this.addForm.effect,
  405. issue: this.addForm.issue,
  406. followDate: this.addForm.date,
  407. followType: this.addForm.modeId,
  408. targetType: this.targetType,
  409. furtherPlan: this.addForm.furtherPlan,
  410. files: this.addForm.files,
  411. },
  412. idParams
  413. )
  414. this.flag = false
  415. const [err, res] = await to(followApi.createFollow(params))
  416. this.flag = true
  417. if (err) return
  418. if (res && res.code == 200) {
  419. this.$refs.uToast.show({
  420. type: 'success',
  421. message: '创建成功',
  422. complete: () => {
  423. uni.navigateBack({
  424. //关闭当前页面,返回上一页面或多级页面。
  425. delta: 1,
  426. })
  427. },
  428. })
  429. }
  430. })
  431. .catch((err) => {
  432. this.$refs.uNotify.show({
  433. top: this.height + this.paddingTop + 10,
  434. type: 'warning',
  435. message: err[0].message,
  436. duration: 1000 * 3,
  437. })
  438. })
  439. },
  440. // // 选择上传触发函数
  441. select(e) {
  442. let url = process.uniEnv.VUE_APP_UPLOAD_WEED
  443. uni.request({
  444. url, //仅为示例,并非真实接口地址。
  445. success: (res) => {
  446. this.uploadFiles(e, res.data)
  447. },
  448. })
  449. },
  450. // 上传函数
  451. async uploadFiles(e, res) {
  452. let action = process.uniEnv.VUE_APP_PROTOCOL + res.publicUrl + '/' + res.fid
  453. uni.uploadFile({
  454. url: action,
  455. filePath: e.tempFilePaths[0],
  456. name: 'file',
  457. formData: {
  458. file: e.tempFiles[0].file,
  459. },
  460. success: (res) => {
  461. this.$refs.uToast.show({
  462. type: 'success',
  463. message: '附件上传成功',
  464. })
  465. this.addForm.files = [
  466. {
  467. fileName: e.tempFiles[0].file.name,
  468. fileUrl: action,
  469. },
  470. ]
  471. },
  472. fail: (err) => {},
  473. })
  474. },
  475. goBack() {
  476. uni.navigateBack({
  477. //关闭当前页面,返回上一页面或多级页面。
  478. delta: 1,
  479. })
  480. },
  481. },
  482. }
  483. </script>
  484. <style>
  485. page {
  486. background: #f2f3f5;
  487. }
  488. </style>
  489. <style lang="scss" scoped>
  490. .home {
  491. padding-top: 188rpx;
  492. .nav {
  493. position: absolute;
  494. left: 0;
  495. top: 0;
  496. width: 100%;
  497. height: 284rpx;
  498. background: #3e7ef8;
  499. .title {
  500. position: relative;
  501. text-align: center;
  502. font-size: 32rpx;
  503. font-weight: bold;
  504. color: #ffffff;
  505. .back {
  506. position: absolute;
  507. top: 0;
  508. bottom: 0;
  509. margin: auto;
  510. left: 70rpx;
  511. display: flex;
  512. }
  513. }
  514. }
  515. .main {
  516. position: absolute;
  517. width: 100%;
  518. height: calc(100vh - 188rpx);
  519. background: #ffffff;
  520. box-shadow: 0 6rpx 19rpx 2rpx rgba(0, 45, 132, 0.15);
  521. border-radius: 31rpx 31rpx 0 0;
  522. padding: 0 32rpx;
  523. overflow: auto;
  524. padding-bottom: 64rpx;
  525. .form-label {
  526. font-size: 32rpx;
  527. font-weight: bold;
  528. color: #323232;
  529. padding-bottom: 18rpx;
  530. .label-tag {
  531. width: 15rpx;
  532. height: 15rpx;
  533. background: #ff4d4f;
  534. border-radius: 50%;
  535. margin-right: 10rpx;
  536. }
  537. }
  538. .upload-file-box {
  539. padding: 20rpx 0;
  540. position: relative;
  541. .upload-btn {
  542. position: absolute;
  543. right: 0;
  544. top: 26rpx;
  545. }
  546. }
  547. }
  548. .save {
  549. width: 569rpx;
  550. height: 92rpx;
  551. background: #3e7ef8;
  552. border-radius: 31rpx;
  553. margin: 116rpx auto 0;
  554. font-size: 32rpx;
  555. color: #ffffff;
  556. text-align: center;
  557. line-height: 92rpx;
  558. }
  559. }
  560. </style>