diff --git a/blog.config.js b/blog.config.js index ed0721cc..3b827a95 100644 --- a/blog.config.js +++ b/blog.config.js @@ -127,7 +127,8 @@ const BLOG = { '/[prefix]/[slug]/[...suffix]': 'LayoutSlug', '/auth/result': 'LayoutAuth', '/sign-in/[[...index]]': 'LayoutSignIn', - '/sign-up/[[...index]]': 'LayoutSignUp' + '/sign-up/[[...index]]': 'LayoutSignUp', + '/dashboard/[[...index]]': 'LayoutDashboard' }, CAN_COPY: process.env.NEXT_PUBLIC_CAN_COPY || true, // 是否允许复制页面内容 默认允许,如果设置为false、则全栈禁止复制内容。 @@ -252,7 +253,7 @@ const BLOG = { ], // 鼠标跟随特效 - MOUSE_FOLLOW: process.env.NEXT_PUBLIC_MOUSE_FOLLOW || true, // 开关 + MOUSE_FOLLOW: process.env.NEXT_PUBLIC_MOUSE_FOLLOW || false, // 开关 // 这两个只有在鼠标跟随特效开启时才生效 // 鼠标类型 1:路劲散点 2:下降散点 3:上升散点 4:边缘向鼠标移动散点 5:跟踪转圈散点 6:路径线条 7:聚集散点 8:聚集网格 9:移动网格 10:上升粒子 11:转圈随机颜色粒子 12:圆锥放射跟随蓝色粒子 MOUSE_FOLLOW_EFFECT_TYPE: 11, // 1-12 @@ -551,7 +552,9 @@ const BLOG = { VERSION: (() => { try { // 优先使用环境变量,否则从package.json中获取版本号 - return process.env.NEXT_PUBLIC_VERSION || require('./package.json').version + return ( + process.env.NEXT_PUBLIC_VERSION || require('./package.json').version + ) } catch (error) { console.warn('Failed to load package.json version:', error) return '1.0.0' // 缺省版本号 diff --git a/components/ui/dashboard/DashboardBody.js b/components/ui/dashboard/DashboardBody.js new file mode 100644 index 00000000..89b60329 --- /dev/null +++ b/components/ui/dashboard/DashboardBody.js @@ -0,0 +1,51 @@ +'use client' +import { UserProfile } from '@clerk/nextjs' +import dynamic from 'next/dynamic' +import { useRouter } from 'next/router' + +const DashboardMenuList = dynamic(() => import('./DashboardMenuList')) +const DashboardItemMembership = dynamic( + () => import('./DashboardItemMembership') +) +const DashboardItemBalance = dynamic(() => import('./DashboardItemBalance')) +const DashboardItemHome = dynamic(() => import('./DashboardItemHome')) +const DashboardItemOrder = dynamic(() => import('./DashboardItemOrder')) +const DashboardItemAffliate = dynamic(() => import('./DashboardItemAffliate')) +/** + * 仪表盘内容主体 + * 组件懒加载 + * @returns + */ +export default function DashboardBody() { + const { asPath } = useRouter() + // 提取不包含查询参数的路径部分 + const basePath = asPath.split('?')[0] + return ( +
+
+ +
+
+ {basePath === '/dashboard' && } + {(basePath === '/dashboard/user-profile' || + basePath === '/dashboard/user-profile/security') && ( + + )} + {basePath === '/dashboard/balance' && } + {basePath === '/dashboard/membership' && } + {basePath === '/dashboard/order' && } + {basePath === '/dashboard/affiliate' && } +
+
+ ) +} diff --git a/components/ui/dashboard/DashboardButton.js b/components/ui/dashboard/DashboardButton.js new file mode 100644 index 00000000..5f382f1a --- /dev/null +++ b/components/ui/dashboard/DashboardButton.js @@ -0,0 +1,27 @@ +import { siteConfig } from '@/lib/config' +import Link from 'next/link' +import { useRouter } from 'next/router' +/** + * 跳转仪表盘的按钮 + * @returns + */ +export default function DashboardButton() { + const { asPath } = useRouter() + const enableDashboardButton = siteConfig('ENABLE_DASHBOARD_BUTTON', false) + + if (!enableDashboardButton) { + return null + } + + if (asPath?.indexOf('/dashboard') === 0) { + return null + } + + return ( + + ) +} diff --git a/components/ui/dashboard/DashboardHeader.js b/components/ui/dashboard/DashboardHeader.js new file mode 100644 index 00000000..7542eea1 --- /dev/null +++ b/components/ui/dashboard/DashboardHeader.js @@ -0,0 +1,53 @@ +import LazyImage from '@/components/LazyImage' +import { useGlobal } from '@/lib/global' +import formatDate from '@/lib/utils/formatDate' +import { SignOutButton } from '@clerk/nextjs' +import Link from 'next/link' +/** + * 仪表盘页头 + * @returns + */ +export default function DashboardHeader() { + const { user } = useGlobal() + + return ( + <> +
+ {/* 头像昵称 */} +
+ + +
+
+ {user?.fullName} + + + 普通用户 + + +
+
+ {user?.username} + {formatDate(user?.createdAt)} +
+
+
+ + {/* 登出按钮 */} +
+ + + +
+
+ + ) +} diff --git a/components/ui/dashboard/DashboardItemAffliate.js b/components/ui/dashboard/DashboardItemAffliate.js new file mode 100644 index 00000000..443e0ac7 --- /dev/null +++ b/components/ui/dashboard/DashboardItemAffliate.js @@ -0,0 +1,187 @@ +import Link from 'next/link' + +/** + * 联盟行销 + * @returns + */ +export default function DashboardItemAffliate() { + const cards = [ + { + title: '¥0.00', + desc: '累计佣金', + className: 'bg-blue-600 hover:bg-blue-700 text-white' + }, + { + title: '¥0.00', + desc: '已提现', + className: 'bg-cyan-600 hover:bg-cyan-700 text-white' + }, + { + title: '¥0.00', + desc: '提现中', + className: 'bg-pink-600 hover:bg-pink-700 text-white' + }, + { + title: '¥0.00', + desc: '可提现', + className: 'bg-emerald-600 hover:bg-emerald-700 text-white' + } + ] + + return ( +
+
+ {cards?.map((card, index) => ( +
+
+ {card.title} +
+

{card.desc}

+
+ ))} +
+
+
+
+ + +
+
+ + +
+ +
+ + +
+
+ +
+ +
+
+ + +
+ +
+ + +
+
+ +
+
+ +
+ +
+
+ + +
+ +
    +
  • 推广说明:
  • +
  • 这只是一个演示页面,不存在真实功能!
  • +
  • + 如需提现请联系网站管理员,发送您的账号信息和收款码进行人工提现 +
  • +
  • + 如果用户是通过您的推广链接购买的资源或者开通会员,则按照推广佣金比列奖励到您的佣金中 +
  • +
  • + 如果用户是通过您的链接新注册的用户,推荐人是您,该用户购买资都会给你佣金 +
  • +
  • + 如果用户是你的下级,用户使用其他推荐人链接购买,以上下级关系为准,优先给注册推荐人而不是推荐链接 +
  • +
  • 推广奖励金额保留一位小数点四舍五入。0.1之类的奖励金额不计算
  • +
  • + 前台无法查看推广订单详情,如需查看详情可联系管理员截图查看详细记录和时间 +
  • +
+
+
+ ) +} diff --git a/components/ui/dashboard/DashboardItemBalance.js b/components/ui/dashboard/DashboardItemBalance.js new file mode 100644 index 00000000..4c623f6e --- /dev/null +++ b/components/ui/dashboard/DashboardItemBalance.js @@ -0,0 +1,137 @@ +import { useEffect, useState } from 'react' + +/** + * 余额 + * @returns + */ +export default function DashboardItemBalance() { + const [selectedCard, setSelectedCard] = useState(null) + const [amount, setAmount] = useState(0) + + const cards = [ + { + title: '0 积分', + desc: '当前余额', + className: 'bg-blue-600 hover:bg-blue-700 text-white' + }, + { + title: '0 积分', + desc: '累计消费', + className: 'bg-cyan-600 hover:bg-cyan-700 text-white' + }, + { + title: '0', + desc: '累计佣金', + className: 'bg-pink-600 hover:bg-pink-700 text-white' + } + ] + + const cardData = [ + { points: '1积分', price: '¥1' }, + { points: '10积分', price: '¥10' }, + { points: '50积分', price: '¥50' }, + { points: '100积分', price: '¥100' }, + { points: '300积分', price: '¥300' }, + { points: '500积分', price: '¥500' } + ] + + const handleCardSelect = index => { + setSelectedCard(index) + } + + const handleAmountChange = e => { + const value = e.target.value + setAmount(value) + } + + useEffect(() => { + if (selectedCard !== null) { + // 如果用户选中了充值卡片,则自动更新支付金额 + const selectedPrice = cardData[selectedCard]?.price + if (selectedPrice) { + setAmount(selectedPrice.replace('¥', '')) + } + } + }, [selectedCard]) + + return ( +
+
+

余额充值中心

+
+
+ + {/* 余额卡片 */} +
+ {cards?.map((card, index) => ( +
handleCardSelect(index)}> +
+ {card.title} +
+

{card.desc}

+
+ ))} +
+ +
+
充值项目(充值比例:1元=1积分)
+ {/* 充值选项 */} +
+ {cardData?.map((item, index) => ( +
handleCardSelect(index)}> +

{item.points}

+ {item.price} +
+ ))} +
+
+ +
+
+ + +
+
+ +
+
+ 支付金额:¥{amount} +
+ +
+ +
    +
  • 充值说明:
  • +
  • 这只是一个演示页面,不存在真实功能!
  • +
  • 充值最低额度为1积分
  • +
  • 充值汇率为1元=1积分,人民币和积分不能互相转换
  • +
  • 余额永久有效,无时间限制
  • +
+
+
+ ) +} diff --git a/components/ui/dashboard/DashboardItemHome.js b/components/ui/dashboard/DashboardItemHome.js new file mode 100644 index 00000000..2988aec3 --- /dev/null +++ b/components/ui/dashboard/DashboardItemHome.js @@ -0,0 +1,64 @@ +/** + * 首页组件 + * @returns + */ +export default function DashboardItemHome() { + return ( +
+ {/* 提示消息 */} +
+ 注意!{' '} + 整个后台都只是页面效果,仅供演示查看,没有对接实际功能。 +
+ + {/* 页面说明 */} +
+

+ 欢迎来到用户中心页面!在这里,您可以查看用户的账号信息与业务订单概况。 +

+
+ + {/* 进度条 */} +
+

当前任务进度

+
+
+
+

+ 任务进度:75% +

+
+ + {/* 背景动画块 */} +
+
+
+

实时数据分析

+

监控您的系统数据,查看实时变化

+
+
+ + {/* 数据卡片模块 */} +
+
+

今日访问量

+

1,245

+
+
+

用户总数

+

12,300

+
+
+

+ 系统健康状态 +

+

正常

+
+
+
+ ) +} diff --git a/components/ui/dashboard/DashboardItemMembership.js b/components/ui/dashboard/DashboardItemMembership.js new file mode 100644 index 00000000..ab0b61b9 --- /dev/null +++ b/components/ui/dashboard/DashboardItemMembership.js @@ -0,0 +1,125 @@ +import { useEffect, useState } from 'react' + +/** + * 会员 + * @returns + */ +export default function DashboardItemMembership() { + const [selectedMembership, setSelectedMembership] = useState(null) + const [amount, setAmount] = useState(0) + + const memberships = [ + { + title: '年度会员', + points: 98, + duration: '365天', + benefits: [ + '日更5到20个热门项目', + '全站资源免费获取', + '内部会员专属交流群', + '可补差价升级', + '推广佣金高达40%' + ] + }, + { + title: '永久会员', + points: 138, + duration: '永久', + benefits: [ + '日更5到20个热门项目', + '全站资源免费获取', + '内部会员专属交流群', + '可补差价升级', + '推广佣金高达70%' + ] + }, + { + title: '站长训练营', + points: 1998, + duration: '永久', + benefits: [ + '站长学员请联系助理对接', + '一对一扶持搭建网站', + '提供独家引流技术照做就能成功', + '全站素材直接复刻到学员新站', + '软件一键同步更新', + '学员专属社群及交流群', + '设立高额福利的打卡机制(增强学员执行力)' + ] + } + ] + + const handleMembershipSelect = index => { + setSelectedMembership(index) + setAmount(memberships[index].points) + } + + const handleAmountChange = e => { + const value = e.target.value + setAmount(value) + } + + useEffect(() => { + if (selectedMembership !== null) { + // 如果用户选中了会员,自动更新支付金额 + const selectedPoints = memberships[selectedMembership]?.points + if (selectedPoints) { + setAmount(selectedPoints) + } + } + }, [selectedMembership]) + + return ( +
+
+

会员注册

+
+
+ + {/* 会员卡片 */} +
+ {memberships.map((membership, index) => ( +
handleMembershipSelect(index)}> +
+ {membership.title} +
+

所需积分:{membership.points} 积分

+

会员时长:{membership.duration}

+
    + {membership.benefits.map((benefit, i) => ( +
  • {benefit}
  • + ))} +
+
+ ))} +
+ +
+
+
+ 支付金额:¥{amount} +
+ +
+ +
    +
  • 开通会员说明:
  • +
  • 这只是一个演示页面,不存在真实功能!
  • +
  • 本站会员账号权限为虚拟数字资源,开通后不可退款
  • +
  • 开通会员后可享有对应会员特权的商品折扣,免费权限
  • +
  • 会员特权到期后不享受特权
  • +
  • 重复购买特权到期时间累计增加
  • +
+
+
+ ) +} diff --git a/components/ui/dashboard/DashboardItemOrder.js b/components/ui/dashboard/DashboardItemOrder.js new file mode 100644 index 00000000..37acc466 --- /dev/null +++ b/components/ui/dashboard/DashboardItemOrder.js @@ -0,0 +1,246 @@ +import { useState } from 'react' + +/** + * 订单列表 + */ +export default function DashboardItemOrder() { + const [currentPage, setCurrentPage] = useState(1) + + const totalPages = 5 + + const columns = [ + { key: 'name', label: '商品名称' }, + { key: 'color', label: '颜色' }, + { key: 'category', label: '分类' }, + { + key: 'accessories', + label: '配件', + render: value => (value ? '是' : '否') + }, + { key: 'available', label: '库存', render: value => (value ? '有' : '无') }, + { key: 'price', label: '价格', render: value => `¥${value}` }, + { key: 'weight', label: '重量' }, + { + key: 'action', + label: '操作', + render: () => ( +
+ + 编辑 + + + 删除 + +
+ ) + } + ] + + const data = [ + { + name: '苹果 MacBook Pro 17"', + color: '银色', + category: '笔记本', + accessories: true, + available: true, + price: 2999, + weight: '3.0 公斤' + }, + { + name: '微软 Surface Pro', + color: '白色', + category: '笔记本电脑', + accessories: false, + available: true, + price: 1999, + weight: '1.0 公斤' + }, + { + name: 'Magic Mouse 2', + color: '黑色', + category: '配件', + accessories: true, + available: false, + price: 99, + weight: '0.2 公斤' + }, + { + name: '苹果手表', + color: '黑色', + category: '手表', + accessories: true, + available: false, + price: 199, + weight: '0.12 公斤' + }, + { + name: 'iPad Pro', + color: '金色', + category: '平板电脑', + accessories: false, + available: true, + price: 699, + weight: '1.3 公斤' + } + ] + + const onPageChange = page => { + if (page >= 1 && page <= totalPages) { + setCurrentPage(page) + } + } + + return ( +
+
+ + +
    +
  • 订单说明:
  • +
  • 这只是一个演示页面,不存在真实功能!
  • +
+ + + ) +} + +/** + * 分页组件 + */ +const Pagination = ({ currentPage, totalPages, onPageChange }) => { + const pages = Array.from({ length: totalPages }, (_, i) => i + 1) + + return ( + + ) +} + +/** + * 表格组件 + */ +const Table = ({ columns, data }) => { + return ( +
+
+ {/* 表头 */} + + + + {columns.map((column, index) => ( + + ))} + + + {/* 表格内容 */} + + {data.map((item, index) => ( + + + {columns.map((column, colIndex) => ( + + ))} + + ))} + +
+
+ + +
+
+ {column.label} +
+
+ + +
+
+ {column.render + ? column.render(item[column.key]) + : item[column.key]} +
+
+ ) +} diff --git a/components/ui/dashboard/DashboardMenuList.js b/components/ui/dashboard/DashboardMenuList.js new file mode 100644 index 00000000..b0ffed2a --- /dev/null +++ b/components/ui/dashboard/DashboardMenuList.js @@ -0,0 +1,57 @@ +import Link from 'next/link' + +/** + * 仪表盘菜单 + * @returns + */ +import { useRouter } from 'next/router' + +/** + * 仪表盘菜单 + * @returns + */ +export default function DashboardMenuList() { + const { asPath } = useRouter() // 获取当前路径 + const dashBoardMenus = [ + { title: '仪表盘', icon: 'fas fa-gauge', href: '/dashboard' }, + { title: '基础资料', icon: 'fas fa-user', href: '/dashboard/user-profile' }, + { title: '我的余额', icon: 'fas fa-coins', href: '/dashboard/balance' }, + { title: '我的会员', icon: 'fas fa-gem', href: '/dashboard/membership' }, + { + title: '我的订单', + icon: 'fas fa-cart-shopping', + href: '/dashboard/order' + }, + { + title: '推广中心', + icon: 'fas fa-hand-holding-usd', + href: '/dashboard/affiliate' + } + ] + + return ( + + ) +} diff --git a/lib/config.js b/lib/config.js index be7815b7..b690c1ae 100644 --- a/lib/config.js +++ b/lib/config.js @@ -36,6 +36,7 @@ export const siteConfig = (key, defaultVal = null, extendConfig = {}) => { case 'POST_URL_PREFIX_MAPPING_CATEGORY': case 'IS_TAG_COLOR_DISTINGUISHED': case 'TAG_SORT_BY_COUNT': + case 'THEME': case 'LINK': return convertVal( getValue(extendConfig[key], getValue(defaultVal, BLOG[key])) diff --git a/lib/utils/formatDate.js b/lib/utils/formatDate.js index 29190e3b..c40a5135 100644 --- a/lib/utils/formatDate.js +++ b/lib/utils/formatDate.js @@ -1,22 +1,31 @@ +import BLOG from '@/blog.config' + /** * 格式化日期 * @param date * @param local * @returns {string} */ -export default function formatDate (date, local) { +export default function formatDate(date, local = BLOG.LANG) { if (!date || !local) return date || '' const d = new Date(date) const options = { year: 'numeric', month: 'short', day: 'numeric' } const res = d.toLocaleDateString(local, options) // 如果格式是中文日期,则转为横杆 - const format = local.slice(0, 2).toLowerCase() === 'zh' - ? res.replace('年', '-').replace('月', '-').replace('日', '') - : res + const format = + local.slice(0, 2).toLowerCase() === 'zh' + ? res.replace('年', '-').replace('月', '-').replace('日', '') + : res return format } -export function formatDateFmt (timestamp, fmt) { +/** + * 时间戳格式化 + * @param {*} timestamp + * @param {*} fmt + * @returns + */ +export function formatDateFmt(timestamp, fmt) { const date = new Date(timestamp) const o = { 'M+': date.getMonth() + 1, // 月份 @@ -28,11 +37,17 @@ export function formatDateFmt (timestamp, fmt) { S: date.getMilliseconds() // 毫秒 } if (/(y+)/.test(fmt)) { - fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)) + fmt = fmt.replace( + RegExp.$1, + (date.getFullYear() + '').substr(4 - RegExp.$1.length) + ) } for (const k in o) { if (new RegExp('(' + k + ')').test(fmt)) { - fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length))) + fmt = fmt.replace( + RegExp.$1, + RegExp.$1.length === 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length) + ) } } return fmt.trim() diff --git a/middleware.ts b/middleware.ts index 0ae00a72..dc623d08 100644 --- a/middleware.ts +++ b/middleware.ts @@ -12,7 +12,9 @@ export const config = { // 限制登录访问的路由 const isTenantRoute = createRouteMatcher([ '/user/organization-selector(.*)', - '/user/orgid/(.*)' + '/user/orgid/(.*)', + '/dashboard', + '/dashboard/(.*)' ]) // 限制权限访问的路由 @@ -32,27 +34,35 @@ const noAuthMiddleware = async (req: any, ev: any) => { // 如果没有配置 Clerk 相关环境变量,返回一个默认响应或者继续处理请求 return NextResponse.next() } - /** * 鉴权中间件 */ const authMiddleware = process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY - ? clerkMiddleware( - (auth, req) => { - // 限制管理员路由访问权限 - if (isTenantAdminRoute(req)) { - auth().protect(has => { - return ( - has({ permission: 'org:sys_memberships:manage' }) || - has({ permission: 'org:sys_domains_manage' }) - ) - }) + ? clerkMiddleware(async (auth, req) => { + const { userId } = auth() + // 处理 /dashboard 路由的登录保护 + if (isTenantRoute(req)) { + if (!userId) { + // 用户未登录,重定向到 /sign-in + const url = new URL('/sign-in', req.url) + url.searchParams.set('redirectTo', req.url) // 保存重定向目标 + return NextResponse.redirect(url) } - // 限制组织路由访问权限 - if (isTenantRoute(req)) auth().protect() } - // { debug: process.env.npm_lifecycle_event === 'dev' } // 开发调试模式打印日志 - ) + + // 处理管理员相关权限保护 + if (isTenantAdminRoute(req)) { + auth().protect(has => { + return ( + has({ permission: 'org:sys_memberships:manage' }) || + has({ permission: 'org:sys_domains_manage' }) + ) + }) + } + + // 默认继续处理请求 + return NextResponse.next() + }) : noAuthMiddleware export default authMiddleware diff --git a/next.config.js b/next.config.js index 00d08665..585c5435 100644 --- a/next.config.js +++ b/next.config.js @@ -220,6 +220,7 @@ const nextConfig = { // export 静态导出时 忽略/pages/sitemap.xml.js , 否则和getServerSideProps这个动态文件冲突 const pages = { ...defaultPathMap } delete pages['/sitemap.xml'] + delete pages['/auth'] return pages }, publicRuntimeConfig: { diff --git a/package.json b/package.json index b27d0d6e..c71a1fdf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "notion-next", - "version": "4.7.9", + "version": "4.7.10", "homepage": "https://github.com/tangly1024/NotionNext.git", "license": "MIT", "repository": { diff --git a/pages/404.js b/pages/404.js index 78278918..4c58031e 100644 --- a/pages/404.js +++ b/pages/404.js @@ -1,6 +1,7 @@ +import BLOG from '@/blog.config' import { siteConfig } from '@/lib/config' import { getGlobalData } from '@/lib/db/getSiteData' -import { getLayoutByTheme } from '@/themes/theme' +import { DynamicLayout } from '@/themes/theme' import { useRouter } from 'next/router' /** @@ -9,12 +10,9 @@ import { useRouter } from 'next/router' * @returns */ const NoFound = props => { - // 根据页面路径加载不同Layout文件 - const Layout = getLayoutByTheme({ - theme: siteConfig('THEME'), - router: useRouter() - }) - return + const router = useRouter() + const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG) + return } export async function getStaticProps(req) { diff --git a/pages/[prefix]/index.js b/pages/[prefix]/index.js index 6b05f7f1..a6d6e82f 100644 --- a/pages/[prefix]/index.js +++ b/pages/[prefix]/index.js @@ -8,7 +8,7 @@ import { getPageTableOfContents } from '@/lib/notion/getPageTableOfContents' import { getPasswordQuery } from '@/lib/password' import { uploadDataToAlgolia } from '@/lib/plugins/algolia' import { checkSlugHasNoSlash, getRecommendPost } from '@/lib/utils/post' -import { getLayoutByTheme } from '@/themes/theme' +import { DynamicLayout } from '@/themes/theme' import md5 from 'js-md5' import { useRouter } from 'next/router' import { idToUuid } from 'notion-utils' @@ -83,15 +83,11 @@ const Slug = props => { }, [router, lock]) props = { ...props, lock, validPassword } - // 根据页面路径加载不同Layout文件 - const Layout = getLayoutByTheme({ - theme: siteConfig('THEME'), - router: useRouter() - }) + const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG) return ( <> {/* 文章布局 */} - + {/* 解锁密码提示框 */} {post?.password && post?.password !== '' && !lock && } {/* 导流工具 */} diff --git a/pages/_app.js b/pages/_app.js index e363b090..12002916 100644 --- a/pages/_app.js +++ b/pages/_app.js @@ -45,7 +45,6 @@ const MyApp = ({ Component, pageProps }) => { // 整体布局 const GLayout = useCallback( props => { - // 根据页面路径加载不同Layout文件 const Layout = getGlobalLayoutByTheme(queryParam) return }, diff --git a/pages/archive/index.js b/pages/archive/index.js index beecf79c..968803ee 100644 --- a/pages/archive/index.js +++ b/pages/archive/index.js @@ -3,17 +3,16 @@ import { siteConfig } from '@/lib/config' import { getGlobalData } from '@/lib/db/getSiteData' import { isBrowser } from '@/lib/utils' import { formatDateFmt } from '@/lib/utils/formatDate' -import { getLayoutByTheme } from '@/themes/theme' +import { DynamicLayout } from '@/themes/theme' import { useRouter } from 'next/router' import { useEffect } from 'react' +/** + * 归档首页 + * @param {*} props + * @returns + */ const ArchiveIndex = props => { - // 根据页面路径加载不同Layout文件 - const Layout = getLayoutByTheme({ - theme: siteConfig('THEME'), - router: useRouter() - }) - useEffect(() => { if (isBrowser) { const anchor = window.location.hash @@ -28,7 +27,9 @@ const ArchiveIndex = props => { } }, []) - return + const router = useRouter() + const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG) + return } export async function getStaticProps({ locale }) { diff --git a/pages/auth/index.js b/pages/auth/index.js new file mode 100644 index 00000000..cce744d9 --- /dev/null +++ b/pages/auth/index.js @@ -0,0 +1,101 @@ +// pages/sitemap.xml.js +import { getGlobalData } from '@/lib/db/getSiteData' +import axios from 'axios' +import { useRouter } from 'next/router' +import { useEffect } from 'react' +import Slug from '../[prefix]' + +/** + * 根据notion的slug访问页面 + * 解析二级目录 /article/about + * @param {*} props + * @returns + */ +const UI = props => { + const { redirect_pathname, redirect_query } = props + const router = useRouter() + useEffect(() => { + router?.push({ pathname: redirect_pathname, query: redirect_query }) + }, []) + return +} + +/** + * 服务端接收参数处理 + * @param {*} ctx + * @returns + */ +export const getServerSideProps = async ctx => { + const from = `auth` + const props = await getGlobalData({ from }) + delete props.allPages + const code = ctx.query.code + + let params = null + if (code) { + params = await fetchToken(code) + } + + // 授权成功的划保存下用户的workspace信息 + if (params?.status === 200) { + console.log('请求成功', params) + props.redirect_query = { + ...params.data, + msg: '成功了' + JSON.stringify(params.data) + } + console.log('用户信息', JSON.stringify(params.data)) + } else if (!params) { + console.log('请求异常', params) + props.redirect_query = { msg: '无效请求' } + } else { + console.log('请求失败', params) + props.redirect_query = { msg: params.statusText } + } + + props.redirect_pathname = '/auth/result' + + return { + props + } +} + +const fetchToken = async code => { + if (!code) { + return '无效请求' + } + console.log('Auth', code) + const clientId = process.env.OAUTH_CLIENT_ID + const clientSecret = process.env.OAUTH_CLIENT_SECRET + const redirectUri = process.env.OAUTH_REDIRECT_URI + + // encode in base 64 + const encoded = Buffer.from(`${clientId}:${clientSecret}`).toString('base64') + + try { + console.log( + `请求Code换取Token ${clientId}:${clientSecret} -- ${redirectUri}` + ) + const response = await axios.post( + 'https://api.notion.com/v1/oauth/token', + { + grant_type: 'authorization_code', + code: code, + redirect_uri: redirectUri + }, + { + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + Authorization: `Basic ${encoded}` + } + } + ) + + console.log('Token response', response.data) + return response + } catch (error) { + console.error('Error fetching token', error) + } +} + +export default UI diff --git a/pages/category/[category]/index.js b/pages/category/[category]/index.js index ea70b454..a3a531a1 100644 --- a/pages/category/[category]/index.js +++ b/pages/category/[category]/index.js @@ -1,7 +1,7 @@ import BLOG from '@/blog.config' import { siteConfig } from '@/lib/config' import { getGlobalData } from '@/lib/db/getSiteData' -import { getLayoutByTheme } from '@/themes/theme' +import { DynamicLayout } from '@/themes/theme' import { useRouter } from 'next/router' /** @@ -10,13 +10,9 @@ import { useRouter } from 'next/router' * @returns */ export default function Category(props) { - // 根据页面路径加载不同Layout文件 - const Layout = getLayoutByTheme({ - theme: siteConfig('THEME'), - router: useRouter() - }) - - return + const router = useRouter() + const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG) + return } export async function getStaticProps({ params: { category }, locale }) { diff --git a/pages/category/[category]/page/[page].js b/pages/category/[category]/page/[page].js index 120d762a..9668cdeb 100644 --- a/pages/category/[category]/page/[page].js +++ b/pages/category/[category]/page/[page].js @@ -1,7 +1,7 @@ import BLOG from '@/blog.config' import { siteConfig } from '@/lib/config' import { getGlobalData } from '@/lib/db/getSiteData' -import { getLayoutByTheme } from '@/themes/theme' +import { DynamicLayout } from '@/themes/theme' import { useRouter } from 'next/router' /** @@ -11,13 +11,9 @@ import { useRouter } from 'next/router' */ export default function Category(props) { - // 根据页面路径加载不同Layout文件 - const Layout = getLayoutByTheme({ - theme: siteConfig('THEME'), - router: useRouter() - }) - - return + const router = useRouter() + const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG) + return } export async function getStaticProps({ params: { category, page } }) { diff --git a/pages/category/index.js b/pages/category/index.js index 62a3cc9f..4b2f3942 100644 --- a/pages/category/index.js +++ b/pages/category/index.js @@ -1,7 +1,7 @@ import BLOG from '@/blog.config' import { siteConfig } from '@/lib/config' import { getGlobalData } from '@/lib/db/getSiteData' -import { getLayoutByTheme } from '@/themes/theme' +import { DynamicLayout } from '@/themes/theme' import { useRouter } from 'next/router' /** @@ -10,13 +10,9 @@ import { useRouter } from 'next/router' * @returns */ export default function Category(props) { - // 根据页面路径加载不同Layout文件 - const Layout = getLayoutByTheme({ - theme: siteConfig('THEME'), - router: useRouter() - }) - - return + const router = useRouter() + const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG) + return } export async function getStaticProps({ locale }) { diff --git a/pages/dashboard/[[...index]].js b/pages/dashboard/[[...index]].js new file mode 100644 index 00000000..0a865d92 --- /dev/null +++ b/pages/dashboard/[[...index]].js @@ -0,0 +1,91 @@ +import BLOG from '@/blog.config' +import { siteConfig } from '@/lib/config' +import { getGlobalData, getPost, getPostBlocks } from '@/lib/db/getSiteData' +import { DynamicLayout } from '@/themes/theme' +import { useRouter } from 'next/router' + +/** + * 根据notion的slug访问页面 + * 只解析一级目录例如 /about + * @param {*} props + * @returns + */ +const Dashboard = props => { + const router = useRouter() + const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG) + return +} + +export async function getStaticProps({ locale }) { + const prefix = 'dashboard' + let fullSlug = 'dashboard' + const from = `slug-props-${fullSlug}` + const props = await getGlobalData({ from, locale }) + if (siteConfig('PSEUDO_STATIC', false, props.NOTION_CONFIG)) { + if (!fullSlug.endsWith('.html')) { + fullSlug += '.html' + } + } + + // 在列表内查找文章 + props.post = props?.allPages?.find(p => { + return p.type.indexOf('Menu') < 0 && p.slug === fullSlug + }) + + // 处理非列表内文章的内信息 + if (!props?.post) { + const pageId = prefix + if (pageId.length >= 32) { + const post = await getPost(pageId) + props.post = post + } + } + // 无法获取文章 + if (!props?.post) { + props.post = null + return { + props, + revalidate: process.env.EXPORT + ? undefined + : siteConfig( + 'NEXT_REVALIDATE_SECOND', + BLOG.NEXT_REVALIDATE_SECOND, + props.NOTION_CONFIG + ) + } + } + + // 文章内容加载 + if (!props?.post?.blockMap) { + props.post.blockMap = await getPostBlocks(props.post.id, from) + } + + delete props.allPages + return { + props, + revalidate: process.env.EXPORT + ? undefined + : siteConfig( + 'NEXT_REVALIDATE_SECOND', + BLOG.NEXT_REVALIDATE_SECOND, + props.NOTION_CONFIG + ) + } +} + +export const getStaticPaths = async () => { + return { + paths: [ + { params: { index: [] } }, // 对应首页路径 + { params: { index: ['membership'] } }, + { params: { index: ['balance'] } }, + { params: { index: ['user-profile'] } }, + { params: { index: ['user-profile', 'security'] } }, // 嵌套路由,按结构传递 + { params: { index: ['order'] } }, + { params: { index: ['affiliate'] } } + ], + fallback: 'blocking' // 或者 true,阻塞式渲染 + } +} + +export default Dashboard diff --git a/pages/index.js b/pages/index.js index a84c9b3b..eaa21167 100644 --- a/pages/index.js +++ b/pages/index.js @@ -4,7 +4,7 @@ import { getGlobalData, getPostBlocks } from '@/lib/db/getSiteData' import { generateRobotsTxt } from '@/lib/robots.txt' import { generateRss } from '@/lib/rss' import { generateSitemapXml } from '@/lib/sitemap.xml' -import { getLayoutByTheme } from '@/themes/theme' +import { DynamicLayout } from '@/themes/theme' import { useRouter } from 'next/router' /** @@ -13,12 +13,9 @@ import { useRouter } from 'next/router' * @returns */ const Index = props => { - // 根据页面路径加载不同Layout文件 - const Layout = getLayoutByTheme({ - theme: siteConfig('THEME'), - router: useRouter() - }) - return + const router = useRouter() + const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG) + return } /** diff --git a/pages/page/[page].js b/pages/page/[page].js index 9ccb3b81..0166aba2 100644 --- a/pages/page/[page].js +++ b/pages/page/[page].js @@ -1,7 +1,7 @@ import BLOG from '@/blog.config' import { siteConfig } from '@/lib/config' import { getGlobalData, getPostBlocks } from '@/lib/db/getSiteData' -import { getLayoutByTheme } from '@/themes/theme' +import { DynamicLayout } from '@/themes/theme' import { useRouter } from 'next/router' /** @@ -10,13 +10,9 @@ import { useRouter } from 'next/router' * @returns */ const Page = props => { - // 根据页面路径加载不同Layout文件 - const Layout = getLayoutByTheme({ - theme: siteConfig('THEME'), - router: useRouter() - }) - - return + const router = useRouter() + const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG) + return } export async function getStaticPaths({ locale }) { diff --git a/pages/search/[keyword]/index.js b/pages/search/[keyword]/index.js index 9b491b00..179583d8 100644 --- a/pages/search/[keyword]/index.js +++ b/pages/search/[keyword]/index.js @@ -2,17 +2,13 @@ import BLOG from '@/blog.config' import { getDataFromCache } from '@/lib/cache/cache_manager' import { siteConfig } from '@/lib/config' import { getGlobalData } from '@/lib/db/getSiteData' -import { getLayoutByTheme } from '@/themes/theme' +import { DynamicLayout } from '@/themes/theme' import { useRouter } from 'next/router' const Index = props => { - // 根据页面路径加载不同Layout文件 - const Layout = getLayoutByTheme({ - theme: siteConfig('THEME'), - router: useRouter() - }) - - return + const router = useRouter() + const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG) + return } /** diff --git a/pages/search/[keyword]/page/[page].js b/pages/search/[keyword]/page/[page].js index bebaa9fc..4ced9f6c 100644 --- a/pages/search/[keyword]/page/[page].js +++ b/pages/search/[keyword]/page/[page].js @@ -2,19 +2,16 @@ import BLOG from '@/blog.config' import { getDataFromCache } from '@/lib/cache/cache_manager' import { siteConfig } from '@/lib/config' import { getGlobalData } from '@/lib/db/getSiteData' -import { getLayoutByTheme } from '@/themes/theme' +import { DynamicLayout } from '@/themes/theme' import { useRouter } from 'next/router' const Index = props => { const { keyword } = props - // 根据页面路径加载不同Layout文件 - const Layout = getLayoutByTheme({ - theme: siteConfig('THEME'), - router: useRouter() - }) props = { ...props, currentSearch: keyword } - return + const router = useRouter() + const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG) + return } /** diff --git a/pages/search/index.js b/pages/search/index.js index 6226a400..2000d72e 100644 --- a/pages/search/index.js +++ b/pages/search/index.js @@ -1,7 +1,7 @@ import BLOG from '@/blog.config' import { siteConfig } from '@/lib/config' import { getGlobalData } from '@/lib/db/getSiteData' -import { getLayoutByTheme } from '@/themes/theme' +import { DynamicLayout } from '@/themes/theme' import { useRouter } from 'next/router' /** @@ -12,12 +12,6 @@ import { useRouter } from 'next/router' const Search = props => { const { posts } = props - // 根据页面路径加载不同Layout文件 - const Layout = getLayoutByTheme({ - theme: siteConfig('THEME'), - router: useRouter() - }) - const router = useRouter() const keyword = router?.query?.s @@ -37,7 +31,8 @@ const Search = props => { props = { ...props, posts: filteredPosts } - return + const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG) + return } /** diff --git a/pages/sign-in/[[...index]].js b/pages/sign-in/[[...index]].js index 2dca85ef..7ed893d5 100644 --- a/pages/sign-in/[[...index]].js +++ b/pages/sign-in/[[...index]].js @@ -2,7 +2,7 @@ import BLOG from '@/blog.config' import { siteConfig } from '@/lib/config' import { getGlobalData } from '@/lib/db/getSiteData' // import { getGlobalData } from '@/lib/db/getSiteData' -import { getLayoutByTheme } from '@/themes/theme' +import { DynamicLayout } from '@/themes/theme' import { useRouter } from 'next/router' /** @@ -11,12 +11,9 @@ import { useRouter } from 'next/router' * @returns */ const SignIn = props => { - // 根据页面路径加载不同Layout文件 - const Layout = getLayoutByTheme({ - theme: siteConfig('THEME'), - router: useRouter() - }) - return + const router = useRouter() + const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG) + return } export async function getStaticProps(req) { diff --git a/pages/sign-up/[[...index]].js b/pages/sign-up/[[...index]].js index 39fdbe4e..72ba97fc 100644 --- a/pages/sign-up/[[...index]].js +++ b/pages/sign-up/[[...index]].js @@ -1,7 +1,7 @@ import BLOG from '@/blog.config' import { siteConfig } from '@/lib/config' import { getGlobalData } from '@/lib/db/getSiteData' -import { getLayoutByTheme } from '@/themes/theme' +import { DynamicLayout } from '@/themes/theme' import { useRouter } from 'next/router' /** @@ -10,12 +10,9 @@ import { useRouter } from 'next/router' * @returns */ const SignUp = props => { - // 根据页面路径加载不同Layout文件 - const Layout = getLayoutByTheme({ - theme: siteConfig('THEME'), - router: useRouter() - }) - return + const router = useRouter() + const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG) + return } export async function getStaticProps(req) { diff --git a/pages/tag/[tag]/index.js b/pages/tag/[tag]/index.js index 79458257..ff366c5f 100644 --- a/pages/tag/[tag]/index.js +++ b/pages/tag/[tag]/index.js @@ -1,7 +1,7 @@ import BLOG from '@/blog.config' import { siteConfig } from '@/lib/config' import { getGlobalData } from '@/lib/db/getSiteData' -import { getLayoutByTheme } from '@/themes/theme' +import { DynamicLayout } from '@/themes/theme' import { useRouter } from 'next/router' /** @@ -10,13 +10,9 @@ import { useRouter } from 'next/router' * @returns */ const Tag = props => { - // 根据页面路径加载不同Layout文件 - const Layout = getLayoutByTheme({ - theme: siteConfig('THEME'), - router: useRouter() - }) - - return + const router = useRouter() + const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG) + return } export async function getStaticProps({ params: { tag }, locale }) { diff --git a/pages/tag/[tag]/page/[page].js b/pages/tag/[tag]/page/[page].js index da93f16f..a13ebe04 100644 --- a/pages/tag/[tag]/page/[page].js +++ b/pages/tag/[tag]/page/[page].js @@ -1,16 +1,13 @@ import BLOG from '@/blog.config' import { siteConfig } from '@/lib/config' import { getGlobalData } from '@/lib/db/getSiteData' -import { getLayoutByTheme } from '@/themes/theme' +import { DynamicLayout } from '@/themes/theme' import { useRouter } from 'next/router' const Tag = props => { - // 根据页面路径加载不同Layout文件 - const Layout = getLayoutByTheme({ - theme: siteConfig('THEME'), - router: useRouter() - }) - return + const router = useRouter() + const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG) + return } export async function getStaticProps({ params: { tag, page }, locale }) { diff --git a/pages/tag/index.js b/pages/tag/index.js index 5433a033..a04a5b88 100644 --- a/pages/tag/index.js +++ b/pages/tag/index.js @@ -1,7 +1,7 @@ import BLOG from '@/blog.config' import { siteConfig } from '@/lib/config' import { getGlobalData } from '@/lib/db/getSiteData' -import { getLayoutByTheme } from '@/themes/theme' +import { DynamicLayout } from '@/themes/theme' import { useRouter } from 'next/router' /** @@ -10,12 +10,9 @@ import { useRouter } from 'next/router' * @returns */ const TagIndex = props => { - // 根据页面路径加载不同Layout文件 - const Layout = getLayoutByTheme({ - theme: siteConfig('THEME'), - router: useRouter() - }) - return + const router = useRouter() + const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG) + return } export async function getStaticProps(req) { diff --git a/themes/gitbook/components/BottomMenuBar.js b/themes/gitbook/components/BottomMenuBar.js index 478a52eb..c872941b 100644 --- a/themes/gitbook/components/BottomMenuBar.js +++ b/themes/gitbook/components/BottomMenuBar.js @@ -1,5 +1,5 @@ -import MobileButtonCatalog from './MobileButtonCatalog' -import MobileButtonPageNav from './MobileButtonPageNav' +import { useGlobal } from '@/lib/global' +import { useGitBookGlobal } from '..' /** * 移动端底部导航 @@ -8,20 +8,43 @@ import MobileButtonPageNav from './MobileButtonPageNav' */ export default function BottomMenuBar({ post, className }) { const showTocButton = post?.toc?.length > 1 + const { locale } = useGlobal() + const { pageNavVisible, changePageNavVisible, tocVisible, changeTocVisible } = + useGitBookGlobal() + const togglePageNavVisible = () => { + changePageNavVisible(!pageNavVisible) + } + + const toggleToc = () => { + changeTocVisible(!tocVisible) + } return ( - <> - {/* 移动端底部导航按钮 */} -
-
- -
+
+
+ + {showTocButton && ( -
- -
+ )}
- +
) } diff --git a/themes/gitbook/components/MobileButtonCatalog.js b/themes/gitbook/components/MobileButtonCatalog.js deleted file mode 100644 index e9e8b4e0..00000000 --- a/themes/gitbook/components/MobileButtonCatalog.js +++ /dev/null @@ -1,31 +0,0 @@ -import { useGlobal } from '@/lib/global' -import { useGitBookGlobal } from '@/themes/gitbook' - -/** - * 移动端目录按钮 - */ -export default function MobileButtonCatalog() { - const { tocVisible, changeTocVisible } = useGitBookGlobal() - const { locale } = useGlobal() - - const toggleToc = () => { - changeTocVisible(!tocVisible) - } - - return ( - - ) -} diff --git a/themes/gitbook/components/MobileButtonPageNav.js b/themes/gitbook/components/MobileButtonPageNav.js deleted file mode 100644 index 78f038f1..00000000 --- a/themes/gitbook/components/MobileButtonPageNav.js +++ /dev/null @@ -1,30 +0,0 @@ -import { useGlobal } from '@/lib/global' -import { useGitBookGlobal } from '@/themes/gitbook' - -/** - * 移动端文章导航按钮 - */ -export default function MobileButtonPageNav() { - const { pageNavVisible, changePageNavVisible } = useGitBookGlobal() - const { locale } = useGlobal() - const togglePageNavVisible = () => { - changePageNavVisible(!pageNavVisible) - } - - return ( - - ) -} diff --git a/themes/gitbook/index.js b/themes/gitbook/index.js index 0fac89b5..07197fdf 100644 --- a/themes/gitbook/index.js +++ b/themes/gitbook/index.js @@ -7,6 +7,8 @@ import LoadingCover from '@/components/LoadingCover' import NotionIcon from '@/components/NotionIcon' import NotionPage from '@/components/NotionPage' import ShareBar from '@/components/ShareBar' +import DashboardBody from '@/components/ui/dashboard/DashboardBody' +import DashboardHeader from '@/components/ui/dashboard/DashboardHeader' import { siteConfig } from '@/lib/config' import { useGlobal } from '@/lib/global' import { isBrowser } from '@/lib/utils' @@ -255,7 +257,9 @@ const LayoutIndex = props => { // 重定向到指定文章 router.push(index).then(() => { setTimeout(() => { - const article = document.querySelector('#article-wrapper #notion-article') + const article = document.querySelector( + '#article-wrapper #notion-article' + ) if (!article) { console.log( '请检查您的Notion数据库中是否包含此slug页面: ', @@ -309,7 +313,9 @@ const LayoutSlug = props => { setTimeout( () => { if (isBrowser) { - const article = document.querySelector('#article-wrapper #notion-article') + const article = document.querySelector( + '#article-wrapper #notion-article' + ) if (!article) { router.push('/404').then(() => { console.warn('找不到页面', router.asPath) @@ -539,11 +545,40 @@ const LayoutSignUp = props => { ) } +/** + * 仪表盘 + * @param {*} props + * @returns + */ +const LayoutDashboard = props => { + const { post } = props + + return ( + <> +
+
+
+ {post && ( +
+ +
+ )} +
+
+
+ {/* 仪表盘 */} + + + + ) +} + export { Layout404, LayoutArchive, LayoutBase, LayoutCategoryIndex, + LayoutDashboard, LayoutIndex, LayoutPostList, LayoutSearch, diff --git a/themes/magzine/components/Header.js b/themes/magzine/components/Header.js index 6464b712..3e4c3456 100644 --- a/themes/magzine/components/Header.js +++ b/themes/magzine/components/Header.js @@ -1,8 +1,9 @@ import Collapse from '@/components/Collapse' import DarkModeButton from '@/components/DarkModeButton' +import DashboardButton from '@/components/ui/dashboard/DashboardButton' import { siteConfig } from '@/lib/config' import { useGlobal } from '@/lib/global' -import { SignInButton, SignedOut, UserButton } from '@clerk/nextjs' +import { SignInButton, SignedIn, SignedOut, UserButton } from '@clerk/nextjs' import throttle from 'lodash.throttle' import { useRouter } from 'next/router' import { useEffect, useRef, useState } from 'react' @@ -200,7 +201,10 @@ export default function Header(props) { - + + + + )}
diff --git a/themes/magzine/index.js b/themes/magzine/index.js index e5165bdd..bdfb2c72 100644 --- a/themes/magzine/index.js +++ b/themes/magzine/index.js @@ -6,6 +6,8 @@ import replaceSearchResult from '@/components/Mark' import NotionPage from '@/components/NotionPage' import ShareBar from '@/components/ShareBar' import WWAds from '@/components/WWAds' +import DashboardBody from '@/components/ui/dashboard/DashboardBody' +import DashboardHeader from '@/components/ui/dashboard/DashboardHeader' import { siteConfig } from '@/lib/config' import { useGlobal } from '@/lib/global' import { isBrowser } from '@/lib/utils' @@ -155,7 +157,9 @@ const LayoutSlug = props => { setTimeout( () => { if (isBrowser) { - const article = document.querySelector('#article-wrapper #notion-article') + const article = document.querySelector( + '#article-wrapper #notion-article' + ) if (!article) { router.push('/404').then(() => { console.warn('找不到页面', router.asPath) @@ -495,11 +499,39 @@ const LayoutSignUp = props => { ) } +/** + * 仪表盘 + * @param {*} props + * @returns + */ +const LayoutDashboard = props => { + const { post } = props + + return ( + <> +
+
+
+ {post && ( +
+ +
+ )} +
+
+
+ {/* 仪表盘 */} + + + + ) +} export { Layout404, LayoutArchive, LayoutBase, LayoutCategoryIndex, + LayoutDashboard, LayoutIndex, LayoutPostList, LayoutSearch, diff --git a/themes/starter/components/Header.js b/themes/starter/components/Header.js index ef69c077..7a88df87 100644 --- a/themes/starter/components/Header.js +++ b/themes/starter/components/Header.js @@ -1,4 +1,5 @@ /* eslint-disable no-unreachable */ +import DashboardButton from '@/components/ui/dashboard/DashboardButton' import { siteConfig } from '@/lib/config' import { useGlobal } from '@/lib/global' import { SignedIn, SignedOut, UserButton } from '@clerk/nextjs' @@ -87,6 +88,7 @@ export const Header = props => { + )} diff --git a/themes/starter/components/MenuItem.js b/themes/starter/components/MenuItem.js index ff184f36..3e32a08b 100644 --- a/themes/starter/components/MenuItem.js +++ b/themes/starter/components/MenuItem.js @@ -42,7 +42,7 @@ export const MenuItem = ({ link }) => { -
+
{link.subMenus.map((sLink, index) => { return ( { target={link?.target} className='block rounded px-4 py-[10px] text-sm text-body-color hover:text-primary dark:text-dark-6 dark:hover:text-primary'> {/* 子菜单SubMenuItem */} - + {link?.icon && ( )}{' '} diff --git a/themes/starter/index.js b/themes/starter/index.js index 3ae161f6..c0311b77 100644 --- a/themes/starter/index.js +++ b/themes/starter/index.js @@ -27,6 +27,8 @@ import { Style } from './style' import Comment from '@/components/Comment' import replaceSearchResult from '@/components/Mark' import ShareBar from '@/components/ShareBar' +import DashboardBody from '@/components/ui/dashboard/DashboardBody' +import DashboardHeader from '@/components/ui/dashboard/DashboardHeader' import { useGlobal } from '@/lib/global' import { loadWowJS } from '@/lib/plugins/wow' import { SignIn, SignUp } from '@clerk/nextjs' @@ -177,6 +179,39 @@ const LayoutSlug = props => { ) } +/** + * 仪表盘 + * @param {*} props + * @returns + */ +const LayoutDashboard = props => { + const { post } = props + + return ( + <> +
+
+
+ {post && ( +
+ +
+ )} +
+
+
+ {/* 仪表盘 */} + + + + ) +} + +/** + * 搜索 + * @param {*} props + * @returns + */ const LayoutSearch = props => { const { keyword } = props const router = useRouter() @@ -491,6 +526,7 @@ export { LayoutArchive, LayoutBase, LayoutCategoryIndex, + LayoutDashboard, LayoutIndex, LayoutPostList, LayoutSearch, diff --git a/themes/theme.js b/themes/theme.js index aa5c07e5..c8760927 100644 --- a/themes/theme.js +++ b/themes/theme.js @@ -8,17 +8,50 @@ import { getQueryParam, getQueryVariable, isBrowser } from '../lib/utils' export const { THEMES = [] } = getConfig().publicRuntimeConfig /** - * 获取主体配置 + * 获取主题配置 + * @param {string} themeQuery - 主题查询参数(支持多个主题用逗号分隔) + * @returns {Promise} 主题配置对象 */ export const getThemeConfig = async themeQuery => { - if (themeQuery && themeQuery !== BLOG.THEME) { - const THEME_CONFIG = await import(`@/themes/${themeQuery}`).then( - m => m.THEME_CONFIG - ) - return THEME_CONFIG - } else { - return ThemeComponents?.THEME_CONFIG + // 如果 themeQuery 存在且不等于默认主题,处理多主题情况 + if (typeof themeQuery === 'string' && themeQuery.trim()) { + // 取 themeQuery 中第一个主题(以逗号为分隔符) + const themeName = themeQuery.split(',')[0].trim() + + // 如果 themeQuery 不等于当前默认主题,则加载指定主题的配置 + if (themeName !== BLOG.THEME) { + try { + // 动态导入主题配置 + const THEME_CONFIG = await import(`@/themes/${themeName}`) + .then(m => m.THEME_CONFIG) + .catch(err => { + console.error(`Failed to load theme ${themeName}:`, err) + return null // 主题加载失败时返回 null 或者其他默认值 + }) + + // 如果主题配置加载成功,返回配置 + if (THEME_CONFIG) { + return THEME_CONFIG + } else { + // 如果加载失败,返回默认主题配置 + console.warn( + `Loading ${themeName} failed. Falling back to default theme.` + ) + return ThemeComponents?.THEME_CONFIG + } + } catch (error) { + // 如果 import 过程中出现异常,返回默认主题配置 + console.error( + `Error loading theme configuration for ${themeName}:`, + error + ) + return ThemeComponents?.THEME_CONFIG + } + } } + + // 如果没有 themeQuery 或 themeQuery 与默认主题相同,返回默认主题配置 + return ThemeComponents?.THEME_CONFIG } /** @@ -38,48 +71,46 @@ export const getGlobalLayoutByTheme = themeQuery => { } } +/** + * 动态获取布局 + * @param {*} props + */ +export const DynamicLayout = props => { + const { router, theme } = props + const SelectedLayout = getLayoutByTheme({ router, theme }) + return +} + /** * 加载主题文件 - * 如果是 * @param {*} router + * @param {*} theme * @returns */ export const getLayoutByTheme = ({ router, theme }) => { const themeQuery = getQueryParam(router.asPath, 'theme') || theme - if (themeQuery !== BLOG.THEME) { - return dynamic( - () => - import(`@/themes/${themeQuery}`).then(m => { - setTimeout(() => { - checkThemeDOM() - }, 500) + const layoutName = getLayoutNameByPath(router.pathname, router.asPath) + const isDefaultTheme = !themeQuery || themeQuery === BLOG.THEME - const components = - m[getLayoutNameByPath(router.pathname, router.asPath)] - if (components) { - return components - } else { - return m.LayoutSlug - } - }), + const loadThemeComponents = componentsSource => { + const components = + componentsSource[layoutName] || componentsSource.LayoutSlug + setTimeout(fixThemeDOM, isDefaultTheme ? 100 : 500) // 根据主题选择延迟时间 + return components + } + + if (isDefaultTheme) { + return loadThemeComponents(ThemeComponents) + } else { + return dynamic( + () => import(`@/themes/${themeQuery}`).then(m => loadThemeComponents(m)), { ssr: true } ) - } else { - setTimeout(() => { - checkThemeDOM() - }, 100) - const components = - ThemeComponents[getLayoutNameByPath(router.pathname, router.asPath)] - if (components) { - return components - } else { - return ThemeComponents.LayoutSlug - } } } /** - * 根据路径 获取对应的layout + * 根据路径 获取对应的layout名称 * @param {*} path * @returns */ @@ -91,8 +122,9 @@ const getLayoutNameByPath = path => { /** * 切换主题时的特殊处理 + * 删除多余的元素 */ -const checkThemeDOM = () => { +const fixThemeDOM = () => { if (isBrowser) { const elements = document.querySelectorAll('[id^="theme-"]') if (elements?.length > 1) {