chore: save current workspace changes

This commit is contained in:
clz
2026-08-05 14:11:51 +08:00
parent f947f3d2f8
commit 56ad003e04
329 changed files with 27577 additions and 3 deletions
@@ -0,0 +1,21 @@
import identifier from '@ohos.identifier.oaid';
import hilog from '@ohos.hilog';
import { BusinessError } from '@ohos.base';
export class DeviceInfoHolder {
init() {
try {
identifier.getOAID().then((data) => {
const oaid: string = data;
hilog.info(0x0000, 'mhy', '%{public}s', `get oaid by promise success, oaid: ${oaid}`);
}).catch((err: BusinessError) => {
hilog.info(0x0000, 'mhy', '%{public}s', `get oaid by promise failed, code: ${err.code}, message: ${err.message}`);
})
} catch(err) {
let e: BusinessError = err as BusinessError;
console.error('createDeviceManager errCode:' + e.code + ',errMessage:' + e.message);
}
}
}
@@ -0,0 +1,301 @@
/**
* 交易日志转换管理器
* 负责将推送数据库中的数据转换到交易日志数据库
*/
import { TradeDailyPushDataManager } from './TradeDailyPushDataManager';
import { TradeDailyPushModel } from './TradeDailyPushModel';
import { TradeDailyDataManager, TradeDailyModel, TradeMsgUniqueIdManager } from 'biz_trade';
import { PushType } from 'biz_trade/src/main/ets/service/push/PushType';
import { AppUtil } from '@pura/harmony-utils';
import { HXLog } from 'biz_common';
import { CodeTableManager } from 'lib_codetablemanager';
import { AbilityConstant, ApplicationStateChangeCallback, common, Context } from '@kit.AbilityKit';
const TAG = 'TradeDailyConvertManager';
/**
* 解析结果接口
*/
interface ParseResult {
dailyTitle: string;
dailyContent: string;
dailyTime: string;
}
export class TradeDailyConvertManager {
private static instance: TradeDailyConvertManager;
private applicationStateChangeCallBack: ApplicationStateChangeCallback = {
onApplicationForeground: (): void => {
this.convertDailyFromPush();
},
onApplicationBackground: (): void => {
}
}
private constructor() {
}
/**
* 获取单例实例
*/
static getInstance(): TradeDailyConvertManager {
if (!TradeDailyConvertManager.instance) {
TradeDailyConvertManager.instance = new TradeDailyConvertManager();
}
return TradeDailyConvertManager.instance;
}
async registerForeground(context: Context): Promise<void> {
context.getApplicationContext().on('applicationStateChange', this.applicationStateChangeCallBack)
await this.convertDailyFromPush();
}
/**
* 应用进入前台时转换推送数据
* 将推送数据库中的数据转换到交易日志数据库
*/
async convertDailyFromPush(): Promise<void> {
try {
HXLog.i(TAG, '开始转换推送数据到交易日志');
// 查询所有推送日志
const pushDailys = await TradeDailyPushDataManager.getInstance().queryDaily();
if (pushDailys.length === 0) {
HXLog.i(TAG, '没有需要转换的推送数据');
return;
}
HXLog.i(TAG, `查询到 ${pushDailys.length} 条推送数据`);
// 转换每条推送日志
for (const pushDaily of pushDailys) {
// 过滤放弃行权的推送(单号不变,无法去重)
if (this.shouldSkipPushDaily(pushDaily)) {
HXLog.i(TAG, `跳过放弃行权推送: ${pushDaily.body}`);
continue;
}
await this.convertPushDailyToTradeDaily(pushDaily);
}
// 删除已转换的推送日志
await TradeDailyPushDataManager.getInstance().deleteDaily(pushDailys);
HXLog.i(TAG, '推送数据转换完成');
} catch (error) {
HXLog.e(TAG, `转换推送数据失败: ${error}`);
}
}
/**
* 判断是否应该跳过该推送日志
* @param pushDaily 推送日志
*/
private shouldSkipPushDaily(pushDaily: TradeDailyPushModel): boolean {
const type = pushDaily.type
// 放弃行权push不记录(单号不变,无法去重)
const abandonExercise = AppUtil.getContext().resourceManager.getStringSync($r('app.string.trade_daily_abandon_exercise'));
if ((type == PushType.TY_OPTION_EXEC_WEITUO ||
type == PushType.TY_OPTION_EXEC_CHEDAN) &&
pushDaily.body.includes(abandonExercise)) {
return true;
}
return false;
}
/**
* 将推送日志转换为交易日志
* @param pushDaily 推送日志
*/
private async convertPushDailyToTradeDaily(pushDaily: TradeDailyPushModel): Promise<void> {
try {
const isSame = await TradeMsgUniqueIdManager.getInstance().isSameIdentify(
pushDaily.time,
pushDaily.iconType,
pushDaily.type,
pushDaily.orderNo
);
if (isSame) {
HXLog.i(TAG, `推送已存在,跳过: ${pushDaily.tradeDate} ${pushDaily.time}`);
return;
}
const body = pushDaily.body;
const title = pushDaily.title;
// 解析推送内容
const parseResult = await this.parsePushContent(body, title, pushDaily);
const dailyTitle = parseResult.dailyTitle;
const dailyContent = parseResult.dailyContent;
const dailyTime = parseResult.dailyTime;
// 创建交易日志模型
const dailyModel = TradeDailyModel.createWithDateTime(
pushDaily.tradeDate,
dailyTime,
dailyTitle,
dailyContent
);
// 插入到所有匹配的账户表中
await this.insertToMatchingAccounts(dailyModel, pushDaily.tradeNo, pushDaily.qsid);
HXLog.i(TAG, `转换推送日志成功: ${pushDaily.tradeDate} ${dailyTime} ${dailyTitle}`);
} catch (error) {
HXLog.e(TAG, `转换推送日志失败: ${error}`);
}
}
/**
* 解析推送内容
* @param body 推送内容
* @param title 推送标题
* @param pushDaily 推送日志
*/
private async parsePushContent(body: string, title: string, pushDaily: TradeDailyPushModel): Promise<ParseResult> {
let dailyTitle = '';
let dailyContent = '';
let dailyTime = pushDaily.tradeTime;
const iconType = pushDaily.iconType;
const orderNo = this.getOrderNo(pushDaily);
const orderNoPrefix = AppUtil.getContext().resourceManager.getStringSync($r('app.string.trade_daily_order_no_prefix_with_comma'));
const transactionReport = AppUtil.getContext().resourceManager.getStringSync($r('app.string.trade_daily_transaction_report'));
// 错误类型处理(iconType == 3 表示错误)
if (iconType == '3') {
dailyContent = body;
dailyTime = pushDaily.tradeTime;
// 提取时间 [15:01:30]
const firstIndex = body.indexOf('[');
const lastIndex = body.indexOf(']');
if (firstIndex > -1 && lastIndex > -1) {
const timeStr = body.substring(firstIndex + 1, lastIndex);
dailyTime = timeStr.replace(/:/g, '');
// 从 body 末��减去时间部分(包括括号)
dailyContent = body.substring(0, body.length - timeStr.length - 4);
}
if (orderNo.length > 0) {
dailyContent += orderNoPrefix + orderNo;
}
} else {
// 正常类型处理
const firstIndex = body.indexOf('(');
const lastIndex = body.indexOf(')');
if (firstIndex > -1 && lastIndex > -1) {
// 标题处理
if (!title.includes(transactionReport)) {
dailyTitle += title;
}
dailyTitle += body.substring(0, firstIndex);
// 内容处理
const alertContent = body.substring(firstIndex + 1, lastIndex);
const alertContentArr = alertContent.split(',');
if (alertContentArr.length > 0) {
const tradeCode = alertContentArr[0];
const stockInfo = await CodeTableManager.get().getStockInfoByTradeCode(tradeCode);
const stockName = stockInfo?.stockName;
if (stockName && stockName.length > 0) {
// 将合约代码替换为合约名
dailyContent += stockName;
for (let i = 1; i < alertContentArr.length; i++) {
dailyContent += ',' + alertContentArr[i];
}
} else {
dailyContent = alertContent;
}
} else {
dailyContent = alertContent;
}
if (orderNo.length > 0) {
dailyContent += orderNoPrefix + orderNo;
}
// 时间处理
// iOS: timeFromBody = body.substring(loc: last + 2, len: body.count - last - 3)
dailyTime = pushDaily.tradeTime;
if (dailyTime.length === 0 && lastIndex + 2 < body.length && body.length - lastIndex - 3 > 0) {
const startPos = lastIndex + 2;
const length = body.length - lastIndex - 3;
dailyTime = body.substring(startPos, startPos + length);
}
}
}
// 移除时间中的冒号
dailyTime = dailyTime.replace(/:/g, '');
return { dailyTitle, dailyContent, dailyTime };
}
/**
* 获取订单号
* @param pushDaily 推送日志
*/
private getOrderNo(pushDaily: TradeDailyPushModel): string {
const type = pushDaily.type;
let orderNo = '';
// 期货成交2,期权成交8,需要用orderSysId
if (type == PushType.TY_JIAOYI_CHENGGONG || type == PushType.TY_OPTION_CHEGNJIAO) {
orderNo = pushDaily.orderSysId || pushDaily.orderNo;
}
// 条件单触发,preOrderNo有值则是附加条件单,用母单编号
else if (type == PushType.TY_CONDITION && pushDaily.preOrderNo) {
orderNo = pushDaily.preOrderNo;
}
else {
orderNo = pushDaily.orderNo;
}
// 移除空格
return orderNo.replace(/\s/g, '');
}
/**
* 插入到所有匹配的账户表中
* @param model 交易日志模型
* @param accountId 账户ID
* @param qsId 券商ID
*/
private async insertToMatchingAccounts(
model: TradeDailyModel,
accountId: string,
qsId: string
): Promise<void> {
try {
// 获取所有表名
const tableNames = await TradeDailyDataManager.getInstance().getAllTableNames();
// 匹配 trade_daily_{accountId}_{qsId}_{wtId} 格式
const prefix = `trade_daily_${accountId}_${qsId}_`;
for (const tableName of tableNames) {
if (tableName.startsWith(prefix)) {
const wtId = tableName.substring(prefix.length);
await TradeDailyDataManager.getInstance().insertDailyToAccount(
model,
accountId,
qsId,
wtId
);
HXLog.i(TAG, `插入到账户表: ${tableName}`);
}
}
} catch (error) {
HXLog.e(TAG, `插入到匹配账户失败: ${error}`);
}
}
}
@@ -0,0 +1,282 @@
/**
* 交易推送日志数据管理器
*/
import relationalStore from '@ohos.data.relationalStore';
import { TradeDailyPushModel } from './TradeDailyPushModel';
import { BusinessError } from '@ohos.base';
import hilog from '@ohos.hilog';
const DB_NAME = 'trade_push_daily.db';
const TAG = 'TradeDailyPushDataManager'
const DOMAIN = 0xFF01
const FORMAT = '%{public}s'
export class TradeDailyPushDataManager {
private static instance: TradeDailyPushDataManager;
private rdbStore: relationalStore.RdbStore | null = null;
private isInitialized: boolean = false;
private constructor() {
}
/**
* 获取单例实例
*/
static getInstance(): TradeDailyPushDataManager {
if (!TradeDailyPushDataManager.instance) {
TradeDailyPushDataManager.instance = new TradeDailyPushDataManager();
}
return TradeDailyPushDataManager.instance;
}
/**
* 初始化数据库
*/
async initDB(context: ESObject): Promise<void> {
if (this.isInitialized && this.rdbStore) {
return;
}
try {
const config: relationalStore.StoreConfig = {
name: DB_NAME,
securityLevel: relationalStore.SecurityLevel.S1,
customDir: 'trade'
};
this.rdbStore = await relationalStore.getRdbStore(context, config);
await this.createTable();
this.isInitialized = true;
} catch (err) {
const error = err as BusinessError;
hilog.error(DOMAIN, TAG, FORMAT, `初始化数据库失败: ${error.code}, ${error.message}`);
}
}
/**
* 创建表
*/
private async createTable(): Promise<void> {
if (!this.rdbStore) {
return;
}
try {
const sql = TradeDailyPushModel.getTableSQL();
await this.rdbStore.executeSql(sql);
} catch (err) {
const error = err as BusinessError;
hilog.error(DOMAIN, TAG, FORMAT, `创建表失败: ${error.code}, ${error.message}`);
}
}
/**
* 插入数据
*/
async insertDaily(daily: TradeDailyPushModel): Promise<void> {
if (!this.rdbStore) {
hilog.error(DOMAIN, TAG, FORMAT, '数据库未初始化');
return;
}
try {
const valueBucket = daily.toDbRecord();
await this.rdbStore.insert(TradeDailyPushModel.getTableName(), valueBucket);
hilog.info(DOMAIN, TAG, FORMAT, '插入数据成功')
} catch (err) {
const error = err as BusinessError;
hilog.error(DOMAIN, TAG, FORMAT, `插入数据失败: ${error.code}, ${error.message}`);
}
}
/**
* 删除数据
*/
async deleteDaily(models: TradeDailyPushModel[]): Promise<void> {
if (!this.rdbStore) {
hilog.error(DOMAIN, TAG, FORMAT, '数据库未初始化');
return;
}
try {
const tableName = TradeDailyPushModel.getTableName();
for (const model of models) {
if (!model.uniqueId) {
continue;
}
const predicates = new relationalStore.RdbPredicates(tableName);
predicates.equalTo('uniqueId', model.uniqueId);
await this.rdbStore.delete(predicates);
}
} catch (err) {
const error = err as BusinessError;
hilog.error(DOMAIN, TAG, FORMAT, `删除数据失败: ${error.code}, ${error.message}`);
}
}
/**
* 查询所有数据
*/
async queryDaily(): Promise<TradeDailyPushModel[]> {
if (!this.rdbStore) {
hilog.error(DOMAIN, TAG, FORMAT, '数据库未初始化');
return [];
}
let resultSet: relationalStore.ResultSet | null = null;
try {
const tableName = TradeDailyPushModel.getTableName();
const predicates = new relationalStore.RdbPredicates(tableName);
resultSet = await this.rdbStore.query(predicates, TradeDailyPushModel.getColumns());
const models: TradeDailyPushModel[] = [];
if (resultSet.rowCount === 0) {
return models;
}
resultSet.goToFirstRow();
do {
const record: relationalStore.ValuesBucket = {};
const columns = TradeDailyPushModel.getColumns();
for (const column of columns) {
const columnIndex = resultSet.getColumnIndex(column);
if (columnIndex >= 0) {
record[column] = resultSet.getString(columnIndex);
}
}
const model = TradeDailyPushModel.fromDbRecord(record);
models.push(model);
} while (resultSet.goToNextRow());
return models;
} catch (err) {
const error = err as BusinessError;
hilog.error(DOMAIN, TAG, FORMAT, `查询数据失败: ${error.code}, ${error.message}`);
return [];
} finally {
resultSet?.close();
}
}
/**
* 将 pushInfo 转换为 TradeDailyPushModel
* @param pushInfo 推送信息
* @returns 转换后的模型,若转换失败则返回 null
*/
convertToTradeDailyPushModel(pushInfo: ESObject): TradeDailyPushModel | null {
const body = pushInfo['DESC'] as string;
const msg = pushInfo['APPMSG'] as Map<string, object>;
if (!msg) {
return null;
}
const tradeNo = msg['tradeno'] as string;
const qsid = msg['qsid'] as string;
if (!tradeNo || !qsid) {
return null;
}
const model = new TradeDailyPushModel();
model.body = body ?? '';
model.tradeNo = tradeNo;
model.qsid = qsid;
const orderNo = msg['orderNumber'] as string;
if (orderNo) {
model.orderNo = orderNo;
}
// 期货成交2,期权成交8,需要用orderSysId
const orderSysId = msg['orderSysId'] as string;
if (orderSysId) {
model.orderSysId = orderSysId;
}
// 条件单触发, preOrderNo有值则是附加条件单,用母单编号
const preOrderNo = msg['preOrderNumber'] as string;
if (preOrderNo) {
model.preOrderNo = preOrderNo;
}
const uniqueid = msg['uniqueid'] as string;
if (uniqueid) {
model.uniqueId = uniqueid;
}
const tradeTime = msg['ST2'] as string;
if (tradeTime) {
model.tradeTime = tradeTime;
model.time = tradeTime;
}
// 日期用收到通知的日期
const now = new Date();
model.tradeDate = this.formatDate(now, 'yyyyMMdd');
const iconType = msg['IC'] as string;
if (iconType) {
model.iconType = iconType;
}
const type = msg['TY'] as string;
if (type) {
model.type = type;
// 期货撤单3、期权撤单9、期权行权撤单52,这几种后端给的委托时间,不对,需要用收到回报的时间
const typeNum = parseInt(type);
if (typeNum === 3 || typeNum === 9 || typeNum === 52) {
model.tradeTime = this.formatDate(now, 'HH:mm:ss');
// 推送中的已撤单给过滤了,和标题重复,产品不想要
model.body = body.replace(',已撤单', '');
}
}
const title = msg['ST'] as string;
if (title) {
model.title = title;
}
return model;
}
/**
* 记录推送日志
*/
async recordPushDaily(model: TradeDailyPushModel): Promise<void> {
hilog.info(DOMAIN, TAG, FORMAT, `[daily] notification-extension push daily: ${model.tradeDate} ${model.time} ${model.title} ${model.body}`);
const type = parseInt(model.type);
// 过滤条件单(5)和盈亏(6)类型
if (type === 5 || type === 6) {
return;
}
await this.insertDaily(model);
}
/**
* 格式化日期
*/
private formatDate(date: Date, format: string): string {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
if (format === 'yyyyMMdd') {
return `${year}${month}${day}`;
} else if (format === 'HH:mm:ss') {
return `${hours}:${minutes}:${seconds}`;
}
return '';
}
}
@@ -0,0 +1,132 @@
/**
* 交易推送日报数据模型
* Created by wenzhe on 2022/6/6.
* Copyright © 2022 hexin. All rights reserved.
*/
import relationalStore from '@ohos.data.relationalStore';
export class TradeDailyPushModel {
/** 交易账号 */
tradeNo: string = '';
/** qsId */
qsid: string = '';
/** 订单号 */
orderNo: string = '';
/** 订单号(已成交) */
orderSysId: string = '';
/** 母单号(附加条件单) */
preOrderNo: string = '';
/** 消息唯一号 */
uniqueId: string = '';
/** 交易类型 */
type: string = '';
/** 交易时间(app显示) */
tradeTime: string = '';
/** 交易时间(后端推的时间) */
time: string = '';
/** 交易日期 */
tradeDate: string = '';
/** 标题 */
title: string = '';
/** 消息内容 */
body: string = '';
/** icon类型 */
iconType: string = '';
constructor() {
}
/**
* 获取表名
*/
static getTableName(): string {
return 'trade_push_daily';
}
/**
* 获取表结构SQL
*/
static getTableSQL(): string {
return `CREATE TABLE IF NOT EXISTS ${TradeDailyPushModel.getTableName()} (
id INTEGER PRIMARY KEY AUTOINCREMENT,
body TEXT,
tradeNo TEXT,
qsid TEXT,
orderNo TEXT,
orderSysId TEXT,
preOrderNo TEXT,
uniqueId TEXT,
type TEXT,
tradeTime TEXT,
time TEXT,
tradeDate TEXT,
title TEXT,
iconType TEXT
)`;
}
/**
* 获取所有列名
*/
static getColumns(): string[] {
return [
'id',
'body',
'tradeNo',
'qsid',
'orderNo',
'orderSysId',
'preOrderNo',
'uniqueId',
'type',
'tradeTime',
'time',
'tradeDate',
'title',
'iconType'
];
}
/**
* 从数据库记录转换为模型对象
*/
static fromDbRecord(record: relationalStore.ValuesBucket): TradeDailyPushModel {
const model = new TradeDailyPushModel();
model.body = (record['body'] as string) ?? '';
model.tradeNo = (record['tradeNo'] as string) ?? '';
model.qsid = (record['qsid'] as string) ?? '';
model.orderNo = (record['orderNo'] as string) ?? '';
model.orderSysId = (record['orderSysId'] as string) ?? '';
model.preOrderNo = (record['preOrderNo'] as string) ?? '';
model.uniqueId = (record['uniqueId'] as string) ?? '';
model.type = (record['type'] as string) ?? '';
model.tradeTime = (record['tradeTime'] as string) ?? '';
model.time = (record['time'] as string) ?? '';
model.tradeDate = (record['tradeDate'] as string) ?? '';
model.title = (record['title'] as string) ?? '';
model.iconType = (record['iconType'] as string) ?? '';
return model;
}
/**
* 转换为数据库记录
*/
toDbRecord(): relationalStore.ValuesBucket {
const record: relationalStore.ValuesBucket = {};
record['body'] = this.body;
record['tradeNo'] = this.tradeNo;
record['qsid'] = this.qsid;
record['orderNo'] = this.orderNo;
record['orderSysId'] = this.orderSysId;
record['preOrderNo'] = this.preOrderNo;
record['uniqueId'] = this.uniqueId;
record['type'] = this.type;
record['tradeTime'] = this.tradeTime;
record['time'] = this.time;
record['tradeDate'] = this.tradeDate;
record['title'] = this.title;
record['iconType'] = this.iconType;
return record;
}
}
@@ -0,0 +1,95 @@
import { HXLog, PreferenceService } from 'biz_common';
import { JSBaseEvent } from 'biz_webview';
import { common } from '@kit.AbilityKit';
/**
* 撤回同意隐私政策协议
*/
export class PrivacyWithDrawEvent extends JSBaseEvent {
getName(): string {
return "privacyWithdraw";
}
getHandler(): Function {
return (data: object, handler: Function) => {
//TODO这里提了ir单确认能否改成自定义弹窗,否则UI效果不统一
AlertDialog.show(
{
title: '撤回同意将退出同花顺,我们无法再为您提供服务',
//subtitle: 'subtitle',
message: '',
autoCancel: true,
alignment: DialogAlignment.Center,
gridCount: 4,
showInSubWindow: true,
isModal: true,
offset: { dx: 0, dy: -20 },
buttonDirection: DialogButtonDirection.HORIZONTAL,
buttons: [
{
value: '撤回并退出',
action: () => {
PreferenceService.putVal('Privacy', 'isUserAgree', false, true);
(getContext(this) as common.UIAbilityContext).terminateSelf(() => {});
}
},
{
value: '继续使用',
action: () => {
}
},
],
cancel: () => {
console.info('Closed callbacks')
}
}
)
}
}
dialogController: CustomDialogController = new CustomDialogController({
builder: CustomDialogExample({
cancel: ()=> { this.onCancel() },
confirm: ()=> { this.onAccept() },
}),
})
onCancel() {
console.info('Callback when the first button is clicked')
}
onAccept() {
console.info('Callback when the second button is clicked')
}
}
@CustomDialog
struct CustomDialogExample {
cancel?: () => void
confirm?: () => void
controller: CustomDialogController
build() {
Column() {
Text('我是内容').fontSize(20).margin({ top: 10, bottom: 10 })
Flex({ justifyContent: FlexAlign.SpaceAround }) {
Button('cancel')
.onClick(() => {
this.controller.close()
if (this.cancel) {
this.cancel()
}
}).backgroundColor(0xffffff).fontColor(Color.Black)
Button('confirm')
.onClick(() => {
this.controller.close()
if (this.confirm) {
this.confirm()
}
}).backgroundColor(0xffffff).fontColor(Color.Red)
}.margin({ bottom: 10 })
}
}
}
@@ -0,0 +1,15 @@
import BackupExtensionAbility, {BundleVersion} from '@ohos.application.BackupExtensionAbility';
import MmkvTransferManager from './MMKVManager'
const TAG = `FileBackupExtensionAbility`;
export default class BackupExtension extends BackupExtensionAbility {
async onBackup () {
console.info(TAG, `onBackup ok`);
}
async onRestore (bundleVersion : BundleVersion) {
console.info(TAG, `onRestore ok ${JSON.stringify(bundleVersion)}`);
console.info(TAG, `onRestore end`);
new MmkvTransferManager().transfer(this.context)
}
}
@@ -0,0 +1,231 @@
import common from '@ohos.app.ability.common';
import fs from '@ohos.file.fs';
import { MMKV} from '@ohos/mmkv'
import dataPreferences from '@ohos.data.preferences';
// mmkv值类型定义
type MmkvValueType = number | Set<string> | string | boolean;
/**
* 双->单 mmkv文件迁移转换管理
*
* @since 2024-04-20
*/
export default class MmkvTransferManager {
/**
* 双->单 mmkv文件转换
*当前例子中:
*
XXAJmmkvX177): source_mmkv source_
mmkv. crc
* 单的mmkv文件名为:target_
mmkv
*
* @param context context上下文
*/
public async transfer(context: common.Context): Promise<void> {
//第1步,move双框架mmkv文件到单框架指定目录
let result = await this.moveMmkvFile(context);
console.log("FileBackupExtensionAbility", "startMove")
if (!result) {
console.log("FileBackupExtensionAbility", "moveFailed")
return;
}
// 第2步,解析双的mmkv的内容
let dataMap = this.parseSourceMmkvInfo(context);
console.log("FileBackupExtensionAbility", "dataMap" + JSON.stringify(dataMap.get("stringData")));
// 第3步,将解析的双的mmkv中的内容保存到单的mmkv中
if (!this.insertToTargetMmkv(context, dataMap)) {
console.log("FileBackupExtensionAbility", "failed to insert target")
// 数据插入失败的处理逻辑,自行实现
return;
}
// 第4步,从单的mmkv中读取数据
let resultMap = this.getDataFromTarget(context);
let mmkvData = resultMap.get("legacyUserData")
if (typeof mmkvData === 'string') {
// // 新建并打开文件
// let file = fs.openSync(context.filesDir + '/userinfo.txt', fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
// // 写入一段内容至文件
// fs.writeSync(file.fd, mmkvData as string);
let options: dataPreferences.Options = { name: 'backupUserInfo' };
let preferences = dataPreferences.getPreferencesSync(context.getApplicationContext(), options);
preferences.putSync('data', mmkvData as string);
preferences.flush()
}
// 处理查询结果,根据情况自行实现
console.log("FileBackupExtensionAbility", "result" + JSON.stringify(resultMap.get("stringData")))
}
//双mmkv文件搬迁
/**
* 移动双框架mmkv文件到单框架指定目录
* 文件参考@link FileTransferManager.moveFiles
*
* @param context context上下文
* @return 成功返回true, 否则false
*/
private async moveMmkvFile(context: common.Context): Promise<boolean> {
try {
if (fs.accessSync("/data/storage/el2/backup/restore/com.hexin.plat.android/ce/files/mmkv_migratable")) {
fs.listFileSync("/data/storage/el2/backup/restore/com.hexin.plat.android/ce/files/mmkv_migratable", {
recursion: true
}).forEach(item => {
console.log("FileBackupExtensionAbility", "name = " + item);
})
}
// 双升单数据迁移后,mmkv默认在files/mmkv目录下
let sourceMmkvPath: string = '/data/storage/el2/backup/restore/com.hexin.plat.android/ce/files/mmkv_migratable';
if (!fs.accessSync(sourceMmkvPath)) {
console.log("FileBackupExtensionAbility", "fsaccess failed")
return false;
}
// 得到源路径下面所有的文件和目录
let listFileNames: string[] = fs.listFileSync(sourceMmkvPath);
if (!listFileNames || listFileNames.length === 0) {
console.log("FileBackupExtensionAbility", "listFile empty")
return false;
}
// 单mmkv路径, 根据实际路径指定
let targetPath: string = context.filesDir + '/mmkv';
// 目标目录不存在,则需要创建
if (!fs.accessSync(targetPath)) {
fs.mkdirSync(targetPath);
}
// 遍历源路径下面的文件或目录,进行移动
listFileNames.forEach((fileName: string) => {
let srcPath: string = `${sourceMmkvPath}/${fileName}`;
let destPath: string = `${targetPath}/${fileName}`;
if (fs.statSync(srcPath).isFile()) {
fs.moveFileSync(srcPath, destPath);
} else {
fs.moveDirSync(srcPath, targetPath);
}
});
return true;
} catch (error) {
// 异常处理逻辑,自行实现
console.log("FileBackupExtensionAbility", "catch error")
return false;
}
}
/**
* 解析双框架mmkv文件
* 当前例子中:
* 双的mmkv文件名为:source_mmkv, source_mmkv.crc
* 其中设置的属性有:intData 整型, boolData bool型, stringData 字符串型,floatData 浮点型,doubleData 双精度浮点型, setData Set<string>类型
* 单的mmkv文件名为:target_mmkv
*
* @param context context上下文
* @return map 包含了双mmkv中的数据
*/
private parseSourceMmkvInfo(context: common.Context): Map<string, MmkvValueType> {
let dataMap = new Map<string, MmkvValueType>();
let mmkv: MMKV | null = null;
try {
// 初始化双的mmkv, mmkv文件移动到context.filesDir + '/mmkv' 目录下了
MMKV.initialize(context.filesDir + '/mmkv', context.cacheDir + '/mmkv');
// mmkv文件名
let files = this.findDeseriredFile(context);
let mmapID: string = files[0];
console.log("FileBackupExtensionAbility", "mmapId = " + mmapID);
// 秘钥,根据实际情况指定秘钥, 当前未设置秘钥
let crpKey: string = '';
mmkv = MMKV.getBackedUpMMKVWithID(mmapID, MMKV.SINGLE_PROCESS_MODE, crpKey, '');
// 注:当前mmkv对于双框架mmkv设置的int long float的类型暂不支持,待后续更新
dataMap.set('stringData', mmkv.decodeString('key_history_user_info', ''));
dataMap.forEach((value, key, map) => {
console.log("FileBackupExtensionAbility", "datamap key = " + key + "value = " + value.toString());
})
} catch (error) {
// 异常处理逻辑,自行实现
} finally {
mmkv?.close();
}
return dataMap;
}
/**
* 从mmkv_migratable目录中找到所有的文件名
*/
private findDeseriredFile(context: common.Context) : string[] {
let result : string[] = [];
let mmkvFilePath = context.filesDir + '/mmkv';
let listFileNames: string[] = fs.listFileSync(mmkvFilePath);
for (let index = 0; index < listFileNames.length; index++) {
const element = listFileNames[index];
console.log("FileBackupExtensionAbility", "mmkv files are = " + element)
if (element.endsWith(".crc")) {
const filePrefix = element.split(".crc")[0];
result.push(filePrefix);
}
}
return result;
}
/**
* 插入数据到单的mmkv中
*
* @param context context上下文
* @param dataMap 待插入的数据
* @return 成功返回true, 否则false
*/
private insertToTargetMmkv(context: Context, dataMap: Map<string, MmkvValueType>): boolean {
let mmkv: MMKV | null = null;
try {
// 初始化单框架mmkv, 获取mmkv的实例可以根据实际情况自行封装成工具类调用
MMKV.initialize(context.filesDir + '/mmkv', context.cacheDir + '/mmkv');
// mmkv文件名
let mmapID: string = 'history_user_info';
// 秘钥,根据实际情况指定秘钥, 当前未设置秘钥
let crpKey: string = '';
mmkv = MMKV.getMMKVWithMMapID(mmapID, MMKV.SINGLE_PROCESS_MODE, crpKey, '');
// // 设置请求参数中的值
mmkv.encodeString('history_user_info', dataMap.get('stringData') as string);
} catch (error) {
console.log("FileBackupExtensionAbility", "insertError" + JSON.stringify(error))
// 异常处理逻辑,自行实现
return false;
} finally {
mmkv?.close();
}
return true;
}
/**
* 获取单框架中mmkv中的数据
*
* @param context context上下文
* @return map 包含单框架mmkv中的数据
*/
private getDataFromTarget(context: common.Context): Map<string, MmkvValueType> {
let dataMap = new Map<string, MmkvValueType>();
let mmkv: MMKV | null = null;
try {
// 初始化单框架mmkv
MMKV.initialize(context.filesDir + '/mmkv', context.cacheDir + '/mmkv');
// mmkv文件名
let files = this.findDeseriredFile(context);
let mmapID: string = "history_user_info";
console.log("FileBackupExtensionAbility", "mmapId = " + mmapID);
// 秘钥,根据实际情况指定秘钥, 当前未设置秘钥
let crpKey: string = '';
mmkv = MMKV.getMMKVWithMMapID(mmapID, MMKV.SINGLE_PROCESS_MODE, crpKey, '');
// 取值,根据mmkv中实际存在的key来取值
dataMap.set('legacyUserData', mmkv.decodeString('history_user_info', ''));
} catch (error) {
// 异常处理逻辑,自行实现
} finally {
mmkv?.close();
}
return dataMap;
}
}
@@ -0,0 +1,9 @@
export const DEFAULT_CONNECT_SITE = '116.63.213.206';
export const DEFAULT_CONNECT_SITE_PORT = 9528;
export const TEST_ENV_CONNECT_SITE = '121.52.252.9';
export const TEST_ENV_CONNECT_SITE_PORT = 9628;
export const TEST_ENV_SITE = '101.71.41.211';
export const TEST_ENV_PORT = 9628;
@@ -0,0 +1,63 @@
import { ConnectionServiceHelper, SiteEntity, SiteManager } from "@kernel/lib_communication";
import { AppInfoConfig, PreferenceService } from "biz_common";
import { DebugToolUtil } from "biz_debug";
import { DEBUG_TOOL, ENABLE_FIX_SERVER,
SERVER_FIX_ADDRESS,
SERVER_FIX_PORT} from "biz_debug/src/main/ets/debug/servertool/constant";
import WTInit from "biz_trade/src/main/ets/utils/WTInit";
import BuildProfile from "BuildProfile";
import { ConfigInitializer } from "../initialize/ConfigInitializor";
import { TEST_ENV_PORT, TEST_ENV_SITE } from "./Config";
import { ServiceHandlerImpl } from "./impl/ServiceHandlerImpl";
import deviceInfo from "@ohos.deviceInfo";
export class ConnectionInitiator {
/**
* 初始化连接库相关配置
*/
static initConnectionService(context : Context) {
ConfigInitializer.init();
// SiteManager.getInstance().init(TEST_ENV_CONNECT_SITE, TEST_ENV_CONNECT_SITE_PORT)
//SiteManager.getInstance().init(DEFAULT_CONNECT_SITE, DEFAULT_CONNECT_SITE_PORT)
//SiteManager.getInstance().setDomainHost(AppInfoConfig.FUTURES_DOMAIN_HOST);
const initEntity = SiteManager.getInstance().getHQSiteEntity()
initEntity?.setDomainHost(AppInfoConfig.FUTURES_DOMAIN_HOST)
initEntity?.setSitePort(AppInfoConfig.FUTURES_HOST_PORT)
if (DebugToolUtil.isInnerNetSimulateMode()) {
// debug模拟器自动切内网环境
ConnectionInitiator.updateServerOnDebugMode();
}
ConnectionInitiator.updateServerByDebugTool();
ConnectionServiceHelper.getInstance().init(new ServiceHandlerImpl());
WTInit.init(context.getApplicationContext());
}
private static updateServerOnDebugMode() {
if (isDebugMode()) {
let siteEntity = new SiteEntity();
siteEntity.init(TEST_ENV_SITE, TEST_ENV_PORT);
SiteManager.getInstance().updateHQSiteEntity(siteEntity);
}
}
/**
* 根据调试工具保存的站点信息初始化行情站点
*/
private static updateServerByDebugTool() {
if (PreferenceService.getBooleanValueSync(DEBUG_TOOL, ENABLE_FIX_SERVER, false)) {
let siteEntity = new SiteEntity();
const siteIp = PreferenceService.getStringValueSync(DEBUG_TOOL, SERVER_FIX_ADDRESS, TEST_ENV_SITE)
const sitePort = PreferenceService.getNumberValueSync(DEBUG_TOOL, SERVER_FIX_PORT, TEST_ENV_PORT);
siteEntity.init(siteIp, sitePort);
SiteManager.getInstance().updateHQSiteEntity(siteEntity);
}
}
}
export function isDebugMode() {
return BuildProfile.BUILD_MODE_NAME.toString() === 'debug'
}
@@ -0,0 +1,210 @@
import {
CBASConnectionConfig,
HqConnectionConfig,
ITcpApmMonitorProxy,
SocketConnection,
TcpApmMonitorProxyHolder,
TcpLogConnectionInfo
} from '@kernel/lib_communication';
import {
IConnectionService,
IServiceHandler,
ServiceConfig
} from '@kernel/lib_communication/src/main/ets/connection/IConnectionService';
import hilog from '@ohos.hilog';
// import TradeJsonStructObserver from 'biz_trade/src/main/ets/features/wtmodule/TradeJsonStructObserver'
import { AuthJsonStructObserver } from '../interceptors/AuthJsonStructObserver';
import { HXAuthManager } from 'biz_auth';
import { RealDataStructObserver } from '../../groupsub/RealDataStructObserver';
import { SelfCodeChangeObserver } from '../interceptors/SelfCodeChangeObserver';
import { SidInstanceIdReceiver } from '../interceptors/SidInstanceIdReceiver';
import { BaseTcpLogInfo, getTcpService, TCPConnectLogInfo } from '@kernel/app_monitor_tcp';
import { DEFAULT_SITE } from '@kernel/lib_communication/src/main/ets/site/SiteConstants';
import connection from '@ohos.net.connection';
import { TradeELKLogStructObserver } from 'biz_trade';
import { HXEventMonitor } from '@kernel/app_monitor_event';
import { Action, HxCBASAgentProvider, logMapValue } from '@b2c-spi/hx_cbas_api';
import { HXLog } from 'biz_common';
export class ServiceHandlerImpl implements IServiceHandler {
onConfig(serviceConfig: ServiceConfig): void {
let hqConnectionConfig = new HqConnectionConfig();
hqConnectionConfig.setAuth(new HXAuthManager());
hqConnectionConfig.addStructReceiver(new SidInstanceIdReceiver());
hqConnectionConfig.addStructReceiver(new AuthJsonStructObserver());
// hqConnectionConfig.addStructReceiver(new TradeJsonStructObserver());
hqConnectionConfig.addStructReceiver(new RealDataStructObserver());
//hqConnectionConfig.addStructReceiver(new SelfCodeChangeObserver());
serviceConfig.registerConnection(hqConnectionConfig);
let cbasConnectionConfig = new CBASConnectionConfig();
cbasConnectionConfig.setAuth(new HXAuthManager());
cbasConnectionConfig.addStructReceiver(new TradeELKLogStructObserver());
serviceConfig.registerConnection(cbasConnectionConfig);
this.registerTcpApmProxy()
}
/**
* 注册tcp的APM监控代理
*/
registerTcpApmProxy() {
TcpApmMonitorProxyHolder.get().tcpLogMonitorProxy = new HXTcpApmMonitorProxy()
}
onPreInit(connService: IConnectionService): void {
initCommonConfig()
}
onInitEnd(connService: IConnectionService): void {
hilog.info(0xff11, "ServiceHandlerImpl", "onInitEnd")
}
onDestroy(connService: IConnectionService): void {
hilog.info(0xff11, "ServiceHandlerImpl", "onDestroy")
}
}
function initCommonConfig() {
}
/**
* tcp APM性能监控代理类
*/
class HXTcpApmMonitorProxy implements ITcpApmMonitorProxy {
private TCP_LOG_MONITOR_MODULE = 'tcp-log';
/**
* 开始解析dns
* @param domainName
*/
dnsStart(domainName: string): void {
const logMap: Map<string, logMapValue> = new Map()
logMap.set('result', domainName)
HxCBASAgentProvider.getInstance().doEvent("mob_systemgeneral_dnsresolve_start", Action.SHOW, logMap)
HXLog.d("HXDNS", `dnsStart,domainName=${domainName}`)
}
/**
* dns解析结束
*/
dnsEnd(isSuccess: boolean, domainName: string, ipList: connection.NetAddress[] | null, cost: number,
errorMsg: string): void {
const logMap: Map<string, logMapValue> = new Map()
logMap.set('type', isSuccess ? "1" : "0")
//dns解析结果
const result = `domainName=${domainName}^ipList=${this.convertNetAddressToIpListString(ipList)}^cost=${cost}^errorMsg=${errorMsg}`
logMap.set('result', result)
HxCBASAgentProvider.getInstance().doEvent("mob_systemgeneral_dnsresolve_result", Action.SHOW, logMap)
HXLog.d("HXDNS", `dnsEnd,success=${isSuccess},result=${result}`)
}
/**
* 转换ip列表字符串格式
*/
private convertNetAddressToIpListString(ipList: connection.NetAddress[] | null): string {
if (ipList == null || ipList.length == 0) {
return ""
}
const ipArray = ipList.map((addr) => addr.address)
//去重
const uniqueIps = Array.from(new Set(ipArray))
return uniqueIps.join(",")
}
setSas(sas: string | null): void {
getTcpService()?.setSas(sas)
}
sendTcpConnectLog(info: TcpLogConnectionInfo): void {
let cost = info.finishTime - info.startTime
if (cost < 0) {
cost = 0
}
//这里需要转换一下
const logInfo = new TCPConnectLogInfo(
cost, //连接耗时
this.getTcpConnectResult(info.state, cost), //统一result字段
this.getTcpConnectErrorCode(info.state, cost), //统一errorCode字段
info.errorCode.toString(), //errorMsg
0, //连接类型 目前没有竞速,默认0
info.siteEntity.getSiteAddress(), //连接的服务器地址
0, //网络栈,目前没有判断
false, //是否ipv6
false, //是否v6优先连接
info.connectSeq, //站点类型
0,
info.siteEntity.getSiteAddress() === DEFAULT_SITE ? 1 : 0// 是否为默认站点
)
getTcpService()?.getTcpConnectLogMonitor()?.addUploadInfo(logInfo)
}
getTcpConnectErrorCode(state: SocketConnection, cost: number): number | undefined {
switch (state) {
case SocketConnection.CONNECTED:
return 4
case SocketConnection.ERROR:
if (cost > 10000) {
return 10
} else {
return 8
}
default:
return -1
}
}
getTcpConnectResult(state: SocketConnection, cost: number): string | null | undefined {
switch (state) {
case SocketConnection.CONNECTED:
return BaseTcpLogInfo.SUCCESS
case SocketConnection.ERROR:
if (cost > 10000) {
return BaseTcpLogInfo.TIMEOUT
} else {
return BaseTcpLogInfo.FAIL
}
default:
return BaseTcpLogInfo.FAIL
}
}
buildRequest(frameId: number, packageId: number, traceId: string | null,
pageIdMappingInstanceId: Map<number, number> | null): void {
getTcpService()?.getTcpReqMonitor()?.buildRequest(frameId, packageId, traceId, pageIdMappingInstanceId)
}
addRequestToQueue(packageId: number): void {
getTcpService()?.getTcpReqMonitor()?.addRequestToQueue(packageId)
}
sendBegin(packageId: number): void {
getTcpService()?.getTcpReqMonitor()?.sendBegin(packageId)
}
sendEnd(packageId: number): void {
getTcpService()?.getTcpReqMonitor()?.sendEnd(packageId)
}
receiveResponse(packageId: number, instanceId: number, rst: number): void {
getTcpService()?.getTcpReqMonitor()?.receiveResponse(packageId, instanceId, rst)
}
addRspToQueue(packageId: number, instanceId: number): void {
getTcpService()?.getTcpReqMonitor()?.addRspToQueue(packageId, instanceId)
}
processReceiveData(packageId: number, instanceId: number): void {
getTcpService()?.getTcpReqMonitor()?.processReceiveData(packageId, instanceId)
}
sendErrorLog(tag: string, msg: string): void {
//在这里将tcp的错误日志发送到apm
HXEventMonitor.onEvent(this.TCP_LOG_MONITOR_MODULE, tag, Object({
errMsg: msg
}));
}
}
@@ -0,0 +1,98 @@
import { getConnectionManager, HXLog } from 'biz_common';
import {
IStructReceiver,
ProtocolDef,
StuffBaseStruct,
StuffResourceStruct,
ConnectionServiceHelper
} from '@kernel/lib_communication';
import { StringUtils } from 'hxutil';
import { ConnectionSeq } from '@kernel/lib_communication';
import { ServerResourceManager, ServerTimeProvider } from 'biz_hxservice';
import { getTcpService } from '@kernel/app_monitor_tcp';
import { APMHelper } from '../../monitor/APMHelper';
/**
* 认证包拦截器
* 这个拦截器用于接受认证后的session数据包以及事件包
* @author menghuiyuan@myhexin.com
*/
export class AuthJsonStructObserver implements IStructReceiver {
constructor() {
}
onIntercept(stuffBaseStruct: StuffBaseStruct): boolean {
if (stuffBaseStruct == null) {
return false;
}
const headId = stuffBaseStruct.packageId;
if (headId == ProtocolDef.DATAID_NEW_REALTIME) {
const dataType = stuffBaseStruct.headType & ProtocolDef.DATA_TYPE_MASK;
if (dataType === ProtocolDef.DATA_TYPE_JSON) {
return true;
}
}
return false;
}
handleStruct(stuffBaseStruct: StuffBaseStruct): boolean {
if (!(stuffBaseStruct instanceof StuffResourceStruct)) {
return false;
}
let tmpBuffer = stuffBaseStruct.buffer;
const decodedStr = StringUtils.decode(tmpBuffer, "GBK")
try {
const parsedObj: AuthStruct = JSON.parse(decodedStr)
ServerResourceManager.getInstance().parseResource(decodedStr);
if (parsedObj.srvTime && (typeof parsedObj.srvTime === 'number')) {
ServerTimeProvider.getInstance().initServiceTime(parseInt(parsedObj.srvTime) * 1000)
}
const hasSessionId = StringUtils.isNotEmpty(parsedObj.sessionId);
//处理sessionId
if (hasSessionId) {
ConnectionServiceHelper.getInstance()
.getConnectionService()
.setSessionId(ConnectionSeq.HQ, parsedObj.sessionId)
//设置tcp监控的sas
getTcpService()?.setSas(`${parsedObj.sessionId}_${getConnectionManager()?.getConnectedIp()}`)
}
const hasClientIP = StringUtils.isNotEmpty(parsedObj.clientIp);
if (hasClientIP) {
APMHelper.clientIp = parsedObj.clientIp
AppStorage.setOrCreate('clientIp', parsedObj.clientIp)
}
if (hasSessionId || hasClientIP) {
//WTLoginAdapterService.receiveSessionIdHandle()
}
//todo 处理账户事件,如强制下线等
if (StringUtils.isNotEmpty(parsedObj.events)) {
HXLog.i("AuthJSONStruct", "error parsing")
}
} catch (e) {
HXLog.e("AuthJSONStruct", "Error parsing " + JSON.stringify(e))
}
return false;
}
getReceiverId(): string {
return "AuthJsonStructObserver"
}
}
/**
* 认证包结构
*/
export class AuthStruct {
srvTime: string = "";
clientIp: string = "";
clientPort: string = "";
serverIp: string = "";
releaseName: string = "";
AuthCountryCodeMd5: string = "";
events: string = "";
sessionId: string = "";
}
@@ -0,0 +1,72 @@
import { StringUtils } from 'hxutil';
import { IStructReceiver, ITCPClient, ProtocolDef, StuffBaseStruct, StuffResourceStruct } from '@kernel/lib_communication';
import { SelfCodeEvent } from '../../selfcode/SelfCodeEvents';
import { UgcClient } from '../../register/ugcregister/UgcClient';
/**
* 自选股更新订阅拦截
* @author menghuiyuan@myhexin.com
*/
export class SelfCodeChangeObserver implements IStructReceiver {
constructor() { }
onIntercept(stuffBaseStruct: StuffBaseStruct): boolean {
if (stuffBaseStruct == null) {
return false;
}
const headId = stuffBaseStruct.packageId;
if (headId == ProtocolDef.DATAID_NEW_REALTIME) {
const dataType = stuffBaseStruct.headType & ProtocolDef.DATA_TYPE_MASK;
if (dataType === ProtocolDef.DATA_TYPE_JSON) {
return true;
}
}
return false;
}
//自选股版本变更推送id
private readonly UPDATE_SIG_VERSION = '00000009';
//自选股更新强制推送id
private readonly UPDATE_SIG_FORCE = '00000010';
handleStruct(stuffBaseStruct: StuffBaseStruct): boolean {
if (!(stuffBaseStruct instanceof StuffResourceStruct)) {
return false;
}
const resStruct = stuffBaseStruct as StuffResourceStruct;
const tmpBuffer = resStruct.buffer;
const decodedStr = StringUtils.decode(tmpBuffer, "GBK")
try {
const parsedObj : SelfCodeEvent = JSON.parse(decodedStr);
if (parsedObj) {
const eventArrayLen = parsedObj.events.length;
for (let index = 0; index < eventArrayLen; index++) {
const eventObj = parsedObj.events[index];
const eventId = eventObj.eventID;
switch (eventId) {
//版本号推送
case this.UPDATE_SIG_VERSION:
const selfCodeVersionData = eventObj.eventData;
//TODO 推送版本号:
UgcClient.getInstance().onSelfCodeRemoteVersion(Number(selfCodeVersionData.version))
return true;
case this.UPDATE_SIG_FORCE:
//TODO 推送强制更新版本
const forceUpdateVersionData = JSON.stringify(eventObj.eventData);
return true;
default:
return false;
}
}
}
} catch (e) {
return false;
}
return false;
}
getReceiverId(): string {
return "SelfCodeChangeObserver";
}
}
@@ -0,0 +1,70 @@
import {
IStructReceiver,
RequestQueueManagement,
StuffBaseStruct,
StuffResourceStruct
} from '@kernel/lib_communication';
import { SidPermissionRefreshClient } from 'biz_auth';
const USER_RESPONSE_TYPE_NOTHING = 0;
/**
* SID 权限刷新特殊包接收器。
*
* 对齐 Android SIDInstanceIdReicever:主动刷新 SID 的回包使用固定 instanceId=13,
* 服务端可能按 type=0 返回普通 body,连接库会先解成 StuffBaseStruct。
* 这里只按包头条件识别该特殊包,把原始 body 包装成 LOGIN_AUTH 资源后继续投递给请求 client。
*/
export class SidInstanceIdReceiver implements IStructReceiver {
onIntercept(stuffBaseStruct: StuffBaseStruct): boolean {
return this.isSidRefreshBaseStruct(stuffBaseStruct);
}
handleStruct(stuffBaseStruct: StuffBaseStruct): boolean {
if (!this.isSidRefreshBaseStruct(stuffBaseStruct)) {
return false;
}
// IStructReceiver 返回 true 后会截断原始 StuffBaseStruct 的普通分发流程,
// 这里需要把包装后的 LOGIN_AUTH 资源主动投递给注册在 REFRESH_SID 上的请求 client。
const sidStruct = this.createSidResourceStruct(stuffBaseStruct);
const clients = RequestQueueManagement.getInstance().getNetworkClient(SidPermissionRefreshClient.REFRESH_SID);
if (!clients || clients.length === 0) {
return false;
}
for (const client of clients) {
client?.receive(sidStruct);
}
return true;
}
getReceiverId(): string {
return 'SidInstanceIdReceiver';
}
private isSidRefreshBaseStruct(stuffBaseStruct: StuffBaseStruct | null): boolean {
if (!stuffBaseStruct || stuffBaseStruct instanceof StuffResourceStruct) {
return false;
}
// 对齐 Android filter:只通过包头 type=0 和固定 instanceId=13 识别 SID 刷新特殊包。
// SID 内容不参与判断,避免把权限字段格式差异误判为客户端解析问题。
const rawBuffer = stuffBaseStruct.rawBuffer;
return stuffBaseStruct.instanceId === SidPermissionRefreshClient.REFRESH_SID &&
stuffBaseStruct.headType === USER_RESPONSE_TYPE_NOTHING &&
rawBuffer !== null &&
rawBuffer.length > 0;
}
private createSidResourceStruct(stuffBaseStruct: StuffBaseStruct): StuffResourceStruct {
// 这里不解码、不解析 SID,只复制主站原始 body 并包装成 LOGIN_AUTH。
// 后续 SID 字符串解析和权限更新仍交给 SidPermissionRefreshClient 统一处理。
const rawBuffer = stuffBaseStruct.rawBuffer as Uint8Array;
const sidBuffer = rawBuffer.slice(0, rawBuffer.length);
const sidStruct = new StuffResourceStruct(StuffResourceStruct.LOGIN_AUTH, sidBuffer);
stuffBaseStruct.copyHead(sidStruct);
sidStruct.connectionSeq = stuffBaseStruct.connectionSeq;
sidStruct.rawBuffer = sidBuffer;
return sidStruct;
}
}
@@ -0,0 +1,27 @@
import { HXLog } from 'biz_common';
import { ICheckCallback } from '@kernel/lib_communication/src/main/ets/connection/ICheckCallback';
/**
* 连接库检查回调类
* @author menghuiyuan@myhexin.com
*/
export class ConnectionCheckImpl implements ICheckCallback {
private readonly TAG = "SocketConnectionCheckImpl"
onChannelOk(): void {
HXLog.i(this.TAG, "onChannelOk");
}
onChannelBad(badType: number): void {
HXLog.i(this.TAG, "onChannelBad" + badType);
}
/**
* 是否需要重新连接
* @returns true,默认开启重连
*/
needReconnect(): boolean {
return true;
}
}
@@ -0,0 +1,12 @@
import { getConnectionManager } from 'biz_common';
import { UgcClient } from '../../register/ugcregister/UgcClient';
/**
* 主动关闭当前的socket连接
*/
export function closeSocketConnection() : void {
if (getConnectionManager()) {
getConnectionManager()?.disconnect(false);
UgcClient.getInstance().onHXSocketClose()
}
}
@@ -0,0 +1,31 @@
import { getConnectionManager, HXLog, RuntimeStatus } from 'biz_common';
import { ConnectionCheckImpl } from './ConnectionCheckImpl';
import { closeSocketConnection } from './ConnectionServiceUtil';
/**
* socket的监控管理
*/
export class HXSocketMonitor {
private readonly TAG : string = "HXSocketMonitor";
//当前App是否处于前台
private isAppForeground = false;
onAppForeground() {
HXLog.i(this.TAG, "onAppForeground")
this.isAppForeground = true;
if (getConnectionManager()) {
HXLog.i(this.TAG, "onAppForeground checkConnection")
getConnectionManager()?.checkConnection(new ConnectionCheckImpl());
}
}
onAppBackground() {
HXLog.i(this.TAG, "onAppBackground")
this.isAppForeground = false;
closeSocketConnection()
RuntimeStatus.getInstance().setTcpDisConnected()
}
}
@@ -0,0 +1,4 @@
export class EntryConstant {
//微信分享id
static readonly WX_SHARE_ID = 'wx6ba37430b7916a86';
}
@@ -0,0 +1,480 @@
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
import hilog from '@ohos.hilog';
import UIAbility from '@ohos.app.ability.UIAbility';
import Want from '@ohos.app.ability.Want';
import window from '@ohos.window';
import {
AppConfigManager,
buildHeader,
CodeTableMonitor,
GlobalContext,
HXLog,
jumpToQuote,
PreferenceService,
RuntimeStatus,
ScreenUtil,
StartManager,
updateBreakpoint,
updateCurrentWindowWith,
WebJumpLinkWhiteManager
} from 'biz_common';
import { AuthProviderService } from 'biz_auth';
import { ContextUtils, StringUtils } from 'hxutil';
import { ContextUtils as ContextUtils2b } from '@b2b/hxutil';
import { ConnectionSeq } from '@kernel/lib_communication/src/main/ets/data/CommunicationConstants';
import { SelfCodeGroupHandler } from '../groupsub/business/SelfCodeGroupHandler';
import { HXSocketMonitor } from '../communication/socketmonitor/HXSocketMonitor';
import { Configuration, ConfigurationConstant, wantConstant } from '@kit.AbilityKit';
import { UgcClient } from '../register/ugcregister/UgcClient';
import { ContinuousDataHelper, CurveNaviModel } from 'biz_quote/src/main/ets/data/ContinuousDataHelper';
import { DelayMarketManager, FutureMarketDataManager, HQTabManager, InactiveContractManager,
MainContractGroupHandler } from 'biz_market';
import { MainContractManager } from 'biz_market/src/main/ets/manager/MainContractManager';
import { SelfCodeMainListHandler } from '../groupsub/business/SelfCodeMainListHandler';
import { UgcAuthListener } from '../register/ugcregister/UgcAuthListener';
import { AppUtil } from '@pura/harmony-utils';
import { ElkUploadRegister } from '../register/elkregister/ElkUploadRegister';
import { HexinVersionControl } from 'biz_trade/src/main/ets/utils/HexinVersionControl';
import { SecurityMonitorInit } from '../monitor/SecurityMonitorInit';
import { TabConfigManager } from '@b2c/biz_discover';
import { ShareConfig } from '@b2c-spi/hx_share_api/src/main/ets/config/ShareConfig';
import { HMRouterMgr } from '@hadss/hmrouter';
import { EntryConstant } from '../constant/EntryConstant';
import { ConnectionInitiator } from '../communication/ConnectionInitiator';
import { APMHelper } from '../monitor/APMHelper';
import { cbasService } from '../register/cbasregister/CbasServiceRegister';
import { eventSubscription } from '../monitor/AppEvent';
import { StockGroupConstants } from 'biz_selfcode';
import { StockGroupHandler } from '../groupsub/business/StockGroupHandler';
import { VarietyGroupHandler } from '../groupsub/business/VarietyGroupHandler';
import { SidGroupHandler } from '../groupsub/business/SidGroupHandler';
import { FuturesCompanyGroupHandler, YYBListGroupHandler, TradeSettingGroupHandler, TradeCloudSyncConfig,
TradeMsgUniqueIdManager,
TradeNotificationTrigger } from 'biz_trade';
import { GroupSubManager } from 'base_mobilepush';
import { CodeTableManager } from 'lib_codetablemanager';
import BuildProfile from 'BuildProfile';
import { GrayTestPushHandler } from '../groupsub/business/GrayTestPushHandler';
import { AccountAnalysisLuaPushHandler } from '../groupsub/business/AccountAnalysisLuaPushHandler';
import { QhStrategyAdPushHandler } from '../groupsub/business/QhStrategyAdPushHandler';
import { GrayTestInitializor } from '../initialize/GrayTestInitializor';
import { CloudStoreLogInitializor } from '../initialize/CloudStoreLogInitializor';
import { HXPushService } from 'service_push';
import { WantCacheManager } from './WantManager';
import { pushService } from '@kit.PushKit';
import { HXNetwork } from '../network/HXNetwork';
import { AdvertisingMonitorService, KaihuFlagManager, ServerTimeProvider, UserSourceManager } from 'biz_hxservice';
import { UnifyUrlManager } from '@b2c-f/url-modify-config';
import { FontManager } from '../font/FontManager';
import { BizType, IndicatorGroupHandler, CommonIndicatorsManage, IndicatorParamsManage } from 'biz_indicator';
import { FigureRegistry } from 'biz_chart_data_processor';
import { TradeInitializor } from '../initialize/TradeInitializor';
import { AdsInitializer } from '../initialize/AdsInitializor';
import { AIGroupClient } from 'service_aigroup'
import { AIGroupPushHandler } from '../groupsub/business/AIGroupPushHandler';
import { DrawLineGroupHandler } from 'biz_quote';
import { CurveStorage } from 'biz_quote';
import { OpenAccountManager } from 'biz_openaccount/src/main/ets/manager/OpenAccountManager';
import { HttpUtils } from '@kernel/rcp';
import { DebugToolUtil } from 'biz_debug';
import { TradeDailyPushDataManager } from '../NotificationExtension/TradeDaily/TradeDailyPushDataManager';
import { TradeDailyConvertManager } from '../NotificationExtension/TradeDaily/TradeDailyConvertManager';
import { PushMessageManager } from 'biz_trade/src/main/ets/service/push/PushMessageManager';
import { ColourModeStatus } from '@kernel/theme_manager';
import { VarietyCodeTransformManager } from 'biz_chart_data_processor/src/main/ets/utils/VarietyCodeTransformManager';
import { ContextUtils as ContextUtils2c } from '@b2c-m/ohos_hx_utils';
import { FunctionAuthorityGroupHandler } from '../groupsub/business/FunctionAuthorityGroupHandler';
import { FunctionAuthorityRequestManager } from '../functionauthority/FunctionAuthorityRequester';
import { QhStrategyAdPagePushHandler } from '../groupsub/business/QhStrategyAdPagePushHandler';
/**
* 应用的主Ability
* @author menghuiyuan@myhexin.com
*/
export default class EntryAbility extends UIAbility {
private readonly tag: string = "EntryAbility"
private windowObj?: window.Window
private curBp: string = ''
//socket连接监控,需要接收应用前后台事件
private socketMonitor: HXSocketMonitor = new HXSocketMonitor();
private curNaviModel: CurveNaviModel | null = null;
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
pushService.receiveMessage('IM', this, (payload) => {
// todo 应用前台弹窗接收push逻辑,待补充。
try {
const data = payload.data;
const pushInfo: string = JSON.parse(data)?.data
HXLog.d(this.tag, `Succeeded in getting notification,data=${JSON.stringify(pushInfo)}`);
const pushModel = TradeDailyPushDataManager.getInstance().convertToTradeDailyPushModel(JSON.parse(pushInfo))
if (!pushModel) {
return
}
// 推送去重判断
TradeMsgUniqueIdManager.getInstance().isSameIdentify(
pushModel.time,
pushModel.iconType,
pushModel.type,
pushModel.orderNo
).then((hasSame) => {
if (!hasSame) {
TradeDailyPushDataManager.getInstance().recordPushDaily(JSON.parse(pushInfo)).then(() => {
TradeDailyConvertManager.getInstance().convertDailyFromPush()
})
PushMessageManager.getInstance().processReceiveHXPushMessage(pushInfo, false)
}
})
} catch (error) {
HXLog.e(this.tag, `Parse push data failed: ${error}`)
}
})
hilog.info(0x0000, this.tag, '%{public}s', 'Ability onCreate');
// ========== 注册自定义 Figure ==========
// 注册所有 iOS 迁移过来的自定义 Figure
// 包括:bar, barDouble, polygon, tower, american, trade, yAxisRange
FigureRegistry.registerAll()
hilog.info(0x0000, this.tag, 'Custom Figures registered successfully');
// =====================================
DebugToolUtil.TARGET_NAME = BuildProfile.TARGET_NAME
DebugToolUtil.PRODUCT_NAME = BuildProfile.PRODUCT_NAME
DebugToolUtil.BUILD_MODE_NAME = BuildProfile.BUILD_MODE_NAME
// Context初始化前移
GlobalContext.initContext(this.context.getApplicationContext())
ContextUtils.initContext(this.context.getApplicationContext())
ContextUtils2c.initContext(this.context.getApplicationContext())
ContextUtils2b.initContext(this.context.getApplicationContext())
AppUtil.init(this.context)
// 交易通知触发器初始化
TradeNotificationTrigger.getInstance().init(this.context)
//崩溃保护代码
SecurityMonitorInit.getInstance().init();
GrayTestInitializor.initGrayTest()
CloudStoreLogInitializor.initCloudStoreLog()
HMRouterMgr.init({ context: this.context });
UnifyUrlManager.getInstance().init(this.context);
// 启动管理:是否为安装后第一次进入APP
StartManager.getInstance().init()
RuntimeStatus.getInstance().init()
ElkUploadRegister.getInstance().init();
UgcClient.getInstance().init()
AuthProviderService.getInstance().setUgcAuthListener(UgcAuthListener.getInstance())
this.context.setMissionContinueState(AbilityConstant.ContinueState.INACTIVE, (result) => {
console.info(`setMissionContinueState: ${JSON.stringify(result)}`);
});
eventSubscription()
this.initRcp()
ShareConfig.setAppId(EntryConstant.WX_SHARE_ID);
this.initFuture()
AdsInitializer.initAds()
// 初始化 AI 分组客户端
AIGroupClient.getInstance().init(DebugToolUtil.isDebugMode())
WantCacheManager.getInstance().stashWant(want)
FontManager.getInstance().loadTHSFonts()
HXLog.init()
OpenAccountManager.getInstance().upCfmmcRecord()
UserSourceManager.getInstance().initUserSource()
}
/**
* 初始化Rcp库配置
*/
initRcp() {
HttpUtils.setGlobalHeaders((url) => {
return buildHeader(true)
})
}
initFuture() {
//大内容tab配置初始化
TabConfigManager.getInstance().initDiscoverTabConfig()
// 初始化常用指标管理(参考 iOS MePool.mm:338 userIdLoginSuccess)
CommonIndicatorsManage.getInstance().init(this.context)
// 初始化指标参数管理(参考 iOS MePool.mm:340)
IndicatorParamsManage.getInstance().init(this.context)
FunctionAuthorityRequestManager.requestAuthorityDataAfterLocalLoad()
//初始化码表
const codeTableInstance = CodeTableManager.get()
codeTableInstance.setInnerDebugMode(DebugToolUtil.isInnerNetSimulateMode())
codeTableInstance.initCodeTable(this.context, HexinVersionControl.disableCodeTableIncrementUpdate(),
CodeTableMonitor.getInstance(), () => {
return ServerTimeProvider.getInstance().getServerTime()
}, (isJson?: boolean) => {
return buildHeader(isJson)
}).then(() => {
//主力合约初始化
MainContractManager.get().init()
//初始化期货行情页面的tab配置
HQTabManager.get().init()
})
//期货市场配置
FutureMarketDataManager.get().init()
//不活跃合约
InactiveContractManager.get().init()
// 初始化延时市场配置
DelayMarketManager.get().init()
// 初始化品种码转换映射表(如果还没加载)
VarietyCodeTransformManager.init()
// 交易管理初始化
TradeInitializor.init(this.context)
// 分时持久化存储UI状态存储初始化
CurveStorage.getInstance().initPersistentStorage()
}
onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
HXLog.i(this.tag, `onNewWant ${AbilityConstant.LaunchReason.CONTINUATION}`)
WantCacheManager.getInstance().stashWant(want)
if (launchParam.launchReason == AbilityConstant.LaunchReason.CONTINUATION) {
// 将上述的保存的数据取出恢复
let continueInput = '';
if (want.parameters != undefined) {
continueInput = want.parameters.curveData as string;
HXLog.i(this.tag, "onNewWant continue input = " + continueInput)
this.curNaviModel = JSON.parse(continueInput) as CurveNaviModel;
} else {
HXLog.i(this.tag, "onNewWant continue input is empty")
}
}
}
onDestroy() {
OpenAccountManager.getInstance().upCfmmcRecord()
PreferenceService.putVal('Install', 'isFirstTimeEntering', false, true);
hilog.info(0x0000, this.tag, '%{public}s', 'Ability onDestroy');
}
onContinue(wantParam: Record<string, Object>): AbilityConstant.OnContinueResult | Promise<AbilityConstant.OnContinueResult> {
let versionDst = wantParam.version; // 获取迁移对端应用的版本号
let versionSrc: number = 1000600; // 获取迁移源端即本端应用的版本号
HXLog.i(this.tag, "versionDst = " + versionDst + "versionSrc = " + versionSrc)
if (versionDst < versionSrc) { // 兼容性校验
// 兼容性校验不满足
HXLog.w(this.tag, "onContinue, dst not match the minumum version, abort continuation process")
return AbilityConstant.OnContinueResult.MISMATCH;
}
if (ContinuousDataHelper.getInstance().getCurrentNavi()) {
hilog.info(0x0000, this.tag, "onContinue set success = ");
wantParam["curveData"] = JSON.stringify(ContinuousDataHelper.getInstance().getCurrentNavi()!);
}
wantParam[wantConstant.Params.SUPPORT_CONTINUE_PAGE_STACK_KEY] = false;
return AbilityConstant.OnContinueResult.AGREE;
}
onWindowStageCreate(windowStage: window.WindowStage) {
//存储程序当前的 windowStage,用于创建子窗口。
AppStorage.setOrCreate('EntryAbilityWindowStage', windowStage);
// 埋点调试工具需要使用的 windowStage
AppStorage.setOrCreate('windowStage', windowStage);
//初始化App的一些公共资源和窗口设置
let windowClass: window.Window = windowStage.getMainWindowSync();
let avoidArea = windowClass.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR);
GlobalContext.initNavigationBarHeight(avoidArea.bottomRect.height)
let statusBarHeight = windowClass.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM).topRect.height;
GlobalContext.initStatusBarHeight(statusBarHeight);
// Main window is created, set main page for this ability
hilog.info(0x0000, this.tag, '%{public}s', 'Ability onWindowStageCreate');
windowStage.getMainWindow().then((windowObj) => {
this.windowObj = windowObj
// 获取应用启动时的窗口尺寸
this.curBp = updateBreakpoint(windowObj.getWindowProperties().windowRect.width, this.curBp)
// 注册回调函数,监听窗口尺寸变化
windowObj.on('windowSizeChange', (windowSize) => {
this.curBp = updateBreakpoint(windowSize.width, this.curBp)
updateCurrentWindowWith(windowSize.width)
})
});
this.registerGroupHandlers();
ColourModeStatus.initColourModeSetting(this.context)
HexinVersionControl.recordFirstInstallVersion();
HexinVersionControl.getInstance().requestVersionConfig();
// 网络不可用
const netAvailable = HXNetwork.getInstance().detectNet();
if (!netAvailable) {
this.loadNetworkAnomalyPage(windowStage);
return;
}
this.initializeCbasService().then(() => {
// 正常流程
}).catch((error: Error) => {
// 即使失败也记录日志,但继续加载页面
HXLog.e(this.tag, 'cbasService init failed: %{public}s');
}).finally(() => {
// 确保页面一定会加载
if (!PreferenceService.getBooleanValueSync('Privacy', 'isUserAgree', false)) {
this.loadPrivacyPage(windowStage);
} else {
this.loadIndexPage(windowStage);
}
});
}
onWindowStageRestore(windowStage: window.WindowStage): void {
//应用窗口恢复时,如果有接续的数据就说明当前为接续开启应用,打开分时页
HXLog.i(this.tag, "onWindowStageRestore")
let parsedData: CurveNaviModel;
if (this.curNaviModel) {
HXLog.i(this.tag, "onWindowStageRestore data is valid")
parsedData = this.curNaviModel;
jumpToQuote(parsedData.code, parsedData.marketId, parsedData.codeList, parsedData.marketIdList);
this.curNaviModel = null;
}
}
private registerGroupHandlers() {
GroupSubManager.getInstance().addUserGroupHandler(SelfCodeMainListHandler.GROUP_HANDLE_NAME, new SelfCodeMainListHandler());
GroupSubManager.getInstance().addUserGroupHandler(SelfCodeGroupHandler.GROUP_HANDLE_NAME, new SelfCodeGroupHandler());
GroupSubManager.getInstance().addUserGroupHandler(YYBListGroupHandler.GROUP_HANDLE_NAME,new YYBListGroupHandler())
GroupSubManager.getInstance().addUserGroupHandler(TradeCloudSyncConfig.FILE_NAME, new TradeSettingGroupHandler())
const indicatorChannels: string[] = [
BizType.COMMON_INDICATORS,
BizType.CUSTOM_INDICATORS,
BizType.INDICATOR_PARAMS,
BizType.SQUARE_INDICATORS
]
for (const channel of indicatorChannels) {
GroupSubManager.getInstance().addUserGroupHandler(channel, new IndicatorGroupHandler(channel))
}
// 接入画线同步 push 分组,收到远端画线后驱动本地刷新与重绘。
GroupSubManager.getInstance().addUserGroupHandler(DrawLineGroupHandler.GROUP_KEY, new DrawLineGroupHandler())
GroupSubManager.getInstance().addUserGroupHandler(AIGroupPushHandler.GROUP_HANDLE_NAME, new AIGroupPushHandler())
GroupSubManager.getInstance().addUserGroupHandler(SidGroupHandler.GROUP_HANDLE_NAME, new SidGroupHandler())
GroupSubManager.getInstance().addUserGroupHandler(
FunctionAuthorityGroupHandler.GROUP_HANDLE_NAME,
new FunctionAuthorityGroupHandler()
)
GroupSubManager.getInstance().addUserGroupHandler(QhStrategyAdPushHandler.GROUP_HANDLE_NAME, new QhStrategyAdPushHandler())
GroupSubManager.getInstance().addAllGroupHandler(QhStrategyAdPagePushHandler.GROUP_HANDLE_NAME, new QhStrategyAdPagePushHandler())
GroupSubManager.getInstance().addAllGroupHandler(GrayTestPushHandler.GROUP_HANDLE_NAME, new GrayTestPushHandler());
GroupSubManager.getInstance().addAllGroupHandler(AccountAnalysisLuaPushHandler.GROUP_HANDLE_NAME, new AccountAnalysisLuaPushHandler());
GroupSubManager.getInstance().addAllGroupHandler(FuturesCompanyGroupHandler.GROUP_HANDLE_NAME,new FuturesCompanyGroupHandler())
GroupSubManager.getInstance().addAllGroupHandler(StockGroupConstants.KEY_STOCK_STORAGE_UPDATE, new StockGroupHandler())
GroupSubManager.getInstance().addAllGroupHandler(StockGroupConstants.KEY_VARIETY_STORAGE_UPDATE, new VarietyGroupHandler())
GroupSubManager.getInstance().addAllGroupHandler(MainContractGroupHandler.GROUP_HANDLE_NAME, new MainContractGroupHandler())
}
onWindowStageDestroy() {
hilog.info(0x0000, this.tag, '%{public}s', 'Ability onWindowStageDestroy');
}
onForeground() {
// Ability has brought to foreground
AppStorage.setOrCreate('isAbilityForeGround', true);
cbasService.onForeground()
this.socketMonitor.onAppForeground();
HXPushService.getInstance().checkPushPermissionStatus()
ColourModeStatus.updateSystemColourModeOnAbilityForeground(this.context)
}
onConfigurationUpdate(newConfig: Configuration): void {
ColourModeStatus.onSystemConfigurationUpdate(newConfig.colorMode)
}
onBackground() {
// Ability has back to background
AppStorage.setOrCreate('isAbilityForeGround', false);
cbasService.onBackground()
this.socketMonitor.onAppBackground();
}
/**
* 加载应用主页面
* @param windowStage
*/
private loadIndexPage(windowStage: window.WindowStage) {
//三方标题栏风险提示,白名单
WebJumpLinkWhiteManager.getInstance().loadWhiteJumpLink()
windowStage.loadContent('pages/Index', (err, data) => {
if (err.code) {
hilog.error(0x0000, this.tag, 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
return;
}
onAppStartLoading(this.context,true);
hilog.info(0x0000, this.tag, 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
});
}
/**
* 加载隐私政策页面
* @param windowStage
*/
private loadPrivacyPage(windowStage: window.WindowStage) {
windowStage.loadContent('pages/LauncherPage', (err, data) => {
if (err.code) {
hilog.error(0x0000, this.tag, 'Failed to load loadPrivacyPage. Cause: %{public}s', JSON.stringify(err) ?? '');
return;
}
onAppStartLoading(this.context);
hilog.info(0x0000, this.tag, 'Succeeded in loadPrivacyPage. Data: %{public}s', JSON.stringify(data) ?? '');
});
}
/**
* 加载网络异常页面
* @param windowStage
*/
private loadNetworkAnomalyPage(windowStage: window.WindowStage) {
windowStage.loadContent('network/NetworkAnomalyPage', (err, data) => {
if (err.code) {
hilog.error(0x0000, this.tag, 'Failed to load NetworkAnomalyPage. Cause: %{public}s', JSON.stringify(err) ?? '');
return;
}
onAppStartLoading(this.context);
hilog.info(0x0000, this.tag, 'Succeeded in NetworkAnomalyPage. Data: %{public}s', JSON.stringify(data) ?? '');
});
}
/**
* 加载埋点初始化
*/
private async initializeCbasService() {
try {
await AppConfigManager.getInstance().fetchIMEI()
let udid = AppConfigManager.getInstance().getUdidSync()
if(!StringUtils.isNotEmpty(udid)) {
udid = AppConfigManager.UDID_NONE
}
await cbasService.init(udid)
HXLog.i(this.tag, 'cbasService initialized successfully');
} catch (error) {
HXLog.i( this.tag, 'cbasService init failed');
}
}
}
function onAppStartLoading(context:Context,isAgreePrivacy:boolean = false) {
ConnectionInitiator.initConnectionService(context)
AppConfigManager.getInstance().fetchIMEI().then((isSuccess: boolean) => {
if (isSuccess) {
AuthProviderService.getInstance().sendAuthRequest(ConnectionSeq.HQ);
}
let udid = AppConfigManager.getInstance().getUdidSync()
if(!StringUtils.isNotEmpty(udid)) {
udid = AppConfigManager.UDID_NONE
}
cbasService.init(udid)
if (isAgreePrivacy) {
AdvertisingMonitorService.getInstance().init(context)
}
});
//初始化APM
APMHelper.init(context);
//初始化窗口工具
ScreenUtil.get().init();
}
@@ -0,0 +1,57 @@
import { MDataModel, AppPushMessage } from "service_push";
export function ProtocolJumpTest() {
let appMsg1: MDataModel = {
'A': '10',
'F': '2205',
'C': 'FG2601',
'ACT': '70',
'TY': '20',
'ST': '预警通知'
}
let appMsg2: MDataModel = {
'A': '10',
'ACT': '70',
"F": "2804",
"U": "https://news.10jqka.com.cn/20251111/c672408397.shtml"
}
let appMsg3: MDataModel = {
// 跳转个股
'A': '10',
'F': '2205',
'C': 'USDIND',
'ACT': '60',
'ST': '美元指数预警'
}
let appMsg4: MDataModel = {
// 跳转外部浏览器
'A': '30',
'ACT': '70',
"F": "2804",
"U": "https://news.10jqka.com.cn/20251111/c672408397.shtml"
}
let appMsg5: MDataModel = {
// 拉起其他应用
'A': '40',
'ACT': '70',
'PN':'com.hexin.hmn.sjcg',
'CN':'EntryAbility',
}
let protocol1: AppPushMessage = {
APPID: '207',
ID: '604900',
TIPTYPE: '1',
TITLE: '标题',
DESC: '内容',
APPMSG: appMsg1
}
let data: Record<string, string> = {};
data['payload'] = JSON.stringify(protocol1);
return data;
}
@@ -0,0 +1,26 @@
import { RemoteNotificationExtensionAbility, pushCommon } from '@kit.PushKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { TradeDailyPushDataManager } from '../NotificationExtension/TradeDaily/TradeDailyPushDataManager';
const DOMAIN = 0x0000;
export default class RemoteNotificationExtAbility extends RemoteNotificationExtensionAbility {
async onReceiveMessage(remoteNotificationInfo: pushCommon.RemoteNotificationInfo):
Promise<pushCommon.RemoteNotificationContent> {
const data = remoteNotificationInfo.data
try {
const pushInfo: string = JSON.parse(data)?.data
await TradeDailyPushDataManager.getInstance().initDB(this.context)
const pushModel = TradeDailyPushDataManager.getInstance().convertToTradeDailyPushModel(JSON.parse(pushInfo))
if (pushModel) {
hilog.info(DOMAIN, 'RemoteNotification', 'info: %{public}s', pushModel.body);
await TradeDailyPushDataManager.getInstance().recordPushDaily(pushModel)
}
} catch (e) {
hilog.error(DOMAIN, 'RemoteNotification', 'error: %{public}s', e);
}
await Promise.resolve();
return {};
}
}
@@ -0,0 +1,219 @@
import Want from '@ohos.app.ability.Want';
import { Queue } from '@kit.ArkTS';
import { ClipUtils, GlobalContext, HXLog, jumpToWebPage } from 'biz_common';
import { AppPushMessage, DoPushCbas, PushConstants, PushMessageManager } from 'service_push';
import { ShareManager } from '@b2c-spi/hx_share_api/src/main/ets/manager/ShareManager';
import { ELK_LEVEL, sendElk } from 'service_monitor';
const TAG: string = 'WantCacheManager'
const BACK_ACTION: string = 'ohos.want.action.viewData'
const BACK_ENTITY: string = 'entity.system.browsable'
export const FIRST_PAGE_ONAPPEAR_EVENT : string = 'FirstPage OnAppear'
/**
* WantCacheManager 类用于管理 Want 对象的缓存和处理。
* 采用单例模式,确保整个应用中只有一个实例。
* 该管理器会缓存接收到的 Want 对象,并在合适的时机进行处理。
*/
export class WantCacheManager {
// 单例模式下的唯一实例
private static instance: WantCacheManager
private pushMessageManager : PushMessageManager = PushMessageManager.getInstance();
private clipUtils: ClipUtils = ClipUtils.getInstance()
/**
* 获取 WantCacheManager 的单例实例。
* 如果实例不存在,则创建一个新实例。
* @returns WantCacheManager 的单例实例
*/
public static getInstance(): WantCacheManager {
if (!WantCacheManager.instance) {
WantCacheManager.instance = new WantCacheManager()
}
return WantCacheManager.instance
}
// 标志位,指示是否可以处理缓存的 Want 对象
private enabled: boolean = false
/**
* 首页加载完成的回调函数。
* 当首页加载完成后,将 enabled 标志位设置为 true,并尝试处理缓存的 Want 对象。
*/
private firstPageFinishedCb = () => {
this.enabled = true
this.popWant()
}
/**
* 私有构造函数,确保只能通过 getInstance 方法获取实例。
* 注册首页加载完成的事件监听器。
*/
private constructor() {
// 监听首页加载完成事件,当事件触发时调用 firstPageFinishedCb 回调函数
GlobalContext.get().eventHub.on(FIRST_PAGE_ONAPPEAR_EVENT, this.firstPageFinishedCb)
}
// 存储 Want 对象的队列
private wantQueue: Queue<Want> = new Queue()
/**
* 存储 Want 对象到队列中。
* 如果 enabled 标志位为 true,则尝试处理队列中的 Want 对象。
* @param want 要存储的 Want 对象
*/
stashWant(want: Want) {
// 将 Want 对象添加到队列中
this.wantQueue.add(want)
// 记录日志,提示接收到新的 Want 对象
HXLog.i(TAG, ' New Want received, want.param : ')
// 如果可以处理 Want 对象,则尝试处理队列中的元素
if (this.enabled) {
this.popWant()
}
}
/**
* 从队列中取出并处理一个 Want 对象。
* 如果队列不为空,则调用不同业务方进行处理,业务方处理保证不更改原有对象
*/
private popWant() {
if (this.wantQueue.length > 0) {
const w = this.wantQueue.pop()
// push协议
this.handlePayload(w)
// 微信分享
this.handleWeChat(w)
// 浏览器回流
this.handleBackWash(w)
// appLinking回流
this.handleBackWashByBrowsable(w)
this.handleReflow(w)
}
}
/**
* 处理推送协议对象。
* 根据want中解析到的推送协议的来源,获取相应的策略并处理。
* @param want 要处理的want
*/
private handlePayload(want : Want) {
if (want?.action === 'com.push.action') {
const param = want?.parameters
if (param) {
const payloadJson = param['payload'] as string
HXLog.i(PushConstants.AM_PUSH, payloadJson)
const payload = JSON.parse(payloadJson) as AppPushMessage
DoPushCbas.pushArriveCbas(payload)
DoPushCbas.pushShowCbas(payload)
this.pushMessageManager.handlerPush(payload)
}
}
}
/**
* 处理wechat分享协议对象。
* 根据want中解析到的分享协议,进行处理
* @param want 要处理的want
*/
private handleWeChat(want: Want) {
// 判断service是否有效
const shareService = ShareManager.getShareService()
if (!shareService) {
HXLog.e("HXShare", 'shareService is null');
return
}
shareService.getWXApi().handleWant(want, ShareManager.getShareService()?.getWXEventHandler())
}
/**
* 端外跳转至期货通
*/
private async handleBackWash(want: Want) {
if (want.action === BACK_ACTION && want.uri && want.entities?.length === 0) {
const params = this.extractUrlParams(want.uri)
if (params['url']) {
jumpToWebPage(params)
}
await this.dealPasteboard()
}
}
private async dealPasteboard(){
if (this.clipUtils.getClipBoardSwitchState()) {
// 请求剪切板权限后再读取
const hasPermission = await this.clipUtils.checkAndRequestPermission()
if (hasPermission) {
const clipboardContent = this.clipUtils.getClipboardContent()
if (this.clipUtils.isValidClipPassword(clipboardContent)) {
this.clipUtils.parseClipboard(clipboardContent)
}
}
}
}
/**
* 浏览器跳转至期货通
*/
private handleBackWashByBrowsable(want: Want) {
if (want.action === BACK_ACTION && want.uri && want.entities &&
want.entities[0] === BACK_ENTITY) {
const params = this.extractUrlParams(want.uri)
if (params['url']) {
jumpToWebPage(params)
}
this.dealPasteboard()
}
}
/**
* 处理回流埋点。
* @param want 要处理的want
*/
private handleReflow(want: Want) {
if (want.parameters) {
const callerBundleName = want.parameters['ohos.aafwk.param.callerBundleName'] as string
if (callerBundleName === 'com.hexin.hmn.sjcg') {
// 手抄跳转
sendElk(TAG, `callerBundleName ${callerBundleName}`, ELK_LEVEL.INFO, true)
}
}
}
private extractUrlParams(input: string): Record<string, string> {
const result: Record<string, string> = {};
// 提取所有形如 key=value 的参数(以 &、^ 或字符串结尾为边界)
const matches = Array.from(input.matchAll(/([a-zA-Z_][a-zA-Z0-9_]*)=([^&\^]*)/gi));
if (matches.length === 0) {
return result;
}
// 迭代解码(兼容含 & 的转义值 %26)
matches.forEach(match => {
const key = match[1];
let decoded = match[2];
let prev = '';
do {
prev = decoded;
try {
decoded = decodeURIComponent(decoded);
} catch {
break; // 非法编码终止
}
} while (decoded !== prev && /%[0-9A-Fa-f]{2}/.test(decoded));
result[key] = decoded;
});
return result;
}
}
@@ -0,0 +1,54 @@
import { TextMetricsMeasurer } from "@b2b/hq_table";
import { text } from "@kit.ArkGraphics2D";
import { HXLog } from "biz_common";
/**
* author:liuqingliang
* created on 2025/12/19
* desc:全局字体管理
*/
export class FontManager {
private static readonly TAG = "FontManager"
private static instance: FontManager
public static getInstance(): FontManager {
if (!FontManager.instance) {
FontManager.instance = new FontManager()
}
return FontManager.instance
}
registerFont(uiContext: UIContext) {
try {
let font = uiContext.getFont();
font.registerFont({
familyName: 'bold',
familySrc: $rawfile('fonts/THSJinRongTi_bold.otf')
});
font.registerFont({
familyName: 'Regular',
familySrc: $rawfile('fonts/THSJinRongTi_Regular.otf')
});
font.registerFont({
familyName: 'Medium',
familySrc: $rawfile('fonts/THSJinRongTi_Medium.otf')
});
TextMetricsMeasurer.getInstance().init()
} catch (e) {
HXLog.e(FontManager.TAG, "registerFont failed:" + JSON.stringify(e))
}
}
private loadFontSync(fontFamily: 'bold' | 'Regular' | 'Medium') {
let fontCollection = text.FontCollection.getGlobalInstance()
fontCollection.loadFontSync(fontFamily, $rawfile(`fonts/THSJinRongTi_${fontFamily}.otf`))
}
loadTHSFonts() {
this.loadFontSync('bold');
this.loadFontSync('Regular');
this.loadFontSync('Medium');
}
}
@@ -0,0 +1,114 @@
import {
CookieManager,
FunctionAuthorityRequester,
HxServiceExportConstants,
HXUserService,
ProductAuthorityManager
} from 'biz_hxservice'
import { AppConfigManager } from 'biz_common'
import { http } from '@kit.NetworkKit'
import { JSON } from '@kit.ArkTS'
import { UnifyUrlManager } from '@b2c-f/url-modify-config'
import emitter from '@ohos.events.emitter'
interface EmptyFunctionAuthorityResponse {
code: number
data: Object[]
}
class AppFunctionAuthorityRequester implements FunctionAuthorityRequester {
async requestAuthorityData(): Promise<object> {
if (HXUserService.getInstance().isTemporaryUser()) {
const response: EmptyFunctionAuthorityResponse = { code: 0, data: [] }
return response
}
const httpRequest = http.createHttp()
try {
const requestOptions: http.HttpRequestOptions = {
method: http.RequestMethod.GET,
header: this.buildHeaders(),
connectTimeout: 30000,
readTimeout: 30000,
expectDataType: http.HttpDataType.STRING
}
const response = await httpRequest.request(UnifyUrlManager.getRealUrl('custom_authority_url'), requestOptions)
if (response.responseCode !== http.ResponseCode.OK) {
throw new Error(`http error: ${response.responseCode}`)
}
const parseOptions: JSON.ParseOptions = {
bigIntMode: JSON.BigIntMode.PARSE_AS_BIGINT
}
return JSON.parse(response.result as string, null, parseOptions) as object
} finally {
httpRequest.destroy()
}
}
private buildHeaders(): Record<string, string> {
const headers: Record<string, string> = {}
headers['Content-Type'] = 'application/json'
const cookie = CookieManager.getInstance().getCookieSync()
if (cookie) {
headers['Cookie'] = cookie
}
return headers
}
}
export class FunctionAuthorityRequestManager {
private static initialized: boolean = false
private static cookieUpdateListener?: () => void
private static requestingAfterCookieUpdate: boolean = false
private static userId: string = ''
static loadAuthorityFromLocal(): boolean {
FunctionAuthorityRequestManager.ensureInitialized()
return ProductAuthorityManager.getInstance().loadAuthorityFromLocal()
}
static requestAuthorityDataAfterLocalLoad(): Promise<boolean> {
FunctionAuthorityRequestManager.loadAuthorityFromLocal()
return FunctionAuthorityRequestManager.requestAuthorityData()
}
static requestAuthorityData(): Promise<boolean> {
FunctionAuthorityRequestManager.ensureInitialized()
return ProductAuthorityManager.getInstance().requestAuthorityData()
}
static requestAuthorityDataForPush(pushTimestamp: number): Promise<boolean> {
FunctionAuthorityRequestManager.ensureInitialized()
return ProductAuthorityManager.getInstance().requestAuthorityDataForPush(pushTimestamp)
}
private static ensureInitialized(): void {
if (FunctionAuthorityRequestManager.initialized) {
return
}
ProductAuthorityManager.getInstance().setRequester(new AppFunctionAuthorityRequester())
FunctionAuthorityRequestManager.registerCookieUpdateListener()
FunctionAuthorityRequestManager.initialized = true
}
private static registerCookieUpdateListener(): void {
if (FunctionAuthorityRequestManager.cookieUpdateListener) {
return
}
FunctionAuthorityRequestManager.cookieUpdateListener = () => {
const newUserId = HXUserService.getInstance().getUserIdFromLocal()
if (FunctionAuthorityRequestManager.requestingAfterCookieUpdate ||
FunctionAuthorityRequestManager.userId == newUserId) {
return
}
FunctionAuthorityRequestManager.userId = newUserId
FunctionAuthorityRequestManager.requestingAfterCookieUpdate = true
FunctionAuthorityRequestManager.requestAuthorityDataAfterLocalLoad()
.finally(() => {
FunctionAuthorityRequestManager.requestingAfterCookieUpdate = false
})
}
emitter.on(HxServiceExportConstants.COOKIE_UPDATE, FunctionAuthorityRequestManager.cookieUpdateListener)
}
}
@@ -0,0 +1,68 @@
import { StringUtils } from "@b2c-m/ohos_hx_utils";
import { IStructReceiver, ProtocolDef, StuffBaseStruct, StuffResourceStruct } from '@kernel/lib_communication';
import { GroupSubManager, L2DowngradeManager } from 'base_mobilepush';
import { HashMap } from '@kit.ArkTS';
import { emitter } from '@kit.BasicServicesKit';
import { EmitterConstants } from 'biz_common'
import { SubscribedPushManager } from "biz_trade";
export class RealDataStructObserver implements IStructReceiver {
constructor() {
}
onIntercept(stuffBaseStruct: StuffBaseStruct): boolean {
if (stuffBaseStruct == null) {
return false;
}
const headId = stuffBaseStruct.packageId;
if (headId == ProtocolDef.DATAID_NEW_REALTIME) {
const dataType = stuffBaseStruct.headType & ProtocolDef.DATA_TYPE_MASK;
if (dataType === ProtocolDef.DATA_TYPE_JSON) {
return true;
}
}
return false;
}
handleStruct(stuffBaseStruct: StuffBaseStruct): boolean {
if (!(stuffBaseStruct instanceof StuffResourceStruct)) {
return false;
}
const resStruct = stuffBaseStruct as StuffResourceStruct;
const tmpBuffer = resStruct.buffer;
const decodedStr = StringUtils.decode(tmpBuffer, "GBK")
try {
const jsonObject: ESObject = JSON.parse(decodedStr);
//解析到分组订阅数据
const parsedObj: ESObject = jsonObject["groupdata"];
if (parsedObj) {
GroupSubManager.getInstance().onReceiveGroupData(JSON.stringify(parsedObj), true);
return true;
}
// 解析到Push订阅数据
if (jsonObject[SubscribedPushManager.PARAM_CHANNEL]) {
SubscribedPushManager.getInstance().onReceivePushData(decodedStr)
}
const parsedEvents: HashMap<string, object>[] = jsonObject["events"]
if (parsedEvents && parsedEvents.length > 0) {
for (let event of parsedEvents) {
if (event["eventID"] == "00000011") {
L2DowngradeManager.getInstance().onReceiveGroupData(JSON.stringify(event));
emitter.emit({ eventId: EmitterConstants.EVENT_ID_GET_PERMISSIONSID })
return true;
}
}
return false
}
} catch (e) {
return false;
}
return false;
}
getReceiverId(): string {
return "RealDataStructObserver";
}
}
@@ -0,0 +1,25 @@
import { ISubDataHandler } from 'base_mobilepush'
import { AIGroupClient, AI_GROUP_BIZ_NAME } from 'service_aigroup'
/**
* AI分组的数据分组订阅处理器
* 参考 iOS LJAIGroupManager 中的 MobiPush 注册机制
* ctrlid = future_aigroupconfig
*/
export class AIGroupPushHandler implements ISubDataHandler {
static readonly GROUP_HANDLE_NAME = AI_GROUP_BIZ_NAME
onReceiveGroupData(data: string): void {
try {
const pushData: AIGroupPushData = JSON.parse(data) as AIGroupPushData
AIGroupClient.getInstance().onAIGroupPushNotification(pushData.ver)
} catch (e) {
// 解析失败时也触发更新,不传版本号
AIGroupClient.getInstance().onAIGroupPushNotification()
}
}
}
interface AIGroupPushData {
ver?: number
}
@@ -0,0 +1,86 @@
import { ISubDataHandler } from 'base_mobilepush'
import { GlobalContext, HXLog } from 'biz_common'
import { ScriptUpdateService } from '@b2c-f/billparsemodule'
/**
* 账户分析 Lua 脚本更新推送处理器
*
* 处理来自服务端的账户分析 Lua 脚本版本更新通知
*/
export class AccountAnalysisLuaPushHandler implements ISubDataHandler {
static readonly GROUP_HANDLE_NAME = "qhtCapitalLuaUpdate"
private static readonly TAG = "AccountAnalysisLuaPush"
onReceiveGroupData(data: string): void {
HXLog.i(AccountAnalysisLuaPushHandler.TAG, `receive: ${data}`)
try {
const pushData: AccountAnalysisLuaPushData = JSON.parse(data) as AccountAnalysisLuaPushData
if (pushData && pushData.AccountAnalysisLua) {
const version = pushData.AccountAnalysisLua.version
if (version) {
HXLog.i(AccountAnalysisLuaPushHandler.TAG, `收到 Lua 脚本版本更新通知: version=${version}`)
this.handleScriptUpdate(version)
} else {
HXLog.w(AccountAnalysisLuaPushHandler.TAG, '版本号为空')
}
} else {
HXLog.w(AccountAnalysisLuaPushHandler.TAG, '推送数据格式错误')
}
} catch (error) {
HXLog.e(AccountAnalysisLuaPushHandler.TAG, `解析推送数据失败: ${error}`)
}
}
/**
* 处理脚本更新
*/
private handleScriptUpdate(version: string): void {
try {
const context = GlobalContext.get()
if (!context) {
HXLog.e(AccountAnalysisLuaPushHandler.TAG, 'Context 为空,无法更新脚本')
return
}
const updateService = new ScriptUpdateService(context)
// 异步执行更新操作
updateService.downloadAndInstall(version)
.then((result) => {
if (result.success) {
if (result.updated) {
HXLog.i(AccountAnalysisLuaPushHandler.TAG,
`Lua 脚本更新成功: version=${result.version}, scriptPath=${result.scriptPath}`)
} else {
HXLog.i(AccountAnalysisLuaPushHandler.TAG,
`Lua 脚本已是最新版本: version=${result.version}`)
}
} else {
HXLog.e(AccountAnalysisLuaPushHandler.TAG,
`Lua 脚本更新失败: ${result.errorMessage}`)
}
})
.catch((error: Error) => {
HXLog.e(AccountAnalysisLuaPushHandler.TAG, `Lua 脚本更新异常: ${error.message}`)
})
} catch (error) {
HXLog.e(AccountAnalysisLuaPushHandler.TAG, `处理脚本更新失败: ${error}`)
}
}
}
/**
* 账户分析 Lua 脚本版本信息
*/
export interface AccountAnalysisLuaVersion {
version: string
}
/**
* 账户分析 Lua 脚本推送数据格式
*/
export interface AccountAnalysisLuaPushData {
AccountAnalysisLua: AccountAnalysisLuaVersion
}
@@ -0,0 +1,78 @@
import { RandomUtil } from "@pura/harmony-utils";
import { ISubDataHandler } from "base_mobilepush";
import { HXLog } from "biz_common";
import { StringUtil } from "biz_common/src/main/ets/utils/StringUtil";
import { HexinVersionControl } from "biz_trade/src/main/ets/utils/HexinVersionControl";
import { CodeTableManager } from "lib_codetablemanager";
export abstract class BaseStorageHandler implements ISubDataHandler {
/**
* 获取日志tag
*/
abstract getTag(): string
/**
* 获取推送中的数据更新时间key
*/
abstract getTimeKey(): string
/**
* 处理升级
*/
abstract update()
/**
* 获取数据库版本信息
*/
abstract getDbVersion(): Promise<number>
private readonly RANDOM_REQUEST_TIME = 10 * 1000
private mTimeoutId: number | null = null
async onReceiveGroupData(data: string) {
if (HexinVersionControl.disableCodeTableIncrementUpdate()) {
return
}
HXLog.d(CodeTableManager.TAG, `${this.getTag()} onReveiveGroupData: ${data}`)
const obj = JSON.parse(data) as Record<string, string>
if (!obj) {
HXLog.e(CodeTableManager.TAG, `${this.getTag()} onReceiveGroupData obj parse invalid`)
return
}
const time: string = obj[this.getTimeKey()]
const dbVersion = await this.getDbVersion()
const remoteVersion = StringUtil.toSafeNumber(time, 0)
HXLog.d(CodeTableManager.TAG, `${this.getTag()} remoteVersion: ${remoteVersion}, dbVersion: ${dbVersion}`)
if (StringUtil.isNumber(time) && remoteVersion > dbVersion) {
this.randomDelayRequest(remoteVersion)
}
}
/**
* 随机延时请求
*/
private randomDelayRequest(remoteVersion: number) {
// 取消之前的延迟任务
if (this.mTimeoutId !== null) {
clearTimeout(this.mTimeoutId)
this.mTimeoutId = null
}
const requestDelay: number = RandomUtil.getRandomInt(0, this.RANDOM_REQUEST_TIME)
console.debug(CodeTableManager.TAG, `${this.getTag()} requestDelay ${requestDelay}`)
// 设置延迟任务
this.mTimeoutId = setTimeout(() => {
this.mTimeoutId = null
// 延迟的这段时间,数据库可能被更新了,因此执行前再判断一次,减少不必要的请求
this.getDbVersion().then((dbVersion: number) => {
HXLog.d(CodeTableManager.TAG, `before update, remoteVersion: ${remoteVersion}, dbVersion: ${dbVersion}`)
if (remoteVersion > dbVersion) {
this.update()
}
})
}, requestDelay)
}
}
@@ -0,0 +1,31 @@
import { ISubDataHandler } from 'base_mobilepush'
import { FunctionAuthorityRequestManager } from '../../functionauthority/FunctionAuthorityRequester'
interface FunctionAuthorityGroupData {
timestamp?: string | number
timeStamp?: string | number
}
export class FunctionAuthorityGroupHandler implements ISubDataHandler {
static readonly GROUP_HANDLE_NAME = 'futures_custom_authority'
onReceiveGroupData(data: string, key?: string, isRealPush?: boolean): void {
const timestamp = this.parseTimestamp(data)
if (timestamp <= 0) {
return
}
FunctionAuthorityRequestManager.requestAuthorityDataForPush(timestamp)
}
private parseTimestamp(data: string | object): number {
try {
const payload = typeof data === 'string'
? JSON.parse(data) as FunctionAuthorityGroupData
: data as FunctionAuthorityGroupData
return Number(payload.timestamp ?? payload.timeStamp ?? 0)
} catch (error) {
console.error(`[FunctionAuthorityGroupHandler] 解析权限推送失败: ${JSON.stringify(error)}`)
return 0
}
}
}
@@ -0,0 +1,19 @@
import { ISubDataHandler } from 'base_mobilepush'
import { HXLog } from 'biz_common'
import { GrayTestManager } from 'lib_graytestmanager'
export class GrayTestPushHandler implements ISubDataHandler {
static readonly GROUP_HANDLE_NAME = "futures_new_gray_test_config_upgrade_notice"
onReceiveGroupData(data: string): void {
HXLog.i("GrayTest", `GrayTestPushHandler receive: ${data}`)
let verTimestamp: PushTimesTamp = JSON.parse(data) as PushTimesTamp
if (verTimestamp && verTimestamp.timestamp > 0) {
GrayTestManager.getInstance().dealReceiveMobilePush(verTimestamp.timestamp)
}
}
}
export interface PushTimesTamp {
timestamp: number
}
@@ -0,0 +1,28 @@
import { AdsPopupManager } from "@b2c-f/fuhm-ads"
import { AdsPopupHost } from "@b2c/first_page"
import { ISubDataHandler } from "base_mobilepush"
import { HXLog } from "biz_common"
/**
* author : liuqingliang@myhexin.com
* time : created on 2026/6/9
* desc : 全域运营位-只会推送一次,不会实时推送
*/
export class QhStrategyAdPagePushHandler implements ISubDataHandler {
static readonly GROUP_HANDLE_NAME = 'app_page_info'
private static readonly KEY_TIME = "app_6"
onReceiveGroupData(data: string) {
try {
const pushData = JSON.parse(data) as Record<string, string>
const timeStamp = pushData[QhStrategyAdPagePushHandler.KEY_TIME] ?? ''
if (timeStamp === AdsPopupManager.pushTimestamp) {
return
}
AdsPopupHost.getInstance().requestPageInfo(timeStamp)
} catch (error) {
HXLog.e('QhStrategyAdPushHandler', `Failed to process push: ${error.message}`)
}
}
}
@@ -0,0 +1,59 @@
import { AdsManager, ADS_TYPE_INDEX_POPUP, ADS_TYPE_QHTMKTPOP } from "@b2c-f/fuhm-ads"
import { AdsPopupHost, FirstPagePopupAdHelper } from "@b2c/first_page"
import { ISubDataHandler } from "base_mobilepush"
import { HXLog, PreferenceService } from "biz_common"
import { HXUserService } from "biz_hxservice"
/**
* <pre>
* author: ME
* desc: 期货策略广告推送处理器,对齐 Android QhStragyAdPushHandler
* createTime: 2026/06/01
* </pre>
*/
export class QhStrategyAdPushHandler implements ISubDataHandler {
static readonly GROUP_HANDLE_NAME = 'qh_strategy_ad_push'
private static readonly PUSH_STRATEGY_AD_KEY = 'push_stragy_ad'
private static readonly SP_STRATEGY_AD = "sp_stragy_ad"
private static readonly KEY_AD_TYPE = "adtype"
private static readonly KEY_AD_IDS = "id"
onReceiveGroupData(data: string) {
try {
const pushData = JSON.parse(data) as Record<string, string>
const adType = pushData[QhStrategyAdPushHandler.KEY_AD_TYPE] ?? ''
const adId = pushData[QhStrategyAdPushHandler.KEY_AD_IDS] ?? ''
if (!adType || !adId) {
HXLog.w('QhStrategyAdPushHandler', 'Invalid push data, missing adType or adId')
return
}
const pushKey = `${adType}_${adId}`
const userId = HXUserService.getInstance().getUserIdFromLocal()
const preferenceKey = `${QhStrategyAdPushHandler.PUSH_STRATEGY_AD_KEY}_${userId}`
const localPushKey = PreferenceService.getStringValueSync(QhStrategyAdPushHandler.SP_STRATEGY_AD,preferenceKey, '')
if (pushKey === localPushKey) {
HXLog.d('QhStrategyAdPushHandler', `Already pushed: ${pushKey}`)
return
}
PreferenceService.putVal(QhStrategyAdPushHandler.SP_STRATEGY_AD,preferenceKey, pushKey, true)
if (adType === ADS_TYPE_INDEX_POPUP) {
// 收到策略广告后重置启动弹窗过的标志
FirstPagePopupAdHelper.getInstance().isShowedPopupAds = false
}
if (adType === ADS_TYPE_QHTMKTPOP) {
// 期货营销悬浮球-处理策略广告推送
AdsPopupHost.getInstance().handlePush()
}
AdsManager.refreshAdsAfterPushByAdType(adType, adId)
} catch (error) {
HXLog.e('QhStrategyAdPushHandler', `Failed to process push: ${error.message}`)
}
}
}
@@ -0,0 +1,19 @@
import { ISubDataHandler } from 'base_mobilepush';
import { UgcClient } from '../../register/ugcregister/UgcClient';
/**
* 自选分组的数据分组订阅
* @author menghuiyuan@myhexin.com
*/
export class SelfCodeGroupHandler implements ISubDataHandler {
static readonly GROUP_HANDLE_NAME = "blockstock"
onReceiveGroupData(data: string): void {
let version:groupUpdate = JSON.parse(data) as groupUpdate
UgcClient.getInstance().onSelfGroupRemoteVersion(version.ver)
}
}
export interface groupUpdate{
ver:number
}
@@ -0,0 +1,14 @@
import { ISubDataHandler } from 'base_mobilepush';
import { UgcClient } from '../../register/ugcregister/UgcClient';
import { groupUpdate } from './SelfCodeGroupHandler';
/**
* 默认自选数据分组订阅
* @author zhangshan2@myhexin.com
*/
export class SelfCodeMainListHandler implements ISubDataHandler {
static readonly GROUP_HANDLE_NAME = "futuresselfcode"
onReceiveGroupData(data: string): void {
let version:groupUpdate = JSON.parse(data) as groupUpdate
UgcClient.getInstance().onSelfCodeRemoteVersion(version.ver)
}
}
@@ -0,0 +1,70 @@
import { ISubDataHandler } from 'base_mobilepush'
import { WxPaySidPermissionMarker } from 'biz_auth'
import { HXLog, PreferenceService } from 'biz_common'
import { SidPermissionRefreshClient } from 'biz_auth/src/main/ets/auth/sid/SidPermissionRefreshClient'
import { HXUserService } from 'biz_hxservice'
const TAG = 'SidGroupHandler'
/**
* SID权限更新分组推送数据。
* 对应分组key: futures_app_sid_update_notice
* timeStamp为服务端下发的更新标记,用于和本地已处理标记比较,变化时主动刷新SID权限。
*/
interface SidGroupData {
timeStamp?: string | number
}
export class SidGroupHandler implements ISubDataHandler {
static readonly GROUP_HANDLE_NAME = 'futures_app_sid_update_notice'
private static readonly PREFERENCES_NAME = 'sid_update_notice'
private static readonly KEY_PREFIX = 'sid_update_'
onReceiveGroupData(data: string): void {
try {
// 主站只用timeStamp表达“SID权限有新版本”,客户端不解析具体权限内容。
const flag = this.parsePushFlag(data)
if (!flag || flag.length === 0) {
return
}
const userId = HXUserService.getInstance().getUserId()
// 本地仅记录已成功处理的timeStamp,避免同一条分组推送重复触发主动请求。
const savedFlag = PreferenceService.getStringValueSync(
SidGroupHandler.PREFERENCES_NAME,
SidGroupHandler.KEY_PREFIX + userId,
''
)
if (flag === savedFlag) {
return
}
// 主动请求成功且SID发生变化后,SidPermissionRefreshClient会写入本次flag。
WxPaySidPermissionMarker.clear()
SidPermissionRefreshClient.requestPermission(true, flag)
} catch (error) {
HXLog.e(TAG, `handle sid group push failed: ${JSON.stringify(error)}`)
}
}
private parsePushFlag(data: string | object): string {
if (!data) {
return ''
}
let payload: SidGroupData | null = null
if (typeof data === 'string') {
try {
payload = JSON.parse(data) as SidGroupData
} catch (error) {
HXLog.e(TAG, `parse sid group push failed: ${JSON.stringify(error)}`)
return ''
}
} else {
// 分组分发链路可能已把payload解析为对象,这里兼容两种输入形态。
payload = data as SidGroupData
}
const flag = payload?.timeStamp
if (flag === undefined || flag === null) {
return ''
}
return flag.toString()
}
}
@@ -0,0 +1,25 @@
import { AppUtil } from "@pura/harmony-utils";
import { HXLog } from "biz_common";
import { CodeTableManager } from "lib_codetablemanager";
import { BaseStorageHandler } from "./BaseStorageHandler";
export class StockGroupHandler extends BaseStorageHandler {
getTag(): string {
return 'StockGroupHandler'
}
getTimeKey(): string {
return 'time'
}
update(): void {
HXLog.d(CodeTableManager.TAG, `${this.getTag()} update`)
CodeTableManager.get().requestStock(AppUtil.getContext(), this.getTag())
}
async getDbVersion(): Promise<number> {
const version = await CodeTableManager.get().getStockInfoDbVersion()
HXLog.d(CodeTableManager.TAG, `${this.getTag()} getDbVersion ${version}`)
return version
}
}
@@ -0,0 +1,26 @@
import { AppUtil } from "@pura/harmony-utils";
import { HXLog } from "biz_common";
import { CodeTableManager } from "lib_codetablemanager";
import { BaseStorageHandler } from "./BaseStorageHandler";
export class VarietyGroupHandler extends BaseStorageHandler {
getTag(): string {
return 'VarietyGroupHandler'
}
getTimeKey(): string {
return 'timestamp'
}
update(): void {
HXLog.d(CodeTableManager.TAG, `${this.getTag()} update`)
CodeTableManager.get().requestVariety(AppUtil.getContext(), this.getTag())
}
async getDbVersion(): Promise<number> {
const version = await CodeTableManager.get().getVarietyInfoDbVersion()
HXLog.d(CodeTableManager.TAG, `${this.getTag()} getDbVersion ${version}`)
return version
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 486 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 513 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 503 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1011 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 948 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 930 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 950 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 930 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 919 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 698 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 693 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 439 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

@@ -0,0 +1,103 @@
import {
AdsManager,
AdRuntimeConfig
} from '@b2c-f/fuhm-ads'
import { AppConfigManager, HXLog, ScreenUtil } from 'biz_common'
import { CookieManager, HXUserService, KaihuFlagManager, NewUserManager, ServerTimeProvider,
UserSourceManager } from 'biz_hxservice'
import { AppUtil } from '@pura/harmony-utils'
const TAG = 'AdsInitializer'
/**
* FuHmAds 运行时配置
* 提供广告组件所需的上下文、用户信息和业务能力
*/
class AdsRuntimeConfig implements AdRuntimeConfig {
getContext(): Object | null {
return AppUtil.getContext()
}
getScreenWidth(): string {
return ScreenUtil.get().getScreenWidth().toString()
}
getScreenHeight(): string {
return ScreenUtil.get().getScreenHeight().toString()
}
getInnerVersion(): string {
return AppConfigManager.getInstance().getConfigProvider()?.getInnerVersionMiddleNumberStr()??''
}
getComeFrom(): string {
return KaihuFlagManager.getInstance().getKaihuFlag()
}
getTrade(): string {
let userSource = UserSourceManager.getInstance().getUserSource()
if (UserSourceManager.NO_SOURCE != userSource) {
return userSource
}else {
return ""
}
}
getDeviceCode(): string {
return ''
}
getUserId(): string {
const userId = HXUserService.getInstance().getUserId()
return userId || ''
}
getUserIdFromPassport(): string {
const userId = HXUserService.getInstance().getUserIdFromLocal()
return userId || ''
}
isNewUser(): boolean {
return NewUserManager.getInstance().isNewUser()
}
getChannelId(): string {
return NewUserManager.getChannelId()
}
getCurrentTimeMillis(): number {
return ServerTimeProvider.getInstance().getServerTimeMillis()
}
getUserAgent(): string {
return AppConfigManager.getInstance().getUA()
}
getCookie(): string {
return CookieManager.getInstance().getCookieSync()
}
uploadElk(message: string): void {
HXLog.w(TAG, `Ads elk ${message}`)
}
}
/**
* 广告模块初始化器
*/
export class AdsInitializer {
/**
* 初始化广告模块
*/
static initAds(): void {
HXLog.i(TAG, 'initAds start')
const adsConfig = new AdsRuntimeConfig()
AdsManager.initAdContext(adsConfig)
HXLog.i(TAG, 'initAds done')
}
}
@@ -0,0 +1,64 @@
import { HXLog } from "biz_common"
import { CloudStoreLogManager, ICloudStoreLogger } from "lib_cloud_store"
import { ElkService, ElkUploadMessage, ElkUploadMessageBuilder } from '@b2c-m/ohos_elk_logger_upload'
import { HXUserService } from 'biz_hxservice'
const CLOUD_STORE = "cloud_store"
/**
* 云存储错误消息构建类
*/
class CloudStoreErrMessageBuilder extends ElkUploadMessageBuilder {
constructor(bizLevel: string) {
super(Date.now(), CLOUD_STORE, bizLevel)
}
}
/**
* 上传云存储错误信息到 ELK
* @param errorMessage 错误信息
*/
function uploadCloudStoreError(error: string) {
const errorMessageBuilder = new CloudStoreErrMessageBuilder('e')
errorMessageBuilder.userid(HXUserService.getInstance().getUserId())
errorMessageBuilder.messageKey('CloudStore')
errorMessageBuilder.messageValue(error)
const message = new ElkUploadMessage(errorMessageBuilder)
ElkService.getInstance().pushBusinessLog(message, true)
}
/**
* HXLog 适配器
* 将 lib_cloud_store 的日志桥接到 HXLog
*/
class HXLogAdapter implements ICloudStoreLogger {
debug(module: string, message: string): void {
HXLog.d(module, message)
}
info(module: string, message: string): void {
HXLog.i(module, message)
}
warn(module: string, message: string): void {
HXLog.w(module, message)
}
error(module: string, message: string): void {
HXLog.e(module, message)
uploadCloudStoreError(`[${module}] ${message}`)
}
}
/**
* 云存储日志初始化器
*/
export class CloudStoreLogInitializor {
/**
* 初始化云存储日志
*/
static initCloudStoreLog() {
const hxLogAdapter = new HXLogAdapter()
CloudStoreLogManager.getInstance().setLogger(hxLogAdapter)
}
}
@@ -0,0 +1,81 @@
import {AppInfoConfig, IAppConfig, IDeviceConfig } from 'biz_common';
export class AppConfigProvider implements IAppConfig {
private static readonly BEGIN_INDEX = 5
getBundleName(): string {
return AppInfoConfig.getBundleName();
}
getInnerVersionFull(): string {
return AppInfoConfig.APPLET_INNER_VERSION_FULL;
}
getInnerVersionMiddle(): string {
return AppInfoConfig.APPLET_INNER_VERSION.substring(AppConfigProvider.BEGIN_INDEX -1)
}
getInnerVersionMiddleNumberStr(): string {
return AppInfoConfig.APPLET_INNER_VERSION.substring(AppConfigProvider.BEGIN_INDEX).replaceAll(".","")
}
getOuterVersion(): string {
return AppInfoConfig.getVersionName();
}
getVersionCode(): string {
return AppInfoConfig.getVersionCode();
}
getAppPlatform(): string {
return AppInfoConfig.APP_PLATFORM;
}
getAppSign(): string {
return AppInfoConfig.APP_FOR;
}
getProgramId(): string {
return AppInfoConfig.APP_PROGID;
}
getChannelId(): string {
return AppInfoConfig.APP_CHANNEL_ID;
}
getSourceId(): string {
return AppInfoConfig.APP_CHANNEL_ID;
}
}
export class DeviceConfigProvider implements IDeviceConfig {
getDeviceWidth(): number {
return 720;
}
getDeviceHeight(): number {
return 1440;
}
getDensity(): number {
return 3.0;
}
getDeviceType(): string {
return "android";
}
getIMEI(): string {
throw new Error('Method not implemented.');
}
getIMSI(): string {
throw new Error('Method not implemented.');
}
getMACAddress(): string {
throw new Error('Method not implemented.');
}
}
@@ -0,0 +1,13 @@
import { AppConfigManager } from 'biz_common'
import { AppConfigProvider, DeviceConfigProvider } from './ConfigImpl';
/**
* 宿主App初始化配置信息
*/
export class ConfigInitializer {
static init() {
AppConfigManager.getInstance().registerConfigProvider(new AppConfigProvider());
AppConfigManager.getInstance().registerDeviceConfigProvider(new DeviceConfigProvider());
}
}
@@ -0,0 +1,33 @@
import { AppConfigManager, AppInfoConfig, } from "biz_common"
import { DebugToolUtil } from "biz_debug"
import { CookieManager, HXUserService, ServerTimeProvider } from "biz_hxservice"
import { GrayTestManager, GrayTestInjectInterface } from "lib_graytestmanager"
export class GrayTestInitializor {
/**
* 初始化灰度配置
*/
static initGrayTest() {
const injectParams = new GrayTestInjectInterface()
injectParams.getUserId = () => {
return HXUserService.getInstance().getUserIdFromLocal()
}
injectParams.getCookie = () => {
return CookieManager.getInstance().getCookieSync()
}
injectParams.getUserAgent = () => {
return AppConfigManager.getInstance().getUA()
}
injectParams.getAppVersion = () => {
return AppInfoConfig.getCurrentInnerVersionNumber().toString()
}
injectParams.getServerTimestamp = () => {
return ServerTimeProvider.getInstance().getServerTimeMillis()
}
injectParams.getDebugMode = () => {
return DebugToolUtil.isDebugMode()
}
GrayTestManager.getInstance().config(injectParams)
}
}
@@ -0,0 +1,145 @@
import { ContractChooseManager, ContractDirectionType, ContractInfo, ContractMainType,
FavoriteGroup } from "@b2c-f/contractchoose";
import { ContractBeanFactory, ContractCode, MarketId, PositionCenter } from "@b2c-f/futures_ohos_trade_sdk";
import { Context } from "@kit.AbilityKit";
import { Markets } from "biz_common";
import { InactiveContractManager } from "biz_market";
import { AccountAnalysisTradeLoginListener,
AccountProvider,
FuturesAccountAnalysisHttpProvider, FuturesBillParseModuleHttpProvider,
TradeUtils, TradeReportDateManager } from "biz_trade";
import { TradeDailyDataManager, TradeDailyManager, TradeHighFrequentManager, TradeLoginOnlineManager,
TradeMsgUniqueIdManager, TradeSettlementBillManager } from "biz_trade";
import { TradeType } from "biz_trade/src/main/ets/trade/manager/TradeCustomTabManager";
import { CodeTableManager, StockRegularUtils } from "lib_codetablemanager";
import { loadCustomGroups } from "service_watchlist";
import { TradeDailyConvertManager } from "../NotificationExtension/TradeDaily/TradeDailyConvertManager";
import { TradeDailyPushDataManager } from "../NotificationExtension/TradeDaily/TradeDailyPushDataManager";
export class TradeInitializor {
/**
* 初始化高频交易管理器
*/
static async init(context: Context) {
TradeInitializor.initContractChoose()
TradeHighFrequentManager.getInstance().initManager(context)
TradeLoginOnlineManager.getInstance().initManager(context)
AccountAnalysisTradeLoginListener.getInstance().start()
TradeSettlementBillManager.getInstance()
FuturesAccountAnalysisHttpProvider.initializeHttpProvider()
FuturesBillParseModuleHttpProvider.initializeHttpProvider()
TradeSettlementBillManager.getInstance()
// 交易日志
TradeDailyManager.getInstance()
await TradeMsgUniqueIdManager.getInstance().initDB(context)
await TradeDailyDataManager.getInstance().init(context)
await TradeDailyPushDataManager.getInstance().initDB(context)
await TradeDailyConvertManager.getInstance().registerForeground(context)
TradeReportDateManager.getInstance().updateTradingDate()
}
/**
* 初始化合约选择方法注入
*/
static initContractChoose() {
ContractChooseManager.getInstance().setIsOptionContractProvider((showCode, marketId) => {
return StockRegularUtils.isOption(showCode)
})
ContractChooseManager.getInstance().setIsMainLinkContractProvider(async (showCode, marketId) => {
let contractBean = await ContractBeanFactory.getInstance()
.buildContract(new ContractCode(showCode), new MarketId(marketId))
return TradeUtils.isCanTradeMainLink(contractBean)
})
ContractChooseManager.getInstance().setIsMainContractProvider(async (showCode, marketId) => {
return TradeUtils.isZhuliCode(showCode)
})
ContractChooseManager.getInstance().setPositionProvider(async () => {
const accountIdentify = AccountProvider.getInstance().getCurrentAccount()?.accountIdentify()
if (!accountIdentify) {
return []
}
const positions = PositionCenter.getInstance().getAllPositionSummary(accountIdentify)?.getPositionInfos()
return positions?.flatMap((position) => {
const stockCode = CodeTableManager.get().getStockInfoByShowCodeSync(position.contractCode.code, position.marketId.code)?.stockCode ?? ''
return new ContractInfo(
position.contractCode.code,
position.marketId.code,
stockCode,
position.contractName,
position.isOptionPosition,
ContractMainType.NONE,
position.isLongPosition() ? ContractDirectionType.BUY : ContractDirectionType.SELL
)
}) ?? []
})
ContractChooseManager.getInstance().setFavoritesProvider(async () => {
const favorites = await TradeUtils.getSelfContractForTrade(TradeType.NONE)
return favorites.flatMap((favorite) => {
if (favorite.contractCode && favorite.marketId) {
const stockCode = CodeTableManager.get().getStockInfoByShowCodeSync(favorite.contractCode.code, favorite.marketId.code)?.stockCode ?? ''
return new ContractInfo(
favorite.contractCode.code,
favorite.marketId.code,
stockCode,
favorite.contractName ?? '',
favorite.isOptionContract(),
TradeUtils.isZhuliCode(stockCode) ? ContractMainType.MAIN : ContractMainType.NOT_MAIN,
ContractDirectionType.NONE,
InactiveContractManager.get().isInactiveContract(stockCode) && !Markets.isArbitrageMarket(favorite.marketId.code)
)
}
return undefined
})
})
ContractChooseManager.getInstance().setFavoritesGroupProvider(async () => {
const groups = loadCustomGroups(true)
const favoriteGroup = new FavoriteGroup()
for (const group of groups) {
const contracts = group.stocks
if (contracts.length > 0) {
favoriteGroup.addGroup(group.name, contracts.flatMap((contract) => {
const stockInfo = CodeTableManager.get().getStockInfoSync(contract.code, contract.market)
const showCode = stockInfo?.showCode
const stockName = stockInfo?.stockName
return new ContractInfo(
showCode,
contract.market,
contract.code,
contract.name ?? stockName,
StockRegularUtils.isOption(showCode),
TradeUtils.isZhuliCode(contract.code) ? ContractMainType.MAIN : ContractMainType.NOT_MAIN,
ContractDirectionType.NONE,
InactiveContractManager.get().isInactiveContract(contract.code) && !Markets.isArbitrageMarket(contract.market)
)
}))
}
}
return favoriteGroup
})
ContractChooseManager.getInstance().setMainContractProvider(async (showCode, marketId) => {
const stockCode = CodeTableManager.get().getStockInfoByShowCodeSync(showCode, marketId)?.stockCode ?? ''
const contract = await TradeUtils.changeMainLinkToMainContract2(stockCode, marketId)
if (contract) {
return new ContractInfo(
contract.contractCode?.code,
contract.marketId?.code,
stockCode,
contract.contractName ?? '',
StockRegularUtils.isOption(showCode),
ContractMainType.NONE,
ContractDirectionType.NONE
)
}
return new ContractInfo()
})
}
}
@@ -0,0 +1,239 @@
import { HXEventMonitorPlugin } from "@kernel/app_monitor_event";
import {
HXMonitor,
HXMonitorLogger,
IElkService,
IMonitorAppConfig,
IMonitorOnStartCallback
} from "@kernel/app_monitor_lib";
import { HXPerformancePlugin } from "@kernel/app_monitor_performance";
import { HXWebMonitorPlugin } from '@kernel/app_monitor_web';
import { HXUserService } from "biz_hxservice";
import { ElkService, ElkUploadMessage, ElkUploadMessageBuilder } from "service_monitor";
import {
AuthLogMonitor,
DefaultLogParamConfig,
HXTcpMonitorPlugin,
LogParamsConfig,
TcpMonitorConfig
} from "@kernel/app_monitor_tcp";
import { HXAuthLogMonitor } from "biz_auth";
import { HXHttpMonitorPlugin } from "@kernel/app_monitor_http";
import { HXCrashMonitorPlugin, ICrashMonitorConfig } from "@kernel/app_monitor_crash";
import { router } from "@kit.ArkUI";
import { GIT_COMMIT } from "biz_common/src/main/ets/utils/debug/GitBuildConfig";
import { AppConfigManager, getConnectionManager } from "biz_common";
import { HxCBASAgentProvider } from "@b2c-spi/hx_cbas_api";
import {
AppContextLike,
type LeakWatcherConfig,
initializeWatcherFromPreference
} from '@kernel/ohos_leak_watcher';
import BuildProfile from "BuildProfile";
const APP_ID = "mobile-archive-am-qihuo-harmony"
const DEBUG_VERSION = "FUHM037.08.999.10.32"
export class APMHelper {
static clientIp: string = "";
/**
* 判断是否为测试版本(debug 或 forTest)
*/
static isDebugOrTestBuild(): boolean {
const modeName = BuildProfile.BUILD_MODE_NAME.toString()
const targetName = BuildProfile.TARGET_NAME.toString()
return modeName === 'debug' || targetName === 'forTest'
}
/**
* 初始化APM
* @param context
*/
static init(context: Context) {
//目前先只在debug包下打印日志
const modeName: string = BuildProfile.BUILD_MODE_NAME.toString()
HXMonitorLogger._logOpen = modeName === 'debug'
HXMonitor.builder(context)
.setAppId(APP_ID)
.hostParameter(new HXMonitorAppConfig())
.setElkService(new APMElkService())
.plugin(new HXPerformancePlugin())
.plugin(new HXEventMonitorPlugin())
.plugin(new HXTcpMonitorPlugin(new HXTcpMonitorConfig()))
.plugin(new HXWebMonitorPlugin())
.plugin(new HXHttpMonitorPlugin())
.plugin(new HXCrashMonitorPlugin(new HXCrashMonitorConfig()))
.build()
.startAllPlugins(new APMMonitorOnStartCallback())
APMHelper.initLeakWatcher(context)
}
/**
* 初始化内存泄漏监控
* @param context 应用上下文
*/
private static initLeakWatcher(context: Context) {
let appContext: AppContextLike = {
filesDir: context.getApplicationContext().filesDir || ''
}
let config: LeakWatcherConfig = {
enabled: true,
appContext: appContext,
baseDir: `${appContext.filesDir}/jsleak_watcher`,
leakObjectTypes: [],
resourceWatcher: {
enabled: true,
row: 1,
size: 10240,
timeOut: 1
},
retention: { maxArtifacts: 50, maxDays: 7, maxTotalBytes: 536870912 },
upload: {
enabled: false,
url: '',
method: 'POST',
headers: []
}
};
initializeWatcherFromPreference(config);
}
}
class APMMonitorOnStartCallback implements IMonitorOnStartCallback {
onStarted(code: number): void {
//暂时不做操作
HXMonitorLogger.d("APMHelper", `APM started:${code}`)
}
}
/**
* apm异常日志回调
*/
class APMElkService implements IElkService {
pushBusinessLog(logType: string, bt: string, message: string | null): void;
pushBusinessLog(logType: string, bt: string, message: string | null, extraData: object | null): void;
pushBusinessLog(logType: string, bt: string, message: string | null, extraData?: object | null): void {
try {
const builder = new APMElkBuilder(logType)
builder.userid(HXUserService.getInstance().getUserId())
builder.messageKey(bt)
builder.messageValue(message || "")
if (extraData) {
builder.messageExt(JSON.stringify(extraData))
}
const elkMessage = new ElkUploadMessage(builder)
ElkService.getInstance().pushBusinessLog(elkMessage)
} catch (e) {
console.error("APMElkService upload message fail", e)
}
}
}
class APMElkBuilder extends ElkUploadMessageBuilder {
constructor(biz_level: string) {
super(Date.now(), "apm", biz_level);
}
}
/**
* 崩溃采集配置
*/
class HXCrashMonitorConfig implements ICrashMonitorConfig {
provideUserName(): string {
return HXUserService.getInstance().getUserName();
}
provideBuildType(): string {
return BuildProfile.BUILD_MODE_NAME.toString()
}
provideAppName(): string {
return getContext().applicationInfo.name
}
async provideCBASInfo(): Promise<string> {
const result = await HxCBASAgentProvider.getInstance().getRecentCBASData()
return result || "none"
}
provideCurrentPageId(): string {
return router.getState().name
}
provideGitVersion(): string {
return GIT_COMMIT
}
providePackageName(): string {
return getContext().applicationInfo.name
}
}
/**
* APM配置
*/
class HXMonitorAppConfig extends IMonitorAppConfig {
getUserId(): string | null {
return HXUserService.getInstance().getUserIdFromLocal()
}
getDeviceCode(): string | null {
//deviceInfo.ODID 目前华为获取设备id的api已经不可靠,现改为userid。
return this.getUserId()
}
getVersion(): string {
if (APMHelper.isDebugOrTestBuild()) {
return DEBUG_VERSION
}
return AppConfigManager.getInstance().getConfigProvider()?.getInnerVersionFull()??DEBUG_VERSION
}
}
/**
* tcp监控配置
*/
class HXTcpMonitorConfig extends TcpMonitorConfig {
getLogParamConfig(): LogParamsConfig {
return new HXTcpLogParamsConfig()
}
getAuthLogMonitor(): AuthLogMonitor {
return HXAuthLogMonitor.getInstance()
}
}
class HXTcpLogParamsConfig extends DefaultLogParamConfig {
getAppId(): string | null {
return APP_ID
}
getAppVersion(): string | null {
if (APMHelper.isDebugOrTestBuild()) {
return DEBUG_VERSION
}
return AppConfigManager.getInstance().getConfigProvider()?.getInnerVersionFull()??DEBUG_VERSION;
}
getPlatform(): string | null {
return AppConfigManager.getInstance().getConfigProvider()?.getAppPlatform()??APP_ID
}
getServerIp(): string | null {
return getConnectionManager()?.getConnectedIp() || ""
}
getUid(): string | null {
return HXUserService.getInstance().getUserIdFromLocal()
}
getCip(): string | null {
return APMHelper.clientIp
}
isUserVIP(): boolean {
return false
}
}
@@ -0,0 +1,96 @@
import hiAppEvent from '@ohos.hiviewdfx.hiAppEvent';
import { HXLog } from 'biz_common/src/main/ets/logger/HXLog';
import { HXUserService } from 'biz_hxservice';
import { ElkService, ElkUploadMessage, ElkUploadMessageBuilder } from 'service_monitor';
const TAG: string = 'eventSubscription';
export function eventSubscription() {
hiAppEvent.addWatcher({
name: "futures",
appEventFilters: [//APP_CRASH, APP_FREEZE事件将在下次启动时获取
{
domain: hiAppEvent.domain.OS,
names: [hiAppEvent.event.APP_CRASH, hiAppEvent.event.APP_FREEZE, hiAppEvent.event.MAIN_THREAD_JANK,
hiAppEvent.event.CPU_USAGE_HIGH, hiAppEvent.event.RESOURCE_OVERLIMIT, hiAppEvent.event.ADDRESS_SANITIZER]
},
],
onReceive: async (domain: string, appEventGroups: Array<hiAppEvent.AppEventGroup>) => {
HXLog.i(TAG, `HiAppEvent onReceive: domain=${domain}`)
setTimeout(() => {
scheduleHiAppEventUpload(appEventGroups)
}, 1000)
}
});
}
export function scheduleHiAppEventUpload(appEventGroups: Array<hiAppEvent.AppEventGroup>) {
try {
let uploadMessage: Array<HiAppEventUploadInfo> = getFaultMessage(appEventGroups)
if (uploadMessage.length > 0) {
uploadMessage.forEach((message: HiAppEventUploadInfo, index: number) => {
uploadAppEventElkMessage("e", message, true)
})
}
} catch (e) {
const error : string = ' Error while scheduleHiAppEventUpload, the appEventGroups is ' + JSON.stringify(appEventGroups)
HXLog.e(TAG, error)
uploadAppEventElkMessage('e',{message_key: 'scheduleHiAppEventUpload', message_value:error},true)
}
}
function getFaultMessage(appEventGroups: Array<hiAppEvent.AppEventGroup>) {
const messageArr: Array<HiAppEventUploadInfo> = []
if (appEventGroups && appEventGroups.length > 0) {
// 遍历事件组
appEventGroups.forEach((eventGroup: hiAppEvent.AppEventGroup) => {
// 遍历事件对象集合
eventGroup.appEventInfos.forEach((eventInfo: hiAppEvent.AppEventInfo) => {
let message: string = '';
message += `HiAppEvent eventInfo.domain=${eventInfo.domain}\n` // 事件领域
+ `HiAppEvent eventInfo.name=${eventInfo.name}\n` // 事件名称
+ `HiAppEvent eventInfo.eventType=${eventInfo.eventType}\n` // 事件名称
+ `HiAppEvent eventInfo.params.time=${eventInfo.params['time']}\n` // 事件发生的时间
+ `HiAppEvent eventInfo.params.crash_type=${eventInfo.params['crash_type']}\n`
+ `HiAppEvent eventInfo.params.foreground=${eventInfo.params['foreground']}\n`
+ `HiAppEvent eventInfo.params.bundle_version=${eventInfo.params['bundle_version']}\n`
+ `HiAppEvent eventInfo.params.bundle_name=${eventInfo.params['bundle_name']}\n`
+ `HiAppEvent eventInfo.params.exception=${JSON.stringify(eventInfo.params['exception'])}\n`
+ `HiAppEvent eventInfo.params.hilog.size=${eventInfo.params['hilog'].length}\n`;
const uploadInfo: HiAppEventUploadInfo = {
message_key: eventInfo.domain,
message_value: eventInfo.name,
message_ext: message
}
messageArr.push(uploadInfo)
})
})
}
return messageArr
}
function uploadAppEventElkMessage(biz_level: string, info: HiAppEventUploadInfo, isInstant: boolean) {
const messageBuilder = new HiAppEventElkBuilder(biz_level)
messageBuilder.userid(HXUserService.getInstance().getUserId())
if (info) {
messageBuilder.messageKey(info?.message_key ? info.message_key : '')
messageBuilder.messageValue(info?.message_value ? info.message_value : '')
messageBuilder.messageExt(info?.message_ext ? info.message_ext : '')
}
const message = new ElkUploadMessage(messageBuilder)
ElkService.getInstance().pushBusinessLog(message, isInstant)
}
interface HiAppEventUploadInfo {
message_key?: string,
message_value?: string,
message_ext?: string
}
class HiAppEventElkBuilder extends ElkUploadMessageBuilder {
constructor(biz_level: string = 'e') {
super(Date.now(), "HiAppEvent", biz_level);
}
}
@@ -0,0 +1,47 @@
import { HXUserService } from 'biz_hxservice';
import { ElkService, ElkUploadMessage, ElkUploadMessageBuilder } from 'service_monitor';
export class EntryMonitor {
private static instance: EntryMonitor;
public static getInstance() : EntryMonitor{
if(!EntryMonitor.instance) {
EntryMonitor.instance = new EntryMonitor()
}
return EntryMonitor.instance
}
public UploadElkMessage(biz_level:string,info: EntryUploadInfo, isInstant:boolean) {
const messageBuilder = new EntryElkBuilder(biz_level)
messageBuilder.userid(HXUserService.getInstance().getUserId())
if(info) {
messageBuilder.messageKey(info?.message_key ? info.message_key : '' )
messageBuilder.messageValue(info?.message_value ? info.message_value : '')
messageBuilder.messageExt(info?.message_ext ? info.message_ext : '')
}
const message = new ElkUploadMessage(messageBuilder)
ElkService.getInstance().pushBusinessLog(message,isInstant)
}
}
export function buildEntryUploadBasicInfo(message:string,caller:string) {
let info : EntryUploadInfo = {
message_key: caller,
message_value: `${caller} upload message ${message} `,
}
return info;
}
export interface EntryUploadInfo {
message_key?:string,
message_value?:string,
message_ext?:string
}
class EntryElkBuilder extends ElkUploadMessageBuilder {
constructor(biz_level: string) {
super(Date.now(), "entry", biz_level);
}
}
@@ -0,0 +1,67 @@
import { PassportManager } from "biz_auth/src/main/ets/auth/PassportManager";
import { CookieManager, HXUserService } from "biz_hxservice";
import { SecurityMonitorProxy } from "lib_apm_security";
import { bundleManager } from "@kit.AbilityKit";
import { AppConfigManager, AppInfoConfig } from "biz_common";
import { StringBuilder } from "hxutil";
import { StrUtil } from "@pura/harmony-utils";
/**
* author:zhangshan2
* created on 2025/1/3
* desc:
*/
export class SecurityMonitorInit{
private static instance:SecurityMonitorInit
private constructor() {
}
static getInstance(){
if (!SecurityMonitorInit.instance) {
SecurityMonitorInit.instance = new SecurityMonitorInit()
}
return SecurityMonitorInit.instance
}
/**
* 崩溃保护初始化
*/
init(){
if (!AppInfoConfig.Official){
return;
}
SecurityMonitorProxy.getInstance().commonInfInfo=()=>{
let userId = HXUserService.getInstance().getUserIdFromLocal();
let info = bundleManager.getBundleInfoForSelfSync(bundleManager.BundleFlag.GET_BUNDLE_INFO_DEFAULT);
let sb = new StringBuilder();
sb.append("userId:").append(userId).append("\r\n")
.append("bundleName:").append(info.name).append("\r\n")
.append("versionName:").append(info.versionName).append("\r\n")
.append("innerVersion:").append(AppInfoConfig.APPLET_INNER_VERSION)
return sb.toString();
}
SecurityMonitorProxy.getInstance().agent=()=>{
return AppConfigManager.getInstance().getUA();
}
SecurityMonitorProxy.getInstance().innerVersion = ()=>{
return AppInfoConfig.APPLET_INNER_VERSION_FULL;
}
SecurityMonitorProxy.getInstance().isUserTemp = ()=>{return HXUserService.getInstance().isTemporaryUser()} ;
SecurityMonitorProxy.getInstance().cooke = ()=>{
let cookie = CookieManager.getInstance().getCookieSync();
if (!cookie) {
return "";
}
return cookie;
};
//初始化启动崩溃保护
SecurityMonitorProxy.getInstance().initSecurityMonitor();
}
}
@@ -0,0 +1,43 @@
import { connection } from "@kit.NetworkKit";
import { Want } from "@kit.AbilityKit";
import { AppUtil } from "@pura/harmony-utils";
import { HXLog } from "biz_common";
export class HXNetwork {
private static instance: HXNetwork;
private TAG: string = "HXNetwork";
public static getInstance(): HXNetwork {
if (!HXNetwork.instance) {
HXNetwork.instance = new HXNetwork()
}
return HXNetwork.instance
}
/**
* 检测网络是否可用
*/
detectNet(): boolean {
try {
let handle = connection.getDefaultNetSync();
if (!handle || handle.netId === 0) {
// 是否获取到有效网络连接
HXLog.e(this.TAG, "Can\'t get available connection!");
return false;
}
let capabilities = connection.getNetCapabilitiesSync(handle);
let cap = capabilities.networkCap || [];
// 网络是否可用
if (!cap.includes(connection.NetCap.NET_CAPABILITY_VALIDATED) && !cap.includes(connection.NetCap.NET_CAPABILITY_INTERNET)) {
HXLog.e(this.TAG, "The network is not available.");
return false;
}
return true;
} catch (e) {
HXLog.e(this.TAG, e);
return false;
}
}
}
@@ -0,0 +1,77 @@
/**
* 网络异常(不可用)弹窗
*/
@Preview
@Component
export struct NetworkAnomalyDialog {
exitApp?: () => void;
setup?: () => void;
retry?: () => void;
exitBtnText: string | ResourceStr = "退出";
setupBtnText: string | ResourceStr = "设置网络";
retryBtnText: string | ResourceStr = "重试";
//弹窗标题
title?: string | ResourceStr = "提示";
//弹窗正文
content?: string | ResourceStr = "联网可能异常,请检查您的网络!";
borderW: number = 0.7;
borderR: number = 10;
marginTop: number = 20;
build() {
Column() {
Text(this.title).fontSize(18).margin({ top: this.marginTop }).fontWeight(FontWeight.Bold)
.fontColor($r('app.color.elements_text_primary_02'))
Text(this.content).fontSize(14).margin({ top: this.marginTop })
.fontColor($r('app.color.elements_text_primary_02'))
Row() {
Button(this.exitBtnText, { type: ButtonType.Normal, stateEffect: true })
.onClick(() => {
if (this.exitApp) {
this.exitApp();
}
})
.fontColor($r('app.color.elements_text_secondary'))
.backgroundColor($r('app.color.surface_layer1_foreground'))
.layoutWeight(1)
.border({
width: { top: this.borderW, right: this.borderW },
color: $r('app.color.elements_others_divider_primary'),
radius: { bottomLeft: this.borderR }
})
Button(this.setupBtnText, { type: ButtonType.Normal, stateEffect: true })
.onClick(() => {
if (this.setup) {
this.setup();
}
})
.fontColor($r('app.color.elements_text_primary_01'))
.backgroundColor($r('app.color.surface_layer1_foreground'))
.layoutWeight(1)
.border({
width: { top: this.borderW },
color: $r('app.color.elements_others_divider_primary'),
})
Button(this.retryBtnText, { type: ButtonType.Normal, stateEffect: true })
.onClick(() => {
if (this.retry) {
this.retry();
}
})
.fontColor($r('app.color.elements_text_primary_01'))
.backgroundColor($r('app.color.surface_layer1_foreground'))
.layoutWeight(1)
.border({
width: { top: this.borderW, left: this.borderW },
color: $r('app.color.elements_others_divider_primary'),
radius: { bottomRight: this.borderR }
})
}
.margin({ top: this.marginTop })
}
.backgroundColor($r('app.color.surface_layer1_foreground'))
.width('75%')
.borderRadius(this.borderR)
}
}
@@ -0,0 +1,123 @@
import { AppConfigManager, HXLog, PreferenceService } from 'biz_common'
import { common, Want } from '@kit.AbilityKit'
import router from '@ohos.router'
import { window } from '@kit.ArkUI'
import { deviceInfo } from '@kit.BasicServicesKit'
import { NetworkAnomalyDialog } from './NetworkAnomalyDialog'
import { HXNetwork } from './HXNetwork'
import { ConnectionSeq } from '@kernel/lib_communication'
import { AuthProviderService } from 'biz_auth'
import { AdvertisingMonitorService } from 'biz_hxservice'
const SettingWant: Want = {
bundleName: 'com.huawei.hmos.settings',
abilityName: 'com.huawei.hmos.settings.MainAbility',
}
/**
* 网络异常页面
* 用于承接空白Logo页面,网络异常弹窗
*/
@Entry
@Component
struct NetworkAnomalyPage {
private TAG: string = "HXNetwork";
private systemBarColor = "#00000000";
private systemBarContentColor = "#FFFFFFFF";
async setSystemBar() {
let context = (this.getUIContext().getHostContext() as common.UIAbilityContext);
// 获取当前应用窗口
let windowClass: window.Window = await window.getLastWindow(context);
// 前提:设置窗口为全屏窗口
if ((deviceInfo.deviceType !== '2in1')) {
windowClass.setWindowLayoutFullScreen(true);
} else {
windowClass.setWindowLayoutFullScreen(false);
}
await windowClass.setWindowSystemBarProperties({
// 颜色属性为ARGB,将蒙层设置为0%使其透明
navigationBarColor: this.systemBarColor,
statusBarColor: this.systemBarColor,
navigationBarContentColor: this.systemBarContentColor,
statusBarContentColor: this.systemBarContentColor
})
}
aboutToAppear(): void {
this.setSystemBar()
}
onExit() {
HXLog.i(this.TAG, "EXIT APP!!");
// 用户退出应用
(this.getUIContext().getHostContext() as common.UIAbilityContext).terminateSelf(() => {
});
}
onJumpToSetup() {
HXLog.i(this.TAG, "JumpSetup!!");
let context = (this.getUIContext().getHostContext() as common.UIAbilityContext);
context.startAbility(SettingWant, (err, data) => {
HXLog.i(this.TAG, `startAbility : err : ${JSON.stringify(err) ?? ''}`);
HXLog.i(this.TAG, `startAbility : data : ${JSON.stringify(data) ?? ''}`);
})
}
onRetry() {
const netAvailable = HXNetwork.getInstance().detectNet();
if (netAvailable) {
// 用户是否同意隐私政策
const userAgree = PreferenceService.getBooleanValueSync('Privacy', 'isUserAgree', false);
const jumpUrl = userAgree ? 'pages/Index' : 'pages/LauncherPage';
router.replaceUrl({ url: jumpUrl }, router.RouterMode.Single, (err) => {
if (err) {
console.error("Failed to start Index Page: " + err)
return;
}
if (userAgree) {
AdvertisingMonitorService.getInstance().init(this.getUIContext().getHostContext() as common.UIAbilityContext)
AppConfigManager.getInstance().fetchIMEI().then((isSuccess: boolean) => {
if (isSuccess) {
AuthProviderService.getInstance().sendAuthRequest(ConnectionSeq.HQ);
}
});
}
})
}
}
build() {
RelativeContainer() {
NetworkAnomalyDialog({
exitApp: this.onExit,
setup: this.onJumpToSetup,
retry: this.onRetry,
title: '提示',
content: '联网可能异常,请检查您的网络!'
})
.zIndex(4)
.align(Alignment.Center)
.alignRules(
{
'top': { 'anchor': '__container__', 'align': VerticalAlign.Top },
'bottom': { 'anchor': '__container__', 'align': VerticalAlign.Bottom },
'left': { 'anchor': '__container__', 'align': HorizontalAlign.Start },
'right': { 'anchor': '__container__', 'align': HorizontalAlign.End }
})
.backgroundColor($r('app.color.dialog_bg_99666666'))
Image($r('app.media.launch_logo'))
.objectFit(ImageFit.Auto)
.width('80%')
.height(205)
.alignRules({
'bottom': {
'anchor': "__container__",
'align': VerticalAlign.Bottom
},
'middle': { 'anchor': '__container__', 'align': HorizontalAlign.Center },
})
}.width('100%').height('100%')
}
}
@@ -0,0 +1,81 @@
import { SectorList } from 'biz_market';
import router from '@ohos.router';
import common from '@ohos.app.ability.common';
import window from '@ohos.window';
import { StatusBar } from '../widget/StatusBar';
import { SubPageBar } from 'biz_selfcode';
import { TypeUtil } from 'biz_common';
import { StockListPageConfig } from 'biz_market/src/main/ets/components/stockListPage/Config';
import { sectorListPageConfig } from 'biz_market/src/main/ets/features/sectorList/Config';
@Entry
@Component
struct SectorListPage {
@State barHeight: number = 0;
private pageConfig: StockListPageConfig = sectorListPageConfig;
private selectedTabIndex: number = 0;
context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext
async setSystemBar() {
// 获取当前应用窗口
let windowClass: window.Window = await window.getLastWindow(this.context)
// 获取状态栏高度
this.barHeight = windowClass.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM).topRect.height
// 将状态栏和导航栏的背景色设置为跟应用窗口相同的颜色
await windowClass.setWindowSystemBarProperties({
navigationBarColor: "#00000000",
statusBarColor: "#00000000",
navigationBarContentColor: "#FFFFFFFF",
statusBarContentColor: "#FFFFFFFF"
})
}
async resetSystemBar() {
// 获取当前应用窗口
let windowClass: window.Window = await window.getLastWindow(this.context)
// 将状态栏和导航栏的背景色设置为跟应用窗口相同的颜色
await windowClass.setWindowSystemBarProperties({
navigationBarColor: "#00000000",
statusBarColor: "#00000000",
navigationBarContentColor: "#FFFFFFFF",
statusBarContentColor: "#FFFFFFFF"
})
}
/**
*
* 从路由中获取参数并初始化页面数据
*/
aboutToAppear() {
let routerParam = router.getParams() as Record<string, Object>;
if (routerParam.selectedTabIndex && TypeUtil.isNumber(routerParam.selectedTabIndex)) {
this.selectedTabIndex = routerParam.selectedTabIndex as number;
}
if (routerParam.pageConfig) {
this.pageConfig = routerParam.pageConfig as StockListPageConfig;
}
this.setSystemBar()
}
aboutToDisappear() {
this.resetSystemBar()
}
build() {
Column() {
StatusBar();
SubPageBar({
title: this.pageConfig.pageTitle,
});
Flex({ direction: FlexDirection.Column }) {
SectorList({
defaultTabIndex: this.selectedTabIndex,
pageConfig: this.pageConfig
})
.flexShrink(1)
.height('100%')
}.layoutWeight(1)
}
}
}
@@ -0,0 +1,92 @@
import router from '@ohos.router'
import web_webview from '@ohos.web.webview';
import window from '@ohos.window';
import common from '@ohos.app.ability.common';
/**
* 暂时使用的页面
*/
@Entry
@Component
export default struct CommonWebPage {
private url: string = '';
private title: string = ''
private webController: WebviewController = new web_webview.WebviewController;
@State barHeight:number = 0;
context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext
async setSystemBar() {
// 获取当前应用窗口
let windowClass: window.Window = await window.getLastWindow(this.context)
// 获取状态栏高度
this.barHeight = windowClass.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM).topRect.height
// 将状态栏和导航栏的背景色设置为跟应用窗口相同的颜色
await windowClass.setWindowSystemBarProperties({
navigationBarColor: "#00000000",
statusBarColor: "#00000000",
navigationBarContentColor: "#FFFFFFFF",
statusBarContentColor: "#FFFFFFFF"
})
}
async resetSystemBar() {
// 获取当前应用窗口
let windowClass: window.Window = await window.getLastWindow(this.context)
// 将状态栏和导航栏的背景色设置为跟应用窗口相同的颜色
await windowClass.setWindowSystemBarProperties({
navigationBarColor: "#00000000",
statusBarColor: "#00000000",
navigationBarContentColor: "#FFFFFFFF",
statusBarContentColor: "#FFFFFFFF"
})
}
aboutToAppear() {
this.setSystemBar()
const routerParam = router.getParams() as Record<string, string>;
this.url = routerParam.url;
this.title = routerParam.title || '';
}
aboutToDisappear(){
this.resetSystemBar()
try {
this.webController.stop()
} catch (e) {
// WebView may already be detached.
}
}
build() {
Column() {
Blank().height(px2vp(this.barHeight) +2).color($r('app.color.subpage_title_red')).width('100%')
Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.Start }) {
Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.Start }) {
Column() {
Image($r('app.media.toolbar_arrow_left'))
.width(19)
.height(24)
.fillColor('#FFFFFF')
}.margin({ right: 16 })
.onClick(() => {
router.back();
})
.padding(10)
Text(this.title).fontSize(19).fontColor('#FFFFFF')
}.margin({ left: 16 })
}
.height(44).backgroundColor($r('app.color.navbar_bg_red')).width('100%')
Web({
src: this.url,
controller: this.webController
}).domStorageAccess(true)
.mixedMode(MixedMode.All)
.flexGrow(1)
.flexShrink(1)
}
.height('100%')
.width('100%')
}
}
@@ -0,0 +1,23 @@
import { DebugService, FloatingDebugWindow} from '@b2c-spi/hx_cbas_api';
/**
* 悬浮窗页面
*/
@Entry
@Component
struct FloatingWindowPage {
build() {
Column() {
FloatingDebugWindow({
onMinimize: (): void => { DebugService.getInstance().minimizeToIcon(); },
onClose: (): void => { DebugService.getInstance().hideWindow(); },
onMove: (deltaX: number, deltaY: number): void => {
DebugService.getInstance().moveWindow(deltaX, deltaY);
}
})
}
.width('100%')
.height('100%')
}
}
@@ -0,0 +1,548 @@
import { LoginCommonConstants } from 'biz_login';
import { RSAKeyManager, WxPaySidPermissionMarker } from 'biz_auth';
import common from '@ohos.app.ability.common';
import window from '@ohos.window';
import { BusinessError } from '@ohos.base';
import { saveSelfcodeSync } from 'service_watchlist';
import emitter from '@ohos.events.emitter';
import WTInit from 'biz_trade/src/main/ets/utils/WTInit';
import {
AdsPageConfig,
EmitterConstants,
GlobalContext,
HeadSettingManager,
HXLog,
HxTags,
IndexTabManager,
OptionPageConstants,
TabManager,
TabManagerConstants
} from 'biz_common';
import hilog from '@ohos.hilog';
import { CookieManager, HXUserService, KaihuFlagManager, NewUserManager, UserSourceManager } from 'biz_hxservice';
import { StatusBar } from '../widget/StatusBar';
import { abilityAccessCtrl, Permissions } from '@kit.AbilityKit';
import { deviceInfo } from '@kit.BasicServicesKit';
import { AdsPopupHost, FirstPage } from '@b2c/first_page';
import { router } from '@kit.ArkUI';
import { HQPage } from './future/HQPage';
import TradeBizPageContainer from './TradeBizPageContainer';
import { GrayTestManager } from 'lib_graytestmanager'
import { HexinVersionControl } from 'biz_trade/src/main/ets/utils/HexinVersionControl';
import { DiscoverPage } from '@b2c/biz_discover';
import { HQPageConstants } from 'biz_common/src/main/ets/constants/HQPageConstants';
import { NumberUtil } from '@pura/harmony-utils';
import { parseInt } from '@wolfx/lodash';
import { cbasService } from '../register/cbasregister/CbasServiceRegister';
import BuildProfile from 'biz_common/BuildProfile';
import { DebugFloatingBall } from './debug/DebugFloatingBall';
import { GroupSubManager } from 'base_mobilepush';
import { HXPushService } from 'service_push';
import { FIRST_PAGE_ONAPPEAR_EVENT } from '../entryability/WantManager';
import { DialogHub } from '@hadss/dialoghub';
import { DrawLineInit, QuoteCustomSettingManager, SettingHelper, SettingDataSource, QuoteSettingEvents, CurveBottomToolsDataManager } from 'biz_quote';
import { TradeSettingManager } from 'biz_trade/src/main/ets/setting';
import { AutoLoginManager } from 'biz_trade/src/main/ets/trade/manager/AutoLoginManager';
import { ColourModeStatus } from '@kernel/theme_manager';
import { enableDarkMode, ThemeManagerEmitterConstants } from '@kernel/theme_manager';
import { CmeTraceForeignManager } from 'biz_quote/src/main/ets/manager/CmeTraceForeignManager';
import { HQUGCManager } from 'biz_market';
import { AdsManager } from '@b2c-f/fuhm-ads';
const TAG: string = 'Struct Index';
const SWIPER_INDEX_0: number = 0;
const SWIPER_INDEX_1: number = 1;
@Entry
@Component
struct IndexM {
/**************************************/
//Tab管理
tabManager: TabManager = new IndexTabManager();
//主页TabIndex
@Provide('mainPageIndex') currentIndex: number = 0;
private tabController: TabsController = new TabsController();
/**************************************/
//交易用的index page show
@Provide MainPageShow: boolean = false;
@Provide pageShow: boolean = false;
/**************************************/
//全局userId, userName快速获取
@Provide userId: string = '';
@Provide userName: string = '';
/**************************************/
context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
/**************************************/
//一多断点
@StorageLink('displayBreakPoint') curBp: string = 'sm'
@StorageLink('windowWidthVp') windowWidthVp: number = 0;
/**************************************/
//自选业务
@Provide('SIDE_BAR_ENABLE') @Watch('sideBarStatusChanged') sideBarEnable: boolean = false;
@Provide('PRESSED_ROW') pressedRow: Array<string> = new Array<string>(3).fill('');
/**************************************/
// 云同步数据更新回调
private onSettingUpdateCallback = () => {
HXLog.i(TAG, 'QuoteSettingEvents.SETTING_UPDATE received, clearing HeadSettingManager cache')
HeadSettingManager.getInstance().clearCache()
}
@Provide('SELF_CODE_TITLE') selfCodeTitle: string = '同花顺自选';
/**************************************/
//侧滑栏
@State scrollable: boolean = false;
@State swiperIndex: number = SWIPER_INDEX_1;
private swiperController: SwiperController = new SwiperController()
@State swiperOffset: number = 0;
@State @Watch('coeChanged') coefficient: number = 0;
//蒙层可见性
@State stackVis: boolean = false
//布局总宽度
private totalWidth = 0
@State isShowStatusBar: boolean = false
@State isHideYaoYi: boolean = false;
// 行情二级Tab
@Provide hqPageSubTabIndex: number = HQPageConstants.HQ_PAGE_INDEX_SELF_CODE_PAGE
// 期权三级Tab
@Provide optionPageL3TabIndex: number = OptionPageConstants.HQ_PAGE_INDEX_T_QUOTE_PAGE
// 主题监听
@StorageProp(enableDarkMode) @Watch('onThemeModeChanged') enableDarkMode: boolean = ColourModeStatus.enableDarkMode()
// 主题变化回调
onThemeModeChanged() {
HXLog.d(TAG, 'onThemeModeChanged triggered, enableDarkMode=' + this.enableDarkMode)
this.setSystemBar(this.stackVis)
}
parseOffset(curOffset: number): number {
if (this.totalWidth > 0) {
let coe = this.swiperIndex === 1 ? (Math.abs(curOffset) / (this.totalWidth * 0.85)) :
(1 - Math.abs(curOffset) / (this.totalWidth * 0.85))
console.log(`lym: coe: ${coe}`)
return coe * 0.4
}
return 0
}
coeChanged() {
}
sideBarStatusChanged() {
}
//沉浸式设置、状态栏设置
async setSystemBar(stackVis: boolean) {
// 获取主窗口
let windowStageInstance: window.WindowStage = AppStorage.get('EntryAbilityWindowStage') as window.WindowStage
if (!windowStageInstance) {
return
}
let windowClass: window.Window = windowStageInstance.getMainWindowSync()
// 前提:设置窗口为全屏窗口
if ((deviceInfo.deviceType !== '2in1')) {
windowClass.setWindowLayoutFullScreen(true);
} else {
console.log("deviceType" + deviceInfo.deviceType)
windowClass.setWindowLayoutFullScreen(false);
}
// 根据主题设置状态栏内容颜色
let isDarkMode = ColourModeStatus.enableDarkMode()
let contentColor = isDarkMode ? "#FFFFFFFF" : "#D6000000"
await windowClass.setWindowSystemBarProperties({
// 颜色属性为ARGB,将蒙尘设置为0%使其透明
navigationBarColor: getContext().resourceManager.getStringByNameSync('entry_transparent_color'),
statusBarColor: getContext().resourceManager.getStringByNameSync('entry_transparent_color'),
navigationBarContentColor: stackVis
? getContext().resourceManager.getStringByNameSync('entry_navigation_bar_content_with_mask_color')
: contentColor,
statusBarContentColor: stackVis
? getContext().resourceManager.getStringByNameSync('entry_navigation_bar_content_with_mask_color')
: contentColor
})
}
@Builder
TabBuilder(title: string, targetIndex: number, selectedImg: Resource, normalImg: Resource) {
Column() {
Image(this.currentIndex === targetIndex ? selectedImg : normalImg)
.size({ width: 24, height: 24 })
.fillColor(this.currentIndex !== targetIndex ? $r('app.color.elements_text_primary_02') : $r('app.color.elements_text_primary_01'))
Text(title)//'#2E58FF'
.fontColor(this.currentIndex === targetIndex ? $r('app.color.elements_text_primary_01') : $r('app.color.elements_text_primary_02')).fontSize('11fp').margin({ top: 4 })
}
.hoverEffect(HoverEffect.Highlight)
//.width('100%')
.height(50)
.justifyContent(FlexAlign.Center)
.backgroundColor($r('app.color.surface_layer1_foreground'))
}
updateUserInfo() {
this.userId = HXUserService.getInstance().getUserId();
this.userName = HXUserService.getInstance().getUserName();
cbasService.setUserId(this.userId)
}
subscribeLoginEvent() {
emitter.on({
eventId: LoginCommonConstants.USER_LOGIN
}, async () => {
HXLog.d(TAG, HxTags.entryTags + 'subscribe login index triggered at ' + Date.now())
this.updateUserInfo();
// 登录后刷新 drawline 用户上下文,确保后续同步按当前账号隔离。
DrawLineInit.init()
WxPaySidPermissionMarker.requestPermissionIfMarked()
WTInit.initAfterAppLogin()
GroupSubManager.getInstance().requestGroupData();
RSAKeyManager.getInstance().requestRSAInfo();
GrayTestManager.getInstance().requestGrayTestWhenUserIdChanged(this.userId)
HXPushService.getInstance().shouldUploadNotificationByUseridChanged();
// 发起 CME 内外盘联动配置请求(只请求一次)
CmeTraceForeignManager.getInstance().request()
NewUserManager.getInstance().onAuthSucceed(() => {
AdsManager.reset()
if (NewUserManager.getInstance().needRequestAdsNow()) {
AdsManager.refreshNotifyFirstPageAds(true)
}
})
KaihuFlagManager.getInstance().onConnectToServer()
UserSourceManager.getInstance().onConnectToServer()
// 更新行情设置管理器的用户ID(登录成功强制重新下载云端数据)
QuoteCustomSettingManager.getInstance().updateUserId(
HXUserService.getInstance().getUserIdFromLocal(),
true // 强制重新下载云端数据
)
// 更新底部操作栏排序管理器的用户ID(登录成功强制重新下载云端数据)
CurveBottomToolsDataManager.getInstance().updateUserId(
HXUserService.getInstance().getUserIdFromLocal(),
true
)
// 更新交易设置管理器的用户ID(登录成功强制重新下载云端数据)
TradeSettingManager.getInstance().updateUserId(
HXUserService.getInstance().getUserIdFromLocal(),
false
)
});
emitter.on({
eventId: LoginCommonConstants.USER_LOGOUT
}, () => {
HXLog.d(TAG, 'LoginEvent LoginCommonConstants.USER_LOGOUT')
this.updateUserInfo();
// 登出后重新写入临时用户上下文,避免沿用上一个账号的画线身份。
DrawLineInit.init()
CurveBottomToolsDataManager.getInstance().updateUserId(
HXUserService.getInstance().getUserIdFromLocal(),
false
)
// 退出账号,重置所有自动登录信息
AutoLoginManager.getInstance().forceClearAutoLoginAccountToken()
// 退出登录时清除表头设置缓存,并发送事件通知页面刷新为默认配置
HeadSettingManager.getInstance().clearCache()
emitter.emit({ eventId: QuoteSettingEvents.SETTING_UPDATE })
});
/*emitter.on({
eventId: EventConstants.ID_JUMP_TO_WT_FIRST_PAGE
}, (data) => {
// 用于其它页面 跳到主页面,并显示指定tab
let index = data.data!!["tabIndex"] as number;
if (this.currentIndex != index) {
router.back({
url: "pages/Index",
params: {
tabIndex: index,
}
})
}
});*/
}
subscribeTabChangeIndex() {
emitter.on({ eventId: EmitterConstants.TAB_UI_MANAGER_CHANGE_INDEX }, (eventData) => {
this.tabController.changeIndex(2)
if (eventData && eventData.data) {
let tabUIManagerIndex: string | undefined = eventData.data[EmitterConstants.EVENT_DATA_KEY_TAB_INDEX]
if (tabUIManagerIndex && NumberUtil.isNumber(tabUIManagerIndex)) {
this.tabController.changeIndex(parseInt(tabUIManagerIndex))
}
let qhPageSubTabIndex: string | undefined =
eventData.data[EmitterConstants.EVENT_DATA_KEY_HQ_PAGE_SUB_TAB_INDEX]
if (qhPageSubTabIndex && NumberUtil.isNumber(qhPageSubTabIndex)) {
this.hqPageSubTabIndex = parseInt(qhPageSubTabIndex)
}
let optionPageL3TabIndex: string | undefined =
eventData.data[EmitterConstants.EVENT_DATA_KEY_OPTION_PAGE_SUB_TAB_INDEX]
if (optionPageL3TabIndex && NumberUtil.isNumber(optionPageL3TabIndex)) {
this.optionPageL3TabIndex = parseInt(optionPageL3TabIndex)
}
}
})
}
subscribeSwiperChangeIndex() {
emitter.on({ eventId: EmitterConstants.SWIPER_CHANGETOINDEX_0 }, () => {
this.swiperController.changeIndex(0, true)
this.coefficient = 0.4
this.stackVis = true
this.setSystemBar(this.stackVis)
})
emitter.on({ eventId: EmitterConstants.SWIPER_CHANGETOINDEX_1_TEMP }, () => {
this.swiperController.changeIndex(1, true)
this.stackVis = false
this.setSystemBar(this.stackVis)
})
}
unSubscribeLoginEvent() {
HXLog.d(TAG, HxTags.entryTags +
'Unsubscribe LoginEvent LoginCommonConstants.USER_LOGOUT ,LoginCommonConstants.USER_LOGOUT triggered at ' +
Date.now())
emitter.off(LoginCommonConstants.USER_LOGIN);
emitter.off(LoginCommonConstants.USER_LOGOUT);
}
aboutToDisappear() {
HXLog.d(TAG, 'Index aboutToDisappear triggered at ' + Date.now())
emitter.off(EmitterConstants.SWIPER_CHANGETOINDEX_1_TEMP)
emitter.off(EmitterConstants.TAB_UI_MANAGER_CHANGE_INDEX)
emitter.off(QuoteSettingEvents.SETTING_UPDATE, this.onSettingUpdateCallback)
this.unSubscribeLoginEvent();
// this.unSubscribeThemeChange();
saveSelfcodeSync()
}
aboutToAppear() {
hilog.info(0x0000, 'Entry IndexPage', '%{public}s', 'page aboutToAppear');
this.subscribeLoginEvent();
this.subscribeSwiperChangeIndex()
this.subscribeTabChangeIndex()
this.setSystemBar(this.stackVis)
this.isHideYaoYi = HexinVersionControl.hideTrade();
DialogHub.init(this.getUIContext())
// 初始化行情设置管理器(使用当前用户ID,可能是临时用户)
QuoteCustomSettingManager.getInstance().initialize(
this.context,
HXUserService.getInstance().getUserIdFromLocal()
).catch((err: Error) => {
HXLog.e(TAG, `QuoteCustomSettingManager early init failed: ${err.message}`)
})
CurveBottomToolsDataManager.getInstance().initialize(
this.context,
HXUserService.getInstance().getUserIdFromLocal()
).catch((err: Error) => {
HXLog.e(TAG, `CurveBottomToolsDataManager early init failed: ${err.message}`)
})
HQUGCManager.get().queryData(CookieManager.getInstance().getCookieSync())
// 配置表头设置管理器的云同步访问器
HeadSettingManager.getInstance().setSettingAccessor(
(key: string, defaultValue: ESObject): ESObject => SettingHelper.getSetting(key, defaultValue, SettingDataSource.QUOTE_CLOUD),
(key: string, value: ESObject): void => { SettingHelper.updateSetting(key, value, SettingDataSource.QUOTE_CLOUD) }
)
// 监听云同步数据更新事件,清除表头设置缓存以便重新从云端读取
emitter.on({ eventId: QuoteSettingEvents.SETTING_UPDATE }, this.onSettingUpdateCallback)
// 初始化交易设置管理器(使用当前用户ID,可能是临时用户)
TradeSettingManager.getInstance().initialize(
this.context
).catch((err: Error) => {
HXLog.e(TAG, `TradeSettingManager early init failed: ${err.message}`)
})
// 冷启动先初始化一次 drawline 基础能力,保证首次进入行情页即可绘制同步线。
DrawLineInit.init()
}
onPageShow() {
this.updateUserInfo();
this.MainPageShow = true;
this.pageShow = true;
this.tabManager.notifyVisibilityChangeWithVisibility(true)
this.setSystemBar(this.stackVis)
//fixme 这里直接通过生命周期去请求是比较取巧的解决方案,应该在认证成功后就发起,后续修改
//index页面在前台且展示自选tab页
if (router.getParams()) {
let params = router.getParams() as Record<string, string>;
let indexStr = params.tabIndex;
if (indexStr) {
let index = Number.parseInt(indexStr.toString());
if (index && index != this.currentIndex) {
this.tabController.changeIndex(index);
}
} else if (params.loginSuccessBackParam && params.loginSuccessBackParam == 'selectjiaoyi') {
this.tabController.changeIndex(TabManagerConstants.INDEX_TAB_INDEX_TRADE_PAGE);
}
}
// 创建调试悬浮球(仅非release包)
if (BuildProfile.BUILD_MODE_NAME.toString() != "release" && DebugFloatingBall.canShow) {
let windowStage = AppStorage.get('EntryAbilityWindowStage') as window.WindowStage
DebugFloatingBall.createDebugFloatingBall(windowStage)
}
}
/**
* 根据是否隐藏交易获取发现tab的索引
* @returns
*/
private getDiscoverIndex():number{
return this.isHideYaoYi?TabManagerConstants.INDEX_TAB_INDEX_DISCOVER_PAGE-1:TabManagerConstants.INDEX_TAB_INDEX_DISCOVER_PAGE
}
/**
* 根据 Tab 索引获取广告悬浮球对应的 pageId
*/
private getFloatBallPageId(tabIndex: number): AdsPageConfig {
if (tabIndex === TabManagerConstants.INDEX_TAB_INDEX_FIRST_PAGE) {
return AdsPageConfig.FIRST_PAGE_ID
}
return AdsPageConfig.INVALID
}
onPageHide() {
this.MainPageShow = false;
this.pageShow = false;
this.tabManager.notifyVisibilityChangeWithVisibility(false)
}
/**
* 当首页节点挂载时触发
*/
private onFirstPageNodeAppear() {
// 发送首页挂载的通知
GlobalContext.get().eventHub.emit(FIRST_PAGE_ONAPPEAR_EVENT);
// 首页挂载后进行push服务的初始化检查和上报
HXPushService.getInstance().onFirstPageAppear(() => {
});
// 通知广告悬浮球页面切换(首次进入首页时需主动调用)
AdsPopupHost.getInstance().onPageJump(AdsPageConfig.FIRST_PAGE_ID);
}
build() {
Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
StatusBar()
.visibility(this.isShowStatusBar ? Visibility.Visible : Visibility.None)
Tabs({ controller: this.tabController }) {
if (this.tabManager.getShowingTabSet().has(TabManagerConstants.FirstPage)) {
TabContent() {
FirstPage({ tabManager: this.tabManager.getManager(TabManagerConstants.FirstPage) })
}
.tabBar(this.TabBuilder("首页", TabManagerConstants.INDEX_TAB_INDEX_FIRST_PAGE, $r("app.media.icon_home_fill_24"),
$r("app.media.icon_home_24")))
.onAppear(()=> {
this.onFirstPageNodeAppear();
})
}
if (this.tabManager.getShowingTabSet().has(TabManagerConstants.SelfcodePage)) {
TabContent() {
HQPage({ tabManager: this.tabManager.getManager(TabManagerConstants.SelfcodePage) })
}
.tabBar(this.TabBuilder("行情", TabManagerConstants.INDEX_TAB_INDEX_QUOTE_PAGE, $r("app.media.icon_line_chart_fill_24"),
$r("app.media.icon_line_chart_24")))
}
if (!this.isHideYaoYi && this.tabManager.getShowingTabSet().has(TabManagerConstants.TradePage)) {
TabContent() {
TradeBizPageContainer({
tabManager: this.tabManager.getManager(TabManagerConstants.TradePage)
});
}
.tabBar(this.TabBuilder("交易", TabManagerConstants.INDEX_TAB_INDEX_TRADE_PAGE, $r("app.media.icon_trade_fill_24")
, $r("app.media.icon_trade_24")))
}
if (this.tabManager.getShowingTabSet().has(TabManagerConstants.NewsPage)) {
TabContent() {
DiscoverPage({
tabManager: this.tabManager.getManager(TabManagerConstants.NewsPage)
})
}
.tabBar(this.TabBuilder("发现", this.getDiscoverIndex(), $r("app.media.icon_compass_fill_24"),
$r("app.media.icon_compass_24")))
}
}
.vertical(this.curBp === 'lg' ? true : false)
.barPosition(this.curBp === 'lg' ? BarPosition.Start : BarPosition.End)
.width('100%')
.height('100%')
.scrollable(false)
.flexGrow(1)
.flexBasis('0%')
.animationDuration(0)
.onChange((index: number) => {
if (!this.isHideYaoYi && this.currentIndex != 2 && index == 2 &&
HXUserService.getInstance().isTemporaryUser()) {
this.tabController.changeIndex(this.currentIndex)
router.pushUrl({
url: 'pages/login/LoginPage',
params: {
flexSwitchFlag: true,
sourcePage: 'Index',
backParam: 'selectjiaoyi'
},
}, router.RouterMode.Single)
} else {
if (index == TabManagerConstants.INDEX_TAB_INDEX_FIRST_PAGE) {
this.isShowStatusBar = false
} else {
this.isShowStatusBar = true
}
this.currentIndex = index;
this.scrollable = (!this.isHideYaoYi &&
this.currentIndex === TabManagerConstants.INDEX_TAB_INDEX_TRADE_PAGE) ? true : false;
this.tabManager.notifyIndexChange(index);
}
})
.onAreaChange((oldArea: Area, newArea: Area) => {
if (newArea.height) {
let height = newArea.height as number;
GlobalContext.initPageHeight(height);
}
})
.backgroundColor($r('app.color.surface_layer1_foreground'))
.barHeight(this.curBp === 'lg' ? '100%' : 50)
.barWidth(this.curBp == 'lg' ? 50 : '100%')
.animationDuration(0)
.divider({
strokeWidth: 1,
color: $r('app.color.elements_others_divider_primary')
})
if (this.curBp !== 'lg') {
Blank().height(px2vp(GlobalContext.navigationBarHeight)).flexShrink(0).backgroundColor($r('app.color.surface_layer1_foreground'))
}
}
.height('100%')
.width('100%')
}
}
function reqPermissionsFromUser(permissions: Array<Permissions>, context: common.UIAbilityContext): void {
let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
// requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗
atManager.requestPermissionsFromUser(context, permissions).then((data) => {
let grantStatus: Array<number> = data.authResults;
let length: number = grantStatus.length;
for (let i = 0; i < length; i++) {
if (grantStatus[i] === 0) {
// 用户授权,可以继续访问目标操作
} else {
// 用户拒绝授权,提示用户必须授权才能访问当前页面的功能,并引导用户到系统设置中打开相应的权限
return;
}
}
// 授权成功
}).catch((err: BusinessError) => {
console.error(`Failed to request permissions from user. Code is ${err.code}, message is ${err.message}`);
})
}
@@ -0,0 +1,19 @@
/**
* 自编指标草稿管理页面入口
*
* 此文件是路由入口,实际页面实现在 biz_indicator 模块中
*/
import { IndicatorDraftManagePage as IndicatorDraftManagePageImpl } from 'biz_indicator'
@Entry
@Component
export struct IndicatorDraftManagePage {
build() {
Column() {
IndicatorDraftManagePageImpl()
}
.width('100%')
.height('100%')
}
}
@@ -0,0 +1,19 @@
/**
* 指标参数设置页面入口
*
* 此文件是路由入口,实际页面实现在 biz_indicator 模块中
*/
import { IndicatorParamPage as IndicatorParamPageImpl } from 'biz_indicator'
@Entry
@Component
export struct IndicatorParamPage {
build() {
Column() {
IndicatorParamPageImpl()
}
.width('100%')
.height('100%')
}
}
@@ -0,0 +1,100 @@
/**
* indicator_parser HAR 验证测试页面
*
* Author: LY
* Date: 2026-01-07
*/
import { IndicatorParserTask } from 'biz_indicator'
@Entry
@Component
struct IndicatorParserTestPage {
@State testResult: string = '点击按钮开始测试'
@State isLoading: boolean = false
build() {
Column() {
Text('Indicator Parser HAR 测试')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ top: 50, bottom: 30 })
Button(this.isLoading ? '测试中...' : '开始测试')
.width('80%')
.height(50)
.fontSize(18)
.enabled(!this.isLoading)
.onClick(() => {
this.runTest()
})
Scroll() {
Text(this.testResult)
.fontSize(14)
.fontColor('#333333')
.padding(20)
}
.width('90%')
.height(400)
.margin({ top: 30 })
.backgroundColor('#F5F5F5')
.borderRadius(10)
}
.width('100%')
.height('100%')
.backgroundColor('#FFFFFF')
}
async runTest() {
this.isLoading = true
this.testResult = '开始测试...\n'
try {
// 1. 创建实例
this.testResult += '\n[1] 创建 IndicatorParserTask 实例...'
const parser = new IndicatorParserTask()
this.testResult += ' ✅ 成功\n'
// 2. 测试预解析 - 使用简单的 MA 均线公式(异步版本)
this.testResult += '\n[2] 测试预解析 (MA均线公式, 异步版本)...'
const script = 'MA5:MA(CLOSE,5);'
const preParseResult = await parser.getPreParseResAsync(script, '', '')
if (preParseResult) {
this.testResult += ' ✅ 成功\n'
this.testResult += ` 解析结果: ${preParseResult.substring(0, 200)}...\n`
} else {
this.testResult += ' ⚠️ 返回空 (可能需要数据)\n'
}
// 3. 测试获取列数
this.testResult += '\n[3] 测试 getColumnNum()...'
const colNum = parser.getColumnNum()
this.testResult += ` ✅ 列数: ${colNum}\n`
// 4. 测试获取行数
this.testResult += '\n[4] 测试 getRowNum()...'
const rowNum = parser.getRowNum()
this.testResult += ` ✅ 行数: ${rowNum}\n`
// 5. 测试清空数据
this.testResult += '\n[5] 测试 clearData()...'
parser.clearData()
this.testResult += ' ✅ 成功\n'
// 6. 销毁实例
this.testResult += '\n[6] 销毁实例...'
parser.destroy()
this.testResult += ' ✅ 成功\n'
this.testResult += '\n=============================\n'
this.testResult += '🎉 HAR 验证通过!所有接口可正常调用\n'
this.testResult += '=============================\n'
} catch (error) {
this.testResult += `\n\n❌ 测试失败: ${error}\n`
} finally {
this.isLoading = false
}
}
}
@@ -0,0 +1,143 @@
import router from '@ohos.router'
import { WebViewComponent, CustomWebviewController, NavigationBarViewModel } from 'biz_webview';
import display from '@ohos.display';
import window from '@ohos.window';
import common from '@ohos.app.ability.common';
import { WebViewUrlUtil } from 'biz_webview/src/main/ets/Util/WebViewUrlUtil';
import { handleBackEvent } from 'biz_common/src/main/ets/pc/PCAdapter';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { emitter } from '@kit.BasicServicesKit';
import { EmitterConstants } from 'biz_common';
const DOMAIN = 0x0000;
const TAG = 'LandscapeWebViewPage';
@Entry
@Component
export default struct LandscapeWebViewPage {
private webViewController: CustomWebviewController = new CustomWebviewController()
private url: string = ''
private refreshOnWidthChange: boolean = false;
@State onShow: boolean = false
navigationBarHandler: NavigationBarViewModel = new NavigationBarViewModel();
context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext
private windowClass: window.Window | null = null
@StorageLink('displayBreakPoint') @Watch('displayChanged') curBp: string = 'sm'
useCookie: boolean = false
//登录状态改变后刷新web
isLoginRefreshWeb: boolean = false
registerJSEvent() {
display.getDefaultDisplaySync()
}
aboutToAppear() {
let routerParam = router.getParams() as Record<string, string>;
this.url = routerParam.url;
routerParam = WebViewUrlUtil.assign(routerParam, this.url)
try {
this.refreshOnWidthChange = routerParam.refreshOnWidthChange === "true" ? true : false;
} catch (error) {
console.error(`getStringSync failed, error code: ${error.code}, message: ${error.message}.`)
}
this.isLoginRefreshWeb = "" + routerParam['isLoginRefreshWeb'] === "true" ? true : false;
if ("" + routerParam['useCookie'] === "true") {
this.useCookie = true
}
this.registerJSEvent()
// 提前切换到横屏,减少对其他页面的影响
this.switchToLandscape()
}
aboutToDisappear() {
}
// 拦截系统返回上一级
onBackPress() {
if (this.webViewController && this.webViewController.isAttached && this.webViewController.accessBackward()) {
this.webViewController.backward()
return true
}
return !this.navigationBarHandler.canGoBack()
}
onPageShow() {
this.onShow = true
// 从后台恢复时重新切换到横屏
this.switchToLandscape()
}
onPageHide() {
if (this.onShow == true) {
this.onShow = false
}
this.switchToPortrait()
}
displayChanged() {
}
/**
* 切换为横屏显示
*/
private async switchToLandscape(): Promise<void> {
try {
const mainWindow = await this.context.windowStage.getMainWindow()
this.windowClass = mainWindow
await this.windowClass.setPreferredOrientation(window.Orientation.LANDSCAPE)
} catch (err) {
hilog.warn(DOMAIN, TAG, '切换横屏失败: %{public}s', JSON.stringify(err));
}
}
/**
* 切换为竖屏显示
*/
private async switchToPortrait(): Promise<void> {
try {
const mainWindow = await this.context.windowStage.getMainWindow()
this.windowClass = mainWindow
await this.windowClass.setPreferredOrientation(window.Orientation.PORTRAIT)
} catch (err) {
hilog.warn(DOMAIN, TAG, '切换竖屏失败: %{public}s', JSON.stringify(err));
}
}
build() {
Stack({ alignContent: Alignment.TopStart }) {
Column() {
WebViewComponent({
urlString: this.url,
onShow: this.onShow,
webViewController: this.webViewController,
navigationBarHandler: this.navigationBarHandler,
refreshOnWidthChange: this.refreshOnWidthChange,
useCookie: this.useCookie,
isLoginRefreshWeb: this.isLoginRefreshWeb,
overScrollMode: OverScrollMode.NEVER
})
.layoutWeight(1)
}
.onKeyEvent((event: KeyEvent) => {
handleBackEvent(event)
})
.height('100%')
.width('100%')
.padding({ left: 44, right: 44 })
// 返回按钮
Image($r("app.media.navigate_back_icon"))
.width(24)
.height(24)
.margin({ left: 60, top: 16 })
.onClick(() => {
emitter.emit({ eventId: EmitterConstants.LANDSCAPE_WEBVIEW_BACK })
router.back();
})
}
.height('100%')
.width('100%')
.id('biz_webview/landscape_container')
}
}
@@ -0,0 +1,107 @@
import { AppConfigManager, PreferenceService } from 'biz_common'
import { common } from '@kit.AbilityKit'
import { hilog } from '@kit.PerformanceAnalysisKit'
import router from '@ohos.router'
import { AuthProviderService, HXAuthManager, LoginParamHelper } from 'biz_auth'
import { PrivacyUserDialog } from '../widget/PrivacyUserDialog'
import { StatusBar } from '../widget/StatusBar'
import { window } from '@kit.ArkUI'
import { ConnectionSeq } from '@kernel/lib_communication/src/main/ets/data/CommunicationConstants'
import { deviceInfo } from '@kit.BasicServicesKit'
import { AdvertisingMonitorService } from 'biz_hxservice'
/**
* 启动页面
* 用于承接空白Logo页面,隐私弹窗确认页面以及广告
* @author menghuiyuan@myhexin.com
*/
@Entry
@Component
struct LauncherPage {
@State userPrivacyDialogVisible : boolean = false;
context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
async setSystemBar() {
// 获取当前应用窗口
let windowClass: window.Window = await window.getLastWindow(this.context);
// 前提:设置窗口为全屏窗口
// PC
if( (deviceInfo.deviceType !== '2in1') ) {
windowClass.setWindowLayoutFullScreen(true);
} else {
windowClass.setWindowLayoutFullScreen(false);
}
await windowClass.setWindowSystemBarProperties({
// 颜色属性为ARGB,将蒙尘设置为0%使其透明
navigationBarColor: "#00000000",
statusBarColor: "#00000000",
navigationBarContentColor: "#FFFFFFFF",
statusBarContentColor: "#FFFFFFFF"
})
}
aboutToAppear(): void {
this.setSystemBar()
this.userPrivacyDialogVisible = !PreferenceService.getBooleanValueSync('Privacy', 'isUserAgree', false);
}
onCancel() {
hilog.info(0xfff1, "LauncherPage.AgreePolicy onCancel", "clicked","");
//如果用户拒绝了授权,关闭应用
(getContext(this) as common.UIAbilityContext).terminateSelf(() => {});
}
onAccept() {
hilog.info(0xfff1, "LauncherPage.AgreePolicyOnAccept", "clicked","");
PreferenceService.putVal('Privacy', 'isUserAgree', true, true);
router.replaceUrl( { url : 'pages/Index'}, router.RouterMode.Single, (err) => {
if (err) {
console.error("Failed to start Index Page: " + err)
return;
}
AdvertisingMonitorService.getInstance().init(getContext(this) as common.UIAbilityContext)
AppConfigManager.getInstance().fetchIMEI().then((isSuccess : boolean) => {
if (isSuccess) {
AuthProviderService.getInstance().sendAuthRequest(ConnectionSeq.HQ);
}
});
})
}
build() {
RelativeContainer() {
if (this.userPrivacyDialogVisible) {
PrivacyUserDialog({
cancel: this.onCancel,
confirm: this.onAccept,
title: $r("app.string.privacy_policy_title"),
content: $r("app.string.privacy_policy_description"),
leftBtnText: $r("app.string.text_disagreed"),
rightBtnText: $r("app.string.text_agreed")
}).zIndex(4)
.align(Alignment.Center)
.alignRules(
{
'top': { 'anchor': '__container__', 'align': VerticalAlign.Top },
'bottom': { 'anchor': '__container__', 'align': VerticalAlign.Bottom },
'left': { 'anchor': '__container__', 'align': HorizontalAlign.Start },
'right': { 'anchor': '__container__', 'align': HorizontalAlign.End }
})
.id('dialog_privacy_policy')
.backgroundColor($r('app.color.dialog_bg_99666666'))
}
Image($r('app.media.launch_logo'))
.objectFit(ImageFit.Auto)
.width('80%').height(205).alignRules({
'bottom': {
'anchor': "__container__",
'align': VerticalAlign.Bottom
},
'middle': { 'anchor': '__container__', 'align': HorizontalAlign.Center },
}).id("image")
}.width('100%').height('100%')
}
}
@@ -0,0 +1,20 @@
import { DebugService, FloatingMinimizedIcon } from '@b2c-spi/hx_cbas_api';
import { common } from '@kit.AbilityKit';
/**
* 最小化图标页面
*/
@Entry
@Component
struct MinimizedIconPage {
build() {
Column() {
FloatingMinimizedIcon({
onExpand: (): void => { DebugService.getInstance().showWindow(getContext(this) as common.UIAbilityContext); }
})
}
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
}
@@ -0,0 +1,146 @@
import { QuotePage, AIAnalysisWebDialogController } from 'biz_quote';
import common from '@ohos.app.ability.common';
import { StatusBar } from '../widget/StatusBar';
import { AbilityConstant } from '@kit.AbilityKit';
import emitter from '@ohos.events.emitter';
import { AdsPageConfig, emitAdsPage, EmitterConstants, ScreenUtil } from 'biz_common';
import { HXLog, HxTags } from 'biz_common';
import { PeriodValue } from 'biz_futures_chart';
import { CurveRuntimeData } from 'biz_quote/src/main/ets/util/CurveRuntimeData';
import { SpotGoodsVarietyManager } from 'biz_chart_data_processor';
import { router, window } from '@kit.ArkUI';
import { TradeLineAskToChangeState } from 'biz_quote/src/main/ets/draw/overlay/hx_line/tradeline_manager/TradeLineAskToChangeState';
/**
* 分时页承载Page
* @author menghuiyuan@myhexin.com
*/
@Entry
@Component
struct QuoteDetailPage {
private readonly TAG = "QuoteDetailPage"
context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext
/**
* 这个pageShow 用于闪电下单,别删除
*/
@Provide pageShow: boolean = false;
@Provide quoteDetailPageShow: boolean = false;
@Provide showFlashTrade: boolean = false;
@Provide showDrawLineStopLoss: boolean = false;
@Provide showDrawOrder: boolean = false;
@Provide bottomToolsPopupVisible: boolean = false;
@StorageLink('isFullScreen') @Watch('onScreenChange') isLandscape: boolean = false; // 是否横屏
@Provide isReLandscape: boolean = false; // 是否反向横屏
// 横屏合约选择列表是否展开
@Provide landStockListExpand: boolean = false;
@Provide currentPeriod: PeriodValue = CurveRuntimeData.get().timeLinePeriod;
private resetPageStatusWhenLogin = () => {
this.resetPageStatus();
}
aboutToAppear(): void {
emitter.on({
eventId: EmitterConstants.USER_LOGIN
}, this.resetPageStatusWhenLogin);
// 进入分时/K 线页面时预加载现货类别列表(成功后内存级缓存,失败下次重试)
SpotGoodsVarietyManager.getInstance().requestSpotCategoryIfNeeded()
}
aboutToDisappear(): void {
HXLog.d("QuoteDetailPage",HxTags.quoteTags + "QuoteDetailPage aboutToDisappear , then unsubscribe (EmitterConstants.USER_LOGIN) ")
emitter.off(EmitterConstants.USER_LOGIN,this.resetPageStatusWhenLogin)
AIAnalysisWebDialogController.close()
}
// 拦截返回操作
onBackPress(): boolean | void {
if (ScreenUtil.get().isSmallLand()) {
// 类直板机横屏
ScreenUtil.get().setPortrait()
}
if (ScreenUtil.get().isFullScreen) {
ScreenUtil.get().isFullScreen = false
ScreenUtil.get().clickFull = false
return true
}
if (this.showDrawLineStopLoss) {
TradeLineAskToChangeState.getInstance().askToDiscard(() => {
router.back()
})
return true
}
if (this.showDrawOrder) {
TradeLineAskToChangeState.getInstance().askToDiscard(() => {
router.back()
})
return true
}
}
onPageShow(): void {
//分时页前台时启用应用接续
this.context.setMissionContinueState(AbilityConstant.ContinueState.ACTIVE, (result) => {
HXLog.i(this.TAG, "setMissionContinueState Active")
});
this.quoteDetailPageShow = true;
this.pageShow = true;
HXLog.d('QuoteDetailPage', HxTags.entryTags + 'QuoteDetailPage page show triggered')
emitAdsPage(AdsPageConfig.QUOTE_DETAIL_PAGE_ID)
}
onPageHide(): void {
this.bottomToolsPopupVisible = false;
this.quoteDetailPageShow = false;
this.pageShow = false;
//当分时页在后台时,关闭应用接续功能
this.context.setMissionContinueState(AbilityConstant.ContinueState.INACTIVE, (result) => {
HXLog.i(this.TAG, "setMissionContinueState InActive")
});
emitAdsPage(AdsPageConfig.INVALID)
if (AppStorage.get('isAbilityForeGround') == true) {
this.onSwitchToOtherPage()
}
}
private resetPageStatus() {
if (this.quoteDetailPageShow) {
this.quoteDetailPageShow = false
setTimeout(() => {
this.quoteDetailPageShow = true
})
}
}
private onSwitchToOtherPage() {
AIAnalysisWebDialogController.close()
}
private onScreenChange() {
if (this.isLandscape) {
// 横屏隐藏状态栏
ScreenUtil.get().setSystemBarEnabled(false);
} else {
ScreenUtil.get().setSystemBarEnabled(true);
}
AIAnalysisWebDialogController.close()
}
build() {
Flex({ direction: FlexDirection.Column }) {
StatusBar({
bkColor: $r('app.color.surface_layer1_foreground'),
maskVisible: this.bottomToolsPopupVisible,
maskColor: $r('app.color.wt_40_000000')
})
.visibility(this.isLandscape ? Visibility.None : Visibility.Visible)
QuotePage()
.flexShrink(1)
.height('100%')
}
.backgroundColor($r('app.color.surface_layer1_background'))
}
}
@@ -0,0 +1,79 @@
import { SearchView} from 'biz_search'
import router from '@ohos.router'
import common from '@ohos.app.ability.common';
import window from '@ohos.window';
import { GlobalContext } from 'biz_common/src/main/ets/utils/GlobalContext';
import { SearchScope, SearchScene } from 'biz_search/src/main/ets/model/SearchParam';
import { TradeSearchScope } from 'biz_search/src/main/ets/model/TradeSearchScope';
@Entry
@Component
struct SearchPage {
@State barHeight:number = 0;
@Provide searchPageShown:boolean = false;
@Provide isFromTrade: boolean = false
context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext
// 行情页搜索范围
searchScope: SearchScope = SearchScope.COMPREHENSIVE_SCOPE
searchScene: string = SearchScene.NORMAL
// 交易下单页搜索范围
@State tradeSearchScope: TradeSearchScope | null = null
async setSystemBar() {
// 获取当前应用窗口
let windowClass: window.Window = await window.getLastWindow(this.context)
// 获取状态栏高度
let barHeight = windowClass.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM).topRect.height
let storeBarHeight = GlobalContext.statusBarHeight
if (barHeight !== storeBarHeight && barHeight !== 0) {
this.barHeight = barHeight
} else {
this.barHeight = storeBarHeight
}
}
onBackPress(){
}
onPageShow(): void {
this.searchPageShown = true
}
onPageHide() {
this.searchPageShown = false
}
aboutToAppear() {
this.setSystemBar()
let routerParam = router.getParams() as Record<string, string | number | object>
if (routerParam) {
this.isFromTrade = routerParam['isFromTrade']?.toString() === '1'
if (this.isFromTrade) {
// 交易下单页 - 解析 TradeSearchScope
let scopeType = routerParam['scopeType'] as number
this.tradeSearchScope = TradeSearchScope.getSearchScope(scopeType) ?? TradeSearchScope.FUTURES_ALL
} else {
// 行情页 - 使用原有 SearchScope 字符串
this.searchScope = routerParam['searchScope'] as string || SearchScope.COMPREHENSIVE_SCOPE
this.searchScene = routerParam['searchScene']?.toString() ?? SearchScene.NORMAL
}
}
}
build() {
Column() {
Blank().height(px2vp(this.barHeight) +2).color($r('app.color.surface_layer1_foreground')).width('100%')
SearchView({
searchScope: this.searchScope,
searchScene: this.searchScene,
tradeSearchScope: this.tradeSearchScope,
isFromTrade: this.isFromTrade
})
}
.width('100%')
.height('100%')
}
}
@@ -0,0 +1,79 @@
import router from '@ohos.router'
import window from '@ohos.window';
import common from '@ohos.app.ability.common';
import { StatusBar } from '../widget/StatusBar';
import { SelfCodeSecondPage } from 'biz_selfcode';
@Entry
@Component
struct SecondPage {
@Provide newsPageShow:number = 0; //0 not show 1 shown
@State barHeight:number = 0;
context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext
@State titlePrefix:string = '';
@State codeListStr:string = '';
@State marketListStr:string = '';
@State stockListStr:string = ''
@State initIndex:number = 0;
async setSystemBar() {
// 获取当前应用窗口
let windowClass: window.Window = await window.getLastWindow(this.context)
// 获取状态栏高度
this.barHeight = windowClass.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM).topRect.height
// 将状态栏和导航栏的背景色设置为跟应用窗口相同的颜色
await windowClass.setWindowSystemBarProperties({
navigationBarColor: "#00000000",
statusBarColor: "#00000000",
navigationBarContentColor: "#FFFFFFFF",
statusBarContentColor: "#FFFFFFFF"
})
}
async resetSystemBar() {
// 获取当前应用窗口
let windowClass: window.Window = await window.getLastWindow(this.context)
// 将状态栏和导航栏的背景色设置为跟应用窗口相同的颜色
await windowClass.setWindowSystemBarProperties({
navigationBarColor: "#00000000",
statusBarColor: "#00000000",
navigationBarContentColor: "#FFFFFFFF",
statusBarContentColor: "#FFFFFFFF"
})
}
/**
* 从路由中获取参数并初始化页面数据
*/
aboutToAppear() {
let routerParam = router.getParams() as Record<string, string>;
this.codeListStr = routerParam.codeList;
this.marketListStr = routerParam.marketList;
this.stockListStr = routerParam.stockList
this.titlePrefix = routerParam.title;
}
aboutToDisappear(){
}
build() {
Column() {
StatusBar()
this.buildSecondPage(this.stockListStr,this.titlePrefix,this.initIndex)
//this.buildTestPage(this.codeListStr, this.marketListStr, this.titlePrefix)
}
.width('100%')
.height('100%')
}
@Builder
buildSecondPage(stockListStr:string, titlePrefix:string, initIndex:number ) {
SelfCodeSecondPage({selfCodeList:stockListStr,titleWord:titlePrefix,initIndex:initIndex})
}
@Builder
buildTestPage(code:string, market:string,title:string) {
Text('入参'+code+market+title)
.fontSize(100)
}
}
@@ -0,0 +1,137 @@
import {
buildHeaderRowData,
CellData,
HeaderItem,
OriginalData,
parseHeaderConfig,
RowData,
SortStatus,
TableData
} from '@b2b/hq_table'
import router from '@ohos.router'
import util from '@ohos.util'
import { HQTableComponent, StockListPageParam } from 'biz_market'
import { SubPageBar } from 'biz_selfcode'
import common from '@ohos.app.ability.common'
import { StatusBar } from '../widget/StatusBar'
import { emitter } from '@kit.BasicServicesKit'
import { EmitterConstants } from 'biz_common/Index'
@Entry
@Component
struct StockListPage {
private frameId: number = 0
private pageId: number = 0
private headerConfig: Array<HeaderItem> = []
//首次进入页面的默认排序字段
private defaultSortId?: number
//默认排序类型
private defaultSortStatus: SortStatus = SortStatus.DESC
//是否需要根据默认排序表头重新设置表头位置
private isNeedReRankHeader: boolean = false
//页面标题
private title: string = ""
@State hasInit: boolean = false
@Provide tableData: TableData = TableData.createEmptyTableData()
@State header: RowData = new RowData([], [], new OriginalData())
@State requestNotifier: number = 0;
@State barHeight: number = 0;
context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext
private relinkCb = () => {
this.requestNotifier++;
}
aboutToAppear() {
let param = router.getParams() as StockListPageParam
this.title = param.pageTitle
this.frameId = param.pageId
this.pageId = param.pageId
this.defaultSortId = param.defaultSortId
this.defaultSortStatus = param.defaultSortStatus
this.isNeedReRankHeader = param.isNeedReRankHeader
if (param.headerConfig) {
this.headerConfig = param.headerConfig
this.initHeaderRow()
} else if (param.headerConfigFile) {
getContext(this).resourceManager.getRawFileContent(param.headerConfigFile, (error, value) => {
if (error) {
console.log(error.message)
} else {
let textDecoder = util.TextDecoder.create('utf-8')
this.headerConfig.push(...parseHeaderConfig(textDecoder.decodeWithStream(value)))
this.initHeaderRow()
}
})
}
this.subscribeNetRelink()
}
subscribeNetRelink() {
emitter.on({
eventId:EmitterConstants.NETWORK_TCP_RECONNECT,
},this.relinkCb)
}
aboutToDisappear() {
emitter.off(EmitterConstants.NETWORK_TCP_RECONNECT,this.relinkCb)
}
private initHeaderRow() {
if (this.headerConfig.length > 0) {
let headerRow = buildHeaderRowData(this.headerConfig)
this.initDefaultSort(headerRow)
this.header = headerRow
this.hasInit = true
}
}
private initDefaultSort(header: RowData) {
if (typeof this.defaultSortId !== 'undefined') {
header.forEachCell((cellData: CellData) => {
if (cellData.dataId === this.defaultSortId) {
cellData.sortStatus = this.defaultSortStatus
header.currentSortCell = cellData
}
})
if (this.isNeedReRankHeader && header.currentSortCell) {
let index = header.scrollableCols.indexOf(header.currentSortCell)
if (index >= 0) {
header.scrollableCols.splice(index, 1)
header.scrollableCols.splice(1, 0, header.currentSortCell)
let configIndex = index + header.fixCols.length
let configItem = this.headerConfig.splice(configIndex, 1)[0]
this.headerConfig.splice(1 + header.fixCols.length, 0, configItem)
}
}
}
}
onPageShow() {
this.requestNotifier++
}
build() {
Column() {
StatusBar()
//Blank().height(px2vp(this.barHeight) +2).color($r('app.color.subpage_title_red')).width('100%')
SubPageBar({
title: this.title
})
Column() {
if (this.hasInit) {
HQTableComponent({
frameId: this.frameId,
pageId: this.pageId,
header: this.header,
headerConfig: this.headerConfig,
requestNotifier: this.requestNotifier
})
}
}.layoutWeight(1)
}
}
}
@@ -0,0 +1,162 @@
/**
* SystemIndicatorsManage 验证测试页面
*
* Author: LY
* Date: 2026-01-12
*/
import { SystemIndicatorsManage, CategoryInfo } from 'biz_indicator'
import { IndicatorInfoModel } from 'biz_indicator'
@Entry
@Component
struct SystemIndicatorsTestPage {
@State testResult: string = '点击按钮开始测试'
@State isLoading: boolean = false
private context: Context = getContext(this)
build() {
Column() {
Text('SystemIndicatorsManage 测试')
.fontSize(22)
.fontWeight(FontWeight.Bold)
.margin({ top: 50, bottom: 30 })
Button(this.isLoading ? '测试中...' : '开始测试')
.width('80%')
.height(50)
.fontSize(18)
.enabled(!this.isLoading)
.onClick(() => {
this.runTest()
})
Scroll() {
Text(this.testResult)
.fontSize(13)
.fontColor('#333333')
.padding(16)
}
.width('90%')
.layoutWeight(1)
.margin({ top: 20 })
.backgroundColor('#F5F5F5')
.borderRadius(10)
}
.width('100%')
.height('100%')
.padding({ bottom: 30 })
.backgroundColor('#FFFFFF')
}
async runTest() {
this.isLoading = true
this.testResult = '========== 开始测试 ==========\n'
try {
const manage = SystemIndicatorsManage.getInstance()
// [1] 从 biz_indicator HSP 模块的 rawfile 初始化数据
this.testResult += '\n[1] 从 biz_indicator HSP 模块加载数据...\n'
await manage.initDataFromModule(this.context)
if (manage.isInitialized()) {
this.testResult += ' ✅ 初始化成功\n'
this.testResult += ` 版本: ${manage.getDataVersion()}\n`
} else {
this.testResult += ' ❌ 初始化失败\n'
return
}
// [2] 验证指标数量
this.testResult += '\n[2] 验证指标数量...\n'
const count = manage.getIndicatorCount()
this.testResult += ` 指标总数: ${count}\n`
if (count === 55) {
this.testResult += ' ✅ 数量正确 (55个)\n'
} else {
this.testResult += ` ⚠️ 数量: ${count}\n`
}
// [3] 获取全部指标
this.testResult += '\n[3] 获取全部指标 getAllIndicators()...\n'
const allIndicators = manage.getAllIndicators()
this.testResult += ` 返回 ${allIndicators.length} 个指标\n`
if (allIndicators.length > 0) {
const first = allIndicators[0]
this.testResult += ` 第一个: ${first.indicatorId} - ${first.name}\n`
this.testResult += ' ✅ 成功\n'
}
// [4] 获取主图指标
this.testResult += '\n[4] 获取主图指标 getMainIndicators()...\n'
const mainIndicators = manage.getMainIndicators()
this.testResult += ` 主图指标数: ${mainIndicators.length}\n`
if (mainIndicators.length > 0) {
const names: string[] = []
for (let i = 0; i < Math.min(5, mainIndicators.length); i++) {
names.push(mainIndicators[i].indicatorId)
}
this.testResult += ` 前5个: ${names.join(', ')}\n`
this.testResult += ' ✅ 成功\n'
}
// [5] 获取副图指标
this.testResult += '\n[5] 获取副图指标 getViceIndicators()...\n'
const viceIndicators = manage.getViceIndicators()
this.testResult += ` 副图指标数: ${viceIndicators.length}\n`
if (viceIndicators.length > 0) {
const names: string[] = []
for (let i = 0; i < Math.min(5, viceIndicators.length); i++) {
names.push(viceIndicators[i].indicatorId)
}
this.testResult += ` 前5个: ${names.join(', ')}\n`
this.testResult += ' ✅ 成功\n'
}
// [6] 查询单个指标
this.testResult += '\n[6] 查询单个指标 getIndicatorInfo("MA")...\n'
const maIndicator = manage.getIndicatorInfo('MA')
if (maIndicator) {
this.testResult += ` 名称: ${maIndicator.name}\n`
this.testResult += ` 源码长度: ${maIndicator.sourceCode.length} 字符\n`
this.testResult += ` 参数数量: ${maIndicator.paramsArray.length}\n`
this.testResult += ` 线条数量: ${maIndicator.lineStyleArray.length}\n`
this.testResult += ' ✅ 成功\n'
} else {
this.testResult += ' ❌ 未找到 MA 指标\n'
}
// [7] 搜索功能
this.testResult += '\n[7] 搜索指标 searchIndicators("MACD")...\n'
const searchResults = manage.searchIndicators('MACD')
this.testResult += ` 搜索结果数: ${searchResults.length}\n`
if (searchResults.length > 0) {
for (let i = 0; i < searchResults.length; i++) {
const item = searchResults[i]
this.testResult += ` - ${item.indicatorId}: ${item.name}\n`
}
this.testResult += ' ✅ 成功\n'
}
// [8] 获取分类列表
this.testResult += '\n[8] 获取分类列表 getAllCategories()...\n'
const categories = manage.getAllCategories()
this.testResult += ` 分类数量: ${categories.length}\n`
for (let i = 0; i < categories.length; i++) {
const cat = categories[i]
this.testResult += ` - ${cat.code}: ${cat.name}\n`
}
this.testResult += ' ✅ 成功\n'
// 测试完成
this.testResult += '\n==============================\n'
this.testResult += '🎉 所有测试通过!\n'
this.testResult += '==============================\n'
} catch (error) {
this.testResult += `\n\n❌ 测试异常: ${error}\n`
} finally {
this.isLoading = false
}
}
}
@@ -0,0 +1,119 @@
import router from '@ohos.router'
import web_webview from '@ohos.web.webview';
import window from '@ohos.window';
import common from '@ohos.app.ability.common';
/**
* 暂时使用的页面
*/
@Entry
@Component
export default struct TemporaryBlockWebPage {
private url: string = '';
private title: string = ''
private webController: WebviewController = new web_webview.WebviewController;
@State barHeight:number = 0;
context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext
async setSystemBar() {
// 获取当前应用窗口
let windowClass: window.Window = await window.getLastWindow(this.context)
// 获取状态栏高度
this.barHeight = windowClass.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM).topRect.height
// 将状态栏和导航栏的背景色设置为跟应用窗口相同的颜色
await windowClass.setWindowSystemBarProperties({
navigationBarColor: "#00000000",
statusBarColor: "#00000000",
navigationBarContentColor: "#FFFFFFFF",
statusBarContentColor: "#FFFFFFFF"
})
}
async resetSystemBar() {
// 获取当前应用窗口
let windowClass: window.Window = await window.getLastWindow(this.context)
// 将状态栏和导航栏的背景色设置为跟应用窗口相同的颜色
await windowClass.setWindowSystemBarProperties({
navigationBarColor: "#00000000",
statusBarColor: "#00000000",
navigationBarContentColor: "#FFFFFFFF",
statusBarContentColor: "#FFFFFFFF"
})
}
aboutToAppear() {
this.setSystemBar()
const routerParam = router.getParams() as Record<string, string>;
this.url = routerParam.url;
this.title = routerParam.title || '';
}
aboutToDisappear(){
this.resetSystemBar()
try {
this.webController.stop()
} catch (e) {
// WebView may already be detached.
}
}
build() {
Column() {
Blank().height(px2vp(this.barHeight) +2).color($r('app.color.subpage_title_red')).width('100%')
Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.Start }) {
Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.Start }) {
Column() {
Image($r('app.media.toolbar_arrow_left'))
.width(19)
.height(24)
.fillColor('#FFFFFF')
.onClick(() => {
router.back();
});
}.margin({ right: 16 })
Text(this.title).fontSize(19).fontColor('#FFFFFF')
}.margin({ left: 16 })
}
.height(44).backgroundColor($r('app.color.navbar_bg_red')).width('100%')
Web({
src: this.url,
controller: this.webController
}).domStorageAccess(true)
.mixedMode(MixedMode.All)
.flexGrow(1)
.flexShrink(1)
.javaScriptProxy({
object: {},
name: 'Falcon',
methodList: [],
controller: this.webController,
})
.onPageEnd(() => {
// 暂时处理办法, 不支持用户关闭回顾按钮和历史分时,手动去掉
this.webController.runJavaScript(
`var elementToRemove = document.querySelector('.fixed.flex.items-center.bg-foreground');
if (elementToRemove) {
elementToRemove.parentNode.removeChild(elementToRemove);
}
var leftArrow = document.querySelector("#bottom-popup-window > div.ml-auto.mr-16 > div.date-picker-container.flex.absolute.top-59.right-26 > div.date-picker-arrow-container.mr-4.text-sec.text-16.z-99.pl-4");
if (leftArrow) {
leftArrow.style.color = "rgba(0,0,0,.2)";
}
var parentElement = document.querySelector("#bottom-popup-window > div.ml-auto.mr-16 > div.date-picker-container.flex.absolute.top-59.right-26")
parentElement.addEventListener('click', function(event) {
var targetElement = event.target.closest("#bottom-popup-window > div.ml-auto.mr-16 > div.date-picker-container.flex.absolute.top-59.right-26 > div.date-picker-arrow-container.mr-4.text-sec.text-16.z-99.pl-4");
var dateEl = event.target.closest("#bottom-popup-window > div.ml-auto.mr-16 > div.date-picker-container.flex.absolute.top-59.right-26 > div.date-picker-content.flex.items-center.justify-center.ml-auto.mb-5.z-99.intraDay.expanded");
if (targetElement || dateEl) {
event.stopPropagation();
targetElement.style.color = "rgba(0,0,0,.2)";
};
}, true);
`)
})
}
.height('100%')
.width('100%')
}
}
@@ -0,0 +1,345 @@
import { EmitterConstants, TradePageTabManager } from 'biz_common';
import {
MoniFutureAccountManager,
MoniPage, TradeConstant, TradeHomePage, TradePage, WeituoLoginPage } from 'biz_trade';
import { AccountProvider } from 'biz_trade/src/main/ets/features/account/models/AccountProvider';
import { emitter } from '@kit.BasicServicesKit';
import { TradeConstants } from 'biz_trade/src/main/ets/constants/TradeConstants';
import { TradeApiInit } from 'biz_trade/src/main/ets/trade/api/TradeApiInit';
import { TradeCustomTabManager } from 'biz_trade/src/main/ets/trade/manager/TradeCustomTabManager';
import { HXUserService } from 'biz_hxservice';
import { router } from '@kit.ArkUI';
import { AutoLoginManager } from 'biz_trade/src/main/ets/trade/manager/AutoLoginManager';
import { Account } from 'biz_trade/src/main/ets/constants/TypeConstants';
import { TradeApiManager } from 'biz_trade/src/main/ets/trade/api/TradeApiManager';
import { TradeOrderPageListType, fromValue } from 'biz_trade/src/main/ets/pages/homepage/TradeHomeConstants';
import { TradeSettingUtils } from 'biz_trade/src/main/ets/utils/TradeSettingUtils';
import { TradeParamManager } from 'biz_trade/src/main/ets/utils/TradeParamManager';
const TAB_INDEX_REAL = 0
const TAB_INDEX_MONI = 1
/**
* 交易tab的通用父页面,主要用来实现顶部tab切换(A股、港美股、基金、期货)
*/
@Preview
@Component
export default struct TradeBizPageContainer {
@ObjectLink tabManager: TradePageTabManager;
@State @Watch('onTabIndexChange') currentIndex: number = 0;
/**
* 是否有登录的实盘账号
*/
@State hasRealLoggedAccount: boolean = false
/**
* 是否有登录的模拟账号
*/
@State hasSimulateLoggedAccount: boolean = false
@State isVisible: boolean = false
/**
* 是否显示交易下单页面
*/
@State showTradeOrderPage: boolean = false
/**
* 交易下单tab index
*/
@State currentTabIndex: number = 0
/**
* 当前账号
*/
@State currentAccount: Account = undefined
private swiperController = new SwiperController()
/**
* 登录订阅回调
*/
private hasLoginAccountCallBack = () => {
this.hasRealLoggedAccount = AccountProvider.getInstance().hasRealLoggedAccount()
this.hasSimulateLoggedAccount = AccountProvider.getInstance().hasSimulateLoggedAccount()
this.currentAccount = AccountProvider.getInstance().getCurrentAccount()
}
/**
* 登出订阅回调
*/
private loginOutAccountCallBack = () => {
this.hasRealLoggedAccount = AccountProvider.getInstance().hasRealLoggedAccount()
this.hasSimulateLoggedAccount = AccountProvider.getInstance().hasSimulateLoggedAccount()
this.showTradeOrderPage = false
this.currentAccount = undefined
}
/**
* 交易回报点击回调
*/
private CJHBPushCallBack = (eventData: emitter.EventData) => {
if (this.showTradeOrderPage) {
// 交易下单页面已经有监听处理
return
}
// 处理在交易首页需要切换到交易下单再切列表
if (eventData && eventData.data) {
let tradeType: number | undefined = eventData.data[TradeConstant.KEY_TRADE_TYPE]
if (tradeType) {
TradeCustomTabManager.get().setTradeType(tradeType);
}
let tableIndex: number | undefined = eventData.data[TradeConstant.KEY_TABLE_INDEX]
this.gotoOrderPage(fromValue(tableIndex ?? 0))
}
}
onTabIndexChange() {
this.swiperController.changeIndex(this.currentIndex)
}
aboutToAppear(): void {
TradeApiInit.getInstance().initTradeCacheData();
MoniFutureAccountManager.getInstance().addMoniAccount()
this.tabManager.visibilityChangeListener = (isVisible) => {
this.isVisible = isVisible
if (isVisible) {
this.onPageShow()
}else {
this.onPageHide()
}
}
emitter.on({ eventId: TradeConstants.TRADE_ACCOUNT_LOGOUT }, this.loginOutAccountCallBack)
emitter.on(EmitterConstants.CJHB_PUSH_CLICK, this.CJHBPushCallBack)
}
aboutToDisappear(): void {
emitter.off(TradeConstants.TRADE_ACCOUNT_LOGOUT, this.loginOutAccountCallBack)
emitter.off(EmitterConstants.CJHB_PUSH_CLICK, this.CJHBPushCallBack)
}
onPageShow(): void {
const needEnterOrderPage = TradeParamManager.get().getNeedEnterOrderPage()
this.hasRealLoggedAccount = AccountProvider.getInstance().hasRealLoggedAccount()
this.hasSimulateLoggedAccount = AccountProvider.getInstance().hasSimulateLoggedAccount()
this.currentAccount = AccountProvider.getInstance().getCurrentAccount()
if (needEnterOrderPage) {
this.showTradeOrderPage = TradeParamManager.get().getNeedEnterOrderPage()
this.currentTabIndex = TradeOrderPageListType.POSITION
TradeParamManager.get().setNeedEnterOrderPage(false)
this.currentIndex = this.currentAccount?.isMoni() ? TAB_INDEX_MONI : TAB_INDEX_REAL
}
this.changeTabAccordingAccount()
emitter.on({eventId: TradeConstants.TRADE_ACCOUNT_LOGIN}, this.hasLoginAccountCallBack)
}
onPageHide(): void {
emitter.off(TradeConstants.TRADE_ACCOUNT_LOGIN, this.hasLoginAccountCallBack)
}
build() {
Flex({ direction: FlexDirection.Column }) {
if (this.currentAccount && ((this.hasRealLoggedAccount && this.currentIndex == TAB_INDEX_REAL) || (this.hasSimulateLoggedAccount && this.currentIndex == TAB_INDEX_MONI))) {
if (this.showTradeOrderPage) {
TradePage({
tradeTabShow: this.tabManager.tabVisibility,
isShowStatusBar: false,
currentTabIndex: this.currentTabIndex,
attachPage: "TradePageFromIndex",
clickBack: () => {
this.onOrderPageBack()
}
})
} else {
this.TitleBuilder()
TradeHomePage({
tradeTabShow: this.tabManager.tabVisibility,
tradeAccount: this.currentAccount,
jumpOrderPage: (listType) => {
this.gotoOrderPage(listType)
},
loginCallBack: {
onSwitchSuccess: () => {
this.loginSuccess(false)
},
onSwitchFailed: () => {}
}
})
}
} else {
this.TitleBuilder()
Divider().vertical(false).height(0.5).color($r('app.color.elements_others_divider_primary'))
Swiper() {
if (this.currentIndex == TAB_INDEX_MONI) {
MoniPage({
onForeground: this.isVisible,
loginSuccess: () => {
this.loginSuccess()
}
})
} else {
WeituoLoginPage({
tradeTabShow: this.tabManager.tabVisibility,
loginSucc: () => {
this.loginSuccess()
}
})
}
}
.index(this.currentIndex)
.indicator(false)
.disableSwipe(true)
}
}
.width('100%')
.height('100%')
}
@Builder
TitleBuilder() {
Row() {
Blank()
.width(40)
.height('100%')
Row() {
Row() {
Text("实盘")
.fontColor($r('app.color.elements_text_primary_02'))
.fontSize(this.currentIndex === TAB_INDEX_REAL ? 22 : 16)
.fontWeight(this.currentIndex === TAB_INDEX_REAL ? FontWeight.Medium : 400)
.textAlign(TextAlign.Center)
.margin({ top: this.currentIndex === TAB_INDEX_REAL ? 0 : 6, right: 12 })
.onClick(() => {
if (this.currentIndex == TAB_INDEX_REAL) {
return
}
this.currentIndex = TAB_INDEX_REAL;
this.selectTabReLogin()
})
Text("模拟")
.fontColor($r('app.color.elements_text_primary_02'))
.fontSize(this.currentIndex === TAB_INDEX_REAL ? 16 : 22)
.fontWeight(this.currentIndex === TAB_INDEX_REAL ? 400 : FontWeight.Medium)
.textAlign(TextAlign.Center)
.margin({ top: this.currentIndex === TAB_INDEX_REAL ? 6 : 0 })
.onClick(() => {
if (this.currentIndex == TAB_INDEX_MONI) {
return
}
AutoLoginManager.getInstance().cancelAutoLoginRequest('click moni btn')
this.currentIndex = TAB_INDEX_MONI;
this.selectTabReLogin()
})
}
.height(22)
}
.height('100%')
.justifyContent(FlexAlign.Center)
.layoutWeight(1)
Row() {
Image($r('app.media.icon_bot_24'))
.fillColor($r('app.color.elements_icon_primary_02'))
.width(24)
.height(24)
.objectFit(ImageFit.Contain)
.onClick(() => {
this.onAgentClick()
})
}
.margin({ right: 16 })
}
.backgroundColor($r('app.color.surface_layer1_foreground'))
.height(44)
.width('100%')
.justifyContent(FlexAlign.Center)
}
/**
* 切换tab重新登录
*/
private selectTabReLogin() {
let tradeAccount: Account = undefined
if (this.currentIndex == TAB_INDEX_REAL) {
tradeAccount = AccountProvider.getInstance().getLastRealLoginAccount()
} else {
tradeAccount = AccountProvider.getInstance().getLastSimulateLoginAccount()
}
TradeApiManager.getInstance().reLoginAccount('trade tab', tradeAccount, () => {
this.loginSuccess(false)
})
}
/**
* 登录成功
*/
private loginSuccess(canEnterOrderPage: boolean = true) {
this.hasRealLoggedAccount = AccountProvider.getInstance().hasRealLoggedAccount()
this.hasSimulateLoggedAccount = AccountProvider.getInstance().hasSimulateLoggedAccount()
this.currentAccount = AccountProvider.getInstance().getCurrentAccount()
// 登录后根据设置自动进入下单页(切换tab触发的登录除外)
if (this.currentAccount && canEnterOrderPage && TradeSettingUtils.getInstance().isDirectEnterOrderPage()) {
this.currentTabIndex = TradeOrderPageListType.POSITION
this.showTradeOrderPage = true
}
}
/**
* 从交易下单页返回
*/
private onOrderPageBack() {
this.showTradeOrderPage = false
this.changeTabAccordingAccount()
}
/**
* 根据账号切换tab
*/
private changeTabAccordingAccount() {
if (this.hasRealLoggedAccount && this.hasSimulateLoggedAccount) {
if (AccountProvider.getInstance().getCurrentAccount()?.isMoni()) {
this.currentIndex = TAB_INDEX_MONI
} else {
this.currentIndex = TAB_INDEX_REAL
}
}
}
/**
* 跳转到交易下单页
*/
private gotoOrderPage(listType: TradeOrderPageListType) {
this.currentTabIndex = listType
this.showTradeOrderPage = true
}
private onAgentClick() {
if (this.autoLogin()) {
return
}
router.pushUrl({
url: 'pages/WebViewComponentPage', params: {
url: 'https://eq.10jqka.com.cn/ai/webapp/?source=ths_liejin_qihuo&llm=1&showHeader=1&navLeftShow=0&comefrom=%1s&isSupportInputUpload=1&fullscreen=1',
title: '',
updateH5Title: '0'
}
},
router.RouterMode.Single, (err) => {
if (err) {
console.error(`go to message page failed , cause : ${err.message}`)
}
})
}
private autoLogin(): boolean {
if (!HXUserService.getInstance().isLoggedIn()) {
const topRoutePath = router.getState().path
router.pushUrl({
url: 'pages/login/LoginPage',
params: {
flexSwitchFlag: true,
sourcePage: `${topRoutePath}${router.getState().name}`
}
})
return true;
}
return false;
}
}
@@ -0,0 +1,23 @@
import router from '@ohos.router'
import { TrendForecastPageParams, TrendForecastView } from 'biz_forecast'
@Entry
@Component
struct TrendForecastPage {
@State params: TrendForecastPageParams = new TrendForecastPageParams()
aboutToAppear(): void {
const routerParams = router.getParams() as Record<string, string | number | boolean | undefined>
this.params = TrendForecastPageParams.fromRecord(routerParams)
}
build() {
Column() {
TrendForecastView({
params: this.params
})
}
.width('100%')
.height('100%')
}
}
@@ -0,0 +1,266 @@
import router from '@ohos.router'
import { WebViewComponent, CustomWebviewController, WebViewRightBarButton, NavigationBarViewModel, WebViewCallbackRegistry } from 'biz_webview';
import display from '@ohos.display';
import window from '@ohos.window';
import common from '@ohos.app.ability.common';
import { StatusBar } from '../widget/StatusBar';
import { WebViewUrlUtil } from 'biz_webview/src/main/ets/Util/WebViewUrlUtil';
import { KeyCode } from '@kit.InputKit';
import { handleBackEvent } from 'biz_common/src/main/ets/pc/PCAdapter';
import { VideoComponent } from 'lib_media';
import { GlobalContext, WebJumpLinkWhiteManager } from 'biz_common';
import { getResourceString } from '@b2c/first_page/src/main/ets/util/ResourceUtil';
// 返回回调注册 key
const BACK_PRESS_CALLBACK_KEY = 'backPressCallbackKey'
@Entry
@Component
export default struct WebViewComponentPage {
private webViewController: CustomWebviewController = new CustomWebviewController()
private url: string = ''
@Prop title: string | Resource = ''
@Prop fullscreen: boolean = false
@Prop showStatusBar: boolean = true
private refreshOnWidthChange: boolean = false;
@State onShow: boolean = false
@State barHeight: number = 0;
navigationBarHandler: NavigationBarViewModel = new NavigationBarViewModel();
context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext
@StorageLink('displayBreakPoint') @Watch('displayChanged') curBp: string = 'sm'
useCookie: boolean = false
@Provide videoPlayerUrl: string | null = ''
@State videoComponentHeight: number = 225
@State updateH5Title: boolean = true
// 是否显示三方资讯标题
@State isShowOtherTitle: boolean = false;
@State otherUrl: string | null = null;
//登录状态改变后刷新web
isLoginRefreshWeb:boolean = false
// 自定义返回事件回调
private onBackPressCallback?: () => void
private backPressCallbackKey?: string
registerJSEvent() {
display.getDefaultDisplaySync()
}
// 网页标题变更
onTitleReceive = (title: string) => {
this.title = title
}
aboutToAppear() {
let routerParam = router.getParams() as Record<string, string>;
this.url = routerParam.url;
routerParam = WebViewUrlUtil.assign(routerParam, this.url)
let defaultTitle = ''
try {
defaultTitle = this.context.resourceManager.getStringSync($r('app.string.webview_default_title').id);
this.refreshOnWidthChange = routerParam.refreshOnWidthChange === "true" ? true : false;
} catch (error) {
console.error(`getStringSync failed, error code: ${error.code}, message: ${error.message}.`)
}
this.title = routerParam.title || defaultTitle;
const webId = routerParam['webid']
this.fullscreen =
routerParam['fullscreen'] == '1' || routerParam['notitlebar'] == '1' || routerParam['ishiddenbar'] == '1' ||
webId == '2305' || webId == '4224' ||
routerParam['express'] == 'fullscreen_web'
if (this.fullscreen) {
this.showStatusBar = false
}
this.isLoginRefreshWeb = ""+routerParam['isLoginRefreshWeb'] === "true" ? true : false;
if (routerParam['showStatusBar'] == '1' && routerParam['StayHighForStatusBar'] != '0' || webId == '4224') {
this.showStatusBar = true
}
if (""+routerParam['useCookie'] === "true") {
this.useCookie = true
}
this.updateH5Title = routerParam['updateH5Title'] != '-1'
this.isShowOtherTitle = routerParam['isShowOtherTitle'] == 'true'
const isOtherUrl = WebJumpLinkWhiteManager.getInstance().isNotInWhiteUrls(this.url);
if (this.isShowOtherTitle && isOtherUrl) {
// 更改标题为三方风险提示
this.title = getResourceString(GlobalContext.get(), $r('app.string.bridge_risk_statement'))
this.otherUrl = this.url
}
// 获取返回事件回调
const callbackKey = routerParam[BACK_PRESS_CALLBACK_KEY]
if (callbackKey) {
this.backPressCallbackKey = callbackKey
this.onBackPressCallback = WebViewCallbackRegistry.getBackPressCallback(callbackKey)
}
this.registerJSEvent()
this.initThirdArticleWhiteList()
}
aboutToDisappear() {
if (this.backPressCallbackKey) {
WebViewCallbackRegistry.unregisterBackPressCallback(this.backPressCallbackKey)
this.backPressCallbackKey = undefined
}
// 停止 WebView 加载,释放 Chromium 渲染资源
try {
if (this.webViewController && this.webViewController.isAttached) {
this.webViewController.stop()
}
} catch (e) {
// WebView 可能已销毁
}
}
// 拦截系统返回上一级
onBackPress() {
// 通知业务方用户点击了返回
if (this.onBackPressCallback) {
this.onBackPressCallback()
}
if (this.webViewController && this.webViewController.isAttached && this.webViewController.accessBackward()) {
this.webViewController.backward()
return true
}
return !this.navigationBarHandler.canGoBack()
}
onPageShow() {
this.onShow = true
}
onPageHide() {
if (this.onShow == true) {
this.onShow = false
}
}
playerVideoVisibility(): Visibility {
const url = this.videoPlayerUrl
if (!url || url == null || url == '') {
return Visibility.None
}
return Visibility.Visible
}
displayChanged() {
this.videoPlayerUrl = null
}
/**
* 初始化第三方风险文章白名单列表
* 这里做一个兜底,防止客户端启动后,第一次请求白名单接口失败
* 导致功能不正常
*/
private initThirdArticleWhiteList() {
if (WebJumpLinkWhiteManager.getInstance().isWhiteListEmpty()) {
WebJumpLinkWhiteManager.getInstance().loadWhiteJumpLink();
}
}
build() {
Column() {
if (this.showStatusBar) {
StatusBar()
}
if (!this.fullscreen) {
RelativeContainer() {
Image($r("app.media.icon_arrow_left_24"))
.fillColor($r('app.color.elements_icon_tertiary'))
.width(24)
.height(24)
.alignRules({
center: { anchor: "__container__", align: VerticalAlign.Center },
left: { anchor: "__container__", align: HorizontalAlign.Start }
})
.margin({ left: 16 })
.hoverEffect(HoverEffect.Highlight)
.onClick(() => {
if (!this.webViewController.isAttached) {
return
}
if (this.onBackPressCallback) {
this.onBackPressCallback()
}
if (this.webViewController.accessBackward()) {
this.webViewController.backward()
} else if (this.navigationBarHandler.canGoBack()) {
router.back();
}
})
.id('left')
Text(this.title)
.maxFontSize(19)
.minFontSize(17)
.fontColor($r('app.color.elements_text_primary_02'))
.textAlign(TextAlign.Center)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.constraintSize({ maxWidth: '70%' })
.alignRules({
center: { anchor: "__container__", align: VerticalAlign.Center },
middle: { anchor: "__container__", align: HorizontalAlign.Center }
})
WebViewRightBarButton({ navigationBarHandler: this.navigationBarHandler })
.alignRules({
center: { anchor: "__container__", align: VerticalAlign.Center },
right: { anchor: "__container__", align: HorizontalAlign.End }
})
.margin({ right: 8 })
}
.height(44).backgroundColor($r("app.color.surface_layer1_foreground")).width('100%')
}
VideoComponent({ onShow: this.onShow })
.width('100%')
.height(this.videoComponentHeight)
.visibility(this.playerVideoVisibility())
.onAreaChange((oldValue, newValue) => {
const ratio = 9 / 16
let newWidth = newValue.width as number
if (newValue.width > 0 && this.videoComponentHeight !== newWidth * ratio) {
this.videoComponentHeight = newWidth * ratio
}
})
WebViewComponent({
urlString: this.url,
onShow: this.onShow,
webViewController: this.webViewController,
onTitleReceive: this.onTitleReceive,
navigationBarHandler: this.navigationBarHandler,
refreshOnWidthChange: this.refreshOnWidthChange,
useCookie: this.useCookie,
isLoginRefreshWeb:this.isLoginRefreshWeb,
overScrollMode: OverScrollMode.NEVER,
updateH5Title: this.updateH5Title,
isShowOtherTitle: this.isShowOtherTitle,
otherUrl: this.otherUrl,
receiveFullScreenEvent: (isFullScreen: boolean) => {
this.fullscreen = isFullScreen
if (isFullScreen) {
this.showStatusBar = false
}
},
onMediaUrlChanged: (url: string | null) => {
this.videoPlayerUrl = url
},
})
.margin({ top: 0 })
.layoutWeight(1)
}
.onKeyEvent((event: KeyEvent) => {
handleBackEvent(event)
})
.height('100%')
.width('100%')
.id('biz_webview/container')
}
}
@@ -0,0 +1,276 @@
import { promptAction, PromptAction, router } from '@kit.ArkUI';
import { AppInfoConfig, GlobalContext, HXLog } from 'biz_common';
import { WebTitleView } from 'biz_login/src/main/ets/view/WebTitleView';
import pasteboard from '@ohos.pasteboard';
import { AppUtil, ToastUtil } from '@pura/harmony-utils';
import { WTClientInfoUtil } from 'biz_trade/src/main/ets/utils/WTClientInfoUtil';
import { url } from '@kit.ArkTS';
import { resourceManager } from '@kit.LocalizationKit';
import { common, UIAbility } from '@kit.AbilityKit';
interface IAboutItem {
title: Resource;
showArrow: boolean;
showDivider: boolean;
routeName?: string;
isJumpWeb:boolean
jumpWebUrl?:Resource;
value?: string;
}
@Entry
@Component
struct AboutUs {
@State appVersion: string|Resource = "版本 "+AppInfoConfig.getVersionName();
@State deviceId: string = "C1864EBD-9F88-496D-A424-253F7163521D";
private aboutItems: IAboutItem[] = [
{
title: $r('app.string.about_us_product_intro'),
showArrow: true,
showDivider: true,
isJumpWeb:false,
routeName: "pages/aboutus/ProductIntroduction"
},
{
title: $r('app.string.about_us_contact'),
showArrow: true,
showDivider: true,
isJumpWeb:false,
routeName: "pages/aboutus/ContactUs"
},
{
title: $r('app.string.about_us_privacy_policy'),
showArrow: true,
showDivider: true,
isJumpWeb:true,
jumpWebUrl:$r('app.string.revoke_privacy_protocol_url')
},
{
title: $r('app.string.about_us_personal_info_download'),
showArrow: true,
showDivider: true,
isJumpWeb:true,
jumpWebUrl:$r('app.string.user_info_download_url')
},
{
title: $r('app.string.user_agreement_text'),
showArrow: true,
showDivider: true,
isJumpWeb:true,
jumpWebUrl:$r('app.string.about_user_agreement_url')
},
{
title: $r('app.string.about_us_recommendation_settings'),
showArrow: true,
showDivider: true,
isJumpWeb:true,
jumpWebUrl: $r('app.string.about_direct_promotion')
}
]
aboutToAppear(): void {
this.deviceId = WTClientInfoUtil.getDeviceId()
}
private copyDataToPasteBoard(dataText:string){
try {
let sysPasteBoard = pasteboard.getSystemPasteboard()
let data = pasteboard.createPlainTextData(dataText)
sysPasteBoard.setDataSync(data)
promptAction.showToast({message:$r('app.string.copy_success'),duration:3000,alignment:Alignment.Center})
}catch (e){
promptAction.showToast({message:$r('app.string.copy_fail'),duration:3000,alignment:Alignment.Center})
}
}
private onItemClick(item:IAboutItem){
if (item.isJumpWeb && item.jumpWebUrl) {
let jumpUrl = AppUtil.getContext().resourceManager.getStringSync(item.jumpWebUrl)
let webTitle = AppUtil.getContext().resourceManager.getStringSync(item.title)
if (jumpUrl && webTitle) {
router.pushUrl({
url: 'pages/WebViewComponentPage', params: {
url: jumpUrl,
title: webTitle,
updateH5Title: '0',
useCookie:true
}
})
}
}else {
if (item.routeName) {
router.pushUrl({ url: item.routeName });
}
}
}
private openInSystemBrowser(url:string){
try {
let context = getContext(this) as common.UIAbilityContext;
context.startAbility({action:'ohos.want.action.viewData',uri:url})
}catch (e) {
promptAction.showToast({message:$r('app.string.web_link_fail'),duration:3000,alignment:Alignment.Center})
}
}
build() {
Column() {
Row().width('100%').height(px2vp(GlobalContext.statusBarHeight)).backgroundColor($r('app.color.surface_layer1_foreground')).flexShrink(0)
WebTitleView({ title: '' })
.backgroundColor($r('app.color.surface_layer1_foreground'))
Column() {
Image($r('app.media.theme_icon_system_config_app'))
.width(100)
.height(120)
.margin({ top: 20 })
Text(this.appVersion)
.fontSize(14)
.fontColor($r('app.color.elements_text_primary_02'))
.margin({ top: 8, bottom: 40 })
}
.width('100%')
.alignItems(HorizontalAlign.Center)
Column() {
ForEach(this.aboutItems, (item: IAboutItem, index: number) => {
Row() {
Text(item.title)
.fontSize(16)
.fontColor($r('app.color.elements_text_primary_02'))
.layoutWeight(1)
if (item.value) {
Text(item.value)
.fontSize(14)
.fontColor($r('app.color.elements_text_primary_02'))
.margin({ right: 4 })
Image($r('app.media.icon_arrow_right_line'))
.fillColor($r('app.color.elements_icon_primary_02'))
.width(16)
.height(16)
}
if (item.showArrow) {
Image($r('app.media.icon_arrow_right_line'))
.fillColor($r('app.color.elements_icon_primary_02'))
.width(16)
.height(16)
}
}
.width('100%')
.height(56)
.padding({ left: 16, right: 16 })
.backgroundColor($r('app.color.surface_layer1_foreground'))
.onClick(() => {
this.onItemClick(item)
})
if (item.showDivider) {
Divider()
.strokeWidth(0.5)
.color($r('app.color.elements_others_divider_primary'))
.margin({ left: 16, right: 16 })
}
})
}
.width('100%')
.backgroundColor($r('app.color.surface_layer1_foreground'))
.margin({ left: 12, right: 12 })
Column() {
Row() {
Column(){
Text($r('app.string.about_us_device_id'))
.fontSize(16)
.fontColor($r('app.color.elements_text_primary_02'))
.align(Alignment.Start)
Text(this.deviceId)
.fontSize(14)
.fontColor($r('app.color.elements_text_primary_02'))
.align(Alignment.Start)
.margin({top:6})
}.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
.justifyContent(FlexAlign.Start)
Image($r('app.media.theme_icon_system_config_copy'))
.width(16)
.height(16)
}
.width('100%')
.height(56)
.padding({ left: 16, right: 16 })
.backgroundColor($r('app.color.surface_layer1_foreground'))
.onClick(() => {
this.copyDataToPasteBoard(this.deviceId)
})
Divider()
.strokeWidth(0.5)
.color($r('app.color.elements_others_divider_primary'))
.margin({ left: 16, right: 16 })
}
.width('100%')
.backgroundColor($r('app.color.surface_layer1_foreground'))
.margin({ left: 12, right: 12 })
Column() {
Text($r('app.string.about_us_disclaimer'))
.fontSize(14)
.fontColor($r('app.color.elements_text_primary_01'))
.margin({ top: 40 })
.onClick(() => {
let item = {
title: $r('app.string.about_us_disclaimer'),
showArrow: true,
showDivider: true,
isJumpWeb:true,
jumpWebUrl: $r('app.string.about_statement_url')
} as IAboutItem
this.onItemClick(item)
})
Column(){
Text($r('app.string.about_us_icp'))
.fontSize(12)
.fontColor($r('app.color.elements_text_secondary'))
.margin({ top: 16 })
Row() {
Image($r('app.media.icon_system_config_app_2'))
.width(16)
.height(16)
.margin({ right: 4 })
Text($r('app.string.about_us_platform_desc'))
.fontSize(12)
.fontColor($r('app.color.elements_text_secondary'))
}
.margin({ top: 8 })
}.width('100%')
.onClick(()=>{
// let item = {
// title: $r('app.string.about_beianhao_query_text'),
// showArrow: true,
// showDivider: true,
// isJumpWeb:true,
// jumpWebUrl: $r('app.string.about_beianhao_query_url')
// } as IAboutItem
// this.onItemClick(item)
this.openInSystemBrowser(AppUtil.getContext().resourceManager.getStringSync($r('app.string.about_beianhao_query_url')))
})
}
.width('100%')
.alignItems(HorizontalAlign.Center)
Blank()
}
.width('100%')
.height('100%')
.backgroundColor($r('app.color.surface_layer1_foreground'))
}
}

Some files were not shown because too many files have changed in this diff Show More