chart and table
diff --git a/package-lock.json b/package-lock.json
index d067e4d..c489f7f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,6 +14,16 @@
             "resolved": "https://registry.nlark.com/@babel/parser/download/@babel/parser-7.14.3.tgz?cache=0&sync_timestamp=1621284389691&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40babel%2Fparser%2Fdownload%2F%40babel%2Fparser-7.14.3.tgz",
             "integrity": "sha1-m1MO7LBx/QyTUZ3yXF/58UdZ8pg="
         },
+        "@babel/polyfill": {
+            "version": "7.12.1",
+            "resolved": "https://registry.npm.taobao.org/@babel/polyfill/download/@babel/polyfill-7.12.1.tgz",
+            "integrity": "sha1-Hy1jcdEmG72WHzxdWQkVDhLQvZY=",
+            "dev": true,
+            "requires": {
+                "core-js": "^2.6.5",
+                "regenerator-runtime": "^0.13.4"
+            }
+        },
         "@babel/types": {
             "version": "7.14.2",
             "resolved": "https://registry.nlark.com/@babel/types/download/@babel/types-7.14.2.tgz",
@@ -403,6 +413,15 @@
                 "@types/node": "*"
             }
         },
+        "@types/jquery": {
+            "version": "3.5.5",
+            "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.5.tgz",
+            "integrity": "sha512-6RXU9Xzpc6vxNrS6FPPapN1SxSHgQ336WC6Jj/N8q30OiaBZ00l1GBgeP7usjVZPivSkGUfL1z/WW6TX989M+w==",
+            "dev": true,
+            "requires": {
+                "@types/sizzle": "*"
+            }
+        },
         "@types/lodash": {
             "version": "4.14.169",
             "resolved": "https://registry.nlark.com/@types/lodash/download/@types/lodash-4.14.169.tgz",
@@ -420,6 +439,12 @@
             "integrity": "sha1-I6Brh+7bUkAWYW6IaxFrj9yxgK8=",
             "dev": true
         },
+        "@types/sizzle": {
+            "version": "2.3.3",
+            "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz",
+            "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==",
+            "dev": true
+        },
         "@types/unist": {
             "version": "2.0.3",
             "resolved": "https://registry.nlark.com/@types/unist/download/@types/unist-2.0.3.tgz?cache=0&sync_timestamp=1621243829208&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40types%2Funist%2Fdownload%2F%40types%2Funist-2.0.3.tgz",
@@ -790,6 +815,11 @@
             "integrity": "sha1-ZfCvOC9Xi83HQr2cKB6cstd2gyg=",
             "dev": true
         },
+        "bignumber.js": {
+            "version": "8.1.1",
+            "resolved": "https://registry.npm.taobao.org/bignumber.js/download/bignumber.js-8.1.1.tgz",
+            "integrity": "sha1-Swcq5a6pwg9nMOTl1SnfEnHE2IU="
+        },
         "binary-extensions": {
             "version": "2.2.0",
             "resolved": "https://registry.npm.taobao.org/binary-extensions/download/binary-extensions-2.2.0.tgz?cache=0&sync_timestamp=1610299293319&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbinary-extensions%2Fdownload%2Fbinary-extensions-2.2.0.tgz",
@@ -1278,6 +1308,12 @@
                 "@babel/types": "^7.6.1"
             }
         },
+        "core-js": {
+            "version": "2.6.12",
+            "resolved": "https://registry.nlark.com/core-js/download/core-js-2.6.12.tgz",
+            "integrity": "sha1-2TM9+nsGXjR8xWgiGdb2kIWcwuw=",
+            "dev": true
+        },
         "core-util-is": {
             "version": "1.0.2",
             "resolved": "http://registry.npm.taobao.org/core-util-is/download/core-util-is-1.0.2.tgz",
@@ -1500,6 +1536,15 @@
             "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=",
             "dev": true
         },
+        "echarts": {
+            "version": "5.1.1",
+            "resolved": "https://registry.nlark.com/echarts/download/echarts-5.1.1.tgz",
+            "integrity": "sha1-sYbxYvAXxVXP1nsS7eZ2K9w939o=",
+            "requires": {
+                "tslib": "2.0.3",
+                "zrender": "5.1.0"
+            }
+        },
         "electron-to-chromium": {
             "version": "1.3.747",
             "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.747.tgz",
@@ -1861,6 +1906,16 @@
             "integrity": "sha1-/wQLKwhTsjw9MQJ1I3BvGIXXa+4=",
             "dev": true
         },
