diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..f73344d
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/qym.iml b/.idea/qym.iml
new file mode 100644
index 0000000..5e764c4
--- /dev/null
+++ b/.idea/qym.iml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.version b/.version
index 7813681..abac1ea 100644
--- a/.version
+++ b/.version
@@ -1 +1 @@
-5
\ No newline at end of file
+47
diff --git a/DEPLOY.md b/DEPLOY.md
deleted file mode 100644
index 81233cb..0000000
--- a/DEPLOY.md
+++ /dev/null
@@ -1,279 +0,0 @@
-# 泉友米车友会 - 部署说明
-
-版本: 0.0.1
-
-## 🚀 快速部署(推荐)
-
-### Windows 用户
-
-```cmd
-# 1. 构建镜像
-build.bat
-
-# 2. 启动服务
-docker-compose up -d
-
-# 3. 初始化管理员
-docker exec -it quanyoumi-app sh
-node init-admin.js
-```
-
-### Linux/Mac 用户
-
-```bash
-# 1. 构建镜像
-chmod +x build.sh
-./build.sh
-
-# 2. 启动服务
-docker-compose up -d
-
-# 3. 初始化管理员
-docker exec -it quanyoumi-app sh
-node init-admin.js
-```
-
-## 📝 部署前准备
-
-### 1. 修改配置文件
-
-编辑 `docker-compose.yml`,修改以下配置:
-
-```yaml
-environment:
- - DB_PASSWORD=your_mysql_password # 修改数据库密码
- - JWT_SECRET=your_jwt_secret_key # 修改JWT密钥(随机字符串)
- - AMAP_KEY=your_amap_key # 填入高德地图Key
-```
-
-同时修改 MySQL 服务的密码:
-
-```yaml
-db:
- environment:
- - MYSQL_ROOT_PASSWORD=your_mysql_password # 与上面保持一致
-```
-
-### 2. 获取高德地图 Key
-
-1. 访问 https://console.amap.com/
-2. 注册/登录账号
-3. 创建应用,选择 Web 端(JS API)
-4. 复制 Key 填入配置
-
-## 🔧 详细步骤
-
-### 步骤 1: 构建 Docker 镜像
-
-镜像包含前端(Vue3)和后端(Node.js),版本号 0.0.1。
-
-**Windows:**
-```cmd
-build.bat
-```
-
-**Linux/Mac:**
-```bash
-chmod +x build.sh
-./build.sh
-```
-
-### 步骤 2: 启动服务
-
-使用 docker-compose 启动完整环境(应用+MySQL+MinIO):
-
-```bash
-docker-compose up -d
-```
-
-查看启动状态:
-```bash
-docker-compose ps
-```
-
-查看日志:
-```bash
-docker-compose logs -f app
-```
-
-### 步骤 3: 初始化数据库
-
-数据库会自动初始化(使用 database.sql),等待约 30 秒。
-
-检查数据库是否就绪:
-```bash
-docker-compose logs db | grep "ready for connections"
-```
-
-### 步骤 4: 创建管理员账号
-
-进入应用容器:
-```bash
-docker exec -it quanyoumi-app sh
-```
-
-运行初始化脚本:
-```bash
-node init-admin.js
-```
-
-按提示输入:
-- 用户名(例如:admin)
-- 密码(至少6位)
-- 确认密码
-
-退出容器:
-```bash
-exit
-```
-
-### 步骤 5: 访问应用
-
-- **前端页面**: http://localhost:3000
-- **后台管理**: http://localhost:3000/admin/login
-- **MinIO 控制台**: http://localhost:9001 (minioadmin/minioadmin)
-
-## 📦 镜像信息
-
-- **镜像名称**: quanyoumi-app
-- **版本**: 0.0.1
-- **基础镜像**: node:18-alpine
-- **大小**: 约 200MB
-- **包含内容**:
- - 前端构建产物(Vue3 + Vant4)
- - 后端服务(Node.js + Express)
- - 依赖包(仅生产环境)
-
-## 🔍 验证部署
-
-### 1. 检查服务状态
-
-```bash
-docker-compose ps
-```
-
-应该看到 3 个服务都是 Up 状态:
-- quanyoumi-app
-- quanyoumi-db
-- quanyoumi-minio
-
-### 2. 检查应用日志
-
-```bash
-docker-compose logs app
-```
-
-应该看到:
-```
-服务器运行在 http://localhost:3000
-运行模式: 生产环境(前后端一体)
-```
-
-### 3. 测试访问
-
-浏览器访问 http://localhost:3000,应该能看到首页。
-
-## 🛠️ 常见问题
-
-### 问题 1: 端口被占用
-
-错误信息:`port is already allocated`
-
-解决方法:
-```bash
-# 修改 docker-compose.yml 中的端口映射
-ports:
- - "3001:3000" # 改为其他端口
-```
-
-### 问题 2: 数据库连接失败
-
-错误信息:`ECONNREFUSED` 或 `Access denied`
-
-解决方法:
-1. 检查数据库是否启动:`docker-compose ps db`
-2. 检查密码是否一致(app 和 db 的密码要相同)
-3. 等待数据库完全启动(约 30 秒)
-
-### 问题 3: 前端页面空白
-
-解决方法:
-1. 检查浏览器控制台是否有错误
-2. 确认后端 API 是否正常:访问 http://localhost:3000/api/activities
-3. 重新构建镜像:`docker-compose build --no-cache`
-
-### 问题 4: 图片上传失败
-
-解决方法:
-1. 检查 MinIO 是否运行:`docker-compose ps minio`
-2. 访问 MinIO 控制台创建 bucket:http://localhost:9001
-3. 检查环境变量配置是否正确
-
-## 🔄 更新应用
-
-```bash
-# 1. 停止服务
-docker-compose down
-
-# 2. 拉取最新代码
-git pull
-
-# 3. 重新构建
-docker-compose build --no-cache
-
-# 4. 启动服务
-docker-compose up -d
-```
-
-## 🗑️ 清理
-
-### 停止服务(保留数据)
-```bash
-docker-compose down
-```
-
-### 完全清理(删除数据)
-```bash
-docker-compose down -v
-```
-
-### 删除镜像
-```bash
-docker rmi quanyoumi-app:0.0.1
-```
-
-## 📊 生产环境建议
-
-1. **使用反向代理**
- - 推荐使用 Nginx 或 Caddy
- - 配置 HTTPS 证书
- - 设置域名
-
-2. **修改默认密码**
- - MySQL root 密码
- - MinIO 访问密钥
- - JWT 密钥(长随机字符串)
-
-3. **数据备份**
- - 定期备份 MySQL 数据
- - 备份 MinIO 存储
- - 备份上传文件
-
-4. **监控告警**
- - 配置日志收集
- - 设置资源监控
- - 配置告警通知
-
-5. **安全加固**
- - 限制数据库外部访问
- - 使用防火墙规则
- - 定期更新镜像
-
-## 📞 技术支持
-
-如有问题,请查看:
-1. 应用日志:`docker-compose logs app`
-2. 数据库日志:`docker-compose logs db`
-3. MinIO 日志:`docker-compose logs minio`
-
-或联系技术支持团队。
diff --git a/PRIZE-FEATURE.md b/PRIZE-FEATURE.md
deleted file mode 100644
index e69de29..0000000
diff --git a/backend/data/database.sqlite b/backend/data/database.sqlite
new file mode 100644
index 0000000..546e04a
Binary files /dev/null and b/backend/data/database.sqlite differ
diff --git a/backend/fix-expense-media.js b/backend/fix-expense-media.js
new file mode 100644
index 0000000..0442e24
--- /dev/null
+++ b/backend/fix-expense-media.js
@@ -0,0 +1,49 @@
+const { Expense } = require('./models');
+
+async function fixExpenseMedia() {
+ try {
+ console.log('开始修复经费媒体数据...');
+
+ const expenses = await Expense.findAll();
+ console.log(`找到 ${expenses.length} 条经费记录`);
+
+ let fixedCount = 0;
+
+ for (const expense of expenses) {
+ let needUpdate = false;
+ let media = expense.media || [];
+
+ // 检查是否有异常数据
+ if (Array.isArray(media) && media.length > 5) {
+ console.log(`经费 #${expense.id} 有 ${media.length} 个媒体文件,超过限制`);
+ media = media.slice(0, 5);
+ needUpdate = true;
+ }
+
+ // 检查是否有无效的媒体项
+ const validMedia = media.filter(item => {
+ return item && item.url && (item.type === 'image' || item.type === 'video');
+ });
+
+ if (validMedia.length !== media.length) {
+ console.log(`经费 #${expense.id} 有无效媒体项,从 ${media.length} 清理到 ${validMedia.length}`);
+ media = validMedia;
+ needUpdate = true;
+ }
+
+ if (needUpdate) {
+ await expense.update({ media });
+ fixedCount++;
+ console.log(`✓ 修复经费 #${expense.id}`);
+ }
+ }
+
+ console.log(`\n修复完成!共修复 ${fixedCount} 条记录`);
+ process.exit(0);
+ } catch (error) {
+ console.error('修复失败:', error);
+ process.exit(1);
+ }
+}
+
+fixExpenseMedia();
diff --git a/backend/init-admin.js b/backend/init-admin.js
index 9b60001..0ef2781 100644
--- a/backend/init-admin.js
+++ b/backend/init-admin.js
@@ -5,39 +5,66 @@ const { Admin, sequelize } = require('./models');
async function initAdmin() {
try {
- // 使用 force: false 避免 alter 表时的问题
- // 如果表不存在则创建,存在则跳过
await sequelize.sync({ force: false });
- // 检查是否已存在管理员账号
- const existingAdmin = await Admin.findOne({ where: { username: 'admin' } });
+ // 检查role字段是否存在
+ const [results] = await sequelize.query("PRAGMA table_info(Admins);");
+ const hasRoleColumn = results.some(col => col.name === 'role');
- if (existingAdmin) {
- console.log('✓ 管理员账号已存在');
- console.log(' 用户名: admin');
- console.log(' 提示: 如需重置密码,请删除数据库文件后重新初始化');
- process.exit(0);
+ if (!hasRoleColumn) {
+ console.log('⚠️ role字段不存在,请先运行迁移: node backend/migrations/index.js');
+ console.log(' 或运行: npm run migrate (在backend目录)');
+ process.exit(1);
return;
}
- const hashedPassword = bcrypt.hashSync('admin123', 10);
- await Admin.create({
- username: 'admin',
- password: hashedPassword,
- name: '管理员'
- });
+ // 检查yvan超级管理员
+ const yvan = await Admin.findOne({ where: { username: 'yvan' } });
+ if (!yvan) {
+ const hashedPassword = bcrypt.hashSync('Xu950329.', 10);
+ await Admin.create({
+ username: 'yvan',
+ password: hashedPassword,
+ name: 'Yvan',
+ role: 'super'
+ });
+ console.log('✓ 超级管理员yvan创建成功');
+ } else {
+ // 确保yvan是super角色
+ if (yvan.role !== 'super') {
+ await yvan.update({ role: 'super' });
+ console.log('✓ 超级管理员yvan角色已更新');
+ } else {
+ console.log('✓ 超级管理员yvan已存在');
+ }
+ }
- console.log('✓ 管理员账号创建成功');
- console.log(' 用户名: admin');
- console.log(' 密码: admin123');
+ // 检查默认管理员
+ const admin = await Admin.findOne({ where: { username: 'admin' } });
+ if (!admin) {
+ const hashedPassword = bcrypt.hashSync('admin123', 10);
+ await Admin.create({
+ username: 'admin',
+ password: hashedPassword,
+ name: '管理员',
+ role: 'admin'
+ });
+ console.log('✓ 默认管理员admin创建成功');
+ } else {
+ // 确保admin是admin角色
+ if (!admin.role || admin.role === 'super') {
+ await admin.update({ role: 'admin' });
+ console.log('✓ 默认管理员admin角色已更新');
+ } else {
+ console.log('✓ 默认管理员admin已存在');
+ }
+ }
+
+ console.log('\n初始化完成!');
process.exit(0);
} catch (error) {
console.error('✗ 初始化失败:', error.message);
- if (error.name === 'SequelizeUniqueConstraintError') {
- console.error(' 原因: 管理员账号已存在');
- } else {
- console.error(' 详细错误:', error);
- }
+ console.error(' 详细错误:', error);
process.exit(1);
}
}
diff --git a/backend/middleware/authSuper.js b/backend/middleware/authSuper.js
new file mode 100644
index 0000000..3c28cfa
--- /dev/null
+++ b/backend/middleware/authSuper.js
@@ -0,0 +1,27 @@
+const jwt = require('jsonwebtoken');
+const { Admin } = require('../models');
+
+const authSuper = async (req, res, next) => {
+ const token = req.header('Authorization')?.replace('Bearer ', '');
+
+ if (!token) {
+ return res.status(401).json({ error: '未授权访问' });
+ }
+
+ try {
+ const decoded = jwt.verify(token, process.env.JWT_SECRET);
+ const admin = await Admin.findByPk(decoded.id);
+
+ if (!admin || admin.role !== 'super') {
+ return res.status(403).json({ error: '需要超级管理员权限' });
+ }
+
+ req.adminId = decoded.id;
+ req.admin = admin;
+ next();
+ } catch (error) {
+ res.status(401).json({ error: '无效的token' });
+ }
+};
+
+module.exports = authSuper;
diff --git a/backend/migrations/0005-add-prize-source.js b/backend/migrations/0005-add-prize-source.js
new file mode 100644
index 0000000..6fe149b
--- /dev/null
+++ b/backend/migrations/0005-add-prize-source.js
@@ -0,0 +1,39 @@
+module.exports = {
+ async up(manager) {
+ console.log(' - 添加奖品来源字段...');
+
+ // 添加source字段,默认值为purchase
+ await manager.executeSql(`
+ ALTER TABLE Prizes ADD COLUMN source TEXT DEFAULT 'purchase';
+ `);
+
+ // 添加sponsor字段
+ await manager.executeSql(`
+ ALTER TABLE Prizes ADD COLUMN sponsor TEXT;
+ `);
+
+ // 为现有数据设置默认值
+ await manager.executeSql(`
+ UPDATE Prizes SET source = 'purchase' WHERE source IS NULL;
+ `);
+
+ console.log(' - 奖品来源字段添加完成');
+ },
+
+ async down(manager) {
+ console.log(' - 移除奖品来源字段...');
+
+ // SQLite不支持DROP COLUMN,需要重建表
+ await manager.executeSql(`
+ CREATE TABLE Prizes_backup AS SELECT
+ id, activityId, name, level, description, image, quantity, sortOrder, createdAt, updatedAt
+ FROM Prizes;
+ `);
+
+ await manager.executeSql(`DROP TABLE Prizes;`);
+
+ await manager.executeSql(`ALTER TABLE Prizes_backup RENAME TO Prizes;`);
+
+ console.log(' - 奖品来源字段移除完成');
+ }
+};
diff --git a/backend/migrations/0006-add-grand-prize-level.js b/backend/migrations/0006-add-grand-prize-level.js
new file mode 100644
index 0000000..502df9e
--- /dev/null
+++ b/backend/migrations/0006-add-grand-prize-level.js
@@ -0,0 +1,74 @@
+module.exports = {
+ async up(manager) {
+ console.log(' - 添加特等奖等级...');
+
+ // SQLite不支持直接修改CHECK约束,需要重建表
+ // 1. 创建新表结构(包含特等奖)
+ await manager.executeSql(`
+ CREATE TABLE Prizes_new (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ activityId INTEGER NOT NULL,
+ name TEXT NOT NULL,
+ level TEXT NOT NULL CHECK(level IN ('特等奖', '一等奖', '二等奖', '三等奖', '参与奖', '伴手礼')),
+ description TEXT,
+ image TEXT,
+ quantity INTEGER DEFAULT 1,
+ sortOrder INTEGER DEFAULT 0,
+ source TEXT DEFAULT 'purchase',
+ sponsor TEXT,
+ createdAt DATETIME NOT NULL,
+ updatedAt DATETIME NOT NULL
+ );
+ `);
+
+ // 2. 复制数据
+ await manager.executeSql(`
+ INSERT INTO Prizes_new
+ SELECT * FROM Prizes;
+ `);
+
+ // 3. 删除旧表
+ await manager.executeSql(`DROP TABLE Prizes;`);
+
+ // 4. 重命名新表
+ await manager.executeSql(`ALTER TABLE Prizes_new RENAME TO Prizes;`);
+
+ console.log(' - 特等奖等级添加完成');
+ },
+
+ async down(manager) {
+ console.log(' - 移除特等奖等级...');
+
+ // 1. 创建旧表结构(不包含特等奖)
+ await manager.executeSql(`
+ CREATE TABLE Prizes_old (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ activityId INTEGER NOT NULL,
+ name TEXT NOT NULL,
+ level TEXT NOT NULL CHECK(level IN ('一等奖', '二等奖', '三等奖', '参与奖', '伴手礼')),
+ description TEXT,
+ image TEXT,
+ quantity INTEGER DEFAULT 1,
+ sortOrder INTEGER DEFAULT 0,
+ source TEXT DEFAULT 'purchase',
+ sponsor TEXT,
+ createdAt DATETIME NOT NULL,
+ updatedAt DATETIME NOT NULL
+ );
+ `);
+
+ // 2. 复制数据(排除特等奖)
+ await manager.executeSql(`
+ INSERT INTO Prizes_old
+ SELECT * FROM Prizes WHERE level != '特等奖';
+ `);
+
+ // 3. 删除新表
+ await manager.executeSql(`DROP TABLE Prizes;`);
+
+ // 4. 重命名旧表
+ await manager.executeSql(`ALTER TABLE Prizes_old RENAME TO Prizes;`);
+
+ console.log(' - 特等奖等级移除完成');
+ }
+};
diff --git a/backend/migrations/0007-add-admin-role.js b/backend/migrations/0007-add-admin-role.js
new file mode 100644
index 0000000..7a9f6cf
--- /dev/null
+++ b/backend/migrations/0007-add-admin-role.js
@@ -0,0 +1,12 @@
+// 添加管理员角色字段
+module.exports = {
+ async up(manager) {
+ console.log(' - 添加 Admins.role 字段...');
+ await manager.executeSql('ALTER TABLE Admins ADD COLUMN role TEXT DEFAULT "admin";');
+ console.log(' - 角色字段添加完成');
+ },
+
+ async down(manager) {
+ await manager.executeSql('ALTER TABLE Admins DROP COLUMN role;');
+ }
+};
diff --git a/backend/migrations/0008-add-fourth-prize-level.js b/backend/migrations/0008-add-fourth-prize-level.js
new file mode 100644
index 0000000..b69ba3f
--- /dev/null
+++ b/backend/migrations/0008-add-fourth-prize-level.js
@@ -0,0 +1,75 @@
+// 添加四等奖等级
+module.exports = {
+ async up(manager) {
+ console.log(' - 添加四等奖等级...');
+
+ // SQLite不支持直接修改CHECK约束,需要重建表
+ // 1. 创建新表结构(包含四等奖)
+ await manager.executeSql(`
+ CREATE TABLE Prizes_new (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ activityId INTEGER NOT NULL,
+ name TEXT NOT NULL,
+ level TEXT NOT NULL CHECK(level IN ('特等奖', '一等奖', '二等奖', '三等奖', '四等奖', '参与奖', '伴手礼')),
+ description TEXT,
+ image TEXT,
+ quantity INTEGER DEFAULT 1,
+ sortOrder INTEGER DEFAULT 0,
+ source TEXT DEFAULT 'purchase',
+ sponsor TEXT,
+ createdAt DATETIME NOT NULL,
+ updatedAt DATETIME NOT NULL
+ );
+ `);
+
+ // 2. 复制数据
+ await manager.executeSql(`
+ INSERT INTO Prizes_new
+ SELECT * FROM Prizes;
+ `);
+
+ // 3. 删除旧表
+ await manager.executeSql(`DROP TABLE Prizes;`);
+
+ // 4. 重命名新表
+ await manager.executeSql(`ALTER TABLE Prizes_new RENAME TO Prizes;`);
+
+ console.log(' - 四等奖等级添加完成');
+ },
+
+ async down(manager) {
+ console.log(' - 移除四等奖等级...');
+
+ // 1. 创建旧表结构(不包含四等奖)
+ await manager.executeSql(`
+ CREATE TABLE Prizes_old (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ activityId INTEGER NOT NULL,
+ name TEXT NOT NULL,
+ level TEXT NOT NULL CHECK(level IN ('特等奖', '一等奖', '二等奖', '三等奖', '参与奖', '伴手礼')),
+ description TEXT,
+ image TEXT,
+ quantity INTEGER DEFAULT 1,
+ sortOrder INTEGER DEFAULT 0,
+ source TEXT DEFAULT 'purchase',
+ sponsor TEXT,
+ createdAt DATETIME NOT NULL,
+ updatedAt DATETIME NOT NULL
+ );
+ `);
+
+ // 2. 复制数据(排除四等奖)
+ await manager.executeSql(`
+ INSERT INTO Prizes_old
+ SELECT * FROM Prizes WHERE level != '四等奖';
+ `);
+
+ // 3. 删除新表
+ await manager.executeSql(`DROP TABLE Prizes;`);
+
+ // 4. 重命名旧表
+ await manager.executeSql(`ALTER TABLE Prizes_old RENAME TO Prizes;`);
+
+ console.log(' - 四等奖等级移除完成');
+ }
+};
diff --git a/backend/migrations/0009-add-group-status.js b/backend/migrations/0009-add-group-status.js
new file mode 100644
index 0000000..89a52d6
--- /dev/null
+++ b/backend/migrations/0009-add-group-status.js
@@ -0,0 +1,20 @@
+/**
+ * 迁移 0009: 添加进群状态字段
+ * 为 Registrations 表添加 groupStatus 字段
+ */
+
+module.exports = {
+ async up(manager) {
+ console.log(' - 添加 Registrations.groupStatus 字段...');
+ await manager.executeSql(`
+ ALTER TABLE Registrations ADD COLUMN groupStatus INTEGER DEFAULT 0;
+ `);
+ },
+
+ async down(manager) {
+ console.log(' - 移除 Registrations.groupStatus 字段...');
+ await manager.executeSql(`
+ ALTER TABLE Registrations DROP COLUMN groupStatus;
+ `);
+ }
+};
diff --git a/backend/models/Admin.js b/backend/models/Admin.js
index dc7eabe..6334b7b 100644
--- a/backend/models/Admin.js
+++ b/backend/models/Admin.js
@@ -16,7 +16,12 @@ const Admin = sequelize.define('Admin', {
type: DataTypes.STRING,
allowNull: false
},
- name: DataTypes.STRING
+ name: DataTypes.STRING,
+ role: {
+ type: DataTypes.STRING,
+ allowNull: false,
+ defaultValue: 'admin'
+ }
});
module.exports = Admin;
diff --git a/backend/models/Prize.js b/backend/models/Prize.js
index 2a5d122..c05a221 100644
--- a/backend/models/Prize.js
+++ b/backend/models/Prize.js
@@ -16,7 +16,7 @@ const Prize = sequelize.define('Prize', {
allowNull: false
},
level: {
- type: DataTypes.ENUM('一等奖', '二等奖', '三等奖', '参与奖', '伴手礼'),
+ type: DataTypes.ENUM('特等奖', '一等奖', '二等奖', '三等奖', '四等奖', '参与奖', '伴手礼'),
allowNull: false
},
description: DataTypes.TEXT,
@@ -29,6 +29,15 @@ const Prize = sequelize.define('Prize', {
type: DataTypes.INTEGER,
defaultValue: 0,
comment: '排序值,数字越小越靠前'
+ },
+ source: {
+ type: DataTypes.ENUM('member', 'sponsor', 'purchase'),
+ defaultValue: 'purchase',
+ comment: '来源: member-群友赞助, sponsor-赞助商提供, purchase-采购'
+ },
+ sponsor: {
+ type: DataTypes.STRING,
+ comment: '赞助者名称(群友或赞助商)'
}
});
diff --git a/backend/models/Registration.js b/backend/models/Registration.js
index c36d62e..468f21e 100644
--- a/backend/models/Registration.js
+++ b/backend/models/Registration.js
@@ -34,7 +34,11 @@ const Registration = sequelize.define('Registration', {
type: DataTypes.BOOLEAN,
defaultValue: false
},
- checkInTime: DataTypes.DATE
+ checkInTime: DataTypes.DATE,
+ groupStatus: {
+ type: DataTypes.BOOLEAN,
+ defaultValue: false
+ }
});
module.exports = Registration;
diff --git a/backend/models/VenueImage.js b/backend/models/VenueImage.js
new file mode 100644
index 0000000..b8a07c5
--- /dev/null
+++ b/backend/models/VenueImage.js
@@ -0,0 +1,25 @@
+const { DataTypes } = require('sequelize');
+const sequelize = require('./db');
+
+const VenueImage = sequelize.define('VenueImage', {
+ id: {
+ type: DataTypes.INTEGER,
+ primaryKey: true,
+ autoIncrement: true
+ },
+ activityId: {
+ type: DataTypes.INTEGER,
+ allowNull: false
+ },
+ image: {
+ type: DataTypes.STRING,
+ allowNull: false
+ },
+ sortOrder: {
+ type: DataTypes.INTEGER,
+ defaultValue: 0,
+ comment: '排序值,数字越小越靠前'
+ }
+});
+
+module.exports = VenueImage;
diff --git a/backend/models/index.js b/backend/models/index.js
index fb667fd..0d2e6a8 100644
--- a/backend/models/index.js
+++ b/backend/models/index.js
@@ -6,6 +6,7 @@ const Photo = require('./Photo');
const Sponsor = require('./Sponsor');
const Prize = require('./Prize');
const Admin = require('./Admin');
+const VenueImage = require('./VenueImage');
// 同步数据库
// 使用 force: false 避免 SQLite alter 表时的约束冲突问题
@@ -23,5 +24,6 @@ module.exports = {
Photo,
Sponsor,
Prize,
- Admin
+ Admin,
+ VenueImage
};
diff --git a/backend/package-lock.json b/backend/package-lock.json
index 823a4b7..e203b6c 100644
--- a/backend/package-lock.json
+++ b/backend/package-lock.json
@@ -16,12 +16,69 @@
"jsonwebtoken": "^9.0.2",
"multer": "^1.4.5-lts.1",
"sequelize": "^6.33.0",
- "sql.js": "^1.14.1"
+ "sql.js": "^1.14.1",
+ "sqlite3": "^5.1.7"
},
"devDependencies": {
"nodemon": "^3.0.1"
}
},
+ "node_modules/@gar/promisify": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmmirror.com/@gar/promisify/-/promisify-1.1.3.tgz",
+ "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/@npmcli/fs": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmmirror.com/@npmcli/fs/-/fs-1.1.1.tgz",
+ "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==",
+ "license": "ISC",
+ "optional": true,
+ "dependencies": {
+ "@gar/promisify": "^1.0.1",
+ "semver": "^7.3.5"
+ }
+ },
+ "node_modules/@npmcli/move-file": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmmirror.com/@npmcli/move-file/-/move-file-1.1.2.tgz",
+ "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==",
+ "deprecated": "This functionality has been moved to @npmcli/fs",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "mkdirp": "^1.0.4",
+ "rimraf": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@npmcli/move-file/node_modules/mkdirp": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-1.0.4.tgz",
+ "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
+ "license": "MIT",
+ "optional": true,
+ "bin": {
+ "mkdirp": "bin/cmd.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@tootallnate/once": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmmirror.com/@tootallnate/once/-/once-1.1.2.tgz",
+ "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/@types/debug": {
"version": "4.1.13",
"resolved": "https://registry.npmmirror.com/@types/debug/-/debug-4.1.13.tgz",
@@ -52,6 +109,13 @@
"integrity": "sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==",
"license": "MIT"
},
+ "node_modules/abbrev": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmmirror.com/abbrev/-/abbrev-1.1.1.tgz",
+ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
+ "license": "ISC",
+ "optional": true
+ },
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmmirror.com/accepts/-/accepts-1.3.8.tgz",
@@ -65,6 +129,81 @@
"node": ">= 0.6"
}
},
+ "node_modules/agent-base": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmmirror.com/agent-base/-/agent-base-6.0.2.tgz",
+ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/agent-base/node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/agent-base/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/agentkeepalive": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmmirror.com/agentkeepalive/-/agentkeepalive-4.6.0.tgz",
+ "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "humanize-ms": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 8.0.0"
+ }
+ },
+ "node_modules/aggregate-error": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmmirror.com/aggregate-error/-/aggregate-error-3.1.0.tgz",
+ "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "clean-stack": "^2.0.0",
+ "indent-string": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/anymatch": {
"version": "3.1.3",
"resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz",
@@ -85,6 +224,43 @@
"integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==",
"license": "MIT"
},
+ "node_modules/aproba": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmmirror.com/aproba/-/aproba-2.1.0.tgz",
+ "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==",
+ "license": "ISC",
+ "optional": true
+ },
+ "node_modules/are-we-there-yet": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmmirror.com/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz",
+ "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==",
+ "deprecated": "This package is no longer supported.",
+ "license": "ISC",
+ "optional": true,
+ "dependencies": {
+ "delegates": "^1.0.0",
+ "readable-stream": "^3.6.0"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/are-we-there-yet/node_modules/readable-stream": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/array-flatten/-/array-flatten-1.1.1.tgz",
@@ -291,6 +467,49 @@
"node": ">= 0.8"
}
},
+ "node_modules/cacache": {
+ "version": "15.3.0",
+ "resolved": "https://registry.npmmirror.com/cacache/-/cacache-15.3.0.tgz",
+ "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==",
+ "license": "ISC",
+ "optional": true,
+ "dependencies": {
+ "@npmcli/fs": "^1.0.0",
+ "@npmcli/move-file": "^1.0.1",
+ "chownr": "^2.0.0",
+ "fs-minipass": "^2.0.0",
+ "glob": "^7.1.4",
+ "infer-owner": "^1.0.4",
+ "lru-cache": "^6.0.0",
+ "minipass": "^3.1.1",
+ "minipass-collect": "^1.0.2",
+ "minipass-flush": "^1.0.5",
+ "minipass-pipeline": "^1.2.2",
+ "mkdirp": "^1.0.3",
+ "p-map": "^4.0.0",
+ "promise-inflight": "^1.0.1",
+ "rimraf": "^3.0.2",
+ "ssri": "^8.0.1",
+ "tar": "^6.0.2",
+ "unique-filename": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/cacache/node_modules/mkdirp": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-1.0.4.tgz",
+ "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
+ "license": "MIT",
+ "optional": true,
+ "bin": {
+ "mkdirp": "bin/cmd.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
@@ -345,6 +564,42 @@
"fsevents": "~2.3.2"
}
},
+ "node_modules/chownr": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmmirror.com/chownr/-/chownr-2.0.0.tgz",
+ "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/clean-stack": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmmirror.com/clean-stack/-/clean-stack-2.2.0.tgz",
+ "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/color-support": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmmirror.com/color-support/-/color-support-1.1.3.tgz",
+ "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==",
+ "license": "ISC",
+ "optional": true,
+ "bin": {
+ "color-support": "bin.js"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/concat-stream": {
"version": "1.6.2",
"resolved": "https://registry.npmmirror.com/concat-stream/-/concat-stream-1.6.2.tgz",
@@ -360,6 +615,13 @@
"typedarray": "^0.0.6"
}
},
+ "node_modules/console-control-strings": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmmirror.com/console-control-strings/-/console-control-strings-1.1.0.tgz",
+ "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==",
+ "license": "ISC",
+ "optional": true
+ },
"node_modules/content-disposition": {
"version": "0.5.4",
"resolved": "https://registry.npmmirror.com/content-disposition/-/content-disposition-0.5.4.tgz",
@@ -452,6 +714,13 @@
"node": ">=4.0.0"
}
},
+ "node_modules/delegates": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmmirror.com/delegates/-/delegates-1.0.0.tgz",
+ "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==",
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz",
@@ -527,6 +796,13 @@
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
"license": "MIT"
},
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-2.0.0.tgz",
@@ -536,6 +812,29 @@
"node": ">= 0.8"
}
},
+ "node_modules/encoding": {
+ "version": "0.1.13",
+ "resolved": "https://registry.npmmirror.com/encoding/-/encoding-0.1.13.tgz",
+ "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "iconv-lite": "^0.6.2"
+ }
+ },
+ "node_modules/encoding/node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/end-of-stream": {
"version": "1.4.5",
"resolved": "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.5.tgz",
@@ -545,6 +844,23 @@
"once": "^1.4.0"
}
},
+ "node_modules/env-paths": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmmirror.com/env-paths/-/env-paths-2.2.1.tgz",
+ "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/err-code": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmmirror.com/err-code/-/err-code-2.0.3.tgz",
+ "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==",
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz",
@@ -706,6 +1022,25 @@
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
"license": "MIT"
},
+ "node_modules/fs-minipass": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmmirror.com/fs-minipass/-/fs-minipass-2.1.0.tgz",
+ "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
+ "license": "ISC",
+ "dependencies": {
+ "minipass": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "license": "ISC",
+ "optional": true
+ },
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz",
@@ -730,6 +1065,27 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/gauge": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmmirror.com/gauge/-/gauge-4.0.4.tgz",
+ "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==",
+ "deprecated": "This package is no longer supported.",
+ "license": "ISC",
+ "optional": true,
+ "dependencies": {
+ "aproba": "^1.0.3 || ^2.0.0",
+ "color-support": "^1.1.3",
+ "console-control-strings": "^1.1.0",
+ "has-unicode": "^2.0.1",
+ "signal-exit": "^3.0.7",
+ "string-width": "^4.2.3",
+ "strip-ansi": "^6.0.1",
+ "wide-align": "^1.1.5"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
@@ -773,6 +1129,28 @@
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
"license": "MIT"
},
+ "node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Glob versions prior to v9 are no longer supported",
+ "license": "ISC",
+ "optional": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz",
@@ -786,6 +1164,37 @@
"node": ">= 6"
}
},
+ "node_modules/glob/node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/glob/node_modules/brace-expansion": {
+ "version": "1.1.13",
+ "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.13.tgz",
+ "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/glob/node_modules/minimatch": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.5.tgz",
+ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
+ "license": "ISC",
+ "optional": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz",
@@ -798,6 +1207,13 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "license": "ISC",
+ "optional": true
+ },
"node_modules/has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-3.0.0.tgz",
@@ -820,6 +1236,13 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/has-unicode": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/has-unicode/-/has-unicode-2.0.1.tgz",
+ "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==",
+ "license": "ISC",
+ "optional": true
+ },
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz",
@@ -832,6 +1255,13 @@
"node": ">= 0.4"
}
},
+ "node_modules/http-cache-semantics": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmmirror.com/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz",
+ "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==",
+ "license": "BSD-2-Clause",
+ "optional": true
+ },
"node_modules/http-errors": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.1.tgz",
@@ -852,6 +1282,95 @@
"url": "https://opencollective.com/express"
}
},
+ "node_modules/http-proxy-agent": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmmirror.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz",
+ "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@tootallnate/once": "1",
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/http-proxy-agent/node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/http-proxy-agent/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
+ "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/https-proxy-agent/node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/https-proxy-agent/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/humanize-ms": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmmirror.com/humanize-ms/-/humanize-ms-1.2.1.tgz",
+ "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "ms": "^2.0.0"
+ }
+ },
"node_modules/iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz",
@@ -891,6 +1410,33 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmmirror.com/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/indent-string": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmmirror.com/indent-string/-/indent-string-4.0.0.tgz",
+ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/infer-owner": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmmirror.com/infer-owner/-/infer-owner-1.0.4.tgz",
+ "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==",
+ "license": "ISC",
+ "optional": true
+ },
"node_modules/inflection": {
"version": "1.13.4",
"resolved": "https://registry.npmmirror.com/inflection/-/inflection-1.13.4.tgz",
@@ -900,6 +1446,18 @@
],
"license": "MIT"
},
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
+ "license": "ISC",
+ "optional": true,
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz",
@@ -912,6 +1470,16 @@
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
"license": "ISC"
},
+ "node_modules/ip-address": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmmirror.com/ip-address/-/ip-address-10.1.0.tgz",
+ "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">= 12"
+ }
+ },
"node_modules/ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
@@ -944,6 +1512,16 @@
"node": ">=0.10.0"
}
},
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz",
@@ -957,6 +1535,13 @@
"node": ">=0.10.0"
}
},
+ "node_modules/is-lambda": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmmirror.com/is-lambda/-/is-lambda-1.0.1.tgz",
+ "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==",
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz",
@@ -973,6 +1558,13 @@
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
"license": "MIT"
},
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "license": "ISC",
+ "optional": true
+ },
"node_modules/jsonwebtoken": {
"version": "9.0.3",
"resolved": "https://registry.npmmirror.com/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz",
@@ -1070,6 +1662,47 @@
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
"license": "MIT"
},
+ "node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "license": "ISC",
+ "optional": true,
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/make-fetch-happen": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmmirror.com/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz",
+ "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==",
+ "license": "ISC",
+ "optional": true,
+ "dependencies": {
+ "agentkeepalive": "^4.1.3",
+ "cacache": "^15.2.0",
+ "http-cache-semantics": "^4.1.0",
+ "http-proxy-agent": "^4.0.1",
+ "https-proxy-agent": "^5.0.0",
+ "is-lambda": "^1.0.1",
+ "lru-cache": "^6.0.0",
+ "minipass": "^3.1.3",
+ "minipass-collect": "^1.0.2",
+ "minipass-fetch": "^1.3.2",
+ "minipass-flush": "^1.0.5",
+ "minipass-pipeline": "^1.2.4",
+ "negotiator": "^0.6.2",
+ "promise-retry": "^2.0.1",
+ "socks-proxy-agent": "^6.0.0",
+ "ssri": "^8.0.0"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -1176,6 +1809,101 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/minipass": {
+ "version": "3.3.6",
+ "resolved": "https://registry.npmmirror.com/minipass/-/minipass-3.3.6.tgz",
+ "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/minipass-collect": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmmirror.com/minipass-collect/-/minipass-collect-1.0.2.tgz",
+ "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==",
+ "license": "ISC",
+ "optional": true,
+ "dependencies": {
+ "minipass": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/minipass-fetch": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmmirror.com/minipass-fetch/-/minipass-fetch-1.4.1.tgz",
+ "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "minipass": "^3.1.0",
+ "minipass-sized": "^1.0.3",
+ "minizlib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "optionalDependencies": {
+ "encoding": "^0.1.12"
+ }
+ },
+ "node_modules/minipass-flush": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmmirror.com/minipass-flush/-/minipass-flush-1.0.7.tgz",
+ "integrity": "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==",
+ "license": "BlueOak-1.0.0",
+ "optional": true,
+ "dependencies": {
+ "minipass": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/minipass-pipeline": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmmirror.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz",
+ "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==",
+ "license": "ISC",
+ "optional": true,
+ "dependencies": {
+ "minipass": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/minipass-sized": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmmirror.com/minipass-sized/-/minipass-sized-1.0.3.tgz",
+ "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==",
+ "license": "ISC",
+ "optional": true,
+ "dependencies": {
+ "minipass": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/minizlib": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmmirror.com/minizlib/-/minizlib-2.1.2.tgz",
+ "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
+ "license": "MIT",
+ "dependencies": {
+ "minipass": "^3.0.0",
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
"node_modules/mkdirp": {
"version": "0.5.6",
"resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-0.5.6.tgz",
@@ -1267,6 +1995,37 @@
"node": ">=10"
}
},
+ "node_modules/node-addon-api": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmmirror.com/node-addon-api/-/node-addon-api-7.1.1.tgz",
+ "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
+ "license": "MIT"
+ },
+ "node_modules/node-gyp": {
+ "version": "8.4.1",
+ "resolved": "https://registry.npmmirror.com/node-gyp/-/node-gyp-8.4.1.tgz",
+ "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "env-paths": "^2.2.0",
+ "glob": "^7.1.4",
+ "graceful-fs": "^4.2.6",
+ "make-fetch-happen": "^9.1.0",
+ "nopt": "^5.0.0",
+ "npmlog": "^6.0.0",
+ "rimraf": "^3.0.2",
+ "semver": "^7.3.5",
+ "tar": "^6.1.2",
+ "which": "^2.0.2"
+ },
+ "bin": {
+ "node-gyp": "bin/node-gyp.js"
+ },
+ "engines": {
+ "node": ">= 10.12.0"
+ }
+ },
"node_modules/nodemon": {
"version": "3.1.14",
"resolved": "https://registry.npmmirror.com/nodemon/-/nodemon-3.1.14.tgz",
@@ -1321,6 +2080,22 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/nopt": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmmirror.com/nopt/-/nopt-5.0.0.tgz",
+ "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==",
+ "license": "ISC",
+ "optional": true,
+ "dependencies": {
+ "abbrev": "1"
+ },
+ "bin": {
+ "nopt": "bin/nopt.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz",
@@ -1331,6 +2106,23 @@
"node": ">=0.10.0"
}
},
+ "node_modules/npmlog": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmmirror.com/npmlog/-/npmlog-6.0.2.tgz",
+ "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==",
+ "deprecated": "This package is no longer supported.",
+ "license": "ISC",
+ "optional": true,
+ "dependencies": {
+ "are-we-there-yet": "^3.0.0",
+ "console-control-strings": "^1.1.0",
+ "gauge": "^4.0.3",
+ "set-blocking": "^2.0.0"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz",
@@ -1373,6 +2165,22 @@
"wrappy": "1"
}
},
+ "node_modules/p-map": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmmirror.com/p-map/-/p-map-4.0.0.tgz",
+ "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "aggregate-error": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz",
@@ -1382,6 +2190,16 @@
"node": ">= 0.8"
}
},
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/path-to-regexp": {
"version": "0.1.13",
"resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-0.1.13.tgz",
@@ -1440,6 +2258,27 @@
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
"license": "MIT"
},
+ "node_modules/promise-inflight": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmmirror.com/promise-inflight/-/promise-inflight-1.0.1.tgz",
+ "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==",
+ "license": "ISC",
+ "optional": true
+ },
+ "node_modules/promise-retry": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/promise-retry/-/promise-retry-2.0.1.tgz",
+ "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "err-code": "^2.0.2",
+ "retry": "^0.12.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmmirror.com/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -1558,12 +2397,39 @@
"node": ">=8.10.0"
}
},
+ "node_modules/retry": {
+ "version": "0.12.0",
+ "resolved": "https://registry.npmmirror.com/retry/-/retry-0.12.0.tgz",
+ "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
"node_modules/retry-as-promised": {
"version": "7.1.1",
"resolved": "https://registry.npmmirror.com/retry-as-promised/-/retry-as-promised-7.1.1.tgz",
"integrity": "sha512-hMD7odLOt3LkTjcif8aRZqi/hybjpLNgSk5oF5FCowfCjok6LukpN2bDX7R5wDmbgBQFn7YoBxSagmtXHaJYJw==",
"license": "MIT"
},
+ "node_modules/rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmmirror.com/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "deprecated": "Rimraf versions prior to v4 are no longer supported",
+ "license": "ISC",
+ "optional": true,
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -1741,6 +2607,13 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/set-blocking": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmmirror.com/set-blocking/-/set-blocking-2.0.0.tgz",
+ "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
+ "license": "ISC",
+ "optional": true
+ },
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz",
@@ -1819,6 +2692,13 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "license": "ISC",
+ "optional": true
+ },
"node_modules/simple-concat": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/simple-concat/-/simple-concat-1.0.1.tgz",
@@ -1877,12 +2757,115 @@
"node": ">=10"
}
},
+ "node_modules/smart-buffer": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmmirror.com/smart-buffer/-/smart-buffer-4.2.0.tgz",
+ "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">= 6.0.0",
+ "npm": ">= 3.0.0"
+ }
+ },
+ "node_modules/socks": {
+ "version": "2.8.7",
+ "resolved": "https://registry.npmmirror.com/socks/-/socks-2.8.7.tgz",
+ "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "ip-address": "^10.0.1",
+ "smart-buffer": "^4.2.0"
+ },
+ "engines": {
+ "node": ">= 10.0.0",
+ "npm": ">= 3.0.0"
+ }
+ },
+ "node_modules/socks-proxy-agent": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmmirror.com/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz",
+ "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "agent-base": "^6.0.2",
+ "debug": "^4.3.3",
+ "socks": "^2.6.2"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/socks-proxy-agent/node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/socks-proxy-agent/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/sql.js": {
"version": "1.14.1",
"resolved": "https://registry.npmmirror.com/sql.js/-/sql.js-1.14.1.tgz",
"integrity": "sha512-gcj8zBWU5cFsi9WUP+4bFNXAyF1iRpA3LLyS/DP5xlrNzGmPIizUeBggKa8DbDwdqaKwUcTEnChtd2grWo/x/A==",
"license": "MIT"
},
+ "node_modules/sqlite3": {
+ "version": "5.1.7",
+ "resolved": "https://registry.npmmirror.com/sqlite3/-/sqlite3-5.1.7.tgz",
+ "integrity": "sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==",
+ "hasInstallScript": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "bindings": "^1.5.0",
+ "node-addon-api": "^7.0.0",
+ "prebuild-install": "^7.1.1",
+ "tar": "^6.1.11"
+ },
+ "optionalDependencies": {
+ "node-gyp": "8.x"
+ },
+ "peerDependencies": {
+ "node-gyp": "8.x"
+ },
+ "peerDependenciesMeta": {
+ "node-gyp": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/ssri": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmmirror.com/ssri/-/ssri-8.0.1.tgz",
+ "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==",
+ "license": "ISC",
+ "optional": true,
+ "dependencies": {
+ "minipass": "^3.1.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
"node_modules/statuses": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.2.tgz",
@@ -1915,6 +2898,34 @@
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"license": "MIT"
},
+ "node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/strip-json-comments": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
@@ -1937,6 +2948,23 @@
"node": ">=4"
}
},
+ "node_modules/tar": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmmirror.com/tar/-/tar-6.2.1.tgz",
+ "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==",
+ "license": "ISC",
+ "dependencies": {
+ "chownr": "^2.0.0",
+ "fs-minipass": "^2.0.0",
+ "minipass": "^5.0.0",
+ "minizlib": "^2.1.1",
+ "mkdirp": "^1.0.3",
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/tar-fs": {
"version": "2.1.4",
"resolved": "https://registry.npmmirror.com/tar-fs/-/tar-fs-2.1.4.tgz",
@@ -1985,6 +3013,27 @@
"node": ">= 6"
}
},
+ "node_modules/tar/node_modules/minipass": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmmirror.com/minipass/-/minipass-5.0.0.tgz",
+ "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/tar/node_modules/mkdirp": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-1.0.4.tgz",
+ "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
+ "license": "MIT",
+ "bin": {
+ "mkdirp": "bin/cmd.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -2067,6 +3116,26 @@
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
"license": "MIT"
},
+ "node_modules/unique-filename": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmmirror.com/unique-filename/-/unique-filename-1.1.1.tgz",
+ "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==",
+ "license": "ISC",
+ "optional": true,
+ "dependencies": {
+ "unique-slug": "^2.0.0"
+ }
+ },
+ "node_modules/unique-slug": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmmirror.com/unique-slug/-/unique-slug-2.0.2.tgz",
+ "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==",
+ "license": "ISC",
+ "optional": true,
+ "dependencies": {
+ "imurmurhash": "^0.1.4"
+ }
+ },
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz",
@@ -2118,6 +3187,32 @@
"node": ">= 0.8"
}
},
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "license": "ISC",
+ "optional": true,
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/wide-align": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmmirror.com/wide-align/-/wide-align-1.1.5.tgz",
+ "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
+ "license": "ISC",
+ "optional": true,
+ "dependencies": {
+ "string-width": "^1.0.2 || 2 || 3 || 4"
+ }
+ },
"node_modules/wkx": {
"version": "0.5.0",
"resolved": "https://registry.npmmirror.com/wkx/-/wkx-0.5.0.tgz",
@@ -2141,6 +3236,12 @@
"engines": {
"node": ">=0.4"
}
+ },
+ "node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "license": "ISC"
}
}
}
diff --git a/backend/routes/activity.js b/backend/routes/activity.js
index a249cab..305b9fd 100644
--- a/backend/routes/activity.js
+++ b/backend/routes/activity.js
@@ -1,6 +1,6 @@
const express = require('express');
const router = express.Router();
-const { Activity, Photo, Sponsor, Expense, Registration, Prize } = require('../models');
+const { Activity, Photo, Sponsor, Expense, Registration, Prize, VenueImage } = require('../models');
const { upload, getFileUrls } = require('../middleware/upload');
// 获取活动列表
@@ -8,7 +8,11 @@ router.get('/activities', async (req, res) => {
try {
const { status } = req.query;
const where = status ? { status } : {};
- const activities = await Activity.findAll({ where, order: [['startTime', 'DESC']] });
+ const activities = await Activity.findAll({
+ where,
+ order: [['startTime', 'DESC']],
+ raw: true
+ });
res.json(activities);
} catch (error) {
res.status(500).json({ error: error.message });
@@ -18,22 +22,42 @@ router.get('/activities', async (req, res) => {
// 获取活动详情
router.get('/activities/:id', async (req, res) => {
try {
- const activity = await Activity.findByPk(req.params.id);
+ const activity = await Activity.findByPk(req.params.id, { raw: true });
+ if (!activity) {
+ return res.status(404).json({ error: '活动不存在' });
+ }
+
// 只返回已审核通过的照片
const photos = await Photo.findAll({
- where: { activityId: req.params.id, status: 'approved' }
+ where: { activityId: req.params.id, status: 'approved' },
+ raw: true
});
const sponsors = await Sponsor.findAll({
where: { activityId: req.params.id },
- order: [['sortOrder', 'ASC'], ['id', 'ASC']]
+ order: [['sortOrder', 'ASC'], ['id', 'ASC']],
+ raw: true
});
- const expenses = await Expense.findAll({ where: { activityId: req.params.id } });
+ const expenses = await Expense.findAll({
+ where: { activityId: req.params.id },
+ raw: true
+ });
+ // 手动解析JSON字段
+ const parsedExpenses = expenses.map(expense => ({
+ ...expense,
+ media: typeof expense.media === 'string' ? JSON.parse(expense.media || '[]') : (expense.media || [])
+ }));
const prizes = await Prize.findAll({
where: { activityId: req.params.id },
- order: [['sortOrder', 'ASC'], ['id', 'ASC']]
+ order: [['sortOrder', 'ASC'], ['id', 'ASC']],
+ raw: true
+ });
+ const venueImages = await VenueImage.findAll({
+ where: { activityId: req.params.id },
+ order: [['sortOrder', 'ASC'], ['id', 'ASC']],
+ raw: true
});
- res.json({ activity, photos, sponsors, expenses, prizes });
+ res.json({ activity, photos, sponsors, expenses: parsedExpenses, prizes, venueImages: venueImages.map(img => img.image).filter(Boolean) });
} catch (error) {
res.status(500).json({ error: error.message });
}
@@ -42,8 +66,30 @@ router.get('/activities/:id', async (req, res) => {
// 获取活动经费(公开接口)
router.get('/activities/:id/expenses', async (req, res) => {
try {
- const expenses = await Expense.findAll({ where: { activityId: req.params.id } });
- res.json(expenses);
+ const expenses = await Expense.findAll({
+ where: { activityId: req.params.id },
+ raw: true
+ });
+ // 手动解析JSON字段
+ const parsedExpenses = expenses.map(expense => ({
+ ...expense,
+ media: typeof expense.media === 'string' ? JSON.parse(expense.media || '[]') : (expense.media || [])
+ }));
+ res.json(parsedExpenses);
+ } catch (error) {
+ res.status(500).json({ error: error.message });
+ }
+});
+
+// 获取场地图片列表
+router.get('/activities/:id/venue-images', async (req, res) => {
+ try {
+ const images = await VenueImage.findAll({
+ where: { activityId: req.params.id },
+ order: [['sortOrder', 'ASC'], ['id', 'ASC']],
+ raw: true
+ });
+ res.json(images.map(img => img.image).filter(Boolean));
} catch (error) {
res.status(500).json({ error: error.message });
}
diff --git a/backend/routes/admin.js b/backend/routes/admin.js
index 2733384..6efdd2a 100644
--- a/backend/routes/admin.js
+++ b/backend/routes/admin.js
@@ -32,6 +32,20 @@ router.post('/login', async (req, res) => {
}
});
+// Token续期
+router.post('/refresh-token', auth, async (req, res) => {
+ try {
+ const admin = await Admin.findByPk(req.adminId);
+ if (!admin) {
+ return res.status(401).json({ error: '用户不存在' });
+ }
+ const token = jwt.sign({ id: admin.id }, process.env.JWT_SECRET, { expiresIn: '7d' });
+ res.json({ token });
+ } catch (error) {
+ res.status(500).json({ error: error.message });
+ }
+});
+
// ========== 管理员管理API(仅超级管理员) ==========
// 获取管理员列表
@@ -173,10 +187,12 @@ router.put('/activities/:id', auth, async (req, res) => {
// 审核报名
router.put('/registrations/:id', auth, async (req, res) => {
try {
- const { status, peopleCount } = req.body;
+ const { status, peopleCount, carNumber, groupStatus } = req.body;
const updateData = {};
if (status) updateData.status = status;
if (peopleCount !== undefined) updateData.peopleCount = peopleCount;
+ if (carNumber !== undefined) updateData.carNumber = carNumber;
+ if (groupStatus !== undefined) updateData.groupStatus = groupStatus;
await Registration.update(updateData, { where: { id: req.params.id } });
const registration = await Registration.findByPk(req.params.id);
@@ -207,6 +223,26 @@ router.put('/registrations/:id/checkin', auth, async (req, res) => {
}
});
+// 进群/取消进群
+router.put('/registrations/:id/group', auth, async (req, res) => {
+ try {
+ const registration = await Registration.findByPk(req.params.id);
+ if (!registration) {
+ return res.status(404).json({ error: '报名信息不存在' });
+ }
+
+ const newStatus = !registration.groupStatus;
+ await Registration.update({
+ groupStatus: newStatus
+ }, { where: { id: req.params.id } });
+
+ const updated = await Registration.findByPk(req.params.id);
+ res.json(updated);
+ } catch (error) {
+ res.status(500).json({ error: error.message });
+ }
+});
+
// 获取所有报名(含待审核)
router.get('/registrations', auth, async (req, res) => {
try {
@@ -264,8 +300,13 @@ router.get('/expenses', auth, async (req, res) => {
try {
const { activityId } = req.query;
const where = activityId ? { activityId } : {};
- const expenses = await Expense.findAll({ where });
- res.json(expenses);
+ const expenses = await Expense.findAll({ where, raw: true });
+ // 手动解析JSON字段
+ const parsedExpenses = expenses.map(expense => ({
+ ...expense,
+ media: typeof expense.media === 'string' ? JSON.parse(expense.media || '[]') : (expense.media || [])
+ }));
+ res.json(parsedExpenses);
} catch (error) {
res.status(500).json({ error: error.message });
}
diff --git a/backend/routes/registration.js b/backend/routes/registration.js
index 6cd8cd5..a0dd6d3 100644
--- a/backend/routes/registration.js
+++ b/backend/routes/registration.js
@@ -16,7 +16,7 @@ router.post('/registrations', async (req, res) => {
activityId, name, phone, carModel, carNumber, note
});
- res.json(registration);
+ res.json(registration.toJSON());
} catch (error) {
res.status(500).json({ error: error.message });
}
@@ -25,7 +25,7 @@ router.post('/registrations', async (req, res) => {
// 查询报名状态
router.get('/registrations/:id', async (req, res) => {
try {
- const registration = await Registration.findByPk(req.params.id);
+ const registration = await Registration.findByPk(req.params.id, { raw: true });
res.json(registration);
} catch (error) {
res.status(500).json({ error: error.message });
@@ -36,7 +36,8 @@ router.get('/registrations/:id', async (req, res) => {
router.get('/activities/:id/registrations', async (req, res) => {
try {
const registrations = await Registration.findAll({
- where: { activityId: req.params.id, status: 'approved' }
+ where: { activityId: req.params.id, status: 'approved' },
+ raw: true
});
res.json(registrations);
} catch (error) {
diff --git a/backend/routes/venue.js b/backend/routes/venue.js
new file mode 100644
index 0000000..e7c5f9d
--- /dev/null
+++ b/backend/routes/venue.js
@@ -0,0 +1,66 @@
+const express = require('express');
+const router = express.Router();
+const auth = require('../middleware/auth');
+const { upload, getFileUrls } = require('../middleware/upload');
+const { VenueImage } = require('../models');
+
+// 获取场地图片列表
+router.get('/venue-images', auth, async (req, res) => {
+ try {
+ const { activityId } = req.query;
+ const images = await VenueImage.findAll({
+ where: { activityId },
+ order: [['sortOrder', 'ASC'], ['id', 'ASC']],
+ raw: true
+ });
+ res.json(images);
+ } catch (error) {
+ res.status(500).json({ error: error.message });
+ }
+});
+
+// 上传场地图片
+router.post('/venue-images', auth, upload.single('image'), async (req, res) => {
+ try {
+ const { activityId, sortOrder } = req.body;
+
+ if (!req.file) {
+ return res.status(400).json({ error: '请选择图片' });
+ }
+
+ const [uploaded] = getFileUrls([req.file]);
+ const image = await VenueImage.create({
+ activityId,
+ image: uploaded.url,
+ sortOrder: sortOrder || 0
+ });
+
+ res.json(image);
+ } catch (error) {
+ res.status(500).json({ error: error.message });
+ }
+});
+
+// 更新场地图片排序
+router.put('/venue-images/:id', auth, async (req, res) => {
+ try {
+ const { sortOrder } = req.body;
+ await VenueImage.update({ sortOrder }, { where: { id: req.params.id } });
+ const image = await VenueImage.findByPk(req.params.id);
+ res.json(image);
+ } catch (error) {
+ res.status(500).json({ error: error.message });
+ }
+});
+
+// 删除场地图片
+router.delete('/venue-images/:id', auth, async (req, res) => {
+ try {
+ await VenueImage.destroy({ where: { id: req.params.id } });
+ res.json({ message: '删除成功' });
+ } catch (error) {
+ res.status(500).json({ error: error.message });
+ }
+});
+
+module.exports = router;
diff --git a/backend/server.js b/backend/server.js
index deb2c3e..24d23eb 100644
--- a/backend/server.js
+++ b/backend/server.js
@@ -16,6 +16,7 @@ app.use('/uploads', express.static(path.join(__dirname, 'uploads')));
app.use('/api', require('./routes/activity'));
app.use('/api', require('./routes/registration'));
app.use('/api/admin', require('./routes/admin'));
+app.use('/api/admin', require('./routes/venue'));
// 生产环境下提供前端静态文件
if (process.env.NODE_ENV === 'production') {
diff --git a/database-migration-checkin.sql b/database-migration-checkin.sql
deleted file mode 100644
index 176d76c..0000000
--- a/database-migration-checkin.sql
+++ /dev/null
@@ -1,4 +0,0 @@
--- 添加签到功能字段
-ALTER TABLE Registrations
-ADD COLUMN checkInStatus BOOLEAN DEFAULT false COMMENT '是否已签到',
-ADD COLUMN checkInTime DATETIME NULL COMMENT '签到时间';
diff --git a/database.sql b/database.sql
deleted file mode 100644
index 07cb768..0000000
--- a/database.sql
+++ /dev/null
@@ -1,90 +0,0 @@
--- 创建数据库
-CREATE DATABASE IF NOT EXISTS quanyoumi CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-
-USE quanyoumi;
-
--- 活动表
-CREATE TABLE IF NOT EXISTS Activities (
- id INT AUTO_INCREMENT PRIMARY KEY,
- title VARCHAR(255) NOT NULL,
- description TEXT,
- location VARCHAR(255),
- locationLat DECIMAL(10, 7),
- locationLng DECIMAL(10, 7),
- startTime DATETIME,
- endTime DATETIME,
- registrationDeadline DATETIME,
- maxParticipants INT,
- budget DECIMAL(10, 2) DEFAULT 0 COMMENT '活动预算',
- status ENUM('upcoming', 'ongoing', 'completed') DEFAULT 'upcoming',
- coverImage VARCHAR(255),
- createdAt DATETIME DEFAULT CURRENT_TIMESTAMP,
- updatedAt DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
-);
-
--- 报名表
-CREATE TABLE IF NOT EXISTS Registrations (
- id INT AUTO_INCREMENT PRIMARY KEY,
- activityId INT NOT NULL,
- name VARCHAR(100) NOT NULL,
- phone VARCHAR(20) NOT NULL,
- carModel VARCHAR(100),
- carNumber VARCHAR(20),
- peopleCount INT DEFAULT 1,
- status ENUM('pending', 'approved', 'rejected') DEFAULT 'pending',
- note TEXT,
- createdAt DATETIME DEFAULT CURRENT_TIMESTAMP,
- updatedAt DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
- FOREIGN KEY (activityId) REFERENCES Activities(id) ON DELETE CASCADE
-);
-
--- 经费表
-CREATE TABLE IF NOT EXISTS Expenses (
- id INT AUTO_INCREMENT PRIMARY KEY,
- activityId INT NOT NULL,
- item VARCHAR(255) NOT NULL,
- amount DECIMAL(10, 2) NOT NULL,
- category VARCHAR(100),
- receipt VARCHAR(255),
- note TEXT,
- createdAt DATETIME DEFAULT CURRENT_TIMESTAMP,
- updatedAt DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
- FOREIGN KEY (activityId) REFERENCES Activities(id) ON DELETE CASCADE
-);
-
--- 照片表
-CREATE TABLE IF NOT EXISTS Photos (
- id INT AUTO_INCREMENT PRIMARY KEY,
- activityId INT NOT NULL,
- url VARCHAR(255) NOT NULL,
- type ENUM('image', 'video') DEFAULT 'image',
- description VARCHAR(255),
- uploadedBy VARCHAR(100),
- status ENUM('pending', 'approved', 'rejected') DEFAULT 'approved',
- createdAt DATETIME DEFAULT CURRENT_TIMESTAMP,
- updatedAt DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
- FOREIGN KEY (activityId) REFERENCES Activities(id) ON DELETE CASCADE
-);
-
--- 赞助商表
-CREATE TABLE IF NOT EXISTS Sponsors (
- id INT AUTO_INCREMENT PRIMARY KEY,
- activityId INT NOT NULL,
- name VARCHAR(255) NOT NULL,
- logo VARCHAR(255),
- type ENUM('title', 'sponsor') DEFAULT 'sponsor',
- link VARCHAR(255),
- createdAt DATETIME DEFAULT CURRENT_TIMESTAMP,
- updatedAt DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
- FOREIGN KEY (activityId) REFERENCES Activities(id) ON DELETE CASCADE
-);
-
--- 管理员表
-CREATE TABLE IF NOT EXISTS Admins (
- id INT AUTO_INCREMENT PRIMARY KEY,
- username VARCHAR(50) NOT NULL UNIQUE,
- password VARCHAR(255) NOT NULL,
- name VARCHAR(100),
- createdAt DATETIME DEFAULT CURRENT_TIMESTAMP,
- updatedAt DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
-);
diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh
index 2686e46..b82ef30 100644
--- a/docker-entrypoint.sh
+++ b/docker-entrypoint.sh
@@ -21,6 +21,10 @@ fi
# 初始化数据库
echo "初始化数据库..."
+echo "[1/2] 运行数据库迁移..."
+node migrations/index.js
+
+echo "[2/2] 初始化管理员账号..."
node init-admin.js
# 替换前端 index.html 中的环境变量占位符
diff --git a/fix-expense.bat b/fix-expense.bat
new file mode 100644
index 0000000..da61e07
--- /dev/null
+++ b/fix-expense.bat
@@ -0,0 +1,8 @@
+@echo off
+echo ==========================================
+echo 修复经费媒体数据
+echo ==========================================
+cd backend
+node fix-expense-media.js
+cd ..
+pause
diff --git a/frontend/public/cd/cd1.jpg b/frontend/public/cd/cd1.jpg
new file mode 100644
index 0000000..a6e0509
Binary files /dev/null and b/frontend/public/cd/cd1.jpg differ
diff --git a/frontend/public/cd/cd2.jpg b/frontend/public/cd/cd2.jpg
new file mode 100644
index 0000000..6678a6a
Binary files /dev/null and b/frontend/public/cd/cd2.jpg differ
diff --git a/frontend/public/cd/cd3.jpg b/frontend/public/cd/cd3.jpg
new file mode 100644
index 0000000..c69ced8
Binary files /dev/null and b/frontend/public/cd/cd3.jpg differ
diff --git a/frontend/public/qun.jpg b/frontend/public/qun.jpg
new file mode 100644
index 0000000..1e3abd4
Binary files /dev/null and b/frontend/public/qun.jpg differ
diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js
index 524609d..77addb3 100644
--- a/frontend/src/api/index.js
+++ b/frontend/src/api/index.js
@@ -5,10 +5,74 @@ const api = axios.create({
timeout: 10000
});
-api.interceptors.request.use(config => {
+// Token续期状态管理
+let isRefreshing = false;
+let refreshSubscribers = [];
+
+// 订阅token续期完成事件
+const subscribeTokenRefresh = (cb) => {
+ refreshSubscribers.push(cb);
+};
+
+// 通知所有订阅者token已更新
+const onTokenRefreshed = (token) => {
+ refreshSubscribers.forEach(cb => cb(token));
+ refreshSubscribers = [];
+};
+
+// 检查token是否即将过期(剩余时间小于1天)
+const shouldRefreshToken = (token) => {
+ if (!token) return false;
+ try {
+ const payload = JSON.parse(atob(token.split('.')[1]));
+ const exp = payload.exp * 1000;
+ const now = Date.now();
+ const oneDay = 24 * 60 * 60 * 1000;
+ return (exp - now) < oneDay && (exp - now) > 0;
+ } catch {
+ return false;
+ }
+};
+
+// 续期token
+const refreshToken = async () => {
+ try {
+ const { data } = await api.post('/admin/refresh-token', {}, {
+ _skipRefresh: true // 标记跳过续期检查
+ });
+ localStorage.setItem('token', data.token);
+ return data.token;
+ } catch (error) {
+ console.error('Token续期失败:', error);
+ return null;
+ }
+};
+
+api.interceptors.request.use(async config => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
+
+ // 跳过续期请求本身的续期检查
+ if (!config._skipRefresh && shouldRefreshToken(token)) {
+ if (!isRefreshing) {
+ isRefreshing = true;
+ const newToken = await refreshToken();
+ isRefreshing = false;
+ if (newToken) {
+ config.headers.Authorization = `Bearer ${newToken}`;
+ onTokenRefreshed(newToken);
+ }
+ } else {
+ // 等待续期完成
+ return new Promise(resolve => {
+ subscribeTokenRefresh(newToken => {
+ config.headers.Authorization = `Bearer ${newToken}`;
+ resolve(config);
+ });
+ });
+ }
+ }
}
return config;
});
@@ -17,7 +81,15 @@ api.interceptors.request.use(config => {
api.interceptors.response.use(
response => response,
error => {
- // 统一错误处理
+ // 401错误:token过期或无效,跳转登录页
+ if (error.response?.status === 401) {
+ localStorage.removeItem('token');
+ localStorage.removeItem('admin');
+ // 避免在登录页重复跳转
+ if (!window.location.pathname.includes('/admin/login')) {
+ window.location.href = '/admin/login';
+ }
+ }
console.error('API请求错误:', error);
return Promise.reject(error);
}
@@ -40,6 +112,7 @@ export default {
// 活动相关
getActivities: (params) => api.get('/activities', { params }),
getActivity: (id) => api.get(`/activities/${id}`),
+ getVenueImages: (id) => api.get(`/activities/${id}/venue-images`),
// 报名相关
submitRegistration: (data) => api.post('/registrations', data),
@@ -57,11 +130,14 @@ export default {
// 管理员相关
adminLogin: (data) => api.post('/admin/login', data),
+ refreshToken: () => api.post('/admin/refresh-token'),
createActivity: (data) => api.post('/admin/activities', data),
updateActivity: (id, data) => api.put(`/admin/activities/${id}`, data),
approveRegistration: (id, status) => api.put(`/admin/registrations/${id}`, { status }),
updateRegistration: (id, data) => api.put(`/admin/registrations/${id}`, data),
checkInRegistration: (id) => api.put(`/admin/registrations/${id}/checkin`),
+ groupRegistration: (id) => api.put(`/admin/registrations/${id}/group`),
+ deleteRegistration: (id) => api.delete(`/admin/registrations/${id}`),
getAllRegistrations: (params) => api.get('/admin/registrations', { params }),
addExpense: (data) => api.post('/admin/expenses', data, {
headers: { 'Content-Type': 'multipart/form-data' },
@@ -97,5 +173,21 @@ export default {
headers: { 'Content-Type': 'multipart/form-data' },
timeout: 60000
}),
- deletePrize: (id) => api.delete(`/admin/prizes/${id}`)
+ deletePrize: (id) => api.delete(`/admin/prizes/${id}`),
+
+ // 管理员账号管理(仅超级管理员)
+ getAdmins: () => api.get('/admin/admins'),
+ createAdmin: (data) => api.post('/admin/admins', data),
+ updateAdmin: (id, data) => api.put(`/admin/admins/${id}`, data),
+ updateAdminPassword: (id, data) => api.put(`/admin/admins/${id}/password`, data),
+ deleteAdmin: (id) => api.delete(`/admin/admins/${id}`),
+
+ // 场地图片管理
+ getVenueImages: (activityId) => api.get('/admin/venue-images', { params: { activityId } }),
+ uploadVenueImage: (data) => api.post('/admin/venue-images', data, {
+ headers: { 'Content-Type': 'multipart/form-data' },
+ timeout: 60000
+ }),
+ deleteVenueImage: (id) => api.delete(`/admin/venue-images/${id}`),
+ updateVenueImageOrder: (id, sortOrder) => api.put(`/admin/venue-images/${id}`, { sortOrder })
};
diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js
index bff66b4..d291a8c 100644
--- a/frontend/src/router/index.js
+++ b/frontend/src/router/index.js
@@ -47,6 +47,12 @@ const routes = [
name: 'ActivityManage',
component: () => import('../views/admin/ActivityManage.vue'),
meta: { requiresAuth: true }
+ },
+ {
+ path: '/admin/admins',
+ name: 'AdminManage',
+ component: () => import('../views/admin/AdminManage.vue'),
+ meta: { requiresAuth: true, requiresSuper: true }
}
];
@@ -58,6 +64,13 @@ const router = createRouter({
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth && !localStorage.getItem('token')) {
next('/admin/login');
+ } else if (to.meta.requiresSuper) {
+ const admin = JSON.parse(localStorage.getItem('admin') || '{}');
+ if (admin.role !== 'super') {
+ next('/admin');
+ } else {
+ next();
+ }
} else {
next();
}
diff --git a/frontend/src/views/ActivityDetail/SponsorDanmaku.vue b/frontend/src/views/ActivityDetail/SponsorDanmaku.vue
index 20ddea5..3b9900f 100644
--- a/frontend/src/views/ActivityDetail/SponsorDanmaku.vue
+++ b/frontend/src/views/ActivityDetail/SponsorDanmaku.vue
@@ -1,28 +1,77 @@
-
-
-
+
+
- 20:00 - 20:30
+ 21:00 以后
-
璀璨烟火秀
-
三重烟花盛宴,点亮夜空绽放精彩
+
自由交流 & 返程
+
自由合影留念,整理现场,安全返程
-
🎆 米兰之夜 - 大型专业烟火表演,绚丽夺目
-
✨ 仙女棒 - 每车配发手持烟花,拍照打卡
-
🔥 加特林压轴 - 震撼收尾,全场欢呼
-
📸 最佳拍摄时机 - 记录璀璨瞬间
-
-
- 💡 提示:拍摄烟花视频并分享可获得 +2分
-
-
-
-
-
-
-
-
-
-
-
- 20:30 以后
-
-
-
积分兑奖 & 自由交流
-
凭积分兑换精美礼品,自由合影欢乐交流
-
-
-
-
🏆 积分兑换礼品
-
- 积分排行榜:
- • 第1名(20分以上):神秘大奖 🎁
- • 第2-3名(15-19分):精美礼品 🎀
- • 第4-10名(10-14分):纪念品 🎈
- • 参与奖(5-9分):小礼物 🎊
-
- 活动收尾:
- • 自由合影留念
- • 整理现场垃圾
- • 提醒安全驾驶返程
- • 期待下次相聚
-
+
📸 自由合影留念
+
🧹 整理现场垃圾
+
🚗 提醒安全驾驶返程
+
👋 期待下次相聚
@@ -332,6 +323,18 @@
+
+
+
场地展示
+
+
+
+
![]()
+
+
+
+
+
活动经费 ¥{{ totalExpense }}
@@ -391,6 +394,12 @@
{{ prize.description }}
-
+
diff --git a/frontend/src/views/Register.vue b/frontend/src/views/Register.vue
index e6f4a33..59aa9b8 100644
--- a/frontend/src/views/Register.vue
+++ b/frontend/src/views/Register.vue
@@ -49,6 +49,7 @@
@click="showCarModelPicker = true"
/>
+
+
+
+
+
+
+
🎉 报名成功!
+
+
+
+
+ 重要!请扫码加入活动群
+
+
+
+

+
+
+
+
+ 长按二维码保存图片
+
+
+
+ 我知道了
+
+
+
@@ -121,6 +162,7 @@ const route = useRoute();
const router = useRouter();
const showCarModelPicker = ref(false);
+const showQrcodeDialog = ref(false);
const carModels = ref([
{ text: '小米SU7', value: '小米SU7' },
{ text: '小米YU7', value: '小米YU7' },
@@ -225,11 +267,18 @@ const onSubmit = async () => {
message: `您的报名ID: ${data.id}\n已自动登录,请等待管理员审核`
});
- router.back();
+ // 显示醒目的群二维码弹窗
+ showQrcodeDialog.value = true;
} catch (error) {
showToast('报名失败');
}
};
+
+// 关闭二维码弹窗
+const closeQrcodeDialog = () => {
+ showQrcodeDialog.value = false;
+ router.back();
+};
diff --git a/frontend/src/views/admin/ActivityManage.vue b/frontend/src/views/admin/ActivityManage.vue
index 5de5729..8b8cae1 100644
--- a/frontend/src/views/admin/ActivityManage.vue
+++ b/frontend/src/views/admin/ActivityManage.vue
@@ -238,6 +238,13 @@
已通过
+
+
+
+
{{ approvedTotalPeople }}
+
参与人数
+
+
+
+
+
+
{{ registrations.filter(r => r.groupStatus).length }}
+
已进群
+
+
+
+
+
+
{{ registrations.filter(r => !r.groupStatus).length }}
+
未进群
+
+
@@ -285,11 +306,6 @@
{{ reg.name || '未知' }}
-
-
- 已签到
-
-
+
+
+
+
+
@@ -414,6 +464,47 @@
+
+
+
+
@@ -579,7 +670,7 @@
+
+
+
+
+
+
+ {{ currentEditReg.name }}
+ 当前车牌号:{{ currentEditReg.carNumber || '未填写' }}
+
+
+
+
+ 确定
+
+
+
+
+
+
+
+
+
车牌号
-
+
{{ currentReg.carNumber }}
+
@@ -892,33 +1077,41 @@
签到时间
{{ formatDate(currentReg.checkInTime) }}
-
- 审核状态
-
- {{ currentReg.status === 'approved' ? '已通过' : currentReg.status === 'rejected' ? '已拒绝' : '待审核' }}
-
-
-
-
签到状态
-
- {{ currentReg.checkInStatus ? '已签到' : '未签到' }}
-
+
+
状态管理
+
+
+ 审核(是否交钱)
+
+ {{ currentReg.status === 'approved' ? '已通过' : currentReg.status === 'rejected' ? '已拒绝' : '待审核' }}
+
+
+
+ 签到(是否到现场)
+
+
+
+ 进群(是否加微信群)
+
+
+
-
- {{ currentReg.checkInStatus ? '取消签到' : '签到' }}
-
- 通过
- 拒绝
+ 通过审核
+ 拒绝审核
删除报名
@@ -965,12 +1158,16 @@ const photoFiles = ref([]);
const pendingPhotos = ref([]);
const expenseMediaFiles = ref([]);
const approvedPhotos = ref([]);
+const venueImages = ref([]);
+const venueFiles = ref([]);
const showVideoPlayer = ref(false);
const currentVideoUrl = ref('');
const regSearchKeyword = ref('');
const showPeopleCountDialog = ref(false);
const currentEditReg = ref({});
const newPeopleCount = ref(1);
+const showCarNumberDialog = ref(false);
+const newCarNumber = ref('');
const showRegDetailDialog = ref(false);
const currentReg = ref({});
const activeFilter = ref('all'); // 过滤器状态
@@ -978,6 +1175,16 @@ const activeFilter = ref('all'); // 过滤器状态
const showActivityForm = ref(false);
const showExpenseForm = ref(false);
const showSponsorForm = ref(false);
+const showRegForm = ref(false);
+
+const regForm = ref({
+ name: '',
+ phone: '',
+ carModel: '',
+ carNumber: '',
+ peopleCount: 1,
+ note: ''
+});
// 奖品管理
const activityId = computed(() => route.params.id);
@@ -1069,6 +1276,13 @@ const totalExpense = computed(() => {
return expenses.value.reduce((sum, e) => sum + parseFloat(e.amount), 0).toFixed(2);
});
+// 审核通过的总人数(车主+带的人)
+const approvedTotalPeople = computed(() => {
+ return registrations.value
+ .filter(r => r.status === 'approved')
+ .reduce((sum, r) => sum + (r.peopleCount || 1), 0);
+});
+
// 过滤报名列表
const filteredRegistrations = computed(() => {
let result = registrations.value;
@@ -1084,6 +1298,10 @@ const filteredRegistrations = computed(() => {
result = result.filter(r => r.checkInStatus);
} else if (activeFilter.value === 'notCheckedIn') {
result = result.filter(r => !r.checkInStatus);
+ } else if (activeFilter.value === 'grouped') {
+ result = result.filter(r => r.groupStatus);
+ } else if (activeFilter.value === 'notGrouped') {
+ result = result.filter(r => !r.groupStatus);
}
// 应用搜索关键词
@@ -1212,7 +1430,11 @@ const confirmCustomCategory = () => {
const loadData = async () => {
try {
const { data } = await api.getActivity(route.params.id);
- activity.value = data.activity;
+ activity.value = {
+ ...data.activity,
+ danmakuEnabled: Boolean(data.activity.danmakuEnabled),
+ registrationEnabled: Boolean(data.activity.registrationEnabled)
+ };
expenses.value = data.expenses || [];
sponsors.value = data.sponsors || [];
@@ -1226,6 +1448,9 @@ const loadData = async () => {
// 加载奖品
await loadPrizes();
+
+ // 加载场地图片
+ await loadVenueImages();
} catch (error) {
showToast('加载失败');
}
@@ -1327,8 +1552,8 @@ const toggleDanmaku = async (value) => {
formData.append('category', expenseForm.value.category);
formData.append('note', expenseForm.value.note);
- // 添加现有媒体
- if (expenseForm.value.media && expenseForm.value.media.length > 0) {
+ // 只在编辑时添加现有媒体
+ if (expenseForm.value.id && expenseForm.value.media && expenseForm.value.media.length > 0) {
formData.append('existingMedia', JSON.stringify(expenseForm.value.media));
}
@@ -1346,9 +1571,7 @@ const toggleDanmaku = async (value) => {
await api.addExpense(formData);
showToast('添加成功');
}
- showExpenseForm.value = false;
- expenseForm.value = { id: null, item: '', amount: '', category: '', note: '', media: [] };
- expenseMediaFiles.value = [];
+ closeExpenseForm();
loadData();
} catch (error) {
showToast(expenseForm.value.id ? '更新失败' : '添加失败');
@@ -1374,13 +1597,76 @@ const addNewExpense = () => {
};
const onExpenseMediaRead = (file) => {
- // 文件读取后的处理,Vant会自动处理
+ // 验证文件大小和类型
+ const fileItem = Array.isArray(file) ? file[0] : file;
+ const maxSize = 50 * 1024 * 1024; // 50MB
+
+ if (fileItem.file.size > maxSize) {
+ showToast('文件大小不能超过50MB');
+ // 移除超大文件
+ const index = expenseMediaFiles.value.findIndex(f => f.file === fileItem.file);
+ if (index > -1) {
+ expenseMediaFiles.value.splice(index, 1);
+ }
+ return;
+ }
};
const removeExpenseMedia = (index) => {
expenseForm.value.media.splice(index, 1);
};
+// 添加新报名
+const addNewRegistration = () => {
+ regForm.value = {
+ name: '',
+ phone: '',
+ carModel: '',
+ carNumber: '',
+ peopleCount: 1,
+ note: ''
+ };
+ showRegForm.value = true;
+};
+
+// 关闭报名表单
+const closeRegForm = () => {
+ showRegForm.value = false;
+ regForm.value = {
+ name: '',
+ phone: '',
+ carModel: '',
+ carNumber: '',
+ peopleCount: 1,
+ note: ''
+ };
+};
+
+// 提交报名
+const submitRegistration = async () => {
+ try {
+ await api.submitRegistration({
+ activityId: activity.value.id,
+ ...regForm.value
+ });
+ showToast('报名成功');
+ closeRegForm();
+ loadData();
+ } catch (error) {
+ showToast('报名失败');
+ }
+};;
+
+const removeNewExpenseMedia = (index) => {
+ expenseMediaFiles.value.splice(index, 1);
+};
+
+const closeExpenseForm = () => {
+ showExpenseForm.value = false;
+ expenseForm.value = { id: null, item: '', amount: '', category: '', note: '', media: [] };
+ expenseMediaFiles.value = [];
+};
+
const deleteExpenseItem = async (id) => {
try {
await showConfirmDialog({
@@ -1592,6 +1878,24 @@ const confirmPeopleCount = async () => {
}
};
+// 修改车牌号
+const editCarNumber = (reg) => {
+ currentEditReg.value = reg;
+ newCarNumber.value = reg.carNumber || '';
+ showCarNumberDialog.value = true;
+};
+
+const confirmCarNumber = async () => {
+ try {
+ await api.updateRegistration(currentEditReg.value.id, { carNumber: newCarNumber.value });
+ showToast('修改成功');
+ showCarNumberDialog.value = false;
+ loadData();
+ } catch (error) {
+ showToast('修改失败');
+ }
+};
+
// 显示报名详情
const showRegDetail = (reg) => {
currentReg.value = reg;
@@ -1600,13 +1904,19 @@ const showRegDetail = (reg) => {
// 从详情弹窗签到
const toggleCheckInFromDetail = async () => {
+ const originalStatus = currentReg.value.checkInStatus;
try {
await api.checkInRegistration(currentReg.value.id);
showToast('操作成功');
- showRegDetailDialog.value = false;
- loadData();
+ await loadData();
+ // 重新打开详情弹窗,显示更新后的数据
+ const updatedReg = registrations.value.find(r => r.id === currentReg.value.id);
+ if (updatedReg) {
+ currentReg.value = updatedReg;
+ }
} catch (error) {
showToast('操作失败');
+ currentReg.value.checkInStatus = originalStatus;
}
};
@@ -1622,6 +1932,22 @@ const approveFromDetail = async (status) => {
}
};
+// 从详情弹窗切换进群状态
+const toggleGroupStatusFromDetail = async () => {
+ try {
+ await api.groupRegistration(currentReg.value.id);
+ showToast('操作成功');
+ await loadData();
+ // 重新打开详情弹窗,显示更新后的数据
+ const updatedReg = registrations.value.find(r => r.id === currentReg.value.id);
+ if (updatedReg) {
+ currentReg.value = updatedReg;
+ }
+ } catch (error) {
+ showToast('操作失败');
+ }
+};
+
// 删除报名
const deleteRegistration = async () => {
try {
@@ -1724,6 +2050,70 @@ const playVideo = (url) => {
showVideoPlayer.value = true;
};
+// 场地管理相关方法
+const loadVenueImages = async () => {
+ try {
+ const { data } = await api.getVenueImages(route.params.id);
+ venueImages.value = data;
+ } catch (error) {
+ console.error('加载场地图片失败:', error);
+ }
+};
+
+const uploadVenueImage = async (file) => {
+ const loadingToast = showToast({
+ type: 'loading',
+ message: '正在上传...',
+ forbidClick: true,
+ duration: 0
+ });
+
+ try {
+ const formData = new FormData();
+ formData.append('image', file.file);
+ formData.append('activityId', route.params.id);
+ formData.append('sortOrder', venueImages.value.length);
+
+ await api.uploadVenueImage(formData);
+ showToast('上传成功');
+ venueFiles.value = [];
+ await loadVenueImages();
+ } catch (error) {
+ console.error('上传失败:', error);
+ showToast('上传失败');
+ } finally {
+ loadingToast.clear();
+ }
+};
+
+const deleteVenueImage = async (id) => {
+ try {
+ await showConfirmDialog({
+ title: '确认删除',
+ message: '删除后无法恢复,确定要删除吗?',
+ });
+ await api.deleteVenueImage(id);
+ showToast('删除成功');
+ await loadVenueImages();
+ } catch (error) {
+ if (error !== 'cancel') {
+ console.error('删除失败:', error);
+ showToast('删除失败');
+ }
+ }
+};
+
+const updateVenueOrder = async (id, sortOrder) => {
+ try {
+ await api.updateVenueImageOrder(id, sortOrder);
+ showToast('排序已更新');
+ await loadVenueImages();
+ } catch (error) {
+ console.error('更新失败:', error);
+ showToast('更新失败');
+ }
+};
+
// 地图选点相关(与Dashboard相同)
const initMapPicker = async () => {
try {
@@ -2400,6 +2790,71 @@ onMounted(loadData);
font-size: 13px;
}
+.reg-right {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ flex-shrink: 0;
+}
+
+.reg-right .van-icon {
+ font-size: 16px;
+}
+
+.status-indicators {
+ display: flex;
+ gap: 6px;
+ align-items: center;
+}
+
+.status-dot {
+ width: 28px;
+ height: 28px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: #f5f5f5;
+ transition: all 0.3s ease;
+}
+
+.status-dot .van-icon {
+ font-size: 14px;
+ color: #999;
+}
+
+.status-dot.status-approved {
+ background: #e8f5e9;
+}
+
+.status-dot.status-approved .van-icon {
+ color: #07c160;
+}
+
+.status-dot.status-rejected {
+ background: #ffebee;
+}
+
+.status-dot.status-rejected .van-icon {
+ color: #ee0a24;
+}
+
+.status-dot.status-pending {
+ background: #fff3e0;
+}
+
+.status-dot.status-pending .van-icon {
+ color: #ffa940;
+}
+
+.status-dot.status-active {
+ background: #e3f2fd;
+}
+
+.status-dot.status-active .van-icon {
+ color: #1989fa;
+}
+
.info-link,
.info-text {
display: inline-flex;
@@ -2441,15 +2896,7 @@ onMounted(loadData);
opacity: 0.7;
}
-.reg-right {
- display: flex;
- align-items: center;
- flex-shrink: 0;
-}
-
-.reg-right .van-icon {
- font-size: 16px;
-}
+/* 移除旧的 reg-right 样式,已在上面重新定义 */
/* 弹窗 */
.form-popup {
@@ -2759,6 +3206,48 @@ onMounted(loadData);
border-radius: 12px;
}
+/* 场地管理 */
+.venue-list {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.venue-item {
+ background: #fff;
+ border-radius: 12px;
+ overflow: hidden;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.04);
+ border: 1px solid #f0f0f0;
+}
+
+.venue-thumb {
+ width: 100%;
+ height: 200px;
+ background: #f5f5f5;
+ cursor: pointer;
+}
+
+.venue-thumb .van-image {
+ width: 100%;
+ height: 100%;
+}
+
+.venue-actions {
+ padding: 12px;
+ display: flex;
+ gap: 8px;
+ align-items: center;
+}
+
+.venue-actions .van-field {
+ flex: 1;
+}
+
+.venue-actions .van-button {
+ flex-shrink: 0;
+}
+
/* 分类选择器 */
.category-picker {
padding: 20px;
@@ -2887,9 +3376,19 @@ onMounted(loadData);
}
.existing-media {
+ margin-top: 12px;
+}
+
+.media-section-title {
+ font-size: 13px;
+ color: #666;
+ margin-bottom: 8px;
+ font-weight: 500;
+}
+
+.media-list {
display: flex;
gap: 8px;
- margin-top: 12px;
flex-wrap: wrap;
}
@@ -3026,6 +3525,33 @@ onMounted(loadData);
border-bottom: 1px solid #f5f5f5;
}
+.detail-row.status-row {
+ flex-direction: column;
+ align-items: stretch;
+ gap: 12px;
+}
+
+.status-controls {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ background: #f7f8fa;
+ padding: 12px;
+ border-radius: 8px;
+}
+
+.status-item {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.status-name {
+ font-size: 14px;
+ color: #333;
+ font-weight: 500;
+}
+
.detail-row:last-child {
border-bottom: none;
}
diff --git a/frontend/src/views/admin/AdminManage.vue b/frontend/src/views/admin/AdminManage.vue
new file mode 100644
index 0000000..0636a7a
--- /dev/null
+++ b/frontend/src/views/admin/AdminManage.vue
@@ -0,0 +1,606 @@
+
+
+
+
+
+
+
+
+
+
+ 📅
+ 创建于 {{ formatDate(admin.createdAt) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/views/admin/Dashboard.vue b/frontend/src/views/admin/Dashboard.vue
index 7fbc36a..7a678e9 100644
--- a/frontend/src/views/admin/Dashboard.vue
+++ b/frontend/src/views/admin/Dashboard.vue
@@ -5,9 +5,20 @@
管理后台
泉友米车友会
-
- 退出
-
+
@@ -230,6 +241,7 @@ import api from '../../api';
const router = useRouter();
const activities = ref([]);
+const isSuper = ref(false);
const showActivityForm = ref(false);
const showMapPicker = ref(false);
@@ -290,14 +302,29 @@ const formatDateTime = (dateTime) => {
return `${year}-${month}-${day} ${hours}:${minutes}`;
};
+// 安全解析datetime-local格式,确保以本地时区创建Date对象
+const parseDateTimeLocal = (dateTimeStr) => {
+ console.log('[时区转换] 输入的datetime-local字符串:', dateTimeStr);
+ const [datePart, timePart] = dateTimeStr.split('T');
+ const [year, month, day] = datePart.split('-').map(Number);
+ const [hours, minutes] = timePart.split(':').map(Number);
+ const localDate = new Date(year, month - 1, day, hours, minutes);
+ console.log('[时区转换] 创建的本地Date对象:', localDate.toString());
+ console.log('[时区转换] 转换为UTC (ISO):', localDate.toISOString());
+ console.log('[时区转换] 时区偏移(分钟):', localDate.getTimezoneOffset());
+ return localDate;
+};
+
// 开始时间确认
const confirmStartTime = () => {
if (!startTimeInput.value) {
showToast('请选择开始时间');
return;
}
- const selectedDate = new Date(startTimeInput.value);
+ console.log('[开始时间] 用户选择的时间:', startTimeInput.value);
+ const selectedDate = parseDateTimeLocal(startTimeInput.value);
activityForm.value.startTime = selectedDate.toISOString();
+ console.log('[开始时间] 保存到表单的UTC时间:', activityForm.value.startTime);
showStartTimePicker.value = false;
};
@@ -307,12 +334,14 @@ const confirmDeadline = () => {
showToast('请选择报名截止时间');
return;
}
- const selectedDate = new Date(deadlineInput.value);
+ console.log('[截止时间] 用户选择的时间:', deadlineInput.value);
+ const selectedDate = parseDateTimeLocal(deadlineInput.value);
if (activityForm.value.startTime && selectedDate > new Date(activityForm.value.startTime)) {
showToast('报名截止时间不能晚于活动开始时间');
return;
}
activityForm.value.registrationDeadline = selectedDate.toISOString();
+ console.log('[截止时间] 保存到表单的UTC时间:', activityForm.value.registrationDeadline);
showDeadlinePicker.value = false;
};
@@ -573,10 +602,17 @@ const submitActivity = async () => {
const logout = () => {
localStorage.removeItem('token');
+ localStorage.removeItem('admin');
router.push('/admin/login');
};
+const goToAdminManage = () => {
+ router.push('/admin/admins');
+};
+
onMounted(() => {
+ const admin = JSON.parse(localStorage.getItem('admin') || '{}');
+ isSuper.value = admin.role === 'super';
loadActivities();
});
@@ -609,6 +645,22 @@ onMounted(() => {
color: #666;
}
+.header-actions {
+ display: flex;
+ gap: 8px;
+}
+
+.admin-btn {
+ background: #4dabf7;
+ border: none;
+ color: white;
+ font-weight: 500;
+}
+
+.admin-btn:active {
+ background: #339af0;
+}
+
.logout-btn {
background: #f5f5f5;
border: none;
diff --git a/frontend/src/views/admin/Login.vue b/frontend/src/views/admin/Login.vue
index 6c210f4..bd466d5 100644
--- a/frontend/src/views/admin/Login.vue
+++ b/frontend/src/views/admin/Login.vue
@@ -62,6 +62,7 @@ const onSubmit = async () => {
try {
const { data } = await api.adminLogin(form.value);
localStorage.setItem('token', data.token);
+ localStorage.setItem('admin', JSON.stringify(data.admin));
showToast('登录成功');
router.push('/admin');
} catch (error) {
diff --git a/frontend/src/views/admin/prize-manage-styles.css b/frontend/src/views/admin/prize-manage-styles.css
index f7aeb5a..56406df 100644
--- a/frontend/src/views/admin/prize-manage-styles.css
+++ b/frontend/src/views/admin/prize-manage-styles.css
@@ -54,6 +54,11 @@
white-space: nowrap;
}
+.prize-level-badge.level-特等奖 {
+ background: linear-gradient(135deg, #FF0000 0%, #FF6B6B 100%);
+ color: #fff;
+}
+
.prize-level-badge.level-一等奖 {
background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%);
color: #fff;
@@ -69,6 +74,11 @@
color: #fff;
}
+.prize-level-badge.level-四等奖 {
+ background: linear-gradient(135deg, #8B7355 0%, #A0826D 100%);
+ color: #fff;
+}
+
.prize-level-badge.level-参与奖 {
background: linear-gradient(135deg, #FF6700 0%, #FF8533 100%);
color: #fff;
@@ -163,6 +173,10 @@
cursor: pointer;
transition: all 0.2s;
text-align: center;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
}
.level-item:active {
diff --git a/frontend/src/views/timeline-data.js b/frontend/src/views/timeline-data.js
index 672c161..f34aed3 100644
--- a/frontend/src/views/timeline-data.js
+++ b/frontend/src/views/timeline-data.js
@@ -1,92 +1,6 @@
-// 活动流程数据
-export const timelineData = [
- {
- title: '签到与集结',
- time: '14:00 - 14:30',
- brief: '车辆到场、签到登记、领取积分卡',
- overview: `
-
-
📍 车辆到达指定地点后,工作人员将引导至临时停车点
-
✍️ 前往签到处签名确认,扫码加入活动群
-
🎫 领取专属积分卡,开启精彩旅程
-
- `,
- content: `
-
-
📋 积分卡说明
-
- • 每人领取一张积分卡(红绿双色章)
- • 初始积分:0分
- • 游戏得分范围:1~5分
- • 游戏扣分范围:1~5分
- • 最终积分可兑换礼品
-
-
- `
- },
- {
- title: '观赏素材拍摄',
- time: '14:30 - 16:30',
- brief: '多点位打卡拍照,记录精彩瞬间',
- overview: `
-
-
📸 专业摄影师带队,前往多个精选点位
-
🚗 全员车前合影、签到墙打卡、地标合照
-
✨ 完成所有点位拍照可获得 +2分
-
- `,
- items: [
- {
- icon: '🏛️',
- name: '宝船门口',
- detail: `
-
-
拍摄内容:
-
• 全员车前合影
-
• 签到墙合照
-
• 个人打卡照
-
- `
- },
- {
- icon: '🌉',
- name: '隧道三点位',
- detail: `
-
-
拍摄内容:
-
• 地标合影
-
• 动态全员上车
-
• 点火瞬间
-
- `
- },
- {
- icon: '🎨',
- name: '特色造型',
- detail: `
-
-
拍摄内容:
-
• 摆"泉州"两个字造型
-
• 创意合影
-
- `
- }
- ]
- },
- {
- title: '互动游戏时间',
- time: '16:30 - 18:00',
- brief: '10+趣味游戏项目,赢取积分',
- overview: `
-
-
🎮 精心准备10+款趣味游戏
-
🏆 每个游戏都有积分奖励,表现越好得分越高
-
⚡ 特别设置复活挑战赛,给你翻盘机会
-
💡 点击下方游戏查看详细规则和得分标准
-
- `,
- items: [
- {
+// 游戏项目数据
+export const gameItems = [
+ {
icon: '🎯',
name: '套圈圈',
detail: `
@@ -98,8 +12,8 @@ export const timelineData = [
• 套中5个:+5分
`
- },
- {
+ },
+ {
icon: '🏺',
name: '投壶',
detail: `
@@ -111,8 +25,8 @@ export const timelineData = [
• 全部投中:+5分
`
- },
- {
+ },
+ {
icon: '🎲',
name: '摇骰子(067玩法)',
detail: `
@@ -124,8 +38,8 @@ export const timelineData = [
• 摇出5个正豹子:+5分(直接判赢)
`
- },
- {
+ },
+ {
icon: '🎲',
name: '摇骰子(单骰比大小)',
detail: `
@@ -136,8 +50,8 @@ export const timelineData = [
• 玩家输庄家:-1分
`
- },
- {
+ },
+ {
icon: '🐂',
name: '牛牛',
detail: `
@@ -149,8 +63,8 @@ export const timelineData = [
• 连赢5局:+5分
`
- },
- {
+ },
+ {
icon: '✊',
name: '闽南划拳(两拳倒)',
detail: `
@@ -164,8 +78,8 @@ export const timelineData = [
💡 投资机制:可叠加投资,赢得越多分数越高!
`
- },
- {
+ },
+ {
icon: '⚽',
name: '足球射门',
detail: `
@@ -177,8 +91,8 @@ export const timelineData = [
• 进5球:+5分
`
- },
- {
+ },
+ {
icon: '🎮',
name: '王者荣耀',
detail: `
@@ -193,8 +107,8 @@ export const timelineData = [
💡 团队配合最重要,选好英雄!
`
- },
- {
+ },
+ {
icon: '💧',
name: '抛水瓶',
detail: `
@@ -206,8 +120,8 @@ export const timelineData = [
• 立住5个:+5分
`
- },
- {
+ },
+ {
icon: '🎴',
name: '扑克游戏',
detail: `
@@ -218,8 +132,8 @@ export const timelineData = [
• 输家:-1分
`
- },
- {
+ },
+ {
icon: '🐊',
name: '鳄鱼牙齿/插剑玩具',
detail: `
@@ -233,8 +147,8 @@ export const timelineData = [
💡 胆大心细,步步为营!
`
- },
- {
+ },
+ {
icon: '🪑',
name: '最终复活挑战 - 抢椅子',
detail: `
@@ -248,170 +162,5 @@ export const timelineData = [
⚡ 这是翻盘的最佳机会!
`
- }
- ]
- },
- {
- title: '落日BBQ',
- time: '18:00 - 19:30',
- brief: '落日余晖下享受美食,拍摄氛围大片',
- overview: `
-
-
🌅 在落日余晖下,车辆排成一排
-
🚗 齐开大灯,营造浪漫氛围
-
🍖 边烤肉边欣赏晚霞,拍摄氛围感大片
-
- `,
- content: `
-
-
- 💡 提示:分享美食照片到朋友圈并@活动官方账号可获得 +1分
-
-
- `
- },
- {
- title: '篝火歌舞会',
- time: '19:00 - 20:00',
- brief: '围炉夜话,互动游戏,才艺表演',
- overview: `
-
-
🔥 点燃篝火,围坐一圈
-
🎤 击鼓传花、喊数抱团等互动游戏
-
🎭 才艺表演,展示个人魅力
-
💡 点击下方查看各个游戏详细规则
-
- `,
- items: [
- {
- icon: '🥁',
- name: '击鼓传花(10轮)',
- detail: `
-
-
游戏规则:音乐停止时持花者上台,吃柠檬或苦瓜
-
得分标准:
-
• 勇敢完成:+2分
-
• 拒绝挑战:-3分
-
- `
- },
- {
- icon: '🤝',
- name: '喊数抱团(10轮)',
- detail: `
-
-
游戏规则:主持人喊数字,参与者按数字抱团,落单者淘汰
-
惩罚:上台自我介绍(1分钟)
-
得分标准:
-
• 成功抱团:+1分
-
• 落单但完成自我介绍:+2分
-
• 拒绝:-2分
-
- `
- },
- {
- icon: '🎵',
- name: '大合唱',
- detail: `
-
-
活动内容:全员参与,气氛热烈
-
得分标准:
-
• 每人:+1分
-
- `
- },
- {
- icon: '🎭',
- name: '个人才艺表演',
- detail: `
-
-
活动内容:自愿上台唱歌、跳舞、表演才艺
-
提示:需提前到后台准备BGM
-
得分标准:
-
• 参与表演:+3分
-
• 精彩表演:+5分
-
- `
- },
- {
- icon: '👶',
- name: '小朋友互动',
- detail: `
-
-
活动内容:主持人邀请小朋友上台互动,参与即有礼物
-
得分标准:
-
• 家长陪同参与:+2分
-
- `
- }
- ]
- },
- {
- title: '璀璨烟火秀',
- time: '20:00 - 20:30',
- brief: '专业烟火表演,点亮夜空',
- overview: `
-
-
🎆 大型专业烟火表演
-
✨ 每车配发手持仙女棒
-
🔥 加特林压轴,震撼收尾
-
- `,
- items: [
- {
- icon: '🌃',
- name: '米兰之夜',
- detail: `
-
- `
- },
- {
- icon: '✨',
- name: '仙女棒',
- detail: `
-
- `
- },
- {
- icon: '🔥',
- name: '加特林压轴',
- detail: `
-
-
表演内容:震撼收尾,全场欢呼
-
💡 拍摄烟花视频并分享可获得 +2分
-
- `
- }
- ]
- },
- {
- title: '积分兑奖 & 自由交流',
- time: '20:30 以后',
- brief: '积分兑换礼品,自由合影交流',
- overview: `
-
-
🏆 根据积分排行榜兑换礼品
-
📸 自由合影,交流心得
-
🚗 整理垃圾,安全驾驶返程
-
- `,
- content: `
-
-
🏆 积分兑换礼品
-
- 积分排行榜:
- • 第1名(20分以上):神秘大奖
- • 第2-3名(15-19分):精美礼品
- • 第4-10名(10-14分):纪念品
- • 参与奖(5-9分):小礼物
-
- 随后自由合影、整理垃圾、提醒安全驾驶返程
-
-
- `
- }
+ }
];
diff --git a/init-all.bat b/init-all.bat
new file mode 100644
index 0000000..4b2dbda
--- /dev/null
+++ b/init-all.bat
@@ -0,0 +1,35 @@
+@echo off
+echo ========================================
+echo 泉友米系统初始化
+echo ========================================
+echo.
+
+echo [1/2] 运行数据库迁移...
+node backend/migrations/index.js
+if %errorlevel% neq 0 (
+ echo.
+ echo ✗ 迁移失败,请检查错误信息
+ pause
+ exit /b 1
+)
+
+echo.
+echo [2/2] 初始化管理员账号...
+node backend/init-admin.js
+if %errorlevel% neq 0 (
+ echo.
+ echo ✗ 初始化失败,请检查错误信息
+ pause
+ exit /b 1
+)
+
+echo.
+echo ========================================
+echo ✓ 初始化完成!
+echo ========================================
+echo.
+echo 管理员账号信息:
+echo 超级管理员: yvan / Xu950329.
+echo 普通管理员: admin / admin123
+echo.
+pause
diff --git a/init-all.sh b/init-all.sh
new file mode 100644
index 0000000..7e0c244
--- /dev/null
+++ b/init-all.sh
@@ -0,0 +1,33 @@
+#!/bin/bash
+
+echo "========================================"
+echo "泉友米系统初始化"
+echo "========================================"
+echo ""
+
+echo "[1/2] 运行数据库迁移..."
+node backend/migrations/index.js
+if [ $? -ne 0 ]; then
+ echo ""
+ echo "✗ 迁移失败,请检查错误信息"
+ exit 1
+fi
+
+echo ""
+echo "[2/2] 初始化管理员账号..."
+node backend/init-admin.js
+if [ $? -ne 0 ]; then
+ echo ""
+ echo "✗ 初始化失败,请检查错误信息"
+ exit 1
+fi
+
+echo ""
+echo "========================================"
+echo "✓ 初始化完成!"
+echo "========================================"
+echo ""
+echo "管理员账号信息:"
+echo " 超级管理员: yvan / Xu950329."
+echo " 普通管理员: admin / admin123"
+echo ""
diff --git a/migrate-group-status.bat b/migrate-group-status.bat
new file mode 100644
index 0000000..3e6a0c7
--- /dev/null
+++ b/migrate-group-status.bat
@@ -0,0 +1,7 @@
+@echo off
+echo Running migration: Add group status field...
+cd backend
+node migrations/index.js
+cd ..
+echo Migration completed!
+pause
diff --git a/migrate-prize-source.bat b/migrate-prize-source.bat
new file mode 100644
index 0000000..7194ac8
--- /dev/null
+++ b/migrate-prize-source.bat
@@ -0,0 +1,7 @@
+@echo off
+echo 正在运行数据库迁移...
+cd backend
+node migrations/index.js
+cd ..
+echo 迁移完成!
+pause