diff --git a/api-app.js b/api-app.js index 57d6d6f..c1f1121 100644 --- a/api-app.js +++ b/api-app.js @@ -7,7 +7,7 @@ const path = require('path'); // 创建Express应用 const app = express(); -const port = 3000; +const port = 2342; // 启用CORS和JSON解析 app.use(cors()); @@ -32,7 +32,7 @@ const dbConfig = { host: 'localhost', port: 25342, // 修改为正确的MySQL端口 user: 'hongm', // 替换为您的数据库用户名 - password: 'hongm', // 替换为您的数据库密码 + password: 'JsKJeG7CX2WnyArt', // 替换为您的数据库密码 database: 'hongm', waitForConnections: true, connectionLimit: 10, @@ -249,7 +249,99 @@ app.put('/api/user/:account/email', async (req, res) => { } }); +// API: 用户注册 +app.post('/api/register', async (req, res) => { + try { + const { account, password, nickname, email, phone, photo } = req.body; + logToFile(`收到注册请求: ${JSON.stringify(req.body)}`); + + // 验证必填字段 + if (!account || !password || !email) { + return res.status(400).json({ + success: false, + message: '账号、密码和邮箱为必填项' + }); + } + + // 检查账号是否已存在 + const [existingUser] = await promisePool.query( + 'SELECT account FROM UserText WHERE account = ?', + [account] + ); + + if (existingUser.length > 0) { + logToFile(`注册失败: 账号 ${account} 已存在`); + return res.status(400).json({ + success: false, + message: '账号已存在' + }); + } + + // 检查邮箱是否已存在 + const [existingEmail] = await promisePool.query( + 'SELECT email FROM UserText WHERE email = ?', + [email] + ); + + if (existingEmail.length > 0) { + logToFile(`注册失败: 邮箱 ${email} 已被使用`); + return res.status(400).json({ + success: false, + message: '邮箱已被使用' + }); + } + + // 开始事务 + const connection = await promisePool.getConnection(); + await connection.beginTransaction(); + + try { + // 插入用户基本信息 + await connection.query( + 'INSERT INTO UserText (account, nickname, email, phone, photo, category) VALUES (?, ?, ?, ?, ?, ?)', + [account, nickname || account, email, phone || null, photo || 'http://139.155.155.67:2342/images/default_avatar.png', 'user'] + ); + + // 插入用户密码 + await connection.query( + 'INSERT INTO UserPassword (account, password) VALUES (?, ?)', + [account, password] + ); + + // 提交事务 + await connection.commit(); + logToFile(`注册成功: 账号 ${account}, 昵称 ${nickname || account}`); + + res.json({ + success: true, + message: '注册成功', + user: { + account, + nickname: nickname || account, + email, + phone: phone || '', + photo: photo || 'http://139.155.155.67:2342/images/default_avatar.png', + category: 'user' + } + }); + } catch (error) { + // 回滚事务 + await connection.rollback(); + throw error; + } finally { + connection.release(); + } + } catch (error) { + logToFile(`注册错误: ${error.message}`); + res.status(500).json({ + success: false, + message: '注册失败,请稍后重试', + error: error.message + }); + } +}); + // 启动服务器 -app.listen(port, () => { +app.listen(port, '0.0.0.0', () => { logToFile(`API服务运行在端口 ${port}`); -}); \ No newline at end of file +}); \ No newline at end of file diff --git a/entry/src/main/ets/common/App.ets b/entry/src/main/ets/common/App.ets new file mode 100644 index 0000000..cfc63be --- /dev/null +++ b/entry/src/main/ets/common/App.ets @@ -0,0 +1,11 @@ +/** + * Copyright (c) 2025 TDCAT.CN + * Author: CC + */ + +// 初始化应用 +export function initApp() { + // Don't register routes during initialization + // This should be handled by the UI components + console.info('App initialized'); +} \ No newline at end of file diff --git a/entry/src/main/ets/common/ClassRoomService.ets b/entry/src/main/ets/common/ClassRoomService.ets index 24f3e25..dcf2805 100644 --- a/entry/src/main/ets/common/ClassRoomService.ets +++ b/entry/src/main/ets/common/ClassRoomService.ets @@ -1,3 +1,8 @@ +/** + * Copyright (c) 2025 TDCAT.CN + * Author: CC + */ + import { hilog } from '@kit.PerformanceAnalysisKit'; import logManager, { LogCategory, LogEventType } from './logtext'; import settingsService from './SettingsService'; diff --git a/entry/src/main/ets/common/DatabaseService.ets b/entry/src/main/ets/common/DatabaseService.ets index 0c52da1..25fd03f 100644 --- a/entry/src/main/ets/common/DatabaseService.ets +++ b/entry/src/main/ets/common/DatabaseService.ets @@ -1,3 +1,8 @@ +/** + * Copyright (c) 2025 TDCAT.CN + * Author: CC + */ + import { hilog } from '@kit.PerformanceAnalysisKit'; import prompt from '@ohos.promptAction'; import http from '@ohos.net.http'; @@ -59,6 +64,16 @@ const API_CONFIG: ApiConfig = { // 修正JSON响应类型处理 export type JsonResponse = string | object | ArrayBuffer; +// 注册用户数据接口 +export interface RegisterUserData { + account: string; + password: string; + nickname: string; + email: string; + phone?: string; + photo?: string; +} + // 真实API实现 - 连接到后端服务 export class DatabaseService { private static instance: DatabaseService; @@ -165,10 +180,10 @@ export class DatabaseService { hilog.error(0, 'ClassMG', `Error validating user: ${error instanceof Error ? error.message : String(error)}`); // 临时降级到模拟验证 - 仅用于开发/测试 - if (account === '2' || account === '9222' || account === '0') { +/* if (account === '2' || account === '9222' || account === '0') { return password === '1'; } - + */ return false; } } @@ -182,11 +197,11 @@ export class DatabaseService { } // 降级逻辑 - 当API不可用时 - if (account.includes('2')) { +/* if (account.includes('2')) { return 'student'; } else if (account.includes('0')) { return 'teacher'; - } + }*/ return ''; } @@ -200,9 +215,9 @@ export class DatabaseService { } // 默认值 - 仅用于开发/测试 - if (account === '2') return '张三'; +/* if (account === '2') return '张三'; if (account === '9222') return '李华'; - if (account === '0') return '教师demo'; + if (account === '0') return '教师demo';*/ return ''; } @@ -265,13 +280,13 @@ export class DatabaseService { hilog.error(0, 'ClassMG', `Error getting user: ${error instanceof Error ? error.message : String(error)}`); // 降级到模拟数据 - 仅用于开发/测试 - if (account === '2') { +/* if (account === '2') { return new UserModel('2', '张三', 'student@qq.com', '17267383831', '', 'student'); } else if (account === '9222') { return new UserModel('9222', '李华', 'student123@qq.com', '12345678901', '', 'student'); } else if (account === '0') { return new UserModel('0', '教师demo', 'teach@qq.com', '', '', 'teacher'); - } + }*/ return null; } @@ -285,16 +300,7 @@ export class DatabaseService { return cachedUser || null; } - // 如果缓存中没有,返回默认模拟数据 - if (account === '2') { - return new UserModel('2', '张三', 'student@qq.com', '17267383831', '', 'student'); - } else if (account === '9222') { - return new UserModel('9222', '李华', 'student123@qq.com', '12345678901', '', 'student'); - } else if (account === '0') { - return new UserModel('0', '教师demo', 'teach@qq.com', '', '', 'teacher'); - } - - // 触发异步加载并返回null + // 如果缓存中没有,返回null并触发异步加载 this.getUserByAccountAsync(account) .then((user: UserModel | null) => { if (user) { @@ -412,4 +418,47 @@ export class DatabaseService { this.userCache.clear(); this.authCache.clear(); } + + // 注册用户 + public async registerUser(userData: RegisterUserData): Promise { + try { + // 创建HTTP客户端 + const httpRequest = this.createHttpClient(); + + // 发送注册请求 + const response = await httpRequest.request( + `${API_CONFIG.baseUrl}/register`, + { + method: http.RequestMethod.POST, + header: { + 'Content-Type': 'application/json' + }, + extraData: JSON.stringify(userData), + connectTimeout: API_CONFIG.timeout, + readTimeout: API_CONFIG.timeout + } + ); + + // 释放HTTP客户端 + httpRequest.destroy(); + + // 检查响应 + if (response.responseCode === 200) { + // 使用显式类型转换处理JSON结果 + const jsonString: string = response.result ? response.result.toString() : '{}'; + try { + const result: ApiResponse = JSON.parse(jsonString) as ApiResponse; + return result.success === true; + } catch (parseError) { + const errorMsg: string = parseError instanceof Error ? parseError.message : String(parseError); + hilog.error(0, 'ClassMG', `Error parsing JSON response: ${errorMsg}`); + } + } + + return false; + } catch (error) { + hilog.error(0, 'ClassMG', `Error registering user: ${error instanceof Error ? error.message : String(error)}`); + return false; + } + } } \ No newline at end of file diff --git a/entry/src/main/ets/common/SettingsService.ets b/entry/src/main/ets/common/SettingsService.ets index 1894981..3d5aec7 100644 --- a/entry/src/main/ets/common/SettingsService.ets +++ b/entry/src/main/ets/common/SettingsService.ets @@ -1,3 +1,9 @@ +/** + * Copyright (c) 2025 TDCAT.CN + * + + */ + // 导入日志管理器 - 仅用于获取版本号 import logManager, { LogCategory, LogEventType, VersionLogItem } from './logtext'; import { UserModel, DatabaseService } from './DatabaseService'; @@ -442,9 +448,9 @@ export class SettingsService { // 降级为同步模式 - 仅用于紧急情况 try { // 进行最简单的验证 - if (account === '2' || account === '9222' || account === '0') { +/* if (account === '2' || account === '9222' || account === '0') { return password === '1'; - } + }*/ return false; } catch (error) { hilog.error(0, 'ClassMG', `Error in legacy validateUser: ${error}`); diff --git a/entry/src/main/ets/common/logtext.ets b/entry/src/main/ets/common/logtext.ets index a836106..b840435 100644 --- a/entry/src/main/ets/common/logtext.ets +++ b/entry/src/main/ets/common/logtext.ets @@ -1,3 +1,8 @@ +/** + * Copyright (c) 2025 TDCAT.CN + * Author: CC + */ + import { hilog } from '@kit.PerformanceAnalysisKit'; // 日志域 - 应用程序的唯一标识符 @@ -43,7 +48,9 @@ export enum LogEventType { PAGE_BACK = 'PAGE_BACK', SYSTEM_INFO = 'SYSTEM_INFO', USER_LOGIN = 'USER_LOGIN', - USER_LOGIN_FAIL = 'USER_LOGIN_FAIL' + USER_LOGIN_FAIL = 'USER_LOGIN_FAIL', + USER_REGISTER = 'USER_REGISTER', + USER_REGISTER_FAIL = 'USER_REGISTER_FAIL' } /** diff --git a/entry/src/main/ets/entryability/EntryAbility.ets b/entry/src/main/ets/entryability/EntryAbility.ets index 0e839d2..78e15bf 100644 --- a/entry/src/main/ets/entryability/EntryAbility.ets +++ b/entry/src/main/ets/entryability/EntryAbility.ets @@ -1,21 +1,59 @@ import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit'; import { hilog } from '@kit.PerformanceAnalysisKit'; import { window } from '@kit.ArkUI'; +import { initApp } from '../common/App'; const DOMAIN = 0x0000; +let copyrightDisplayed = false; export default class EntryAbility extends UIAbility { onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET); + + this.displayCopyright(); + + // 初始化应用,注册路由 + initApp(); + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate'); } + private displayCopyright(): void { + if (copyrightDisplayed) return; + + const asciiArt = ` + ███████╗███╗ ███╗ █████╗ ██████╗ ███████╗██████╗ ██████╗ ██╗ ██╗ + ██╔════╝████╗ ████║██╔══██╗██╔══██╗██╔════╝██╔══██╗ ██╔══██╗██║ ██╔╝ + ███████╗██╔████╔██║███████║██████╔╝█████╗ ██████╔╝ ██████╔╝█████╔╝ + ╚════██║██║╚██╔╝██║██╔══██║██╔══██╗██╔══╝ ██╔══██╗ ██╔══██╗██╔═██╗ + ███████║██║ ╚═╝ ██║██║ ██║██║ ██║███████╗██║ ██║ ██║ ██║██║ ██╗ + ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ + `; + + console.info('=========================================='); + console.info(asciiArt); + console.info('Copyright (c) 2025 TDCAT.CN'); + console.info('Author: CC'); + console.info('=========================================='); + + hilog.info(DOMAIN, 'COPYRIGHT', '=========================================='); + hilog.info(DOMAIN, 'COPYRIGHT', 'Copyright (c) 2025 TDCAT.CN'); + hilog.info(DOMAIN, 'COPYRIGHT', 'Author: CC'); + hilog.info(DOMAIN, 'COPYRIGHT', '=========================================='); + + hilog.info(DOMAIN, 'COPYRIGHT', '%{public}s', '=========================================='); + hilog.info(DOMAIN, 'COPYRIGHT', '%{public}s', 'Copyright (c) 2025 TDCAT.CN'); + hilog.info(DOMAIN, 'COPYRIGHT', '%{public}s', 'Author: CC'); + hilog.info(DOMAIN, 'COPYRIGHT', '%{public}s', '=========================================='); + + copyrightDisplayed = true; + } + onDestroy(): void { hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onDestroy'); } onWindowStageCreate(windowStage: window.WindowStage): void { - // Main window is created, set main page for this ability hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate'); windowStage.loadContent('pages/WelcomePage', (err) => { @@ -28,17 +66,14 @@ export default class EntryAbility extends UIAbility { } onWindowStageDestroy(): void { - // Main window is destroyed, release UI related resources hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageDestroy'); } onForeground(): void { - // Ability has brought to foreground hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onForeground'); } onBackground(): void { - // Ability has back to background hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onBackground'); } } \ No newline at end of file diff --git a/entry/src/main/ets/pages/AccountPage.ets b/entry/src/main/ets/pages/AccountPage.ets new file mode 100644 index 0000000..705c3a3 --- /dev/null +++ b/entry/src/main/ets/pages/AccountPage.ets @@ -0,0 +1,230 @@ +/** + * Copyright (c) 2025 TDCAT.CN + * Author: CC + */ + +import { router } from '@kit.ArkUI'; +import settingsService, { SettingsModel, TextResources } from '../common/SettingsService'; +import logManager, { LogCategory, LogEventType } from '../common/logtext'; + +@Entry +@Component +struct AccountPage { + @State settings: SettingsModel = settingsService.getSettings(); + @State texts: TextResources = settingsService.getTextResources(); + @State userName: string = settingsService.getCurrentAccount() || "用户"; + @State userEmail: string = "未设置邮箱"; + + aboutToAppear() { + // 注册语言变化的回调 + settingsService.registerLanguageChangeCallback(() => { + this.texts = settingsService.getTextResources(); + }); + + // 获取用户名 + this.userName = settingsService.getCurrentAccount() || "用户"; + + this.Log_Event('aboutToAppear'); + } + + build() { + Column() { + // 顶部导航栏 + Row() { + Text("我的账户").fontSize(22).fontColor(Color.White).fontWeight(FontWeight.Bold) + } + .width('100%') + .backgroundColor(this.settings.themeColor) + .height(60) + .justifyContent(FlexAlign.Center) + .padding({ left: 20 }) + + // 页面内容区 + Scroll() { + Column() { + // 用户基本信息 + Column() { + Image($r('app.media.tdcat')) + .width(80) + .height(80) + .borderRadius(40) + .margin({ top: 20, bottom: 10 }) + + Text(this.userName) + .fontSize(20) + .fontWeight(FontWeight.Bold) + .margin({ bottom: 8 }) + + Text(this.userEmail) + .fontSize(14) + .fontColor('#666666') + .margin({ bottom: 20 }) + } + .width('100%') + .alignItems(HorizontalAlign.Center) + + // 账户选项列表 + Column() { + // 账户信息 + this.AccountListItem("账户信息", "查看和编辑您的个人资料") + + // 安全设置 + this.AccountListItem("安全设置", "修改密码和安全选项") + + // 通知管理 + this.AccountListItem("通知管理", "管理您收到的通知") + + // 隐私设置 + this.AccountListItem("隐私设置", "管理您的隐私选项") + + // 退出登录 + Button("退出登录") + .width('90%') + .height(50) + .fontSize(16) + .backgroundColor('#FF5A5A') + .fontColor(Color.White) + .margin({ top: 40, bottom: 20 }) + .onClick(() => { + this.logout(); + }) + } + .width('100%') + .padding({ top: 20 }) + .backgroundColor('#FFFFFF') + .borderRadius(16) + .margin({ top: 20 }) + .alignItems(HorizontalAlign.Center) + } + .width('100%') + .padding({ left: 16, right: 16 }) + } + .layoutWeight(1) + .scrollBar(BarState.Off) + + // 底部导航栏 + this.BottomNavigationBar('account') + } + .width('100%') + .height('100%') + .backgroundColor('#F5F7FA') + } + + @Builder + AccountListItem(title: string, description: string) { + Row() { + Column() { + Text(title) + .fontSize(16) + .fontWeight(FontWeight.Medium) + + Text(description) + .fontSize(12) + .fontColor('#999999') + .margin({ top: 4 }) + } + .alignItems(HorizontalAlign.Start) + .layoutWeight(1) + + Image($r('app.media.arrow_right')) + .width(20) + .height(20) + .margin({ right: 10 }) + } + .width('90%') + .padding({ top: 16, bottom: 16 }) + .borderRadius(8) + .backgroundColor('#FFFFFF') + .margin({ bottom: 12 }) + } + + @Builder + BottomNavigationBar(activePage: string) { + Row() { + // 首页 + Column() { + Image($r('app.media.home')) + .width(24) + .height(24) + .fillColor(activePage === 'home' ? this.settings.themeColor : '#999999') + Text("首页") + .fontSize(12) + .fontColor(activePage === 'home' ? this.settings.themeColor : '#999999') + .margin({ top: 4 }) + } + .layoutWeight(1) + .onClick(() => { + router.pushUrl({ url: 'pages/HomePage' }); + }) + + // 课程 + Column() { + Image($r('app.media.class')) + .width(24) + .height(24) + .fillColor(activePage === 'class' ? this.settings.themeColor : '#999999') + Text("课程") + .fontSize(12) + .fontColor(activePage === 'class' ? this.settings.themeColor : '#999999') + .margin({ top: 4 }) + } + .layoutWeight(1) + .onClick(() => { + router.pushUrl({ url: 'pages/ClassPage' }); + }) + + // 账户 + Column() { + Image($r('app.media.info')) + .width(24) + .height(24) + .fillColor(activePage === 'account' ? this.settings.themeColor : '#999999') + Text("账户") + .fontSize(12) + .fontColor(activePage === 'account' ? this.settings.themeColor : '#999999') + .margin({ top: 4 }) + } + .layoutWeight(1) + + // 设置 + Column() { + Image($r('app.media.settings')) + .width(24) + .height(24) + .fillColor(activePage === 'settings' ? this.settings.themeColor : '#999999') + Text("设置") + .fontSize(12) + .fontColor(activePage === 'settings' ? this.settings.themeColor : '#999999') + .margin({ top: 4 }) + } + .layoutWeight(1) + .onClick(() => { + router.pushUrl({ url: 'pages/SettingsPage' }); + }) + } + .width('100%') + .height(60) + .backgroundColor(Color.White) + .padding({ top: 8, bottom: 8 }) + .justifyContent(FlexAlign.SpaceAround) + .alignItems(VerticalAlign.Center) + } + + // 退出登录方法 + private logout() { + settingsService.setCurrentAccount(""); // Clear the account by setting it empty + logManager.info(LogCategory.USER, LogEventType.SYSTEM_INFO, "User logged out"); + + // 跳转到登录页 + router.replaceUrl({ + url: 'pages/login' + }); + } + + // 日志记录方法 + private Log_Event(eventName: string): void { + logManager.info(LogCategory.HOME, LogEventType.PAGE_APPEAR, `AccountPage: ${eventName}`); + } +} + +export { AccountPage } \ No newline at end of file diff --git a/entry/src/main/ets/pages/ClassLivePage.ets b/entry/src/main/ets/pages/ClassLivePage.ets index ce346cd..c6921c5 100644 --- a/entry/src/main/ets/pages/ClassLivePage.ets +++ b/entry/src/main/ets/pages/ClassLivePage.ets @@ -1,3 +1,8 @@ +/** + * Copyright (c) 2025 TDCAT.CN + * Author: CC + */ + import { router } from '@kit.ArkUI'; import promptAction from '@ohos.promptAction'; import settingsService, { SettingsModel, TextResources } from '../common/SettingsService'; diff --git a/entry/src/main/ets/pages/ClassPage.ets b/entry/src/main/ets/pages/ClassPage.ets index 2f92d9c..c4ff5eb 100644 --- a/entry/src/main/ets/pages/ClassPage.ets +++ b/entry/src/main/ets/pages/ClassPage.ets @@ -1,3 +1,8 @@ +/** + * Copyright (c) 2025 TDCAT.CN + * Author: CC + */ + import { router } from '@kit.ArkUI'; import { hilog } from '@kit.PerformanceAnalysisKit'; import { MonitorDataType } from './HomePage'; @@ -889,4 +894,6 @@ struct BottomNavigation { } } +export { ClassPage } + diff --git a/entry/src/main/ets/pages/HomePage.ets b/entry/src/main/ets/pages/HomePage.ets index f7b2e7c..c6c40cd 100644 --- a/entry/src/main/ets/pages/HomePage.ets +++ b/entry/src/main/ets/pages/HomePage.ets @@ -1,3 +1,8 @@ +/** + * Copyright (c) 2025 TDCAT.CN + * Author: CC + */ + import { router } from '@kit.ArkUI'; import logManager, { LogCategory, LogEventType } from '../common/logtext'; import settingsService, { SettingsModel, TextResources } from '../common/SettingsService'; @@ -658,4 +663,6 @@ struct CameraDialogBuilder { .borderRadius(16) .padding(16) } -} \ No newline at end of file +} + +export { HomePage } \ No newline at end of file diff --git a/entry/src/main/ets/pages/Index.ets b/entry/src/main/ets/pages/Index.ets new file mode 100644 index 0000000..9e3e0e4 --- /dev/null +++ b/entry/src/main/ets/pages/Index.ets @@ -0,0 +1,31 @@ +import router from '@ohos.router'; + +@Entry +@Component +struct Index { + build() { + Column() { + Text('Welcome to ClassMG') + .fontSize(24) + .fontWeight(FontWeight.Bold) + .margin({ bottom: 20 }) + + Text('This is the default index page') + .fontSize(16) + .margin({ bottom: 40 }) + + Button('Go to Login') + .onClick(() => { + router.pushUrl({ url: 'pages/login' }); + }) + } + .width('100%') + .height('100%') + .justifyContent(FlexAlign.Center) + .alignItems(HorizontalAlign.Center) + } +} + +export default function DefaultIndex() { + return; +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/SettingsPage.ets b/entry/src/main/ets/pages/SettingsPage.ets index 0664abf..18c9622 100644 --- a/entry/src/main/ets/pages/SettingsPage.ets +++ b/entry/src/main/ets/pages/SettingsPage.ets @@ -1,3 +1,8 @@ +/** + * Copyright (c) 2025 TDCAT.CN + * Author: CC + */ + import { router } from '@kit.ArkUI'; import { hilog } from '@kit.PerformanceAnalysisKit'; import settingsService, { ThemeColor, Language, SettingsModel, TextResources, UserInfoModel } from '../common/SettingsService'; @@ -177,6 +182,8 @@ struct SettingsPage { } } +export { SettingsPage } + // 用户信息卡片组件 - 美化版 @Component struct UserCard { diff --git a/entry/src/main/ets/pages/StartPage.ets b/entry/src/main/ets/pages/StartPage.ets new file mode 100644 index 0000000..f2e4cc2 --- /dev/null +++ b/entry/src/main/ets/pages/StartPage.ets @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2025 TDCAT.CN + * Author: CC + */ + +import { router } from '@kit.ArkUI'; +import settingsService from '../common/SettingsService'; + +@Entry +@Component +struct StartPage { + @State isLoggedIn: boolean = false; + + aboutToAppear() { + // Check if user is logged in + const currentAccount = settingsService.getCurrentAccount(); + this.isLoggedIn = !!currentAccount; + + // Auto navigate after checking login status + setTimeout(() => { + if (this.isLoggedIn) { + router.replaceUrl({ + url: 'pages/HomePage' + }); + } else { + router.replaceUrl({ + url: 'pages/login' + }); + } + }, 500); + } + + build() { + Column() { + Image($r('app.media.tdcat')) + .width(150) + .height(150) + .margin({ bottom: 20 }) + Text('智能教室管理系统') + .fontSize(24) + .fontWeight(FontWeight.Bold) + .margin({ bottom: 40 }) + LoadingProgress() + .width(50) + .height(50) + .color('#2196F3') + } + .width('100%') + .height('100%') + .backgroundColor('#FFFFFF') + .justifyContent(FlexAlign.Center) + .alignItems(HorizontalAlign.Center) + } +} + +export { StartPage } \ No newline at end of file diff --git a/entry/src/main/ets/pages/TransitionPage.ets b/entry/src/main/ets/pages/TransitionPage.ets index 0300c37..14c9d0e 100644 --- a/entry/src/main/ets/pages/TransitionPage.ets +++ b/entry/src/main/ets/pages/TransitionPage.ets @@ -1,3 +1,8 @@ +/** + * Copyright (c) 2025 TDCAT.CN + * Author: CC + */ + import { router } from '@kit.ArkUI'; import settingsService from '../common/SettingsService'; import logManager, { LogCategory, LogEventType } from '../common/logtext'; diff --git a/entry/src/main/ets/pages/WelcomePage.ets b/entry/src/main/ets/pages/WelcomePage.ets index d7cc9a0..14414ee 100644 --- a/entry/src/main/ets/pages/WelcomePage.ets +++ b/entry/src/main/ets/pages/WelcomePage.ets @@ -1,3 +1,8 @@ +/** + * Copyright (c) 2025 TDCAT.CN + * Author: CC + */ + import { router } from '@kit.ArkUI'; import logManager, { LogCategory, LogEventType } from '../common/logtext'; diff --git a/entry/src/main/ets/pages/agreements/DataProcessingStatement.ets b/entry/src/main/ets/pages/agreements/DataProcessingStatement.ets new file mode 100644 index 0000000..18d58eb --- /dev/null +++ b/entry/src/main/ets/pages/agreements/DataProcessingStatement.ets @@ -0,0 +1,185 @@ +/** + * Copyright (c) 2025 TDCAT.CN + * Author: CC + */ + +import { router } from '@kit.ArkUI'; + +@Entry +@Component +struct DataProcessingStatement { + @State title: string = '数据处理声明'; + + build() { + Column() { + // 顶部栏 + Row() { + Image($r('app.media.back')) + .width(24) + .height(24) + .margin({ left: 16 }) + .onClick(() => { + router.back(); + }) + + Text(this.title) + .fontSize(18) + .fontWeight(FontWeight.Medium) + .margin({ left: 16 }) + + Blank() + } + .width('100%') + .height(56) + .backgroundColor('#FFFFFF') + .alignItems(VerticalAlign.Center) + + // 内容区域 + Scroll() { + Column() { + Text('智能教室管理系统数据处理声明') + .fontSize(20) + .fontWeight(FontWeight.Bold) + .margin({ top: 20, bottom: 20 }) + .textAlign(TextAlign.Center) + .width('100%') + + Text('最近更新日期:2023年12月1日\n文件编号:CMSDP-2023-001\n生效日期:2023年12月15日') + .fontSize(14) + .fontColor('#666666') + .margin({ bottom: 20 }) + .width('100%') + .textAlign(TextAlign.Center) + + Text('总则') + .fontSize(16) + .fontWeight(FontWeight.Bold) + .margin({ bottom: 10 }) + + Text('本数据处理声明(以下简称"本声明")阐述了数据猫(以下简称"我们")作为数据控制者和/或数据处理者如何处理通过智能教室管理系统(以下简称"本系统")收集的个人数据以及与数据处理相关的权利和责任。本声明是我们《用户服务协议》和《隐私政策》的补充文件,应当与前述文件一并阅读和理解。\n\n本声明基于《中华人民共和国个人信息保护法》、《中华人民共和国数据安全法》以及其他适用的法律法规制定。我们承诺以负责任、透明的方式处理您的个人数据,并尊重您的隐私权。') + .fontSize(14) + .margin({ bottom: 20 }) + .textAlign(TextAlign.Start) + + Text('第一条 定义') + .fontSize(16) + .fontWeight(FontWeight.Bold) + .margin({ bottom: 10 }) + + Text('在本声明中,除非另有规定,下列术语具有以下含义:\n\n1.1 "个人数据"或"个人信息":指与已识别或可识别的自然人("数据主体")相关的任何信息。可识别的自然人是指可以直接或间接地识别的人,特别是通过诸如姓名、身份证号码、位置数据、网上标识符或者一个或多个特定于该自然人的物理、生理、遗传、心理、经济、文化或社会身份的因素。\n\n1.2 "数据控制者":指单独或与他人共同决定处理个人数据的目的和方法的自然人或法人、公共机构、代理机构或其他团体。\n\n1.3 "数据处理者":指代表数据控制者处理个人数据的自然人或法人、公共机构、代理机构或其他团体。\n\n1.4 "处理":指对个人数据进行的任何操作或一系列操作,无论是否通过自动化手段,如收集、记录、组织、构造、存储、改编或修改、检索、咨询、使用、通过传输、传播或其他方式提供、对齐或组合、限制、删除或销毁。\n\n1.5 "数据主体":指个人数据所关联的自然人。') + .fontSize(14) + .margin({ bottom: 20 }) + .textAlign(TextAlign.Start) + + Text('第二条 数据控制者和处理者') + .fontSize(16) + .fontWeight(FontWeight.Bold) + .margin({ bottom: 10 }) + + Text('2.1 数据控制者\n\n公司名称:数据猫\n注册地址:中国北京市海淀区科学园路XX号\n企业统一社会信用代码:XXXXXXXXXXXXXXXXXXXX\n负责人:数据保护官\n联系电话:400-123-4567\n电子邮件:support@tdcat.cn\n\n2.2 数据处理者\n\n在某些情况下,我们会委托第三方服务提供商作为数据处理者处理部分个人数据。所有第三方处理者都必须遵守我们的数据保护标准和安全要求,并且仅能根据我们明确的书面指示处理个人数据。我们仅选择能够提供足够保证的数据处理者,这些保证应包括实施适当的技术和组织措施,确保数据处理符合适用的数据保护法律要求,并保护数据主体的权利。\n\n我们会与所有数据处理者签订符合法律要求的数据处理协议,明确其数据处理义务和责任。') + .fontSize(14) + .margin({ bottom: 20 }) + .textAlign(TextAlign.Start) + + Text('第三条 数据处理的法律依据') + .fontSize(16) + .fontWeight(FontWeight.Bold) + .margin({ bottom: 10 }) + + Text('我们处理个人数据的法律依据包括但不限于:\n\n3.1 合同必要性\n当处理对于履行我们与您之间的合同(如《用户服务协议》)必要时,或者根据您的请求在签订合同前采取措施时。例如,为了向您提供本系统的核心功能,我们需要处理您的账号信息和相关数据。\n\n3.2 法律义务\n当处理是遵守我们所适用的法律义务所必需时。例如,为了满足教育监管要求或保留特定时间段内的记录。\n\n3.3 合法权益\n当处理对于我们或第三方的合法权益所必需时,除非您的权益或基本权利和自由凌驾于这些权益之上。例如,为了维护系统安全性和改进服务质量,我们可能会分析用户使用模式和系统性能数据。\n\n3.4 同意\n在某些情况下,我们会根据您的明确同意处理特定的个人数据。在此情况下,您有权随时撤回您的同意。例如,我们在向您发送非必要服务通知前会征求您的同意。\n\n3.5 重要利益\n当处理对于保护数据主体或其他自然人的重要利益所必需时。例如,在紧急情况下为保障师生安全而处理位置信息。\n\n3.6 公共利益\n当处理对于执行公共利益或履行数据控制者被授予的官方权限所必需时。例如,为教育研究或行政管理目的进行的数据分析(通常采用去标识化处理)。') + .fontSize(14) + .margin({ bottom: 20 }) + .textAlign(TextAlign.Start) + + Text('第四条 数据处理活动') + .fontSize(16) + .fontWeight(FontWeight.Bold) + .margin({ bottom: 10 }) + + Text('4.1 用户账号管理\n处理目的:创建和管理用户账号,提供身份验证和授权。\n数据类别:用户名、密码(加密存储)、电子邮件地址、手机号码、账号状态信息。\n处理性质:电子存储、验证、检索、更新、备份。\n保留期限:账号存续期间,以及账号关闭后根据适用法律要求和合法业务需要保留的时间(通常不超过3年)。\n法律依据:合同必要性、合法权益。\n\n4.2 核心功能提供\n处理目的:提供教室预约、设备管理、课程安排、考勤管理等核心功能。\n数据类别:用户身份信息、预约记录、使用记录、课程信息、考勤数据、操作日志。\n处理性质:收集、存储、分析、检索、更新、传输。\n保留期限:服务期间及服务终止后的合理期限(通常为5年,部分教育管理相关数据可能根据教育法规要求保留更长时间)。\n法律依据:合同必要性、法律义务、合法权益。\n\n4.3 系统安全与运维\n处理目的:确保系统安全,防止欺诈,进行技术维护和故障排除。\n数据类别:日志数据、设备信息、网络数据、用户行为数据、错误报告。\n处理性质:自动收集、监控、分析、故障排除。\n保留期限:根据安全和合规要求,通常为6-24个月。\n法律依据:合法权益、法律义务。\n\n4.4 服务优化与分析\n处理目的:分析系统使用情况,改进服务质量和用户体验。\n数据类别:使用统计数据、功能使用频率、界面交互数据、用户反馈(通常为匿名化或假名化处理)。\n处理性质:统计分析、模式识别、聚合处理。\n保留期限:通常为2年,之后进行永久匿名化处理。\n法律依据:合法权益、同意(针对特定非必要分析)。\n\n4.5 沟通与通知\n处理目的:向用户发送服务相关通知、更新和重要信息。\n数据类别:联系信息、通知偏好设置、通知历史记录。\n处理性质:电子通信、推送通知、邮件发送。\n保留期限:账号存续期间及之后的合理期限(通常不超过1年)。\n法律依据:合同必要性、合法权益、同意(针对非必要通知)。') + .fontSize(14) + .margin({ bottom: 20 }) + .textAlign(TextAlign.Start) + + Text('第五条 数据接收者') + .fontSize(16) + .fontWeight(FontWeight.Bold) + .margin({ bottom: 10 }) + + Text('5.1 我们可能会将个人数据传输给以下类别的接收者:\n\n5.1.1 内部接收者\n• 我们的员工和授权代表(根据工作职责的需要和最小必要权限原则)\n• 关联公司和子公司(仅在必要时且遵循适当的数据保护标准)\n\n5.1.2 外部接收者\n• 云服务和托管服务提供商\n• IT和系统维护服务提供商\n• 分析和技术支持服务提供商\n• 客户支持服务提供商\n• 审计师和专业顾问(如法律顾问、税务顾问)\n• 教育主管部门(在法律要求的情况下)\n• 政府机构和执法部门(应法律要求)\n\n5.2 数据传输保障\n5.2.1 所有外部接收者都必须提供适当的数据安全保证,并签署数据处理协议或包含数据保护条款的服务协议。\n5.2.2 对于外部数据接收者,我们确保其仅在指定目的范围内处理数据,并遵守合同约定和适用法律的保密和安全要求。\n5.2.3 当与第三方共享数据时,我们会尽量限制共享的数据范围,优先采用匿名化或假名化处理。') + .fontSize(14) + .margin({ bottom: 20 }) + .textAlign(TextAlign.Start) + + Text('第六条 数据安全') + .fontSize(16) + .fontWeight(FontWeight.Bold) + .margin({ bottom: 10 }) + + Text('6.1 技术措施\n我们实施了适当的技术措施,包括但不限于:\n6.1.1 数据加密:使用行业标准的加密技术保护传输中的数据(TLS/SSL)和存储的敏感数据(AES-256等)。\n6.1.2 访问控制:实施基于角色的访问控制、多因素认证和最小权限原则。\n6.1.3 网络安全:部署防火墙、入侵检测/防御系统、DDoS防护措施。\n6.1.4 安全监控:实施7×24小时的系统监控,日志审计和异常检测。\n6.1.5 定期安全评估:进行漏洞扫描、渗透测试和安全审计。\n\n6.2 组织措施\n我们还实施了组织措施,包括但不限于:\n6.2.1 安全管理制度:建立全面的信息安全管理体系,定期更新安全政策。\n6.2.2 员工培训:定期对员工进行数据安全和隐私保护培训。\n6.2.3 访问管理:严格控制对个人数据的访问,实行职责分离。\n6.2.4 供应商管理:评估和监督第三方服务提供商的安全措施。\n6.2.5 安全事件响应:建立安全事件响应程序,确保及时处理数据泄露事件。\n\n6.3 持续改进\n我们定期审查和更新这些措施,以适应不断变化的技术环境和安全威胁。我们的安全措施遵循行业最佳实践和适用的安全标准,如ISO/IEC 27001、等级保护标准等。') + .fontSize(14) + .margin({ bottom: 20 }) + .textAlign(TextAlign.Start) + + Text('第七条 数据跨境传输') + .fontSize(16) + .fontWeight(FontWeight.Bold) + .margin({ bottom: 10 }) + + Text('7.1 数据本地化\n我们的服务器和数据存储设施主要位于中国境内。我们收集的个人数据通常存储在中国境内,并遵守中国的数据保护法律。\n\n7.2 跨境传输情形\n在下列情形下,我们可能需要将您的个人数据传输到中国境外:\n7.2.1 法律法规明确规定;\n7.2.2 依法获得您的单独同意;\n7.2.3 为履行您作为一方当事人的合同所必需;\n7.2.4 为了保护自然人的生命、健康和财产安全等重大合法权益所必需;\n7.2.5 法律、行政法规规定的其他情形。\n\n7.3 跨境传输保障\n如需将您的个人数据传输至中国境外,我们将:\n7.3.1 依法进行个人信息出境评估或安全评估;\n7.3.2 确保接收方具备不低于中国法律规定的个人信息保护标准;\n7.3.3 采取加密等安全措施保护传输数据;\n7.3.4 通过合同或其他形式确保接收方履行数据保护义务。') + .fontSize(14) + .margin({ bottom: 20 }) + .textAlign(TextAlign.Start) + + Text('第八条 数据主体权利') + .fontSize(16) + .fontWeight(FontWeight.Bold) + .margin({ bottom: 10 }) + + Text('8.1 权利内容\n根据适用的数据保护法律,您作为数据主体享有以下权利:\n\n8.1.1 知情权\n您有权获取有关我们如何处理您的个人数据的信息,包括处理的目的、个人数据类别、保留期限等。\n\n8.1.2 访问权\n您有权获取我们是否处理您的个人数据的确认,以及在处理的情况下,访问该数据的副本。\n\n8.1.3 更正权\n您有权要求更正不准确的个人数据,并有权要求补充不完整的个人数据。\n\n8.1.4 删除权(被遗忘权)\n在特定情况下,您有权要求删除与您相关的个人数据,例如当数据不再必要或您撤回同意时。\n\n8.1.5 处理限制权\n在特定情况下,您有权限制我们处理您的个人数据,例如当您质疑数据准确性或处理合法性时。\n\n8.1.6 数据可携权\n您有权以结构化、常用的、机器可读的格式接收您提供给我们的个人数据,并有权将这些数据传输给另一个数据控制者。\n\n8.1.7 反对权\n基于与您的特定情况相关的理由,您有权随时反对处理您的个人数据。\n\n8.1.8 自动化决策权\n您有权不受完全基于自动化处理(包括分析)的决策约束,除非此类决策对合同履行必要或您明确同意。\n\n8.2 行使权利的方式\n8.2.1 您可以通过本声明第九条提供的联系方式向我们发送请求,行使上述权利。\n8.2.2 为保障安全,我们可能需要验证您的身份,然后才能处理您的请求。\n8.2.3 我们将在收到您的请求后的30天内做出回应。如需延长回应时间,我们将告知您延迟原因。\n8.2.4 在特定情况下,如请求明显无根据、重复或过度,我们可能会收取合理费用或拒绝处理请求,但我们会告知您拒绝的理由。') + .fontSize(14) + .margin({ bottom: 20 }) + .textAlign(TextAlign.Start) + + Text('第九条 数据泄露通知') + .fontSize(16) + .fontWeight(FontWeight.Bold) + .margin({ bottom: 10 }) + + Text('9.1 安全事件识别\n我们建立了全面的数据安全事件识别机制,通过安全监控系统和人工审核相结合的方式,及时发现可能的数据泄露事件。\n\n9.2 内部报告程序\n9.2.1 一旦识别到数据安全事件,我们的工作人员将按照内部安全事件响应程序,立即向安全响应团队和数据保护官报告。\n9.2.2 安全响应团队将评估事件性质、规模和可能的影响,确定事件的严重程度。\n\n9.3 监管机构通知\n9.3.1 如发生可能导致个人信息泄露的数据安全事件,我们将按照法律法规的要求,在规定时间内向相关监管机构报告。\n9.3.2 报告内容将包括但不限于:事件性质、涉及的个人数据类别和数量、可能产生的后果、我们已采取的补救措施、联系方式等。\n\n9.4 数据主体通知\n9.4.1 对于可能导致数据主体权利受到严重影响的数据泄露事件,我们将在事件确认后及时通知受影响的数据主体。\n9.4.2 通知方式可能包括:电子邮件、短信、电话、系统内通知或其他合适的方式。\n9.4.3 通知内容将包括:事件描述、可能的影响、我们已采取的措施、建议数据主体采取的措施以及联系方式。\n\n9.5 持续改进\n每次数据安全事件后,我们将进行全面的事后审查,分析事件根源,完善安全措施,避免类似事件再次发生。') + .fontSize(14) + .margin({ bottom: 20 }) + .textAlign(TextAlign.Start) + + Text('第十条 联系方式') + .fontSize(16) + .fontWeight(FontWeight.Bold) + .margin({ bottom: 10 }) + + Text('如果您对我们的数据处理有任何疑问,或希望行使数据主体权利,请通过以下方式联系我们:\n\n数据保护官:数据保护官\n数据保护团队:个人信息保护办公室\n电子邮件:support@tdcat.cn\n电话:400-123-4567\n地址:中国北京市海淀区科学园路XX号\n邮编:100080\n\n我们将尽快处理您的请求,通常会在收到请求后的30天内回复。如果由于特殊情况需要更长的处理时间,我们会告知您延迟的原因及预计回复时间。\n\n如果您对我们的回复不满意,您还可以向相关数据保护监管机构投诉或寻求司法救济。') + .fontSize(14) + .margin({ bottom: 30 }) + .textAlign(TextAlign.Start) + + Text('本声明最终解释权归数据猫所有。') + .fontSize(14) + .fontWeight(FontWeight.Bold) + .margin({ bottom: 30 }) + .textAlign(TextAlign.Center) + } + .width('90%') + .padding({ bottom: 50 }) + .alignItems(HorizontalAlign.Start) + } + .width('100%') + .layoutWeight(1) + .backgroundColor('#F5F7FA') + .scrollBar(BarState.Auto) + .edgeEffect(EdgeEffect.Spring) + } + .width('100%') + .height('100%') + } +} + +export { DataProcessingStatement } \ No newline at end of file diff --git a/entry/src/main/ets/pages/agreements/PrivacyPolicy.ets b/entry/src/main/ets/pages/agreements/PrivacyPolicy.ets new file mode 100644 index 0000000..e8b0a40 --- /dev/null +++ b/entry/src/main/ets/pages/agreements/PrivacyPolicy.ets @@ -0,0 +1,181 @@ +/** + * Copyright (c) 2025 TDCAT.CN + * Author: CC + */ + +import { router } from '@kit.ArkUI'; + +@Entry +@Component +struct PrivacyPolicy { + @State title: string = '隐私政策'; + + build() { + Column() { + // 顶部栏 + Row() { + Image($r('app.media.back')) + .width(24) + .height(24) + .margin({ left: 16 }) + .onClick(() => { + router.back(); + }) + + Text(this.title) + .fontSize(18) + .fontWeight(FontWeight.Medium) + .margin({ left: 16 }) + + Blank() + } + .width('100%') + .height(56) + .backgroundColor('#FFFFFF') + .alignItems(VerticalAlign.Center) + + // 内容区域 + Scroll() { + Column() { + Text('智能教室管理系统隐私政策') + .fontSize(20) + .fontWeight(FontWeight.Bold) + .margin({ top: 20, bottom: 20 }) + .textAlign(TextAlign.Center) + .width('100%') + + Text('最近更新日期:2023年12月1日\n文件编号:CMSPP-2023-001\n生效日期:2023年12月15日') + .fontSize(14) + .fontColor('#666666') + .margin({ bottom: 20 }) + .width('100%') + .textAlign(TextAlign.Center) + + Text('引言') + .fontSize(16) + .fontWeight(FontWeight.Bold) + .margin({ bottom: 10 }) + + Text('数据猫(以下简称"我们")深知个人信息对您的重要性,我们非常重视您的个人隐私和个人信息保护。本隐私政策(以下简称"本政策")为智能教室管理系统所制定,阐明了我们如何收集、使用、存储、共享、转让、公开披露和保护您的个人信息,以及您享有的相关权利。在使用我们的服务前,请您务必仔细阅读并了解本政策的全部内容(特别是加粗或下划线标注的内容),以便做出适当的选择。') + .fontSize(14) + .margin({ bottom: 20 }) + .textAlign(TextAlign.Start) + + Text('如您是18周岁以下的未成年人,请在监护人的陪同下阅读本政策,并确保已征得监护人的同意后使用我们的服务。') + .fontSize(14) + .fontWeight(FontWeight.Bold) + .margin({ bottom: 20 }) + .textAlign(TextAlign.Start) + + Text('第一条 定义') + .fontSize(16) + .fontWeight(FontWeight.Bold) + .margin({ bottom: 10 }) + + Text('1.1 个人信息:指以电子或者其他方式记录的能够单独或者与其他信息结合识别特定自然人身份或者反映特定自然人活动情况的各种信息。\n\n1.2 个人敏感信息:指一旦泄露、非法提供或滥用可能危害人身和财产安全,极易导致个人名誉、身心健康受到损害或歧视性待遇的个人信息,主要包括身份证件号码、个人生物识别信息、银行账号、通信内容、健康生理信息等。\n\n1.3 设备:指可用于访问智能教室管理系统服务的设备,例如台式电脑、笔记本电脑、平板电脑或智能手机。\n\n1.4 唯一设备标识符:指具有唯一性的设备标识符(有时称为通用唯一标识符或UUID),例如设备的IMEI号、MAC地址、软件安装列表等。') + .fontSize(14) + .margin({ bottom: 20 }) + .textAlign(TextAlign.Start) + + Text('第二条 我们收集的信息') + .fontSize(16) + .fontWeight(FontWeight.Bold) + .margin({ bottom: 10 }) + + Text('2.1 您提供的信息\n根据服务类型的不同,我们可能收集以下信息:\n\n2.1.1 账号信息:您在注册和使用本系统时提供的用户名、密码(以加密形式存储)、电子邮件地址、手机号码。\n2.1.2 个人资料:您在使用本系统中填写的姓名、职位、所属部门、学号/工号等信息。\n2.1.3 用户反馈:您在使用我们服务过程中向我们提供的反馈信息,包括但不限于问题报告、建议、调查问卷回复等。\n2.1.4 其他您选择提供的信息。\n\n2.2 我们自动收集的信息\n当您使用我们的服务时,我们可能会自动收集以下信息:\n\n2.2.1 设备信息:设备类型、操作系统及版本、唯一设备标识符、IP地址、设备设置等信息。\n2.2.2 日志信息:您使用本系统的详细情况,包括登录时间、使用时长、操作记录、搜索查询内容等使用服务的操作日志。\n2.2.3 位置信息:在您授权的情况下,我们可能会收集您的位置信息。您可以通过设备的设置功能随时关闭或开启定位功能。\n2.2.4 网络信息:网络类型、网络状态、连接状态、HTTP头等网络通信数据。') + .fontSize(14) + .margin({ bottom: 20 }) + .textAlign(TextAlign.Start) + + Text('第三条 信息使用') + .fontSize(16) + .fontWeight(FontWeight.Bold) + .margin({ bottom: 10 }) + + Text('我们严格遵循合法、正当、必要的原则,出于以下目的使用收集的信息:\n\n3.1 向您提供服务\n3.1.1 创建和管理您的账号。\n3.1.2 验证您的身份,确保系统安全。\n3.1.3 处理您的请求和交易,响应您的查询。\n3.1.4 提供、维护和改进我们的服务功能和质量。\n\n3.2 服务优化与安全\n3.2.1 开发新的服务或功能。\n3.2.2 分析用户如何使用我们的服务,以便改进用户体验。\n3.2.3 向您推送重要的通知,如系统更新、服务条款和政策变更等。\n3.2.4 防止、检测和调查欺诈、未授权或非法活动。\n3.2.5 保护我们和用户的合法权益。\n\n3.3 其他用途\n3.3.1 履行适用法律法规规定的义务。\n3.3.2 基于学术研究、公共利益目的进行统计分析(去标识化处理后)。\n3.3.3 根据您的授权或法律法规允许的其他用途。') + .fontSize(14) + .margin({ bottom: 20 }) + .textAlign(TextAlign.Start) + + Text('第四条 信息共享') + .fontSize(16) + .fontWeight(FontWeight.Bold) + .margin({ bottom: 10 }) + + Text('4.1 我们不会将您的个人信息出售给任何第三方。\n\n4.2 在以下情况下,我们可能会共享您的个人信息:\n4.2.1 经您明确同意的情况下。\n4.2.2 为完成合并、收购或资产出售而转让给其他实体,但我们会要求此类实体继续按照本政策处理个人信息。\n4.2.3 出于法律原因,如遵守适用的法律法规、法律程序或政府强制要求;保护我们或公众的权利、财产或安全;检测、防止和解决欺诈、安全或技术问题;保护我们或其他用户的合法权益免受损害等。\n4.2.4 与我们的第三方服务提供商共享,这些提供商需要访问这些信息来代表我们执行服务,例如云服务提供商、数据分析服务商、维护服务提供商等,但我们会确保他们遵守合同约定的保密和安全措施,并禁止他们将您的个人信息用于任何其他用途。\n\n4.3 在共享信息前,我们将确保接收方的数据安全能力,并签署严格的保密协议,要求他们按照本政策以及我们要求的其他适当的保密和安全措施来处理个人信息。') + .fontSize(14) + .margin({ bottom: 20 }) + .textAlign(TextAlign.Start) + + Text('第五条 信息存储和安全') + .fontSize(16) + .fontWeight(FontWeight.Bold) + .margin({ bottom: 10 }) + + Text('5.1 信息存储\n5.1.1 我们将在中华人民共和国境内收集和产生的个人信息存储在中国境内。\n5.1.2 我们将在实现本政策所述目的所需的期限内保留您的个人信息,除非法律要求或允许更长的保留期限。超出保留期限后,我们将对您的个人信息进行删除或匿名化处理,但以下情形除外:\n(a) 法律法规等有明确规定;\n(b) 取得您的明确授权;\n(c) 涉及国家安全、国防安全、公共安全、公共卫生、公共利益等。\n\n5.2 信息安全\n5.2.1 我们采取各种合理的物理、技术和管理措施,以保护您的个人信息免受未经授权的访问、使用或泄露。这些措施包括但不限于:\n(a) 数据加密:对传输中和存储的敏感数据进行加密;\n(b) 访问控制:严格限制有权访问个人信息的员工范围,并实施多重身份验证;\n(c) 安全培训:定期对员工进行数据安全培训;\n(d) 漏洞扫描:定期评估和更新安全措施,及时修补安全漏洞;\n(e) 数据备份:定期备份数据以防丢失。\n\n5.2.2 我们会尽力防止您的信息遭到泄露、破坏或丢失。尽管我们采取了合理的安全措施,但请注意,由于互联网环境并非百分之百安全,我们无法保证信息传输的绝对安全性。\n\n5.2.3 若发生个人信息安全事件,我们将按照法律法规的要求向您告知事件的基本情况、我们采取的处置措施以及您可自主防范和降低风险的建议措施等。我们将通过电子邮件、短信、电话等方式告知您,当难以逐一告知时,我们将采取合理、有效的方式进行告知。同时,我们还将按照监管部门要求,主动上报个人信息安全事件的处置情况。') + .fontSize(14) + .margin({ bottom: 20 }) + .textAlign(TextAlign.Start) + + Text('第六条 您的权利') + .fontSize(16) + .fontWeight(FontWeight.Bold) + .margin({ bottom: 10 }) + + Text('根据适用的法律法规,您享有以下权利:\n\n6.1 访问权\n您有权访问您的个人信息,包括获知我们处理您个人信息的情况,并获取我们持有的关于您的个人信息的副本。\n\n6.2 更正权\n当您发现我们处理的关于您的个人信息有错误或不完整时,您有权要求我们更正或补充。\n\n6.3 删除权\n在以下情形中,您可以向我们提出删除个人信息的请求:\n6.3.1 处理目的已实现、无法实现或不再需要;\n6.3.2 我们停止提供服务,或保存期限已届满;\n6.3.3 您撤回同意;\n6.3.4 我们违反法律法规或与您的约定收集、使用个人信息。\n\n6.4 撤回同意权\n对于基于您同意而收集和使用的个人信息,您有权随时撤回您的同意。但这不会影响我们在您撤回同意前基于您的同意所进行的个人信息处理的合法性。\n\n6.5 限制处理权\n在特定情况下,您有权要求我们暂停或限制对您个人信息的处理。\n\n6.6 数据可携权\n在技术可行的情况下,您有权要求我们以结构化、常用的、机器可读的格式提供您的个人信息,并有权将这些信息传输给另一个控制者。\n\n6.7 反对权\n在特定情况下,您有权基于与您的特定情况相关的理由,反对处理您的个人信息。\n\n行使上述权利时,请通过本政策第九条提供的联系方式与我们联系。我们将在收到您的请求后的30天内做出回复。如出于安全考虑,我们可能会要求您提供书面请求,或以其他方式证明您的身份。') + .fontSize(14) + .margin({ bottom: 20 }) + .textAlign(TextAlign.Start) + + Text('第七条 儿童隐私') + .fontSize(16) + .fontWeight(FontWeight.Bold) + .margin({ bottom: 10 }) + + Text('7.1 我们的服务主要面向成人。如您为未满14周岁的儿童,在使用我们的服务前,应当事先取得您的监护人的同意。\n\n7.2 对于经监护人同意收集的儿童个人信息,我们只会在法律允许、监护人明确同意或者为保护儿童所必要的情况下使用或披露。\n\n7.3 如果我们发现自己在未获得可证实的监护人同意的情况下收集了儿童的个人信息,则会设法尽快删除相关信息。\n\n7.4 若您是儿童的监护人,如您对您所监护的儿童的个人信息有任何疑问,请通过本政策第九条提供的联系方式与我们联系。') + .fontSize(14) + .margin({ bottom: 20 }) + .textAlign(TextAlign.Start) + + Text('第八条 隐私政策更新') + .fontSize(16) + .fontWeight(FontWeight.Bold) + .margin({ bottom: 10 }) + + Text('8.1 我们可能会不时更新本隐私政策,此类更新将构成本政策的一部分。政策更新后,我们会通过在智能教室管理系统相关页面发布通知、发送电子邮件或其他适当方式通知您更新的内容,并标明最新更新日期。\n\n8.2 对于重大变更,我们还会提供更为显著的通知(包括对于特定服务的通知,我们会通过电子邮件发送通知或以弹窗方式通知)。本政策所指的重大变更包括但不限于:\n8.2.1 我们的服务模式发生重大变化,如处理个人信息的目的、处理的个人信息类型、个人信息的使用方式等;\n8.2.2 我们在控制权、组织架构等方面发生重大变化,如业务调整、破产并购等引起的所有者变更等;\n8.2.3 个人信息共享、转让或公开披露的主要对象发生变化;\n8.2.4 您参与个人信息处理方面的权利及其行使方式发生重大变化;\n8.2.5 我们负责处理个人信息安全的责任部门、联络方式及投诉渠道发生变化。\n\n8.3 若您继续使用我们的服务,即表示您同意接受修订后的本政策的约束。我们鼓励您定期查看本隐私政策,以了解我们如何保护您的信息。') + .fontSize(14) + .margin({ bottom: 20 }) + .textAlign(TextAlign.Start) + + Text('第九条 联系我们') + .fontSize(16) + .fontWeight(FontWeight.Bold) + .margin({ bottom: 10 }) + + Text('如果您对本隐私政策有任何疑问、意见或建议,或者想行使您的权利,请通过以下方式联系我们:\n\n个人信息保护负责人:数据保护官\n联系部门:个人信息保护办公室\n电子邮件:support@tdcat.cn\n电话:400-123-4567\n地址:中国北京市海淀区科学园路XX号\n邮编:100080\n\n一般情况下,我们将在收到您的请求后30天内答复。如您不满意我们的回复,特别是我们的个人信息处理行为损害了您的合法权益,您还可以通过以下外部途径寻求解决方案:向所在地的人民法院提起诉讼。') + .fontSize(14) + .margin({ bottom: 30 }) + .textAlign(TextAlign.Start) + + Text('本政策最终解释权归数据猫所有。') + .fontSize(14) + .fontWeight(FontWeight.Bold) + .margin({ bottom: 30 }) + .textAlign(TextAlign.Center) + } + .width('90%') + .padding({ bottom: 50 }) + .alignItems(HorizontalAlign.Start) + } + .width('100%') + .layoutWeight(1) + .backgroundColor('#F5F7FA') + .scrollBar(BarState.Auto) + .edgeEffect(EdgeEffect.Spring) + } + .width('100%') + .height('100%') + } +} + +export { PrivacyPolicy } \ No newline at end of file diff --git a/entry/src/main/ets/pages/agreements/UserAgreement.ets b/entry/src/main/ets/pages/agreements/UserAgreement.ets new file mode 100644 index 0000000..5619719 --- /dev/null +++ b/entry/src/main/ets/pages/agreements/UserAgreement.ets @@ -0,0 +1,179 @@ +/** + * Copyright (c) 2025 TDCAT.CN + * Author: CC + */ + +import { router } from '@kit.ArkUI'; + +@Entry +@Component +struct UserAgreement { + @State title: string = '用户服务协议'; + + build() { + Column() { + // 顶部栏 + Row() { + Image($r('app.media.back')) + .width(24) + .height(24) + .margin({ left: 16 }) + .onClick(() => { + router.back(); + }) + + Text(this.title) + .fontSize(18) + .fontWeight(FontWeight.Medium) + .margin({ left: 16 }) + + Blank() + } + .width('100%') + .height(56) + .backgroundColor('#FFFFFF') + .alignItems(VerticalAlign.Center) + + // 内容区域 + Scroll() { + Column() { + Text('智能教室管理系统用户服务协议') + .fontSize(20) + .fontWeight(FontWeight.Bold) + .margin({ top: 20, bottom: 20 }) + .textAlign(TextAlign.Center) + .width('100%') + + Text('最近更新日期:2023年12月1日\n文件编号:CMSUSR-2023-001\n生效日期:2023年12月15日') + .fontSize(14) + .fontColor('#666666') + .margin({ bottom: 20 }) + .width('100%') + .textAlign(TextAlign.Center) + + Text('前言') + .fontSize(16) + .fontWeight(FontWeight.Bold) + .margin({ bottom: 10 }) + + Text('本协议是由智能教室管理系统(以下简称"本系统")的所有者与用户(以下简称"您")就本系统的使用等相关事宜所订立的有效合约。请您在使用本系统前,仔细阅读并充分理解本协议各条款。您使用本系统,即表示您已充分阅读并同意接受本协议的全部条款及其它与本系统相关的政策和规则。如您不同意本协议的任何内容,请勿注册或使用本系统服务。') + .fontSize(14) + .margin({ bottom: 20 }) + .textAlign(TextAlign.Start) + + Text('第一条 定义') + .fontSize(16) + .fontWeight(FontWeight.Bold) + .margin({ bottom: 10 }) + + Text('1.1 智能教室管理系统:指由数据猫开发并运营的教育信息管理软件系统,包括但不限于桌面应用、移动应用等各种形式的应用程序。\n\n1.2 用户:指注册、登录、使用本系统的个人或单位。\n\n1.3 账号:指用户为使用本系统服务而注册的登录凭证,包括用户名和密码等验证信息。') + .fontSize(14) + .margin({ bottom: 20 }) + .textAlign(TextAlign.Start) + + Text('第二条 服务内容') + .fontSize(16) + .fontWeight(FontWeight.Bold) + .margin({ bottom: 10 }) + + Text('2.1 本系统是为教育机构提供的一套智能化教室管理解决方案,包括但不限于教室预约、设备管理、课程安排、考勤管理等功能。具体服务内容以本系统实际提供的为准。\n\n2.2 本系统会不断努力提升服务质量,优化用户体验。本系统有权在必要时变更、中断或终止部分或全部网络服务,但会尽可能提前在相关页面进行通知。\n\n2.3 本系统仅提供相关的网络服务,除此之外与相关网络服务有关的设备(如个人电脑、手机等)及所需的费用(如为接入互联网而支付的电话费及上网费等)均由用户自行承担。') + .fontSize(14) + .margin({ bottom: 20 }) + .textAlign(TextAlign.Start) + + Text('第三条 用户账号') + .fontSize(16) + .fontWeight(FontWeight.Bold) + .margin({ bottom: 10 }) + + Text('3.1 注册资格\n用户应具有法定的相应权利能力和行为能力,能够独立承担法律责任。若用户不具备前述条件,则用户及用户的监护人应依照法律规定承担因此而导致的一切后果。\n\n3.2 账号获取\n用户可通过教育机构分配或自行注册等方式获取本系统账号。用户注册成功后,将获得一个用户账号及相应的密码,该账号和密码由用户负责保管。\n\n3.3 账号安全\n3.3.1 用户应对账号进行的所有活动和事件承担法律责任,包括用户所产生的任何数据和造成的后果,无论该使用是否经过用户授权。\n3.3.2 为维护账号安全,用户应自行加强密码安全性设置,不应将账号信息提供给他人使用。\n3.3.3 如发现有他人未经授权使用用户账号,或存在其他安全问题,用户应立即通知本系统。\n\n3.4 账号注销\n在符合本系统规定条件的情况下,用户可通过系统设置等方式申请注销账号。账号注销后,用户将无法使用该账号,也无法恢复和查询账号中的任何内容或信息。') + .fontSize(14) + .margin({ bottom: 20 }) + .textAlign(TextAlign.Start) + + Text('第四条 用户行为规范') + .fontSize(16) + .fontWeight(FontWeight.Bold) + .margin({ bottom: 10 }) + + Text('4.1 用户在使用本系统过程中必须遵循以下原则:\n4.1.1 遵守中华人民共和国有关的法律法规;\n4.1.2 不得为任何非法目的而使用本系统;\n4.1.3 遵守所有与网络服务有关的协议、规定和程序;\n4.1.4 不得利用本系统进行任何可能对互联网正常运转造成不利影响的行为;\n4.1.5 不得利用本系统传播任何非法、有害、骚扰、侵害、中伤、粗俗、猥亵或其他道德上令人反感的内容;\n4.1.6 不得侵犯他人知识产权或其他合法权益。\n\n4.2 如用户违反上述任何规定,本系统有权采取以下措施:\n4.2.1 立即终止向该用户提供服务;\n4.2.2 删除违反规定的内容;\n4.2.3 保存有关记录,并向有关政府部门报告;\n4.2.4 追究用户的法律责任。') + .fontSize(14) + .margin({ bottom: 20 }) + .textAlign(TextAlign.Start) + + Text('第五条 知识产权') + .fontSize(16) + .fontWeight(FontWeight.Bold) + .margin({ bottom: 10 }) + + Text('5.1 本系统所包含的所有内容,包括但不限于文本、图形、标志、按钮图标、图像、声音文件片段、数字下载、数据编译和软件,均为数据猫或内容提供者所有,受中华人民共和国著作权法、商标法、专利法及其他知识产权法律法规和国际公约的保护。\n\n5.2 未经数据猫事先书面同意,用户不得以任何方式使用、复制、修改、传播、展示、出版、发行本系统的任何内容,或创作与之相关的衍生作品。\n\n5.3 用户通过本系统上传、发布的内容,用户保证对该等内容拥有合法权利。用户同意授予数据猫非独占的、永久的、不可撤销的、全球范围内的、免费的许可使用权,数据猫有权为提供服务的目的使用、复制、修改、改编、出版、翻译、据以创作衍生作品、传播、表演和展示此等内容。') + .fontSize(14) + .margin({ bottom: 20 }) + .textAlign(TextAlign.Start) + + Text('第六条 免责声明') + .fontSize(16) + .fontWeight(FontWeight.Bold) + .margin({ bottom: 10 }) + + Text('6.1 本系统不担保服务一定能满足用户的所有要求,也不担保服务不会中断,对服务的及时性、安全性、准确性也不作担保。\n\n6.2 对于因下列任一情况导致的服务中断或受阻,本系统不承担任何法律责任:\n6.2.1 因计算机病毒、木马或其他恶意程序、黑客攻击、网络故障、系统故障、通讯故障、电力故障、罢工、暴乱、火灾、洪水、风暴、爆炸、战争、政府行为、司法行政机关的命令或第三方的不作为而造成的服务中断或延迟;\n6.2.2 用户或本系统的电脑软件、硬件、通信线路、Internet接入故障;\n6.2.3 用户操作不当;\n6.2.4 用户通过非本系统授权的方式使用本服务;\n6.2.5 其他本系统无法控制或合理预见的情形。\n\n6.3 用户理解,本系统需要定期或不定期对系统进行维护、升级,如因此类情况而造成服务在合理时间内的中断,本系统无需为此承担任何责任。') + .fontSize(14) + .margin({ bottom: 20 }) + .textAlign(TextAlign.Start) + + Text('第七条 协议修改') + .fontSize(16) + .fontWeight(FontWeight.Bold) + .margin({ bottom: 10 }) + + Text('7.1 数据猫保留随时修改本协议的权利,修改后的协议条款将在发布后生效。\n\n7.2 如用户不同意相关变更,请停止使用本系统服务。用户继续使用本系统服务,则视为用户已接受修改后的协议。\n\n7.3 本系统会在主要页面或修改的条款页面通过合理方式向用户提示修改内容。') + .fontSize(14) + .margin({ bottom: 20 }) + .textAlign(TextAlign.Start) + + Text('第八条 适用法律与争议解决') + .fontSize(16) + .fontWeight(FontWeight.Bold) + .margin({ bottom: 10 }) + + Text('8.1 本协议的订立、执行和解释及争议的解决均应适用中华人民共和国法律。\n\n8.2 如双方就本协议内容或其执行发生任何争议,应尽量友好协商解决;协商不成时,任何一方均有权将争议提交至北京市海淀区人民法院诉讼解决。\n\n8.3 本协议任何条款被有管辖权的法院认定为无效的,不应影响其他条款或其任何部分的效力。') + .fontSize(14) + .margin({ bottom: 20 }) + .textAlign(TextAlign.Start) + + Text('第九条 其他') + .fontSize(16) + .fontWeight(FontWeight.Bold) + .margin({ bottom: 10 }) + + Text('9.1 本协议构成双方对本协议之约定事项及其他有关事宜的完整协议,除本协议规定的之外,未赋予本协议各方其他权利。\n\n9.2 如本协议中的任何条款无论因何种原因完全或部分无效或不具有执行力,本协议的其余条款仍应有效并且有约束力。\n\n9.3 本协议中的标题仅为方便而设,不具有法律或契约效果。\n\n9.4 数据猫未行使或执行本协议任何权利或规定,不构成对前述权利或权利之放弃。\n\n9.5 本协议内容同时包括本系统的隐私政策、数据处理声明等其他相关规则。上述内容一经正式发布,即为本协议不可分割的组成部分,用户同样应当遵守。') + .fontSize(14) + .margin({ bottom: 20 }) + .textAlign(TextAlign.Start) + + Text('联系方式') + .fontSize(16) + .fontWeight(FontWeight.Bold) + .margin({ bottom: 10 }) + + Text('如您对本协议或本系统服务有任何疑问,请通过以下方式与我们联系:\n\n名称:数据猫\n电子邮件:support@tdcat.cn\n电话:400-123-4567\n地址:中国北京市海淀区科学园路XX号\n邮编:100080') + .fontSize(14) + .margin({ bottom: 30 }) + .textAlign(TextAlign.Start) + } + .width('90%') + .padding({ bottom: 50 }) + .alignItems(HorizontalAlign.Start) + } + .width('100%') + .layoutWeight(1) + .backgroundColor('#F5F7FA') + .scrollBar(BarState.Auto) + .edgeEffect(EdgeEffect.Spring) + } + .width('100%') + .height('100%') + } +} + +export { UserAgreement } \ No newline at end of file diff --git a/entry/src/main/ets/pages/login.ets b/entry/src/main/ets/pages/login.ets index 3a1eabe..a7c90f9 100644 --- a/entry/src/main/ets/pages/login.ets +++ b/entry/src/main/ets/pages/login.ets @@ -1,8 +1,14 @@ -import { router } from '@kit.ArkUI'; +/** + * Copyright (c) 2025 TDCAT.CN + * Author: CC + */ + +import router from '@ohos.router'; import logManager, { LogCategory, LogEventType } from '../common/logtext'; import settingsService from '../common/SettingsService'; import { DatabaseService } from '../common/DatabaseService'; - +import promptAction from '@ohos.promptAction'; +import common from '@ohos.app.ability.common'; @Entry @Component @@ -13,6 +19,7 @@ struct Login { @State password: string = ''; // 密码 @State isLoading: boolean = false; // 加载状态 @State errorMessage: string = ''; // 错误信息 + @State isAgreementChecked: boolean = false; // 是否同意条款 // 获取数据库服务实例 private dbService: DatabaseService = DatabaseService.getInstance(); @@ -36,6 +43,13 @@ struct Login { return; } + // 必须同意条款才能登录 + if (!this.isAgreementChecked) { + this.errorMessage = '请阅读并同意相关条款'; + this.isLoading = false; + return; + } + try { // 使用异步验证方法 const isValid = await this.dbService.validateUser(this.username, this.password); @@ -66,177 +80,262 @@ struct Login { } build() { - Column({space: 20}) { // 增加行间距 - // 顶部标题和图标 - Column() { - Image('http://139.155.155.67:2342/images/tdcat.webp') - .height(150) - .width(150) - .margin({ top: 40, bottom: 20 }) + Stack() { + // 原有登录页面内容 + Column({space: 20}) { // 增加行间距 + // 顶部标题和图标 + Column() { + Image($r('app.media.logo')) + .objectFit(ImageFit.Contain) + .width('80%') + .aspectRatio(1.5) + .margin({ top: 40 }) + .borderRadius(8) + + // Text(this.message) + // .fontColor('#333333') + // .fontSize(28) + // .fontWeight(FontWeight.Bold) + // .margin({ top: 20, bottom: 30 }) + } + .width('100%') + .alignItems(HorizontalAlign.Center) + + // 登录表单 + Column({ space: 30 }) { + // 账号输入框 + Column({ space: 8 }) { + Text("账号") + .fontSize(16) + .fontColor('#666666') + .alignSelf(ItemAlign.Start) + + TextInput({ placeholder: '请输入账号' }) + .type(InputType.Normal) + .placeholderColor('#999999') + .placeholderFont({ size: 16 }) + .caretColor('#2196F3') + .width('100%') + .height(50) + .fontSize(16) + .fontColor('#333333') + .backgroundColor('#F5F5F5') + .borderRadius(8) + .padding({ left: 16, right: 16 }) + .onChange((value: string) => { + this.username = value; + this.errorMessage = ''; // 清除错误信息 + }) + } + .width('100%') - Text(this.message) - .fontColor('#333333') - .fontSize(28) - .fontWeight(FontWeight.Bold) - .margin({ bottom: 30 }) + // 密码输入框 + Column({ space: 8 }) { + Text("密码") + .fontSize(16) + .fontColor('#666666') + .alignSelf(ItemAlign.Start) + + TextInput({ placeholder: '请输入密码' }) + .type(InputType.Password) + .placeholderColor('#999999') + .placeholderFont({ size: 16 }) + .caretColor('#2196F3') + .width('100%') + .height(50) + .fontSize(16) + .fontColor('#333333') + .backgroundColor('#F5F5F5') + .borderRadius(8) + .padding({ left: 16, right: 16 }) + .onChange((value: string) => { + this.password = value; + this.errorMessage = ''; // 清除错误信息 + }) + } + .width('100%') + + // 错误信息提示 + if (this.errorMessage !== '') { + Text(this.errorMessage) + .fontSize(14) + .fontColor('#FF0000') + .width('100%') + } + + // 条款同意选项 + Row() { + Checkbox() + .select(this.isAgreementChecked) + .selectedColor('#2196F3') + .width(20) + .height(20) + .margin({ right: 8 }) + .onChange((value: boolean) => { + this.isAgreementChecked = value; + this.errorMessage = ''; // 清除错误信息 + }) + + Text() { + Span('我已阅读并同意') + .fontSize(14) + .fontColor('#666666') + + Span('《用户服务协议》') + .fontSize(14) + .fontColor('#2196F3') + .fontWeight(FontWeight.Medium) + .onClick(() => { + router.pushUrl({ + url: 'pages/agreements/UserAgreement' + }); + }) + + Span('、') + .fontSize(14) + .fontColor('#666666') + + Span('《隐私政策》') + .fontSize(14) + .fontColor('#2196F3') + .fontWeight(FontWeight.Medium) + .onClick(() => { + router.pushUrl({ + url: 'pages/agreements/PrivacyPolicy' + }); + }) + + Span('和') + .fontSize(14) + .fontColor('#666666') + + Span('《数据处理声明》') + .fontSize(14) + .fontColor('#2196F3') + .fontWeight(FontWeight.Medium) + .onClick(() => { + router.pushUrl({ + url: 'pages/agreements/DataProcessingStatement' + }); + }) + } + .width('90%') + .textAlign(TextAlign.Start) + } + .width('100%') + .margin({ top: 16, bottom: 16 }) + .alignItems(VerticalAlign.Center) + + // 忘记密码和注册按钮 + Row() { + Text('忘记密码') + .fontSize(14) + .fontColor('#2196F3') + .onClick(() => { + AlertDialog.show({ + message:"该功能尚未开发", + confirm:{ + value:"确定", + action:()=>{} + } + }); + //跳转忘记密码页面 + /* router.pushUrl({ + url: 'pages/forgot-password' + });*/ + }) + + Blank() + + Text('注册账号') + .fontSize(14) + .fontColor('#2196F3') + .onClick(() => { + router.pushUrl({ + url: 'pages/register' + }); + }) + } + .width('100%') + .justifyContent(FlexAlign.SpaceBetween) + .margin({ top: 10 }) + + // 登录按钮 + Button('登录') + .type(ButtonType.Capsule) + .width('100%') + .height(50) + .fontSize(18) + .fontWeight(FontWeight.Medium) + .backgroundColor('#2196F3') + .onClick(() => { + this.handleLogin(); + }) + .margin({ top: 20 }) + } + .width('100%') + .padding({ left: 30, right: 30 }) } .width('100%') - .alignItems(HorizontalAlign.Center) - - // 登录表单 - Column({ space: 30 }) { - // 账号输入框 - Column({ space: 8 }) { - Text("账号") + .height('100%') + + // 浮动测试按钮 + Button({ type: ButtonType.Circle }) { + Row() { + Image($r('app.media.ic_test')) + .width(24) + .height(24) + .margin({ right: 4 }) + Text('测试') .fontSize(16) - .fontColor('#666666') - .alignSelf(ItemAlign.Start) - - TextInput({ placeholder: '请输入账号' }) - .type(InputType.Normal) - .placeholderColor('#999999') - .placeholderFont({ size: 16 }) - .caretColor('#2196F3') - .width('100%') - .height(50) - .fontSize(16) - .fontColor('#333333') - .backgroundColor('#F5F5F5') - .borderRadius(8) - .padding({ left: 16, right: 16 }) - .onChange((value: string) => { - this.username = value; - this.errorMessage = ''; // 清除错误信息 - }) - - - + .fontColor('#FFFFFF') } - .width('100%') + } + .width(100) + .height(40) + .backgroundColor('#2196F3') + .position({ x: '80%', y: 50 }) + .enabled(!this.isLoading) + .onClick(() => { + if (this.isLoading) return; + this.isLoading = true; - // 密码输入框 - Column({ space: 8 }) { - Text("密码") - .fontSize(16) - .fontColor('#666666') - .alignSelf(ItemAlign.Start) - - TextInput({ placeholder: '请输入密码' }) - .type(InputType.Password) - .placeholderColor('#999999') - .placeholderFont({ size: 16 }) - .caretColor('#2196F3') - .width('100%') - .height(50) - .fontSize(16) - .fontColor('#333333') - .backgroundColor('#F5F5F5') - .borderRadius(8) - .padding({ left: 16, right: 16 }) - .onChange((value: string) => { - this.password = value; - this.errorMessage = ''; // 清除错误信息 - }) - } - .width('100%') - - // 错误信息提示 - if (this.errorMessage !== '') { - Text(this.errorMessage) - .fontSize(14) - .fontColor('#FF0000') - .width('100%') - } - - // 登录按钮 - Button("登录") - .width('100%') - .height(50) - .fontSize(18) - .fontWeight(FontWeight.Medium) - .backgroundColor('#2196F3') - .borderRadius(8) - .fontColor(Color.White) - .stateEffect(true) - .enabled(!this.isLoading) - .opacity(this.isLoading ? 0.6 : 1) - .onClick(() => { - //this.Apply() - this.handleLogin(); - }) - - // 测试按钮 - 直接跳转到首页 - Button("测试直接进入") - .width('100%') - .height(40) - .fontSize(16) - .fontWeight(FontWeight.Medium) - .backgroundColor('#4CAF50') - .borderRadius(8) - .fontColor(Color.White) - .margin({ top: 10 }) - .onClick(() => { - // 设置账户为测试用户 - settingsService.setCurrentAccount('test'); - - // 记录测试登录 - logManager.info(LogCategory.USER, LogEventType.USER_LOGIN, 'Test user logged in'); - - // 直接跳转到首页 - router.replaceUrl({ + // 使用测试账号直接登录 + this.dbService.validateUser('teststudent21', '123456').then((success: boolean) => { + if (success) { + // 设置当前用户 + settingsService.setCurrentAccount('teststudent21'); + // 记录登录成功日志 + logManager.info(LogCategory.USER, LogEventType.USER_LOGIN, `Test login success: teststudent21`); + // 跳转到主页 + router.pushUrl({ url: 'pages/HomePage' }); - }) - } - .width('90%') - .padding(20) - .backgroundColor(Color.White) - .borderRadius(16) - .shadow({ radius: 8, color: 'rgba(0,0,0,0.1)', offsetY: 4 }) - - // 底部提示 - Row() { - Button({ type: ButtonType.Capsule }) { - Text("忘记密码?") - .fontSize(14) - .fontColor('#2196F3') - } - .backgroundColor(Color.Transparent) - .onClick(() => { - AlertDialog.show({ - message: "忘记密码功能尚未开发,请联系管理员重置密码。", - confirm: { - value: "确定", - action: () => {} - } + } + }).catch((error: Error) => { + logManager.error(LogCategory.USER, `Test login failed: ${error.message}`); + promptAction.showToast({ + message: '测试登录失败', + duration: 2000 }); - }) - - Blank(100) - - Button({ type: ButtonType.Capsule }) { - Text("注册") - .fontSize(14) - .fontColor('#2196F3') - } - .backgroundColor(Color.Transparent) - .onClick(() => { - AlertDialog.show({ - message: "注册功能尚未开发,请联系管理员创建账号。", - confirm: { - value: "确定", - action: () => {} - } - }); - }) - } - .width('90%') - .margin({ top: 20 }) + }).finally(() => { + this.isLoading = false; + }); + }) + .shadow({ + radius: 8, + color: 'rgba(0, 0, 0, 0.2)', + offsetX: 2, + offsetY: 4 + }) + .animation({ + duration: 250, + curve: Curve.EaseInOut + }) + .opacity(this.isLoading ? 0.6 : 1) } - .height('100%') .width('100%') - .alignItems(HorizontalAlign.Center) - .backgroundColor('#F5F7FA') // 页面背景色 + .height('100%') } // 页面生命周期方法 @@ -287,3 +386,5 @@ struct Login { } +export { Login } + diff --git a/entry/src/main/ets/pages/register.ets b/entry/src/main/ets/pages/register.ets new file mode 100644 index 0000000..8ed45b2 --- /dev/null +++ b/entry/src/main/ets/pages/register.ets @@ -0,0 +1,490 @@ +/** + * Copyright (c) 2025 TDCAT.CN + * Author: CC + */ + +import router from '@ohos.router'; +import prompt from '@ohos.promptAction'; +import { DatabaseService, RegisterUserData } from '../common/DatabaseService'; +import logManager, { LogCategory, LogEventType } from '../common/logtext'; + +@Entry +@Component +struct Register { + @State message: string = '注册新账号'; + @State username: string = ''; // 账号 + @State password: string = ''; // 密码 + @State confirmPassword: string = ''; // 确认密码 + @State nickname: string = ''; // 昵称 + @State email: string = ''; // 邮箱 + @State phone: string = ''; // 手机号 + @State photo: string = ''; // 用户照片 + @State isLoading: boolean = false; // 加载状态 + @State errorMessage: string = ''; // 错误信息 + @State isAgreementChecked: boolean = false; // 是否同意条款 + + // 获取数据库服务实例 + private dbService: DatabaseService = DatabaseService.getInstance(); + + // 验证输入 + private validateInput(): boolean { + // 验证账号 + if (!this.username || this.username.trim() === '') { + this.errorMessage = '请输入账号'; + return false; + } + if (this.username.length < 4) { + this.errorMessage = '账号长度不能少于4位'; + return false; + } + + // 验证密码 + if (!this.password || this.password.trim() === '') { + this.errorMessage = '请输入密码'; + return false; + } + if (this.password.length < 6) { + this.errorMessage = '密码长度不能少于6位'; + return false; + } + + // 验证确认密码 + if (this.password !== this.confirmPassword) { + this.errorMessage = '两次输入的密码不一致'; + return false; + } + + // 验证邮箱 + if (!this.email || this.email.trim() === '') { + this.errorMessage = '请输入邮箱'; + return false; + } + if (!this.dbService.validateEmailFormat(this.email)) { + this.errorMessage = '请输入有效的邮箱地址'; + return false; + } + + // 验证手机号(如果填写) + if (this.phone && !/^1[3-9]\d{9}$/.test(this.phone)) { + this.errorMessage = '请输入有效的手机号'; + return false; + } + + // 验证条款 + if (!this.isAgreementChecked) { + this.errorMessage = '请阅读并同意相关条款'; + return false; + } + + return true; + } + + // 处理注册 + private async handleRegister(): Promise { + if (!this.validateInput()) { + return; + } + + this.isLoading = true; + this.errorMessage = ''; + + try { + // 准备注册数据 + const userData: RegisterUserData = { + account: this.username, + password: this.password, + nickname: this.nickname || this.username, + email: this.email, + phone: this.phone, + photo: this.photo || 'http://139.155.155.67:2342/images/default_avatar.png' + }; + + // 发送注册请求 + const success = await this.dbService.registerUser(userData); + if (success) { + logManager.info(LogCategory.USER, LogEventType.USER_REGISTER, `User registered: ${this.username}`); + prompt.showToast({ + message: '注册成功', + duration: 2000 + }); + router.back(); + } else { + this.errorMessage = '注册失败,请稍后重试'; + } + } catch { + this.errorMessage = '注册失败,请稍后重试'; + logManager.error(LogCategory.USER, 'Register error occurred'); + } finally { + this.isLoading = false; + } + } + + build() { + Column() { + // 顶部导航栏 + Row() { + Image($r('app.media.back')) + .width(24) + .height(24) + .margin({ right: 16 }) + .onClick(() => { + router.back(); + }) + + Text(this.message) + .fontSize(24) + .fontWeight(FontWeight.Bold) + .fontColor('#333333') + } + .width('100%') + .justifyContent(FlexAlign.Start) + .padding({ top: 16, bottom: 16, left: 16, right: 16 }) + + // 主滚动内容 + Scroll() { + Column({ space: 20 }) { + // 用户照片上传区域 + Column() { + Image(this.photo || $r('app.media.default_avatar')) + .width(100) + .height(100) + .borderRadius(50) + .margin({ top: 10, bottom: 10 }) + .onClick(() => { + prompt.showToast({ + message: '照片上传功能开发中', + duration: 2000 + }); + }) + + Text("点击更换头像") + .fontSize(14) + .fontColor('#666666') + } + .width('100%') + .justifyContent(FlexAlign.Center) + .alignItems(HorizontalAlign.Center) + + // 表单区域 + Column({ space: 16 }) { + // 账号输入框 + Column({ space: 4 }) { + Row() { + Text("账号") + .fontSize(16) + .fontColor('#333333') + .fontWeight(FontWeight.Medium) + Text("*") + .fontSize(16) + .fontColor('#FF0000') + } + .width('100%') + .justifyContent(FlexAlign.Start) + + TextInput({ placeholder: '请输入账号(至少4位)' }) + .type(InputType.Normal) + .placeholderColor('#BBBBBB') + .placeholderFont({ size: 16 }) + .caretColor('#2196F3') + .width('100%') + .height(50) + .fontSize(16) + .fontColor('#333333') + .backgroundColor('#F5F5F5') + .borderRadius(8) + .padding({ left: 16, right: 16 }) + .onChange((value: string) => { + this.username = value; + this.errorMessage = ''; + }) + } + .width('100%') + + // 密码输入框 + Column({ space: 4 }) { + Row() { + Text("密码") + .fontSize(16) + .fontColor('#333333') + .fontWeight(FontWeight.Medium) + Text("*") + .fontSize(16) + .fontColor('#FF0000') + } + .width('100%') + .justifyContent(FlexAlign.Start) + + TextInput({ placeholder: '请输入密码(至少6位)' }) + .type(InputType.Password) + .placeholderColor('#BBBBBB') + .placeholderFont({ size: 16 }) + .caretColor('#2196F3') + .width('100%') + .height(50) + .fontSize(16) + .fontColor('#333333') + .backgroundColor('#F5F5F5') + .borderRadius(8) + .padding({ left: 16, right: 16 }) + .onChange((value: string) => { + this.password = value; + this.errorMessage = ''; + }) + } + .width('100%') + + // 确认密码输入框 + Column({ space: 4 }) { + Row() { + Text("确认密码") + .fontSize(16) + .fontColor('#333333') + .fontWeight(FontWeight.Medium) + Text("*") + .fontSize(16) + .fontColor('#FF0000') + } + .width('100%') + .justifyContent(FlexAlign.Start) + + TextInput({ placeholder: '请再次输入密码' }) + .type(InputType.Password) + .placeholderColor('#BBBBBB') + .placeholderFont({ size: 16 }) + .caretColor('#2196F3') + .width('100%') + .height(50) + .fontSize(16) + .fontColor('#333333') + .backgroundColor('#F5F5F5') + .borderRadius(8) + .padding({ left: 16, right: 16 }) + .onChange((value: string) => { + this.confirmPassword = value; + this.errorMessage = ''; + }) + } + .width('100%') + + // 昵称输入框 + Column({ space: 4 }) { + Row() { + Text("昵称") + .fontSize(16) + .fontColor('#333333') + .fontWeight(FontWeight.Medium) + Text("(选填)") + .fontSize(16) + .fontColor('#666666') + } + .width('100%') + .justifyContent(FlexAlign.Start) + + TextInput({ placeholder: '请输入昵称' }) + .type(InputType.Normal) + .placeholderColor('#BBBBBB') + .placeholderFont({ size: 16 }) + .caretColor('#2196F3') + .width('100%') + .height(50) + .fontSize(16) + .fontColor('#333333') + .backgroundColor('#F5F5F5') + .borderRadius(8) + .padding({ left: 16, right: 16 }) + .onChange((value: string) => { + this.nickname = value; + this.errorMessage = ''; + }) + } + .width('100%') + + // 邮箱输入框 + Column({ space: 4 }) { + Row() { + Text("邮箱") + .fontSize(16) + .fontColor('#333333') + .fontWeight(FontWeight.Medium) + Text("*") + .fontSize(16) + .fontColor('#FF0000') + } + .width('100%') + .justifyContent(FlexAlign.Start) + + TextInput({ placeholder: '请输入邮箱' }) + .type(InputType.Email) + .placeholderColor('#BBBBBB') + .placeholderFont({ size: 16 }) + .caretColor('#2196F3') + .width('100%') + .height(50) + .fontSize(16) + .fontColor('#333333') + .backgroundColor('#F5F5F5') + .borderRadius(8) + .padding({ left: 16, right: 16 }) + .onChange((value: string) => { + this.email = value; + this.errorMessage = ''; + }) + } + .width('100%') + + // 手机号输入框 + Column({ space: 4 }) { + Row() { + Text("手机号") + .fontSize(16) + .fontColor('#333333') + .fontWeight(FontWeight.Medium) + Text("(选填)") + .fontSize(16) + .fontColor('#666666') + } + .width('100%') + .justifyContent(FlexAlign.Start) + + TextInput({ placeholder: '请输入手机号' }) + .type(InputType.Number) + .placeholderColor('#BBBBBB') + .placeholderFont({ size: 16 }) + .caretColor('#2196F3') + .width('100%') + .height(50) + .fontSize(16) + .fontColor('#333333') + .backgroundColor('#F5F5F5') + .borderRadius(8) + .padding({ left: 16, right: 16 }) + .onChange((value: string) => { + this.phone = value; + this.errorMessage = ''; + }) + } + .width('100%') + + // 错误信息提示 + if (this.errorMessage !== '') { + Text(this.errorMessage) + .fontSize(14) + .fontColor('#FF0000') + .width('100%') + .backgroundColor('#FFF0F0') + .padding(8) + .borderRadius(4) + } + + // 条款同意选项 + Row() { + Checkbox() + .select(this.isAgreementChecked) + .selectedColor('#2196F3') + .width(20) + .height(20) + .margin({ right: 8 }) + .onChange((value: boolean) => { + this.isAgreementChecked = value; + this.errorMessage = ''; + }) + + Text() { + Span('我已阅读并同意') + .fontSize(14) + .fontColor('#666666') + + Span('《用户服务协议》') + .fontSize(14) + .fontColor('#2196F3') + .fontWeight(FontWeight.Medium) + .onClick(() => { + router.pushUrl({ + url: 'pages/agreements/UserAgreement' + }); + }) + + Span('、') + .fontSize(14) + .fontColor('#666666') + + Span('《隐私政策》') + .fontSize(14) + .fontColor('#2196F3') + .fontWeight(FontWeight.Medium) + .onClick(() => { + router.pushUrl({ + url: 'pages/agreements/PrivacyPolicy' + }); + }) + + Span('和') + .fontSize(14) + .fontColor('#666666') + + Span('《数据处理声明》') + .fontSize(14) + .fontColor('#2196F3') + .fontWeight(FontWeight.Medium) + .onClick(() => { + router.pushUrl({ + url: 'pages/agreements/DataProcessingStatement' + }); + }) + } + .width('90%') + .textAlign(TextAlign.Start) + } + .width('100%') + .alignItems(VerticalAlign.Center) + .margin({ top: 10, bottom: 10 }) + + // 提交按钮 + Button() { + if (this.isLoading) { + Row() { + LoadingProgress() + .width(24) + .height(24) + .color(Color.White) + Text('注册中...') + .fontSize(18) + .fontWeight(FontWeight.Medium) + .fontColor(Color.White) + .margin({ left: 12 }) + } + } else { + Text('注册账号') + .fontSize(18) + .fontWeight(FontWeight.Medium) + .fontColor(Color.White) + } + } + .type(ButtonType.Capsule) + .width('100%') + .height(50) + .backgroundColor('#2196F3') + .enabled(!this.isLoading) + .stateEffect(true) + .onClick(() => { + this.handleRegister(); + }) + .margin({ top: 10, bottom: 20 }) + } + .width('100%') + .padding(16) + .backgroundColor(Color.White) + .borderRadius(16) + } + .width('100%') + .padding({ left: 16, right: 16, bottom: 30 }) + } + .width('100%') + .layoutWeight(1) + .scrollBar(BarState.Off) + } + .width('100%') + .height('100%') + .backgroundColor('#F0F5FF') + } +} \ No newline at end of file diff --git a/entry/src/main/resources/base/media/default_avatar.webp b/entry/src/main/resources/base/media/default_avatar.webp new file mode 100644 index 0000000..fa51ee9 Binary files /dev/null and b/entry/src/main/resources/base/media/default_avatar.webp differ diff --git a/entry/src/main/resources/base/media/ic_test.png b/entry/src/main/resources/base/media/ic_test.png new file mode 100644 index 0000000..21dd2d9 Binary files /dev/null and b/entry/src/main/resources/base/media/ic_test.png differ diff --git a/entry/src/main/resources/base/media/logo.png b/entry/src/main/resources/base/media/logo.png new file mode 100644 index 0000000..7a6bd9f Binary files /dev/null and b/entry/src/main/resources/base/media/logo.png differ diff --git a/entry/src/main/resources/base/profile/main_pages.json b/entry/src/main/resources/base/profile/main_pages.json index d70ca8f..6ce49d9 100644 --- a/entry/src/main/resources/base/profile/main_pages.json +++ b/entry/src/main/resources/base/profile/main_pages.json @@ -1,11 +1,18 @@ { "src": [ "pages/login", + "pages/register", "pages/WelcomePage", "pages/HomePage", "pages/SettingsPage", "pages/ClassPage", "pages/TransitionPage", - "pages/ClassLivePage" + "pages/ClassLivePage", + "pages/AccountPage", + "pages/StartPage", + "pages/Index", + "pages/agreements/PrivacyPolicy", + "pages/agreements/UserAgreement", + "pages/agreements/DataProcessingStatement" ] } \ No newline at end of file