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,226 @@
<script setup lang="ts">
import { computed, onMounted, watch } from 'vue'
import { storeToRefs } from 'pinia'
import { useRoute, useRouter } from 'vue-router'
import { AppButton, StatusTag } from '@/components'
// 页面容器:数据只经 store,不直接 import api
import { useFinanceStore } from '@/stores/modules/finance'
import type { BillStatus } from '@/types/business/finance'
import { formatMoney } from '@/utils/format'
const route = useRoute()
const router = useRouter()
const financeStore = useFinanceStore()
const { currentBill } = storeToRefs(financeStore)
const billId = computed(() => String(route.params.id))
const STATUS_VARIANTS: Record<BillStatus, 'warning' | 'success' | 'info'> = {
待支付: 'warning',
已支付: 'success',
未到期: 'info',
}
async function load(): Promise<void> {
await financeStore.fetchDetail(billId.value)
if (currentBill.value?.status === '已支付') {
await financeStore.fetchResult(billId.value)
}
}
function onPay(): void {
void router.push(`/finance/bills/${billId.value}/pay`)
}
watch(billId, () => {
void load()
})
onMounted(() => {
void load()
})
</script>
<template>
<div class="bill-detail">
<nav class="bill-detail__breadcrumb text-secondary">
<RouterLink to="/finance" class="link">
财务中心
</RouterLink>
<span> &gt; </span>
<RouterLink to="/finance/bills" class="link">
账单明细
</RouterLink>
<span> &gt; </span>
<span>账单详情</span>
</nav>
<template v-if="currentBill">
<section class="bill-detail__card card">
<div class="bill-detail__card-head">
<h3 class="bill-detail__card-title">
订单信息
</h3>
<StatusTag :label="currentBill.status" :variant="STATUS_VARIANTS[currentBill.status]" />
</div>
<div class="bill-detail__fields">
<div class="bill-detail__field">
<span class="bill-detail__label text-secondary">账单名称</span>
<span class="bill-detail__value">{{ currentBill.planName }}</span>
</div>
<div class="bill-detail__field">
<span class="bill-detail__label text-secondary">账单号</span>
<span class="bill-detail__value">{{ currentBill.billNo }}</span>
</div>
<div class="bill-detail__field">
<span class="bill-detail__label text-secondary">项目名称</span>
<span class="bill-detail__value">{{ currentBill.projectName }}</span>
</div>
<div class="bill-detail__field">
<span class="bill-detail__label text-secondary">订单金额</span>
<span class="bill-detail__value bill-detail__value--amount">¥{{ formatMoney(currentBill.amount) }}</span>
</div>
</div>
<!-- 待支付:立即支付 -->
<div v-if="currentBill.status === '待支付'" class="bill-detail__pay">
<AppButton type="primary" @click="onPay">
立即支付
</AppButton>
</div>
<!-- 已支付:支付信息 -->
<template v-else-if="currentBill.status === '已支付'">
<div class="bill-detail__pay-info">
<h4 class="bill-detail__sub-title">
支付信息
</h4>
<div class="bill-detail__fields">
<div class="bill-detail__field">
<span class="bill-detail__label text-secondary">付款金额</span>
<span class="bill-detail__value bill-detail__value--amount">¥{{ formatMoney(financeStore.result?.amount ?? currentBill.amount) }}</span>
</div>
<div class="bill-detail__field">
<span class="bill-detail__label text-secondary">支付方式</span>
<span class="bill-detail__value">微信支付</span>
</div>
<div class="bill-detail__field">
<span class="bill-detail__label text-secondary">支付时间</span>
<span class="bill-detail__value">{{ financeStore.result?.payTime ?? '—' }}</span>
</div>
<div class="bill-detail__field">
<span class="bill-detail__label text-secondary">交易流水号</span>
<span class="bill-detail__value">{{ financeStore.result?.tradeNo ?? currentBill.tradeNo ?? '—' }}</span>
</div>
</div>
</div>
</template>
<!-- 未到期:提示 -->
<div v-else class="bill-detail__tip text-secondary">
账单尚未到期,到期后可前往账单明细完成支付。
</div>
</section>
</template>
<p v-else class="bill-detail__empty text-secondary">
账单不存在或已下线
</p>
<div class="bill-detail__back">
<AppButton @click="router.push('/finance/bills')">
返回
</AppButton>
</div>
</div>
</template>
<style lang="less" scoped>
.bill-detail {
padding-top: @spacing-lg;
padding-bottom: @spacing-xxl;
&__breadcrumb {
margin-bottom: @spacing-base;
font-size: @font-size-base;
}
&__card {
padding: @spacing-lg;
border-radius: @radius-lg;
background: @white;
box-shadow: @shadow-base;
}
&__card-head {
.flex(row, space-between, center);
margin-bottom: @spacing-base;
}
&__card-title {
font-size: @font-size-lg;
font-weight: 600;
color: @heading-color;
}
&__fields {
.flex(column, flex-start);
gap: @spacing-sm;
}
&__field {
.flex(row, flex-start, center);
gap: @spacing-base;
font-size: @font-size-base;
}
&__label {
width: 80px;
flex-shrink: 0;
}
&__value {
color: @heading-color;
&--amount {
font-weight: 600;
color: @danger-color;
}
}
&__pay {
margin-top: @spacing-lg;
}
&__pay-info {
margin-top: @spacing-lg;
padding-top: @spacing-base;
border-top: 1px solid @border-color;
}
&__sub-title {
margin-bottom: @spacing-sm;
font-size: @font-size-base;
font-weight: 600;
color: @heading-color;
}
&__tip {
margin-top: @spacing-lg;
padding: @spacing-base;
border-radius: @radius-base;
background: @bg-color;
font-size: @font-size-base;
}
&__empty {
padding: @spacing-xl 0;
text-align: center;
font-size: @font-size-base;
}
&__back {
margin-top: @spacing-lg;
}
}
</style>