Commit bf9336df9e2f92ddb5cab32692a1440be1be8cf2
1 parent
a16b16b8
收银台微信支付
Showing
8 changed files
with
649 additions
and
232 deletions
apps/web-payment/.env
apps/web-payment/index.html
| @@ -17,6 +17,7 @@ | @@ -17,6 +17,7 @@ | ||
| 17 | </head> | 17 | </head> |
| 18 | <body> | 18 | <body> |
| 19 | <div id="app"></div> | 19 | <div id="app"></div> |
| 20 | + <script type="text/javascript" src="http://res.wx.qq.com/open/js/jweixin-1.6.0.js" ></script> | ||
| 20 | <script type="module" src="/src/main.ts"></script> | 21 | <script type="module" src="/src/main.ts"></script> |
| 21 | </body> | 22 | </body> |
| 22 | </html> | 23 | </html> |
apps/web-payment/src/api/index.ts
apps/web-payment/src/api/payment.ts
0 → 100644
| 1 | +import { requestClient } from '#/api/request'; | ||
| 2 | + | ||
| 3 | +export namespace PaymentApi { | ||
| 4 | + export interface orderPaymentParams { | ||
| 5 | + tradeId: string; | ||
| 6 | + pipelineId: string; | ||
| 7 | + params: { openId: string }; | ||
| 8 | + } | ||
| 9 | +} | ||
| 10 | + | ||
| 11 | +/** | ||
| 12 | + * 订单 | ||
| 13 | + * @param data | ||
| 14 | + * @returns | ||
| 15 | + */ | ||
| 16 | +export async function orderPayment(data: PaymentApi.orderPaymentParams) { | ||
| 17 | + return requestClient.post<any>('/payment/cashier/orderPayment', data); | ||
| 18 | +} | ||
| 19 | + | ||
| 20 | +/** | ||
| 21 | + * 获取收银台信息 | ||
| 22 | + * @param data | ||
| 23 | + * @returns | ||
| 24 | + */ | ||
| 25 | +export async function orderInfo(token: string) { | ||
| 26 | + return requestClient.post<any>(`/payment/cashier/orderInfo?token=${token}`); | ||
| 27 | +} | ||
| 28 | + | ||
| 29 | +/** | ||
| 30 | + * | ||
| 31 | + * @param pipelineId 收银台支付方式的pipelineId | ||
| 32 | + * @param userId userId | ||
| 33 | + * @returns | ||
| 34 | + */ | ||
| 35 | +export async function listUserCards( | ||
| 36 | + pipelineId: number | string, | ||
| 37 | + userId: number | string, | ||
| 38 | +) { | ||
| 39 | + return requestClient.post<any>( | ||
| 40 | + `/card/payment/listUserCards?pipelineId=${pipelineId}&userId=${userId}`, | ||
| 41 | + ); | ||
| 42 | +} |
apps/web-payment/src/api/request.ts
| @@ -76,7 +76,7 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) { | @@ -76,7 +76,7 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) { | ||
| 76 | defaultResponseInterceptor({ | 76 | defaultResponseInterceptor({ |
| 77 | codeField: 'code', | 77 | codeField: 'code', |
| 78 | dataField: 'data', | 78 | dataField: 'data', |
| 79 | - successCode: 0, | 79 | + successCode: '200', |
| 80 | }), | 80 | }), |
| 81 | ); | 81 | ); |
| 82 | 82 |
apps/web-payment/src/router/routes/core.ts
| @@ -40,7 +40,7 @@ const coreRoutes: RouteRecordRaw[] = [ | @@ -40,7 +40,7 @@ const coreRoutes: RouteRecordRaw[] = [ | ||
| 40 | component: () => import('#/views/payment/index.vue'), | 40 | component: () => import('#/views/payment/index.vue'), |
| 41 | meta: { | 41 | meta: { |
| 42 | icon: 'lucide:area-chart', | 42 | icon: 'lucide:area-chart', |
| 43 | - title: '收银台', | 43 | + title: '订单支付', |
| 44 | }, | 44 | }, |
| 45 | }, | 45 | }, |
| 46 | ]; | 46 | ]; |
apps/web-payment/src/views/payment/index.vue
| 1 | <script setup lang="ts"> | 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 | - | 2 | +import { computed, ref } from 'vue'; |
| 3 | +import { useRoute } from 'vue-router'; | ||
| 4 | + | ||
| 5 | +import { | ||
| 6 | + CreditCard, | ||
| 7 | + SuccessFilled, | ||
| 8 | + Wallet, | ||
| 9 | + WarningFilled, | ||
| 10 | +} from '@element-plus/icons-vue'; | ||
| 11 | +import { ElButton, ElDialog, ElIcon, ElMessage, ElRadio } from 'element-plus'; | ||
| 12 | +import qs from 'qs'; | ||
| 13 | + | ||
| 14 | +import { listUserCards, orderInfo, orderPayment } from '#/api'; | ||
| 7 | import EnvironmentDetector from '#/composables/environmentDetector'; | 15 | import EnvironmentDetector from '#/composables/environmentDetector'; |
| 16 | + | ||
| 8 | // 类型定义 | 17 | // 类型定义 |
| 9 | interface Card { | 18 | interface Card { |
| 10 | - id: string; | ||
| 11 | - cardNo: string; | ||
| 12 | - balance: number; | ||
| 13 | - holderName: string; | 19 | + customerId: number | string; |
| 20 | + accountId: number | string; | ||
| 21 | + cardNo: number | string; | ||
| 22 | + name: string; | ||
| 23 | + amount: number | string; | ||
| 24 | +} | ||
| 25 | + | ||
| 26 | +interface Pipeline { | ||
| 27 | + pipelineId: number; | ||
| 28 | + channelId: number; | ||
| 29 | + channelName: string; | ||
| 14 | } | 30 | } |
| 15 | 31 | ||
| 16 | -// 创建检测器实例 | ||
| 17 | const detector = new EnvironmentDetector(); | 32 | const detector = new EnvironmentDetector(); |
| 18 | detector.logEnvironment(); | 33 | detector.logEnvironment(); |
| 19 | 34 | ||
| 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 | -]; | 35 | +const route = useRoute(); |
| 36 | +const loadLoading = ref(false); | ||
| 37 | +const token = ref<string>(''); | ||
| 38 | +const openId = ref<string>(''); | ||
| 39 | +const env = ref(); | ||
| 40 | +env.value = detector.env; | ||
| 41 | + | ||
| 42 | +// 检查参数是否有效 | ||
| 43 | +const hasValidParams = computed(() => { | ||
| 44 | + return !!(token.value && openId.value); | ||
| 45 | +}); | ||
| 46 | + | ||
| 47 | +const cardList = ref<Card[]>([]); | ||
| 48 | +const orderInfoData = ref<any>(null); | ||
| 36 | 49 | ||
| 37 | // 响应式数据 | 50 | // 响应式数据 |
| 38 | -const amount = ref<number>(128.5); | 51 | +const currentPayType = ref<null | Pipeline>(null); |
| 39 | const payBtnShow = ref<boolean>(false); | 52 | const payBtnShow = ref<boolean>(false); |
| 40 | -const currentPaymentMethod = ref<string>(''); | 53 | +const currentPaymentMethod = ref<null | number>(null); // 改为存储 pipelineId |
| 41 | const showCardDialog = ref<boolean>(false); | 54 | const showCardDialog = ref<boolean>(false); |
| 42 | const selectedCard = ref<Card | null>(null); | 55 | const selectedCard = ref<Card | null>(null); |
| 43 | const loading = ref<boolean>(false); | 56 | const loading = ref<boolean>(false); |
| 44 | const paymentSuccess = ref<boolean>(false); | 57 | const paymentSuccess = ref<boolean>(false); |
| 45 | 58 | ||
| 59 | +// 计算显示金额 | ||
| 60 | +const displayAmount = computed(() => { | ||
| 61 | + return orderInfoData.value?.amount || 0; | ||
| 62 | +}); | ||
| 63 | + | ||
| 64 | +// 获取支付方式列表 | ||
| 65 | +const paymentPipelines = computed(() => { | ||
| 66 | + return orderInfoData.value?.pipelines || []; | ||
| 67 | +}); | ||
| 68 | + | ||
| 69 | +// 根据渠道名称获取图标和颜色 | ||
| 70 | +const getPaymentIcon = (channelName: string) => { | ||
| 71 | + const lowerName = channelName.toLowerCase(); | ||
| 72 | + if (lowerName.includes('微信')) { | ||
| 73 | + return { | ||
| 74 | + type: 'wechat', | ||
| 75 | + icon: 'wechat', | ||
| 76 | + color: '#10b981', | ||
| 77 | + activeColor: '#EA4200', | ||
| 78 | + bgColor: '#d1fae5', | ||
| 79 | + activeBgColor: '#fee2e2', | ||
| 80 | + }; | ||
| 81 | + } else if (lowerName.includes('园区卡') || lowerName.includes('卡')) { | ||
| 82 | + return { | ||
| 83 | + type: 'card', | ||
| 84 | + icon: 'card', | ||
| 85 | + color: '#10b981', | ||
| 86 | + activeColor: '#EA4200', | ||
| 87 | + bgColor: '#d1fae5', | ||
| 88 | + activeBgColor: '#fee2e2', | ||
| 89 | + }; | ||
| 90 | + } else if (lowerName.includes('支付宝')) { | ||
| 91 | + return { | ||
| 92 | + type: 'alipay', | ||
| 93 | + icon: 'wallet', | ||
| 94 | + color: '#1677ff', | ||
| 95 | + activeColor: '#EA4200', | ||
| 96 | + bgColor: '#e6f4ff', | ||
| 97 | + activeBgColor: '#fee2e2', | ||
| 98 | + }; | ||
| 99 | + } else { | ||
| 100 | + return { | ||
| 101 | + type: 'other', | ||
| 102 | + icon: 'wallet', | ||
| 103 | + color: '#8b5cf6', | ||
| 104 | + activeColor: '#EA4200', | ||
| 105 | + bgColor: '#ede9fe', | ||
| 106 | + activeBgColor: '#fee2e2', | ||
| 107 | + }; | ||
| 108 | + } | ||
| 109 | +}; | ||
| 110 | + | ||
| 111 | +// 获取支付方式描述 | ||
| 112 | +const getPaymentDesc = (pipeline: Pipeline) => { | ||
| 113 | + if ( | ||
| 114 | + pipeline.pipelineId === currentPaymentMethod.value && | ||
| 115 | + selectedCard.value | ||
| 116 | + ) { | ||
| 117 | + return `卡号:${selectedCard.value.cardNo}`; | ||
| 118 | + } | ||
| 119 | + const channelName = pipeline.channelName.toLowerCase(); | ||
| 120 | + if (channelName.includes('微信')) { | ||
| 121 | + return '推荐使用'; | ||
| 122 | + } else if (channelName.includes('园区卡') || channelName.includes('卡')) { | ||
| 123 | + return '点击选择园区卡'; | ||
| 124 | + } else if (channelName.includes('支付宝')) { | ||
| 125 | + return '快捷支付'; | ||
| 126 | + } else { | ||
| 127 | + return '安全便捷'; | ||
| 128 | + } | ||
| 129 | +}; | ||
| 130 | + | ||
| 46 | // 方法 | 131 | // 方法 |
| 47 | -const handlepayBtnShowClick = (method: string) => { | ||
| 48 | - currentPaymentMethod.value = method; | 132 | +const handlepayBtnShowClick = async (pipeline: Pipeline) => { |
| 133 | + currentPaymentMethod.value = pipeline.pipelineId; | ||
| 134 | + currentPayType.value = pipeline; | ||
| 49 | payBtnShow.value = false; | 135 | payBtnShow.value = false; |
| 50 | - if (method === 'card') { | 136 | + |
| 137 | + const channelName = pipeline.channelName.toLowerCase(); | ||
| 138 | + | ||
| 139 | + // 判断是否是园区卡支付 | ||
| 140 | + if (channelName.includes('园区卡') || channelName.includes('卡支付')) { | ||
| 141 | + // 如果还没有加载园区卡列表,先加载 | ||
| 142 | + if (cardList.value.length === 0) { | ||
| 143 | + await getListUserCards(); | ||
| 144 | + } | ||
| 51 | showCardDialog.value = true; | 145 | showCardDialog.value = true; |
| 52 | } else { | 146 | } else { |
| 147 | + // 其他支付方式直接显示支付按钮 | ||
| 53 | payBtnShow.value = true; | 148 | payBtnShow.value = true; |
| 54 | } | 149 | } |
| 55 | }; | 150 | }; |
| @@ -57,144 +152,310 @@ const handlepayBtnShowClick = (method: string) => { | @@ -57,144 +152,310 @@ const handlepayBtnShowClick = (method: string) => { | ||
| 57 | const handleCardSelect = (card: Card) => { | 152 | const handleCardSelect = (card: Card) => { |
| 58 | selectedCard.value = card; | 153 | selectedCard.value = card; |
| 59 | showCardDialog.value = false; | 154 | showCardDialog.value = false; |
| 60 | - ElMessage.success(`已选择 ${card.holderName} 的园区卡`); | 155 | + ElMessage.success(`已选择 ${card.name} 的园区卡`); |
| 61 | payBtnShow.value = true; | 156 | payBtnShow.value = true; |
| 62 | - // 园区卡选择后自动支付 | ||
| 63 | - // handlePay(); | 157 | +}; |
| 158 | + | ||
| 159 | +const queryPayment = async () => { | ||
| 160 | + if (!openId.value || !currentPayType.value) { | ||
| 161 | + ElMessage.error('支付参数不完整'); | ||
| 162 | + return false; | ||
| 163 | + } | ||
| 164 | + | ||
| 165 | + try { | ||
| 166 | + const params = { | ||
| 167 | + tradeId: orderInfoData.value.tradeId, | ||
| 168 | + pipelineId: currentPayType.value.pipelineId, | ||
| 169 | + params: { openId: openId.value }, | ||
| 170 | + }; | ||
| 171 | + | ||
| 172 | + const data = await orderPayment(params); | ||
| 173 | + const queryString = qs.stringify(data); | ||
| 174 | + | ||
| 175 | + // 跳转到微信支付页面 | ||
| 176 | + if (typeof jWeixin !== 'undefined' && jWeixin.miniProgram) { | ||
| 177 | + jWeixin.miniProgram.redirectTo({ | ||
| 178 | + url: `/packageA/pages/wePay/index?${queryString}`, | ||
| 179 | + }); | ||
| 180 | + } else { | ||
| 181 | + console.warn('非微信小程序环境'); | ||
| 182 | + // 模拟支付成功 | ||
| 183 | + setTimeout(() => { | ||
| 184 | + loading.value = false; | ||
| 185 | + paymentSuccess.value = true; | ||
| 186 | + setTimeout(() => { | ||
| 187 | + paymentSuccess.value = false; | ||
| 188 | + resetPaymentState(); | ||
| 189 | + }, 2000); | ||
| 190 | + }, 1500); | ||
| 191 | + } | ||
| 192 | + } catch (error) { | ||
| 193 | + console.error('支付失败:', error); | ||
| 194 | + ElMessage.error('支付请求失败,请重试'); | ||
| 195 | + loading.value = false; | ||
| 196 | + } | ||
| 64 | }; | 197 | }; |
| 65 | 198 | ||
| 66 | const handlePay = async () => { | 199 | const handlePay = async () => { |
| 200 | + if (!currentPayType.value) { | ||
| 201 | + ElMessage.warning('请先选择支付方式'); | ||
| 202 | + return; | ||
| 203 | + } | ||
| 204 | + | ||
| 205 | + const channelName = currentPayType.value.channelName.toLowerCase(); | ||
| 206 | + const isCardPayment = | ||
| 207 | + channelName.includes('园区卡') || channelName.includes('卡支付'); | ||
| 208 | + | ||
| 209 | + if (isCardPayment && !selectedCard.value) { | ||
| 210 | + ElMessage.warning('请先选择园区卡'); | ||
| 211 | + return; | ||
| 212 | + } | ||
| 213 | + | ||
| 67 | loading.value = true; | 214 | loading.value = true; |
| 68 | 215 | ||
| 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); | 216 | + if (isCardPayment) { |
| 217 | + // 园区卡支付逻辑 | ||
| 218 | + try { | ||
| 219 | + const params = { | ||
| 220 | + tradeId: orderInfoData.value.tradeId, | ||
| 221 | + pipelineId: currentPayType.value.pipelineId, | ||
| 222 | + params: { | ||
| 223 | + openId: openId.value, | ||
| 224 | + cardNo: selectedCard.value?.cardNo, | ||
| 225 | + }, | ||
| 226 | + }; | ||
| 227 | + | ||
| 228 | + const data = await orderPayment(params); | ||
| 229 | + loading.value = false; | ||
| 230 | + paymentSuccess.value = true; | ||
| 231 | + | ||
| 232 | + setTimeout(() => { | ||
| 233 | + paymentSuccess.value = false; | ||
| 234 | + resetPaymentState(); | ||
| 235 | + }, 2000); | ||
| 236 | + } catch (error) { | ||
| 237 | + console.error('园区卡支付失败:', error); | ||
| 238 | + ElMessage.error('支付失败,请重试'); | ||
| 239 | + loading.value = false; | ||
| 240 | + } | ||
| 241 | + } else { | ||
| 242 | + // 其他支付方式(微信、支付宝等) | ||
| 243 | + await queryPayment(); | ||
| 244 | + } | ||
| 245 | +}; | ||
| 246 | + | ||
| 247 | +const resetPaymentState = () => { | ||
| 248 | + payBtnShow.value = false; | ||
| 249 | + currentPaymentMethod.value = null; | ||
| 250 | + selectedCard.value = null; | ||
| 251 | + currentPayType.value = null; | ||
| 81 | }; | 252 | }; |
| 82 | 253 | ||
| 83 | const handleCloseDialog = () => { | 254 | const handleCloseDialog = () => { |
| 84 | showCardDialog.value = false; | 255 | showCardDialog.value = false; |
| 85 | payBtnShow.value = false; | 256 | payBtnShow.value = false; |
| 86 | }; | 257 | }; |
| 258 | + | ||
| 259 | +// 获取订单信息 支付方式等数据 | ||
| 260 | +const getOrderInfo = async () => { | ||
| 261 | + try { | ||
| 262 | + const data = await orderInfo(token.value); | ||
| 263 | + orderInfoData.value = data || {}; | ||
| 264 | + } catch (error) { | ||
| 265 | + console.error('获取订单信息失败:', error); | ||
| 266 | + throw error; | ||
| 267 | + } | ||
| 268 | +}; | ||
| 269 | + | ||
| 270 | +// 查询园区卡列表 | ||
| 271 | +const getListUserCards = async () => { | ||
| 272 | + if (!currentPayType.value || !orderInfoData.value.userId) { | ||
| 273 | + return; | ||
| 274 | + } | ||
| 275 | + | ||
| 276 | + try { | ||
| 277 | + const data = await listUserCards( | ||
| 278 | + currentPayType.value.pipelineId, | ||
| 279 | + orderInfoData.value.userId, | ||
| 280 | + ); | ||
| 281 | + cardList.value = data || []; | ||
| 282 | + | ||
| 283 | + if (cardList.value.length === 0) { | ||
| 284 | + ElMessage.warning('未找到可用的园区卡'); | ||
| 285 | + } | ||
| 286 | + } catch (error) { | ||
| 287 | + console.error('获取园区卡列表失败:', error); | ||
| 288 | + ElMessage.error('获取园区卡列表失败'); | ||
| 289 | + } | ||
| 290 | +}; | ||
| 291 | + | ||
| 292 | +const init = async () => { | ||
| 293 | + token.value = (route.query?.token as string) || ''; | ||
| 294 | + openId.value = (route.query?.openId as string) || ''; | ||
| 295 | + | ||
| 296 | + if (!hasValidParams.value) { | ||
| 297 | + return; | ||
| 298 | + } | ||
| 299 | + | ||
| 300 | + try { | ||
| 301 | + loadLoading.value = true; | ||
| 302 | + await getOrderInfo(); | ||
| 303 | + } catch { | ||
| 304 | + ElMessage.error('加载订单信息失败'); | ||
| 305 | + } finally { | ||
| 306 | + loadLoading.value = false; | ||
| 307 | + } | ||
| 308 | +}; | ||
| 309 | + | ||
| 310 | +init(); | ||
| 87 | </script> | 311 | </script> |
| 88 | 312 | ||
| 89 | <template> | 313 | <template> |
| 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 /> | 314 | + <div class="cashier-container" v-loading="loadLoading"> |
| 315 | + <!-- 无效参数提示 --> | ||
| 316 | + <div v-if="!hasValidParams" class="error-container"> | ||
| 317 | + <div class="error-content"> | ||
| 318 | + <div class="error-icon"> | ||
| 319 | + <ElIcon :size="64" color="#f59e0b"> | ||
| 320 | + <WarningFilled /> | ||
| 97 | </ElIcon> | 321 | </ElIcon> |
| 98 | </div> | 322 | </div> |
| 99 | - <h1 class="title">地利收银台</h1> | ||
| 100 | - <p class="subtitle">请选择支付方式完成订单</p> | 323 | + <h3 class="error-title">未查询到有效的订单信息</h3> |
| 324 | + <p class="error-desc">请确认订单链接是否正确或重新发起支付</p> | ||
| 325 | + <div class="error-tips"> | ||
| 326 | + <p>可能的原因:</p> | ||
| 327 | + <ul> | ||
| 328 | + <li>订单链接已过期</li> | ||
| 329 | + <li>订单参数不完整</li> | ||
| 330 | + <li>网络连接异常</li> | ||
| 331 | + </ul> | ||
| 332 | + </div> | ||
| 101 | </div> | 333 | </div> |
| 334 | + </div> | ||
| 102 | 335 | ||
| 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> | 336 | + <!-- 正常支付界面 --> |
| 337 | + <div v-else class="cashier-wrapper"> | ||
| 338 | + <div class="cashier-content"> | ||
| 339 | + <!-- 头部 --> | ||
| 340 | + <div class="header"> | ||
| 341 | + <h1 class="title">地利收银台</h1> | ||
| 342 | + <p class="subtitle">快捷·安全</p> | ||
| 108 | </div> | 343 | </div> |
| 109 | - </ElCard> | ||
| 110 | - | ||
| 111 | - <!-- 支付方式选择 --> | ||
| 112 | - <ElCard class="payment-card" shadow="always"> | ||
| 113 | - <template #header> | ||
| 114 | - <span class="card-header">选择支付方式</span> | ||
| 115 | - </template> | ||
| 116 | 344 | ||
| 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> | 345 | + <!-- 订单金额卡片 --> |
| 346 | + <div class="amount-card"> | ||
| 347 | + <div class="amount-content"> | ||
| 348 | + <p class="amount-label">订单金额</p> | ||
| 349 | + <p class="amount-value">¥{{ displayAmount }}</p> | ||
| 350 | + <p class="goods-info">{{ orderInfoData?.goods || '商品信息' }}</p> | ||
| 138 | </div> | 351 | </div> |
| 139 | - <ElIcon | ||
| 140 | - :size="20" | ||
| 141 | - :color="currentPaymentMethod === 'wechat' ? '#fff' : '#909399'" | ||
| 142 | - > | ||
| 143 | - <ArrowRight /> | ||
| 144 | - </ElIcon> | ||
| 145 | </div> | 352 | </div> |
| 146 | 353 | ||
| 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'" | 354 | + <!-- 支付方式选择 --> |
| 355 | + <div class="payment-section"> | ||
| 356 | + <div class="section-header"> | ||
| 357 | + <span class="section-title">选择支付方式</span> | ||
| 358 | + </div> | ||
| 359 | + | ||
| 360 | + <!-- 循环渲染支付方式 --> | ||
| 361 | + <div | ||
| 362 | + v-for="pipeline in paymentPipelines" | ||
| 363 | + :key="pipeline.pipelineId" | ||
| 364 | + class="payment-item" | ||
| 365 | + :class="{ active: currentPaymentMethod === pipeline.pipelineId }" | ||
| 366 | + @click="handlepayBtnShowClick(pipeline)" | ||
| 367 | + > | ||
| 368 | + <div class="payment-item-left"> | ||
| 369 | + <div | ||
| 370 | + class="payment-icon" | ||
| 371 | + :class="{ | ||
| 372 | + active: currentPaymentMethod === pipeline.pipelineId, | ||
| 373 | + }" | ||
| 374 | + :style="{ | ||
| 375 | + backgroundColor: | ||
| 376 | + currentPaymentMethod === pipeline.pipelineId | ||
| 377 | + ? getPaymentIcon(pipeline.channelName).activeBgColor | ||
| 378 | + : getPaymentIcon(pipeline.channelName).bgColor, | ||
| 379 | + }" | ||
| 161 | > | 380 | > |
| 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> | 381 | + <!-- 微信支付图标 --> |
| 382 | + <svg | ||
| 383 | + v-if="getPaymentIcon(pipeline.channelName).icon === 'wechat'" | ||
| 384 | + class="payment-svg-icon" | ||
| 385 | + viewBox="0 0 24 24" | ||
| 386 | + fill="currentColor" | ||
| 387 | + :style="{ | ||
| 388 | + color: | ||
| 389 | + currentPaymentMethod === pipeline.pipelineId | ||
| 390 | + ? getPaymentIcon(pipeline.channelName).activeColor | ||
| 391 | + : getPaymentIcon(pipeline.channelName).color, | ||
| 392 | + }" | ||
| 393 | + > | ||
| 394 | + <path | ||
| 395 | + 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" | ||
| 396 | + /> | ||
| 397 | + </svg> | ||
| 398 | + | ||
| 399 | + <!-- 园区卡图标 --> | ||
| 400 | + <ElIcon | ||
| 401 | + v-else-if=" | ||
| 402 | + getPaymentIcon(pipeline.channelName).icon === 'card' | ||
| 403 | + " | ||
| 404 | + :size="28" | ||
| 405 | + :color=" | ||
| 406 | + currentPaymentMethod === pipeline.pipelineId | ||
| 407 | + ? getPaymentIcon(pipeline.channelName).activeColor | ||
| 408 | + : getPaymentIcon(pipeline.channelName).color | ||
| 409 | + " | ||
| 410 | + > | ||
| 411 | + <CreditCard /> | ||
| 412 | + </ElIcon> | ||
| 413 | + | ||
| 414 | + <!-- 其他支付方式图标 --> | ||
| 415 | + <ElIcon | ||
| 416 | + v-else | ||
| 417 | + :size="28" | ||
| 418 | + :color=" | ||
| 419 | + currentPaymentMethod === pipeline.pipelineId | ||
| 420 | + ? getPaymentIcon(pipeline.channelName).activeColor | ||
| 421 | + : getPaymentIcon(pipeline.channelName).color | ||
| 422 | + " | ||
| 423 | + > | ||
| 424 | + <Wallet /> | ||
| 425 | + </ElIcon> | ||
| 426 | + </div> | ||
| 427 | + <div class="payment-info"> | ||
| 428 | + <p class="payment-name">{{ pipeline.channelName }}</p> | ||
| 429 | + <p class="payment-desc">{{ getPaymentDesc(pipeline) }}</p> | ||
| 430 | + </div> | ||
| 174 | </div> | 431 | </div> |
| 432 | + <ElRadio | ||
| 433 | + :model-value="currentPaymentMethod" | ||
| 434 | + :label="pipeline.pipelineId" | ||
| 435 | + size="large" | ||
| 436 | + /> | ||
| 437 | + </div> | ||
| 438 | + | ||
| 439 | + <!-- 无支付方式提示 --> | ||
| 440 | + <div v-if="paymentPipelines.length === 0" class="no-payment"> | ||
| 441 | + <p>暂无可用支付方式</p> | ||
| 175 | </div> | 442 | </div> |
| 176 | - <ElIcon | ||
| 177 | - :size="20" | ||
| 178 | - :color="currentPaymentMethod === 'card' ? '#fff' : '#909399'" | ||
| 179 | - > | ||
| 180 | - <ArrowRight /> | ||
| 181 | - </ElIcon> | ||
| 182 | </div> | 443 | </div> |
| 183 | - </ElCard> | 444 | + </div> |
| 184 | 445 | ||
| 185 | - <!-- 微信支付确认按钮 --> | ||
| 186 | - <transition name="slide-fade"> | 446 | + <!-- 底部支付按钮 --> |
| 447 | + <div class="payment-footer"> | ||
| 187 | <ElButton | 448 | <ElButton |
| 188 | - v-if="payBtnShow === true" | 449 | + :disabled="!payBtnShow" |
| 189 | class="pay-button" | 450 | class="pay-button" |
| 190 | - type="success" | 451 | + type="primary" |
| 191 | size="large" | 452 | size="large" |
| 192 | :loading="loading" | 453 | :loading="loading" |
| 193 | @click="handlePay" | 454 | @click="handlePay" |
| 194 | > | 455 | > |
| 195 | - {{ loading ? '处理中...' : `确认支付 ¥${amount.toFixed(2)}` }} | 456 | + {{ loading ? '处理中...' : `确认支付 ¥${displayAmount}` }} |
| 196 | </ElButton> | 457 | </ElButton> |
| 197 | - </transition> | 458 | + </div> |
| 198 | </div> | 459 | </div> |
| 199 | 460 | ||
| 200 | <!-- 园区卡选择弹窗 --> | 461 | <!-- 园区卡选择弹窗 --> |
| @@ -208,20 +469,25 @@ const handleCloseDialog = () => { | @@ -208,20 +469,25 @@ const handleCloseDialog = () => { | ||
| 208 | > | 469 | > |
| 209 | <div class="card-list-dialog"> | 470 | <div class="card-list-dialog"> |
| 210 | <div | 471 | <div |
| 211 | - v-for="card in mockCards" | ||
| 212 | - :key="card.id" | 472 | + v-for="card in cardList" |
| 473 | + :key="card.cardNo" | ||
| 213 | class="card-item-dialog" | 474 | class="card-item-dialog" |
| 214 | @click="handleCardSelect(card)" | 475 | @click="handleCardSelect(card)" |
| 215 | > | 476 | > |
| 216 | <div class="card-item-info"> | 477 | <div class="card-item-info"> |
| 217 | - <p class="card-holder">{{ card.holderName }}</p> | 478 | + <p class="card-holder">{{ card.name }}</p> |
| 218 | <p class="card-number">{{ card.cardNo }}</p> | 479 | <p class="card-number">{{ card.cardNo }}</p> |
| 219 | </div> | 480 | </div> |
| 220 | <div class="card-balance"> | 481 | <div class="card-balance"> |
| 221 | <p class="balance-label">余额</p> | 482 | <p class="balance-label">余额</p> |
| 222 | - <p class="balance-value">¥{{ card.balance.toFixed(2) }}</p> | 483 | + <p class="balance-value"> |
| 484 | + ¥{{ (Number(card.amount) / 100).toFixed(2) }} | ||
| 485 | + </p> | ||
| 223 | </div> | 486 | </div> |
| 224 | </div> | 487 | </div> |
| 488 | + <div v-if="cardList.length === 0" class="empty-card"> | ||
| 489 | + <p>暂无可用园区卡</p> | ||
| 490 | + </div> | ||
| 225 | </div> | 491 | </div> |
| 226 | <template #footer> | 492 | <template #footer> |
| 227 | <ElButton @click="handleCloseDialog">取消</ElButton> | 493 | <ElButton @click="handleCloseDialog">取消</ElButton> |
| @@ -296,39 +562,113 @@ const handleCloseDialog = () => { | @@ -296,39 +562,113 @@ const handleCloseDialog = () => { | ||
| 296 | 562 | ||
| 297 | .cashier-container { | 563 | .cashier-container { |
| 298 | min-height: 100vh; | 564 | min-height: 100vh; |
| 565 | + background: linear-gradient( | ||
| 566 | + to bottom, | ||
| 567 | + #fff9f1 0%, | ||
| 568 | + #ffe9d4 20%, | ||
| 569 | + #ffe3c8 29%, | ||
| 570 | + #f7f7f7 45%, | ||
| 571 | + #f7f7f7 100% | ||
| 572 | + ); | ||
| 573 | +} | ||
| 574 | + | ||
| 575 | +// 错误提示容器 | ||
| 576 | +.error-container { | ||
| 577 | + display: flex; | ||
| 578 | + align-items: center; | ||
| 579 | + justify-content: center; | ||
| 580 | + min-height: 100vh; | ||
| 299 | padding: 20px; | 581 | padding: 20px; |
| 300 | - background: linear-gradient(135deg, #f0fdf4 0%, #d1fae5 100%); | 582 | +} |
| 301 | 583 | ||
| 302 | - @media (min-width: 768px) { | ||
| 303 | - padding: 40px; | 584 | +.error-content { |
| 585 | + max-width: 500px; | ||
| 586 | + padding: 40px 30px; | ||
| 587 | + text-align: center; | ||
| 588 | + background: #fff; | ||
| 589 | + border-radius: 16px; | ||
| 590 | + box-shadow: 0 4px 20px rgb(0 0 0 / 8%); | ||
| 591 | + animation: fadeIn 0.5s ease-out; | ||
| 592 | + | ||
| 593 | + .error-icon { | ||
| 594 | + margin-bottom: 24px; | ||
| 595 | + animation: bounceIn 0.6s ease-out; | ||
| 596 | + } | ||
| 597 | + | ||
| 598 | + .error-title { | ||
| 599 | + margin: 0 0 12px; | ||
| 600 | + font-size: 22px; | ||
| 601 | + font-weight: bold; | ||
| 602 | + color: #1f2937; | ||
| 603 | + } | ||
| 604 | + | ||
| 605 | + .error-desc { | ||
| 606 | + margin: 0 0 24px; | ||
| 607 | + font-size: 15px; | ||
| 608 | + line-height: 1.6; | ||
| 609 | + color: #6b7280; | ||
| 610 | + } | ||
| 611 | + | ||
| 612 | + .error-tips { | ||
| 613 | + padding: 20px; | ||
| 614 | + text-align: left; | ||
| 615 | + background: #fef3c7; | ||
| 616 | + border-radius: 12px; | ||
| 617 | + | ||
| 618 | + p { | ||
| 619 | + margin: 0 0 12px; | ||
| 620 | + font-size: 14px; | ||
| 621 | + font-weight: 600; | ||
| 622 | + color: #92400e; | ||
| 623 | + } | ||
| 624 | + | ||
| 625 | + ul { | ||
| 626 | + padding-left: 20px; | ||
| 627 | + margin: 0; | ||
| 628 | + | ||
| 629 | + li { | ||
| 630 | + margin-bottom: 8px; | ||
| 631 | + font-size: 13px; | ||
| 632 | + line-height: 1.5; | ||
| 633 | + color: #78350f; | ||
| 634 | + | ||
| 635 | + &:last-child { | ||
| 636 | + margin-bottom: 0; | ||
| 637 | + } | ||
| 638 | + } | ||
| 639 | + } | ||
| 304 | } | 640 | } |
| 305 | } | 641 | } |
| 306 | 642 | ||
| 307 | .cashier-wrapper { | 643 | .cashier-wrapper { |
| 644 | + display: flex; | ||
| 645 | + flex-direction: column; | ||
| 308 | max-width: 500px; | 646 | max-width: 500px; |
| 647 | + min-height: 100vh; | ||
| 309 | margin: 0 auto; | 648 | margin: 0 auto; |
| 310 | } | 649 | } |
| 311 | 650 | ||
| 651 | +.cashier-content { | ||
| 652 | + flex: 1; | ||
| 653 | + padding: 20px; | ||
| 654 | + overflow-y: auto; | ||
| 655 | + | ||
| 656 | + @media (min-width: 768px) { | ||
| 657 | + padding: 40px 20px 20px; | ||
| 658 | + } | ||
| 659 | +} | ||
| 660 | + | ||
| 312 | // 头部样式 | 661 | // 头部样式 |
| 313 | .header { | 662 | .header { |
| 663 | + display: flex; | ||
| 664 | + align-items: end; | ||
| 665 | + justify-content: flex-start; | ||
| 314 | margin-bottom: 30px; | 666 | margin-bottom: 30px; |
| 315 | text-align: center; | 667 | text-align: center; |
| 316 | animation: fadeIn 0.5s ease-out; | 668 | animation: fadeIn 0.5s ease-out; |
| 317 | 669 | ||
| 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 { | 670 | .title { |
| 331 | - margin: 0 0 8px; | 671 | + margin-right: 10px; |
| 332 | font-size: 28px; | 672 | font-size: 28px; |
| 333 | font-weight: bold; | 673 | font-weight: bold; |
| 334 | color: #1f2937; | 674 | color: #1f2937; |
| @@ -339,7 +679,7 @@ const handleCloseDialog = () => { | @@ -339,7 +679,7 @@ const handleCloseDialog = () => { | ||
| 339 | } | 679 | } |
| 340 | 680 | ||
| 341 | .subtitle { | 681 | .subtitle { |
| 342 | - margin: 0; | 682 | + margin-top: 18px; |
| 343 | font-size: 14px; | 683 | font-size: 14px; |
| 344 | color: #6b7280; | 684 | color: #6b7280; |
| 345 | } | 685 | } |
| @@ -347,16 +687,23 @@ const handleCloseDialog = () => { | @@ -347,16 +687,23 @@ const handleCloseDialog = () => { | ||
| 347 | 687 | ||
| 348 | // 金额卡片 | 688 | // 金额卡片 |
| 349 | .amount-card { | 689 | .amount-card { |
| 690 | + display: flex; | ||
| 691 | + flex-direction: column; | ||
| 692 | + align-items: center; | ||
| 693 | + justify-content: center; | ||
| 694 | + min-height: 100px; | ||
| 695 | + padding: 32px; | ||
| 350 | margin-bottom: 24px; | 696 | margin-bottom: 24px; |
| 697 | + background: linear-gradient(180deg, #fff8ee 0%, #fff 100%); | ||
| 351 | border: none; | 698 | border: none; |
| 352 | border-radius: 16px; | 699 | border-radius: 16px; |
| 353 | animation: slideUp 0.5s ease-out 0.1s both; | 700 | animation: slideUp 0.5s ease-out 0.1s both; |
| 354 | 701 | ||
| 355 | - :deep(.el-card__body) { | ||
| 356 | - padding: 32px; | ||
| 357 | - } | ||
| 358 | - | ||
| 359 | .amount-content { | 702 | .amount-content { |
| 703 | + display: flex; | ||
| 704 | + flex-direction: column; | ||
| 705 | + align-items: center; | ||
| 706 | + justify-content: center; | ||
| 360 | text-align: center; | 707 | text-align: center; |
| 361 | 708 | ||
| 362 | .amount-label { | 709 | .amount-label { |
| @@ -366,29 +713,38 @@ const handleCloseDialog = () => { | @@ -366,29 +713,38 @@ const handleCloseDialog = () => { | ||
| 366 | } | 713 | } |
| 367 | 714 | ||
| 368 | .amount-value { | 715 | .amount-value { |
| 369 | - margin: 0; | ||
| 370 | - font-size: 48px; | ||
| 371 | - font-weight: bold; | ||
| 372 | - color: #10b981; | 716 | + margin: 0 0 8px; |
| 717 | + font-size: 26px; | ||
| 718 | + font-weight: 700; | ||
| 719 | + color: #ea4200; | ||
| 373 | 720 | ||
| 374 | @media (min-width: 768px) { | 721 | @media (min-width: 768px) { |
| 375 | - font-size: 56px; | 722 | + font-size: 48px; |
| 376 | } | 723 | } |
| 377 | } | 724 | } |
| 725 | + | ||
| 726 | + .goods-info { | ||
| 727 | + margin: 0; | ||
| 728 | + font-size: 14px; | ||
| 729 | + color: #6b7280; | ||
| 730 | + } | ||
| 378 | } | 731 | } |
| 379 | } | 732 | } |
| 380 | 733 | ||
| 381 | -// 支付方式卡片 | ||
| 382 | -.payment-card { | 734 | +// 支付方式区域 |
| 735 | +.payment-section { | ||
| 383 | margin-bottom: 24px; | 736 | margin-bottom: 24px; |
| 384 | - border: none; | ||
| 385 | - border-radius: 16px; | ||
| 386 | animation: slideUp 0.5s ease-out 0.2s both; | 737 | animation: slideUp 0.5s ease-out 0.2s both; |
| 387 | 738 | ||
| 388 | - .card-header { | ||
| 389 | - font-size: 16px; | ||
| 390 | - font-weight: 600; | ||
| 391 | - color: #1f2937; | 739 | + .section-header { |
| 740 | + padding: 0 4px; | ||
| 741 | + margin-bottom: 16px; | ||
| 742 | + | ||
| 743 | + .section-title { | ||
| 744 | + font-size: 16px; | ||
| 745 | + font-weight: 600; | ||
| 746 | + color: #1f2937; | ||
| 747 | + } | ||
| 392 | } | 748 | } |
| 393 | 749 | ||
| 394 | .payment-item { | 750 | .payment-item { |
| @@ -398,7 +754,8 @@ const handleCloseDialog = () => { | @@ -398,7 +754,8 @@ const handleCloseDialog = () => { | ||
| 398 | padding: 16px; | 754 | padding: 16px; |
| 399 | margin-bottom: 12px; | 755 | margin-bottom: 12px; |
| 400 | cursor: pointer; | 756 | cursor: pointer; |
| 401 | - background: #f9fafb; | 757 | + background: #fff; |
| 758 | + border: 2px solid transparent; | ||
| 402 | border-radius: 12px; | 759 | border-radius: 12px; |
| 403 | transition: all 0.3s ease; | 760 | transition: all 0.3s ease; |
| 404 | 761 | ||
| @@ -407,19 +764,11 @@ const handleCloseDialog = () => { | @@ -407,19 +764,11 @@ const handleCloseDialog = () => { | ||
| 407 | } | 764 | } |
| 408 | 765 | ||
| 409 | &:hover { | 766 | &:hover { |
| 410 | - background: #f3f4f6; | ||
| 411 | - transform: translateX(4px); | 767 | + border-color: #fecaca; |
| 412 | } | 768 | } |
| 413 | 769 | ||
| 414 | &.active { | 770 | &.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 | - } | 771 | + border-color: #ea4200; |
| 423 | } | 772 | } |
| 424 | 773 | ||
| 425 | .payment-item-left { | 774 | .payment-item-left { |
| @@ -434,29 +783,20 @@ const handleCloseDialog = () => { | @@ -434,29 +783,20 @@ const handleCloseDialog = () => { | ||
| 434 | justify-content: center; | 783 | justify-content: center; |
| 435 | width: 48px; | 784 | width: 48px; |
| 436 | height: 48px; | 785 | height: 48px; |
| 437 | - background: #d1fae5; | ||
| 438 | border-radius: 50%; | 786 | border-radius: 50%; |
| 439 | transition: all 0.3s ease; | 787 | transition: all 0.3s ease; |
| 440 | 788 | ||
| 441 | - &.active { | ||
| 442 | - background: rgb(255 255 255 / 20%); | ||
| 443 | - } | ||
| 444 | - | ||
| 445 | - .wechat-icon { | 789 | + .payment-svg-icon { |
| 446 | width: 28px; | 790 | width: 28px; |
| 447 | height: 28px; | 791 | height: 28px; |
| 448 | - color: #10b981; | ||
| 449 | } | 792 | } |
| 450 | } | 793 | } |
| 451 | 794 | ||
| 452 | - &.active .wechat-icon { | ||
| 453 | - color: white; | ||
| 454 | - } | ||
| 455 | - | ||
| 456 | .payment-name { | 795 | .payment-name { |
| 457 | margin: 0 0 4px; | 796 | margin: 0 0 4px; |
| 458 | font-size: 16px; | 797 | font-size: 16px; |
| 459 | font-weight: 600; | 798 | font-weight: 600; |
| 799 | + color: #1f2937; | ||
| 460 | } | 800 | } |
| 461 | 801 | ||
| 462 | .payment-desc { | 802 | .payment-desc { |
| @@ -464,27 +804,73 @@ const handleCloseDialog = () => { | @@ -464,27 +804,73 @@ const handleCloseDialog = () => { | ||
| 464 | font-size: 13px; | 804 | font-size: 13px; |
| 465 | color: #6b7280; | 805 | color: #6b7280; |
| 466 | } | 806 | } |
| 807 | + | ||
| 808 | + :deep(.el-radio) { | ||
| 809 | + margin-right: 0; | ||
| 810 | + | ||
| 811 | + .el-radio__input { | ||
| 812 | + .el-radio__inner { | ||
| 813 | + width: 20px; | ||
| 814 | + height: 20px; | ||
| 815 | + border-width: 2px; | ||
| 816 | + } | ||
| 817 | + } | ||
| 818 | + | ||
| 819 | + .el-radio__label { | ||
| 820 | + display: none; | ||
| 821 | + } | ||
| 822 | + } | ||
| 823 | + | ||
| 824 | + &.active :deep(.el-radio) { | ||
| 825 | + .el-radio__input.is-checked { | ||
| 826 | + .el-radio__inner { | ||
| 827 | + background-color: #ea4200; | ||
| 828 | + border-color: #ea4200; | ||
| 829 | + } | ||
| 830 | + } | ||
| 831 | + } | ||
| 832 | + } | ||
| 833 | + | ||
| 834 | + .no-payment { | ||
| 835 | + padding: 40px 20px; | ||
| 836 | + color: #9ca3af; | ||
| 837 | + text-align: center; | ||
| 838 | + background: #fff; | ||
| 839 | + border-radius: 12px; | ||
| 467 | } | 840 | } |
| 468 | } | 841 | } |
| 469 | 842 | ||
| 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%); | 843 | +// 底部支付按钮区域 |
| 844 | +.payment-footer { | ||
| 845 | + padding: 16px 20px; | ||
| 846 | + padding-bottom: calc(16px + env(safe-area-inset-bottom)); | ||
| 847 | + background: #fff; | ||
| 848 | + border-top: 1px solid #f0f0f0; | ||
| 480 | 849 | ||
| 481 | - &:hover { | ||
| 482 | - box-shadow: 0 12px 32px rgb(16 185 129 / 50%); | ||
| 483 | - transform: translateY(-2px); | ||
| 484 | - } | 850 | + .pay-button { |
| 851 | + width: 100%; | ||
| 852 | + height: 42px; | ||
| 853 | + font-size: 18px; | ||
| 854 | + font-weight: 600; | ||
| 855 | + color: #fff; | ||
| 856 | + background: #ea4200; | ||
| 857 | + border: none; | ||
| 858 | + border-radius: 21px; | ||
| 859 | + box-shadow: 0 4px 16px rgb(234 66 0 / 30%); | ||
| 860 | + | ||
| 861 | + &:hover:not(:disabled) { | ||
| 862 | + background: #d93d00; | ||
| 863 | + box-shadow: 0 6px 20px rgb(234 66 0 / 40%); | ||
| 864 | + } | ||
| 865 | + | ||
| 866 | + &:active:not(:disabled) { | ||
| 867 | + transform: scale(0.98); | ||
| 868 | + } | ||
| 485 | 869 | ||
| 486 | - &:active { | ||
| 487 | - transform: scale(0.98); | 870 | + &:disabled { |
| 871 | + cursor: not-allowed; | ||
| 872 | + opacity: 0.5; | ||
| 873 | + } | ||
| 488 | } | 874 | } |
| 489 | } | 875 | } |
| 490 | 876 | ||
| @@ -512,6 +898,12 @@ const handleCloseDialog = () => { | @@ -512,6 +898,12 @@ const handleCloseDialog = () => { | ||
| 512 | gap: 12px; | 898 | gap: 12px; |
| 513 | max-height: 400px; | 899 | max-height: 400px; |
| 514 | overflow-y: auto; | 900 | overflow-y: auto; |
| 901 | + | ||
| 902 | + .empty-card { | ||
| 903 | + padding: 40px 20px; | ||
| 904 | + color: #9ca3af; | ||
| 905 | + text-align: center; | ||
| 906 | + } | ||
| 515 | } | 907 | } |
| 516 | 908 | ||
| 517 | .card-item-dialog { | 909 | .card-item-dialog { |
| @@ -602,23 +994,4 @@ const handleCloseDialog = () => { | @@ -602,23 +994,4 @@ const handleCloseDialog = () => { | ||
| 602 | color: #6b7280; | 994 | color: #6b7280; |
| 603 | } | 995 | } |
| 604 | } | 996 | } |
| 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> | 997 | </style> |
apps/web-payment/vite.config.mts
| @@ -17,7 +17,7 @@ export default defineConfig(async () => { | @@ -17,7 +17,7 @@ export default defineConfig(async () => { | ||
| 17 | changeOrigin: true, | 17 | changeOrigin: true, |
| 18 | rewrite: (path) => path.replace(/^\/api/, ''), | 18 | rewrite: (path) => path.replace(/^\/api/, ''), |
| 19 | // mock代理目标地址 | 19 | // mock代理目标地址 |
| 20 | - target: 'http://localhost:5320/api', | 20 | + target: 'http://10.28.3.24:8686', |
| 21 | ws: true, | 21 | ws: true, |
| 22 | }, | 22 | }, |
| 23 | }, | 23 | }, |