index.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701
  1. <template>
  2. <view class="my-page">
  3. <!-- 顶部背景墙 -->
  4. <view class="header-bg">
  5. <image src="/static/images/my-header.png" class="header-img" mode="widthFix"></image>
  6. <view class="user-block" @click="goToLogin" @longpress="onDebugTap">
  7. <image class="user-avatar" :src="userInfo?.avatarUrl || '/static/images/default-avatar.png'"
  8. mode="aspectFill"></image>
  9. <view class="user-info">
  10. <text class="user-name">{{ userInfo ? userInfo.nickName : '点击登录' }}</text>
  11. <text class="user-desc-text">{{ userInfo ? (userInfo.remark || '这位用户很懒,什么都没写 🐾') : '登录后享受更多权益 🐾'
  12. }}</text>
  13. </view>
  14. <view v-if="!userInfo" class="css-right-arrow-gold"></view>
  15. </view>
  16. </view>
  17. <!-- 我的服务订单区 -->
  18. <view class="cute-card order-wrap">
  19. <view class="card-head">
  20. <text class="card-title">我的服务订单</text>
  21. <view class="card-more" @click="goToOrder('all')">
  22. <text>查看全部</text>
  23. <view class="css-right-arrow-small"></view>
  24. </view>
  25. </view>
  26. <view class="order-nav">
  27. <view class="nav-item" v-for="item in orderItems" :key="item.key" @click="goToOrder(item.key)">
  28. <view class="icon-bulb">
  29. <image class="custom-icon" :src="item.icon" mode="aspectFit"></image>
  30. </view>
  31. <text class="nav-label">{{ item.label }}</text>
  32. </view>
  33. </view>
  34. </view>
  35. <!-- 服务与工具 -->
  36. <view class="cute-card tool-wrap">
  37. <view class="card-head">
  38. <text class="card-title">服务与工具</text>
  39. </view>
  40. <view class="tool-grid">
  41. <view class="tool-item" v-for="item in menuItems" :key="item.path || item.title"
  42. @click="goToMenu(item)">
  43. <image class="custom-icon tool-icon" :src="item.icon" mode="aspectFit"></image>
  44. <text class="tool-text">{{ item.title }}</text>
  45. </view>
  46. </view>
  47. </view>
  48. <view class="footer-msg">
  49. <text>~ 感谢您的陪伴 ~</text>
  50. </view>
  51. <!-- 客服中心弹窗 @Author: Antigravity -->
  52. <view class="service-popup-mask" v-if="showServicePopup" @click="closeServicePopup">
  53. <view class="service-popup" @click.stop>
  54. <view class="service-header">
  55. <text class="service-title">联系客服</text>
  56. <view class="css-close-btn" @click="closeServicePopup"></view>
  57. </view>
  58. <view class="qr-section">
  59. <text class="qr-title">客服二维码</text>
  60. <image class="qr-img" :src="customerSetting.qrCodeUrl || '/static/images/logo.png'" mode="aspectFit"
  61. @click="previewQRCode">
  62. </image>
  63. <text class="qr-desc">点击查看大图</text>
  64. </view>
  65. <view class="service-list">
  66. <view class="service-row">
  67. <view class="service-icon-box online-bg">
  68. <image class="service-icon-img" src="/static/icon/my/customerservice/online.png"
  69. mode="aspectFit"></image>
  70. </view>
  71. <view class="service-info">
  72. <text class="service-name">在线客服</text>
  73. <text class="service-desc">{{ customerSetting.wechatAccount || '企业微信专属客服在线解答' }}</text>
  74. </view>
  75. <view class="call-btn-mini green-btn" @click="openOnlineService">
  76. <text>去咨询</text>
  77. </view>
  78. </view>
  79. <view class="service-row">
  80. <view class="service-icon-box phone-bg">
  81. <image class="service-icon-img" src="/static/icon/my/customerservice/phone.png"
  82. mode="aspectFit"></image>
  83. </view>
  84. <view class="service-info">
  85. <text class="service-name">客服电话</text>
  86. <text class="service-desc">{{ customerSetting.phoneNumber || '暂无电话' }}</text>
  87. </view>
  88. <view class="call-btn-mini orange-btn" @click="callServicePhone">
  89. <text>拨打</text>
  90. </view>
  91. </view>
  92. </view>
  93. </view>
  94. </view>
  95. <custom-tabbar></custom-tabbar>
  96. </view>
  97. </template>
  98. <script>
  99. // @Author: Antigravity
  100. import { getInfo } from '@/api/system/user'
  101. import { getCustomerServiceSetting } from '@/api/system/customerServiceSetting'
  102. import customTabbar from '@/components/custom-tabbar/index.vue'
  103. import orderStatusData from '@/json/orderStatus.json'
  104. // 订单状态图标映射
  105. const iconMap = {
  106. 0: '/static/images/my-pendingdispatch.png',
  107. 1: '/static/images/my-pendingaccept.png',
  108. 2: '/static/images/my-inservice.png',
  109. 3: '/static/images/my-pendingservice.png',
  110. 4: '/static/images/my-finished.png',
  111. 5: '/static/images/my-cancel.png'
  112. }
  113. export default {
  114. components: { customTabbar },
  115. data() {
  116. return {
  117. userInfo: null,
  118. showServicePopup: false,
  119. debugTapCount: 0, // 调试触发计数器
  120. customerSetting: {
  121. wechatAccount: '',
  122. phoneNumber: '',
  123. qrCode: '',
  124. qrCodeUrl: '',
  125. enterpriseWechatLink: ''
  126. },
  127. // 订单状态导航(过滤掉已拒绝和已关闭)
  128. orderItems: orderStatusData.filter(item => ![6, 7].includes(item.value)).map(item => ({
  129. key: item.value,
  130. label: item.label,
  131. icon: iconMap[item.value] || '/static/images/my-pendingdispatch.png'
  132. })),
  133. // 功能菜单
  134. menuItems: [
  135. { title: '宠物档案', icon: '/static/images/my-pet.png', path: '/pages/my/pet/list/index' },
  136. { title: '用户管理', icon: '/static/images/my-customer.png', path: '/pages/my/user/list/index' },
  137. { title: '投诉管理', icon: '/static/images/my-complaint.png', path: '/pages/my/complaint/list/index' },
  138. { title: '服务费统计', icon: '/static/images/my-fee.png', path: '/pages/my/fee/statistics/index' },
  139. { title: '客服中心', icon: '/static/images/my-customerservice.png', path: '' },
  140. { title: '协议中心', icon: '/static/images/my-agreement.png', path: '/pages/my/agreement/list/index' },
  141. { title: '系统设置', icon: '/static/images/my-systemsetting.png', path: '/pages/my/settings/index' }
  142. ]
  143. }
  144. },
  145. onShow() {
  146. this.fetchUserInfo()
  147. this.fetchCustomerSetting()
  148. },
  149. methods: {
  150. // 获取当前登录用户信息
  151. async fetchUserInfo() {
  152. const token = uni.getStorageSync('token')
  153. if (token) {
  154. try {
  155. const res = await getInfo()
  156. if (res && res.user) {
  157. this.userInfo = res.user
  158. }
  159. } catch (error) {
  160. console.error('获取用户信息失败', error)
  161. uni.showToast({ title: typeof error === 'string' ? error : '获取用户信息失败', icon: 'none' })
  162. }
  163. } else {
  164. this.userInfo = null
  165. }
  166. },
  167. // 获取客服配置 (ID=2)
  168. async fetchCustomerSetting() {
  169. try {
  170. const res = await getCustomerServiceSetting(2)
  171. if (res) {
  172. // 商户端 request 拦截器已处理 res.data,此处 res 即为配置对象
  173. Object.assign(this.customerSetting, res)
  174. }
  175. } catch (error) {
  176. console.error('获取客服配置失败', error)
  177. uni.showToast({ title: typeof error === 'string' ? error : '获取客服配置失败', icon: 'none' })
  178. }
  179. },
  180. // 跳转到登录页(未登录时)
  181. goToLogin() {
  182. if (!this.userInfo) {
  183. uni.navigateTo({ url: '/pages/login/index' })
  184. }
  185. },
  186. // 跳转到订单列表
  187. goToOrder(statusValue) {
  188. if (statusValue === 'all') {
  189. uni.reLaunch({ url: '/pages/order/list/index' })
  190. } else {
  191. uni.reLaunch({ url: `/pages/order/list/index?status=${statusValue}` })
  192. }
  193. },
  194. // 菜单项点击处理
  195. goToMenu(item) {
  196. if (item.title === '客服中心') {
  197. this.showServicePopup = true
  198. return
  199. }
  200. if (item.path) {
  201. uni.navigateTo({ url: item.path })
  202. }
  203. },
  204. // 关闭客服弹窗
  205. closeServicePopup() {
  206. this.showServicePopup = false
  207. },
  208. // 预览客服二维码
  209. previewQRCode() {
  210. if (!this.customerSetting.qrCodeUrl) return
  211. uni.previewImage({
  212. urls: [this.customerSetting.qrCodeUrl]
  213. })
  214. },
  215. // 打开在线客服
  216. openOnlineService() {
  217. if (this.customerSetting.enterpriseWechatLink) {
  218. // #ifdef H5
  219. window.location.href = this.customerSetting.enterpriseWechatLink
  220. // #endif
  221. // #ifndef H5
  222. uni.setClipboardData({
  223. data: this.customerSetting.wechatAccount || this.customerSetting.enterpriseWechatLink,
  224. success: () => {
  225. uni.showToast({ title: '客服账号已复制,请在微信中添加', icon: 'none' })
  226. }
  227. })
  228. // #endif
  229. } else {
  230. uni.showToast({ title: '在线客服暂未配置', icon: 'none' })
  231. }
  232. },
  233. // 拨打客服电话
  234. callServicePhone() {
  235. const phoneNumber = String(this.customerSetting.phoneNumber || '').trim()
  236. console.log('=== callServicePhone 触发 ===')
  237. console.log('phone number:', phoneNumber)
  238. console.log('makePhoneCall 类型:', typeof uni.makePhoneCall)
  239. if (!phoneNumber) {
  240. uni.showToast({ title: '暂无客服电话', icon: 'none' })
  241. return
  242. }
  243. uni.makePhoneCall({
  244. phoneNumber,
  245. success: () => { console.log('拨号成功') },
  246. fail: (err) => {
  247. console.error('拨号失败:', JSON.stringify(err))
  248. uni.showModal({
  249. title: '拨号失败',
  250. content: typeof err === 'object' ? JSON.stringify(err) : String(err),
  251. showCancel: false
  252. })
  253. }
  254. })
  255. },
  256. // 长按头像区备触发调试日志查看
  257. onDebugTap() {
  258. this.debugTapCount++
  259. if (this.debugTapCount >= 3) {
  260. this.debugTapCount = 0
  261. this.showDebugLog()
  262. }
  263. },
  264. // 读取并展示全局错误日志
  265. showDebugLog() {
  266. try {
  267. const logs = JSON.parse(uni.getStorageSync('__debug_logs') || '[]')
  268. // eslint-disable-next-line no-undef
  269. const plusAvail = typeof plus !== 'undefined' ? 'yes' : 'no'
  270. // eslint-disable-next-line no-undef
  271. const plusDial = (typeof plus !== 'undefined' && plus.device && plus.device.dial) ? 'yes' : 'no'
  272. const winAvail = typeof window !== 'undefined' ? 'yes' : 'no'
  273. const makePhoneType = typeof uni.makePhoneCall
  274. const baseInfo = `plus:${plusAvail} | plus.device.dial:${plusDial} | window:${winAvail} | makePhoneCall:${makePhoneType} | 日志:${logs.length}条`
  275. const logText = logs.length > 0
  276. ? logs.slice(0, 5).map(l => `${l.t}\n${l.msg}`).join('\n---\n')
  277. : '暂无错误日志'
  278. uni.showModal({
  279. title: '调试信息',
  280. content: `${baseInfo}\n\n${logText}`,
  281. showCancel: true,
  282. cancelText: '清队日志',
  283. confirmText: '关闭',
  284. success: (res) => {
  285. if (res.cancel) {
  286. uni.removeStorageSync('__debug_logs')
  287. uni.showToast({ title: '日志已清队', icon: 'none' })
  288. }
  289. }
  290. })
  291. } catch (e) {
  292. uni.showModal({ title: '读取日志失败', content: e.message || String(e), showCancel: false })
  293. }
  294. }
  295. }
  296. }
  297. </script>
  298. <style lang="scss" scoped>
  299. /* 保持原有样式并添加弹窗样式 @Author: Antigravity */
  300. .my-page {
  301. min-height: 100vh;
  302. background-color: #fcfaf5;
  303. padding-bottom: 140rpx;
  304. }
  305. /* ... 之前的样式保留,下文补充新样式 ... */
  306. .service-popup-mask {
  307. position: fixed;
  308. top: 0;
  309. left: 0;
  310. right: 0;
  311. bottom: 0;
  312. background-color: rgba(0, 0, 0, 0.6);
  313. z-index: 1001;
  314. display: flex;
  315. justify-content: center;
  316. align-items: center;
  317. }
  318. .service-popup {
  319. width: 620rpx;
  320. background-color: #fff;
  321. border-radius: 48rpx;
  322. padding: 0;
  323. overflow: hidden;
  324. box-shadow: 0 20rpx 60rpx rgba(0, 0, 0, 0.1);
  325. animation: slide-in 0.3s cubic-bezier(0.18, 0.89, 0.32, 1.28);
  326. }
  327. @keyframes slide-in {
  328. from {
  329. transform: scale(0.85);
  330. opacity: 0;
  331. }
  332. to {
  333. transform: scale(1);
  334. opacity: 1;
  335. }
  336. }
  337. .service-header {
  338. display: flex;
  339. justify-content: space-between;
  340. align-items: center;
  341. padding: 40rpx;
  342. border-bottom: 2rpx solid #EEEEEE;
  343. position: relative;
  344. }
  345. .service-title {
  346. font-size: 32rpx;
  347. font-weight: 800;
  348. color: #4a3e2e;
  349. }
  350. /* CSS 关闭叉号 @Author: Antigravity */
  351. .css-close-btn {
  352. width: 44rpx;
  353. height: 44rpx;
  354. position: relative;
  355. &::before,
  356. &::after {
  357. content: '';
  358. position: absolute;
  359. top: 20rpx;
  360. left: 8rpx;
  361. width: 28rpx;
  362. height: 4rpx;
  363. background-color: #ccc;
  364. transform: rotate(45deg);
  365. border-radius: 4rpx;
  366. }
  367. &::after {
  368. transform: rotate(-45deg);
  369. }
  370. &:active {
  371. opacity: 0.6;
  372. }
  373. }
  374. .qr-section {
  375. padding: 48rpx 0;
  376. display: flex;
  377. flex-direction: column;
  378. align-items: center;
  379. background: linear-gradient(180deg, #fff 0%, #fffbf2 100%);
  380. }
  381. .qr-title {
  382. font-size: 28rpx;
  383. font-weight: 700;
  384. color: #6d5b45;
  385. margin-bottom: 30rpx;
  386. }
  387. .qr-img {
  388. width: 320rpx;
  389. height: 320rpx;
  390. border-radius: 32rpx;
  391. padding: 16rpx;
  392. background: #fff;
  393. box-shadow: 0 8rpx 32rpx rgba(247, 202, 62, 0.15);
  394. margin-bottom: 20rpx;
  395. }
  396. .qr-desc {
  397. font-size: 24rpx;
  398. color: #a39686;
  399. font-weight: 600;
  400. }
  401. .service-list {
  402. padding: 20rpx 40rpx 40rpx;
  403. }
  404. .service-row {
  405. display: flex;
  406. align-items: center;
  407. padding: 32rpx 0;
  408. border-bottom: 2rpx solid #EEEEEE;
  409. &:last-child {
  410. border-bottom: none;
  411. }
  412. }
  413. .service-icon-box {
  414. width: 88rpx;
  415. height: 88rpx;
  416. border-radius: 28rpx;
  417. display: flex;
  418. align-items: center;
  419. justify-content: center;
  420. margin-right: 24rpx;
  421. &.online-bg {
  422. background: linear-gradient(135deg, #71d192 0%, #4caf50 100%);
  423. box-shadow: 0 8rpx 16rpx rgba(76, 175, 80, 0.25);
  424. }
  425. &.phone-bg {
  426. background: linear-gradient(135deg, #ffcc33 0%, #f7ca3e 100%);
  427. box-shadow: 0 8rpx 16rpx rgba(247, 202, 62, 0.25);
  428. }
  429. }
  430. .service-icon-img {
  431. width: 56rpx;
  432. height: 56rpx;
  433. }
  434. .service-info {
  435. flex: 1;
  436. }
  437. .service-name {
  438. display: block;
  439. font-size: 28rpx;
  440. font-weight: 800;
  441. color: #4a3e2e;
  442. margin-bottom: 4rpx;
  443. }
  444. .service-desc {
  445. display: block;
  446. font-size: 22rpx;
  447. color: #a39686;
  448. font-weight: 600;
  449. }
  450. .call-btn-mini {
  451. padding: 10rpx 26rpx;
  452. border-radius: 20rpx;
  453. font-size: 24rpx;
  454. font-weight: 700;
  455. transition: transform 0.2s;
  456. background: transparent;
  457. border: 2rpx solid transparent;
  458. &:active {
  459. transform: scale(0.95);
  460. }
  461. &.green-btn {
  462. border-color: #5ec686;
  463. color: #5ec686;
  464. }
  465. &.orange-btn {
  466. border-color: #f7ca3e;
  467. color: #f7ca3e;
  468. }
  469. }
  470. /* ====== 顶部背景图区域(替换原纯色背景) ====== */
  471. .header-bg {
  472. position: relative;
  473. padding-top: calc(var(--status-bar-height, 44px) + 20rpx);
  474. height: 440rpx;
  475. overflow: hidden;
  476. }
  477. .header-img {
  478. width: 100%;
  479. height: 100%;
  480. display: block;
  481. object-fit: cover;
  482. }
  483. .user-block {
  484. display: flex;
  485. align-items: center;
  486. position: absolute;
  487. top: 50%;
  488. transform: translateY(-50%);
  489. left: 40rpx;
  490. right: 40rpx;
  491. z-index: 2;
  492. }
  493. /* CSS 右箭头 (金色) @Author: Antigravity */
  494. .css-right-arrow-gold {
  495. width: 16rpx;
  496. height: 16rpx;
  497. border-right: 4rpx solid rgba(100, 70, 20, 0.4);
  498. border-top: 4rpx solid rgba(100, 70, 20, 0.4);
  499. transform: rotate(45deg);
  500. margin-left: auto;
  501. }
  502. /* CSS 右箭头 (小号) @Author: Antigravity */
  503. .css-right-arrow-small {
  504. width: 12rpx;
  505. height: 12rpx;
  506. border-right: 3rpx solid #a39686;
  507. border-top: 3rpx solid #a39686;
  508. transform: rotate(45deg);
  509. }
  510. .user-avatar {
  511. width: 140rpx;
  512. height: 140rpx;
  513. border: 8rpx solid #fff;
  514. border-radius: 50%;
  515. background-color: #fff;
  516. }
  517. .user-info {
  518. flex: 1;
  519. margin-left: 32rpx;
  520. }
  521. .user-name {
  522. display: block;
  523. font-size: 40rpx;
  524. font-weight: 800;
  525. color: #5c4314;
  526. margin-bottom: 12rpx;
  527. letter-spacing: 2rpx;
  528. }
  529. .user-desc-text {
  530. display: block;
  531. font-size: 24rpx;
  532. color: #5c4314;
  533. opacity: 0.85;
  534. font-weight: 600;
  535. }
  536. .cute-card {
  537. background: #fff;
  538. border-radius: 40rpx;
  539. padding: 40rpx;
  540. margin: 0 32rpx;
  541. box-shadow: 0 12rpx 40rpx rgba(220, 212, 196, 0.4);
  542. position: relative;
  543. z-index: 3;
  544. }
  545. .order-wrap {
  546. margin-top: -60rpx;
  547. border: 4rpx solid #fffdfa;
  548. }
  549. .card-head {
  550. display: flex;
  551. justify-content: space-between;
  552. align-items: center;
  553. margin-bottom: 32rpx;
  554. }
  555. .card-title {
  556. font-size: 30rpx;
  557. font-weight: 800;
  558. color: #4a3e2e;
  559. }
  560. .card-more {
  561. display: flex;
  562. align-items: center;
  563. gap: 10rpx;
  564. font-size: 24rpx;
  565. color: #a39686;
  566. font-weight: 600;
  567. }
  568. .order-nav {
  569. display: flex;
  570. justify-content: space-between;
  571. }
  572. .nav-item {
  573. display: flex;
  574. flex-direction: column;
  575. align-items: center;
  576. gap: 12rpx;
  577. }
  578. .icon-bulb {
  579. width: 84rpx;
  580. height: 84rpx;
  581. background: transparent;
  582. border-radius: 28rpx;
  583. display: flex;
  584. align-items: center;
  585. justify-content: center;
  586. }
  587. /* ====== 服务与工具网格卡片 ====== */
  588. .tool-wrap {
  589. margin-top: 40rpx;
  590. }
  591. .tool-grid {
  592. display: flex;
  593. flex-wrap: wrap;
  594. }
  595. .tool-item {
  596. width: 25%;
  597. display: flex;
  598. flex-direction: column;
  599. align-items: center;
  600. padding: 24rpx 0;
  601. gap: 10rpx;
  602. &:active {
  603. opacity: 0.7;
  604. }
  605. }
  606. .tool-icon {
  607. width: 52rpx;
  608. height: 52rpx;
  609. }
  610. .custom-icon {
  611. width: 48rpx;
  612. height: 48rpx;
  613. }
  614. .tool-text {
  615. font-size: 22rpx;
  616. color: #4a3e2e;
  617. font-weight: 600;
  618. }
  619. .nav-label {
  620. font-size: 22rpx;
  621. font-weight: 600;
  622. }
  623. .footer-msg {
  624. text-align: center;
  625. margin-top: 60rpx;
  626. margin-bottom: 40rpx;
  627. font-size: 24rpx;
  628. font-weight: 600;
  629. color: #d1c5b4;
  630. letter-spacing: 4rpx;
  631. }
  632. </style>