Skip to content
Snippets Groups Projects
Commit cbe60866 authored by zhaoting's avatar zhaoting
Browse files

feat: 新增echart脚本

parent ee551598
No related branches found
No related tags found
No related merge requests found
Pipeline #506 canceled
FROM centos:7
ADD phantomjs /usr/etc/
ADD echartsconvert /opt
RUN yum install -y fontconfig yum -y install bitmap-fonts bitmap-fonts-cjk chmod 777 /usr/etc/bin/phantomjs
\ No newline at end of file
This diff is collapsed.
/**
* Created by SaintLee on 2017/6/22.
*/
;(function (window, document, undefined) {
"use strict";
// 引入module
var system = require('system'), // 获取参数
path = phantom.libraryPath,
command = require(path + '/module/command.js');// 参数module
/**
* phantomJs 全局异常监听
* @param msg
* @param trace
*/
phantom.onError = function (msg, trace) {
var msgStack = ['Convert ERROR: ' + msg];
if (trace && trace.length) {
msgStack.push('TRACE:');
trace.forEach(function (t) {
msgStack.push(' -> ' + (t.file || t.sourceURL) + ': ' + t.line + (t.function ? ' (in function ' + t.function + ')' : ''));
});
}
console.error(msgStack.join('\n'));
phantom.exit(1);
};
/**
* 参数
* @type {Command}
*/
var commandParams = command
.version('0.0.1')
.option('-s, --server', 'provide echarts convert http server')
.option('-p, --port <number>', 'change server port when add -s or --server', 9090)
.option('-o, --opt <json>', 'add the param of echarts method [ eChart.setOption(opt) ]')
.option('-t, --type <value>', 'provide file/base64 for image, default file', /^(file|base64)$/i, 'base64')
.option('-f, --outfile <path>', 'add output of the image file path')
.option('-w, --width <number>', 'change image width', '800')
.option('-h, --height <number>', 'change image height', '400')
.parse(system.args);
// ***********************************
// Echarts转换器
// ***********************************
function Convert(params) {
this.params = params || commandParams; // 参数命令
this.external = {
JQUERY3: path + '/script/jquery-3.2.1.min.js',
ECHARTS3: path + '/script/echarts.min.js',
ECHARTS_CHINA: path + '/script/china.js'
}; // 外部js
}
/**
* 初始化
*/
Convert.prototype.init = function () {
var params = this.params;
this.check(params);
if (params.server) {
this.server(params);
} else {
this.client(params);
}
};
/**
* 参数检查
* @param params
*/
Convert.prototype.check = function (params) {
if (undefined === params.server && undefined === params.opt) {
this.error("option argument missing -o, --opt <json>");
}
if (undefined !== params.opt) {
var isJson = this.checkJson(params.opt);
if (!isJson) {
this.error("--opt <json> args not json string");
}
}
if ('file' === params.type && undefined === params.outfile) {
this.createTmpDir();
}
};
/**
* 检查是否是json字符串
* @param value
* @returns {boolean}
*/
Convert.prototype.checkJson = function (value) {
var re = /^\{[\s\S]*\}$|^\[[\s\S]*\]$/;
// 类型为string
if (typeof value !== 'string') {
return false;
}
// 正则验证
if (!re.test(value)) {
return false;
}
// 是否能解析
try {
value = "\"" + value + "\"";
JSON.parse(value);
} catch (err) {
return false;
}
return true;
};
/**
* 创建临时目录,并指定输出路径
*/
Convert.prototype.createTmpDir = function () {
var fs = require('fs'); // 文件操作
var tmpDir = fs.workingDirectory + '/tmp';
// 临时目录是否存在且可写
if (!fs.exists(tmpDir)) {
if (!fs.makeDirectory(tmpDir)) {
this.error('Cannot make ' + tmpDir + ' directory\n');
}
}
this.params.outfile = tmpDir + "/" + new Date().getTime() + ".png";
};
/**
* 服务
* @param params
*/
Convert.prototype.server = function (params) {
var server = require('webserver').create(), // 服务端
convert = this;
var listen = server.listen(params.port, function (request, response) {
/**
* 输出
* @param data
* @param success
*/
function write(data, success, msg) {
response.statusCode = 200;
response.headers = {
'Cache': 'no-cache',
'Content-Type': 'application/json;charset=utf-8'
};
response.write(convert.serverResult(data, success, msg));
response.close();
}
//获取参数
var args = convert.serverGetArgs(request);
if (args.opt !== undefined) {
var check = convert.serverCheckAndSet(params, args);
if (check) {
convert.client(params, write);
} else {
write("", false, "failed to get image, please check parameter [opt] is a JSON");
}
} else {
write("", false, "failed to get image, missing parameter [opt]");
}
});
// 判断服务是否启动成功
if (!listen) {
this.error("could not create echarts-convert server listening on port " + params.port);
} else {
console.log("echarts-convert server start success. [pid]=" + system.pid);
}
};
/**
* 服务参数检查和赋值
* @param params
* @param args
* @returns {boolean}
*/
Convert.prototype.serverCheckAndSet = function (params, args) {
if (this.checkJson(args.opt)) {
params.opt = args.opt;
} else {
return false;
}
if (/^(file|base64)$/i.exec(args.type)) {
params.type = args.type;
}
if (!isNaN(args.width)) {
params.width = args.width;
}
if (!isNaN(args.height)) {
params.height = args.height;
}
return true;
};
/**
* 结果返回
* @param data
* @param success
* @param msg
*/
Convert.prototype.serverResult = function (data, success, msg) {
var result = {
code: success ? 1 : 0,
msg: undefined === msg ? success ? "success" : "failure" : msg,
data: data
};
return JSON.stringify(result);
};
/**
* 获取参数
* @param request
* @returns {{}}
*/
Convert.prototype.serverGetArgs = function (request) {
var args = {};
if ('GET' === request.method) {
var index = request.url.indexOf('?');
if (index !== -1) {
var getQuery = request.url.substr(index + 1);
args = this.serverParseArgs(getQuery);
}
} else if ('POST' === request.method) {
var postQuery = request.post;
args = this.serverParseArgs(postQuery);
}
return args;
};
/**
* 解析参数
* @param query 字符串
* @returns {{}} 对象
*/
Convert.prototype.serverParseArgs = function (query) {
var args = {},
pairs = query.split("&");
for (var i = 0; i < pairs.length; i++) {
var pos = pairs[i].indexOf('=');
if (pos === -1)
continue;
var key = pairs[i].substring(0, pos);
var value = pairs[i].substring(pos + 1);
// 中文解码,必须写两层
value = decodeURIComponent(decodeURIComponent(value));
args[key] = value;
}
return args;
};
/**
* 访问渲染
* @param params
* @param fn
*/
Convert.prototype.client = function (params, fn) {
var page = require('webpage').create(); // 客户端
var convert = this,
external = this.external,
render,
output;
/**
* 渲染
* @returns {*}
*/
render = function () {
switch (params.type) {
case 'file':
// 渲染图片
page.render(params.outfile);
return params.outfile;
case 'base64':
default:
var base64 = page.renderBase64('PNG');
return base64;
}
};
/**
* 输出
* @param content 内容
* @param success 是否成功
*/
output = function (content, success, msg) {
if (params.server) {
fn(content, success, msg);
page.close();
} else {
console.log(success ? "[SUCCESS]:" : "[ERROR]:" + content);
page.close();
convert.exit(params);// exit
}
};
/**
* 页面console监听
* @param msg
* @param lineNum
* @param sourceId
*/
page.onConsoleMessage = function (msg, lineNum, sourceId) {
console.log(msg);
};
/**
* 页面错误监听
* @param msg
* @param trace
*/
page.onError = function (msg, trace) {
output("", false, msg); // 失败,返回错误信息
};
// 空白页
page.open("about:blank", function (status) {
// 注入依赖js包
var hasJquery = page.injectJs(external.JQUERY3);
var hasEchart = page.injectJs(external.ECHARTS3);
var hasEchartChina = page.injectJs(external.ECHARTS_CHINA);
// 检查js是否引用成功
if (!hasJquery && !hasEchart) {
output("Could not found " + external.JQUERY3 + " or " + external.ECHARTS3, false);
}
// 创建echarts
page.evaluate(createEchartsDom, params);
// 定义剪切范围,如果定义则截取全屏
page.clipRect = {
top: 0,
left: 0,
width: params.width,
height: params.height
};
// 渲染
var result = render();
// 成功输出,返回图片或其他信息
output(result, true);
});
};
/**
* 创建eCharts Dom层
* @param params 参数
*/
function createEchartsDom(params) {
// 动态加载js,获取options数据
// 解决url的decode错误
params.opt=params.opt.replace("%25","%").replace("%23","#").replace("%26","&").replace("%3D","=");
$('<script>')
.attr('type', 'text/javascript')
.html('var options = ' + params.opt)
.appendTo(document.head);
// 取消动画,否则生成图片过快,会出现无数据
if (options !== undefined) {
options.animation = false;
}
// body背景设置为白色
$(document.body).css('backgroundColor', 'white');
// echarts容器
var container = $("<div>")
.attr('id', 'container')
.css({
width: params.width,
height: params.height
}).appendTo(document.body);
var eChart = echarts.init(container[0]);
eChart.setOption(options);
}
/**
* debug,将对象转成json对象
* @param obj
*/
Convert.prototype.debug = function (obj) {
console.log(JSON.stringify(obj, null, 4));
};
/**
* 错误信息打印并退出
* @param str 错误信息
*/
Convert.prototype.error = function (str) {
console.error("Error:" + str);
this.exit();
};
/**
* 退出,参数为空或是server时,不退出
* @param params 参数
*/
Convert.prototype.exit = function (params) {
if (undefined === params || undefined === params.server) {
phantom.exit();
}
};
// 构建,入口
new Convert(commandParams).init();
}(this, this.document));
/**
* Created by SaintLee on 2017/6/21.
*/
exports = module.exports = new Command();
exports.Command = Command;
exports.Option = Option;
/**
* 选项参数
* @param flags 选项标识
* @param description 描述信息
* @constructor 选项参数构造器
*/
function Option(flags, description) {
this.flags = flags; // 选项标识
this.required = ~flags.indexOf('<'); // 必须,包含<>
this.optional = ~flags.indexOf('['); // 可选,包含[]
this.bool = !~flags.indexOf('-no-'); // 禁用,包含-no-
flags = flags.split(/[ ,|]+/);
if (flags.length > 1 && !/^[[<]/.test(flags[1]))
this.short = flags.shift(); // 短选项
this.long = flags.shift();// 长选项
this.description = description || ''; // 描述信息
}
/**
* 参数名称,以长选项为名字
* @returns {XML|string}
*/
Option.prototype.name = function () {
return this.long
.replace('--', '')
.replace('no-', '');
};
/**
* 判断是短选项还是长选项
* @param arg
* @returns {boolean}
*/
Option.prototype.is = function (arg) {
return arg === this.short || arg === this.long;
};
/**
* 命令行
* @param name 命令行名称
* @constructor 命令行构造器
*/
function Command(name) {
this.options = []; // 选项集合
this._allowUnknownOption = false; // 是否允许未知参数
this._args = []; // 参数集合
this._name = name || ''; // 名称
}
Command.prototype.name = function (str) {
this._name = str;
return this;
};
/**
* 版本号
* @param str 版本号
* @param flags 选项
* @returns {*}
* @api public
*/
Command.prototype.version = function (str, flags) {
if (0 === arguments.length)
return this._version;
this._version = str;
flags = flags || '-V, --version';
this.option(flags, 'output the version number');
return this;
};
/**
* 使用说明
* @param str
* @returns {*}
* @api public
*/
Command.prototype.usage = function (str) {
var args = this._args.map(function (arg) {
return humanReadableArgName(arg);
});
var usage = '[options]'
+ (this._args.length ? ' ' + args.join(' ') : '');
if (0 === arguments.length)
return this._usage || usage;
this._usage = str;
return this;
};
/**
* 命令行选项赋值
* @param flags 选项参数
* @param description 描述
* @param fn
* @param defaultValue
* @returns {Command}
* @api public
*/
Command.prototype.option = function (flags, description, fn, defaultValue) {
var self = this
, option = new Option(flags, description)
, oname = option.name()
, name = camelcase(oname);
// 参数为三个
if (typeof fn !== 'function') {
if (fn instanceof RegExp) {
var regex = fn;
fn = function (val, def) {
var m = regex.exec(val);
return m ? m[0] : def;
}
} else {
defaultValue = fn;
fn = null;
}
}
// 为禁用--no-*,可选[optional],必选<required>设置默认值
if (false === option.bool || option.optional || option.required) {
if (false === option.bool)
defaultValue = true; // 默认为true
if (undefined !== defaultValue)
self[name] = defaultValue;
}
// 注册
this.options.push(option);
return this;
};
/**
* 解析参数
* @param argv 参数
* @returns {Command}
* @api public
*/
Command.prototype.parse = function (argv) {
// 存储原始参数
this.rawArgs = argv;
// 猜名字
this._name = this._name || argv[0];
// 解析参数
var parsed = this.parseOptions(this.normalize(argv.slice(1)));
var args = this.args = parsed.args;
var result = this.parseArgs(this.args, parsed.unknown);
return result;
};
/**
* 允许未知选项
* @param arg
* @returns {Command}
* @api public
*/
Command.prototype.allowUnknownOption = function (arg) {
this._allowUnknownOption = arguments.length === 0 || arg;
return this;
};
/**
* 规范化参数,主要处理多个短选项如:-xvf和长选项:--options=xxx
* @param args
* @returns {Array}
* @api private
*/
Command.prototype.normalize = function (args) {
var ret = []
, arg
, lastOpt
, index;
for (var i = 0, len = args.length; i < len; ++i) {
arg = args[i];
if (i > 0) {
lastOpt = this.optionFor(args[i - 1]);
}
if (arg === '--') {
ret = ret.concat(args.slice(i));
break;
} else if (lastOpt && lastOpt.required) {
ret.push(arg);
} else if (arg.length > 1 && '-' === arg[0] && '-' !== arg[1]) {
arg.slice(1).split('').forEach(function (c) {
ret.push('-' + c);
});
} else if (/^--/.test(arg) && ~(index = arg.indexOf('='))) {
ret.push(arg.slice(0, index), arg.slice(index + 1));
} else {
ret.push(arg);
}
}
return ret;
};
/**
* 解析参数
* @param argv
* @returns {{args: Array, unknown: Array}}
* @api private
*/
Command.prototype.parseOptions = function (argv) {
var args = []
, len = argv.length
, literal
, option
, arg;
var unknownOptions = [];
// 解析选项
for (var i = 0; i < len; ++i) {
arg = argv[i];
// 参数后为'-- xxx'时表示为文字而非参数
if ('--' === arg) {
literal = true;
continue;
}
if (literal) {
args.push(arg);
continue;
}
// 找到匹配的选项
option = this.optionFor(arg);
// 定义
if (option) {
if (option.required) { // 选项必须
arg = argv[++i];
if (undefined === arg || null === arg)
return this.optionMissingArgument(option);
this[option.name()] = arg;
} else if (option.optional) { // 选项可选
arg = argv[i + 1];
if (undefined === arg || null === arg || ('-' === arg[0] && '-' !== arg)) {
arg = null;
} else {
++i;
}
this[option.name()] = arg;
} else {
if ('version' !== option.name()) {
this[option.name()] = true;
} else {
this.outputVersion();
this.exit();
}
}
args.push(arg);
continue;
}
// 未知选项
if (arg.length > 1 && '-' === arg[0]) {
unknownOptions.push(arg);
if (argv[i + 1] && '-' !== argv[i + 1][0]) {
unknownOptions.push(argv[++i]);
}
continue;
}
args.push(arg);
}
return {args: args, unknown: unknownOptions};
};
/**
* 参数匹配选项
* @param arg
* @returns {*}
* @api private
*/
Command.prototype.optionFor = function (arg) {
for (var i = 0, len = this.options.length; i < len; ++i) {
if (this.options[i].is(arg)) {
return this.options[i];
}
}
};
/**
* 解析参数
* @param args
* @param unknown
* @returns {Command}
*/
Command.prototype.parseArgs = function (args, unknown) {
var name;
if (args.length) {
name = args[0];
} else {
outputHelpIfNecessary(this, unknown);
if (unknown.length > 0) {
this.unknownOption(unknown[0]);
}
}
return this;
};
/**
* 未知选项
* @param flag
*/
Command.prototype.unknownOption = function (flag) {
if (this._allowUnknownOption)
return;
console.error();
console.error("error: unknown option `%s'", flag);
console.error();
this.exit();
};
Command.prototype.optionMissingArgument = function (option, flag) {
console.error();
if (flag) {
console.error("error: option `%s' argument missing, got `%s'", option.flags, flag);
} else {
console.error("error: option `%s' argument missing", option.flags);
}
console.error();
this.exit();
};
Command.prototype.outputVersion = function () {
console.log(this._version);
};
Command.prototype.outputHelp = function (cb) {
if (!cb) {
cb = function (passthru) {
return passthru;
}
}
console.log(cb(this.helpInformation()));
};
Command.prototype.helpInformation = function () {
var desc = [];
if (this._description) {
desc = [
' ' + this._description
, ''
];
}
var cmdName = this._name;
if (this._alias) {
cmdName = cmdName + '|' + this._alias;
}
var usage = [
''
, ' Usage: ' + cmdName + ' ' + this.usage()
, ''
];
var options = [
' Options:'
, ''
, '' + this.optionHelp().replace(/^/gm, ' ')
, ''
, ''
];
return usage
.concat(desc)
.concat(options)
.join('\n');
};
Command.prototype.optionHelp = function () {
var width = this.largestOptionLength();
return [pad('-h, --help', width) + ' ' + 'output usage information']
.concat(this.options.map(function (option) {
return pad(option.flags, width) + ' ' + option.description;
}))
.join('\n');
};
Command.prototype.largestOptionLength = function () {
return this.options.reduce(function (max, option) {
return Math.max(max, option.flags.length);
}, 0);
};
Command.prototype.exit = function () {
phantom.exit();
};
function camelcase(flag) {
return flag.split('-').reduce(function (str, word) {
return str + word[0].toUpperCase() + word.slice(1);
});
}
function outputHelpIfNecessary(cmd, options) {
options = options || [];
for (var i = 0; i < options.length; i++) {
if (options[i] === '--help' || options[i] === '-h') {
cmd.outputHelp();
cmd.exit();
}
}
}
function humanReadableArgName(arg) {
var nameOutput = arg.name + (arg.variadic === true ? '...' : '');
return arg.required
? '<' + nameOutput + '>'
: '[' + nameOutput + ']'
}
function pad(str, width) {
var len = Math.max(0, width - str.length);
return str + new Array(len + 1).join(' ');
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment