index.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699
  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. }
  162. } else {
  163. this.userInfo = null
  164. }
  165. },
  166. // 获取客服配置 (ID=2)
  167. async fetchCustomerSetting() {
  168. try {
  169. const res = await getCustomerServiceSetting(2)
  170. if (res) {
  171. // 商户端 request 拦截器已处理 res.data,此处 res 即为配置对象
  172. Object.assign(this.customerSetting, res)
  173. }
  174. } catch (error) {
  175. console.error('获取客服配置失败', error)
  176. }
  177. },
  178. // 跳转到登录页(未登录时)
  179. goToLogin() {
  180. if (!this.userInfo) {
  181. uni.navigateTo({ url: '/pages/login/index' })
  182. }
  183. },
  184. // 跳转到订单列表
  185. goToOrder(statusValue) {
  186. if (statusValue === 'all') {
  187. uni.reLaunch({ url: '/pages/order/list/index' })
  188. } else {
  189. uni.reLaunch({ url: `/pages/order/list/index?status=${statusValue}` })
  190. }
  191. },
  192. // 菜单项点击处理
  193. goToMenu(item) {
  194. if (item.title === '客服中心') {
  195. this.showServicePopup = true
  196. return
  197. }
  198. if (item.path) {
  199. uni.navigateTo({ url: item.path })
  200. }
  201. },
  202. // 关闭客服弹窗
  203. closeServicePopup() {
  204. this.showServicePopup = false
  205. },
  206. // 预览客服二维码
  207. previewQRCode() {
  208. if (!this.customerSetting.qrCodeUrl) return
  209. uni.previewImage({
  210. urls: [this.customerSetting.qrCodeUrl]
  211. })
  212. },
  213. // 打开在线客服
  214. openOnlineService() {
  215. if (this.customerSetting.enterpriseWechatLink) {
  216. // #ifdef H5
  217. window.location.href = this.customerSetting.enterpriseWechatLink
  218. // #endif
  219. // #ifndef H5
  220. uni.setClipboardData({
  221. data: this.customerSetting.wechatAccount || this.customerSetting.enterpriseWechatLink,
  222. success: () => {
  223. uni.showToast({ title: '客服账号已复制,请在微信中添加', icon: 'none' })
  224. }
  225. })
  226. // #endif
  227. } else {
  228. uni.showToast({ title: '在线客服暂未配置', icon: 'none' })
  229. }
  230. },
  231. // 拨打客服电话
  232. callServicePhone() {
  233. const phoneNumber = String(this.customerSetting.phoneNumber || '').trim()
  234. console.log('=== callServicePhone 触发 ===')
  235. console.log('phone number:', phoneNumber)
  236. console.log('makePhoneCall 类型:', typeof uni.makePhoneCall)
  237. if (!phoneNumber) {
  238. uni.showToast({ title: '暂无客服电话', icon: 'none' })
  239. return
  240. }
  241. uni.makePhoneCall({
  242. phoneNumber,
  243. success: () => { console.log('拨号成功') },
  244. fail: (err) => {
  245. console.error('拨号失败:', JSON.stringify(err))
  246. uni.showModal({
  247. title: '拨号失败',
  248. content: typeof err === 'object' ? JSON.stringify(err) : String(err),
  249. showCancel: false
  250. })
  251. }
  252. })
  253. },
  254. // 长按头像区备触发调试日志查看
  255. onDebugTap() {
  256. this.debugTapCount++
  257. if (this.debugTapCount >= 3) {
  258. this.debugTapCount = 0
  259. this.showDebugLog()
  260. }
  261. },
  262. // 读取并展示全局错误日志
  263. showDebugLog() {
  264. try {
  265. const logs = JSON.parse(uni.getStorageSync('__debug_logs') || '[]')
  266. // eslint-disable-next-line no-undef
  267. const plusAvail = typeof plus !== 'undefined' ? 'yes' : 'no'
  268. // eslint-disable-next-line no-undef
  269. const plusDial = (typeof plus !== 'undefined' && plus.device && plus.device.dial) ? 'yes' : 'no'
  270. const winAvail = typeof window !== 'undefined' ? 'yes' : 'no'
  271. const makePhoneType = typeof uni.makePhoneCall
  272. const baseInfo = `plus:${plusAvail} | plus.device.dial:${plusDial} | window:${winAvail} | makePhoneCall:${makePhoneType} | 日志:${logs.length}条`
  273. const logText = logs.length > 0
  274. ? logs.slice(0, 5).map(l => `${l.t}\n${l.msg}`).join('\n---\n')
  275. : '暂无错误日志'
  276. uni.showModal({
  277. title: '调试信息',
  278. content: `${baseInfo}\n\n${logText}`,
  279. showCancel: true,
  280. cancelText: '清队日志',
  281. confirmText: '关闭',
  282. success: (res) => {
  283. if (res.cancel) {
  284. uni.removeStorageSync('__debug_logs')
  285. uni.showToast({ title: '日志已清队', icon: 'none' })
  286. }
  287. }
  288. })
  289. } catch (e) {
  290. uni.showModal({ title: '读取日志失败', content: e.message || String(e), showCancel: false })
  291. }
  292. }
  293. }
  294. }
  295. </script>
  296. <style lang="scss" scoped>
  297. /* 保持原有样式并添加弹窗样式 @Author: Antigravity */
  298. .my-page {
  299. min-height: 100vh;
  300. background-color: #fcfaf5;
  301. padding-bottom: 140rpx;
  302. }
  303. /* ... 之前的样式保留,下文补充新样式 ... */
  304. .service-popup-mask {
  305. position: fixed;
  306. top: 0;
  307. left: 0;
  308. right: 0;
  309. bottom: 0;
  310. background-color: rgba(0, 0, 0, 0.6);
  311. z-index: 1001;
  312. display: flex;
  313. justify-content: center;
  314. align-items: center;
  315. }
  316. .service-popup {
  317. width: 620rpx;
  318. background-color: #fff;
  319. border-radius: 48rpx;
  320. padding: 0;
  321. overflow: hidden;
  322. box-shadow: 0 20rpx 60rpx rgba(0, 0, 0, 0.1);
  323. animation: slide-in 0.3s cubic-bezier(0.18, 0.89, 0.32, 1.28);
  324. }
  325. @keyframes slide-in {
  326. from {
  327. transform: scale(0.85);
  328. opacity: 0;
  329. }
  330. to {
  331. transform: scale(1);
  332. opacity: 1;
  333. }
  334. }
  335. .service-header {
  336. display: flex;
  337. justify-content: space-between;
  338. align-items: center;
  339. padding: 40rpx;
  340. border-bottom: 2rpx solid #EEEEEE;
  341. position: relative;
  342. }
  343. .service-title {
  344. font-size: 32rpx;
  345. font-weight: 800;
  346. color: #4a3e2e;
  347. }
  348. /* CSS 关闭叉号 @Author: Antigravity */
  349. .css-close-btn {
  350. width: 44rpx;
  351. height: 44rpx;
  352. position: relative;
  353. &::before,
  354. &::after {
  355. content: '';
  356. position: absolute;
  357. top: 20rpx;
  358. left: 8rpx;
  359. width: 28rpx;
  360. height: 4rpx;
  361. background-color: #ccc;
  362. transform: rotate(45deg);
  363. border-radius: 4rpx;
  364. }
  365. &::after {
  366. transform: rotate(-45deg);
  367. }
  368. &:active {
  369. opacity: 0.6;
  370. }
  371. }
  372. .qr-section {
  373. padding: 48rpx 0;
  374. display: flex;
  375. flex-direction: column;
  376. align-items: center;
  377. background: linear-gradient(180deg, #fff 0%, #fffbf2 100%);
  378. }
  379. .qr-title {
  380. font-size: 28rpx;
  381. font-weight: 700;
  382. color: #6d5b45;
  383. margin-bottom: 30rpx;
  384. }
  385. .qr-img {
  386. width: 320rpx;
  387. height: 320rpx;
  388. border-radius: 32rpx;
  389. padding: 16rpx;
  390. background: #fff;
  391. box-shadow: 0 8rpx 32rpx rgba(247, 202, 62, 0.15);
  392. margin-bottom: 20rpx;
  393. }
  394. .qr-desc {
  395. font-size: 24rpx;
  396. color: #a39686;
  397. font-weight: 600;
  398. }
  399. .service-list {
  400. padding: 20rpx 40rpx 40rpx;
  401. }
  402. .service-row {
  403. display: flex;
  404. align-items: center;
  405. padding: 32rpx 0;
  406. border-bottom: 2rpx solid #EEEEEE;
  407. &:last-child {
  408. border-bottom: none;
  409. }
  410. }
  411. .service-icon-box {
  412. width: 88rpx;
  413. height: 88rpx;
  414. border-radius: 28rpx;
  415. display: flex;
  416. align-items: center;
  417. justify-content: center;
  418. margin-right: 24rpx;
  419. &.online-bg {
  420. background: linear-gradient(135deg, #71d192 0%, #4caf50 100%);
  421. box-shadow: 0 8rpx 16rpx rgba(76, 175, 80, 0.25);
  422. }
  423. &.phone-bg {
  424. background: linear-gradient(135deg, #ffcc33 0%, #f7ca3e 100%);
  425. box-shadow: 0 8rpx 16rpx rgba(247, 202, 62, 0.25);
  426. }
  427. }
  428. .service-icon-img {
  429. width: 56rpx;
  430. height: 56rpx;
  431. }
  432. .service-info {
  433. flex: 1;
  434. }
  435. .service-name {
  436. display: block;
  437. font-size: 28rpx;
  438. font-weight: 800;
  439. color: #4a3e2e;
  440. margin-bottom: 4rpx;
  441. }
  442. .service-desc {
  443. display: block;
  444. font-size: 22rpx;
  445. color: #a39686;
  446. font-weight: 600;
  447. }
  448. .call-btn-mini {
  449. padding: 10rpx 26rpx;
  450. border-radius: 20rpx;
  451. font-size: 24rpx;
  452. font-weight: 700;
  453. transition: transform 0.2s;
  454. background: transparent;
  455. border: 2rpx solid transparent;
  456. &:active {
  457. transform: scale(0.95);
  458. }
  459. &.green-btn {
  460. border-color: #5ec686;
  461. color: #5ec686;
  462. }
  463. &.orange-btn {
  464. border-color: #f7ca3e;
  465. color: #f7ca3e;
  466. }
  467. }
  468. /* ====== 顶部背景图区域(替换原纯色背景) ====== */
  469. .header-bg {
  470. position: relative;
  471. padding-top: calc(var(--status-bar-height, 44px) + 20rpx);
  472. height: 440rpx;
  473. overflow: hidden;
  474. }
  475. .header-img {
  476. width: 100%;
  477. height: 100%;
  478. display: block;
  479. object-fit: cover;
  480. }
  481. .user-block {
  482. display: flex;
  483. align-items: center;
  484. position: absolute;
  485. top: 50%;
  486. transform: translateY(-50%);
  487. left: 40rpx;
  488. right: 40rpx;
  489. z-index: 2;
  490. }
  491. /* CSS 右箭头 (金色) @Author: Antigravity */
  492. .css-right-arrow-gold {
  493. width: 16rpx;
  494. height: 16rpx;
  495. border-right: 4rpx solid rgba(100, 70, 20, 0.4);
  496. border-top: 4rpx solid rgba(100, 70, 20, 0.4);
  497. transform: rotate(45deg);
  498. margin-left: auto;
  499. }
  500. /* CSS 右箭头 (小号) @Author: Antigravity */
  501. .css-right-arrow-small {
  502. width: 12rpx;
  503. height: 12rpx;
  504. border-right: 3rpx solid #a39686;
  505. border-top: 3rpx solid #a39686;
  506. transform: rotate(45deg);
  507. }
  508. .user-avatar {
  509. width: 140rpx;
  510. height: 140rpx;
  511. border: 8rpx solid #fff;
  512. border-radius: 50%;
  513. background-color: #fff;
  514. }
  515. .user-info {
  516. flex: 1;
  517. margin-left: 32rpx;
  518. }
  519. .user-name {
  520. display: block;
  521. font-size: 40rpx;
  522. font-weight: 800;
  523. color: #5c4314;
  524. margin-bottom: 12rpx;
  525. letter-spacing: 2rpx;
  526. }
  527. .user-desc-text {
  528. display: block;
  529. font-size: 24rpx;
  530. color: #5c4314;
  531. opacity: 0.85;
  532. font-weight: 600;
  533. }
  534. .cute-card {
  535. background: #fff;
  536. border-radius: 40rpx;
  537. padding: 40rpx;
  538. margin: 0 32rpx;
  539. box-shadow: 0 12rpx 40rpx rgba(220, 212, 196, 0.4);
  540. position: relative;
  541. z-index: 3;
  542. }
  543. .order-wrap {
  544. margin-top: -60rpx;
  545. border: 4rpx solid #fffdfa;
  546. }
  547. .card-head {
  548. display: flex;
  549. justify-content: space-between;
  550. align-items: center;
  551. margin-bottom: 32rpx;
  552. }
  553. .card-title {
  554. font-size: 30rpx;
  555. font-weight: 800;
  556. color: #4a3e2e;
  557. }
  558. .card-more {
  559. display: flex;
  560. align-items: center;
  561. gap: 10rpx;
  562. font-size: 24rpx;
  563. color: #a39686;
  564. font-weight: 600;
  565. }
  566. .order-nav {
  567. display: flex;
  568. justify-content: space-between;
  569. }
  570. .nav-item {
  571. display: flex;
  572. flex-direction: column;
  573. align-items: center;
  574. gap: 12rpx;
  575. }
  576. .icon-bulb {
  577. width: 84rpx;
  578. height: 84rpx;
  579. background: transparent;
  580. border-radius: 28rpx;
  581. display: flex;
  582. align-items: center;
  583. justify-content: center;
  584. }
  585. /* ====== 服务与工具网格卡片 ====== */
  586. .tool-wrap {
  587. margin-top: 40rpx;
  588. }
  589. .tool-grid {
  590. display: flex;
  591. flex-wrap: wrap;
  592. }
  593. .tool-item {
  594. width: 25%;
  595. display: flex;
  596. flex-direction: column;
  597. align-items: center;
  598. padding: 24rpx 0;
  599. gap: 10rpx;
  600. &:active {
  601. opacity: 0.7;
  602. }
  603. }
  604. .tool-icon {
  605. width: 52rpx;
  606. height: 52rpx;
  607. }
  608. .custom-icon {
  609. width: 48rpx;
  610. height: 48rpx;
  611. }
  612. .tool-text {
  613. font-size: 22rpx;
  614. color: #4a3e2e;
  615. font-weight: 600;
  616. }
  617. .nav-label {
  618. font-size: 22rpx;
  619. font-weight: 600;
  620. }
  621. .footer-msg {
  622. text-align: center;
  623. margin-top: 60rpx;
  624. margin-bottom: 40rpx;
  625. font-size: 24rpx;
  626. font-weight: 600;
  627. color: #d1c5b4;
  628. letter-spacing: 4rpx;
  629. }
  630. </style>