248 lines
8.6 KiB
JavaScript
248 lines
8.6 KiB
JavaScript
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,
|
||
};
|
||
}
|