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