115 lines
2.5 KiB
Vue
115 lines
2.5 KiB
Vue
<script setup lang="ts">
|
||
import { computed, onMounted, ref } from 'vue'
|
||
import { useCountDown } from '@/composables/useCountDown'
|
||
|
||
interface Props {
|
||
/** 二维码有效期(秒),默认 4:32 */
|
||
expiresIn?: number
|
||
size?: number
|
||
}
|
||
|
||
const props = withDefaults(defineProps<Props>(), {
|
||
expiresIn: 272,
|
||
size: 200,
|
||
})
|
||
|
||
const emit = defineEmits<{ refresh: [] }>()
|
||
|
||
const { seconds, start } = useCountDown(props.expiresIn)
|
||
const started = ref(false)
|
||
|
||
const expired = computed(() => started.value && seconds.value <= 0)
|
||
|
||
const countdownText = computed(() => {
|
||
const m = Math.floor(seconds.value / 60)
|
||
const s = seconds.value % 60
|
||
return `${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`
|
||
})
|
||
|
||
function refresh(): void {
|
||
start(props.expiresIn)
|
||
emit('refresh')
|
||
}
|
||
|
||
onMounted(() => {
|
||
start(props.expiresIn)
|
||
started.value = true
|
||
})
|
||
</script>
|
||
|
||
<template>
|
||
<div class="qr-panel">
|
||
<div
|
||
class="qr-panel__box"
|
||
:class="{ 'is-expired': expired }"
|
||
:style="{ width: `${size}px`, height: `${size}px` }"
|
||
@click="refresh"
|
||
>
|
||
<!-- TODO(api): 二维码内容由支付/签署接口返回后渲染 -->
|
||
<template v-if="!expired">
|
||
<span class="qr-panel__icon">▦</span>
|
||
<span class="qr-panel__tip">扫码支付/签署</span>
|
||
</template>
|
||
<template v-else>
|
||
<span class="qr-panel__icon">⟳</span>
|
||
<span class="qr-panel__tip">二维码已过期<br>点击刷新</span>
|
||
</template>
|
||
</div>
|
||
<p class="qr-panel__status text-secondary">
|
||
<template v-if="!expired">
|
||
二维码将在 <strong class="qr-panel__count">{{ countdownText }}</strong> 后失效(过期可点击二维码刷新)
|
||
</template>
|
||
<template v-else>
|
||
二维码已过期,请点击刷新
|
||
</template>
|
||
</p>
|
||
</div>
|
||
</template>
|
||
|
||
<style lang="less" scoped>
|
||
.qr-panel {
|
||
.flex(column, center);
|
||
gap: @spacing-sm;
|
||
|
||
&__box {
|
||
.flex(column, center);
|
||
gap: @spacing-xs;
|
||
border: 1px solid @border-color;
|
||
border-radius: @radius-base;
|
||
background: @white;
|
||
cursor: pointer;
|
||
transition: border-color 0.2s;
|
||
|
||
&:hover {
|
||
border-color: @primary-color;
|
||
}
|
||
|
||
&.is-expired {
|
||
background: @bg-color;
|
||
color: @text-color-secondary;
|
||
}
|
||
}
|
||
|
||
&__icon {
|
||
font-size: 56px;
|
||
color: @heading-color;
|
||
line-height: 1;
|
||
}
|
||
|
||
&__tip {
|
||
font-size: @font-size-sm;
|
||
color: @text-color-secondary;
|
||
text-align: center;
|
||
}
|
||
|
||
&__status {
|
||
font-size: @font-size-sm;
|
||
}
|
||
|
||
&__count {
|
||
color: @danger-color;
|
||
font-weight: 600;
|
||
}
|
||
}
|
||
</style>
|