├── .editorconfig ├── .gitignore ├── .npmrc ├── .prettierignore ├── README.md ├── env.d.ts ├── index.html ├── package.json ├── pnpm-lock.yaml ├── public ├── data │ ├── custom │ │ ├── dot_black_list.json │ │ ├── embedded_rules_list.xml │ │ ├── embedded_setting_config.xml │ │ ├── fixed_orientation_list.xml │ │ └── third_party_app_optimize.prop │ ├── origin │ │ ├── autoui_list.xml │ │ ├── embedded_rules_list.xml │ │ ├── embedded_setting_config.xml │ │ ├── fixed_orientation_list.xml │ │ ├── package_info.json │ │ ├── system_app_optimize.prop │ │ ├── teg_config.db │ │ └── third_party_app_optimize.prop │ └── system │ │ ├── DisplayModeRecord.json │ │ ├── app.txt │ │ ├── dot_black_list.json │ │ ├── embedded_rules_list.xml │ │ ├── fixed_orientation_list.xml │ │ └── gamebooster_table.json ├── favicon.ico └── images │ ├── ai_cover_dark_bg.webp │ ├── ai_cover_pc.png │ ├── ai_footer_animation.webp │ ├── apps │ ├── bodian.webp │ ├── brainmusic.jpg │ ├── btremote.png │ ├── egggame.png │ ├── filmlytv.webp │ ├── flipaclip.png │ ├── flix.png │ ├── freenote.png │ ├── gamecores.webp │ ├── habicat.webp │ ├── honor_music.png │ ├── iunistoolsdisplay.png │ ├── kingdee.webp │ ├── liuzhoufile.png │ ├── magisk.webp │ ├── mi_device_connect.jpg │ ├── mi_home.png │ ├── mi_music.png │ ├── mi_note.png │ ├── mi_remote.png │ ├── mi_soundrecorder.webp │ ├── mi_theme.webp │ ├── mi_whitenoise.png │ ├── pianoperfect.webp │ ├── qinalt.jpg │ ├── qq_music.jpg │ ├── samsung_browser.png │ ├── sspai.png │ ├── starNote.webp │ ├── uu_remote.jpg │ └── youshi.webp │ ├── happy_new_year_banner.jpg │ └── icons │ ├── ai_icon.png │ ├── aicr.png │ ├── all_app.png │ ├── deepseek.png │ ├── google.png │ ├── happy_new_year.png │ ├── miui_content_extension_app.webp │ ├── qq_doc.png │ └── win_play_mobile.webp ├── src ├── App.vue ├── apis │ ├── autouiApi.ts │ ├── deviceApi.ts │ ├── dotBlackListApi.ts │ ├── embeddedApi.ts │ └── gameBoosterApi.ts ├── assets │ ├── applicationName.json │ ├── base.css │ ├── fonts.css │ ├── fonts │ │ ├── HarmonyOS_Sans │ │ │ ├── HarmonyOS_Sans_Black.woff2 │ │ │ ├── HarmonyOS_Sans_Bold.woff2 │ │ │ ├── HarmonyOS_Sans_Light.woff2 │ │ │ ├── HarmonyOS_Sans_Medium.woff2 │ │ │ ├── HarmonyOS_Sans_Regular.woff2 │ │ │ └── HarmonyOS_Sans_Thin.woff2 │ │ ├── MiSans-Regular.woff2 │ │ └── OPPO-Sans-4.0.woff │ ├── iconfont.js │ ├── logo.svg │ └── main.css ├── components │ ├── AutoUIAppDrawer.vue │ ├── DotBlackListAppDrawer.vue │ ├── EmbeddedActivityRuleDrawer.vue │ ├── EmbeddedAppDrawer.vue │ ├── EmbeddedPlaceholderDrawer.vue │ ├── EmbeddedSplitPairRuleDrawer.vue │ ├── EmbeddedTransitionRulesDrawer.vue │ ├── ErrorModal.vue │ ├── GameBoosterAppDrawer.vue │ ├── HelloWorld.vue │ ├── Sidebar │ │ ├── Sidebar.vue │ │ └── index.ts │ ├── SplashScreen.vue │ ├── TheWelcome.vue │ ├── WelcomeItem.vue │ └── icons │ │ ├── IconCommunity.vue │ │ ├── IconDocumentation.vue │ │ ├── IconEcosystem.vue │ │ ├── IconSupport.vue │ │ └── IconTooling.vue ├── config │ ├── blacklistApplications.tsx │ ├── rulePerceptionApplications.tsx │ └── whitelistApplications.ts ├── constant │ └── gameBooster.ts ├── hooks │ ├── useABTestActivation.tsx │ ├── useAmktiao.tsx │ ├── useAutoUI.ts │ ├── useDevelopmentSettingsEnabled.tsx │ ├── useDisabledOS2SystemAppOptimize.tsx │ ├── useDisabledOS2SystemPreStart.tsx │ ├── useDisplayModeRecord.tsx │ ├── useDisplaySettings.tsx │ ├── useEmbedded.ts │ ├── useFbo.tsx │ ├── useGameMode.tsx │ ├── useHideGestureLine.tsx │ ├── useInVisibleMode.tsx │ ├── useInstalledAppNames.ts │ ├── useMIUIContentExtension.tsx │ ├── useMiuiCursorStyle.tsx │ ├── useMiuiDesktopMode.tsx │ ├── useMouseGestureNaturalscroll.tsx │ ├── useNavigation.tsx │ ├── useOS2InstallModuleTips.tsx │ ├── usePointerSpeed.tsx │ ├── useQQDoc.tsx │ ├── useRealQuantity.tsx │ ├── useShowNotificationIconNum.tsx │ ├── useSidebar.tsx │ ├── useUFSHealth.ts │ ├── useVideoWallpaperLoop.tsx │ └── useZRAMWriteback.tsx ├── main.ts ├── router │ ├── device_routes │ │ ├── fold.ts │ │ ├── phone.ts │ │ └── tablet.ts │ └── index.ts ├── stores │ ├── autoui.ts │ ├── cloudFeature.ts │ ├── counter.ts │ ├── device.ts │ ├── dotBlackList.ts │ ├── embedded.ts │ ├── font.ts │ ├── gameBooster.ts │ ├── logs.ts │ └── package.ts ├── style.css ├── types │ ├── AutoUIItem.d.ts │ ├── AutoUIMergeRuleItem.d.ts │ ├── AutoUIPolicy.ts │ ├── AutoUISettingRuleItem.d.ts │ ├── DotBlackListItem.d.ts │ ├── DotBlackListMergeItem.d.ts │ ├── EmbeddedMergeRuleItem.d.ts │ ├── EmbeddedPlaceholder.d.ts │ ├── EmbeddedRuleItem.d.ts │ ├── EmbeddedSettingRuleItem.d.ts │ ├── EmbeddedSplitPairRuleItem.ts │ ├── ErrorLogging.d.ts │ ├── FixedOrientationRuleItem.d.ts │ ├── GameBoosterTableItem.d.ts │ └── PackageItem.d.ts ├── utils │ ├── autoUIFun.ts │ ├── common.ts │ ├── embeddedFun.ts │ ├── eventBus.ts │ ├── format.ts │ ├── handlePromiseWithLogging.ts │ ├── kernelsu │ │ ├── index.d.ts │ │ └── index.js │ ├── sqio.ts │ ├── validateFun.ts │ └── xmlFormat.ts └── views │ ├── AboutView.vue │ ├── AppStore.vue │ ├── AutoUIView.vue │ ├── BatteryHealth.vue │ ├── DotBlackListView.vue │ ├── EmbeddedActivityView.vue │ ├── EmbeddedWebView.vue │ ├── GameBooster.vue │ ├── GameTurboConfig.vue │ ├── HappyNewYearEgg.vue │ ├── LogView.vue │ ├── MagicControlView.vue │ ├── MemoryHealth.vue │ ├── NotFoundView.vue │ ├── SettingsView.vue │ └── SystemExperienceEnhance.vue ├── tailwind.config.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_size = 4 6 | end_of_line = lf 7 | indent_style = tab 8 | spelling_language = zh-CN 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.yml] 13 | indent_style = space 14 | 15 | [*.md] 16 | indent_size = 2 17 | indent_style = space 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | *.log 3 | 4 | # MacOS 5 | .DS_Store 6 | 7 | # Node Module 8 | node_modules 9 | 10 | # Editor Files 11 | .idea 12 | .vscode 13 | 14 | # Vite 15 | *.timestamp-* 16 | 17 | # Build 18 | /dist 19 | /stats.html 20 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmmirror.com/ -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Node Module 2 | node_modules 3 | 4 | # Build 5 | /dist 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HyperOS 完美横屏应用计划 For Web UI 2 | 3 | ## 模块首页 4 | 5 | 可通过项目首页快速了解本模块: 6 | 7 | [HyperOS 完美横屏应用计划 - MIUI MagicWindow+](https://hyper-magic-window.sothx.com/) 8 | 9 | ## 项目说明 10 | 11 | 本项目为支撑《HyperOS 完美横屏应用计划》模块的 Web UI 端,提供可视化界面修改模块部分配置的能力。 12 | 13 | ## 推荐 IDE 14 | 15 | [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur). 16 | 17 | ## 项目安装 18 | 19 | ```sh 20 | pnpm install 21 | ``` 22 | 23 | ### 编译和热重载开发 24 | 25 | ```sh 26 | pnpm dev 27 | ``` 28 | 29 | ### 编译并构建生产包 30 | 31 | ```sh 32 | pnpm build 33 | ``` 34 | -------------------------------------------------------------------------------- /env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 完美横屏应用计划 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hyper-magic-window-web-ui", 3 | "version": "0.0.0", 4 | "private": true, 5 | "type": "module", 6 | "devEngines": { 7 | "runtime": { 8 | "name": "node", 9 | "onFail": "error" 10 | }, 11 | "packageManager": { 12 | "name": "pnpm", 13 | "onFail": "error" 14 | } 15 | }, 16 | "browserslist": [ 17 | "android >= 11" 18 | ], 19 | "prettier": { 20 | "semi": true, 21 | "tabWidth": 4, 22 | "useTabs": true, 23 | "endOfLine": "lf", 24 | "printWidth": 120, 25 | "singleQuote": true, 26 | "jsxSingleQuote": true, 27 | "trailingComma": "all", 28 | "arrowParens": "avoid", 29 | "bracketSameLine": true, 30 | "htmlWhitespaceSensitivity": "strict", 31 | "tailwindConfig": "./tailwind.config.ts", 32 | "plugins": [ 33 | "prettier-plugin-tailwindcss" 34 | ], 35 | "overrides": [ 36 | { 37 | "files": "*.yml", 38 | "options": { 39 | "useTabs": false 40 | } 41 | }, 42 | { 43 | "files": "*.md", 44 | "options": { 45 | "tabWidth": 2, 46 | "useTabs": false 47 | } 48 | } 49 | ] 50 | }, 51 | "scripts": { 52 | "dev": "vite", 53 | "clean": "rimraf dist", 54 | "preview": "vite preview", 55 | "build-only": "vite build", 56 | "format": "prettier --write .", 57 | "type-check": "vue-tsc --build --force", 58 | "build": "run-p type-check \"build-only {@}\" --" 59 | }, 60 | "dependencies": { 61 | "pinia": "^2.1.7", 62 | "rollup-plugin-visualizer": "^5.14.0", 63 | "vue": "^3.4.29", 64 | "vue-router": "^4.3.3" 65 | }, 66 | "devDependencies": { 67 | "@headlessui/vue": "^1.7.23", 68 | "@heroicons/vue": "^2.1.5", 69 | "@tsconfig/node20": "^20.1.4", 70 | "@types/lodash-es": "^4.17.12", 71 | "@types/node": "^20.14.5", 72 | "@types/pako": "^2.0.3", 73 | "@vitejs/plugin-legacy": "^5.4.3", 74 | "@vitejs/plugin-vue": "^5.0.5", 75 | "@vitejs/plugin-vue-jsx": "^4.0.0", 76 | "@vue/tsconfig": "^0.5.1", 77 | "await-to-js": "^3.0.0", 78 | "axios": "^1.7.7", 79 | "child_process": "^1.0.2", 80 | "clsx": "^2.1.1", 81 | "darkreader": "^4.9.95", 82 | "highlight.js": "^11.10.0", 83 | "http": "0.0.1-security", 84 | "i": "^0.3.7", 85 | "lodash-es": "^4.17.21", 86 | "lz-string": "^1.5.0", 87 | "mitt": "^3.0.1", 88 | "naive-ui": "^2.40.1", 89 | "npm-run-all2": "^6.2.0", 90 | "nprogress": "^0.2.0", 91 | "pako": "^2.1.0", 92 | "pinia-plugin-persistedstate": "^4.1.1", 93 | "postcss-preset-env": "^10.0.7", 94 | "prettier-plugin-tailwindcss": "^0.6.8", 95 | "rimraf": "^6.0.1", 96 | "tailwindcss": "^3.4.13", 97 | "terser": "^5.36.0", 98 | "typescript": "~5.4.0", 99 | "url": "^0.11.4", 100 | "uuid": "^11.1.0", 101 | "vfonts": "^0.0.3", 102 | "vite": "^5.3.1", 103 | "vite-plugin-compression": "^0.5.1", 104 | "vite-plugin-vue-devtools": "^7.3.1", 105 | "vue-tsc": "^2.0.21" 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /public/data/custom/dot_black_list.json: -------------------------------------------------------------------------------- 1 | [ 2 | "com.coolapk.market", 3 | "com.tencent.mobileqq" 4 | ] -------------------------------------------------------------------------------- /public/data/custom/embedded_rules_list.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /public/data/custom/embedded_setting_config.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/data/custom/embedded_setting_config.xml -------------------------------------------------------------------------------- /public/data/custom/fixed_orientation_list.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/data/custom/fixed_orientation_list.xml -------------------------------------------------------------------------------- /public/data/custom/third_party_app_optimize.prop: -------------------------------------------------------------------------------- 1 | com.sspai.sspaiandroid:3 2 | com.coolapk.market:3 -------------------------------------------------------------------------------- /public/data/origin/system_app_optimize.prop: -------------------------------------------------------------------------------- 1 | com.miui.voiceassist:3 2 | com.miui.securitycenter:3 3 | com.android.browser:3 -------------------------------------------------------------------------------- /public/data/origin/teg_config.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/data/origin/teg_config.db -------------------------------------------------------------------------------- /public/data/origin/third_party_app_optimize.prop: -------------------------------------------------------------------------------- 1 | ai.zuoye.app:3 -------------------------------------------------------------------------------- /public/data/system/DisplayModeRecord.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "width": 1800, 5 | "height": 2880, 6 | "fps": 120.00001, 7 | "alternativeRefreshRates": [30.000002, 48.000004, 50.0, 60.000004, 90.0, 144.00002], 8 | "supportedHdrTypes": [1, 2, 3, 4] 9 | }, 10 | { 11 | "id": 2, 12 | "width": 1800, 13 | "height": 2880, 14 | "fps": 144.00002, 15 | "alternativeRefreshRates": [30.000002, 48.000004, 50.0, 60.000004, 90.0, 120.00001], 16 | "supportedHdrTypes": [1, 2, 3, 4] 17 | }, 18 | { 19 | "id": 3, 20 | "width": 1800, 21 | "height": 2880, 22 | "fps": 90.0, 23 | "alternativeRefreshRates": [30.000002, 48.000004, 50.0, 60.000004, 120.00001, 144.00002], 24 | "supportedHdrTypes": [1, 2, 3, 4] 25 | }, 26 | { 27 | "id": 4, 28 | "width": 1800, 29 | "height": 2880, 30 | "fps": 60.000004, 31 | "alternativeRefreshRates": [30.000002, 48.000004, 50.0, 90.0, 120.00001, 144.00002], 32 | "supportedHdrTypes": [1, 2, 3, 4] 33 | }, 34 | { 35 | "id": 5, 36 | "width": 1800, 37 | "height": 2880, 38 | "fps": 50.0, 39 | "alternativeRefreshRates": [30.000002, 48.000004, 60.000004, 90.0, 120.00001, 144.00002], 40 | "supportedHdrTypes": [1, 2, 3, 4] 41 | }, 42 | { 43 | "id": 6, 44 | "width": 1800, 45 | "height": 2880, 46 | "fps": 48.000004, 47 | "alternativeRefreshRates": [30.000002, 50.0, 60.000004, 90.0, 120.00001, 144.00002], 48 | "supportedHdrTypes": [1, 2, 3, 4] 49 | }, 50 | { 51 | "id": 7, 52 | "width": 1800, 53 | "height": 2880, 54 | "fps": 30.000002, 55 | "alternativeRefreshRates": [48.000004, 50.0, 60.000004, 90.0, 120.00001, 144.00002], 56 | "supportedHdrTypes": [1, 2, 3, 4] 57 | } 58 | ] -------------------------------------------------------------------------------- /public/data/system/gamebooster_table.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "package_name": "com.netease.nshm", 4 | "app_name": "逆水寒", 5 | "game_ratio": "0.0", 6 | "game_gravity": "17" 7 | }, 8 | { 9 | "package_name": "com.tencent.pokemonunite.cn", 10 | "app_name": "宝可梦大集结", 11 | "game_ratio": "2.3333333", 12 | "game_gravity": "80" 13 | } 14 | ] -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/favicon.ico -------------------------------------------------------------------------------- /public/images/ai_cover_dark_bg.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/images/ai_cover_dark_bg.webp -------------------------------------------------------------------------------- /public/images/ai_cover_pc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/images/ai_cover_pc.png -------------------------------------------------------------------------------- /public/images/ai_footer_animation.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/images/ai_footer_animation.webp -------------------------------------------------------------------------------- /public/images/apps/bodian.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/images/apps/bodian.webp -------------------------------------------------------------------------------- /public/images/apps/brainmusic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/images/apps/brainmusic.jpg -------------------------------------------------------------------------------- /public/images/apps/btremote.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/images/apps/btremote.png -------------------------------------------------------------------------------- /public/images/apps/egggame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/images/apps/egggame.png -------------------------------------------------------------------------------- /public/images/apps/filmlytv.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/images/apps/filmlytv.webp -------------------------------------------------------------------------------- /public/images/apps/flipaclip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/images/apps/flipaclip.png -------------------------------------------------------------------------------- /public/images/apps/flix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/images/apps/flix.png -------------------------------------------------------------------------------- /public/images/apps/freenote.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/images/apps/freenote.png -------------------------------------------------------------------------------- /public/images/apps/gamecores.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/images/apps/gamecores.webp -------------------------------------------------------------------------------- /public/images/apps/habicat.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/images/apps/habicat.webp -------------------------------------------------------------------------------- /public/images/apps/honor_music.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/images/apps/honor_music.png -------------------------------------------------------------------------------- /public/images/apps/iunistoolsdisplay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/images/apps/iunistoolsdisplay.png -------------------------------------------------------------------------------- /public/images/apps/kingdee.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/images/apps/kingdee.webp -------------------------------------------------------------------------------- /public/images/apps/liuzhoufile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/images/apps/liuzhoufile.png -------------------------------------------------------------------------------- /public/images/apps/magisk.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/images/apps/magisk.webp -------------------------------------------------------------------------------- /public/images/apps/mi_device_connect.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/images/apps/mi_device_connect.jpg -------------------------------------------------------------------------------- /public/images/apps/mi_home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/images/apps/mi_home.png -------------------------------------------------------------------------------- /public/images/apps/mi_music.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/images/apps/mi_music.png -------------------------------------------------------------------------------- /public/images/apps/mi_note.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/images/apps/mi_note.png -------------------------------------------------------------------------------- /public/images/apps/mi_remote.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/images/apps/mi_remote.png -------------------------------------------------------------------------------- /public/images/apps/mi_soundrecorder.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/images/apps/mi_soundrecorder.webp -------------------------------------------------------------------------------- /public/images/apps/mi_theme.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/images/apps/mi_theme.webp -------------------------------------------------------------------------------- /public/images/apps/mi_whitenoise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/images/apps/mi_whitenoise.png -------------------------------------------------------------------------------- /public/images/apps/pianoperfect.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/images/apps/pianoperfect.webp -------------------------------------------------------------------------------- /public/images/apps/qinalt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/images/apps/qinalt.jpg -------------------------------------------------------------------------------- /public/images/apps/qq_music.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/images/apps/qq_music.jpg -------------------------------------------------------------------------------- /public/images/apps/samsung_browser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/images/apps/samsung_browser.png -------------------------------------------------------------------------------- /public/images/apps/sspai.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/images/apps/sspai.png -------------------------------------------------------------------------------- /public/images/apps/starNote.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/images/apps/starNote.webp -------------------------------------------------------------------------------- /public/images/apps/uu_remote.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/images/apps/uu_remote.jpg -------------------------------------------------------------------------------- /public/images/apps/youshi.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/images/apps/youshi.webp -------------------------------------------------------------------------------- /public/images/happy_new_year_banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/images/happy_new_year_banner.jpg -------------------------------------------------------------------------------- /public/images/icons/ai_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/images/icons/ai_icon.png -------------------------------------------------------------------------------- /public/images/icons/aicr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/images/icons/aicr.png -------------------------------------------------------------------------------- /public/images/icons/all_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/images/icons/all_app.png -------------------------------------------------------------------------------- /public/images/icons/deepseek.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/images/icons/deepseek.png -------------------------------------------------------------------------------- /public/images/icons/google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/images/icons/google.png -------------------------------------------------------------------------------- /public/images/icons/happy_new_year.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/images/icons/happy_new_year.png -------------------------------------------------------------------------------- /public/images/icons/miui_content_extension_app.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/images/icons/miui_content_extension_app.webp -------------------------------------------------------------------------------- /public/images/icons/qq_doc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/images/icons/qq_doc.png -------------------------------------------------------------------------------- /public/images/icons/win_play_mobile.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/public/images/icons/win_play_mobile.webp -------------------------------------------------------------------------------- /src/apis/gameBoosterApi.ts: -------------------------------------------------------------------------------- 1 | import { exec, spawn, fullScreen, toast, moduleInfo, type ExecResults } from '@/utils/kernelsu/index.js'; 2 | import axios from 'axios'; 3 | import handlePromiseWithLogging from '@/utils/handlePromiseWithLogging'; 4 | import type GameBoosterTableItem from '@/types/GameBoosterTableItem'; 5 | 6 | export interface SmartFocusIOResult extends ExecResults { 7 | stdout: 'on' | 'off'; 8 | } 9 | 10 | export interface AndroidAppPackageJobsResult extends Omit { 11 | stdout: number; 12 | } 13 | 14 | export const getGameBoosterList = (): Promise => { 15 | const sqlite3 = '/data/adb/modules/MIUI_MagicWindow+/common/utils/sqlite3'; 16 | const GameBoosterDataBase = `/data/data/com.miui.securitycenter/databases/gamebooster.db`; 17 | const shellCommon = `echo "$(${sqlite3} ${GameBoosterDataBase} "SELECT * FROM gamebooster_table WHERE package_name!='none';" -json)"`; 18 | return handlePromiseWithLogging( 19 | new Promise(async (resolve, reject) => { 20 | if (import.meta.env.MODE === 'development') { 21 | const response = await axios.get('/data/system/gamebooster_table.json'); 22 | const jsonText = response.data; // 这是 XML 内容 23 | resolve(jsonText as unknown as GameBoosterTableItem[]); 24 | } else { 25 | const { errno, stdout, stderr }: ExecResults = (await exec( 26 | shellCommon, 27 | )) as unknown as ExecResults; 28 | if (errno) { 29 | reject(stderr); 30 | } 31 | if (stdout) { 32 | try { 33 | const gameBoosterData = JSON.parse(stdout); 34 | resolve(gameBoosterData) 35 | } catch (err) { 36 | reject(err); 37 | } 38 | } else { 39 | reject(errno) 40 | } 41 | } 42 | }), 43 | shellCommon, 44 | ); 45 | }; 46 | 47 | export const openAddGame = ():Promise => { 48 | const shellCommon = `am start -n com.miui.securitycenter/com.miui.gamebooster.ui.SelectGameLandActivity` 49 | return handlePromiseWithLogging( 50 | new Promise(async (resolve, reject) => { 51 | if (import.meta.env.MODE === 'development') { 52 | resolve(`success`); 53 | } else { 54 | const { errno, stdout, stderr }: ExecResults = await exec(shellCommon); 55 | errno ? reject(stderr) : resolve(stdout) 56 | } 57 | }), 58 | shellCommon, 59 | ); 60 | } 61 | 62 | export const updateGameRatioSetting = (packageName:GameBoosterTableItem['package_name'],gameRatio: GameBoosterTableItem['game_ratio'], gameGravity:GameBoosterTableItem['game_gravity']): Promise => { 63 | const sqlite3 = '/data/adb/modules/MIUI_MagicWindow+/common/utils/sqlite3'; 64 | const GameBoosterDataBase = `/data/data/com.miui.securitycenter/databases/gamebooster.db`; 65 | const shellCommon = `echo "$(${sqlite3} ${GameBoosterDataBase} "UPDATE gamebooster_table SET game_ratio='${gameRatio}', game_gravity='${gameGravity}' WHERE package_name='${packageName}'; SELECT changes();")"`; 66 | return handlePromiseWithLogging( 67 | new Promise(async (resolve, reject) => { 68 | if (import.meta.env.MODE === 'development') { 69 | resolve(`1`); 70 | } else { 71 | const { errno, stdout, stderr }: ExecResults = (await exec( 72 | shellCommon, 73 | )) as unknown as ExecResults; 74 | if (errno) { 75 | reject(stderr); 76 | } 77 | if (stdout) { 78 | resolve(stdout) 79 | } 80 | } 81 | }), 82 | shellCommon, 83 | ); 84 | }; 85 | 86 | -------------------------------------------------------------------------------- /src/assets/base.css: -------------------------------------------------------------------------------- 1 | /* color palette from */ 2 | :root { 3 | /* --vt-c-white: #ffffff; 4 | --vt-c-white-soft: #f8f8f8; 5 | --vt-c-white-mute: #f2f2f2; 6 | 7 | --vt-c-black: #181818; 8 | --vt-c-black-soft: #222222; 9 | --vt-c-black-mute: #282828; 10 | 11 | --vt-c-indigo: #2c3e50; 12 | 13 | --vt-c-divider-light-1: rgba(60, 60, 60, 0.29); 14 | --vt-c-divider-light-2: rgba(60, 60, 60, 0.12); 15 | --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65); 16 | --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48); 17 | 18 | --vt-c-text-light-1: var(--vt-c-indigo); 19 | --vt-c-text-light-2: rgba(60, 60, 60, 0.66); 20 | --vt-c-text-dark-1: var(--vt-c-white); 21 | --vt-c-text-dark-2: rgba(235, 235, 235, 0.64); */ 22 | } 23 | 24 | /* semantic color variables for this project */ 25 | :root { 26 | /* --color-background: var(--vt-c-white); 27 | --color-background-soft: var(--vt-c-white-soft); 28 | --color-background-mute: var(--vt-c-white-mute); 29 | 30 | --color-border: var(--vt-c-divider-light-2); 31 | --color-border-hover: var(--vt-c-divider-light-1); 32 | 33 | --color-heading: var(--vt-c-text-light-1); 34 | --color-text: var(--vt-c-text-light-1); 35 | 36 | --section-gap: 160px; */ 37 | } 38 | 39 | /* @media (prefers-color-scheme: dark) { 40 | :root { 41 | --color-background: var(--vt-c-black); 42 | --color-background-soft: var(--vt-c-black-soft); 43 | --color-background-mute: var(--vt-c-black-mute); 44 | 45 | --color-border: var(--vt-c-divider-dark-2); 46 | --color-border-hover: var(--vt-c-divider-dark-1); 47 | 48 | --color-heading: var(--vt-c-text-dark-1); 49 | --color-text: var(--vt-c-text-dark-2); 50 | } 51 | } */ 52 | 53 | *, 54 | *::before, 55 | *::after { 56 | box-sizing: border-box; 57 | margin: 0; 58 | font-weight: normal; 59 | } 60 | 61 | html { 62 | min-height: 100vh; 63 | scrollbar-gutter: stable; 64 | background-color: var(--n-color); 65 | } 66 | 67 | 68 | body { 69 | min-height: 100vh; 70 | height:100%; 71 | /* color: var(--color-text); */ 72 | /* background: var(--n-color); */ 73 | transition: 74 | color 0.5s, 75 | background-color 0.5s; 76 | line-height: 1.6; 77 | /* font-family: 78 | Inter, 79 | -apple-system, 80 | BlinkMacSystemFont, 81 | 'Segoe UI', 82 | Roboto, 83 | Oxygen, 84 | Ubuntu, 85 | Cantarell, 86 | 'Fira Sans', 87 | 'Droid Sans', 88 | 'Helvetica Neue', 89 | sans-serif; */ 90 | font-size: 15px; 91 | text-rendering: optimizeLegibility; 92 | -webkit-font-smoothing: antialiased; 93 | -moz-osx-font-smoothing: grayscale; 94 | } 95 | -------------------------------------------------------------------------------- /src/assets/fonts.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | @font-face { 4 | font-family: 'MiSans'; 5 | src: url('@/assets/fonts/MiSans-Regular.woff2') format('woff2'); 6 | font-weight: normal; 7 | font-style: normal; 8 | } 9 | 10 | @font-face { 11 | font-family: 'OPPO Sans'; 12 | src: url('@/assets/fonts/OPPO-Sans-4.0.woff') format('woff'); 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | 17 | 18 | /* CSS for HarmonyOS Sans */ 19 | 20 | /* Thin */ 21 | @font-face { 22 | font-family: 'HarmonyOS Sans'; 23 | src: url('@/assets/fonts/HarmonyOS_Sans/HarmonyOS_Sans_Thin.woff2') format('woff2'); 24 | font-weight: 100; 25 | font-style: normal; 26 | font-display: swap; 27 | } 28 | 29 | /* Light */ 30 | @font-face { 31 | font-family: 'HarmonyOS Sans'; 32 | src: url('@/assets/fonts/HarmonyOS_Sans/HarmonyOS_Sans_Light.woff2') format('woff2'); 33 | font-weight: 300; 34 | font-style: normal; 35 | font-display: swap; 36 | } 37 | 38 | /* Regular */ 39 | @font-face { 40 | font-family: 'HarmonyOS Sans'; 41 | src: url('@/assets/fonts/HarmonyOS_Sans/HarmonyOS_Sans_Regular.woff2') format('woff2'); 42 | font-weight: normal; 43 | font-style: normal; 44 | font-display: swap; 45 | } 46 | 47 | 48 | /* Medium */ 49 | @font-face { 50 | font-family: 'HarmonyOS Sans'; 51 | src: url('@/assets/fonts/HarmonyOS_Sans/HarmonyOS_Sans_Medium.woff2') format('woff2'); 52 | font-weight: 500; 53 | font-style: normal; 54 | font-display: swap; 55 | } 56 | 57 | /* Bold */ 58 | @font-face { 59 | font-family: 'HarmonyOS Sans'; 60 | src: url('@/assets/fonts/HarmonyOS_Sans/HarmonyOS_Sans_Bold.woff2') format('woff2'); 61 | font-weight: bold; 62 | font-style: normal; 63 | font-display: swap; 64 | } 65 | 66 | 67 | /* Black */ 68 | @font-face { 69 | font-family: 'HarmonyOS Sans'; 70 | src: url('@/assets/fonts/HarmonyOS_Sans/HarmonyOS_Sans_Black.woff2') format('woff2'); 71 | font-weight: 900; 72 | font-style: normal; 73 | font-display: swap; 74 | } 75 | -------------------------------------------------------------------------------- /src/assets/fonts/HarmonyOS_Sans/HarmonyOS_Sans_Black.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/src/assets/fonts/HarmonyOS_Sans/HarmonyOS_Sans_Black.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/HarmonyOS_Sans/HarmonyOS_Sans_Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/src/assets/fonts/HarmonyOS_Sans/HarmonyOS_Sans_Bold.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/HarmonyOS_Sans/HarmonyOS_Sans_Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/src/assets/fonts/HarmonyOS_Sans/HarmonyOS_Sans_Light.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/HarmonyOS_Sans/HarmonyOS_Sans_Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/src/assets/fonts/HarmonyOS_Sans/HarmonyOS_Sans_Medium.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/HarmonyOS_Sans/HarmonyOS_Sans_Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/src/assets/fonts/HarmonyOS_Sans/HarmonyOS_Sans_Regular.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/HarmonyOS_Sans/HarmonyOS_Sans_Thin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/src/assets/fonts/HarmonyOS_Sans/HarmonyOS_Sans_Thin.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/MiSans-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/src/assets/fonts/MiSans-Regular.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/OPPO-Sans-4.0.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/src/assets/fonts/OPPO-Sans-4.0.woff -------------------------------------------------------------------------------- /src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/assets/main.css: -------------------------------------------------------------------------------- 1 | @import './base.css'; 2 | 3 | .icon { 4 | width: 1em; 5 | height: 1em; 6 | vertical-align: -0.15em; 7 | fill: currentColor; 8 | overflow: hidden; 9 | } 10 | 11 | #app { 12 | min-height: 100%; 13 | /* height:100%; */ 14 | /* max-width: 1280px; */ 15 | margin: 0 auto; 16 | /* padding: 2rem; */ 17 | /* font-weight: normal; */ 18 | } 19 | 20 | @media (max-width: 640px) { 21 | .responsive-modal { 22 | width: 100% !important; 23 | min-width: auto !important; 24 | } 25 | } 26 | 27 | /* a, 28 | .green { 29 | text-decoration: none; 30 | color: hsla(160, 100%, 37%, 1); 31 | transition: 0.4s; 32 | padding: 3px; 33 | } */ 34 | 35 | /* @media (hover: hover) { 36 | a:hover { 37 | background-color: hsla(160, 100%, 37%, 0.2); 38 | } 39 | } */ 40 | 41 | /* @media (min-width: 1024px) { 42 | body { 43 | display: flex; 44 | place-items: center; 45 | } 46 | 47 | #app { 48 | display: grid; 49 | grid-template-columns: 1fr 1fr; 50 | padding: 0 2rem; 51 | } 52 | } */ 53 | -------------------------------------------------------------------------------- /src/components/EmbeddedActivityRuleDrawer.vue: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/src/components/EmbeddedActivityRuleDrawer.vue -------------------------------------------------------------------------------- /src/components/EmbeddedPlaceholderDrawer.vue: -------------------------------------------------------------------------------- 1 | 138 | 139 | 176 | 177 | 178 | -------------------------------------------------------------------------------- /src/components/EmbeddedSplitPairRuleDrawer.vue: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/src/components/EmbeddedSplitPairRuleDrawer.vue -------------------------------------------------------------------------------- /src/components/EmbeddedTransitionRulesDrawer.vue: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sothx/hyper-magic-window-web-ui/0bd2e4ae0565ecb9fd7ba7fc8e4026bbbc636470/src/components/EmbeddedTransitionRulesDrawer.vue -------------------------------------------------------------------------------- /src/components/ErrorModal.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 19 | 20 | 44 | -------------------------------------------------------------------------------- /src/components/Sidebar/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Sidebar } from './Sidebar.vue' -------------------------------------------------------------------------------- /src/components/SplashScreen.vue: -------------------------------------------------------------------------------- 1 | 5 | 13 | 14 | 37 | -------------------------------------------------------------------------------- /src/components/TheWelcome.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 117 | -------------------------------------------------------------------------------- /src/components/WelcomeItem.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 88 | -------------------------------------------------------------------------------- /src/components/icons/IconCommunity.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /src/components/icons/IconDocumentation.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /src/components/icons/IconEcosystem.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /src/components/icons/IconSupport.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /src/components/icons/IconTooling.vue: -------------------------------------------------------------------------------- 1 | 2 | 20 | -------------------------------------------------------------------------------- /src/constant/gameBooster.ts: -------------------------------------------------------------------------------- 1 | import { cloneDeep, invert } from 'lodash-es'; 2 | type Dictionary = { 3 | [key: string]: T; 4 | }; 5 | type GameRatioMap = Dictionary; 6 | type GameGravityMap = Dictionary; 7 | 8 | export const gameRatioMap: GameRatioMap =invert({ 9 | RATIO_FULLSCREEN: "0.0", 10 | RATIO_16_TO_9: "1.7777778", 11 | RATIO_4_TO_3: "1.3333333", 12 | RATIO_21_TO_9: "2.3333333", 13 | RATIO_32_TO_9: "3.5555556" 14 | }); 15 | 16 | 17 | export const ratioOptionItems = { 18 | RATIO_FULLSCREEN: { 19 | type: 'info', 20 | name: '全屏', 21 | color: {} 22 | }, // 屏幕填充 23 | RATIO_16_TO_9: { 24 | type: 'error', 25 | name: '16:9', 26 | color: {} 27 | }, // 16:9 28 | RATIO_4_TO_3: { 29 | type: 'success', 30 | name: '4:3', 31 | color: {} 32 | }, // 4:3 33 | RATIO_21_TO_9: { 34 | type: 'warning', 35 | name: '21:9', 36 | color: {} 37 | }, 38 | RATIO_32_TO_9: { 39 | type: 'primary', 40 | name: '32:9', 41 | color: { 42 | color: "rgba(255, 105, 180, 0.1)", 43 | borderColor: "rgba(255, 105, 180, 0.3)", 44 | textColor: "#ff69b4" 45 | }, 46 | } 47 | } as const; 48 | 49 | export interface GameRatioOptions { 50 | label:string; 51 | value: string; 52 | type?: string; 53 | color?: { 54 | color: string; 55 | borderColor: string; 56 | textColor: string; 57 | }; 58 | } 59 | 60 | export const gameRatioOptions = (inputArr?:GameRatioOptions[]) : GameRatioOptions[] => { 61 | console.log(inputArr,'inputArr') 62 | const copyInputArr = cloneDeep(inputArr) 63 | let ratioOptions = Object.entries(gameRatioMap).map(([value, key]) => { 64 | // 通过反转后的 key(比例标识)从 ratioOptionItems 获取对应的选项 65 | const option = ratioOptionItems[key as keyof typeof ratioOptionItems]; 66 | 67 | return { 68 | label: option.name, // 映射为名称 69 | value: value, // 保留比例值 70 | type: option.type, 71 | color: option.color // 映射为颜色 72 | }; 73 | }) as GameRatioOptions[]; 74 | 75 | if (copyInputArr) { 76 | ratioOptions = [ 77 | ...ratioOptions, 78 | ...copyInputArr 79 | ] 80 | } 81 | 82 | return ratioOptions; 83 | }; 84 | 85 | export const gameGravityMap: GameGravityMap = invert({ 86 | GRAVITY_CENTER: 17, 87 | GRAVITY_TOP: 48, 88 | GRAVITY_BOTTOM: 80, 89 | }) 90 | 91 | export const gravityOptionItems = { 92 | GRAVITY_CENTER: { 93 | color: 'info', 94 | name: '居中显示' 95 | }, 96 | GRAVITY_TOP: { 97 | color: 'success', 98 | name: '居顶显示' 99 | }, 100 | GRAVITY_BOTTOM: { 101 | color: 'error', 102 | name: '居底显示' 103 | } 104 | } 105 | 106 | export interface GameGravityOptions { 107 | label:string; 108 | value: string; 109 | color: string; 110 | } 111 | 112 | export const gameGravityOptions = () : GameGravityOptions[] => { 113 | return Object.entries(gameGravityMap).map(([value, key]) => { 114 | // 使用反转后的 key 来查找 gravityOptionItems 115 | const option = gravityOptionItems[key as keyof typeof gravityOptionItems]; 116 | 117 | return { 118 | label: option.name, // 映射为名称 119 | value: value, // 保留重力值 120 | color: option.color // 映射为颜色 121 | }; 122 | }); 123 | } 124 | 125 | 126 | 127 | /** 128 | * 为了保持色彩的和谐与多样性,可以考虑以下常见的颜色和它们的变种: 129 | 130 | 橙色 131 | 青色(青蓝) 132 | 粉色 133 | 深蓝色 134 | 深灰色 135 | 这些颜色都可以在设计中与绿色、紫色、蓝色、黄色和红色搭配使用,避免单一色调的单调感。 136 | 137 | 以下是我为你生成的这几种颜色的 border、text 和 background 格式: 138 | 1. 橙色 139 | border: rgba(255, 165, 0, 0.3) 140 | text: #ffa500 141 | background: rgba(255, 165, 0, 0.1) 142 | 2. 青色(青蓝) 143 | border: rgba(0, 255, 255, 0.3) 144 | text: #00ffff 145 | background: rgba(0, 255, 255, 0.1) 146 | 3. 粉色 147 | border: rgba(255, 105, 180, 0.3) 148 | text: #ff69b4 149 | background: rgba(255, 105, 180, 0.1) 150 | 4. 深蓝色 151 | border: rgba(0, 0, 139, 0.3) 152 | text: #00008b 153 | background: rgba(0, 0, 139, 0.1) 154 | 5. 深灰色 155 | border: rgba(169, 169, 169, 0.3) 156 | text: #a9a9a9 157 | background: rgba(169, 169, 169, 0.1) 158 | 159 | 160 | */ -------------------------------------------------------------------------------- /src/hooks/useABTestActivation.tsx: -------------------------------------------------------------------------------- 1 | import { computed, ref } from 'vue'; 2 | import { useDeviceStore } from '@/stores/device'; 3 | import { createDiscreteApi, darkTheme, lightTheme, useModal, type ConfigProviderProps } from 'naive-ui'; // 假设你用的是 Naive UI 的 modal 4 | 5 | export function useABTestActivation() { 6 | const deviceStore = useDeviceStore(); 7 | const configProviderPropsRef = computed(() => ({ 8 | theme: deviceStore.isDarkMode ? darkTheme : lightTheme, 9 | })); 10 | const { message, modal } = createDiscreteApi(['message', 'modal'], { 11 | configProviderProps: configProviderPropsRef, 12 | }); 13 | const loading = ref(false); 14 | 15 | function activateABTest(activateABTestRuleContent: Record) { 16 | loading.value = true; 17 | 18 | if (activateABTestRuleContent.OS2_PAD_EMBEDDED_APP_MANAGER) { 19 | deviceStore.ABTestInfo.OS2_PAD_EMBEDDED_APP_MANAGER = true; 20 | modal.create({ 21 | title: '操作成功', 22 | type: 'success', 23 | preset: 'dialog', 24 | content: () => ( 25 |
26 |

