index.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. <template>
  2. <view class="caseInfo_wrap">
  3. <template v-if="clueDetail.id">
  4. <view class="caseInfo_content_wrap">
  5. <view class="caseInfo_title">
  6. <image src="/static/clueDetail/icon-caseInfo.png" mode=""></image>
  7. <text class="info_text">线索基础信息</text>
  8. </view>
  9. <view class="caseInfo_main_wrap">
  10. <template v-if="params.type === '1'">
  11. <view class="Info_item" v-for="(item, index) in caseInfoColumn" :key="item.prop">
  12. <text class="label">{{ item.label }}</text>
  13. <show-real-text :real="clueDetail.telephone" :type='params.type'
  14. v-if="item.prop === 'telephone' && !hasEditPermission"></show-real-text>
  15. <view class="phone_display value"
  16. v-else-if="item.prop === 'telephone' && hasEditPermission && !phoneEditing"
  17. @click="phoneEditing = true">{{ clueDetail.dsPhone || '点击编辑' }}</view>
  18. <view class="input_wrap"
  19. v-else-if="item.prop === 'telephone' && hasEditPermission && phoneEditing">
  20. <input v-model="clueDetail.telephone" :focus="phoneEditing"
  21. style="text-align: right; padding-right: 10px;" @blur="onPhoneBlur" />
  22. </view>
  23. <view class="input_wrap" v-else-if="item.type === 'date'">
  24. <picker mode="date"
  25. :value="clueDetail[item.prop] ? clueDetail[item.prop].slice(0, 10) : ''"
  26. :disabled="!hasEditPermission" @change="onDateChange">
  27. <view class="picker_value">{{ clueDetail[item.prop] ? clueDetail[item.prop].slice(0,
  28. 10) : '请选择日期' }}</view>
  29. </picker>
  30. </view>
  31. <view class="input_wrap corporate_select" v-else-if="item.prop === 'genderTypeCode'">
  32. <view class="corporate_select_inner">
  33. <ld-select v-if="hasEditPermission" :list="sysUserSexDict" label-key="dictLabel"
  34. value-key="dictValue" placeholder="请选择" v-model="clueDetail.genderTypeCode"
  35. :border="false" @change="updateClueMainInfo" />
  36. <text v-else class="value">{{ genderTypeCodeLabel }}</text>
  37. </view>
  38. </view>
  39. <view class="input_wrap corporate_select" v-else-if="item.prop === 'corporateStatus'">
  40. <view class="corporate_select_inner">
  41. <ld-select v-if="hasEditPermission" :list="crmCorporateStatusDict"
  42. label-key="dictLabel" value-key="dictValue" placeholder="请选择"
  43. v-model="clueDetail.corporateStatus" :border="false"
  44. @change="updateClueMainInfo" />
  45. <text v-else class="value">{{ corporateStatusLabel }}</text>
  46. </view>
  47. </view>
  48. <view class="input_wrap" v-else-if="item.input">
  49. <input v-model="clueDetail[item.prop]" :disabled="!hasEditPermission"
  50. style="text-align: right; padding-right: 10px;" @blur="updateClueMainInfo" />
  51. </view>
  52. <text v-else-if="item.color" class="value highlight" :style="handleStyle(item.color)">
  53. {{ clueDetail[item.prop] }}
  54. </text>
  55. <text v-else class="value">{{ clueDetail[item.prop] }}</text>
  56. </view>
  57. </template>
  58. <template v-if="params.type === '2'">
  59. <view class="Info_item" v-for="(item, index) in caseInfoColumn" :key="item.prop">
  60. <text class="label">{{ item.label }}</text>
  61. <view class="phone_display value"
  62. v-if="item.prop === 'telephone' && hasEditPermission && !phoneEditing"
  63. @click="phoneEditing = true">{{ clueDetail.dsPhone || '点击编辑' }}</view>
  64. <view class="input_wrap"
  65. v-else-if="item.prop === 'telephone' && hasEditPermission && phoneEditing">
  66. <input v-model="clueDetail.telephone" :focus="phoneEditing"
  67. style="text-align: right; padding-right: 10px;" @blur="onPhoneBlur" />
  68. </view>
  69. <text v-else-if="item.prop === 'telephone'" class="value">{{ clueDetail.dsPhone ||
  70. clueDetail.telephone }}</text>
  71. <view class="input_wrap" v-else-if="item.type === 'date'">
  72. <picker mode="date"
  73. :value="clueDetail[item.prop] ? clueDetail[item.prop].slice(0, 10) : ''"
  74. :disabled="!hasEditPermission" @change="onDateChange">
  75. <view class="picker_value">{{ clueDetail[item.prop] ? clueDetail[item.prop].slice(0,
  76. 10) : '请选择日期' }}</view>
  77. </picker>
  78. </view>
  79. <view class="input_wrap corporate_select" v-else-if="item.prop === 'genderTypeCode'">
  80. <view class="corporate_select_inner">
  81. <ld-select v-if="hasEditPermission" :list="sysUserSexDict" label-key="dictLabel"
  82. value-key="dictValue" placeholder="请选择" v-model="clueDetail.genderTypeCode"
  83. :border="false" @change="updateClueMainInfo" />
  84. <text v-else class="value">{{ genderTypeCodeLabel }}</text>
  85. </view>
  86. </view>
  87. <view class="input_wrap corporate_select" v-else-if="item.prop === 'corporateStatus'">
  88. <view class="corporate_select_inner">
  89. <ld-select v-if="hasEditPermission" :list="crmCorporateStatusDict"
  90. label-key="dictLabel" value-key="dictValue" placeholder="请选择"
  91. v-model="clueDetail.corporateStatus" :border="false"
  92. @change="updateClueMainInfo" />
  93. <text v-else class="value">{{ corporateStatusLabel }}</text>
  94. </view>
  95. </view>
  96. <view class="input_wrap" v-else-if="item.input">
  97. <input v-model="clueDetail[item.prop]" :disabled="!hasEditPermission"
  98. style="text-align: right; padding-right: 10px;" @blur="updateClueMainInfo" />
  99. </view>
  100. <text v-else-if="item.color" class="value highlight" :style="handleStyle(item.color)">
  101. {{ clueDetail[item.prop] }}
  102. </text>
  103. <text v-else class="value">{{ clueDetail[item.prop] }}</text>
  104. </view>
  105. </template>
  106. <template v-if="parsedRemarkDict && Object.keys(parsedRemarkDict).length > 0">
  107. <view class="Info_item" v-for="(value, key) in parsedRemarkDict" :key="key">
  108. <text class="label">{{ key }}</text>
  109. <text class="value">{{ value }}</text>
  110. </view>
  111. </template>
  112. </view>
  113. </view>
  114. </template>
  115. </view>
  116. </template>
  117. <script>
  118. import {
  119. cloneDeep,
  120. isEqual
  121. } from 'lodash';
  122. import { checkPermi } from '@/utils/permission';
  123. import ldSelect from '@/components/ld-select/ld-select.vue';
  124. const caseInfoColumn = [{
  125. prop: 'name',
  126. label: '姓名',
  127. input: true
  128. },
  129. {
  130. prop: 'telephone',
  131. label: '电话',
  132. input: true
  133. },
  134. {
  135. prop: 'genderTypeCode',
  136. label: '性别',
  137. type: 'select',
  138. dictType: 'sys_user_sex'
  139. },
  140. {
  141. prop: 'age',
  142. label: '年龄',
  143. input: true
  144. },
  145. {
  146. prop: 'weixin',
  147. label: '微信',
  148. input: true
  149. },
  150. {
  151. prop: 'corporateStatus',
  152. label: '企业号状态',
  153. type: 'select',
  154. dictType: 'crm_corporate_status'
  155. },
  156. {
  157. prop: 'userDouyinId',
  158. label: '抖音号',
  159. },
  160. {
  161. prop: 'autoAddress',
  162. label: '自动定位城市',
  163. },
  164. {
  165. prop: 'telAddr',
  166. label: '手机号归属地',
  167. },
  168. {
  169. prop: 'createTime',
  170. label: '线索创建时间',
  171. },
  172. {
  173. prop: 'updateTime',
  174. label: '最新修改时间',
  175. },
  176. {
  177. prop: 'address',
  178. label: '详细地址',
  179. input: true
  180. },
  181. {
  182. prop: 'remark',
  183. label: '备注',
  184. input: true
  185. },
  186. {
  187. prop: 'qq',
  188. label: 'QQ号',
  189. input: true
  190. },
  191. {
  192. prop: 'email',
  193. label: '邮箱',
  194. input: true
  195. },
  196. {
  197. prop: 'date',
  198. label: '日期',
  199. type: 'date'
  200. },
  201. {
  202. prop: 'manualAddressCode',
  203. label: '手动填写地域',
  204. input: true
  205. }
  206. ]
  207. export default {
  208. components: { ldSelect },
  209. props: {
  210. clueId: {
  211. required: true
  212. },
  213. params: {
  214. type: Object,
  215. required: true
  216. },
  217. clueDetailVo: {
  218. type: Object,
  219. required: true
  220. },
  221. },
  222. computed: {
  223. parsedRemarkDict() {
  224. try {
  225. const remarkDict = this.clueDetailVo.remarkDict
  226. if (!remarkDict) {
  227. return null
  228. }
  229. if (typeof remarkDict === 'string') {
  230. const parsed = JSON.parse(remarkDict)
  231. return typeof parsed === 'object' && parsed !== null ? parsed : null
  232. }
  233. return typeof remarkDict === 'object' && remarkDict !== null ? remarkDict : null
  234. } catch (error) {
  235. console.error('解析remarkDict失败:', error)
  236. return null
  237. }
  238. },
  239. corporateStatusLabel() {
  240. if (!this.clueDetail.corporateStatus || !this.crmCorporateStatusDict.length) return this.clueDetail.corporateStatus || ''
  241. const item = this.crmCorporateStatusDict.find(d => d.dictValue === this.clueDetail.corporateStatus)
  242. return item ? item.dictLabel : this.clueDetail.corporateStatus
  243. },
  244. genderTypeCodeLabel() {
  245. if (!this.clueDetail.genderTypeCode || !this.sysUserSexDict.length) return this.clueDetail.genderTypeCode || ''
  246. const item = this.sysUserSexDict.find(d => d.dictValue === this.clueDetail.genderTypeCode)
  247. return item ? item.dictLabel : this.clueDetail.genderTypeCode
  248. },
  249. hasEditPermission() {
  250. if (this.params?.type === '2') return true;
  251. if (this.params?.type === '1') return checkPermi(['crm:PublicClue:detail:edit']);
  252. return false;
  253. }
  254. },
  255. methods: {
  256. onDateChange(e) {
  257. const val = e.detail && e.detail.value;
  258. if (val) {
  259. this.$set(this.clueDetail, 'date', val);
  260. this.updateClueMainInfo();
  261. }
  262. },
  263. async updateClueMainInfo() {
  264. if (!this.clueDetail.name) {
  265. uni.$u.toast("姓名不能为空");
  266. return;
  267. }
  268. if (!isEqual(this.cloneClueDetail, this.clueDetail)) {
  269. await uni.$u.api.updateClueMainInfo(this.clueDetail);
  270. uni.$u.toast("修改成功");
  271. this.getData();
  272. }
  273. },
  274. async getData() {
  275. const {
  276. data
  277. } = await uni.$u.api.getClueMainInfoById({
  278. id: this.clueId
  279. });
  280. if (data.telephone && data.telephone.length >= 7) {
  281. const phone = data.telephone.substring(3, data.telephone.length - 4);
  282. data.dsPhone = data.telephone.replace(phone, '****');
  283. } else {
  284. data.dsPhone = data.telephone || '';
  285. }
  286. this.phoneEditing = false;
  287. this.clueDetail = data;
  288. this.cloneClueDetail = cloneDeep(data);
  289. },
  290. onPhoneBlur() {
  291. this.phoneEditing = false;
  292. this.updateClueMainInfo();
  293. }
  294. },
  295. data() {
  296. return {
  297. cloneClueDetail: {},
  298. clueDetail: {},
  299. phoneEditing: false,
  300. crmCorporateStatusDict: [],
  301. sysUserSexDict: [],
  302. caseInfoColumn,
  303. }
  304. },
  305. created() {
  306. this.getData();
  307. this.$getDicts('crm_corporate_status').then(res => {
  308. this.crmCorporateStatusDict = res || [];
  309. });
  310. this.$getDicts('sys_user_sex').then(res => {
  311. this.sysUserSexDict = res || [];
  312. });
  313. }
  314. }
  315. </script>
  316. <style lang="scss" scoped>
  317. .caseInfo_wrap {
  318. min-height: 400px;
  319. .caseInfo_title {
  320. display: flex;
  321. align-items: center;
  322. height: 80rpx;
  323. background: #f4f4f6;
  324. padding-left: 40rpx;
  325. image {
  326. width: 24rpx;
  327. height: 24rpx;
  328. margin-right: 10rpx;
  329. }
  330. .info_text {
  331. font-size: 24rpx;
  332. color: #202020;
  333. }
  334. .tabs_wrap {
  335. display: flex;
  336. .tab_item {
  337. margin-left: 30rpx;
  338. }
  339. }
  340. }
  341. .caseInfo_main_wrap {
  342. .caseCards_list_wrap {
  343. background: #ebf6ff;
  344. margin-bottom: 10rpx;
  345. margin-left: 10rpx;
  346. margin-right: 10rpx;
  347. }
  348. .Info_item {
  349. padding: 18rpx 40rpx;
  350. display: flex;
  351. justify-content: space-between;
  352. .label {
  353. color: #999999;
  354. font-size: 26rpx;
  355. }
  356. .input_wrap {
  357. border-bottom: 1px solid #ddd;
  358. }
  359. .picker_value {
  360. text-align: right;
  361. padding-right: 10px;
  362. font-size: 26rpx;
  363. color: #202020;
  364. }
  365. .phone_display {
  366. text-align: right;
  367. font-size: 26rpx;
  368. color: #202020;
  369. }
  370. .corporate_select {
  371. flex: 1;
  372. display: flex;
  373. justify-content: flex-end;
  374. min-width: 0;
  375. border-bottom: none;
  376. }
  377. .corporate_select_inner {
  378. display: inline-block;
  379. border-bottom: 1px solid #ddd;
  380. min-width: 120rpx;
  381. }
  382. .corporate_select .value {
  383. text-align: right;
  384. }
  385. ::v-deep .corporate_select .main,
  386. ::v-deep .corporate_select .ldSelectInput {
  387. justify-content: flex-end;
  388. text-align: right;
  389. }
  390. ::v-deep .corporate_select input {
  391. text-align: right !important;
  392. }
  393. .value {
  394. font-size: 26rpx;
  395. color: #202020;
  396. border-radius: 100rpx;
  397. }
  398. .highlight {
  399. font-size: 22rpx;
  400. font-weight: 900;
  401. padding: 6rpx 20rpx;
  402. }
  403. }
  404. }
  405. }
  406. </style>