+        "handsontable": {
+            "version": "6.2.2",
+            "resolved": "https://registry.nlark.com/handsontable/download/handsontable-6.2.2.tgz?cache=0&sync_timestamp=1622544081773&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fhandsontable%2Fdownload%2Fhandsontable-6.2.2.tgz",
+            "integrity": "sha1-8SUPPzdKvffUoAgJUEgtPt7qjwc=",
+            "requires": {
+                "moment": "2.20.1",
+                "numbro": "^2.0.6",
+                "pikaday": "1.5.1"
+            }
+        },
         "has": {
             "version": "1.0.3",
             "resolved": "https://registry.npm.taobao.org/has/download/has-1.0.3.tgz",
@@ -2275,6 +2330,11 @@
             "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
             "dev": true
         },
+        "jquery": {
+            "version": "3.6.0",
+            "resolved": "https://registry.npm.taobao.org/jquery/download/jquery-3.6.0.tgz?cache=0&sync_timestamp=1614705221099&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjquery%2Fdownload%2Fjquery-3.6.0.tgz",
+            "integrity": "sha1-xyoJ8Vwb3OFC9J2/EXC9+K2sJHA="
+        },
         "js-stringify": {
             "version": "1.0.2",
             "resolved": "https://registry.npm.taobao.org/js-stringify/download/js-stringify-1.0.2.tgz",
@@ -2575,6 +2635,11 @@
             "integrity": "sha512-2lMlY1Yc1+CUy0gw4H95uNN7vjbpoED7NNRSBHE25nWfLBdmMzFCsPshlzbxHz+gYMcBEUN8V4pU16prcdPSgA==",
             "dev": true
         },
+        "moment": {
+            "version": "2.20.1",
+            "resolved": "https://registry.npm.taobao.org/moment/download/moment-2.20.1.tgz?cache=0&sync_timestamp=1601983320283&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmoment%2Fdownload%2Fmoment-2.20.1.tgz",
+            "integrity": "sha1-1usaRsvMFKKy+UNBEsH/iQfzE/0="
+        },
         "ms": {
             "version": "2.1.3",
             "resolved": "https://registry.npm.taobao.org/ms/download/ms-2.1.3.tgz?cache=0&sync_timestamp=1607433905701&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fms%2Fdownload%2Fms-2.1.3.tgz",
@@ -2680,6 +2745,14 @@
             "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
             "dev": true
         },
+        "numbro": {
+            "version": "2.3.2",
+            "resolved": "https://registry.npm.taobao.org/numbro/download/numbro-2.3.2.tgz",
+            "integrity": "sha1-SWfGMbDqmbF91/iM3R6qSs5NSXE=",
+            "requires": {
+                "bignumber.js": "^8.1.1"
+            }
+        },
         "object-assign": {
             "version": "4.1.1",
             "resolved": "http://registry.npm.taobao.org/object-assign/download/object-assign-4.1.1.tgz",
@@ -2887,6 +2960,14 @@
             "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
             "dev": true
         },
+        "pikaday": {
+            "version": "1.5.1",
+            "resolved": "https://registry.npm.taobao.org/pikaday/download/pikaday-1.5.1.tgz",
+            "integrity": "sha1-CkhUm8GhTqHQjEQHTXYbwvK/z9M=",
+            "requires": {
+                "moment": "2.x"
+            }
+        },
         "pinkie": {
             "version": "2.0.4",
             "resolved": "http://registry.npm.taobao.org/pinkie/download/pinkie-2.0.4.tgz",
@@ -3492,6 +3573,12 @@
                 }
             }
         },
+        "regenerator-runtime": {
+            "version": "0.13.7",
+            "resolved": "https://registry.npm.taobao.org/regenerator-runtime/download/regenerator-runtime-0.13.7.tgz",
+            "integrity": "sha1-ysLazIoepnX+qrrriugziYrkb1U=",
+            "dev": true
+        },
         "registry-auth-token": {
             "version": "4.2.1",
             "resolved": "https://registry.npm.taobao.org/registry-auth-token/download/registry-auth-token-4.2.1.tgz",
@@ -3976,6 +4063,11 @@
             "integrity": "sha1-uLY5zvrX0LsqvTfUM/+Ck++l9AY=",
             "dev": true
         },
