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, }; }