折腾了一个关于睡眠记录的项目,不眠不休的几天,不断失败,现在分阶段讲一下我从入门以来走过的弯路

第一阶段——无知者无畏

直接下载了一个炫酷的前端模板,结果发现里面的内容完全修改不了,没这个能力,完全依赖GPT和没有GPT的情况是一致的。

image-20250207164142894

第二阶段——开始碰壁

开始在哔哩哔哩上搜索别人的毕业设计项目,结果就是,封装好的我都打不开,不停报错,又是两三天砸进去。

第三阶段——从零开始

真正开始自己做了,让我的GPT给我一个最简单的前后端,成功了,接着就想着做一个睡眠的系统,记录我的睡眠状况,这个过程又是2天的不眠不休!下面介绍下方法:

软件清单:

1
npm、node、vue

向GPT提出需求:

1
你好,我现在要编辑一个前端和后端的项目,我现在有的软件是IDE、npm和nodejs等,请给我用时最短,工作量最小的工作流程。此外,我在这个领域一点基础也没有,请从头教我怎么做。

具体过程:

1、新建一个文件夹project任意英文名都可以

2、使用VScode打开project文件夹,并打开终端依次输入以下代码(这些代码都要在project文件夹下运行)

:one:

1
npx @vue/cli create my-frontend

:two:

1
mkdir my-backend && cd my-backend

:three:

1
npm init -y

:four:

1
npm install express cors body-parser

:five:

1
touch server.js

:six:

1
cd project/my-frontend

:seven:

1
npm install axios

前端全文粘贴修改VUE就可以,这是我的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
<template>
<div class="container">
<h1>睡眠记录</h1>
<!-- 输入表单 -->
<form @submit.prevent="saveRecord">
<div class="grid-container">
<!-- 第一行:日期和星期 -->
<div class="input-box">
<label>日期:</label>
<datepicker v-model="newRecord.date" format="yyyy-MM-dd" required class="datepicker-input" />
</div>
<div class="input-box">
<label>星期:</label>
<!-- 下拉框选择星期 -->
<select v-model="newRecord.week" required>
<option disabled value="">请选择星期</option>
<option v-for="(day, index) in weekdays" :key="index" :value="day">{{ day }}</option>
</select>
</div>

<!-- 第二行:入睡时间和苏醒时间 -->
<div class="input-box">
<label>入睡时间:</label>
<input v-model="newRecord.sleepTime" type="time" required />
</div>
<div class="input-box">
<label>苏醒时间:</label>
<input v-model="newRecord.wakeTime" type="time" required />
</div>
</div>
<button type="submit">保存</button>
</form>

<h2>睡眠时长记录表</h2>
<div id="app">
<table>
<thead>
<tr>
<th>日期</th>
<th>星期</th>
<th>入睡时间</th>
<th>苏醒时间</th>
<th>睡眠时长</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(record, index) in sleepRecords" :key="index">
<td class="date-column">{{ formatDate(record.date) }}</td>
<td>{{ record.week }}</td>
<td>{{ record.sleepTime }}</td>
<td>{{ record.wakeTime }}</td>
<td class="duration-column">{{ record.sleepDuration }}</td>
<td>
<button @click="deleteRecord(index)">删除</button>
</td>
</tr>
</tbody>
</table>
</div>

<!-- 睡眠统计表 -->
<h2>睡眠时长统计表</h2>
<table border="1">
<thead>
<tr>
<th>每日睡眠平均时长 (小时)</th>
<th>睡眠总时间 (小时)</th>
<th>持续总天数</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{ averageSleepDuration }}</td>
<td>{{ totalSleepDuration }}</td>
<td>{{ totalDays }}</td>
</tr>
</tbody>
</table>
</div>
</template>

<script>
import axios from 'axios';
import Datepicker from 'vue3-datepicker'; // 导入日期选择器

