Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1628efd971 | ||
|
|
75b21f3e6f | ||
|
|
61500c5a6b | ||
|
|
b7daa8cb20 | ||
|
|
f968a05ea5 | ||
|
|
b0086bfbd2 | ||
|
|
fc169823c9 | ||
|
|
a6f99fe82a | ||
|
|
672c090311 | ||
|
|
f345559f2f | ||
|
|
f455ac17a7 | ||
|
|
5b71b7b7f6 | ||
|
|
4d96aec152 | ||
|
|
d311f41dcf | ||
|
|
e5ea2ae7c9 | ||
|
|
c824641a9e | ||
|
|
a7ba3b5375 | ||
|
|
1e1374f665 | ||
|
|
edce5df9d1 | ||
|
|
38ecc9a082 | ||
|
|
d0ce5231d6 | ||
|
|
66d33c6703 | ||
|
|
edc65b9d22 | ||
|
|
181bd3f9c0 | ||
|
|
8def20ac27 | ||
|
|
2906d1d3fc | ||
|
|
afa521fae9 | ||
|
|
c2449cd2f0 | ||
|
|
b725918a85 | ||
|
|
f17c993447 | ||
|
|
21ba85ac5c | ||
|
|
7097f53525 | ||
|
|
853a55b4e9 | ||
|
|
b38885b46c | ||
|
|
7b42bbd69c | ||
|
|
77b121029f | ||
|
|
df754f3c27 | ||
|
|
6ff6cdbcb3 | ||
|
|
478d6c5747 | ||
|
|
2fc368896e | ||
|
|
cd8ebe4bcb | ||
|
|
863a0166b7 | ||
|
|
487b6cf158 |
0
.env.development
Normal file → Executable file
3
.env.production
Normal file → Executable file
@@ -1 +1,2 @@
|
||||
process.env.TARO_API_API="https://api.nomadcna.com"
|
||||
# process.env.TARO_API_API="https://api.nomadcna.com"
|
||||
process.env.TARO_API_API="https://api.hackrobot.cn"
|
||||
|
||||
3
.gitignore
vendored
Normal file → Executable file
@@ -4,4 +4,5 @@ deploy_versions/
|
||||
.rn_temp/
|
||||
node_modules/
|
||||
.DS_Store
|
||||
.swc
|
||||
.swc
|
||||
/dist/.user.ini
|
||||
|
||||
0
babel.config.js
Normal file → Executable file
0
config/dev.js
Normal file → Executable file
102
config/index.js
Normal file → Executable file
@@ -1,133 +1,111 @@
|
||||
import path from "path";
|
||||
// const { GenerateSW } = require("workbox-webpack-plugin");
|
||||
|
||||
const WebpackObfuscator = require("webpack-obfuscator");
|
||||
|
||||
const config = {
|
||||
projectName: "cityh5",
|
||||
|
||||
date: "2023-12-2",
|
||||
designWidth: 750,
|
||||
deviceRatio: {
|
||||
640: 2.34 / 2,
|
||||
750: 1,
|
||||
828: 1.81 / 2,
|
||||
},
|
||||
deviceRatio: { 640: 2.34 / 2, 750: 1, 828: 1.81 / 2 },
|
||||
sourceRoot: "src",
|
||||
outputRoot: "dist",
|
||||
plugins: [],
|
||||
defineConstants: {},
|
||||
copy: {
|
||||
patterns: [],
|
||||
options: {},
|
||||
},
|
||||
copy: { patterns: [], options: {} },
|
||||
framework: "react",
|
||||
compiler: "webpack5",
|
||||
|
||||
cache: {
|
||||
enable: false, // Webpack 持久化缓存配置,建议开启。默认配置请参考:https://docs.taro.zone/docs/config-detail#cache
|
||||
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");
|
||||
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") {
|
||||
// js代码混淆
|
||||
// ✅ 保留原有混淆逻辑
|
||||
chain.plugin("obfuscator").use(WebpackObfuscator, [
|
||||
{
|
||||
rotateStringArray: true,
|
||||
stringArray: true,
|
||||
stringArrayThreshold: 0.5, // 降低比例,提高速度
|
||||
stringArrayThreshold: 0.5,
|
||||
compact: true,
|
||||
controlFlowFlattening: false, // 关闭,提高速度
|
||||
deadCodeInjection: false, // 关闭,提高速度
|
||||
controlFlowFlattening: false,
|
||||
deadCodeInjection: false,
|
||||
debugProtection: false,
|
||||
disableConsoleOutput: true,
|
||||
selfDefending: false, // 关闭,提高速度
|
||||
simplify: true, // 开启简化,提高性能
|
||||
// 禁用对 async/await 的转换
|
||||
target: "browser", // 或设置为适合目标环境的 ECMAScript 版本
|
||||
disableOptimizations: true, // 禁用某些优化来避免处理 async/await
|
||||
selfDefending: false,
|
||||
simplify: true,
|
||||
target: "browser",
|
||||
disableOptimizations: true,
|
||||
},
|
||||
["**/*.js", "!node_modules/**"], // 避免对 node_modules 进行混淆
|
||||
["**/*.js", "!node_modules/**"],
|
||||
]);
|
||||
}
|
||||
|
||||
// ✅ markdown 支持
|
||||
chain.module
|
||||
.rule("markdown")
|
||||
.test(/\.md$/)
|
||||
.use("raw-loader")
|
||||
.loader("raw-loader")
|
||||
.end();
|
||||
|
||||
// ✅ ico 支持
|
||||
chain.module
|
||||
.rule("ico")
|
||||
.test(/\.ico$/)
|
||||
.use("file-loader")
|
||||
.loader("file-loader")
|
||||
.options({
|
||||
name: "static/images/[name].[ext]",
|
||||
});
|
||||
.options({ name: "static/images/[name].[ext]" });
|
||||
},
|
||||
|
||||
esnextModules: ["taro-ui"],
|
||||
publicPath: "/",
|
||||
// publicPath: `/v${new Date().getTime()}/`,
|
||||
|
||||
publicPath: "/", // ✅ 部署到根目录;如果是子路径,需要改成 /xxx/
|
||||
staticDirectory: "static",
|
||||
|
||||
postcss: {
|
||||
autoprefixer: {
|
||||
enable: true,
|
||||
config: {},
|
||||
},
|
||||
autoprefixer: { enable: true, config: {} },
|
||||
cssModules: {
|
||||
enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true
|
||||
enable: false,
|
||||
config: {
|
||||
namingPattern: "module", // 转换模式,取值为 global/module
|
||||
namingPattern: "module",
|
||||
generateScopedName: "[name]__[local]___[hash:base64:5]",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
router: {
|
||||
// basename: '/myapp',
|
||||
mode: "browser", // 或者是 'hash'
|
||||
mode: "browser",
|
||||
customRoutes: {
|
||||
// "页面路径": "自定义路由"
|
||||
"/pages/index/index": "/",
|
||||
"/pages/book/index": "/book",
|
||||
// '/pages/detail/index': ['/detail'], // 可以通过数组为页面配置多个自定义路由
|
||||
// "pages/my/index": "/my",
|
||||
// "pages/meetup/index": "/meetup",
|
||||
// "pages/meetup/Detail/index": "/detail",
|
||||
},
|
||||
},
|
||||
// defineConstants: {
|
||||
// HOST: '"/api"'
|
||||
// },
|
||||
|
||||
devServer: {
|
||||
// host: 'nomad.hackrobot.cn', // 替换为您的域名
|
||||
// port: 10087, // 选择一个适当的端口号
|
||||
proxy: {
|
||||
"/api/": {
|
||||
target: JSON.parse('"http://localhost:8700"'), //本地调试端口
|
||||
pathRewrite: {
|
||||
"^/api/": "/",
|
||||
},
|
||||
target: "http://localhost:8700",
|
||||
pathRewrite: { "^/api/": "/" },
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
// 允许特定域名访问
|
||||
allowedHosts: ["nomadro.com", "localhost", ".nomadro.com","hackrobot.cn"], // 允许所有 *.nomadyt.com 子域访问
|
||||
allowedHosts: ["nomadro.com", "localhost", ".nomadro.com", "hackrobot.cn",".ngrok-free.app"],
|
||||
},
|
||||
|
||||
htmlPluginOption: {
|
||||
favicon: path.resolve(__dirname, "..", "src", "images", "dgnomad.png"), // 同样处理 favicon 路径
|
||||
favicon: path.resolve(__dirname, "..", "src", "images", "dgnomad.png"),
|
||||
meta: {
|
||||
"cache-control": "no-cache, no-store, must-revalidate",
|
||||
pragma: "no-cache",
|
||||
|
||||
152
config/indexBack.js
Executable file
@@ -0,0 +1,152 @@
|
||||
import path from "path";
|
||||
// const { GenerateSW } = require("workbox-webpack-plugin");
|
||||
|
||||
const WebpackObfuscator = require("webpack-obfuscator");
|
||||
const config = {
|
||||
projectName: "cityh5",
|
||||
|
||||
date: "2023-12-2",
|
||||
designWidth: 750,
|
||||
deviceRatio: {
|
||||
640: 2.34 / 2,
|
||||
750: 1,
|
||||
828: 1.81 / 2,
|
||||
},
|
||||
sourceRoot: "src",
|
||||
outputRoot: "dist",
|
||||
plugins: [],
|
||||
defineConstants: {},
|
||||
copy: {
|
||||
patterns: [],
|
||||
options: {},
|
||||
},
|
||||
framework: "react",
|
||||
compiler: "webpack5",
|
||||
cache: {
|
||||
enable: false, // Webpack 持久化缓存配置,建议开启。默认配置请参考:https://docs.taro.zone/docs/config-detail#cache
|
||||
},
|
||||
|
||||
h5: {
|
||||
webpackChain(chain) {
|
||||
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",
|
||||
},
|
||||
]);
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
// js代码混淆
|
||||
chain.plugin("obfuscator").use(WebpackObfuscator, [
|
||||
{
|
||||
rotateStringArray: true,
|
||||
stringArray: true,
|
||||
stringArrayThreshold: 0.5, // 降低比例,提高速度
|
||||
compact: true,
|
||||
controlFlowFlattening: false, // 关闭,提高速度
|
||||
deadCodeInjection: false, // 关闭,提高速度
|
||||
debugProtection: false,
|
||||
disableConsoleOutput: true,
|
||||
selfDefending: false, // 关闭,提高速度
|
||||
simplify: true, // 开启简化,提高性能
|
||||
// 禁用对 async/await 的转换
|
||||
target: "browser", // 或设置为适合目标环境的 ECMAScript 版本
|
||||
disableOptimizations: true, // 禁用某些优化来避免处理 async/await
|
||||
},
|
||||
["**/*.js", "!node_modules/**"], // 避免对 node_modules 进行混淆
|
||||
]);
|
||||
}
|
||||
|
||||
chain.module
|
||||
.rule("markdown")
|
||||
.test(/\.md$/)
|
||||
.use("raw-loader")
|
||||
.loader("raw-loader")
|
||||
.end();
|
||||
chain.module
|
||||
.rule("ico")
|
||||
.test(/\.ico$/)
|
||||
.use("file-loader")
|
||||
.loader("file-loader")
|
||||
.options({
|
||||
name: "static/images/[name].[ext]",
|
||||
});
|
||||
},
|
||||
|
||||
esnextModules: ["taro-ui"],
|
||||
publicPath: "/",
|
||||
// publicPath: `/v${new Date().getTime()}/`,
|
||||
|
||||
staticDirectory: "static",
|
||||
postcss: {
|
||||
autoprefixer: {
|
||||
enable: true,
|
||||
config: {},
|
||||
},
|
||||
cssModules: {
|
||||
enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true
|
||||
config: {
|
||||
namingPattern: "module", // 转换模式,取值为 global/module
|
||||
generateScopedName: "[name]__[local]___[hash:base64:5]",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
router: {
|
||||
// basename: '/myapp',
|
||||
mode: "browser", // 或者是 'hash'
|
||||
customRoutes: {
|
||||
// "页面路径": "自定义路由"
|
||||
"/pages/index/index": "/",
|
||||
"/pages/book/index": "/book",
|
||||
// '/pages/detail/index': ['/detail'], // 可以通过数组为页面配置多个自定义路由
|
||||
// "pages/my/index": "/my",
|
||||
// "pages/meetup/index": "/meetup",
|
||||
// "pages/meetup/Detail/index": "/detail",
|
||||
},
|
||||
},
|
||||
// defineConstants: {
|
||||
// HOST: '"/api"'
|
||||
// },
|
||||
devServer: {
|
||||
// host: 'nomad.hackrobot.cn', // 替换为您的域名
|
||||
// port: 10087, // 选择一个适当的端口号
|
||||
proxy: {
|
||||
"/api/": {
|
||||
target: JSON.parse('"http://localhost:8700"'), //本地调试端口
|
||||
pathRewrite: {
|
||||
"^/api/": "/",
|
||||
},
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
// 允许特定域名访问
|
||||
allowedHosts: [
|
||||
"nomadro.com",
|
||||
"localhost",
|
||||
".nomadro.com",
|
||||
"hackrobot.cn",
|
||||
], // 允许所有 *.nomadyt.com 子域访问
|
||||
},
|
||||
htmlPluginOption: {
|
||||
favicon: path.resolve(__dirname, "..", "src", "images", "dgnomad.png"), // 同样处理 favicon 路径
|
||||
meta: {
|
||||
"cache-control": "no-cache, no-store, must-revalidate",
|
||||
pragma: "no-cache",
|
||||
expires: "0",
|
||||
},
|
||||
},
|
||||
|
||||
target: ["web", "browserslist:> 0.25%, not dead"],
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = function (merge) {
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
return merge({}, config, require("./dev"));
|
||||
}
|
||||
return merge({}, config, require("./prod"));
|
||||
};
|
||||
2
config/prod.js
Normal file → Executable file
@@ -3,7 +3,7 @@ module.exports = {
|
||||
NODE_ENV: '"production"'
|
||||
},
|
||||
defineConstants: {
|
||||
'process.env.TARO_API_API': JSON.stringify("https://api.nomadcna.com")
|
||||
'process.env.TARO_API_API': JSON.stringify("https://api.hackrobot.cn")
|
||||
},
|
||||
mini: {},
|
||||
h5: {
|
||||
|
||||
3
package.json
Normal file → Executable file
@@ -63,13 +63,16 @@
|
||||
"@types/react": "^18.0.0",
|
||||
"@types/webpack-env": "^1.13.6",
|
||||
"babel-preset-taro": "3.6.20",
|
||||
"compression-webpack-plugin": "^11.1.0",
|
||||
"eslint": "^8.12.0",
|
||||
"eslint-config-taro": "3.6.20",
|
||||
"eslint-plugin-import": "^2.12.0",
|
||||
"eslint-plugin-react": "^7.8.2",
|
||||
"eslint-plugin-react-hooks": "^1.6.1",
|
||||
"glob": "^11.0.3",
|
||||
"javascript-obfuscator": "^4.1.1",
|
||||
"postcss": "^8.4.18",
|
||||
"purgecss-webpack-plugin": "^7.0.2",
|
||||
"react-refresh": "^0.11.0",
|
||||
"stylelint": "9.3.0",
|
||||
"ts-node": "^10.9.1",
|
||||
|
||||
0
project.config.json
Normal file → Executable file
0
roboth5.sh
Normal file → Executable file
0
src/app.config.js
Normal file → Executable file
0
src/app.js
Normal file → Executable file
0
src/app.scss
Normal file → Executable file
0
src/images/AI.png
Normal file → Executable file
|
Before Width: | Height: | Size: 320 KiB After Width: | Height: | Size: 320 KiB |
0
src/images/AIGC.png
Normal file → Executable file
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.2 KiB |
0
src/images/avatar.jpg
Normal file → Executable file
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
0
src/images/book.png
Normal file → Executable file
|
Before Width: | Height: | Size: 623 KiB After Width: | Height: | Size: 623 KiB |
0
src/images/book.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
0
src/images/bookqr.jpg
Normal file → Executable file
|
Before Width: | Height: | Size: 326 KiB After Width: | Height: | Size: 326 KiB |
0
src/images/cat.jpg
Normal file → Executable file
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
0
src/images/code.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.3 KiB |
0
src/images/demo.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 140 KiB |
0
src/images/dgnomad.png
Normal file → Executable file
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
0
src/images/emoji.png
Normal file → Executable file
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
0
src/images/girl.jpg
Normal file → Executable file
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
0
src/images/group.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
BIN
src/images/hackrobot.jpg
Executable file
|
After Width: | Height: | Size: 8.9 KiB |
0
src/images/host.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
0
src/images/joinGroup.png
Normal file → Executable file
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
0
src/images/laptop.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.6 KiB |
0
src/images/loft.jpg
Normal file → Executable file
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
0
src/images/loft.png
Normal file → Executable file
|
Before Width: | Height: | Size: 966 KiB After Width: | Height: | Size: 966 KiB |
0
src/images/meetup.jpg
Normal file → Executable file
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
0
src/images/nomad.png
Normal file → Executable file
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.2 KiB |
0
src/images/nomadro.png
Normal file → Executable file
|
Before Width: | Height: | Size: 229 KiB After Width: | Height: | Size: 229 KiB |
0
src/images/nomadvloglife.png
Normal file → Executable file
|
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 120 KiB |
0
src/images/phone.png
Normal file → Executable file
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
BIN
src/images/phone2.png
Executable file
|
After Width: | Height: | Size: 2.7 KiB |
0
src/images/qrcode.png
Normal file → Executable file
|
Before Width: | Height: | Size: 131 KiB After Width: | Height: | Size: 131 KiB |
0
src/images/robot.png
Normal file → Executable file
|
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 8.1 KiB |
0
src/images/robot.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
0
src/images/robot192.png
Normal file → Executable file
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
0
src/images/ssh.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 625 B After Width: | Height: | Size: 625 B |
BIN
src/images/tiktok.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
0
src/images/touxiang.png
Normal file → Executable file
|
Before Width: | Height: | Size: 154 KiB After Width: | Height: | Size: 154 KiB |
0
src/images/travel.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
BIN
src/images/web.png
Normal file
|
After Width: | Height: | Size: 108 KiB |
0
src/images/youtube copy.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 613 B After Width: | Height: | Size: 613 B |
0
src/images/youtube.ico
Normal file → Executable file
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
BIN
src/images/youtube.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
0
src/images/youtube.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 613 B After Width: | Height: | Size: 613 B |
0
src/images/yt2.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 8.2 KiB |
0
src/images/yt3.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
0
src/images/yt4.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 7.7 KiB |
0
src/images/ytGpt.png
Normal file → Executable file
|
Before Width: | Height: | Size: 230 KiB After Width: | Height: | Size: 230 KiB |
47
src/index.html
Normal file → Executable file
@@ -1,34 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
||||
<meta content="width=device-width,initial-scale=1,user-scalable=no" name="viewport">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-touch-fullscreen" content="yes">
|
||||
<meta name="format-detection" content="telephone=no,address=no">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="white">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" >
|
||||
<link rel="apple-touch-icon" href="/static/images/images/dgnomad.png" />
|
||||
<!-- <link rel="apple-touch-icon" href="/static/images/images/youtube.svg" /> -->
|
||||
|
||||
<link
|
||||
rel="icon"
|
||||
sizes="192x192"
|
||||
href="/static/images/images/dgnomad.png"
|
||||
|
||||
|
||||
/>
|
||||
<title>数字游民</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
|
||||
<title>数字游民:地理套利与自动化杠杆</title>
|
||||
<meta name="description" content="数字游民">
|
||||
<meta property="og:description" content="数字游民">
|
||||
<meta name="twitter:description" content="数字游民">
|
||||
|
||||
<!-- Icons -->
|
||||
<link rel="apple-touch-icon" href="https://minioweb.hackrobot.cn/hackrobot/phone2.png" />
|
||||
<link rel="icon" sizes="192x192" href="https://minioweb.hackrobot.cn/hackrobot/phone2.png" />
|
||||
|
||||
<!-- ✅ 提前建立连接 -->
|
||||
<link rel="preconnect" href="https://minioweb.hackrobot.cn" crossorigin>
|
||||
<link rel="dns-prefetch" href="https://minioweb.hackrobot.cn">
|
||||
|
||||
<!-- ✅ 预加载 LCP 图片 (WebP 优先,PNG 回退) -->
|
||||
<link rel="preload" as="image" href="https://minioweb.hackrobot.cn/hackrobot/phone2.png" type="image/png" fetchpriority="high">
|
||||
|
||||
<!-- JS 注入 -->
|
||||
<script src="https://unpkg.com/pocketbase/dist/pocketbase.umd.js"></script>
|
||||
<script><%= htmlWebpackPlugin.options.script %></script>
|
||||
<!-- 禁止缓存 -->
|
||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
||||
<meta http-equiv="Pragma" content="no-cache">
|
||||
<meta http-equiv="Expires" content="0">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
1344
src/pages/book/book.md
Normal file → Executable file
0
src/pages/book/index.config.js
Normal file → Executable file
29
src/pages/book/index.scss
Normal file → Executable file
@@ -69,10 +69,16 @@
|
||||
// color: #72d572;
|
||||
}
|
||||
|
||||
// img {
|
||||
// max-width: 100%;
|
||||
// height: auto;
|
||||
// /* 保持图像的纵横比 */
|
||||
// }
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
/* 保持图像的纵横比 */
|
||||
/* 如果需要让 img 当 flex item 居中,不需要额外样式 */
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.at-drawer__content {
|
||||
@@ -101,7 +107,6 @@
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
// background-color: yellow;
|
||||
.book_left {
|
||||
width: 400px;
|
||||
@@ -110,9 +115,12 @@
|
||||
}
|
||||
|
||||
.markdown-body {
|
||||
width: 100%;
|
||||
max-width: 1400px;
|
||||
font-size: 22px;
|
||||
// width: 100%;
|
||||
width: 700px;
|
||||
// max-width: 1400px;
|
||||
// font-size: 22px;
|
||||
font-size: 18px;
|
||||
|
||||
// color: red;
|
||||
// background-color: #72d572;
|
||||
}
|
||||
@@ -126,8 +134,15 @@
|
||||
.joinSalon {
|
||||
// width: 100%;
|
||||
// height: 100px;
|
||||
|
||||
display: none;
|
||||
|
||||
// background-color: yellow;
|
||||
}
|
||||
}
|
||||
.Book img {
|
||||
/* 如果需要让 img 当 flex item 居中,不需要额外样式 */
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
473
src/pages/book/index.tsx
Normal file → Executable file
@@ -1,6 +1,7 @@
|
||||
// @ts-nocheck
|
||||
import { Component } from "react";
|
||||
import { View, Text, 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";
|
||||
@@ -8,7 +9,6 @@ import { AtGrid } from "taro-ui";
|
||||
import { Swiper, SwiperItem } from "@tarojs/components";
|
||||
import { AtCard } from "taro-ui";
|
||||
import { AtImagePicker } from "taro-ui";
|
||||
import Taro from "@tarojs/taro";
|
||||
import { AtAvatar } from "taro-ui";
|
||||
import { AtTag } from "taro-ui";
|
||||
import { AtList, AtListItem } from "taro-ui";
|
||||
@@ -16,7 +16,46 @@ import { AtForm, AtMessage, AtDrawer, AtIcon } from "taro-ui";
|
||||
import { marked } from "marked";
|
||||
import infoText from "./info.md";
|
||||
import bookText from "./book.md";
|
||||
import { getPayEnv, getDeviceFromEnv } from "../../utils/payEnv";
|
||||
// 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);
|
||||
|
||||
/** PC 扫码支付:生成带二维码和轮询的 HTML 页面 */
|
||||
function buildQrcodePageHtml(imgSrc, orderId, returnUrl, apiBase) {
|
||||
const esc = (s) =>
|
||||
String(s)
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """);
|
||||
const statusUrl = `${apiBase}/nomadvip/order_status?order_id=${encodeURIComponent(orderId)}`;
|
||||
return `<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>微信扫码支付</title>
|
||||
<style>*{box-sizing:border-box;}body{font-family:system-ui;display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;margin:0;background:linear-gradient(135deg,#667eea 0%,#764ba2 100%);}
|
||||
.card{background:#fff;border-radius:20px;padding:40px;box-shadow:0 25px 50px -12px rgba(0,0,0,.25);max-width:420px;}
|
||||
h2{color:#1e293b;margin:0 0 8px;font-size:1.5rem;}.hint{color:#64748b;font-size:14px;margin-bottom:20px;}
|
||||
.qr-wrap img{width:200px;height:200px;border-radius:12px;border:2px solid #e2e8f0;background:#fff;}
|
||||
.tip{font-size:13px;color:#64748b;margin-top:16px;}.success-wrap{display:none;}.success-wrap.show{display:block;}
|
||||
.wait-wrap.hide{display:none;}.success-icon{font-size:4rem;margin-bottom:16px;}
|
||||
.success-title{font-size:1.25rem;color:#16a34a;font-weight:600;margin-bottom:8px;}
|
||||
.success-desc{color:#64748b;font-size:14px;margin-bottom:24px;}
|
||||
</style></head><body><div class="card">
|
||||
<div class="wait-wrap" id="waitWrap"><h2>微信扫码支付</h2><p class="hint">请使用微信扫描下方二维码完成支付</p>
|
||||
<div class="qr-wrap"><img src="${esc(imgSrc)}" alt="支付二维码"/></div><p class="tip" id="tip">等待支付中...</p></div>
|
||||
<div class="success-wrap" id="successWrap"><div class="success-icon">✅</div><div class="success-title">支付成功</div>
|
||||
<div class="success-desc">感谢您的支持,正在返回...</div></div></div>
|
||||
<script>
|
||||
(function(){var orderId="${esc(orderId)}";var returnUrl="${esc(returnUrl)}";var statusUrl="${esc(statusUrl)}";
|
||||
var waitWrap=document.getElementById("waitWrap");var successWrap=document.getElementById("successWrap");
|
||||
function showSuccess(){waitWrap.classList.add("hide");successWrap.classList.add("show");
|
||||
setTimeout(function(){window.location.href=returnUrl;},1500);}
|
||||
function check(){fetch(statusUrl).then(function(r){return r.json();}).then(function(d){
|
||||
if(d&&d.paid){showSuccess();return;}setTimeout(check,1000);}).catch(function(){setTimeout(check,1000);});}
|
||||
setTimeout(check,1000);})();
|
||||
</script></body></html>`;
|
||||
}
|
||||
|
||||
marked.setOptions({
|
||||
breaks: true, // 是否回车换行
|
||||
@@ -48,9 +87,149 @@ const Book = () => {
|
||||
const [headers, setHeaders] = useState([]); // 存储所有 <h1> 的内容
|
||||
const contentRef = useRef(null); // 用于获取渲染后的 DOM
|
||||
|
||||
const [paidType, setPaidType] = useState(null); // 新增:支付类型 'vip' | 'book' | null
|
||||
|
||||
// 把这些变量名全部改为 userId
|
||||
const [userId, setUserId] = useState(Taro.getStorageSync("userId") || null);
|
||||
const [loading, setLoading] = useState(true); // 新增:加载状态
|
||||
const router = useRouter(); // Taro 3+ 获取路由参数
|
||||
|
||||
const [vipbook, setVipbook] = useState(false); //是否在vip群打开
|
||||
|
||||
|
||||
// 在组件挂载时检测 referrer
|
||||
useEffect(() => {
|
||||
// window.localStorage.removeItem("book");
|
||||
if (router && router.params && router.params.type === 'vipbook') {
|
||||
setVipbook(true)
|
||||
} else {
|
||||
setVipbook(false)
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 保存 userId 到本地
|
||||
const saveUserId = (id) => {
|
||||
Taro.setStorageSync("userId", id);
|
||||
setUserId(id);
|
||||
};
|
||||
|
||||
// 检查本地是否已有支付类型
|
||||
const getLocalPaidType = () => {
|
||||
return Taro.getStorageSync("paidType") || null;
|
||||
};
|
||||
|
||||
// 保存支付类型到本地
|
||||
const savePaidType = (type) => {
|
||||
Taro.setStorageSync("paidType", type);
|
||||
setPaidType(type);
|
||||
};
|
||||
|
||||
// 查询 PocketBase 是否支付过
|
||||
const checkPaymentFromPB = async (userId) => {
|
||||
// window.alert('查询pocketbase')
|
||||
try {
|
||||
// 查询 payments 表中该 user_id 的记录,按创建时间倒序(最新的一笔)
|
||||
const records = await pb.collection("payments").getList(1, 1, {
|
||||
filter: `user_id = "${userId}"`,
|
||||
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;
|
||||
}
|
||||
} 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);
|
||||
} catch (error) {
|
||||
console.error("清空存储失败:", error);
|
||||
Taro.showToast({
|
||||
title: "清空存储失败",
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// 判断是否在微信
|
||||
isWeChatFun();
|
||||
// 方法1:Taro 3+ 推荐方式(支持小程序 + H5)
|
||||
if (router && router.params && router.params.type === "clean") {
|
||||
clearAllStorage();
|
||||
return;
|
||||
}
|
||||
|
||||
const init = async () => {
|
||||
const currentUserId = getOrCreateUserId(); // 确保 userId 存在
|
||||
|
||||
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(() => {
|
||||
@@ -98,49 +277,197 @@ const Book = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const payh5 = (type, total_fee = 1000) => {
|
||||
// console.log("111");
|
||||
/** 微信内:xorpay 收银台 */
|
||||
const payWechatXorpay = (type, total_fee) => {
|
||||
const userId = getOrCreateUserId();
|
||||
const returnUrl = window.location.href;
|
||||
Taro.request({
|
||||
method: "POST",
|
||||
url: `${process.env.TARO_API_API}/payh5`, //仅为示例,并非真实的接口地址
|
||||
// url: "https://pay.hackrobot.cn${process.env.TARO_API_API}/payh5", //仅为示例,并非真实的接口地址
|
||||
|
||||
url: `${process.env.TARO_API_API}/nomadvip/payh5`,
|
||||
data: {
|
||||
callback_url: window.location.href,
|
||||
openid: window.localStorage.getItem("openid"),
|
||||
total_fee: total_fee, // 金额,单位:分
|
||||
type: type, //订单类型 book/meetup/video/vip
|
||||
user_id: userId,
|
||||
total_fee,
|
||||
type,
|
||||
channel: "wxpay",
|
||||
return_url: returnUrl,
|
||||
provider: "xorpay",
|
||||
},
|
||||
header: {
|
||||
"content-type": "application/json", // 默认值
|
||||
},
|
||||
success: function (res) {
|
||||
console.log(res.data);
|
||||
// window.alert(res.data.jsapi);
|
||||
WeixinJSBridge.invoke(
|
||||
"getBrandWCPayRequest",
|
||||
{
|
||||
// 以下6个支付参数通过payjs的jsapi接口获取
|
||||
// appId: "wxc5205a653b0259bf",
|
||||
// timeStamp: "1701401644",
|
||||
// nonceStr: "KhOYB0wFV6j9qyQK",
|
||||
// package: "prepay_id=wx01113404850024729b35f4d69b73500000",
|
||||
// signType: "MD5",
|
||||
// paySign: "2A823C8D47CCF871C6C65A56DC228CF8",
|
||||
...(res?.data?.jsapi ?? {}),
|
||||
},
|
||||
function (res) {
|
||||
// 支付成功
|
||||
if (res.err_msg == "get_brand_wcpay_request:ok") {
|
||||
window.localStorage.setItem(type, 1); //订单类型 book/meetup/video/vip
|
||||
WeixinJSBridge.call("closeWindow");
|
||||
}
|
||||
header: { "content-type": "application/json" },
|
||||
success: (res) => {
|
||||
if (res.data?.status === "ok" && res.data?.xorpay_params) {
|
||||
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" });
|
||||
}
|
||||
},
|
||||
fail: () => Taro.showToast({ title: "网络错误", icon: "none" }),
|
||||
});
|
||||
};
|
||||
|
||||
/** PC:zpay 扫码,新窗口展示二维码 */
|
||||
const payPcZpay = (type, total_fee) => {
|
||||
const userId = getOrCreateUserId();
|
||||
const returnUrl = window.location.href;
|
||||
Taro.request({
|
||||
method: "POST",
|
||||
url: `${process.env.TARO_API_API}/nomadvip/payh5/redirect`,
|
||||
data: {
|
||||
user_id: userId,
|
||||
total_fee,
|
||||
type,
|
||||
channel: "wxpay",
|
||||
return_url: returnUrl,
|
||||
device: "pc",
|
||||
provider: "zpay",
|
||||
},
|
||||
header: { "content-type": "application/json" },
|
||||
success: (res) => {
|
||||
const d = res.data;
|
||||
if (d?.status === "ok" && d?.qrcode_mode && d?.img) {
|
||||
const apiBase = (process.env.TARO_API_API || "").replace(/\/$/, "");
|
||||
const html = buildQrcodePageHtml(d.img, d.order_id, d.return_url || returnUrl, apiBase);
|
||||
const win = window.open("", "_blank");
|
||||
if (win) {
|
||||
win.document.write(html);
|
||||
win.document.close();
|
||||
} else {
|
||||
Taro.showToast({ title: "请允许弹窗以显示支付二维码", icon: "none" });
|
||||
}
|
||||
} else {
|
||||
Taro.showToast({ title: "支付发起失败", icon: "none" });
|
||||
}
|
||||
},
|
||||
fail: () => Taro.showToast({ title: "网络错误", icon: "none" }),
|
||||
});
|
||||
};
|
||||
|
||||
/** 手机浏览器:zpay 唤醒微信,form 提交到 redirect 接口触发 302 */
|
||||
const payH5Zpay = (type, total_fee) => {
|
||||
const userId = getOrCreateUserId();
|
||||
const returnUrl = window.location.href;
|
||||
const form = document.createElement("form");
|
||||
form.method = "POST";
|
||||
form.action = `${(process.env.TARO_API_API || "").replace(/\/$/, "")}/nomadvip/payh5/redirect`;
|
||||
form.style.display = "none";
|
||||
const fields = [
|
||||
["user_id", userId],
|
||||
["total_fee", String(total_fee)],
|
||||
["type", type],
|
||||
["channel", "wxpay"],
|
||||
["return_url", returnUrl],
|
||||
["device", "h5"],
|
||||
["provider", "zpay"],
|
||||
];
|
||||
fields.forEach(([k, v]) => {
|
||||
const input = document.createElement("input");
|
||||
input.type = "hidden";
|
||||
input.name = k;
|
||||
input.value = v;
|
||||
form.appendChild(input);
|
||||
});
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
};
|
||||
|
||||
// 生成或获取 userId(修改为无下划线)
|
||||
const getOrCreateUserId = () => {
|
||||
let userId = Taro.getStorageSync("userId");
|
||||
if (!userId) {
|
||||
// 修改这里:去掉下划线
|
||||
userId = `user${Date.now()}`; // 示例:user1766043770158
|
||||
Taro.setStorageSync("userId", userId);
|
||||
console.log("新生成 userId:", userId);
|
||||
}
|
||||
return userId;
|
||||
};
|
||||
|
||||
/** 统一支付入口:PC扫码(zpay) / 手机浏览器唤醒(zpay) / 微信内(xorpay) */
|
||||
const handlePay = (type, total_fee) => {
|
||||
const env = getPayEnv();
|
||||
const device = getDeviceFromEnv(env);
|
||||
if (device === "wechat") {
|
||||
payWechatXorpay(type, total_fee);
|
||||
} else if (device === "pc") {
|
||||
payPcZpay(type, total_fee);
|
||||
} else {
|
||||
payH5Zpay(type, total_fee);
|
||||
}
|
||||
};
|
||||
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const _legacyPayh5 = (type, total_fee) => {
|
||||
const userId = 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",
|
||||
},
|
||||
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);
|
||||
} else {
|
||||
Taro.showToast({
|
||||
title: "支付发起失败",
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
Taro.showToast({
|
||||
title: "网络错误",
|
||||
icon: "none",
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<View className="book_header_div"></View>
|
||||
@@ -158,7 +485,7 @@ const Book = () => {
|
||||
>
|
||||
<AtIcon value="chevron-left"></AtIcon>
|
||||
</View>
|
||||
<View className="book_header_middle">{`📗 数字游民`}</View>
|
||||
<View className="book_header_middle">{`云手机`}</View>
|
||||
<View
|
||||
className="book_header_right"
|
||||
onClick={() => {
|
||||
@@ -174,7 +501,9 @@ const Book = () => {
|
||||
className="markdown-body" // 添加 GitHub Markdown 主题类
|
||||
dangerouslySetInnerHTML={{
|
||||
// __html: marked(markText),
|
||||
__html: marked(book == 1 ? bookText : infoText),
|
||||
// __html: marked(book == 1 ? bookText : infoText),
|
||||
__html: marked(paidType=='book'|| vipbook ? bookText : infoText),
|
||||
|
||||
}}
|
||||
></View>
|
||||
|
||||
@@ -197,48 +526,50 @@ const Book = () => {
|
||||
</View> */}
|
||||
</AtDrawer>
|
||||
|
||||
{/* 判断是否是微信 */}
|
||||
{!isWeChat && (
|
||||
// 公众号二维码
|
||||
<div
|
||||
style={{
|
||||
color: "red",
|
||||
display: "flex", // 关键:flex 布局
|
||||
justifyContent: "center", // 水平居中
|
||||
alignItems: "center", // 垂直居中(可选,如果你想整块垂直也居中)
|
||||
flexDirection: "column", // 可选:让内容垂直排列(如果下面还有文字)
|
||||
margin: "20px 0", // 可选:上下留点间距
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
className="hackrobot"
|
||||
src={hackrobot}
|
||||
style={{
|
||||
width: "300px",
|
||||
height: "300px",
|
||||
maxWidth: "none",
|
||||
maxHeight: "none",
|
||||
}}
|
||||
// src={`https://minioweb.hackrobot.cn/hackrobot/propless.svg`}
|
||||
lazyLoad
|
||||
></Image>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 判断有没有wxid */}
|
||||
{book !=1 ? (
|
||||
{/* {book != 1 ? ( */}
|
||||
{paidType != "book" ? (
|
||||
<AtButton
|
||||
type="primary"
|
||||
size="normal"
|
||||
className="joinSalon"
|
||||
onClick={() => {
|
||||
if (book == 1) {
|
||||
return false;
|
||||
} else {
|
||||
if (!!isWeChat) {
|
||||
// 19.8元
|
||||
payh5("book", 1980);
|
||||
} else {
|
||||
window.location.href = `https://mp.weixin.qq.com/s/GBU3THKaXRRkdpZXH_18jw`;
|
||||
}
|
||||
}
|
||||
if (paidType == "book" || vipbook) return;
|
||||
handlePay("book", 19900);
|
||||
}}
|
||||
>
|
||||
{`阅读更多`}
|
||||
{`🔒阅读更多`}
|
||||
</AtButton>
|
||||
) : <span style={{color:'green'}}>持续更新中....</span>}
|
||||
|
||||
{/* <AtButton
|
||||
type="primary"
|
||||
size="normal"
|
||||
className="joinSalon"
|
||||
onClick={() => {
|
||||
if (book == 1) {
|
||||
return false;
|
||||
} else {
|
||||
if (!!isWeChat) {
|
||||
// 198元
|
||||
payh5("group", 29800);
|
||||
} else {
|
||||
window.location.href = `https://mp.weixin.qq.com/s/GBU3THKaXRRkdpZXH_18jw`;
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
{`游民社群`}
|
||||
</AtButton> */}
|
||||
) : // <span style={{ color: "green" }}>持续更新中....</span>
|
||||
null}
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
|
||||
1215
src/pages/book/info copy.md
Executable file
637
src/pages/book/info.md
Normal file → Executable file
@@ -1,609 +1,108 @@
|
||||
# 前言
|
||||
|
||||
## 🚀 数字游民
|
||||
# 低成本工作室: 云手机
|
||||
|
||||
<img src="https://hackrobot-1258475618.cos.ap-shenzhen-fsi.myqcloud.com/fqwfqwff.jpg?imageSlim" title="" alt="fqwfqwff.jpg" width="333">
|
||||
> 电子书+视频教程
|
||||
|
||||
对于普通人而言,最好的杠杆依然是`媒体`和`代码`
|
||||
## Tiktok无人直播
|
||||
|
||||
如今有了AIGC的加持
|
||||

|
||||
|
||||
以前创作的门槛,已经不再是`门槛`
|
||||
<!--  -->
|
||||
|
||||
数字游民,翻译自`digital nomad`,指的是依靠依靠互联网获取收入,并且像游牧民族一样旅居各个地方的群体
|
||||
<!-- > tiktokz直播
|
||||
|
||||
## 目录
|
||||
|
||||
- 被动收入:YouTube运营
|
||||
|
||||
- 副业:wordpress建站
|
||||
<video style="max-width:100%;height:auto;" controls>
|
||||
|
||||
- 独立开发:sass业务
|
||||
<source src="https://hackrobot-1258475618.cos.ap-shenzhen-fsi.myqcloud.com/9.%20tiktok%E7%9B%B4%E6%92%AD-2.mp4" type="video/mp4">
|
||||
|
||||
- 一人公司/分布式团队
|
||||
您的浏览器不支持 video 标签。
|
||||
|
||||
- 展望与结束语
|
||||
</video> -->
|
||||
|
||||
本书不是`教人赚钱`的指南,但作为参考,应该可以给你一些启发
|
||||
### 目录
|
||||
|
||||
持续更新中,未完待续
|
||||
> 📱系统安装
|
||||
|
||||
- 开源硬件选择 (百元级),
|
||||
|
||||
- 刷入安卓系统(Android)
|
||||
|
||||
- 获取root权限(magisk面具)
|
||||
|
||||
- lsposed环境 (xposed外挂模块)
|
||||
|
||||
- 谷歌框架
|
||||
|
||||
> 🚫风控设置
|
||||
|
||||
- 机型参数修改
|
||||
|
||||
- 静态住宅ip (链式代理)
|
||||
|
||||
- gps定位 (fake location)
|
||||
|
||||
- 权限控制
|
||||
|
||||
- 安卓虚拟机 (多开系统)
|
||||
|
||||
> 💻远程控制
|
||||
|
||||
- frp内网穿透
|
||||
|
||||
- scrcpy (adb远控)
|
||||
|
||||
- qscrcpy(群控)
|
||||
|
||||
> 💰应用场景
|
||||
|
||||
- Tikrok无人直播
|
||||
|
||||
---
|
||||
|
||||
# 远程工作
|
||||
## Youtube无人直播
|
||||
|
||||
大多数人成为数字游民,还是依赖一份旱涝保收的`远程工作`
|
||||
<img title="" src="https://minioweb.hackrobot.cn/hackrobot/qqwfqwf_20260107_221850_7499.jpg" alt="qqwfqwf_20260107_221850_7499.jpg" width="368">
|
||||
|
||||
然后经营自己的副业,直到成为主页
|
||||
### 目录
|
||||
|
||||

|
||||
> 📱系统安装
|
||||
|
||||
以下是海外常用的远程工作平台
|
||||
- 开源硬件选择 (百元级),
|
||||
|
||||
- [fiverr](https://go.fiverr.com/visit/?bta=1111508&brand=fiverrmarketplace)
|
||||
- 刷入linux系统(server服务器)
|
||||
|
||||
- [upwork](https://www.upwork.com/)
|
||||
> 💻软件配置
|
||||
|
||||
- [remoteok](https://remoteok.com/)
|
||||
- 解决科学姿势上网 (clash安装)
|
||||
|
||||
英语和重要,必要的沟通,还是要行
|
||||
- 直播间实时录屏/转发
|
||||
|
||||
## 谨防欺诈
|
||||
- srs直播服务器
|
||||
|
||||

|
||||
- 安卓虚拟机 (Redroid容器)
|
||||
|
||||
很多工作机会,并不是真实的,平台也不一定能监管到位
|
||||
- 浏览器.设置油猴脚本外挂
|
||||
|
||||
从人力性价比来说,印度的更便宜,有原生的`语言和网络优势`,以及`汇率`
|
||||
> 💰Youtube无人直播
|
||||
|
||||
而菲律宾也是`呼叫中心`的核心外包国家
|
||||
- ypp频道收益
|
||||
|
||||
作为一名中国人,在很多业务上,并没有优势,很多活跃都是外贸采购人员
|
||||
|
||||
但平台可以帮助我们确认:才是才是需求高的`业务方向`
|
||||
|
||||
## 国内平台
|
||||
|
||||
<img src="https://hackrobot-1258475618.cos.ap-shenzhen-fsi.myqcloud.com/wegwgweg.jpg?imageSlim" title="" alt="wegwgweg.jpg" width="383">
|
||||
|
||||
国内有很多XX社区,在做致力于数字游民的事业
|
||||
|
||||
但由于本土众所周知的原因
|
||||
|
||||
大多都是套了一层壳,本质还是`劳务中介`和`二房东`的生意
|
||||
|
||||
不是创始人不够努力,也不是技术和运营的问题,而是土壤天生不允许发芽
|
||||
|
||||
如果寻求远程,可以在某直聘`搜索职位时,加上远程`
|
||||
|
||||
有些`开发,客户,运营`是存在远程岗位的
|
||||
|
||||
话说,这是一个向企业和用户双方都收费的平台🤣...可以试试
|
||||
- 非ypp频道 (积累播放时长)
|
||||
|
||||
---
|
||||
|
||||
# 被动收入:YouTube运营
|
||||
## 低成本工作室 : homelab
|
||||
|
||||
你可以在 [nomadyt.com](https://nomadyt.com) 阅读,关于youtube运营的电子书
|
||||

|
||||
|
||||
<img src="https://hackrobot-1258475618.cos.ap-shenzhen-fsi.myqcloud.com/2025-03-30-01-40-00-Youtube%E8%BF%90%E8%90%A5%E7%AC%94%E8%AE%B0.png?imageSlim" title="" alt="2025-03-30-01-40-00-Youtube运营笔记.png" width="189">
|
||||
> 📱工作室硬件
|
||||
|
||||
但是本书,会展示更多运营的细节
|
||||
- 开源硬件选择
|
||||
|
||||
比如无人直播,除了`ffmpeg推流`,以及`obs开源软件`之外
|
||||
- 智能家居:homeasstant
|
||||
|
||||
如何使用`安卓开发板+采集卡`的方案,模拟移动端直播,权重更高,平台推荐,且防封
|
||||
- P2P组网 (异地远程办公)
|
||||
|
||||
除此之外,例如使用`链式代理`,如何选择`静态住宅ip`,也是出海业务的基础
|
||||
|
||||
如果没有掌握,一切都是徒劳,会面临0播,限流,甚至封号
|
||||
|
||||
---
|
||||
|
||||
# wordpess建站
|
||||
|
||||
> 网站已死?网站永生
|
||||
|
||||
网站,在国内,似乎是一个很古老的词,因为它是互联网的下等公民
|
||||
|
||||
在移动互联网时代,被各种`小程序`或者`快应用`取代
|
||||
|
||||
最不济也是用`自研app`,圈地画牢,获取用户的数据
|
||||
|
||||
而web网站,或者H5,都是排除在生态之外,用户信任度也较低
|
||||
|
||||
> 但如果目光望向海外呢
|
||||
|
||||
各种当下火热的AI,都是`网页为先,支持pwa`
|
||||
|
||||
如果想要成为成为独立开发者,只需要接入stripe支付即可
|
||||
|
||||
各种独立站,电子商务,私域也让各种商家大赚特赚
|
||||
|
||||
> wordpress建站
|
||||
|
||||

|
||||
|
||||
wordpress是基于php开发的开源建站程序,拥有最丰富的`主题和插件`生态
|
||||
|
||||
即便是小白,也能分分钟上线一个网站,可以是:
|
||||
|
||||
- 个人博客
|
||||
|
||||
- 企业官网
|
||||
|
||||
- woocommerce独立站
|
||||
|
||||
- 网校/电子书/论坛
|
||||
|
||||
- 等等
|
||||
|
||||
一夜之间上线网站,全球访问,是一件很简单的事情
|
||||
|
||||
如果想要盈利,最简单的就是接入`谷歌的广告`,自动化嵌入
|
||||
|
||||
## 一夜之间上线网站
|
||||
|
||||
> 购买域名
|
||||
|
||||
可以在 [ namesilo](https://www.namesilo.com/?rid=3bd4047mc) 购买域名,也就是类似`google.com`的网址
|
||||
|
||||
因为是海外服务商,所以不需要备案
|
||||
|
||||
<img src="https://hackrobot-1258475618.cos.ap-shenzhen-fsi.myqcloud.com/2025-04-02-08-15-14-Low-Cost%20Domain%20Names%20%26%20Hosting%20from%20%240.99%20_%20NameSilo.png?imageSlim" title="" alt="2025-04-02-08-15-14-Low-Cost Domain Names & Hosting from $0.99 _ NameSilo.png" width="480">
|
||||
|
||||
> 购买vps服务器
|
||||
|
||||
域名虽然是英文字母, 但是用户访问后,实际是`google.com==>8.8.8.8`,
|
||||
|
||||
dns会把网址解析到 服务器的默认`公网ip`地址
|
||||
|
||||
因为是海外的厂商,所以也不要备案
|
||||
|
||||
在 [vultr](https://www.vultr.com/?ref=8985003) 购买一个vps服务器,后,建议立即用一键脚本部署`宝塔面板`
|
||||
|
||||
自带wordpress安装程序
|
||||
|
||||
<img title="" src="https://hackrobot-1258475618.cos.ap-shenzhen-fsi.myqcloud.com/2025-04-02-08-18-13-index.tsx%20-%20nomadro.com%20-%20Visual%20Studio%20Code.png?imageSlim" alt="2025-04-02-08-18-13-index.tsx - nomadro.com - Visual Studio Code.png" width="469">
|
||||
|
||||
## adsense赚美金
|
||||
|
||||
> wordpress插件: [google site kit](https://wordpress.org/plugins/google-site-kit/)
|
||||
|
||||
<img title="" src="https://hackrobot-1258475618.cos.ap-shenzhen-fsi.myqcloud.com/afqfqfqwf.jpg?imageSlim" alt="afqfqfqwf.jpg" width="466">
|
||||
|
||||
插件会自动帮你关联`google分析`,与`adsense账户`
|
||||
|
||||
当你点击申请后,官方会人工审核网站的`内容和流量`,一般2周,快的次日就有结果
|
||||
|
||||
## 拓展其他收益:affiliate
|
||||
|
||||
<img src="https://hackrobot-1258475618.cos.ap-shenzhen-fsi.myqcloud.com/2025-04-02-09-25-35-images.jpg.png?imageSlim" title="" alt="2025-04-02-09-25-35-images.jpg.png" width="457">
|
||||
|
||||
仅仅在wordpress自动放广告,如果不是站群,收益不会太多
|
||||
|
||||
ps: 站群:可以基于`k8s,docker`技术,自动批量建站
|
||||
|
||||
但大多数人,都是技术小白,远不到玩站群的层次,也没必要
|
||||
|
||||
在自己没核心业务:比如无职业资源`律师,医生,销售`也没有商品.产品等背景下
|
||||
|
||||
建议采用`affiliate佣金`模式,增加收入,别入
|
||||
|
||||
- amazon: 商品佣金 + kindle书籍
|
||||
|
||||
- vps服务器: [vultr](https://www.vultr.com/?ref=8985003) 和域名平台: [namesilo](https://www.namesilo.com/?rid=3bd4047mc)
|
||||
|
||||
- 金融:[ wise](https://wise.com/invite/amc/xiaoshuangh)钱包 和 [okx](https://okx.com/join/84999861)交易所
|
||||
|
||||
- 等等
|
||||
|
||||
affiliate marketing的好处是,推广的服务基本都是互联网平台
|
||||
|
||||
而且.平台/推广者/用户,三方收益
|
||||
|
||||
平台有新客户,新用户有优惠,推广者有提成
|
||||
|
||||
---
|
||||
|
||||
# 独立开发sass
|
||||
|
||||
> web优先,ios app其次
|
||||
|
||||
<img title="" src="https://hackrobot-1258475618.cos.ap-shenzhen-fsi.myqcloud.com/fqwfqwfqw.jpg?imageSlim" alt="fqwfqwfqw.jpg" width="323">
|
||||
|
||||
web只需要解决`登录`和`支付`2个核心问题,优势就太多了
|
||||
|
||||
- 几分钟上线
|
||||
|
||||
- 快速迭代更新
|
||||
|
||||
- 用户点击进入(短信/文章/聊天框)
|
||||
|
||||
大多数人,之所以选择iOS app, 是因为它的生态:
|
||||
|
||||
- `推送`,`全球分发`,`云数据`,`身份id认证``支付收款`
|
||||
|
||||
但`下载`的成本很高,在测试用户是否具有粘性之前
|
||||
|
||||
用web先行测试是一种策略
|
||||
|
||||
## 数字游民CNA
|
||||
|
||||
如果你了解过数字游民,那么一定会知晓`nomadlist`这个网站
|
||||
|
||||
模仿它的特色,我正在尝试开发类似的网站,针对中文用户
|
||||
|
||||
目前仍然很简陋,但会持续迭代,可访问: [nomadcna](https://nomadcna.com/)
|
||||
|
||||
<img src="https://hackrobot-1258475618.cos.ap-shenzhen-fsi.myqcloud.com/2025-04-02-08-38-51-%E6%95%B0%E5%AD%97%E6%B8%B8%E6%B0%91CNA.png?imageSlim" title="" alt="2025-04-02-08-38-51-数字游民CNA.png" width="391">
|
||||
|
||||
## 技术栈
|
||||
|
||||
在nomadlist的创始人的`MAKER`一书中,作者用着`最古老的jquery和php以及sqlite数据库`,却开发了平均 40k$/m纯利润各种网站,包括 `AI 工具站/电子商务`,其数据都在X上实时更新
|
||||
|
||||
其实只需要5$/月的 [vultr](https://www.vultr.com/?ref=8985003) 服务器,就足以支撑业务
|
||||
|
||||
<img src="https://hackrobot-1258475618.cos.ap-shenzhen-fsi.myqcloud.com/2025-04-02-08-46-57-%40levelsio%20(%40levelsio)%20_%20X.png?imageSlim" title="" alt="2025-04-02-08-46-57-@levelsio (@levelsio) _ X.png" width="427">
|
||||
|
||||
本人比较推荐基于js的react生态,它可以满足
|
||||
|
||||
- react开发web网站
|
||||
|
||||
- chrome插件
|
||||
|
||||
- electron开发pc客户端软件
|
||||
|
||||
- react native开发iOS app
|
||||
|
||||
当然,如果你想开发一个完整的应用,会涉及很多,体验/功能 比如`用户无法感知`的
|
||||
|
||||
- 埋点:分析/错误监控
|
||||
|
||||
- 多语言 (翻译)
|
||||
|
||||
- 加固混淆 (防止逆向渗透)
|
||||
|
||||
- pwa (将web进行app化)
|
||||
|
||||
- wasm,three.js,webVR (边缘计算能力与3D可视化)
|
||||
|
||||
思虑越多,越难以行动,完美主义很致命
|
||||
|
||||
即便是一个`hello world`,当它被各个世界的用户,可点击访问时
|
||||
|
||||
你就成功了一半,因为它以为,除了`自媒体作品`之外,你有了`创造产品`的能力
|
||||
|
||||
<img src="https://hackrobot-1258475618.cos.ap-shenzhen-fsi.myqcloud.com/fqwfwqfqwf.png?imageSlim" title="" alt="fqwfwqfqwf.png" width="390">
|
||||
|
||||
## 收益: admob广告与内购
|
||||
|
||||
<img src="https://hackrobot-1258475618.cos.ap-shenzhen-fsi.myqcloud.com/wqfqwfqwf.png?imageSlim" title="" alt="wqfqwfqwf.png" width="400">
|
||||
|
||||
以iOS app为例子,收益的方式有很多
|
||||
|
||||
- admob广告
|
||||
|
||||
- 购买下载 (一次性买断)
|
||||
|
||||
- 内购:订阅制 (月付)
|
||||
|
||||
如果不想向用户收费,可以直接接入admob广告,也是google 广告
|
||||
|
||||
进一步可以采取pro升级模式,用户付费后,免广告,且提供高级功能
|
||||
|
||||
在国内,用户付费意愿较低
|
||||
|
||||
如果用户基础大,也可以采取`开屏广告`和`广告位`形式获取获取企业厂商赞助
|
||||
|
||||
最理想的模式的是`订阅制`,基于`1000个粉丝`的理论,开发者就可以实现客观的收入了
|
||||
|
||||
但风险也是对等的
|
||||
|
||||
一旦成功,就会无数团队模仿,利用资金/资源/投放广告,来碾压开发者
|
||||
|
||||
如果建立护城河,是一个难题
|
||||
|
||||
---
|
||||
|
||||
# 地理套利
|
||||
|
||||
数字游民最本质的优势在于`地理套利`
|
||||
|
||||

|
||||
|
||||
更详细的优势如下:
|
||||
|
||||
- 地理优势:东亚气候温暖,风景唯美,适合旅居
|
||||
|
||||
- 生活成本:三四线城市消费水平低,物价便宜
|
||||
|
||||
- 汇率差:赚美元,换算成泰铢可以达到1:30,人民币1:7左右
|
||||
|
||||
- 当地特产/文化:比如泰国旅游业,印度仿制药,香港金融等等
|
||||
|
||||
- 时间差:淡季旅行性价比高,错峰出行,看见不一样的文化
|
||||
|
||||
即便是每月1000$/m的收益,也可以过得很舒服惬意,也拥有无限的可能性
|
||||
|
||||
## 大理与乌托邦
|
||||
|
||||
在中国境内,`大理`是所有裸辞打工人的第一站
|
||||
|
||||
逃离钢筋水泥,回归自然田园
|
||||
|
||||

|
||||
|
||||
房租编译,风景怡人,各个XX社区,遍地开花
|
||||
|
||||
很多东南亚的客户,也来这里旅行,通过 [ctrip](https://www.trip.com/nomadcna?Allianceid=6238114&SID=193455508&trip_sub1=&trip_sub3=D2700672) 订票,也就是某程英文版
|
||||
|
||||
其实`惠州`也值得一试,离广东的`深圳/广州`一线城市距离近
|
||||
|
||||

|
||||
|
||||
房价/物价便宜,气候温暖
|
||||
|
||||
乘坐高铁,到附近省份也方便
|
||||
|
||||
至于`鹤岗`,个人认为纯粹不值得,生活在南方的人,无法理解北方的酷寒
|
||||
|
||||
不知道鹅毛大雪几米深的恐怖,也不知道暖气停供一晚,如何撑得过零下几十度
|
||||
|
||||
连救护车开到了医院大门,即便轮胎上了防滑链,都有没法开进去的情况
|
||||
|
||||
需要男护士/男医生踩着雪抬进去
|
||||
|
||||
当选择一个环境的时候,有时候,气候,是保证一个人生命安全的关键
|
||||
|
||||
## 泰国:曼谷与清迈
|
||||
|
||||
据nomadlist统计报告,泰国`曼谷`是最受欢迎的数字游民聚集地
|
||||
|
||||
.png?imageSlim)
|
||||
|
||||
其次是`清迈`
|
||||
|
||||
而且该国对数字游民,也比较友好,出台了很多`针对性的政策`
|
||||
|
||||
但不得不提醒的是,泰国也被列为最危险的国家
|
||||
|
||||
- 不禁枪支
|
||||
|
||||
- 风月场所合法,比如芭提雅
|
||||
|
||||
- 周边国家,存在贩毒与电诈
|
||||
|
||||
- 存在人口贩卖和地下奴隶市场
|
||||
|
||||
当一个博主从`印度`或者`泰国`回来,用"很安全,我不就是好好的吗?"来解答你的疑惑时
|
||||
|
||||
这个人要么愚蠢,要么下贱
|
||||
|
||||
`幸存者偏差`的意思是,只有或者的人,才有机会开口说话
|
||||
|
||||
那么遭遇危险的人,已无法发生
|
||||
|
||||
---
|
||||
|
||||
# 多元杠杆
|
||||
|
||||
成为一个数字游民,就必须让自己的过程,产生多元的杠杆价值,放大收益
|
||||
|
||||

|
||||
|
||||
这里有个框架,3个关键词,可以套用在任何事物上,如下
|
||||
|
||||
- `直播`live
|
||||
|
||||
- `社群`互动
|
||||
|
||||
- `录制`存档
|
||||
|
||||
在框架的基础,我也会解释`副产品`的优势
|
||||
|
||||
现在我们举个栗子
|
||||
|
||||
假设你是一个开发者,`正在coding编程`,这个如何实现多元化呢?
|
||||
|
||||

|
||||
|
||||
链路如下:
|
||||
|
||||
1. 实时直播,手机=>拍着正在敲键盘的你
|
||||
|
||||
2. 交流互动,针对观众的提问(看副屏),进行回答,让业务有实时的反馈
|
||||
|
||||
3. OBS后台录屏 (副产品:技术视频教程)
|
||||
|
||||
4. 编码本身 (软件产品,ios app,观众可以成为种子用户)
|
||||
|
||||
5. Api互动: 根据打赏的礼物,控制`显示器/esp32`进行趣味玩法
|
||||
|
||||
6. 橱窗带货:比如桌面炫酷/萌萌的摆件
|
||||
|
||||
总结一下,实时编码,不仅让业务有`即时反馈`,也会产生很多`副产品`
|
||||
|
||||
增加了收益渠道
|
||||
|
||||
## RPA自动化
|
||||
|
||||

|
||||
|
||||
前面我们提到了直播,可以实现多元化杠杆,但,它是一个实时同步的过程
|
||||
|
||||
那么有没有24小时无人值守,自动化的方案呢?
|
||||
|
||||
有,比如
|
||||
|
||||
- 基于ffmmpeg/obs直播推流 (录播素材)
|
||||
|
||||
- 社媒mitm爬虫:数据分析/自动点赞评论
|
||||
|
||||
- 发卡平台: 虚拟资源,卡密网盘发货
|
||||
|
||||
- N8N: 平台与流程自动化,积木一样串联工作流
|
||||
|
||||
- 群机器人bot: telegram和微信群
|
||||
|
||||
- 等等
|
||||
|
||||
缺点是最技术的要求较高
|
||||
|
||||
---
|
||||
|
||||
# 分布式团队
|
||||
|
||||
成为一个数字游民,并不意味着一个人完成所有的项目过程
|
||||
|
||||
参与或者成立一个分布式团队,进行远程办公,也是必要的
|
||||
|
||||
<img src="https://hackrobot-1258475618.cos.ap-shenzhen-fsi.myqcloud.com/fqwfqfqwf.jpg?imageSlim" title="" alt="fqwfqfqwf.jpg" width="397">
|
||||
|
||||
可以雇佣兼职/外包,或者让用户产生内容 [UGC]
|
||||
|
||||
接下来我会推荐`开源`的远程工具
|
||||
|
||||
1. 任务看板:`planka`
|
||||
|
||||
2. `eliment `(基于martix斜体的聊天工具)
|
||||
|
||||
3. 文档wiki/知识库: `docmost`
|
||||
|
||||
使用开源的好处在于:`低成本,保护隐私,完全控制,可拓展`
|
||||
|
||||
> 任务看板planka
|
||||
|
||||
以任务卡片的形式,给成员安排任务
|
||||
|
||||
设置子任务/完成时间/上传附件/以及评论@
|
||||
|
||||
所有进度一目了然
|
||||
|
||||
<img src="https://hackrobot-1258475618.cos.ap-shenzhen-fsi.myqcloud.com/2025-04-02-11-11-29-Sample%20Project%20_%20Sample%20Board.png?imageSlim" title="" alt="2025-04-02-11-11-29-Sample Project _ Sample Board.png" width="463">
|
||||
|
||||
> eliment
|
||||
|
||||
eleliment只是基于martrix协议的一个客户端
|
||||
|
||||
目前支持PC/mac/Android/ios/linux全平台
|
||||
|
||||
可以聊天/视频语音通话
|
||||
|
||||
后台可以进行消息统计/分析,防止商业机密泄露
|
||||
|
||||
<img src="https://hackrobot-1258475618.cos.ap-shenzhen-fsi.myqcloud.com/2025-04-02-11-14-33-Element%20_%20Secure%20collaboration%20and%20messaging.png?imageSlim" title="" alt="2025-04-02-11-14-33-Element _ Secure collaboration and messaging.png" width="469">
|
||||
|
||||
> wiki文档: docmost
|
||||
|
||||
它内置编辑器支持 Mermaid、Draw.io 和 Excalidraw 图表工具
|
||||
|
||||
可以将团队的文档整理输出
|
||||
|
||||
方便成员学习,分享
|
||||
|
||||
<img src="https://hackrobot-1258475618.cos.ap-shenzhen-fsi.myqcloud.com/qwdqwdqwd.png?imageSlim" title="" alt="qwdqwdqwd.png" width="506">
|
||||
|
||||
> 如何搭建?
|
||||
|
||||
购买 [vultr](https://www.vultr.com/?ref=8985003) 服务器,按照官方提供的`docker镜像`,一键安装
|
||||
|
||||
---
|
||||
|
||||
# 一人公司
|
||||
|
||||
> 数字游民与一人公司区别很大
|
||||
|
||||

|
||||
|
||||
数字游民一般是以`远程工作`为主,本质还是偶雇佣关系,把工位换成了远程旅居
|
||||
|
||||
也进行一些`副业`
|
||||
|
||||
但一人公司,更偏向于`创业`
|
||||
|
||||
经营`自媒体/独立开发/电商`等收益上限比较高的互联网业务
|
||||
|
||||
## 基础设备
|
||||
|
||||
一人公司,也意味着硬件和软件资产的流程化,比如
|
||||
|
||||
- windows办公主机
|
||||
|
||||
- 安卓手机:产品测试/社媒运营
|
||||
|
||||
- linux服务器:应用部署
|
||||
|
||||
<img title="" src="https://hackrobot-1258475618.cos.ap-shenzhen-fsi.myqcloud.com/1000029229.jpg?imageSlim" alt="1000029229.jpg" width="339">
|
||||
|
||||
图示是作者用mini主机+开发板打造的远程操作系统
|
||||
|
||||
可以随时随地,`远控/群控`
|
||||
|
||||
利用[vultr](https://www.vultr.com/?ref=8985003) 服务器,设置`frp内网穿透`即可,或者`wireguard点对点VPN异地组网`
|
||||
|
||||
## 开源部署
|
||||
|
||||
<img src="https://hackrobot-1258475618.cos.ap-shenzhen-fsi.myqcloud.com/2025-04-02-11-46-55-Uptime%20Kuma%20Status.png?imageSlim" title="" alt="2025-04-02-11-46-55-Uptime Kuma Status.png" width="395">
|
||||
|
||||
经营业务,必须准备大量的基础服务来支撑自动化运营,比如
|
||||
|
||||
- erp系统: ERPNext
|
||||
|
||||
- scrm客服:chatwoot
|
||||
|
||||
- 邮件营销:邮件营销
|
||||
|
||||
- 项目开发: `禅道`(项目管理) /` gitlab`(代码仓库)/`YAPI`接口文档
|
||||
|
||||
- 运维CI/CD:jenkins自动部署/`rancher`容器管理
|
||||
|
||||
- 埋点监控: `Sentry`错误监控/`plausible`统计/`Uptime Kuma`服务状态
|
||||
|
||||
- 流程自动化`N8N`
|
||||
|
||||
- 等等
|
||||
|
||||
都是开源免费
|
||||
|
||||
- 缺点:需要自己部署,维护
|
||||
|
||||
- 优点:省下数以万计的费用
|
||||
|
||||
---
|
||||
|
||||
# 展望与结束语
|
||||
|
||||

|
||||
|
||||
在以上内容,并没有提及2个领域:`跨境电商`和`加密货币`
|
||||
|
||||
前者偏向于实体,成本高,链路复杂,市场因政策变化频繁
|
||||
|
||||
后者遇到投机主义,一不小心就容易倾家荡产
|
||||
|
||||
换个思路,不做卖家,可以做amazon affiliate, 买入闲置,用涨跌来跨越大周期,穿越牛熊
|
||||
|
||||
原本只打算写2个小册
|
||||
|
||||
- [youtube运营笔记](https://nomadyt.com/) (free)
|
||||
|
||||
- [从零开始,成为数字游民](https://nomadro.com/) (试读80% +pay)
|
||||
|
||||
后续会酌情增加
|
||||
|
||||
- [一人公司](https://nomadvps.com/) (预订)
|
||||
|
||||
最初的是打算写技术书籍的~~
|
||||
|
||||
<img src="https://hackrobot-1258475618.cos.ap-shenzhen-fsi.myqcloud.com/2025-04-03-02-14-22-AI%20%E4%B8%8E%20RPA%E8%87%AA%E5%8A%A8%E5%8C%96.png?imageSlim" title="" alt="2025-04-03-02-14-22-AI 与 RPA自动化.png" width="375">
|
||||
|
||||
> 不管是生意还是创业,第一原则,先找到`买家`
|
||||
|
||||
在某个领域里,你发现了需求,或者有人为你的创造买单,就有收益,然后放大
|
||||
|
||||
如果没有调研市场
|
||||
|
||||
就默默地闭门造车,结局会很惨
|
||||
|
||||
持续更新中.....
|
||||
- 待更新
|
||||
|
||||
0
src/pages/group/index.config.js
Normal file → Executable file
0
src/pages/group/index.scss
Normal file → Executable file
0
src/pages/group/index.tsx
Normal file → Executable file
0
src/pages/index/index.config.js
Normal file → Executable file
203
src/pages/index/index.jsx
Normal file → Executable file
@@ -1,5 +1,5 @@
|
||||
import { Component } from "react";
|
||||
import Taro from "@tarojs/taro";
|
||||
import Taro,{useRouter} from "@tarojs/taro";
|
||||
import { View, Text, Image, Button } from "@tarojs/components";
|
||||
import "./index.scss";
|
||||
import { AtTabBar } from "taro-ui";
|
||||
@@ -40,7 +40,7 @@ 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 youtube from "../../images/youtube.svg";
|
||||
import ssh from "../../images/ssh.svg";
|
||||
import robot from "../../images/robot.svg";
|
||||
import host from "../../images/host.svg";
|
||||
@@ -49,12 +49,20 @@ 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);
|
||||
@@ -63,7 +71,9 @@ const Index = () => {
|
||||
const [userInfo, setuserInfo] = useState({});
|
||||
|
||||
const [open, setopen] = useState(false);
|
||||
const [referer, setreferer] = useState(window.document.referrer);
|
||||
// const [referer, setreferer] = useState(window.document.referrer);
|
||||
const [referer, setreferer] = useState("");
|
||||
|
||||
|
||||
const [isOpened, setisOpened] = useState(false);
|
||||
const [meetupQR, setmeetupQR] = useState(false);
|
||||
@@ -75,6 +85,18 @@ const Index = () => {
|
||||
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)
|
||||
}
|
||||
}, [router.params]);
|
||||
|
||||
const getOpenidFromUrl = (url) => {
|
||||
const pattern = /[?&]openid=([^&#]+)/;
|
||||
@@ -134,10 +156,29 @@ const Index = () => {
|
||||
},
|
||||
});
|
||||
} else {
|
||||
let nowUrl = window.location.href;
|
||||
// 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");
|
||||
let payUrl = `https://payjs.cn/api/openid?mchid=1561724891&callback_url=${nowUrl}`;
|
||||
window.location.href = payUrl;
|
||||
|
||||
// 当前完整 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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -157,7 +198,7 @@ const Index = () => {
|
||||
}
|
||||
}
|
||||
},
|
||||
complete: function (finy) {},
|
||||
complete: function (finy) { },
|
||||
});
|
||||
};
|
||||
|
||||
@@ -188,7 +229,7 @@ const Index = () => {
|
||||
window.localStorage.removeItem("userInfo");
|
||||
window.localStorage.removeItem("openid");
|
||||
window.localStorage.removeItem("wxid");
|
||||
window.location.href = "https://nomadro.com";
|
||||
window.location.href = "https://hackrobot.cn";
|
||||
} else {
|
||||
// 有完整的用户信息userInfo
|
||||
setuserInfo(res?.data?.userInfo);
|
||||
@@ -227,9 +268,9 @@ const Index = () => {
|
||||
header: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
success: function (res) {},
|
||||
fail: function (err) {},
|
||||
complete: function (finy) {},
|
||||
success: function (res) { },
|
||||
fail: function (err) { },
|
||||
complete: function (finy) { },
|
||||
});
|
||||
};
|
||||
|
||||
@@ -248,9 +289,9 @@ const Index = () => {
|
||||
header: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
success: function (res) {},
|
||||
fail: function (err) {},
|
||||
complete: function (finy) {},
|
||||
success: function (res) { },
|
||||
fail: function (err) { },
|
||||
complete: function (finy) { },
|
||||
});
|
||||
};
|
||||
|
||||
@@ -258,9 +299,8 @@ const Index = () => {
|
||||
const tgMessage = () => {
|
||||
Taro.request({
|
||||
method: "GET",
|
||||
url: `${
|
||||
process.env.TARO_API_API
|
||||
}/tgMessage?openid=${window.localStorage.getItem("openid")}`, //仅为示例,并非真实的接口地址
|
||||
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("访问提醒发送成功");
|
||||
@@ -271,11 +311,10 @@ const Index = () => {
|
||||
// 入群申请
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
let inwechat = isWeChatFun();
|
||||
if (inwechat) {
|
||||
// 获取openId
|
||||
getOpenid();
|
||||
// getOpenid();
|
||||
} else {
|
||||
// window.alert('不在微信')
|
||||
}
|
||||
@@ -295,56 +334,22 @@ const Index = () => {
|
||||
setopen(value);
|
||||
};
|
||||
|
||||
const payh5 = (type, total_fee = 1000) => {
|
||||
// console.log("111");
|
||||
Taro.request({
|
||||
method: "POST",
|
||||
url: `${process.env.TARO_API_API}/payh5`,
|
||||
// url: "${process.env.TARO_API_API}/payh5",
|
||||
|
||||
// url: "https://pay.hackrobot.cn${process.env.TARO_API_API}/payh5", //仅为示例,并非真实的接口地址
|
||||
|
||||
data: {
|
||||
callback_url: window.location.href,
|
||||
openid: window.localStorage.getItem("openid"),
|
||||
total_fee: total_fee, // 金额,单位:分
|
||||
type: type, //订单类型 book/meetup/video/vip
|
||||
},
|
||||
header: {
|
||||
"content-type": "application/json", // 默认值
|
||||
},
|
||||
success: function (res) {
|
||||
console.log(res.data);
|
||||
// window.alert(res.data.jsapi);
|
||||
WeixinJSBridge.invoke(
|
||||
"getBrandWCPayRequest",
|
||||
{
|
||||
// 以下6个支付参数通过payjs的jsapi接口获取
|
||||
// appId: "wxc5205a653b0259bf",
|
||||
// timeStamp: "1701401644",
|
||||
// nonceStr: "KhOYB0wFV6j9qyQK",
|
||||
// package: "prepay_id=wx01113404850024729b35f4d69b73500000",
|
||||
// signType: "MD5",
|
||||
// paySign: "2A823C8D47CCF871C6C65A56DC228CF8",
|
||||
...(res?.data?.jsapi ?? {}),
|
||||
},
|
||||
function (res) {
|
||||
// 支付成功
|
||||
if (res.err_msg == "get_brand_wcpay_request:ok") {
|
||||
window.localStorage.setItem(type, 1); //订单类型 book/meetup/video/vip
|
||||
WeixinJSBridge.call("closeWindow");
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const goRead = () => {
|
||||
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}`,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -354,7 +359,9 @@ const Index = () => {
|
||||
{/* 参考资料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_title">《云手机》</View> */}
|
||||
|
||||
{/* <View className="left_text">手册3</View> */}
|
||||
{/* <View className="left_mobile">
|
||||
<Text>🚀 边旅行转赚钱</Text>
|
||||
@@ -374,34 +381,48 @@ const Index = () => {
|
||||
<View className="cardItem_left">
|
||||
<Image
|
||||
className="cardItem_left_img youtubeSvg"
|
||||
src={travel}
|
||||
// src={travel}
|
||||
src={tiktok}
|
||||
|
||||
// src={`https://minioweb.hackrobot.cn/hackrobot/travel.svg`}
|
||||
lazyLoad
|
||||
></Image>
|
||||
</View>
|
||||
<View className="cardItem_right">
|
||||
<View className="cardItem_rightUp">地理套利</View>
|
||||
<View className="cardItem_rightDown">
|
||||
边旅行边赚钱,无国界
|
||||
</View>
|
||||
<View className="cardItem_rightUp">Tiktok</View>
|
||||
<View className="cardItem_rightDown">无人直播</View>
|
||||
</View>
|
||||
</View>
|
||||
<View className="cardItem">
|
||||
<View className="cardItem_left">
|
||||
<Image className="cardItem_left_img" src={laptop}></Image>
|
||||
<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">远程办公</View>
|
||||
<View className="cardItem_rightDown">
|
||||
自由职业,分布式团队
|
||||
</View>
|
||||
<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}></Image>
|
||||
<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">游民社区</View>
|
||||
<View className="cardItem_rightDown">信息交流,快人一步</View>
|
||||
<View className="cardItem_rightUp">web</View>
|
||||
<View className="cardItem_rightDown">web:saas+web3</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
@@ -422,9 +443,14 @@ const Index = () => {
|
||||
</View>
|
||||
<View className="index_book_img">
|
||||
<Image
|
||||
className="book_img"
|
||||
// style="width: 40%px;height:100%;background: #fff;"
|
||||
src={nomadro}
|
||||
className="book_img"
|
||||
src={phone2}
|
||||
// src={dgnomad}
|
||||
// src="https://minioweb.hackrobot.cn/hackrobot/phone2.png"
|
||||
fetchpriority="high" // ✅ 提示浏览器尽快下载
|
||||
decoding="async" // ✅ 异步解码,加快渲染
|
||||
lazyLoad={false} // ✅ 禁止延迟加载(LCP 图必须优先加载)
|
||||
alt="云手机封面"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
@@ -436,8 +462,12 @@ const Index = () => {
|
||||
// style="width: 40%px;height:100%;background: #fff;"
|
||||
// src={book}
|
||||
src={laptop}
|
||||
// src={phone2}
|
||||
// src={tiktok}
|
||||
|
||||
lazyLoad
|
||||
/>
|
||||
<View className="bookImg_text">远程办公</View>
|
||||
<View className="bookImg_text">无人直播</View>
|
||||
</View>
|
||||
|
||||
<View className="bookImg">
|
||||
@@ -445,16 +475,21 @@ const Index = () => {
|
||||
// style="width: 40%px;height:100%;background: #fff;"
|
||||
// src={book}
|
||||
src={travel}
|
||||
// src={youtube}
|
||||
|
||||
lazyLoad
|
||||
/>
|
||||
<View className="bookImg_text">地理套利</View>
|
||||
<View className="bookImg_text">RPA自动化</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">独立开发</View>
|
||||
<View className="bookImg_text">web:saas+web3</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
16
src/pages/index/index.scss
Normal file → Executable file
@@ -74,8 +74,8 @@
|
||||
// background-color: #69b1ff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
// align-items: center;
|
||||
// justify-content: center;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 40px;
|
||||
.cardItem_left_img {
|
||||
width: 80px;
|
||||
@@ -143,8 +143,9 @@
|
||||
}
|
||||
.index_book_img {
|
||||
margin-top: 80px;
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,12 +187,17 @@
|
||||
@media (min-width: 1536px) {
|
||||
.index {
|
||||
// padding: 100px;
|
||||
// height: 100vh;
|
||||
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;
|
||||
|
||||
.index-book {
|
||||
|
||||
0
src/pages/meetup/Detail/index.jsx
Normal file → Executable file
0
src/pages/meetup/Detail/index.scss
Normal file → Executable file
0
src/pages/meetup/index.jsx
Normal file → Executable file
0
src/pages/meetup/index.scss
Normal file → Executable file
0
src/pages/model/index.config.js
Normal file → Executable file
0
src/pages/model/index.scss
Normal file → Executable file
0
src/pages/model/index.tsx
Normal file → Executable file
0
src/pages/my/index.jsx
Normal file → Executable file
0
src/pages/my/index.scss
Normal file → Executable file
0
src/pages/planet/index.config.js
Normal file → Executable file
0
src/pages/planet/index.scss
Normal file → Executable file
0
src/pages/planet/index.tsx
Normal file → Executable file
0
src/pages/salon/index.config.js
Normal file → Executable file
0
src/pages/salon/index.scss
Normal file → Executable file
0
src/pages/salon/index.tsx
Normal file → Executable file
2
src/pages/sphone/index.config.js
Normal file → Executable file
@@ -1,3 +1,3 @@
|
||||
export default {
|
||||
navigationBarTitleText: '云手机'
|
||||
navigationBarTitleText: '数字游民'
|
||||
}
|
||||
|
||||
0
src/pages/sphone/index.scss
Normal file → Executable file
0
src/pages/sphone/index.tsx
Normal file → Executable file
0
src/pages/sphone/info.md
Normal file → Executable file
0
src/pages/sphone/pay.md
Normal file → Executable file
27
src/utils/payEnv.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 支付环境检测:PC / H5 / 微信浏览器
|
||||
* 参考 nomadvip app/lib/payEnv.ts
|
||||
*/
|
||||
|
||||
export function isWeChat() {
|
||||
if (typeof navigator === "undefined") return false;
|
||||
return /MicroMessenger/i.test(navigator.userAgent);
|
||||
}
|
||||
|
||||
export function isMobile() {
|
||||
if (typeof navigator === "undefined") return false;
|
||||
return /AppleWebKit.*Mobile|Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
|
||||
navigator.userAgent
|
||||
);
|
||||
}
|
||||
|
||||
export function getPayEnv() {
|
||||
if (isWeChat()) return "wechat";
|
||||
if (isMobile()) return "h5";
|
||||
return "pc";
|
||||
}
|
||||
|
||||
export function getDeviceFromEnv(env) {
|
||||
if (env === "wechat") return "wechat";
|
||||
return env === "pc" ? "pc" : "h5";
|
||||
}
|
||||
117
yarn.lock
Normal file → Executable file
@@ -4116,6 +4116,11 @@ commander@10.0.0:
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.0.tgz#71797971162cd3cf65f0b9d24eb28f8d303acdf1"
|
||||
integrity sha512-zS5PnTI22FIRM6ylNW8G4Ap0IEOyk62fhLSD0+uHRT9McRCLGpkVNvao4bjimpK/GShynyQkFFxHhwMcETmduA==
|
||||
|
||||
commander@^12.1.0:
|
||||
version "12.1.0"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3"
|
||||
integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==
|
||||
|
||||
commander@^2.19.0, commander@^2.20.0, commander@^2.8.1:
|
||||
version "2.20.3"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||
@@ -4153,6 +4158,14 @@ compressible@~2.0.18:
|
||||
dependencies:
|
||||
mime-db ">= 1.43.0 < 2"
|
||||
|
||||
compression-webpack-plugin@^11.1.0:
|
||||
version "11.1.0"
|
||||
resolved "https://registry.yarnpkg.com/compression-webpack-plugin/-/compression-webpack-plugin-11.1.0.tgz#ee340d2029cf99ccecdea9ad1410b377d15b48b3"
|
||||
integrity sha512-zDOQYp10+upzLxW+VRSjEpRRwBXJdsb5lBMlRxx1g8hckIFBpe3DTI0en2w7h+beuq89576RVzfiXrkdPGrHhA==
|
||||
dependencies:
|
||||
schema-utils "^4.2.0"
|
||||
serialize-javascript "^6.0.2"
|
||||
|
||||
compression@^1.7.4:
|
||||
version "1.8.1"
|
||||
resolved "https://registry.yarnpkg.com/compression/-/compression-1.8.1.tgz#4a45d909ac16509195a9a28bd91094889c180d79"
|
||||
@@ -5124,6 +5137,14 @@ enhanced-resolve@^5.17.2, enhanced-resolve@^5.9.3:
|
||||
graceful-fs "^4.2.4"
|
||||
tapable "^2.2.0"
|
||||
|
||||
enhanced-resolve@^5.17.3:
|
||||
version "5.18.3"
|
||||
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz#9b5f4c5c076b8787c78fe540392ce76a88855b44"
|
||||
integrity sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==
|
||||
dependencies:
|
||||
graceful-fs "^4.2.4"
|
||||
tapable "^2.2.0"
|
||||
|
||||
entities@^1.1.1:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56"
|
||||
@@ -6266,7 +6287,7 @@ for-own@^0.1.4:
|
||||
dependencies:
|
||||
for-in "^1.0.1"
|
||||
|
||||
foreground-child@^3.1.0:
|
||||
foreground-child@^3.1.0, foreground-child@^3.3.1:
|
||||
version "3.3.1"
|
||||
resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.1.tgz#32e8e9ed1b68a3497befb9ac2b6adf92a638576f"
|
||||
integrity sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==
|
||||
@@ -6547,6 +6568,18 @@ glob@10.2.6:
|
||||
minipass "^5.0.0 || ^6.0.2"
|
||||
path-scurry "^1.7.0"
|
||||
|
||||
glob@^11.0.0, glob@^11.0.3:
|
||||
version "11.0.3"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-11.0.3.tgz#9d8087e6d72ddb3c4707b1d2778f80ea3eaefcd6"
|
||||
integrity sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==
|
||||
dependencies:
|
||||
foreground-child "^3.3.1"
|
||||
jackspeak "^4.1.1"
|
||||
minimatch "^10.0.3"
|
||||
minipass "^7.1.2"
|
||||
package-json-from-dist "^1.0.0"
|
||||
path-scurry "^2.0.0"
|
||||
|
||||
glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.6:
|
||||
version "7.2.3"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
|
||||
@@ -7898,6 +7931,13 @@ jackspeak@^2.0.3:
|
||||
optionalDependencies:
|
||||
"@pkgjs/parseargs" "^0.11.0"
|
||||
|
||||
jackspeak@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-4.1.1.tgz#96876030f450502047fc7e8c7fcf8ce8124e43ae"
|
||||
integrity sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==
|
||||
dependencies:
|
||||
"@isaacs/cliui" "^8.0.2"
|
||||
|
||||
jake@^10.8.5:
|
||||
version "10.9.2"
|
||||
resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.2.tgz#6ae487e6a69afec3a5e167628996b59f35ae2b7f"
|
||||
@@ -8531,6 +8571,11 @@ lru-cache@^10.2.0:
|
||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119"
|
||||
integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==
|
||||
|
||||
lru-cache@^11.0.0:
|
||||
version "11.2.2"
|
||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.2.2.tgz#40fd37edffcfae4b2940379c0722dc6eeaa75f24"
|
||||
integrity sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==
|
||||
|
||||
lru-cache@^4.1.2:
|
||||
version "4.1.5"
|
||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd"
|
||||
@@ -8923,7 +8968,7 @@ minimalistic-assert@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
|
||||
integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==
|
||||
|
||||
minimatch@*:
|
||||
minimatch@*, minimatch@^10.0.3:
|
||||
version "10.0.3"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.0.3.tgz#cf7a0314a16c4d9ab73a7730a0e8e3c3502d47aa"
|
||||
integrity sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==
|
||||
@@ -8992,7 +9037,7 @@ minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6, minimist@^1.2.8:
|
||||
resolved "https://registry.yarnpkg.com/minipass/-/minipass-6.0.2.tgz#542844b6c4ce95b202c0995b0a471f1229de4c81"
|
||||
integrity sha512-MzWSV5nYVT7mVyWCwn2o7JH13w2TBRmmSqSRCKzTw+lmft9X4z+3wjvs06Tzijo5z4W/kahUCDpRXTF+ZrmF/w==
|
||||
|
||||
"minipass@^5.0.0 || ^6.0.2 || ^7.0.0":
|
||||
"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2:
|
||||
version "7.1.2"
|
||||
resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707"
|
||||
integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==
|
||||
@@ -9639,6 +9684,11 @@ p-try@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
|
||||
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
|
||||
|
||||
package-json-from-dist@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505"
|
||||
integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==
|
||||
|
||||
package-json@^6.3.0, package-json@^6.5.0:
|
||||
version "6.5.0"
|
||||
resolved "https://registry.yarnpkg.com/package-json/-/package-json-6.5.0.tgz#6feedaca35e75725876d0b0e64974697fed145b0"
|
||||
@@ -9809,6 +9859,14 @@ path-scurry@^1.7.0:
|
||||
lru-cache "^10.2.0"
|
||||
minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
|
||||
|
||||
path-scurry@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-2.0.0.tgz#9f052289f23ad8bf9397a2a0425e7b8615c58580"
|
||||
integrity sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==
|
||||
dependencies:
|
||||
lru-cache "^11.0.0"
|
||||
minipass "^7.1.2"
|
||||
|
||||
path-to-regexp@0.1.12:
|
||||
version "0.1.12"
|
||||
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz#d5e1a12e478a976d432ef3c58d534b9923164bb7"
|
||||
@@ -10242,7 +10300,7 @@ postcss-selector-parser@^3.1.0:
|
||||
indexes-of "^1.0.1"
|
||||
uniq "^1.0.1"
|
||||
|
||||
postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5, postcss-selector-parser@^6.0.9:
|
||||
postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5, postcss-selector-parser@^6.0.9, postcss-selector-parser@^6.1.2:
|
||||
version "6.1.2"
|
||||
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz#27ecb41fb0e3b6ba7a1ec84fff347f734c7929de"
|
||||
integrity sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==
|
||||
@@ -10325,7 +10383,7 @@ postcss@^7.0.1, postcss@^7.0.23, postcss@^7.0.32, postcss@^7.0.36:
|
||||
picocolors "^0.2.1"
|
||||
source-map "^0.6.1"
|
||||
|
||||
postcss@^8.2.14, postcss@^8.3.5, postcss@^8.4.18, postcss@^8.4.33, postcss@^8.5.6:
|
||||
postcss@^8.2.14, postcss@^8.3.5, postcss@^8.4.18, postcss@^8.4.33, postcss@^8.4.47, postcss@^8.5.6:
|
||||
version "8.5.6"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.6.tgz#2825006615a619b4f62a9e7426cc120b349a8f3c"
|
||||
integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==
|
||||
@@ -10476,6 +10534,24 @@ pupa@^2.1.1:
|
||||
dependencies:
|
||||
escape-goat "^2.0.0"
|
||||
|
||||
purgecss-webpack-plugin@^7.0.2:
|
||||
version "7.0.2"
|
||||
resolved "https://registry.yarnpkg.com/purgecss-webpack-plugin/-/purgecss-webpack-plugin-7.0.2.tgz#76e7591ac1fac8d10538ad1659df586dae23d588"
|
||||
integrity sha512-YKN93Ndg/T/FsJaUL7KJdFBh/kfGi3pH7sm1OmVbhzb11mmS+vbRAQ5UIvqUd70kQrJ57+ZpPkDtRv66d89q9A==
|
||||
dependencies:
|
||||
purgecss "^7.0.2"
|
||||
webpack ">=5.95.0"
|
||||
|
||||
purgecss@^7.0.2:
|
||||
version "7.0.2"
|
||||
resolved "https://registry.yarnpkg.com/purgecss/-/purgecss-7.0.2.tgz#b7dccc3ead65a4301eed98e014793719a511c633"
|
||||
integrity sha512-4Ku8KoxNhOWi9X1XJ73XY5fv+I+hhTRedKpGs/2gaBKU8ijUiIKF/uyyIyh7Wo713bELSICF5/NswjcuOqYouQ==
|
||||
dependencies:
|
||||
commander "^12.1.0"
|
||||
glob "^11.0.0"
|
||||
postcss "^8.4.47"
|
||||
postcss-selector-parser "^6.1.2"
|
||||
|
||||
qs@6.13.0:
|
||||
version "6.13.0"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906"
|
||||
@@ -13191,6 +13267,37 @@ webpack-virtual-modules@^0.5.0:
|
||||
resolved "https://registry.yarnpkg.com/webpack-virtual-modules/-/webpack-virtual-modules-0.5.0.tgz#362f14738a56dae107937ab98ea7062e8bdd3b6c"
|
||||
integrity sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==
|
||||
|
||||
webpack@>=5.95.0:
|
||||
version "5.101.3"
|
||||
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.101.3.tgz#3633b2375bb29ea4b06ffb1902734d977bc44346"
|
||||
integrity sha512-7b0dTKR3Ed//AD/6kkx/o7duS8H3f1a4w3BYpIriX4BzIhjkn4teo05cptsxvLesHFKK5KObnadmCHBwGc+51A==
|
||||
dependencies:
|
||||
"@types/eslint-scope" "^3.7.7"
|
||||
"@types/estree" "^1.0.8"
|
||||
"@types/json-schema" "^7.0.15"
|
||||
"@webassemblyjs/ast" "^1.14.1"
|
||||
"@webassemblyjs/wasm-edit" "^1.14.1"
|
||||
"@webassemblyjs/wasm-parser" "^1.14.1"
|
||||
acorn "^8.15.0"
|
||||
acorn-import-phases "^1.0.3"
|
||||
browserslist "^4.24.0"
|
||||
chrome-trace-event "^1.0.2"
|
||||
enhanced-resolve "^5.17.3"
|
||||
es-module-lexer "^1.2.1"
|
||||
eslint-scope "5.1.1"
|
||||
events "^3.2.0"
|
||||
glob-to-regexp "^0.4.1"
|
||||
graceful-fs "^4.2.11"
|
||||
json-parse-even-better-errors "^2.3.1"
|
||||
loader-runner "^4.2.0"
|
||||
mime-types "^2.1.27"
|
||||
neo-async "^2.6.2"
|
||||
schema-utils "^4.3.2"
|
||||
tapable "^2.1.1"
|
||||
terser-webpack-plugin "^5.3.11"
|
||||
watchpack "^2.4.1"
|
||||
webpack-sources "^3.3.3"
|
||||
|
||||
webpack@^5.78.0:
|
||||
version "5.100.2"
|
||||
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.100.2.tgz#e2341facf9f7de1d702147c91bcb65b693adf9e8"
|
||||
|
||||