finish
This commit is contained in:
26
Dockerfile
Normal file
26
Dockerfile
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# 使用官方 Python 3.9 slim 镜像作为基础镜像
|
||||||
|
FROM python:3.9-slim
|
||||||
|
|
||||||
|
# 设置工作目录
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# 设置环境变量
|
||||||
|
# 防止 Python 生成 .pyc 文件
|
||||||
|
ENV PYTHONDONTWRITEBYTECODE=1
|
||||||
|
# 确保 Python 输出不被缓冲
|
||||||
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
|
# 复制依赖文件
|
||||||
|
COPY requirements.txt .
|
||||||
|
|
||||||
|
# 安装依赖
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# 复制项目文件
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# 暴露端口
|
||||||
|
EXPOSE 8800
|
||||||
|
|
||||||
|
# 启动命令
|
||||||
|
CMD ["python", "main.py", "--port", "8800"]
|
||||||
306
README.md
Normal file
306
README.md
Normal file
@@ -0,0 +1,306 @@
|
|||||||
|
# 云南AI共生大会签到系统
|
||||||
|
|
||||||
|
## 项目概述
|
||||||
|
|
||||||
|
这是一个专为云南AI共生大会设计的智能签到系统,基于FastAPI框架开发,提供完整的会议签到解决方案。系统集成了智能座位分配、嘉宾信息管理、同桌推荐等功能,为参会者提供流畅的签到体验。
|
||||||
|
|
||||||
|
## 主要功能
|
||||||
|
|
||||||
|
### 🎯 核心功能
|
||||||
|
- **智能签到流程**:支持手机号或姓名搜索,快速定位参会嘉宾
|
||||||
|
- **智能座位分配**:基于行业分布和人数均衡的算法自动分配座位
|
||||||
|
- **同桌嘉宾推荐**:签到后展示同table的3位随机嘉宾,促进交流
|
||||||
|
- **嘉宾信息补全**:签到时可补充单位、职务、业务范围等信息
|
||||||
|
- **重复签到检测**:自动识别已签到用户,避免重复操作
|
||||||
|
|
||||||
|
### 🔧 管理功能
|
||||||
|
- **新嘉宾添加**:支持现场添加新参会嘉宾
|
||||||
|
- **多用户搜索**:模糊搜索支持,可处理重名情况
|
||||||
|
- **信息验证**:手机号唯一性验证,防止重复注册
|
||||||
|
|
||||||
|
### 🎨 用户体验
|
||||||
|
- **现代化UI设计**:采用AI主题的未来主义风格界面
|
||||||
|
- **响应式布局**:适配各种设备屏幕
|
||||||
|
- **实时反馈**:清晰的操作提示和错误处理
|
||||||
|
- **模态框详情**:点击同桌嘉宾可查看详细信息
|
||||||
|
|
||||||
|
## 技术架构
|
||||||
|
|
||||||
|
### 后端技术栈
|
||||||
|
- **FastAPI**:高性能Python Web框架
|
||||||
|
- **PostgreSQL**:数据持久化存储
|
||||||
|
- **psycopg2**:PostgreSQL数据库适配器
|
||||||
|
- **Pydantic**:数据验证和序列化
|
||||||
|
- **Jinja2**:模板引擎
|
||||||
|
- **UUID**:唯一标识符生成
|
||||||
|
|
||||||
|
### 前端技术
|
||||||
|
- **HTML5 + CSS3**:现代化界面设计
|
||||||
|
- **原生JavaScript**:轻量级交互逻辑
|
||||||
|
- **AJAX**:异步数据交互
|
||||||
|
- **响应式设计**:移动端友好
|
||||||
|
|
||||||
|
### 部署方案
|
||||||
|
- **Docker容器化**:便于部署和扩展
|
||||||
|
- **自动化脚本**:一键构建和部署
|
||||||
|
- **多架构支持**:支持AMD64和ARM64架构
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
.
|
||||||
|
├── main.py # 主应用文件,包含所有API端点
|
||||||
|
├── templates/ # HTML模板目录
|
||||||
|
│ ├── index.html # 主签到页面
|
||||||
|
│ └── add_user.html # 添加新嘉宾页面
|
||||||
|
├── static/ # 静态资源目录
|
||||||
|
│ └── image.jpg # 会议主题图片
|
||||||
|
├── docker_deply.sh # Docker部署自动化脚本
|
||||||
|
├── gsdh.csv # 嘉宾数据文件(示例)
|
||||||
|
└── README.md # 项目文档
|
||||||
|
```
|
||||||
|
|
||||||
|
## 核心算法
|
||||||
|
|
||||||
|
### 座位分配算法
|
||||||
|
系统采用双层策略进行智能座位分配:
|
||||||
|
|
||||||
|
1. **均衡分布**:优先选择人数最少的table,确保13个table人数均衡(每桌最多12人)
|
||||||
|
2. **行业多样性**:在人数最少的table中,选择该行业占比最少的table,促进行业交流
|
||||||
|
|
||||||
|
```python
|
||||||
|
def assign_seat(cur, user_industry: str) -> str:
|
||||||
|
# 1. 统计现有table人数和行业分布
|
||||||
|
# 2. 筛选未满的table(每桌≤12人)
|
||||||
|
# 3. 选择人数最少的table
|
||||||
|
# 4. 在候选table中选择该行业占比最少的
|
||||||
|
# 5. 返回分配的桌号(如"第5桌")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 同桌推荐算法
|
||||||
|
- 随机选择同table的3位嘉宾(排除自己)
|
||||||
|
- 展示基本信息:姓名、行业、公司、职务、业务范围、2026愿景
|
||||||
|
- 支持点击查看详细信息
|
||||||
|
|
||||||
|
## API接口文档
|
||||||
|
|
||||||
|
### 搜索接口
|
||||||
|
```http
|
||||||
|
GET /api/search?query={phone_or_name}
|
||||||
|
```
|
||||||
|
- 支持手机号精确搜索和姓名模糊搜索
|
||||||
|
- 返回用户信息和签到状态
|
||||||
|
|
||||||
|
### 签到接口
|
||||||
|
```http
|
||||||
|
POST /api/checkin
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"gsdh_id": "uuid",
|
||||||
|
"name": "姓名",
|
||||||
|
"phone": "手机号",
|
||||||
|
"company_name": "公司名称",
|
||||||
|
"position": "职务",
|
||||||
|
"business_scope": "业务范围",
|
||||||
|
"vision_2026": "2026愿景",
|
||||||
|
"location": "座位号"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 添加用户接口
|
||||||
|
```http
|
||||||
|
POST /api/add-user
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "姓名",
|
||||||
|
"phone": "手机号",
|
||||||
|
"industry_company": "公司/行业"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 数据库设计
|
||||||
|
|
||||||
|
### 主要数据表
|
||||||
|
|
||||||
|
#### gsdh_data(嘉宾基础信息表)
|
||||||
|
| 字段名 | 类型 | 说明 |
|
||||||
|
|--------|------|------|
|
||||||
|
| new_id | UUID | 主键,唯一标识 |
|
||||||
|
| name | VARCHAR | 姓名 |
|
||||||
|
| phone | VARCHAR | 手机号(唯一) |
|
||||||
|
| industry_company | VARCHAR | 公司/行业信息 |
|
||||||
|
| is_signed | BOOLEAN | 签到状态 |
|
||||||
|
|
||||||
|
#### checkin_info(签到信息表)
|
||||||
|
| 字段名 | 类型 | 说明 |
|
||||||
|
|--------|------|------|
|
||||||
|
| id | SERIAL | 主键,自增 |
|
||||||
|
| gsdh_id | UUID | 关联gsdh_data |
|
||||||
|
| name | VARCHAR | 签到时确认的姓名 |
|
||||||
|
| phone | VARCHAR | 签到时确认的手机号 |
|
||||||
|
| company_name | VARCHAR | 公司名称 |
|
||||||
|
| position | VARCHAR | 职务 |
|
||||||
|
| business_scope | TEXT | 业务范围 |
|
||||||
|
| vision_2026 | TEXT | 2026年业务愿景 |
|
||||||
|
| location | VARCHAR | 分配的座位号 |
|
||||||
|
| created_at | TIMESTAMP | 签到时间 |
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
### 环境要求
|
||||||
|
- Python 3.8+
|
||||||
|
- PostgreSQL 12+
|
||||||
|
- Docker(可选)
|
||||||
|
|
||||||
|
### 本地开发
|
||||||
|
|
||||||
|
1. **克隆项目**
|
||||||
|
```bash
|
||||||
|
git clone <repository-url>
|
||||||
|
cd yunnan-ai-conference-checkin
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **安装依赖**
|
||||||
|
```bash
|
||||||
|
pip install fastapi uvicorn psycopg2-binary pydantic jinja2
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **配置数据库**
|
||||||
|
```bash
|
||||||
|
# 创建数据库
|
||||||
|
createdb gsdh
|
||||||
|
|
||||||
|
# 执行SQL初始化脚本(需要创建相关表结构)
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **启动应用**
|
||||||
|
```bash
|
||||||
|
python main.py
|
||||||
|
# 或使用uvicorn
|
||||||
|
uvicorn main:app --host 0.0.0.0 --port 8000
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **访问应用**
|
||||||
|
- 主签到页面:http://localhost:8000
|
||||||
|
- 添加嘉宾页面:http://localhost:8000/add-user
|
||||||
|
|
||||||
|
### Docker部署
|
||||||
|
|
||||||
|
1. **构建镜像**
|
||||||
|
```bash
|
||||||
|
# 完整构建和部署流程(默认AMD64架构)
|
||||||
|
./docker_deply.sh
|
||||||
|
|
||||||
|
# 构建ARM64架构镜像
|
||||||
|
./docker_deply.sh -arm
|
||||||
|
|
||||||
|
# 仅上传已存在的tar文件
|
||||||
|
./docker_deply.sh -upload
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **配置参数**
|
||||||
|
编辑 `docker_deply.sh` 脚本顶部的配置:
|
||||||
|
```bash
|
||||||
|
SERVER_HOST="your-server-ip" # 服务器IP地址
|
||||||
|
SERVER_USER="ubuntu" # 服务器用户名
|
||||||
|
SERVER_PASSWORD="your-password" # 服务器密码
|
||||||
|
SERVER_PORT="22" # SSH端口
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **访问应用**
|
||||||
|
部署完成后,通过 `http://your-server-ip:8011` 访问
|
||||||
|
|
||||||
|
## 配置说明
|
||||||
|
|
||||||
|
### 数据库配置
|
||||||
|
在 `main.py` 中修改数据库连接参数:
|
||||||
|
```python
|
||||||
|
DB_CONFIG = {
|
||||||
|
"host": "121.43.104.161",
|
||||||
|
"port": "6432",
|
||||||
|
"user": "gsdh",
|
||||||
|
"password": "123gsdh",
|
||||||
|
"database": "gsdh"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 座位分配规则
|
||||||
|
- 总table数:13桌
|
||||||
|
- 每桌最大人数:12人
|
||||||
|
- 分配策略:人数均衡 + 行业多样性
|
||||||
|
|
||||||
|
## 使用流程
|
||||||
|
|
||||||
|
### 正常签到流程
|
||||||
|
1. 嘉宾输入手机号或姓名进行搜索
|
||||||
|
2. 系统显示匹配的嘉宾信息
|
||||||
|
3. 嘉宾确认并补充完整信息
|
||||||
|
4. 系统智能分配座位并展示同桌嘉宾
|
||||||
|
5. 签到完成,显示座位号
|
||||||
|
|
||||||
|
### 现场添加嘉宾
|
||||||
|
1. 访问添加嘉宾页面
|
||||||
|
2. 输入新嘉宾的基本信息
|
||||||
|
3. 系统验证手机号唯一性
|
||||||
|
4. 添加成功后,新嘉宾可进行正常签到
|
||||||
|
|
||||||
|
## 安全特性
|
||||||
|
|
||||||
|
- **数据验证**:所有输入都经过Pydantic严格验证
|
||||||
|
- **错误处理**:完善的异常捕获和用户友好的错误提示
|
||||||
|
- **手机号保护**:手机号信息不公开显示
|
||||||
|
- **防重复签到**:基于数据库事务确保数据一致性
|
||||||
|
|
||||||
|
## 性能优化
|
||||||
|
|
||||||
|
- **数据库连接池**:复用数据库连接,减少连接开销
|
||||||
|
- **索引优化**:在搜索字段上建立适当索引
|
||||||
|
- **缓存策略**:静态资源缓存,提升加载速度
|
||||||
|
- **异步处理**:非阻塞的API响应
|
||||||
|
|
||||||
|
## 扩展功能
|
||||||
|
|
||||||
|
### 未来可考虑的功能
|
||||||
|
- **二维码签到**:生成个人二维码,扫码快速签到
|
||||||
|
- **实时统计**:后台实时查看签到进度和统计
|
||||||
|
- **消息推送**:签到成功后发送确认短信/邮件
|
||||||
|
- **数据导出**:支持签到数据导出为Excel等格式
|
||||||
|
- **多语言支持**:支持中英文切换
|
||||||
|
- **人脸识别**:集成人脸识别技术,提升签到体验
|
||||||
|
|
||||||
|
## 故障排除
|
||||||
|
|
||||||
|
### 常见问题
|
||||||
|
|
||||||
|
**Q: 数据库连接失败**
|
||||||
|
A: 检查数据库配置是否正确,确保PostgreSQL服务正常运行
|
||||||
|
|
||||||
|
**Q: 座位分配算法异常**
|
||||||
|
A: 检查数据库中table数据是否完整,确保location字段格式正确
|
||||||
|
|
||||||
|
**Q: 搜索功能无结果**
|
||||||
|
A: 确认数据库中有对应的数据,检查搜索关键词是否正确
|
||||||
|
|
||||||
|
**Q: Docker部署失败**
|
||||||
|
A: 检查服务器配置和网络连接,确保Docker服务正常运行
|
||||||
|
|
||||||
|
## 维护建议
|
||||||
|
|
||||||
|
1. **定期备份**:定期备份数据库,防止数据丢失
|
||||||
|
2. **监控性能**:监控API响应时间和数据库性能
|
||||||
|
3. **日志分析**:定期分析应用日志,发现潜在问题
|
||||||
|
4. **安全更新**:及时更新依赖包,修复安全漏洞
|
||||||
|
|
||||||
|
## 许可证
|
||||||
|
|
||||||
|
本项目采用MIT许可证,详见LICENSE文件。
|
||||||
|
|
||||||
|
## 联系方式
|
||||||
|
|
||||||
|
如有问题或建议,请联系项目维护者。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**特别说明**:本项目专为云南AI共生大会定制开发,结合了会议的实际需求和AI技术的特色,旨在为参会嘉宾提供便捷、智能的签到体验。
|
||||||
23
docker-compose.yml
Normal file
23
docker-compose.yml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
web:
|
||||||
|
build: .
|
||||||
|
container_name: yunnan-ai-checkin
|
||||||
|
ports:
|
||||||
|
- "8800:8800"
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
# - DB_HOST=6.6.6.6
|
||||||
|
# - DB_PORT=5432
|
||||||
|
# - DB_USER=gsdh
|
||||||
|
# - DB_PASSWORD=123gsdh
|
||||||
|
# - DB_NAME=gsdh
|
||||||
|
|
||||||
|
- DB_HOST=121.43.104.161
|
||||||
|
- DB_PORT=6432
|
||||||
|
- DB_USER=gsdh
|
||||||
|
- DB_PASSWORD=123gsdh
|
||||||
|
- DB_NAME=gsdh
|
||||||
|
volumes:
|
||||||
|
- .:/app
|
||||||
35
main.py
35
main.py
@@ -8,16 +8,17 @@ from psycopg2.extras import RealDictCursor
|
|||||||
from typing import Optional, List, Dict
|
from typing import Optional, List, Dict
|
||||||
import random
|
import random
|
||||||
import uuid
|
import uuid
|
||||||
|
import os
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
||||||
# Database connection parameters
|
# Database connection parameters
|
||||||
DB_CONFIG = {
|
DB_CONFIG = {
|
||||||
"host": "121.43.104.161",
|
"host": os.getenv("DB_HOST", "121.43.104.161"),
|
||||||
"port": "6432",
|
"port": os.getenv("DB_PORT", "6432"),
|
||||||
"user": "gsdh",
|
"user": os.getenv("DB_USER", "gsdh"),
|
||||||
"password": "123gsdh",
|
"password": os.getenv("DB_PASSWORD", "123gsdh"),
|
||||||
"database": "gsdh"
|
"database": os.getenv("DB_NAME", "gsdh")
|
||||||
}
|
}
|
||||||
|
|
||||||
# Mount static files
|
# Mount static files
|
||||||
@@ -39,6 +40,8 @@ class AddUserRequest(BaseModel):
|
|||||||
name: str
|
name: str
|
||||||
phone: str
|
phone: str
|
||||||
industry_company: Optional[str] = None
|
industry_company: Optional[str] = None
|
||||||
|
fee: Optional[str] = None
|
||||||
|
payment_channel: Optional[str] = None
|
||||||
|
|
||||||
def get_db_connection():
|
def get_db_connection():
|
||||||
conn = psycopg2.connect(**DB_CONFIG)
|
conn = psycopg2.connect(**DB_CONFIG)
|
||||||
@@ -165,6 +168,7 @@ async def search_user(query: str):
|
|||||||
"""
|
"""
|
||||||
Search user by phone (exact match) or name (fuzzy match).
|
Search user by phone (exact match) or name (fuzzy match).
|
||||||
"""
|
"""
|
||||||
|
print(f"DEBUG: Searching for query: {query}")
|
||||||
try:
|
try:
|
||||||
conn = get_db_connection()
|
conn = get_db_connection()
|
||||||
cur = conn.cursor(cursor_factory=RealDictCursor)
|
cur = conn.cursor(cursor_factory=RealDictCursor)
|
||||||
@@ -287,13 +291,24 @@ async def add_user_api(user_data: AddUserRequest):
|
|||||||
conn.close()
|
conn.close()
|
||||||
return JSONResponse(content={"success": False, "message": "该手机号已存在"}, status_code=400)
|
return JSONResponse(content={"success": False, "message": "该手机号已存在"}, status_code=400)
|
||||||
|
|
||||||
new_id = str(uuid.uuid4())
|
# Calculate next new_id
|
||||||
|
cur.execute("SELECT MAX(CAST(new_id AS INTEGER)) FROM gsdh_data WHERE new_id ~ '^[0-9]+$'")
|
||||||
|
row = cur.fetchone()
|
||||||
|
max_id = row[0] if row and row[0] is not None else 0
|
||||||
|
new_id = str(max_id + 1)
|
||||||
|
|
||||||
insert_sql = """
|
insert_sql = """
|
||||||
INSERT INTO gsdh_data (new_id, name, phone, industry_company, is_signed)
|
INSERT INTO gsdh_data (new_id, name, phone, industry_company, fee, payment_channel, is_signed)
|
||||||
VALUES (%s, %s, %s, %s, 'FALSE')
|
VALUES (%s, %s, %s, %s, %s, %s, 'FALSE')
|
||||||
"""
|
"""
|
||||||
cur.execute(insert_sql, (new_id, user_data.name, user_data.phone, user_data.industry_company))
|
cur.execute(insert_sql, (
|
||||||
|
new_id,
|
||||||
|
user_data.name,
|
||||||
|
user_data.phone,
|
||||||
|
user_data.industry_company,
|
||||||
|
user_data.fee,
|
||||||
|
user_data.payment_channel
|
||||||
|
))
|
||||||
|
|
||||||
conn.commit()
|
conn.commit()
|
||||||
cur.close()
|
cur.close()
|
||||||
@@ -307,4 +322,4 @@ async def add_user_api(user_data: AddUserRequest):
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import uvicorn
|
import uvicorn
|
||||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
uvicorn.run(app, host="0.0.0.0", port=8800)
|
||||||
|
|||||||
6
requirements.txt
Normal file
6
requirements.txt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
fastapi
|
||||||
|
uvicorn
|
||||||
|
psycopg2-binary
|
||||||
|
pydantic
|
||||||
|
jinja2
|
||||||
|
python-multipart
|
||||||
@@ -211,9 +211,47 @@
|
|||||||
0% { transform: rotate(0deg); }
|
0% { transform: rotate(0deg); }
|
||||||
100% { transform: rotate(360deg); }
|
100% { transform: rotate(360deg); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Password Overlay Styles */
|
||||||
|
#password-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
background-image: inherit;
|
||||||
|
z-index: 9999;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
transition: opacity 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-card {
|
||||||
|
background: var(--card-bg);
|
||||||
|
padding: 40px;
|
||||||
|
border-radius: 16px;
|
||||||
|
text-align: center;
|
||||||
|
width: 90%;
|
||||||
|
max-width: 400px;
|
||||||
|
border: 1px solid rgba(0, 242, 255, 0.2);
|
||||||
|
box-shadow: 0 0 50px rgba(0,0,0,0.5);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<!-- Password Overlay -->
|
||||||
|
<div id="password-overlay">
|
||||||
|
<div class="auth-card">
|
||||||
|
<h2 style="margin-bottom: 20px;">请输入访问密钥</h2>
|
||||||
|
<input type="password" id="secret-input" placeholder="输入密钥" style="margin-bottom: 20px; text-align: center;">
|
||||||
|
<button onclick="checkSecret()">确认</button>
|
||||||
|
<p id="secret-error" class="error-msg hidden">密钥错误</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<header>
|
<header>
|
||||||
<img src="/static/image.jpg" alt="云南AI共生大会" class="header-img" onerror="this.style.display='none'">
|
<img src="/static/image.jpg" alt="云南AI共生大会" class="header-img" onerror="this.style.display='none'">
|
||||||
@@ -237,6 +275,16 @@
|
|||||||
<input type="text" id="company" placeholder="请输入单位名称或行业信息">
|
<input type="text" id="company" placeholder="请输入单位名称或行业信息">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="input-group">
|
||||||
|
<label>费用 (Fee)</label>
|
||||||
|
<input type="text" id="fee" placeholder="请输入费用信息">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="input-group">
|
||||||
|
<label>支付渠道 (Payment Channel)</label>
|
||||||
|
<input type="text" id="payment_channel" placeholder="请输入支付渠道">
|
||||||
|
</div>
|
||||||
|
|
||||||
<button type="submit" id="submit-btn">确认添加</button>
|
<button type="submit" id="submit-btn">确认添加</button>
|
||||||
</form>
|
</form>
|
||||||
<div id="submit-error" class="error-msg hidden"></div>
|
<div id="submit-error" class="error-msg hidden"></div>
|
||||||
@@ -249,6 +297,31 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
// Password Check Logic
|
||||||
|
function checkSecret() {
|
||||||
|
const input = document.getElementById('secret-input');
|
||||||
|
const error = document.getElementById('secret-error');
|
||||||
|
const overlay = document.getElementById('password-overlay');
|
||||||
|
|
||||||
|
if (input.value === '123quant-speed') {
|
||||||
|
overlay.style.opacity = '0';
|
||||||
|
setTimeout(() => {
|
||||||
|
overlay.style.display = 'none';
|
||||||
|
}, 500);
|
||||||
|
} else {
|
||||||
|
error.classList.remove('hidden');
|
||||||
|
input.value = '';
|
||||||
|
input.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow pressing Enter to submit password
|
||||||
|
document.getElementById('secret-input').addEventListener('keypress', function (e) {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
checkSecret();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
async function submitAddUser(e) {
|
async function submitAddUser(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const btn = document.getElementById('submit-btn');
|
const btn = document.getElementById('submit-btn');
|
||||||
@@ -263,7 +336,9 @@
|
|||||||
const payload = {
|
const payload = {
|
||||||
name: document.getElementById('name').value,
|
name: document.getElementById('name').value,
|
||||||
phone: document.getElementById('phone').value,
|
phone: document.getElementById('phone').value,
|
||||||
industry_company: document.getElementById('company').value
|
industry_company: document.getElementById('company').value,
|
||||||
|
fee: document.getElementById('fee').value,
|
||||||
|
payment_channel: document.getElementById('payment_channel').value
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user