index.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. <template>
  2. <div class="bgcolor complaintDetail">
  3. <div class="navBarTOP">
  4. <van-nav-bar class="navBar" left-arrow :title="title" @click-left="onClickLeft" />
  5. </div>
  6. <div class="lineGrey"></div>
  7. <div class="lineGrey"></div>
  8. <div class="lineGrey"></div>
  9. <div class="lineGrey"></div>
  10. <div class="lineGrey"></div>
  11. <!-- 客诉详情 -->
  12. <infoDetail v-if="infoData" :infoData="infoData" :customerClassify="customerClassifyOption">
  13. </infoDetail>
  14. <!-- 客诉记录 -->
  15. <complaintLog
  16. v-if="infoData && infoData.customerClueInfoComplaintList.length"
  17. :customerClueInfoComplaintList="infoData.customerClueInfoComplaintList"></complaintLog>
  18. <!-- 跟进记录 userCustomerClueList -->
  19. <!-- 历史跟进记录 -->
  20. <p style="margin: 0 16px 8px; color: #888" v-if="infoData && infoData.userCustomerClueList">
  21. 该客诉历史跟进记录
  22. </p>
  23. <van-cell-group inset class="cardclewContentCell" v-if="infoData">
  24. <div style="border-radius: 6px; overflow: hidden">
  25. <van-cell
  26. is-link
  27. v-for="(item, index) in infoData.userCustomerClueList"
  28. :key="index"
  29. @click="viewFn(item.userCustomerClueId)">
  30. <template #title>
  31. <span class="custom-title">{{ item.nickName }}</span>
  32. </template>
  33. <div class="cardContent">
  34. <p class="textLeft" style="padding-bottom: 0px; margin: 0">{{ item.createTime }}</p>
  35. </div>
  36. </van-cell>
  37. </div>
  38. </van-cell-group>
  39. <!-- 跟进任务填写 -->
  40. <div class="assign" v-if="infoData && infoData.isClose != 1">
  41. <!-- <div class="assign"> -->
  42. <!-- 来电分类 -->
  43. <div class="complaintType">
  44. <div class="typeItem">
  45. <van-field
  46. readonly
  47. clickable
  48. name="picker"
  49. :value="customerClassifyValue"
  50. label="来电分类一"
  51. placeholder="点击选择来电分类一"
  52. @click="showPicker1 = true" />
  53. </div>
  54. <div class="typeItem">
  55. <van-field
  56. readonly
  57. clickable
  58. name="picker"
  59. :value="customerSubClassifyValue"
  60. label="来电分类二"
  61. placeholder="点击选择来电分类二"
  62. @click="showPicker2 = true" />
  63. </div>
  64. </div>
  65. <!-- <div class="followUp required">跟进结果</div> -->
  66. <div class="taskGather" v-if="taskGather">
  67. <radioGroup :clueOptionList="taskGather"></radioGroup>
  68. </div>
  69. <div class="tc" style="padding: 0 16px">
  70. <van-button class="submitBtn" block type="info" color="#0057ba" @click="onSubmit">
  71. 提交
  72. </van-button>
  73. </div>
  74. </div>
  75. <!-- 客诉跟进历史 -->
  76. <van-dialog
  77. v-model="showView"
  78. title="客诉历史"
  79. show-cancel-button
  80. cancel-button-text="关闭"
  81. :show-confirm-button="false"
  82. class="dialogz">
  83. <followUpHistory :historyId="historyId" :showView="showView"></followUpHistory>
  84. </van-dialog>
  85. <van-popup v-model="showPicker1" position="bottom">
  86. <van-picker
  87. show-toolbar
  88. value-key="dictLabel"
  89. :columns="customerClassifyOption"
  90. @confirm="onConfirm1"
  91. @cancel="showPicker1 = false" />
  92. </van-popup>
  93. <van-popup v-model="showPicker2" position="bottom">
  94. <van-picker
  95. show-toolbar
  96. value-key="dictLabel"
  97. :columns="customerSubClassifyOption"
  98. @confirm="onConfirm2"
  99. @cancel="showPicker2 = false" />
  100. </van-popup>
  101. </div>
  102. </template>
  103. <script>
  104. import {
  105. getComplaintCustomerClueInfoById,
  106. insertCustomerClueAnswerKs,
  107. } from '@/api/complaintDetail';
  108. import infoDetail from './infoDetail.vue';
  109. import complaintLog from './complaintLog.vue';
  110. import { mapState } from 'vuex';
  111. import radioGroup from './radioGroup';
  112. import followUpHistory from './followUpHistory';
  113. import { getDictOption } from '@/api/index';
  114. export default {
  115. name: 'complaintDetail',
  116. components: {
  117. infoDetail,
  118. complaintLog,
  119. radioGroup,
  120. followUpHistory,
  121. },
  122. computed: {
  123. ...mapState({
  124. userInfo: (state) => state.user.userInfo,
  125. }),
  126. },
  127. data() {
  128. return {
  129. id: '',
  130. infoData: null,
  131. title: '',
  132. postName: '',
  133. taskGather: null, //跟进任务集合
  134. requiredFlag: true, //问题必填检验
  135. requiredMessage: '', //必填提示信息
  136. showView: false,
  137. historyId: '',
  138. customerClassifyValue: '',
  139. customerClassify: '',
  140. customerSubClassifyValue: '',
  141. customerSubClassify: '',
  142. showPicker1: false,
  143. showPicker2: false,
  144. customerClassifyOption: [],
  145. customerSubClassifyOption: [],
  146. };
  147. },
  148. watch: {},
  149. activated() {
  150. this.id = this.$route.query.id;
  151. this.postName = localStorage.getItem('postName');
  152. this.getComplaintCustomerClueInfoByIdFun();
  153. },
  154. methods: {
  155. async getCustomerClassify() {
  156. let option = await getDictOption({}, 'customer_classify');
  157. this.customerClassifyOption = option.data || [];
  158. // 来电分类一
  159. let item = this.customerClassifyOption.find(
  160. (val) => val.dictValue == this.infoData.customerClassify
  161. );
  162. this.customerClassifyValue = item.dictLabel || '';
  163. this.customerClassify = item.dictValue;
  164. },
  165. async getCustomerSubClassify() {
  166. let option = await getDictOption({}, 'customer_sub_classify');
  167. this.customerSubClassifyOption = option.data || [];
  168. // 来电分类二
  169. let item = this.customerSubClassifyOption.find(
  170. (val) => val.dictValue == this.infoData.customerSubClassify
  171. );
  172. this.customerSubClassifyValue = item.dictLabel || '';
  173. this.customerSubClassify = item.dictValue;
  174. },
  175. getComplaintCustomerClueInfoByIdFun() {
  176. this.toastLoading(0, '加载中...', true);
  177. this.id = this.$route.query.id;
  178. getComplaintCustomerClueInfoById({ customerClueInfoId: this.id }).then((response) => {
  179. this.toastLoading().clear();
  180. if (response.code == 200) {
  181. this.infoData = response.data;
  182. this.title = response.data.name;
  183. if (this.infoData.isClose != 1) {
  184. // response.data.customerClue.customerClueItemList[0].customerClueInfoId = this.id;
  185. this.taskGather = response.data.customerClue.customerClueItemList;
  186. }
  187. this.getCustomerClassify();
  188. this.getCustomerSubClassify();
  189. } else {
  190. this.$toast(res.msg);
  191. }
  192. });
  193. },
  194. onSubmit() {
  195. // 没有选择跟进记录
  196. // if (!this.taskGather[0].searchValue) {
  197. // this.$toast('请选择跟进结果');
  198. // return;
  199. // }
  200. this.requiredFlag = true;
  201. let customerClueItemList = [];
  202. // 每一个层级都是一道题的题目,子级就是题,被选中和填写的题要带上题目一块上传(题的同级也要上传)
  203. // 第一级题目下的题默认都要上传
  204. let params = {
  205. customerClueItemList: [],
  206. customerClassify: this.customerClassify,
  207. customerSubClassify: this.customerSubClassify,
  208. };
  209. params.customerClueItemList.push(...this.deepClone(this.taskGather, 0));
  210. // let optionList = this.taskGather[0].customerClueOptionList;
  211. this.filterOption(this.taskGather, params);
  212. console.log(JSON.stringify(params));
  213. // 必填验证
  214. if (this.requiredFlag) {
  215. this.toastLoading(0, '加载中...', true);
  216. insertCustomerClueAnswerKs(params).then((res) => {
  217. this.toastLoading().clear();
  218. if (res.code == 200) {
  219. this.$toast(res.msg);
  220. window.location.replace(window.location.origin + '/mobile/clew');
  221. } else {
  222. this.$toast(res.msg);
  223. }
  224. });
  225. } else {
  226. this.$toast(this.requiredMessage);
  227. }
  228. },
  229. filterOption(optionList, params) {
  230. for (let val = 0; val < optionList.length; val++) {
  231. if (
  232. optionList[val].isMust == '0' &&
  233. optionList[val].searchValue == null &&
  234. optionList[val].answerType == 'dx'
  235. ) {
  236. // 题目必填校验
  237. this.requiredFlag = false;
  238. this.requiredMessage = '请选择' + optionList[val].customerClueName;
  239. return;
  240. } else if (optionList[val].isMust == '0' && optionList[val].searchValue) {
  241. let customerClueOptionList = optionList[val].customerClueOptionList;
  242. for (let i = 0; i < customerClueOptionList.length; i++) {
  243. if (customerClueOptionList[i].value == 'Y') {
  244. if (customerClueOptionList[i].customerClueItemList) {
  245. // 必填校验
  246. this.isRequiredFlag(customerClueOptionList[i].customerClueItemList);
  247. let customerClueItemList =
  248. params.customerClueItemList[val].customerClueOptionList[i].customerClueItemList;
  249. customerClueItemList.push(
  250. ...this.deepClone(customerClueOptionList[i].customerClueItemList, 0)
  251. );
  252. if (customerClueOptionList[i].customerClueItemList[0]) {
  253. this.filterOption(customerClueOptionList[i].customerClueItemList, params);
  254. }
  255. }
  256. }
  257. }
  258. }
  259. }
  260. },
  261. // 深拷贝指定拷贝层级
  262. deepClone(obj, num) {
  263. // 检查是否为对象或数组
  264. if (obj === null || typeof obj !== 'object') {
  265. return obj; // 基本类型直接返回
  266. }
  267. // 创建一个数组或对象
  268. const copy = Array.isArray(obj) ? [] : {};
  269. // 遍历对象的每个属性
  270. for (const key in obj) {
  271. if (obj.hasOwnProperty(key) && num < 2) {
  272. // 递归调用深拷贝
  273. if (key == 'customerClueOptionList' || key == 'customerClueItemList') {
  274. num = num + 1;
  275. }
  276. copy[key] = this.deepClone(obj[key], num);
  277. }
  278. }
  279. return copy;
  280. },
  281. isRequiredFlag(optionList) {
  282. // console.log(optionList);
  283. // 必填类型
  284. for (let i = 0; i < optionList.length; i++) {
  285. if (optionList[i].answerType == 'wb' && optionList[i].isMust == 0) {
  286. if (!optionList[i].answerValue) {
  287. this.requiredFlag = false;
  288. this.requiredMessage = optionList[i].remark;
  289. return;
  290. }
  291. }
  292. }
  293. },
  294. // 校验错误返回信息
  295. onFailed(errorInfo) {
  296. console.log('failed', errorInfo);
  297. },
  298. // 查看历史跟进记录
  299. viewFn(val) {
  300. // this.viewTextShow = false;
  301. this.historyId = val;
  302. this.showView = true;
  303. },
  304. onConfirm1(value) {
  305. this.customerClassifyValue = value.dictLabel;
  306. this.customerClassify = value.dictValue;
  307. this.showPicker1 = false;
  308. },
  309. onConfirm2(value) {
  310. this.customerSubClassifyValue = value.dictLabel;
  311. this.customerSubClassify = value.dictValue;
  312. this.showPicker2 = false;
  313. },
  314. onClickLeft() {
  315. this.$router.go(-1);
  316. },
  317. },
  318. };
  319. </script>
  320. <style scoped lang="scss">
  321. .complaintDetail {
  322. .assign {
  323. margin: 10px;
  324. background-color: #fff;
  325. padding-bottom: 20px;
  326. .followUp {
  327. padding: 16px;
  328. font-size: 14px;
  329. font-weight: 600;
  330. }
  331. }
  332. .taskGather {
  333. padding-left: 20px;
  334. .title {
  335. font-size: 15px;
  336. font-weight: 600;
  337. padding: 5px 0;
  338. }
  339. }
  340. }
  341. </style>
  342. <style lang="scss">
  343. .complaintDetail {
  344. .van-field__label {
  345. width: 100px;
  346. &::before {
  347. content: '*';
  348. color: red;
  349. }
  350. }
  351. .centerBtn {
  352. float: right;
  353. background: #0057ba;
  354. border-color: #0057ba;
  355. color: #fff;
  356. margin-top: -33px;
  357. border-radius: 5px;
  358. }
  359. .dialogz {
  360. width: 100%;
  361. display: flex;
  362. flex-direction: column;
  363. overflow: hidden;
  364. height: 72vh;
  365. .van-dialog__content {
  366. flex: 1;
  367. overflow-y: auto;
  368. }
  369. }
  370. .van-cell {
  371. font-size: 15px;
  372. color: #000;
  373. }
  374. }
  375. </style>