export default {
components: {
Datepicker, // 注册组件
},
data() {
return {
weekdays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
newRecord: { date: '', week: '', sleepTime: '', wakeTime: '', sleepDuration: 0 },
sleepRecords: [],
};
},
computed: {
// 计算每日睡眠平均时长 (小时)
averageSleepDuration() {
if (this.sleepRecords.length === 0) return 0;
const totalDuration = this.sleepRecords.reduce((total, record) => total + parseFloat(record.sleepDuration), 0);
return (totalDuration / this.sleepRecords.length).toFixed(2);
},
// 计算睡眠总时间 (小时)
totalSleepDuration() {
const totalDuration = this.sleepRecords.reduce((total, record) => {
return total + (record.sleepDuration ? parseFloat(record.sleepDuration) : 0); // 确保记录存在有效的 sleepDuration
}, 0);
return totalDuration.toFixed(2); // 返回有效的数值,保留两位小数
},
// 计算持续总天数
totalDays() {
return this.sleepRecords.length;
},
},

methods: {
async deleteRecord(index) {
if (!confirm("确定要删除这条记录吗?")) return;
// 直接从前端移除数据
this.sleepRecords.splice(index, 1);
},

async saveRecord() {
// 自动计算星期
const weekday = this.getWeekday(this.newRecord.date);
// 填充星期
this.newRecord.week = weekday;

// 计算睡眠时长
const sleepDuration = this.calculateSleepDuration(this.newRecord.sleepTime, this.newRecord.wakeTime);
this.newRecord.sleepDuration = sleepDuration;

try {
const response = await axios.post('http://localhost:3000/api/sleep-records', this.newRecord);
this.sleepRecords.push(response.data);
this.newRecord = { date: '', week: '', sleepTime: '', wakeTime: '', sleepDuration: 0 }; // 重置表单
} catch (error) {
console.error('Error saving record:', error);
}
},

// 计算星期
getWeekday(date) {
const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
const d = new Date(date);
return days[d.getDay()]; // 返回星期几
},

// 计算睡眠时长
calculateSleepDuration(sleepTime, wakeTime) {
const sleep = this.timeToMinutes(sleepTime);
const wake = this.timeToMinutes(wakeTime);
let duration = wake - sleep;

// 如果苏醒时间小于入睡时间,表示跨越了午夜,进行调整
if (duration < 0) {
duration += 24 * 60; // 加上24小时的分钟数
}

return (duration / 60).toFixed(2); // 返回小时数,保留两位小数
},

// 将时间(HH:MM)转换为分钟数
timeToMinutes(time) {
const [hours, minutes] = time.split(':').map(num => parseInt(num, 10));
return hours * 60 + minutes;
},

// 格式化日期为 YYYY年MM月DD日
formatDate(date) {
const d = new Date(date);
const year = d.getFullYear();
const month = (d.getMonth() + 1).toString().padStart(2, '0');
const day = d.getDate().toString().padStart(2, '0');
return `${year}${month}${day}日`;
},
},

async mounted() {
try {
const response = await axios.get('http://localhost:3000/api/sleep-records');
this.sleepRecords = response.data.map(record => {
record.sleepDuration = this.calculateSleepDuration(record.sleepTime, record.wakeTime);
return record;
});
} catch (error) {
console.error('Error fetching records:', error);
}
},
};
</script>

<style scoped>
/* 设置皮卡丘背景图 */
.container {
background-image: url('https://p.sda1.dev/21/f49fe2aab1553c84f68627b04432d22b/toy-3633751.jpg'); /* 替换为皮卡丘图片链接 */
background-size: cover;
background-position: center;
background-repeat: no-repeat; /* 避免重复 */
min-height: 100vh;
color: #333;
font-family: Arial, sans-serif;
padding: 20px;
position: relative; /* 确保元素定位不受影响 */
}

h1 {
color: #fff;
}

form {
display: flex;
flex-direction: column;
margin: 20px;
padding: 20px;
background-color: rgba(255, 255, 255, 0.8); /* 半透明背景 */
border-radius: 5px;
}

