diff --git a/visualdl/frontend/.babelrc b/visualdl/frontend/.babelrc new file mode 100644 index 0000000000000000000000000000000000000000..f35b67b9f32475b0940b87ad5a08d865745a3c7f --- /dev/null +++ b/visualdl/frontend/.babelrc @@ -0,0 +1,7 @@ +{ + "presets": [ + ["es2015", {"modules": false}], + "stage-0" + ], + "plugins": ["transform-class-properties"] +} diff --git a/visualdl/frontend/.fecsignore b/visualdl/frontend/.fecsignore new file mode 100644 index 0000000000000000000000000000000000000000..77d57a3a22fa07a925648af0075673f2e8787ff7 --- /dev/null +++ b/visualdl/frontend/.fecsignore @@ -0,0 +1,6 @@ +node_modules/**/* +dep/**/* +test/**/* +mock/**/* +example/**/* +output/**/* diff --git a/visualdl/frontend/.fecsrc b/visualdl/frontend/.fecsrc new file mode 100644 index 0000000000000000000000000000000000000000..c5bd35919dba798de45761156de33a9650eb4796 --- /dev/null +++ b/visualdl/frontend/.fecsrc @@ -0,0 +1,26 @@ +{ + "files": [ + "./src/**/*.san", + "./src/**/*.js", + "./template/**/*.html" + ], + "eslint": { + "rules": { + "fecs-esnext-ext": [ + "2", + [ + "js", + "san" + ] + ], + "fecs-valid-jsdoc": [ + "0" + ] + } + }, + "csshint": {}, + "htmlcs": {}, + "jformatter": {}, + "esformatter": {}, + "csscomb": {} +} diff --git a/visualdl/frontend/.gitignore b/visualdl/frontend/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..a7a3b001058056c400cb03b5db7934828af6cc29 --- /dev/null +++ b/visualdl/frontend/.gitignore @@ -0,0 +1,3 @@ +/node_modules +/.vscode +package-lock.json diff --git a/visualdl/frontend/mock/example/mock.js b/visualdl/frontend/mock/example/mock.js new file mode 100644 index 0000000000000000000000000000000000000000..5544c73a6c32264b37e16ea8bfed0f7760997cf9 --- /dev/null +++ b/visualdl/frontend/mock/example/mock.js @@ -0,0 +1,26 @@ +/** + * frontend mock data + * + * @param {string} path request path + * @param {Object} queryParam params + * @param {Object} postParam post post params + * @return {Object} + */ +module.exports = function (path, queryParam, postParam) { + return { + // delay + _timeout: 0, + + // http code status + _status: 200, + + // response data + _data: { + // 0 for sucsuss, others for error + status: 0, + // error msg + msg: '', + data: '' + } + }; +}; diff --git a/visualdl/frontend/package.json b/visualdl/frontend/package.json new file mode 100644 index 0000000000000000000000000000000000000000..d6115a19b91d55c75e848a76be89da2164b86c08 --- /dev/null +++ b/visualdl/frontend/package.json @@ -0,0 +1,72 @@ +{ + "name": "front", + "version": "1.0.1", + "description": "VisualDL frontend", + "author": "ecomfe", + "private": true, + "scripts": { + "release": "cross-env NODE_ENV=production node ./tool/build.js", + "build": "cross-env NODE_ENV=dev node ./tool/build.js", + "dev": "cross-env NODE_ENV=dev node tool/dev-server.js", + "lint": "./node_modules/fecs/bin/fecs --rule", + "precommit": "npm run lint", + "prepush": "npm run lint" + }, + "engines": { + "node": ">= 6.4.0" + }, + "dependencies": { + "axios": "^0.16.1", + "file-saver": "^1.3.3", + "lodash": "^4.17.4", + "normalize.css": "^6.0.0", + "san": "3.2.3", + "san-mui": "^1.0.4", + "san-router": "^1.1.1", + "san-store": "^1.0.1", + "san-update": "^2.1.0", + "xlsx": "^0.11.3" + }, + "devDependencies": { + "autoprefixer": "^6.7.7", + "autoresponse": "^0.2.0", + "babel-core": "^6.25.0", + "babel-loader": "^6.2.7", + "babel-plugin-transform-class-properties": "^6.19.0", + "babel-plugin-transform-runtime": "^6.23.0", + "babel-preset-es2015": "^6.18.0", + "babel-preset-stage-0": "^6.16.0", + "babel-register": "^6.0.0", + "babel-runtime": "^6.26.0", + "case-sensitive-paths-webpack-plugin": "^2.1.1", + "chalk": "^1.1.3", + "copy-webpack-plugin": "^4.0.1", + "cross-env": "^4.0.0", + "css-loader": "^0.28.0", + "extract-text-webpack-plugin": "^2.1.0", + "fecs": "^1.5.2", + "file-loader": "^0.11.1", + "friendly-errors-webpack-plugin": "^1.6.1", + "glob": "^7.1.1", + "html-loader": "^0.4.4", + "html-webpack-plugin": "^2.28.0", + "http-proxy-middleware": "^0.17.4", + "husky": "^0.14.3", + "json-loader": "^0.5.4", + "opn": "^5.1.0", + "optimize-css-assets-webpack-plugin": "^1.3.2", + "ora": "^1.1.0", + "postcss-loader": "^1.3.3", + "raw-loader": "^0.5.1", + "san-loader": "^0.0.4", + "style-loader": "^0.16.1", + "stylus": "^0.54.5", + "stylus-loader": "^3.0.1", + "url-loader": "^0.5.8", + "webpack": "^2.4.1", + "webpack-dev-server": "^2.4.2", + "webpack-hot-middleware": "^2.19.1", + "webpack-merge": "^4.1.0", + "yargs": "^8.0.2" + } +} diff --git a/visualdl/frontend/src/App.san b/visualdl/frontend/src/App.san new file mode 100644 index 0000000000000000000000000000000000000000..3e8f7d7fb525d90d794af934a0b93afa35628249 --- /dev/null +++ b/visualdl/frontend/src/App.san @@ -0,0 +1,57 @@ + + + + + diff --git a/visualdl/frontend/src/common/fun/downLoadCSV.js b/visualdl/frontend/src/common/fun/downLoadCSV.js new file mode 100644 index 0000000000000000000000000000000000000000..cfb887be400ccc5456402347f47bae751221a881 --- /dev/null +++ b/visualdl/frontend/src/common/fun/downLoadCSV.js @@ -0,0 +1,52 @@ +import XLSX from 'xlsx'; +import FileSaver from 'file-saver'; +// const JSON_TO_SHEET = XLSX.utils.json_to_sheet; +const aoaToSheet = XLSX.utils.aoa_to_sheet; +const saveAs = FileSaver.saveAs; +function s2ab(s) { + if (typeof ArrayBuffer !== 'undefined') { + let buf = new ArrayBuffer(s.length); + let view = new Uint8Array(buf); + for (let i = 0; i !== s.length; ++i) { + view[i] = s.charCodeAt(i) & 0xFF; + } + return buf; + } + let buf = new Array(s.length); + for (let i = 0; i !== s.length; ++i) { + buf[i] = s.charCodeAt(i) & 0xFF; + } + return buf; +} + +/** + * download Excel + * + * @desc transform data like [['A', 'B', 'C'], ['1', '2', '3'],[['1-1', '2-1', '3-1']]] to xlsx and download + * @param {Array} data the data for the xlsx + * @param {string} name filename + */ +export const generateXLSXandAutoDownload = function (data, name) { + let wopts = { + bookType: 'xlsx', + bookSST: false, + type: 'binary' + }; + let ws = aoaToSheet(data); + let wb = { + SheetNames: ['Export'], + Sheets: {}, + Props: {} + }; + wb.Sheets.Export = ws; + let wbout = XLSX.write(wb, wopts); + saveAs( + new Blob( + [s2ab(wbout)], + { + type: 'application/octet-stream' + } + ), + name + '.xlsx' || 'sheetjs.xlsx' + ); +}; diff --git a/visualdl/frontend/src/home/Home.san b/visualdl/frontend/src/home/Home.san new file mode 100644 index 0000000000000000000000000000000000000000..dec8ff6b0928a736c0b0f71778e14405ae7c3c9e --- /dev/null +++ b/visualdl/frontend/src/home/Home.san @@ -0,0 +1,18 @@ + + + + diff --git a/visualdl/frontend/src/home/index.js b/visualdl/frontend/src/home/index.js new file mode 100644 index 0000000000000000000000000000000000000000..0ec3406a49ecc93bdb8581d649c3188ddd01f985 --- /dev/null +++ b/visualdl/frontend/src/home/index.js @@ -0,0 +1,10 @@ +import {router} from 'san-router'; + + +import HomePage from './Home'; + +router.add({ + target: '#content', + rule: '/home', + Component: HomePage +}); diff --git a/visualdl/frontend/src/index.js b/visualdl/frontend/src/index.js new file mode 100644 index 0000000000000000000000000000000000000000..74c3dd0a64db71261214c06f1b652b1c6a53bc7b --- /dev/null +++ b/visualdl/frontend/src/index.js @@ -0,0 +1,9 @@ +import 'normalize.css/normalize.css'; +import 'san-mui/index.css'; +let App = require('./App'); +new App({ + data: { + titleName: 'VisualDL' + } +}).attach(document.getElementById('root')); + diff --git a/visualdl/frontend/src/style/variables.styl b/visualdl/frontend/src/style/variables.styl new file mode 100644 index 0000000000000000000000000000000000000000..c93c863a4200dc234ffc9f8e09d45054a5df76ea --- /dev/null +++ b/visualdl/frontend/src/style/variables.styl @@ -0,0 +1 @@ +prefix = 'visual-dl-' diff --git a/visualdl/frontend/template/index.html b/visualdl/frontend/template/index.html new file mode 100755 index 0000000000000000000000000000000000000000..0840dad12bcdd3ef7ba12c1985e541ba36c6c895 --- /dev/null +++ b/visualdl/frontend/template/index.html @@ -0,0 +1,12 @@ + + + + + VisualDL + + + + +
+ + diff --git a/visualdl/frontend/tool/HtmlReplacePlugin.js b/visualdl/frontend/tool/HtmlReplacePlugin.js new file mode 100644 index 0000000000000000000000000000000000000000..ce67a520d36f6c7d0fa0fe61e9cee42241cec5f6 --- /dev/null +++ b/visualdl/frontend/tool/HtmlReplacePlugin.js @@ -0,0 +1,24 @@ +function noopReplace (val) { return val; } + +function HtmlReplacePlugin(options) { + this.replacer = options.replacer || noopReplace; +} + +HtmlReplacePlugin.prototype.apply = function(compiler) { + + var replacer = this.replacer; + + compiler.plugin('compilation', function(compilation) { + + compilation.plugin('html-webpack-plugin-after-html-processing', function(htmlPluginData, callback) { + + htmlPluginData.html = replacer(htmlPluginData.html, htmlPluginData); + + callback(null, htmlPluginData); + + }); + }); + +}; + +module.exports = HtmlReplacePlugin; diff --git a/visualdl/frontend/tool/build.js b/visualdl/frontend/tool/build.js new file mode 100644 index 0000000000000000000000000000000000000000..190d4e9e3538fe51ab3ad0fc13c1b83e2405e5bd --- /dev/null +++ b/visualdl/frontend/tool/build.js @@ -0,0 +1,64 @@ +const webpack = require('webpack'); +const rm = require('rimraf'); +const ora = require('ora'); +const chalk = require('chalk'); +const HtmlReplacePlugin = require('./HtmlReplacePlugin'); + +// env 'production' +process.env.WEBPACK_ENV = 'production'; + +let webpackConfig = require('./webpack.prod.config'); + +let spinner = ora('building for production...'); +spinner.start(); + +let feRoots = { + 'index': '/dist/' +}; + +webpackConfig.plugins = webpackConfig.plugins.concat([ + + new HtmlReplacePlugin({ + replacer: function(html, opt) { + + var name = opt.outputName.replace(/\.html$/, ''); + + var feRoot = feRoots[name]; + + if (feRoot) { + html = html + .replace(/href="/g, 'href="' + feRoot) + .replace(/src="/g, 'src="' + feRoot); + } + + return html; + + } + }) + +]); + +rm(webpackConfig.output.path, err => { + + if (err) throw err; + + webpack(webpackConfig, function(err, stats) { + spinner.stop() + if (err) throw err + + process.stdout.write(stats.toString({ + colors: true, + modules: false, + children: false, + chunks: false, + chunkModules: false + }) + '\n\n'); + + console.log(chalk.cyan(' Build complete.\n')); + console.log(chalk.yellow( + ' Tip: built files are meant to be served over an HTTP server.\n' + + ' Opening index.html over file:// won\'t work.\n' + )); + }) + +}); diff --git a/visualdl/frontend/tool/dev-client.js b/visualdl/frontend/tool/dev-client.js new file mode 100644 index 0000000000000000000000000000000000000000..d882b7bf83be35b3fca65f4d790c7451120e35c9 --- /dev/null +++ b/visualdl/frontend/tool/dev-client.js @@ -0,0 +1,7 @@ +var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true') + +hotClient.subscribe(function (event) { + if (event.action === 'reload') { + window.location.reload() + } +}) diff --git a/visualdl/frontend/tool/dev-server.js b/visualdl/frontend/tool/dev-server.js new file mode 100644 index 0000000000000000000000000000000000000000..f86feb1f5751fa3fc6bc6335dcf783eb3716ccb9 --- /dev/null +++ b/visualdl/frontend/tool/dev-server.js @@ -0,0 +1,116 @@ +process.env.NODE_ENV = 'dev'; +let devPort = 8999; +let opn = require('opn'); +let express = require('express'); +let webpack = require('webpack'); +let proxyMiddleware = require('http-proxy-middleware'); +let webpackConfig = require('./webpack.dev.config'); +let autoresponse = require('autoresponse'); +let path = require('path'); + +let port = devPort; +let autoOpenBrowser = false; + +let app = express(); +let compiler = webpack(webpackConfig); + +let devMiddleware = require('webpack-dev-middleware')(compiler, { + publicPath: webpackConfig.output.publicPath, + disableHostCheck: true, + quiet: false, + noInfo: false, + stats: { + colors: true + }, + headers: {'Access-Control-Allow-Origin': '*'} +}); + +let hotMiddleware = require('webpack-hot-middleware')(compiler, { + heartbeat: 2000 +}); +// force page reload when html-webpack-plugin template changes +compiler.plugin('compilation', function (compilation) { + compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { + hotMiddleware.publish({ + action: 'reload' + }); + cb(); + }); +}); + +var context = [ + '/example', +]; +var proxypath = ''; + +var options = { + target: proxypath, + changeOrigin: true, +}; +if (context.length) { + // app.use(proxyMiddleware(context, options)); + app.use('/example', proxyMiddleware({ + target: 'www.baidu.com', + changeOrigin: true, + })); +} +app.use(autoresponse({ + logLevel: 'debug', + root: path.dirname(__dirname), + rules: [ + { + match: '/example/:id', + method: ['get'] + } + ], + post: { + match: function (reqPathName) { + return !/\.(html|js|map)$/.test(reqPathName) && /^\/(api)(.*)/.test(reqPathName); + } + }, + delete: { + match: function () { + return true; + } + }, + get: { + match: function (reqPathName) { + return !/\.(html|js|map)$/.test(reqPathName) && /^\/(api)(.*)/.test(reqPathName); + } + } + +})); + +// serve webpack bundle output +app.use(devMiddleware); + +// enable hot-reload and state-preserving +// compilation error display +app.use(hotMiddleware); + +let uri = 'http://localhost:' + port; + +let _resolve; +let readyPromise = new Promise(resolve => { + _resolve = resolve; +}); + +console.log('> Starting dev server...'); +devMiddleware.waitUntilValid(() => { + console.log('> Listening at ' + uri + '\n'); + // when env is testing, don't need open it + if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') { + opn(uri); + } + + _resolve(); +}); + +let server = app.listen(port); + +module.exports = { + ready: readyPromise, + close() { + server.close(); + } +}; diff --git a/visualdl/frontend/tool/entry.js b/visualdl/frontend/tool/entry.js new file mode 100644 index 0000000000000000000000000000000000000000..78754c6a5dc394ade713c1522657690d9be3b3fc --- /dev/null +++ b/visualdl/frontend/tool/entry.js @@ -0,0 +1,76 @@ +const path = require('path'); +const projectPath = path.resolve(__dirname, '..'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); + +/** + * default apps + * + * @type {Array} + */ +let defaultApps = [ + { + name: 'index', + feRoot: '/dist' + } +]; + +/** + * get entry js file + * + * @param {Array} apps appname + * @return {string} file path + */ +function getModules(apps) { + + let modules = {}; + apps.forEach(function (item) { + let app = item.name; + modules[app] = path.join(projectPath, 'src/' + app + '.js'); + }); + + return modules; +} + +/** + * get HtmlWebpackPlugin + * + * @param {string} app appname + * @param {boolan} template use template + * @return {HtmlWebpackPlugin} HtmlWebpackPlugin + */ +function getTemplate(app, template) { + let templateUrl = 'template/index.html'; + if (template) { + templateUrl = `ejs-render-loader!template/${template}.ejs`; + } + return new HtmlWebpackPlugin({ + filename: app + '.html', + template: templateUrl + }); +} + +/** + * get entry config + * + * @param {string} app appname + * @param {boolan} template use template + * @return {Object} config + */ +function getEntry(app, template) { + + let buildApps = defaultApps.filter(function (item) { + let name = item.name; + return name === app; + }); + + buildApps = buildApps.length > 0 ? buildApps : defaultApps; + + return { + module: getModules(buildApps), + template: buildApps.map(item => getTemplate(item.name, template)) + }; +} + + +module.exports.get = getEntry; +module.exports.entry = defaultApps; diff --git a/visualdl/frontend/tool/webpack.config.js b/visualdl/frontend/tool/webpack.config.js new file mode 100644 index 0000000000000000000000000000000000000000..24764208c456bb784655b5934e7c272782abf101 --- /dev/null +++ b/visualdl/frontend/tool/webpack.config.js @@ -0,0 +1,133 @@ +const webpack = require('webpack'); +const path = require('path'); +const projectPath = path.resolve(__dirname, '..'); +const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin'); +const argv = require('yargs').argv; +const isDev = process.env.NODE_ENV === 'dev'; +const entry = require('./entry'); +const ExtractTextPlugin = require('extract-text-webpack-plugin'); + +function getLoaders(isDev, ext) { + let arr = ['css-loader']; + if (ext) { + arr.push(ext + '-loader'); + } + if (isDev) { + arr.unshift('style-loader'); + return arr; + } + + return ExtractTextPlugin.extract({ + use: arr, + fallback: 'style-loader' + }); + +} + +/** + * entry config + * + * @type {Object} + */ + +const ENTR_CONFIG = entry.get(argv.app, argv.template); +/** + * webpack config + * + * @type {Object} + */ +const config = { + entry: ENTR_CONFIG.module, + output: { + path: path.resolve(projectPath, 'dist'), + filename: '[name].[hash].js' + }, + resolve: { + + alias: { + 'san-mui': 'san-mui/lib', + axios: 'axios/dist/axios.min.js' + }, + + extensions: ['.js', '.json', '.styl', '.css', '.html', '.san'] + }, + + module: { + noParse: [ + /node_modules\/(san|axios)\// + ], + rules: [ + { + test: /\.san$/, + loader: 'san-loader', + options: { + loaders: { + stylus: getLoaders(isDev, 'stylus') + } + } + }, + { + test: /\.js$/, + exclude: /node_modules/, + include: [ + path.resolve(projectPath, 'src') + ], + loader: 'babel-loader' + }, + { + test: /\.json$/, + loader: 'json-loader' + }, + { + test: /\.html/, + loader: 'html-loader', + options: { + minimize: false + } + }, + { + test: /\.css$/, + use: getLoaders(isDev) + }, + { + test: /\.styl$/, + use: getLoaders(isDev, 'stylus') + }, + { + test: /\.(gif|png|jpe?g)$/i, + loader: 'file-loader', + options: { + name: 'images/[name].[hash].[ext]' + } + }, + { + test: /\.woff2?$/, + loader: 'url-loader', + options: { + name: 'fonts/[name].[hash].[ext]', + limit: '10000', + mimetype: 'application/font-woff' + } + }, + { + test: /\.(ttf|eot|svg)$/, + loader: 'file-loader', + options: { + name: 'fonts/[name].[hash].[ext]' + } + } + ] + }, + plugins: [ + + new CaseSensitivePathsPlugin(), + new webpack.LoaderOptionsPlugin({ + test: /\.(styl|san)$/ + }) + ] +}; + +// template config +config.plugins = config.plugins.concat(ENTR_CONFIG.template); + +module.exports = config; diff --git a/visualdl/frontend/tool/webpack.dev.config.js b/visualdl/frontend/tool/webpack.dev.config.js new file mode 100644 index 0000000000000000000000000000000000000000..5b16958fafa422373ac0ba00075de5616d4d97ca --- /dev/null +++ b/visualdl/frontend/tool/webpack.dev.config.js @@ -0,0 +1,32 @@ +const webpack = require('webpack'); +const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin'); +let merge = require('webpack-merge'); +let baseWebpackConfig = require('./webpack.config'); + +// add hot-reload related code to entry chunks +Object.keys(baseWebpackConfig.entry).forEach(function (name) { + baseWebpackConfig.entry[name] = ['./tool/dev-client'].concat(baseWebpackConfig.entry[name]); +}); + +/** + * dev config + * + * @type {Object} + */ + +module.exports = merge(baseWebpackConfig, { + // cheap-module-eval-source-map is faster for development + devtool: '#cheap-module-eval-source-map', + plugins: [ + new webpack.DefinePlugin({ + 'process.env': { + 'NODE_ENV': '"dev"' + } + }), + // https://github.com/glenjamin/webpack-hot-middleware#installation--usage + new webpack.HotModuleReplacementPlugin(), + new webpack.NoEmitOnErrorsPlugin(), + // https://github.com/ampedandwired/html-webpack-plugin + new FriendlyErrorsPlugin() + ] +}); diff --git a/visualdl/frontend/tool/webpack.prod.config.js b/visualdl/frontend/tool/webpack.prod.config.js new file mode 100644 index 0000000000000000000000000000000000000000..fa73c7837ef2a7ea12803ba5d6885f01520bbc8b --- /dev/null +++ b/visualdl/frontend/tool/webpack.prod.config.js @@ -0,0 +1,73 @@ +const webpack = require('webpack'); +const ExtractTextPlugin = require('extract-text-webpack-plugin'); +const path = require('path'); +const autoprefixer = require('autoprefixer'); +const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin'); +const bizCss = new ExtractTextPlugin('biz.[chunkhash].css'); +let merge = require('webpack-merge'); +let baseWebpackConfig = require('./webpack.config'); +const autoPrefixOptions = { + browsers: [ + 'iOS >= 7', + 'Android >= 4.0', + 'ExplorerMobile >= 10', + 'ie >= 9' + ] +}; + +/** + * pro config + * + * @type {Object} + */ + +module.exports = merge(baseWebpackConfig, { + plugins: [ + new webpack.DefinePlugin({ + 'process.env': { + 'NODE_ENV': 'production' + } + }), + + new webpack.LoaderOptionsPlugin({ + test: /\.(styl|san)$/, + san: { + autoprefixer: autoPrefixOptions + } + }), + + new webpack.optimize.CommonsChunkPlugin({ + name: 'vendor', + filename: 'vendor.[chunkhash].js', + minChunks: function (module, count) { + const resPath = module.resource; + return resPath && /\.js$/.test(resPath) + && resPath.indexOf( + path.join(__dirname, '../node_modules') + ) === 0; + } + }), + + new webpack.optimize.CommonsChunkPlugin({ + name: 'manifest', + minChunks: Infinity + }), + + new webpack.optimize.UglifyJsPlugin({ + compress: { + 'screw_ie8': true, // no ie6/7/8 + 'warnings': false + }, + comments: false, + sourceMap: false + }), + + bizCss, + + new OptimizeCSSPlugin({ + cssProcessorOptions: { + safe: true + } + }) + ] +});