client-detail.vue 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500
  1. <template>
  2. <view class="client-detail" :class="{ 'dark-mode': isDarkTheme }">
  3. <!-- 顶部导航栏 -->
  4. <view class="header">
  5. <view class="back-icon" @click="handleBack"></view>
  6. <text class="header-title">客户信息</text>
  7. <view class="header-right"></view>
  8. </view>
  9. <!-- 客户基本信息 -->
  10. <view class="client-basic-info">
  11. <image :src="clientInfo.avatar" class="client-avatar" mode="aspectFill"></image>
  12. <view class="basic-info">
  13. <view class="client-name-wrapper">
  14. <text class="client-name">{{ clientInfo.name }}</text>
  15. <text class="gender-tag">{{ clientInfo.gender }}</text>
  16. </view>
  17. <view class="status-tag-wrapper">
  18. <text class="status-tag register-tag registered">已注册</text>
  19. <text class="status-tag match-tag" :class="{ 'matched': followForm.matchStatus === 1, 'unmatched': followForm.matchStatus === 0 || followForm.matchStatus === null }">
  20. {{ followForm.matchStatus === 1 ? '已匹配' : '未匹配' }}
  21. </text>
  22. </view>
  23. <view class="client-tags">
  24. <text class="tag" v-for="(tag, index) in clientInfo.tags" :key="index">{{ tag }}</text>
  25. </view>
  26. </view>
  27. </view>
  28. <!-- 择偶要求 -->
  29. <view class="requirement-section">
  30. <view class="section-title">
  31. <text class="title-icon">💑</text>
  32. <text class="title-text">择偶要求</text>
  33. </view>
  34. <text class="requirement-content">{{ clientInfo.requirement }}</text>
  35. </view>
  36. <!-- 联系方式 -->
  37. <view class="contact-section">
  38. <view class="contact-item">
  39. <text class="contact-label">联系方式</text>
  40. <view class="contact-value">
  41. <text class="contact-number">{{ clientInfo.contact }}</text>
  42. <text class="copy-btn" @click="handleCopy(clientInfo.contact)">复制</text>
  43. </view>
  44. </view>
  45. <view class="contact-item" v-if="clientInfo.backupContact">
  46. <text class="contact-label">备用联系方式</text>
  47. <view class="contact-value">
  48. <text class="contact-number">{{ clientInfo.backupContact }}</text>
  49. <text class="copy-btn" @click="handleCopy(clientInfo.backupContact)">复制</text>
  50. </view>
  51. </view>
  52. </view>
  53. <!-- 跟进统计 -->
  54. <view class="stats-section">
  55. <text class="stats-title">跟进统计</text>
  56. <view class="stats-grid">
  57. <view class="stat-item">
  58. <text class="stat-value">{{ clientInfo.stats.followTimes }}</text>
  59. <text class="stat-label">跟进次数</text>
  60. </view>
  61. <view class="stat-item">
  62. <text class="stat-value">{{ clientInfo.stats.matchCount }}</text>
  63. <text class="stat-label">匹配对象</text>
  64. </view>
  65. <view class="stat-item">
  66. <text class="stat-value">{{ clientInfo.stats.phoneCalls }}</text>
  67. <text class="stat-label">电话沟通</text>
  68. </view>
  69. <view class="stat-item">
  70. <text class="stat-value">{{ clientInfo.stats.interviews }}</text>
  71. <text class="stat-label">面谈次数</text>
  72. </view>
  73. </view>
  74. </view>
  75. <!-- 标签切换 -->
  76. <view class="tabs-section">
  77. <text class="tab" :class="{ active: activeTab === 'details' }" @click="switchTab('details')">详细信息</text>
  78. <text class="tab" :class="{ active: activeTab === 'follow' }" @click="switchTab('follow')">跟进</text>
  79. </view>
  80. <!-- 详细信息 -->
  81. <scroll-view scroll-y class="details-section" v-if="activeTab === 'details'">
  82. <!-- 基本信息 -->
  83. <view class="detail-item">
  84. <text class="detail-label">婚姻状况:</text>
  85. <text class="detail-value">{{ clientInfo.details.maritalStatus }}</text>
  86. </view>
  87. <view class="detail-item">
  88. <text class="detail-label">学历:</text>
  89. <text class="detail-value">{{ clientInfo.details.education }}</text>
  90. </view>
  91. <view class="detail-item">
  92. <text class="detail-label">职业:</text>
  93. <text class="detail-value">{{ clientInfo.details.occupation }}</text>
  94. </view>
  95. <view class="detail-item">
  96. <text class="detail-label">收入:</text>
  97. <text class="detail-value">
  98. {{ clientInfo.details.income }}
  99. <text v-if="clientInfo.details.income && clientInfo.details.income !== '未知'">万元/年</text>
  100. </text>
  101. </view>
  102. <view class="detail-item">
  103. <text class="detail-label">购房情况:</text>
  104. <text class="detail-value">{{ clientInfo.details.housing }}</text>
  105. </view>
  106. <!-- 择偶要求详情 -->
  107. <view class="sub-section-title requirement-section-title">择偶详细要求</view>
  108. <view class="detail-item">
  109. <text class="detail-label">年龄范围:</text>
  110. <text class="detail-value">{{ clientInfo.details.requirement.ageRange }}</text>
  111. </view>
  112. <view class="detail-item">
  113. <text class="detail-label">身高要求:</text>
  114. <text class="detail-value">{{ clientInfo.details.requirement.height }}</text>
  115. </view>
  116. <view class="detail-item">
  117. <text class="detail-label">婚姻状况:</text>
  118. <text class="detail-value">{{ clientInfo.details.requirement.maritalStatus }}</text>
  119. </view>
  120. <view class="detail-item">
  121. <text class="detail-label">学历要求:</text>
  122. <text class="detail-value">{{ clientInfo.details.requirement.education }}</text>
  123. </view>
  124. <view class="detail-item" v-if="clientInfo.details.requirement.other">
  125. <text class="detail-label">其他要求:</text>
  126. <text class="detail-value">{{ clientInfo.details.requirement.other }}</text>
  127. </view>
  128. <!-- 客户标签 -->
  129. <view class="sub-section-title tags-section-title">客户标签</view>
  130. <view class="client-tags">
  131. <text class="tag" v-for="(tag, index) in clientInfo.tags" :key="index">{{ tag }}</text>
  132. </view>
  133. </scroll-view>
  134. <!-- 跟进信息 -->
  135. <scroll-view scroll-y class="follow-section" v-else>
  136. <!-- 跟进方式 -->
  137. <view class="follow-group">
  138. <text class="group-label">跟进方式</text>
  139. <view class="follow-type-options">
  140. <view class="follow-type-item" :class="{ active: followForm.type === 'phone' }" @click="followForm.type = 'phone'">
  141. <text class="radio-circle"></text>
  142. <text class="option-text">电话</text>
  143. </view>
  144. <view class="follow-type-item" :class="{ active: followForm.type === 'meet' }" @click="followForm.type = 'meet'">
  145. <text class="radio-circle"></text>
  146. <text class="option-text">面谈</text>
  147. </view>
  148. <view class="follow-type-item" :class="{ active: followForm.type === 'other' }" @click="followForm.type = 'other'">
  149. <text class="radio-circle"></text>
  150. <text class="option-text">其他</text>
  151. </view>
  152. </view>
  153. </view>
  154. <!-- 匹配状态 -->
  155. <view class="follow-group">
  156. <text class="group-label">匹配状态</text>
  157. <view class="status-tags-row">
  158. <view class="status-tag" :class="{ active: followForm.matchStatus === 0 }" @click="handleMatchStatusChange(0)">未匹配</view>
  159. <view class="status-tag" :class="{ active: followForm.matchStatus === 1 }" @click="handleMatchStatusChange(1)">已匹配</view>
  160. </view>
  161. </view>
  162. <!-- 跟进进度 -->
  163. <view class="follow-group">
  164. <text class="group-label">跟进进度</text>
  165. <view class="status-tags-row">
  166. <view class="status-tag" :class="{ active: followForm.progress === 'notMet' }" @click="followForm.progress = 'notMet'">未见面</view>
  167. <view class="status-tag" :class="{ active: followForm.progress === 'communicating' }" @click="followForm.progress = 'communicating'">沟通中</view>
  168. <view class="status-tag" :class="{ active: followForm.invitationStatus === 0 }" @click="handleInvitationStatusChange(0)">未邀约</view>
  169. <view class="status-tag" :class="{ active: followForm.invitationStatus === 1 }" @click="handleInvitationStatusChange(1)">已邀约</view>
  170. </view>
  171. </view>
  172. <!-- 邀约时间 -->
  173. <view class="follow-group">
  174. <text class="group-label">邀约时间</text>
  175. <view class="input-like">
  176. <picker mode="date" :value="followForm.inviteDate" @change="onInviteDateChange">
  177. <view class="picker-inner">{{ followForm.inviteDate || '请选择日期' }}</view>
  178. </picker>
  179. </view>
  180. </view>
  181. <!-- 跟进备注 -->
  182. <view class="follow-group">
  183. <text class="group-label">跟进备注</text>
  184. <textarea class="textarea" v-model="followForm.remark" placeholder="请输入跟进备注详细信息..." placeholder-style="color:#C5A4D8" />
  185. </view>
  186. <!-- 提交按钮(预留) -->
  187. <view class="follow-submit-btn" :class="{ 'disabled': !canFollow }" @click="handleSubmitFollow">保存跟进</view>
  188. </scroll-view>
  189. </view>
  190. </template>
  191. <script>
  192. export default {
  193. name: 'client-detail',
  194. data() {
  195. return {
  196. activeTab: 'details',
  197. followForm: {
  198. type: 'phone',
  199. progress: 'matched',
  200. matchStatus: 0, // 0-未匹配,1-已匹配
  201. invitationStatus: 0, // 0-未邀约,1-已邀约
  202. inviteDate: '',
  203. remark: ''
  204. },
  205. resourceId: null, // 资源ID
  206. fromQualityResources: false, // 是否从优质资源列表进入
  207. currentUserId: null, // 当前登录用户的用户ID
  208. currentMatchmakerId: null, // 当前红娘的matchmaker_id
  209. resourceMatchmakerId: null, // 资源的matchmaker_id
  210. canFollow: true, // 是否可以跟进
  211. clientInfo: {
  212. id: 1,
  213. name: '',
  214. gender: '男',
  215. status: '已匹配',
  216. tags: [],
  217. avatar: '',
  218. requirement: '暂无要求',
  219. contact: '',
  220. backupContact: '',
  221. originalPhone: '', // 保存原始手机号用于复制
  222. originalBackupPhone: '', // 保存原始备用手机号用于复制
  223. stats: {
  224. followTimes: 0,
  225. matchCount: 0,
  226. phoneCalls: 0,
  227. interviews: 0
  228. },
  229. details: {
  230. maritalStatus: '',
  231. education: '',
  232. occupation: '',
  233. income: '',
  234. housing: '',
  235. requirement: {
  236. ageRange: '',
  237. height: '',
  238. maritalStatus: '',
  239. education: '',
  240. other: ''
  241. }
  242. }
  243. },
  244. isDarkTheme: false
  245. }
  246. },
  247. onShow() {
  248. const stored = uni.getStorageSync('matchmakerDarkMode')
  249. if (stored === true || stored === false) {
  250. this.isDarkTheme = stored
  251. }
  252. // 如果已经有resourceId,刷新统计数据
  253. if (this.resourceId) {
  254. this.loadFollowStatistics(this.resourceId)
  255. }
  256. },
  257. onLoad(options) {
  258. console.log('=== 客户详情页面 onLoad ===')
  259. console.log('options:', JSON.stringify(options, null, 2))
  260. // 从URL参数获取资源ID
  261. let resourceId = null
  262. if (options.resourceId) {
  263. resourceId = options.resourceId
  264. console.log('从resourceId参数获取:', resourceId)
  265. } else if (options.id) {
  266. // 兼容旧的id参数
  267. resourceId = options.id
  268. console.log('从id参数获取 (兼容):', resourceId)
  269. }
  270. // 检查是否从优质资源列表进入
  271. if (options.fromQualityResources === '1' || options.fromQualityResources === 1) {
  272. this.fromQualityResources = true
  273. console.log('从优质资源列表进入')
  274. }
  275. // 获取当前登录用户的ID
  276. const userId = uni.getStorageSync('userId')
  277. if (userId) {
  278. const rawUserId = parseInt(userId)
  279. if (!isNaN(rawUserId) && rawUserId > 0) {
  280. this.currentUserId = rawUserId
  281. console.log('当前登录用户ID:', this.currentUserId)
  282. // 获取当前红娘的matchmaker_id
  283. this.getCurrentMatchmakerId()
  284. }
  285. }
  286. if (resourceId) {
  287. // 确保resourceId是数字类型
  288. resourceId = parseInt(resourceId)
  289. if (isNaN(resourceId) || resourceId <= 0) {
  290. console.error('资源ID无效:', resourceId)
  291. uni.showToast({
  292. title: '资源ID无效',
  293. icon: 'none'
  294. })
  295. return
  296. }
  297. console.log('最终使用的resourceId:', resourceId, '类型:', typeof resourceId)
  298. this.resourceId = resourceId
  299. this.loadClientInfo(resourceId)
  300. } else {
  301. console.error('未获取到资源ID')
  302. uni.showToast({
  303. title: '未获取到资源ID',
  304. icon: 'none'
  305. })
  306. }
  307. },
  308. methods: {
  309. // 返回上一页
  310. handleBack() {
  311. uni.navigateBack()
  312. },
  313. // 复制联系方式
  314. handleCopy(contact) {
  315. // 如果传入的是脱敏的手机号,使用原始手机号
  316. let phoneToCopy = contact
  317. if (contact === this.clientInfo.contact && this.clientInfo.originalPhone) {
  318. phoneToCopy = this.clientInfo.originalPhone
  319. } else if (contact === this.clientInfo.backupContact && this.clientInfo.originalBackupPhone) {
  320. phoneToCopy = this.clientInfo.originalBackupPhone
  321. } else {
  322. // 尝试从脱敏号码中提取
  323. phoneToCopy = contact.replace(/\*+/g, '')
  324. }
  325. uni.setClipboardData({
  326. data: phoneToCopy,
  327. success: () => {
  328. uni.showToast({
  329. title: '复制成功',
  330. icon: 'success'
  331. })
  332. },
  333. fail: () => {
  334. uni.showToast({
  335. title: '复制失败',
  336. icon: 'none'
  337. })
  338. }
  339. })
  340. },
  341. // 切换标签
  342. switchTab(tab) {
  343. this.activeTab = tab
  344. },
  345. // 邀约日期变更
  346. onInviteDateChange(e) {
  347. this.followForm.inviteDate = e.detail.value
  348. },
  349. // 匹配状态变更
  350. handleMatchStatusChange(status) {
  351. this.followForm.matchStatus = status
  352. },
  353. // 邀约状态变更
  354. handleInvitationStatusChange(status) {
  355. this.followForm.invitationStatus = status
  356. // 如果选择已邀约且没有设置邀约时间,提示用户选择
  357. if (status === 1 && !this.followForm.inviteDate) {
  358. uni.showToast({
  359. title: '请选择邀约时间',
  360. icon: 'none'
  361. })
  362. }
  363. },
  364. // 加载最新的跟进信息,更新表单字段
  365. async loadLatestFollowInfo(resourceId) {
  366. try {
  367. const baseUrl = process.env.NODE_ENV === 'development'
  368. ? 'http://localhost:8083/api' // 开发环境 - 通过网关
  369. : 'https://your-domain.com/api' // 生产环境
  370. // 获取最新的跟进记录
  371. const [error, res] = await uni.request({
  372. url: `${baseUrl}/my-resource/latest-follow/${resourceId}`,
  373. method: 'GET',
  374. header: {
  375. 'Content-Type': 'application/json'
  376. }
  377. })
  378. if (error) {
  379. console.error('加载最新跟进信息失败:', error)
  380. return
  381. }
  382. if (res.statusCode === 200 && res.data && res.data.code === 200) {
  383. const followUp = res.data.data
  384. if (followUp) {
  385. // 更新跟进方式(1-电话,2-面谈,3-其他)
  386. if (followUp.followType !== null && followUp.followType !== undefined) {
  387. const followType = followUp.followType
  388. if (followType === 1) {
  389. this.followForm.type = 'phone'
  390. } else if (followType === 2) {
  391. this.followForm.type = 'meet'
  392. } else if (followType === 3) {
  393. this.followForm.type = 'other'
  394. }
  395. }
  396. // 更新跟进进度(1-已匹配,2-未见面,3-沟通中)
  397. if (followUp.followProgress !== null && followUp.followProgress !== undefined) {
  398. const progress = followUp.followProgress
  399. if (progress === 1) {
  400. this.followForm.progress = 'matched'
  401. } else if (progress === 2) {
  402. this.followForm.progress = 'notMet'
  403. } else if (progress === 3) {
  404. this.followForm.progress = 'communicating'
  405. }
  406. }
  407. // 更新匹配状态
  408. if (followUp.isMatch !== null && followUp.isMatch !== undefined) {
  409. this.followForm.matchStatus = parseInt(followUp.isMatch) || 0
  410. }
  411. // 更新邀约状态
  412. if (followUp.isInvitation !== null && followUp.isInvitation !== undefined) {
  413. this.followForm.invitationStatus = parseInt(followUp.isInvitation) || 0
  414. }
  415. // 更新邀约时间
  416. if (followUp.invitationTime) {
  417. const date = new Date(followUp.invitationTime)
  418. const year = date.getFullYear()
  419. const month = String(date.getMonth() + 1).padStart(2, '0')
  420. const day = String(date.getDate()).padStart(2, '0')
  421. this.followForm.inviteDate = `${year}-${month}-${day}`
  422. } else {
  423. this.followForm.inviteDate = ''
  424. }
  425. // 更新备注
  426. if (followUp.remark !== null && followUp.remark !== undefined) {
  427. this.followForm.remark = followUp.remark || ''
  428. } else {
  429. this.followForm.remark = ''
  430. }
  431. console.log('表单字段已更新:', {
  432. type: this.followForm.type,
  433. progress: this.followForm.progress,
  434. matchStatus: this.followForm.matchStatus,
  435. invitationStatus: this.followForm.invitationStatus,
  436. inviteDate: this.followForm.inviteDate,
  437. remark: this.followForm.remark
  438. })
  439. } else {
  440. console.log('暂无跟进记录,保持表单当前值')
  441. }
  442. }
  443. } catch (e) {
  444. console.error('加载最新跟进信息异常:', e)
  445. }
  446. },
  447. // 加载跟进统计数据
  448. async loadFollowStatistics(resourceId) {
  449. try {
  450. const baseUrl = process.env.NODE_ENV === 'development'
  451. ? 'http://localhost:8083/api' // 开发环境 - 通过网关
  452. : 'https://your-domain.com/api' // 生产环境
  453. const [error, res] = await uni.request({
  454. url: `${baseUrl}/my-resource/follow-statistics/${resourceId}`,
  455. method: 'GET',
  456. header: {
  457. 'Content-Type': 'application/json'
  458. }
  459. })
  460. if (error) {
  461. console.error('加载统计数据失败:', error)
  462. return
  463. }
  464. if (res.statusCode === 200 && res.data && res.data.code === 200) {
  465. const statistics = res.data.data
  466. console.log('=== 统计数据响应 ===')
  467. console.log('完整响应:', JSON.stringify(res.data, null, 2))
  468. console.log('统计数据对象:', statistics)
  469. console.log('followCount:', statistics?.followCount, statistics?.follow_count)
  470. console.log('matchCount:', statistics?.matchCount, statistics?.match_count)
  471. console.log('phoneCount:', statistics?.phoneCount, statistics?.phone_count)
  472. console.log('interviewCount:', statistics?.interviewCount, statistics?.interview_count)
  473. // 更新统计数据(支持驼峰和下划线两种字段名格式)
  474. if (this.clientInfo && this.clientInfo.stats) {
  475. const followCount = statistics?.followCount ?? statistics?.follow_count ?? 0
  476. const matchCount = statistics?.matchCount ?? statistics?.match_count ?? 0
  477. const phoneCount = statistics?.phoneCount ?? statistics?.phone_count ?? 0
  478. const interviewCount = statistics?.interviewCount ?? statistics?.interview_count ?? 0
  479. this.clientInfo.stats.followTimes = followCount
  480. this.clientInfo.stats.matchCount = matchCount
  481. this.clientInfo.stats.phoneCalls = phoneCount
  482. this.clientInfo.stats.interviews = interviewCount
  483. console.log('更新后的统计数据:', {
  484. followTimes: this.clientInfo.stats.followTimes,
  485. matchCount: this.clientInfo.stats.matchCount,
  486. phoneCalls: this.clientInfo.stats.phoneCalls,
  487. interviews: this.clientInfo.stats.interviews
  488. })
  489. } else {
  490. console.warn('clientInfo或stats不存在')
  491. }
  492. } else {
  493. console.error('统计数据接口返回错误:', res.data)
  494. }
  495. } catch (e) {
  496. console.error('加载统计数据异常:', e)
  497. }
  498. },
  499. // 提交跟进
  500. async handleSubmitFollow() {
  501. // 检查是否可以跟进
  502. if (!this.canFollow) {
  503. uni.showToast({
  504. title: '该用户还没有添加到你的资源当中',
  505. icon: 'none',
  506. duration: 2000
  507. })
  508. return
  509. }
  510. if (!this.resourceId) {
  511. uni.showToast({
  512. title: '资源ID不存在',
  513. icon: 'none'
  514. })
  515. return
  516. }
  517. try {
  518. uni.showLoading({
  519. title: '保存中...'
  520. })
  521. const baseUrl = process.env.NODE_ENV === 'development'
  522. ? 'http://localhost:8083/api' // 开发环境 - 通过网关
  523. : 'https://your-domain.com/api' // 生产环境
  524. // 准备请求数据
  525. // 跟进方式转换:phone->1, meet->2, other->3
  526. let followType = 1 // 默认电话
  527. if (this.followForm.type === 'meet') {
  528. followType = 2
  529. } else if (this.followForm.type === 'other') {
  530. followType = 3
  531. }
  532. // 跟进进度转换:matched->1, notMet->2, communicating->3
  533. let progress = 1 // 默认已匹配
  534. if (this.followForm.progress === 'notMet') {
  535. progress = 2
  536. } else if (this.followForm.progress === 'communicating') {
  537. progress = 3
  538. }
  539. const requestData = {
  540. followType: followType,
  541. progress: this.followForm.progress, // 保持字符串格式,后端会转换
  542. matchStatus: this.followForm.matchStatus,
  543. invitationStatus: this.followForm.invitationStatus,
  544. invitationTime: this.followForm.inviteDate || null,
  545. remark: this.followForm.remark || '' // 添加备注字段
  546. }
  547. console.log('保存跟进数据:', requestData)
  548. const [error, res] = await uni.request({
  549. url: `${baseUrl}/my-resource/update-follow/${this.resourceId}`,
  550. method: 'PUT',
  551. data: requestData,
  552. header: {
  553. 'Content-Type': 'application/json'
  554. }
  555. })
  556. uni.hideLoading()
  557. if (error) {
  558. console.error('保存跟进失败:', error)
  559. uni.showToast({
  560. title: '保存失败',
  561. icon: 'none'
  562. })
  563. return
  564. }
  565. if (res.statusCode === 200 && res.data && res.data.code === 200) {
  566. uni.showToast({
  567. title: '跟进已保存',
  568. icon: 'success'
  569. })
  570. // 刷新统计数据
  571. if (this.resourceId) {
  572. this.loadFollowStatistics(this.resourceId)
  573. }
  574. // 重新加载最新的跟进信息,更新表单字段
  575. if (this.resourceId) {
  576. this.loadLatestFollowInfo(this.resourceId)
  577. }
  578. // 触发资源列表刷新事件
  579. uni.$emit('refreshResourceList')
  580. } else {
  581. const errorMsg = res.data?.message || '保存失败'
  582. uni.showToast({
  583. title: errorMsg,
  584. icon: 'none'
  585. })
  586. }
  587. } catch (e) {
  588. uni.hideLoading()
  589. console.error('保存跟进异常:', e)
  590. uni.showToast({
  591. title: '保存失败,请稍后重试',
  592. icon: 'none'
  593. })
  594. }
  595. },
  596. // 获取当前红娘的matchmaker_id
  597. async getCurrentMatchmakerId() {
  598. if (!this.currentUserId) {
  599. console.warn('当前用户ID为空,无法获取红娘ID')
  600. return
  601. }
  602. try {
  603. const baseUrl = process.env.NODE_ENV === 'development'
  604. ? 'http://localhost:8083/api' // 开发环境 - 通过网关
  605. : 'https://your-domain.com/api' // 生产环境
  606. // 根据user_id查询红娘信息
  607. const [error, res] = await uni.request({
  608. url: `${baseUrl}/matchmaker/by-user/${this.currentUserId}`,
  609. method: 'GET'
  610. })
  611. if (error) {
  612. console.error('获取红娘信息失败:', error)
  613. return
  614. }
  615. if (res.statusCode === 200 && res.data && res.data.code === 200) {
  616. const matchmaker = res.data.data
  617. this.currentMatchmakerId = matchmaker.matchmakerId || matchmaker.matchmaker_id
  618. console.log('获取到当前红娘的matchmaker_id:', this.currentMatchmakerId)
  619. // 如果已经加载了资源信息,重新判断是否可以跟进
  620. if (this.resourceMatchmakerId !== null && this.resourceMatchmakerId !== undefined) {
  621. if (this.fromQualityResources) {
  622. if (!this.currentMatchmakerId || this.resourceMatchmakerId !== this.currentMatchmakerId) {
  623. this.canFollow = false
  624. console.log('从优质资源列表进入,且资源不属于当前红娘,禁用跟进功能')
  625. } else {
  626. this.canFollow = true
  627. console.log('资源属于当前红娘,可以跟进')
  628. }
  629. }
  630. }
  631. }
  632. } catch (e) {
  633. console.error('获取红娘信息异常:', e)
  634. }
  635. },
  636. // 从API加载客户信息
  637. async loadClientInfo(resourceId) {
  638. try {
  639. console.log('=== 开始加载客户信息 ===')
  640. console.log('resourceId:', resourceId, '类型:', typeof resourceId)
  641. uni.showLoading({
  642. title: '加载中...'
  643. })
  644. const baseUrl = process.env.NODE_ENV === 'development'
  645. ? 'http://localhost:8083/api' // 开发环境 - 通过网关
  646. : 'https://your-domain.com/api' // 生产环境
  647. const requestUrl = `${baseUrl}/my-resource/client-detail/${resourceId}`
  648. console.log('请求URL:', requestUrl)
  649. const [error, res] = await uni.request({
  650. url: requestUrl,
  651. method: 'GET',
  652. timeout: 30000, // 设置30秒超时
  653. header: {
  654. 'Content-Type': 'application/json'
  655. }
  656. })
  657. console.log('请求结果 - error:', error)
  658. console.log('请求结果 - res:', res)
  659. uni.hideLoading()
  660. if (error) {
  661. console.error('加载客户信息失败:', error)
  662. let errorMsg = '加载失败'
  663. if (error.errMsg) {
  664. if (error.errMsg.includes('timeout') || error.errMsg.includes('超时')) {
  665. errorMsg = '请求超时,请检查网络连接'
  666. } else if (error.errMsg.includes('fail')) {
  667. errorMsg = '网络连接失败,请检查服务器是否启动'
  668. }
  669. }
  670. uni.showToast({
  671. title: errorMsg,
  672. icon: 'none',
  673. duration: 3000
  674. })
  675. return
  676. }
  677. if (res.statusCode === 200 && res.data && res.data.code === 200) {
  678. const data = res.data.data
  679. console.log('=== 客户详情数据 ===')
  680. console.log('完整数据:', JSON.stringify(data, null, 2))
  681. // 支持驼峰和下划线两种字段名格式
  682. const resourceId = data.resourceId || data.resource_id
  683. const avatarUrl = data.avatarUrl || data.avatar_url
  684. const backupPhone = data.backupPhone || data.backup_phone
  685. const marrStatus = data.marrStatus !== undefined ? data.marrStatus : data.marr_status
  686. // 择偶要求从my_resource表的mate_selection_criteria字段获取
  687. const mateSelectionCriteria = data.mateSelectionCriteria || data.mate_selection_criteria
  688. // 获取资源的matchmaker_id
  689. const resourceMatchmakerId = data.matchmakerId || data.matchmaker_id
  690. this.resourceMatchmakerId = resourceMatchmakerId
  691. console.log('资源的matchmaker_id:', resourceMatchmakerId)
  692. console.log('当前红娘的matchmaker_id:', this.currentMatchmakerId)
  693. // 判断是否可以跟进:如果从优质资源列表进入,且资源不属于当前红娘,则不能跟进
  694. if (this.fromQualityResources) {
  695. // 如果还没有获取到当前红娘的ID,先获取
  696. if (!this.currentMatchmakerId) {
  697. await this.getCurrentMatchmakerId()
  698. }
  699. if (!this.currentMatchmakerId || this.resourceMatchmakerId !== this.currentMatchmakerId) {
  700. this.canFollow = false
  701. console.log('从优质资源列表进入,且资源不属于当前红娘,禁用跟进功能')
  702. } else {
  703. this.canFollow = true
  704. console.log('资源属于当前红娘,可以跟进')
  705. }
  706. } else {
  707. // 从我的资源列表进入,默认可以跟进
  708. this.canFollow = true
  709. }
  710. // 择偶要求详情字段(从partner_requirement表,用于详情页)
  711. const minAge = data.minAge !== undefined ? data.minAge : data.min_age
  712. const maxAge = data.maxAge !== undefined ? data.maxAge : data.max_age
  713. const minHeight = data.minHeight !== undefined ? data.minHeight : data.min_height
  714. const maxHeight = data.maxHeight !== undefined ? data.maxHeight : data.max_height
  715. const educationLevel = data.educationLevel !== undefined ? data.educationLevel : data.education_level
  716. const salaryRange = data.salaryRange !== undefined ? data.salaryRange : data.salary_range
  717. const houseRequirement = data.houseRequirement !== undefined ? data.houseRequirement : data.house_requirement
  718. const carRequirement = data.carRequirement !== undefined ? data.carRequirement : data.car_requirement
  719. const maritalStatusRequirement = data.maritalStatusRequirement !== undefined ? data.maritalStatusRequirement : data.marital_status_requirement
  720. const preferredCity = data.preferredCity || data.preferred_city
  721. const otherRequirements = data.otherRequirements || data.other_requirements
  722. console.log('资源ID:', resourceId)
  723. console.log('姓名:', data.name)
  724. console.log('头像URL:', avatarUrl)
  725. console.log('备用手机号:', backupPhone)
  726. console.log('择偶要求(mate_selection_criteria):', mateSelectionCriteria)
  727. console.log('择偶要求详情数据:', {
  728. minAge, maxAge, minHeight, maxHeight,
  729. educationLevel, salaryRange, houseRequirement,
  730. carRequirement, maritalStatusRequirement,
  731. preferredCity, otherRequirements
  732. })
  733. // 处理标签列表(从后端返回的tags字段,如果没有则使用星座和职业)
  734. let clientTags = []
  735. if (data.tags && Array.isArray(data.tags) && data.tags.length > 0) {
  736. // 使用后端返回的标签
  737. clientTags = data.tags
  738. } else {
  739. // 如果没有tags,使用星座和职业作为标签
  740. if (data.constellation) {
  741. clientTags.push(data.constellation)
  742. }
  743. if (data.occupation) {
  744. clientTags.push(data.occupation)
  745. }
  746. }
  747. // 加载匹配状态和邀约状态
  748. const isMatch = data.isMatch !== null && data.isMatch !== undefined ? data.isMatch :
  749. (data.is_match !== null && data.is_match !== undefined ? data.is_match : 0)
  750. const isInvitation = data.isInvitation !== null && data.isInvitation !== undefined ? data.isInvitation :
  751. (data.is_Invitation !== null && data.is_Invitation !== undefined ? data.is_Invitation : 0)
  752. const invitationTime = data.invitationTime || data.Invitation_time
  753. this.followForm.matchStatus = parseInt(isMatch) || 0
  754. this.followForm.invitationStatus = parseInt(isInvitation) || 0
  755. if (invitationTime) {
  756. // 将日期转换为YYYY-MM-DD格式
  757. const date = new Date(invitationTime)
  758. const year = date.getFullYear()
  759. const month = String(date.getMonth() + 1).padStart(2, '0')
  760. const day = String(date.getDate()).padStart(2, '0')
  761. this.followForm.inviteDate = `${year}-${month}-${day}`
  762. }
  763. // 映射数据到clientInfo
  764. this.clientInfo = {
  765. id: resourceId,
  766. name: data.name || '',
  767. gender: data.gender === 1 ? '男' : data.gender === 2 ? '女' : '未知',
  768. status: '已匹配', // 可以根据实际字段判断
  769. tags: clientTags, // 使用处理后的标签列表
  770. avatar: avatarUrl || '',
  771. requirement: mateSelectionCriteria || '暂无要求', // 择偶要求显示my_resource表的mate_selection_criteria字段
  772. contact: data.phone ? data.phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2') : '',
  773. backupContact: backupPhone ? backupPhone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2') : '',
  774. originalPhone: data.phone || '', // 保存原始手机号
  775. originalBackupPhone: backupPhone || '', // 保存原始备用手机号
  776. stats: {
  777. followTimes: 0, // 将从统计数据接口获取
  778. matchCount: 0,
  779. phoneCalls: 0,
  780. interviews: 0
  781. },
  782. details: {
  783. maritalStatus: this.formatMaritalStatus(marrStatus),
  784. education: data.diploma || '未知',
  785. occupation: data.occupation || '未知',
  786. income: data.income || '未知',
  787. housing: data.house === 1 ? '已购房' : data.house === 0 ? '未购房' : '未知',
  788. requirement: {
  789. ageRange: minAge && maxAge ? `${minAge}-${maxAge}岁` : '不限',
  790. height: minHeight ? `${minHeight}cm以上` : '不限',
  791. maritalStatus: this.formatMaritalStatusRequirement(maritalStatusRequirement),
  792. education: this.formatEducationLevel(educationLevel),
  793. other: otherRequirements || ''
  794. }
  795. }
  796. }
  797. // 标签已经在上面处理过了,这里不需要再添加
  798. console.log('=== 映射后的clientInfo ===')
  799. console.log('clientInfo:', JSON.stringify(this.clientInfo, null, 2))
  800. // 加载跟进统计数据
  801. this.loadFollowStatistics(resourceId)
  802. } else {
  803. uni.showToast({
  804. title: res.data.message || '加载失败',
  805. icon: 'none'
  806. })
  807. }
  808. } catch (e) {
  809. uni.hideLoading()
  810. console.error('加载客户信息失败:', e)
  811. uni.showToast({
  812. title: '加载失败',
  813. icon: 'none'
  814. })
  815. }
  816. },
  817. // 格式化婚姻状况
  818. formatMaritalStatus(status) {
  819. if (status === null || status === undefined) return '未知'
  820. switch (status) {
  821. case 0: return '未婚'
  822. case 1: return '离异'
  823. case 2: return '丧偶'
  824. default: return '未知'
  825. }
  826. },
  827. // 格式化婚姻状况要求
  828. formatMaritalStatusRequirement(status) {
  829. if (status === null || status === undefined) return '不限'
  830. switch (status) {
  831. case 0: return '不限'
  832. case 1: return '未婚'
  833. case 2: return '离异'
  834. case 3: return '丧偶'
  835. default: return '不限'
  836. }
  837. },
  838. // 格式化学历要求
  839. formatEducationLevel(level) {
  840. if (level === null || level === undefined) return '不限'
  841. switch (level) {
  842. case 0: return '不限'
  843. case 1: return '高中及以上'
  844. case 2: return '专科及以上'
  845. case 3: return '本科及以上'
  846. case 4: return '硕士及以上'
  847. case 5: return '博士及以上'
  848. default: return '不限'
  849. }
  850. }
  851. }
  852. }
  853. </script>
  854. <style lang="scss" scoped>
  855. .client-detail {
  856. min-height: 100vh;
  857. background: linear-gradient(180deg, #FFF5F8 0%, #F8E8F0 50%, #FFF9F9 100%);
  858. padding-top: 90rpx;
  859. }
  860. /* 顶部导航栏 */
  861. .header {
  862. position: fixed;
  863. top: 0;
  864. left: 0;
  865. right: 0;
  866. height: 90rpx;
  867. display: flex;
  868. align-items: center;
  869. justify-content: space-between;
  870. padding: 0 20rpx;
  871. background: linear-gradient(135deg, #FCE4EC 0%, #F8BBD0 100%);
  872. z-index: 999;
  873. .back-icon {
  874. width: 44rpx;
  875. height: 44rpx;
  876. background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23333"><path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></svg>');
  877. background-size: contain;
  878. background-repeat: no-repeat;
  879. background-position: center;
  880. }
  881. .header-title {
  882. font-size: 38rpx;
  883. font-weight: bold;
  884. color: #333;
  885. }
  886. .header-right {
  887. width: 44rpx;
  888. }
  889. }
  890. /* 客户基本信息 */
  891. .client-basic-info {
  892. display: flex;
  893. align-items: flex-start;
  894. padding: 35rpx;
  895. background: linear-gradient(135deg, #FFFFFF 0%, #FFFBFD 100%);
  896. margin: 20rpx;
  897. border-radius: 24rpx;
  898. box-shadow: 0 8rpx 24rpx rgba(156, 39, 176, 0.12), 0 2rpx 8rpx rgba(156, 39, 176, 0.08);
  899. border: 1rpx solid rgba(243, 229, 245, 0.8);
  900. .client-avatar {
  901. width: 160rpx;
  902. height: 160rpx;
  903. border-radius: 50%;
  904. margin-right: 30rpx;
  905. background-color: #F5F5F5;
  906. }
  907. .basic-info {
  908. flex: 1;
  909. .client-name-wrapper {
  910. display: flex;
  911. align-items: center;
  912. gap: 12rpx;
  913. margin-bottom: 12rpx;
  914. }
  915. .client-name {
  916. font-size: 38rpx;
  917. font-weight: 600;
  918. color: #2C2C2C;
  919. letter-spacing: 0.5rpx;
  920. }
  921. .gender-tag {
  922. font-size: 24rpx;
  923. font-weight: 500;
  924. padding: 6rpx 14rpx;
  925. border-radius: 12rpx;
  926. background: #E3F2FD;
  927. color: #2196F3;
  928. border: 1rpx solid #BBDEFB;
  929. }
  930. .status-tag-wrapper {
  931. display: flex;
  932. align-items: center;
  933. gap: 10rpx;
  934. margin-bottom: 18rpx;
  935. }
  936. .status-tag {
  937. display: inline-block;
  938. padding: 4rpx 12rpx;
  939. border-radius: 12rpx;
  940. font-size: 22rpx;
  941. font-weight: 500;
  942. &.register-tag {
  943. &.registered {
  944. background: #E8F5E9;
  945. color: #4CAF50;
  946. }
  947. }
  948. &.match-tag {
  949. &.matched {
  950. background: #E3F2FD;
  951. color: #2196F3;
  952. }
  953. &.unmatched {
  954. background: #FFF3E0;
  955. color: #FF9800;
  956. }
  957. }
  958. }
  959. .client-status {
  960. font-size: 26rpx;
  961. color: #FF6B8A;
  962. margin-bottom: 18rpx;
  963. display: inline-block;
  964. padding: 4rpx 16rpx;
  965. background: linear-gradient(135deg, #FFE5EB 0%, #FFF0F5 100%);
  966. border-radius: 12rpx;
  967. font-weight: 500;
  968. }
  969. .client-tags {
  970. display: flex;
  971. flex-wrap: wrap;
  972. gap: 10rpx;
  973. .tag {
  974. padding: 10rpx 22rpx;
  975. background: linear-gradient(135deg, #F3E5F5 0%, #E1BEE7 100%);
  976. color: #7B1FA2;
  977. border-radius: 24rpx;
  978. font-size: 24rpx;
  979. font-weight: 600;
  980. box-shadow: 0 2rpx 8rpx rgba(156, 39, 176, 0.15);
  981. border: 1rpx solid rgba(156, 39, 176, 0.2);
  982. }
  983. }
  984. }
  985. }
  986. /* 择偶要求 */
  987. .requirement-section {
  988. padding: 35rpx;
  989. background: linear-gradient(135deg, #FFFFFF 0%, #FFFBFD 100%);
  990. margin: 0 20rpx 20rpx;
  991. border-radius: 24rpx;
  992. box-shadow: 0 8rpx 24rpx rgba(156, 39, 176, 0.12), 0 2rpx 8rpx rgba(156, 39, 176, 0.08);
  993. border: 1rpx solid rgba(243, 229, 245, 0.8);
  994. .section-title {
  995. display: flex;
  996. align-items: center;
  997. margin-bottom: 20rpx;
  998. .title-icon {
  999. font-size: 28rpx;
  1000. margin-right: 10rpx;
  1001. }
  1002. .title-text {
  1003. font-size: 30rpx;
  1004. font-weight: 600;
  1005. color: #2C2C2C;
  1006. letter-spacing: 0.5rpx;
  1007. }
  1008. }
  1009. .requirement-content {
  1010. font-size: 28rpx;
  1011. color: #555;
  1012. line-height: 1.8;
  1013. font-weight: 400;
  1014. }
  1015. }
  1016. /* 联系方式 */
  1017. .contact-section {
  1018. padding: 35rpx;
  1019. background: linear-gradient(135deg, #FFFFFF 0%, #FFFBFD 100%);
  1020. margin: 0 20rpx 20rpx;
  1021. border-radius: 24rpx;
  1022. box-shadow: 0 8rpx 24rpx rgba(156, 39, 176, 0.12), 0 2rpx 8rpx rgba(156, 39, 176, 0.08);
  1023. border: 1rpx solid rgba(243, 229, 245, 0.8);
  1024. .contact-item {
  1025. display: flex;
  1026. justify-content: space-between;
  1027. align-items: center;
  1028. margin-bottom: 25rpx;
  1029. &:last-child {
  1030. margin-bottom: 0;
  1031. }
  1032. .contact-label {
  1033. font-size: 28rpx;
  1034. color: #2C2C2C;
  1035. font-weight: 500;
  1036. }
  1037. .contact-value {
  1038. display: flex;
  1039. align-items: center;
  1040. gap: 20rpx;
  1041. .contact-number {
  1042. font-size: 28rpx;
  1043. color: #555;
  1044. font-weight: 400;
  1045. }
  1046. .copy-btn {
  1047. padding: 10rpx 24rpx;
  1048. background: linear-gradient(135deg, #F3E5F5 0%, #E1BEE7 100%);
  1049. color: #7B1FA2;
  1050. border-radius: 24rpx;
  1051. font-size: 24rpx;
  1052. font-weight: 600;
  1053. box-shadow: 0 2rpx 8rpx rgba(156, 39, 176, 0.2);
  1054. border: 1rpx solid rgba(156, 39, 176, 0.2);
  1055. transition: all 0.3s;
  1056. &:active {
  1057. transform: scale(0.95);
  1058. box-shadow: 0 1rpx 4rpx rgba(156, 39, 176, 0.3);
  1059. }
  1060. }
  1061. }
  1062. }
  1063. }
  1064. /* 跟进统计 */
  1065. .stats-section {
  1066. padding: 30rpx;
  1067. background-color: #FFFFFF;
  1068. margin: 0 20rpx 20rpx;
  1069. border-radius: 20rpx;
  1070. box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
  1071. .stats-title {
  1072. font-size: 28rpx;
  1073. font-weight: bold;
  1074. color: #333;
  1075. margin-bottom: 25rpx;
  1076. }
  1077. .stats-grid {
  1078. display: grid;
  1079. grid-template-columns: repeat(4, 1fr);
  1080. gap: 20rpx;
  1081. .stat-item {
  1082. display: flex;
  1083. flex-direction: column;
  1084. align-items: center;
  1085. justify-content: center;
  1086. padding: 20rpx;
  1087. background-color: #FFF3F5;
  1088. border-radius: 15rpx;
  1089. .stat-value {
  1090. font-size: 48rpx;
  1091. font-weight: bold;
  1092. color: #FF6B8A;
  1093. margin-bottom: 10rpx;
  1094. }
  1095. .stat-label {
  1096. font-size: 22rpx;
  1097. color: #666;
  1098. }
  1099. }
  1100. }
  1101. }
  1102. /* 标签切换 */
  1103. .tabs-section {
  1104. display: flex;
  1105. padding: 0 20rpx;
  1106. background: linear-gradient(135deg, #FFFFFF 0%, #FFFBFD 100%);
  1107. margin: 0 20rpx 20rpx;
  1108. border-radius: 24rpx;
  1109. box-shadow: 0 8rpx 24rpx rgba(156, 39, 176, 0.12), 0 2rpx 8rpx rgba(156, 39, 176, 0.08);
  1110. border: 1rpx solid rgba(243, 229, 245, 0.8);
  1111. .tab {
  1112. flex: 1;
  1113. text-align: center;
  1114. padding: 24rpx 0;
  1115. font-size: 30rpx;
  1116. color: #888;
  1117. font-weight: 500;
  1118. transition: all 0.3s;
  1119. position: relative;
  1120. &.active {
  1121. color: #7B1FA2;
  1122. font-weight: 600;
  1123. &::after {
  1124. content: '';
  1125. position: absolute;
  1126. bottom: 0;
  1127. left: 50%;
  1128. transform: translateX(-50%);
  1129. width: 60rpx;
  1130. height: 6rpx;
  1131. background: linear-gradient(90deg, #9C27B0 0%, #E1BEE7 100%);
  1132. border-radius: 3rpx;
  1133. }
  1134. }
  1135. }
  1136. }
  1137. /* 详细信息滚动区域 */
  1138. .details-section {
  1139. padding: 35rpx 25rpx 100rpx;
  1140. background: linear-gradient(135deg, #FFFFFF 0%, #FFFBFD 100%);
  1141. margin: 0 20rpx 20rpx;
  1142. border-radius: 24rpx;
  1143. box-shadow: 0 8rpx 24rpx rgba(156, 39, 176, 0.12), 0 2rpx 8rpx rgba(156, 39, 176, 0.08);
  1144. border: 1rpx solid rgba(243, 229, 245, 0.8);
  1145. box-sizing: border-box;
  1146. position: relative;
  1147. .detail-item {
  1148. display: flex;
  1149. margin-bottom: 28rpx;
  1150. padding: 12rpx 0;
  1151. border-bottom: 1rpx solid rgba(240, 240, 240, 0.6);
  1152. &:last-of-type {
  1153. border-bottom: none;
  1154. }
  1155. .detail-label {
  1156. width: 200rpx;
  1157. font-size: 28rpx;
  1158. color: #2C2C2C;
  1159. font-weight: 500;
  1160. letter-spacing: 0.3rpx;
  1161. }
  1162. .detail-value {
  1163. flex: 1;
  1164. font-size: 28rpx;
  1165. color: #555;
  1166. font-weight: 400;
  1167. text-align: right;
  1168. }
  1169. }
  1170. .sub-section-title {
  1171. font-size: 32rpx;
  1172. font-weight: 600;
  1173. color: #2C2C2C;
  1174. margin: 30rpx 0 20rpx;
  1175. padding-bottom: 12rpx;
  1176. border-bottom: 2rpx solid rgba(240, 240, 240, 0.8);
  1177. letter-spacing: 0.5rpx;
  1178. }
  1179. /* 择偶详细要求部分 - 添加明显的上分隔 */
  1180. .requirement-section-title {
  1181. margin-top: 60rpx;
  1182. padding: 32rpx 24rpx 20rpx;
  1183. border-top: 3rpx solid rgba(156, 39, 176, 0.15);
  1184. border-bottom: 2rpx solid rgba(156, 39, 176, 0.1);
  1185. position: relative;
  1186. background: linear-gradient(135deg, #F8F4F9 0%, #FFFBFD 100%);
  1187. border-radius: 16rpx 16rpx 0 0;
  1188. }
  1189. .requirement-section-title::before {
  1190. content: '';
  1191. position: absolute;
  1192. left: 0;
  1193. top: 0;
  1194. width: 8rpx;
  1195. height: 100%;
  1196. background: linear-gradient(180deg, #9C27B0 0%, #BA68C8 50%, #E1BEE7 100%);
  1197. border-radius: 0 4rpx 4rpx 0;
  1198. box-shadow: 2rpx 0 8rpx rgba(156, 39, 176, 0.2);
  1199. }
  1200. /* 客户标签部分 - 添加明显的上分隔 */
  1201. .tags-section-title {
  1202. margin-top: 60rpx;
  1203. padding: 32rpx 24rpx 20rpx;
  1204. border-top: 3rpx solid rgba(156, 39, 176, 0.15);
  1205. border-bottom: 2rpx solid rgba(156, 39, 176, 0.1);
  1206. position: relative;
  1207. background: linear-gradient(135deg, #F8F4F9 0%, #FFFBFD 100%);
  1208. border-radius: 16rpx 16rpx 0 0;
  1209. }
  1210. .tags-section-title::before {
  1211. content: '';
  1212. position: absolute;
  1213. left: 0;
  1214. top: 0;
  1215. width: 8rpx;
  1216. height: 100%;
  1217. background: linear-gradient(180deg, #9C27B0 0%, #BA68C8 50%, #E1BEE7 100%);
  1218. border-radius: 0 4rpx 4rpx 0;
  1219. box-shadow: 2rpx 0 8rpx rgba(156, 39, 176, 0.2);
  1220. }
  1221. .client-tags {
  1222. display: flex;
  1223. flex-wrap: wrap;
  1224. gap: 12rpx;
  1225. padding: 20rpx 0;
  1226. .tag {
  1227. padding: 12rpx 24rpx;
  1228. background: linear-gradient(135deg, #F3E5F5 0%, #E1BEE7 100%);
  1229. color: #7B1FA2;
  1230. border-radius: 24rpx;
  1231. font-size: 26rpx;
  1232. font-weight: 600;
  1233. box-shadow: 0 2rpx 8rpx rgba(156, 39, 176, 0.15);
  1234. border: 1rpx solid rgba(156, 39, 176, 0.2);
  1235. transition: all 0.3s;
  1236. &:active {
  1237. transform: scale(0.95);
  1238. }
  1239. }
  1240. }
  1241. }
  1242. /* 跟进信息 */
  1243. .follow-section {
  1244. background-color: #FFFFFF;
  1245. margin: 0 20rpx 20rpx;
  1246. border-radius: 20rpx;
  1247. box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
  1248. padding: 30rpx 20rpx 40rpx;
  1249. max-height: 700rpx;
  1250. box-sizing: border-box;
  1251. }
  1252. .follow-group {
  1253. margin-bottom: 30rpx;
  1254. .group-label {
  1255. font-size: 28rpx;
  1256. color: #333;
  1257. font-weight: bold;
  1258. margin-bottom: 16rpx;
  1259. display: block;
  1260. }
  1261. }
  1262. /* 跟进方式单选 */
  1263. .follow-type-options {
  1264. display: flex;
  1265. align-items: center;
  1266. gap: 30rpx;
  1267. .follow-type-item {
  1268. display: flex;
  1269. align-items: center;
  1270. gap: 10rpx;
  1271. font-size: 26rpx;
  1272. color: #666;
  1273. .radio-circle {
  1274. width: 26rpx;
  1275. height: 26rpx;
  1276. border-radius: 50%;
  1277. border: 2rpx solid #C5A4D8;
  1278. box-sizing: border-box;
  1279. }
  1280. &.active {
  1281. color: #D81B60;
  1282. .radio-circle {
  1283. background-color: #D81B60;
  1284. border-color: #D81B60;
  1285. }
  1286. }
  1287. }
  1288. }
  1289. /* 跟进进度标签 */
  1290. .status-tags-row {
  1291. display: flex;
  1292. flex-wrap: wrap;
  1293. gap: 16rpx;
  1294. .status-tag {
  1295. min-width: 140rpx;
  1296. text-align: center;
  1297. padding: 14rpx 20rpx;
  1298. border-radius: 20rpx;
  1299. border: 2rpx solid #E1BEE7;
  1300. font-size: 26rpx;
  1301. color: #9C27B0;
  1302. background-color: #FAF5FF;
  1303. box-sizing: border-box;
  1304. &.active {
  1305. background-color: #F8BBD0;
  1306. border-color: #F06292;
  1307. color: #FFFFFF;
  1308. }
  1309. }
  1310. }
  1311. /* 邀约状态/时间 等输入类展示 */
  1312. .input-like {
  1313. width: 100%;
  1314. padding: 18rpx 22rpx;
  1315. border-radius: 16rpx;
  1316. background-color: #FAF5FF;
  1317. font-size: 26rpx;
  1318. color: #666;
  1319. box-sizing: border-box;
  1320. }
  1321. .picker-inner {
  1322. font-size: 26rpx;
  1323. color: #666;
  1324. }
  1325. /* 备注输入框 */
  1326. .textarea {
  1327. width: 100%;
  1328. min-height: 160rpx;
  1329. padding: 18rpx 22rpx;
  1330. border-radius: 16rpx;
  1331. background-color: #FAF5FF;
  1332. font-size: 26rpx;
  1333. color: #333;
  1334. box-sizing: border-box;
  1335. }
  1336. /* 保存按钮 */
  1337. .follow-submit-btn {
  1338. &.disabled {
  1339. background: #CCCCCC !important;
  1340. color: #999999 !important;
  1341. opacity: 0.6;
  1342. cursor: not-allowed;
  1343. }
  1344. margin-top: 10rpx;
  1345. width: 100%;
  1346. padding: 22rpx 0;
  1347. text-align: center;
  1348. border-radius: 26rpx;
  1349. background: linear-gradient(135deg, #F48FB1 0%, #EC407A 100%);
  1350. color: #FFFFFF;
  1351. font-size: 30rpx;
  1352. font-weight: bold;
  1353. }
  1354. </style>