index.vue 16 KB

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