feat 搭建架构骨架
This commit is contained in:
364
src/views/finance/invoices.vue
Normal file
364
src/views/finance/invoices.vue
Normal file
@@ -0,0 +1,364 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { RouterLink } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { FilterPanel, PaginationBar, StatusTag } from '@/components'
|
||||
// 页面容器:数据只经 store,不直接 import api
|
||||
import { useFinanceStore } from '@/stores/modules/finance'
|
||||
import type { Invoice, InvoiceStatus } from '@/types/business/finance'
|
||||
import { formatMoney } from '@/utils/format'
|
||||
|
||||
const financeStore = useFinanceStore()
|
||||
const { titles, invoices, invoiceQuery, invoiceTotal } = storeToRefs(financeStore)
|
||||
|
||||
const keyword = ref('')
|
||||
const selectedIds = ref<string[]>([])
|
||||
|
||||
const defaultTitle = computed(() => titles.value.find((item) => item.isDefault) ?? titles.value[0] ?? null)
|
||||
|
||||
const STATUS_TABS: Array<{ label: string; value: InvoiceStatus | 'all' }> = [
|
||||
{ label: '全部', value: 'all' },
|
||||
{ label: '已开票', value: '已开票' },
|
||||
{ label: '开票中', value: '开票中' },
|
||||
{ label: '未开票', value: '未开票' },
|
||||
]
|
||||
|
||||
const STATUS_VARIANTS: Record<InvoiceStatus, 'success' | 'warning' | 'info'> = {
|
||||
已开票: 'success',
|
||||
开票中: 'warning',
|
||||
未开票: 'info',
|
||||
}
|
||||
|
||||
function onTab(value: InvoiceStatus | 'all'): void {
|
||||
if (invoiceQuery.value.status === value) return
|
||||
financeStore.setInvoiceQuery({ ...invoiceQuery.value, status: value, keyword: keyword.value })
|
||||
selectedIds.value = []
|
||||
void financeStore.searchInvoices()
|
||||
}
|
||||
|
||||
function onSearch(): void {
|
||||
financeStore.setInvoiceQuery({ ...invoiceQuery.value, keyword: keyword.value })
|
||||
void financeStore.searchInvoices()
|
||||
}
|
||||
|
||||
function onReset(): void {
|
||||
keyword.value = ''
|
||||
selectedIds.value = []
|
||||
financeStore.setInvoiceQuery({ keyword: '', status: 'all', page: 1, pageSize: invoiceQuery.value.pageSize })
|
||||
void financeStore.fetchInvoices()
|
||||
}
|
||||
|
||||
function onPageChange(page: number): void {
|
||||
void financeStore.changeInvoicePage(page)
|
||||
}
|
||||
|
||||
async function onApply(invoice: Invoice): Promise<void> {
|
||||
await financeStore.applyInvoice(invoice.id)
|
||||
ElMessage.success('开票申请已提交')
|
||||
}
|
||||
|
||||
function onPreview(): void {
|
||||
ElMessage.info('附件待接口接入')
|
||||
}
|
||||
|
||||
function onDownload(): void {
|
||||
ElMessage.info('附件待接口接入')
|
||||
}
|
||||
|
||||
function onSendEmail(): void {
|
||||
ElMessage.success('发票已发送至绑定邮箱(演示)')
|
||||
}
|
||||
|
||||
function onToggleSelect(id: string): void {
|
||||
if (selectedIds.value.includes(id)) {
|
||||
selectedIds.value = selectedIds.value.filter((item) => item !== id)
|
||||
}
|
||||
else {
|
||||
selectedIds.value = [...selectedIds.value, id]
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
keyword.value = invoiceQuery.value.keyword
|
||||
void Promise.all([financeStore.fetchTitles(), financeStore.fetchInvoices()])
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="invoices">
|
||||
<nav class="invoices__breadcrumb text-secondary">
|
||||
<RouterLink to="/finance" class="link">
|
||||
财务中心
|
||||
</RouterLink>
|
||||
<span> > </span>
|
||||
<span>发票申请</span>
|
||||
</nav>
|
||||
|
||||
<section v-if="defaultTitle" class="invoices__title-card card">
|
||||
<div class="invoices__title-main">
|
||||
<div class="invoices__title-head">
|
||||
<span class="invoices__title-label text-secondary">默认发票抬头</span>
|
||||
<StatusTag :label="defaultTitle.type" variant="primary" />
|
||||
<StatusTag label="默认" variant="success" />
|
||||
</div>
|
||||
<p class="invoices__title-name">
|
||||
{{ defaultTitle.title }}
|
||||
</p>
|
||||
<p class="invoices__title-tax text-secondary">
|
||||
税号:{{ defaultTitle.taxNo }}
|
||||
</p>
|
||||
</div>
|
||||
<RouterLink to="/finance/invoice-titles" class="invoices__title-manage">
|
||||
管理抬头 >
|
||||
</RouterLink>
|
||||
</section>
|
||||
|
||||
<div class="invoices__tabs">
|
||||
<button
|
||||
v-for="tab in STATUS_TABS"
|
||||
:key="tab.value"
|
||||
class="invoices__tab"
|
||||
:class="{ 'is-active': invoiceQuery.status === tab.value }"
|
||||
@click="onTab(tab.value)"
|
||||
>
|
||||
{{ tab.label }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<FilterPanel
|
||||
v-model:keyword="keyword"
|
||||
keyword-placeholder="搜索发票抬头 / 账单号 / 项目名称"
|
||||
@search="onSearch"
|
||||
@reset="onReset"
|
||||
/>
|
||||
|
||||
<div class="invoices__table card">
|
||||
<table class="invoices__table-el">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="invoices__check">
|
||||
多选
|
||||
</th>
|
||||
<th>序号</th>
|
||||
<th>账单名称</th>
|
||||
<th>账单号</th>
|
||||
<th>交易流水号</th>
|
||||
<th>项目名称</th>
|
||||
<th>订单金额</th>
|
||||
<th>状态</th>
|
||||
<th>申请时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(invoice, index) in invoices" :key="invoice.id">
|
||||
<td class="invoices__check">
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="selectedIds.includes(invoice.id)"
|
||||
@change="onToggleSelect(invoice.id)"
|
||||
>
|
||||
</td>
|
||||
<td>{{ index + 1 }}</td>
|
||||
<td>{{ invoice.planName }}</td>
|
||||
<td>{{ invoice.billNo }}</td>
|
||||
<td>{{ invoice.tradeNo ?? '—' }}</td>
|
||||
<td>{{ invoice.projectName }}</td>
|
||||
<td class="invoices__amount">¥{{ formatMoney(invoice.amount) }}</td>
|
||||
<td>
|
||||
<StatusTag :label="invoice.status" :variant="STATUS_VARIANTS[invoice.status]" />
|
||||
</td>
|
||||
<td>{{ invoice.applyTime }}</td>
|
||||
<td class="invoices__ops">
|
||||
<template v-if="invoice.status === '已开票'">
|
||||
<button class="invoices__link" @click="onPreview">
|
||||
预览
|
||||
</button>
|
||||
<button class="invoices__link" @click="onDownload">
|
||||
下载
|
||||
</button>
|
||||
<button class="invoices__link" @click="onSendEmail">
|
||||
发送邮箱
|
||||
</button>
|
||||
</template>
|
||||
<span v-else-if="invoice.status === '开票中'" class="invoices__dash">—</span>
|
||||
<button v-else class="invoices__link" @click="onApply(invoice)">
|
||||
申请开票
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="invoices.length === 0">
|
||||
<td colspan="10" class="invoices__empty text-secondary">
|
||||
暂无发票记录
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<PaginationBar
|
||||
:page="invoiceQuery.page"
|
||||
:page-size="invoiceQuery.pageSize"
|
||||
:total="invoiceTotal"
|
||||
@page-change="onPageChange"
|
||||
/>
|
||||
|
||||
<p class="invoices__tip text-secondary">
|
||||
开票将使用默认抬头,如需调整请前往 <RouterLink to="/finance/invoice-titles" class="link">管理抬头</RouterLink>。
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.invoices {
|
||||
.flex(column, flex-start);
|
||||
gap: @spacing-base;
|
||||
padding-top: @spacing-lg;
|
||||
padding-bottom: @spacing-xxl;
|
||||
|
||||
&__breadcrumb {
|
||||
font-size: @font-size-base;
|
||||
}
|
||||
|
||||
&__title-card {
|
||||
.flex(row, space-between, center);
|
||||
padding: @spacing-base @spacing-lg;
|
||||
border-radius: @radius-lg;
|
||||
background: @white;
|
||||
box-shadow: @shadow-base;
|
||||
}
|
||||
|
||||
&__title-main {
|
||||
.flex(column, flex-start);
|
||||
gap: @spacing-xs;
|
||||
}
|
||||
|
||||
&__title-head {
|
||||
.flex(row, flex-start, center);
|
||||
gap: @spacing-sm;
|
||||
}
|
||||
|
||||
&__title-label {
|
||||
font-size: @font-size-sm;
|
||||
}
|
||||
|
||||
&__title-name {
|
||||
font-size: @font-size-lg;
|
||||
font-weight: 600;
|
||||
color: @heading-color;
|
||||
}
|
||||
|
||||
&__title-tax {
|
||||
font-size: @font-size-base;
|
||||
}
|
||||
|
||||
&__title-manage {
|
||||
color: @primary-color;
|
||||
font-size: @font-size-base;
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover {
|
||||
color: @primary-hover;
|
||||
}
|
||||
}
|
||||
|
||||
&__tabs {
|
||||
.flex(row, flex-start);
|
||||
gap: @spacing-sm;
|
||||
}
|
||||
|
||||
&__tab {
|
||||
height: 34px;
|
||||
padding: 0 @spacing-base;
|
||||
border: 1px solid @border-color;
|
||||
border-radius: @radius-base;
|
||||
background: @white;
|
||||
color: @text-color;
|
||||
font-size: @font-size-base;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
border-color: @primary-color;
|
||||
color: @primary-color;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
border-color: @primary-color;
|
||||
background: @primary-color;
|
||||
color: @white;
|
||||
}
|
||||
}
|
||||
|
||||
&__table {
|
||||
padding: @spacing-base;
|
||||
border-radius: @radius-lg;
|
||||
background: @white;
|
||||
box-shadow: @shadow-base;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
&__table-el {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: @font-size-base;
|
||||
color: @text-color;
|
||||
|
||||
th,
|
||||
td {
|
||||
padding: @spacing-sm;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
border-bottom: 1px solid @border-color;
|
||||
}
|
||||
|
||||
th {
|
||||
background: @bg-color-light;
|
||||
color: @text-color-secondary;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
tbody tr:hover {
|
||||
background: @bg-color-light;
|
||||
}
|
||||
}
|
||||
|
||||
&__check {
|
||||
width: 48px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__amount {
|
||||
color: @heading-color;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
&__ops {
|
||||
.flex(row, flex-start);
|
||||
gap: @spacing-sm;
|
||||
}
|
||||
|
||||
&__link {
|
||||
color: @primary-color;
|
||||
font-size: @font-size-base;
|
||||
transition: color 0.2s;
|
||||
|
||||
&:hover {
|
||||
color: @primary-hover;
|
||||
}
|
||||
}
|
||||
|
||||
&__dash {
|
||||
color: @text-color-secondary;
|
||||
}
|
||||
|
||||
&__empty {
|
||||
text-align: center;
|
||||
padding: @spacing-xl 0;
|
||||
}
|
||||
|
||||
&__tip {
|
||||
font-size: @font-size-sm;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user