From aed78201a933441197ac72a47e59977738146d6a Mon Sep 17 00:00:00 2001 From: eric Date: Sat, 7 Mar 2026 20:02:47 -0600 Subject: [PATCH] 'cbook' --- config/dev.js | 12 +- config/index.js | 37 +-- src/app.js | 72 +---- src/pages/book/index.scss | 564 ++++++++++++++++++++++++++++++------ src/pages/book/index.tsx | 522 +++++++++++---------------------- src/pages/index/index.jsx | 548 +++++------------------------------ src/pages/index/index.scss | 517 +++++++++++++++++++-------------- src/pages/sphone/index.scss | 175 +++++++++-- 8 files changed, 1213 insertions(+), 1234 deletions(-) diff --git a/config/dev.js b/config/dev.js index 03e5f44..19a6109 100644 --- a/config/dev.js +++ b/config/dev.js @@ -6,5 +6,15 @@ module.exports = { 'process.env.TARO_API_API': JSON.stringify("/api") }, mini: {}, - h5: {} + h5: { + devServer: { + hot: true, + client: { + overlay: { + errors: true, + warnings: false + } + } + } + } } diff --git a/config/index.js b/config/index.js index 255b181..9e7dfe6 100644 --- a/config/index.js +++ b/config/index.js @@ -1,5 +1,6 @@ import path from "path"; -const WebpackObfuscator = require("webpack-obfuscator"); + +const isDev = process.env.NODE_ENV === "development"; const config = { projectName: "cityh5", @@ -15,25 +16,28 @@ const config = { compiler: "webpack5", cache: { - enable: true, // ✅ 开启缓存,加快构建 + enable: true, type: "filesystem", }, h5: { webpackChain(chain) { - // ✅ 避免 undefined.js,统一使用标准占位符 - chain.output.filename("static/js/[name].[contenthash:8].js"); - chain.output.chunkFilename("static/js/[name].[contenthash:8].js"); + if (isDev) { + chain.output.filename("static/js/[name].js"); + chain.output.chunkFilename("static/js/[name].js"); + chain.devtool("eval-cheap-module-source-map"); + } else { + chain.output.filename("static/js/[name].[contenthash:8].js"); + chain.output.chunkFilename("static/js/[name].[contenthash:8].js"); - chain.plugin("MiniCssExtractPlugin").use(require("mini-css-extract-plugin"), [ - { - filename: "static/css/[name].[contenthash:8].css", - chunkFilename: "static/css/[name].[contenthash:8].css", - }, - ]); + chain.plugin("MiniCssExtractPlugin").use(require("mini-css-extract-plugin"), [ + { + filename: "static/css/[name].[contenthash:8].css", + chunkFilename: "static/css/[name].[contenthash:8].css", + }, + ]); - if (process.env.NODE_ENV === "production") { - // ✅ 保留原有混淆逻辑 + const WebpackObfuscator = require("webpack-obfuscator"); chain.plugin("obfuscator").use(WebpackObfuscator, [ { rotateStringArray: true, @@ -53,7 +57,6 @@ const config = { ]); } - // ✅ markdown 支持 chain.module .rule("markdown") .test(/\.md$/) @@ -61,7 +64,6 @@ const config = { .loader("raw-loader") .end(); - // ✅ ico 支持 chain.module .rule("ico") .test(/\.ico$/) @@ -71,7 +73,7 @@ const config = { }, esnextModules: ["taro-ui"], - publicPath: "/", // ✅ 部署到根目录;如果是子路径,需要改成 /xxx/ + publicPath: "/", staticDirectory: "static", postcss: { @@ -94,6 +96,7 @@ const config = { }, devServer: { + hot: true, proxy: { "/api/": { target: "http://localhost:8700", @@ -101,7 +104,7 @@ const config = { changeOrigin: true, }, }, - allowedHosts: ["nomadro.com", "localhost", ".nomadro.com", "hackrobot.cn",".ngrok-free.app"], + allowedHosts: ["nomadro.com", "localhost", ".nomadro.com", "hackrobot.cn", ".ngrok-free.app"], }, htmlPluginOption: { diff --git a/src/app.js b/src/app.js index ccaee58..3d0fb49 100644 --- a/src/app.js +++ b/src/app.js @@ -1,72 +1,26 @@ import { Component } from 'react' -import SDK from 'sr-sdk-h5' - import './app.scss' -import 'animate.css'; -import 'taro-ui/dist/style/index.scss' // 全局引入一次即可 +import 'animate.css' +import 'taro-ui/dist/style/index.scss' -/** - * 有数埋点SDK 默认配置 - * 使用方法请参考文档 https://mp.zhls.qq.com/youshu-docs/develop/sdk/Taro.html - * 如对有数SDK埋点接入有任何疑问,请联系微信:sr_data_service - */ - - window.srt = new SDK({ - - /** - * 有数 - ka‘接入测试用’ 分配的 app_id,对应的业务 - */ - token: 'bi72fccc7184ef45f9', +const isProd = process.env.NODE_ENV === 'production' - /** - * 传入自定义的后台上报接口,若传入则token将无意义 - */ - // serverUrl: 'aaa.baidu.com', - - /** - * 开启打印调试信息, 默认 false - */ - debug: true, - - /** - * 代理配置,自动代理页面浏览事件、页面离开事件、点击事件,默认 false。建议开启 - */ - autoTrack: true, - - /** - * 自动开始上报,默认 true。建议开启 - */ - autoStart: true, - - /** - * 上报返回的钩子函数,返回 false 则代表不通过,SDK不会清除本地记录,会继续重拾 - */ - // onUploaded?: () => {}, - - /** - * 跳过初始化时对 token 的必要检查 - * 满足场景:服务商转发SDK上报数据,在服务端回填appid,解决大量小程序同时接入的问题,默认false - */ - // skipTokenCheck: false, - - /** - * 小程序appid - */ - appid: '' -}); - -// window.srt.setChan({chan_id: 'xxx'}) // 设置渠道,渠道信息将会被设置在props.chan对象中 - -// window.srt.setUser({user_id: 'xxx'}) // 设置用户信息,用户信息将会被设置在props.wx_user对象中 +if (isProd) { + const SDK = require('sr-sdk-h5').default + window.srt = new SDK({ + token: 'bi72fccc7184ef45f9', + debug: false, + autoTrack: true, + autoStart: true, + appid: '' + }) +} class App extends Component { componentDidMount () {} - componentDidShow () {} - componentDidHide () {} - // this.props.children 是将要会渲染的页面 render () { return this.props.children } diff --git a/src/pages/book/index.scss b/src/pages/book/index.scss index d1b285c..4c69056 100644 --- a/src/pages/book/index.scss +++ b/src/pages/book/index.scss @@ -1,148 +1,534 @@ -// 在 src/index.scss 中引入 GitHub Markdown 主题 @import "github-markdown-css/github-markdown.css"; -// marktext的换行处理 -.markdown-body p, -.markdown-body blockquote, -.markdown-body ul, -.markdown-body ol, -.markdown-body dl, -.markdown-body table, -.markdown-body pre, -.markdown-body details { - margin-bottom: 10px; -} - -.markdown-body li > p { - margin-top: 0px; -} - +// ===== 顶部导航栏 ===== .book_header_div { width: 100%; - height: 80px; + height: 88px; } + .book_header { position: fixed; - top: 0px; - width: 100%; - height: 80px; - // background-color: aquamarine; - // background-color: #f0f5ff; - border-bottom: 2px solid #f0f5ff; - background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1)); - + top: 0; + left: 0; + right: 0; + z-index: 100; + height: 88px; + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(10px); + border-bottom: 1px solid #f0f0f0; display: flex; flex-direction: row; align-items: center; justify-content: space-between; + .book_header_left { width: 100px; display: flex; - flex-direction: row; align-items: center; justify-content: center; - color: #69b1ff; + color: #1a1a2e; + cursor: pointer; } + .book_header_middle { flex: 1; display: flex; - flex-direction: row; align-items: center; justify-content: center; - color: #69b1ff; + font-size: 32px; + font-weight: 600; + color: #1a1a2e; + letter-spacing: 2px; } + .book_header_right { - width: 100px; - // background-color: yellow; + width: 72px; + height: 72px; + padding: 0; display: flex; - flex-direction: row; align-items: center; justify-content: center; - color: #69b1ff; + color: #4facfe; + cursor: pointer; + margin-right: 10px; + background: #f0f7ff; + border-radius: 50%; + transition: all 0.2s ease; + + &:active { + background: #dceeff; + transform: scale(0.96); + } + + &.active { + background: #4facfe; + color: #fff; + } + + .pc-only { + display: none; + } } } + +// ===== 内容区域 ===== .Book { - padding: 40px; + padding: 30px; + max-width: 100%; .markdown-body { - font-size: 30px; - // color: #72d572; + font-size: 32px !important; + line-height: 1.85 !important; + color: #2c3e50 !important; + word-wrap: break-word; + overflow-wrap: break-word; + + h1 { + font-size: 48px !important; + font-weight: 800; + color: #1a1a2e; + margin: 48px 0 24px; + padding-bottom: 16px; + border-bottom: 2px solid #e8e8e8; + line-height: 1.3; + } + + h2 { + font-size: 42px !important; + font-weight: 700; + color: #2c3e50; + margin: 40px 0 20px; + padding-bottom: 12px; + border-bottom: 1px solid #f0f0f0; + line-height: 1.3; + } + + h3 { + font-size: 36px !important; + font-weight: 600; + color: #34495e; + margin: 32px 0 16px; + } + + h4, h5, h6 { + font-size: 32px !important; + font-weight: 600; + color: #4a5568; + margin: 24px 0 12px; + } + + p { + font-size: 32px !important; + margin-bottom: 20px; + line-height: 1.9; + } + + blockquote { + margin: 24px 0; + padding: 20px 24px; + border-left: 6px solid #4facfe; + background: #f8fbff; + border-radius: 0 12px 12px 0; + color: #4a5568; + + p { + margin-bottom: 8px; + &:last-child { margin-bottom: 0; } + } + } + + pre { + margin: 24px 0; + padding: 24px; + background: #1e1e2e; + border-radius: 12px; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + + code { + font-family: "SF Mono", "Fira Code", Menlo, Monaco, Consolas, monospace; + font-size: 26px !important; + line-height: 1.6; + color: #e2e8f0; + background: none !important; + padding: 0; + border-radius: 0; + white-space: pre; + word-break: normal; + word-wrap: normal; + } + } + + code { + font-family: "SF Mono", "Fira Code", Menlo, Monaco, Consolas, monospace; + font-size: 28px !important; + padding: 4px 10px; + background: #f1f5f9; + color: #e53e3e; + border-radius: 6px; + word-break: break-word; + } + + li { + font-size: 32px !important; + } + + ul, ol { + margin: 16px 0; + padding-left: 40px; + + li { + margin-bottom: 10px; + line-height: 1.85; + > p { + margin-top: 0; + margin-bottom: 8px; + } + } + } + + ul li { list-style-type: disc; } + ol li { list-style-type: decimal; } + + table { + width: 100%; + margin: 24px 0; + border-collapse: collapse; + border-spacing: 0; + display: block; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + + th { + background: #f8fafc; + font-weight: 600; + padding: 16px 20px; + text-align: left; + border: 1px solid #e2e8f0; + white-space: nowrap; + font-size: 28px !important; + } + + td { + padding: 14px 20px; + border: 1px solid #e2e8f0; + font-size: 28px !important; + } + + tr:nth-child(even) { background: #f8fafc; } + } + + hr { + margin: 40px 0; + border: none; + border-top: 2px solid #f0f0f0; + } + + img { + display: block; + margin: 24px auto; + max-width: 100%; + height: auto; + border-radius: 8px; + } + + a { + color: #4facfe; + text-decoration: none; + font-size: inherit !important; + border-bottom: 1px solid transparent; + transition: border-color 0.2s; + &:hover { border-bottom-color: #4facfe; } + } + + strong { + font-weight: 700; + color: #1a1a2e; + font-size: inherit !important; + } + + em { + font-style: italic; + color: #4a5568; + } + + details { + margin: 16px 0; + padding: 16px; + background: #f8fafc; + border-radius: 8px; + border: 1px solid #e2e8f0; + summary { + cursor: pointer; + font-weight: 600; + color: #2c3e50; + } + } } - // img { - // max-width: 100%; - // height: auto; - // /* 保持图像的纵横比 */ - // } - - img { - /* 如果需要让 img 当 flex item 居中,不需要额外样式 */ - display: block; - margin: 0 auto; + .markdown-loading { + display: flex; + justify-content: center; + align-items: center; + min-height: 400px; + color: #8c8c8c; + font-size: 28px; } - .at-drawer__content { - .at-list { - .at-list__item { - font-size: 30px; + // 二维码区域 + .qrcode-section { + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + margin: 40px 0; + } - // background-color: aqua; - .at-list__item-container { - .at-list__item-content { + // 抽屉目录 + .book_AtDrawer { + .at-drawer__content { + background: #fff; + padding-top: 88px; + box-shadow: 4px 0 24px rgba(0, 0, 0, 0.1); + + .at-list { + .at-list__item { + font-size: 30px; + padding: 20px 24px; + border-bottom: 1px solid #f5f5f5; + color: #2c3e50; + transition: background 0.15s; + + &:active { + background: #f0f7ff; + } + + .at-list__item-container { + .at-list__item-content__info-title { + font-size: 30px; + line-height: 1.5; + color: #2c3e50; + } } } } } + + .at-drawer__mask { + background: rgba(0, 0, 0, 0.35); + backdrop-filter: blur(2px); + } } + + // 支付按钮 .joinSalon { - margin-top: 30px; + margin-top: 40px; + margin-bottom: 40px; + border-radius: 12px; + font-size: 30px; + font-weight: 600; } } -@media (min-width: 1536px) { +/* stylelint-disable */ +// ===== Tablet/Desktop: 768px+ ===== +// 使用 PX (大写) 防止 Taro postcss-pxtransform 转换 +@media (min-width: 768PX) { + .book_header { + height: 56PX; + + .book_header_middle { + font-size: 18PX; + } + + .book_header_right { + width: auto; + height: 34PX; + padding: 0 14PX; + margin-right: 16PX; + gap: 5PX; + border-radius: 17PX; + font-size: 13PX; + + &:hover { + background: #dceeff; + } + + .pc-only { + display: inline; + font-size: 13PX; + font-weight: 500; + } + } + } + + .book_header_div { + height: 56PX; + } + .Book { - width: 100%; - // height: 100%; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - // background-color: yellow; - .book_left { - width: 400px; - height: 100%; - background-color: red; + padding: 30PX 60PX; + + .book_AtDrawer .at-drawer__content { + padding-top: 56PX; + width: 300PX; + + .at-list .at-list__item { + font-size: 21PX; + padding: 14PX 18PX; + + .at-list__item-container .at-list__item-content__info-title { + font-size: 21PX; + } + + .at-list__item-extra__icon-arrow { + font-size: 12PX !important; + } + + &:hover { + background: #f0f7ff; + } + } } .markdown-body { - // width: 100%; - width: 700px; - // max-width: 1400px; - // font-size: 22px; - font-size: 18px; + font-size: 16PX !important; + line-height: 1.8 !important; + max-width: 800PX; + margin: 0 auto; - // color: red; - // background-color: #72d572; - } + h1 { + font-size: 28PX !important; + margin: 32PX 0 16PX; + padding-bottom: 10PX; + border-bottom-width: 2PX; + } - .book_right { - width: 400px; - height: 100%; - background-color: greenyellow; + h2 { + font-size: 23PX !important; + margin: 26PX 0 12PX; + } + + h3 { + font-size: 19PX !important; + margin: 22PX 0 10PX; + } + + h4, h5, h6 { + font-size: 16PX !important; + } + + p { + font-size: 16PX !important; + margin-bottom: 14PX; + } + + li { + font-size: 16PX !important; + } + + pre { + padding: 18PX; + border-radius: 8PX; + margin: 20PX 0; + + code { + font-size: 14PX !important; + } + } + + code { + font-size: 14PX !important; + padding: 2PX 6PX; + border-radius: 4PX; + } + + table th, table td { + font-size: 14PX !important; + padding: 8PX 12PX; + } + + blockquote { + padding: 14PX 18PX; + border-left-width: 4PX; + margin: 18PX 0; + border-radius: 0 8PX 8PX 0; + } + + ul, ol { padding-left: 24PX; } + img { border-radius: 8PX; margin: 20PX auto; } + hr { margin: 24PX 0; border-top-width: 2PX; } + + a { font-size: inherit !important; border-bottom-width: 1PX; } + strong { font-size: inherit !important; } } .joinSalon { - // width: 100%; - // height: 100px; - - display: none; - - // background-color: yellow; + max-width: 360PX; + margin-left: auto; + margin-right: auto; + display: block; + font-size: 16PX; + border-radius: 8PX; } } - .Book img { - /* 如果需要让 img 当 flex item 居中,不需要额外样式 */ - display: block; - margin: 0 auto; +} + +// ===== Desktop: 1024px+ ===== +@media (min-width: 1024PX) { + .Book { + display: flex; + flex-direction: column; + align-items: center; + padding: 30PX 80PX; + + .markdown-body { + width: 720PX; + font-size: 17PX !important; + line-height: 1.85 !important; + + h1 { font-size: 30PX !important; } + h2 { font-size: 25PX !important; } + h3 { font-size: 20PX !important; } + p { font-size: 17PX !important; } + li { font-size: 17PX !important; } + + pre { + padding: 18PX 22PX; + code { font-size: 14PX !important; } + } + + img { + border-radius: 10PX; + max-width: 90%; + } + } + + .joinSalon { + max-width: 280PX; + font-size: 15PX; + } } } + +// ===== Large Desktop: 1536px+ ===== +@media (min-width: 1536PX) { + .Book { + .markdown-body { + width: 780PX; + font-size: 18PX !important; + + h1 { font-size: 32PX !important; } + h2 { font-size: 26PX !important; } + h3 { font-size: 22PX !important; } + p { font-size: 18PX !important; } + li { font-size: 18PX !important; } + pre code { font-size: 15PX !important; } + } + + .joinSalon { + display: none; + } + } +} +/* stylelint-enable */ diff --git a/src/pages/book/index.tsx b/src/pages/book/index.tsx index 2530044..46732d9 100644 --- a/src/pages/book/index.tsx +++ b/src/pages/book/index.tsx @@ -1,477 +1,297 @@ // @ts-nocheck -import { Component } from "react"; -import { View, Text, Image } from "@tarojs/components"; +import { View, Image } from "@tarojs/components"; import Taro, { useRouter } from "@tarojs/taro"; import "./index.scss"; -import { AtButton, AtInput, AtTabBar } from "taro-ui"; -import React, { useCallback, useEffect, useState, useRef } from "react"; -import { AtGrid } from "taro-ui"; -import { Swiper, SwiperItem } from "@tarojs/components"; -import { AtCard } from "taro-ui"; -import { AtImagePicker } from "taro-ui"; -import { AtAvatar } from "taro-ui"; -import { AtTag } from "taro-ui"; -import { AtList, AtListItem } from "taro-ui"; -import { AtForm, AtMessage, AtDrawer, AtIcon } from "taro-ui"; +import { AtButton, AtDrawer, AtIcon } from "taro-ui"; +import React, { useEffect, useState, useRef, useMemo, useCallback } from "react"; import { marked } from "marked"; import infoText from "./info.md"; import bookText from "./book.md"; -// import youtubeIco from "../../images/youtube.ico"; import hackrobot from "../../images/hackrobot.jpg"; -// PocketBase 配置 + const PB_URL = "https://pocketbase.hackrobot.cn"; const pb = new PocketBase(PB_URL); -marked.setOptions({ - breaks: true, // 是否回车换行 - gfm: true, //使用经批准的 GitHub Flavored Markdown (GFM) 规范 - // pedantic: true, //严格模式 - // highlight(code:any, lang:any) { - // // 语法高亮 - // let val = code; - // if (lang) { - // val = hljs.highlight(lang, code).value; - // } else { - // val = hljs.highlightAuto(code).value; - // } - // return val; - // }, +marked.setOptions({ breaks: true, gfm: true }); + +function addLazyLoadToImages(html) { + return html.replace(/])/i); + return parts.filter(p => p.trim().length > 0); +} + +function extractHeaders(html) { + const regex = /]*>(.*?)<\/h1>/gi; + const results = []; + let match; + while ((match = regex.exec(html)) !== null) { + results.push(match[1].replace(/<[^>]+>/g, "")); + } + return results; +} + +const LazyChunk = React.memo(({ html, forceVisible }) => { + const [visible, setVisible] = useState(forceVisible); + const ref = useRef(null); + + useEffect(() => { + if (forceVisible) { setVisible(true); return; } + if (visible) return; + const el = ref.current; + if (!el) return; + const observer = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting) { + setVisible(true); + observer.disconnect(); + } + }, + { rootMargin: "800px 0px" } + ); + observer.observe(el); + return () => observer.disconnect(); + }, [forceVisible, visible]); + + if (visible) { + return
; + } + return
; }); -// const renderer = new marked.Renderer(); -// // 使用拓展 -// marked.use({ renderer }); - -// export default class Index extends Component { const Book = () => { - const [book, setbook] = useState(window.localStorage.getItem("book")); - const [markText, setmarkText] = useState(book == 1 ? bookText : infoText); const [isWeChat, setisWeChat] = useState(false); const [show, setshow] = useState(false); - - const [headers, setHeaders] = useState([]); // 存储所有

