73 Commits

Author SHA1 Message Date
Eamon-meng
49725fd38e 提取IP页面地区筛选数据更新为调用后端接口返回 2026-06-12 16:42:05 +08:00
Eamon-meng
7947fc48a2 帮助中心数据实现动态渲染 2026-06-11 16:30:24 +08:00
Eamon-meng
99039b6622 购买页面提示取消 2026-06-02 13:19:28 +08:00
Eamon-meng
db1acf6f70 购买套餐没有套餐时修复页面展示 2026-06-02 13:17:45 +08:00
Eamon-meng
3a2fbe29fb 修改提取ip时的传参方式 2026-05-22 17:02:19 +08:00
Eamon-meng
5c236c0b01 解决我的账单页面报错 & 提交记录添加套餐号筛选 2026-05-20 13:43:44 +08:00
Eamon-meng
fde097c601 添加seo功能 2026-05-15 16:56:05 +08:00
Eamon-meng
670961c17d 添加获取价格响应异常显示loading & 时间显示到秒 2026-05-14 15:44:31 +08:00
Eamon-meng
84a5e27c05 提取IP添加主机格式选项功能 2026-05-09 13:34:36 +08:00
Eamon-meng
9dea370a87 更改白名单上限 2026-05-06 15:14:33 +08:00
Eamon-meng
602372e58d 套餐页面添加标题字段 2026-04-30 16:57:00 +08:00
Eamon-meng
574ad0e662 购买页面 & IP提取页面样式调整 2026-04-28 18:02:26 +08:00
Eamon-meng
78d916ade1 购买页面调间隙高度 2026-04-28 18:02:25 +08:00
Eamon-meng
6a9e7289b5 购买套餐页面更改字段和添加小字提示 & 添加余额管理页面
Co-authored-by: Copilot <copilot@github.com>
2026-04-28 18:02:23 +08:00
6adcd33943 放开提取接口权限 2026-04-28 18:01:57 +08:00
Eamon-meng
165151b9d2 解决构建报错问题 2026-04-23 14:09:38 +08:00
Eamon-meng
1e76275f04 套餐添加操作列 & 登录后已实名的更新状态显示 &更新脚本的远程仓库地址 2026-04-23 13:43:06 +08:00
Eamon-meng
d9cd5eb41b 发布v1.9.0版本 2026-04-22 13:18:40 +08:00
Eamon-meng
7ff42861f1 套餐管理添加IP检查状态字段 &修复套餐提取和购买的数量显示问题 2026-04-22 10:27:19 +08:00
Eamon-meng
eb4c2d2d5f 暂无可用套餐时取消加载中 & 登录页加返回首页 & 加联系专属客服 & 修改白名单上限10个 2026-04-21 16:51:30 +08:00
a0b0956677 免费与菜单提示文字 2026-04-21 16:19:11 +08:00
Eamon-meng
27e694ee0d 取消关闭支付弹窗调用关闭订单接口 & 添加未实名去支付时的拦截 2026-04-20 16:22:49 +08:00
Eamon-meng
74d53c619d 购买套餐添加count_min字段 2026-04-20 15:36:50 +08:00
8f8def3a87 完善异常跳转 2026-04-20 15:34:49 +08:00
Eamon-meng
ed73d8579f 更新发布v1.7.0 2026-04-18 17:36:22 +08:00
Eamon-meng
e3c61a77e6 修复未登录时提取ip时的套餐显示和不调用获取套餐接口 2026-04-18 17:13:26 +08:00
5f7756199a 修复 sku 缺失显示问题 2026-04-18 15:47:20 +08:00
6aa108e8d3 优化完善套餐购买页面 2026-04-18 15:35:35 +08:00
Eamon-meng
8b65a1745c 修复用户未/授权购买套餐价格计算 & 发布v1.6.0版本 2026-04-16 17:46:11 +08:00
Eamon-meng
5607217625 动态生成购买套餐 & 取消初次进后台修改密码的弹窗 & 添加总折扣字段 & 发布v1.5.0版本 2026-04-16 14:41:42 +08:00
319baea5e8 修复环境变量问题 2026-04-14 11:34:28 +08:00
Eamon-meng
9a8a1826c9 发布v1.4.0版本 2026-04-13 11:33:19 +08:00
Eamon-meng
c2a0310ee5 修复未认证提取ip的接口调用户会话 2026-04-13 10:23:19 +08:00
Eamon-meng
8ee8feb2bf 修改构建脚本路径 2026-04-02 17:56:48 +08:00
Eamon-meng
1e090f5c88 更新配置文件修复构建问题 2026-04-02 17:52:15 +08:00
Eamon-meng
665ce79e1d 发布v1.3.0版本 2026-04-02 17:35:51 +08:00
Eamon-meng
93110954bb 更新订单详情显示字段名称 2026-04-02 14:08:32 +08:00
Eamon-meng
8ce5f99a24 开启充值和余额支付功能 2026-03-31 16:11:47 +08:00
Eamon-meng
e27869fb4a 重新计算价格显示 2026-03-31 16:11:46 +08:00
Eamon-meng
01c4afd209 更新发布v1.2.3版本 2026-03-31 16:11:46 +08:00
2a959fa9cf 优化客户端请求机制 2026-03-31 16:09:43 +08:00
Eamon-meng
d9f267e257 调整桌面端产品订购的菜单栏布局 2026-03-14 18:00:27 +08:00
Eamon-meng
83530d7f1e 修改移动端菜单栏侧边栏布局 2026-03-14 15:25:23 +08:00
Eamon-meng
b2c36196b4 修复实名认证阶段的问题 2026-03-13 18:26:23 +08:00
Eamon-meng
d2d6c1709c 我的套餐新增状态筛选字段 2026-03-13 18:12:22 +08:00
Eamon-meng
a76e61beb0 补充按钮手形样式显示 2026-03-13 14:30:48 +08:00
Eamon-meng
d83ad11241 我的账单页面取消退款和操作列 & 后台Header添加返回首页功能 2026-03-13 14:15:29 +08:00
Eamon-meng
bce7e41adf 修改网站图标显示 & 所以按钮添加手形样式 2026-03-13 14:15:28 +08:00
2b77ea189b 支付组件统一使用二维码展示 2026-03-13 14:13:06 +08:00
Eamon-meng
82bd8051d8 更新发布v1.2.2版本 2026-03-11 17:34:21 +08:00
Eamon-meng
4e27d707ec 手机端支付修改为桌面支付方式 2026-03-11 17:29:29 +08:00
Eamon-meng
32c08d96d4 调整帮助中心移动端文档布局 2026-03-10 17:15:30 +08:00
Eamon-meng
1031630712 更新README.md文档 2026-03-10 17:06:54 +08:00
Eamon-meng
31c26e9636 更新README.md文档项目目录目录结构 2026-03-03 15:47:34 +08:00
Eamon-meng
333bd3f686 更新README.md文档 2026-03-03 14:28:53 +08:00
Eamon-meng
9201a819be 更新README.md文档 2026-03-03 13:23:18 +08:00
Eamon-meng
a2187adb05 新增本地构建脚本 2026-02-27 16:41:38 +08:00
Eamon-meng
4b18c91157 修复修改密码弹窗&取消后台显示客服弹窗 & 取消退出登录profile为空抛异常的判断 2026-02-27 15:03:17 +08:00
Eamon-meng
2125f1ef9e 更新首页文档跳转链接&调整后台分页显示 2026-02-26 16:37:50 +08:00
Eamon-meng
85f241e8e3 更新发布v1.1.1版本 2026-01-21 10:19:01 +08:00
Eamon-meng
671ad8ab9d 去除用户信息的邮箱显示 2026-01-20 18:51:45 +08:00
Eamon-meng
fc47ec9d18 调整后台用户资料显示的逻辑 2026-01-19 10:02:03 +08:00
Eamon-meng
7dc562aad0 修改帮助中心的文档路径 2026-01-14 17:27:31 +08:00
Eamon-meng
e709490014 更新发布v1.1.0版本 2026-01-14 13:32:42 +08:00
367215db71 修复部分路径问题 2026-01-14 11:44:26 +08:00
39d8f47c11 重构文档目录结构,更新文章链接引用地址 2026-01-13 19:12:16 +08:00
f5c6e67a06 微调文档内容 2026-01-13 18:53:46 +08:00
Eamon-meng
f861a5731d 补充帮助中心操作指南和客户端教程相关文档 2026-01-13 17:10:53 +08:00
f262d6b1f1 补全产品文档 2026-01-13 15:46:44 +08:00
db8119e1ae 调整帮助中心文档 2026-01-12 18:28:50 +08:00
91add59393 屏蔽余额功能 2025-12-23 17:12:09 +08:00
a3588fd1be 重构首页,优化组件结构 2025-12-23 17:11:46 +08:00
Eamon-meng
78d605749f 修改相关协议和文档 2025-12-22 11:31:14 +08:00
205 changed files with 5730 additions and 4716 deletions

4
.env.example Normal file
View File

@@ -0,0 +1,4 @@
# 开发环境配置
API_BASE_URL=http://192.168.0.15:8080
CLIENT_ID=web
CLIENT_SECRET=web

View File

@@ -122,7 +122,7 @@ type UserProfile = ExtraResp<typeof getProfile>
## 环境变量
需要配置:
- `NEXT_PUBLIC_API_BASE_URL` - 后端 API 地址
- `API_BASE_URL` - 后端 API 地址
- `CLIENT_ID`, `CLIENT_SECRET` - OAuth2 设备认证凭据
## 部署

2
.gitignore vendored
View File

@@ -31,7 +31,7 @@ yarn-error.log*
.pnpm-debug.log*
# env files (can opt-in for committing if needed)
.env*
.env
# vercel
.vercel

View File

@@ -1 +1 @@
bun run lint
#bun run lint

View File

@@ -7,4 +7,7 @@
"[json]": {
"editor.defaultFormatter": "vscode.json-language-features"
},
"[typescriptreact]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
}

View File

@@ -16,6 +16,7 @@ COPY . .
ENV NEXT_TELEMETRY_DISABLED=1
RUN mv .env.example .env
RUN bun run build
# 生产阶段
@@ -31,6 +32,7 @@ RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
RUN rm .env
USER nextjs

135
README.md
View File

@@ -1,40 +1,113 @@
## TODO
分离公共 api 接口 env 定义
统一前端基础库类型api
购买页固定套餐
### 禁止直接依赖 form
`\[(.*,)?form(,.*)?\]`
### 次要
业务定制页面每月需求用量,可选项需要确认是否合理
页头高度降低
帮助中心文档优化
考虑重新组织导航栏
- 产品购买
- 提取 IP
- 业务场景
- 帮助中心
- 大客户定制
购买与提取手机端优化,尽量一页展示全部
全部替换封装时间范围组件,检查结束时间字段手机端适配问题(需要尾部对齐)
迁移到 tanstack form
页尾链接完善跳转地址
树组件优化
### 架构改进
## 目录结构
考虑使用 swr 或 react query 来代替直接的服务端 react cache 缓存以及客户端 zustand 缓存,以将服务端请求的数据能够水合到客户端,避免重复请求
```
├── node_modules/ # 项目依赖包
├── public/ # 静态资源(如 favicon、图片等可直接通过根路径访问
├── src/ # 源代码目录
│ ├── actions/ # 服务端操作或 API 逻辑(如 Server Actions
│ ├── app/ # Next.js 13+ App Router 目录,存放页面、布局、路由等
| | ├── (api)/ # API 路由目录
| | ├── (auth)/ # 认证相关页面
| | ├── (home)/ # 首页模块
| | ├── admin/ # 管理后台模块
| | ├── effects.tsx # 全局副作用/状态管理(如 Redux、SWR 初始化)
| | ├── favicon.ico # 网站图标
| | ├── globals.css # 全局样式
| | └── layout.tsx # 根布局组件(所有页面共享的布局结构)
| |
│ ├── assets/ # 项目资源文件(如图片、字体、样式等)
│ ├── components/ # 可复用的 React 组件
│ ├── lib/ # 工具函数、配置、服务等
│ ├── mdx-components.tsx # MDX 组件配置
│ └── proxy.ts # 代理配置(如 API 代理)
├── .dockerignore # Docker 忽略文件
├── .env # 环境变量文件
├── .gitignore # Git 忽略文件
├── .npmrc # npm 配置
├── bun.lock # Bun 包管理器锁文件
├── components.json # 组件库配置(如 shadcn/ui
├── Dockerfile # Docker 构建配置
├── eslint.config.mjs # ESLint 代码检查配置
├── next-env.d.ts # Next.js 类型声明
├── next.config.ts # Next.js 配置文件
├── package.json # 项目依赖和脚本
├── postcss.config.mjs # PostCSS 配置
├── publish.ps1 # PowerShell 发布脚本
├── README.md # 项目说明文档
└── tsconfig.json # TypeScript 配置
```
### 需要确认
## 技术栈
| 类别 | 场景/库名 | 推荐方案/用途 |
| :--- | :--- | :--- |
| **状态管理** | 简单跨组件通信 | React Context |
| | 复杂全局状态 | Zustand + persist |
| | 服务端状态 | TanStack Query |
| | 表单状态 | React Hook Form |
| | 路由状态 | Next.js 内置 (useSearchParams, useParams) |
| **核心框架** | Next.js | 服务框架 (React 全栈框架) |
| **UI / 样式体系** | shadcn/ui | UI 组件库 |
| | Radix UI | 无样式基础 UI 组件原语 |
| | Tailwind CSS | CSS 框架 (原子化 CSS) |
| | lucide-react | 图标库 |
| **表单与数据验证** | React Hook Form | 表单状态管理及验证 |
| | Zod | 数据验证与类型推断 |
| **数据管理与通信** | Zustand | 全局状态管理库 |
| | TanStack Query | 服务端状态管理 (数据请求、缓存) |
| | TanStack Table | 无头 UI 表格库 |
| **图表可视化** | Recharts | 图表库 |
| **工具库** | date-fns | 日期时间处理库 |
| | qrcode | QR 码生成库 |
页面内操作是否需要关联到 url 上,以在使用后退功能时返回到上一次操作
## 搭建开发环境
项目基于 bun 运行bun 是一个 typescript 原生的运行时环境,用于代替 nodejs可以带来更高的性能提升。
1. 拉取项目:`git clone https://43.226.58.254:53000/lanhu/web`
2. 安装依赖包:`bun install`
3. 创建环境变量文件 .env复制 .env.example 中的内容到 .env并根据实际情况修改
4. 运行项目 `bun run dev`
## 构建项目 & 版本管理
1. 在 package.json 文件中修改版本号
2. 构建并上传镜像, 终端运行 `./publish.ps1 <版本号>`
3. 终端执行成功后在 `https://43.226.58.254:53000/lanhu/web` 发布最新版本
生产环境的项目部署通过单独的部署脚本进行管理,前端开发上线只需要构建以及发布版本,无需考虑部署问题。
## 开发规范 & 主要业务逻辑
原型图https://lanhuapp.com/link/#/invite?sid=lxgnSyga
### 快速创建前台新页面
wrap.tsx :新页面间距复用组件
page.tsx新页面统一布局
stores共享状态组件
### 数据流与状态管理: 组件间通信方式
- 通信方式Props 父传子 / 回调函数 子传父 / Context 跨组件 useContext() / Zustand Store 全局状态管理useStore() / URL 参数 页面间状态共享 useSearchParams()
路由与导航: 路由使用的Next.js App Router 架构 Server Actions 实现。
### 认证与权限控制:登录流程-路由守卫(中间件)
### 支付流程实现: 支付二维码生成
### SSE 支付状态监听
## 注意事项
- 图片使用 Next.js Image 组件自动优化
- 动态导入 (next/dynamic) 实现纯客户端组件

