customerDetails.vue 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775
  1. <template>
  2. <div class="customer-detail-container" v-if="item!=null">
  3. <!-- 顶部客户信息卡片 -->
  4. <div class="customer-header-card">
  5. <div class="customer-avatar">
  6. <el-avatar :size="64" icon="el-icon-user-solid"></el-avatar>
  7. </div>
  8. <div class="customer-info">
  9. <div class="customer-main-info">
  10. <h2 class="customer-name-title">
  11. {{ showDuplicate ? item.customerName + '[从]' : item.customerName }}
  12. <el-tag v-if="item.status" :type="getStatusType(item.status)" size="medium"
  13. style="margin-left: 12px;"
  14. >
  15. {{ getStatusText(item.status) }}
  16. </el-tag>
  17. </h2>
  18. <div class="customer-meta">
  19. <span class="meta-item">
  20. <i class="el-icon-phone"></i>
  21. {{ item.mobile || '未填写' }}
  22. <el-button type="text" v-if="isReceive" size="mini"
  23. @click="callNumber(item.customerId,null,null,null)" icon="el-icon-phone-outline"
  24. >拨打</el-button>
  25. <el-button type="text" v-if="isReceive" size="mini" @click="handleSms(item.mobile)"
  26. icon="el-icon-message"
  27. >短信</el-button>
  28. </span>
  29. <span class="meta-item">
  30. <i class="el-icon-location"></i>
  31. {{ item.address || '未填写' }}
  32. </span>
  33. <span class="meta-item">
  34. <i class="el-icon-time"></i>
  35. 创建于 {{ item.createTime }}
  36. </span>
  37. </div>
  38. </div>
  39. <div class="customer-actions">
  40. <el-button type="primary" size="small" icon="el-icon-edit" v-hasPermi="['crm:customer:edit']"
  41. @click="handleEdit()"
  42. >编辑客户
  43. </el-button>
  44. <el-button type="warning" size="small" icon="el-icon-price-tag" v-hasPermi="['crm:customer:addTag']"
  45. @click="handleAddTag()"
  46. >打标签
  47. </el-button>
  48. <el-button type="info" size="small" icon="el-icon-document" v-hasPermi="['crm:customer:addRemark']"
  49. @click="handleAddRemark()"
  50. >备注
  51. </el-button>
  52. <el-button size="small" v-if="showDuplicate" v-hasPermi="['crm:customer:lookDuplicate']"
  53. @click="getDetails"
  54. >
  55. 主客户
  56. </el-button>
  57. </div>
  58. </div>
  59. </div>
  60. <!-- 标签展示区 -->
  61. <div class="customer-tags-section" v-if="item && item.tags">
  62. <span class="tags-label">客户标签:</span>
  63. <el-tag v-for="tag in item.tags.split(',')" :key="tag" size="small" effect="plain"
  64. style="margin-right: 8px;"
  65. >
  66. {{ tag }}
  67. </el-tag>
  68. </div>
  69. <!-- 信息分组展示 -->
  70. <div class="info-sections">
  71. <!-- 基本信息 -->
  72. <div class="info-section">
  73. <div class="section-header">
  74. <span class="section-title"><i class="el-icon-user"></i> 基本信息</span>
  75. </div>
  76. <div class="section-content">
  77. <el-row :gutter="24">
  78. <el-col :span="6">
  79. <div class="info-item">
  80. <span class="info-label">客户编号</span>
  81. <span class="info-value">{{ item.customerCode || '-' }}</span>
  82. </div>
  83. </el-col>
  84. <el-col :span="6">
  85. <div class="info-item">
  86. <span class="info-label">客户名称</span>
  87. <span class="info-value">{{ item.customerName || '-' }}</span>
  88. </div>
  89. </el-col>
  90. <el-col :span="6">
  91. <div class="info-item">
  92. <span class="info-label">性别</span>
  93. <span class="info-value">
  94. <el-tag v-for="dict in sexOptions" :key="dict.dictValue"
  95. v-if="item.sex==dict.dictValue" size="mini"
  96. >{{ dict.dictLabel }}</el-tag>
  97. <span v-if="!item.sex">-</span>
  98. </span>
  99. </div>
  100. </el-col>
  101. <el-col :span="6">
  102. <div class="info-item">
  103. <span class="info-label">微信号</span>
  104. <span class="info-value">{{ item.weixin || '-' }}</span>
  105. </div>
  106. </el-col>
  107. </el-row>
  108. <el-row :gutter="24">
  109. <el-col :span="6">
  110. <div class="info-item">
  111. <span class="info-label">客户来源</span>
  112. <span class="info-value">
  113. <el-tag v-for="dict in sourceOptions" :key="dict.dictValue"
  114. v-if="item.source==dict.dictValue" size="mini" type="info"
  115. >{{ dict.dictLabel }}</el-tag>
  116. <span v-if="!item.source">-</span>
  117. </span>
  118. </div>
  119. </el-col>
  120. <el-col :span="6">
  121. <div class="info-item">
  122. <span class="info-label">客户类型</span>
  123. <span class="info-value">
  124. <el-tag v-for="dict in typeOptions" :key="dict.dictValue"
  125. v-if="item.customerType==dict.dictValue" size="mini" type="warning"
  126. >{{ dict.dictLabel }}</el-tag>
  127. <span v-if="!item.customerType">-</span>
  128. </span>
  129. </div>
  130. </el-col>
  131. <el-col :span="6">
  132. <div class="info-item">
  133. <span class="info-label">最后一次跟进</span>
  134. <span class="info-value">{{ item.visitTime || '-' }}</span>
  135. </div>
  136. </el-col>
  137. <el-col :span="6">
  138. <div class="info-item">
  139. <span class="info-label">入公海时间</span>
  140. <span class="info-value">{{ item.poolTime || '-' }}</span>
  141. </div>
  142. </el-col>
  143. </el-row>
  144. </div>
  145. </div>
  146. <!-- 购买信息 -->
  147. <div class="info-section">
  148. <div class="section-header">
  149. <span class="section-title"><i class="el-icon-shopping-cart-2"></i> 购买信息</span>
  150. </div>
  151. <div class="section-content">
  152. <el-row :gutter="24">
  153. <el-col :span="6">
  154. <div class="info-item">
  155. <span class="info-label">购买渠道</span>
  156. <span class="info-value">{{ item.platformName || '-' }}</span>
  157. </div>
  158. </el-col>
  159. <el-col :span="6">
  160. <div class="info-item">
  161. <span class="info-label">购买商品</span>
  162. <span class="info-value">{{ item.goodsName || '-' }} {{
  163. item.goodsSpecification || ''
  164. }}</span>
  165. </div>
  166. </el-col>
  167. <el-col :span="6">
  168. <div class="info-item">
  169. <span class="info-label">购买店铺</span>
  170. <span class="info-value">{{ item.shopName || '-' }}</span>
  171. </div>
  172. </el-col>
  173. <el-col :span="6">
  174. <div class="info-item">
  175. <span class="info-label">消费金额</span>
  176. <span class="info-value highlight">¥{{ item.payMoney || '0' }}</span>
  177. </div>
  178. </el-col>
  179. </el-row>
  180. <el-row :gutter="24">
  181. <el-col :span="6">
  182. <div class="info-item">
  183. <span class="info-label">购买次数</span>
  184. <span class="info-value">{{ item.buyCount || '0' }} 次</span>
  185. </div>
  186. </el-col>
  187. <el-col :span="6">
  188. <div class="info-item">
  189. <span class="info-label">来源渠道编码</span>
  190. <span class="info-value">{{ item.sourceCode || '-' }}</span>
  191. </div>
  192. </el-col>
  193. <el-col :span="6">
  194. <div class="info-item">
  195. <span class="info-label">推荐编码</span>
  196. <span class="info-value">{{ item.pushCode || '-' }}</span>
  197. </div>
  198. </el-col>
  199. <el-col :span="6">
  200. <div class="info-item">
  201. <span class="info-label">推荐时间</span>
  202. <span class="info-value">{{ item.pushTime || '-' }}</span>
  203. </div>
  204. </el-col>
  205. </el-row>
  206. </div>
  207. </div>
  208. <!-- 进线信息 -->
  209. <div class="info-section">
  210. <div class="section-header">
  211. <span class="section-title"><i class="el-icon-connection"></i> 进线信息</span>
  212. </div>
  213. <div class="section-content">
  214. <el-row :gutter="24">
  215. <el-col :span="6">
  216. <div class="info-item">
  217. <span class="info-label">进线日期</span>
  218. <span class="info-value">{{ item.registerDate || '-' }}</span>
  219. </div>
  220. </el-col>
  221. <el-col :span="6">
  222. <div class="info-item">
  223. <span class="info-label">进线方式</span>
  224. <span class="info-value">{{ item.registerType || '-' }}</span>
  225. </div>
  226. </el-col>
  227. <el-col :span="6">
  228. <div class="info-item">
  229. <span class="info-label">进线填写时间</span>
  230. <span class="info-value">{{ item.registerSubmitTime || '-' }}</span>
  231. </div>
  232. </el-col>
  233. <el-col :span="6">
  234. <div class="info-item">
  235. <span class="info-label">进线链接</span>
  236. <span class="info-value text-ellipsis" :title="item.registerLinkUrl">{{
  237. item.registerLinkUrl || '-'
  238. }}</span>
  239. </div>
  240. </el-col>
  241. </el-row>
  242. <el-row :gutter="24">
  243. <el-col :span="24">
  244. <div class="info-item">
  245. <span class="info-label">进线客户详情</span>
  246. <span class="info-value">{{ item.registerDesc || '-' }}</span>
  247. </div>
  248. </el-col>
  249. </el-row>
  250. </div>
  251. </div>
  252. <!-- 扩展信息 -->
  253. <div class="info-section" v-if="exts && exts.length > 0">
  254. <div class="section-header">
  255. <span class="section-title"><i class="el-icon-tickets"></i> 扩展信息</span>
  256. </div>
  257. <div class="section-content">
  258. <el-row :gutter="24">
  259. <el-col :span="6" v-for="ext in exts" :key="ext.extId">
  260. <div class="info-item">
  261. <span class="info-label">{{ ext.name }}</span>
  262. <span class="info-value">{{ ext.value || '-' }}</span>
  263. </div>
  264. </el-col>
  265. </el-row>
  266. </div>
  267. </div>
  268. <!-- 备注信息 -->
  269. <div class="info-section" v-if="item.remark">
  270. <div class="section-header">
  271. <span class="section-title"><i class="el-icon-document"></i> 备注信息</span>
  272. </div>
  273. <div class="section-content">
  274. <div class="remark-content">{{ item.remark }}</div>
  275. </div>
  276. </div>
  277. <!-- AI标签 -->
  278. <div class="info-section">
  279. <div class="ai-tag-wrapper" v-if="item && item.customerId">
  280. <ai-tag-panel ref="aiTagPanel" :customer-id="item.customerId" @tag-change="handleTagChange"
  281. ></ai-tag-panel>
  282. </div>
  283. </div>
  284. </div>
  285. <!-- Tab 和 AI 标签区域 -->
  286. <div class="content-area">
  287. <el-tabs v-model="activeName" type="card" @tab-click="handleClick" class="customer-tabs">
  288. <el-tab-pane label="跟进记录" name="visit">
  289. <customer-visit-list ref="visit"></customer-visit-list>
  290. </el-tab-pane>
  291. <el-tab-pane label="联系人" name="contacts">
  292. <customer-contacts ref="contacts"></customer-contacts>
  293. </el-tab-pane>
  294. <el-tab-pane label="订单记录" name="storeOrder">
  295. <customer-store-order-list ref="storeOrder"></customer-store-order-list>
  296. </el-tab-pane>
  297. <el-tab-pane label="通话记录" name="voiceLogs">
  298. <customer-voice-logs-list ref="voiceLogs"></customer-voice-logs-list>
  299. </el-tab-pane>
  300. <el-tab-pane label="短信记录" name="smsLogs">
  301. <customer-sms-logs-list ref="smsLogs"></customer-sms-logs-list>
  302. </el-tab-pane>
  303. <el-tab-pane label="客户日志" name="logs">
  304. <customer-logs-list ref="logs"></customer-logs-list>
  305. </el-tab-pane>
  306. <el-tab-pane label="历史订单" name="hisOrder">
  307. <customer-his-order-list ref="hisOrder"></customer-his-order-list>
  308. </el-tab-pane>
  309. <el-tab-pane label="AI 通话" name="aiVoiceLogs">
  310. <ai-call-voice-log ref="aiVoiceRef"></ai-call-voice-log>
  311. </el-tab-pane>
  312. <el-tab-pane label="AI 加微" name="aiAddWxLogs">
  313. <ai-add-wx-log ref="aiAddWxRef"></ai-add-wx-log>
  314. </el-tab-pane>
  315. <el-tab-pane label="AI 短信" name="aiSendMsgLogs">
  316. <ai-send-msg-log ref="aiSendMsgRef"></ai-send-msg-log>
  317. </el-tab-pane>
  318. </el-tabs>
  319. </div>
  320. <!-- 弹窗 -->
  321. <el-dialog :title="addTag.title" :visible.sync="addTag.open" width="600px" append-to-body :show-close="true" class="customer-dialog">
  322. <add-tag ref="tag" @close="closeTag()"></add-tag>
  323. <span slot="footer" class="dialog-footer">
  324. <el-button @click="addTag.open = false">取 消</el-button>
  325. <el-button type="primary" @click="$refs.tag.submitForm()">确 定</el-button>
  326. </span>
  327. </el-dialog>
  328. <el-dialog :title="addRemark.title" :visible.sync="addRemark.open" width="600px" append-to-body :show-close="true" class="customer-dialog">
  329. <add-remark ref="remark" @close="closeRemark()"></add-remark>
  330. <span slot="footer" class="dialog-footer">
  331. <el-button @click="addRemark.open = false">取 消</el-button>
  332. <el-button type="primary" @click="$refs.remark.submitForm()">确 定</el-button>
  333. </span>
  334. </el-dialog>
  335. <el-dialog :title="addSms.title" :visible.sync="addSms.open" width="800px" append-to-body class="customer-dialog">
  336. <add-sms ref="sms" @close="closeSms()"></add-sms>
  337. </el-dialog>
  338. <el-dialog :title="customer.title" :visible.sync="customer.open" width="1000px" append-to-body :show-close="true" class="customer-dialog">
  339. <add-or-edit-customer ref="customer" @close="closeCustomer()"></add-or-edit-customer>
  340. <span slot="footer" class="dialog-footer">
  341. <el-button @click="customer.open = false">取 消</el-button>
  342. <el-button type="primary" @click="$refs.customer.submitForm()">确 定</el-button>
  343. </span>
  344. </el-dialog>
  345. <el-drawer size="75%" :modal="false" :title="duplicate.title" :visible.sync="duplicate.open">
  346. <duplicate-customer ref="duplicateCustomer"/>
  347. </el-drawer>
  348. </div>
  349. </template>
  350. <script>
  351. import { listCustomerExt } from '@/api/crm/customerExt'
  352. import customerVisitList from '../components/customerVisitList.vue'
  353. import customerLogsList from '../components/customerLogsList.vue'
  354. import customerSmsLogsList from '../components/customerSmsLogsList.vue'
  355. import customerVoiceLogsList from '../components/customerVoiceLogsList.vue'
  356. import customerStoreOrderList from '../components/customerStoreOrderList.vue'
  357. import duplicateCustomer from '../components/duplicateCustomer.vue'
  358. import customerContacts from './customerContacts.vue'
  359. import customerHisOrderList from '../components/customerHisOrderList.vue'
  360. import aiCallVoiceLog from './aiCallVoiceLog'
  361. import aiAddWxLog from './aiAddWxLog'
  362. import aiSendMsgLog from './aiSendMsgLog'
  363. import AiTagPanel from './AiTagPanel.vue'
  364. import { getCustomerDetails1, updateCustomer, getCustomer1 } from '@/api/crm/customer'
  365. import addTag from './addTag.vue'
  366. import addRemark from './addRemark.vue'
  367. import addSms from './addSms.vue'
  368. import addOrEditCustomer from '../components/addOrEditCustomer.vue'
  369. export default {
  370. name: 'customer',
  371. components: {
  372. customerHisOrderList,
  373. addOrEditCustomer,
  374. addSms,
  375. addTag,
  376. addRemark,
  377. customerContacts,
  378. customerVisitList,
  379. customerLogsList,
  380. customerVoiceLogsList,
  381. customerStoreOrderList,
  382. customerSmsLogsList,
  383. duplicateCustomer,
  384. aiCallVoiceLog,
  385. aiAddWxLog,
  386. aiSendMsgLog,
  387. AiTagPanel
  388. },
  389. data() {
  390. return {
  391. calleesId: null,
  392. roboticId: null,
  393. customer: { open: false, title: '修改客户' },
  394. isReceive: false,
  395. tagId: null,
  396. tagsOptions: [],
  397. addSms: { open: false, title: '发短信' },
  398. addTag: { open: false, title: '打标签' },
  399. addRemark: { open: false, title: '客户备注' },
  400. duplicate: { open: false, title: '客户详情' },
  401. customerId: null,
  402. title: '',
  403. open: false,
  404. cityIds: [],
  405. citys: [],
  406. tags: [],
  407. inputVisible: false,
  408. inputValue: '',
  409. receiveOptions: [],
  410. statusOptions: [],
  411. typeOptions: [],
  412. sourceOptions: [],
  413. sexOptions: [],
  414. customerExts: [],
  415. activeName: '',
  416. item: null,
  417. showDuplicate: false,
  418. dCustomerId: null
  419. }
  420. },
  421. created() {
  422. this.getDicts('crm_customer_source').then((response) => {
  423. this.sourceOptions = response.data
  424. })
  425. this.getDicts('sys_sex').then((response) => {
  426. this.sexOptions = response.data
  427. })
  428. this.getDicts('crm_customer_tag').then((response) => {
  429. this.tagsOptions = response.data
  430. })
  431. this.getDicts('crm_customer_status').then((response) => {
  432. this.statusOptions = response.data
  433. })
  434. this.getDicts('crm_customer_type').then((response) => {
  435. this.typeOptions = response.data
  436. })
  437. this.getDicts('crm_customer_is_receive').then((response) => {
  438. this.receiveOptions = response.data
  439. })
  440. },
  441. methods: {
  442. getStatusType(status) {
  443. const statusMap = { '1': 'success', '2': 'warning', '3': 'danger', '4': 'info' }
  444. return statusMap[status] || 'info'
  445. },
  446. getStatusText(status) {
  447. const item = this.statusOptions.find(d => d.dictValue === status)
  448. return item ? item.dictLabel : status
  449. },
  450. handleMobile() {
  451. const customerId = this.item.customerId
  452. getCustomer1(customerId).then(response => {
  453. this.item.mobile = response.mobile
  454. })
  455. },
  456. handleEdit() {
  457. this.customer.open = true
  458. var that = this
  459. setTimeout(() => {
  460. that.$refs.customer.handleUpdate(that.customerId)
  461. }, 200)
  462. },
  463. closeCustomer() {
  464. this.customer.open = false
  465. this.getDetails(this.customerId)
  466. },
  467. tagsChange(e) {
  468. var item = this.tagsOptions.find(val => val.dictValue === e)
  469. this.tags.push(item.dictLabel)
  470. this.form.tags = this.tags.toString()
  471. },
  472. closeSms() {
  473. this.addSms.open = false
  474. this.getDetails(this.customerId)
  475. },
  476. handleSms(mobile) {
  477. this.addSms.open = true
  478. var that = this
  479. setTimeout(() => {
  480. that.$refs.sms.reset(this.item.customerId, mobile, 1)
  481. }, 500)
  482. },
  483. closeRemark() {
  484. this.addRemark.open = false
  485. this.getDetails(this.customerId)
  486. },
  487. handleAddRemark() {
  488. this.addRemark.open = true
  489. var that = this
  490. setTimeout(() => {
  491. that.$refs.remark.reset(this.item)
  492. }, 500)
  493. },
  494. closeTag() {
  495. this.addTag.open = false
  496. this.getDetails(this.customerId)
  497. },
  498. handleAddTag() {
  499. this.addTag.open = true
  500. var that = this
  501. setTimeout(() => {
  502. that.$refs.tag.reset(this.item)
  503. }, 500)
  504. },
  505. handleClick(tab, event) {
  506. if (tab.name == 'contacts') {
  507. this.$refs.contacts.getData(this.item.customerId)
  508. }
  509. if (tab.name == 'visit') {
  510. this.$refs.visit.getData(this.item.customerId, this.isReceive)
  511. }
  512. if (tab.name == 'logs') {
  513. this.$refs.logs.getData(this.item.customerId)
  514. }
  515. if (tab.name == 'voiceLogs') {
  516. this.$refs.voiceLogs.getData(this.item.customerId)
  517. }
  518. if (tab.name == 'storeOrder') {
  519. this.$refs.storeOrder.getData(this.item.customerId)
  520. }
  521. if (tab.name == 'smsLogs') {
  522. this.$refs.smsLogs.getData(this.item.customerId)
  523. }
  524. if (tab.name == 'hisOrder') {
  525. this.$refs.hisOrder.getData(this.item.customerId)
  526. }
  527. if (tab.name == 'aiVoiceLogs') {
  528. this.$refs.aiVoiceRef.getData(this.item.customerId, this.calleesId)
  529. } else if (tab.name == 'aiAddWxLogs') {
  530. this.$refs.aiAddWxRef.getData(this.item.customerId, this.roboticId)
  531. } else if (tab.name == 'aiSendMsgLogs') {
  532. this.$refs.aiSendMsgRef.getData(this.item.customerId, this.calleesId, this.roboticId)
  533. }
  534. },
  535. getDetails(customerId, calleesId, roboticId) {
  536. if (!!calleesId) {
  537. this.calleesId = calleesId
  538. }
  539. if (!!roboticId) {
  540. this.roboticId = roboticId
  541. }
  542. var data = { customerId: customerId }
  543. this.customerId = customerId
  544. var that = this
  545. this.exts = []
  546. listCustomerExt(data).then(response => {
  547. this.customerExts = response.data
  548. this.customerExts.forEach(element => {
  549. var data = { extId: element.extId, name: element.name, value: '' }
  550. this.exts.push(data)
  551. })
  552. })
  553. getCustomerDetails1(data).then(response => {
  554. this.item = response.customer
  555. this.isReceive = response.isReceive
  556. if (this.item && this.item.extJson != null) {
  557. var extList = JSON.parse(this.item.extJson)
  558. that.exts.forEach(item => {
  559. extList.forEach(element => {
  560. if (item.extId == element.extId) {
  561. item.value = element.value
  562. }
  563. })
  564. })
  565. }
  566. if (this.item) {
  567. this.activeName = 'visit'
  568. setTimeout(() => {
  569. that.$refs.visit.getData(customerId)
  570. }, 500)
  571. }
  572. })
  573. },
  574. initDuplicate(isDuplicate, dCustomerId) {
  575. this.showDuplicate = isDuplicate
  576. this.dCustomerId = dCustomerId
  577. },
  578. handleDuplicate() {
  579. this.duplicate.open = true
  580. var that = this
  581. setTimeout(() => {
  582. that.$refs.duplicateCustomer.getDetails(that.dCustomerId)
  583. }, 200)
  584. },
  585. closeDuplicate() {
  586. this.duplicate.open = false
  587. this.getDetails(this.customerId)
  588. },
  589. handleTagChange() {
  590. console.log('AI 标签发生变化')
  591. }
  592. }
  593. }
  594. </script>
  595. <style lang="scss" scoped>
  596. .customer-detail-container {
  597. background-color: #f5f7fa;
  598. min-height: 100%;
  599. padding: 20px;
  600. }
  601. .customer-header-card {
  602. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  603. border-radius: 12px;
  604. padding: 24px;
  605. display: flex;
  606. align-items: center;
  607. margin-bottom: 20px;
  608. box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
  609. .customer-avatar {
  610. margin-right: 20px;
  611. }
  612. .customer-info {
  613. flex: 1;
  614. display: flex;
  615. justify-content: space-between;
  616. align-items: center;
  617. }
  618. .customer-main-info {
  619. .customer-name-title {
  620. color: #fff;
  621. font-size: 24px;
  622. margin: 0 0 12px 0;
  623. display: flex;
  624. align-items: center;
  625. }
  626. .customer-meta {
  627. display: flex;
  628. gap: 24px;
  629. .meta-item {
  630. color: rgba(255, 255, 255, 0.9);
  631. font-size: 14px;
  632. display: flex;
  633. align-items: center;
  634. gap: 6px;
  635. i {
  636. font-size: 16px;
  637. }
  638. .el-button {
  639. color: rgba(255, 255, 255, 0.9);
  640. padding: 0 8px;
  641. &:hover {
  642. color: #fff;
  643. }
  644. }
  645. }
  646. }
  647. }
  648. .customer-actions {
  649. display: flex;
  650. gap: 12px;
  651. }
  652. }
  653. .customer-tags-section {
  654. background: #fff;
  655. border-radius: 8px;
  656. padding: 16px 20px;
  657. margin-bottom: 20px;
  658. display: flex;
  659. align-items: center;
  660. .tags-label {
  661. font-weight: 500;
  662. color: #606266;
  663. margin-right: 12px;
  664. }
  665. }
  666. .info-sections {
  667. margin-bottom: 20px;
  668. }
  669. .info-section {
  670. background: #fff;
  671. border-radius: 8px;
  672. margin-bottom: 16px;
  673. overflow: hidden;
  674. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
  675. .section-header {
  676. padding: 16px 20px;
  677. border-bottom: 1px solid #ebeef5;
  678. background: #fafbfc;
  679. .section-title {
  680. font-size: 15px;
  681. font-weight: 600;
  682. color: #303133;
  683. display: flex;
  684. align-items: center;
  685. gap: 8px;
  686. i {
  687. color: #409eff;
  688. }
  689. }
  690. }
  691. .section-content {
  692. padding: 20px;
  693. }
  694. }
  695. .info-item {
  696. margin-bottom: 16px;
  697. .info-label {
  698. display: block;
  699. font-size: 12px;
  700. color: #909399;
  701. margin-bottom: 6px;
  702. }
  703. .info-value {
  704. font-size: 14px;
  705. color: #303133;
  706. &.highlight {
  707. color: #f56c6c;
  708. font-weight: 600;
  709. font-size: 16px;
  710. }
  711. &.text-ellipsis {
  712. overflow: hidden;
  713. text-overflow: ellipsis;
  714. white-space: nowrap;
  715. }
  716. }
  717. }
  718. .remark-content {
  719. background: #fafbfc;
  720. padding: 16px;
  721. border-radius: 6px;
  722. color: #606266;
  723. line-height: 1.6;
  724. }
  725. .content-area {
  726. .customer-tabs {
  727. background: #fff;
  728. border-radius: 8px;
  729. padding: 16px;
  730. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
  731. }
  732. .ai-tag-wrapper {
  733. position: sticky;
  734. top: 20px;
  735. }
  736. }
  737. </style>