table {
width: 100%;
margin-top: 20px;
border-collapse: collapse;
text-align: center;
background: linear-gradient(45deg, #f4f4f4, #e0e0e0);
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}


/* 保持现有的样式 */
:deep(.datepicker-input),
:deep(.vue3-datepicker__input) {
padding: 10px !important;
margin-bottom: 10px !important;
border-radius: 5px !important;
border: 1px solid #ccc !important;
font-size: 16px !important;
width: 100% !important;
box-sizing: border-box !important;
}

:deep(.vue3-datepicker-wrapper) {
width: 100% !important;
display: block !important;
}

:deep(.vue3-datepicker) {
font-size: 16px !important;
}

.input-box input, .datepicker-input, select {
padding: 10px;
margin-bottom: 10px;
border-radius: 5px;
border: 1px solid #ccc;
font-size: 16px;
width: 100%;
box-sizing: border-box;
}

h1 {
color: #4CAF50;
text-align: center;
}

form {
display: flex;
flex-direction: column;
margin: 20px;
padding: 20px;
background-color: rgba(255, 255, 255, 0.8); /* 半透明背景 */
border-radius: 5px;
}

.grid-container {
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 20px;
margin-bottom: 20px;
}

.input-box {
display: flex;
flex-direction: column;
}

label {
margin-bottom: 8px;
font-weight: bold;
}

button {
background-color: #4CAF50;
color: white;
padding: 10px;
border: none;
border-radius: 5px;
cursor: pointer;
}

button:hover {
background-color: #45a049;
}

h2 {
text-align: center;
}

ul {
list-style: none;
padding: 0;
}

li {
background-color: #f9f9f9;
margin: 5px 0;
padding: 10px;
border-radius: 5px;
}

table {
width: 100%;
margin-top: 20px;
border-collapse: collapse;
text-align: center;
background: linear-gradient(45deg, #f4f4f4, #e0e0e0);
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}

table th,
table td {
padding: 12px 20px;
border: 1px solid #ccc;
text-align: center;
font-size: 16px;
border-radius: 8px;
}

table th {
background-color: #4CAF50;
color: white;
}

table td {
background-color: #fff;
color: #333;
}

table tr:hover td {
background-color: #f1f1f1;
}

table th, table td {
transition: background-color 0.3s ease;
}

table .date-column {
font-weight: bold;
}

table .duration-column {
color: #4CAF50;
font-weight: bold;
}
</style>

后端改server.js,这是我的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
const express = require('express');
const cors = require('cors'); // 引入 CORS 模块
const app = express();
const port = 3000;

// 使用 CORS 中间件
app.use(cors({
origin: 'http://192.168.31.71:8080', // 允许来自此地址的请求
}));

app.use(express.json()); // 解析 JSON 格式的请求体

let sleepData = [];

// 处理根路径请求
app.get('/', (req, res) => {
res.send('Hello, this is the server!');
});

// 获取所有睡眠记录
app.get('/api/sleep-records', (req, res) => {
res.json(sleepData);
});

// 保存新的睡眠记录
app.post('/api/sleep-records', (req, res) => {
const { date, week, sleepTime, wakeTime } = req.body;
const duration = calculateSleepDuration(sleepTime, wakeTime);
const newRecord = { date, week, sleepTime, wakeTime, sleepDuration: duration }; // 确保字段名一致
sleepData.push(newRecord);
res.status(201).json(newRecord);
});

// 计算睡眠时长(小时)
function calculateSleepDuration(sleepTime, wakeTime) {
const sleep = new Date(`1970-01-01T${sleepTime}:00`); // 将时间转为 Date 对象
const wake = new Date(`1970-01-01T${wakeTime}:00`);
let duration = (wake - sleep) / (1000 * 60 * 60); // 计算小时

if (duration < 0) duration += 24; // 处理跨越午夜的情况
return parseFloat(duration.toFixed(2)); // 保持为数字
}

// 启动服务
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});

3、可以开始运行了

前端是:

1
npm run serve

后端是:

1
node server.js

4、在运行过程中可能会发现,一个情况就是当前端后端重启以后数据就会消失,对于这种情况可以考虑引入SQLite 数据库

运行以下代码:

1
npm install sqlite3

修改后的完整前端代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
<template>
<div class="container">
<h1>睡眠记录</h1>
<!-- 输入表单 -->
<form @submit.prevent="saveRecord">
<div class="grid-container">
<!-- 第一行:日期和星期 -->
<div class="input-box">
<label>日期:</label>
<datepicker v-model="newRecord.date" format="yyyy-MM-dd" required class="datepicker-input" />
</div>
<div class="input-box">
<label>星期:</label>
<!-- 下拉框选择星期 -->
<select v-model="newRecord.week" required>
<option disabled value="">请选择星期</option>
<option v-for="(day, index) in weekdays" :key="index" :value="day">{{ day }}</option>
</select>
</div>

