commit ad900489f5e36fca8abbe195d854a637050a2434 Author: clz Date: Tue Jul 21 19:36:30 2026 +0800 feat: 完成市场排名和AI看涨跌页面 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d2ff201 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +/node_modules +/oh_modules +/local.properties +/.idea +**/build +/.hvigor +.cxx +/.clangd +/.clang-format +/.clang-tidy +**/.test +/.appanalyzer \ No newline at end of file diff --git a/AppScope/app.json5 b/AppScope/app.json5 new file mode 100644 index 0000000..f265d74 --- /dev/null +++ b/AppScope/app.json5 @@ -0,0 +1,11 @@ +{ + "app": { + "bundleName": "com.example.carddemo", + "vendor": "example", + "versionCode": 1000000, + "versionName": "1.0.0", + "buildVersion": "1", + "icon": "$media:layered_image", + "label": "$string:app_name" + } +} diff --git a/AppScope/resources/base/element/string.json b/AppScope/resources/base/element/string.json new file mode 100644 index 0000000..22af7fc --- /dev/null +++ b/AppScope/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "CardDemo" + } + ] +} diff --git a/AppScope/resources/base/media/background.png b/AppScope/resources/base/media/background.png new file mode 100644 index 0000000..923f2b3 Binary files /dev/null and b/AppScope/resources/base/media/background.png differ diff --git a/AppScope/resources/base/media/foreground.png b/AppScope/resources/base/media/foreground.png new file mode 100644 index 0000000..eb94275 Binary files /dev/null and b/AppScope/resources/base/media/foreground.png differ diff --git a/AppScope/resources/base/media/layered_image.json b/AppScope/resources/base/media/layered_image.json new file mode 100644 index 0000000..fb49920 --- /dev/null +++ b/AppScope/resources/base/media/layered_image.json @@ -0,0 +1,7 @@ +{ + "layered-image": + { + "background" : "$media:background", + "foreground" : "$media:foreground" + } +} \ No newline at end of file diff --git a/build-profile.json5 b/build-profile.json5 new file mode 100644 index 0000000..870c3be --- /dev/null +++ b/build-profile.json5 @@ -0,0 +1,56 @@ +{ + "app": { + "signingConfigs": [ + { + "name": "default", + "type": "HarmonyOS", + "material": { + "certpath": "/Users/clz/.ohos/config/default_CardHarmoyos_W--YuVliKoR8ViwvKioCmbZtQyNk2FWomi6j7mDjNFc=.cer", + "keyAlias": "debugKey", + "keyPassword": "0000001AA8F4264C8FA73F1D6DE953ECCA6B42CFC32D16F90083DE5B4A205CB42D5E69AB8F95C26776A3", + "profile": "/Users/clz/.ohos/config/default_CardHarmoyos_W--YuVliKoR8ViwvKioCmbZtQyNk2FWomi6j7mDjNFc=.p7b", + "signAlg": "SHA256withECDSA", + "storeFile": "/Users/clz/.ohos/config/default_CardHarmoyos_W--YuVliKoR8ViwvKioCmbZtQyNk2FWomi6j7mDjNFc=.p12", + "storePassword": "0000001AD665FB9BE23A4E2F855EDAC2A32E9CCF776E50E51F7DC22DFBF3367C985469B635EE5EF6B428" + } + } + ], + "products": [ + { + "name": "default", + "signingConfig": "default", + "targetSdkVersion": "6.1.0(23)", + "compatibleSdkVersion": "6.0.0(20)", + "runtimeOS": "HarmonyOS", + "buildOption": { + "strictMode": { + "caseSensitiveCheck": true, + "useNormalizedOHMUrl": true + } + } + } + ], + "buildModeSet": [ + { + "name": "debug", + }, + { + "name": "release" + } + ] + }, + "modules": [ + { + "name": "entry", + "srcPath": "./entry", + "targets": [ + { + "name": "default", + "applyToProducts": [ + "default" + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/code-linter.json5 b/code-linter.json5 new file mode 100644 index 0000000..073990f --- /dev/null +++ b/code-linter.json5 @@ -0,0 +1,32 @@ +{ + "files": [ + "**/*.ets" + ], + "ignore": [ + "**/src/ohosTest/**/*", + "**/src/test/**/*", + "**/src/mock/**/*", + "**/node_modules/**/*", + "**/oh_modules/**/*", + "**/build/**/*", + "**/.preview/**/*" + ], + "ruleSet": [ + "plugin:@performance/recommended", + "plugin:@typescript-eslint/recommended" + ], + "rules": { + "@security/no-unsafe-aes": "error", + "@security/no-unsafe-hash": "error", + "@security/no-unsafe-mac": "warn", + "@security/no-unsafe-dh": "error", + "@security/no-unsafe-dsa": "error", + "@security/no-unsafe-ecdsa": "error", + "@security/no-unsafe-rsa-encrypt": "error", + "@security/no-unsafe-rsa-sign": "error", + "@security/no-unsafe-rsa-key": "error", + "@security/no-unsafe-dsa-key": "error", + "@security/no-unsafe-dh-key": "error", + "@security/no-unsafe-3des": "error" + } +} \ No newline at end of file diff --git a/entry/.gitignore b/entry/.gitignore new file mode 100644 index 0000000..e2713a2 --- /dev/null +++ b/entry/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/entry/build-profile.json5 b/entry/build-profile.json5 new file mode 100644 index 0000000..6bd6457 --- /dev/null +++ b/entry/build-profile.json5 @@ -0,0 +1,33 @@ +{ + "apiType": "stageMode", + "buildOption": { + "resOptions": { + "copyCodeResource": { + "enable": false + } + } + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": false, + "files": [ + "./obfuscation-rules.txt" + ] + } + } + } + }, + ], + "targets": [ + { + "name": "default" + }, + { + "name": "ohosTest", + } + ] +} \ No newline at end of file diff --git a/entry/hvigorfile.ts b/entry/hvigorfile.ts new file mode 100644 index 0000000..b0e3a1a --- /dev/null +++ b/entry/hvigorfile.ts @@ -0,0 +1,6 @@ +import { hapTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins: [] /* Custom plugin to extend the functionality of Hvigor. */ +} \ No newline at end of file diff --git a/entry/obfuscation-rules.txt b/entry/obfuscation-rules.txt new file mode 100644 index 0000000..1e7e54e --- /dev/null +++ b/entry/obfuscation-rules.txt @@ -0,0 +1,23 @@ +# Define project specific obfuscation rules here. +# You can include the obfuscation configuration files in the current module's build-profile.json5. +# +# For more details, see +# https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/source-obfuscation + +# Obfuscation options: +# -disable-obfuscation: disable all obfuscations +# -enable-property-obfuscation: obfuscate the property names +# -enable-toplevel-obfuscation: obfuscate the names in the global scope +# -compact: remove unnecessary blank spaces and all line feeds +# -remove-log: remove all console.* statements +# -print-namecache: print the name cache that contains the mapping from the old names to new names +# -apply-namecache: reuse the given cache file + +# Keep options: +# -keep-property-name: specifies property names that you want to keep +# -keep-global-name: specifies names that you want to keep in the global scope + +-enable-property-obfuscation +-enable-toplevel-obfuscation +-enable-filename-obfuscation +-enable-export-obfuscation \ No newline at end of file diff --git a/entry/oh-package.json5 b/entry/oh-package.json5 new file mode 100644 index 0000000..248c3b7 --- /dev/null +++ b/entry/oh-package.json5 @@ -0,0 +1,10 @@ +{ + "name": "entry", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "", + "author": "", + "license": "", + "dependencies": {} +} + diff --git a/entry/src/main/ets/ai-view/AIView.ets b/entry/src/main/ets/ai-view/AIView.ets new file mode 100644 index 0000000..2698c31 --- /dev/null +++ b/entry/src/main/ets/ai-view/AIView.ets @@ -0,0 +1,161 @@ +import { ContractItem, TabData } from './AIViewTypes'; +import { AIViewModel } from './AIViewModel'; + +@Component +export struct AIView { + @State vm: AIViewModel = new AIViewModel(); + @State tabIdx: number = 0; + + // 涨为红、跌为绿、零/无效为默认色,对齐 data-display.vue 的 getValueColor + private accuracyColor(value: string): ResourceStr { + const num = parseFloat(value); + if (num > 0) { + return $r('app.color.color_rise'); + } + if (num < 0) { + return $r('app.color.color_fall'); + } + return $r('app.color.text_primary'); + } + + build() { + Column() { + // 标题栏 + Row() { + Text('AI看涨跌') + .fontSize(18) + .fontWeight(FontWeight.Bold) + .fontColor($r('app.color.text_primary')) + Blank() + Text('›') + .fontSize(18) + .fontColor($r('app.color.text_tertiary')) + } + .width('100%') + .padding({ bottom: 20 }) + + // 统计数据区 + Column() { + Row() { + Column({ space: 4 }) { + Text('近20日正确率') + .fontSize(13) + .fontColor($r('app.color.text_tertiary')) + Text(this.vm.last20Accuracy) + .fontSize(16) + .fontWeight(FontWeight.Medium) + .fontColor(this.accuracyColor(this.vm.last20Accuracy)) + } + .layoutWeight(1) + .alignItems(HorizontalAlign.Center) + + Divider() + .vertical(true) + .height(32) + .color($r('app.color.divider_color')) + + Column({ space: 4 }) { + Row({ space: 4 }) { + Text('昨日正确率') + .fontSize(13) + .fontColor($r('app.color.text_tertiary')) + Text('›') + .fontSize(13) + .fontColor($r('app.color.text_tertiary')) + } + Text(this.vm.lastDayAccuracy) + .fontSize(16) + .fontWeight(FontWeight.Medium) + .fontColor(this.accuracyColor(this.vm.lastDayAccuracy)) + } + .layoutWeight(1) + .alignItems(HorizontalAlign.Center) + } + .width('100%') + .padding({ top: 16, bottom: 16 }) + + Text('风险提示:AI推理方向, 不构成投资建议, 交易者需自主决策并承担风险') + .fontSize(10) + .fontColor($r('app.color.text_grey')) + .textAlign(TextAlign.Center) + .width('100%') + .margin({ top: 4 }) + } + .width('100%') + .borderRadius(8) + .backgroundColor($r('app.color.stats_bg')) + .padding({ bottom: 8 }) + + // Tab 栏(胶囊按钮)+ 右侧更新时间 + Row() { + Row({ space: 8 }) { + ForEach(this.vm.tabs, (tab: TabData, index: number) => { + Text(tab.title) + .fontSize(14) + .fontWeight(this.tabIdx === index ? FontWeight.Bold : FontWeight.Normal) + .fontColor(this.tabIdx === index ? $r('app.color.text_blue') : $r('app.color.text_secondary')) + .constraintSize({ minWidth: 63 }) + .textAlign(TextAlign.Center) + .padding({ top: 6, bottom: 6, left: 12, right: 12 }) + .borderRadius(4) + .backgroundColor(this.tabIdx === index ? $r('app.color.pill_active_bg') : $r('app.color.pill_inactive_bg')) + .onClick(() => { + this.tabIdx = index; + }) + }, (tab: TabData) => tab.title) + } + + Blank() + + Text('交易日 17:00 更新') + .fontSize(12) + .fontColor($r('app.color.text_tertiary')) + } + .width('100%') + .margin({ top: 20, bottom: 8 }) + + // 表格表头 + Row() { + Text('主力合约').fontSize(12).fontColor($r('app.color.text_tertiary')).layoutWeight(1) + Text('方向').fontSize(12).fontColor($r('app.color.text_tertiary')).layoutWeight(1).textAlign(TextAlign.Center) + Text('实际涨幅').fontSize(12).fontColor($r('app.color.text_tertiary')).layoutWeight(1).textAlign(TextAlign.Center) + Text('品种正确率').fontSize(12).fontColor($r('app.color.text_tertiary')).layoutWeight(1).textAlign(TextAlign.End) + } + .width('100%') + .padding({ bottom: 8 }) + + // 表格列表 + ForEach(this.vm.tabs[this.tabIdx].items, (item: ContractItem) => { + Row() { + Text(item.name).fontSize(16).fontColor($r('app.color.text_primary')).layoutWeight(1) + Text(item.direction) + .fontSize(16) + .fontColor(item.isRise ? $r('app.color.color_rise') : $r('app.color.color_fall')) + .layoutWeight(1) + .textAlign(TextAlign.Center) + Text(item.actualIncrease) + .fontSize(16) + .fontColor(item.isRise ? $r('app.color.color_rise') : $r('app.color.color_fall')) + .layoutWeight(1) + .textAlign(TextAlign.Center) + Text(item.accuracy) + .fontSize(16) + .fontColor($r('app.color.text_primary')) + .layoutWeight(1) + .textAlign(TextAlign.End) + } + .width('100%') + .padding({ top: 10, bottom: 10 }) + }, (item: ContractItem) => item.name) + + // 查看更多 + Text('查看更多') + .fontSize(14) + .fontColor($r('app.color.text_tertiary')) + .textAlign(TextAlign.Center) + .width('100%') + .margin({ top: 12 }) + } + .width('100%') + } +} diff --git a/entry/src/main/ets/ai-view/AIViewMock.ets b/entry/src/main/ets/ai-view/AIViewMock.ets new file mode 100644 index 0000000..64e5135 --- /dev/null +++ b/entry/src/main/ets/ai-view/AIViewMock.ets @@ -0,0 +1,24 @@ +import { StatsData, TabData } from './AIViewTypes'; + +export const MOCK_STATS: StatsData = { + last20Accuracy: '68%', + lastDayAccuracy: '72%', +}; + +export const MOCK_TABS: TabData[] = [ + { + title: '看涨 (3个)', + items: [ + { name: '沪铜2501', direction: '看涨', actualIncrease: '+1.23%', accuracy: '72%', isRise: true }, + { name: '螺纹钢2505', direction: '看涨', actualIncrease: '+0.85%', accuracy: '65%', isRise: true }, + { name: '豆粕2505', direction: '看涨', actualIncrease: '+2.14%', accuracy: '58%', isRise: true }, + ], + }, + { + title: '看跌 (2个)', + items: [ + { name: '铁矿石2505', direction: '看跌', actualIncrease: '-1.02%', accuracy: '61%', isRise: false }, + { name: '棕榈油2505', direction: '看跌', actualIncrease: '-0.46%', accuracy: '54%', isRise: false }, + ], + }, +]; diff --git a/entry/src/main/ets/ai-view/AIViewModel.ets b/entry/src/main/ets/ai-view/AIViewModel.ets new file mode 100644 index 0000000..a8d6f78 --- /dev/null +++ b/entry/src/main/ets/ai-view/AIViewModel.ets @@ -0,0 +1,21 @@ +import { TabData } from './AIViewTypes'; +import { MOCK_STATS, MOCK_TABS } from './AIViewMock'; + +@Observed +export class AIViewModel { + last20Accuracy: string = MOCK_STATS.last20Accuracy; + lastDayAccuracy: string = MOCK_STATS.lastDayAccuracy; + tabs: TabData[] = MOCK_TABS; + + // TODO: 接入后端接口后,在此方法内发起请求并更新 last20Accuracy + async updateLast20Accuracy(): Promise { + } + + // TODO: 接入后端接口后,在此方法内发起请求并更新 lastDayAccuracy + async updateLastDayAccuracy(): Promise { + } + + // TODO: 接入后端接口后,在此方法内发起请求并更新 tabs + async updateTabs(): Promise { + } +} diff --git a/entry/src/main/ets/ai-view/AIViewTypes.ets b/entry/src/main/ets/ai-view/AIViewTypes.ets new file mode 100644 index 0000000..c10b803 --- /dev/null +++ b/entry/src/main/ets/ai-view/AIViewTypes.ets @@ -0,0 +1,41 @@ +// ============ 视图数据类型(合并接口 + 行情后,供 View 消费) ============ + +export interface ContractItem { + name: string; + direction: string; + actualIncrease: string; + accuracy: string; + isRise: boolean; +} + +export interface TabData { + title: string; + items: ContractItem[]; +} + +export interface StatsData { + last20Accuracy: string; + lastDayAccuracy: string; +} + +// ============ 后端接口原始响应类型(trend_forecast) ============ +// 对应 docs/cards/interfaces.md「一、HTTP 接口 - 2. AI 涨跌预测结果」 + +export interface TrendForecastContract { + contract: string; + market: string; + accuracy: string; + show_quote: boolean; +} + +export interface TrendForecastDirection { + count: number; + data: TrendForecastContract[]; +} + +export interface TrendForecastResponse { + last_twenty_trade_date_accuracy: string; + last_trade_date_accuracy: string; + rise: TrendForecastDirection; + fall: TrendForecastDirection; +} diff --git a/entry/src/main/ets/common/Card.ets b/entry/src/main/ets/common/Card.ets new file mode 100644 index 0000000..1324654 --- /dev/null +++ b/entry/src/main/ets/common/Card.ets @@ -0,0 +1,19 @@ +@Component +export struct Card { + @BuilderParam content: () => void = this.defaultContent; + + @Builder + defaultContent() { + } + + build() { + Column() { + this.content() + } + .width('100%') + .padding(20) + .borderRadius(12) + .backgroundColor($r('app.color.card_bg')) + .shadow({ radius: 12, color: '#1A000000', offsetX: 0, offsetY: 4 }) + } +} diff --git a/entry/src/main/ets/common/NumberFormat.ets b/entry/src/main/ets/common/NumberFormat.ets new file mode 100644 index 0000000..b03724f --- /dev/null +++ b/entry/src/main/ets/common/NumberFormat.ets @@ -0,0 +1,34 @@ +// 通用数值格式化/取色工具(格式化和取色都是纯函数,不掺业务判断)。 +// 目前由 market-ranking 使用,其它卡片如果有同样的涨跌色/百分比/大数格式化需求也可以直接复用。 + +// 两位小数 + 正负号 + % +export function formatPercent(value?: number): string { + if (value === undefined) { + return '--'; + } + const fixed = value.toFixed(2); + return value > 0 ? `+${fixed}%` : `${fixed}%`; +} + +// 超过万/亿/万亿换算单位,不额外加后缀 +export function formatGreatNumber(value?: number): string { + if (value === undefined) { + return '--'; + } + const abs = Math.abs(value); + if (abs >= 1e12) { + return `${(value / 1e12).toFixed(2)}万亿`; + } + if (abs >= 1e8) { + return `${(value / 1e8).toFixed(2)}亿`; + } + if (abs >= 1e4) { + return `${(value / 1e4).toFixed(2)}万`; + } + return `${value}`; +} + +// 涨红跌绿:按数值正负取色,未处理无效值时的灰色分支 +export function riseFallColor(value?: number): Resource { + return (value ?? 0) >= 0 ? $r('app.color.color_rise') : $r('app.color.color_fall'); +} diff --git a/entry/src/main/ets/entryability/EntryAbility.ets b/entry/src/main/ets/entryability/EntryAbility.ets new file mode 100644 index 0000000..091797f --- /dev/null +++ b/entry/src/main/ets/entryability/EntryAbility.ets @@ -0,0 +1,48 @@ +import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { window } from '@kit.ArkUI'; + +const DOMAIN = 0x0000; + +export default class EntryAbility extends UIAbility { + onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { + try { + this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET); + } catch (err) { + hilog.error(DOMAIN, 'testTag', 'Failed to set colorMode. Cause: %{public}s', JSON.stringify(err)); + } + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate'); + } + + onDestroy(): void { + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onDestroy'); + } + + onWindowStageCreate(windowStage: window.WindowStage): void { + // Main window is created, set main page for this ability + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate'); + + windowStage.loadContent('pages/Index', (err) => { + if (err.code) { + hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err)); + return; + } + hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.'); + }); + } + + onWindowStageDestroy(): void { + // Main window is destroyed, release UI related resources + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageDestroy'); + } + + onForeground(): void { + // Ability has brought to foreground + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onForeground'); + } + + onBackground(): void { + // Ability has back to background + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onBackground'); + } +} \ No newline at end of file diff --git a/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets b/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets new file mode 100644 index 0000000..8e4de99 --- /dev/null +++ b/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets @@ -0,0 +1,16 @@ +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { BackupExtensionAbility, BundleVersion } from '@kit.CoreFileKit'; + +const DOMAIN = 0x0000; + +export default class EntryBackupAbility extends BackupExtensionAbility { + async onBackup() { + hilog.info(DOMAIN, 'testTag', 'onBackup ok'); + await Promise.resolve(); + } + + async onRestore(bundleVersion: BundleVersion) { + hilog.info(DOMAIN, 'testTag', 'onRestore ok %{public}s', JSON.stringify(bundleVersion)); + await Promise.resolve(); + } +} \ No newline at end of file diff --git a/entry/src/main/ets/market-ranking/MarketRanking.ets b/entry/src/main/ets/market-ranking/MarketRanking.ets new file mode 100644 index 0000000..920d9c6 --- /dev/null +++ b/entry/src/main/ets/market-ranking/MarketRanking.ets @@ -0,0 +1,187 @@ +import { PeriodRange, RankItem, TabMetric } from './MarketRankingTypes'; +import { MarketRankingModel } from './MarketRankingModel'; +import { formatGreatNumber, formatPercent, riseFallColor } from '../common/NumberFormat'; + +const NORMAL_SIZE = 3; +const MAX_SIZE = 5; + +@Component +export struct MarketRanking { + @State vm: MarketRankingModel = new MarketRankingModel(); + // Tab 选中/展开状态是纯 UI 交互状态,留在 View 里 + @State primaryIdx: number = 0; + @State secondaryIdx: number = 0; + @State unfolded: boolean = false; + + // 第三列标题随 Tab 变化 + private thirdColumnTitle(): string { + const metric = this.vm.tabMetrics[this.primaryIdx]; + if (metric.hasChildren) { + return `${this.vm.periodRanges[this.secondaryIdx].label}${metric.label}`; + } + if (metric.id === 'rise' || metric.id === 'fall') { + return '涨跌幅'; + } + return metric.label; + } + + // 成交额/日增仓列用中性色,其余按涨跌上色 + private isNeutralColumn(): boolean { + const id = this.vm.tabMetrics[this.primaryIdx].id; + return id === 'turnOver' || id === 'increPosit'; + } + + // 第三列展示值:按当前一级 Tab 从对应原始字段取值并格式化 + private thirdColumnValue(item: RankItem): string { + const id = this.vm.tabMetrics[this.primaryIdx].id; + if (id === 'turnOver') { + return formatGreatNumber(item.turnover); + } + if (id === 'increPosit') { + return formatGreatNumber(item.incre_posit); + } + if (id === 'riseSpeed' || id === 'fallSpeed') { + return formatPercent(item.chg_speed); + } + return formatPercent(item.price_chg); + } + + // 第三列取色:成交额/日增仓走中性色,其余按对应原始字段的正负取色 + private thirdColumnColor(item: RankItem): Resource { + if (this.isNeutralColumn()) { + return $r('app.color.text_primary'); + } + const id = this.vm.tabMetrics[this.primaryIdx].id; + const raw = id === 'riseSpeed' || id === 'fallSpeed' ? item.chg_speed : item.price_chg; + return riseFallColor(raw); + } + + build() { + Column() { + // 标题栏:标题 + 展开/收起胶囊紧挨着,都在左侧 + Row() { + Text('市场排名') + .fontSize(18) + .fontWeight(FontWeight.Bold) + .fontColor($r('app.color.text_primary')) + Text(this.unfolded ? '收起' : '展开') + .fontSize(12) + .fontColor($r('app.color.text_tertiary')) + .height(20) + .padding({ left: 8, right: 8 }) + .borderRadius(11) + .backgroundColor($r('app.color.pill_inactive_bg')) + .textAlign(TextAlign.Center) + .margin({ left: 16 }) + .onClick(() => { + this.unfolded = !this.unfolded; + }) + } + .width('100%') + .padding({ bottom: 16 }) + + // 一级 Tab(胶囊按钮,可横向滚动) + Scroll() { + Row({ space: 8 }) { + ForEach(this.vm.tabMetrics, (metric: TabMetric, index: number) => { + Text(metric.label) + .fontSize(14) + .fontWeight(this.primaryIdx === index ? FontWeight.Bold : FontWeight.Normal) + .fontColor(this.primaryIdx === index ? $r('app.color.text_blue') : $r('app.color.text_secondary')) + .constraintSize({ minWidth: 63 }) + .textAlign(TextAlign.Center) + .padding({ top: 6, bottom: 6, left: 12, right: 12 }) + .borderRadius(4) + .backgroundColor(this.primaryIdx === index + ? $r('app.color.pill_active_bg') + : $r('app.color.pill_inactive_bg')) + .onClick(() => { + this.primaryIdx = index; + this.secondaryIdx = 0; + }) + }, (metric: TabMetric) => metric.id) + } + } + .width('100%') + .scrollable(ScrollDirection.Horizontal) + .scrollBar(BarState.Off) + .align(Alignment.Start) + + // 二级 Tab(仅涨速/跌速出现,右对齐文字 + 分隔线) + if (this.vm.tabMetrics[this.primaryIdx].hasChildren) { + Row() { + ForEach(this.vm.periodRanges, (period: PeriodRange, index: number) => { + if (index !== 0) { + Divider() + .vertical(true) + .height(10) + .color($r('app.color.divider_color')) + .margin({ left: 8, right: 8 }) + } + Text(period.label) + .fontSize(12) + .fontWeight(this.secondaryIdx === index ? FontWeight.Bold : FontWeight.Normal) + .fontColor(this.secondaryIdx === index + ? $r('app.color.text_primary') + : $r('app.color.text_tertiary')) + .padding({ top: 4, bottom: 4 }) + .onClick(() => { + this.secondaryIdx = index; + }) + }, (period: PeriodRange) => period.id) + } + .width('100%') + .justifyContent(FlexAlign.End) + .margin({ top: 8 }) + } + + // 表头 + Row() { + Text('合约名称').fontSize(12).fontColor($r('app.color.text_tertiary')).width('35%') + Text('最新价').fontSize(12).fontColor($r('app.color.text_tertiary')).layoutWeight(1).textAlign(TextAlign.End) + Text(this.thirdColumnTitle()) + .fontSize(12) + .fontColor($r('app.color.text_tertiary')) + .layoutWeight(1) + .textAlign(TextAlign.End) + } + .width('100%') + .margin({ top: 10 }) + .padding({ bottom: 2 }) + .border({ width: { bottom: 0.5 }, color: $r('app.color.divider_color') }) + + // 表格列表 + ForEach( + this.vm.currentList(this.primaryIdx, this.secondaryIdx).slice(0, this.unfolded ? MAX_SIZE : NORMAL_SIZE), + (item: RankItem, index: number) => { + Row() { + Text(item.name).fontSize(16).fontColor($r('app.color.text_primary')).width('35%') + Text(`${item.price}`) + .fontSize(16) + .fontColor(riseFallColor(item.price_chg)) + .layoutWeight(1) + .textAlign(TextAlign.End) + Text(this.thirdColumnValue(item)) + .fontSize(16) + .fontColor(this.thirdColumnColor(item)) + .layoutWeight(1) + .textAlign(TextAlign.End) + } + .width('100%') + .padding({ top: 12, bottom: 12 }) + .border(index === Math.min( + this.unfolded ? MAX_SIZE : NORMAL_SIZE, + this.vm.currentList(this.primaryIdx, this.secondaryIdx).length, + ) - 1 + ? undefined + : { width: { bottom: 0.5 }, color: $r('app.color.divider_color') }) + .onClick(() => { + this.vm.jumpToDetail(item); + }) + }, + (item: RankItem) => item.code, + ) + } + .width('100%') + } +} diff --git a/entry/src/main/ets/market-ranking/MarketRankingConstant.ets b/entry/src/main/ets/market-ranking/MarketRankingConstant.ets new file mode 100644 index 0000000..e9ce299 --- /dev/null +++ b/entry/src/main/ets/market-ranking/MarketRankingConstant.ets @@ -0,0 +1,19 @@ +import { PeriodRange, TabMetric } from './MarketRankingTypes'; + +// 一级 Tab 配置:涨幅/跌幅/涨速/跌速/成交额/日增仓,hqId/hqName 用于后续接入行情推送 +export const TAB_METRICS: TabMetric[] = [ + { id: 'rise', label: '涨幅', stat: 'rise', api: 'rise_percent', hqId: '34818', hqName: 'price_chg', index: 0, hasChildren: false }, + { id: 'fall', label: '跌幅', stat: 'fall', api: 'fall_percent', hqId: '34818', hqName: 'price_chg', index: 1, hasChildren: false }, + { id: 'riseSpeed', label: '涨速', stat: 'riseSpeed', api: 'rise_percent', hqId: '34821', hqName: 'chg_speed', index: 2, hasChildren: true }, + { id: 'fallSpeed', label: '跌速', stat: 'fallSpeed', api: 'fall_percent', hqId: '34821', hqName: 'chg_speed', index: 3, hasChildren: true }, + { id: 'turnOver', label: '成交额', stat: 'turnOver', api: 'turnover', hqId: '19', hqName: 'turnover', index: 4, hasChildren: false }, + { id: 'increPosit', label: '日增仓', stat: 'increPosit', api: 'increase_position', hqId: '34355', hqName: 'incre_posit', index: 5, hasChildren: false }, +]; + +// 二级 Tab 配置:仅涨速/跌速下出现,按周期筛选 +export const PERIOD_RANGES: PeriodRange[] = [ + { id: '1min', label: '1分钟', stat: 'min1', api: 'one_minute', hqId: '34874', index: 0 }, + { id: '5min', label: '5分钟', stat: 'min5', api: 'five_minute', hqId: '34325', index: 1 }, + { id: '10min', label: '10分钟', stat: 'min10', api: 'ten_minute', hqId: '34875', index: 2 }, + { id: '15min', label: '15分钟', stat: 'min15', api: 'fifteen_minute', hqId: '34876', index: 3 }, +]; diff --git a/entry/src/main/ets/market-ranking/MarketRankingMock.ets b/entry/src/main/ets/market-ranking/MarketRankingMock.ets new file mode 100644 index 0000000..76ce026 --- /dev/null +++ b/entry/src/main/ets/market-ranking/MarketRankingMock.ets @@ -0,0 +1,95 @@ +import { RankItem } from './MarketRankingTypes'; + +export const MOCK_RANK_LISTS: RankItem[][] = [ + // rise 涨幅 + [ + { code: 'CU2501', market: '70', name: '沪铜2501', price: 68520, price_chg: 3.21 }, + { code: 'RB2505', market: '70', name: '螺纹钢2505', price: 3452, price_chg: 2.15 }, + { code: 'M2505', market: '70', name: '豆粕2505', price: 2986, price_chg: 1.87 }, + { code: 'AU2506', market: '70', name: '沪金2506', price: 598.4, price_chg: 1.52 }, + { code: 'SR2505', market: '70', name: '白糖2505', price: 6234, price_chg: 1.1 }, + ], + // fall 跌幅 + [ + { code: 'I2505', market: '70', name: '铁矿石2505', price: 812, price_chg: -2.86 }, + { code: 'P2505', market: '70', name: '棕榈油2505', price: 7654, price_chg: -2.13 }, + { code: 'JD2505', market: '70', name: '鸡蛋2505', price: 3568, price_chg: -1.75 }, + { code: 'FG2505', market: '70', name: '玻璃2505', price: 1289, price_chg: -1.34 }, + { code: 'SM2505', market: '70', name: '锰硅2505', price: 5876, price_chg: -0.92 }, + ], + // riseSpeed 涨速(占位,实际渲染走 MOCK_SPEED_LISTS) + [], + // fallSpeed 跌速(占位,实际渲染走 MOCK_SPEED_LISTS) + [], + // turnOver 成交额(原始数值,未换算单位;View 层用 greatNumberFormat 换算成 万/亿) + [ + { code: 'RB2505', market: '70', name: '螺纹钢2505', price: 3452, price_chg: 2.15, turnover: 1286000 }, + { code: 'M2505', market: '70', name: '豆粕2505', price: 2986, price_chg: 1.87, turnover: 963000 }, + { code: 'I2505', market: '70', name: '铁矿石2505', price: 812, price_chg: -2.86, turnover: 841000 }, + ], + // increPosit 日增仓(原始数值,未换算单位) + [ + { code: 'CU2501', market: '70', name: '沪铜2501', price: 68520, price_chg: 3.21, incre_posit: 12000 }, + { code: 'AU2506', market: '70', name: '沪金2506', price: 598.4, price_chg: 1.52, incre_posit: 9000 }, + { code: 'P2505', market: '70', name: '棕榈油2505', price: 7654, price_chg: -2.13, incre_posit: -6000 }, + ], +]; + +// 外层索引 0=riseSpeed、1=fallSpeed;内层索引与 PERIOD_RANGES 一一对应(1/5/10/15 分钟) +// chg_speed 存原始 %数值;price_chg 同样按「行情固定字段」填充,供「最新价」列取色。 +export const MOCK_SPEED_LISTS: RankItem[][][] = [ + // riseSpeed 涨速 + [ + // 1分钟 + [ + { code: 'CU2501', market: '70', name: '沪铜2501', price: 68520, price_chg: 3.21, chg_speed: 0.85 }, + { code: 'AU2506', market: '70', name: '沪金2506', price: 598.4, price_chg: 1.52, chg_speed: 0.62 }, + { code: 'SR2505', market: '70', name: '白糖2505', price: 6234, price_chg: 1.1, chg_speed: 0.41 }, + ], + // 5分钟 + [ + { code: 'AU2506', market: '70', name: '沪金2506', price: 598.4, price_chg: 1.52, chg_speed: 1.34 }, + { code: 'CU2501', market: '70', name: '沪铜2501', price: 68520, price_chg: 3.21, chg_speed: 1.02 }, + { code: 'M2505', market: '70', name: '豆粕2505', price: 2986, price_chg: 1.87, chg_speed: 0.78 }, + ], + // 10分钟 + [ + { code: 'M2505', market: '70', name: '豆粕2505', price: 2986, price_chg: 1.87, chg_speed: 2.15 }, + { code: 'SR2505', market: '70', name: '白糖2505', price: 6234, price_chg: 1.1, chg_speed: 1.76 }, + { code: 'RB2505', market: '70', name: '螺纹钢2505', price: 3452, price_chg: 2.15, chg_speed: 1.3 }, + ], + // 15分钟 + [ + { code: 'RB2505', market: '70', name: '螺纹钢2505', price: 3452, price_chg: 2.15, chg_speed: 2.68 }, + { code: 'CU2501', market: '70', name: '沪铜2501', price: 68520, price_chg: 3.21, chg_speed: 2.21 }, + { code: 'AU2506', market: '70', name: '沪金2506', price: 598.4, price_chg: 1.52, chg_speed: 1.85 }, + ], + ], + // fallSpeed 跌速 + [ + // 1分钟 + [ + { code: 'I2505', market: '70', name: '铁矿石2505', price: 812, price_chg: -2.86, chg_speed: -0.73 }, + { code: 'FG2505', market: '70', name: '玻璃2505', price: 1289, price_chg: -1.34, chg_speed: -0.55 }, + { code: 'SM2505', market: '70', name: '锰硅2505', price: 5876, price_chg: -0.92, chg_speed: -0.38 }, + ], + // 5分钟 + [ + { code: 'FG2505', market: '70', name: '玻璃2505', price: 1289, price_chg: -1.34, chg_speed: -1.12 }, + { code: 'SM2505', market: '70', name: '锰硅2505', price: 5876, price_chg: -0.92, chg_speed: -0.89 }, + { code: 'I2505', market: '70', name: '铁矿石2505', price: 812, price_chg: -2.86, chg_speed: -0.64 }, + ], + // 10分钟 + [ + { code: 'SM2505', market: '70', name: '锰硅2505', price: 5876, price_chg: -0.92, chg_speed: -1.87 }, + { code: 'JD2505', market: '70', name: '鸡蛋2505', price: 3568, price_chg: -1.75, chg_speed: -1.45 }, + { code: 'FG2505', market: '70', name: '玻璃2505', price: 1289, price_chg: -1.34, chg_speed: -1.06 }, + ], + // 15分钟 + [ + { code: 'JD2505', market: '70', name: '鸡蛋2505', price: 3568, price_chg: -1.75, chg_speed: -2.34 }, + { code: 'I2505', market: '70', name: '铁矿石2505', price: 812, price_chg: -2.86, chg_speed: -1.98 }, + { code: 'SM2505', market: '70', name: '锰硅2505', price: 5876, price_chg: -0.92, chg_speed: -1.52 }, + ], + ], +]; diff --git a/entry/src/main/ets/market-ranking/MarketRankingModel.ets b/entry/src/main/ets/market-ranking/MarketRankingModel.ets new file mode 100644 index 0000000..f758853 --- /dev/null +++ b/entry/src/main/ets/market-ranking/MarketRankingModel.ets @@ -0,0 +1,34 @@ +import router from '@ohos.router'; +import { PeriodRange, RankItem, TabMetric } from './MarketRankingTypes'; +import { TAB_METRICS, PERIOD_RANGES } from './MarketRankingConstant'; +import { MOCK_RANK_LISTS, MOCK_SPEED_LISTS } from './MarketRankingMock'; + +// 状态持有 + 编排类; +@Observed +export class MarketRankingModel { + tabMetrics: TabMetric[] = TAB_METRICS; + periodRanges: PeriodRange[] = PERIOD_RANGES; + rankLists: RankItem[][] = MOCK_RANK_LISTS; + speedLists: RankItem[][][] = MOCK_SPEED_LISTS; + + // 按当前一级/二级 Tab 取展示列表:View 只需直接调用,不用自己判断 rankLists/speedLists 分支 + currentList(primaryIdx: number, secondaryIdx: number): RankItem[] { + if (this.tabMetrics[primaryIdx].hasChildren) { + const speedGroupIdx = primaryIdx === 2 ? 0 : 1; + return this.speedLists[speedGroupIdx][secondaryIdx]; + } + return this.rankLists[primaryIdx]; + } + + // 跳转到分时详情页 + jumpToDetail(item: RankItem): void { + router.pushUrl({ + url: 'pages/Detail', + params: { code: item.code, market: item.market }, + }); + } + + // TODO: 接入后端接口后,在此方法内按 (primaryIdx, secondaryIdx) 发起请求并更新 rankLists/speedLists + async updateRankList(primaryIdx: number, secondaryIdx: number): Promise { + } +} diff --git a/entry/src/main/ets/market-ranking/MarketRankingTypes.ets b/entry/src/main/ets/market-ranking/MarketRankingTypes.ets new file mode 100644 index 0000000..bd4d299 --- /dev/null +++ b/entry/src/main/ets/market-ranking/MarketRankingTypes.ets @@ -0,0 +1,50 @@ +// ============ 视图数据类型(合并接口 + 行情后,供 View 消费) ============ +// 保留原始字段,不在数据层预先格式化/预先判断涨跌色,格式化和取色都留给 View 层做。 +export interface RankItem { + code: string; + market: string; + name: string; + price: number; + // 涨跌幅(%数值,如 3.21 表示 +3.21%)。行情推送里固定字段,任何 Tab 下都存在, + // 用于「最新价」列的取色,也是 涨幅/跌幅 Tab 第三列的数据来源。 + price_chg?: number; + // 涨速/跌速(%数值)。仅 riseSpeed/fallSpeed Tab 下有值。 + chg_speed?: number; + // 成交额(原始数值,未换算单位)。仅 turnOver Tab 下有值。 + turnover?: number; + // 日增仓(原始数值,未换算单位)。仅 increPosit Tab 下有值。 + incre_posit?: number; +} + +export interface TabMetric { + id: string; + label: string; + stat: string; // 埋点用 + api: string; // HTTP 接口 quote_type 参数用 + hqId: string; // 行情订阅 sortid 用 + hqName: string; // 推送字段映射用 + index: number; + hasChildren: boolean; // Vue 用 children 数组本身判断,这里简化为布尔标记 +} + +export interface PeriodRange { + id: string; + label: string; + stat: string; + api: string; + hqId: string; + index: number; +} + +// ============ 后端接口原始响应类型(recommend_futures) ============ +// 对应 docs/cards/interfaces.md「一、HTTP 接口 - 1. 市场排名列表」 + +export interface RecommendFuturesItem { + contract_code: string; + contract_name: string; +} + +export interface RecommendFuturesResponse { + code: number; + data: RecommendFuturesItem[]; +} diff --git a/entry/src/main/ets/pages/Detail.ets b/entry/src/main/ets/pages/Detail.ets new file mode 100644 index 0000000..03e008a --- /dev/null +++ b/entry/src/main/ets/pages/Detail.ets @@ -0,0 +1,87 @@ +import router from '@ohos.router'; + +interface DetailParams { + code: string; + market: string; +} + +@Entry +@Component +struct Detail { + @State code: string = ''; + @State market: string = ''; + + aboutToAppear(): void { + const params = router.getParams() as DetailParams; + if (params) { + this.code = params.code ?? ''; + this.market = params.market ?? ''; + } + } + + build() { + Column() { + Row() { + Text('返回') + .fontSize(14) + .fontColor($r('app.color.text_blue')) + .onClick(() => { + router.back(); + }) + Text('分时详情') + .fontSize(18) + .fontWeight(FontWeight.Bold) + .fontColor($r('app.color.text_primary')) + .margin({ left: 16 }) + } + .width('100%') + .padding(16) + + Column({ space: 12 }) { + Row() { + Text('合约代码') + .fontSize(14) + .fontColor($r('app.color.text_tertiary')) + .layoutWeight(1) + Text(this.code) + .fontSize(16) + .fontColor($r('app.color.text_primary')) + .layoutWeight(2) + .textAlign(TextAlign.End) + } + .width('100%') + .padding({ top: 12, bottom: 12 }) + .border({ width: { bottom: 0.5 }, color: $r('app.color.divider_color') }) + + Row() { + Text('市场标识') + .fontSize(14) + .fontColor($r('app.color.text_tertiary')) + .layoutWeight(1) + Text(this.market) + .fontSize(16) + .fontColor($r('app.color.text_primary')) + .layoutWeight(2) + .textAlign(TextAlign.End) + } + .width('100%') + .padding({ top: 12, bottom: 12 }) + .border({ width: { bottom: 0.5 }, color: $r('app.color.divider_color') }) + + Text('mock 分时/详情页,对齐 Vue market-ranking 跳到 jumpToFenShi(this.code, this.market) 的目标页') + .fontSize(12) + .fontColor($r('app.color.text_tertiary')) + .margin({ top: 16 }) + } + .width('100%') + .padding(16) + .borderRadius(12) + .backgroundColor($r('app.color.card_bg')) + } + .width('100%') + .height('100%') + .backgroundColor($r('app.color.page_bg')) + .alignItems(HorizontalAlign.Start) + .justifyContent(FlexAlign.Start) + } +} diff --git a/entry/src/main/ets/pages/Index.ets b/entry/src/main/ets/pages/Index.ets new file mode 100644 index 0000000..6528c0b --- /dev/null +++ b/entry/src/main/ets/pages/Index.ets @@ -0,0 +1,28 @@ +import { Card } from '../common/Card'; +import { AIView } from '../ai-view/AIView'; +import { MarketRanking } from '../market-ranking/MarketRanking'; + +@Entry +@Component +struct Index { + build() { + Scroll() { + Column({ space: 16 }) { + Card() { + AIView() + } + Card() { + MarketRanking() + } + } + .width('100%') + .padding(16) + .alignItems(HorizontalAlign.Center) + .justifyContent(FlexAlign.Start) + } + .width('100%') + .height('100%') + .align(Alignment.Top) + .backgroundColor($r('app.color.page_bg')) + } +} diff --git a/entry/src/main/module.json5 b/entry/src/main/module.json5 new file mode 100644 index 0000000..53024e8 --- /dev/null +++ b/entry/src/main/module.json5 @@ -0,0 +1,50 @@ +{ + "module": { + "name": "entry", + "type": "entry", + "description": "$string:module_desc", + "mainElement": "EntryAbility", + "deviceTypes": [ + "phone" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:main_pages", + "abilities": [ + { + "name": "EntryAbility", + "srcEntry": "./ets/entryability/EntryAbility.ets", + "description": "$string:EntryAbility_desc", + "icon": "$media:layered_image", + "label": "$string:EntryAbility_label", + "startWindowIcon": "$media:startIcon", + "startWindowBackground": "$color:start_window_background", + "exported": true, + "skills": [ + { + "entities": [ + "entity.system.home" + ], + "actions": [ + "ohos.want.action.home" + ] + } + ] + } + ], + "extensionAbilities": [ + { + "name": "EntryBackupAbility", + "srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets", + "type": "backup", + "exported": false, + "metadata": [ + { + "name": "ohos.extension.backup", + "resource": "$profile:backup_config" + } + ], + } + ] + } +} \ No newline at end of file diff --git a/entry/src/main/resources/base/element/color.json b/entry/src/main/resources/base/element/color.json new file mode 100644 index 0000000..affaef5 --- /dev/null +++ b/entry/src/main/resources/base/element/color.json @@ -0,0 +1,18 @@ +{ + "color": [ + { "name": "start_window_background", "value": "#FFFFFF" }, + { "name": "page_bg", "value": "#F5F5F5" }, + { "name": "card_bg", "value": "#FFFFFF" }, + { "name": "stats_bg", "value": "#0A000000" }, + { "name": "text_primary", "value": "#D6000000" }, + { "name": "text_secondary", "value": "#99000000" }, + { "name": "text_tertiary", "value": "#66000000" }, + { "name": "text_grey", "value": "#4D000000" }, + { "name": "text_blue", "value": "#2E58FF" }, + { "name": "divider_color", "value": "#14000000" }, + { "name": "pill_active_bg", "value": "#1A2E58FF" }, + { "name": "pill_inactive_bg", "value": "#0A000000" }, + { "name": "color_rise", "value": "#FF2436" }, + { "name": "color_fall", "value": "#07AB4B" } + ] +} diff --git a/entry/src/main/resources/base/element/float.json b/entry/src/main/resources/base/element/float.json new file mode 100644 index 0000000..33ea223 --- /dev/null +++ b/entry/src/main/resources/base/element/float.json @@ -0,0 +1,8 @@ +{ + "float": [ + { + "name": "page_text_font_size", + "value": "50fp" + } + ] +} diff --git a/entry/src/main/resources/base/element/string.json b/entry/src/main/resources/base/element/string.json new file mode 100644 index 0000000..f945955 --- /dev/null +++ b/entry/src/main/resources/base/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "label" + } + ] +} \ No newline at end of file diff --git a/entry/src/main/resources/base/media/background.png b/entry/src/main/resources/base/media/background.png new file mode 100644 index 0000000..923f2b3 Binary files /dev/null and b/entry/src/main/resources/base/media/background.png differ diff --git a/entry/src/main/resources/base/media/foreground.png b/entry/src/main/resources/base/media/foreground.png new file mode 100644 index 0000000..97014d3 Binary files /dev/null and b/entry/src/main/resources/base/media/foreground.png differ diff --git a/entry/src/main/resources/base/media/layered_image.json b/entry/src/main/resources/base/media/layered_image.json new file mode 100644 index 0000000..fb49920 --- /dev/null +++ b/entry/src/main/resources/base/media/layered_image.json @@ -0,0 +1,7 @@ +{ + "layered-image": + { + "background" : "$media:background", + "foreground" : "$media:foreground" + } +} \ No newline at end of file diff --git a/entry/src/main/resources/base/media/startIcon.png b/entry/src/main/resources/base/media/startIcon.png new file mode 100644 index 0000000..205ad8b Binary files /dev/null and b/entry/src/main/resources/base/media/startIcon.png differ diff --git a/entry/src/main/resources/base/profile/backup_config.json b/entry/src/main/resources/base/profile/backup_config.json new file mode 100644 index 0000000..78f40ae --- /dev/null +++ b/entry/src/main/resources/base/profile/backup_config.json @@ -0,0 +1,3 @@ +{ + "allowToBackupRestore": true +} \ No newline at end of file diff --git a/entry/src/main/resources/base/profile/main_pages.json b/entry/src/main/resources/base/profile/main_pages.json new file mode 100644 index 0000000..0a21ce4 --- /dev/null +++ b/entry/src/main/resources/base/profile/main_pages.json @@ -0,0 +1,6 @@ +{ + "src": [ + "pages/Index", + "pages/Detail" + ] +} diff --git a/entry/src/main/resources/dark/element/color.json b/entry/src/main/resources/dark/element/color.json new file mode 100644 index 0000000..2f5b3ae --- /dev/null +++ b/entry/src/main/resources/dark/element/color.json @@ -0,0 +1,18 @@ +{ + "color": [ + { "name": "start_window_background", "value": "#000000" }, + { "name": "page_bg", "value": "#0F0F0F" }, + { "name": "card_bg", "value": "#2B2B2B" }, + { "name": "stats_bg", "value": "#0FFFFFFF" }, + { "name": "text_primary", "value": "#D6FFFFFF" }, + { "name": "text_secondary", "value": "#99FFFFFF" }, + { "name": "text_tertiary", "value": "#66FFFFFF" }, + { "name": "text_grey", "value": "#4DFFFFFF" }, + { "name": "text_blue", "value": "#527DFF" }, + { "name": "divider_color", "value": "#14FFFFFF" }, + { "name": "pill_active_bg", "value": "#1A527DFF" }, + { "name": "pill_inactive_bg", "value": "#0FFFFFFF" }, + { "name": "color_rise", "value": "#FF2436" }, + { "name": "color_fall", "value": "#07AB4B" } + ] +} diff --git a/entry/src/mock/mock-config.json5 b/entry/src/mock/mock-config.json5 new file mode 100644 index 0000000..7a73a41 --- /dev/null +++ b/entry/src/mock/mock-config.json5 @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/entry/src/ohosTest/ets/test/Ability.test.ets b/entry/src/ohosTest/ets/test/Ability.test.ets new file mode 100644 index 0000000..85c78f6 --- /dev/null +++ b/entry/src/ohosTest/ets/test/Ability.test.ets @@ -0,0 +1,35 @@ +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function abilityTest() { + describe('ActsAbilityTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }) + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }) + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }) + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }) + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + hilog.info(0x0000, 'testTag', '%{public}s', 'it begin'); + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }) + }) +} \ No newline at end of file diff --git a/entry/src/ohosTest/ets/test/List.test.ets b/entry/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 0000000..794c7dc --- /dev/null +++ b/entry/src/ohosTest/ets/test/List.test.ets @@ -0,0 +1,5 @@ +import abilityTest from './Ability.test'; + +export default function testsuite() { + abilityTest(); +} \ No newline at end of file diff --git a/entry/src/ohosTest/module.json5 b/entry/src/ohosTest/module.json5 new file mode 100644 index 0000000..509a3a2 --- /dev/null +++ b/entry/src/ohosTest/module.json5 @@ -0,0 +1,11 @@ +{ + "module": { + "name": "entry_test", + "type": "feature", + "deviceTypes": [ + "phone" + ], + "deliveryWithInstall": true, + "installationFree": false + } +} diff --git a/entry/src/test/List.test.ets b/entry/src/test/List.test.ets new file mode 100644 index 0000000..bb5b5c3 --- /dev/null +++ b/entry/src/test/List.test.ets @@ -0,0 +1,5 @@ +import localUnitTest from './LocalUnit.test'; + +export default function testsuite() { + localUnitTest(); +} \ No newline at end of file diff --git a/entry/src/test/LocalUnit.test.ets b/entry/src/test/LocalUnit.test.ets new file mode 100644 index 0000000..165fc16 --- /dev/null +++ b/entry/src/test/LocalUnit.test.ets @@ -0,0 +1,33 @@ +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function localUnitTest() { + describe('localUnitTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }); + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }); + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }); + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }); + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }); + }); +} \ No newline at end of file diff --git a/hvigor/hvigor-config.json5 b/hvigor/hvigor-config.json5 new file mode 100644 index 0000000..20f88f3 --- /dev/null +++ b/hvigor/hvigor-config.json5 @@ -0,0 +1,23 @@ +{ + "modelVersion": "6.1.0", + "dependencies": { + }, + "execution": { + // "analyze": "normal", /* Define the build analyze mode. Value: [ "normal" | "advanced" | "ultrafine" | false ]. Default: "normal" */ + // "daemon": true, /* Enable daemon compilation. Value: [ true | false ]. Default: true */ + // "incremental": true, /* Enable incremental compilation. Value: [ true | false ]. Default: true */ + // "parallel": true, /* Enable parallel compilation. Value: [ true | false ]. Default: true */ + // "typeCheck": false, /* Enable typeCheck. Value: [ true | false ]. Default: false */ + // "optimizationStrategy": "memory" /* Define the optimization strategy. Value: [ "memory" | "performance" ]. Default: "memory" */ + }, + "logging": { + // "level": "info" /* Define the log level. Value: [ "debug" | "info" | "warn" | "error" ]. Default: "info" */ + }, + "debugging": { + // "stacktrace": false /* Disable stacktrace compilation. Value: [ true | false ]. Default: false */ + }, + "nodeOptions": { + // "maxOldSpaceSize": 8192 /* Enable nodeOptions maxOldSpaceSize compilation. Unit M. Used for the daemon process. Default: 8192*/ + // "exposeGC": true /* Enable to trigger garbage collection explicitly. Default: true*/ + } +} diff --git a/hvigorfile.ts b/hvigorfile.ts new file mode 100644 index 0000000..47113e2 --- /dev/null +++ b/hvigorfile.ts @@ -0,0 +1,6 @@ +import { appTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins: [] /* Custom plugin to extend the functionality of Hvigor. */ +} \ No newline at end of file diff --git a/oh-package-lock.json5 b/oh-package-lock.json5 new file mode 100644 index 0000000..c5a91ec --- /dev/null +++ b/oh-package-lock.json5 @@ -0,0 +1,28 @@ +{ + "meta": { + "stableOrder": true, + "enableUnifiedLockfile": false + }, + "lockfileVersion": 3, + "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", + "specifiers": { + "@ohos/hamock@1.0.0": "@ohos/hamock@1.0.0", + "@ohos/hypium@1.0.25": "@ohos/hypium@1.0.25" + }, + "packages": { + "@ohos/hamock@1.0.0": { + "name": "@ohos/hamock", + "version": "1.0.0", + "integrity": "sha512-K6lDPYc6VkKe6ZBNQa9aoG+ZZMiwqfcR/7yAVFSUGIuOAhPvCJAo9+t1fZnpe0dBRBPxj2bxPPbKh69VuyAtDg==", + "resolved": "https://ohpm.openharmony.cn/ohpm/@ohos/hamock/-/hamock-1.0.0.har", + "registryType": "ohpm" + }, + "@ohos/hypium@1.0.25": { + "name": "@ohos/hypium", + "version": "1.0.25", + "integrity": "sha512-l6uO2pjl8HyEKdekLqQt7tUpWbDqX/42zoAzkagtUVZAW9jT6lMvbe54MVjoLxq/RwQGygRvi6j4GpypSMFSHw==", + "resolved": "https://ohpm.openharmony.cn/ohpm/@ohos/hypium/-/hypium-1.0.25.har", + "registryType": "ohpm" + } + } +} \ No newline at end of file diff --git a/oh-package.json5 b/oh-package.json5 new file mode 100644 index 0000000..41eb3e0 --- /dev/null +++ b/oh-package.json5 @@ -0,0 +1,10 @@ +{ + "modelVersion": "6.1.0", + "description": "Please describe the basic information.", + "dependencies": { + }, + "devDependencies": { + "@ohos/hypium": "1.0.25", + "@ohos/hamock": "1.0.0" + } +}