| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501 |
- <script setup lang="ts">
- import { useI18n } from 'vue-i18n'
- import { useOrderPopup } from '~/composables/useOrderPopup'
- import OrderCheckSvg from '~/assets/icons/order/check.svg'
- import ConfirmDialog from '~/components/common/ConfirmDialog.vue'
- const { state, totalPrice, hasDiscount, discountAmount, discountRatePercent, close, confirm, setQuantity } = useOrderPopup()
- const { wallet } = useAuth()
- const { openRecharge } = useRecharge()
- const { t } = useI18n()
- const img = useImage()
- const walletCoins = computed(() => wallet.value?.goldCoin ?? 0)
- const isWalletBalanceEnough = computed(() => totalPrice.value <= walletCoins.value)
- const orderConfirmVisible = ref<boolean>(false)
- const handleUpdateShow = (value: boolean) => {
- if (!value) {
- close()
- }
- }
- const handleQuantityChange = (value: number) => {
- setQuantity(value)
- }
- const handleRecharge = () => {
- openRecharge()
- }
- const handleConfirmOpen = () => {
- orderConfirmVisible.value = true
- }
- const handleConfirm = () => {
- confirm()
- }
- const handleCancel = () => {
- orderConfirmVisible.value = false
- }
- </script>
- <template>
- <van-popup
- :show="state.visible"
- round
- position="bottom"
- class="order-popup"
- @update:show="handleUpdateShow"
- >
- <div class="order-popup__container">
- <!-- 头像卡片 + 单价、数量、总价 -->
- <section class="order-popup__card order-popup__card--profile">
- <div class="order-popup__tag truncate">
- {{ state.productType }}
- </div>
- <div class="order-popup__avatar-wrapper">
- <div class="order-popup__avatar">
- <NuxtImg
- :src="state.avatar"
- :alt="state.name"
- loading="lazy"
- :placeholder="img('/avatar.png')"
- />
- </div>
- </div>
- <p class="order-popup__name">
- {{ state.name }}
- </p>
- <div class="order-popup__divider" />
- <div class="order-popup__price-row">
- <div class="order-popup__price-left">
- <p class="order-popup__price-label">
- {{ t('order.detail.unitPrice') }}
- </p>
- <div class="order-popup__rate-info">
- <span
- class="order-popup__coin order-popup__coin--small"
- aria-hidden="true"
- />
- <span class="order-popup__rate-value">
- {{ state.rate.toLocaleString() }}
- </span>
- <span class="order-popup__rate-unit">
- / {{ state.unit }}
- </span>
- </div>
- </div>
- <template v-if="state.specificQuantity == null">
- <CommonStepper
- :model-value="state.quantity"
- @update:model-value="handleQuantityChange"
- />
- </template>
- <template v-else>
- <span class="order-popup__fixed-quantity">
- x{{ state.quantity }}
- </span>
- </template>
- </div>
- <div
- v-if="hasDiscount"
- class="order-popup__discount-row"
- >
- <div class="order-popup__discount-left">
- <span class="order-popup__discount-label">{{ t('order.detail.discount') }}</span>
- <span class="order-popup__discount-tag">
- <span>{{ t('order.detail.discountTagNewOnly') }}</span>
- <span class="order-popup__discount-tag-sep" />
- <span>{{ discountRatePercent }} {{ t('order.detail.discountTagOff') }}</span>
- </span>
- </div>
- <div class="order-popup__discount-amount">
- <span
- class="order-popup__coin order-popup__coin--discount"
- aria-hidden="true"
- />
- <span class="order-popup__discount-value">-{{ discountAmount.toLocaleString() }}</span>
- </div>
- </div>
- <div class="order-popup__total-row">
- <span class="order-popup__total-label">
- {{ t('order.detail.totalLabel') }}
- </span>
- <div class="order-popup__total">
- <span
- class="order-popup__coin order-popup__coin--large"
- aria-hidden="true"
- />
- <span class="order-popup__total-value">
- {{ totalPrice.toLocaleString() }}
- </span>
- </div>
- </div>
- </section>
- <!-- 支付方式:钱包 -->
- <section class="order-popup__card order-popup__card--wallet">
- <div class="order-popup__wallet-main">
- <span class="order-popup__wallet-icon" />
- <div class="order-popup__wallet-info">
- <p class="order-popup__wallet-title">
- {{ t('order.detail.wallet') }}
- <span
- v-if="!isWalletBalanceEnough"
- class="order-popup__wallet-status"
- >
- {{ t('order.detail.walletBalanceNotEnough') }}
- </span>
- </p>
- <p class="order-popup__wallet-balance">
- <span
- class="order-popup__coin order-popup__coin--small"
- aria-hidden="true"
- />
- <span class="order-popup__wallet-balance-value">
- {{ walletCoins.toLocaleString() }}
- </span>
- </p>
- </div>
- </div>
- <div class="order-popup__wallet-check">
- <OrderCheckSvg />
- </div>
- </section>
- <!-- 底部支付按钮 -->
- <button
- v-if="isWalletBalanceEnough"
- type="button"
- class="order-popup__pay-btn"
- @click="handleConfirmOpen"
- >
- {{ t('order.detail.payNow') }}
- </button>
- <button
- v-else
- type="button"
- class="order-popup__pay-btn"
- @click="handleRecharge"
- >
- {{ t('payment.topup.recharge') }}
- </button>
- </div>
- </van-popup>
- <ConfirmDialog
- v-model:show="orderConfirmVisible"
- :title="t('order.confirm.pay.title')"
- :confirm-text="t('order.confirm.pay.confirm')"
- :cancel-text="t('order.confirm.pay.cancel')"
- @confirm="handleConfirm"
- @cancel="handleCancel"
- />
- </template>
- <style scoped lang="scss">
- .order-popup {
- background-color: transparent;
- &__container {
- position: relative;
- padding: 20px 16px 24px;
- border-radius: 20px 20px 0 0;
- background-color: var(--color-bg-primary);
- display: flex;
- flex-direction: column;
- gap: 12px;
- }
- &__card {
- position: relative;
- background-color: #fff;
- border-radius: 12px;
- padding: 24px 12px 12px;
- &--profile {
- padding-top: 54px;
- }
- &--wallet {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 10px 12px;
- }
- }
- &__tag {
- max-width: px2vw(125);
- position: absolute;
- top: 0;
- left: 0;
- padding: 7px 14px;
- border-radius: 12px 0 12px 0;
- background-image: linear-gradient(90deg, #4ED2FF 0%, #B1EF5D 100%);
- font-size: 12px;
- font-weight: 600;
- color: #fff;
- line-height: 1;
- }
- &__avatar-wrapper {
- position: absolute;
- top: -8px;
- left: 50%;
- transform: translateX(-50%);
- }
- &__avatar {
- @include size(60px);
- border-radius: 50%;
- overflow: hidden;
- background: #fff;
- display: flex;
- align-items: center;
- justify-content: center;
- img {
- width: 96%;
- height: 96%;
- border-radius: 50%;
- object-fit: cover;
- display: block;
- }
- }
- &__name {
- text-align: center;
- font-family: var(--font-title);
- font-size: 14px;
- font-weight: 600;
- color: var(--color-text-primary);
- }
- &__divider {
- margin: 16px 0 12px;
- height: 1px;
- background-color: #f2f3f5;
- }
- &__price-row {
- display: flex;
- align-items: center;
- justify-content: space-between;
- }
- &__price-left {
- display: flex;
- align-items: center;
- gap: 8px;
- }
- &__price-label {
- font-size: 12px;
- font-weight: 600;
- color: #1d2129;
- }
- &__discount-row {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 6px 0 7px;
- margin-top: 12px;
- }
- &__discount-left {
- display: flex;
- align-items: center;
- gap: 8px;
- }
- &__discount-label {
- font-size: 12px;
- font-weight: 600;
- color: #1d2129;
- }
- &__discount-tag {
- display: inline-flex;
- align-items: center;
- gap: 4px;
- padding: 4px 6px;
- border-radius: 4px;
- background-color: #ff6f32;
- font-size: 11px;
- font-weight: 400;
- color: #fff;
- line-height: 14px;
- &-sep {
- width: 1px;
- height: 12px;
- background-color: rgba(255, 255, 255, 0.5);
- }
- }
- &__discount-amount {
- display: flex;
- align-items: center;
- gap: 4px;
- }
- &__coin--discount {
- @include size(20px);
- @include bg('~/assets/images/common/coin.png');
- filter: none;
- }
- &__discount-value {
- font-family: var(--font-title);
- font-size: 16px;
- font-weight: 600;
- color: #ff6f32;
- line-height: 17px;
- }
- &__rate-info {
- display: flex;
- align-items: center;
- gap: 3px;
- }
- &__rate-value {
- font-family: var(--font-title);
- font-size: 14px;
- font-weight: 600;
- color: #1d2129;
- transform: translateY(1px);
- }
- &__rate-unit {
- font-size: 14px;
- color: var(--color-text-secondary);
- }
- &__total-row {
- display: flex;
- align-items: center;
- justify-content: flex-end;
- margin-top: 12px;
- }
- &__total-label {
- margin-right: 8px;
- font-size: 14px;
- color: #1d2129;
- }
- &__total {
- display: flex;
- align-items: center;
- gap: 4px;
- }
- &__coin {
- @include bg('~/assets/images/common/coin.png');
- &--small {
- @include size(14px);
- }
- &--large {
- @include size(18px);
- }
- }
- &__total-value {
- font-family: var(--font-title);
- font-size: 24px;
- font-weight: 600;
- color: #1d2129;
- }
- &__fixed-quantity {
- font-size: 14px;
- font-weight: 400;
- color: #1f2024;
- }
- &__wallet-icon {
- @include size(38px);
- @include bg('~/assets/images/order/popup-wallet.png');
- }
- &__wallet-main {
- display: flex;
- align-items: center;
- gap: 8px;
- }
- &__wallet-info {
- display: flex;
- flex-direction: column;
- gap: 2px;
- }
- &__wallet-title {
- font-size: 12px;
- font-weight: 600;
- color: #1d2129;
- }
- &__wallet-status {
- margin-left: 4px;
- font-size: 12px;
- font-weight: 600;
- color: #1d2129;
- }
- &__wallet-balance {
- display: flex;
- align-items: center;
- gap: 4px;
- font-size: 14px;
- color: #86909c;
- }
- &__wallet-balance-value {
- font-size: 14px;
- }
- &__wallet-check {
- margin-left: auto;
- }
- &__pay-btn {
- margin-top: 4px;
- @include size(100%, 47px);
- border: none;
- border-radius: 999px;
- background-image: linear-gradient(90deg, #2f95ff 0%, #50ffd8 100%);
- color: #fff;
- font-family: var(--font-title);
- font-size: 16px;
- font-weight: 600;
- display: flex;
- align-items: center;
- justify-content: center;
- cursor: pointer;
- -webkit-tap-highlight-color: transparent;
- }
- }
- </style>
- <style lang="scss">
- .van-popup.order-popup {
- overflow-y: unset;
- }
- </style>
|