27 | 已成功参与OS2{' '} 28 | 29 | 应用横屏布局 For Web UI 30 | {' '} 31 | 的Beta测试w。由于小米在OS2新开发的{' '} 32 | 33 | 应用横屏布局 34 | {' '} 35 | 存在较多BUG,模块强制劫持了所有配置,仅能通过Web UI去调整应用横屏适配,在{' '} 36 | 37 | 平板专区 38 | {' '} 39 | 所做的相关修改会在重启后丢失。 40 |

41 |

42 | 开发Hyper OS 2.0模块的Web 43 | UI真的消耗了我大量的个人时间和精力QwQ(特别是在小米的BUG加持下),如果对{' '} 44 | 45 | 完美横屏应用计划感到满意 46 | {' '} 47 | ,求个随缘打赏。(打赏入口在Web UI侧边栏) 48 |

49 |
50 | ), 51 | negativeText: '确定', 52 | }); 53 | } else if (activateABTestRuleContent.Hyper_OS_DOT_BLACK_LIST_MANAGER) { 54 | deviceStore.ABTestInfo.Hyper_OS_DOT_BLACK_LIST_MANAGER = true; 55 | modal.create({ 56 | title: '操作成功', 57 | type: 'success', 58 | preset: 'dialog', 59 | content: () => ( 60 |
61 |

62 | 已成功参与Hyper OS{' '} 63 | 64 | 窗口控制器 For Web UI 65 | {' '} 66 | 的Beta测试w。该功能可能会随{' '} 67 | 68 | 小米云控 69 | {' '} 70 | 下发导致失效,失效时可以前往界面查看失效状态,并且选择{' '} 71 | 72 | 热重载应用配置 73 | {' '} 74 | 来恢复模块对窗口控制器的控制权。 75 |

76 |

77 | 开发Hyper OS 模块的Web UI真的消耗了我大量的个人时间和精力QwQ,如果对{' '} 78 | 79 | 完美横屏应用计划感到满意 80 | {' '} 81 | ,求个随缘打赏。(打赏入口在Web UI侧边栏) 82 |

83 |
84 | ), 85 | negativeText: '确定', 86 | }); 87 | } else if (activateABTestRuleContent.GAME_BOOSTER_RADIO_MANAGER) { 88 | deviceStore.ABTestInfo.GAME_BOOSTER_RADIO_MANAGER = true; 89 | modal.create({ 90 | title: '操作成功', 91 | type: 'success', 92 | preset: 'dialog', 93 | content: () => ( 94 |
95 |

96 | 已成功参与{' '} 97 | 98 | 游戏显示布局 2.0 99 | {' '} 100 | 的Beta测试w。该功能调整后会改变游戏的显示比例,获得更大的游戏视野,但并非所有游戏都兼容游戏显示比例调整,且部分游戏可能会对游戏显示比例的修改作为风控管理,可能导致游戏账号被封禁,调整游戏显示布局前,即认可并了解这些须知。 101 |

102 |

103 | 开发Hyper OS 模块的Web UI真的消耗了我大量的个人时间和精力QwQ,如果对{' '} 104 | 105 | 完美横屏应用计划感到满意 106 | {' '} 107 | ,求个随缘打赏。(打赏入口在Web UI侧边栏) 108 |

109 |
110 | ), 111 | negativeText: '确定', 112 | }); 113 | } else if (activateABTestRuleContent.GAME_BOOSTER_CUSTOM_RATIO) { 114 | // if (!deviceStore.ABTestInfo.GAME_BOOSTER_RADIO_MANAGER) { 115 | // modal.create({ 116 | // title: '激活自定义游戏比例失败', 117 | // type: 'error', 118 | // preset: 'dialog', 119 | // content: () =>

激活自定义游戏比例失败了QwQ,请先激活游戏显示布局2.0的Beta测试!

, 120 | // negativeText: '确定', 121 | // }); 122 | // return; 123 | // } 124 | deviceStore.ABTestInfo.GAME_BOOSTER_CUSTOM_RATIO = true; 125 | modal.create({ 126 | title: '操作成功', 127 | type: 'success', 128 | preset: 'dialog', 129 | content: () => ( 130 |
131 |

132 | 已成功激活{' '} 133 | 134 | 游戏显示布局 2.0 135 | {' '} 136 | 的自定义游戏比例。自定义游戏比例存在使用风险,如果配置了不恰当的自定义游戏比例,可能会触发部分游戏风控导致游戏账号被封,激活该功能即认可并了解这些须知。 137 |

138 |

139 | 开发Hyper OS 模块的Web UI真的消耗了我大量的个人时间和精力QwQ,如果对{' '} 140 | 141 | 完美横屏应用计划感到满意 142 | {' '} 143 | ,求个随缘打赏。(打赏入口在Web UI侧边栏) 144 |

145 |
146 | ), 147 | negativeText: '确定', 148 | }); 149 | } else { 150 | modal.create({ 151 | title: '解析激活口令失败', 152 | type: 'error', 153 | preset: 'dialog', 154 | content: () =>

解析激活口令失败了QwQ,请检查激活口令是否有误

, 155 | negativeText: '确定', 156 | }); 157 | } 158 | 159 | loading.value = false; 160 | } 161 | 162 | return { activateABTest, loading }; 163 | } 164 | -------------------------------------------------------------------------------- /src/hooks/useAutoUI.ts: -------------------------------------------------------------------------------- 1 | import { ref, computed, onMounted } from 'vue'; 2 | import { useDeviceStore } from '@/stores/device' 3 | import $to from 'await-to-js' 4 | import AutoUIAppDrawer from '@/components/AutoUIAppDrawer.vue'; 5 | import { NButton, createDiscreteApi, darkTheme, lightTheme, type ConfigProviderProps, type DataTableColumns, type NInput } from 'naive-ui' 6 | import type AutoUIMergeRuleItem from '@/types/AutoUIMergeRuleItem'; 7 | export function useAutoUI() { 8 | const deviceStore = useDeviceStore(); 9 | const configProviderPropsRef = computed(() => ({ 10 | theme: deviceStore.isDarkMode ? darkTheme : lightTheme 11 | })) 12 | const { message, modal } = createDiscreteApi(['message', 'modal'],{ 13 | configProviderProps: configProviderPropsRef 14 | }) 15 | 16 | const add = () => { 17 | 18 | } 19 | 20 | const update = () => { 21 | 22 | } 23 | 24 | onMounted(() => { 25 | }) 26 | 27 | 28 | return { 29 | add, 30 | update 31 | } 32 | 33 | 34 | } -------------------------------------------------------------------------------- /src/hooks/useDevelopmentSettingsEnabled.tsx: -------------------------------------------------------------------------------- 1 | import { ref, computed, onMounted, nextTick } from 'vue'; 2 | import { useDeviceStore } from '@/stores/device'; 3 | import $to from 'await-to-js'; 4 | import { 5 | createDiscreteApi, 6 | darkTheme, 7 | lightTheme, 8 | type ConfigProviderProps, 9 | } from 'naive-ui'; 10 | import * as deviceApi from '@/apis/deviceApi'; 11 | 12 | export function useDevelopmentSettingsEnabled() { 13 | const deviceStore = useDeviceStore(); 14 | const configProviderPropsRef = computed(() => ({ 15 | theme: deviceStore.isDarkMode ? darkTheme : lightTheme, 16 | })); 17 | 18 | const { message, modal } = createDiscreteApi(['message', 'modal'], { 19 | configProviderProps: configProviderPropsRef, 20 | }); 21 | 22 | const isEnabled = ref(false); 23 | 24 | const loading = ref(true); 25 | 26 | const isInit = ref(false); 27 | 28 | 29 | 30 | 31 | const change = async (value: 1 | 0) => { 32 | const [negativeRes, positiveRes] = await $to( 33 | new Promise((resolve, reject) => { 34 | modal.create({ 35 | title: `确定${value ? '开启' : '关闭'}开发者模式吗?`, 36 | type: 'info', 37 | preset: 'dialog', 38 | content: () => ( 39 |
40 |

即将{value ? '开启': '关闭'}开发者模式,确定要继续吗?

41 |
42 | ), 43 | positiveText: '确认', 44 | negativeText: '取消', 45 | onPositiveClick: () => { 46 | resolve('positiveClick'); 47 | }, 48 | onNegativeClick: () => { 49 | reject('negativeClick'); 50 | }, 51 | }); 52 | }), 53 | ); 54 | if (positiveRes) { 55 | const [putDevelopmentSettingsEnabledErr, putDevelopmentSettingsEnabledRes] = await $to( 56 | deviceApi.putDevelopmentSettingsEnabled(value) 57 | ); 58 | if (putDevelopmentSettingsEnabledErr) { 59 | modal.create({ 60 | title: '操作失败', 61 | type: 'error', 62 | preset: 'dialog', 63 | content: () =>

修改失败,详情请查看日志记录~

, 64 | negativeText: '确定', 65 | }); 66 | } else { 67 | isEnabled.value = value === 1 ? true : false 68 | } 69 | } 70 | }; 71 | 72 | const fetchData = async () => { 73 | const [getDevelopmentSettingsEnabledErr, getDevelopmentSettingsEnabledRes] = await $to(deviceApi.getDevelopmentSettingsEnabled()); 74 | if (getDevelopmentSettingsEnabledRes) { 75 | if (getDevelopmentSettingsEnabledRes === '1') { 76 | isEnabled.value = true 77 | } else { 78 | isEnabled.value = false 79 | } 80 | isInit.value = true; 81 | loading.value = false; 82 | } 83 | } 84 | 85 | 86 | onMounted(() => { 87 | setTimeout(() => { 88 | fetchData(); // 确保 UI 先渲染,再执行耗时操作 89 | },0); 90 | }); 91 | 92 | return { 93 | change, 94 | isEnabled, 95 | isInit, 96 | loading, 97 | }; 98 | } 99 | -------------------------------------------------------------------------------- /src/hooks/useDisabledOS2SystemAppOptimize.tsx: -------------------------------------------------------------------------------- 1 | import { ref, computed, onMounted } from 'vue'; 2 | import { useDeviceStore } from '@/stores/device'; 3 | import $to from 'await-to-js'; 4 | import { 5 | NButton, 6 | createDiscreteApi, 7 | darkTheme, 8 | lightTheme, 9 | type ConfigProviderProps, 10 | type DataTableColumns, 11 | type NInput, 12 | } from 'naive-ui'; 13 | import * as deviceApi from '@/apis/deviceApi'; 14 | 15 | export function useDisabledOS2SystemAppOptimize() { 16 | const deviceStore = useDeviceStore(); 17 | const configProviderPropsRef = computed(() => ({ 18 | theme: deviceStore.isDarkMode ? darkTheme : lightTheme, 19 | })); 20 | 21 | const { message, modal } = createDiscreteApi(['message', 'modal'], { 22 | configProviderProps: configProviderPropsRef, 23 | }); 24 | 25 | const changeDisabledOS2SystemAppOptimize = async (value: boolean) => { 26 | const [removeIsDisabledOS2SystemAppOptimizeErr, removeIsDisabledOS2SystemAppOptimizeRes] = await $to( 27 | deviceApi.removeIsDisabledOS2SystemAppOptimize(), 28 | ); 29 | if (removeIsDisabledOS2SystemAppOptimizeErr) { 30 | modal.create({ 31 | title: '操作失败', 32 | type: 'error', 33 | preset: 'dialog', 34 | content: () =>

修改失败,详情请查看日志记录~

, 35 | negativeText: '确定', 36 | }); 37 | return; 38 | } 39 | if (value) { 40 | const [addIsDisabledOS2SystemAppOptimizeErr, addIsDisabledOS2SystemAppOptimizeRes] = await $to( 41 | deviceApi.addIsDisabledOS2SystemAppOptimize(), 42 | ); 43 | if (addIsDisabledOS2SystemAppOptimizeErr) { 44 | modal.create({ 45 | title: '操作失败', 46 | type: 'error', 47 | preset: 'dialog', 48 | content: () =>

修改失败,详情请查看日志记录~

, 49 | negativeText: '确定', 50 | }); 51 | return; 52 | } 53 | } 54 | deviceStore.isDisabledOS2SystemAppOptimize = value; 55 | }; 56 | 57 | 58 | onMounted(() => { 59 | }); 60 | 61 | return { 62 | change: changeDisabledOS2SystemAppOptimize 63 | }; 64 | } 65 | -------------------------------------------------------------------------------- /src/hooks/useDisabledOS2SystemPreStart.tsx: -------------------------------------------------------------------------------- 1 | import { ref, computed, onMounted } from 'vue'; 2 | import { useDeviceStore } from '@/stores/device'; 3 | import $to from 'await-to-js'; 4 | import { 5 | NButton, 6 | createDiscreteApi, 7 | darkTheme, 8 | lightTheme, 9 | type ConfigProviderProps, 10 | type DataTableColumns, 11 | type NInput, 12 | } from 'naive-ui'; 13 | import * as deviceApi from '@/apis/deviceApi'; 14 | 15 | export function useDisabledOS2SystemPreStart() { 16 | const deviceStore = useDeviceStore(); 17 | const configProviderPropsRef = computed(() => ({ 18 | theme: deviceStore.isDarkMode ? darkTheme : lightTheme, 19 | })); 20 | 21 | const { message, modal } = createDiscreteApi(['message', 'modal'], { 22 | configProviderProps: configProviderPropsRef, 23 | }); 24 | 25 | const isShow = computed(() => { 26 | return ( 27 | deviceStore.preStartProp.build && 28 | deviceStore.androidTargetSdk >= 35 && 29 | deviceStore.MIOSVersion && 30 | deviceStore.MIOSVersion >= 2 31 | ); 32 | }); 33 | 34 | const change = async (value: boolean) => { 35 | const [negativeRes, positiveRes] = await $to( 36 | new Promise((resolve, reject) => { 37 | modal.create({ 38 | title: value ? '想开启应用预加载吗?' : '想禁用应用预加载吗?', 39 | type: 'info', 40 | preset: 'dialog', 41 | content: () => ( 42 |
43 | {!value && ( 44 |

45 | 禁用{' '} 46 | 48 | 应用预加载 49 | {' '} 50 | 后,触摸系统桌面上的{' '} 51 | 53 | 任意应用 54 | {' '} 55 | 不再触发应用预加载,需要设备重启才会生效,确定要继续禁用吗? 56 |

57 | )} 58 | {value && ( 59 |

60 | 开启{' '} 61 | 63 | 应用预加载 64 | {' '} 65 | 后,触摸系统桌面上的{' '} 66 | 68 | 任意应用 69 | {' '} 70 | 将会触发应用预加载,需要设备重启才会生效,确定要继续开启吗? 71 |

72 | )} 73 |
74 | ), 75 | positiveText: '确定', 76 | negativeText: '我再想想', 77 | onPositiveClick: () => { 78 | resolve('positiveClick'); 79 | }, 80 | onNegativeClick: () => { 81 | reject('negativeClick'); 82 | }, 83 | }); 84 | }), 85 | ); 86 | if (positiveRes) { 87 | const [removeDisabledPreStartProcErr] = await $to(deviceApi.removeDisabledPreStartProc()); 88 | if (removeDisabledPreStartProcErr) { 89 | modal.create({ 90 | title: '操作失败', 91 | type: 'error', 92 | preset: 'dialog', 93 | content: () =>

无法修改应用预加载的配置,详情请查看日志记录~

, 94 | negativeText: '确定', 95 | }); 96 | return; 97 | } 98 | if (!value) { 99 | const [addDisabledPreStartProcErr] = await $to(deviceApi.addDisabledPreStartProc()); 100 | if (addDisabledPreStartProcErr) { 101 | modal.create({ 102 | title: '操作失败', 103 | type: 'error', 104 | preset: 'dialog', 105 | content: () =>

无法修改应用预加载的配置,详情请查看日志记录~

, 106 | negativeText: '确定', 107 | }); 108 | return; 109 | } 110 | } 111 | modal.create({ 112 | title: '操作成功', 113 | type: 'success', 114 | preset: 'dialog', 115 | content: () =>

好耶w,已{value ? '开启' : '关闭'}应用预加载~实际生效还需要重启设备,确定要重启吗?

, 116 | positiveText: '立即重启', 117 | negativeText: '稍后手动重启', 118 | onPositiveClick() { 119 | deviceApi.rebootDevice().catch(err => { 120 | modal.create({ 121 | title: '操作失败', 122 | type: 'error', 123 | preset: 'dialog', 124 | content: () =>

无法重启设备,详情请查看日志记录~

, 125 | negativeText: '确定', 126 | }); 127 | return; 128 | }); 129 | }, 130 | }); 131 | } 132 | }; 133 | 134 | onMounted(() => {}); 135 | 136 | return { 137 | isShow, 138 | change, 139 | }; 140 | } 141 | -------------------------------------------------------------------------------- /src/hooks/useDisplayModeRecord.tsx: -------------------------------------------------------------------------------- 1 | import { ref, computed, onMounted, reactive, h, renderSlot, type CSSProperties } from 'vue'; 2 | import { useDeviceStore } from '@/stores/device' 3 | import * as deviceApi from '@/apis/deviceApi'; 4 | import $to from 'await-to-js'; 5 | import { type ConfigProviderProps, darkTheme, lightTheme, createDiscreteApi, NSwitch } from 'naive-ui'; 6 | 7 | export interface DisplayModeItem { 8 | id: number, 9 | width: number, 10 | height: number, 11 | fps: number, 12 | vsync?: number, 13 | synthetic?: boolean, 14 | alternativeRefreshRates: number[], 15 | supportedHdrTypes: number[] 16 | } 17 | 18 | export function useDisplayModeRecord() { 19 | const railStyle = ({ focused, checked }: { focused: boolean; checked: boolean }) => { 20 | const style: CSSProperties = {}; 21 | if (checked) { 22 | style.background = '#2080f0'; 23 | if (focused) { 24 | style.boxShadow = '0 0 0 2px #2080f040'; 25 | } 26 | } else { 27 | style.background = '#d03050'; 28 | if (focused) { 29 | style.boxShadow = '0 0 0 2px #d0305040'; 30 | } 31 | } 32 | return style; 33 | }; 34 | const deviceStore = useDeviceStore(); 35 | 36 | const configProviderPropsRef = computed(() => ({ 37 | theme: deviceStore.isDarkMode ? darkTheme : lightTheme, 38 | })); 39 | 40 | const { message, modal } = createDiscreteApi(['message', 'modal'], { 41 | configProviderProps: configProviderPropsRef, 42 | }); 43 | 44 | const supportHDRTypes = computed(() => { 45 | if (Array.isArray(deviceStore.displayModeList) && deviceStore.displayModeList.length) { 46 | return deviceStore.displayModeList[0].supportedHdrTypes 47 | } 48 | 49 | return [] 50 | }) 51 | 52 | const formatDisplayModeList = computed(() => { 53 | return deviceStore.displayModeList.map(item => ({ 54 | ...item, 55 | fps: Math.round(item.fps), // 将 fps 转换为整数 56 | alternativeRefreshRates: item.alternativeRefreshRates.map(rate => Math.round(rate)) // 将 alternativeRefreshRates 转换为整数数组 57 | })); 58 | }) 59 | 60 | 61 | 62 | const selectDisplayMode = async (data:DisplayModeItem) => { 63 | const switchRender = () => h( 64 | NSwitch, // 目标组件 65 | { 66 | class: 'mt-2', 67 | railStyle: railStyle 68 | }, 69 | { 70 | checked: () => '开机自启动', // checked 插槽的内容 71 | unchecked: () => '仅本次生效', // unchecked 插槽的内容 72 | } 73 | ) 74 | modal.create({ 75 | title: '想应用该配置吗?', 76 | type: 'info', 77 | preset: 'dialog', 78 | content: () =>
应用后设备分辨率将配置为{data.width}x{data.height},刷新率将配置为{data.fps}Hz,在设备下次重启前将一直维持该配置,该功能可能受触控笔和其他第三方模块影响不一定生效,如需恢复系统设置内的默认分辨率及刷新率配置,请手动重启设备。{deviceStore.deviceType === 'tablet' && 连接触控笔蓝牙期间,为了确保触控笔正常工作,系统也会强行重置该配置,断开触控笔蓝牙后需要重新配置,}确定要继续应用该配置么?
, 79 | negativeText: '取消', 80 | positiveText: '确定', 81 | onPositiveClick() { 82 | setDisplayMode(data.id - 1); 83 | } 84 | }); 85 | } 86 | 87 | const setDisplayMode = async (displayModeID: number) => { 88 | const [setDisplayModeErr,setDisplayModeRes] = await $to(deviceApi.setDisplayMode(displayModeID)) 89 | if (setDisplayModeErr) { 90 | modal.create({ 91 | title: '操作失败', 92 | type: 'error', 93 | preset: 'dialog', 94 | content: () =>

修改失败,详情请查看日志记录~

, 95 | negativeText: '确定', 96 | }); 97 | } 98 | if (setDisplayModeRes) { 99 | modal.create({ 100 | title: '操作成功', 101 | type: 'success', 102 | preset: 'dialog', 103 | content: () =>

已成功应用该分辨率及刷新率配置,在设备下次重启前将一直维持该配置,如需恢复系统设置内的默认分辨率及刷新率配置,请手动重启设备。

, 104 | negativeText: '确定', 105 | }); 106 | } 107 | } 108 | 109 | 110 | 111 | onMounted(async () => { 112 | }) 113 | 114 | 115 | return { 116 | supportHDRTypes, 117 | formatDisplayModeList, 118 | setDisplayMode, 119 | selectDisplayMode 120 | } 121 | 122 | 123 | } -------------------------------------------------------------------------------- /src/hooks/useDisplaySettings.tsx: -------------------------------------------------------------------------------- 1 | import { ref, computed, onMounted, nextTick } from 'vue'; 2 | import $to from 'await-to-js'; 3 | import { useDeviceStore } from '@/stores/device'; 4 | import { 5 | NButton, 6 | createDiscreteApi, 7 | darkTheme, 8 | lightTheme, 9 | type ConfigProviderProps, 10 | type DataTableColumns, 11 | type NInput, 12 | } from 'naive-ui'; 13 | import * as deviceApi from '@/apis/deviceApi'; 14 | import { useLogsStore } from '@/stores/logs'; 15 | 16 | export function useDisplaySettings() { 17 | const deviceStore = useDeviceStore(); 18 | 19 | const configProviderPropsRef = computed(() => ({ 20 | theme: deviceStore.isDarkMode ? darkTheme : lightTheme, 21 | })); 22 | 23 | const { message, modal } = createDiscreteApi(['message', 'modal'], { 24 | configProviderProps: configProviderPropsRef, 25 | }); 26 | 27 | const hasQComDisplayBrightness = ref(false); 28 | 29 | const hasMTKDisplayBrightness = ref(false); 30 | 31 | const open = async () => { 32 | modal.create({ 33 | title: '确认使用强制屏幕最低亮度吗?', 34 | type: 'warning', 35 | preset: 'dialog', 36 | content: () => ( 37 |
38 |

39 | 通过将屏幕亮度调整为0,达到熄灭屏幕但是不影响屏幕的触控操作。可能适合部分特殊场景使用,游戏或者视频场景仍然推荐使用{' '} 40 | 41 | 熄屏挂机 42 | {' '} 43 | 和{' '} 44 | 45 | 熄屏听剧 46 | {' '} 47 | ,使用该功能会自动关闭{' '} 48 | 49 | 自动亮度 50 | {' '} 51 | ,请悉知,如需恢复屏幕显示需要敲击两次{' '} 52 | 53 | 电源键 54 | {' '} 55 | ,确定要继续吗? 56 |

57 |
58 | ), 59 | positiveText: '确定开启', 60 | negativeText: '我再想想', 61 | onPositiveClick: async () => { 62 | if (hasQComDisplayBrightness.value) { 63 | await deviceApi.setQComDisplayBrightnessToZero(); 64 | } 65 | if (hasMTKDisplayBrightness.value) { 66 | await deviceApi.setMTKDisplayBrightnessToZero(); 67 | } 68 | }, 69 | }); 70 | }; 71 | 72 | const fetchData = async () => { 73 | deviceApi.getHasQComDisplayBrightness().then(res => { 74 | if (res === 'exists') { 75 | hasQComDisplayBrightness.value = true; 76 | } 77 | }); 78 | deviceApi.getHasMTKDisplayBrightness().then(res => { 79 | if (res === 'exists') { 80 | hasMTKDisplayBrightness.value = true; 81 | } 82 | }); 83 | }; 84 | 85 | onMounted(() => { 86 | setTimeout(() => { 87 | fetchData(); // 确保 UI 先渲染,再执行耗时操作 88 | },0); 89 | }); 90 | 91 | return { 92 | hasQComDisplayBrightness, 93 | open, 94 | hasMTKDisplayBrightness, 95 | }; 96 | } 97 | -------------------------------------------------------------------------------- /src/hooks/useEmbedded.ts: -------------------------------------------------------------------------------- 1 | import { ref, computed, onMounted, reactive, toRefs } from 'vue'; 2 | import { useDeviceStore } from '@/stores/device' 3 | import { cloneDeep } from 'lodash-es'; 4 | import $to from 'await-to-js' 5 | import { NButton, createDiscreteApi, darkTheme, lightTheme, type ConfigProviderProps, type DataTableColumns, type NInput } from 'naive-ui' 6 | import { useEmbeddedStore } from '@/stores/embedded'; 7 | import type EmbeddedRuleItem from '@/types/EmbeddedRuleItem'; 8 | import type FixedOrientationRuleItem from '@/types/FixedOrientationRuleItem'; 9 | export function useEmbedded(rowName: string) { 10 | const embeddedStore = useEmbeddedStore(); // 根据实际情况获取你的 store 11 | 12 | interface EmbeddedState { 13 | moduleEmbeddedRules: EmbeddedRuleItem; // 你可以根据实际情况修改为具体类型 14 | currentEmbeddedRules: EmbeddedRuleItem; 15 | moduleFixedOrientation: FixedOrientationRuleItem; 16 | currentFixedOrientation: FixedOrientationRuleItem; 17 | } 18 | 19 | // 使用 reactive 创建一个响应式对象 20 | const state: EmbeddedState = reactive({ 21 | moduleEmbeddedRules: cloneDeep( 22 | embeddedStore.isPatchMode 23 | ? embeddedStore.patchEmbeddedRulesList[rowName] 24 | : embeddedStore.sourceEmbeddedRulesList[rowName] 25 | ), 26 | currentEmbeddedRules: cloneDeep( 27 | embeddedStore.customConfigEmbeddedRulesList[rowName] || 28 | (embeddedStore.isPatchMode 29 | ? embeddedStore.patchEmbeddedRulesList[rowName] 30 | : embeddedStore.sourceEmbeddedRulesList[rowName]) 31 | ) || {}, 32 | moduleFixedOrientation: cloneDeep( 33 | embeddedStore.isPatchMode 34 | ? embeddedStore.patchFixedOrientationList[rowName] 35 | : embeddedStore.sourceFixedOrientationList[rowName] 36 | ), 37 | currentFixedOrientation: cloneDeep( 38 | embeddedStore.customConfigFixedOrientationList[rowName] || 39 | (embeddedStore.isPatchMode 40 | ? embeddedStore.patchFixedOrientationList[rowName] 41 | : embeddedStore.sourceFixedOrientationList[rowName]) 42 | ) || {}, 43 | }); 44 | 45 | return { 46 | ...toRefs(state), // 将 reactive 对象转换为可以解构的 ref 47 | }; 48 | } -------------------------------------------------------------------------------- /src/hooks/useGameMode.tsx: -------------------------------------------------------------------------------- 1 | import { ref, computed, onMounted } from 'vue'; 2 | import { useDeviceStore } from '@/stores/device'; 3 | import * as deviceApi from '@/apis/deviceApi'; 4 | import { createDiscreteApi, darkTheme, lightTheme, type ConfigProviderProps } from 'naive-ui'; 5 | import $to from 'await-to-js'; 6 | 7 | export function useGameMode() { 8 | const configProviderPropsRef = computed(() => ({ 9 | theme: deviceStore.isDarkMode ? darkTheme : lightTheme, 10 | })); 11 | const deviceStore = useDeviceStore(); 12 | const isSupportGameMode = computed(() => { 13 | return deviceStore.miuiCompatEnable && deviceStore.androidTargetSdk && deviceStore.androidTargetSdk > 31; 14 | }); 15 | 16 | const { message, modal } = createDiscreteApi(['message', 'modal'], { 17 | configProviderProps: configProviderPropsRef, 18 | }); 19 | 20 | const vaildModuleVersion = async () => { 21 | return new Promise((resolve, reject) => { 22 | if ( 23 | deviceStore.androidTargetSdk >= 35 && 24 | deviceStore.MIOSVersion && 25 | deviceStore.MIOSVersion >= 2 && 26 | deviceStore.deviceType === 'tablet' 27 | ) { 28 | if (deviceStore.MIOSVersion === 2) { 29 | if ( 30 | !['pad-ext-', 'pad-hyperos2-based-on-vanillaIceCream-'].some( 31 | item => 32 | ((deviceStore.moduleInfo && deviceStore.moduleInfo.version) || '').indexOf(item) === 0, 33 | ) 34 | ) { 35 | modal.create({ 36 | title: '获取专版模块', 37 | type: 'info', 38 | preset: 'dialog', 39 | content: () => ( 40 |
41 |

42 | 需要安装{' '} 43 | 45 | 小米平板安卓15澎湃2.0专版(pad-hyperos2-based-on-vanillaIceCream) 46 | {' '} 47 | 才可以正常使用{' '} 48 | 50 | 游戏显示布局 51 | {' '} 52 | ,请先替换模块版本~ 53 |

54 |

下载地址:https://caiyun.139.com/m/i?135CdgGlXeVEC

55 |
56 | ), 57 | positiveText: '复制下载链接到剪切板', 58 | negativeText: '取消', 59 | onPositiveClick: () => { 60 | navigator.clipboard.writeText(`https://caiyun.139.com/m/i?135CdgGlXeVEC`); 61 | }, 62 | onNegativeClick: () => {}, 63 | }); 64 | reject('error'); 65 | } else { 66 | resolve('success'); 67 | } 68 | } else { 69 | modal.create({ 70 | title: '未适配系统版本', 71 | type: 'error', 72 | preset: 'dialog', 73 | content: () =>

该系统版本尚未适配游戏显示布局,请等待模块后续更新~

, 74 | }); 75 | reject('error'); 76 | } 77 | } else { 78 | resolve('success'); 79 | } 80 | }); 81 | }; 82 | 83 | const changeGameMode = async (value: boolean) => { 84 | if (value) { 85 | const [vaildModuleVersionErr] = await $to(vaildModuleVersion()); 86 | if (vaildModuleVersionErr) { 87 | return; 88 | } 89 | } 90 | const [negativeRes, positiveRes] = await $to( 91 | new Promise((resolve, reject) => { 92 | modal.create({ 93 | title: value ? '想开启游戏显示布局吗?' : '想关闭游戏显示布局吗?', 94 | type: 'info', 95 | preset: 'dialog', 96 | content: () => ( 97 |
98 |

99 | {value ? '开启' : '关闭'}{' '} 100 | 101 | 游戏显示布局 102 | {' '} 103 | 后需要设备重启才会生效~ 104 |

105 | {value && 106 | deviceStore.deviceType === 'tablet' && 107 | deviceStore.MIOSVersion && 108 | deviceStore.MIOSVersion >= 2 && 109 | deviceStore.androidTargetSdk >= 35 && ( 110 |

111 | 从Hyper OS 2.0开始,小米平板需要搭配配套的{' '} 112 | 114 | 修改版平板/手机管家 115 | {' '} 116 | 才能使用游戏显示布局,详情请前往模块首页了解~ 117 |

118 | )} 119 |

是否继续{value ? '开启' : '关闭'}游戏显示布局?

120 |
121 | ), 122 | positiveText: '确定', 123 | negativeText: '取消', 124 | onPositiveClick: () => { 125 | resolve('positiveClick'); 126 | }, 127 | onNegativeClick: () => { 128 | reject('negativeClick'); 129 | }, 130 | }); 131 | }), 132 | ); 133 | if (positiveRes) { 134 | const [deleteGameModeErr] = await $to(deviceApi.deleteGameMode()); 135 | if (deleteGameModeErr) { 136 | modal.create({ 137 | title: '操作失败', 138 | type: 'error', 139 | preset: 'dialog', 140 | content: () =>

无法修改模块配置文件,详情请查看日志记录~

, 141 | negativeText: '确定', 142 | }); 143 | return; 144 | } 145 | if (value) { 146 | const [addGameModeErr] = await $to(deviceApi.addGameMode()); 147 | if (addGameModeErr) { 148 | modal.create({ 149 | title: '操作失败', 150 | type: 'error', 151 | preset: 'dialog', 152 | content: () =>

无法修改模块配置文件,详情请查看日志记录~

, 153 | negativeText: '确定', 154 | }); 155 | return; 156 | } 157 | } 158 | modal.create({ 159 | title: '操作成功', 160 | type: 'success', 161 | preset: 'dialog', 162 | content: () => ( 163 |

164 | 好耶w,已经成功{value ? '开启' : '关闭'}游戏显示布局~实际生效还需要重启设备,确定要重启吗? 165 |

166 | ), 167 | positiveText: '立即重启', 168 | negativeText: '稍后手动重启', 169 | onPositiveClick() { 170 | deviceApi 171 | .rebootDevice() 172 | .catch(err => { 173 | modal.create({ 174 | title: '操作失败', 175 | type: 'error', 176 | preset: 'dialog', 177 | content: () =>

无法重启设备,详情请查看日志记录~

, 178 | negativeText: '确定', 179 | }); 180 | return; 181 | }); 182 | }, 183 | }); 184 | } 185 | }; 186 | 187 | onMounted(() => {}); 188 | 189 | return { 190 | vaildModuleVersion, 191 | changeGameMode, 192 | isSupportGameMode, 193 | }; 194 | } 195 | -------------------------------------------------------------------------------- /src/hooks/useHideGestureLine.tsx: -------------------------------------------------------------------------------- 1 | import { ref, computed, onMounted, nextTick } from 'vue'; 2 | import { useDeviceStore } from '@/stores/device'; 3 | import $to from 'await-to-js'; 4 | import { 5 | NButton, 6 | createDiscreteApi, 7 | darkTheme, 8 | lightTheme, 9 | type ConfigProviderProps, 10 | type DataTableColumns, 11 | type NInput, 12 | } from 'naive-ui'; 13 | import * as deviceApi from '@/apis/deviceApi'; 14 | 15 | export type IsHideGestureLine = 0 | 1; 16 | 17 | export function useHideGestureLine() { 18 | const deviceStore = useDeviceStore(); 19 | const configProviderPropsRef = computed(() => ({ 20 | theme: deviceStore.isDarkMode ? darkTheme : lightTheme, 21 | })); 22 | 23 | const { message, modal } = createDiscreteApi(['message', 'modal'], { 24 | configProviderProps: configProviderPropsRef, 25 | }); 26 | 27 | const isInit = ref(false); 28 | 29 | const currentIsHideGestureLine = ref(0); 30 | 31 | 32 | const changeIsHideGestureLine = async (value: boolean) => { 33 | const [setHideGestureLineErr, setHideGestureLineRes] = await $to( 34 | deviceApi.setHideGestureLine(value ? 1 : 0), 35 | ); 36 | if (setHideGestureLineErr) { 37 | modal.create({ 38 | title: '操作失败', 39 | type: 'error', 40 | preset: 'dialog', 41 | content: () =>

修改失败,详情请查看日志记录~

, 42 | negativeText: '确定', 43 | }); 44 | } else { 45 | if (value) { 46 | const [addIsHideGestureLineErr, addIsHideGestureLineRes] = await $to( 47 | deviceApi.addIsHideGestureLine(), 48 | ); 49 | if (addIsHideGestureLineErr) { 50 | modal.create({ 51 | title: '操作失败', 52 | type: 'error', 53 | preset: 'dialog', 54 | content: () =>

修改失败,详情请查看日志记录~

, 55 | negativeText: '确定', 56 | }); 57 | } else { 58 | modal.create({ 59 | title: '修改成功', 60 | type: 'success', 61 | preset: 'dialog', 62 | content: () =>

好耶w!隐藏手势提示线(小白条)成功,请知晓,隐藏手势提示线(小白条)的情况下,旋转建议提示按钮也将变成不可用的状态~

, 63 | positiveText: '确定', 64 | }); 65 | currentIsHideGestureLine.value = 1; 66 | } 67 | } else { 68 | const [removeIsHideGestureLineErr, removeIsHideGestureLineRes] = await $to( 69 | deviceApi.removeIsHideGestureLine(), 70 | ); 71 | if (removeIsHideGestureLineErr) { 72 | modal.create({ 73 | title: '操作失败', 74 | type: 'error', 75 | preset: 'dialog', 76 | content: () =>

修改失败,详情请查看日志记录~

, 77 | negativeText: '确定', 78 | }); 79 | } else { 80 | modal.create({ 81 | title: '修改成功', 82 | type: 'success', 83 | preset: 'dialog', 84 | content: () =>

好耶w!已经将手势提示线(小白条)调整为显示状态~

, 85 | positiveText: '确定', 86 | }); 87 | currentIsHideGestureLine.value = 0; 88 | } 89 | } 90 | } 91 | }; 92 | 93 | const fetchData = async () => { 94 | const [, getHideGestureLineRes] = await $to(deviceApi.getHideGestureLine()); 95 | if (Number(getHideGestureLineRes)) { 96 | currentIsHideGestureLine.value = 1; 97 | } 98 | isInit.value = true; 99 | } 100 | 101 | 102 | onMounted(() => { 103 | setTimeout(() => { 104 | fetchData(); // 确保 UI 先渲染,再执行耗时操作 105 | },0); 106 | }); 107 | 108 | return { 109 | currentIsHideGestureLine, 110 | changeIsHideGestureLine, 111 | isInit 112 | }; 113 | } 114 | -------------------------------------------------------------------------------- /src/hooks/useInVisibleMode.tsx: -------------------------------------------------------------------------------- 1 | import { ref, computed, onMounted, nextTick } from 'vue'; 2 | import { useDeviceStore } from '@/stores/device'; 3 | import $to from 'await-to-js'; 4 | import { 5 | NButton, 6 | createDiscreteApi, 7 | darkTheme, 8 | lightTheme, 9 | type ConfigProviderProps, 10 | type DataTableColumns, 11 | type NInput, 12 | } from 'naive-ui'; 13 | import * as deviceApi from '@/apis/deviceApi'; 14 | 15 | export type IsInvisibleMode = 0 | 1; 16 | 17 | export function useInVisibleMode() { 18 | const deviceStore = useDeviceStore(); 19 | const configProviderPropsRef = computed(() => ({ 20 | theme: deviceStore.isDarkMode ? darkTheme : lightTheme, 21 | })); 22 | 23 | const { message, modal } = createDiscreteApi(['message', 'modal'], { 24 | configProviderProps: configProviderPropsRef, 25 | }); 26 | 27 | const currentIsInVisibleMode = ref(0); 28 | 29 | 30 | const changeIsInvisibleMode = async (value: boolean) => { 31 | const [setInVisibleModeErr, setInVisibleModeRes] = await $to( 32 | deviceApi.setInVisibleMode(value ? 1 : 0), 33 | ); 34 | if (setInVisibleModeErr) { 35 | modal.create({ 36 | title: '操作失败', 37 | type: 'error', 38 | preset: 'dialog', 39 | content: () =>

修改失败,详情请查看日志记录~

, 40 | negativeText: '确定', 41 | }); 42 | } else { 43 | currentIsInVisibleMode.value = value ? 1 : 0; 44 | } 45 | }; 46 | 47 | const fetchData = async () => { 48 | const [, getInVisibleModeRes] = await $to(deviceApi.getInVisibleMode()); 49 | if (Number(getInVisibleModeRes)) { 50 | currentIsInVisibleMode.value = 1; 51 | } 52 | } 53 | 54 | 55 | onMounted(() => { 56 | setTimeout(() => { 57 | fetchData(); // 确保 UI 先渲染,再执行耗时操作 58 | },0); 59 | }); 60 | 61 | return { 62 | currentIsInVisibleMode, 63 | changeIsInvisibleMode 64 | }; 65 | } 66 | -------------------------------------------------------------------------------- /src/hooks/useInstalledAppNames.ts: -------------------------------------------------------------------------------- 1 | import { ref, computed, onMounted } from 'vue'; 2 | import { useDeviceStore } from '@/stores/device' 3 | import * as deviceApi from '@/apis/deviceApi'; 4 | import $to from 'await-to-js' 5 | export type InstallAppNameListDictionary = Record; 6 | 7 | 8 | export function useInstalledAppNames() { 9 | const deviceStore = useDeviceStore(); 10 | 11 | 12 | const procceelocked = ref(false); 13 | 14 | const loading = ref(false); 15 | 16 | const getList = async () => { 17 | loading.value = true; 18 | if (procceelocked.value) { 19 | loading.value = false; 20 | return Promise.reject('已经存在任务了!') 21 | } 22 | return new Promise(async (resolve,reject) => { 23 | procceelocked.value = true; 24 | setTimeout(async () => { 25 | const [getListErr,getListRes] = await $to(deviceApi.getInstalledAppNameList()) 26 | if (getListErr) { 27 | procceelocked.value = false; 28 | loading.value = false; 29 | reject(getListErr) 30 | } 31 | if (getListRes) { 32 | const lines = getListRes.trim().split('\n').filter(line => line); 33 | 34 | // 创建目标对象 35 | const result:InstallAppNameListDictionary = {}; 36 | 37 | // 遍历每一行,将包名和应用名称添加到对象中 38 | lines.forEach(line => { 39 | const [ , packageName, appName ] = line.split(','); 40 | result[packageName] = appName; 41 | }); 42 | deviceStore.installedAppNameList = result; 43 | procceelocked.value = false; 44 | loading.value = false; 45 | resolve(result) 46 | } 47 | },0) 48 | }) 49 | } 50 | 51 | 52 | return { 53 | loading, 54 | procceelocked, 55 | installedAppNameList: deviceStore.installedAppNameList, 56 | getList 57 | } 58 | 59 | 60 | } -------------------------------------------------------------------------------- /src/hooks/useMiuiCursorStyle.tsx: -------------------------------------------------------------------------------- 1 | import { ref, computed, onMounted, nextTick } from 'vue'; 2 | import { useDeviceStore } from '@/stores/device'; 3 | import $to from 'await-to-js'; 4 | import { 5 | NButton, 6 | createDiscreteApi, 7 | darkTheme, 8 | lightTheme, 9 | type ConfigProviderProps, 10 | type DataTableColumns, 11 | type NInput, 12 | } from 'naive-ui'; 13 | import * as deviceApi from '@/apis/deviceApi'; 14 | 15 | export type miuiCursorStyleType = 3 | 1 | 0; 16 | 17 | export type miuiAutoStartCursorStyleType = 3 | 1 | 0 | undefined; 18 | 19 | export function useMiuiCursorStyle() { 20 | const deviceStore = useDeviceStore(); 21 | const loading = ref(true); 22 | const isInit = ref(false); 23 | const configProviderPropsRef = computed(() => ({ 24 | theme: deviceStore.isDarkMode ? darkTheme : lightTheme, 25 | })); 26 | 27 | const { message, modal } = createDiscreteApi(['message', 'modal'], { 28 | configProviderProps: configProviderPropsRef, 29 | }); 30 | 31 | const currentMiuiCursorStyleType = ref(3); 32 | 33 | const currentAutoStartMiuiCursorStyleType = ref(); 34 | 35 | const changeMiuiCursorStyleType = async (value: miuiCursorStyleType) => { 36 | const [setMiuiCursorStyleTypeErr, setMiuiCursorStyleTypeRes] = await $to( 37 | deviceApi.setMiuiCursorStyleType(value), 38 | ); 39 | if (setMiuiCursorStyleTypeErr) { 40 | modal.create({ 41 | title: '操作失败', 42 | type: 'error', 43 | preset: 'dialog', 44 | content: () =>

修改失败,详情请查看日志记录~

, 45 | negativeText: '确定', 46 | }); 47 | } else { 48 | currentMiuiCursorStyleType.value = value; 49 | } 50 | }; 51 | 52 | const changeAutoStartMiuiCursorStyleType = async (type: boolean) => { 53 | if (type) { 54 | const [removeMiuiCursorStyleTypeErr, removeMiuiCursorStyleTypeRes] = await $to( 55 | deviceApi.removeAutoStartMiuiCursorStyleType(), 56 | ); 57 | if (removeMiuiCursorStyleTypeErr) { 58 | modal.create({ 59 | title: '操作失败', 60 | type: 'error', 61 | preset: 'dialog', 62 | content: () =>

修改失败,详情请查看日志记录~

, 63 | negativeText: '确定', 64 | }); 65 | return; 66 | } else { 67 | const [addMiuiCursorStyleTypeErr, addMiuiCursorStyleTypeRes] = await $to( 68 | deviceApi.addAutoStartMiuiCursorStyleType(currentMiuiCursorStyleType.value), 69 | ); 70 | if (addMiuiCursorStyleTypeErr) { 71 | modal.create({ 72 | title: '操作失败', 73 | type: 'error', 74 | preset: 'dialog', 75 | content: () =>

修改失败,详情请查看日志记录~

, 76 | negativeText: '确定', 77 | }); 78 | } else { 79 | modal.create({ 80 | title: '添加自启动成功', 81 | type: 'success', 82 | preset: 'dialog', 83 | content: () =>

添加「鼠标光标样式」开机自启动配置成功,后续请通过模块 Web UI 修改「鼠标光标样式」,系统设置内的修改会在重启后失效~

, 84 | positiveText: '确定', 85 | }); 86 | currentAutoStartMiuiCursorStyleType.value = currentMiuiCursorStyleType.value 87 | } 88 | } 89 | } else { 90 | const [removeMiuiCursorStyleTypeErr, removeMiuiCursorStyleTypeRes] = await $to( 91 | deviceApi.removeAutoStartMiuiCursorStyleType(), 92 | ); 93 | if (removeMiuiCursorStyleTypeErr) { 94 | modal.create({ 95 | title: '操作失败', 96 | type: 'error', 97 | preset: 'dialog', 98 | content: () =>

修改失败,详情请查看日志记录~

, 99 | negativeText: '确定', 100 | }); 101 | } else { 102 | modal.create({ 103 | title: '取消自启动成功', 104 | type: 'success', 105 | preset: 'dialog', 106 | content: () =>

取消「鼠标光标样式」开机自启动配置成功~

, 107 | positiveText: '确定', 108 | }); 109 | currentAutoStartMiuiCursorStyleType.value = undefined 110 | } 111 | } 112 | }; 113 | 114 | const fetchData = async () => { 115 | const [ 116 | [, getMiuiCursorStyleTypeRes], 117 | [, getAutoStartMiuiCursorStyleTypeRes] 118 | ] = await Promise.all([ 119 | $to(deviceApi.getMiuiCursorStyleType()), 120 | $to(deviceApi.getAutoStartMiuiCursorStyleType()) 121 | ]); 122 | 123 | // 赋值 124 | if (Number(getMiuiCursorStyleTypeRes)) { 125 | currentMiuiCursorStyleType.value = Number(getMiuiCursorStyleTypeRes) as miuiCursorStyleType; 126 | } 127 | if (Number(getAutoStartMiuiCursorStyleTypeRes)) { 128 | currentAutoStartMiuiCursorStyleType.value = Number(getAutoStartMiuiCursorStyleTypeRes) as miuiAutoStartCursorStyleType; 129 | } 130 | isInit.value = true; 131 | loading.value = false; 132 | }; 133 | 134 | 135 | onMounted(() => { 136 | setTimeout(() => { 137 | fetchData(); // 确保 UI 先渲染,再执行耗时操作 138 | },0); 139 | }); 140 | 141 | return { 142 | currentMiuiCursorStyleType, 143 | changeMiuiCursorStyleType, 144 | currentAutoStartMiuiCursorStyleType, 145 | changeAutoStartMiuiCursorStyleType, 146 | isInit, 147 | loading 148 | }; 149 | } 150 | -------------------------------------------------------------------------------- /src/hooks/useMiuiDesktopMode.tsx: -------------------------------------------------------------------------------- 1 | import { ref, computed, onMounted, nextTick } from 'vue'; 2 | import { useDeviceStore } from '@/stores/device'; 3 | import $to from 'await-to-js'; 4 | import { 5 | NButton, 6 | createDiscreteApi, 7 | darkTheme, 8 | lightTheme, 9 | type ConfigProviderProps, 10 | type DataTableColumns, 11 | type NInput, 12 | } from 'naive-ui'; 13 | import * as deviceApi from '@/apis/deviceApi'; 14 | 15 | export type KeyboardMode = 0 | 1 | 2; 16 | export function useMiuiDesktopMode() { 17 | const deviceStore = useDeviceStore(); 18 | const configProviderPropsRef = computed(() => ({ 19 | theme: deviceStore.isDarkMode ? darkTheme : lightTheme, 20 | })); 21 | 22 | const { message, modal } = createDiscreteApi(['message', 'modal'], { 23 | configProviderProps: configProviderPropsRef, 24 | }); 25 | 26 | const isInit = ref(false); 27 | 28 | const currentMiuiDktMode = ref(false); 29 | 30 | const changeMiuiDktMode = async (value: boolean) => { 31 | const [putCurrentPenEnableErr, putCurrentPenEnableRes] = await $to( 32 | deviceApi.putCurrentMiuiDktMode(value ? 1 : 'null') 33 | ); 34 | if (putCurrentPenEnableErr) { 35 | modal.create({ 36 | title: '操作失败', 37 | type: 'error', 38 | preset: 'dialog', 39 | content: () =>

修改失败,详情请查看日志记录~

, 40 | negativeText: '确定', 41 | }); 42 | } else { 43 | currentMiuiDktMode.value = value; 44 | } 45 | } 46 | 47 | const changeMiuiDesktopModeEnabled = async () => { 48 | const [negativeRes, positiveRes] = await $to( 49 | new Promise((resolve, reject) => { 50 | modal.create({ 51 | title: '想激活工作台模式吗?', 52 | type: 'info', 53 | preset: 'dialog', 54 | content: () => ( 55 |
56 |

57 | 激活{' '} 58 | 59 | 工作台模式 60 | {' '} 61 | 后需要设备重启才会生效~ 62 |

63 |

是否继续激活?

64 |
65 | ), 66 | positiveText: '确认', 67 | negativeText: '取消', 68 | onPositiveClick: () => { 69 | resolve('positiveClick'); 70 | }, 71 | onNegativeClick: () => { 72 | reject('negativeClick'); 73 | }, 74 | }); 75 | }), 76 | ); 77 | if (positiveRes) { 78 | const [removeIsAddDesktopModeEnabledErr, removeIsAddDesktopModeEnabledRes] = await $to( 79 | deviceApi.removeIsAddDesktopModeEnabled(), 80 | ); 81 | if (removeIsAddDesktopModeEnabledErr) { 82 | modal.create({ 83 | title: '操作失败', 84 | type: 'error', 85 | preset: 'dialog', 86 | content: () =>

修改失败,详情请查看日志记录~

, 87 | negativeText: '确定', 88 | }); 89 | return; 90 | } 91 | const [addIsAddDesktopModeEnabledErr, addIsAddDesktopModeEnabledRes] = await $to( 92 | deviceApi.addIsAddDesktopModeEnabled(), 93 | ); 94 | if (addIsAddDesktopModeEnabledErr) { 95 | modal.create({ 96 | title: '操作失败', 97 | type: 'error', 98 | preset: 'dialog', 99 | content: () =>

修改失败,详情请查看日志记录~

, 100 | negativeText: '确定', 101 | }); 102 | return; 103 | } 104 | const [removeMiuiDesktopModeEnabledErr, removeMiuiDesktopModeEnabledRes] = await $to( 105 | deviceApi.removeMiuiDesktopModeEnabled(), 106 | ); 107 | if (removeMiuiDesktopModeEnabledErr) { 108 | modal.create({ 109 | title: '操作失败', 110 | type: 'error', 111 | preset: 'dialog', 112 | content: () =>

修改失败,详情请查看日志记录~

, 113 | negativeText: '确定', 114 | }); 115 | return; 116 | } 117 | const [addMiuiDesktopModeEnabledErr, addMiuiDesktopModeEnabledRes] = await $to( 118 | deviceApi.addMiuiDesktopModeEnabled(), 119 | ); 120 | if (addMiuiDesktopModeEnabledErr) { 121 | modal.create({ 122 | title: '操作失败', 123 | type: 'error', 124 | preset: 'dialog', 125 | content: () =>

修改失败,详情请查看日志记录~

, 126 | negativeText: '确定', 127 | }); 128 | return; 129 | } 130 | modal.create({ 131 | title: '操作成功', 132 | type: 'success', 133 | preset: 'dialog', 134 | content: () => ( 135 |

136 | 好耶w,已经成功激活工作台模式~实际生效还需要重启设备,确定要重启吗? 137 |

138 | ), 139 | positiveText: '立即重启', 140 | negativeText: '稍后手动重启', 141 | onPositiveClick() { 142 | deviceApi 143 | .rebootDevice() 144 | .catch(err => { 145 | modal.create({ 146 | title: '操作失败', 147 | type: 'error', 148 | preset: 'dialog', 149 | content: () =>

无法重启设备,详情请查看日志记录~

, 150 | negativeText: '确定', 151 | }); 152 | return; 153 | }); 154 | }, 155 | }); 156 | } 157 | }; 158 | 159 | const fetchData = async () => { 160 | if (deviceStore.enabledMiuiDesktopMode) { 161 | const [, getCurrentMiuiDktModeResolve] = await $to(deviceApi.getCurrentMiuiDktMode()); 162 | 163 | if (Number(getCurrentMiuiDktModeResolve) === 1) { 164 | currentMiuiDktMode.value = true; 165 | } 166 | } 167 | isInit.value = true; 168 | } 169 | 170 | onMounted(() => { 171 | setTimeout(() => { 172 | fetchData(); // 确保 UI 先渲染,再执行耗时操作 173 | },0); 174 | }); 175 | 176 | return { 177 | currentMiuiDktMode, 178 | changeMiuiDktMode, 179 | changeMiuiDesktopModeEnabled, 180 | isInit 181 | }; 182 | } 183 | -------------------------------------------------------------------------------- /src/hooks/useMouseGestureNaturalscroll.tsx: -------------------------------------------------------------------------------- 1 | import { ref, computed, onMounted, nextTick } from 'vue'; 2 | import { useDeviceStore } from '@/stores/device'; 3 | import $to from 'await-to-js'; 4 | import { 5 | NButton, 6 | createDiscreteApi, 7 | darkTheme, 8 | lightTheme, 9 | type ConfigProviderProps, 10 | type DataTableColumns, 11 | type NInput, 12 | } from 'naive-ui'; 13 | import * as deviceApi from '@/apis/deviceApi'; 14 | 15 | export type IsInvisibleMode = 0 | 1; 16 | 17 | export function useMouseGestureNaturalscroll() { 18 | const deviceStore = useDeviceStore(); 19 | const configProviderPropsRef = computed(() => ({ 20 | theme: deviceStore.isDarkMode ? darkTheme : lightTheme, 21 | })); 22 | 23 | const loading = ref(true); 24 | const isInit = ref(false); 25 | 26 | const { message, modal } = createDiscreteApi(['message', 'modal'], { 27 | configProviderProps: configProviderPropsRef, 28 | }); 29 | 30 | const currentMouseGestureNaturalscroll = ref(0); 31 | 32 | const changeMouseGestureNaturalscroll = async (value: boolean) => { 33 | const [setMouseGestureNaturalscrollErr, setMouseGestureNaturalscrollRes] = await $to( 34 | deviceApi.setMouseGestureNaturalscroll(value ? 1 : 0), 35 | ); 36 | if (setMouseGestureNaturalscrollErr) { 37 | modal.create({ 38 | title: '操作失败', 39 | type: 'error', 40 | preset: 'dialog', 41 | content: () =>

修改失败,详情请查看日志记录~

, 42 | negativeText: '确定', 43 | }); 44 | } else { 45 | currentMouseGestureNaturalscroll.value = value ? 1 : 0; 46 | } 47 | }; 48 | 49 | const fetchData = async () => { 50 | const [, getMouseGestureNaturalscrollRes] = await $to(deviceApi.getMouseGestureNaturalscroll()); 51 | if (Number(getMouseGestureNaturalscrollRes)) { 52 | currentMouseGestureNaturalscroll.value = 1; 53 | } 54 | isInit.value = true; 55 | loading.value = false; 56 | }; 57 | 58 | onMounted(() => { 59 | setTimeout(() => { 60 | fetchData(); // 确保 UI 先渲染,再执行耗时操作 61 | },0); 62 | }); 63 | 64 | return { 65 | currentMouseGestureNaturalscroll, 66 | changeMouseGestureNaturalscroll, 67 | isInit, 68 | loading 69 | }; 70 | } 71 | -------------------------------------------------------------------------------- /src/hooks/useOS2InstallModuleTips.tsx: -------------------------------------------------------------------------------- 1 | import { ref, computed, onMounted } from 'vue'; 2 | import { useDeviceStore } from '@/stores/device'; 3 | import $to from 'await-to-js'; 4 | import { 5 | NButton, 6 | createDiscreteApi, 7 | darkTheme, 8 | lightTheme, 9 | type ConfigProviderProps, 10 | type DataTableColumns, 11 | type NInput, 12 | } from 'naive-ui'; 13 | import * as deviceApi from '@/apis/deviceApi'; 14 | 15 | export function useOS2InstallModuleTips() { 16 | const deviceStore = useDeviceStore(); 17 | const configProviderPropsRef = computed(() => ({ 18 | theme: deviceStore.isDarkMode ? darkTheme : lightTheme, 19 | })); 20 | 21 | const { message, modal } = createDiscreteApi(['message', 'modal'], { 22 | configProviderProps: configProviderPropsRef, 23 | }); 24 | 25 | const change = async (value: boolean) => { 26 | const [negativeRes, positiveRes] = await $to( 27 | new Promise((resolve, reject) => { 28 | modal.create({ 29 | title: value ? '想禁用模块使用须知吗?' : '想开启模块使用须知吗?', 30 | type: 'info', 31 | preset: 'dialog', 32 | content: () => ( 33 |
34 | {!value && ( 35 |

36 | 开启{' '} 37 | 39 | 模块使用须知 40 | {' '} 41 | 后,模块每次安装过程会带使用须知的{' '} 42 | 44 | 强提醒 45 | {' '} 46 | ,需要设备重启才会生效,确定要继续开启吗? 47 |

48 | )} 49 | {value && ( 50 |

51 | 禁用{' '} 52 | 54 | 模块使用须知 55 | {' '} 56 | 后,模块每次安装过程将会{' '} 57 | 59 | 静默安装 60 | {' '} 61 | ,需要设备重启才会生效,确定要继续禁用吗? 62 |

63 | )} 64 |
65 | ), 66 | positiveText: '确定', 67 | negativeText: '我再想想', 68 | onPositiveClick: () => { 69 | resolve('positiveClick'); 70 | }, 71 | onNegativeClick: () => { 72 | reject('negativeClick'); 73 | }, 74 | }); 75 | }), 76 | ); 77 | if (positiveRes) { 78 | const [removeDisabledOS2InstallModuleTipsErr] = await $to(deviceApi.removeDisabledOS2InstallModuleTips()); 79 | if (removeDisabledOS2InstallModuleTipsErr) { 80 | modal.create({ 81 | title: '操作失败', 82 | type: 'error', 83 | preset: 'dialog', 84 | content: () =>

无法修改模块使用须知,详情请查看日志记录~

, 85 | negativeText: '确定', 86 | }); 87 | return; 88 | } 89 | if (value) { 90 | const [addDisabledOS2InstallModuleTipsErr] = await $to(deviceApi.addDisabledOS2InstallModuleTips()); 91 | if (addDisabledOS2InstallModuleTipsErr) { 92 | modal.create({ 93 | title: '操作失败', 94 | type: 'error', 95 | preset: 'dialog', 96 | content: () =>

无法修改模块使用须知,详情请查看日志记录~

, 97 | negativeText: '确定', 98 | }); 99 | return; 100 | } 101 | } 102 | modal.create({ 103 | title: '操作成功', 104 | type: 'success', 105 | preset: 'dialog', 106 | content: () => ( 107 |

108 | 好耶w,已经成功{value ? '禁用' : '开启'}模块使用须知~实际生效还需要重启设备,确定要重启吗? 109 |

110 | ), 111 | positiveText: '立即重启', 112 | negativeText: '稍后手动重启', 113 | onPositiveClick() { 114 | deviceApi 115 | .rebootDevice() 116 | .catch(err => { 117 | modal.create({ 118 | title: '操作失败', 119 | type: 'error', 120 | preset: 'dialog', 121 | content: () =>

无法重启设备,详情请查看日志记录~

, 122 | negativeText: '确定', 123 | }); 124 | return; 125 | }); 126 | }, 127 | }); 128 | } 129 | }; 130 | 131 | onMounted(() => {}); 132 | 133 | return { 134 | change, 135 | }; 136 | } 137 | -------------------------------------------------------------------------------- /src/hooks/usePointerSpeed.tsx: -------------------------------------------------------------------------------- 1 | import { ref, computed, onMounted, nextTick } from 'vue'; 2 | import { useDeviceStore } from '@/stores/device'; 3 | import $to from 'await-to-js'; 4 | import { 5 | NButton, 6 | createDiscreteApi, 7 | darkTheme, 8 | lightTheme, 9 | type ConfigProviderProps, 10 | type DataTableColumns, 11 | type NInput, 12 | } from 'naive-ui'; 13 | import * as deviceApi from '@/apis/deviceApi'; 14 | 15 | export function usePointerSpeed() { 16 | const deviceStore = useDeviceStore(); 17 | const configProviderPropsRef = computed(() => ({ 18 | theme: deviceStore.isDarkMode ? darkTheme : lightTheme, 19 | })); 20 | 21 | const loading = ref(true); 22 | const isInit = ref(false); 23 | 24 | const { message, modal } = createDiscreteApi(['message', 'modal'], { 25 | configProviderProps: configProviderPropsRef, 26 | }); 27 | 28 | const currentPointerSpeed = ref(0); 29 | 30 | 31 | const changePointerSpeed = async (value: number) => { 32 | const [setPointerSpeedErr, setPointerSpeedRes] = await $to( 33 | deviceApi.setPointerSpeed(value) 34 | ); 35 | if (setPointerSpeedErr) { 36 | modal.create({ 37 | title: '操作失败', 38 | type: 'error', 39 | preset: 'dialog', 40 | content: () =>

修改失败,详情请查看日志记录~

, 41 | negativeText: '确定', 42 | }); 43 | } else { 44 | currentPointerSpeed.value = value 45 | } 46 | }; 47 | 48 | const fetchData = async () => { 49 | const [, getPointerSpeedRes] = await $to(deviceApi.getPointerSpeed()); 50 | if (Number(getPointerSpeedRes)) { 51 | currentPointerSpeed.value = Number(getPointerSpeedRes); 52 | } 53 | isInit.value = true; 54 | loading.value = false; 55 | } 56 | 57 | 58 | onMounted(() => { 59 | setTimeout(() => { 60 | fetchData(); // 确保 UI 先渲染,再执行耗时操作 61 | },0); 62 | }); 63 | 64 | return { 65 | changePointerSpeed, 66 | currentPointerSpeed, 67 | isInit, 68 | loading 69 | }; 70 | } 71 | -------------------------------------------------------------------------------- /src/hooks/useQQDoc.tsx: -------------------------------------------------------------------------------- 1 | import { ref, computed, onMounted } from 'vue'; 2 | import { useDeviceStore } from '@/stores/device'; 3 | import * as deviceApi from '@/apis/deviceApi'; 4 | import { createDiscreteApi, darkTheme, lightTheme, type ConfigProviderProps } from 'naive-ui'; 5 | import $to from 'await-to-js'; 6 | import { divide } from 'lodash-es'; 7 | 8 | export function useQQDoc() { 9 | const configProviderPropsRef = computed(() => ({ 10 | theme: deviceStore.isDarkMode ? darkTheme : lightTheme, 11 | })); 12 | const deviceStore = useDeviceStore(); 13 | const isSupportGameMode = computed(() => { 14 | return deviceStore.miuiCompatEnable && deviceStore.androidTargetSdk && deviceStore.androidTargetSdk > 31; 15 | }); 16 | 17 | const { message, modal } = createDiscreteApi(['message', 'modal'], { 18 | configProviderProps: configProviderPropsRef, 19 | }); 20 | 21 | const getModal = async () => { 22 | modal.create({ 23 | title: '应用适配收集表', 24 | type: 'info', 25 | preset: 'dialog', 26 | content: () => ( 27 |
28 |

您可以通过收集表提交您的应用适配需求OwO,提交前请认真阅读须知~

29 |

https://docs.qq.com/form/page/DRUhJQkhzSnp6dWhm

30 |
31 | ), 32 | positiveText: '复制收集表链接到剪切板', 33 | negativeText: '取消', 34 | onPositiveClick: () => { 35 | navigator.clipboard.writeText(`https://docs.qq.com/form/page/DRUhJQkhzSnp6dWhm`); 36 | }, 37 | }); 38 | }; 39 | 40 | onMounted(() => {}); 41 | 42 | return { 43 | getModal, 44 | }; 45 | } 46 | -------------------------------------------------------------------------------- /src/hooks/useRealQuantity.tsx: -------------------------------------------------------------------------------- 1 | import { ref, computed, onMounted, reactive, watchEffect, onUnmounted, nextTick } from 'vue'; 2 | import { useDeviceStore } from '@/stores/device'; 3 | import $to from 'await-to-js'; 4 | import { 5 | NButton, 6 | createDiscreteApi, 7 | darkTheme, 8 | lightTheme, 9 | type ConfigProviderProps, 10 | type DataTableColumns, 11 | type NInput, 12 | } from 'naive-ui'; 13 | import * as deviceApi from '@/apis/deviceApi'; 14 | 15 | export function useRealQuantity() { 16 | const deviceStore = useDeviceStore(); 17 | const configProviderPropsRef = computed(() => ({ 18 | theme: deviceStore.isDarkMode ? darkTheme : lightTheme, 19 | })); 20 | 21 | const { message, modal } = createDiscreteApi(['message', 'modal'], { 22 | configProviderProps: configProviderPropsRef, 23 | }); 24 | 25 | interface realQuantityInfo { 26 | current: number 27 | autoReload: boolean 28 | timer: number 29 | reload: () => Promise 30 | interval: number | null 31 | } 32 | 33 | const qcomBatteryFg1RSocInfo = reactive({ 34 | current: 0, 35 | autoReload: false, 36 | timer: 3, 37 | reload: async() => { 38 | const [, getQcomBatteryFg1RSocRes] = await $to(deviceApi.getQcomBatteryFg1RSoc()); 39 | if (getQcomBatteryFg1RSocRes) { 40 | qcomBatteryFg1RSocInfo.current = Number(getQcomBatteryFg1RSocRes); 41 | } 42 | }, 43 | interval: null 44 | }) 45 | 46 | const capacityRawInfo = reactive({ 47 | current: 0, 48 | autoReload: false, 49 | timer: 3, 50 | reload: async() => { 51 | const [, getCapacityRawRes] = await $to(deviceApi.getCapacityRaw()); 52 | if (getCapacityRawRes) { 53 | capacityRawInfo.current = Number(getCapacityRawRes); 54 | } 55 | }, 56 | interval: null 57 | }) 58 | 59 | const setupAutoReload = (info: realQuantityInfo) => { 60 | // Clear the existing interval if any 61 | if (info.interval) { 62 | clearInterval(info.interval); 63 | info.interval = null; 64 | } 65 | 66 | // Set a new interval if autoReload is true 67 | if (info.autoReload) { 68 | info.interval = setInterval(() => { 69 | info.reload(); 70 | }, info.timer * 1000); 71 | } 72 | }; 73 | 74 | watchEffect(() => setupAutoReload(qcomBatteryFg1RSocInfo)); 75 | watchEffect(() => setupAutoReload(capacityRawInfo)); 76 | 77 | onMounted(() => { 78 | setTimeout(() => { 79 | qcomBatteryFg1RSocInfo.reload() 80 | capacityRawInfo.reload() 81 | },0); 82 | }); 83 | 84 | onUnmounted(() => { 85 | // Clear intervals when the component is unmounted 86 | setTimeout(() => { 87 | if (qcomBatteryFg1RSocInfo.interval !== null) clearInterval(qcomBatteryFg1RSocInfo.interval); 88 | if (capacityRawInfo.interval !== null) clearInterval(capacityRawInfo.interval); 89 | },0); 90 | }); 91 | 92 | return { 93 | qcomBatteryFg1RSocInfo, 94 | capacityRawInfo, 95 | }; 96 | } 97 | -------------------------------------------------------------------------------- /src/hooks/useShowNotificationIconNum.tsx: -------------------------------------------------------------------------------- 1 | import { ref, computed, onMounted, nextTick } from 'vue'; 2 | import { useDeviceStore } from '@/stores/device'; 3 | import $to from 'await-to-js'; 4 | import { 5 | NButton, 6 | createDiscreteApi, 7 | darkTheme, 8 | lightTheme, 9 | type ConfigProviderProps, 10 | type DataTableColumns, 11 | type NInput, 12 | } from 'naive-ui'; 13 | import * as deviceApi from '@/apis/deviceApi'; 14 | 15 | export type KeyboardMode = 0 | 1 | 2; 16 | export function useShowNotificationIcon() { 17 | const deviceStore = useDeviceStore(); 18 | const configProviderPropsRef = computed(() => ({ 19 | theme: deviceStore.isDarkMode ? darkTheme : lightTheme, 20 | })); 21 | 22 | const { message, modal } = createDiscreteApi(['message', 'modal'], { 23 | configProviderProps: configProviderPropsRef, 24 | }); 25 | 26 | const currentNum = ref(3); 27 | 28 | const isInit = ref(false); 29 | 30 | const changeEnableMode = async (value: boolean) => { 31 | if (value) { 32 | const [removeIsEnableShowNotificationIconNumErr] = await $to( 33 | deviceApi.removeIsEnableShowNotificationIconNum(), 34 | ); 35 | if (removeIsEnableShowNotificationIconNumErr) { 36 | modal.create({ 37 | title: '操作失败', 38 | type: 'error', 39 | preset: 'dialog', 40 | content: () =>

修改失败,详情请查看日志记录~

, 41 | negativeText: '确定', 42 | }); 43 | return; 44 | } 45 | const [addIsEnableShowNotificationIconNumErr] = await $to( 46 | deviceApi.addIsEnableShowNotificationIconNum(), 47 | ); 48 | if (addIsEnableShowNotificationIconNumErr) { 49 | modal.create({ 50 | title: '操作失败', 51 | type: 'error', 52 | preset: 'dialog', 53 | content: () =>

修改失败,详情请查看日志记录~

, 54 | negativeText: '确定', 55 | }); 56 | return; 57 | } 58 | deviceStore.isEnableShowNotificationIconNum = true; 59 | } else { 60 | const [removeIsEnableShowNotificationIconNumErr] = await $to( 61 | deviceApi.removeIsEnableShowNotificationIconNum(), 62 | ); 63 | if (removeIsEnableShowNotificationIconNumErr) { 64 | modal.create({ 65 | title: '操作失败', 66 | type: 'error', 67 | preset: 'dialog', 68 | content: () =>

修改失败,详情请查看日志记录~

, 69 | negativeText: '确定', 70 | }); 71 | return; 72 | } 73 | deviceStore.isEnableShowNotificationIconNum = false; 74 | } 75 | }; 76 | 77 | const changeNum = async (num: number) => { 78 | const [removeShowNotificationIconNumErr] = await $to(deviceApi.removeShowNotificationIconNum()); 79 | if (removeShowNotificationIconNumErr) { 80 | modal.create({ 81 | title: '操作失败', 82 | type: 'error', 83 | preset: 'dialog', 84 | content: () =>

修改失败,详情请查看日志记录~

, 85 | negativeText: '确定', 86 | }); 87 | return; 88 | } 89 | 90 | const [addShowNotificationIconNumErr] = await $to(deviceApi.addShowNotificationIconNum(num)); 91 | 92 | if (addShowNotificationIconNumErr) { 93 | modal.create({ 94 | title: '操作失败', 95 | type: 'error', 96 | preset: 'dialog', 97 | content: () =>

修改失败,详情请查看日志记录~

, 98 | negativeText: '确定', 99 | }); 100 | } 101 | 102 | const [putCurrentStatusBarShowNotificationIconErr] = await $to( 103 | deviceApi.putCurrentStatusBarShowNotificationIcon(num), 104 | ); 105 | 106 | if (putCurrentStatusBarShowNotificationIconErr) { 107 | modal.create({ 108 | title: '操作失败', 109 | type: 'error', 110 | preset: 'dialog', 111 | content: () =>

修改失败,详情请查看日志记录~

, 112 | negativeText: '确定', 113 | }); 114 | } 115 | }; 116 | 117 | const fetchData = async () => { 118 | if (deviceStore.isEnableShowNotificationIconNum) { 119 | const [, getIsEnableShowNotificationIconNumResolve] = await $to( 120 | deviceApi.getIsEnableShowNotificationIconNum(), 121 | ); 122 | if ( 123 | getIsEnableShowNotificationIconNumResolve && 124 | !Number.isNaN(Number(getIsEnableShowNotificationIconNumResolve)) 125 | ) { 126 | const numRes = Number(getIsEnableShowNotificationIconNumResolve); 127 | currentNum.value = numRes; 128 | } 129 | } 130 | isInit.value = true; 131 | }; 132 | 133 | onMounted(() => { 134 | if (deviceStore.isEnableShowNotificationIconNum) { 135 | setTimeout(() => { 136 | fetchData(); // 确保 UI 先渲染,再执行耗时操作 137 | },0); 138 | } 139 | }); 140 | 141 | return { 142 | currentNum, 143 | changeNum, 144 | changeEnableMode, 145 | isInit 146 | }; 147 | } 148 | -------------------------------------------------------------------------------- /src/hooks/useSidebar.tsx: -------------------------------------------------------------------------------- 1 | import { ref, computed, onMounted } from 'vue'; 2 | import { useDeviceStore } from '@/stores/device'; 3 | import * as deviceApi from '@/apis/deviceApi'; 4 | import { createDiscreteApi, darkTheme, lightTheme, type ConfigProviderProps } from 'naive-ui'; 5 | import $to from 'await-to-js'; 6 | import { divide } from 'lodash-es'; 7 | 8 | export interface messageCenterOptions { 9 | label: string; 10 | key: string; 11 | } 12 | 13 | export function useSidebar() { 14 | const configProviderPropsRef = computed(() => ({ 15 | theme: deviceStore.isDarkMode ? darkTheme : lightTheme, 16 | })); 17 | const deviceStore = useDeviceStore(); 18 | const isSupportGameMode = computed(() => { 19 | return deviceStore.miuiCompatEnable && deviceStore.androidTargetSdk && deviceStore.androidTargetSdk > 31; 20 | }); 21 | 22 | const { message, modal } = createDiscreteApi(['message', 'modal'], { 23 | configProviderProps: configProviderPropsRef, 24 | }); 25 | 26 | const MESSAGE_CENTER_OPTIONS: messageCenterOptions[] = [ 27 | { 28 | label: '创建到桌面快捷方式', 29 | key: 'createShortCut', 30 | }, 31 | { 32 | label: '查看更新日志', 33 | key: 'changelog', 34 | } 35 | ]; 36 | 37 | const handleMessageCenterOptionClick = (key: string, option: messageCenterOptions) => { 38 | message.warning('此区域尚未开放,请以后再来探索吧~') 39 | } 40 | 41 | 42 | 43 | const messageCenterOptions = ref([]) 44 | const getModal = async () => { 45 | modal.create({ 46 | title: '应用适配收集表', 47 | type: 'info', 48 | preset: 'dialog', 49 | content: () => ( 50 |
51 |

您可以通过收集表提交您的应用适配需求OwO,提交前请认真阅读须知~

52 |

https://docs.qq.com/form/page/DRUhJQkhzSnp6dWhm

53 |
54 | ), 55 | positiveText: '复制收集表链接到剪切板', 56 | negativeText: '取消', 57 | onPositiveClick: () => { 58 | navigator.clipboard.writeText(`https://docs.qq.com/form/page/DRUhJQkhzSnp6dWhm`); 59 | }, 60 | }); 61 | }; 62 | 63 | onMounted(() => {}); 64 | 65 | return { 66 | MESSAGE_CENTER_OPTIONS, 67 | handleMessageCenterOptionClick 68 | }; 69 | } 70 | -------------------------------------------------------------------------------- /src/hooks/useUFSHealth.ts: -------------------------------------------------------------------------------- 1 | import { ref, computed, onMounted, nextTick } from 'vue'; 2 | import { useDeviceStore } from '@/stores/device'; 3 | import * as deviceApi from '@/apis/deviceApi'; 4 | import { pick } from 'lodash-es'; 5 | import $to from 'await-to-js'; 6 | export type InstallAppNameListDictionary = Record; 7 | 8 | // 定义健康数据的类型 9 | interface UFSHealthData { 10 | bPreEOLInfo: number; 11 | bDeviceLifeTimeEstA: number; 12 | bDeviceLifeTimeEstB: number; 13 | } 14 | 15 | export function useUFSHealth() { 16 | 17 | const bPreEOLInfo = ref(); 18 | 19 | const isInit = ref(false); 20 | 21 | const bDeviceLifeTimeEstA = ref(); 22 | 23 | const bDeviceLifeTimeEstB = ref(); 24 | 25 | const correctedPreEOLStatus = computed(() => { 26 | if (bDeviceLifeTimeEstA.value && bDeviceLifeTimeEstB.value) { 27 | const preEOL = bPreEOLInfo.value; 28 | const maxLife = Math.max(bDeviceLifeTimeEstA.value, bDeviceLifeTimeEstB.value); 29 | if (maxLife >= 5 || preEOL === 2) return '寿命接近终点'; 30 | if (maxLife >= 7 || preEOL === 3) return '接近失效'; 31 | return '健康' 32 | } 33 | return ''; 34 | }); 35 | 36 | const isShow = computed(() => { 37 | return Boolean(bPreEOLInfo.value && bDeviceLifeTimeEstA.value && bDeviceLifeTimeEstB.value && isInit.value); 38 | }); 39 | 40 | const deviceLifeTimeEstA = computed(() => { 41 | if (bDeviceLifeTimeEstA.value && bDeviceLifeTimeEstA.value >= 1 && bDeviceLifeTimeEstA.value <= 9) { 42 | return `${bDeviceLifeTimeEstA.value * 10}%`; 43 | } 44 | return ''; 45 | }); 46 | 47 | const deviceLifeTimeEstB = computed(() => { 48 | if (bDeviceLifeTimeEstB.value && bDeviceLifeTimeEstB.value >= 1 && bDeviceLifeTimeEstB.value <= 9) { 49 | return `${bDeviceLifeTimeEstB.value * 10}%`; 50 | } 51 | return ''; 52 | }); 53 | 54 | const fetchData = async () => { 55 | // 先获取 UFS 健康信息 56 | const [getUFSHealthInfoErr, getUFSHealthInfoRes] = await $to(deviceApi.getUFSHealthInfo()); 57 | 58 | if (getUFSHealthInfoRes) { 59 | // 使用正则提取健康相关字段 60 | const healthFields: (keyof UFSHealthData)[] = ['bPreEOLInfo', 'bDeviceLifeTimeEstA', 'bDeviceLifeTimeEstB']; 61 | const regex = new RegExp(`(${healthFields.join('|')}) = 0x([0-9a-fA-F]+)`, 'g'); 62 | 63 | const parseUfsHealthData = (data: string): UFSHealthData => { 64 | const healthData: Partial = {}; 65 | let match: RegExpExecArray | null; 66 | 67 | while ((match = regex.exec(data)) !== null) { 68 | healthData[match[1] as keyof UFSHealthData] = parseInt(match[2], 16); 69 | } 70 | 71 | return pick(healthData, healthFields) as UFSHealthData; 72 | }; 73 | 74 | const result: UFSHealthData = parseUfsHealthData(getUFSHealthInfoRes); 75 | 76 | if (result.bPreEOLInfo !== undefined) bPreEOLInfo.value = result.bPreEOLInfo; 77 | if (result.bDeviceLifeTimeEstA !== undefined) bDeviceLifeTimeEstA.value = result.bDeviceLifeTimeEstA; 78 | if (result.bDeviceLifeTimeEstB !== undefined) bDeviceLifeTimeEstB.value = result.bDeviceLifeTimeEstB; 79 | } else if (getUFSHealthInfoErr) { 80 | // 如果 UFS 健康信息获取失败,则并行请求其他数据 81 | const [ 82 | [, getUFSEOLInfoRes], 83 | [, getUFSLifeTimeEstimationARes], 84 | [, getUFSLifeTimeEstimationBRes] 85 | ] = await Promise.all([ 86 | $to(deviceApi.getUFSEOLInfo()), 87 | $to(deviceApi.getUFSLifeTimeEstimationA()), 88 | $to(deviceApi.getUFSLifeTimeEstimationB()) 89 | ]); 90 | 91 | if (getUFSEOLInfoRes) bPreEOLInfo.value = parseInt(getUFSEOLInfoRes, 16); 92 | if (getUFSLifeTimeEstimationARes) bDeviceLifeTimeEstA.value = parseInt(getUFSLifeTimeEstimationARes, 16); 93 | if (getUFSLifeTimeEstimationBRes) bDeviceLifeTimeEstB.value = parseInt(getUFSLifeTimeEstimationBRes, 16); 94 | } 95 | isInit.value = true; 96 | }; 97 | onMounted(() => { 98 | setTimeout(() => { 99 | fetchData(); // 确保 UI 先渲染,再执行耗时操作 100 | },0); 101 | }); 102 | 103 | return { 104 | bPreEOLInfo, 105 | correctedPreEOLStatus, 106 | bDeviceLifeTimeEstA, 107 | deviceLifeTimeEstA, 108 | bDeviceLifeTimeEstB, 109 | deviceLifeTimeEstB, 110 | isShow, 111 | }; 112 | } 113 | -------------------------------------------------------------------------------- /src/hooks/useVideoWallpaperLoop.tsx: -------------------------------------------------------------------------------- 1 | import { ref, computed, onMounted } from 'vue'; 2 | import { useDeviceStore } from '@/stores/device'; 3 | import $to from 'await-to-js'; 4 | import { 5 | NButton, 6 | createDiscreteApi, 7 | darkTheme, 8 | lightTheme, 9 | type ConfigProviderProps, 10 | type DataTableColumns, 11 | type NInput, 12 | } from 'naive-ui'; 13 | import * as deviceApi from '@/apis/deviceApi'; 14 | 15 | export function useVideoWallpaperLoop() { 16 | const deviceStore = useDeviceStore(); 17 | const configProviderPropsRef = computed(() => ({ 18 | theme: deviceStore.isDarkMode ? darkTheme : lightTheme, 19 | })); 20 | 21 | const { message, modal } = createDiscreteApi(['message', 'modal'], { 22 | configProviderProps: configProviderPropsRef, 23 | }); 24 | 25 | const change = async () => { 26 | const [setHomeVideoWallpaperLoopErr,setHomeVideoWallpaperLoopRes] = await $to(deviceApi.setHomeVideoWallpaperLoop()) 27 | if (setHomeVideoWallpaperLoopErr) { 28 | modal.create({ 29 | title: '设置桌面壁纸循环播放失败', 30 | type: 'error', 31 | preset: 'dialog', 32 | content: () => ( 33 |

