diff --git a/backend/src/database/relational/postgresql/init.sql b/backend/src/database/relational/postgresql/init.sql
index 7a98a95..5ad6285 100644
--- a/backend/src/database/relational/postgresql/init.sql
+++ b/backend/src/database/relational/postgresql/init.sql
@@ -7,11 +7,11 @@ CREATE EXTENSION IF NOT EXISTS pgcrypto;
--- 用户权限枚举
CREATE TYPE privilege_level AS ENUM ('visitor', 'contributor', 'administrators');
--- 用户信息表
-CREATE TABLE person
+CREATE TABLE persons
(
person_name VARCHAR(100) PRIMARY KEY, --- 用户名
person_email VARCHAR(255) UNIQUE NOT NULL, --- 用户邮箱
- person_picture VARCHAR(255), --- 用户头像
+ person_icon VARCHAR(255), --- 用户头像
person_password VARCHAR(255) NOT NULL, --- 用户密码
person_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, --- 用户创建时间
person_updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, --- 用户更新时间
@@ -28,7 +28,7 @@ CREATE TABLE pages
page_id SERIAL PRIMARY KEY, --- 独立页面唯一id主键
page_meta_keywords VARCHAR(255) NOT NULL, ---页面mata关键字
page_meta_description VARCHAR(255) NOT NULL, ---页面mata描述
- post_title VARCHAR(255) NOT NULL, --- 文章标题
+ page_title VARCHAR(255) NOT NULL, --- 文章标题
page_content TEXT NOT NULL, --- 独立页面内容
page_mould VARCHAR(50), --- 独立页面模板名称
page_fields JSON, --- 自定义字段
@@ -47,8 +47,8 @@ CREATE TABLE posts
post_content TEXT NOT NULL, --- 文章内容
post_status publication_status DEFAULT 'draft', --- 文章状态
post_editor BOOLEAN DEFAULT FALSE, --- 文章是否编辑未保存
- posts_unsaved_content TEXT, --- 未保存的文章
- posts_path VARCHAR(255), --- 文章路径
+ post_unsaved_content TEXT, --- 未保存的文章
+ post_path VARCHAR(255), --- 文章路径
post_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, --- 文章创建时间
post_updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, --- 文章更新时间
post_published_at TIMESTAMP, --- 文章发布时间
@@ -58,7 +58,7 @@ CREATE TABLE posts
CREATE TABLE tags
(
tag_name VARCHAR(50) PRIMARY KEY CHECK (LOWER(tag_name) = tag_name), --- 标签名称主键
- tag_picture VARCHAR(255) --- 标签图标
+ tag_icon VARCHAR(255) --- 标签图标
);
--- 文章与标签的关系表
CREATE TABLE post_tags
diff --git a/frontend/app/env.d.ts b/frontend/app/env.d.ts
index 66cb48d..4553dcc 100644
--- a/frontend/app/env.d.ts
+++ b/frontend/app/env.d.ts
@@ -7,10 +7,10 @@
///
interface ImportMetaEnv {
- readonly VITE_APP_TYPE: boolean; //用于判断是动态博客还是静态博客
readonly VITE_APP_API: string; // 用于访问API的基础URL
readonly VITE_THEME_PATH: string; // 存储主题文件的目录路径
readonly VITE_CONTENT_PATH: string; //mark文章存储的位置
+ readonly VITE_CONTENT_STATIC_PATH: string; //导出文章静态存储的位置
readonly VITE_PLUGINS_PATH: string; // 存储插件文件的目录路径
readonly VITE_ASSETS_PATH: string; // 存储静态资源的目录路径
}
diff --git a/frontend/app/root.tsx b/frontend/app/root.tsx
index 217994e..8913fdb 100644
--- a/frontend/app/root.tsx
+++ b/frontend/app/root.tsx
@@ -1,40 +1,25 @@
-import {
- Links,
- Meta,
- Outlet,
- Scripts,
- ScrollRestoration,
-} from "@remix-run/react";
-import type { LinksFunction } from "@remix-run/node";
-
+import { Meta, Outlet, Scripts, ScrollRestoration } from "@remix-run/react";
+import type { LoaderFunction } from "@remix-run/node";
+import { useLoaderData } from "@remix-run/react";
+import { ThemeProvider } from "hooks/themeContext";
+import { createContext, useContext, ReactNode } from 'react';
import "./tailwind.css";
-export const links: LinksFunction = () => [
- { rel: "preconnect", href: "https://fonts.googleapis.com" },
- {
- rel: "preconnect",
- href: "https://fonts.gstatic.com",
- crossOrigin: "anonymous",
- },
- {
- rel: "stylesheet",
- href: "https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap",
- },
-];
-
export function Layout({ children }: { children: React.ReactNode }) {
- console.log(import.meta.env.VITE_THEME_NAME);
-
return (
-
+
-
- {children}
+
+ {children}
+
@@ -43,5 +28,9 @@ export function Layout({ children }: { children: React.ReactNode }) {
}
export default function App() {
- return ;
-}
+ return (
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/hooks/themeContext.tsx b/frontend/hooks/themeContext.tsx
new file mode 100644
index 0000000..004567f
--- /dev/null
+++ b/frontend/hooks/themeContext.tsx
@@ -0,0 +1,23 @@
+// service/theme/themeContext.tsx
+import { createContext, useContext, ReactNode } from 'react';
+import { ThemeService } from 'service/themeService';
+
+const ThemeContext = createContext(undefined);
+
+export function ThemeProvider({ children }: { children: ReactNode }) {
+ const themeService = ThemeService.getInstance();
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function useTheme(): ThemeService {
+ const context = useContext(ThemeContext);
+ if (context === undefined) {
+ throw new Error('useTheme must be used within a ThemeProvider');
+ }
+ return context;
+}
\ No newline at end of file
diff --git a/frontend/service/extensionService.ts b/frontend/service/extensionService.ts
new file mode 100644
index 0000000..e060e6e
--- /dev/null
+++ b/frontend/service/extensionService.ts
@@ -0,0 +1,84 @@
+// File path: service/extensionService.ts
+
+/**
+ * ExtensionManage 是一个单例类,用于管理扩展的实例。
+ * 提供注册、触发和移除插件扩展的功能。
+ */
+import { ExtensionType } from "types/extensionType";
+import React from "react";
+
+class ExtensionManage {
+ /** 存储扩展的映射,键为扩展名称,值为插件名称和扩展的集合 */
+ private extensions: Map> = new Map();
+ /** ExtensionManage 的唯一实例 */
+ private static instance: ExtensionManage;
+
+ /** 私有构造函数,防止外部实例化 */
+ private constructor() {}
+
+ /**
+ * 获取 ExtensionManage 的唯一实例。
+ * @returns {ExtensionManage} 返回 ExtensionManage 的唯一实例。
+ */
+ public static getInstance(): ExtensionManage {
+ if (!this.instance) {
+ this.instance = new ExtensionManage();
+ }
+ return this.instance;
+ }
+
+ /** 注册扩展 */
+ private register(extensionName: string, pluginName: string, extension: ExtensionType) {
+ const handlers = this.extensions.get(extensionName) || new Set();
+ handlers.add({ pluginName, extension });
+ this.extensions.set(extensionName, handlers);
+ }
+
+ /** 执行扩展方法 */
+ private executeExtensionMethod(extensionName: string, method: keyof ExtensionType, ...args: any[]): Set {
+ const result = new Set();
+ const handlers = this.extensions.get(extensionName);
+
+ if (handlers) {
+ handlers.forEach(({ extension }) => {
+ const methodFunction = extension[method];
+ if (methodFunction) {
+ try {
+ const value = methodFunction(...args);
+ if (value && (typeof value === 'string' || React.isValidElement(value))) {
+ result.add(value as T);
+ }
+ } catch (error) {
+ console.error(`Error executing hook ${extensionName}:`, error);
+ }
+ }
+ });
+ }
+ return result;
+ }
+
+ /** 触发扩展的动作 */
+ private triggerAction(extensionName: string, ...args: any[]): void {
+ this.executeExtensionMethod(extensionName, 'action', ...args);
+ }
+
+ /** 触发扩展的组件 */
+ private triggerComponent(extensionName: string, ...args: any[]): Set {
+ return this.executeExtensionMethod(extensionName, 'component', ...args);
+ }
+
+ /** 触发扩展的文本 */
+ private triggerText(extensionName: string, ...args: any[]): Set {
+ return this.executeExtensionMethod(extensionName, 'text', ...args);
+ }
+
+ /** 移除指定插件的扩展 */
+ private removePluginExtensions(pluginName: string) {
+ this.extensions.forEach((handlers, extensionName) => {
+ const newHandlers = new Set(
+ Array.from(handlers).filter(handler => handler.pluginName !== pluginName)
+ );
+ this.extensions.set(extensionName, newHandlers);
+ });
+ }
+}
diff --git a/frontend/service/pluginService.ts b/frontend/service/pluginService.ts
new file mode 100644
index 0000000..742fb84
--- /dev/null
+++ b/frontend/service/pluginService.ts
@@ -0,0 +1,24 @@
+import { PluginConfig ,PluginType ,PluginConfiguration} from "types/pluginType";
+
+
+
+export class PluginService {
+ private static pluginInstance: PluginService | null = null; // 单例实例
+ private pluginComponents: Map> = new Map(); // 插件组件缓存
+ private constructor (){};
+
+ public static getInstance(): PluginService {
+ if (!this.pluginInstance) {
+ this.pluginInstance = new PluginService();
+ }
+ return this.pluginInstance;
+ }
+
+
+}
\ No newline at end of file
diff --git a/frontend/service/themeService.ts b/frontend/service/themeService.ts
new file mode 100644
index 0000000..e684b2f
--- /dev/null
+++ b/frontend/service/themeService.ts
@@ -0,0 +1,52 @@
+// service/theme/themeService.ts
+import type { ThemeConfig } from 'types/themeType';
+
+export class ThemeService {
+ private static themeInstance: ThemeService; // 单例实例
+ private themeConfig: ThemeConfig | null = null; // 当前主题配置
+ private themeComponents: Map = new Map(); // 主题组件缓存
+
+ private constructor() { } // 私有构造函数,防止外部实例化
+
+ // 获取单例实例
+ public static getInstance(): ThemeService {
+ if (!ThemeService.themeInstance) {
+ ThemeService.themeInstance = new ThemeService();
+ }
+ return ThemeService.themeInstance;
+ }
+
+ // 加载主题
+ async loadTheme(themeConfig: ThemeConfig): Promise {
+ this.themeConfig = themeConfig; // 设置当前主题
+ await this.loadThemeComponents(themeConfig); // 加载主题组件
+ }
+
+ // 加载主题组件
+ private async loadThemeComponents(config:ThemeConfig): Promise {
+ // 清除现有组件缓存
+ this.themeComponents.clear();
+
+ // 动态导入主题入口组件
+ const entryComponent = await import(config.entry);
+ this.themeComponents.set('entry', entryComponent.default); // 缓存入口路径
+
+ // 加载所有模板组件
+ for (const [key, template] of config.templates.entries()) {
+ const component = await import(template.path);
+ this.themeComponents.set(key, component.default); // 缓存模板组件
+ }
+ }
+
+ // 获取指定模板名称的组件
+ getComponent(templateName: string): React.ComponentType | null {
+ return this.themeComponents.get(templateName) || null; // 返回组件或null
+ }
+
+ // 获取当前主题配置
+ getCurrentTheme(): ThemeConfig | null {
+ return this.currentTheme; // 返回当前主题配置
+ }
+}
+
+
diff --git a/frontend/theme/default/theme.config.ts b/frontend/theme/default/theme.config.ts
new file mode 100644
index 0000000..d8be258
--- /dev/null
+++ b/frontend/theme/default/theme.config.ts
@@ -0,0 +1,25 @@
+import { ThemeConfig } from "types/themeType";
+
+export const themeConfig: ThemeConfig = {
+ name: 'default',
+ displayName: '默认主题',
+ version: '1.0.0',
+ description: '一个简约风格的博客主题',
+ author: 'lsy',
+ entry: './index.tsx',
+ templates: new Map([
+ ['page', {
+ path: './templates/page',
+ name: '文章列表模板',
+ description: '博客首页展示模板'
+ }],
+ ]),
+
+ settingsSchema: undefined,
+ routes: {
+ post: "",
+ tag: "",
+ category: "",
+ page: ""
+ }
+}
\ No newline at end of file
diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json
index 37f8077..7d099cd 100644
--- a/frontend/tsconfig.json
+++ b/frontend/tsconfig.json
@@ -25,6 +25,8 @@
"paths": {
"theme/*":["./theme/*"],
"types/*":["./types/*"],
+ "service/*":["./service/*"],
+ "hooks/*":["./hooks/*"],
},
// Vite takes care of building everything, not tsc.
diff --git a/frontend/types/extensionType.ts b/frontend/types/extensionType.ts
new file mode 100644
index 0000000..3f2a3ae
--- /dev/null
+++ b/frontend/types/extensionType.ts
@@ -0,0 +1,20 @@
+/**
+ * File path: types/extensionType.ts
+ *
+ * 该文件定义了扩展类型接口 ExtensionType,包含可选的操作、组件和文本生成函数。
+ *
+ * 接口属性说明:
+ * - action: 可选的操作函数,接受任意参数并返回 void。
+ * - component: 可选的组件函数,接受任意参数并返回一个 React 组件。
+ * - text: 可选的文本生成函数,接受任意参数并返回一个字符串。
+ */
+export interface ExtensionType {
+ /** 可选的操作函数,接受任意参数并返回 void */
+ action?: (...args: any[]) => void;
+
+ /** 可选的组件函数,接受任意参数并返回一个 React 组件 */
+ component?: (...args: any[]) => React.FC;
+
+ /** 可选的文本生成函数,接受任意参数并返回一个字符串 */
+ text?: (...args: any[]) => string;
+}
diff --git a/frontend/types/plugin.ts b/frontend/types/plugin.ts
deleted file mode 100644
index 3fcd4b4..0000000
--- a/frontend/types/plugin.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-// File path: types/plugin.ts
-
-/**
- * 插件配置接口
- *
- * 该接口定义了插件的基本配置,包括插件的名称、版本、描述、作者等信息。
- * 还包括插件的生命周期钩子和依赖项的配置。
- */
-export interface PluginConfig {
- name: string; // 插件名称
- version: string; // 插件版本
- displayName: string; // 插件显示名称
- description?: string; // 插件描述(可选)
- author?: string; // 插件作者(可选)
- enabled: boolean; // 插件是否启用
- icon?: string; // 插件图标URL(可选)
- managePath?: string; // 插件管理页面路径(可选)
- entry: string; // 插件入口组件路径
- // 主题配置
- settingsSchema?: {
- type: string; // 配置类型
- properties: Record;
- };
- // 依赖
- dependencies?: {
- plugins?: string[]; // 依赖的插件列表(可选)
- themes?: string[]; // 依赖的主题列表(可选)
- };
- // 插件生命周期钩子
- hooks?: {
- onInstall?: string; // 安装时调用的钩子(可选)
- onUninstall?: string; // 卸载时调用的钩子(可选)
- onEnable?: string; // 启用时调用的钩子(可选)
- onDisable?: string; // 禁用时调用的钩子(可选)
- };
-}
\ No newline at end of file
diff --git a/frontend/types/pluginType.ts b/frontend/types/pluginType.ts
new file mode 100644
index 0000000..c64e537
--- /dev/null
+++ b/frontend/types/pluginType.ts
@@ -0,0 +1,48 @@
+// File path: types/pluginType.ts
+
+/**
+ * 插件配置接口
+ *
+ * 该接口定义了插件的基本配置,包括插件的名称、版本、描述、作者等信息。
+ * 还包括插件的生命周期钩子和依赖项的配置。
+ */
+export interface PluginConfig {
+ name: string; // 插件名称
+ version: string; // 插件版本
+ displayName: string; // 插件显示名称
+ description?: string; // 插件描述(可选)
+ author?: string; // 插件作者(可选)
+ enabled: boolean; // 插件是否启用
+ icon?: string; // 插件图标URL(可选)
+ managePath?: string; // 插件管理页面路径(可选)
+ configuration?: PluginConfiguration; // 插件配置
+ dependencies?: {
+ plugins?: string[]; // 依赖的插件列表(可选)
+ themes?: string[]; // 依赖的主题列表(可选)
+ };
+ hooks?: {
+ onInstall?: (context: any) => {}; // 安装时调用的钩子(可选)
+ onUninstall?: (context: any) => {}; // 卸载时调用的钩子(可选)
+ onEnable?: (context: any) => {}; // 启用时调用的钩子(可选)
+ onDisable?: (context: any) => {}; // 禁用时调用的钩子(可选)
+ };
+ routs: Set<{
+ description?: string; // 路由描述(可选)
+ path: string; // 路由路径
+ }>;
+}
+
+/**
+ * 插件配置接口
+ *
+ * 该接口定义了插件的配置类型及其属性。
+ */
+export interface PluginConfiguration {
+ type: string; // 配置类型
+ properties: Record;
+}
\ No newline at end of file
diff --git a/frontend/types/templateType.ts b/frontend/types/templateType.ts
new file mode 100644
index 0000000..9c71c2e
--- /dev/null
+++ b/frontend/types/templateType.ts
@@ -0,0 +1,34 @@
+// File path: types/templateType.ts
+/**
+ * 插件配置接口
+ *
+ * 该接口定义了模板的基本配置,包括依赖项、钩子和页面渲染函数。
+ */
+import React from "react";
+import { ExtensionType } from "types/extensionType";
+
+export interface TemplateConfig {
+ /**
+ * 依赖项配置
+ *
+ * 记录每个依赖字段的名称、描述和是否必填。
+ */
+ dependencies: Record;
+
+
+ extensions?: Record;
+
+ /**
+ * 页面渲染函数
+ *
+ * 接受参数并返回一个 React 组件。
+ */
+ page(params: Map): React.FC;
+}
diff --git a/frontend/types/theme.ts b/frontend/types/themeType.ts
similarity index 63%
rename from frontend/types/theme.ts
rename to frontend/types/themeType.ts
index 2a06256..5a661e4 100644
--- a/frontend/types/theme.ts
+++ b/frontend/types/themeType.ts
@@ -1,4 +1,4 @@
-// types/theme.ts
+// File path: types/themeType.ts
/**
* 主题配置和模板接口定义文件
* 该文件包含主题配置接口和主题模板接口的定义,用于主题管理和渲染。
@@ -6,17 +6,18 @@
/**
* 主题配置接口
+ * 定义主题的基本信息、模板、全局配置、依赖、钩子和路由。
*/
export interface ThemeConfig {
- name: string; // 主题的唯一标识符
+ name: string; // 主题的名称
displayName: string; // 主题的显示名称
version: string; // 主题的版本号
description?: string; // 主题的描述信息
author?: string; // 主题的作者信息
- entry: string; // 主题的入口组件路径
+ entry?: string; // 主题的入口路径
templates: Map; // 主题模板的映射表
/** 主题全局配置 */
- globalSettings: {
+ globalSettings?: {
layout?: string; // 主题的布局配置
css?: string; // 主题的CSS配置
};
@@ -34,22 +35,23 @@ export interface ThemeConfig {
};
/** 钩子 */
hooks?: {
- beforeRender?: string; // 渲染前执行的钩子
- afterRender?: string; // 渲染后执行的钩子
- onActivate?: string; // 主题激活时执行的钩子
- onDeactivate?: string; // 主题停用时执行的钩子
+ onActivate?: () => {}; // 主题激活时执行的钩子
+ onDeactivate?: () => {}; // 主题停用时执行的钩子
};
/** 路由 */
- routes:{
- post:string; // 文章使用的模板
- tag:string; // 标签使用的模板
- category:string; // 分类使用的模板
- page:string; // 独立页面模板路径
+ routes: {
+ index: string; // 首页使用的模板
+ post: string; // 文章使用的模板
+ tag: string; // 标签使用的模板
+ category: string; // 分类使用的模板
+ error: string; // 错误页面用的模板
+ page: Map; // 独立页面模板
}
}
/**
* 主题模板接口
+ * 定义主题模板的基本信息,包括路径、名称和描述。
*/
export interface ThemeTemplate {
path: string; // 模板文件的路径