Commit a16b16b817a405bf69eca53ff2ff2448db114653

Authored by tianwu
1 parent 162b9d70

收银台页面开发中

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':