index.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  1. <template>
  2. <view class="post_item" @click.stop="handlePostRootClick">
  3. <view class="post_top">
  4. <view class="top_left">{{ item.name }}
  5. &nbsp;&nbsp;&nbsp;
  6. <view v-if="item.price" class="price">
  7. <u-icon name="rmb-circle"></u-icon>
  8. {{ item.price }}
  9. <view class="more" v-if="item.hasMoreInquiryPrice" @click.stop="handleInquiry(item)">
  10. 更多
  11. </view>
  12. </view>
  13. </view>
  14. <view class="top_right">{{ item.assignStateCode === '1' ? "已分配" : "未分配" }}</view>
  15. </view>
  16. <view class="post_info">
  17. <view class="telPhone">
  18. <view class="phone">
  19. <text>电话:</text>
  20. <show-real-text :real="item.telephone" :type='type'
  21. :style="{ color: item.repetitionOperationName ? 'red' : 'black' }"></show-real-text>
  22. <template v-if="item.telAddr">
  23. ({{ item.telAddr }})
  24. </template>
  25. <text v-if="item.repetitionOperationName">( 撞 : {{ item.repetitionOperationName }})</text>
  26. </view>
  27. <view class="copy_btn" @click.stop="handleCopy(item)" v-if="type != '1'">复制</view>
  28. </view>
  29. <view class="info" v-if="item.weixin">
  30. <text>微信:</text>
  31. <view class="phone">
  32. <text :style="{ color: item.repetitionOperWeixinName ? 'red' : 'black' }">{{ item.weixin }}</text>
  33. <text v-if="item.repetitionOperWeixinName">( 撞 : {{ item.repetitionOperWeixinName }})</text>
  34. </view>
  35. </view>
  36. <view class="info">
  37. <view class="createTime">{{ item.createTime }}</view>
  38. </view>
  39. <view class="info">
  40. <view>{{ item.appName }}</view>
  41. </view>
  42. <view class="info">
  43. <view class="owner">所属人:{{ item.clueOwnerName ? item.clueOwnerName : "-" }}</view>
  44. </view>
  45. <view class="info">
  46. <view class="operation">运营人:{{ item.clueOperationName ? item.clueOperationName : "-" }}</view>
  47. </view>
  48. <view class="info latest_follow_row" @click.stop="handleShowFollowDetail(item)">
  49. <view class="label">最新跟进记录:</view>
  50. <view class="latest_follow_content">
  51. <text class="latest_follow_text">{{ item.latestDynamicData || '暂无' }}</text>
  52. <text class="latest_follow_time" v-if="item.latestDynamicTime">{{ item.latestDynamicTime }}</text>
  53. </view>
  54. <!-- <view class="latest_follow_add" @click.stop="handleAddFollow(item)">增加</view> -->
  55. </view>
  56. </view>
  57. <view class="clue_state_wrap">
  58. <view class="clue_state">
  59. <view class="state_wrap">
  60. <view class="label">线索阶段:</view>{{ crm_clue_phase(item.clueState) }}
  61. </view>
  62. <view class="clueTag">
  63. <view class="label">线索标签:</view>
  64. <u-tag :text="tag.name" plain plainFill borderColor="#fff" size="mini"
  65. v-for="(tag) in item.clueTags" :key="tag.id" style="margin-right: 10px;" :bgColor="tag.color"
  66. color="#fff"></u-tag>
  67. </view>
  68. </view>
  69. </view>
  70. <view class="sendOrder_wrap">
  71. <view class="sendOrder inquiry" @click.stop="handleInquiry(item)">
  72. <image src='/static/publicClue/inquiry.png' mode="aspectFit" class="sendOrder_img"></image>
  73. <view>询价</view>
  74. </view>
  75. <view class="sendOrder" @click.stop="handleSendOrder(item)">
  76. <image src='/static/publicClue/littlePlane.png' mode="aspectFit" class="sendOrder_img"></image>
  77. <view>发单</view>
  78. </view>
  79. </view>
  80. <add-inquiry-dialog ref="inquiryDialog" :clueId="clueId" :isClue="true" :editOrAdd="editOrAdd" :editInfo="editInfo" title="询价" @cancel="handleInquiryCancel" :type="1"/>
  81. <!-- 最新跟进记录详情弹窗;关闭时记时间戳,避免蒙层点击冒泡触发详情跳转 -->
  82. <u-popup :show="followPopupVisible" mode="bottom" round="16" @close="onFollowPopupClose"
  83. :closeOnClickOverlay="true" @open="loadFollowList">
  84. <view class="follow_popup">
  85. <view class="follow_popup_title">跟进记录</view>
  86. <scroll-view scroll-y class="follow_popup_list" v-if="followList.length">
  87. <view class="follow_item" v-for="f in followList" :key="f.id">
  88. <view class="follow_top">
  89. <text class="person_name">{{ f.createNickname || f.createNickName || '-' }}</text>
  90. <text class="follow_time">{{ f.createTime }}</text>
  91. </view>
  92. <view class="follow_content">{{ f.content }}</view>
  93. </view>
  94. </scroll-view>
  95. <view class="follow_popup_empty" v-else-if="!followLoading">{{ followListError || '暂无跟进记录' }}</view>
  96. <view class="follow_popup_loading" v-else>加载中...</view>
  97. <!-- <view class="follow_popup_footer">
  98. <u-button type="primary" @click="handleAddFollowFromPopup">增加跟进</u-button>
  99. </view> -->
  100. </view>
  101. </u-popup>
  102. </view>
  103. </template>
  104. <script>
  105. import addInquiryDialog from '@/components/add-inquiry-dialog/index.vue'
  106. import {
  107. selectDictLabel
  108. } from "@/utils/util";
  109. export default {
  110. components: {
  111. addInquiryDialog
  112. },
  113. props: {
  114. item: {
  115. type: Object,
  116. required: true
  117. },
  118. dicts: {
  119. type: Object,
  120. required: true
  121. },
  122. type: {
  123. type: String | Number,
  124. required: true
  125. },
  126. },
  127. data() {
  128. return {
  129. caseStatusDicts: [],
  130. clueId: '',
  131. editOrAdd: 'add',
  132. editInfo: {},
  133. followPopupVisible: false,
  134. followList: [],
  135. followLoading: false,
  136. followListError: '',
  137. currentFollowClue: null,
  138. closedFollowPopupAt: 0
  139. }
  140. },
  141. methods: {
  142. // 字典翻译
  143. crm_clue_phase(caseStatus) {
  144. return selectDictLabel(this.dicts.caseStatusDicts, caseStatus);
  145. },
  146. handleCopy(item) {
  147. uni.setClipboardData({
  148. data: item.telephone,
  149. success: function () {
  150. uni.$u.toast("复制成功");
  151. }
  152. });
  153. },
  154. // 主页面的发单
  155. async handleSendOrder(item) {
  156. console.log(item);
  157. const {
  158. data: count
  159. } = await uni.$u.api.getClueSendFormCountByClueId({
  160. clueId: item.id
  161. });
  162. console.log(count);
  163. if (count > 0) {
  164. uni.showModal({
  165. title: '该线索已发单是否再次发单?',
  166. success: (res) => {
  167. if (res.confirm) {
  168. this.toOrderForm(item)
  169. }
  170. }
  171. });
  172. } else {
  173. this.toOrderForm(item)
  174. }
  175. },
  176. toOrderForm(item) {
  177. console.log(item);
  178. const {
  179. id,
  180. ownLatestDynamicTime,
  181. createTime,
  182. clueOwnerId
  183. } = item;
  184. if (this.$store.state.user.userInfo.userId === clueOwnerId) {
  185. uni.navigateTo({
  186. url: `/pages/orderForm/index?clueId=${id}`
  187. })
  188. } else {
  189. // 确定用于判断的目标时间(ownLatestDynamicTime,为null则用createTime)
  190. const date = ownLatestDynamicTime || createTime
  191. const twoDaysLater = new Date(date)
  192. twoDaysLater.setDate(twoDaysLater.getDate() + 2) // 日期加2天
  193. // 是否大于当前时间 小于可以发单
  194. let isOrderForm = false
  195. if (twoDaysLater) {
  196. isOrderForm = twoDaysLater.getTime() <= new Date().getTime()
  197. }
  198. if (isOrderForm) {
  199. uni.navigateTo({
  200. url: `/pages/orderForm/index?clueId=${id}`
  201. })
  202. } else {
  203. uni.$u.toast('非所属人需两天内无跟进记录才可发单')
  204. }
  205. }
  206. },
  207. // 询价
  208. async handleInquiry(item) {
  209. this.clueId = item.id
  210. if(Number(item.count) > 0){//count 默认是0,新增之后变为1,之后每编辑一次就会+1
  211. const data = {
  212. clueId: item.id,
  213. type:1
  214. }
  215. this.editOrAdd = 'editForm'
  216. uni.$u.api.inquiryDetail(data).then(res=>{
  217. if(res.code === 200){
  218. this.$nextTick(()=>{
  219. this.editInfo = res.data
  220. this.$refs.inquiryDialog.showDialog()
  221. })
  222. }
  223. })
  224. }else if(!item.count){
  225. this.editOrAdd = 'add'
  226. this.$refs.inquiryDialog.showDialog()
  227. }
  228. },
  229. // 询价取消
  230. handleInquiryCancel() {
  231. this.$refs.inquiryDialog.closeDialog()
  232. },
  233. // 卡片根节点点击:若刚因点蒙层关闭弹窗则不跳转,否则通知父组件跳转详情
  234. handlePostRootClick() {
  235. if (Date.now() - this.closedFollowPopupAt < 400) return
  236. this.$emit('to-detail', this.item)
  237. },
  238. onFollowPopupClose() {
  239. this.followPopupVisible = false
  240. this.closedFollowPopupAt = Date.now()
  241. },
  242. // 点击最新跟进记录,打开详情弹窗
  243. handleShowFollowDetail(item) {
  244. this.currentFollowClue = item
  245. this.followPopupVisible = true
  246. this.followList = []
  247. this.followListError = ''
  248. },
  249. // 弹窗打开时加载该线索的跟进列表
  250. async loadFollowList() {
  251. if (!this.currentFollowClue || !this.currentFollowClue.id) return
  252. this.followLoading = true
  253. this.followListError = ''
  254. try {
  255. const res = await uni.$u.api.getClueFollowListByClueId({ clueId: this.currentFollowClue.id })
  256. this.followList = (res.data || res) || []
  257. } catch (e) {
  258. console.error('获取跟进记录失败', e)
  259. this.followListError = '加载失败'
  260. this.followList = []
  261. } finally {
  262. this.followLoading = false
  263. }
  264. },
  265. // 增加跟进:跳转添加跟进页
  266. handleAddFollow(item) {
  267. const id = (item && item.id) || (this.currentFollowClue && this.currentFollowClue.id)
  268. if (!id) return
  269. uni.navigateTo({
  270. url: `/pages/addFollow/index?clueId=${id}`
  271. })
  272. this.followPopupVisible = false
  273. },
  274. // 弹窗内点击「增加跟进」
  275. handleAddFollowFromPopup() {
  276. this.handleAddFollow(this.currentFollowClue)
  277. },
  278. },
  279. }
  280. </script>
  281. <style lang="scss" scoped>
  282. .post_item {
  283. background: #fff;
  284. border-radius: 20px;
  285. padding: 20px;
  286. margin-bottom: 20px;
  287. position: relative;
  288. .post_top {
  289. display: flex;
  290. justify-content: space-between;
  291. margin-bottom: 10px;
  292. .top_left {
  293. font-size: 18px;
  294. display: flex;
  295. .price{
  296. display: flex;
  297. align-items: center;
  298. gap: 8rpx;
  299. .more{
  300. font-size: 20rpx;
  301. border: 1px solid #3c9cff;
  302. border-radius:10rpx;
  303. padding: 3rpx 8rpx;
  304. color: #3c9cff;
  305. }
  306. }
  307. }
  308. .top_right {
  309. color: #87bf66;
  310. }
  311. }
  312. .post_info {
  313. font-size: 14px;
  314. display: grid;
  315. grid-template-columns: 1fr 1fr;
  316. gap: 10rpx;
  317. .telPhone {
  318. display: flex;
  319. grid-column: 1 / -1;
  320. .copy_btn {
  321. color: #4fa5fe;
  322. margin-left: 10px;
  323. }
  324. }
  325. .info {
  326. display: flex;
  327. // margin-bottom: 10px;
  328. overflow: hidden;
  329. > view {
  330. white-space: nowrap;
  331. overflow: hidden;
  332. text-overflow: ellipsis;
  333. flex: 1;
  334. }
  335. .createTime {
  336. margin-right: 10px;
  337. }
  338. .copy_btn {
  339. display: flex;
  340. flex-wrap: wrap;
  341. }
  342. }
  343. .latest_follow_row {
  344. align-items: flex-start;
  345. flex-wrap: wrap;
  346. grid-column: 1 / -1;
  347. .label {
  348. flex: 0 0 90px;
  349. }
  350. .latest_follow_content {
  351. flex: 1;
  352. min-width: 0;
  353. display: flex;
  354. gap: 4px;
  355. flex-direction: column;
  356. }
  357. .latest_follow_text {
  358. font-size: 13px;
  359. word-break: break-all;
  360. }
  361. .latest_follow_time {
  362. font-size: 12px;
  363. }
  364. .latest_follow_add {
  365. flex-shrink: 0;
  366. color: #4fa5fe;
  367. font-size: 14px;
  368. margin-left: 8px;
  369. }
  370. }
  371. }
  372. .clue_state_wrap {
  373. font-size: 14px;
  374. background: #f8f9fb;
  375. padding: 10px;
  376. color: #9b9aa2;
  377. overflow: hidden;
  378. .state_wrap {
  379. display: flex;
  380. margin-bottom: 10px;
  381. }
  382. .label {
  383. flex: 0 0 66px;
  384. }
  385. .clueTag {
  386. display: flex;
  387. }
  388. }
  389. .sendOrder_wrap {
  390. display: flex;
  391. justify-content: space-between;
  392. gap: 20rpx;
  393. .sendOrder {
  394. width: 50%;
  395. height: 40px;
  396. background-color: rgb(36, 98, 234);
  397. color: #fff;
  398. border-radius: 20rpx;
  399. display: flex;
  400. justify-content: center;
  401. align-items: center;
  402. gap: 20rpx;
  403. font-size: 28rpx;
  404. font-weight: 700;
  405. font-family: "uicon";
  406. margin-top: 10rpx;
  407. .sendOrder_img {
  408. width: 20px;
  409. height: 20px;
  410. }
  411. }
  412. .inquiry {
  413. right: 140rpx;
  414. color: #2563eb;
  415. border: 1px solid #2563eb;
  416. background-color: #fff;
  417. }
  418. }
  419. }
  420. .follow_popup {
  421. padding: 24rpx 30rpx 40rpx;
  422. max-height: 70vh;
  423. display: flex;
  424. flex-direction: column;
  425. .follow_popup_title {
  426. font-size: 34rpx;
  427. font-weight: 600;
  428. margin-bottom: 24rpx;
  429. text-align: center;
  430. }
  431. .follow_popup_list {
  432. flex: 1;
  433. max-height: 50vh;
  434. margin-bottom: 24rpx;
  435. }
  436. .follow_item {
  437. padding: 20rpx 0;
  438. border-bottom: 1rpx solid #eee;
  439. &:last-child {
  440. border-bottom: none;
  441. }
  442. .follow_top {
  443. display: flex;
  444. justify-content: space-between;
  445. align-items: center;
  446. margin-bottom: 12rpx;
  447. font-size: 24rpx;
  448. .person_name {
  449. color: #333;
  450. font-weight: 500;
  451. }
  452. .follow_time {
  453. color: #999;
  454. }
  455. }
  456. .follow_content {
  457. font-size: 26rpx;
  458. color: #666;
  459. line-height: 1.5;
  460. word-break: break-all;
  461. }
  462. }
  463. .follow_popup_empty,
  464. .follow_popup_loading {
  465. text-align: center;
  466. color: #999;
  467. font-size: 28rpx;
  468. padding: 60rpx 0;
  469. }
  470. .follow_popup_footer {
  471. padding-top: 20rpx;
  472. }
  473. }
  474. </style>