View File

@@ -35,6 +35,7 @@
"lucide-react": "^0.479.0",
"next": "^16.0.10",
"next-themes": "^0.4.6",
"photoswipe": "^5.4.4",
"qrcode": "^1.5.4",
"react": "^19.2.1",
"react-day-picker": "8.10.1",
@@ -45,6 +46,7 @@
"sonner": "^2.0.7",
"tailwind-merge": "^3.4.0",
"tailwindcss-animate": "^1.0.7",
"vaul": "^1.1.2",
"zod": "^3.25.76",
"zustand": "^5.0.9",
},
@@ -1163,6 +1165,8 @@
"path-parse": ["path-parse@1.0.7", "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
"photoswipe": ["photoswipe@5.4.4", "https://registry.npmmirror.com/photoswipe/-/photoswipe-5.4.4.tgz", {}, "sha512-WNFHoKrkZNnvFFhbHL93WDkW3ifwVOXSW3w1UuZZelSmgXpIGiZSNlZJq37rR8YejqME2rHs9EhH9ZvlvFH2NA=="],
"picocolors": ["picocolors@1.1.1", "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"picomatch": ["picomatch@4.0.3", "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
@@ -1399,6 +1403,8 @@
"util-deprecate": ["util-deprecate@1.0.2", "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
"vaul": ["vaul@1.1.2", "https://registry.npmmirror.com/vaul/-/vaul-1.1.2.tgz", { "dependencies": { "@radix-ui/react-dialog": "^1.1.1" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA=="],
"vfile": ["vfile@6.0.3", "https://registry.npmmirror.com/vfile/-/vfile-6.0.3.tgz", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="],
"vfile-message": ["vfile-message@4.0.3", "https://registry.npmmirror.com/vfile-message/-/vfile-message-4.0.3.tgz", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="],

View File

@@ -1,6 +1,6 @@
{
"name": "lanhu-web",
"version": "1.0.0",
"version": "1.13.0",
"private": true,
"scripts": {
"dev": "next dev -H 0.0.0.0 --turbopack",
@@ -41,6 +41,7 @@
"lucide-react": "^0.479.0",
"next": "^16.0.10",
"next-themes": "^0.4.6",
"photoswipe": "^5.4.4",
"qrcode": "^1.5.4",
"react": "^19.2.1",
"react-day-picker": "8.10.1",
@@ -51,6 +52,7 @@
"sonner": "^2.0.7",
"tailwind-merge": "^3.4.0",
"tailwindcss-animate": "^1.0.7",
"vaul": "^1.1.2",
"zod": "^3.25.76",
"zustand": "^5.0.9"
},
@@ -74,5 +76,5 @@
"typescript": "^5.9.3",
"babel-plugin-react-compiler": "^1.0.0"
},
"packageManager": "bun@1.2.19"
"packageManager": "bun@1.3.2"
}

16
publish.ps1 Normal file
View File

@@ -0,0 +1,16 @@
if (-not $args) {
Write-Error "需要指定版本号"
exit 1
}
$confrim = Read-Host "构建版本为 [web:$($args[0])],是否继续?(y/n)"
if ($confrim -ne "y") {
Write-Host "已取消构建"
exit 0
}
docker build -t repo.lanhuip.com/lanhu/web:latest .
docker build -t repo.lanhuip.com/lanhu/web:$($args[0]) .
docker push repo.lanhuip.com/lanhu/web:latest
docker push repo.lanhuip.com/lanhu/web:$($args[0])

12
src/actions/article.ts Normal file
View File

@@ -0,0 +1,12 @@
'use server'
import {callByDevice} from './base'
import {ArticleDetail, ArticleNavGroup} from '@/lib/models/article'
export async function getArticleNav(params: {}) {
return await callByDevice<ArticleNavGroup[]>('/api/article/nav', params)
}
export async function getArticleDetail(params: {id: number}) {
return await callByDevice<ArticleDetail>('/api/article/get', params)
}

View File

@@ -2,15 +2,7 @@
import {cookies} from 'next/headers'
import {ApiResponse, UnauthorizedError} from '@/lib/api'
import {User} from '@/lib/models'
import {callByDevice, callByUser} from '@/actions/base'
type TokenResp = {
access_token: string
refresh_token: string
expires_in: number
token_type: string
scope?: string
}
import {callByDevice, callByUser, TokenResp} from '@/actions/base'
export type LoginMode = 'phone_code' | 'password'

14
src/actions/balance.ts Normal file
View File

@@ -0,0 +1,14 @@
'use server'
import {Balance} from '@/lib/models'
import {callByUser} from '@/actions/base'
import {PageRecord} from '@/lib/api'
export async function listBalances(params: {
page?: number
size?: number
bill_no?: string
created_at_start?: Date
created_at_end?: Date
}) {
return await callByUser<PageRecord<Balance>>('/api/balance/page', params)
}

View File

@@ -1,8 +1,24 @@
'use server'
import {API_BASE_URL, ApiResponse, CLIENT_ID, CLIENT_SECRET} from '@/lib/api'
import {add, isBefore} from 'date-fns'
import {cookies, headers} from 'next/headers'
import {cache} from 'react'
import {redirect} from 'next/navigation'
import {cache} from 'react'
export type TokenResp = {
access_token: string
refresh_token: string
expires_in: number
token_type: string
scope?: string
}
export async function getApiUrl() {
return {
success: true,
data: API_BASE_URL,
} satisfies ApiResponse<string>
}
// ======================
// public
@@ -26,6 +42,9 @@ const _callPublic = cache(async <R = undefined>(
// device
// ======================
let token: string | null = null
let token_expire: Date | null = null
async function callByDevice<R = undefined>(
endpoint: string,
data: unknown,
@@ -37,18 +56,20 @@ const _callByDevice = cache(async <R = undefined>(
endpoint: string,
data?: string,
): Promise<ApiResponse<R>> => {
// 获取设备令牌
if (!CLIENT_ID || !CLIENT_SECRET) {
return {
success: false,
status: 401,
message: '未配置 CLIENT_ID 或 CLIENT_SECRET',
if (!token || !token_expire || isBefore(token_expire, new Date())) {
const basic = Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString('base64url')
const resp = await call<TokenResp>(`${API_BASE_URL}/api/auth/token`, JSON.stringify({
grant_type: 'client_credentials',
}), `Basic ${basic}`)
if (!resp.success) {
return resp
}
token = resp.data.access_token
token_expire = add(new Date(), {seconds: resp.data.expires_in})
}
const token = Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString('base64url')
// 发起请求
return call(`${API_BASE_URL}${endpoint}`, data, `Basic ${token}`)
return call(`${API_BASE_URL}${endpoint}`, data, `Bearer ${token}`)
})
// ======================
@@ -86,7 +107,6 @@ const _callByUser = cache(async <R = undefined>(
// ======================
async function call<R = undefined>(url: string, body: RequestInit['body'], auth?: string): Promise<ApiResponse<R>> {
let response: Response
try {
const reqHeaders = await headers()
const reqIP = reqHeaders.get('x-forwarded-for')
@@ -98,73 +118,59 @@ async function call<R = undefined>(url: string, body: RequestInit['body'], auth?
if (reqIP) callHeaders['X-Forwarded-For'] = reqIP
if (reqUA) callHeaders['User-Agent'] = reqUA
response = await fetch(url, {
const response = await fetch(url, {
method: 'POST',
headers: callHeaders,
body,
})
if (response.status === 401) {
return redirect('/login?redirect=' + encodeURIComponent(url.replace(API_BASE_URL, '')))
}
const type = response.headers.get('Content-Type') ?? 'text/plain'
if (type.indexOf('text/plain') !== -1) {
const text = await response.text()
if (!response.ok) {
console.log('后端请求失败', url, `status=${response.status}`, text)
return {
success: false,
status: response.status,
message: text || '请求失败',
}
}
if (!!text?.trim()?.length) {
console.log('未处理的响应成功', `type=text`, `text=${text}`)
}
return {
success: true,
data: undefined as R, // 强转类型,考虑优化
}
}
else if (type.indexOf('application/json') !== -1) {
const json = await response.json()
if (!response.ok) {
console.log('后端请求失败', url, `status=${response.status}`, json)
return {
success: false,
status: response.status,
message: json.message || json.error_description || '请求失败', // 业务错误message或者 oauth 错误error_description
}
}
return {
success: true,
data: json,
}
}
throw new Error(`无法解析响应数据,未处理的 Content-Type: ${type}`)
}
catch (e) {
console.error('后端请求失败', url, (e as Error).message)
throw new Error(`请求失败,网络错误`)
}
const type = response.headers.get('Content-Type') ?? 'text/plain'
if (type.indexOf('text/plain') !== -1) {
const text = await response.text()
if (!response.ok) {
console.log('后端请求失败', url, `status=${response.status}`, text)
return {
success: false,
status: response.status,
message: text || '请求失败',
}
}
if (!!text?.trim()?.length) {
console.log('未处理的响应成功', `type=text`, `text=${text}`)
}
return {
success: true,
data: undefined as R, // 强转类型,考虑优化
}
}
else if (type.indexOf('application/json') !== -1) {
const json = await response.json()
if (!response.ok) {
console.log('后端请求失败', url, `status=${response.status}`, json)
return {
success: false,
status: response.status,
message: json.message || json.error_description || '请求失败', // 业务错误message或者 oauth 错误error_description
}
}
return {
success: true,
data: json,
}
}
throw new Error(`无法解析响应数据,未处理的 Content-Type: ${type}`)
}
async function postCall<R = undefined>(rawResp: Promise<ApiResponse<R>>) {
const header = await headers()
const pathname = header.get('x-pathname') || '/'
const resp = await rawResp
// 重定向到登录页
const match = [
RegExp(`^/admin.*`),
].some(item => item.test(pathname))
if (match && !resp.success && resp.status === 401) {
console.log('🚗🚗🚗🚗🚗 非正常重定向 🚗🚗🚗🚗🚗')
redirect('/login?force=true')
}
return resp
}
// 导出

View File

@@ -3,6 +3,11 @@ import {PageRecord} from '@/lib/api'
import {Batch} from '@/lib/models/batch'
import {callByUser} from './base'
export async function pageBatch(props: {page: number, size: number}) {
export async function pageBatch(props: {
page: number
size: number
time_start?: Date
time_end?: Date
resource_no?: string}) {
return callByUser<PageRecord<Batch>>('/api/batch/page', props)
}

View File

@@ -17,6 +17,7 @@ export async function listChannels(props: {
}
type CreateChannelsResp = {
ip: string
host: string
port: string
username?: string
@@ -28,9 +29,23 @@ export async function createChannels(params: {
protocol: number
auth_type: number
count: number
prov?: string
city?: string
// prov?: string
area_id?: number
isp?: number
host_format?: number
}) {
return callPublic<CreateChannelsResp[]>('/api/channel/create', params)
}
export async function createChannelsV3(params: {
resource_no: string
protocol: number
auth_type: number
count: number
// prov?: string
area_id?: number
isp?: number
host_format?: number
}) {
return callPublic<CreateChannelsResp[]>('/api/channel/create/v3', params)
}

12
src/actions/product.ts Normal file
View File

@@ -0,0 +1,12 @@
import {callByUser, callPublic} from './base'
import {Product} from '@/lib/models/product'
export type ProductItem = Product
export async function listProduct(props: {}) {
return callByUser<Product[]>('/api/product/list', props)
}
export async function listProductHome(props: {}) {
return callPublic<Product[]>('/api/product/list', props)
}

View File

@@ -24,6 +24,7 @@ export async function listResourceShort(props: {
size: number
resource_no?: string
type?: number
status?: number
create_after?: Date
create_before?: Date
expire_after?: Date
@@ -37,6 +38,7 @@ export async function listResourceLong(props: {
size: number
resource_no?: string
type?: number
status?: number
create_after?: Date
create_before?: Date
expire_after?: Date
@@ -87,9 +89,28 @@ export async function payClose(props: {
}
export async function getPrice(props: CreateResourceReq) {
return callByDevice<{
return callByUser<{
price: string
discounted_price?: string
discounted?: number
actual?: string
discounted?: string
}>('/api/resource/price', props)
}
export async function getPriceHome(props: CreateResourceReq) {
return callByDevice<{
price: string
actual?: string
discounted?: string
}>('/api/resource/price', props)
}
export async function updateCheckip(props: {
id: number
checkip: boolean
}) {
return callByUser('/api/resource/update/checkip', props)
}
export async function getAreaList(props: {}) {
return callByUser('/api/area/list', props)
}

View File

@@ -38,16 +38,16 @@ export async function Identify(props: {
}
export async function update(props: {
username: string
email: string
contact_qq: string
contact_wechat: string
username?: string
email?: string
contact_qq?: string
contact_wechat?: string
}) {
return await callByUser('/api/user/update', props)
}
export async function updatePassword(props: {
phone: string
// phone: string
code: string
password: string
}) {

View File

@@ -1,6 +1,6 @@
'use server'
import {ApiResponse} from '@/lib/api'
import {callByDevice} from '@/actions/base'
import {callByDevice, callByUser} from '@/actions/base'
import {getCap} from '@/lib/cap'
export async function sendSMS(props: {
@@ -28,7 +28,7 @@ export async function sendSMS(props: {
}
// 请求发送短信
return await callByDevice('/api/auth/verify/sms', {
return await callByDevice('/api/verify/sms', {
phone: props.phone,
purpose: 0,
})
@@ -38,3 +38,35 @@ export async function sendSMS(props: {
throw new Error('验证码验证失败', {cause: error})
}
}
export async function updateSendSMS(props: {
captcha: string
}): Promise<ApiResponse> {
try {
// 人机验证
if (!props.captcha?.length) {
return {
success: false,
status: 400,
message: '请输入验证码',
}
}
const cap = await getCap()
const valid = await cap.validateToken(props.captcha)
if (!valid) {
return {
success: false,
status: 400,
message: '验证码错误或已过期',
}
}
// 请求发送短信
return await callByUser('/api/verify/sms/password', {})
}
catch (error) {
console.error('验证码验证失败:', error)
throw new Error('验证码验证失败', {cause: error})
}
}

View File

@@ -1,11 +1,13 @@
import {NextRequest, NextResponse} from 'next/server'
import {createChannels} from '@/actions/channel'
import {createChannels, createChannelsV3} from '@/actions/channel'
export async function GET(req: NextRequest) {
const params = req.nextUrl.searchParams
try {
const resource_id = params.get('i')
if (!resource_id) {
const resourceParam = params.get('i')
if (!resourceParam) {
throw new Error('需要指定资源ID')
}
let protocol = params.get('x')
@@ -20,19 +22,50 @@ export async function GET(req: NextRequest) {
if (!count) {
throw new Error('需要指定通道创建数量')
}
const prov = params.get('a') || undefined
const city = params.get('b') || undefined
// const prov = params.get('a') || undefined
const area_id = params.get('b') || undefined
const isp = params.get('s') || undefined
const hostFormat = params.get('rh') || 'domain'
const isNumeric = /^\d+$/.test(resourceParam)
let result
if (!isNumeric) {
console.log(area_id, 'area_id', params.get('b'), 'params.get')
result = await createChannelsV3({
resource_no: resourceParam,
auth_type: Number(auth_type),
protocol: Number(protocol),
count: Number(count),
// prov,
area_id: Number(area_id),
isp: Number(isp),
host_format: hostFormat === 'domain' ? 1 : 2,
})
console.log({
resource_no: resourceParam,
auth_type: Number(auth_type),
protocol: Number(protocol),
count: Number(count),
// prov,
area_id: Number(area_id),
isp: Number(isp),
host_format: hostFormat === 'domain' ? 1 : 2,
})
}
else {
result = await createChannels({
resource_id: Number(resourceParam),
auth_type: Number(auth_type),
protocol: Number(protocol),
count: Number(count),
// prov,
area_id: Number(area_id),
isp: Number(isp),
host_format: hostFormat === 'domain' ? 1 : 2,
})
}
const result = await createChannels({
resource_id: Number(resource_id),
auth_type: Number(auth_type),
protocol: Number(protocol),
count: Number(count),
prov,
city,
isp: Number(isp),
})
if (!result.success) {
throw new Error(result.message)
}
@@ -46,10 +79,32 @@ export async function GET(req: NextRequest) {
switch (format) {
case 'json':
return NextResponse.json(result.data)
if (hostFormat === 'domain') {
const domainFormatData = result.data.map(item => ({
host: item.host,
port: item.port,
...(item.username && item.password ? {username: item.username, password: item.password} : {}),
}))
return NextResponse.json(domainFormatData)
}
else {
const ipFormatData = result.data.map(item => ({
ip: item.ip,
port: item.port,
...(item.username && item.password ? {username: item.username, password: item.password} : {}),
}))
return NextResponse.json(ipFormatData)
}
case 'text':
const text = result.data.map((item) => {
const list = [item.host, item.port]
let hostValue: string
if (hostFormat === 'domain') {
hostValue = item.host
}
else {
hostValue = item.ip
}
const list = [hostValue, String(item.port)]
if (item.username && item.password) {
list.push(item.username)
list.push(item.password)

View File

@@ -6,7 +6,7 @@ import {Card, CardContent} from '@/components/ui/card'
import {Form, FormField} from '@/components/ui/form'
import {Label} from '@/components/ui/label'
import {Tabs, TabsList, TabsTrigger} from '@/components/ui/tabs'
import {EyeClosedIcon, EyeIcon} from 'lucide-react'
import {EyeClosedIcon, EyeIcon, HomeIcon} from 'lucide-react'
import {useState, ReactNode, useEffect, Suspense} from 'react'
import zod from 'zod'
import {useForm, useFormContext, useWatch} from 'react-hook-form'
@@ -16,6 +16,7 @@ import {useRouter} from 'next/navigation'
import {login, LoginMode} from '@/actions/auth'
import {useProfileStore} from '@/components/stores/profile'
import dynamic from 'next/dynamic'
import Link from 'next/link'
const smsSchema = zod.object({
username: zod.string().length(11, '请输入正确的手机号码'),
@@ -34,20 +35,12 @@ export type LoginSchema = zod.infer<typeof smsSchema | typeof pwdSchema>
export default function LoginCard() {
const router = useRouter()
const refreshProfile = useProfileStore(store => store.refreshProfile)
const [mode, setMode] = useState<LoginMode>('phone_code')
const [mode, setMode] = useState<LoginMode>('password')
const [submitting, setSubmitting] = useState(false)
const updateLoginMode = (mode: LoginMode) => {
sessionStorage.setItem('login_mode', mode)
}
useEffect(() => {
const mode = sessionStorage.getItem('login_mode')
if (mode) {
setMode(mode as LoginMode)
}
}, [])
const form = useForm<LoginSchema>({
resolver: zodResolver(mode === 'phone_code' ? smsSchema : pwdSchema),
defaultValues: {
@@ -55,7 +48,16 @@ export default function LoginCard() {
password: '',
remember: false,
},
mode: 'onChange',
})
useEffect(() => {
const savedMode = sessionStorage.getItem('login_mode') as LoginMode
if (savedMode && savedMode === 'phone_code') {
setMode(savedMode)
}
}, [])
const handler = form.handleSubmit(async (data) => {
setSubmitting(true)
try {
@@ -87,107 +89,122 @@ export default function LoginCard() {
const [showPwd, setShowPwd] = useState(false)
return (
<Card className="w-96 mx-4 shadow-lg relative z-20 py-8">
<CardContent className="px-8">
{/* 登录方式切换 */}
<Tabs
value={mode}
onValueChange={(val) => {
setMode(val as typeof mode)
form.reset({username: form.getValues('username'), password: '', remember: false})
}}
className="mb-6">
<TabsList className="w-full p-0 bg-white">
<Tab value="password"></Tab>
<Tab value="phone_code"></Tab>
</TabsList>
</Tabs>
<Form<LoginSchema> className="space-y-6" form={form} handler={handler}>
<FormField name="username" label={mode === 'phone_code' ? '手机号' : '用户名'}>
{({id, field}) => (
<Input
{...field}
id={id}
type="tel"
placeholder={mode === 'phone_code' ? '请输入手机号' : '请输入用户名/手机号/邮箱'}
autoComplete="tel-national"
/>
)}
</FormField>
<FormField name="password" label={mode === 'phone_code' ? '验证码' : '密码'}>
{({id, field}) =>
mode === 'phone_code' ? (
<div className="flex space-x-4">
<Input
{...field}
id={id}
className="h-10"
placeholder="请输入验证码"
autoComplete="one-time-code"
/>
<SendMsgByUsername/>
</div>
) : (
<div className="relative">
<Input
{...field}
id={id}
type={showPwd ? 'text' : 'password'}
className="h-10 pr-10"
placeholder="至少6位密码需包含字母和数字"
autoComplete="current-password"
minLength={6}
/>
<button
type="button"
tabIndex={-1}
className="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600"
onClick={() => setShowPwd(v => !v)}
aria-label={showPwd ? '隐藏密码' : '显示密码'}
>
{showPwd ? (
<EyeIcon size={20}/>
) : (
<EyeClosedIcon size={20}/>
)}
</button>
</div>
)
}
</FormField>
<FormField name="remember">
{({id, field}) => (
<div className="flex flex-row items-start space-x-2 space-y-0">
<Checkbox
<div className="relative flex flex-col items-center">
<div className="relative w-96 mx-4">
<Link
href="/"
className="absolute -top-8 right-0 inline-flex items-center text-sm transition-colors px-10"
>
<HomeIcon size={18} className="mr-1"/>
</Link>
</div>
<Card className="w-96 mx-4 shadow-lg relative z-20 py-8">
<CardContent className="px-8">
{/* 登录方式切换 */}
<Tabs
value={mode}
onValueChange={(val) => {
setMode(val as LoginMode)
form.reset({username: '', password: '', remember: false})
form.clearErrors()
}}
className="mb-6">
<TabsList className="w-full p-0 bg-white">
<Tab value="password"></Tab>
<Tab value="phone_code">/</Tab>
</TabsList>
</Tabs>
<Form<LoginSchema> className="space-y-6" form={form} handler={handler}>
<FormField name="username" label={mode === 'phone_code' ? '手机号' : '用户名'}>
{({id, field}) => (
<Input
{...field}
id={id}
checked={field.value}
onCheckedChange={field.onChange}
type="tel"
placeholder={mode === 'phone_code' ? '请输入手机号' : '请输入用户名/手机号/邮箱'}
autoComplete="tel-national"
/>
<div className="space-y-1 leading-none">
<Label></Label>
)}
</FormField>
<FormField name="password" label={mode === 'phone_code' ? '验证码' : '密码'}>
{({id, field}) =>
mode === 'phone_code' ? (
<div className="flex space-x-4">
<Input
{...field}
id={id}
className="h-10"
placeholder="请输入验证码"
autoComplete="one-time-code"
disabled={submitting}
/>
<SendMsgByUsername/>
</div>
) : (
<div className="relative">
<Input
{...field}
id={id}
type={showPwd ? 'text' : 'password'}
className="h-10 pr-10"
placeholder="至少6位密码需包含字母和数字"
autoComplete="current-password"
minLength={6}
disabled={submitting}
/>
<button
type="button"
tabIndex={-1}
className="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 cursor-pointer"
onClick={() => setShowPwd(v => !v)}
aria-label={showPwd ? '隐藏密码' : '显示密码'}
>
{showPwd ? (
<EyeIcon size={20}/>
) : (
<EyeClosedIcon size={20}/>
)}
</button>
</div>
)
}
</FormField>
<FormField name="remember">
{({id, field}) => (
<div className="flex flex-row items-start space-x-2 space-y-0">
<Checkbox
id={id}
checked={field.value}
onCheckedChange={field.onChange}
disabled={submitting}
/>
<div className="space-y-1 leading-none">
<Label></Label>
</div>
</div>
</div>
)}
</FormField>
<div className="flex flex-col gap-3">
<Button
className="w-full h-12 text-lg"
type="submit"
theme="gradient"
disabled={submitting}
>
{submitting ? '登录中...' : (mode === 'phone_code' ? '首次登录即注册' : '立即登录')}
</Button>
<p className="text-xs text-center text-gray-500">
<a href="/userAgreement" className="text-blue-600 hover:text-blue-500"></a>
<a href="/privacyPolicy" className="text-blue-600 hover:text-blue-500"></a>
</p>
</div>
</Form>
</CardContent>
</Card>
)}
</FormField>
<div className="flex flex-col gap-3">
<Button
className="w-full h-12 text-lg"
type="submit"
theme="gradient"
disabled={submitting}
>
{submitting ? '登录中...' : (mode === 'phone_code' ? '首次登录即注册' : '立即登录')}
</Button>
<p className="text-xs text-center text-gray-500">
<a href="/userAgreement" className="text-blue-600 hover:text-blue-500"></a>
<a href="/privacyPolicy" className="text-blue-600 hover:text-blue-500"></a>
</p>
</div>
</Form>
</CardContent>
</Card>
</div>
)
}

View File

@@ -0,0 +1,17 @@
import {ReactNode} from 'react'
import {Metadata} from 'next'
export async function generateMetadata(): Promise<Metadata> {
return {
title: '隐私政策',
description: '蓝狐代理隐私政策 - 了解我们如何收集、使用和保护您的个人信息',
openGraph: {
title: '隐私政策',
description: '蓝狐代理隐私政策 - 了解我们如何收集、使用和保护您的个人信息',
},
}
}
export default function PrivacyPolicyLayout({children}: {children: ReactNode}) {
return children
}

View File

@@ -1,19 +1,19 @@
# 隐私政策
神龙HTTP以下或简称"我们")尊重并保护用户信息,并将以高度的责任感和谨慎的态度对待这些信息。当您使用神龙HTTP提供的代理服务时我们将根据本隐私政策来收集、处理及分享您的信息。我们希望通过本隐私政策向您清晰地说明我们如何处理您的信息。因此我们建议您完整阅读本隐私政策以便了解如何保护您的隐私权。如果您有任何疑问、意见或建议可以通过神龙HTTP提供的联系方式与我们联系。本政策将帮助您了解以下内容
蓝狐HTTP以下或简称"我们")尊重并保护用户信息,并将以高度的责任感和谨慎的态度对待这些信息。当您使用蓝狐HTTP提供的代理服务时我们将根据本隐私政策来收集、处理及分享您的信息。我们希望通过本隐私政策向您清晰地说明我们如何处理您的信息。因此我们建议您完整阅读本隐私政策以便了解如何保护您的隐私权。如果您有任何疑问、意见或建议可以通过蓝狐HTTP提供的联系方式与我们联系。本政策将帮助您了解以下内容
一、适用范围
1.1 本隐私政策适用于神龙HTTP网站提供的所有服务。服务包括提供页面浏览、网站登录服务以及通过神龙HTTP网站提供的网络服务。
1.1 本隐私政策适用于蓝狐HTTP网站提供的所有服务。服务包括提供页面浏览、网站登录服务以及通过蓝狐HTTP网站提供的网络服务。
1.2 本隐私政策不适用于其他第三方提供的服务。
1.3 特别说明,如果您使用神龙HTTP的网络服务为您的用户提供服务您的业务数据归您所有您应当与您的用户另行约定隐私政策。
1.3 特别说明,如果您使用蓝狐HTTP的网络服务为您的用户提供服务您的业务数据归您所有您应当与您的用户另行约定隐私政策。
二、用户信息的收集和使用
2.1 协助您成为我们的会员
2.1.1 当您在神龙HTTP网站创建账户时您需要选择会员身份类型个人、企业并向我们提供会员名、单位名称及联系人姓名用于账户实名认证及开票抬头、设置并确认您的登录密码、可用电子邮箱、所在区域国家、省份、城市以及您在中国境内的手机号码。您提供的手机号码将用于注册、登录、绑定账户、找回密码并作为您与神龙HTTP联系的方式之一接收相关业务通知或进行业务沟通。
2.1.1 当您在蓝狐HTTP网站创建账户时您需要选择会员身份类型个人、企业并向我们提供会员名、单位名称及联系人姓名用于账户实名认证及开票抬头、设置并确认您的登录密码、可用电子邮箱、所在区域国家、省份、城市以及您在中国境内的手机号码。您提供的手机号码将用于注册、登录、绑定账户、找回密码并作为您与蓝狐HTTP联系的方式之一接收相关业务通知或进行业务沟通。
2.1.2 如果您仅需使用浏览、搜索等基本服务,您无需注册成为我们的会员及提供上述信息。
@@ -23,11 +23,11 @@
2.2.2 在您使用服务过程中我们会根据您在注册、登录账号及使用产品时授予的权限接收并记录您所使用的设备相关信息如设备型号、操作系统版本、浏览器cookies、唯一设备标识符等软硬件特征信息、设备所在位置相关信息如IP地址、MAC地址、GPS位置以及能够提供相关信息的Wi-Fi接入点、蓝牙和基站等传感器信息。我们可能会将这些信息与您的账户信息关联以便在不同设备上为您提供一致的服务。
2.2.3 日志信息当您使用我们的网站或代理IP服务时我们会自动收集您对我们服务的详细使用情况作为有关网络日志保存例如您进入神龙HTTP站的搜索查询内容、登录账号、IP地址、浏览器类型、电信运营商、使用的语言、访问日期及代理IP使用中的详细日志信息等
2.2.3 日志信息当您使用我们的网站或代理IP服务时我们会自动收集您对我们服务的详细使用情况作为有关网络日志保存例如您进入蓝狐HTTP站的搜索查询内容、登录账号、IP地址、浏览器类型、电信运营商、使用的语言、访问日期及代理IP使用中的详细日志信息等
2.2.4 请注意,单独的设备信息、日志信息等是无法识别特定自然人身份的信息。如果我们将其与个人信息结合使用,则在结合使用期间,这类非个人信息将被视为个人信息,除取得您授权或法律法规另有规定外,我们会将该类个人信息做匿名化、去标识化处理。
2.2.5 用户账户的支持信息:基于您使用神龙HTTP服务而产生的咨询记录、保障记录和针对用户故障的排障过程如通信或通话记录我们将通过记录、分析这些信息以便更及时响应您的帮助请求以及用于改进服务。
2.2.5 用户账户的支持信息:基于您使用蓝狐HTTP服务而产生的咨询记录、保障记录和针对用户故障的排障过程如通信或通话记录我们将通过记录、分析这些信息以便更及时响应您的帮助请求以及用于改进服务。
2.3 我们出于如下目的使用您提交及我们收集的用户信息:
@@ -37,7 +37,7 @@
2.3.3 在法律法规允许的前提下,向您推荐产品;
2.3.4 为提高您使用我们及我们关联公司、合作伙伴提供服务的安全性,保护您或其他用户或公众的人身财产安全免遭侵害,更好地预防钓鱼网站、欺诈、网络漏洞、计算机病毒、网络攻击、网络侵入等安全风险,更准确地识别违反法律法规或神龙HTTP相关协议、规则的情况我们可能使用您的会员信息、并整合设备信息、有关网络日志以及我们关联公司、合作伙伴分享的信息来进行判断账户及交易风险、进行身份验证、安全事件的检测及防范并依法采取必要的记录、审计、分析、处置措施
2.3.4 为提高您使用我们及我们关联公司、合作伙伴提供服务的安全性,保护您或其他用户或公众的人身财产安全免遭侵害,更好地预防钓鱼网站、欺诈、网络漏洞、计算机病毒、网络攻击、网络侵入等安全风险,更准确地识别违反法律法规或蓝狐HTTP相关协议、规则的情况我们可能使用您的会员信息、并整合设备信息、有关网络日志以及我们关联公司、合作伙伴分享的信息来进行判断账户及交易风险、进行身份验证、安全事件的检测及防范并依法采取必要的记录、审计、分析、处置措施
2.3.5 如超出收集用户信息时所声明的目的或超出具有直接或合理关联的范围后使用用户信息前,我们会再次向您告知并征得您的明确同意。
@@ -53,7 +53,7 @@
3.1.2 在法定情形下的共享:我们可能会根据法律法规规定、诉讼、仲裁解决需要,或按行政、司法机关依法提出的要求,对外共享您的用户信息;
3.1.3 为了促成交易或协助解决争议,某些情况下只有共享您的用户信息,才能促成交易或处理您与他人的纠纷或争议,例如,在神龙HTTP上创建的某一交易中如交易任何一方履行或部分履行了交易义务并提出信息披露请求的神龙HTTP有权决定向该用户提供其交易对方的联络方式等必要信息以促成交易的完成
3.1.3 为了促成交易或协助解决争议,某些情况下只有共享您的用户信息,才能促成交易或处理您与他人的纠纷或争议,例如,在蓝狐HTTP上创建的某一交易中如交易任何一方履行或部分履行了交易义务并提出信息披露请求的蓝狐HTTP有权决定向该用户提供其交易对方的联络方式等必要信息以促成交易的完成
3.2 转让
@@ -69,7 +69,7 @@
3.3.1 获得您明确同意或基于您的主动选择,我们可能会公开披露您的用户信息;
3.3.2 或为保护神龙HTTP平台及其关联公司用户或公众的人身财产安全免遭侵害我们可能依据适用的法律或神龙HTTP平台相关协议、规则披露关于您的用户信息。
3.3.2 或为保护蓝狐HTTP平台及其关联公司用户或公众的人身财产安全免遭侵害我们可能依据适用的法律或蓝狐HTTP平台相关协议、规则披露关于您的用户信息。
3.4 共享、转让、公开披露用户信息时事先征得授权同意的例外
@@ -91,21 +91,21 @@
4.1 用户业务数据
4.1.1 您通过神龙HTTP提供的代理IP服务从事加工、存储、上传、下载、分发以及通过其他方式处理的数据均为您的用户业务数据您完全拥有您的用户业务数据。作为代理IP服务提供商我们只会严格执行您的指示处理您的业务数据除按与您协商一致或执行明确的法律法规要求外我们不会对您的业务数据进行任何非授权的使用或披露。
4.1.1 您通过蓝狐HTTP提供的代理IP服务从事加工、存储、上传、下载、分发以及通过其他方式处理的数据均为您的用户业务数据您完全拥有您的用户业务数据。作为代理IP服务提供商我们只会严格执行您的指示处理您的业务数据除按与您协商一致或执行明确的法律法规要求外我们不会对您的业务数据进行任何非授权的使用或披露。
4.1.2 您应对您的用户业务数据来源及内容负责,我们提醒您谨慎判断数据来源及内容的合法性。因您的用户业务数据内容违反法律法规、部门规章或国家政策而造成的全部结果及责任均由您自行承担。
4.2 公开信息
4.2.1 公开信息是指您公开分享的任何信息,任何人都可以在使用和未使用神龙HTTP网站服务期间查看或访问这些信息。
4.2.1 公开信息是指您公开分享的任何信息,任何人都可以在使用和未使用蓝狐HTTP网站服务期间查看或访问这些信息。
4.2.2 在使用神龙HTTP网站时如您发现自己的个人信息泄露尤其是您的账户或密码发生泄露请您立即联络神龙HTTP客服以便我们采取相应措施。
4.2.2 在使用蓝狐HTTP网站时如您发现自己的个人信息泄露尤其是您的账户或密码发生泄露请您立即联络蓝狐HTTP客服以便我们采取相应措施。
五、用户信息的管理
5.1 您可以登录神龙HTTP网站查看您填写提交的基本业务信息基本资料和联系人等信息。
5.1 您可以登录蓝狐HTTP网站查看您填写提交的基本业务信息基本资料和联系人等信息。
5.2 您在神龙HTTP注册账号并完成实名认证以后为保障您在平台的合法权益我们将不支持任何形式变更实名认证的操作充分有效保障账户的安全与一致性。
5.2 您在蓝狐HTTP注册账号并完成实名认证以后为保障您在平台的合法权益我们将不支持任何形式变更实名认证的操作充分有效保障账户的安全与一致性。
5.3 在以下情形中,您可以向我们提出删除用户信息的请求:
@@ -119,7 +119,7 @@
六、Cookie 和同类技术的使用
6.1 为确保网站正常运转、为您获得更轻松的访问体验、向您推荐您可能感兴趣的内容,我们会在您的计算机或移动设备上存储名为 Cookie 的小数据文件。Cookie 通常包含标识符、站点名称以及一些号码和字符。神龙HTTP只能读取神龙HTTP提供的cookies。
6.1 为确保网站正常运转、为您获得更轻松的访问体验、向您推荐您可能感兴趣的内容,我们会在您的计算机或移动设备上存储名为 Cookie 的小数据文件。Cookie 通常包含标识符、站点名称以及一些号码和字符。蓝狐HTTP只能读取蓝狐HTTP提供的cookies。
七、用户信息的安全
@@ -135,7 +135,7 @@
7.6 我们将收集到的您的用户信息存放在中华人民共和国境内,如在符合适用法律规定的情形下因业务需要向境外传输个人信息的,我们会事先征得您的同意,并向您告知用户信息出境的目的、接收方、安全保障措施、安全风险等情况。
7.7 如出现神龙HTTP产品和服务停止运营的情形我们会采取合理措施保护您用户信息安全包括及时停止继续收集用户信息的活动停止运营的通知将以逐一送达或公告的形式通知用户并对所持有的个人信息进行删除或匿名化处理等。
7.7 如出现蓝狐HTTP产品和服务停止运营的情形我们会采取合理措施保护您用户信息安全包括及时停止继续收集用户信息的活动停止运营的通知将以逐一送达或公告的形式通知用户并对所持有的个人信息进行删除或匿名化处理等。
八、未成年人用户信息的特别约定

View File

@@ -0,0 +1,17 @@
import {ReactNode} from 'react'
import {Metadata} from 'next'
export async function generateMetadata(): Promise<Metadata> {
return {
title: '用户协议',
description: '蓝狐代理用户服务协议 - 使用服务前请仔细阅读用户协议条款',
openGraph: {
title: '用户协议',
description: '蓝狐代理用户服务协议 - 使用服务前请仔细阅读用户协议条款',
},
}
}
export default function UserAgreementLayout({children}: {children: ReactNode}) {
return children
}

View File

@@ -1,19 +1,19 @@
# 用户协议
神龙HTTP以下或简称"我们")尊重并保护用户信息,并将以高度的责任感和谨慎的态度对待这些信息。当您使用神龙HTTP提供的代理服务时我们将根据本隐私政策来收集、处理及分享您的信息。我们希望通过本隐私政策向您清晰地说明我们如何处理您的信息。因此我们建议您完整阅读本隐私政策以便了解如何保护您的隐私权。如果您有任何疑问、意见或建议可以通过神龙HTTP提供的联系方式与我们联系。本政策将帮助您了解以下内容
蓝狐HTTP以下或简称"我们")尊重并保护用户信息,并将以高度的责任感和谨慎的态度对待这些信息。当您使用蓝狐HTTP提供的代理服务时我们将根据本隐私政策来收集、处理及分享您的信息。我们希望通过本隐私政策向您清晰地说明我们如何处理您的信息。因此我们建议您完整阅读本隐私政策以便了解如何保护您的隐私权。如果您有任何疑问、意见或建议可以通过蓝狐HTTP提供的联系方式与我们联系。本政策将帮助您了解以下内容
一、适用范围
1.1 本隐私政策适用于神龙HTTP网站提供的所有服务。服务包括提供页面浏览、网站登录服务以及通过神龙HTTP网站提供的网络服务。
1.1 本隐私政策适用于蓝狐HTTP网站提供的所有服务。服务包括提供页面浏览、网站登录服务以及通过蓝狐HTTP网站提供的网络服务。
1.2 本隐私政策不适用于其他第三方提供的服务。
1.3 特别说明,如果您使用神龙HTTP的网络服务为您的用户提供服务您的业务数据归您所有您应当与您的用户另行约定隐私政策。
1.3 特别说明,如果您使用蓝狐HTTP的网络服务为您的用户提供服务您的业务数据归您所有您应当与您的用户另行约定隐私政策。
二、用户信息的收集和使用
2.1 协助您成为我们的会员
2.1.1 当您在神龙HTTP网站创建账户时您需要选择会员身份类型个人、企业并向我们提供会员名、单位名称及联系人姓名用于账户实名认证及开票抬头、设置并确认您的登录密码、可用电子邮箱、所在区域国家、省份、城市以及您在中国境内的手机号码。您提供的手机号码将用于注册、登录、绑定账户、找回密码并作为您与神龙HTTP联系的方式之一接收相关业务通知或进行业务沟通。
2.1.1 当您在蓝狐HTTP网站创建账户时您需要选择会员身份类型个人、企业并向我们提供会员名、单位名称及联系人姓名用于账户实名认证及开票抬头、设置并确认您的登录密码、可用电子邮箱、所在区域国家、省份、城市以及您在中国境内的手机号码。您提供的手机号码将用于注册、登录、绑定账户、找回密码并作为您与蓝狐HTTP联系的方式之一接收相关业务通知或进行业务沟通。
2.1.2 如果您仅需使用浏览、搜索等基本服务,您无需注册成为我们的会员及提供上述信息。
@@ -23,11 +23,11 @@
2.2.2 在您使用服务过程中我们会根据您在注册、登录账号及使用产品时授予的权限接收并记录您所使用的设备相关信息如设备型号、操作系统版本、浏览器cookies、唯一设备标识符等软硬件特征信息、设备所在位置相关信息如IP地址、MAC地址、GPS位置以及能够提供相关信息的Wi-Fi接入点、蓝牙和基站等传感器信息。我们可能会将这些信息与您的账户信息关联以便在不同设备上为您提供一致的服务。
2.2.3 日志信息当您使用我们的网站或代理IP服务时我们会自动收集您对我们服务的详细使用情况作为有关网络日志保存例如您进入神龙HTTP站的搜索查询内容、登录账号、IP地址、浏览器类型、电信运营商、使用的语言、访问日期及代理IP使用中的详细日志信息等
2.2.3 日志信息当您使用我们的网站或代理IP服务时我们会自动收集您对我们服务的详细使用情况作为有关网络日志保存例如您进入蓝狐HTTP站的搜索查询内容、登录账号、IP地址、浏览器类型、电信运营商、使用的语言、访问日期及代理IP使用中的详细日志信息等
2.2.4 请注意,单独的设备信息、日志信息等是无法识别特定自然人身份的信息。如果我们将其与个人信息结合使用,则在结合使用期间,这类非个人信息将被视为个人信息,除取得您授权或法律法规另有规定外,我们会将该类个人信息做匿名化、去标识化处理。
2.2.5 用户账户的支持信息:基于您使用神龙HTTP服务而产生的咨询记录、保障记录和针对用户故障的排障过程如通信或通话记录我们将通过记录、分析这些信息以便更及时响应您的帮助请求以及用于改进服务。
2.2.5 用户账户的支持信息:基于您使用蓝狐HTTP服务而产生的咨询记录、保障记录和针对用户故障的排障过程如通信或通话记录我们将通过记录、分析这些信息以便更及时响应您的帮助请求以及用于改进服务。
2.3 我们出于如下目的使用您提交及我们收集的用户信息:
@@ -37,7 +37,7 @@
2.3.3 在法律法规允许的前提下,向您推荐产品;
2.3.4 为提高您使用我们及我们关联公司、合作伙伴提供服务的安全性,保护您或其他用户或公众的人身财产安全免遭侵害,更好地预防钓鱼网站、欺诈、网络漏洞、计算机病毒、网络攻击、网络侵入等安全风险,更准确地识别违反法律法规或神龙HTTP相关协议、规则的情况我们可能使用您的会员信息、并整合设备信息、有关网络日志以及我们关联公司、合作伙伴分享的信息来进行判断账户及交易风险、进行身份验证、安全事件的检测及防范并依法采取必要的记录、审计、分析、处置措施
2.3.4 为提高您使用我们及我们关联公司、合作伙伴提供服务的安全性,保护您或其他用户或公众的人身财产安全免遭侵害,更好地预防钓鱼网站、欺诈、网络漏洞、计算机病毒、网络攻击、网络侵入等安全风险,更准确地识别违反法律法规或蓝狐HTTP相关协议、规则的情况我们可能使用您的会员信息、并整合设备信息、有关网络日志以及我们关联公司、合作伙伴分享的信息来进行判断账户及交易风险、进行身份验证、安全事件的检测及防范并依法采取必要的记录、审计、分析、处置措施
2.3.5 如超出收集用户信息时所声明的目的或超出具有直接或合理关联的范围后使用用户信息前,我们会再次向您告知并征得您的明确同意。
@@ -53,7 +53,7 @@
3.1.2 在法定情形下的共享:我们可能会根据法律法规规定、诉讼、仲裁解决需要,或按行政、司法机关依法提出的要求,对外共享您的用户信息;
3.1.3 为了促成交易或协助解决争议,某些情况下只有共享您的用户信息,才能促成交易或处理您与他人的纠纷或争议,例如,在神龙HTTP上创建的某一交易中如交易任何一方履行或部分履行了交易义务并提出信息披露请求的神龙HTTP有权决定向该用户提供其交易对方的联络方式等必要信息以促成交易的完成
3.1.3 为了促成交易或协助解决争议,某些情况下只有共享您的用户信息,才能促成交易或处理您与他人的纠纷或争议,例如,在蓝狐HTTP上创建的某一交易中如交易任何一方履行或部分履行了交易义务并提出信息披露请求的蓝狐HTTP有权决定向该用户提供其交易对方的联络方式等必要信息以促成交易的完成
3.2 转让
@@ -69,7 +69,7 @@
3.3.1 获得您明确同意或基于您的主动选择,我们可能会公开披露您的用户信息;
3.3.2 或为保护神龙HTTP平台及其关联公司用户或公众的人身财产安全免遭侵害我们可能依据适用的法律或神龙HTTP平台相关协议、规则披露关于您的用户信息。
3.3.2 或为保护蓝狐HTTP平台及其关联公司用户或公众的人身财产安全免遭侵害我们可能依据适用的法律或蓝狐HTTP平台相关协议、规则披露关于您的用户信息。
3.4 共享、转让、公开披露用户信息时事先征得授权同意的例外
@@ -91,21 +91,21 @@
4.1 用户业务数据
4.1.1 您通过神龙HTTP提供的代理IP服务从事加工、存储、上传、下载、分发以及通过其他方式处理的数据均为您的用户业务数据您完全拥有您的用户业务数据。作为代理IP服务提供商我们只会严格执行您的指示处理您的业务数据除按与您协商一致或执行明确的法律法规要求外我们不会对您的业务数据进行任何非授权的使用或披露。
4.1.1 您通过蓝狐HTTP提供的代理IP服务从事加工、存储、上传、下载、分发以及通过其他方式处理的数据均为您的用户业务数据您完全拥有您的用户业务数据。作为代理IP服务提供商我们只会严格执行您的指示处理您的业务数据除按与您协商一致或执行明确的法律法规要求外我们不会对您的业务数据进行任何非授权的使用或披露。
4.1.2 您应对您的用户业务数据来源及内容负责,我们提醒您谨慎判断数据来源及内容的合法性。因您的用户业务数据内容违反法律法规、部门规章或国家政策而造成的全部结果及责任均由您自行承担。
4.2 公开信息
4.2.1 公开信息是指您公开分享的任何信息,任何人都可以在使用和未使用神龙HTTP网站服务期间查看或访问这些信息。
4.2.1 公开信息是指您公开分享的任何信息,任何人都可以在使用和未使用蓝狐HTTP网站服务期间查看或访问这些信息。
4.2.2 在使用神龙HTTP网站时如您发现自己的个人信息泄露尤其是您的账户或密码发生泄露请您立即联络神龙HTTP客服以便我们采取相应措施。
4.2.2 在使用蓝狐HTTP网站时如您发现自己的个人信息泄露尤其是您的账户或密码发生泄露请您立即联络蓝狐HTTP客服以便我们采取相应措施。
五、用户信息的管理
5.1 您可以登录神龙HTTP网站查看您填写提交的基本业务信息基本资料和联系人等信息。
5.1 您可以登录蓝狐HTTP网站查看您填写提交的基本业务信息基本资料和联系人等信息。
5.2 您在神龙HTTP注册账号并完成实名认证以后为保障您在平台的合法权益我们将不支持任何形式变更实名认证的操作充分有效保障账户的安全与一致性。
5.2 您在蓝狐HTTP注册账号并完成实名认证以后为保障您在平台的合法权益我们将不支持任何形式变更实名认证的操作充分有效保障账户的安全与一致性。
5.3 在以下情形中,您可以向我们提出删除用户信息的请求:
@@ -119,7 +119,7 @@
六、Cookie 和同类技术的使用
6.1 为确保网站正常运转、为您获得更轻松的访问体验、向您推荐您可能感兴趣的内容,我们会在您的计算机或移动设备上存储名为 Cookie 的小数据文件。Cookie 通常包含标识符、站点名称以及一些号码和字符。神龙HTTP只能读取神龙HTTP提供的cookies。
6.1 为确保网站正常运转、为您获得更轻松的访问体验、向您推荐您可能感兴趣的内容,我们会在您的计算机或移动设备上存储名为 Cookie 的小数据文件。Cookie 通常包含标识符、站点名称以及一些号码和字符。蓝狐HTTP只能读取蓝狐HTTP提供的cookies。
七、用户信息的安全
@@ -135,7 +135,7 @@
7.6 我们将收集到的您的用户信息存放在中华人民共和国境内,如在符合适用法律规定的情形下因业务需要向境外传输个人信息的,我们会事先征得您的同意,并向您告知用户信息出境的目的、接收方、安全保障措施、安全风险等情况。
7.7 如出现神龙HTTP产品和服务停止运营的情形我们会采取合理措施保护您用户信息安全包括及时停止继续收集用户信息的活动停止运营的通知将以逐一送达或公告的形式通知用户并对所持有的个人信息进行删除或匿名化处理等。
7.7 如出现蓝狐HTTP产品和服务停止运营的情形我们会采取合理措施保护您用户信息安全包括及时停止继续收集用户信息的活动停止运营的通知将以逐一送达或公告的形式通知用户并对所持有的个人信息进行删除或匿名化处理等。
八、未成年人用户信息的特别约定

View File

Before

Width:  |  Height:  |  Size: 186 KiB

After

Width:  |  Height:  |  Size: 186 KiB

View File

Before

Width:  |  Height:  |  Size: 169 KiB

After

Width:  |  Height:  |  Size: 169 KiB

View File

Before

Width:  |  Height:  |  Size: 176 KiB

After

Width:  |  Height:  |  Size: 176 KiB

View File

Before

Width:  |  Height:  |  Size: 142 KiB

After

Width:  |  Height:  |  Size: 142 KiB

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

Before

Width:  |  Height:  |  Size: 540 KiB

After

Width:  |  Height:  |  Size: 540 KiB

View File

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 97 KiB

View File

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View File

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 110 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

View File

@@ -0,0 +1,50 @@
import Image, {StaticImageData} from 'next/image'
import {PageSection} from './page-section'
import s4_1_main from './_assets/s4-1-main.webp'
import s4_2_main from './_assets/s4-2-main.webp'
import s4_1_1 from './_assets/s4-1-1.webp'
import s4_1_2 from './_assets/s4-1-2.webp'
import s4_1_3 from './_assets/s4-1-3.webp'
import s4_2_1 from './_assets/s4-2-1.webp'
import s4_2_2 from './_assets/s4-2-2.webp'
import s4_2_3 from './_assets/s4-2-3.webp'
export function AdvantagesSection() {
return (
<PageSection title="HTTP 产品优势">
<div className="flex gap-36">
<ul className="flex-1 flex flex-col gap-6">
<AdvantageItem icon={s4_1_1} title="安全合规" description="国内三大运营商支持"/>
<AdvantageItem icon={s4_1_2} title="稳定链接" description="IP纯净度高达99.9%"/>
<AdvantageItem icon={s4_1_3} title="超匿名性" description="稳定传输,保护隐私安全"/>
</ul>
<Image src={s4_1_main} alt="产品优势展示" className="w-0 flex-1 object-contain max-lg:hidden"/>
</div>
<div className="flex gap-36">
<Image src={s4_2_main} alt="技术优势展示" className="w-0 flex-1 object-contain max-lg:hidden"/>
<ul className="flex-1 flex flex-col gap-6">
<AdvantageItem icon={s4_2_1} title="API接口文档" description="与第三方软件轻松集成"/>
<AdvantageItem icon={s4_2_2} title="多种编程语言代码" description="C语言、GO语言、Python..."/>
<AdvantageItem icon={s4_2_3} title="双重认证方式" description="API提取+账密认证"/>
</ul>
</div>
</PageSection>
)
}
function AdvantageItem(props: {
icon: StaticImageData
title: string
description: string
}) {
return (
<li className="flex gap-8 items-center p-4 lg:p-8 shadow-[4px_4px_20px_4px] shadow-blue-50 rounded-lg">
<Image src={props.icon} alt={props.title} aria-hidden className="w-24 h-24 object-contain"/>
<div className="flex flex-col gap-3">
<h3 className="text-xl">{props.title}</h3>
<p>{props.description}</p>
</div>
</li>
)
}

View File

@@ -0,0 +1,70 @@
import {ReactNode} from 'react'
import Link from 'next/link'
import {PageSection} from './page-section'
import {BookOpen, Smartphone, HelpCircle} from 'lucide-react'
export function ArticlesSection() {
return (
<PageSection title="推荐文章">
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
<ArticleCard
icon={<BookOpen className="w-12 h-12"/>}
title="浏览器设置代理教程"
description="快速上手5分钟学会在浏览器中配置代理服务器"
href="/docs/browser-proxy"
/>
<ArticleCard
icon={<Smartphone className="w-12 h-12"/>}
title="Windows10 代理配置"
description="详细图文教程,帮助你在 Windows 系统中设置代理"
href="/docs/windows10-proxy"
/>
<ArticleCard
icon={<HelpCircle className="w-12 h-12"/>}
title="常见问题总览"
description="解决使用过程中遇到的各类问题,快速找到答案"
href="/docs/faq-general"
/>
</div>
</PageSection>
)
}
function ArticleCard(props: {
icon: ReactNode
title: string
description: string
href: string
}) {
return (
<Link
href={props.href}
className={[
`group block p-8 shadow-[4px_4px_20px_4px] shadow-blue-50 rounded-lg bg-white`,
`transition-all duration-200`,
].join(' ')}
>
<div className="flex flex-col items-center text-center gap-6">
<div className="p-4 rounded-xl bg-linear-to-br from-blue-500 to-cyan-400 text-white group-hover:scale-110 transition-transform">
{props.icon}
</div>
<div>
<h3 className="text-xl font-semibold mb-3 group-hover:text-blue-600 transition-colors">
{props.title}
</h3>
<p className="text-gray-500 text-sm leading-relaxed">
{props.description}
</p>
</div>
<div className="mt-2 text-blue-500 text-sm flex items-center gap-2 group-hover:gap-3 transition-all">
<span></span>
<span className="text-lg"></span>
</div>
</div>
</Link>
)
}

View File

@@ -0,0 +1,37 @@
import Image from 'next/image'
import Wrap from '@/components/wrap'
import FreeTrial from '@/components/free-trial'
import banner from '../_assets/banner.webp'
import check_main from '@/assets/check-main.svg'
export function HeroSection() {
return (
<section className="w-full relative">
<Image src={banner} alt="banner" className="absolute inset-0 h-full object-cover"/>
<Wrap className="relative pt-64 pb-48 max-md:pt-32 max-md:pb-24">
<h1 className="text-4xl"></h1>
<p className="mt-10 text-gray-500">IP代理服务</p>
<div className="mt-24 max-md:mt-14 flex gap-8 max-md:flex-col">
<p className="flex gap-4 items-center">
<Image src={check_main} alt="checkbox" width={24} height={24}/>
<span className="lg:text-lg">200+</span>
</p>
<p className="flex gap-4 items-center">
<Image src={check_main} alt="checkbox" width={24} height={24}/>
<span className="lg:text-lg">300+</span>
</p>
<p className="flex gap-4 items-center">
<Image src={check_main} alt="checkbox" width={24} height={24}/>
<span className="lg:text-lg">&</span>
</p>
</div>
<FreeTrial className={[
`mt-32 max-md:mt-20 w-96 max-md:w-full h-16 md:h-24 rounded-lg shadow-lg cursor-pointer`,
`bg-linear-to-r from-blue-500 to-cyan-400 text-white text-xl lg:text-4xl cursor-pointer`,
].join(' ')}/>
</Wrap>
</section>
)
}

View File

@@ -0,0 +1,15 @@
import {ReactNode} from 'react'
export function PageSection(props: {
title: string
children: ReactNode
}) {
return (
<section>
<div className="max-w-[1232px] mx-auto px-4 flex flex-col items-stretch">
<h2 className="text-center text-3xl mb-8 lg:mb-24">{props.title}</h2>
{props.children}
</div>
</section>
)
}

View File

@@ -0,0 +1,42 @@
import {Metadata} from 'next'
import {siteConfig} from '@/config/site'
import {HeroSection} from './hero-section'
import {StatsSection} from './stats-section'
import {ProductTypesSection} from './product-types-section'
import {AdvantagesSection} from './advantages-section'
import {ArticlesSection} from './articles-section'
export async function generateMetadata(): Promise<Metadata> {
return {
title: siteConfig.name,
description: siteConfig.description,
openGraph: {
title: siteConfig.name,
description: siteConfig.description,
url: siteConfig.url,
images: [
{
url: siteConfig.ogImage.url,
width: siteConfig.ogImage.width,
height: siteConfig.ogImage.height,
alt: siteConfig.name,
},
],
},
alternates: {
canonical: siteConfig.url,
},
}
}
export default function Home() {
return (
<main className="flex flex-col gap-16 lg:gap-32 pb-16 lg:pb-32 bg-white">
<HeroSection/>
<StatsSection/>
<ProductTypesSection/>
<AdvantagesSection/>
<ArticlesSection/>
</main>
)
}

View File

@@ -0,0 +1,78 @@
import Image, {StaticImageData} from 'next/image'
import {PageSection} from './page-section'
import s1_1 from './_assets/s1-1.webp'
import s1_2 from './_assets/s1-2.webp'
import s1_3 from './_assets/s1-3.webp'
import s1_4 from './_assets/s1-4.webp'
import s1_check from './_assets/s1-check.svg'
export function ProductTypesSection() {
return (
<PageSection title="HTTP安全合规的代理IP资源池">
<ul className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
<ProductTypeCard
icon={s1_1}
title="短期动态IP池"
features={[
{icon: s1_check, text: 'IP时效3-30分钟(可定制)'},
{icon: s1_check, text: '支持高并发提取'},
]}
/>
<ProductTypeCard
icon={s1_2}
title="长期静态IP池"
features={[
{icon: s1_check, text: 'IP覆盖全国各地'},
{icon: s1_check, text: '平均响应时长0.03s'},
]}
/>
<ProductTypeCard
icon={s1_3}
title="固定IP池"
features={[
{icon: s1_check, text: '稳定长输不掉线'},
{icon: s1_check, text: '全国热门静态IP线路'},
]}
/>
<ProductTypeCard
icon={s1_4}
title="企业级定制池"
features={[
{icon: s1_check, text: '可视化监控设计'},
{icon: s1_check, text: '技术团队现场支持'},
]}
/>
</ul>
</PageSection>
)
}
function ProductTypeCard(props: {
icon: StaticImageData
title: string
features: {
icon: StaticImageData
text: string
}[]
}) {
return (
<li
className={[
`p-8 flex flex-col gap-5 shadow-[4px_4px_20px_4px] shadow-blue-50 bg-white rounded-lg`,
`max-md:items-center`,
].join(' ')}>
<Image src={props.icon} alt={props.title} aria-hidden className="self-center w-44 h-44 object-cover"/>
<h3 className="text-xl">{props.title}</h3>
<div className="flex flex-col gap-3">
{props.features.map((item, index) => {
return (
<p key={index} className="text-sm text-gray-500 flex gap-3 items-center">
<Image src={item.icon} alt="check" aria-hidden className="w-5 h-5"/>
<span>{item.text}</span>
</p>
)
})}
</div>
</li>
)
}

View File

@@ -0,0 +1,32 @@
import Image from 'next/image'
import {PageSection} from './page-section'
import map from '../_assets/map.webp'
export function StatsSection() {
return (
<PageSection title="覆盖全国的IP资源及超大的带宽线路">
<ul className="shadow-[0_0_20px_4px] shadow-blue-50 p-8 flex max-lg:flex-col">
<li className="flex-1 flex flex-col items-center justify-center lg:border-r max-lg:mb-4 border-gray-200">
<p className="text-xl">线</p>
<p className="mt-9 max-lg:mt-2 text-5xl bg-linear-to-t from-blue-500 to-cyan-400 bg-clip-text text-transparent font-bold pb-2 -mb-2">350+</p>
<div className="lg:hidden w-24 border-b mt-4 border-gray-200"></div>
</li>
<li className="flex-1 flex flex-col items-center justify-center lg:border-r max-lg:mb-4 border-gray-200">
<p className="text-xl">IP数量</p>
<p className="mt-9 max-lg:mt-2 text-5xl bg-linear-to-t from-blue-500 to-cyan-400 bg-clip-text text-transparent font-bold pb-2 -mb-2">1,350,129</p>
<div className="lg:hidden w-24 border-b mt-4 border-gray-200"></div>
</li>
<li className="flex-1 flex flex-col items-center justify-center lg:border-r max-lg:mb-4 border-gray-200">
<p className="text-xl"></p>
<p className="mt-9 max-lg:mt-2 text-5xl bg-linear-to-t from-blue-500 to-cyan-400 bg-clip-text text-transparent font-bold pb-2 -mb-2">26,578</p>
<div className="lg:hidden w-24 border-b mt-4 border-gray-200"></div>
</li>
<li className="flex-1 flex flex-col items-center justify-center">
<p className="text-xl">IP可用率</p>
<p className="mt-9 max-lg:mt-2 text-5xl bg-linear-to-t from-blue-500 to-cyan-400 bg-clip-text text-transparent font-bold pb-2 -mb-2">99%</p>
</li>
</ul>
<Image src={map} alt="map" className="w-[1200px]"/>
</PageSection>
)
}

View File

@@ -17,12 +17,21 @@ export default function HelpMenu() {
icon={h01}
title="提取 IP"
items={[
{lead: '短效 IP 提取', href: '/collect?type=short'},
{lead: '长效 IP 提取', href: '/collect?type=long'},
{lead: '短效/长效 IP 提取', href: '/collect?type=short'},
// {lead: '长效 IP 提取', href: '/collect?type=long'},
]}
/>
<Column
icon={h02}
title="操作指南"
items={[
{lead: '修改信息', href: '/docs/profile-settings'},
{lead: '提取链接', href: '/docs/extract-link'},
{lead: '查看记录', href: '/docs/payment-records'},
]}
/>
<Column
icon={h03}
title="平台教程"
items={[
{lead: 'iOS 设置', href: '/docs/ios-proxy'},
@@ -30,15 +39,6 @@ export default function HelpMenu() {
{lead: 'Windows 设置', href: '/docs/windows10-proxy'},
]}
/>
<Column
icon={h03}
title="高级功能"
items={[
{lead: '套餐管理', href: '/docs/package-operations'},
{lead: 'Socks5 教程', href: '/docs/socks5-usage'},
{lead: '固定 IP 套餐', href: '/docs/fixed-package'},
]}
/>
<Image src={banner} alt="banner" className="hidden lg:block"/>
</Wrap>
)

View File

@@ -1,31 +1,313 @@
import ProductMenu from './menu-product'
import HelpMenu from './menu-help'
import SolutionMenu from './menu-solution'
'use client'
import {useContext, useEffect, useState} from 'react'
import {useRouter} from 'next/navigation'
import {X} from 'lucide-react'
import {HeaderContext} from './common'
import Image, {StaticImageData} from 'next/image'
import prod from '@/assets/header/product/prod.svg'
import custom from '@/assets/header/product/custom.svg'
import s01 from '@/assets/header/solution/01.svg'
import s02 from '@/assets/header/solution/02.svg'
import s03 from '@/assets/header/solution/03.svg'
import s04 from '@/assets/header/solution/04.svg'
import s05 from '@/assets/header/solution/05.svg'
import s06 from '@/assets/header/solution/06.svg'
import s07 from '@/assets/header/solution/07.svg'
import s08 from '@/assets/header/solution/08.svg'
import h01 from '@/assets/header/help/01.svg'
import h02 from '@/assets/header/help/02.svg'
import h03 from '@/assets/header/help/03.svg'
import {merge} from '@/lib/utils'
import Link from 'next/link'
import logo from '@/assets/logo.webp'
import {Product} from '@/lib/models/product'
import {listProductHome} from '@/actions/product'
export type MobileMenuProps = {}
export default function MobileMenu(props: MobileMenuProps) {
const ctx = useContext(HeaderContext)
const router = useRouter()
const [productTab, setProductTab] = useState<'domestic' | 'oversea'>('domestic')
if (!ctx) {
throw new Error(`HeaderContext not found`)
}
const navigate = (href: string) => {
ctx.setMenu(false)
router.push(href)
}
const [productList, setProductList] = useState<Product[]>([])
useEffect(() => {
const fetchProducts = async () => {
const res = await listProductHome({})
if (res.success) {
setProductList(res.data)
}
}
fetchProducts()
}, [])
const shortProduct = productList.find(p => p.name?.includes('短效') || p.code === 'short')
const longProduct = productList.find(p => p.name?.includes('长效') || p.code === 'long')
return (
<div className="flex flex-col gap-8">
<div className="flex flex-col gap-4">
<ProductMenu/>
<div className="h-full flex flex-col bg-white">
<div className="flex items-center justify-between px-4 h-16 border-b border-gray-100">
{/* logo */}
<Link href="/" className="flex items-center">
<Image src={logo} alt="logo" height={40} className="translate-y-0.5"/>
</Link>
<button
type="button"
className="rounded-md p-2 text-gray-400 hover:text-gray-600 hover:bg-gray-50 transition-colors"
onClick={() => ctx.setMenu(false)}
aria-label="关闭菜单"
>
<X className="h-5 w-5"/>
</button>
</div>
<div className="flex flex-col gap-4">
<MenuTitle title="帮助中心"/>
<HelpMenu/>
</div>
<div className="flex flex-col gap-4">
<MenuTitle title="业务场景"/>
<SolutionMenu/>
<div className="flex-1 overflow-y-auto px-4 py-6 space-y-8">
<div className="space-y-3">
<h3 className="text-sm font-semibold text-gray-500 tracking-wide">
</h3>
<div className="flex rounded-lg bg-gray-100">
<button
className={merge(
'flex-1 py-2.5 text-sm font-medium rounded-md transition-all',
productTab === 'domestic'
? 'bg-white text-blue-600 shadow-sm'
: 'text-gray-600 hover:text-gray-900',
)}
onClick={() => setProductTab('domestic')}
>
</button>
<button
className={merge(
'flex-1 py-2.5 text-sm font-medium rounded-md transition-all',
productTab === 'oversea'
? 'bg-white text-blue-600 shadow-sm'
: 'text-gray-600 hover:text-gray-900',
)}
onClick={() => setProductTab('oversea')}
>
</button>
</div>
{productTab === 'domestic' && (
<div className="space-y-2">
{shortProduct && (
<ProductItem
icon={prod}
label="短效动态IP"
badge="最低4.5折"
href={`/product?type=${shortProduct.code}`}
onNavigate={navigate}
/>
)}
{longProduct && (
<ProductItem
icon={prod}
label="长效静态IP"
badge="最低4.5折"
href={`/product?type=${longProduct.code}`}
onNavigate={navigate}
/>
)}
<ProductItem
icon={custom}
label="优质/企业/精选IP"
badge="专属定制"
href="/custom"
onNavigate={navigate}
/>
</div>
)}
{productTab === 'oversea' && (
<div className="mt-4 p-4 bg-blue-50 rounded-lg">
<p className="text-sm text-blue-600 text-center">
线
</p>
</div>
)}
</div>
<MenuSection title="业务场景">
<div className="grid grid-cols-2 gap-3">
<SolutionItem
icon={s01}
label="数据采集"
href="/data-capture"
onNavigate={navigate}
/>
<SolutionItem
icon={s02}
label="电商运营"
href="/e-commerce"
onNavigate={navigate}
/>
<SolutionItem
icon={s03}
label="市场调研"
href="/market-research"
onNavigate={navigate}
/>
<SolutionItem
icon={s04}
label="SEO优化"
href="/seo-optimization"
onNavigate={navigate}
/>
<SolutionItem
icon={s05}
label="社交媒体"
href="/social-media"
onNavigate={navigate}
/>
<SolutionItem
icon={s06}
label="广告投放"
href="/advertising"
onNavigate={navigate}
/>
<SolutionItem
icon={s07}
label="账号管理"
href="/account-management"
onNavigate={navigate}
/>
<SolutionItem
icon={s08}
label="网络测试"
href="/network-testing"
onNavigate={navigate}
/>
</div>
</MenuSection>
<MenuSection title="帮助中心">
<div className="space-y-2">
<HelpItem
icon={h01}
label="短效IP提取"
onClick={() => navigate('/collect?type=short')}
/>
<HelpItem
icon={h02}
label="操作指南"
onClick={() => navigate('/docs/profile-settings')}
/>
<HelpItem
icon={h03}
label="平台教程"
onClick={() => navigate('/docs/ios-proxy')}
/>
</div>
</MenuSection>
<div className="space-y-2 pt-2">
<OtherLink
label="业务定制"
href="/custom"
onNavigate={navigate}
/>
</div>
</div>
</div>
)
}
function MenuTitle(props: {title: string}) {
function MenuSection(props: {title: string, children: React.ReactNode}) {
return (
<h3 className="text-xl text-weak px-4">
{props.title}
</h3>
<div className="space-y-3">
<h3 className="text-sm font-semibold text-gray-500 tracking-wide">
{props.title}
</h3>
{props.children}
</div>
)
}
function ProductItem(props: {
icon: StaticImageData
label: string
badge?: string
href: string
onNavigate: (href: string) => void
}) {
return (
<button
type="button"
className="w-full flex items-center gap-3 rounded-lg border border-gray-100 bg-white px-4 py-3 text-left transition-all hover:border-blue-200 hover:shadow-sm"
onClick={() => props.onNavigate(props.href)}
>
<div className="shrink-0 w-8 h-8 bg-linear-to-br from-blue-50 to-cyan-50 rounded-lg flex items-center justify-center">
<Image src={props.icon} alt="" width={20} height={20} className="opacity-80"/>
</div>
<span className="flex-1 font-medium text-sm text-gray-900">{props.label}</span>
{props.badge && (
<span className="text-xs text-orange-600 bg-orange-50 px-2 py-1 rounded-full">
{props.badge}
</span>
)}
</button>
)
}
function SolutionItem(props: {
icon: StaticImageData
label: string
href: string
onNavigate: (href: string) => void
}) {
return (
<button
type="button"
className="flex flex-col items-center gap-2 p-3 rounded-lg border border-gray-100 hover:border-blue-200 hover:bg-blue-50/50 transition-all"
onClick={() => props.onNavigate(props.href)}
>
<div className="w-10 h-10 bg-linear-to-br from-blue-50 to-cyan-50 rounded-full flex items-center justify-center">
<Image src={props.icon} alt="" width={20} height={20} className="opacity-80"/>
</div>
<span className="text-xs font-medium text-gray-700">{props.label}</span>
</button>
)
}
function HelpItem(props: {
icon: StaticImageData
label: string
onClick: () => void
}) {
return (
<button
type="button"
className="w-full flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-gray-50 transition-colors"
onClick={props.onClick}
>
<Image src={props.icon} alt="" width={20} height={20} className="opacity-70"/>
<span className="text-sm text-gray-700">{props.label}</span>
</button>
)
}
function OtherLink(props: {
label: string
href: string
onNavigate: (href: string) => void
}) {
return (
<button
type="button"
className="w-full flex items-center px-3 py-2.5 text-sm text-gray-700 rounded-lg hover:bg-gray-50 transition-colors"
onClick={() => props.onNavigate(props.href)}
>
{props.label}
</button>
)
}

View File

@@ -1,13 +1,17 @@
'use client'
import {ReactNode, useContext, useState} from 'react'
import {ReactNode, useContext, useEffect, useState} from 'react'
import Wrap from '@/components/wrap'
import Image, {StaticImageData} from 'next/image'
import Link from 'next/link'
import {merge} from '@/lib/utils'
import prod from '@/assets/header/product/prod.svg'
import custom from '@/assets/header/product/custom.svg'
import {useRouter} from 'next/navigation'
import {FragmentTitle, HeaderContext} from './common'
import {HeaderContext} from './common'
import {Product} from '@/lib/models/product'
import {listProductHome} from '@/actions/product'
export type ProductItem = Product
type TabType = 'domestic' | 'oversea'
export default function ProductMenu() {
@@ -52,33 +56,51 @@ export function Tab(props: {
}
export function Domestic(props: {}) {
const [productList, setProductList] = useState<Product[]>([])
useEffect(() => {
const fetchProducts = async () => {
const res = await listProductHome({})
if (res.success) {
setProductList(res.data)
}
}
fetchProducts()
}, [])
const shortProduct = productList.find(p => p.name?.includes('短效') || p.code === 'short')
const longProduct = productList.find(p => p.name?.includes('长效') || p.code === 'long')
return (
<section role="tabpanel" className="flex-auto flex flex-col lg:flex-row justify-evenly gap-3 lg:gap-0">
<div className="w-full lg:w-64 flex flex-col">
<FragmentTitle img={prod} text="短效 IP"/>
<DomesticLink
label="短效动态 IP"
desc="全国 300+ 城市级定位节点IP 池资源充足自动高频切换。适用于数据采集、市场调研、SEO 优化等高并发场景。稳定可靠,响应迅速,助力业务高效运转。"
href="/product?type=short"
discount={45}
/>
</div>
<div className="w-full lg:w-64 flex flex-col">
<FragmentTitle img={prod} text="长效 IP"/>
<DomesticLink
label="长效动态 IP"
desc="IP 存活时长可达数小时至数天,连接稳定不掉线。适用于账号养号、社交运营、电商管理等需要持续在线的场景。优质线路保障,为您的长期业务保驾护航。"
href="/product?type=long"
discount={45}
/>
</div>
<div className="w-full lg:w-64 flex flex-col">
<FragmentTitle img={custom} text="业务定制"/>
<DomesticLink
label="优质/企业/精选IP"
desc="超 1000 家企业共同信赖之选!大客户经理全程 1 对 1 沟通,随时为您排忧解难,提供 24 小时不间断支持"
href="/custom"
/>
<section role="tabpanel" className="flex-auto">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-3">
<div className="grid grid-cols-1 gap-3">
{shortProduct && (
<ProductCard
icon={prod}
label="短效动态 IP"
discount="最低4.5折"
desc="全国 300+ 城市级定位节点IP 池资源充足自动高频切换。适用于数据采集、市场调研、SEO 优化等高并发场景。稳定可靠,响应迅速,助力业务高效运转。"
href={`/product?type=${shortProduct.code}`}
/>
)}
{longProduct && (
<ProductCard
icon={prod}
label="长效动态 IP"
discount="最低4.5折"
desc="IP 存活时长可达数小时至数天,连接稳定不掉线。适用于账号养号、社交运营、电商管理等需要持续在线的场景。优质线路保障,为您的长期业务保驾护航。"
href={`/product?type=${longProduct.code}`}
/>
)}
</div>
<div className="flex flex-col gap-3">
<ProductCard
icon={custom}
label="业务定制"
discount="1V1 专属服务"
desc="超 1000 家企业共同信赖之选!大客户经理全程 1 对 1 沟通,随时为您排忧解难,提供 24 小时不间断支持"
href="/custom"
/>
</div>
</div>
</section>
)
@@ -92,11 +114,12 @@ export function Oversea(props: {}) {
)
}
export function DomesticLink(props: {
export function ProductCard(props: {
icon: StaticImageData
label: string
discount: string
desc: string
href: string
discount?: number
}) {
const router = useRouter()
const ctx = useContext(HeaderContext)
@@ -116,18 +139,24 @@ export function DomesticLink(props: {
`transition-colors duration-150 ease-in-out`,
`p-4 rounded-lg flex flex-col gap-1 hover:bg-blue-50`,
)}
onClick={onClick}>
<p className="flex gap-2">
<span>{props.label}</span>
{props.discount && (
<span className="text-orange-500 text-xs text-light px-2 py-1 bg-orange-50 rounded-full">
{props.discount}%
</span>
)}
</p>
<p className="text-gray-400 text-sm">
{props.desc}
</p>
onClick={onClick}
>
<div className="flex items-start gap-3">
<div className="flex-none">
<Image src={props.icon} alt="" width={30} height={30}/>
</div>
<div className="flex-1">
<div className="flex items-center justify-between gap-3">
<span className="font-bold">{props.label}</span>
<span className="text-xs font-medium text-orange-600 bg-orange-50 px-2 py-1 rounded-full">
{props.discount}
</span>
</div>
<div className="mt-2 text-sm text-gray-400 space-y-1">
{props.desc}
</div>
</div>
</div>
</Link>
)
}

View File

@@ -1,4 +1,6 @@
import {Metadata} from 'next'
import ScenePage, {ScenePageConfig} from '@/components/scene-page'
import {siteConfig} from '@/config/site'
import bannerImg from './_assets/banner.webp'
import solutionImg from './_assets/solution-main.webp'
import value1Img from './_assets/value-1.webp'
@@ -46,6 +48,28 @@ const config: ScenePageConfig = {
},
}
export async function generateMetadata(): Promise<Metadata> {
return {
title: config.banner.title,
description: config.banner.description,
openGraph: {
title: config.banner.title,
description: config.banner.description,
images: [
{
url: siteConfig.ogImage.url,
width: siteConfig.ogImage.width,
height: siteConfig.ogImage.height,
alt: config.banner.title,
},
],
},
alternates: {
canonical: `${siteConfig.url}/account-management`,
},
}
}
export default function AccountManagementPage() {
return <ScenePage {...config}/>
}

View File

@@ -1,4 +1,6 @@
import {Metadata} from 'next'
import ScenePage, {ScenePageConfig} from '@/components/scene-page'
import {siteConfig} from '@/config/site'
import bannerImg from './_assets/banner.webp'
import solutionImg from './_assets/solution-main.webp'
import value1Img from './_assets/value-1.webp'
@@ -46,6 +48,28 @@ const config: ScenePageConfig = {
},
}
export async function generateMetadata(): Promise<Metadata> {
return {
title: config.banner.title,
description: config.banner.description,
openGraph: {
title: config.banner.title,
description: config.banner.description,
images: [
{
url: siteConfig.ogImage.url,
width: siteConfig.ogImage.width,
height: siteConfig.ogImage.height,
alt: config.banner.title,
},
],
},
alternates: {
canonical: `${siteConfig.url}/advertising`,
},
}
}
export default function AdvertisingPage() {
return <ScenePage {...config}/>
}

View File

@@ -1,3 +1,5 @@
import {Metadata} from 'next'
import {siteConfig} from '@/config/site'
import BreadCrumb from '@/components/bread-crumb'
import Wrap from '@/components/wrap'
import Extract from '@/components/composites/extract'
@@ -5,6 +7,28 @@ import HomePage from '@/components/home/page'
export type CollectPageProps = {}
export async function generateMetadata(): Promise<Metadata> {
return {
title: 'IP提取',
description: '短效/长效IP提取高可用性代理IP支持API调用即时获取全国各地代理IP适用于数据采集、网络测试等场景',
openGraph: {
title: 'IP提取',
description: '短效/长效IP提取高可用性代理IP支持API调用即时获取全国各地代理IP',
images: [
{
url: siteConfig.ogImage.url,
width: siteConfig.ogImage.width,
height: siteConfig.ogImage.height,
alt: 'IP提取',
},
],
},
alternates: {
canonical: `${siteConfig.url}/collect`,
},
}
}
export default function CollectPage(props: CollectPageProps) {
return (
// <main className="mt-20 flex flex-col gap-4">
@@ -16,7 +40,7 @@ export default function CollectPage(props: CollectPageProps) {
// </Wrap>
// </main>
<HomePage path={[
{label: '短效IP 提取', href: '/collect'},
{label: '短效/长效IP 提取', href: '/collect'},
]}>
<Wrap>
<Extract/>

View File

@@ -0,0 +1,264 @@
'use client'
import {useState} from 'react'
import Image from 'next/image'
import {useRouter} from 'next/navigation'
import {useForm} from 'react-hook-form'
import {zodResolver} from '@hookform/resolvers/zod'
import {z} from 'zod'
import {toast} from 'sonner'
import HomePage from '@/components/home/page'
import Wrap from '@/components/wrap'
import {Form, FormField} from '@/components/ui/form'
import {Input} from '@/components/ui/input'
import {Button} from '@/components/ui/button'
import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue} from '@/components/ui/select'
import {merge} from '@/lib/utils'
import {submitInquiry} from '@/actions/inquiry'
import group from './_assets/Group.webp'
import SelfDesc from '@/components/features/self-desc'
const formSchema = z.object({
company: z.string().min(2, '企业名称至少2个字符'),
name: z.string().min(2, '联系人姓名至少2个字符'),
phone: z.string().regex(/^1[3-9]\d{9}$/, '请输入正确的11位手机号码'),
usage: z.string().min(1, '请选择您需要的用量'),
purpose: z.string().min(2, '请输入用途说明').max(200, '用途说明不超过200字符'),
})
type FormValues = z.infer<typeof formSchema>
export default function CustomPage() {
const router = useRouter()
const [isSubmitting, setIsSubmitting] = useState(false)
const form = useForm<FormValues>({
resolver: zodResolver(formSchema),
defaultValues: {
company: '',
name: '',
phone: '',
usage: '',
purpose: '',
},
})
const onSubmit = async (data: FormValues) => {
setIsSubmitting(true)
try {
const result = await submitInquiry(data)
if (result.success) {
toast.success('提交成功我们的专属顾问会在24小时内联系您')
form.reset()
}
else {
toast.error(result.message || '提交失败,请稍后重试')
}
}
catch (error) {
toast.error('网络错误,请稍后重试')
}
finally {
setIsSubmitting(false)
}
}
const scrollToForm = () => {
const formElement = document.getElementById('inquiry-form')
if (formElement) {
formElement.scrollIntoView({behavior: 'smooth', block: 'start'})
}
}
return (
<HomePage
path={[
{label: '业务定制', href: '/custom'},
]}
>
<Wrap className="flex flex-col gap-16">
{/* 1. 顶部介绍区 */}
<SelfDesc onInquiry={() => {
document.getElementById('inquiry-form')?.scrollIntoView({behavior: 'smooth', block: 'start'})
}}/>
{/* 2. 表单区 */}
<section id="inquiry-form" className="bg-white rounded-lg p-6 lg:p-12">
<div className="text-center mb-8 lg:mb-12">
<h2 className="text-2xl lg:text-3xl font-semibold"></h2>
<p className="text-gray-500 mt-2 text-sm lg:text-base">
24
</p>
</div>
<Form form={form} handler={form.handleSubmit(onSubmit)}>
<div className="mx-auto max-w-2xl space-y-6">
{/* 企业名称 */}
<FormField name="companyName">
{({id, field}) => (
<div className="flex flex-col lg:flex-row lg:items-start lg:gap-4">
<label
htmlFor={id}
className="flex items-center gap-1 lg:w-32 lg:text-right lg:pt-2 text-sm"
>
<span className="text-red-500">*</span>
<span></span>
</label>
<div className="flex-1 lg:max-w-md">
<Input
{...field}
id={id}
placeholder="请输入企业名称"
disabled={isSubmitting}
aria-required="true"
/>
</div>
</div>
)}
</FormField>
{/* 联系人姓名 */}
<FormField name="contactName">
{({id, field}) => (
<div className="flex flex-col lg:flex-row lg:items-start lg:gap-4">
<label
htmlFor={id}
className="flex items-center gap-1 lg:w-32 lg:text-right lg:pt-2 text-sm"
>
<span className="text-red-500">*</span>
<span></span>
</label>
<div className="flex-1 lg:max-w-md">
<Input
{...field}
id={id}
placeholder="请输入联系人姓名"
disabled={isSubmitting}
aria-required="true"
/>
</div>
</div>
)}
</FormField>
{/* 联系人手机号码 */}
<FormField name="phone">
{({id, field}) => (
<div className="flex flex-col lg:flex-row lg:items-start lg:gap-4">
<label
htmlFor={id}
className="flex items-center gap-1 lg:w-32 lg:text-right lg:pt-2 text-sm"
>
<span className="text-red-500">*</span>
<span></span>
</label>
<div className="flex-1 lg:max-w-md">
<Input
{...field}
id={id}
type="tel"
placeholder="请输入11位手机号码"
disabled={isSubmitting}
aria-required="true"
/>
</div>
</div>
)}
</FormField>
{/* 每月需求用量 */}
<FormField name="monthlyUsage">
{({id, field}) => (
<div className="flex flex-col lg:flex-row lg:items-start lg:gap-4">
<label
htmlFor={id}
className="flex items-center gap-1 lg:w-32 lg:text-right lg:pt-2 text-sm"
>
<span className="text-red-500">*</span>
<span></span>
</label>
<div className="flex-1 lg:max-w-md">
<Select
onValueChange={field.onChange}
value={field.value}
disabled={isSubmitting}
>
<SelectTrigger id={id} aria-required="true">
<SelectValue placeholder="请选择您需要的用量"/>
</SelectTrigger>
<SelectContent>
<SelectItem value="less20">20</SelectItem>
<SelectItem value="20-100">20~100</SelectItem>
<SelectItem value="100-500">100~500</SelectItem>
<SelectItem value="more500">500</SelectItem>
</SelectContent>
</Select>
</div>
</div>
)}
</FormField>
{/* 用途 */}
<FormField name="purpose">
{({id, field}) => (
<div className="flex flex-col lg:flex-row lg:items-start lg:gap-4">
<label
htmlFor={id}
className="flex items-center gap-1 lg:w-32 lg:text-right lg:pt-2 text-sm"
>
<span className="text-red-500">*</span>
<span></span>
</label>
<div className="flex-1 lg:max-w-md">
<Input
{...field}
id={id}
placeholder="请输入用途,例如:数据采集、市场调研等"
disabled={isSubmitting}
aria-required="true"
/>
</div>
</div>
)}
</FormField>
<div className="pt-4 flex justify-center">
<Button
type="submit"
className="bg-blue-600 hover:bg-blue-700 px-12 py-2.5"
disabled={isSubmitting}
>
{isSubmitting ? '提交中...' : '提交'}
</Button>
</div>
</div>
</Form>
</section>
{/* 3. 底部引导区 */}
<section className="relative rounded-lg overflow-hidden h-48 lg:h-56">
<Image
src={group}
alt="立即试用背景"
fill
className="object-cover"
priority
/>
<div className="absolute inset-0 flex items-center justify-center">
<div className="w-full max-w-4xl px-6 flex flex-col lg:flex-row items-center gap-4 lg:gap-10 justify-center lg:justify-between">
<div className="text-blue-600 font-bold text-xl lg:text-2xl text-center lg:text-left">
5000IP
</div>
<Button
className={merge(
'bg-blue-600 hover:bg-blue-700 text-white px-8 py-3 rounded-md whitespace-nowrap',
)}
onClick={() => router.push('/product')}
>
</Button>
</div>
</div>
</section>
</Wrap>
</HomePage>
)
}

View File

@@ -1,264 +1,27 @@
'use client'
import {useState} from 'react'
import Image from 'next/image'
import {useRouter} from 'next/navigation'
import {useForm} from 'react-hook-form'
import {zodResolver} from '@hookform/resolvers/zod'
import {z} from 'zod'
import {toast} from 'sonner'
import HomePage from '@/components/home/page'
import Wrap from '@/components/wrap'
import {Form, FormField} from '@/components/ui/form'
import {Input} from '@/components/ui/input'
import {Button} from '@/components/ui/button'
import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue} from '@/components/ui/select'
import {merge} from '@/lib/utils'
import {submitInquiry} from '@/actions/inquiry'
import group from './_assets/Group.webp'
import SelfDesc from '@/components/features/self-desc'
import {Metadata} from 'next'
import {siteConfig} from '@/config/site'
import CustomPage from './_client'
const formSchema = z.object({
company: z.string().min(2, '企业名称至少2个字符'),
name: z.string().min(2, '联系人姓名至少2个字符'),
phone: z.string().regex(/^1[3-9]\d{9}$/, '请输入正确的11位手机号码'),
usage: z.string().min(1, '请选择您需要的用量'),
purpose: z.string().min(2, '请输入用途说明').max(200, '用途说明不超过200字符'),
})
type FormValues = z.infer<typeof formSchema>
export default function CustomPage() {
const router = useRouter()
const [isSubmitting, setIsSubmitting] = useState(false)
const form = useForm<FormValues>({
resolver: zodResolver(formSchema),
defaultValues: {
company: '',
name: '',
phone: '',
usage: '',
purpose: '',
export async function generateMetadata(): Promise<Metadata> {
return {
title: '业务定制',
description: '蓝狐代理为您提供企业级代理IP定制服务专属顾问1对1服务量身打造代理解决方案满足企业个性化需求',
openGraph: {
title: '业务定制',
description: '蓝狐代理为您提供企业级代理IP定制服务专属顾问1对1服务量身打造代理解决方案',
images: [
{
url: siteConfig.ogImage.url,
width: siteConfig.ogImage.width,
height: siteConfig.ogImage.height,
alt: '业务定制',
},
],
},
alternates: {
canonical: `${siteConfig.url}/custom`,
},
})
const onSubmit = async (data: FormValues) => {
setIsSubmitting(true)
try {
const result = await submitInquiry(data)
if (result.success) {
toast.success('提交成功我们的专属顾问会在24小时内联系您')
form.reset()
}
else {
toast.error(result.message || '提交失败,请稍后重试')
}
}
catch (error) {
toast.error('网络错误,请稍后重试')
}
finally {
setIsSubmitting(false)
}
}
const scrollToForm = () => {
const formElement = document.getElementById('inquiry-form')
if (formElement) {
formElement.scrollIntoView({behavior: 'smooth', block: 'start'})
}
}
return (
<HomePage
path={[
{label: '业务定制', href: '/custom'},
]}
>
<Wrap className="flex flex-col gap-16">
{/* 1. 顶部介绍区 */}
<SelfDesc onInquiry={() => {
document.getElementById('inquiry-form')?.scrollIntoView({behavior: 'smooth', block: 'start'})
}}/>
{/* 2. 表单区 */}
<section id="inquiry-form" className="bg-white rounded-lg p-6 lg:p-12">
<div className="text-center mb-8 lg:mb-12">
<h2 className="text-2xl lg:text-3xl font-semibold"></h2>
<p className="text-gray-500 mt-2 text-sm lg:text-base">
24
</p>
</div>
<Form form={form} handler={form.handleSubmit(onSubmit)}>
<div className="mx-auto max-w-2xl space-y-6">
{/* 企业名称 */}
<FormField name="companyName">
{({id, field}) => (
<div className="flex flex-col lg:flex-row lg:items-start lg:gap-4">
<label
htmlFor={id}
className="flex items-center gap-1 lg:w-32 lg:text-right lg:pt-2 text-sm"
>
<span className="text-red-500">*</span>
<span></span>
</label>
<div className="flex-1 lg:max-w-md">
<Input
{...field}
id={id}
placeholder="请输入企业名称"
disabled={isSubmitting}
aria-required="true"
/>
</div>
</div>
)}
</FormField>
{/* 联系人姓名 */}
<FormField name="contactName">
{({id, field}) => (
<div className="flex flex-col lg:flex-row lg:items-start lg:gap-4">
<label
htmlFor={id}
className="flex items-center gap-1 lg:w-32 lg:text-right lg:pt-2 text-sm"
>
<span className="text-red-500">*</span>
<span></span>
</label>
<div className="flex-1 lg:max-w-md">
<Input
{...field}
id={id}
placeholder="请输入联系人姓名"
disabled={isSubmitting}
aria-required="true"
/>
</div>
</div>
)}
</FormField>
{/* 联系人手机号码 */}
<FormField name="phone">
{({id, field}) => (
<div className="flex flex-col lg:flex-row lg:items-start lg:gap-4">
<label
htmlFor={id}
className="flex items-center gap-1 lg:w-32 lg:text-right lg:pt-2 text-sm"
>
<span className="text-red-500">*</span>
<span></span>
</label>
<div className="flex-1 lg:max-w-md">
<Input
{...field}
id={id}
type="tel"
placeholder="请输入11位手机号码"
disabled={isSubmitting}
aria-required="true"
/>
</div>
</div>
)}
</FormField>
{/* 每月需求用量 */}
<FormField name="monthlyUsage">
{({id, field}) => (
<div className="flex flex-col lg:flex-row lg:items-start lg:gap-4">
<label
htmlFor={id}
className="flex items-center gap-1 lg:w-32 lg:text-right lg:pt-2 text-sm"
>
<span className="text-red-500">*</span>
<span></span>
</label>
<div className="flex-1 lg:max-w-md">
<Select
onValueChange={field.onChange}
value={field.value}
disabled={isSubmitting}
>
<SelectTrigger id={id} aria-required="true">
<SelectValue placeholder="请选择您需要的用量"/>
</SelectTrigger>
<SelectContent>
<SelectItem value="less20">20</SelectItem>
<SelectItem value="20-100">20~100</SelectItem>
<SelectItem value="100-500">100~500</SelectItem>
<SelectItem value="more500">500</SelectItem>
</SelectContent>
</Select>
</div>
</div>
)}
</FormField>
{/* 用途 */}
<FormField name="purpose">
{({id, field}) => (
<div className="flex flex-col lg:flex-row lg:items-start lg:gap-4">
<label
htmlFor={id}
className="flex items-center gap-1 lg:w-32 lg:text-right lg:pt-2 text-sm"
>
<span className="text-red-500">*</span>
<span></span>
</label>
<div className="flex-1 lg:max-w-md">
<Input
{...field}
id={id}
placeholder="请输入用途,例如:数据采集、市场调研等"
disabled={isSubmitting}
aria-required="true"
/>
</div>
</div>
)}
</FormField>
<div className="pt-4 flex justify-center">
<Button
type="submit"
className="bg-blue-600 hover:bg-blue-700 px-12 py-2.5"
disabled={isSubmitting}
>
{isSubmitting ? '提交中...' : '提交'}
</Button>
</div>
</div>
</Form>
</section>
{/* 3. 底部引导区 */}
<section className="relative rounded-lg overflow-hidden h-48 lg:h-56">
<Image
src={group}
alt="免费试用背景"
fill
className="object-cover"
priority
/>
<div className="absolute inset-0 flex items-center justify-center">
<div className="w-full max-w-4xl px-6 flex flex-col lg:flex-row items-center gap-4 lg:gap-10 justify-center lg:justify-between">
<div className="text-blue-600 font-bold text-xl lg:text-2xl text-center lg:text-left">
5000IP
</div>
<Button
className={merge(
'bg-blue-600 hover:bg-blue-700 text-white px-8 py-3 rounded-md whitespace-nowrap',
)}
onClick={() => router.push('/login')}
>
</Button>
</div>
</div>
</section>
</Wrap>
</HomePage>
)
}
export default CustomPage

View File

@@ -1,4 +1,6 @@
import {Metadata} from 'next'
import ScenePage, {ScenePageConfig} from '@/components/scene-page'
import {siteConfig} from '@/config/site'
import bannerImg from './_assets/banner.webp'
import solutionImg from './_assets/solution-main.webp'
import value1Img from './_assets/value-1.webp'
@@ -46,6 +48,28 @@ const config: ScenePageConfig = {
},
}
export async function generateMetadata(): Promise<Metadata> {
return {
title: config.banner.title,
description: config.banner.description,
openGraph: {
title: config.banner.title,
description: config.banner.description,
images: [
{
url: siteConfig.ogImage.url,
width: siteConfig.ogImage.width,
height: siteConfig.ogImage.height,
alt: config.banner.title,
},
],
},
alternates: {
canonical: `${siteConfig.url}/data-capture`,
},
}
}
export default function DataCapturePage() {
return <ScenePage {...config}/>
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,30 @@
import Image from 'next/image';
import android from './images/android.webp';
import android01 from './images/android01.webp';
import android02 from './images/android02.webp';
# 安卓手机设置代理教程
1、打开设置进入WLAN
<Image
src={android}
alt="IE浏览器设置"
width={'100%'}
margin-top={'0'}
/>
2、选择已连接的WIFI网络,点小箭头进入设置页面
<Image
src={android01}
alt="IE浏览器设置"
width={'100%'}
margin-top={'0'}
/>
3、将代理设置为“手动”填写主机名端口信息点击右上角保存
<Image
src={android02}
alt="IE浏览器设置"
width={'100%'}
margin-top={'0'}
/>
4、打开手机浏览器搜索IP地址查询若查询结果和设置的代理IP一致则代理设置成功

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,25 @@
import Image from 'next/image';
import ieConfig from './images/ieConfig.webp';
import lanConfig from './images/lanConfig.webp';
# 浏览器代理设置教程
打开IE浏览器选择“设置”点击“Internet选项”在弹出的“局域网LAN设置”中代理服务器的复选框打上勾并填写从蓝狐HTTP获取的ip地址及端口号点击确定刷新浏览器浏览器的IP就改变了。
1、打开IE浏览器选择“设置”
<Image
src={ieConfig}
alt="IE浏览器设置"
width={'100%'}
margin-top={'0'}
/>
2、点击“Internet选项”
3、弹出“Internet选项”弹窗选择连接—局域网设置
<Image
src={lanConfig}
alt="IE浏览器设置"
width={'100%'}
margin-top={'0'}
/>
4、在弹出的“局域网LAN设置”中代理服务器的复选框打上勾并填写从蓝狐代理获取的ip地址及端口。点击确定即设置成功了。

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@@ -0,0 +1,36 @@
import Image from 'next/image';
import Ios from './images/Ios.webp';
import ioS2 from './images/ioS2.webp';
import ios3 from './images/ios3.webp';
# iOS 设置代理教程
1、打开设置选择无线局域网
2、找到已连接的wifi点击最右侧的小图标
<Image
src={Ios}
alt="IE浏览器设置"
width={'100%'}
margin-top={'0'}
/>
3、进入配置代理界面选择“手动”
<Image
src={ioS2}
alt="IE浏览器设置"
width={'100%'}
margin-top={'0'}
/>
4、填写服务器、端口点击右上角存储
<Image
src={ios3}
alt="IE浏览器设置"
width={'100%'}
margin-top={'0'}
/>
5、在浏览器搜索IP地址查询若查询结果和设置的代理IP一致则代理设置成

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -0,0 +1,21 @@
import Image from 'next/image';
import win10 from './images/win10.webp';
import win101 from './images/win101.webp';
# Windows10 电脑设置代理教程
1、点击桌面右下角网络连接图标选择"网络和Internet设置"选项
<Image
src={win10}
alt="IE浏览器设置"
width={'100%'}
margin-top={'0'}
/>
2、点击"代理",将"使用代理服务器"设置为开,输入地址和端口后保存
<Image
src={win101}
alt="IE浏览器设置"
width={'100%'}
margin-top={'0'}
/>
3、浏览器打开百度输入IP地址查询若查询结果和设置的代理IP一致则代理设置成功

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -0,0 +1,47 @@
import Image from 'next/image';
import extract from './images/extract.webp';
import extract01 from './images/extract01.webp';
import extract02 from './images/extract02.webp';
import extract03 from './images/extract03.webp';
import extract04 from './images/extract04.webp';
# 如何生成提取链接
一、 进入控制台选择提取IP页面提取链接需要满足一下条件
1. 添加白名单,
2. 必须实名认证
<Image
src={extract}
alt="IE浏览器设置"
width={'100%'}
margin-top={'0'}
/>
<Image
src={extract02}
alt="IE浏览器设置"
width={'100%'}
margin-top={'0'}
/>
二、 选择套餐和其他选项以及选择数量后,点击打开链接
<Image
src={extract01}
alt="IE浏览器设置"
width={'100%'}
margin-top={'0'}
/>
三、 自动生成提取链接
<Image
src={extract03}
alt="IE浏览器设置"
width={'100%'}
margin-top={'0'}
/>
<Image
src={extract04}
alt="IE浏览器设置"
width={'100%'}
margin-top={'0'}
/>

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -0,0 +1,27 @@
import Image from 'next/image';
import pay from './images/pay.webp';
import pay01 from './images/pay01.webp';
import pay02 from './images/pay02.webp';
# 查看支付和使用记录
1. 进入控制台,选择我的账单,查看当前支付记录
<Image
src={pay}
alt="IE浏览器设置"
width={'100%'}
margin-top={'0'}
/>
2. 选择我的套餐,查看当前长效/短效的状态和使用记录
<Image
src={pay01}
alt="IE浏览器设置"
width={'100%'}
margin-top={'0'}
/>
<Image
src={pay02}
alt="IE浏览器设置"
width={'100%'}
margin-top={'0'}
/>

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

View File

@@ -0,0 +1,38 @@
import Image from 'next/image';
import modify from './images/modify.webp';
import modify01 from './images/modify01.webp';
import modify02 from './images/modify02.webp';
import modify03 from './images/modify03.webp';
# 修改个人信息和重置密码
1. 进入控制台
<Image
src={modify02}
alt="IE浏览器设置"
width={'100%'}
margin-top={'0'}
/>
2. 点击个人中心的基本信息页面,选择修改密码按钮
<Image
src={modify}
alt="IE浏览器设置"
width={'100%'}
margin-top={'0'}
/>
3. 选择重置密码
<Image
src={modify01}
alt="IE浏览器设置"
width={'100%'}
margin-top={'0'}
/>
4.修改个人基本信息
<Image
src={modify03}
alt="IE浏览器设置"
width={'100%'}
margin-top={'0'}
/>

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

@@ -0,0 +1,38 @@
import Image from 'next/image';
import verify04 from './images/verify04.webp';
import verify01 from './images/verify01.webp';
import verify02 from './images/verify02.webp';
import verify03 from './images/verify03.webp';
import verify05 from './images/verify05.webp';
# 如何进行实名认证
一、个人认证
1. 进入控制台页面,选择立即认证
<Image
src={verify05}
alt="IE浏览器设置"
width={'100%'}
margin-top={'0'}
/>
<Image
src={verify04}
alt="IE浏览器设置"
width={'100%'}
margin-top={'0'}
/>
2. 点击立即认证。
<Image
src={verify02}
alt="IE浏览器设置"
width={'100%'}
margin-top={'0'}
/>
3. 输入身份证姓名及身份证号码,生成二维码使用支付宝扫描认证。认证通过后,平台账户标记为“个人认证”。
<Image
src={verify03}
alt="IE浏览器设置"
width={'100%'}
margin-top={'0'}
/>

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -0,0 +1,35 @@
import Image from 'next/image';
import whitelist from './images/whitelist.webp';
import whitelist01 from './images/whitelist01.webp';
import whitelist02 from './images/whitelist02.webp';
import whitelist03 from './images/whitelist03.webp';
# 如何添加白名单
1. 进入控制台选择白名单点击添加白名单或选择提取IP点击添加白名单。
<Image
src={whitelist}
alt="IE浏览器设置"
width={'100%'}
margin-top={'0'}
/>
<Image
src={whitelist01}
alt="IE浏览器设置"
width={'100%'}
margin-top={'0'}
/>
2. 填写白名单的IP地址/当前IP地址点击保存。
<Image
src={whitelist02}
alt="IE浏览器设置"
width={'100%'}
margin-top={'0'}
/>
3. 生成白名单记录。
<Image
src={whitelist03}
alt="IE浏览器设置"
width={'100%'}
margin-top={'0'}
/>

Some files were not shown because too many files have changed in this diff Show More