<!-- 第二行:入睡时间和苏醒时间 -->
<div class="input-box">
<label>入睡时间:</label>
<input v-model="newRecord.sleepTime" type="time" required />
</div>
<div class="input-box">
<label>苏醒时间:</label>
<input v-model="newRecord.wakeTime" type="time" required />
</div>
</div>
<button type="submit">保存</button>
</form>

<h2>睡眠时长记录表</h2>
<div id="app">
<table>
<thead>
<tr>
<th>日期</th>
<th>星期</th>
<th>入睡时间</th>
<th>苏醒时间</th>
<th>睡眠时长</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(record, index) in sleepRecords" :key="index">
<td class="date-column">{{ formatDate(record.date) }}</td>
<td>{{ record.week }}</td>
<td>{{ record.sleepTime }}</td>
<td>{{ record.wakeTime }}</td>
<td class="duration-column">{{ record.sleepDuration }}</td>
<td>
<button @click="deleteRecord(index)">删除</button>
</td>
</tr>
</tbody>
</table>
</div>

<!-- 睡眠统计表 -->
<h2>睡眠时长统计表</h2>
<table border="1">
<thead>
<tr>
<th>每日睡眠平均时长 (小时)</th>
<th>睡眠总时间 (小时)</th>
<th>持续总天数</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{ averageSleepDuration }}</td>
<td>{{ totalSleepDuration }}</td>
<td>{{ totalDays }}</td>
</tr>
</tbody>
</table>
</div>
</template>

<script>
import axios from 'axios';
import Datepicker from 'vue3-datepicker'; // 导入日期选择器

export default {
components: {
Datepicker, // 注册组件
},
data() {
return {
weekdays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
newRecord: { date: '', week: '', sleepTime: '', wakeTime: '', sleepDuration: 0 },
sleepRecords: [],
};
},
computed: {
// 计算每日睡眠平均时长 (小时)
averageSleepDuration() {
if (this.sleepRecords.length === 0) return 0;
const totalDuration = this.sleepRecords.reduce((total, record) => total + parseFloat(record.sleepDuration), 0);
return (totalDuration / this.sleepRecords.length).toFixed(2);
},
// 计算睡眠总时间 (小时)
totalSleepDuration() {
const totalDuration = this.sleepRecords.reduce((total, record) => {
return total + (record.sleepDuration ? parseFloat(record.sleepDuration) : 0); // 确保记录存在有效的 sleepDuration
}, 0);
return totalDuration.toFixed(2); // 返回有效的数值,保留两位小数
},
// 计算持续总天数
totalDays() {
return this.sleepRecords.length;
},
},

methods: {
async deleteRecord(index) {
if (!confirm("确定要删除这条记录吗?")) return;

// 获取要删除记录的日期(或者其他唯一标识符)
const recordToDelete = this.sleepRecords[index];

try {
// 向后端发送删除请求
const response = await axios.delete(`http://localhost:3000/api/sleep-records/${recordToDelete.date}`);

// 如果后端删除成功,移除前端数据
if (response.status === 200) {
this.sleepRecords.splice(index, 1);
} else {
console.error('删除失败:', response.data.message);
}
} catch (error) {
console.error('Error deleting record:', error);
}
},

async saveRecord() {
// 自动计算星期
const weekday = this.getWeekday(this.newRecord.date);
// 填充星期
this.newRecord.week = weekday;

// 计算睡眠时长
const sleepDuration = this.calculateSleepDuration(this.newRecord.sleepTime, this.newRecord.wakeTime);
this.newRecord.sleepDuration = sleepDuration;

try {
const response = await axios.post('http://localhost:3000/api/sleep-records', this.newRecord);
this.sleepRecords.push(response.data);
this.newRecord = { date: '', week: '', sleepTime: '', wakeTime: '', sleepDuration: 0 }; // 重置表单
} catch (error) {
console.error('Error saving record:', error);
}
},

// 计算星期
getWeekday(date) {
const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
const d = new Date(date);
return days[d.getDay()]; // 返回星期几
},

// 计算睡眠时长
calculateSleepDuration(sleepTime, wakeTime) {
const sleep = this.timeToMinutes(sleepTime);
const wake = this.timeToMinutes(wakeTime);
let duration = wake - sleep;

// 如果苏醒时间小于入睡时间,表示跨越了午夜,进行调整
if (duration < 0) {
duration += 24 * 60; // 加上24小时的分钟数
}

return (duration / 60).toFixed(2); // 返回小时数,保留两位小数
},

// 将时间(HH:MM)转换为分钟数
timeToMinutes(time) {
const [hours, minutes] = time.split(':').map(num => parseInt(num, 10));
return hours * 60 + minutes;
},

// 格式化日期为 YYYY年MM月DD日
formatDate(date) {
const d = new Date(date);
const year = d.getFullYear();
const month = (d.getMonth() + 1).toString().padStart(2, '0');
const day = d.getDate().toString().padStart(2, '0');
return `${year}${month}${day}日`;
},
},

async mounted() {
try {
const response = await axios.get('http://localhost:3000/api/sleep-records');
this.sleepRecords = response.data.map(record => {
record.sleepDuration = this.calculateSleepDuration(record.sleepTime, record.wakeTime);
return record;
});
} catch (error) {
console.error('Error fetching records:', error);
}
},
};
</script>

