mirror of
https://github.com/d0zingcat/NotionNext.git
synced 2026-05-13 23:16:47 +00:00
@@ -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' // 缺省版本号
|
||||
|
||||
51
components/ui/dashboard/DashboardBody.js
Normal file
51
components/ui/dashboard/DashboardBody.js
Normal file
@@ -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 (
|
||||
<div className='flex flex-col md:flex-row w-full container gap-x-4 min-h-96 mx-auto mb-12 justify-center'>
|
||||
<div className='side-tabs w-full md:w-72'>
|
||||
<DashboardMenuList />
|
||||
</div>
|
||||
<div className='main-content-wrapper w-full'>
|
||||
{basePath === '/dashboard' && <DashboardItemHome />}
|
||||
{(basePath === '/dashboard/user-profile' ||
|
||||
basePath === '/dashboard/user-profile/security') && (
|
||||
<UserProfile
|
||||
appearance={{
|
||||
elements: {
|
||||
cardBox: 'w-full',
|
||||
rootBox: 'w-full'
|
||||
}
|
||||
}}
|
||||
className='bg-blue-300'
|
||||
routing='path'
|
||||
path='/dashboard/user-profile'
|
||||
/>
|
||||
)}
|
||||
{basePath === '/dashboard/balance' && <DashboardItemBalance />}
|
||||
{basePath === '/dashboard/membership' && <DashboardItemMembership />}
|
||||
{basePath === '/dashboard/order' && <DashboardItemOrder />}
|
||||
{basePath === '/dashboard/affiliate' && <DashboardItemAffliate />}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
27
components/ui/dashboard/DashboardButton.js
Normal file
27
components/ui/dashboard/DashboardButton.js
Normal file
@@ -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 (
|
||||
<button
|
||||
type='button'
|
||||
className='text-white bg-gray-800 hover:bg-gray-900 hover:ring-4 hover:ring-gray-300 focus:outline-none focus:ring-4 focus:ring-gray-300 font-medium rounded-lg text-sm px-5 py-2 me-2 dark:bg-gray-800 dark:hover:bg-gray-700 dark:focus:ring-gray-700 dark:border-gray-700'>
|
||||
<Link href='/dashboard'>仪表盘</Link>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
53
components/ui/dashboard/DashboardHeader.js
Normal file
53
components/ui/dashboard/DashboardHeader.js
Normal file
@@ -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 (
|
||||
<>
|
||||
<div className='flex w-full container mx-auto mt-12 mb-12 justify-ends'>
|
||||
{/* 头像昵称 */}
|
||||
<div className='flex items-center gap-4 w-full'>
|
||||
<LazyImage
|
||||
className='w-10 h-10 rounded-full'
|
||||
src={user?.imageUrl}
|
||||
alt={user?.fullName}
|
||||
/>
|
||||
|
||||
<div class='font-medium dark:text-white'>
|
||||
<div className='flex items-center gap-x-2'>
|
||||
<span>{user?.fullName}</span>
|
||||
<Link href='/dashboard/membership'>
|
||||
<span class='bg-gray-100 text-gray-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-gray-300'>
|
||||
普通用户
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
<div className='text-sm text-gray-500 gap-x-2 flex dark:text-gray-400'>
|
||||
<span>{user?.username}</span>
|
||||
<span>{formatDate(user?.createdAt)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 登出按钮 */}
|
||||
<div className='flex items-center'>
|
||||
<SignOutButton redirectUrl='/'>
|
||||
<button className='text-white bg-gray-800 hover:bg-gray-900 hover:ring-4 hover:ring-gray-300 focus:outline-none focus:ring-4 focus:ring-gray-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-gray-800 dark:hover:bg-gray-700 dark:focus:ring-gray-700 dark:border-gray-700'>
|
||||
<span className='text-nowrap'>
|
||||
<i className='fas fa-right-from-bracket' /> Sign Out
|
||||
</span>
|
||||
</button>
|
||||
</SignOutButton>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
187
components/ui/dashboard/DashboardItemAffliate.js
Normal file
187
components/ui/dashboard/DashboardItemAffliate.js
Normal file
@@ -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 (
|
||||
<div className='bg-white rounded-lg shadow-lg p-6 border'>
|
||||
<div className='grid grid-cols-4 gap-4'>
|
||||
{cards?.map((card, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`block max-w-sm p-6 text-center border cursor-pointer rounded-lg shadow ${card.className}`}>
|
||||
<h5 className='mb-2 text-2xl font-bold tracking-tight'>
|
||||
{card.title}
|
||||
</h5>
|
||||
<p className='font-normal'>{card.desc}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<form className='mt-6'>
|
||||
<div className='grid gap-6 mb-6 md:grid-cols-2'>
|
||||
<div>
|
||||
<label
|
||||
for='last_name'
|
||||
className='block mb-2 text-sm font-medium text-gray-900 dark:text-white'>
|
||||
推广总数
|
||||
</label>
|
||||
<input
|
||||
disabled
|
||||
type='text'
|
||||
id='last_name'
|
||||
className='bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500'
|
||||
placeholder='123'
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
for='company'
|
||||
className='block mb-2 text-sm font-medium text-gray-900 dark:text-white'>
|
||||
推广链接
|
||||
</label>
|
||||
<input
|
||||
disabled
|
||||
type='text'
|
||||
id='company'
|
||||
className='bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500'
|
||||
placeholder='https://tangly1024.com'
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
for='website'
|
||||
className='block mb-2 text-sm font-medium text-gray-900 dark:text-white'>
|
||||
推广佣金提成
|
||||
</label>
|
||||
<input
|
||||
disabled
|
||||
type='url'
|
||||
id='website'
|
||||
className='bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500'
|
||||
placeholder='5%'
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr className='my-6' />
|
||||
|
||||
<div className='grid gap-6 mb-6 md:grid-cols-2'>
|
||||
<div>
|
||||
<label
|
||||
for='first_name'
|
||||
className='block mb-2 text-sm font-medium text-gray-900 dark:text-white'>
|
||||
提现账号
|
||||
</label>
|
||||
<input
|
||||
type='text'
|
||||
id='first_name'
|
||||
className='bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500'
|
||||
placeholder='John'
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
for='visitors'
|
||||
className='block mb-2 text-sm font-medium text-gray-900 dark:text-white'>
|
||||
提现金额
|
||||
</label>
|
||||
<input
|
||||
type='number'
|
||||
id='visitors'
|
||||
className='bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500'
|
||||
placeholder=''
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex items-start mb-6'>
|
||||
<div className='flex items-center h-5'>
|
||||
<input
|
||||
id='remember'
|
||||
type='checkbox'
|
||||
value=''
|
||||
className='w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800'
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<label
|
||||
for='remember'
|
||||
className='ms-2 text-sm font-medium text-gray-900 dark:text-gray-300'>
|
||||
我以阅读并同意{' '}
|
||||
<Link
|
||||
href='/terms-of-use'
|
||||
className='text-blue-600 hover:underline dark:text-blue-500'>
|
||||
服务协议
|
||||
</Link>
|
||||
.
|
||||
</label>
|
||||
</div>
|
||||
<div className='flex gap-x-2'>
|
||||
<button
|
||||
type='submit'
|
||||
className='text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800'>
|
||||
提现RMB
|
||||
</button>
|
||||
<button
|
||||
type='submit'
|
||||
className='text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800'>
|
||||
提现到余额
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<ul className='text-gray-600 list-disc pl-6'>
|
||||
<li>推广说明:</li>
|
||||
<li className='font-bold'>这只是一个演示页面,不存在真实功能!</li>
|
||||
<li>
|
||||
如需提现请联系网站管理员,发送您的账号信息和收款码进行人工提现
|
||||
</li>
|
||||
<li>
|
||||
如果用户是通过您的推广链接购买的资源或者开通会员,则按照推广佣金比列奖励到您的佣金中
|
||||
</li>
|
||||
<li>
|
||||
如果用户是通过您的链接新注册的用户,推荐人是您,该用户购买资都会给你佣金
|
||||
</li>
|
||||
<li>
|
||||
如果用户是你的下级,用户使用其他推荐人链接购买,以上下级关系为准,优先给注册推荐人而不是推荐链接
|
||||
</li>
|
||||
<li>推广奖励金额保留一位小数点四舍五入。0.1之类的奖励金额不计算</li>
|
||||
<li>
|
||||
前台无法查看推广订单详情,如需查看详情可联系管理员截图查看详细记录和时间
|
||||
</li>
|
||||
</ul>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
137
components/ui/dashboard/DashboardItemBalance.js
Normal file
137
components/ui/dashboard/DashboardItemBalance.js
Normal file
@@ -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 (
|
||||
<div className='bg-white rounded-lg shadow-lg p-6 border'>
|
||||
<div>
|
||||
<h2 className='text-2xl font-bold mb-4'>余额充值中心</h2>
|
||||
<hr className='my-2' />
|
||||
</div>
|
||||
|
||||
{/* 余额卡片 */}
|
||||
<div className='grid grid-cols-3 gap-4'>
|
||||
{cards?.map((card, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`block max-w-sm p-6 text-center border cursor-pointer rounded-lg shadow ${card.className}`}
|
||||
onClick={() => handleCardSelect(index)}>
|
||||
<h5 className='mb-2 text-2xl font-bold tracking-tight'>
|
||||
{card.title}
|
||||
</h5>
|
||||
<p className='font-normal'>{card.desc}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<form className='mt-6'>
|
||||
<div className='py-2'>充值项目(充值比例:1元=1积分)</div>
|
||||
{/* 充值选项 */}
|
||||
<div className='grid gap-6 mb-6 grid-cols-4'>
|
||||
{cardData?.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`border rounded-lg text-center bg-gray-50 py-4 cursor-pointer ${
|
||||
selectedCard === index ? 'bg-blue-100' : ''
|
||||
}`}
|
||||
onClick={() => handleCardSelect(index)}>
|
||||
<h3 className='text-yellow-500 font-bold'>{item.points}</h3>
|
||||
<span>{item.price}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<hr className='my-6' />
|
||||
|
||||
<div className='grid gap-6 mb-6 md:grid-cols-2'>
|
||||
<div>
|
||||
<label
|
||||
htmlFor='amount'
|
||||
className='block mb-2 text-sm font-medium text-gray-900 dark:text-white'>
|
||||
充值其它数量
|
||||
</label>
|
||||
<input
|
||||
type='number'
|
||||
id='amount'
|
||||
className='bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500'
|
||||
placeholder='输入数量'
|
||||
value={amount}
|
||||
onChange={handleAmountChange}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex justify-between w-full'>
|
||||
<div>
|
||||
支付金额:<span className='text-red-500'>¥{amount}</span>
|
||||
</div>
|
||||
<button
|
||||
type='submit'
|
||||
className='text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800'>
|
||||
在线充值
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<ul className='text-gray-600 list-disc pl-6'>
|
||||
<li>充值说明:</li>
|
||||
<li className='font-bold'>这只是一个演示页面,不存在真实功能!</li>
|
||||
<li>充值最低额度为1积分</li>
|
||||
<li>充值汇率为1元=1积分,人民币和积分不能互相转换</li>
|
||||
<li>余额永久有效,无时间限制</li>
|
||||
</ul>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
64
components/ui/dashboard/DashboardItemHome.js
Normal file
64
components/ui/dashboard/DashboardItemHome.js
Normal file
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* 首页组件
|
||||
* @returns
|
||||
*/
|
||||
export default function DashboardItemHome() {
|
||||
return (
|
||||
<div className='w-full mx-auto'>
|
||||
{/* 提示消息 */}
|
||||
<div
|
||||
className='p-4 mb-4 text-xl font-bold text-green-800 rounded-lg bg-green-50 dark:bg-gray-800 dark:text-green-400'
|
||||
role='alert'>
|
||||
<span className='font-medium'>注意!</span>{' '}
|
||||
整个后台都只是页面效果,仅供演示查看,没有对接实际功能。
|
||||
</div>
|
||||
|
||||
{/* 页面说明 */}
|
||||
<div className='mb-8 text-lg text-gray-700 dark:text-gray-300'>
|
||||
<p>
|
||||
欢迎来到用户中心页面!在这里,您可以查看用户的账号信息与业务订单概况。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 进度条 */}
|
||||
<div className='mb-8'>
|
||||
<h3 className='text-xl text-gray-800 dark:text-white'>当前任务进度</h3>
|
||||
<div className='bg-gray-200 dark:bg-gray-700 rounded-full h-2.5 my-2'>
|
||||
<div
|
||||
className='bg-green-500 h-2.5 rounded-full'
|
||||
style={{ width: '75%' }}></div>
|
||||
</div>
|
||||
<p className='text-sm text-gray-500 dark:text-gray-400'>
|
||||
任务进度:75%
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 背景动画块 */}
|
||||
<div className='relative w-full h-64 rounded-lg bg-gradient-to-r from-blue-500 via-purple-500 to-pink-500 overflow-hidden'>
|
||||
<div className='absolute inset-0 w-full h-full animate-pulse bg-black opacity-50'></div>
|
||||
<div className='relative z-10 text-center text-white font-bold pt-24'>
|
||||
<h3 className='text-2xl'>实时数据分析</h3>
|
||||
<p className='text-lg'>监控您的系统数据,查看实时变化</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 数据卡片模块 */}
|
||||
<div className='grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 mb-8'>
|
||||
<div className='bg-white shadow-lg p-6 rounded-lg hover:scale-105 transition-all transform duration-300'>
|
||||
<h3 className='text-xl text-gray-800 dark:text-white'>今日访问量</h3>
|
||||
<p className='text-3xl text-green-600'>1,245</p>
|
||||
</div>
|
||||
<div className='bg-white shadow-lg p-6 rounded-lg hover:scale-105 transition-all transform duration-300'>
|
||||
<h3 className='text-xl text-gray-800 dark:text-white'>用户总数</h3>
|
||||
<p className='text-3xl text-blue-600'>12,300</p>
|
||||
</div>
|
||||
<div className='bg-white shadow-lg p-6 rounded-lg hover:scale-105 transition-all transform duration-300'>
|
||||
<h3 className='text-xl text-gray-800 dark:text-white'>
|
||||
系统健康状态
|
||||
</h3>
|
||||
<p className='text-3xl text-red-600'>正常</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
125
components/ui/dashboard/DashboardItemMembership.js
Normal file
125
components/ui/dashboard/DashboardItemMembership.js
Normal file
@@ -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 (
|
||||
<div className='bg-white rounded-lg shadow-lg p-6 border'>
|
||||
<div>
|
||||
<h2 className='text-2xl font-bold mb-4'>会员注册</h2>
|
||||
<hr className='my-2' />
|
||||
</div>
|
||||
|
||||
{/* 会员卡片 */}
|
||||
<div className='grid grid-cols-3 gap-4'>
|
||||
{memberships.map((membership, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`block max-w-sm p-6 text-center border cursor-pointer rounded-lg shadow ${
|
||||
selectedMembership === index ? 'bg-blue-100' : 'bg-gray-50'
|
||||
}`}
|
||||
onClick={() => handleMembershipSelect(index)}>
|
||||
<h5 className='mb-2 text-2xl font-bold tracking-tight'>
|
||||
{membership.title}
|
||||
</h5>
|
||||
<p className='font-normal'>所需积分:{membership.points} 积分</p>
|
||||
<p className='font-normal'>会员时长:{membership.duration}</p>
|
||||
<ul className='text-gray-600 mt-2'>
|
||||
{membership.benefits.map((benefit, i) => (
|
||||
<li key={i}>{benefit}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<form className='mt-6'>
|
||||
<div className='flex justify-between w-full mb-4'>
|
||||
<div>
|
||||
支付金额:<span className='text-red-500'>¥{amount}</span>
|
||||
</div>
|
||||
<button
|
||||
type='submit'
|
||||
className='text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800'>
|
||||
立即开通
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<ul className='text-gray-600 list-disc pl-6'>
|
||||
<li>开通会员说明:</li>
|
||||
<li className='font-bold'>这只是一个演示页面,不存在真实功能!</li>
|
||||
<li>本站会员账号权限为虚拟数字资源,开通后不可退款</li>
|
||||
<li>开通会员后可享有对应会员特权的商品折扣,免费权限</li>
|
||||
<li>会员特权到期后不享受特权</li>
|
||||
<li>重复购买特权到期时间累计增加</li>
|
||||
</ul>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
246
components/ui/dashboard/DashboardItemOrder.js
Normal file
246
components/ui/dashboard/DashboardItemOrder.js
Normal file
@@ -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: () => (
|
||||
<div className='flex items-center space-x-3'>
|
||||
<a
|
||||
href='#'
|
||||
className='font-medium text-blue-600 dark:text-blue-500 hover:underline'>
|
||||
编辑
|
||||
</a>
|
||||
<a
|
||||
href='#'
|
||||
className='font-medium text-red-600 dark:text-red-500 hover:underline'>
|
||||
删除
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
]
|
||||
|
||||
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 (
|
||||
<div className='bg-white rounded-lg shadow-lg p-6 border'>
|
||||
<div className='flex flex-col'>
|
||||
<Table columns={columns} data={data} />
|
||||
<Pagination
|
||||
currentPage={currentPage}
|
||||
totalPages={totalPages}
|
||||
onPageChange={onPageChange}
|
||||
/>
|
||||
<ul className='text-gray-600 list-disc pl-6'>
|
||||
<li>订单说明:</li>
|
||||
<li className='font-bold'>这只是一个演示页面,不存在真实功能!</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页组件
|
||||
*/
|
||||
const Pagination = ({ currentPage, totalPages, onPageChange }) => {
|
||||
const pages = Array.from({ length: totalPages }, (_, i) => i + 1)
|
||||
|
||||
return (
|
||||
<nav
|
||||
aria-label='page-navigation'
|
||||
className='w-full flex mx-auto justify-center items-center py-4'>
|
||||
<ul className='inline-flex -space-x-px text-sm'>
|
||||
{/* 上一页 */}
|
||||
<li>
|
||||
<button
|
||||
onClick={() => onPageChange(currentPage - 1)}
|
||||
disabled={currentPage === 1}
|
||||
className={`flex items-center justify-center px-3 h-8 ms-0 leading-tight border border-e-0 rounded-s-lg ${
|
||||
currentPage === 1
|
||||
? 'text-gray-400 bg-gray-200 cursor-not-allowed'
|
||||
: 'text-gray-500 bg-white hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white'
|
||||
}`}>
|
||||
上一页
|
||||
</button>
|
||||
</li>
|
||||
|
||||
{/* 页码列表 */}
|
||||
{pages.map(page => (
|
||||
<li key={page}>
|
||||
<button
|
||||
onClick={() => onPageChange(page)}
|
||||
className={`flex items-center justify-center px-3 h-8 leading-tight border ${
|
||||
currentPage === page
|
||||
? 'text-blue-600 bg-blue-50 hover:bg-blue-100 hover:text-blue-700 dark:border-gray-700 dark:bg-gray-700 dark:text-white'
|
||||
: 'text-gray-500 bg-white hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white'
|
||||
}`}>
|
||||
{page}
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
|
||||
{/* 下一页 */}
|
||||
<li>
|
||||
<button
|
||||
onClick={() => onPageChange(currentPage + 1)}
|
||||
disabled={currentPage === totalPages}
|
||||
className={`flex items-center justify-center px-3 h-8 leading-tight border rounded-e-lg ${
|
||||
currentPage === totalPages
|
||||
? 'text-gray-400 bg-gray-200 cursor-not-allowed'
|
||||
: 'text-gray-500 bg-white hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white'
|
||||
}`}>
|
||||
下一页
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 表格组件
|
||||
*/
|
||||
const Table = ({ columns, data }) => {
|
||||
return (
|
||||
<div className='relative overflow-x-auto shadow-md sm:rounded-lg'>
|
||||
<table className='w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400'>
|
||||
{/* 表头 */}
|
||||
<thead className='text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400'>
|
||||
<tr>
|
||||
<th scope='col' className='p-4 w-4'>
|
||||
<div className='flex items-center'>
|
||||
<input
|
||||
id='checkbox-all'
|
||||
type='checkbox'
|
||||
className='w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:focus:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600'
|
||||
/>
|
||||
<label htmlFor='checkbox-all' className='sr-only'>
|
||||
全选
|
||||
</label>
|
||||
</div>
|
||||
</th>
|
||||
{columns.map((column, index) => (
|
||||
<th
|
||||
key={index}
|
||||
scope='col'
|
||||
className={`${
|
||||
column.key === 'name'
|
||||
? 'px-6 py-3 w-[25%]'
|
||||
: 'px-4 py-3 w-[10%]'
|
||||
}`}>
|
||||
{column.label}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
{/* 表格内容 */}
|
||||
<tbody>
|
||||
{data.map((item, index) => (
|
||||
<tr
|
||||
key={index}
|
||||
className='bg-white border-b dark:bg-gray-800 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600'>
|
||||
<td className='w-4 p-4'>
|
||||
<div className='flex items-center'>
|
||||
<input
|
||||
id={`checkbox-${index}`}
|
||||
type='checkbox'
|
||||
className='w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:focus:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600'
|
||||
/>
|
||||
<label htmlFor={`checkbox-${index}`} className='sr-only'>
|
||||
选择
|
||||
</label>
|
||||
</div>
|
||||
</td>
|
||||
{columns.map((column, colIndex) => (
|
||||
<td
|
||||
key={colIndex}
|
||||
className={`${
|
||||
column.key === 'name'
|
||||
? 'px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white w-[25%]'
|
||||
: 'px-4 py-4 w-[10%]'
|
||||
}`}>
|
||||
{column.render
|
||||
? column.render(item[column.key])
|
||||
: item[column.key]}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
57
components/ui/dashboard/DashboardMenuList.js
Normal file
57
components/ui/dashboard/DashboardMenuList.js
Normal file
@@ -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 (
|
||||
<ul
|
||||
role='menu'
|
||||
className='side-tabs-list bg-white border rounded-lg shadow-lg p-2 space-y-2 mb-6'>
|
||||
{dashBoardMenus.map((item, index) => {
|
||||
// 判断当前菜单是否高亮
|
||||
const isActive = asPath === item.href
|
||||
return (
|
||||
<li
|
||||
role='menuitem'
|
||||
key={index}
|
||||
className={`rounded-lg cursor-pointer block ${
|
||||
isActive ? 'bg-blue-100 text-blue-600' : 'hover:bg-gray-100'
|
||||
}`}>
|
||||
<Link
|
||||
href={item.href}
|
||||
className='block py-2 px-4 w-full items-center justify-center'>
|
||||
<i className={`${item.icon} w-6 mr-2`}></i>
|
||||
<span className='whitespace-nowrap'>{item.title}</span>
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
@@ -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]))
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
12
pages/404.js
12
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 <Layout {...props} />
|
||||
const router = useRouter()
|
||||
const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG)
|
||||
return <DynamicLayout theme={theme} router={router} {...props} />
|
||||
}
|
||||
|
||||
export async function getStaticProps(req) {
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
{/* 文章布局 */}
|
||||
<Layout {...props} />
|
||||
<DynamicLayout theme={theme} router={router} {...props} />
|
||||
{/* 解锁密码提示框 */}
|
||||
{post?.password && post?.password !== '' && !lock && <Notification />}
|
||||
{/* 导流工具 */}
|
||||
|
||||
@@ -45,7 +45,6 @@ const MyApp = ({ Component, pageProps }) => {
|
||||
// 整体布局
|
||||
const GLayout = useCallback(
|
||||
props => {
|
||||
// 根据页面路径加载不同Layout文件
|
||||
const Layout = getGlobalLayoutByTheme(queryParam)
|
||||
return <Layout {...props} />
|
||||
},
|
||||
|
||||
@@ -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 <Layout {...props} />
|
||||
const router = useRouter()
|
||||
const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG)
|
||||
return <DynamicLayout theme={theme} router={router} {...props} />
|
||||
}
|
||||
|
||||
export async function getStaticProps({ locale }) {
|
||||
|
||||
101
pages/auth/index.js
Normal file
101
pages/auth/index.js
Normal file
@@ -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 <Slug {...props} />
|
||||
}
|
||||
|
||||
/**
|
||||
* 服务端接收参数处理
|
||||
* @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
|
||||
@@ -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 <Layout {...props} />
|
||||
const router = useRouter()
|
||||
const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG)
|
||||
return <DynamicLayout theme={theme} router={router} {...props} />
|
||||
}
|
||||
|
||||
export async function getStaticProps({ params: { category }, locale }) {
|
||||
|
||||
@@ -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 <Layout {...props} />
|
||||
const router = useRouter()
|
||||
const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG)
|
||||
return <DynamicLayout theme={theme} router={router} {...props} />
|
||||
}
|
||||
|
||||
export async function getStaticProps({ params: { category, page } }) {
|
||||
|
||||
@@ -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 <Layout {...props} />
|
||||
const router = useRouter()
|
||||
const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG)
|
||||
return <DynamicLayout theme={theme} router={router} {...props} />
|
||||
}
|
||||
|
||||
export async function getStaticProps({ locale }) {
|
||||
|
||||
91
pages/dashboard/[[...index]].js
Normal file
91
pages/dashboard/[[...index]].js
Normal file
@@ -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 <DynamicLayout theme={theme} router={router} {...props} />
|
||||
}
|
||||
|
||||
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
|
||||
@@ -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 <Layout {...props} />
|
||||
const router = useRouter()
|
||||
const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG)
|
||||
return <DynamicLayout theme={theme} router={router} {...props} />
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 <Layout {...props} />
|
||||
const router = useRouter()
|
||||
const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG)
|
||||
return <DynamicLayout theme={theme} router={router} {...props} />
|
||||
}
|
||||
|
||||
export async function getStaticPaths({ locale }) {
|
||||
|
||||
@@ -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 <Layout {...props} />
|
||||
const router = useRouter()
|
||||
const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG)
|
||||
return <DynamicLayout theme={theme} router={router} {...props} />
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 <Layout {...props} />
|
||||
const router = useRouter()
|
||||
const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG)
|
||||
return <DynamicLayout theme={theme} router={router} {...props} />
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 <Layout {...props} />
|
||||
const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG)
|
||||
return <DynamicLayout theme={theme} router={router} {...props} />
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 <Layout {...props} />
|
||||
const router = useRouter()
|
||||
const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG)
|
||||
return <DynamicLayout theme={theme} router={router} {...props} />
|
||||
}
|
||||
|
||||
export async function getStaticProps(req) {
|
||||
|
||||
@@ -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 <Layout {...props} />
|
||||
const router = useRouter()
|
||||
const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG)
|
||||
return <DynamicLayout theme={theme} router={router} {...props} />
|
||||
}
|
||||
|
||||
export async function getStaticProps(req) {
|
||||
|
||||
@@ -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 <Layout {...props} />
|
||||
const router = useRouter()
|
||||
const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG)
|
||||
return <DynamicLayout theme={theme} router={router} {...props} />
|
||||
}
|
||||
|
||||
export async function getStaticProps({ params: { tag }, locale }) {
|
||||
|
||||
@@ -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 <Layout {...props} />
|
||||
const router = useRouter()
|
||||
const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG)
|
||||
return <DynamicLayout theme={theme} router={router} {...props} />
|
||||
}
|
||||
|
||||
export async function getStaticProps({ params: { tag, page }, locale }) {
|
||||
|
||||
@@ -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 <Layout {...props} />
|
||||
const router = useRouter()
|
||||
const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG)
|
||||
return <DynamicLayout theme={theme} router={router} {...props} />
|
||||
}
|
||||
|
||||
export async function getStaticProps(req) {
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
{/* 移动端底部导航按钮 */}
|
||||
<div className='pb-2 dark:bg-hexo-black-gray bottom-button-group md:hidden w-screen h-14 px-4 fixed flex items-center justify-between right-left bottom-0 z-30 bg-white border-t dark:border-gray-800'>
|
||||
<div className='w-full'>
|
||||
<MobileButtonPageNav />
|
||||
</div>
|
||||
<div className='md:hidden fixed bottom-0 left-0 z-50 w-full h-16 bg-white border-t border-gray-200 dark:bg-gray-700 dark:border-gray-600'>
|
||||
<div
|
||||
className={`grid h-full max-w-lg mx-auto font-medium ${showTocButton && 'grid-cols-2'}`}>
|
||||
<button
|
||||
type='button'
|
||||
onClick={togglePageNavVisible}
|
||||
className='inline-flex flex-col items-center justify-center px-5 border-gray-200 border-x hover:bg-gray-50 dark:hover:bg-gray-800 group dark:border-gray-600'>
|
||||
<i className='fa-book fas w-5 h-5 mb-2 text-gray-500 dark:text-gray-400 group-hover:text-gray-600 dark:group-hover:text-gray-500' />
|
||||
<span class='text-sm text-gray-500 dark:text-gray-400 group-hover:text-gray-600 dark:group-hover:text-gray-500'>
|
||||
{locale.COMMON.ARTICLE_LIST}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
{showTocButton && (
|
||||
<div className='w-full'>
|
||||
<MobileButtonCatalog />
|
||||
</div>
|
||||
<button
|
||||
type='button'
|
||||
onClick={toggleToc}
|
||||
className='inline-flex flex-col items-center justify-center px-5 border-gray-200 border-x hover:bg-gray-50 dark:hover:bg-gray-800 group dark:border-gray-600'>
|
||||
<i className='fa-list-ol fas w-5 h-5 mb-2 text-gray-500 dark:text-gray-400 group-hover:text-gray-600 dark:group-hover:text-gray-500' />
|
||||
<span class='text-sm text-gray-500 dark:text-gray-400 group-hover:text-gray-600 dark:group-hover:text-gray-500'>
|
||||
{locale.COMMON.TABLE_OF_CONTENTS}
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<div
|
||||
onClick={toggleToc}
|
||||
className={
|
||||
'text-black flex justify-center items-center dark:text-gray-200 dark:bg-hexo-black-gray py-2 px-2'
|
||||
}>
|
||||
<a
|
||||
id='toc-button'
|
||||
className={
|
||||
'space-x-4 cursor-pointer hover:scale-150 transform duration-200'
|
||||
}>
|
||||
<i className='fa-list-ol fas' />
|
||||
<span>{locale.COMMON.TABLE_OF_CONTENTS}</span>
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -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 (
|
||||
<div
|
||||
onClick={togglePageNavVisible}
|
||||
className={
|
||||
'text-black flex justify-center items-center dark:text-gray-200 dark:bg-hexo-black-gray py-2 px-2'
|
||||
}>
|
||||
<a
|
||||
id='nav-button'
|
||||
className={
|
||||
'space-x-4 cursor-pointer hover:scale-150 transform duration-200'
|
||||
}>
|
||||
<i className='fa-book fas' />
|
||||
<span>{locale.COMMON.ARTICLE_LIST}</span>
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -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 (
|
||||
<>
|
||||
<div className='container grow'>
|
||||
<div className='flex flex-wrap justify-center -mx-4'>
|
||||
<div id='container-inner' className='w-full p-4'>
|
||||
{post && (
|
||||
<div id='article-wrapper' className='mx-auto'>
|
||||
<NotionPage {...props} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* 仪表盘 */}
|
||||
<DashboardHeader />
|
||||
<DashboardBody />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
Layout404,
|
||||
LayoutArchive,
|
||||
LayoutBase,
|
||||
LayoutCategoryIndex,
|
||||
LayoutDashboard,
|
||||
LayoutIndex,
|
||||
LayoutPostList,
|
||||
LayoutSearch,
|
||||
|
||||
@@ -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) {
|
||||
</button>
|
||||
</SignInButton>
|
||||
</SignedOut>
|
||||
<UserButton />
|
||||
<SignedIn>
|
||||
<UserButton />
|
||||
<DashboardButton />
|
||||
</SignedIn>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
<div className='container grow'>
|
||||
<div className='flex flex-wrap justify-center -mx-4'>
|
||||
<div id='container-inner' className='w-full p-4'>
|
||||
{post && (
|
||||
<div id='article-wrapper' className='mx-auto'>
|
||||
<NotionPage {...props} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* 仪表盘 */}
|
||||
<DashboardHeader />
|
||||
<DashboardBody />
|
||||
</>
|
||||
)
|
||||
}
|
||||
export {
|
||||
Layout404,
|
||||
LayoutArchive,
|
||||
LayoutBase,
|
||||
LayoutCategoryIndex,
|
||||
LayoutDashboard,
|
||||
LayoutIndex,
|
||||
LayoutPostList,
|
||||
LayoutSearch,
|
||||
|
||||
@@ -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 => {
|
||||
</SignedOut>
|
||||
<SignedIn>
|
||||
<UserButton />
|
||||
<DashboardButton />
|
||||
</SignedIn>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -42,7 +42,7 @@ export const MenuItem = ({ link }) => {
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<div className='submenu relative left-0 top-full hidden w-[250px] rounded-sm bg-white p-4 transition-[top] duration-300 group-hover:opacity-100 dark:bg-dark-2 lg:invisible lg:absolute lg:top-[110%] lg:block lg:opacity-0 lg:shadow-lg lg:group-hover:visible lg:group-hover:top-full'>
|
||||
<div className='submenu border dark:border-gray-600 relative left-0 top-full hidden w-[250px] rounded-sm bg-white p-4 transition-[top] duration-300 group-hover:opacity-100 dark:bg-dark-2 lg:invisible lg:absolute lg:top-[110%] lg:block lg:opacity-0 lg:shadow-lg lg:group-hover:visible lg:group-hover:top-full'>
|
||||
{link.subMenus.map((sLink, index) => {
|
||||
return (
|
||||
<Link
|
||||
@@ -51,7 +51,7 @@ export const MenuItem = ({ link }) => {
|
||||
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 */}
|
||||
<span className='text-sm ml-4 whitespace-nowrap'>
|
||||
<span className='text-md ml-2 whitespace-nowrap'>
|
||||
{link?.icon && (
|
||||
<i className={sLink.icon + ' mr-2 my-auto'} />
|
||||
)}{' '}
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
<div className='container grow'>
|
||||
<div className='flex flex-wrap justify-center -mx-4'>
|
||||
<div id='container-inner' className='w-full p-4'>
|
||||
{post && (
|
||||
<div id='article-wrapper' className='mx-auto'>
|
||||
<NotionPage {...props} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* 仪表盘 */}
|
||||
<DashboardHeader />
|
||||
<DashboardBody />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索
|
||||
* @param {*} props
|
||||
* @returns
|
||||
*/
|
||||
const LayoutSearch = props => {
|
||||
const { keyword } = props
|
||||
const router = useRouter()
|
||||
@@ -491,6 +526,7 @@ export {
|
||||
LayoutArchive,
|
||||
LayoutBase,
|
||||
LayoutCategoryIndex,
|
||||
LayoutDashboard,
|
||||
LayoutIndex,
|
||||
LayoutPostList,
|
||||
LayoutSearch,
|
||||
|
||||
106
themes/theme.js
106
themes/theme.js
@@ -8,17 +8,50 @@ import { getQueryParam, getQueryVariable, isBrowser } from '../lib/utils'
|
||||
export const { THEMES = [] } = getConfig().publicRuntimeConfig
|
||||
|
||||
/**
|
||||
* 获取主体配置
|
||||
* 获取主题配置
|
||||
* @param {string} themeQuery - 主题查询参数(支持多个主题用逗号分隔)
|
||||
* @returns {Promise<object>} 主题配置对象
|
||||
*/
|
||||
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 <SelectedLayout {...props} />
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载主题文件
|
||||
* 如果是
|
||||
* @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) {
|
||||
|
||||
Reference in New Issue
Block a user