Files
tms-erp-web/src/views/payment/invoice-application.vue
T
gxwebsoft 899a8c1c2b style(payment): 优化票据搜索条件交互和标签宽度统一
- 票据账册搜索改用 el-check-tag 实现快捷筛选,提升用户交互体验
- 查询按钮增加图标,重置按钮同样添加图标及文字说明
- 调整票据搜索表单标签宽度由160px统一为88px,优化界面一致性
- 全站多处搜索栏表单标签宽度由160px改为88px,涵盖多页面及组件
- 修复业务组件内el-autocomplete输入框宽度适配,避免placeholder被裁剪
- 业务页面货物名称表格列宽由150调整为240,改善显示效果
- 修正系统部门操作列宽度避免按钮竖排溢出问题
- 修改多个表单页和搜索板块样式,确保标签宽度和对齐一致
- 调整表单按钮等样式,提升整体页面美观性和操作友好度
2026-08-26 11:59:49 +08:00

479 lines
16 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<basic-container class="invoice-page">
<section class="invoice-page__search">
<el-form :model="query" label-position="right" label-width="88px" @submit.prevent>
<div class="invoice-page__search-grid">
<el-form-item label="单据号">
<el-input v-model="query.applicationNo" clearable placeholder="请输入" />
</el-form-item>
<el-form-item label="所属项目">
<el-input v-model="query.projectName" clearable placeholder="请输入" />
</el-form-item>
<el-form-item label="所属组织">
<el-input v-model="query.deptName" clearable placeholder="请输入" />
</el-form-item>
<el-form-item class="invoice-kingdee-item" label="金蝶单据状态">
<el-select v-model="query.kingdeeStatus" clearable placeholder="请选择">
<el-option
v-for="item in kingdeeStatusOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item v-if="searchExpanded" label="状态">
<el-select v-model="query.approvalStatus" clearable placeholder="请选择">
<el-option
v-for="item in approvalStatusOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</div>
<div class="invoice-page__search-actions">
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button @click="resetSearch">重置</el-button>
<el-tooltip :content="searchExpanded ? '折叠' : '展开'" placement="top">
<el-button text @click="searchExpanded = !searchExpanded">
<el-icon><component :is="searchExpanded ? ArrowUp : ArrowDown" /></el-icon>
</el-button>
</el-tooltip>
</div>
</el-form>
</section>
<section class="invoice-page__table-panel">
<div class="invoice-page__toolbar">
<div class="invoice-page__table-title">开票申请单列表</div>
<div class="invoice-page__toolbar-actions">
<el-button
v-if="hasPermission('invoice_application_sync')"
type="primary"
plain
@click="handleBatchSync"
>批量同步</el-button
>
<el-button
v-if="hasPermission('invoice_application_add')"
type="primary"
@click="openCreate"
>新增</el-button
>
<el-button
v-if="hasPermission('invoice_application_export')"
type="primary"
plain
@click="handleExport"
>导出</el-button
>
<el-tooltip content="刷新" placement="top">
<el-button text :icon="Refresh" @click="loadTable" />
</el-tooltip>
</div>
</div>
<el-table v-loading="loading" :data="rows" border @selection-change="selection = $event">
<el-table-column type="selection" width="52" fixed="left" align="center" />
<el-table-column type="index" label="序号" width="64" fixed="left" align="center" />
<el-table-column
v-for="column in columns"
:key="column.prop"
v-bind="column"
align="center"
show-overflow-tooltip
>
<template #default="{ row }">
<el-link v-if="column.link && row[column.prop]" type="primary" @click="openView(row)">
{{ row[column.prop] }}
</el-link>
<el-tag v-else-if="column.status" :type="statusType(row.approvalStatus)" class="status-text">
{{ displayValue(row[column.prop]) }}
</el-tag>
<span v-else-if="column.money">{{ formatMoney(row[column.prop]) }}</span>
<span v-else>{{ displayValue(row[column.prop]) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="320" fixed="right" align="center">
<template #default="{ row }">
<div class="invoice-page__links">
<el-link
v-if="hasPermission('invoice_application_view')"
type="primary"
@click="openView(row)"
>查看</el-link
>
<el-link
v-if="
hasPermission('invoice_application_edit') &&
['draft', 'returned'].includes(row.approvalStatus)
"
type="primary"
@click="openEdit(row)"
>编辑</el-link
>
<el-link
v-if="hasPermission('invoice_application_delete') && row.approvalStatus === 'draft'"
type="danger"
@click="handleDelete(row)"
>删除</el-link
>
<el-link
v-if="
hasPermission('invoice_application_submit') &&
['draft', 'returned'].includes(row.approvalStatus)
"
type="primary"
@click="handleSubmit(row)"
>提交</el-link
>
<el-link
v-if="hasPermission('invoice_application_view') && row.approvalStatus !== 'draft'"
type="primary"
@click="openFlow(row)"
>流程</el-link
>
<el-link
v-if="
hasPermission('invoice_application_approve') && row.approvalStatus === 'reviewing'
"
type="primary"
@click="handleApprove(row)"
>通过</el-link
>
<el-link
v-if="
hasPermission('invoice_application_approve') && row.approvalStatus === 'reviewing'
"
type="danger"
@click="handleReturn(row)"
>驳回</el-link
>
<el-link
v-if="
hasPermission('invoice_application_void') && row.approvalStatus === 'approved'
"
type="danger"
@click="handleVoid(row)"
>作废</el-link
>
</div>
</template>
</el-table-column>
</el-table>
<div class="invoice-page__pagination">
<el-pagination
v-model:current-page="page.current"
v-model:page-size="page.size"
:total="page.total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
@current-change="loadTable"
@size-change="handleSizeChange"
/>
</div>
</section>
<el-dialog v-model="flowDialog.visible" title="审批流程" width="900px" append-to-body>
<el-descriptions :column="2" border>
<el-descriptions-item label="开票申请单号">
{{ flowDialog.row.applicationNo || '-' }}
</el-descriptions-item>
<el-descriptions-item label="状态">
{{ flowDialog.row.approvalStatusName || '-' }}
</el-descriptions-item>
<el-descriptions-item label="当前节点">
{{ flowDialog.row.currentNode || '-' }}
</el-descriptions-item>
<el-descriptions-item label="当前处理人">
{{ flowDialog.row.currentProcessor || '-' }}
</el-descriptions-item>
</el-descriptions>
<el-table
v-loading="flowDialog.loading"
:data="flowDialog.records"
border
class="invoice-page__flow-table"
>
<el-table-column type="index" label="序号" width="64" align="center" />
<el-table-column prop="actionName" label="操作" min-width="110" align="center" />
<el-table-column label="原状态" min-width="100" align="center">
<template #default="{ row }">{{ statusName(row.fromStatus) }}</template>
</el-table-column>
<el-table-column label="新状态" min-width="100" align="center">
<template #default="{ row }">{{ statusName(row.toStatus) }}</template>
</el-table-column>
<el-table-column prop="operatorName" label="操作人" min-width="110" align="center" />
<el-table-column prop="createTime" label="操作时间" min-width="170" align="center" />
<el-table-column prop="reason" label="原因" min-width="160" show-overflow-tooltip />
<el-table-column prop="kingdeeBillNo" label="金蝶单据号" min-width="150" />
</el-table>
</el-dialog>
</basic-container>
</template>
<script>
import { ArrowDown, ArrowUp, Refresh } from '@element-plus/icons-vue';
import { mapGetters } from 'vuex';
import * as XLSX from 'xlsx';
import * as api from '@/api/payment/invoiceApplication';
import { invoiceApplicationTableColumns } from '@/option/payment/invoiceApplication';
const emptyQuery = () => ({
applicationNo: '',
projectName: '',
deptName: '',
kingdeeStatus: '',
approvalStatus: '',
});
export default {
name: 'InvoiceApplication',
data() {
return {
ArrowDown,
ArrowUp,
Refresh,
query: emptyQuery(),
searchExpanded: false,
approvalStatusOptions: api.approvalStatusOptions,
kingdeeStatusOptions: api.kingdeeStatusOptions,
columns: invoiceApplicationTableColumns,
rows: [],
selection: [],
loading: false,
page: { current: 1, size: 10, total: 0 },
flowDialog: { visible: false, loading: false, row: {}, records: [] },
};
},
computed: {
...mapGetters(['permission']),
},
mounted() {
this.loadTable();
},
methods: {
hasPermission(code) {
return this.permission?.[code] !== false;
},
unwrapData(response) {
const body = response?.data || response || {};
return body?.data || body;
},
async loadTable() {
this.loading = true;
try {
const data = this.unwrapData(
await api.getList(this.page.current, this.page.size, this.query)
);
this.rows = data.records || [];
this.page.total = Number(data.total || 0);
} finally {
this.loading = false;
}
},
handleSearch() {
this.page.current = 1;
this.loadTable();
},
resetSearch() {
this.query = emptyQuery();
this.handleSearch();
},
handleSizeChange() {
this.page.current = 1;
this.loadTable();
},
openCreate() {
this.$router.push({ path: '/payment/invoice-application/form', query: { mode: 'add' } });
},
openEdit(row) {
this.$router.push({
path: '/payment/invoice-application/form',
query: { mode: 'edit', id: row.id },
});
},
openView(row) {
this.$router.push({
path: '/payment/invoice-application/form',
query: { mode: 'view', id: row.id },
});
},
async openFlow(row) {
this.flowDialog.row = { ...row };
this.flowDialog.records = [];
this.flowDialog.visible = true;
this.flowDialog.loading = true;
try {
const detail = this.unwrapData(await api.getDetail(row.id));
this.flowDialog.row = { ...row, ...detail };
this.flowDialog.records = detail.records || [];
} finally {
this.flowDialog.loading = false;
}
},
async handleDelete(row) {
await this.$confirm('确认删除该开票申请?', '提示', { type: 'warning' });
await api.remove(row.id);
this.$message.success('删除成功');
this.loadTable();
},
async handleSubmit(row) {
await api.submit({ id: row.id });
this.$message.success('提交成功');
this.loadTable();
},
async handleApprove(row) {
await this.$confirm('确认审批通过该开票申请?', '提示', { type: 'warning' });
await api.approve({ id: row.id });
this.$message.success('审批通过');
this.loadTable();
},
async handleReturn(row) {
const { value } = await this.$prompt('请输入驳回原因', '审批驳回');
await api.returnBill({ id: row.id, reason: value });
this.$message.success('已驳回');
this.loadTable();
},
async handleVoid(row) {
const { value } = await this.$prompt('请输入作废原因', '作废开票申请');
await api.voidBill({ id: row.id, reason: value });
this.$message.success('作废成功');
this.loadTable();
},
async handleBatchSync() {
const rows = this.selection.filter(row => row.approvalStatus === 'approved');
if (!rows.length) {
this.$message.warning('请选择至少一条审批通过的开票申请');
return;
}
await Promise.all(rows.map(row => api.syncKingdee(row.id)));
this.$message.success(`已同步${rows.length}条开票申请`);
this.loadTable();
},
async handleExport() {
const data = this.unwrapData(await api.getList(1, 100000, this.query));
const exportRows = (data.records || []).map(item => ({
单据号: item.applicationNo,
结算单号: item.settlementNos,
所属项目: item.projectName,
所属组织: item.deptName,
开票方: item.issuerName,
受票方: item.receiverName,
开票金额: Number(item.invoiceAmount || 0).toFixed(2),
创建人: item.createUserName,
创建时间: item.createTime,
状态: item.approvalStatusName,
当前节点: item.currentNode,
当前处理人: item.currentProcessor,
金蝶单据号: item.kingdeeBillNo,
金蝶单据状态: item.kingdeeStatusName,
}));
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, XLSX.utils.json_to_sheet(exportRows), '开票申请');
XLSX.writeFile(workbook, `开票申请${this.$dayjs().format('YYYY-MM-DD HH-mm-ss')}.xlsx`);
},
statusType(status) {
return (
{ approved: 'success', reviewing: 'warning', returned: 'danger', voided: 'info' }[status] ||
''
);
},
statusName(status) {
return (
{
draft: '草稿',
reviewing: '审批中',
approved: '审批通过',
returned: '已驳回',
voided: '已作废',
}[status] ||
status ||
'-'
);
},
displayValue(value) {
return value === null || value === undefined || value === '' ? '-' : value;
},
formatMoney(value) {
return Number(value || 0).toFixed(2);
},
},
};
</script>
<style scoped lang="scss">
.invoice-page__search {
padding: 12px 12px 4px;
background: #fff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
}
.invoice-page__search-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 8px 24px;
}
.invoice-page__search :deep(.el-form-item) {
margin-bottom: 8px;
}
.invoice-page__search :deep(.el-input),
.invoice-page__search :deep(.el-select) {
width: 100%;
}
// 金蝶单据状态 label 6 个汉字去掉 EP 默认 line-height:32px避免与控件行高不对齐
.invoice-page__search .invoice-kingdee-item :deep(.el-form-item__label) {
line-height: normal;
}
.invoice-page__search-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
margin-bottom: 8px;
}
.invoice-page__table-panel {
margin-top: 8px;
}
.invoice-page__toolbar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 0;
}
.invoice-page__toolbar-actions,
.invoice-page__links {
display: flex;
align-items: center;
gap: 8px;
}
.invoice-page__table-title {
font-size: 16px;
font-weight: 600;
}
.invoice-page__links {
flex-wrap: wrap;
justify-content: center;
}
.invoice-page__pagination {
display: flex;
justify-content: flex-end;
padding: 16px 0;
}
.invoice-page :deep(.el-table) {
--el-table-border-color: #eff1f7;
}
.invoice-page :deep(.el-table__row:nth-child(even) > td.el-table__cell) {
background: #fafafa;
}
.invoice-page__flow-table {
margin-top: 16px;
}
@media (max-width: 1200px) {
.invoice-page__search-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
</style>