<style scoped>
/* 设置皮卡丘背景图 */
.container {
background-image: url('https://p.sda1.dev/21/f49fe2aab1553c84f68627b04432d22b/toy-3633751.jpg'); /* 替换为皮卡丘图片链接 */
background-size: cover;
background-position: center;
background-repeat: no-repeat; /* 避免重复 */
min-height: 100vh;
color: #333;
font-family: Arial, sans-serif;
padding: 20px;
position: relative; /* 确保元素定位不受影响 */
}

h1 {
color: #fff;
}

form {
display: flex;
flex-direction: column;
margin: 20px;
padding: 20px;
background-color: rgba(255, 255, 255, 0.8); /* 半透明背景 */
border-radius: 5px;
}

table {
width: 100%;
margin-top: 20px;
border-collapse: collapse;
text-align: center;
background: linear-gradient(45deg, #f4f4f4, #e0e0e0);
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}


/* 保持现有的样式 */
:deep(.datepicker-input),
:deep(.vue3-datepicker__input) {
padding: 10px !important;
margin-bottom: 10px !important;
border-radius: 5px !important;
border: 1px solid #ccc !important;
font-size: 16px !important;
width: 100% !important;
box-sizing: border-box !important;
}

:deep(.vue3-datepicker-wrapper) {
width: 100% !important;
display: block !important;
}

:deep(.vue3-datepicker) {
font-size: 16px !important;
}

.input-box input, .datepicker-input, select {
padding: 10px;
margin-bottom: 10px;
border-radius: 5px;
border: 1px solid #ccc;
font-size: 16px;
width: 100%;
box-sizing: border-box;
}

h1 {
color: #4CAF50;
text-align: center;
}

form {
display: flex;
flex-direction: column;
margin: 20px;
padding: 20px;
background-color: rgba(255, 255, 255, 0.8); /* 半透明背景 */
border-radius: 5px;
}

.grid-container {
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 20px;
margin-bottom: 20px;
}

.input-box {
display: flex;
flex-direction: column;
}

label {
margin-bottom: 8px;
font-weight: bold;
}

button {
background-color: #4CAF50;
color: white;
padding: 10px;
border: none;
border-radius: 5px;
cursor: pointer;
}

button:hover {
background-color: #45a049;
}

h2 {
text-align: center;
}

ul {
list-style: none;
padding: 0;
}

li {
background-color: #f9f9f9;
margin: 5px 0;
padding: 10px;
border-radius: 5px;
}

table {
width: 100%;
margin-top: 20px;
border-collapse: collapse;
text-align: center;
background: linear-gradient(45deg, #f4f4f4, #e0e0e0);
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}

table th,
table td {
padding: 12px 20px;
border: 1px solid #ccc;
text-align: center;
font-size: 16px;
border-radius: 8px;
}

table th {
background-color: #4CAF50;
color: white;
}

table td {
background-color: #fff;
color: #333;
}

table tr:hover td {
background-color: #f1f1f1;
}

table th, table td {
transition: background-color 0.3s ease;
}

table .date-column {
font-weight: bold;
}

table .duration-column {
color: #4CAF50;
font-weight: bold;
}
</style>

完整的后端代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
const express = require('express');
const cors = require('cors');
const sqlite3 = require('sqlite3').verbose();

const app = express();
const port = 3000;

// 允许 CORS 访问
app.use(cors({ origin: 'http://192.168.31.71:8080' }));
app.use(express.json());

// 连接 SQLite 数据库(如果 sleep_data.db 不存在,则自动创建)
const db = new sqlite3.Database('./sleep_data.db', (err) => {
if (err) console.error('❌ SQLite 连接失败:', err.message);
else console.log('✅ 成功连接 SQLite 数据库');
});

// 创建表(如果不存在)
db.run(`CREATE TABLE IF NOT EXISTS sleep_records (
id INTEGER PRIMARY KEY AUTOINCREMENT,
date TEXT,
week TEXT,
sleepTime TEXT,
wakeTime TEXT,
sleepDuration REAL
)`);

// 获取所有睡眠记录
app.get('/api/sleep-records', (req, res) => {
db.all('SELECT * FROM sleep_records', [], (err, rows) => {
if (err) return res.status(500).json({ error: err.message });
res.json(rows);
});
});

// 保存新的睡眠记录
app.post('/api/sleep-records', (req, res) => {
const { date, week, sleepTime, wakeTime } = req.body;
if (!date || !week || !sleepTime || !wakeTime) {
return res.status(400).json({ error: '所有字段都是必填的' });
}

const sleepDuration = calculateSleepDuration(sleepTime, wakeTime);

db.run(
'INSERT INTO sleep_records (date, week, sleepTime, wakeTime, sleepDuration) VALUES (?, ?, ?, ?, ?)',
[date, week, sleepTime, wakeTime, sleepDuration],
function (err) {
if (err) return res.status(500).json({ error: err.message });

res.status(201).json({
id: this.lastID,
date,
week,
sleepTime,
wakeTime,
sleepDuration,
});
}
);
});

// 计算睡眠时长
function calculateSleepDuration(sleepTime, wakeTime) {
const sleep = new Date(`1970-01-01T${sleepTime}:00`);
const wake = new Date(`1970-01-01T${wakeTime}:00`);
let duration = (wake - sleep) / (1000 * 60 * 60); // 计算小时

if (duration < 0) duration += 24; // 处理跨夜情况
return parseFloat(duration.toFixed(2));
}

// 删除指定的睡眠记录
app.delete('/api/sleep-records/:date', (req, res) => {
const date = req.params.date;
console.log('请求删除记录的日期:', date); // 查看是否收到了正确的日期

db.run('DELETE FROM sleep_records WHERE date = ?', [date], function(err) {
if (err) {
console.error('删除错误:', err);
return res.status(500).json({ message: '删除失败', error: err });
}

if (this.changes === 0) {
return res.status(404).json({ message: '未找到记录' });
}

res.status(200).json({ message: '记录已删除' });
});
});

// 启动服务器
app.listen(port, () => {
console.log(`🚀 服务器运行在 http://localhost:${port}`);
});

执行完毕之后就会有一个睡眠记录的系统了,下面就是一些细节的调整,调整的过程中一定要不断停下来看看,避免一头扎进去,忙了很多天还是没有进展,如果突破不了,别怕从头开始。

5、封装和一步启动

创建 start_project.bat 文件,写入如下代码:

1
2
3
4
5
6
@echo off
start cmd /k "cd /d 你的后端完整路径 && node index.js"
start cmd /k "cd /d 你的前端完整路径 && npm run serve"
timeout /t 5 >nul
start "" "你的网址"
exit

至此,大功告成!如果出现一些bug啥的,耐心点儿分析分析,想想办法,别跳起来拍桌子。