+        "tslib": {
+            "version": "2.0.3",
+            "resolved": "https://registry.npm.taobao.org/tslib/download/tslib-2.0.3.tgz?cache=0&sync_timestamp=1617647074515&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ftslib%2Fdownload%2Ftslib-2.0.3.tgz",
+            "integrity": "sha1-jgdBrEX8DCJuWKF7/D5kubxsphw="
+        },
         "type-fest": {
             "version": "0.3.1",
             "resolved": "https://registry.nlark.com/type-fest/download/type-fest-0.3.1.tgz?cache=0&sync_timestamp=1621402383646&other_urls=https%3A%2F%2Fregistry.nlark.com%2Ftype-fest%2Fdownload%2Ftype-fest-0.3.1.tgz",
@@ -5031,6 +5123,14 @@
             "requires": {
                 "camelcase": "^4.1.0"
             }
+        },
+        "zrender": {
+            "version": "5.1.0",
+            "resolved": "https://registry.nlark.com/zrender/download/zrender-5.1.0.tgz",
+            "integrity": "sha1-tqhMOqfMxmQu4FGZAcpMCDXE2F4=",
+            "requires": {
+                "tslib": "2.0.3"
+            }
         }
     }
 }
diff --git a/package.json b/package.json
index cd90565..9d4a02a 100644
--- a/package.json
+++ b/package.json
@@ -8,7 +8,9 @@
         "serve": "vite preview"
     },
     "devDependencies": {
+        "@babel/polyfill": "^7.12.1",
         "@tailwindcss/postcss7-compat": "^2.1.4",
+        "@types/jquery": "^3.5.5",
         "@vitejs/plugin-vue": "^1.2.2",
         "@vue/compiler-sfc": "^3.0.11",
         "autoprefixer": "^9.8.6",
@@ -28,7 +30,10 @@
         "yargs": "^6.6.0"
     },
     "dependencies": {
+        "echarts": "^5.1.1",
         "element-plus": "^1.0.2-beta.44",
+        "handsontable": "^6.2.2",
+        "jquery": "^3.6.0",
         "lodash": "^4.17.19",
         "vue": "^3.0.11",
         "vue-i18n": "^9.1.6"
diff --git a/src/components/BTable.vue b/src/components/BTable.vue
index ea64fd7..9f6fc36 100644
--- a/src/components/BTable.vue
+++ b/src/components/BTable.vue
@@ -1,34 +1,58 @@
 <template>
     <div class="w-full h-full">
-        <div class="grid grid-cols-3 h-full">
-            <div id="el-table" class="col-span-2 h-full" style="border: 1px solid red;"></div>
-            <div id="el-config" class="p-3 align-middle">
-                <div>
-                    <el-button type="button" onclick="run()" size="medium">运行</el-button>
-                    <el-button type="button" size="medium">导出</el-button>
+        <div class="grid grid-cols-12 h-full text-sm">
+            <el-card class="box-card col-span-3">
+                <h1 slot="header" class="clearfix text-xl">
+                    Apache ECharts Bar-Race 生成器
+                </h1>
+                <div id="el-config" class="align-middle">
+                    <!-- <div class="my-3">
+                        <el-button onclick="run()" size="medium">运行</el-button>
+                        <el-button size="medium">导出</el-button>
+                    </div> -->
+                    <el-form ref="form">
+                        <div class="grid grid-cols-3 form-row">
+                            <label class="col-span-1">标题</label>
+                            <el-input id="input-title" value="汽车产量动态排名" size="medium" class="col-span-2"></el-input>
+                        </div>
+                        <div class="grid grid-cols-3 form-row">
+                            <label class="col-span-1">显示排名上限</label>
+                            <el-input id="input-max" type="number" value="10" size="medium" class="col-span-2"></el-input>
+                        </div>
+                    </el-form>
                 </div>
-                <el-form ref="form">
-                    <div class="grid grid-cols-2 form-row">
-                        <label>标题</label>
-                        <el-input id="input-title" value="汽车产量动态排名" size="medium"></el-input>
-                    </div>
-                    <div class="grid grid-cols-2 form-row">
-                        <label>显示排名上限</label>
-                        <el-input id="input-max" type="number" value="10" size="medium"></el-input>
-                    </div>
-                </el-form>
-            </div>
+            </el-card>
+            <el-card class="box-card col-span-4 relative" body-style="height: 100%">
+                <div slot="header" class="clearfix text-base">
+                    数据
+                </div>
+                <div id="table-panel" class="overflow-auto absolute bottom-4 top-14 left-5 right-5 border">
+                </div>
+            </el-card>
+            <el-card class="box-card col-span-5 relative" body-style="height: 100%">
+                <div slot="header" class="clearfix text-base">
+                    预览
+                    <a href="#">
+                        <i class="el-icon-refresh"></i>
+                    </a>
+                </div>
+                <div id="bar-race-preview" class="absolute bottom-4 top-14 left-5 right-5 border">
+                </div>
+            </el-card>
         </div>
     </div>
 </template>
 
 <script lang="ts">
-import {defineComponent} from 'vue'
+import {defineComponent} from 'vue';
+import btable from './btable';
+
 export default defineComponent({
     name: 'BTable',
     props: {
     },
-    setup: () => {
+    mounted: () => {
+        btable.initTable();
     }
 })
 </script>
@@ -42,5 +66,9 @@
             @apply py-1;
         }
     }
