chore(hmos): 导入生产环境鸿蒙工程
- 以 D:\SmartSenior 生产工程为源,纳入 AppScope、entry、cjagent、hvigor 及根配置 - 移除 build-profile.json5 的 signingConfigs(p12/p7b/cer 路径与加密口令)及 products.signingConfig 引用 - 不纳入本地与开发产物:local.properties、.idea、.hvigor、.codegenie、demo、backup_a2a_native、SESSION_LOG.md、build_err.txt、build/、oh_modules - .gitignore 追加 .codegenie 与密钥文件通配(*.p12/*.p7b/*.cer/*.jks/*.keystore/*.pem)
This commit is contained in:
@@ -0,0 +1,299 @@
|
||||
// 消息通知页(ArkTS 严格类型,无 any)
|
||||
// 数据来源:/auth/notifications/(用户通知)+ /alerts/(预警),合并时间线
|
||||
import { promptAction } from '@kit.ArkUI';
|
||||
import { HdsNavigation, HdsNavigationTitleMode, ScrollEffectType } from '@kit.UIDesignKit';
|
||||
import { hdsMaterial } from '@hms.hds.hdsMaterial';
|
||||
import { BusinessError } from '@kit.BasicServicesKit';
|
||||
import { Page, normalizeError } from '../../common/ApiConstants';
|
||||
import { api, EmptyBody } from '../../utils/Request';
|
||||
import { FontScaleLevel, FONT_SCALE_MAP } from '../../utils/FontScale';
|
||||
import { AuthStore } from '../../utils/AuthStore';
|
||||
|
||||
interface UserNotice {
|
||||
id: number;
|
||||
title: string;
|
||||
content: string;
|
||||
is_read: boolean;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
interface AlertItem {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
// 合并后的信息流条目(source 区分类型)
|
||||
interface FeedItem {
|
||||
key: string;
|
||||
source: string; // 'user' | 'alert'
|
||||
id: number;
|
||||
title: string;
|
||||
content: string;
|
||||
isRead: boolean;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
function formatDateTime(iso: string): string {
|
||||
const d = new Date(iso);
|
||||
if (Number.isNaN(d.getTime())) {
|
||||
return '--';
|
||||
}
|
||||
const mm = String(d.getMonth() + 1).padStart(2, '0');
|
||||
const dd = String(d.getDate()).padStart(2, '0');
|
||||
const hh = String(d.getHours()).padStart(2, '0');
|
||||
const mi = String(d.getMinutes()).padStart(2, '0');
|
||||
return `${mm}/${dd} ${hh}:${mi}`;
|
||||
}
|
||||
|
||||
@Entry
|
||||
@Component
|
||||
struct Notifications {
|
||||
@StorageProp('fontScale') fontScaleLevel: FontScaleLevel = 'system';
|
||||
@State feed: Array<FeedItem> = [];
|
||||
@State loading: boolean = true;
|
||||
@State markingAll: boolean = false;
|
||||
|
||||
private scroller: Scroller = new Scroller();
|
||||
|
||||
private scaleFor(): number {
|
||||
return FONT_SCALE_MAP[this.fontScaleLevel] ?? 1.0;
|
||||
}
|
||||
|
||||
aboutToAppear(): void {
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
private async loadData(): Promise<void> {
|
||||
this.loading = true;
|
||||
try {
|
||||
// 护理预警仅限护理/管理角色:消防、销售不拉取 /alerts/
|
||||
const state = await AuthStore.getState();
|
||||
const role = state.role;
|
||||
const includeAlerts = role === null || role === 'admin' || role === 'nurse' || role === 'staff_nurse';
|
||||
const responses: Array<Page<UserNotice> | Page<AlertItem> | null> = await Promise.all([
|
||||
api.get<Page<UserNotice>>('/auth/notifications/', { page_size: 30 }).catch((): Page<UserNotice> | null => null),
|
||||
includeAlerts
|
||||
? api.get<Page<AlertItem>>('/alerts/', { page_size: 30, ordering: '-created_at' }).catch((): Page<AlertItem> | null => null)
|
||||
: Promise.resolve(null),
|
||||
]);
|
||||
const noticeRes = responses[0] as Page<UserNotice> | null;
|
||||
const alertRes = responses[1] as Page<AlertItem> | null;
|
||||
const items: Array<FeedItem> = [];
|
||||
if (noticeRes) {
|
||||
noticeRes.results.forEach((n: UserNotice) => {
|
||||
items.push({
|
||||
key: `user-${n.id}`,
|
||||
source: 'user',
|
||||
id: n.id,
|
||||
title: n.title,
|
||||
content: n.content,
|
||||
isRead: n.is_read,
|
||||
createdAt: n.created_at,
|
||||
});
|
||||
});
|
||||
}
|
||||
if (alertRes) {
|
||||
alertRes.results.forEach((a: AlertItem) => {
|
||||
items.push({
|
||||
key: `alert-${a.id}`,
|
||||
source: 'alert',
|
||||
id: a.id,
|
||||
title: a.title,
|
||||
content: a.description,
|
||||
isRead: true,
|
||||
createdAt: a.created_at,
|
||||
});
|
||||
});
|
||||
}
|
||||
items.sort((a: FeedItem, b: FeedItem): number => {
|
||||
return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
|
||||
});
|
||||
this.feed = items;
|
||||
} catch (err) {
|
||||
promptAction.showToast({ message: normalizeError(err as BusinessError | Error) });
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async markRead(item: FeedItem): Promise<void> {
|
||||
if (item.source !== 'user' || item.isRead) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await api.post(`/auth/notifications/${item.id}/mark-read/`, new EmptyBody());
|
||||
this.feed = this.feed.map((f: FeedItem): FeedItem => {
|
||||
if (f.key === item.key) {
|
||||
return { key: f.key, source: f.source, id: f.id, title: f.title, content: f.content, isRead: true, createdAt: f.createdAt } as FeedItem;
|
||||
}
|
||||
return f;
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('[Notifications] mark read failed:', normalizeError(err as BusinessError | Error));
|
||||
}
|
||||
}
|
||||
|
||||
private async markAllRead(): Promise<void> {
|
||||
if (this.markingAll) {
|
||||
return;
|
||||
}
|
||||
this.markingAll = true;
|
||||
try {
|
||||
await api.post('/auth/notifications/mark-all-read/', new EmptyBody());
|
||||
this.feed = this.feed.map((f: FeedItem): FeedItem =>
|
||||
({ key: f.key, source: f.source, id: f.id, title: f.title, content: f.content, isRead: true, createdAt: f.createdAt }) as FeedItem);
|
||||
promptAction.showToast({ message: '已全部标记为已读' });
|
||||
} catch (err) {
|
||||
promptAction.showToast({ message: normalizeError(err as BusinessError | Error) });
|
||||
} finally {
|
||||
this.markingAll = false;
|
||||
}
|
||||
}
|
||||
|
||||
private hasUnread(): boolean {
|
||||
return this.feed.some((f: FeedItem) => f.source === 'user' && !f.isRead);
|
||||
}
|
||||
|
||||
@Builder
|
||||
FeedCard(key: string) {
|
||||
Column({ space: 8 }) {
|
||||
Row({ space: 8 }) {
|
||||
if (!this.feedItem(key).isRead) {
|
||||
Circle()
|
||||
.width(8)
|
||||
.height(8)
|
||||
.fill($r('app.color.accent_red'))
|
||||
}
|
||||
Text(this.feedItem(key).source === 'user' ? '通知' : '预警')
|
||||
.fontSize(Math.round(11 * this.scaleFor()))
|
||||
.fontColor(this.feedItem(key).source === 'user' ? $r('app.color.accent_blue') : '#FF9500')
|
||||
.padding({ left: 6, right: 6, top: 2, bottom: 2 })
|
||||
.borderRadius(6)
|
||||
.border({ width: 1, color: this.feedItem(key).source === 'user' ? $r('app.color.accent_blue') : '#FF9500' })
|
||||
Blank()
|
||||
Text(formatDateTime(this.feedItem(key).createdAt))
|
||||
.fontSize(Math.round(12 * this.scaleFor()))
|
||||
.fontColor($r('app.color.inactive_tab'))
|
||||
}
|
||||
.width('100%')
|
||||
|
||||
Text(this.feedItem(key).title)
|
||||
.fontSize(Math.round(15 * this.scaleFor()))
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor($r('app.color.text_primary'))
|
||||
.maxLines(1)
|
||||
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
||||
.width('100%')
|
||||
|
||||
if (this.feedItem(key).content && this.feedItem(key).content.length > 0) {
|
||||
Text(this.feedItem(key).content)
|
||||
.fontSize(Math.round(13 * this.scaleFor()))
|
||||
.fontColor($r('app.color.text_secondary'))
|
||||
.maxLines(2)
|
||||
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
||||
.width('100%')
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
.padding(16)
|
||||
.backgroundColor($r('app.color.background_card'))
|
||||
.borderRadius(14)
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.onClick(() => {
|
||||
this.markRead(this.feedItem(key));
|
||||
})
|
||||
}
|
||||
|
||||
// 按 key 从 @State feed 读取(保证 markAllRead 后重渲红点)
|
||||
private feedItem(key: string): FeedItem {
|
||||
const found = this.feed.find((f: FeedItem) => f.key === key);
|
||||
if (found) {
|
||||
return found;
|
||||
}
|
||||
return { key: key, source: 'user', id: 0, title: '', content: '', isRead: true, createdAt: '' };
|
||||
}
|
||||
|
||||
build() {
|
||||
HdsNavigation() {
|
||||
Column() {
|
||||
if (this.loading) {
|
||||
Text('加载中...')
|
||||
.fontSize(Math.round(14 * this.scaleFor()))
|
||||
.fontColor($r('app.color.inactive_tab'))
|
||||
.margin({ top: 60 })
|
||||
} else if (this.feed.length === 0) {
|
||||
Column({ space: 8 }) {
|
||||
Text('🔔')
|
||||
.fontSize(Math.round(32 * this.scaleFor()))
|
||||
Text('暂无消息通知')
|
||||
.fontSize(Math.round(15 * this.scaleFor()))
|
||||
.fontColor($r('app.color.text_secondary'))
|
||||
}
|
||||
.width('100%')
|
||||
.margin({ top: 80 })
|
||||
.alignItems(HorizontalAlign.Center)
|
||||
} else {
|
||||
Scroll(this.scroller) {
|
||||
Column({ space: 10 }) {
|
||||
if (this.hasUnread()) {
|
||||
Row() {
|
||||
Text(`共 ${this.feed.length} 条 · 有新通知`)
|
||||
.fontSize(Math.round(13 * this.scaleFor()))
|
||||
.fontColor($r('app.color.text_secondary'))
|
||||
Blank()
|
||||
Text(this.markingAll ? '处理中...' : '全部已读')
|
||||
.fontSize(Math.round(14 * this.scaleFor()))
|
||||
.fontColor($r('app.color.accent_blue'))
|
||||
.onClick(() => {
|
||||
this.markAllRead();
|
||||
})
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: 4, right: 4, top: 8, bottom: 8 })
|
||||
}
|
||||
ForEach(this.feed, (item: FeedItem) => {
|
||||
this.FeedCard(item.key)
|
||||
}, (item: FeedItem) => item.key)
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: 16, right: 16, top: 108, bottom: 24 })
|
||||
}
|
||||
.width('100%')
|
||||
.layoutWeight(1)
|
||||
.scrollBar(BarState.Auto)
|
||||
.edgeEffect(EdgeEffect.Spring, { alwaysEnabled: true })
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
}
|
||||
.mode(NavigationMode.Stack)
|
||||
.titleBar({
|
||||
content: {
|
||||
title: {
|
||||
mainTitle: '消息通知',
|
||||
},
|
||||
},
|
||||
style: {
|
||||
scrollEffectOpts: {
|
||||
enableScrollEffect: false,
|
||||
scrollEffectType: ScrollEffectType.GRADIENT_BLUR,
|
||||
},
|
||||
systemMaterialEffect: {
|
||||
materialType: hdsMaterial.MaterialType.ADAPTIVE,
|
||||
materialLevel: hdsMaterial.MaterialLevel.EXQUISITE,
|
||||
},
|
||||
},
|
||||
avoidLayoutSafeArea: false,
|
||||
enableComponentSafeArea: false,
|
||||
})
|
||||
.bindToScrollable([this.scroller])
|
||||
.hideBackButton(false)
|
||||
.titleMode(HdsNavigationTitleMode.MINI)
|
||||
.ignoreLayoutSafeArea([LayoutSafeAreaType.SYSTEM], [LayoutSafeAreaEdge.TOP, LayoutSafeAreaEdge.BOTTOM])
|
||||
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
|
||||
.backgroundColor($r('app.color.background'))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user