chore: 完成多批次代码优化与重构

- 重构工作流模块目录结构,迁移代码文件
- 修复类型断言空值安全问题,添加 ! 操作符
- 优化样式类名,替换 flex-cc 为标准 flex 工具类
- 更新路由标签简化文案,移除冗余注释
- 调整 ruff 配置,放宽行长度限制
- 更新 README 与多语言文案,优化项目描述
- 修复表单、图表组件的类型与样式问题
- 简化搜索表单、数据卡片的布局代码
This commit is contained in:
zhangtao
2026-06-20 05:31:46 +08:00
parent 82b742620b
commit d34d4a4c50
378 changed files with 4149 additions and 6375 deletions
@@ -272,13 +272,13 @@
</template>
<script setup lang="ts">
import { h } from "vue";
import { useAuth } from "@/hooks/core/useAuth";
import { useTable } from "@/hooks/core/useTable";
import { useTableSelection } from "@/hooks/core/useTableSelection";
import { useCrudDialog } from "@/hooks/core/useCrudDialog";
import { confirmDelete, confirmBatchDelete } from "@/hooks/core/useConfirm";
import { renderTableOperationCell, type TableOperationAction } from "@utils";
import type { ColumnOption } from "@/types/component";
import { renderTableOperationCell, type TableOperationAction, resolveStatusColumns } from "@utils";
import EmailAPI, {
type EmailConfigTable,
type EmailConfigCreateForm,
@@ -290,9 +290,9 @@ import EmailAPI, {
} from "@/api/module_platform/email";
import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue";
import type { FormItem } from "@/components/forms/fa-form/index.vue";
import FaSearchBar from "@/components/forms/fa-search-bar/index.vue";
import FaForm from "@/components/forms/fa-form/index.vue";
import { ElTag, ElMessage } from "element-plus";
import type FaForm from "@/components/forms/fa-form/index.vue";
import FaStatusTag from "@/components/others/fa-status-tag/index.vue";
import { ElMessage } from "element-plus";
defineOptions({ name: "Email" });
@@ -343,7 +343,7 @@ const {
core: {
apiFn: EmailAPI.listConfig,
apiParams: { page_no: 1, page_size: 10 },
columnsFactory: (): ColumnOption<EmailConfigTable>[] => [
columnsFactory: resolveStatusColumns<EmailConfigTable>(() => [
{ type: "selection", width: 48, fixed: "left" },
{ type: "globalIndex", width: 56, label: "序号" },
{ prop: "name", label: "配置名称", minWidth: 140, showOverflowTooltip: true },
@@ -355,30 +355,28 @@ const {
prop: "use_tls",
label: "SSL/TLS",
width: 90,
formatter: (row: EmailConfigTable) =>
h(ElTag, { type: row.use_tls ? "success" : "info", size: "small" }, () =>
row.use_tls ? "启用" : "禁用"
),
status: {
true: { type: "success", text: "启用" },
false: { type: "info", text: "禁用" },
},
},
{
prop: "is_default",
label: "默认",
width: 70,
formatter: (row: EmailConfigTable) =>
row.is_default
? h(ElTag, { type: "warning", size: "small" }, () => "默认")
: h("span", { class: "text-g-400" }, () => "—"),
status: {
true: { type: "warning", size: "small", text: "默认" },
false: { type: "info", size: "small", text: "—" },
},
},
{
prop: "status",
label: "状态",
width: 70,
formatter: (row: EmailConfigTable) =>
h(
ElTag,
{ type: row.status === ("0" as any) ? "success" : "danger", size: "small" },
() => (row.status === ("0" as any) ? "正常" : "禁用")
),
status: {
"0": { type: "success", size: "small", text: "正常" },
"1": { type: "danger", size: "small", text: "禁用" },
},
},
{
prop: "operation",
@@ -413,7 +411,7 @@ const {
return renderTableOperationCell(actions.filter((a) => a.perm != null && hasAuth(a.perm)));
},
},
],
]),
},
});
@@ -469,7 +467,7 @@ const {
core: {
apiFn: EmailAPI.listTemplate,
apiParams: { page_no: 1, page_size: 10 },
columnsFactory: (): ColumnOption<EmailTemplateTable>[] => [
columnsFactory: resolveStatusColumns<EmailTemplateTable>(() => [
{ type: "selection", width: 48, fixed: "left" },
{ type: "globalIndex", width: 56, label: "序号" },
{ prop: "name", label: "模板名称", minWidth: 140, showOverflowTooltip: true },
@@ -478,19 +476,17 @@ const {
label: "模板编码",
width: 140,
formatter: (row: EmailTemplateTable) =>
h(ElTag, { type: "info", size: "small" }, () => row.template_code),
h(FaStatusTag, { type: "info", size: "small", label: row.template_code }),
},
{ prop: "subject", label: "邮件主题", minWidth: 200, showOverflowTooltip: true },
{
prop: "status",
label: "状态",
width: 70,
formatter: (row: EmailTemplateTable) =>
h(
ElTag,
{ type: row.status === ("0" as any) ? "success" : "danger", size: "small" },
() => (row.status === ("0" as any) ? "正常" : "禁用")
),
status: {
"0": { type: "success", size: "small", text: "正常" },
"1": { type: "danger", size: "small", text: "禁用" },
},
},
{ prop: "description", label: "备注", minWidth: 120, showOverflowTooltip: true },
{
@@ -519,7 +515,7 @@ const {
return renderTableOperationCell(actions.filter((a) => a.perm != null && hasAuth(a.perm)));
},
},
],
]),
},
});
@@ -569,7 +565,7 @@ const {
core: {
apiFn: EmailAPI.listLog,
apiParams: { page_no: 1, page_size: 10 },
columnsFactory: (): ColumnOption<EmailLogTable>[] => [
columnsFactory: resolveStatusColumns<EmailLogTable>(() => [
{ type: "globalIndex", width: 56, label: "序号" },
{ prop: "to_email", label: "收件人", minWidth: 180, showOverflowTooltip: true },
{ prop: "subject", label: "主题", minWidth: 200, showOverflowTooltip: true },
@@ -579,7 +575,7 @@ const {
width: 120,
formatter: (row: EmailLogTable) =>
row.template_code
? h(ElTag, { type: "info", size: "small" }, () => row.template_code!)
? h(FaStatusTag, { type: "info", size: "small", label: row.template_code! })
: h("span", { class: "text-g-400" }, () => "—"),
},
{
@@ -587,16 +583,16 @@ const {
label: "业务类型",
width: 110,
formatter: (row: EmailLogTable) =>
h(ElTag, { size: "small" }, () => bizTypeLabel(row.biz_type)),
h(FaStatusTag, { size: "small", label: bizTypeLabel(row.biz_type) }),
},
{
prop: "status",
label: "发送状态",
width: 90,
formatter: (row: EmailLogTable) =>
h(ElTag, { type: row.status === 1 ? "success" : "danger", size: "small" }, () =>
row.status === 1 ? "成功" : "失败"
),
status: {
1: { type: "success", text: "成功" },
0: { type: "danger", text: "失败" },
},
},
{ prop: "retry_count", label: "重试", width: 60 },
{
@@ -608,7 +604,7 @@ const {
row.error_msg || h("span", { class: "text-g-400" }, () => "—"),
},
{ prop: "created_time", label: "发送时间", width: 168, showOverflowTooltip: true },
],
]),
},
});
@@ -1063,7 +1059,7 @@ async function handleTemplateBatchDelete() {
// ══════════════════ 搜索逻辑 ════════════════════
async function handleConfigSearch(p: ConfigSearchForm) {
replaceConfigSearchParams({ name: p.name || undefined } as any);
replaceConfigSearchParams({ name: p.name || undefined } as unknown as ConfigSearchForm);
getConfigData();
}
function onConfigResetSearch() {
@@ -1075,7 +1071,7 @@ async function handleTemplateSearch(p: TemplateSearchForm) {
replaceTemplateSearchParams({
name: p.name || undefined,
template_code: p.template_code || undefined,
} as any);
} as unknown as TemplateSearchForm);
getTemplateData();
}
function onTemplateResetSearch() {
@@ -1087,7 +1083,7 @@ async function handleLogSearch(p: LogSearchForm) {
replaceLogSearchParams({
to_email: p.to_email || undefined,
template_code: p.template_code || undefined,
} as any);
} as unknown as LogSearchForm);
getLogData();
}
function onLogResetSearch() {
@@ -84,16 +84,15 @@
</template>
<script setup lang="ts">
import { ref, reactive, computed, h } from "vue";
import { ElMessageBox, ElButton, ElTabs, ElTabPane, ElTag } from "element-plus";
import { ref, reactive, computed } from "vue";
import { ElMessageBox, ElButton, ElTabs, ElTabPane } 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";
import { renderTableOperationCell, type TableOperationAction, resolveStatusColumns } from "@utils";
defineOptions({ name: "Invoice" });
@@ -103,18 +102,6 @@ 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[] {
@@ -196,7 +183,7 @@ const {
page_no: 1,
page_size: 50,
},
columnsFactory: (): ColumnOption<InvoiceTable>[] => [
columnsFactory: resolveStatusColumns<InvoiceTable>(() => [
{ prop: "id", label: "ID", width: 60 },
{ prop: "tenant_id", label: "租户ID", width: 80 },
{ prop: "invoice_no", label: "发票号", width: 180, showOverflowTooltip: true },
@@ -217,11 +204,11 @@ const {
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)
);
status: {
0: { type: "warning", text: "待开具" },
1: { type: "success", text: "已开具" },
2: { type: "danger", text: "开票失败" },
3: { type: "info", text: "已作废" },
},
},
{ prop: "address_info", label: "地址电话", width: 180, showOverflowTooltip: true },
@@ -233,7 +220,7 @@ const {
align: "center",
formatter: (row) => formatPlatformOpCell(row),
},
],
]),
},
});
@@ -288,7 +275,7 @@ const {
page_no: 1,
page_size: 50,
},
columnsFactory: (): ColumnOption<InvoiceTable>[] => [
columnsFactory: resolveStatusColumns<InvoiceTable>(() => [
{ prop: "id", label: "ID", width: 60 },
{ prop: "invoice_no", label: "发票号", width: 180, showOverflowTooltip: true },
{ prop: "title", label: "抬头", minWidth: 180, showOverflowTooltip: true },
@@ -308,11 +295,11 @@ const {
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)
);
status: {
0: { type: "warning", text: "待开具" },
1: { type: "success", text: "已开具" },
2: { type: "danger", text: "开票失败" },
3: { type: "info", text: "已作废" },
},
},
{
@@ -322,7 +309,7 @@ const {
showOverflowTooltip: true,
formatter: (row) => row.created_time || "—",
},
],
]),
},
});
@@ -81,15 +81,19 @@
:scrollbar="false"
>
<template #type="{ row }">
<ElTag v-if="row?.type === MenuTypeEnum.CATALOG" type="warning">目录</ElTag>
<ElTag v-if="row?.type === MenuTypeEnum.MENU" type="success">菜单</ElTag>
<ElTag v-if="row?.type === MenuTypeEnum.BUTTON" type="danger">按钮</ElTag>
<ElTag v-if="row?.type === MenuTypeEnum.EXTLINK" type="info">外链</ElTag>
<FaStatusTag v-if="row?.type === MenuTypeEnum.CATALOG" type="warning" label="目录" />
<FaStatusTag v-if="row?.type === MenuTypeEnum.MENU" type="success" label="菜单" />
<FaStatusTag v-if="row?.type === MenuTypeEnum.BUTTON" type="danger" label="按钮" />
<FaStatusTag v-if="row?.type === MenuTypeEnum.EXTLINK" type="info" label="外链" />
</template>
<template #client="{ row }">
<ElTag v-if="row?.client === MenuClientEnum.PC" type="primary">PC</ElTag>
<ElTag v-else-if="row?.client === MenuClientEnum.APP" type="success">APP</ElTag>
<ElTag v-else type="info">{{ row?.client || "—" }}</ElTag>
<FaStatusTag v-if="row?.client === MenuClientEnum.PC" type="primary" label="PC" />
<FaStatusTag
v-else-if="row?.client === MenuClientEnum.APP"
type="success"
label="APP"
/>
<FaStatusTag v-else type="info" :label="(row as unknown as MenuTable)?.client || '—'" />
</template>
<template #icon="{ row }">
<template v-if="row?.icon">
@@ -332,6 +336,7 @@
</template>
<script setup lang="ts">
import { h } from "vue";
defineOptions({
name: "SysMenu",
inheritAttrs: false,
@@ -349,13 +354,13 @@ import MenuAPI, {
import { MenuClientEnum, MenuTypeEnum } from "@/enums/system/menu.enum";
import { formatTree } from "@utils/common";
import { useAuth } from "@/hooks/core/useAuth";
import { renderTableOperationCell, type TableOperationAction } from "@utils";
import { renderTableOperationCell, type TableOperationAction, resolveStatusColumns } from "@utils";
import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue";
import type FaSearchBar from "@/components/forms/fa-search-bar/index.vue";
import type { FormItem } from "@/components/forms/fa-form/index.vue";
import FaSearchBar from "@/components/forms/fa-search-bar/index.vue";
import FaForm from "@/components/forms/fa-form/index.vue";
import FaMenuRouteIcon from "@/components/others/fa-menu-routeIcon/index.vue";
import { ElTag, ElMessage, ElMessageBox } from "element-plus";
import type FaForm from "@/components/forms/fa-form/index.vue";
import FaMenuRouteIcon from "@/components/others/fa-menu-route-icon/index.vue";
import { ElMessage, ElMessageBox } from "element-plus";
const { hasAuth } = useAuth();
const appStore = useAppStore();
@@ -363,7 +368,7 @@ const userStore = useUserStore();
type MenuSearchForm = {
name?: string;
status?: string;
status?: number;
created_time?: string[];
};
@@ -376,25 +381,6 @@ function buildMenuListQuery(p: MenuSearchForm): MenuPageQuery {
};
}
function menuTypeTag(row: MenuTable) {
const t = row.type;
if (t === MenuTypeEnum.CATALOG) return h(ElTag, { type: "warning" }, () => "目录");
if (t === MenuTypeEnum.MENU) return h(ElTag, { type: "success" }, () => "菜单");
if (t === MenuTypeEnum.BUTTON) return h(ElTag, { type: "danger" }, () => "按钮");
if (t === MenuTypeEnum.EXTLINK) return h(ElTag, { type: "info" }, () => "外链");
return h("span", "—");
}
function menuClientTag(row: MenuTable) {
if (row.client === MenuClientEnum.PC) return h(ElTag, { type: "primary" }, () => "PC");
if (row.client === MenuClientEnum.APP) return h(ElTag, { type: "success" }, () => "APP");
return h(ElTag, { type: "info" }, () => String(row.client ?? "—"));
}
function ynTag(v: boolean | undefined, yes = "是", no = "否") {
return h(ElTag, { type: v ? "success" : "danger" }, () => (v ? yes : no));
}
function buildMenuRowActions(
row: MenuTable,
ctx: {
@@ -885,118 +871,159 @@ const opCtx = {
onDelete: deleteMenuRow,
};
const { columnChecks, columns } = useTableColumns<MenuTable>(() => [
{ type: "selection", width: 48, fixed: "left" },
{ type: "index", label: "序号", width: 60, fixed: "left" },
{ prop: "name", label: "菜单名称", minWidth: 200, showOverflowTooltip: true },
{
prop: "icon",
label: "图标",
width: 72,
align: "center",
formatter: (row: MenuTable) =>
row.icon
? h(FaMenuRouteIcon, { icon: row.icon, style: { verticalAlign: "-0.15em" } })
: h("span", { class: "text-g-400" }, "—"),
},
{
prop: "status",
label: "状态",
width: 88,
formatter: (row: MenuTable) =>
h(ElTag, { type: row.status === 0 ? "success" : "danger" }, () =>
row.status === 0 ? "启用" : "停用"
),
},
{ prop: "type", label: "类型", width: 88, align: "center", formatter: menuTypeTag },
{ prop: "client", label: "终端", width: 88, align: "center", formatter: menuClientTag },
{
prop: "scope",
label: "可见范围",
width: 96,
align: "center",
formatter: (row: MenuTable) =>
row.scope
? h(ElTag, { type: row.scope === "platform" ? "warning" : "success" }, () =>
row.scope === "platform" ? "仅平台" : "租户可用"
)
: h("span", { class: "text-g-400" }, "—"),
},
{ prop: "order", label: "排序", width: 80 },
{ prop: "route_name", label: "路由名称", minWidth: 100, showOverflowTooltip: true },
{ prop: "route_path", label: "路由路径", minWidth: 140, showOverflowTooltip: true },
{ prop: "permission", label: "权限标识", minWidth: 160, showOverflowTooltip: true },
{ prop: "component_path", label: "组件路径", minWidth: 140, showOverflowTooltip: true },
{ prop: "active_path", label: "激活路径", minWidth: 100, showOverflowTooltip: true },
{ prop: "redirect", label: "重定向", minWidth: 100, showOverflowTooltip: true },
{ prop: "link", label: "外链地址", minWidth: 140, showOverflowTooltip: true },
{
prop: "keep_alive",
label: "是否缓存",
width: 96,
formatter: (row: MenuTable) => ynTag(row.keep_alive),
},
{
prop: "hidden",
label: "是否隐藏",
width: 96,
formatter: (row: MenuTable) => ynTag(row.hidden),
},
{
prop: "always_show",
label: "显示根路由",
width: 108,
formatter: (row: MenuTable) => ynTag(row.always_show),
},
{
prop: "affix",
label: "固定路由",
width: 96,
formatter: (row: MenuTable) => ynTag(row.affix),
},
{ prop: "title", label: "菜单标题", minWidth: 100, showOverflowTooltip: true },
{
prop: "is_iframe",
label: "嵌入iframe",
width: 100,
formatter: (row: MenuTable) => ynTag(row.is_iframe),
},
{
prop: "is_hide_tab",
label: "隐藏标签页",
width: 100,
formatter: (row: MenuTable) => ynTag(row.is_hide_tab),
},
{
prop: "show_badge",
label: "红点角标",
width: 96,
formatter: (row: MenuTable) => ynTag(row.show_badge),
},
{ prop: "show_text_badge", label: "文字角标", width: 100, showOverflowTooltip: true },
{
prop: "params",
label: "路由参数",
minWidth: 100,
formatter: (row: MenuTable) =>
row.params == null
? "—"
: typeof row.params === "object"
? JSON.stringify(row.params)
: String(row.params),
},
{ prop: "description", label: "描述", minWidth: 140, showOverflowTooltip: true },
{ prop: "created_time", label: "创建时间", width: 168, showOverflowTooltip: true },
{ prop: "updated_time", label: "更新时间", width: 168, showOverflowTooltip: true },
{
prop: "operation",
label: "操作",
width: 220,
fixed: "right",
align: "right",
formatter: (row: MenuTable) => formatMenuOperationCell(row, opCtx),
},
]);
const { columnChecks, columns } = useTableColumns<MenuTable>(
resolveStatusColumns(() => [
{ type: "selection", width: 48, fixed: "left" },
{ type: "index", label: "序号", width: 60, fixed: "left" },
{ prop: "name", label: "菜单名称", minWidth: 200, showOverflowTooltip: true },
{
prop: "icon",
label: "图标",
width: 72,
align: "center",
formatter: (row: MenuTable) =>
row.icon
? h(FaMenuRouteIcon, { icon: row.icon, style: { verticalAlign: "-0.15em" } })
: h("span", { class: "text-g-400" }, "—"),
},
{
prop: "status",
label: "状态",
width: 88,
status: {
0: { type: "success", text: "启用" },
1: { type: "danger", text: "停用" },
},
},
{
prop: "type",
label: "类型",
width: 88,
align: "center",
status: {
1: { type: "warning", text: "目录" },
2: { type: "success", text: "菜单" },
3: { type: "danger", text: "按钮" },
4: { type: "info", text: "外链" },
},
},
{
prop: "client",
label: "终端",
width: 88,
align: "center",
status: {
pc: { type: "primary", text: "PC" },
app: { type: "success", text: "APP" },
},
},
{
prop: "scope",
label: "可见范围",
width: 96,
align: "center",
status: {
platform: { type: "warning", text: "仅平台" },
tenant: { type: "success", text: "租户可用" },
},
},
{ prop: "order", label: "排序", width: 80 },
{ prop: "route_name", label: "路由名称", minWidth: 100, showOverflowTooltip: true },
{ prop: "route_path", label: "路由路径", minWidth: 140, showOverflowTooltip: true },
{ prop: "permission", label: "权限标识", minWidth: 160, showOverflowTooltip: true },
{ prop: "component_path", label: "组件路径", minWidth: 140, showOverflowTooltip: true },
{ prop: "active_path", label: "激活路径", minWidth: 100, showOverflowTooltip: true },
{ prop: "redirect", label: "重定向", minWidth: 100, showOverflowTooltip: true },
{ prop: "link", label: "外链地址", minWidth: 140, showOverflowTooltip: true },
{
prop: "keep_alive",
label: "是否缓存",
width: 96,
status: {
true: { type: "success", text: "是" },
false: { type: "danger", text: "否" },
},
},
{
prop: "hidden",
label: "是否隐藏",
width: 96,
status: {
true: { type: "success", text: "是" },
false: { type: "danger", text: "否" },
},
},
{
prop: "always_show",
label: "显示根路由",
width: 108,
status: {
true: { type: "success", text: "是" },
false: { type: "danger", text: "否" },
},
},
{
prop: "affix",
label: "固定路由",
width: 96,
status: {
true: { type: "success", text: "是" },
false: { type: "danger", text: "否" },
},
},
{ prop: "title", label: "菜单标题", minWidth: 100, showOverflowTooltip: true },
{
prop: "is_iframe",
label: "嵌入iframe",
width: 100,
status: {
true: { type: "success", text: "是" },
false: { type: "danger", text: "否" },
},
},
{
prop: "is_hide_tab",
label: "隐藏标签页",
width: 100,
status: {
true: { type: "success", text: "是" },
false: { type: "danger", text: "否" },
},
},
{
prop: "show_badge",
label: "红点角标",
width: 96,
status: {
true: { type: "success", text: "是" },
false: { type: "danger", text: "否" },
},
},
{ prop: "show_text_badge", label: "文字角标", width: 100, showOverflowTooltip: true },
{
prop: "params",
label: "路由参数",
minWidth: 100,
formatter: (row: MenuTable) =>
row.params == null
? "—"
: typeof row.params === "object"
? JSON.stringify(row.params)
: String(row.params),
},
{ prop: "description", label: "描述", minWidth: 140, showOverflowTooltip: true },
{ prop: "created_time", label: "创建时间", width: 168, showOverflowTooltip: true },
{ prop: "updated_time", label: "更新时间", width: 168, showOverflowTooltip: true },
{
prop: "operation",
label: "操作",
width: 220,
fixed: "right",
align: "right",
formatter: (row: MenuTable) => formatMenuOperationCell(row, opCtx),
},
])
);
const rules = reactive({
name: [
@@ -120,7 +120,7 @@ const methodLabel = computed(() => (selectedMethod.value === "alipay" ? "支付
async function loadOrderStatus() {
try {
const res = await OrderAPI.queryPaymentStatus(orderId.value);
const data = (res.data as any)?.data;
const data = res.data?.data;
if (data?.paid) {
paid.value = true;
orderNo.value = data.order_no || "";
@@ -136,7 +136,7 @@ async function startPay() {
paying.value = true;
try {
const res = await OrderAPI.payOrder(orderId.value, { pay_method: selectedMethod.value });
const data = (res.data as any)?.data;
const data = res.data?.data;
orderNo.value = data.order_no;
amount.value = data.amount;
qrCodeUrl.value = data.qr_code_url || "";
@@ -171,7 +171,7 @@ function startPolling() {
pollTimer = setInterval(async () => {
try {
const res = await OrderAPI.queryPaymentStatus(orderId.value);
const data = (res.data as any)?.data;
const data = res.data?.data;
if (data?.paid) {
paid.value = true;
orderNo.value = data.order_no || orderNo.value;
@@ -134,16 +134,15 @@
</template>
<script setup lang="ts">
import { ref, reactive, computed, h } from "vue";
import { ElMessageBox, ElButton, ElTabs, ElTabPane, ElTag } from "element-plus";
import { ref, reactive, computed } from "vue";
import { ElMessageBox, ElButton, ElTabs, ElTabPane } from "element-plus";
import { useTable } from "@/hooks/core/useTable";
import { useAuth } from "@/hooks/core/useAuth";
import OrderAPI from "@/api/module_platform/order";
import type { OrderTable, PaymentRecordTable, RefundTable } from "@/api/module_platform/order";
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";
import { renderTableOperationCell, type TableOperationAction, resolveStatusColumns } from "@utils";
defineOptions({ name: "Order" });
@@ -165,27 +164,11 @@ function orderTypeLabel(t: string): string {
return m[t] || t;
}
function orderStatusLabel(s: number): string {
const m: Record<number, string> = {
0: "待支付",
1: "已支付",
2: "已取消",
3: "已退款",
4: "已过期",
};
return m[s] ?? String(s);
}
function payMethodLabel(m: string): string {
const mp: Record<string, string> = { alipay: "支付宝", wxpay: "微信支付" };
return mp[m] || m;
}
function refundStatusLabel(s: number): string {
const m: Record<number, string> = { 0: "待审核", 1: "已批准", 2: "已驳回", 3: "已完成" };
return m[s] ?? String(s);
}
// ══════════════════ 订单列表 ════════════════════
function buildOrderRowActions(row: OrderTable): TableOperationAction[] {
@@ -254,8 +237,6 @@ const orderSearchItems = computed<SearchFormItem[]>(() => [
},
]);
const orderSearchBarRef = ref();
const {
columns: orderColumns,
columnChecks: orderColumnChecks,
@@ -274,7 +255,7 @@ const {
page_no: 1,
page_size: 20,
},
columnsFactory: (): ColumnOption<OrderTable>[] => [
columnsFactory: resolveStatusColumns<OrderTable>(() => [
{ prop: "id", label: "ID", width: 60 },
{ prop: "order_no", label: "订单号", minWidth: 180, showOverflowTooltip: true },
{ prop: "tenant_id", label: "租户ID", width: 80 },
@@ -283,8 +264,12 @@ const {
prop: "order_type",
label: "类型",
width: 90,
formatter: (row: OrderTable) =>
h(ElTag, { size: "small" as const }, () => orderTypeLabel(row.order_type)),
status: {
new: { type: "info", text: "新购" },
renew: { type: "info", text: "续费" },
upgrade: { type: "info", text: "升级" },
downgrade: { type: "info", text: "降级" },
},
},
{
prop: "amount",
@@ -296,14 +281,12 @@ const {
prop: "status",
label: "状态",
width: 90,
formatter: (row: OrderTable) => {
const typeMap = ["warning", "success", "info", "warning", "danger"] as const;
const t = typeMap[row.status] || "info";
return h(
ElTag,
{ type: t as "warning" | "success" | "info" | "danger", size: "small" as const },
() => orderStatusLabel(row.status)
);
status: {
0: { type: "warning", text: "待支付" },
1: { type: "success", text: "已支付" },
2: { type: "info", text: "已取消" },
3: { type: "danger", text: "已退款" },
4: { type: "danger", text: "已过期" },
},
},
{
@@ -327,7 +310,7 @@ const {
align: "center",
formatter: (row: OrderTable) => formatOrderOpCell(row),
},
],
]),
},
});
@@ -422,7 +405,7 @@ const {
page_no: 1,
page_size: 20,
},
columnsFactory: (): ColumnOption<PaymentRecordTable>[] => [
columnsFactory: resolveStatusColumns<PaymentRecordTable>(() => [
{ prop: "id", label: "ID", width: 60 },
{ prop: "order_id", label: "订单ID", width: 80 },
{
@@ -448,19 +431,14 @@ const {
prop: "status",
label: "状态",
width: 90,
formatter: (row: PaymentRecordTable) =>
h(
ElTag,
{
type: (row.status === 1 ? "success" : "danger") as "success" | "danger",
size: "small" as const,
},
() => (row.status === 1 ? "成功" : "失败")
),
status: {
1: { type: "success", text: "成功" },
0: { type: "danger", text: "失败" },
},
},
{ prop: "pay_time", label: "支付时间", width: 160, showOverflowTooltip: true },
{ prop: "created_time", label: "创建时间", width: 160, showOverflowTooltip: true },
],
]),
},
});
@@ -516,8 +494,6 @@ const refundSearchItems = computed<SearchFormItem[]>(() => [
},
]);
const refundSearchBarRef = ref();
const {
columns: refundColumns,
columnChecks: refundColumnChecks,
@@ -536,7 +512,7 @@ const {
page_no: 1,
page_size: 20,
},
columnsFactory: (): ColumnOption<RefundTable>[] => [
columnsFactory: resolveStatusColumns<RefundTable>(() => [
{ prop: "id", label: "ID", width: 60 },
{ prop: "refund_no", label: "退款单号", minWidth: 180, showOverflowTooltip: true },
{ prop: "order_id", label: "订单ID", width: 80 },
@@ -551,14 +527,11 @@ const {
prop: "status",
label: "状态",
width: 100,
formatter: (row: RefundTable) => {
const typeMap = ["warning", "success", "danger", "info"] as const;
const t = typeMap[row.status] || "info";
return h(
ElTag,
{ type: t as "warning" | "success" | "info" | "danger", size: "small" as const },
() => refundStatusLabel(row.status)
);
status: {
0: { type: "warning", text: "待审核" },
1: { type: "success", text: "已批准" },
2: { type: "danger", text: "已驳回" },
3: { type: "info", text: "已完成" },
},
},
{
@@ -578,7 +551,7 @@ const {
align: "center",
formatter: (row: RefundTable) => formatRefundOpCell(row),
},
],
]),
},
});
@@ -141,18 +141,15 @@ import { useCrudDialog } from "@/hooks/core/useCrudDialog";
import { useTableSelection } from "@/hooks/core/useTableSelection";
import { useCrudForm } from "@/hooks/core/useCrudForm";
import { confirmDelete, confirmBatchDelete, confirmToggleStatus } from "@/hooks/core/useConfirm";
import type { ColumnOption } from "@/types/component";
import PackageAPI, { type PackageForm, type PackageTable } from "@/api/module_platform/package";
import MenuAPI from "@/api/module_platform/menu";
import { useAuth } from "@/hooks/core/useAuth";
import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue";
import type FaSearchBar from "@/components/forms/fa-search-bar/index.vue";
import type { FormItem } from "@/components/forms/fa-form/index.vue";
import FaSearchBar from "@/components/forms/fa-search-bar/index.vue";
import FaForm from "@/components/forms/fa-form/index.vue";
import { renderTableOperationCell, type TableOperationAction } from "@utils";
import { ElTag, ElMessage } from "element-plus";
import FaDrawer from "@/components/modal/fa-drawer/index.vue";
import FaMenuTreeTable from "@/components/others/fa-menu-tree-drawer/index.vue";
import FaMenuTreeTable from "@/components/others/fa-menu-tree-table/index.vue";
import { renderTableOperationCell, type TableOperationAction, resolveStatusColumns } from "@utils";
import { ElMessage } from "element-plus";
defineOptions({
name: "Package",
@@ -311,7 +308,7 @@ const {
page_no: 1,
page_size: 10,
},
columnsFactory: (): ColumnOption<PackageTable>[] => [
columnsFactory: resolveStatusColumns<PackageTable>(() => [
{ type: "selection", width: 48, fixed: "left" },
{ type: "globalIndex", width: 56, label: "序号" },
{ prop: "name", label: "套餐名称", minWidth: 120, showOverflowTooltip: true },
@@ -320,10 +317,10 @@ const {
prop: "status",
label: "状态",
width: 80,
formatter: (row: PackageTable) =>
h(ElTag, { type: row.status === 0 ? "success" : "danger" }, () =>
row.status === 0 ? "正常" : "禁用"
),
status: {
0: { type: "success", text: "正常" },
1: { type: "danger", text: "禁用" },
},
},
{ prop: "sort", label: "排序", width: 60, align: "center" },
{
@@ -353,7 +350,7 @@ const {
align: "right",
formatter: (row: PackageTable) => formatPkgOperationCell(row, opCtx),
},
],
]),
},
});
@@ -580,9 +577,14 @@ const { submitLoading, handleCloseDialog, handleOpenDialog, handleSubmit } =
dialogVisible,
dataFormRef,
formRenderKey: pkgFormRenderKey,
detailApi: PackageAPI.detailPackage as any,
createApi: PackageAPI.createPackage as any,
updateApi: PackageAPI.updatePackage as any,
detailApi: PackageAPI.detailPackage as unknown as (
id: number
) => Promise<{ data: { data?: PackageForm } }>,
createApi: PackageAPI.createPackage as unknown as (form: PackageForm) => Promise<unknown>,
updateApi: PackageAPI.updatePackage as unknown as (
id: number,
form: PackageForm
) => Promise<unknown>,
detailFormData,
titles: { create: "新增套餐", update: "修改套餐", detail: "套餐详情" },
onCreateSuccess: refreshCreate,
@@ -254,9 +254,6 @@ import PluginAPI, { type PluginForm, type PluginTable } from "@/api/module_platf
import { useAuth } from "@/hooks/core/useAuth";
import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue";
import type { FormItem } from "@/components/forms/fa-form/index.vue";
import FaSearchBar from "@/components/forms/fa-search-bar/index.vue";
import FaForm from "@/components/forms/fa-form/index.vue";
import FaPagination from "@/components/others/fa-pagination/index.vue";
import {
ElTag,
ElMessage,
@@ -276,10 +273,10 @@ defineOptions({
const { hasAuth } = useAuth();
// ─── 搜索表单 ───
const searchForm = ref<{ name?: string; category?: string; status?: string }>({
const searchForm = ref<{ name?: string; category?: string; status?: number }>({
name: "",
category: "",
status: "",
status: undefined,
});
const showSearchBar = ref(true);
@@ -331,8 +328,7 @@ async function fetchData() {
loading.value = true;
try {
const res = await PluginAPI.list(
{ page_no: pageNo.value, page_size: pageSize.value },
searchForm.value as any
{ page_no: pageNo.value, page_size: pageSize.value, ...searchForm.value }
);
const result = res.data?.data;
data.value = (result?.items as PluginTable[]) || [];
@@ -356,14 +352,14 @@ async function handleSearchBarSearch(params: Record<string, unknown>) {
searchForm.value = {
name: (params.name as string) ?? "",
category: (params.category as string) ?? "",
status: (params.status as string) ?? "",
status: params.status !== undefined ? Number(params.status) : undefined,
};
pageNo.value = 1;
await fetchData();
}
async function onResetSearch() {
searchForm.value = { name: "", category: "", status: "" };
searchForm.value = { name: "", category: "", status: undefined };
pageNo.value = 1;
await fetchData();
}
@@ -580,7 +576,9 @@ const { submitLoading, handleCloseDialog, handleOpenDialog, handleSubmit } =
dialogVisible,
dataFormRef,
formRenderKey: pluginFormRenderKey,
detailApi: PluginAPI.detail as any,
detailApi: PluginAPI.detail as unknown as (
id: number
) => Promise<{ data: { data?: PluginForm } }>,
createApi: PluginAPI.create,
updateApi: PluginAPI.update,
titles: { create: "新增插件", update: "编辑插件", detail: "插件详情" },
@@ -274,9 +274,8 @@
</template>
<script setup lang="ts">
import { ref, computed, h, onMounted } from "vue";
import { ref, computed, onMounted } from "vue";
import type { TabPaneName } from "element-plus";
import { ElTag, ElButton } from "element-plus";
import { useRouter } from "vue-router";
import { useTable } from "@/hooks/core/useTable";
import FaStatsCard from "@/components/cards/fa-stats-card/index.vue";
@@ -290,7 +289,7 @@ import type {
SelfServiceOrderItem,
WorkspaceData,
} from "@/api/module_platform/self_service";
import type { ColumnOption } from "@/types/component";
import { resolveStatusColumns } from "@utils";
import type { DescriptionsItem } from "@/components/others/fa-descriptions/index.vue";
defineOptions({ name: "SelfService" });
@@ -354,15 +353,19 @@ const {
page_no: 1,
page_size: 20,
},
columnsFactory: (): ColumnOption<SelfServiceOrderItem>[] => [
columnsFactory: resolveStatusColumns<SelfServiceOrderItem>(() => [
{ prop: "order_no", label: "订单号", minWidth: 200, showOverflowTooltip: true },
{ prop: "package_name", label: "套餐", width: 120 },
{
prop: "order_type",
label: "类型",
width: 100,
formatter: (row: SelfServiceOrderItem) =>
h(ElTag, { size: "small" as const }, () => orderTypeLabel(row.order_type)),
status: {
new: { type: "info", text: "新购" },
renew: { type: "info", text: "续费" },
upgrade: { type: "info", text: "升级" },
downgrade: { type: "info", text: "降级" },
},
},
{
prop: "amount",
@@ -374,14 +377,11 @@ const {
prop: "status",
label: "状态",
width: 100,
formatter: (row: SelfServiceOrderItem) => {
const typeMap = ["warning", "success", "info", "danger"] as const;
const t = typeMap[row.status] || "info";
return h(
ElTag,
{ type: t as "warning" | "success" | "info" | "danger", size: "small" as const },
() => orderStatusLabel(row.status)
);
status: {
0: { type: "warning", text: "待支付" },
1: { type: "success", text: "已支付" },
2: { type: "info", text: "已取消" },
3: { type: "danger", text: "已退款" },
},
},
{
@@ -392,7 +392,7 @@ const {
},
{ prop: "pay_time", label: "支付时间", width: 160, showOverflowTooltip: true },
{ prop: "created_at", label: "创建时间", width: 160, showOverflowTooltip: true },
],
]),
},
});
@@ -435,21 +435,6 @@ function actionTypeTag(
return map[action];
}
function orderTypeLabel(type: string): string {
const map: Record<string, string> = {
buy: "新购",
renew: "续费",
upgrade: "升级",
downgrade: "降级",
};
return map[type] || type;
}
function orderStatusLabel(status: number): string {
const map: Record<number, string> = { 0: "待支付", 1: "已支付", 2: "已取消", 3: "已退款" };
return map[status] || String(status);
}
// ─── 加载 ───
async function loadPackages() {
packagesLoading.value = true;
@@ -497,10 +482,10 @@ async function confirmAction() {
package_id: currentPackage.value.id,
order_type: currentAction.value as "buy" | "renew" | "upgrade" | "downgrade",
});
const orderData = res?.data as { id: number; order_no: string; amount?: number } | undefined;
const orderData = res?.data as { order_id: number; amount?: number } | undefined;
actionDialogVisible.value = false;
if (orderData?.id && (orderData?.amount || 0) > 0) {
router.push(`/payment/${orderData.id}`);
if (orderData?.order_id && (orderData?.amount || 0) > 0) {
router.push(`/payment/${orderData.order_id}`);
} else {
activeTab.value = "orders";
getOrderData();
@@ -206,7 +206,6 @@ import { useTable } from "@/hooks/core/useTable";
import { useCrudDialog } from "@/hooks/core/useCrudDialog";
import { useTableSelection } from "@/hooks/core/useTableSelection";
import { confirmDelete, confirmBatchDelete } from "@/hooks/core/useConfirm";
import type { ColumnOption } from "@/types/component";
import TenantAPI, {
type TenantCreateForm,
type TenantForm,
@@ -214,22 +213,12 @@ import TenantAPI, {
type TenantUpdateForm,
} from "@/api/module_platform/tenant";
import { useAuth } from "@/hooks/core/useAuth";
import { renderTableOperationCell, type TableOperationAction } from "@utils";
import FaSearchBar from "@/components/forms/fa-search-bar/index.vue";
import FaForm from "@/components/forms/fa-form/index.vue";
import FaUpload from "@/components/others/fa-upload/index.vue";
import { renderTableOperationCell, type TableOperationAction, resolveStatusColumns } from "@utils";
import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue";
import type FaSearchBar from "@/components/forms/fa-search-bar/index.vue";
import type { FormItem } from "@/components/forms/fa-form/index.vue";
import {
ElTag,
ElMessage,
ElTabs,
ElTabPane,
ElForm,
ElFormItem,
ElRow,
ElCol,
} from "element-plus";
import type FaForm from "@/components/forms/fa-form/index.vue";
import { ElMessage, ElTabs, ElTabPane, ElForm, ElFormItem, ElRow, ElCol } from "element-plus";
import { h, ref } from "vue";
defineOptions({
@@ -242,7 +231,7 @@ const { hasAuth } = useAuth();
type TenantSearchForm = {
name?: string;
code?: string;
status?: string;
status?: number;
created_time?: string[];
};
@@ -420,19 +409,19 @@ const {
page_no: 1,
page_size: 10,
},
columnsFactory: (): ColumnOption<TenantTable>[] => [
columnsFactory: resolveStatusColumns<TenantTable>(() => [
{ type: "selection", width: 48, fixed: "left" },
{ type: "globalIndex", width: 56, label: "序号" },
{ prop: "name", label: "租户名称", minWidth: 120, showOverflowTooltip: true },
{ prop: "code", label: "租户编码", minWidth: 80, showOverflowTooltip: true },
{ prop: "code", label: "租户编码", minWidth: 120, showOverflowTooltip: true },
{
prop: "status",
label: "状态",
width: 80,
formatter: (row: TenantTable) =>
h(ElTag, { type: row.status === 0 ? "success" : "danger" }, () =>
row.status === 0 ? "正常" : "禁用"
),
status: {
0: { type: "success", text: "正常" },
1: { type: "danger", text: "禁用" },
},
},
{ prop: "contact_name", label: "联系人", minWidth: 100, showOverflowTooltip: true },
{ prop: "contact_phone", label: "联系电话", width: 110, showOverflowTooltip: true },
@@ -448,7 +437,7 @@ const {
formatter: (row: TenantTable) =>
renderTableOperationCell(buildTenantRowActions(row, opCtx)),
},
],
]),
},
});
@@ -834,9 +823,9 @@ function onResetSearch() {
async function handleSubmit() {
const formRef = dataFormRef.value;
if (!formRef) return;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
const valid = await formRef.validate().catch(() => false);
const valid = await (formRef as unknown as { validate: () => Promise<boolean> })
.validate()
.catch(() => false);
if (!valid) return;
submitLoading.value = true;
const id = formData.value.id as number | undefined;