| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728 |
- <script setup lang="ts">
- import { ref, onMounted, computed, watch, onBeforeUnmount } from 'vue'
- import { storeToRefs } from 'pinia'
- import { useI18n } from 'vue-i18n'
- import { useDepositStore } from '@/stores/deposit'
- import { rechargeStatusTranslate } from '@/utils/rechargeI18n'
- import { useToast } from '@/composables/useToast'
- import { useWalletConnectDeposit } from '@/composables/useWalletConnectDeposit'
- import { useWalletConnectTronDeposit } from '@/composables/useWalletConnectTronDeposit'
- import { resolveEvmChainId } from '@/utils/evmRecharge'
- import { qrCodeImageUrl } from '@/utils/qr'
- import { WalletFlowCancelledError } from '@/utils/reownConnectWait'
- const DEPOSIT_MODE_KEY = 'ibit-deposit-mode'
- function loadDepositMode(): 'manual' | 'wallet' {
- try {
- const stored = sessionStorage.getItem(DEPOSIT_MODE_KEY)
- if (stored === 'manual' || stored === 'wallet') {
- return stored
- }
- } catch {
- // ignore
- }
- return 'manual'
- }
- function saveDepositMode(mode: 'manual' | 'wallet') {
- try {
- sessionStorage.setItem(DEPOSIT_MODE_KEY, mode)
- } catch {
- // ignore
- }
- }
- const { t } = useI18n()
- const store = useDepositStore()
- const {
- parentCoins,
- selectedParentIndex,
- selectedNetworkId,
- selectedSubCoinIndex,
- networkOptions,
- subCoinOptions,
- loadingParents,
- childrenLoading,
- subCoinsLoading,
- error,
- currentOrder,
- orderSubmitting,
- hashSubmitting,
- selectedParent,
- selectedNetwork,
- selectedConfig,
- } = storeToRefs(store)
- const toast = useToast()
- const { wcBusy, wcConfigured, resetWalletBusy, canPayWithWallet, connectAndPay } = useWalletConnectDeposit()
- const { wcTronBusy, wcTronConfigured, resetTronWalletBusy, canPayTronWithWallet, connectAndPayTron } =
- useWalletConnectTronDeposit()
- const canUseWalletConnect = computed(() =>
- canPayWithWallet(selectedNetwork.value, currentOrder.value),
- )
- const canUseTronWalletConnect = computed(() =>
- canPayTronWithWallet(selectedNetwork.value, currentOrder.value),
- )
- const anyWcBusy = computed(() => wcBusy.value || wcTronBusy.value)
- const isTronLikeNetwork = computed(() => {
- const o = selectedNetwork.value
- if (!o) {
- return false
- }
- if (resolveEvmChainId(o.protocol, o.networkName) !== null) {
- return false
- }
- const p = o.protocol.toLowerCase()
- const n = o.networkName.toLowerCase()
- return p.includes('tron') || n === 'trc20'
- })
- const amount = ref('')
- const amountError = ref('')
- const txHashInput = ref('')
- const txHashError = ref('')
- const rechargeQrUrl = ref<string | null>(null)
- const depositMode = ref<'manual' | 'wallet'>(loadDepositMode())
- /** 创建订单后锁定充值方式,链上/手工不可再切换 */
- const depositModeLocked = ref(false)
- const walletAttemptSeq = ref(0)
- watch(depositMode, (mode) => {
- if (!depositModeLocked.value) {
- saveDepositMode(mode)
- }
- })
- watch(
- () => currentOrder.value?.rechargeAddress?.trim() ?? '',
- async (addr) => {
- if (!addr) {
- rechargeQrUrl.value = null
- return
- }
- try {
- rechargeQrUrl.value = await qrCodeImageUrl(addr, 200)
- } catch {
- rechargeQrUrl.value = null
- }
- },
- { immediate: true },
- )
- onMounted(() => {
- if (currentOrder.value) {
- depositModeLocked.value = true
- }
- store.loadParentCoins().catch(() => {
- toast.error(store.error ?? t('common.loadFailed'))
- })
- })
- async function onSelectParent(i: number) {
- try {
- await store.selectParent(i)
- } catch {
- toast.error(store.error ?? t('deposit.loadNetworksFailed'))
- }
- }
- async function onSelectNetwork(networkId: number) {
- try {
- await store.selectNetwork(networkId)
- } catch {
- toast.error(store.error ?? t('deposit.loadNetworksFailed'))
- }
- }
- function onSelectSubCoin(i: number) {
- store.selectSubCoin(i)
- }
- function subCoinTabLabel(opt: { coinName: string; coinNameCn: string }) {
- const cn = opt.coinNameCn?.trim()
- if (cn && cn !== opt.coinName) {
- return `${cn} (${opt.coinName})`
- }
- return opt.coinName
- }
- function validateAmount(): boolean {
- amountError.value = ''
- const s = amount.value.trim()
- if (!s) {
- amountError.value = t('deposit.amountRequired')
- return false
- }
- const n = Number(s)
- if (!Number.isFinite(n) || n <= 0) {
- amountError.value = t('deposit.amountPositive')
- return false
- }
- return true
- }
- async function onCreateOrder() {
- if (!validateAmount()) {
- return
- }
- resetPendingWalletUi()
- try {
- await store.createRechargeOrder(amount.value)
- depositModeLocked.value = true
- saveDepositMode(depositMode.value)
- txHashInput.value = ''
- txHashError.value = ''
- toast.success(t('deposit.orderCreated'))
- } catch {
- toast.error(store.error ?? t('deposit.orderFailed'))
- }
- }
- function copyAddress() {
- const addr = currentOrder.value?.rechargeAddress
- if (!addr) {
- return
- }
- navigator.clipboard.writeText(addr).then(() => {
- toast.success(t('deposit.addressCopy'))
- })
- }
- function copyOrderNo() {
- const no = currentOrder.value?.orderNo
- if (!no) {
- return
- }
- navigator.clipboard.writeText(no).then(() => {
- toast.success(t('deposit.orderNoCopy'))
- })
- }
- // function validateTxHash(): boolean {
- // txHashError.value = ''
- // const s = txHashInput.value.trim()
- // if (!s) {
- // txHashError.value = t('deposit.hashRequired')
- // return false
- // }
- // return true
- // }
- onBeforeUnmount(() => {
- resetPendingWalletUi()
- })
- function isWalletConnectCancelError(e: unknown): boolean {
- if (e instanceof WalletFlowCancelledError) {
- return true
- }
- const msg = e instanceof Error ? e.message : typeof e === 'string' ? e : ''
- const normalized = msg.toLowerCase()
- const code = e && typeof e === 'object' && 'code' in e ? Number((e as { code?: unknown }).code) : NaN
- return (
- code === 4001 ||
- normalized.includes('connection request reset') ||
- normalized.includes('user rejected') ||
- normalized.includes('user denied') ||
- normalized.includes('user closed') ||
- normalized.includes('user cancelled') ||
- normalized.includes('user canceled') ||
- normalized.includes('rejected the request') ||
- normalized.includes('rejected transaction') ||
- normalized.includes('modal closed') ||
- normalized.includes('proposal expired') ||
- normalized.includes('action_rejected')
- )
- }
- // async function onSubmitHash() {
- // const o = currentOrder.value
- // if (!o?.orderNo) {
- // return
- // }
- // if (Number(o.status) !== 0) {
- // toast.error(t('deposit.statusNotSubmittable'))
- // return
- // }
- // if (!validateTxHash()) {
- // return
- // }
- // try {
- // await store.submitTxHash(o.orderNo, txHashInput.value)
- // toast.success(t('deposit.hashSubmitted'))
- // } catch {
- // toast.error(store.error ?? t('deposit.hashFailed'))
- // }
- // }
- async function onWalletPay() {
- const o = currentOrder.value
- const net = selectedNetwork.value
- if (!o?.orderNo || !net || Number(o.status) !== 0) {
- return
- }
- const seq = ++walletAttemptSeq.value
- try {
- const hash = await connectAndPay({ network: net, order: o })
- if (seq !== walletAttemptSeq.value || currentOrder.value?.orderNo !== o.orderNo) {
- return
- }
- txHashInput.value = hash
- await store.submitTxHash(o.orderNo, hash)
- toast.success(t('deposit.broadcastSuccess'))
- } catch (e) {
- if (isWalletConnectCancelError(e)) {
- resetWalletBusy()
- return
- }
- const msg = e instanceof Error ? e.message : t('deposit.walletPayFallback')
- toast.error(msg)
- }
- }
- async function onTronWalletPay() {
- const o = currentOrder.value
- const net = selectedNetwork.value
- if (!o?.orderNo || !net || Number(o.status) !== 0) {
- return
- }
- const seq = ++walletAttemptSeq.value
- try {
- const txid = await connectAndPayTron({ network: net, order: o })
- if (seq !== walletAttemptSeq.value || currentOrder.value?.orderNo !== o.orderNo) {
- return
- }
- txHashInput.value = txid
- await store.submitTxHash(o.orderNo, txid)
- toast.success(t('deposit.tronBroadcastSuccess'))
- } catch (e) {
- if (isWalletConnectCancelError(e)) {
- resetTronWalletBusy()
- return
- }
- const msg = e instanceof Error ? e.message : t('deposit.tronWalletPayFallback')
- toast.error(msg)
- }
- }
- function resetPendingWalletUi() {
- walletAttemptSeq.value += 1
- resetWalletBusy()
- resetTronWalletBusy()
- }
- function onNewDeposit() {
- resetPendingWalletUi()
- store.clearCurrentOrder()
- amount.value = ''
- amountError.value = ''
- txHashInput.value = ''
- txHashError.value = ''
- depositModeLocked.value = false
- depositMode.value = 'wallet'
- }
- function statusLabel(status: number) {
- return rechargeStatusTranslate(t, status)
- }
- </script>
- <template>
- <main class="page-main">
- <div class="page-container content">
- <div class="page-header">
- <RouterLink to="/assets" class="back-link">{{ t('deposit.backWithArrow') }}</RouterLink>
- <div class="title-row">
- <h1 class="page-title">{{ t('deposit.title') }}</h1>
- <RouterLink to="/assets/deposit/history" class="hist-link">{{ t('deposit.histLink') }}</RouterLink>
- </div>
- </div>
- <div v-if="loadingParents" class="loading-state">
- <div class="spinner" />
- </div>
- <div v-else-if="error && !parentCoins.length" class="error-state dark-card">
- <p class="error-msg">{{ error }}</p>
- <button class="retry-btn" type="button" @click="store.loadParentCoins()">{{ t('deposit.retryBtn') }}</button>
- </div>
- <template v-else-if="parentCoins.length">
- <!-- 已创建订单:展示付款与回写 hash -->
- <template v-if="currentOrder">
- <div class="toolbar-row">
- <button type="button" class="link-btn" @click="onNewDeposit">{{ t('deposit.newDeposit') }}</button>
- </div>
- <div class="form-card dark-card order-detail-card">
- <h2 class="section-title order-title">{{ t('deposit.orderInfo') }}</h2>
- <div class="order-meta">
- <div class="meta-row">
- <span class="meta-k">{{ t('deposit.orderNo') }}</span>
- <span class="meta-v">{{ currentOrder.orderNo }}</span>
- <button type="button" class="mini-copy" @click="copyOrderNo">{{ t('deposit.copyAddress') }}</button>
- </div>
- <div class="meta-row">
- <span class="meta-k">{{ t('deposit.status') }}</span>
- <span class="meta-v status" :class="'st-' + currentOrder.status">{{ statusLabel(Number(currentOrder.status)) }}</span>
- </div>
- <div class="meta-row">
- <span class="meta-k">{{ t('deposit.coinLabel') }}</span>
- <span class="meta-v">{{ currentOrder.coinName }}</span>
- </div>
- <div class="meta-row">
- <span class="meta-k">{{ t('deposit.networkLabel') }}</span>
- <span class="meta-v">{{ currentOrder.networkName }}</span>
- </div>
- <div class="meta-row">
- <span class="meta-k">{{ t('deposit.amount') }}</span>
- <span class="meta-v amt">{{ currentOrder.amount }}</span>
- </div>
- </div>
- <div class="pay-destination">
- <h3 class="sub-section-title">{{ t('deposit.payAddress') }}</h3>
- <p class="addr-hint">{{ t('deposit.payAddressHint') }}</p>
- <div class="addr-row">
- <p class="addr-text">{{ currentOrder.rechargeAddress || '-' }}</p>
- <button type="button" class="copy-btn" @click="copyAddress">{{ t('deposit.copyAddress') }}</button>
- </div>
- <div v-if="depositMode === 'manual' && rechargeQrUrl" class="qr-block">
- <h3 class="sub-section-title qr-block-title">{{ t('deposit.qrTitle') }}</h3>
- <p class="addr-hint">{{ t('deposit.qrHint') }}</p>
- <div class="qr-frame">
- <img class="qr-img" :src="rechargeQrUrl" :alt="t('deposit.qrAlt')" width="200" height="200">
- </div>
- </div>
- <div v-if="depositMode === 'manual' && selectedNetwork?.contractAddress" class="contract-box">
- <span class="meta-k">{{ t('deposit.contractAddress') }}</span>
- <p class="mono small">{{ selectedNetwork.contractAddress }}</p>
- </div>
- <template v-if="depositMode === 'wallet' && Number(currentOrder.status) === 0">
- <p class="wc-hint" v-if="canUseWalletConnect && wcConfigured">
- {{ t('deposit.wcHintEvmConfigured') }}
- </p>
- <p class="wc-hint muted" v-if="canUseWalletConnect && !wcConfigured">
- {{ t('deposit.wcHintEvmUnconfigured') }}
- </p>
- <p class="wc-hint" v-if="isTronLikeNetwork && canUseTronWalletConnect && wcTronConfigured">
- {{ t('deposit.wcHintTronConfigured') }}
- </p>
- <p class="wc-hint muted" v-if="isTronLikeNetwork && canUseTronWalletConnect && !wcTronConfigured">
- {{ t('deposit.wcHintTronUnconfigured') }}
- </p>
- <p class="wc-hint muted" v-if="isTronLikeNetwork && !canUseTronWalletConnect">
- {{ t('deposit.wcHintTronManualOnly') }}
- </p>
- <button
- v-if="canUseWalletConnect && wcConfigured"
- type="button"
- class="wc-btn"
- :disabled="anyWcBusy || hashSubmitting"
- @click="onWalletPay"
- >
- {{ wcBusy ? t('deposit.walletPayingEvm') : t('deposit.walletPayEvm') }}
- </button>
- <button
- v-if="isTronLikeNetwork && canUseTronWalletConnect && wcTronConfigured"
- type="button"
- class="wc-btn"
- :disabled="anyWcBusy || hashSubmitting"
- @click="onTronWalletPay"
- >
- {{ wcTronBusy ? t('deposit.walletPayingTron') : t('deposit.walletPayTron') }}
- </button>
- </template>
- </div>
- <!-- <div v-if="Number(currentOrder.status) === 0" class="hash-block">
- <p class="form-label hash-label">{{ t('deposit.orSubmitHash') }}</p>
- <input
- v-model="txHashInput"
- type="text"
- class="text-input"
- :placeholder="t('deposit.hashPlaceholder')"
- autocomplete="off"
- >
- <p v-if="txHashError" class="field-err">{{ txHashError }}</p>
- <button
- type="button"
- class="primary-btn"
- :disabled="hashSubmitting || anyWcBusy"
- @click="onSubmitHash"
- >
- {{ hashSubmitting ? t('deposit.submittingHash') : t('deposit.submitHash') }}
- </button>
- </div>
- <p v-else-if="currentOrder.txHash" class="hash-done">
- {{ t('deposit.submittedHash') }}<span class="mono">{{ currentOrder.txHash }}</span>
- </p> -->
- </div>
- </template>
- <!-- 未创建订单:选币种与网络、填金额 -->
- <template v-else>
- <div class="two-col">
- <div class="form-column">
- <div class="mode-tabs">
- <button
- type="button"
- class="mode-tab"
- :class="{ active: depositMode === 'wallet' }"
- @click="depositMode = 'wallet'"
- >
- {{ t('deposit.walletDepositTab') }}
- </button>
- <button
- type="button"
- class="mode-tab"
- :class="{ active: depositMode === 'manual' }"
- @click="depositMode = 'manual'"
- >
- {{ t('deposit.manualDepositTab') }}
- </button>
- </div>
- <div class="form-card dark-card">
- <div v-if="parentCoins.length > 1" class="form-group">
- <p class="form-label">{{ t('deposit.coin') }}</p>
- <div class="network-tabs">
- <button
- v-for="(p, i) in parentCoins"
- :key="p.name"
- type="button"
- class="net-tab"
- :class="{ active: selectedParentIndex === i }"
- @click="onSelectParent(i)"
- >
- {{ p.nameCn || p.unit || p.name }}
- </button>
- </div>
- </div>
- <div class="form-group">
- <p class="form-label">{{ t('deposit.protocol') }}</p>
- <div v-if="childrenLoading" class="sub-loading">
- <div class="spinner sm" />
- </div>
- <div v-else-if="!networkOptions.length" class="empty-net">{{ t('deposit.noNetwork') }}</div>
- <div v-else class="network-tabs">
- <button
- v-for="net in networkOptions"
- :key="net.id"
- type="button"
- class="net-tab"
- :class="{ active: selectedNetworkId === net.id }"
- @click="onSelectNetwork(net.id)"
- >
- {{ net.name }}{{ net.protocol ? ` · ${net.protocol}` : '' }}
- </button>
- </div>
- </div>
- <div v-if="selectedNetworkId" class="form-group">
- <p class="form-label">{{ t('deposit.subCoin') }}</p>
- <div v-if="subCoinsLoading" class="sub-loading">
- <div class="spinner sm" />
- </div>
- <div v-else-if="!subCoinOptions.length" class="empty-net">{{ t('deposit.noSubCoin') }}</div>
- <div v-else class="network-tabs">
- <button
- v-for="(opt, i) in subCoinOptions"
- :key="opt.coinName + '-' + opt.networkConfigId"
- type="button"
- class="net-tab"
- :class="{ active: selectedSubCoinIndex === i }"
- @click="onSelectSubCoin(i)"
- >
- {{ subCoinTabLabel(opt) }}
- </button>
- </div>
- </div>
- <div v-if="selectedNetwork?.contractAddress" class="net-detail">
- <div class="info-row">
- <span class="info-label">{{ t('deposit.contract') }}</span>
- <span class="info-val mono break">{{ selectedNetwork.contractAddress }}</span>
- </div>
- </div>
- <div class="form-group">
- <p class="form-label">{{ t('deposit.depositAmount') }}</p>
- <input
- v-model="amount"
- type="text"
- inputmode="decimal"
- class="text-input"
- :placeholder="t('deposit.amountPlaceholder')"
- autocomplete="off"
- >
- <p v-if="amountError" class="field-err">{{ amountError }}</p>
- </div>
- <button
- type="button"
- class="primary-btn full"
- :disabled="orderSubmitting || !selectedNetwork"
- @click="onCreateOrder"
- >
- {{ orderSubmitting ? t('deposit.generating') : t('deposit.generateOrder') }}
- </button>
- </div>
- </div>
- <div class="address-card dark-card muted-card">
- <h2 class="addr-title">{{ t('deposit.intro') }}</h2>
- <p class="hint-p">{{ t('deposit.introText') }}</p>
- <p v-if="selectedParent" class="hint-p">
- {{ t('deposit.currentPrefix') }}<strong>{{ selectedParent.nameCn || selectedParent.name }}</strong>
- <template v-if="selectedNetwork"> - {{ selectedNetwork.networkName }}{{ selectedConfig ? ` / ${selectedConfig.coinName}` : '' }}</template>
- </p>
- </div>
- </div>
- </template>
- </template>
- </div>
- </main>
- </template>
- <style scoped>
- .content { padding-top: var(--space-8); padding-bottom: var(--space-16); max-width: 780px; }
- .page-header { margin-bottom: var(--space-5); }
- .back-link { font-size: var(--text-small); color: var(--color-text-secondary); display: inline-block; margin-bottom: var(--space-3); text-decoration: none; }
- .back-link:hover { color: var(--color-primary); }
- .title-row { display: flex; align-items: center; justify-content: space-between; }
- .page-title { font-size: var(--text-h1); font-weight: var(--fw-bold); color: var(--color-text-primary); line-height: 1.15; }
- .hist-link { font-size: var(--text-small); color: var(--color-primary); text-decoration: none; }
- .loading-state { display: flex; justify-content: center; padding: var(--space-16); }
- .spinner { width: 32px; height: 32px; border-radius: 50%; border: 2px solid var(--color-border); border-top-color: var(--color-primary); animation: spin .8s linear infinite; }
- .spinner.sm { width: 22px; height: 22px; border-width: 2px; }
- @keyframes spin { to { transform: rotate(360deg); } }
- .sub-loading { display: flex; padding: var(--space-4); }
- .error-state { padding: var(--space-8); text-align: center; }
- .error-msg { color: var(--color-text-secondary); margin-bottom: var(--space-4); }
- .retry-btn { background: transparent; border: 1px solid var(--color-primary); border-radius: var(--radius-md); color: var(--color-primary); padding: var(--space-2) var(--space-5); cursor: pointer; }
- .toolbar-row { margin-bottom: var(--space-3); }
- .link-btn { background: none; border: none; color: var(--color-primary); font-size: var(--text-small); cursor: pointer; padding: 0; }
- .link-btn:hover { opacity: .85; }
- .two-col { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-3); align-items: start; }
- .form-column { min-width: 0; }
- .form-card, .address-card { background: #151515; padding: var(--space-5); border-color: rgba(255,255,255,.06); }
- .address-card { min-height: 330px; }
- .muted-card { opacity: .96; }
- .mode-tabs { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-2); margin-bottom: var(--space-4); }
- .mode-tab {
- height: 40px;
- border: 1px solid transparent;
- border-radius: var(--radius-md);
- background: #101010;
- color: var(--color-text-primary);
- font-size: var(--text-small);
- font-weight: var(--fw-semibold);
- cursor: pointer;
- transition: all var(--transition);
- }
- .mode-tab.active { background: var(--color-primary); color: #0a0a0a; box-shadow: 0 6px 18px rgba(240,185,11,.18); }
- .section-title { font-size: var(--text-body); font-weight: var(--fw-semibold); color: var(--color-text-primary); margin-bottom: var(--space-5); }
- .order-detail-card { max-width: 720px; margin: 0 auto; padding: var(--space-6); }
- .order-title { text-align: center; margin-bottom: var(--space-6); }
- .form-group { margin-bottom: var(--space-5); }
- .form-label { font-size: var(--text-small); color: var(--color-text-secondary); margin-bottom: var(--space-2); display: block; }
- .network-tabs { display: flex; gap: var(--space-2); flex-wrap: wrap; }
- .net-tab {
- padding: 7px var(--space-4);
- border: 1px solid var(--color-border);
- border-radius: var(--radius-md);
- background: transparent;
- color: var(--color-text-secondary);
- font-size: var(--text-caption);
- cursor: pointer;
- transition: all var(--transition);
- }
- .net-tab:hover { border-color: var(--color-primary); color: var(--color-primary); }
- .net-tab.active { border-color: rgba(240,185,11,.7); background: rgba(240,185,11,.1); color: var(--color-primary); }
- .empty-net { font-size: var(--text-small); color: var(--color-text-muted); padding: var(--space-3); }
- .net-detail { background: rgba(255,255,255,.03); border-radius: var(--radius-md); padding: var(--space-4); margin-bottom: var(--space-5); }
- .info-row { display: flex; justify-content: space-between; gap: var(--space-3); padding: var(--space-2) 0; align-items: flex-start; }
- .info-label { font-size: var(--text-small); color: var(--color-text-secondary); flex-shrink: 0; }
- .info-val { font-size: var(--text-small); color: var(--color-text-primary); font-weight: var(--fw-medium); text-align: right; }
- .text-input {
- width: 100%;
- box-sizing: border-box;
- padding: var(--space-3) var(--space-4);
- border: 1px solid transparent;
- border-radius: var(--radius-md);
- outline: none;
- background: #202020;
- color: var(--color-text-primary);
- font-size: var(--text-small);
- }
- .text-input:focus { border-color: rgba(240,185,11,.35); }
- .text-input::placeholder { color: var(--color-text-muted); }
- .field-err { font-size: 12px; color: var(--color-down); margin-top: var(--space-2); }
- .primary-btn {
- margin-top: var(--space-3);
- min-width: 150px;
- padding: 13px var(--space-6);
- border: none;
- border-radius: var(--radius-md);
- background: var(--color-primary);
- color: #0a0a0a;
- font-weight: var(--fw-semibold);
- cursor: pointer;
- }
- .primary-btn:disabled { opacity: .5; cursor: not-allowed; }
- .primary-btn.full { width: 100%; margin-top: var(--space-5); }
- .addr-title { font-size: var(--text-body); font-weight: var(--fw-semibold); color: var(--color-text-primary); margin-bottom: var(--space-4); }
- .addr-hint { font-size: var(--text-caption); color: var(--color-text-muted); line-height: 1.6; margin-bottom: var(--space-4); }
- .addr-row { display: flex; align-items: flex-start; gap: var(--space-3); }
- .addr-text { flex: 1; font-size: var(--text-caption); color: var(--color-text-secondary); font-family: var(--font-family-num); word-break: break-all; line-height: 1.5; }
- .copy-btn { white-space: nowrap; background: none; border: none; color: var(--color-text-secondary); font-size: var(--text-caption); cursor: pointer; flex-shrink: 0; }
- .copy-btn:hover { color: var(--color-primary); }
- .qr-block { margin-top: var(--space-5); }
- .qr-block-title { margin-bottom: var(--space-2); }
- .qr-frame { display: inline-flex; padding: var(--space-3); background: #fff; border-radius: var(--radius-md); }
- .qr-img { display: block; width: 152px; height: 152px; }
- .order-meta { display: flex; flex-direction: column; gap: var(--space-3); }
- .meta-row { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; font-size: var(--text-small); }
- .meta-k { color: var(--color-text-secondary); min-width: 64px; }
- .meta-v { color: var(--color-text-primary); font-weight: var(--fw-medium); }
- .meta-v.amt { font-family: var(--font-family-num); }
- .meta-v.status.st-0, .meta-v.status.st-1 { color: var(--color-primary); }
- .meta-v.status.st-2 { color: var(--color-up); }
- .meta-v.status.st-3, .meta-v.status.st-4 { color: var(--color-text-muted); }
- .mini-copy { font-size: 12px; padding: 2px 8px; border: none; border-radius: var(--radius-sm); background: rgba(255,255,255,.04); color: var(--color-text-secondary); cursor: pointer; }
- .mini-copy:hover { color: var(--color-primary); }
- .pay-destination { margin-top: var(--space-6); padding-top: var(--space-5); border-top: 1px solid var(--color-border); }
- .hash-block { margin-top: var(--space-5); }
- .sub-section-title { font-size: var(--text-small); font-weight: var(--fw-semibold); color: var(--color-text-primary); margin-bottom: var(--space-2); }
- .wc-hint { font-size: var(--text-caption); color: var(--color-text-secondary); line-height: 1.6; margin: var(--space-4) 0 var(--space-3); }
- .wc-hint.muted { color: var(--color-text-muted); }
- .wc-btn {
- width: 100%;
- margin-bottom: var(--space-5);
- padding: var(--space-3) var(--space-6);
- border: 1px solid rgba(240,185,11,.5);
- border-radius: var(--radius-md);
- background: rgba(240,185,11,.14);
- color: var(--color-primary);
- font-weight: var(--fw-semibold);
- cursor: pointer;
- }
- .wc-btn:disabled { opacity: .5; cursor: not-allowed; }
- .hash-label { margin-top: var(--space-4); }
- .hash-done { margin-top: var(--space-5); font-size: var(--text-small); color: var(--color-text-secondary); word-break: break-all; }
- .contract-box { margin-top: var(--space-5); padding: var(--space-4); background: rgba(255,255,255,.04); border-radius: var(--radius-md); }
- .mono { font-family: var(--font-family-num); }
- .mono.small { font-size: 12px; line-height: 1.5; word-break: break-all; }
- .break { word-break: break-all; }
- .hint-p { font-size: var(--text-small); color: var(--color-text-secondary); line-height: 1.8; margin-bottom: var(--space-4); }
- .hint-p strong { color: var(--color-text-primary); font-weight: var(--fw-medium); }
- @media (max-width: 768px) {
- .content { max-width: 100%; }
- .two-col { grid-template-columns: 1fr; }
- .address-card { min-height: 0; }
- .order-detail-card { padding: var(--space-5); }
- }
- </style>
|