This commit is contained in:
eric
2026-03-07 20:02:47 -06:00
parent 20fe6ca205
commit aed78201a9
8 changed files with 1213 additions and 1234 deletions

View File

@@ -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
}
}
}
}
}

View File

@@ -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: {

View File

@@ -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
}

View File

@@ -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 */

View File

@@ -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(/<img /g, '<img loading="lazy" decoding="async" ');
}
function splitIntoChunks(html) {
const parts = html.split(/(?=<h1[\s>])/i);
return parts.filter(p => p.trim().length > 0);
}
function extractHeaders(html) {
const regex = /<h1[^>]*>(.*?)<\/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 <div dangerouslySetInnerHTML={{ __html: html }} />;
}
return <div ref={ref} style={{ minHeight: "200px" }} />;
});
// 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([]); // 存储所有 <h1> 的内容
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.localStorageH5 环境兼容)
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();
// 方法1Taro 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 (
<>
<View className="book_header_div"></View>
<View className="book_header_div" />
<View className="book_header">
<View
className="book_header_left"
onClick={() => {
Taro.redirectTo({
url: `/`,
});
// Taro.navigateBack({
// delta: 1, // 表示返回的页面数,默认是 1表示返回上一页
// });
}}
>
<AtIcon value="chevron-left"></AtIcon>
<View className="book_header_left" onClick={() => Taro.redirectTo({ url: "/" })}>
<AtIcon value="chevron-left" />
</View>
<View className="book_header_middle">{`数字游民`}</View>
<View
className="book_header_right"
onClick={() => {
setshow(!show);
}}
>
<AtIcon value="bullet-list"></AtIcon>
<View className="book_header_middle"></View>
<View className={`book_header_right ${show ? 'active' : ''}`} onClick={() => setshow(!show)}>
<AtIcon value="bullet-list" />
<View className="book_header_right_text pc-only"></View>
</View>
</View>
<View className="Book">
<View
ref={contentRef}
className="markdown-body" // 添加 GitHub Markdown 主题类
dangerouslySetInnerHTML={{
// __html: marked(markText),
// __html: marked(book == 1 ? bookText : infoText),
__html: marked(paidType=='book'|| vipbook ? bookText : infoText),
}}
></View>
<View className="Book">
{!contentReady ? (
<View className="markdown-loading">...</View>
) : (
<View ref={contentRef} className="markdown-body">
{chunks.map((chunk, i) => (
<LazyChunk
key={i}
html={chunk}
forceVisible={forceShowAll || i === 0}
/>
))}
</View>
)}
<AtDrawer
className="book_AtDrawer"
show={show}
mask
items={headers}
onItemClick={(index) => {
handleJump(headers[index]);
}}
onClose={() => {
setshow(false);
}}
>
<View className="drawer-item"></View>
{/* <View className="drawer-item">如果items没有数据就会展示children</View> */}
{/* <View className="drawer-item">
这是自定义内容 <AtIcon value="home" size="20" />
</View> */}
</AtDrawer>
onItemClick={(index) => handleJump(headers[index])}
onClose={() => setshow(false)}
/>
{/* 判断是否是微信 */}
{!isWeChat && (
// 公众号二维码
<div
style={{
color: "red",
display: "flex", // 关键flex 布局
justifyContent: "center", // 水平居中
alignItems: "center", // 垂直居中(可选,如果你想整块垂直也居中)
flexDirection: "column", // 可选:让内容垂直排列(如果下面还有文字)
margin: "20px 0", // 可选:上下留点间距
}}
>
<View className="qrcode-section">
<Image
className="hackrobot"
src={hackrobot}
style={{
width: "300px",
height: "300px",
maxWidth: "none",
maxHeight: "none",
}}
// src={`https://minioweb.hackrobot.cn/hackrobot/propless.svg`}
style={{ width: "300px", height: "300px" }}
lazyLoad
></Image>
</div>
/>
</View>
)}
{/* 判断有没有wxid */}
{/* {book != 1 ? ( */}
{paidType != "book" && !vipbook ? (
{!isPaid && (
<AtButton
type="primary"
size="normal"
className="joinSalon"
onClick={() => {
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";
}
}}
>
{`🔒阅读更多`}
</AtButton>
) : // <span style={{ color: "green" }}>持续更新中....</span>
null}
)}
</View>
</>
);

View File

