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%; }
+ }
}
-
}