File size: 7,271 Bytes
b4edbc0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
(function() {
    'use strict';
    class UserDataSync {
        constructor() {
            this.username = null;
            this.data = {
                favorite_channels: [],
                download_concurrency: 16,
                batch_download_concurrency: 3,
                fab_position: { bottom: 30, right: 30 },
                playback_history: [],
                program_reminders: []
            };
            this.isInitialized = false;
            this.pendingUpdates = {};
            this.saveTimer = null;
        }

        /**

         * 初始化用户数据

         */
        init(username) {
            if (!username) {
                console.warn('⚠️ UserDataSync: 未提供用户名');
                return false;
            }

            this.username = username;
            
            // ✅ 从 sessionStorage 加载(登录时后端已写入)
            const userDataKey = `user_data_${username}`;
            const savedData = sessionStorage.getItem(userDataKey);
            
            if (savedData) {
                try {
                    const parsed = JSON.parse(savedData);
                    this.data = { ...this.data, ...parsed };
                    console.log('✅ 用户数据已加载:', Object.keys(this.data));
                } catch (e) {
                    console.error('❌ 解析用户数据失败:', e);
                }
            }
            
            this.isInitialized = true;
            return true;
        }

        /**

         * 标记数据已修改(防抖保存)

         */
        markChanged(key) {
            if (!this.isInitialized) return;
            
            this.pendingUpdates[key] = this.data[key];
            
            // 防抖:1秒后保存
            if (this.saveTimer) {
                clearTimeout(this.saveTimer);
            }
            
            this.saveTimer = setTimeout(() => {
                this.save();
            }, 1000);
        }

        /**

         * 立即保存到后端

         */
        async save(force = false) {
            if (!this.isInitialized || !this.username) return false;
            if (!force && Object.keys(this.pendingUpdates).length === 0) return true;

            const updates = force ? this.data : this.pendingUpdates;

            try {
                // ✅ 保存到 sessionStorage(同步)
                const userDataKey = `user_data_${this.username}`;
                sessionStorage.setItem(userDataKey, JSON.stringify(this.data));

                // ✅ 通过后端保存到 Redis
                const response = await fetch('/api/user/data/sync', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify({
                        username: this.username,
                        data: updates
                    })
                });

                if (response.ok) {
                    console.log('✅ 用户数据已同步到 Redis:', Object.keys(updates));
                    this.pendingUpdates = {};
                    return true;
                } else {
                    console.warn('⚠️ 同步失败,数据已保存到本地');
                    return false;
                }
            } catch (error) {
                console.error('❌ 同步失败:', error);
                return false;
            }
        }

        // ==================== 便捷方法 ====================

        getFavorites() {
            return this.data.favorite_channels || [];
        }

        setFavorites(favorites) {
            this.data.favorite_channels = Array.isArray(favorites) ? favorites : [];
            this.markChanged('favorite_channels');
        }

        getDownloadConcurrency() {
            return this.data.download_concurrency || 16;
        }

        setDownloadConcurrency(concurrency) {
            const value = parseInt(concurrency);
            if (value >= 1 && value <= 32) {
                this.data.download_concurrency = value;
                this.markChanged('download_concurrency');
            }
        }

        getBatchConcurrency() {
            return this.data.batch_download_concurrency || 3;
        }

        setBatchConcurrency(concurrency) {
            const value = parseInt(concurrency);
            if (value >= 1 && value <= 10) {
                this.data.batch_download_concurrency = value;
                this.markChanged('batch_download_concurrency');
            }
        }

        getFabPosition() {
            return this.data.fab_position || { bottom: 30, right: 30 };
        }

        setFabPosition(position) {
            if (position && typeof position === 'object') {
                this.data.fab_position = position;
                this.markChanged('fab_position');
            }
        }

        getPlaybackHistory() {
            return this.data.playback_history || [];
        }

        addPlaybackHistory(item) {
            if (!Array.isArray(this.data.playback_history)) {
                this.data.playback_history = [];
            }
            
            // 去重
            this.data.playback_history = this.data.playback_history.filter(
                h => h.path !== item.path
            );
            
            // 添加到开头
            this.data.playback_history.unshift(item);
            
            // 最多保留 50 条
            if (this.data.playback_history.length > 50) {
                this.data.playback_history = this.data.playback_history.slice(0, 50);
            }
            
            this.markChanged('playback_history');
        }

        getProgramReminders() {
            return this.data.program_reminders || [];
        }

        addProgramReminder(reminder) {
            if (!Array.isArray(this.data.program_reminders)) {
                this.data.program_reminders = [];
            }
            
            const exists = this.data.program_reminders.some(
                r => r.title === reminder.title && r.startTime === reminder.startTime
            );
            
            if (!exists) {
                this.data.program_reminders.push(reminder);
                this.markChanged('program_reminders');
            }
        }

        removeProgramReminder(reminderId) {
            if (Array.isArray(this.data.program_reminders)) {
                this.data.program_reminders = this.data.program_reminders.filter(
                    r => r.id !== reminderId
                );
                this.markChanged('program_reminders');
            }
        }
    }

    // 创建全局单例
    window.userDataSync = new UserDataSync();

    // 页面卸载时保存
    window.addEventListener('beforeunload', () => {
        if (window.userDataSync && window.userDataSync.isInitialized) {
            window.userDataSync.save(true);
        }
    });

    console.log('✅ UserDataSync 已加载(无需 API)');

})();