feat 合同中心

This commit is contained in:
Ironsp
2026-10-09 17:17:43 +08:00
parent fd1eca95a6
commit 123d259f92
18 changed files with 799 additions and 649 deletions

View File

@@ -2,7 +2,7 @@
import { computed, onMounted, ref } from 'vue'
import { storeToRefs } from 'pinia'
import { RouterLink } from 'vue-router'
import { ElMessage } from 'element-plus'
import { ElMessage, ElButton, ElRadioGroup, ElRadioButton, ElTable, ElTableColumn } from 'element-plus'
import { FilterPanel, PaginationBar, StatusTag } from '@/components'
import { useFinanceStore } from '@/stores/modules/finance'
import type { Invoice, InvoiceStatus } from '@/types/business/finance'
@@ -36,6 +36,10 @@ function onTab(value: InvoiceStatus | 'all'): void {
void financeStore.searchInvoices()
}
function onTabChange(val: string | number | boolean | undefined): void {
onTab(val as InvoiceStatus | 'all')
}
function onSearch(): void {
financeStore.setInvoiceQuery({ ...invoiceQuery.value, keyword: keyword.value })
void financeStore.searchInvoices()
@@ -69,13 +73,8 @@ 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]
}
function onSelectionChange(rows: Invoice[]): void {
selectedIds.value = rows.map((row) => row.id)
}
onMounted(() => {
@@ -113,17 +112,19 @@ onMounted(() => {
</RouterLink>
</section>
<div class="invoices__tabs">
<button
<el-radio-group
class="invoices__tabs"
:model-value="invoiceQuery.status"
@change="onTabChange"
>
<el-radio-button
v-for="tab in STATUS_TABS"
:key="tab.value"
class="invoices__tab"
:class="{ 'is-active': invoiceQuery.status === tab.value }"
@click="onTab(tab.value)"
:value="tab.value"
>
{{ tab.label }}
</button>
</div>
</el-radio-button>
</el-radio-group>
<FilterPanel
v-model:keyword="keyword"
@@ -133,67 +134,121 @@ onMounted(() => {
/>
<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">
<el-table
class="invoices__table-el"
:data="invoices"
row-key="id"
@selection-change="onSelectionChange"
>
<el-table-column
type="selection"
width="48"
/>
<el-table-column
type="index"
label="序号"
width="60"
/>
<el-table-column
prop="planName"
label="账单名称"
min-width="180"
show-overflow-tooltip
/>
<el-table-column
prop="billNo"
label="账单号"
width="150"
show-overflow-tooltip
/>
<el-table-column
prop="tradeNo"
label="交易流水号"
min-width="180"
show-overflow-tooltip
/>
<el-table-column
prop="projectName"
label="项目名称"
min-width="160"
show-overflow-tooltip
/>
<el-table-column
label="订单金额"
width="110"
>
<template #default="{ row }">
<span class="invoices__amount">¥{{ formatMoney((row as Invoice).amount) }}</span>
</template>
</el-table-column>
<el-table-column
label="状态"
width="90"
>
<template #default="{ row }">
<StatusTag
:label="(row as Invoice).status"
:variant="STATUS_VARIANTS[(row as Invoice).status]"
/>
</template>
</el-table-column>
<el-table-column
prop="applyTime"
label="申请时间"
width="140"
show-overflow-tooltip
/>
<el-table-column
label="操作"
width="180"
fixed="right"
>
<template #default="{ row }">
<span class="invoices__ops">
<template v-if="(row as Invoice).status === '已开票'">
<el-button
link
type="primary"
class="invoices__link"
@click="onPreview"
>
预览
</button>
<button class="invoices__link" @click="onDownload">
</el-button>
<el-button
link
type="primary"
class="invoices__link"
@click="onDownload"
>
下载
</button>
<button class="invoices__link" @click="onSendEmail">
</el-button>
<el-button
link
type="primary"
class="invoices__link"
@click="onSendEmail"
>
发送邮箱
</button>
</el-button>
</template>
<span v-else-if="invoice.status === '开票中'" class="invoices__dash">—</span>
<button v-else class="invoices__link" @click="onApply(invoice)">
<span v-else-if="(row as Invoice).status === '开票中'" class="invoices__dash">—</span>
<el-button
v-else
link
type="primary"
class="invoices__link"
@click="onApply(row as Invoice)"
>
申请开票
</button>
</td>
</tr>
<tr v-if="invoices.length === 0">
<td colspan="10" class="invoices__empty text-secondary">
暂无发票记录
</td>
</tr>
</tbody>
</table>
</el-button>
</span>
</template>
</el-table-column>
<template #empty>
<span class="invoices__empty text-secondary">暂无发票记录</span>
</template>
</el-table>
</div>
<PaginationBar
@@ -271,29 +326,26 @@ onMounted(() => {
}
&__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;
:deep(.el-radio-button__inner) {
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;
box-shadow: none;
}
&.is-active {
:deep(.el-radio-button + .el-radio-button) {
margin-left: @spacing-sm;
}
:deep(.el-radio-button.is-active .el-radio-button__inner) {
border-color: @primary-color;
background: @primary-color;
color: @white;
box-shadow: -1px 0 0 0 @primary-color;
}
}
@@ -307,32 +359,35 @@ onMounted(() => {
&__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;
:deep(th.el-table__cell) {
background: @bg-color-light;
border-bottom: 1px solid @border-color;
}
th {
background: @bg-color-light;
color: @text-color-secondary;
font-size: @font-size-base;
font-weight: 500;
text-align: left;
}
tbody tr:hover {
:deep(td.el-table__cell) {
padding: @spacing-sm 0;
border-bottom: 1px solid @border-color;
font-size: @font-size-base;
}
:deep(.el-table__body tr:hover > td.el-table__cell) {
background: @bg-color-light;
}
}
&__check {
width: 48px;
text-align: center;
:deep(.el-table .cell) {
padding-left: @spacing-sm;
padding-right: @spacing-sm;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
&__amount {
@@ -341,17 +396,23 @@ onMounted(() => {
}
&__ops {
.flex(row, flex-start);
display: inline-flex;
flex-direction: row;
align-items: center;
gap: @spacing-sm;
}
&__link {
padding: 0;
border: none;
background: transparent;
color: @primary-color;
font-size: @font-size-base;
transition: color 0.2s;
&:hover {
color: @primary-hover;
background: transparent;
}
}