feat(web): 用户工作台 - 产品化壳层、内容库布局与积分入口

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
ddCat
2026-09-16 22:59:45 +08:00
parent f52702b70f
commit 969e2831b2
46 changed files with 5355 additions and 944 deletions

View File

@@ -0,0 +1,31 @@
import { useEffect } from "react";
/** 一次点击的触觉式反馈,不表达请求成功,也不接管业务回调或画布事件。 */
export function useWorkspaceButtonFeedback(enabled: boolean) {
useEffect(() => {
if (!enabled) return;
const reduced = window.matchMedia("(prefers-reduced-motion: reduce)");
const animations = new Set<Animation>();
const clicked = (event: MouseEvent) => {
if (reduced.matches || !(event.target instanceof Element)) return;
const button = event.target.closest<HTMLElement>('button, [role="button"]');
if (!button || button.matches(':disabled, [aria-disabled="true"], .ant-btn-loading') || !button.closest(".app-product-workspace, .ant-popover, .ant-dropdown, .ant-modal, .ant-drawer")) return;
if (typeof button.animate === "function") {
const press = button.animate([{ scale: ".98" }, { scale: "1" }], { duration: 180, easing: "cubic-bezier(.16,1,.3,1)" });
animations.add(press);
press.onfinish = press.oncancel = () => animations.delete(press);
}
const icon = button.querySelector<SVGElement>("svg:not(.animate-spin)");
if (!icon || typeof icon.animate !== "function" || getComputedStyle(icon).animationName !== "none") return;
const animation = icon.animate([
{ translate: "0 0", scale: "1" },
{ translate: "0 -2px", scale: ".88", offset: .35 },
{ translate: "0 0", scale: "1" },
], { duration: 360, easing: "cubic-bezier(.16,1,.3,1)" });
animations.add(animation);
animation.onfinish = animation.oncancel = () => animations.delete(animation);
};
document.addEventListener("click", clicked, true);
return () => { document.removeEventListener("click", clicked, true); animations.forEach((animation) => animation.cancel()); };
}, [enabled]);
}