feat: NCT集训报名系统 - 陈村校区 + 北滘校区(数据独立)
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
node_modules/
|
||||||
|
data/*.db
|
||||||
112
db.js
Normal file
112
db.js
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
const initSqlJs = require('sql.js');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const ADMIN_PASSWORD = 'admin123'; // 管理员密码,可自行修改
|
||||||
|
|
||||||
|
// 工厂函数:为每个校区创建独立的数据库实例
|
||||||
|
function createCampusDB(campusName) {
|
||||||
|
const dbDir = path.join(__dirname, 'data');
|
||||||
|
const dbPath = path.join(dbDir, campusName + '.db');
|
||||||
|
let db = null;
|
||||||
|
|
||||||
|
async function init() {
|
||||||
|
const SQL = await initSqlJs();
|
||||||
|
|
||||||
|
if (!fs.existsSync(dbDir)) {
|
||||||
|
fs.mkdirSync(dbDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fs.existsSync(dbPath)) {
|
||||||
|
const buffer = fs.readFileSync(dbPath);
|
||||||
|
db = new SQL.Database(buffer);
|
||||||
|
} else {
|
||||||
|
db = new SQL.Database();
|
||||||
|
}
|
||||||
|
|
||||||
|
db.run(`
|
||||||
|
CREATE TABLE IF NOT EXISTS registrations (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
period TEXT NOT NULL,
|
||||||
|
teacher TEXT NOT NULL,
|
||||||
|
createdAt TEXT DEFAULT (datetime('now', 'localtime'))
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
|
||||||
|
save();
|
||||||
|
console.log('[db] ' + campusName + ' 数据库初始化完成');
|
||||||
|
}
|
||||||
|
|
||||||
|
function save() {
|
||||||
|
const data = db.export();
|
||||||
|
const buffer = Buffer.from(data);
|
||||||
|
fs.writeFileSync(dbPath, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
function insertRegistration(name, period, teacher) {
|
||||||
|
db.run(
|
||||||
|
'INSERT INTO registrations (name, period, teacher) VALUES (?, ?, ?)',
|
||||||
|
[name, period, teacher]
|
||||||
|
);
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAllRegistrations() {
|
||||||
|
const result = db.exec('SELECT * FROM registrations ORDER BY id DESC');
|
||||||
|
if (result.length === 0) return [];
|
||||||
|
const columns = result[0].columns;
|
||||||
|
return result[0].values.map(row => {
|
||||||
|
const obj = {};
|
||||||
|
columns.forEach((col, i) => { obj[col] = row[i]; });
|
||||||
|
return obj;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteRegistration(id) {
|
||||||
|
db.run('DELETE FROM registrations WHERE id = ?', [id]);
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStats() {
|
||||||
|
const total = db.exec('SELECT COUNT(*) as count FROM registrations');
|
||||||
|
const totalCount = total.length > 0 ? total[0].values[0][0] : 0;
|
||||||
|
|
||||||
|
const byPeriod = db.exec(
|
||||||
|
'SELECT period, COUNT(*) as count FROM registrations GROUP BY period'
|
||||||
|
);
|
||||||
|
const periodStats = {};
|
||||||
|
if (byPeriod.length > 0) {
|
||||||
|
byPeriod[0].values.forEach(row => {
|
||||||
|
periodStats[row[0]] = row[1];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const byTeacher = db.exec(
|
||||||
|
'SELECT teacher, COUNT(*) as count FROM registrations GROUP BY teacher'
|
||||||
|
);
|
||||||
|
const teacherStats = {};
|
||||||
|
if (byTeacher.length > 0) {
|
||||||
|
byTeacher[0].values.forEach(row => {
|
||||||
|
teacherStats[row[0]] = row[1];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return { total: totalCount, byPeriod: periodStats, byTeacher: teacherStats };
|
||||||
|
}
|
||||||
|
|
||||||
|
function verifyPassword(password) {
|
||||||
|
return password === ADMIN_PASSWORD;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
init,
|
||||||
|
insertRegistration,
|
||||||
|
getAllRegistrations,
|
||||||
|
deleteRegistration,
|
||||||
|
getStats,
|
||||||
|
verifyPassword
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { createCampusDB };
|
||||||
835
package-lock.json
generated
Normal file
835
package-lock.json
generated
Normal file
@@ -0,0 +1,835 @@
|
|||||||
|
{
|
||||||
|
"name": "nct-registration",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "nct-registration",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"express": "^4.22.2",
|
||||||
|
"sql.js": "^1.14.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/accepts": {
|
||||||
|
"version": "1.3.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
||||||
|
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"mime-types": "~2.1.34",
|
||||||
|
"negotiator": "0.6.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/array-flatten": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/body-parser": {
|
||||||
|
"version": "1.20.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz",
|
||||||
|
"integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"bytes": "~3.1.2",
|
||||||
|
"content-type": "~1.0.5",
|
||||||
|
"debug": "2.6.9",
|
||||||
|
"depd": "2.0.0",
|
||||||
|
"destroy": "~1.2.0",
|
||||||
|
"http-errors": "~2.0.1",
|
||||||
|
"iconv-lite": "~0.4.24",
|
||||||
|
"on-finished": "~2.4.1",
|
||||||
|
"qs": "~6.15.1",
|
||||||
|
"raw-body": "~2.5.3",
|
||||||
|
"type-is": "~1.6.18",
|
||||||
|
"unpipe": "~1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8",
|
||||||
|
"npm": "1.2.8000 || >= 1.4.16"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/bytes": {
|
||||||
|
"version": "3.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||||
|
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/call-bind-apply-helpers": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"function-bind": "^1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/call-bound": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bind-apply-helpers": "^1.0.2",
|
||||||
|
"get-intrinsic": "^1.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/content-disposition": {
|
||||||
|
"version": "0.5.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||||
|
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"safe-buffer": "5.2.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/content-type": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cookie": {
|
||||||
|
"version": "0.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
|
||||||
|
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cookie-signature": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
|
||||||
|
"integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/debug": {
|
||||||
|
"version": "2.6.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||||
|
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/depd": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/destroy": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8",
|
||||||
|
"npm": "1.2.8000 || >= 1.4.16"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/dunder-proto": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bind-apply-helpers": "^1.0.1",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"gopd": "^1.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ee-first": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/encodeurl": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/es-define-property": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/es-errors": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/es-object-atoms": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"es-errors": "^1.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/escape-html": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/etag": {
|
||||||
|
"version": "1.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||||
|
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/express": {
|
||||||
|
"version": "4.22.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz",
|
||||||
|
"integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"accepts": "~1.3.8",
|
||||||
|
"array-flatten": "1.1.1",
|
||||||
|
"body-parser": "~1.20.5",
|
||||||
|
"content-disposition": "~0.5.4",
|
||||||
|
"content-type": "~1.0.4",
|
||||||
|
"cookie": "~0.7.1",
|
||||||
|
"cookie-signature": "~1.0.6",
|
||||||
|
"debug": "2.6.9",
|
||||||
|
"depd": "2.0.0",
|
||||||
|
"encodeurl": "~2.0.0",
|
||||||
|
"escape-html": "~1.0.3",
|
||||||
|
"etag": "~1.8.1",
|
||||||
|
"finalhandler": "~1.3.1",
|
||||||
|
"fresh": "~0.5.2",
|
||||||
|
"http-errors": "~2.0.0",
|
||||||
|
"merge-descriptors": "1.0.3",
|
||||||
|
"methods": "~1.1.2",
|
||||||
|
"on-finished": "~2.4.1",
|
||||||
|
"parseurl": "~1.3.3",
|
||||||
|
"path-to-regexp": "~0.1.12",
|
||||||
|
"proxy-addr": "~2.0.7",
|
||||||
|
"qs": "~6.15.1",
|
||||||
|
"range-parser": "~1.2.1",
|
||||||
|
"safe-buffer": "5.2.1",
|
||||||
|
"send": "~0.19.0",
|
||||||
|
"serve-static": "~1.16.2",
|
||||||
|
"setprototypeof": "1.2.0",
|
||||||
|
"statuses": "~2.0.1",
|
||||||
|
"type-is": "~1.6.18",
|
||||||
|
"utils-merge": "1.0.1",
|
||||||
|
"vary": "~1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.10.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/express"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/finalhandler": {
|
||||||
|
"version": "1.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
|
||||||
|
"integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"debug": "2.6.9",
|
||||||
|
"encodeurl": "~2.0.0",
|
||||||
|
"escape-html": "~1.0.3",
|
||||||
|
"on-finished": "~2.4.1",
|
||||||
|
"parseurl": "~1.3.3",
|
||||||
|
"statuses": "~2.0.2",
|
||||||
|
"unpipe": "~1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/forwarded": {
|
||||||
|
"version": "0.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||||
|
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/fresh": {
|
||||||
|
"version": "0.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||||
|
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/function-bind": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/get-intrinsic": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bind-apply-helpers": "^1.0.2",
|
||||||
|
"es-define-property": "^1.0.1",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"es-object-atoms": "^1.1.1",
|
||||||
|
"function-bind": "^1.1.2",
|
||||||
|
"get-proto": "^1.0.1",
|
||||||
|
"gopd": "^1.2.0",
|
||||||
|
"has-symbols": "^1.1.0",
|
||||||
|
"hasown": "^2.0.2",
|
||||||
|
"math-intrinsics": "^1.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/get-proto": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"dunder-proto": "^1.0.1",
|
||||||
|
"es-object-atoms": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/gopd": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/has-symbols": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/hasown": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"function-bind": "^1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/http-errors": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"depd": "~2.0.0",
|
||||||
|
"inherits": "~2.0.4",
|
||||||
|
"setprototypeof": "~1.2.0",
|
||||||
|
"statuses": "~2.0.2",
|
||||||
|
"toidentifier": "~1.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/express"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/iconv-lite": {
|
||||||
|
"version": "0.4.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||||
|
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"safer-buffer": ">= 2.1.2 < 3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/inherits": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
|
"node_modules/ipaddr.js": {
|
||||||
|
"version": "1.9.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||||
|
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/math-intrinsics": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/media-typer": {
|
||||||
|
"version": "0.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||||
|
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/merge-descriptors": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/methods": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mime": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||||
|
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"mime": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mime-db": {
|
||||||
|
"version": "1.52.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||||
|
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mime-types": {
|
||||||
|
"version": "2.1.35",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||||
|
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"mime-db": "1.52.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ms": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/negotiator": {
|
||||||
|
"version": "0.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
||||||
|
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/object-inspect": {
|
||||||
|
"version": "1.13.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
|
||||||
|
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/on-finished": {
|
||||||
|
"version": "2.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
||||||
|
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ee-first": "1.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/parseurl": {
|
||||||
|
"version": "1.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||||
|
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/path-to-regexp": {
|
||||||
|
"version": "0.1.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz",
|
||||||
|
"integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/proxy-addr": {
|
||||||
|
"version": "2.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||||
|
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"forwarded": "0.2.0",
|
||||||
|
"ipaddr.js": "1.9.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/qs": {
|
||||||
|
"version": "6.15.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz",
|
||||||
|
"integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"side-channel": "^1.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/range-parser": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/raw-body": {
|
||||||
|
"version": "2.5.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
|
||||||
|
"integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"bytes": "~3.1.2",
|
||||||
|
"http-errors": "~2.0.1",
|
||||||
|
"iconv-lite": "~0.4.24",
|
||||||
|
"unpipe": "~1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/safe-buffer": {
|
||||||
|
"version": "5.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||||
|
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/safer-buffer": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/send": {
|
||||||
|
"version": "0.19.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
|
||||||
|
"integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"debug": "2.6.9",
|
||||||
|
"depd": "2.0.0",
|
||||||
|
"destroy": "1.2.0",
|
||||||
|
"encodeurl": "~2.0.0",
|
||||||
|
"escape-html": "~1.0.3",
|
||||||
|
"etag": "~1.8.1",
|
||||||
|
"fresh": "~0.5.2",
|
||||||
|
"http-errors": "~2.0.1",
|
||||||
|
"mime": "1.6.0",
|
||||||
|
"ms": "2.1.3",
|
||||||
|
"on-finished": "~2.4.1",
|
||||||
|
"range-parser": "~1.2.1",
|
||||||
|
"statuses": "~2.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/send/node_modules/ms": {
|
||||||
|
"version": "2.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/serve-static": {
|
||||||
|
"version": "1.16.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
|
||||||
|
"integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"encodeurl": "~2.0.0",
|
||||||
|
"escape-html": "~1.0.3",
|
||||||
|
"parseurl": "~1.3.3",
|
||||||
|
"send": "~0.19.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/setprototypeof": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
|
"node_modules/side-channel": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"object-inspect": "^1.13.4",
|
||||||
|
"side-channel-list": "^1.0.1",
|
||||||
|
"side-channel-map": "^1.0.1",
|
||||||
|
"side-channel-weakmap": "^1.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/side-channel-list": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"object-inspect": "^1.13.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/side-channel-map": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bound": "^1.0.2",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"get-intrinsic": "^1.2.5",
|
||||||
|
"object-inspect": "^1.13.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/side-channel-weakmap": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bound": "^1.0.2",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"get-intrinsic": "^1.2.5",
|
||||||
|
"object-inspect": "^1.13.3",
|
||||||
|
"side-channel-map": "^1.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sql.js": {
|
||||||
|
"version": "1.14.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/sql.js/-/sql.js-1.14.1.tgz",
|
||||||
|
"integrity": "sha512-gcj8zBWU5cFsi9WUP+4bFNXAyF1iRpA3LLyS/DP5xlrNzGmPIizUeBggKa8DbDwdqaKwUcTEnChtd2grWo/x/A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/statuses": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/toidentifier": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/type-is": {
|
||||||
|
"version": "1.6.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||||
|
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"media-typer": "0.3.0",
|
||||||
|
"mime-types": "~2.1.24"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/unpipe": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/utils-merge": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vary": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
package.json
Normal file
18
package.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "nct-registration",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "NCT 集训报名系统",
|
||||||
|
"main": "server.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node server.js",
|
||||||
|
"dev": "node --watch server.js"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"type": "commonjs",
|
||||||
|
"dependencies": {
|
||||||
|
"express": "^4.22.2",
|
||||||
|
"sql.js": "^1.14.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
529
public/beijiao/admin.html
Normal file
529
public/beijiao/admin.html
Normal file
@@ -0,0 +1,529 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>报名管理后台 | 北滘校区</title>
|
||||||
|
<style>
|
||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
:root {
|
||||||
|
--orange: #FF6B35;
|
||||||
|
--orange-dark: #E85A24;
|
||||||
|
--blue: #4A6CF7;
|
||||||
|
--dark: #1A1A2E;
|
||||||
|
--text: #333;
|
||||||
|
--text-light: #666;
|
||||||
|
--bg: #F5F7FA;
|
||||||
|
--white: #fff;
|
||||||
|
--shadow: 0 4px 24px rgba(0,0,0,0.08);
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--text);
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====== Header ====== */
|
||||||
|
.header {
|
||||||
|
background: linear-gradient(135deg, var(--dark) 0%, #2D2D5E 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 24px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.header h1 { font-size: 22px; font-weight: 700; }
|
||||||
|
.header p { font-size: 13px; opacity: 0.6; margin-top: 4px; }
|
||||||
|
|
||||||
|
/* ====== Login ====== */
|
||||||
|
.login-section {
|
||||||
|
max-width: 400px;
|
||||||
|
margin: 60px auto;
|
||||||
|
background: var(--white);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 40px 32px;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.login-section h2 { font-size: 20px; color: var(--dark); margin-bottom: 8px; }
|
||||||
|
.login-section p { font-size: 14px; color: var(--text-light); margin-bottom: 24px; }
|
||||||
|
.login-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border: 2px solid #e8e8e8;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 16px;
|
||||||
|
outline: none;
|
||||||
|
transition: border-color 0.3s;
|
||||||
|
text-align: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.login-input:focus { border-color: var(--orange); }
|
||||||
|
.login-btn {
|
||||||
|
width: 100%;
|
||||||
|
padding: 14px;
|
||||||
|
background: var(--orange);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top: 16px;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
.login-btn:hover { background: var(--orange-dark); }
|
||||||
|
.login-error {
|
||||||
|
color: #FF4444;
|
||||||
|
font-size: 13px;
|
||||||
|
margin-top: 10px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====== Dashboard ====== */
|
||||||
|
.dashboard { display: none; }
|
||||||
|
.dashboard.active { display: block; }
|
||||||
|
|
||||||
|
/* Stats Bar */
|
||||||
|
.stats-bar {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 24px;
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.stat-card {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 140px;
|
||||||
|
background: var(--white);
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.stat-card .stat-num {
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--orange);
|
||||||
|
}
|
||||||
|
.stat-card .stat-label {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--text-light);
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Controls */
|
||||||
|
.controls {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 24px;
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.controls select, .controls input {
|
||||||
|
padding: 10px 14px;
|
||||||
|
border: 2px solid #e8e8e8;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
outline: none;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
.controls select:focus, .controls input:focus {
|
||||||
|
border-color: var(--orange);
|
||||||
|
}
|
||||||
|
.ctrl-btn {
|
||||||
|
padding: 10px 20px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.2s;
|
||||||
|
}
|
||||||
|
.ctrl-btn:hover { transform: scale(1.03); }
|
||||||
|
.ctrl-btn.orange { background: var(--orange); color: white; }
|
||||||
|
.ctrl-btn.blue { background: var(--blue); color: white; }
|
||||||
|
.ctrl-btn.outline { background: transparent; border: 2px solid #ddd; color: var(--text-light); }
|
||||||
|
.ctrl-btn.outline:hover { border-color: var(--orange); color: var(--orange); }
|
||||||
|
.ctrl-btn.danger { background: #FF4444; color: white; }
|
||||||
|
.spacer { flex: 1; }
|
||||||
|
|
||||||
|
/* Table */
|
||||||
|
.table-section {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 20px auto;
|
||||||
|
padding: 0 24px 40px;
|
||||||
|
}
|
||||||
|
.table-wrap {
|
||||||
|
background: var(--white);
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
background: #f8f9fa;
|
||||||
|
padding: 14px 16px;
|
||||||
|
text-align: left;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-light);
|
||||||
|
border-bottom: 2px solid #eee;
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
padding: 14px 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
tr:last-child td { border-bottom: none; }
|
||||||
|
tr:hover td { background: #FAFAFA; }
|
||||||
|
.badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 3px 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.badge-orange { background: #FFF0E8; color: var(--orange); }
|
||||||
|
.badge-blue { background: #EEF2FF; color: var(--blue); }
|
||||||
|
.del-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #FF4444;
|
||||||
|
font-size: 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0.6;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
}
|
||||||
|
.del-btn:hover { opacity: 1; }
|
||||||
|
|
||||||
|
/* Empty State */
|
||||||
|
.empty {
|
||||||
|
text-align: center;
|
||||||
|
padding: 48px 20px;
|
||||||
|
color: #bbb;
|
||||||
|
}
|
||||||
|
.empty-icon { font-size: 48px; margin-bottom: 12px; }
|
||||||
|
.empty p { font-size: 15px; }
|
||||||
|
|
||||||
|
/* Confirm Modal */
|
||||||
|
.modal-overlay {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 0; left: 0; right: 0; bottom: 0;
|
||||||
|
background: rgba(0,0,0,0.4);
|
||||||
|
z-index: 1000;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.modal-overlay.show { display: flex; }
|
||||||
|
.modal {
|
||||||
|
background: white;
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 32px;
|
||||||
|
width: 90%;
|
||||||
|
max-width: 360px;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: 0 20px 60px rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
.modal h3 { font-size: 18px; color: var(--dark); margin-bottom: 8px; }
|
||||||
|
.modal p { font-size: 14px; color: var(--text-light); margin-bottom: 20px; }
|
||||||
|
.modal-btns { display: flex; gap: 12px; }
|
||||||
|
.modal-btns button {
|
||||||
|
flex: 1;
|
||||||
|
padding: 12px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.modal-btns .cancel-btn { background: #f0f0f0; color: var(--text-light); }
|
||||||
|
.modal-btns .confirm-btn { background: #FF4444; color: white; }
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.stats-bar { padding: 16px; gap: 10px; }
|
||||||
|
.stat-card { min-width: 100px; padding: 14px; }
|
||||||
|
.stat-card .stat-num { font-size: 24px; }
|
||||||
|
.controls { padding: 0 16px; }
|
||||||
|
.table-section { padding: 0 16px 24px; }
|
||||||
|
th, td { padding: 10px 12px; font-size: 13px; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="header">
|
||||||
|
<h1>📋 报名管理后台</h1>
|
||||||
|
<p>北滘校区 · NCT 集训报名数据管理</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Login -->
|
||||||
|
<div class="login-section" id="loginSection">
|
||||||
|
<h2>🔐 管理员登录</h2>
|
||||||
|
<p>请输入管理员密码查看报名数据</p>
|
||||||
|
<input class="login-input" type="password" id="adminPassword" placeholder="输入管理员密码" onkeydown="if(event.key==='Enter') doLogin()">
|
||||||
|
<button class="login-btn" onclick="doLogin()">登 录</button>
|
||||||
|
<div class="login-error" id="loginError">密码错误,请重试</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Dashboard -->
|
||||||
|
<div class="dashboard" id="dashboard">
|
||||||
|
|
||||||
|
<!-- Stats -->
|
||||||
|
<div class="stats-bar" id="statsBar">
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-num" id="statTotal">0</div>
|
||||||
|
<div class="stat-label">总报名人数</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-num" id="statP1">0</div>
|
||||||
|
<div class="stat-label">第一期</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-num" id="statP2">0</div>
|
||||||
|
<div class="stat-label">第二期</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-num" id="statP3">0</div>
|
||||||
|
<div class="stat-label">第三期</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Controls -->
|
||||||
|
<div class="controls">
|
||||||
|
<select id="filterPeriod" onchange="loadData()">
|
||||||
|
<option value="">全部期数</option>
|
||||||
|
<option value="第一期">第一期</option>
|
||||||
|
<option value="第二期">第二期</option>
|
||||||
|
<option value="第三期">第三期</option>
|
||||||
|
</select>
|
||||||
|
<select id="filterTeacher" onchange="loadData()">
|
||||||
|
<option value="">全部老师</option>
|
||||||
|
</select>
|
||||||
|
<input type="text" id="filterName" placeholder="搜索姓名" oninput="loadData()">
|
||||||
|
<div class="spacer"></div>
|
||||||
|
<button class="ctrl-btn outline" onclick="loadData()">🔄 刷新</button>
|
||||||
|
<button class="ctrl-btn blue" onclick="exportCSV()">📥 导出 CSV</button>
|
||||||
|
<button class="ctrl-btn outline" onclick="doLogout()">退出登录</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Table -->
|
||||||
|
<div class="table-section">
|
||||||
|
<div class="table-wrap">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>序号</th>
|
||||||
|
<th>姓名</th>
|
||||||
|
<th>期数</th>
|
||||||
|
<th>老师</th>
|
||||||
|
<th>报名时间</th>
|
||||||
|
<th>操作</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="tableBody">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="empty" id="emptyState" style="display:none">
|
||||||
|
<div class="empty-icon">📭</div>
|
||||||
|
<p>暂无报名记录</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Delete Confirm Modal -->
|
||||||
|
<div class="modal-overlay" id="deleteModal">
|
||||||
|
<div class="modal">
|
||||||
|
<h3>确认删除</h3>
|
||||||
|
<p id="deleteMsg">确定要删除这条报名记录吗?此操作不可撤销。</p>
|
||||||
|
<div class="modal-btns">
|
||||||
|
<button class="cancel-btn" onclick="closeDeleteModal()">取消</button>
|
||||||
|
<button class="confirm-btn" onclick="confirmDelete()">确认删除</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var adminPwd = '';
|
||||||
|
var allData = [];
|
||||||
|
var deleteId = null;
|
||||||
|
|
||||||
|
function doLogin() {
|
||||||
|
adminPwd = document.getElementById('adminPassword').value;
|
||||||
|
if (!adminPwd) return;
|
||||||
|
|
||||||
|
fetch('api/stats?password=' + encodeURIComponent(adminPwd))
|
||||||
|
.then(function(r) { return r.json(); })
|
||||||
|
.then(function(data) {
|
||||||
|
if (data.ok) {
|
||||||
|
document.getElementById('loginSection').style.display = 'none';
|
||||||
|
document.getElementById('dashboard').classList.add('active');
|
||||||
|
updateStats(data.data);
|
||||||
|
loadData();
|
||||||
|
} else {
|
||||||
|
document.getElementById('loginError').style.display = 'block';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function() {
|
||||||
|
document.getElementById('loginError').style.display = 'block';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function doLogout() {
|
||||||
|
adminPwd = '';
|
||||||
|
document.getElementById('loginSection').style.display = '';
|
||||||
|
document.getElementById('dashboard').classList.remove('active');
|
||||||
|
document.getElementById('adminPassword').value = '';
|
||||||
|
document.getElementById('loginError').style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateStats(stats) {
|
||||||
|
document.getElementById('statTotal').textContent = stats.total || 0;
|
||||||
|
var periods = stats.byPeriod || {};
|
||||||
|
document.getElementById('statP1').textContent = periods['第一期:7月13日–7月25日'] || 0;
|
||||||
|
document.getElementById('statP2').textContent = periods['第二期:7月27日–8月8日'] || 0;
|
||||||
|
document.getElementById('statP3').textContent = periods['第三期:8月10日–8月22日'] || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadData() {
|
||||||
|
fetch('api/registrations?password=' + encodeURIComponent(adminPwd))
|
||||||
|
.then(function(r) { return r.json(); })
|
||||||
|
.then(function(data) {
|
||||||
|
if (!data.ok) return;
|
||||||
|
allData = data.data;
|
||||||
|
applyFilters();
|
||||||
|
updateTeacherFilter(allData);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTeacherFilter(records) {
|
||||||
|
var sel = document.getElementById('filterTeacher');
|
||||||
|
var current = sel.value;
|
||||||
|
var teachers = {};
|
||||||
|
records.forEach(function(r) { teachers[r.teacher] = true; });
|
||||||
|
sel.innerHTML = '<option value="">全部老师</option>';
|
||||||
|
Object.keys(teachers).sort().forEach(function(t) {
|
||||||
|
var opt = document.createElement('option');
|
||||||
|
opt.value = t;
|
||||||
|
opt.textContent = t;
|
||||||
|
if (t === current) opt.selected = true;
|
||||||
|
sel.appendChild(opt);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyFilters() {
|
||||||
|
var periodFilter = document.getElementById('filterPeriod').value;
|
||||||
|
var teacherFilter = document.getElementById('filterTeacher').value;
|
||||||
|
var nameFilter = document.getElementById('filterName').value.trim().toLowerCase();
|
||||||
|
|
||||||
|
var filtered = allData.filter(function(r) {
|
||||||
|
if (periodFilter && !r.period.includes(periodFilter)) return false;
|
||||||
|
if (teacherFilter && r.teacher !== teacherFilter) return false;
|
||||||
|
if (nameFilter && !r.name.toLowerCase().includes(nameFilter)) return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
renderTable(filtered);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderTable(records) {
|
||||||
|
var tbody = document.getElementById('tableBody');
|
||||||
|
var empty = document.getElementById('emptyState');
|
||||||
|
|
||||||
|
if (records.length === 0) {
|
||||||
|
tbody.innerHTML = '';
|
||||||
|
empty.style.display = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
empty.style.display = 'none';
|
||||||
|
var html = '';
|
||||||
|
records.forEach(function(r, i) {
|
||||||
|
var badgeClass = r.period.includes('第二期') ? 'badge-blue' : 'badge-orange';
|
||||||
|
var periodShort = r.period;
|
||||||
|
html += '<tr>' +
|
||||||
|
'<td>' + (i + 1) + '</td>' +
|
||||||
|
'<td><strong>' + escHtml(r.name) + '</strong></td>' +
|
||||||
|
'<td><span class="badge ' + badgeClass + '">' + escHtml(periodShort) + '</span></td>' +
|
||||||
|
'<td>' + escHtml(r.teacher) + '</td>' +
|
||||||
|
'<td>' + escHtml(r.createdAt || '-') + '</td>' +
|
||||||
|
'<td><button class="del-btn" onclick="showDeleteModal(' + r.id + ', \'' + escHtml(r.name) + '\')">删除</button></td>' +
|
||||||
|
'</tr>';
|
||||||
|
});
|
||||||
|
tbody.innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function escHtml(str) {
|
||||||
|
var d = document.createElement('div');
|
||||||
|
d.textContent = str;
|
||||||
|
return d.innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
function showDeleteModal(id, name) {
|
||||||
|
deleteId = id;
|
||||||
|
document.getElementById('deleteMsg').textContent = '确定要删除「' + name + '」的报名记录吗?此操作不可撤销。';
|
||||||
|
document.getElementById('deleteModal').classList.add('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeDeleteModal() {
|
||||||
|
document.getElementById('deleteModal').classList.remove('show');
|
||||||
|
deleteId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirmDelete() {
|
||||||
|
if (!deleteId) return;
|
||||||
|
fetch('api/registrations/' + deleteId + '?password=' + encodeURIComponent(adminPwd), {
|
||||||
|
method: 'DELETE'
|
||||||
|
})
|
||||||
|
.then(function(r) { return r.json(); })
|
||||||
|
.then(function(data) {
|
||||||
|
closeDeleteModal();
|
||||||
|
if (data.ok) {
|
||||||
|
loadData();
|
||||||
|
fetch('api/stats?password=' + encodeURIComponent(adminPwd))
|
||||||
|
.then(function(r) { return r.json(); })
|
||||||
|
.then(function(s) { if (s.ok) updateStats(s.data); });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function exportCSV() {
|
||||||
|
var periodFilter = document.getElementById('filterPeriod').value;
|
||||||
|
var teacherFilter = document.getElementById('filterTeacher').value;
|
||||||
|
var nameFilter = document.getElementById('filterName').value.trim().toLowerCase();
|
||||||
|
|
||||||
|
var filtered = allData.filter(function(r) {
|
||||||
|
if (periodFilter && !r.period.includes(periodFilter)) return false;
|
||||||
|
if (teacherFilter && r.teacher !== teacherFilter) return false;
|
||||||
|
if (nameFilter && !r.name.toLowerCase().includes(nameFilter)) return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
var csv = '序号,姓名,期数,老师,报名时间\n';
|
||||||
|
filtered.forEach(function(r, i) {
|
||||||
|
csv += (i + 1) + ',' + r.name + ',' + r.period + ',' + r.teacher + ',' + (r.createdAt || '') + '\n';
|
||||||
|
});
|
||||||
|
|
||||||
|
var blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
||||||
|
var url = URL.createObjectURL(blob);
|
||||||
|
var a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = '北滘校区_报名数据_' + new Date().toISOString().slice(0, 10) + '.csv';
|
||||||
|
a.click();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('deleteModal').addEventListener('click', function(e) {
|
||||||
|
if (e.target === this) closeDeleteModal();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
704
public/beijiao/css/style.css
Normal file
704
public/beijiao/css/style.css
Normal file
@@ -0,0 +1,704 @@
|
|||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--orange: #FF6B35;
|
||||||
|
--orange-light: #FF8F5E;
|
||||||
|
--orange-dark: #E85A24;
|
||||||
|
--blue: #4A6CF7;
|
||||||
|
--blue-light: #6B8CFF;
|
||||||
|
--purple: #7C5CFC;
|
||||||
|
--dark: #1A1A2E;
|
||||||
|
--text: #333;
|
||||||
|
--text-light: #666;
|
||||||
|
--bg: #F5F7FA;
|
||||||
|
--white: #fff;
|
||||||
|
--shadow: 0 4px 24px rgba(0,0,0,0.08);
|
||||||
|
--shadow-lg: 0 12px 40px rgba(0,0,0,0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--text);
|
||||||
|
line-height: 1.6;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====== Hero ====== */
|
||||||
|
.hero {
|
||||||
|
background: linear-gradient(135deg, #FF6B35 0%, #FF8F5E 40%, #FFB347 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 60px 24px 80px;
|
||||||
|
text-align: center;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -50%;
|
||||||
|
left: -50%;
|
||||||
|
width: 200%;
|
||||||
|
height: 200%;
|
||||||
|
background: radial-gradient(circle, rgba(255,255,255,0.08) 1px, transparent 1px);
|
||||||
|
background-size: 30px 30px;
|
||||||
|
animation: heroPattern 20s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes heroPattern {
|
||||||
|
from { transform: translate(0, 0); }
|
||||||
|
to { transform: translate(30px, 30px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-content {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
max-width: 700px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-badge {
|
||||||
|
display: inline-block;
|
||||||
|
background: rgba(255,255,255,0.2);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
padding: 6px 20px;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero h1 {
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: 800;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
text-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero .subtitle {
|
||||||
|
font-size: 18px;
|
||||||
|
opacity: 0.95;
|
||||||
|
margin-bottom: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-stats {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 32px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-stat {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-stat .num {
|
||||||
|
font-size: 36px;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-stat .label {
|
||||||
|
font-size: 13px;
|
||||||
|
opacity: 0.85;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====== Sections ====== */
|
||||||
|
.section {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 48px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-header h2 {
|
||||||
|
font-size: 26px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--dark);
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-header p {
|
||||||
|
color: var(--text-light);
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====== Info Cards ====== */
|
||||||
|
.info-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-card {
|
||||||
|
background: var(--white);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 24px;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: flex-start;
|
||||||
|
transition: transform 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-card:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-icon {
|
||||||
|
width: 52px;
|
||||||
|
height: 52px;
|
||||||
|
border-radius: 14px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 26px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-icon.orange { background: #FFF0E8; }
|
||||||
|
.info-icon.blue { background: #EEF2FF; }
|
||||||
|
.info-icon.purple { background: #F3EEFF; }
|
||||||
|
.info-icon.green { background: #E8F9EF; }
|
||||||
|
|
||||||
|
.info-card h3 {
|
||||||
|
font-size: 17px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--dark);
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-card p {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--text-light);
|
||||||
|
line-height: 1.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====== Timeline ====== */
|
||||||
|
.period-section {
|
||||||
|
background: var(--white);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 24px;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.period-badge {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 3px 12px;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.period-badge.orange { background: #FFF0E8; color: var(--orange); }
|
||||||
|
.period-badge.blue { background: #EEF2FF; color: var(--blue); }
|
||||||
|
|
||||||
|
.period-section h3 {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--dark);
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.period-info {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--text-light);
|
||||||
|
margin-bottom: 16px;
|
||||||
|
line-height: 1.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====== Registration Input ====== */
|
||||||
|
.reg-input-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reg-input {
|
||||||
|
flex: 1;
|
||||||
|
padding: 10px 14px;
|
||||||
|
border: 2px solid #e8e8e8;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
outline: none;
|
||||||
|
transition: border-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reg-input:focus {
|
||||||
|
border-color: var(--orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
.reg-input::placeholder {
|
||||||
|
color: #bbb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reg-btn {
|
||||||
|
padding: 10px 20px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
white-space: nowrap;
|
||||||
|
transition: transform 0.2s, opacity 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reg-btn:hover { transform: scale(1.03); }
|
||||||
|
.reg-btn:active { transform: scale(0.97); }
|
||||||
|
.reg-btn.orange { background: var(--orange); }
|
||||||
|
.reg-btn.blue { background: var(--blue); }
|
||||||
|
|
||||||
|
/* ====== Exam Info ====== */
|
||||||
|
.exam-card {
|
||||||
|
background: linear-gradient(135deg, var(--dark) 0%, #2D2D5E 100%);
|
||||||
|
color: white;
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 32px 28px;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exam-card::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -40%;
|
||||||
|
right: -20%;
|
||||||
|
width: 200px;
|
||||||
|
height: 200px;
|
||||||
|
background: radial-gradient(circle, rgba(255,107,53,0.15), transparent);
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exam-card h3 {
|
||||||
|
font-size: 15px;
|
||||||
|
opacity: 0.7;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exam-date {
|
||||||
|
font-size: 38px;
|
||||||
|
font-weight: 800;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
background: linear-gradient(135deg, #FF6B35, #FFB347);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exam-note {
|
||||||
|
font-size: 14px;
|
||||||
|
opacity: 0.6;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====== Value Cards ====== */
|
||||||
|
.value-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-card {
|
||||||
|
background: var(--white);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 22px 18px;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
transition: transform 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-card:hover {
|
||||||
|
transform: translateY(-3px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-emoji {
|
||||||
|
font-size: 36px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-card h3 {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--dark);
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-card p {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--text-light);
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====== Highlights ====== */
|
||||||
|
.highlight-strip {
|
||||||
|
background: linear-gradient(135deg, #FFF8E1, #FFF3E0);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 20px 24px;
|
||||||
|
border-left: 5px solid #FFB347;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight-strip p {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #8B6914;
|
||||||
|
line-height: 1.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight-strip strong {
|
||||||
|
color: #E85A24;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====== Fee Section ====== */
|
||||||
|
.fee-table {
|
||||||
|
width: 100%;
|
||||||
|
background: var(--white);
|
||||||
|
border-radius: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fee-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 18px 24px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fee-row:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fee-label {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 15px;
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fee-value {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====== FAQ ====== */
|
||||||
|
.faq-item {
|
||||||
|
background: var(--white);
|
||||||
|
border-radius: 14px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
box-shadow: 0 2px 12px rgba(0,0,0,0.05);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faq-q {
|
||||||
|
padding: 18px 24px;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--dark);
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
user-select: none;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faq-q:hover {
|
||||||
|
background: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faq-q .arrow {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
transition: transform 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faq-item.open .faq-q .arrow {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.faq-a {
|
||||||
|
display: none;
|
||||||
|
padding: 0 24px 18px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--text-light);
|
||||||
|
line-height: 1.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faq-item.open .faq-a {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====== CTA ====== */
|
||||||
|
.cta-section {
|
||||||
|
text-align: center;
|
||||||
|
padding: 48px 20px 24px;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-box {
|
||||||
|
background: linear-gradient(135deg, #FF6B35, #FF8F5E, #FFB347);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 40px 28px;
|
||||||
|
color: white;
|
||||||
|
box-shadow: 0 8px 32px rgba(255,107,53,0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-box h2 {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-box p {
|
||||||
|
font-size: 15px;
|
||||||
|
opacity: 0.95;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
line-height: 1.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-btn {
|
||||||
|
display: inline-block;
|
||||||
|
background: white;
|
||||||
|
color: var(--orange);
|
||||||
|
padding: 14px 40px;
|
||||||
|
border-radius: 30px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-decoration: none;
|
||||||
|
box-shadow: 0 4px 16px rgba(0,0,0,0.15);
|
||||||
|
transition: transform 0.3s, box-shadow 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-btn:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 20px rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====== Countdown ====== */
|
||||||
|
.countdown-section {
|
||||||
|
background: var(--dark);
|
||||||
|
color: white;
|
||||||
|
padding: 36px 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.countdown-title {
|
||||||
|
font-size: 14px;
|
||||||
|
opacity: 0.6;
|
||||||
|
letter-spacing: 3px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.countdown-boxes {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.countdown-box {
|
||||||
|
background: rgba(255,255,255,0.08);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 16px 20px;
|
||||||
|
min-width: 72px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.countdown-box .num {
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--orange-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.countdown-box .unit {
|
||||||
|
font-size: 12px;
|
||||||
|
opacity: 0.5;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====== Footer ====== */
|
||||||
|
.footer {
|
||||||
|
text-align: center;
|
||||||
|
padding: 32px 20px;
|
||||||
|
color: #aaa;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====== Modal ====== */
|
||||||
|
.modal-overlay {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 0; left: 0; right: 0; bottom: 0;
|
||||||
|
background: rgba(0,0,0,0.4);
|
||||||
|
z-index: 1000;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-overlay.show {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
background: white;
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 32px;
|
||||||
|
width: 90%;
|
||||||
|
max-width: 380px;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: 0 20px 60px rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal h3 {
|
||||||
|
font-size: 20px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
color: var(--dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal .modal-period {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--orange);
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border: 2px solid #e8e8e8;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 16px;
|
||||||
|
outline: none;
|
||||||
|
transition: border-color 0.3s;
|
||||||
|
box-sizing: border-box;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-input:focus {
|
||||||
|
border-color: var(--orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border: 2px solid #e8e8e8;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 16px;
|
||||||
|
outline: none;
|
||||||
|
background: white;
|
||||||
|
color: #999;
|
||||||
|
appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%23999' stroke-width='1.5' fill='none'/%3E%3C/svg%3E");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: right 14px center;
|
||||||
|
cursor: pointer;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-select:focus {
|
||||||
|
border-color: var(--orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-select.has-value {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-submit {
|
||||||
|
width: 100%;
|
||||||
|
padding: 14px;
|
||||||
|
background: var(--orange);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top: 14px;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-submit:hover { background: var(--orange-dark); }
|
||||||
|
.modal-submit:disabled { background: #ccc; cursor: default; }
|
||||||
|
|
||||||
|
.modal-cancel {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #999;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-top: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-cancel:hover { color: #666; }
|
||||||
|
|
||||||
|
/* ====== Toast ====== */
|
||||||
|
.toast {
|
||||||
|
position: fixed;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%) scale(0.85);
|
||||||
|
background: rgba(0,0,0,0.82);
|
||||||
|
color: #fff;
|
||||||
|
padding: 28px 36px;
|
||||||
|
border-radius: 18px;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1.8;
|
||||||
|
text-align: center;
|
||||||
|
z-index: 2000;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s, transform 0.3s;
|
||||||
|
max-width: 340px;
|
||||||
|
box-shadow: 0 12px 40px rgba(0,0,0,0.25);
|
||||||
|
pointer-events: none;
|
||||||
|
white-space: pre-line;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast.show {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translate(-50%, -50%) scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====== Animations ====== */
|
||||||
|
.fade-up {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(30px);
|
||||||
|
transition: opacity 0.6s ease, transform 0.6s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-up.visible {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====== Mobile ====== */
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.hero { padding: 48px 20px 64px; }
|
||||||
|
.hero h1 { font-size: 26px; }
|
||||||
|
.hero .subtitle { font-size: 15px; }
|
||||||
|
.hero-stats { gap: 20px; }
|
||||||
|
.hero-stat .num { font-size: 28px; }
|
||||||
|
.section { padding: 36px 16px; }
|
||||||
|
.section-header h2 { font-size: 22px; }
|
||||||
|
.value-grid { grid-template-columns: 1fr; }
|
||||||
|
.exam-date { font-size: 30px; }
|
||||||
|
.countdown-boxes { gap: 8px; }
|
||||||
|
.countdown-box { padding: 12px 16px; min-width: 60px; }
|
||||||
|
.countdown-box .num { font-size: 26px; }
|
||||||
|
}
|
||||||
320
public/beijiao/index.html
Normal file
320
public/beijiao/index.html
Normal file
@@ -0,0 +1,320 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>2026 暑假 Kitten 图形化编程等级考试集训 | 北滘校区</title>
|
||||||
|
<link rel="stylesheet" href="css/style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<!-- Hero -->
|
||||||
|
<div class="hero">
|
||||||
|
<div class="hero-content">
|
||||||
|
<span class="hero-badge">🎯 2026 暑假集训</span>
|
||||||
|
<h1>Kitten 图形化编程<br>等级考试集训</h1>
|
||||||
|
<p class="subtitle">NCT 青少年编程能力等级测试 · 北滘校区</p>
|
||||||
|
<div class="hero-stats">
|
||||||
|
<div class="hero-stat">
|
||||||
|
<div class="num">100%</div>
|
||||||
|
<div class="label">集训学员通过率</div>
|
||||||
|
</div>
|
||||||
|
<div class="hero-stat">
|
||||||
|
<div class="num">75W+</div>
|
||||||
|
<div class="label">全国考生累计</div>
|
||||||
|
</div>
|
||||||
|
<div class="hero-stat">
|
||||||
|
<div class="num">8人</div>
|
||||||
|
<div class="label">小班精品教学</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 倒计时 -->
|
||||||
|
<div class="countdown-section">
|
||||||
|
<div class="countdown-title">距离 NCT 考试还有</div>
|
||||||
|
<div class="countdown-boxes">
|
||||||
|
<div class="countdown-box">
|
||||||
|
<div class="num" id="cd-days">--</div>
|
||||||
|
<div class="unit">天</div>
|
||||||
|
</div>
|
||||||
|
<div class="countdown-box">
|
||||||
|
<div class="num" id="cd-hours">--</div>
|
||||||
|
<div class="unit">时</div>
|
||||||
|
</div>
|
||||||
|
<div class="countdown-box">
|
||||||
|
<div class="num" id="cd-mins">--</div>
|
||||||
|
<div class="unit">分</div>
|
||||||
|
</div>
|
||||||
|
<div class="countdown-box">
|
||||||
|
<div class="num" id="cd-secs">--</div>
|
||||||
|
<div class="unit">秒</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- NCT 是什么 -->
|
||||||
|
<div class="section fade-up">
|
||||||
|
<div class="section-header">
|
||||||
|
<h2>什么是 NCT?</h2>
|
||||||
|
<p>国内最权威的青少年编程能力等级测试</p>
|
||||||
|
</div>
|
||||||
|
<div class="info-grid">
|
||||||
|
<div class="info-card">
|
||||||
|
<div class="info-icon orange">🏛️</div>
|
||||||
|
<div>
|
||||||
|
<h3>中国软件行业协会主办</h3>
|
||||||
|
<p>NCT 由<strong>中国软件行业协会</strong>盖章颁发等级证书,是国家民政部认可的首批 AAA 级行业协会,唯一代表中国计算机软件产业界的全国性一级行业组织。</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-card">
|
||||||
|
<div class="info-icon blue">🌐</div>
|
||||||
|
<div>
|
||||||
|
<h3>国内国际双认证</h3>
|
||||||
|
<p>通过<strong>教育部 CELTSC</strong> 标准符合性认证,同时通过国际教育技术协会 <strong>ISTE</strong> 认证,是国内首家获得 ISTE Seal of Alignment 的编程测评项目。</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-card">
|
||||||
|
<div class="info-icon purple">🎓</div>
|
||||||
|
<div>
|
||||||
|
<h3>联合国教科文组织合作项目</h3>
|
||||||
|
<p>2021 年成为<strong>联合国教科文组织亚非地区青少年编程教育合作项目</strong>(AAYC)指定测评服务平台,获得国际认可。</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-card">
|
||||||
|
<div class="info-icon green">👨🎓</div>
|
||||||
|
<div>
|
||||||
|
<h3>全国超过 75 万考生参与</h3>
|
||||||
|
<p>覆盖中国 34 个省级行政区、全球 25+ 国家及地区,3000+ 所学校和机构参与,新东方、编程猫、童程童美等知名品牌合作。</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 证书价值 -->
|
||||||
|
<div class="section fade-up">
|
||||||
|
<div class="section-header">
|
||||||
|
<h2>考级证书的价值</h2>
|
||||||
|
<p>不只是一张证书,更是孩子成长的里程碑</p>
|
||||||
|
</div>
|
||||||
|
<div class="value-grid">
|
||||||
|
<div class="value-card">
|
||||||
|
<div class="value-emoji">🏅</div>
|
||||||
|
<h3>权威认证</h3>
|
||||||
|
<p>中国软件行业协会盖章颁发,含金量高</p>
|
||||||
|
</div>
|
||||||
|
<div class="value-card">
|
||||||
|
<div class="value-emoji">📋</div>
|
||||||
|
<h3>科技特长</h3>
|
||||||
|
<p>可作为信息科技特长参考依据,综合素质评价加分</p>
|
||||||
|
</div>
|
||||||
|
<div class="value-card">
|
||||||
|
<div class="value-emoji">🧠</div>
|
||||||
|
<h3>能力提升</h3>
|
||||||
|
<p>锻炼逻辑思维、拆题能力与问题解决能力</p>
|
||||||
|
</div>
|
||||||
|
<div class="value-card">
|
||||||
|
<div class="value-emoji">🎯</div>
|
||||||
|
<h3>阶段目标</h3>
|
||||||
|
<p>清晰的等级体系,给孩子学习锚定方向与动力</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 考试日期 -->
|
||||||
|
<div class="section fade-up" style="padding-top: 0;">
|
||||||
|
<div class="exam-card">
|
||||||
|
<h3>NCT 考试时间</h3>
|
||||||
|
<div class="exam-date">2026 年 8 月 22 – 23 日</div>
|
||||||
|
<p class="exam-note">具体考试场次 6 月上旬公布</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 集训时间安排 -->
|
||||||
|
<div class="section fade-up">
|
||||||
|
<div class="section-header">
|
||||||
|
<h2>集训时间安排</h2>
|
||||||
|
<p>三期可选,内容一致,根据时间灵活选择</p>
|
||||||
|
</div>
|
||||||
|
<div class="period-section">
|
||||||
|
<span class="period-badge orange">第一期</span>
|
||||||
|
<h3>7 月集训 · 第一期</h3>
|
||||||
|
<p class="period-info">2 周 · 周一至周六 · 每周日休息 · 每节课 2.5 小时</p>
|
||||||
|
<div style="font-size:18px;font-weight:700;color:#1A1A2E;margin:12px 0 8px">7 月 13 日 – 7 月 25 日</div>
|
||||||
|
<div class="reg-input-row">
|
||||||
|
<input class="reg-input" type="text" placeholder="填写孩子姓名" maxlength="20">
|
||||||
|
<button class="reg-btn orange" onclick="register(this, '第一期:7月13日–7月25日')">我要报名</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="period-section">
|
||||||
|
<span class="period-badge blue">第二期</span>
|
||||||
|
<h3>7–8 月集训 · 第二期</h3>
|
||||||
|
<p class="period-info">2 周 · 周一至周六 · 每周日休息 · 每节课 2.5 小时</p>
|
||||||
|
<div style="font-size:18px;font-weight:700;color:#1A1A2E;margin:12px 0 8px">7 月 27 日 – 8 月 8 日</div>
|
||||||
|
<div class="reg-input-row">
|
||||||
|
<input class="reg-input" type="text" placeholder="填写孩子姓名" maxlength="20">
|
||||||
|
<button class="reg-btn blue" onclick="register(this, '第二期:7月27日–8月8日')">我要报名</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="period-section">
|
||||||
|
<span class="period-badge orange">第三期</span>
|
||||||
|
<h3>8 月集训 · 第三期</h3>
|
||||||
|
<p class="period-info">2 周 · 周一至周六 · 每周日休息 · 每节课 2.5 小时</p>
|
||||||
|
<div style="font-size:18px;font-weight:700;color:#1A1A2E;margin:12px 0 8px">8 月 10 日 – 8 月 22 日</div>
|
||||||
|
<div class="reg-input-row">
|
||||||
|
<input class="reg-input" type="text" placeholder="填写孩子姓名" maxlength="20">
|
||||||
|
<button class="reg-btn orange" onclick="register(this, '第三期:8月10日–8月22日')">我要报名</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="highlight-strip">
|
||||||
|
<p>💡 三期集训内容完全一致,家长可根据孩子时间安排自由选择。每周日休息。<br>填写孩子姓名后点击<strong>「我要报名」</strong>即可提交,老师会主动联系您确认。</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 报名门槛 -->
|
||||||
|
<div class="section fade-up" style="padding-top: 0;">
|
||||||
|
<div class="section-header">
|
||||||
|
<h2>报名条件</h2>
|
||||||
|
</div>
|
||||||
|
<div class="info-grid">
|
||||||
|
<div class="info-card">
|
||||||
|
<div class="info-icon orange">📐</div>
|
||||||
|
<div>
|
||||||
|
<h3>K2 阶段学员</h3>
|
||||||
|
<p>报考 <strong>NCT 一级</strong>,夯实基础,建立信心</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-card">
|
||||||
|
<div class="info-icon blue">🔬</div>
|
||||||
|
<div>
|
||||||
|
<h3>K4 阶段学员</h3>
|
||||||
|
<p>报考 <strong>NCT 二级</strong>,挑战进阶,证明实力</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="highlight-strip" style="margin-top: 16px;">
|
||||||
|
<p>📌 未考过一级的学生需先考一级,不可跳级。所有在读 Kitten 学员均可报名。</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 费用说明 -->
|
||||||
|
<div class="section fade-up" style="padding-top: 0;">
|
||||||
|
<div class="section-header">
|
||||||
|
<h2>费用说明</h2>
|
||||||
|
</div>
|
||||||
|
<div class="fee-table">
|
||||||
|
<div class="fee-row">
|
||||||
|
<div class="fee-label">📝 NCT 官方报名费</div>
|
||||||
|
<div class="fee-value">300 元 / 人</div>
|
||||||
|
</div>
|
||||||
|
<div class="fee-row">
|
||||||
|
<div class="fee-label">🎒 集训费用</div>
|
||||||
|
<div class="fee-value">正常课时扣费</div>
|
||||||
|
</div>
|
||||||
|
<div class="fee-row">
|
||||||
|
<div class="fee-label">⏱️ 课时规则</div>
|
||||||
|
<div class="fee-value">每节扣 2 课时</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="highlight-strip" style="margin-top: 16px;">
|
||||||
|
<p>🏆 <strong>奖学金激励:</strong>成绩优异的学员可获得校区颁发的奖学金,既是荣誉也是激励!</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 常见问题 -->
|
||||||
|
<div class="section fade-up" style="padding-top: 0;">
|
||||||
|
<div class="section-header">
|
||||||
|
<h2>常见问题</h2>
|
||||||
|
</div>
|
||||||
|
<div class="faq-item">
|
||||||
|
<div class="faq-q">
|
||||||
|
<span>孩子零基础可以参加吗?</span>
|
||||||
|
<span class="arrow">▼</span>
|
||||||
|
</div>
|
||||||
|
<div class="faq-a">
|
||||||
|
集训面向在读 Kitten 学员。K2 阶段报一级、K4 阶段报二级,难度与孩子当前学习进度匹配,老师全程指导,可以放心参加。
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="faq-item">
|
||||||
|
<div class="faq-q">
|
||||||
|
<span>考试形式是什么样的?</span>
|
||||||
|
<span class="arrow">▼</span>
|
||||||
|
</div>
|
||||||
|
<div class="faq-a">
|
||||||
|
NCT 采用线上考试形式,支持居家和线下考点在线考试。AI 智能监考系统保证考试公正、专业,足不出户即可参加。
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="faq-item">
|
||||||
|
<div class="faq-q">
|
||||||
|
<span>集训会不会影响暑假出行?</span>
|
||||||
|
<span class="arrow">▼</span>
|
||||||
|
</div>
|
||||||
|
<div class="faq-a">
|
||||||
|
集训每期内容一样,选一期参加即可。每周日休息,三期覆盖整个暑假,合理安排完全不会影响家庭出行计划。
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="faq-item">
|
||||||
|
<div class="faq-q">
|
||||||
|
<span>通过率怎么样?</span>
|
||||||
|
<span class="arrow">▼</span>
|
||||||
|
</div>
|
||||||
|
<div class="faq-a">
|
||||||
|
参加集训的学员通过率基本达到 <strong>100%</strong>。集训内容紧扣考级知识点,系统刷题 + 模拟实操,帮助孩子充分准备。
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="faq-item">
|
||||||
|
<div class="faq-q">
|
||||||
|
<span>证书长什么样?有含金量吗?</span>
|
||||||
|
<span class="arrow">▼</span>
|
||||||
|
</div>
|
||||||
|
<div class="faq-a">
|
||||||
|
新版证书采用 PU 皮革材质、烫金凹印技术、圆角裁剪工艺,精美大气。由中国软件行业协会盖章颁发,通过顺丰快递寄送。该证书通过教育部 CELTSC 标准认证和国际 ISTE 认证,是目前国内外双认证的权威编程等级证书。
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 报名入口 -->
|
||||||
|
<div class="cta-section">
|
||||||
|
<div class="cta-box">
|
||||||
|
<h2>🚀 暑假集训,助孩子高效拿证</h2>
|
||||||
|
<p>小班精品教学 · 系统备考方案 · 权威等级证书<br>名额有限,每班仅限 8 人,先到先得!</p>
|
||||||
|
<a href="#" class="cta-btn" onclick="showToast('请往上滑,填写孩子姓名\n并点击「我要报名」提交', 3000); return false;">立即咨询报名</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 底部 -->
|
||||||
|
<div class="footer">
|
||||||
|
<p>穹狼科创 · 北滘校区 · 2026 年暑假</p>
|
||||||
|
<p style="margin-top: 6px;">NCT 青少年编程能力等级测试</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Toast 提示 -->
|
||||||
|
<div class="toast" id="toast"></div>
|
||||||
|
|
||||||
|
<!-- 报名确认弹窗 -->
|
||||||
|
<div class="modal-overlay" id="regModal">
|
||||||
|
<div class="modal">
|
||||||
|
<h3>确认报名</h3>
|
||||||
|
<div class="modal-period" id="modalPeriod"></div>
|
||||||
|
<input class="modal-input" id="modalName" type="text" placeholder="请输入孩子姓名" maxlength="20" style="margin-bottom:10px">
|
||||||
|
<select class="modal-select" id="modalTeacher" onchange="this.classList.toggle('has-value', this.value)">
|
||||||
|
<option value="" disabled selected>选择负责老师</option>
|
||||||
|
<option value="东东老师">东东老师</option>
|
||||||
|
<option value="橙子老师">橙子老师</option>
|
||||||
|
<option value="福福老师">福福老师</option>
|
||||||
|
<option value="坦克老师">坦克老师</option>
|
||||||
|
<option value="菠萝老师">菠萝老师</option>
|
||||||
|
</select>
|
||||||
|
<button class="modal-submit" id="modalSubmit" onclick="submitReg()">确认提交</button>
|
||||||
|
<br>
|
||||||
|
<button class="modal-cancel" onclick="closeModal()">取消</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="js/app.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
126
public/beijiao/js/app.js
Normal file
126
public/beijiao/js/app.js
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
// ============ 倒计时 ============
|
||||||
|
function updateCountdown() {
|
||||||
|
var examDate = new Date('2026-08-22T09:00:00');
|
||||||
|
var now = new Date();
|
||||||
|
var diff = examDate - now;
|
||||||
|
if (diff <= 0) {
|
||||||
|
document.getElementById('cd-days').textContent = '0';
|
||||||
|
document.getElementById('cd-hours').textContent = '0';
|
||||||
|
document.getElementById('cd-mins').textContent = '0';
|
||||||
|
document.getElementById('cd-secs').textContent = '0';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
document.getElementById('cd-days').textContent = Math.floor(diff / 864e5);
|
||||||
|
document.getElementById('cd-hours').textContent = Math.floor((diff % 864e5) / 36e5);
|
||||||
|
document.getElementById('cd-mins').textContent = Math.floor((diff % 36e5) / 6e4);
|
||||||
|
document.getElementById('cd-secs').textContent = Math.floor((diff % 6e4) / 1e3);
|
||||||
|
}
|
||||||
|
updateCountdown();
|
||||||
|
setInterval(updateCountdown, 1000);
|
||||||
|
|
||||||
|
// ============ FAQ 折叠 ============
|
||||||
|
document.querySelectorAll('.faq-q').forEach(function(q) {
|
||||||
|
q.addEventListener('click', function() {
|
||||||
|
this.parentElement.classList.toggle('open');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ============ 滚动渐入 ============
|
||||||
|
var observer = new IntersectionObserver(function(entries) {
|
||||||
|
entries.forEach(function(entry) {
|
||||||
|
if (entry.isIntersecting) entry.target.classList.add('visible');
|
||||||
|
});
|
||||||
|
}, { threshold: 0.1 });
|
||||||
|
document.querySelectorAll('.fade-up').forEach(function(el) {
|
||||||
|
observer.observe(el);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ============ Toast 提示 ============
|
||||||
|
function showToast(msg, duration) {
|
||||||
|
duration = duration || 2500;
|
||||||
|
var el = document.getElementById('toast');
|
||||||
|
el.textContent = msg;
|
||||||
|
el.classList.add('show');
|
||||||
|
clearTimeout(el._timer);
|
||||||
|
el._timer = setTimeout(function() {
|
||||||
|
el.classList.remove('show');
|
||||||
|
}, duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ 报名流程 ============
|
||||||
|
var pendingPeriod = '';
|
||||||
|
|
||||||
|
function register(btn, period) {
|
||||||
|
var row = btn.closest('.reg-input-row');
|
||||||
|
var name = row.querySelector('.reg-input').value.trim();
|
||||||
|
if (!name) {
|
||||||
|
row.querySelector('.reg-input').focus();
|
||||||
|
row.querySelector('.reg-input').style.borderColor = '#FF4444';
|
||||||
|
setTimeout(function() {
|
||||||
|
row.querySelector('.reg-input').style.borderColor = '';
|
||||||
|
}, 1500);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pendingPeriod = period;
|
||||||
|
document.getElementById('modalPeriod').textContent = period;
|
||||||
|
document.getElementById('modalName').value = name;
|
||||||
|
document.getElementById('regModal').classList.add('show');
|
||||||
|
document.getElementById('modalName').focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeModal() {
|
||||||
|
document.getElementById('regModal').classList.remove('show');
|
||||||
|
pendingPeriod = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('regModal').addEventListener('click', function(e) {
|
||||||
|
if (e.target === this) closeModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
function submitReg() {
|
||||||
|
var name = document.getElementById('modalName').value.trim();
|
||||||
|
var teacher = document.getElementById('modalTeacher').value;
|
||||||
|
var btn = document.getElementById('modalSubmit');
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
document.getElementById('modalName').focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!teacher) {
|
||||||
|
document.getElementById('modalTeacher').style.borderColor = '#FF4444';
|
||||||
|
setTimeout(function() {
|
||||||
|
document.getElementById('modalTeacher').style.borderColor = '';
|
||||||
|
}, 1500);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.textContent = '提交中...';
|
||||||
|
|
||||||
|
fetch('api/register', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||||
|
body: 'name=' + encodeURIComponent(name) +
|
||||||
|
'&period=' + encodeURIComponent(pendingPeriod) +
|
||||||
|
'&teacher=' + encodeURIComponent(teacher)
|
||||||
|
})
|
||||||
|
.then(function(res) { return res.json(); })
|
||||||
|
.then(function(data) {
|
||||||
|
closeModal();
|
||||||
|
if (data.ok) {
|
||||||
|
showToast('✅ 报名成功!\n\n' + name + '\n' + pendingPeriod + '\n' + teacher, 3000);
|
||||||
|
document.querySelectorAll('.reg-input').forEach(function(el) { el.value = ''; });
|
||||||
|
document.getElementById('modalTeacher').selectedIndex = 0;
|
||||||
|
document.getElementById('modalTeacher').classList.remove('has-value');
|
||||||
|
} else {
|
||||||
|
showToast('❌ ' + (data.msg || '提交失败,请稍后再试'));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function() {
|
||||||
|
showToast('❌ 网络错误,请稍后再试');
|
||||||
|
})
|
||||||
|
.finally(function() {
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.textContent = '确认提交';
|
||||||
|
});
|
||||||
|
}
|
||||||
529
public/chencun/admin.html
Normal file
529
public/chencun/admin.html
Normal file
@@ -0,0 +1,529 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>报名管理后台 | 陈村校区</title>
|
||||||
|
<style>
|
||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
:root {
|
||||||
|
--orange: #FF6B35;
|
||||||
|
--orange-dark: #E85A24;
|
||||||
|
--blue: #4A6CF7;
|
||||||
|
--dark: #1A1A2E;
|
||||||
|
--text: #333;
|
||||||
|
--text-light: #666;
|
||||||
|
--bg: #F5F7FA;
|
||||||
|
--white: #fff;
|
||||||
|
--shadow: 0 4px 24px rgba(0,0,0,0.08);
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--text);
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====== Header ====== */
|
||||||
|
.header {
|
||||||
|
background: linear-gradient(135deg, var(--dark) 0%, #2D2D5E 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 24px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.header h1 { font-size: 22px; font-weight: 700; }
|
||||||
|
.header p { font-size: 13px; opacity: 0.6; margin-top: 4px; }
|
||||||
|
|
||||||
|
/* ====== Login ====== */
|
||||||
|
.login-section {
|
||||||
|
max-width: 400px;
|
||||||
|
margin: 60px auto;
|
||||||
|
background: var(--white);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 40px 32px;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.login-section h2 { font-size: 20px; color: var(--dark); margin-bottom: 8px; }
|
||||||
|
.login-section p { font-size: 14px; color: var(--text-light); margin-bottom: 24px; }
|
||||||
|
.login-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border: 2px solid #e8e8e8;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 16px;
|
||||||
|
outline: none;
|
||||||
|
transition: border-color 0.3s;
|
||||||
|
text-align: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.login-input:focus { border-color: var(--orange); }
|
||||||
|
.login-btn {
|
||||||
|
width: 100%;
|
||||||
|
padding: 14px;
|
||||||
|
background: var(--orange);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top: 16px;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
.login-btn:hover { background: var(--orange-dark); }
|
||||||
|
.login-error {
|
||||||
|
color: #FF4444;
|
||||||
|
font-size: 13px;
|
||||||
|
margin-top: 10px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====== Dashboard ====== */
|
||||||
|
.dashboard { display: none; }
|
||||||
|
.dashboard.active { display: block; }
|
||||||
|
|
||||||
|
/* Stats Bar */
|
||||||
|
.stats-bar {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 24px;
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.stat-card {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 140px;
|
||||||
|
background: var(--white);
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.stat-card .stat-num {
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--orange);
|
||||||
|
}
|
||||||
|
.stat-card .stat-label {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--text-light);
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Controls */
|
||||||
|
.controls {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 24px;
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.controls select, .controls input {
|
||||||
|
padding: 10px 14px;
|
||||||
|
border: 2px solid #e8e8e8;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
outline: none;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
.controls select:focus, .controls input:focus {
|
||||||
|
border-color: var(--orange);
|
||||||
|
}
|
||||||
|
.ctrl-btn {
|
||||||
|
padding: 10px 20px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.2s;
|
||||||
|
}
|
||||||
|
.ctrl-btn:hover { transform: scale(1.03); }
|
||||||
|
.ctrl-btn.orange { background: var(--orange); color: white; }
|
||||||
|
.ctrl-btn.blue { background: var(--blue); color: white; }
|
||||||
|
.ctrl-btn.outline { background: transparent; border: 2px solid #ddd; color: var(--text-light); }
|
||||||
|
.ctrl-btn.outline:hover { border-color: var(--orange); color: var(--orange); }
|
||||||
|
.ctrl-btn.danger { background: #FF4444; color: white; }
|
||||||
|
.spacer { flex: 1; }
|
||||||
|
|
||||||
|
/* Table */
|
||||||
|
.table-section {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 20px auto;
|
||||||
|
padding: 0 24px 40px;
|
||||||
|
}
|
||||||
|
.table-wrap {
|
||||||
|
background: var(--white);
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
background: #f8f9fa;
|
||||||
|
padding: 14px 16px;
|
||||||
|
text-align: left;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-light);
|
||||||
|
border-bottom: 2px solid #eee;
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
padding: 14px 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
tr:last-child td { border-bottom: none; }
|
||||||
|
tr:hover td { background: #FAFAFA; }
|
||||||
|
.badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 3px 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.badge-orange { background: #FFF0E8; color: var(--orange); }
|
||||||
|
.badge-blue { background: #EEF2FF; color: var(--blue); }
|
||||||
|
.del-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #FF4444;
|
||||||
|
font-size: 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0.6;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
}
|
||||||
|
.del-btn:hover { opacity: 1; }
|
||||||
|
|
||||||
|
/* Empty State */
|
||||||
|
.empty {
|
||||||
|
text-align: center;
|
||||||
|
padding: 48px 20px;
|
||||||
|
color: #bbb;
|
||||||
|
}
|
||||||
|
.empty-icon { font-size: 48px; margin-bottom: 12px; }
|
||||||
|
.empty p { font-size: 15px; }
|
||||||
|
|
||||||
|
/* Confirm Modal */
|
||||||
|
.modal-overlay {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 0; left: 0; right: 0; bottom: 0;
|
||||||
|
background: rgba(0,0,0,0.4);
|
||||||
|
z-index: 1000;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.modal-overlay.show { display: flex; }
|
||||||
|
.modal {
|
||||||
|
background: white;
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 32px;
|
||||||
|
width: 90%;
|
||||||
|
max-width: 360px;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: 0 20px 60px rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
.modal h3 { font-size: 18px; color: var(--dark); margin-bottom: 8px; }
|
||||||
|
.modal p { font-size: 14px; color: var(--text-light); margin-bottom: 20px; }
|
||||||
|
.modal-btns { display: flex; gap: 12px; }
|
||||||
|
.modal-btns button {
|
||||||
|
flex: 1;
|
||||||
|
padding: 12px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.modal-btns .cancel-btn { background: #f0f0f0; color: var(--text-light); }
|
||||||
|
.modal-btns .confirm-btn { background: #FF4444; color: white; }
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.stats-bar { padding: 16px; gap: 10px; }
|
||||||
|
.stat-card { min-width: 100px; padding: 14px; }
|
||||||
|
.stat-card .stat-num { font-size: 24px; }
|
||||||
|
.controls { padding: 0 16px; }
|
||||||
|
.table-section { padding: 0 16px 24px; }
|
||||||
|
th, td { padding: 10px 12px; font-size: 13px; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="header">
|
||||||
|
<h1>📋 报名管理后台</h1>
|
||||||
|
<p>陈村校区 · NCT 集训报名数据管理</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Login -->
|
||||||
|
<div class="login-section" id="loginSection">
|
||||||
|
<h2>🔐 管理员登录</h2>
|
||||||
|
<p>请输入管理员密码查看报名数据</p>
|
||||||
|
<input class="login-input" type="password" id="adminPassword" placeholder="输入管理员密码" onkeydown="if(event.key==='Enter') doLogin()">
|
||||||
|
<button class="login-btn" onclick="doLogin()">登 录</button>
|
||||||
|
<div class="login-error" id="loginError">密码错误,请重试</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Dashboard -->
|
||||||
|
<div class="dashboard" id="dashboard">
|
||||||
|
|
||||||
|
<!-- Stats -->
|
||||||
|
<div class="stats-bar" id="statsBar">
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-num" id="statTotal">0</div>
|
||||||
|
<div class="stat-label">总报名人数</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-num" id="statP1">0</div>
|
||||||
|
<div class="stat-label">第一期</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-num" id="statP2">0</div>
|
||||||
|
<div class="stat-label">第二期</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-num" id="statP3">0</div>
|
||||||
|
<div class="stat-label">第三期</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Controls -->
|
||||||
|
<div class="controls">
|
||||||
|
<select id="filterPeriod" onchange="loadData()">
|
||||||
|
<option value="">全部期数</option>
|
||||||
|
<option value="第一期">第一期</option>
|
||||||
|
<option value="第二期">第二期</option>
|
||||||
|
<option value="第三期">第三期</option>
|
||||||
|
</select>
|
||||||
|
<select id="filterTeacher" onchange="loadData()">
|
||||||
|
<option value="">全部老师</option>
|
||||||
|
</select>
|
||||||
|
<input type="text" id="filterName" placeholder="搜索姓名" oninput="loadData()">
|
||||||
|
<div class="spacer"></div>
|
||||||
|
<button class="ctrl-btn outline" onclick="loadData()">🔄 刷新</button>
|
||||||
|
<button class="ctrl-btn blue" onclick="exportCSV()">📥 导出 CSV</button>
|
||||||
|
<button class="ctrl-btn outline" onclick="doLogout()">退出登录</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Table -->
|
||||||
|
<div class="table-section">
|
||||||
|
<div class="table-wrap">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>序号</th>
|
||||||
|
<th>姓名</th>
|
||||||
|
<th>期数</th>
|
||||||
|
<th>老师</th>
|
||||||
|
<th>报名时间</th>
|
||||||
|
<th>操作</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="tableBody">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="empty" id="emptyState" style="display:none">
|
||||||
|
<div class="empty-icon">📭</div>
|
||||||
|
<p>暂无报名记录</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Delete Confirm Modal -->
|
||||||
|
<div class="modal-overlay" id="deleteModal">
|
||||||
|
<div class="modal">
|
||||||
|
<h3>确认删除</h3>
|
||||||
|
<p id="deleteMsg">确定要删除这条报名记录吗?此操作不可撤销。</p>
|
||||||
|
<div class="modal-btns">
|
||||||
|
<button class="cancel-btn" onclick="closeDeleteModal()">取消</button>
|
||||||
|
<button class="confirm-btn" onclick="confirmDelete()">确认删除</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var adminPwd = '';
|
||||||
|
var allData = [];
|
||||||
|
var deleteId = null;
|
||||||
|
|
||||||
|
function doLogin() {
|
||||||
|
adminPwd = document.getElementById('adminPassword').value;
|
||||||
|
if (!adminPwd) return;
|
||||||
|
|
||||||
|
fetch('api/stats?password=' + encodeURIComponent(adminPwd))
|
||||||
|
.then(function(r) { return r.json(); })
|
||||||
|
.then(function(data) {
|
||||||
|
if (data.ok) {
|
||||||
|
document.getElementById('loginSection').style.display = 'none';
|
||||||
|
document.getElementById('dashboard').classList.add('active');
|
||||||
|
updateStats(data.data);
|
||||||
|
loadData();
|
||||||
|
} else {
|
||||||
|
document.getElementById('loginError').style.display = 'block';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function() {
|
||||||
|
document.getElementById('loginError').style.display = 'block';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function doLogout() {
|
||||||
|
adminPwd = '';
|
||||||
|
document.getElementById('loginSection').style.display = '';
|
||||||
|
document.getElementById('dashboard').classList.remove('active');
|
||||||
|
document.getElementById('adminPassword').value = '';
|
||||||
|
document.getElementById('loginError').style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateStats(stats) {
|
||||||
|
document.getElementById('statTotal').textContent = stats.total || 0;
|
||||||
|
var periods = stats.byPeriod || {};
|
||||||
|
document.getElementById('statP1').textContent = periods['第一期:7月13日–7月25日'] || 0;
|
||||||
|
document.getElementById('statP2').textContent = periods['第二期:7月27日–8月8日'] || 0;
|
||||||
|
document.getElementById('statP3').textContent = periods['第三期:8月10日–8月22日'] || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadData() {
|
||||||
|
fetch('api/registrations?password=' + encodeURIComponent(adminPwd))
|
||||||
|
.then(function(r) { return r.json(); })
|
||||||
|
.then(function(data) {
|
||||||
|
if (!data.ok) return;
|
||||||
|
allData = data.data;
|
||||||
|
applyFilters();
|
||||||
|
updateTeacherFilter(allData);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTeacherFilter(records) {
|
||||||
|
var sel = document.getElementById('filterTeacher');
|
||||||
|
var current = sel.value;
|
||||||
|
var teachers = {};
|
||||||
|
records.forEach(function(r) { teachers[r.teacher] = true; });
|
||||||
|
sel.innerHTML = '<option value="">全部老师</option>';
|
||||||
|
Object.keys(teachers).sort().forEach(function(t) {
|
||||||
|
var opt = document.createElement('option');
|
||||||
|
opt.value = t;
|
||||||
|
opt.textContent = t;
|
||||||
|
if (t === current) opt.selected = true;
|
||||||
|
sel.appendChild(opt);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyFilters() {
|
||||||
|
var periodFilter = document.getElementById('filterPeriod').value;
|
||||||
|
var teacherFilter = document.getElementById('filterTeacher').value;
|
||||||
|
var nameFilter = document.getElementById('filterName').value.trim().toLowerCase();
|
||||||
|
|
||||||
|
var filtered = allData.filter(function(r) {
|
||||||
|
if (periodFilter && !r.period.includes(periodFilter)) return false;
|
||||||
|
if (teacherFilter && r.teacher !== teacherFilter) return false;
|
||||||
|
if (nameFilter && !r.name.toLowerCase().includes(nameFilter)) return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
renderTable(filtered);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderTable(records) {
|
||||||
|
var tbody = document.getElementById('tableBody');
|
||||||
|
var empty = document.getElementById('emptyState');
|
||||||
|
|
||||||
|
if (records.length === 0) {
|
||||||
|
tbody.innerHTML = '';
|
||||||
|
empty.style.display = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
empty.style.display = 'none';
|
||||||
|
var html = '';
|
||||||
|
records.forEach(function(r, i) {
|
||||||
|
var badgeClass = r.period.includes('第二期') ? 'badge-blue' : 'badge-orange';
|
||||||
|
var periodShort = r.period;
|
||||||
|
html += '<tr>' +
|
||||||
|
'<td>' + (i + 1) + '</td>' +
|
||||||
|
'<td><strong>' + escHtml(r.name) + '</strong></td>' +
|
||||||
|
'<td><span class="badge ' + badgeClass + '">' + escHtml(periodShort) + '</span></td>' +
|
||||||
|
'<td>' + escHtml(r.teacher) + '</td>' +
|
||||||
|
'<td>' + escHtml(r.createdAt || '-') + '</td>' +
|
||||||
|
'<td><button class="del-btn" onclick="showDeleteModal(' + r.id + ', \'' + escHtml(r.name) + '\')">删除</button></td>' +
|
||||||
|
'</tr>';
|
||||||
|
});
|
||||||
|
tbody.innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function escHtml(str) {
|
||||||
|
var d = document.createElement('div');
|
||||||
|
d.textContent = str;
|
||||||
|
return d.innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
function showDeleteModal(id, name) {
|
||||||
|
deleteId = id;
|
||||||
|
document.getElementById('deleteMsg').textContent = '确定要删除「' + name + '」的报名记录吗?此操作不可撤销。';
|
||||||
|
document.getElementById('deleteModal').classList.add('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeDeleteModal() {
|
||||||
|
document.getElementById('deleteModal').classList.remove('show');
|
||||||
|
deleteId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirmDelete() {
|
||||||
|
if (!deleteId) return;
|
||||||
|
fetch('api/registrations/' + deleteId + '?password=' + encodeURIComponent(adminPwd), {
|
||||||
|
method: 'DELETE'
|
||||||
|
})
|
||||||
|
.then(function(r) { return r.json(); })
|
||||||
|
.then(function(data) {
|
||||||
|
closeDeleteModal();
|
||||||
|
if (data.ok) {
|
||||||
|
loadData();
|
||||||
|
fetch('api/stats?password=' + encodeURIComponent(adminPwd))
|
||||||
|
.then(function(r) { return r.json(); })
|
||||||
|
.then(function(s) { if (s.ok) updateStats(s.data); });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function exportCSV() {
|
||||||
|
var periodFilter = document.getElementById('filterPeriod').value;
|
||||||
|
var teacherFilter = document.getElementById('filterTeacher').value;
|
||||||
|
var nameFilter = document.getElementById('filterName').value.trim().toLowerCase();
|
||||||
|
|
||||||
|
var filtered = allData.filter(function(r) {
|
||||||
|
if (periodFilter && !r.period.includes(periodFilter)) return false;
|
||||||
|
if (teacherFilter && r.teacher !== teacherFilter) return false;
|
||||||
|
if (nameFilter && !r.name.toLowerCase().includes(nameFilter)) return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
var csv = '序号,姓名,期数,老师,报名时间\n';
|
||||||
|
filtered.forEach(function(r, i) {
|
||||||
|
csv += (i + 1) + ',' + r.name + ',' + r.period + ',' + r.teacher + ',' + (r.createdAt || '') + '\n';
|
||||||
|
});
|
||||||
|
|
||||||
|
var blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
||||||
|
var url = URL.createObjectURL(blob);
|
||||||
|
var a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = '陈村校区_报名数据_' + new Date().toISOString().slice(0, 10) + '.csv';
|
||||||
|
a.click();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('deleteModal').addEventListener('click', function(e) {
|
||||||
|
if (e.target === this) closeDeleteModal();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
704
public/chencun/css/style.css
Normal file
704
public/chencun/css/style.css
Normal file
@@ -0,0 +1,704 @@
|
|||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--orange: #FF6B35;
|
||||||
|
--orange-light: #FF8F5E;
|
||||||
|
--orange-dark: #E85A24;
|
||||||
|
--blue: #4A6CF7;
|
||||||
|
--blue-light: #6B8CFF;
|
||||||
|
--purple: #7C5CFC;
|
||||||
|
--dark: #1A1A2E;
|
||||||
|
--text: #333;
|
||||||
|
--text-light: #666;
|
||||||
|
--bg: #F5F7FA;
|
||||||
|
--white: #fff;
|
||||||
|
--shadow: 0 4px 24px rgba(0,0,0,0.08);
|
||||||
|
--shadow-lg: 0 12px 40px rgba(0,0,0,0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--text);
|
||||||
|
line-height: 1.6;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====== Hero ====== */
|
||||||
|
.hero {
|
||||||
|
background: linear-gradient(135deg, #FF6B35 0%, #FF8F5E 40%, #FFB347 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 60px 24px 80px;
|
||||||
|
text-align: center;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -50%;
|
||||||
|
left: -50%;
|
||||||
|
width: 200%;
|
||||||
|
height: 200%;
|
||||||
|
background: radial-gradient(circle, rgba(255,255,255,0.08) 1px, transparent 1px);
|
||||||
|
background-size: 30px 30px;
|
||||||
|
animation: heroPattern 20s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes heroPattern {
|
||||||
|
from { transform: translate(0, 0); }
|
||||||
|
to { transform: translate(30px, 30px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-content {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
max-width: 700px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-badge {
|
||||||
|
display: inline-block;
|
||||||
|
background: rgba(255,255,255,0.2);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
padding: 6px 20px;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero h1 {
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: 800;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
text-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero .subtitle {
|
||||||
|
font-size: 18px;
|
||||||
|
opacity: 0.95;
|
||||||
|
margin-bottom: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-stats {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 32px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-stat {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-stat .num {
|
||||||
|
font-size: 36px;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-stat .label {
|
||||||
|
font-size: 13px;
|
||||||
|
opacity: 0.85;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====== Sections ====== */
|
||||||
|
.section {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 48px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-header h2 {
|
||||||
|
font-size: 26px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--dark);
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-header p {
|
||||||
|
color: var(--text-light);
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====== Info Cards ====== */
|
||||||
|
.info-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-card {
|
||||||
|
background: var(--white);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 24px;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: flex-start;
|
||||||
|
transition: transform 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-card:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-icon {
|
||||||
|
width: 52px;
|
||||||
|
height: 52px;
|
||||||
|
border-radius: 14px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 26px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-icon.orange { background: #FFF0E8; }
|
||||||
|
.info-icon.blue { background: #EEF2FF; }
|
||||||
|
.info-icon.purple { background: #F3EEFF; }
|
||||||
|
.info-icon.green { background: #E8F9EF; }
|
||||||
|
|
||||||
|
.info-card h3 {
|
||||||
|
font-size: 17px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--dark);
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-card p {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--text-light);
|
||||||
|
line-height: 1.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====== Timeline ====== */
|
||||||
|
.period-section {
|
||||||
|
background: var(--white);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 24px;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.period-badge {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 3px 12px;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.period-badge.orange { background: #FFF0E8; color: var(--orange); }
|
||||||
|
.period-badge.blue { background: #EEF2FF; color: var(--blue); }
|
||||||
|
|
||||||
|
.period-section h3 {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--dark);
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.period-info {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--text-light);
|
||||||
|
margin-bottom: 16px;
|
||||||
|
line-height: 1.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====== Registration Input ====== */
|
||||||
|
.reg-input-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reg-input {
|
||||||
|
flex: 1;
|
||||||
|
padding: 10px 14px;
|
||||||
|
border: 2px solid #e8e8e8;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
outline: none;
|
||||||
|
transition: border-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reg-input:focus {
|
||||||
|
border-color: var(--orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
.reg-input::placeholder {
|
||||||
|
color: #bbb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reg-btn {
|
||||||
|
padding: 10px 20px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
white-space: nowrap;
|
||||||
|
transition: transform 0.2s, opacity 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reg-btn:hover { transform: scale(1.03); }
|
||||||
|
.reg-btn:active { transform: scale(0.97); }
|
||||||
|
.reg-btn.orange { background: var(--orange); }
|
||||||
|
.reg-btn.blue { background: var(--blue); }
|
||||||
|
|
||||||
|
/* ====== Exam Info ====== */
|
||||||
|
.exam-card {
|
||||||
|
background: linear-gradient(135deg, var(--dark) 0%, #2D2D5E 100%);
|
||||||
|
color: white;
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 32px 28px;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exam-card::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -40%;
|
||||||
|
right: -20%;
|
||||||
|
width: 200px;
|
||||||
|
height: 200px;
|
||||||
|
background: radial-gradient(circle, rgba(255,107,53,0.15), transparent);
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exam-card h3 {
|
||||||
|
font-size: 15px;
|
||||||
|
opacity: 0.7;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exam-date {
|
||||||
|
font-size: 38px;
|
||||||
|
font-weight: 800;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
background: linear-gradient(135deg, #FF6B35, #FFB347);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exam-note {
|
||||||
|
font-size: 14px;
|
||||||
|
opacity: 0.6;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====== Value Cards ====== */
|
||||||
|
.value-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-card {
|
||||||
|
background: var(--white);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 22px 18px;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
transition: transform 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-card:hover {
|
||||||
|
transform: translateY(-3px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-emoji {
|
||||||
|
font-size: 36px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-card h3 {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--dark);
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-card p {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--text-light);
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====== Highlights ====== */
|
||||||
|
.highlight-strip {
|
||||||
|
background: linear-gradient(135deg, #FFF8E1, #FFF3E0);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 20px 24px;
|
||||||
|
border-left: 5px solid #FFB347;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight-strip p {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #8B6914;
|
||||||
|
line-height: 1.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight-strip strong {
|
||||||
|
color: #E85A24;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====== Fee Section ====== */
|
||||||
|
.fee-table {
|
||||||
|
width: 100%;
|
||||||
|
background: var(--white);
|
||||||
|
border-radius: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fee-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 18px 24px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fee-row:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fee-label {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 15px;
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fee-value {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====== FAQ ====== */
|
||||||
|
.faq-item {
|
||||||
|
background: var(--white);
|
||||||
|
border-radius: 14px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
box-shadow: 0 2px 12px rgba(0,0,0,0.05);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faq-q {
|
||||||
|
padding: 18px 24px;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--dark);
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
user-select: none;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faq-q:hover {
|
||||||
|
background: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faq-q .arrow {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
transition: transform 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faq-item.open .faq-q .arrow {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.faq-a {
|
||||||
|
display: none;
|
||||||
|
padding: 0 24px 18px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--text-light);
|
||||||
|
line-height: 1.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faq-item.open .faq-a {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====== CTA ====== */
|
||||||
|
.cta-section {
|
||||||
|
text-align: center;
|
||||||
|
padding: 48px 20px 24px;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-box {
|
||||||
|
background: linear-gradient(135deg, #FF6B35, #FF8F5E, #FFB347);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 40px 28px;
|
||||||
|
color: white;
|
||||||
|
box-shadow: 0 8px 32px rgba(255,107,53,0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-box h2 {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-box p {
|
||||||
|
font-size: 15px;
|
||||||
|
opacity: 0.95;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
line-height: 1.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-btn {
|
||||||
|
display: inline-block;
|
||||||
|
background: white;
|
||||||
|
color: var(--orange);
|
||||||
|
padding: 14px 40px;
|
||||||
|
border-radius: 30px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-decoration: none;
|
||||||
|
box-shadow: 0 4px 16px rgba(0,0,0,0.15);
|
||||||
|
transition: transform 0.3s, box-shadow 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-btn:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 20px rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====== Countdown ====== */
|
||||||
|
.countdown-section {
|
||||||
|
background: var(--dark);
|
||||||
|
color: white;
|
||||||
|
padding: 36px 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.countdown-title {
|
||||||
|
font-size: 14px;
|
||||||
|
opacity: 0.6;
|
||||||
|
letter-spacing: 3px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.countdown-boxes {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.countdown-box {
|
||||||
|
background: rgba(255,255,255,0.08);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 16px 20px;
|
||||||
|
min-width: 72px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.countdown-box .num {
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--orange-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.countdown-box .unit {
|
||||||
|
font-size: 12px;
|
||||||
|
opacity: 0.5;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====== Footer ====== */
|
||||||
|
.footer {
|
||||||
|
text-align: center;
|
||||||
|
padding: 32px 20px;
|
||||||
|
color: #aaa;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====== Modal ====== */
|
||||||
|
.modal-overlay {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 0; left: 0; right: 0; bottom: 0;
|
||||||
|
background: rgba(0,0,0,0.4);
|
||||||
|
z-index: 1000;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-overlay.show {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
background: white;
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 32px;
|
||||||
|
width: 90%;
|
||||||
|
max-width: 380px;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: 0 20px 60px rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal h3 {
|
||||||
|
font-size: 20px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
color: var(--dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal .modal-period {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--orange);
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border: 2px solid #e8e8e8;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 16px;
|
||||||
|
outline: none;
|
||||||
|
transition: border-color 0.3s;
|
||||||
|
box-sizing: border-box;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-input:focus {
|
||||||
|
border-color: var(--orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border: 2px solid #e8e8e8;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 16px;
|
||||||
|
outline: none;
|
||||||
|
background: white;
|
||||||
|
color: #999;
|
||||||
|
appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%23999' stroke-width='1.5' fill='none'/%3E%3C/svg%3E");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: right 14px center;
|
||||||
|
cursor: pointer;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-select:focus {
|
||||||
|
border-color: var(--orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-select.has-value {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-submit {
|
||||||
|
width: 100%;
|
||||||
|
padding: 14px;
|
||||||
|
background: var(--orange);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top: 14px;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-submit:hover { background: var(--orange-dark); }
|
||||||
|
.modal-submit:disabled { background: #ccc; cursor: default; }
|
||||||
|
|
||||||
|
.modal-cancel {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #999;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-top: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-cancel:hover { color: #666; }
|
||||||
|
|
||||||
|
/* ====== Toast ====== */
|
||||||
|
.toast {
|
||||||
|
position: fixed;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%) scale(0.85);
|
||||||
|
background: rgba(0,0,0,0.82);
|
||||||
|
color: #fff;
|
||||||
|
padding: 28px 36px;
|
||||||
|
border-radius: 18px;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1.8;
|
||||||
|
text-align: center;
|
||||||
|
z-index: 2000;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s, transform 0.3s;
|
||||||
|
max-width: 340px;
|
||||||
|
box-shadow: 0 12px 40px rgba(0,0,0,0.25);
|
||||||
|
pointer-events: none;
|
||||||
|
white-space: pre-line;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast.show {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translate(-50%, -50%) scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====== Animations ====== */
|
||||||
|
.fade-up {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(30px);
|
||||||
|
transition: opacity 0.6s ease, transform 0.6s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-up.visible {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====== Mobile ====== */
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.hero { padding: 48px 20px 64px; }
|
||||||
|
.hero h1 { font-size: 26px; }
|
||||||
|
.hero .subtitle { font-size: 15px; }
|
||||||
|
.hero-stats { gap: 20px; }
|
||||||
|
.hero-stat .num { font-size: 28px; }
|
||||||
|
.section { padding: 36px 16px; }
|
||||||
|
.section-header h2 { font-size: 22px; }
|
||||||
|
.value-grid { grid-template-columns: 1fr; }
|
||||||
|
.exam-date { font-size: 30px; }
|
||||||
|
.countdown-boxes { gap: 8px; }
|
||||||
|
.countdown-box { padding: 12px 16px; min-width: 60px; }
|
||||||
|
.countdown-box .num { font-size: 26px; }
|
||||||
|
}
|
||||||
320
public/chencun/index.html
Normal file
320
public/chencun/index.html
Normal file
@@ -0,0 +1,320 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>2026 暑假 Kitten 图形化编程等级考试集训 | 陈村校区</title>
|
||||||
|
<link rel="stylesheet" href="css/style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<!-- Hero -->
|
||||||
|
<div class="hero">
|
||||||
|
<div class="hero-content">
|
||||||
|
<span class="hero-badge">🎯 2026 暑假集训</span>
|
||||||
|
<h1>Kitten 图形化编程<br>等级考试集训</h1>
|
||||||
|
<p class="subtitle">NCT 青少年编程能力等级测试 · 陈村校区</p>
|
||||||
|
<div class="hero-stats">
|
||||||
|
<div class="hero-stat">
|
||||||
|
<div class="num">100%</div>
|
||||||
|
<div class="label">集训学员通过率</div>
|
||||||
|
</div>
|
||||||
|
<div class="hero-stat">
|
||||||
|
<div class="num">75W+</div>
|
||||||
|
<div class="label">全国考生累计</div>
|
||||||
|
</div>
|
||||||
|
<div class="hero-stat">
|
||||||
|
<div class="num">8人</div>
|
||||||
|
<div class="label">小班精品教学</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 倒计时 -->
|
||||||
|
<div class="countdown-section">
|
||||||
|
<div class="countdown-title">距离 NCT 考试还有</div>
|
||||||
|
<div class="countdown-boxes">
|
||||||
|
<div class="countdown-box">
|
||||||
|
<div class="num" id="cd-days">--</div>
|
||||||
|
<div class="unit">天</div>
|
||||||
|
</div>
|
||||||
|
<div class="countdown-box">
|
||||||
|
<div class="num" id="cd-hours">--</div>
|
||||||
|
<div class="unit">时</div>
|
||||||
|
</div>
|
||||||
|
<div class="countdown-box">
|
||||||
|
<div class="num" id="cd-mins">--</div>
|
||||||
|
<div class="unit">分</div>
|
||||||
|
</div>
|
||||||
|
<div class="countdown-box">
|
||||||
|
<div class="num" id="cd-secs">--</div>
|
||||||
|
<div class="unit">秒</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- NCT 是什么 -->
|
||||||
|
<div class="section fade-up">
|
||||||
|
<div class="section-header">
|
||||||
|
<h2>什么是 NCT?</h2>
|
||||||
|
<p>国内最权威的青少年编程能力等级测试</p>
|
||||||
|
</div>
|
||||||
|
<div class="info-grid">
|
||||||
|
<div class="info-card">
|
||||||
|
<div class="info-icon orange">🏛️</div>
|
||||||
|
<div>
|
||||||
|
<h3>中国软件行业协会主办</h3>
|
||||||
|
<p>NCT 由<strong>中国软件行业协会</strong>盖章颁发等级证书,是国家民政部认可的首批 AAA 级行业协会,唯一代表中国计算机软件产业界的全国性一级行业组织。</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-card">
|
||||||
|
<div class="info-icon blue">🌐</div>
|
||||||
|
<div>
|
||||||
|
<h3>国内国际双认证</h3>
|
||||||
|
<p>通过<strong>教育部 CELTSC</strong> 标准符合性认证,同时通过国际教育技术协会 <strong>ISTE</strong> 认证,是国内首家获得 ISTE Seal of Alignment 的编程测评项目。</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-card">
|
||||||
|
<div class="info-icon purple">🎓</div>
|
||||||
|
<div>
|
||||||
|
<h3>联合国教科文组织合作项目</h3>
|
||||||
|
<p>2021 年成为<strong>联合国教科文组织亚非地区青少年编程教育合作项目</strong>(AAYC)指定测评服务平台,获得国际认可。</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-card">
|
||||||
|
<div class="info-icon green">👨🎓</div>
|
||||||
|
<div>
|
||||||
|
<h3>全国超过 75 万考生参与</h3>
|
||||||
|
<p>覆盖中国 34 个省级行政区、全球 25+ 国家及地区,3000+ 所学校和机构参与,新东方、编程猫、童程童美等知名品牌合作。</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 证书价值 -->
|
||||||
|
<div class="section fade-up">
|
||||||
|
<div class="section-header">
|
||||||
|
<h2>考级证书的价值</h2>
|
||||||
|
<p>不只是一张证书,更是孩子成长的里程碑</p>
|
||||||
|
</div>
|
||||||
|
<div class="value-grid">
|
||||||
|
<div class="value-card">
|
||||||
|
<div class="value-emoji">🏅</div>
|
||||||
|
<h3>权威认证</h3>
|
||||||
|
<p>中国软件行业协会盖章颁发,含金量高</p>
|
||||||
|
</div>
|
||||||
|
<div class="value-card">
|
||||||
|
<div class="value-emoji">📋</div>
|
||||||
|
<h3>科技特长</h3>
|
||||||
|
<p>可作为信息科技特长参考依据,综合素质评价加分</p>
|
||||||
|
</div>
|
||||||
|
<div class="value-card">
|
||||||
|
<div class="value-emoji">🧠</div>
|
||||||
|
<h3>能力提升</h3>
|
||||||
|
<p>锻炼逻辑思维、拆题能力与问题解决能力</p>
|
||||||
|
</div>
|
||||||
|
<div class="value-card">
|
||||||
|
<div class="value-emoji">🎯</div>
|
||||||
|
<h3>阶段目标</h3>
|
||||||
|
<p>清晰的等级体系,给孩子学习锚定方向与动力</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 考试日期 -->
|
||||||
|
<div class="section fade-up" style="padding-top: 0;">
|
||||||
|
<div class="exam-card">
|
||||||
|
<h3>NCT 考试时间</h3>
|
||||||
|
<div class="exam-date">2026 年 8 月 22 – 23 日</div>
|
||||||
|
<p class="exam-note">具体考试场次 6 月上旬公布</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 集训时间安排 -->
|
||||||
|
<div class="section fade-up">
|
||||||
|
<div class="section-header">
|
||||||
|
<h2>集训时间安排</h2>
|
||||||
|
<p>三期可选,内容一致,根据时间灵活选择</p>
|
||||||
|
</div>
|
||||||
|
<div class="period-section">
|
||||||
|
<span class="period-badge orange">第一期</span>
|
||||||
|
<h3>7 月集训 · 第一期</h3>
|
||||||
|
<p class="period-info">2 周 · 周一至周六 · 每周日休息 · 每节课 2.5 小时</p>
|
||||||
|
<div style="font-size:18px;font-weight:700;color:#1A1A2E;margin:12px 0 8px">7 月 13 日 – 7 月 25 日</div>
|
||||||
|
<div class="reg-input-row">
|
||||||
|
<input class="reg-input" type="text" placeholder="填写孩子姓名" maxlength="20">
|
||||||
|
<button class="reg-btn orange" onclick="register(this, '第一期:7月13日–7月25日')">我要报名</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="period-section">
|
||||||
|
<span class="period-badge blue">第二期</span>
|
||||||
|
<h3>7–8 月集训 · 第二期</h3>
|
||||||
|
<p class="period-info">2 周 · 周一至周六 · 每周日休息 · 每节课 2.5 小时</p>
|
||||||
|
<div style="font-size:18px;font-weight:700;color:#1A1A2E;margin:12px 0 8px">7 月 27 日 – 8 月 8 日</div>
|
||||||
|
<div class="reg-input-row">
|
||||||
|
<input class="reg-input" type="text" placeholder="填写孩子姓名" maxlength="20">
|
||||||
|
<button class="reg-btn blue" onclick="register(this, '第二期:7月27日–8月8日')">我要报名</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="period-section">
|
||||||
|
<span class="period-badge orange">第三期</span>
|
||||||
|
<h3>8 月集训 · 第三期</h3>
|
||||||
|
<p class="period-info">2 周 · 周一至周六 · 每周日休息 · 每节课 2.5 小时</p>
|
||||||
|
<div style="font-size:18px;font-weight:700;color:#1A1A2E;margin:12px 0 8px">8 月 10 日 – 8 月 22 日</div>
|
||||||
|
<div class="reg-input-row">
|
||||||
|
<input class="reg-input" type="text" placeholder="填写孩子姓名" maxlength="20">
|
||||||
|
<button class="reg-btn orange" onclick="register(this, '第三期:8月10日–8月22日')">我要报名</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="highlight-strip">
|
||||||
|
<p>💡 三期集训内容完全一致,家长可根据孩子时间安排自由选择。每周日休息。<br>填写孩子姓名后点击<strong>「我要报名」</strong>即可提交,老师会主动联系您确认。</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 报名门槛 -->
|
||||||
|
<div class="section fade-up" style="padding-top: 0;">
|
||||||
|
<div class="section-header">
|
||||||
|
<h2>报名条件</h2>
|
||||||
|
</div>
|
||||||
|
<div class="info-grid">
|
||||||
|
<div class="info-card">
|
||||||
|
<div class="info-icon orange">📐</div>
|
||||||
|
<div>
|
||||||
|
<h3>K2 阶段学员</h3>
|
||||||
|
<p>报考 <strong>NCT 一级</strong>,夯实基础,建立信心</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-card">
|
||||||
|
<div class="info-icon blue">🔬</div>
|
||||||
|
<div>
|
||||||
|
<h3>K4 阶段学员</h3>
|
||||||
|
<p>报考 <strong>NCT 二级</strong>,挑战进阶,证明实力</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="highlight-strip" style="margin-top: 16px;">
|
||||||
|
<p>📌 未考过一级的学生需先考一级,不可跳级。所有在读 Kitten 学员均可报名。</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 费用说明 -->
|
||||||
|
<div class="section fade-up" style="padding-top: 0;">
|
||||||
|
<div class="section-header">
|
||||||
|
<h2>费用说明</h2>
|
||||||
|
</div>
|
||||||
|
<div class="fee-table">
|
||||||
|
<div class="fee-row">
|
||||||
|
<div class="fee-label">📝 NCT 官方报名费</div>
|
||||||
|
<div class="fee-value">300 元 / 人</div>
|
||||||
|
</div>
|
||||||
|
<div class="fee-row">
|
||||||
|
<div class="fee-label">🎒 集训费用</div>
|
||||||
|
<div class="fee-value">正常课时扣费</div>
|
||||||
|
</div>
|
||||||
|
<div class="fee-row">
|
||||||
|
<div class="fee-label">⏱️ 课时规则</div>
|
||||||
|
<div class="fee-value">每节扣 2 课时</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="highlight-strip" style="margin-top: 16px;">
|
||||||
|
<p>🏆 <strong>奖学金激励:</strong>成绩优异的学员可获得校区颁发的奖学金,既是荣誉也是激励!</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 常见问题 -->
|
||||||
|
<div class="section fade-up" style="padding-top: 0;">
|
||||||
|
<div class="section-header">
|
||||||
|
<h2>常见问题</h2>
|
||||||
|
</div>
|
||||||
|
<div class="faq-item">
|
||||||
|
<div class="faq-q">
|
||||||
|
<span>孩子零基础可以参加吗?</span>
|
||||||
|
<span class="arrow">▼</span>
|
||||||
|
</div>
|
||||||
|
<div class="faq-a">
|
||||||
|
集训面向在读 Kitten 学员。K2 阶段报一级、K4 阶段报二级,难度与孩子当前学习进度匹配,老师全程指导,可以放心参加。
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="faq-item">
|
||||||
|
<div class="faq-q">
|
||||||
|
<span>考试形式是什么样的?</span>
|
||||||
|
<span class="arrow">▼</span>
|
||||||
|
</div>
|
||||||
|
<div class="faq-a">
|
||||||
|
NCT 采用线上考试形式,支持居家和线下考点在线考试。AI 智能监考系统保证考试公正、专业,足不出户即可参加。
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="faq-item">
|
||||||
|
<div class="faq-q">
|
||||||
|
<span>集训会不会影响暑假出行?</span>
|
||||||
|
<span class="arrow">▼</span>
|
||||||
|
</div>
|
||||||
|
<div class="faq-a">
|
||||||
|
集训每期内容一样,选一期参加即可。每周日休息,三期覆盖整个暑假,合理安排完全不会影响家庭出行计划。
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="faq-item">
|
||||||
|
<div class="faq-q">
|
||||||
|
<span>通过率怎么样?</span>
|
||||||
|
<span class="arrow">▼</span>
|
||||||
|
</div>
|
||||||
|
<div class="faq-a">
|
||||||
|
参加集训的学员通过率基本达到 <strong>100%</strong>。集训内容紧扣考级知识点,系统刷题 + 模拟实操,帮助孩子充分准备。
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="faq-item">
|
||||||
|
<div class="faq-q">
|
||||||
|
<span>证书长什么样?有含金量吗?</span>
|
||||||
|
<span class="arrow">▼</span>
|
||||||
|
</div>
|
||||||
|
<div class="faq-a">
|
||||||
|
新版证书采用 PU 皮革材质、烫金凹印技术、圆角裁剪工艺,精美大气。由中国软件行业协会盖章颁发,通过顺丰快递寄送。该证书通过教育部 CELTSC 标准认证和国际 ISTE 认证,是目前国内外双认证的权威编程等级证书。
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 报名入口 -->
|
||||||
|
<div class="cta-section">
|
||||||
|
<div class="cta-box">
|
||||||
|
<h2>🚀 暑假集训,助孩子高效拿证</h2>
|
||||||
|
<p>小班精品教学 · 系统备考方案 · 权威等级证书<br>名额有限,每班仅限 8 人,先到先得!</p>
|
||||||
|
<a href="#" class="cta-btn" onclick="showToast('请往上滑,填写孩子姓名\n并点击「我要报名」提交', 3000); return false;">立即咨询报名</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 底部 -->
|
||||||
|
<div class="footer">
|
||||||
|
<p>穹狼科创 · 陈村校区 · 2026 年暑假</p>
|
||||||
|
<p style="margin-top: 6px;">NCT 青少年编程能力等级测试</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Toast 提示 -->
|
||||||
|
<div class="toast" id="toast"></div>
|
||||||
|
|
||||||
|
<!-- 报名确认弹窗 -->
|
||||||
|
<div class="modal-overlay" id="regModal">
|
||||||
|
<div class="modal">
|
||||||
|
<h3>确认报名</h3>
|
||||||
|
<div class="modal-period" id="modalPeriod"></div>
|
||||||
|
<input class="modal-input" id="modalName" type="text" placeholder="请输入孩子姓名" maxlength="20" style="margin-bottom:10px">
|
||||||
|
<select class="modal-select" id="modalTeacher" onchange="this.classList.toggle('has-value', this.value)">
|
||||||
|
<option value="" disabled selected>选择负责老师</option>
|
||||||
|
<option value="东东老师">东东老师</option>
|
||||||
|
<option value="橙子老师">橙子老师</option>
|
||||||
|
<option value="福福老师">福福老师</option>
|
||||||
|
<option value="坦克老师">坦克老师</option>
|
||||||
|
<option value="菠萝老师">菠萝老师</option>
|
||||||
|
</select>
|
||||||
|
<button class="modal-submit" id="modalSubmit" onclick="submitReg()">确认提交</button>
|
||||||
|
<br>
|
||||||
|
<button class="modal-cancel" onclick="closeModal()">取消</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="js/app.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
126
public/chencun/js/app.js
Normal file
126
public/chencun/js/app.js
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
// ============ 倒计时 ============
|
||||||
|
function updateCountdown() {
|
||||||
|
var examDate = new Date('2026-08-22T09:00:00');
|
||||||
|
var now = new Date();
|
||||||
|
var diff = examDate - now;
|
||||||
|
if (diff <= 0) {
|
||||||
|
document.getElementById('cd-days').textContent = '0';
|
||||||
|
document.getElementById('cd-hours').textContent = '0';
|
||||||
|
document.getElementById('cd-mins').textContent = '0';
|
||||||
|
document.getElementById('cd-secs').textContent = '0';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
document.getElementById('cd-days').textContent = Math.floor(diff / 864e5);
|
||||||
|
document.getElementById('cd-hours').textContent = Math.floor((diff % 864e5) / 36e5);
|
||||||
|
document.getElementById('cd-mins').textContent = Math.floor((diff % 36e5) / 6e4);
|
||||||
|
document.getElementById('cd-secs').textContent = Math.floor((diff % 6e4) / 1e3);
|
||||||
|
}
|
||||||
|
updateCountdown();
|
||||||
|
setInterval(updateCountdown, 1000);
|
||||||
|
|
||||||
|
// ============ FAQ 折叠 ============
|
||||||
|
document.querySelectorAll('.faq-q').forEach(function(q) {
|
||||||
|
q.addEventListener('click', function() {
|
||||||
|
this.parentElement.classList.toggle('open');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ============ 滚动渐入 ============
|
||||||
|
var observer = new IntersectionObserver(function(entries) {
|
||||||
|
entries.forEach(function(entry) {
|
||||||
|
if (entry.isIntersecting) entry.target.classList.add('visible');
|
||||||
|
});
|
||||||
|
}, { threshold: 0.1 });
|
||||||
|
document.querySelectorAll('.fade-up').forEach(function(el) {
|
||||||
|
observer.observe(el);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ============ Toast 提示 ============
|
||||||
|
function showToast(msg, duration) {
|
||||||
|
duration = duration || 2500;
|
||||||
|
var el = document.getElementById('toast');
|
||||||
|
el.textContent = msg;
|
||||||
|
el.classList.add('show');
|
||||||
|
clearTimeout(el._timer);
|
||||||
|
el._timer = setTimeout(function() {
|
||||||
|
el.classList.remove('show');
|
||||||
|
}, duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ 报名流程 ============
|
||||||
|
var pendingPeriod = '';
|
||||||
|
|
||||||
|
function register(btn, period) {
|
||||||
|
var row = btn.closest('.reg-input-row');
|
||||||
|
var name = row.querySelector('.reg-input').value.trim();
|
||||||
|
if (!name) {
|
||||||
|
row.querySelector('.reg-input').focus();
|
||||||
|
row.querySelector('.reg-input').style.borderColor = '#FF4444';
|
||||||
|
setTimeout(function() {
|
||||||
|
row.querySelector('.reg-input').style.borderColor = '';
|
||||||
|
}, 1500);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pendingPeriod = period;
|
||||||
|
document.getElementById('modalPeriod').textContent = period;
|
||||||
|
document.getElementById('modalName').value = name;
|
||||||
|
document.getElementById('regModal').classList.add('show');
|
||||||
|
document.getElementById('modalName').focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeModal() {
|
||||||
|
document.getElementById('regModal').classList.remove('show');
|
||||||
|
pendingPeriod = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('regModal').addEventListener('click', function(e) {
|
||||||
|
if (e.target === this) closeModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
function submitReg() {
|
||||||
|
var name = document.getElementById('modalName').value.trim();
|
||||||
|
var teacher = document.getElementById('modalTeacher').value;
|
||||||
|
var btn = document.getElementById('modalSubmit');
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
document.getElementById('modalName').focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!teacher) {
|
||||||
|
document.getElementById('modalTeacher').style.borderColor = '#FF4444';
|
||||||
|
setTimeout(function() {
|
||||||
|
document.getElementById('modalTeacher').style.borderColor = '';
|
||||||
|
}, 1500);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.textContent = '提交中...';
|
||||||
|
|
||||||
|
fetch('api/register', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||||
|
body: 'name=' + encodeURIComponent(name) +
|
||||||
|
'&period=' + encodeURIComponent(pendingPeriod) +
|
||||||
|
'&teacher=' + encodeURIComponent(teacher)
|
||||||
|
})
|
||||||
|
.then(function(res) { return res.json(); })
|
||||||
|
.then(function(data) {
|
||||||
|
closeModal();
|
||||||
|
if (data.ok) {
|
||||||
|
showToast('✅ 报名成功!\n\n' + name + '\n' + pendingPeriod + '\n' + teacher, 3000);
|
||||||
|
document.querySelectorAll('.reg-input').forEach(function(el) { el.value = ''; });
|
||||||
|
document.getElementById('modalTeacher').selectedIndex = 0;
|
||||||
|
document.getElementById('modalTeacher').classList.remove('has-value');
|
||||||
|
} else {
|
||||||
|
showToast('❌ ' + (data.msg || '提交失败,请稍后再试'));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function() {
|
||||||
|
showToast('❌ 网络错误,请稍后再试');
|
||||||
|
})
|
||||||
|
.finally(function() {
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.textContent = '确认提交';
|
||||||
|
});
|
||||||
|
}
|
||||||
143
server.js
Normal file
143
server.js
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const path = require('path');
|
||||||
|
const { createCampusDB } = require('./db');
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
const PORT = 3000;
|
||||||
|
|
||||||
|
// 中间件
|
||||||
|
app.use(express.urlencoded({ extended: true }));
|
||||||
|
app.use(express.json());
|
||||||
|
|
||||||
|
// ============ 为每个校区创建独立路由 ============
|
||||||
|
|
||||||
|
function setupCampus(routePrefix, campusName, dirName) {
|
||||||
|
const router = express.Router();
|
||||||
|
const campusDB = createCampusDB(dirName);
|
||||||
|
|
||||||
|
// 静态文件
|
||||||
|
router.use(express.static(path.join(__dirname, 'public', dirName)));
|
||||||
|
|
||||||
|
// 报名接口
|
||||||
|
router.post('/api/register', (req, res) => {
|
||||||
|
const { name, period, teacher } = req.body;
|
||||||
|
|
||||||
|
if (!name || !period || !teacher) {
|
||||||
|
return res.json({ ok: false, msg: '请填写完整信息' });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
campusDB.insertRegistration(name.trim(), period, teacher);
|
||||||
|
console.log(`[报名·${campusName}] ${name} - ${period} - ${teacher}`);
|
||||||
|
res.json({ ok: true, msg: '报名成功' });
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`[报名错误·${campusName}]`, err);
|
||||||
|
res.json({ ok: false, msg: '服务器错误,请稍后再试' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取报名列表(需密码)
|
||||||
|
router.get('/api/registrations', (req, res) => {
|
||||||
|
const password = req.query.password || req.headers['x-admin-password'];
|
||||||
|
if (!campusDB.verifyPassword(password)) {
|
||||||
|
return res.status(401).json({ ok: false, msg: '密码错误' });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const registrations = campusDB.getAllRegistrations();
|
||||||
|
res.json({ ok: true, data: registrations });
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`[查询错误·${campusName}]`, err);
|
||||||
|
res.status(500).json({ ok: false, msg: '服务器错误' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取统计(需密码)
|
||||||
|
router.get('/api/stats', (req, res) => {
|
||||||
|
const password = req.query.password || req.headers['x-admin-password'];
|
||||||
|
if (!campusDB.verifyPassword(password)) {
|
||||||
|
return res.status(401).json({ ok: false, msg: '密码错误' });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const stats = campusDB.getStats();
|
||||||
|
res.json({ ok: true, data: stats });
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`[统计错误·${campusName}]`, err);
|
||||||
|
res.status(500).json({ ok: false, msg: '服务器错误' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 删除报名记录(需密码)
|
||||||
|
router.delete('/api/registrations/:id', (req, res) => {
|
||||||
|
const password = req.query.password || req.headers['x-admin-password'];
|
||||||
|
if (!campusDB.verifyPassword(password)) {
|
||||||
|
return res.status(401).json({ ok: false, msg: '密码错误' });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
campusDB.deleteRegistration(parseInt(req.params.id));
|
||||||
|
res.json({ ok: true, msg: '删除成功' });
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`[删除错误·${campusName}]`, err);
|
||||||
|
res.status(500).json({ ok: false, msg: '服务器错误' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.use(routePrefix, router);
|
||||||
|
return campusDB;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ 注册校区 ============
|
||||||
|
|
||||||
|
const chencunDB = setupCampus('/chencunnct', '陈村校区', 'chencun');
|
||||||
|
const beijiaoDB = setupCampus('/beijiaonct', '北滘校区', 'beijiao');
|
||||||
|
|
||||||
|
// 根路径跳转提示
|
||||||
|
app.get('/', (req, res) => {
|
||||||
|
res.send(`
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||||
|
<title>校区选择</title>
|
||||||
|
<style>
|
||||||
|
* { margin:0; padding:0; box-sizing:border-box; }
|
||||||
|
body { font-family:-apple-system,"PingFang SC","Microsoft YaHei",sans-serif;
|
||||||
|
background:#F5F7FA; display:flex; justify-content:center; align-items:center;
|
||||||
|
min-height:100vh; }
|
||||||
|
.card { background:#fff; border-radius:20px; padding:48px 40px; box-shadow:0 4px 24px rgba(0,0,0,0.08);
|
||||||
|
text-align:center; max-width:420px; width:90%; }
|
||||||
|
h1 { font-size:22px; color:#1A1A2E; margin-bottom:8px; }
|
||||||
|
p { color:#666; font-size:14px; margin-bottom:28px; }
|
||||||
|
.links { display:flex; flex-direction:column; gap:14px; }
|
||||||
|
a { display:block; padding:16px 24px; border-radius:14px; text-decoration:none;
|
||||||
|
font-size:16px; font-weight:700; color:#fff; transition:transform 0.2s; }
|
||||||
|
a:hover { transform:translateY(-2px); }
|
||||||
|
.chencun { background:linear-gradient(135deg,#FF6B35,#FFB347); }
|
||||||
|
.beijiao { background:linear-gradient(135deg,#4A6CF7,#6B8CFF); }
|
||||||
|
</style></head>
|
||||||
|
<body><div class="card">
|
||||||
|
<h1>🏫 选择校区</h1>
|
||||||
|
<p>请选择您所在的校区</p>
|
||||||
|
<div class="links">
|
||||||
|
<a class="chencun" href="/chencunnct/">陈村校区</a>
|
||||||
|
<a class="beijiao" href="/beijiaonct/">北滘校区</a>
|
||||||
|
</div>
|
||||||
|
</div></body></html>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ============ 启动服务器 ============
|
||||||
|
|
||||||
|
async function start() {
|
||||||
|
await chencunDB.init();
|
||||||
|
await beijiaoDB.init();
|
||||||
|
app.listen(PORT, () => {
|
||||||
|
console.log('\n🚀 服务器已启动: http://localhost:' + PORT);
|
||||||
|
console.log('🏫 陈村校区: http://localhost:' + PORT + '/chencunnct/');
|
||||||
|
console.log('🏫 北滘校区: http://localhost:' + PORT + '/beijiaonct/');
|
||||||
|
console.log('🔑 管理员密码: admin123\n');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
start();
|
||||||
Reference in New Issue
Block a user