/** * 通用数据存储模块 * 支持两种模式: * 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();