client-detail.vue 39 KB

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