发生异常错误,设置动态壁纸循环播放失败QwQ,详细错误请查看日志~

34 | ), 35 | }); 36 | return; 37 | } 38 | const [setLockVideoWallpaperLoopErr,setLockVideoWallpaperLoopRes] = await $to(deviceApi.setLockVideoWallpaperLoop()) 39 | if (setLockVideoWallpaperLoopErr) { 40 | modal.create({ 41 | title: '设置锁屏壁纸循环播放失败', 42 | type: 'error', 43 | preset: 'dialog', 44 | content: () => ( 45 |

发生异常错误,设置动态壁纸循环播放失败QwQ,详细错误请查看日志~

46 | ), 47 | }); 48 | return; 49 | } 50 | modal.create({ 51 | title: '设置循环播放成功', 52 | type: 'success', 53 | preset: 'dialog', 54 | content: () => ( 55 |

56 | 好耶w,已经成功动态壁纸循环播放~实际生效还需要重启{' '} 57 | 58 | 壁纸 59 | {' '} 60 | 的作用域,确定要继续吗? 61 |

62 | ), 63 | positiveText: '确定重启作用域', 64 | negativeText: '稍后手动重启', 65 | onPositiveClick() { 66 | deviceApi 67 | .killMiWallpaper() 68 | .then(async res => { 69 | modal.create({ 70 | title: '重启作用域成功', 71 | type: 'success', 72 | preset: 'dialog', 73 | content: () =>

已经成功为你重启对应的作用域,请查看是否生效~

, 74 | }); 75 | }) 76 | .catch(err => { 77 | modal.create({ 78 | title: '重启作用域失败', 79 | type: 'error', 80 | preset: 'dialog', 81 | content: () => ( 82 |

发生异常错误,重启系统界面作用域失败QwQ,详细错误请查看日志~

83 | ), 84 | }); 85 | }); 86 | }, 87 | }); 88 | } 89 | 90 | return { 91 | change 92 | }; 93 | } 94 | -------------------------------------------------------------------------------- /src/hooks/useZRAMWriteback.tsx: -------------------------------------------------------------------------------- 1 | import { ref, computed, onMounted, nextTick } from 'vue'; 2 | import { useDeviceStore } from '@/stores/device'; 3 | import $to from 'await-to-js'; 4 | import { 5 | NButton, 6 | createDiscreteApi, 7 | darkTheme, 8 | lightTheme, 9 | type ConfigProviderProps, 10 | type DataTableColumns, 11 | type NInput, 12 | } from 'naive-ui'; 13 | import * as deviceApi from '@/apis/deviceApi'; 14 | export function useZRAMWriteback() { 15 | const deviceStore = useDeviceStore(); 16 | const configProviderPropsRef = computed(() => ({ 17 | theme: deviceStore.isDarkMode ? darkTheme : lightTheme, 18 | })); 19 | 20 | const { message, modal } = createDiscreteApi(['message', 'modal'], { 21 | configProviderProps: configProviderPropsRef, 22 | }); 23 | 24 | const backingDev = ref(''); 25 | 26 | const miuiExtmDmOptEnable = ref(false); 27 | 28 | const totalWriteBack = ref(0); 29 | 30 | const hasWriteBack = ref(0); 31 | 32 | const totalRead = ref(0); 33 | 34 | const isInit = ref(false); 35 | 36 | const fetchData = async () => { 37 | const [ 38 | [, getMiuiExtmDmOptEnableResolve], 39 | [, getBackingDevResolve], 40 | [, getMiuiExtmDmOptTotalWriteBackResolve], 41 | [, getMiuiExtmDmOptTotalReadResolve], 42 | [, getMiuiExtmDmOptHasWriteBackResolve] 43 | ] = await Promise.all([ 44 | $to(deviceApi.getMiuiExtmDmOptEnable()), 45 | $to(deviceApi.getBackingDev()), 46 | $to(deviceApi.getMiuiExtmDmOptTotalWriteBack()), 47 | $to(deviceApi.getMiuiExtmDmOptTotalRead()), 48 | $to(deviceApi.getMiuiExtmDmOptHasWriteBack()) 49 | ]); 50 | 51 | // 赋值 52 | if (getMiuiExtmDmOptEnableResolve === 'true') miuiExtmDmOptEnable.value = true; 53 | if (getBackingDevResolve) backingDev.value = getBackingDevResolve; 54 | if (Number(getMiuiExtmDmOptTotalWriteBackResolve) > 0) totalWriteBack.value = Number(getMiuiExtmDmOptTotalWriteBackResolve); 55 | if (Number(getMiuiExtmDmOptTotalReadResolve) > 0) totalRead.value = Number(getMiuiExtmDmOptTotalReadResolve); 56 | if (Number(getMiuiExtmDmOptHasWriteBackResolve) > 0) hasWriteBack.value = Number(getMiuiExtmDmOptHasWriteBackResolve); 57 | 58 | isInit.value = true; 59 | }; 60 | 61 | onMounted(() => { 62 | setTimeout(() => { 63 | fetchData(); // 确保 UI 先渲染,再执行耗时操作 64 | },0); 65 | }); 66 | 67 | return { 68 | backingDev, 69 | miuiExtmDmOptEnable, 70 | totalWriteBack, 71 | hasWriteBack, 72 | totalRead, 73 | isInit 74 | }; 75 | } 76 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import "./assets/fonts.css"; 2 | import "./assets/main.css"; 3 | import "./style.css"; 4 | import "./assets/iconfont.js"; 5 | // 通用字体 6 | import "vfonts/Lato.css"; 7 | // 等宽字体 8 | import "vfonts/FiraCode.css"; 9 | import { createApp } from "vue"; 10 | import { createPinia } from "pinia"; 11 | import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' 12 | import { 13 | // create naive ui 14 | create, 15 | // component 16 | NButton, 17 | NSplit, 18 | NDrawer, 19 | NDrawerContent, 20 | NSwitch, 21 | NTable, 22 | NDataTable, 23 | NMessageProvider, 24 | NTag, 25 | NTab, 26 | NTabs, 27 | NTabPane, 28 | NInput, 29 | NInputGroup, 30 | darkTheme, 31 | NSpin, 32 | NSpace, 33 | NCard, 34 | NWatermark, 35 | NModal, 36 | NAlert, 37 | NRadioGroup, 38 | NRadioButton, 39 | NBadge, 40 | NInputGroupLabel, 41 | NInputNumber, 42 | NDropdown, 43 | NSlider, 44 | NAvatar, 45 | NBreadcrumb, 46 | NBreadcrumbItem, 47 | NSkeleton, 48 | NCheckboxGroup, 49 | NCheckbox, 50 | NGi, 51 | NIcon, 52 | NStatistic, 53 | NThemeEditor, 54 | NCode, 55 | NGrid, 56 | NPageHeader, 57 | NConfigProvider, 58 | NLog, 59 | NCollapse, 60 | NCollapseItem, 61 | NCollapseTransition 62 | } from "naive-ui"; 63 | 64 | import App from "./App.vue"; 65 | import router from "./router"; 66 | 67 | const naive = create({ 68 | components: [ 69 | NButton, 70 | NDrawer, 71 | NDrawerContent, 72 | NTable, 73 | NDataTable, 74 | NPageHeader, 75 | NMessageProvider, 76 | NDropdown, 77 | NSwitch, 78 | NTag, 79 | NInputGroup, 80 | NInput, 81 | NSpin, 82 | NStatistic, 83 | NSplit, 84 | NSpace, 85 | NModal, 86 | NAlert, 87 | NCard, 88 | NTab, 89 | NTabs, 90 | NTabPane, 91 | NRadioGroup, 92 | NRadioButton, 93 | NInputGroupLabel, 94 | NInputNumber, 95 | darkTheme, 96 | NSlider, 97 | NThemeEditor, 98 | NAvatar, 99 | NBreadcrumb, 100 | NBreadcrumbItem, 101 | NConfigProvider, 102 | NWatermark, 103 | NCheckboxGroup, 104 | NLog, 105 | NCheckbox, 106 | NSkeleton, 107 | NGi, 108 | NGrid, 109 | NIcon, 110 | NBadge, 111 | NCollapse, 112 | NCollapseItem, 113 | NCollapseTransition, 114 | NCode 115 | ], 116 | }); 117 | 118 | const app = createApp(App); 119 | // enableDarkMode({}); 120 | const pinia = createPinia(); 121 | pinia.use(piniaPluginPersistedstate) 122 | app.use(pinia); 123 | app.use(naive); 124 | app.use(router); 125 | app.mount("#app"); 126 | -------------------------------------------------------------------------------- /src/router/device_routes/fold.ts: -------------------------------------------------------------------------------- 1 | 2 | import { createRouter, createWebHashHistory, type RouteRecordRaw } from 'vue-router'; 3 | const routes: RouteRecordRaw[] = [ 4 | { 5 | path: '/', 6 | redirect: '/home', 7 | }, 8 | { 9 | path: '/home', 10 | name: 'home', 11 | component: () => import('../../views/EmbeddedActivityView.vue'), 12 | }, 13 | { 14 | path: '/autoui', 15 | name: 'autoui', 16 | // route level code-splitting 17 | // this generates a separate chunk (About.[hash].js) for this route 18 | // which is lazy-loaded when the route is visited. 19 | component: () => import('../../views/AutoUIView.vue'), 20 | }, 21 | { 22 | path: '/settings', 23 | name: 'settings', 24 | // route level code-splitting 25 | // this generates a separate chunk (About.[hash].js) for this route 26 | // which is lazy-loaded when the route is visited. 27 | component: () => import('../../views/SettingsView.vue'), 28 | }, 29 | { 30 | path: '/system-experience-enhance', 31 | name: 'system-experience-enhance', 32 | // route level code-splitting 33 | // this generates a separate chunk (About.[hash].js) for this route 34 | // which is lazy-loaded when the route is visited. 35 | component: () => import('../../views/SystemExperienceEnhance.vue'), 36 | }, 37 | { 38 | path: '/embedded-webview', 39 | name: 'embedded-webview', 40 | component: () => import('../../views/EmbeddedWebView.vue'), 41 | }, 42 | { 43 | path: '/appStore', 44 | name: 'appStore', 45 | component: () => import('../../views/AppStore.vue'), 46 | }, 47 | { 48 | path: '/logs', 49 | name: 'logs', 50 | component: () => import('../../views/LogView.vue'), 51 | }, 52 | { 53 | path: '/game-booster', 54 | name: 'game-booster', 55 | component: () => import('../../views/GameBooster.vue'), 56 | }, 57 | { 58 | path: '/game-turbo-config', 59 | name: 'game-turbo-config', 60 | component: () => import('../../views/GameTurboConfig.vue'), 61 | }, 62 | { 63 | path: '/dot-black-list', 64 | name: 'dot-black-list', 65 | component: () => import('../../views/DotBlackListView.vue'), 66 | }, 67 | { 68 | path: '/magic-control', 69 | name: 'magic-control', 70 | component: () => import('../../views/MagicControlView.vue'), 71 | }, 72 | { 73 | path: '/eggs', 74 | name: 'eggs', 75 | component: () => import('../../views/HappyNewYearEgg.vue'), 76 | }, 77 | { 78 | path: '/:pathMatch(.*)*', // 捕获所有未匹配的路由 79 | component: () => import('../../views/NotFoundView.vue'), 80 | }, 81 | ] 82 | 83 | export default routes -------------------------------------------------------------------------------- /src/router/device_routes/phone.ts: -------------------------------------------------------------------------------- 1 | 2 | import { createRouter, createWebHashHistory, type RouteRecordRaw } from 'vue-router'; 3 | const routes: RouteRecordRaw[] = [ 4 | { 5 | path: '/', 6 | redirect: '/home', 7 | }, 8 | { 9 | path: '/home', 10 | name: 'home', 11 | component: () => import('../../views/SystemExperienceEnhance.vue'), 12 | }, 13 | { 14 | path: '/embedded-webview', 15 | name: 'embedded-webview', 16 | component: () => import('../../views/EmbeddedWebView.vue'), 17 | }, 18 | { 19 | path: '/memory-health', 20 | name: 'memory-health', 21 | component: () => import('../../views/MemoryHealth.vue'), 22 | }, 23 | { 24 | path: '/battery-health', 25 | name: 'battery-health', 26 | component: () => import('../../views/BatteryHealth.vue'), 27 | }, 28 | { 29 | path: '/appStore', 30 | name: 'appStore', 31 | component: () => import('../../views/AppStore.vue'), 32 | }, 33 | { 34 | path: '/logs', 35 | name: 'logs', 36 | component: () => import('../../views/LogView.vue'), 37 | }, 38 | { 39 | path: '/settings', 40 | name: 'settings', 41 | // route level code-splitting 42 | // this generates a separate chunk (About.[hash].js) for this route 43 | // which is lazy-loaded when the route is visited. 44 | component: () => import('../../views/SettingsView.vue'), 45 | }, 46 | { 47 | path: '/:pathMatch(.*)*', // 捕获所有未匹配的路由 48 | component: () => import('../../views/NotFoundView.vue'), 49 | }, 50 | ] 51 | 52 | export default routes -------------------------------------------------------------------------------- /src/router/device_routes/tablet.ts: -------------------------------------------------------------------------------- 1 | 2 | import { createRouter, createWebHashHistory, type RouteRecordRaw } from 'vue-router'; 3 | const routes: RouteRecordRaw[] = [ 4 | { 5 | path: '/', 6 | redirect: '/home', 7 | }, 8 | { 9 | path: '/home', 10 | name: 'home', 11 | component: () => import('../../views/EmbeddedActivityView.vue'), 12 | }, 13 | { 14 | path: '/autoui', 15 | name: 'autoui', 16 | // route level code-splitting 17 | // this generates a separate chunk (About.[hash].js) for this route 18 | // which is lazy-loaded when the route is visited. 19 | component: () => import('../../views/AutoUIView.vue'), 20 | }, 21 | { 22 | path: '/settings', 23 | name: 'settings', 24 | // route level code-splitting 25 | // this generates a separate chunk (About.[hash].js) for this route 26 | // which is lazy-loaded when the route is visited. 27 | component: () => import('../../views/SettingsView.vue'), 28 | }, 29 | { 30 | path: '/system-experience-enhance', 31 | name: 'system-experience-enhance', 32 | // route level code-splitting 33 | // this generates a separate chunk (About.[hash].js) for this route 34 | // which is lazy-loaded when the route is visited. 35 | component: () => import('../../views/SystemExperienceEnhance.vue'), 36 | }, 37 | { 38 | path: '/embedded-webview', 39 | name: 'embedded-webview', 40 | component: () => import('../../views/EmbeddedWebView.vue'), 41 | }, 42 | { 43 | path: '/appStore', 44 | name: 'appStore', 45 | component: () => import('../../views/AppStore.vue'), 46 | }, 47 | { 48 | path: '/logs', 49 | name: 'logs', 50 | component: () => import('../../views/LogView.vue'), 51 | }, 52 | { 53 | path: '/game-booster', 54 | name: 'game-booster', 55 | component: () => import('../../views/GameBooster.vue'), 56 | }, 57 | { 58 | path: '/game-turbo-config', 59 | name: 'game-turbo-config', 60 | component: () => import('../../views/GameTurboConfig.vue'), 61 | }, 62 | { 63 | path: '/dot-black-list', 64 | name: 'dot-black-list', 65 | component: () => import('../../views/DotBlackListView.vue'), 66 | }, 67 | { 68 | path: '/magic-control', 69 | name: 'magic-control', 70 | component: () => import('../../views/MagicControlView.vue'), 71 | }, 72 | { 73 | path: '/eggs', 74 | name: 'eggs', 75 | component: () => import('../../views/HappyNewYearEgg.vue'), 76 | }, 77 | { 78 | path: '/:pathMatch(.*)*', // 捕获所有未匹配的路由 79 | component: () => import('../../views/NotFoundView.vue'), 80 | }, 81 | ] 82 | 83 | export default routes -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHashHistory, type RouteRecordRaw } from 'vue-router'; 2 | import { createApp, defineAsyncComponent, defineComponent } from 'vue'; 3 | import { createDiscreteApi } from 'naive-ui'; 4 | import SettingsView from '../views/SettingsView.vue'; 5 | 6 | const { loadingBar } = createDiscreteApi(['loadingBar']); 7 | 8 | const router = createRouter({ 9 | // history: createWebHistory(import.meta.env.BASE_URL), 10 | history: createWebHashHistory(), 11 | routes: [], 12 | scrollBehavior(to, from, savedPosition) { 13 | if (to.hash) { 14 | return { 15 | el: to.hash, 16 | behavior: 'smooth', 17 | block: 'center', 18 | }; 19 | } 20 | return savedPosition || { top: 0 }; 21 | }, 22 | }); 23 | 24 | // 进入路由前,显示 loadingBar 25 | router.beforeEach((to, from, next) => { 26 | loadingBar.start(); 27 | next(); 28 | }); 29 | 30 | // 进入路由后,结束 loadingBar 31 | router.afterEach(() => { 32 | loadingBar.finish(); 33 | }); 34 | 35 | export default router; 36 | -------------------------------------------------------------------------------- /src/stores/cloudFeature.ts: -------------------------------------------------------------------------------- 1 | import { ref, computed, reactive } from 'vue'; 2 | import { defineStore } from 'pinia'; 3 | 4 | export const useCloudFeatureStore = defineStore('cloudFeature', () => { 5 | const joyseBootserConfig = reactive({}); 6 | return { joyseBootserConfig }; 7 | }); 8 | -------------------------------------------------------------------------------- /src/stores/counter.ts: -------------------------------------------------------------------------------- 1 | import { ref, computed } from 'vue'; 2 | import { defineStore } from 'pinia'; 3 | 4 | export const useCounterStore = defineStore('counter', () => { 5 | const count = ref(0); 6 | const doubleCount = computed(() => count.value * 2); 7 | function increment() { 8 | count.value++; 9 | } 10 | 11 | return { count, doubleCount, increment }; 12 | }); 13 | -------------------------------------------------------------------------------- /src/stores/font.ts: -------------------------------------------------------------------------------- 1 | import { ref, computed } from 'vue'; 2 | import { defineStore } from 'pinia'; 3 | 4 | export type fontType = 'MiSans' | 'OPPO Sans' | 'HarmonyOS Sans'; 5 | 6 | export const useFontStore = defineStore( 7 | 'font', 8 | () => { 9 | const currentFont = ref(`MiSans`); 10 | 11 | const currentFontFamily = computed(() => { 12 | return `${currentFont.value}, system-ui, sans-serif` 13 | }) 14 | 15 | const setFont = (fontType: fontType) => { 16 | currentFont.value = fontType; 17 | }; 18 | 19 | return { currentFont, setFont,currentFontFamily }; 20 | }, 21 | { 22 | persist: { 23 | pick: ['currentFont'], 24 | }, 25 | }, 26 | ); 27 | -------------------------------------------------------------------------------- /src/stores/gameBooster.ts: -------------------------------------------------------------------------------- 1 | import { ref, computed, reactive, type ComputedRef } from 'vue'; 2 | import { defineStore } from 'pinia'; 3 | import $to from 'await-to-js'; 4 | import * as gameBoosterApi from '@/apis/gameBoosterApi'; 5 | import * as deviceApi from '@/apis/deviceApi'; 6 | import type { ErrorLogging } from '@/types/ErrorLogging'; 7 | import type GameBoosterTableItem from '@/types/GameBoosterTableItem'; 8 | 9 | export const useGameBoosterStore = defineStore( 10 | 'gameBooster', 11 | () => { 12 | // 游戏显示布局 13 | const gameBoosterList = ref([]) 14 | // 搜索后的配置列表 15 | const filterGameBoosterList:ComputedRef = computed(() => { 16 | const searchValue = searchKeyWord.value.trim().toLowerCase(); 17 | const cachedGameBoosterList = gameBoosterList.value; 18 | return cachedGameBoosterList.reduce((result:GameBoosterTableItem[], item) => { 19 | const itemName = item.package_name.trim().toLowerCase(); 20 | 21 | // 过滤条件,检查 name 和 applicationName 是否包含 searchValue 22 | const applicationNameLower = item.app_name ? item.app_name.toLowerCase() : ''; 23 | if (!itemName.includes(searchValue) && !applicationNameLower.includes(searchValue)) { 24 | return result; 25 | } 26 | 27 | result.push(item); 28 | return result; 29 | },[]); 30 | }) 31 | // 搜索后的配置列表 32 | // 是否弹出错误信息弹窗 33 | const isNeedShowErrorModal = computed(() => Boolean(errorLogging.length > 0)); 34 | // 应用总数 35 | const listCount = computed(() => gameBoosterList.value.length); 36 | // 搜索值 37 | const searchKeyWord = ref(''); 38 | // 加载状态 39 | const loading = ref(true); 40 | // 数据库记录 41 | const hasGameBoosterDataBase = ref(false); 42 | // 错误存储 43 | const errorLogging = reactive([]); 44 | // 45 | const allPackageName = computed(() => { 46 | const allPackages = new Set([ 47 | ...gameBoosterList.value 48 | ]); 49 | return allPackages; 50 | }); 51 | 52 | async function initDefault() { 53 | loading.value = true; 54 | 55 | const [getHasGameBoosterDataBaseErr, getHasGameBoosterDataBaseRes] = await $to(deviceApi.getHasGameBoosterDataBase()) 56 | 57 | if (getHasGameBoosterDataBaseErr) { 58 | loading.value = false; 59 | hasGameBoosterDataBase.value = false; 60 | } 61 | 62 | if (getHasGameBoosterDataBaseRes) { 63 | hasGameBoosterDataBase.value = true; 64 | const [getGameBoosterListErr, getGameBoosterListRes] = await $to( 65 | gameBoosterApi.getGameBoosterList(), 66 | ); 67 | if (getGameBoosterListErr) { 68 | loading.value = false; 69 | gameBoosterList.value = []; 70 | } 71 | 72 | if (getGameBoosterListRes) { 73 | loading.value = false; 74 | gameBoosterList.value = getGameBoosterListRes; 75 | } 76 | } 77 | 78 | if (!errorLogging.length) { 79 | loading.value = false; 80 | } 81 | } 82 | 83 | return { 84 | gameBoosterList, 85 | filterGameBoosterList, 86 | allPackageName, 87 | hasGameBoosterDataBase, 88 | searchKeyWord, 89 | errorLogging, 90 | isNeedShowErrorModal, 91 | loading, 92 | initDefault, 93 | }; 94 | }, 95 | { 96 | persist: { 97 | pick: [], 98 | }, 99 | }, 100 | ); 101 | -------------------------------------------------------------------------------- /src/stores/logs.ts: -------------------------------------------------------------------------------- 1 | import { ref, computed } from 'vue'; 2 | import { defineStore } from 'pinia'; 3 | 4 | export const useLogsStore = defineStore('logs', () => { 5 | const content = ref(''); 6 | 7 | function error(title: string, msg?: string | number | boolean | undefined) { 8 | content.value += `[error] ${title}${msg !== undefined ? ':' + msg : ''}\n`; 9 | } 10 | 11 | function info(title: string, msg?: string | number | boolean | undefined) { 12 | const mergeMsg = `[info] ${title}${msg !== undefined ? ':' + msg : ''}\n`; 13 | content.value += mergeMsg; 14 | console.log(mergeMsg); 15 | } 16 | 17 | function success(title: string, msg?: string | number | boolean | undefined) { 18 | content.value += `[success] ${title}${ 19 | msg !== undefined ? ':' + msg : '' 20 | }\n`; 21 | } 22 | 23 | return { content, error, info, success }; 24 | }); 25 | -------------------------------------------------------------------------------- /src/stores/package.ts: -------------------------------------------------------------------------------- 1 | import { ref, computed, reactive } from 'vue'; 2 | import { defineStore } from 'pinia'; 3 | import type PackageItem from '@/types/PackageItem'; 4 | 5 | export const useCounterStore = defineStore('package', () => { 6 | const packageList = reactive([]); 7 | 8 | const reloadList = () => {}; 9 | 10 | return { packageList, reloadList }; 11 | }); 12 | -------------------------------------------------------------------------------- /src/style.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --global-font-family: MiSans, system-ui, sans-serif; 7 | } 8 | 9 | body { 10 | font-family: var(--global-font-family); 11 | } 12 | 13 | .animated-bg { 14 | background-image: linear-gradient( 15 | 101.22deg, 16 | rgb(255, 182, 133) -18.32%, 17 | rgb(255, 111, 29) 7.01%, 18 | rgb(252, 181, 232) 41.59%, 19 | rgb(135, 148, 255) 70.98%, 20 | rgb(60, 112, 255) 91.35%, 21 | rgb(60, 112, 255) 110.17% 22 | ); 23 | background-size: 300% 300%; 24 | animation: gradientShift 6s infinite ease-in-out; 25 | } 26 | 27 | .gradient-box { 28 | background: linear-gradient(90deg, #c5dfff, #eaf7ff, #ffe6f2); 29 | border-radius: 8px; 30 | display: flex; 31 | align-items: center; 32 | justify-content: space-between; 33 | padding: 0 15px; 34 | font-family: Arial, sans-serif; 35 | color: #333; 36 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); 37 | } 38 | 39 | 40 | @keyframes gradientShift { 41 | 0% { 42 | background-position: 30% 50%; 43 | } 44 | 50% { 45 | background-position: 70% 50%; 46 | } 47 | 100% { 48 | background-position: 30% 50%; 49 | } 50 | } 51 | 52 | .theme-dark-mode{ 53 | .n-data-table__pagination{ 54 | color: #fff; 55 | } 56 | } 57 | 58 | @media (prefers-color-scheme: dark) { 59 | body { 60 | background-color: #18181A; 61 | } 62 | } -------------------------------------------------------------------------------- /src/types/AutoUIItem.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 表示应用布局优化的规则配置项。 3 | */ 4 | export default interface AutoUIItem { 5 | /** 应用包名(必填) */ 6 | name: string; 7 | 8 | /** 默认是否启用规则,默认 true (必填) */ 9 | enable?: boolean; 10 | 11 | /** 应用布局优化配置规则 (必填) */ 12 | activityRule?: string; 13 | 14 | /** 是否优化Webview页面,默认 false(可选) */ 15 | optimizeWebView?: boolean; 16 | 17 | /** 跳过App配置改变,默认 false(可选) */ 18 | skippedAppConfigChange?: boolean; 19 | 20 | /** 搭配activityRule使用,如果activityRule的规则配置了通配符,这里可以用于声明排除哪些Activity不应该用于"应用布局优化"(可选) */ 21 | skippedActivityRule?: string; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/types/AutoUIMergeRuleItem.d.ts: -------------------------------------------------------------------------------- 1 | import type AutoUIItem from "@/types/AutoUIItem"; 2 | import type AutoUISettingRuleItem from "@/types/AutoUISettingRuleItem"; 3 | 4 | 5 | 6 | export default interface AutoUIMergeRuleItem { 7 | name: string; 8 | applicationName?:string; 9 | settingMode: 'UNDEFINED_VIEW_POLICY' | 'VIEW_POLICY_DEFAULT' | 'VIEW_POLICY_STRETCH' | 'VIEW_POLICY_AUTO_COLUMNS' | 'VIEW_POLICY_FLOAT' | 'CUSTOM_VIEW_POLICY' 10 | ruleMode: 'module' | 'custom'; 11 | autoUIRule?: Omit; 12 | settingRule?: Omit; 13 | }; -------------------------------------------------------------------------------- /src/types/AutoUIPolicy.ts: -------------------------------------------------------------------------------- 1 | export enum AutoUIViewPolicy { 2 | DEFAULT_AUTO_UI_POLICY_TYPE = -1, 3 | VIEW_POLICY_DEFAULT = 0, 4 | VIEW_POLICY_STRETCH = 1, 5 | VIEW_POLICY_AUTO_COLUMNS = 2, 6 | VIEW_POLICY_IGNORE = 3, 7 | VIEW_POLICY_TOP = 4, 8 | VIEW_POLICY_BOTTOM = 5, 9 | VIEW_POLICY_FLOAT = 6, 10 | VIEW_POLICY_TOP_PARENT = 7, 11 | VIEW_POLICY_MATCH_PARENT = 8, 12 | VIEW_POLICY_MATCH_PARENT_WIDTH = 9, 13 | VIEW_POLICY_MATCH_PARENT_HEIGHT = 10, 14 | VIEW_POLICY_WRAP_CONTENT = 11, 15 | VIEW_POLICY_WRAP_CONTENT_WIDTH = 12, 16 | VIEW_POLICY_WRAP_CONTENT_HEIGHT = 13, 17 | VIEW_POLICY_BITMAP_ORIGINAL_SPLIT = 14, 18 | VIEW_POLICY_KEEP_ORIGINAL_HEIGHT = 15, 19 | VIEW_POLICY_CENTER_ORIGINAL_SIZE = 16, 20 | VIEW_POLICY_BITMAP_AVERAGE_COLOR = 17, 21 | VIEW_POLICY_BITMAP_ORG = 18, 22 | VIEW_POLICY_ALL_CHILDREN_MATCH_SCREEN_WIDTH = 19 23 | } 24 | 25 | -------------------------------------------------------------------------------- /src/types/AutoUISettingRuleItem.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 表示应用布局优化的规则配置项。 3 | */ 4 | export default interface AutoUISettingRuleItem { 5 | /** 应用包名(必填) */ 6 | name: string; 7 | 8 | /** 默认是否启用规则,默认 true (必填) */ 9 | enable: boolean; 10 | } 11 | -------------------------------------------------------------------------------- /src/types/DotBlackListItem.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 表示窗口控制器的规则配置项。 3 | */ 4 | export default interface DotBlackListItem { 5 | /** 数据ID */ 6 | dataId: number; 7 | 8 | /** 数据库原始数据 */ 9 | productData: any; 10 | 11 | /** 窗口控制器列表 */ 12 | dataList: array[]; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/types/DotBlackListMergeItem.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 表示窗口控制器的规则配置项。 3 | */ 4 | export default interface DotBlackListMergeItem { 5 | name: string; 6 | applicationName: string; 7 | ruleMode: 'system' | 'custom'; 8 | status: boolean; 9 | } 10 | -------------------------------------------------------------------------------- /src/types/EmbeddedMergeRuleItem.d.ts: -------------------------------------------------------------------------------- 1 | import type EmbeddedRuleItem from "@/types/EmbeddedRuleItem"; 2 | import type FixedOrientationRuleItem from "@/types/FixedOrientationRuleItem"; 3 | import type EmbeddedSettingRuleItem from "@/types/EmbeddedSettingRuleItem"; 4 | 5 | export default interface EmbeddedMergeRuleItem { 6 | name: string; 7 | applicationName?:string; 8 | settingMode: 'fullScreen' | 'embedded' | 'fixedOrientation' | 'disabled'; 9 | isSupportEmbedded: boolean; 10 | isSupportFullScreen: boolean; 11 | isSupportSystemEmbedded: boolean; 12 | isSupportFixedOrientation: boolean; 13 | thirdPartyAppOptimize?: boolean; 14 | ruleMode: 'module' | 'custom'; 15 | embeddedRules?: Omit; 16 | fixedOrientationRule?: Omit; 17 | settingRule?: Omit; 18 | }; -------------------------------------------------------------------------------- /src/types/EmbeddedPlaceholder.d.ts: -------------------------------------------------------------------------------- 1 | 2 | export default interface EmbeddedPlaceholder { 3 | mainPageActivity: string 4 | relatedPageActivity: string 5 | }; -------------------------------------------------------------------------------- /src/types/EmbeddedRuleItem.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 表示平行窗口的规则配置项。 3 | */ 4 | export default interface EmbeddedRuleItem { 5 | /** 应用包名(必填) */ 6 | name: string; 7 | 8 | /** 支持左右滑动条,默认 false(可选) */ 9 | isShowDivider?: boolean; 10 | 11 | /** 搭配 isShowDivider 使用,支持将一侧平行窗口拉伸至全屏,默认 false(可选) */ 12 | supportFullSize?: boolean; 13 | 14 | /** 让应用强制全屏的配置参数,推荐 * 或者 nra:cr:rcr:nr(可选) */ 15 | fullRule?: string; 16 | 17 | /** 禁用方向传感器,默认 false(可选) */ 18 | disableSensor?: boolean; 19 | 20 | /** 应用缩放配置,会跟左右滑动条冲突,无法共存,推荐写 1(可选) */ 21 | scaleMode?: number; 22 | 23 | /** 启动默认展开平行窗口左右分屏的配置(可选) */ 24 | placeholder?: string; 25 | 26 | /** 该应用的平行窗口配置默认启用还是禁用,默认为 true(可选) */ 27 | defaultSettings?: boolean; 28 | 29 | /** 平行窗口的应用布局优化,Hyper OS 2.0+ 生效(可选) */ 30 | autoUiRule?: string; 31 | 32 | /** 搭配 middleRule,推荐为 true,当应用自适配平行窗口,则 middleRule 应该会自动失效(可选) */ 33 | procCompat?: boolean; 34 | 35 | /** 控制哪些 Activity 应该居中显示(可选) */ 36 | middleRule?: string; 37 | 38 | /** 平行窗口分屏递进配置(可选) */ 39 | splitPairRule?: string; 40 | 41 | /** 平行窗口全屏 Activity 列表配置(可选) */ 42 | activityRule?: string; 43 | 44 | /** 平行窗口过渡 Activity 列表配置(可选) */ 45 | transitionRules?: string; 46 | 47 | /** 是否支持拍照预览(可选) */ 48 | supportCameraPreview?: boolean; 49 | 50 | /** 配置平行窗口分割线的颜色,支持深色模式(可选) */ 51 | splitLineColor?: string; 52 | 53 | /** 平行窗口默认分屏比例,默认 0.5(可选) */ 54 | splitRatio?: number; 55 | 56 | /** 跳过应用自适配,Hyper OS 2.0+ 生效(可选) */ 57 | skipSelfAdaptive?: boolean; 58 | 59 | /** 平行窗口显示比例发生变化时是否重载,默认 false(可选) */ 60 | relaunch?: boolean; 61 | 62 | /** 取值”true”或 false,默认为 false。启动 activity 窗口分屏,避免右分屏出现多实例(可选) */ 63 | clearTop?: boolean; 64 | 65 | /** 强制居中显示的 activity(可选) */ 66 | forcePortraitActivity?: string; 67 | 68 | /** 一些有关平行窗口的额外配置(可选) */ 69 | flags?: string; 70 | 71 | /** 主窗口可分屏显示的最小 sw 值(可选) */ 72 | splitMinSmallestWidth?: number; 73 | 74 | /** 默认不推荐配置,主窗口可分屏显示的最小窗口宽度(可选) */ 75 | splitMinWidth?: number; 76 | 77 | /** 若 primary container 中所有 activity 都 finish,则 secondary container 中所有 activity 也会 finish,默认为 always(0)(可选) */ 78 | finishSecondaryWithPrimary?: number; 79 | 80 | /** 若 secondary container 中所有 activity 都 finish,则 primary container 中创建分屏的 activity 也会 finish,默认为 never(-1)(可选) */ 81 | finishPrimaryWithSecondary?: number; 82 | 83 | /** 强制居中显示的 activity,当切换时生效(Hyper OS 2.0+)(可选) */ 84 | forcePortraitWhenSwitch?: string; 85 | 86 | /** 当切换时强制终止应用(Hyper OS 2.0+)(可选) */ 87 | forceKillWhenSwitch?: string; 88 | 89 | /** 是否禁用拍照预览(可选) */ 90 | disableCameraPreview?: boolean; 91 | 92 | /** 大小兼容比例(仅安卓12有效),用于重绘 activity,推荐不配置(可选) */ 93 | sizecompatRatio?: number; 94 | 95 | /** 哪些 activity 需要重绘大小(仅安卓12有效),推荐不配置(可选) */ 96 | sizecompatRule?: string; 97 | 98 | /** 应用的布局方向,默认为随语言切换。不推荐主动配置(可选) */ 99 | layoutDirection?: string; 100 | 101 | /** 暂时没有明确用途,搁置(可选) */ 102 | allowRepeatPage?: boolean; 103 | 104 | /** 自适应前摄挖孔,仅折叠屏生效(可选) */ 105 | adaptCutout?: boolean; 106 | 107 | /** 似乎是废弃参数,无效(可选) */ 108 | useMiuiSplit?: boolean; 109 | 110 | /** Miui-Embedded 库的最小支持版本,不推荐配置(可选) */ 111 | minSupportVersion?: string; 112 | } -------------------------------------------------------------------------------- /src/types/EmbeddedSettingRuleItem.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 表示设置规则的配置项。 3 | */ 4 | export default interface EmbeddedSettingRuleItem { 5 | /** 应用包名(必填) */ 6 | name: string; 7 | 8 | /** 是否启用平行窗口规则,默认 false(可选) */ 9 | embeddedEnable?: boolean; 10 | 11 | /** 是否启用居中布局规则(Hyper OS 2.0+ 有效),默认 false(可选) */ 12 | fixedOrientationEnable?: boolean; 13 | 14 | /** 是否启用全屏规则(Hyper OS 2.0+ 有效),默认 false(可选) */ 15 | fullScreenEnable?: boolean; 16 | 17 | /** 配置是否已被修改(Hyper OS 2.0+ 有效),默认 false(可选) */ 18 | isModified?: boolean; 19 | 20 | /** 是否启用 16:9 比例(Hyper OS 2.0+ 有效),默认 false(可选) */ 21 | ratio_16_9_Enable?: boolean; 22 | 23 | /** 是否启用 4:3 比例(Hyper OS 2.0+ 有效),默认 false(可选) */ 24 | ratio_4_3_Enable?: boolean; 25 | 26 | /** 是否启用全屏比例(Hyper OS 2.0+ 有效),默认 false(可选) */ 27 | ratio_fullScreenEnable?: boolean; 28 | } -------------------------------------------------------------------------------- /src/types/EmbeddedSplitPairRuleItem.ts: -------------------------------------------------------------------------------- 1 | 2 | export default interface EmbeddedSplitPairRuleItem { 3 | leftActivityName: string 4 | rightActivityName: string 5 | }; -------------------------------------------------------------------------------- /src/types/ErrorLogging.d.ts: -------------------------------------------------------------------------------- 1 | export interface ErrorLogging { 2 | type: string; 3 | title: string; 4 | msg: string; 5 | } -------------------------------------------------------------------------------- /src/types/FixedOrientationRuleItem.d.ts: -------------------------------------------------------------------------------- 1 | // 定义 FixedOrientationRuleItem 的类型 2 | /** 3 | * 表示居中布局的规则配置项。 4 | */ 5 | export default interface FixedOrientationRuleItem { 6 | /** 应用包名(必填) */ 7 | name: string; 8 | 9 | /** 是否禁用该配置,默认 false(可选) */ 10 | disable?: boolean; 11 | 12 | /** 是否启用缩放,默认 false(可选) */ 13 | isScale?: boolean; 14 | 15 | /** 是否显示平行窗口滑动条,默认 false(可选) */ 16 | isShowDivider?: boolean; 17 | 18 | /** 配置比例(例如:窗口缩放的比例),取值范围1.01-1.99(可选) */ 19 | ratio?: number; 20 | 21 | /** 当比例变化时是否重启应用,默认 false(可选) */ 22 | relaunch?: boolean; 23 | 24 | /** 重启规则(当 relaunch 为 true 时生效)(可选) */ 25 | relaunchRule?: string; 26 | 27 | /** 跳过兼容模式,默认 false(可选) */ 28 | skipCompatMode?: boolean; 29 | 30 | /** 跳过应用自适配,默认 false(可选) */ 31 | skipSelfAdaptive?: boolean; 32 | 33 | /** 应用兼容性变更配置,默认为空(可选) */ 34 | compatChange?: string; 35 | 36 | /** 是否全部强制竖屏,默认 false(可选) */ 37 | allPortrait?: boolean; 38 | 39 | /** 是否自适应前摄挖孔,仅折叠屏生效,默认 false(可选) */ 40 | adaptCutout?: boolean; 41 | 42 | /** 是否允许嵌入竖屏模式,默认 false(可选) */ 43 | allowEmbInPortrait?: boolean; 44 | 45 | /** 支持的模式列表(Hyper OS 2.0+可用),例如full,fo,支持全屏和居中布局(可选) */ 46 | supportModes?: string; 47 | 48 | /** 是否支持相机预览,默认 false(可选) */ 49 | supportCameraPreview?: boolean; 50 | 51 | /** 是否启用透明状态栏,默认 false(可选) */ 52 | transparentBar?: boolean; 53 | 54 | /** 平行窗口应用布局优化规则(可选) */ 55 | autoUI?: string; 56 | 57 | /** 是否配置默认设置(Hyper OS 2.0+可用),例如full,默认全屏(可选) */ 58 | defaultSettings?: boolean | 'full' | 'ae' | 'fo'; 59 | 60 | /** 是否禁用相机预览,默认 false(可选) */ 61 | disableCameraPreview?: boolean; 62 | 63 | /** 切换时是否强制终止应用,默认 false(可选) */ 64 | forceKillWhenSwitch?: boolean; 65 | 66 | /** 强制竖屏显示的 Activity 列表(可选) */ 67 | forcePortraitActivity?: string; 68 | 69 | /** 全屏强制竖屏显示的 Activity 列表(可选) */ 70 | fullForcePortraitActivity?: string; 71 | 72 | /** 搭配 isShowDivider 使用,支持将一侧平行窗口拉伸至全屏,默认 false(可选) */ 73 | supportFullSize?: boolean; 74 | } 75 | -------------------------------------------------------------------------------- /src/types/GameBoosterTableItem.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 表示游戏显示布局的规则配置项。 3 | */ 4 | export default interface GameBoosterTableItem { 5 | /** 包名 */ 6 | package_name: string; 7 | 8 | /** 应用名称 */ 9 | app_name: string; 10 | 11 | /** 游戏显示比例 */ 12 | // 0.0 | 2.3333333 | 1.3333333 | 1.7777778 13 | game_ratio: string; 14 | 15 | /** 游戏显示位置 居底80,居顶48,居中17 */ 16 | // 17 | 48 | 80 17 | game_gravity: string; 18 | } 19 | -------------------------------------------------------------------------------- /src/types/PackageItem.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 表示Android应用的包名信息 3 | */ 4 | export default interface PackageItem { 5 | /** 应用包名(必填) */ 6 | package_name: string; 7 | 8 | /** 应用名称 (必填) */ 9 | app_name: string; 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/utils/autoUIFun.ts: -------------------------------------------------------------------------------- 1 | 2 | export const getBuiltInSettingMode = (activityRule: string) => { 3 | if (!activityRule) { 4 | return 'UNDEFINED_VIEW_POLICY' 5 | } 6 | if (activityRule === '*:0') { 7 | return 'VIEW_POLICY_DEFAULT' 8 | } 9 | if (activityRule === '*:1') { 10 | return 'VIEW_POLICY_STRETCH' 11 | } 12 | if (activityRule === '*:2') { 13 | return 'VIEW_POLICY_AUTO_COLUMNS' 14 | } 15 | if (activityRule === '*:6') { 16 | return 'VIEW_POLICY_FLOAT' 17 | } 18 | 19 | return 'CUSTOM_VIEW_POLICY' 20 | } -------------------------------------------------------------------------------- /src/utils/common.ts: -------------------------------------------------------------------------------- 1 | export const findBase64InString = (input: string): string | null => { 2 | const base64Pattern = /([A-Za-z0-9+/=]{16,})/g; // 至少16个字符的Base64 3 | const match = input.match(base64Pattern); 4 | 5 | return match && match.length > 0 ? match[0] : null; // 返回第一个匹配的Base64字符串,若无则返回null 6 | } 7 | 8 | export const renderApplicationName = (name:string,packageName?:string) => { 9 | return packageName ? `${packageName}(${name})` : name; 10 | } 11 | 12 | export const parsePropContent = (content: string): { [key: string]: string } => { 13 | const result: { [key: string]: string } = {}; 14 | content.split('\n').forEach(line => { 15 | line = line.trim(); 16 | if (line && !line.startsWith('#')) { 17 | const [key, value] = line.split('=') as [string, string]; 18 | if (key && value) { 19 | result[key.trim()] = value.trim(); 20 | } 21 | } 22 | }); 23 | return result; 24 | }; -------------------------------------------------------------------------------- /src/utils/embeddedFun.ts: -------------------------------------------------------------------------------- 1 | import type { ThirdPartyAppOptimizeAppModeType } from '@/stores/embedded'; 2 | import type EmbeddedMergeRuleItem from '@/types/EmbeddedMergeRuleItem'; 3 | import type EmbeddedRuleItem from '@/types/EmbeddedRuleItem'; 4 | import type FixedOrientationRuleItem from '@/types/FixedOrientationRuleItem'; 5 | 6 | type SettingModeType = EmbeddedMergeRuleItem['settingMode']; 7 | 8 | export interface SettingSupportEnableMode { 9 | embeddedEnable?: boolean 10 | fullScreenEnable?: boolean 11 | fixedOrientationEnable?: boolean 12 | ratio_fullScreenEnable?: boolean 13 | } 14 | 15 | export const getSettingEnableMode = (embeddedConfig: EmbeddedRuleItem, fixedOrientationConfig: FixedOrientationRuleItem,settingMode:EmbeddedMergeRuleItem['settingMode']) => { 16 | 17 | const supportModes = fixedOrientationConfig ? fixedOrientationConfig.supportModes?.split(',') : null; 18 | 19 | const defaultSettings = fixedOrientationConfig ? fixedOrientationConfig.defaultSettings : null; 20 | 21 | const enableModes: SettingSupportEnableMode = {} 22 | 23 | if (settingMode === 'embedded') { 24 | enableModes.embeddedEnable = true 25 | } else { 26 | delete enableModes.embeddedEnable 27 | } 28 | 29 | if (settingMode === 'fullScreen') { 30 | enableModes.ratio_fullScreenEnable = true; 31 | if (embeddedConfig && embeddedConfig.hasOwnProperty('fullRule')) { 32 | enableModes.ratio_fullScreenEnable = true; 33 | enableModes.fullScreenEnable = true; 34 | } else { 35 | delete enableModes.fullScreenEnable 36 | } 37 | } else { 38 | delete enableModes.ratio_fullScreenEnable 39 | delete enableModes.fullScreenEnable 40 | } 41 | 42 | if (fixedOrientationConfig) { 43 | if (settingMode === 'fixedOrientation') { 44 | enableModes.fixedOrientationEnable = true 45 | } else { 46 | enableModes.fixedOrientationEnable = false 47 | } 48 | } 49 | 50 | return enableModes 51 | 52 | } 53 | 54 | export const getSettingMode = (embeddedConfig: EmbeddedRuleItem, fixedOrientationConfig: FixedOrientationRuleItem) => { 55 | const getSupportModes = fixedOrientationConfig?.supportModes?.split(','); 56 | const getDefaultSettings = fixedOrientationConfig?.defaultSettings; 57 | let settingMode: EmbeddedMergeRuleItem['settingMode'] = 'disabled'; 58 | if (fixedOrientationConfig) { 59 | settingMode = 'fixedOrientation'; 60 | } 61 | if (fixedOrientationConfig && fixedOrientationConfig.hasOwnProperty('disable') && fixedOrientationConfig.disable) { 62 | settingMode = 'disabled'; 63 | } 64 | if ( 65 | fixedOrientationConfig && 66 | fixedOrientationConfig.hasOwnProperty('supportModes') && 67 | getSupportModes?.includes('full') && 68 | (!getDefaultSettings || getDefaultSettings === 'full') 69 | ) { 70 | settingMode = 'fullScreen'; 71 | } 72 | if ( 73 | embeddedConfig && 74 | !embeddedConfig.hasOwnProperty('fullRule') && 75 | (!getDefaultSettings || getDefaultSettings === 'ae') 76 | ) { 77 | settingMode = 'embedded'; 78 | } 79 | return settingMode; 80 | }; 81 | 82 | export const getAppModeCode = (settingMode: EmbeddedMergeRuleItem['settingMode']) => { 83 | if (settingMode === 'disabled') { 84 | return 0; 85 | } 86 | 87 | if (settingMode === 'embedded') { 88 | return 1; 89 | } 90 | 91 | if (settingMode === 'fixedOrientation') { 92 | return 2; 93 | } 94 | 95 | if (settingMode === 'fullScreen') { 96 | return 3; 97 | } 98 | 99 | throw new Error('wrong error AppModeCode!'); 100 | }; 101 | 102 | export const getAppMode = (settingMode: 0 | 1 | 2 | 3) => { 103 | if (settingMode === 0) { 104 | return 'disabled'; 105 | } 106 | 107 | if (settingMode === 1) { 108 | return 'embedded'; 109 | } 110 | 111 | if (settingMode === 2) { 112 | return 'fixedOrientation'; 113 | } 114 | 115 | if (settingMode === 3) { 116 | return 'fullScreen'; 117 | } 118 | 119 | throw new Error('wrong error AppMode!'); 120 | }; 121 | 122 | 123 | export const thirdPartyAppOptimizeConfigFormatToJSON = (data: string) => { 124 | return data.trim().split('\n').reduce((acc, line) => { 125 | const [name, mode] = line.split(':'); 126 | acc[name] = Number(mode) as ThirdPartyAppOptimizeAppModeType; // 将数字映射为对应的 settingMode 127 | return acc; 128 | }, {} as Record); 129 | } 130 | 131 | export const thirdPartyAppOptimizeJSONFormatToProp = (jsonData: Record) => { 132 | return Object.entries(jsonData).map(([key, value]) => { 133 | return `${key}:${value}`; 134 | }).join('\n'); 135 | } 136 | 137 | export const thirdPartyAppOptimizeJSONFormatToRunnerShell = (jsonData: Record) => { 138 | return Object.entries(jsonData) 139 | .map(([packageName, mode]) => `cmd miui_embedding_window set-appMode ${packageName} ${mode}`) 140 | .join('\n'); 141 | } 142 | 143 | export const UFSHealthType = (num:number) => { 144 | if (num < 5) { 145 | return 'success' 146 | } else if (num < 7) { 147 | return 'warning' 148 | } else { 149 | return 'error' 150 | } 151 | } -------------------------------------------------------------------------------- /src/utils/eventBus.ts: -------------------------------------------------------------------------------- 1 | import mitt from 'mitt'; 2 | 3 | const eventBus = mitt(); 4 | 5 | export function once(event: string, handler: (data: T) => void): void { 6 | const onceHandler = (data: unknown) => { 7 | handler(data as T); 8 | eventBus.off(event, onceHandler); // 移除事件监听 9 | }; 10 | eventBus.on(event, onceHandler); 11 | } 12 | 13 | export default { 14 | once, 15 | ...eventBus 16 | }; -------------------------------------------------------------------------------- /src/utils/format.ts: -------------------------------------------------------------------------------- 1 | // 将 Uint8Array 转换为 Base64 字符串 2 | export function arrayBufferToBase64(buffer: Uint8Array): string { 3 | let binary = ''; 4 | let bytes = new Uint8Array(buffer); 5 | let len = bytes.byteLength; 6 | for (let i = 0; i < len; i++) { 7 | binary += String.fromCharCode(bytes[i]); 8 | } 9 | return window.btoa(binary); // 使用 btoa 转换为 Base64 10 | } 11 | 12 | // 将 Base64 字符串转换为 Uint8Array 13 | export function base64ToArrayBuffer(base64: string): Uint8Array { 14 | const binaryString = window.atob(base64); // 使用 atob 解码 15 | const len = binaryString.length; 16 | const bytes = new Uint8Array(len); 17 | 18 | for (let i = 0; i < len; i++) { 19 | bytes[i] = binaryString.charCodeAt(i); 20 | } 21 | 22 | return bytes; 23 | } 24 | -------------------------------------------------------------------------------- /src/utils/handlePromiseWithLogging.ts: -------------------------------------------------------------------------------- 1 | import { useLogsStore } from "@/stores/logs"; 2 | 3 | const handlePromiseWithLogging = (promise: Promise,title = 'Uncaught') => { 4 | return promise 5 | .then(result => { 6 | logBasedOnType('Resolved', result, title); 7 | return result; 8 | }) 9 | .catch(error => { 10 | logBasedOnType('Rejected', error,title); 11 | throw error; // 重新抛出错误以维持 Promise 链的正确性 12 | }); 13 | }; 14 | 15 | const logBasedOnType = (status: 'Resolved' | 'Rejected', data: any,title:string) => { 16 | const logsStore = useLogsStore(); 17 | const logFuncName = status === 'Resolved' ? 'success' : 'error' 18 | if (typeof data === 'string') { 19 | if (data.trim().startsWith('<') && data.trim().endsWith('>')) { 20 | logsStore[logFuncName](`${status}:${title}`, 'XML data detected.'); 21 | } else { 22 | logsStore[logFuncName](`${status}:${title}`, data); 23 | } 24 | } else if (typeof data === 'object') { 25 | if (Array.isArray(data)) { 26 | logsStore[logFuncName](`${status}:${title}`, JSON.stringify(data, null, 2)); // 数组 27 | } else if (data instanceof Error) { 28 | logsStore.error(`${status}::${title}`, data.message); // 错误对象 29 | } else { 30 | logsStore[logFuncName](`${status}:${title}`, JSON.stringify(data, null, 2)); // 对象 31 | } 32 | } else if (typeof data === 'function') { 33 | logsStore[logFuncName](`${status}:${title}`, `Function called ${data.name ? data.name : 'anonymous function'}`); 34 | } else { 35 | logsStore[logFuncName](`${status}:${title}`, data); // 其他类型 36 | } 37 | }; 38 | 39 | export default handlePromiseWithLogging -------------------------------------------------------------------------------- /src/utils/kernelsu/index.d.ts: -------------------------------------------------------------------------------- 1 | interface ExecOptions { 2 | cwd?: string, 3 | env?: { [key: string]: string } 4 | } 5 | 6 | export interface ExecResults { 7 | errno: number, 8 | stdout: string, 9 | stderr: string 10 | } 11 | 12 | declare function exec(command: string): Promise; 13 | declare function exec(command: string, options: ExecOptions): Promise; 14 | 15 | interface SpawnOptions { 16 | cwd?: string, 17 | env?: { [key: string]: string } 18 | } 19 | 20 | interface Stdio { 21 | on(event: 'data', callback: (data: string) => void) 22 | } 23 | 24 | interface ChildProcess { 25 | stdout: Stdio, 26 | stderr: Stdio, 27 | on(event: 'exit', callback: (code: number) => void) 28 | on(event: 'error', callback: (err: any) => void) 29 | } 30 | 31 | declare function spawn(command: string): ChildProcess; 32 | declare function spawn(command: string, args: string[]): ChildProcess; 33 | declare function spawn(command: string, options: SpawnOptions): ChildProcess; 34 | declare function spawn(command: string, args: string[], options: SpawnOptions): ChildProcess; 35 | 36 | declare function fullScreen(isFullScreen: boolean); 37 | 38 | declare function toast(message: string); 39 | 40 | declare function moduleInfo(): string; 41 | 42 | export { 43 | exec, 44 | spawn, 45 | fullScreen, 46 | toast, 47 | moduleInfo 48 | } -------------------------------------------------------------------------------- /src/utils/kernelsu/index.js: -------------------------------------------------------------------------------- 1 | let callbackCounter = 0; 2 | function getUniqueCallbackName(prefix) { 3 | return `${prefix}_callback_${Date.now()}_${callbackCounter++}`; 4 | } 5 | 6 | export function exec(command, options) { 7 | if (typeof options === "undefined") { 8 | options = {}; 9 | } 10 | 11 | return new Promise((resolve, reject) => { 12 | // Generate a unique callback function name 13 | const callbackFuncName = getUniqueCallbackName("exec"); 14 | 15 | // Define the success callback function 16 | window[callbackFuncName] = (errno, stdout, stderr) => { 17 | resolve({ errno, stdout, stderr }); 18 | cleanup(callbackFuncName); 19 | }; 20 | 21 | function cleanup(successName) { 22 | delete window[successName]; 23 | } 24 | 25 | try { 26 | ksu.exec(command, JSON.stringify(options), callbackFuncName); 27 | } catch (error) { 28 | reject(error); 29 | cleanup(callbackFuncName); 30 | } 31 | }); 32 | } 33 | 34 | class Stdio { 35 | constructor() { 36 | this.listeners = {}; 37 | } 38 | on(event, listener) { 39 | if (!this.listeners[event]) { 40 | this.listeners[event] = []; 41 | } 42 | this.listeners[event].push(listener); 43 | } 44 | emit(event, ...args) { 45 | if (this.listeners[event]) { 46 | this.listeners[event].forEach((listener) => listener(...args)); 47 | } 48 | } 49 | } 50 | 51 | 52 | 53 | class ChildProcess { 54 | constructor() { 55 | this.listeners = {}; 56 | this.stdin = new Stdio(); 57 | this.stdout = new Stdio(); 58 | this.stderr = new Stdio(); 59 | } 60 | on(event, listener) { 61 | if (!this.listeners[event]) { 62 | this.listeners[event] = []; 63 | } 64 | this.listeners[event].push(listener); 65 | } 66 | emit(event, ...args) { 67 | if (this.listeners[event]) { 68 | this.listeners[event].forEach((listener) => listener(...args)); 69 | } 70 | } 71 | } 72 | 73 | 74 | 75 | export function spawn(command, args, options) { 76 | if (typeof args === "undefined") { 77 | args = []; 78 | } else if (!(args instanceof Array)) { 79 | // allow for (command, options) signature 80 | options = args; 81 | } 82 | 83 | if (typeof options === "undefined") { 84 | options = {}; 85 | } 86 | 87 | const child = new ChildProcess(); 88 | const childCallbackName = getUniqueCallbackName("spawn"); 89 | window[childCallbackName] = child; 90 | 91 | function cleanup(name) { 92 | delete window[name]; 93 | } 94 | 95 | child.on("exit", code => { 96 | cleanup(childCallbackName); 97 | }); 98 | 99 | try { 100 | ksu.spawn( 101 | command, 102 | JSON.stringify(args), 103 | JSON.stringify(options), 104 | childCallbackName 105 | ); 106 | } catch (error) { 107 | child.emit("error", error); 108 | cleanup(childCallbackName); 109 | } 110 | return child; 111 | } 112 | 113 | export function fullScreen(isFullScreen) { 114 | ksu.fullScreen(isFullScreen); 115 | } 116 | 117 | export function toast(message) { 118 | ksu.toast(message); 119 | } 120 | 121 | export function moduleInfo() { 122 | return ksu.moduleInfo(); 123 | } -------------------------------------------------------------------------------- /src/utils/sqio.ts: -------------------------------------------------------------------------------- 1 | import $to from 'await-to-js' 2 | import axios from 'axios'; 3 | import handlePromiseWithLogging from "@/utils/handlePromiseWithLogging"; 4 | import { 5 | exec, 6 | type ExecResults 7 | } from "@/utils/kernelsu/index.js"; 8 | export default async function sqioInstance(cmd: string) { 9 | return handlePromiseWithLogging(new Promise(async (resolve, reject) => { 10 | if (import.meta.env.MODE === "development") { 11 | const [sqioErr, sqioRes] = await $to(axios.get('/api/exec', { 12 | params: { 13 | cmd: `C:\\Users\\sothx\\scoop\\shims\\sq.exe '${cmd}'` 14 | } 15 | })) 16 | if (sqioErr) { 17 | console.log(sqioErr,'err') 18 | reject(sqioErr.response.data) 19 | } else { 20 | console.log(sqioRes,'res') 21 | resolve(sqioRes) 22 | } 23 | } else { 24 | const { errno, stdout, stderr }: ExecResults = await exec( 25 | `/data/adb/modules/MIUI_MagicWindow+/common/utils/sq ${cmd}` 26 | ); 27 | errno ? reject(stderr) : resolve(stdout); 28 | } 29 | }), `sq ${cmd}`) 30 | } -------------------------------------------------------------------------------- /src/utils/validateFun.ts: -------------------------------------------------------------------------------- 1 | export const validateAndroidPackageName = (packageName: string): boolean => { 2 | // 正则表达式校验安卓包名规则 3 | const packageNameRegex = /^[a-zA-Z0-9_.]+$/; 4 | 5 | // 允许空字符串 6 | if (packageName === '') { 7 | return true; 8 | } 9 | 10 | // 检查是否包含空格 11 | if (/\s/.test(packageName)) { 12 | return false; 13 | } 14 | 15 | // 校验是否符合安卓包名规范 16 | if (!packageNameRegex.test(packageName)) { 17 | return false; 18 | } 19 | 20 | return true; 21 | } 22 | 23 | export const validateFullRule = (packageName: string): boolean => { 24 | // 正则表达式校验全屏规则 25 | const fullRuleRegex = /^[a-zA-Z*:]+$/; 26 | 27 | // 允许空字符串 28 | if (packageName === '') { 29 | return true; 30 | } 31 | 32 | // 检查是否包含空格 33 | if (/\s/.test(packageName)) { 34 | return false; 35 | } 36 | 37 | // 校验是否符合全屏规则 38 | if (!fullRuleRegex.test(packageName)) { 39 | return false; 40 | } 41 | 42 | return true; 43 | } 44 | 45 | export const vaildateActivityName = (activity: string): boolean => { 46 | // 正则表达式校验全屏规则 47 | const activityRegex = /^[a-zA-Z_][\w]*(\.[a-zA-Z_][\w]*)*$/; 48 | 49 | // 允许空字符串 50 | if (activity === '') { 51 | return true; 52 | } 53 | 54 | // 检查是否包含空格 55 | if (/\s/.test(activity)) { 56 | return false; 57 | } 58 | 59 | // 校验是否符合Activity规则 60 | if (!activityRegex.test(activity)) { 61 | return false; 62 | } 63 | 64 | return true; 65 | } 66 | 67 | 68 | export const validateAutoUIRule = (activityRule: string): boolean => { 69 | // 正则表达式校验应用布局优化规则 70 | const autoUIRuleRegex = /^[a-zA-Z0-9_:,.*-]+$/; 71 | 72 | // 允许空字符串 73 | if (activityRule === '') { 74 | return true; 75 | } 76 | 77 | // 检查是否包含空格 78 | if (/\s/.test(activityRule)) { 79 | return false; 80 | } 81 | 82 | // 校验是否符合 83 | if (!autoUIRuleRegex.test(activityRule)) { 84 | return false; 85 | } 86 | 87 | return true; 88 | } 89 | 90 | 91 | export const validateAutoUISkipActivityRule = (activityRule: string): boolean => { 92 | // 正则表达式校验应用布局优化规则 93 | const autoUISkipActivityRuleRegex = /^[a-zA-Z0-9_;.]+$/; 94 | 95 | // 允许空字符串 96 | if (activityRule === '') { 97 | return true; 98 | } 99 | 100 | // 检查是否包含空格 101 | if (/\s/.test(activityRule)) { 102 | return false; 103 | } 104 | 105 | // 校验是否符合 106 | if (!autoUISkipActivityRuleRegex.test(activityRule)) { 107 | return false; 108 | } 109 | 110 | return true; 111 | } 112 | 113 | 114 | export const validateNotEmpty = (input: string): boolean => { 115 | return input.trim() !== ''; 116 | }; 117 | -------------------------------------------------------------------------------- /src/views/AboutView.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 16 | -------------------------------------------------------------------------------- /src/views/EmbeddedWebView.vue: -------------------------------------------------------------------------------- 1 | 56 | 57 | 70 | 71 | 107 | -------------------------------------------------------------------------------- /src/views/GameTurboConfig.vue: -------------------------------------------------------------------------------- 1 | 47 | 69 | 70 | 75 | -------------------------------------------------------------------------------- /src/views/HappyNewYearEgg.vue: -------------------------------------------------------------------------------- 1 | 13 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/views/LogView.vue: -------------------------------------------------------------------------------- 1 | 95 | 185 | 186 | 191 | -------------------------------------------------------------------------------- /src/views/NotFoundView.vue: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from 'tailwindcss'; 2 | 3 | export default { 4 | mode: 'jit', 5 | content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'], 6 | theme: { 7 | extend: { 8 | colors: { 9 | 'sothx-gray-color': 'rgba(255, 255, 255, 0.09)' 10 | } 11 | }, 12 | screens: { 13 | 'sm': '640px', 14 | 'md': '768px', 15 | 'lg': '896px', // 修改默认 lg 断点 16 | 'xl': '1024px', 17 | '2xl': '1280px', 18 | }, 19 | } 20 | } satisfies Config; 21 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.dom.json", 3 | "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], 4 | "exclude": ["src/**/__tests__/*"], 5 | "compilerOptions": { 6 | "composite": true, 7 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 8 | 9 | "baseUrl": ".", 10 | "paths": { 11 | "@/*": ["./src/*"] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { 5 | "path": "./tsconfig.node.json" 6 | }, 7 | { 8 | "path": "./tsconfig.app.json" 9 | } 10 | ], 11 | "compilerOptions": { 12 | "lib": ["DOM","ESNext"], 13 | "target": "ESNext", 14 | "module": "ESNext", 15 | "strict": true, 16 | "verbatimModuleSyntax": true, 17 | "jsx": "preserve", 18 | "jsxImportSource": "vue" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node20/tsconfig.json", 3 | "include": [ 4 | "vite.config.*", 5 | "vitest.config.*", 6 | "cypress.config.*", 7 | "nightwatch.conf.*", 8 | "playwright.config.*" 9 | ], 10 | "compilerOptions": { 11 | "composite": true, 12 | "noEmit": true, 13 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 14 | 15 | "module": "ESNext", 16 | "moduleResolution": "Bundler", 17 | "types": ["node"] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { parse } from 'node:url'; 2 | import { defineConfig } from 'vite'; 3 | import vue from '@vitejs/plugin-vue'; 4 | import { exec } from 'child_process'; 5 | import tailwindcss from 'tailwindcss'; 6 | import vueJsx from '@vitejs/plugin-vue-jsx'; 7 | import { fileURLToPath, URL } from 'node:url'; 8 | import postcssPresetEnv from 'postcss-preset-env'; 9 | import vueDevTools from 'vite-plugin-vue-devtools'; 10 | 11 | export default defineConfig({ 12 | css: { 13 | postcss: { 14 | plugins: [ 15 | tailwindcss(), 16 | postcssPresetEnv({ 17 | stage: 4, 18 | }), 19 | ], 20 | }, 21 | }, 22 | plugins: [ 23 | vue(), 24 | vueJsx(), 25 | vueDevTools(), 26 | { 27 | name: 'run-powershell-command', 28 | configureServer(server) { 29 | server.middlewares.use('/api/exec', (req, res) => { 30 | if (process.env.NODE_ENV === 'development') { 31 | const queryObject = parse(req.url as string, true).query; 32 | const command = queryObject.cmd as string; // 默认命令 33 | const shell = process.platform === 'win32' ? 'powershell.exe' : '/bin/bash'; 34 | if (command) { 35 | exec( 36 | command, 37 | { 38 | shell, 39 | }, 40 | (error, stdout, stderr) => { 41 | if (error) { 42 | res.statusCode = 500; 43 | res.end(`Error: ${error.message}`); 44 | return; 45 | } 46 | if (stderr) { 47 | res.statusCode = 500; 48 | res.end(`Error: ${stderr}`); 49 | return; 50 | } 51 | res.statusCode = 200; 52 | res.end(stdout); // 返回命令执行结果 53 | }, 54 | ); 55 | } else { 56 | res.statusCode = 500; 57 | res.end(`Error: unknow command`); 58 | } 59 | } 60 | }); 61 | }, 62 | }, 63 | // visualizer({ 64 | // open: true, 65 | // gzipSize: false 66 | // }), 67 | // legacy({ 68 | // targets: ['defaults', 'not IE 11', 'chrome >= 87', 'android >= 5.0'], 69 | // additionalLegacyPolyfills: ['regenerator-runtime/runtime'], 70 | // renderLegacyChunks: true, 71 | // polyfills: [ 72 | // 'es.symbol', 73 | // 'es.promise', 74 | // 'es.promise.finally', 75 | // 'es/map', 76 | // 'es/set', 77 | // 'es.array.filter', 78 | // 'es.array.for-each', 79 | // 'es.array.flat-map', 80 | // 'es.object.define-properties', 81 | // 'es.object.define-property', 82 | // 'es.object.get-own-property-descriptor', 83 | // 'es.object.get-own-property-descriptors', 84 | // 'es.object.keys', 85 | // 'es.object.to-string', 86 | // 'web.dom-collections.for-each', 87 | // 'esnext.global-this', 88 | // 'esnext.string.match-all' 89 | // ] 90 | // }) 91 | ], 92 | build: { 93 | minify:'terser', 94 | rollupOptions: { 95 | output: { 96 | manualChunks(id) { 97 | if (id.includes('pako')) { 98 | return 'pako'; 99 | } 100 | if (id.includes('iconfont')) { 101 | return 'iconfont'; 102 | } 103 | if (id.includes('lodash')) { 104 | return 'lodash'; 105 | } 106 | if (id.includes('hooks')) { 107 | return 'hooks'; 108 | } 109 | if (id.includes('apis')) { 110 | return 'apis'; 111 | } 112 | } 113 | } 114 | } 115 | }, 116 | assetsInclude: ['**/*.xml'], // 添加这一行以包括 XML 文件 117 | resolve: { 118 | alias: { 119 | '@': fileURLToPath(new URL('./src', import.meta.url)), 120 | }, 121 | }, 122 | }); 123 | --------------------------------------------------------------------------------