@@ -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 (
<>
<View className="index">
{/* 在线图书 */}
<View className="index">
<View className="index-book animate__animated animate__pulse">
<View className="index_book_left">
<View className="left_title">数字游民:地理套利与自动化</View>
<View className="left_subtitle">边旅行边赚钱 · 杠杆主义 · 地理套利</View>
{/* 参考资料https://readmake.com/ */}
<View className="index-book animate__animated animate__pulse">
<View className="index_book_left">
<View className="left_title">数字游民:地理套利与自动化</View>
{/* <View className="left_title">《云手机》</View> */}
{/* <View className="left_text">手册3</View> */}
{/* <View className="left_mobile">
<Text>🚀 边旅行转赚钱</Text>
<Text>1⃣ 杠杆:媒体与代码</Text>
<Text>2⃣ 出海:地理套利</Text>
<Text>3⃣ 即时业务:一夜之间上线网站</Text>
<Text>4⃣ homelab:你的全球总部</Text>
<Text>5⃣ 远程团队:分布式协作</Text>
<Text>5⃣ -----------------</Text>
<Text>🔥 一起实践,交流</Text>
</View> */}
{/* 卡片 */}
<View className="card">
<View className="cardItem">
<View className="cardItem_left">
<Image
className="cardItem_left_img youtubeSvg"
// src={travel}
src={tiktok}
// src={`https://minioweb.hackrobot.cn/hackrobot/travel.svg`}
lazyLoad
></Image>
</View>
<View className="cardItem_right">
<View className="cardItem_rightUp">Tiktok</View>
<View className="cardItem_rightDown">无人直播</View>
</View>
<View className="card">
<View className="cardItem">
<View className="cardItem_left">
<Image className="cardItem_left_img" src={tiktok} lazyLoad />
</View>
<View className="cardItem">
<View className="cardItem_left">
<Image
className="cardItem_left_img"
// src={laptop}
src={youtube}
// src={`https://minioweb.hackrobot.cn/hackrobot/laptop.svg`}
lazyLoad
></Image>
</View>
<View className="cardItem_right">
<View className="cardItem_rightUp">Youtube</View>
<View className="cardItem_rightDown">自媒体</View>
</View>
</View>
<View className="cardItem">
<View className="cardItem_left">
<Image
className="cardItem_left_img"
// src={group}
src={web}
// src={`https://minioweb.hackrobot.cn/hackrobot/propless.svg`}
lazyLoad
></Image>
</View>
<View className="cardItem_right">
<View className="cardItem_rightUp">web</View>
<View className="cardItem_rightDown">saas+web3</View>
</View>
<View className="cardItem_right">
<View className="cardItem_rightUp">Tiktok</View>
<View className="cardItem_rightDown">无人直播</View>
</View>
</View>
<View className="left_btn ">
<AtButton
onClick={() => {
goRead();
}}
className="startRead"
type="primary"
size="small"
circle
>
开始阅读
</AtButton>
<View className="cardItem">
<View className="cardItem_left">
<Image className="cardItem_left_img" src={youtube} lazyLoad />
</View>
<View className="cardItem_right">
<View className="cardItem_rightUp">Youtube</View>
<View className="cardItem_rightDown">自媒体</View>
</View>
</View>
<View className="cardItem">
<View className="cardItem_left">
<Image className="cardItem_left_img" src={web} lazyLoad />
</View>
<View className="cardItem_right">
<View className="cardItem_rightUp">Web</View>
<View className="cardItem_rightDown">SaaS + Web3</View>
</View>
</View>
</View>
<View className="index_book_img">
<Image
className="book_img"
// src={phone2}
src={dgnomad}
// src="https://minioweb.hackrobot.cn/hackrobot/phone2.png"
fetchpriority="high" // 提示浏览器尽快下载
decoding="async" // 异步解码加快渲染
lazyLoad={false} // 禁止延迟加载LCP 图必须优先加载
alt="云手机封面"
/>
<View className="left_btn">
<AtButton
onClick={goRead}
className="startRead"
type="primary"
size="small"
circle
>
开始阅读
</AtButton>
</View>
</View>
<View className="svgItems">
<View className="svgItem">
<View className="bookImg">
<Image
// style="width: 40%px;height:100%;background: #fff;"
// src={book}
src={laptop}
// src={phone2}
// src={tiktok}
lazyLoad
/>
<View className="bookImg_text">Tiktok无人直播</View>
</View>
<View className="bookImg">
<Image
// style="width: 40%px;height:100%;background: #fff;"
// src={book}
src={travel}
// src={youtube}
lazyLoad
/>
<View className="bookImg_text">Youtube自媒体</View>
</View>
<View className="bookImg">
<Image
// style="width: 40%px;height:100%;background: #fff;"
// src={book}
src={code}
// src={web}
lazyLoad
/>
<View className="bookImg_text">web:saas+web3</View>
</View>
</View>
<View className="index_book_img">
<Image
className="book_img"
src={dgnomad}
fetchpriority="high"
decoding="async"
lazyLoad={false}
alt="数字游民封面"
/>
</View>
{/* <View className="footer">
<View>Copyright © 2025 </View>
</View> */}
</View>
{/* <View className="footer_div"></View> */}
</>
<View className="svgItems">
<View className="svgItem">
<View className="bookImg">
<Image src={laptop} lazyLoad />
<View className="bookImg_text">Tiktok无人直播</View>
</View>
<View className="bookImg">
<Image src={travel} lazyLoad />
<View className="bookImg_text">Youtube自媒体</View>
</View>
<View className="bookImg">
<Image src={code} lazyLoad />
<View className="bookImg_text">Web: SaaS + Web3</View>
</View>
</View>
</View>
</View>
);
};

View File

@@ -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 */

View File

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