From 65abd70f235b597ce34053337b21636060167224 Mon Sep 17 00:00:00 2001 From: jingrow Date: Wed, 3 Sep 2025 22:58:01 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0Jsite=20Presentation=20Catego?= =?UTF-8?q?ry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/[...slug]/page.jsx | 42 ++- app/(presentation)/presentation/page.jsx | 118 ++++++ app/(website)/globals.css | 52 +++ components/presentation/Presentation.jsx | 45 ++- components/presentation/themes/jingrow.css | 164 ++++++++- components/products/ProductImageGallery.jsx | 346 ++++++++++++++++++ data/presentation.js | 5 +- data/presentation/jingrow.md | 69 +--- 8 files changed, 738 insertions(+), 103 deletions(-) create mode 100644 app/(presentation)/presentation/page.jsx create mode 100644 components/products/ProductImageGallery.jsx 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 `${htmlContent}`; + return `${htmlContent}`; }).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); + } + }} + > + {`${data?.title { + e.target.style.display = 'none'; + }} + /> +
+
+
+
+
+ )); + }, [data.attachments, currentImageIndex, data?.title, changeMainImageByIndex]); + + return ( +
+
+
+ {data?.title + + {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