refactor: 大规模代码整理与功能优化

1. 重构后端API路由、CRUD与模块结构,整合日志管理,移除废弃demo代码
2. 优化前端组件类型定义、样式与路由配置,修复权限判断逻辑
3. 调整默认排序规则、滚动条样式与工具类函数,更新依赖与配置文件
4. 修复多处类型不匹配与默认值问题,完善表单与菜单验证逻辑
This commit is contained in:
zhangtao
2026-06-17 01:56:31 +08:00
parent 17b3cd0a4c
commit 73f2823692
500 changed files with 40763 additions and 26616 deletions
@@ -0,0 +1,466 @@
<template>
<div class="fa-full-height">
<ElTabs v-model="activeTab" @tab-change="onTabChange">
<!-- 平台端:全部发票 -->
<ElTabPane v-if="isSuperAdmin" label="发票管理" name="platform">
<FaSearchBar
v-show="platformShowSearchBar"
ref="platformSearchBarRef"
v-model="platformSearchForm"
:items="platformSearchItems"
:rules="{}"
:is-expand="false"
:show-expand="true"
:show-reset="true"
:show-search="true"
:disabled-search="false"
:default-expanded="false"
:button-left-limit="0"
@search="handlePlatformSearch"
@reset="handlePlatformReset"
/>
<ElCard
shadow="hover"
class="fa-table-card"
:style="{ 'margin-top': platformShowSearchBar ? '12px' : '0' }"
>
<FaTableHeader
v-model:columns="platformColumnChecks"
v-model:showSearchBar="platformShowSearchBar"
:loading="platformLoading"
@refresh="getPlatformData"
/>
<FaTable
ref="platformTableRef"
:loading="platformLoading"
:data="platformData"
:columns="platformColumns"
:pagination="platformPagination"
@pagination:size-change="handlePlatformSizeChange"
@pagination:current-change="handlePlatformCurrentChange"
/>
</ElCard>
</ElTabPane>
<!-- 租户端:我的发票 -->
<ElTabPane label="我的发票" name="my">
<ElCard shadow="hover" class="fa-table-card" :style="{ 'margin-top': '0' }">
<FaTableHeader :loading="myLoading" @refresh="getMyData">
<template #left>
<FaTableHeaderLeft perm-create="tenant:admin" @add="openApplyDialog" />
</template>
</FaTableHeader>
<FaTable
ref="myTableRef"
:loading="myLoading"
:data="myData"
:columns="myColumns"
:pagination="myPagination"
@pagination:size-change="handleMySizeChange"
@pagination:current-change="handleMyCurrentChange"
/>
</ElCard>
</ElTabPane>
</ElTabs>
<!-- 申请开票弹窗 -->
<FaDialog v-model="applyDialogVisible" title="申请开票" width="520px">
<FaForm
ref="applyFormRef"
v-model="applyFormData"
:items="applyFormItems"
:rules="applyRules"
:show-footer="false"
/>
<template #footer>
<ElButton @click="applyDialogVisible = false">取消</ElButton>
<ElButton type="primary" :loading="applySubmitting" @click="submitApply">提交申请</ElButton>
</template>
</FaDialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed, h } from "vue";
import { ElMessageBox, ElButton, ElTabs, ElTabPane, ElTag } from "element-plus";
import { useTable } from "@/hooks/core/useTable";
import { useAuth } from "@/hooks/core/useAuth";
import InvoiceAPI from "@/api/module_platform/invoice";
import type { InvoiceTable } from "@/api/module_platform/invoice";
import type { ColumnOption } from "@/types/component";
import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue";
import type { FormItem } from "@/components/forms/fa-form/index.vue";
import { renderTableOperationCell, type TableOperationAction } from "@utils";
defineOptions({ name: "Invoice" });
const { hasAuth } = useAuth();
const activeTab = ref("my");
const isSuperAdmin = ref(true);
const platformShowSearchBar = ref(true);
// ══════════════════ 标签函数 ════════════════════
function invoiceStatusLabel(status: number): string {
const map: Record<number, string> = { 0: "待开具", 1: "已开具", 2: "开票失败", 3: "已作废" };
return map[status] || String(status);
}
function invoiceStatusType(status: number): string {
const map: Record<number, string> = { 0: "warning", 1: "success", 2: "danger", 3: "info" };
return map[status] || "info";
}
// ══════════════════ 平台端:全部发票 ════════════════════
function buildPlatformRowActions(row: InvoiceTable): TableOperationAction[] {
const actions: TableOperationAction[] = [];
if (row.status === 0) {
actions.push({
key: "issue",
label: "开具",
artType: "edit",
perm: "module_platform:invoice:update",
run: () => issueInvoice(row),
});
}
if (row.status === 1) {
actions.push({
key: "void",
label: "作废",
artType: "delete",
perm: "module_platform:invoice:update",
run: () => voidInvoice(row),
});
}
return actions.filter((a) => a.perm != null && hasAuth(String(a.perm)));
}
function formatPlatformOpCell(row: InvoiceTable) {
const actions = buildPlatformRowActions(row);
if (actions.length === 0) return "—";
return renderTableOperationCell(actions, {
wrapperClass: "inline-flex flex-wrap items-center justify-end gap-1",
});
}
const platformSearchForm = ref<Record<string, unknown>>({ tenant_id: "", status: undefined });
const platformSearchItems = computed<SearchFormItem[]>(() => [
{
label: "租户ID",
key: "tenant_id",
type: "input",
placeholder: "租户ID",
span: 6,
props: { clearable: true },
},
{
label: "状态",
key: "status",
type: "select",
placeholder: "状态",
span: 6,
props: {
clearable: true,
options: [
{ label: "待开具", value: 0 },
{ label: "已开具", value: 1 },
{ label: "已作废", value: 2 },
],
},
},
]);
const platformSearchBarRef = ref();
const {
columns: platformColumns,
columnChecks: platformColumnChecks,
data: platformData,
loading: platformLoading,
pagination: platformPagination,
getData: getPlatformData,
replaceSearchParams: replacePlatformSearchParams,
resetSearchParams: resetPlatformSearchParams,
handleSizeChange: handlePlatformSizeChange,
handleCurrentChange: handlePlatformCurrentChange,
} = useTable({
core: {
apiFn: InvoiceAPI.listInvoices,
apiParams: {
page_no: 1,
page_size: 50,
},
columnsFactory: (): ColumnOption<InvoiceTable>[] => [
{ prop: "id", label: "ID", width: 60 },
{ prop: "tenant_id", label: "租户ID", width: 80 },
{ prop: "invoice_no", label: "发票号", width: 180, showOverflowTooltip: true },
{ prop: "title", label: "抬头", minWidth: 180, showOverflowTooltip: true },
{
prop: "invoice_type",
label: "类型",
width: 110,
formatter: (row) => row.invoice_type || "—",
},
{
prop: "amount",
label: "金额",
width: 120,
formatter: (row) => `¥${((row.amount || 0) / 100).toFixed(2)}`,
},
{
prop: "status",
label: "状态",
width: 100,
formatter: (row) => {
const t = invoiceStatusType(row.status) as "warning" | "success" | "info" | "danger";
return h(ElTag, { type: t, size: "small" as const }, () =>
invoiceStatusLabel(row.status)
);
},
},
{ prop: "address_info", label: "地址电话", width: 180, showOverflowTooltip: true },
{ prop: "created_time", label: "申请时间", width: 160, showOverflowTooltip: true },
{
label: "操作",
width: 150,
fixed: "right",
align: "center",
formatter: (row) => formatPlatformOpCell(row),
},
],
},
});
function handlePlatformSearch() {
const params: Record<string, unknown> = {};
if (platformSearchForm.value.status !== undefined && platformSearchForm.value.status !== "") {
params.status = platformSearchForm.value.status;
}
const tid = parseInt(String(platformSearchForm.value.tenant_id || ""));
if (!isNaN(tid)) params.tenant_id = tid;
replacePlatformSearchParams(params);
}
function handlePlatformReset() {
platformSearchForm.value = { tenant_id: "", status: undefined };
resetPlatformSearchParams();
}
async function issueInvoice(row: InvoiceTable) {
try {
await InvoiceAPI.issueInvoice(row.id, {});
getPlatformData();
} catch {
/* ignore */
}
}
async function voidInvoice(row: InvoiceTable) {
try {
const { value } = await ElMessageBox.prompt("请输入作废原因", "作废发票", { type: "warning" });
await InvoiceAPI.voidInvoice(row.id, value);
getPlatformData();
} catch {
/* cancelled */
}
}
// ══════════════════ 租户端:我的发票 ════════════════════
const {
columns: myColumns,
data: myData,
loading: myLoading,
pagination: myPagination,
getData: getMyData,
handleSizeChange: handleMySizeChange,
handleCurrentChange: handleMyCurrentChange,
} = useTable({
core: {
apiFn: InvoiceAPI.tenantListInvoices,
apiParams: {
page_no: 1,
page_size: 50,
},
columnsFactory: (): ColumnOption<InvoiceTable>[] => [
{ prop: "id", label: "ID", width: 60 },
{ prop: "invoice_no", label: "发票号", width: 180, showOverflowTooltip: true },
{ prop: "title", label: "抬头", minWidth: 180, showOverflowTooltip: true },
{
prop: "invoice_type",
label: "类型",
width: 110,
formatter: (row) => row.invoice_type || "—",
},
{
prop: "amount",
label: "金额",
width: 120,
formatter: (row) => `¥${((row.amount || 0) / 100).toFixed(2)}`,
},
{
prop: "status",
label: "状态",
width: 100,
formatter: (row) => {
const t = invoiceStatusType(row.status) as "warning" | "success" | "info" | "danger";
return h(ElTag, { type: t, size: "small" as const }, () =>
invoiceStatusLabel(row.status)
);
},
},
{
prop: "created_time",
label: "申请时间",
width: 160,
showOverflowTooltip: true,
formatter: (row) => row.created_time || "—",
},
],
},
});
// ══════════════════ 申请开票弹窗 ════════════════════
const applyDialogVisible = ref(false);
const applySubmitting = ref(false);
const applyFormRef = ref();
let applyFormData = reactive({
order_id: null as number | null,
title: "",
invoice_type: "vat_normal",
tax_no: "",
address_info: "",
bank_info: "",
});
const applyFormItems: FormItem[] = [
{
label: "关联订单ID",
key: "order_id",
type: "inputNumber",
span: 24,
props: { min: 1, placeholder: "输入已支付订单ID" },
},
{
label: "发票抬头",
key: "title",
type: "input",
span: 24,
props: { placeholder: "公司全称或个人姓名", maxlength: 100 },
},
{
label: "发票类型",
key: "invoice_type",
type: "select",
span: 24,
props: {
options: [
{ label: "普通发票", value: "vat_normal" },
{ label: "增值税专用发票", value: "vat_special" },
],
},
},
{
label: "税号",
key: "tax_no",
type: "input",
span: 24,
props: { placeholder: "统一社会信用代码(可选)", maxlength: 30 },
},
{
label: "银行信息",
key: "bank_info",
type: "input",
span: 24,
props: { placeholder: "开户行及账号(可选)", maxlength: 100 },
},
{
label: "地址电话",
key: "address_info",
type: "input",
span: 24,
props: { placeholder: "注册地址及电话(专票必填)", maxlength: 200 },
},
];
const applyRules = {
order_id: [{ required: true, message: "请输入订单ID", trigger: "blur" }],
title: [{ required: true, message: "请输入发票抬头", trigger: "blur" }],
};
function openApplyDialog() {
Object.assign(applyFormData, {
order_id: null,
title: "",
invoice_type: "vat_normal",
tax_no: "",
address_info: "",
bank_info: "",
});
applyFormRef.value?.resetFields?.();
applyDialogVisible.value = true;
}
async function submitApply() {
const form: any = applyFormRef.value;
if (!form) return;
const valid = await form.validate().catch(() => false);
if (!valid) return;
applySubmitting.value = true;
try {
await InvoiceAPI.applyInvoice({
order_id: applyFormData.order_id!,
title: applyFormData.title,
invoice_type: applyFormData.invoice_type,
tax_no: applyFormData.tax_no || undefined,
address_info: applyFormData.address_info || undefined,
bank_info: applyFormData.bank_info || undefined,
});
applyDialogVisible.value = false;
getMyData();
} catch {
/* ignore */
} finally {
applySubmitting.value = false;
}
}
// ══════════════════ Tab 切换 ════════════════════
function onTabChange(tab: string | number) {
if (tab === "platform") getPlatformData();
else if (tab === "my") getMyData();
}
// ══════════════════ 初始化 ════════════════════
getMyData();
</script>
<style scoped lang="scss">
:deep(.el-tabs) {
display: flex;
flex: 1;
flex-direction: column;
min-height: 0;
}
:deep(.el-tabs__content) {
flex: 1;
min-height: 0;
overflow: visible;
}
:deep(.el-tab-pane) {
display: flex;
flex-direction: column;
height: 100%;
}
</style>