diff --git a/components/CursorDot.js b/components/CursorDot.js new file mode 100644 index 00000000..cba42327 --- /dev/null +++ b/components/CursorDot.js @@ -0,0 +1,97 @@ +import { useEffect } from 'react'; + +const CursorDot = () => { + useEffect(() => { + // 创建小白点元素 + const dot = document.createElement('div'); + dot.classList.add('cursor-dot'); + document.body.appendChild(dot); + + // 鼠标坐标和缓动目标坐标 + let mouse = { x: -100, y: -100 }; // 初始位置在屏幕外 + let dotPos = { x: mouse.x, y: mouse.y }; + + // 监听鼠标移动 + const handleMouseMove = (e) => { + mouse.x = e.clientX; + mouse.y = e.clientY; + }; + document.addEventListener('mousemove', handleMouseMove); + + // 监听鼠标悬停在可点击对象上的事件 + const handleMouseEnter = () => { + dot.classList.add('cursor-dot-hover'); // 添加放大样式 + }; + const handleMouseLeave = () => { + dot.classList.remove('cursor-dot-hover'); // 移除放大样式 + }; + + + // 为所有可点击元素和包含 hover 或 group-hover 类名的元素添加事件监听 + const clickableElements = document.querySelectorAll( + 'a, button, [role="button"], [onclick], [cursor="pointer"], [class*="hover"], [class*="group-hover"], [class*="cursor-pointer"]' + ); + clickableElements.forEach((el) => { + el.addEventListener('mouseenter', handleMouseEnter); + el.addEventListener('mouseleave', handleMouseLeave); + }); + + // 动画循环:延迟更新小白点位置 + const updateDotPosition = () => { + const damping = 0.2; // 阻尼系数,值越小延迟越明显 + dotPos.x += (mouse.x - dotPos.x) * damping; + dotPos.y += (mouse.y - dotPos.y) * damping; + + // 更新DOM + dot.style.left = `${dotPos.x}px`; + dot.style.top = `${dotPos.y}px`; + + requestAnimationFrame(updateDotPosition); + }; + + // 启动动画 + updateDotPosition(); + + // 清理函数 + return () => { + document.removeEventListener('mousemove', handleMouseMove); + clickableElements.forEach((el) => { + el.removeEventListener('mouseenter', handleMouseEnter); + el.removeEventListener('mouseleave', handleMouseLeave); + }); + document.body.removeChild(dot); + }; + }, []); + + return ( + + ); +}; + +export default CursorDot; \ No newline at end of file diff --git a/components/IconFont.js b/components/IconFont.js index a363eae8..72b8349b 100644 --- a/components/IconFont.js +++ b/components/IconFont.js @@ -12,7 +12,7 @@ export default function IconFont() { useEffect(() => { loadExternalResource('/webfonts/iconfont.js') .then(u => { - console.log('iconfont loaded'); + console.log('iconfont loaded:', u); // 查找所有 标签且 class 包含 'icon-' const iElements = document.querySelectorAll('i[class*="icon-"]'); @@ -30,7 +30,7 @@ export default function IconFont() { // 替换原来的 元素 element.replaceWith(svgElement); - console.log(`Replaced with class "${className}" to `); + // console.log(`Replaced with class "${className}" to `); } }); }) diff --git a/components/Lenis.js b/components/Lenis.js new file mode 100644 index 00000000..43b1c062 --- /dev/null +++ b/components/Lenis.js @@ -0,0 +1,69 @@ +import { useEffect, useRef } from 'react' +import { loadExternalResource } from '@/lib/utils' + +/** + * 滚动阻尼特效 + * 目前只用在proxio主题 + * @returns + */ +const Lenis = () => { + const lenisRef = useRef(null) // 用于存储 Lenis 实例 + + useEffect(() => { + // 异步加载 + async function loadLenis() { + loadExternalResource('/js/lenis.js', 'js').then(() => { + // console.log('Lenis', window.Lenis) + if (!window.Lenis) { + console.error('Lenis not loaded') + return + } + const Lenis = window.Lenis + + // 创建 Lenis 实例 + const lenis = new Lenis({ + duration: 1.2, + easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)), // https://www.desmos.com/calculator/brs54l4xou + direction: 'vertical', // vertical, horizontal + gestureDirection: 'vertical', // vertical, horizontal, both + smooth: true, + mouseMultiplier: 1, + smoothTouch: false, + touchMultiplier: 2, + infinite: false, + }) + + // 存储实例到 ref + lenisRef.current = lenis + + // 监听滚动事件 + // lenis.on('scroll', ({ scroll, limit, velocity, direction, progress }) => { + // // console.log({ scroll, limit, velocity, direction, progress }) + // }) + + // 动画帧循环 + function raf(time) { + lenis.raf(time) + requestAnimationFrame(raf) + } + + requestAnimationFrame(raf) + }) + } + + loadLenis() + + return () => { + // 在组件卸载时清理资源 + if (lenisRef.current) { + lenisRef.current.destroy() // 销毁 Lenis 实例 + lenisRef.current = null + // console.log('Lenis instance destroyed') + } + } + }, []) + + return <> +} + +export default Lenis diff --git a/public/images/themes-preview/proxio.png b/public/images/themes-preview/proxio.png new file mode 100644 index 00000000..8aa0a3e1 Binary files /dev/null and b/public/images/themes-preview/proxio.png differ diff --git a/public/js/lenis.js b/public/js/lenis.js new file mode 100644 index 00000000..0e48e0c9 --- /dev/null +++ b/public/js/lenis.js @@ -0,0 +1,2 @@ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t||self).Lenis=e()}(this,function(){function t(t,e){for(var i=0;i1,hasPointer:!!window.navigator.msPointerEnabled,hasKeyDown:"onkeydown"in document,isFirefox:navigator.userAgent.indexOf("Firefox")>-1}),i(this,l)[l]=Object.assign({mouseMultiplier:1,touchMultiplier:2,firefoxMultiplier:15,keyStep:120,preventTouch:!1,unpreventTouchClass:"vs-touchmove-allowed",useKeyboard:!0,useTouch:!0},t),i(this,a)[a]=new n,i(this,c)[c]={y:0,x:0,deltaX:0,deltaY:0},i(this,u)[u]={x:null,y:null},i(this,d)[d]=null,void 0!==i(this,l)[l].passive&&(this.listenerOptions={passive:i(this,l)[l].passive})}var e=t.prototype;return e._notify=function(t){var e=i(this,c)[c];e.x+=e.deltaX,e.y+=e.deltaY,i(this,a)[a].emit(s,{x:e.x,y:e.y,deltaX:e.deltaX,deltaY:e.deltaY,originalEvent:t})},e._bind=function(){r.hasWheelEvent&&i(this,h)[h].addEventListener("wheel",this._onWheel,this.listenerOptions),r.hasMouseWheelEvent&&i(this,h)[h].addEventListener("mousewheel",this._onMouseWheel,this.listenerOptions),r.hasTouch&&i(this,l)[l].useTouch&&(i(this,h)[h].addEventListener("touchstart",this._onTouchStart,this.listenerOptions),i(this,h)[h].addEventListener("touchmove",this._onTouchMove,this.listenerOptions)),r.hasPointer&&r.hasTouchWin&&(i(this,d)[d]=document.body.style.msTouchAction,document.body.style.msTouchAction="none",i(this,h)[h].addEventListener("MSPointerDown",this._onTouchStart,!0),i(this,h)[h].addEventListener("MSPointerMove",this._onTouchMove,!0)),r.hasKeyDown&&i(this,l)[l].useKeyboard&&document.addEventListener("keydown",this._onKeyDown)},e._unbind=function(){r.hasWheelEvent&&i(this,h)[h].removeEventListener("wheel",this._onWheel),r.hasMouseWheelEvent&&i(this,h)[h].removeEventListener("mousewheel",this._onMouseWheel),r.hasTouch&&(i(this,h)[h].removeEventListener("touchstart",this._onTouchStart),i(this,h)[h].removeEventListener("touchmove",this._onTouchMove)),r.hasPointer&&r.hasTouchWin&&(document.body.style.msTouchAction=i(this,d)[d],i(this,h)[h].removeEventListener("MSPointerDown",this._onTouchStart,!0),i(this,h)[h].removeEventListener("MSPointerMove",this._onTouchMove,!0)),r.hasKeyDown&&i(this,l)[l].useKeyboard&&document.removeEventListener("keydown",this._onKeyDown)},e.on=function(t,e){i(this,a)[a].on(s,t,e);var o=i(this,a)[a].e;o&&o[s]&&1===o[s].length&&this._bind()},e.off=function(t,e){i(this,a)[a].off(s,t,e);var o=i(this,a)[a].e;(!o[s]||o[s].length<=0)&&this._unbind()},e.destroy=function(){i(this,a)[a].off(),this._unbind()},t}()}()}(e),e.exports}();function l(t,e){var i=t%e;return(e>0&&i<0||e<0&&i>0)&&(i+=e),i}var h=["duration","easing"],a=/*#__PURE__*/function(){function t(){}var o=t.prototype;return o.to=function(t,e){var o=this,n=void 0===e?{}:e,r=n.duration,s=void 0===r?1:r,l=n.easing,a=void 0===l?function(t){return t}:l,c=function(t,e){if(null==t)return{};var i,o,n={},r=Object.keys(t);for(o=0;o=0||(n[i]=t[i]);return n}(n,h);this.target=t,this.fromKeys=i({},c),this.toKeys=i({},c),this.keys=Object.keys(i({},c)),this.keys.forEach(function(e){o.fromKeys[e]=t[e]}),this.duration=s,this.easing=a,this.currentTime=0,this.isRunning=!0},o.stop=function(){this.isRunning=!1},o.raf=function(t){var e=this;if(this.isRunning){this.currentTime=Math.min(this.currentTime+t,this.duration);var i=this.progress>=1?1:this.easing(this.progress);this.keys.forEach(function(t){var o=e.fromKeys[t];e.target[t]=o+(e.toKeys[t]-o)*i}),1===i&&this.stop()}},e(t,[{key:"progress",get:function(){return this.currentTime/this.duration}}]),t}();/*#__PURE__*/ +return function(t){var i,n;function r(e){var i,o,n,r,l=void 0===e?{}:e,h=l.duration,c=void 0===h?1.2:h,u=l.easing,d=void 0===u?function(t){return Math.min(1,1.001-Math.pow(2,-10*t))}:u,p=l.smooth,f=void 0===p||p,v=l.mouseMultiplier,w=void 0===v?1:v,y=l.smoothTouch,m=void 0!==y&&y,g=l.touchMultiplier,b=void 0===g?2:g,T=l.direction,M=void 0===T?"vertical":T,S=l.gestureDirection,_=void 0===S?"vertical":S,O=l.infinite,E=void 0!==O&&O,W=l.wrapper,x=void 0===W?window:W,D=l.content,N=void 0===D?document.body:D;(r=t.call(this)||this).onWindowResize=function(){r.wrapperWidth=window.innerWidth,r.wrapperHeight=window.innerHeight},r.onWrapperResize=function(t){var e=t[0];if(e){var i=e.contentRect;r.wrapperWidth=i.width,r.wrapperHeight=i.height}},r.onContentResize=function(t){var e=t[0];if(e){var i=e.contentRect;r.contentWidth=i.width,r.contentHeight=i.height}},r.onVirtualScroll=function(t){var e=t.deltaY,i=t.deltaX,o=t.originalEvent;if(!("vertical"===r.gestureDirection&&0===e||"horizontal"===r.gestureDirection&&0===i)){var n=!!o.composedPath().find(function(t){return t.hasAttribute&&t.hasAttribute("data-lenis-prevent")});o.ctrlKey||n||(r.smooth=o.changedTouches?r.smoothTouch:r.options.smooth,r.stopped?o.preventDefault():r.smooth&&4!==o.buttons&&(r.smooth&&o.preventDefault(),r.targetScroll-="both"===r.gestureDirection?i+e:"horizontal"===r.gestureDirection?i:e,r.scrollTo(r.targetScroll)))}},r.onScroll=function(t){r.isScrolling&&r.smooth||(r.targetScroll=r.scroll=r.lastScroll=r.wrapperNode[r.scrollProperty],r.notify())},window.lenisVersion="0.2.28",r.options={duration:c,easing:d,smooth:f,mouseMultiplier:w,smoothTouch:m,touchMultiplier:b,direction:M,gestureDirection:_,infinite:E,wrapper:x,content:N},r.duration=c,r.easing=d,r.smooth=f,r.mouseMultiplier=w,r.smoothTouch=m,r.touchMultiplier=b,r.direction=M,r.gestureDirection=_,r.infinite=E,r.wrapperNode=x,r.contentNode=N,r.wrapperNode.addEventListener("scroll",r.onScroll),r.wrapperNode===window?(r.wrapperNode.addEventListener("resize",r.onWindowResize),r.onWindowResize()):(r.wrapperHeight=r.wrapperNode.offsetHeight,r.wrapperWidth=r.wrapperNode.offsetWidth,r.wrapperObserver=new ResizeObserver(r.onWrapperResize),r.wrapperObserver.observe(r.wrapperNode)),r.contentHeight=r.contentNode.offsetHeight,r.contentWidth=r.contentNode.offsetWidth,r.contentObserver=new ResizeObserver(r.onContentResize),r.contentObserver.observe(r.contentNode),r.targetScroll=r.scroll=r.lastScroll=r.wrapperNode[r.scrollProperty],r.animate=new a;var P=(null==(i=navigator)||null==(o=i.userAgentData)?void 0:o.platform)||(null==(n=navigator)?void 0:n.platform)||"unknown";return r.virtualScroll=new s({el:r.wrapperNode,firefoxMultiplier:50,mouseMultiplier:r.mouseMultiplier*(P.includes("Win")||P.includes("Linux")?.84:.4),touchMultiplier:r.touchMultiplier,passive:!1,useKeyboard:!1,useTouch:!0}),r.virtualScroll.on(r.onVirtualScroll),r}n=t,(i=r).prototype=Object.create(n.prototype),i.prototype.constructor=i,o(i,n);var h=r.prototype;return h.start=function(){var t=this.wrapperNode;this.wrapperNode===window&&(t=document.documentElement),t.classList.remove("lenis-stopped"),this.stopped=!1},h.stop=function(){var t=this.wrapperNode;this.wrapperNode===window&&(t=document.documentElement),t.classList.add("lenis-stopped"),this.stopped=!0,this.animate.stop()},h.destroy=function(){var t;this.wrapperNode===window&&this.wrapperNode.removeEventListener("resize",this.onWindowResize),this.wrapperNode.removeEventListener("scroll",this.onScroll),this.virtualScroll.destroy(),null==(t=this.wrapperObserver)||t.disconnect(),this.contentObserver.disconnect()},h.raf=function(t){var e=t-(this.now||0);this.now=t,!this.stopped&&this.smooth&&(this.lastScroll=this.scroll,this.animate.raf(.001*e),this.scroll===this.targetScroll&&(this.lastScroll=this.scroll),this.isScrolling&&(this.setScroll(this.scroll),this.notify()),this.isScrolling=this.scroll!==this.targetScroll)},h.setScroll=function(t){var e=this.infinite?l(t,this.limit):t;"horizontal"===this.direction?this.wrapperNode.scrollTo(e,0):this.wrapperNode.scrollTo(0,e)},h.notify=function(){var t=this.infinite?l(this.scroll,this.limit):this.scroll;this.emit("scroll",{scroll:t,limit:this.limit,velocity:this.velocity,direction:0===this.velocity?0:this.velocity>0?1:-1,progress:t/this.limit})},h.scrollTo=function(t,e){var i=void 0===e?{}:e,o=i.offset,n=void 0===o?0:o,r=i.immediate,s=void 0!==r&&r,l=i.duration,h=void 0===l?this.duration:l,a=i.easing,c=void 0===a?this.easing:a;if(null!=t&&!this.stopped){var u;if("number"==typeof t)u=t;else if("top"===t||"#top"===t)u=0;else if("bottom"===t)u=this.limit;else{var d;if("string"==typeof t)d=document.querySelector(t);else{if(null==t||!t.nodeType)return;d=t}if(!d)return;var p=0;if(this.wrapperNode!==window){var f=this.wrapperNode.getBoundingClientRect();p="horizontal"===this.direction?f.left:f.top}var v=d.getBoundingClientRect();u=("horizontal"===this.direction?v.left:v.top)+this.scroll-p}u+=n,this.targetScroll=this.infinite?u:Math.max(0,Math.min(u,this.limit)),!this.smooth||s?(this.animate.stop(),this.scroll=this.lastScroll=this.targetScroll,this.setScroll(this.targetScroll)):this.animate.to(this,{duration:h,easing:c,scroll:this.targetScroll})}},e(r,[{key:"scrollProperty",get:function(){return this.wrapperNode===window?"horizontal"===this.direction?"scrollX":"scrollY":"horizontal"===this.direction?"scrollLeft":"scrollTop"}},{key:"limit",get:function(){return"horizontal"===this.direction?this.contentWidth-this.wrapperWidth:this.contentHeight-this.wrapperHeight}},{key:"velocity",get:function(){return this.scroll-this.lastScroll}}]),r}(r)}); \ No newline at end of file diff --git a/themes/proxio/components/Announcement.js b/themes/proxio/components/Announcement.js new file mode 100644 index 00000000..24de87eb --- /dev/null +++ b/themes/proxio/components/Announcement.js @@ -0,0 +1,30 @@ +// import { useGlobal } from '@/lib/global' +import dynamic from 'next/dynamic' + +const NotionPage = dynamic(() => import('@/components/NotionPage')) + +/** + * Magzine主题的公告 + */ +const Announcement = ({ post, className }) => { + // const { locale } = useGlobal() + if (post?.blockMap) { + return ( +
+
+ {/*
{locale.COMMON.ANNOUNCEMENT}
*/} + {post && ( +
+ +
+ )} +
+
+ ) + } else { + return <> + } +} +export default Announcement diff --git a/themes/proxio/components/ArticleLock.js b/themes/proxio/components/ArticleLock.js new file mode 100644 index 00000000..3a5f4b4c --- /dev/null +++ b/themes/proxio/components/ArticleLock.js @@ -0,0 +1,53 @@ +import { useGlobal } from '@/lib/global' +import { useEffect, useRef } from 'react' + +/** + * 加密文章校验组件 + * @param {password, validPassword} props + * @param password 正确的密码 + * @param validPassword(bool) 回调函数,校验正确回调入参为true + * @returns + */ +export const ArticleLock = props => { + const { validPassword } = props + const { locale } = useGlobal() + + const submitPassword = () => { + const p = document.getElementById('password') + if (!validPassword(p?.value)) { + const tips = document.getElementById('tips') + if (tips) { + tips.innerHTML = '' + tips.innerHTML = `
${locale.COMMON.PASSWORD_ERROR}
` + } + } + } + + const passwordInputRef = useRef(null) + useEffect(() => { + // 选中密码输入框并将其聚焦 + passwordInputRef.current.focus() + }, []) + + return
+
+
{locale.COMMON.ARTICLE_LOCK_TIPS}
+
+ { + if (e.key === 'Enter') { + submitPassword() + } + }} + ref={passwordInputRef} // 绑定ref到passwordInputRef变量 + className='outline-none w-full text-sm pl-5 rounded-l transition focus:shadow-lg font-light leading-10 text-black dark:bg-gray-500 bg-gray-50' + > +
+  {locale.COMMON.SUBMIT} +
+
+
+
+
+
+} diff --git a/themes/proxio/components/BackToTopButton.js b/themes/proxio/components/BackToTopButton.js new file mode 100644 index 00000000..f6876021 --- /dev/null +++ b/themes/proxio/components/BackToTopButton.js @@ -0,0 +1,75 @@ +import throttle from 'lodash.throttle' +import { useCallback, useEffect } from 'react' + +/** + * 回顶按钮 + * @returns + */ +export const BackToTopButton = () => { + useEffect(() => { + Math.easeInOutQuad = function (t, b, c, d) { + t /= d / 2 + if (t < 1) return (c / 2) * t * t + b + t-- + return (-c / 2) * (t * (t - 2) - 1) + b + } + + window.addEventListener('scroll', navBarScollListener) + return () => { + window.removeEventListener('scroll', navBarScollListener) + } + }, []) + + // 滚动监听 + const throttleMs = 200 + const navBarScollListener = useCallback( + throttle(() => { + const scrollY = window.scrollY + // 显示或隐藏返回顶部按钮 + const backToTop = document.querySelector('.back-to-top') + if (backToTop) { + backToTop.style.display = scrollY > 50 ? 'flex' : 'none' + } + }, throttleMs) + ) + + // ====== scroll top js + function scrollTo(element, to = 0, duration = 500) { + const start = element.scrollTop + const change = to - start + const increment = 20 + let currentTime = 0 + + const animateScroll = () => { + currentTime += increment + + const val = Math.easeInOutQuad(currentTime, start, change, duration) + + element.scrollTop = val + + if (currentTime < duration) { + setTimeout(animateScroll, increment) + } + } + + animateScroll() + } + + function scrollTop() { + if (document) { + scrollTo(document.documentElement) + } + } + + return ( + <> + {/* */} + + + + {/* */} + + ) +} diff --git a/themes/proxio/components/Banner.js b/themes/proxio/components/Banner.js new file mode 100644 index 00000000..18370a2d --- /dev/null +++ b/themes/proxio/components/Banner.js @@ -0,0 +1,49 @@ +/** + * 页面顶部宣传栏 + * @returns + */ +export const Banner = ({ title, description }) => { + return ( + <> + {/* */} +
+
+
+
+
+
+

+ {title} +

+

+ {description} +

+ + {/* */} +
+
+
+
+
+ {/* */} + + ) +} diff --git a/themes/proxio/components/Blog.js b/themes/proxio/components/Blog.js new file mode 100644 index 00000000..9773413b --- /dev/null +++ b/themes/proxio/components/Blog.js @@ -0,0 +1,82 @@ +/* eslint-disable @next/next/no-img-element */ +import LazyImage from '@/components/LazyImage' +import { siteConfig } from '@/lib/config' +import Link from 'next/link' + +/** + * 博文列表 + * @param {*} param0 + * @returns + */ +export const Blog = ({ posts }) => { + return ( + <> + {/* */} +
+
+ {/* 区块标题文字 */} +
+
+
+ + {siteConfig('PROXIO_BLOG_TITLE')} + + +

+ {siteConfig('PROXIO_BLOG_TEXT_1')} +

+
+
+
+ {/* 博客列表 此处优先展示3片文章 */} +
+ {posts?.map((item, index) => { + return ( +
+
+
+ {item.pageCoverThumbnail && ( + + {/* 图片半透明 */} + + + )} + {/* 遮罩层,仅覆盖图片部分 */} +
+ {/* 鼠标悬停时显示的文字内容 */} +
+

+ {item.summary} +

+
+ {/* 内容部分 */} +
+ + {item.publishDay} + +

+ + {item.title} + +

+ +
+
+
+ ) + })} +
+
+
+ {/* */} + + ) +} \ No newline at end of file diff --git a/themes/proxio/components/Brand.js b/themes/proxio/components/Brand.js new file mode 100644 index 00000000..032c3d1b --- /dev/null +++ b/themes/proxio/components/Brand.js @@ -0,0 +1,71 @@ +/* eslint-disable @next/next/no-img-element */ + +import { siteConfig } from '@/lib/config' +import { useEffect, useRef } from 'react' + +/** + * 合作伙伴滚动组件 + * @returns + */ +export const Brand = () => { + const brands = siteConfig('PROXIO_BRANDS', []) + + const scrollContainerRef = useRef(null) + + useEffect(() => { + const scrollContainer = scrollContainerRef.current + + let scrollAmount = 0 + const scrollSpeed = 1 // 滚动速度 + + const scroll = () => { + if (scrollContainer) { + scrollAmount += scrollSpeed + scrollContainer.scrollLeft = scrollAmount + + // 如果滚动到内容的一半,立即重置滚动位置 + if (scrollAmount >= scrollContainer.scrollWidth / 2) { + scrollAmount = 0 + } + } + requestAnimationFrame(scroll) + } + + scroll() + + return () => cancelAnimationFrame(scroll) + }, []) + + return ( + <> + {/* */} +
+
+
+ {brands?.map((item, index) => ( + + {item} + + ))} + {/* 克隆一份内容,用于无缝滚动 */} + {brands.map((item, index) => ( + + {item} + + ))} +
+
+
+ {/* */} + + ) +} \ No newline at end of file diff --git a/themes/proxio/components/CTA.js b/themes/proxio/components/CTA.js new file mode 100644 index 00000000..ce980329 --- /dev/null +++ b/themes/proxio/components/CTA.js @@ -0,0 +1,56 @@ +import { siteConfig } from '@/lib/config' +import Link from 'next/link' + +/** + * CTA,用于创建一个呼吁用户行动的部分(Call To Action,简称 CTA)。 + * 该组件通过以下方式激励用户进行特定操作 + * 用户的公告栏内容将在此显示 + **/ +export const CTA = () => { + const enable = siteConfig('PROXIO_CTA_ENABLE') + if (!enable) { + return null + } + return ( + <> + {/* */} +
+
+
+
+
+
+
+ + {siteConfig('PROXIO_CTA_TITLE')} + +
+

+ + + {siteConfig('PROXIO_CTA_TITLE_2')} + +

+

+ {siteConfig('PROXIO_CTA_DESCRIPTION')} +

+ {siteConfig('PROXIO_CTA_BUTTON') && ( + <> + + {siteConfig('PROXIO_CTA_BUTTON_TEXT')} + + + )} +
+
+
+
+
+ +
+ {/* */} + + ) +} diff --git a/themes/proxio/components/Career.js b/themes/proxio/components/Career.js new file mode 100644 index 00000000..162abb40 --- /dev/null +++ b/themes/proxio/components/Career.js @@ -0,0 +1,62 @@ +/* eslint-disable @next/next/no-img-element */ +/* eslint-disable react/no-unescaped-entities */ +import { siteConfig } from '@/lib/config' +import Link from 'next/link' + +/** + * 首页的生涯模块 + */ +export const Career = () => { + const Careers = siteConfig('PROXIO_CAREERS') + return ( + <> + {/* */} +
+
+
+ {/* 左侧的文字说明板块 */} +
+
+ + {siteConfig('PROXIO_CAREER_TITLE')} + +

{siteConfig('PROXIO_CAREER_TEXT')}

+
+
+ +
+ {Careers?.map((item, index) => { + return + })} +
+
+
+
+ {/* */} + + ) +} + + +// 生涯内容 +const CareerItem = ({ title, bio, text }) => { + return
+
+

+ {title} + {bio} +

+ +
+
+

+ {text} +

+
+
+ +} \ No newline at end of file diff --git a/themes/proxio/components/DarkModeButton.js b/themes/proxio/components/DarkModeButton.js new file mode 100644 index 00000000..9d4ceef1 --- /dev/null +++ b/themes/proxio/components/DarkModeButton.js @@ -0,0 +1,84 @@ +import { useGlobal } from '@/lib/global'; +import { useRouter } from 'next/router'; + +export const DarkModeButton = () => { + const { toggleDarkMode } = useGlobal() + const router = useRouter() + return <> + + +} diff --git a/themes/proxio/components/FAQ.js b/themes/proxio/components/FAQ.js new file mode 100644 index 00000000..eb26b91f --- /dev/null +++ b/themes/proxio/components/FAQ.js @@ -0,0 +1,85 @@ +import { siteConfig } from '@/lib/config' +import { useState } from 'react' +import { SVGCircleBG } from './svg/SVGCircleBG' + +/** + * 问答 + * @returns + */ +export const FAQ = () => { + const FAQS = siteConfig('PROXIO_FAQS', []) + + const [openIndex, setOpenIndex] = useState(null) + + const toggleFAQ = (index) => { + setOpenIndex(openIndex === index ? null : index) + } + + return ( + <> + {/* */} +
+
+
+
+
+
+ + {siteConfig('PROXIO_FAQ_TITLE')} + +
+

+ {siteConfig('PROXIO_FAQ_TEXT_1')} +

+

+ {siteConfig('PROXIO_FAQ_TEXT_2')} +

+
+
+
+ + {/* FAQ 列表 */} +
+ {FAQS?.map((faq, index) => ( +
toggleFAQ(index)} + > +
+ {/* 问题部分 */} +
+

+ {faq.q} +

+ +
+ {/* 答案部分 */} +
+
+
+ ))} +
+
+ + {/* 背景图案 */} +
+ + + + + + +
+
+ {/* */} + + ) +} \ No newline at end of file diff --git a/themes/proxio/components/Features.js b/themes/proxio/components/Features.js new file mode 100644 index 00000000..72985adb --- /dev/null +++ b/themes/proxio/components/Features.js @@ -0,0 +1,99 @@ +import { siteConfig } from '@/lib/config' +import { SVGDesign } from './svg/SVGDesign' +import { SVGEssential } from './svg/SVGEssential' +import { SVGGifts } from './svg/SVGGifts' +import { SVGTemplate } from './svg/SVGTemplate' +import Link from 'next/link' +/** + * 产品特性相关,将显示在首页中 + * @returns + */ +export const Features = () => { + return ( + <> + {/* */} +
+
+ +
+
+
+ + {siteConfig('PROXIO_FEATURE_TITLE')} + +

+ {siteConfig('PROXIO_FEATURE_TEXT_1')} +

+

+ {siteConfig('PROXIO_FEATURE_TEXT_2')} +

+
+
+
+ {/* 支持三个特性 */} +
+ +
+
+
+
+ +
+
+

+ {siteConfig('PROXIO_FEATURE_1_TITLE_1')} +

+

+ {siteConfig('PROXIO_FEATURE_1_TEXT_1')} +

+
+
+ +
+
+
+
+ +
+
+

+ {siteConfig('PROXIO_FEATURE_2_TITLE_1')} +

+

+ {siteConfig('PROXIO_FEATURE_2_TEXT_1')} +

+
+
+ +
+
+
+
+ +
+
+

+ {siteConfig('PROXIO_FEATURE_3_TITLE_1')} +

+

+ {siteConfig('PROXIO_FEATURE_3_TEXT_1')} +

+
+
+ +
+ +
+ + {siteConfig('PROXIO_FEATURE_BUTTON_TEXT')} + + +
+
+
+ {/* */} + + ) +} diff --git a/themes/proxio/components/Footer.js b/themes/proxio/components/Footer.js new file mode 100644 index 00000000..0f68f4ab --- /dev/null +++ b/themes/proxio/components/Footer.js @@ -0,0 +1,96 @@ +import AnalyticsBusuanzi from '@/components/AnalyticsBusuanzi' +import { BeiAnGongAn } from '@/components/BeiAnGongAn' +import BeiAnSite from '@/components/BeiAnSite' +import CopyRightDate from '@/components/CopyRightDate' +import DarkModeButton from '@/components/DarkModeButton' +import LazyImage from '@/components/LazyImage' +import PoweredBy from '@/components/PoweredBy' +import { siteConfig } from '@/lib/config' +import { useGlobal } from '@/lib/global' +import Link from 'next/link' +import CONFIG from '../config' +import SocialButton from './SocialButton' + +/** + * 网页底脚 + */ +export const Footer = ({ title }) => { + const { siteInfo } = useGlobal() + const PROXIO_FOOTER_LINKS = siteConfig('PROXIO_FOOTER_LINKS', [], CONFIG) + + return ( +
+
+ {/* 信息与链接区块 */} +
+
+
+ +

{title}

+ + {siteConfig('AUTHOR')} + +
+
{siteConfig('DESCRIPTION')}
+
{siteConfig('CONTACT_EMAIL')}
+
+ + {/* 右侧链接区块 */} +
+ {PROXIO_FOOTER_LINKS?.map((group, index) => { + return ( +
+
+ {group.name} +
+
+ {group?.menus?.map((menu, index) => { + return ( +
+ + {menu.title} + +
+ ) + })} +
+
+ ) + })} +
+
+ + {/* 页脚 */} +
+
+ + +
+ + + +
+
+ + +
+
+
+ + {/* 备案 */} +
+ + +
+
+
+ ) +} \ No newline at end of file diff --git a/themes/proxio/components/Header.js b/themes/proxio/components/Header.js new file mode 100644 index 00000000..ed8ca5b4 --- /dev/null +++ b/themes/proxio/components/Header.js @@ -0,0 +1,71 @@ +/* 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' +import throttle from 'lodash.throttle' +import Link from 'next/link' +import { useRouter } from 'next/router' +import { useCallback, useEffect, useState } from 'react' +import { DarkModeButton } from './DarkModeButton' +import { Logo } from './Logo' +import { MenuList } from './MenuList' + +/** + * 顶部导航栏 + */ +export const Header = props => { + const router = useRouter() + const { isDarkMode } = useGlobal() + const [buttonTextColor, setColor] = useState( + router.route === '/' ? 'text-white' : '' + ) + + useEffect(() => { + if (isDarkMode || router.route === '/') { + setColor('text-white') + } else { + setColor('') + } + // ======= Sticky + // window.addEventListener('scroll', navBarScollListener) + // return () => { + // window.removeEventListener('scroll', navBarScollListener) + // } + }, [isDarkMode]) + + // 滚动监听 + const throttleMs = 200 + // const navBarScollListener = useCallback( + // throttle(() => { + // // eslint-disable-next-line camelcase + // const ud_header = document.querySelector('.ud-header') + // const scrollY = window.scrollY + // // 控制台输出当前滚动位置和 sticky 值 + // if (scrollY > 0) { + // ud_header?.classList?.add('sticky') + // } else { + // ud_header?.classList?.remove('sticky') + // } + // }, throttleMs) + // ) + + return ( + <> + {/* */} +
+
+
+ {/* Logo */} + + {/* 右侧菜单 */} +
+ +
+
+
+
+ {/* */} + + ) +} diff --git a/themes/proxio/components/Hero.js b/themes/proxio/components/Hero.js new file mode 100644 index 00000000..580ca70a --- /dev/null +++ b/themes/proxio/components/Hero.js @@ -0,0 +1,72 @@ +/* eslint-disable @next/next/no-img-element */ +import LazyImage from '@/components/LazyImage' +import { siteConfig } from '@/lib/config' +import CONFIG from '../config' +import Link from 'next/link' + +/** + * 英雄大图区块 + */ +export const Hero = props => { + const config = props?.NOTION_CONFIG || CONFIG + const pageCover = props?.siteInfo?.pageCover + const bannerImage = siteConfig('PROXIO_HERO_BANNER_IMAGE', null, config) || pageCover + const bannerIframe = siteConfig('PROXIO_HERO_BANNER_IFRAME_URL',null,config) + return ( + <> + {/* */} +
+ {/* 横幅图片 */} + {!bannerIframe && bannerImage && ( + + )} +