2026-07-08 22:30:46 +08:00

248 lines
8.6 KiB
JavaScript
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.

import { useMemo, useState } from "react";
import { toPageQuery } from "@/shared/api/query";
import { usePaginatedQuery } from "@/shared/hooks/usePaginatedQuery.js";
import { useRegionOptions } from "@/shared/hooks/useRegionOptions.js";
import { useToast } from "@/shared/ui/ToastProvider.jsx";
import {
createPolicyInstance,
listPolicyInstances,
listPolicyTemplates,
publishPolicyInstance,
savePolicyTemplate,
updatePolicyInstanceStatus,
} from "@/features/policy-config/api";
import { usePolicyConfigAbilities } from "@/features/policy-config/permissions.js";
import {
policyInstanceFormFromTemplate,
policyInstancePayloadFromForm,
policyTemplateFormFromItem,
policyTemplatePayloadFromForm,
} from "@/features/policy-config/schema";
const pageSize = 50;
export function usePolicyConfigPage() {
const abilities = usePolicyConfigAbilities();
const { showToast } = useToast();
const { loadingRegions, regionOptions } = useRegionOptions();
const [activeTab, setActiveTab] = useState("templates");
const [templateKeyword, setTemplateKeyword] = useState("");
const [templateStatus, setTemplateStatus] = useState("");
const [templatePage, setTemplatePage] = useState(1);
const [instanceKeyword, setInstanceKeyword] = useState("");
const [instanceStatus, setInstanceStatus] = useState("");
const [instancePage, setInstancePage] = useState(1);
const [templateDrawerOpen, setTemplateDrawerOpen] = useState(false);
const [instanceDrawerOpen, setInstanceDrawerOpen] = useState(false);
const [editingTemplate, setEditingTemplate] = useState(null);
const [templateForm, setTemplateForm] = useState(() => policyTemplateFormFromItem(null));
const [instanceForm, setInstanceForm] = useState(() => policyInstanceFormFromTemplate(null));
const [loadingAction, setLoadingAction] = useState("");
const templateFilters = useMemo(
() => ({ keyword: templateKeyword, status: templateStatus }),
[templateKeyword, templateStatus],
);
const instanceFilters = useMemo(
() => ({ keyword: instanceKeyword, status: instanceStatus }),
[instanceKeyword, instanceStatus],
);
const templates = usePaginatedQuery({
errorMessage: "加载收益政策模板失败",
fetcher: listPolicyTemplates,
filters: templateFilters,
page: templatePage,
pageSize,
queryKey: ["policy-templates", toPageQuery({ ...templateFilters, page: templatePage, pageSize })],
});
const instances = usePaginatedQuery({
errorMessage: "加载收益政策实例失败",
fetcher: listPolicyInstances,
filters: instanceFilters,
page: instancePage,
pageSize,
queryKey: ["policy-instances", toPageQuery({ ...instanceFilters, page: instancePage, pageSize })],
});
const templateOptions = useMemo(
() =>
(templates.data.items || []).map((template) => ({
label: `${template.name || template.templateCode} · ${template.templateVersion}`,
templateCode: template.templateCode,
templateVersion: template.templateVersion,
value: `${template.templateCode}:${template.templateVersion}`,
})),
[templates.data.items],
);
const regionLabelMap = useMemo(
() =>
regionOptions.reduce((labels, region) => {
labels[String(region.regionId)] = region.label;
return labels;
}, {}),
[regionOptions],
);
const openTemplateEditor = (template = null) => {
setEditingTemplate(template);
setTemplateForm(policyTemplateFormFromItem(template));
setTemplateDrawerOpen(true);
};
const closeTemplateDrawer = () => {
if (loadingAction === "template-save") {
return;
}
setTemplateDrawerOpen(false);
setEditingTemplate(null);
};
const openInstanceCreator = (template = null) => {
setInstanceForm(policyInstanceFormFromTemplate(template || templates.data.items?.[0] || null));
setInstanceDrawerOpen(true);
};
const closeInstanceDrawer = () => {
if (loadingAction === "instance-create") {
return;
}
setInstanceDrawerOpen(false);
};
const submitTemplate = async (event) => {
event.preventDefault();
if (!abilities.canSaveTemplate) {
return;
}
await runAction("template-save", "收益政策模板已保存", async () => {
// 模板 POST 是 code+version 维度的 upsert保存只更新 admin 草稿,运行侧必须再发布实例。
await savePolicyTemplate(policyTemplatePayloadFromForm(templateForm));
setTemplateDrawerOpen(false);
setEditingTemplate(null);
await templates.reload();
});
};
const submitInstance = async (event) => {
event.preventDefault();
if (!abilities.canCreateInstance) {
return;
}
await runAction("instance-create", "收益政策实例已创建", async () => {
// app_code 不在表单中提交,由 apiRequest 的 X-App-Code 和 admin-server appctx 共同确定。
await createPolicyInstance(policyInstancePayloadFromForm(instanceForm));
setInstanceDrawerOpen(false);
await instances.reload();
});
};
const toggleInstanceStatus = async (instance, nextEnabled = instance.status !== "active") => {
if (!instance?.instanceId || !abilities.canUpdateInstance || (instance.status === "active") === nextEnabled) {
return;
}
await runAction(`instance-status-${instance.instanceId}`, nextEnabled ? "收益政策实例已启用" : "收益政策实例已停用", async () => {
// 停用实例要同步 owner service 运行表;不能只在前端隐藏。
await updatePolicyInstanceStatus(instance.instanceId, nextEnabled ? "active" : "disabled");
await instances.reload();
});
};
const publishInstance = async (instance) => {
if (!instance?.instanceId || !abilities.canPublishInstance) {
return;
}
await runAction(`instance-publish-${instance.instanceId}`, "收益政策实例已发布", async () => {
// 发布是 admin 配置进入 wallet/activity runtime 的唯一入口;模板保存本身不会影响线上结算。
await publishPolicyInstance(instance.instanceId);
await instances.reload();
});
};
const changeTemplateKeyword = (value) => {
setTemplateKeyword(value);
setTemplatePage(1);
};
const changeTemplateStatus = (value) => {
setTemplateStatus(value);
setTemplatePage(1);
};
const changeInstanceKeyword = (value) => {
setInstanceKeyword(value);
setInstancePage(1);
};
const changeInstanceStatus = (value) => {
setInstanceStatus(value);
setInstancePage(1);
};
const resetTemplateFilters = () => {
setTemplateKeyword("");
setTemplateStatus("");
setTemplatePage(1);
};
const resetInstanceFilters = () => {
setInstanceKeyword("");
setInstanceStatus("");
setInstancePage(1);
};
const runAction = async (action, successMessage, submitter) => {
setLoadingAction(action);
try {
await submitter();
showToast(successMessage, "success");
} catch (error) {
showToast(error?.message || "操作失败", "error");
} finally {
setLoadingAction("");
}
};
return {
abilities,
activeTab,
changeInstanceKeyword,
changeInstanceStatus,
changeTemplateKeyword,
changeTemplateStatus,
closeInstanceDrawer,
closeTemplateDrawer,
editingTemplate,
instanceDrawerOpen,
instanceFilters: { keyword: instanceKeyword, status: instanceStatus },
instanceForm,
instancePage,
instances,
loadingAction,
loadingRegions,
openInstanceCreator,
openTemplateEditor,
publishInstance,
regionLabelMap,
regionOptions,
resetInstanceFilters,
resetTemplateFilters,
setActiveTab,
setInstanceForm,
setInstancePage,
setTemplateForm,
setTemplatePage,
submitInstance,
submitTemplate,
templateDrawerOpen,
templateFilters: { keyword: templateKeyword, status: templateStatus },
templateForm,
templateOptions,
templatePage,
templates,
toggleInstanceStatus,
};
}