index.vue 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. <template>
  2. <view class="container">
  3. <!-- 中部内容 -->
  4. <view class="middle-section">
  5. <view class="app-title">AI零售柜</view>
  6. </view>
  7. <!-- 核心扫码按钮区域 -->
  8. <view class="scan-area">
  9. <!-- 左侧我的按钮 -->
  10. <view class="bottom-item" @click="goToMy">
  11. <image class="bottom-icon" src="/static/icons/my.svg"></image>
  12. <view class="bottom-text">我的</view>
  13. </view>
  14. <!-- 核心扫码按钮 -->
  15. <button class="scan-button" @click="scanCode">
  16. <view class="scan-icon">
  17. <image class="scan-svg-icon" src="/static/icons/scan.svg"></image>
  18. </view>
  19. <view class="scan-text">扫码开门</view>
  20. </button>
  21. <!-- 右侧退款按钮 -->
  22. <view class="bottom-item" @click="goToRefund">
  23. <image class="bottom-icon" src="/static/icons/refund.svg"></image>
  24. <view class="bottom-text">退款</view>
  25. </view>
  26. </view>
  27. <!-- 底部信息 -->
  28. <view class="info-section">
  29. <text class="info-text">💚 微信支付分 | 550分及以上优享</text>
  30. <text class="info-text">客服电话:400-123-4567</text>
  31. </view>
  32. </view>
  33. </template>
  34. <script setup lang="ts">
  35. import { ref } from 'vue'
  36. import { scanDoor } from '../../api/device'
  37. const scanCode = () => {
  38. // 调用摄像头扫码
  39. uni.scanCode({
  40. success: async function (res) {
  41. console.log('扫码结果:', res.result);
  42. // 从扫码结果中解析设备ID
  43. // 二维码格式: https://hh.hahabianli.com/B142977?_wxpmm0=6009000C0000
  44. // 需要提取路径中的设备ID: B142977
  45. let deviceId = '';
  46. try {
  47. // 尝试从URL中提取deviceId
  48. const urlPattern = /\/([A-Z0-9]+)(\?|$)/;
  49. const match = res.result.match(urlPattern);
  50. if (match && match[1]) {
  51. deviceId = match[1];
  52. console.log('提取到设备ID:', deviceId);
  53. } else {
  54. // 如果不是URL格式,尝试解析JSON格式
  55. try {
  56. const qrData = JSON.parse(res.result);
  57. if (qrData.deviceId) {
  58. deviceId = qrData.deviceId;
  59. }
  60. } catch (e) {
  61. // 不是JSON格式,直接使用扫码结果作为deviceId
  62. deviceId = res.result;
  63. }
  64. }
  65. if (!deviceId) {
  66. throw new Error('无法解析设备ID');
  67. }
  68. } catch (error) {
  69. console.error('解析设备ID失败:', error);
  70. uni.showToast({
  71. title: '二维码格式错误',
  72. icon: 'none'
  73. });
  74. return;
  75. }
  76. // 显示加载提示
  77. uni.showLoading({
  78. title: '正在开门...',
  79. mask: true
  80. });
  81. try {
  82. // 调用真实接口扫码开门
  83. const response = await scanDoor(deviceId);
  84. // 隐藏加载提示
  85. uni.hideLoading();
  86. // 开门成功
  87. uni.showToast({
  88. title: '开门成功',
  89. icon: 'success'
  90. });
  91. // 将设备信息存储到本地,供购物页面使用
  92. uni.setStorageSync('currentDeviceId', response.deviceId);
  93. uni.setStorageSync('currentOutTradeNo', response.outTradeNo);
  94. uni.setStorageSync('currentOrderNo', response.orderNo);
  95. // 跳转到购物进行中页面
  96. setTimeout(() => {
  97. uni.navigateTo({
  98. url: '/pages/shopping/shopping'
  99. });
  100. }, 1000);
  101. } catch (error: any) {
  102. // 隐藏加载提示
  103. uni.hideLoading();
  104. console.error('开门失败:', error);
  105. // 错误信息已经在request工具中显示,这里不需要重复显示
  106. }
  107. },
  108. fail: function (err) {
  109. console.log('扫码失败:', err);
  110. uni.showToast({
  111. title: '扫码失败',
  112. icon: 'none'
  113. });
  114. }
  115. });
  116. };
  117. const goToMy = () => {
  118. // 跳转到个人中心
  119. uni.navigateTo({
  120. url: '/pages/my/my'
  121. });
  122. };
  123. const goToRefund = () => {
  124. // 跳转到订单列表页面
  125. uni.navigateTo({
  126. url: '/pages/orders/orders'
  127. });
  128. };
  129. </script>
  130. <style>
  131. .container {
  132. display: flex;
  133. flex-direction: column;
  134. min-height: 100vh;
  135. background-color: #ffffff;
  136. position: relative;
  137. padding-bottom: 100rpx;
  138. margin: 0;
  139. padding-left: 0;
  140. padding-right: 0;
  141. box-sizing: border-box;
  142. }
  143. /* 中部内容区域 */
  144. .middle-section {
  145. flex: 1;
  146. display: flex;
  147. flex-direction: column;
  148. align-items: center;
  149. justify-content: flex-start;
  150. padding: 120rpx 0 20rpx;
  151. position: relative;
  152. z-index: 1;
  153. min-height: 40vh;
  154. flex-basis: 40vh;
  155. }
  156. /* 应用标题 */
  157. .app-title {
  158. font-size: 80rpx;
  159. font-weight: bold;
  160. color: #333;
  161. text-align: center;
  162. margin-bottom: 40rpx;
  163. letter-spacing: 6rpx;
  164. position: relative;
  165. animation: titleSlide 0.8s ease-out;
  166. }
  167. /* 标题动画 */
  168. @keyframes titleSlide {
  169. 0% {
  170. opacity: 0;
  171. transform: translateY(-50rpx);
  172. }
  173. 100% {
  174. opacity: 1;
  175. transform: translateY(0);
  176. }
  177. }
  178. /* 标题下方装饰线 */
  179. .app-title::after {
  180. content: '';
  181. position: absolute;
  182. bottom: -20rpx;
  183. left: 50%;
  184. transform: translateX(-50%);
  185. width: 150rpx;
  186. height: 6rpx;
  187. background-color: #FFD700;
  188. border-radius: 3rpx;
  189. animation: lineSlide 0.8s ease-out 0.3s both;
  190. }
  191. /* 装饰线动画 */
  192. @keyframes lineSlide {
  193. 0% {
  194. width: 0;
  195. opacity: 0;
  196. }
  197. 100% {
  198. width: 150rpx;
  199. opacity: 1;
  200. }
  201. }
  202. /* 扫码按钮区域 */
  203. .scan-area {
  204. display: flex;
  205. align-items: center;
  206. justify-content: space-around;
  207. padding: 0 80rpx 50rpx;
  208. position: relative;
  209. z-index: 1;
  210. }
  211. /* 核心扫码按钮 */
  212. .scan-button {
  213. display: flex;
  214. flex-direction: column;
  215. align-items: center;
  216. justify-content: center;
  217. width: 350rpx;
  218. height: 350rpx;
  219. border-radius: 50%;
  220. background-color: #FFD700;
  221. border: none;
  222. box-shadow: 0 8rpx 30rpx rgba(255, 215, 0, 0.6);
  223. z-index: 2;
  224. transition: all 0.3s ease;
  225. position: relative;
  226. overflow: hidden;
  227. }
  228. /* 扫码按钮悬停效果 */
  229. .scan-button:hover {
  230. transform: scale(1.05);
  231. box-shadow: 0 12rpx 40rpx rgba(255, 215, 0, 0.8);
  232. }
  233. /* 扫码按钮点击效果 */
  234. .scan-button:active {
  235. transform: scale(0.95);
  236. box-shadow: 0 4rpx 20rpx rgba(255, 215, 0, 0.6);
  237. }
  238. /* 扫码按钮脉冲动画 */
  239. .scan-button::before {
  240. content: '';
  241. position: absolute;
  242. top: 50%;
  243. left: 50%;
  244. width: 0;
  245. height: 0;
  246. border-radius: 50%;
  247. background-color: rgba(255, 255, 255, 0.3);
  248. transform: translate(-50%, -50%);
  249. animation: pulse 2s infinite;
  250. }
  251. @keyframes pulse {
  252. 0% {
  253. width: 0;
  254. height: 0;
  255. opacity: 0.5;
  256. }
  257. 100% {
  258. width: 400rpx;
  259. height: 400rpx;
  260. opacity: 0;
  261. }
  262. }
  263. /* 扫码图标 */
  264. .scan-icon {
  265. width: 200rpx;
  266. height: 200rpx;
  267. margin-bottom: 20rpx;
  268. display: flex;
  269. align-items: center;
  270. justify-content: center;
  271. }
  272. /* 扫码SVG图标 */
  273. .scan-svg-icon {
  274. width: 200rpx;
  275. height: 200rpx;
  276. }
  277. /* 扫码文字 */
  278. .scan-text {
  279. font-size: 32rpx;
  280. font-weight: bold;
  281. color: #333;
  282. }
  283. /* 底部操作按钮 */
  284. .bottom-item {
  285. display: flex;
  286. flex-direction: column;
  287. align-items: center;
  288. z-index: 1;
  289. transition: all 0.3s ease;
  290. padding: 20rpx;
  291. border-radius: 15rpx;
  292. }
  293. /* 底部按钮悬停效果 */
  294. .bottom-item:hover {
  295. transform: translateY(-5rpx);
  296. background-color: #f5f5f5;
  297. }
  298. /* 底部按钮点击效果 */
  299. .bottom-item:active {
  300. transform: translateY(0);
  301. background-color: #e8e8e8;
  302. }
  303. /* 底部图标 */
  304. .bottom-icon {
  305. width: 50rpx;
  306. height: 50rpx;
  307. margin-bottom: 10rpx;
  308. }
  309. /* 底部文字 */
  310. .bottom-text {
  311. font-size: 24rpx;
  312. color: #666;
  313. }
  314. /* 底部信息区域 */
  315. .info-section {
  316. padding: 30rpx 0;
  317. text-align: center;
  318. background-color: #fff;
  319. border-top: 1rpx solid #eee;
  320. position: relative;
  321. z-index: 1;
  322. animation: infoSlide 0.8s ease-out 0.5s both;
  323. }
  324. /* 底部信息动画 */
  325. @keyframes infoSlide {
  326. 0% {
  327. opacity: 0;
  328. transform: translateY(30rpx);
  329. }
  330. 100% {
  331. opacity: 1;
  332. transform: translateY(0);
  333. }
  334. }
  335. /* 信息文字 */
  336. .info-text {
  337. font-size: 22rpx;
  338. color: #999;
  339. margin: 8rpx 0;
  340. display: block;
  341. }
  342. </style>