This commit is contained in:
Harry
2026-04-25 10:09:16 +08:00
committed by GitHub
commit bf215c4b9f
15 changed files with 2482 additions and 0 deletions

287
storage.js Normal file
View File

@@ -0,0 +1,287 @@
/**
* 通用数据存储模块
* 支持两种模式:
* 1. 服务器模式通过HTTP API与后端交互
* 2. 本地模式使用localStorage存储数据
*/
class DataStorage {
constructor() {
this.apiBase = this.getApiBase();
this.isServerMode = this.checkServerMode();
console.log(`数据存储模式: ${this.isServerMode ? '服务器模式' : '本地模式'}`);
}
/**
* 检测运行模式
*/
checkServerMode() {
// 如果是通过HTTP服务器访问非file协议则使用服务器模式
return window.location.protocol === 'http:' || window.location.protocol === 'https:';
}
/**
* 获取API基础URL
*/
getApiBase() {
if (window.location.protocol === 'http:' || window.location.protocol === 'https:') {
return `${window.location.protocol}//${window.location.host}/api`;
}
return null;
}
/**
* HTTP请求封装
*/
async httpRequest(url, method = 'GET', data = null) {
const options = {
method: method,
headers: {
'Content-Type': 'application/json'
}
};
if (data && method !== 'GET') {
options.body = JSON.stringify(data);
}
try {
const response = await fetch(url, options);
// 检查响应类型
const contentType = response.headers.get('content-type');
const isJson = contentType && contentType.includes('application/json');
if (!response.ok) {
// 如果响应不是JSON尝试读取文本
if (!isJson) {
const text = await response.text();
throw new Error(`服务器错误 (${response.status}): 返回了非JSON响应`);
}
const result = await response.json();
throw new Error(result.error || `HTTP错误: ${response.status}`);
}
// 检查成功响应是否为JSON
if (!isJson) {
throw new Error('服务器返回了非JSON响应请检查API接口');
}
const result = await response.json();
return result;
} catch (error) {
// 处理网络错误
if (error.message === 'Failed to fetch') {
throw new Error('无法连接到服务器,请确保:\n1. 已启动Node.js服务器\n2. 服务器运行在正确端口\n3. 或使用本地模式直接打开HTML文件');
}
// 处理JSON解析错误
if (error.message.includes('Unexpected token')) {
throw new Error('服务器返回了HTML页面而非JSON数据可能是\n1. API接口不存在\n2. 服务器配置错误\n3. 建议使用本地模式或检查服务器');
}
throw error;
}
}
/**
* 登录
*/
async login(username, password) {
if (this.isServerMode) {
// 服务器模式
const result = await this.httpRequest(`${this.apiBase}/login`, 'POST', { username, password });
return result.data;
} else {
// 本地模式
if (username === 'admin' && password === 'admin123') {
return { token: 'local-token-' + Date.now() };
}
throw new Error('用户名或密码错误');
}
}
/**
* 创建点名名单
*/
async createList(name, members) {
if (this.isServerMode) {
const result = await this.httpRequest(`${this.apiBase}/create-list`, 'POST', { name, members });
return result.data;
} else {
// 本地模式
const lists = this.getLocalLists();
const newList = {
id: Date.now(),
name: name,
code: this.generateCode(),
members: members.map(memberName => ({
name: memberName,
completed: false,
timestamp: null
})),
createdAt: new Date().toLocaleString()
};
lists.push(newList);
localStorage.setItem('rollCallLists', JSON.stringify(lists));
return newList;
}
}
/**
* 获取所有名单
*/
async getLists() {
if (this.isServerMode) {
const result = await this.httpRequest(`${this.apiBase}/get-lists`);
return result.data;
} else {
return this.getLocalLists();
}
}
/**
* 获取单个名单
*/
async getList(code) {
if (this.isServerMode) {
const result = await this.httpRequest(`${this.apiBase}/get-list?code=${code}`);
return result.data;
} else {
const lists = this.getLocalLists();
const list = lists.find(l => l.code === code);
if (!list) {
throw new Error('名单不存在');
}
return list;
}
}
/**
* 更新名单
*/
async updateList(id, name, members) {
if (this.isServerMode) {
const result = await this.httpRequest(`${this.apiBase}/update-list`, 'POST', { id, name, members });
return result.data;
} else {
const lists = this.getLocalLists();
const list = lists.find(l => l.id === id);
if (!list) {
throw new Error('名单不存在');
}
if (name) list.name = name;
if (members && Array.isArray(members)) {
list.members = members.map(memberName => {
const existing = list.members.find(m => m.name === memberName);
return existing || { name: memberName, completed: false, timestamp: null };
});
}
localStorage.setItem('rollCallLists', JSON.stringify(lists));
return list;
}
}
/**
* 删除名单
*/
async deleteList(id) {
if (this.isServerMode) {
const result = await this.httpRequest(`${this.apiBase}/delete-list`, 'POST', { id });
return result.data;
} else {
let lists = this.getLocalLists();
lists = lists.filter(l => l.id !== id);
localStorage.setItem('rollCallLists', JSON.stringify(lists));
return { message: '删除成功' };
}
}
/**
* 点名
*/
async rollCall(code, name) {
if (this.isServerMode) {
const result = await this.httpRequest(`${this.apiBase}/roll-call`, 'POST', { code, name });
return result.data;
} else {
const lists = this.getLocalLists();
const list = lists.find(l => l.code === code);
if (!list) {
throw new Error('名单不存在');
}
const member = list.members.find(m => m.name === name);
if (!member) {
throw new Error('成员不存在');
}
if (member.completed) {
throw new Error('该成员已点名');
}
member.completed = true;
member.timestamp = new Date().toLocaleString();
// 添加记录
const records = this.getLocalRecords();
records.push({
name: name,
listName: list.name,
code: code,
timestamp: member.timestamp
});
localStorage.setItem('rollCallLists', JSON.stringify(lists));
localStorage.setItem('rollCallRecords', JSON.stringify(records));
return { message: '点名成功', member: member };
}
}
/**
* 获取点名记录
*/
async getRecords() {
if (this.isServerMode) {
const result = await this.httpRequest(`${this.apiBase}/get-records`);
return result.data;
} else {
return this.getLocalRecords();
}
}
/**
* 本地模式:获取名单
*/
getLocalLists() {
const data = localStorage.getItem('rollCallLists');
return data ? JSON.parse(data) : [];
}
/**
* 本地模式:获取记录
*/
getLocalRecords() {
const data = localStorage.getItem('rollCallRecords');
return data ? JSON.parse(data) : [];
}
/**
* 生成随机编号
*/
generateCode() {
const length = Math.floor(Math.random() * 7) + 6; // 6-12位
let code = '';
for (let i = 0; i < length; i++) {
code += Math.floor(Math.random() * 10);
}
return code;
}
}
// 创建全局实例
window.dataStorage = new DataStorage();