This commit is contained in:
2025-03-21 22:40:00 +08:00
parent 7a4578c16d
commit 6e921e82fb
12 changed files with 146 additions and 19 deletions

View File

@@ -1,7 +1,7 @@
"use client";
import { useEffect, useState } from "react";
import DraggablePanel from "@/components/DraggablePanel";
import Logo from "@/components/Logo";
import Logo from "@/widgets/Logo";
import Preview from "@/components/Draggable/Preview";
import Draggable from "@/components/Draggable/Draggable";
import PreviewStore from "@/stores/previewStore";
@@ -94,7 +94,7 @@ export default function Home() {
y={item.y}
width={item.width}
height={item.height}
component={() => <Logo />}
componentsId={item.componentsId}
/>
))}
</DraggablePanel>

View File

@@ -1,5 +1,5 @@
import "@/app/globals.css";
import Logo from "@/components/Logo";
import Logo from "@/widgets/Logo";
export default function LoginPage() {
return (

View File

@@ -1,11 +1,19 @@
"use client";
import "@/app/globals.css";
import { useRef } from "react";
import { useRef, useState } from "react";
import ComponentsStore from "@/stores/componentStore";
import { componentsLibrary } from "./Draggable/Draggable";
import { useDynamicWidgets } from "@/hooks/useDynamicWidgets";
export default function ComponentPaletteDrawer() {
const checkboxRef = useRef<HTMLInputElement>(null);
const [componentsId, setComponentsId] = useState("logo");
const [data, setData] = useState("");
const {widgets} = useDynamicWidgets();
const onSubmit = (e: SubmitEvent) => {
e.preventDefault();
ComponentsStore.addComponent(componentsId, data && JSON.parse(data));
checkboxRef.current?.click();
};
return (
@@ -25,7 +33,7 @@ export default function ComponentPaletteDrawer() {
Add Component
</label>
</div>
<div className="drawer-side">
<div className="drawer-side z-50">
<label
htmlFor="ComponentPaletteDrawer"
aria-label="close sidebar"
@@ -37,7 +45,7 @@ export default function ComponentPaletteDrawer() {
</div>
<div className="p-4">
<div role="alert" className="alert alert-warning">
<div role="alert" className="alert alert-warning mb-4">
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6 shrink-0 stroke-current"
@@ -53,13 +61,31 @@ export default function ComponentPaletteDrawer() {
</svg>
<span>Warning: 内容处于开发阶段仅供参考</span>
</div>
<div className="resize-y border overflow-hidden max-w-full h-64 ">
{
componentsId && componentsLibrary[componentsId](data)
}
</div>
{
JSON.stringify(widgets)
}
<form onSubmit={onSubmit} className="w-full">
<fieldset className="fieldset w-full">
<legend className="fieldset-legend"></legend>
<select defaultValue="请选择小部件" className="select w-full">
<select
value={componentsId}
onChange={(e) => setComponentsId(e.target.value)}
className="select w-full"
>
<option disabled={true}></option>
<option></option>
<option>Logo</option>
{
widgets.map(item => {
return (<option value={item.id}>{item.name}</option>)
})
}
</select>
<p className="fieldset-label"></p>
</fieldset>
@@ -67,9 +93,11 @@ export default function ComponentPaletteDrawer() {
<fieldset className="fieldset w-full">
<legend className="fieldset-legend"></legend>
<textarea
onChange={(e) => setData(e.target.value)}
className="textarea h-24 w-full"
placeholder="请输入 JSON 格式的配置信息"
></textarea>
<p className="fieldset-label">JSON格式的配置信息</p>
</fieldset>
<button className="btn w-full btn-primary"></button>

View File

@@ -6,8 +6,8 @@ import { useDraggable } from "@dnd-kit/core";
import { useEffect, useRef, useState } from "react";
import { nearestMultiple } from "./utils";
import PreviewStore from "@/stores/previewStore";
import Logo from "../Logo";
import Text from "../Text";
import Logo from "../../widgets/Logo";
import Text from "../../widgets/Text";
export default function Draggable(props: DraggablePropsType) {
const targetRef = useRef<HTMLDivElement>(null);
@@ -78,7 +78,7 @@ export default function Draggable(props: DraggablePropsType) {
...style,
}} {...attributes}>
<button
className="btn absolute top-1 right-1 z-50 btn-square btn-soft"
className="btn absolute top-1 right-1 z-40 btn-square btn-soft"
{...listeners}
onMouseDown={(e) => e.stopPropagation()}
>

View File

@@ -28,12 +28,12 @@ export default function Preview(props: PreviewPropsType) {
<span
className="absolute text-2xl bg-emerald-500 border-emerald-500 text-white top-0 p-0.5"
>
{width * height === 0 || `${width} * ${height}`}
{width + height === 0 || `${width} * ${height}`}
</span>
<span
className="absolute text-2xl bg-emerald-500 border-emerald-500 text-white bottom-0 p-0.5"
>
{x * y === 0 || `${x}, ${y}`}
{x + y === 0 || `${x}, ${y}`}
</span>
</div>
);

View File

@@ -4,6 +4,6 @@
* @param [x=16] 推荐使用偶数
* @returns 最近的 x 的倍数
*/
export function nearestMultiple(n: number, x: number = 32): number {
export function nearestMultiple(n: number, x: number = 18): number {
return Math.floor((n + x/2) / x) * x;
}

View File

@@ -43,7 +43,7 @@ export default function DraggablePanel(props: DraggablePanelType) {
PreviewStore.clearPreview();
ComponentStore.changeComponent({
id: event?.active?.data?.current?.id,
component: event?.active?.data?.current?.component,
componentsId: event?.active?.data?.current?.componentsId,
x,
y,
width: nearestMultiple(width),

View File

@@ -0,0 +1,68 @@
import { useState, useCallback, useEffect } from 'react';
import dynamic from 'next/dynamic';
export interface WidgetModule {
id: string;
name: string;
version: string;
component: ReturnType<typeof dynamic>;
}
export const useDynamicWidgets = (): {
widgets: WidgetModule[];
refreshWidgets: () => Promise<void>;
} => {
const [widgets, setWidgets] = useState<WidgetModule[]>([]);
const loadWidgets = useCallback(async () => {
try {
// Webpack 的 require.context 匹配 widgets 目录
const context = require.context('../widgets', true, /\/index\.tsx$/);
console.log("[DEBUG] Matched files:", context.keys());
const modules = await Promise.all(
context.keys().map(async (key) => {
const folderName = key.split('/')[1];
// 动态导入元数据
console.log("[DEBUG] import files:", `../widgets/${folderName}/index.tsx`);
const meta = await import(`../widgets/${folderName}/index.tsx`);
// 创建动态组件(单独导入确保 Tree Shaking
const Component = dynamic(
() => import(`../widgets/${folderName}/index.tsx`),
{ ssr: false }
);
return {
id: meta.id,
name: meta.name,
version: meta.version,
component: Component
};
})
);
console.log(modules);
setWidgets(modules);
} catch (error) {
console.error('Failed to load widgets:', error);
setWidgets([]);
}
}, []);
// 初始化加载
useEffect(() => {
loadWidgets();
}, [loadWidgets]);
// 暴露刷新方法
const refreshWidgets = useCallback(async () => {
await loadWidgets();
}, [loadWidgets]);
return { widgets, refreshWidgets };
};

View File

@@ -1,7 +1,17 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
// next.config.js
const nextConfig:NextConfig = {
// 移除自定义 webpack 配置Next.js 已内置 TypeScript 支持
// 保留以下配置即可
experimental: {
externalDir: true, // 如果需要引用外部目录
},
webpack: (config) => {
// 保留其他必要配置
return config;
},
};
export default nextConfig;

View File

@@ -0,0 +1,15 @@
"use client";
import "@/app/globals.css";
export const id = "logo";
export const name = "Logo";
export const version = "1.0.0";
export default function Logo() {
return (
<div className="flex h-full w-full items-center justify-center text-5xl bg-base-200 font-bold">
<span className="whitespace-nowrap text-primary">
NEXUSHUB
</span>
</div>
);
}

View File

@@ -1,6 +1,9 @@
"use client";
import "@/app/globals.css";
export const id = "logo";
export const name = "Logo";
export const version = "1.0.0";
export default function Logo() {
return (
<div className="flex h-full w-full items-center justify-center text-5xl bg-base-200 font-bold">

View File

@@ -1,6 +1,9 @@
import React, { CSSProperties } from 'react';
import { DraggablePropsType } from '../Draggable/Draggable';
import { DraggablePropsType } from '../../components/Draggable/Draggable';
export const id = "text";
export const name = "文字";
export const version = "1.0.0";
type FontData = {
content: string;
fontSize?: number; // 字体大小px