├── cc-cli ├── entry.js ├── src │ ├── detail.config │ ├── home.config │ ├── home.css │ ├── detail.css │ ├── home.html │ ├── detail.js │ ├── detail.html │ ├── home.js │ ├── home.json │ └── detail.json ├── src_bak │ ├── example.config │ ├── example.css │ ├── for.css │ ├── for.js │ ├── for.html │ ├── for.json │ ├── example.html │ └── example.js ├── env.js ├── tpl │ ├── example.css.tpl │ ├── example.html.tpl │ └── example.js.tpl ├── transform.js ├── wppackage.js ├── webpack.prod.js ├── README.md ├── test.js ├── config.js ├── logger.js ├── command-tpl.js ├── package.json ├── webpack.common.js ├── zip.js ├── start.js ├── index.js └── watch.js ├── HybridFlutter ├── ios │ ├── Flutter │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ ├── flutter_export_environment.sh │ │ └── AppFrameworkInfo.plist │ ├── Runner │ │ ├── AppDelegate.h │ │ ├── Assets.xcassets │ │ │ ├── LaunchImage.imageset │ │ │ │ ├── LaunchImage.png │ │ │ │ ├── LaunchImage@2x.png │ │ │ │ ├── LaunchImage@3x.png │ │ │ │ ├── README.md │ │ │ │ └── Contents.json │ │ │ └── AppIcon.appiconset │ │ │ │ ├── Icon-App-20x20@1x.png │ │ │ │ ├── Icon-App-20x20@2x.png │ │ │ │ ├── Icon-App-20x20@3x.png │ │ │ │ ├── Icon-App-29x29@1x.png │ │ │ │ ├── Icon-App-29x29@2x.png │ │ │ │ ├── Icon-App-29x29@3x.png │ │ │ │ ├── Icon-App-40x40@1x.png │ │ │ │ ├── Icon-App-40x40@2x.png │ │ │ │ ├── Icon-App-40x40@3x.png │ │ │ │ ├── Icon-App-60x60@2x.png │ │ │ │ ├── Icon-App-60x60@3x.png │ │ │ │ ├── Icon-App-76x76@1x.png │ │ │ │ ├── Icon-App-76x76@2x.png │ │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ │ └── Contents.json │ │ ├── main.m │ │ ├── AppDelegate.m │ │ ├── Info.plist │ │ └── Base.lproj │ │ │ ├── Main.storyboard │ │ │ └── LaunchScreen.storyboard │ ├── Runner.xcworkspace │ │ └── contents.xcworkspacedata │ └── Runner.xcodeproj │ │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── android │ ├── gradle.properties │ ├── app │ │ ├── src │ │ │ └── main │ │ │ │ ├── res │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── xml │ │ │ │ │ └── network_security_config.xml │ │ │ │ ├── layout │ │ │ │ │ └── dialog_loading.xml │ │ │ │ ├── drawable │ │ │ │ │ └── launch_background.xml │ │ │ │ └── values │ │ │ │ │ └── styles.xml │ │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── cc │ │ │ │ │ └── hybrid │ │ │ │ │ ├── http │ │ │ │ │ ├── exception │ │ │ │ │ │ └── HttpParamsException.kt │ │ │ │ │ ├── HttpRequestUtil.kt │ │ │ │ │ └── HttpRequest.kt │ │ │ │ │ ├── bridge │ │ │ │ │ ├── js │ │ │ │ │ │ ├── JSConsole.kt │ │ │ │ │ │ └── JSNetwork.kt │ │ │ │ │ └── flutter │ │ │ │ │ │ ├── Methods.kt │ │ │ │ │ │ └── FlutterPluginMethodChannel.kt │ │ │ │ │ ├── MFlutterApplication.kt │ │ │ │ │ ├── util │ │ │ │ │ ├── SpUtil.kt │ │ │ │ │ ├── ToastUtil.kt │ │ │ │ │ ├── LoadingUtil.kt │ │ │ │ │ └── TimerManager.kt │ │ │ │ │ ├── Logger.kt │ │ │ │ │ ├── event │ │ │ │ │ └── EventManager.kt │ │ │ │ │ ├── debug │ │ │ │ │ └── Debugger.kt │ │ │ │ │ ├── v8 │ │ │ │ │ ├── V8Manager.kt │ │ │ │ │ └── V8PropertyMap.java │ │ │ │ │ └── MainActivity.kt │ │ │ │ ├── AndroidManifest.xml │ │ │ │ └── assets │ │ │ │ └── framework.js │ │ ├── .classpath │ │ ├── .project │ │ └── build.gradle │ ├── .gitignore │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ ├── .project │ ├── settings.gradle │ └── build.gradle ├── lib │ ├── entity │ │ ├── data.dart │ │ ├── property.dart │ │ └── component.dart │ ├── util │ │ ├── base64.dart │ │ ├── event_util.dart │ │ ├── color_util.dart │ │ └── expression_util.dart │ └── ui │ │ ├── expanded.dart │ │ ├── center.dart │ │ ├── image.dart │ │ ├── aspect_ratio.dart │ │ ├── single_child_scrollview.dart │ │ ├── visibility.dart │ │ ├── positioned.dart │ │ ├── fractionally_sized_box.dart │ │ ├── text.dart │ │ ├── container.dart │ │ ├── stack.dart │ │ ├── circular_progress_indicator.dart │ │ ├── row.dart │ │ ├── column.dart │ │ ├── base_widget.dart │ │ ├── raised_button.dart │ │ └── list_view.dart ├── .metadata ├── README.md ├── test │ └── widget_test.dart ├── .gitignore └── pubspec.yaml ├── image ├── api.gif ├── total.gif ├── file-dir.png └── real-time.gif └── webpack-framework ├── index.html ├── README.md ├── package.json ├── webpack.config.js ├── src ├── framework.js ├── page.js └── observer.js └── dist └── framework.js /cc-cli/entry.js: -------------------------------------------------------------------------------- 1 | module.exports = '' -------------------------------------------------------------------------------- /HybridFlutter/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /HybridFlutter/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /image/api.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubliruiyuan/HybridFlutter/HEAD/image/api.gif -------------------------------------------------------------------------------- /image/total.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubliruiyuan/HybridFlutter/HEAD/image/total.gif -------------------------------------------------------------------------------- /cc-cli/src/detail.config: -------------------------------------------------------------------------------- 1 | { 2 | "navigationBarTitleText": "", 3 | "backgroundColor": "#eeeeee" 4 | } -------------------------------------------------------------------------------- /image/file-dir.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubliruiyuan/HybridFlutter/HEAD/image/file-dir.png -------------------------------------------------------------------------------- /image/real-time.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubliruiyuan/HybridFlutter/HEAD/image/real-time.gif -------------------------------------------------------------------------------- /HybridFlutter/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true -------------------------------------------------------------------------------- /cc-cli/src/home.config: -------------------------------------------------------------------------------- 1 | { 2 | "navigationBarTitleText": "", 3 | "backgroundColor": "#eeeeee", 4 | "enablePullDownRefresh": true 5 | } -------------------------------------------------------------------------------- /cc-cli/src_bak/example.config: -------------------------------------------------------------------------------- 1 | { 2 | "navigationBarTitleText": "", 3 | "backgroundColor": "#eeeeee", 4 | "enablePullDownRefresh": true 5 | } -------------------------------------------------------------------------------- /HybridFlutter/ios/Runner/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /cc-cli/env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | cmd: { 3 | watch: 'devcp', 4 | dev: 'dev' 5 | }, 6 | cache: { 7 | entryfilename: 'entry.js' 8 | } 9 | } -------------------------------------------------------------------------------- /cc-cli/src_bak/example.css: -------------------------------------------------------------------------------- 1 | /* example */ 2 | .item-container { 3 | margin-top:10; 4 | margin-left: 10; 5 | margin-right: 10; 6 | padding:10; 7 | color: white; 8 | } -------------------------------------------------------------------------------- /HybridFlutter/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubliruiyuan/HybridFlutter/HEAD/HybridFlutter/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /HybridFlutter/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubliruiyuan/HybridFlutter/HEAD/HybridFlutter/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /HybridFlutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubliruiyuan/HybridFlutter/HEAD/HybridFlutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /HybridFlutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubliruiyuan/HybridFlutter/HEAD/HybridFlutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /HybridFlutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubliruiyuan/HybridFlutter/HEAD/HybridFlutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /cc-cli/src_bak/for.css: -------------------------------------------------------------------------------- 1 | /* for */ 2 | .container { 3 | width: 100px; 4 | height:100px; 5 | color: blue; 6 | } 7 | 8 | .text { 9 | font-size: 14px; 10 | color: white; 11 | } 12 | -------------------------------------------------------------------------------- /HybridFlutter/android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | *.class 3 | .gradle 4 | /local.properties 5 | /.idea/workspace.xml 6 | /.idea/libraries 7 | .DS_Store 8 | /build 9 | /captures 10 | GeneratedPluginRegistrant.java -------------------------------------------------------------------------------- /cc-cli/tpl/example.css.tpl: -------------------------------------------------------------------------------- 1 | /* ${code} */ 2 | .container { 3 | width: 100px; 4 | height:100px; 5 | color: blue; 6 | } 7 | 8 | .text { 9 | font-size: 14px; 10 | color: white; 11 | } 12 | -------------------------------------------------------------------------------- /HybridFlutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubliruiyuan/HybridFlutter/HEAD/HybridFlutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /HybridFlutter/android/app/src/main/res/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /HybridFlutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubliruiyuan/HybridFlutter/HEAD/HybridFlutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /HybridFlutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubliruiyuan/HybridFlutter/HEAD/HybridFlutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /HybridFlutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubliruiyuan/HybridFlutter/HEAD/HybridFlutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /HybridFlutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubliruiyuan/HybridFlutter/HEAD/HybridFlutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /HybridFlutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubliruiyuan/HybridFlutter/HEAD/HybridFlutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /HybridFlutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubliruiyuan/HybridFlutter/HEAD/HybridFlutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /HybridFlutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubliruiyuan/HybridFlutter/HEAD/HybridFlutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /HybridFlutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubliruiyuan/HybridFlutter/HEAD/HybridFlutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /HybridFlutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubliruiyuan/HybridFlutter/HEAD/HybridFlutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /HybridFlutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubliruiyuan/HybridFlutter/HEAD/HybridFlutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /HybridFlutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubliruiyuan/HybridFlutter/HEAD/HybridFlutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /HybridFlutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubliruiyuan/HybridFlutter/HEAD/HybridFlutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /HybridFlutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubliruiyuan/HybridFlutter/HEAD/HybridFlutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /HybridFlutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubliruiyuan/HybridFlutter/HEAD/HybridFlutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /HybridFlutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubliruiyuan/HybridFlutter/HEAD/HybridFlutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /HybridFlutter/android/app/src/main/kotlin/com/cc/hybrid/http/exception/HttpParamsException.kt: -------------------------------------------------------------------------------- 1 | package com.cc.hybrid.http.exception 2 | 3 | import java.lang.Exception 4 | 5 | class HttpParamsException(message: String): Exception(message) -------------------------------------------------------------------------------- /HybridFlutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubliruiyuan/HybridFlutter/HEAD/HybridFlutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /HybridFlutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubliruiyuan/HybridFlutter/HEAD/HybridFlutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /HybridFlutter/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /HybridFlutter/android/app/src/main/kotlin/com/cc/hybrid/bridge/js/JSConsole.kt: -------------------------------------------------------------------------------- 1 | package com.cc.hybrid.bridge.js 2 | 3 | import android.util.Log 4 | 5 | class JSConsole { 6 | fun log(string: Any) { 7 | Log.d("js", string.toString()) 8 | } 9 | } -------------------------------------------------------------------------------- /HybridFlutter/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /HybridFlutter/lib/entity/data.dart: -------------------------------------------------------------------------------- 1 | import 'package:hybrid_flutter/entity/property.dart'; 2 | import 'package:hybrid_flutter/ui/base_widget.dart'; 3 | 4 | class Data { 5 | 6 | Map map; 7 | List children; 8 | 9 | Data(this.map); 10 | 11 | } -------------------------------------------------------------------------------- /HybridFlutter/lib/util/base64.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | String decodeBase64(String data) { 4 | if (null == data) return null; 5 | Utf8Decoder utf8decoder = new Utf8Decoder(); 6 | return utf8decoder 7 | .convert(String.fromCharCodes(base64Decode(data)).codeUnits); 8 | } -------------------------------------------------------------------------------- /webpack-framework/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | webpack-helloworld 6 | 7 | 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /HybridFlutter/ios/Runner/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char* argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /HybridFlutter/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Apr 14 10:46:24 CST 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip 7 | -------------------------------------------------------------------------------- /cc-cli/transform.js: -------------------------------------------------------------------------------- 1 | /** 2 | * es6 转 es5 3 | */ 4 | const babelcore = require('babel-core'); 5 | 6 | transform = function (es6Code) { 7 | return es6Code; 8 | var es5Code = babelcore.transform(es6Code, { 9 | presets: ['es2015'] 10 | }).code; 11 | return es5Code; 12 | } 13 | module.exports = transform; -------------------------------------------------------------------------------- /cc-cli/wppackage.js: -------------------------------------------------------------------------------- 1 | const { execFile} = require('child_process'); 2 | const path = require('path'); 3 | const chalk = require('chalk'); 4 | const fs = require('fs'); 5 | const logger = require('./logger') 6 | const env = require('./env') 7 | 8 | module.exports = async function(dir, cmd, file){ 9 | return Promise.resovle(); 10 | }; -------------------------------------------------------------------------------- /cc-cli/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const merge = require('webpack-merge'); 2 | var WebpackShellPlugin = require('webpack-shell-plugin'); 3 | const common = require('./webpack.common'); 4 | 5 | module.exports = merge(common, { 6 | devtool: "source-map", 7 | plugins: [ 8 | new WebpackShellPlugin({onBuildEnd: ['node ./index.js build']}) 9 | ] 10 | }); 11 | -------------------------------------------------------------------------------- /HybridFlutter/android/app/src/main/kotlin/com/cc/hybrid/MFlutterApplication.kt: -------------------------------------------------------------------------------- 1 | package com.cc.hybrid 2 | 3 | import android.app.Application 4 | import com.cc.hybrid.v8.V8Manager 5 | 6 | class MFlutterApplication: Application() { 7 | 8 | override fun onCreate() { 9 | super.onCreate() 10 | V8Manager.initV8(this) 11 | } 12 | 13 | } -------------------------------------------------------------------------------- /HybridFlutter/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 5391447fae6209bb21a89e6a5a6583cac1af9b4b 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /cc-cli/tpl/example.html.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | this is a demo 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /HybridFlutter/android/app/src/main/res/layout/dialog_loading.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | -------------------------------------------------------------------------------- /HybridFlutter/android/app/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /cc-cli/README.md: -------------------------------------------------------------------------------- 1 | # 环境要求 2 | > 环境:node版本 >= 10.15 3 | > 查看node版本 4 | ``` 5 | node -v 6 | ``` 7 | > 如果node版本低于环境要求,请升级node https://nodejs.org/zh-cn/ 下载LTS版本安装即可 8 | # 使用示例 9 | 1. 进入到cc-cli目录下初始化 10 | ``` 11 | npm install 12 | ``` 13 | 2. 创建模板 14 | ``` 15 | node index.js tpl -n example -c example 16 | -n 页面名字 17 | -c 页面编号 18 | ``` 19 | 20 | 3. 编译 21 | ## 开启实时编译模式 22 | ``` 23 | npm start 24 | ``` 25 | 26 | -------------------------------------------------------------------------------- /HybridFlutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /cc-cli/tpl/example.js.tpl: -------------------------------------------------------------------------------- 1 | /** ${code} */ 2 | Page({ 3 | /** 4 | * 页面数据 5 | */ 6 | data: { 7 | name: "demo" 8 | }, 9 | 10 | /** 11 | * 页面加载时触发。一个页面只会调用一次,可以在 onLoad 的参数中获取打开当前页面路径中的参数。 12 | */ 13 | onLoad(e) { 14 | console.log('name = ' + this.data.name); 15 | }, 16 | 17 | /** 18 | * 页面卸载时触发。如cc.redirectTo或cc.navigateBack到其他页面时。 19 | */ 20 | onUnload() { 21 | 22 | } 23 | }); -------------------------------------------------------------------------------- /cc-cli/src_bak/for.js: -------------------------------------------------------------------------------- 1 | /** for */ 2 | Page({ 3 | /** 4 | * 页面数据 5 | */ 6 | data: { 7 | list: ['x1','y1','z1'], 8 | colors:['green','red','blue'], 9 | }, 10 | 11 | /** 12 | * 页面加载时触发。一个页面只会调用一次,可以在 onLoad 的参数中获取打开当前页面路径中的参数。 13 | */ 14 | onLoad(e) { 15 | 16 | }, 17 | 18 | /** 19 | * 页面卸载时触发。如cc.redirectTo或cc.navigateBack到其他页面时。 20 | */ 21 | onUnload() { 22 | 23 | } 24 | }); -------------------------------------------------------------------------------- /HybridFlutter/ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #include "AppDelegate.h" 2 | #include "GeneratedPluginRegistrant.h" 3 | 4 | @implementation AppDelegate 5 | 6 | - (BOOL)application:(UIApplication *)application 7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 8 | [GeneratedPluginRegistrant registerWithRegistry:self]; 9 | // Override point for customization after application launch. 10 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 11 | } 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /HybridFlutter/android/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | android 4 | Project android created by Buildship. 5 | 6 | 7 | 8 | 9 | org.eclipse.buildship.core.gradleprojectbuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.buildship.core.gradleprojectnature 16 | 17 | 18 | -------------------------------------------------------------------------------- /HybridFlutter/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | 13 | -------------------------------------------------------------------------------- /cc-cli/test.js: -------------------------------------------------------------------------------- 1 | const readline = require('readline'); 2 | 3 | module.exports = async function () { 4 | let promise = new Promise((resolve, reject) => { 5 | const rl = readline.createInterface({ 6 | input: process.stdin, 7 | output: process.stdout 8 | }); 9 | rl.on('line', function (input) { 10 | resolve([input, rl]); 11 | }); 12 | 13 | rl.on('close', function() { 14 | 15 | }); 16 | }); 17 | return promise; 18 | } -------------------------------------------------------------------------------- /HybridFlutter/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /cc-cli/src_bak/for.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | {{'id:' + index + ' text:' + item}} 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /HybridFlutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /HybridFlutter/android/app/src/main/kotlin/com/cc/hybrid/bridge/flutter/Methods.kt: -------------------------------------------------------------------------------- 1 | package com.cc.hybrid.bridge.flutter 2 | 3 | object Methods { 4 | const val EVENT = "event" 5 | const val ON_LOAD = "onLoad" 6 | const val ON_UNLOAD = "onUnload" 7 | const val INIT_COMPLETE = "initComplete" 8 | const val ON_PULL_DOWN_REFRESH = "onPullDownRefresh" 9 | const val HANDLE_REPEAT = "handleRepeat" 10 | const val HANDLE_EXPRESSION = "handleExpression" 11 | const val ATTACH_PAGE = "attachPage" 12 | const val REMOVE_OBSERVER = "removeObserver" 13 | 14 | } -------------------------------------------------------------------------------- /HybridFlutter/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /cc-cli/src/home.css: -------------------------------------------------------------------------------- 1 | /* home */ 2 | .btn-container{ 3 | margin-top:10; 4 | margin-left: 10; 5 | margin-right: 10; 6 | } 7 | 8 | .raised-button { 9 | color: white; 10 | } 11 | 12 | .image-container { 13 | width: 100px; 14 | height:100px; 15 | padding: 5; 16 | } 17 | 18 | .column-text { 19 | cross-axis-alignment: start; 20 | } 21 | 22 | .text-title { 23 | font-size: 14px; 24 | color: black; 25 | } 26 | 27 | .text-publisher { 28 | font-size: 12px; 29 | color: gray; 30 | } 31 | 32 | .text-summary { 33 | font-size: 12px; 34 | color: gray; 35 | } 36 | -------------------------------------------------------------------------------- /HybridFlutter/README.md: -------------------------------------------------------------------------------- 1 | # flutter_app 2 | 3 | A new Flutter application. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.io/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.io/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.io/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /HybridFlutter/ios/Flutter/flutter_export_environment.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This is a generated file; do not edit or check into version control. 3 | export "FLUTTER_ROOT=F:\flutter\flutter-sdk" 4 | export "FLUTTER_APPLICATION_PATH=F:\code\HybridFlutter\HybridFlutter" 5 | export "COCOAPODS_PARALLEL_CODE_SIGN=true" 6 | export "FLUTTER_TARGET=lib\main.dart" 7 | export "FLUTTER_BUILD_DIR=build" 8 | export "FLUTTER_BUILD_NAME=1.0.0" 9 | export "FLUTTER_BUILD_NUMBER=1" 10 | export "DART_OBFUSCATION=false" 11 | export "TRACK_WIDGET_CREATION=false" 12 | export "TREE_SHAKE_ICONS=false" 13 | export "PACKAGE_CONFIG=.dart_tool/package_config.json" 14 | -------------------------------------------------------------------------------- /cc-cli/config.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var glob = require('glob') 3 | /** 4 | * 读取指定路径下的文件 5 | * @param {String} glob 表达式 6 | * @param {String} base 基础路径 7 | */ 8 | exports.getEntries = function (globPath, base, replaceed) { 9 | var entries = {} 10 | glob.sync(globPath).forEach(function (entry) { 11 | //获取对应文件的名称 12 | console.log(entry) 13 | var moduleName = path.basename(entry, '.js'); 14 | let entry_ = './src/'+path.basename(entry); 15 | // let entry_ = base +path.basename(entry); 16 | entries[moduleName] = entry_ 17 | }) 18 | console.log(entries) 19 | return entries; 20 | } 21 | -------------------------------------------------------------------------------- /HybridFlutter/android/app/src/main/kotlin/com/cc/hybrid/http/HttpRequestUtil.kt: -------------------------------------------------------------------------------- 1 | package com.cc.hybrid.http 2 | 3 | import okhttp3.OkHttpClient 4 | import java.util.concurrent.TimeUnit 5 | 6 | internal object HttpRequestUtil { 7 | private val TAG = "HttpRequestUtil" 8 | 9 | var okHttpClient: OkHttpClient 10 | init { 11 | val builder = OkHttpClient.Builder() 12 | .connectTimeout(10, TimeUnit.SECONDS) 13 | .retryOnConnectionFailure(true) 14 | .readTimeout(10, TimeUnit.SECONDS) 15 | .writeTimeout(10, TimeUnit.SECONDS) 16 | okHttpClient = builder.build() 17 | } 18 | 19 | 20 | } -------------------------------------------------------------------------------- /HybridFlutter/android/app/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | app 4 | Project app created by Buildship. 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.buildship.core.gradleprojectbuilder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.buildship.core.gradleprojectnature 22 | 23 | 24 | -------------------------------------------------------------------------------- /webpack-framework/README.md: -------------------------------------------------------------------------------- 1 | webpack-cc 2 | =================== 3 | 4 | ## About 5 | 6 | A demo app made with Webpack 4, featuring Sass, Babel and Lodash. 7 | 8 | 9 | ## What's Included? 10 | 11 | *webpack-helloworld* is very unopinionated, letting you extend the application using the framework you want. It does include `lodash` by default to simplify some UI tasks. The configuration file already comes with all necessary settings for Sass and Babel 7. 12 | 13 | ## Building 14 | 15 | After installing all dependencies, run the following command: 16 | 17 | ```bash 18 | npm run dev 19 | ``` 20 | 21 | For a production build, run: 22 | 23 | ```bash 24 | npm run prod 25 | ``` 26 | 27 | ## License 28 | 29 | Licensed under the Apache License, Version 2.0. 30 | -------------------------------------------------------------------------------- /HybridFlutter/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.6.10' 3 | ext.appcompat_version = '1.4.0' 4 | repositories { 5 | google() 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:7.1.2' 11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | google() 18 | mavenCentral() 19 | } 20 | } 21 | 22 | rootProject.buildDir = '../build' 23 | subprojects { 24 | project.buildDir = "${rootProject.buildDir}/${project.name}" 25 | } 26 | subprojects { 27 | project.evaluationDependsOn(':app') 28 | } 29 | 30 | task clean(type: Delete) { 31 | delete rootProject.buildDir 32 | } 33 | -------------------------------------------------------------------------------- /HybridFlutter/lib/entity/property.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | class Property { 4 | String property, _expValue; 5 | bool containExpression = false; 6 | 7 | Property(this.property) { 8 | this._expValue = property; 9 | containExpression = _containExpression(property); 10 | } 11 | 12 | void setValue(String value) { 13 | _expValue = value; 14 | } 15 | 16 | String getValue() { 17 | return _expValue; 18 | } 19 | 20 | bool _containExpression(String content) { 21 | if (null == content) return false; 22 | var trim = content.trim(); 23 | if (trim.isEmpty) return false; 24 | return trim.contains("{{") && trim.contains("}}"); 25 | } 26 | 27 | @override 28 | String toString() { 29 | return 'Property{property: $property, _expValue: $_expValue}'; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /HybridFlutter/android/app/src/main/kotlin/com/cc/hybrid/util/SpUtil.kt: -------------------------------------------------------------------------------- 1 | package com.cc.hybrid.util 2 | 3 | import android.content.Context 4 | import android.content.SharedPreferences 5 | 6 | object SpUtil { 7 | private lateinit var sp: SharedPreferences 8 | 9 | fun initSp(context: Context) { 10 | sp = context.getSharedPreferences("cc", Context.MODE_PRIVATE) 11 | } 12 | 13 | fun put(key: String, value: Any) { 14 | when (value) { 15 | is Boolean -> sp.edit().putBoolean(key, value).apply() 16 | is Int -> sp.edit().putInt(key, value).apply() 17 | is String -> sp.edit().putString(key, value).apply() 18 | is Long -> sp.edit().putLong(key, value).apply() 19 | is Float -> sp.edit().putFloat(key, value).apply() 20 | } 21 | } 22 | 23 | fun get(key: String): Any? { 24 | return sp.all[key] 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /HybridFlutter/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /HybridFlutter/android/app/src/main/kotlin/com/cc/hybrid/Logger.kt: -------------------------------------------------------------------------------- 1 | package com.cc.hybrid 2 | 3 | import android.util.Log 4 | 5 | object Logger { 6 | 7 | 8 | private val TAG = "cms" 9 | 10 | private var debug = true 11 | 12 | fun init(mode: Boolean) { 13 | debug = mode 14 | } 15 | 16 | fun d(tag: String, msg: String) { 17 | if (debug) { 18 | Log.d(tag, msg) 19 | } 20 | } 21 | 22 | fun v(tag: String, msg: String) { 23 | if (debug) { 24 | Log.v(tag, msg) 25 | } 26 | } 27 | 28 | fun i(tag: String, msg: String) { 29 | if (debug) { 30 | Log.i(tag, msg) 31 | } 32 | } 33 | 34 | fun e(tag: String, msg: String) { 35 | if (debug) { 36 | Log.e(tag, msg) 37 | } 38 | } 39 | 40 | fun printError(e: Throwable?) { 41 | if (debug) { 42 | e?.printStackTrace() 43 | } 44 | } 45 | 46 | 47 | } -------------------------------------------------------------------------------- /HybridFlutter/android/app/src/main/kotlin/com/cc/hybrid/util/ToastUtil.kt: -------------------------------------------------------------------------------- 1 | package com.cc.hybrid.util 2 | 3 | import android.content.Context 4 | import android.text.TextUtils 5 | import android.widget.Toast 6 | import com.eclipsesource.v8.V8Object 7 | 8 | object ToastUtil { 9 | 10 | private lateinit var context: Context 11 | 12 | private var toast: Toast? = null 13 | 14 | fun initToast(context: Context) { 15 | this.context = context 16 | } 17 | 18 | fun showToast(obj: V8Object?) { 19 | if (null != obj && !obj.isUndefined) { 20 | if (obj.contains("title")) { 21 | val title = obj.getString("title") 22 | if (!TextUtils.isEmpty(title)) { 23 | toast = Toast.makeText(context, title, Toast.LENGTH_SHORT) 24 | toast?.show() 25 | } 26 | } 27 | } 28 | } 29 | 30 | fun hideToast() { 31 | toast?.cancel() 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /cc-cli/logger.js: -------------------------------------------------------------------------------- 1 | var chalk = require('chalk') 2 | var format = require('util').format 3 | 4 | /** 5 | * Prefix. 6 | */ 7 | 8 | var prefix = ' cc-cli' 9 | var sep = chalk.gray('·') 10 | 11 | /** 12 | * Log a `message` to the console. 13 | * 14 | * @param {String} message 15 | */ 16 | 17 | exports.log = function () { 18 | var msg = format.apply(format, arguments) 19 | console.log(chalk.white(prefix), sep, msg) 20 | } 21 | 22 | /** 23 | * Log an error `message` to the console and exit. 24 | * 25 | * @param {String} message 26 | */ 27 | 28 | exports.fatal = function (message) { 29 | if (message instanceof Error) message = message.message.trim() 30 | var msg = format.apply(format, arguments) 31 | console.error(chalk.red(prefix), sep, msg) 32 | process.exit(1) 33 | } 34 | 35 | /** 36 | * Log a success `message` to the console and exit. 37 | * 38 | * @param {String} message 39 | */ 40 | 41 | exports.success = function () { 42 | var msg = format.apply(format, arguments) 43 | console.log(chalk.green(prefix), sep, msg) 44 | } 45 | -------------------------------------------------------------------------------- /webpack-framework/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webpack-framework", 3 | "version": "2.0.0", 4 | "scripts": { 5 | "dev": "webpack --mode=development", 6 | "prod": "webpack --mode=production" 7 | }, 8 | "description": "A demo app made using Webpack 4", 9 | "author": "Emmanuel Antico", 10 | "license": "Apache-2.0", 11 | "repository": "https://github.com/emaphp/webpack-helloworld.git", 12 | "dependencies": { 13 | "@babel/runtime": "^7.2.0", 14 | "lodash": "^4.17.11" 15 | }, 16 | "devDependencies": { 17 | "@babel/core": "^7.2.2", 18 | "@babel/plugin-proposal-function-bind": "^7.2.0", 19 | "@babel/plugin-transform-runtime": "^7.2.0", 20 | "@babel/preset-env": "^7.2.3", 21 | "@babel/register": "^7.0.0", 22 | "babel-loader": "^8.0.5", 23 | "css-loader": "^2.1.0", 24 | "file-loader": "^0.8.5", 25 | "node-sass": "^4.11.0", 26 | "sass-loader": "^7.1.0", 27 | "style-loader": "^0.23.1", 28 | "url-loader": "^0.5.9", 29 | "webpack": "^4.28.4", 30 | "webpack-cli": "^3.2.1" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /cc-cli/src/detail.css: -------------------------------------------------------------------------------- 1 | /* detail */ 2 | .scroll-container { 3 | width-factor: 1; 4 | height-factor: 1; 5 | } 6 | 7 | .column { 8 | cross-axis-alignment: start; 9 | } 10 | 11 | .row-container { 12 | color: white; 13 | padding: 10; 14 | } 15 | 16 | .image-container { 17 | width: 120px; 18 | height:160px; 19 | margin-right: 5; 20 | } 21 | 22 | .title { 23 | font-size: 14px; 24 | color: black; 25 | } 26 | 27 | .summary-container { 28 | color: white; 29 | padding: 10; 30 | } 31 | 32 | .label-container { 33 | padding-left: 10; 34 | padding-top: 5; 35 | padding-bottom: 5; 36 | } 37 | 38 | .summary-label { 39 | font-size: 16; 40 | color: green; 41 | } 42 | 43 | .catalog-container { 44 | color: white; 45 | padding-top: 10; 46 | padding-left: 10; 47 | padding-right: 10; 48 | } 49 | 50 | .more-container { 51 | padding-left: 10; 52 | padding-right: 10; 53 | padding-bottom: 10; 54 | color: white; 55 | } 56 | 57 | .more-btn { 58 | color: white; 59 | } 60 | 61 | .more { 62 | color: blue; 63 | } 64 | -------------------------------------------------------------------------------- /HybridFlutter/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter_test/flutter_test.dart'; 9 | 10 | 11 | abstract class A { 12 | String a; 13 | } 14 | 15 | class B extends A { 16 | B(a){ 17 | this.a = a; 18 | } 19 | } 20 | 21 | void main() { 22 | 23 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 24 | 25 | // var b = B("qwer"); 26 | // print(b.a); 27 | // var url = "home?a=1&b=2"; 28 | // var uri = Uri.parse(url); 29 | // print("uri = ${uri.toString()}"); 30 | 31 | var list = [0,1,2,3,4,5]; 32 | print(list.getRange(0, 3)); 33 | print(list.getRange(3, 5)); 34 | list.removeRange(3, 5); 35 | print(list); 36 | 37 | 38 | // double v; 39 | // print(v); 40 | // print(v ?? 1); 41 | 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /HybridFlutter/lib/ui/expanded.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:hybrid_flutter/entity/component.dart'; 4 | import 'package:hybrid_flutter/entity/data.dart'; 5 | import 'package:hybrid_flutter/ui/base_widget.dart'; 6 | 7 | import 'basic.dart'; 8 | 9 | class ExpandedStateless extends BaseWidget { 10 | ExpandedStateless(BaseWidget parent, String pageId, 11 | MethodChannel methodChannel, Component component) 12 | : super( 13 | parent: parent, 14 | pageId: pageId, 15 | methodChannel: methodChannel, 16 | component: component, 17 | data: ValueNotifier(Data(component.properties))); 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return ValueListenableBuilder( 22 | builder: (BuildContext context, Data data, Widget child) { 23 | return Expanded( 24 | key: ObjectKey(component), 25 | flex: MInt.parse(data.map['flex'], defaultValue: 1), 26 | child: data.children.isNotEmpty ? data.children[0] : null); 27 | }, 28 | valueListenable: this.data); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /cc-cli/command-tpl.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 创建模板 3 | */ 4 | let path = require('path'); 5 | let fs = require('fs'); 6 | let logger = require('./logger') 7 | module.exports = function(tplcode, tplname){ 8 | let projectDir = path.resolve('.'), clidir = __dirname, tplDir = path.resolve(clidir, 'tpl') 9 | logger.log(`cli dir:${clidir}\n template create dir:${projectDir}`) 10 | 11 | let srcDir = path.resolve(projectDir, 'src') 12 | if(!fs.existsSync(srcDir)){ 13 | fs.mkdirSync(srcDir) 14 | } 15 | 16 | let files = fs.readdirSync(tplDir).filter((x) => x.endsWith('.tpl')) 17 | files.forEach((x) => { 18 | let name = x.replace('.tpl', ''), ext = path.extname(name) 19 | name = `${tplcode}${ext}` 20 | 21 | let src = path.resolve(tplDir, x) 22 | let dest = path.resolve(projectDir, 'src', name) 23 | 24 | let txt = fs.readFileSync(src).toString() 25 | 26 | //replace code、name 27 | txt = txt.replace('${code}', tplcode) 28 | txt = txt.replace('${name}', tplname) 29 | 30 | //write file 31 | fs.writeFileSync(dest, txt) 32 | }); 33 | 34 | logger.success(`create template cdoe :${tplcode} name:${tplname} successfully`) 35 | } -------------------------------------------------------------------------------- /HybridFlutter/lib/ui/center.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:hybrid_flutter/entity/component.dart'; 4 | import 'package:hybrid_flutter/entity/data.dart'; 5 | import 'package:hybrid_flutter/ui/base_widget.dart'; 6 | 7 | import 'basic.dart'; 8 | 9 | class CenterStateless extends BaseWidget { 10 | CenterStateless(BaseWidget parent, String pageId, MethodChannel methodChannel, 11 | Component component) 12 | : super( 13 | parent: parent, 14 | pageId: pageId, 15 | methodChannel: methodChannel, 16 | component: component, 17 | data: ValueNotifier(Data(component.properties))); 18 | @override 19 | Widget build(BuildContext context) { 20 | return ValueListenableBuilder( 21 | builder: (BuildContext context, Data data, Widget child) { 22 | return Center( 23 | key: ObjectKey(component), 24 | widthFactor: MDouble.parse(data.map['width-factor']), 25 | heightFactor: MDouble.parse(data.map['height-factor']), 26 | child: data.children.isNotEmpty ? data.children[0] : null); 27 | }, 28 | valueListenable: this.data); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /HybridFlutter/lib/ui/image.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:hybrid_flutter/entity/component.dart'; 4 | import 'package:hybrid_flutter/entity/data.dart'; 5 | import 'package:hybrid_flutter/ui/base_widget.dart'; 6 | import 'package:hybrid_flutter/ui/basic.dart'; 7 | 8 | class ImageStateless extends BaseWidget { 9 | ImageStateless(BaseWidget parent, String pageId, MethodChannel methodChannel, 10 | Component component) 11 | : super( 12 | parent: parent, 13 | pageId: pageId, 14 | methodChannel: methodChannel, 15 | component: component, 16 | data: ValueNotifier(Data(component.properties))); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return ValueListenableBuilder( 21 | builder: (BuildContext context, Data data, Widget child) { 22 | var src = data.map['src'].getValue(); 23 | return Image.network(null == src ? '' : src, 24 | key: ObjectKey(component), 25 | width: MDouble.parse(data.map['width']), 26 | height: MDouble.parse(data.map['height'])); 27 | }, 28 | valueListenable: this.data); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /HybridFlutter/lib/ui/aspect_ratio.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:hybrid_flutter/entity/component.dart'; 4 | import 'package:hybrid_flutter/entity/data.dart'; 5 | import 'package:hybrid_flutter/ui/base_widget.dart'; 6 | import 'package:hybrid_flutter/ui/basic.dart'; 7 | 8 | class AspectRatioStateless extends BaseWidget { 9 | AspectRatioStateless(BaseWidget parent, String pageId, 10 | MethodChannel methodChannel, Component component) 11 | : super( 12 | parent: parent, 13 | pageId: pageId, 14 | methodChannel: methodChannel, 15 | component: component, 16 | data: ValueNotifier(Data(component.properties))); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return ValueListenableBuilder( 21 | builder: (BuildContext context, Data data, Widget child) { 22 | return AspectRatio( 23 | key: ObjectKey(component), 24 | aspectRatio: 25 | MDouble.parse(data.map['aspect-ratio'], defaultValue: 0), 26 | child: data.children.isNotEmpty ? data.children[0] : null); 27 | }, 28 | valueListenable: this.data); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /HybridFlutter/lib/ui/single_child_scrollview.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:hybrid_flutter/entity/component.dart'; 4 | import 'package:hybrid_flutter/entity/data.dart'; 5 | import 'package:hybrid_flutter/ui/base_widget.dart'; 6 | 7 | import 'basic.dart'; 8 | 9 | class SingleChildScrollViewStateless extends BaseWidget { 10 | SingleChildScrollViewStateless(BaseWidget parent, String pageId, 11 | MethodChannel methodChannel, Component component) 12 | : super( 13 | parent: parent, 14 | pageId: pageId, 15 | methodChannel: methodChannel, 16 | component: component, 17 | data: ValueNotifier(Data(component.properties))); 18 | @override 19 | Widget build(BuildContext context) { 20 | return ValueListenableBuilder( 21 | builder: (BuildContext context, Data data, Widget child) { 22 | return SingleChildScrollView( 23 | key: ObjectKey(component), 24 | scrollDirection: MAxis.parse(data.map["scroll-direction"], 25 | defaultValue: Axis.vertical), 26 | child: data.children.isNotEmpty ? data.children[0] : null); 27 | }, 28 | valueListenable: this.data); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /cc-cli/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webpack-cc", 3 | "version": "1.0.0", 4 | "description": "", 5 | "private": true, 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "watch:wp": "webpack --watch --mode production --config webpack.prod.js", 9 | "watch:cc": "node ./index.js watch", 10 | "start": "npm-run-all --parallel watch:cc watch:wp", 11 | "build": "webpack --config webpack.prod.js" 12 | }, 13 | "dependencies": { 14 | "adm-zip": "^0.4.13", 15 | "babel-core": "^6.26.3", 16 | "babel-preset-es2015": "^6.24.1", 17 | "chalk": "^2.4.2", 18 | "clean-webpack-plugin": "^2.0.1", 19 | "htmlparser2": "^3.10.1", 20 | "node-watch": "^0.6.0", 21 | "ora": "^3.2.0", 22 | "shady-css-parser": "^0.1.0", 23 | "shelljs": "^0.8.3", 24 | "yargs": "^13.2.2", 25 | "@babel/core": "^7.4.3", 26 | "@babel/preset-env": "^7.4.3", 27 | "babel-loader": "^8.0.5", 28 | "css-loader": "^2.1.1", 29 | "file-loader": "^3.0.1", 30 | "npm-run-all": "^4.1.5", 31 | "style-loader": "^0.23.1", 32 | "webpack": "^4.27.1", 33 | "webpack-cli": "^3.1.2", 34 | "webpack-dev-server": "^3.2.1", 35 | "webpack-merge": "^4.2.1", 36 | "webpack-shell-plugin": "^0.5.0", 37 | "xml-loader": "^1.2.1" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /cc-cli/src/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | {{item.title}} 23 | {{item.publisher}} 24 | {{item.summary.substring(0, 20) + '...'}} 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /HybridFlutter/android/app/src/main/kotlin/com/cc/hybrid/event/EventManager.kt: -------------------------------------------------------------------------------- 1 | package com.cc.hybrid.event 2 | 3 | import android.os.Handler 4 | import android.os.Message 5 | import org.json.JSONObject 6 | 7 | class EventManager { 8 | 9 | companion object { 10 | const val TYPE_SOCKET = 0 11 | const val TYPE_REFRESH = 1 12 | const val TYPE_NAVIGATION_BAR_TITLE = 2 13 | const val TYPE_NAVIGATE_TO = 3 14 | const val TYPE_NAVIGATION_BAR_COLOR = 4 15 | const val TYPE_BACKGROUND_COLOR = 5 16 | const val TYPE_START_PULL_DOWN_REFRESH = 6 17 | const val TYPE_STOP_PULL_DOWN_REFRESH = 7 18 | var instance = EventManager() 19 | } 20 | 21 | private var handler: Handler? = null 22 | 23 | fun initHandler(handler: Handler) { 24 | this.handler = handler 25 | } 26 | 27 | fun sendMessage(what: Int, pageId: String, obj: Any) { 28 | val jsonObject = JSONObject() 29 | jsonObject.put("pageId", pageId) 30 | jsonObject.put("obj", obj) 31 | val msg = Message.obtain() 32 | msg.what = what 33 | msg.obj = jsonObject 34 | handler?.sendMessage(msg) 35 | } 36 | 37 | fun destroy(){ 38 | handler?.removeCallbacksAndMessages(null) 39 | handler = null 40 | } 41 | 42 | 43 | } -------------------------------------------------------------------------------- /HybridFlutter/lib/ui/visibility.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:hybrid_flutter/entity/component.dart'; 4 | import 'package:hybrid_flutter/entity/data.dart'; 5 | 6 | import 'base_widget.dart'; 7 | import 'basic.dart'; 8 | 9 | class VisibilityStateless extends BaseWidget { 10 | VisibilityStateless(BaseWidget parent, String pageId, 11 | MethodChannel methodChannel, Component component) 12 | : super( 13 | parent: parent, 14 | pageId: pageId, 15 | methodChannel: methodChannel, 16 | component: component, 17 | data: ValueNotifier(Data(component.properties))); 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return ValueListenableBuilder( 22 | builder: (BuildContext context, Data data, Widget child) { 23 | return Visibility( 24 | key: ObjectKey(component), 25 | visible: MBool.parse(data.map['visible'], defaultValue: true), 26 | child: data.children.length > 0 ? data.children[0] : null, 27 | replacement: data.children.length > 1 28 | ? data.children[1] 29 | : const SizedBox.shrink()); 30 | }, 31 | valueListenable: this.data); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /cc-cli/webpack.common.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const CleanWebpackPlugin = require('clean-webpack-plugin'); 3 | var utils = require('./config'); 4 | var glob = require('glob') 5 | const fs = require('fs') 6 | var entrycache = require('./entry.js') 7 | //当前工作目录的绝对路径 8 | var ROOT_PATH = path.resolve(__dirname); 9 | //要打包的源代码路径 10 | var RESOURCES_PATH = path.resolve(ROOT_PATH, 'src'); 11 | //设置要打包的js文件为入口文件 12 | var entrys = utils.getEntries(path.join(RESOURCES_PATH, '/*.js'), path.join(RESOURCES_PATH, '/')); 13 | if(entrycache.length){ 14 | let entrynew = {} 15 | entrynew[entrycache] = entrys[entrycache] 16 | entrys = entrynew 17 | } 18 | console.log('entrys:...') 19 | console.log(entrys) 20 | module.exports = { 21 | entry: entrys, 22 | devServer: { 23 | contentBase: './dist' 24 | }, 25 | plugins: [ 26 | new CleanWebpackPlugin() 27 | ], 28 | output: { 29 | filename: '[name].bundle.js', 30 | path: path.resolve(__dirname, 'dist') 31 | }, 32 | module: { 33 | rules: [ 34 | { 35 | test:/\.js$/, 36 | exclude: __dirname + 'node_modules', 37 | use:{ 38 | loader:'babel-loader', 39 | options:{ 40 | //plugins:["@babel/plugin-transform-arrow-functions"] 41 | } 42 | } 43 | } 44 | ] 45 | } 46 | } -------------------------------------------------------------------------------- /cc-cli/src/detail.js: -------------------------------------------------------------------------------- 1 | /** detail */ 2 | Page({ 3 | /** 4 | * 页面数据 5 | */ 6 | data: { 7 | detail: {}, 8 | catalogShort: "", 9 | showLong: false, 10 | btnText: "查看更多", 11 | }, 12 | 13 | /** 14 | * 页面加载时触发。一个页面只会调用一次,可以在 onLoad 的参数中获取打开当前页面路径中的参数。 15 | */ 16 | onLoad(e) { 17 | var detail = JSON.parse(e.item); 18 | cc.setNavigationBarTitle({ 19 | title: detail.title 20 | }); 21 | var catalogShort = detail.catalog; 22 | if (catalogShort.length > 50) { 23 | catalogShort = catalogShort.substring(0, 50) + "..."; 24 | } 25 | this.setData({ 26 | detail: detail, 27 | catalogShort: catalogShort 28 | }); 29 | }, 30 | 31 | onMoreClick(e) { 32 | var showLong = !this.data.showLong; 33 | var catalogShort = this.data.detail.catalog; 34 | var btnText = "收起更多"; 35 | if (!showLong) { 36 | if (catalogShort.length > 50) { 37 | catalogShort = catalogShort.substring(0, 50) + "..."; 38 | } 39 | btnText = "查看更多"; 40 | } 41 | this.setData({ 42 | showLong: showLong, 43 | catalogShort: catalogShort, 44 | btnText: btnText 45 | }); 46 | }, 47 | 48 | /** 49 | * 页面卸载时触发。如cc.redirectTo或cc.navigateBack到其他页面时。 50 | */ 51 | onUnload() { 52 | 53 | } 54 | }); -------------------------------------------------------------------------------- /HybridFlutter/lib/ui/positioned.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:hybrid_flutter/entity/component.dart'; 4 | import 'package:hybrid_flutter/entity/data.dart'; 5 | import 'package:hybrid_flutter/ui/base_widget.dart'; 6 | 7 | import 'basic.dart'; 8 | 9 | class PositionedStateless extends BaseWidget { 10 | PositionedStateless(BaseWidget parent, String pageId, 11 | MethodChannel methodChannel, Component component) 12 | : super( 13 | parent: parent, 14 | pageId: pageId, 15 | methodChannel: methodChannel, 16 | component: component, 17 | data: ValueNotifier(Data(component.properties))); 18 | @override 19 | Widget build(BuildContext context) { 20 | return ValueListenableBuilder( 21 | builder: (BuildContext context, Data data, Widget child) { 22 | return Positioned( 23 | key: ObjectKey(component), 24 | left: MDouble.parse(data.map['left']), 25 | right: MDouble.parse(data.map['right']), 26 | top: MDouble.parse(data.map['top']), 27 | bottom: MDouble.parse(data.map['bottom']), 28 | width: MDouble.parse(data.map['width']), 29 | height: MDouble.parse(data.map['height']), 30 | child: data.children.isNotEmpty ? data.children[0] : null); 31 | }, 32 | valueListenable: this.data); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /HybridFlutter/lib/ui/fractionally_sized_box.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:hybrid_flutter/entity/component.dart'; 4 | import 'package:hybrid_flutter/entity/data.dart'; 5 | import 'package:hybrid_flutter/ui/base_widget.dart'; 6 | import 'package:hybrid_flutter/ui/basic.dart'; 7 | 8 | class FractionallySizedBoxStateless extends BaseWidget { 9 | FractionallySizedBoxStateless(BaseWidget parent, String pageId, 10 | MethodChannel methodChannel, Component component) 11 | : super( 12 | parent: parent, 13 | pageId: pageId, 14 | methodChannel: methodChannel, 15 | component: component, 16 | data: ValueNotifier(Data(component.properties))); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return ValueListenableBuilder( 21 | builder: (BuildContext context, Data data, Widget child) { 22 | var widthFactor = 23 | MDouble.parse(data.map['width-factor'], defaultValue: 0); 24 | var heightFactor = 25 | MDouble.parse(data.map['height-factor'], defaultValue: 0); 26 | return FractionallySizedBox( 27 | key: ObjectKey(component), 28 | widthFactor: widthFactor, 29 | heightFactor: heightFactor, 30 | child: data.children.isNotEmpty ? data.children[0] : null); 31 | }, 32 | valueListenable: this.data); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /HybridFlutter/lib/ui/text.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:hybrid_flutter/entity/component.dart'; 4 | import 'package:hybrid_flutter/entity/data.dart'; 5 | import 'package:hybrid_flutter/ui/base_widget.dart'; 6 | import 'package:hybrid_flutter/ui/basic.dart'; 7 | import 'package:hybrid_flutter/util/color_util.dart'; 8 | 9 | class TextStateless extends BaseWidget { 10 | TextStateless(BaseWidget parent, String pageId, MethodChannel methodChannel, 11 | Component component) 12 | : super( 13 | parent: parent, 14 | pageId: pageId, 15 | methodChannel: methodChannel, 16 | component: component, 17 | data: ValueNotifier(Data(component.properties))); 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return ValueListenableBuilder( 22 | builder: (BuildContext context, Data data, Widget child) { 23 | return Text(data.map['innerHTML'].getValue(), 24 | key: ObjectKey(component), 25 | style: TextStyle( 26 | inherit: MBool.parse(data.map['inherit'], defaultValue: true), 27 | fontSize: 28 | MDouble.parse(data.map['font-size'], defaultValue: 14), 29 | backgroundColor: dealColor(data.map['background-color']), 30 | color: dealFontColor(data.map['color']))); 31 | }, 32 | valueListenable: this.data); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /HybridFlutter/lib/ui/container.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:hybrid_flutter/entity/component.dart'; 4 | import 'package:hybrid_flutter/entity/data.dart'; 5 | import 'package:hybrid_flutter/ui/base_widget.dart'; 6 | 7 | import 'basic.dart'; 8 | 9 | class ContainerStateless extends BaseWidget { 10 | ContainerStateless(BaseWidget parent, String pageId, 11 | MethodChannel methodChannel, Component component) 12 | : super( 13 | parent: parent, 14 | pageId: pageId, 15 | methodChannel: methodChannel, 16 | component: component, 17 | data: ValueNotifier(Data(component.properties))); 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return ValueListenableBuilder( 22 | builder: (BuildContext context, Data data, Widget child) { 23 | var alignment = MAlignment.parse(data.map['alignment'], 24 | defaultValue: Alignment.topLeft); 25 | 26 | return Container( 27 | key: ObjectKey(component), 28 | alignment: alignment, 29 | color: MColor.parse(data.map['color']), 30 | width: MDouble.parse(data.map['width']), 31 | height: MDouble.parse(data.map['height']), 32 | margin: MMargin.parse(data.map), 33 | padding: MPadding.parse(data.map), 34 | child: data.children.isNotEmpty ? data.children[0] : null); 35 | }, 36 | valueListenable: this.data); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /HybridFlutter/lib/ui/stack.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:hybrid_flutter/entity/component.dart'; 4 | import 'package:hybrid_flutter/entity/data.dart'; 5 | import 'package:hybrid_flutter/ui/base_widget.dart'; 6 | 7 | import 'basic.dart'; 8 | 9 | class StackStateless extends BaseWidget { 10 | StackStateless(BaseWidget parent, String pageId, MethodChannel methodChannel, 11 | Component component) 12 | : super( 13 | parent: parent, 14 | pageId: pageId, 15 | methodChannel: methodChannel, 16 | component: component, 17 | data: ValueNotifier(Data(component.properties))); 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return ValueListenableBuilder( 22 | builder: (BuildContext context, Data data, Widget child) { 23 | var alignment = MAlignmentDirectional.parse(data.map['alignment'], 24 | defaultValue: AlignmentDirectional.topStart); 25 | return Stack( 26 | key: ObjectKey(component), 27 | alignment: alignment, 28 | textDirection: MTextDirection.parse(data.map['text-direction']), 29 | fit: MStackFit.parse(data.map['fit'], 30 | defaultValue: StackFit.loose), 31 | // overflow: MOverflow.parse(data.map['overflow'], 32 | // defaultValue: Overflow.clip), 33 | children: data.children); 34 | }, 35 | valueListenable: this.data); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /HybridFlutter/lib/ui/circular_progress_indicator.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/services.dart'; 4 | import 'package:hybrid_flutter/entity/component.dart'; 5 | import 'package:hybrid_flutter/entity/data.dart'; 6 | import 'package:hybrid_flutter/ui/basic.dart'; 7 | 8 | import 'base_widget.dart'; 9 | 10 | class CircularProgressIndicatorStateless extends BaseWidget { 11 | CircularProgressIndicatorStateless(BaseWidget parent, String pageId, 12 | MethodChannel methodChannel, Component component) 13 | : super( 14 | parent: parent, 15 | pageId: pageId, 16 | methodChannel: methodChannel, 17 | component: component, 18 | data: ValueNotifier(Data(component.properties))); 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return ValueListenableBuilder( 23 | builder: (BuildContext context, Data data, Widget child) { 24 | return CircularProgressIndicator( 25 | key: ObjectKey(component), 26 | value: MDouble.parse(data.map['value']), 27 | backgroundColor: MColor.parse(data.map['background-color']), 28 | // valueColor: , 29 | strokeWidth: 30 | MDouble.parse(data.map["stroke-width"], defaultValue: 4.0), 31 | semanticsLabel: data.map["semantics-label"]?.getValue(), 32 | semanticsValue: data.map["semantics-value"]?.getValue(), 33 | ); 34 | }, 35 | valueListenable: this.data); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /HybridFlutter/android/app/src/main/kotlin/com/cc/hybrid/util/LoadingUtil.kt: -------------------------------------------------------------------------------- 1 | package com.cc.hybrid.util 2 | 3 | import android.content.Context 4 | import androidx.appcompat.app.AppCompatDialog 5 | import android.text.TextUtils 6 | import com.cc.hybrid.R 7 | import com.eclipsesource.v8.V8Object 8 | 9 | object LoadingUtil { 10 | 11 | private lateinit var dialog: AppCompatDialog 12 | 13 | fun initDialog(context: Context) { 14 | dialog = AppCompatDialog(context, R.style.Alert) 15 | dialog.apply { 16 | setContentView(R.layout.dialog_loading) 17 | setCancelable(true) 18 | window?.decorView?.setBackgroundResource(android.R.color.transparent) 19 | } 20 | } 21 | 22 | fun showLoading(obj: V8Object?) { 23 | if (null != obj && !obj.isUndefined) { 24 | if (obj.contains("title")) { 25 | val title = obj.getString("title") 26 | if (!TextUtils.isEmpty(title)) { 27 | dialog.setTitle(title) 28 | } 29 | } 30 | if (obj.contains("message")) { 31 | val message = obj.getString("message") 32 | if (!TextUtils.isEmpty(message)) { 33 | dialog.setTitle(message) 34 | } 35 | } 36 | } 37 | if (!dialog.isShowing) { 38 | dialog.show() 39 | } 40 | } 41 | 42 | fun hideLoading() { 43 | dialog.dismiss() 44 | } 45 | 46 | fun destroy() { 47 | if(dialog.isShowing) { 48 | dialog.dismiss() 49 | } 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /HybridFlutter/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.lock 4 | *.log 5 | *.pyc 6 | *.swp 7 | .DS_Store 8 | .atom/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # Visual Studio Code related 20 | .vscode/ 21 | 22 | # Flutter/Dart/Pub related 23 | **/doc/api/ 24 | .dart_tool/ 25 | .flutter-plugins 26 | .packages 27 | .pub-cache/ 28 | .pub/ 29 | build/ 30 | 31 | # Android related 32 | **/android/**/gradle-wrapper.jar 33 | **/android/.gradle 34 | **/android/captures/ 35 | **/android/gradlew 36 | **/android/gradlew.bat 37 | **/android/local.properties 38 | **/android/**/GeneratedPluginRegistrant.java 39 | 40 | # iOS/XCode related 41 | **/ios/**/*.mode1v3 42 | **/ios/**/*.mode2v3 43 | **/ios/**/*.moved-aside 44 | **/ios/**/*.pbxuser 45 | **/ios/**/*.perspectivev3 46 | **/ios/**/*sync/ 47 | **/ios/**/.sconsign.dblite 48 | **/ios/**/.tags* 49 | **/ios/**/.vagrant/ 50 | **/ios/**/DerivedData/ 51 | **/ios/**/Icon? 52 | **/ios/**/Pods/ 53 | **/ios/**/.symlinks/ 54 | **/ios/**/profile 55 | **/ios/**/xcuserdata 56 | **/ios/.generated/ 57 | **/ios/Flutter/App.framework 58 | **/ios/Flutter/Flutter.framework 59 | **/ios/Flutter/Generated.xcconfig 60 | **/ios/Flutter/app.flx 61 | **/ios/Flutter/app.zip 62 | **/ios/Flutter/flutter_assets/ 63 | **/ios/ServiceDefinitions.json 64 | **/ios/Runner/GeneratedPluginRegistrant.* 65 | 66 | # Exceptions to above rules. 67 | !**/ios/**/default.mode1v3 68 | !**/ios/**/default.mode2v3 69 | !**/ios/**/default.pbxuser 70 | !**/ios/**/default.perspectivev3 71 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 72 | -------------------------------------------------------------------------------- /cc-cli/zip.js: -------------------------------------------------------------------------------- 1 | const AdmZip = require('adm-zip'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const chalk = require('chalk'); 5 | 6 | function exitsFile(filepath) { 7 | return fs.existsSync(filepath); 8 | } 9 | function makeZip(filepath) { 10 | if(!filepath || !filepath.endsWith('.html')){ 11 | return; 12 | } 13 | let htmlfile = filepath, 14 | cssfile = filepath.replace('.html', '.css'), 15 | jsonfile = filepath.replace('.html', '.json'); 16 | 17 | if(!exitsFile(htmlfile)) { 18 | console.log(chalk.red(`${htmlfile} not exits.Ignore zip.`)); 19 | return; 20 | } 21 | if(!exitsFile(jsonfile)) { 22 | console.log(chalk.red(`${jsonfile} not exits.Ignore zip.`)); 23 | return; 24 | } 25 | 26 | let zip = new AdmZip(); 27 | zip.addLocalFile(htmlfile); 28 | zip.addLocalFile(jsonfile); 29 | 30 | if(exitsFile(cssfile)){ 31 | zip.addLocalFile(cssfile); 32 | } 33 | 34 | let zipfile = filepath.replace('.html', '.zip'); 35 | zip.writeZip(zipfile, (err) =>{ 36 | if(err){ 37 | console.log(chalk.red(`${zipfile} zip error ${err}`)); 38 | } 39 | }); 40 | } 41 | 42 | zipper = function (fileOrDir){ 43 | if(fs.lstatSync(fileOrDir).isFile()){ 44 | //file 45 | makeZip(fileOrDir); 46 | } else { 47 | var paths = fs.readdirSync(fileOrDir).filter((value, i) => { 48 | return value.endsWith('.html'); 49 | }); 50 | paths.forEach((f) => { 51 | let file = path.join(fileOrDir, f); 52 | makeZip(file); 53 | }); 54 | } 55 | } 56 | 57 | module.exports = zipper; -------------------------------------------------------------------------------- /cc-cli/start.js: -------------------------------------------------------------------------------- 1 | /** 2 | * webpack打包 ->模板编译成json 3 | */ 4 | const parser = require('./parser'); 5 | const pwpackage = require('./wppackage'); 6 | const chalk = require('chalk') 7 | const fs = require('fs') 8 | const path = require('path') 9 | const watcher = require('./watch') 10 | 11 | module.exports = async function (argv) { 12 | if (argv.e && argv.e === 'dev') { 13 | try { 14 | let dir = argv.d || argv.f; 15 | if (fs.statSync(dir).isFile()) { 16 | dir = path.dirname(dir) 17 | } 18 | let w = new watcher(dir); 19 | w.start(); 20 | } catch (error) { 21 | return Promise.reject(error); 22 | } 23 | } 24 | if (argv.d) {//编译目录 25 | let { d, e } = argv; 26 | if (argv.e && argv.e != 'dev') { 27 | await pwpackage(d, 'build'); 28 | } 29 | 30 | let dir = d; 31 | if (dir) { 32 | var paths = fs.readdirSync(dir).filter((value, i) => { 33 | return value.endsWith('.html'); 34 | }); 35 | paths.forEach((v, i) => { 36 | let fullpath = path.join(dir, v); 37 | new parser.Parser(fullpath).parse() 38 | }); 39 | } 40 | return Promise.resolve(); 41 | } else if (argv.f) {//编译单个文件 42 | let { f, e } = argv; 43 | let dir = path.dirname(f); 44 | if (argv.env && argv.env != 'dev') { 45 | await pwpackage(dir ,'build'); 46 | } 47 | return await new parser.Parser(f).parse(); 48 | } else { 49 | console.log(chalk.red('请用--f指定模板文件或用--d指定模板目录\n')); 50 | return Promise.resolve(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /HybridFlutter/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | flutter_app 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /HybridFlutter/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /HybridFlutter/android/app/src/main/kotlin/com/cc/hybrid/http/HttpRequest.kt: -------------------------------------------------------------------------------- 1 | package com.cc.hybrid.http 2 | 3 | import com.cc.hybrid.http.exception.HttpParamsException 4 | 5 | 6 | internal class HttpRequest { 7 | companion object { 8 | val METHOD_POST = "post" 9 | val METHOD_GET = "get" 10 | } 11 | val paramsMap = HashMap() 12 | val headerMap = HashMap() 13 | var url: String? = null 14 | var errorRetryTime = 0 15 | var securirtyTransfer = false 16 | var isBusinessInterface = false 17 | var method: String = METHOD_POST 18 | 19 | class Builder { 20 | private val request = HttpRequest() 21 | fun addParams(key: String, value: Any): Builder { 22 | request.paramsMap[key] = value 23 | return this 24 | } 25 | 26 | fun addHeader(key: String, value: Any): Builder { 27 | request.headerMap.put(key, value) 28 | return this 29 | } 30 | 31 | fun url(url: String): Builder { 32 | request.url = url 33 | return this 34 | } 35 | 36 | fun securityTransfer(boolean: Boolean): Builder { 37 | request.securirtyTransfer = boolean 38 | return this 39 | } 40 | 41 | fun isBusinessInterface(boolean: Boolean): Builder { 42 | request.isBusinessInterface = boolean 43 | return this 44 | } 45 | 46 | fun method(method: String): Builder { 47 | request.method = method; 48 | return this 49 | } 50 | 51 | fun build(): HttpRequest { 52 | if (null == request.url) { 53 | throw HttpParamsException("url can not be null") 54 | } 55 | return request 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /HybridFlutter/lib/ui/row.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:hybrid_flutter/entity/component.dart'; 4 | import 'package:hybrid_flutter/entity/data.dart'; 5 | import 'package:hybrid_flutter/ui/base_widget.dart'; 6 | 7 | import 'basic.dart'; 8 | 9 | class RowStateless extends BaseWidget { 10 | RowStateless(BaseWidget parent, String pageId, MethodChannel methodChannel, 11 | Component component) 12 | : super( 13 | parent: parent, 14 | pageId: pageId, 15 | methodChannel: methodChannel, 16 | component: component, 17 | data: ValueNotifier(Data(component.properties))); 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return ValueListenableBuilder( 22 | builder: (BuildContext context, Data data, Widget child) { 23 | return Row( 24 | key: ObjectKey(component), 25 | mainAxisAlignment: MMainAxisAlignment.parse( 26 | data.map["main-axis-alignment"], 27 | defaultValue: MainAxisAlignment.start), 28 | mainAxisSize: MMainAxisSize.parse(data.map["main-axis-size"], 29 | defaultValue: MainAxisSize.max), 30 | crossAxisAlignment: MCrossAxisAlignment.parse( 31 | data.map["cross-axis-alignment"], 32 | defaultValue: CrossAxisAlignment.center), 33 | textDirection: MTextDirection.parse(data.map["text-direction"]), 34 | verticalDirection: MVerticalDirection.parse( 35 | data.map["vertical-direction"], 36 | defaultValue: VerticalDirection.down), 37 | textBaseline: MTextBaseline.parse(data.map["text-baseline"]), 38 | children: data.children); 39 | }, 40 | valueListenable: this.data); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /HybridFlutter/lib/ui/column.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:hybrid_flutter/entity/component.dart'; 4 | import 'package:hybrid_flutter/entity/data.dart'; 5 | import 'package:hybrid_flutter/ui/base_widget.dart'; 6 | 7 | import 'basic.dart'; 8 | 9 | class ColumnStateless extends BaseWidget { 10 | ColumnStateless(BaseWidget parent, String pageId, MethodChannel methodChannel, 11 | Component component) 12 | : super( 13 | parent: parent, 14 | pageId: pageId, 15 | methodChannel: methodChannel, 16 | component: component, 17 | data: ValueNotifier(Data(component.properties))); 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return ValueListenableBuilder( 22 | builder: (BuildContext context, Data data, Widget child) { 23 | return Column( 24 | key: ObjectKey(component), 25 | mainAxisAlignment: MMainAxisAlignment.parse( 26 | data.map["main-axis-alignment"], 27 | defaultValue: MainAxisAlignment.start), 28 | mainAxisSize: MMainAxisSize.parse(data.map["main-axis-size"], 29 | defaultValue: MainAxisSize.max), 30 | crossAxisAlignment: MCrossAxisAlignment.parse( 31 | data.map["cross-axis-alignment"], 32 | defaultValue: CrossAxisAlignment.center), 33 | textDirection: MTextDirection.parse(data.map["text-direction"]), 34 | verticalDirection: MVerticalDirection.parse( 35 | data.map["vertical-direction"], 36 | defaultValue: VerticalDirection.down), 37 | textBaseline: MTextBaseline.parse(data.map["text-baseline"]), 38 | children: data.children); 39 | }, 40 | valueListenable: this.data); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /webpack-framework/webpack.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | eslint-disable import/no-extraneous-dependencies 3 | eslint-disable arrow-body-style 4 | eslint-disable no-unused-vars 5 | */ 6 | 7 | var path = require('path'); 8 | 9 | module.exports = { 10 | entry: "./src/framework.js", 11 | output: { 12 | path: path.resolve("./", "dist"), 13 | filename: "framework.js", 14 | }, 15 | module: { 16 | rules: [ 17 | { 18 | test: [/.js$/], 19 | exclude: [/node_modules/], 20 | loader: 'babel-loader' 21 | }, 22 | { 23 | test: /\.scss$/, 24 | exclude: [/node_modules/], 25 | use: [ 26 | { 27 | loader: 'style-loader' 28 | }, 29 | { 30 | loader: 'css-loader' 31 | }, 32 | { 33 | loader: 'sass-loader' 34 | } 35 | ] 36 | }, 37 | { 38 | test: /\.css$/, 39 | exclude: [/node_modules/], 40 | use: [ 41 | { 42 | loader: 'style-loader' 43 | }, 44 | { 45 | loader: 'css-loader' 46 | } 47 | ] 48 | }, 49 | { 50 | test: /\.woff(\?v=[0-9]\.[0-9]\.[0-9])?$/, 51 | loader: "url-loader?limit=10000&minetype=application/font-woff" 52 | }, 53 | { 54 | test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, 55 | loader: "file-loader" 56 | } 57 | ], 58 | }, 59 | stats: { 60 | colors: true, 61 | }, 62 | // devtool: 'source-map' 63 | }; 64 | -------------------------------------------------------------------------------- /HybridFlutter/lib/ui/base_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:hybrid_flutter/entity/component.dart'; 4 | import 'package:hybrid_flutter/entity/data.dart'; 5 | import 'package:hybrid_flutter/entity/property.dart'; 6 | 7 | abstract class BaseWidget extends StatelessWidget { 8 | final String pageId; 9 | final Component component; 10 | final MethodChannel methodChannel; 11 | final BaseWidget parent; 12 | final ValueNotifier data; 13 | 14 | BaseWidget( 15 | {this.pageId, 16 | this.component, 17 | this.methodChannel, 18 | this.parent, 19 | this.data}); 20 | 21 | void setChildren(List children) { 22 | data.value.children = children; 23 | } 24 | 25 | void updateProperties(Map properties) { 26 | var newData = Data(properties); 27 | newData.children = data.value.children; 28 | data.value = newData; 29 | } 30 | 31 | void updateProperty(dynamic it) { 32 | var property = component.properties[it['key']]; 33 | if (null != property) { 34 | property.setValue(it['value'].toString()); 35 | } 36 | var newData = Data(component.properties); 37 | newData.children = data.value.children; 38 | data.value = newData; 39 | } 40 | 41 | void updateChildren(List children) { 42 | var newData = Data(data.value.map); 43 | newData.children = children; 44 | data.value = newData; 45 | } 46 | 47 | void addChildren(List children) { 48 | var newData = Data(data.value.map); 49 | newData.children = data.value.children; 50 | newData.children.addAll(children); 51 | data.value = newData; 52 | } 53 | 54 | void insertChildren(int index, List children) { 55 | var newData = Data(data.value.map); 56 | newData.children = data.value.children; 57 | newData.children.insertAll(index, children); 58 | data.value = newData; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /cc-cli/src_bak/for.json: -------------------------------------------------------------------------------- 1 | {"style":{".container":{"width":"100px","height":"100px","color":"blue"},".text":{"font-size":"14px","color":"white"}},"body":{"tag":"body","innerHTML":"","childNodes":[{"tag":"row","innerHTML":"","childNodes":[{"tag":"container","innerHTML":"","childNodes":[{"tag":"text","innerHTML":"e3snaWQ6JyArIGluZGV4ICsgJyB0ZXh0OicgKyBpdGVtfX0=","childNodes":[],"datasets":{},"events":{},"directives":{},"attribStyle":{"font-size":"14px","color":"white"},"attrib":{}}],"datasets":{},"events":{},"directives":{"repeat":{"name":"or","expression":"{{list}}","item":"item","index":"index"}},"attribStyle":{"width":"100px","height":"100px","color":"green","margin-right":"15px"},"attrib":{}}],"datasets":{},"events":{},"directives":{},"attribStyle":{},"attrib":{}}],"datasets":{},"events":{},"directives":{},"attribStyle":{},"attrib":{}},"type":{},"align":{},"description":{},"script":{"tag":"script","innerHTML":"IWZ1bmN0aW9uKGUpe3ZhciB0PXt9O2Z1bmN0aW9uIG4ocil7aWYodFtyXSlyZXR1cm4gdFtyXS5leHBvcnRzO3ZhciBvPXRbcl09e2k6cixsOiExLGV4cG9ydHM6e319O3JldHVybiBlW3JdLmNhbGwoby5leHBvcnRzLG8sby5leHBvcnRzLG4pLG8ubD0hMCxvLmV4cG9ydHN9bi5tPWUsbi5jPXQsbi5kPWZ1bmN0aW9uKGUsdCxyKXtuLm8oZSx0KXx8T2JqZWN0LmRlZmluZVByb3BlcnR5KGUsdCx7ZW51bWVyYWJsZTohMCxnZXQ6cn0pfSxuLnI9ZnVuY3Rpb24oZSl7InVuZGVmaW5lZCIhPXR5cGVvZiBTeW1ib2wmJlN5bWJvbC50b1N0cmluZ1RhZyYmT2JqZWN0LmRlZmluZVByb3BlcnR5KGUsU3ltYm9sLnRvU3RyaW5nVGFnLHt2YWx1ZToiTW9kdWxlIn0pLE9iamVjdC5kZWZpbmVQcm9wZXJ0eShlLCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KX0sbi50PWZ1bmN0aW9uKGUsdCl7aWYoMSZ0JiYoZT1uKGUpKSw4JnQpcmV0dXJuIGU7aWYoNCZ0JiYib2JqZWN0Ij09dHlwZW9mIGUmJmUmJmUuX19lc01vZHVsZSlyZXR1cm4gZTt2YXIgcj1PYmplY3QuY3JlYXRlKG51bGwpO2lmKG4ucihyKSxPYmplY3QuZGVmaW5lUHJvcGVydHkociwiZGVmYXVsdCIse2VudW1lcmFibGU6ITAsdmFsdWU6ZX0pLDImdCYmInN0cmluZyIhPXR5cGVvZiBlKWZvcih2YXIgbyBpbiBlKW4uZChyLG8sZnVuY3Rpb24odCl7cmV0dXJuIGVbdF19LmJpbmQobnVsbCxvKSk7cmV0dXJuIHJ9LG4ubj1mdW5jdGlvbihlKXt2YXIgdD1lJiZlLl9fZXNNb2R1bGU/ZnVuY3Rpb24oKXtyZXR1cm4gZS5kZWZhdWx0fTpmdW5jdGlvbigpe3JldHVybiBlfTtyZXR1cm4gbi5kKHQsImEiLHQpLHR9LG4ubz1mdW5jdGlvbihlLHQpe3JldHVybiBPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwoZSx0KX0sbi5wPSIiLG4obi5zPTEpfShbLGZ1bmN0aW9uKGUsdCl7UGFnZSh7ZGF0YTp7bGlzdDpbIngxIiwieTEiLCJ6MSJdLGNvbG9yczpbImdyZWVuIiwicmVkIiwiYmx1ZSJdfSxvbkxvYWQoZSl7fSxvblVubG9hZCgpe319KX1dKTsKLy8jIHNvdXJjZU1hcHBpbmdVUkw9Zm9yLmJ1bmRsZS5qcy5tYXA=","childNodes":[],"datasets":{},"events":{},"directives":{},"attribStyle":{},"attrib":{}}} -------------------------------------------------------------------------------- /HybridFlutter/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 10 | 15 | 20 | 28 | 32 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /HybridFlutter/lib/util/event_util.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/services.dart'; 4 | import 'package:hybrid_flutter/entity/property.dart'; 5 | 6 | const String TYPE_TAP = "tab"; 7 | const String TYPE_SCROLL = "scroll"; 8 | 9 | String _createTapEvent(String id, Map dataSet, String type) { 10 | Map target = Map(); 11 | target.putIfAbsent('id', () => id); 12 | target.putIfAbsent('dataset', () => dataSet); 13 | Map event = Map(); 14 | event.putIfAbsent('type', () => type); 15 | event.putIfAbsent('target', () => target); 16 | return jsonEncode(event); 17 | } 18 | 19 | String _createScrollEvent(String id, double offset, String type) { 20 | Map detail = Map(); 21 | detail.putIfAbsent('id', () => id); 22 | detail.putIfAbsent('offset', () => offset); 23 | Map event = Map(); 24 | event.putIfAbsent('type', () => type); 25 | event.putIfAbsent('detail', () => detail); 26 | return jsonEncode(event); 27 | } 28 | 29 | onTapEvent(MethodChannel methodChannel, String pageId, String id, 30 | Map properties, String event) { 31 | var prefix = 'data-'; 32 | var dataSet = Map(); 33 | properties.forEach((k, v) { 34 | if (k.startsWith(prefix)) { 35 | var key = k.substring(prefix.length); 36 | try { 37 | dataSet.putIfAbsent(key, jsonDecode(v.getValue())); 38 | } catch (e) { 39 | dataSet.putIfAbsent(key, () => v.getValue()); 40 | } 41 | } 42 | }); 43 | var func = event.replaceAll('()', ''); 44 | String json = _createTapEvent(id, dataSet, TYPE_TAP); 45 | // print('json = $json'); 46 | methodChannel 47 | .invokeMethod('event', {'pageId': pageId, 'event': func, 'data': json}); 48 | } 49 | 50 | onScrollEvent(MethodChannel methodChannel, String pageId, String id, 51 | String event, double offset) { 52 | var func = event.replaceAll('()', ''); 53 | String json = _createScrollEvent(id, offset, TYPE_SCROLL); 54 | // print('json = $json'); 55 | methodChannel 56 | .invokeMethod('event', {'pageId': pageId, 'event': func, 'data': json}); 57 | } 58 | 59 | onScrollLimitEvent( 60 | MethodChannel methodChannel, String pageId, String id, String event) { 61 | var func = event.replaceAll('()', ''); 62 | methodChannel 63 | .invokeMethod('event', {'pageId': pageId, 'event': func, 'data': ""}); 64 | } 65 | -------------------------------------------------------------------------------- /HybridFlutter/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion flutter.compileSdkVersion 30 | ndkVersion flutter.ndkVersion 31 | 32 | sourceSets { 33 | main.java.srcDirs += 'src/main/kotlin' 34 | } 35 | 36 | defaultConfig { 37 | applicationId "com.cc.hybrid" 38 | minSdkVersion flutter.minSdkVersion 39 | targetSdkVersion flutter.targetSdkVersion 40 | versionCode flutterVersionCode.toInteger() 41 | versionName flutterVersionName 42 | } 43 | 44 | buildTypes { 45 | debug { 46 | ndk { 47 | abiFilters 'armeabi', 'armeabi-v7a', 'x86' 48 | } 49 | } 50 | release { 51 | ndk { 52 | abiFilters 'armeabi', 'armeabi-v7a' 53 | } 54 | signingConfig signingConfigs.debug 55 | } 56 | } 57 | } 58 | 59 | flutter { 60 | source '../..' 61 | } 62 | 63 | dependencies { 64 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 65 | implementation 'com.eclipsesource.j2v8:j2v8:6.1.0@aar' 66 | implementation 'com.alibaba:fastjson:1.2.46+' 67 | implementation 'com.squareup.okhttp3:okhttp:3.4.0+' 68 | implementation 'com.squareup.okio:okio:1.12.0+' 69 | implementation("androidx.appcompat:appcompat:$appcompat_version") 70 | // For loading and tinting drawables on older versions of the platform 71 | implementation("androidx.appcompat:appcompat-resources:$appcompat_version") 72 | } 73 | -------------------------------------------------------------------------------- /HybridFlutter/lib/ui/raised_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/services.dart'; 4 | import 'package:hybrid_flutter/entity/component.dart'; 5 | import 'package:hybrid_flutter/entity/data.dart'; 6 | import 'package:hybrid_flutter/ui/base_widget.dart'; 7 | import 'package:hybrid_flutter/util/color_util.dart'; 8 | import 'package:hybrid_flutter/util/event_util.dart'; 9 | 10 | class RaisedButtonStateless extends BaseWidget { 11 | RaisedButtonStateless(BaseWidget parent, String pageId, 12 | MethodChannel methodChannel, Component component) 13 | : super( 14 | parent: parent, 15 | pageId: pageId, 16 | methodChannel: methodChannel, 17 | component: component, 18 | data: ValueNotifier(Data(component.properties))); 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return ValueListenableBuilder( 23 | builder: (BuildContext context, Data data, Widget child) { 24 | Color color = dealColor(data.map['color']); 25 | Color textColor = dealColor(data.map['text-color']); 26 | Color disabledTextColor = dealColor(data.map['disabled-text-color']); 27 | Color disabledColor = dealColor(data.map['disabled-color']); 28 | Color focusColor = dealColor(data.map['focus-color']); 29 | Color hoverColor = dealColor(data.map['hover-color']); 30 | Color highlightColor = dealColor(data.map['highlight-color']); 31 | Color splashColor = dealColor(data.map['splash-color']); 32 | 33 | return RaisedButton( 34 | onPressed: () { 35 | var bindTap = component.events['bindtap']; 36 | if (null != bindTap) { 37 | onTapEvent(methodChannel, pageId, this.hashCode.toString(), 38 | data.map, bindTap); 39 | } 40 | }, 41 | key: ObjectKey(component), 42 | textColor: textColor, 43 | disabledTextColor: disabledTextColor, 44 | color: color, 45 | disabledColor: disabledColor, 46 | focusColor: focusColor, 47 | hoverColor: hoverColor, 48 | highlightColor: highlightColor, 49 | splashColor: splashColor, 50 | child: data.children.isNotEmpty ? data.children[0] : null); 51 | }, 52 | valueListenable: this.data); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /cc-cli/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /** 3 | * use example : 4 | * node index.js c -e dev -d /Users/xxx/Documents/npm/demo 5 | * node index.js zip -p /Users/xxx/Documents/npm/demo 6 | */ 7 | const start = require('./start'); 8 | const zip = require('./zip'); 9 | const path = require('path'); 10 | const createtemplate = require('./command-tpl'); 11 | const watcher = require('./watch') 12 | const logger = require('./logger') 13 | 14 | const argv = require('yargs') 15 | .command( 16 | 'tpl', 17 | '创建页面', 18 | function (yargs) {//可选参数 19 | return yargs 20 | .option('name', { 21 | alias: 'n', 22 | describe: '指定页面名称' 23 | }) 24 | .option('code', { 25 | alias: 'c', 26 | describe: '指定页面code' 27 | }) 28 | }, 29 | function (argv) { 30 | let {code , name} = argv 31 | if(!code){ 32 | logger.fatal(`请用--code参数指定页面名字`); 33 | return; 34 | } 35 | if(!name){ 36 | logger.fatal(`请用--name 参数指定页面名字`); 37 | return; 38 | } 39 | createtemplate(code, name) 40 | } 41 | ) 42 | .command( 43 | 'watch', 44 | '开启实时编译', 45 | function (yargs) {//可选参数 46 | return yargs 47 | .option('help', { 48 | alias: 'h', 49 | describe: '查看帮助' 50 | }) 51 | }, 52 | function (argv) { 53 | let dir = path.resolve('.') 54 | new watcher(dir).start() 55 | } 56 | ) 57 | .command( 58 | 'build', 59 | '编译工程', 60 | function (yargs) {//可选参数 61 | return yargs 62 | .option('help', { 63 | alias: 'h', 64 | describe: '查看帮助' 65 | }) 66 | }, 67 | function (argv) { 68 | let dir = path.resolve('.', 'src') 69 | start({"d": dir}) 70 | } 71 | ) 72 | .command( 73 | 'zip', 74 | 'zip压缩模板文件', 75 | function(yargs) {//可选参数 76 | return yargs 77 | .option('path', { 78 | alias: 'p', 79 | describe: '文件路径或目录' 80 | }) 81 | .example(//示例 82 | 'cc-cli -p /xxx/tpl', 83 | '压缩/xxx/tpl目录下的模板文件' 84 | ) 85 | .example(//示例 86 | 'cc-cli -p /xxx/tpl/xxx.html', 87 | '压缩/xxx/tpl/xxx的模板文件' 88 | ) 89 | }, 90 | function(argv) { 91 | if(argv.path){ 92 | zip(argv.path); 93 | } 94 | } 95 | ) 96 | .help('help').argv; 97 | 98 | ;(function () { 99 | if (argv.v) {//查看版本号 100 | let json = require('./package.json') 101 | console.log(json.version) 102 | } 103 | })(); -------------------------------------------------------------------------------- /HybridFlutter/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /HybridFlutter/lib/util/color_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:hybrid_flutter/entity/property.dart'; 3 | 4 | Color hexToColor(String s) { 5 | // 如果传入的十六进制颜色值不符合要求,返回默认值 6 | if (s.length == 7) { 7 | return Color(int.parse(s.substring(1, 7), radix: 16) + 0xFF000000); 8 | } else { 9 | return Color(int.parse(s.substring(1, 9), radix: 16)); 10 | } 11 | } 12 | 13 | Color dealFontColor(Property property) { 14 | Color color = Colors.black; 15 | if (null == property) { 16 | return color; 17 | } 18 | String str = property.getValue(); 19 | if (null != str) { 20 | if (str.startsWith('#')) { 21 | color = hexToColor(str); 22 | } else { 23 | color = _getColor(str, defaultValue: Colors.black); 24 | } 25 | } 26 | return color; 27 | } 28 | 29 | Color dealColor(Property property) { 30 | Color color = Colors.transparent; 31 | if (null == property) { 32 | return color; 33 | } 34 | String str = property.getValue(); 35 | if (null != str) { 36 | if (str.startsWith('#')) { 37 | color = hexToColor(str); 38 | } else { 39 | color = _getColor(str); 40 | } 41 | } 42 | return color; 43 | } 44 | 45 | Color parseColor(String str, {Color defaultValue = Colors.transparent}) { 46 | Color color = defaultValue; 47 | if (null != str) { 48 | if (str.startsWith('#')) { 49 | color = hexToColor(str); 50 | } else { 51 | color = _getColor(str); 52 | } 53 | } 54 | return color; 55 | } 56 | 57 | Color _getColor(String str, {Color defaultValue = Colors.transparent}) { 58 | switch (str) { 59 | case 'white': 60 | return Colors.white; 61 | case 'blue': 62 | return Colors.blue; 63 | case 'green': 64 | return Colors.green; 65 | case 'yellow': 66 | return Colors.yellow; 67 | case 'cyan': 68 | return Colors.cyan; 69 | case 'gray': 70 | return Colors.grey; 71 | case 'black': 72 | return Colors.black; 73 | case 'red': 74 | return Colors.red; 75 | case 'orange': 76 | return Colors.orange; 77 | case 'brown': 78 | return Colors.brown; 79 | case 'pink': 80 | return Colors.pink; 81 | case 'purple': 82 | return Colors.purple; 83 | case 'indigo': 84 | return Colors.indigo; 85 | case 'teal': 86 | return Colors.teal; 87 | case 'lime': 88 | return Colors.lime; 89 | case 'amber': 90 | return Colors.amber; 91 | case 'transparent': 92 | return Colors.transparent; 93 | default: 94 | return defaultValue; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /HybridFlutter/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: hybrid_flutter 2 | description: A new Flutter application. 3 | 4 | # The following defines the version and build number for your application. 5 | # A version number is three numbers separated by dots, like 1.2.43 6 | # followed by an optional build number separated by a +. 7 | # Both the version and the builder number may be overridden in flutter 8 | # build by specifying --build-name and --build-number, respectively. 9 | # Read more about versioning at semver.org. 10 | version: 1.0.0+1 11 | 12 | environment: 13 | sdk: ">=2.1.0 <3.0.0" 14 | 15 | dependencies: 16 | flutter: 17 | sdk: flutter 18 | 19 | # The following adds the Cupertino Icons font to your application. 20 | # Use with the CupertinoIcons class for iOS style icons. 21 | cupertino_icons: ^0.1.2 22 | english_words: ^3.1.0 23 | json_annotation: ^2.0.0 24 | 25 | dev_dependencies: 26 | flutter_test: 27 | sdk: flutter 28 | 29 | 30 | # For information on the generic Dart part of this file, see the 31 | # following page: https://www.dartlang.org/tools/pub/pubspec 32 | 33 | # The following section is specific to Flutter. 34 | flutter: 35 | 36 | # The following line ensures that the Material Icons font is 37 | # included with your application, so that you can use the icons in 38 | # the material Icons class. 39 | uses-material-design: true 40 | 41 | # To add assets to your application, add an assets section, like this: 42 | # assets: 43 | # - images/a_dot_burr.jpeg 44 | # - images/a_dot_ham.jpeg 45 | 46 | # An image asset can refer to one or more resolution-specific "variants", see 47 | # https://flutter.io/assets-and-images/#resolution-aware. 48 | 49 | # For details regarding adding assets from package dependencies, see 50 | # https://flutter.io/assets-and-images/#from-packages 51 | 52 | # To add custom fonts to your application, add a fonts section here, 53 | # in this "flutter" section. Each entry in this list should have a 54 | # "family" key with the font family name, and a "fonts" key with a 55 | # list giving the asset and other descriptors for the font. For 56 | # example: 57 | # fonts: 58 | # - family: Schyler 59 | # fonts: 60 | # - asset: fonts/Schyler-Regular.ttf 61 | # - asset: fonts/Schyler-Italic.ttf 62 | # style: italic 63 | # - family: Trajan Pro 64 | # fonts: 65 | # - asset: fonts/TrajanPro.ttf 66 | # - asset: fonts/TrajanPro_Bold.ttf 67 | # weight: 700 68 | # 69 | # For details regarding fonts from package dependencies, 70 | # see https://flutter.io/custom-fonts/#from-packages 71 | -------------------------------------------------------------------------------- /HybridFlutter/android/app/src/main/kotlin/com/cc/hybrid/debug/Debugger.kt: -------------------------------------------------------------------------------- 1 | package com.cc.hybrid.debug 2 | 3 | import com.cc.hybrid.event.EventManager 4 | import java.io.ByteArrayOutputStream 5 | import java.io.IOException 6 | import java.io.InputStream 7 | import java.lang.ref.WeakReference 8 | import java.net.Socket 9 | import java.nio.ByteBuffer 10 | import java.nio.ByteOrder 11 | import java.util.concurrent.Executors 12 | 13 | // Thread to read content from Socket 14 | class Debugger(host: String, port: Int) : Thread() { 15 | 16 | var socket: WeakReference? = null 17 | var host: String? = null 18 | var port: Int? = null 19 | private val mThreadPool = Executors.newCachedThreadPool() 20 | 21 | init { 22 | this.host = host 23 | this.port = port 24 | } 25 | 26 | private var isStart = true 27 | 28 | fun release() { 29 | isStart = false 30 | releaseSocket() 31 | } 32 | 33 | private fun readLength(inputStream: InputStream): Int { 34 | val lengthBuffer = ByteArray(4) 35 | inputStream.read(lengthBuffer, 0, 4) 36 | val byteBuffer = ByteBuffer.wrap(lengthBuffer) 37 | byteBuffer.order(ByteOrder.LITTLE_ENDIAN) 38 | return byteBuffer.int 39 | } 40 | 41 | override fun run() { 42 | super.run() 43 | val socket = socket?.get() 44 | if (null != socket) { 45 | try { 46 | val inputStream = socket.getInputStream() 47 | while (!socket.isClosed && !socket.isInputShutdown && isStart) { 48 | val totalLength = readLength(inputStream) 49 | val baos = ByteArrayOutputStream() 50 | var i = 0 51 | while (baos.size() != totalLength) { 52 | i = inputStream.read() 53 | baos.write(i) 54 | } 55 | val json = baos.toString() 56 | EventManager.instance.sendMessage(EventManager.TYPE_SOCKET, "", json) 57 | } 58 | } catch (e: IOException) { 59 | 60 | } 61 | } 62 | } 63 | 64 | private fun releaseSocket() { 65 | try { 66 | if (null != socket) { 67 | val sk = socket!!.get() 68 | if (!sk!!.isClosed) { 69 | sk.close() 70 | } 71 | } 72 | } catch (e: IOException) { 73 | 74 | } 75 | 76 | } 77 | 78 | fun startSocket() { 79 | mThreadPool.execute { 80 | try { 81 | socket = WeakReference(Socket(host!!, port!!)) 82 | start() 83 | } catch (e: IOException) { 84 | 85 | } 86 | } 87 | } 88 | 89 | } -------------------------------------------------------------------------------- /cc-cli/src/detail.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | {{"书名:" + detail.title}} 23 | 24 | 25 | {{"作者:" + detail.author}} 26 | 27 | 28 | {{"出版社:" + detail.publisher}} 29 | 30 | 31 | {{"副标题:" + detail.subtitle}} 32 | 33 | 34 | 35 | 36 | 37 | 内容简介 38 | 39 | 40 | {{detail.summary}} 41 | 42 | 43 | 作者简介 44 | 45 | 46 | {{detail.author_intro}} 47 | 48 | 49 | 目录 50 | 51 | 52 | {{catalogShort}} 53 | 54 | 55 | 56 | {{btnText}} 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /HybridFlutter/android/app/src/main/kotlin/com/cc/hybrid/v8/V8Manager.kt: -------------------------------------------------------------------------------- 1 | package com.cc.hybrid.v8 2 | 3 | import android.content.Context 4 | import com.cc.hybrid.Logger 5 | import com.cc.hybrid.bridge.js.JSConsole 6 | import com.cc.hybrid.util.TimerManager 7 | import com.eclipsesource.v8.JavaCallback 8 | import com.eclipsesource.v8.V8 9 | import com.eclipsesource.v8.V8Array 10 | import com.eclipsesource.v8.V8Object 11 | import okio.Okio 12 | import java.io.IOException 13 | 14 | object V8Manager { 15 | 16 | lateinit var v8: V8 17 | 18 | fun initV8(context: Context) { 19 | v8 = V8.createV8Runtime() 20 | executeScript("var global = this;") 21 | registerObj() 22 | registerFunc() 23 | evaluateJsFileFromAsset(context, "framework.js") 24 | } 25 | 26 | private fun registerObj() { 27 | val v8Console = V8Object(v8) 28 | v8.add("console", v8Console) 29 | val jsConsole = JSConsole() 30 | v8Console.registerJavaMethod(jsConsole, "log", "log", arrayOf>(java.lang.Object::class.java)) 31 | v8Console.release() 32 | } 33 | 34 | private fun registerFunc() { 35 | v8.registerJavaMethod(JavaCallback { receiver, parameters -> 36 | val pageId = parameters.getString(0) 37 | val timerId = parameters.getString(1) 38 | val delayed = parameters?.getInteger(2) 39 | if (null != delayed) { 40 | TimerManager.setTimeout(pageId, timerId, delayed) 41 | } 42 | receiver as Any 43 | }, "__native__setTimeout") 44 | v8.registerJavaMethod(JavaCallback { receiver, parameters -> 45 | val timerId = parameters.getString(0) 46 | TimerManager.delTimer(timerId) 47 | receiver as Any 48 | }, "__native__clearTimeout") 49 | v8.registerJavaMethod(JavaCallback { receiver, parameters -> 50 | val pageId = parameters.getString(0) 51 | val timerId = parameters.getString(1) 52 | val delayed = parameters?.getInteger(2) 53 | if (null != delayed) { 54 | TimerManager.setInterval(pageId, timerId, delayed) 55 | } 56 | receiver as Any 57 | }, "__native__setInterval") 58 | v8.registerJavaMethod(JavaCallback { receiver, parameters -> 59 | val timerId = parameters.getString(0) 60 | TimerManager.delTimer(timerId) 61 | receiver as Any 62 | }, "__native__clearInterval") 63 | } 64 | 65 | @Throws(IOException::class) 66 | fun evaluateJsFileFromAsset(context: Context, filename: String) { 67 | val source = Okio.buffer(Okio.source(context.assets.open(filename))) 68 | var script = source.readUtf8() 69 | if (null == script) { 70 | script = "" 71 | } 72 | source.close() 73 | executeScript(script) 74 | } 75 | 76 | fun executeScript(script: String): Any? { 77 | try { 78 | return v8.executeScript(script) 79 | } catch (e: Exception) { 80 | Logger.printError(e) 81 | } 82 | return null 83 | } 84 | 85 | 86 | } -------------------------------------------------------------------------------- /HybridFlutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /webpack-framework/src/framework.js: -------------------------------------------------------------------------------- 1 | 2 | global.guid = function() { 3 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { 4 | let r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8); 5 | return v.toString(16); 6 | }); 7 | }; 8 | 9 | global.judgeIsNotNull = function(pageId, id, val) { 10 | return !!(pageId && id && val); 11 | 12 | }; 13 | 14 | global.getExpValue = function (data, script) { 15 | const expFunc = exp => { 16 | return new Function('', 'with(this){' + exp + '}').bind(data)(); 17 | }; 18 | let value = expFunc(script); 19 | if (value instanceof Object) { 20 | return JSON.stringify(value); 21 | } 22 | if (value instanceof Array) { 23 | return JSON.stringify(value); 24 | } 25 | return value; 26 | }; 27 | 28 | require('./page'); 29 | 30 | // let test = "Page({\n" + 31 | // " data: {\n" + 32 | // " color1:\"red\",\n" + 33 | // " color2:\"green\",\n" + 34 | // " color3:\"blue\",\n" + 35 | // " list: [{n:3, m:4},{n:4, m:5}],\n" + 36 | // " list2: [{n:3, m:4},{n:4, m:5}],\n" + 37 | // " list3: [{n:4, m:4},{n:5, m:5},{n:6, m:6}]\n" + 38 | // " },\n" + 39 | // " onLoad() {\n" + 40 | // " let list = this.data.list3;\n" + 41 | // " this.setData({\n" + 42 | // " color1:\"black\",\n" + 43 | // " color2:\"black\",\n" + 44 | // " list: list\n" + 45 | // " });\n" + 46 | // " },\n" + 47 | // " onLoadT() {\n" + 48 | // " let list = this.data.list2;\n" + 49 | // " this.setData({\n" + 50 | // " color1:\"red\",\n" + 51 | // " color2:\"red\",\n" + 52 | // " list: list\n" + 53 | // " });\n" + 54 | // " }\n" + 55 | // "});"; 56 | // 57 | // function main() { 58 | // loadPage("1111"); 59 | // // global.page.onLoad(); 60 | // let page = getPage("1111"); 61 | // page.__native__evalInPage(test); 62 | // page.data = global.page.data; 63 | // page.onLoad = global.page.onLoad; 64 | // page.onLoadT = global.page.onLoadT; 65 | // // console.log(page.data); 66 | // page.__native__initComplete(); 67 | // // console.log(page.data); 68 | // // page.__native__getExpValue("x1", "t1", "color", true, "return color1"); 69 | // // let x = page.__native__getExpValue("x1", "t1", "visible", true, "return 'x' + color1"); 70 | // // let y = page.__native__getExpValue('x4', "t1", "for", "return list.length"); 71 | // // console.log("x = " + x); 72 | // // page.__native__getExpValue("x2", "t1", "color", true, "return color2"); 73 | // // page.__native__getExpValue("x3", "t1", "color", true, "return color3"); 74 | // // page.__native__getExpValue("x5", "t1", "innerHTML", true, "var index = 0; var item = list[index]; return item.n"); 75 | // // page.__native__getExpValue("x6", "t1", "innerHTML", true, "var index = 0; var item = list[index]; return item.m"); 76 | // page.__native__getExpValue("x6", "t1", "innerHTML", true, "return list[1].m"); 77 | // // console.log("y = " + y); 78 | // page.onLoad(); 79 | // 80 | // page.__native__removeObserverByIds(['x1']); 81 | // page.onLoadT(); 82 | // } 83 | // 84 | // main(); -------------------------------------------------------------------------------- /cc-cli/src_bak/example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | cc.setNavigationBarTitle 17 | 18 | 19 | 设置标题 20 | 21 | 22 | 23 | 24 | 25 | cc.setNavigationBarColor 26 | 27 | 28 | 设置颜色 29 | 30 | 31 | 32 | 33 | 34 | cc.setBackgroundColor 35 | 36 | 37 | 设置背景 38 | 39 | 40 | 41 | 42 | 43 | cc.showToast 44 | 45 | 46 | Toast 47 | 48 | 49 | 50 | 51 | 52 | 53 | cc.showLoading 54 | cc.hideLoading 55 | 56 | 57 | 58 | Loading 59 | 60 | 61 | 62 | 63 | 64 | 65 | cc.startPullDownRefresh 66 | cc.stopPullDownRefresh 67 | 68 | 69 | 70 | start 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /HybridFlutter/android/app/src/main/kotlin/com/cc/hybrid/util/TimerManager.kt: -------------------------------------------------------------------------------- 1 | package com.cc.hybrid.util 2 | 3 | import android.os.Handler 4 | import android.os.Message 5 | import com.cc.hybrid.bridge.js.JSPageManager 6 | 7 | object TimerManager { 8 | 9 | private const val TYPE_SET_TIME_OUT = 1 10 | private const val TYPE_TICK = 2 11 | 12 | private val timers = mutableSetOf() 13 | private val pageTimers = mutableMapOf>() 14 | 15 | private var intervalHandler: MHandler? = null 16 | 17 | init { 18 | intervalHandler = MHandler() 19 | } 20 | 21 | private fun addTimer(pageId: String, timerId: String) { 22 | timers.add(timerId) 23 | var set = pageTimers[pageId] 24 | if (null == set) { 25 | set = mutableSetOf() 26 | } 27 | set.add(timerId) 28 | pageTimers[pageId] = set 29 | } 30 | 31 | fun setTimeout(pageId: String, timerId: String, delayed: Int) { 32 | val msg = intervalHandler?.obtainMessage() ?: Message() 33 | msg.what = timerId.hashCode() 34 | val intervalEvent = IntervalEvent(timerId, TYPE_SET_TIME_OUT, delayed) 35 | msg.obj = intervalEvent 36 | intervalHandler?.sendMessageDelayed(msg, delayed.toLong()) 37 | addTimer(pageId, timerId) 38 | } 39 | 40 | fun setInterval(pageId: String, timerId: String, delayed: Int) { 41 | val msg = intervalHandler?.obtainMessage() ?: Message() 42 | msg.what = timerId.hashCode() 43 | val intervalEvent = IntervalEvent(timerId, TYPE_TICK, delayed) 44 | msg.obj = intervalEvent 45 | intervalHandler?.sendMessageDelayed(msg, delayed.toLong()) 46 | addTimer(pageId, timerId) 47 | } 48 | 49 | fun delTimer(timerId: String) { 50 | intervalHandler?.removeMessages(timerId.hashCode()) 51 | timers.remove(timerId) 52 | } 53 | 54 | fun delTimerByPageId(pageId: String) { 55 | val set = pageTimers[pageId] 56 | if (null != set) { 57 | timers.removeAll(set) 58 | } 59 | } 60 | 61 | internal class IntervalEvent constructor(val timerId: String, val type: Int, val millis: Int) 62 | 63 | internal class MHandler : Handler() { 64 | override fun handleMessage(msg: Message) { 65 | val intervalEvent = msg.obj as IntervalEvent 66 | if (isExist(intervalEvent)) { 67 | when (intervalEvent.type) { 68 | TYPE_SET_TIME_OUT -> { 69 | JSPageManager.callback(intervalEvent.timerId) 70 | timers.remove(intervalEvent.timerId) 71 | } 72 | TYPE_TICK -> { 73 | JSPageManager.callback(intervalEvent.timerId) 74 | onTick(intervalEvent) 75 | } 76 | } 77 | } 78 | super.handleMessage(msg) 79 | } 80 | 81 | private fun isExist(intervalEvent: IntervalEvent): Boolean { 82 | return timers.contains(intervalEvent.timerId) 83 | } 84 | 85 | private fun onTick(intervalEvent: IntervalEvent) { 86 | val msg = this.obtainMessage() 87 | msg.what = intervalEvent.timerId.hashCode() 88 | msg.obj = intervalEvent 89 | this.sendMessageDelayed(msg, intervalEvent.millis.toLong()) 90 | } 91 | } 92 | 93 | } -------------------------------------------------------------------------------- /cc-cli/src_bak/example.js: -------------------------------------------------------------------------------- 1 | Page({ 2 | /** 3 | * 页面数据 4 | */ 5 | data: { 6 | colors:['red','green','blue'], 7 | backgroundColors:['white','gray','#eeeeee'], 8 | count: 0, 9 | random: 0 10 | }, 11 | setNavigationBarTitle() { 12 | this.data.count++; 13 | cc.setNavigationBarTitle({ 14 | title: '设置标题' + this.data.count 15 | }); 16 | }, 17 | setNavigationBarColor() { 18 | // let random = Math.ceil(Math.random() * 3); 19 | // console.log('random = ' + random); 20 | this.data.random++; 21 | if (this.data.random > 2) { 22 | this.data.random = 0; 23 | } 24 | let color = this.data.colors[this.data.random]; 25 | cc.setNavigationBarColor({ 26 | backgroundColor: color 27 | }); 28 | }, 29 | setBackgroundColor() { 30 | this.data.random++; 31 | if (this.data.random > 2) { 32 | this.data.random = 0; 33 | } 34 | let color = this.data.backgroundColors[this.data.random]; 35 | cc.setBackgroundColor({ 36 | backgroundColor: color 37 | }); 38 | }, 39 | showToast() { 40 | cc.showToast({ 41 | title: "I'm toast!" 42 | }); 43 | }, 44 | showLoading() { 45 | cc.showLoading({}); 46 | var timerId = setTimeout(function (...args) { 47 | console.log(JSON.stringify(...args)); 48 | cc.hideLoading(); 49 | }, 3000, "1","2"); 50 | console.log("timerId = " + timerId); 51 | }, 52 | startPullDownRefresh() { 53 | cc.startPullDownRefresh(); 54 | }, 55 | onclick() { 56 | // let name = "cms" 57 | // let width = this.data.width + 0.1; 58 | // let height = this.data.height + 0.1; 59 | 60 | // let random = Math.ceil(Math.random() * 3); 61 | // console.log('random = ' + random); 62 | 63 | // let btnColor = this.data.colors[random]; 64 | // this.setData({ name, width, height, btnColor }); 65 | 66 | // let that = this; 67 | // cc.request({ 68 | // url: 'https://www.easy-mock.com/mock/5ab46236e1c17b3b2cc55843/example/items', 69 | // data: {}, 70 | // header: {}, 71 | // method: 'get', 72 | // success: function (response) { 73 | // console.log('request success:' + JSON.stringify(response)); 74 | // that.setData({ 75 | // list: response.body.payload 76 | // }); 77 | // }, 78 | // fail: function (error) { 79 | // console.log('request error:' + JSON.stringify(error)); 80 | // }, 81 | // complete: function () { 82 | // console.log('request complete'); 83 | // } 84 | // }); 85 | }, 86 | /** 87 | * 页面加载时触发。一个页面只会调用一次,可以在 onLoad 的参数中获取打开当前页面路径中的参数。 88 | */ 89 | onLoad(e) { 90 | 91 | }, 92 | 93 | onPullDownRefresh() { 94 | console.log("onPullDownRefresh"); 95 | setTimeout(function() { 96 | cc.stopPullDownRefresh(); 97 | }, 3000); 98 | }, 99 | 100 | /** 101 | * 页面卸载时触发。如cc.redirectTo或cc.navigateBack到其他页面时。 102 | */ 103 | onUnload() { 104 | 105 | } 106 | }); -------------------------------------------------------------------------------- /HybridFlutter/lib/util/expression_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | import 'package:hybrid_flutter/entity/component.dart'; 3 | 4 | const String TYPE_PROPERTY = "property"; 5 | const String TYPE_DIRECTIVE = "directive"; 6 | 7 | ///在双花括号中获取表达式 8 | String getExpression(String dataSource) { 9 | var trim = dataSource?.trim(); 10 | if (trim.length <= 4) return ""; 11 | return trim.substring(2, trim.length - 2); 12 | } 13 | 14 | ///获取在for里面的表达式 判断是否有表达式前缀,有则需要拼接 15 | ///e.g.:return list 16 | ///e.g.:var index = 0; var item = list[index]; return item 17 | String getInRepeatExp(Component component, String exp) { 18 | if (null == component.inRepeatPrefixExp) { 19 | return 'return $exp'; 20 | } 21 | return '${component.inRepeatPrefixExp} return $exp'; 22 | } 23 | 24 | ///获取在for里面的表达前缀,判断父层级是否有前缀,有则需要在拼接在前面 25 | ///e.g.:var index = 0; var item = list[index]; 26 | ///e.g.; var index = 0; var item = list[index]; var idx = 0; var it = item[idx]; 27 | String getInRepeatPrefixExp( 28 | indexName, itemName, exp, inRepeatIndex, parentInRepeatPrefixExp) { 29 | var prefix = 30 | 'var $indexName = $inRepeatIndex; var $itemName = $exp[$indexName];'; 31 | if (null != parentInRepeatPrefixExp && parentInRepeatPrefixExp.isNotEmpty) { 32 | prefix = '$parentInRepeatPrefixExp $prefix'; 33 | } 34 | return prefix; 35 | } 36 | 37 | ///处理property以及innerHTML 38 | Future handleProperty( 39 | MethodChannel methodChannel, String pageId, Component component) async { 40 | for (var entry in component.properties.entries.toList()) { 41 | var exp = entry.value.property; 42 | if (entry.value.containExpression) { 43 | exp = getExpression(exp); 44 | var watch = true; 45 | if (component.isInRepeat) { 46 | exp = getInRepeatExp(component, exp); 47 | watch = false; 48 | } else { 49 | exp = 'return $exp'; 50 | } 51 | var result = await _calcExpression(methodChannel, pageId, component.id, 52 | TYPE_PROPERTY, entry.key, watch, exp); 53 | // print("$exp = $result"); 54 | entry.value.setValue(result); 55 | } 56 | } 57 | } 58 | 59 | /// pageId: 页面ID 60 | /// componentId :组件ID 61 | /// type :TYPE_PROPERTY(属性), TYPE_DIRECTIVE(指令) 62 | /// key : properties对应的key,方便结果回调查找 63 | /// expression : 表达式 64 | Future calcRepeatSize(MethodChannel methodChannel, String pageId, 65 | String componentId, String type, String key, String expression) async { 66 | return await methodChannel.invokeMethod('handleRepeat', { 67 | 'pageId': pageId, 68 | 'id': componentId, 69 | 'type': type, 70 | 'key': key, 71 | 'watch': true, 72 | 'expression': '$expression.length' 73 | }); 74 | } 75 | 76 | /// pageId: 页面ID 77 | /// componentId :组件ID 78 | /// type :TYPE_PROPERTY(属性), TYPE_DIRECTIVE(指令) 79 | /// key : properties对应的key,方便结果回调查找 80 | /// watch: 是否监听表达式进行局部刷新 81 | /// expression : 表达式 82 | Future _calcExpression( 83 | MethodChannel methodChannel, 84 | String pageId, 85 | String componentId, 86 | String type, 87 | String key, 88 | bool watch, 89 | String expression) async { 90 | return await methodChannel.invokeMethod('handleExpression', { 91 | 'pageId': pageId, 92 | 'id': componentId, 93 | 'type': type, 94 | 'key': key, 95 | 'watch': watch, 96 | 'expression': expression 97 | }); 98 | } 99 | 100 | /// 组件移除监听 101 | /// ids 组件id集合 102 | Future removeObserver( 103 | MethodChannel methodChannel, String pageId, List ids) async { 104 | await methodChannel 105 | .invokeMethod('removeObserver', {'pageId': pageId, 'ids': ids}); 106 | } 107 | -------------------------------------------------------------------------------- /HybridFlutter/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /HybridFlutter/lib/ui/list_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/services.dart'; 4 | import 'package:hybrid_flutter/entity/component.dart'; 5 | import 'package:hybrid_flutter/entity/data.dart'; 6 | import 'package:hybrid_flutter/util/event_util.dart'; 7 | 8 | import 'base_widget.dart'; 9 | import 'basic.dart'; 10 | 11 | class ListViewStateless extends BaseWidget { 12 | ListViewStateless(BaseWidget parent, String pageId, 13 | MethodChannel methodChannel, Component component) 14 | : super( 15 | parent: parent, 16 | pageId: pageId, 17 | methodChannel: methodChannel, 18 | component: component, 19 | data: ValueNotifier(Data(component.properties))); 20 | 21 | void _scrollToUpper() { 22 | var upper = component.events["bindscrolltoupper"]; 23 | if (null != upper) { 24 | onScrollLimitEvent(methodChannel, pageId, component.id, upper); 25 | } 26 | } 27 | 28 | void _scrollToLower() { 29 | var lower = component.events["bindscrolltolower"]; 30 | if (null != lower) { 31 | onScrollLimitEvent(methodChannel, pageId, component.id, lower); 32 | } 33 | } 34 | 35 | void _scroll(double pixels) { 36 | var bindScroll = component.events["bindscroll"]; 37 | if (null != bindScroll) { 38 | onScrollEvent(methodChannel, pageId, component.id, bindScroll, pixels); 39 | } 40 | } 41 | 42 | @override 43 | Widget build(BuildContext context) { 44 | return Scrollbar( 45 | key: ObjectKey(component), 46 | child: NotificationListener( 47 | onNotification: (ScrollNotification notification) { 48 | if (notification is ScrollEndNotification) { 49 | if (notification.metrics.pixels == 50 | notification.metrics.minScrollExtent) { 51 | _scrollToUpper(); 52 | } else if (notification.metrics.pixels == 53 | notification.metrics.maxScrollExtent) { 54 | _scrollToLower(); 55 | } 56 | _scroll(notification.metrics.pixels); 57 | } 58 | return true; // 返回false可见滚动条 59 | }, 60 | child: ValueListenableBuilder( 61 | builder: (BuildContext context, Data data, Widget child) { 62 | return ListView( 63 | key: UniqueKey(), 64 | scrollDirection: MAxis.parse(data.map["scroll-direction"], 65 | defaultValue: Axis.vertical), 66 | reverse: 67 | MBool.parse(data.map["reverse"], defaultValue: false), 68 | // controller: _scrollController, 69 | primary: MBool.parse(data.map["primary"]), 70 | // physics: data.map["physics"], 71 | shrinkWrap: MBool.parse(data.map["shrink-wrap"], 72 | defaultValue: false), 73 | padding: MPadding.parse(data.map), 74 | itemExtent: MDouble.parse(data.map["item-extent"]), 75 | addAutomaticKeepAlives: MBool.parse( 76 | data.map["add-automatic-keep-alives"], 77 | defaultValue: true), 78 | addRepaintBoundaries: MBool.parse( 79 | data.map["add-repaint-boundaries"], 80 | defaultValue: true), 81 | addSemanticIndexes: MBool.parse( 82 | data.map["add-semantic-indexes"], 83 | defaultValue: true), 84 | cacheExtent: MDouble.parse(data.map["cache-extent"]), 85 | children: data.children, 86 | semanticChildCount: 87 | MInt.parse(data.map["semantic-child-count"])); 88 | }, 89 | valueListenable: this.data)), 90 | ); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /cc-cli/src/home.js: -------------------------------------------------------------------------------- 1 | /** home */ 2 | Page({ 3 | /** 4 | * 页面数据 5 | */ 6 | data: { 7 | list: [], 8 | }, 9 | 10 | /** 11 | * 页面加载时触发。一个页面只会调用一次,可以在 onLoad 的参数中获取打开当前页面路径中的参数。 12 | */ 13 | onLoad(e) { 14 | cc.setNavigationBarTitle({ 15 | title: 'Python系列丛书' 16 | }); 17 | cc.showLoading({}); 18 | this.doRequest(true); 19 | }, 20 | 21 | doRequest(isOnload) { 22 | const book = { 23 | title:"Python编程", 24 | image:"https://img1.doubanio.com/view/subject/s/public/s33716278.jpg", 25 | author:"[美]埃里克·马瑟斯(Eric Matthes)", 26 | publisher:"人民邮电出版社", 27 | subtitle:"从入门到实践(第2版)", 28 | summary:"本书是针对所有层次Python读者而作的Python入门书。全书分两部分:第一部分介绍用Python编程所必须了解的基本概念,包括Matplotlib等强大的Python库和工具,以及列表、字典、if语句、类、文件与异常、代码测试等内容;第二部分将理论付诸实践,讲解如何开发三个项目,包括简单的2D游戏、利用数据生成交互式的信息图以及创建和定制简单的Web应用,并帮助读者解决常见编程问题和困惑。第2版进行了全面修订,简化了Python安装流程,新增了f字符串、get()方法等内容,并且在项目中使用了Plotly库以及新版本的Django和Bootstrap,等等。", 29 | author_intro:"埃里克·马瑟斯(Eric Matthes)\n\n高中科学和数学老师,现居住在阿拉斯加,在当地讲授Python入门课程。他从5岁开始就一直在编写程序。", 30 | catalog:"第一部分 基础知识\n" + 31 | "第1章 起步  2\n" + 32 | "1.1 搭建编程环境  2\n" + 33 | "1.1.1 Python版本  2\n" + 34 | "1.1.2 运行Python代码片段  2\n" + 35 | "1.1.3 Sublime Text简介  3\n" + 36 | "1.2 在不同操作系统中搭建Python编程环境  3\n" + 37 | "1.2.1 在Windows系统中搭建Python编程环境  4\n" + 38 | "1.2.2 在macOS系统中搭建Python编程环境  5\n" + 39 | "1.2.3 在Linux 系统中搭建Python编程环境  7\n" + 40 | "1.3 运行Hello World 程序  8\n" + 41 | "1.3.1 配置Sublime Text以使用正确的Python版本  8\n" + 42 | "1.3.2 运行程序hello_world.py  8\n" + 43 | "1.4 解决安装问题  9\n" + 44 | "1.5 从终端运行Python程序  9\n" + 45 | "1.5.1 在Windows系统中从终端运行Python 程序  10\n" + 46 | "1.5.2 在Linux和macOS系统中从终端运行Python程序  10\n", 47 | }; 48 | let that = this; 49 | that.setData({ 50 | list: [ 51 | book, 52 | book, 53 | book, 54 | book, 55 | book, 56 | book, 57 | book, 58 | book, 59 | book, 60 | book, 61 | book, 62 | book, 63 | book, 64 | book, 65 | book, 66 | book, 67 | book, 68 | book, 69 | book, 70 | book, 71 | ] 72 | }); 73 | if (isOnload) { 74 | cc.hideLoading(); 75 | } else { 76 | cc.stopPullDownRefresh(); 77 | } 78 | // cc.request({ 79 | // url: 'http://47.107.46.220:10808/query', //'https://douban.uieee.com/v2/book/search?q=python', 80 | // data: {}, 81 | // header: {}, 82 | // method: 'get', 83 | // success: function (response) { 84 | // that.setData({ 85 | // list: response.body.books 86 | // }); 87 | // cc.showToast({ 88 | // title: '加载成功' 89 | // }); 90 | // }, 91 | // fail: function (error) { 92 | // console.log('request error:' + JSON.stringify(error)); 93 | // cc.showToast({ 94 | // title: '加载失败' 95 | // }); 96 | // }, 97 | // complete: function () { 98 | // console.log('request complete'); 99 | // if (isOnload) { 100 | // cc.hideLoading(); 101 | // } else { 102 | // cc.stopPullDownRefresh(); 103 | // } 104 | // } 105 | // }); 106 | }, 107 | 108 | onItemClick(e) { 109 | var item = this.data.list[e.target.dataset.index]; 110 | cc.navigateTo({ 111 | url: "detail?item=" + JSON.stringify(item) 112 | }); 113 | }, 114 | 115 | onPullDownRefresh() { 116 | console.log("onPullDownRefresh"); 117 | this.doRequest(false); 118 | }, 119 | 120 | /** 121 | * 页面卸载时触发。如cc.redirectTo或cc.navigateBack到其他页面时。 122 | */ 123 | onUnload() { 124 | 125 | } 126 | }); -------------------------------------------------------------------------------- /HybridFlutter/android/app/src/main/kotlin/com/cc/hybrid/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.cc.hybrid 2 | 3 | import android.os.Bundle 4 | import android.os.Handler 5 | import android.os.Message 6 | import com.cc.hybrid.bridge.flutter.FlutterPluginMethodChannel 7 | import com.cc.hybrid.debug.Debugger 8 | import com.cc.hybrid.event.EventManager 9 | import com.cc.hybrid.util.LoadingUtil 10 | import com.cc.hybrid.util.SpUtil 11 | import com.cc.hybrid.util.ToastUtil 12 | import io.flutter.embedding.android.FlutterActivity 13 | import io.flutter.embedding.engine.FlutterEngine 14 | import io.flutter.plugin.common.BasicMessageChannel 15 | import io.flutter.plugin.common.MethodChannel 16 | import io.flutter.plugin.common.StringCodec 17 | import io.flutter.plugins.GeneratedPluginRegistrant 18 | import org.json.JSONObject 19 | import java.lang.ref.WeakReference 20 | 21 | 22 | class MainActivity : FlutterActivity() { 23 | 24 | private lateinit var debug: Debugger 25 | private lateinit var channel: BasicMessageChannel 26 | 27 | override fun configureFlutterEngine(flutterEngine: FlutterEngine) { 28 | GeneratedPluginRegistrant.registerWith(flutterEngine) 29 | MethodChannel(flutterEngine.dartExecutor.binaryMessenger, FlutterPluginMethodChannel.CHANNEL) 30 | .setMethodCallHandler() {call, result -> 31 | FlutterPluginMethodChannel.onMethodCall(call, result) 32 | } 33 | channel = BasicMessageChannel(flutterEngine.dartExecutor.binaryMessenger, "com.cc.hybrid/basic", StringCodec.INSTANCE) 34 | } 35 | 36 | override fun onCreate(savedInstanceState: Bundle?) { 37 | super.onCreate(savedInstanceState) 38 | SpUtil.initSp(this) 39 | ToastUtil.initToast(this) 40 | LoadingUtil.initDialog(this) 41 | initHandler() 42 | debug() 43 | } 44 | 45 | private fun initHandler() { 46 | EventManager.instance.initHandler(MHandler(this)) 47 | } 48 | 49 | private fun debug() { 50 | debug = Debugger("192.168.12.170", 9999) 51 | debug.startSocket() 52 | } 53 | 54 | private fun sendMessage2Flutter(type: Int, pageId: String, content: String) { 55 | val jsonObject = JSONObject() 56 | jsonObject.put("type", type) 57 | jsonObject.put("pageId", pageId) 58 | jsonObject.put("message", content) 59 | channel.send(jsonObject.toString()) 60 | } 61 | 62 | override fun onDestroy() { 63 | super.onDestroy() 64 | LoadingUtil.destroy() 65 | EventManager.instance.destroy() 66 | debug.release() 67 | } 68 | 69 | class MHandler(activity: MainActivity) : Handler() { 70 | private val mActivity: WeakReference = WeakReference(activity) 71 | override fun handleMessage(msg: Message) { 72 | super.handleMessage(msg) 73 | val jsonObject = msg.obj as JSONObject 74 | val pageId = jsonObject.getString("pageId") 75 | val json = jsonObject.get("obj").toString() 76 | when (msg.what) { 77 | EventManager.TYPE_SOCKET -> { 78 | mActivity.get()?.sendMessage2Flutter(EventManager.TYPE_SOCKET, pageId, json) 79 | } 80 | EventManager.TYPE_REFRESH -> { 81 | mActivity.get()?.sendMessage2Flutter(EventManager.TYPE_REFRESH, pageId, json) 82 | } 83 | EventManager.TYPE_NAVIGATION_BAR_TITLE -> { 84 | mActivity.get()?.sendMessage2Flutter(EventManager.TYPE_NAVIGATION_BAR_TITLE, pageId, json) 85 | } 86 | EventManager.TYPE_NAVIGATE_TO -> { 87 | mActivity.get()?.sendMessage2Flutter(EventManager.TYPE_NAVIGATE_TO, pageId, json) 88 | } 89 | EventManager.TYPE_NAVIGATION_BAR_COLOR -> { 90 | mActivity.get()?.sendMessage2Flutter(EventManager.TYPE_NAVIGATION_BAR_COLOR, pageId, json) 91 | } 92 | EventManager.TYPE_BACKGROUND_COLOR -> { 93 | mActivity.get()?.sendMessage2Flutter(EventManager.TYPE_BACKGROUND_COLOR, pageId, json) 94 | } 95 | EventManager.TYPE_START_PULL_DOWN_REFRESH -> { 96 | mActivity.get()?.sendMessage2Flutter(EventManager.TYPE_START_PULL_DOWN_REFRESH, pageId, json) 97 | } 98 | EventManager.TYPE_STOP_PULL_DOWN_REFRESH -> { 99 | mActivity.get()?.sendMessage2Flutter(EventManager.TYPE_STOP_PULL_DOWN_REFRESH, pageId, json) 100 | } 101 | } 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /HybridFlutter/android/app/src/main/kotlin/com/cc/hybrid/bridge/js/JSNetwork.kt: -------------------------------------------------------------------------------- 1 | package com.cc.hybrid.bridge.js 2 | 3 | import android.os.Handler 4 | import android.os.Looper 5 | import com.alibaba.fastjson.JSON 6 | import com.alibaba.fastjson.JSONObject 7 | import com.eclipsesource.v8.V8Object 8 | import com.cc.hybrid.http.HttpRequest 9 | import com.cc.hybrid.http.HttpRequestUtil 10 | import com.cc.hybrid.v8.V8Util 11 | import okhttp3.Call 12 | import okhttp3.Callback 13 | import okhttp3.Request 14 | import okhttp3.Response 15 | import java.io.IOException 16 | 17 | class JSNetwork { 18 | 19 | fun request(request: V8Object) { 20 | val builder = Request.Builder() 21 | val url = request.getString(URL) 22 | builder.url(url) 23 | val header = request.getObject(HEADER) 24 | val body = request.getObject(DATA) 25 | var method = HttpRequest.METHOD_POST 26 | if (request.contains(METHOD)) { 27 | method = request.getString(METHOD) 28 | } 29 | val headerMap: HashMap 30 | if (null != header && !header.isUndefined) { 31 | headerMap = HashMap() 32 | val temp = HashMap(V8Util.toMap(header)) 33 | temp.keys.forEach { 34 | if (null != temp[it]) { 35 | headerMap[it] = temp[it].toString() 36 | } 37 | } 38 | } else { 39 | headerMap = HashMap() 40 | } 41 | 42 | for (key in headerMap.keys) { 43 | if (null != headerMap[key]) { 44 | builder.addHeader(key, headerMap[key]!!) 45 | } 46 | } 47 | 48 | val bodyMap: HashMap 49 | if (null != body && !body.isUndefined) { 50 | bodyMap = HashMap() 51 | val temp = HashMap(V8Util.toMap(body)) 52 | temp.keys.forEach { 53 | if (null != temp[it]) { 54 | bodyMap[it] = temp[it]!! 55 | } 56 | } 57 | } else { 58 | bodyMap = HashMap() 59 | } 60 | 61 | for (key in bodyMap.keys) { 62 | if (null != bodyMap[key]) { 63 | // builder.(key, bodyObject[key]!!) 64 | } 65 | } 66 | 67 | val requestId = request.getString(REQUEST_ID) 68 | val pageId = request.getString(PAGE_ID) 69 | val handler = Handler(Looper.getMainLooper()) 70 | 71 | HttpRequestUtil.okHttpClient.newCall(builder.build()).enqueue(object : Callback { 72 | override fun onFailure(call: Call?, e: IOException?) { 73 | val result = JSONObject() 74 | result[CODE] = -1 75 | result[MESSAGE] = e?.message 76 | handler.post { 77 | JSPageManager.onNetworkResult(pageId, requestId, FAIL, result.toJSONString()) 78 | } 79 | } 80 | 81 | override fun onResponse(call: Call?, response: Response?) { 82 | val result = JSONObject() 83 | result[CODE] = -1 84 | result[MESSAGE] = "" 85 | try { 86 | if (null != response) { 87 | result[CODE] = response.code() 88 | result[BODY] = JSON.parse(response.body().string()) 89 | result[MESSAGE] = response.message() 90 | result[HEADERS] = response.headers() 91 | result[HANDSHAKE] = response.handshake() 92 | result[PROTOCOL] = response.protocol() 93 | handler.post { 94 | JSPageManager.onNetworkResult(pageId, requestId, SUCCESS, result.toJSONString()) 95 | } 96 | } 97 | } catch (e: Exception) { 98 | result[MESSAGE] = e.message 99 | handler.post { 100 | JSPageManager.onNetworkResult(pageId, requestId, FAIL, result.toJSONString()) 101 | } 102 | } 103 | } 104 | }) 105 | } 106 | 107 | companion object { 108 | private val URL = "url" 109 | private val HEADER = "header" 110 | private val DATA = "data" 111 | private val METHOD = "method" 112 | private val REQUEST_ID = "requestId" 113 | private val PAGE_ID = "pageId" 114 | 115 | private val SUCCESS = "success" 116 | private val FAIL = "fail" 117 | 118 | private val RESULT = "result" 119 | private val CODE = "code" 120 | private val MESSAGE = "message" 121 | private val BODY = "body" 122 | private val HEADERS = "headers" 123 | private val HANDSHAKE = "Handshake" 124 | private val PROTOCOL = "protocol" 125 | 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /webpack-framework/src/page.js: -------------------------------------------------------------------------------- 1 | require('./observer'); 2 | 3 | global.pages = {}; 4 | global.callbacks = {}; 5 | global.callbackArgs = {}; 6 | 7 | function loadPage(pageId) { 8 | if (!pageId) return; 9 | 10 | function CC(pageId) { 11 | 12 | this.pageId = pageId; 13 | 14 | this.requestData = {}; 15 | 16 | this.onNetworkResult = function (requestId, result, json) { 17 | let req = this.requestData[requestId]; 18 | if (req) { 19 | if (result === 'success') { 20 | req['success'](JSON.parse(json)); 21 | } else { 22 | req['fail'](JSON.parse(json)); 23 | } 24 | req['complete'](); 25 | } 26 | } 27 | } 28 | 29 | // __native__ 开头是内部方法,避免与外部冲突 30 | function RealPage(pageId) { 31 | 32 | this.observer = new Observer(); 33 | 34 | this.pageId = pageId; 35 | 36 | this.cc = new CC(pageId); 37 | 38 | // 需要加这一行赋值,不然在模板使用cc.调用不到 39 | let cc = this.cc; 40 | 41 | this.__native__evalInPage = function (jsContent) { 42 | if (!jsContent) { 43 | console.log("js content is empty!"); 44 | } 45 | eval(jsContent); 46 | }; 47 | 48 | this.__native__getExpValue = function (id, type, prefix, watch, script) { 49 | if (watch === true) { 50 | let watcher = new Watcher(id, type, prefix, script); 51 | this.observer.currentWatcher = watcher; 52 | this.observer.addWatcher(watcher); 53 | } 54 | let value = getExpValue(this.data, script); 55 | if (watch === true) { 56 | this.observer.currentWatcher = undefined; 57 | } 58 | return value; 59 | }; 60 | 61 | this.__native__initComplete = function () { 62 | this.observer.observe(this.data); 63 | }; 64 | 65 | this.setData = function (dataObj) { 66 | console.log("call setData"); 67 | for (let key in dataObj) { 68 | let str = "this.data." + key + " = dataObj['" + key + "']"; 69 | eval(str); 70 | } 71 | let startTime = Date.now(); 72 | let needUpdateMapping = this.observer.assembler.getNeedUpdateMapping(); 73 | let endTime = Date.now(); 74 | console.log("耗时:" + (endTime - startTime)); 75 | if (needUpdateMapping) { 76 | this.__native__refresh(needUpdateMapping); 77 | } 78 | }; 79 | 80 | this.__native__removeObserverByIds = function (ids) { 81 | this.observer.removeWatcher(ids); 82 | }; 83 | 84 | function setTimeout(callback, ms, ...args) { 85 | let timerId = guid(); 86 | callbacks[timerId] = callback; 87 | callbackArgs[timerId] = args; 88 | __native__setTimeout(pageId, timerId, ms); 89 | return timerId; 90 | } 91 | 92 | function clearTimeout(timerId) { 93 | let callback = callbacks[timerId]; 94 | if (callback) { 95 | callbacks[timerId] = undefined; 96 | callbackArgs[timerId] = undefined; 97 | } 98 | __native__clearTimeout(timerId); 99 | } 100 | 101 | function setInterval(callback, ms, ...args) { 102 | let timerId = guid(); 103 | callbacks[timerId] = callback; 104 | callbackArgs[timerId] = args; 105 | __native__setInterval(pageId, timerId, ms); 106 | return timerId; 107 | } 108 | 109 | function clearInterval(timerId) { 110 | let callback = callbacks[timerId]; 111 | if (callback) { 112 | callbacks[timerId] = undefined; 113 | callbackArgs[timerId] = undefined; 114 | } 115 | __native__clearInterval(timerId); 116 | } 117 | } 118 | 119 | let pageObj = new RealPage(pageId); 120 | cachePage(pageId, pageObj); 121 | } 122 | 123 | function cachePage(pageId, page) { 124 | if (page) { 125 | pages[pageId] = page; 126 | } else { 127 | console.log("page: (" + pageId + ") is empty"); 128 | } 129 | } 130 | 131 | function removePage(pageId) { 132 | pages[pageId] = undefined; 133 | } 134 | 135 | function callback(callbackId) { 136 | let callback = callbacks[callbackId]; 137 | if (callback) { 138 | let args = callbackArgs[callbackId]; 139 | callback(args); 140 | } else { 141 | console.log("callback: (" + callbackId + ") is empty"); 142 | } 143 | } 144 | 145 | global.getPage = function (pageId) { 146 | return pages[pageId]; 147 | }; 148 | 149 | global.Page = function (obj) { 150 | // 这里的page是个临时变量 151 | global.page = obj; 152 | }; 153 | 154 | global.loadPage = loadPage; 155 | global.callback = callback; 156 | global.removePage = removePage; 157 | -------------------------------------------------------------------------------- /HybridFlutter/android/app/src/main/kotlin/com/cc/hybrid/v8/V8PropertyMap.java: -------------------------------------------------------------------------------- 1 | package com.cc.hybrid.v8; 2 | 3 | /******************************************************************************* 4 | * Copyright (c) 2015 EclipseSource and others. 5 | * All rights reserved. This program and the accompanying materials 6 | * are made available under the terms of the Eclipse Public License v1.0 7 | * which accompanies this distribution, and is available at 8 | * http://www.eclipse.org/legal/epl-v10.html 9 | * 10 | * Contributors: 11 | * EclipseSource - initial API and implementation 12 | ******************************************************************************/ 13 | 14 | import java.util.AbstractMap.SimpleEntry; 15 | import java.util.ArrayList; 16 | import java.util.Collection; 17 | import java.util.HashSet; 18 | import java.util.Hashtable; 19 | import java.util.Map; 20 | import java.util.Set; 21 | 22 | /** 23 | * A custom map is needed because the existing HashMaps 24 | * do not self containment, and Hashtables do not 25 | * allow nulls as values. 26 | * 27 | * This class is not considered API. 28 | */ 29 | class V8PropertyMap implements Map { 30 | 31 | private Hashtable map = new Hashtable(); 32 | private Set nulls = new HashSet(); 33 | 34 | /* 35 | * (non-Javadoc) 36 | * @see java.util.Map#size() 37 | */ 38 | @Override 39 | public int size() { 40 | return map.size() + nulls.size(); 41 | } 42 | 43 | /* 44 | * (non-Javadoc) 45 | * @see java.util.Map#isEmpty() 46 | */ 47 | @Override 48 | public boolean isEmpty() { 49 | return map.isEmpty() && nulls.isEmpty(); 50 | } 51 | 52 | /* 53 | * (non-Javadoc) 54 | * @see java.util.Map#containsKey(java.lang.Object) 55 | */ 56 | @Override 57 | public boolean containsKey(final Object key) { 58 | return map.containsKey(key) || nulls.contains(key); 59 | } 60 | 61 | /* 62 | * (non-Javadoc) 63 | * @see java.util.Map#containsValue(java.lang.Object) 64 | */ 65 | @Override 66 | public boolean containsValue(final Object value) { 67 | if ((value == null) && !nulls.isEmpty()) { 68 | return true; 69 | } else if (value == null) { 70 | return false; 71 | } 72 | return map.containsValue(value); 73 | } 74 | 75 | /* 76 | * (non-Javadoc) 77 | * @see java.util.Map#get(java.lang.Object) 78 | */ 79 | @Override 80 | public V get(final Object key) { 81 | if (nulls.contains(key)) { 82 | return null; 83 | } 84 | return map.get(key); 85 | } 86 | 87 | /* 88 | * (non-Javadoc) 89 | * @see java.util.Map#put(java.lang.Object, java.lang.Object) 90 | */ 91 | @Override 92 | public V put(final String key, final V value) { 93 | if (value == null) { 94 | if (map.containsKey(key)) { 95 | map.remove(key); 96 | } 97 | nulls.add(key); 98 | return null; 99 | } 100 | if (nulls.contains(key)) { 101 | nulls.remove(key); 102 | } 103 | return map.put(key, value); 104 | } 105 | 106 | /* 107 | * (non-Javadoc) 108 | * @see java.util.Map#remove(java.lang.Object) 109 | */ 110 | @Override 111 | public V remove(final Object key) { 112 | if (nulls.contains(key)) { 113 | nulls.remove(key); 114 | return null; 115 | } 116 | return map.remove(key); 117 | } 118 | 119 | /* 120 | * (non-Javadoc) 121 | * @see java.util.Map#putAll(java.util.Map) 122 | */ 123 | @Override 124 | public void putAll(final Map m) { 125 | for (Entry entry : m.entrySet()) { 126 | this.put(entry.getKey(), entry.getValue()); 127 | } 128 | } 129 | 130 | /* 131 | * (non-Javadoc) 132 | * @see java.util.Map#clear() 133 | */ 134 | @Override 135 | public void clear() { 136 | map.clear(); 137 | nulls.clear(); 138 | } 139 | 140 | /* 141 | * (non-Javadoc) 142 | * @see java.util.Map#keySet() 143 | */ 144 | @Override 145 | public Set keySet() { 146 | HashSet result = new HashSet(map.keySet()); 147 | result.addAll(nulls); 148 | return result; 149 | } 150 | 151 | /* 152 | * (non-Javadoc) 153 | * @see java.util.Map#values() 154 | */ 155 | @Override 156 | public Collection values() { 157 | ArrayList result = new ArrayList(map.values()); 158 | for (int i = 0; i < nulls.size(); i++) { 159 | result.add(null); 160 | } 161 | return result; 162 | } 163 | 164 | /* 165 | * (non-Javadoc) 166 | * @see java.util.Map#entrySet() 167 | */ 168 | @Override 169 | public Set> entrySet() { 170 | HashSet> result = new HashSet>(map.entrySet()); 171 | for (String nullKey : nulls) { 172 | result.add(new SimpleEntry(nullKey, null)); 173 | } 174 | return result; 175 | } 176 | 177 | } 178 | -------------------------------------------------------------------------------- /webpack-framework/dist/framework.js: -------------------------------------------------------------------------------- 1 | !function(e){var t={};function a(r){if(t[r])return t[r].exports;var n=t[r]={i:r,l:!1,exports:{}};return e[r].call(n.exports,n,n.exports,a),n.l=!0,n.exports}a.m=e,a.c=t,a.d=function(e,t,r){a.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},a.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(e,t){if(1&t&&(e=a(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(a.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)a.d(r,n,function(t){return e[t]}.bind(null,n));return r},a.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return a.d(t,"a",t),t},a.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},a.p="",a(a.s=1)}([function(e,t){var a;a=function(){return this}();try{a=a||new Function("return this")()}catch(e){"object"==typeof window&&(a=window)}e.exports=a},function(e,t,a){(function(e){e.guid=function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(e){let t=16*Math.random()|0;return("x"===e?t:3&t|8).toString(16)})},e.judgeIsNotNull=function(e,t,a){return!!(e&&t&&a)},e.getExpValue=function(e,t){let a=(t=>new Function("","with(this){"+t+"}").bind(e)())(t);return a instanceof Object?JSON.stringify(a):a instanceof Array?JSON.stringify(a):a},a(2)}).call(this,a(0))},function(module,exports,__webpack_require__){(function(global){function loadPage(pageId){if(!pageId)return;function CC(e){this.pageId=e,this.requestData={},this.onNetworkResult=function(e,t,a){let r=this.requestData[e];r&&("success"===t?r.success(JSON.parse(a)):r.fail(JSON.parse(a)),r.complete())}}function RealPage(pageId){this.observer=new Observer,this.pageId=pageId,this.cc=new CC(pageId);let cc=this.cc;function setTimeout(e,t,...a){let r=guid();return callbacks[r]=e,callbackArgs[r]=a,__native__setTimeout(pageId,r,t),r}function clearTimeout(e){callbacks[e]&&(callbacks[e]=void 0,callbackArgs[e]=void 0),__native__clearTimeout(e)}function setInterval(e,t,...a){let r=guid();return callbacks[r]=e,callbackArgs[r]=a,__native__setInterval(pageId,r,t),r}function clearInterval(e){callbacks[e]&&(callbacks[e]=void 0,callbackArgs[e]=void 0),__native__clearInterval(e)}this.__native__evalInPage=function(jsContent){jsContent||console.log("js content is empty!"),eval(jsContent)},this.__native__getExpValue=function(e,t,a,r,n){if(!0===r){let r=new Watcher(e,t,a,n);this.observer.currentWatcher=r,this.observer.addWatcher(r)}let i=getExpValue(this.data,n);return!0===r&&(this.observer.currentWatcher=void 0),i},this.__native__initComplete=function(){this.observer.observe(this.data)},this.setData=function(dataObj){console.log("call setData");for(let key in dataObj){let str="this.data."+key+" = dataObj['"+key+"']";eval(str)}let startTime=Date.now(),needUpdateMapping=this.observer.assembler.getNeedUpdateMapping(),endTime=Date.now();console.log("耗时:"+(endTime-startTime)),needUpdateMapping&&this.__native__refresh(needUpdateMapping)},this.__native__removeObserverByIds=function(e){this.observer.removeWatcher(e)}}let pageObj=new RealPage(pageId);cachePage(pageId,pageObj)}function cachePage(e,t){t?pages[e]=t:console.log("page: ("+e+") is empty")}function removePage(e){pages[e]=void 0}function callback(e){let t=callbacks[e];if(t){t(callbackArgs[e])}else console.log("callback: ("+e+") is empty")}__webpack_require__(3),global.pages={},global.callbacks={},global.callbackArgs={},global.getPage=function(e){return pages[e]},global.Page=function(e){global.page=e},global.loadPage=loadPage,global.callback=callback,global.removePage=removePage}).call(this,__webpack_require__(0))},function(e,t,a){(function(e){class t{constructor(e){this.observer=e,this.watchers={}}addWatcher(e){this.watchers[e.key()]=e}removeWatcher(e){this.watchers[e]&&delete this.watchers[e]}notify(e){for(const t in this.watchers){let a=this.watchers[t];a.value=getExpValue(e,a.script),this.observer.assembler.addPackagingObject(a.format())}}}class a{constructor(){this.packagingArray=[]}addPackagingObject(e){this.packagingArray.push(e)}getNeedUpdateMapping(){let e=this.packing();return this.packagingArray=[],e}packing(){let e=JSON.stringify(this.packagingArray);return console.log("组装映射结果:"+e),e}}e.Observer=class{constructor(){this.currentWatcher=void 0,this.collectors=[],this.watchers={},this.assembler=new a}observe(e){if(e&&void 0!==e&&"object"==typeof e)for(const t in e){let a=e[t];void 0!==a&&this.defineReactive(e,t,a)}}defineReactive(e,a,r){const n=Object.getOwnPropertyDescriptor(e,a);if(n&&!1===n.configurable)return;const i=n&&n.get,c=n&&n.set;i&&!c||2!==arguments.length||(r=e[a]);let s=this,o=new t(s);this.collectors.push(o),Object.defineProperty(e,a,{enumerable:!0,configurable:!0,get:function(){const t=i?i.call(e):r;return s.currentWatcher&&o.addWatcher(s.currentWatcher),t},set:function(t){const a=i?i.call(e):r;t===a||t!=t&&a!=a||(c?c.call(e,t):r=t,o.notify(e))}})}addWatcher(e){void 0===this.watchers[e.id]&&(this.watchers[e.id]=[]),this.watchers[e.id].push(e)}removeWatcher(e){if(e){let t=[];e.forEach(e=>{this.watchers[e]&&(this.watchers[e].forEach(e=>{t.push(e.key())}),this.watchers[e]=void 0)}),this.collectors&&this.collectors.forEach(e=>{t.forEach(t=>{e.removeWatcher(t)})})}}},e.Watcher=class{constructor(e,t,a,r){this.id=e,this.type=t,this.script=r,this.prefix=a,this.value={}}format(){let e={};return e.id=this.id,e.type=this.type,e.key=this.prefix,e.value=this.value,e}key(){return this.id+"-"+this.type+"-"+this.script}}}).call(this,a(0))}]); -------------------------------------------------------------------------------- /HybridFlutter/android/app/src/main/assets/framework.js: -------------------------------------------------------------------------------- 1 | !function(e){var t={};function a(r){if(t[r])return t[r].exports;var n=t[r]={i:r,l:!1,exports:{}};return e[r].call(n.exports,n,n.exports,a),n.l=!0,n.exports}a.m=e,a.c=t,a.d=function(e,t,r){a.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},a.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(e,t){if(1&t&&(e=a(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(a.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)a.d(r,n,function(t){return e[t]}.bind(null,n));return r},a.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return a.d(t,"a",t),t},a.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},a.p="",a(a.s=1)}([function(e,t){var a;a=function(){return this}();try{a=a||new Function("return this")()}catch(e){"object"==typeof window&&(a=window)}e.exports=a},function(e,t,a){(function(e){e.guid=function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(e){let t=16*Math.random()|0;return("x"===e?t:3&t|8).toString(16)})},e.judgeIsNotNull=function(e,t,a){return!!(e&&t&&a)},e.getExpValue=function(e,t){let a=(t=>new Function("","with(this){"+t+"}").bind(e)())(t);return a instanceof Object?JSON.stringify(a):a instanceof Array?JSON.stringify(a):a},a(2)}).call(this,a(0))},function(module,exports,__webpack_require__){(function(global){function loadPage(pageId){if(!pageId)return;function CC(e){this.pageId=e,this.requestData={},this.onNetworkResult=function(e,t,a){let r=this.requestData[e];r&&("success"===t?r.success(JSON.parse(a)):r.fail(JSON.parse(a)),r.complete())}}function RealPage(pageId){this.observer=new Observer,this.pageId=pageId,this.cc=new CC(pageId);let cc=this.cc;function setTimeout(e,t,...a){let r=guid();return callbacks[r]=e,callbackArgs[r]=a,__native__setTimeout(pageId,r,t),r}function clearTimeout(e){callbacks[e]&&(callbacks[e]=void 0,callbackArgs[e]=void 0),__native__clearTimeout(e)}function setInterval(e,t,...a){let r=guid();return callbacks[r]=e,callbackArgs[r]=a,__native__setInterval(pageId,r,t),r}function clearInterval(e){callbacks[e]&&(callbacks[e]=void 0,callbackArgs[e]=void 0),__native__clearInterval(e)}this.__native__evalInPage=function(jsContent){jsContent||console.log("js content is empty!"),eval(jsContent)},this.__native__getExpValue=function(e,t,a,r,n){if(!0===r){let r=new Watcher(e,t,a,n);this.observer.currentWatcher=r,this.observer.addWatcher(r)}let i=getExpValue(this.data,n);return!0===r&&(this.observer.currentWatcher=void 0),i},this.__native__initComplete=function(){this.observer.observe(this.data)},this.setData=function(dataObj){console.log("call setData");for(let key in dataObj){let str="this.data."+key+" = dataObj['"+key+"']";eval(str)}let startTime=Date.now(),needUpdateMapping=this.observer.assembler.getNeedUpdateMapping(),endTime=Date.now();console.log("耗时:"+(endTime-startTime)),needUpdateMapping&&this.__native__refresh(needUpdateMapping)},this.__native__removeObserverByIds=function(e){this.observer.removeWatcher(e)}}let pageObj=new RealPage(pageId);cachePage(pageId,pageObj)}function cachePage(e,t){t?pages[e]=t:console.log("page: ("+e+") is empty")}function removePage(e){pages[e]=void 0}function callback(e){let t=callbacks[e];if(t){t(callbackArgs[e])}else console.log("callback: ("+e+") is empty")}__webpack_require__(3),global.pages={},global.callbacks={},global.callbackArgs={},global.getPage=function(e){return pages[e]},global.Page=function(e){global.page=e},global.loadPage=loadPage,global.callback=callback,global.removePage=removePage}).call(this,__webpack_require__(0))},function(e,t,a){(function(e){class t{constructor(e){this.observer=e,this.watchers={}}addWatcher(e){this.watchers[e.key()]=e}removeWatcher(e){this.watchers[e]&&delete this.watchers[e]}notify(e){for(const t in this.watchers){let a=this.watchers[t];a.value=getExpValue(e,a.script),this.observer.assembler.addPackagingObject(a.format())}}}class a{constructor(){this.packagingArray=[]}addPackagingObject(e){this.packagingArray.push(e)}getNeedUpdateMapping(){let e=this.packing();return this.packagingArray=[],e}packing(){let e=JSON.stringify(this.packagingArray);return console.log("组装映射结果:"+e),e}}e.Observer=class{constructor(){this.currentWatcher=void 0,this.collectors=[],this.watchers={},this.assembler=new a}observe(e){if(e&&void 0!==e&&"object"==typeof e)for(const t in e){let a=e[t];void 0!==a&&this.defineReactive(e,t,a)}}defineReactive(e,a,r){const n=Object.getOwnPropertyDescriptor(e,a);if(n&&!1===n.configurable)return;const i=n&&n.get,c=n&&n.set;i&&!c||2!==arguments.length||(r=e[a]);let s=this,o=new t(s);this.collectors.push(o),Object.defineProperty(e,a,{enumerable:!0,configurable:!0,get:function(){const t=i?i.call(e):r;return s.currentWatcher&&o.addWatcher(s.currentWatcher),t},set:function(t){const a=i?i.call(e):r;t===a||t!=t&&a!=a||(c?c.call(e,t):r=t,o.notify(e))}})}addWatcher(e){void 0===this.watchers[e.id]&&(this.watchers[e.id]=[]),this.watchers[e.id].push(e)}removeWatcher(e){if(e){let t=[];e.forEach(e=>{this.watchers[e]&&(this.watchers[e].forEach(e=>{t.push(e.key())}),this.watchers[e]=void 0)}),this.collectors&&this.collectors.forEach(e=>{t.forEach(t=>{e.removeWatcher(t)})})}}},e.Watcher=class{constructor(e,t,a,r){this.id=e,this.type=t,this.script=r,this.prefix=a,this.value={}}format(){let e={};return e.id=this.id,e.type=this.type,e.key=this.prefix,e.value=this.value,e}key(){return this.id+"-"+this.type+"-"+this.script}}}).call(this,a(0))}]); -------------------------------------------------------------------------------- /HybridFlutter/android/app/src/main/kotlin/com/cc/hybrid/bridge/flutter/FlutterPluginMethodChannel.kt: -------------------------------------------------------------------------------- 1 | package com.cc.hybrid.bridge.flutter 2 | 3 | import com.cc.hybrid.Logger 4 | import com.cc.hybrid.bridge.js.JSPageManager 5 | import com.cc.hybrid.util.TimerManager 6 | import io.flutter.plugin.common.MethodCall 7 | import io.flutter.plugin.common.MethodChannel 8 | 9 | object FlutterPluginMethodChannel { 10 | 11 | const val CHANNEL = "com.cc.hybrid/method" 12 | 13 | fun onMethodCall(methodCall: MethodCall, result: MethodChannel.Result) { 14 | when (methodCall.method) { 15 | Methods.ATTACH_PAGE -> { 16 | if (methodCall.hasArgument("pageId") && methodCall.hasArgument("script")) { 17 | val pageId = methodCall.argument("pageId") 18 | val script = methodCall.argument("script") 19 | JSPageManager.attachPageScriptToJsCore(pageId!!, script!!) 20 | result.success("success") 21 | } 22 | } 23 | Methods.ON_LOAD -> { 24 | if (methodCall.hasArgument("pageId")) { 25 | val pageId = methodCall.argument("pageId") 26 | val args = methodCall.argument("args") 27 | JSPageManager.callMethodInPage(pageId!!, Methods.ON_LOAD, args) 28 | result.success("success") 29 | } 30 | } 31 | Methods.ON_UNLOAD -> { 32 | if (methodCall.hasArgument("pageId")) { 33 | val pageId = methodCall.argument("pageId") 34 | TimerManager.delTimerByPageId(pageId!!) 35 | JSPageManager.callMethodInPage(pageId, Methods.ON_UNLOAD) 36 | JSPageManager.removePage(pageId) 37 | result.success("success") 38 | } 39 | } 40 | Methods.EVENT -> { 41 | if (methodCall.hasArgument("pageId") && methodCall.hasArgument("event") && methodCall.hasArgument("data")) { 42 | val pageId = methodCall.argument("pageId") 43 | val event = methodCall.argument("event") 44 | val data = methodCall.argument("data") 45 | JSPageManager.callMethodInPage(pageId!!, event!!, data) 46 | result.success("success") 47 | } 48 | } 49 | Methods.ON_PULL_DOWN_REFRESH -> { 50 | if (methodCall.hasArgument("pageId")) { 51 | val pageId = methodCall.argument("pageId") 52 | JSPageManager.callMethodInPage(pageId!!, Methods.ON_PULL_DOWN_REFRESH, null) 53 | result.success("success") 54 | } 55 | } 56 | //以下是__native__内部函数回调 57 | Methods.INIT_COMPLETE -> { 58 | if (methodCall.hasArgument("pageId")) { 59 | val pageId = methodCall.argument("pageId") 60 | JSPageManager.onInitComplete(pageId!!) 61 | result.success("success") 62 | } 63 | } 64 | Methods.HANDLE_EXPRESSION -> { 65 | if (methodCall.hasArgument("pageId") && methodCall.hasArgument("id") && methodCall.hasArgument("type") && methodCall.hasArgument("key") && methodCall.hasArgument("expression")) { 66 | val pageId = methodCall.argument("pageId") 67 | val id = methodCall.argument("id") 68 | val type = methodCall.argument("type") 69 | val key = methodCall.argument("key") 70 | val watch = methodCall.argument("watch") 71 | val expression = methodCall.argument("expression") 72 | val obj = JSPageManager.handleExpression(pageId!!, id!!, type!!, key!!, watch!!, expression!!) 73 | result.success(obj) 74 | } 75 | } 76 | Methods.HANDLE_REPEAT -> { 77 | if (methodCall.hasArgument("pageId") && methodCall.hasArgument("id") && methodCall.hasArgument("type") && methodCall.hasArgument("key") && methodCall.hasArgument("expression")) { 78 | val pageId = methodCall.argument("pageId") 79 | val id = methodCall.argument("id") 80 | val type = methodCall.argument("type") 81 | val key = methodCall.argument("key") 82 | val watch = methodCall.argument("watch") 83 | val expression = methodCall.argument("expression") 84 | val obj = JSPageManager.handleRepeat(pageId!!, id!!, type!!, key!!, watch!!, expression!!) 85 | result.success(obj) 86 | } 87 | } 88 | Methods.REMOVE_OBSERVER -> { 89 | if (methodCall.hasArgument("pageId") && methodCall.hasArgument("ids")) { 90 | val pageId = methodCall.argument("pageId") 91 | val ids = methodCall.argument>("ids") 92 | try { 93 | JSPageManager.removeObserver(pageId!!, ids!!) 94 | } catch (e: Exception) { 95 | Logger.printError(e) 96 | } 97 | result.success("success") 98 | } 99 | } 100 | } 101 | } 102 | } -------------------------------------------------------------------------------- /HybridFlutter/lib/entity/component.dart: -------------------------------------------------------------------------------- 1 | import 'package:hybrid_flutter/entity/property.dart'; 2 | import 'package:hybrid_flutter/util/base64.dart'; 3 | import 'package:hybrid_flutter/util/expression_util.dart'; 4 | 5 | class Component { 6 | /// 唯一id 7 | String id; 8 | 9 | /// 标签类型 10 | String tag; 11 | 12 | /// 父节点 13 | Component parent; 14 | 15 | /// 节点 16 | Map node; 17 | 18 | /// 样式 19 | Map styles; 20 | 21 | /// 事件 22 | Map events; 23 | 24 | /// 指令 25 | Map directives; 26 | 27 | /// 属性 28 | Map properties; 29 | 30 | /// 子节点 31 | List children = []; 32 | 33 | /// 是否在for里面 34 | bool isInRepeat = false; 35 | 36 | /// 在for里面的下标 37 | int inRepeatIndex; 38 | 39 | /// 在for里面的表达式前缀 40 | String inRepeatPrefixExp; 41 | 42 | /// 克隆节点id, inRepeatIndex, inRepeatPrefixExp不为空 43 | Component(this.parent, this.node, this.styles, 44 | {id, inRepeatIndex, inRepeatPrefixExp}) { 45 | this.tag = node["tag"]; 46 | this.id = id ?? "$tag-$hashCode"; 47 | this.properties = _initProperties(node, styles); 48 | this.directives = _initDirectives(node); 49 | this.events = _initEvents(node); 50 | this.isInRepeat = _isInRepeat(); 51 | this.inRepeatIndex = inRepeatIndex ?? parent?.inRepeatIndex; 52 | this.inRepeatPrefixExp = inRepeatPrefixExp ?? parent?.inRepeatPrefixExp; 53 | this.parent = parent; 54 | } 55 | 56 | bool _isInRepeat() { 57 | if (null != getForExpression()) { 58 | return true; 59 | } else { 60 | return null == parent ? false : parent?.isInRepeat; 61 | } 62 | } 63 | 64 | void insertChildren(int index, List children) { 65 | this.children.insertAll(index, children); 66 | } 67 | 68 | void removeRangeChildren(int start, int end) { 69 | this.children.removeRange(start, end); 70 | } 71 | 72 | Map _initProperties( 73 | Map node, Map styles) { 74 | if (null == node) { 75 | return null; 76 | } 77 | Map properties = new Map(); 78 | if (null != node['id'] && node['id'] != '') { 79 | Map idStyles = styles['.' + node['id']]; 80 | if (idStyles != null) { 81 | idStyles.forEach((k, v) { 82 | properties.putIfAbsent(k, () => Property(v)); 83 | }); 84 | } 85 | } 86 | var attr = node['attrib']; 87 | if (null != attr) { 88 | attr.forEach((k, v) { 89 | properties.putIfAbsent(k, () => Property(v)); 90 | }); 91 | } 92 | var attrStyle = node['attribStyle']; 93 | if (null != attrStyle) { 94 | attrStyle.forEach((k, v) { 95 | properties.putIfAbsent(k, () => Property(v)); 96 | }); 97 | } 98 | 99 | if (null != node["innerHTML"]) { 100 | properties.putIfAbsent( 101 | "innerHTML", () => Property(decodeBase64(node["innerHTML"]))); 102 | } 103 | return properties; 104 | } 105 | 106 | Map _initEvents(Map node) { 107 | if (null == node) { 108 | return null; 109 | } 110 | return node['events']; 111 | } 112 | 113 | Map _initDirectives(Map node) { 114 | if (null == node) { 115 | return null; 116 | } 117 | return node['directives']; 118 | } 119 | 120 | String getIfExpression() { 121 | if (null == directives) { 122 | return null; 123 | } 124 | var shown = directives["shown"]; 125 | if (null == shown) { 126 | return null; 127 | } 128 | var directiveName = shown["name"]; 129 | if (directiveName == "if") { 130 | return shown["expression"]; 131 | } 132 | return null; 133 | } 134 | 135 | String getElseIfExpression() { 136 | if (null == directives) { 137 | return null; 138 | } 139 | var shown = directives["shown"]; 140 | if (null == shown) { 141 | return null; 142 | } 143 | var directiveName = shown["name"]; 144 | if (directiveName == "elif") { 145 | return shown["expression"]; 146 | } 147 | return null; 148 | } 149 | 150 | bool containElseExpression() { 151 | if (null == directives) { 152 | return false; 153 | } 154 | var shown = directives["shown"]; 155 | if (null == shown) { 156 | return false; 157 | } 158 | var directiveName = shown["name"]; 159 | if (directiveName == "else") { 160 | return true; 161 | } 162 | return false; 163 | } 164 | 165 | void handleShown() { 166 | if (null == directives) { 167 | return; 168 | } 169 | var shown = directives["shown"]; 170 | if (null == shown) { 171 | return; 172 | } 173 | } 174 | 175 | String getForExpression() { 176 | if (null == directives) { 177 | return null; 178 | } 179 | var repeat = directives["repeat"]; 180 | if (null == repeat) { 181 | return null; 182 | } 183 | return repeat["expression"]; 184 | } 185 | 186 | String getForIndexName() { 187 | if (null == directives) { 188 | return null; 189 | } 190 | var repeat = directives["repeat"]; 191 | if (null == repeat) { 192 | return null; 193 | } 194 | return repeat["index"]; 195 | } 196 | 197 | String getForItemName() { 198 | if (null == directives) { 199 | return null; 200 | } 201 | var repeat = directives["repeat"]; 202 | if (null == repeat) { 203 | return null; 204 | } 205 | return repeat["item"]; 206 | } 207 | 208 | bool containRepeat() { 209 | if (null == directives) { 210 | return false; 211 | } 212 | return directives.containsKey("repeat"); 213 | } 214 | 215 | String getRealForExpression() { 216 | if (null == directives) { 217 | return null; 218 | } 219 | var repeat = directives["repeat"]; 220 | if (null == repeat) { 221 | return null; 222 | } 223 | return getExpression(repeat["expression"]); 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /webpack-framework/src/observer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 观察者,用于观察data对象属性变化 3 | * @param data 4 | * @constructor 5 | */ 6 | class Observer { 7 | 8 | constructor() { 9 | this.currentWatcher = undefined; 10 | this.collectors = []; 11 | this.watchers = {}; 12 | this.assembler = new Assembler(); 13 | } 14 | 15 | /** 16 | * 将data的属性变成可响应对象,为了监听变化回调 17 | * @param data 18 | */ 19 | observe(data) { 20 | if (!data || data === undefined || typeof (data) !== "object") { 21 | return; 22 | } 23 | for (const key in data) { 24 | let value = data[key]; 25 | if (value === undefined) { 26 | continue; 27 | } 28 | // console.log("key = " + key + " value = " + value); 29 | this.defineReactive(data, key, value); 30 | } 31 | } 32 | 33 | defineReactive(data, key, val) { 34 | const property = Object.getOwnPropertyDescriptor(data, key); 35 | if (property && property.configurable === false) { 36 | return 37 | } 38 | const getter = property && property.get; 39 | const setter = property && property.set; 40 | if ((!getter || setter) && arguments.length === 2) { 41 | val = data[key]; 42 | } 43 | 44 | let that = this; 45 | let collector = new WatcherCollector(that); 46 | this.collectors.push(collector); 47 | 48 | Object.defineProperty(data, key, { 49 | enumerable: true, 50 | configurable: true, 51 | get: function reactiveGetter() { 52 | const value = getter ? getter.call(data) : val; 53 | // 在这里将data的数据与对应的watcher进行关联 54 | if (that.currentWatcher) { 55 | collector.addWatcher(that.currentWatcher); 56 | } 57 | return value; 58 | }, 59 | set: function reactiveSetter(newVal) { 60 | const value = getter ? getter.call(data) : val; 61 | if (newVal === value || (newVal !== newVal && value !== value)) { 62 | return; 63 | } 64 | if (setter) { 65 | setter.call(data, newVal); 66 | } else { 67 | val = newVal; 68 | } 69 | collector.notify(data); 70 | } 71 | }); 72 | } 73 | 74 | addWatcher(watcher) { 75 | if (this.watchers[watcher.id] === undefined) { 76 | this.watchers[watcher.id] = []; 77 | } 78 | this.watchers[watcher.id].push(watcher); 79 | } 80 | 81 | removeWatcher(ids) { 82 | if (ids) { 83 | let keys = []; 84 | ids.forEach((id) => { 85 | if (this.watchers[id]) { 86 | this.watchers[id].forEach((watcher) => { 87 | keys.push(watcher.key()); 88 | }); 89 | this.watchers[id] = undefined; 90 | } 91 | }); 92 | if (this.collectors) { 93 | this.collectors.forEach((collector) => { 94 | keys.forEach((key) => { 95 | collector.removeWatcher(key) 96 | }); 97 | }); 98 | } 99 | } 100 | } 101 | } 102 | 103 | 104 | /** 105 | * watcher收集器,收集订阅的容器,用于增减观察者队列中的观察者,并发布更新通知 106 | * @constructor 107 | */ 108 | class WatcherCollector { 109 | 110 | constructor(observer) { 111 | this.observer = observer; 112 | this.watchers = {}; 113 | } 114 | 115 | /** 116 | * 将当前的Watcher与对应的Data变量关联起来 117 | */ 118 | addWatcher(watcher) { 119 | // console.log("watcher key = " + watcher.key()); 120 | this.watchers[watcher.key()] = watcher; 121 | } 122 | 123 | removeWatcher(key) { 124 | if (this.watchers[key]) { 125 | // console.log("delete sub key = " + key); 126 | delete this.watchers[key]; 127 | } 128 | } 129 | 130 | /** 131 | * 通知所有订阅者,同时把当前Dep持有的所有订阅者的映射数组(id-表达式)添加到组装者中,等待组装 132 | */ 133 | notify(data) { 134 | for (const _k in this.watchers) { 135 | let watcher = this.watchers[_k]; 136 | watcher.value = getExpValue(data, watcher.script); 137 | this.observer.assembler.addPackagingObject(watcher.format()); 138 | } 139 | } 140 | } 141 | 142 | /** 143 | * 订阅者,用于响应观察者的变化 144 | * @constructor 145 | */ 146 | class Watcher { 147 | 148 | constructor(id, type, prefix, script) { 149 | this.id = id; 150 | this.type = type; 151 | this.script = script; 152 | this.prefix = prefix; 153 | this.value = {}; 154 | } 155 | 156 | format() { 157 | let obj = {}; 158 | obj.id = this.id; 159 | obj.type = this.type; 160 | // obj.script = this.script; 161 | obj.key = this.prefix; 162 | obj.value = this.value; 163 | return obj; 164 | } 165 | 166 | key() { 167 | return this.id + '-' + this.type + '-' + this.script; 168 | } 169 | 170 | } 171 | 172 | /** 173 | * 组装者,用于合并组装 id-属性 映射的结果,回传给原生做表达式计算和局部刷新 174 | * 因为表达式计算是各自独立的,所以 id-属性 映射散乱在各个Watcher中,需要在Dep层收集起来,在组装者中打平多余的层级 175 | * @constructor 176 | */ 177 | class Assembler { 178 | 179 | constructor() { 180 | this.packagingArray = []; 181 | } 182 | 183 | addPackagingObject(item) { 184 | this.packagingArray.push(item); 185 | } 186 | 187 | getNeedUpdateMapping() { 188 | let result = this.packing(); 189 | this.packagingArray = []; 190 | return result; 191 | } 192 | 193 | /** 194 | * 组装映射关系 195 | * @returns [] 组装结果 196 | */ 197 | packing() { 198 | let result = JSON.stringify(this.packagingArray); 199 | console.log("组装映射结果:" + result); 200 | return result; 201 | } 202 | } 203 | 204 | 205 | global.Observer = Observer; 206 | global.Watcher = Watcher; 207 | 208 | -------------------------------------------------------------------------------- /cc-cli/src/home.json: -------------------------------------------------------------------------------- 1 | {"style":{".btn-container":{"margin-top":"10","margin-left":"10","margin-right":"10"},".raised-button":{"color":"white"},".image-container":{"width":"100px","height":"100px","padding":"5"},".column-text":{"cross-axis-alignment":"start"},".text-title":{"font-size":"14px","color":"black"},".text-publisher":{"font-size":"12px","color":"gray"},".text-summary":{"font-size":"12px","color":"gray"}},"body":{"tag":"body","innerHTML":"","childNodes":[{"tag":"singlechildscrollview","innerHTML":"","childNodes":[{"tag":"column","innerHTML":"","childNodes":[{"tag":"container","innerHTML":"","childNodes":[{"tag":"raisedbutton","innerHTML":"","childNodes":[{"tag":"row","innerHTML":"","childNodes":[{"tag":"container","innerHTML":"","childNodes":[{"tag":"image","innerHTML":"","childNodes":[],"datasets":{},"events":{},"directives":{},"attribStyle":{"src":"{{item.image}}"},"attrib":{}}],"datasets":{},"events":{},"directives":{},"attribStyle":{},"attrib":{},"id":"image-container"},{"tag":"expanded","innerHTML":"","childNodes":[{"tag":"column","innerHTML":"","childNodes":[{"tag":"text","innerHTML":"e3tpdGVtLnRpdGxlfX0=","childNodes":[],"datasets":{},"events":{},"directives":{},"attribStyle":{},"attrib":{},"id":"text-title"},{"tag":"text","innerHTML":"e3tpdGVtLnB1Ymxpc2hlcn19","childNodes":[],"datasets":{},"events":{},"directives":{},"attribStyle":{},"attrib":{},"id":"text-publisher"},{"tag":"text","innerHTML":"e3tpdGVtLnN1bW1hcnkuc3Vic3RyaW5nKDAsIDIwKSArICcuLi4nfX0=","childNodes":[],"datasets":{},"events":{},"directives":{},"attribStyle":{},"attrib":{},"id":"text-summary"}],"datasets":{},"events":{},"directives":{},"attribStyle":{},"attrib":{},"id":"column-text"}],"datasets":{},"events":{},"directives":{},"attribStyle":{},"attrib":{}}],"datasets":{},"events":{},"directives":{},"attribStyle":{},"attrib":{}}],"datasets":{"ex":"{{index}}"},"events":{"bindtap":"onItemClick"},"directives":{},"attribStyle":{},"attrib":{"data-index":"{{index}}"},"id":"raised-button"}],"datasets":{},"events":{},"directives":{"repeat":{"name":"or","expression":"{{list}}","item":"item","index":"index"}},"attribStyle":{},"attrib":{},"id":"btn-container"}],"datasets":{},"events":{},"directives":{},"attribStyle":{},"attrib":{}}],"datasets":{},"events":{},"directives":{},"attribStyle":{},"attrib":{}}],"datasets":{},"events":{},"directives":{},"attribStyle":{},"attrib":{}},"script":"IWZ1bmN0aW9uKHQpe3ZhciBuPXt9O2Z1bmN0aW9uIGUobyl7aWYobltvXSlyZXR1cm4gbltvXS5leHBvcnRzO3ZhciByPW5bb109e2k6byxsOiExLGV4cG9ydHM6e319O3JldHVybiB0W29dLmNhbGwoci5leHBvcnRzLHIsci5leHBvcnRzLGUpLHIubD0hMCxyLmV4cG9ydHN9ZS5tPXQsZS5jPW4sZS5kPWZ1bmN0aW9uKHQsbixvKXtlLm8odCxuKXx8T2JqZWN0LmRlZmluZVByb3BlcnR5KHQsbix7ZW51bWVyYWJsZTohMCxnZXQ6b30pfSxlLnI9ZnVuY3Rpb24odCl7InVuZGVmaW5lZCIhPXR5cGVvZiBTeW1ib2wmJlN5bWJvbC50b1N0cmluZ1RhZyYmT2JqZWN0LmRlZmluZVByb3BlcnR5KHQsU3ltYm9sLnRvU3RyaW5nVGFnLHt2YWx1ZToiTW9kdWxlIn0pLE9iamVjdC5kZWZpbmVQcm9wZXJ0eSh0LCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KX0sZS50PWZ1bmN0aW9uKHQsbil7aWYoMSZuJiYodD1lKHQpKSw4Jm4pcmV0dXJuIHQ7aWYoNCZuJiYib2JqZWN0Ij09dHlwZW9mIHQmJnQmJnQuX19lc01vZHVsZSlyZXR1cm4gdDt2YXIgbz1PYmplY3QuY3JlYXRlKG51bGwpO2lmKGUucihvKSxPYmplY3QuZGVmaW5lUHJvcGVydHkobywiZGVmYXVsdCIse2VudW1lcmFibGU6ITAsdmFsdWU6dH0pLDImbiYmInN0cmluZyIhPXR5cGVvZiB0KWZvcih2YXIgciBpbiB0KWUuZChvLHIsZnVuY3Rpb24obil7cmV0dXJuIHRbbl19LmJpbmQobnVsbCxyKSk7cmV0dXJuIG99LGUubj1mdW5jdGlvbih0KXt2YXIgbj10JiZ0Ll9fZXNNb2R1bGU/ZnVuY3Rpb24oKXtyZXR1cm4gdC5kZWZhdWx0fTpmdW5jdGlvbigpe3JldHVybiB0fTtyZXR1cm4gZS5kKG4sImEiLG4pLG59LGUubz1mdW5jdGlvbih0LG4pe3JldHVybiBPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwodCxuKX0sZS5wPSIiLGUoZS5zPTEpfShbLGZ1bmN0aW9uKHQsbil7UGFnZSh7ZGF0YTp7bGlzdDpbXX0sb25Mb2FkKHQpe2NjLnNldE5hdmlnYXRpb25CYXJUaXRsZSh7dGl0bGU6IlB5dGhvbuezu+WIl+S4m+S5piJ9KSxjYy5zaG93TG9hZGluZyh7fSksdGhpcy5kb1JlcXVlc3QoITApfSxkb1JlcXVlc3QodCl7Y29uc3Qgbj17dGl0bGU6IlB5dGhvbue8lueoiyIsaW1hZ2U6Imh0dHBzOi8vaW1nMS5kb3ViYW5pby5jb20vdmlldy9zdWJqZWN0L3MvcHVibGljL3MzMzcxNjI3OC5qcGciLGF1dGhvcjoiW+e+jl3ln4Pph4zlhYvCt+mprOeRn+aWr++8iEVyaWMgTWF0dGhlc++8iSIscHVibGlzaGVyOiLkurrmsJHpgq7nlLXlh7rniYjnpL4iLHN1YnRpdGxlOiLku47lhaXpl6jliLDlrp7ot7XvvIjnrKwy54mI77yJIixzdW1tYXJ5OiLmnKzkuabmmK/pkojlr7nmiYDmnInlsYLmrKFQeXRob27or7vogIXogIzkvZznmoRQeXRob27lhaXpl6jkuabjgILlhajkuabliIbkuKTpg6jliIbvvJrnrKzkuIDpg6jliIbku4vnu43nlKhQeXRob27nvJbnqIvmiYDlv4Xpobvkuobop6PnmoTln7rmnKzmpoLlv7XvvIzljIXmi6xNYXRwbG90bGli562J5by65aSn55qEUHl0aG9u5bqT5ZKM5bel5YW377yM5Lul5Y+K5YiX6KGo44CB5a2X5YW444CBaWbor63lj6XjgIHnsbvjgIHmlofku7bkuI7lvILluLjjgIHku6PnoIHmtYvor5XnrYnlhoXlrrnvvJvnrKzkuozpg6jliIblsIbnkIborrrku5jor7jlrp7ot7XvvIzorrLop6PlpoLkvZXlvIDlj5HkuInkuKrpobnnm67vvIzljIXmi6znroDljZXnmoQyROa4uOaIj+OAgeWIqeeUqOaVsOaNrueUn+aIkOS6pOS6kuW8j+eahOS/oeaBr+WbvuS7peWPiuWIm+W7uuWSjOWumuWItueugOWNleeahFdlYuW6lOeUqO+8jOW5tuW4ruWKqeivu+iAheino+WGs+W4uOingee8lueoi+mXrumimOWSjOWbsOaDkeOAguesrDLniYjov5vooYzkuoblhajpnaLkv67orqLvvIznroDljJbkuoZQeXRob27lronoo4XmtYHnqIvvvIzmlrDlop7kuoZm5a2X56ym5Liy44CBZ2V0KCnmlrnms5XnrYnlhoXlrrnvvIzlubbkuJTlnKjpobnnm67kuK3kvb/nlKjkuoZQbG90bHnlupPku6Xlj4rmlrDniYjmnKznmoREamFuZ2/lkoxCb290c3RyYXDvvIznrYnnrYnjgIIiLGF1dGhvcl9pbnRybzoi5Z+D6YeM5YWLwrfpqaznkZ/mlq/vvIhFcmljIE1hdHRoZXPvvIlcblxu6auY5Lit56eR5a2m5ZKM5pWw5a2m6ICB5biI77yM546w5bGF5L2P5Zyo6Zi/5ouJ5pav5Yqg77yM5Zyo5b2T5Zyw6K6y5o6IUHl0aG9u5YWl6Zeo6K++56iL44CC5LuW5LuONeWygeW8gOWni+WwseS4gOebtOWcqOe8luWGmeeoi+W6j+OAgiIsY2F0YWxvZzoi56ys5LiA6YOo5YiGIOWfuuehgOefpeivhlxu56ysMeeroCDotbfmraXjgIDjgIAyXG4xLjEg5pCt5bu657yW56iL546v5aKD44CA44CAMlxuMS4xLjEgUHl0aG9u54mI5pys44CA44CAMlxuMS4xLjIg6L+Q6KGMUHl0aG9u5Luj56CB54mH5q6144CA44CAMlxuMS4xLjMgU3VibGltZSBUZXh0566A5LuL44CA44CAM1xuMS4yIOWcqOS4jeWQjOaTjeS9nOezu+e7n+S4reaQreW7ulB5dGhvbue8lueoi+eOr+Wig+OAgOOAgDNcbjEuMi4xIOWcqFdpbmRvd3Pns7vnu5/kuK3mkK3lu7pQeXRob27nvJbnqIvnjq/looPjgIDjgIA0XG4xLjIuMiDlnKhtYWNPU+ezu+e7n+S4reaQreW7ulB5dGhvbue8lueoi+eOr+Wig+OAgOOAgDVcbjEuMi4zIOWcqExpbnV4IOezu+e7n+S4reaQreW7ulB5dGhvbue8lueoi+eOr+Wig+OAgOOAgDdcbjEuMyDov5DooYxIZWxsbyBXb3JsZCDnqIvluo/jgIDjgIA4XG4xLjMuMSDphY3nva5TdWJsaW1lIFRleHTku6Xkvb/nlKjmraPnoa7nmoRQeXRob27niYjmnKzjgIDjgIA4XG4xLjMuMiDov5DooYznqIvluo9oZWxsb193b3JsZC5weeOAgOOAgDhcbjEuNCDop6PlhrPlronoo4Xpl67popjjgIDjgIA5XG4xLjUg5LuO57uI56uv6L+Q6KGMUHl0aG9u56iL5bqP44CA44CAOVxuMS41LjEg5ZyoV2luZG93c+ezu+e7n+S4reS7jue7iOerr+i/kOihjFB5dGhvbiDnqIvluo/jgIDjgIAxMFxuMS41LjIg5ZyoTGludXjlkoxtYWNPU+ezu+e7n+S4reS7jue7iOerr+i/kOihjFB5dGhvbueoi+W6j+OAgOOAgDEwXG4ifTt0aGlzLnNldERhdGEoe2xpc3Q6W24sbixuLG4sbixuLG4sbixuLG4sbixuLG4sbixuLG4sbixuLG4sbl19KSx0P2NjLmhpZGVMb2FkaW5nKCk6Y2Muc3RvcFB1bGxEb3duUmVmcmVzaCgpfSxvbkl0ZW1DbGljayh0KXt2YXIgbj10aGlzLmRhdGEubGlzdFt0LnRhcmdldC5kYXRhc2V0LmluZGV4XTtjYy5uYXZpZ2F0ZVRvKHt1cmw6ImRldGFpbD9pdGVtPSIrSlNPTi5zdHJpbmdpZnkobil9KX0sb25QdWxsRG93blJlZnJlc2goKXtjb25zb2xlLmxvZygib25QdWxsRG93blJlZnJlc2giKSx0aGlzLmRvUmVxdWVzdCghMSl9LG9uVW5sb2FkKCl7fX0pfV0pOwovLyMgc291cmNlTWFwcGluZ1VSTD1ob21lLmJ1bmRsZS5qcy5tYXA=","config":{"navigationBarTitleText":"","backgroundColor":"#eeeeee","enablePullDownRefresh":true}} -------------------------------------------------------------------------------- /cc-cli/watch.js: -------------------------------------------------------------------------------- 1 | var watch = require('node-watch'); 2 | var path = require('path'); 3 | var fs = require('fs'); 4 | var net = require('net'); 5 | const chalk = require('chalk'); 6 | const parser = require('./parser'); 7 | 8 | function _getLocalIP() { 9 | const os = require('os'); 10 | const osType = os.type(); //系统类型 11 | const netInfo = os.networkInterfaces(); //网络信息 12 | let ip = ''; 13 | if (osType === 'Windows_NT') { 14 | for (let dev in netInfo) { 15 | if (dev === '本地连接') { 16 | for (let j = 0; j < netInfo[dev].length; j++) { 17 | if (netInfo[dev][j].family === 'IPv4') { 18 | ip = netInfo[dev][j].address; 19 | break; 20 | } 21 | } 22 | } 23 | } 24 | 25 | } else { 26 | ip = '127.0.0.1' 27 | } 28 | console.log(chalk.green(`local ip:${ip}`)) 29 | 30 | return ip; 31 | } 32 | 33 | class Watcher { 34 | constructor(dir) { 35 | this.dir = dir; 36 | 37 | this.watcher = null; 38 | this.server = null; 39 | this.socket = null; 40 | } 41 | /** 42 | * 开启监听模板修改 43 | * @param {监听模板目录} dir 44 | */ 45 | start() { 46 | if (!this.dir) { 47 | throw new Error('dir is null') 48 | } 49 | this._createWatcher(); 50 | this._createServer(); 51 | } 52 | /** 53 | * stop watch file 54 | */ 55 | stop() { 56 | try { 57 | //close watch 58 | this.watcher.close(); 59 | 60 | //close server 61 | this.socket = null; 62 | if (this.server) { 63 | this.server.close(); 64 | } 65 | } catch (error) { 66 | console.log(chalk.red(error)); 67 | } 68 | } 69 | _createServer() { 70 | let that = this; 71 | //start server 72 | //create a server 73 | var server = net.createServer(function (socket) { 74 | var client = socket.remoteAddress + ':' + socket.remotePort; 75 | console.log(`Accept new connection from client ${client}`); 76 | 77 | //received data from client 78 | socket.on('data', function (data) { 79 | console.log(chalk.yellow(`Received data from client: ${data}`)); 80 | }); 81 | //the end event of socket 82 | socket.on('end', function () { 83 | console.log(chalk.red('Client disconnected.')); 84 | }); 85 | socket.on('close', function () { 86 | console.log(chalk.red('Client close.')); 87 | }); 88 | socket.on('error', function (err) { 89 | console.log(chalk.red('Client error.')); 90 | }); 91 | socket.on('timeout', function () { 92 | console.log(chalk.red('Client timeout.')); 93 | }); 94 | that.socket = socket; 95 | }); 96 | 97 | //start listen a port 98 | server.listen(9999, _getLocalIP(), function () { 99 | 100 | // Get server address info. 101 | var serverInfo = server.address(); 102 | 103 | var serverInfoJson = JSON.stringify(serverInfo); 104 | 105 | console.log(chalk.green('TCP server listen on address : ' + serverInfoJson)); 106 | 107 | server.on('close', function () { 108 | console.log(chalk.red('TCP server socket is closed.')); 109 | }); 110 | 111 | server.on('error', function (error) { 112 | console.error(chalk.red(JSON.stringify(error))); 113 | }); 114 | 115 | }); 116 | 117 | this.server = server; 118 | } 119 | _createWatcher() { 120 | /** 121 | * https://www.npmjs.com/package/node-watch 122 | */ 123 | let that = this; 124 | let watcher = watch(this.dir, { 125 | recursive: true, filter: f => { 126 | if (/node_modules\/|temp\/|node_modules\\|temp\\/.test(f)) {//match unix or windows path 127 | return false; 128 | } 129 | return /\.js$|\.config$|\.html|\.css$/.test(f);//only observe js,html,css,config content 130 | } 131 | }, function (evt, name) { 132 | that._fileHandler(name); 133 | console.log(chalk.yellow(`${name} changed.`)); 134 | }); 135 | 136 | watcher.on('error', function (err) { 137 | // handle error 138 | console.log(chalk.red(err)); 139 | }); 140 | 141 | watcher.on('ready', function () { 142 | // the watcher is ready to respond to changes 143 | console.log(chalk.green(`watch dir:${that.dir}`)) 144 | }); 145 | this.watcher = watcher; 146 | } 147 | async _fileHandler(filepath) { 148 | if (path.extname(filepath) === '.js' && filepath.endsWith('.bundle.js')) { 149 | filepath = path.resolve('.', 'src', path.basename(filepath, '.bundle.js') + '.html') 150 | } 151 | //complie html file 152 | let htmlfile = filepath, ext = path.extname(filepath); 153 | if (ext !== '.html') { 154 | htmlfile = path.join(path.dirname(htmlfile), path.basename(htmlfile, ext) + '.html') 155 | } 156 | if (fs.existsSync(htmlfile) === false) { 157 | console.log(chalk.red(`File ${htmlfile} not exits.Ignore complie.`)) 158 | return; 159 | } 160 | await new parser.Parser(htmlfile).parse(); 161 | 162 | //send json file to client 163 | let json_name = path.basename(filepath); 164 | json_name = json_name.replace(path.extname(filepath), ''); 165 | 166 | let jsonPath = filepath.replace(path.extname(filepath), '.json') 167 | let json_content = fs.readFileSync(jsonPath).toString(); 168 | let res = JSON.stringify({ 169 | 'pageCode': json_name, 170 | 'content': json_content 171 | }); 172 | if (this.socket == null) { 173 | console.log(chalk.yellow(`Client not connected.changed file: ${jsonPath}`)) 174 | return; 175 | } 176 | //write content length 177 | let buffer = Buffer.alloc(4); 178 | buffer.writeInt32LE(res.length);//convert to little-endian 179 | this.socket.write(buffer); 180 | 181 | //write content 182 | this.socket.write(res); 183 | 184 | console.log(chalk.yellow('Socket send data: ' + filepath)); 185 | } 186 | } 187 | 188 | module.exports = Watcher; -------------------------------------------------------------------------------- /cc-cli/src/detail.json: -------------------------------------------------------------------------------- 1 | {"style":{".scroll-container":{"width-factor":"1","height-factor":"1"},".column":{"cross-axis-alignment":"start"},".row-container":{"color":"white","padding":"10"},".image-container":{"width":"120px","height":"160px","margin-right":"5"},".title":{"font-size":"14px","color":"black"},".summary-container":{"color":"white","padding":"10"},".label-container":{"padding-left":"10","padding-top":"5","padding-bottom":"5"},".summary-label":{"font-size":"16","color":"green"},".catalog-container":{"color":"white","padding-top":"10","padding-left":"10","padding-right":"10"},".more-container":{"padding-left":"10","padding-right":"10","padding-bottom":"10","color":"white"},".more-btn":{"color":"white"},".more":{"color":"blue"}},"body":{"tag":"body","innerHTML":"","childNodes":[{"tag":"fractionallysizedbox","innerHTML":"","childNodes":[{"tag":"singlechildscrollview","innerHTML":"","childNodes":[{"tag":"column","innerHTML":"","childNodes":[{"tag":"container","innerHTML":"","childNodes":[{"tag":"row","innerHTML":"","childNodes":[{"tag":"container","innerHTML":"","childNodes":[{"tag":"image","innerHTML":"","childNodes":[],"datasets":{},"events":{},"directives":{},"attribStyle":{"src":"{{detail.image}}"},"attrib":{},"id":"image"}],"datasets":{},"events":{},"directives":{},"attribStyle":{},"attrib":{},"id":"image-container"},{"tag":"column","innerHTML":"","childNodes":[{"tag":"container","innerHTML":"","childNodes":[{"tag":"text","innerHTML":"e3si5Lmm5ZCN77yaIiArIGRldGFpbC50aXRsZX19","childNodes":[],"datasets":{},"events":{},"directives":{},"attribStyle":{},"attrib":{},"id":"title"}],"datasets":{},"events":{},"directives":{},"attribStyle":{},"attrib":{}},{"tag":"container","innerHTML":"","childNodes":[{"tag":"text","innerHTML":"e3si5L2c6ICF77yaIiArIGRldGFpbC5hdXRob3J9fQ==","childNodes":[],"datasets":{},"events":{},"directives":{},"attribStyle":{},"attrib":{},"id":"title"}],"datasets":{},"events":{},"directives":{},"attribStyle":{},"attrib":{}},{"tag":"container","innerHTML":"","childNodes":[{"tag":"text","innerHTML":"e3si5Ye654mI56S+77yaIiArIGRldGFpbC5wdWJsaXNoZXJ9fQ==","childNodes":[],"datasets":{},"events":{},"directives":{},"attribStyle":{},"attrib":{},"id":"title"}],"datasets":{},"events":{},"directives":{},"attribStyle":{},"attrib":{}},{"tag":"container","innerHTML":"","childNodes":[{"tag":"text","innerHTML":"e3si5Ymv5qCH6aKY77yaIiArIGRldGFpbC5zdWJ0aXRsZX19","childNodes":[],"datasets":{},"events":{},"directives":{},"attribStyle":{},"attrib":{},"id":"title"}],"datasets":{},"events":{},"directives":{},"attribStyle":{},"attrib":{}}],"datasets":{},"events":{},"directives":{},"attribStyle":{},"attrib":{},"id":"column"}],"datasets":{},"events":{},"directives":{},"attribStyle":{},"attrib":{}}],"datasets":{},"events":{},"directives":{},"attribStyle":{},"attrib":{},"id":"row-container"},{"tag":"container","innerHTML":"","childNodes":[{"tag":"text","innerHTML":"5YaF5a65566A5LuL","childNodes":[],"datasets":{},"events":{},"directives":{},"attribStyle":{},"attrib":{},"id":"summary-label"}],"datasets":{},"events":{},"directives":{},"attribStyle":{},"attrib":{},"id":"label-container"},{"tag":"container","innerHTML":"","childNodes":[{"tag":"text","innerHTML":"e3tkZXRhaWwuc3VtbWFyeX19","childNodes":[],"datasets":{},"events":{},"directives":{},"attribStyle":{},"attrib":{},"id":"summary"}],"datasets":{},"events":{},"directives":{},"attribStyle":{},"attrib":{},"id":"summary-container"},{"tag":"container","innerHTML":"","childNodes":[{"tag":"text","innerHTML":"5L2c6ICF566A5LuL","childNodes":[],"datasets":{},"events":{},"directives":{},"attribStyle":{},"attrib":{},"id":"summary-label"}],"datasets":{},"events":{},"directives":{},"attribStyle":{},"attrib":{},"id":"label-container"},{"tag":"container","innerHTML":"","childNodes":[{"tag":"text","innerHTML":"e3tkZXRhaWwuYXV0aG9yX2ludHJvfX0=","childNodes":[],"datasets":{},"events":{},"directives":{},"attribStyle":{},"attrib":{},"id":"author"}],"datasets":{},"events":{},"directives":{},"attribStyle":{},"attrib":{},"id":"summary-container"},{"tag":"container","innerHTML":"","childNodes":[{"tag":"text","innerHTML":"55uu5b2V","childNodes":[],"datasets":{},"events":{},"directives":{},"attribStyle":{},"attrib":{},"id":"summary-label"}],"datasets":{},"events":{},"directives":{},"attribStyle":{},"attrib":{},"id":"label-container"},{"tag":"container","innerHTML":"","childNodes":[{"tag":"text","innerHTML":"e3tjYXRhbG9nU2hvcnR9fQ==","childNodes":[],"datasets":{},"events":{},"directives":{},"attribStyle":{},"attrib":{},"id":"catalog"}],"datasets":{},"events":{},"directives":{},"attribStyle":{},"attrib":{},"id":"catalog-container"},{"tag":"container","innerHTML":"","childNodes":[{"tag":"raisedbutton","innerHTML":"","childNodes":[{"tag":"text","innerHTML":"e3tidG5UZXh0fX0=","childNodes":[],"datasets":{},"events":{},"directives":{},"attribStyle":{},"attrib":{},"id":"more"}],"datasets":{},"events":{"bindtap":"onMoreClick"},"directives":{},"attribStyle":{},"attrib":{},"id":"more-btn"}],"datasets":{},"events":{},"directives":{},"attribStyle":{},"attrib":{},"id":"more-container"}],"datasets":{},"events":{},"directives":{},"attribStyle":{},"attrib":{},"id":"column"}],"datasets":{},"events":{},"directives":{},"attribStyle":{},"attrib":{}}],"datasets":{},"events":{},"directives":{},"attribStyle":{},"attrib":{},"id":"scroll-container"}],"datasets":{},"events":{},"directives":{},"attribStyle":{},"attrib":{}},"script":"IWZ1bmN0aW9uKHQpe3ZhciBlPXt9O2Z1bmN0aW9uIG4ocil7aWYoZVtyXSlyZXR1cm4gZVtyXS5leHBvcnRzO3ZhciBvPWVbcl09e2k6cixsOiExLGV4cG9ydHM6e319O3JldHVybiB0W3JdLmNhbGwoby5leHBvcnRzLG8sby5leHBvcnRzLG4pLG8ubD0hMCxvLmV4cG9ydHN9bi5tPXQsbi5jPWUsbi5kPWZ1bmN0aW9uKHQsZSxyKXtuLm8odCxlKXx8T2JqZWN0LmRlZmluZVByb3BlcnR5KHQsZSx7ZW51bWVyYWJsZTohMCxnZXQ6cn0pfSxuLnI9ZnVuY3Rpb24odCl7InVuZGVmaW5lZCIhPXR5cGVvZiBTeW1ib2wmJlN5bWJvbC50b1N0cmluZ1RhZyYmT2JqZWN0LmRlZmluZVByb3BlcnR5KHQsU3ltYm9sLnRvU3RyaW5nVGFnLHt2YWx1ZToiTW9kdWxlIn0pLE9iamVjdC5kZWZpbmVQcm9wZXJ0eSh0LCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KX0sbi50PWZ1bmN0aW9uKHQsZSl7aWYoMSZlJiYodD1uKHQpKSw4JmUpcmV0dXJuIHQ7aWYoNCZlJiYib2JqZWN0Ij09dHlwZW9mIHQmJnQmJnQuX19lc01vZHVsZSlyZXR1cm4gdDt2YXIgcj1PYmplY3QuY3JlYXRlKG51bGwpO2lmKG4ucihyKSxPYmplY3QuZGVmaW5lUHJvcGVydHkociwiZGVmYXVsdCIse2VudW1lcmFibGU6ITAsdmFsdWU6dH0pLDImZSYmInN0cmluZyIhPXR5cGVvZiB0KWZvcih2YXIgbyBpbiB0KW4uZChyLG8sZnVuY3Rpb24oZSl7cmV0dXJuIHRbZV19LmJpbmQobnVsbCxvKSk7cmV0dXJuIHJ9LG4ubj1mdW5jdGlvbih0KXt2YXIgZT10JiZ0Ll9fZXNNb2R1bGU/ZnVuY3Rpb24oKXtyZXR1cm4gdC5kZWZhdWx0fTpmdW5jdGlvbigpe3JldHVybiB0fTtyZXR1cm4gbi5kKGUsImEiLGUpLGV9LG4ubz1mdW5jdGlvbih0LGUpe3JldHVybiBPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwodCxlKX0sbi5wPSIiLG4obi5zPTApfShbZnVuY3Rpb24odCxlKXtQYWdlKHtkYXRhOntkZXRhaWw6e30sY2F0YWxvZ1Nob3J0OiIiLHNob3dMb25nOiExLGJ0blRleHQ6Iuafpeeci+abtOWkmiJ9LG9uTG9hZCh0KXt2YXIgZT1KU09OLnBhcnNlKHQuaXRlbSk7Y2Muc2V0TmF2aWdhdGlvbkJhclRpdGxlKHt0aXRsZTplLnRpdGxlfSk7dmFyIG49ZS5jYXRhbG9nO24ubGVuZ3RoPjUwJiYobj1uLnN1YnN0cmluZygwLDUwKSsiLi4uIiksdGhpcy5zZXREYXRhKHtkZXRhaWw6ZSxjYXRhbG9nU2hvcnQ6bn0pfSxvbk1vcmVDbGljayh0KXt2YXIgZT0hdGhpcy5kYXRhLnNob3dMb25nLG49dGhpcy5kYXRhLmRldGFpbC5jYXRhbG9nLHI9IuaUtui1t+abtOWkmiI7ZXx8KG4ubGVuZ3RoPjUwJiYobj1uLnN1YnN0cmluZygwLDUwKSsiLi4uIikscj0i5p+l55yL5pu05aSaIiksdGhpcy5zZXREYXRhKHtzaG93TG9uZzplLGNhdGFsb2dTaG9ydDpuLGJ0blRleHQ6cn0pfSxvblVubG9hZCgpe319KX1dKTsKLy8jIHNvdXJjZU1hcHBpbmdVUkw9ZGV0YWlsLmJ1bmRsZS5qcy5tYXA=","config":{"navigationBarTitleText":"","backgroundColor":"#eeeeee"}} --------------------------------------------------------------------------------