feat(entry): 复刻四大主页面与底部导航

This commit is contained in:
AndyChen1024
2026-09-11 20:48:15 +08:00
parent d9f1893f67
commit 58a91134a1
6 changed files with 812 additions and 49 deletions

View File

@@ -4,6 +4,11 @@ import { HttpClient, AuthInterceptor, SessionManager, Logger } from 'common';
import { ServiceLocator } from '../di/ServiceLocator';
import { UserApi } from '../data/api/UserApi';
import { UserRepositoryImpl } from '../data/repository/UserRepository';
import { SessionRepositoryImpl } from '../data/repository/SessionRepository';
import { FakeHomeRepository } from '../data/repository/HomeRepository';
import { FakeCheckinRepository } from '../data/repository/CheckinRepository';
import { FakePlanRepository } from '../data/repository/PlanRepository';
import { FakeProfileRepository } from '../data/repository/ProfileRepository';
const TAG: string = 'EntryAbility';
@@ -29,9 +34,16 @@ export default class EntryAbility extends UIAbility {
httpClient.addInterceptor(new AuthInterceptor());
ServiceLocator.register('httpClient', httpClient);
// 数据层
// 数据层(真实示例)
const userApi = new UserApi(httpClient);
ServiceLocator.register('userRepository', new UserRepositoryImpl(userApi));
// 数据层(明缘四入口,本地 mock)
ServiceLocator.register('sessionRepository', new SessionRepositoryImpl());
ServiceLocator.register('homeRepository', new FakeHomeRepository());
ServiceLocator.register('checkinRepository', new FakeCheckinRepository());
ServiceLocator.register('planRepository', new FakePlanRepository());
ServiceLocator.register('profileRepository', new FakeProfileRepository());
}
onDestroy(): void {

View File

@@ -0,0 +1,232 @@
import { AppColor, AppSpacing, AppFontSize, AppRadius, LoadingView, ErrorState, LoggedOutState, EmptyState, SegmentedControl, Avatar, StatusBadge, StatusPill } from 'uikit';
import { SessionManager, SessionStore } from 'common';
import { CheckinViewModel } from '../viewmodel/CheckinViewModel';
import { CheckinStatus, GroupMember, PersonalCheckinRecord } from '../model/CheckinModels';
/**
* 打卡状态 -> 颜色映射(领域逻辑放在页面层,保持 uikit 通用)。
*/
function checkinStatusColor(status: string): string {
switch (status) {
case CheckinStatus.NORMAL:
return AppColor.statusNormal;
case CheckinStatus.LATE:
return AppColor.statusLate;
case CheckinStatus.LEAVE:
return AppColor.statusLeave;
case CheckinStatus.EARLY_LEAVE:
return AppColor.statusEarlyLeave;
case CheckinStatus.ABSENT:
return AppColor.statusAbsent;
default:
return AppColor.gold;
}
}
/**
* 打卡页:顶部打卡卡 + 小组/个人记录。
*/
@ComponentV2
export struct CheckinPage {
@Local viewModel: CheckinViewModel = CheckinViewModel.create();
@Local session: SessionStore = SessionManager.get();
@Local checkinTab: number = 0;
@Event onGoLogin: () => void;
aboutToAppear(): void {
this.viewModel.load();
}
@Monitor('session.token')
onSessionChange(): void {
this.viewModel.load();
}
build() {
Column() {
if (!this.viewModel.loggedIn) {
LoggedOutState({ message: '登录后即可打卡', onLogin: () => { this.onGoLogin(); } })
} else if (this.viewModel.loading) {
Column() {
LoadingView({ text: '加载中...' })
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
} else if (this.viewModel.error.length > 0) {
ErrorState({ message: this.viewModel.error, onRetry: () => { this.viewModel.load(); } })
} else {
Scroll() {
Column({ space: AppSpacing.md }) {
this.statusCard()
this.records()
}
.width('100%')
.padding(AppSpacing.lg)
}
.width('100%')
.height('100%')
.scrollBar(BarState.Off)
}
}
.width('100%')
.height('100%')
.backgroundColor(AppColor.background)
}
@Builder
statusCard() {
Column({ space: AppSpacing.md }) {
Row({ space: AppSpacing.md }) {
Column({ space: 2 }) {
Text(this.viewModel.record.weekday)
.fontSize(AppFontSize.caption)
.fontColor(AppColor.goldLight)
Text(this.viewModel.record.dayNum)
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor(AppColor.white)
}
.width(64)
.padding({ top: 12, bottom: 12 })
.backgroundColor(AppColor.deepBrown)
.borderRadius(AppRadius.md)
.alignItems(HorizontalAlign.Center)
Column({ space: 4 }) {
Text(this.viewModel.record.checked ? '已打卡' : '未打卡')
.fontSize(22)
.fontWeight(FontWeight.Bold)
.fontColor(this.viewModel.record.checked ? AppColor.statusNormal : AppColor.gold)
Text(this.viewModel.record.checked ? '已完成今日打卡' : '请选择状态完成打卡')
.fontSize(AppFontSize.caption)
.fontColor(AppColor.textMuted)
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
}
Column({ space: 8 }) {
Row({ space: 8 }) {
ForEach(CheckinStatus.ALL.slice(0, 3), (status: string) => {
StatusPill({
label: status,
color: checkinStatusColor(status),
selected: this.viewModel.record.status === status,
onTap: () => { this.viewModel.submit(status); }
})
}, (status: string) => status)
}
Row({ space: 8 }) {
ForEach(CheckinStatus.ALL.slice(3), (status: string) => {
StatusPill({
label: status,
color: checkinStatusColor(status),
selected: this.viewModel.record.status === status,
onTap: () => { this.viewModel.submit(status); }
})
}, (status: string) => status)
}
}
}
.width('100%')
.padding(AppSpacing.md)
.backgroundColor(AppColor.white)
.borderRadius(AppRadius.lg)
}
@Builder
records() {
Column({ space: AppSpacing.md }) {
SegmentedControl({
items: ['小组', '个人'],
selectedIndex: this.checkinTab,
onSelect: (index: number) => { this.checkinTab = index; }
})
if (this.checkinTab === 0) {
this.groupTab()
} else {
this.personalTab()
}
}
.width('100%')
}
@Builder
groupTab() {
Column() {
if (this.viewModel.record.groupMembers.length === 0) {
EmptyState({ emoji: '📭', message: '暂无成员数据' })
} else {
ForEach(this.viewModel.record.groupMembers, (m: GroupMember) => {
Row({ space: AppSpacing.md }) {
Avatar({ name: m.name, diameter: 40 })
Column({ space: 2 }) {
Text(m.name)
.fontSize(AppFontSize.body)
.fontWeight(FontWeight.Medium)
.fontColor(AppColor.textPrimary)
if (m.checkedIn) {
Text('打卡 ' + m.checkinTime)
.fontSize(AppFontSize.caption)
.fontColor(AppColor.textMuted)
}
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
if (m.checkedIn) {
StatusBadge({ label: m.status, color: checkinStatusColor(m.status) })
} else {
Text('未打卡')
.fontSize(AppFontSize.caption)
.fontColor(AppColor.textMuted)
.padding({ left: 10, right: 10, top: 4, bottom: 4 })
.backgroundColor(AppColor.neutralChip)
.borderRadius(AppRadius.pill)
}
}
.width('100%')
.padding({ top: 12, bottom: 12 })
}, (m: GroupMember) => m.id)
}
}
.width('100%')
.backgroundColor(AppColor.white)
.borderRadius(AppRadius.lg)
.padding({ left: 16, right: 16, top: 4, bottom: 4 })
}
@Builder
personalTab() {
Column({ space: AppSpacing.sm }) {
if (this.viewModel.record.personalHistory.length === 0) {
EmptyState({ emoji: '📒', message: '暂无个人打卡记录' })
} else {
ForEach(this.viewModel.record.personalHistory, (h: PersonalCheckinRecord) => {
Row() {
Column({ space: 2 }) {
Text(h.date)
.fontSize(AppFontSize.body)
.fontWeight(FontWeight.Medium)
.fontColor(AppColor.textPrimary)
Text(h.time)
.fontSize(AppFontSize.caption)
.fontColor(AppColor.textMutedLight)
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
StatusBadge({ label: h.status, color: checkinStatusColor(h.status) })
}
.width('100%')
.padding(AppSpacing.md)
.backgroundColor(AppColor.white)
.borderRadius(AppRadius.md)
}, (h: PersonalCheckinRecord) => h.id)
}
}
.width('100%')
}
}

View File

@@ -0,0 +1,155 @@
import { AppColor, AppSpacing, AppFontSize, AppRadius, LoadingView, ErrorState } from 'uikit';
import { SessionManager, SessionStore } from 'common';
import { HomeViewModel } from '../viewmodel/HomeViewModel';
import { HomeModule, IntroCard } from '../model/HomeModels';
/**
* 首页:未登录走游客介绍页,已登录走原生首页(Hero + 模块 + 产品介绍)。
*/
@ComponentV2
export struct HomePage {
@Local viewModel: HomeViewModel = HomeViewModel.create();
@Local session: SessionStore = SessionManager.get();
@Event onGoLogin: () => void;
aboutToAppear(): void {
this.viewModel.load();
}
@Monitor('session.token')
onSessionChange(): void {
this.viewModel.load();
}
build() {
Column() {
if (this.viewModel.loading) {
LoadingView({ text: '加载中...' })
} else if (this.viewModel.error.length > 0) {
ErrorState({ message: this.viewModel.error, onRetry: () => { this.viewModel.load(); } })
} else {
Scroll() {
Column({ space: AppSpacing.md }) {
this.hero()
this.moduleGrid()
this.introSection()
}
.width('100%')
.padding(AppSpacing.lg)
}
.width('100%')
.height('100%')
.scrollBar(BarState.Off)
}
}
.width('100%')
.height('100%')
.backgroundColor(AppColor.background)
}
@Builder
hero() {
Column({ space: AppSpacing.sm }) {
Text(this.viewModel.data.greeting)
.fontSize(AppFontSize.caption)
.fontColor(AppColor.goldLight)
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.backgroundColor(AppColor.goldChip)
.borderRadius(AppRadius.pill)
Text(this.viewModel.data.displayName)
.fontSize(32)
.fontWeight(FontWeight.Bold)
.fontColor(AppColor.white)
Text(this.viewModel.data.encouragement)
.fontSize(AppFontSize.body)
.fontColor(AppColor.goldLight)
if (this.viewModel.isGuest) {
Text('登录账号解锁完整功能 ›')
.fontSize(AppFontSize.caption)
.fontColor(AppColor.goldLight)
.padding({ left: 16, right: 16, top: 8, bottom: 8 })
.backgroundColor(AppColor.goldChipStrong)
.borderRadius(AppRadius.pill)
.onClick(() => {
this.onGoLogin();
})
}
}
.width('100%')
.alignItems(HorizontalAlign.Start)
.padding(AppSpacing.lg)
.borderRadius(AppRadius.xl)
.linearGradient({ angle: 135, colors: [[AppColor.heroStart, 0.0], [AppColor.heroMid, 0.55], [AppColor.heroEnd, 1.0]] })
}
@Builder
moduleGrid() {
Flex({ direction: FlexDirection.Row, wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceBetween }) {
ForEach(this.viewModel.data.modules, (m: HomeModule) => {
Column({ space: AppSpacing.sm }) {
Stack({ alignContent: Alignment.Center }) {
Text(m.emoji)
.fontSize(20)
}
.width(48)
.height(48)
.borderRadius(24)
.backgroundColor(AppColor.neutralChip)
Text(m.title)
.fontSize(AppFontSize.body)
.fontWeight(FontWeight.Bold)
.fontColor(AppColor.textPrimary)
Text(m.description)
.fontSize(AppFontSize.caption)
.fontColor(AppColor.textMuted)
}
.width('31%')
.alignItems(HorizontalAlign.Center)
.padding({ top: 20, bottom: 20, left: 8, right: 8 })
.backgroundColor(AppColor.white)
.borderRadius(AppRadius.lg)
}, (m: HomeModule) => m.id)
}
.width('100%')
}
@Builder
introSection() {
Column({ space: AppSpacing.md }) {
Text('产品介绍')
.fontSize(AppFontSize.title)
.fontWeight(FontWeight.Bold)
.fontColor(AppColor.textPrimary)
ForEach(this.viewModel.data.intros, (intro: IntroCard) => {
Row({ space: AppSpacing.md }) {
Text(intro.emoji)
.fontSize(22)
Column({ space: AppSpacing.xs }) {
Text(intro.heading)
.fontSize(AppFontSize.body)
.fontWeight(FontWeight.Bold)
.fontColor(AppColor.gold)
Text(intro.body)
.fontSize(AppFontSize.caption)
.fontColor(AppColor.textSecondary)
.lineHeight(20)
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
}
.width('100%')
.padding(AppSpacing.md)
.backgroundColor(AppColor.white)
.borderRadius(AppRadius.lg)
}, (intro: IntroCard) => intro.heading)
}
.width('100%')
.alignItems(HorizontalAlign.Start)
}
}

View File

@@ -1,63 +1,60 @@
import { AppColor, AppSpacing, AppButton, LoadingView, EmptyView } from 'uikit';
import { HomeViewModel } from '../viewmodel/HomeViewModel';
import { User } from '../model/User';
import { AppRouter } from '../router/AppRouter';
import { RouteMap, RouteNames } from '../router/RouteMap';
import { StatusCard } from '../view/StatusCard';
import { AppColor, AppFontSize } from 'uikit';
import { HomePage } from './HomePage';
import { CheckinPage } from './CheckinPage';
import { PlanPage } from './PlanPage';
import { ProfilePage } from './ProfilePage';
/**
* 首页(@Entry):Navigation 根容器,仅编排。
* 演示「页面 -> ViewModel -> Repository -> Api -> HttpClient」完整链路。
* 应用入口:底部四 tab 壳(首页 / 打卡 / 计划 / 我的)。
*/
@Entry
@ComponentV2
struct Index {
@Local pathStack: NavPathStack = new NavPathStack();
@Local viewModel: HomeViewModel = HomeViewModel.create();
@Local title: string = 'Harmony Client';
aboutToAppear(): void {
AppRouter.init(this.pathStack);
this.viewModel.load();
}
@Local currentIndex: number = 0;
build() {
Navigation(this.pathStack) {
Column({ space: AppSpacing.md }) {
Text(this.title)
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor(AppColor.textPrimary)
if (this.viewModel.loading) {
LoadingView({ text: '加载中...' })
} else if (this.viewModel.error.length > 0) {
EmptyView({ text: this.viewModel.error })
} else {
ForEach(this.viewModel.users, (user: User) => {
StatusCard({ title: user.name, subtitle: user.email })
}, (user: User) => user.id)
Tabs({ barPosition: BarPosition.End, index: this.currentIndex }) {
TabContent() {
HomePage({ onGoLogin: () => { this.currentIndex = 3; } })
}
.tabBar(this.tabBarBuilder(0, '首页', '🏠'))
AppButton({
text: '进入详情页',
onTap: () => {
AppRouter.push(RouteNames.DETAIL, '来自首页的参数');
TabContent() {
CheckinPage({ onGoLogin: () => { this.currentIndex = 3; } })
}
.tabBar(this.tabBarBuilder(1, '打卡', '✅'))
TabContent() {
PlanPage({ onGoLogin: () => { this.currentIndex = 3; } })
}
.tabBar(this.tabBarBuilder(2, '计划', '📝'))
TabContent() {
ProfilePage()
}
.tabBar(this.tabBarBuilder(3, '我的', '👤'))
}
.onChange((index: number) => {
this.currentIndex = index;
})
}
.width('100%')
.height('100%')
.padding(AppSpacing.lg)
.backgroundColor(AppColor.background)
}
.navDestination(this.routeMap)
.mode(NavigationMode.Stack)
.hideTitleBar(true)
}
@Builder
routeMap(name: string, param: Object) {
RouteMap(name, param)
tabBarBuilder(index: number, title: string, icon: string) {
Column({ space: 2 }) {
Text(icon)
.fontSize(20)
Text(title)
.fontSize(AppFontSize.caption)
.fontColor(index === this.currentIndex ? AppColor.tabActive : AppColor.tabInactive)
.fontWeight(index === this.currentIndex ? FontWeight.Bold : FontWeight.Normal)
}
.width('100%')
.height(52)
.justifyContent(FlexAlign.Center)
.backgroundColor(AppColor.tabBg)
}
}

View File

@@ -0,0 +1,183 @@
import { AppColor, AppSpacing, AppFontSize, AppRadius, LoadingView, ErrorState, LoggedOutState, SegmentedControl, ProgressBar } from 'uikit';
import { SessionManager, SessionStore } from 'common';
import { PlanViewModel } from '../viewmodel/PlanViewModel';
import { PlanType, PlanGroup } from '../model/PlanModels';
/**
* 计划页:周/月/年切换 + 我的计划状态 + 小组进度。
*/
@ComponentV2
export struct PlanPage {
@Local viewModel: PlanViewModel = PlanViewModel.create();
@Local session: SessionStore = SessionManager.get();
@Event onGoLogin: () => void;
aboutToAppear(): void {
this.viewModel.load();
}
@Monitor('session.token')
onSessionChange(): void {
this.viewModel.load();
}
build() {
Column() {
if (!this.viewModel.loggedIn) {
LoggedOutState({ message: '登录后即可提交计划', onLogin: () => { this.onGoLogin(); } })
} else if (this.viewModel.loading) {
Column() {
LoadingView({ text: '加载中...' })
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
} else if (this.viewModel.error.length > 0) {
ErrorState({ message: this.viewModel.error, onRetry: () => { this.viewModel.load(); } })
} else {
Scroll() {
Column({ space: AppSpacing.md }) {
SegmentedControl({
items: PlanType.ALL,
selectedIndex: PlanType.ALL.indexOf(this.viewModel.selectedType),
onSelect: (index: number) => { this.viewModel.selectType(PlanType.ALL[index]); }
})
this.head()
this.myStatusCard()
if (this.viewModel.record.message.length > 0) {
Text(this.viewModel.record.message)
.fontSize(AppFontSize.caption)
.fontColor(AppColor.danger)
.width('100%')
.padding({ top: 10, bottom: 10 })
.textAlign(TextAlign.Center)
.backgroundColor(AppColor.warningBg)
.borderRadius(AppRadius.md)
}
this.groupList()
}
.width('100%')
.padding(AppSpacing.lg)
}
.width('100%')
.height('100%')
.scrollBar(BarState.Off)
}
}
.width('100%')
.height('100%')
.backgroundColor(AppColor.creamDark)
}
@Builder
head() {
Row() {
Column({ space: 2 }) {
Text(this.viewModel.selectedType)
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor(AppColor.textPrimary)
Text(this.viewModel.record.periodKey)
.fontSize(AppFontSize.caption)
.fontColor(AppColor.textMutedLight)
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
Text(this.viewModel.record.myChecked ? '已提交' : '待提交')
.fontSize(AppFontSize.caption)
.fontColor(this.viewModel.record.myChecked ? AppColor.statusNormal : AppColor.gold)
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.backgroundColor(this.viewModel.record.myChecked ? AppColor.successBg : AppColor.warningBg)
.borderRadius(AppRadius.pill)
}
.width('100%')
.padding({ left: 4, right: 4 })
}
@Builder
myStatusCard() {
Row({ space: AppSpacing.md }) {
Stack({ alignContent: Alignment.Center }) {
Text(this.viewModel.record.myChecked ? '✓' : '●')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor(AppColor.white)
}
.width(48)
.height(48)
.borderRadius(24)
.backgroundColor(this.viewModel.record.myChecked ? AppColor.statusNormal : AppColor.danger)
Column({ space: 4 }) {
Text(this.viewModel.record.myChecked ? '已完成' : '未完成')
.fontSize(AppFontSize.caption)
.fontColor(AppColor.textMuted)
Text('我的' + this.viewModel.selectedType)
.fontSize(AppFontSize.title)
.fontWeight(FontWeight.Bold)
.fontColor(AppColor.textPrimary)
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
Text(this.viewModel.record.myChecked ? '修改' : '提交计划')
.fontSize(AppFontSize.body)
.fontColor(AppColor.white)
.padding({ left: 20, right: 20, top: 10, bottom: 10 })
.backgroundColor(AppColor.gold)
.borderRadius(AppRadius.pill)
}
.width('100%')
.padding(AppSpacing.md)
.backgroundColor(AppColor.white)
.borderRadius(AppRadius.lg)
}
@Builder
groupList() {
Column({ space: AppSpacing.sm }) {
Text('小组统计')
.fontSize(AppFontSize.title)
.fontWeight(FontWeight.Bold)
.fontColor(AppColor.textPrimary)
.width('100%')
ForEach(this.viewModel.record.groups, (g: PlanGroup, index: number) => {
Column({ space: AppSpacing.sm }) {
Row({ space: AppSpacing.sm }) {
Text((index + 1).toString())
.fontSize(AppFontSize.caption)
.fontWeight(FontWeight.Bold)
.fontColor(AppColor.gold)
.width(36)
.height(36)
.borderRadius(10)
.textAlign(TextAlign.Center)
.backgroundColor(AppColor.neutralChip)
Text(g.name)
.fontSize(AppFontSize.body)
.fontWeight(FontWeight.Bold)
.fontColor(AppColor.textPrimary)
Blank()
Text('已提交 ' + g.checkedCount + ' · ' + g.memberCount + '人')
.fontSize(AppFontSize.caption)
.fontColor(AppColor.textMuted)
}
.width('100%')
ProgressBar({ percent: g.memberCount > 0 ? Math.round(g.checkedCount / g.memberCount * 100) : 0 })
}
.width('100%')
.padding(AppSpacing.md)
.backgroundColor(AppColor.white)
.borderRadius(AppRadius.lg)
}, (g: PlanGroup) => g.id)
}
.width('100%')
.alignItems(HorizontalAlign.Start)
}
}

View File

@@ -0,0 +1,184 @@
import { AppColor, AppSpacing, AppFontSize, AppRadius, LoadingView, ErrorState, Avatar, LabeledRow } from 'uikit';
import { SessionManager, SessionStore } from 'common';
import { ProfileViewModel } from '../viewmodel/ProfileViewModel';
import { ClassGroup } from '../model/ProfileModels';
/**
* 我的页:资料展示 + 模拟登录 / 退出登录。
*/
@ComponentV2
export struct ProfilePage {
@Local viewModel: ProfileViewModel = ProfileViewModel.create();
@Local session: SessionStore = SessionManager.get();
aboutToAppear(): void {
this.viewModel.load();
}
@Monitor('session.token')
onSessionChange(): void {
this.viewModel.load();
}
build() {
Column() {
if (!this.viewModel.loggedIn) {
this.loginCard()
} else if (this.viewModel.loading) {
Column() {
LoadingView({ text: '加载中...' })
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
} else if (this.viewModel.error.length > 0) {
ErrorState({ message: this.viewModel.error, onRetry: () => { this.viewModel.load(); } })
} else {
Scroll() {
Column({ space: AppSpacing.md }) {
this.profileHeader()
this.infoCard()
if (this.viewModel.profile.classGroups.length > 0) {
this.classGroupCard()
}
this.logoutButton()
}
.width('100%')
.padding(AppSpacing.lg)
}
.width('100%')
.height('100%')
.scrollBar(BarState.Off)
}
}
.width('100%')
.height('100%')
.backgroundColor(AppColor.background)
}
@Builder
loginCard() {
Column({ space: AppSpacing.md }) {
Text('👤')
.fontSize(40)
Text('未登录')
.fontSize(AppFontSize.title)
.fontWeight(FontWeight.Bold)
.fontColor(AppColor.textPrimary)
Text('登录后即可查看个人信息')
.fontSize(AppFontSize.body)
.fontColor(AppColor.textMuted)
Text('模拟登录')
.fontSize(AppFontSize.body)
.fontColor(AppColor.white)
.padding({ left: 32, right: 32, top: 10, bottom: 10 })
.backgroundColor(AppColor.gold)
.borderRadius(AppRadius.pill)
.onClick(() => {
this.viewModel.login();
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
@Builder
profileHeader() {
Row({ space: AppSpacing.md }) {
Avatar({ name: this.viewModel.profile.displayName, diameter: 56 })
Column({ space: 4 }) {
Row({ space: 8 }) {
Text(this.viewModel.profile.displayName)
.fontSize(AppFontSize.headline)
.fontWeight(FontWeight.Bold)
.fontColor(AppColor.textPrimary)
Text(this.viewModel.profile.role)
.fontSize(AppFontSize.caption)
.fontColor(AppColor.gold)
.padding({ left: 10, right: 10, top: 4, bottom: 4 })
.backgroundColor(AppColor.neutralChip)
.borderRadius(AppRadius.pill)
}
Text(this.viewModel.profile.studentId)
.fontSize(AppFontSize.caption)
.fontColor(AppColor.textMuted)
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
}
.width('100%')
.padding(AppSpacing.md)
.backgroundColor(AppColor.white)
.borderRadius(AppRadius.lg)
}
@Builder
infoCard() {
Column({ space: 0 }) {
Text('个人信息')
.fontSize(AppFontSize.title)
.fontWeight(FontWeight.Bold)
.fontColor(AppColor.textPrimary)
.width('100%')
.margin({ bottom: 4 })
LabeledRow({ label: '姓名', value: this.viewModel.profile.name })
LabeledRow({ label: '学号', value: this.viewModel.profile.studentId })
LabeledRow({ label: '手机号', value: this.viewModel.profile.phone })
LabeledRow({ label: '角色', value: this.viewModel.profile.role })
LabeledRow({ label: '小组', value: this.viewModel.profile.group })
LabeledRow({ label: '登录方式', value: this.viewModel.profile.loginType })
}
.width('100%')
.padding(AppSpacing.md)
.backgroundColor(AppColor.white)
.borderRadius(AppRadius.lg)
}
@Builder
classGroupCard() {
Column({ space: 0 }) {
Text('小组分组')
.fontSize(AppFontSize.title)
.fontWeight(FontWeight.Bold)
.fontColor(AppColor.textPrimary)
.width('100%')
.margin({ bottom: 4 })
ForEach(this.viewModel.profile.classGroups, (cg: ClassGroup) => {
Row() {
Text(cg.period)
.fontSize(AppFontSize.body)
.fontColor(AppColor.textMuted)
Blank()
Text(cg.group + ' · ' + cg.role)
.fontSize(AppFontSize.body)
.fontColor(AppColor.textPrimary)
}
.width('100%')
.padding({ top: 12, bottom: 12 })
}, (cg: ClassGroup) => cg.period + cg.group)
}
.width('100%')
.padding(AppSpacing.md)
.backgroundColor(AppColor.white)
.borderRadius(AppRadius.lg)
}
@Builder
logoutButton() {
Text('退出登录')
.fontSize(AppFontSize.body)
.fontColor(AppColor.danger)
.width('100%')
.height(48)
.textAlign(TextAlign.Center)
.backgroundColor(AppColor.white)
.borderRadius(AppRadius.md)
.border({ width: 1, color: AppColor.danger })
.onClick(() => {
this.viewModel.logout();
})
}
}