| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444 |
- <template>
- <view class="container">
- <!-- 顶部品牌区 -->
- <view class="brand-section">
- <text class="brand-title">AI零售柜</text>
- <text class="brand-slogan">智能视觉 · 即拿即走</text>
- </view>
- <!-- 核心操作区 -->
- <view class="action-section">
- <button class="scan-button" @click="scanCode">
- <view class="scan-button-inner">
- <image class="scan-icon" src="/static/icons/scan.svg" mode="aspectFill"></image>
- <text class="scan-text">扫码开门</text>
- </view>
- <view class="scan-button-ripple"></view>
- </button>
- </view>
- <!-- 快捷功能区 -->
- <view class="quick-actions">
- <view class="quick-action-item" @click="goToMy">
- <view class="action-icon">
- <image src="/static/icons/my.svg" mode="aspectFit"></image>
- </view>
- <text class="action-label">我的</text>
- </view>
- <view class="quick-action-item" @click="goToOrders">
- <view class="action-icon">
- <image src="/static/icons/orders.svg" mode="aspectFit"></image>
- </view>
- <text class="action-label">订单</text>
- </view>
- <view class="quick-action-item" @click="goToCouponCenter">
- <view class="action-icon">
- <image src="/static/icons/coupon.svg" mode="aspectFit"></image>
- </view>
- <text class="action-label">优惠券</text>
- </view>
- </view>
- <!-- 底部信息卡片 -->
- <view class="info-card">
- <view class="info-item">
- <text class="info-icon">💚</text>
- <text class="info-text">微信支付分 550分及以上优享</text>
- </view>
- <view class="info-item">
- <text class="info-icon">📞</text>
- <text class="info-text">客服: 400-0759-515</text>
- </view>
- </view>
- </view>
- </template>
- <script setup lang="ts">
- import { ref, onMounted } from 'vue'
- import { onShow } from '@dcloudio/uni-app'
- import { scanDoor } from '../../api/device'
- import { checkPayscoreEnabled } from '../../api/payscore'
- import { getCouponCount } from '../../api/coupon'
- import { isLoggedIn, getToken } from '../../utils/auth'
- // 页面显示时检查 token
- onShow(() => {
- const token = getToken()
- console.log('[首页 onShow] token 状态:', token ? '存在' : '不存在')
- })
- // 处理支付分开通成功
- const onPayscoreEnabled = () => {
- console.log('[首页] 用户已开通支付分')
- // 可以自动触发扫码
- setTimeout(() => {
- scanCode()
- }, 500)
- }
- // 定义暴露给其他页面的方法
- defineExpose({
- onPayscoreEnabled
- })
- const scanCode = async () => {
- // 检查登录状态
- if (!isLoggedIn()) {
- uni.showModal({
- title: '提示',
- content: '请先登录后再扫码开门',
- showCancel: false,
- success: () => {
- uni.reLaunch({
- url: '/pages/login/login?redirect=/pages/index/index'
- });
- }
- });
- return;
- }
- // 检查微信支付分开通状态
- try {
- const payscoreResult = await checkPayscoreEnabled()
- if (!payscoreResult.enabled) {
- // 未开通,跳转到开通页面
- uni.navigateTo({
- url: '/pages/payscore/enable'
- })
- return
- }
- } catch (error: any) {
- console.error('检查支付分状态失败:', error)
- // 如果检查失败,也跳转到开通页面
- uni.navigateTo({
- url: '/pages/payscore/enable'
- })
- return
- }
- // 调用摄像头扫码
- uni.scanCode({
- success: async function (res) {
- console.log('扫码结果:', res.result);
- // 从扫码结果中解析设备ID
- // 二维码格式: https://hh.hahabianli.com/B142977?_wxpmm0=6009000C0000
- // 需要提取路径中的设备ID: B142977
- let deviceId = '';
- try {
- // 尝试从URL中提取deviceId
- const urlPattern = /\/([A-Z0-9]+)(\?|$)/;
- const match = res.result.match(urlPattern);
- if (match && match[1]) {
- deviceId = match[1];
- console.log('提取到设备ID:', deviceId);
- } else {
- // 如果不是URL格式,尝试解析JSON格式
- try {
- const qrData = JSON.parse(res.result);
- if (qrData.deviceId) {
- deviceId = qrData.deviceId;
- }
- } catch (e) {
- // 不是JSON格式,直接使用扫码结果作为deviceId
- deviceId = res.result;
- }
- }
- if (!deviceId) {
- throw new Error('无法解析设备ID');
- }
- } catch (error) {
- console.error('解析设备ID失败:', error);
- uni.showToast({
- title: '二维码格式错误',
- icon: 'none'
- });
- return;
- }
- // 显示加载提示
- uni.showLoading({
- title: '正在开门...',
- mask: true
- });
- try {
- // 调用真实接口扫码开门
- const response = await scanDoor(deviceId);
- // 隐藏加载提示
- uni.hideLoading();
- // 开门成功
- uni.showToast({
- title: '开门成功',
- icon: 'success'
- });
- // 将设备信息存储到本地,供购物页面使用
- uni.setStorageSync('currentDeviceId', response.deviceId);
- uni.setStorageSync('currentOutTradeNo', response.outTradeNo);
- uni.setStorageSync('currentOrderNo', response.orderNo);
- // 清理可能存在的旧轮询状态标记
- uni.removeStorageSync('shoppingPollingActive');
- // 跳转到购物进行中页面
- setTimeout(() => {
- uni.navigateTo({
- url: '/pages/shopping/shopping'
- });
- }, 1000);
- } catch (error: any) {
- // 隐藏加载提示
- uni.hideLoading();
- console.error('开门失败:', error);
- // 错误信息已经在request工具中显示,这里不需要重复显示
- }
- },
- fail: function (err) {
- console.log('扫码取消:', err);
- uni.showToast({
- title: '扫码取消',
- icon: 'none'
- });
- }
- });
- };
- const goToMy = () => {
- uni.vibrateShort({ type: 'light' })
- uni.navigateTo({
- url: '/pages/my/my'
- })
- }
- const goToOrders = () => {
- uni.vibrateShort({ type: 'light' })
- uni.navigateTo({
- url: '/pages/orders/orders'
- })
- }
- const goToCouponCenter = () => {
- uni.vibrateShort({ type: 'light' })
- uni.navigateTo({
- url: '/pages/couponCenter/couponCenter'
- })
- }
- </script>
- <style lang="scss">
- .container {
- min-height: 100vh;
- background: $color-bg-secondary;
- display: flex;
- flex-direction: column;
- padding: $spacing-xxl $spacing-lg;
- padding-bottom: calc(100rpx + constant(safe-area-inset-bottom));
- padding-bottom: calc(100rpx + env(safe-area-inset-bottom));
- box-sizing: border-box;
- }
- /* ========== 品牌区 ========== */
- .brand-section {
- text-align: center;
- padding: $spacing-xxl 0 $spacing-xl;
- animation: slideUp 0.6s cubic-bezier(0.25, 0.1, 0.25, 1);
-
- .brand-title {
- font-size: 56rpx;
- font-weight: 300;
- color: $color-text-primary;
- letter-spacing: 8rpx;
- display: block;
- margin-bottom: $spacing-sm;
- }
-
- .brand-slogan {
- font-size: 24rpx;
- color: $color-text-secondary;
- letter-spacing: 4rpx;
- }
- }
- /* ========== 核心操作区 ========== */
- .action-section {
- display: flex;
- justify-content: center;
- align-items: center;
- padding: $spacing-xl 0;
- animation: scaleIn 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55);
- }
- .scan-button {
- position: relative;
- width: 400rpx;
- height: 400rpx;
- min-width: 400rpx;
- min-height: 400rpx;
- aspect-ratio: 1 / 1;
- border-radius: 50%;
- background: transparent;
- border: none;
- padding: 0;
- margin: 0;
- overflow: hidden;
-
- &::after {
- border: none;
- }
-
- &-inner {
- width: 100%;
- height: 100%;
- border-radius: 50%;
- background: linear-gradient(135deg, $color-primary-light 0%, $color-primary 100%);
- box-shadow: $shadow-primary;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- transition: all $duration-normal $ease-out;
-
- &:active {
- transform: scale(0.95);
- box-shadow: 0 4rpx 16rpx rgba(255, 193, 7, 0.3);
- }
- }
-
- &-ripple {
- position: absolute;
- top: 50%;
- left: 50%;
- width: 100%;
- height: 100%;
- border-radius: 50%;
- background: $color-primary;
- transform: translate(-50%, -50%);
- animation: pulse 2s cubic-bezier(0.42, 0, 0.58, 1) infinite;
- opacity: 0.3;
- z-index: -1;
- }
- }
- .scan-icon {
- width: 160rpx;
- height: 160rpx;
- margin-bottom: $spacing-md;
- flex-shrink: 0;
- display: block;
- }
- .scan-text {
- font-size: 40rpx;
- font-weight: 600;
- color: #1A1A1A;
- letter-spacing: 4rpx;
- }
- /* ========== 快捷功能区 ========== */
- .quick-actions {
- display: flex;
- justify-content: center;
- align-items: center;
- gap: $spacing-xl;
- margin: $spacing-xxl 0;
- animation: slideUp 1s cubic-bezier(0.25, 0.1, 0.25, 1);
-
- &-item {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- padding: $spacing-md;
- transition: all $duration-fast $ease-out;
- position: relative;
- flex: 0 0 auto;
-
- &:active {
- transform: scale(0.9);
- }
- }
-
- .action-icon {
- width: 120rpx;
- height: 120rpx;
- background: #ffffff;
- border-radius: $radius-lg;
- display: flex;
- align-items: center;
- justify-content: center;
- box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
- margin-bottom: $spacing-sm;
- transition: all $duration-fast $ease-out;
- border: 2rpx solid #F0F0F0;
- flex-shrink: 0;
-
- image {
- width: 64rpx;
- height: 64rpx;
- display: block;
- }
-
- &:active {
- box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
- transform: translateY(-4rpx);
- border-color: $color-primary-light;
- }
- }
-
- .action-label {
- font-size: 28rpx;
- color: $color-text-primary;
- font-weight: 500;
- text-align: center;
- display: block;
- width: auto;
- }
- }
- /* ========== 底部信息卡片 ========== */
- .info-card {
- background: $color-bg-primary;
- border-radius: $radius-lg;
- padding: $spacing-lg;
- box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
- animation: slideUp 1.2s cubic-bezier(0.25, 0.1, 0.25, 1);
- margin-top: auto;
- width: 100%;
- box-sizing: border-box;
-
- .info-item {
- display: flex;
- align-items: center;
- padding: $spacing-sm 0;
- min-height: 60rpx;
-
- &:not(:last-child) {
- border-bottom: 1rpx solid $color-border;
- padding-bottom: $spacing-md;
- margin-bottom: $spacing-md;
- }
- }
-
- .info-icon {
- font-size: 28rpx;
- margin-right: $spacing-sm;
- flex-shrink: 0;
- line-height: 1;
- }
-
- .info-text {
- font-size: 24rpx;
- color: $color-text-secondary;
- line-height: 1.5;
- flex: 1;
- word-break: break-all;
- }
- }
- </style>
|