diff --git a/app/(presentation)/presentation/[...slug]/page.jsx b/app/(presentation)/presentation/[...slug]/page.jsx
index 2dbb2f3..307c7e7 100644
--- a/app/(presentation)/presentation/[...slug]/page.jsx
+++ b/app/(presentation)/presentation/[...slug]/page.jsx
@@ -3,27 +3,41 @@ import { getPageData } from "@/utils/data";
import { getLocalPageData } from "@/data/presentation";
import Presentation from "@/components/presentation/Presentation";
-export default async function PresentationPage({ params }) {
+const baseSlug = 'presentation';
+
+const LoadingSpinner = () => (
+
+);
+
+export default async function Page({ params, searchParams }) {
const resolvedParams = await params;
- const slugArr = resolvedParams.slug;
+ const slug = resolvedParams.slug || [];
+ const slugArr = [baseSlug, ...(Array.isArray(slug) ? slug : [slug])];
- // 优先从本地markdown文件获取数据
- const localData = await getLocalPageData(slugArr);
-
- if (localData.data) {
- // 如果本地文件存在,直接使用本地数据
- return ;
- }
- // 如果本地文件不存在,从API获取数据
const { data, error } = await getPageData({
slug_list: slugArr,
- downloadFiles: true
+ downloadFiles: true,
});
- if (error || !data) {
+ if (error) {
notFound();
}
- return ;
-}
+ if (data) {
+ // 优先从本地markdown文件获取数据
+ const localData = await getLocalPageData(slugArr);
+
+ if (localData.data) {
+ // 如果本地文件存在,直接使用本地数据
+ return ;
+ }
+
+ // 如果本地文件不存在,使用API数据
+ return ;
+ } else {
+ notFound();
+ }
+}
diff --git a/app/(presentation)/presentation/page.jsx b/app/(presentation)/presentation/page.jsx
new file mode 100644
index 0000000..43b371c
--- /dev/null
+++ b/app/(presentation)/presentation/page.jsx
@@ -0,0 +1,118 @@
+import Banner from "@/components/banner/Banner";
+import Category from "@/components/sidebar/Category";
+import { getSiteSettings } from "@/utils/data";
+import { notFound } from 'next/navigation';
+import DynamicListPage from "@/components/common/DynamicListPage";
+import { Suspense } from 'react';
+import { getPageData } from "@/utils/data";
+import { getLocalPageData } from "@/data/presentation";
+import Presentation from "@/components/presentation/Presentation";
+
+const baseSlug = 'presentation';
+
+const LoadingSpinner = () => (
+
+);
+
+export const revalidate = 3600;
+
+export async function generateMetadata({ params }) {
+ const resolvedParams = await params;
+ const slug = resolvedParams.slug || [];
+ const slugArr = [baseSlug, ...(Array.isArray(slug) ? slug : [slug])];
+ const { data, error, page_info } = await getPageData({ slug_list: slugArr });
+ const siteSettings = await getSiteSettings();
+ const siteTitle = siteSettings.site_title || '';
+ const siteName = siteSettings.site_name || '';
+ const siteNameInPageTitles = siteSettings.site_name_in_page_titles || 'None';
+
+ let title = '';
+ if (error) {
+ title = error.title || 'Page Error';
+ return {
+ title,
+ description: error.message || '',
+ };
+ }
+ if (Array.isArray(data) && page_info) {
+ title = page_info.meta_title || page_info.title || '';
+ if (siteName && title) {
+ if (siteNameInPageTitles === 'After') {
+ title = `${title} - ${siteName}`;
+ } else if (siteNameInPageTitles === 'Before') {
+ title = `${siteName} - ${title}`;
+ }
+ }
+ return {
+ title,
+ description: page_info.meta_description || '',
+ };
+ }
+ title = data?.meta_title || data?.title || '';
+ if (siteName && title) {
+ if (siteNameInPageTitles === 'After') {
+ title = `${title} - ${siteName}`;
+ } else if (siteNameInPageTitles === 'Before') {
+ title = `${siteName} - ${title}`;
+ }
+ }
+ return {
+ title,
+ description: data?.meta_description || '',
+ };
+}
+
+export default async function Page({ params, searchParams }) {
+ const resolvedParams = await params;
+ const slug = resolvedParams.slug || [];
+ const slugArr = [baseSlug, ...(Array.isArray(slug) ? slug : [slug])];
+
+ const siteSettings = await getSiteSettings();
+ const pageSize = Number(siteSettings.page_size) || 12;
+
+ const { data, error, total } = await getPageData({
+ slug_list: slugArr,
+ page: 1,
+ page_size: pageSize,
+ downloadFiles: true,
+ });
+
+ if (error) {
+ notFound();
+ }
+ const bannerComponentName = 'Banner-' + baseSlug;
+ const categoryComponentName = 'Category-' + baseSlug;
+ if (Array.isArray(data)) {
+ const currentPath = '/' + slugArr.join('/');
+ const listColumns = 4;
+ return (
+ <>
+
+
+ >
+ );
+ } else {
+ notFound();
+ }
+}
diff --git a/app/(website)/globals.css b/app/(website)/globals.css
index 1993a49..1225778 100644
--- a/app/(website)/globals.css
+++ b/app/(website)/globals.css
@@ -1468,6 +1468,8 @@ mark.doc {
@apply inline-block;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
+ background: linear-gradient(45deg, #667eea 0%, #764ba2 100%);
+ background-clip: text;
}
.text-gradient em {
@apply tracking-[normal] px-[0.05em];
@@ -1493,6 +1495,56 @@ mark.doc {
.text-gradient.text-line.gradient-7:before {
@apply bg-[#0093e9];
}
+
+/* 为gradient-1到gradient-7添加渐变背景 */
+.text-gradient.gradient-1 {
+ background: linear-gradient(45deg, #f857a6 0%, #ff6b9d 100%);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+}
+
+.text-gradient.gradient-2 {
+ background: linear-gradient(45deg, #f5b161 0%, #ffa726 100%);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+}
+
+.text-gradient.gradient-3 {
+ background: linear-gradient(45deg, #fbda61 0%, #ffeb3b 100%);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+}
+
+.text-gradient.gradient-4 {
+ background: linear-gradient(45deg, #9040db 0%, #ab47bc 100%);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+}
+
+.text-gradient.gradient-5 {
+ background: linear-gradient(45deg, #4158d0 0%, #667eea 100%);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+}
+
+.text-gradient.gradient-6 {
+ background: linear-gradient(45deg, #08aeea 0%, #2af598 100%);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+}
+
+.text-gradient.gradient-7 {
+ background: linear-gradient(45deg, #0093e9 0%, #80d0c7 100%);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+}
input,
select,
textarea {
diff --git a/components/presentation/Presentation.jsx b/components/presentation/Presentation.jsx
index 73e0e05..f29bfc4 100644
--- a/components/presentation/Presentation.jsx
+++ b/components/presentation/Presentation.jsx
@@ -91,29 +91,56 @@ export default function Presentation({ data }) {
}, []);
const parseContentSlides = (data) => {
+ // 移除frontmatter部分,只处理内容部分
+ let content = data.content;
+
+ // 如果内容以---开头,说明有frontmatter,需要移除
+ if (content.startsWith('---')) {
+ const frontmatterEnd = content.indexOf('---', 3);
+ if (frontmatterEnd !== -1) {
+ content = content.substring(frontmatterEnd + 3).trim();
+ }
+ }
+
const slideSeparators = /^---$/gm;
- const sections = data.content.split(slideSeparators);
+ const sections = content.split(slideSeparators);
return sections.map((section, index) => {
const trimmedSection = section.trim();
if (!trimmedSection) return '';
- const backgroundMatch = trimmedSection.match(//);
+ // 匹配背景和类属性
+ const slideCommentMatch = trimmedSection.match(//);
let backgroundAttr = '';
- let contentWithoutBackground = trimmedSection;
+ let classAttr = '';
+ let contentWithoutComment = trimmedSection;
- if (backgroundMatch) {
- backgroundAttr = ` data-background="${backgroundMatch[1]}"`;
- contentWithoutBackground = trimmedSection.replace(//g, '');
+ if (slideCommentMatch) {
+ const slideAttrs = slideCommentMatch[1];
+
+ // 提取data-background属性
+ const backgroundMatch = slideAttrs.match(/data-background="([^"]+)"/);
+ if (backgroundMatch) {
+ backgroundAttr = ` data-background="${backgroundMatch[1]}"`;
+ }
+
+ // 提取class属性
+ const classMatch = slideAttrs.match(/class="([^"]+)"/);
+ if (classMatch) {
+ classAttr = ` class="${classMatch[1]}"`;
+ }
+
+ // 移除整个slide注释
+ contentWithoutComment = trimmedSection.replace(//g, '');
}
- const htmlContent = marked(contentWithoutBackground);
+ const htmlContent = marked(contentWithoutComment);
if (index === 0 && !htmlContent.includes('') && data.title) {
- return `${data.title}
${htmlContent}`;
+ return `${data.title}
${htmlContent}`;
}
- return ``;
+ return ``;
}).join('');
};
diff --git a/components/presentation/themes/jingrow.css b/components/presentation/themes/jingrow.css
index 1299456..4770e7d 100644
--- a/components/presentation/themes/jingrow.css
+++ b/components/presentation/themes/jingrow.css
@@ -87,22 +87,7 @@ section.has-dark-background, section.has-dark-background h1, section.has-dark-ba
/*********************************************
* HEADERS
*********************************************/
-.reveal h1,
-.reveal h2,
-.reveal h3,
-.reveal h4,
-.reveal h5,
-.reveal h6 {
- margin: var(--r-heading-margin);
- color: var(--r-heading-color);
- font-family: var(--r-heading-font);
- font-weight: var(--r-heading-font-weight);
- line-height: var(--r-heading-line-height);
- letter-spacing: var(--r-heading-letter-spacing);
- text-transform: var(--r-heading-text-transform);
- text-shadow: var(--r-heading-text-shadow);
- word-wrap: break-word;
-}
+
.reveal h1 {
font-size: var(--r-heading1-size);
@@ -376,4 +361,151 @@ section.has-dark-background, section.has-dark-background h1, section.has-dark-ba
.backgrounds {
background-color: var(--r-background-color);
}
+}
+
+/*********************************************
+ * 字体颜色类
+ *********************************************/
+/* 白色字体 - 适用于深色背景 */
+.text-white {
+ color: #ffffff !important;
+}
+
+/* 黑色字体 - 适用于浅色背景 */
+.text-black {
+ color: #000000 !important;
+}
+
+/* 深灰色字体 */
+.text-gray-dark {
+ color: #333333 !important;
+}
+
+/* 中灰色字体 */
+.text-gray {
+ color: #666666 !important;
+}
+
+/* 浅灰色字体 */
+.text-gray-light {
+ color: #999999 !important;
+}
+
+/* 红色字体 */
+.text-red {
+ color: #dc3545 !important;
+}
+
+/* 绿色字体 */
+.text-green {
+ color: #28a745 !important;
+}
+
+/* 蓝色字体 */
+.text-blue {
+ color: #007bff !important;
+}
+
+/* 黄色字体 */
+.text-yellow {
+ color: #ffc107 !important;
+}
+
+/* 橙色字体 */
+.text-orange {
+ color: #fd7e14 !important;
+}
+
+/* 紫色字体 */
+.text-purple {
+ color: #6f42c1 !important;
+}
+
+/* 青色字体 */
+.text-cyan {
+ color: #17a2b8 !important;
+}
+
+/* 粉色字体 */
+.text-pink {
+ color: #e83e8c !important;
+}
+
+/* 棕色字体 */
+.text-brown {
+ color: #795548 !important;
+}
+
+/* 金色字体 */
+.text-gold {
+ color: #ffd700 !important;
+}
+
+/* 银色字体 */
+.text-silver {
+ color: #c0c0c0 !important;
+}
+
+/* 半透明白色 - 适用于深色背景的次要文字 */
+.text-white-75 {
+ color: rgba(255, 255, 255, 0.75) !important;
+}
+
+.text-white-50 {
+ color: rgba(255, 255, 255, 0.5) !important;
+}
+
+/* 半透明黑色 - 适用于浅色背景的次要文字 */
+.text-black-75 {
+ color: rgba(0, 0, 0, 0.75) !important;
+}
+
+.text-black-50 {
+ color: rgba(0, 0, 0, 0.5) !important;
+}
+
+/* 渐变文字效果 */
+.text-gradient {
+ background: linear-gradient(45deg, #667eea 0%, #764ba2 100%);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+}
+
+.text-gradient-primary {
+ background: linear-gradient(45deg, #007bff 0%, #0056b3 100%);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+}
+
+.text-gradient-success {
+ background: linear-gradient(45deg, #28a745 0%, #1e7e34 100%);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+}
+
+/* 文字阴影效果 - 增强可读性 */
+.text-shadow {
+ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
+}
+
+.text-shadow-light {
+ text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3);
+}
+
+.text-shadow-white {
+ text-shadow: 2px 2px 4px rgba(255, 255, 255, 0.5);
+}
+
+/* 高对比度文字类 */
+.text-high-contrast {
+ color: #000000 !important;
+ text-shadow: 1px 1px 2px rgba(255, 255, 255, 0.8);
+}
+
+.text-high-contrast-inverse {
+ color: #ffffff !important;
+ text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.8);
}
\ No newline at end of file
diff --git a/components/products/ProductImageGallery.jsx b/components/products/ProductImageGallery.jsx
new file mode 100644
index 0000000..cb466d7
--- /dev/null
+++ b/components/products/ProductImageGallery.jsx
@@ -0,0 +1,346 @@
+'use client';
+
+import { useState, useEffect, useRef, useCallback, useMemo } from 'react';
+import { Swiper, SwiperSlide } from "swiper/react";
+import { Navigation, Pagination, Thumbs, FreeMode } from "swiper/modules";
+
+import "swiper/css";
+import "swiper/css/navigation";
+import "swiper/css/pagination";
+import "swiper/css/thumbs";
+import "swiper/css/free-mode";
+
+const ProductImageGallery = ({ data }) => {
+ const [currentImageIndex, setCurrentImageIndex] = useState(0);
+ const [thumbsSwiper, setThumbsSwiper] = useState(null);
+ const thumbsSwiperRef = useRef(null);
+
+ if (!data?.attachments || data.attachments.length === 0) {
+ return null;
+ }
+
+ const mainImageIndex = useMemo(() => {
+ if (!data.image) return 0;
+
+ const mainImageIndex = data.attachments.findIndex(
+ attachment => attachment.file_url === data.image
+ );
+
+ return mainImageIndex >= 0 ? mainImageIndex : 0;
+ }, [data.image, data.attachments]);
+
+ const currentImage = useMemo(() => {
+ return data.attachments[currentImageIndex]?.file_url || data.attachments[0]?.file_url;
+ }, [data.attachments, currentImageIndex]);
+
+ const scrollThumbnailToIndex = useCallback((index) => {
+ if (thumbsSwiperRef.current?.swiper) {
+ try {
+ const swiper = thumbsSwiperRef.current.swiper;
+
+ if (index === 0) {
+ swiper.scrollTo(0, 300);
+ } else {
+ swiper.slideTo(index, 300);
+ }
+ } catch (error) {
+ }
+ }
+ }, []);
+
+ const changeMainImage = useCallback((direction) => {
+ if (data.attachments.length <= 1) return;
+
+ let newIndex;
+ if (direction === 'next') {
+ newIndex = (currentImageIndex + 1) % data.attachments.length;
+ } else {
+ newIndex = (currentImageIndex - 1 + data.attachments.length) % data.attachments.length;
+ }
+
+ setCurrentImageIndex(newIndex);
+
+ setTimeout(() => {
+ scrollThumbnailToIndex(newIndex);
+ }, 100);
+ }, [currentImageIndex, data.attachments.length, scrollThumbnailToIndex]);
+
+ const changeMainImageByIndex = useCallback((index) => {
+ setCurrentImageIndex(index);
+
+ setTimeout(() => {
+ scrollThumbnailToIndex(index);
+ }, 100);
+ }, [scrollThumbnailToIndex]);
+
+ useEffect(() => {
+ setCurrentImageIndex(mainImageIndex);
+ }, [mainImageIndex]);
+
+ const handleKeyDown = useCallback((event) => {
+ if (data.attachments.length <= 1) return;
+
+ switch (event.key) {
+ case 'ArrowLeft':
+ event.preventDefault();
+ changeMainImage('prev');
+ break;
+ case 'ArrowRight':
+ event.preventDefault();
+ changeMainImage('next');
+ break;
+ case 'Home':
+ event.preventDefault();
+ setCurrentImageIndex(0);
+ setTimeout(() => scrollThumbnailToIndex(0), 100);
+ break;
+ case 'End':
+ event.preventDefault();
+ setCurrentImageIndex(data.attachments.length - 1);
+ setTimeout(() => scrollThumbnailToIndex(data.attachments.length - 1), 100);
+ break;
+ default:
+ break;
+ }
+ }, [data.attachments.length, changeMainImage, scrollThumbnailToIndex]);
+
+ useEffect(() => {
+ document.addEventListener('keydown', handleKeyDown);
+
+ return () => {
+ document.removeEventListener('keydown', handleKeyDown);
+ };
+ }, [handleKeyDown]);
+
+ const thumbnailSlides = useMemo(() => {
+ return data.attachments.map((attachment, index) => (
+ changeMainImageByIndex(index)}
+ >
+ {
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault();
+ changeMainImageByIndex(index);
+ }
+ }}
+ >
+

{
+ e.target.style.display = 'none';
+ }}
+ />
+
+
+
+ ));
+ }, [data.attachments, currentImageIndex, data?.title, changeMainImageByIndex]);
+
+ return (
+
+
+
+

+
+ {data.attachments.length > 1 && (
+ <>
+
+
+ >
+ )}
+
+
+ {data.attachments.length > 1 && (
+
+
+ {thumbnailSlides}
+
+
+ )}
+
+
+
+
+ );
+};
+
+export default ProductImageGallery;
diff --git a/data/presentation.js b/data/presentation.js
index 2103ba4..4b31310 100644
--- a/data/presentation.js
+++ b/data/presentation.js
@@ -9,8 +9,8 @@ export async function getLocalPageData(slugArr) {
return { data: null };
}
- // 构建markdown文件路径
- const fileName = slugArr.join('-') + '.md';
+ // 构建markdown文件路径 - 只使用最后一个slug作为文件名
+ const fileName = slugArr[slugArr.length - 1] + '.md';
const filePath = path.join(process.cwd(), 'data', 'presentation', fileName);
// 检查文件是否存在
@@ -32,7 +32,6 @@ export async function getLocalPageData(slugArr) {
return { data: pageData };
} catch (error) {
- console.error("Error reading local markdown file:", error);
return { data: null };
}
}
diff --git a/data/presentation/jingrow.md b/data/presentation/jingrow.md
index 45ccfef..d23e4f9 100644
--- a/data/presentation/jingrow.md
+++ b/data/presentation/jingrow.md
@@ -1,14 +1,14 @@
---
theme: jingrow
-backgroundTransition: slide
-transition: slide
---
-# Jingrow
-## 一站式通用企业数字化平台
-### 下一代智能工作平台
-*让企业数字化变得简单而强大*
+
+# Jingrow
+## 一站式通用数字化平台
+##### AI Agent智能体、可视化工作流、零代码可视化数据建模、自动化任务调度、实时协作与权限管理
+###### 注册即用/免部署/零运维
+*一种新的面向未来的工作方式*
---
@@ -326,59 +326,6 @@ Jingrow 采用独特的"页面化"管理理念,将所有业务对象统一抽
# 🎉 感谢聆听
-## Jingrow - 让企业数字化变得简单而强大
-
-### 开启您的数字化之旅
-
----
-
-
-
-## 📚 附录:Jingrow 系统开发指南
-
-### 🔄 关键词替换规则
-
-| 原始关键词 | 替换为关键词 |
-|---|----|
-| `frappe` | `jingrow` |
-| `doctype` | `pagetype` |
-| `DocType` | `PageType` |
-| `doc` | `pg` |
-| `get_doc` | `get_pg` |
-
----
-
-
-
-### ⚠️ 错误日志与异常处理
-
-```python
-# 打印错误日志
-jingrow.log_error(title, message)
-
-# 抛出异常
-jingrow.throw(title, message)
-```
-
----
-
-
-
-### 🔌 Jingrow 调用外部API开发规范
-
-- **🔑 统一认证**:使用 `get_jingrow_api_headers` 发送认证
-- **📝 函数命名**:`call_<模块名>_api`
-- **🛡️ 安全校验**:包含鉴权校验、余额不足判断、错误提示弹窗
-- **🔄 完整闭环**:保证脚本具备完整闭环能力,提升用户体验
-
----
-
-
-
-### 💡 最佳实践建议
-
-- **🏗️ 架构遵循**:遵循系统架构与开发风格
-- **⚡ 简洁高效**:代码需简洁高效,避免重复
-- **📁 统一管理**:通用函数应统一存放于合适路径下的 `utils.py` 文件
-- **🔧 便于维护**:所有模块应便于维护与升级
+## Jingrow - Jingrow帮助您快速构建从内部管理到对外门户的一站式数字化解决方案
+### 开启您的数字化之旅
\ No newline at end of file