diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..76d0271
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,20 @@
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+indent_style = space
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.{js,ts,jsx,tsx}]
+indent_size = 4
+
+[*.{less,html,ejs}]
+indent_size = 2
+
+[*.json]
+indent_size = 2
+
+[tsconfig.json]
+indent_size = 4
diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 0000000..c94e368
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,5 @@
+types
+node_modules
+dist
+assets
+docs
diff --git a/.eslintrc b/.eslintrc
index b85ad48..3a3ff31 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -1,171 +1,47 @@
+
{
- "extends": "eslint:recommended",
+ "env": {
+ "browser": true,
+ "node": true,
+ "es6": true,
+ "jest/globals": true
+ },
+ "plugins": ["jest", "react"],
+ "extends": ["plugin:@fui/typescript", "plugin:@fui/prettier"],
+ "parserOptions": {
+ "ecmaFeatures": {
+ "legacyDecorators": true,
+ "jsx": true
+ }
+ },
"rules": {
- // 声明
- "no-use-before-define": "error",
- //禁止定义前使用
-
- // 对象
- "no-dupe-keys": "error",
- // 禁止在对象字面量中出现重复的键
- "quote-props": [
+ // 由于prettier和eslint一起使用需要关闭一些规则,但是prettier没有以下规则的配置,统一写在这里
+ // 对象字面量简写语法
+ "object-shorthand": ["error", "always"],
+ // 函数体在必要的时候使用大括号
+ "arrow-body-style": [
"error",
- "as-needed"
- ],
- // 对象属性只在需要的时候加引号
-
- // 字符串
- "quotes": [
- "error",
- "double",
+ "as-needed",
{
- "allowTemplateLiterals": true
+ "requireReturnForObjectLiteral": true
}
],
- // 字符串开头和结束使用双引号
- "no-useless-concat": "error",
- // 禁止没有必要的字符拼接
- "no-useless-escape": "error",
- // 禁用不必要的转义
-
- // 函数
- "no-dupe-args": "error",
- // 禁止在 function 定义中出现重复的参数
- "space-before-function-paren": "error",
- // 函数括号前必须要有空格
-
- // 变量
- "no-undef": "error",
- // 禁止使用未声明的变量
-
- // 比较运算符 & 相等运算符
- "eqeqeq": "error",
- // 使用 === 和 !== 代替 == 和 !=
- "no-unneeded-ternary": "error",
- //禁止可以在有更简单的可替代的表达式时使用三元操作符
-
- // 条件
- "default-case": "error",
- // 要求 Switch 语句中有 Default 分支
- "no-else-return": "error",
+ // 回调使用用箭头函数
+ "prefer-arrow-callback": "error",
// 如果 if 块中包含了一个 return 语句,else 块就成了多余的了。可以将其内容移至块外
+ "no-else-return": "error",
+ "no-use-before-define": "off",
- // 代码块
- "brace-style": [
- "error",
- "1tbs",
- {
- "allowSingleLine": true
- }
- ],
- // 代码块左括号紧跟上一行结束
- "curly": [
- "error",
- "multi-line"
- ],
- // if、else if、else、for、while强制使用大括号,但允许在单行中省略大括号
-
- // 注释
- "spaced-comment": "error",
- // 注释前有空格
-
- // 空白
- "indent": [
- "error",
- 4,
- {
- "SwitchCase": 1
- }
- ],
- // 缩进控制4空格
- "no-mixed-spaces-and-tabs": "error",
- // 禁止使用 空格 和 tab 混合缩进
- "space-before-blocks": [
- "error",
- "always"
- ],
- // 语句块之前的需要有空格
- "space-infix-ops": [
- "error",
- {
- "int32Hint": false
- }
- ],
- // 要求中缀操作符周围有空格,设置 int32Hint 选项为 true (默认 false) 允许 a|0 不带空格
- "no-trailing-spaces": [
- "error",
- {
- "skipBlankLines": true
- }
- ],
- // 禁用行尾空格
- "key-spacing": [
- "error",
- {
- "afterColon": true
- }
- ],
- // 要求在对象字面量的冒号和值之间存在至少有一个空格
-
-
- // 逗号
- "comma-style": "error",
- // 逗号必须放在行末
- "comma-dangle": [
- "error",
- "never"
- ],
- // 多行对象字面量中要求不要拖尾逗号
- "comma-spacing": [
- "error",
- {
- "before": false,
- "after": true
- }
- ],
- //在变量声明、数组字面量、对象字面量、函数参数 和 序列中禁止在逗号前使用空格,要求在逗号后使用一个或多个空格
-
-
- // 分号
- "semi": "error",
- //不得省略语句结束的分号
- "semi-spacing": [
- "error",
- {
- "before": false,
- "after": true
- }
- ],
- //禁止分号周围的空格
- "no-extra-semi": "error",
- // 禁用不必要的分号
-
-
- // 类型转换
- "no-extra-boolean-cast": "error",
- // 禁止不必要的布尔类型转换
-
-
- // 其他最佳实践或规范
- "no-unexpected-multiline": "error",
- // 禁止使用令人困惑的多行表达式
- "no-unreachable": "error",
- // 禁止在 return、throw、continue 和 break 语句后出现不可达代码
- "valid-typeof": "error",
- // 强制 typeof 表达式与有效的字符串进行比较
- "no-new-wrappers": "error"
- // 禁止通过 new 操作符使用 String、Number 和 Boolean
+ "react/jsx-uses-react": "error",
+ "react/jsx-uses-vars": "error"
},
- "globals": {
- "window": true,
- "BI": true,
- "BICst": true,
- "Data": true,
- "Fix": true,
- "module": true,
- "describe": true,
- "it": true,
- "expect": true,
- "Dec": true
- }
-}
\ No newline at end of file
+ "overrides": [
+ {
+ "files": ["*.ts", "*.tsx"],
+ "rules": {
+ "no-undef": "off",
+ "no-unused-vars": "off"
+ }
+ }
+ ]
+}
diff --git a/.gitignore b/.gitignore
index 1efa1e2..ea883ba 100644
--- a/.gitignore
+++ b/.gitignore
@@ -47,5 +47,7 @@ crashlytics.properties
crashlytics-build.properties
/node_modules/
+
+/dist
package-lock.json
yarn.lock
\ No newline at end of file
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 0000000..ee99b80
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1,2 @@
+registry=https://registry.npm.taobao.org
+@fui:registry=https://npm.fineres.com/
\ No newline at end of file
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..c94e368
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,5 @@
+types
+node_modules
+dist
+assets
+docs
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..7bb8fe5
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,5 @@
+{
+ "singleQuote": true,
+ "arrowParens": "avoid",
+ "printWidth": 160
+}
diff --git a/.stylelintignore b/.stylelintignore
new file mode 100644
index 0000000..c94e368
--- /dev/null
+++ b/.stylelintignore
@@ -0,0 +1,5 @@
+types
+node_modules
+dist
+assets
+docs
diff --git a/.stylelintrc b/.stylelintrc
new file mode 100644
index 0000000..c671fba
--- /dev/null
+++ b/.stylelintrc
@@ -0,0 +1,7 @@
+{
+ "extends": ["stylelint-config-standard", "stylelint-prettier/recommended"],
+ "rules": {
+ "no-descending-specificity": null,
+ "selector-pseudo-element-colon-notation": "single"
+ }
+}
diff --git a/Gruntfile.js b/Gruntfile.js
deleted file mode 100644
index 70aa6fa..0000000
--- a/Gruntfile.js
+++ /dev/null
@@ -1,121 +0,0 @@
-module.exports = function (grunt) {
- // Project configuration.
- grunt.initConfig({
- pkg: grunt.file.readJSON("package.json"),
- concat: {
- options: {
- separator: ""
- },
- bundleJs: {
- src: [
- "src/modules/**/*.js",
- "src/index.js"
- ],
- dest: "dist/bundle.js"
- },
- bundleCss: {
- src: ["src/css/**/*.css"],
- dest: "dist/bundle.css"
- }
- },
-
- less: {
- main: {
- expand: true,
- cwd: "src/modules",
- src: ["**/*.less"],
- dest: "src/css/",
- ext: ".css"
- },
- dev: {
- options: {
- compress: true,
- yuicompress: false
- }
- }
- },
-
- uglify: {
- options: {
- banner:
- "/*! <%= pkg.name %> <%= grunt.template.today(\"dd-mm-yyyy\") %> */\n"
- },
- dist: {
- files: {
- "dist/bundle.min.js": ["<%= concat.bundleJs.dest %>"]
- }
- }
- },
-
- cssmin: {
- bundleCss: {
- src: "<%= concat.bundleCss.dest %>",
-
- dest: "dist/bundle.min.css"
- }
- },
-
- jshint: {
- files: ["src/**/*.js"],
- options: {
- globals: {
- $: true,
- jQuery: true,
- console: true,
- module: true,
- document: true
- },
- browser: true,
- expr: true
- }
- },
- watch: {
- scripts: {
- files: ["src/**/*.js", "src/**/*.less"],
- tasks: ["less", "concat"],
- options: {
- spanw: true,
- interrupt: true
- }
- },
- livereload: {
- options: {
- livereload: "<%= connect.options.livereload %>"
- },
- files: ["src/**/*.js", "src/**/*.less"]
- }
- },
- connect: {
- options: {
- port: 9000,
- open: true,
- livereload: 35799,
- // Change this to '0.0.0.0' to access the server from outside
- hostname: "localhost"
- },
- server: {
- options: {
- port: 9009,
- base: "./"
- }
- }
- }
- });
-
- grunt.loadNpmTasks("grunt-contrib-uglify");
- grunt.loadNpmTasks("grunt-contrib-jshint");
- grunt.loadNpmTasks("grunt-contrib-less");
- grunt.loadNpmTasks("grunt-contrib-watch");
- grunt.loadNpmTasks("grunt-contrib-concat");
- grunt.loadNpmTasks("grunt-contrib-cssmin");
- grunt.loadNpmTasks("grunt-contrib-connect");
-
- grunt.registerTask("default", [
- "jshint",
- "less",
- "concat",
- "connect",
- "watch"
- ]);
- grunt.registerTask("min", ["less", "concat", "uglify", "cssmin"]);
-};
diff --git a/README.md b/README.md
index 78002c7..427ed9f 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,63 @@
-# FineUI-Start
+# README
-## 安装运行
-`git clone --recursive https://git.fanruan.com/dailer/FineUI-Start.git`
+## 准备工作
-`npm install`
+### 克隆仓库
-`npm run grunt`
+```shell
+git clone https://code.fineres.com/scm/fui/fineui-starter.git
+```
-## 示例效果
-![](./screenshorts/todolist.gif)
\ No newline at end of file
+### 安装依赖
+
+```shell
+# 账户:public
+# 密码:fr123456
+# 邮箱:任意
+npm adduser --registry https://npm.fineres.com
+npm install
+```
+
+### 安装扩展
+
+以 VSCode 为例,为保证代码规范和格式统一,请安装`ESLint`、`stylelint`、`Prettier`、`EditorConfig`扩展,以使相关配置文件生效。
+
+## 开发工作
+
+### 项目基础配置
+
+布局配置请见`src/modules/app/layout/layout.constant.ts`。
+
+样式配置请见`src/less/`,譬如`src/less/lib/constant`中的`@color-app-primary`表示项目的主色。
+
+### 项目调试、打包与测试
+
+```shell
+# 调试
+npm run start
+
+# 打包
+npm run build
+
+# 测试
+npm run test
+```
+
+### 项目规范和格式检查
+
+```shell
+# 代码规范
+npm run eslint
+npm run eslint:fix
+
+# 样式规范
+npm run stylelint
+npm run stylelint:fix
+
+# 代码格式
+npm run prettier
+```
+
+## 效果预览
+
+![页面截图](./screenshots/demo.jpeg)
diff --git a/advance.html b/advance.html
deleted file mode 100644
index 2e85732..0000000
--- a/advance.html
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/assets/images/1x/logo.png b/assets/images/1x/logo.png
new file mode 100644
index 0000000..11baaa1
Binary files /dev/null and b/assets/images/1x/logo.png differ
diff --git a/assets/images/2x/logo.png b/assets/images/2x/logo.png
new file mode 100644
index 0000000..17f645e
Binary files /dev/null and b/assets/images/2x/logo.png differ
diff --git a/babel.config.js b/babel.config.js
new file mode 100644
index 0000000..12cd09e
--- /dev/null
+++ b/babel.config.js
@@ -0,0 +1 @@
+module.exports = require('@fui/babel-preset-fineui').configs.base;
diff --git a/dist/bundle.css b/dist/bundle.css
deleted file mode 100644
index 456246a..0000000
--- a/dist/bundle.css
+++ /dev/null
@@ -1,36 +0,0 @@
-/**
- 列表项的less,其中用到了部分FineUI提供的字号,颜色常量,还有border-radius,box-shadow方法等.请选择性使用.不强制要求
- */
-.my-todolist-header {
- background-color: #3d4d66;
-}
-.my-todolist-header .my-todolist-title {
- font-size: 22px;
- color: #FFF;
-}
-.my-todolist-header .my-todolist-header-editor {
- background-color: #FFF;
- -webkit-border-radius: 5px;
- -moz-border-radius: 5px;
- border-radius: 5px;
-}
-/**
- 列表项的less,其中用到了部分FineUI提供的字号,颜色常量,还有border-radius方法等.请选择性使用.不强制要求
- */
-.my-todolist-list .my-todolist-list-text {
- font-size: 16px;
- font-weight: bold;
-}
-.my-todolist-list .my-todolist-list-count-container {
- -webkit-border-radius: 10px;
- -moz-border-radius: 10px;
- border-radius: 10px;
- background-color: #3d4d66;
- color: #ffffff;
-}
-/**
- 列表项的less,其中用到了部分FineUI提供的字号,颜色常量,还有border-radius,box-shadow方法等.请选择性使用.不强制要求
- */
-.my-todolist-background {
- background-color: #f7f8fa;
-}
diff --git a/dist/bundle.js b/dist/bundle.js
deleted file mode 100644
index c7db7c3..0000000
--- a/dist/bundle.js
+++ /dev/null
@@ -1,286 +0,0 @@
-!(function() {
- /**
- * 顶部组件,提供输入框添加todo项目
- * 布局: bi.horizontal_auto 实现水平居中. bi.left_right_vertical_adapt 实现标题是输入框的靠左靠右垂直居中
- */
- var ToDoListHeader = BI.inherit(BI.Widget, {
-
- props: {
- // 指定组件的className
- baseCls: "my-todolist-header"
- },
-
- render: function() {
- var self = this, o = this.options;
- return {
- type: "bi.horizontal_auto", // 水平居中布局
- items: [
- {
- el: {
- type: "bi.left_right_vertical_adapt", // 左右垂直居中布局
- width: 600,
- height: o.height,
- items: {
- left: [
- {
- el: {
- type: "bi.label",
- cls: "my-todolist-title",
- text: "FineUI ToDoList",
- height: o.height
- }
- }
- ],
- right: [
- {
- el: {
- type: "bi.editor",
- ref: function(_ref) {
- self.editor = _ref;
- },
- allowBlank: true,
- cls: "my-todolist-header-editor",
- watermark: "添加ToDo",
- width: 300,
- height: 24,
- listeners: [
- { // 监听bi.editor 组件的"EVENT_ENTER"事件(即敲回车),触发事件ToDoListHeader.EVENT_ADD事件并将输入框值置空
- eventName: "EVENT_ENTER",
- action: function() {
- // 注意:在这里this指向的是bi.editor的实例.通过bi.editor的getValue()方法获取输入框输入值.
- self.fireEvent(ToDoListHeader.EVENT_ADD, this.getValue());
- self.editor.setValue("");
- }
- }
- ]
- }
- }
- ]
- }
- }
- }
- ]
- };
- }
- });
-
- ToDoListHeader.EVENT_ADD = "EVENT_ADD";
-
- BI.shortcut("my.todolist.header", ToDoListHeader);
-})();!(function() {
- /**
- * todo项列表
- *
- */
- var List = BI.inherit(BI.Widget, {
-
- props: {
- // 指定组件的className
- baseCls: "my-todolist-list",
- text: "正在进行"
- },
-
- render: function() {
- var self = this, o = this.options;
- return {
- type: "bi.vertical",
- items: [
- {
- el: {
- type: "bi.vertical_adapt",
- height: 40,
- items: [
- {
- type: "bi.label",
- cls: "my-todolist-list-text",
- textAlign: "left",
- text: o.text,
- width: 580
- }, {
- type: "bi.center_adapt",
- cls: "my-todolist-list-count-container",
- width: 20,
- height: 20,
- items: [
- {
- el: {
- type: "bi.label",
- ref: function(_ref) {
- self.count = _ref;
- },
- text: "0"
- }
- }
- ]
- }
- ]
- }
- }, { // 用bi.vertical布局作为列表项的容器.
- type: "bi.vertical",
- vgap: 10,
- ref: function(_ref) {
- self.list = _ref;
- },
- items: this._createItems(o.items)
- }
- ]
- };
- },
-
- _createItems: function(items) {
- var self = this;
- return BI.map(items, function(index, item) {
- return BI.extend(item, {
- type: "bi.multi_select_item", // 节点采用复选节点展示
- selected: item.done, // 已完成的todo项置为选中状态
- disabled: item.done, // 已完成的todo项置为灰化状态
- listeners: [
- { // 为每个todo项添加"EVENT_CHANGE"事件监听,触发组件自身"EVENT_CHANGE"事件
- eventName: "EVENT_CHANGE",
- action: function(v) {
- self.fireEvent("EVENT_CHANGE", v);
- }
- }
- ]
- });
- });
- },
-
- _setCount: function(count) {
- this.count.setText(count);
- },
-
- populate: function(items) {
- this.list.populate(this._createItems(items));
- this._setCount(items.length);
- }
- });
- BI.shortcut("my.todolist.list", List);
-})();!(function() {
- /**
- * todolist 组件
- */
- var ToDoList = BI.inherit(BI.Widget, {
-
- props: {
- baseCls: "fine-to-do-list"
- },
-
- // 生命周期函数,在组件创建前
- beforeCreate: function() {
- // 初始化存储数据
- this.list = localStorage.getItem("fine-todolist") ? JSON.parse(localStorage.getItem("fine-todolist")) : [];
- },
-
- render: function() {
- var self = this, o = this.options;
- return {
- type: "bi.vtape", // vtape布局,顶部高度固定,下部分列表占满高度
- items: [
- {
- el: {
- type: "my.todolist.header", // 顶部组件
- listeners: [
- { // 监听组件的EVENT_ADD事件,新建todo项
- eventName: "EVENT_ADD",
- action: function(v) {
- self.addToDo(v);
- }
- }
- ],
- height: 40
- },
- height: 40
- }, {
- type: "bi.horizontal_auto", // 水平居中布局
- cls: "my-todolist-background", // 添加className
- items: [
- {
- el: {
- type: "my.todolist.list", // need todo项列表
- ref: function(_ref) {
- self.todolist = _ref;
- },
- items: this._getNeedTodoList(),
- text: "正在进行",
- listeners: [
- { // 监听EVENT_CHANGE事件,完成某一项todo
- eventName: "EVENT_CHANGE",
- action: function(v) {
- self.finishTodo(v);
- }
- }
- ],
- width: 600
- }
- }, {
- el: {
- type: "my.todolist.list", // 已经完成的todo项列表
- text: "已经完成",
- items: this._getAlreadyDoneList(),
- ref: function(_ref) {
- self.donelist = _ref;
- },
- width: 600
- }
- }
- ]
- }
- ]
- };
- },
-
- _updateLocalStorage: function() {
- localStorage.setItem("fine-todolist", JSON.stringify(this.list));
- },
-
- _getNeedTodoList: function() {
- return BI.filter(this.list, function(index, item) {
- return !item.done;
- });
- },
-
- _getAlreadyDoneList: function() {
- return BI.filter(this.list, function(index, item) {
- return item.done;
- });
- },
-
- /**
- * 添加todo项
- * @param text todo项的内容
- */
- addToDo: function(text) {
- this.list.push({
- value: BI.UUID(),
- text: text,
- done: false
- });
- this.todolist.populate(this._getNeedTodoList());
- this._updateLocalStorage();
- },
-
- /**
- * 完成某一项todo
- * @param v todo项的value
- */
- finishTodo: function(v) {
- BI.some(this.list, function(index, item) {
- if (item.value === v) {
- item.done = true;
- }
- });
- this.todolist.populate(this._getNeedTodoList());
- this.donelist.populate(this._getAlreadyDoneList());
- this._updateLocalStorage();
- }
- });
- BI.shortcut("my.todolist", ToDoList);
-})();
-!(function () {
- // 将todolist组件挂载到#wrapper上.
- BI.createWidget({
- type: "my.todolist",
- element: "#wrapper"
- });
-})();
diff --git a/dist/font/iconfont.eot b/dist/font/iconfont.eot
deleted file mode 100644
index 693b5dc..0000000
Binary files a/dist/font/iconfont.eot and /dev/null differ
diff --git a/dist/font/iconfont.svg b/dist/font/iconfont.svg
deleted file mode 100644
index 973571d..0000000
--- a/dist/font/iconfont.svg
+++ /dev/null
@@ -1,1002 +0,0 @@
-
-
-
-
diff --git a/dist/font/iconfont.ttf b/dist/font/iconfont.ttf
deleted file mode 100644
index e62143d..0000000
Binary files a/dist/font/iconfont.ttf and /dev/null differ
diff --git a/dist/font/iconfont.woff b/dist/font/iconfont.woff
deleted file mode 100644
index 9c9686c..0000000
Binary files a/dist/font/iconfont.woff and /dev/null differ
diff --git a/dist/images/1x/background/auto_color.png b/dist/images/1x/background/auto_color.png
deleted file mode 100644
index 63b3edf..0000000
Binary files a/dist/images/1x/background/auto_color.png and /dev/null differ
diff --git a/dist/images/1x/background/line_conn.gif b/dist/images/1x/background/line_conn.gif
deleted file mode 100644
index d561d36..0000000
Binary files a/dist/images/1x/background/line_conn.gif and /dev/null differ
diff --git a/dist/images/1x/background/marker.png b/dist/images/1x/background/marker.png
deleted file mode 100644
index 3929bbb..0000000
Binary files a/dist/images/1x/background/marker.png and /dev/null differ
diff --git a/dist/images/1x/background/mask.png b/dist/images/1x/background/mask.png
deleted file mode 100644
index b0a4d40..0000000
Binary files a/dist/images/1x/background/mask.png and /dev/null differ
diff --git a/dist/images/1x/background/trans_color.png b/dist/images/1x/background/trans_color.png
deleted file mode 100644
index 8fa4492..0000000
Binary files a/dist/images/1x/background/trans_color.png and /dev/null differ
diff --git a/dist/images/1x/background/wheel.png b/dist/images/1x/background/wheel.png
deleted file mode 100644
index 97b343d..0000000
Binary files a/dist/images/1x/background/wheel.png and /dev/null differ
diff --git a/dist/images/1x/icon/check_box_active.png b/dist/images/1x/icon/check_box_active.png
deleted file mode 100644
index af54808..0000000
Binary files a/dist/images/1x/icon/check_box_active.png and /dev/null differ
diff --git a/dist/images/1x/icon/check_box_disable.png b/dist/images/1x/icon/check_box_disable.png
deleted file mode 100644
index bb7f451..0000000
Binary files a/dist/images/1x/icon/check_box_disable.png and /dev/null differ
diff --git a/dist/images/1x/icon/check_box_disable2.png b/dist/images/1x/icon/check_box_disable2.png
deleted file mode 100644
index fb593ff..0000000
Binary files a/dist/images/1x/icon/check_box_disable2.png and /dev/null differ
diff --git a/dist/images/1x/icon/check_box_normal.png b/dist/images/1x/icon/check_box_normal.png
deleted file mode 100644
index 2974ad7..0000000
Binary files a/dist/images/1x/icon/check_box_normal.png and /dev/null differ
diff --git a/dist/images/1x/icon/dark/tree_collapse_1.png b/dist/images/1x/icon/dark/tree_collapse_1.png
deleted file mode 100644
index 583c00f..0000000
Binary files a/dist/images/1x/icon/dark/tree_collapse_1.png and /dev/null differ
diff --git a/dist/images/1x/icon/dark/tree_collapse_2.png b/dist/images/1x/icon/dark/tree_collapse_2.png
deleted file mode 100644
index 083529b..0000000
Binary files a/dist/images/1x/icon/dark/tree_collapse_2.png and /dev/null differ
diff --git a/dist/images/1x/icon/dark/tree_collapse_3.png b/dist/images/1x/icon/dark/tree_collapse_3.png
deleted file mode 100644
index cc3d25c..0000000
Binary files a/dist/images/1x/icon/dark/tree_collapse_3.png and /dev/null differ
diff --git a/dist/images/1x/icon/dark/tree_collapse_4.png b/dist/images/1x/icon/dark/tree_collapse_4.png
deleted file mode 100644
index d599e57..0000000
Binary files a/dist/images/1x/icon/dark/tree_collapse_4.png and /dev/null differ
diff --git a/dist/images/1x/icon/dark/tree_expand_1.png b/dist/images/1x/icon/dark/tree_expand_1.png
deleted file mode 100644
index 76c5863..0000000
Binary files a/dist/images/1x/icon/dark/tree_expand_1.png and /dev/null differ
diff --git a/dist/images/1x/icon/dark/tree_expand_2.png b/dist/images/1x/icon/dark/tree_expand_2.png
deleted file mode 100644
index 53063e7..0000000
Binary files a/dist/images/1x/icon/dark/tree_expand_2.png and /dev/null differ
diff --git a/dist/images/1x/icon/dark/tree_expand_3.png b/dist/images/1x/icon/dark/tree_expand_3.png
deleted file mode 100644
index 0f35d6e..0000000
Binary files a/dist/images/1x/icon/dark/tree_expand_3.png and /dev/null differ
diff --git a/dist/images/1x/icon/dark/tree_expand_4.png b/dist/images/1x/icon/dark/tree_expand_4.png
deleted file mode 100644
index 4752f81..0000000
Binary files a/dist/images/1x/icon/dark/tree_expand_4.png and /dev/null differ
diff --git a/dist/images/1x/icon/dark/tree_vertical_line_1.png b/dist/images/1x/icon/dark/tree_vertical_line_1.png
deleted file mode 100644
index d13fc8d..0000000
Binary files a/dist/images/1x/icon/dark/tree_vertical_line_1.png and /dev/null differ
diff --git a/dist/images/1x/icon/dark/tree_vertical_line_2.png b/dist/images/1x/icon/dark/tree_vertical_line_2.png
deleted file mode 100644
index ebee980..0000000
Binary files a/dist/images/1x/icon/dark/tree_vertical_line_2.png and /dev/null differ
diff --git a/dist/images/1x/icon/dark/tree_vertical_line_3.png b/dist/images/1x/icon/dark/tree_vertical_line_3.png
deleted file mode 100644
index d2f7595..0000000
Binary files a/dist/images/1x/icon/dark/tree_vertical_line_3.png and /dev/null differ
diff --git a/dist/images/1x/icon/dark/tree_vertical_line_4.png b/dist/images/1x/icon/dark/tree_vertical_line_4.png
deleted file mode 100644
index 277d0e5..0000000
Binary files a/dist/images/1x/icon/dark/tree_vertical_line_4.png and /dev/null differ
diff --git a/dist/images/1x/icon/dark/tree_vertical_line_5.png b/dist/images/1x/icon/dark/tree_vertical_line_5.png
deleted file mode 100644
index 85aa1cc..0000000
Binary files a/dist/images/1x/icon/dark/tree_vertical_line_5.png and /dev/null differ
diff --git a/dist/images/1x/icon/dots.png b/dist/images/1x/icon/dots.png
deleted file mode 100644
index 55b461b..0000000
Binary files a/dist/images/1x/icon/dots.png and /dev/null differ
diff --git a/dist/images/1x/icon/half_selected.png b/dist/images/1x/icon/half_selected.png
deleted file mode 100644
index 298dd91..0000000
Binary files a/dist/images/1x/icon/half_selected.png and /dev/null differ
diff --git a/dist/images/1x/icon/icon_down_arrow.png b/dist/images/1x/icon/icon_down_arrow.png
deleted file mode 100644
index b77c330..0000000
Binary files a/dist/images/1x/icon/icon_down_arrow.png and /dev/null differ
diff --git a/dist/images/1x/icon/loading.gif b/dist/images/1x/icon/loading.gif
deleted file mode 100644
index d04fdd2..0000000
Binary files a/dist/images/1x/icon/loading.gif and /dev/null differ
diff --git a/dist/images/1x/icon/push_down.png b/dist/images/1x/icon/push_down.png
deleted file mode 100644
index 7eb88eb..0000000
Binary files a/dist/images/1x/icon/push_down.png and /dev/null differ
diff --git a/dist/images/1x/icon/push_up.png b/dist/images/1x/icon/push_up.png
deleted file mode 100644
index a3042a4..0000000
Binary files a/dist/images/1x/icon/push_up.png and /dev/null differ
diff --git a/dist/images/1x/icon/radio_active.png b/dist/images/1x/icon/radio_active.png
deleted file mode 100644
index 7b2ee27..0000000
Binary files a/dist/images/1x/icon/radio_active.png and /dev/null differ
diff --git a/dist/images/1x/icon/radio_disable.png b/dist/images/1x/icon/radio_disable.png
deleted file mode 100644
index c0389fd..0000000
Binary files a/dist/images/1x/icon/radio_disable.png and /dev/null differ
diff --git a/dist/images/1x/icon/radio_disable2.png b/dist/images/1x/icon/radio_disable2.png
deleted file mode 100644
index f5a71f3..0000000
Binary files a/dist/images/1x/icon/radio_disable2.png and /dev/null differ
diff --git a/dist/images/1x/icon/radio_normal.png b/dist/images/1x/icon/radio_normal.png
deleted file mode 100644
index 2388bb8..0000000
Binary files a/dist/images/1x/icon/radio_normal.png and /dev/null differ
diff --git a/dist/images/1x/icon/slider_active.png b/dist/images/1x/icon/slider_active.png
deleted file mode 100644
index a84164f..0000000
Binary files a/dist/images/1x/icon/slider_active.png and /dev/null differ
diff --git a/dist/images/1x/icon/slider_active_small.png b/dist/images/1x/icon/slider_active_small.png
deleted file mode 100644
index 1c29cec..0000000
Binary files a/dist/images/1x/icon/slider_active_small.png and /dev/null differ
diff --git a/dist/images/1x/icon/slider_normal.png b/dist/images/1x/icon/slider_normal.png
deleted file mode 100644
index 8a611ff..0000000
Binary files a/dist/images/1x/icon/slider_normal.png and /dev/null differ
diff --git a/dist/images/1x/icon/slider_normal_small.png b/dist/images/1x/icon/slider_normal_small.png
deleted file mode 100644
index 7bd6fc7..0000000
Binary files a/dist/images/1x/icon/slider_normal_small.png and /dev/null differ
diff --git a/dist/images/1x/icon/tree_collapse_1.png b/dist/images/1x/icon/tree_collapse_1.png
deleted file mode 100644
index dfc427e..0000000
Binary files a/dist/images/1x/icon/tree_collapse_1.png and /dev/null differ
diff --git a/dist/images/1x/icon/tree_collapse_2.png b/dist/images/1x/icon/tree_collapse_2.png
deleted file mode 100644
index 8db0aa7..0000000
Binary files a/dist/images/1x/icon/tree_collapse_2.png and /dev/null differ
diff --git a/dist/images/1x/icon/tree_collapse_3.png b/dist/images/1x/icon/tree_collapse_3.png
deleted file mode 100644
index efbbed9..0000000
Binary files a/dist/images/1x/icon/tree_collapse_3.png and /dev/null differ
diff --git a/dist/images/1x/icon/tree_collapse_4.png b/dist/images/1x/icon/tree_collapse_4.png
deleted file mode 100644
index 743bf8b..0000000
Binary files a/dist/images/1x/icon/tree_collapse_4.png and /dev/null differ
diff --git a/dist/images/1x/icon/tree_expand_1.png b/dist/images/1x/icon/tree_expand_1.png
deleted file mode 100644
index 914e324..0000000
Binary files a/dist/images/1x/icon/tree_expand_1.png and /dev/null differ
diff --git a/dist/images/1x/icon/tree_expand_2.png b/dist/images/1x/icon/tree_expand_2.png
deleted file mode 100644
index 95a8af0..0000000
Binary files a/dist/images/1x/icon/tree_expand_2.png and /dev/null differ
diff --git a/dist/images/1x/icon/tree_expand_3.png b/dist/images/1x/icon/tree_expand_3.png
deleted file mode 100644
index b1c1e94..0000000
Binary files a/dist/images/1x/icon/tree_expand_3.png and /dev/null differ
diff --git a/dist/images/1x/icon/tree_expand_4.png b/dist/images/1x/icon/tree_expand_4.png
deleted file mode 100644
index 6702029..0000000
Binary files a/dist/images/1x/icon/tree_expand_4.png and /dev/null differ
diff --git a/dist/images/1x/icon/tree_vertical_line_1.png b/dist/images/1x/icon/tree_vertical_line_1.png
deleted file mode 100644
index d13fc8d..0000000
Binary files a/dist/images/1x/icon/tree_vertical_line_1.png and /dev/null differ
diff --git a/dist/images/1x/icon/tree_vertical_line_2.png b/dist/images/1x/icon/tree_vertical_line_2.png
deleted file mode 100644
index ebee980..0000000
Binary files a/dist/images/1x/icon/tree_vertical_line_2.png and /dev/null differ
diff --git a/dist/images/1x/icon/tree_vertical_line_3.png b/dist/images/1x/icon/tree_vertical_line_3.png
deleted file mode 100644
index d2f7595..0000000
Binary files a/dist/images/1x/icon/tree_vertical_line_3.png and /dev/null differ
diff --git a/dist/images/1x/icon/tree_vertical_line_4.png b/dist/images/1x/icon/tree_vertical_line_4.png
deleted file mode 100644
index 277d0e5..0000000
Binary files a/dist/images/1x/icon/tree_vertical_line_4.png and /dev/null differ
diff --git a/dist/images/1x/icon/tree_vertical_line_5.png b/dist/images/1x/icon/tree_vertical_line_5.png
deleted file mode 100644
index 85aa1cc..0000000
Binary files a/dist/images/1x/icon/tree_vertical_line_5.png and /dev/null differ
diff --git a/dist/images/2x/background/auto_color.png b/dist/images/2x/background/auto_color.png
deleted file mode 100644
index fc976e4..0000000
Binary files a/dist/images/2x/background/auto_color.png and /dev/null differ
diff --git a/dist/images/2x/background/line_conn.gif b/dist/images/2x/background/line_conn.gif
deleted file mode 100644
index d561d36..0000000
Binary files a/dist/images/2x/background/line_conn.gif and /dev/null differ
diff --git a/dist/images/2x/background/marker.png b/dist/images/2x/background/marker.png
deleted file mode 100644
index 3929bbb..0000000
Binary files a/dist/images/2x/background/marker.png and /dev/null differ
diff --git a/dist/images/2x/background/mask.png b/dist/images/2x/background/mask.png
deleted file mode 100644
index b0a4d40..0000000
Binary files a/dist/images/2x/background/mask.png and /dev/null differ
diff --git a/dist/images/2x/background/trans_color.png b/dist/images/2x/background/trans_color.png
deleted file mode 100644
index 262626f..0000000
Binary files a/dist/images/2x/background/trans_color.png and /dev/null differ
diff --git a/dist/images/2x/background/wheel.png b/dist/images/2x/background/wheel.png
deleted file mode 100644
index 97b343d..0000000
Binary files a/dist/images/2x/background/wheel.png and /dev/null differ
diff --git a/dist/images/2x/icon/check_box_active.png b/dist/images/2x/icon/check_box_active.png
deleted file mode 100644
index f5083b8..0000000
Binary files a/dist/images/2x/icon/check_box_active.png and /dev/null differ
diff --git a/dist/images/2x/icon/check_box_disable.png b/dist/images/2x/icon/check_box_disable.png
deleted file mode 100644
index b6767f3..0000000
Binary files a/dist/images/2x/icon/check_box_disable.png and /dev/null differ
diff --git a/dist/images/2x/icon/check_box_disable2.png b/dist/images/2x/icon/check_box_disable2.png
deleted file mode 100644
index dbc3da4..0000000
Binary files a/dist/images/2x/icon/check_box_disable2.png and /dev/null differ
diff --git a/dist/images/2x/icon/check_box_normal.png b/dist/images/2x/icon/check_box_normal.png
deleted file mode 100644
index cf167a7..0000000
Binary files a/dist/images/2x/icon/check_box_normal.png and /dev/null differ
diff --git a/dist/images/2x/icon/dark/tree_collapse_1.png b/dist/images/2x/icon/dark/tree_collapse_1.png
deleted file mode 100644
index 740b50a..0000000
Binary files a/dist/images/2x/icon/dark/tree_collapse_1.png and /dev/null differ
diff --git a/dist/images/2x/icon/dark/tree_collapse_2.png b/dist/images/2x/icon/dark/tree_collapse_2.png
deleted file mode 100644
index 6b7192f..0000000
Binary files a/dist/images/2x/icon/dark/tree_collapse_2.png and /dev/null differ
diff --git a/dist/images/2x/icon/dark/tree_collapse_3.png b/dist/images/2x/icon/dark/tree_collapse_3.png
deleted file mode 100644
index 172b933..0000000
Binary files a/dist/images/2x/icon/dark/tree_collapse_3.png and /dev/null differ
diff --git a/dist/images/2x/icon/dark/tree_collapse_4.png b/dist/images/2x/icon/dark/tree_collapse_4.png
deleted file mode 100644
index 69eb9c3..0000000
Binary files a/dist/images/2x/icon/dark/tree_collapse_4.png and /dev/null differ
diff --git a/dist/images/2x/icon/dark/tree_expand_1.png b/dist/images/2x/icon/dark/tree_expand_1.png
deleted file mode 100644
index ce1480d..0000000
Binary files a/dist/images/2x/icon/dark/tree_expand_1.png and /dev/null differ
diff --git a/dist/images/2x/icon/dark/tree_expand_2.png b/dist/images/2x/icon/dark/tree_expand_2.png
deleted file mode 100644
index 114dc2f..0000000
Binary files a/dist/images/2x/icon/dark/tree_expand_2.png and /dev/null differ
diff --git a/dist/images/2x/icon/dark/tree_expand_3.png b/dist/images/2x/icon/dark/tree_expand_3.png
deleted file mode 100644
index fb15500..0000000
Binary files a/dist/images/2x/icon/dark/tree_expand_3.png and /dev/null differ
diff --git a/dist/images/2x/icon/dark/tree_expand_4.png b/dist/images/2x/icon/dark/tree_expand_4.png
deleted file mode 100644
index 75c9f23..0000000
Binary files a/dist/images/2x/icon/dark/tree_expand_4.png and /dev/null differ
diff --git a/dist/images/2x/icon/dark/tree_vertical_line_1.png b/dist/images/2x/icon/dark/tree_vertical_line_1.png
deleted file mode 100644
index e88f2a6..0000000
Binary files a/dist/images/2x/icon/dark/tree_vertical_line_1.png and /dev/null differ
diff --git a/dist/images/2x/icon/dark/tree_vertical_line_2.png b/dist/images/2x/icon/dark/tree_vertical_line_2.png
deleted file mode 100644
index 554d8b3..0000000
Binary files a/dist/images/2x/icon/dark/tree_vertical_line_2.png and /dev/null differ
diff --git a/dist/images/2x/icon/dark/tree_vertical_line_3.png b/dist/images/2x/icon/dark/tree_vertical_line_3.png
deleted file mode 100644
index 8e7da8a..0000000
Binary files a/dist/images/2x/icon/dark/tree_vertical_line_3.png and /dev/null differ
diff --git a/dist/images/2x/icon/dark/tree_vertical_line_4.png b/dist/images/2x/icon/dark/tree_vertical_line_4.png
deleted file mode 100644
index 4645b52..0000000
Binary files a/dist/images/2x/icon/dark/tree_vertical_line_4.png and /dev/null differ
diff --git a/dist/images/2x/icon/dark/tree_vertical_line_5.png b/dist/images/2x/icon/dark/tree_vertical_line_5.png
deleted file mode 100644
index 3fd2c56..0000000
Binary files a/dist/images/2x/icon/dark/tree_vertical_line_5.png and /dev/null differ
diff --git a/dist/images/2x/icon/dots.png b/dist/images/2x/icon/dots.png
deleted file mode 100644
index beae59d..0000000
Binary files a/dist/images/2x/icon/dots.png and /dev/null differ
diff --git a/dist/images/2x/icon/half_selected.png b/dist/images/2x/icon/half_selected.png
deleted file mode 100644
index 765aabc..0000000
Binary files a/dist/images/2x/icon/half_selected.png and /dev/null differ
diff --git a/dist/images/2x/icon/icon_down_arrow.png b/dist/images/2x/icon/icon_down_arrow.png
deleted file mode 100644
index 5285f73..0000000
Binary files a/dist/images/2x/icon/icon_down_arrow.png and /dev/null differ
diff --git a/dist/images/2x/icon/loading.gif b/dist/images/2x/icon/loading.gif
deleted file mode 100644
index d04fdd2..0000000
Binary files a/dist/images/2x/icon/loading.gif and /dev/null differ
diff --git a/dist/images/2x/icon/push_down.png b/dist/images/2x/icon/push_down.png
deleted file mode 100644
index 88ef8d5..0000000
Binary files a/dist/images/2x/icon/push_down.png and /dev/null differ
diff --git a/dist/images/2x/icon/push_up.png b/dist/images/2x/icon/push_up.png
deleted file mode 100644
index 040d502..0000000
Binary files a/dist/images/2x/icon/push_up.png and /dev/null differ
diff --git a/dist/images/2x/icon/radio_active.png b/dist/images/2x/icon/radio_active.png
deleted file mode 100644
index e465430..0000000
Binary files a/dist/images/2x/icon/radio_active.png and /dev/null differ
diff --git a/dist/images/2x/icon/radio_disable.png b/dist/images/2x/icon/radio_disable.png
deleted file mode 100644
index dfe0413..0000000
Binary files a/dist/images/2x/icon/radio_disable.png and /dev/null differ
diff --git a/dist/images/2x/icon/radio_disable2.png b/dist/images/2x/icon/radio_disable2.png
deleted file mode 100644
index 8476062..0000000
Binary files a/dist/images/2x/icon/radio_disable2.png and /dev/null differ
diff --git a/dist/images/2x/icon/radio_normal.png b/dist/images/2x/icon/radio_normal.png
deleted file mode 100644
index 0bbd274..0000000
Binary files a/dist/images/2x/icon/radio_normal.png and /dev/null differ
diff --git a/dist/images/2x/icon/slider_active.png b/dist/images/2x/icon/slider_active.png
deleted file mode 100644
index cf361f7..0000000
Binary files a/dist/images/2x/icon/slider_active.png and /dev/null differ
diff --git a/dist/images/2x/icon/slider_active_small.png b/dist/images/2x/icon/slider_active_small.png
deleted file mode 100644
index 1c29cec..0000000
Binary files a/dist/images/2x/icon/slider_active_small.png and /dev/null differ
diff --git a/dist/images/2x/icon/slider_normal.png b/dist/images/2x/icon/slider_normal.png
deleted file mode 100644
index a81cf03..0000000
Binary files a/dist/images/2x/icon/slider_normal.png and /dev/null differ
diff --git a/dist/images/2x/icon/slider_normal_small.png b/dist/images/2x/icon/slider_normal_small.png
deleted file mode 100644
index 7bd6fc7..0000000
Binary files a/dist/images/2x/icon/slider_normal_small.png and /dev/null differ
diff --git a/dist/images/2x/icon/tree_collapse_1.png b/dist/images/2x/icon/tree_collapse_1.png
deleted file mode 100644
index f2d63e2..0000000
Binary files a/dist/images/2x/icon/tree_collapse_1.png and /dev/null differ
diff --git a/dist/images/2x/icon/tree_collapse_2.png b/dist/images/2x/icon/tree_collapse_2.png
deleted file mode 100644
index 4f87550..0000000
Binary files a/dist/images/2x/icon/tree_collapse_2.png and /dev/null differ
diff --git a/dist/images/2x/icon/tree_collapse_3.png b/dist/images/2x/icon/tree_collapse_3.png
deleted file mode 100644
index 88cfeba..0000000
Binary files a/dist/images/2x/icon/tree_collapse_3.png and /dev/null differ
diff --git a/dist/images/2x/icon/tree_collapse_4.png b/dist/images/2x/icon/tree_collapse_4.png
deleted file mode 100644
index 31c33b9..0000000
Binary files a/dist/images/2x/icon/tree_collapse_4.png and /dev/null differ
diff --git a/dist/images/2x/icon/tree_expand_1.png b/dist/images/2x/icon/tree_expand_1.png
deleted file mode 100644
index 2fb7e3b..0000000
Binary files a/dist/images/2x/icon/tree_expand_1.png and /dev/null differ
diff --git a/dist/images/2x/icon/tree_expand_2.png b/dist/images/2x/icon/tree_expand_2.png
deleted file mode 100644
index 9a26edb..0000000
Binary files a/dist/images/2x/icon/tree_expand_2.png and /dev/null differ
diff --git a/dist/images/2x/icon/tree_expand_3.png b/dist/images/2x/icon/tree_expand_3.png
deleted file mode 100644
index 6f6b5a7..0000000
Binary files a/dist/images/2x/icon/tree_expand_3.png and /dev/null differ
diff --git a/dist/images/2x/icon/tree_expand_4.png b/dist/images/2x/icon/tree_expand_4.png
deleted file mode 100644
index c202c72..0000000
Binary files a/dist/images/2x/icon/tree_expand_4.png and /dev/null differ
diff --git a/dist/images/2x/icon/tree_vertical_line_1.png b/dist/images/2x/icon/tree_vertical_line_1.png
deleted file mode 100644
index e88f2a6..0000000
Binary files a/dist/images/2x/icon/tree_vertical_line_1.png and /dev/null differ
diff --git a/dist/images/2x/icon/tree_vertical_line_2.png b/dist/images/2x/icon/tree_vertical_line_2.png
deleted file mode 100644
index 554d8b3..0000000
Binary files a/dist/images/2x/icon/tree_vertical_line_2.png and /dev/null differ
diff --git a/dist/images/2x/icon/tree_vertical_line_3.png b/dist/images/2x/icon/tree_vertical_line_3.png
deleted file mode 100644
index 8e7da8a..0000000
Binary files a/dist/images/2x/icon/tree_vertical_line_3.png and /dev/null differ
diff --git a/dist/images/2x/icon/tree_vertical_line_4.png b/dist/images/2x/icon/tree_vertical_line_4.png
deleted file mode 100644
index 4645b52..0000000
Binary files a/dist/images/2x/icon/tree_vertical_line_4.png and /dev/null differ
diff --git a/dist/images/2x/icon/tree_vertical_line_5.png b/dist/images/2x/icon/tree_vertical_line_5.png
deleted file mode 100644
index 3fd2c56..0000000
Binary files a/dist/images/2x/icon/tree_vertical_line_5.png and /dev/null differ
diff --git a/dist/resource/ZeroClipboard.swf b/dist/resource/ZeroClipboard.swf
deleted file mode 100644
index 13bf8e3..0000000
Binary files a/dist/resource/ZeroClipboard.swf and /dev/null differ
diff --git a/index.html b/index.html
deleted file mode 100644
index fd39c4e..0000000
--- a/index.html
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-
- FineUI-Start
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/package.json b/package.json
index 3c92d91..4888ecd 100644
--- a/package.json
+++ b/package.json
@@ -1,35 +1,77 @@
{
- "name": "fineui-start",
+ "name": "fineui-starter",
"version": "1.0.0",
- "description": "fineui start",
- "main": "index.js",
+ "description": "fineui starter",
+ "scripts": {
+ "start": "webpack-dev-server -p --progress --host 127.0.0.1 --config=webpack/webpack.dev.js --mode development",
+ "build": "webpack -p --progress --config=webpack/webpack.prod.js --mode production",
+ "test": "jest",
+ "eslint": "eslint \"./**/*.{js,ts,jsx,tsx}\"",
+ "eslint:fix": "eslint \"./**/*.{js,ts,tsx}\" --fix",
+ "stylelint": "stylelint \"./**/*.less\" --syntax less",
+ "stylelint:fix": "stylelint \"./**/*.less\" --syntax less --fix",
+ "prettier": "prettier --write \"./**/*.{js,ts,jsx,tsx,less}\""
+ },
"dependencies": {
- "fineui": "^2.0.0"
+ "@fui/core": "^2.0.0"
},
"devDependencies": {
- "chai": "4.1.2",
- "eslint": "4.12.0",
- "eslint-plugin-prettier": "2.3.1",
- "express": "4.15.2",
- "grunt": "1.0.1",
- "grunt-contrib-concat": "1.0.1",
- "grunt-contrib-connect": "1.0.2",
- "grunt-contrib-cssmin": "1.0.1",
- "grunt-contrib-jshint": "1.0.0",
- "grunt-contrib-less": "1.4.1",
- "grunt-contrib-uglify": "1.0.1",
- "grunt-contrib-watch": "1.0.0",
- "lodash": "4.17.10",
- "less": "^4.1.2",
- "mocha": "4.0.1",
- "open": "0.0.5",
- "prettier": "1.8.2"
- },
- "scripts": {
- "grunt": "grunt",
- "uglify": "grunt min",
- "eslint": "eslint src --fix"
+ "@fui/babel-preset-fineui": "^1.0.0",
+ "@fui/eslint-plugin": "^1.0.7",
+ "@fui/typescript-configs": "^1.0.1",
+ "@types/jest": "^24.0.11",
+ "autoprefixer": "9.6.1",
+ "babel-loader": "8.0.6",
+ "babel-plugin-transform-runtime": "6.23.0",
+ "cross-env": "6.0.0",
+ "css-loader": "3.0.0",
+ "eslint": "^7.30.0",
+ "eslint-plugin-jest": "^24.3.6",
+ "eslint-plugin-react": "^7.24.0",
+ "fork-ts-checker-webpack-plugin": "1.4.3",
+ "html-webpack-plugin": "3.2.0",
+ "husky": "^7.0.1",
+ "jest": "24.7.1",
+ "jest-css-modules-transform": "2.5.0",
+ "jest-environment-jsdom": "24.7.1",
+ "jest-snapshot": "23.6.0",
+ "less": "^3.11.3",
+ "less-loader": "5.0.0",
+ "lint-staged": "^11.0.1",
+ "mini-css-extract-plugin": "0.7.0",
+ "npm-run-all": "4.1.5",
+ "optimize-css-assets-webpack-plugin": "5.0.3",
+ "postcss-loader": "3.0.0",
+ "postcss-simple-vars": "5.0.2",
+ "prettier": "^2.3.2",
+ "source-map-loader": "0.2.4",
+ "style-loader": "0.23.1",
+ "stylelint": "^13.13.1",
+ "stylelint-config-prettier": "^8.0.2",
+ "stylelint-config-standard": "^22.0.0",
+ "stylelint-prettier": "^1.2.0",
+ "typescript": "3.9.2",
+ "uglifyjs-webpack-plugin": "2.2.0",
+ "webpack": "4.35.2",
+ "webpack-cli": "3.3.5",
+ "webpack-dev-server": "3.7.2",
+ "webpack-merge": "4.2.1"
},
"author": "",
- "license": "MIT"
+ "license": "MIT",
+ "husky": {
+ "hooks": {
+ "pre-commit": "lint-staged"
+ }
+ },
+ "lint-staged": {
+ "*.{js,ts}": [
+ "eslint --fix",
+ "git add"
+ ],
+ "*.less": [
+ "stylelint --fix",
+ "git add"
+ ]
+ }
}
diff --git a/screenshorts/todolist.gif b/screenshorts/todolist.gif
deleted file mode 100644
index 15a465e..0000000
Binary files a/screenshorts/todolist.gif and /dev/null differ
diff --git a/screenshots/demo.jpeg b/screenshots/demo.jpeg
new file mode 100644
index 0000000..94decdb
Binary files /dev/null and b/screenshots/demo.jpeg differ
diff --git a/src/common/app.less b/src/common/app.less
deleted file mode 100644
index 491bdf4..0000000
--- a/src/common/app.less
+++ /dev/null
@@ -1,14 +0,0 @@
-@import "../../fineui/src/less/resource/app";
-
-//定义自己的全局样式配置
-#body {
-}
-
-#wrapper {
- position: absolute;
- left: 0;
- right: 0;
- top: 0;
- bottom: 0;
- .overflow-hidden();
-}
\ No newline at end of file
diff --git a/src/common/background.less b/src/common/background.less
deleted file mode 100644
index f2dfaa9..0000000
--- a/src/common/background.less
+++ /dev/null
@@ -1,3 +0,0 @@
-@import "../../fineui/src/less/resource/background";
-
-//定义自己的背景
\ No newline at end of file
diff --git a/src/common/font.less b/src/common/font.less
deleted file mode 100644
index 9459171..0000000
--- a/src/common/font.less
+++ /dev/null
@@ -1,3 +0,0 @@
-@import "../../fineui/src/less/resource/font";
-
-//定义自己的字体
diff --git a/src/common/icon.less b/src/common/icon.less
deleted file mode 100644
index 610fc21..0000000
--- a/src/common/icon.less
+++ /dev/null
@@ -1,3 +0,0 @@
-@import "../../fineui/src/less/resource/icon";
-
-//定义自己的icon
\ No newline at end of file
diff --git a/src/css/header/header.css b/src/css/header/header.css
deleted file mode 100644
index 051de4b..0000000
--- a/src/css/header/header.css
+++ /dev/null
@@ -1,16 +0,0 @@
-/**
- 列表项的less,其中用到了部分FineUI提供的字号,颜色常量,还有border-radius,box-shadow方法等.请选择性使用.不强制要求
- */
-.my-todolist-header {
- background-color: #3d4d66;
-}
-.my-todolist-header .my-todolist-title {
- font-size: 22px;
- color: #FFF;
-}
-.my-todolist-header .my-todolist-header-editor {
- background-color: #FFF;
- -webkit-border-radius: 5px;
- -moz-border-radius: 5px;
- border-radius: 5px;
-}
diff --git a/src/css/index.css b/src/css/index.css
deleted file mode 100644
index e69de29..0000000
diff --git a/src/css/list/list.css b/src/css/list/list.css
deleted file mode 100644
index 7635093..0000000
--- a/src/css/list/list.css
+++ /dev/null
@@ -1,14 +0,0 @@
-/**
- 列表项的less,其中用到了部分FineUI提供的字号,颜色常量,还有border-radius方法等.请选择性使用.不强制要求
- */
-.my-todolist-list .my-todolist-list-text {
- font-size: 16px;
- font-weight: bold;
-}
-.my-todolist-list .my-todolist-list-count-container {
- -webkit-border-radius: 10px;
- -moz-border-radius: 10px;
- border-radius: 10px;
- background-color: #3d4d66;
- color: #ffffff;
-}
diff --git a/src/css/main.css b/src/css/main.css
deleted file mode 100644
index 214161e..0000000
--- a/src/css/main.css
+++ /dev/null
@@ -1,6 +0,0 @@
-/**
- 列表项的less,其中用到了部分FineUI提供的字号,颜色常量,还有border-radius,box-shadow方法等.请选择性使用.不强制要求
- */
-.my-todolist-background {
- background-color: #f7f8fa;
-}
diff --git a/src/css/nav/nav.css b/src/css/nav/nav.css
deleted file mode 100644
index e69de29..0000000
diff --git a/src/index.js b/src/index.js
deleted file mode 100644
index d906090..0000000
--- a/src/index.js
+++ /dev/null
@@ -1,7 +0,0 @@
-!(function () {
- // 将todolist组件挂载到#wrapper上.
- BI.createWidget({
- type: "my.todolist",
- element: "#wrapper"
- });
-})();
diff --git a/src/index.less b/src/index.less
index 9814e1a..0ec9d28 100644
--- a/src/index.less
+++ b/src/index.less
@@ -1,5 +1,9 @@
-@import "fineui/src/less/index";
+@import './less/index.less';
-@fontUrl: 'font/'; //字体存放路径
-@imageUrl: 'images/1x/'; //图片的基本地址
-@image2xUrl: 'images/2x/'; //2x图片的基本地址
\ No newline at end of file
+#wrapper {
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: 0;
+ bottom: 0;
+}
diff --git a/src/index.ts b/src/index.ts
new file mode 100644
index 0000000..267debf
--- /dev/null
+++ b/src/index.ts
@@ -0,0 +1,7 @@
+import Layout from '@app/layout/layout';
+import './index.less';
+
+BI.createWidget({
+ type: Layout.xtype,
+ element: '#wrapper',
+});
diff --git a/src/less/index.less b/src/less/index.less
new file mode 100644
index 0000000..4dff600
--- /dev/null
+++ b/src/less/index.less
@@ -0,0 +1,7 @@
+@import '~@fui/core/src/less/index.less';
+@import '~@fui/core/src/less/image.less';
+@import './var.less';
+@import './resource/app.less';
+@import './resource/background.less';
+@import './resource/font.less';
+@import './resource/custom.less';
diff --git a/src/less/lib/background.less b/src/less/lib/background.less
new file mode 100644
index 0000000..09e9a54
--- /dev/null
+++ b/src/less/lib/background.less
@@ -0,0 +1,3 @@
+// 背景图片
+
+@background-logo: 'logo.png';
diff --git a/src/less/lib/constant.less b/src/less/lib/constant.less
new file mode 100644
index 0000000..d034086
--- /dev/null
+++ b/src/less/lib/constant.less
@@ -0,0 +1,3 @@
+// 常量
+
+@color-app-primary: yellowgreen;
diff --git a/src/less/lib/font.less b/src/less/lib/font.less
new file mode 100644
index 0000000..6e91cb0
--- /dev/null
+++ b/src/less/lib/font.less
@@ -0,0 +1,4 @@
+// 字体图标,请参考:http://fanruan.design/#/docs/spec/icon
+
+@font-directory: 'e748';
+@font-management: 'e777';
diff --git a/src/less/resource/app.less b/src/less/resource/app.less
new file mode 100644
index 0000000..b344a50
--- /dev/null
+++ b/src/less/resource/app.less
@@ -0,0 +1,47 @@
+@import '../lib/constant.less';
+
+// 左边框式列表选项
+.app-list-item-border-left {
+ border-left: 4px solid transparent;
+ border-right: 4px solid transparent;
+ color: @color-bi-text-gray;
+ &:hover,
+ &.hover {
+ color: @color-bi-text-white;
+ }
+ &.active,
+ &:active {
+ border-left: 4px solid white;
+ color: @color-bi-text-white;
+ }
+ &.disabled {
+ &,
+ &:hover,
+ &:active {
+ color: @color-bi-text-disabled !important;
+ }
+ }
+}
+
+// 下划线式列表选项
+.app-list-item-underline {
+ .box-sizing(border-box);
+ &:hover,
+ &.hover {
+ font-weight: bold;
+ }
+ &.active,
+ &:active {
+ border-bottom: 2px solid @color-app-primary;
+ font-weight: bold;
+ color: @color-app-primary;
+ background-color: @color-bi-background-default;
+ }
+ &.disabled {
+ &,
+ &:hover,
+ &:active {
+ color: @color-bi-text-disabled !important;
+ }
+ }
+}
diff --git a/src/less/resource/background.less b/src/less/resource/background.less
new file mode 100644
index 0000000..af3c91d
--- /dev/null
+++ b/src/less/resource/background.less
@@ -0,0 +1,3 @@
+@import '../lib/background.less';
+
+.background(logo-background, @background-logo);
diff --git a/src/less/resource/custom.less b/src/less/resource/custom.less
new file mode 100644
index 0000000..9a3823d
--- /dev/null
+++ b/src/less/resource/custom.less
@@ -0,0 +1,40 @@
+@import '../lib/constant.less';
+
+.bi-high-light {
+ color: @color-app-primary;
+}
+
+.bi-high-light-background {
+ background-color: @color-app-primary;
+}
+
+.bi-list-item-select {
+ &:hover,
+ &.hover {
+ .background-color(@color-app-primary, 10%);
+ }
+ &:active,
+ &.active {
+ color: white;
+ background-color: @color-app-primary;
+ }
+}
+
+.bi-list-item {
+ &:hover,
+ &.hover {
+ .background-color(@color-app-primary, 10%);
+ }
+}
+
+.bi-list-item-active2 {
+ &:hover,
+ &.hover {
+ .background-color(@color-app-primary, 10%);
+ }
+ &:active,
+ &.active {
+ color: white;
+ background-color: @color-app-primary;
+ }
+}
diff --git a/src/less/resource/font.less b/src/less/resource/font.less
new file mode 100644
index 0000000..0b557c6
--- /dev/null
+++ b/src/less/resource/font.less
@@ -0,0 +1,4 @@
+@import '../lib/font.less';
+
+.font(directory-font, @font-directory);
+.font(management-font, @font-management);
diff --git a/src/less/var.less b/src/less/var.less
new file mode 100644
index 0000000..cfd1607
--- /dev/null
+++ b/src/less/var.less
@@ -0,0 +1,3 @@
+@fontUrl: 'assets/font/'; // 字体的存放路径
+@imageUrl: 'assets/images/1x/'; // 1x图片的基本路径
+@image2xUrl: 'assets/images/2x/'; // 2x图片的基本路径
diff --git a/src/modules/app/layout/content/content.less b/src/modules/app/layout/content/content.less
new file mode 100644
index 0000000..e4e25e3
--- /dev/null
+++ b/src/modules/app/layout/content/content.less
@@ -0,0 +1,5 @@
+@import '~@/index.less';
+
+.app-layout-content {
+ background-color: @background-color-default;
+}
diff --git a/src/modules/app/layout/content/content.model.ts b/src/modules/app/layout/content/content.model.ts
new file mode 100644
index 0000000..5908489
--- /dev/null
+++ b/src/modules/app/layout/content/content.model.ts
@@ -0,0 +1,41 @@
+import { model, Model } from '@core/decorator';
+import LayoutModel from '../layout.model';
+
+@model()
+export default class LayoutContentModel extends Model<{
+ types: {
+ openedCards: LayoutModel['TYPE']['openedCards'];
+ activeCard: LayoutModel['TYPE']['activeCard'];
+ };
+ context: LayoutContentModel['context'];
+}> {
+ static xtype = 'app.model.layout_content';
+
+ public context = ['openedCards', 'activeCard'];
+
+ public actions = {
+ /**
+ * 切换card
+ * @param value 要切换的card的key
+ */
+ changeCard: (value: string) => {
+ this.model.activeCard = value;
+ },
+ /**
+ * 关闭card
+ * @param value 要关闭的card的key
+ */
+ closeCard: (value: string) => {
+ // 获取要关闭card在已打开cards中的下标
+ const index = BI.indexOf(this.model.openedCards, value);
+ // 如果要关闭card就是当前card,则自动切换至其它card
+ if (value === this.model.activeCard) {
+ const [prevIndex, nextIndex] = [index - 1, index + 1];
+ const [prevCard, nextCard] = [this.model.openedCards[prevIndex], this.model.openedCards[nextIndex]];
+ this.model.activeCard = nextCard || prevCard;
+ }
+ // 然后从已打开cards中移除目标card
+ BI.removeAt(this.model.openedCards, index);
+ },
+ };
+}
diff --git a/src/modules/app/layout/content/content.tsx b/src/modules/app/layout/content/content.tsx
new file mode 100644
index 0000000..9ce508e
--- /dev/null
+++ b/src/modules/app/layout/content/content.tsx
@@ -0,0 +1,156 @@
+import { Tab } from '@fui/core';
+import { shortcut, store } from '@core/decorator';
+import { Nav, NavItemInfo, NavItemStyle } from '@base/nav/nav';
+import { RouteType, RouteInfo, ROUTE_INFOS } from '@/routes';
+import LayoutContentModel from './content.model';
+import LayoutConstant from '../layout.constant';
+import './content.less';
+
+// 路由信息value与页面内容的映射关系,可以理解成一个经过faltten的路由信息集合
+export interface ContentsMap {
+ [key: string]: NavItemInfo & Required> & Partial>;
+}
+
+// 路由类别与导航选项风格的映射关系
+export const ROUTE_TYPE_NAV_ITEM_STYLE_MAP = {
+ [RouteType.Single]: NavItemStyle.Block,
+ [RouteType.Multiple]: NavItemStyle.Underline,
+};
+
+/**
+ * 应用布局的内容区域,包括导航栏和实际内容区域
+ */
+@shortcut()
+@store(LayoutContentModel)
+export class LayoutContent extends BI.Widget {
+ static xtype = 'app.layout_content';
+
+ public props = {
+ baseCls: 'app-layout-content',
+ };
+
+ public watch = {
+ openedCards: (values: string[]) => {
+ this.udpateNavItemInfos(values);
+ this.updateNavValue();
+ },
+ activeCard: (value: string) => {
+ this.updateNavItemStyle(value);
+ this.updateNavValue();
+ this.tabRef.setSelect(value);
+ },
+ };
+
+ private model: LayoutContentModel['model'];
+ private store: LayoutContentModel['store'];
+ private navRef: Nav;
+ private tabRef: Tab;
+ private contentsMap: ContentsMap;
+
+ /**
+ * 初始化ContentsMap
+ * @param routeInfos 路由信息集合
+ * @param closable 当前路由信息对应的导航选项是否可关闭
+ */
+ private initContentsMap(routeInfos: RouteInfo[], closable: boolean) {
+ if (!this.contentsMap) {
+ this.contentsMap = {};
+ }
+ for (const routeInfo of routeInfos) {
+ const { type, value, text, card: Card, cards, children } = routeInfo;
+ const isHome = type !== undefined;
+ const newClosable = type === RouteType.Single ? true : closable;
+ this.contentsMap[value] = {
+ type,
+ value,
+ text: isHome ? '首页' : text,
+ closable: isHome ? false : newClosable,
+ card: Card ? : ,
+ };
+ if (children) {
+ this.initContentsMap(children, newClosable);
+ }
+ if (cards) {
+ this.initContentsMap(cards, newClosable);
+ }
+ }
+ }
+
+ /**
+ * 更新导航组件的itemInfos
+ * @param values 当前打卡的卡片们的values
+ */
+ private udpateNavItemInfos(values: string[]) {
+ const itemInfos = BI.map(values, (_index, value) => this.contentsMap[value]);
+ this.navRef.setItemInfos(itemInfos);
+ }
+
+ /**
+ * 更新导航组件的itemStyle
+ * @param value 当前激活卡片的value
+ */
+ private updateNavItemStyle(value: string) {
+ const { type } = this.contentsMap[value];
+ if (type !== undefined) {
+ const itemStyle = ROUTE_TYPE_NAV_ITEM_STYLE_MAP[type];
+ this.navRef.setItemStyle(itemStyle);
+ }
+ }
+
+ /**
+ * 根据当前激活卡片的value更新导航组件的值
+ */
+ private updateNavValue() {
+ this.navRef.setValue(this.model.activeCard);
+ }
+
+ public init() {
+ this.initContentsMap([{ value: '', text: 'blank', icon: '' }, ...ROUTE_INFOS], false);
+ }
+
+ public render() {
+ const { NAV_HEIGHT } = LayoutConstant;
+ const navValue = ROUTE_INFOS[0].value;
+ const navItemInfo = this.contentsMap[navValue];
+ const navItemType = navItemInfo.type as RouteType;
+ const navItemStyle = ROUTE_TYPE_NAV_ITEM_STYLE_MAP[navItemType];
+
+ return (
+
+
+ );
+ }
+}
diff --git a/src/modules/app/layout/header/header.less b/src/modules/app/layout/header/header.less
new file mode 100644
index 0000000..a84fa3e
--- /dev/null
+++ b/src/modules/app/layout/header/header.less
@@ -0,0 +1,17 @@
+@import '~@/index.less';
+
+.app-layout-header {
+ .box-shadow(0 0 10px, rgba(@background-color-black, 10%));
+ .logo {
+ }
+ .title {
+ font-size: @font-size-16;
+ }
+ .user {
+ .avatar {
+ border-radius: 100%;
+ }
+ .name {
+ }
+ }
+}
diff --git a/src/modules/app/layout/header/header.tsx b/src/modules/app/layout/header/header.tsx
new file mode 100644
index 0000000..b684c2e
--- /dev/null
+++ b/src/modules/app/layout/header/header.tsx
@@ -0,0 +1,59 @@
+import { shortcut } from '@core/decorator';
+import LayoutConstant from '../layout.constant';
+import './header.less';
+
+// 测试用的用户信息
+const userInfo = {
+ name: 'Finer',
+ avatarSrc: 'https://code.fanruan.com/img/gitea-sm.png',
+};
+
+/**
+ * 应用布局的顶栏,包括应用logo、标题和用户信息
+ */
+@shortcut()
+export class LayoutHeader extends BI.Widget {
+ static xtype = 'app.layout_header';
+
+ public props = {
+ baseCls: 'app-layout-header',
+ };
+
+ public render() {
+ const { APP_TITLE_TEXT, MAIN_MENU_WIDTH, HEADER_HEIGHT } = LayoutConstant;
+
+ return (
+
+
+ ,
+ ,
+ ],
+ right: [
+ {
+ BI.Msg.toast(`您点击了第一个图标`);
+ }}
+ />,
+ {
+ BI.Msg.toast(`您点击了第二个图标`);
+ }}
+ />,
+
+
+
+ ,
+ ],
+ }}
+ />
+ );
+ }
+}
diff --git a/src/modules/app/layout/layout.constant.ts b/src/modules/app/layout/layout.constant.ts
new file mode 100644
index 0000000..506067d
--- /dev/null
+++ b/src/modules/app/layout/layout.constant.ts
@@ -0,0 +1,7 @@
+export default {
+ APP_TITLE_TEXT: 'FineBI 商业智能', // 应用标题文字
+ MAIN_MENU_WIDTH: 72, // 一级菜单宽度
+ SUB_MENU_WIDTH: 208, // 二级菜单宽度
+ HEADER_HEIGHT: 40, // 顶栏高度
+ NAV_HEIGHT: 36, // 导航栏高度
+};
diff --git a/src/modules/app/layout/layout.less b/src/modules/app/layout/layout.less
new file mode 100644
index 0000000..9f30789
--- /dev/null
+++ b/src/modules/app/layout/layout.less
@@ -0,0 +1,16 @@
+@import '~@/index.less';
+
+.app-layout {
+ .app-layout-header {
+ .z-index-layer(2);
+ }
+ .app-layout-body {
+ .z-index-layer(1);
+ .app-layout-sider {
+ .z-index-layer(2);
+ }
+ .app-layout-content {
+ .z-index-layer(1);
+ }
+ }
+}
diff --git a/src/modules/app/layout/layout.model.ts b/src/modules/app/layout/layout.model.ts
new file mode 100644
index 0000000..03f357b
--- /dev/null
+++ b/src/modules/app/layout/layout.model.ts
@@ -0,0 +1,21 @@
+import { model, Model } from '@core/decorator';
+import { ROUTE_INFOS } from '@/routes';
+
+@model()
+export default class LayoutModel extends Model {
+ static xtype = 'app.model.layout';
+
+ public childContext = ['openedCards', 'activeCard'];
+
+ public state(): LayoutModelState {
+ return {
+ openedCards: [ROUTE_INFOS[0].value],
+ activeCard: ROUTE_INFOS[0].value,
+ };
+ }
+}
+
+interface LayoutModelState {
+ openedCards: string[]; // 打开卡片的key的集合
+ activeCard: string; // 当前卡片的key
+}
diff --git a/src/modules/app/layout/layout.tsx b/src/modules/app/layout/layout.tsx
new file mode 100644
index 0000000..81a1ad0
--- /dev/null
+++ b/src/modules/app/layout/layout.tsx
@@ -0,0 +1,34 @@
+import { shortcut, store } from '@core/decorator';
+import { LayoutHeader } from './header/header';
+import { LayoutSider } from './sider/sider';
+import { LayoutContent } from './content/content';
+import LayoutConstant from './layout.constant';
+import LayoutModel from './layout.model';
+import './layout.less';
+
+/**
+ * 应用布局
+ */
+@shortcut()
+@store(LayoutModel)
+export default class Layout extends BI.Widget {
+ static xtype = 'app.layout';
+
+ public props = {
+ baseCls: 'app-layout',
+ };
+
+ public render() {
+ const { HEADER_HEIGHT, MAIN_MENU_WIDTH, SUB_MENU_WIDTH } = LayoutConstant;
+
+ return (
+
+
+
+
+
+
+
+ );
+ }
+}
diff --git a/src/modules/app/layout/sider/sider.less b/src/modules/app/layout/sider/sider.less
new file mode 100644
index 0000000..713d0df
--- /dev/null
+++ b/src/modules/app/layout/sider/sider.less
@@ -0,0 +1,10 @@
+@import '~@/index.less';
+
+.app-layout-sider {
+ .app-layout-sider-main-menu {
+ // background-color: @background-color-black;
+ background-color: darken(@color-app-primary, 25%);
+ }
+ .app-layout-sider-sub-menu {
+ }
+}
diff --git a/src/modules/app/layout/sider/sider.model.ts b/src/modules/app/layout/sider/sider.model.ts
new file mode 100644
index 0000000..21c7019
--- /dev/null
+++ b/src/modules/app/layout/sider/sider.model.ts
@@ -0,0 +1,43 @@
+import { model, Model } from '@core/decorator';
+import LayoutModel from '../layout.model';
+
+@model()
+export default class LayoutSiderModel extends Model<{
+ types: {
+ openedCards: LayoutModel['TYPE']['openedCards'];
+ activeCard: LayoutModel['TYPE']['activeCard'];
+ };
+ context: LayoutSiderModel['context'];
+}> {
+ static xtype = 'app.model.layout_sider';
+
+ public context = ['openedCards', 'activeCard'];
+
+ public actions = {
+ /**
+ * 用于RouteType为Single时打开单个card
+ * @param value 要打开的card
+ */
+ openSingleCard: (value: string) => {
+ if (BI.indexOf(this.model.openedCards, value) === -1) {
+ this.model.openedCards.push(value);
+ }
+ this.model.activeCard = value;
+ },
+ /**
+ * 用于RouteType为Multiple时打开多个card
+ * @param values 要打开的card们
+ */
+ openMultipleCards: (values: string[]) => {
+ this.model.openedCards = values;
+ this.model.activeCard = values[0];
+ },
+ /**
+ * 用于切换一级菜单时关闭所有cards
+ */
+ closeAllCards: () => {
+ this.model.openedCards = [];
+ this.model.activeCard = '';
+ },
+ };
+}
diff --git a/src/modules/app/layout/sider/sider.tsx b/src/modules/app/layout/sider/sider.tsx
new file mode 100644
index 0000000..3cb92cf
--- /dev/null
+++ b/src/modules/app/layout/sider/sider.tsx
@@ -0,0 +1,119 @@
+import { shortcut, store } from '@core/decorator';
+import { Menu, MenuItemInfo, MenuItemStyle } from '@base/menu/menu';
+import { RouteType, RouteInfo, ROUTE_INFOS } from '@/routes';
+import LayoutSiderModel from './sider.model';
+import LayoutConstant from '../layout.constant';
+import './sider.less';
+
+/**
+ * 应用布局的侧栏,包括一级菜单和二级菜单
+ */
+@shortcut()
+@store(LayoutSiderModel)
+export class LayoutSider extends BI.Widget {
+ static xtype = 'app.layout_sider';
+
+ public props = {
+ baseCls: 'app-layout-sider',
+ };
+
+ public watch = {
+ openedCards: (values: string[]) => {
+ this.updateSubMenuValueByOpenedCardChange(values);
+ },
+ activeCard: (value: string) => {
+ this.updateSubMenuValueByActiveCardChange(value);
+ },
+ };
+
+ private model: LayoutSiderModel['model'];
+ private store: LayoutSiderModel['store'];
+ private subMenuRef: Menu;
+
+ /**
+ * 更新二级菜单的itemInfos
+ * @param value 当前选中的一级菜单选项的value值
+ */
+ private updateSubMenuItemInfos(value: string) {
+ const mainMenuItemInfo = BI.find(ROUTE_INFOS, { value }) as RouteInfo;
+ const subMenuItemInfos = mainMenuItemInfo.children as MenuItemInfo[];
+ this.subMenuRef.setItemInfos(subMenuItemInfos);
+ }
+
+ /**
+ * 更新二级菜单的value
+ */
+ private updateSubMenuValueByOpenedCardChange(values: string[]) {
+ // console.log('openedcard change', values);
+ const mainRouteInfo = BI.find(ROUTE_INFOS, { value: values[0] });
+ if (mainRouteInfo && mainRouteInfo.type === RouteType.Single && values.length === 1) {
+ this.subMenuRef.setValue('');
+ }
+ }
+
+ /**
+ * 更新二级菜单的value
+ */
+ private updateSubMenuValueByActiveCardChange(value: string) {
+ // console.log('activecard change', value);
+ const mainRouteInfo = BI.find(ROUTE_INFOS, { value: this.model.openedCards[0] });
+ if (mainRouteInfo && mainRouteInfo.type === RouteType.Single) {
+ this.subMenuRef.setValue(value);
+ }
+ }
+
+ public render() {
+ const { MAIN_MENU_WIDTH, SUB_MENU_WIDTH } = LayoutConstant;
+ const mainMenuItemInfos = BI.map(ROUTE_INFOS, (_index, routeInfo) => {
+ const { value, text, icon } = routeInfo;
+
+ return {
+ value,
+ text,
+ icon,
+ };
+ });
+ const subMenuItemInfos = ROUTE_INFOS[0].children;
+
+ return (
+
+ {/* 一级菜单 */}
+
+ );
+ }
+}
diff --git a/src/modules/base/menu/item/item.less b/src/modules/base/menu/item/item.less
new file mode 100644
index 0000000..13aa247
--- /dev/null
+++ b/src/modules/base/menu/item/item.less
@@ -0,0 +1,16 @@
+@import '~@/index.less';
+
+.app-base-menu-item {
+ &.app-base-menu-item-main {
+ .icon {
+ }
+ .text {
+ }
+ }
+ &.app-base-menu-item-sub {
+ .icon {
+ }
+ .text {
+ }
+ }
+}
diff --git a/src/modules/base/menu/item/item.tsx b/src/modules/base/menu/item/item.tsx
new file mode 100644
index 0000000..4fe95b5
--- /dev/null
+++ b/src/modules/base/menu/item/item.tsx
@@ -0,0 +1,71 @@
+import { Widget, BasicButton } from '@fui/core';
+import { shortcut } from '@core/decorator';
+import { MenuItemStyle } from '../menu';
+import './item.less';
+
+/**
+ * 菜单选项
+ */
+@shortcut()
+export class MenuItem extends BI.BasicButton {
+ static xtype = 'app.base.menu_item';
+
+ public props: MenuItemProps & BasicButton['props'] = {
+ baseCls: 'app-base-menu-item',
+ value: '',
+ text: '',
+ icon: '',
+ level: 0,
+ style: MenuItemStyle.Main,
+ };
+
+ /**
+ * 创建Main风格的菜单选项组件
+ * @returns 创建的菜单选项组件
+ */
+ private createMainMenuItem(): Widget {
+ const { text, icon } = this.options;
+
+ return (
+
+
+
+
+ );
+ }
+
+ /**
+ * 创建Sub风格的菜单选项组件
+ * @returns 创建的菜单选项组件
+ */
+ private createSubMenuItem() {
+ const { text, icon, level } = this.options;
+
+ return (
+
+
+
+
+ );
+ }
+
+ public render() {
+ const { style } = this.options;
+ // 菜单选项与组件创建函数的映射关系
+ const menuItemStyleCreatorsMap = {
+ [MenuItemStyle.Main]: this.createMainMenuItem.bind(this),
+ [MenuItemStyle.Sub]: this.createSubMenuItem.bind(this),
+ };
+
+ return menuItemStyleCreatorsMap[style]();
+ }
+}
+
+interface MenuItemProps {
+ baseCls: string;
+ value: string;
+ text: string;
+ icon: string;
+ level: number;
+ style: MenuItemStyle;
+}
diff --git a/src/modules/base/menu/menu.less b/src/modules/base/menu/menu.less
new file mode 100644
index 0000000..db66d7f
--- /dev/null
+++ b/src/modules/base/menu/menu.less
@@ -0,0 +1,4 @@
+@import '~@/index.less';
+
+.app-base-menu {
+}
diff --git a/src/modules/base/menu/menu.tsx b/src/modules/base/menu/menu.tsx
new file mode 100644
index 0000000..58d34a4
--- /dev/null
+++ b/src/modules/base/menu/menu.tsx
@@ -0,0 +1,125 @@
+import { Widget, CustomTree } from '@fui/core';
+import { shortcut } from '@core/decorator';
+import { MenuNode } from './node/node';
+import { MenuItem } from './item/item';
+import './menu.less';
+
+// 菜单选项信息
+export interface MenuItemInfo {
+ value: string;
+ text: string;
+ icon: string;
+ card?: typeof Widget;
+ cards?: MenuItemInfo[];
+ children?: MenuItemInfo[];
+}
+
+// 菜单选项风格
+export enum MenuItemStyle {
+ Main,
+ Sub,
+}
+
+/**
+ * 菜单,有一级菜单和二级菜单两种风格
+ */
+@shortcut()
+export class Menu extends BI.Widget {
+ static xtype = 'app.base.menu';
+
+ static EVENT = {
+ CHANGE: 'EVENT_CHANGE',
+ };
+
+ public props: MenuProps = {
+ baseCls: 'app-base-menu',
+ value: '',
+ itemInfos: [],
+ itemStyle: MenuItemStyle.Main,
+ };
+
+ private menuRef: CustomTree;
+
+ /**
+ * 创建菜单选项组件
+ * @param itemInfos 菜单选项信息
+ * @param itemStyle 菜单选项风格
+ * @param level 菜单选项层次
+ * @returns 菜单选项组件
+ */
+ private createMenuItems(itemInfos: MenuItemInfo[], itemStyle: MenuItemStyle, level: number): (MenuNode | MenuItem)[] {
+ return BI.map(itemInfos, (_index, itemInfo) => {
+ const { value, text, icon, cards, children } = itemInfo;
+ const valueOrValues = cards ? BI.map(cards, (_index, card) => card.value).join(',') : value;
+
+ return children ? (
+
+ ) : (
+
+ );
+ });
+ }
+
+ /**
+ * 获取菜单选项组件
+ * @returns 菜单选项组件
+ */
+ private getMenuItems(): (MenuNode | MenuItem)[] {
+ const { itemInfos, itemStyle } = this.options;
+
+ return this.createMenuItems(itemInfos, itemStyle, 0);
+ }
+
+ /**
+ * 设置itemInfos,会根据其值更新menuRef
+ * @param itemInfos 要设置的itemInfos值
+ */
+ public setItemInfos(itemInfos: MenuItemInfo[]) {
+ this.options.itemInfos = itemInfos;
+ const menuItems = this.getMenuItems();
+ this.menuRef.populate(menuItems);
+ }
+
+ /**
+ * 设置value
+ * @param value 要设置的value值
+ */
+ public setValue(value: string) {
+ this.options.value = value;
+ this.menuRef.setValue(value);
+ }
+
+ public render() {
+ const { itemStyle, value } = this.options;
+ const menuItems = this.getMenuItems();
+ const vgap = itemStyle === MenuItemStyle.Main ? 24 : 0;
+
+ return (
+ {
+ this.menuRef = ref;
+ }}
+ expander={} />}
+ el={]} chooseType={BI.Selection.Single} />}
+ items={menuItems}
+ value={value}
+ listeners={[
+ {
+ eventName: BI.CustomTree.EVENT_CHANGE,
+ action: (value: string) => {
+ const valueOrValues = value.includes(',') ? value.split(',') : value;
+ this.fireEvent(Menu.EVENT.CHANGE, valueOrValues);
+ },
+ },
+ ]}
+ />
+ );
+ }
+}
+
+interface MenuProps {
+ baseCls: string;
+ value: string;
+ itemInfos: MenuItemInfo[];
+ itemStyle: MenuItemStyle;
+}
diff --git a/src/modules/base/menu/node/node.less b/src/modules/base/menu/node/node.less
new file mode 100644
index 0000000..766c45d
--- /dev/null
+++ b/src/modules/base/menu/node/node.less
@@ -0,0 +1,10 @@
+@import '~@/index.less';
+
+.app-base-menu-node {
+ .icon {
+ }
+ .text {
+ }
+ .arrow {
+ }
+}
diff --git a/src/modules/base/menu/node/node.tsx b/src/modules/base/menu/node/node.tsx
new file mode 100644
index 0000000..c746eb8
--- /dev/null
+++ b/src/modules/base/menu/node/node.tsx
@@ -0,0 +1,73 @@
+import { NodeButton, IconChangeButton } from '@fui/core';
+import { shortcut } from '@core/decorator';
+import { MenuItem } from '../item/item';
+import './node.less';
+
+// 节点展开和折叠状态分别对应的类名
+export const ARROW_CLASSES_MAP = {
+ collapse: 'column-next-page-h-font',
+ expand: 'column-pre-page-h-font',
+};
+
+/**
+ * 菜单选项中的展开/折叠节点
+ */
+@shortcut()
+export class MenuNode extends BI.NodeButton {
+ static xtype = 'app.base.menu_node';
+
+ public props: MenuNodeProps & NodeButton['props'] = {
+ baseCls: 'app-base-menu-node bi-list-item',
+ height: 36,
+ open: false,
+ value: '',
+ text: '',
+ icon: '',
+ level: 0,
+ children: [],
+ };
+
+ private arrowRef: IconChangeButton;
+
+ /**
+ * 设置菜单节点的展开情况
+ * @param opened 要设置的展开情况,ture表示展开,false表示折叠
+ */
+ public setOpened(opened: boolean) {
+ Object.getPrototypeOf(MenuNode).prototype.setOpened.call(this, opened);
+ const arrowCls = ARROW_CLASSES_MAP[opened ? 'expand' : 'collapse'];
+ this.arrowRef.setIcon(arrowCls);
+ }
+
+ public render() {
+ const { text, icon, level } = this.options;
+
+ return (
+ , ],
+ right: [
+ {
+ this.arrowRef = ref;
+ }}
+ iconCls="arrow"
+ />,
+ ],
+ }}
+ />
+ );
+ }
+}
+
+interface MenuNodeProps {
+ baseCls: string;
+ height: number;
+ value: string;
+ text: string;
+ icon: string;
+ level: number;
+ children: (MenuNode | MenuItem)[];
+}
diff --git a/src/modules/base/nav/item/item.less b/src/modules/base/nav/item/item.less
new file mode 100644
index 0000000..1fb684c
--- /dev/null
+++ b/src/modules/base/nav/item/item.less
@@ -0,0 +1,13 @@
+@import '~@/index.less';
+
+.app-base-nav-item {
+ .text {
+ }
+ .close {
+ border-radius: 100%;
+ &:hover {
+ color: @font-color-white;
+ background-color: @background-color-negative;
+ }
+ }
+}
diff --git a/src/modules/base/nav/item/item.tsx b/src/modules/base/nav/item/item.tsx
new file mode 100644
index 0000000..cee8eec
--- /dev/null
+++ b/src/modules/base/nav/item/item.tsx
@@ -0,0 +1,50 @@
+import { BasicButton } from '@fui/core';
+import { shortcut } from '@core/decorator';
+import { NavItemStyle, NAV_ITEM_STYLE_CLASSES_MAP } from '../nav';
+import './item.less';
+
+@shortcut()
+export class NavItem extends BI.BasicButton {
+ static xtype = 'app.base.nav_item';
+
+ static EVENT = {
+ CLOSE: 'EVENT_CLOSE',
+ };
+
+ public props: NavItemProps & BasicButton['props'] = {
+ baseCls: 'app-base-nav-item bi-split-left bi-split-right',
+ value: '',
+ text: '',
+ closable: false,
+ style: NavItemStyle.Block,
+ };
+
+ public render() {
+ const { value, text, closable, style } = this.options;
+ const cls = NAV_ITEM_STYLE_CLASSES_MAP[style];
+
+ return (
+
+
+ {
+ this.fireEvent(NavItem.EVENT.CLOSE, value);
+ }}
+ />
+
+ );
+ }
+}
+
+interface NavItemProps {
+ baseCls: string;
+ value: string;
+ text: string;
+ closable: boolean;
+ style: NavItemStyle;
+}
diff --git a/src/modules/base/nav/nav.less b/src/modules/base/nav/nav.less
new file mode 100644
index 0000000..2948e45
--- /dev/null
+++ b/src/modules/base/nav/nav.less
@@ -0,0 +1,4 @@
+@import '~@/index.less';
+
+.app-base-nav {
+}
diff --git a/src/modules/base/nav/nav.tsx b/src/modules/base/nav/nav.tsx
new file mode 100644
index 0000000..f3483c7
--- /dev/null
+++ b/src/modules/base/nav/nav.tsx
@@ -0,0 +1,134 @@
+import { ButtonGroup } from '@fui/core';
+import { shortcut } from '@core/decorator';
+import { NavItem } from './item/item';
+import './nav.less';
+
+// 导航选项信息
+export interface NavItemInfo {
+ value: string;
+ text: string;
+ closable: boolean;
+}
+
+// 导航选项风格
+export enum NavItemStyle {
+ Block,
+ Underline,
+}
+
+// 导航选项风格与class的映射关系
+export const NAV_ITEM_STYLE_CLASSES_MAP = {
+ [NavItemStyle.Block]: 'bi-list-item-select',
+ [NavItemStyle.Underline]: 'app-list-item-underline',
+};
+
+@shortcut()
+export class Nav extends BI.Widget {
+ static xtype = 'app.base.nav';
+
+ static EVENT = {
+ CHANGE: 'EVENT_CHANGE',
+ CLOSE: 'EVENT_CLOSE',
+ };
+
+ public props: NavProps = {
+ baseCls: 'app-base-nav',
+ height: 36,
+ value: '',
+ itemInfos: [],
+ itemStyle: NavItemStyle.Block,
+ };
+
+ private navRef: ButtonGroup;
+
+ /**
+ * 获取导航菜单选项组件集合
+ * @returns 导航菜单选项组件集合
+ */
+ private getNavItems(): NavItem[] {
+ const { height, itemInfos, itemStyle } = this.options;
+
+ return BI.map(itemInfos, (_index, itemInfo) => {
+ const { value, text, closable } = itemInfo;
+
+ return (
+ {
+ this.fireEvent(Nav.EVENT.CLOSE, value);
+ },
+ },
+ ]}
+ />
+ );
+ });
+ }
+
+ /**
+ * 设置itemInfos,并根据其值更新navRef
+ * @param itemInfos 要设置的itemInfos
+ */
+ public setItemInfos(itemInfos: NavItemInfo[]) {
+ this.options.itemInfos = itemInfos;
+ const navItems = this.getNavItems();
+ this.navRef.populate(navItems);
+ }
+
+ /**
+ * 设置itemStyle,并根据其值更新navRef
+ * @param itemStyle 要设置的itemStyle
+ */
+ public setItemStyle(itemStyle: NavItemStyle) {
+ this.options.itemStyle = itemStyle;
+ const navItems = this.getNavItems();
+ this.navRef.populate(navItems);
+ }
+
+ /**
+ * 设置value
+ * @param value 要设置的value值
+ */
+ public setValue(value: string) {
+ this.options.value = value;
+ this.navRef.setValue(value);
+ }
+
+ public render() {
+ const { value } = this.options;
+ const navItems = this.getNavItems();
+
+ return (
+ {
+ this.navRef = ref;
+ }}
+ layouts={[]}
+ listeners={[
+ {
+ eventName: BI.ButtonGroup.EVENT_CHANGE,
+ action: value => {
+ this.fireEvent(Nav.EVENT.CHANGE, value);
+ },
+ },
+ ]}
+ items={navItems}
+ value={value}
+ />
+ );
+ }
+}
+
+interface NavProps {
+ baseCls: string;
+ height: number;
+ value: string;
+ itemInfos: NavItemInfo[];
+ itemStyle: NavItemStyle;
+}
diff --git a/src/modules/card/demo/demo.less b/src/modules/card/demo/demo.less
new file mode 100644
index 0000000..2b0a10f
--- /dev/null
+++ b/src/modules/card/demo/demo.less
@@ -0,0 +1,5 @@
+@import '~@/index.less';
+
+.app-demo {
+ font-size: @font-size-24;
+}
diff --git a/src/modules/card/demo/demo.model.ts b/src/modules/card/demo/demo.model.ts
new file mode 100644
index 0000000..aac8e0c
--- /dev/null
+++ b/src/modules/card/demo/demo.model.ts
@@ -0,0 +1,15 @@
+import { model, Model } from '@core/decorator';
+import LayoutModel from '@app/layout/layout.model';
+
+@model()
+export default class DemoModel extends Model<{
+ types: {
+ openedCards: LayoutModel['TYPE']['openedCards'];
+ activeCard: LayoutModel['TYPE']['activeCard'];
+ };
+ context: DemoModel['context'];
+}> {
+ static xtype = 'app.model.demo';
+
+ public context = ['openedCards', 'activeCard'];
+}
diff --git a/src/modules/card/demo/demo.tsx b/src/modules/card/demo/demo.tsx
new file mode 100644
index 0000000..522931b
--- /dev/null
+++ b/src/modules/card/demo/demo.tsx
@@ -0,0 +1,73 @@
+import { Label } from '@fui/core';
+import { shortcut, store } from '@core/decorator';
+import DemoModel from './demo.model';
+import './demo.less';
+
+/**
+ * 用于进行基本演示的测试组件
+ */
+@shortcut()
+@store(DemoModel)
+export class Demo extends BI.Widget {
+ static xtype = 'app.demo';
+
+ public props: DeomProps = {
+ baseCls: 'app-demo',
+ cardName: '',
+ };
+
+ public watch = {
+ openedCards: () => {
+ this.updateLabelRef();
+ },
+ activeCard: () => {
+ this.updateLabelRef();
+ },
+ };
+
+ private model: DemoModel['model'];
+ private labelRef: Label;
+
+ /**
+ * 基于cardName和model获取标签的文本内容
+ * @returns 标签的文本内容
+ */
+ private getLabelText(): string {
+ const { cardName } = this.options;
+ const { openedCards, activeCard } = this.model;
+ const text = `
+ 组件名称为 ${cardName},
+ openedCards 为 [ ${openedCards} ],
+ openedCards 的长度为 ${openedCards.length},
+ activeCard 为 ${activeCard}
+ `;
+
+ return text;
+ }
+
+ /**
+ * 更新标签的文本内容
+ */
+ private updateLabelRef() {
+ const labelText = this.getLabelText();
+ this.labelRef.setText(labelText);
+ }
+
+ public render() {
+ const labelText = this.getLabelText();
+
+ return (
+ {
+ this.labelRef = ref;
+ }}
+ text={labelText}
+ />
+ );
+ }
+}
+
+interface DeomProps {
+ baseCls: string;
+ cardName: string;
+}
diff --git a/src/modules/card/home/home.less b/src/modules/card/home/home.less
new file mode 100644
index 0000000..b830f63
--- /dev/null
+++ b/src/modules/card/home/home.less
@@ -0,0 +1,5 @@
+@import '~@/index.less';
+
+.app-home {
+ font-size: @font-size-24;
+}
diff --git a/src/modules/card/home/home.tsx b/src/modules/card/home/home.tsx
new file mode 100644
index 0000000..8ba9b39
--- /dev/null
+++ b/src/modules/card/home/home.tsx
@@ -0,0 +1,27 @@
+import { shortcut } from '@core/decorator';
+import './home.less';
+
+/**
+ * 用于充当各一级菜单首页的测试组件
+ */
+@shortcut()
+export class Home extends BI.Widget {
+ static xtype = 'app.home';
+
+ public props: HomeProps = {
+ baseCls: 'app-home',
+ cardName: '',
+ };
+
+ public render() {
+ const { cardName } = this.options;
+ const labelText = `我是一级菜单 ${cardName} 的首页`;
+
+ return ;
+ }
+}
+
+interface HomeProps {
+ baseCls: string;
+ cardName: string;
+}
diff --git a/src/modules/card/todolist/header/header.less b/src/modules/card/todolist/header/header.less
new file mode 100644
index 0000000..dcb020d
--- /dev/null
+++ b/src/modules/card/todolist/header/header.less
@@ -0,0 +1,15 @@
+@import '~@/index.less';
+
+.app-todolist-header {
+ background-color: @color-bi-background-light-black;
+ .title {
+ font-size: @font-size-24;
+ color: @color-bi-text-white;
+ }
+ .editor {
+ background-color: @color-bi-background-default;
+ .border-radius(@radius-2);
+ }
+ .button {
+ }
+}
diff --git a/src/modules/card/todolist/header/header.tsx b/src/modules/card/todolist/header/header.tsx
new file mode 100644
index 0000000..d15c858
--- /dev/null
+++ b/src/modules/card/todolist/header/header.tsx
@@ -0,0 +1,76 @@
+import { TextEditor, Button } from '@fui/core';
+import { shortcut } from '@core/decorator';
+import './header.less';
+
+/**
+ * ToDoList中的Header组件,包括标题,以及用于添加待办事项的编辑框和按钮
+ */
+@shortcut()
+export class TodolistHeader extends BI.Widget {
+ static xtype = 'app.todolist_header';
+
+ static EVENT = {
+ ADD: 'EVENT_ADD',
+ };
+
+ public props = {
+ baseCls: 'app-todolist-header',
+ };
+
+ private editorRef: TextEditor;
+ private buttonRef: Button;
+
+ public render() {
+ return (
+
+ ],
+ right: [
+ {
+ this.editorRef = ref;
+ }}
+ cls="editor"
+ width={256}
+ height={32}
+ allowBlank
+ watermark="请填写待办事项"
+ listeners={[
+ {
+ eventName: BI.Editor.EVENT_CHANGE,
+ action: () => {
+ const editorValue = this.editorRef.getValue();
+ const buttonEnabled = editorValue.length > 0;
+ this.buttonRef.setEnable(buttonEnabled);
+ },
+ },
+ ]}
+ />,
+ {
+ this.buttonRef = ref;
+ }}
+ cls="button"
+ height={32}
+ text="添加"
+ iconCls="plus-font"
+ disabled
+ handler={() => {
+ const editorValue = this.editorRef.getValue();
+ this.fireEvent(TodolistHeader.EVENT.ADD, editorValue);
+ this.editorRef.setValue('');
+ this.buttonRef.setEnable(false);
+ }}
+ />,
+ ],
+ }}
+ />
+
+ );
+ }
+}
diff --git a/src/modules/card/todolist/list/list.less b/src/modules/card/todolist/list/list.less
new file mode 100644
index 0000000..9650ec8
--- /dev/null
+++ b/src/modules/card/todolist/list/list.less
@@ -0,0 +1,30 @@
+@import '~@/index.less';
+
+.app-todolist-list {
+ .header {
+ .title {
+ font-size: @font-size-16;
+ font-weight: bold;
+ }
+ .count {
+ border-radius: 100%;
+ color: @font-color-white;
+ background-color: @color-bi-background-light-black;
+ }
+ }
+ .items {
+ .item {
+ .check {
+ }
+ .text {
+ }
+ .close {
+ &:hover,
+ &.hover {
+ font-weight: bold;
+ color: @color-bi-text-redmark;
+ }
+ }
+ }
+ }
+}
diff --git a/src/modules/card/todolist/list/list.tsx b/src/modules/card/todolist/list/list.tsx
new file mode 100644
index 0000000..576260b
--- /dev/null
+++ b/src/modules/card/todolist/list/list.tsx
@@ -0,0 +1,117 @@
+import { Widget, Label, VirtualGroup } from '@fui/core';
+import { shortcut } from '@core/decorator';
+import { TodolistItemInfo } from '../todolist';
+import './list.less';
+
+/**
+ * ToDoList中的List组件,表示待办事项清单
+ */
+@shortcut()
+export class TodolistList extends BI.Widget {
+ static xtype = 'app.todolist_list';
+
+ static EVENT = {
+ FINISH: 'EVENT_FINISH',
+ REMOVE: 'EVENT_REMOVE',
+ };
+
+ public props: TodolistListProps = {
+ baseCls: 'app-todolist-list',
+ title: '',
+ itemInfos: [],
+ };
+
+ private countRef: Label;
+ private itemsRef: VirtualGroup;
+
+ /**
+ * 根据itemInfos获取待办事项列表的items
+ * @returns 用于传给itemsRef的items
+ */
+ private getItems(): Widget[] {
+ const { itemInfos } = this.options;
+
+ return BI.map(itemInfos, (_index, itemInfo) => {
+ const { text, done } = itemInfo;
+
+ return (
+
+ {
+ this.fireEvent(TodolistList.EVENT.FINISH, itemInfo);
+ }}
+ />
+
+ {
+ this.fireEvent(TodolistList.EVENT.REMOVE, itemInfo);
+ }}
+ />
+
+ );
+ });
+ }
+
+ /**
+ * 设置itemInfos,并更新itemsRef
+ * @param itemInfos 待办事项信息集合
+ */
+ public setItemInfos(itemInfos: TodolistItemInfo[]) {
+ this.options.itemInfos = itemInfos;
+ const items = this.getItems();
+ this.itemsRef.populate(items);
+ this.countRef.setText(`${items.length}`);
+ }
+
+ public render() {
+ const { title } = this.options;
+ const items = this.getItems();
+ const count = `${items.length}`;
+
+ return (
+
+ ],
+ right: [
+ {
+ this.countRef = ref;
+ }}
+ cls="count"
+ width={24}
+ height={24}
+ text={count}
+ />,
+ ],
+ }}
+ />
+ {
+ this.itemsRef = ref;
+ }}
+ cls="items"
+ layouts={[]}
+ items={items}
+ />
+
+ );
+ }
+}
+
+interface TodolistListProps {
+ baseCls: string;
+ title: string;
+ itemInfos: TodolistItemInfo[];
+}
diff --git a/src/modules/card/todolist/todolist.less b/src/modules/card/todolist/todolist.less
new file mode 100644
index 0000000..d7912f1
--- /dev/null
+++ b/src/modules/card/todolist/todolist.less
@@ -0,0 +1,4 @@
+@import '~@/index.less';
+
+.app-todolist {
+}
diff --git a/src/modules/card/todolist/todolist.tsx b/src/modules/card/todolist/todolist.tsx
new file mode 100644
index 0000000..a6a8775
--- /dev/null
+++ b/src/modules/card/todolist/todolist.tsx
@@ -0,0 +1,174 @@
+import { shortcut } from '@core/decorator';
+import { TodolistHeader } from './header/header';
+import { TodolistList } from './list/list';
+import './todolist.less';
+
+// 待办事项信息
+export interface TodolistItemInfo {
+ id: string;
+ text: string;
+ done: boolean;
+}
+
+/**
+ * ToDoList组件,由header和list两个组件组成
+ */
+@shortcut()
+export class Todolist extends BI.Widget {
+ static xtype = 'app.todolist';
+
+ public props: TodolistProps = {
+ baseCls: 'app-todolist',
+ storageKey: 'app.todolist',
+ };
+
+ private undoneListRef: TodolistList;
+ private doneListRef: TodolistList;
+
+ /**
+ * 从window.localStorage中获取待办事项信息集合,并将其转换为对象格式
+ * @returns 对象格式的待办事项信息集合
+ */
+ private getItemInfos(): TodolistItemInfo[] {
+ const { storageKey } = this.options;
+ const itemsInfosStr = localStorage.getItem(storageKey) || JSON.stringify([]);
+
+ return JSON.parse(itemsInfosStr);
+ }
+
+ /**
+ * 更新localStorage中存储的待办事项信息集合,并更新undoneListRef和doneListRef
+ * @param itemInfos 待办事项信息集合
+ */
+ private setItemInfos(itemInfos: TodolistItemInfo[]) {
+ const { storageKey } = this.options;
+ const itemsInfosStr = JSON.stringify(itemInfos);
+ localStorage.setItem(storageKey, itemsInfosStr);
+ this.updateListRefs();
+ }
+
+ /**
+ * 添加待办事项
+ * @param itemInfo 要添加的待办事项信息
+ */
+ private addItemInfo(itemInfo: TodolistItemInfo) {
+ const itemInfos = this.getItemInfos();
+ itemInfos.push(itemInfo);
+ this.setItemInfos(itemInfos);
+ }
+
+ /**
+ * 完成待办事项
+ * @param itemInfo 要完成的待办事项信息
+ */
+ private finishItemInfo(itemInfo: TodolistItemInfo) {
+ const itemInfos = this.getItemInfos();
+ (BI.find(itemInfos, { id: itemInfo.id }) as TodolistItemInfo).done = true;
+ this.setItemInfos(itemInfos);
+ }
+
+ /**
+ * 删除待办事项
+ * @param itemInfo 要删除的待办事项信息
+ */
+ private removeItemInfo(itemInfo: TodolistItemInfo) {
+ const itemInfos = this.getItemInfos();
+ BI.remove(itemInfos, (_index: number, curItemInfo: TodolistItemInfo) => curItemInfo.id === itemInfo.id);
+ this.setItemInfos(itemInfos);
+ }
+
+ /**
+ * 获取未完成的待办事项信息集合
+ * @returns 未完成的待办事项信息集合
+ */
+ private getUndoneItemInfos(): TodolistItemInfo[] {
+ const itemInfos = this.getItemInfos();
+
+ return BI.filter(itemInfos, (_index, itemInfo) => itemInfo.done === false);
+ }
+
+ /**
+ * 获取已完成的待办事项信息集合
+ * @returns 已完成的待办事项信息集合
+ */
+ private getDoneItemInfos(): TodolistItemInfo[] {
+ const itemInfos = this.getItemInfos();
+
+ return BI.filter(itemInfos, (_index, itemInfo) => itemInfo.done === true);
+ }
+
+ /**
+ * 更新undoneListRef和doneListRef
+ */
+ private updateListRefs() {
+ const undoneItemInfos = this.getUndoneItemInfos();
+ const doneItemInfos = this.getDoneItemInfos();
+ this.undoneListRef.setItemInfos(undoneItemInfos);
+ this.doneListRef.setItemInfos(doneItemInfos);
+ }
+
+ public render() {
+ const undoneItemInfos = this.getUndoneItemInfos();
+ const doneItemInfos = this.getDoneItemInfos();
+
+ return (
+
+ {
+ this.addItemInfo({
+ id: BI.UUID(),
+ text,
+ done: false,
+ });
+ },
+ },
+ ]}
+ />
+ {
+ this.undoneListRef = ref;
+ }}
+ title="待办"
+ itemInfos={undoneItemInfos}
+ listeners={[
+ {
+ eventName: TodolistList.EVENT.FINISH,
+ action: itemInfo => {
+ this.finishItemInfo(itemInfo);
+ },
+ },
+ {
+ eventName: TodolistList.EVENT.REMOVE,
+ action: itemInfo => {
+ this.removeItemInfo(itemInfo);
+ },
+ },
+ ]}
+ />
+ {
+ this.doneListRef = ref;
+ }}
+ title="已办"
+ itemInfos={doneItemInfos}
+ listeners={[
+ {
+ eventName: TodolistList.EVENT.REMOVE,
+ action: itemInfo => {
+ this.removeItemInfo(itemInfo);
+ },
+ },
+ ]}
+ />
+
+ );
+ }
+}
+
+interface TodolistProps {
+ baseCls: string;
+ storageKey: string;
+}
diff --git a/src/modules/core/decorator.ts b/src/modules/core/decorator.ts
new file mode 100644
index 0000000..aaf0b8c
--- /dev/null
+++ b/src/modules/core/decorator.ts
@@ -0,0 +1,3 @@
+export const { shortcut, model, store, Model } = BI.Decorators;
+
+export type Constructor = new (...args: any[]) => T;
diff --git a/src/modules/header/header.js b/src/modules/header/header.js
deleted file mode 100644
index c7ce6e1..0000000
--- a/src/modules/header/header.js
+++ /dev/null
@@ -1,70 +0,0 @@
-!(function() {
- /**
- * 顶部组件,提供输入框添加todo项目
- * 布局: bi.horizontal_auto 实现水平居中. bi.left_right_vertical_adapt 实现标题是输入框的靠左靠右垂直居中
- */
- var ToDoListHeader = BI.inherit(BI.Widget, {
-
- props: {
- // 指定组件的className
- baseCls: "my-todolist-header"
- },
-
- render: function() {
- var self = this, o = this.options;
- return {
- type: "bi.horizontal_auto", // 水平居中布局
- items: [
- {
- el: {
- type: "bi.left_right_vertical_adapt", // 左右垂直居中布局
- width: 600,
- height: o.height,
- items: {
- left: [
- {
- el: {
- type: "bi.label",
- cls: "my-todolist-title",
- text: "FineUI ToDoList",
- height: o.height
- }
- }
- ],
- right: [
- {
- el: {
- type: "bi.editor",
- ref: function(_ref) {
- self.editor = _ref;
- },
- allowBlank: true,
- cls: "my-todolist-header-editor",
- watermark: "添加ToDo",
- width: 300,
- height: 24,
- listeners: [
- { // 监听bi.editor 组件的"EVENT_ENTER"事件(即敲回车),触发事件ToDoListHeader.EVENT_ADD事件并将输入框值置空
- eventName: "EVENT_ENTER",
- action: function() {
- // 注意:在这里this指向的是bi.editor的实例.通过bi.editor的getValue()方法获取输入框输入值.
- self.fireEvent(ToDoListHeader.EVENT_ADD, this.getValue());
- self.editor.setValue("");
- }
- }
- ]
- }
- }
- ]
- }
- }
- }
- ]
- };
- }
- });
-
- ToDoListHeader.EVENT_ADD = "EVENT_ADD";
-
- BI.shortcut("my.todolist.header", ToDoListHeader);
-})();
\ No newline at end of file
diff --git a/src/modules/header/header.less b/src/modules/header/header.less
deleted file mode 100644
index 32fcaac..0000000
--- a/src/modules/header/header.less
+++ /dev/null
@@ -1,18 +0,0 @@
-@import "../../index.less";
-/**
- 列表项的less,其中用到了部分FineUI提供的字号,颜色常量,还有border-radius,box-shadow方法等.请选择性使用.不强制要求
- */
-.my-todolist-header {
- background-color: #3d4d66;
-
- .my-todolist-title {
- font-size: @font-size-22;
- color: #FFF;
- }
-
- .my-todolist-header-editor {
- background-color: #FFF;
- .border-radius(5px);
- .box-shadow(0 1px 0 rgba(255, 255, 255, 0.24), 0 1px 6px rgba(0, 0, 0, 0.45) inset)
- }
-}
\ No newline at end of file
diff --git a/src/modules/list/list.js b/src/modules/list/list.js
deleted file mode 100644
index d4e0d3a..0000000
--- a/src/modules/list/list.js
+++ /dev/null
@@ -1,90 +0,0 @@
-!(function() {
- /**
- * todo项列表
- *
- */
- var List = BI.inherit(BI.Widget, {
-
- props: {
- // 指定组件的className
- baseCls: "my-todolist-list",
- text: "正在进行"
- },
-
- render: function() {
- var self = this, o = this.options;
- return {
- type: "bi.vertical",
- items: [
- {
- el: {
- type: "bi.vertical_adapt",
- height: 40,
- items: [
- {
- type: "bi.label",
- cls: "my-todolist-list-text",
- textAlign: "left",
- text: o.text,
- width: 580
- }, {
- type: "bi.center_adapt",
- cls: "my-todolist-list-count-container",
- width: 20,
- height: 20,
- items: [
- {
- el: {
- type: "bi.label",
- ref: function(_ref) {
- self.count = _ref;
- },
- text: "0"
- }
- }
- ]
- }
- ]
- }
- }, { // 用bi.vertical布局作为列表项的容器.
- type: "bi.vertical",
- vgap: 10,
- ref: function(_ref) {
- self.list = _ref;
- },
- items: this._createItems(o.items)
- }
- ]
- };
- },
-
- _createItems: function(items) {
- var self = this;
- return BI.map(items, function(index, item) {
- return BI.extend(item, {
- type: "bi.multi_select_item", // 节点采用复选节点展示
- selected: item.done, // 已完成的todo项置为选中状态
- disabled: item.done, // 已完成的todo项置为灰化状态
- listeners: [
- { // 为每个todo项添加"EVENT_CHANGE"事件监听,触发组件自身"EVENT_CHANGE"事件
- eventName: "EVENT_CHANGE",
- action: function(v) {
- self.fireEvent("EVENT_CHANGE", v);
- }
- }
- ]
- });
- });
- },
-
- _setCount: function(count) {
- this.count.setText(count);
- },
-
- populate: function(items) {
- this.list.populate(this._createItems(items));
- this._setCount(items.length);
- }
- });
- BI.shortcut("my.todolist.list", List);
-})();
\ No newline at end of file
diff --git a/src/modules/list/list.less b/src/modules/list/list.less
deleted file mode 100644
index 7b1d751..0000000
--- a/src/modules/list/list.less
+++ /dev/null
@@ -1,16 +0,0 @@
-@import "../../index.less";
-/**
- 列表项的less,其中用到了部分FineUI提供的字号,颜色常量,还有border-radius方法等.请选择性使用.不强制要求
- */
-.my-todolist-list {
- .my-todolist-list-text {
- font-size: @font-size-16;
- font-weight: bold;
- }
-
- .my-todolist-list-count-container {
- .border-radius(10px);
- background-color: #3d4d66;
- color: @font-color-white;
- }
-}
\ No newline at end of file
diff --git a/src/modules/main.js b/src/modules/main.js
deleted file mode 100644
index 9152cee..0000000
--- a/src/modules/main.js
+++ /dev/null
@@ -1,121 +0,0 @@
-!(function() {
- /**
- * todolist 组件
- */
- var ToDoList = BI.inherit(BI.Widget, {
-
- props: {
- baseCls: "fine-to-do-list"
- },
-
- // 生命周期函数,在组件创建前
- beforeCreate: function() {
- // 初始化存储数据
- this.list = localStorage.getItem("fine-todolist") ? JSON.parse(localStorage.getItem("fine-todolist")) : [];
- },
-
- render: function() {
- var self = this, o = this.options;
- return {
- type: "bi.vtape", // vtape布局,顶部高度固定,下部分列表占满高度
- items: [
- {
- el: {
- type: "my.todolist.header", // 顶部组件
- listeners: [
- { // 监听组件的EVENT_ADD事件,新建todo项
- eventName: "EVENT_ADD",
- action: function(v) {
- self.addToDo(v);
- }
- }
- ],
- height: 40
- },
- height: 40
- }, {
- type: "bi.horizontal_auto", // 水平居中布局
- cls: "my-todolist-background", // 添加className
- items: [
- {
- el: {
- type: "my.todolist.list", // need todo项列表
- ref: function(_ref) {
- self.todolist = _ref;
- },
- items: this._getNeedTodoList(),
- text: "正在进行",
- listeners: [
- { // 监听EVENT_CHANGE事件,完成某一项todo
- eventName: "EVENT_CHANGE",
- action: function(v) {
- self.finishTodo(v);
- }
- }
- ],
- width: 600
- }
- }, {
- el: {
- type: "my.todolist.list", // 已经完成的todo项列表
- text: "已经完成",
- items: this._getAlreadyDoneList(),
- ref: function(_ref) {
- self.donelist = _ref;
- },
- width: 600
- }
- }
- ]
- }
- ]
- };
- },
-
- _updateLocalStorage: function() {
- localStorage.setItem("fine-todolist", JSON.stringify(this.list));
- },
-
- _getNeedTodoList: function() {
- return BI.filter(this.list, function(index, item) {
- return !item.done;
- });
- },
-
- _getAlreadyDoneList: function() {
- return BI.filter(this.list, function(index, item) {
- return item.done;
- });
- },
-
- /**
- * 添加todo项
- * @param text todo项的内容
- */
- addToDo: function(text) {
- this.list.push({
- value: BI.UUID(),
- text: text,
- done: false
- });
- this.todolist.populate(this._getNeedTodoList());
- this._updateLocalStorage();
- },
-
- /**
- * 完成某一项todo
- * @param v todo项的value
- */
- finishTodo: function(v) {
- BI.some(this.list, function(index, item) {
- if (item.value === v) {
- item.done = true;
- }
- });
- this.todolist.populate(this._getNeedTodoList());
- this.donelist.populate(this._getAlreadyDoneList());
- this._updateLocalStorage();
- }
- });
- BI.shortcut("my.todolist", ToDoList);
-})();
diff --git a/src/modules/main.less b/src/modules/main.less
deleted file mode 100644
index 14cef72..0000000
--- a/src/modules/main.less
+++ /dev/null
@@ -1,6 +0,0 @@
-/**
- 列表项的less,其中用到了部分FineUI提供的字号,颜色常量,还有border-radius,box-shadow方法等.请选择性使用.不强制要求
- */
-.my-todolist-background {
- background-color: #f7f8fa;
-}
\ No newline at end of file
diff --git a/src/routes.ts b/src/routes.ts
new file mode 100644
index 0000000..e8c237d
--- /dev/null
+++ b/src/routes.ts
@@ -0,0 +1,186 @@
+import { Widget } from '@fui/core';
+import { Home } from '@card/home/home';
+import { Demo } from '@card/demo/demo';
+import { Todolist } from '@card/todolist/todolist';
+
+// 路由类型
+export enum RouteType {
+ Single,
+ Multiple,
+}
+
+// 路由信息
+export interface RouteInfo {
+ type?: RouteType;
+ value: string;
+ text: string;
+ icon: string;
+ card?: typeof Widget;
+ cards?: RouteInfo[];
+ children?: RouteInfo[];
+}
+
+/*
+ * 路由映射信息,配置说明:
+ * 1.顶层路由将作为一级菜单,第二层及更深层路由将作为二级菜单
+ * 2.为顶层路由配置type属性
+ * 2.1 RouteType.Single: 每个二级菜单选项对应一个card,导航标签可关闭,其形式类似BI的目录页面
+ * 2.2 RouteType.Multiple: 每个二级菜单选项对应多个card,导航标签不可关闭,其形式类似BI的管理系统页面
+ * 3.路由公共配置如下:
+ * 3.1 value: 路由的唯一索引
+ * 3.2 text: 路由对应的菜单项名称和导航标签名称
+ * 3.3 icon: 路由对应的菜单项图标
+ * 3.4 children: 路由的子路由
+ * 3.5 card|cards:
+ * 3.5.1 card: 顶层路由的type为RouteType.Single时可用,表示该路由对应的单个card
+ * 3.5.2 cards: 顶层路由的type为RouteType.Multiple时可用,表示该路由对应的多个card
+ * 3.5.3 顶层路由的card表示一级菜单对应的默认card
+ */
+export const ROUTE_INFOS: RouteInfo[] = [
+ {
+ type: RouteType.Single,
+ value: 'directory',
+ text: '模板目录',
+ icon: 'directory-font',
+ card: Home,
+ children: [
+ {
+ value: 'todolist',
+ text: 'Todolist',
+ icon: 'date-font',
+ card: Todolist,
+ },
+ {
+ value: 'menu-level-1',
+ text: '一级菜单',
+ icon: 'text-align-center-font',
+ children: [
+ {
+ value: 'menu-level-1-todolist',
+ text: '一级 Todolist',
+ icon: 'date-font',
+ card: Todolist,
+ },
+ {
+ value: 'menu-level-2',
+ text: '二级菜单',
+ icon: 'text-align-center-font',
+ children: createDemoRouteInfos(4, '二级'),
+ },
+ {
+ value: 'menu-level-2-another',
+ text: '另一个二级菜单',
+ icon: 'text-align-center-font',
+ children: createDemoRouteInfos(4, '另一个二级'),
+ },
+ ...createDemoRouteInfos(4, '一级'),
+ ],
+ },
+ ...createDemoRouteInfos(8),
+ ],
+ },
+ {
+ type: RouteType.Multiple,
+ value: 'management',
+ text: '系统管理',
+ icon: 'management-font',
+ card: Home,
+ children: [
+ {
+ value: 'series-1',
+ text: '系列一',
+ icon: 'copy-font',
+ cards: createDemoRouteInfos(4, '系列一的'),
+ },
+ {
+ value: 'series-2',
+ text: '系列二',
+ icon: 'copy-font',
+ cards: [...createDemoRouteInfos(4, '系列二的'), ...createDemoRouteInfos(4, '另一个')],
+ },
+ {
+ value: 'series-level-1',
+ text: '一级菜单',
+ icon: 'text-align-center-font',
+ children: [
+ {
+ value: 'series-level-1-1',
+ text: '一级系列一',
+ icon: 'copy-font',
+ cards: createDemoRouteInfos(8, '一级系列一的'),
+ },
+ {
+ value: 'series-level-1-2',
+ text: '一级系列二',
+ icon: 'copy-font',
+ cards: createDemoRouteInfos(8, '一级系列二的'),
+ },
+ {
+ value: 'series-level-2',
+ text: '二级菜单',
+ icon: 'text-align-center-font',
+ children: [
+ {
+ value: 'series-level-2-1',
+ text: '二级系列一',
+ icon: 'copy-font',
+ cards: createDemoRouteInfos(5, '二级系列一的'),
+ },
+ {
+ value: 'series-level-2-2',
+ text: '二级系列二',
+ icon: 'copy-font',
+ cards: createDemoRouteInfos(6, '二级系列二的'),
+ },
+ {
+ value: 'series-level-2-3',
+ text: '二级系列三',
+ icon: 'copy-font',
+ cards: createDemoRouteInfos(7, '二级系列三的'),
+ },
+ {
+ value: 'series-level-2-4',
+ text: '二级系列四',
+ icon: 'copy-font',
+ cards: createDemoRouteInfos(8, '二级系列四的'),
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+];
+
+// ================ 以下为测试用的工具函数 ================
+
+/**
+ * 生成用于测试的路由信息集合
+ * @param number 要生成路由信息的数量
+ * @param textPrefix 要生成路由信息的名称前缀
+ * @returns 所生成的路由信息集合
+ */
+function createDemoRouteInfos(number: number, textPrefix = ''): RouteInfo[] {
+ const iconList = [
+ 'search-font',
+ 'date-font',
+ 'time-font',
+ 'date-change-h-font',
+ 'copy-font',
+ 'primary-key-font',
+ 'text-bold-font',
+ 'text-italic-font',
+ 'text-underline-font',
+ 'text-color-font',
+ 'text-background-font',
+ ];
+
+ return BI.map(BI.range(0, number, 1), index => {
+ return {
+ value: BI.UUID(),
+ text: `${textPrefix} Demo ${index + 1}`,
+ icon: iconList[index % iconList.length],
+ card: Demo,
+ };
+ });
+}
diff --git a/template/favicon.ico b/template/favicon.ico
new file mode 100644
index 0000000..8b87a0f
Binary files /dev/null and b/template/favicon.ico differ
diff --git a/template/index.html b/template/index.html
new file mode 100644
index 0000000..8af506a
--- /dev/null
+++ b/template/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+ FineUI Starter
+
+
+
+
+
+
+
+
diff --git a/test/sum.test.ts b/test/sum.test.ts
new file mode 100644
index 0000000..854e5ff
--- /dev/null
+++ b/test/sum.test.ts
@@ -0,0 +1,5 @@
+import { sum } from './sum';
+
+test('adds 1 + 2 to equal 3', () => {
+ expect(sum(1, 2)).toBe(3);
+});
diff --git a/test/sum.ts b/test/sum.ts
new file mode 100644
index 0000000..a8c4991
--- /dev/null
+++ b/test/sum.ts
@@ -0,0 +1,3 @@
+export function sum(a: number, b: number): number {
+ return a + b;
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..c480d0d
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,22 @@
+{
+ "extends": "@fui/typescript-configs/application.json",
+ "include": ["src/*.ts", "src/*.tsx", "src/**/*.ts", "src/**/*.tsx", "test", "typings"],
+ "compilerOptions": {
+ /* 基本选项 */
+ "jsx": "react",
+ /* 严格的类型检查选项 */
+ "strict": false,
+ /* 模块解析选项 */
+ "baseUrl": "./",
+ "rootDir": ".",
+ "paths": {
+ "@/*": ["src/*"],
+ "@app/*": ["src/modules/app/*"],
+ "@base/*": ["src/modules/base/*"],
+ "@card/*": ["src/modules/card/*"],
+ "@core/*": ["src/modules/core/*"],
+ "@service/*": ["src/modules/service/*"],
+ "@types/*": ["src/modules/types/*"]
+ }
+ }
+}
diff --git a/typings/globals.d.ts b/typings/globals.d.ts
new file mode 100644
index 0000000..eea1690
--- /dev/null
+++ b/typings/globals.d.ts
@@ -0,0 +1,14 @@
+// eslint-disable-next-line spaced-comment
+///
+
+interface Obj {
+ [key: string]: any;
+}
+
+declare let BI: Obj & import('@fui/core').BI;
+declare const Fix: Obj;
+declare const $: ((el: any) => any) & Obj;
+
+declare interface String {
+ replaceAll(regx: string | RegExp, callback: (str: string) => void): string;
+}
diff --git a/webpack/dirs.js b/webpack/dirs.js
new file mode 100644
index 0000000..36f3d53
--- /dev/null
+++ b/webpack/dirs.js
@@ -0,0 +1,18 @@
+const { pathResolve, pathJoin } = require('./utils');
+
+module.exports = {
+ NODE_MODULES: pathResolve('../node_modules'),
+ DEST: pathResolve('../dist'),
+ SRC: pathResolve('../src'),
+ SRC_MODULES_APP: pathResolve('../src/modules/app'),
+ SRC_MODULES_BASE: pathResolve('../src/modules/base'),
+ SRC_MODULES_CARD: pathResolve('../src/modules/card'),
+ SRC_MODULES_CORE: pathResolve('../src/modules/core'),
+ SRC_MODULES_SERVICE: pathResolve('../src/modules/service'),
+ SRC_MODULES_TYPES: pathResolve('../src/modules/types'),
+ SRC_CORE: pathResolve('../src/core'),
+ PRIVATE: pathResolve('../private'),
+ INDEX: pathResolve('../template/index.html'),
+ BABEL_CONFIG: pathResolve('../babel.config.js'),
+ CONTENT_BASE: pathJoin('..'),
+};
diff --git a/webpack/utils.js b/webpack/utils.js
new file mode 100644
index 0000000..3739bb3
--- /dev/null
+++ b/webpack/utils.js
@@ -0,0 +1,6 @@
+const path = require('path');
+
+module.exports = {
+ pathResolve: dirpath => path.resolve(__dirname, dirpath),
+ pathJoin: dirpath => path.join(__dirname, dirpath),
+};
diff --git a/webpack/webpack.common.js b/webpack/webpack.common.js
new file mode 100644
index 0000000..9cf3484
--- /dev/null
+++ b/webpack/webpack.common.js
@@ -0,0 +1,73 @@
+const MiniCssExtractPlugin = require('mini-css-extract-plugin');
+const AutoPrefixer = require('autoprefixer');
+const dirs = require('./dirs');
+
+module.exports = {
+ entry: {
+ bundle: ['./src/index.ts'],
+ },
+ resolve: {
+ mainFields: ['module', 'main'],
+ mainFiles: ['index'],
+ extensions: ['.js', '.jsx', '.ts', '.tsx'],
+ alias: {
+ '@': dirs.SRC,
+ '@app': dirs.SRC_MODULES_APP,
+ '@base': dirs.SRC_MODULES_BASE,
+ '@card': dirs.SRC_MODULES_CARD,
+ '@core': dirs.SRC_MODULES_CORE,
+ '@service': dirs.SRC_MODULES_SERVICE,
+ '@types': dirs.SRC_MODULES_TYPES,
+ },
+ },
+ stats: {
+ children: false,
+ modules: false,
+ },
+ module: {
+ rules: [
+ {
+ test: /\.(jsx?|tsx?)$/,
+ include: [dirs.NODE_MODULES, dirs.SRC],
+ use: [
+ {
+ loader: 'babel-loader',
+ options: {
+ configFile: dirs.BABEL_CONFIG,
+ },
+ },
+ {
+ loader: 'source-map-loader',
+ options: {
+ enforce: 'pre',
+ },
+ },
+ ],
+ },
+ {
+ test: /\.(css|less)$/,
+ use: [
+ MiniCssExtractPlugin.loader,
+ {
+ loader: 'css-loader',
+ options: {
+ url: false,
+ },
+ },
+ {
+ loader: 'postcss-loader',
+ options: {
+ plugins: [AutoPrefixer],
+ },
+ },
+ {
+ loader: 'less-loader',
+ options: {
+ relativeUrls: false,
+ },
+ },
+ ],
+ },
+ ],
+ },
+};
diff --git a/webpack/webpack.dev.js b/webpack/webpack.dev.js
new file mode 100644
index 0000000..bff83a8
--- /dev/null
+++ b/webpack/webpack.dev.js
@@ -0,0 +1,51 @@
+const merge = require('webpack-merge');
+const MiniCssExtractPlugin = require('mini-css-extract-plugin');
+const HtmlWebpackPlugin = require('html-webpack-plugin');
+const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
+const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
+const dirs = require('./dirs');
+const common = require('./webpack.common.js');
+
+module.exports = merge(common, {
+ devtool: 'eval-source-map',
+ entry: {},
+ output: {
+ path: dirs.DEST,
+ filename: '[name].[contenthash].js',
+ },
+ devServer: {
+ open: true,
+ contentBase: dirs.CONTENT_BASE,
+ port: 8080,
+ liveReload: true,
+ },
+ plugins: [
+ new ForkTsCheckerWebpackPlugin({}),
+ new MiniCssExtractPlugin({
+ path: dirs.DEST,
+ filename: '[contenthash].css',
+ }),
+ new HtmlWebpackPlugin({
+ template: dirs.INDEX,
+ chunks: ['bundle'],
+ chunksSortMode: 'manual',
+ nodeModules: dirs.NODE_MODULES,
+ }),
+ new OptimizeCssAssetsPlugin({
+ assetNameRegExp: /\.css$/g,
+ cssProcessor: require('cssnano'),
+ cssProcessorPluginOptions: {
+ preset: [
+ 'default',
+ {
+ discardComments: {
+ removeAll: true,
+ },
+ normalizeUnicode: false,
+ },
+ ],
+ },
+ canPrint: true,
+ }),
+ ],
+});
diff --git a/webpack/webpack.prod.js b/webpack/webpack.prod.js
new file mode 100644
index 0000000..7dde1de
--- /dev/null
+++ b/webpack/webpack.prod.js
@@ -0,0 +1,57 @@
+const webpack = require('webpack');
+const merge = require('webpack-merge');
+const MiniCssExtractPlugin = require('mini-css-extract-plugin');
+const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
+const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
+const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
+const dirs = require('./dirs');
+const common = require('./webpack.common.js');
+
+module.exports = merge.smart(common, {
+ mode: 'production',
+ optimization: {
+ minimizer: [
+ new UglifyJsPlugin({
+ parallel: true,
+ sourceMap: true,
+ uglifyOptions: {
+ ie8: true,
+ output: {
+ comments: false,
+ },
+ },
+ }),
+ ],
+ },
+ devtool: 'hidden-source-map',
+ output: {
+ path: dirs.DEST,
+ filename: 'bundle.js',
+ },
+ plugins: [
+ new ForkTsCheckerWebpackPlugin({}),
+ new MiniCssExtractPlugin({
+ path: dirs.DEST,
+ filename: 'bundle.css',
+ }),
+ new webpack.BannerPlugin({
+ banner: `time: ${new Date().toLocaleString()}`,
+ }),
+ new OptimizeCssAssetsPlugin({
+ assetNameRegExp: /\.css$/g,
+ cssProcessor: require('cssnano'),
+ cssProcessorPluginOptions: {
+ preset: [
+ 'default',
+ {
+ discardComments: {
+ removeAll: true,
+ },
+ normalizeUnicode: false,
+ },
+ ],
+ },
+ canPrint: true,
+ }),
+ ],
+});