feat 搭建架构骨架

This commit is contained in:
Ironsp
2026-09-24 09:09:47 +08:00
parent dd9f6b77a4
commit 7a2efa3fea
180 changed files with 18967 additions and 9 deletions

View File

@@ -0,0 +1,114 @@
<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>