+
+    .box-card {
+        @apply m-1;
+    }
 }
 </style>
diff --git a/src/components/btable.ts b/src/components/btable.ts
new file mode 100644
index 0000000..43eafec
--- /dev/null
+++ b/src/components/btable.ts
@@ -0,0 +1,243 @@
+import * as $ from 'jquery';
+import Handsontable from 'handsontable';
+import * as echarts from 'echarts';
+
+const headerLength = 2;
+
+let chart: echarts.ECharts;
+const data = [
+    ["Name", "Ford", "Tesla", "Toyota", "Honda"],
+    ["Color", "", "", "", ""],
+    ["2017", 10, 11, 12, 13],
+    ["2018", 20, 11, 14, 13],
+    ["2019", 30, 15, 12, 13]
+];
+
+function initTable() {
+    for (let i = 0; i < data.length; ++i) {
+        for (let j = data[i].length; j < 50; ++j) {
+            data[i].push('');
+        }
+    }
+    for (let i = data.length; i < 100; ++i) {
+        const row = [];
+        for (let j = 0; j < 50; ++j) {
+            row.push('');
+        }
+        data.push(row);
+    }
+
+    // function colorRenderer(instance, td, row, col) {
+    //     //- console.log(instance);
+
+    // }
+
+    const container = document.getElementById('table-panel') as Element;
+    console.log(container)
+
+    const table = new Handsontable(container, {
+        data: data,
+        rowHeaders: true,
+        colHeaders: true,
+        filters: true,
+        dropdownMenu: true,
+        // cell: [{
+        //     row: 0,
+        //     col: 0,
+        //     readOnly: true
+        // }, {
+        //     row: 1,
+        //     col: 0,
+        //     readOnly: true
+        // }],
+        //- cells: function (row, col) {
+        //-     if (row === 1) {
+        //-         return {
+        //-             renderer: colorRenderer
+        //-         }
+        //-     }
+        //-     else {
+        //-         return {};
+        //-     }
+        //- }
+    });
+    table.updateSettings({
+        afterChange: function () {
+            run();
+        }
+    });
+
+    chart = echarts.init($('#bar-race-preview')[0]);
+    run();
+}
+
+const timeoutHandlers: number[] = [];
+
+function clearTimeoutHandlers() {
+    for (let i = 0; i < timeoutHandlers.length; ++i) {
+        clearTimeout(timeoutHandlers[i]);
+        timeoutHandlers.splice(i, 1);
+    }
+}
+function removeTimeoutHandlers(handler: number) {
+    for (let i = 0; i < timeoutHandlers.length; ++i) {
+        if (timeoutHandlers[i] === handler) {
+            timeoutHandlers.splice(i, 1);
+        }
+    }
+}
+
+function initEvents() {
+    $('.form-group').change(function () {
+        run();
+    });
+}
+
+
+function run() {
+    clearTimeoutHandlers();
+
+    const title = $('#input-title').val();
+    const max = $('#input-max').val();
+    chart.setOption({
+        title: [{
+            text: getDataName(0),
+            textStyle: {
+                fontFamily: 'monospace',
+                fontSize: 80,
+                color: 'rgba(100, 100, 100, 0.2)'
+            },
+            bottom: 60,
+            right: 20
+        }, {
+            text: title
+        }],
+        grid: {
+            right: 20
+        },
+        yAxis: {
+            type: 'category',
+            data: getYData(),
+            inverse: true,
+            max: max,
+            animationDuration: 0,
+            animationDurationUpdate: 0
+        },
+        xAxis: {},
+        series: [{
+            type: 'bar',
+            data: getChartData(0),
+            realtimeSort: true,
+            colorBy: 'item',
+            label: {
+                show: true,
+                position: 'insideRight'
+            }
+        }],
+        animationDurationUpdate: 5000,
+        animationEasing: 'linear',
+        animationEasingUpdate: 'linear'
+    }, true);
+
+    const rows = trimRows();
+    for (let i = 1; i < rows.length; ++i) {
+        (function (i) {
+            var dataRow = getChartData(i);
+            let timeout: number;
+            const timeoutCb = function () {
+                chart.setOption({
+                    title: [{
+                        text: getDataName(i)
+                    }],
+                    yAxis: {
+                        animationDuration: 300,
+                        animationDurationUpdate: 300,
+                    },
+                    series: [{
+                        type: 'bar',
+                        data: dataRow
+                    }]
+                });
+                removeTimeoutHandlers(timeout);
+            };
+            timeout = window.setTimeout(timeoutCb, (i - 1) * 5000);
+            timeoutHandlers.push(timeout);
+        })(i);
+    }
+}
+
+const trimColumns = function (rowData: (string | number)[]) {
+    for (let i = rowData.length - 1; i > 0; --i) {
+        if (rowData[i] && rowData[i] !== '') {
+            return rowData.slice(1, i + 1);
+        }
+    }
+    return [];
+};
+
+const trimRows = function () {
+    if (data.length <= headerLength) {
+        return [];
+    }
+    for (let i = data.length - 1; i >= headerLength; --i) {
+        let isEmpty = true;
+        for (let j = 1; j < data[i].length; ++j) {
+            if (data[i][j] && data[i][j] !== '') {
+                isEmpty = false;
+                break;
+            }
+        }
+        if (!isEmpty) {
+            return data.slice(headerLength, i + 1);
+        }
+    }
+    return [];
+};
+
+const getYData = function () {
+    if (data.length <= headerLength) {
+        return [];
+    }
+    return trimColumns(data[0]);
+};
+
+const getChartData = function (id: number) {
+    if (data.length <= id + headerLength) {
+        return [];
+    }
+    return trimColumns(data[id + headerLength]);
+};
+
+const getDataName = function (id: number) {
+    if (data.length <= id + headerLength) {
+        return '';
+    }
+    else {
+        return data[id + headerLength][0];
+    }
+}
+
+function download() {
+    // saveFile()
+}
+
+function saveFile(name: string, type: string) {
+    // if (isSafari()) {
+    //     window.open('data:text/plain;charset=utf-8,' + encodeURIComponent(data));
+    // } else {
+    //     try {
+    //         var file = new Blob([data], {type: type});
+    //         saveAs(file, name);
+    //     } catch(e) {
+    //         console.error(e);
+    //         window.open('data:text/plain;charset=utf-8,' + encodeURIComponent(data));
+    //     }
+    // }
+}
+
+function isSafari() {
+    return navigator.userAgent.indexOf('Safari') > 0 &&
+        navigator.userAgent.indexOf('Chrome') < 0;
+}
+
+export default {initTable};
diff --git a/tsconfig.json b/tsconfig.json
index 5f1b278..6823c9a 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -11,5 +11,7 @@
         "allowJs": true,
     },
     "include": ["src/**/*"],
-    "exclude": ["node_modules"]
+    "exclude": ["node_modules"],
+    "esModuleInterop": true,
+    "allowSyntheticDefaultImports": true
 }
\ No newline at end of file