的内容 - const contentRef = useRef(null); // 用于获取渲染后的 DOM - - const [paidType, setPaidType] = useState(null); // 新增:支付类型 'vip' | 'book' | null - - // 把这些变量名全部改为 userId + const [headers, setHeaders] = useState([]); + const contentRef = useRef(null); + const [paidType, setPaidType] = useState(null); const [userId, setUserId] = useState(Taro.getStorageSync("userId") || null); - const [loading, setLoading] = useState(true); // 新增:加载状态 - const router = useRouter(); // Taro 3+ 获取路由参数 + const [loading, setLoading] = useState(true); + const [contentReady, setContentReady] = useState(false); + const [forceShowAll, setForceShowAll] = useState(false); + const router = useRouter(); + const [vipbook, setVipbook] = useState(false); - const [vipbook, setVipbook] = useState(false); //是否在vip群打开 - - - // 在组件挂载时检测 referrer useEffect(() => { - if (router && router.params && router.params.type === 'vipbook') { - setVipbook(true) - } else { - setVipbook(false) - } + setVipbook(router?.params?.type === "vipbook"); }, []); - // 保存 userId 到本地 const saveUserId = (id) => { Taro.setStorageSync("userId", id); setUserId(id); }; - // 检查本地是否已有支付类型 - const getLocalPaidType = () => { - return Taro.getStorageSync("paidType") || null; - }; + const getLocalPaidType = () => Taro.getStorageSync("paidType") || null; - // 保存支付类型到本地 const savePaidType = (type) => { Taro.setStorageSync("paidType", type); setPaidType(type); }; - // 查询 PocketBase 是否支付过 - const checkPaymentFromPB = async (userId) => { - // window.alert('查询pocketbase') + const checkPaymentFromPB = async (uid) => { try { - // 查询 payments 表中该 user_id 的记录,按创建时间倒序(最新的一笔) const records = await pb.collection("payments").getList(1, 1, { - filter: `user_id = "${userId}"`, - sort: "-created", // 最新支付优先 + filter: `user_id = "${uid}"`, + sort: "-created", }); - if (records.items.length > 0) { - alert("records.items[0]", records.items[0]); - const latestType = records.items[0].type; // 取最新一笔的 type - const dbUserId = records.items[0].user_id; // 数据库里的真实 user_id - // window.alert('latestType', dbUserId) - savePaidType(latestType); - - saveUserId(dbUserId); // 正确保存真实的 user_id - console.log("从 PocketBase 查询到已支付:", latestType); - return latestType; - } else { - // window.alert('未找到支付记录') - - console.log("未找到支付记录"); - savePaidType(null); - return null; + savePaidType(records.items[0].type); + saveUserId(records.items[0].user_id); + return records.items[0].type; } + savePaidType(null); + return null; } catch (err) { - // window.alert('查询 PocketBase 失败', err) console.error("查询 PocketBase 失败:", err); - // 网络错误时不影响本地缓存,使用本地旧值 return getLocalPaidType(); - } finally { } }; - // 清空所有本地存储(测试专用) const clearAllStorage = () => { try { - // 清空 Taro 的 storage(推荐方式) Taro.clearStorageSync(); - - // 同时清空 window.localStorage(H5 环境兼容) if (typeof window !== "undefined" && window.localStorage) { window.localStorage.clear(); } - - // 可选:手动清除你知道的关键字段,防止残留 - Taro.removeStorageSync("userId"); - Taro.removeStorageSync("paidType"); - Taro.removeStorageSync("openid"); - Taro.removeStorageSync("wxid"); - - // 状态重置 setPaidType(null); - setUserName(null); setLoading(false); - - Taro.showToast({ - title: "本地存储已清空", - icon: "success", - duration: 2000, - }); - - // 可选:刷新页面让效果立即生效 - setTimeout(() => { - // window.location.reload(); - }, 1000); + Taro.showToast({ title: "本地存储已清空", icon: "success", duration: 2000 }); } catch (error) { console.error("清空存储失败:", error); - Taro.showToast({ - title: "清空存储失败", - icon: "none", - }); + Taro.showToast({ title: "清空存储失败", icon: "none" }); } }; + const isWeChatFun = () => { + const ua = navigator.userAgent.toLowerCase(); + const result = ua.includes("micromessenger"); + setisWeChat(result); + return result; + }; + + const getOrCreateUserId = () => { + let id = Taro.getStorageSync("userId"); + if (!id) { + id = `user${Date.now()}`; + Taro.setStorageSync("userId", id); + } + return id; + }; + useEffect(() => { - // 判断是否在微信 isWeChatFun(); - // 方法1:Taro 3+ 推荐方式(支持小程序 + H5) - if (router && router.params && router.params.type === "clean") { + if (router?.params?.type === "clean") { clearAllStorage(); return; } - const init = async () => { - const currentUserId = getOrCreateUserId(); // 确保 userId 存在 - + const currentUserId = getOrCreateUserId(); const localType = getLocalPaidType(); const localUserId = Taro.getStorageSync("userId"); - - // 只有当本地同时有 paidType 和 userId 时,才信任本地缓存 if (localType && localUserId) { setPaidType(localType); setUserId(localUserId); setLoading(false); - console.log("使用本地缓存支付状态:", localType); return; } - - // 否则去 PocketBase 查最新状态 await checkPaymentFromPB(currentUserId); setLoading(false); }; - init(); }, []); - useEffect(() => { - if (contentRef.current) { + const handleJump = useCallback((text) => { + setForceShowAll(true); + setTimeout(() => { + if (!contentRef.current) return; const h1Elements = contentRef.current.querySelectorAll("h1"); - const h2Elements = contentRef.current.querySelectorAll("h2"); - - const h1Texts = Array.from(h1Elements).map((el) => el.textContent); - const h2Texts = Array.from(h2Elements).map((el) => el.textContent); - - setHeaders(h1Texts); - // console.log("获取到的 H1 标题:"); // 控制台查看 - console.log("获取到的 H1 标题:", h1Texts); // 控制台查看 - console.log("获取到的 H2 标题:", h2Texts); // 控制台查看 - } - }, [markText]); // 监听 HTML 内容变化 - - const handleJump = (text) => { - const h1Elements = contentRef.current.querySelectorAll("h1"); - for (let h1 of h1Elements) { - if (h1.textContent.trim() === text.trim()) { - // if (h1.textContent === text) { - h1.scrollIntoView({ behavior: "smooth" }); - break; - } - } - }; - - // const handleClick = (value) => { - // // 跳转到目的页面,在当前页面打开 - // Taro.navigateTo({ - // url: "/pages/index/index", - // }); - // }; - - // 是否在微信客户端 - const isWeChatFun = () => { - var ua = navigator.userAgent.toLowerCase(); - if (ua.indexOf("micromessenger") != -1) { - setisWeChat(true); - return true; - } else { - setisWeChat(false); - return false; - } - }; - - const payh51111 = (type, total_fee = 1000) => { - Taro.request({ - method: "POST", - url: `${process.env.TARO_API_API}/payh5`, - data: { - total_fee: total_fee, - type: type, - // callback_url 临时不传 - }, - header: { - "content-type": "application/json", - }, - success: (res) => { - if (res.data?.status === "ok" && res.data?.xorpay_params) { - // 创建隐藏 form POST 到 XorPay - const form = document.createElement("form"); - form.method = "POST"; - form.action = res.data.xorpay_url; - form.style.display = "none"; - - for (const key in res.data.xorpay_params) { - const input = document.createElement("input"); - input.type = "hidden"; - input.name = key; - input.value = res.data.xorpay_params[key]; - form.appendChild(input); - } - - document.body.appendChild(form); - form.submit(); - } else { - Taro.showToast({ - title: "支付发起失败", - icon: "none", - }); + for (let h1 of h1Elements) { + if (h1.textContent.trim() === text.trim()) { + h1.scrollIntoView({ behavior: "smooth" }); + break; } - }, - fail: () => { - Taro.showToast({ - title: "网络错误", - icon: "none", - }); - }, - }); - }; + } + }, 150); + }, []); - // 生成或获取 userId(修改为无下划线) - const getOrCreateUserId = () => { - let userId = Taro.getStorageSync("userId"); - if (!userId) { - // 修改这里:去掉下划线 - userId = `user${Date.now()}`; // 示例:user1766043770158 - Taro.setStorageSync("userId", userId); - console.log("新生成 userId:", userId); + const submitPayForm = (xorpayUrl, xorpayParams) => { + const form = document.createElement("form"); + form.method = "POST"; + form.action = xorpayUrl; + form.style.display = "none"; + for (const key in xorpayParams) { + const input = document.createElement("input"); + input.type = "hidden"; + input.name = key; + input.value = xorpayParams[key]; + form.appendChild(input); } - return userId; + document.body.appendChild(form); + form.submit(); + setTimeout(() => document.body.removeChild(form), 1000); }; const payh5 = (type, total_fee = 1000) => { - const userId = getOrCreateUserId(); // ← 新增:获取 userId + const uid = getOrCreateUserId(); Taro.request({ method: "POST", url: `${process.env.TARO_API_API}/payh5`, - data: { - total_fee: total_fee, - type: type, - // callback_url 临时不传 - user_id: userId, // ← 新增字段 - }, - header: { - "content-type": "application/json", - }, + data: { total_fee, type, user_id: uid }, + header: { "content-type": "application/json" }, success: (res) => { if (res.data?.status === "ok" && res.data?.xorpay_params) { - // 1. 乐观更新:立即缓存 paidType,用户看到“加载中”或直接显示已支付 - // savePaidType(type); - // saveUserName(userId); // ← 乐观缓存当前 userId - // Taro.showToast({ - // title: "正在跳转支付,请稍后...", - // icon: "loading", - // duration: 2000, - // }); - - // 创建隐藏 form POST 到 XorPay - const form = document.createElement("form"); - form.method = "POST"; - form.action = res.data.xorpay_url; - form.style.display = "none"; - - for (const key in res.data.xorpay_params) { - const input = document.createElement("input"); - input.type = "hidden"; - input.name = key; - input.value = res.data.xorpay_params[key]; - form.appendChild(input); - } - - document.body.appendChild(form); - form.submit(); - - // 2. 可选:提交后清理 form,避免内存泄漏 - setTimeout(() => { - document.body.removeChild(form); - }, 1000); + submitPayForm(res.data.xorpay_url, res.data.xorpay_params); } else { - Taro.showToast({ - title: "支付发起失败", - icon: "none", - }); + Taro.showToast({ title: "支付发起失败", icon: "none" }); } }, - fail: () => { - Taro.showToast({ - title: "网络错误", - icon: "none", - }); - }, + fail: () => Taro.showToast({ title: "网络错误", icon: "none" }), }); }; + + const isPaid = paidType === "book" || vipbook; + + const markdownSource = isPaid ? bookText : infoText; + const parsedHtml = useMemo(() => addLazyLoadToImages(marked(markdownSource)), [markdownSource]); + const chunks = useMemo(() => splitIntoChunks(parsedHtml), [parsedHtml]); + const headerList = useMemo(() => extractHeaders(parsedHtml), [parsedHtml]); + + useEffect(() => { + if (loading) return; + setHeaders(headerList); + const raf = requestAnimationFrame(() => { + setContentReady(true); + }); + return () => cancelAnimationFrame(raf); + }, [loading, headerList]); + return ( <> - + - { - Taro.redirectTo({ - url: `/`, - }); - // Taro.navigateBack({ - // delta: 1, // 表示返回的页面数,默认是 1,表示返回上一页 - // }); - }} - > - + Taro.redirectTo({ url: "/" })}> + - {`数字游民`} - { - setshow(!show); - }} - > - + 数字游民 + setshow(!show)}> + + 目录 - - + + {!contentReady ? ( + 加载中... + ) : ( + + {chunks.map((chunk, i) => ( + + ))} + + )} { - handleJump(headers[index]); - }} - onClose={() => { - setshow(false); - }} - > - - {/* 如果items没有数据就会展示children */} - {/* - 这是自定义内容 - */} - + onItemClick={(index) => handleJump(headers[index])} + onClose={() => setshow(false)} + /> - {/* 判断是否是微信 */} {!isWeChat && ( - // 公众号二维码 -
+ -
+ /> +
)} - {/* 判断有没有wxid */} - {/* {book != 1 ? ( */} - {paidType != "book" && !vipbook ? ( + {!isPaid && ( { - if (paidType == "book" || vipbook) { - return false; + if (isWeChat) { + payh5("book", 4900); } else { - if (!!isWeChat) { - //49元 - payh5("book", 4900); - // window.location.href = `https://vip.hackrobot.cn`; - } else { - window.location.href = `https://mp.weixin.qq.com/s/PBbnS8qC_kqhL9HJO5m-Iw`; - } + window.location.href = "https://mp.weixin.qq.com/s/PBbnS8qC_kqhL9HJO5m-Iw"; } }} > - {`🔒阅读更多`} + 阅读更多 - ) : // 持续更新中.... - null} + )}
); diff --git a/src/pages/index/index.jsx b/src/pages/index/index.jsx index 4b69f11..1d83ddb 100644 --- a/src/pages/index/index.jsx +++ b/src/pages/index/index.jsx @@ -1,504 +1,112 @@ -import { Component } from "react"; -import Taro,{useRouter} from "@tarojs/taro"; -import { View, Text, Image, Button } from "@tarojs/components"; +import Taro, { useRouter } from "@tarojs/taro"; +import { View, Image } from "@tarojs/components"; import "./index.scss"; -import { AtTabBar } from "taro-ui"; -import React, { useCallback, useEffect, useState } from "react"; -import { AtGrid } from "taro-ui"; -import { Swiper, SwiperItem } from "@tarojs/components"; -import { AtCard } from "taro-ui"; -import { AtImagePicker } from "taro-ui"; import { AtButton } from "taro-ui"; -import { AtSearchBar } from "taro-ui"; -import { Picker } from "@tarojs/components"; -import { AtList, AtListItem, AtNavBar, AtTag, AtMessage } from "taro-ui"; -import { - AtModal, - AtModalHeader, - AtModalContent, - AtModalAction, - AtAvatar, - AtAccordion, -} from "taro-ui"; -import { AtToast } from "taro-ui"; -import { AtIcon } from "taro-ui"; -import { VIEW } from "@tarojs/runtime"; +import React, { useEffect, useState } from "react"; import "animate.css"; -import touxiang from "../../images/touxiang.png"; -import cat from "../../images/cat.jpg"; -import girl from "../../images/girl.jpg"; - -import emoji from "../../images/emoji.png"; -import phone from "../../images/phone.png"; -// import robot from "../../images/robot.png"; -import nomad from "../../images/nomad.png"; -// import book from "../../images/book.png"; -import qrcode from "../../images/qrcode.png"; - -import meetup from "../../images/meetup.jpg"; -import bookqr from "../../images/bookqr.jpg"; -import loft from "../../images/loft.png"; -import AIGC from "../../images/AIGC.png"; -import robot192 from "../../images/robot192.png"; -// import youtube from "../../images/youtube.svg"; -import ssh from "../../images/ssh.svg"; -import robot from "../../images/robot.svg"; -import host from "../../images/host.svg"; -import ytGpt from "../../images/ytGpt.png"; - -import demo from "../../images/demo.svg"; -import AI from "../../images/AI.png"; -import nomadro from "../../images/nomadro.png"; -import phone2 from "../../images/phone2.png"; import code from "../../images/code.svg"; import laptop from "../../images/laptop.svg"; import travel from "../../images/travel.svg"; -import group from "../../images/group.svg"; -import book from "../../images/book.svg"; -import youtubesvg from "../../images/youtube.svg"; import dgnomad from "../../images/dgnomad.png"; import tiktok from "../../images/tiktok.png"; import youtube from "../../images/youtube.png"; import web from "../../images/web.png"; - -// export default class Index extends Component { const Index = () => { - const [current, setcurrent] = useState(0); - const [openId, setopenId] = useState(null); - const [wxid, setwxid] = useState(null); - const [userInfo, setuserInfo] = useState({}); + const [vipbook, setVipbook] = useState(false); + const router = useRouter(); - const [open, setopen] = useState(false); - // const [referer, setreferer] = useState(window.document.referrer); - const [referer, setreferer] = useState(""); - - - const [isOpened, setisOpened] = useState(false); - const [meetupQR, setmeetupQR] = useState(false); - - const [bookToast, setbookToast] = useState(false); - const [meetupToast, setmeetupToast] = useState(false); - - const [joinMeetup, setjoinMeetup] = useState(false); - const [bookpay, setbookpay] = useState(0); - - const [isWeChat, setisWeChat] = useState(false); - const [vipbook, setVipbook] = useState(false); //是否在vip群打开 - - const router = useRouter(); // Taro 3+ 获取路由参数 - - // 在组件挂载时检测 referrer useEffect(() => { - if (router && router.params && router.params.type === 'vipbook') { - setVipbook(true) - } else { - setVipbook(false) - } + setVipbook(router?.params?.type === "vipbook"); }, [router.params]); - const getOpenidFromUrl = (url) => { - const pattern = /[?&]openid=([^&#]+)/; - const match = url.match(pattern); - if (match) { - return match[1]; - } - return null; - }; - - // 是否在微信客户端 - const isWeChatFun = () => { - var ua = navigator.userAgent.toLowerCase(); - if (ua.indexOf("micromessenger") != -1) { - setisWeChat(true); - return true; - } else { - setisWeChat(false); - return false; - } - }; - - // 获取openid - const getOpenid = () => { - // let newparams = Taro.getCurrentInstance().router.params; - // let urlopenid = newparams.openid; - - // hash模式 - // http://192.168.31.245:10086/?openid=o7LFAwR32hWGq9XOpd7ZxK1wZxq8#/pages/index/index - let hashopenid = null; - if (window.location.href.includes("openid")) { - hashopenid = getOpenidFromUrl(window.location.href); - } else { - // hashopenid = window.localStorage.getItem("openid"); - } - if (!!hashopenid && hashopenid != "undefined" && hashopenid != "null") { - // 获取到openid - window.localStorage.setItem("openid", hashopenid); - setopenId(hashopenid); - - // window.alert(hashopenid); - // 查询数据库是否存在openid - Taro.request({ - method: "GET", - url: `${process.env.TARO_API_API}/user/${hashopenid}`, //仅为示例,并非真实的接口地址 - // url: `${process.env.TARO_API_API}/user/${window.localStorage.getItem("openid")}`, //仅为示例,并非真实的接口地址 - success: function (res) { - if (res?.data?.userInfo) { - // 有完整的用户信息userInfo - setuserInfo(res?.data?.userInfo); - window.localStorage.setItem("userInfo", userInfo); - } - }, - complete: function (finy) { - // 获取wxid参数 - getWxidFromUrl(); - }, - }); - } else { - // let nowUrl = window.location.href; - // window.localStorage.removeItem("openid"); - // // let payUrl = `https://payjs.cn/api/openid?mchid=1561724891&callback_url=${nowUrl}`; - // let payUrl = `https://xorpay.com/api/openid/8220&callback_url=${nowUrl}`; - // window.location.href = payUrl; - - // 清除旧 openid(可选,建议保留) - window.localStorage.removeItem("openid"); - - // 当前完整 URL(包括 ? 参数和 # hash) - let nowUrl = window.location.href; - - // 正确构造 XorPay openid 获取地址 - // 1. 参数名必须是 callback - // 2. 值必须 encodeURIComponent 编码 - let payUrl = `https://xorpay.com/api/openid/8220?callback=${encodeURIComponent( - nowUrl - )}`; - - console.log("跳转获取 openid 的 URL:", payUrl); - // 示例输出:https://xorpay.com/api/openid/8220?callback=http%3A%2F%2F192.168.31.222%3A10086%2F%23%2Fpay - - // window.location.href = payUrl; - } - }; - - // 获取更新用户信息 - const getUserinfo = (wxid) => { - Taro.request({ - method: "GET", - url: `${process.env.TARO_API_API}/user/${wxid}`, //仅为示例,并非真实的接口地址 - // url: `${process.env.TARO_API_API}/user/${window.localStorage.getItem("openid")}`, //仅为示例,并非真实的接口地址 - success: function (res) { - if (res?.data?.userInfo) { - //判断wxid是否存在 - if (res?.data?.userInfo?.openid) { - // 有完整的用户信息userInfo - setuserInfo(res?.data?.userInfo); - window.localStorage.setItem("userInfo", userInfo); - } - } - }, - complete: function (finy) { }, - }); - }; - - // 获取url中的wxid - const getWxidFromUrl = () => { - const urlParams = new URLSearchParams(window.location.search); - const urlwxid = urlParams.get("wxid"); - if (!!urlwxid) { - setwxid(urlwxid); - window.localStorage.setItem("wxid", wxid); - // 优先查询openid是否存在数据库 - Taro.request({ - method: "GET", - url: `${process.env.TARO_API_API}/user/${urlwxid}`, //仅为示例,并非真实的接口地址 - // url: `${process.env.TARO_API_API}/user/${window.localStorage.getItem("openid")}`, //仅为示例,并非真实的接口地址 - success: function (res) { - if (res?.data?.userInfo) { - //判断wxid是否存在 - if (res?.data?.userInfo?.openid) { - // window.alert(res?.data?.userInfo?.openid); - // window.alert(window.localStorage.getItem("openid")); - - if ( - res?.data?.userInfo?.openid !== - window.localStorage.getItem("openid") - ) { - // 删除其他用户的wxid - window.localStorage.removeItem("userInfo"); - window.localStorage.removeItem("openid"); - window.localStorage.removeItem("wxid"); - window.location.href = "https://hackrobot.cn"; - } else { - // 有完整的用户信息userInfo - setuserInfo(res?.data?.userInfo); - window.localStorage.setItem("userInfo", userInfo); - } - } else { - // 绑定wxid与openid到数据库 - wxidTapopenid(urlwxid); - } - } else { - // 新增wxid用户 - } - }, - complete: function (finy) { - // tg消息通知 - // tgMessage(); - }, - }); - } else { - // tg消息通知 - // tgMessage(); - } - return wxid; - }; - - // 绑定wxid与openid - const wxidTapopenid = (newWxid) => { - const userUpdateData = { - openid: window.localStorage.getItem("openid"), // 只更新 openid 字段 - }; - Taro.request({ - method: "PUT", - // url: `${process.env.TARO_API_API}/user/${urlwxid}`, //仅为示例,并非真实的接口地址 - url: `${process.env.TARO_API_API}/user/${newWxid}`, //仅为示例,并非真实的接口地址 - data: userUpdateData, - header: { - "Content-Type": "application/json", - }, - success: function (res) { }, - fail: function (err) { }, - complete: function (finy) { }, - }); - }; - - // 新增openid用户 - const addopenidUser = () => { - const userUpdateData = { - openid: window.localStorage.getItem("openid"), // 只更新 openid 字段 - }; - Taro.request({ - method: "GET", - // url: `${process.env.TARO_API_API}/user/${urlwxid}`, //仅为示例,并非真实的接口地址 - url: `${process.env.TARO_API_API}/user/${window.localStorage.getItem( - "openid" - )}`, //仅为示例,并非真实的接口地址 - data: userUpdateData, - header: { - "Content-Type": "application/json", - }, - success: function (res) { }, - fail: function (err) { }, - complete: function (finy) { }, - }); - }; - - // 网页查看通知 - const tgMessage = () => { - Taro.request({ - method: "GET", - url: `${process.env.TARO_API_API - }/tgMessage?openid=${window.localStorage.getItem("openid")}`, //仅为示例,并非真实的接口地址 - // url: "https://pay.hackrobot.cn${process.env.TARO_API_API}/payh5", //仅为示例,并非真实的接口地址 - success: function (res) { - console.log("访问提醒发送成功"); - }, - }); - }; - - // 入群申请 - - useEffect(() => { - let inwechat = isWeChatFun(); - if (inwechat) { - // 获取openId - // getOpenid(); - } else { - // window.alert('不在微信') - } - }, []); - - useEffect(() => { - // alert(window.localStorage.getItem("bookpay")) - setbookpay(window.localStorage.getItem("bookpay")); - }, []); - - const goBack = () => { - Taro.navigateBack({}); - }; - - const handleClick = (value) => { - // window.alert(value) - setopen(value); - }; - - - const goRead2 = () => { - Taro.navigateTo({ - url: "/pages/book/index", - }); - }; const goRead = () => { - // 极简:直接用 router.params 构建参数字符串(无参数时自动为空) - const paramsStr = router.params - ? '?' + new URLSearchParams(router.params).toString() - : ''; - - Taro.navigateTo({ - url: `/pages/book/index${paramsStr}`, - }); -}; + const paramsStr = router.params + ? "?" + new URLSearchParams(router.params).toString() + : ""; + Taro.navigateTo({ url: `/pages/book/index${paramsStr}` }); + }; return ( - <> - - {/* 在线图书 */} + + + + 《数字游民:地理套利与自动化》 + 边旅行边赚钱 · 杠杆主义 · 地理套利 - {/* 参考资料https://readmake.com/ */} - - - 《数字游民:地理套利与自动化》 - {/* 《云手机》 */} - - {/* 手册3 */} - {/* - 🚀 边旅行转赚钱 - 1️⃣ 杠杆:媒体与代码 - 2️⃣ 出海:地理套利 - 3️⃣ 即时业务:一夜之间上线网站 - 4️⃣ homelab:你的全球总部 - 5️⃣ 远程团队:分布式协作 - 5️⃣ ----------------- - - 🔥 一起实践,交流 - */} - - {/* 卡片 */} - - - - - - - Tiktok - 无人直播 - + + + + - - - - - - Youtube - 自媒体 - - - - - - - - web - saas+web3 - + + Tiktok + 无人直播 - - - { - goRead(); - }} - className="startRead" - type="primary" - size="small" - circle - > - 开始阅读 - + + + + + + Youtube + 自媒体 + + + + + + + + Web + SaaS + Web3 + - - 云手机封面 + + + + 开始阅读 + - - - - - Tiktok无人直播 - - - - - Youtube自媒体 - - - - web:saas+web3 - - + + 数字游民封面 - {/* - Copyright © 2025 - */} - {/* */} - + + + + + + Tiktok无人直播 + + + + Youtube自媒体 + + + + Web: SaaS + Web3 + + + + ); }; diff --git a/src/pages/index/index.scss b/src/pages/index/index.scss index 37d3629..e73a239 100644 --- a/src/pages/index/index.scss +++ b/src/pages/index/index.scss @@ -1,316 +1,393 @@ .index { - // background-color: #002329; - // width: 100vw; - // padding-top: 80px; - height: 100vh; - // height: 100%; - // padding-left: 100px; - // padding-right: 100px; - // background-color: yellow; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + padding: 0 30px; + background: linear-gradient(180deg, #f8fbff 0%, #fff 100%); + .index-book { - // margin-top: 80px; - // width: 100%; + width: 100%; min-height: 300px; - // padding: 20px; display: flex; flex-direction: column-reverse; align-items: center; - justify-content: space-between; - // background-color: #f0f0f0; - // background-color: #f0f5ff; - // background-color: yellow; + justify-content: center; + padding: 40px 0; + .index_book_left { - min-width: 600px; - min-height: 300px; + width: 100%; display: flex; flex-direction: column; align-items: center; - justify-content: space-around; - // background-color: rgb(208, 255, 0); + justify-content: center; + .left_title { - font-size: 50px; - font-weight: 700; - color: #69b1ff; + font-size: 40px; + font-weight: 800; + color: #1a1a2e; + text-align: center; + line-height: 1.4; + letter-spacing: 2px; } - .left_text { - font-size: 30px; - } - .left_mobile { - margin-top: 20px; - width: 100%; - font-size: 30px; - display: flex; - flex-direction: column; - // align-items: center; - // justify-content: space-around; - // background-color: yellow; + + .left_subtitle { + font-size: 26px; + color: #8c8c8c; + margin-top: 16px; + text-align: center; + letter-spacing: 1px; } .card { - margin-top: 40px; + margin-top: 50px; + width: 100%; display: flex; flex-direction: column; align-items: center; - justify-content: center; - .cardItem { - padding: 20px; - // padding-top: 20px; - width: 500px; - // height: 300px; + gap: 30px; - margin-bottom: 20px; + .cardItem { + width: 100%; + max-width: 600px; + padding: 28px 34px; display: flex; flex-direction: row; - background-color: rgb(var(--color-gray-50) / var(--tw-bg-opacity, 1)); - // background-color: yellow; - background-color: #f0f5ff; - border-radius: 20px; + align-items: center; + background: #fff; + border-radius: 16px; + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06); + transition: transform 0.2s ease, box-shadow 0.2s ease; + + &:active { + transform: scale(0.98); + } - // align-items: center; - // justify-content: center; .cardItem_left { - // width: 100px; - // height: 100%; - // background-color: #69b1ff; - display: flex; - flex-direction: column; - // align-items: center; - // justify-content: center; - margin-right: 40px; + margin-right: 30px; + flex-shrink: 0; + .cardItem_left_img { width: 80px; height: 80px; - } - .youtubeSvg { - position: relative; - top: -5px; + border-radius: 12px; } } + .cardItem_right { flex: 1; display: flex; flex-direction: column; - align-items: center; justify-content: center; - // background-color: red; + .cardItem_rightUp { - width: 100%; - height: 80px; - // background-color: aquamarine; - display: flex; - flex-direction: row; font-weight: 700; - font-size: 35px; - // align-items: center; - // justify-content: center; + font-size: 34px; + color: #1a1a2e; + line-height: 1.4; } + .cardItem_rightDown { - width: 100%; - // height: 200px; - flex: 1; - // background-color: red; - display: flex; - flex-direction: row; - // align-items: center; - // justify-content: center; - font-size: 30px; - flex-wrap: wrap; - color: #262626; + font-size: 28px; + color: #8c8c8c; + margin-top: 6px; } } - .left_mobile { - margin-top: 20px; - // width: 100%; - font-size: 30px; - display: flex; - flex-direction: column; - // background-color: aqua; - // align-items: center; - // justify-content: space-around; - // background-color: yellow; - } } } .left_btn { - margin-top: 40px; - // background-color: aqua; - // display: none; + margin-top: 60px; + margin-bottom: 60px; + width: 100%; + display: flex; + justify-content: center; + .startRead { - width: 500px; + width: 100%; + max-width: 600px; + height: 96px; + font-size: 34px; + font-weight: 600; + border-radius: 48px; + background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); + border: none; + color: #fff; + display: flex; + align-items: center; + justify-content: center; + line-height: 1; } } } + .index_book_img { - margin-top: 80px; - width: 300px; - height: 300px; + width: 240px; + height: 240px; margin-bottom: 40px; + flex-shrink: 0; + + .book_img { + width: 100%; + height: 100%; + object-fit: contain; + border-radius: 20px; + } } } .svgItems { - display: flex; - flex-direction: column; - align-items: center; - justify-content: space-around; - // background-color: yellow; - margin-top: 20px; - // padding: 20px; - .bookImg { - display: none; - } - } - .index_btn { - // background-color: yellow; - // margin-top: 40px; - .startRead { - width: 200px; - } - } - .footer { - position: absolute; - // position: fixed; - bottom: 0px; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - width: 100%; - height: 60px; - // margin-bottom: 20px; - font-size: 14px; - // background-color: yellow; + display: none; } } -@media (min-width: 1536px) { +// ===== Tablet: 768px+ ===== +@media (min-width: 768PX) { .index { - // padding: 100px; - height: 90vh; - padding-top: 20px; - padding-bottom: 10px; - padding-left: 20px; - padding-right: 20px; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - // background-color: aqua; - // color: red; - position: relative; + padding: 0 40PX; .index-book { - width: 100%; - max-width: 1400px; - - // max-width: 2000px; - // padding: 20px; - display: flex; - flex-direction: row; - align-items: center; - // justify-content: center; - justify-content: space-between; - background-color: #f0f5ff; - border-radius: 20px; + padding: 40PX 0; .index_book_left { - display: flex; - flex-direction: column; - align-items: center; - justify-content: space-around; - // background-color: yellow; - // background-color: #69b1ff; - .left_mobile { - display: none; + .left_title { + font-size: 32PX; } + + .left_subtitle { + font-size: 16PX; + } + .card { - display: none; + margin-top: 28PX; + flex-direction: row; + flex-wrap: wrap; + justify-content: center; + gap: 16PX; + + .cardItem { + width: calc(50% - 8PX); + max-width: 320PX; + padding: 16PX 20PX; + + .cardItem_left { + margin-right: 16PX; + .cardItem_left_img { + width: 48PX; + height: 48PX; + border-radius: 8PX; + } + } + + .cardItem_right { + .cardItem_rightUp { + font-size: 17PX; + } + .cardItem_rightDown { + font-size: 14PX; + margin-top: 2PX; + } + } + } } + .left_btn { - display: flex; + margin-top: 28PX; + margin-bottom: 28PX; .startRead { - // display: flex; - width: 200px; + max-width: 280PX; + height: 44PX; + font-size: 16PX; + border-radius: 22PX; + line-height: 44PX; } } } - .index_book_img { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - // height: 100%; - // background-color: yellow; - .book_img{ - margin-bottom: 40px; + .index_book_img { + width: 200PX; + height: 200PX; + margin-bottom: 20PX; + + .book_img { + border-radius: 14PX; + } + } + } + } +} + +/* stylelint-disable */ +// ===== Desktop: 1024px+ ===== +// 使用 PX (大写) 防止 Taro postcss-pxtransform 转换 +@media (min-width: 1024PX) { + .index { + justify-content: center; + padding: 30PX 80PX 0; + + .index-book { + flex-direction: row; + max-width: 1200PX; + padding: 60PX 50PX; + background: #f0f5ff; + border-radius: 24PX; + gap: 60PX; + + .index_book_left { + flex: 1; + align-items: flex-start; + + .left_title { + font-size: 36PX; + text-align: left; + line-height: 1.4; + letter-spacing: 1PX; + } + + .left_subtitle { + font-size: 16PX; + text-align: left; + margin-top: 12PX; + } + + .card { + display: none; + } + + .left_btn { + margin-top: 36PX; + margin-bottom: 0; + max-width: 500PX; + justify-content: center; + + .startRead { + width: 200PX; + max-width: 200PX; + height: 48PX; + font-size: 16PX; + font-weight: 600; + border-radius: 24PX; + line-height: 48PX; + } + } + } + + .index_book_img { + width: 260PX; + height: 260PX; + margin-bottom: 0; + + .book_img { + border-radius: 16PX; } } } .svgItems { - // width: 1400px; - width: 100%; - max-width: 1400px; - margin-top: 80px; - // padding: 0px; display: flex; - flex-direction: row; - align-items: center; + width: 100%; + max-width: 1200PX; + margin-top: 60PX; + margin-bottom: 60PX; justify-content: center; - // background-color: #69b1ff; - // background-color: yellow; - // background-color: brown; + .svgItem { - // min-width: 1400px; width: 100%; display: flex; flex-direction: row; - align-items: center; - justify-content: space-around; - // background-color: #69b1ff; + align-items: stretch; + justify-content: center; + gap: 40PX; + .bookImg { - width: 350px; - height: 350px; + width: 240PX; + min-height: 240PX; display: flex; flex-direction: column; align-items: center; justify-content: center; - // background-color: yellow; + padding: 36PX 24PX; + background: #fff; + border-radius: 16PX; + box-shadow: 0 4PX 20PX rgba(0, 0, 0, 0.06); + transition: transform 0.3s ease, box-shadow 0.3s ease; + + &:hover { + transform: translateY(-4PX); + box-shadow: 0 8PX 30PX rgba(0, 0, 0, 0.1); + } + .bookImg_text { - margin-top: 20px; - font-size: 30px; + margin-top: 16PX; + font-size: 16PX; + font-weight: 600; + color: #1a1a2e; + text-align: center; } } } } - - .index_btn { - .startRead { - display: none; - } - } - - .footer { - position: absolute; - // position: fixed; - bottom: 0px; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - width: 100%; - height: 60px; - // margin-bottom: 20px; - font-size: 14px; - // background-color: yellow; - } } } -.footer_div { - width: 100%; - height: 60px; +// ===== Large Desktop: 1536px+ ===== +@media (min-width: 1536PX) { + .index { + padding-top: 40PX; + + .index-book { + max-width: 1400PX; + padding: 80PX 60PX; + + .index_book_left { + .left_title { + font-size: 42PX; + } + + .left_subtitle { + font-size: 18PX; + } + + .left_btn { + max-width: 580PX; + + .startRead { + width: 220PX; + max-width: 220PX; + height: 52PX; + font-size: 17PX; + border-radius: 26PX; + line-height: 52PX; + } + } + } + + .index_book_img { + width: 300PX; + height: 300PX; + } + } + + .svgItems { + max-width: 1400PX; + margin-top: 80PX; + + .svgItem { + gap: 50PX; + + .bookImg { + width: 300PX; + min-height: 300PX; + padding: 40PX 30PX; + + .bookImg_text { + font-size: 18PX; + margin-top: 20PX; + } + } + } + } + } } +/* stylelint-enable */ diff --git a/src/pages/sphone/index.scss b/src/pages/sphone/index.scss index 27fd091..9023e8e 100644 --- a/src/pages/sphone/index.scss +++ b/src/pages/sphone/index.scss @@ -1,37 +1,158 @@ - -// 在 src/index.scss 中引入 GitHub Markdown 主题 -@import 'github-markdown-css/github-markdown.css'; +@import "github-markdown-css/github-markdown.css"; .Sphone { - padding: 40px; -// font-size: 30px !important; - // color: #666; + padding: 30px; - .markdown-body{ - font-size: 30px !important; - } - img { - max-width: 100%; - height: auto; /* 保持图像的纵横比 */ - } - .formCode { - display: flex; - flex-direction: row; - .codeBtn { - background-color: aquamarine; - // height: 80px; - // color: red; + .markdown-body { + font-size: 30px; + line-height: 1.8; + color: #2c3e50; + word-wrap: break-word; + overflow-wrap: break-word; + + h1 { + font-size: 44px; + font-weight: 800; + color: #1a1a2e; + margin: 48px 0 24px; + padding-bottom: 16px; + border-bottom: 2px solid #e8e8e8; + } + + h2 { + font-size: 38px; + font-weight: 700; + color: #2c3e50; + margin: 40px 0 20px; + padding-bottom: 12px; + border-bottom: 1px solid #f0f0f0; + } + + h3 { + font-size: 34px; + font-weight: 600; + color: #34495e; + margin: 32px 0 16px; + } + + blockquote { + margin: 24px 0; + padding: 20px 24px; + border-left: 6px solid #4facfe; + background: #f8fbff; + border-radius: 0 12px 12px 0; + color: #4a5568; + } + + pre { + margin: 24px 0; + padding: 24px; + background: #1e1e2e; + border-radius: 12px; + overflow-x: auto; + + code { + font-family: "SF Mono", "Fira Code", Menlo, Monaco, Consolas, monospace; + font-size: 26px; + color: #e2e8f0; + background: none; + padding: 0; + } + } + + code { + font-size: 0.88em; + padding: 4px 10px; + background: #f1f5f9; + color: #e53e3e; + border-radius: 6px; + } + + img { + display: block; + margin: 24px auto; + max-width: 100%; + height: auto; + border-radius: 8px; + } + + a { + color: #4facfe; + text-decoration: none; + } + + table { + width: 100%; + display: block; + overflow-x: auto; + + th { + background: #f8fafc; + font-weight: 600; + padding: 16px 20px; + border: 1px solid #e2e8f0; + font-size: 28px; + } + + td { + padding: 14px 20px; + border: 1px solid #e2e8f0; + font-size: 28px; + } + + tr:nth-child(even) { + background: #f8fafc; + } } } .joinSalon { - // position: relative; - // bottom: 20px; - // position: fixed; - width: 700px; + margin-top: 40px; + margin-bottom: 40px; + border-radius: 12px; font-weight: 600; - background-color:#72D572 ; - border-color: #72D572 ;; + background-color: #72D572; + border-color: #72D572; + } +} + +@media (min-width: 768px) { + .Sphone { + padding: 30px 60px; + + .markdown-body { + font-size: 16px; + max-width: 800px; + margin: 0 auto; + + h1 { font-size: 28px; } + h2 { font-size: 24px; } + h3 { font-size: 20px; } + pre code { font-size: 14px; } + table th, table td { font-size: 15px; padding: 10px 16px; } + } + + .joinSalon { + max-width: 400px; + margin-left: auto; + margin-right: auto; + display: block; + } + } +} + +@media (min-width: 1024px) { + .Sphone { + display: flex; + flex-direction: column; + align-items: center; + + .markdown-body { + width: 720px; + font-size: 17px; + + pre code { font-size: 14px; } + img { max-width: 90%; } + } } - }