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

@@ -1,5 +1,7 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { ref, watch } from 'vue'
import { ElTable, ElTableColumn } from 'element-plus'
import type { TableInstance } from 'element-plus'
import type { Bill } from '@/types/business/finance'
import { formatMoney } from '@/utils/format'
@@ -16,223 +18,158 @@ const emit = defineEmits<{
'update:selection': [ids: string[]]
}>()
const selection = ref<Set<string>>(new Set(props.selectedIds))
const tableRef = ref<TableInstance>()
function onSelectionChange(rows: Bill[]): void {
emit('update:selection', rows.map((row) => row.id))
}
// 外部清空选中(如重置查询)时同步清空 el-table 选择态
watch(
() => props.selectedIds,
(ids) => {
selection.value = new Set(ids)
if (ids.length === 0 && tableRef.value) tableRef.value.clearSelection()
},
)
const allSelected = computed(
() => props.bills.length > 0 && props.bills.every((bill) => selection.value.has(bill.id)),
)
const someSelected = computed(
() => props.bills.some((bill) => selection.value.has(bill.id)) && !allSelected.value,
)
function toggle(id: string): void {
if (selection.value.has(id)) selection.value.delete(id)
else selection.value.add(id)
emit('update:selection', [...selection.value])
}
function toggleAll(): void {
if (allSelected.value) {
for (const bill of props.bills) selection.value.delete(bill.id)
}
else {
for (const bill of props.bills) selection.value.add(bill.id)
}
emit('update:selection', [...selection.value])
}
const COLUMNS = [
{ key: 'index', label: '序号', className: 'bill-table__col--index' },
{ key: 'planName', label: '账单名称', className: 'bill-table__col--name' },
{ key: 'billNo', label: '账单号', className: 'bill-table__col--no' },
{ key: 'tradeNo', label: '交易流水号', className: 'bill-table__col--trade' },
{ key: 'projectName', label: '项目名称', className: 'bill-table__col--project' },
{ key: 'amount', label: '订单金额', className: 'bill-table__col--amount' },
{ key: 'status', label: '状态', className: 'bill-table__col--status' },
{ key: 'ops', label: '操作', className: 'bill-table__col--ops' },
] as const
</script>
<template>
<table class="bill-table">
<colgroup>
<col class="bill-table__col--check">
<col
v-for="column in COLUMNS"
:key="column.key"
:class="column.className"
>
</colgroup>
<thead>
<tr>
<th class="bill-table__cell bill-table__cell--check">
<input
type="checkbox"
:checked="allSelected"
:indeterminate="someSelected"
@change="toggleAll"
<el-table
ref="tableRef"
class="bill-table"
:data="bills"
row-key="id"
@selection-change="onSelectionChange"
>
<el-table-column
type="selection"
width="51"
/>
<el-table-column
type="index"
label="序号"
width="235"
/>
<el-table-column
prop="planName"
label="账单名称"
width="310"
show-overflow-tooltip
/>
<el-table-column
prop="billNo"
label="账单号"
width="148"
show-overflow-tooltip
/>
<el-table-column
prop="tradeNo"
label="交易流水号"
width="394"
show-overflow-tooltip
/>
<el-table-column
prop="projectName"
label="项目名称"
width="339"
show-overflow-tooltip
/>
<el-table-column
label="订单金额"
width="97"
>
<template #default="{ row }">
{{ formatMoney((row as Bill).amount, 0) }}
</template>
</el-table-column>
<el-table-column
label="状态"
width="95"
>
<template #default="{ row }">
{{ (row as Bill).status }}
</template>
</el-table-column>
<el-table-column
label="操作"
width="96"
fixed="right"
>
<template #default="{ row }">
<span class="bill-table__ops">
<el-button
v-if="(row as Bill).status === '待支付'"
link
type="primary"
class="bill-table__link"
@click="emit('pay', (row as Bill).id)"
>
</th>
<th
v-for="column in COLUMNS"
:key="column.key"
class="bill-table__cell"
>
{{ column.label }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="(bill, index) in bills" :key="bill.id">
<td class="bill-table__cell bill-table__cell--check">
<input
type="checkbox"
:checked="selection.has(bill.id)"
@change="toggle(bill.id)"
去支付
</el-button>
<el-button
link
type="primary"
class="bill-table__link"
@click="emit('view', (row as Bill).id)"
>
</td>
<td class="bill-table__cell">
{{ index + 1 }}
</td>
<td class="bill-table__cell">
{{ bill.planName }}
</td>
<td class="bill-table__cell">
{{ bill.billNo }}
</td>
<td class="bill-table__cell">
{{ bill.tradeNo ?? '—' }}
</td>
<td class="bill-table__cell">
{{ bill.projectName }}
</td>
<td class="bill-table__cell">
{{ formatMoney(bill.amount, 0) }}
</td>
<td class="bill-table__cell">
{{ bill.status }}
</td>
<td class="bill-table__cell bill-table__cell--ops">
<span class="bill-table__ops">
<button
v-if="bill.status === '待支付'"
class="bill-table__link"
@click="emit('pay', bill.id)"
>
去支付
</button>
<button class="bill-table__link" @click="emit('view', bill.id)">
详情
</button>
</span>
</td>
</tr>
<tr v-if="bills.length === 0">
<td colspan="9" class="bill-table__empty">
暂无账单数据
</td>
</tr>
</tbody>
</table>
详情
</el-button>
</span>
</template>
</el-table-column>
<template #empty>
<span class="bill-table__empty">暂无账单数据</span>
</template>
</el-table>
</template>
<style lang="less" scoped>
.bill-table {
width: 1765px;
margin: 6.5px 0 0 14.5px;
border-collapse: collapse;
table-layout: fixed;
font-size: 12px;
color: @finance-entry-text;
&__col--check { width: 51px; }
&__col--index { width: 235.4px; }
&__col--name { width: 310.2px; }
&__col--no { width: 148px; }
&__col--trade { width: 394.1px; }
&__col--project { width: 339.4px; }
&__col--amount { width: 97px; }
&__col--status { width: 95px; }
&__col--ops { width: 95.5px; }
&__cell {
height: 43.77px;
padding: 0;
border-bottom: 1px solid @project-divider;
text-align: left;
vertical-align: middle;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
box-sizing: border-box;
&:not(&--check):not(&--ops) {
padding-left: 10px;
}
&--check {
padding-left: 43.5px;
}
&--ops {
padding-right: 22px;
text-align: right;
}
}
@col-pad-1: 43.5px;
@col-pad-2: 149.9px;
@col-pad-3: 8px;
@col-pad-4: 17px;
@col-pad-5: 5px;
@col-pad-6: 14.6px;
@col-pad-7: 4.6px;
@col-pad-8: 6px;
thead &__cell,
tbody &__cell {
&:nth-child(1) { padding-left: @col-pad-1; }
&:nth-child(2) { padding-left: @col-pad-2; }
&:nth-child(3) { padding-left: @col-pad-3; }
&:nth-child(4) { padding-left: @col-pad-4; }
&:nth-child(5) { padding-left: @col-pad-5; }
&:nth-child(6) { padding-left: @col-pad-6; }
&:nth-child(7) { padding-left: @col-pad-7; }
&:nth-child(8) { padding-left: @col-pad-8; }
&:nth-child(9) { padding-left: 0; padding-right: 10px; }
}
thead &__cell {
:deep(.el-table__header-wrapper th.el-table__cell) {
height: 41.77px;
padding: 0;
background: @finance-table-head-bg;
border-right: 1px solid @project-divider;
border-bottom: 1px solid @project-divider;
color: @finance-entry-text;
font-size: 12px;
font-weight: 600;
&:first-child {
border-left: 1px solid @project-divider;
}
text-align: left;
}
tbody tr:hover {
:deep(.el-table__header-wrapper th.el-table__cell:first-child) {
border-left: 1px solid @project-divider;
}
:deep(.el-table__body-wrapper td.el-table__cell) {
height: 43.77px;
padding: 0;
border-bottom: 1px solid @project-divider;
box-sizing: border-box;
}
:deep(.el-table__body tr:hover > td.el-table__cell) {
background: @bg-color-light;
}
&__cell--check input {
width: 14px;
height: 14px;
margin: 0;
accent-color: @primary-strong;
vertical-align: middle;
:deep(.el-table .cell) {
padding-left: 10px;
padding-right: 10px;
line-height: normal;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
// 首列(多选)左缩进对齐设计稿
:deep(.el-table-column--selection .cell) {
padding-left: 43.5px;
}
&__ops {
@@ -240,31 +177,18 @@ const COLUMNS = [
flex-direction: row;
align-items: center;
justify-content: flex-end;
gap: 18px;
white-space: nowrap;
}
&__link {
padding: 0;
border: none;
background: transparent;
color: @primary-strong;
font-size: 12px;
font-weight: 400;
transition: color 0.2s;
+ .bill-table__link {
margin-left: 18.36px;
}
&:hover {
color: @primary-color;
}
padding: 0;
}
&__empty {
height: 43.77px;
color: @project-muted;
text-align: center;
}
}
</style>