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:
CAPTAIN1275
2026-09-29 18:44:37 +08:00
parent d7f0e2f8a8
commit baf307e9d5
184 changed files with 31334 additions and 0 deletions
@@ -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'))
}
}