Commit a16b16b817a405bf69eca53ff2ff2448db114653
1 parent
162b9d70
收银台页面开发中
Showing
4 changed files
with
888 additions
and
19 deletions
apps/web-payment/package.json
| @@ -26,6 +26,7 @@ | @@ -26,6 +26,7 @@ | ||
| 26 | "#/*": "./src/*" | 26 | "#/*": "./src/*" |
| 27 | }, | 27 | }, |
| 28 | "dependencies": { | 28 | "dependencies": { |
| 29 | + "@element-plus/icons-vue": "^2.3.2", | ||
| 29 | "@vben/access": "workspace:*", | 30 | "@vben/access": "workspace:*", |
| 30 | "@vben/common-ui": "workspace:*", | 31 | "@vben/common-ui": "workspace:*", |
| 31 | "@vben/constants": "workspace:*", | 32 | "@vben/constants": "workspace:*", |
apps/web-payment/src/composables/environmentDetector.ts
0 → 100644
| 1 | +/** | ||
| 2 | + * H5收银台环境检测工具 (TypeScript版本) | ||
| 3 | + * 用于识别当前运行环境:普通浏览器、微信浏览器、小程序webview等 | ||
| 4 | + */ | ||
| 5 | + | ||
| 6 | +// 环境信息接口 | ||
| 7 | +interface EnvironmentInfo { | ||
| 8 | + isWeChat: boolean; | ||
| 9 | + isMiniProgram: boolean; | ||
| 10 | + isMiniProgramAsync: boolean | null; | ||
| 11 | + isAlipay: boolean; | ||
| 12 | + isAlipayMiniProgram: boolean; | ||
| 13 | + isNormalBrowser: boolean; | ||
| 14 | + os: string; | ||
| 15 | + browser: string; | ||
| 16 | +} | ||
| 17 | + | ||
| 18 | +// 微信小程序API类型声明 | ||
| 19 | +interface WxMiniProgram { | ||
| 20 | + getEnv: (callback: (res: { miniprogram: boolean }) => void) => void; | ||
| 21 | + navigateTo?: (options: { url: string }) => void; | ||
| 22 | + postMessage?: (options: { data: any }) => void; | ||
| 23 | +} | ||
| 24 | + | ||
| 25 | +interface WxNamespace { | ||
| 26 | + miniProgram: WxMiniProgram; | ||
| 27 | +} | ||
| 28 | + | ||
| 29 | +declare global { | ||
| 30 | + interface Window { | ||
| 31 | + wx?: WxNamespace; | ||
| 32 | + } | ||
| 33 | +} | ||
| 34 | + | ||
| 35 | +class EnvironmentDetector { | ||
| 36 | + public env: EnvironmentInfo; | ||
| 37 | + private ua: string; | ||
| 38 | + | ||
| 39 | + constructor() { | ||
| 40 | + this.ua = navigator.userAgent.toLowerCase(); | ||
| 41 | + this.env = this.detect(); | ||
| 42 | + } | ||
| 43 | + | ||
| 44 | + /** | ||
| 45 | + * 获取环境描述 | ||
| 46 | + */ | ||
| 47 | + public getEnvironmentDesc(): string { | ||
| 48 | + if (this.env.isMiniProgram || this.env.isAlipayMiniProgram) { | ||
| 49 | + return '小程序webview'; | ||
| 50 | + } | ||
| 51 | + if (this.env.isWeChat) { | ||
| 52 | + return '微信浏览器'; | ||
| 53 | + } | ||
| 54 | + if (this.env.isAlipay) { | ||
| 55 | + return '支付宝浏览器'; | ||
| 56 | + } | ||
| 57 | + return '普通浏览器'; | ||
| 58 | + } | ||
| 59 | + | ||
| 60 | + /** | ||
| 61 | + * 获取完整环境信息(包含异步检测结果) | ||
| 62 | + */ | ||
| 63 | + public async getFullEnvironment(): Promise<EnvironmentInfo> { | ||
| 64 | + const isMiniProgramAsync = await this.isMiniProgramAsync(); | ||
| 65 | + this.env.isMiniProgramAsync = isMiniProgramAsync; | ||
| 66 | + return this.env; | ||
| 67 | + } | ||
| 68 | + | ||
| 69 | + /** | ||
| 70 | + * 异步检测是否在微信小程序中(更准确) | ||
| 71 | + * 使用微信JSSDK的方法 | ||
| 72 | + */ | ||
| 73 | + public async isMiniProgramAsync(): Promise<boolean> { | ||
| 74 | + if (!this.isWeChat()) { | ||
| 75 | + return false; | ||
| 76 | + } | ||
| 77 | + | ||
| 78 | + return new Promise<boolean>((resolve) => { | ||
| 79 | + // 如果UA中已经有miniprogram标识,直接返回true | ||
| 80 | + if (this.isMiniProgram()) { | ||
| 81 | + resolve(true); | ||
| 82 | + return; | ||
| 83 | + } | ||
| 84 | + | ||
| 85 | + // 使用wx.miniProgram.getEnv检测 | ||
| 86 | + if (window.wx !== undefined && window.wx.miniProgram) { | ||
| 87 | + window.wx.miniProgram.getEnv((res) => { | ||
| 88 | + resolve(res.miniprogram === true); | ||
| 89 | + }); | ||
| 90 | + } else { | ||
| 91 | + // 如果wx对象不存在,可能还没加载,等待一下 | ||
| 92 | + setTimeout(() => { | ||
| 93 | + if (window.wx !== undefined && window.wx.miniProgram) { | ||
| 94 | + window.wx.miniProgram.getEnv((res) => { | ||
| 95 | + resolve(res.miniprogram === true); | ||
| 96 | + }); | ||
| 97 | + } else { | ||
| 98 | + resolve(false); | ||
| 99 | + } | ||
| 100 | + }, 300); | ||
| 101 | + } | ||
| 102 | + }); | ||
| 103 | + } | ||
| 104 | + | ||
| 105 | + /** | ||
| 106 | + * 打印环境信息 | ||
| 107 | + */ | ||
| 108 | + public logEnvironment(): void { | ||
| 109 | + console.log('===== 环境检测结果 ====='); | ||
| 110 | + console.log('环境描述:', this.getEnvironmentDesc()); | ||
| 111 | + console.log('详细信息:', this.env); | ||
| 112 | + console.log('User Agent:', this.ua); | ||
| 113 | + console.log('======================'); | ||
| 114 | + } | ||
| 115 | + | ||
| 116 | + /** | ||
| 117 | + * 检测当前环境 | ||
| 118 | + * @returns {EnvironmentInfo} 环境信息对象 | ||
| 119 | + */ | ||
| 120 | + private detect(): EnvironmentInfo { | ||
| 121 | + return { | ||
| 122 | + isWeChat: this.isWeChat(), | ||
| 123 | + isMiniProgram: this.isMiniProgram(), | ||
| 124 | + isMiniProgramAsync: null, | ||
| 125 | + isAlipay: this.isAlipay(), | ||
| 126 | + isAlipayMiniProgram: this.isAlipayMiniProgram(), | ||
| 127 | + isNormalBrowser: this.isNormalBrowser(), | ||
| 128 | + os: this.getOS(), | ||
| 129 | + browser: this.getBrowser(), | ||
| 130 | + }; | ||
| 131 | + } | ||
| 132 | + | ||
| 133 | + /** | ||
| 134 | + * 获取浏览器类型 | ||
| 135 | + */ | ||
| 136 | + private getBrowser(): string { | ||
| 137 | + if (this.isWeChat()) return 'WeChat'; | ||
| 138 | + if (this.isAlipay()) return 'Alipay'; | ||
| 139 | + if (/chrome/.test(this.ua)) return 'Chrome'; | ||
| 140 | + if (/safari/.test(this.ua)) return 'Safari'; | ||
| 141 | + if (/firefox/.test(this.ua)) return 'Firefox'; | ||
| 142 | + return 'Other'; | ||
| 143 | + } | ||
| 144 | + | ||
| 145 | + /** | ||
| 146 | + * 获取操作系统 | ||
| 147 | + */ | ||
| 148 | + private getOS(): string { | ||
| 149 | + if (/android/.test(this.ua)) return 'Android'; | ||
| 150 | + if (/iphone|ipad|ipod/.test(this.ua)) return 'iOS'; | ||
| 151 | + if (/windows/.test(this.ua)) return 'Windows'; | ||
| 152 | + if (/mac/.test(this.ua)) return 'MacOS'; | ||
| 153 | + return 'Unknown'; | ||
| 154 | + } | ||
| 155 | + | ||
| 156 | + /** | ||
| 157 | + * 检测是否在支付宝中 | ||
| 158 | + */ | ||
| 159 | + private isAlipay(): boolean { | ||
| 160 | + return /alipayclient/.test(this.ua); | ||
| 161 | + } | ||
| 162 | + | ||
| 163 | + /** | ||
| 164 | + * 检测是否在支付宝小程序中 | ||
| 165 | + */ | ||
| 166 | + private isAlipayMiniProgram(): boolean { | ||
| 167 | + return /miniprogram/.test(this.ua) && this.isAlipay(); | ||
| 168 | + } | ||
| 169 | + | ||
| 170 | + /** | ||
| 171 | + * 同步检测是否在小程序中(通过UA) | ||
| 172 | + * 注意:这个方法不够准确,建议配合异步方法使用 | ||
| 173 | + */ | ||
| 174 | + private isMiniProgram(): boolean { | ||
| 175 | + // 微信小程序webview会包含miniprogram标识 | ||
| 176 | + return /miniprogram/.test(this.ua) && this.isWeChat(); | ||
| 177 | + } | ||
| 178 | + | ||
| 179 | + /** | ||
| 180 | + * 检测是否在普通浏览器中(非微信、非支付宝等) | ||
| 181 | + */ | ||
| 182 | + private isNormalBrowser(): boolean { | ||
| 183 | + return !this.isWeChat() && !this.isAlipay(); | ||
| 184 | + } | ||
| 185 | + | ||
| 186 | + /** | ||
| 187 | + * 检测是否在微信中 | ||
| 188 | + */ | ||
| 189 | + private isWeChat(): boolean { | ||
| 190 | + return /micromessenger/.test(this.ua); | ||
| 191 | + } | ||
| 192 | +} | ||
| 193 | + | ||
| 194 | +export default EnvironmentDetector; | ||
| 195 | +export type { EnvironmentInfo, WxMiniProgram, WxNamespace }; |
apps/web-payment/src/views/payment/index.vue
| 1 | +<script setup lang="ts"> | ||
| 2 | +import { ref } from 'vue'; | ||
| 3 | + | ||
| 4 | +import { ArrowRight, CreditCard, SuccessFilled } from '@element-plus/icons-vue'; | ||
| 5 | +import { ElButton, ElCard, ElDialog, ElIcon, ElMessage } from 'element-plus'; | ||
| 6 | + | ||
| 7 | +import EnvironmentDetector from '#/composables/environmentDetector'; | ||
| 8 | +// 类型定义 | ||
| 9 | +interface Card { | ||
| 10 | + id: string; | ||
| 11 | + cardNo: string; | ||
| 12 | + balance: number; | ||
| 13 | + holderName: string; | ||
| 14 | +} | ||
| 15 | + | ||
| 16 | +// 创建检测器实例 | ||
| 17 | +const detector = new EnvironmentDetector(); | ||
| 18 | +detector.logEnvironment(); | ||
| 19 | + | ||
| 20 | +// 模拟的园区卡数据 | ||
| 21 | +const mockCards: Card[] = [ | ||
| 22 | + { | ||
| 23 | + id: '1', | ||
| 24 | + cardNo: '6225 8801 2345 6789', | ||
| 25 | + balance: 1580.5, | ||
| 26 | + holderName: '张三', | ||
| 27 | + }, | ||
| 28 | + { id: '2', cardNo: '6225 8801 9876 5432', balance: 3200, holderName: '李四' }, | ||
| 29 | + { | ||
| 30 | + id: '3', | ||
| 31 | + cardNo: '6225 8801 1111 2222', | ||
| 32 | + balance: 500.8, | ||
| 33 | + holderName: '王五', | ||
| 34 | + }, | ||
| 35 | +]; | ||
| 36 | + | ||
| 37 | +// 响应式数据 | ||
| 38 | +const amount = ref<number>(128.5); | ||
| 39 | +const payBtnShow = ref<boolean>(false); | ||
| 40 | +const currentPaymentMethod = ref<string>(''); | ||
| 41 | +const showCardDialog = ref<boolean>(false); | ||
| 42 | +const selectedCard = ref<Card | null>(null); | ||
| 43 | +const loading = ref<boolean>(false); | ||
| 44 | +const paymentSuccess = ref<boolean>(false); | ||
| 45 | + | ||
| 46 | +// 方法 | ||
| 47 | +const handlepayBtnShowClick = (method: string) => { | ||
| 48 | + currentPaymentMethod.value = method; | ||
| 49 | + payBtnShow.value = false; | ||
| 50 | + if (method === 'card') { | ||
| 51 | + showCardDialog.value = true; | ||
| 52 | + } else { | ||
| 53 | + payBtnShow.value = true; | ||
| 54 | + } | ||
| 55 | +}; | ||
| 56 | + | ||
| 57 | +const handleCardSelect = (card: Card) => { | ||
| 58 | + selectedCard.value = card; | ||
| 59 | + showCardDialog.value = false; | ||
| 60 | + ElMessage.success(`已选择 ${card.holderName} 的园区卡`); | ||
| 61 | + payBtnShow.value = true; | ||
| 62 | + // 园区卡选择后自动支付 | ||
| 63 | + // handlePay(); | ||
| 64 | +}; | ||
| 65 | + | ||
| 66 | +const handlePay = async () => { | ||
| 67 | + loading.value = true; | ||
| 68 | + | ||
| 69 | + // 模拟支付处理 | ||
| 70 | + setTimeout(() => { | ||
| 71 | + loading.value = false; | ||
| 72 | + paymentSuccess.value = true; | ||
| 73 | + | ||
| 74 | + // 2秒后重置状态 | ||
| 75 | + setTimeout(() => { | ||
| 76 | + paymentSuccess.value = false; | ||
| 77 | + payBtnShow.value = false; | ||
| 78 | + selectedCard.value = null; | ||
| 79 | + }, 2000); | ||
| 80 | + }, 1500); | ||
| 81 | +}; | ||
| 82 | + | ||
| 83 | +const handleCloseDialog = () => { | ||
| 84 | + showCardDialog.value = false; | ||
| 85 | + payBtnShow.value = false; | ||
| 86 | +}; | ||
| 87 | +</script> | ||
| 88 | + | ||
| 1 | <template> | 89 | <template> |
| 2 | - <div>收银台!!!!!!!!</div> | 90 | + <div class="cashier-container"> |
| 91 | + <div class="cashier-wrapper"> | ||
| 92 | + <!-- 头部 --> | ||
| 93 | + <div class="header"> | ||
| 94 | + <div class="icon-wrapper"> | ||
| 95 | + <ElIcon :size="32" color="#fff"> | ||
| 96 | + <CreditCard /> | ||
| 97 | + </ElIcon> | ||
| 98 | + </div> | ||
| 99 | + <h1 class="title">地利收银台</h1> | ||
| 100 | + <p class="subtitle">请选择支付方式完成订单</p> | ||
| 101 | + </div> | ||
| 102 | + | ||
| 103 | + <!-- 订单金额卡片 --> | ||
| 104 | + <ElCard class="amount-card" shadow="always"> | ||
| 105 | + <div class="amount-content"> | ||
| 106 | + <p class="amount-label">订单金额</p> | ||
| 107 | + <p class="amount-value">¥{{ amount.toFixed(2) }}</p> | ||
| 108 | + </div> | ||
| 109 | + </ElCard> | ||
| 110 | + | ||
| 111 | + <!-- 支付方式选择 --> | ||
| 112 | + <ElCard class="payment-card" shadow="always"> | ||
| 113 | + <template #header> | ||
| 114 | + <span class="card-header">选择支付方式</span> | ||
| 115 | + </template> | ||
| 116 | + | ||
| 117 | + <!-- 微信支付 --> | ||
| 118 | + <div | ||
| 119 | + class="payment-item" | ||
| 120 | + :class="{ active: currentPaymentMethod === 'wechat' }" | ||
| 121 | + @click="handlepayBtnShowClick('wechat')" | ||
| 122 | + > | ||
| 123 | + <div class="payment-item-left"> | ||
| 124 | + <div | ||
| 125 | + class="payment-icon" | ||
| 126 | + :class="{ active: currentPaymentMethod === 'wechat' }" | ||
| 127 | + > | ||
| 128 | + <svg class="wechat-icon" viewBox="0 0 24 24" fill="currentColor"> | ||
| 129 | + <path | ||
| 130 | + d="M8.5 9.5c-.8 0-1.5.7-1.5 1.5s.7 1.5 1.5 1.5 1.5-.7 1.5-1.5-.7-1.5-1.5-1.5zm7 0c-.8 0-1.5.7-1.5 1.5s.7 1.5 1.5 1.5 1.5-.7 1.5-1.5-.7-1.5-1.5-1.5zM12 2C6.5 2 2 6.5 2 12s4.5 10 10 10 10-4.5 10-10S17.5 2 12 2zm0 18c-1.7 0-3.3-.5-4.7-1.3l-4.8 1.5 1.5-4.5C3.4 14.5 3 13.3 3 12c0-5 4-9 9-9s9 4 9 9-4 9-9 9z" | ||
| 131 | + /> | ||
| 132 | + </svg> | ||
| 133 | + </div> | ||
| 134 | + <div class="payment-info"> | ||
| 135 | + <p class="payment-name">微信支付</p> | ||
| 136 | + <p class="payment-desc">快捷安全</p> | ||
| 137 | + </div> | ||
| 138 | + </div> | ||
| 139 | + <ElIcon | ||
| 140 | + :size="20" | ||
| 141 | + :color="currentPaymentMethod === 'wechat' ? '#fff' : '#909399'" | ||
| 142 | + > | ||
| 143 | + <ArrowRight /> | ||
| 144 | + </ElIcon> | ||
| 145 | + </div> | ||
| 146 | + | ||
| 147 | + <!-- 园区卡支付 --> | ||
| 148 | + <div | ||
| 149 | + class="payment-item" | ||
| 150 | + :class="{ active: currentPaymentMethod === 'card' }" | ||
| 151 | + @click="handlepayBtnShowClick('card')" | ||
| 152 | + > | ||
| 153 | + <div class="payment-item-left"> | ||
| 154 | + <div | ||
| 155 | + class="payment-icon" | ||
| 156 | + :class="{ active: currentPaymentMethod === 'card' }" | ||
| 157 | + > | ||
| 158 | + <ElIcon | ||
| 159 | + :size="28" | ||
| 160 | + :color="currentPaymentMethod === 'card' ? '#fff' : '#10b981'" | ||
| 161 | + > | ||
| 162 | + <CreditCard /> | ||
| 163 | + </ElIcon> | ||
| 164 | + </div> | ||
| 165 | + <div class="payment-info"> | ||
| 166 | + <p class="payment-name">园区卡支付</p> | ||
| 167 | + <p class="payment-desc"> | ||
| 168 | + {{ | ||
| 169 | + selectedCard | ||
| 170 | + ? `卡号:${selectedCard.cardNo}` | ||
| 171 | + : '点击选择园区卡' | ||
| 172 | + }} | ||
| 173 | + </p> | ||
| 174 | + </div> | ||
| 175 | + </div> | ||
| 176 | + <ElIcon | ||
| 177 | + :size="20" | ||
| 178 | + :color="currentPaymentMethod === 'card' ? '#fff' : '#909399'" | ||
| 179 | + > | ||
| 180 | + <ArrowRight /> | ||
| 181 | + </ElIcon> | ||
| 182 | + </div> | ||
| 183 | + </ElCard> | ||
| 184 | + | ||
| 185 | + <!-- 微信支付确认按钮 --> | ||
| 186 | + <transition name="slide-fade"> | ||
| 187 | + <ElButton | ||
| 188 | + v-if="payBtnShow === true" | ||
| 189 | + class="pay-button" | ||
| 190 | + type="success" | ||
| 191 | + size="large" | ||
| 192 | + :loading="loading" | ||
| 193 | + @click="handlePay" | ||
| 194 | + > | ||
| 195 | + {{ loading ? '处理中...' : `确认支付 ¥${amount.toFixed(2)}` }} | ||
| 196 | + </ElButton> | ||
| 197 | + </transition> | ||
| 198 | + </div> | ||
| 199 | + | ||
| 200 | + <!-- 园区卡选择弹窗 --> | ||
| 201 | + <ElDialog | ||
| 202 | + v-model="showCardDialog" | ||
| 203 | + title="选择园区卡" | ||
| 204 | + width="90%" | ||
| 205 | + :style="{ maxWidth: '500px' }" | ||
| 206 | + class="card-dialog" | ||
| 207 | + :close-on-click-modal="false" | ||
| 208 | + > | ||
| 209 | + <div class="card-list-dialog"> | ||
| 210 | + <div | ||
| 211 | + v-for="card in mockCards" | ||
| 212 | + :key="card.id" | ||
| 213 | + class="card-item-dialog" | ||
| 214 | + @click="handleCardSelect(card)" | ||
| 215 | + > | ||
| 216 | + <div class="card-item-info"> | ||
| 217 | + <p class="card-holder">{{ card.holderName }}</p> | ||
| 218 | + <p class="card-number">{{ card.cardNo }}</p> | ||
| 219 | + </div> | ||
| 220 | + <div class="card-balance"> | ||
| 221 | + <p class="balance-label">余额</p> | ||
| 222 | + <p class="balance-value">¥{{ card.balance.toFixed(2) }}</p> | ||
| 223 | + </div> | ||
| 224 | + </div> | ||
| 225 | + </div> | ||
| 226 | + <template #footer> | ||
| 227 | + <ElButton @click="handleCloseDialog">取消</ElButton> | ||
| 228 | + </template> | ||
| 229 | + </ElDialog> | ||
| 230 | + | ||
| 231 | + <!-- 支付成功对话框 --> | ||
| 232 | + <ElDialog | ||
| 233 | + v-model="paymentSuccess" | ||
| 234 | + width="90%" | ||
| 235 | + :style="{ maxWidth: '400px' }" | ||
| 236 | + align-center | ||
| 237 | + :show-close="false" | ||
| 238 | + class="success-dialog" | ||
| 239 | + > | ||
| 240 | + <div class="success-content"> | ||
| 241 | + <div class="success-icon"> | ||
| 242 | + <ElIcon :size="48" color="#fff"> | ||
| 243 | + <SuccessFilled /> | ||
| 244 | + </ElIcon> | ||
| 245 | + </div> | ||
| 246 | + <h3 class="success-title">支付成功!</h3> | ||
| 247 | + <p class="success-desc">感谢您的购买</p> | ||
| 248 | + </div> | ||
| 249 | + </ElDialog> | ||
| 250 | + </div> | ||
| 3 | </template> | 251 | </template> |
| 252 | + | ||
| 253 | +<style scoped lang="scss"> | ||
| 254 | +// 动画 | ||
| 255 | +@keyframes fadeIn { | ||
| 256 | + from { | ||
| 257 | + opacity: 0; | ||
| 258 | + } | ||
| 259 | + | ||
| 260 | + to { | ||
| 261 | + opacity: 1; | ||
| 262 | + } | ||
| 263 | +} | ||
| 264 | + | ||
| 265 | +@keyframes slideUp { | ||
| 266 | + from { | ||
| 267 | + opacity: 0; | ||
| 268 | + transform: translateY(20px); | ||
| 269 | + } | ||
| 270 | + | ||
| 271 | + to { | ||
| 272 | + opacity: 1; | ||
| 273 | + transform: translateY(0); | ||
| 274 | + } | ||
| 275 | +} | ||
| 276 | + | ||
| 277 | +@keyframes bounceIn { | ||
| 278 | + 0% { | ||
| 279 | + opacity: 0; | ||
| 280 | + transform: scale(0.3); | ||
| 281 | + } | ||
| 282 | + | ||
| 283 | + 50% { | ||
| 284 | + transform: scale(1.05); | ||
| 285 | + } | ||
| 286 | + | ||
| 287 | + 70% { | ||
| 288 | + transform: scale(0.9); | ||
| 289 | + } | ||
| 290 | + | ||
| 291 | + 100% { | ||
| 292 | + opacity: 1; | ||
| 293 | + transform: scale(1); | ||
| 294 | + } | ||
| 295 | +} | ||
| 296 | + | ||
| 297 | +.cashier-container { | ||
| 298 | + min-height: 100vh; | ||
| 299 | + padding: 20px; | ||
| 300 | + background: linear-gradient(135deg, #f0fdf4 0%, #d1fae5 100%); | ||
| 301 | + | ||
| 302 | + @media (min-width: 768px) { | ||
| 303 | + padding: 40px; | ||
| 304 | + } | ||
| 305 | +} | ||
| 306 | + | ||
| 307 | +.cashier-wrapper { | ||
| 308 | + max-width: 500px; | ||
| 309 | + margin: 0 auto; | ||
| 310 | +} | ||
| 311 | + | ||
| 312 | +// 头部样式 | ||
| 313 | +.header { | ||
| 314 | + margin-bottom: 30px; | ||
| 315 | + text-align: center; | ||
| 316 | + animation: fadeIn 0.5s ease-out; | ||
| 317 | + | ||
| 318 | + .icon-wrapper { | ||
| 319 | + display: inline-flex; | ||
| 320 | + align-items: center; | ||
| 321 | + justify-content: center; | ||
| 322 | + width: 64px; | ||
| 323 | + height: 64px; | ||
| 324 | + margin-bottom: 16px; | ||
| 325 | + background: linear-gradient(135deg, #10b981 0%, #059669 100%); | ||
| 326 | + border-radius: 50%; | ||
| 327 | + box-shadow: 0 10px 30px rgb(16 185 129 / 30%); | ||
| 328 | + } | ||
| 329 | + | ||
| 330 | + .title { | ||
| 331 | + margin: 0 0 8px; | ||
| 332 | + font-size: 28px; | ||
| 333 | + font-weight: bold; | ||
| 334 | + color: #1f2937; | ||
| 335 | + | ||
| 336 | + @media (min-width: 768px) { | ||
| 337 | + font-size: 32px; | ||
| 338 | + } | ||
| 339 | + } | ||
| 340 | + | ||
| 341 | + .subtitle { | ||
| 342 | + margin: 0; | ||
| 343 | + font-size: 14px; | ||
| 344 | + color: #6b7280; | ||
| 345 | + } | ||
| 346 | +} | ||
| 347 | + | ||
| 348 | +// 金额卡片 | ||
| 349 | +.amount-card { | ||
| 350 | + margin-bottom: 24px; | ||
| 351 | + border: none; | ||
| 352 | + border-radius: 16px; | ||
| 353 | + animation: slideUp 0.5s ease-out 0.1s both; | ||
| 354 | + | ||
| 355 | + :deep(.el-card__body) { | ||
| 356 | + padding: 32px; | ||
| 357 | + } | ||
| 358 | + | ||
| 359 | + .amount-content { | ||
| 360 | + text-align: center; | ||
| 361 | + | ||
| 362 | + .amount-label { | ||
| 363 | + margin: 0 0 8px; | ||
| 364 | + font-size: 14px; | ||
| 365 | + color: #6b7280; | ||
| 366 | + } | ||
| 367 | + | ||
| 368 | + .amount-value { | ||
| 369 | + margin: 0; | ||
| 370 | + font-size: 48px; | ||
| 371 | + font-weight: bold; | ||
| 372 | + color: #10b981; | ||
| 373 | + | ||
| 374 | + @media (min-width: 768px) { | ||
| 375 | + font-size: 56px; | ||
| 376 | + } | ||
| 377 | + } | ||
| 378 | + } | ||
| 379 | +} | ||
| 380 | + | ||
| 381 | +// 支付方式卡片 | ||
| 382 | +.payment-card { | ||
| 383 | + margin-bottom: 24px; | ||
| 384 | + border: none; | ||
| 385 | + border-radius: 16px; | ||
| 386 | + animation: slideUp 0.5s ease-out 0.2s both; | ||
| 387 | + | ||
| 388 | + .card-header { | ||
| 389 | + font-size: 16px; | ||
| 390 | + font-weight: 600; | ||
| 391 | + color: #1f2937; | ||
| 392 | + } | ||
| 393 | + | ||
| 394 | + .payment-item { | ||
| 395 | + display: flex; | ||
| 396 | + align-items: center; | ||
| 397 | + justify-content: space-between; | ||
| 398 | + padding: 16px; | ||
| 399 | + margin-bottom: 12px; | ||
| 400 | + cursor: pointer; | ||
| 401 | + background: #f9fafb; | ||
| 402 | + border-radius: 12px; | ||
| 403 | + transition: all 0.3s ease; | ||
| 404 | + | ||
| 405 | + &:last-child { | ||
| 406 | + margin-bottom: 0; | ||
| 407 | + } | ||
| 408 | + | ||
| 409 | + &:hover { | ||
| 410 | + background: #f3f4f6; | ||
| 411 | + transform: translateX(4px); | ||
| 412 | + } | ||
| 413 | + | ||
| 414 | + &.active { | ||
| 415 | + color: white; | ||
| 416 | + background: linear-gradient(135deg, #10b981 0%, #059669 100%); | ||
| 417 | + box-shadow: 0 8px 24px rgb(16 185 129 / 40%); | ||
| 418 | + transform: scale(1.02); | ||
| 419 | + | ||
| 420 | + .payment-desc { | ||
| 421 | + color: rgb(255 255 255 / 80%); | ||
| 422 | + } | ||
| 423 | + } | ||
| 424 | + | ||
| 425 | + .payment-item-left { | ||
| 426 | + display: flex; | ||
| 427 | + gap: 16px; | ||
| 428 | + align-items: center; | ||
| 429 | + } | ||
| 430 | + | ||
| 431 | + .payment-icon { | ||
| 432 | + display: flex; | ||
| 433 | + align-items: center; | ||
| 434 | + justify-content: center; | ||
| 435 | + width: 48px; | ||
| 436 | + height: 48px; | ||
| 437 | + background: #d1fae5; | ||
| 438 | + border-radius: 50%; | ||
| 439 | + transition: all 0.3s ease; | ||
| 440 | + | ||
| 441 | + &.active { | ||
| 442 | + background: rgb(255 255 255 / 20%); | ||
| 443 | + } | ||
| 444 | + | ||
| 445 | + .wechat-icon { | ||
| 446 | + width: 28px; | ||
| 447 | + height: 28px; | ||
| 448 | + color: #10b981; | ||
| 449 | + } | ||
| 450 | + } | ||
| 451 | + | ||
| 452 | + &.active .wechat-icon { | ||
| 453 | + color: white; | ||
| 454 | + } | ||
| 455 | + | ||
| 456 | + .payment-name { | ||
| 457 | + margin: 0 0 4px; | ||
| 458 | + font-size: 16px; | ||
| 459 | + font-weight: 600; | ||
| 460 | + } | ||
| 461 | + | ||
| 462 | + .payment-desc { | ||
| 463 | + margin: 0; | ||
| 464 | + font-size: 13px; | ||
| 465 | + color: #6b7280; | ||
| 466 | + } | ||
| 467 | + } | ||
| 468 | +} | ||
| 469 | + | ||
| 470 | +// 支付按钮 | ||
| 471 | +.pay-button { | ||
| 472 | + width: 100%; | ||
| 473 | + height: 56px; | ||
| 474 | + font-size: 18px; | ||
| 475 | + font-weight: 600; | ||
| 476 | + background: linear-gradient(135deg, #10b981 0%, #059669 100%); | ||
| 477 | + border: none; | ||
| 478 | + border-radius: 16px; | ||
| 479 | + box-shadow: 0 8px 24px rgb(16 185 129 / 40%); | ||
| 480 | + | ||
| 481 | + &:hover { | ||
| 482 | + box-shadow: 0 12px 32px rgb(16 185 129 / 50%); | ||
| 483 | + transform: translateY(-2px); | ||
| 484 | + } | ||
| 485 | + | ||
| 486 | + &:active { | ||
| 487 | + transform: scale(0.98); | ||
| 488 | + } | ||
| 489 | +} | ||
| 490 | + | ||
| 491 | +// 园区卡选择弹窗 | ||
| 492 | +.card-dialog { | ||
| 493 | + :deep(.el-dialog__header) { | ||
| 494 | + padding: 20px 24px; | ||
| 495 | + border-bottom: 1px solid #f0f0f0; | ||
| 496 | + } | ||
| 497 | + | ||
| 498 | + :deep(.el-dialog__title) { | ||
| 499 | + font-size: 18px; | ||
| 500 | + font-weight: 600; | ||
| 501 | + color: #1f2937; | ||
| 502 | + } | ||
| 503 | + | ||
| 504 | + :deep(.el-dialog__body) { | ||
| 505 | + padding: 24px; | ||
| 506 | + } | ||
| 507 | +} | ||
| 508 | + | ||
| 509 | +.card-list-dialog { | ||
| 510 | + display: flex; | ||
| 511 | + flex-direction: column; | ||
| 512 | + gap: 12px; | ||
| 513 | + max-height: 400px; | ||
| 514 | + overflow-y: auto; | ||
| 515 | +} | ||
| 516 | + | ||
| 517 | +.card-item-dialog { | ||
| 518 | + display: flex; | ||
| 519 | + align-items: center; | ||
| 520 | + justify-content: space-between; | ||
| 521 | + padding: 16px; | ||
| 522 | + cursor: pointer; | ||
| 523 | + background: linear-gradient(135deg, #f0fdf4 0%, #d1fae5 100%); | ||
| 524 | + border: 2px solid transparent; | ||
| 525 | + border-radius: 12px; | ||
| 526 | + transition: all 0.3s ease; | ||
| 527 | + | ||
| 528 | + &:hover { | ||
| 529 | + background: linear-gradient(135deg, #d1fae5 0%, #a7f3d0 100%); | ||
| 530 | + border-color: #10b981; | ||
| 531 | + box-shadow: 0 4px 12px rgb(16 185 129 / 20%); | ||
| 532 | + transform: translateY(-2px); | ||
| 533 | + } | ||
| 534 | + | ||
| 535 | + .card-item-info { | ||
| 536 | + .card-holder { | ||
| 537 | + margin: 0 0 4px; | ||
| 538 | + font-size: 16px; | ||
| 539 | + font-weight: 600; | ||
| 540 | + color: #1f2937; | ||
| 541 | + } | ||
| 542 | + | ||
| 543 | + .card-number { | ||
| 544 | + margin: 0; | ||
| 545 | + font-family: 'Courier New', monospace; | ||
| 546 | + font-size: 13px; | ||
| 547 | + color: #6b7280; | ||
| 548 | + } | ||
| 549 | + } | ||
| 550 | + | ||
| 551 | + .card-balance { | ||
| 552 | + text-align: right; | ||
| 553 | + | ||
| 554 | + .balance-label { | ||
| 555 | + margin: 0 0 4px; | ||
| 556 | + font-size: 12px; | ||
| 557 | + color: #6b7280; | ||
| 558 | + } | ||
| 559 | + | ||
| 560 | + .balance-value { | ||
| 561 | + margin: 0; | ||
| 562 | + font-size: 18px; | ||
| 563 | + font-weight: bold; | ||
| 564 | + color: #10b981; | ||
| 565 | + } | ||
| 566 | + } | ||
| 567 | +} | ||
| 568 | + | ||
| 569 | +// 成功对话框 | ||
| 570 | +.success-dialog { | ||
| 571 | + :deep(.el-dialog__body) { | ||
| 572 | + padding: 20px; | ||
| 573 | + } | ||
| 574 | +} | ||
| 575 | + | ||
| 576 | +.success-content { | ||
| 577 | + padding: 20px; | ||
| 578 | + text-align: center; | ||
| 579 | + | ||
| 580 | + .success-icon { | ||
| 581 | + display: flex; | ||
| 582 | + align-items: center; | ||
| 583 | + justify-content: center; | ||
| 584 | + width: 80px; | ||
| 585 | + height: 80px; | ||
| 586 | + margin: 0 auto 20px; | ||
| 587 | + background: linear-gradient(135deg, #10b981 0%, #059669 100%); | ||
| 588 | + border-radius: 50%; | ||
| 589 | + animation: bounceIn 0.6s ease-out; | ||
| 590 | + } | ||
| 591 | + | ||
| 592 | + .success-title { | ||
| 593 | + margin: 0 0 8px; | ||
| 594 | + font-size: 24px; | ||
| 595 | + font-weight: bold; | ||
| 596 | + color: #1f2937; | ||
| 597 | + } | ||
| 598 | + | ||
| 599 | + .success-desc { | ||
| 600 | + margin: 0; | ||
| 601 | + font-size: 14px; | ||
| 602 | + color: #6b7280; | ||
| 603 | + } | ||
| 604 | +} | ||
| 605 | + | ||
| 606 | +// 过渡动画 | ||
| 607 | +.slide-fade-enter-active { | ||
| 608 | + transition: all 0.3s ease-out; | ||
| 609 | +} | ||
| 610 | + | ||
| 611 | +.slide-fade-leave-active { | ||
| 612 | + transition: all 0.2s cubic-bezier(1, 0.5, 0.8, 1); | ||
| 613 | +} | ||
| 614 | + | ||
| 615 | +.slide-fade-enter-from { | ||
| 616 | + opacity: 0; | ||
| 617 | + transform: translateY(20px); | ||
| 618 | +} | ||
| 619 | + | ||
| 620 | +.slide-fade-leave-to { | ||
| 621 | + opacity: 0; | ||
| 622 | + transform: translateY(-20px); | ||
| 623 | +} | ||
| 624 | +</style> |
pnpm-lock.yaml
| @@ -27,9 +27,6 @@ catalogs: | @@ -27,9 +27,6 @@ catalogs: | ||
| 27 | '@eslint/js': | 27 | '@eslint/js': |
| 28 | specifier: ^9.39.1 | 28 | specifier: ^9.39.1 |
| 29 | version: 9.39.1 | 29 | version: 9.39.1 |
| 30 | - '@faker-js/faker': | ||
| 31 | - specifier: ^9.9.0 | ||
| 32 | - version: 9.9.0 | ||
| 33 | '@iconify/json': | 30 | '@iconify/json': |
| 34 | specifier: ^2.2.406 | 31 | specifier: ^2.2.406 |
| 35 | version: 2.2.407 | 32 | version: 2.2.407 |
| @@ -87,9 +84,6 @@ catalogs: | @@ -87,9 +84,6 @@ catalogs: | ||
| 87 | '@types/json-bigint': | 84 | '@types/json-bigint': |
| 88 | specifier: ^1.0.4 | 85 | specifier: ^1.0.4 |
| 89 | version: 1.0.4 | 86 | version: 1.0.4 |
| 90 | - '@types/jsonwebtoken': | ||
| 91 | - specifier: ^9.0.10 | ||
| 92 | - version: 9.0.10 | ||
| 93 | '@types/lodash.clonedeep': | 87 | '@types/lodash.clonedeep': |
| 94 | specifier: ^4.5.9 | 88 | specifier: ^4.5.9 |
| 95 | version: 4.5.9 | 89 | version: 4.5.9 |
| @@ -285,9 +279,6 @@ catalogs: | @@ -285,9 +279,6 @@ catalogs: | ||
| 285 | globals: | 279 | globals: |
| 286 | specifier: ^16.3.0 | 280 | specifier: ^16.3.0 |
| 287 | version: 16.5.0 | 281 | version: 16.5.0 |
| 288 | - h3: | ||
| 289 | - specifier: ^1.15.3 | ||
| 290 | - version: 1.15.4 | ||
| 291 | happy-dom: | 282 | happy-dom: |
| 292 | specifier: ^17.6.3 | 283 | specifier: ^17.6.3 |
| 293 | version: 17.6.3 | 284 | version: 17.6.3 |
| @@ -303,9 +294,6 @@ catalogs: | @@ -303,9 +294,6 @@ catalogs: | ||
| 303 | jsonc-eslint-parser: | 294 | jsonc-eslint-parser: |
| 304 | specifier: ^2.4.1 | 295 | specifier: ^2.4.1 |
| 305 | version: 2.4.1 | 296 | version: 2.4.1 |
| 306 | - jsonwebtoken: | ||
| 307 | - specifier: ^9.0.2 | ||
| 308 | - version: 9.0.2 | ||
| 309 | lefthook: | 297 | lefthook: |
| 310 | specifier: ^1.13.6 | 298 | specifier: ^1.13.6 |
| 311 | version: 1.13.6 | 299 | version: 1.13.6 |
| @@ -318,9 +306,6 @@ catalogs: | @@ -318,9 +306,6 @@ catalogs: | ||
| 318 | medium-zoom: | 306 | medium-zoom: |
| 319 | specifier: ^1.1.0 | 307 | specifier: ^1.1.0 |
| 320 | version: 1.1.0 | 308 | version: 1.1.0 |
| 321 | - naive-ui: | ||
| 322 | - specifier: ^2.42.0 | ||
| 323 | - version: 2.43.2 | ||
| 324 | nitropack: | 309 | nitropack: |
| 325 | specifier: ^2.11.13 | 310 | specifier: ^2.11.13 |
| 326 | version: 2.12.9 | 311 | version: 2.12.9 |
| @@ -432,9 +417,6 @@ catalogs: | @@ -432,9 +417,6 @@ catalogs: | ||
| 432 | tailwindcss-animate: | 417 | tailwindcss-animate: |
| 433 | specifier: ^1.0.7 | 418 | specifier: ^1.0.7 |
| 434 | version: 1.0.7 | 419 | version: 1.0.7 |
| 435 | - tdesign-vue-next: | ||
| 436 | - specifier: ^1.17.1 | ||
| 437 | - version: 1.17.3 | ||
| 438 | theme-colors: | 420 | theme-colors: |
| 439 | specifier: ^0.1.0 | 421 | specifier: ^0.1.0 |
| 440 | version: 0.1.0 | 422 | version: 0.1.0 |
| @@ -839,6 +821,76 @@ importers: | @@ -839,6 +821,76 @@ importers: | ||
| 839 | specifier: 'catalog:' | 821 | specifier: 'catalog:' |
| 840 | version: 4.6.3(vue@3.5.24(typescript@5.9.3)) | 822 | version: 4.6.3(vue@3.5.24(typescript@5.9.3)) |
| 841 | 823 | ||
| 824 | + apps/web-payment: | ||
| 825 | + dependencies: | ||
| 826 | + '@element-plus/icons-vue': | ||
| 827 | + specifier: ^2.3.2 | ||
| 828 | + version: 2.3.2(vue@3.5.24(typescript@5.9.3)) | ||
| 829 | + '@vben/access': | ||
| 830 | + specifier: workspace:* | ||
| 831 | + version: link:../../packages/effects/access | ||
| 832 | + '@vben/common-ui': | ||
| 833 | + specifier: workspace:* | ||
| 834 | + version: link:../../packages/effects/common-ui | ||
| 835 | + '@vben/constants': | ||
| 836 | + specifier: workspace:* | ||
| 837 | + version: link:../../packages/constants | ||
| 838 | + '@vben/hooks': | ||
| 839 | + specifier: workspace:* | ||
| 840 | + version: link:../../packages/effects/hooks | ||
| 841 | + '@vben/icons': | ||
| 842 | + specifier: workspace:* | ||
| 843 | + version: link:../../packages/icons | ||
| 844 | + '@vben/layouts': | ||
| 845 | + specifier: workspace:* | ||
| 846 | + version: link:../../packages/effects/layouts | ||
| 847 | + '@vben/locales': | ||
| 848 | + specifier: workspace:* | ||
| 849 | + version: link:../../packages/locales | ||
| 850 | + '@vben/plugins': | ||
| 851 | + specifier: workspace:* | ||
| 852 | + version: link:../../packages/effects/plugins | ||
| 853 | + '@vben/preferences': | ||
| 854 | + specifier: workspace:* | ||
| 855 | + version: link:../../packages/preferences | ||
| 856 | + '@vben/request': | ||
| 857 | + specifier: workspace:* | ||
| 858 | + version: link:../../packages/effects/request | ||
| 859 | + '@vben/stores': | ||
| 860 | + specifier: workspace:* | ||
| 861 | + version: link:../../packages/stores | ||
| 862 | + '@vben/styles': | ||
| 863 | + specifier: workspace:* | ||
| 864 | + version: link:../../packages/styles | ||
| 865 | + '@vben/types': | ||
| 866 | + specifier: workspace:* | ||
| 867 | + version: link:../../packages/types | ||
| 868 | + '@vben/utils': | ||
| 869 | + specifier: workspace:* | ||
| 870 | + version: link:../../packages/utils | ||
| 871 | + '@vueuse/core': | ||
| 872 | + specifier: 'catalog:' | ||
| 873 | + version: 13.9.0(vue@3.5.24(typescript@5.9.3)) | ||
| 874 | + dayjs: | ||
| 875 | + specifier: 'catalog:' | ||
| 876 | + version: 1.11.19 | ||
| 877 | + element-plus: | ||
| 878 | + specifier: 'catalog:' | ||
| 879 | + version: 2.11.8(vue@3.5.24(typescript@5.9.3)) | ||
| 880 | + pinia: | ||
| 881 | + specifier: ^3.0.3 | ||
| 882 | + version: 3.0.4(typescript@5.9.3)(vue@3.5.24(typescript@5.9.3)) | ||
| 883 | + vue: | ||
| 884 | + specifier: ^3.5.24 | ||
| 885 | + version: 3.5.24(typescript@5.9.3) | ||
| 886 | + vue-router: | ||
| 887 | + specifier: 'catalog:' | ||
| 888 | + version: 4.6.3(vue@3.5.24(typescript@5.9.3)) | ||
| 889 | + devDependencies: | ||
| 890 | + unplugin-element-plus: | ||
| 891 | + specifier: 'catalog:' | ||
| 892 | + version: 0.11.1(magicast@0.5.1) | ||
| 893 | + | ||
| 842 | apps/web-tdesign: | 894 | apps/web-tdesign: |
| 843 | dependencies: | 895 | dependencies: |
| 844 | '@vben/access': | 896 | '@vben/access': |